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