xref: /illumos-gate/usr/src/uts/common/os/ddi_hp_impl.c (revision 6f1fa39e3cf1b335f342bbca41590e9d76ab29b7)
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  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  *
25  * Copyright 2019 Joyent, Inc.
26  */
27 
28 /*
29  * Sun DDI hotplug implementation specific functions
30  */
31 
32 /*
33  *			HOTPLUG FRAMEWORK
34  *
35  * The hotplug framework (also referred to "SHP", for "Solaris Hotplug
36  * Framework") refers to a large set of userland and kernel interfaces,
37  * including those in this file, that provide functionality related to device
38  * hotplug.
39  *
40  * Hotplug is a broad term that refers to both removal and insertion of devices
41  * on a live system. Such operations can have varying levels of notification to
42  * the system. Coordinated hotplug means that the operating system is notified
43  * in advance that a device will have a hotplug operation performed on it.
44  * Non-coordinated hotplug, also called "surprise removal", does not have such
45  * notification, and the device is simply removed or inserted from the system.
46  *
47  * The goals of a correct hotplug operation will vary based on the device. In
48  * general, though, we want the system to gracefully notice the device change
49  * and clean up (or create) any relevant structures related to using the device
50  * in the system.
51  *
52  * The goals of the hotplug framework are to provide common interfaces for nexus
53  * drivers, device drivers, and userland programs to build a foundation for
54  * implementing hotplug for a variety of devices. Notably, common support for
55  * PCIe devices is available. See also: the nexus driver for PCIe devices at
56  * uts/i86pc/io/pciex/npe.c.
57  *
58  *
59  * TERMINOLOGY
60  *
61  *	The following terms may be useful when exploring hotplug-related code.
62  *
63  *	PHYSICAL HOTPLUG
64  *	Refers to hotplug operations on a physical hardware receptacle.
65  *
66  *	VIRTUAL HOTPLUG
67  *	Refers to hotplug operations on an arbitrary device node in the device
68  *	tree.
69  *
70  *	CONNECTION (often abbreviated "cn")
71  *	A place where either physical or virtual hotplug happens. This is a more
72  *	generic term to refer to "connectors" and "ports", which represent
73  *	physical and virtual places where hotplug happens, respectively.
74  *
75  *	CONNECTOR
76  *	A place where physical hotplug happens. For example: a PCIe slot, a USB
77  *	port, a SAS port, and a fiber channel port are all connectors.
78  *
79  *	PORT
80  *	A place where virtual hotplug happens. A port refers to an arbitrary
81  *	place under a nexus dev_info node in the device tree.
82  *
83  *
84  * CONNECTION STATE MACHINE
85  *
86  * Connections have the states below. Connectors and ports are grouped into
87  * the same state machine. It is worth noting that the edges here are incomplete
88  * -- it is possible for a connection to move straight from ENABLED to EMPTY,
89  * for instance, if there is a surprise removal of its device.
90  *
91  * State changes are kicked off through two ways:
92  *	- Through the nexus driver interface, ndi_hp_state_change_req. PCIe
93  *	nexus drivers that pass a hotplug interrupt through to pciehpc will kick
94  *	off state changes in this way.
95  *	- Through coordinated removal, ddihp_modctl. Both cfgadm(1M) and
96  *	hotplug(1M) pass state change requests through hotplugd, which uses
97  *	modctl to request state changes to the DDI hotplug framework. That
98  *	interface is ultimately implemented by ddihp_modctl.
99  *
100  *		(start)
101  *		   |
102  *		   v
103  *		EMPTY		no component plugged into connector
104  *		   ^
105  *		   v
106  *		PRESENT		component plugged into connector
107  *		   ^
108  *		   v
109  *		POWERED		connector is powered
110  *		   ^
111  *		   v
112  *		ENABLED		connector is fully functional
113  *		   |
114  *		   .
115  *		   .
116  *		   .
117  *		   v
118  *		(create port)
119  *		   |
120  *		   v
121  *		PORT EMPTY	port has no device occupying it
122  *		   ^
123  *		   v
124  *		PORT PRESENT	port occupied by device
125  *
126  *
127  * ARCHITECTURE DIAGRAM
128  *
129  * The following is a non-exhaustive summary of various components in the system
130  * that implement pieces of the hotplug framework. More detailed descriptions
131  * of some key components are below.
132  *
133  *				+------------+
134  *				| cfgadm(1M) |
135  *				+------------+
136  *				      |
137  *			    +-------------------+
138  *			    | SHP cfgadm plugin |
139  *			    +-------------------+
140  *				      |
141  *	+-------------+		 +------------+
142  *	| hotplug(1M) |----------| libhotplug |
143  *	+-------------+		 +------------+
144  *				      |
145  *				 +----------+
146  *				 | hotplugd |
147  *				 +----------+
148  *				      |
149  *			      +----------------+
150  *			      | modctl (HP op) |
151  *			      +----------------+
152  *				|
153  *				|
154  * User				|
155  * =============================|===============================================
156  * Kernel		        |
157  *			        |
158  *			        |
159  *	+------------------------+     +----------------+
160  *	| DDI hotplug interfaces | --- | Device Drivers |
161  *	+------------------------+     +----------------+
162  *	    |		|
163  *	    | +------------------------+
164  *	    | | NDI hotplug interfaces |
165  *	    | +------------------------+
166  *	    |   |
167  *	    |   |
168  *	+-------------+	   +--------------+	+---------------------------+
169  *	| `bus_hp_op` | -- |"pcie" module | --- | "npe" (PCIe nexus driver) |
170  *	+-------------+	   +--------------+	+---------------------------+
171  *				|     |
172  *				|  +-------------------+
173  *				|  | PCIe configurator |
174  *				|  +-------------------+
175  *				|
176  *			   +-------------------------------------+
177  *			   | "pciehpc" (PCIe hotplug controller) |
178  *			   +-------------------------------------+
179  *
180  *
181  *		.
182  *		.
183  *		.
184  *		.
185  *		.
186  *		|
187  *		|
188  *    +-----------------------------------+
189  *    |		I/O Subsystem		  |
190  *    | (LDI notifications and contracts) |
191  *    +-----------------------------------+
192  *
193  *
194  * KEY HOTPLUG SOFTWARE COMPONENTS
195  *
196  *	CFGADM(1M)
197  *
198  *	cfgadm is the canonical tool for hotplug operations. It can be used to
199  *	list connections on the system and change their state in a coordinated
200  *	fashion. For more information, see its manual page.
201  *
202  *
203  *	HOTPLUG(1M)
204  *
205  *	hotplug is a command line tool for managing hotplug connections for
206  *	connectors. For more information, see its manual page.
207  *
208  *
209  *	DDI HOTPLUG INTERFACES
210  *
211  *	This part of the framework provides interfaces for changing device state
212  *	for connectors, including onlining and offlining child devices. Many of
213  *	these functions are defined in this file.
214  *
215  *
216  *	NDI HOTPLUG INTERFACES
217  *
218  *	Nexus drivers can define their own hotplug bus implementations by
219  *	defining a bus_hp_op entry point. This entry point must implement
220  *	a set of hotplug related commands, including getting, probing, and
221  *	changing connection state, as well as port creation and removal.
222  *
223  *	Nexus drivers may also want to use the following interfaces for
224  *	implementing hotplug. Note that the PCIe Hotplug Controller ("pciehpc")
225  *	already takes care of using these:
226  *		ndi_hp_{register,unregister}
227  *		ndi_hp_state_change_req
228  *		ndi_hp_walk_cn
229  *
230  *	PCIe nexus drivers should use the common entry point pcie_hp_common_ops,
231  *	which implements hotplug commands for PCIe devices, calling into other
232  *	parts of the framework as needed.
233  *
234  *
235  *	NPE DRIVER ("npe")
236  *
237  *	npe is the common nexus driver for PCIe devices on x86. It implements
238  *	hotplug using the NDI interfaces. For more information, see
239  *	uts/i86pc/io/pciex/npe.c.
240  *
241  *	The equivalent driver for SPARC is "px".
242  *
243  *
244  *	PCIe HOTPLUG CONTROLLER DRIVER ("pciehpc")
245  *
246  *	All hotplug-capable PCIe buses will initialize their own PCIe HPC,
247  *	including the pcieb and ppb drivers. The controller maintains
248  *	hotplug-related state about the slots on its bus, including their status
249  *	and port state. It also features a common implementation of handling
250  *	hotplug-related PCIe interrupts.
251  *
252  *	For more information, see its interfaces in
253  *	uts/common/sys/hotplug/pci/pciehpc.h.
254  *
255  */
256 
257 #include <sys/sysmacros.h>
258 #include <sys/types.h>
259 #include <sys/file.h>
260 #include <sys/param.h>
261 #include <sys/systm.h>
262 #include <sys/kmem.h>
263 #include <sys/cmn_err.h>
264 #include <sys/debug.h>
265 #include <sys/avintr.h>
266 #include <sys/autoconf.h>
267 #include <sys/ddi.h>
268 #include <sys/sunndi.h>
269 #include <sys/ndi_impldefs.h>
270 #include <sys/sysevent.h>
271 #include <sys/sysevent/eventdefs.h>
272 #include <sys/sysevent/dr.h>
273 #include <sys/fs/dv_node.h>
274 
275 /*
276  * Local function prototypes
277  */
278 /* Connector operations */
279 static int ddihp_cn_pre_change_state(ddi_hp_cn_handle_t *hdlp,
280     ddi_hp_cn_state_t target_state);
281 static int ddihp_cn_post_change_state(ddi_hp_cn_handle_t *hdlp,
282     ddi_hp_cn_state_t new_state);
283 static int ddihp_cn_handle_state_change(ddi_hp_cn_handle_t *hdlp);
284 static int ddihp_cn_change_children_state(ddi_hp_cn_handle_t *hdlp,
285     boolean_t online);
286 /* Port operations */
287 static int ddihp_port_change_state(ddi_hp_cn_handle_t *hdlp,
288     ddi_hp_cn_state_t target_state);
289 static int ddihp_port_upgrade_state(ddi_hp_cn_handle_t *hdlp,
290     ddi_hp_cn_state_t target_state);
291 static int ddihp_port_downgrade_state(ddi_hp_cn_handle_t *hdlp,
292     ddi_hp_cn_state_t target_state);
293 /* Misc routines */
294 static void ddihp_update_last_change(ddi_hp_cn_handle_t *hdlp);
295 static boolean_t ddihp_check_status_prop(dev_info_t *dip);
296 
297 /*
298  * Global functions (called within hotplug framework)
299  */
300 
301 /*
302  * Implement modctl() commands for hotplug.
303  * Called by modctl_hp() in modctl.c
304  */
305 int
306 ddihp_modctl(int hp_op, char *path, char *cn_name, uintptr_t arg,
307     uintptr_t rval)
308 {
309 	dev_info_t		*dip;
310 	ddi_hp_cn_handle_t	*hdlp;
311 	ddi_hp_op_t		op = (ddi_hp_op_t)hp_op;
312 	int			count, rv, error;
313 
314 	/* Get the dip of nexus node */
315 	dip = e_ddi_hold_devi_by_path(path, 0);
316 
317 	if (dip == NULL)
318 		return (ENXIO);
319 
320 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_modctl: dip %p op %x path %s "
321 	    "cn_name %s arg %p rval %p\n", (void *)dip, hp_op, path, cn_name,
322 	    (void *)arg, (void *)rval));
323 
324 	if (!NEXUS_HAS_HP_OP(dip)) {
325 		ddi_release_devi(dip);
326 		return (ENOTSUP);
327 	}
328 
329 	/* Lock before access */
330 	ndi_devi_enter(dip, &count);
331 
332 	hdlp = ddihp_cn_name_to_handle(dip, cn_name);
333 
334 	if (hp_op == DDI_HPOP_CN_CREATE_PORT) {
335 		if (hdlp != NULL) {
336 			/* this port already exists. */
337 			error = EEXIST;
338 
339 			goto done;
340 		}
341 		rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))(
342 		    dip, cn_name, op, NULL, NULL);
343 	} else {
344 		if (hdlp == NULL) {
345 			/* Invalid Connection name */
346 			error = ENXIO;
347 
348 			goto done;
349 		}
350 		if (hp_op == DDI_HPOP_CN_CHANGE_STATE) {
351 			ddi_hp_cn_state_t target_state = (ddi_hp_cn_state_t)arg;
352 			ddi_hp_cn_state_t result_state = 0;
353 
354 			DDIHP_CN_OPS(hdlp, op, (void *)&target_state,
355 			    (void *)&result_state, rv);
356 
357 			DDI_HP_IMPLDBG((CE_CONT, "ddihp_modctl: target_state="
358 			    "%x, result_state=%x, rv=%x \n",
359 			    target_state, result_state, rv));
360 		} else {
361 			DDIHP_CN_OPS(hdlp, op, (void *)arg, (void *)rval, rv);
362 		}
363 	}
364 	switch (rv) {
365 	case DDI_SUCCESS:
366 		error = 0;
367 		break;
368 	case DDI_EINVAL:
369 		error = EINVAL;
370 		break;
371 	case DDI_EBUSY:
372 		error = EBUSY;
373 		break;
374 	case DDI_ENOTSUP:
375 		error = ENOTSUP;
376 		break;
377 	case DDI_ENOMEM:
378 		error = ENOMEM;
379 		break;
380 	default:
381 		error = EIO;
382 	}
383 
384 done:
385 	ndi_devi_exit(dip, count);
386 
387 	ddi_release_devi(dip);
388 
389 	return (error);
390 }
391 
392 /*
393  * Fetch the state of Hotplug Connection (CN).
394  * This function will also update the state and last changed timestamp in the
395  * connection handle structure if the state has changed.
396  */
397 int
398 ddihp_cn_getstate(ddi_hp_cn_handle_t *hdlp)
399 {
400 	ddi_hp_cn_state_t	new_state;
401 	int			ret;
402 
403 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_getstate: pdip %p hdlp %p\n",
404 	    (void *)hdlp->cn_dip, (void *)hdlp));
405 
406 	ASSERT(DEVI_BUSY_OWNED(hdlp->cn_dip));
407 
408 	DDIHP_CN_OPS(hdlp, DDI_HPOP_CN_GET_STATE,
409 	    NULL, (void *)&new_state, ret);
410 	if (ret != DDI_SUCCESS) {
411 		DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_getstate: "
412 		    "CN %p getstate command failed\n", (void *)hdlp));
413 
414 		return (ret);
415 	}
416 
417 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_getstate: hdlp %p "
418 	    "current Connection state %x new Connection state %x\n",
419 	    (void *)hdlp, hdlp->cn_info.cn_state, new_state));
420 
421 	if (new_state != hdlp->cn_info.cn_state) {
422 		hdlp->cn_info.cn_state = new_state;
423 		ddihp_update_last_change(hdlp);
424 	}
425 
426 	return (ret);
427 }
428 
429 /*
430  * Implementation function for unregistering the Hotplug Connection (CN)
431  */
432 int
433 ddihp_cn_unregister(ddi_hp_cn_handle_t *hdlp)
434 {
435 	dev_info_t	*dip = hdlp->cn_dip;
436 
437 	DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_unregister: hdlp %p\n",
438 	    (void *)hdlp));
439 
440 	ASSERT(DEVI_BUSY_OWNED(dip));
441 
442 	(void) ddihp_cn_getstate(hdlp);
443 
444 	if (hdlp->cn_info.cn_state > DDI_HP_CN_STATE_OFFLINE) {
445 		DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_unregister: dip %p, hdlp %p "
446 		    "state %x. Device busy, failed to unregister connection!\n",
447 		    (void *)dip, (void *)hdlp, hdlp->cn_info.cn_state));
448 
449 		return (DDI_EBUSY);
450 	}
451 
452 	/* unlink the handle */
453 	DDIHP_LIST_REMOVE(ddi_hp_cn_handle_t, (DEVI(dip)->devi_hp_hdlp), hdlp);
454 
455 	kmem_free(hdlp->cn_info.cn_name, strlen(hdlp->cn_info.cn_name) + 1);
456 	kmem_free(hdlp, sizeof (ddi_hp_cn_handle_t));
457 	return (DDI_SUCCESS);
458 }
459 
460 /*
461  * For a given Connection name and the dip node where the Connection is
462  * supposed to be, find the corresponding hotplug handle.
463  */
464 ddi_hp_cn_handle_t *
465 ddihp_cn_name_to_handle(dev_info_t *dip, char *cn_name)
466 {
467 	ddi_hp_cn_handle_t *hdlp;
468 
469 	ASSERT(DEVI_BUSY_OWNED(dip));
470 
471 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_name_to_handle: "
472 	    "dip %p cn_name to find: %s", (void *)dip, cn_name));
473 	for (hdlp = DEVI(dip)->devi_hp_hdlp; hdlp; hdlp = hdlp->next) {
474 		DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_name_to_handle: "
475 		    "current cn_name: %s", hdlp->cn_info.cn_name));
476 
477 		if (strcmp(cn_name, hdlp->cn_info.cn_name) == 0) {
478 			/* found */
479 			return (hdlp);
480 		}
481 	}
482 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_name_to_handle: "
483 	    "failed to find cn_name"));
484 	return (NULL);
485 }
486 
487 /*
488  * Process the hotplug operations for Connector and also create Port
489  * upon user command.
490  */
491 int
492 ddihp_connector_ops(ddi_hp_cn_handle_t *hdlp, ddi_hp_op_t op,
493     void *arg, void *result)
494 {
495 	int			rv = DDI_SUCCESS;
496 	dev_info_t		*dip = hdlp->cn_dip;
497 
498 	ASSERT(DEVI_BUSY_OWNED(dip));
499 
500 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_connector_ops: pdip=%p op=%x "
501 	    "hdlp=%p arg=%p\n", (void *)dip, op, (void *)hdlp, arg));
502 
503 	if (op == DDI_HPOP_CN_CHANGE_STATE) {
504 		ddi_hp_cn_state_t target_state = *(ddi_hp_cn_state_t *)arg;
505 
506 		rv = ddihp_cn_pre_change_state(hdlp, target_state);
507 		if (rv != DDI_SUCCESS) {
508 			/* the state is not changed */
509 			*((ddi_hp_cn_state_t *)result) =
510 			    hdlp->cn_info.cn_state;
511 			return (rv);
512 		}
513 	}
514 	ASSERT(NEXUS_HAS_HP_OP(dip));
515 	rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))(
516 	    dip, hdlp->cn_info.cn_name, op, arg, result);
517 
518 	if (rv != DDI_SUCCESS) {
519 		DDI_HP_IMPLDBG((CE_CONT, "ddihp_connector_ops: "
520 		    "bus_hp_op failed: pdip=%p cn_name:%s op=%x "
521 		    "hdlp=%p arg=%p\n", (void *)dip, hdlp->cn_info.cn_name,
522 		    op, (void *)hdlp, arg));
523 	}
524 	if (op == DDI_HPOP_CN_CHANGE_STATE) {
525 		int rv_post;
526 
527 		DDI_HP_IMPLDBG((CE_CONT, "ddihp_connector_ops: "
528 		    "old_state=%x, new_state=%x, rv=%x\n",
529 		    hdlp->cn_info.cn_state, *(ddi_hp_cn_state_t *)result, rv));
530 
531 		/*
532 		 * After state change op is successfully done or
533 		 * failed at some stages, continue to do some jobs.
534 		 */
535 		rv_post = ddihp_cn_post_change_state(hdlp,
536 		    *(ddi_hp_cn_state_t *)result);
537 
538 		if (rv_post != DDI_SUCCESS)
539 			rv = rv_post;
540 	}
541 
542 	return (rv);
543 }
544 
545 /*
546  * Process the hotplug op for Port
547  */
548 int
549 ddihp_port_ops(ddi_hp_cn_handle_t *hdlp, ddi_hp_op_t op,
550     void *arg, void *result)
551 {
552 	int		ret = DDI_SUCCESS;
553 
554 	ASSERT(DEVI_BUSY_OWNED(hdlp->cn_dip));
555 
556 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_port_ops: pdip=%p op=%x hdlp=%p "
557 	    "arg=%p\n", (void *)hdlp->cn_dip, op, (void *)hdlp, arg));
558 
559 	switch (op) {
560 	case DDI_HPOP_CN_GET_STATE:
561 	{
562 		int state;
563 
564 		state = hdlp->cn_info.cn_state;
565 
566 		if (hdlp->cn_info.cn_child == NULL) {
567 			/* No child. Either present or empty. */
568 			if (state >= DDI_HP_CN_STATE_PORT_PRESENT)
569 				state = DDI_HP_CN_STATE_PORT_PRESENT;
570 			else
571 				state = DDI_HP_CN_STATE_PORT_EMPTY;
572 
573 		} else { /* There is a child of this Port */
574 
575 			/* Check DEVI(dip)->devi_node_state */
576 			switch (i_ddi_node_state(hdlp->cn_info.cn_child)) {
577 			case	DS_INVAL:
578 			case	DS_PROTO:
579 			case	DS_LINKED:
580 			case	DS_BOUND:
581 			case	DS_INITIALIZED:
582 			case	DS_PROBED:
583 				state = DDI_HP_CN_STATE_OFFLINE;
584 				break;
585 			case	DS_ATTACHED:
586 				state = DDI_HP_CN_STATE_MAINTENANCE;
587 				break;
588 			case	DS_READY:
589 				state = DDI_HP_CN_STATE_ONLINE;
590 				break;
591 			default:
592 				/* should never reach here */
593 				ASSERT("unknown devinfo state");
594 			}
595 			/*
596 			 * Check DEVI(dip)->devi_state in case the node is
597 			 * downgraded or quiesced.
598 			 */
599 			if (state == DDI_HP_CN_STATE_ONLINE &&
600 			    ddi_get_devstate(hdlp->cn_info.cn_child) !=
601 			    DDI_DEVSTATE_UP)
602 				state = DDI_HP_CN_STATE_MAINTENANCE;
603 		}
604 
605 		*((ddi_hp_cn_state_t *)result) = state;
606 
607 		break;
608 	}
609 	case DDI_HPOP_CN_CHANGE_STATE:
610 	{
611 		ddi_hp_cn_state_t target_state = *(ddi_hp_cn_state_t *)arg;
612 		ddi_hp_cn_state_t curr_state = hdlp->cn_info.cn_state;
613 
614 		ret = ddihp_port_change_state(hdlp, target_state);
615 		if (curr_state != hdlp->cn_info.cn_state) {
616 			ddihp_update_last_change(hdlp);
617 		}
618 		*((ddi_hp_cn_state_t *)result) = hdlp->cn_info.cn_state;
619 
620 		break;
621 	}
622 	case DDI_HPOP_CN_REMOVE_PORT:
623 	{
624 		(void) ddihp_cn_getstate(hdlp);
625 
626 		if (hdlp->cn_info.cn_state != DDI_HP_CN_STATE_PORT_EMPTY) {
627 			/* Only empty PORT can be removed by commands */
628 			ret = DDI_EBUSY;
629 
630 			break;
631 		}
632 
633 		ret = ddihp_cn_unregister(hdlp);
634 		break;
635 	}
636 	default:
637 		ret = DDI_ENOTSUP;
638 		break;
639 	}
640 
641 	return (ret);
642 }
643 
644 /*
645  * Generate the system event with a possible hint
646  */
647 /* ARGSUSED */
648 void
649 ddihp_cn_gen_sysevent(ddi_hp_cn_handle_t *hdlp,
650     ddi_hp_cn_sysevent_t event_sub_class, int hint, int kmflag)
651 {
652 	dev_info_t	*dip = hdlp->cn_dip;
653 	char		*cn_path, *ap_id;
654 	char		*ev_subclass = NULL;
655 	nvlist_t	*ev_attr_list = NULL;
656 	sysevent_id_t	eid;
657 	int		ap_id_len, err;
658 
659 	cn_path = kmem_zalloc(MAXPATHLEN, kmflag);
660 	if (cn_path == NULL) {
661 		cmn_err(CE_WARN,
662 		    "%s%d: Failed to allocate memory for hotplug"
663 		    " connection: %s\n",
664 		    ddi_driver_name(dip), ddi_get_instance(dip),
665 		    hdlp->cn_info.cn_name);
666 
667 		return;
668 	}
669 
670 	/*
671 	 * Minor device name will be bus path
672 	 * concatenated with connection name.
673 	 * One of consumers of the sysevent will pass it
674 	 * to cfgadm as AP ID.
675 	 */
676 	(void) strcpy(cn_path, "/devices");
677 	(void) ddi_pathname(dip, cn_path + strlen("/devices"));
678 
679 	ap_id_len = strlen(cn_path) + strlen(":") +
680 	    strlen(hdlp->cn_info.cn_name) + 1;
681 	ap_id = kmem_zalloc(ap_id_len, kmflag);
682 	if (ap_id == NULL) {
683 		cmn_err(CE_WARN,
684 		    "%s%d: Failed to allocate memory for AP ID: %s:%s\n",
685 		    ddi_driver_name(dip), ddi_get_instance(dip),
686 		    cn_path, hdlp->cn_info.cn_name);
687 		kmem_free(cn_path, MAXPATHLEN);
688 
689 		return;
690 	}
691 
692 	(void) strcpy(ap_id, cn_path);
693 	(void) strcat(ap_id, ":");
694 	(void) strcat(ap_id, hdlp->cn_info.cn_name);
695 	kmem_free(cn_path, MAXPATHLEN);
696 
697 	err = nvlist_alloc(&ev_attr_list, NV_UNIQUE_NAME_TYPE, kmflag);
698 
699 	if (err != 0) {
700 		cmn_err(CE_WARN,
701 		    "%s%d: Failed to allocate memory for event subclass %d\n",
702 		    ddi_driver_name(dip), ddi_get_instance(dip),
703 		    event_sub_class);
704 		kmem_free(ap_id, ap_id_len);
705 
706 		return;
707 	}
708 
709 	switch (event_sub_class) {
710 	case DDI_HP_CN_STATE_CHANGE:
711 		ev_subclass = ESC_DR_AP_STATE_CHANGE;
712 
713 		switch (hint) {
714 		case SE_NO_HINT:	/* fall through */
715 		case SE_HINT_INSERT:	/* fall through */
716 		case SE_HINT_REMOVE:
717 			err = nvlist_add_string(ev_attr_list, DR_HINT,
718 			    SE_HINT2STR(hint));
719 
720 			if (err != 0) {
721 				cmn_err(CE_WARN, "%s%d: Failed to add attr [%s]"
722 				    " for %s event\n", ddi_driver_name(dip),
723 				    ddi_get_instance(dip), DR_HINT,
724 				    ESC_DR_AP_STATE_CHANGE);
725 
726 				goto done;
727 			}
728 			break;
729 
730 		default:
731 			cmn_err(CE_WARN, "%s%d: Unknown hint on sysevent\n",
732 			    ddi_driver_name(dip), ddi_get_instance(dip));
733 
734 			goto done;
735 		}
736 
737 		break;
738 
739 	/* event sub class: DDI_HP_CN_REQ */
740 	case DDI_HP_CN_REQ:
741 		ev_subclass = ESC_DR_REQ;
742 
743 		switch (hint) {
744 		case SE_INVESTIGATE_RES: /* fall through */
745 		case SE_INCOMING_RES:	/* fall through */
746 		case SE_OUTGOING_RES:	/* fall through */
747 			err = nvlist_add_string(ev_attr_list, DR_REQ_TYPE,
748 			    SE_REQ2STR(hint));
749 
750 			if (err != 0) {
751 				cmn_err(CE_WARN,
752 				    "%s%d: Failed to add attr [%s] for %s \n"
753 				    "event", ddi_driver_name(dip),
754 				    ddi_get_instance(dip),
755 				    DR_REQ_TYPE, ESC_DR_REQ);
756 
757 				goto done;
758 			}
759 			break;
760 
761 		default:
762 			cmn_err(CE_WARN, "%s%d:  Unknown hint on sysevent\n",
763 			    ddi_driver_name(dip), ddi_get_instance(dip));
764 
765 			goto done;
766 		}
767 
768 		break;
769 
770 	default:
771 		cmn_err(CE_WARN, "%s%d:  Unknown Event subclass\n",
772 		    ddi_driver_name(dip), ddi_get_instance(dip));
773 
774 		goto done;
775 	}
776 
777 	/*
778 	 * Add Hotplug Connection (CN) as attribute (common attribute)
779 	 */
780 	err = nvlist_add_string(ev_attr_list, DR_AP_ID, ap_id);
781 	if (err != 0) {
782 		cmn_err(CE_WARN, "%s%d: Failed to add attr [%s] for %s event\n",
783 		    ddi_driver_name(dip), ddi_get_instance(dip),
784 		    DR_AP_ID, EC_DR);
785 
786 		goto done;
787 	}
788 
789 	/*
790 	 * Log this event with sysevent framework.
791 	 */
792 	err = ddi_log_sysevent(dip, DDI_VENDOR_SUNW, EC_DR,
793 	    ev_subclass, ev_attr_list, &eid,
794 	    ((kmflag == KM_SLEEP) ? DDI_SLEEP : DDI_NOSLEEP));
795 
796 	if (err != 0) {
797 		cmn_err(CE_WARN, "%s%d: Failed to log %s event\n",
798 		    ddi_driver_name(dip), ddi_get_instance(dip), EC_DR);
799 	}
800 
801 done:
802 	nvlist_free(ev_attr_list);
803 	kmem_free(ap_id, ap_id_len);
804 }
805 
806 /*
807  * Local functions (called within this file)
808  */
809 
810 /*
811  * Connector operations
812  */
813 
814 /*
815  * Prepare to change state for a Connector: offline, unprobe, etc.
816  */
817 static int
818 ddihp_cn_pre_change_state(ddi_hp_cn_handle_t *hdlp,
819     ddi_hp_cn_state_t target_state)
820 {
821 	ddi_hp_cn_state_t	curr_state = hdlp->cn_info.cn_state;
822 	dev_info_t		*dip = hdlp->cn_dip;
823 	int			rv = DDI_SUCCESS;
824 
825 	if (curr_state > target_state &&
826 	    curr_state == DDI_HP_CN_STATE_ENABLED) {
827 		/*
828 		 * If the Connection goes to a lower state from ENABLED,
829 		 * then offline all children under it.
830 		 */
831 		rv = ddihp_cn_change_children_state(hdlp, B_FALSE);
832 		if (rv != DDI_SUCCESS) {
833 			cmn_err(CE_WARN,
834 			    "(%s%d): "
835 			    "failed to unconfigure the device in the"
836 			    " Connection %s\n", ddi_driver_name(dip),
837 			    ddi_get_instance(dip),
838 			    hdlp->cn_info.cn_name);
839 
840 			return (rv);
841 		}
842 		ASSERT(NEXUS_HAS_HP_OP(dip));
843 		/*
844 		 * Remove all the children and their ports
845 		 * after they are offlined.
846 		 */
847 		rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))(
848 		    dip, hdlp->cn_info.cn_name, DDI_HPOP_CN_UNPROBE,
849 		    NULL, NULL);
850 		if (rv != DDI_SUCCESS) {
851 			cmn_err(CE_WARN,
852 			    "(%s%d): failed"
853 			    " to unprobe the device in the Connector"
854 			    " %s\n", ddi_driver_name(dip),
855 			    ddi_get_instance(dip),
856 			    hdlp->cn_info.cn_name);
857 
858 			return (rv);
859 		}
860 
861 		DDI_HP_NEXDBG((CE_CONT,
862 		    "ddihp_connector_ops (%s%d): device"
863 		    " is unconfigured and unprobed in Connector %s\n",
864 		    ddi_driver_name(dip), ddi_get_instance(dip),
865 		    hdlp->cn_info.cn_name));
866 	}
867 
868 	return (rv);
869 }
870 
871 /*
872  * Jobs after change state of a Connector: update state, last change time,
873  * probe, online, sysevent, etc.
874  */
875 static int
876 ddihp_cn_post_change_state(ddi_hp_cn_handle_t *hdlp,
877     ddi_hp_cn_state_t new_state)
878 {
879 	int			rv = DDI_SUCCESS;
880 	ddi_hp_cn_state_t	curr_state = hdlp->cn_info.cn_state;
881 
882 	/* Update the state in handle */
883 	if (new_state != curr_state) {
884 		hdlp->cn_info.cn_state = new_state;
885 		ddihp_update_last_change(hdlp);
886 	}
887 
888 	if (curr_state < new_state &&
889 	    new_state == DDI_HP_CN_STATE_ENABLED) {
890 		/*
891 		 * Probe and online devices if state is
892 		 * upgraded to ENABLED.
893 		 */
894 		rv = ddihp_cn_handle_state_change(hdlp);
895 	}
896 	if (curr_state != hdlp->cn_info.cn_state) {
897 		/*
898 		 * For Connector, generate a sysevent on
899 		 * state change.
900 		 */
901 		ddihp_cn_gen_sysevent(hdlp, DDI_HP_CN_STATE_CHANGE,
902 		    SE_NO_HINT, KM_SLEEP);
903 	}
904 
905 	return (rv);
906 }
907 
908 /*
909  * Handle Connector state change.
910  *
911  * This function is called after connector is upgraded to ENABLED sate.
912  * It probes the device plugged in the connector to setup devinfo nodes
913  * and then online the nodes.
914  */
915 static int
916 ddihp_cn_handle_state_change(ddi_hp_cn_handle_t *hdlp)
917 {
918 	dev_info_t		*dip = hdlp->cn_dip;
919 	int			rv = DDI_SUCCESS;
920 
921 	ASSERT(DEVI_BUSY_OWNED(dip));
922 	ASSERT(NEXUS_HAS_HP_OP(dip));
923 	/*
924 	 * If the Connection went to state ENABLED from a lower state,
925 	 * probe it.
926 	 */
927 	rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))(
928 	    dip, hdlp->cn_info.cn_name, DDI_HPOP_CN_PROBE, NULL, NULL);
929 
930 	if (rv != DDI_SUCCESS) {
931 		ddi_hp_cn_state_t	target_state = DDI_HP_CN_STATE_POWERED;
932 		ddi_hp_cn_state_t	result_state = 0;
933 
934 		/*
935 		 * Probe failed. Disable the connector so that it can
936 		 * be enabled again by a later try from userland.
937 		 */
938 		(void) (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))(
939 		    dip, hdlp->cn_info.cn_name, DDI_HPOP_CN_CHANGE_STATE,
940 		    (void *)&target_state, (void *)&result_state);
941 
942 		if (result_state && result_state != hdlp->cn_info.cn_state) {
943 			hdlp->cn_info.cn_state = result_state;
944 			ddihp_update_last_change(hdlp);
945 		}
946 
947 		cmn_err(CE_WARN,
948 		    "(%s%d): failed to probe the Connection %s\n",
949 		    ddi_driver_name(dip), ddi_get_instance(dip),
950 		    hdlp->cn_info.cn_name);
951 
952 		return (rv);
953 	}
954 	/*
955 	 * Try to online all the children of CN.
956 	 */
957 	(void) ddihp_cn_change_children_state(hdlp, B_TRUE);
958 
959 	DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_event_handler (%s%d): "
960 	    "device is configured in the Connection %s\n",
961 	    ddi_driver_name(dip), ddi_get_instance(dip),
962 	    hdlp->cn_info.cn_name));
963 	return (rv);
964 }
965 
966 /*
967  * Online/Offline all the children under the Hotplug Connection (CN)
968  *
969  * Do online operation when the online parameter is true; otherwise do offline.
970  */
971 static int
972 ddihp_cn_change_children_state(ddi_hp_cn_handle_t *hdlp, boolean_t online)
973 {
974 	dev_info_t		*dip = hdlp->cn_dip;
975 	dev_info_t		*cdip;
976 	ddi_hp_cn_handle_t	*h;
977 	int			rv = DDI_SUCCESS;
978 
979 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_change_children_state:"
980 	    " dip %p hdlp %p, online %x\n",
981 	    (void *)dip, (void *)hdlp, online));
982 
983 	ASSERT(DEVI_BUSY_OWNED(dip));
984 
985 	/*
986 	 * Return invalid if Connection state is < DDI_HP_CN_STATE_ENABLED
987 	 * when try to online children.
988 	 */
989 	if (online && hdlp->cn_info.cn_state < DDI_HP_CN_STATE_ENABLED) {
990 		DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_change_children_state: "
991 		    "Connector %p is not in probed state\n", (void *)hdlp));
992 
993 		return (DDI_EINVAL);
994 	}
995 
996 	/* Now, online/offline all the devices depending on the Connector */
997 
998 	if (!online) {
999 		/*
1000 		 * For offline operation we need to firstly clean up devfs
1001 		 * so as not to prevent driver detach.
1002 		 */
1003 		(void) devfs_clean(dip, NULL, DV_CLEAN_FORCE);
1004 	}
1005 	for (h = DEVI(dip)->devi_hp_hdlp; h; h = h->next) {
1006 		if (h->cn_info.cn_type != DDI_HP_CN_TYPE_VIRTUAL_PORT)
1007 			continue;
1008 
1009 		if (h->cn_info.cn_num_dpd_on !=
1010 		    hdlp->cn_info.cn_num)
1011 			continue;
1012 
1013 		cdip = h->cn_info.cn_child;
1014 		ASSERT(cdip);
1015 		if (online) {
1016 			/* online children */
1017 			if (!ddihp_check_status_prop(dip))
1018 				continue;
1019 
1020 			if (ndi_devi_online(cdip,
1021 			    NDI_ONLINE_ATTACH | NDI_CONFIG) != NDI_SUCCESS) {
1022 				cmn_err(CE_WARN,
1023 				    "(%s%d):"
1024 				    " failed to attach driver for a device"
1025 				    " (%s%d) under the Connection %s\n",
1026 				    ddi_driver_name(dip), ddi_get_instance(dip),
1027 				    ddi_driver_name(cdip),
1028 				    ddi_get_instance(cdip),
1029 				    hdlp->cn_info.cn_name);
1030 				/*
1031 				 * One of the devices failed to online, but we
1032 				 * want to continue to online the rest siblings
1033 				 * after mark the failure here.
1034 				 */
1035 				rv = DDI_FAILURE;
1036 
1037 				continue;
1038 			}
1039 		} else {
1040 			/* offline children */
1041 			if (ndi_devi_offline(cdip, NDI_UNCONFIG) !=
1042 			    NDI_SUCCESS) {
1043 				cmn_err(CE_WARN,
1044 				    "(%s%d):"
1045 				    " failed to detach driver for the device"
1046 				    " (%s%d) in the Connection %s\n",
1047 				    ddi_driver_name(dip), ddi_get_instance(dip),
1048 				    ddi_driver_name(cdip),
1049 				    ddi_get_instance(cdip),
1050 				    hdlp->cn_info.cn_name);
1051 
1052 				return (DDI_EBUSY);
1053 			}
1054 		}
1055 	}
1056 
1057 	return (rv);
1058 }
1059 
1060 /*
1061  * Port operations
1062  */
1063 
1064 /*
1065  * Change Port state to target_state.
1066  */
1067 static int
1068 ddihp_port_change_state(ddi_hp_cn_handle_t *hdlp,
1069     ddi_hp_cn_state_t target_state)
1070 {
1071 	ddi_hp_cn_state_t curr_state = hdlp->cn_info.cn_state;
1072 
1073 	if (target_state < DDI_HP_CN_STATE_PORT_EMPTY ||
1074 	    target_state > DDI_HP_CN_STATE_ONLINE) {
1075 
1076 		return (DDI_EINVAL);
1077 	}
1078 
1079 	if (curr_state < target_state)
1080 		return (ddihp_port_upgrade_state(hdlp, target_state));
1081 	else if (curr_state > target_state)
1082 		return (ddihp_port_downgrade_state(hdlp, target_state));
1083 	else
1084 		return (DDI_SUCCESS);
1085 }
1086 
1087 /*
1088  * Upgrade port state to target_state.
1089  */
1090 static int
1091 ddihp_port_upgrade_state(ddi_hp_cn_handle_t *hdlp,
1092     ddi_hp_cn_state_t target_state)
1093 {
1094 	ddi_hp_cn_state_t	curr_state, new_state, result_state;
1095 	dev_info_t		*cdip;
1096 	int			rv = DDI_SUCCESS;
1097 
1098 	curr_state = hdlp->cn_info.cn_state;
1099 	while (curr_state < target_state) {
1100 		switch (curr_state) {
1101 		case DDI_HP_CN_STATE_PORT_EMPTY:
1102 			/* Check the existence of the corresponding hardware */
1103 			new_state = DDI_HP_CN_STATE_PORT_PRESENT;
1104 			rv = ddihp_connector_ops(hdlp,
1105 			    DDI_HPOP_CN_CHANGE_STATE,
1106 			    (void *)&new_state, (void *)&result_state);
1107 			if (rv == DDI_SUCCESS) {
1108 				hdlp->cn_info.cn_state =
1109 				    result_state;
1110 			}
1111 			break;
1112 		case DDI_HP_CN_STATE_PORT_PRESENT:
1113 			/* Read-only probe the corresponding hardware. */
1114 			new_state = DDI_HP_CN_STATE_OFFLINE;
1115 			rv = ddihp_connector_ops(hdlp,
1116 			    DDI_HPOP_CN_CHANGE_STATE,
1117 			    (void *)&new_state, &cdip);
1118 			if (rv == DDI_SUCCESS) {
1119 				hdlp->cn_info.cn_state =
1120 				    DDI_HP_CN_STATE_OFFLINE;
1121 
1122 				ASSERT(hdlp->cn_info.cn_child == NULL);
1123 				hdlp->cn_info.cn_child = cdip;
1124 			}
1125 			break;
1126 		case DDI_HP_CN_STATE_OFFLINE:
1127 			/* fall through */
1128 		case DDI_HP_CN_STATE_MAINTENANCE:
1129 
1130 			cdip = hdlp->cn_info.cn_child;
1131 
1132 			rv = ndi_devi_online(cdip,
1133 			    NDI_ONLINE_ATTACH | NDI_CONFIG);
1134 			if (rv == NDI_SUCCESS) {
1135 				hdlp->cn_info.cn_state =
1136 				    DDI_HP_CN_STATE_ONLINE;
1137 				rv = DDI_SUCCESS;
1138 			} else {
1139 				rv = DDI_FAILURE;
1140 				DDI_HP_IMPLDBG((CE_CONT,
1141 				    "ddihp_port_upgrade_state: "
1142 				    "failed to online device %p at port: %s\n",
1143 				    (void *)cdip, hdlp->cn_info.cn_name));
1144 			}
1145 			break;
1146 		case DDI_HP_CN_STATE_ONLINE:
1147 
1148 			break;
1149 		default:
1150 			/* should never reach here */
1151 			ASSERT("unknown devinfo state");
1152 		}
1153 		curr_state = hdlp->cn_info.cn_state;
1154 		if (rv != DDI_SUCCESS) {
1155 			DDI_HP_IMPLDBG((CE_CONT, "ddihp_port_upgrade_state: "
1156 			    "failed curr_state=%x, target_state=%x \n",
1157 			    curr_state, target_state));
1158 			return (rv);
1159 		}
1160 	}
1161 
1162 	return (rv);
1163 }
1164 
1165 /*
1166  * Downgrade state to target_state
1167  */
1168 static int
1169 ddihp_port_downgrade_state(ddi_hp_cn_handle_t *hdlp,
1170     ddi_hp_cn_state_t target_state)
1171 {
1172 	ddi_hp_cn_state_t	curr_state, new_state, result_state;
1173 	dev_info_t		*dip = hdlp->cn_dip;
1174 	dev_info_t		*cdip;
1175 	int			rv = DDI_SUCCESS;
1176 
1177 	curr_state = hdlp->cn_info.cn_state;
1178 	while (curr_state > target_state) {
1179 
1180 		switch (curr_state) {
1181 		case DDI_HP_CN_STATE_PORT_EMPTY:
1182 
1183 			break;
1184 		case DDI_HP_CN_STATE_PORT_PRESENT:
1185 			/* Check the existence of the corresponding hardware */
1186 			new_state = DDI_HP_CN_STATE_PORT_EMPTY;
1187 			rv = ddihp_connector_ops(hdlp,
1188 			    DDI_HPOP_CN_CHANGE_STATE,
1189 			    (void *)&new_state, (void *)&result_state);
1190 			if (rv == DDI_SUCCESS)
1191 				hdlp->cn_info.cn_state =
1192 				    result_state;
1193 
1194 			break;
1195 		case DDI_HP_CN_STATE_OFFLINE:
1196 			/*
1197 			 * Read-only unprobe the corresponding hardware:
1198 			 * 1. release the assigned resource;
1199 			 * 2. remove the node pointed by the port's cn_child
1200 			 */
1201 			new_state = DDI_HP_CN_STATE_PORT_PRESENT;
1202 			rv = ddihp_connector_ops(hdlp,
1203 			    DDI_HPOP_CN_CHANGE_STATE,
1204 			    (void *)&new_state, (void *)&result_state);
1205 			if (rv == DDI_SUCCESS)
1206 				hdlp->cn_info.cn_state =
1207 				    DDI_HP_CN_STATE_PORT_PRESENT;
1208 			break;
1209 		case DDI_HP_CN_STATE_MAINTENANCE:
1210 			/* fall through. */
1211 		case DDI_HP_CN_STATE_ONLINE:
1212 			cdip = hdlp->cn_info.cn_child;
1213 
1214 			(void) devfs_clean(dip, NULL, DV_CLEAN_FORCE);
1215 			rv = ndi_devi_offline(cdip, NDI_UNCONFIG);
1216 			if (rv == NDI_SUCCESS) {
1217 				hdlp->cn_info.cn_state =
1218 				    DDI_HP_CN_STATE_OFFLINE;
1219 				rv = DDI_SUCCESS;
1220 			} else {
1221 				rv = DDI_EBUSY;
1222 				DDI_HP_IMPLDBG((CE_CONT,
1223 				    "ddihp_port_downgrade_state: failed "
1224 				    "to offline node, rv=%x, cdip=%p \n",
1225 				    rv, (void *)cdip));
1226 			}
1227 
1228 			break;
1229 		default:
1230 			/* should never reach here */
1231 			ASSERT("unknown devinfo state");
1232 		}
1233 		curr_state = hdlp->cn_info.cn_state;
1234 		if (rv != DDI_SUCCESS) {
1235 			DDI_HP_IMPLDBG((CE_CONT,
1236 			    "ddihp_port_downgrade_state: failed "
1237 			    "curr_state=%x, target_state=%x \n",
1238 			    curr_state, target_state));
1239 			return (rv);
1240 		}
1241 	}
1242 
1243 	return (rv);
1244 }
1245 
1246 /*
1247  * Misc routines
1248  */
1249 
1250 /* Update the last state change time */
1251 static void
1252 ddihp_update_last_change(ddi_hp_cn_handle_t *hdlp)
1253 {
1254 	time_t			time;
1255 
1256 	if (drv_getparm(TIME, (void *)&time) != DDI_SUCCESS)
1257 		hdlp->cn_info.cn_last_change = (time_t)-1;
1258 	else
1259 		hdlp->cn_info.cn_last_change = (time32_t)time;
1260 }
1261 
1262 /*
1263  * Check the device for a 'status' property.  A conforming device
1264  * should have a status of "okay", "disabled", "fail", or "fail-xxx".
1265  *
1266  * Return FALSE for a conforming device that is disabled or faulted.
1267  * Return TRUE in every other case.
1268  *
1269  * 'status' property is NOT a bus specific property. It is defined in page 184,
1270  * IEEE 1275 spec. The full name of the spec is "IEEE Standard for
1271  * Boot (Initialization Configuration) Firmware: Core Requirements and
1272  * Practices".
1273  */
1274 static boolean_t
1275 ddihp_check_status_prop(dev_info_t *dip)
1276 {
1277 	char		*status_prop;
1278 	boolean_t	rv = B_TRUE;
1279 
1280 	/* try to get the 'status' property */
1281 	if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
1282 	    "status", &status_prop) == DDI_PROP_SUCCESS) {
1283 		/*
1284 		 * test if the status is "disabled", "fail", or
1285 		 * "fail-xxx".
1286 		 */
1287 		if (strcmp(status_prop, "disabled") == 0) {
1288 			rv = B_FALSE;
1289 			DDI_HP_IMPLDBG((CE_CONT, "ddihp_check_status_prop "
1290 			    "(%s%d): device is in disabled state",
1291 			    ddi_driver_name(dip), ddi_get_instance(dip)));
1292 		} else if (strncmp(status_prop, "fail", 4) == 0) {
1293 			rv = B_FALSE;
1294 			cmn_err(CE_WARN,
1295 			    "hotplug (%s%d): device is in fault state (%s)\n",
1296 			    ddi_driver_name(dip), ddi_get_instance(dip),
1297 			    status_prop);
1298 		}
1299 
1300 		ddi_prop_free(status_prop);
1301 	}
1302 
1303 	return (rv);
1304 }
1305