Current Linux kernels support a huge number of devices. Device drivers account for half of the size of the source tree (actually two-thirds if you exclude architecture-specific code that you are not using). They account for almost 1500 C-language files and more than 800 headers.
The drivers
directory itself doesn’t host any
source file, only subdirectories (and, obviously, a makefile).
Structuring the huge amount of source code is not easy, and the
developers haven’t followed any strict rules. The original division
between drivers/char
and
drivers/block
is inefficient nowadays, and more
directories have been created according to several different
requirements. Still, the most generic char and block drivers are found
in drivers/char
and
drivers/block
, so we’ll start by visiting those
two.
The drivers/char directory is perhaps the most
important in the drivers
hierarchy, because it
hosts a lot of driver-independent code.
The generic tty layer (as well as line disciplines, tty software
drivers, and similar features) is implemented in this directory.
console.c
defines the linux
terminal type (by implementing its specific escape sequences and
keyboard encoding). vt.c
defines the virtual
consoles, including code for switching from one virtual console to
another. Selection support (the cut-and-paste capability of the Linux
text console) is implemented by selection.c
; the
default line discipline is implemented by
n_tty.c
.
There are other files that, despite what you might expect, are device
independent. lp.c
implements a generic parallel
port printer driver that includes a console-on-line-printer
capability. It remains device independent by using the
parport device driver to map operations to actual
hardware (as seen in Figure 2-2). Similarly,
keyboard.c
implements the higher levels of
keyboard handling; it exports the handle_scancode
function so that platform-specific keyboard drivers (like
pc_keyb.c
, in the same directory) can benefit
from generalized management. mem.c
implements
/dev/mem
, /dev/null
, and
/dev/zero
, basic resources you can’t do without.
Actually, since mem.c
is never left out of the
compilation process, it has been elected as the home of
chr_dev_init, which in turn initializes several
other device drivers if they have been selected for compilation.
There are other device-independent and platform-independent source
files in drivers/char
. If you are interested in
looking at the role of each source file, the best place to start is
the makefile for this directory, an interesting and pretty much
self-explanatory file.
Like the preceding drivers/char
directory,
drivers/block
has been present in Linux
development for a long time. It used to host all block device
drivers, and for this reason it included some device-independent code
that is still present.
The most important file is ll_rw_blk.c
(low-level
read-write block). It implements all the request management functions
that we described in Chapter 12.
A relatively new entry in this directory is
blkpg.c
(added as of 2.3.3). The file implements
generic code for partition and geometry handling in block devices. Its
code, together with the fs/partitions
directory
described earlier, replaces what was earlier part of “generic hard
disk” support. The file called genhd.c
still
exists, but now includes only the generic initialization function for
block drivers (similar to the one for char drivers that is part of
mem.c
). One of the public functions exported by
blkpg.c
is blk_ioctl,
covered by Section 12.6 in Chapter 12.
The last device-independent file found in
drivers/block
is
elevator.o
. This file implements the mechanism to
change the elevator function associated with a block device
driver. The functionality can be exploited by means of
ioctl commands briefly introduced in Section 12.6.
In addition to the hardware-dependent device drivers you would expect
to find in drivers/block
, the directory also
includes software device drivers that are inherently cross-platform,
just like the sbull and
spull drivers that we introduced in this
book. They are the RAM disk rd.c
, the “network
block device” nbd.c
, and the loopback block
device loop.c
. The loopback device is used to
mount files as if they were block devices. (See the manpage for
mount, where it describes the -o
loop option.) The network block device can be used to
access remote resources as block devices (thus allowing, for example,
a remote swap device).
Other files in the directory implement drivers for specific hardware, such as the various different floppy drives, the old-fashioned x86 XT disk controller, and a few more. Most of the important families of block drivers have been moved to a separate directory.
The IDE family of device drivers used to live in
drivers/block
but has expanded to the point where
they were moved into a separate directory. As a matter of fact, the
IDE interface has been enhanced and extended over time in order to
support more than just conventional hard disks. For example, IDE tapes
are now supported as well.
The drivers/ide
directory is a whole world of its
own, with some generalized code and its own programming
interface. You’ll note in the directory some files that are just a few
kilobytes long; they include only the IDE controller detection code,
and rely on the generalized IDE driver for everything else. They are
interesting reading if you are curious about IDE drivers.
This directory is concerned with implementing RAID functionality and the Logical Volume Manager abstraction. The code registers its own char and block major numbers, so it can be considered a driver just like those traditional drivers; nonetheless, the code has been kept separate because it has nothing to do with direct hardware management.
This directory hosts the generic CD-ROM interface. Both the IDE and
SCSI cdrom drivers rely on
drivers/cdrom/cdrom.c
for some of their
functionality. The main entry points to the file are
register_cdrom and
unregister_cdrom; the caller passes them a
pointer to struct cdrom_device_info
as the main
object involved in CD-ROM management.
Other files in this directory are concerned with specific hardware drives that are neither IDE nor SCSI. Those devices are pretty rare nowadays, as they have been made obsolete by modern IDE controllers.
Everything related to the SCSI bus has always been placed in this directory. This includes both controller-independent support for specific devices (such as hard drives and tapes) and drivers for specific SCSI controller boards.
Management of the SCSI bus interface is scattered in several files:
scsi.c
, hosts.c
,
scsi_ioctl.c
, and a dozen more. If you are
interested in the whole list, you’d better browse the makefile, where
scsi_mod-objs
is defined. All public entry points
to this group of files have been collected in
scsi_syms.c
.
Code that supports a specific type of hardware drive plugs into the
SCSI core system by calling scsi_register_module
with an argument of MODULE_SCSI_DEV
. This is how
disk support is added to the core system by sd.c
,
CD-ROM support by sr.c
(which, internally, refers
to the cdrom_ class of functions), tape support
by st.c
, and generic devices by
sg.c
.
The “generic” driver is used to provide user-space programs with
direct access to SCSI devices. The underlying device can be virtually
anything; currently both CD burners and scanner programs rely on the
SCSI generic device to access the hardware they drive. By opening the
/dev/sg
devices, a user-space driver can do
anything it needs without specific support in the kernel.
Host adapters (i.e., SCSI controller hardware) can be plugged into the
core system by calling scsi_register_module with
an argument of MODULE_SCSI_HA
. Most drivers
currently do that by using the scsi_module.c
facility to register themselves: the driver’s source file defines its
(static) data structures and then includes
scsi_module.c
. This file defines standard
initialization and cleanup functions, based on
<linux/init.h>
and the init calls
mechanisms. This technique allows drivers to serve as either modules
or compiled-in functions without any #ifdef
lines.
Interestingly, one of the host adapters supported in
drivers/scsi
is the IDE SCSI emulation code, a
software host adapter that maps to IDE devices. It is used, as an
example, for CD mastering: the system sees all of the drives as SCSI
devices, and the user-space program need only be SCSI aware.
Please note that several SCSI drivers have been contributed to Linux by the manufacturers rather than by your preferred hacker community; therefore not all of them are fun reading.
As you might expect, this directory is the home for most interface
adapters. Unlike drivers/scsi
, this directory
doesn’t include the actual communication protocols, which live in the
top-level net
directory tree. Nonetheless,
there’s still some bit of software abstraction implemented in
drivers/net
, namely, the implementation of the
various line disciplines used by serial-based network communication.
The line discipline is the software layer responsible for the data
that traverses the communication line. Every tty device has a line
discipline attached. Each line discipline is identified by a number,
and the number, as usual, is specified using a symbolic name. The
default Linux line discipline is N_TTY
, that is,
the normal tty management routines, defined in
drivers/char/n_tty.c
.
When PPP, SLIP, or other communication protocols are concerned,
however, the default line discipline must be replaced. User-space
programs switch the discipline to N_PPP
or
N_SLIP
, and the default will be restored when the
device is finally closed. The reason that
pppd and
slattach don’t exit, after setting up the
communication link is just this: as soon as they exit, the device is
closed and the default line discipline gets restored.
The job of initializing network drivers hasn’t yet been transferred to
the init calls mechanism, because some subtle technical details
prevent the switch. Initialization is therefore still performed the
old way: the Space.c
file performs the
initialization by scanning a list of known hardware and probing for
it. The list is controlled by #ifdef
directives
that select which devices are actually included at compile time.
Like drivers/scsi
and
drivers/net
, this directory includes all the
drivers for sound cards. The contents of the directory are somewhat
similar to the SCSI directory: a few files make up the core sound
system, and individual device drivers stack on top of it. The core
sound system is in charge of requesting the major number
SOUND_MAJOR
and dispatching any use of it to the
underlying device drivers. A hardware driver plugs into the core by
calling sound_install_audiodrv, declared in
dev_table.c
.
The list of device-independent files in this directory is pretty long, since it includes generic support for mixers, generic support for sequencers, and so on. To those who want to probe further, we suggest using the makefile as a reference to what is what.
Here you find all the frame buffer video devices. The directory is
concerned with video output, not video input. Like
/drivers/sound
, the whole directory implements a
single char device driver; a core frame buffer system dispatches
actual access to the various frame buffers available on the computer.
The entry point to /dev/fb
devices is in
fbmem.c
. The file registers the major number and
maintains an internal list of which frame buffer device is in charge
of each minor number. A hardware driver registers itself by calling
register_framebuffer, passing a pointer to
struct fb_info
. The data structure includes
everything that’s needed for specific device management. It includes
the open and release
methods, but no read, write,
or mmap; these methods are implemented in a
generalized way in fbmem.c
itself.
In addition to frame buffer memory, this directory is in charge of
frame buffer consoles. Because the layout of pixels in frame buffer
memory is standardized to some extent, kernel developers have been
able to implement generic console support for the various layouts of
display memory. Once a hardware driver registers its own
struct fb_info
, it automatically gets a text
console attached to it, according to its declared layout of video
memory.
Unfortunately, there is no real standardization in this area, so the
kernel currently supports 17 different screen layouts; they range from
the fairly standard 16-bit and 32-bit color displays to the hairy VGA
and Mac pixel placements. The files concerned with placing text on
frame buffers are called
fbcon-
name
.c
.
When the first frame buffer device is registered, the function
register_framebuffer calls
take_over_console (exported by
drivers/char/console.c
) in order to actually set
up the current frame buffer as the system console. At boot time,
before frame buffer initialization, the console is either the native
text screen or, if none is there, the first serial port. The command
line starting the kernel, of course, can override the default by
selecting a specific console device. Kernel developers created
take_over_console to add support for frame buffer
consoles without complicating the boot code. (Usually frame buffer
drivers depend on PCI or equivalent support, so they can’t be active
too early during the boot process.) The
take_over_console feature, however, is not
limited to frame buffers; it’s available to any code involving any
hardware. If you want to transmit kernel messages using a Morse beeper
or UDP network packets, you can do that by calling
take_over_console from your kernel module.
Input management is another facility meant to simplify and standardize
activities that are common to several drivers, and to offer a unified
interface to user space. The core file here is called
input.c
. It registers itself as a char driver
using INPUT_MAJOR
as its major number. Its role is
collecting events from low-level device drivers and dispatching them
to higher layers.
The input interface is defined in
<linux/input.h>
. Each low-level driver
registers itself by calling
input_register_device. After registration, users
are able to feed new events to the system by calling
input_event.
Higher-level modules can register with input.c
by
calling input_register_handler and specifying
what kind of events they are interested in. This is, for example, how
keybdev.c
expresses its interest in keyboard
events (which it ultimately feeds to
driver/char/keyboard.c
).
A high-level module can also register its own minor numbers so it can
use its own file operations and become the owner of an input-related
special file in /dev
. Currently, however,
third-party modules can’t easily register minor numbers, and the
feature can be used reliably only by the files in
drivers/input
. Minor numbers can currently be
used to support mice, joysticks, and generic even channels in user
space.
This directory, introduced as of version 2.4.0-test7, collects other
communication media, currently radio and video input devices. Both the
media/radio
and media/video
drivers currently stack on video/videodev.c
,
which implements the “Video For Linux” API.
video/videodev.c
is a generic container. It
requests a major number and makes it available to hardware
drivers. Individual low-level drivers register by calling
video_register_device. They pass a pointer to
their own struct video_device
and an integer that
specifies the type of device. Supported devices are frame grabbers
(VFL_TYPE_GRABBER
), radios
(VFL_TYPE_RADIO
), teletext devices
(VFL_TYPE_VTX
), and undecoded vertical-blank
information (VFL_TYPE_VBI
).
Some of the subdirectories of drivers
are
specific to devices that plug into a particular bus architecture. They
have been separated from the generic char
and
block
directories because quite a good deal of
code is generic to the bus architecture (as opposed to specific to the
hardware device).
The least populated of these directories is
drivers/pci
. It contains only code that talks
with PCI controllers (or to system BIOS), whereas PCI hardware drivers
are scattered all over the place. The PCI interface is so widespread
that it makes no sense to relegate PCI cards to a specific place.
If you are wondering whether ISA has a specific directory, the answer
is no. There are no specific ISA support files because the bus offers
no resource management or standardization to build a software layer
over it. ISA hardware drivers fit best in
drivers/char
or
drivers/sound
or elsewhere.
Other bus-specific directories range from less known internal computer buses to widely used external interface standards.
The former class includes drivers/sbus
,
drivers/nubus
, drivers/zorro
(the bus used in Amiga computers), drivers/dio
(the bus of the HP300 class of computers), and
drivers/tc
(Turbo Channel, used in MIPS
DECstations). Whereas sbus
includes both SBus
support functions and drivers for
some SBus devices, the others include only support functions.
Hardware drivers based on all of these buses live in
drivers/net
, drivers/scsi
,
or wherever is appropriate for the actual hardware (with the exception
of a few SBus drivers, as noted). A few of these buses are currently
used by just one driver.
Directories devoted to external buses include
drivers/usb
, drivers/pcmcia
,
drivers/parport
(generic cross-platform parallel
port support, which defines a whole new class of device drivers),
drivers/isdn
(all ISDN controllers supported by
Linux and their common support functions),
drivers/atm
(the same, for ATM network
connections), and drivers/ieee1394
(FireWire).
Sometimes, a computer platform has its own directory tree in the
drivers
hierarchy. This has tended to happen when
kernel development for that platform has proceeded alongside the main
source tree without being merged for a while. In these cases, keeping
the directory tree separate helped in maintaining the code. Examples
include drivers/acorn
(old ARM-based computers),
drivers/macintosh
,
drivers/sgi
(Silicon Graphics workstations), and
drivers/s390
(IBM mainframes). There is little of
value, usually, in looking at that code, unless you are interested in
the specific platform.
There are other subdirectories in drivers
, but
they are, in our opinion, currently of minor interest and very
specific use. drivers/mtd
implements a Memory
Technology Device layer, which is used to manage solid-state disks
(flash memories and other kinds of
EEPROM). drivers/i2c
offers an implementation of
the i2c protocol, which is the “Inter Integrated Circuit” two-wire
bus used internally by several modern peripherals, especially frame
grabbers. drivers/i2o
, similarly, handles I2O
devices (a proprietary high-speed communication standard for certain
PCI devices, which has been unveiled under pressure from the free
software community). drivers/pnp
is a collection
of common ISA Plug-and-Play code from various drivers, but fortunately
the PnP hack is not really used nowadays by manufacturers.
Under drivers/
you also find initial support for
new device classes that are currently implemented by a very small
range of devices.
That’s the case for fiber channel support
(drivers/fc4
) and
drivers/telephony
. There’s even an empty
directory drivers/misc
, which claims to be “for
misc devices that really don’t fit anywhere else.” The directory is
empty of code, but hosts an (empty) makefile with the comment just
quoted.
The Linux kernel is so huge that it’s impossible to cover it all in a
few pages. Moreover, it is a moving target, and once you think you are
finished, you find that the new patch released by your preferred
hackers includes a whole lot of new material. It may well be that the
misc
directory in 2.4 is not empty anymore as you
read this.
Although we consider it unlikely, it may even happen that 2.6 or 3.0 will turn out to be pretty different from 2.4; unfortunately, this edition of the book won’t automatically update itself to cover the new releases and will become obsolete over time. Despite our best efforts to cover the current version of the kernel, both in this chapter and in the whole book, there’s no substitute for direct reference to the source code.
Get Linux Device Drivers, Second Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.