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 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <stdio.h> 27 #include <string.h> 28 #include <stdlib.h> 29 #include <unistd.h> 30 #include <sys/param.h> 31 #include <errno.h> 32 #include <sys/types.h> 33 #include <dirent.h> 34 #include <libdevinfo.h> 35 #include <fcntl.h> 36 #include <sys/types.h> 37 #include <sys/stat.h> 38 #include <sys/pci.h> 39 #include <sys/biosdisk.h> 40 41 42 /* 43 * structure used for searching device tree for a node matching 44 * pci bus/dev/fn 45 */ 46 typedef struct pcibdf { 47 int busnum; 48 int devnum; 49 int funcnum; 50 di_node_t di_node; 51 } pcibdf_t; 52 53 /* 54 * structure used for searching device tree for a node matching 55 * USB serial number. 56 */ 57 typedef struct { 58 uint64_t serialno; 59 di_node_t node; 60 } usbser_t; 61 62 /* 63 * structure for holding the mapping info 64 */ 65 typedef struct { 66 int disklist_index; /* index to disk_list of the mapped path */ 67 int matchcount; /* number of matches per this device number */ 68 } mapinfo_t; 69 70 #define DEVFS_PREFIX "/devices" 71 #define DISKS_LIST_INCR 20 /* increment for resizing disk_list */ 72 73 #define BIOSPROPNAME_TMPL "biosdev-0x%x" 74 #define BIOSPROPNAME_TMPL_LEN 13 75 #define BIOSDEV_NUM 8 76 #define STARTING_DRVNUM 0x80 77 78 /* 79 * array to hold mappings. Element at index X corresponds to BIOS device 80 * number 0x80 + X 81 */ 82 static mapinfo_t mapinfo[BIOSDEV_NUM]; 83 84 /* 85 * Cache copy of kernel device tree snapshot root handle, includes devices 86 * that are detached 87 */ 88 static di_node_t root_node = DI_NODE_NIL; 89 90 /* 91 * kernel device tree snapshot with currently attached devices. Detached 92 * devices are not included. 93 */ 94 static di_node_t root_allnode = DI_NODE_NIL; 95 96 /* 97 * handle to retrieve prom properties 98 */ 99 100 static di_prom_handle_t prom_hdl = DI_PROM_HANDLE_NIL; 101 102 static char **disk_list = NULL; /* array of physical device pathnames */ 103 static int disk_list_len = 0; /* length of disk_list */ 104 static int disk_list_valid = 0; /* number of valid entries in disk_list */ 105 106 static int debug = 0; /* used for enabling debug output */ 107 108 109 /* Local function prototypes */ 110 static void new_disk_list_entry(di_node_t node); 111 static int i_disktype(di_node_t node, di_minor_t minor, void *arg); 112 static void build_disk_list(); 113 static int search_disklist_match_path(char *path); 114 static void free_disks(); 115 static void cleanup_and_exit(int); 116 117 static int match_edd(biosdev_data_t *bd); 118 static int match_first_block(biosdev_data_t *bd); 119 120 static di_node_t search_tree_match_pcibdf(di_node_t node, int bus, int dev, 121 int fn); 122 static int i_match_pcibdf(di_node_t node, void *arg); 123 124 static di_node_t search_tree_match_usbserialno(di_node_t node, 125 uint64_t serialno); 126 static int i_match_usbserialno(di_node_t node, void *arg); 127 128 static di_node_t search_children_match_busaddr(di_node_t node, 129 char *matchbusaddr); 130 131 132 133 static void 134 new_disk_list_entry(di_node_t node) 135 { 136 size_t newsize; 137 char **newlist; 138 int newlen; 139 char *devfspath; 140 141 if (disk_list_valid >= disk_list_len) { 142 /* valid should never really be larger than len */ 143 /* if they are equal we need to init or realloc */ 144 newlen = disk_list_len + DISKS_LIST_INCR; 145 newsize = newlen * sizeof (*disk_list); 146 147 newlist = (char **)realloc(disk_list, newsize); 148 if (newlist == NULL) { 149 (void) printf("realloc failed to resize disk table\n"); 150 cleanup_and_exit(1); 151 } 152 disk_list = newlist; 153 disk_list_len = newlen; 154 } 155 156 devfspath = di_devfs_path(node); 157 disk_list[disk_list_valid] = devfspath; 158 if (debug) 159 (void) printf("adding %s\n", devfspath); 160 disk_list_valid++; 161 } 162 163 /* ARGSUSED */ 164 static int 165 i_disktype(di_node_t node, di_minor_t minor, void *arg) 166 { 167 char *minortype; 168 169 if (di_minor_spectype(minor) == S_IFCHR) { 170 minortype = di_minor_nodetype(minor); 171 172 /* exclude CD's */ 173 if (strncmp(minortype, DDI_NT_CD, sizeof (DDI_NT_CD) - 1) != 0) 174 /* only take p0 raw device */ 175 if (strcmp(di_minor_name(minor), "q,raw") == 0) 176 new_disk_list_entry(node); 177 } 178 return (DI_WALK_CONTINUE); 179 } 180 181 static void 182 build_disk_list() 183 { 184 int ret; 185 ret = di_walk_minor(root_node, DDI_NT_BLOCK, 0, NULL, 186 i_disktype); 187 if (ret != 0) { 188 (void) fprintf(stderr, "di_walk_minor failed errno %d\n", 189 errno); 190 cleanup_and_exit(1); 191 } 192 } 193 194 static void 195 free_disks() 196 { 197 int i; 198 199 if (disk_list) { 200 for (i = 0; i < disk_list_valid; i++) 201 di_devfs_path_free(disk_list[i]); 202 203 free(disk_list); 204 } 205 } 206 207 static int 208 i_match_pcibdf(di_node_t node, void *arg) 209 { 210 pcibdf_t *pbp; 211 int len; 212 uint32_t regval; 213 uint32_t busnum, funcnum, devicenum; 214 char *devtype; 215 uint32_t *regbuf = NULL; 216 di_node_t parentnode; 217 218 pbp = (pcibdf_t *)arg; 219 220 parentnode = di_parent_node(node); 221 222 len = di_prop_lookup_strings(DDI_DEV_T_ANY, parentnode, 223 "device_type", (char **)&devtype); 224 225 if ((len <= 0) || 226 ((strcmp(devtype, "pci") != 0) && (strcmp(devtype, "pciex") != 0))) 227 return (DI_WALK_CONTINUE); 228 229 len = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "reg", 230 (int **)®buf); 231 232 if (len <= 0) { 233 /* Try PROM property */ 234 len = di_prom_prop_lookup_ints(prom_hdl, node, "reg", 235 (int **)®buf); 236 } 237 238 239 if (len > 0) { 240 regval = regbuf[0]; 241 242 busnum = PCI_REG_BUS_G(regval); 243 devicenum = PCI_REG_DEV_G(regval); 244 funcnum = PCI_REG_FUNC_G(regval); 245 246 if ((busnum == pbp->busnum) && 247 (devicenum == pbp->devnum) && 248 (funcnum == pbp->funcnum)) { 249 /* found it */ 250 pbp->di_node = node; 251 return (DI_WALK_TERMINATE); 252 } 253 } 254 255 return (DI_WALK_CONTINUE); 256 } 257 258 static di_node_t 259 search_tree_match_pcibdf(di_node_t node, int bus, int dev, int fn) 260 { 261 pcibdf_t pb; 262 pb.busnum = bus; 263 pb.devnum = dev; 264 pb.funcnum = fn; 265 pb.di_node = DI_NODE_NIL; 266 267 (void) di_walk_node(node, DI_WALK_CLDFIRST, &pb, i_match_pcibdf); 268 return (pb.di_node); 269 270 } 271 272 static int 273 i_match_usbserialno(di_node_t node, void *arg) 274 { 275 int len; 276 char *serialp; 277 usbser_t *usbsp; 278 279 usbsp = (usbser_t *)arg; 280 281 len = di_prop_lookup_bytes(DDI_DEV_T_ANY, node, "usb-serialno", 282 (uchar_t **)&serialp); 283 284 if ((len > 0) && (strncmp((char *)&usbsp->serialno, serialp, 285 sizeof (uint64_t)) == 0)) { 286 usbsp->node = node; 287 return (DI_WALK_TERMINATE); 288 } 289 return (DI_WALK_CONTINUE); 290 } 291 292 static di_node_t 293 search_tree_match_usbserialno(di_node_t node, uint64_t serialno) 294 { 295 296 usbser_t usbs; 297 298 usbs.serialno = serialno; 299 usbs.node = DI_NODE_NIL; 300 301 (void) di_walk_node(node, DI_WALK_CLDFIRST, &usbs, i_match_usbserialno); 302 return (usbs.node); 303 } 304 305 /* 306 * returns the index to the disklist to the disk with matching path 307 */ 308 static int 309 search_disklist_match_path(char *path) 310 { 311 int i; 312 for (i = 0; i < disk_list_valid; i++) 313 if (strcmp(disk_list[i], path) == 0) { 314 return (i); 315 } 316 return (-1); 317 } 318 319 /* 320 * Find first child of 'node' whose unit address is 'matchbusaddr' 321 */ 322 static di_node_t 323 search_children_match_busaddr(di_node_t node, char *matchbusaddr) 324 { 325 di_node_t cnode; 326 char *busaddr; 327 di_path_t pi = DI_PATH_NIL; 328 329 if (matchbusaddr == NULL) 330 return (DI_NODE_NIL); 331 332 while ((pi = di_path_phci_next_path(node, pi)) != DI_PATH_NIL) { 333 busaddr = di_path_bus_addr(pi); 334 if (busaddr == NULL) 335 continue; 336 if (strncmp(busaddr, matchbusaddr, MAXNAMELEN) == 0) 337 return (di_path_client_node(pi)); 338 } 339 340 for (cnode = di_child_node(node); cnode != DI_NODE_NIL; 341 cnode = di_sibling_node(cnode)) { 342 busaddr = di_bus_addr(cnode); 343 if (busaddr == NULL) 344 continue; 345 if (strncmp(busaddr, matchbusaddr, MAXNAMELEN) == 0) 346 return (cnode); 347 } 348 349 return (DI_NODE_NIL); 350 } 351 352 /* 353 * Construct a physical device pathname from EDD and verify the 354 * path exists. Return the index of in disk_list for the mapped 355 * path on success, -1 on failure. 356 */ 357 static int 358 match_edd(biosdev_data_t *bdata) 359 { 360 di_node_t node, cnode = DI_NODE_NIL; 361 char *devfspath = NULL; 362 fn48_t *bd; 363 int index; 364 char busaddrbuf[MAXNAMELEN]; 365 366 if (!bdata->edd_valid) { 367 if (debug) 368 (void) printf("edd not valid\n"); 369 return (-1); 370 } 371 372 bd = &bdata->fn48_dev_params; 373 374 if (bd->magic != 0xBEDD || bd->pathinfo_len == 0) { 375 /* EDD extensions for devicepath not present */ 376 if (debug) 377 (void) printf("magic not valid %x pathinfolen %d\n", 378 bd->magic, bd->pathinfo_len); 379 return (-1); 380 } 381 382 /* we handle only PCI scsi, ata or sata for now */ 383 if (strncmp(bd->bustype, "PCI", 3) != 0) { 384 if (debug) 385 (void) printf("was not pci %s\n", bd->bustype); 386 return (-1); 387 } 388 if (debug) 389 (void) printf("match_edd bdf %d %d %d\n", 390 bd->interfacepath.pci.bus, 391 bd->interfacepath.pci.device, 392 bd->interfacepath.pci.function); 393 394 /* look into devinfo tree and find a node with matching pci b/d/f */ 395 node = search_tree_match_pcibdf(root_node, bd->interfacepath.pci.bus, 396 bd->interfacepath.pci.device, bd->interfacepath.pci.function); 397 398 if (node == DI_NODE_NIL) { 399 if (debug) 400 (void) printf(" could not find a node in tree " 401 "matching bdf\n"); 402 return (-1); 403 } 404 405 if (debug) { 406 int i; 407 (void) printf("interface type "); 408 for (i = 0; i < 8; i++) 409 (void) printf("%c", bd->interface_type[i]); 410 (void) printf(" pci channel %x target %x\n", 411 bd->interfacepath.pci.channel, 412 bd->devicepath.scsi.target); 413 } 414 415 if (strncmp(bd->interface_type, "SCSI", 4) == 0) { 416 417 (void) snprintf(busaddrbuf, MAXNAMELEN, "%x,%x", 418 bd->devicepath.scsi.target, bd->devicepath.scsi.lun_lo); 419 420 cnode = search_children_match_busaddr(node, busaddrbuf); 421 422 } else if ((strncmp(bd->interface_type, "ATAPI", 5) == 0) || 423 (strncmp(bd->interface_type, "ATA", 3) == 0) || 424 (strncmp(bd->interface_type, "SATA", 4) == 0)) { 425 426 if (strncmp(di_node_name(node), "pci-ide", 7) == 0) { 427 /* 428 * Legacy using pci-ide 429 * the child should be ide@<x>, where x is 430 * the channel number 431 */ 432 (void) snprintf(busaddrbuf, MAXNAMELEN, "%d", 433 bd->interfacepath.pci.channel); 434 435 if ((cnode = search_children_match_busaddr(node, 436 busaddrbuf)) != DI_NODE_NIL) { 437 438 (void) snprintf(busaddrbuf, MAXNAMELEN, "%x,0", 439 bd->devicepath.ata.chan); 440 cnode = search_children_match_busaddr(cnode, 441 busaddrbuf); 442 443 if (cnode == DI_NODE_NIL) 444 if (debug) 445 (void) printf("Interface %s " 446 "using pci-ide no " 447 "grandchild at %s\n", 448 bd->interface_type, 449 busaddrbuf); 450 } else { 451 if (debug) 452 (void) printf("Interface %s using " 453 "pci-ide, with no child at %s\n", 454 bd->interface_type, busaddrbuf); 455 } 456 } else { 457 if (strncmp(bd->interface_type, "SATA", 4) == 0) { 458 /* 459 * The current EDD (EDD-2) spec does not 460 * address port number. This is work in 461 * progress. 462 * Interprete the first field of device path 463 * as port number. Needs to be revisited 464 * with port multiplier support. 465 */ 466 (void) snprintf(busaddrbuf, MAXNAMELEN, "%x,0", 467 bd->devicepath.ata.chan); 468 469 cnode = search_children_match_busaddr(node, 470 busaddrbuf); 471 } else { 472 if (debug) 473 (void) printf("Interface %s, not using" 474 " pci-ide\n", bd->interface_type); 475 } 476 } 477 478 } else if (strncmp(bd->interface_type, "USB", 3) == 0) { 479 cnode = search_tree_match_usbserialno(node, 480 bd->devicepath.usb.usb_serial_id); 481 } else { 482 if (debug) 483 (void) printf("sorry not supported interface %s\n", 484 bd->interface_type); 485 } 486 487 if (cnode != DI_NODE_NIL) { 488 devfspath = di_devfs_path(cnode); 489 index = search_disklist_match_path(devfspath); 490 di_devfs_path_free(devfspath); 491 if (index >= 0) 492 return (index); 493 } 494 495 return (-1); 496 } 497 498 /* 499 * For each disk in list of disks, compare the first block with the 500 * one from bdd. On the first match, return the index of path in 501 * disk_list. If none matched return -1. 502 */ 503 static int 504 match_first_block(biosdev_data_t *bd) 505 { 506 507 char diskpath[MAXPATHLEN]; 508 int fd; 509 char buf[512]; 510 ssize_t num_read; 511 int i; 512 513 if (!bd->first_block_valid) 514 return (-1); 515 516 for (i = 0; i < disk_list_valid; i++) { 517 (void) snprintf(diskpath, MAXPATHLEN, "%s/%s:q,raw", 518 DEVFS_PREFIX, disk_list[i]); 519 fd = open(diskpath, O_RDONLY); 520 if (fd < 0) { 521 (void) fprintf(stderr, "opening %s failed errno %d\n", 522 diskpath, errno); 523 continue; 524 } 525 num_read = read(fd, buf, 512); 526 if (num_read != 512) { 527 (void) printf("read only %d bytes from %s\n", num_read, 528 diskpath); 529 continue; 530 } 531 532 if (memcmp(buf, bd->first_block, 512) == 0) { 533 /* found it */ 534 return (i); 535 } 536 } 537 return (-1); 538 } 539 540 541 static void 542 cleanup_and_exit(int exitcode) 543 { 544 545 free_disks(); 546 547 if (root_node != DI_NODE_NIL) 548 di_fini(root_node); 549 550 if (root_allnode != DI_NODE_NIL) 551 di_fini(root_allnode); 552 553 if (prom_hdl != DI_PROM_HANDLE_NIL) 554 di_prom_fini(prom_hdl); 555 exit(exitcode); 556 557 } 558 559 560 int 561 main(int argc, char *argv[]) 562 { 563 biosdev_data_t *biosdata; 564 int i, c, j; 565 int matchedindex = -1; 566 char biospropname[BIOSPROPNAME_TMPL_LEN]; 567 int totalmatches = 0; 568 biosdev_data_t *biosdataarray[BIOSDEV_NUM]; 569 570 571 while ((c = getopt(argc, argv, "d")) != -1) { 572 switch (c) { 573 case 'd': 574 debug = 1; 575 break; 576 default: 577 (void) printf("unknown option %c\n", c); 578 exit(1); 579 } 580 } 581 582 if ((prom_hdl = di_prom_init()) == DI_PROM_HANDLE_NIL) { 583 (void) fprintf(stderr, "di_prom_init failed\n"); 584 cleanup_and_exit(1); 585 } 586 587 if ((root_node = di_init("/", DINFOCACHE)) == DI_NODE_NIL) { 588 (void) fprintf(stderr, "di_init failed\n"); 589 cleanup_and_exit(1); 590 } 591 592 if ((root_allnode = di_init("/", DINFOCPYALL)) == DI_NODE_NIL) { 593 (void) fprintf(stderr, "di_init failed\n"); 594 cleanup_and_exit(1); 595 } 596 597 (void) memset(mapinfo, 0, sizeof (mapinfo)); 598 599 /* get a list of all disks in the system */ 600 build_disk_list(); 601 602 /* Get property values that were created at boot up time */ 603 for (i = 0; i < BIOSDEV_NUM; i++) { 604 605 (void) snprintf((char *)biospropname, BIOSPROPNAME_TMPL_LEN, 606 BIOSPROPNAME_TMPL, i + STARTING_DRVNUM); 607 if (di_prop_lookup_bytes(DDI_DEV_T_ANY, root_allnode, 608 biospropname, (uchar_t **)&biosdataarray[i]) <= 0) 609 biosdataarray[i] = NULL; 610 } 611 612 /* Try to match based on device/interface path info from BIOS */ 613 for (i = 0; i < BIOSDEV_NUM; i++) { 614 615 if ((biosdata = biosdataarray[i]) == NULL) 616 continue; 617 if (debug) 618 (void) printf("matching edd 0x%x\n", 619 i + STARTING_DRVNUM); 620 621 matchedindex = match_edd(biosdata); 622 623 if (matchedindex != -1) { 624 if (debug) { 625 (void) printf("matched by edd\n"); 626 (void) printf("0x%x %s\n", i + STARTING_DRVNUM, 627 disk_list[matchedindex]); 628 } 629 630 mapinfo[i].disklist_index = matchedindex; 631 mapinfo[i].matchcount++; 632 633 for (j = 0; j < i; j++) { 634 if (mapinfo[j].matchcount > 0 && 635 mapinfo[j].disklist_index == matchedindex) { 636 mapinfo[j].matchcount++; 637 mapinfo[i].matchcount++; 638 } 639 } 640 641 } else 642 if (debug) 643 (void) printf("No matches by edd\n"); 644 } 645 646 /* 647 * Go through the list and ignore any found matches that are dups. 648 * This is to workaround issues with BIOSes that do not implement 649 * providing interface/device path info correctly. 650 */ 651 652 for (i = 0; i < BIOSDEV_NUM; i++) { 653 if (mapinfo[i].matchcount > 1) { 654 if (debug) 655 (void) printf("Ignoring dup match_edd\n(count " 656 "%d): 0x%x %s\n", mapinfo[i].matchcount, 657 i + STARTING_DRVNUM, 658 disk_list[mapinfo[i].disklist_index]); 659 660 mapinfo[i].matchcount = 0; 661 mapinfo[i].disklist_index = 0; 662 } 663 } 664 665 666 /* 667 * For each bios dev number that we do not have exactly one match 668 * already, try to match based on first block 669 */ 670 for (i = 0; i < BIOSDEV_NUM; i++) { 671 if (mapinfo[i].matchcount == 1) 672 continue; 673 674 if ((biosdata = biosdataarray[i]) == NULL) 675 continue; 676 677 if (debug) 678 (void) printf("matching first block 0x%x\n", 679 i + STARTING_DRVNUM); 680 681 matchedindex = match_first_block(biosdata); 682 if (matchedindex != -1) { 683 if (debug) { 684 (void) printf("matched by first block\n"); 685 (void) printf("0x%x %s\n", i + STARTING_DRVNUM, 686 disk_list[matchedindex]); 687 } 688 689 mapinfo[i].disklist_index = matchedindex; 690 mapinfo[i].matchcount++; 691 692 for (j = 0; j < i; j++) { 693 if (mapinfo[j].matchcount > 0 && 694 mapinfo[j].disklist_index == matchedindex) { 695 mapinfo[j].matchcount++; 696 mapinfo[i].matchcount++; 697 } 698 } 699 } else 700 if (debug) { 701 (void) printf(" No matches by first block\n"); 702 (void) fprintf(stderr, "Could not match 0x%x\n", 703 i + STARTING_DRVNUM); 704 } 705 } 706 707 708 for (i = 0; i < BIOSDEV_NUM; i++) { 709 if (mapinfo[i].matchcount == 1) { 710 (void) printf("0x%x %s\n", i + STARTING_DRVNUM, 711 disk_list[mapinfo[i].disklist_index]); 712 totalmatches++; 713 } else if (debug && mapinfo[i].matchcount > 1) { 714 (void) printf("0x%x %s matchcount %d\n", 715 i + STARTING_DRVNUM, 716 disk_list[mapinfo[i].disklist_index], 717 mapinfo[i].matchcount); 718 } 719 } 720 721 if (totalmatches == 0) { 722 (void) fprintf(stderr, "biosdev: Could not match any!!\n"); 723 cleanup_and_exit(1); 724 } 725 726 cleanup_and_exit(0); 727 /* NOTREACHED */ 728 return (0); 729 } 730