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