|
@@ -14,6 +14,8 @@
|
|
|
|
|
|
#include <acpi/acpi_drivers.h>
|
|
|
|
|
|
+#include <asm/pgtable.h>
|
|
|
+
|
|
|
#include "internal.h"
|
|
|
|
|
|
#define _COMPONENT ACPI_BUS_COMPONENT
|
|
@@ -27,6 +29,8 @@ extern struct acpi_device *acpi_root;
|
|
|
|
|
|
#define ACPI_IS_ROOT_DEVICE(device) (!(device)->parent)
|
|
|
|
|
|
+#define INVALID_ACPI_HANDLE ((acpi_handle)empty_zero_page)
|
|
|
+
|
|
|
/*
|
|
|
* If set, devices will be hot-removed even if they cannot be put offline
|
|
|
* gracefully (from the kernel's standpoint).
|
|
@@ -907,9 +911,91 @@ struct bus_type acpi_bus_type = {
|
|
|
.uevent = acpi_device_uevent,
|
|
|
};
|
|
|
|
|
|
-static void acpi_bus_data_handler(acpi_handle handle, void *context)
|
|
|
+static void acpi_device_del(struct acpi_device *device)
|
|
|
+{
|
|
|
+ mutex_lock(&acpi_device_lock);
|
|
|
+ if (device->parent)
|
|
|
+ list_del(&device->node);
|
|
|
+
|
|
|
+ list_del(&device->wakeup_list);
|
|
|
+ mutex_unlock(&acpi_device_lock);
|
|
|
+
|
|
|
+ acpi_power_add_remove_device(device, false);
|
|
|
+ acpi_device_remove_files(device);
|
|
|
+ if (device->remove)
|
|
|
+ device->remove(device);
|
|
|
+
|
|
|
+ device_del(&device->dev);
|
|
|
+}
|
|
|
+
|
|
|
+static LIST_HEAD(acpi_device_del_list);
|
|
|
+static DEFINE_MUTEX(acpi_device_del_lock);
|
|
|
+
|
|
|
+static void acpi_device_del_work_fn(struct work_struct *work_not_used)
|
|
|
+{
|
|
|
+ for (;;) {
|
|
|
+ struct acpi_device *adev;
|
|
|
+
|
|
|
+ mutex_lock(&acpi_device_del_lock);
|
|
|
+
|
|
|
+ if (list_empty(&acpi_device_del_list)) {
|
|
|
+ mutex_unlock(&acpi_device_del_lock);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ adev = list_first_entry(&acpi_device_del_list,
|
|
|
+ struct acpi_device, del_list);
|
|
|
+ list_del(&adev->del_list);
|
|
|
+
|
|
|
+ mutex_unlock(&acpi_device_del_lock);
|
|
|
+
|
|
|
+ acpi_device_del(adev);
|
|
|
+ /*
|
|
|
+ * Drop references to all power resources that might have been
|
|
|
+ * used by the device.
|
|
|
+ */
|
|
|
+ acpi_power_transition(adev, ACPI_STATE_D3_COLD);
|
|
|
+ put_device(&adev->dev);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * acpi_scan_drop_device - Drop an ACPI device object.
|
|
|
+ * @handle: Handle of an ACPI namespace node, not used.
|
|
|
+ * @context: Address of the ACPI device object to drop.
|
|
|
+ *
|
|
|
+ * This is invoked by acpi_ns_delete_node() during the removal of the ACPI
|
|
|
+ * namespace node the device object pointed to by @context is attached to.
|
|
|
+ *
|
|
|
+ * The unregistration is carried out asynchronously to avoid running
|
|
|
+ * acpi_device_del() under the ACPICA's namespace mutex and the list is used to
|
|
|
+ * ensure the correct ordering (the device objects must be unregistered in the
|
|
|
+ * same order in which the corresponding namespace nodes are deleted).
|
|
|
+ */
|
|
|
+static void acpi_scan_drop_device(acpi_handle handle, void *context)
|
|
|
{
|
|
|
- /* Intentionally empty. */
|
|
|
+ static DECLARE_WORK(work, acpi_device_del_work_fn);
|
|
|
+ struct acpi_device *adev = context;
|
|
|
+
|
|
|
+ mutex_lock(&acpi_device_del_lock);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Use the ACPI hotplug workqueue which is ordered, so this work item
|
|
|
+ * won't run after any hotplug work items submitted subsequently. That
|
|
|
+ * prevents attempts to register device objects identical to those being
|
|
|
+ * deleted from happening concurrently (such attempts result from
|
|
|
+ * hotplug events handled via the ACPI hotplug workqueue). It also will
|
|
|
+ * run after all of the work items submitted previosuly, which helps
|
|
|
+ * those work items to ensure that they are not accessing stale device
|
|
|
+ * objects.
|
|
|
+ */
|
|
|
+ if (list_empty(&acpi_device_del_list))
|
|
|
+ acpi_queue_hotplug_work(&work);
|
|
|
+
|
|
|
+ list_add_tail(&adev->del_list, &acpi_device_del_list);
|
|
|
+ /* Make acpi_ns_validate_handle() return NULL for this handle. */
|
|
|
+ adev->handle = INVALID_ACPI_HANDLE;
|
|
|
+
|
|
|
+ mutex_unlock(&acpi_device_del_lock);
|
|
|
}
|
|
|
|
|
|
int acpi_bus_get_device(acpi_handle handle, struct acpi_device **device)
|
|
@@ -919,7 +1005,7 @@ int acpi_bus_get_device(acpi_handle handle, struct acpi_device **device)
|
|
|
if (!device)
|
|
|
return -EINVAL;
|
|
|
|
|
|
- status = acpi_get_data(handle, acpi_bus_data_handler, (void **)device);
|
|
|
+ status = acpi_get_data(handle, acpi_scan_drop_device, (void **)device);
|
|
|
if (ACPI_FAILURE(status) || !*device) {
|
|
|
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No context for object [%p]\n",
|
|
|
handle));
|
|
@@ -939,7 +1025,7 @@ int acpi_device_add(struct acpi_device *device,
|
|
|
if (device->handle) {
|
|
|
acpi_status status;
|
|
|
|
|
|
- status = acpi_attach_data(device->handle, acpi_bus_data_handler,
|
|
|
+ status = acpi_attach_data(device->handle, acpi_scan_drop_device,
|
|
|
device);
|
|
|
if (ACPI_FAILURE(status)) {
|
|
|
acpi_handle_err(device->handle,
|
|
@@ -957,6 +1043,7 @@ int acpi_device_add(struct acpi_device *device,
|
|
|
INIT_LIST_HEAD(&device->node);
|
|
|
INIT_LIST_HEAD(&device->wakeup_list);
|
|
|
INIT_LIST_HEAD(&device->physical_node_list);
|
|
|
+ INIT_LIST_HEAD(&device->del_list);
|
|
|
mutex_init(&device->physical_node_lock);
|
|
|
|
|
|
new_bus_id = kzalloc(sizeof(struct acpi_device_bus_id), GFP_KERNEL);
|
|
@@ -1020,27 +1107,14 @@ int acpi_device_add(struct acpi_device *device,
|
|
|
mutex_unlock(&acpi_device_lock);
|
|
|
|
|
|
err_detach:
|
|
|
- acpi_detach_data(device->handle, acpi_bus_data_handler);
|
|
|
+ acpi_detach_data(device->handle, acpi_scan_drop_device);
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
static void acpi_device_unregister(struct acpi_device *device)
|
|
|
{
|
|
|
- mutex_lock(&acpi_device_lock);
|
|
|
- if (device->parent)
|
|
|
- list_del(&device->node);
|
|
|
-
|
|
|
- list_del(&device->wakeup_list);
|
|
|
- mutex_unlock(&acpi_device_lock);
|
|
|
-
|
|
|
- acpi_detach_data(device->handle, acpi_bus_data_handler);
|
|
|
-
|
|
|
- acpi_power_add_remove_device(device, false);
|
|
|
- acpi_device_remove_files(device);
|
|
|
- if (device->remove)
|
|
|
- device->remove(device);
|
|
|
-
|
|
|
- device_del(&device->dev);
|
|
|
+ acpi_detach_data(device->handle, acpi_scan_drop_device);
|
|
|
+ acpi_device_del(device);
|
|
|
/*
|
|
|
* Transition the device to D3cold to drop the reference counts of all
|
|
|
* power resources the device depends on and turn off the ones that have
|