8987 bootadm: add bootfile fallback to unix
[unleashed.git] / usr / src / cmd / biosdev / biosdev.c
blobe12e444a536824d0cf2d73cc8f1f83f609821ae6
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
26 #pragma ident "%Z%%M% %I% %E% SMI"
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>
45 * structure used for searching device tree for a node matching
46 * pci bus/dev/fn
48 typedef struct pcibdf {
49 int busnum;
50 int devnum;
51 int funcnum;
52 di_node_t di_node;
53 } pcibdf_t;
56 * structure used for searching device tree for a node matching
57 * USB serial number.
59 typedef struct {
60 uint64_t serialno;
61 di_node_t node;
62 } usbser_t;
65 * structure for holding the mapping info
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;
72 #define DEVFS_PREFIX "/devices"
73 #define DISKS_LIST_INCR 20 /* increment for resizing disk_list */
75 #define BIOSPROPNAME_TMPL "biosdev-0x%x"
76 #define BIOSPROPNAME_TMPL_LEN 13
77 #define BIOSDEV_NUM 8
78 #define STARTING_DRVNUM 0x80
81 * array to hold mappings. Element at index X corresponds to BIOS device
82 * number 0x80 + X
84 static mapinfo_t mapinfo[BIOSDEV_NUM];
87 * Cache copy of kernel device tree snapshot root handle, includes devices
88 * that are detached
90 static di_node_t root_node = DI_NODE_NIL;
93 * kernel device tree snapshot with currently attached devices. Detached
94 * devices are not included.
96 static di_node_t root_allnode = DI_NODE_NIL;
99 * handle to retrieve prom properties
102 static di_prom_handle_t prom_hdl = DI_PROM_HANDLE_NIL;
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 */
108 static int debug = 0; /* used for enabling debug output */
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);
119 static int match_edd(biosdev_data_t *bd);
120 static int match_first_block(biosdev_data_t *bd);
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);
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);
130 static di_node_t search_children_match_busaddr(di_node_t node,
131 char *matchbusaddr);
135 static void
136 new_disk_list_entry(di_node_t node)
138 size_t newsize;
139 char **newlist;
140 int newlen;
141 char *devfspath;
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);
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);
154 disk_list = newlist;
155 disk_list_len = newlen;
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++;
165 /* ARGSUSED */
166 static int
167 i_disktype(di_node_t node, di_minor_t minor, void *arg)
169 char *minortype;
171 if (di_minor_spectype(minor) == S_IFCHR) {
172 minortype = di_minor_nodetype(minor);
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);
180 return (DI_WALK_CONTINUE);
183 static void
184 build_disk_list()
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);
196 static void
197 free_disks()
199 int i;
201 if (disk_list) {
202 for (i = 0; i < disk_list_valid; i++)
203 di_devfs_path_free(disk_list[i]);
205 free(disk_list);
209 static int
210 i_match_pcibdf(di_node_t node, void *arg)
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;
220 pbp = (pcibdf_t *)arg;
222 parentnode = di_parent_node(node);
224 len = di_prop_lookup_strings(DDI_DEV_T_ANY, parentnode,
225 "device_type", (char **)&devtype);
227 if ((len <= 0) ||
228 ((strcmp(devtype, "pci") != 0) && (strcmp(devtype, "pciex") != 0)))
229 return (DI_WALK_CONTINUE);
231 len = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "reg",
232 (int **)&regbuf);
234 if (len <= 0) {
235 /* Try PROM property */
236 len = di_prom_prop_lookup_ints(prom_hdl, node, "reg",
237 (int **)&regbuf);
241 if (len > 0) {
242 regval = regbuf[0];
244 busnum = PCI_REG_BUS_G(regval);
245 devicenum = PCI_REG_DEV_G(regval);
246 funcnum = PCI_REG_FUNC_G(regval);
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);
257 return (DI_WALK_CONTINUE);
260 static di_node_t
261 search_tree_match_pcibdf(di_node_t node, int bus, int dev, int fn)
263 pcibdf_t pb;
264 pb.busnum = bus;
265 pb.devnum = dev;
266 pb.funcnum = fn;
267 pb.di_node = DI_NODE_NIL;
269 (void) di_walk_node(node, DI_WALK_CLDFIRST, &pb, i_match_pcibdf);
270 return (pb.di_node);
274 static int
275 i_match_usbserialno(di_node_t node, void *arg)
277 int len;
278 char *serialp;
279 usbser_t *usbsp;
281 usbsp = (usbser_t *)arg;
283 len = di_prop_lookup_bytes(DDI_DEV_T_ANY, node, "usb-serialno",
284 (uchar_t **)&serialp);
286 if ((len > 0) && (strncmp((char *)&usbsp->serialno, serialp,
287 sizeof (uint64_t)) == 0)) {
288 usbsp->node = node;
289 return (DI_WALK_TERMINATE);
291 return (DI_WALK_CONTINUE);
294 static di_node_t
295 search_tree_match_usbserialno(di_node_t node, uint64_t serialno)
298 usbser_t usbs;
300 usbs.serialno = serialno;
301 usbs.node = DI_NODE_NIL;
303 (void) di_walk_node(node, DI_WALK_CLDFIRST, &usbs, i_match_usbserialno);
304 return (usbs.node);
308 * returns the index to the disklist to the disk with matching path
310 static int
311 search_disklist_match_path(char *path)
313 int i;
314 for (i = 0; i < disk_list_valid; i++)
315 if (strcmp(disk_list[i], path) == 0) {
316 return (i);
318 return (-1);
322 * Find first child of 'node' whose unit address is 'matchbusaddr'
324 static di_node_t
325 search_children_match_busaddr(di_node_t node, char *matchbusaddr)
327 di_node_t cnode;
328 char *busaddr;
329 di_path_t pi = DI_PATH_NIL;
331 if (matchbusaddr == NULL)
332 return (DI_NODE_NIL);
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));
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);
351 return (DI_NODE_NIL);
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.
359 static int
360 match_edd(biosdev_data_t *bdata)
362 di_node_t node, cnode = DI_NODE_NIL;
363 char *devfspath = NULL;
364 fn48_t *bd;
365 int index;
366 char busaddrbuf[MAXNAMELEN];
368 if (!bdata->edd_valid) {
369 if (debug)
370 (void) printf("edd not valid\n");
371 return (-1);
374 bd = &bdata->fn48_dev_params;
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);
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);
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);
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);
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);
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);
417 if (strncmp(bd->interface_type, "SCSI", 4) == 0) {
419 (void) snprintf(busaddrbuf, MAXNAMELEN, "%x,%x",
420 bd->devicepath.scsi.target, bd->devicepath.scsi.lun_lo);
422 cnode = search_children_match_busaddr(node, busaddrbuf);
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)) {
428 if (strncmp(di_node_name(node), "pci-ide", 7) == 0) {
430 * Legacy using pci-ide
431 * the child should be ide@<x>, where x is
432 * the channel number
434 (void) snprintf(busaddrbuf, MAXNAMELEN, "%d",
435 bd->interfacepath.pci.channel);
437 if ((cnode = search_children_match_busaddr(node,
438 busaddrbuf)) != DI_NODE_NIL) {
440 (void) snprintf(busaddrbuf, MAXNAMELEN, "%x,0",
441 bd->devicepath.ata.chan);
442 cnode = search_children_match_busaddr(cnode,
443 busaddrbuf);
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);
458 } else {
459 if (strncmp(bd->interface_type, "SATA", 4) == 0) {
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.
468 (void) snprintf(busaddrbuf, MAXNAMELEN, "%x,0",
469 bd->devicepath.ata.chan);
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);
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);
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);
497 return (-1);
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.
505 static int
506 match_first_block(biosdev_data_t *bd)
509 char diskpath[MAXPATHLEN];
510 int fd;
511 char buf[512];
512 ssize_t num_read;
513 int i;
515 if (!bd->first_block_valid)
516 return (-1);
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;
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;
534 if (memcmp(buf, bd->first_block, 512) == 0) {
535 /* found it */
536 return (i);
539 return (-1);
543 static void
544 cleanup_and_exit(int exitcode)
547 free_disks();
549 if (root_node != DI_NODE_NIL)
550 di_fini(root_node);
552 if (root_allnode != DI_NODE_NIL)
553 di_fini(root_allnode);
555 if (prom_hdl != DI_PROM_HANDLE_NIL)
556 di_prom_fini(prom_hdl);
557 exit(exitcode);
563 main(int argc, char *argv[])
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];
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);
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);
589 if ((root_node = di_init("/", DINFOCACHE)) == DI_NODE_NIL) {
590 (void) fprintf(stderr, "di_init failed\n");
591 cleanup_and_exit(1);
594 if ((root_allnode = di_init("/", DINFOCPYALL)) == DI_NODE_NIL) {
595 (void) fprintf(stderr, "di_init failed\n");
596 cleanup_and_exit(1);
599 (void) memset(mapinfo, 0, sizeof (mapinfo));
601 /* get a list of all disks in the system */
602 build_disk_list();
604 /* Get property values that were created at boot up time */
605 for (i = 0; i < BIOSDEV_NUM; i++) {
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;
614 /* Try to match based on device/interface path info from BIOS */
615 for (i = 0; i < BIOSDEV_NUM; i++) {
617 if ((biosdata = biosdataarray[i]) == NULL)
618 continue;
619 if (debug)
620 (void) printf("matching edd 0x%x\n",
621 i + STARTING_DRVNUM);
623 matchedindex = match_edd(biosdata);
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]);
632 mapinfo[i].disklist_index = matchedindex;
633 mapinfo[i].matchcount++;
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++;
643 } else
644 if (debug)
645 (void) printf("No matches by edd\n");
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.
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]);
662 mapinfo[i].matchcount = 0;
663 mapinfo[i].disklist_index = 0;
669 * For each bios dev number that we do not have exactly one match
670 * already, try to match based on first block
672 for (i = 0; i < BIOSDEV_NUM; i++) {
673 if (mapinfo[i].matchcount == 1)
674 continue;
676 if ((biosdata = biosdataarray[i]) == NULL)
677 continue;
679 if (debug)
680 (void) printf("matching first block 0x%x\n",
681 i + STARTING_DRVNUM);
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]);
691 mapinfo[i].disklist_index = matchedindex;
692 mapinfo[i].matchcount++;
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++;
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);
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);
723 if (totalmatches == 0) {
724 (void) fprintf(stderr, "biosdev: Could not match any!!\n");
725 cleanup_and_exit(1);
728 cleanup_and_exit(0);
729 /* NOTREACHED */
730 return (0);