summaryrefslogtreecommitdiff
path: root/drivers/acpi/glue.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/acpi/glue.c')
-rw-r--r--drivers/acpi/glue.c425
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);
}