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