diff options
Diffstat (limited to 'drivers/acpi/glue.c')
| -rw-r--r-- | drivers/acpi/glue.c | 465 |
1 files changed, 232 insertions, 233 deletions
diff --git a/drivers/acpi/glue.c b/drivers/acpi/glue.c index 3937adf4e5e..f774c65ecb8 100644 --- a/drivers/acpi/glue.c +++ b/drivers/acpi/glue.c @@ -6,37 +6,47 @@ * * This file is released under the GPLv2. */ +#include <linux/export.h> #include <linux/init.h> #include <linux/list.h> #include <linux/device.h> +#include <linux/slab.h> #include <linux/rwsem.h> #include <linux/acpi.h> +#include "internal.h" + #define ACPI_GLUE_DEBUG 0 #if ACPI_GLUE_DEBUG -#define DBG(x...) printk(PREFIX x) +#define DBG(fmt, ...) \ + printk(KERN_DEBUG PREFIX fmt, ##__VA_ARGS__) #else -#define DBG(x...) +#define DBG(fmt, ...) \ +do { \ + if (0) \ + printk(KERN_DEBUG PREFIX fmt, ##__VA_ARGS__); \ +} while (0) #endif static LIST_HEAD(bus_type_list); static DECLARE_RWSEM(bus_type_sem); +#define PHYSICAL_NODE_STRING "physical_node" +#define PHYSICAL_NODE_NAME_SIZE (sizeof(PHYSICAL_NODE_STRING) + 10) + int register_acpi_bus_type(struct acpi_bus_type *type) { if (acpi_disabled) return -ENODEV; - if (type && type->bus && type->find_device) { + if (type && type->match && type->find_companion) { down_write(&bus_type_sem); list_add_tail(&type->list, &bus_type_list); up_write(&bus_type_sem); - printk(KERN_INFO PREFIX "bus type %s registered\n", - type->bus->name); + printk(KERN_INFO PREFIX "bus type %s registered\n", type->name); return 0; } return -ENODEV; } - -EXPORT_SYMBOL(register_acpi_bus_type); +EXPORT_SYMBOL_GPL(register_acpi_bus_type); int unregister_acpi_bus_type(struct acpi_bus_type *type) { @@ -46,22 +56,21 @@ int unregister_acpi_bus_type(struct acpi_bus_type *type) down_write(&bus_type_sem); list_del_init(&type->list); up_write(&bus_type_sem); - printk(KERN_INFO PREFIX "ACPI bus type %s unregistered\n", - type->bus->name); + printk(KERN_INFO PREFIX "bus type %s unregistered\n", + type->name); return 0; } return -ENODEV; } +EXPORT_SYMBOL_GPL(unregister_acpi_bus_type); -EXPORT_SYMBOL(unregister_acpi_bus_type); - -static struct acpi_bus_type *acpi_get_bus_type(struct bus_type *type) +static struct acpi_bus_type *acpi_get_bus_type(struct device *dev) { 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) { + if (tmp->match(dev)) { ret = tmp; break; } @@ -70,268 +79,250 @@ static struct acpi_bus_type *acpi_get_bus_type(struct bus_type *type) return ret; } -static int acpi_find_bridge_device(struct device *dev, acpi_handle * handle) +#define FIND_CHILD_MIN_SCORE 1 +#define FIND_CHILD_MAX_SCORE 2 + +static int find_child_checks(struct acpi_device *adev, bool check_children) { - struct acpi_bus_type *tmp; - int ret = -ENODEV; + bool sta_present = true; + unsigned long long sta; + acpi_status status; - 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; -} + status = acpi_evaluate_integer(adev->handle, "_STA", NULL, &sta); + if (status == AE_NOT_FOUND) + sta_present = false; + else if (ACPI_FAILURE(status) || !(sta & ACPI_STA_DEVICE_ENABLED)) + return -ENODEV; + + if (check_children && list_empty(&adev->children)) + return -ENODEV; -/* 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; -}; + return sta_present ? FIND_CHILD_MAX_SCORE : FIND_CHILD_MIN_SCORE; +} -static acpi_status -do_root_bridge_busnr_callback(struct acpi_resource *resource, void *data) +struct acpi_device *acpi_find_child_device(struct acpi_device *parent, + u64 address, bool check_children) { - unsigned long *busnr = (unsigned long *)data; - struct acpi_resource_address64 address; + struct acpi_device *adev, *ret = NULL; + int ret_score = 0; - if (resource->id != ACPI_RSTYPE_ADDRESS16 && - resource->id != ACPI_RSTYPE_ADDRESS32 && - resource->id != ACPI_RSTYPE_ADDRESS64) - return AE_OK; + if (!parent) + return NULL; - acpi_resource_to_address64(resource, &address); - if ((address.address_length > 0) && - (address.resource_type == ACPI_BUS_NUMBER_RANGE)) - *busnr = address.min_address_range; + list_for_each_entry(adev, &parent->children, node) { + unsigned long long addr; + acpi_status status; + int score; - return AE_OK; -} + status = acpi_evaluate_integer(adev->handle, METHOD_NAME__ADR, + NULL, &addr); + if (ACPI_FAILURE(status) || addr != address) + continue; -static int get_root_bridge_busnr(acpi_handle handle) -{ - acpi_status status; - unsigned long 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, - &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; + if (!ret) { + /* This is the first matching object. Save it. */ + ret = adev; + continue; + } + /* + * There is more than one matching device object with the same + * _ADR value. That really is unexpected, so we are kind of + * beyond the scope of the spec here. We have to choose which + * one to return, though. + * + * First, check if the previously found object is good enough + * and return it if so. Second, do the same for the object that + * we've just found. + */ + if (!ret_score) { + ret_score = find_child_checks(ret, check_children); + if (ret_score == FIND_CHILD_MAX_SCORE) + return ret; + } + score = find_child_checks(adev, check_children); + if (score == FIND_CHILD_MAX_SCORE) { + return adev; + } else if (score > ret_score) { + ret = adev; + ret_score = score; + } } - exit: - acpi_os_free(buffer.pointer); - return (int)bbn; + return ret; } +EXPORT_SYMBOL_GPL(acpi_find_child_device); -static acpi_status -find_pci_rootbridge(acpi_handle handle, u32 lvl, void *context, void **rv) +static void acpi_physnode_link_name(char *buf, unsigned int node_id) { - 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); + if (node_id > 0) + snprintf(buf, PHYSICAL_NODE_NAME_SIZE, + PHYSICAL_NODE_STRING "%u", node_id); + else + strcpy(buf, PHYSICAL_NODE_STRING); +} - status = acpi_evaluate_integer(handle, METHOD_NAME__SEG, NULL, &seg); - if (status == AE_NOT_FOUND) { - /* Assume seg = 0 */ - status = AE_OK; - seg = 0; +int acpi_bind_one(struct device *dev, struct acpi_device *acpi_dev) +{ + struct acpi_device_physical_node *physical_node, *pn; + char physical_node_name[PHYSICAL_NODE_NAME_SIZE]; + struct list_head *physnode_list; + unsigned int node_id; + int retval = -EINVAL; + + if (ACPI_COMPANION(dev)) { + if (acpi_dev) { + dev_warn(dev, "ACPI companion already set\n"); + return -EINVAL; + } else { + acpi_dev = ACPI_COMPANION(dev); + } } - if (ACPI_FAILURE(status)) { - status = AE_CTRL_DEPTH; - goto exit; + if (!acpi_dev) + return -EINVAL; + + get_device(&acpi_dev->dev); + get_device(dev); + physical_node = kzalloc(sizeof(*physical_node), GFP_KERNEL); + if (!physical_node) { + retval = -ENOMEM; + goto err; } - 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; + mutex_lock(&acpi_dev->physical_node_lock); + + /* + * Keep the list sorted by node_id so that the IDs of removed nodes can + * be recycled easily. + */ + physnode_list = &acpi_dev->physical_node_list; + node_id = 0; + list_for_each_entry(pn, &acpi_dev->physical_node_list, node) { + /* Sanity check. */ + if (pn->dev == dev) { + mutex_unlock(&acpi_dev->physical_node_lock); + + dev_warn(dev, "Already associated with ACPI node\n"); + kfree(physical_node); + if (ACPI_COMPANION(dev) != acpi_dev) + goto err; + + put_device(dev); + put_device(&acpi_dev->dev); + return 0; + } + if (pn->node_id == node_id) { + physnode_list = &pn->node; + node_id++; + } } - 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 }; + physical_node->node_id = node_id; + physical_node->dev = dev; + list_add(&physical_node->node, physnode_list); + acpi_dev->physical_node_count++; - acpi_get_devices(PCI_ROOT_HID_STRING, find_pci_rootbridge, &find, NULL); - return find.handle; -} + if (!ACPI_COMPANION(dev)) + ACPI_COMPANION_SET(dev, acpi_dev); -/* Get device's handler per its address under its parent */ -struct acpi_find_child { - acpi_handle handle; - acpi_integer address; -}; + acpi_physnode_link_name(physical_node_name, node_id); + retval = sysfs_create_link(&acpi_dev->dev.kobj, &dev->kobj, + physical_node_name); + if (retval) + dev_err(&acpi_dev->dev, "Failed to create link %s (%d)\n", + physical_node_name, retval); -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; -} + retval = sysfs_create_link(&dev->kobj, &acpi_dev->dev.kobj, + "firmware_node"); + if (retval) + dev_err(dev, "Failed to create link firmware_node (%d)\n", + retval); -acpi_handle acpi_get_child(acpi_handle parent, acpi_integer address) -{ - struct acpi_find_child find = { NULL, address }; + mutex_unlock(&acpi_dev->physical_node_lock); - if (!parent) - return NULL; - acpi_walk_namespace(ACPI_TYPE_DEVICE, parent, - 1, do_acpi_find_child, &find, NULL); - return find.handle; -} + if (acpi_dev->wakeup.flags.valid) + device_set_wakeup_capable(dev, true); -EXPORT_SYMBOL(acpi_get_child); + return 0; -/* Link ACPI devices with physical devices */ -static void acpi_glue_data_handler(acpi_handle handle, - u32 function, void *context) -{ - /* we provide an empty handler */ + err: + ACPI_COMPANION_SET(dev, NULL); + put_device(dev); + put_device(&acpi_dev->dev); + return retval; } +EXPORT_SYMBOL_GPL(acpi_bind_one); -/* Note: a success call will increase reference count by one */ -struct device *acpi_get_physical_device(acpi_handle handle) +int acpi_unbind_one(struct device *dev) { - acpi_status status; - struct device *dev; + struct acpi_device *acpi_dev = ACPI_COMPANION(dev); + struct acpi_device_physical_node *entry; - status = acpi_get_data(handle, acpi_glue_data_handler, (void **)&dev); - if (ACPI_SUCCESS(status)) - return get_device(dev); - return NULL; -} + if (!acpi_dev) + return 0; -EXPORT_SYMBOL(acpi_get_physical_device); + mutex_lock(&acpi_dev->physical_node_lock); -static int acpi_bind_one(struct device *dev, acpi_handle handle) -{ - acpi_status status; + list_for_each_entry(entry, &acpi_dev->physical_node_list, node) + if (entry->dev == dev) { + char physnode_name[PHYSICAL_NODE_NAME_SIZE]; - 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; + list_del(&entry->node); + acpi_dev->physical_node_count--; - return 0; -} + acpi_physnode_link_name(physnode_name, entry->node_id); + sysfs_remove_link(&acpi_dev->dev.kobj, physnode_name); + sysfs_remove_link(&dev->kobj, "firmware_node"); + ACPI_COMPANION_SET(dev, NULL); + /* Drop references taken by acpi_bind_one(). */ + put_device(dev); + put_device(&acpi_dev->dev); + kfree(entry); + break; + } -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); - } + mutex_unlock(&acpi_dev->physical_node_lock); return 0; } +EXPORT_SYMBOL_GPL(acpi_unbind_one); 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) { - DBG("No ACPI bus support for %s\n", dev->bus_id); - ret = -EINVAL; - goto end; + struct acpi_bus_type *type = acpi_get_bus_type(dev); + struct acpi_device *adev; + int ret; + + ret = acpi_bind_one(dev, NULL); + if (ret && type) { + struct acpi_device *adev; + + adev = type->find_companion(dev); + if (!adev) { + DBG("Unable to get handle for %s\n", dev_name(dev)); + ret = -ENODEV; + goto out; + } + ret = acpi_bind_one(dev, adev); + if (ret) + goto out; } - if ((ret = type->find_device(dev, &handle)) != 0) - DBG("Can't get handler for %s\n", dev->bus_id); - end: - if (!ret) - acpi_bind_one(dev, handle); + adev = ACPI_COMPANION(dev); + if (!adev) + goto out; + + if (type && type->setup) + type->setup(dev); + else if (adev->handler && adev->handler->bind) + adev->handler->bind(dev); + out: #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); + acpi_get_name(ACPI_HANDLE(dev), ACPI_FULL_PATHNAME, &buffer); + DBG("Device %s -> %s\n", dev_name(dev), (char *)buffer.pointer); + kfree(buffer.pointer); } else - DBG("Device %s -> No ACPI support\n", dev->bus_id); + DBG("Device %s -> No ACPI support\n", dev_name(dev)); #endif return ret; @@ -339,14 +330,24 @@ static int acpi_platform_notify(struct device *dev) static int acpi_platform_notify_remove(struct device *dev) { + struct acpi_device *adev = ACPI_COMPANION(dev); + struct acpi_bus_type *type; + + if (!adev) + return 0; + + type = acpi_get_bus_type(dev); + if (type && type->cleanup) + type->cleanup(dev); + else if (adev->handler && adev->handler->unbind) + adev->handler->unbind(dev); + acpi_unbind_one(dev); return 0; } -static int __init init_acpi_device_notify(void) +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; @@ -355,5 +356,3 @@ static int __init init_acpi_device_notify(void) platform_notify_remove = acpi_platform_notify_remove; return 0; } - -arch_initcall(init_acpi_device_notify); |
