By Jonathan Corbet
July 30, 2013
Writing device drivers can be a painful process; hardware has a tendency to
behave in ways other than those described in the documentation. The job
can be even harder, though, in the absence of the hardware itself.
Developing a complete driver without the hardware can require a simulator
built into a tool like QEMU — a development project in its own right. For
simpler situations, though, it may be enough to fool the driver about the
contents of a few device registers. Rui Wang's recently posted
I/O hook patch set aims to make that
functionality available.
The I/O hook module works by overriding the normal functions used to access
I/O memory, I/O ports, and the PCI configuration space. When kernel code
calls one of those functions, the new version will check to see whether
an override has been configured for the address/port of interest; if so,
the operation will be redirected. In the absence of an explicit override,
the I/O operation will proceed as usual. Needless to say, adding this kind
of overhead to every I/O operation executed by the kernel could slow things
down significantly. In an attempt to minimize the impact, the static key mechanism is used to patch the
kernel at run time. So the I/O hooks will
not run unless they are in active use at the time.
There is an in-kernel interface that can be used to set up register
overrides; it is a simple matter of calling:
void hook_add_ovrd(int spaceid, u64 address, u64 value, u64 mask,
u32 length, u8 attrib);
Here, spaceid is one of OVRD_SPACE_MEM for regular I/O
memory, OVRD_SPACE_IO for an I/O port, or
OVRD_SPACE_PCICONF for the PCI configuration space. The
combination of address, mask, and length
describe the range of addresses to be overridden, while value is
the initial value to be set in the overridden space. By using the mask
value it is possible to override a
space as narrow as a single bit. The attrib parameter describes
how the space is to behave: OVRD_RW for a normal read/write
register, OVRD_RO for read-only, OVRD_RC for a register
whose bits are cleared on being read, or OVRD_WC to clear bits on
a write.
There are two functions, hook_start_ovrd() and
hook_stop_ovrd(), that are used to turn the mechanism on and off.
Any number of overrides can be set up prior to turning the whole thing on,
so a complicated set of virtual registers can be configured. It's worth
noting, though, that the overrides are stored internally in a simple linked
list, suggesting that the number of overrides is expected to be relatively
small.
While the in-kernel interface may be useful, it will probably be more common
to control this facility through the debugfs interface. The module
provides a set of files through which overrides can be set up; see the documentation file for details on the
syntax. The debugfs interface also provides a mechanism by which a
simulated interrupt can be delivered to the driver; if an interrupt number
is given to the system (by writing it to the appropriate debugfs file),
that interrupt will be triggered once the overrides
are enabled.
A system like this clearly cannot be used to emulate anything other than
the simplest of devices. A real device has a long list of registers and,
importantly, the contents of those registers will change as the device
performs the operations requested of it. One could imagine enhancing this
module with an interface by which a user-space process could supply
register values on demand, but there comes a point where it is probably
better just to add a virtual device to an emulator like QEMU.
So where, then, does a tool like this fit in? The use cases provided with
the patch posting mostly have to do with the testing of hotplug operations
on hardware without hotplug support. A hotplug event typically involves an
interrupt and a relatively small number of registers; by overriding just
those registers, the I/O hook mechanism can convince a driver that its
hardware just went away (or came back). That allows testing the hotplug
paths without needing to have suitably capable hardware.
Similarly, overrides can be used to test error paths by injecting various
types of errors into the system. Error paths are often difficult to
exercise; there are almost certainly large numbers of error paths in the
kernel that have never been executed. Code that has never run has a
higher-than-average chance of containing bugs. The fault injection framework can be used to test
a wide range of error paths, but it is not comprehensive; the I/O hook
module could be useful to fill in the gaps.
But, then, anecdotal evidence suggests that relatively few developers even
use the fault injection facilities, so uptake of a more complex mechanism
may be limited. But, for those who use it, the I/O hook subsystem might
well prove to be a useful addition to the debugging toolbox.
(
Log in to post comments)