diff options
author | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2013-03-03 17:05:29 -0500 |
---|---|---|
committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2013-03-04 08:25:32 -0500 |
commit | a33ec399e9fc266ba20f9b71d693aa63658bf2aa (patch) | |
tree | 404418949973ac053cfe02df039124d7189db1ed /drivers/acpi/scan.c | |
parent | c56980744ed99994799850903627c4bbb5fed006 (diff) |
ACPI / scan: Introduce common code for ACPI-based device hotplug
Multiple drivers handling hotplug-capable ACPI device nodes install
notify handlers covering the same types of events in a very similar
way. Moreover, those handlers are installed in separate namespace
walks, although that really should be done during namespace scans
carried out by acpi_bus_scan(). This leads to substantial code
duplication, unnecessary overhead and behavior that is hard to
follow.
For this reason, introduce common code in drivers/acpi/scan.c for
handling hotplug-related notification and carrying out device
insertion and eject operations in a generic fashion, such that it
may be used by all of the relevant drivers in the future. To cover
the existing differences between those drivers introduce struct
acpi_hotplug_profile for representing collections of hotplug
settings associated with different ACPI scan handlers that can be
used by the drivers to make the common code reflect their current
behavior.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Acked-by: Toshi Kani <toshi.kani@hp.com>
Tested-by: Toshi Kani <toshi.kani@hp.com>
Diffstat (limited to 'drivers/acpi/scan.c')
-rw-r--r-- | drivers/acpi/scan.c | 269 |
1 files changed, 216 insertions, 53 deletions
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index cc1b0020478b..de73fdf89598 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c | |||
@@ -107,32 +107,19 @@ acpi_device_modalias_show(struct device *dev, struct device_attribute *attr, cha | |||
107 | } | 107 | } |
108 | static DEVICE_ATTR(modalias, 0444, acpi_device_modalias_show, NULL); | 108 | static DEVICE_ATTR(modalias, 0444, acpi_device_modalias_show, NULL); |
109 | 109 | ||
110 | /** | 110 | static int acpi_scan_hot_remove(struct acpi_device *device) |
111 | * acpi_bus_hot_remove_device: hot-remove a device and its children | ||
112 | * @context: struct acpi_eject_event pointer (freed in this func) | ||
113 | * | ||
114 | * Hot-remove a device and its children. This function frees up the | ||
115 | * memory space passed by arg context, so that the caller may call | ||
116 | * this function asynchronously through acpi_os_hotplug_execute(). | ||
117 | */ | ||
118 | void acpi_bus_hot_remove_device(void *context) | ||
119 | { | 111 | { |
120 | struct acpi_eject_event *ej_event = context; | ||
121 | struct acpi_device *device = ej_event->device; | ||
122 | acpi_handle handle = device->handle; | 112 | acpi_handle handle = device->handle; |
123 | acpi_handle temp; | 113 | acpi_handle not_used; |
124 | struct acpi_object_list arg_list; | 114 | struct acpi_object_list arg_list; |
125 | union acpi_object arg; | 115 | union acpi_object arg; |
126 | acpi_status status = AE_OK; | 116 | acpi_status status; |
127 | u32 ost_code = ACPI_OST_SC_NON_SPECIFIC_FAILURE; /* default */ | ||
128 | |||
129 | mutex_lock(&acpi_scan_lock); | ||
130 | 117 | ||
131 | /* If there is no handle, the device node has been unregistered. */ | 118 | /* If there is no handle, the device node has been unregistered. */ |
132 | if (!device->handle) { | 119 | if (!handle) { |
133 | dev_dbg(&device->dev, "ACPI handle missing\n"); | 120 | dev_dbg(&device->dev, "ACPI handle missing\n"); |
134 | put_device(&device->dev); | 121 | put_device(&device->dev); |
135 | goto out; | 122 | return -EINVAL; |
136 | } | 123 | } |
137 | 124 | ||
138 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, | 125 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, |
@@ -143,7 +130,7 @@ void acpi_bus_hot_remove_device(void *context) | |||
143 | put_device(&device->dev); | 130 | put_device(&device->dev); |
144 | device = NULL; | 131 | device = NULL; |
145 | 132 | ||
146 | if (ACPI_SUCCESS(acpi_get_handle(handle, "_LCK", &temp))) { | 133 | if (ACPI_SUCCESS(acpi_get_handle(handle, "_LCK", ¬_used))) { |
147 | arg_list.count = 1; | 134 | arg_list.count = 1; |
148 | arg_list.pointer = &arg; | 135 | arg_list.pointer = &arg; |
149 | arg.type = ACPI_TYPE_INTEGER; | 136 | arg.type = ACPI_TYPE_INTEGER; |
@@ -161,18 +148,158 @@ void acpi_bus_hot_remove_device(void *context) | |||
161 | */ | 148 | */ |
162 | status = acpi_evaluate_object(handle, "_EJ0", &arg_list, NULL); | 149 | status = acpi_evaluate_object(handle, "_EJ0", &arg_list, NULL); |
163 | if (ACPI_FAILURE(status)) { | 150 | if (ACPI_FAILURE(status)) { |
164 | if (status != AE_NOT_FOUND) | 151 | if (status == AE_NOT_FOUND) { |
152 | return -ENODEV; | ||
153 | } else { | ||
165 | acpi_handle_warn(handle, "Eject failed\n"); | 154 | acpi_handle_warn(handle, "Eject failed\n"); |
155 | return -EIO; | ||
156 | } | ||
157 | } | ||
158 | return 0; | ||
159 | } | ||
166 | 160 | ||
167 | /* Tell the firmware the hot-remove operation has failed. */ | 161 | static void acpi_bus_device_eject(void *context) |
168 | acpi_evaluate_hotplug_ost(handle, ej_event->event, | 162 | { |
169 | ost_code, NULL); | 163 | acpi_handle handle = context; |
164 | struct acpi_device *device = NULL; | ||
165 | struct acpi_scan_handler *handler; | ||
166 | u32 ost_code = ACPI_OST_SC_NON_SPECIFIC_FAILURE; | ||
167 | |||
168 | mutex_lock(&acpi_scan_lock); | ||
169 | |||
170 | acpi_bus_get_device(handle, &device); | ||
171 | if (!device) | ||
172 | goto err_out; | ||
173 | |||
174 | handler = device->handler; | ||
175 | if (!handler || !handler->hotplug.enabled) { | ||
176 | ost_code = ACPI_OST_SC_EJECT_NOT_SUPPORTED; | ||
177 | goto err_out; | ||
178 | } | ||
179 | acpi_evaluate_hotplug_ost(handle, ACPI_NOTIFY_EJECT_REQUEST, | ||
180 | ACPI_OST_SC_EJECT_IN_PROGRESS, NULL); | ||
181 | if (handler->hotplug.mode == AHM_CONTAINER) { | ||
182 | device->flags.eject_pending = true; | ||
183 | kobject_uevent(&device->dev.kobj, KOBJ_OFFLINE); | ||
184 | } else { | ||
185 | int error; | ||
186 | |||
187 | get_device(&device->dev); | ||
188 | error = acpi_scan_hot_remove(device); | ||
189 | if (error) | ||
190 | goto err_out; | ||
170 | } | 191 | } |
171 | 192 | ||
172 | out: | 193 | out: |
173 | mutex_unlock(&acpi_scan_lock); | 194 | mutex_unlock(&acpi_scan_lock); |
174 | kfree(context); | ||
175 | return; | 195 | return; |
196 | |||
197 | err_out: | ||
198 | acpi_evaluate_hotplug_ost(handle, ACPI_NOTIFY_EJECT_REQUEST, ost_code, | ||
199 | NULL); | ||
200 | goto out; | ||
201 | } | ||
202 | |||
203 | static void acpi_scan_bus_device_check(acpi_handle handle, u32 ost_source) | ||
204 | { | ||
205 | struct acpi_device *device = NULL; | ||
206 | u32 ost_code = ACPI_OST_SC_NON_SPECIFIC_FAILURE; | ||
207 | int error; | ||
208 | |||
209 | mutex_lock(&acpi_scan_lock); | ||
210 | |||
211 | acpi_bus_get_device(handle, &device); | ||
212 | if (device) { | ||
213 | dev_warn(&device->dev, "Attempt to re-insert\n"); | ||
214 | goto out; | ||
215 | } | ||
216 | acpi_evaluate_hotplug_ost(handle, ost_source, | ||
217 | ACPI_OST_SC_INSERT_IN_PROGRESS, NULL); | ||
218 | error = acpi_bus_scan(handle); | ||
219 | if (error) { | ||
220 | acpi_handle_warn(handle, "Namespace scan failure\n"); | ||
221 | goto out; | ||
222 | } | ||
223 | error = acpi_bus_get_device(handle, &device); | ||
224 | if (error) { | ||
225 | acpi_handle_warn(handle, "Missing device node object\n"); | ||
226 | goto out; | ||
227 | } | ||
228 | ost_code = ACPI_OST_SC_SUCCESS; | ||
229 | if (device->handler && device->handler->hotplug.mode == AHM_CONTAINER) | ||
230 | kobject_uevent(&device->dev.kobj, KOBJ_ONLINE); | ||
231 | |||
232 | out: | ||
233 | acpi_evaluate_hotplug_ost(handle, ost_source, ost_code, NULL); | ||
234 | mutex_unlock(&acpi_scan_lock); | ||
235 | } | ||
236 | |||
237 | static void acpi_scan_bus_check(void *context) | ||
238 | { | ||
239 | acpi_scan_bus_device_check((acpi_handle)context, | ||
240 | ACPI_NOTIFY_BUS_CHECK); | ||
241 | } | ||
242 | |||
243 | static void acpi_scan_device_check(void *context) | ||
244 | { | ||
245 | acpi_scan_bus_device_check((acpi_handle)context, | ||
246 | ACPI_NOTIFY_DEVICE_CHECK); | ||
247 | } | ||
248 | |||
249 | static void acpi_hotplug_notify_cb(acpi_handle handle, u32 type, void *not_used) | ||
250 | { | ||
251 | acpi_osd_exec_callback callback; | ||
252 | acpi_status status; | ||
253 | |||
254 | switch (type) { | ||
255 | case ACPI_NOTIFY_BUS_CHECK: | ||
256 | acpi_handle_debug(handle, "ACPI_NOTIFY_BUS_CHECK event\n"); | ||
257 | callback = acpi_scan_bus_check; | ||
258 | break; | ||
259 | case ACPI_NOTIFY_DEVICE_CHECK: | ||
260 | acpi_handle_debug(handle, "ACPI_NOTIFY_DEVICE_CHECK event\n"); | ||
261 | callback = acpi_scan_device_check; | ||
262 | break; | ||
263 | case ACPI_NOTIFY_EJECT_REQUEST: | ||
264 | acpi_handle_debug(handle, "ACPI_NOTIFY_EJECT_REQUEST event\n"); | ||
265 | callback = acpi_bus_device_eject; | ||
266 | break; | ||
267 | default: | ||
268 | /* non-hotplug event; possibly handled by other handler */ | ||
269 | return; | ||
270 | } | ||
271 | status = acpi_os_hotplug_execute(callback, handle); | ||
272 | if (ACPI_FAILURE(status)) | ||
273 | acpi_evaluate_hotplug_ost(handle, type, | ||
274 | ACPI_OST_SC_NON_SPECIFIC_FAILURE, | ||
275 | NULL); | ||
276 | } | ||
277 | |||
278 | /** | ||
279 | * acpi_bus_hot_remove_device: hot-remove a device and its children | ||
280 | * @context: struct acpi_eject_event pointer (freed in this func) | ||
281 | * | ||
282 | * Hot-remove a device and its children. This function frees up the | ||
283 | * memory space passed by arg context, so that the caller may call | ||
284 | * this function asynchronously through acpi_os_hotplug_execute(). | ||
285 | */ | ||
286 | void acpi_bus_hot_remove_device(void *context) | ||
287 | { | ||
288 | struct acpi_eject_event *ej_event = context; | ||
289 | struct acpi_device *device = ej_event->device; | ||
290 | acpi_handle handle = device->handle; | ||
291 | int error; | ||
292 | |||
293 | mutex_lock(&acpi_scan_lock); | ||
294 | |||
295 | error = acpi_scan_hot_remove(device); | ||
296 | if (error && handle) | ||
297 | acpi_evaluate_hotplug_ost(handle, ej_event->event, | ||
298 | ACPI_OST_SC_NON_SPECIFIC_FAILURE, | ||
299 | NULL); | ||
300 | |||
301 | mutex_unlock(&acpi_scan_lock); | ||
302 | kfree(context); | ||
176 | } | 303 | } |
177 | EXPORT_SYMBOL(acpi_bus_hot_remove_device); | 304 | EXPORT_SYMBOL(acpi_bus_hot_remove_device); |
178 | 305 | ||
@@ -206,51 +333,61 @@ static ssize_t | |||
206 | acpi_eject_store(struct device *d, struct device_attribute *attr, | 333 | acpi_eject_store(struct device *d, struct device_attribute *attr, |
207 | const char *buf, size_t count) | 334 | const char *buf, size_t count) |
208 | { | 335 | { |
209 | int ret = count; | ||
210 | acpi_status status; | ||
211 | acpi_object_type type = 0; | ||
212 | struct acpi_device *acpi_device = to_acpi_device(d); | 336 | struct acpi_device *acpi_device = to_acpi_device(d); |
213 | struct acpi_eject_event *ej_event; | 337 | struct acpi_eject_event *ej_event; |
338 | acpi_object_type not_used; | ||
339 | acpi_status status; | ||
340 | u32 ost_source; | ||
341 | int ret; | ||
214 | 342 | ||
215 | if ((!count) || (buf[0] != '1')) { | 343 | if (!count || buf[0] != '1') |
216 | return -EINVAL; | 344 | return -EINVAL; |
217 | } | ||
218 | if (!acpi_device->driver && !acpi_device->handler) { | ||
219 | ret = -ENODEV; | ||
220 | goto err; | ||
221 | } | ||
222 | status = acpi_get_type(acpi_device->handle, &type); | ||
223 | if (ACPI_FAILURE(status) || (!acpi_device->flags.ejectable)) { | ||
224 | ret = -ENODEV; | ||
225 | goto err; | ||
226 | } | ||
227 | 345 | ||
228 | ej_event = kmalloc(sizeof(*ej_event), GFP_KERNEL); | 346 | if ((!acpi_device->handler || !acpi_device->handler->hotplug.enabled) |
229 | if (!ej_event) { | 347 | && !acpi_device->driver) |
230 | ret = -ENOMEM; | 348 | return -ENODEV; |
231 | goto err; | 349 | |
232 | } | 350 | status = acpi_get_type(acpi_device->handle, ¬_used); |
351 | if (ACPI_FAILURE(status) || !acpi_device->flags.ejectable) | ||
352 | return -ENODEV; | ||
353 | |||
354 | mutex_lock(&acpi_scan_lock); | ||
233 | 355 | ||
234 | get_device(&acpi_device->dev); | ||
235 | ej_event->device = acpi_device; | ||
236 | if (acpi_device->flags.eject_pending) { | 356 | if (acpi_device->flags.eject_pending) { |
237 | /* event originated from ACPI eject notification */ | 357 | /* ACPI eject notification event. */ |
238 | ej_event->event = ACPI_NOTIFY_EJECT_REQUEST; | 358 | ost_source = ACPI_NOTIFY_EJECT_REQUEST; |
239 | acpi_device->flags.eject_pending = 0; | 359 | acpi_device->flags.eject_pending = 0; |
240 | } else { | 360 | } else { |
241 | /* event originated from user */ | 361 | /* Eject initiated by user space. */ |
242 | ej_event->event = ACPI_OST_EC_OSPM_EJECT; | 362 | ost_source = ACPI_OST_EC_OSPM_EJECT; |
243 | (void) acpi_evaluate_hotplug_ost(acpi_device->handle, | ||
244 | ej_event->event, ACPI_OST_SC_EJECT_IN_PROGRESS, NULL); | ||
245 | } | 363 | } |
246 | 364 | ej_event = kmalloc(sizeof(*ej_event), GFP_KERNEL); | |
365 | if (!ej_event) { | ||
366 | ret = -ENOMEM; | ||
367 | goto err_out; | ||
368 | } | ||
369 | acpi_evaluate_hotplug_ost(acpi_device->handle, ost_source, | ||
370 | ACPI_OST_SC_EJECT_IN_PROGRESS, NULL); | ||
371 | ej_event->device = acpi_device; | ||
372 | ej_event->event = ost_source; | ||
373 | get_device(&acpi_device->dev); | ||
247 | status = acpi_os_hotplug_execute(acpi_bus_hot_remove_device, ej_event); | 374 | status = acpi_os_hotplug_execute(acpi_bus_hot_remove_device, ej_event); |
248 | if (ACPI_FAILURE(status)) { | 375 | if (ACPI_FAILURE(status)) { |
249 | put_device(&acpi_device->dev); | 376 | put_device(&acpi_device->dev); |
250 | kfree(ej_event); | 377 | kfree(ej_event); |
378 | ret = status == AE_NO_MEMORY ? -ENOMEM : -EAGAIN; | ||
379 | goto err_out; | ||
251 | } | 380 | } |
252 | err: | 381 | ret = count; |
382 | |||
383 | out: | ||
384 | mutex_unlock(&acpi_scan_lock); | ||
253 | return ret; | 385 | return ret; |
386 | |||
387 | err_out: | ||
388 | acpi_evaluate_hotplug_ost(acpi_device->handle, ost_source, | ||
389 | ACPI_OST_SC_NON_SPECIFIC_FAILURE, NULL); | ||
390 | goto out; | ||
254 | } | 391 | } |
255 | 392 | ||
256 | static DEVICE_ATTR(eject, 0200, NULL, acpi_eject_store); | 393 | static DEVICE_ATTR(eject, 0200, NULL, acpi_eject_store); |
@@ -1555,6 +1692,30 @@ static struct acpi_scan_handler *acpi_scan_match_handler(char *idstr, | |||
1555 | return NULL; | 1692 | return NULL; |
1556 | } | 1693 | } |
1557 | 1694 | ||
1695 | static void acpi_scan_init_hotplug(acpi_handle handle) | ||
1696 | { | ||
1697 | struct acpi_device_info *info; | ||
1698 | struct acpi_scan_handler *handler; | ||
1699 | |||
1700 | if (ACPI_FAILURE(acpi_get_object_info(handle, &info))) | ||
1701 | return; | ||
1702 | |||
1703 | if (!(info->valid & ACPI_VALID_HID)) { | ||
1704 | kfree(info); | ||
1705 | return; | ||
1706 | } | ||
1707 | |||
1708 | /* | ||
1709 | * This relies on the fact that acpi_install_notify_handler() will not | ||
1710 | * install the same notify handler routine twice for the same handle. | ||
1711 | */ | ||
1712 | handler = acpi_scan_match_handler(info->hardware_id.string, NULL); | ||
1713 | kfree(info); | ||
1714 | if (handler && handler->hotplug.enabled) | ||
1715 | acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY, | ||
1716 | acpi_hotplug_notify_cb, NULL); | ||
1717 | } | ||
1718 | |||
1558 | static acpi_status acpi_bus_check_add(acpi_handle handle, u32 lvl_not_used, | 1719 | static acpi_status acpi_bus_check_add(acpi_handle handle, u32 lvl_not_used, |
1559 | void *not_used, void **return_value) | 1720 | void *not_used, void **return_value) |
1560 | { | 1721 | { |
@@ -1577,6 +1738,8 @@ static acpi_status acpi_bus_check_add(acpi_handle handle, u32 lvl_not_used, | |||
1577 | return AE_OK; | 1738 | return AE_OK; |
1578 | } | 1739 | } |
1579 | 1740 | ||
1741 | acpi_scan_init_hotplug(handle); | ||
1742 | |||
1580 | if (!(sta & ACPI_STA_DEVICE_PRESENT) && | 1743 | if (!(sta & ACPI_STA_DEVICE_PRESENT) && |
1581 | !(sta & ACPI_STA_DEVICE_FUNCTIONING)) { | 1744 | !(sta & ACPI_STA_DEVICE_FUNCTIONING)) { |
1582 | struct acpi_device_wakeup wakeup; | 1745 | struct acpi_device_wakeup wakeup; |