The Linux PCI Interface An introduction to the PCI configuration space registers
Some background on PCI ISA: Industry Standard Architecture (1981) PCI: Peripheral Component Interconnect An Intel-backed industry initiative (1992-9) Main goals: –Improve data-xfers to/from peripheral devices –Eliminate (or reduce) platform dependencies –Simplify adding/removing peripheral devices –Lower total consumption of electrical power
Features for driver-writers Support for “auto-detection” of devices Device configuration is “programmable” Introduces “PCI Configuration Space” A nonvolatile data-structure of device info A standard “header” layout: 64 longwords Linux provides some interface functions: #include
pci_dev_struct Linux extracts info from PCI Config. Space Stores the info in linked-lists of structures Examples: –Name of the device’s manufacturer –Name and release version of the product –Hardware resources provided by the product –System resources allocated to the product (Linux provides “search-and-extract” routines)
The ‘lspci’ command Linux scans PCI Configuration Space It builds a list of ‘pci_dev_struct’ objects It exports partial info using a ‘/proc’ file You can view this info using a command: $ /sbin/lspci Or you can directly view the /proc/pci file: $ cat /proc/pci
An illustrative example: vram.c Let’s write another character device-driver It will allow read/write access to video ram Very analogous to our prior ‘ram.c’ driver Some differences: –Device’s memory resides on PCI the bus –Can safely allow writing as well as reading –Hardware uses memory in nonstandard ways –We really need vendor’s programmer manual
init_module() Driver’s first job is ‘device detection’ PCI devices are identified by numbers Device classes also have ID-numbers VGA device-class:0x –’03’ means a ‘display device’ –’00’ means ‘VGA compatible’ –’00’ means ‘revision-number’
pci_find_class(); Define the manifest constant: #define VGA_CLASS 0x Declare a null-pointer: struct pci_dev_struct *devp = NULL; Call ‘pci_find_class()’ function: devp = pci_find_class( VGA_CLASS, devp ); Check for ‘device-not-found’: if ( devp == NULL ) return –ENODEV;
Locate the VGA ‘frame buffer’ In PCI Configuration Space: – offset 0x10: base_address0 – offset 0x14: base_address1 – offset 0x18: base_address2 –... etc.... (complete layout on page 475 in textbook) A convenient Linux extraction-function: fb_base = pci_resource_start( devp, 0 );
How big is the frame buffer? Size of video memory varies with product Driver needs to determine memory-size PCI: a standard way to determine size (But product might provide support for larger vram than is currently installed) Two-step size-detection method: –First determine maximum supported size –Then check for ‘redundant’ addressing
Maximum memory-size Some bits in ‘base_address0’ are ‘wired’ But other bit-values are ‘programmable’ Bits 0..3 have some special meanings So we will need to examing bits Find least significant ‘programmable’ bit It tells the ‘maximum’ supported memory
Programming algorithm Get configuration longword at offset 0x10 Save it (so that we can restore it later) Write a new value: all bits set to 1’s Read back the longword just written The ‘hard-wired’ bits will still be 0’s We will scan for first bit that’s ‘1’ Be sure to restore the original longword
Loop to find the ‘lowest 1’ Implementing the PCI size-test: pci_write_config_dword( devp, 0x10, ~0 ); pci_read_config_dword( devp, 0x10, &val); inti; for (i = 4; i < 32; i++) if ( val & ( 1 << i ) ) break; fb_maxsize = ( 1 << i );
Checking for memory ‘wrap’ Do vram bytes have multiple addresses? We use a ‘quick-and-dirty’ check write to one address, read from another If what we read didn’t change: no ‘wrap’! Some assumptions we make: –Memory-size will be a power of 2 –If two bytes differ, all between them do, too (Should we question these assumptions?)
Device-memory: read and write The CPU understands ‘virtual’ addresses They must be ‘mapped’ to bus addresses The kernel must setup page-table entries Linux kernel provides functions: vaddr = ioremap( physaddr, memsize ); iounmap( vaddr ); Alternative: can use ‘ioremap_nocache()’
For copying: ram to/from vram Linux provides special ‘memcpy’ functions: –memcpy_fromio( ram, vram, nbytes ); –memcpy_toio( vram, ram, nbytes );
Our ‘vram.c’ driver We imitate the code in our ‘ram.c’ driver We use ‘temporary’ mappings (one page) Our ‘read()’ and ‘write()’ are very similar One notable difference: ‘read()’ is supposed to return 0 in case the file’s pointer is at the ‘end-of-file’ (This defect should be corrected in ‘ram.c’)
Warning about Red Hat 9.0 Red Hat 9.0 is now available in stores It advertises kernel version But it’s not identical to in our class Our demo modules do not always work Changes were made to kernel structures (e.g., task_struct) Changes were made to exported symbols (e.g., sys_call_table)
Need a ‘work-around’ Our ‘vram.c’ doesn’t create its device-node Requires users to create a node manually We ‘hard-coded’ the device major number So decisions differ from our ‘past practice’
Exercises Get ‘vram.c’ from our class website Compile and install the ‘vram.c’ driver Create the device special file: ‘/dev/vram’ Change file-attributes (to allow writing) Try copying video frame-buffer to a file Use ‘dump.cpp’ to view that binary file Try using ‘fileview.cpp’ to view video ram