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