Hands-on Projects for the Linux Graphics Subsystem

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.

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).

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:

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:

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.

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:

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:

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'.

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.

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:

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.

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 :

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.

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.

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

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.

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):