/* $XFree86: xc/programs/Xserver/hw/xfree86/scanpci/xf86ScanPci.c,v 1.13 2003/08/24 17:37:10 dawes Exp $ */
/*
 * Display the Subsystem Vendor Id and Subsystem Id in order to identify
 * the cards installed in this computer
 *
 * A lot of this comes from Robin Cutshaw's scanpci
 *
 */
/*
 * Copyright (c) 1995-2002 by The XFree86 Project, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name of the copyright holder(s)
 * and author(s) shall not be used in advertising or otherwise to promote
 * the sale, use or other dealings in this Software without prior written
 * authorization from the copyright holder(s) and author(s).
 */

/*
 * This file is used to build both the scanpci and pcidata modules.
 * The interfaces have changed compared with XFree86 4.2.0 and earlier.
 * The data is no longer exported directly.  Lookup functions are provided.
 * This means that the data format can change in the future without affecting
 * the exported interfaces.
 *
 * The namespaces for pcidata and scanpci clash, so both modules can't be
 * loaded at the same time.  The X server should only load the scanpci module
 * when run with the '-scanpci' flag.  The main difference between the
 * two modules is size.  pcidata only holds the subset of data that is
 * "interesting" to the X server.  "Interesting" is determined by the
 * PCI_VENDOR_* defines in ../common/xf86PciInfo.h.
 */


/* XXX This is including a lot of stuff that modules should not include! */

#include "X.h"
#include "os.h"
#include "xf86.h"
#include "xf86Priv.h"
#include "xf86Pci.h"
#include "xf86_OSproc.h"

#ifndef IN_MODULE
#include <ctype.h>
#include <stdlib.h>
#else
#include <xf86_ansic.h>
#endif

#ifndef PCIDATA
#define VENDOR_INCLUDE_NONVIDEO
#endif
#define INIT_SUBSYS_INFO
#define INIT_VENDOR_SUBSYS_INFO

#include "xf86PciStr.h"
#include "xf86PciIds.h"
#include "xf86ScanPci.h"

/*
 * PCI classes that have messages printed always.  The others are only
 * have a message printed when the vendor/dev IDs are recognised.
 */
#define PCIALWAYSPRINTCLASSES(b,s)					      \
    (((b) == PCI_CLASS_PREHISTORIC && (s) == PCI_SUBCLASS_PREHISTORIC_VGA) || \
     ((b) == PCI_CLASS_DISPLAY) ||					      \
     ((b) == PCI_CLASS_MULTIMEDIA && (s) == PCI_SUBCLASS_MULTIMEDIA_VIDEO))


#ifdef XFree86LOADER

#include "xf86Module.h"

#ifdef PCIDATA

static XF86ModuleVersionInfo pciDataVersRec = {
	"pcidata",
	MODULEVENDORSTRING,
	MODINFOSTRING1,
	MODINFOSTRING2,
	XORG_VERSION_CURRENT,
	1, 0, 0,
	ABI_CLASS_VIDEODRV,
	ABI_VIDEODRV_VERSION,
	NULL,
	{0, 0, 0, 0}
};

XF86ModuleData pcidataModuleData = { &pciDataVersRec, NULL, NULL };

#else 

static XF86ModuleVersionInfo scanPciVersRec = {
	"scanpci",
	MODULEVENDORSTRING,
	MODINFOSTRING1,
	MODINFOSTRING2,
	XORG_VERSION_CURRENT,
	1, 0, 0,
	ABI_CLASS_VIDEODRV,
	ABI_VIDEODRV_VERSION,
	NULL,
	{0, 0, 0, 0}
};

XF86ModuleData scanpciModuleData = { &scanPciVersRec, NULL, NULL };

#endif /* PCIDATA */

#endif /* XFree86LOADER */

/* Initialisation/Close hooks, in case they're ever needed. */
Bool
ScanPciSetupPciIds(void)
{
    return TRUE;
}

void
ScanPciClosePciIds(void)
{
    return;
}

/*
 * The return value is the number of strings found, or -1 for an error.
 * Requested strings that aren't found are set to NULL.
 */

int
ScanPciFindPciNamesByDevice(unsigned short vendor, unsigned short device,
			 unsigned short svendor, unsigned short subsys,
			 const char **vname, const char **dname,
			 const char **svname, const char **sname)
{
    int i, j, k;
    const pciDeviceInfo **pDev;
    const pciSubsystemInfo **pSub;

    /* It's an error to not provide the Vendor */
    if (vendor == NOVENDOR)
	return -1;

    /* Initialise returns requested/provided to NULL */
    if (vname)
	*vname = NULL;
    if (device != NODEVICE && dname)
	*dname = NULL;
    if (svendor != NOVENDOR && svname)
	*svname = NULL;
    if (subsys != NOSUBSYS && sname)
	*sname = NULL;

    for (i = 0; pciVendorInfoList[i].VendorName; i++) {
	if (vendor == pciVendorInfoList[i].VendorID) {
	    if (vname) {
		*vname = pciVendorInfoList[i].VendorName;
	    }
	    if (device == NODEVICE) {
		return 1;
	    }
	    pDev = pciVendorInfoList[i].Device;
	    if (!pDev) {
		return 1;
	    }
	    for (j = 0; pDev[j]; j++) {
		if (device == pDev[j]->DeviceID) {
		    if (dname) {
			*dname = pDev[j]->DeviceName;
		    }
		    if (svendor == NOVENDOR) {
			return 2;
		    }
		    for (k = 0; pciVendorInfoList[k].VendorName; k++) {
			if (svendor &&
			    svendor == pciVendorInfoList[k].VendorID) {
			    if (svname) {
				*svname = pciVendorInfoList[k].VendorName;
			    }
			    if (subsys == NOSUBSYS) {
				return 3;
			    }
			    break;
			}
		    }
		    if (!pciVendorInfoList[k].VendorName) {
			return 2;
		    }
		    pSub = pDev[j]->Subsystem;
		    if (!pSub) {
			return 3;
		    }
		    for (k = 0; pSub[k]; k++) {
			if (svendor == pSub[k]->VendorID &&
			    subsys == pSub[k]->SubsystemID) {
			    if (sname)
				*sname = pSub[k]->SubsystemName;
			    return 4;
			}
		    }
		    /* No vendor/subsys match */
		    return 3;
		}
	    }
	    /* No device match */
	    return 1;
	}
    }
    /* No vendor match */
    return 0;
}

Bool
ScanPciFindPciNamesBySubsys(unsigned short svendor, unsigned short subsys,
			 const char **svname, const char **sname)
{
    int i, j;
    const pciSubsystemInfo **pSub;

    /* It's an error to not provide the Vendor */
    if (svendor == NOVENDOR)
	return -1;

    /* Initialise returns requested/provided to NULL */
    if (svname)
	*svname = NULL;
    if (subsys != NOSUBSYS && sname)
	*sname = NULL;

    for (i = 0; pciVendorSubsysInfoList[i].VendorName; i++) {
	if (svendor == pciVendorSubsysInfoList[i].VendorID) {
	    if (svname) {
		*svname = pciVendorSubsysInfoList[i].VendorName;
	    }
	    if (subsys == NOSUBSYS) {
		return 1;
	    }
	    pSub = pciVendorSubsysInfoList[i].Subsystem;
	    if (!pSub) {
		return 1;
	    }
	    for (j = 0; pSub[j]; j++) {
		if (subsys == pSub[j]->SubsystemID) {
		    if (sname) {
			*sname = pSub[j]->SubsystemName;
		    }
		}
	    }
	    /* No subsys match */
	    return 1;
	}
    }
    /* No vendor match */
    return 0;
}

CARD32
ScanPciFindPciClassBySubsys(unsigned short vendor, unsigned short subsys)
{
    int i, j;
    const pciSubsystemInfo **pSub;

    if (vendor == NOVENDOR || subsys == NOSUBSYS)
	return 0;

    for (i = 0; pciVendorSubsysInfoList[i].VendorName; i++) {
	if (vendor == pciVendorSubsysInfoList[i].VendorID) {
	    pSub = pciVendorSubsysInfoList[i].Subsystem;
	    if (!pSub) {
		return 0;
	    }
	    for (j = 0; pSub[j]; j++) {
		if (subsys == pSub[j]->SubsystemID) {
		    return pSub[j]->class;
		}
	    }
	    break;
	}
    }
    return 0;
}

CARD32
ScanPciFindPciClassByDevice(unsigned short vendor, unsigned short device)
{
    int i, j;
    const pciDeviceInfo **pDev;

    if (vendor == NOVENDOR || device == NODEVICE)
	return 0;

    for (i = 0; pciVendorInfoList[i].VendorName; i++) {
	if (vendor == pciVendorInfoList[i].VendorID) {
	    pDev = pciVendorInfoList[i].Device;
	    if (!pDev) {
		return 0;
	    }
	    for (j = 0; pDev[j]; j++) {
		if (device == pDev[j]->DeviceID) {
		    return pDev[j]->class;
		}
	    }
	    break;
	}
    }
    return 0;
}

#ifndef PCIDATA
void
ScanPciDisplayPCICardInfo(int verbosity)
{
    pciConfigPtr pcrp, *pcrpp;
    int i;

    xf86EnableIO();
    pcrpp = xf86scanpci(0);

    if (pcrpp == NULL) {
        xf86MsgVerb(X_NONE,0,"No PCI info available\n");
	return;
    }
    xf86MsgVerb(X_NONE,0,"Probing for PCI devices (Bus:Device:Function)\n\n");
    for (i = 0; (pcrp = pcrpp[i]); i++) {
	const char *svendorname = NULL, *subsysname = NULL;
	const char *vendorname = NULL, *devicename = NULL;
	Bool noCard = FALSE;
	const char *prefix1 = "", *prefix2 = "";

	xf86MsgVerb(X_NONE, -verbosity, "(%d:%d:%d) ",
		    pcrp->busnum, pcrp->devnum, pcrp->funcnum);

	/*
	 * Lookup as much as we can about the device.
	 */
	if (pcrp->pci_subsys_vendor || pcrp->pci_subsys_card) {
	    ScanPciFindPciNamesByDevice(pcrp->pci_vendor, pcrp->pci_device,
				     NOVENDOR, NOSUBSYS,
				     &vendorname, &devicename, NULL, NULL);
	} else {
	    ScanPciFindPciNamesByDevice(pcrp->pci_vendor, pcrp->pci_device,
				     pcrp->pci_subsys_vendor,
				     pcrp->pci_subsys_card,
				     &vendorname, &devicename,
				     &svendorname, &subsysname);
	}

	if (svendorname)
	    xf86MsgVerb(X_NONE, -verbosity, "%s ", svendorname);
	if (subsysname)
	    xf86MsgVerb(X_NONE, -verbosity, "%s ", subsysname);
	if (svendorname && !subsysname) {
	    if (pcrp->pci_subsys_card && pcrp->pci_subsys_card != NOSUBSYS) {
		xf86MsgVerb(X_NONE, -verbosity, "unknown card (0x%04x) ",
			    pcrp->pci_subsys_card);
	    } else {
		xf86MsgVerb(X_NONE, -verbosity, "card ");
	    }
	}
	if (!svendorname && !subsysname) {
	    /*
	     * We didn't find a text representation of the information 
	     * about the card.
	     */
	    if (pcrp->pci_subsys_vendor || pcrp->pci_subsys_card) {
		/*
		 * If there was information and we just couldn't interpret
		 * it, print it out as unknown, anyway.
		 */
		xf86MsgVerb(X_NONE, -verbosity,
			    "unknown card (0x%04x/0x%04x) ",
			    pcrp->pci_subsys_vendor, pcrp->pci_subsys_card);
	    } else
		noCard = TRUE;
	}
	if (!noCard) {
	    prefix1 = "using a ";
	    prefix2 = "using an ";
	}
	if (vendorname && devicename) {
	    xf86MsgVerb(X_NONE, -verbosity,"%s%s %s\n", prefix1, vendorname,
			devicename);
	} else if (vendorname) {
	    xf86MsgVerb(X_NONE, -verbosity,
			"%sunknown chip (DeviceId 0x%04x) from %s\n",
			prefix2, pcrp->pci_device, vendorname);
	} else {
	    xf86MsgVerb(X_NONE, -verbosity,
			"%sunknown chipset(0x%04x/0x%04x)\n",
			prefix2, pcrp->pci_vendor, pcrp->pci_device);
	}
    }
}
#endif

