OpenSolaris_b135/cmd/biosdev/biosdev.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/param.h>
#include <errno.h>
#include <sys/types.h>
#include <dirent.h>
#include <libdevinfo.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/pci.h>
#include <sys/biosdisk.h>


/*
 * structure used for searching device tree for a node matching
 * pci bus/dev/fn
 */
typedef struct pcibdf {
	int busnum;
	int devnum;
	int funcnum;
	di_node_t	di_node;
} pcibdf_t;

/*
 * structure used for searching device tree for a node matching
 * USB serial number.
 */
typedef struct {
	uint64_t serialno;
	di_node_t	node;
} usbser_t;

/*
 * structure for holding the mapping info
 */
typedef struct {
	int disklist_index;	/* index to disk_list of the mapped path */
	int matchcount;		/* number of matches per this device number */
} mapinfo_t;

#define	DEVFS_PREFIX "/devices"
#define	DISKS_LIST_INCR		20	/* increment for resizing disk_list */

#define	BIOSPROPNAME_TMPL	"biosdev-0x%x"
#define	BIOSPROPNAME_TMPL_LEN	13
#define	BIOSDEV_NUM		8
#define	STARTING_DRVNUM		0x80

/*
 * array to hold mappings. Element at index X corresponds to BIOS device
 * number 0x80 + X
 */
static mapinfo_t mapinfo[BIOSDEV_NUM];

/*
 * Cache copy of kernel device tree snapshot root handle, includes devices
 * that are detached
 */
static di_node_t root_node = DI_NODE_NIL;

/*
 * kernel device tree snapshot with currently attached devices. Detached
 * devices are not included.
 */
static di_node_t root_allnode = DI_NODE_NIL;

/*
 * handle to retrieve prom properties
 */

static di_prom_handle_t prom_hdl = DI_PROM_HANDLE_NIL;

static char **disk_list = NULL;	/* array of physical device pathnames */
static int disk_list_len = 0;		/* length of disk_list */
static int disk_list_valid = 0;	/* number of valid entries in disk_list */

static int debug = 0;			/* used for enabling debug output */


/* Local function prototypes */
static void new_disk_list_entry(di_node_t node);
static int i_disktype(di_node_t node, di_minor_t minor, void *arg);
static void build_disk_list();
static int search_disklist_match_path(char *path);
static void free_disks();
static void cleanup_and_exit(int);

static int match_edd(biosdev_data_t *bd);
static int match_first_block(biosdev_data_t *bd);

static di_node_t search_tree_match_pcibdf(di_node_t node, int bus, int dev,
    int fn);
static int i_match_pcibdf(di_node_t node, void *arg);

static di_node_t search_tree_match_usbserialno(di_node_t node,
    uint64_t serialno);
static int i_match_usbserialno(di_node_t node, void *arg);

static di_node_t search_children_match_busaddr(di_node_t node,
    char *matchbusaddr);



static void
new_disk_list_entry(di_node_t node)
{
	size_t	newsize;
	char **newlist;
	int newlen;
	char *devfspath;

	if (disk_list_valid >= disk_list_len)	{
		/* valid should never really be larger than len */
		/* if they are equal we need to init or realloc */
		newlen = disk_list_len + DISKS_LIST_INCR;
		newsize = newlen * sizeof (*disk_list);

		newlist = (char **)realloc(disk_list, newsize);
		if (newlist == NULL) {
			(void) printf("realloc failed to resize disk table\n");
			cleanup_and_exit(1);
		}
		disk_list = newlist;
		disk_list_len = newlen;
	}

	devfspath = di_devfs_path(node);
	disk_list[disk_list_valid] = devfspath;
	if (debug)
		(void) printf("adding %s\n", devfspath);
	disk_list_valid++;
}

/* ARGSUSED */
static int
i_disktype(di_node_t node, di_minor_t minor, void *arg)
{
	char *minortype;

	if (di_minor_spectype(minor) == S_IFCHR) {
		minortype = di_minor_nodetype(minor);

		/* exclude CD's */
		if (strncmp(minortype, DDI_NT_CD, sizeof (DDI_NT_CD) - 1) != 0)
			/* only take p0 raw device */
			if (strcmp(di_minor_name(minor), "q,raw") == 0)
				new_disk_list_entry(node);
	}
	return (DI_WALK_CONTINUE);
}

static void
build_disk_list()
{
	int ret;
	ret = di_walk_minor(root_node, DDI_NT_BLOCK, 0, NULL,
	    i_disktype);
	if (ret != 0) {
		(void) fprintf(stderr, "di_walk_minor failed errno %d\n",
		    errno);
		cleanup_and_exit(1);
	}
}

static void
free_disks()
{
	int i;

	if (disk_list) {
		for (i = 0; i < disk_list_valid; i++)
			di_devfs_path_free(disk_list[i]);

		free(disk_list);
	}
}

static int
i_match_pcibdf(di_node_t node, void *arg)
{
	pcibdf_t *pbp;
	int len;
	uint32_t	regval;
	uint32_t	busnum, funcnum, devicenum;
	char *devtype;
	uint32_t *regbuf = NULL;
	di_node_t	parentnode;

	pbp = (pcibdf_t *)arg;

	parentnode = di_parent_node(node);

	len = di_prop_lookup_strings(DDI_DEV_T_ANY, parentnode,
	    "device_type", (char **)&devtype);

	if ((len <= 0) ||
	    ((strcmp(devtype, "pci") != 0) && (strcmp(devtype, "pciex") != 0)))
		return (DI_WALK_CONTINUE);

	len = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "reg",
	    (int **)&regbuf);

	if (len <= 0) {
		/* Try PROM property */
		len = di_prom_prop_lookup_ints(prom_hdl, node, "reg",
		    (int **)&regbuf);
	}


	if (len > 0) {
		regval = regbuf[0];

		busnum = PCI_REG_BUS_G(regval);
		devicenum = PCI_REG_DEV_G(regval);
		funcnum = PCI_REG_FUNC_G(regval);

		if ((busnum == pbp->busnum) &&
		    (devicenum == pbp->devnum) &&
		    (funcnum == pbp->funcnum)) {
			/* found it */
			pbp->di_node = node;
			return (DI_WALK_TERMINATE);
		}
	}

	return (DI_WALK_CONTINUE);
}

static di_node_t
search_tree_match_pcibdf(di_node_t node, int bus, int dev, int fn)
{
	pcibdf_t pb;
	pb.busnum = bus;
	pb.devnum = dev;
	pb.funcnum = fn;
	pb.di_node = DI_NODE_NIL;

	(void) di_walk_node(node, DI_WALK_CLDFIRST, &pb, i_match_pcibdf);
	return (pb.di_node);

}

static int
i_match_usbserialno(di_node_t node, void *arg)
{
	int len;
	char *serialp;
	usbser_t *usbsp;

	usbsp = (usbser_t *)arg;

	len = di_prop_lookup_bytes(DDI_DEV_T_ANY, node, "usb-serialno",
	    (uchar_t **)&serialp);

	if ((len > 0) && (strncmp((char *)&usbsp->serialno, serialp,
	    sizeof (uint64_t)) == 0)) {
		usbsp->node = node;
		return (DI_WALK_TERMINATE);
	}
	return (DI_WALK_CONTINUE);
}

static di_node_t
search_tree_match_usbserialno(di_node_t node, uint64_t serialno)
{

	usbser_t usbs;

	usbs.serialno = serialno;
	usbs.node = DI_NODE_NIL;

	(void) di_walk_node(node, DI_WALK_CLDFIRST, &usbs, i_match_usbserialno);
	return (usbs.node);
}

/*
 * returns the index to the disklist to the disk with matching path
 */
static int
search_disklist_match_path(char *path)
{
	int i;
	for (i = 0; i < disk_list_valid; i++)
		if (strcmp(disk_list[i], path) == 0) {
			return (i);
		}
	return (-1);
}

/*
 * Find first child of 'node' whose unit address is 'matchbusaddr'
 */
static di_node_t
search_children_match_busaddr(di_node_t node, char *matchbusaddr)
{
	di_node_t cnode;
	char *busaddr;
	di_path_t pi = DI_PATH_NIL;

	if (matchbusaddr == NULL)
		return (DI_NODE_NIL);

	while ((pi = di_path_phci_next_path(node, pi)) != DI_PATH_NIL) {
		busaddr = di_path_bus_addr(pi);
		if (busaddr == NULL)
			continue;
		if (strncmp(busaddr, matchbusaddr, MAXNAMELEN) == 0)
			return (di_path_client_node(pi));
	}

	for (cnode = di_child_node(node); cnode != DI_NODE_NIL;
	    cnode = di_sibling_node(cnode)) {
		busaddr = di_bus_addr(cnode);
		if (busaddr == NULL)
			continue;
		if (strncmp(busaddr, matchbusaddr, MAXNAMELEN) == 0)
			return (cnode);
	}

	return (DI_NODE_NIL);
}

/*
 * Construct a physical device pathname from EDD and verify the
 * path exists. Return the index of in disk_list for the mapped
 * path on success, -1 on failure.
 */
static int
match_edd(biosdev_data_t *bdata)
{
	di_node_t node, cnode = DI_NODE_NIL;
	char *devfspath = NULL;
	fn48_t *bd;
	int index;
	char busaddrbuf[MAXNAMELEN];

	if (!bdata->edd_valid) {
		if (debug)
			(void) printf("edd not valid\n");
		return (-1);
	}

	bd = &bdata->fn48_dev_params;

	if (bd->magic != 0xBEDD || bd->pathinfo_len == 0) {
		/* EDD extensions for devicepath not present */
		if (debug)
			(void) printf("magic not valid %x pathinfolen %d\n",
			    bd->magic, bd->pathinfo_len);
		return (-1);
	}

	/* we handle only PCI scsi, ata or sata for now */
	if (strncmp(bd->bustype, "PCI", 3) != 0) {
		if (debug)
			(void) printf("was not pci %s\n", bd->bustype);
		return (-1);
	}
	if (debug)
		(void) printf("match_edd bdf %d %d %d\n",
		    bd->interfacepath.pci.bus,
		    bd->interfacepath.pci.device,
		    bd->interfacepath.pci.function);

	/* look into devinfo tree and find a node with matching pci b/d/f */
	node = search_tree_match_pcibdf(root_node, bd->interfacepath.pci.bus,
	    bd->interfacepath.pci.device, bd->interfacepath.pci.function);

	if (node == DI_NODE_NIL) {
		if (debug)
			(void) printf(" could not find a node in tree "
			    "matching bdf\n");
		return (-1);
	}

	if (debug) {
		int i;
		(void) printf("interface type ");
		for (i = 0; i < 8; i++)
			(void) printf("%c", bd->interface_type[i]);
		(void) printf(" pci channel %x target %x\n",
		    bd->interfacepath.pci.channel,
		    bd->devicepath.scsi.target);
	}

	if (strncmp(bd->interface_type, "SCSI", 4) == 0) {

		(void) snprintf(busaddrbuf, MAXNAMELEN, "%x,%x",
		    bd->devicepath.scsi.target, bd->devicepath.scsi.lun_lo);

		cnode = search_children_match_busaddr(node, busaddrbuf);

	} else if ((strncmp(bd->interface_type, "ATAPI", 5) == 0) ||
	    (strncmp(bd->interface_type, "ATA", 3) == 0) ||
	    (strncmp(bd->interface_type, "SATA", 4) == 0)) {

		if (strncmp(di_node_name(node), "pci-ide", 7) == 0) {
			/*
			 * Legacy using pci-ide
			 * the child should be ide@<x>, where x is
			 * the channel number
			 */
			(void) snprintf(busaddrbuf, MAXNAMELEN, "%d",
			    bd->interfacepath.pci.channel);

			if ((cnode = search_children_match_busaddr(node,
			    busaddrbuf)) != DI_NODE_NIL) {

				(void) snprintf(busaddrbuf, MAXNAMELEN, "%x,0",
				    bd->devicepath.ata.chan);
				cnode = search_children_match_busaddr(cnode,
				    busaddrbuf);

				if (cnode == DI_NODE_NIL)
					if (debug)
						(void) printf("Interface %s "
						    "using pci-ide no "
						    "grandchild at %s\n",
						    bd->interface_type,
						    busaddrbuf);
			} else {
				if (debug)
					(void) printf("Interface %s using "
					    "pci-ide, with no child at %s\n",
					    bd->interface_type, busaddrbuf);
			}
		} else {
			if (strncmp(bd->interface_type, "SATA", 4) == 0) {
				/*
				 * The current EDD (EDD-2) spec does not
				 * address port number. This is work in
				 * progress.
				 * Interprete the first field of device path
				 * as port number. Needs to be revisited
				 * with port multiplier support.
				 */
				(void) snprintf(busaddrbuf, MAXNAMELEN, "%x,0",
				    bd->devicepath.ata.chan);

				cnode = search_children_match_busaddr(node,
				    busaddrbuf);
			} else {
				if (debug)
					(void) printf("Interface %s, not using"
					    " pci-ide\n", bd->interface_type);
			}
		}

	} else if (strncmp(bd->interface_type, "USB", 3) == 0) {
		cnode = search_tree_match_usbserialno(node,
		    bd->devicepath.usb.usb_serial_id);
	} else {
		if (debug)
			(void) printf("sorry not supported interface %s\n",
			    bd->interface_type);
	}

	if (cnode != DI_NODE_NIL) {
		devfspath = di_devfs_path(cnode);
		index = search_disklist_match_path(devfspath);
		di_devfs_path_free(devfspath);
		if (index >= 0)
			return (index);
	}

	return (-1);
}

/*
 * For each disk in list of disks, compare the first block with the
 * one from bdd. On the first match, return the index of path in
 * disk_list. If none matched return -1.
 */
static int
match_first_block(biosdev_data_t *bd)
{

	char diskpath[MAXPATHLEN];
	int fd;
	char buf[512];
	ssize_t	num_read;
	int i;

	if (!bd->first_block_valid)
		return (-1);

	for (i = 0; i < disk_list_valid; i++) {
		(void) snprintf(diskpath, MAXPATHLEN, "%s/%s:q,raw",
		    DEVFS_PREFIX, disk_list[i]);
		fd = open(diskpath, O_RDONLY);
		if (fd  < 0) {
			(void) fprintf(stderr, "opening %s failed errno %d\n",
			    diskpath, errno);
			continue;
		}
		num_read = read(fd, buf, 512);
		if (num_read != 512) {
			(void) printf("read only %d bytes from %s\n", num_read,
			    diskpath);
			continue;
		}

		if (memcmp(buf, bd->first_block, 512) == 0)	 {
			/* found it */
			return (i);
		}
	}
	return (-1);
}


static void
cleanup_and_exit(int exitcode)
{

	free_disks();

	if (root_node != DI_NODE_NIL)
		di_fini(root_node);

	if (root_allnode != DI_NODE_NIL)
		di_fini(root_allnode);

	if (prom_hdl != DI_PROM_HANDLE_NIL)
		di_prom_fini(prom_hdl);
	exit(exitcode);

}


int
main(int argc, char *argv[])
{
	biosdev_data_t		*biosdata;
	int i, c, j;
	int matchedindex = -1;
	char biospropname[BIOSPROPNAME_TMPL_LEN];
	int totalmatches = 0;
	biosdev_data_t *biosdataarray[BIOSDEV_NUM];


	while ((c = getopt(argc, argv, "d")) != -1)  {
		switch (c) {
		case 'd':
			debug = 1;
			break;
		default:
			(void) printf("unknown option %c\n", c);
			exit(1);
		}
	}

	if ((prom_hdl = di_prom_init()) == DI_PROM_HANDLE_NIL) {
		(void) fprintf(stderr, "di_prom_init failed\n");
		cleanup_and_exit(1);
	}

	if ((root_node = di_init("/", DINFOCACHE)) == DI_NODE_NIL) {
		(void) fprintf(stderr, "di_init failed\n");
		cleanup_and_exit(1);
	}

	if ((root_allnode = di_init("/", DINFOCPYALL)) == DI_NODE_NIL) {
		(void) fprintf(stderr, "di_init failed\n");
		cleanup_and_exit(1);
	}

	(void) memset(mapinfo, 0, sizeof (mapinfo));

	/* get a list of all disks in the system */
	build_disk_list();

	/*  Get property values that were created at boot up time */
	for (i = 0; i < BIOSDEV_NUM; i++) {

		(void) snprintf((char *)biospropname, BIOSPROPNAME_TMPL_LEN,
		    BIOSPROPNAME_TMPL, i + STARTING_DRVNUM);
		if (di_prop_lookup_bytes(DDI_DEV_T_ANY, root_allnode,
		    biospropname, (uchar_t **)&biosdataarray[i]) <= 0)
			biosdataarray[i] = NULL;
	}

	/* Try to match based on device/interface path info from BIOS */
	for (i = 0; i < BIOSDEV_NUM; i++) {

		if ((biosdata = biosdataarray[i]) == NULL)
			continue;
		if (debug)
			(void) printf("matching edd 0x%x\n",
			    i + STARTING_DRVNUM);

		matchedindex = match_edd(biosdata);

		if (matchedindex != -1) {
			if (debug) {
				(void) printf("matched by edd\n");
				(void) printf("0x%x %s\n", i + STARTING_DRVNUM,
				    disk_list[matchedindex]);
			}

			mapinfo[i].disklist_index = matchedindex;
			mapinfo[i].matchcount++;

			for (j = 0; j < i; j++) {
				if (mapinfo[j].matchcount > 0 &&
				    mapinfo[j].disklist_index == matchedindex) {
					mapinfo[j].matchcount++;
					mapinfo[i].matchcount++;
				}
			}

		} else
			if (debug)
				(void) printf("No matches by edd\n");
	}

	/*
	 * Go through the list and ignore any found matches that are dups.
	 * This is to workaround issues with BIOSes that do not implement
	 * providing interface/device path info correctly.
	 */

	for (i = 0; i < BIOSDEV_NUM; i++) {
		if (mapinfo[i].matchcount > 1) {
			if (debug)
				(void) printf("Ignoring dup match_edd\n(count "
				    "%d): 0x%x %s\n", mapinfo[i].matchcount,
				    i + STARTING_DRVNUM,
				    disk_list[mapinfo[i].disklist_index]);

			mapinfo[i].matchcount = 0;
			mapinfo[i].disklist_index = 0;
		}
	}


	/*
	 * For each bios dev number that we do not have exactly one match
	 * already, try to match based on first block
	 */
	for (i = 0; i < BIOSDEV_NUM; i++) {
		if (mapinfo[i].matchcount == 1)
			continue;

		if ((biosdata = biosdataarray[i]) == NULL)
			continue;

		if (debug)
			(void) printf("matching first block 0x%x\n",
			    i + STARTING_DRVNUM);

		matchedindex = match_first_block(biosdata);
		if (matchedindex != -1) {
			if (debug) {
				(void) printf("matched by first block\n");
				(void) printf("0x%x %s\n", i + STARTING_DRVNUM,
				    disk_list[matchedindex]);
			}

			mapinfo[i].disklist_index = matchedindex;
			mapinfo[i].matchcount++;

			for (j = 0; j < i; j++) {
				if (mapinfo[j].matchcount > 0 &&
				    mapinfo[j].disklist_index == matchedindex) {
					mapinfo[j].matchcount++;
					mapinfo[i].matchcount++;
				}
			}
		} else
			if (debug) {
				(void) printf(" No matches by first block\n");
				(void) fprintf(stderr, "Could not match 0x%x\n",
				    i + STARTING_DRVNUM);
			}
	}


	for (i = 0; i < BIOSDEV_NUM; i++) {
		if (mapinfo[i].matchcount == 1) {
			(void) printf("0x%x %s\n", i + STARTING_DRVNUM,
			    disk_list[mapinfo[i].disklist_index]);
			totalmatches++;
		} else if (debug && mapinfo[i].matchcount > 1) {
			(void) printf("0x%x %s matchcount %d\n",
			    i + STARTING_DRVNUM,
			    disk_list[mapinfo[i].disklist_index],
			    mapinfo[i].matchcount);
		}
	}

	if (totalmatches == 0) {
		(void) fprintf(stderr, "biosdev: Could not match any!!\n");
		cleanup_and_exit(1);
	}

	cleanup_and_exit(0);
	/* NOTREACHED */
	return (0);
}