/*
 *	This program opens the openprom	device and prints the platform
 *	name (root node	name property) and the prom version.
 *
 *	NOTE: /dev/openprom is readable	only by	user 'root' or group 'sys'.
 */
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/openpromio.h>
#include <ctype.h>

#define	min(a, b)	(a < b ? a : b)
#define	max(a, b)	(a > b ? a : b)
#define MAXNAMESZ	32	   /* Maximum property *name* size */
#define BUFSZ		1024		   /* A	Handly default buffer size */
#define MAXVALSZ	(BUFSZ - sizeof (int))

static char *promdev = "/dev/openprom";

int	vflag = 0;

/*
 *	Allocate an openpromio structure big enough to contain
 *	a bufsize'd oprom_array. Zero out the structure	and
 *	set the	oprom_size field to bufsize.
 */
static struct openpromio *
opp_zalloc(size_t bufsize)
{
	struct	openpromio *opp;

	opp = malloc(sizeof (struct openpromio) + bufsize);
	bzero(opp, sizeof (struct openpromio) + bufsize);
	opp->oprom_size = bufsize;
	return	(opp);
}
/*
 *	Free a 'struct openpromio' allocated by	opp_zalloc
 */
static void
opp_free(struct openpromio *opp)
{
	free(opp);
}

/*
 *	Get the	peer node of the given node.  The root node is the peer	of zero.
 *	After changing nodes, property lookups apply to	that node.  The	driver
 *	'remembers' what node you are in.
 */
static int
next_node(int nodeid, int fd)
{
	struct openpromio *opp;
	int	i;

	opp = opp_zalloc(sizeof (int));
	opp->oprom_node = nodeid;
	if (ioctl(fd, OPROMNEXT, opp) < 0) {
		perror("OPROMNEXT");
		opp_free(opp);
		return 0;
	}
	i = opp->oprom_node;
	opp_free(opp);
#ifdef notdef
	printf("next_node: node id:0x%x\n", i);
#endif
	return(i);
}

static int
child_node(int nodeid, int fd)
{
	struct	openpromio *opp;
	int i;
	opp = opp_zalloc(sizeof (int));
	opp->oprom_node = nodeid;
	if (ioctl(fd, OPROMCHILD, opp) < 0) {
		perror("OPROMCHILD");
		opp_free(opp);
		return 0;
	}
	i = opp->oprom_node;
	opp_free(opp);
#ifdef notdef
	printf("child_node: node id:0x%x\n", i);
#endif
	return(i);
}

static int
root_node(int fd)
{
	return next_node(0, fd);
}

int
get_prop_len(int fd, char *name)
{
	struct openpromio *opp;
	int		proplen;
	size_t		buflen;
	int		i;
	int		n;

	/*
	 * Allocate an openpromio structure sized big enough to
	 * take the string "name" as input and	return the int-sized
	 * length of the 'name' property.
	 * Then, get the length of the	'name' property.
	 */
	buflen= max(sizeof (int), strlen(name) + 1);
	opp = opp_zalloc(buflen);

	(void)strcpy(opp->oprom_array, name);
	if (ioctl(fd, OPROMGETPROPLEN,	opp) < 0) {
		perror("OPROMGETPROPLEN");
		proplen = 0;
	} else {
		proplen = opp->oprom_len;
	}
	opp_free(opp);

	return proplen;
}

get_prop(int fd, char *name, char *buf, int bufsize)
{
	struct openpromio *opp;
	int		proplen;
	size_t		buflen;
	int		i;
	int		n;


	proplen = get_prop_len(fd, name);
	if (proplen <= 0 || bufsize < proplen) {
		return 0;
	}

	/*
	 * Allocate an openpromio structure sized big enough
	 * to take the string 'name' as input and to return
	 * 'proplen + 1' bytes.  Then,	get the	value of the
	 * 'name' property. Note how we make sure to size the
	 * array at least one byte more than the returned length
	 * to guarantee NULL termination.
	 */
	buflen	= (proplen ? proplen + 1 : MAXVALSZ);
	buflen	= max(buflen, strlen(name) + 1);
	opp = opp_zalloc(buflen);
	(void)	strcpy(opp->oprom_array, name);
	if (ioctl(fd, OPROMGETPROP, opp) < 0) {
		perror("OPROMGETPROP");
		exit(1);
	}

	if (opp->oprom_size > buflen)  {
		return 0;
	}

	bcopy(opp->oprom_array, buf, opp->oprom_size);

	return opp->oprom_size;
}

int
get_int_prop(int fd, char *name, int *val)
{
	if (get_prop_len(fd, name) != sizeof(int)) {
		return -1;
	}
	return get_prop(fd, name, (char *)val, sizeof(int));
}

void
print_compatible(char *bp, int len)
{
	int	i;
	int	rest;
	int	is_first = 1;

	for (rest = len; rest > 0; rest -= i, bp += i) {
		i = strlen(bp) + 1;
		if (i == 1) {
			continue;
		}
		if (!is_first) {
			printf("+", bp);
		}
		is_first = 0;
		printf("'%s'", bp);
	}
	printf("\n");
}

print_property(int fd, char *name, int indent)
{
	struct openpromio *opp;
	int		proplen;
	size_t		buflen;
	int		i;
	int		n;
	char		*bp;
	int		printable;
	int		is_first;

	/*
	 * Allocate an openpromio structure sized big enough to
	 * take the string "name" as input and	return the int-sized
	 * length of the 'name' property.
	 * Then, get the length of the	'name' property.
	 */
	buflen= max(sizeof (int), strlen(name) + 1);
	opp = opp_zalloc(buflen);

	(void)strcpy(opp->oprom_array, name);
	if (ioctl(fd, OPROMGETPROPLEN,	opp) < 0) {
		perror("OPROMGETPROPLEN");
		proplen = 0;
	} else
		proplen = opp->oprom_len;
	opp_free(opp);

	if (proplen ==	-1) {
#ifdef notdef
		printf("'%s' property does not exist!\n", name);
		exit (1);
#endif
		return;
	}
	/*
	 * Allocate an openpromio structure sized big enough
	 * to take the string 'name' as input and to return
	 * 'proplen + 1' bytes.  Then,	get the	value of the
	 * 'name' property. Note how we make sure to size the
	 * array at least one byte more than the returned length
	 * to guarantee NULL termination.
	 */
	buflen	= (proplen ? proplen + 1 : MAXVALSZ);
	buflen	= max(buflen, strlen(name) + 1);
	opp = opp_zalloc(buflen);
	(void)	strcpy(opp->oprom_array, name);
	if (ioctl(fd, OPROMGETPROP, opp) < 0) {
		perror("OPROMGETPROP");
		exit(1);
	}
	if (opp->oprom_size !=	0) {
		for (i=0; i<indent; i++) {
			printf("    ");
		}
#ifdef notdef
		printf("name <%s>	property len <%d>\n",
			 opp->oprom_array, proplen);
#endif
		printf("%s: ", name);

		if (strcmp(name, "compatible") == 0) {
			print_compatible(opp->oprom_array, opp->oprom_size);
			goto x;
		}

		printable = 1;
		for (i=0; i<opp->oprom_size; i++) {
			if (i == opp->oprom_size - 1 &&
			     opp->oprom_array[i] == 0) {
				/* this is tailing NULL, printable */
				break;
			}
			if (!isprint(opp->oprom_array[i])) {
				printable = 0;
				break;
			}
		}
		if (printable) {
			printf("'%s'\n", opp->oprom_array);
		}
		else {
			n = opp->oprom_size;
			bp = (char *)opp->oprom_array;
			is_first = 1;
			while (n) {
				if (!is_first) {
					printf(".");
				}
				is_first = 0;

				if (n >= 4) {
					printf("%08x", *(unsigned int *)bp);
					bp += 4;
					n -= 4;
					continue;
				}
				if (n >= 2) {
					printf("%04x", *(unsigned short *)bp);
					bp += 2;
					n -= 2;
					continue;
				}
				printf("%02x", *(unsigned char *)bp);
				bp += 1;
				n -= 1;
			}
			printf("\n");
		}
	}
x:
	opp_free(opp);
}

print_all_properties(int fd, int indent)
{
	struct openpromio *opp;

	opp = opp_zalloc(MAXVALSZ);
	(void)strcpy(opp->oprom_array, "");
	while (1) {
		opp->oprom_size = MAXVALSZ;
		if (ioctl(fd, OPROMNXTPROP, opp) < 0) {
			perror("OPROMNEXTPROP");
			break;
		}

		if (opp->oprom_size == 0) {
			break;
		}
#ifdef notdef
		printf("nextopt: <%s>\n", opp->oprom_array);
#endif
		print_property(fd, opp->oprom_array, indent);
	}
	opp_free(opp);
}

char	ansbuf[1024];
struct devname {
	struct devname	*next;
	char		dname[1];
} *dhead = NULL;

void
found(char *name)
{
	int	len;
	struct devname	*dp;

#ifdef notdef
	len = strlen(ansbuf);
	if (len > 0) {
		ansbuf[len] = ' ';
		len++;
	}
	sprintf(&ansbuf[len], "\"%s\"", name);
#endif
	for (dp = dhead; dp; dp = dp->next) {
		if (strcmp(name, dp->dname) == 0) {
			/* registered already */
			return;
		}
	}

	dp = (struct devname *) malloc(sizeof(struct devname) + strlen(name));
	strcpy(dp->dname, name);
	dp->next = dhead;
	dhead = dp;
}

void
print_devlist(FILE * fp)
{
	int	is_first = 1;
	struct devname	*dp;

	for (dp = dhead; dp; dp = dp->next) {
		if (!is_first) {
			putc(' ', fp);
		}
		is_first = 0;
		putc('"', fp);
		fputs(dp->dname, fp);
		putc('"', fp);
	}
	putc('\n', fp);

	return;
}

search(int cur_node, int fd, int level, int vendor_id, int device_id)
{
	int	child_id;
	int	i;
	char	name[MAXVALSZ];
	char	compat[MAXVALSZ];
	int	compat_len;
	unsigned int	vid, did;

	if (cur_node == -1) {
		return;
	}

	/* check brothers */
	do {
#ifdef notdef
		print_property(fd, "name", level);
#endif
		if (vflag) {
			for (i=0; i<level; i++) {
				printf("    ");
			}
			printf("Node: 0x%x\n", cur_node);

			print_all_properties(fd, level+1);
		}
		if (get_int_prop(fd, "vendor-id", &vid) < 0) {
			goto x;
		}
		if (get_int_prop(fd, "device-id", &did) < 0) {
			goto x;
		}
		if (!(vid == vendor_id && did == device_id)) {
			goto x;
		}

		if (get_prop(fd, "name", name, sizeof(name)) < 0) {
			goto x;
		}

		compat_len = get_prop(fd, "compatible", compat, sizeof(compat));

		if (vflag) {
			printf("found: %s ", name);
			print_compatible(compat, compat_len);
		}

		found(name);
x:
		/* check children */
		child_id = child_node(cur_node, fd);
		if (child_id != 0) {
			search(child_id, fd, level+2, vendor_id, device_id);
		}

	} while ((cur_node = next_node(cur_node, fd)) != 0);
}

int
main(int argc, char **argv)
{
	struct openpromio *opp;
	int		fd, proplen;
	size_t		buflen;
	int		cur_node;
	int		c;
	extern char     *optarg;
	extern int      optind;
	int             errflg;
	unsigned int	vendor_id;
	unsigned int	device_id;
	char		name[32];

	vflag = 0;
	errflg = 0;

	vendor_id = 0xffff;
	device_id = 0xffff;

	while ((c = getopt(argc, argv, "Vd:v:")) != EOF) {
		switch (c) {
		case 'V':
			vflag = 1;
			break;

		case 'd':
			sscanf(optarg, "%x", &device_id);
			break;

		case 'v':
			sscanf(optarg, "%x", &vendor_id);
			break;

		case '?':
			errflg++;
		}
	}

	if (vendor_id == 0xffff || device_id == 0xffff || errflg) {
		fprintf(stderr, "usage: %s [-V] -v vendor-id -d device-id\n",
			argv[0]);
		exit(1);
	}

	if (vflag) {
		fprintf(stderr, "tagget is (%x, %x)\n", vendor_id, device_id);
	}

	if ((fd = open(promdev, O_RDONLY)) < 0)  {
		fprintf(stderr, "Cannot open openprom device\n");
		exit(1);
	}
	
	/* Append default device name */
	sprintf(name, "pci%x,%x", vendor_id, device_id);
	found(name);

	/*
	 * Get and print the length and value of the
	 * root node 'name' property
	 */
	cur_node = root_node(fd);	    /* Navigate	to the root node */
	search(cur_node, fd, 0, vendor_id, device_id);

#ifdef notdef
	/*
	 * Allocate an openpromio structure assumed to be
	 * big enough to get the 'prom	version	string'.
	 * Get and print the prom version.
	 */
	opp = opp_zalloc(MAXVALSZ);
	opp->oprom_size = MAXVALSZ;
	if (ioctl(fd, OPROMGETVERSION, opp) < 0) {
		perror("OPROMGETVERSION");
		exit(1);
	}
	printf("Prom version <%s>\n", opp->oprom_array);
	opp_free(opp);
	(void)close(fd);
	return(0);
#endif

#ifdef notdef
	found("pci1259,a11c");
	printf("%s\n", ansbuf);
#endif
	print_devlist(stdout);
	exit(0);
}
