xref: /illumos-gate/usr/src/cmd/biosdev/biosdev.c (revision 628e3cbed6489fa1db545d8524a06cd6535af456)
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 **)&regbuf);
233 
234 	if (len <= 0) {
235 		/* Try PROM property */
236 		len = di_prom_prop_lookup_ints(prom_hdl, node, "reg",
237 		    (int **)&regbuf);
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