#!/bin/sh

#
# print a list of PCI devices installed in the system
# (basically "prtconf -pv", with a readable format)
#


# "PCI Comma-Delimited Device List" file, from
# <URL:http://www.pcidatabase.com/reports.php?type=csv>

pcidev_url="http://www.pcidatabase.com/reports.php?type=csv"
pcidev=`dirname $0`/pcidev.csv
if [ -s "$pcidev" ]
then
	:
else
	pcidev=/usr/local/etc/pcidev.csv
fi

verbose=0
update_pcidev=0
prtconf_output=

USAGE='usage:
prtpci [-v] [-p pcidev.csv] [-u] [-i prtconf_pv_file]
    -v	verbose output
    -p	location of PCI comma-delimited device list file 
    -u	update PCI comma-delimited device list file from web site
    -i	operate on given file with "prtconf -pv" output, instead of
	examining the live system'


while getopts i:p:uv c
do
	case $c in
	v)	verbose=1;;
	u)	update_pcidev=1;;
	p)	pcidev="$OPTARG";;
	i)	prtconf_output="$OPTARG";;
	\?)	echo "$USAGE"; exit 2;;
	esac
done
shift `expr $OPTIND - 1`


if [ $update_pcidev -ne 0 ]
then
	echo "Update $pcidev from $pcidev_url..."
	wget -O /tmp/pcidev$$ "$pcidev_url" && mv /tmp/pcidev$$ "$pcidev"

	echo "PCI device database $pcidev updated:"
	ls -l "$pcidev"
	exit 0
fi


# try to use GNU awk, if possible
# (on SPARC, there's a huge "translations" property on the "virtial-memory"
# node which confuses nawk)
if type gawk >/dev/null
then
	awk=gawk
else
	awk=nawk
fi


if [ -z "$prtconf_output" ]
then
	if [ $verbose -ne 0 ]
	then
		prtconf -Dv
	else
		prtconf -D
	fi
fi


if [ -z "$prtconf_output" ]
then
	prtconf_cmd="prtconf -pv"
else
	prtconf_cmd="cat $prtconf_output"
fi

$prtconf_cmd | $awk -v verbose=$verbose -v pcidev="$pcidev" '

function hex(hex_str) {
	hex_str = tolower(hex_str);
	if (hex_str ~ /^0x/) {
		hex_str = substr(hex_str, 3);
	}
	v = 0;
	for (j = 1; j <= length(hex_str); j++) {
		v = v * 16;
		hex_dig = substr(hex_str, j, 1);
		if (hex_dig ~ /[0-9]/) {
			v += hex_dig;
		} else if (hex_dig == "a") {
			v += 10;
		} else if (hex_dig == "b") {
			v += 11;
		} else if (hex_dig == "c") {
			v += 12;
		} else if (hex_dig == "d") {
			v += 13;
		} else if (hex_dig == "e") {
			v += 14;
		} else if (hex_dig == "f") {
			v += 15;
		}
	}
	return v;
}

#
# Load PCI Comma-Delimited Device List
# available from http://www.yourvote.com/pci/
# <URL:http://www.yourvote.com/pci/pcidev.csv>
#
function load_pcidev_file(pcidev) {
	while (getline pcidev_line < pcidev == 1) {
		split(pcidev_line, pci_el, "\"");
		vendor_id = hex(pci_el[2]);
		device_id = hex(pci_el[4]);
		vendor_desc = pci_el[6];
		device_desc = pci_el[8];
		device_desc2 = pci_el[10];

		if (device_desc == "") {
			device_desc = device_desc2;
		}

		pci_vendor[vendor_id] = vendor_desc;
		pci_device[vendor_id, device_id] = device_desc;
	}
}

#
# Populate pci_cc array to decode PCI class codes
#
function init_pciclass_data() {
	# PCI base classes
	pci_cc[0] = "pre-2.0 PCI specification device";
	pci_cc[1] = "Mass storage Controller";
	pci_cc[2] = "Network Controller";
	pci_cc[3] = "Display Controller";
	pci_cc[4] = "Multimedia Controller";
	pci_cc[5] = "Memory Controller";
	pci_cc[6] = "Bridge Controller";
	pci_cc[7] = "Communications Controller";
	pci_cc[8] = "Peripheral Controller";
	pci_cc[9] = "Input Device";
	pci_cc[10] = "Docking Station";
	pci_cc[11] = "Processor";
	pci_cc[12] = "Serial Bus";
	pci_cc[13] = "Wireless Controller";
	pci_cc[14] = "Intelligent IO Controller";
	pci_cc[15] = "Satellite Communication";
	pci_cc[16] = "Encrytion/Decryption";
	pci_cc[17] = "Signal Processing";

	# Mass storage subclasses
	pci_cc[1, 0] = "SCSI bus Controller";
	pci_cc[1, 1] = "IDE Controller";
	pci_cc[1, 2] = "Floppy disk Controller";
	pci_cc[1, 3] = "IPI bus Controller";
	pci_cc[1, 4] = "RAID Controller";

	# Network subclasses
	pci_cc[2, 0] = "Ethernet Controller";
	pci_cc[2, 1] = "Token Ring Controller";
	pci_cc[2, 2] = "FDDI Controller";
	pci_cc[2, 3] = "ATM Controller";
	pci_cc[2, 4] = "ISDN Controller";

	# Display subclasses
	pci_cc[3, 0] = "VGA device";
	pci_cc[3, 1] = "XGA device";

	# Multimedia subclasses
	pci_cc[4, 0] = "Video device";
	pci_cc[4, 1] = "Audio device";
	pci_cc[4, 2] = "Telephony device";

	# Memory subclasses
	pci_cc[5, 0] = "RAM device";
	pci_cc[5, 1] = "FLASH device";

	# Bridge subclasses
	pci_cc[6, 0] = "Host/PCI Bridge";
	pci_cc[6, 1] = "PCI/ISA Bridge";
	pci_cc[6, 2] = "PCI/EISA Bridge";
	pci_cc[6, 3] = "PCI/MC Bridge";
	pci_cc[6, 4] = "PCI/PCI Bridge";
	pci_cc[6, 4, 1] = "Subtractive";
	pci_cc[6, 5] = "PCI/PCMCIA Bridge";
	pci_cc[6, 6] = "PCI/NUBUS Bridge";
	pci_cc[6, 7] = "PCI/CARDBUS Bridge";

	# Serial bus subclasses
	pci_cc[12, 0] = "IEEE 1394";
	pci_cc[12, 1] = "ACCESS.bus";
	pci_cc[12, 2] = "SSA";
	pci_cc[12, 3] = "Universal Serial Bus";
	pci_cc[12, 4] = "Fibre Channel";
	pci_cc[12, 5] = "SMBus";

	pci_cc[12, 0, 0] = "Firewire";
	pci_cc[12, 0, 16] = "OpenHCI";
	pci_cc[12, 3, 0] = "UHCI";
	pci_cc[12, 3, 16] = "OHCI";
	pci_cc[12, 3, 32] = "EHCI";
}

#
# convert the "class-code" property word to a printable string
#
function decode_pci_class(cc) {
	cc0 = hex(cc);
	cc2 = cc0 % 256; cc0 = int(cc0 / 256);
	cc1 = cc0 % 256; cc0 = int(cc0 / 256);

	class_desc = "";
	if (pci_cc[cc0, cc1, cc2] != "") {
		class_desc = pci_cc[cc0, cc1, cc2] ", " class_desc;
	}
	if (pci_cc[cc0, cc1] != "") {
		class_desc = pci_cc[cc0, cc1] ", " class_desc;
	}
	class_desc = pci_cc[cc0] ", " class_desc;
	return substr(class_desc, 1, length(class_desc)-2);
}

#
# decode word 0 from the "reg" property of a pci device node
#
function decode_pci_reg_type(type) {
	reg_no = type % 256;	type = int(type / 256);
	func_no = type % 8;	type = int(type / 8);
	dev = type % 32; 	type = int(type / 32);
	bus = type % 256;	type = int(type / 256);
	asi = type % 4;		type = int(type / 4);
}

#
# extract the PCI bus/device/function from the "reg" property
# and return a formatted string with that information
#
function decode_pci_bdf(reg) {
	if (!split(reg, r, "\.")) {
		return "";
	}
	decode_pci_reg_type(hex(r[1]));
	return sprintf("%d,%d,%d", bus, dev, func_no);
}

#
# decode the address information contained in the "reg" or
# "assigned-addresses" property.
#
function decode_pci_reg(reg) {
	n = split(reg, r, "\.");

	reg = "";
	for (i = 1; i <= n; i += 5) {
		type = hex(r[i]);
		addr_hi = r[i+1];
		addr_lo = r[i+2];
		size_hi = r[i+3];
		size_lo = r[i+4];

		decode_pci_reg_type(type);

		if (asi == 0) {
			s = "Cfg  ";
		} else if (asi == 1) {
			s = "I/O  ";
		} else if (asi == 2) {
			s = "Mem32";
		} else if (asi == 3) {
			s = "Mem64";
		}
		reg = reg sprintf("\t%s %s%s %s%s (@%02x)", s, addr_hi, addr_lo, size_hi, size_lo, reg_no);
		reg = reg "\n";

	}
	return reg;
}

#
# prtconf -pv does not print the value of the "compatible" property as
# a list of ascii strings on S8 x86, it prints an array of hex words.
#
# Decode the hex words back to printable format.
#
function decode_compatible(s) {
	if (s !~ /^[0-9a-z.]*$/) {
		return s;
	}
	n = split(s, w, "[.]");
	s = "";
	s0 = "";
	s_sep = "";
	for (i = 1; i <= n; i++) {
		for (ii = 7; ii > 0; ii-=2) {
			ch = hex(substr(w[i], ii, 2));
			if (ch >= 32 && ch < 128) {
				s0 = s0 sprintf("%c", ch);
			} else if (ch != 0) {
				s0 = s0 sprintf("\\x%x", ch);
			} else if (s0 != "") {
				s = s s_sep "'\''" s0 "'\''";
				s_sep = " + ";
				s0 = "";
			}
		}
	}
	return s;
}

BEGIN {
	init_pciclass_data();
	load_pcidev_file(pcidev);

	node = 0;
	pci_node_level = 0;
}

#
# start of a new "Node" in the prtconf -pv output
#

$1 == "Node" {
	node = 1;
	node_level = (index($0, "Node ") - 1) / 4;
}

#
# collect key/value pairs from the node"s properties
#
node == 1 {
	colon = index($0, ":");
	key = substr($0, 1, colon-1);
	val = substr($0, colon+1);
	sub(/^ */, "", key);
	sub(/^ */, "", val);
	sub(/ *$/, "", val);
	node_prop[key] = val;
}

#
# an empty line (no fields) marks the end of a "Node"
#
NF == 0 {
	node = 0;
	node_name = node_prop["name"];
	if (pci_node_level == 0 && node_name == "'\'pci\''") {

		# the root of a PCI bus subtree starts here
		pci_node_level = node_level;

	} else if (pci_node_level > 0 && node_level < pci_node_level) {

		# we are leaving the PCI bus subtree
		pci_node_level = 0;

	} else if (pci_node_level > 0 && node_level > pci_node_level && node_prop["vendor-id"] != "") {

		# a PCI device node

		printf("_______________________________________________________________________________\n");


		printf("Bus,Device,Function:\t%s\n", decode_pci_bdf(node_prop["reg"]));
		vendor_id = hex(node_prop["vendor-id"]);
		device_id = hex(node_prop["device-id"]);
		revision  = hex(node_prop["revision-id"]);
		printf("Vendor,Device ID:\t%-15s", sprintf("%x,%x Rev %x", vendor_id, device_id, revision));
		if (pci_vendor[vendor_id] != "" || pci_device[vendor_id, device_id] != "") {
			printf(" (%s, %s)", pci_vendor[vendor_id], pci_device[vendor_id, device_id]);
		}
		printf("\n");

		if (node_prop["subsystem-vendor-id"] != "") {
			subs_vendor_id = hex(node_prop["subsystem-vendor-id"]);
			subs_id        = hex(node_prop["subsystem-id"]);
			printf("Subsys. Vendor,Device:\t%-15s", sprintf("%x,%x", subs_vendor_id, subs_id));
			if (pci_vendor[subs_vendor_id] != "" || pci_device[subs_vendor_id, subs_id] != "") {
				printf(" (%s, %s)", pci_vendor[subs_vendor_id], pci_device[subs_vendor_id, subs_id]);
			}
			printf("\n");
		}

		class_code = substr(node_prop["class-code"], 3);
		printf("Class code:\t\t%s\t\t(%s)\n", class_code, decode_pci_class(class_code));

		if (node_prop["interrupts"] != "") {
			printf("Interrupt pin:\t\t%x\n", hex(node_prop["interrupts"]));
		}

		if (verbose && node_prop["min-grant"] != "") {
			printf("Min-Gnt:\t\t%x\t\tMax-Lat:\t%x\n", hex(node_prop["min-grant"]), hex(node_prop["max-latency"]));
		}

		if (verbose) {
			printf("\nreg:\n%s", decode_pci_reg(node_prop["reg"]));
		}

		printf("\nassigned-addresses:\n%s", decode_pci_reg(node_prop["assigned-addresses"]));

		printf("\n");
		if (node_prop["model"] != "") {
			printf("Model:\t\t\t%s\n", node_prop["model"]);
		}
		printf("Name:\t\t\t%s\n", node_prop["name"]);
		printf("compatible:\t\t%s\n", decode_compatible(node_prop["compatible"]));
	}

	for (var in node_prop) {
		delete node_prop[var];
	}
}
'

exit 0
