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