summary refs log tree commit diff
path: root/drivers/acpi/dock.c
diff options
context:
space:
mode:
authorZhang Rui <rui.zhang@intel.com>2008-08-28 10:05:06 +0800
committerLen Brown <len.brown@intel.com>2008-09-23 23:12:38 -0400
commit19cd847ab24fefe9e50101ec94479e0400a08650 (patch)
tree9af663d1f43e59d6ce3f942d89b910dda5e1c542 /drivers/acpi/dock.c
parent6bd00a61ab63d4ceb635ae0316353c11c900b8d8 (diff)
downloadlinux-19cd847ab24fefe9e50101ec94479e0400a08650.tar.gz
ACPI: fix hotplug race
The hotplug notification handler and drivers' notification handler all
run in one workqueue.  Before hotplug removes an acpi device, the
device driver's notification handler is already be recorded to run just
after global notification handler.  After hotplug notification handler
runs, acpica will notice a NULL notification handler and crash.

So now we run run hotplug in another workqueue and wait
for all acpi notication handlers finish.
This was found in battery hotplug, but actually all
hotplug can be affected.

Signed-off-by: Zhang Rui <rui.zhang@intel.com>
Signed-off-by: Shaohua Li <shaohua.li@intel.com>
Signed-off-by: Len Brown <len.brown@intel.com>
Diffstat (limited to 'drivers/acpi/dock.c')
-rw-r--r--drivers/acpi/dock.c25
1 files changed, 24 insertions, 1 deletions
diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c
index 2563bc62987d..4b395b1e61b2 100644
--- a/drivers/acpi/dock.c
+++ b/drivers/acpi/dock.c
@@ -748,6 +748,20 @@ static void dock_notify(acpi_handle handle, u32 event, void *data)
 	}
 }
 
+struct dock_data {
+	acpi_handle handle;
+	unsigned long event;
+	struct dock_station *ds;
+};
+
+static void acpi_dock_deferred_cb(void *context)
+{
+	struct dock_data *data = (struct dock_data *)context;
+
+	dock_notify(data->handle, data->event, data->ds);
+	kfree(data);
+}
+
 static int acpi_dock_notifier_call(struct notifier_block *this,
 	unsigned long event, void *data)
 {
@@ -759,7 +773,16 @@ static int acpi_dock_notifier_call(struct notifier_block *this,
 		return 0;
 	list_for_each_entry(dock_station, &dock_stations, sibiling) {
 		if (dock_station->handle == handle) {
-			dock_notify(handle, event, dock_station);
+			struct dock_data *dock_data;
+
+			dock_data = kmalloc(sizeof(*dock_data), GFP_KERNEL);
+			if (!dock_data)
+				return 0;
+			dock_data->handle = handle;
+			dock_data->event = event;
+			dock_data->ds = dock_station;
+			acpi_os_hotplug_execute(acpi_dock_deferred_cb,
+				dock_data);
 			return 0 ;
 		}
 	}