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:
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() 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");
}
|
|