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 */ 27 28 /* 29 * Sun DDI hotplug implementation specific functions 30 */ 31 32 /* 33 * HOTPLUG FRAMEWORK 34 * 35 * The hotplug framework (also referred to "SHP", for "Solaris Hotplug 36 * Framework") refers to a large set of userland and kernel interfaces, 37 * including those in this file, that provide functionality related to device 38 * hotplug. 39 * 40 * Hotplug is a broad term that refers to both removal and insertion of devices 41 * on a live system. Such operations can have varying levels of notification to 42 * the system. Coordinated hotplug means that the operating system is notified 43 * in advance that a device will have a hotplug operation performed on it. 44 * Non-coordinated hotplug, also called "surprise removal", does not have such 45 * notification, and the device is simply removed or inserted from the system. 46 * 47 * The goals of a correct hotplug operation will vary based on the device. In 48 * general, though, we want the system to gracefully notice the device change 49 * and clean up (or create) any relevant structures related to using the device 50 * in the system. 51 * 52 * The goals of the hotplug framework are to provide common interfaces for nexus 53 * drivers, device drivers, and userland programs to build a foundation for 54 * implementing hotplug for a variety of devices. Notably, common support for 55 * PCIe devices is available. See also: the nexus driver for PCIe devices at 56 * uts/i86pc/io/pciex/npe.c. 57 * 58 * 59 * TERMINOLOGY 60 * 61 * The following terms may be useful when exploring hotplug-related code. 62 * 63 * PHYSICAL HOTPLUG 64 * Refers to hotplug operations on a physical hardware receptacle. 65 * 66 * VIRTUAL HOTPLUG 67 * Refers to hotplug operations on an arbitrary device node in the device 68 * tree. 69 * 70 * CONNECTION (often abbreviated "cn") 71 * A place where either physical or virtual hotplug happens. This is a more 72 * generic term to refer to "connectors" and "ports", which represent 73 * physical and virtual places where hotplug happens, respectively. 74 * 75 * CONNECTOR 76 * A place where physical hotplug happens. For example: a PCIe slot, a USB 77 * port, a SAS port, and a fiber channel port are all connectors. 78 * 79 * PORT 80 * A place where virtual hotplug happens. A port refers to an arbitrary 81 * place under a nexus dev_info node in the device tree. 82 * 83 * 84 * CONNECTION STATE MACHINE 85 * 86 * Connections have the states below. Connectors and ports are grouped into 87 * the same state machine. It is worth noting that the edges here are incomplete 88 * -- it is possible for a connection to move straight from ENABLED to EMPTY, 89 * for instance, if there is a surprise removal of its device. 90 * 91 * State changes are kicked off through two ways: 92 * - Through the nexus driver interface, ndi_hp_state_change_req. PCIe 93 * nexus drivers that pass a hotplug interrupt through to pciehpc will kick 94 * off state changes in this way. 95 * - Through coordinated removal, ddihp_modctl. Both cfgadm(1M) and 96 * hotplug(1M) pass state change requests through hotplugd, which uses 97 * modctl to request state changes to the DDI hotplug framework. That 98 * interface is ultimately implemented by ddihp_modctl. 99 * 100 * (start) 101 * | 102 * v 103 * EMPTY no component plugged into connector 104 * ^ 105 * v 106 * PRESENT component plugged into connector 107 * ^ 108 * v 109 * POWERED connector is powered 110 * ^ 111 * v 112 * ENABLED connector is fully functional 113 * | 114 * . 115 * . 116 * . 117 * v 118 * (create port) 119 * | 120 * v 121 * PORT EMPTY port has no device occupying it 122 * ^ 123 * v 124 * PORT PRESENT port occupied by device 125 * 126 * 127 * ARCHITECTURE DIAGRAM 128 * 129 * The following is a non-exhaustive summary of various components in the system 130 * that implement pieces of the hotplug framework. More detailed descriptions 131 * of some key components are below. 132 * 133 * +------------+ 134 * | cfgadm(1M) | 135 * +------------+ 136 * | 137 * +-------------------+ 138 * | SHP cfgadm plugin | 139 * +-------------------+ 140 * | 141 * +-------------+ +------------+ 142 * | hotplug(1M) |----------| libhotplug | 143 * +-------------+ +------------+ 144 * | 145 * +----------+ 146 * | hotplugd | 147 * +----------+ 148 * | 149 * +----------------+ 150 * | modctl (HP op) | 151 * +----------------+ 152 * | 153 * | 154 * User | 155 * =============================|=============================================== 156 * Kernel | 157 * | 158 * | 159 * +------------------------+ +----------------+ 160 * | DDI hotplug interfaces | --- | Device Drivers | 161 * +------------------------+ +----------------+ 162 * | | 163 * | +------------------------+ 164 * | | NDI hotplug interfaces | 165 * | +------------------------+ 166 * | | 167 * | | 168 * +-------------+ +--------------+ +---------------------------+ 169 * | `bus_hp_op` | -- |"pcie" module | --- | "npe" (PCIe nexus driver) | 170 * +-------------+ +--------------+ +---------------------------+ 171 * | | 172 * | +-------------------+ 173 * | | PCIe configurator | 174 * | +-------------------+ 175 * | 176 * +-------------------------------------+ 177 * | "pciehpc" (PCIe hotplug controller) | 178 * +-------------------------------------+ 179 * 180 * 181 * . 182 * . 183 * . 184 * . 185 * . 186 * | 187 * | 188 * +-----------------------------------+ 189 * | I/O Subsystem | 190 * | (LDI notifications and contracts) | 191 * +-----------------------------------+ 192 * 193 * 194 * KEY HOTPLUG SOFTWARE COMPONENTS 195 * 196 * CFGADM(1M) 197 * 198 * cfgadm is the canonical tool for hotplug operations. It can be used to 199 * list connections on the system and change their state in a coordinated 200 * fashion. For more information, see its manual page. 201 * 202 * 203 * HOTPLUG(1M) 204 * 205 * hotplug is a command line tool for managing hotplug connections for 206 * connectors. For more information, see its manual page. 207 * 208 * 209 * DDI HOTPLUG INTERFACES 210 * 211 * This part of the framework provides interfaces for changing device state 212 * for connectors, including onlining and offlining child devices. Many of 213 * these functions are defined in this file. 214 * 215 * 216 * NDI HOTPLUG INTERFACES 217 * 218 * Nexus drivers can define their own hotplug bus implementations by 219 * defining a bus_hp_op entry point. This entry point must implement 220 * a set of hotplug related commands, including getting, probing, and 221 * changing connection state, as well as port creation and removal. 222 * 223 * Nexus drivers may also want to use the following interfaces for 224 * implementing hotplug. Note that the PCIe Hotplug Controller ("pciehpc") 225 * already takes care of using these: 226 * ndi_hp_{register,unregister} 227 * ndi_hp_state_change_req 228 * ndi_hp_walk_cn 229 * 230 * PCIe nexus drivers should use the common entry point pcie_hp_common_ops, 231 * which implements hotplug commands for PCIe devices, calling into other 232 * parts of the framework as needed. 233 * 234 * 235 * NPE DRIVER ("npe") 236 * 237 * npe is the common nexus driver for PCIe devices on x86. It implements 238 * hotplug using the NDI interfaces. For more information, see 239 * uts/i86pc/io/pciex/npe.c. 240 * 241 * The equivalent driver for SPARC is "px". 242 * 243 * 244 * PCIe HOTPLUG CONTROLLER DRIVER ("pciehpc") 245 * 246 * All hotplug-capable PCIe buses will initialize their own PCIe HPC, 247 * including the pcieb and ppb drivers. The controller maintains 248 * hotplug-related state about the slots on its bus, including their status 249 * and port state. It also features a common implementation of handling 250 * hotplug-related PCIe interrupts. 251 * 252 * For more information, see its interfaces in 253 * uts/common/sys/hotplug/pci/pciehpc.h. 254 * 255 */ 256 257 #include <sys/sysmacros.h> 258 #include <sys/types.h> 259 #include <sys/file.h> 260 #include <sys/param.h> 261 #include <sys/systm.h> 262 #include <sys/kmem.h> 263 #include <sys/cmn_err.h> 264 #include <sys/debug.h> 265 #include <sys/avintr.h> 266 #include <sys/autoconf.h> 267 #include <sys/ddi.h> 268 #include <sys/sunndi.h> 269 #include <sys/ndi_impldefs.h> 270 #include <sys/sysevent.h> 271 #include <sys/sysevent/eventdefs.h> 272 #include <sys/sysevent/dr.h> 273 #include <sys/fs/dv_node.h> 274 275 /* 276 * Local function prototypes 277 */ 278 /* Connector operations */ 279 static int ddihp_cn_pre_change_state(ddi_hp_cn_handle_t *hdlp, 280 ddi_hp_cn_state_t target_state); 281 static int ddihp_cn_post_change_state(ddi_hp_cn_handle_t *hdlp, 282 ddi_hp_cn_state_t new_state); 283 static int ddihp_cn_handle_state_change(ddi_hp_cn_handle_t *hdlp); 284 static int ddihp_cn_change_children_state(ddi_hp_cn_handle_t *hdlp, 285 boolean_t online); 286 /* Port operations */ 287 static int ddihp_port_change_state(ddi_hp_cn_handle_t *hdlp, 288 ddi_hp_cn_state_t target_state); 289 static int ddihp_port_upgrade_state(ddi_hp_cn_handle_t *hdlp, 290 ddi_hp_cn_state_t target_state); 291 static int ddihp_port_downgrade_state(ddi_hp_cn_handle_t *hdlp, 292 ddi_hp_cn_state_t target_state); 293 /* Misc routines */ 294 static void ddihp_update_last_change(ddi_hp_cn_handle_t *hdlp); 295 static boolean_t ddihp_check_status_prop(dev_info_t *dip); 296 297 /* 298 * Global functions (called within hotplug framework) 299 */ 300 301 /* 302 * Implement modctl() commands for hotplug. 303 * Called by modctl_hp() in modctl.c 304 */ 305 int 306 ddihp_modctl(int hp_op, char *path, char *cn_name, uintptr_t arg, 307 uintptr_t rval) 308 { 309 dev_info_t *dip; 310 ddi_hp_cn_handle_t *hdlp; 311 ddi_hp_op_t op = (ddi_hp_op_t)hp_op; 312 int count, rv, error; 313 314 /* Get the dip of nexus node */ 315 dip = e_ddi_hold_devi_by_path(path, 0); 316 317 if (dip == NULL) 318 return (ENXIO); 319 320 DDI_HP_IMPLDBG((CE_CONT, "ddihp_modctl: dip %p op %x path %s " 321 "cn_name %s arg %p rval %p\n", (void *)dip, hp_op, path, cn_name, 322 (void *)arg, (void *)rval)); 323 324 if (!NEXUS_HAS_HP_OP(dip)) { 325 ddi_release_devi(dip); 326 return (ENOTSUP); 327 } 328 329 /* Lock before access */ 330 ndi_devi_enter(dip, &count); 331 332 hdlp = ddihp_cn_name_to_handle(dip, cn_name); 333 334 if (hp_op == DDI_HPOP_CN_CREATE_PORT) { 335 if (hdlp != NULL) { 336 /* this port already exists. */ 337 error = EEXIST; 338 339 goto done; 340 } 341 rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))( 342 dip, cn_name, op, NULL, NULL); 343 } else { 344 if (hdlp == NULL) { 345 /* Invalid Connection name */ 346 error = ENXIO; 347 348 goto done; 349 } 350 if (hp_op == DDI_HPOP_CN_CHANGE_STATE) { 351 ddi_hp_cn_state_t target_state = (ddi_hp_cn_state_t)arg; 352 ddi_hp_cn_state_t result_state = 0; 353 354 DDIHP_CN_OPS(hdlp, op, (void *)&target_state, 355 (void *)&result_state, rv); 356 357 DDI_HP_IMPLDBG((CE_CONT, "ddihp_modctl: target_state=" 358 "%x, result_state=%x, rv=%x \n", 359 target_state, result_state, rv)); 360 } else { 361 DDIHP_CN_OPS(hdlp, op, (void *)arg, (void *)rval, rv); 362 } 363 } 364 switch (rv) { 365 case DDI_SUCCESS: 366 error = 0; 367 break; 368 case DDI_EINVAL: 369 error = EINVAL; 370 break; 371 case DDI_EBUSY: 372 error = EBUSY; 373 break; 374 case DDI_ENOTSUP: 375 error = ENOTSUP; 376 break; 377 case DDI_ENOMEM: 378 error = ENOMEM; 379 break; 380 default: 381 error = EIO; 382 } 383 384 done: 385 ndi_devi_exit(dip, count); 386 387 ddi_release_devi(dip); 388 389 return (error); 390 } 391 392 /* 393 * Fetch the state of Hotplug Connection (CN). 394 * This function will also update the state and last changed timestamp in the 395 * connection handle structure if the state has changed. 396 */ 397 int 398 ddihp_cn_getstate(ddi_hp_cn_handle_t *hdlp) 399 { 400 ddi_hp_cn_state_t new_state; 401 int ret; 402 403 DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_getstate: pdip %p hdlp %p\n", 404 (void *)hdlp->cn_dip, (void *)hdlp)); 405 406 ASSERT(DEVI_BUSY_OWNED(hdlp->cn_dip)); 407 408 DDIHP_CN_OPS(hdlp, DDI_HPOP_CN_GET_STATE, 409 NULL, (void *)&new_state, ret); 410 if (ret != DDI_SUCCESS) { 411 DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_getstate: " 412 "CN %p getstate command failed\n", (void *)hdlp)); 413 414 return (ret); 415 } 416 417 DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_getstate: hdlp %p " 418 "current Connection state %x new Connection state %x\n", 419 (void *)hdlp, hdlp->cn_info.cn_state, new_state)); 420 421 if (new_state != hdlp->cn_info.cn_state) { 422 hdlp->cn_info.cn_state = new_state; 423 ddihp_update_last_change(hdlp); 424 } 425 426 return (ret); 427 } 428 429 /* 430 * Implementation function for unregistering the Hotplug Connection (CN) 431 */ 432 int 433 ddihp_cn_unregister(ddi_hp_cn_handle_t *hdlp) 434 { 435 dev_info_t *dip = hdlp->cn_dip; 436 437 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_unregister: hdlp %p\n", 438 (void *)hdlp)); 439 440 ASSERT(DEVI_BUSY_OWNED(dip)); 441 442 (void) ddihp_cn_getstate(hdlp); 443 444 if (hdlp->cn_info.cn_state > DDI_HP_CN_STATE_OFFLINE) { 445 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_unregister: dip %p, hdlp %p " 446 "state %x. Device busy, failed to unregister connection!\n", 447 (void *)dip, (void *)hdlp, hdlp->cn_info.cn_state)); 448 449 return (DDI_EBUSY); 450 } 451 452 /* unlink the handle */ 453 DDIHP_LIST_REMOVE(ddi_hp_cn_handle_t, (DEVI(dip)->devi_hp_hdlp), hdlp); 454 455 kmem_free(hdlp->cn_info.cn_name, strlen(hdlp->cn_info.cn_name) + 1); 456 kmem_free(hdlp, sizeof (ddi_hp_cn_handle_t)); 457 return (DDI_SUCCESS); 458 } 459 460 /* 461 * For a given Connection name and the dip node where the Connection is 462 * supposed to be, find the corresponding hotplug handle. 463 */ 464 ddi_hp_cn_handle_t * 465 ddihp_cn_name_to_handle(dev_info_t *dip, char *cn_name) 466 { 467 ddi_hp_cn_handle_t *hdlp; 468 469 ASSERT(DEVI_BUSY_OWNED(dip)); 470 471 DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_name_to_handle: " 472 "dip %p cn_name to find: %s", (void *)dip, cn_name)); 473 for (hdlp = DEVI(dip)->devi_hp_hdlp; hdlp; hdlp = hdlp->next) { 474 DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_name_to_handle: " 475 "current cn_name: %s", hdlp->cn_info.cn_name)); 476 477 if (strcmp(cn_name, hdlp->cn_info.cn_name) == 0) { 478 /* found */ 479 return (hdlp); 480 } 481 } 482 DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_name_to_handle: " 483 "failed to find cn_name")); 484 return (NULL); 485 } 486 487 /* 488 * Process the hotplug operations for Connector and also create Port 489 * upon user command. 490 */ 491 int 492 ddihp_connector_ops(ddi_hp_cn_handle_t *hdlp, ddi_hp_op_t op, 493 void *arg, void *result) 494 { 495 int rv = DDI_SUCCESS; 496 dev_info_t *dip = hdlp->cn_dip; 497 498 ASSERT(DEVI_BUSY_OWNED(dip)); 499 500 DDI_HP_IMPLDBG((CE_CONT, "ddihp_connector_ops: pdip=%p op=%x " 501 "hdlp=%p arg=%p\n", (void *)dip, op, (void *)hdlp, arg)); 502 503 if (op == DDI_HPOP_CN_CHANGE_STATE) { 504 ddi_hp_cn_state_t target_state = *(ddi_hp_cn_state_t *)arg; 505 506 rv = ddihp_cn_pre_change_state(hdlp, target_state); 507 if (rv != DDI_SUCCESS) { 508 /* the state is not changed */ 509 *((ddi_hp_cn_state_t *)result) = 510 hdlp->cn_info.cn_state; 511 return (rv); 512 } 513 } 514 ASSERT(NEXUS_HAS_HP_OP(dip)); 515 rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))( 516 dip, hdlp->cn_info.cn_name, op, arg, result); 517 518 if (rv != DDI_SUCCESS) { 519 DDI_HP_IMPLDBG((CE_CONT, "ddihp_connector_ops: " 520 "bus_hp_op failed: pdip=%p cn_name:%s op=%x " 521 "hdlp=%p arg=%p\n", (void *)dip, hdlp->cn_info.cn_name, 522 op, (void *)hdlp, arg)); 523 } 524 if (op == DDI_HPOP_CN_CHANGE_STATE) { 525 int rv_post; 526 527 DDI_HP_IMPLDBG((CE_CONT, "ddihp_connector_ops: " 528 "old_state=%x, new_state=%x, rv=%x\n", 529 hdlp->cn_info.cn_state, *(ddi_hp_cn_state_t *)result, rv)); 530 531 /* 532 * After state change op is successfully done or 533 * failed at some stages, continue to do some jobs. 534 */ 535 rv_post = ddihp_cn_post_change_state(hdlp, 536 *(ddi_hp_cn_state_t *)result); 537 538 if (rv_post != DDI_SUCCESS) 539 rv = rv_post; 540 } 541 542 return (rv); 543 } 544 545 /* 546 * Process the hotplug op for Port 547 */ 548 int 549 ddihp_port_ops(ddi_hp_cn_handle_t *hdlp, ddi_hp_op_t op, 550 void *arg, void *result) 551 { 552 int ret = DDI_SUCCESS; 553 554 ASSERT(DEVI_BUSY_OWNED(hdlp->cn_dip)); 555 556 DDI_HP_IMPLDBG((CE_CONT, "ddihp_port_ops: pdip=%p op=%x hdlp=%p " 557 "arg=%p\n", (void *)hdlp->cn_dip, op, (void *)hdlp, arg)); 558 559 switch (op) { 560 case DDI_HPOP_CN_GET_STATE: 561 { 562 int state; 563 564 state = hdlp->cn_info.cn_state; 565 566 if (hdlp->cn_info.cn_child == NULL) { 567 /* No child. Either present or empty. */ 568 if (state >= DDI_HP_CN_STATE_PORT_PRESENT) 569 state = DDI_HP_CN_STATE_PORT_PRESENT; 570 else 571 state = DDI_HP_CN_STATE_PORT_EMPTY; 572 573 } else { /* There is a child of this Port */ 574 575 /* Check DEVI(dip)->devi_node_state */ 576 switch (i_ddi_node_state(hdlp->cn_info.cn_child)) { 577 case DS_INVAL: 578 case DS_PROTO: 579 case DS_LINKED: 580 case DS_BOUND: 581 case DS_INITIALIZED: 582 case DS_PROBED: 583 state = DDI_HP_CN_STATE_OFFLINE; 584 break; 585 case DS_ATTACHED: 586 state = DDI_HP_CN_STATE_MAINTENANCE; 587 break; 588 case DS_READY: 589 state = DDI_HP_CN_STATE_ONLINE; 590 break; 591 default: 592 /* should never reach here */ 593 ASSERT("unknown devinfo state"); 594 } 595 /* 596 * Check DEVI(dip)->devi_state in case the node is 597 * downgraded or quiesced. 598 */ 599 if (state == DDI_HP_CN_STATE_ONLINE && 600 ddi_get_devstate(hdlp->cn_info.cn_child) != 601 DDI_DEVSTATE_UP) 602 state = DDI_HP_CN_STATE_MAINTENANCE; 603 } 604 605 *((ddi_hp_cn_state_t *)result) = state; 606 607 break; 608 } 609 case DDI_HPOP_CN_CHANGE_STATE: 610 { 611 ddi_hp_cn_state_t target_state = *(ddi_hp_cn_state_t *)arg; 612 ddi_hp_cn_state_t curr_state = hdlp->cn_info.cn_state; 613 614 ret = ddihp_port_change_state(hdlp, target_state); 615 if (curr_state != hdlp->cn_info.cn_state) { 616 ddihp_update_last_change(hdlp); 617 } 618 *((ddi_hp_cn_state_t *)result) = hdlp->cn_info.cn_state; 619 620 break; 621 } 622 case DDI_HPOP_CN_REMOVE_PORT: 623 { 624 (void) ddihp_cn_getstate(hdlp); 625 626 if (hdlp->cn_info.cn_state != DDI_HP_CN_STATE_PORT_EMPTY) { 627 /* Only empty PORT can be removed by commands */ 628 ret = DDI_EBUSY; 629 630 break; 631 } 632 633 ret = ddihp_cn_unregister(hdlp); 634 break; 635 } 636 default: 637 ret = DDI_ENOTSUP; 638 break; 639 } 640 641 return (ret); 642 } 643 644 /* 645 * Generate the system event with a possible hint 646 */ 647 /* ARGSUSED */ 648 void 649 ddihp_cn_gen_sysevent(ddi_hp_cn_handle_t *hdlp, 650 ddi_hp_cn_sysevent_t event_sub_class, int hint, int kmflag) 651 { 652 dev_info_t *dip = hdlp->cn_dip; 653 char *cn_path, *ap_id; 654 char *ev_subclass = NULL; 655 nvlist_t *ev_attr_list = NULL; 656 sysevent_id_t eid; 657 int ap_id_len, err; 658 659 cn_path = kmem_zalloc(MAXPATHLEN, kmflag); 660 if (cn_path == NULL) { 661 cmn_err(CE_WARN, 662 "%s%d: Failed to allocate memory for hotplug" 663 " connection: %s\n", 664 ddi_driver_name(dip), ddi_get_instance(dip), 665 hdlp->cn_info.cn_name); 666 667 return; 668 } 669 670 /* 671 * Minor device name will be bus path 672 * concatenated with connection name. 673 * One of consumers of the sysevent will pass it 674 * to cfgadm as AP ID. 675 */ 676 (void) strcpy(cn_path, "/devices"); 677 (void) ddi_pathname(dip, cn_path + strlen("/devices")); 678 679 ap_id_len = strlen(cn_path) + strlen(":") + 680 strlen(hdlp->cn_info.cn_name) + 1; 681 ap_id = kmem_zalloc(ap_id_len, kmflag); 682 if (ap_id == NULL) { 683 cmn_err(CE_WARN, 684 "%s%d: Failed to allocate memory for AP ID: %s:%s\n", 685 ddi_driver_name(dip), ddi_get_instance(dip), 686 cn_path, hdlp->cn_info.cn_name); 687 kmem_free(cn_path, MAXPATHLEN); 688 689 return; 690 } 691 692 (void) strcpy(ap_id, cn_path); 693 (void) strcat(ap_id, ":"); 694 (void) strcat(ap_id, hdlp->cn_info.cn_name); 695 kmem_free(cn_path, MAXPATHLEN); 696 697 err = nvlist_alloc(&ev_attr_list, NV_UNIQUE_NAME_TYPE, kmflag); 698 699 if (err != 0) { 700 cmn_err(CE_WARN, 701 "%s%d: Failed to allocate memory for event subclass %d\n", 702 ddi_driver_name(dip), ddi_get_instance(dip), 703 event_sub_class); 704 kmem_free(ap_id, ap_id_len); 705 706 return; 707 } 708 709 switch (event_sub_class) { 710 case DDI_HP_CN_STATE_CHANGE: 711 ev_subclass = ESC_DR_AP_STATE_CHANGE; 712 713 switch (hint) { 714 case SE_NO_HINT: /* fall through */ 715 case SE_HINT_INSERT: /* fall through */ 716 case SE_HINT_REMOVE: 717 err = nvlist_add_string(ev_attr_list, DR_HINT, 718 SE_HINT2STR(hint)); 719 720 if (err != 0) { 721 cmn_err(CE_WARN, "%s%d: Failed to add attr [%s]" 722 " for %s event\n", ddi_driver_name(dip), 723 ddi_get_instance(dip), DR_HINT, 724 ESC_DR_AP_STATE_CHANGE); 725 726 goto done; 727 } 728 break; 729 730 default: 731 cmn_err(CE_WARN, "%s%d: Unknown hint on sysevent\n", 732 ddi_driver_name(dip), ddi_get_instance(dip)); 733 734 goto done; 735 } 736 737 break; 738 739 /* event sub class: DDI_HP_CN_REQ */ 740 case DDI_HP_CN_REQ: 741 ev_subclass = ESC_DR_REQ; 742 743 switch (hint) { 744 case SE_INVESTIGATE_RES: /* fall through */ 745 case SE_INCOMING_RES: /* fall through */ 746 case SE_OUTGOING_RES: /* fall through */ 747 err = nvlist_add_string(ev_attr_list, DR_REQ_TYPE, 748 SE_REQ2STR(hint)); 749 750 if (err != 0) { 751 cmn_err(CE_WARN, 752 "%s%d: Failed to add attr [%s] for %s \n" 753 "event", ddi_driver_name(dip), 754 ddi_get_instance(dip), 755 DR_REQ_TYPE, ESC_DR_REQ); 756 757 goto done; 758 } 759 break; 760 761 default: 762 cmn_err(CE_WARN, "%s%d: Unknown hint on sysevent\n", 763 ddi_driver_name(dip), ddi_get_instance(dip)); 764 765 goto done; 766 } 767 768 break; 769 770 default: 771 cmn_err(CE_WARN, "%s%d: Unknown Event subclass\n", 772 ddi_driver_name(dip), ddi_get_instance(dip)); 773 774 goto done; 775 } 776 777 /* 778 * Add Hotplug Connection (CN) as attribute (common attribute) 779 */ 780 err = nvlist_add_string(ev_attr_list, DR_AP_ID, ap_id); 781 if (err != 0) { 782 cmn_err(CE_WARN, "%s%d: Failed to add attr [%s] for %s event\n", 783 ddi_driver_name(dip), ddi_get_instance(dip), 784 DR_AP_ID, EC_DR); 785 786 goto done; 787 } 788 789 /* 790 * Log this event with sysevent framework. 791 */ 792 err = ddi_log_sysevent(dip, DDI_VENDOR_SUNW, EC_DR, 793 ev_subclass, ev_attr_list, &eid, 794 ((kmflag == KM_SLEEP) ? DDI_SLEEP : DDI_NOSLEEP)); 795 796 if (err != 0) { 797 cmn_err(CE_WARN, "%s%d: Failed to log %s event\n", 798 ddi_driver_name(dip), ddi_get_instance(dip), EC_DR); 799 } 800 801 done: 802 nvlist_free(ev_attr_list); 803 kmem_free(ap_id, ap_id_len); 804 } 805 806 /* 807 * Local functions (called within this file) 808 */ 809 810 /* 811 * Connector operations 812 */ 813 814 /* 815 * Prepare to change state for a Connector: offline, unprobe, etc. 816 */ 817 static int 818 ddihp_cn_pre_change_state(ddi_hp_cn_handle_t *hdlp, 819 ddi_hp_cn_state_t target_state) 820 { 821 ddi_hp_cn_state_t curr_state = hdlp->cn_info.cn_state; 822 dev_info_t *dip = hdlp->cn_dip; 823 int rv = DDI_SUCCESS; 824 825 if (curr_state > target_state && 826 curr_state == DDI_HP_CN_STATE_ENABLED) { 827 /* 828 * If the Connection goes to a lower state from ENABLED, 829 * then offline all children under it. 830 */ 831 rv = ddihp_cn_change_children_state(hdlp, B_FALSE); 832 if (rv != DDI_SUCCESS) { 833 cmn_err(CE_WARN, 834 "(%s%d): " 835 "failed to unconfigure the device in the" 836 " Connection %s\n", ddi_driver_name(dip), 837 ddi_get_instance(dip), 838 hdlp->cn_info.cn_name); 839 840 return (rv); 841 } 842 ASSERT(NEXUS_HAS_HP_OP(dip)); 843 /* 844 * Remove all the children and their ports 845 * after they are offlined. 846 */ 847 rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))( 848 dip, hdlp->cn_info.cn_name, DDI_HPOP_CN_UNPROBE, 849 NULL, NULL); 850 if (rv != DDI_SUCCESS) { 851 cmn_err(CE_WARN, 852 "(%s%d): failed" 853 " to unprobe the device in the Connector" 854 " %s\n", ddi_driver_name(dip), 855 ddi_get_instance(dip), 856 hdlp->cn_info.cn_name); 857 858 return (rv); 859 } 860 861 DDI_HP_NEXDBG((CE_CONT, 862 "ddihp_connector_ops (%s%d): device" 863 " is unconfigured and unprobed in Connector %s\n", 864 ddi_driver_name(dip), ddi_get_instance(dip), 865 hdlp->cn_info.cn_name)); 866 } 867 868 return (rv); 869 } 870 871 /* 872 * Jobs after change state of a Connector: update state, last change time, 873 * probe, online, sysevent, etc. 874 */ 875 static int 876 ddihp_cn_post_change_state(ddi_hp_cn_handle_t *hdlp, 877 ddi_hp_cn_state_t new_state) 878 { 879 int rv = DDI_SUCCESS; 880 ddi_hp_cn_state_t curr_state = hdlp->cn_info.cn_state; 881 882 /* Update the state in handle */ 883 if (new_state != curr_state) { 884 hdlp->cn_info.cn_state = new_state; 885 ddihp_update_last_change(hdlp); 886 } 887 888 if (curr_state < new_state && 889 new_state == DDI_HP_CN_STATE_ENABLED) { 890 /* 891 * Probe and online devices if state is 892 * upgraded to ENABLED. 893 */ 894 rv = ddihp_cn_handle_state_change(hdlp); 895 } 896 if (curr_state != hdlp->cn_info.cn_state) { 897 /* 898 * For Connector, generate a sysevent on 899 * state change. 900 */ 901 ddihp_cn_gen_sysevent(hdlp, DDI_HP_CN_STATE_CHANGE, 902 SE_NO_HINT, KM_SLEEP); 903 } 904 905 return (rv); 906 } 907 908 /* 909 * Handle Connector state change. 910 * 911 * This function is called after connector is upgraded to ENABLED sate. 912 * It probes the device plugged in the connector to setup devinfo nodes 913 * and then online the nodes. 914 */ 915 static int 916 ddihp_cn_handle_state_change(ddi_hp_cn_handle_t *hdlp) 917 { 918 dev_info_t *dip = hdlp->cn_dip; 919 int rv = DDI_SUCCESS; 920 921 ASSERT(DEVI_BUSY_OWNED(dip)); 922 ASSERT(NEXUS_HAS_HP_OP(dip)); 923 /* 924 * If the Connection went to state ENABLED from a lower state, 925 * probe it. 926 */ 927 rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))( 928 dip, hdlp->cn_info.cn_name, DDI_HPOP_CN_PROBE, NULL, NULL); 929 930 if (rv != DDI_SUCCESS) { 931 ddi_hp_cn_state_t target_state = DDI_HP_CN_STATE_POWERED; 932 ddi_hp_cn_state_t result_state = 0; 933 934 /* 935 * Probe failed. Disable the connector so that it can 936 * be enabled again by a later try from userland. 937 */ 938 (void) (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))( 939 dip, hdlp->cn_info.cn_name, DDI_HPOP_CN_CHANGE_STATE, 940 (void *)&target_state, (void *)&result_state); 941 942 if (result_state && result_state != hdlp->cn_info.cn_state) { 943 hdlp->cn_info.cn_state = result_state; 944 ddihp_update_last_change(hdlp); 945 } 946 947 cmn_err(CE_WARN, 948 "(%s%d): failed to probe the Connection %s\n", 949 ddi_driver_name(dip), ddi_get_instance(dip), 950 hdlp->cn_info.cn_name); 951 952 return (rv); 953 } 954 /* 955 * Try to online all the children of CN. 956 */ 957 (void) ddihp_cn_change_children_state(hdlp, B_TRUE); 958 959 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_event_handler (%s%d): " 960 "device is configured in the Connection %s\n", 961 ddi_driver_name(dip), ddi_get_instance(dip), 962 hdlp->cn_info.cn_name)); 963 return (rv); 964 } 965 966 /* 967 * Online/Offline all the children under the Hotplug Connection (CN) 968 * 969 * Do online operation when the online parameter is true; otherwise do offline. 970 */ 971 static int 972 ddihp_cn_change_children_state(ddi_hp_cn_handle_t *hdlp, boolean_t online) 973 { 974 dev_info_t *dip = hdlp->cn_dip; 975 dev_info_t *cdip; 976 ddi_hp_cn_handle_t *h; 977 int rv = DDI_SUCCESS; 978 979 DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_change_children_state:" 980 " dip %p hdlp %p, online %x\n", 981 (void *)dip, (void *)hdlp, online)); 982 983 ASSERT(DEVI_BUSY_OWNED(dip)); 984 985 /* 986 * Return invalid if Connection state is < DDI_HP_CN_STATE_ENABLED 987 * when try to online children. 988 */ 989 if (online && hdlp->cn_info.cn_state < DDI_HP_CN_STATE_ENABLED) { 990 DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_change_children_state: " 991 "Connector %p is not in probed state\n", (void *)hdlp)); 992 993 return (DDI_EINVAL); 994 } 995 996 /* Now, online/offline all the devices depending on the Connector */ 997 998 if (!online) { 999 /* 1000 * For offline operation we need to firstly clean up devfs 1001 * so as not to prevent driver detach. 1002 */ 1003 (void) devfs_clean(dip, NULL, DV_CLEAN_FORCE); 1004 } 1005 for (h = DEVI(dip)->devi_hp_hdlp; h; h = h->next) { 1006 if (h->cn_info.cn_type != DDI_HP_CN_TYPE_VIRTUAL_PORT) 1007 continue; 1008 1009 if (h->cn_info.cn_num_dpd_on != 1010 hdlp->cn_info.cn_num) 1011 continue; 1012 1013 cdip = h->cn_info.cn_child; 1014 ASSERT(cdip); 1015 if (online) { 1016 /* online children */ 1017 if (!ddihp_check_status_prop(dip)) 1018 continue; 1019 1020 if (ndi_devi_online(cdip, 1021 NDI_ONLINE_ATTACH | NDI_CONFIG) != NDI_SUCCESS) { 1022 cmn_err(CE_WARN, 1023 "(%s%d):" 1024 " failed to attach driver for a device" 1025 " (%s%d) under the Connection %s\n", 1026 ddi_driver_name(dip), ddi_get_instance(dip), 1027 ddi_driver_name(cdip), 1028 ddi_get_instance(cdip), 1029 hdlp->cn_info.cn_name); 1030 /* 1031 * One of the devices failed to online, but we 1032 * want to continue to online the rest siblings 1033 * after mark the failure here. 1034 */ 1035 rv = DDI_FAILURE; 1036 1037 continue; 1038 } 1039 } else { 1040 /* offline children */ 1041 if (ndi_devi_offline(cdip, NDI_UNCONFIG) != 1042 NDI_SUCCESS) { 1043 cmn_err(CE_WARN, 1044 "(%s%d):" 1045 " failed to detach driver for the device" 1046 " (%s%d) in the Connection %s\n", 1047 ddi_driver_name(dip), ddi_get_instance(dip), 1048 ddi_driver_name(cdip), 1049 ddi_get_instance(cdip), 1050 hdlp->cn_info.cn_name); 1051 1052 return (DDI_EBUSY); 1053 } 1054 } 1055 } 1056 1057 return (rv); 1058 } 1059 1060 /* 1061 * Port operations 1062 */ 1063 1064 /* 1065 * Change Port state to target_state. 1066 */ 1067 static int 1068 ddihp_port_change_state(ddi_hp_cn_handle_t *hdlp, 1069 ddi_hp_cn_state_t target_state) 1070 { 1071 ddi_hp_cn_state_t curr_state = hdlp->cn_info.cn_state; 1072 1073 if (target_state < DDI_HP_CN_STATE_PORT_EMPTY || 1074 target_state > DDI_HP_CN_STATE_ONLINE) { 1075 1076 return (DDI_EINVAL); 1077 } 1078 1079 if (curr_state < target_state) 1080 return (ddihp_port_upgrade_state(hdlp, target_state)); 1081 else if (curr_state > target_state) 1082 return (ddihp_port_downgrade_state(hdlp, target_state)); 1083 else 1084 return (DDI_SUCCESS); 1085 } 1086 1087 /* 1088 * Upgrade port state to target_state. 1089 */ 1090 static int 1091 ddihp_port_upgrade_state(ddi_hp_cn_handle_t *hdlp, 1092 ddi_hp_cn_state_t target_state) 1093 { 1094 ddi_hp_cn_state_t curr_state, new_state, result_state; 1095 dev_info_t *cdip; 1096 int rv = DDI_SUCCESS; 1097 1098 curr_state = hdlp->cn_info.cn_state; 1099 while (curr_state < target_state) { 1100 switch (curr_state) { 1101 case DDI_HP_CN_STATE_PORT_EMPTY: 1102 /* Check the existence of the corresponding hardware */ 1103 new_state = DDI_HP_CN_STATE_PORT_PRESENT; 1104 rv = ddihp_connector_ops(hdlp, 1105 DDI_HPOP_CN_CHANGE_STATE, 1106 (void *)&new_state, (void *)&result_state); 1107 if (rv == DDI_SUCCESS) { 1108 hdlp->cn_info.cn_state = 1109 result_state; 1110 } 1111 break; 1112 case DDI_HP_CN_STATE_PORT_PRESENT: 1113 /* Read-only probe the corresponding hardware. */ 1114 new_state = DDI_HP_CN_STATE_OFFLINE; 1115 rv = ddihp_connector_ops(hdlp, 1116 DDI_HPOP_CN_CHANGE_STATE, 1117 (void *)&new_state, &cdip); 1118 if (rv == DDI_SUCCESS) { 1119 hdlp->cn_info.cn_state = 1120 DDI_HP_CN_STATE_OFFLINE; 1121 1122 ASSERT(hdlp->cn_info.cn_child == NULL); 1123 hdlp->cn_info.cn_child = cdip; 1124 } 1125 break; 1126 case DDI_HP_CN_STATE_OFFLINE: 1127 /* fall through */ 1128 case DDI_HP_CN_STATE_MAINTENANCE: 1129 1130 cdip = hdlp->cn_info.cn_child; 1131 1132 rv = ndi_devi_online(cdip, 1133 NDI_ONLINE_ATTACH | NDI_CONFIG); 1134 if (rv == NDI_SUCCESS) { 1135 hdlp->cn_info.cn_state = 1136 DDI_HP_CN_STATE_ONLINE; 1137 rv = DDI_SUCCESS; 1138 } else { 1139 rv = DDI_FAILURE; 1140 DDI_HP_IMPLDBG((CE_CONT, 1141 "ddihp_port_upgrade_state: " 1142 "failed to online device %p at port: %s\n", 1143 (void *)cdip, hdlp->cn_info.cn_name)); 1144 } 1145 break; 1146 case DDI_HP_CN_STATE_ONLINE: 1147 1148 break; 1149 default: 1150 /* should never reach here */ 1151 ASSERT("unknown devinfo state"); 1152 } 1153 curr_state = hdlp->cn_info.cn_state; 1154 if (rv != DDI_SUCCESS) { 1155 DDI_HP_IMPLDBG((CE_CONT, "ddihp_port_upgrade_state: " 1156 "failed curr_state=%x, target_state=%x \n", 1157 curr_state, target_state)); 1158 return (rv); 1159 } 1160 } 1161 1162 return (rv); 1163 } 1164 1165 /* 1166 * Downgrade state to target_state 1167 */ 1168 static int 1169 ddihp_port_downgrade_state(ddi_hp_cn_handle_t *hdlp, 1170 ddi_hp_cn_state_t target_state) 1171 { 1172 ddi_hp_cn_state_t curr_state, new_state, result_state; 1173 dev_info_t *dip = hdlp->cn_dip; 1174 dev_info_t *cdip; 1175 int rv = DDI_SUCCESS; 1176 1177 curr_state = hdlp->cn_info.cn_state; 1178 while (curr_state > target_state) { 1179 1180 switch (curr_state) { 1181 case DDI_HP_CN_STATE_PORT_EMPTY: 1182 1183 break; 1184 case DDI_HP_CN_STATE_PORT_PRESENT: 1185 /* Check the existence of the corresponding hardware */ 1186 new_state = DDI_HP_CN_STATE_PORT_EMPTY; 1187 rv = ddihp_connector_ops(hdlp, 1188 DDI_HPOP_CN_CHANGE_STATE, 1189 (void *)&new_state, (void *)&result_state); 1190 if (rv == DDI_SUCCESS) 1191 hdlp->cn_info.cn_state = 1192 result_state; 1193 1194 break; 1195 case DDI_HP_CN_STATE_OFFLINE: 1196 /* 1197 * Read-only unprobe the corresponding hardware: 1198 * 1. release the assigned resource; 1199 * 2. remove the node pointed by the port's cn_child 1200 */ 1201 new_state = DDI_HP_CN_STATE_PORT_PRESENT; 1202 rv = ddihp_connector_ops(hdlp, 1203 DDI_HPOP_CN_CHANGE_STATE, 1204 (void *)&new_state, (void *)&result_state); 1205 if (rv == DDI_SUCCESS) 1206 hdlp->cn_info.cn_state = 1207 DDI_HP_CN_STATE_PORT_PRESENT; 1208 break; 1209 case DDI_HP_CN_STATE_MAINTENANCE: 1210 /* fall through. */ 1211 case DDI_HP_CN_STATE_ONLINE: 1212 cdip = hdlp->cn_info.cn_child; 1213 1214 (void) devfs_clean(dip, NULL, DV_CLEAN_FORCE); 1215 rv = ndi_devi_offline(cdip, NDI_UNCONFIG); 1216 if (rv == NDI_SUCCESS) { 1217 hdlp->cn_info.cn_state = 1218 DDI_HP_CN_STATE_OFFLINE; 1219 rv = DDI_SUCCESS; 1220 } else { 1221 rv = DDI_EBUSY; 1222 DDI_HP_IMPLDBG((CE_CONT, 1223 "ddihp_port_downgrade_state: failed " 1224 "to offline node, rv=%x, cdip=%p \n", 1225 rv, (void *)cdip)); 1226 } 1227 1228 break; 1229 default: 1230 /* should never reach here */ 1231 ASSERT("unknown devinfo state"); 1232 } 1233 curr_state = hdlp->cn_info.cn_state; 1234 if (rv != DDI_SUCCESS) { 1235 DDI_HP_IMPLDBG((CE_CONT, 1236 "ddihp_port_downgrade_state: failed " 1237 "curr_state=%x, target_state=%x \n", 1238 curr_state, target_state)); 1239 return (rv); 1240 } 1241 } 1242 1243 return (rv); 1244 } 1245 1246 /* 1247 * Misc routines 1248 */ 1249 1250 /* Update the last state change time */ 1251 static void 1252 ddihp_update_last_change(ddi_hp_cn_handle_t *hdlp) 1253 { 1254 time_t time; 1255 1256 if (drv_getparm(TIME, (void *)&time) != DDI_SUCCESS) 1257 hdlp->cn_info.cn_last_change = (time_t)-1; 1258 else 1259 hdlp->cn_info.cn_last_change = (time32_t)time; 1260 } 1261 1262 /* 1263 * Check the device for a 'status' property. A conforming device 1264 * should have a status of "okay", "disabled", "fail", or "fail-xxx". 1265 * 1266 * Return FALSE for a conforming device that is disabled or faulted. 1267 * Return TRUE in every other case. 1268 * 1269 * 'status' property is NOT a bus specific property. It is defined in page 184, 1270 * IEEE 1275 spec. The full name of the spec is "IEEE Standard for 1271 * Boot (Initialization Configuration) Firmware: Core Requirements and 1272 * Practices". 1273 */ 1274 static boolean_t 1275 ddihp_check_status_prop(dev_info_t *dip) 1276 { 1277 char *status_prop; 1278 boolean_t rv = B_TRUE; 1279 1280 /* try to get the 'status' property */ 1281 if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, 1282 "status", &status_prop) == DDI_PROP_SUCCESS) { 1283 /* 1284 * test if the status is "disabled", "fail", or 1285 * "fail-xxx". 1286 */ 1287 if (strcmp(status_prop, "disabled") == 0) { 1288 rv = B_FALSE; 1289 DDI_HP_IMPLDBG((CE_CONT, "ddihp_check_status_prop " 1290 "(%s%d): device is in disabled state", 1291 ddi_driver_name(dip), ddi_get_instance(dip))); 1292 } else if (strncmp(status_prop, "fail", 4) == 0) { 1293 rv = B_FALSE; 1294 cmn_err(CE_WARN, 1295 "hotplug (%s%d): device is in fault state (%s)\n", 1296 ddi_driver_name(dip), ddi_get_instance(dip), 1297 status_prop); 1298 } 1299 1300 ddi_prop_free(status_prop); 1301 } 1302 1303 return (rv); 1304 } 1305