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