200 likes | 209 Views
An overview of the PCI interface in Linux, including device data extraction, frame buffer detection, and memory size determination. Learn to write a character device driver for video RAM.
E N D
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 <linux/pci.h>
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: 0x030000 • ’03’ means a ‘display device’ • ’00’ means ‘VGA compatible’ • ’00’ means ‘revision-number’
pci_find_class(); • Define the manifest constant: #define VGA_CLASS 0x030000 • 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 4..31 • 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); • int i; • 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 2.4.20 • But it’s not identical to 2.4.20 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