/*	$OpenBSD: pci_subr.c,v 1.21 2007/02/21 13:08:22 dlg Exp $	*/
/*	$NetBSD: pci_subr.c,v 1.19 1996/10/13 01:38:29 christos Exp $	*/

/*
 * Copyright (c) 1995, 1996 Christopher G. Demetriou.  All rights reserved.
 * Copyright (c) 1994 Charles Hannum.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Charles Hannum.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * PCI autoconfiguration support functions.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>

#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#ifdef PCIVERBOSE
#include <dev/pci/pcidevs.h>
#include <dev/pci/pcidevs_data.h>
#endif

/*
 * Descriptions of known PCI classes and subclasses.
 *
 * Subclasses are described in the same way as classes, but have a
 * NULL subclass pointer.
 */
struct pci_class {
	const char	*name;
	int		val;		/* as wide as pci_{,sub}class_t */
	const struct pci_class *subclasses;
};

const struct pci_class pci_subclass_prehistoric[] = {
	{ "miscellaneous",	PCI_SUBCLASS_PREHISTORIC_MISC,		},
	{ "VGA",		PCI_SUBCLASS_PREHISTORIC_VGA,		},
	{ 0 }
};

const struct pci_class pci_subclass_mass_storage[] = {
	{ "SCSI",		PCI_SUBCLASS_MASS_STORAGE_SCSI,		},
	{ "IDE",		PCI_SUBCLASS_MASS_STORAGE_IDE,		},
	{ "floppy",		PCI_SUBCLASS_MASS_STORAGE_FLOPPY,	},
	{ "IPI",		PCI_SUBCLASS_MASS_STORAGE_IPI,		},
	{ "RAID",		PCI_SUBCLASS_MASS_STORAGE_RAID,		},
	{ "ATA",		PCI_SUBCLASS_MASS_STORAGE_ATA,		},
	{ "SATA",		PCI_SUBCLASS_MASS_STORAGE_SATA,		},
	{ "SAS",		PCI_SUBCLASS_MASS_STORAGE_SAS,		},
	{ "miscellaneous",	PCI_SUBCLASS_MASS_STORAGE_MISC,		},
	{ 0 },
};

const struct pci_class pci_subclass_network[] = {
	{ "ethernet",		PCI_SUBCLASS_NETWORK_ETHERNET,		},
	{ "token ring",		PCI_SUBCLASS_NETWORK_TOKENRING,		},
	{ "FDDI",		PCI_SUBCLASS_NETWORK_FDDI,		},
	{ "ATM",		PCI_SUBCLASS_NETWORK_ATM,		},
	{ "ISDN",		PCI_SUBCLASS_NETWORK_ISDN,		},
	{ "WorldFip",		PCI_SUBCLASS_NETWORK_WORLDFIP,		},
	{ "PCMIG Multi Computing", PCI_SUBCLASS_NETWORK_PCIMGMULTICOMP,	},
	{ "miscellaneous",	PCI_SUBCLASS_NETWORK_MISC,		},
	{ 0 },
};

const struct pci_class pci_subclass_display[] = {
	{ "VGA",		PCI_SUBCLASS_DISPLAY_VGA,		},
	{ "XGA",		PCI_SUBCLASS_DISPLAY_XGA,		},
	{ "3D",			PCI_SUBCLASS_DISPLAY_3D,		},
	{ "miscellaneous",	PCI_SUBCLASS_DISPLAY_MISC,		},
	{ 0 },
};

const struct pci_class pci_subclass_multimedia[] = {
	{ "video",		PCI_SUBCLASS_MULTIMEDIA_VIDEO,		},
	{ "audio",		PCI_SUBCLASS_MULTIMEDIA_AUDIO,		},
	{ "telephony",		PCI_SUBCLASS_MULTIMEDIA_TELEPHONY,	},
	{ "hdaudio",		PCI_SUBCLASS_MULTIMEDIA_HDAUDIO,	},
	{ "miscellaneous",	PCI_SUBCLASS_MULTIMEDIA_MISC,		},
	{ 0 },
};

const struct pci_class pci_subclass_memory[] = {
	{ "RAM",		PCI_SUBCLASS_MEMORY_RAM,		},
	{ "flash",		PCI_SUBCLASS_MEMORY_FLASH,		},
	{ "miscellaneous",	PCI_SUBCLASS_MEMORY_MISC,		},
	{ 0 },
};

const struct pci_class pci_subclass_bridge[] = {
	{ "host",		PCI_SUBCLASS_BRIDGE_HOST,		},
	{ "ISA",		PCI_SUBCLASS_BRIDGE_ISA,		},
	{ "EISA",		PCI_SUBCLASS_BRIDGE_EISA,		},
	{ "MicroChannel",	PCI_SUBCLASS_BRIDGE_MC,			},
	{ "PCI",		PCI_SUBCLASS_BRIDGE_PCI,		},
	{ "PCMCIA",		PCI_SUBCLASS_BRIDGE_PCMCIA,		},
	{ "NuBus",		PCI_SUBCLASS_BRIDGE_NUBUS,		},
	{ "CardBus",		PCI_SUBCLASS_BRIDGE_CARDBUS,		},
	{ "RACEway",		PCI_SUBCLASS_BRIDGE_RACEWAY,		},
	{ "Semi-transparent PCI", PCI_SUBCLASS_BRIDGE_STPCI,		},
	{ "InfiniBand",		PCI_SUBCLASS_BRIDGE_INFINIBAND,		},
	{ "miscellaneous",	PCI_SUBCLASS_BRIDGE_MISC,		},
	{ 0 },
};

const struct pci_class pci_subclass_communications[] = {
	{ "serial",		PCI_SUBCLASS_COMMUNICATIONS_SERIAL,	},
	{ "parallel",		PCI_SUBCLASS_COMMUNICATIONS_PARALLEL,	},
	{ "multi-port serial",	PCI_SUBCLASS_COMMUNICATIONS_MPSERIAL,	},
	{ "modem",		PCI_SUBCLASS_COMMUNICATIONS_MODEM,	},
	{ "GPIB",		PCI_SUBCLASS_COMMUNICATIONS_GPIB,	},
	{ "smartcard",		PCI_SUBCLASS_COMMUNICATIONS_SMARTCARD,	},
	{ "miscellaneous",	PCI_SUBCLASS_COMMUNICATIONS_MISC,	},
	{ 0 },
};

const struct pci_class pci_subclass_system[] = {
	{ "interrupt",		PCI_SUBCLASS_SYSTEM_PIC,		},
	{ "8237 DMA",		PCI_SUBCLASS_SYSTEM_DMA,		},
	{ "8254 timer",		PCI_SUBCLASS_SYSTEM_TIMER,		},
	{ "RTC",		PCI_SUBCLASS_SYSTEM_RTC,		},
	{ "PCI Hot-Plug",	PCI_SUBCLASS_SYSTEM_PCIHOTPLUG,		},
	{ "SD Host Controller",	PCI_SUBCLASS_SYSTEM_SDHC,		},
	{ "miscellaneous",	PCI_SUBCLASS_SYSTEM_MISC,		},
	{ 0 },
};

const struct pci_class pci_subclass_input[] = {
	{ "keyboard",		PCI_SUBCLASS_INPUT_KEYBOARD,		},
	{ "digitizer",		PCI_SUBCLASS_INPUT_DIGITIZER,		},
	{ "mouse",		PCI_SUBCLASS_INPUT_MOUSE,		},
	{ "scanner",		PCI_SUBCLASS_INPUT_SCANNER,		},
	{ "game port",		PCI_SUBCLASS_INPUT_GAMEPORT,		},
	{ "miscellaneous",	PCI_SUBCLASS_INPUT_MISC,		},
	{ 0 },
};

const struct pci_class pci_subclass_dock[] = {
	{ "generic",		PCI_SUBCLASS_DOCK_GENERIC,		},
	{ "miscellaneous",	PCI_SUBCLASS_DOCK_MISC,			},
	{ 0 },
};

const struct pci_class pci_subclass_processor[] = {
	{ "386",		PCI_SUBCLASS_PROCESSOR_386,		},
	{ "486",		PCI_SUBCLASS_PROCESSOR_486,		},
	{ "Pentium",		PCI_SUBCLASS_PROCESSOR_PENTIUM,		},
	{ "Alpha",		PCI_SUBCLASS_PROCESSOR_ALPHA,		},
	{ "PowerPC",		PCI_SUBCLASS_PROCESSOR_POWERPC,		},
	{ "MIPS",		PCI_SUBCLASS_PROCESSOR_MIPS,		},
	{ "Co-processor",	PCI_SUBCLASS_PROCESSOR_COPROC,		},
	{ 0 },
};

const struct pci_class pci_subclass_serialbus[] = {
	{ "Firewire",		PCI_SUBCLASS_SERIALBUS_FIREWIRE,	},
	{ "ACCESS.bus",		PCI_SUBCLASS_SERIALBUS_ACCESS,		},
	{ "SSA",		PCI_SUBCLASS_SERIALBUS_SSA,		},
	{ "USB",		PCI_SUBCLASS_SERIALBUS_USB,		},
	/* XXX Fiber Channel/_FIBRECHANNEL */
	{ "Fiber Channel",	PCI_SUBCLASS_SERIALBUS_FIBER,		},
	{ "SMBus",		PCI_SUBCLASS_SERIALBUS_SMBUS,		},
	{ "InfiniBand",		PCI_SUBCLASS_SERIALBUS_INFINIBAND,	},
	{ "IPMI",		PCI_SUBCLASS_SERIALBUS_IPMI,		},
	{ "SERCOS",		PCI_SUBCLASS_SERIALBUS_SERCOS,		},
	{ "CANbus",		PCI_SUBCLASS_SERIALBUS_CANBUS,		},
	{ 0 },
};

const struct pci_class pci_subclass_wireless[] = {
	{ "IrDA",		PCI_SUBCLASS_WIRELESS_IRDA,		},
	{ "Consumer IR",	PCI_SUBCLASS_WIRELESS_CONSUMERIR,	},
	{ "RF",			PCI_SUBCLASS_WIRELESS_RF,		},
	{ "bluetooth",		PCI_SUBCLASS_WIRELESS_BLUETOOTH,	},
	{ "broadband",		PCI_SUBCLASS_WIRELESS_BROADBAND,	},
	{ "802.11a (5 GHz)",	PCI_SUBCLASS_WIRELESS_802_11A,		},
	{ "802.11b (2.4 GHz)",	PCI_SUBCLASS_WIRELESS_802_11B,		},
	{ "miscellaneous",	PCI_SUBCLASS_WIRELESS_MISC,		},
	{ 0 },
};

const struct pci_class pci_subclass_i2o[] = {
	{ "standard",		PCI_SUBCLASS_I2O_STANDARD,		},
	{ 0 },
};

const struct pci_class pci_subclass_satcom[] = {
	{ "TV",			PCI_SUBCLASS_SATCOM_TV,			},
	{ "audio",		PCI_SUBCLASS_SATCOM_AUDIO,		},
	{ "voice",		PCI_SUBCLASS_SATCOM_VOICE,		},
	{ "data",		PCI_SUBCLASS_SATCOM_DATA,		},
	{ 0 },
};

const struct pci_class pci_subclass_crypto[] = {
	{ "network/computing",	PCI_SUBCLASS_CRYPTO_NETCOMP,		},
	{ "entertainment",	PCI_SUBCLASS_CRYPTO_ENTERTAINMENT,	},
	{ "miscellaneous",	PCI_SUBCLASS_CRYPTO_MISC,		},
	{ 0 },
};

const struct pci_class pci_subclass_dasp[] = {
	{ "DPIO",		PCI_SUBCLASS_DASP_DPIO,			},
	{ "Time and Frequency",	PCI_SUBCLASS_DASP_TIMEFREQ,		},
	{ "synchronization",	PCI_SUBCLASS_DASP_SYNC,			},
	{ "management",		PCI_SUBCLASS_DASP_MGMT,			},
	{ "miscellaneous",	PCI_SUBCLASS_DASP_MISC,			},
	{ 0 },
};

const struct pci_class pci_class[] = {
	{ "prehistoric",	PCI_CLASS_PREHISTORIC,
	    pci_subclass_prehistoric,				},
	{ "mass storage",	PCI_CLASS_MASS_STORAGE,
	    pci_subclass_mass_storage,				},
	{ "network",		PCI_CLASS_NETWORK,
	    pci_subclass_network,				},
	{ "display",		PCI_CLASS_DISPLAY,
	    pci_subclass_display,				},
	{ "multimedia",		PCI_CLASS_MULTIMEDIA,
	    pci_subclass_multimedia,				},
	{ "memory",		PCI_CLASS_MEMORY,
	    pci_subclass_memory,				},
	{ "bridge",		PCI_CLASS_BRIDGE,
	    pci_subclass_bridge,				},
	{ "communications",	PCI_CLASS_COMMUNICATIONS,
	    pci_subclass_communications,			},
	{ "system",		PCI_CLASS_SYSTEM,
	    pci_subclass_system,				},
	{ "input",		PCI_CLASS_INPUT,
	    pci_subclass_input,					},
	{ "dock",		PCI_CLASS_DOCK,
	    pci_subclass_dock,					},
	{ "processor",		PCI_CLASS_PROCESSOR,
	    pci_subclass_processor,				},
	{ "serial bus",		PCI_CLASS_SERIALBUS,
	    pci_subclass_serialbus,				},
	{ "wireless",		PCI_CLASS_WIRELESS,
	    pci_subclass_wireless,				},
	{ "I2O",		PCI_CLASS_I2O,
	    pci_subclass_i2o,					},
	{ "satellite comm",	PCI_CLASS_SATCOM,
	    pci_subclass_satcom,				},
	{ "crypto",		PCI_CLASS_CRYPTO,
	    pci_subclass_crypto,				},
	{ "DASP",		PCI_CLASS_DASP,
	    pci_subclass_dasp,					},
	{ "undefined",		PCI_CLASS_UNDEFINED,
	    0,							},
	{ 0 },
};

const char *
pci_findvendor(pcireg_t id_reg)
{
#ifdef PCIVERBOSE
	pci_vendor_id_t vendor = PCI_VENDOR(id_reg);
	const struct pci_known_vendor *kdp;

	kdp = pci_known_vendors;
        while (kdp->vendorname != NULL) {	/* all have vendor name */
                if (kdp->vendor == vendor)
                        break;
		kdp++;
	}
        return (kdp->vendorname);
#else
	return (NULL);
#endif
}

const char *
pci_findproduct(pcireg_t id_reg)
{
#ifdef PCIVERBOSE
	pci_vendor_id_t vendor = PCI_VENDOR(id_reg);
	pci_product_id_t product = PCI_PRODUCT(id_reg);
	const struct pci_known_product *pkp;

	pkp = pci_known_products;
	while (pkp->productname != NULL) {	/* all have product name */
		if (pkp->vendor == vendor && pkp->product == product)
			break;
		pkp++;
	}
	return (pkp->productname);
#else
	return NULL;
#endif
}

void
pci_devinfo(pcireg_t id_reg, pcireg_t class_reg, int showclass, char *cp,
	    size_t cp_max)
{
	pci_vendor_id_t vendor;
	pci_product_id_t product;
	pci_class_t class;
	pci_subclass_t subclass;
	pci_interface_t interface;
	pci_revision_t revision;
	const char *vendor_namep = NULL, *product_namep = NULL;
	const struct pci_class *classp, *subclassp;
	size_t cp_len = 0;
#ifdef PCIVERBOSE
	const char *unmatched = "unknown ";
#else
	const char *unmatched = "";
#endif

	vendor = PCI_VENDOR(id_reg);
	product = PCI_PRODUCT(id_reg);

	class = PCI_CLASS(class_reg);
	subclass = PCI_SUBCLASS(class_reg);
	interface = PCI_INTERFACE(class_reg);
	revision = PCI_REVISION(class_reg);

#ifdef PCIVERBOSE
	vendor_namep = pci_findvendor(id_reg);
	if (vendor_namep != NULL)
		product_namep = pci_findproduct(id_reg);
#endif /* PCIVERBOSE */

	classp = pci_class;
	while (classp->name != NULL) {
		if (class == classp->val)
			break;
		classp++;
	}

	subclassp = (classp->name != NULL) ? classp->subclasses : NULL;
	while (subclassp && subclassp->name != NULL) {
		if (subclass == subclassp->val)
			break;
		subclassp++;
	}

	if (vendor_namep == NULL)
		snprintf(cp, cp_max, "%svendor 0x%04x product 0x%04x",
		    unmatched, vendor, product);
	else if (product_namep != NULL)
		snprintf(cp, cp_max, "\"%s %s\"", vendor_namep, product_namep);
	else
		snprintf(cp, cp_max, "vendor \"%s\", unknown product 0x%04x",
		    vendor_namep, product);
	if (showclass && product_namep == NULL) {
		strlcat(cp, " (", cp_max);
		cp_len = strlen(cp);
		if (classp->name == NULL)
			snprintf(cp + cp_len, cp_max - cp_len,
			    "unknown class 0x%02x, subclass 0x%02x",
			    class, subclass);
		else if (subclassp == NULL || subclassp->name == NULL)
			snprintf(cp + cp_len, cp_max - cp_len,
			    "class %s unknown subclass 0x%02x", classp->name,
			    subclass);
		else
			snprintf(cp + cp_len, cp_max - cp_len,
			    "class %s subclass %s", classp->name,
			    subclassp->name);
#if 0 /* not very useful */
		cp_len = strlen(cp);
		snprintf(cp + cp_len, cp_max - cp_len,
		    ", interface 0x%02x", interface);
#endif
		cp_len = strlen(cp);
		snprintf(cp + cp_len, cp_max - cp_len,
		    ", rev 0x%02x)", revision);
	} else {
		cp_len = strlen(cp);
		snprintf(cp + cp_len, cp_max - cp_len, " rev 0x%02x",
		    revision);
	}
}
