Skip to content

Commit

Permalink
ACPI / hotplug: Fix concurrency issues and memory leaks
Browse files Browse the repository at this point in the history
This changeset is aimed at fixing a few different but related
problems in the ACPI hotplug infrastructure.

First of all, since notify handlers may be run in parallel with
acpi_bus_scan(), acpi_bus_trim() and acpi_bus_hot_remove_device()
and some of them are installed for ACPI handles that have no struct
acpi_device objects attached (i.e. before those objects are created),
those notify handlers have to take acpi_scan_lock to prevent races
from taking place (e.g. a struct acpi_device is found to be present
for the given ACPI handle, but right after that it is removed by
acpi_bus_trim() running in parallel to the given notify handler).
Moreover, since some of them call acpi_bus_scan() and
acpi_bus_trim(), this leads to the conclusion that acpi_scan_lock
should be acquired by the callers of these two funtions rather by
these functions themselves.

For these reasons, make all notify handlers that can handle device
addition and eject events take acpi_scan_lock and remove the
acpi_scan_lock locking from acpi_bus_scan() and acpi_bus_trim().
Accordingly, update all of their users to make sure that they
are always called under acpi_scan_lock.

Furthermore, since eject operations are carried out asynchronously
with respect to the notify events that trigger them, with the help
of acpi_bus_hot_remove_device(), even if notify handlers take the
ACPI scan lock, it still is possible that, for example,
acpi_bus_trim() will run between acpi_bus_hot_remove_device() and
the notify handler that scheduled its execution and that
acpi_bus_trim() will remove the device node passed to
acpi_bus_hot_remove_device() for ejection.  In that case, the struct
acpi_device object obtained by acpi_bus_hot_remove_device() will be
invalid and not-so-funny things will ensue.  To protect agaist that,
make the users of acpi_bus_hot_remove_device() run get_device() on
ACPI device node objects that are about to be passed to it and make
acpi_bus_hot_remove_device() run put_device() on them and check if
their ACPI handles are not NULL (make acpi_device_unregister() clear
the device nodes' ACPI handles for that check to work).

Finally, observe that acpi_os_hotplug_execute() actually can fail,
in which case its caller ought to free memory allocated for the
context object to prevent leaks from happening.  It also needs to
run put_device() on the device node that it ran get_device() on
previously in that case.  Modify the code accordingly.

Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Acked-by: Yinghai Lu <yinghai@kernel.org>
  • Loading branch information
Rafael J. Wysocki committed Feb 13, 2013
1 parent 64fd740 commit 3757b94
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 55 deletions.
56 changes: 37 additions & 19 deletions drivers/acpi/acpi_memhotplug.c
Original file line number Diff line number Diff line change
Expand Up @@ -153,14 +153,16 @@ acpi_memory_get_device_resources(struct acpi_memory_device *mem_device)
return 0;
}

static int
acpi_memory_get_device(acpi_handle handle,
struct acpi_memory_device **mem_device)
static int acpi_memory_get_device(acpi_handle handle,
struct acpi_memory_device **mem_device)
{
struct acpi_device *device = NULL;
int result;
int result = 0;

if (!acpi_bus_get_device(handle, &device) && device)
acpi_scan_lock_acquire();

acpi_bus_get_device(handle, &device);
if (device)
goto end;

/*
Expand All @@ -169,23 +171,28 @@ acpi_memory_get_device(acpi_handle handle,
*/
result = acpi_bus_scan(handle);
if (result) {
acpi_handle_warn(handle, "Cannot add acpi bus\n");
return -EINVAL;
acpi_handle_warn(handle, "ACPI namespace scan failed\n");
result = -EINVAL;
goto out;
}
result = acpi_bus_get_device(handle, &device);
if (result) {
acpi_handle_warn(handle, "Missing device object\n");
return -EINVAL;
result = -EINVAL;
goto out;
}

end:
end:
*mem_device = acpi_driver_data(device);
if (!(*mem_device)) {
dev_err(&device->dev, "driver data not found\n");
return -ENODEV;
result = -ENODEV;
goto out;
}

return 0;
out:
acpi_scan_lock_release();
return result;
}

static int acpi_memory_check_device(struct acpi_memory_device *mem_device)
Expand Down Expand Up @@ -305,6 +312,7 @@ static void acpi_memory_device_notify(acpi_handle handle, u32 event, void *data)
struct acpi_device *device;
struct acpi_eject_event *ej_event = NULL;
u32 ost_code = ACPI_OST_SC_NON_SPECIFIC_FAILURE; /* default */
acpi_status status;

switch (event) {
case ACPI_NOTIFY_BUS_CHECK:
Expand All @@ -327,29 +335,40 @@ static void acpi_memory_device_notify(acpi_handle handle, u32 event, void *data)
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"\nReceived EJECT REQUEST notification for device\n"));

status = AE_ERROR;
acpi_scan_lock_acquire();

if (acpi_bus_get_device(handle, &device)) {
acpi_handle_err(handle, "Device doesn't exist\n");
break;
goto unlock;
}
mem_device = acpi_driver_data(device);
if (!mem_device) {
acpi_handle_err(handle, "Driver Data is NULL\n");
break;
goto unlock;
}

ej_event = kmalloc(sizeof(*ej_event), GFP_KERNEL);
if (!ej_event) {
pr_err(PREFIX "No memory, dropping EJECT\n");
break;
goto unlock;
}

get_device(&device->dev);
ej_event->device = device;
ej_event->event = ACPI_NOTIFY_EJECT_REQUEST;
acpi_os_hotplug_execute(acpi_bus_hot_remove_device,
(void *)ej_event);
/* The eject is carried out asynchronously. */
status = acpi_os_hotplug_execute(acpi_bus_hot_remove_device,
ej_event);
if (ACPI_FAILURE(status)) {
put_device(&device->dev);
kfree(ej_event);
}

/* eject is performed asynchronously */
return;
unlock:
acpi_scan_lock_release();
if (ACPI_SUCCESS(status))
return;
default:
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Unsupported event [0x%x]\n", event));
Expand All @@ -360,7 +379,6 @@ static void acpi_memory_device_notify(acpi_handle handle, u32 event, void *data)

/* Inform firmware that the hotplug operation has completed */
(void) acpi_evaluate_hotplug_ost(handle, event, ost_code, NULL);
return;
}

static void acpi_memory_device_free(struct acpi_memory_device *mem_device)
Expand Down
12 changes: 8 additions & 4 deletions drivers/acpi/container.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ static void container_notify_cb(acpi_handle handle, u32 type, void *context)
acpi_status status;
u32 ost_code = ACPI_OST_SC_NON_SPECIFIC_FAILURE; /* default */

acpi_scan_lock_acquire();

switch (type) {
case ACPI_NOTIFY_BUS_CHECK:
/* Fall through */
Expand All @@ -103,7 +105,7 @@ static void container_notify_cb(acpi_handle handle, u32 type, void *context)
/* device exist and this is a remove request */
device->flags.eject_pending = 1;
kobject_uevent(&device->dev.kobj, KOBJ_OFFLINE);
return;
goto out;
}
break;
}
Expand All @@ -130,18 +132,20 @@ static void container_notify_cb(acpi_handle handle, u32 type, void *context)
if (!acpi_bus_get_device(handle, &device) && device) {
device->flags.eject_pending = 1;
kobject_uevent(&device->dev.kobj, KOBJ_OFFLINE);
return;
goto out;
}
break;

default:
/* non-hotplug event; possibly handled by other handler */
return;
goto out;
}

/* Inform firmware that the hotplug operation has completed */
(void) acpi_evaluate_hotplug_ost(handle, type, ost_code, NULL);
return;

out:
acpi_scan_lock_release();
}

static bool is_container(acpi_handle handle)
Expand Down
19 changes: 16 additions & 3 deletions drivers/acpi/dock.c
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,9 @@ static void acpi_dock_deferred_cb(void *context)
{
struct dock_data *data = context;

acpi_scan_lock_acquire();
dock_notify(data->handle, data->event, data->ds);
acpi_scan_lock_release();
kfree(data);
}

Expand All @@ -757,20 +759,31 @@ static int acpi_dock_notifier_call(struct notifier_block *this,
if (event != ACPI_NOTIFY_BUS_CHECK && event != ACPI_NOTIFY_DEVICE_CHECK
&& event != ACPI_NOTIFY_EJECT_REQUEST)
return 0;

acpi_scan_lock_acquire();

list_for_each_entry(dock_station, &dock_stations, sibling) {
if (dock_station->handle == handle) {
struct dock_data *dd;
acpi_status status;

dd = kmalloc(sizeof(*dd), GFP_KERNEL);
if (!dd)
return 0;
break;

dd->handle = handle;
dd->event = event;
dd->ds = dock_station;
acpi_os_hotplug_execute(acpi_dock_deferred_cb, dd);
return 0 ;
status = acpi_os_hotplug_execute(acpi_dock_deferred_cb,
dd);
if (ACPI_FAILURE(status))
kfree(dd);

break;
}
}

acpi_scan_lock_release();
return 0;
}

Expand Down
24 changes: 17 additions & 7 deletions drivers/acpi/processor_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -683,8 +683,11 @@ static void acpi_processor_hotplug_notify(acpi_handle handle,
struct acpi_device *device = NULL;
struct acpi_eject_event *ej_event = NULL;
u32 ost_code = ACPI_OST_SC_NON_SPECIFIC_FAILURE; /* default */
acpi_status status;
int result;

acpi_scan_lock_acquire();

switch (event) {
case ACPI_NOTIFY_BUS_CHECK:
case ACPI_NOTIFY_DEVICE_CHECK:
Expand Down Expand Up @@ -733,25 +736,32 @@ static void acpi_processor_hotplug_notify(acpi_handle handle,
break;
}

get_device(&device->dev);
ej_event->device = device;
ej_event->event = ACPI_NOTIFY_EJECT_REQUEST;
acpi_os_hotplug_execute(acpi_bus_hot_remove_device,
(void *)ej_event);

/* eject is performed asynchronously */
return;
/* The eject is carried out asynchronously. */
status = acpi_os_hotplug_execute(acpi_bus_hot_remove_device,
ej_event);
if (ACPI_FAILURE(status)) {
put_device(&device->dev);
kfree(ej_event);
break;
}
goto out;

default:
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Unsupported event [0x%x]\n", event));

/* non-hotplug event; possibly handled by other handler */
return;
goto out;
}

/* Inform firmware that the hotplug operation has completed */
(void) acpi_evaluate_hotplug_ost(handle, event, ost_code, NULL);
return;

out:
acpi_scan_lock_release();
}

static acpi_status is_processor_device(acpi_handle handle)
Expand Down
Loading

0 comments on commit 3757b94

Please sign in to comment.