xref: /illumos-gate/usr/src/uts/common/os/ddi_hp_impl.c (revision 88d39f6f84496446f1d8b04fcc43e10e816fc86f)
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 2025 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 	 * See the theory statement near `ndi_devi_enter` in
338 	 * `common/os/devcfg.c` for more details.
339 	 */
340 	pdip = ddi_get_parent(dip);
341 	if (pdip != NULL)
342 		ndi_devi_enter(pdip);
343 
344 	/* Lock before access */
345 	ndi_devi_enter(dip);
346 
347 	hdlp = ddihp_cn_name_to_handle(dip, cn_name);
348 
349 	if (hp_op == DDI_HPOP_CN_CREATE_PORT) {
350 		if (hdlp != NULL) {
351 			/* this port already exists. */
352 			error = EEXIST;
353 
354 			goto done;
355 		}
356 		rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))(
357 		    dip, cn_name, op, NULL, NULL);
358 	} else {
359 		if (hdlp == NULL) {
360 			/* Invalid Connection name */
361 			error = ENXIO;
362 
363 			goto done;
364 		}
365 		if (hp_op == DDI_HPOP_CN_CHANGE_STATE) {
366 			ddi_hp_cn_state_t target_state = (ddi_hp_cn_state_t)arg;
367 			ddi_hp_cn_state_t result_state = 0;
368 
369 			DDIHP_CN_OPS(hdlp, op, (void *)&target_state,
370 			    (void *)&result_state, rv);
371 
372 			DDI_HP_IMPLDBG((CE_CONT, "ddihp_modctl: target_state="
373 			    "%x, result_state=%x, rv=%x \n",
374 			    target_state, result_state, rv));
375 		} else {
376 			DDIHP_CN_OPS(hdlp, op, (void *)arg, (void *)rval, rv);
377 		}
378 	}
379 	switch (rv) {
380 	case DDI_SUCCESS:
381 		error = 0;
382 		break;
383 	case DDI_EINVAL:
384 		error = EINVAL;
385 		break;
386 	case DDI_EBUSY:
387 		error = EBUSY;
388 		break;
389 	case DDI_ENOTSUP:
390 		error = ENOTSUP;
391 		break;
392 	case DDI_ENOMEM:
393 		error = ENOMEM;
394 		break;
395 	default:
396 		error = EIO;
397 	}
398 
399 done:
400 	ndi_devi_exit(dip);
401 	if (pdip != NULL)
402 		ndi_devi_exit(pdip);
403 
404 	ddi_release_devi(dip);
405 
406 	return (error);
407 }
408 
409 /*
410  * Fetch the state of Hotplug Connection (CN).
411  * This function will also update the state and last changed timestamp in the
412  * connection handle structure if the state has changed.
413  */
414 int
ddihp_cn_getstate(ddi_hp_cn_handle_t * hdlp)415 ddihp_cn_getstate(ddi_hp_cn_handle_t *hdlp)
416 {
417 	ddi_hp_cn_state_t	new_state;
418 	int			ret;
419 
420 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_getstate: pdip %p hdlp %p\n",
421 	    (void *)hdlp->cn_dip, (void *)hdlp));
422 
423 	ASSERT(DEVI_BUSY_OWNED(hdlp->cn_dip));
424 
425 	DDIHP_CN_OPS(hdlp, DDI_HPOP_CN_GET_STATE,
426 	    NULL, (void *)&new_state, ret);
427 	if (ret != DDI_SUCCESS) {
428 		DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_getstate: "
429 		    "CN %p getstate command failed\n", (void *)hdlp));
430 
431 		return (ret);
432 	}
433 
434 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_getstate: hdlp %p "
435 	    "current Connection state %x new Connection state %x\n",
436 	    (void *)hdlp, hdlp->cn_info.cn_state, new_state));
437 
438 	if (new_state != hdlp->cn_info.cn_state) {
439 		hdlp->cn_info.cn_state = new_state;
440 		ddihp_update_last_change(hdlp);
441 	}
442 
443 	return (ret);
444 }
445 
446 /*
447  * Implementation function for unregistering the Hotplug Connection (CN)
448  */
449 int
ddihp_cn_unregister(ddi_hp_cn_handle_t * hdlp)450 ddihp_cn_unregister(ddi_hp_cn_handle_t *hdlp)
451 {
452 	dev_info_t	*dip = hdlp->cn_dip;
453 
454 	DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_unregister: hdlp %p\n",
455 	    (void *)hdlp));
456 
457 	ASSERT(DEVI_BUSY_OWNED(dip));
458 
459 	(void) ddihp_cn_getstate(hdlp);
460 
461 	if (hdlp->cn_info.cn_state > DDI_HP_CN_STATE_OFFLINE) {
462 		DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_unregister: dip %p, hdlp %p "
463 		    "state %x. Device busy, failed to unregister connection!\n",
464 		    (void *)dip, (void *)hdlp, hdlp->cn_info.cn_state));
465 
466 		return (DDI_EBUSY);
467 	}
468 
469 	/* unlink the handle */
470 	DDIHP_LIST_REMOVE(ddi_hp_cn_handle_t, (DEVI(dip)->devi_hp_hdlp), hdlp);
471 
472 	kmem_free(hdlp->cn_info.cn_name, strlen(hdlp->cn_info.cn_name) + 1);
473 	kmem_free(hdlp, sizeof (ddi_hp_cn_handle_t));
474 	return (DDI_SUCCESS);
475 }
476 
477 /*
478  * For a given Connection name and the dip node where the Connection is
479  * supposed to be, find the corresponding hotplug handle.
480  */
481 ddi_hp_cn_handle_t *
ddihp_cn_name_to_handle(dev_info_t * dip,char * cn_name)482 ddihp_cn_name_to_handle(dev_info_t *dip, char *cn_name)
483 {
484 	ddi_hp_cn_handle_t *hdlp;
485 
486 	ASSERT(DEVI_BUSY_OWNED(dip));
487 
488 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_name_to_handle: "
489 	    "dip %p cn_name to find: %s", (void *)dip, cn_name));
490 	for (hdlp = DEVI(dip)->devi_hp_hdlp; hdlp; hdlp = hdlp->next) {
491 		DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_name_to_handle: "
492 		    "current cn_name: %s", hdlp->cn_info.cn_name));
493 
494 		if (strcmp(cn_name, hdlp->cn_info.cn_name) == 0) {
495 			/* found */
496 			return (hdlp);
497 		}
498 	}
499 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_name_to_handle: "
500 	    "failed to find cn_name"));
501 	return (NULL);
502 }
503 
504 /*
505  * Process the hotplug operations for Connector and also create Port
506  * upon user command.
507  */
508 int
ddihp_connector_ops(ddi_hp_cn_handle_t * hdlp,ddi_hp_op_t op,void * arg,void * result)509 ddihp_connector_ops(ddi_hp_cn_handle_t *hdlp, ddi_hp_op_t op,
510     void *arg, void *result)
511 {
512 	int			rv = DDI_SUCCESS;
513 	dev_info_t		*dip = hdlp->cn_dip;
514 
515 	ASSERT(DEVI_BUSY_OWNED(dip));
516 
517 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_connector_ops: pdip=%p op=%x "
518 	    "hdlp=%p arg=%p\n", (void *)dip, op, (void *)hdlp, arg));
519 
520 	if (op == DDI_HPOP_CN_CHANGE_STATE) {
521 		ddi_hp_cn_state_t target_state = *(ddi_hp_cn_state_t *)arg;
522 
523 		rv = ddihp_cn_pre_change_state(hdlp, target_state);
524 		if (rv != DDI_SUCCESS) {
525 			/* the state is not changed */
526 			*((ddi_hp_cn_state_t *)result) =
527 			    hdlp->cn_info.cn_state;
528 			return (rv);
529 		}
530 	}
531 	ASSERT(NEXUS_HAS_HP_OP(dip));
532 	rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))(
533 	    dip, hdlp->cn_info.cn_name, op, arg, result);
534 
535 	if (rv != DDI_SUCCESS) {
536 		DDI_HP_IMPLDBG((CE_CONT, "ddihp_connector_ops: "
537 		    "bus_hp_op failed: pdip=%p cn_name:%s op=%x "
538 		    "hdlp=%p arg=%p\n", (void *)dip, hdlp->cn_info.cn_name,
539 		    op, (void *)hdlp, arg));
540 	}
541 	if (op == DDI_HPOP_CN_CHANGE_STATE) {
542 		int rv_post;
543 
544 		DDI_HP_IMPLDBG((CE_CONT, "ddihp_connector_ops: "
545 		    "old_state=%x, new_state=%x, rv=%x\n",
546 		    hdlp->cn_info.cn_state, *(ddi_hp_cn_state_t *)result, rv));
547 
548 		/*
549 		 * After state change op is successfully done or
550 		 * failed at some stages, continue to do some jobs.
551 		 */
552 		rv_post = ddihp_cn_post_change_state(hdlp,
553 		    *(ddi_hp_cn_state_t *)result);
554 
555 		if (rv_post != DDI_SUCCESS)
556 			rv = rv_post;
557 	}
558 
559 	return (rv);
560 }
561 
562 /*
563  * Process the hotplug op for Port
564  */
565 int
ddihp_port_ops(ddi_hp_cn_handle_t * hdlp,ddi_hp_op_t op,void * arg,void * result)566 ddihp_port_ops(ddi_hp_cn_handle_t *hdlp, ddi_hp_op_t op,
567     void *arg, void *result)
568 {
569 	int		ret = DDI_SUCCESS;
570 
571 	ASSERT(DEVI_BUSY_OWNED(hdlp->cn_dip));
572 
573 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_port_ops: pdip=%p op=%x hdlp=%p "
574 	    "arg=%p\n", (void *)hdlp->cn_dip, op, (void *)hdlp, arg));
575 
576 	switch (op) {
577 	case DDI_HPOP_CN_GET_STATE:
578 	{
579 		int state;
580 
581 		state = hdlp->cn_info.cn_state;
582 
583 		if (hdlp->cn_info.cn_child == NULL) {
584 			/* No child. Either present or empty. */
585 			if (state >= DDI_HP_CN_STATE_PORT_PRESENT)
586 				state = DDI_HP_CN_STATE_PORT_PRESENT;
587 			else
588 				state = DDI_HP_CN_STATE_PORT_EMPTY;
589 
590 		} else { /* There is a child of this Port */
591 
592 			/* Check DEVI(dip)->devi_node_state */
593 			switch (i_ddi_node_state(hdlp->cn_info.cn_child)) {
594 			case	DS_INVAL:
595 			case	DS_PROTO:
596 			case	DS_LINKED:
597 			case	DS_BOUND:
598 			case	DS_INITIALIZED:
599 			case	DS_PROBED:
600 				state = DDI_HP_CN_STATE_OFFLINE;
601 				break;
602 			case	DS_ATTACHED:
603 				state = DDI_HP_CN_STATE_MAINTENANCE;
604 				break;
605 			case	DS_READY:
606 				state = DDI_HP_CN_STATE_ONLINE;
607 				break;
608 			default:
609 				/* should never reach here */
610 				ASSERT("unknown devinfo state");
611 			}
612 			/*
613 			 * Check DEVI(dip)->devi_state in case the node is
614 			 * downgraded or quiesced.
615 			 */
616 			if (state == DDI_HP_CN_STATE_ONLINE &&
617 			    ddi_get_devstate(hdlp->cn_info.cn_child) !=
618 			    DDI_DEVSTATE_UP)
619 				state = DDI_HP_CN_STATE_MAINTENANCE;
620 		}
621 
622 		*((ddi_hp_cn_state_t *)result) = state;
623 
624 		break;
625 	}
626 	case DDI_HPOP_CN_CHANGE_STATE:
627 	{
628 		ddi_hp_cn_state_t target_state = *(ddi_hp_cn_state_t *)arg;
629 		ddi_hp_cn_state_t curr_state = hdlp->cn_info.cn_state;
630 
631 		ret = ddihp_port_change_state(hdlp, target_state);
632 		if (curr_state != hdlp->cn_info.cn_state) {
633 			ddihp_update_last_change(hdlp);
634 		}
635 		*((ddi_hp_cn_state_t *)result) = hdlp->cn_info.cn_state;
636 
637 		break;
638 	}
639 	case DDI_HPOP_CN_REMOVE_PORT:
640 	{
641 		(void) ddihp_cn_getstate(hdlp);
642 
643 		if (hdlp->cn_info.cn_state != DDI_HP_CN_STATE_PORT_EMPTY) {
644 			/* Only empty PORT can be removed by commands */
645 			ret = DDI_EBUSY;
646 
647 			break;
648 		}
649 
650 		ret = ddihp_cn_unregister(hdlp);
651 		break;
652 	}
653 	default:
654 		ret = DDI_ENOTSUP;
655 		break;
656 	}
657 
658 	return (ret);
659 }
660 
661 /*
662  * Generate the system event with a possible hint
663  */
664 /* ARGSUSED */
665 void
ddihp_cn_gen_sysevent(ddi_hp_cn_handle_t * hdlp,ddi_hp_cn_sysevent_t event_sub_class,int hint,int kmflag)666 ddihp_cn_gen_sysevent(ddi_hp_cn_handle_t *hdlp,
667     ddi_hp_cn_sysevent_t event_sub_class, int hint, int kmflag)
668 {
669 	dev_info_t	*dip = hdlp->cn_dip;
670 	char		*cn_path, *ap_id;
671 	char		*ev_subclass = NULL;
672 	nvlist_t	*ev_attr_list = NULL;
673 	sysevent_id_t	eid;
674 	int		ap_id_len, err;
675 
676 	cn_path = kmem_zalloc(MAXPATHLEN, kmflag);
677 	if (cn_path == NULL) {
678 		cmn_err(CE_WARN,
679 		    "%s%d: Failed to allocate memory for hotplug"
680 		    " connection: %s\n",
681 		    ddi_driver_name(dip), ddi_get_instance(dip),
682 		    hdlp->cn_info.cn_name);
683 
684 		return;
685 	}
686 
687 	/*
688 	 * Minor device name will be bus path
689 	 * concatenated with connection name.
690 	 * One of consumers of the sysevent will pass it
691 	 * to cfgadm as AP ID.
692 	 */
693 	(void) strcpy(cn_path, "/devices");
694 	(void) ddi_pathname(dip, cn_path + strlen("/devices"));
695 
696 	ap_id_len = strlen(cn_path) + strlen(":") +
697 	    strlen(hdlp->cn_info.cn_name) + 1;
698 	ap_id = kmem_zalloc(ap_id_len, kmflag);
699 	if (ap_id == NULL) {
700 		cmn_err(CE_WARN,
701 		    "%s%d: Failed to allocate memory for AP ID: %s:%s\n",
702 		    ddi_driver_name(dip), ddi_get_instance(dip),
703 		    cn_path, hdlp->cn_info.cn_name);
704 		kmem_free(cn_path, MAXPATHLEN);
705 
706 		return;
707 	}
708 
709 	(void) strcpy(ap_id, cn_path);
710 	(void) strcat(ap_id, ":");
711 	(void) strcat(ap_id, hdlp->cn_info.cn_name);
712 	kmem_free(cn_path, MAXPATHLEN);
713 
714 	err = nvlist_alloc(&ev_attr_list, NV_UNIQUE_NAME_TYPE, kmflag);
715 
716 	if (err != 0) {
717 		cmn_err(CE_WARN,
718 		    "%s%d: Failed to allocate memory for event subclass %d\n",
719 		    ddi_driver_name(dip), ddi_get_instance(dip),
720 		    event_sub_class);
721 		kmem_free(ap_id, ap_id_len);
722 
723 		return;
724 	}
725 
726 	switch (event_sub_class) {
727 	case DDI_HP_CN_STATE_CHANGE:
728 		ev_subclass = ESC_DR_AP_STATE_CHANGE;
729 
730 		switch (hint) {
731 		case SE_NO_HINT:	/* fall through */
732 		case SE_HINT_INSERT:	/* fall through */
733 		case SE_HINT_REMOVE:
734 			err = nvlist_add_string(ev_attr_list, DR_HINT,
735 			    SE_HINT2STR(hint));
736 
737 			if (err != 0) {
738 				cmn_err(CE_WARN, "%s%d: Failed to add attr [%s]"
739 				    " for %s event\n", ddi_driver_name(dip),
740 				    ddi_get_instance(dip), DR_HINT,
741 				    ESC_DR_AP_STATE_CHANGE);
742 
743 				goto done;
744 			}
745 			break;
746 
747 		default:
748 			cmn_err(CE_WARN, "%s%d: Unknown hint on sysevent\n",
749 			    ddi_driver_name(dip), ddi_get_instance(dip));
750 
751 			goto done;
752 		}
753 
754 		break;
755 
756 	/* event sub class: DDI_HP_CN_REQ */
757 	case DDI_HP_CN_REQ:
758 		ev_subclass = ESC_DR_REQ;
759 
760 		switch (hint) {
761 		case SE_INVESTIGATE_RES: /* fall through */
762 		case SE_INCOMING_RES:	/* fall through */
763 		case SE_OUTGOING_RES:	/* fall through */
764 			err = nvlist_add_string(ev_attr_list, DR_REQ_TYPE,
765 			    SE_REQ2STR(hint));
766 
767 			if (err != 0) {
768 				cmn_err(CE_WARN,
769 				    "%s%d: Failed to add attr [%s] for %s \n"
770 				    "event", ddi_driver_name(dip),
771 				    ddi_get_instance(dip),
772 				    DR_REQ_TYPE, ESC_DR_REQ);
773 
774 				goto done;
775 			}
776 			break;
777 
778 		default:
779 			cmn_err(CE_WARN, "%s%d:  Unknown hint on sysevent\n",
780 			    ddi_driver_name(dip), ddi_get_instance(dip));
781 
782 			goto done;
783 		}
784 
785 		break;
786 
787 	default:
788 		cmn_err(CE_WARN, "%s%d:  Unknown Event subclass\n",
789 		    ddi_driver_name(dip), ddi_get_instance(dip));
790 
791 		goto done;
792 	}
793 
794 	/*
795 	 * Add Hotplug Connection (CN) as attribute (common attribute)
796 	 */
797 	err = nvlist_add_string(ev_attr_list, DR_AP_ID, ap_id);
798 	if (err != 0) {
799 		cmn_err(CE_WARN, "%s%d: Failed to add attr [%s] for %s event\n",
800 		    ddi_driver_name(dip), ddi_get_instance(dip),
801 		    DR_AP_ID, EC_DR);
802 
803 		goto done;
804 	}
805 
806 	/*
807 	 * Log this event with sysevent framework.
808 	 */
809 	err = ddi_log_sysevent(dip, DDI_VENDOR_SUNW, EC_DR,
810 	    ev_subclass, ev_attr_list, &eid,
811 	    ((kmflag == KM_SLEEP) ? DDI_SLEEP : DDI_NOSLEEP));
812 
813 	if (err != 0) {
814 		cmn_err(CE_WARN, "%s%d: Failed to log %s event\n",
815 		    ddi_driver_name(dip), ddi_get_instance(dip), EC_DR);
816 	}
817 
818 done:
819 	nvlist_free(ev_attr_list);
820 	kmem_free(ap_id, ap_id_len);
821 }
822 
823 /*
824  * Local functions (called within this file)
825  */
826 
827 /*
828  * Connector operations
829  */
830 
831 /*
832  * Prepare to change state for a Connector: offline, unprobe, etc.
833  */
834 static int
ddihp_cn_pre_change_state(ddi_hp_cn_handle_t * hdlp,ddi_hp_cn_state_t target_state)835 ddihp_cn_pre_change_state(ddi_hp_cn_handle_t *hdlp,
836     ddi_hp_cn_state_t target_state)
837 {
838 	ddi_hp_cn_state_t	curr_state = hdlp->cn_info.cn_state;
839 	dev_info_t		*dip = hdlp->cn_dip;
840 	int			rv = DDI_SUCCESS;
841 
842 	if (curr_state > target_state &&
843 	    curr_state == DDI_HP_CN_STATE_ENABLED) {
844 		/*
845 		 * If the Connection goes to a lower state from ENABLED,
846 		 * then offline all children under it.
847 		 */
848 		rv = ddihp_cn_change_children_state(hdlp, B_FALSE);
849 		if (rv != DDI_SUCCESS) {
850 			cmn_err(CE_WARN,
851 			    "(%s%d): "
852 			    "failed to unconfigure the device in the"
853 			    " Connection %s\n", ddi_driver_name(dip),
854 			    ddi_get_instance(dip),
855 			    hdlp->cn_info.cn_name);
856 
857 			return (rv);
858 		}
859 		ASSERT(NEXUS_HAS_HP_OP(dip));
860 		/*
861 		 * Remove all the children and their ports
862 		 * after they are offlined.
863 		 */
864 		rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))(
865 		    dip, hdlp->cn_info.cn_name, DDI_HPOP_CN_UNPROBE,
866 		    NULL, NULL);
867 		if (rv != DDI_SUCCESS) {
868 			cmn_err(CE_WARN,
869 			    "(%s%d): failed"
870 			    " to unprobe the device in the Connector"
871 			    " %s\n", ddi_driver_name(dip),
872 			    ddi_get_instance(dip),
873 			    hdlp->cn_info.cn_name);
874 
875 			return (rv);
876 		}
877 
878 		DDI_HP_NEXDBG((CE_CONT,
879 		    "ddihp_connector_ops (%s%d): device"
880 		    " is unconfigured and unprobed in Connector %s\n",
881 		    ddi_driver_name(dip), ddi_get_instance(dip),
882 		    hdlp->cn_info.cn_name));
883 	}
884 
885 	return (rv);
886 }
887 
888 /*
889  * Jobs after change state of a Connector: update state, last change time,
890  * probe, online, sysevent, etc.
891  */
892 static int
ddihp_cn_post_change_state(ddi_hp_cn_handle_t * hdlp,ddi_hp_cn_state_t new_state)893 ddihp_cn_post_change_state(ddi_hp_cn_handle_t *hdlp,
894     ddi_hp_cn_state_t new_state)
895 {
896 	int			rv = DDI_SUCCESS;
897 	ddi_hp_cn_state_t	curr_state = hdlp->cn_info.cn_state;
898 
899 	/* Update the state in handle */
900 	if (new_state != curr_state) {
901 		hdlp->cn_info.cn_state = new_state;
902 		ddihp_update_last_change(hdlp);
903 	}
904 
905 	if (curr_state < new_state &&
906 	    new_state == DDI_HP_CN_STATE_ENABLED) {
907 		/*
908 		 * Probe and online devices if state is
909 		 * upgraded to ENABLED.
910 		 */
911 		rv = ddihp_cn_handle_state_change(hdlp);
912 	}
913 	if (curr_state != hdlp->cn_info.cn_state) {
914 		/*
915 		 * For Connector, generate a sysevent on
916 		 * state change.
917 		 */
918 		ddihp_cn_gen_sysevent(hdlp, DDI_HP_CN_STATE_CHANGE,
919 		    SE_NO_HINT, KM_SLEEP);
920 	}
921 
922 	return (rv);
923 }
924 
925 /*
926  * Handle Connector state change.
927  *
928  * This function is called after connector is upgraded to ENABLED sate.
929  * It probes the device plugged in the connector to setup devinfo nodes
930  * and then online the nodes.
931  */
932 static int
ddihp_cn_handle_state_change(ddi_hp_cn_handle_t * hdlp)933 ddihp_cn_handle_state_change(ddi_hp_cn_handle_t *hdlp)
934 {
935 	dev_info_t		*dip = hdlp->cn_dip;
936 	int			rv = DDI_SUCCESS;
937 
938 	ASSERT(DEVI_BUSY_OWNED(dip));
939 	ASSERT(NEXUS_HAS_HP_OP(dip));
940 	/*
941 	 * If the Connection went to state ENABLED from a lower state,
942 	 * probe it.
943 	 */
944 	rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))(
945 	    dip, hdlp->cn_info.cn_name, DDI_HPOP_CN_PROBE, NULL, NULL);
946 
947 	if (rv != DDI_SUCCESS) {
948 		ddi_hp_cn_state_t	target_state = DDI_HP_CN_STATE_POWERED;
949 		ddi_hp_cn_state_t	result_state = 0;
950 
951 		/*
952 		 * Probe failed. Disable the connector so that it can
953 		 * be enabled again by a later try from userland.
954 		 */
955 		(void) (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))(
956 		    dip, hdlp->cn_info.cn_name, DDI_HPOP_CN_CHANGE_STATE,
957 		    (void *)&target_state, (void *)&result_state);
958 
959 		if (result_state && result_state != hdlp->cn_info.cn_state) {
960 			hdlp->cn_info.cn_state = result_state;
961 			ddihp_update_last_change(hdlp);
962 		}
963 
964 		cmn_err(CE_WARN,
965 		    "(%s%d): failed to probe the Connection %s\n",
966 		    ddi_driver_name(dip), ddi_get_instance(dip),
967 		    hdlp->cn_info.cn_name);
968 
969 		return (rv);
970 	}
971 	/*
972 	 * Try to online all the children of CN.
973 	 */
974 	(void) ddihp_cn_change_children_state(hdlp, B_TRUE);
975 
976 	DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_event_handler (%s%d): "
977 	    "device is configured in the Connection %s\n",
978 	    ddi_driver_name(dip), ddi_get_instance(dip),
979 	    hdlp->cn_info.cn_name));
980 	return (rv);
981 }
982 
983 /*
984  * Online/Offline all the children under the Hotplug Connection (CN)
985  *
986  * Do online operation when the online parameter is true; otherwise do offline.
987  */
988 static int
ddihp_cn_change_children_state(ddi_hp_cn_handle_t * hdlp,boolean_t online)989 ddihp_cn_change_children_state(ddi_hp_cn_handle_t *hdlp, boolean_t online)
990 {
991 	dev_info_t		*dip = hdlp->cn_dip;
992 	dev_info_t		*cdip;
993 	ddi_hp_cn_handle_t	*h;
994 	int			rv = DDI_SUCCESS;
995 
996 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_change_children_state:"
997 	    " dip %p hdlp %p, online %x\n",
998 	    (void *)dip, (void *)hdlp, online));
999 
1000 	ASSERT(DEVI_BUSY_OWNED(dip));
1001 
1002 	/*
1003 	 * Return invalid if Connection state is < DDI_HP_CN_STATE_ENABLED
1004 	 * when try to online children.
1005 	 */
1006 	if (online && hdlp->cn_info.cn_state < DDI_HP_CN_STATE_ENABLED) {
1007 		DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_change_children_state: "
1008 		    "Connector %p is not in probed state\n", (void *)hdlp));
1009 
1010 		return (DDI_EINVAL);
1011 	}
1012 
1013 	/* Now, online/offline all the devices depending on the Connector */
1014 
1015 	if (!online) {
1016 		/*
1017 		 * For offline operation we need to firstly clean up devfs
1018 		 * so as not to prevent driver detach.
1019 		 */
1020 		(void) devfs_clean(dip, NULL, DV_CLEAN_FORCE);
1021 	}
1022 	for (h = DEVI(dip)->devi_hp_hdlp; h; h = h->next) {
1023 		if (h->cn_info.cn_type != DDI_HP_CN_TYPE_VIRTUAL_PORT)
1024 			continue;
1025 
1026 		if (h->cn_info.cn_num_dpd_on !=
1027 		    hdlp->cn_info.cn_num)
1028 			continue;
1029 
1030 		cdip = h->cn_info.cn_child;
1031 		ASSERT(cdip);
1032 		if (online) {
1033 			/* online children */
1034 			if (!ddihp_check_status_prop(dip))
1035 				continue;
1036 
1037 			if (ndi_devi_online(cdip,
1038 			    NDI_ONLINE_ATTACH | NDI_CONFIG) != NDI_SUCCESS) {
1039 				cmn_err(CE_WARN,
1040 				    "(%s%d):"
1041 				    " failed to attach driver for a device"
1042 				    " (%s%d) under the Connection %s\n",
1043 				    ddi_driver_name(dip), ddi_get_instance(dip),
1044 				    ddi_driver_name(cdip),
1045 				    ddi_get_instance(cdip),
1046 				    hdlp->cn_info.cn_name);
1047 				/*
1048 				 * One of the devices failed to online, but we
1049 				 * want to continue to online the rest siblings
1050 				 * after mark the failure here.
1051 				 */
1052 				rv = DDI_FAILURE;
1053 
1054 				continue;
1055 			}
1056 		} else {
1057 			/* offline children */
1058 			if (ndi_devi_offline(cdip, NDI_UNCONFIG) !=
1059 			    NDI_SUCCESS) {
1060 				cmn_err(CE_WARN,
1061 				    "(%s%d):"
1062 				    " failed to detach driver for the device"
1063 				    " (%s%d) in the Connection %s\n",
1064 				    ddi_driver_name(dip), ddi_get_instance(dip),
1065 				    ddi_driver_name(cdip),
1066 				    ddi_get_instance(cdip),
1067 				    hdlp->cn_info.cn_name);
1068 
1069 				return (DDI_EBUSY);
1070 			}
1071 		}
1072 	}
1073 
1074 	return (rv);
1075 }
1076 
1077 /*
1078  * Port operations
1079  */
1080 
1081 /*
1082  * Change Port state to target_state.
1083  */
1084 static int
ddihp_port_change_state(ddi_hp_cn_handle_t * hdlp,ddi_hp_cn_state_t target_state)1085 ddihp_port_change_state(ddi_hp_cn_handle_t *hdlp,
1086     ddi_hp_cn_state_t target_state)
1087 {
1088 	ddi_hp_cn_state_t curr_state = hdlp->cn_info.cn_state;
1089 
1090 	if (target_state < DDI_HP_CN_STATE_PORT_EMPTY ||
1091 	    target_state > DDI_HP_CN_STATE_ONLINE) {
1092 
1093 		return (DDI_EINVAL);
1094 	}
1095 
1096 	if (curr_state < target_state)
1097 		return (ddihp_port_upgrade_state(hdlp, target_state));
1098 	else if (curr_state > target_state)
1099 		return (ddihp_port_downgrade_state(hdlp, target_state));
1100 	else
1101 		return (DDI_SUCCESS);
1102 }
1103 
1104 /*
1105  * Upgrade port state to target_state.
1106  */
1107 static int
ddihp_port_upgrade_state(ddi_hp_cn_handle_t * hdlp,ddi_hp_cn_state_t target_state)1108 ddihp_port_upgrade_state(ddi_hp_cn_handle_t *hdlp,
1109     ddi_hp_cn_state_t target_state)
1110 {
1111 	ddi_hp_cn_state_t	curr_state, new_state, result_state;
1112 	dev_info_t		*cdip;
1113 	int			rv = DDI_SUCCESS;
1114 
1115 	curr_state = hdlp->cn_info.cn_state;
1116 	while (curr_state < target_state) {
1117 		switch (curr_state) {
1118 		case DDI_HP_CN_STATE_PORT_EMPTY:
1119 			/* Check the existence of the corresponding hardware */
1120 			new_state = DDI_HP_CN_STATE_PORT_PRESENT;
1121 			rv = ddihp_connector_ops(hdlp,
1122 			    DDI_HPOP_CN_CHANGE_STATE,
1123 			    (void *)&new_state, (void *)&result_state);
1124 			if (rv == DDI_SUCCESS) {
1125 				hdlp->cn_info.cn_state =
1126 				    result_state;
1127 			}
1128 			break;
1129 		case DDI_HP_CN_STATE_PORT_PRESENT:
1130 			/* Read-only probe the corresponding hardware. */
1131 			new_state = DDI_HP_CN_STATE_OFFLINE;
1132 			rv = ddihp_connector_ops(hdlp,
1133 			    DDI_HPOP_CN_CHANGE_STATE,
1134 			    (void *)&new_state, &cdip);
1135 			if (rv == DDI_SUCCESS) {
1136 				hdlp->cn_info.cn_state =
1137 				    DDI_HP_CN_STATE_OFFLINE;
1138 
1139 				ASSERT(hdlp->cn_info.cn_child == NULL);
1140 				hdlp->cn_info.cn_child = cdip;
1141 			}
1142 			break;
1143 		case DDI_HP_CN_STATE_OFFLINE:
1144 			/* fall through */
1145 		case DDI_HP_CN_STATE_MAINTENANCE:
1146 
1147 			cdip = hdlp->cn_info.cn_child;
1148 
1149 			rv = ndi_devi_online(cdip,
1150 			    NDI_ONLINE_ATTACH | NDI_CONFIG);
1151 			if (rv == NDI_SUCCESS) {
1152 				hdlp->cn_info.cn_state =
1153 				    DDI_HP_CN_STATE_ONLINE;
1154 				rv = DDI_SUCCESS;
1155 			} else {
1156 				rv = DDI_FAILURE;
1157 				DDI_HP_IMPLDBG((CE_CONT,
1158 				    "ddihp_port_upgrade_state: "
1159 				    "failed to online device %p at port: %s\n",
1160 				    (void *)cdip, hdlp->cn_info.cn_name));
1161 			}
1162 			break;
1163 		case DDI_HP_CN_STATE_ONLINE:
1164 
1165 			break;
1166 		default:
1167 			/* should never reach here */
1168 			ASSERT("unknown devinfo state");
1169 		}
1170 		curr_state = hdlp->cn_info.cn_state;
1171 		if (rv != DDI_SUCCESS) {
1172 			DDI_HP_IMPLDBG((CE_CONT, "ddihp_port_upgrade_state: "
1173 			    "failed curr_state=%x, target_state=%x \n",
1174 			    curr_state, target_state));
1175 			return (rv);
1176 		}
1177 	}
1178 
1179 	return (rv);
1180 }
1181 
1182 /*
1183  * Downgrade state to target_state
1184  */
1185 static int
ddihp_port_downgrade_state(ddi_hp_cn_handle_t * hdlp,ddi_hp_cn_state_t target_state)1186 ddihp_port_downgrade_state(ddi_hp_cn_handle_t *hdlp,
1187     ddi_hp_cn_state_t target_state)
1188 {
1189 	ddi_hp_cn_state_t	curr_state, new_state, result_state;
1190 	dev_info_t		*dip = hdlp->cn_dip;
1191 	dev_info_t		*cdip;
1192 	int			rv = DDI_SUCCESS;
1193 
1194 	curr_state = hdlp->cn_info.cn_state;
1195 	while (curr_state > target_state) {
1196 
1197 		switch (curr_state) {
1198 		case DDI_HP_CN_STATE_PORT_EMPTY:
1199 
1200 			break;
1201 		case DDI_HP_CN_STATE_PORT_PRESENT:
1202 			/* Check the existence of the corresponding hardware */
1203 			new_state = DDI_HP_CN_STATE_PORT_EMPTY;
1204 			rv = ddihp_connector_ops(hdlp,
1205 			    DDI_HPOP_CN_CHANGE_STATE,
1206 			    (void *)&new_state, (void *)&result_state);
1207 			if (rv == DDI_SUCCESS)
1208 				hdlp->cn_info.cn_state =
1209 				    result_state;
1210 
1211 			break;
1212 		case DDI_HP_CN_STATE_OFFLINE:
1213 			/*
1214 			 * Read-only unprobe the corresponding hardware:
1215 			 * 1. release the assigned resource;
1216 			 * 2. remove the node pointed by the port's cn_child
1217 			 */
1218 			new_state = DDI_HP_CN_STATE_PORT_PRESENT;
1219 			rv = ddihp_connector_ops(hdlp,
1220 			    DDI_HPOP_CN_CHANGE_STATE,
1221 			    (void *)&new_state, (void *)&result_state);
1222 			if (rv == DDI_SUCCESS)
1223 				hdlp->cn_info.cn_state =
1224 				    DDI_HP_CN_STATE_PORT_PRESENT;
1225 			break;
1226 		case DDI_HP_CN_STATE_MAINTENANCE:
1227 			/* fall through. */
1228 		case DDI_HP_CN_STATE_ONLINE:
1229 			cdip = hdlp->cn_info.cn_child;
1230 
1231 			(void) devfs_clean(dip, NULL, DV_CLEAN_FORCE);
1232 			rv = ndi_devi_offline(cdip, NDI_UNCONFIG);
1233 			if (rv == NDI_SUCCESS) {
1234 				hdlp->cn_info.cn_state =
1235 				    DDI_HP_CN_STATE_OFFLINE;
1236 				rv = DDI_SUCCESS;
1237 			} else {
1238 				rv = DDI_EBUSY;
1239 				DDI_HP_IMPLDBG((CE_CONT,
1240 				    "ddihp_port_downgrade_state: failed "
1241 				    "to offline node, rv=%x, cdip=%p \n",
1242 				    rv, (void *)cdip));
1243 			}
1244 
1245 			break;
1246 		default:
1247 			/* should never reach here */
1248 			ASSERT("unknown devinfo state");
1249 		}
1250 		curr_state = hdlp->cn_info.cn_state;
1251 		if (rv != DDI_SUCCESS) {
1252 			DDI_HP_IMPLDBG((CE_CONT,
1253 			    "ddihp_port_downgrade_state: failed "
1254 			    "curr_state=%x, target_state=%x \n",
1255 			    curr_state, target_state));
1256 			return (rv);
1257 		}
1258 	}
1259 
1260 	return (rv);
1261 }
1262 
1263 /*
1264  * Misc routines
1265  */
1266 
1267 /* Update the last state change time */
1268 static void
ddihp_update_last_change(ddi_hp_cn_handle_t * hdlp)1269 ddihp_update_last_change(ddi_hp_cn_handle_t *hdlp)
1270 {
1271 	time_t			time;
1272 
1273 	if (drv_getparm(TIME, (void *)&time) != DDI_SUCCESS)
1274 		hdlp->cn_info.cn_last_change = (time_t)-1;
1275 	else
1276 		hdlp->cn_info.cn_last_change = (time32_t)time;
1277 }
1278 
1279 /*
1280  * Check the device for a 'status' property.  A conforming device
1281  * should have a status of "okay", "disabled", "fail", or "fail-xxx".
1282  *
1283  * Return FALSE for a conforming device that is disabled or faulted.
1284  * Return TRUE in every other case.
1285  *
1286  * 'status' property is NOT a bus specific property. It is defined in page 184,
1287  * IEEE 1275 spec. The full name of the spec is "IEEE Standard for
1288  * Boot (Initialization Configuration) Firmware: Core Requirements and
1289  * Practices".
1290  */
1291 static boolean_t
ddihp_check_status_prop(dev_info_t * dip)1292 ddihp_check_status_prop(dev_info_t *dip)
1293 {
1294 	char		*status_prop;
1295 	boolean_t	rv = B_TRUE;
1296 
1297 	/* try to get the 'status' property */
1298 	if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
1299 	    "status", &status_prop) == DDI_PROP_SUCCESS) {
1300 		/*
1301 		 * test if the status is "disabled", "fail", or
1302 		 * "fail-xxx".
1303 		 */
1304 		if (strcmp(status_prop, "disabled") == 0) {
1305 			rv = B_FALSE;
1306 			DDI_HP_IMPLDBG((CE_CONT, "ddihp_check_status_prop "
1307 			    "(%s%d): device is in disabled state",
1308 			    ddi_driver_name(dip), ddi_get_instance(dip)));
1309 		} else if (strncmp(status_prop, "fail", 4) == 0) {
1310 			rv = B_FALSE;
1311 			cmn_err(CE_WARN,
1312 			    "hotplug (%s%d): device is in fault state (%s)\n",
1313 			    ddi_driver_name(dip), ddi_get_instance(dip),
1314 			    status_prop);
1315 		}
1316 
1317 		ddi_prop_free(status_prop);
1318 	}
1319 
1320 	return (rv);
1321 }
1322