xref: /illumos-gate/usr/src/cmd/biosdev/biosdev.c (revision 55381082)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <stdio.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <sys/param.h>
34 #include <errno.h>
35 #include <sys/types.h>
36 #include <dirent.h>
37 #include <libdevinfo.h>
38 #include <fcntl.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <sys/pci.h>
42 #include <sys/biosdisk.h>
43 
44 
45 /*
46  * structure used for searching device tree for a node matching
47  * pci bus/dev/fn
48  */
49 typedef struct pcibdf {
50 	int busnum;
51 	int devnum;
52 	int funcnum;
53 	di_node_t	di_node;
54 } pcibdf_t;
55 
56 /*
57  * structure used for searching device tree for a node matching
58  * USB serial number.
59  */
60 typedef struct {
61 	uint64_t serialno;
62 	di_node_t	node;
63 } usbser_t;
64 
65 /*
66  * structure for holding the mapping info
67  */
68 typedef struct {
69 	int disklist_index;	/* index to disk_list of the mapped path */
70 	int matchcount;		/* number of matches per this device number */
71 } mapinfo_t;
72 
73 #define	DEVFS_PREFIX "/devices"
74 #define	DISKS_LIST_INCR		20	/* increment for resizing disk_list */
75 
76 #define	BIOSPROPNAME_TMPL	"biosdev-0x%x"
77 #define	BIOSPROPNAME_TMPL_LEN	13
78 #define	BIOSDEV_NUM		8
79 #define	STARTING_DRVNUM		0x80
80 
81 /*
82  * array to hold mappings. Element at index X corresponds to BIOS device
83  * number 0x80 + X
84  */
85 static mapinfo_t mapinfo[BIOSDEV_NUM];
86 
87 /*
88  * Cache copy of kernel device tree snapshot root handle, includes devices
89  * that are detached
90  */
91 static di_node_t root_node = DI_NODE_NIL;
92 
93 /*
94  * kernel device tree snapshot with currently attached devices. Detached
95  * devices are not included.
96  */
97 static di_node_t root_allnode = DI_NODE_NIL;
98 
99 /*
100  * handle to retrieve prom properties
101  */
102 
103 static di_prom_handle_t prom_hdl = DI_PROM_HANDLE_NIL;
104 
105 static char **disk_list = NULL;	/* array of physical device pathnames */
106 static int disk_list_len = 0;		/* length of disk_list */
107 static int disk_list_valid = 0;	/* number of valid entries in disk_list */
108 
109 static int debug = 0;			/* used for enabling debug output */
110 
111 
112 /* Local function prototypes */
113 static void new_disk_list_entry(di_node_t node);
114 static int find_disks_callback(di_node_t node, di_minor_t minor, void *arg);
115 static void build_disk_list();
116 static void free_disks();
117 static int matchpcibdf(di_node_t node, void *arg);
118 static int matchusbserial(di_node_t node, void *arg);
119 static di_node_t search_tree_forpcibdf(int bus, int dev, int fn);
120 static int match_edd(biosdev_data_t *bd);
121 static int match_first_block(biosdev_data_t *bd);
122 static void cleanup_and_exit(int);
123 static int find_path_index_in_disk_list(char *path);
124 
125 
126 static void
127 new_disk_list_entry(di_node_t node)
128 {
129 	size_t	newsize;
130 	char **newlist;
131 	int newlen;
132 	char *devfspath;
133 
134 	if (disk_list_valid >= disk_list_len)	{
135 		/* valid should never really be larger than len */
136 		/* if they are equal we need to init or realloc */
137 		newlen = disk_list_len + DISKS_LIST_INCR;
138 		newsize = newlen * sizeof (*disk_list);
139 
140 		newlist = (char **)realloc(disk_list, newsize);
141 		if (newlist == NULL) {
142 			(void) printf("realloc failed to resize disk table\n");
143 			cleanup_and_exit(1);
144 		}
145 		disk_list = newlist;
146 		disk_list_len = newlen;
147 	}
148 
149 	devfspath = di_devfs_path(node);
150 	disk_list[disk_list_valid] = devfspath;
151 	if (debug)
152 		(void) printf("adding %s\n", devfspath);
153 	disk_list_valid++;
154 }
155 
156 /* ARGSUSED */
157 static int
158 find_disks_callback(di_node_t node, di_minor_t minor, void *arg)
159 {
160 	char *minortype;
161 
162 	if (di_minor_spectype(minor) == S_IFCHR) {
163 		minortype = di_minor_nodetype(minor);
164 
165 		/* exclude CD's */
166 		if (strncmp(minortype, DDI_NT_CD, sizeof (DDI_NT_CD) - 1) != 0)
167 			/* only take p0 raw device */
168 			if (strcmp(di_minor_name(minor), "q,raw") == 0)
169 				new_disk_list_entry(node);
170 	}
171 	return (DI_WALK_CONTINUE);
172 }
173 
174 static void
175 build_disk_list()
176 {
177 	int ret;
178 	ret = di_walk_minor(root_node, DDI_NT_BLOCK, 0, NULL,
179 	    find_disks_callback);
180 	if (ret != 0) {
181 		(void) fprintf(stderr, "di_walk_minor failed errno %d\n",
182 		    errno);
183 		cleanup_and_exit(1);
184 	}
185 }
186 
187 static void
188 free_disks()
189 {
190 	int i;
191 
192 	if (disk_list) {
193 		for (i = 0; i < disk_list_valid; i++)
194 			di_devfs_path_free(disk_list[i]);
195 
196 		free(disk_list);
197 	}
198 }
199 
200 static int
201 matchpcibdf(di_node_t node, void *arg)
202 {
203 	pcibdf_t *pbp;
204 	int len;
205 	uint32_t	regval;
206 	uint32_t	busnum, funcnum, devicenum;
207 	char *devtype;
208 	uint32_t *regbuf = NULL;
209 	di_node_t	parentnode;
210 
211 	pbp = (pcibdf_t *)arg;
212 
213 	parentnode = di_parent_node(node);
214 
215 	len = di_prop_lookup_strings(DDI_DEV_T_ANY, parentnode,
216 	    "device_type", (char **)&devtype);
217 
218 	if ((len <= 0) ||
219 	    ((strcmp(devtype, "pci") != 0) && (strcmp(devtype, "pciex") != 0)))
220 		return (DI_WALK_CONTINUE);
221 
222 	len = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "reg",
223 	    (int **)&regbuf);
224 
225 	if (len <= 0) {
226 		/* Try PROM property */
227 		len = di_prom_prop_lookup_ints(prom_hdl, node, "reg",
228 		    (int **)&regbuf);
229 	}
230 
231 
232 	if (len > 0) {
233 		regval = regbuf[0];
234 
235 		busnum = PCI_REG_BUS_G(regval);
236 		devicenum = PCI_REG_DEV_G(regval);
237 		funcnum = PCI_REG_FUNC_G(regval);
238 
239 		if ((busnum == pbp->busnum) &&
240 		    (devicenum == pbp->devnum) &&
241 		    (funcnum == pbp->funcnum)) {
242 			/* found it */
243 			pbp->di_node = node;
244 			return (DI_WALK_TERMINATE);
245 		}
246 	}
247 
248 	return (DI_WALK_CONTINUE);
249 }
250 
251 static di_node_t
252 search_tree_forpcibdf(int bus, int dev, int fn)
253 {
254 	pcibdf_t pb;
255 	pb.busnum = bus;
256 	pb.devnum = dev;
257 	pb.funcnum = fn;
258 	pb.di_node = DI_NODE_NIL;
259 
260 	(void) di_walk_node(root_node, DI_WALK_CLDFIRST, &pb, matchpcibdf);
261 	return (pb.di_node);
262 
263 }
264 
265 static int
266 matchusbserial(di_node_t node, void *arg)
267 {
268 	int len;
269 	char *serialp;
270 	usbser_t *usbsp;
271 
272 	usbsp = (usbser_t *)arg;
273 
274 	len = di_prop_lookup_bytes(DDI_DEV_T_ANY, node, "usb-serialno",
275 		(uchar_t **)&serialp);
276 
277 	if ((len > 0) && (strcmp((char *)&usbsp->serialno, serialp) == 0)) {
278 		usbsp->node = node;
279 		return (DI_WALK_TERMINATE);
280 	}
281 	return (DI_WALK_CONTINUE);
282 }
283 
284 static di_node_t
285 match_usb(di_node_t node, uint64_t serialno)
286 {
287 
288 	usbser_t usbs;
289 
290 	usbs.serialno = serialno;
291 	usbs.node = DI_NODE_NIL;
292 
293 	(void) di_walk_node(node, DI_WALK_CLDFIRST, &usbs, matchusbserial);
294 	return (usbs.node);
295 }
296 
297 
298 static int
299 find_path_index_in_disk_list(char *path)
300 {
301 	int i;
302 	for (i = 0; i < disk_list_valid; i++)
303 		if (strcmp(disk_list[i], path) == 0) {
304 			return (i);
305 		}
306 	return (-1);
307 }
308 
309 
310 /*
311  * Construct a physical device pathname from EDD and verify the
312  * path exists. Return the index of in disk_list for the mapped
313  * path on success, -1 on failure.
314  */
315 static int
316 match_edd(biosdev_data_t *bdata)
317 {
318 	di_node_t node;
319 	char *devfspath;
320 	fn48_t *bd;
321 	int index;
322 	char path[MAXPATHLEN];
323 
324 	if (!bdata->edd_valid) {
325 		if (debug)
326 			(void) printf("edd not valid\n");
327 		return (-1);
328 	}
329 
330 	bd = &bdata->fn48_dev_params;
331 
332 	if (bd->magic != 0xBEDD || bd->pathinfo_len == 0) {
333 		/* EDD extensions for devicepath not present */
334 		if (debug)
335 			(void) printf("magic not valid %x pathinfolen %d\n",
336 			    bd->magic, bd->pathinfo_len);
337 		return (-1);
338 	}
339 
340 	/* we handle only PCI scsi, ata or sata for now */
341 	if (strncmp(bd->bustype, "PCI", 3) != 0) {
342 		if (debug)
343 			(void) printf("was not pci %s\n", bd->bustype);
344 		return (-1);
345 	}
346 	if (debug)
347 		(void) printf("match_edd bdf %d %d %d\n",
348 			bd->interfacepath.pci.bus,
349 			bd->interfacepath.pci.device,
350 			bd->interfacepath.pci.function);
351 
352 	/* look into devinfo tree and find a node with matching pci b/d/f */
353 	node = search_tree_forpcibdf(bd->interfacepath.pci.bus,
354 		bd->interfacepath.pci.device,
355 		bd->interfacepath.pci.function);
356 
357 	if (node == DI_NODE_NIL) {
358 		if (debug)
359 			(void) printf(" could not find a node in tree "
360 			    "matching bdf\n");
361 		return (-1);
362 	}
363 
364 	/* construct a path */
365 	devfspath = di_devfs_path(node);
366 
367 	if (debug)
368 		(void) printf("interface type %s\n", bd->interface_type);
369 
370 	if (strncmp(bd->interface_type, "SCSI", 4) == 0) {
371 
372 		/* at this time sd doesnot support luns greater than uchar_t */
373 		(void) snprintf(path, MAXPATHLEN, "%s/sd@%d,%d", devfspath,
374 		    bd->devicepath.scsi.target, bd->devicepath.scsi.lun_lo);
375 
376 	} else if (strncmp(bd->interface_type, "ATAPI", 5) == 0) {
377 
378 		(void) snprintf(path, MAXPATHLEN, "%s/ide@%d/sd@%d,0",
379 		    devfspath, bd->interfacepath.pci.channel,
380 		    bd->devicepath.ata.chan);
381 
382 	} else if (strncmp(bd->interface_type, "ATA", 3) == 0) {
383 
384 		(void) snprintf(path, MAXPATHLEN, "%s/ide@%d/cmdk@%d,0",
385 		    devfspath, bd->interfacepath.pci.channel,
386 		    bd->devicepath.ata.chan);
387 
388 	} else if (strncmp(bd->interface_type, "SATA", 4) == 0) {
389 
390 		(void) snprintf(path, MAXPATHLEN, "%s/ide@%d/cmdk@%d,0",
391 		    devfspath, bd->interfacepath.pci.channel,
392 		    bd->devicepath.ata.chan);
393 
394 	} else if (strncmp(bd->interface_type, "USB", 3) == 0) {
395 		(void) printf("USB\n");
396 		node = match_usb(node, bd->devicepath.usb.usb_serial_id);
397 		if (node != DI_NODE_NIL) {
398 			if (debug)
399 				(void) printf("usb path %s\n",
400 				    di_devfs_path(node));
401 			(void) snprintf(path, MAXPATHLEN, "%s", devfspath);
402 		} else
403 			return (-1);
404 	} else {
405 		if (debug)
406 			(void) printf("sorry not supported interface %s\n",
407 		    bd->interface_type);
408 		return (-1);
409 	}
410 
411 	index = find_path_index_in_disk_list(path);
412 	if (index >= 0) {
413 		return (index);
414 	}
415 
416 	return (-1);
417 }
418 
419 /*
420  * For each disk in list of disks, compare the first block with the
421  * one from bdd. On the first match, return the index of path in
422  * disk_list. If none matched return -1.
423  */
424 static int
425 match_first_block(biosdev_data_t *bd)
426 {
427 
428 	char diskpath[MAXPATHLEN];
429 	int fd;
430 	char buf[512];
431 	ssize_t	num_read;
432 	int i;
433 
434 	if (!bd->first_block_valid)
435 		return (-1);
436 
437 	for (i = 0; i < disk_list_valid; i++) {
438 		(void) snprintf(diskpath, MAXPATHLEN, "%s/%s:q,raw",
439 		    DEVFS_PREFIX, disk_list[i]);
440 		fd = open(diskpath, O_RDONLY);
441 		if (fd  < 0) {
442 			(void) fprintf(stderr, "opening %s failed errno %d\n",
443 			    diskpath, errno);
444 			continue;
445 		}
446 		num_read = read(fd, buf, 512);
447 		if (num_read != 512) {
448 			(void) printf("read only %d bytes from %s\n", num_read,
449 			    diskpath);
450 			continue;
451 		}
452 
453 		if (memcmp(buf, bd->first_block, 512) == 0)	 {
454 			/* found it */
455 			return (i);
456 		}
457 	}
458 	return (-1);
459 }
460 
461 
462 static void
463 cleanup_and_exit(int exitcode)
464 {
465 
466 	free_disks();
467 
468 	if (root_node != DI_NODE_NIL)
469 		di_fini(root_node);
470 
471 	if (root_allnode != DI_NODE_NIL)
472 		di_fini(root_allnode);
473 
474 	if (prom_hdl != DI_PROM_HANDLE_NIL)
475 		di_prom_fini(prom_hdl);
476 	exit(exitcode);
477 
478 }
479 
480 
481 int
482 main(int argc, char *argv[])
483 {
484 	biosdev_data_t		*biosdata;
485 	int len, i, c, j;
486 	int matchedindex = -1;
487 	char biospropname[BIOSPROPNAME_TMPL_LEN];
488 	int totalmatches = 0;
489 
490 
491 	while ((c = getopt(argc, argv, "d")) != -1)  {
492 		switch (c) {
493 		case 'd':
494 			debug = 1;
495 			break;
496 		default:
497 			(void) printf("unknown option %c\n", c);
498 			exit(1);
499 		}
500 	}
501 
502 	if ((prom_hdl = di_prom_init()) == DI_PROM_HANDLE_NIL) {
503 		(void) fprintf(stderr, "di_prom_init failed\n");
504 		cleanup_and_exit(1);
505 	}
506 
507 	if ((root_node = di_init("/", DINFOCACHE)) == DI_NODE_NIL) {
508 		(void) fprintf(stderr, "di_init failed\n");
509 		cleanup_and_exit(1);
510 	}
511 
512 	if ((root_allnode = di_init("/", DINFOCPYALL)) == DI_NODE_NIL) {
513 		(void) fprintf(stderr, "di_init failed\n");
514 		cleanup_and_exit(1);
515 	}
516 
517 	(void) memset(mapinfo, 0, sizeof (mapinfo));
518 
519 	/* get a list of all disks in the system */
520 	build_disk_list();
521 
522 	/* for each property try to match with a disk */
523 	for (i = 0; i < BIOSDEV_NUM; i++) {
524 
525 		(void) snprintf((char *)biospropname, BIOSPROPNAME_TMPL_LEN,
526 		    BIOSPROPNAME_TMPL, i + STARTING_DRVNUM);
527 
528 		len = di_prop_lookup_bytes(DDI_DEV_T_ANY, root_allnode,
529 		    biospropname, (uchar_t **)&biosdata);
530 
531 		if (len <= 0) {
532 			continue;
533 		}
534 
535 		if (debug)
536 			(void) printf("matching %s\n", biospropname);
537 
538 		matchedindex = match_edd(biosdata);
539 
540 		if (matchedindex == -1) {
541 			matchedindex = match_first_block(biosdata);
542 			if (debug && matchedindex != -1)
543 				(void) printf("matched first block\n");
544 		} else if (debug)
545 			(void) printf("matched thru edd\n");
546 
547 		if (matchedindex != -1) {
548 			mapinfo[i].disklist_index = matchedindex;
549 			mapinfo[i].matchcount++;
550 			if (debug)
551 				(void) printf("0x%x %s\n", i + STARTING_DRVNUM,
552 				    disk_list[matchedindex]);
553 			for (j = 0; j < i; j++) {
554 				if (mapinfo[j].matchcount > 0 &&
555 				    mapinfo[j].disklist_index == matchedindex) {
556 					mapinfo[j].matchcount++;
557 					mapinfo[i].matchcount++;
558 				}
559 			}
560 
561 		} else if (debug)
562 			(void) fprintf(stderr, "Could not match %s\n",
563 			    biospropname);
564 	}
565 
566 	for (i = 0; i < BIOSDEV_NUM; i++) {
567 		if (mapinfo[i].matchcount == 1) {
568 			(void) printf("0x%x %s\n", i + STARTING_DRVNUM,
569 			    disk_list[mapinfo[i].disklist_index]);
570 			totalmatches++;
571 		} else if (debug && mapinfo[i].matchcount > 1) {
572 			(void) printf("0x%x %s matchcount %d\n",
573 			    i + STARTING_DRVNUM,
574 			    disk_list[mapinfo[i].disklist_index],
575 				mapinfo[i].matchcount);
576 		}
577 	}
578 
579 	if (totalmatches == 0) {
580 		(void) fprintf(stderr, "biosdev: Could not match any!!\n");
581 		cleanup_and_exit(1);
582 	}
583 
584 	cleanup_and_exit(0);
585 	/* NOTREACHED */
586 	return (0);
587 }
588