[ACPI] Bind PCI devices with ACPI devices
authorDavid Shaohua Li <shaohua.li@intel.com>
Fri, 18 Mar 2005 23:45:35 +0000 (18:45 -0500)
committerLen Brown <len.brown@intel.com>
Tue, 12 Jul 2005 03:28:24 +0000 (23:28 -0400)
Implement the framework for binding physical devices
with ACPI devices. A physical bus like PCI bus
should create a 'acpi_bus_type', with:

.find_device:
        For device which has parent such as normal PCI devices.

.find_bridge:
        It's for special devices, such as PCI root bridge
or IDE controller.  Such devices generally haven't a
parent or ->bus. We use the special method
to get an ACPI handle.

Uses new field in struct device: firmware_data

http://bugzilla.kernel.org/show_bug.cgi?id=4277

Signed-off-by: David Shaohua Li <shaohua.li@intel.com>
Signed-off-by: Len Brown <len.brown@intel.com>
drivers/acpi/Makefile
drivers/acpi/glue.c [new file with mode: 0644]
drivers/acpi/ibm_acpi.c
include/acpi/acpi_bus.h
include/linux/device.h

index 24eb397e17b8e69a01fc8d3cbd2cba9ed9e0c9c8..ad67e8f61e6c5ba5db2976155e29ba64d7b35646 100644 (file)
@@ -36,7 +36,7 @@ processor-objs        += processor_perflib.o
 endif
 
 obj-$(CONFIG_ACPI_BUS)         += sleep/
 endif
 
 obj-$(CONFIG_ACPI_BUS)         += sleep/
-obj-$(CONFIG_ACPI_BUS)         += bus.o
+obj-$(CONFIG_ACPI_BUS)         += bus.o glue.o
 obj-$(CONFIG_ACPI_AC)          += ac.o
 obj-$(CONFIG_ACPI_BATTERY)     += battery.o
 obj-$(CONFIG_ACPI_BUTTON)      += button.o
 obj-$(CONFIG_ACPI_AC)          += ac.o
 obj-$(CONFIG_ACPI_BATTERY)     += battery.o
 obj-$(CONFIG_ACPI_BUTTON)      += button.o
diff --git a/drivers/acpi/glue.c b/drivers/acpi/glue.c
new file mode 100644 (file)
index 0000000..b6d2045
--- /dev/null
@@ -0,0 +1,362 @@
+/*
+ * Link physical devices with ACPI devices support
+ *
+ * Copyright (c) 2005 David Shaohua Li <shaohua.li@intel.com>
+ * Copyright (c) 2005 Intel Corp.
+ *
+ * This file is released under the GPLv2.
+ */
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/device.h>
+#include <linux/rwsem.h>
+#include <linux/acpi.h>
+
+#define ACPI_GLUE_DEBUG        0
+#if ACPI_GLUE_DEBUG
+#define DBG(x...) printk(PREFIX x)
+#else
+#define DBG(x...)
+#endif
+static LIST_HEAD(bus_type_list);
+static DECLARE_RWSEM(bus_type_sem);
+
+int register_acpi_bus_type(struct acpi_bus_type *type)
+{
+       if (acpi_disabled)
+               return -ENODEV;
+       if (type && type->bus && type->find_device) {
+               down_write(&bus_type_sem);
+               list_add_tail(&type->list, &bus_type_list);
+               up_write(&bus_type_sem);
+               DBG("ACPI bus type %s registered\n", type->bus->name);
+               return 0;
+       }
+       return -ENODEV;
+}
+
+EXPORT_SYMBOL(register_acpi_bus_type);
+
+int unregister_acpi_bus_type(struct acpi_bus_type *type)
+{
+       if (acpi_disabled)
+               return 0;
+       if (type) {
+               down_write(&bus_type_sem);
+               list_del_init(&type->list);
+               up_write(&bus_type_sem);
+               DBG("ACPI bus type %s unregistered\n", type->bus->name);
+               return 0;
+       }
+       return -ENODEV;
+}
+
+EXPORT_SYMBOL(unregister_acpi_bus_type);
+
+static struct acpi_bus_type *acpi_get_bus_type(struct bus_type *type)
+{
+       struct acpi_bus_type *tmp, *ret = NULL;
+
+       down_read(&bus_type_sem);
+       list_for_each_entry(tmp, &bus_type_list, list) {
+               if (tmp->bus == type) {
+                       ret = tmp;
+                       break;
+               }
+       }
+       up_read(&bus_type_sem);
+       return ret;
+}
+
+static int acpi_find_bridge_device(struct device *dev, acpi_handle * handle)
+{
+       struct acpi_bus_type *tmp;
+       int ret = -ENODEV;
+
+       down_read(&bus_type_sem);
+       list_for_each_entry(tmp, &bus_type_list, list) {
+               if (tmp->find_bridge && !tmp->find_bridge(dev, handle)) {
+                       ret = 0;
+                       break;
+               }
+       }
+       up_read(&bus_type_sem);
+       return ret;
+}
+
+/* Get PCI root bridge's handle from its segment and bus number */
+struct acpi_find_pci_root {
+       unsigned int seg;
+       unsigned int bus;
+       acpi_handle handle;
+};
+
+static acpi_status
+do_root_bridge_busnr_callback(struct acpi_resource *resource, void *data)
+{
+       int *busnr = (int *)data;
+       struct acpi_resource_address64 address;
+
+       if (resource->id != ACPI_RSTYPE_ADDRESS16 &&
+           resource->id != ACPI_RSTYPE_ADDRESS32 &&
+           resource->id != ACPI_RSTYPE_ADDRESS64)
+               return AE_OK;
+
+       acpi_resource_to_address64(resource, &address);
+       if ((address.address_length > 0) &&
+           (address.resource_type == ACPI_BUS_NUMBER_RANGE))
+               *busnr = address.min_address_range;
+
+       return AE_OK;
+}
+
+static int get_root_bridge_busnr(acpi_handle handle)
+{
+       acpi_status status;
+       int bus, bbn;
+       struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+
+       acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer);
+
+       status = acpi_evaluate_integer(handle, METHOD_NAME__BBN, NULL,
+                                      (unsigned long *)&bbn);
+       if (status == AE_NOT_FOUND) {
+               /* Assume bus = 0 */
+               printk(KERN_INFO PREFIX
+                      "Assume root bridge [%s] bus is 0\n",
+                      (char *)buffer.pointer);
+               status = AE_OK;
+               bbn = 0;
+       }
+       if (ACPI_FAILURE(status)) {
+               bbn = -ENODEV;
+               goto exit;
+       }
+       if (bbn > 0)
+               goto exit;
+
+       /* _BBN in some systems return 0 for all root bridges */
+       bus = -1;
+       status = acpi_walk_resources(handle, METHOD_NAME__CRS,
+                                    do_root_bridge_busnr_callback, &bus);
+       /* If _CRS failed, we just use _BBN */
+       if (ACPI_FAILURE(status) || (bus == -1))
+               goto exit;
+       /* We select _CRS */
+       if (bbn != bus) {
+               printk(KERN_INFO PREFIX
+                      "_BBN and _CRS returns different value for %s. Select _CRS\n",
+                      (char *)buffer.pointer);
+               bbn = bus;
+       }
+      exit:
+       acpi_os_free(buffer.pointer);
+       return bbn;
+}
+
+static acpi_status
+find_pci_rootbridge(acpi_handle handle, u32 lvl, void *context, void **rv)
+{
+       struct acpi_find_pci_root *find = (struct acpi_find_pci_root *)context;
+       unsigned long seg, bus;
+       acpi_status status;
+       int tmp;
+       struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+
+       acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer);
+
+       status = acpi_evaluate_integer(handle, METHOD_NAME__SEG, NULL, &seg);
+       if (status == AE_NOT_FOUND) {
+               /* Assume seg = 0 */
+               printk(KERN_INFO PREFIX
+                      "Assume root bridge [%s] segment is 0\n",
+                      (char *)buffer.pointer);
+               status = AE_OK;
+               seg = 0;
+       }
+       if (ACPI_FAILURE(status)) {
+               status = AE_CTRL_DEPTH;
+               goto exit;
+       }
+
+       tmp = get_root_bridge_busnr(handle);
+       if (tmp < 0) {
+               printk(KERN_ERR PREFIX
+                      "Find root bridge failed for %s\n",
+                      (char *)buffer.pointer);
+               status = AE_CTRL_DEPTH;
+               goto exit;
+       }
+       bus = tmp;
+
+       if (seg == find->seg && bus == find->bus)
+               find->handle = handle;
+       status = AE_OK;
+      exit:
+       acpi_os_free(buffer.pointer);
+       return status;
+}
+
+acpi_handle acpi_get_pci_rootbridge_handle(unsigned int seg, unsigned int bus)
+{
+       struct acpi_find_pci_root find = { seg, bus, NULL };
+
+       acpi_get_devices(PCI_ROOT_HID_STRING, find_pci_rootbridge, &find, NULL);
+       return find.handle;
+}
+
+/* Get device's handler per its address under its parent */
+struct acpi_find_child {
+       acpi_handle handle;
+       acpi_integer address;
+};
+
+static acpi_status
+do_acpi_find_child(acpi_handle handle, u32 lvl, void *context, void **rv)
+{
+       acpi_status status;
+       struct acpi_device_info *info;
+       struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+       struct acpi_find_child *find = (struct acpi_find_child *)context;
+
+       status = acpi_get_object_info(handle, &buffer);
+       if (ACPI_SUCCESS(status)) {
+               info = buffer.pointer;
+               if (info->address == find->address)
+                       find->handle = handle;
+               acpi_os_free(buffer.pointer);
+       }
+       return AE_OK;
+}
+
+acpi_handle acpi_get_child(acpi_handle parent, acpi_integer address)
+{
+       struct acpi_find_child find = { NULL, address };
+
+       if (!parent)
+               return NULL;
+       acpi_walk_namespace(ACPI_TYPE_DEVICE, parent,
+                           1, do_acpi_find_child, &find, NULL);
+       return find.handle;
+}
+
+EXPORT_SYMBOL(acpi_get_child);
+
+/* Link ACPI devices with physical devices */
+static void acpi_glue_data_handler(acpi_handle handle,
+                                  u32 function, void *context)
+{
+       /* we provide an empty handler */
+}
+
+/* Note: a success call will increase reference count by one */
+struct device *acpi_get_physical_device(acpi_handle handle)
+{
+       acpi_status status;
+       struct device *dev;
+
+       status = acpi_get_data(handle, acpi_glue_data_handler, (void **)&dev);
+       if (ACPI_SUCCESS(status))
+               return get_device(dev);
+       return NULL;
+}
+
+EXPORT_SYMBOL(acpi_get_physical_device);
+
+static int acpi_bind_one(struct device *dev, acpi_handle handle)
+{
+       acpi_status status;
+
+       if (dev->firmware_data) {
+               printk(KERN_WARNING PREFIX
+                      "Drivers changed 'firmware_data' for %s\n", dev->bus_id);
+               return -EINVAL;
+       }
+       get_device(dev);
+       status = acpi_attach_data(handle, acpi_glue_data_handler, dev);
+       if (ACPI_FAILURE(status)) {
+               put_device(dev);
+               return -EINVAL;
+       }
+       dev->firmware_data = handle;
+
+       return 0;
+}
+
+static int acpi_unbind_one(struct device *dev)
+{
+       if (!dev->firmware_data)
+               return 0;
+       if (dev == acpi_get_physical_device(dev->firmware_data)) {
+               /* acpi_get_physical_device increase refcnt by one */
+               put_device(dev);
+               acpi_detach_data(dev->firmware_data, acpi_glue_data_handler);
+               dev->firmware_data = NULL;
+               /* acpi_bind_one increase refcnt by one */
+               put_device(dev);
+       } else {
+               printk(KERN_ERR PREFIX
+                      "Oops, 'firmware_data' corrupt for %s\n", dev->bus_id);
+       }
+       return 0;
+}
+
+static int acpi_platform_notify(struct device *dev)
+{
+       struct acpi_bus_type *type;
+       acpi_handle handle;
+       int ret = -EINVAL;
+
+       if (!dev->bus || !dev->parent) {
+               /* bridge devices genernally haven't bus or parent */
+               ret = acpi_find_bridge_device(dev, &handle);
+               goto end;
+       }
+       type = acpi_get_bus_type(dev->bus);
+       if (!type) {
+               printk(KERN_INFO PREFIX "No ACPI bus support for %s\n",
+                      dev->bus_id);
+               ret = -EINVAL;
+               goto end;
+       }
+       if ((ret = type->find_device(dev, &handle)) != 0)
+               printk(KERN_INFO PREFIX "Can't get handler for %s\n",
+                      dev->bus_id);
+      end:
+       if (!ret)
+               acpi_bind_one(dev, handle);
+
+#if ACPI_GLUE_DEBUG
+       if (!ret) {
+               struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+
+               acpi_get_name(dev->firmware_data, ACPI_FULL_PATHNAME, &buffer);
+               DBG("Device %s -> %s\n", dev->bus_id, (char *)buffer.pointer);
+               acpi_os_free(buffer.pointer);
+       } else
+               DBG("Device %s -> No ACPI support\n", dev->bus_id);
+#endif
+
+       return ret;
+}
+
+static int acpi_platform_notify_remove(struct device *dev)
+{
+       acpi_unbind_one(dev);
+       return 0;
+}
+
+static int __init init_acpi_device_notify(void)
+{
+       if (acpi_disabled)
+               return 0;
+       if (platform_notify || platform_notify_remove) {
+               printk(KERN_ERR PREFIX "Can't use platform_notify\n");
+               return 0;
+       }
+       platform_notify = acpi_platform_notify;
+       platform_notify_remove = acpi_platform_notify_remove;
+       return 0;
+}
+
+arch_initcall(init_acpi_device_notify);
index 6c8291c3e774afbe046c2d96eaee2d4ac2961fb2..ad85e10001f461f81710bd33656a39ef086e44fd 100644 (file)
@@ -1025,7 +1025,7 @@ static int setup_notify(struct ibm_struct *ibm)
        return 0;
 }
 
        return 0;
 }
 
-static int device_add(struct acpi_device *device)
+static int ibmacpi_device_add(struct acpi_device *device)
 {
        return 0;
 }
 {
        return 0;
 }
@@ -1043,7 +1043,7 @@ static int register_driver(struct ibm_struct *ibm)
        memset(ibm->driver, 0, sizeof(struct acpi_driver));
        sprintf(ibm->driver->name, "%s/%s", IBM_NAME, ibm->name);
        ibm->driver->ids = ibm->hid;
        memset(ibm->driver, 0, sizeof(struct acpi_driver));
        sprintf(ibm->driver->name, "%s/%s", IBM_NAME, ibm->name);
        ibm->driver->ids = ibm->hid;
-       ibm->driver->ops.add = &device_add;
+       ibm->driver->ops.add = &ibmacpi_device_add;
 
        ret = acpi_bus_register_driver(ibm->driver);
        if (ret < 0) {
 
        ret = acpi_bus_register_driver(ibm->driver);
        if (ret < 0) {
index c627bc408a6b11f876c7a8a0d392a5f7e5056b4a..53b821d7b8a838c16dea2fe10515f0f7630f9c65 100644 (file)
@@ -336,6 +336,27 @@ int acpi_match_ids (struct acpi_device     *device, char   *ids);
 int acpi_create_dir(struct acpi_device *);
 void acpi_remove_dir(struct acpi_device *);
 
 int acpi_create_dir(struct acpi_device *);
 void acpi_remove_dir(struct acpi_device *);
 
+
+/*
+ * Bind physical devices with ACPI devices
+ */
+#include <linux/device.h>
+struct acpi_bus_type {
+       struct list_head        list;
+       struct bus_type         *bus;
+       /* For general devices under the bus*/
+       int (*find_device)(struct device *, acpi_handle*);
+       /* For bridges, such as PCI root bridge, IDE controller */
+       int (*find_bridge)(struct device *, acpi_handle *);
+};
+int register_acpi_bus_type(struct acpi_bus_type *);
+int unregister_acpi_bus_type(struct acpi_bus_type *);
+struct device *acpi_get_physical_device(acpi_handle);
+/* helper */
+acpi_handle acpi_get_child(acpi_handle, acpi_integer);
+acpi_handle acpi_get_pci_rootbridge_handle(unsigned int, unsigned int);
+#define DEVICE_ACPI_HANDLE(dev) ((acpi_handle)((dev)->firmware_data))
+
 #endif /*CONFIG_ACPI_BUS*/
 
 #endif /*__ACPI_BUS_H__*/
 #endif /*CONFIG_ACPI_BUS*/
 
 #endif /*__ACPI_BUS_H__*/
index df94c0de53f2b6b0d91c56142838766db5fd0a3b..de2d6fe349de3c593da0754b8d90b1d01952f07e 100644 (file)
@@ -269,8 +269,10 @@ struct device {
        struct device_driver *driver;   /* which driver has allocated this
                                           device */
        void            *driver_data;   /* data private to the driver */
        struct device_driver *driver;   /* which driver has allocated this
                                           device */
        void            *driver_data;   /* data private to the driver */
-       void            *platform_data; /* Platform specific data (e.g. ACPI,
-                                          BIOS data relevant to device) */
+       void            *platform_data; /* Platform specific data, device
+                                          core doesn't touch it */
+       void            *firmware_data; /* Firmware specific data (e.g. ACPI,
+                                          BIOS data),reserved for device core*/
        struct dev_pm_info      power;
 
        u64             *dma_mask;      /* dma mask (if dma'able device) */
        struct dev_pm_info      power;
 
        u64             *dma_mask;      /* dma mask (if dma'able device) */