Introduction to writing and using Linux drivers
Updated: 2004-01-28
Created: 2004-01-14
- A device driver: drives a devices and
on behalf of some other program.
- In a typical UNIX or GNU/Linux system there are three types
of drivers, depending on the program they are written for:
- Kernel generic device drivers.
- X11 graphics card and input device drivers.
- Ghostscript printer drivers.
It's more complicated than this, though (GGI,
svgalib
, ...)
- In all cases a driver is a library.
- Libraries may be statically or dynamically linked into
the program that uses them, and so drivers.
- On the device side, a driver does one or both of things:
- Operates the state machine of the device.
- Transfers data between the device and RAM.
UNIX is an IO multiplexor (Thompson)
- We will discuss generic drivers, and in particular
drivers in the
kernel,
not in a user process (kernel mode linux etc.).
- Almost all of the Linux kernel source is drivers.
- Many types of kernel drivers:
- Network devices.
- Character vs. block devices.
- USB/SCSI/1394 buses and devices.
- lots more...
- Other kernel modules, not quite drivers: filesystems,
protocols, subsystems, ...
- Each kernel version does things differently.
- Much more important to understand the principles, the details
are always in the source.
- The driver mechanism can be abused to provide
almost arbitrary kernel extensions.
Very common with closed source UNIX, less so with open source
Linux.
- All drivers, which are basically leaf glue code between an IO
multiplexor and a device, have three aspects to them, which
can/should be distinct code:
- Code that models and drives the state
machine of the device.
- Code that accepts requests from the kernel.
- Code that uses the resources of the kernel to implement
the previous two parts.
- Some of the kernel is not directly part of the IO
multiplexor: it's helper functions for drivers.
- The most important kernel resource used by a driver is
memory, either for keeping track of state, or for buffering
transfers. The second most important, if used, is threads (or
similar).
- It is easy to write a simple driver; it is very easy to
write a bad driver; it is difficult to write a good one; it is
difficult and time consuming to figure out devices; it is time
consuming to track kernel internal evolution.
- There are two types of state machines in devices:
- synchronous
- For devices that change state only in response to
device driver interactions, or apparently so. For example,
a
USB Christmas tree.
- asynchronous
- Some devices can change state at any time. For example,
an Ethernet card, because of packets arriving, or a SCSI HA,
because of operations completing.
- It is much harder to handle asynchronous state machines:
- Driver must lend a thread of control (interrupts,
...) at any time (bad news!) to the device to let it update the
driver's state machine representation.
- Even worse when data transfers accompanies state
change (allocations at inconvenient times).
- Especially with asynchronous device one has several hard
problems:
- Figure out how to change the state of the machine. Reverse
engineering often only way.
- Ensure that concurrent status updates, from the device and
from the IO multiplexor, occur in the right order.
- Handle concurrent requests in the case of multiple CPUs.
- Ensure the device state is kept well defined in case of
foreseen and unforeseen errors.
- For devices doing data transfers there are additional
concerns, for example:
- Allocate memory resources optimally for data transfers.
- Don't waste time on memory copies for data transfers.
- Convert data formats between device format and that expected
by kernel and applications (but only if really necessary).
- A good definition of the device state machine is very very
important, also a precide definition of the commands that change
the state.
- What kind of commands?
- The commands are device dependent, but there are broad
standards.
- If implemented, the standards are often implemented
with several mistakes.
- The documentation of the standards may be expensive.
- Reverse engineering is often needed even in the presence of
documentation.
- Linux driver authors have produced reverse engineering
tools, and there are hardware probes that are however
often expensive.
- Obtaining documentation and/or reverse engineering the state
machine is often the most difficult and time consuming part of
writing a device driver.
- The kernel will invoke the driver, synchronously.
- The invocation usually is triggered by some user program
action, but sometimes not voluntarily.
- Examples: the user program wants to read a character from a
serial port is voluntary invocation; a disk driver is used to
write some cached file blocks to disk because a user program
is filling the cache is involuntary invocation.
- In all cases, a well defined table of function
pointers must be defines by the driver as its main invocation
interface.
- Sometimes the user program directly invokes the
driver's routines; sometimes the user program directly invokes
a kernel subsystem, and this invokes the driver.
- The specific table of function pointers the driver has to
provide depends on the type of direct interface, or the type
of subsystem that invokes the driver.
- The functions a provider need to provide depend on the type
of device interface and subsystem the driver can be invoked by.
- Types of devices interface:
- character device (direct interface);
- block device (indirect interface via
request
queue);
- network device (not in filesystem,
socket buffer
queue).
- Main types of subsystem:
- SCSI/USB/IEEE1394/... bus;
- ALSA/OSS sound;
- console video;
and many more others.
- Pitfalls:
- Kernel only does minimal argument security and
validity checking.
- Subsystem interfaces change relatively often.
- Traditionally a driver has been divided in a top
half and a bottom half.
- The top half deals with requests and data transfers from
user programs or kernel subsystems.
- The bottom half deals with the actual device.
- Either may be minimal. In some cases the bottom half
may in effect be entirely missing (in memory devices like
/dev/null
at one extreme).
- The bottom half may involve, for asychronous devices,
independent processes/threads/tasklets that interact with the
top half using queues or shared buffers and interlocking.
- Static data:
- Tables of constants.
- Table of hardware device states.
- Table of virtual device states.
- Table of function pointers.
- Table of module parameters.
- Functions (usually in most specific to most general order):
- Driver initialization/finalization.
- Bottom half:
- Device access.
- Interrupt handling.
- Processes/treads/tasklets.
- Top half:
- Utilities and error handling.
- Open/close device.
- Ioctl/seek/... device.
- Read/write device, or process queue.
- Kernel services are usually essential.
- The kernel service API changes frequently:
Linus makes no guarantees.
- Types of kernel services:
- Platform abstraction.
- Access checking.
- Buffer management.
- Memory allocation.
- Locking.
- Taking.
- Usually drive hardware directly.
- Often not part of a subsystem.
- Very different purposes.
- Same
function table
for all of them, with many entries, not all them must be
implemented.
- Almost invariably part of a subsystem.
- Have to handle possibly complex buffering issues, for
example buffering while seeking. Automagic.
- Based on a producer/consumer relationship with the buffer
management code, via a queue of read/write
requests.
- Minimize copying: UNIX is memory-to-memory copy limited.
- Probably the most difficult.
- Extremely asynchronous nature.
- Hardware has any weird quirks.
- Too complex a subject. for here.
- Very advanced course.
- Usually not much in the way of operations, but must keep
list of subordinate devices and handle
/proc
,
hotplugging, etc.
- First and foremost, hardware device information.
- Take a driver of the same ilk and modify it; alternatively
some drivers are very simple and can be used as starting
points.
- Never forget to write the man page (section 4) and
documentation, both of the device and of the internal
logic of the driver. Make both terse, readers
of driver documentation should be assumed to be knowledgeable.
- Always, always put in:
- Module wrapper.
- Hotplugging hooks.
devfs
/sysfs
hooks.
/proc
handler.
- These also help a lot:
- Device reset and restart logic.
- Many assertions, especially on real device side.
- Copious parametric debugging output.
- Tests on kernel version (ideally in header).
- Minimal starting point drivers:
and lots more.
- More developed but still usable drivers:
- A hideous hack, but very useful regardless.
- Boilerplate module initialization/deinitilization
system.
- Exporting symbols, as few as possible.
- Importing symbols, as few as possible.
- Concept of module stack.
- The user level utilities.
- There are good HOWTOs on
building the kernel
an
dealing with modules.