Site hosted by Angelfire.com: Build your free website today!

Hands-on Projects for the Linux Graphics Subsystem

New book from Christos Karayiannis

Available in Amazon Kindle format at:

amazon.com   amazon.co.uk   amazon.de   amazon.es   amazon.fr   amazon.it

General PCI bus probe

Introduction

IMPORTANT NOTICE: The new section 4.2 and its subsections provide more detailed info on PCI than the current box. Also the pci "function" and the pci "configuration space" describe the same notion.

At this section the configuration space maintained at the PCI devices is examined. Some of the registers are set by the device (read only) and some registers are set by system software or firmware (usually BIOS).

To read the configuration space registers (through I/O registers or through BIOS32 as seen in section 4.2) of each device, the triplet bus number, device number (of the specific bus devices) and function number (of the specific device function) is used to indicate a certain device. A device is thus geographically indicated (plug and play: no device has to keep a certain slot - devices can change slots and the system starts as before).

Since we are interested here mainly in video cards the info is filtered only for this category of devices. The filtering is based on the configuration space info and the result is filling xf86PciVideoInfo[].

The first step is to read the configuration space registers maintained at each PCI device of the system. This is indicated by the white squares in the figure above. From the configuration space of the devices the X Server finds the video card device(s), indicated as VGA controller in the figure, and the card's configuration space fields are used to provide the necessary information, that the video drivers require to manipulate the video card(s). For instance to find the frame buffer area, which is the system memory area that corresponds to the video card memory and each write operation updates the screen. Also the system memory that maps the I/O registers of the specific video card.

This figure illustrates the frame buffer memory area discovered from the PCI configuration registers of the video card that is used in the example at the end of the current section. The framebuffer size is 256 MB and the start location is 0xC0000000 (3221225472 decimal)

The source code at a glance

X Server's main() calls InitOutput() and this FindPCIVideoInfo() to collect info about the PCI video devices. FindPCIVideoInfo() calls xf86scanpci(), which returns a pointer to an array of pciDevice items. Then FindPCIVideoInfo() issues a 'while' loop to add a xf86PciVideoInfo[] item for pciDevice, created of xf86scanpci() has video device characteristics and the filtered devices are further processed.

xf86scanpci() calls pciGenFindNext() in two stages: first as a pciFindFirst() call. This encapsulates pciGenFindNext() and sets the initial bus number to start from. Second a 'while' loop begins which scans the whole PCI system calling pciGenFindNext() foe every possible bus, device, function combination. The global variables:

int    pciBusNum;           /* Bus Number of current device */
int    pciDevNum;           /* Device number of current device */
int    pciFuncNum;          /* Function number of current device */
are used to keep count of the current bus, device, function triplet.

This triplet is used to indicate the specific configuration space of the current device at the specific bus and is passed at pciReadLong(), which read the configuration space and allows for critical information to be collected for a certain device. For instance if this is a video device or the system memory memory location and size it requires.

The figure bellow shows the three major routines mentioned above and its colors are the same as the color of the comment boxes that commends them:

InitOutput(), called by the X Server's main() routine, makes a bus probe (PCI bus probe for x86 platforms), by calling xf86BusProbe() as:

    /* Do a general bus probe.  This will be a PCI probe for x86 platforms */
    xf86BusProbe();

xf86BusProbe() just calls xf86PciProbe(), which by its turn mainly calls FindPCIVideoInfo().

FindPCIVideoInfo()

FindPCIVideoInfo() is the routine that fills the global xf86PciVideoInfo array with info that will be used then by the video drivers. xf86PciVideoInfo is defined in xf86pciBus.c as:

pciVideoPtr *xf86PciVideoInfo = NULL;		/* PCI probe for video hw */

pciVideoPtr points to pciVideoRec. Both are defined in xf86str.h as:

typedef struct {
    int			vendor;
    int			chipType;
    int			chipRev;
    int			subsysVendor;
    int			subsysCard;
    int			bus;
    int			device;
    int			func;
    int			class;
    int			subclass;
    int			interface;
    memType  	        memBase[6];
    memType  	        ioBase[6];
    int			size[6];
    unsigned char	type[6];
    memType   	        biosBase;
    int			biosSize;
    pointer		thisCard;
    Bool                validSize;
    Bool                validate;
    CARD32              listed_class;
} pciVideoRec, *pciVideoPtr;

FindPCIVideoInfo() is implemented as:

static void
FindPCIVideoInfo(void)
{
    pciConfigPtr pcrp, *pcrpp;
    int i = 0, j, k;
    int num = 0;
    pciVideoPtr info;
    int DoIsolateDeviceCheck = 0;

    if (xf86IsolateDevice.bus || xf86IsolateDevice.device || xf86IsolateDevice.func)
        DoIsolateDeviceCheck = 1;
    pcrpp = xf86PciInfo = xf86scanpci(0);
    getPciClassFlags(pcrpp);
    
    if (pcrpp == NULL) {
	xf86PciVideoInfo = NULL;
	return;
    }
    xf86PciBus = xf86GetPciBridgeInfo();
    
    while ((pcrp = pcrpp[i])) {
	int baseclass;
	int subclass;

	if (pcrp->listed_class & 0xffff) {
	    baseclass = (pcrp->listed_class >> 8) & 0xff;
	    subclass = pcrp->listed_class & 0xff;
	} else {
	    baseclass = pcrp->pci_base_class;
	    subclass = pcrp->pci_sub_class;
	}
	
	if (PCIINFOCLASSES(baseclass, subclass) &&
	    (DoIsolateDeviceCheck ?
	    (xf86IsolateDevice.bus == pcrp->busnum &&
	     xf86IsolateDevice.device == pcrp->devnum &&
	     xf86IsolateDevice.func == pcrp->funcnum) : 1)) {
	    num++;
	    xf86PciVideoInfo = xnfrealloc(xf86PciVideoInfo,
					  sizeof(pciVideoPtr) * (num + 1));
	    xf86PciVideoInfo[num] = NULL;
	    info = xf86PciVideoInfo[num - 1] = xnfalloc(sizeof(pciVideoRec));
	    info->validSize = FALSE;
	    info->vendor = pcrp->pci_vendor;
	    info->chipType = pcrp->pci_device;
	    info->chipRev = pcrp->pci_rev_id;
	    info->subsysVendor = pcrp->pci_subsys_vendor;
	    info->subsysCard = pcrp->pci_subsys_card;
	    info->bus = pcrp->busnum;
	    info->device = pcrp->devnum;
	    info->func = pcrp->funcnum;
	    info->class = baseclass;
	    info->subclass = pcrp->pci_sub_class;
	    info->interface = pcrp->pci_prog_if;
	    info->biosBase = PCIGETROM(pcrp->pci_baserom);
	    info->biosSize = pciGetBaseSize(pcrp->tag, 6, TRUE, NULL);
	    info->thisCard = pcrp;
	    info->validate = FALSE;
#ifdef INCLUDE_XF86_NO_DOMAIN
	    if ((PCISHAREDIOCLASSES(baseclass, subclass))
		&& (pcrp->pci_command & PCI_CMD_IO_ENABLE) &&
		(pcrp->pci_prog_if == 0)) {

		/*
		 * Attempt to ensure that VGA is actually routed to this
		 * adapter on entry.  This needs to be fixed when we finally
		 * grok host bridges (and multiple bus trees).
		 */
		j = info->bus;
		while (TRUE) {
		    PciBusPtr pBus = xf86PciBus;
		    while (pBus && j != pBus->secondary)
			pBus = pBus->next;
		    if (!pBus || !(pBus->brcontrol & PCI_PCI_BRIDGE_VGA_EN))
			break;
		    if (j == pBus->primary) {
			if (primaryBus.type == BUS_NONE) {
			    /* assumption: primary adapter is always VGA */
			    primaryBus.type = BUS_PCI;
			    primaryBus.id.pci.bus = pcrp->busnum;
			    primaryBus.id.pci.device = pcrp->devnum;
			    primaryBus.id.pci.func = pcrp->funcnum;
			} else if (primaryBus.type < BUS_last) {
			    xf86Msg(X_NOTICE,
				    "More than one primary device found\n");
			    primaryBus.type ^= (BusType)(-1);
			}
			break;
		    }
		    j = pBus->primary;
		}
	    }
#endif
	    
	    for (j = 0; j < 6; j++) {
		info->memBase[j] = 0;
		info->ioBase[j] = 0;
		if (PCINONSYSTEMCLASSES(baseclass, subclass)) {
		    info->size[j] =
			pciGetBaseSize(pcrp->tag, j, TRUE, &info->validSize);
		    pcrp->minBasesize = info->validSize;
		} else {
		    info->size[j] = pcrp->basesize[j];
		    info->validSize = pcrp->minBasesize;
		}
		info->type[j] = 0;
	    }

	    if (PCINONSYSTEMCLASSES(baseclass, subclass)) {
		/*
		 * Check of a PCI base is unassigned. If so
		 * attempt to fix it. Validation will determine
		 * if the value was correct later on.
		 */
		CARD32 *base = &pcrp->pci_base0;

		for (j = 0; j < 6; j++) {
		    if (!PCI_MAP_IS64BITMEM(base[j])) {
			if (info->size[j] && IsBaseUnassigned(base[j])) 
			    base[j] = pciCheckForBrokenBase(pcrp->tag, j);
		    } else {
			if (j == 5) /* bail out */
			    break;
			if (info->size[j]
			    && IsBaseUnassigned64(base[j],base[j+1])) {
			    base[j] = pciCheckForBrokenBase(pcrp->tag, j);
			    j++;
			    base[j] = pciCheckForBrokenBase(pcrp->tag, j);
			}
		    }
		}
	    }
	    
	    /*
	     * 64-bit base addresses are checked for and avoided on 32-bit
	     * platforms.
	     */
	    for (j = 0; j < 6; ++j) {
		CARD32  bar = (&pcrp->pci_base0)[j];

		if (bar != 0) {
		    if (bar & PCI_MAP_IO) {
			info->ioBase[j] = (memType)PCIGETIO(bar);
			info->type[j] = bar & PCI_MAP_IO_ATTR_MASK;
		    } else {
			info->type[j] = bar & PCI_MAP_MEMORY_ATTR_MASK;
			info->memBase[j] = (memType)PCIGETMEMORY(bar);
			if (PCI_MAP_IS64BITMEM(bar)) {
			    if (j == 5) {
				xf86MsgVerb(X_WARNING, 0,
				    "****BAR5 specified as 64-bit wide, "
				    "which is not possible. "
				    "Ignoring BAR5.****\n");
				info->memBase[j] = 0;
			    } else {
				CARD32  bar_hi = PCIGETMEMORY64HIGH((&pcrp->pci_base0)[j]);
#if defined(LONG64) || defined(WORD64)
				    /* 64 bit architecture */
				    info->memBase[j] |=
					(memType)bar_hi << 32;
#else
				    if (bar_hi != 0)
					info->memBase[j] = 0;
#endif
				    ++j;    /* Step over the next BAR */
			    }
			}
		    }
		}
	    }
	    info->listed_class = pcrp->listed_class;
	}
	i++;
    }

    /* If we haven't found a primary device try a different heuristic */
    if (primaryBus.type == BUS_NONE && num) {
	for (i = 0;  i < num;  i++) {
	    info = xf86PciVideoInfo[i];
	    pcrp = info->thisCard;
	    
	    if ((pcrp->pci_command & PCI_CMD_MEM_ENABLE) &&
		(num == 1 ||
		 ((info->class == PCI_CLASS_DISPLAY) &&
		  (info->subclass == PCI_SUBCLASS_DISPLAY_VGA)))) {
		if (primaryBus.type == BUS_NONE) {
		    primaryBus.type = BUS_PCI;
		    primaryBus.id.pci.bus = pcrp->busnum;
		    primaryBus.id.pci.device = pcrp->devnum;
		    primaryBus.id.pci.func = pcrp->funcnum;
		} else {
		    xf86Msg(X_NOTICE,
			    "More than one possible primary device found\n");
		    primaryBus.type ^= (BusType)(-1);
		}
	    }
	}
    }
    
    /* Print a summary of the video devices found */
    for (k = 0; k < num; k++) {
	const char *vendorname = NULL, *chipname = NULL;
	const char *prim = " ";
	char busnum[8];
	Bool memdone = FALSE, iodone = FALSE;

	i = 0; 
	info = xf86PciVideoInfo[k];
	xf86FormatPciBusNumber(info->bus, busnum);
	xf86FindPciNamesByDevice(info->vendor, info->chipType,
				 NOVENDOR, NOSUBSYS,
				 &vendorname, &chipname, NULL, NULL);
	if ((!vendorname || !chipname) &&
	    !PCIALWAYSPRINTCLASSES(info->class, info->subclass))
	    continue;
	if (xf86IsPrimaryPci(info))
	    prim = "*";

	xf86Msg(X_PROBED, "PCI:%s(%s:%d:%d) ", prim, busnum, info->device,
		info->func);
	if (vendorname)
	    xf86ErrorF("%s ", vendorname);
	else
	    xf86ErrorF("unknown vendor (0x%04x) ", info->vendor);
	if (chipname)
	    xf86ErrorF("%s ", chipname);
	else
	    xf86ErrorF("unknown chipset (0x%04x) ", info->chipType);
	xf86ErrorF("rev %d", info->chipRev);
	for (i = 0; i < 6; i++) {
	    if (info->memBase[i] &&
		(info->memBase[i] < (memType)(-1 << info->size[i]))) {
		if (!memdone) {
		    xf86ErrorF(", Mem @ ");
		    memdone = TRUE;
		} else
		    xf86ErrorF(", ");
		xf86ErrorF("0x%08lx/%d", info->memBase[i], info->size[i]);
	    }
	}
	for (i = 0; i < 6; i++) {
	    if (info->ioBase[i] &&
		(info->ioBase[i] < (memType)(-1 << info->size[i]))) {
		if (!iodone) {
		    xf86ErrorF(", I/O @ ");
		    iodone = TRUE;
		} else
		    xf86ErrorF(", ");
		xf86ErrorF("0x%04lx/%d", info->ioBase[i], info->size[i]);
	    }
	}
	if (info->biosBase &&
	    (info->biosBase < (memType)(-1 << info->biosSize)))
	    xf86ErrorF(", BIOS @ 0x%08lx/%d", info->biosBase, info->biosSize);
	xf86ErrorF("\n");
    }
}

FindPCIVideoInfo() calls xf86scanpci() as:

    pcrpp = xf86PciInfo = xf86scanpci(0);

xf86scanpci()

xf86scanpci() returns info for all PCI devices. The return type, pciConfigPtr, is defined in xf86Pci.h as:

/*
 * Data structure returned by xf86scanpci including contents of
 * PCI config space header
 */
typedef struct pci_device {
    PCITAG    tag;
    int	      busnum;
    int	      devnum;
    int	      funcnum;
    pciCfgSpc cfgspc;
    int	      basesize[7];	/* number of bits in base addr allocations */
    Bool      minBasesize;
    CARD32    listed_class;
    pointer   businfo;		/* pointer to secondary's bus info structure */
    Bool      fakeDevice;	/* Device added by system chipset support */
} pciDevice, *pciConfigPtr;

xf86scanpci() is a large function. It is implemented as:

pciConfigPtr *
xf86scanpci(int flags)
{
    pciConfigPtr devp;
    pciBusInfo_t *busp;
    int          idx = 0, i;
    PCITAG       tag;
    static Bool  done = FALSE;

    /*
     * if we haven't found PCI devices checking for pci_devp may
     * result in an endless recursion if platform/OS specific PCI
     * bus probing code calls this function from with in it.
     */
    if (done || pci_devp[0])
	return pci_devp;

    done = TRUE;

    pciInit();

#ifdef XF86SCANPCI_WRAPPER
    XF86SCANPCI_WRAPPER(SCANPCI_INIT);
#endif

    tag = pciFindFirst(0,0);  /* 0 mask means match any valid device */
    /* Check if no devices, return now */
    if (tag == PCI_NOT_FOUND) {
#ifdef XF86SCANPCI_WRAPPER
	XF86SCANPCI_WRAPPER(SCANPCI_TERM);
#endif
	return NULL;
    }

#ifdef DEBUGPCI
    ErrorF("xf86scanpci: tag = 0x%lx\n", tag);
#endif
#ifndef OLD_FORMAT
    xf86MsgVerb(X_INFO, 2, "PCI: PCI scan (all values are in hex)\n");
#endif

    while (idx < MAX_PCI_DEVICES && tag != PCI_NOT_FOUND) {
	devp = xcalloc(1, sizeof(pciDevice));
	if (!devp) {
	    xf86Msg(X_ERROR,
		"xf86scanpci: Out of memory after %d devices!!\n", idx);
	    return (pciConfigPtr *)NULL;
	}

	/* Identify pci device by bus, dev, func, and tag */
	devp->tag = tag;
	devp->busnum = PCI_BUS_FROM_TAG(tag);
	devp->devnum = PCI_DEV_FROM_TAG(tag);
	devp->funcnum = PCI_FUNC_FROM_TAG(tag);

	/* Read config space for this device */
	for (i = 0; i < 17; i++)  /* PCI hdr plus 1st dev spec dword */
	    devp->cfgspc.dwords[i] = pciReadLong(tag, i * sizeof(CARD32));

	/* Some broken devices don't implement this field... */
	if (devp->pci_header_type == 0xff)
	    devp->pci_header_type = 0;

	switch (devp->pci_header_type & 0x7f) {
	case 0:
	    /* Get base address sizes for type 0 headers */
	    for (i = 0; i < 7; i++)
		devp->basesize[i] =
		    pciGetBaseSize(tag, i, FALSE, &devp->minBasesize);
	    break;

	case 1:
	case 2:
	    /* Allow master aborts to complete normally on secondary buses */
	    if (!(devp->pci_bridge_control & PCI_PCI_BRIDGE_MASTER_ABORT_EN))
		break;
	    pciWriteByte(tag, PCI_PCI_BRIDGE_CONTROL_REG,
		devp->pci_bridge_control &
		     ~(PCI_PCI_BRIDGE_MASTER_ABORT_EN |
		       PCI_PCI_BRIDGE_SECONDARY_RESET));
	    break;

	default:
	    break;
	}

#ifdef OLD_FORMAT
	xf86MsgVerb(X_INFO, 2, "PCI: BusID 0x%.2x,0x%02x,0x%1x "
		    "ID 0x%04x,0x%04x Rev 0x%02x Class 0x%02x,0x%02x\n",
		    devp->busnum, devp->devnum, devp->funcnum,
		    devp->pci_vendor, devp->pci_device, devp->pci_rev_id,
		    devp->pci_base_class, devp->pci_sub_class);
#else
	xf86MsgVerb(X_INFO, 2, "PCI: %.2x:%02x:%1x: chip %04x,%04x"
		    " card %04x,%04x rev %02x class %02x,%02x,%02x hdr %02x\n",
		    devp->busnum, devp->devnum, devp->funcnum,
		    devp->pci_vendor, devp->pci_device,
		    devp->pci_subsys_vendor, devp->pci_subsys_card,
		    devp->pci_rev_id, devp->pci_base_class,
		    devp->pci_sub_class, devp->pci_prog_if,
		    devp->pci_header_type);
#endif

	pci_devp[idx++] = devp;
	tag = pciFindNext();

#ifdef DEBUGPCI
	ErrorF("xf86scanpci: tag = pciFindNext = 0x%lx\n", tag);
#endif
    }

    /* Restore modified data (in reverse order), and link buses */
    while (--idx >= 0) {
	devp = pci_devp[idx];
	switch (devp->pci_header_type & 0x7f) {
	case 0:
	    if ((devp->pci_base_class != PCI_CLASS_BRIDGE) ||
		(devp->pci_sub_class != PCI_SUBCLASS_BRIDGE_HOST))
		break;
	    pciBusInfo[devp->busnum]->bridge = devp;
	    pciBusInfo[devp->busnum]->primary_bus = devp->busnum;
	    break;

	case 1:
	case 2:
	    i = PCI_SECONDARY_BUS_EXTRACT(devp->pci_pp_bus_register, devp->tag);
	    if (i > devp->busnum) {
		if (pciBusInfo[i]) {
		    pciBusInfo[i]->bridge = devp;
		    /*
                     * The back link needs to be set here, and is unlikely to
		     * change.
		     */
		    devp->businfo = pciBusInfo[i];
		}
#ifdef ARCH_PCI_PCI_BRIDGE
		ARCH_PCI_PCI_BRIDGE(devp);
#endif
	    }
	    if (!(devp->pci_bridge_control & PCI_PCI_BRIDGE_MASTER_ABORT_EN))
		break;
	    pciWriteByte(devp->tag, PCI_PCI_BRIDGE_CONTROL_REG,
		devp->pci_bridge_control & ~PCI_PCI_BRIDGE_SECONDARY_RESET);
	    break;

	default:
	    break;
	}
    }

#ifdef XF86SCANPCI_WRAPPER
    XF86SCANPCI_WRAPPER(SCANPCI_TERM);
#endif

    /*
     * Lastly, link bridges to their secondary bus, after the architecture has
     * had a chance to modify these assignments.
     */
    for (idx = 0;  idx < pciNumBuses;  idx++) {
	if (!(busp = pciBusInfo[idx]) || !(devp = busp->bridge))
	    continue;
	devp->businfo = busp;
    }

#ifndef OLD_FORMAT
    xf86MsgVerb(X_INFO, 2, "PCI: End of PCI scan\n");
#endif

    return pci_devp;
}

xf86scanpci() calls pciInit(). This routine calls ARCH_PCI_OS_INIT if this routine is defined. For instance for the Linux OS and i386 platform ARCH_PCI_OS_INIT is defined in Pci.h as:

#  define ARCH_PCI_OS_INIT linuxPciInit
linuxPciInit() is implemented in linuxPci.c as:

void
linuxPciInit()
{
	struct stat st;
	if ((xf86Info.pciFlags == PCIForceNone) ||
	    (-1 == stat("/proc/bus/pci", &st))) {
		/* when using this as default for all linux architectures,
		   we'll need a fallback for 2.0 kernels here */
		return;
	}
	pciNumBuses    = 1;
	pciBusInfo[0]  = &linuxPci0;
	pciFindFirstFP = pciGenFindFirst;
	pciFindNextFP  = pciGenFindNext;
}

Recall from the stat man page that stat and friends "return information about a file" and also that "On success, zero is returned. On error, -1 is returned, and errno is set appropriately."

On success pciBusInfo[0] points to linuxPci0, which is defined in the same file as:

static pciBusInfo_t linuxPci0 = {
/* configMech  */	PCI_CFG_MECH_OTHER,
/* numDevices  */	32,
/* secondary   */	FALSE,
/* primary_bus */	0,
#ifdef PowerMAX_OS
/* ppc_io_base */	0,
/* ppc_io_size */	0,
#endif
/* funcs       */	&linuxFuncs0,
/* pciBusPriv  */	NULL,
/* bridge      */	NULL
};

linuxFuncs0 is given at the same file:

static pciBusFuncs_t linuxFuncs0 = {
/* pciReadLong      */	linuxPciCfgRead,
/* pciWriteLong     */	linuxPciCfgWrite,
/* pciSetBitsLong   */	linuxPciCfgSetBits,
#if defined(__powerpc__)
/* pciAddrHostToBus */	linuxPpcHostAddrToBusAddr,
/* pciAddrBusToHost */	linuxPpcBusAddrToHostAddr,
#else
/* pciAddrHostToBus */	pciAddrNOOP,
/* pciAddrBusToHost */	linuxTransAddrBusToHost,
#endif

/* pciControlBridge */		NULL,
/* pciGetBridgeBuses */		NULL,
/* pciGetBridgeResources */	NULL,

/* pciReadByte */	linuxPciCfgReadByte,
/* pciWriteByte */	linuxPciCfgWriteByte,

/* pciReadWord */	linuxPciCfgReadWord,
/* pciWriteWord */	linuxPciCfgWriteWord,
};

xf86scanpci() calls next pciFindFirst(), which as the comment in Pci.c says:

*	pciFindFirst()         - Find a PCI device by dev/vend id
This is called as:

    tag = pciFindFirst(0,0);  /* 0 mask means match any valid device */

pciFindFirst() is implemented as:

PCITAG
pciFindFirst(CARD32 id, CARD32 mask)
{
#ifdef DEBUGPCI
  ErrorF("pciFindFirst(0x%lx, 0x%lx), pciInit = %d\n", id, mask, pciInitialized);
#endif
  pciInit();

  pciDevid = id & mask;
  pciDevidMask = mask;

  return((*pciFindFirstFP)());
}

*pciFindFirstFP is defined in Pci.c as pciGenFindFirst():

PCITAG
pciGenFindFirst(void)
{
  /* Reset PCI bus number to start from top */
  pciBusNum = -1;

  return pciGenFindNext();
}

pciGenFindNext()

pciGenFindNext() aims to fill global variable pciBusInfo[], which is defined in the same file as:

pciBusInfo_t  *pciBusInfo[MAX_PCI_BUSES] = { NULL, };

Some other global variables that are used in this routine are:

int    pciBusNum;           /* Bus Number of current device */
int    pciDevNum;           /* Device number of current device */
int    pciFuncNum;          /* Function number of current device */
PCITAG pciDeviceTag;        /* Tag for current device */

It is implemented as:

/*
 * Generic find/read/write functions
 */
PCITAG
pciGenFindNext(void)
{
    CARD32 devid, tmp;
    int sec_bus, pri_bus;
    static int previousBus = 0;
    Bool speculativeProbe = FALSE;
    unsigned char base_class, sub_class;

#ifdef DEBUGPCI
    ErrorF("pciGenFindNext\n");
#endif

    for (;;) {

#ifdef DEBUGPCI
	ErrorF("pciGenFindNext: pciBusNum %d\n", pciBusNum);
#endif
	if (pciBusNum == -1) {
	    /*
	     * Start at top of the order
	     */
	    if (pciNumBuses <= 0)
		return(PCI_NOT_FOUND);

	    /* Skip ahead to the first bus defined by pciInit() */
	    for (pciBusNum = 0;  !pciBusInfo[pciBusNum];  ++pciBusNum);
	    pciFuncNum = 0;
	    pciDevNum = 0;
	    previousBus = pciBusNum; /* make sure previousBus exists */
	} else {
#ifdef PCI_MFDEV_SUPPORT
#ifdef DEBUGPCI
	    ErrorF("pciGenFindNext: pciFuncNum %d\n", pciFuncNum);
#endif
	    /*
	     * Somewhere in middle of order.  Determine who's
	     * next up
	     */
	    if (pciFuncNum == 0) {
		/*
		 * Is current dev a multifunction device?
		 */
		if (!speculativeProbe && pciMfDev(pciBusNum, pciDevNum))
		    /* Probe for other functions */
		    pciFuncNum = 1;
		else
		    /*
		     * No more functions this device. Next
		     * device please
		     */
		    pciDevNum ++;
	    } else if (++pciFuncNum >= 8) {
		/* No more functions for this device. Next device please */
		pciFuncNum = 0;
		pciDevNum ++;
	    }
#else
	    pciDevNum ++;
#endif
	    if (pciDevNum >= 32 ||
		!pciBusInfo[pciBusNum] ||
		pciDevNum >= pciBusInfo[pciBusNum]->numDevices) {
#ifdef DEBUGPCI
		ErrorF("pciGenFindNext: next bus\n");
#endif
		/*
		 * No more devices for this bus. Next bus please
		 */
		if (speculativeProbe) {
	NextSpeculativeBus:
		    xfree(pciBusInfo[pciBusNum]);
		    pciBusInfo[pciBusNum] = NULL;
		    speculativeProbe = FALSE;
		}

		if (++pciBusNum >= pciMaxBusNum) {
#ifdef DEBUGPCI
		    ErrorF("pciGenFindNext: out of buses\n");
#endif
		    /* No more buses.  All done for now */
		    return(PCI_NOT_FOUND);
		}

		pciDevNum = 0;
	    }
	}

#ifdef DEBUGPCI
	ErrorF("pciGenFindNext: pciBusInfo[%d] = 0x%lx\n", pciBusNum, pciBusInfo[pciBusNum]);
#endif
	if (!pciBusInfo[pciBusNum]) {
	    pciBusInfo[pciBusNum] = xnfalloc(sizeof(pciBusInfo_t));
	    *pciBusInfo[pciBusNum] = *pciBusInfo[previousBus];

	    speculativeProbe = TRUE;
	}

	/*
	 * At this point, pciBusNum, pciDevNum, and pciFuncNum have been
	 * advanced to the next device.  Compute the tag, and read the
	 * device/vendor ID field.
	 */
#ifdef DEBUGPCI
	ErrorF("pciGenFindNext: [%d, %d, %d]\n", pciBusNum, pciDevNum, pciFuncNum);
#endif
	pciDeviceTag = PCI_MAKE_TAG(pciBusNum, pciDevNum, pciFuncNum);
	inProbe = TRUE;
	devid = pciReadLong(pciDeviceTag, PCI_ID_REG);
	inProbe = FALSE;
#ifdef DEBUGPCI
	ErrorF("pciGenFindNext: pciDeviceTag = 0x%lx, devid = 0x%lx\n", pciDeviceTag, devid);
#endif
	if ((CARD16)(devid + 1U) <= (CARD16)1UL)
	    continue; /* Nobody home.  Next device please */

	/*
	 * Some devices mis-decode configuration cycles in such a way as to
	 * create phantom buses.
	 */
	if (speculativeProbe && (pciDevNum == 0) && (pciFuncNum == 0) &&
	    (PCI_BUS_NO_DOMAIN(pciBusNum) > 0)) {
	    for (;;) {
	        if (++pciDevNum >= pciBusInfo[pciBusNum]->numDevices)
		    goto NextSpeculativeBus;
		if (devid !=
		    pciReadLong(PCI_MAKE_TAG(pciBusNum, pciDevNum, 0),
			        PCI_ID_REG))
		    break;
	    }

	    pciDevNum = 0;
	}

	if (pciNumBuses <= pciBusNum)
	    pciNumBuses = pciBusNum + 1;

	speculativeProbe = FALSE;
	previousBus = pciBusNum;

#ifdef PCI_BRIDGE_SUPPORT
	/*
	 * Before checking for a specific devid, look for enabled
	 * PCI to PCI bridge devices.  If one is found, create and
	 * initialize a bus info record (if one does not already exist).
	 */
	tmp = pciReadLong(pciDeviceTag, PCI_CLASS_REG);
	base_class = PCI_CLASS_EXTRACT(tmp);
	sub_class = PCI_SUBCLASS_EXTRACT(tmp);
	if ((base_class == PCI_CLASS_BRIDGE) &&
	    ((sub_class == PCI_SUBCLASS_BRIDGE_PCI) ||
	     (sub_class == PCI_SUBCLASS_BRIDGE_CARDBUS))) {
	    tmp = pciReadLong(pciDeviceTag, PCI_PCI_BRIDGE_BUS_REG);
	    sec_bus = PCI_SECONDARY_BUS_EXTRACT(tmp, pciDeviceTag);
	    pri_bus = PCI_PRIMARY_BUS_EXTRACT(tmp, pciDeviceTag);
#ifdef DEBUGPCI
	    ErrorF("pciGenFindNext: pri_bus %d sec_bus %d\n",
		   pri_bus, sec_bus);
#endif
	    if (pciBusNum != pri_bus) {
		/* Some bridges do not implement the primary bus register */
		if ((PCI_BUS_NO_DOMAIN(pri_bus) != 0) ||
		    (sub_class != PCI_SUBCLASS_BRIDGE_CARDBUS))
		    xf86Msg(X_WARNING,
			    "pciGenFindNext:  primary bus mismatch on PCI"
			    " bridge 0x%08lx (0x%02x, 0x%02x)\n",
			    pciDeviceTag, pciBusNum, pri_bus);
		pri_bus = pciBusNum;
	    }
	    if ((pri_bus < sec_bus) && (sec_bus < pciMaxBusNum) &&
		pciBusInfo[pri_bus]) {
		/*
		 * Found a secondary PCI bus
		 */
		if (!pciBusInfo[sec_bus]) {
		    pciBusInfo[sec_bus] = xnfalloc(sizeof(pciBusInfo_t));

		    /* Copy parents settings... */
		    *pciBusInfo[sec_bus] = *pciBusInfo[pri_bus];
		}

		/* ...but not everything same as parent */
		pciBusInfo[sec_bus]->primary_bus = pri_bus;
		pciBusInfo[sec_bus]->secondary = TRUE;
		pciBusInfo[sec_bus]->numDevices = 32;

		if (pciNumBuses <= sec_bus)
		    pciNumBuses = sec_bus + 1;
	    }
	}
#endif

	/*
	 * Does this device match the requested devid after
	 * applying mask?
	 */
#ifdef DEBUGPCI
	ErrorF("pciGenFindNext: pciDevidMask = 0x%lx, pciDevid = 0x%lx\n", pciDevidMask, pciDevid);
#endif
	if ((devid & pciDevidMask) == pciDevid)
	    /* Yes - Return it.  Otherwise, next device */
	    return(pciDeviceTag); /* got a match */

    } /* for */
    /*NOTREACHED*/
}

pciGenFindNext starts from the first Bus returned by pciInit() e.g. 0 and starts with the first Function (0), and first Device (0). Also memory space for pciBusInfo[pciBusNum] is allocated e.g. pciBusInfo[0].

pciBusInfo[] is defined in Pci.c as:

pciBusInfo_t  *pciBusInfo[MAX_PCI_BUSES] = { NULL, };

where pciBusInfo_t is defined in Pci.h as:

/*
 * pciBusInfo_t - One structure per defined PCI bus
 */
typedef struct pci_bus_info {
	unsigned char  configMech;   /* PCI config type to use      */
	unsigned char  numDevices;   /* Range of valid devnums      */
	unsigned char  secondary;    /* Boolean: bus is a secondary */
	int            primary_bus;  /* Parent bus                  */
#ifdef PowerMAX_OS
	unsigned long  ppc_io_base;  /* PowerPC I/O spc membase     */
	unsigned long  ppc_io_size;  /* PowerPC I/O spc size        */
#endif
	pciBusFuncs_p  funcs;        /* PCI access functions        */
	void          *pciBusPriv;   /* Implementation private data */
	pciConfigPtr   bridge;       /* bridge that opens this bus  */
} pciBusInfo_t;

Some of the highlights of the code are:

When starting the bus investigation of the current system pciBusNum is set to -1 by pciGenFindFirst(). This is a non-real world value that indicates that we are at the beginning and no useful value is already set.

	if (pciBusNum == -1) {
	    /*
	     * Start at top of the order
	     */

	    for (pciBusNum = 0;  !pciBusInfo[pciBusNum];  ++pciBusNum);
	    pciFuncNum = 0;
	    pciDevNum = 0;

This sets the initial values of function, device for all pciBusInfo[] elements to zero.

	    if (pciFuncNum == 0) {
In the case pciBusNum has a valid value (so there is a bus) we check in the case we are in the first function number, zero, if another function exist and we have a multifunction device.

	    } else if (++pciFuncNum >= 8) {
		/* No more functions for this device. Next device please */
		pciFuncNum = 0;
		pciDevNum ++;
We keep checking until the function number is 8, the max.

In the case we have for certain a non multifunctional device (PCI_MFDEV_SUPPORT is not set) we simply advance to the next device:

#else
	    pciDevNum ++;
Having completed the function(s) info for the device(s) of the current bus we move on to the next bus. The criteria are:
	    if (pciDevNum >= 32 ||
		!pciBusInfo[pciBusNum] ||
		pciDevNum >= pciBusInfo[pciBusNum]->numDevices) {
We proceed only in the case we have not reached the max bus number:
		if (++pciBusNum >= pciMaxBusNum) {
At this point and in the case we just started the probing (the other case is to be in the middle of the probing) a pciBusInfo[], which is the array that gets filled in the current routine, is allocated: if (!pciBusInfo[pciBusNum]) { pciBusInfo[pciBusNum] = xnfalloc(sizeof(pciBusInfo_t)); Next we meet the following comment:
	/*
	 * At this point, pciBusNum, pciDevNum, and pciFuncNum have been
	 * advanced to the next device.  Compute the tag, and read the
	 * device/vendor ID field.
	 */
For the current bus, device, function numbers we compute the Pci tag. What exactly is this? As we see in section 4.2.1, where we discuss PCI in detail, the 1st configuration method of PCI involves the CONFIG_ADDRESS, which has the form:

31 30 - 24 23 - 16 15 - 11 10 - 8 7 - 2 1 - 0
Enable Bit Reserved Bus Number Device Number Function Number Register Number 00

Following the notion of the 1st configuration method (which is not mandatory, there is a 2nd as well) the pciDeviceTag is the result of macro PCI_MAKE_TAG, which for the given triplet of bus, device, and function numbers sets those numbers at the exact position as found in the CONFIG_ADDRESS. For instance device number is masked with 0x00001fu, which is 1f in hex unsigned or 11111 in binary. Therefore only 5 bits remain and those bits are left-shifted to the position 15-11 of the CONFIG_ADDRESS.

PCI_MAKE_TAG is defined in Pci.h as:

#define PCI_MAKE_TAG(b,d,f)  ((((b) & (PCI_DOMBUS_MASK)) << 16) | \
			      (((d) & 0x00001fu) << 11) | \
			      (((f) & 0x000007u) << 8))
The code line that uses PCI_MAKE_TAG is:

	pciDeviceTag = PCI_MAKE_TAG(pciBusNum, pciDevNum, pciFuncNum);
Next pciReadLong() is used to read the Device ID. It actually reads both Device ID and Vendor ID, since it reads a 32-bit long. Those fields of the PCI configuration space are highlighted in the following figure, which reminds the configuration space format (see section 4.2.1 for more information):

register bits 31-24 bits 23-16 bits 15-8 bits 7-0
00 Device ID Vendor ID
04 Status Command
08 Class code Subclass Prog IF Revision ID
0C BIST Header type Latency Timer Cache Line Size
10 Base address #0 (BAR0)
14 Base address #1 (BAR1)
18 Base address #2 (BAR2)
1C Base address #3 (BAR3)
20 Base address #4 (BAR4)
24 Base address #5 (BAR5)
28 Cardbus CIS Pointer
2C Subsystem ID Subsystem Vendor ID
30 Expansion ROM base address
34 Reserved Capabilities Pointer
38 Reserved
3C Max latency Min Grant Interrupt PIN Interrupt Line

The code part in pciGenFindNext() that calls pciReadLong() is:

	inProbe = TRUE;
	devid = pciReadLong(pciDeviceTag, PCI_ID_REG);
	inProbe = FALSE;

PCI_ID_REG is the offset in the configuration space that a long value is read. It is defined in xf86Pci.h as:

#define PCI_ID_REG			0x00

Therefore the long is read from the start of the 'function' (a.k.a PCI configuration space) where the Vendor ID and the device ID is found.

pciReadLong() is implemented as:

CARD32
pciReadLong(PCITAG tag, int offset)
{
  int bus = PCI_BUS_FROM_TAG(tag);

#ifdef DEBUGPCI
  ErrorF("pciReadLong(0x%lx, %d)\n", tag, offset);
#endif
  pciInit();

  if ((bus >= 0) && ((bus < pciNumBuses) || inProbe) && pciBusInfo[bus] &&
	pciBusInfo[bus]->funcs->pciReadLong) {
    CARD32 rv = (*pciBusInfo[bus]->funcs->pciReadLong)(tag, offset);

    PCITRACE(1, ("pciReadLong: tag=0x%x [b=%d,d=%d,f=%d] returns 0x%08x\n",
		 tag, bus, PCI_DEV_FROM_TAG(tag), PCI_FUNC_FROM_TAG(tag), rv));
    return(rv);
   }

  return(PCI_NOT_FOUND);
}

which means that in the Linux case (see linuxFuncs0 above) pciReadLong() is substituted by linuxPciCfgRead():

static CARD32
linuxPciCfgRead(PCITAG tag, int off)
{
	int	fd;
	CARD32	val = 0xffffffff;

	if (-1 != (fd = linuxPciOpenFile(tag,FALSE))) {
		lseek(fd,off,SEEK_SET);
		read(fd,&val,4);
	}
	return PCI_CPU(val);
}

linuxPciOpenFile() opens the proc (process information pseudo-filesystem) specific file according to bus, device, function combination that was passed in the tag (the first argument of linuxPciOpenFile). For instance it opens /proc/bus/pci/00/11.0 (see an example of the /proc/bus/pci contents here).

For more details on the mechanism of /proc see section 4.2.2.

An lseek() (reposition read/write file offset) call is used then to start the read() that follows from 'off' bytes. The offset is PCI_ID_REG, which as seen previously is zero. Therefore the Vendor ID and the device ID is read.

The next part of pciGenFindNext() code with interest is:

	if ((CARD16)(devid + 1U) <= (CARD16)1UL)
	    continue; /* Nobody home.  Next device please */

Those two lines try to find if a device is found in the current bus of the running loop with the current device number and a valid id in the current configuration space (function).

At the moment pciReadLong() has obtained the first 32bit of the current configuration space at devid. Also in the case of failure devid becomes PCI_NOT_FOUND. This is defined in xf86Pci.h as:

#define PCI_NOT_FOUND	0xFFFFFFFFU
Since we care only the for the "Device ID", which is the 16-bit part of devid (see previously highlighted field of the configuration space) the 32-bit value devid is casted to the 16-bit type of CARD16. CARD16 and CARD32 are defined in Xmd.h as:
typedef unsigned short CARD16;
...
typedef unsigned long CARD32;
To better understand the condition:
(CARD16)(devid + 1U) <= (CARD16)1UL
we find a nice explanation at Type Conversions in C:

Conversion rules are more complicated when unsigned operands are involved. The problem is that comparisons between signed and unsigned values are machine-dependent, because they depend on the sizes of the various integer types. For example, suppose that int is 16 bits and long is 32 bits. Then -1L < 1U, because 1U, which is an unsigned int, is promoted to a signed long. But -1L > 1UL because -1L is promoted to unsigned long and thus appears to be a large positive number.

Conversions take place across assignments; the value of the right side is converted to the type of the left, which is the type of the result.

A character is converted to an integer, either by sign extension or not, as described above.

Longer integers are converted to shorter ones or to chars by dropping the excess high-order bits.

Using the previous information we can make the parts together. The return value in the case of failure is 0xFFFFFFFFU, where U stands for unsigned value. This is number 4294967295 as an unsigned 32-bit value, however it is also the representation of -1 if we consider it a signed 32-bit value.

pciReadLong() returned the value read as a CARD32 type, an unsigned long. The usual C return value for failure (-1) is to returned now to devid with its unsigned form 0xFFFFFFFFU. In the case of failure devid + 1U, which is 0xFFFFFFFFU plus 1U becomes 0. Since this result is <= 1 and the condition holds true (nobody home) and this means no device is found. See also the device id list at sourceforge.net.

In the case of success the Device ID part is obtained after the casting with CARD16. We have to denote that the PCI configuration space is "little endian", which means that the first register of the configuration space "Device ID - Vendor ID" is read in the devid as "Vendor ID - Device ID" and the remaining part is the lower one Device ID.

	if ((devid & pciDevidMask) == pciDevid)
	    /* Yes - Return it.  Otherwise, next device */
	    return(pciDeviceTag); /* got a match */
Any valid device is returned since no restrictions apply here. Both pciDevidMask and pciDevid were set to 0 by pciFindFirst().

We continue xf86scanpci() with the next part of code:

    while (idx < MAX_PCI_DEVICES && tag != PCI_NOT_FOUND) {
	devp = xcalloc(1, sizeof(pciDevice));
MAX_PCI_DEVICES is defined in Pci.h as:
#define MAX_PCI_DEVICES 128	/* Max number of devices accomodated */
				/* by xf86scanpci	
In the case pciGenFindNext() has returned a valid PCI device and the number of the total devices are 128 at most space is allocated for a pciDevice struct. Why is the total pci device number of the system 128? This is 32 devices per bus x 4 buses. The only restriction for this number is to be large enough to contain all devices. For more read this message from the XFree86 mailing list.

pciDevice is defined in xf86Pci.h as the typedefed struct we also met at the start of the current section when we described pciConfigPtr:

/*
 * Data structure returned by xf86scanpci including contents of
 * PCI config space header
 */
typedef struct pci_device {
    PCITAG    tag;
    int	      busnum;
    int	      devnum;
    int	      funcnum;
    pciCfgSpc cfgspc;
    int	      basesize[7];	/* number of bits in base addr allocations */
    Bool      minBasesize;
    CARD32    listed_class;
    pointer   businfo;		/* pointer to secondary's bus info structure */
    Bool      fakeDevice;	/* Device added by system chipset support */
} pciDevice, *pciConfigPtr;

Next some of the fields of pciDevice can instantly be filled by the bus, device and function values found in the tag previously returned by pciGenFindNext():

	/* Identify pci device by bus, dev, func, and tag */
	devp->tag = tag;
	devp->busnum = PCI_BUS_FROM_TAG(tag);
	devp->devnum = PCI_DEV_FROM_TAG(tag);
	devp->funcnum = PCI_FUNC_FROM_TAG(tag);
The previous macros are defined in Pci.h as:
#ifndef PCI_DOM_MASK
# define PCI_DOM_MASK 0x0ffu
#endif
#define PCI_DOMBUS_MASK (((PCI_DOM_MASK) << 8) | 0x0ffu)

#define PCI_DOM_FROM_TAG(tag)  (((tag) >> 24) & (PCI_DOM_MASK))
#define PCI_BUS_FROM_TAG(tag)  (((tag) >> 16) & (PCI_DOMBUS_MASK))
#define PCI_DEV_FROM_TAG(tag)  (((tag) & 0x0000f800u) >> 11)
#define PCI_FUNC_FROM_TAG(tag) (((tag) & 0x00000700u) >> 8)
Next the configuration space of the current device is read to the cfgspc field of the pciDevice, using pciReadLong(). As we previously saw for the Linux implementation pciReadLong() becomes linuxPciCfgRead(), which was previously introduced.
	/* Read config space for this device */
	for (i = 0; i < 17; i++)  /* PCI hdr plus 1st dev spec dword */
	    devp->cfgspc.dwords[i] = pciReadLong(tag, i * sizeof(CARD32));

	/* Some broken devices don't implement this field... */
	if (devp->pci_header_type == 0xff)
	    devp->pci_header_type = 0;
At this point the configuration space is read to devp->cfgspc.dwords[]:

register bits 31-24 bits 23-16 bits 15-8 bits 7-0
00 Device ID Vendor ID
04 Status Command
08 Class code Subclass Prog IF Revision ID
0C BIST Header type Latency Timer Cache Line Size
10 Base address #0 (BAR0)
14 Base address #1 (BAR1)
18 Base address #2 (BAR2)
1C Base address #3 (BAR3)
20 Base address #4 (BAR4)
24 Base address #5 (BAR5)
28 Cardbus CIS Pointer
2C Subsystem ID Subsystem Vendor ID
30 Expansion ROM base address
34 Reserved Capabilities Pointer
38 Reserved
3C Max latency Min Grant Interrupt PIN Interrupt Line

Notice that pci_header_type is not a pciDevice field but is a shortcut to the fields of pciDevice, defined in xf86Pci.h as:

#define pci_header_type		      cfgspc.regs.bhlc.bhlc.header_type

Next the header type (see highlighted field in the previous configuration space figure) is examined:

	switch (devp->pci_header_type & 0x7f) {
	case 0:
	    /* Get base address sizes for type 0 headers */
	    for (i = 0; i < 7; i++)
		devp->basesize[i] =
		    pciGetBaseSize(tag, i, FALSE, &devp->minBasesize);
	    break;
From this OSDev page we read:

Header Type: Identifies the layout of the rest of the header beginning at byte 0x10 of the header and also specifies whether or not the device has multiple functions. Where a value of 0x00 specifies a general device, a value of 0x01 specifies a PCI-to-PCI bridge, and a value of 0x02 specifies a CardBus bridge. If bit 7 of this register is set, the device has multiple functions; otherwise, it is a single function device.

For a general device pciGetBaseSize() is called to return to devp->basesize[] the size of the memory (or I/O) space that are required for the memory (or I/O) mapping of each BAR in bytes. The following comment of pciGetBaseSize() is a little misleading since macro PCIGETMEMORY used in this routine turns the size from bits to bytes. See also the example at the end of the FindPCIVideoInfo() box, where the value of 268435456 that is used is certainly in bytes:

pciGetBaseSize() returns the size of a PCI base address mapping in bits. 

register bits 31-24 bits 23-16 bits 15-8 bits 7-0
00 Device ID Vendor ID
04 Status Command
08 Class code Subclass Prog IF Revision ID
0C BIST Header type Latency Timer Cache Line Size
10 Base address #0 (BAR0)
14 Base address #1 (BAR1)
18 Base address #2 (BAR2)
1C Base address #3 (BAR3)
20 Base address #4 (BAR4)
24 Base address #5 (BAR5)
28 Cardbus CIS Pointer
2C Subsystem ID Subsystem Vendor ID
30 Expansion ROM base address
34 Reserved Capabilities Pointer
38 Reserved
3C Max latency Min Grant Interrupt PIN Interrupt Line

For the mechanism of this routine see section 4.2.1. Also a synopsis of this mechanism is given by this OSDev page:

To determine the amount of address space needed by a PCI device, you must save the original value of the BAR, write a value of all 1's to the register, then read it back.

The part of pciGetBaseSize() that saves the original value of the BAR, then writes the all 1s value (0xffffffff), reads the BAR and then restores the original value is the following:

  addr1 = pciReadLong(tag, offset);

. . .

  if (destructive) {
    pciWriteLong(tag, offset, 0xffffffff);
    mask1 = pciReadLong(tag, offset);
    pciWriteLong(tag, offset, addr1);

pciReadLong() was described previously. For the Linux implementation this routine used pseudo-filesystem /proc to read the relevant info. pciWriteLong() for Linux also uses /proc to write to the pci configuration space.

Linux pciWriteLong() implementation

pciWriteLong() for Linux is substituted by linuxPciCfgWrite(), which like linuxPciCfgRead() also uses linuxPciOpenFile() to open the /proc/bus/pci/ pseudo-filesystem. As we read from this link about /proc/bus/pci/: "This is maintained by the pci driver. It's not a real file, but data wich is collected by the driver when try to read from this "file" (there is a hook function you specify in a struct which you register via a call to proc_register)."

static void
linuxPciCfgWrite(PCITAG tag, int off, CARD32 val)
{
	int	fd;

	if (-1 != (fd = linuxPciOpenFile(tag,TRUE))) {
		lseek(fd,off,SEEK_SET);
		val = PCI_CPU(val);
		write(fd,&val,4);
	}
}

The point here is that the write() system call above is not the usual write() used for a conventional file 'write' operation but the specific write() registered with the /proc pseudofile system. As we see in Linux file proc.c the function registered for a 'write' operation in proc is proc_bus_pci_write():

 static struct file_operations proc_bus_pci_operations = {
         .llseek         = proc_bus_pci_lseek,
         .read           = proc_bus_pci_read,
         .write          = proc_bus_pci_write,
         .ioctl          = proc_bus_pci_ioctl,
 #ifdef HAVE_PCI_MMAP
         .open           = proc_bus_pci_open,
         .release        = proc_bus_pci_release,
         .mmap           = proc_bus_pci_mmap,
 #ifdef HAVE_ARCH_PCI_GET_UNMAPPED_AREA
         .get_unmapped_area = get_pci_unmapped_area,
 #endif /* HAVE_ARCH_PCI_GET_UNMAPPED_AREA */
 #endif /* HAVE_PCI_MMAP */
 };

proc_bus_pci_write() calls pci_write_config_dword(), which is implemented as:

 static inline int pci_write_config_dword(struct pci_dev *dev, int where, u32 val)
 {
         return pci_bus_write_config_dword (dev->bus, dev->devfn, where, val);
 } 

pci_bus_write_config_dword() is implemented as:

 int pci_bus_write_config_##size \
         (struct pci_bus *bus, unsigned int devfn, int pos, type value)  \
 {                                                                       \
         int res;                                                        \
         unsigned long flags;                                            \
         if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER;       \
         spin_lock_irqsave(&pci_lock, flags);                            \
         res = bus->ops->write(bus, devfn, pos, len, value);             \
         spin_unlock_irqrestore(&pci_lock, flags);                       \
         return res;                                                     \
 } 

bus is of type pci_bus, defined as:

 struct pci_bus {
         struct list_head node;          /* node in list of buses */
         struct pci_bus  *parent;        /* parent bus this bridge is on */
         struct list_head children;      /* list of child buses */
         struct list_head devices;       /* list of devices on this bus */
         struct pci_dev  *self;          /* bridge device as seen by parent */
         struct resource *resource[PCI_BUS_NUM_RESOURCES];
                                         /* address space routed to this bus */
 
         struct pci_ops  *ops;           /* configuration access functions */
         void            *sysdata;       /* hook for sys-specific extension */
         struct proc_dir_entry *procdir; /* directory entry in /proc/bus/pci */
 
         unsigned char   number;         /* bus number */
         unsigned char   primary;        /* number of primary bridge */
         unsigned char   secondary;      /* number of secondary bridge */
         unsigned char   subordinate;    /* max number of subordinate buses */
 
         char            name[48];
 
         unsigned short  bridge_ctl;     /* manage NO_ISA/FBB/et al behaviors */
         unsigned short  pad2;
         struct device           *bridge;
         struct class_device     class_dev;
         struct bin_attribute    *legacy_io; /* legacy I/O for this bus */
         struct bin_attribute    *legacy_mem; /* legacy mem */
 };

ops is of type pci_ops defined as:

 struct pci_ops {
         int (*read)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val);
         int (*write)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val);
 };

The specific pci_ops of the bus, assigned when the system is initialised are pci_root_ops:

 struct pci_ops pci_root_ops = {
         .read = pci_read,
         .write = pci_write,
 };

Why the pci_ops filed of a x86 bus is sets to pci_root_ops?

From The PCI Interface we read: "The various PCI buses in the system are detected at system boot, and that's when the struct pci_bus items are created and associated with their features, including the ops field."

The source code thread that does that in a short is the following:

In file x86_init.c:

 struct x86_init_ops x86_init __initdata = {

. . .
         .pci = {
                 .init                   = x86_default_pci_init,
                 .init_irq               = x86_default_pci_init_irq,
                 .fixup_irqs             = x86_default_pci_fixup_irqs,
         },
. . .

In file pci_x86.h:

#  define x86_default_pci_init          pci_legacy_init

In file legacy.c:

 int __init pci_subsys_init(void)
 {
         /*
          * The init function returns an non zero value when
          * pci_legacy_init should be invoked.
          */
         if (x86_init.pci.init())
                 pci_legacy_init();
 
         pcibios_fixup_peer_bridges();
         x86_init.pci.init_irq();
         pcibios_init();
 
         return 0;
 }
 subsys_initcall(pci_subsys_init); 

In file legacy.c:

 int __init pci_legacy_init(void)
 {
         if (!raw_pci_ops) {
                 printk("PCI: System does not support PCI\n");
                 return 0;
         }
 
         printk("PCI: Probing PCI hardware\n");
         pci_root_bus = pcibios_scan_root(0);
         if (pci_root_bus)
                 pci_bus_add_devices(pci_root_bus);
 
         return 0;
 }

In file common.c:

 struct pci_bus * __devinit pcibios_scan_root(int busnum)
 {
. . .
         bus = pci_scan_bus_parented(NULL, busnum, &pci_root_ops, sd);
. . . 

pci_read() and pci_write() are defied in common.c. For instance consider pci_write():

 static int pci_write(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 value)
 {
         return raw_pci_write(pci_domain_nr(bus), bus->number,
                                   devfn, where, size, value);
 }

raw_pci_write() is implemented as:

 int raw_pci_write(unsigned int domain, unsigned int bus, unsigned int devfn,
                                                 int reg, int len, u32 val)
 {
         if (domain == 0 && reg < 256 && raw_pci_ops)
                 return raw_pci_ops->write(domain, bus, devfn, reg, len, val);
         if (raw_pci_ext_ops)
                 return raw_pci_ext_ops->write(domain, bus, devfn, reg, len, val);
         return -EINVAL;
 }

Which is the raw_pci_ops?

To find the raw_pci_ops read and write members we follow the next Linux code thread:

When the x86 Linux kernel runs it calls the following macro in x86/pci/init.c:

arch_initcall(pci_access_init);
where pci_access_init() is implemented as:

 static __init int pci_access_init(void)
 {
         int type __maybe_unused = 0;
 
 #ifdef CONFIG_PCI_DIRECT
         type = pci_direct_probe();
 #endif
 #ifdef CONFIG_PCI_MMCONFIG
         pci_mmcfg_init(type);
 #endif
         if (raw_pci_ops)
                 return 0;
 #ifdef CONFIG_PCI_BIOS
         pci_pcbios_init();
 #endif
         /*
          * don't check for raw_pci_ops here because we want pcbios as last
          * fallback, yet it's needed to run first to set pcibios_last_bus
          * in case legacy PCI probing is used. otherwise detecting peer busses
          * fails.
          */
 #ifdef CONFIG_PCI_DIRECT
         pci_direct_init(type);
 #endif
         if (!raw_pci_ops)
                 printk(KERN_ERR
                 "PCI: Fatal: No config space access function found\n");
 
         dmi_check_pciprobe();
 
         return 0;
 }

Also pci_raw_ops is defined as:

 struct pci_raw_ops {
         int (*read)(int dom, int bus, int devfn, int reg, int len, u32 *val);
         int (*write)(int dom, int bus, int devfn, int reg, int len, u32 val);
 };
 
 extern struct pci_raw_ops *raw_pci_ops;

For the case study we accept that CONFIG_PCI_BIOS is set and therefore pci_pcbios_init() is called in pci_access_init(). Function pci_pcbios_init() is implemented in pcbios.c as:

 void __init pci_pcbios_init(void)
 {
         if ((pci_probe & PCI_PROBE_BIOS) 
                 && ((raw_pci_ops = pci_find_bios()))) {
                 pci_probe |= PCI_BIOS_SORT;
                 pci_bios_present = 1;
         }
 }

pci_find_bios() is implemented in the same file as:

 static struct pci_raw_ops * __devinit pci_find_bios(void)
 {
         union bios32 *check;
         unsigned char sum;
         int i, length;
 
         /*
          * Follow the standard procedure for locating the BIOS32 Service
          * directory by scanning the permissible address range from
          * 0xe0000 through 0xfffff for a valid BIOS32 structure.
          */
 
         for (check = (union bios32 *) __va(0xe0000);
              check <= (union bios32 *) __va(0xffff0);
              ++check) {
                 long sig;
                 if (probe_kernel_address(&check->fields.signature, sig))
                         continue;
 
                 if (check->fields.signature != BIOS32_SIGNATURE)
                         continue;
                 length = check->fields.length * 16;
                 if (!length)
                         continue;
                 sum = 0;
                 for (i = 0; i < length ; ++i)
                         sum += check->chars[i];
                 if (sum != 0)
                         continue;
                 if (check->fields.revision != 0) {
                         printk("PCI: unsupported BIOS32 revision %d at 0x%p\n",
                                 check->fields.revision, check);
                         continue;
                 }
                 DBG("PCI: BIOS32 Service Directory structure at 0x%p\n", check);
                 if (check->fields.entry >= 0x100000) {
                         printk("PCI: BIOS32 entry (0x%p) in high memory, "
                                         "cannot use.\n", check);
                         return NULL;
                 } else {
                         unsigned long bios32_entry = check->fields.entry;
                         DBG("PCI: BIOS32 Service Directory entry at 0x%lx\n",
                                         bios32_entry);
                         bios32_indirect.address = bios32_entry + PAGE_OFFSET;
                         if (check_pcibios())
                                 return &pci_bios_access;
                 }
                 break;  /* Hopefully more than one BIOS32 cannot happen... */
         }
 
         return NULL;
 }

This routine tries to find the BIOS32 Service directory (see section 4.2.1 and also the relevant routine pcibios_init() covered in section 4.2.2). On success pci_bios_access is returned, defined as:

 static struct pci_raw_ops pci_bios_access = {
         .read =         pci_bios_read,
         .write =        pci_bios_write
 };

pci_bios_write() is implemented as:

 static int pci_bios_write (int seg, int bus, int devfn, int reg, int len, u32 value)
 {
         unsigned long result = 0;
         unsigned long flags;
         unsigned long bx = (bus << 8) | devfn;
 
         if ((bus > 255) || (devfn > 255) || (reg > 255)) 
                 return -EINVAL;
 
         spin_lock_irqsave(&pci_config_lock, flags);
 
         switch (len) {
         case 1:
                 __asm__("lcall *(%%esi); cld\n\t"
                         "jc 1f\n\t"
                         "xor %%ah, %%ah\n"
                         "1:"
                         : "=a" (result)
                         : "" (PCIBIOS_WRITE_CONFIG_BYTE),
                           "c" (value),
                           "b" (bx),
                           "D" ((long)reg),
                           "S" (&pci_indirect));
                 break;
         case 2:
                 __asm__("lcall *(%%esi); cld\n\t"
                         "jc 1f\n\t"
                         "xor %%ah, %%ah\n"
                         "1:"
                         : "=a" (result)
                         : "" (PCIBIOS_WRITE_CONFIG_WORD),
                           "c" (value),
                           "b" (bx),
                           "D" ((long)reg),
                           "S" (&pci_indirect));
                 break;
         case 4:
                 __asm__("lcall *(%%esi); cld\n\t"
                         "jc 1f\n\t"
                         "xor %%ah, %%ah\n"
                         "1:"
                         : "=a" (result)
                         : "" (PCIBIOS_WRITE_CONFIG_DWORD),
                           "c" (value),
                           "b" (bx),
                           "D" ((long)reg),
                           "S" (&pci_indirect));
                 break;
         }
 
         spin_unlock_irqrestore(&pci_config_lock, flags);
 
         return (int)((result & 0xff00) >> 8);
 }

Again as we saw with the other examples of section 4.2.1 the BIOS32 routines as described in the PCI BIOS SPECIFICATION are used. In the current example the specific routine is "Write Configuration Byte".

From the Specification text we read:

4.3.4. Write Configuration Byte

This function allows writing individual bytes to the configuration space of a specific device.

ENTRY:

     [AH]       PCI_FUNCTION_ID
     [AL]       WRITE_CONFIG_BYTE
     [BH]       Bus Number (0...255)
     [BL]       Device Number in upper 5 bits,
                Function Number in lower 3 bits
     [DI]       Register Number (0...255)
     [CL]       Byte Value to Write

EXIT:

     [AH] Return Code:
                 SUCCESSFUL
     [CF] Completion Status, set = error, reset = success

PCIBIOS_WRITE_CONFIG_BYTE is defined as:

#define PCIBIOS_WRITE_CONFIG_BYTE       0xb10b    

PCIBIOS_WRITE_CONFIG_BYTE combines the PCI BIOS code, B1h, and also the subfunction code (WRITE_CONFIG_BYTE), which is 0Bh.

Commentaries for similar assembly language PCI BIOS routines are included in section 4.2.1

The current loop, of the 'while' instruction that scans for all PCI devices is ended:

    while (idx < MAX_PCI_DEVICES && tag != PCI_NOT_FOUND) {
and before we enter it again the pci_devp[] index is increased for a possible new entry (a new device) which will be returned by calling pciFindNext() for another time:
pci_devp[idx++] = devp;
	tag = pciFindNext();
At the end of the 'while' loop pci_devp, which has info for all pci devices is finally returned to FindPCIVideoInfo().
    return pci_devp;

The following part of FindPCIVideoInfo() code is boxed in dark gray color because it has become obsolete for newer versions of X11. For instance in the newer X11R7.2 version of FindPCIVideoInfo() getPciClassFlags() is just missing. The difference this routine makes is that in order to find the class and subclass fields the code first searches the pci.ids, instead of looking directly at the configuration space of the current device.

getPciClassFlags(pcrpp);

getPciClassFlags() is implemented as:

static void
getPciClassFlags(pciConfigPtr *pcrpp)
{
    pciConfigPtr pcrp;
    int i = 0;

    if (!pcrpp)
	return;
    while ((pcrp = pcrpp[i])) {
	if (!(pcrp->listed_class =
		xf86FindPciClassBySubsys(pcrp->pci_subsys_vendor,
					 pcrp->pci_subsys_card))) {
	    pcrp->listed_class =
		xf86FindPciClassByDevice(pcrp->pci_vendor, pcrp->pci_device);
	}
	i++;
    }
}

For all elements of pcrpp[], that is for all pci devices in the system, xf86FindPciClassBySubsys() or xf86FindPciClassByDevice() is called. If for instance the first routine is called it is substituted in xf86PciProbe() (the routine that calls FindPCIVideoInfo) with ScanPciFindPciClassBySubsys(). This is implemented as:

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;
}

pciVendorSubsysInfoList[] is defined in xf86PciStdIds.h as a large list, the first four items of which are presented bellow:

#if defined(INIT_VENDOR_SUBSYS_INFO) && defined(INIT_SUBSYS_INFO)
static const pciVendorSubsysInfo pciVendorSubsysInfoList[] = {
#ifdef VENDOR_INCLUDE_NONVIDEO
	{0x0000, pci_vendor_0000, pci_ss_list_0000},
#endif
#ifdef VENDOR_INCLUDE_NONVIDEO
	{0x001a, pci_vendor_001a, pci_ss_list_001a},
#endif
#ifdef VENDOR_INCLUDE_NONVIDEO
	{0x0033, pci_vendor_0033, pci_ss_list_0033},
#endif
	{0x003d, pci_vendor_003d, pci_ss_list_003d},
#ifdef VENDOR_INCLUDE_NONVIDEO
	{0x0059, pci_vendor_0059, pci_ss_list_0059},
#endif
. . . 

the list items are of type pciVendorSubsysInfo, which is defined as:

typedef struct {
    unsigned short VendorID;
    const char *VendorName;
    const pciSubsystemInfo **Subsystem;
} pciVendorSubsysInfo;

where pciSubsystemInfo is defined in the same file as:

typedef struct {
    unsigned short VendorID;
    unsigned short SubsystemID;
    const char *SubsystemName;
    unsigned short class;
} pciSubsystemInfo;

For instance the Subsystem of the fourth element is defined in xf86PciStdIds.h as:

#ifdef VENDOR_INCLUDE_NONVIDEO
static const pciSubsystemInfo *pci_ss_list_0059[] = {
	&pci_ss_info_0059_0001,
	&pci_ss_info_0059_0003,
	NULL
};
#endif

Also the following are defined in the same file:

static const pciSubsystemInfo pci_ss_info_e159_0001_0059_0001 =
	{0x0059, 0x0001, pci_subsys_e159_0001_0059_0001, 0};

#define pci_ss_info_0059_0001 pci_ss_info_e159_0001_0059_0001

. . .

static const pciSubsystemInfo pci_ss_info_e159_0001_0059_0003 =
	{0x0059, 0x0003, pci_subsys_e159_0001_0059_0003, 0};

#define pci_ss_info_0059_0003 pci_ss_info_e159_0001_0059_0003

Following now the ScanPciFindPciClassBySubsys() code we have:

If the Vendor field of the current device's configuration space is the same as the VendorID of the list (in the case of the fourth element 0x0059) then pSub holds the following value:

static const pciSubsystemInfo *pci_ss_list_0059[] = {
	&pci_ss_info_0059_0001,
	&pci_ss_info_0059_0003,
	NULL
};

For both subsys of the current pci device configuration space subsystem id is the same as the ones of either pci_ss_info_0059_0001 or pci_ss_info_0059_0003 then the class value (fourth element) of those structs is returned:

register bits 31-24 bits 23-16 bits 15-8 bits 7-0
00 Device ID Vendor ID
04 Status Command
08 Class code Subclass Prog IF Revision ID
0C BIST Header type Latency Timer Cache Line Size
10 Base address #0 (BAR0)
14 Base address #1 (BAR1)
18 Base address #2 (BAR2)
1C Base address #3 (BAR3)
20 Base address #4 (BAR4)
24 Base address #5 (BAR5)
28 Cardbus CIS Pointer
2C Subsystem ID Subsystem Vendor ID
30 Expansion ROM base address
34 Reserved Capabilities Pointer
38 Reserved
3C Max latency Min Grant Interrupt PIN Interrupt Line

We continue with FindPCIVideoInfo(), omitting from our thread the grey letters part, which cover the obsolete version, discussed in the previous grey box:

    while ((pcrp = pcrpp[i])) {
	int baseclass;
	int subclass;
        
	if (pcrp->listed_class & 0xffff) {
	    baseclass = (pcrp->listed_class >> 8) & 0xff;
	    subclass = pcrp->listed_class & 0xff;
	} else {
	    baseclass = pcrp->pci_base_class;
	    subclass = pcrp->pci_sub_class;
	}
	
	if (PCIINFOCLASSES(baseclass, subclass) &&
	    (DoIsolateDeviceCheck ?
	    (xf86IsolateDevice.bus == pcrp->busnum &&
	     xf86IsolateDevice.device == pcrp->devnum &&
	     xf86IsolateDevice.func == pcrp->funcnum) : 1)) {
	    num++;
	    xf86PciVideoInfo = xnfrealloc(xf86PciVideoInfo,
					  sizeof(pciVideoPtr) * (num + 1));
	    xf86PciVideoInfo[num] = NULL;
	    info = xf86PciVideoInfo[num - 1] = xnfalloc(sizeof(pciVideoRec));
	    info->validSize = FALSE;
	    info->vendor = pcrp->pci_vendor;
	    info->chipType = pcrp->pci_device;
	    info->chipRev = pcrp->pci_rev_id;
	    info->subsysVendor = pcrp->pci_subsys_vendor;
	    info->subsysCard = pcrp->pci_subsys_card;
	    info->bus = pcrp->busnum;
	    info->device = pcrp->devnum;
	    info->func = pcrp->funcnum;
	    info->class = baseclass;
	    info->subclass = pcrp->pci_sub_class;
	    info->interface = pcrp->pci_prog_if;
	    info->biosBase = PCIGETROM(pcrp->pci_baserom);
	    info->biosSize = pciGetBaseSize(pcrp->tag, 6, TRUE, NULL);
	    info->thisCard = pcrp;
	    info->validate = FALSE;

A 'while' loop starts for all elements of the table returned from xf86scanpci(), which we remind are of type pciDevice:

/*
 * Data structure returned by xf86scanpci including contents of
 * PCI config space header
 */
typedef struct pci_device {
    PCITAG    tag;
    int	      busnum;
    int	      devnum;
    int	      funcnum;
    pciCfgSpc cfgspc;
    int	      basesize[7];	/* number of bits in base addr allocations */
    Bool      minBasesize;
    CARD32    listed_class;
    pointer   businfo;		/* pointer to secondary's bus info structure */
    Bool      fakeDevice;	/* Device added by system chipset support */
} pciDevice, *pciConfigPtr;
In the while loop The Class and Subclass code are returned from the configuration space at baseclass and subclass respectively. Notice that pcrp->pci_base_class and pcrp->pci_sub_class are shortcuts (elements of elements, etc, of cfgspc field of pciDevice) defined in xf86Pci.h as:
#define pci_sub_class		      cfgspc.regs.class_rev.cr.sub_class
#define pci_base_class		      cfgspc.regs.class_rev.cr.base_class
baseclass and subclass are passed to macro PCIINFOCLASSES in order to find if the class and subclass pair are qualified to be included in xf86PciVideoInfo[]. This struct holds information only for the Video related devices.

/* PCI classes that get included in xf86PciVideoInfo */
#define PCIINFOCLASSES(b,s)						      \
    (((b) == PCI_CLASS_PREHISTORIC) ||					      \
     ((b) == PCI_CLASS_DISPLAY) ||					      \
     ((b) == PCI_CLASS_MULTIMEDIA && (s) == PCI_SUBCLASS_MULTIMEDIA_VIDEO) || \
     ((b) == PCI_CLASS_PROCESSOR && (s) == PCI_SUBCLASS_PROCESSOR_COPROC))

where the previous macros are defined in xf86Pci.h:

/* base class values */
#define PCI_CLASS_PREHISTORIC		0x00
. . .
#define PCI_CLASS_DISPLAY		0x03
#define PCI_CLASS_MULTIMEDIA		0x04
. . .
#define PCI_CLASS_PROCESSOR		0x0b
. . .
/* 0x04 multimedia subclasses */
#define PCI_SUBCLASS_MULTIMEDIA_VIDEO	0x00
. . .
/* 0x0b processor subclasses */
. . .
#define PCI_SUBCLASS_PROCESSOR_COPROC	0x40

As we read in the Class Code Table the cases allowed are:


Class   Description                                        Subclass Description
----------------------------------------------------------------------------------- 
0x00  	Devices built before class codes (i.e. pre PCI 2.0)
0x03  	Display controller
0x04  	Multimedia device                                  0x00     Video device
0x0B  	Processors                                         0x40     Co-Processor

The first case include the 'prehistoric' devices, i.e. produced before the class codes were implemented, in an attempt to find there 'prehistoric' graphic cards. The second and the third case are obvious and the fourth include co-processors. A definition for co-processors is found in PC Mag page: Graphics hardware that performs various 2D and 3D geometry and rendering functions, offloading the main CPU from performing such tasks. This typically refers to a very high-end graphics subsystem, but may also refer to a graphics accelerator.

The following is an example of Co-processor info returned from lspci:

00:01.3 Co-processor: nVidia Corporation MCP67 Co-processor (rev a2)
Subsystem: Hewlett-Packard Company Device 30cf
Flags: bus master, 66MHz, fast devsel, latency 0, IRQ 11
Memory at f6200000 (32-bit, non-prefetchable) [size=512K]

If the test is passed a new element of xf86PciVideoInfo[] is allocated and some fields are filled:

	    info = xf86PciVideoInfo[num - 1] = xnfalloc(sizeof(pciVideoRec));
	    info->validSize = FALSE;
	    info->vendor = pcrp->pci_vendor;
	    info->chipType = pcrp->pci_device;
	    info->chipRev = pcrp->pci_rev_id;
	    info->subsysVendor = pcrp->pci_subsys_vendor;
	    info->subsysCard = pcrp->pci_subsys_card;
	    info->bus = pcrp->busnum;
	    info->device = pcrp->devnum;
	    info->func = pcrp->funcnum;
	    info->class = baseclass;
	    info->subclass = pcrp->pci_sub_class;
	    info->interface = pcrp->pci_prog_if;
	    info->biosBase = PCIGETROM(pcrp->pci_baserom);
	    info->biosSize = pciGetBaseSize(pcrp->tag, 6, TRUE, NULL);
	    info->thisCard = pcrp;
	    info->validate = FALSE;

Next with FindPCIVideoInfo():

	    for (j = 0; j < 6; j++) {
		info->memBase[j] = 0;
		info->ioBase[j] = 0;
		if (PCINONSYSTEMCLASSES(baseclass, subclass)) {
		    info->size[j] =
			pciGetBaseSize(pcrp->tag, j, TRUE, &info->validSize);
		    pcrp->minBasesize = info->validSize;
		} else {
		    info->size[j] = pcrp->basesize[j];
		    info->validSize = pcrp->minBasesize;
		}
		info->type[j] = 0;
	    }

PCINONSYSTEMCLASSES is defined as "PCI classes for which potentially destructive checking of the map sizes may be done":

/*
 * 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))
 
/*
 * PCI classes for which potentially destructive checking of the map sizes
 * may be done.  Any classes where this may be unsafe should be omitted
 * from this list.
 */
#define PCINONSYSTEMCLASSES(b,s) PCIALWAYSPRINTCLASSES(b,s)

Therefore in the cases of PCI classes for which potentially destructive checking of the map sizes may be done pciGetBaseSize() is used. We previously met pciGetBaseSize() at xf86scanpci(). As we read in the comment of this routine:

                                    If destructive is TRUE, it will write
 * to the base address register to get an accurate result.  Otherwise it
 * makes a conservative guess based on the alignment of the already allocated
 * address. 
Here the third parameter (destructive) is set to TRUE therefore the accurate method is tried. Notice that an detailed description of this method is given in section 4.2.1. An outline of this method is given from The Linux Kernel:

"To find out just how much of each address space a given Base Address Register is requesting, you write all 1s into the register and then read it back. The device will specify zeros in the don't care address bits, effectively specifying the address space required. This design implies that all address spaces used are a power of two and are naturally aligned."

Notice that PCINONSYSTEMCLASSES, which are used here are different from PCIINFOCLASSES mainly in the absence of class PCI_CLASS_PROCESSOR, subclass PCI_SUBCLASS_PROCESSOR_COPROC.

With the last part of FindPCIVideoInfo() code, bellow, a message is logged in file Xorg.0.log. An example of this message could be:

PCI:*(0:1:0:0) 10de:0649:103c:30f4 nVidia Corporation G96 [GeForce 9600M GT] rev 161,
Mem @ 0xd2000000/16777216, 0xc0000000/268435456, 0xd0000000/33554432, I/O @ 0x00007000/128

This message entered in Xorg.0.log shows clearly that at this point the most important information for the Graphics Card is acquired. For instance consider the second BAR returned the following value for the memory:

0xc0000000/268435456
This shows the size of the memory GeForce 9600M GT uses as a frame buffer (the memory area that each write is transfered to the card memory and then to the screen), which is 268435456 or 256 MB and also the position of the system memory that this frame buffer starts. This is c0000000, which at 3.2 GB of the system memory. As we read in section 4.2.1 the usual area that device driver system memory is between 3 anf 4 GB.

     
    /* Print a summary of the video devices found */
    for (k = 0; k < num; k++) {
	const char *vendorname = NULL, *chipname = NULL;
	const char *prim = " ";
	char busnum[8];
	Bool memdone = FALSE, iodone = FALSE;

	i = 0; 
	info = xf86PciVideoInfo[k];
	xf86FormatPciBusNumber(info->bus, busnum);
	xf86FindPciNamesByDevice(info->vendor, info->chipType,
				 NOVENDOR, NOSUBSYS,
				 &vendorname, &chipname, NULL, NULL);
	if ((!vendorname || !chipname) &&
	    !PCIALWAYSPRINTCLASSES(info->class, info->subclass))
	    continue;
	if (xf86IsPrimaryPci(info))
	    prim = "*";

	xf86Msg(X_PROBED, "PCI:%s(%s:%d:%d) ", prim, busnum, info->device,
		info->func);
	if (vendorname)
	    xf86ErrorF("%s ", vendorname);
	else
	    xf86ErrorF("unknown vendor (0x%04x) ", info->vendor);
	if (chipname)
	    xf86ErrorF("%s ", chipname);
	else
	    xf86ErrorF("unknown chipset (0x%04x) ", info->chipType);
	xf86ErrorF("rev %d", info->chipRev);
	for (i = 0; i < 6; i++) {
	    if (info->memBase[i] &&
		(info->memBase[i] < (memType)(-1 << info->size[i]))) {
		if (!memdone) {
		    xf86ErrorF(", Mem @ ");
		    memdone = TRUE;
		} else
		    xf86ErrorF(", ");
		xf86ErrorF("0x%08lx/%d", info->memBase[i], info->size[i]);
	    }
	}
	for (i = 0; i < 6; i++) {
	    if (info->ioBase[i] &&
		(info->ioBase[i] < (memType)(-1 << info->size[i]))) {
		if (!iodone) {
		    xf86ErrorF(", I/O @ ");
		    iodone = TRUE;
		} else
		    xf86ErrorF(", ");
		xf86ErrorF("0x%04lx/%d", info->ioBase[i], info->size[i]);
	    }
	}
	if (info->biosBase &&
	    (info->biosBase < (memType)(-1 << info->biosSize)))
	    xf86ErrorF(", BIOS @ 0x%08lx/%d", info->biosBase, info->biosSize);
	xf86ErrorF("\n");
    }

PCI in a Nutshell

From PCI bus info and code from a programmer's perspective we read:

There are 4 components to the PCI subsytem:

Bus Number
Device Number
Function Number
Register Number

There are up to 256 available Buses on a PCI system, most commonly all the cards and chips will be located on Bus 0 and Bus 1. When scanning for hardware, it's a good idea to scan all 256 buses as it won't take that much additional time.

A Device is a physical thing on the PCI bus. It could be a video card, an ethernet card, a Northbridge, anything. There is a software maximum of 32 devices that can exist on each bus. Your software must scan all 32 devices on each bus.

All devices have at least 1 function. Multi-function devices, such as a combination modem/soundcard will usually have 2 devices, typically numbered 0 and 1. There are 8 possible functions per device, numbered 0-7. Any device that has more than 1 function is (hey clever!) called a multi-function device. Functions start numbering at 0 and work up to 7

Every function of a device has 256 eight-bit registers. Registers 0-3F are defined by the PCI specification and provide a wealth of information about the particular function. Registers 40-FF are vendor defined and control the properties of the function itself.

A typical PCI function looks like this:

          x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF
00000000: B7 10 55 90 17 01 10 02 30 00 00 02 08 50 00 00 
00000010: 81 10 00 00 00 00 00 0C 00 00 00 00 00 00 00 00 
00000020: 00 00 00 00 00 00 00 00 00 00 00 00 B7 10 55 90 
00000030: 00 00 00 00 DC 00 00 00 00 00 00 00 0B 01 0A 0A 
00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00000050: 00 00 00 00 40 00 00 00 00 00 00 00 00 00 00 00 
00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
000000C0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
000000D0: 00 00 00 00 00 00 00 00 00 00 00 00 01 00 01 F6 
000000E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
000000F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

Registers 0 and 1 are defined by the PCI spec as being the vendor ID. The vendor ID is a 16bit value, in this case 10B7h Register 2 and 3 are the device ID, 9055h in this example.

By looking up the vendor ID number 10b7 in the PCI vendor database you can see this is a 3COM card. The 9055h is a 3COM generated number which describes what model card this is.

Other registers can tell us it's an Ethernet controller, it's using IRQ 11, uses a base I/O address of 1080h and more.

Notice that the little ENDIAN is used here, meaning all multiple byte fields have the least significant values at the lower address.

REFERENCES:

The first 15 lines of Linux header file pci.h

PCI startup sequence

OSDev Wiki

PCI_Configuration_space

Number of views for the current page since July 25, 2010: