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 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 pcount, count, 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, &pcount); 340 341 /* Lock before access */ 342 ndi_devi_enter(dip, &count); 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, count); 398 if (pdip != NULL) 399 ndi_devi_exit(pdip, pcount); 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 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 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 * 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 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 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 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 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 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 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 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 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 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 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 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 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