diff options
Diffstat (limited to 'drivers/acpi/glue.c')
| -rw-r--r-- | drivers/acpi/glue.c | 425 |
1 files changed, 268 insertions, 157 deletions
diff --git a/drivers/acpi/glue.c b/drivers/acpi/glue.c index f68095756fb7..a194f30876c5 100644 --- a/drivers/acpi/glue.c +++ b/drivers/acpi/glue.c @@ -1,11 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * 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. */ + +#define pr_fmt(fmt) "ACPI: " fmt + +#include <linux/acpi_iort.h> #include <linux/export.h> #include <linux/init.h> #include <linux/list.h> @@ -13,34 +16,28 @@ #include <linux/slab.h> #include <linux/rwsem.h> #include <linux/acpi.h> +#include <linux/dma-mapping.h> +#include <linux/pci.h> +#include <linux/pci-acpi.h> +#include <linux/platform_device.h> #include "internal.h" -#define ACPI_GLUE_DEBUG 0 -#if ACPI_GLUE_DEBUG -#define DBG(fmt, ...) \ - printk(KERN_DEBUG PREFIX fmt, ##__VA_ARGS__) -#else -#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->match && 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->name); + pr_info("bus type %s registered\n", type->name); return 0; } return -ENODEV; @@ -55,8 +52,7 @@ 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 "bus type %s unregistered\n", - type->name); + pr_info("bus type %s unregistered\n", type->name); return 0; } return -ENODEV; @@ -78,59 +74,178 @@ static struct acpi_bus_type *acpi_get_bus_type(struct device *dev) return ret; } -static acpi_status do_acpi_find_child(acpi_handle handle, u32 lvl_not_used, - void *addr_p, void **ret_p) +#define FIND_CHILD_MIN_SCORE 1 +#define FIND_CHILD_MID_SCORE 2 +#define FIND_CHILD_MAX_SCORE 3 + +static int match_any(struct acpi_device *adev, void *not_used) +{ + return 1; +} + +static bool acpi_dev_has_children(struct acpi_device *adev) +{ + return acpi_dev_for_each_child(adev, match_any, NULL) > 0; +} + +static int find_child_checks(struct acpi_device *adev, bool check_children) { - unsigned long long addr, sta; + unsigned long long sta; acpi_status status; - status = acpi_evaluate_integer(handle, METHOD_NAME__ADR, NULL, &addr); - if (ACPI_SUCCESS(status) && addr == *((u64 *)addr_p)) { - *ret_p = handle; - status = acpi_bus_get_status_handle(handle, &sta); - if (ACPI_SUCCESS(status) && (sta & ACPI_STA_DEVICE_ENABLED)) - return AE_CTRL_TERMINATE; + if (check_children && !acpi_dev_has_children(adev)) + return -ENODEV; + + status = acpi_evaluate_integer(adev->handle, "_STA", NULL, &sta); + if (status == AE_NOT_FOUND) { + /* + * Special case: backlight device objects without _STA are + * preferred to other objects with the same _ADR value, because + * it is more likely that they are actually useful. + */ + if (adev->pnp.type.backlight) + return FIND_CHILD_MID_SCORE; + + return FIND_CHILD_MIN_SCORE; } - return AE_OK; + + if (ACPI_FAILURE(status) || !(sta & ACPI_STA_DEVICE_ENABLED)) + return -ENODEV; + + /* + * If the device has a _HID returning a valid ACPI/PNP device ID, it is + * better to make it look less attractive here, so that the other device + * with the same _ADR value (that may not have a valid device ID) can be + * matched going forward. [This means a second spec violation in a row, + * so whatever we do here is best effort anyway.] + */ + if (adev->pnp.type.platform_id) + return FIND_CHILD_MIN_SCORE; + + return FIND_CHILD_MAX_SCORE; } -acpi_handle acpi_get_child(acpi_handle parent, u64 address) +struct find_child_walk_data { + struct acpi_device *adev; + u64 address; + int score; + bool check_sta; + bool check_children; +}; + +static int check_one_child(struct acpi_device *adev, void *data) { - void *ret = NULL; + struct find_child_walk_data *wd = data; + int score; - if (!parent) - return NULL; + if (!adev->pnp.type.bus_address || acpi_device_adr(adev) != wd->address) + return 0; - acpi_walk_namespace(ACPI_TYPE_DEVICE, parent, 1, NULL, - do_acpi_find_child, &address, &ret); - return (acpi_handle)ret; + if (!wd->adev) { + /* + * This is the first matching object, so save it. If it is not + * necessary to look for any other matching objects, stop the + * search. + */ + wd->adev = adev; + return !(wd->check_sta || wd->check_children); + } + + /* + * 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, get the score for the previously found object and terminate + * the walk if it is maximum. + */ + if (!wd->score) { + score = find_child_checks(wd->adev, wd->check_children); + if (score == FIND_CHILD_MAX_SCORE) + return 1; + + wd->score = score; + } + /* + * Second, if the object that has just been found has a better score, + * replace the previously found one with it and terminate the walk if + * the new score is maximum. + */ + score = find_child_checks(adev, wd->check_children); + if (score > wd->score) { + wd->adev = adev; + if (score == FIND_CHILD_MAX_SCORE) + return 1; + + wd->score = score; + } + + /* Continue, because there may be better matches. */ + return 0; } -EXPORT_SYMBOL(acpi_get_child); -int acpi_bind_one(struct device *dev, acpi_handle handle) +static struct acpi_device *acpi_find_child(struct acpi_device *parent, + u64 address, bool check_children, + bool check_sta) +{ + struct find_child_walk_data wd = { + .address = address, + .check_children = check_children, + .check_sta = check_sta, + .adev = NULL, + .score = 0, + }; + + if (parent) + acpi_dev_for_each_child(parent, check_one_child, &wd); + + return wd.adev; +} + +struct acpi_device *acpi_find_child_device(struct acpi_device *parent, + u64 address, bool check_children) +{ + return acpi_find_child(parent, address, check_children, true); +} +EXPORT_SYMBOL_GPL(acpi_find_child_device); + +struct acpi_device *acpi_find_child_by_adr(struct acpi_device *adev, + acpi_bus_address adr) +{ + return acpi_find_child(adev, adr, false, false); +} +EXPORT_SYMBOL_GPL(acpi_find_child_by_adr); + +static void acpi_physnode_link_name(char *buf, unsigned int node_id) +{ + if (node_id > 0) + snprintf(buf, PHYSICAL_NODE_NAME_SIZE, + PHYSICAL_NODE_STRING "%u", node_id); + else + strcpy(buf, PHYSICAL_NODE_STRING); +} + +int acpi_bind_one(struct device *dev, struct acpi_device *acpi_dev) { - struct acpi_device *acpi_dev; - acpi_status status; struct acpi_device_physical_node *physical_node, *pn; - char physical_node_name[sizeof(PHYSICAL_NODE_STRING) + 2]; + char physical_node_name[PHYSICAL_NODE_NAME_SIZE]; + struct list_head *physnode_list; + unsigned int node_id; int retval = -EINVAL; - if (ACPI_HANDLE(dev)) { - if (handle) { - dev_warn(dev, "ACPI handle is already set\n"); + if (has_acpi_companion(dev)) { + if (acpi_dev) { + dev_warn(dev, "ACPI companion already set\n"); return -EINVAL; } else { - handle = ACPI_HANDLE(dev); + acpi_dev = ACPI_COMPANION(dev); } } - if (!handle) + if (!acpi_dev) return -EINVAL; + acpi_dev_get(acpi_dev); get_device(dev); - status = acpi_bus_get_device(handle, &acpi_dev); - if (ACPI_FAILURE(status)) - goto err; - physical_node = kzalloc(sizeof(*physical_node), GFP_KERNEL); if (!physical_node) { retval = -ENOMEM; @@ -139,41 +254,54 @@ int acpi_bind_one(struct device *dev, acpi_handle handle) mutex_lock(&acpi_dev->physical_node_lock); - /* Sanity check. */ - list_for_each_entry(pn, &acpi_dev->physical_node_list, node) + /* + * 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"); - goto err_free; - } + kfree(physical_node); + if (ACPI_COMPANION(dev) != acpi_dev) + goto err; - /* allocate physical node id according to physical_node_id_bitmap */ - physical_node->node_id = - find_first_zero_bit(acpi_dev->physical_node_id_bitmap, - ACPI_MAX_PHYSICAL_NODE); - if (physical_node->node_id >= ACPI_MAX_PHYSICAL_NODE) { - retval = -ENOSPC; - goto err_free; + put_device(dev); + acpi_dev_put(acpi_dev); + return 0; + } + if (pn->node_id == node_id) { + physnode_list = &pn->node; + node_id++; + } } - set_bit(physical_node->node_id, acpi_dev->physical_node_id_bitmap); + physical_node->node_id = node_id; physical_node->dev = dev; - list_add_tail(&physical_node->node, &acpi_dev->physical_node_list); + list_add(&physical_node->node, physnode_list); acpi_dev->physical_node_count++; - mutex_unlock(&acpi_dev->physical_node_lock); - - if (!ACPI_HANDLE(dev)) - ACPI_HANDLE_SET(dev, acpi_dev->handle); + if (!has_acpi_companion(dev)) + ACPI_COMPANION_SET(dev, acpi_dev); - if (!physical_node->node_id) - strcpy(physical_node_name, PHYSICAL_NODE_STRING); - else - sprintf(physical_node_name, - "physical_node%d", physical_node->node_id); + acpi_physnode_link_name(physical_node_name, node_id); retval = sysfs_create_link(&acpi_dev->dev.kobj, &dev->kobj, - physical_node_name); + physical_node_name); + if (retval) + dev_err(&acpi_dev->dev, "Failed to create link %s (%d)\n", + physical_node_name, retval); + retval = sysfs_create_link(&dev->kobj, &acpi_dev->dev.kobj, - "firmware_node"); + "firmware_node"); + if (retval) + dev_err(dev, "Failed to create link firmware_node (%d)\n", + retval); + + mutex_unlock(&acpi_dev->physical_node_lock); if (acpi_dev->wakeup.flags.valid) device_set_wakeup_capable(dev, true); @@ -181,123 +309,106 @@ int acpi_bind_one(struct device *dev, acpi_handle handle) return 0; err: - ACPI_HANDLE_SET(dev, NULL); + ACPI_COMPANION_SET(dev, NULL); put_device(dev); + acpi_dev_put(acpi_dev); return retval; - - err_free: - mutex_unlock(&acpi_dev->physical_node_lock); - kfree(physical_node); - goto err; } EXPORT_SYMBOL_GPL(acpi_bind_one); int acpi_unbind_one(struct device *dev) { + struct acpi_device *acpi_dev = ACPI_COMPANION(dev); struct acpi_device_physical_node *entry; - struct acpi_device *acpi_dev; - acpi_status status; - struct list_head *node, *next; - if (!ACPI_HANDLE(dev)) + if (!acpi_dev) return 0; - status = acpi_bus_get_device(ACPI_HANDLE(dev), &acpi_dev); - if (ACPI_FAILURE(status)) - goto err; - mutex_lock(&acpi_dev->physical_node_lock); - list_for_each_safe(node, next, &acpi_dev->physical_node_list) { - char physical_node_name[sizeof(PHYSICAL_NODE_STRING) + 2]; - - entry = list_entry(node, struct acpi_device_physical_node, - node); - if (entry->dev != dev) - continue; - - list_del(node); - clear_bit(entry->node_id, acpi_dev->physical_node_id_bitmap); - - acpi_dev->physical_node_count--; - - if (!entry->node_id) - strcpy(physical_node_name, PHYSICAL_NODE_STRING); - else - sprintf(physical_node_name, - "physical_node%d", entry->node_id); - - sysfs_remove_link(&acpi_dev->dev.kobj, physical_node_name); - sysfs_remove_link(&dev->kobj, "firmware_node"); - ACPI_HANDLE_SET(dev, NULL); - /* acpi_bind_one increase refcnt by one */ - put_device(dev); - kfree(entry); - } - mutex_unlock(&acpi_dev->physical_node_lock); - return 0; + list_for_each_entry(entry, &acpi_dev->physical_node_list, node) + if (entry->dev == dev) { + char physnode_name[PHYSICAL_NODE_NAME_SIZE]; + + list_del(&entry->node); + acpi_dev->physical_node_count--; + + 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); + acpi_dev_put(acpi_dev); + kfree(entry); + break; + } -err: - dev_err(dev, "Oops, 'acpi_handle' corrupt\n"); - return -EINVAL; + mutex_unlock(&acpi_dev->physical_node_lock); + return 0; } EXPORT_SYMBOL_GPL(acpi_unbind_one); -static int acpi_platform_notify(struct device *dev) +void acpi_device_notify(struct device *dev) { - struct acpi_bus_type *type = acpi_get_bus_type(dev); - acpi_handle handle; + struct acpi_device *adev; int ret; ret = acpi_bind_one(dev, NULL); - if (ret && type) { - ret = type->find_device(dev, &handle); - if (ret) { - DBG("Unable to get handle for %s\n", dev_name(dev)); - goto out; + if (ret) { + struct acpi_bus_type *type = acpi_get_bus_type(dev); + + if (!type) + goto err; + + adev = type->find_companion(dev); + if (!adev) { + dev_dbg(dev, "ACPI companion not found\n"); + goto err; } - ret = acpi_bind_one(dev, handle); + ret = acpi_bind_one(dev, adev); if (ret) - goto out; + goto err; + + if (type->setup) { + type->setup(dev); + goto done; + } + } else { + adev = ACPI_COMPANION(dev); + + if (dev_is_pci(dev)) { + pci_acpi_setup(dev, adev); + goto done; + } else if (dev_is_platform(dev)) { + acpi_configure_pmsi_domain(dev); + } } - if (type && type->setup) - type->setup(dev); + 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 }; +done: + acpi_handle_debug(ACPI_HANDLE(dev), "Bound to device %s\n", + dev_name(dev)); - 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_name(dev)); -#endif + return; - return ret; +err: + dev_dbg(dev, "No ACPI support\n"); } -static int acpi_platform_notify_remove(struct device *dev) +void acpi_device_notify_remove(struct device *dev) { - struct acpi_bus_type *type; + struct acpi_device *adev = ACPI_COMPANION(dev); - type = acpi_get_bus_type(dev); - if (type && type->cleanup) - type->cleanup(dev); + if (!adev) + return; - acpi_unbind_one(dev); - return 0; -} + if (dev_is_pci(dev)) + pci_acpi_cleanup(dev, adev); + else if (adev->handler && adev->handler->unbind) + adev->handler->unbind(dev); -int __init init_acpi_device_notify(void) -{ - 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; + acpi_unbind_one(dev); } |
