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