summaryrefslogtreecommitdiff
path: root/drivers/acpi/scan.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/acpi/scan.c')
-rw-r--r--drivers/acpi/scan.c284
1 files changed, 257 insertions, 27 deletions
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
index d670158a26c5..1b1cf558d3d3 100644
--- a/drivers/acpi/scan.c
+++ b/drivers/acpi/scan.c
@@ -36,6 +36,8 @@ bool acpi_force_hot_remove;
static const char *dummy_hid = "device";
+static LIST_HEAD(acpi_dep_list);
+static DEFINE_MUTEX(acpi_dep_list_lock);
static LIST_HEAD(acpi_bus_id_list);
static DEFINE_MUTEX(acpi_scan_lock);
static LIST_HEAD(acpi_scan_handlers_list);
@@ -43,6 +45,12 @@ DEFINE_MUTEX(acpi_device_lock);
LIST_HEAD(acpi_wakeup_device_list);
static DEFINE_MUTEX(acpi_hp_context_lock);
+struct acpi_dep_data {
+ struct list_head node;
+ acpi_handle master;
+ acpi_handle slave;
+};
+
struct acpi_device_bus_id{
char bus_id[15];
unsigned int instance_no;
@@ -124,17 +132,56 @@ static int create_modalias(struct acpi_device *acpi_dev, char *modalias,
if (list_empty(&acpi_dev->pnp.ids))
return 0;
- len = snprintf(modalias, size, "acpi:");
- size -= len;
-
- list_for_each_entry(id, &acpi_dev->pnp.ids, list) {
- count = snprintf(&modalias[len], size, "%s:", id->id);
- if (count < 0)
- return -EINVAL;
- if (count >= size)
- return -ENOMEM;
- len += count;
- size -= count;
+ /*
+ * If the device has PRP0001 we expose DT compatible modalias
+ * instead in form of of:NnameTCcompatible.
+ */
+ if (acpi_dev->data.of_compatible) {
+ struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER };
+ const union acpi_object *of_compatible, *obj;
+ int i, nval;
+ char *c;
+
+ acpi_get_name(acpi_dev->handle, ACPI_SINGLE_NAME, &buf);
+ /* DT strings are all in lower case */
+ for (c = buf.pointer; *c != '\0'; c++)
+ *c = tolower(*c);
+
+ len = snprintf(modalias, size, "of:N%sT", (char *)buf.pointer);
+ ACPI_FREE(buf.pointer);
+
+ of_compatible = acpi_dev->data.of_compatible;
+ if (of_compatible->type == ACPI_TYPE_PACKAGE) {
+ nval = of_compatible->package.count;
+ obj = of_compatible->package.elements;
+ } else { /* Must be ACPI_TYPE_STRING. */
+ nval = 1;
+ obj = of_compatible;
+ }
+ for (i = 0; i < nval; i++, obj++) {
+ count = snprintf(&modalias[len], size, "C%s",
+ obj->string.pointer);
+ if (count < 0)
+ return -EINVAL;
+ if (count >= size)
+ return -ENOMEM;
+
+ len += count;
+ size -= count;
+ }
+ } else {
+ len = snprintf(modalias, size, "acpi:");
+ size -= len;
+
+ list_for_each_entry(id, &acpi_dev->pnp.ids, list) {
+ count = snprintf(&modalias[len], size, "%s:", id->id);
+ if (count < 0)
+ return -EINVAL;
+ if (count >= size)
+ return -ENOMEM;
+ len += count;
+ size -= count;
+ }
}
modalias[len] = '\0';
@@ -142,6 +189,53 @@ static int create_modalias(struct acpi_device *acpi_dev, char *modalias,
}
/*
+ * acpi_companion_match() - Can we match via ACPI companion device
+ * @dev: Device in question
+ *
+ * Check if the given device has an ACPI companion and if that companion has
+ * a valid list of PNP IDs, and if the device is the first (primary) physical
+ * device associated with it.
+ *
+ * If multiple physical devices are attached to a single ACPI companion, we need
+ * to be careful. The usage scenario for this kind of relationship is that all
+ * of the physical devices in question use resources provided by the ACPI
+ * companion. A typical case is an MFD device where all the sub-devices share
+ * the parent's ACPI companion. In such cases we can only allow the primary
+ * (first) physical device to be matched with the help of the companion's PNP
+ * IDs.
+ *
+ * Additional physical devices sharing the ACPI companion can still use
+ * resources available from it but they will be matched normally using functions
+ * provided by their bus types (and analogously for their modalias).
+ */
+static bool acpi_companion_match(const struct device *dev)
+{
+ struct acpi_device *adev;
+ bool ret;
+
+ adev = ACPI_COMPANION(dev);
+ if (!adev)
+ return false;
+
+ if (list_empty(&adev->pnp.ids))
+ return false;
+
+ mutex_lock(&adev->physical_node_lock);
+ if (list_empty(&adev->physical_node_list)) {
+ ret = false;
+ } else {
+ const struct acpi_device_physical_node *node;
+
+ node = list_first_entry(&adev->physical_node_list,
+ struct acpi_device_physical_node, node);
+ ret = node->dev == dev;
+ }
+ mutex_unlock(&adev->physical_node_lock);
+
+ return ret;
+}
+
+/*
* Creates uevent modalias field for ACPI enumerated devices.
* Because the other buses does not support ACPI HIDs & CIDs.
* e.g. for a device with hid:IBM0001 and cid:ACPI0001 you get:
@@ -149,20 +243,14 @@ static int create_modalias(struct acpi_device *acpi_dev, char *modalias,
*/
int acpi_device_uevent_modalias(struct device *dev, struct kobj_uevent_env *env)
{
- struct acpi_device *acpi_dev;
int len;
- acpi_dev = ACPI_COMPANION(dev);
- if (!acpi_dev)
- return -ENODEV;
-
- /* Fall back to bus specific way of modalias exporting */
- if (list_empty(&acpi_dev->pnp.ids))
+ if (!acpi_companion_match(dev))
return -ENODEV;
if (add_uevent_var(env, "MODALIAS="))
return -ENOMEM;
- len = create_modalias(acpi_dev, &env->buf[env->buflen - 1],
+ len = create_modalias(ACPI_COMPANION(dev), &env->buf[env->buflen - 1],
sizeof(env->buf) - env->buflen);
if (len <= 0)
return len;
@@ -179,18 +267,12 @@ EXPORT_SYMBOL_GPL(acpi_device_uevent_modalias);
*/
int acpi_device_modalias(struct device *dev, char *buf, int size)
{
- struct acpi_device *acpi_dev;
int len;
- acpi_dev = ACPI_COMPANION(dev);
- if (!acpi_dev)
- return -ENODEV;
-
- /* Fall back to bus specific way of modalias exporting */
- if (list_empty(&acpi_dev->pnp.ids))
+ if (!acpi_companion_match(dev))
return -ENODEV;
- len = create_modalias(acpi_dev, buf, size -1);
+ len = create_modalias(ACPI_COMPANION(dev), buf, size -1);
if (len <= 0)
return len;
buf[len++] = '\n';
@@ -853,6 +935,9 @@ const struct acpi_device_id *acpi_match_device(const struct acpi_device_id *ids,
if (!ids || !handle || acpi_bus_get_device(handle, &adev))
return NULL;
+ if (!acpi_companion_match(dev))
+ return NULL;
+
return __acpi_match_device(adev, ids);
}
EXPORT_SYMBOL_GPL(acpi_match_device);
@@ -864,6 +949,51 @@ int acpi_match_device_ids(struct acpi_device *device,
}
EXPORT_SYMBOL(acpi_match_device_ids);
+/* Performs match against special "PRP0001" shoehorn ACPI ID */
+static bool acpi_of_driver_match_device(struct device *dev,
+ const struct device_driver *drv)
+{
+ const union acpi_object *of_compatible, *obj;
+ struct acpi_device *adev;
+ int i, nval;
+
+ adev = ACPI_COMPANION(dev);
+ if (!adev)
+ return false;
+
+ of_compatible = adev->data.of_compatible;
+ if (!drv->of_match_table || !of_compatible)
+ return false;
+
+ if (of_compatible->type == ACPI_TYPE_PACKAGE) {
+ nval = of_compatible->package.count;
+ obj = of_compatible->package.elements;
+ } else { /* Must be ACPI_TYPE_STRING. */
+ nval = 1;
+ obj = of_compatible;
+ }
+ /* Now we can look for the driver DT compatible strings */
+ for (i = 0; i < nval; i++, obj++) {
+ const struct of_device_id *id;
+
+ for (id = drv->of_match_table; id->compatible[0]; id++)
+ if (!strcasecmp(obj->string.pointer, id->compatible))
+ return true;
+ }
+
+ return false;
+}
+
+bool acpi_driver_match_device(struct device *dev,
+ const struct device_driver *drv)
+{
+ if (!drv->acpi_match_table)
+ return acpi_of_driver_match_device(dev, drv);
+
+ return !!acpi_match_device(drv->acpi_match_table, dev);
+}
+EXPORT_SYMBOL_GPL(acpi_driver_match_device);
+
static void acpi_free_power_resources_lists(struct acpi_device *device)
{
int i;
@@ -884,6 +1014,7 @@ static void acpi_device_release(struct device *dev)
{
struct acpi_device *acpi_dev = to_acpi_device(dev);
+ acpi_free_properties(acpi_dev);
acpi_free_pnp_ids(&acpi_dev->pnp);
acpi_free_power_resources_lists(acpi_dev);
kfree(acpi_dev);
@@ -1266,6 +1397,26 @@ int acpi_device_add(struct acpi_device *device,
return result;
}
+struct acpi_device *acpi_get_next_child(struct device *dev,
+ struct acpi_device *child)
+{
+ struct acpi_device *adev = ACPI_COMPANION(dev);
+ struct list_head *head, *next;
+
+ if (!adev)
+ return NULL;
+
+ head = &adev->children;
+ if (list_empty(head))
+ return NULL;
+
+ if (!child)
+ return list_first_entry(head, struct acpi_device, node);
+
+ next = child->node.next;
+ return next == head ? NULL : list_entry(next, struct acpi_device, node);
+}
+
/* --------------------------------------------------------------------------
Driver Management
-------------------------------------------------------------------------- */
@@ -1885,9 +2036,11 @@ void acpi_init_device_object(struct acpi_device *device, acpi_handle handle,
device->device_type = type;
device->handle = handle;
device->parent = acpi_bus_get_parent(handle);
+ device->fwnode.type = FWNODE_ACPI;
acpi_set_device_status(device, sta);
acpi_device_get_busid(device);
acpi_set_pnp_ids(handle, &device->pnp, type);
+ acpi_init_properties(device);
acpi_bus_get_flags(device);
device->flags.match_driver = false;
device->flags.initialized = true;
@@ -2048,6 +2201,59 @@ static void acpi_scan_init_hotplug(struct acpi_device *adev)
}
}
+static void acpi_device_dep_initialize(struct acpi_device *adev)
+{
+ struct acpi_dep_data *dep;
+ struct acpi_handle_list dep_devices;
+ acpi_status status;
+ int i;
+
+ if (!acpi_has_method(adev->handle, "_DEP"))
+ return;
+
+ status = acpi_evaluate_reference(adev->handle, "_DEP", NULL,
+ &dep_devices);
+ if (ACPI_FAILURE(status)) {
+ dev_err(&adev->dev, "Failed to evaluate _DEP.\n");
+ return;
+ }
+
+ for (i = 0; i < dep_devices.count; i++) {
+ struct acpi_device_info *info;
+ int skip;
+
+ status = acpi_get_object_info(dep_devices.handles[i], &info);
+ if (ACPI_FAILURE(status)) {
+ dev_err(&adev->dev, "Error reading device info\n");
+ continue;
+ }
+
+ /*
+ * Skip the dependency of Windows System Power
+ * Management Controller
+ */
+ skip = info->valid & ACPI_VALID_HID &&
+ !strcmp(info->hardware_id.string, "INT3396");
+
+ kfree(info);
+
+ if (skip)
+ continue;
+
+ dep = kzalloc(sizeof(struct acpi_dep_data), GFP_KERNEL);
+ if (!dep)
+ return;
+
+ dep->master = dep_devices.handles[i];
+ dep->slave = adev->handle;
+ adev->dep_unmet++;
+
+ mutex_lock(&acpi_dep_list_lock);
+ list_add_tail(&dep->node , &acpi_dep_list);
+ mutex_unlock(&acpi_dep_list_lock);
+ }
+}
+
static acpi_status acpi_bus_check_add(acpi_handle handle, u32 lvl_not_used,
void *not_used, void **return_value)
{
@@ -2074,6 +2280,7 @@ static acpi_status acpi_bus_check_add(acpi_handle handle, u32 lvl_not_used,
return AE_CTRL_DEPTH;
acpi_scan_init_hotplug(device);
+ acpi_device_dep_initialize(device);
out:
if (!*return_value)
@@ -2194,6 +2401,29 @@ static void acpi_bus_attach(struct acpi_device *device)
device->handler->hotplug.notify_online(device);
}
+void acpi_walk_dep_device_list(acpi_handle handle)
+{
+ struct acpi_dep_data *dep, *tmp;
+ struct acpi_device *adev;
+
+ mutex_lock(&acpi_dep_list_lock);
+ list_for_each_entry_safe(dep, tmp, &acpi_dep_list, node) {
+ if (dep->master == handle) {
+ acpi_bus_get_device(dep->slave, &adev);
+ if (!adev)
+ continue;
+
+ adev->dep_unmet--;
+ if (!adev->dep_unmet)
+ acpi_bus_attach(adev);
+ list_del(&dep->node);
+ kfree(dep);
+ }
+ }
+ mutex_unlock(&acpi_dep_list_lock);
+}
+EXPORT_SYMBOL_GPL(acpi_walk_dep_device_list);
+
/**
* acpi_bus_scan - Add ACPI device node objects in a given namespace scope.
* @handle: Root of the namespace scope to scan.