In Linux, a device driver is code that implements a userspace orkernelspace abstraction of a physical device. Examples of devicedrivers include code that allows user applications to stream datathrough a 16550 UART, code that configures an Epson S1D13xxx LCDcontroller chip, and code manages the AT91RM9200's built-in Ethernetcontroller.
Device drivers come in several different types, depending on whatabstraction they provide. Serial port drivers, for example, oftenimplement the character device type. The framebuffer driver type isnormally used to enable userspace applications to write to an LCD orCRT display. Ethernet driversallow the Linux kernel's TCP/IP protocol stack to send packets over anEthernet network.
A device driver abstraction is purely that, an abstraction. Theunderlying hardware associated with an Ethernet device driver may notactually be an Ethernet controller, for example; there may not even beany physical hardware at all! Such is the case for the usbnet driver,which allows a USB Device to communicate with a USB Host as though thetwo were connected via Ethernet. (The usbnet driver packages Ethernetpackets and forwards them to the USB host/device drivers fortransmission).
There may be more than one device driver abstraction associated witha particular piece of hardware. A multifunction chip, like the SiliconMotion SM501 Multimedia Controller, will usually have one or moreassociated framebuffer drivers for its video controllers, a USB Hostbus controller driver for its OHCI Host port, an AC97 codec driver forits AC97-Link interface, and a character driver for its serial ports.
For many device driver types, user applications communicate with thedriver via a pseudofile called a devicenode . Device nodes look like files, and applications can even open() and close() them.But when data is written to a device node, the data is passed to thenode's associated driver, and not stored in a filesystem.
To send data out a serial port, for example, an application willoften open and write data to a device node named /dev/ttyS0 . Thedevice driver associated with that node handles enabling and disablinginterrupts on the UART, queuing bytes for transmission, anddeactivating the UART when transmission is complete.
The name of a device node is arbitrary, but there are someconventions. Serial ports are often called ttyS
You can see the major and minor numbers associated with a devicenode using thels command:
$ ls -l /dev/console
crw——-1 bgat root 5, 1 Mar 7 08:30 /dev/console
The /dev/consoledevice node, which helps applications communicate with theirassociated display device, is major number 5, minor number 1. Withinthe Linux kernel sources, a device driver (the console device driver)registers itself as “driver major number 5”, which associates it withthe /dev/console device node.
The crw in the above output indicates that the console device node expects tobe associated with a character device abstraction, and may be both readfrom and written to.
The character device driver implements a byte-oriented interface thatcan be read from or written to. The interface is also stream-oriented,meaning that you cannot “seek” forwards or backwards through the datathe way you can with an ordinary data file. Character devices areappropriate for things like serial port drivers, etc.
The most important data structure used by character device driversis the file_operations structure, a portion of which appears below:
Character device drivers at a minimum must implement the open() and release() methods, but usually also implement the read() and write() methodsas well. The poll() method assists applications in “sleeping” until data isavailable, and the ioctl() method provides an out-of-band channel often used for thingslike specifying the bit rate and parity settings in a UART driver'sassociated hardware device.
About struct cdev
Later Linux-2.6 kernels offer a struct cdev structure,which encapsulates the file_operations structure and some other important driver information. By using cdev , character devices gain a moreexible and uniform interface to the kernel's internal character deviceresources, including proc filesystem entries and udev .
All new character drivers are expected to use cdev; existing driversare being migrated as time permits.
The ioctl() entrypoint in a character device driver provides for out-of-bandcommunications with the device driver itself. Common uses for thiscapability are to assert bit rate and parity settings for serial porthardware, and to change framebuffer modes.
A character device driver's ioctl method looks like this:
This example prints the parameters passed via the ioctl() systemcall. Device drivers that offer ioctls provide enumerations for the cmd parameter, and when thosecommands need arguments, the arg parametercan pass either an unsigned long parameter value, or the address of adata structure containing a collection of parameter values.
An application with a tweak_example_device()function that sends a hypothetical MYDRIVER_IOCTL1 command to a driver might look like this:
A common usage for ioctls is to enable the “user interrupt” featureof the real-time clocks used in many PCs. The code to do that mightlook like this:
Under Linux, interrupt handlers are not confined to device drivers.Functions not associated with device drivers may also bind to interruptsources. An LED that blinks when a button is pressed is most easilydone using a simple interrupt handler, for example.
Interrupt handler functions bind to interrupt sources using thekernel'srequest_irq() function. Under Linux interrupt handlers areordinary C functions, but they are restricted in the kernel featuresthey may use. Interrupt handlers cannot block on semaphores, forexample.
A simple interrupt handler for a hypothetical interrupt source namedGPIO_PIN_PB29 looks like this:
This interrupt handler disables the associated interrupt source whenten interrupts are detected (a button is pressed ten times, forexample). Assuming the host processor provides a suitable definitionfor GPIO_PIN_PB29 ,the handler may be bound to the interrupt source using somethingsimilar to the following code:
A call to request_irq() may fail if a previously-registered interrupt handler did not permitthe source to be shared with other handlers.
A block device driver implements an abstraction commonly associatedwith hard drives and other data-block-oriented media. A block deviceoffers persistent storage, which (generally) allows applications to”seek” data within the device. Properly-implemented block devicedrivers can be controlled by the kernel's Virtual File Systemfunctionality, allowing all manner of devices to be used to storenearly all kernel-supported filesystems with little additional effort.
Block device drivers use the same file_operations structure used bycharacter device drivers, but use structure members that are ignored bycharacter devices. Block device drivers are more complex than characterdevice drivers. The best example of a block device driver is “sbull “, from Rubini's LinuxDevice Drivers .
MTD Chips and Maps
The Memory Technology Devices (MTD) implementation uses a layereddriver approach. Chip drivers control the memory device itself, and mapdrivers use chip drivers to interact with memory chips using either ablock or character device API. The JFFS2 filesystem depends on MTD, butMTD may also be used with other filesystems.
Among other things, MTD chip device drivers can probe ash memorychips and similar media to identify their geometry. This informationallows MTD to properly erase and program the device. With the rise ofthe Common Flash Interface (CFI) protocol, the need to implement newchip device drivers is fast becoming history.
MTD map drivers provide the lowest-level read/write functionalityfor ash media, which is useful for hardware that makes ash memoryavailable in pages rather than one large window in the host's memoryspace (very large ash chips may exceed the memory space accessible bythe hardware). Map drivers also tell MTD the physical addresses ofavailable ash memory, and can be used to limit MTD's accesses to onlycertain areas of that memory.
MTD block device nodes are usually named /dev/mtdblock
Framebuffer device drivers provide for direct userspace access to videoframe buffers : thememory space used by an LCD controller to store the image actuallyvisible on the LCD panel. XFree86, Qtopia, Microwindows and other GUIlibraries interact directly with frame buffer memory.
Framebuffer drivers are mostly informational; they providestandardized interfaces that allow applications to set and query videomodes and refresh rates. User applications use the framebuffer driver'smmap() system call to locate frame buffer memory.
The properties of a framebuffer driver's associated hardware arestored in a structfb_info structure. This structure also contains the physicaladdress of the frame buffer memory, and the physical addresses of theregisters that control display modes, geometry and timing.
Framebuffer drivers also use a struct fb_ops structure, which is roughly equivalent to the struct file_operations structure used by character and block device drivers. Applications likefbset use a standardized set of ioctls to invoke these frame buffer”operations”.
Framebuffer device nodes are usually named /dev/fb
I2C Bus Interfaces and Chips
The Linux I2C implementation uses a layered stack of device drivers tocontrol chips connected to an I2C-compatible bus. At the lowest levelare “algorithm” and “bus” drivers, which provide a uniform abstractionfor interacting with the physical I2C media. I2C bus implementationsvary, from simple “bit bang” GPIO-based signaling to high-performancededicated peripherals.
I2C “chip” drivers control a member of an I2C bus, includingtemperature sensors, GPIO controllers, ADCs, and so forth. Theuniformity provided by I2C algorithm and bus drivers allow the samechip driver to be used regardless of how the actual I2C bus isimplemented.
I2C chip drivers provide a detect() function that allows the I2C system to confirm the presence of theassociated device. If the device is found, the driver registers itselfwith the I2C system using the i2c_attach_client() function.
Once registered, I2C chip drivers vary widely in the kernelresources they use. Touch screen controller chip drivers, for example,may interact with the kernel's input subsystem, interrupt handlers,proc filesystem, and other functionality. Temperature and fan speedsensors are more uniform, so that userspace monitoring applicationswill work across the wide range of devices employed by PCs and embeddedhardware.
Ethernet drivers provide an Ethernet-specific abstraction that focusesalmost exclusively on data exchange with the network media accesscontroller (MAC). Other network-related functions, like IP packetassembly and decoding, are handled within the kernel's network protocolstacks and do not directly influence the implementation of an Ethernetdevice driver.
The most common reason for getting involved with an Ethernet devicedriver is to add support for a new physical access controller (PHY). Inan existing Ethernet device driver's probe() function, the PHY must often be identified so that the Ethernet networkcontroller can be properly configured.
The network controller generally provides control registers forcommunicating with an attached PHY, including reading the device's IDregister. Often, the only change required is to add an enumeration sothat the new PHY will be recognized during probing.
The Platform Model
The Platform Model offered by kernel versions 2.6 and beyond provides arefined way to attach devices to drivers that eliminates the need fordevice drivers to contain hard coded physical addresses of the devicesthey control. The platform model also prevents resource conflicts andimproves kernel portability, helps get startup ordering right, andintegrates with the kernel's power management features.
Under the platform model, device drivers know how to control adevice once informed of its physical location and interrupt lines. Thisinformation is provided to the driver in the form of a resource list”passed to the driver during probing.
A framebuffer driver needs to know the physical addresses of theframe buffer memory and control registers. The resource list might looklike this:
Once the resources are defined, that information is merged into a platform_device structure:
Finally, the resource list and associated driver name areregistered:
At some point during kernel startup, a device driver named “s1d13xxxfb ” mayregister itself. After registration, the platform model implementationinvokes the driver's probe() function, passing it the associated resource list. Helper functionsallow the driver to extract resource entries from the list and therebylocate the device in question.
BillGatliff is a freelance embedded developer and training consultant with10 years of experience using GNU and other tools for building embeddedsystems targeting automotive, aerospace, and medical instrumentationapplications. He is a contributing editor for Embedded Systems Design,author of the Embedded GNU Jumpstart and Embedded Linux Jumpstartseries of training materials.
This paper was written for andpresented at the Embedded Systems Conference Silicon Valley 2006. Formore information, please visit www.embedded.com/esc/sv