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 26 /* 27 * Sun DDI hotplug implementation specific functions 28 */ 29 30 #include <sys/sysmacros.h> 31 #include <sys/types.h> 32 #include <sys/file.h> 33 #include <sys/param.h> 34 #include <sys/systm.h> 35 #include <sys/kmem.h> 36 #include <sys/cmn_err.h> 37 #include <sys/debug.h> 38 #include <sys/avintr.h> 39 #include <sys/autoconf.h> 40 #include <sys/ddi.h> 41 #include <sys/sunndi.h> 42 #include <sys/ndi_impldefs.h> 43 #include <sys/sysevent.h> 44 #include <sys/sysevent/eventdefs.h> 45 #include <sys/sysevent/dr.h> 46 #include <sys/fs/dv_node.h> 47 48 /* 49 * Local function prototypes 50 */ 51 /* Connector operations */ 52 static int ddihp_cn_pre_change_state(ddi_hp_cn_handle_t *hdlp, 53 ddi_hp_cn_state_t target_state); 54 static int ddihp_cn_post_change_state(ddi_hp_cn_handle_t *hdlp, 55 ddi_hp_cn_state_t new_state); 56 static int ddihp_cn_handle_state_change(ddi_hp_cn_handle_t *hdlp); 57 static int ddihp_cn_change_children_state(ddi_hp_cn_handle_t *hdlp, 58 boolean_t online); 59 static int ddihp_change_node_state(dev_info_t *dip, void *arg); 60 /* Port operations */ 61 static int ddihp_port_change_state(ddi_hp_cn_handle_t *hdlp, 62 ddi_hp_cn_state_t target_state); 63 static int ddihp_port_upgrade_state(ddi_hp_cn_handle_t *hdlp, 64 ddi_hp_cn_state_t target_state); 65 static int ddihp_port_downgrade_state(ddi_hp_cn_handle_t *hdlp, 66 ddi_hp_cn_state_t target_state); 67 /* Misc routines */ 68 static void ddihp_update_last_change(ddi_hp_cn_handle_t *hdlp); 69 static boolean_t ddihp_check_status_prop(dev_info_t *dip); 70 71 /* 72 * Global functions (called within hotplug framework) 73 */ 74 75 /* 76 * Implement modctl() commands for hotplug. 77 * Called by modctl_hp() in modctl.c 78 */ 79 int 80 ddihp_modctl(int hp_op, char *path, char *cn_name, uintptr_t arg, 81 uintptr_t rval) 82 { 83 dev_info_t *dip; 84 ddi_hp_cn_handle_t *hdlp; 85 ddi_hp_op_t op = (ddi_hp_op_t)hp_op; 86 int count, rv, error; 87 88 /* Get the dip of nexus node */ 89 dip = e_ddi_hold_devi_by_path(path, 0); 90 91 if (dip == NULL) 92 return (ENXIO); 93 94 DDI_HP_IMPLDBG((CE_CONT, "ddihp_modctl: dip %p op %x path %s " 95 "cn_name %s arg %p rval %p\n", (void *)dip, hp_op, path, cn_name, 96 (void *)arg, (void *)rval)); 97 98 if (!NEXUS_HAS_HP_OP(dip)) { 99 ddi_release_devi(dip); 100 return (ENOTSUP); 101 } 102 103 /* Lock before access */ 104 ndi_devi_enter(dip, &count); 105 106 hdlp = ddihp_cn_name_to_handle(dip, cn_name); 107 108 if (hp_op == DDI_HPOP_CN_CREATE_PORT) { 109 if (hdlp != NULL) { 110 /* this port already exists. */ 111 error = EEXIST; 112 113 goto done; 114 } 115 rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))( 116 dip, cn_name, op, NULL, NULL); 117 } else { 118 if (hdlp == NULL) { 119 /* Invalid Connection name */ 120 error = ENXIO; 121 122 goto done; 123 } 124 if (hp_op == DDI_HPOP_CN_CHANGE_STATE) { 125 ddi_hp_cn_state_t target_state = (ddi_hp_cn_state_t)arg; 126 ddi_hp_cn_state_t result_state = 0; 127 128 DDIHP_CN_OPS(hdlp, op, (void *)&target_state, 129 (void *)&result_state, rv); 130 131 DDI_HP_IMPLDBG((CE_CONT, "ddihp_modctl: target_state=" 132 "%x, result_state=%x, rv=%x \n", 133 target_state, result_state, rv)); 134 } else { 135 DDIHP_CN_OPS(hdlp, op, (void *)arg, (void *)rval, rv); 136 } 137 } 138 switch (rv) { 139 case DDI_SUCCESS: 140 error = 0; 141 break; 142 case DDI_EINVAL: 143 error = EINVAL; 144 break; 145 case DDI_EBUSY: 146 error = EBUSY; 147 break; 148 case DDI_ENOTSUP: 149 error = ENOTSUP; 150 break; 151 case DDI_ENOMEM: 152 error = ENOMEM; 153 break; 154 default: 155 error = EIO; 156 } 157 158 done: 159 ndi_devi_exit(dip, count); 160 161 ddi_release_devi(dip); 162 163 return (error); 164 } 165 166 /* 167 * Return the state of Hotplug Connection (CN) 168 */ 169 int 170 ddihp_cn_getstate(ddi_hp_cn_handle_t *hdlp) 171 { 172 ddi_hp_cn_state_t new_state; 173 int ret; 174 175 DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_getstate: pdip %p hdlp %p\n", 176 (void *)hdlp->cn_dip, (void *)hdlp)); 177 178 ASSERT(DEVI_BUSY_OWNED(hdlp->cn_dip)); 179 180 DDIHP_CN_OPS(hdlp, DDI_HPOP_CN_GET_STATE, 181 NULL, (void *)&new_state, ret); 182 if (ret != DDI_SUCCESS) { 183 DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_getstate: " 184 "CN %p getstate command failed\n", (void *)hdlp)); 185 186 return (ret); 187 } 188 189 DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_getstate: hdlp %p " 190 "current Connection state %x new Connection state %x\n", 191 (void *)hdlp, hdlp->cn_info.cn_state, new_state)); 192 193 if (new_state != hdlp->cn_info.cn_state) { 194 hdlp->cn_info.cn_state = new_state; 195 ddihp_update_last_change(hdlp); 196 } 197 198 return (ret); 199 } 200 201 /* 202 * Implementation function for unregistering the Hotplug Connection (CN) 203 */ 204 int 205 ddihp_cn_unregister(ddi_hp_cn_handle_t *hdlp) 206 { 207 dev_info_t *dip = hdlp->cn_dip; 208 209 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_unregister: hdlp %p\n", 210 (void *)hdlp)); 211 212 ASSERT(DEVI_BUSY_OWNED(dip)); 213 214 (void) ddihp_cn_getstate(hdlp); 215 216 if (hdlp->cn_info.cn_state > DDI_HP_CN_STATE_OFFLINE) { 217 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_unregister: dip %p, hdlp %p " 218 "state %x. Device busy, failed to unregister connection!\n", 219 (void *)dip, (void *)hdlp, hdlp->cn_info.cn_state)); 220 221 return (DDI_EBUSY); 222 } 223 224 /* unlink the handle */ 225 DDIHP_LIST_REMOVE(ddi_hp_cn_handle_t, (DEVI(dip)->devi_hp_hdlp), hdlp); 226 227 kmem_free(hdlp->cn_info.cn_name, strlen(hdlp->cn_info.cn_name) + 1); 228 kmem_free(hdlp, sizeof (ddi_hp_cn_handle_t)); 229 return (DDI_SUCCESS); 230 } 231 232 /* 233 * For a given Connection name and the dip node where the Connection is 234 * supposed to be, find the corresponding hotplug handle. 235 */ 236 ddi_hp_cn_handle_t * 237 ddihp_cn_name_to_handle(dev_info_t *dip, char *cn_name) 238 { 239 ddi_hp_cn_handle_t *hdlp; 240 241 ASSERT(DEVI_BUSY_OWNED(dip)); 242 243 DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_name_to_handle: " 244 "dip %p cn_name to find: %s", (void *)dip, cn_name)); 245 for (hdlp = DEVI(dip)->devi_hp_hdlp; hdlp; hdlp = hdlp->next) { 246 DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_name_to_handle: " 247 "current cn_name: %s", hdlp->cn_info.cn_name)); 248 249 if (strcmp(cn_name, hdlp->cn_info.cn_name) == 0) { 250 /* found */ 251 return (hdlp); 252 } 253 } 254 DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_name_to_handle: " 255 "failed to find cn_name")); 256 return (NULL); 257 } 258 259 /* 260 * Process the hotplug operations for Connector and also create Port 261 * upon user command. 262 */ 263 int 264 ddihp_connector_ops(ddi_hp_cn_handle_t *hdlp, ddi_hp_op_t op, 265 void *arg, void *result) 266 { 267 int rv = DDI_SUCCESS; 268 dev_info_t *dip = hdlp->cn_dip; 269 270 ASSERT(DEVI_BUSY_OWNED(dip)); 271 272 DDI_HP_IMPLDBG((CE_CONT, "ddihp_connector_ops: pdip=%p op=%x " 273 "hdlp=%p arg=%p\n", (void *)dip, op, (void *)hdlp, arg)); 274 275 if (op == DDI_HPOP_CN_CHANGE_STATE) { 276 ddi_hp_cn_state_t target_state = *(ddi_hp_cn_state_t *)arg; 277 278 rv = ddihp_cn_pre_change_state(hdlp, target_state); 279 if (rv != DDI_SUCCESS) { 280 /* the state is not changed */ 281 *((ddi_hp_cn_state_t *)result) = 282 hdlp->cn_info.cn_state; 283 return (rv); 284 } 285 } 286 ASSERT(NEXUS_HAS_HP_OP(dip)); 287 rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))( 288 dip, hdlp->cn_info.cn_name, op, arg, result); 289 290 if (rv != DDI_SUCCESS) { 291 DDI_HP_IMPLDBG((CE_CONT, "ddihp_connector_ops: " 292 "bus_hp_op failed: pdip=%p cn_name:%s op=%x " 293 "hdlp=%p arg=%p\n", (void *)dip, hdlp->cn_info.cn_name, 294 op, (void *)hdlp, arg)); 295 } 296 if (op == DDI_HPOP_CN_CHANGE_STATE) { 297 int rv_post; 298 299 DDI_HP_IMPLDBG((CE_CONT, "ddihp_connector_ops: " 300 "old_state=%x, new_state=%x, rv=%x\n", 301 hdlp->cn_info.cn_state, *(ddi_hp_cn_state_t *)result, rv)); 302 303 /* 304 * After state change op is successfully done or 305 * failed at some stages, continue to do some jobs. 306 */ 307 rv_post = ddihp_cn_post_change_state(hdlp, 308 *(ddi_hp_cn_state_t *)result); 309 310 if (rv_post != DDI_SUCCESS) 311 rv = rv_post; 312 } 313 314 return (rv); 315 } 316 317 /* 318 * Process the hotplug op for Port 319 */ 320 int 321 ddihp_port_ops(ddi_hp_cn_handle_t *hdlp, ddi_hp_op_t op, 322 void *arg, void *result) 323 { 324 int ret = DDI_SUCCESS; 325 326 ASSERT(DEVI_BUSY_OWNED(hdlp->cn_dip)); 327 328 DDI_HP_IMPLDBG((CE_CONT, "ddihp_port_ops: pdip=%p op=%x hdlp=%p " 329 "arg=%p\n", (void *)hdlp->cn_dip, op, (void *)hdlp, arg)); 330 331 switch (op) { 332 case DDI_HPOP_CN_GET_STATE: 333 { 334 int state; 335 336 state = hdlp->cn_info.cn_state; 337 338 if (hdlp->cn_info.cn_child == NULL) { 339 /* No child. Either present or empty. */ 340 if (state >= DDI_HP_CN_STATE_PORT_PRESENT) 341 state = DDI_HP_CN_STATE_PORT_PRESENT; 342 else 343 state = DDI_HP_CN_STATE_PORT_EMPTY; 344 345 } else { /* There is a child of this Port */ 346 347 /* Check DEVI(dip)->devi_node_state */ 348 switch (i_ddi_node_state(hdlp->cn_info.cn_child)) { 349 case DS_INVAL: 350 case DS_PROTO: 351 case DS_LINKED: 352 case DS_BOUND: 353 case DS_INITIALIZED: 354 case DS_PROBED: 355 state = DDI_HP_CN_STATE_OFFLINE; 356 break; 357 case DS_ATTACHED: 358 state = DDI_HP_CN_STATE_MAINTENANCE; 359 break; 360 case DS_READY: 361 state = DDI_HP_CN_STATE_ONLINE; 362 break; 363 default: 364 /* should never reach here */ 365 ASSERT("unknown devinfo state"); 366 } 367 /* 368 * Check DEVI(dip)->devi_state in case the node is 369 * downgraded or quiesced. 370 */ 371 if (state == DDI_HP_CN_STATE_ONLINE && 372 ddi_get_devstate(hdlp->cn_info.cn_child) != 373 DDI_DEVSTATE_UP) 374 state = DDI_HP_CN_STATE_MAINTENANCE; 375 } 376 377 *((ddi_hp_cn_state_t *)result) = state; 378 379 break; 380 } 381 case DDI_HPOP_CN_CHANGE_STATE: 382 { 383 ddi_hp_cn_state_t target_state = *(ddi_hp_cn_state_t *)arg; 384 ddi_hp_cn_state_t curr_state = hdlp->cn_info.cn_state; 385 386 ret = ddihp_port_change_state(hdlp, target_state); 387 if (curr_state != hdlp->cn_info.cn_state) { 388 ddihp_update_last_change(hdlp); 389 } 390 *((ddi_hp_cn_state_t *)result) = hdlp->cn_info.cn_state; 391 392 break; 393 } 394 case DDI_HPOP_CN_REMOVE_PORT: 395 { 396 (void) ddihp_cn_getstate(hdlp); 397 398 if (hdlp->cn_info.cn_state != DDI_HP_CN_STATE_PORT_EMPTY) { 399 /* Only empty PORT can be removed by commands */ 400 ret = DDI_EBUSY; 401 402 break; 403 } 404 405 ret = ddihp_cn_unregister(hdlp); 406 break; 407 } 408 default: 409 ret = DDI_ENOTSUP; 410 break; 411 } 412 413 return (ret); 414 } 415 416 /* 417 * Generate the system event with a possible hint 418 */ 419 /* ARGSUSED */ 420 void 421 ddihp_cn_gen_sysevent(ddi_hp_cn_handle_t *hdlp, 422 ddi_hp_cn_sysevent_t event_sub_class, int hint, int kmflag) 423 { 424 dev_info_t *dip = hdlp->cn_dip; 425 char *cn_path, *ap_id; 426 char *ev_subclass = NULL; 427 nvlist_t *ev_attr_list = NULL; 428 sysevent_id_t eid; 429 int ap_id_len, err; 430 431 cn_path = kmem_zalloc(MAXPATHLEN, kmflag); 432 if (cn_path == NULL) { 433 cmn_err(CE_WARN, 434 "%s%d: Failed to allocate memory for hotplug" 435 " connection: %s\n", 436 ddi_driver_name(dip), ddi_get_instance(dip), 437 hdlp->cn_info.cn_name); 438 439 return; 440 } 441 442 /* 443 * Minor device name will be bus path 444 * concatenated with connection name. 445 * One of consumers of the sysevent will pass it 446 * to cfgadm as AP ID. 447 */ 448 (void) strcpy(cn_path, "/devices"); 449 (void) ddi_pathname(dip, cn_path + strlen("/devices")); 450 451 ap_id_len = strlen(cn_path) + strlen(":") + 452 strlen(hdlp->cn_info.cn_name) + 1; 453 ap_id = kmem_zalloc(ap_id_len, kmflag); 454 if (ap_id == NULL) { 455 cmn_err(CE_WARN, 456 "%s%d: Failed to allocate memory for AP ID: %s:%s\n", 457 ddi_driver_name(dip), ddi_get_instance(dip), 458 cn_path, hdlp->cn_info.cn_name); 459 kmem_free(cn_path, MAXPATHLEN); 460 461 return; 462 } 463 464 (void) strcpy(ap_id, cn_path); 465 (void) strcat(ap_id, ":"); 466 (void) strcat(ap_id, hdlp->cn_info.cn_name); 467 kmem_free(cn_path, MAXPATHLEN); 468 469 err = nvlist_alloc(&ev_attr_list, NV_UNIQUE_NAME_TYPE, kmflag); 470 471 if (err != 0) { 472 cmn_err(CE_WARN, 473 "%s%d: Failed to allocate memory for event subclass %d\n", 474 ddi_driver_name(dip), ddi_get_instance(dip), 475 event_sub_class); 476 kmem_free(ap_id, ap_id_len); 477 478 return; 479 } 480 481 switch (event_sub_class) { 482 case DDI_HP_CN_STATE_CHANGE: 483 ev_subclass = ESC_DR_AP_STATE_CHANGE; 484 485 switch (hint) { 486 case SE_NO_HINT: /* fall through */ 487 case SE_HINT_INSERT: /* fall through */ 488 case SE_HINT_REMOVE: 489 err = nvlist_add_string(ev_attr_list, DR_HINT, 490 SE_HINT2STR(hint)); 491 492 if (err != 0) { 493 cmn_err(CE_WARN, "%s%d: Failed to add attr [%s]" 494 " for %s event\n", ddi_driver_name(dip), 495 ddi_get_instance(dip), DR_HINT, 496 ESC_DR_AP_STATE_CHANGE); 497 498 goto done; 499 } 500 break; 501 502 default: 503 cmn_err(CE_WARN, "%s%d: Unknown hint on sysevent\n", 504 ddi_driver_name(dip), ddi_get_instance(dip)); 505 506 goto done; 507 } 508 509 break; 510 511 /* event sub class: DDI_HP_CN_REQ */ 512 case DDI_HP_CN_REQ: 513 ev_subclass = ESC_DR_REQ; 514 515 switch (hint) { 516 case SE_INVESTIGATE_RES: /* fall through */ 517 case SE_INCOMING_RES: /* fall through */ 518 case SE_OUTGOING_RES: /* fall through */ 519 err = nvlist_add_string(ev_attr_list, DR_REQ_TYPE, 520 SE_REQ2STR(hint)); 521 522 if (err != 0) { 523 cmn_err(CE_WARN, 524 "%s%d: Failed to add attr [%s] for %s \n" 525 "event", ddi_driver_name(dip), 526 ddi_get_instance(dip), 527 DR_REQ_TYPE, ESC_DR_REQ); 528 529 goto done; 530 } 531 break; 532 533 default: 534 cmn_err(CE_WARN, "%s%d: Unknown hint on sysevent\n", 535 ddi_driver_name(dip), ddi_get_instance(dip)); 536 537 goto done; 538 } 539 540 break; 541 542 default: 543 cmn_err(CE_WARN, "%s%d: Unknown Event subclass\n", 544 ddi_driver_name(dip), ddi_get_instance(dip)); 545 546 goto done; 547 } 548 549 /* 550 * Add Hotplug Connection (CN) as attribute (common attribute) 551 */ 552 err = nvlist_add_string(ev_attr_list, DR_AP_ID, ap_id); 553 if (err != 0) { 554 cmn_err(CE_WARN, "%s%d: Failed to add attr [%s] for %s event\n", 555 ddi_driver_name(dip), ddi_get_instance(dip), 556 DR_AP_ID, EC_DR); 557 558 goto done; 559 } 560 561 /* 562 * Log this event with sysevent framework. 563 */ 564 err = ddi_log_sysevent(dip, DDI_VENDOR_SUNW, EC_DR, 565 ev_subclass, ev_attr_list, &eid, 566 ((kmflag == KM_SLEEP) ? DDI_SLEEP : DDI_NOSLEEP)); 567 568 if (err != 0) { 569 cmn_err(CE_WARN, "%s%d: Failed to log %s event\n", 570 ddi_driver_name(dip), ddi_get_instance(dip), EC_DR); 571 } 572 573 done: 574 nvlist_free(ev_attr_list); 575 kmem_free(ap_id, ap_id_len); 576 } 577 578 /* 579 * Local functions (called within this file) 580 */ 581 582 /* 583 * Connector operations 584 */ 585 586 /* 587 * Prepare to change state for a Connector: offline, unprobe, etc. 588 */ 589 static int 590 ddihp_cn_pre_change_state(ddi_hp_cn_handle_t *hdlp, 591 ddi_hp_cn_state_t target_state) 592 { 593 ddi_hp_cn_state_t curr_state = hdlp->cn_info.cn_state; 594 dev_info_t *dip = hdlp->cn_dip; 595 int rv = DDI_SUCCESS; 596 597 if (curr_state > target_state && 598 curr_state == DDI_HP_CN_STATE_ENABLED) { 599 /* 600 * If the Connection goes to a lower state from ENABLED, 601 * then offline all children under it. 602 */ 603 rv = ddihp_cn_change_children_state(hdlp, B_FALSE); 604 if (rv != DDI_SUCCESS) { 605 cmn_err(CE_WARN, 606 "(%s%d): " 607 "failed to unconfigure the device in the" 608 " Connection %s\n", ddi_driver_name(dip), 609 ddi_get_instance(dip), 610 hdlp->cn_info.cn_name); 611 612 return (rv); 613 } 614 ASSERT(NEXUS_HAS_HP_OP(dip)); 615 /* 616 * Remove all the children and their ports 617 * after they are offlined. 618 */ 619 rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))( 620 dip, hdlp->cn_info.cn_name, DDI_HPOP_CN_UNPROBE, 621 NULL, NULL); 622 if (rv != DDI_SUCCESS) { 623 cmn_err(CE_WARN, 624 "(%s%d): failed" 625 " to unprobe the device in the Connector" 626 " %s\n", ddi_driver_name(dip), 627 ddi_get_instance(dip), 628 hdlp->cn_info.cn_name); 629 630 return (rv); 631 } 632 633 DDI_HP_NEXDBG((CE_CONT, 634 "ddihp_connector_ops (%s%d): device" 635 " is unconfigured and unprobed in Connector %s\n", 636 ddi_driver_name(dip), ddi_get_instance(dip), 637 hdlp->cn_info.cn_name)); 638 } 639 640 return (rv); 641 } 642 643 /* 644 * Jobs after change state of a Connector: update last change time, 645 * probe, online, sysevent, etc. 646 */ 647 static int 648 ddihp_cn_post_change_state(ddi_hp_cn_handle_t *hdlp, 649 ddi_hp_cn_state_t new_state) 650 { 651 int rv = DDI_SUCCESS; 652 ddi_hp_cn_state_t curr_state = hdlp->cn_info.cn_state; 653 654 /* Update the state in handle */ 655 if (new_state != curr_state) { 656 hdlp->cn_info.cn_state = new_state; 657 ddihp_update_last_change(hdlp); 658 } 659 660 if (curr_state < new_state && 661 new_state == DDI_HP_CN_STATE_ENABLED) { 662 /* 663 * Probe and online devices if state is 664 * upgraded to ENABLED. 665 */ 666 rv = ddihp_cn_handle_state_change(hdlp); 667 } 668 if (curr_state != hdlp->cn_info.cn_state) { 669 /* 670 * For Connector, generate a sysevent on 671 * state change. 672 */ 673 ddihp_cn_gen_sysevent(hdlp, DDI_HP_CN_STATE_CHANGE, 674 SE_NO_HINT, KM_SLEEP); 675 } 676 677 return (rv); 678 } 679 680 /* 681 * Handle Connector state change. 682 * 683 * This function is called after connector is upgraded to ENABLED sate. 684 * It probes the device plugged in the connector to setup devinfo nodes 685 * and then online the nodes. 686 */ 687 static int 688 ddihp_cn_handle_state_change(ddi_hp_cn_handle_t *hdlp) 689 { 690 dev_info_t *dip = hdlp->cn_dip; 691 int rv = DDI_SUCCESS; 692 693 ASSERT(DEVI_BUSY_OWNED(dip)); 694 ASSERT(NEXUS_HAS_HP_OP(dip)); 695 /* 696 * If the Connection went to state ENABLED from a lower state, 697 * probe it. 698 */ 699 rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))( 700 dip, hdlp->cn_info.cn_name, DDI_HPOP_CN_PROBE, NULL, NULL); 701 702 if (rv != DDI_SUCCESS) { 703 ddi_hp_cn_state_t target_state = DDI_HP_CN_STATE_POWERED; 704 ddi_hp_cn_state_t result_state = 0; 705 706 /* 707 * Probe failed. Disable the connector so that it can 708 * be enabled again by a later try from userland. 709 */ 710 (void) (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))( 711 dip, hdlp->cn_info.cn_name, DDI_HPOP_CN_CHANGE_STATE, 712 (void *)&target_state, (void *)&result_state); 713 714 if (result_state && result_state != hdlp->cn_info.cn_state) { 715 hdlp->cn_info.cn_state = result_state; 716 ddihp_update_last_change(hdlp); 717 } 718 719 cmn_err(CE_WARN, 720 "(%s%d): failed to probe the Connection %s\n", 721 ddi_driver_name(dip), ddi_get_instance(dip), 722 hdlp->cn_info.cn_name); 723 724 return (rv); 725 } 726 /* 727 * Try to online all the children of CN. 728 */ 729 (void) ddihp_cn_change_children_state(hdlp, B_TRUE); 730 731 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_event_handler (%s%d): " 732 "device is configured in the Connection %s\n", 733 ddi_driver_name(dip), ddi_get_instance(dip), 734 hdlp->cn_info.cn_name)); 735 return (rv); 736 } 737 738 /* 739 * Online/Offline all the children under the Hotplug Connection (CN) 740 * 741 * Do online operation when the online parameter is true; otherwise do offline. 742 */ 743 static int 744 ddihp_cn_change_children_state(ddi_hp_cn_handle_t *hdlp, boolean_t online) 745 { 746 ddi_hp_cn_cfg_t cn_cfg; 747 dev_info_t *dip = hdlp->cn_dip; 748 dev_info_t *cdip; 749 ddi_hp_cn_handle_t *h; 750 int rv = DDI_SUCCESS; 751 752 DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_change_children_state:" 753 " dip %p hdlp %p, online %x\n", 754 (void *)dip, (void *)hdlp, online)); 755 756 cn_cfg.online = online; 757 ASSERT(DEVI_BUSY_OWNED(dip)); 758 759 /* 760 * Return invalid if Connection state is < DDI_HP_CN_STATE_ENABLED 761 * when try to online children. 762 */ 763 if (online && hdlp->cn_info.cn_state < DDI_HP_CN_STATE_ENABLED) { 764 DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_change_children_state: " 765 "Connector %p is not in probed state\n", (void *)hdlp)); 766 767 return (DDI_EINVAL); 768 } 769 770 /* Now, online/offline all the devices depending on the Connector */ 771 772 if (!online) { 773 /* 774 * For offline operation we need to firstly clean up devfs 775 * so as not to prevent driver detach. 776 */ 777 (void) devfs_clean(dip, NULL, DV_CLEAN_FORCE); 778 } 779 for (h = DEVI(dip)->devi_hp_hdlp; h; h = h->next) { 780 if (h->cn_info.cn_type != DDI_HP_CN_TYPE_VIRTUAL_PORT) 781 continue; 782 783 if (h->cn_info.cn_num_dpd_on != 784 hdlp->cn_info.cn_num) 785 continue; 786 787 cdip = h->cn_info.cn_child; 788 ASSERT(cdip); 789 if (online) { 790 /* online children */ 791 if (!ddihp_check_status_prop(dip)) 792 continue; 793 794 if (ndi_devi_online(cdip, 795 NDI_ONLINE_ATTACH | NDI_CONFIG) != NDI_SUCCESS) { 796 cmn_err(CE_WARN, 797 "(%s%d):" 798 " failed to attach driver for a device" 799 " (%s%d) under the Connection %s\n", 800 ddi_driver_name(dip), ddi_get_instance(dip), 801 ddi_driver_name(cdip), 802 ddi_get_instance(cdip), 803 hdlp->cn_info.cn_name); 804 /* 805 * One of the devices failed to online, but we 806 * want to continue to online the rest siblings 807 * after mark the failure here. 808 */ 809 rv = DDI_FAILURE; 810 811 continue; 812 } 813 cn_cfg.rv = NDI_SUCCESS; 814 if (ddi_get_child(cdip)) { 815 /* Continue to online grand children */ 816 int c; 817 818 ndi_devi_enter(cdip, &c); 819 ddi_walk_devs(ddi_get_child(cdip), 820 ddihp_change_node_state, 821 (void *)&cn_cfg); 822 ndi_devi_exit(cdip, c); 823 } 824 if (cn_cfg.rv != NDI_SUCCESS) { 825 /* 826 * one of the grand children is not ONLINE'd. 827 */ 828 cmn_err(CE_WARN, 829 "(%s%d):" 830 " failed to attach driver for a grandchild" 831 "device (%s%d) in the Connection %s\n", 832 ddi_driver_name(dip), ddi_get_instance(dip), 833 ddi_driver_name(cdip), 834 ddi_get_instance(cdip), 835 hdlp->cn_info.cn_name); 836 837 rv = DDI_FAILURE; 838 } 839 840 } else { 841 /* offline children */ 842 if (ndi_devi_offline(cdip, NDI_UNCONFIG) != 843 NDI_SUCCESS) { 844 cmn_err(CE_WARN, 845 "(%s%d):" 846 " failed to dettach driver for the device" 847 " (%s%d) in the Connection %s\n", 848 ddi_driver_name(dip), ddi_get_instance(dip), 849 ddi_driver_name(cdip), 850 ddi_get_instance(cdip), 851 hdlp->cn_info.cn_name); 852 853 return (DDI_EBUSY); 854 } 855 } 856 } 857 858 return (rv); 859 } 860 861 /* 862 * This function is called to online or offline the dev_info nodes for an 863 * Hotplug Connection (CN). 864 */ 865 static int 866 ddihp_change_node_state(dev_info_t *dip, void *arg) 867 { 868 ddi_hp_cn_cfg_t *cn_cfg_p = (ddi_hp_cn_cfg_t *)arg; 869 int rv; 870 871 if (cn_cfg_p->online) { 872 /* It is online operation */ 873 if (!ddihp_check_status_prop(dip)) 874 return (DDI_WALK_PRUNECHILD); 875 876 rv = ndi_devi_online(dip, NDI_ONLINE_ATTACH | NDI_CONFIG); 877 } else { 878 /* It is offline operation */ 879 (void) devfs_clean(ddi_get_parent(dip), NULL, DV_CLEAN_FORCE); 880 rv = ndi_devi_offline(dip, NDI_UNCONFIG); 881 } 882 if (rv != NDI_SUCCESS) { 883 DDI_HP_IMPLDBG((CE_CONT, "ddihp_change_devinfo_node_state:" 884 " failed op %x rv %d\n", cn_cfg_p->online, rv)); 885 cn_cfg_p->rv = rv; 886 887 /* Failed to attach/detach the driver(s) */ 888 return (DDI_WALK_PRUNECHILD); 889 } 890 891 /* Continue the walk */ 892 return (DDI_WALK_CONTINUE); 893 } 894 895 /* 896 * Port operations 897 */ 898 899 /* 900 * Change Port state to target_state. 901 */ 902 static int 903 ddihp_port_change_state(ddi_hp_cn_handle_t *hdlp, 904 ddi_hp_cn_state_t target_state) 905 { 906 ddi_hp_cn_state_t curr_state = hdlp->cn_info.cn_state; 907 908 if (target_state < DDI_HP_CN_STATE_PORT_EMPTY || 909 target_state > DDI_HP_CN_STATE_ONLINE) { 910 911 return (DDI_EINVAL); 912 } 913 914 if (curr_state < target_state) 915 return (ddihp_port_upgrade_state(hdlp, target_state)); 916 else if (curr_state > target_state) 917 return (ddihp_port_downgrade_state(hdlp, target_state)); 918 else 919 return (DDI_SUCCESS); 920 } 921 922 /* 923 * Upgrade port state to target_state. 924 */ 925 static int 926 ddihp_port_upgrade_state(ddi_hp_cn_handle_t *hdlp, 927 ddi_hp_cn_state_t target_state) 928 { 929 ddi_hp_cn_state_t curr_state, new_state, result_state; 930 dev_info_t *cdip; 931 int rv = DDI_SUCCESS; 932 933 curr_state = hdlp->cn_info.cn_state; 934 while (curr_state < target_state) { 935 switch (curr_state) { 936 case DDI_HP_CN_STATE_PORT_EMPTY: 937 /* Check the existence of the corresponding hardware */ 938 new_state = DDI_HP_CN_STATE_PORT_PRESENT; 939 rv = ddihp_connector_ops(hdlp, 940 DDI_HPOP_CN_CHANGE_STATE, 941 (void *)&new_state, (void *)&result_state); 942 if (rv == DDI_SUCCESS) { 943 hdlp->cn_info.cn_state = 944 result_state; 945 } 946 break; 947 case DDI_HP_CN_STATE_PORT_PRESENT: 948 /* Read-only probe the corresponding hardware. */ 949 new_state = DDI_HP_CN_STATE_OFFLINE; 950 rv = ddihp_connector_ops(hdlp, 951 DDI_HPOP_CN_CHANGE_STATE, 952 (void *)&new_state, &cdip); 953 if (rv == DDI_SUCCESS) { 954 hdlp->cn_info.cn_state = 955 DDI_HP_CN_STATE_OFFLINE; 956 957 ASSERT(hdlp->cn_info.cn_child == NULL); 958 hdlp->cn_info.cn_child = cdip; 959 } 960 break; 961 case DDI_HP_CN_STATE_OFFLINE: 962 /* fall through */ 963 case DDI_HP_CN_STATE_MAINTENANCE: 964 965 cdip = hdlp->cn_info.cn_child; 966 967 rv = ndi_devi_online(cdip, 968 NDI_ONLINE_ATTACH | NDI_CONFIG); 969 if (rv == NDI_SUCCESS) { 970 hdlp->cn_info.cn_state = 971 DDI_HP_CN_STATE_ONLINE; 972 rv = DDI_SUCCESS; 973 } else { 974 rv = DDI_FAILURE; 975 DDI_HP_IMPLDBG((CE_CONT, 976 "ddihp_port_upgrade_state: " 977 "failed to online device %p at port: %s\n", 978 (void *)cdip, hdlp->cn_info.cn_name)); 979 } 980 break; 981 case DDI_HP_CN_STATE_ONLINE: 982 983 break; 984 default: 985 /* should never reach here */ 986 ASSERT("unknown devinfo state"); 987 } 988 curr_state = hdlp->cn_info.cn_state; 989 if (rv != DDI_SUCCESS) { 990 DDI_HP_IMPLDBG((CE_CONT, "ddihp_port_upgrade_state: " 991 "failed curr_state=%x, target_state=%x \n", 992 curr_state, target_state)); 993 return (rv); 994 } 995 } 996 997 return (rv); 998 } 999 1000 /* 1001 * Downgrade state to target_state 1002 */ 1003 static int 1004 ddihp_port_downgrade_state(ddi_hp_cn_handle_t *hdlp, 1005 ddi_hp_cn_state_t target_state) 1006 { 1007 ddi_hp_cn_state_t curr_state, new_state, result_state; 1008 dev_info_t *dip = hdlp->cn_dip; 1009 dev_info_t *cdip; 1010 int rv = DDI_SUCCESS; 1011 1012 curr_state = hdlp->cn_info.cn_state; 1013 while (curr_state > target_state) { 1014 1015 switch (curr_state) { 1016 case DDI_HP_CN_STATE_PORT_EMPTY: 1017 1018 break; 1019 case DDI_HP_CN_STATE_PORT_PRESENT: 1020 /* Check the existence of the corresponding hardware */ 1021 new_state = DDI_HP_CN_STATE_PORT_EMPTY; 1022 rv = ddihp_connector_ops(hdlp, 1023 DDI_HPOP_CN_CHANGE_STATE, 1024 (void *)&new_state, (void *)&result_state); 1025 if (rv == DDI_SUCCESS) 1026 hdlp->cn_info.cn_state = 1027 result_state; 1028 1029 break; 1030 case DDI_HP_CN_STATE_OFFLINE: 1031 /* 1032 * Read-only unprobe the corresponding hardware: 1033 * 1. release the assigned resource; 1034 * 2. remove the node pointed by the port's cn_child 1035 */ 1036 new_state = DDI_HP_CN_STATE_PORT_PRESENT; 1037 rv = ddihp_connector_ops(hdlp, 1038 DDI_HPOP_CN_CHANGE_STATE, 1039 (void *)&new_state, (void *)&result_state); 1040 if (rv == DDI_SUCCESS) 1041 hdlp->cn_info.cn_state = 1042 DDI_HP_CN_STATE_PORT_PRESENT; 1043 break; 1044 case DDI_HP_CN_STATE_MAINTENANCE: 1045 /* fall through. */ 1046 case DDI_HP_CN_STATE_ONLINE: 1047 cdip = hdlp->cn_info.cn_child; 1048 1049 (void) devfs_clean(dip, NULL, DV_CLEAN_FORCE); 1050 rv = ndi_devi_offline(cdip, NDI_UNCONFIG); 1051 if (rv == NDI_SUCCESS) { 1052 hdlp->cn_info.cn_state = 1053 DDI_HP_CN_STATE_OFFLINE; 1054 rv = DDI_SUCCESS; 1055 } else { 1056 rv = DDI_EBUSY; 1057 DDI_HP_IMPLDBG((CE_CONT, 1058 "ddihp_port_downgrade_state: failed " 1059 "to offline node, rv=%x, cdip=%p \n", 1060 rv, (void *)cdip)); 1061 } 1062 1063 break; 1064 default: 1065 /* should never reach here */ 1066 ASSERT("unknown devinfo state"); 1067 } 1068 curr_state = hdlp->cn_info.cn_state; 1069 if (rv != DDI_SUCCESS) { 1070 DDI_HP_IMPLDBG((CE_CONT, 1071 "ddihp_port_downgrade_state: failed " 1072 "curr_state=%x, target_state=%x \n", 1073 curr_state, target_state)); 1074 return (rv); 1075 } 1076 } 1077 1078 return (rv); 1079 } 1080 1081 /* 1082 * Misc routines 1083 */ 1084 1085 /* Update the last state change time */ 1086 static void 1087 ddihp_update_last_change(ddi_hp_cn_handle_t *hdlp) 1088 { 1089 time_t time; 1090 1091 if (drv_getparm(TIME, (void *)&time) != DDI_SUCCESS) 1092 hdlp->cn_info.cn_last_change = (time_t)-1; 1093 else 1094 hdlp->cn_info.cn_last_change = (time32_t)time; 1095 } 1096 1097 /* 1098 * Check the device for a 'status' property. A conforming device 1099 * should have a status of "okay", "disabled", "fail", or "fail-xxx". 1100 * 1101 * Return FALSE for a conforming device that is disabled or faulted. 1102 * Return TRUE in every other case. 1103 * 1104 * 'status' property is NOT a bus specific property. It is defined in page 184, 1105 * IEEE 1275 spec. The full name of the spec is "IEEE Standard for 1106 * Boot (Initialization Configuration) Firmware: Core Requirements and 1107 * Practices". 1108 */ 1109 static boolean_t 1110 ddihp_check_status_prop(dev_info_t *dip) 1111 { 1112 char *status_prop; 1113 boolean_t rv = B_TRUE; 1114 1115 /* try to get the 'status' property */ 1116 if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, 1117 "status", &status_prop) == DDI_PROP_SUCCESS) { 1118 /* 1119 * test if the status is "disabled", "fail", or 1120 * "fail-xxx". 1121 */ 1122 if (strcmp(status_prop, "disabled") == 0) { 1123 rv = B_FALSE; 1124 DDI_HP_IMPLDBG((CE_CONT, "ddihp_check_status_prop " 1125 "(%s%d): device is in disabled state", 1126 ddi_driver_name(dip), ddi_get_instance(dip))); 1127 } else if (strncmp(status_prop, "fail", 4) == 0) { 1128 rv = B_FALSE; 1129 cmn_err(CE_WARN, 1130 "hotplug (%s%d): device is in fault state (%s)\n", 1131 ddi_driver_name(dip), ddi_get_instance(dip), 1132 status_prop); 1133 } 1134 1135 ddi_prop_free(status_prop); 1136 } 1137 1138 return (rv); 1139 } 1140