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