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 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 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 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 * 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 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 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 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 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 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 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 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 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 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 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 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 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