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

Case Study: Linux

bios32.c

At PCI Subsystem we find the Linux PCI startup sequence. The first step starts from do_basic_setup(), which is called from init(), a major startup function in "linux/init/main.c" (see Initialization for IA-32 HOWTO):

1. do_basic_setup() calls pci_init(), which is defined in 'drivers/pci/pci.c'.
2. pci_init() first calls pcibios_init(). If you enable CONFIG_NEW_PCI,
 pcibios_init() is implemented in the 'arch/mips/kernel/pci.c' file. 

From The Linux Kernel Sources we read:

The PCI pseudo driver is in drivers/pci/pci.c with the system wide definitions in
 include/linux/pci.h. Each architecture has some specific PCI BIOS code, Alpha AXP's 
is in arch/alpha/kernel/bios32.c.

The i386 bios32.c is the Linux source file that provides the BIOS32 Service Directory structure, for the i386 architecture. This is given as:

union bios32 {
	struct {
		unsigned long signature;	/* _32_ */
		unsigned long entry;		/* 32 bit physical address */
		unsigned char revision;		/* Revision level, 0 */
		unsigned char length;		/* Length in paragraphs should be 01 */
		unsigned char checksum;		/* All bytes must add up to zero */
		unsigned char reserved[5]; 	/* Must be zero */
	} fields;
	char chars[16];
};

The entry of the bios32 service is returned by the pcibios_init(). This function tries to find a valid BIOS32 structure by checking the physical address range 0xe0000 through 0xfffff for the signature "_32_" (see the Standard BIOS 32-bit Service Directory Proposal).

unsigned long pcibios_init(unsigned long memory_start, unsigned long memory_end)
{
#ifdef CONFIG_PCI
	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 *) 0xe0000;
	     check <= (union bios32 *) 0xffff0;
	     ++check) {
		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("pcibios_init : unsupported revision %d at 0x%p, mail drew@colorado.edu\n",
				check->fields.revision, check);
			continue;
		}
		printk ("pcibios_init : BIOS32 Service Directory structure at 0x%p\n", check);
		if (!bios32_entry) {
			if (check->fields.entry >= 0x100000) {
				printk("pcibios_init: entry in high memory, trying direct PCI access\n");
				access_pci = check_direct_pci();
			} else {
				bios32_entry = check->fields.entry;
				printk ("pcibios_init : BIOS32 Service Directory entry at 0x%lx\n", bios32_entry);
				bios32_indirect.address = bios32_entry;
			}
		}
	}
	if (bios32_entry && check_pcibios())
 		access_pci = &pci_bios_access;
#endif
	return memory_start;
}

pcibios_init() scans the physical address space 0xe0000 to 0xfffff simply by placing pointers as:

check = (union bios32 *) 0xe0000
Recall that this function runs from the Linux Kernel and therefore from the physical and not the Virtual address space. For this we read at Linux Memory Management:

It does not make much sense for the operating system itself to run in virtual memory. This would be a nightmare situation where the operating system must maintain page tables for itself. Most multi-purpose processors support the notion of a physical address mode as well as a virtual address mode. Physical addressing mode requires no page tables and the processor does not attempt to perform any address translations in this mode. The Linux kernel is linked to run in physical address space.

Let's follow the case from pcibios_init(), where the bios32_entry is zero yet and then the entry point is located in the area 0xe0000 to 0xffff0 and that the entry point (the second field of the BIOS32 Service Directory Header - see Standard BIOS 32-bit Service Directory Proposal) is not in the high memory (that is >= 0x100000). Then bios32_entry takes the value of the entry point:

		if (!bios32_entry) {
			if (check->fields.entry >= 0x100000) {
				printk("pcibios_init: entry in high memory, trying direct PCI access\n");
				access_pci = check_direct_pci();
			} else {
				bios32_entry = check->fields.entry;
				printk ("pcibios_init : BIOS32 Service Directory entry at 0x%lx\n", bios32_entry);
				bios32_indirect.address = bios32_entry;
			}
		}
	}
	if (bios32_entry && check_pcibios())
 		access_pci = &pci_bios_access;
If the entry point is found and also check_pcibios() returns true the access_pci takes the address of pci_bios_access, which is defined as:

/*
 * function table for BIOS32 access
 */
static struct pci_access pci_bios_access = {
      pci_bios_find_device,
      pci_bios_find_class,
      pci_bios_read_config_byte,
      pci_bios_read_config_word,
      pci_bios_read_config_dword,
      pci_bios_write_config_byte,
      pci_bios_write_config_word,
      pci_bios_write_config_dword
};

check_pcibios() is the BIOS32 function 0xb101 for INT 0x1A. WE already know that PCI BIOS is present for in 32-bit protected mode. This function returns some more additional information. If PCI BIOS is present in real mode, register AL returns info about configuration, CL the number of the last PCI bus segment in the system. It is implemented as:

static int check_pcibios(void)
{
	unsigned long signature;
	unsigned char present_status;
	unsigned char major_revision;
	unsigned char minor_revision;
	unsigned long flags;
	int pack;

	if ((pcibios_entry = bios32_service(PCI_SERVICE))) {
		pci_indirect.address = pcibios_entry;

		save_flags(flags); cli();
		__asm__("lcall (%%edi)\n\t"
			"jc 1f\n\t"
			"xor %%ah, %%ah\n"
			"1:\tshl $8, %%eax\n\t"
			"movw %%bx, %%ax"
			: "=d" (signature),
			  "=a" (pack)
			: "1" (PCIBIOS_PCI_BIOS_PRESENT),
			  "D" (&pci_indirect)
			: "bx", "cx");
		restore_flags(flags);

		present_status = (pack >> 16) & 0xff;
		major_revision = (pack >> 8) & 0xff;
		minor_revision = pack & 0xff;
		if (present_status || (signature != PCI_SIGNATURE)) {
			printk ("pcibios_init : %s : BIOS32 Service Directory says PCI BIOS is present,\n"
				"	but PCI_BIOS_PRESENT subfunction fails with present status of 0x%x\n"
				"	and signature of 0x%08lx (%c%c%c%c).  mail drew@Colorado.EDU\n",
				(signature == PCI_SIGNATURE) ?  "WARNING" : "ERROR",
				present_status, signature,
				(char) (signature >>  0), (char) (signature >>  8),
				(char) (signature >> 16), (char) (signature >> 24));

			if (signature != PCI_SIGNATURE)
				pcibios_entry = 0;
		}
		if (pcibios_entry) {
			printk ("pcibios_init : PCI BIOS revision %x.%02x entry at 0x%lx\n",
				major_revision, minor_revision, pcibios_entry);
			return 1;
		}
	}
	return 0;
}

check_pcibios() is implemented with inline assembly code. We won't discuss the previous routine, but rather we will focus on one of the pci_bios_access routines that also use inline assembly, namely pci_bios_read_config_dword(). This will be used as a case study for check_pcibios() and the rest routines of pci_bios_access. pci_bios_read_config_dword(), is the implementation of 'Read Configuration Dword', discussed in section 4.2.1.

static int pci_bios_read_config_dword (unsigned char bus,
	unsigned char device_fn, unsigned char where, unsigned int *value)
{
	unsigned long ret;
	unsigned long bx = (bus << 8) | device_fn;
	unsigned long flags;

	save_flags(flags); cli();
	__asm__("lcall (%%esi)\n\t"
		"jc 1f\n\t"
		"xor %%ah, %%ah\n"
		"1:"
		: "=c" (*value),
		  "=a" (ret)
		: "1" (PCIBIOS_READ_CONFIG_DWORD),
		  "b" (bx),
		  "D" ((long) where),
		  "S" (&pci_indirect));
	restore_flags(flags);
	return (int) (ret & 0xff00) >> 8;
}

The previous code snippet seems a little messy, however with the help of the GCC-Inline-Assembly-HOWTO we find the extended inline assembly format:

       asm ( assembler template 
           : output operands                  /* optional */
           : input operands                   /* optional */
           : list of clobbered registers      /* optional */
           );

The registers that participate in the output and input list are determined by the output and the input of the specific function. For instance function 'Read Configuration Dword' requires the registers that are presented in the box '4.3.3. Read Configuration Dword', that follows. Another input register esi (or "S" here) is needed for the memory location of the lcall. What about the clobbered registers? For an explanation see Inline assembly for x86 in Linux. In the example of this page eax is a clobbered register. We read: "And since %eax is specified in the clobbered list, GCC doesn't use it anywhere else to store data."

For pci_bios_read_config_dword() we have the following input operants:

"1" (PCIBIOS_READ_CONFIG_DWORD),
"b" (bx),
"D" ((long) where),
"S" (&pci_indirect));

PCIBIOS_READ_CONFIG_DWORD is define in bios32.c as:

#define PCIBIOS_READ_CONFIG_DWORD	0xb10a
This is the subfunction READ_CONFIG_DWORD (0Ah) discussed in section 4.2.1. Given the following output operants:
		: "=c" (*value),
		  "=a" (ret)
PCIBIOS_READ_CONFIG_DWORD is loaded to register eax. This is because the second of the output operands is 'a', that is eax. Number "1" corresponds to the second outputoperant, since number "0" corresponds to the first. Therefore register ah is loaded with B1h and register al with 0Ah, which is according to the APPENDIX A: Function List of the PCI BIOS Specification.

variable bx holds the bus number and device number:

	unsigned long bx = (bus << 8) | device_fn;
and the value of this variable is then transferred to register ebx (or b):
 "b" (bx),

edi is loaded with the specific location in the configuration space, where we will read the double word:

"D" ((long) where),

where is passed as the 3rd argument of pci_bios_read_config_dword().

esi is loaded with pci_indirect, which is initially defined as:

static struct {
	unsigned long address;
	unsigned short segment;
} pci_indirect = { 0, KERNEL_CS };
and then is assigned the value of the entry point into the specific BIOS service, by calling check_pcibios() with argument the specific service, PCI_SERVICE (the identifier "$PCI"):
	if ((pcibios_entry = bios32_service(PCI_SERVICE))) {
		pci_indirect.address = pcibios_entry;
Since the registers have obtained the appropriate values the lcall x86 assembly language instruction is used as:
	__asm__("lcall (%%esi)\n\t"
This is a far call to the entry point of the specific BIOS service.

All previous register values are given in the PCI BIOS Specification on bios32 function 'Read Configuration Dword':


4.3.3. Read Configuration Dword

This function allows reading individual dwords from the configuration space of a specific
device. The Register Number parameter must be a multiple of four (i.e., bits 0 and 1
must be set to 0).

ENTRY:

    [AH] PCI_FUNCTION_ID
    [AL] READ_CONFIG_DWORD
    [BH] Bus Number (0...255)
    [BL] Device Number in upper 5 bits,
         Function Number in lower 3 bits
    [DI] Register Number (0,4,8,...252)

EXIT:

    [ECX] Dword read.
    [AH] Return Code:
                SUCCESSFUL
                BAD_REGISTER_NUMBER
    [CF] Completion Status, set = error, reset = success

Notice: at the following lines we discuss the bios32_service() routine, mentioned previously, which returns the entry point into the specific BIOS service. This will be used with a lcall instruction to call the specific BIOS service and return back to pci_bios_read_config_dword(). In our case the service identifier is "$PCI".


1. To access the configuration space a device driver can use the BIOS32 functions. The BIOS32 data structure, which is required, is located somewhere in the physical address range 0E0000h - 0FFFFFh. This provides at its second field the entry point that will be used from the Calling Interface for BIOS32 Service Directory.

2. The Calling Interface for the BIOS32 Service Directory is accessed through a CALL FAR to the entry point, provided in the Service data structure. It uses the service identifier at register eax and returns to the register ebx the Physical address of the base of the BIOS service. See also the 'PCI BIOS Specification'.

3. With a CALL FAR to the address returned to register ebx the specific function, for instance Read Configuration Dword, can be utilized. The arguments and the return values for all functions can be found at the 'PCI BIOS Specification'.

From PCI BIOS Specification we read:

The BIOS32 Service Directory may be used to detect the presence of the PCI BIOS. The Service Identifier for the PCI BIOS is "$PCI" (049435024h).
The 32-bit PCI BIOS functions must be accessed using CALL FAR.

Also we read the description for the 'PCI BIOS 32-bit service':

3.4. PCI BIOS 32-bit service


The BIOS32 Service Directory may be used to detect the presence of the PCI BIOS. The
Service Identifier for the PCI BIOS is "$PCI" (049435024h).

The 32-bit PCI BIOS functions must be accessed using CALL FAR. The CS and DS
descriptors must be setup to encompass the physical addresses specified by the Base and
Length parameters returned by the BIOS32 Service Directory. The CS and DS
descriptors must have the same base. The calling environment must allow access to IO
space and provide at least 1K of stack space. Platform BIOS writers must assume that CS
is execute-only and DS is read-only.

The 'Calling Interface for BIOS32 Service Directory' is described in the 'PCI BIOS Specification' document as:

The BIOS32 Service Directory provides a single function to determine whether a particular 32-bit BIOS service is supported by the platform. All parameters to the function are passed in registers. Parameter descriptions are provided below. If a particular service is implemented in the platform BIOS, three values are returned. The first value is the base physical address of the BIOS service. The second value is the length of the BIOS service. These two values can be used to build the code segment selector and data segment selector for accessing the service. The third value provides the entry point to the BIOS service encoded as an offset from the base.
ENTRY:
     [EAX] Service Identifier. This is a four character string used to
specifically identify which 32-bit BIOS Service is being
sought.
     [EBX] The low order byte ([BL]) is the BIOS32 Service Directory
function selector. Currently only one function is defined
(with the encoding of zero) which returns the values
provided below.

The upper three bytes of [EBX] are reserved and must be
zero on entry.

EXIT:
    [AL] Return code.
         00h = Service corresponding to Service Identifier is present.
         80h = Service corresponding to Service Identifier is not present
         81h = Unimplemented function for BIOS Service Directory
         (i.e. BL has an unrecognized value).
    [EBX] Physical address of the base of the BIOS service.
    [ECX] Length of the BIOS service.
    [EDX] Entry point into BIOS service. This is an offset from the base
provided in EBX.

bios32_service() is implemented as:

/*
 * Returns the entry point for the given service, NULL on error
 */

static unsigned long bios32_service(unsigned long service)
{
	unsigned char return_code;	/* %al */
	unsigned long address;		/* %ebx */
	unsigned long length;		/* %ecx */
	unsigned long entry;		/* %edx */
	unsigned long flags;

	save_flags(flags); cli();
	__asm__("lcall (%%edi)"
		: "=a" (return_code),
		  "=b" (address),
		  "=c" (length),
		  "=d" (entry)
		: "0" (service),
		  "1" (0),
		  "D" (&bios32_indirect));
	restore_flags(flags);

	switch (return_code) {
		case 0:
			return address + entry;
		case 0x80:	/* Not present */
			printk("bios32_service(0x%lx) : not present\n", service);
			return 0;
		default: /* Shouldn't happen */
			printk("bios32_service(0x%lx) : returned 0x%x, mail drew@colorado.edu\n",
				service, return_code);
			return 0;
	}
}

PCI_SERVICE is the string "$PCI", defined in bios32.c as:

/* PCI service signature: "$PCI" */
#define PCI_SERVICE		(('$' << 0) + ('P' << 8) + ('C' << 16) + ('I' << 24))
Recall also from pcibios_init() that bios32_indirect was assigned to the entry point of the bios32 directory:
bios32_indirect.address = bios32_entry;
The inline assembly code follows the standards from the PCI BIOS Specification and register eax is filled with the service number, register ebx is zero and edi the location of the bios32 service directory. As we read from the inline assembly language manuals for the following input operants the "0" stands for the first output operant and 1 for the second output operant:
		: "0" (service),
		  "1" (0),
		  "D" (&bios32_indirect));
The output operants are given in the order eax, ebx, ecx and edx:
		: "=a" (return_code),
		  "=b" (address),
		  "=c" (length),
		  "=d" (entry)
the long call (lcall) assembly language instruction we read from x86 Assembly Language Reference Manual:

The lcall instruction calls intersegment (far) procedures using a full pointer. lcall causes the procedure named in the operand to be executed. When the called procedure completes, execution flow resumes at the instruction following the lcall instruction (see the return instruction).

Therefore execution starts from the entry of the bios32 directory. Notice that from the following text of the description of the 'PCI BIOS 32-bit service' from the 'PCI BIOS Specification' the long call (lcall) is referred as CALL FAR and also that CS has the right value, which remain 0 (the KERNEL_CS).

The 32-bit PCI BIOS functions must be accessed using CALL FAR. The CS and DS descriptors must be setup to encompass the physical addresses specified by the Base and Length parameters returned by the BIOS32 Service Directory.

Note: see also Mixing Assembly and C and Inline assembly for x86 in Linux.

How Linux device drivers use the PCI BIOS routines

The document Programming PCI-Devices under Linux provides a nice example on using the Linux PCI BIOS routines, for instance pcibios_find_device() and pcibios_read_config_dword():

#if defined(CONFIG_PCI)
    if (pcibios_present()) {
        int pci_index;
        for (pci_index = 0; pci_index < 8; pci_index++) {
               unsigned char pci_bus, pci_device_fn;
             unsigned int pci_ioaddr;

        /* Currently only Realtek are making PCI ne2k clones. */
        if (pcibios_find_device (PCI_VENDOR_ID_REALTEK,
                   PCI_DEVICE_ID_REALTEK_8029, pci_index,
                   &pci_bus, &pci_device_fn) != 0)
               break;      /* OK, now try to probe for std. ISA card */

        pcibios_read_config_byte(pci_bus, pci_device_fn,
                     PCI_INTERRUPT_LINE, &pci_irq_line);
        pcibios_read_config_dword(pci_bus, pci_device_fn,
                     PCI_BASE_ADDRESS_0, &pci_ioaddr);

        . . .

From the same site we read:

The Linux PCI-bios support is implemented on top of the Bios32 routines and defines some useful routines to handle the PCI configuration block and configure the PCI subsystem. Normally no changes in the configuration block are necessary because the BIOS sets up all parameters to operate correctly. Because Linux is intended to run on many hardware architectures it is recommended to use this functions instead of accessing the configuration registers directly.

Again it is important to recall that device drivers need to use those routines in order to locate where the device is mapped in memory and the device's I/O space. From Linux Device Drivers we read:

After the driver has detected the device, it usually needs to read from or write to the three address spaces: memory, port, and configuration. In particular, accessing the configuration space is vital to the driver because it is the only way it can find out where the device is mapped in memory and in the I/O space.

Because the microprocessor has no way to access the configuration space directly, the computer vendor has to provide a way to do it. To access configuration space, the CPU must write and read registers in the PCI controller, but the exact implementation is vendor dependent and not relevant to this discussion because Linux offers a standard interface to access the configuration space.

As far as the driver is concerned, the configuration space can be accessed through 8-bit, 16-bit, or 32-bit data transfers. The relevant functions are prototyped in :

int pci_read_config_byte(struct pci_dev *dev, int where, u8 *ptr);
int pci_read_config_word(struct pci_dev *dev, int where, u16 *ptr);
int pci_read_config_dword(struct pci_dev *dev, int where, u32 *ptr);

Notice that for another scenario in the bios32.c, in pcibios_init() function access_pci became check_direct_pci instead of pci_bios_access. Therefore a function of the above, for instance pci_read_config_dword(), instead of becoming pci_bios_read_config_dword() could take another value, for instance pci_conf1_read_config_dword(). Therefore the Linux pci_read_config_dword() is a generic function, which can be substituted by the Bios32 routine or by the function that handles the direct 0xCF8, 0xCFC I/O locations to read the double word configuration space register.

/proc/pci (/proc/bus/pci/) and lspci

Linux command lspci, whose source code is available inside pciutils package, provides information about the pci buses and devices of the Linux system. From lspci man page we read:

The PCI utilities use PCILIB (a portable library providing platform-independent 
functions for PCI configuration space access) to talk to the PCI cards. It supports 
the following access methods:
...
linux_proc
    The /proc/bus/pci interface supported by Linux 2.1 and newer. The standard header
of the config space is available to all users, the rest only to root.

The previous code suggests that lspci can make use of the /proc pseudo-filesystem and more specifically /proc/pci or /proc/bus/pci/ for newer Linux versions.

From Linux Device Drivers we read:

If you want to browse the configuration space of the PCI devices on your system, you can proceed in one of two ways. The easier path is using the resources that Linux already offers via /proc/bus/pci, although these were not available in version 2.0 of the kernel. The alternative that we follow here is, instead, writing some code of our own to perform the task; such code is both portable across all known 2.x kernel releases and a good way to look at the tools in action. The source file pci/pcidata.c is included in the sample code provided on the O'Reilly FTP site.

This module creates a dynamic /proc/pcidata file that contains a binary snapshot of the configuration space for your PCI devices. The snapshot is updated every time the file is read.

pcidata.c makes use of drivers/pci/pci.c and also of the various configuration routines, for instance pci_read_config_dword(), previously covered. The 'write' routine, pci_write_config_dword() is covered in detail in section 3.2.2.3.

This lspci example shows the output of lspci command:

$ lspci 
00:00.0 Host bridge: VIA Technologies, Inc. \
    VT8366/A/7 [Apollo KT266/A/333]
00:01.0 PCI bridge: VIA Technologies, Inc. \
    VT8366/A/7 [Apollo KT266/A/333 AGP]
00:0b.0 Ethernet controller: Digital Equipment Corporation \
    DECchip 21140 [FasterNet] (rev 22)
00:10.0 USB Controller: VIA Technologies, Inc. USB (rev 80)
00:10.1 USB Controller: VIA Technologies, Inc. USB (rev 80)
00:10.2 USB Controller: VIA Technologies, Inc. USB (rev 80)
00:10.3 USB Controller: VIA Technologies, Inc. USB 2.0 (rev 82)
00:11.0 ISA bridge: VIA Technologies, Inc. VT8235 ISA Bridge
00:11.1 IDE interface: VIA Technologies, Inc. VT82C586/B/686A/B \
    PIPC Bus Master IDE (rev 06)
00:11.5 Multimedia audio controller: VIA Technologies, Inc. \
    VT8233 AC97 Audio Controller (rev 50)
01:00.0 VGA compatible controller: Matrox Graphics, Inc. \
    MGA G550 AGP (rev 01)

and also the verbose (-v) listing:

$ lspci -v
[...]
01:00.0 \
 VGA compatible controller: Matrox Graphics, Inc. MGA G550 AGP (rev 01) \
    (prog-if 00 [VGA])
 Subsystem: Matrox Graphics, Inc. Millennium G550 Dual Head DDR 32Mb
 Flags: bus master, medium devsel, latency 32, IRQ 10
 Memory at d8000000 (32-bit, prefetchable) [size=32M]
 Memory at da000000 (32-bit, non-prefetchable) [size=16K]
 Memory at db000000 (32-bit, non-prefetchable) [size=8M]
 Expansion ROM at  [disabled] [size=128K]
 Capabilities: <available only to root>

Another example comes from Linux Device Drivers:

rudo% lspci | cut -d: -f1-2
00:00.0 Host bridge
00:01.0 PCI bridge
00:07.0 ISA bridge
00:07.1 IDE interface
00:07.3 Bridge
00:07.4 USB Controller
00:09.0 SCSI storage controller
00:0b.0 Multimedia video controller
01:05.0 VGA compatible controller

rudo% cat /proc/bus/pci/devices | cut -d\        -f1,3
0000    0
0008    0
0038    0
0039    0
003b    0
003c    b
0048    a
0058    b
0128    a
The two lists of devices are sorted in the same order, since lspci uses the /procfiles as its source of information. Taking the VGA video controller as an example, 0x128 means 01:05.0 when split into bus (eight bits), device (five bits) and function (three bits). The second field in the two listings shown shows the class of device and the interrupt number, respectively.

We see that in both examples the VGA compatible controller is found in bus 1. From Determining the video card's bus identifier we read: "In many computers the PCI bus 0 is the regular PCI bus, and PCI bus 1 is the AGP, but this is not the case in nearly all situations."

About the video bus numbering refer also to the following image from this Linux PCI page:

The PCI Device Driver

From PCI we read:

The PCI device driver is not really a device driver at all but a function of the operating system called at system initialisation time. The PCI initialisation code must scan all of the PCI buses in the system looking for all PCI devices in the system.

It uses the PCI BIOS code to find out if every possible slot in the current PCI bus that it is scanning is occupied.

What is important here is that the PCI related items of the /proc filesystem in Linux it is maintained by the PCI Device Driver. From this link we read about "/proc/bus/pci/devices":

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 throu a call to proc_register).

At the start of this page we mentioned that the PCI Device Driver is implemented in drivers/pci/pci.c (and also some other files in the same directory). pci_proc_detach_bus() is the pci.c routine that adds an entry to /proc/bus/pci.

 int pci_proc_attach_bus(struct pci_bus* bus)
  {
         struct proc_dir_entry *de = bus->procdir;
 
         if (!proc_initialized)
                 return -EACCES;
 
         if (!de) {
                 char name[16];
                 sprintf(name, "%02x", bus->number);
                 de = bus->procdir = proc_mkdir(name, proc_bus_pci_dir);
                 if (!de)
                         return -ENOMEM;
         }
         return 0;
 }

Notice that procdir is defined in pci.h as a pci_bus field:

 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 list_head slots;         /* list of slots on this bus */
         struct resource *resource[PCI_BRIDGE_RESOURCE_NUM];
         struct list_head 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 */
         unsigned char   max_bus_speed;  /* enum pci_bus_speed */
         unsigned char   cur_bus_speed;  /* enum pci_bus_speed */
 
         char            name[48];
 
         unsigned short  bridge_ctl;     /* manage NO_ISA/FBB/et al behaviors */
         pci_bus_flags_t bus_flags;      /* Inherited by child busses */
         struct device           *bridge;
         struct device           dev;
         struct bin_attribute    *legacy_io; /* legacy I/O for this bus */
         struct bin_attribute    *legacy_mem; /* legacy mem */
         unsigned int            is_added:1;
 };

pci_proc_attach_device() is called by __init pci_proc_init() when the driver is initialised:

 static int __init pci_proc_init(void)
 {
         struct pci_dev *dev = NULL;
         proc_bus_pci_dir = proc_mkdir("bus/pci", NULL);
         proc_create("devices", 0, proc_bus_pci_dir,
                     &proc_bus_pci_dev_operations);
         proc_initialized = 1;
         while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) {
                 pci_proc_attach_device(dev);
         }
         return 0;
 }

How the X Window source used the Linux PCI info

At section 3.2.2.3 xf86scanpci(), in the X Server source code, for the Linux implementation, calls linuxPciInit(). This uses pciReadLong() to read the PCI configuration space in order acquire the PCI info about the Devide ID, Vendor ID and function information for every active PCI device.

pciReadLong() is substituted by linuxPciCfgReadWord(), which actually reads the /proc/bus/pci/ pseudo-filesystem.

Conclusion

The following image presents a synopsis of the software parts, discussed in this page and their relationship, as the one relies on the other (indicated by the arrow):