xref: /illumos-gate/usr/src/uts/i86pc/io/acpi/acpinex/acpinex_event.c (revision b23a7923c0fe493ed4beebaf775ad634ea217080)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 2010, Intel Corporation.
24  * All rights reserved.
25  */
26 
27 #include <sys/types.h>
28 #include <sys/atomic.h>
29 #include <sys/bitmap.h>
30 #include <sys/cmn_err.h>
31 #include <sys/note.h>
32 #include <sys/sunndi.h>
33 #include <sys/fastboot_impl.h>
34 #include <sys/sysevent.h>
35 #include <sys/sysevent/dr.h>
36 #include <sys/sysevent/eventdefs.h>
37 #include <sys/acpi/acpi.h>
38 #include <sys/acpica.h>
39 #include <sys/acpidev.h>
40 #include <sys/acpidev_dr.h>
41 #include <sys/acpinex.h>
42 
43 int acpinex_event_support_remove = 0;
44 
45 static volatile uint_t acpinex_dr_event_cnt = 0;
46 static ulong_t acpinex_object_type_mask[BT_BITOUL(ACPI_TYPE_NS_NODE_MAX + 1)];
47 
48 /*
49  * Generate DR_REQ event to syseventd.
50  * Please refer to sys/sysevent/dr.h for message definition.
51  */
52 static int
53 acpinex_event_generate_event(dev_info_t *dip, ACPI_HANDLE hdl, int req,
54     int event, char *objname)
55 {
56 	int rv = 0;
57 	sysevent_id_t eid;
58 	sysevent_value_t evnt_val;
59 	sysevent_attr_list_t *evnt_attr_list = NULL;
60 	char *attach_pnt;
61 	char event_type[32];
62 
63 	/* Add "attachment point" attribute. */
64 	attach_pnt = kmem_zalloc(MAXPATHLEN, KM_SLEEP);
65 	if (ACPI_FAILURE(acpidev_dr_get_attachment_point(hdl,
66 	    attach_pnt, MAXPATHLEN))) {
67 		cmn_err(CE_WARN,
68 		    "!acpinex: failed to generate AP name for %s.", objname);
69 		kmem_free(attach_pnt, MAXPATHLEN);
70 		return (-1);
71 	}
72 	ASSERT(attach_pnt[0] != '\0');
73 	evnt_val.value_type = SE_DATA_TYPE_STRING;
74 	evnt_val.value.sv_string = attach_pnt;
75 	rv = sysevent_add_attr(&evnt_attr_list, DR_AP_ID, &evnt_val, KM_SLEEP);
76 	if (rv != 0) {
77 		cmn_err(CE_WARN,
78 		    "!acpinex: failed to add attr [%s] for %s event.",
79 		    DR_AP_ID, EC_DR);
80 		kmem_free(attach_pnt, MAXPATHLEN);
81 		return (rv);
82 	}
83 
84 	/* Add "request type" attribute. */
85 	evnt_val.value_type = SE_DATA_TYPE_STRING;
86 	evnt_val.value.sv_string = SE_REQ2STR(req);
87 	rv = sysevent_add_attr(&evnt_attr_list, DR_REQ_TYPE, &evnt_val,
88 	    KM_SLEEP);
89 	if (rv != 0) {
90 		cmn_err(CE_WARN,
91 		    "!acpinex: failed to add attr [%s] for %s event.",
92 		    DR_REQ_TYPE, EC_DR);
93 		sysevent_free_attr(evnt_attr_list);
94 		kmem_free(attach_pnt, MAXPATHLEN);
95 		return (rv);
96 	}
97 
98 	/* Add "acpi-event-type" attribute. */
99 	switch (event) {
100 	case ACPI_NOTIFY_BUS_CHECK:
101 		(void) snprintf(event_type, sizeof (event_type),
102 		    ACPIDEV_EVENT_TYPE_BUS_CHECK);
103 		break;
104 	case ACPI_NOTIFY_DEVICE_CHECK:
105 		(void) snprintf(event_type, sizeof (event_type),
106 		    ACPIDEV_EVENT_TYPE_DEVICE_CHECK);
107 		break;
108 	case ACPI_NOTIFY_DEVICE_CHECK_LIGHT:
109 		(void) snprintf(event_type, sizeof (event_type),
110 		    ACPIDEV_EVENT_TYPE_DEVICE_CHECK_LIGHT);
111 		break;
112 	case ACPI_NOTIFY_EJECT_REQUEST:
113 		(void) snprintf(event_type, sizeof (event_type),
114 		    ACPIDEV_EVENT_TYPE_EJECT_REQUEST);
115 		break;
116 	default:
117 		cmn_err(CE_WARN,
118 		    "!acpinex: unknown ACPI event type %d.", event);
119 		sysevent_free_attr(evnt_attr_list);
120 		kmem_free(attach_pnt, MAXPATHLEN);
121 		return (-1);
122 	}
123 	evnt_val.value_type = SE_DATA_TYPE_STRING;
124 	evnt_val.value.sv_string = event_type;
125 	rv = sysevent_add_attr(&evnt_attr_list, ACPIDEV_EVENT_TYPE_ATTR_NAME,
126 	    &evnt_val, KM_SLEEP);
127 	if (rv != 0) {
128 		cmn_err(CE_WARN,
129 		    "!acpinex: failed to add attr [%s] for %s event.",
130 		    ACPIDEV_EVENT_TYPE_ATTR_NAME, EC_DR);
131 		sysevent_free_attr(evnt_attr_list);
132 		kmem_free(attach_pnt, MAXPATHLEN);
133 		return (rv);
134 	}
135 
136 	rv = ddi_log_sysevent(dip, DDI_VENDOR_SUNW, EC_DR, ESC_DR_REQ,
137 	    evnt_attr_list, &eid, KM_SLEEP);
138 	if (rv != DDI_SUCCESS) {
139 		cmn_err(CE_WARN,
140 		    "!acpinex: failed to log DR_REQ event for %s.", objname);
141 		rv = -1;
142 	}
143 
144 	nvlist_free(evnt_attr_list);
145 	kmem_free(attach_pnt, MAXPATHLEN);
146 
147 	return (rv);
148 }
149 
150 /*
151  * Event handler for ACPI EJECT_REQUEST notifications.
152  * EJECT_REQUEST notifications should be generated on the device to be ejected,
153  * so no need to scan subtree of it.
154  * It also invokes ACPI _OST method to update event status if call_ost is true.
155  */
156 static void
157 acpinex_event_handle_eject_request(ACPI_HANDLE hdl, acpinex_softstate_t *sp,
158     boolean_t call_ost)
159 {
160 	int code;
161 	char *objname;
162 
163 	ASSERT(hdl != NULL);
164 	objname = acpidev_get_object_name(hdl);
165 
166 	ASSERT(sp != NULL);
167 	ASSERT(sp->ans_dip != NULL && sp->ans_hdl != NULL);
168 	if (sp == NULL || sp->ans_dip == NULL || sp->ans_hdl == NULL) {
169 		if (call_ost) {
170 			(void) acpidev_eval_ost(hdl, ACPI_NOTIFY_EJECT_REQUEST,
171 			    ACPI_OST_STA_FAILURE, NULL, 0);
172 		}
173 		ACPINEX_DEBUG(CE_WARN,
174 		    "!acpinex: softstate data structure is invalid.");
175 		cmn_err(CE_WARN,
176 		    "!acpinex: failed to handle EJECT_REQUEST event from %s.",
177 		    objname);
178 		acpidev_free_object_name(objname);
179 		return;
180 	}
181 
182 	if (acpinex_event_support_remove == 0) {
183 		cmn_err(CE_WARN,
184 		    "!acpinex: hot-removing of device %s is unsupported.",
185 		    objname);
186 		code = ACPI_OST_STA_EJECT_NOT_SUPPORT;
187 	} else if (acpinex_event_generate_event(sp->ans_dip, hdl,
188 	    SE_OUTGOING_RES, ACPI_NOTIFY_EJECT_REQUEST, objname) != 0) {
189 		cmn_err(CE_WARN, "!acpinex: failed to generate ESC_DR_REQ "
190 		    "event for device eject request from %s.", objname);
191 		code = ACPI_OST_STA_FAILURE;
192 	} else {
193 		cmn_err(CE_NOTE, "!acpinex: generate ESC_DR_REQ event for "
194 		    "device eject request from %s.", objname);
195 		code = ACPI_OST_STA_EJECT_IN_PROGRESS;
196 	}
197 	if (call_ost) {
198 		(void) acpidev_eval_ost(hdl, ACPI_NOTIFY_EJECT_REQUEST,
199 		    code, NULL, 0);
200 	}
201 
202 	acpidev_free_object_name(objname);
203 }
204 
205 struct acpinex_event_check_arg {
206 	acpinex_softstate_t	*softstatep;
207 	int			event_type;
208 	uint32_t		device_insert;
209 	uint32_t		device_remove;
210 	uint32_t		device_fail;
211 };
212 
213 static ACPI_STATUS
214 acpinex_event_handle_check_one(ACPI_HANDLE hdl, UINT32 lvl, void *ctx,
215     void **retval)
216 {
217 	_NOTE(ARGUNUSED(lvl, retval));
218 
219 	char *objname;
220 	int status, psta, csta;
221 	acpidev_data_handle_t dhdl;
222 	struct acpinex_event_check_arg *argp;
223 
224 	ASSERT(hdl != NULL);
225 	ASSERT(ctx != NULL);
226 	argp = (struct acpinex_event_check_arg *)ctx;
227 
228 	dhdl = acpidev_data_get_handle(hdl);
229 	if (dhdl == NULL) {
230 		/* Skip subtree if failed to get the data handle. */
231 		ACPINEX_DEBUG(CE_NOTE,
232 		    "!acpinex: failed to get data associated with %p.", hdl);
233 		return (AE_CTRL_DEPTH);
234 	} else if (!acpidev_data_dr_capable(dhdl)) {
235 		return (AE_OK);
236 	}
237 
238 	objname = acpidev_get_object_name(hdl);
239 
240 	status = 0;
241 	/* Query previous device status. */
242 	psta = acpidev_data_get_status(dhdl);
243 	if (acpidev_check_device_enabled(psta)) {
244 		status |= 0x1;
245 	}
246 	/* Query current device status. */
247 	csta = acpidev_query_device_status(hdl);
248 	if (acpidev_check_device_enabled(csta)) {
249 		status |= 0x2;
250 	}
251 
252 	switch (status) {
253 	case 0x0:
254 		/*FALLTHROUGH*/
255 	case 0x3:
256 		/* No status changes, keep on walking. */
257 		acpidev_free_object_name(objname);
258 		return (AE_OK);
259 
260 	case 0x1:
261 		/* Surprising removal. */
262 		cmn_err(CE_WARN,
263 		    "!acpinex: device %s has been surprisingly removed.",
264 		    objname);
265 		if (argp->event_type == ACPI_NOTIFY_BUS_CHECK) {
266 			/*
267 			 * According to ACPI spec, BUS_CHECK notification
268 			 * should be triggered for hot-adding events only.
269 			 */
270 			ACPINEX_DEBUG(CE_WARN,
271 			    "!acpinex: device %s has been surprisingly removed "
272 			    "when handling BUS_CHECK event.", objname);
273 		}
274 		acpidev_free_object_name(objname);
275 		argp->device_remove++;
276 		return (AE_CTRL_DEPTH);
277 
278 	case 0x2:
279 		/* Hot-adding. */
280 		ACPINEX_DEBUG(CE_NOTE,
281 		    "!acpinex: device %s has been inserted.", objname);
282 		argp->device_insert++;
283 		if (acpinex_event_generate_event(argp->softstatep->ans_dip, hdl,
284 		    SE_INCOMING_RES, argp->event_type, objname) != 0) {
285 			cmn_err(CE_WARN,
286 			    "!acpinex: failed to generate ESC_DR_REQ event for "
287 			    "device insert request from %s.", objname);
288 			argp->device_fail++;
289 		} else {
290 			cmn_err(CE_NOTE, "!acpinex: generate ESC_DR_REQ event "
291 			    "for device insert request from %s.", objname);
292 		}
293 		acpidev_free_object_name(objname);
294 		return (AE_OK);
295 
296 	default:
297 		ASSERT(0);
298 		break;
299 	}
300 
301 	return (AE_ERROR);
302 }
303 
304 /*
305  * Event handler for BUS_CHECK/DEVICE_CHECK/DEVICE_CHECK_LIGHT notifications.
306  * These events may be signaled on parent/ancestor of devices to be hot-added,
307  * so need to scan ACPI namespace to figure out devices in question.
308  * It also invokes ACPI _OST method to update event status if call_ost is true.
309  */
310 static void
311 acpinex_event_handle_check_request(int event, ACPI_HANDLE hdl,
312     acpinex_softstate_t *sp, boolean_t call_ost)
313 {
314 	ACPI_STATUS rv;
315 	int code;
316 	char *objname;
317 	struct acpinex_event_check_arg arg;
318 
319 	ASSERT(hdl != NULL);
320 	objname = acpidev_get_object_name(hdl);
321 
322 	ASSERT(sp != NULL);
323 	ASSERT(sp->ans_dip != NULL && sp->ans_hdl != NULL);
324 	if (sp == NULL || sp->ans_dip == NULL || sp->ans_hdl == NULL) {
325 		if (call_ost) {
326 			(void) acpidev_eval_ost(hdl, event,
327 			    ACPI_OST_STA_FAILURE, NULL, 0);
328 		}
329 		ACPINEX_DEBUG(CE_WARN,
330 		    "!acpinex: softstate data structure is invalid.");
331 		cmn_err(CE_WARN, "!acpinex: failed to handle "
332 		    "BUS/DEVICE_CHECK event from %s.", objname);
333 		acpidev_free_object_name(objname);
334 		return;
335 	}
336 
337 	bzero(&arg, sizeof (arg));
338 	arg.event_type = event;
339 	arg.softstatep = sp;
340 	rv = acpinex_event_handle_check_one(hdl, 0, &arg, NULL);
341 	if (ACPI_SUCCESS(rv)) {
342 		rv = AcpiWalkNamespace(ACPI_TYPE_DEVICE, hdl,
343 		    ACPIDEV_MAX_ENUM_LEVELS,
344 		    &acpinex_event_handle_check_one, NULL, &arg, NULL);
345 	}
346 
347 	if (ACPI_FAILURE(rv)) {
348 		/* Failed to scan the ACPI namespace. */
349 		cmn_err(CE_WARN, "!acpinex: failed to handle event %d from %s.",
350 		    event, objname);
351 		code = ACPI_OST_STA_FAILURE;
352 	} else if (arg.device_remove != 0) {
353 		/* Surprising removal happened. */
354 		ACPINEX_DEBUG(CE_WARN,
355 		    "!acpinex: some devices have been surprisingly removed.");
356 		code = ACPI_OST_STA_NOT_SUPPORT;
357 	} else if (arg.device_fail != 0) {
358 		/* Failed to handle some devices. */
359 		ACPINEX_DEBUG(CE_WARN,
360 		    "!acpinex: failed to check status of some devices.");
361 		code = ACPI_OST_STA_FAILURE;
362 	} else if (arg.device_insert == 0) {
363 		/* No hot-added devices found. */
364 		cmn_err(CE_WARN,
365 		    "!acpinex: no hot-added devices under %s found.", objname);
366 		code = ACPI_OST_STA_FAILURE;
367 	} else {
368 		code = ACPI_OST_STA_INSERT_IN_PROGRESS;
369 	}
370 	if (call_ost) {
371 		(void) acpidev_eval_ost(hdl, event, code, NULL, 0);
372 	}
373 
374 	acpidev_free_object_name(objname);
375 }
376 
377 static void
378 acpinex_event_system_handler(ACPI_HANDLE hdl, UINT32 type, void *arg)
379 {
380 	acpinex_softstate_t *sp;
381 
382 	ASSERT(hdl != NULL);
383 	ASSERT(arg != NULL);
384 	sp = (acpinex_softstate_t *)arg;
385 
386 	acpidev_dr_lock_all();
387 	mutex_enter(&sp->ans_lock);
388 
389 	switch (type) {
390 	case ACPI_NOTIFY_BUS_CHECK:
391 		/*
392 		 * Bus Check. This notification is performed on a device object
393 		 * to indicate to OSPM that it needs to perform the Plug and
394 		 * Play re-enumeration operation on the device tree starting
395 		 * from the point where it has been notified. OSPM will only
396 		 * perform this operation at boot, and when notified. It is
397 		 * the responsibility of the ACPI AML code to notify OSPM at
398 		 * any other times that this operation is required. The more
399 		 * accurately and closer to the actual device tree change the
400 		 * notification can be done, the more efficient the operating
401 		 * system response will be; however, it can also be an issue
402 		 * when a device change cannot be confirmed. For example, if
403 		 * the hardware cannot notice a device change for a particular
404 		 * location during a system sleeping state, it issues a Bus
405 		 * Check notification on wake to inform OSPM that it needs to
406 		 * check the configuration for a device change.
407 		 */
408 		/*FALLTHROUGH*/
409 	case ACPI_NOTIFY_DEVICE_CHECK:
410 		/*
411 		 * Device Check. Used to notify OSPM that the device either
412 		 * appeared or disappeared. If the device has appeared, OSPM
413 		 * will re-enumerate from the parent. If the device has
414 		 * disappeared, OSPM will invalidate the state of the device.
415 		 * OSPM may optimize out re-enumeration. If _DCK is present,
416 		 * then Notify(object,1) is assumed to indicate an undock
417 		 * request.
418 		 */
419 		/*FALLTHROUGH*/
420 	case ACPI_NOTIFY_DEVICE_CHECK_LIGHT:
421 		/*
422 		 * Device Check Light. Used to notify OSPM that the device
423 		 * either appeared or disappeared. If the device has appeared,
424 		 * OSPM will re-enumerate from the device itself, not the
425 		 * parent. If the device has disappeared, OSPM will invalidate
426 		 * the state of the device.
427 		 */
428 		atomic_inc_uint(&acpinex_dr_event_cnt);
429 		acpinex_event_handle_check_request(type, hdl, sp, B_TRUE);
430 		break;
431 
432 	case ACPI_NOTIFY_EJECT_REQUEST:
433 		/*
434 		 * Eject Request. Used to notify OSPM that the device should
435 		 * be ejected, and that OSPM needs to perform the Plug and Play
436 		 * ejection operation. OSPM will run the _EJx method.
437 		 */
438 		atomic_inc_uint(&acpinex_dr_event_cnt);
439 		acpinex_event_handle_eject_request(hdl, sp, B_TRUE);
440 		break;
441 
442 	default:
443 		ACPINEX_DEBUG(CE_NOTE,
444 		    "!acpinex: unhandled event(%d) on hdl %p under %s.",
445 		    type, hdl, sp->ans_path);
446 		(void) acpidev_eval_ost(hdl, type, ACPI_OST_STA_NOT_SUPPORT,
447 		    NULL, 0);
448 		break;
449 	}
450 
451 	if (acpinex_dr_event_cnt != 0) {
452 		/*
453 		 * Disable fast reboot if a CPU/MEM/IOH hotplug event happens.
454 		 * Note: this is a temporary solution and will be revised when
455 		 * fast reboot can support CPU/MEM/IOH DR operations in the
456 		 * future.
457 		 *
458 		 * ACPI BIOS generates some static ACPI tables, such as MADT,
459 		 * SRAT and SLIT, to describe the system hardware configuration
460 		 * on power-on. When a CPU/MEM/IOH hotplug event happens, those
461 		 * static tables won't be updated and will become stale.
462 		 *
463 		 * If we reset the system by fast reboot, BIOS will have no
464 		 * chance to regenerate those staled static tables. Fast reboot
465 		 * can't tolerate such inconsistency between staled ACPI tables
466 		 * and real hardware configuration yet.
467 		 *
468 		 * A temporary solution is introduced to disable fast reboot if
469 		 * CPU/MEM/IOH hotplug event happens. This solution should be
470 		 * revised when fast reboot is enhanced to support CPU/MEM/IOH
471 		 * DR operations.
472 		 */
473 		fastreboot_disable(FBNS_HOTPLUG);
474 	}
475 
476 	mutex_exit(&sp->ans_lock);
477 	acpidev_dr_unlock_all();
478 }
479 
480 /*
481  * Install event handler for ACPI system events.
482  * Acpinex driver handles ACPI system events for its children,
483  * device specific events will be handled by device drivers.
484  * Return DDI_SUCCESS on success, and DDI_FAILURE on failure.
485  */
486 static int
487 acpinex_event_install_handler(ACPI_HANDLE hdl, void *arg,
488     ACPI_DEVICE_INFO *infop, acpidev_data_handle_t dhdl)
489 {
490 	int rc = DDI_SUCCESS;
491 
492 	ASSERT(hdl != NULL);
493 	ASSERT(dhdl != NULL);
494 	ASSERT(infop != NULL);
495 
496 	/*
497 	 * Check whether the event handler has already been installed on the
498 	 * device object. With the introduction of ACPI Alias objects, which are
499 	 * similar to symlinks in file systems, there may be multiple name
500 	 * objects in the ACPI namespace pointing to the same underlying device
501 	 * object. Those Alias objects need to be filtered out, otherwise
502 	 * it will attempt to install the event handler multiple times on the
503 	 * same device object which will fail.
504 	 */
505 	if (acpidev_data_get_flag(dhdl, ACPIDEV_DATA_HANDLER_READY)) {
506 		return (DDI_SUCCESS);
507 	}
508 	if (ACPI_SUCCESS(AcpiInstallNotifyHandler(hdl, ACPI_SYSTEM_NOTIFY,
509 	    acpinex_event_system_handler, arg))) {
510 		acpidev_data_set_flag(dhdl, ACPIDEV_DATA_HANDLER_READY);
511 	} else {
512 		char *objname;
513 
514 		objname = acpidev_get_object_name(hdl);
515 		cmn_err(CE_WARN,
516 		    "!acpinex: failed to install system event handler for %s.",
517 		    objname);
518 		acpidev_free_object_name(objname);
519 		rc = DDI_FAILURE;
520 	}
521 
522 	return (rc);
523 }
524 
525 /*
526  * Uninstall event handler for ACPI system events.
527  * Return DDI_SUCCESS on success, and DDI_FAILURE on failure.
528  */
529 static int
530 acpinex_event_uninstall_handler(ACPI_HANDLE hdl, ACPI_DEVICE_INFO *infop,
531     acpidev_data_handle_t dhdl)
532 {
533 	ASSERT(hdl != NULL);
534 	ASSERT(dhdl != NULL);
535 	ASSERT(infop != NULL);
536 
537 	if (!acpidev_data_get_flag(dhdl, ACPIDEV_DATA_HANDLER_READY)) {
538 		return (DDI_SUCCESS);
539 	}
540 	if (ACPI_SUCCESS(AcpiRemoveNotifyHandler(hdl, ACPI_SYSTEM_NOTIFY,
541 	    acpinex_event_system_handler))) {
542 		acpidev_data_clear_flag(dhdl, ACPIDEV_DATA_HANDLER_READY);
543 	} else {
544 		char *objname;
545 
546 		objname = acpidev_get_object_name(hdl);
547 		cmn_err(CE_WARN, "!acpinex: failed to uninstall system event "
548 		    "handler for %s.", objname);
549 		acpidev_free_object_name(objname);
550 		return (DDI_FAILURE);
551 	}
552 
553 	return (DDI_SUCCESS);
554 }
555 
556 /*
557  * Install/uninstall ACPI system event handler for child objects of hdl.
558  * Return DDI_SUCCESS on success, and DDI_FAILURE on failure.
559  */
560 static int
561 acpinex_event_walk(boolean_t init, acpinex_softstate_t *sp, ACPI_HANDLE hdl)
562 {
563 	int rc;
564 	int retval = DDI_SUCCESS;
565 	dev_info_t *dip;
566 	ACPI_HANDLE child = NULL;
567 	ACPI_OBJECT_TYPE type;
568 	ACPI_DEVICE_INFO *infop;
569 	acpidev_data_handle_t dhdl;
570 
571 	/* Walk all child objects. */
572 	ASSERT(hdl != NULL);
573 	while (ACPI_SUCCESS(AcpiGetNextObject(ACPI_TYPE_ANY, hdl, child,
574 	    &child))) {
575 		/* Skip unwanted object types. */
576 		if (ACPI_FAILURE(AcpiGetType(child, &type)) ||
577 		    type > ACPI_TYPE_NS_NODE_MAX ||
578 		    BT_TEST(acpinex_object_type_mask, type) == 0) {
579 			continue;
580 		}
581 
582 		/* Get data associated with the object. Skip it if fails. */
583 		dhdl = acpidev_data_get_handle(child);
584 		if (dhdl == NULL) {
585 			ACPINEX_DEBUG(CE_NOTE, "!acpinex: failed to get data "
586 			    "associated with %p, skip.", child);
587 			continue;
588 		}
589 
590 		/* Query ACPI object info for the object. */
591 		if (ACPI_FAILURE(AcpiGetObjectInfo(child, &infop))) {
592 			cmn_err(CE_WARN,
593 			    "!acpidnex: failed to get object info for %p.",
594 			    child);
595 			continue;
596 		}
597 
598 		if (init) {
599 			rc = acpinex_event_install_handler(child, sp, infop,
600 			    dhdl);
601 			if (rc != DDI_SUCCESS) {
602 				ACPINEX_DEBUG(CE_WARN, "!acpinex: failed to "
603 				    "install handler for child %p of %s.",
604 				    child, sp->ans_path);
605 				retval = DDI_FAILURE;
606 			/*
607 			 * Try to handle descendants if both of the
608 			 * following two conditions are true:
609 			 * 1) Device corresponding to the current object is
610 			 *    enabled. If the device is absent/disabled,
611 			 *    no notification should be generated from
612 			 *    descendant objects of it.
613 			 * 2) No Solaris device node has been created for the
614 			 *    current object yet. If the device node has been
615 			 *    created for the current object, notification
616 			 *    events from child objects should be handled by
617 			 *    the corresponding driver.
618 			 */
619 			} else if (acpidev_check_device_enabled(
620 			    acpidev_data_get_status(dhdl)) &&
621 			    ACPI_FAILURE(acpica_get_devinfo(child, &dip))) {
622 				rc = acpinex_event_walk(B_TRUE, sp, child);
623 				if (rc != DDI_SUCCESS) {
624 					ACPINEX_DEBUG(CE_WARN,
625 					    "!acpinex: failed to install "
626 					    "handler for descendants of %s.",
627 					    sp->ans_path);
628 					retval = DDI_FAILURE;
629 				}
630 			}
631 		} else {
632 			rc = DDI_SUCCESS;
633 			/* Uninstall handler for descendants if needed. */
634 			if (ACPI_FAILURE(acpica_get_devinfo(child, &dip))) {
635 				rc = acpinex_event_walk(B_FALSE, sp, child);
636 			}
637 			if (rc == DDI_SUCCESS) {
638 				rc = acpinex_event_uninstall_handler(child,
639 				    infop, dhdl);
640 			}
641 			/* Undo will be done by caller in case of failure. */
642 			if (rc != DDI_SUCCESS) {
643 				ACPINEX_DEBUG(CE_WARN, "!acpinex: failed to "
644 				    "uninstall handler for descendants of %s.",
645 				    sp->ans_path);
646 				AcpiOsFree(infop);
647 				retval = DDI_FAILURE;
648 				break;
649 			}
650 		}
651 
652 		/* Release cached resources. */
653 		AcpiOsFree(infop);
654 	}
655 
656 	return (retval);
657 }
658 
659 int
660 acpinex_event_scan(acpinex_softstate_t *sp, boolean_t init)
661 {
662 	int rc;
663 
664 	ASSERT(sp != NULL);
665 	ASSERT(sp->ans_hdl != NULL);
666 	ASSERT(sp->ans_dip != NULL);
667 	if (sp == NULL || sp->ans_hdl == NULL || sp->ans_dip == NULL) {
668 		ACPINEX_DEBUG(CE_WARN,
669 		    "!acpinex: invalid parameter to acpinex_event_scan().");
670 		return (DDI_FAILURE);
671 	}
672 
673 	/* Lock current device node and walk all child device nodes of it. */
674 	mutex_enter(&sp->ans_lock);
675 
676 	rc = acpinex_event_walk(init, sp, sp->ans_hdl);
677 	if (rc != DDI_SUCCESS) {
678 		if (init) {
679 			ACPINEX_DEBUG(CE_WARN, "!acpinex: failed to "
680 			    "configure child objects of %s.", sp->ans_path);
681 			rc = DDI_FAILURE;
682 		} else {
683 			ACPINEX_DEBUG(CE_WARN, "!acpinex: failed to "
684 			    "unconfigure child objects of %s.", sp->ans_path);
685 			/* Undo in case of errors */
686 			(void) acpinex_event_walk(B_TRUE, sp, sp->ans_hdl);
687 			rc = DDI_FAILURE;
688 		}
689 	}
690 
691 	mutex_exit(&sp->ans_lock);
692 
693 	return (rc);
694 }
695 
696 void
697 acpinex_event_init(void)
698 {
699 	/*
700 	 * According to ACPI specifications, notification is only supported on
701 	 * Device, Processor and ThermalZone. Currently we only need to handle
702 	 * Device and Processor objects.
703 	 */
704 	BT_SET(acpinex_object_type_mask, ACPI_TYPE_PROCESSOR);
705 	BT_SET(acpinex_object_type_mask, ACPI_TYPE_DEVICE);
706 }
707 
708 void
709 acpinex_event_fini(void)
710 {
711 	bzero(acpinex_object_type_mask, sizeof (acpinex_object_type_mask));
712 }
713