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 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #include "cfga_scsi.h" 29 30 /* Structure for walking the tree */ 31 typedef struct { 32 apid_t *apidp; 33 char *hba_logp; 34 ldata_list_t *listp; 35 scfga_cmd_t cmd; 36 cfga_stat_t chld_config; 37 cfga_stat_t hba_rstate; 38 scfga_ret_t ret; 39 int l_errno; 40 } scfga_list_t; 41 42 typedef struct { 43 uint_t itype; 44 const char *ntype; 45 const char *name; 46 } scfga_devtype_t; 47 48 /* The TYPE field is parseable and should not contain spaces */ 49 #define SCFGA_BUS_TYPE "scsi-bus" 50 51 /* Function prototypes */ 52 static scfga_ret_t postprocess_list_data(const ldata_list_t *listp, 53 scfga_cmd_t cmd, cfga_stat_t chld_config, int *np); 54 static int stat_dev(di_node_t node, void *arg); 55 static scfga_ret_t do_stat_bus(scfga_list_t *lap, int limited_bus_stat); 56 static int get_bus_state(di_node_t node, void *arg); 57 58 static scfga_ret_t do_stat_dev(const di_node_t node, const char *nodepath, 59 scfga_list_t *lap, int limited_dev_stat); 60 static cfga_stat_t bus_devinfo_to_recep_state(uint_t bus_di_state); 61 static cfga_stat_t dev_devinfo_to_occupant_state(uint_t dev_di_state); 62 static char *get_device_type(di_node_t); 63 static void get_hw_info(di_node_t node, cfga_list_data_t *clp); 64 65 66 static scfga_devtype_t device_list[] = { 67 { DTYPE_DIRECT, DDI_NT_BLOCK_CHAN, "disk"}, 68 { DTYPE_DIRECT, DDI_NT_BLOCK, "disk"}, 69 { DTYPE_DIRECT, DDI_NT_BLOCK_WWN, "disk"}, 70 { DTYPE_DIRECT, DDI_NT_BLOCK_FABRIC, "disk"}, 71 { DTYPE_SEQUENTIAL, DDI_NT_TAPE, "tape"}, 72 { DTYPE_PRINTER, NULL, "printer"}, 73 { DTYPE_PROCESSOR, NULL, "processor"}, 74 { DTYPE_WORM, NULL, "WORM"}, 75 { DTYPE_RODIRECT, DDI_NT_CD_CHAN, "CD-ROM"}, 76 { DTYPE_RODIRECT, DDI_NT_CD, "CD-ROM"}, 77 { DTYPE_SCANNER, NULL, "scanner"}, 78 { DTYPE_OPTICAL, NULL, "optical"}, 79 { DTYPE_CHANGER, NULL, "med-changer"}, 80 { DTYPE_COMM, NULL, "comm-device"}, 81 { DTYPE_ARRAY_CTRL, NULL, "array-ctrl"}, 82 { DTYPE_ESI, NULL, "ESI"} 83 }; 84 85 #define N_DEVICE_TYPES (sizeof (device_list) / sizeof (device_list[0])) 86 87 scfga_ret_t 88 do_list( 89 apid_t *apidp, 90 scfga_cmd_t cmd, 91 ldata_list_t **llpp, 92 int *nelemp, 93 char **errstring) 94 { 95 int n = -1, l_errno = 0, limited_bus_stat; 96 walkarg_t u; 97 scfga_list_t larg = {NULL}; 98 scfga_ret_t ret; 99 int init_flag; 100 101 assert(apidp->hba_phys != NULL && apidp->path != NULL); 102 103 if (*llpp != NULL || *nelemp != 0) { 104 return (SCFGA_ERR); 105 } 106 107 /* Create the HBA logid (also base component of logical ap_id) */ 108 ret = make_hba_logid(apidp->hba_phys, &larg.hba_logp, &l_errno); 109 if (ret != SCFGA_OK) { 110 cfga_err(errstring, l_errno, ERR_LIST, 0); 111 return (SCFGA_ERR); 112 } 113 114 assert(larg.hba_logp != NULL); 115 116 larg.cmd = cmd; 117 larg.apidp = apidp; 118 larg.hba_rstate = CFGA_STAT_NONE; 119 120 121 /* 122 * For all list commands, the bus and 1 or more devices 123 * needs to be stat'ed 124 */ 125 126 /* 127 * By default we use DINFOCACHE to get a "full" snapshot 128 * This much faster than DINFOFORCE which actually 129 * attaches devices. DINFOFORCE used only if caller 130 * explicitly requests it via a private option. 131 */ 132 init_flag = (apidp->flags & FLAG_USE_DIFORCE) ? DINFOFORCE : DINFOCACHE; 133 limited_bus_stat = 0; 134 135 switch (larg.cmd) { 136 case SCFGA_STAT_DEV: 137 limited_bus_stat = 1; /* We need only bus state */ 138 /*FALLTHRU*/ 139 case SCFGA_STAT_ALL: 140 break; 141 case SCFGA_STAT_BUS: 142 /* limited_bus_stat = 0 and no DINFOCACHE/DINFOFORCE */ 143 init_flag = 0; 144 break; 145 default: 146 cfga_err(errstring, EINVAL, ERR_LIST, 0); 147 goto out; 148 } 149 150 /* 151 * DINFOCACHE implies DINFOCPYALL. DINFOCPYALL shouldn't 152 * be ORed with DINFOCACHE, else libdevinfo will return 153 * error 154 */ 155 if (init_flag != DINFOCACHE) 156 init_flag |= DINFOCPYALL; 157 158 if ((ret = do_stat_bus(&larg, limited_bus_stat)) != SCFGA_OK) { 159 cfga_err(errstring, larg.l_errno, ERR_LIST, 0); 160 goto out; 161 } 162 163 #ifdef DEBUG 164 if (limited_bus_stat) { 165 assert(larg.listp == NULL); 166 } else { 167 assert(larg.listp != NULL); 168 } 169 #endif 170 171 /* Assume that the bus has no configured children */ 172 larg.chld_config = CFGA_STAT_UNCONFIGURED; 173 174 /* 175 * If stat'ing a specific device, we don't know if it exists yet. 176 * If stat'ing a bus or a bus and child devices, we have at least the 177 * bus stat data at this point. 178 */ 179 if (larg.cmd == SCFGA_STAT_DEV) { 180 larg.ret = SCFGA_APID_NOEXIST; 181 } else { 182 larg.ret = SCFGA_OK; 183 } 184 185 /* we need to stat at least 1 device for all commands */ 186 u.node_args.flags = DI_WALK_CLDFIRST; 187 u.node_args.fcn = stat_dev; 188 189 /* 190 * Subtree is ALWAYS rooted at the HBA (not at the device) as 191 * otherwise deadlock may occur if bus is disconnected. 192 */ 193 ret = walk_tree(apidp->hba_phys, &larg, init_flag, &u, 194 SCFGA_WALK_NODE, &larg.l_errno); 195 196 if (ret != SCFGA_OK || (ret = larg.ret) != SCFGA_OK) { 197 if (ret != SCFGA_APID_NOEXIST) { 198 cfga_err(errstring, larg.l_errno, ERR_LIST, 0); 199 } 200 goto out; 201 } 202 203 assert(larg.listp != NULL); 204 205 n = 0; 206 ret = postprocess_list_data(larg.listp, cmd, larg.chld_config, &n); 207 if (ret != SCFGA_OK) { 208 cfga_err(errstring, 0, ERR_LIST, 0); 209 ret = SCFGA_LIB_ERR; 210 goto out; 211 } 212 213 *nelemp = n; 214 *llpp = larg.listp; 215 ret = SCFGA_OK; 216 /* FALLTHROUGH */ 217 out: 218 if (ret != SCFGA_OK) list_free(&larg.listp); 219 S_FREE(larg.hba_logp); 220 return (ret); 221 } 222 223 static scfga_ret_t 224 postprocess_list_data( 225 const ldata_list_t *listp, 226 scfga_cmd_t cmd, 227 cfga_stat_t chld_config, 228 int *np) 229 { 230 ldata_list_t *tmplp = NULL; 231 cfga_list_data_t *hba_ldatap = NULL; 232 int i; 233 234 235 *np = 0; 236 237 if (listp == NULL) { 238 return (SCFGA_ERR); 239 } 240 241 hba_ldatap = NULL; 242 tmplp = (ldata_list_t *)listp; 243 for (i = 0; tmplp != NULL; tmplp = tmplp->next) { 244 i++; 245 if (GET_DYN(tmplp->ldata.ap_phys_id) == NULL) { 246 /* A bus stat data */ 247 assert(GET_DYN(tmplp->ldata.ap_log_id) == NULL); 248 hba_ldatap = &tmplp->ldata; 249 #ifdef DEBUG 250 } else { 251 assert(GET_DYN(tmplp->ldata.ap_log_id) != NULL); 252 #endif 253 } 254 } 255 256 switch (cmd) { 257 case SCFGA_STAT_DEV: 258 if (i != 1 || hba_ldatap != NULL) { 259 return (SCFGA_LIB_ERR); 260 } 261 break; 262 case SCFGA_STAT_BUS: 263 if (i != 1 || hba_ldatap == NULL) { 264 return (SCFGA_LIB_ERR); 265 } 266 break; 267 case SCFGA_STAT_ALL: 268 if (i < 1 || hba_ldatap == NULL) { 269 return (SCFGA_LIB_ERR); 270 } 271 break; 272 default: 273 return (SCFGA_LIB_ERR); 274 } 275 276 *np = i; 277 278 /* Fill in the occupant (child) state. */ 279 if (hba_ldatap != NULL) { 280 hba_ldatap->ap_o_state = chld_config; 281 } 282 return (SCFGA_OK); 283 } 284 285 static int 286 stat_dev(di_node_t node, void *arg) 287 { 288 scfga_list_t *lap = NULL; 289 char *devfsp = NULL, *nodepath = NULL; 290 size_t len = 0; 291 int limited_dev_stat = 0, match_minor, rv; 292 scfga_ret_t ret; 293 294 lap = (scfga_list_t *)arg; 295 296 /* Skip stub nodes */ 297 if (IS_STUB_NODE(node)) { 298 return (DI_WALK_CONTINUE); 299 } 300 301 /* Skip partial nodes */ 302 if (!known_state(node)) { 303 return (DI_WALK_CONTINUE); 304 } 305 306 devfsp = di_devfs_path(node); 307 if (devfsp == NULL) { 308 rv = DI_WALK_CONTINUE; 309 goto out; 310 } 311 312 len = strlen(DEVICES_DIR) + strlen(devfsp) + 1; 313 314 nodepath = calloc(1, len); 315 if (nodepath == NULL) { 316 lap->l_errno = errno; 317 lap->ret = SCFGA_LIB_ERR; 318 rv = DI_WALK_TERMINATE; 319 goto out; 320 } 321 322 (void) snprintf(nodepath, len, "%s%s", DEVICES_DIR, devfsp); 323 324 /* Skip node if it is HBA */ 325 match_minor = 0; 326 if (!dev_cmp(lap->apidp->hba_phys, nodepath, match_minor)) { 327 rv = DI_WALK_CONTINUE; 328 goto out; 329 } 330 331 /* If stat'ing a specific device, is this that device */ 332 if (lap->cmd == SCFGA_STAT_DEV) { 333 assert(lap->apidp->path != NULL); 334 if (dev_cmp(lap->apidp->path, nodepath, match_minor)) { 335 rv = DI_WALK_CONTINUE; 336 goto out; 337 } 338 } 339 340 /* 341 * If stat'ing a bus only, we look at device nodes only to get 342 * bus configuration status. So a limited stat will suffice. 343 */ 344 if (lap->cmd == SCFGA_STAT_BUS) { 345 limited_dev_stat = 1; 346 } else { 347 limited_dev_stat = 0; 348 } 349 350 /* 351 * Ignore errors if stat'ing a bus or listing all 352 */ 353 ret = do_stat_dev(node, nodepath, lap, limited_dev_stat); 354 if (ret != SCFGA_OK) { 355 if (lap->cmd == SCFGA_STAT_DEV) { 356 lap->ret = ret; 357 rv = DI_WALK_TERMINATE; 358 } else { 359 rv = DI_WALK_CONTINUE; 360 } 361 goto out; 362 } 363 364 /* Are we done ? */ 365 rv = DI_WALK_CONTINUE; 366 if (lap->cmd == SCFGA_STAT_BUS && 367 lap->chld_config == CFGA_STAT_CONFIGURED) { 368 rv = DI_WALK_TERMINATE; 369 } else if (lap->cmd == SCFGA_STAT_DEV) { 370 /* 371 * If stat'ing a specific device, we are done at this point. 372 */ 373 lap->ret = SCFGA_OK; 374 rv = DI_WALK_TERMINATE; 375 } 376 377 /*FALLTHRU*/ 378 out: 379 S_FREE(nodepath); 380 if (devfsp != NULL) di_devfs_path_free(devfsp); 381 return (rv); 382 } 383 384 385 struct bus_state { 386 int b_state; 387 int b_retired; 388 }; 389 390 static scfga_ret_t 391 do_stat_bus(scfga_list_t *lap, int limited_bus_stat) 392 { 393 cfga_list_data_t *clp = NULL; 394 ldata_list_t *listp = NULL; 395 int l_errno = 0; 396 struct bus_state bstate = {0}; 397 walkarg_t u; 398 scfga_ret_t ret; 399 400 assert(lap->hba_logp != NULL); 401 402 /* Get bus state */ 403 u.node_args.flags = 0; 404 u.node_args.fcn = get_bus_state; 405 406 ret = walk_tree(lap->apidp->hba_phys, &bstate, DINFOPROP, &u, 407 SCFGA_WALK_NODE, &l_errno); 408 if (ret == SCFGA_OK) { 409 lap->hba_rstate = bus_devinfo_to_recep_state(bstate.b_state); 410 } else { 411 lap->hba_rstate = CFGA_STAT_NONE; 412 } 413 414 if (limited_bus_stat) { 415 /* We only want to know bus(receptacle) connect status */ 416 return (SCFGA_OK); 417 } 418 419 listp = calloc(1, sizeof (ldata_list_t)); 420 if (listp == NULL) { 421 lap->l_errno = errno; 422 return (SCFGA_LIB_ERR); 423 } 424 425 clp = &listp->ldata; 426 427 (void) snprintf(clp->ap_log_id, sizeof (clp->ap_log_id), "%s", 428 lap->hba_logp); 429 (void) snprintf(clp->ap_phys_id, sizeof (clp->ap_phys_id), "%s", 430 lap->apidp->hba_phys); 431 432 clp->ap_class[0] = '\0'; /* Filled by libcfgadm */ 433 clp->ap_r_state = lap->hba_rstate; 434 clp->ap_o_state = CFGA_STAT_NONE; /* filled in later by the plug-in */ 435 clp->ap_cond = 436 (bstate.b_retired) ? CFGA_COND_FAILED : CFGA_COND_UNKNOWN; 437 clp->ap_busy = 0; 438 clp->ap_status_time = (time_t)-1; 439 clp->ap_info[0] = '\0'; 440 441 (void) snprintf(clp->ap_type, sizeof (clp->ap_type), "%s", 442 SCFGA_BUS_TYPE); 443 444 /* Link it in */ 445 listp->next = lap->listp; 446 lap->listp = listp; 447 448 return (SCFGA_OK); 449 } 450 451 static int 452 get_bus_state(di_node_t node, void *arg) 453 { 454 struct bus_state *bsp = (struct bus_state *)arg; 455 456 bsp->b_state = di_state(node); 457 bsp->b_retired = di_retired(node); 458 459 return (DI_WALK_TERMINATE); 460 } 461 462 static scfga_ret_t 463 do_stat_dev( 464 const di_node_t node, 465 const char *nodepath, 466 scfga_list_t *lap, 467 int limited_dev_stat) 468 { 469 uint_t devinfo_state = 0; 470 char *dyncomp = NULL; 471 cfga_list_data_t *clp = NULL; 472 ldata_list_t *listp = NULL; 473 cfga_stat_t ostate; 474 scfga_ret_t ret; 475 476 assert(lap->apidp->hba_phys != NULL); 477 assert(lap->hba_logp != NULL); 478 479 devinfo_state = di_state(node); 480 ostate = dev_devinfo_to_occupant_state(devinfo_state); 481 482 /* If child device is configured, record it */ 483 if (ostate == CFGA_STAT_CONFIGURED) { 484 lap->chld_config = CFGA_STAT_CONFIGURED; 485 } 486 487 if (limited_dev_stat) { 488 /* We only want to know device config state */ 489 return (SCFGA_OK); 490 } 491 492 listp = calloc(1, sizeof (ldata_list_t)); 493 if (listp == NULL) { 494 lap->l_errno = errno; 495 return (SCFGA_LIB_ERR); 496 } 497 498 clp = &listp->ldata; 499 500 /* Create the dynamic component */ 501 ret = make_dyncomp(node, nodepath, &dyncomp, &lap->l_errno); 502 if (ret != SCFGA_OK) { 503 S_FREE(listp); 504 return (ret); 505 } 506 507 assert(dyncomp != NULL); 508 509 /* Create logical and physical ap_id */ 510 (void) snprintf(clp->ap_log_id, sizeof (clp->ap_log_id), "%s%s%s", 511 lap->hba_logp, DYN_SEP, dyncomp); 512 513 (void) snprintf(clp->ap_phys_id, sizeof (clp->ap_phys_id), "%s%s%s", 514 lap->apidp->hba_phys, DYN_SEP, dyncomp); 515 516 S_FREE(dyncomp); 517 518 clp->ap_class[0] = '\0'; /* Filled in by libcfgadm */ 519 clp->ap_r_state = lap->hba_rstate; 520 clp->ap_o_state = ostate; 521 clp->ap_cond = di_retired(node) ? CFGA_COND_FAILED : CFGA_COND_UNKNOWN; 522 clp->ap_busy = 0; /* no way to determine state change */ 523 clp->ap_status_time = (time_t)-1; 524 525 get_hw_info(node, clp); 526 527 /* Link it in */ 528 listp->next = lap->listp; 529 lap->listp = listp; 530 531 return (SCFGA_OK); 532 } 533 534 /* fill in device type, vid, pid from properties */ 535 static void 536 get_hw_info(di_node_t node, cfga_list_data_t *clp) 537 { 538 char *cp = NULL; 539 char *inq_vid, *inq_pid; 540 541 /* 542 * Fill in type information 543 */ 544 cp = (char *)get_device_type(node); 545 if (cp == NULL) { 546 cp = (char *)GET_MSG_STR(ERR_UNAVAILABLE); 547 } 548 (void) snprintf(clp->ap_type, sizeof (clp->ap_type), "%s", S_STR(cp)); 549 550 /* 551 * Fill in vendor and product ID. 552 */ 553 if ((di_prop_lookup_strings(DDI_DEV_T_ANY, node, 554 "inquiry-product-id", &inq_pid) == 1) && 555 (di_prop_lookup_strings(DDI_DEV_T_ANY, node, 556 "inquiry-vendor-id", &inq_vid) == 1)) { 557 (void) snprintf(clp->ap_info, sizeof (clp->ap_info), 558 "%s %s", inq_vid, inq_pid); 559 } 560 } 561 562 /* 563 * Get dtype from "inquiry-device-type" property. If not present, 564 * derive it from minor node type 565 */ 566 static char * 567 get_device_type(di_node_t node) 568 { 569 char *name = NULL; 570 int *inq_dtype; 571 int i; 572 573 /* first, derive type based on inquiry property */ 574 if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "inquiry-device-type", 575 &inq_dtype) == 1) { 576 int itype = (*inq_dtype) & DTYPE_MASK; 577 578 for (i = 0; i < N_DEVICE_TYPES; i++) { 579 if (device_list[i].itype == DTYPE_UNKNOWN) 580 continue; 581 if (itype == device_list[i].itype) { 582 name = (char *)device_list[i].name; 583 break; 584 } 585 } 586 } 587 588 /* if property fails, use minor nodetype */ 589 if (name == NULL) { 590 char *nodetype; 591 di_minor_t minor = di_minor_next(node, DI_MINOR_NIL); 592 593 if ((minor != DI_MINOR_NIL) && 594 ((nodetype = di_minor_nodetype(minor)) != NULL)) { 595 for (i = 0; i < N_DEVICE_TYPES; i++) { 596 if (device_list[i].ntype && 597 (strcmp(nodetype, device_list[i].ntype) 598 == 0)) { 599 name = (char *)device_list[i].name; 600 break; 601 } 602 } 603 } 604 } 605 606 if (name == NULL) /* default to unknown */ 607 name = "unknown"; 608 return (name); 609 } 610 611 /* Transform linked list into an array */ 612 scfga_ret_t 613 list_ext_postprocess( 614 ldata_list_t **llpp, 615 int nelem, 616 cfga_list_data_t **ap_id_list, 617 int *nlistp, 618 char **errstring) 619 { 620 cfga_list_data_t *ldatap = NULL; 621 ldata_list_t *tmplp = NULL; 622 int i = -1; 623 624 *ap_id_list = NULL; 625 *nlistp = 0; 626 627 if (*llpp == NULL || nelem < 0) { 628 return (SCFGA_LIB_ERR); 629 } 630 631 if (nelem == 0) { 632 return (SCFGA_APID_NOEXIST); 633 } 634 635 ldatap = calloc(nelem, sizeof (cfga_list_data_t)); 636 if (ldatap == NULL) { 637 cfga_err(errstring, errno, ERR_LIST, 0); 638 return (SCFGA_LIB_ERR); 639 } 640 641 /* Extract the list_data structures from the linked list */ 642 tmplp = *llpp; 643 for (i = 0; i < nelem && tmplp != NULL; i++) { 644 ldatap[i] = tmplp->ldata; 645 tmplp = tmplp->next; 646 } 647 648 if (i < nelem || tmplp != NULL) { 649 S_FREE(ldatap); 650 return (SCFGA_LIB_ERR); 651 } 652 653 *nlistp = nelem; 654 *ap_id_list = ldatap; 655 656 return (SCFGA_OK); 657 } 658 659 /* 660 * Convert bus state to receptacle state 661 */ 662 static cfga_stat_t 663 bus_devinfo_to_recep_state(uint_t bus_di_state) 664 { 665 cfga_stat_t rs; 666 667 switch (bus_di_state) { 668 case DI_BUS_QUIESCED: 669 case DI_BUS_DOWN: 670 rs = CFGA_STAT_DISCONNECTED; 671 break; 672 /* 673 * NOTE: An explicit flag for active should probably be added to 674 * libdevinfo. 675 */ 676 default: 677 rs = CFGA_STAT_CONNECTED; 678 break; 679 } 680 681 return (rs); 682 } 683 684 /* 685 * Convert device state to occupant state 686 */ 687 static cfga_stat_t 688 dev_devinfo_to_occupant_state(uint_t dev_di_state) 689 { 690 /* Driver attached ? */ 691 if ((dev_di_state & DI_DRIVER_DETACHED) != DI_DRIVER_DETACHED) { 692 return (CFGA_STAT_CONFIGURED); 693 } 694 695 if ((dev_di_state & DI_DEVICE_OFFLINE) == DI_DEVICE_OFFLINE || 696 (dev_di_state & DI_DEVICE_DOWN) == DI_DEVICE_DOWN) { 697 return (CFGA_STAT_UNCONFIGURED); 698 } else { 699 return (CFGA_STAT_NONE); 700 } 701 } 702