|
|
|
|
@@ -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)
|
|
|
|
|
{
|
|
|
|
|
/* Intentionally empty. */
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
|