While playing with scull and similar toys can be a pleasant way to become familiar with the software interface of a Linux device driver, testing a real device requires hardware. The driver is the abstraction layer between software concepts and hardware circuitry; as such, it needs to talk with both of them. Up to now, we have examined the internals of software concepts; this chapter should complete the picture by showing you how a driver can access I/O ports and I/O memory, while being portable across Linux platforms.
As usual, I won’t bind the sample code to a particular device. However, we can no longer use a memory-based device like scull. Instead, the examples in this chapter use the parallel port to show I/O instructions and the standard video buffer of text-mode VGA boards to show memory-mapped I/O.
I chose the parallel port because it offers direct input and output of several bits of information. Data bits written to the device appear on the output pins, and voltage levels on the input pins are directly accessible by the processor. In practice, you have to connect LEDs to the port to actually see the results of an I/O operation. The parallel port is easy to program, much easier than the serial port, and almost every computer (even the Alpha) has a parallel port that works like the one in the PC.
As far as memory-mapped I/O is concerned, text-mode VGA is the most standardized memory-mapped device, and almost every computer has a VGA-compatible text mode. Unfortunately, not every Alpha has a VGA video adapter, and the Sparc definitely doesn’t, so our VGA-related code won’t be as portable as the parallel port example. Also, you’ll have to switch the computer to text mode in order to run the sample code, which shouldn’t be a serious constraint. The biggest problem with experimenting using VGA memory is that the sample driver will unavoidably trash the foreground virtual console.
In some sense, I/O ports are like memory locations: they can be read and written by means of the same electrical signals that memory chips receive. But they are not exactly the same; port operations talk directly to peripheral devices, which are often less flexible than RAM. In particular, there are 8-bit ports, 16-bit ports, and 32-bit ports, and you can’t mix them.[23]
A C program, therefore, must call different functions to access
different size ports. The Linux kernel headers (specifically, the
architecture-dependent header <asm/io.h>
) define the
inline functions listed below.
From now on, when I use unsigned
without further
type specifications, I am referring to an architecture-dependent
definition whose exact nature is not relevant. The functions are
almost portable because the compiler automatically casts the
values during assignment--their being unsigned helps prevent
compilation-time warnings. No information is lost with such
casts as long as the programmer assigns sensible values to avoid
overflow. I’ll stick to this convention of ``incomplete typing'' for
the rest of the chapter.
unsigned inb(unsigned port);
,
void outb(unsigned char byte, unsigned port);
Read or write byte ports (8 bits wide). The port
argument is defined as unsigned long
for some
platforms and unsigned short
for others. The return
type of inb is also different across architectures.
unsigned inw(unsigned port);
,
void outw(unsigned short word, unsigned port);
These functions access 16-bit ports (``word-wide''), they are not available in the M68k version of Linux because the processor supports byte I/O, but neither word nor long operations.
unsigned inl(unsigned port);
,
void outl(unsigned doubleword, unsigned port);
These functions access 32-bit ports.
doubleword
is either declared as unsigned long
or unsigned int
, according to the platform.
In addition to the single-shot in and out operations, most processors implement special instructions to transfer a sequence of bytes, words, or longs to and from a single I/O port. These are the so-called ``string instructions,'' which are introduced in Section 8.1.3, later in this chapter.
Note that no 64-bit I/O operations are defined. Even on 64-bit architectures, I/O ports only use a 32-bit data path.
The functions described above are primarily meant to be used by device
drivers, but they can also be used from user space (the preprocessor
definitions or inline declarations are not protected by #ifdef __KERNEL__
). The following conditions should, however, apply in
order for inb and friends to be used in user-space code:
The program must be compiled with the -O option to force expansion of inline functions.
ioperm or iopl must be used to get permission to perform I/O operations on ports. ioperm gets permission for individual ports, while iopl gets permission for the entire I/O space. Both these functions are Intel-specific.
The program must run as root to invoke ioperm. Alternatively, one of its ancestors must have gained port access running as root.
The sample sources misc-progs/inp.c
and misc-progs/outp.c
are a minimal tool for reading and writing 8-bit ports
from the command line, in user space. I have run them successfully on
my PC. They don’t run on other plaftorms due to the
missing ioperm capability. The programs can be made set-uid, if you
want to live dangerously and play with your hardware without acquiring
explicit privileges.
If you are looking for porting problems, you’ll find that I/O instructions are the most processor-dependent of all computer instructions. As a consequence, much of the source code related to port I/O is platform-dependent.
The Linux system, though portable, isn’t completely transparent to processor peculiarities. Most hardware devices are not portable across platforms, and driver writers don’t generally address more than two or three architectures in the same module.
You can see one of the incompatibilities, data typing, by looking
back at the list of
functions, where the arguments are typed differently based
on the architectural differences between platforms. For example, a
port is unsigned short
on the x86 (where the processor
supports a 64KB-byte I/O space), but unsigned long
on
the Alpha, whose ports are just special locations in the same address
space as memory; the Alpha, by design, has no I/O address space, and its
ports are folded to non-cacheable memory addresses.
I/O typing is one part of the kernel that still needs some
cleaning up, although things work correctly now. The best solution to
ambiguous typing would be to define an architecture-specific
port_t
type and use u8
, u16
, and
u32
for the data items (see Section 10.2
in Chapter 10). Nobody has taken care of the problem yet,
however, as the issue is mostly cosmetic.
Other platform dependencies arise from basic structural differences in the processors and thus are unavoidable. I won’t go into detail about the differences, because I assume that you won’t be writing a device driver for a particular system without understanding the underlying hardware. Instead, the following is an overview of the capabilities of the supported architectures:
The architecture supports all the functions described in this chapter.
All the functions are supported, but there are
differences in the implementation of port I/O for different
Alpha platforms. String functions are implemented in C and
defined in arch/alpha/lib/io.c
. Unfortunately, only
word and long string operations are exported in 2.0
kernels till 2.0.29; therefore, insb and outsb
are not available to
modules. This problem has been fixed in version 2.0.30
and 2.1.3.
The Sparc doesn’t have special I/O instructions. I/O space is memory-mapped and is marked by flags in the page-table entry. The header defines empty functions for inb and the other functions to prevent the compiler from complaining when first porting drivers to the Sparc architecture.
Only inb, outb, and their pausing counterparts (see below) are supported. No string functions are defined for the 68000, nor are readb, writeb, and friends defined.
The Mips port supports all the functions. String operations are implemented with tight assembly loops, as the processor lacks machine-level string I/O.
The curious reader can extract more information from the
io.h
files, which sometimes define a few architecture-specific
functions in addition to those I describe in this chapter.
It’s interesting to note that the Alpha processors don’t feature a different address space for ports, even though AXP computers are often shipped with ISA and PCI slots, and both buses feature signal lines to differentiate memory and I/O operations. Alpha-based PCs implement the Intel-compatible I/O abstraction through specific interface chips that translate references to particular memory addresses into I/O port access.
I/O operations on the Alpha are well described in the ``Alpha Reference Manual,'' which is available free from Digital Equipment Corporation. The manual thoroughly describes the I/O issue and tells how the AXP processors divide virtual addresses into ``memory-like'' and ``non-memory-like'' regions; the latter are used for memory-mapped I/O.
Some platforms--most notably the i386--can have problems when the processor tries to transfer data too quickly to or from the bus. The problems can arise because the processor is over-clocked with respect to the ISA bus, and can show up when the device board is too slow; the solution is to insert a small delay after an I/O instruction if another such instruction follows. If your device misses some data, or if you fear it might miss some, you can use pausing functions in place of the normal ones. The pausing functions are exactly like those listed above, but their names end in _p; they are called inb_p, outb_p, and so on. For all the supported architectures, when the non-pausing function is defined, the pausing equivalent is defined as well, even in cases where they expand to the same code.
If you want to explicitly insert a small pause in your driver
(smaller than you’d get with udelay), you can use the explicit
SLOW_DOWN_IO
statement. This macro expands to
instructions that do nothing except delay execution. You might want to
insert the statement at critical points in your
source. SLOW_DOWN_IO
actually executes the same code as that
added to outb when outb_p is expanded.
The definition of SLOW_DOWN_IO
(and thus the
_p
pause) depends on whether SLOW_IO_BY_JUMPING
and/or
REALLY_SLOW_IO
are defined before <asm/io.h>
is included. Fortunately, new hardware doesn’t require
the programmer to deal with these questions, so I won’t talk
about pausing any more. The interested reader is urged to browse
<asm/io.h>
. As a driver writer, you should nonetheless remember that
SLOW_DOWN_IO
is undefined for the Sparc and M68k architectures
(though pausing calls like outb_p are defined, with the
limitations outlined in Section 8.1.1, earlier in this chapter).
The Linux headers define functions to perform string operations, which can be used by some drivers to get better performance than a C-language loop. The Linux implementation for string I/O maps either to a single machine instruction or to a tight loop, or it is missing altogether, depending on the capabilities of the target processor or platform.
The prototypes for string functions are the following:
void insb(unsigned port, void *addr, unsigned long count);
,
void outsb(unsigned port, void *addr, unsigned long count);
Read or write count
bytes starting at the
memory address addr
. Data is read from or written
to the single port port
.
void insw(unsigned port, void *addr, unsigned long count);
,
void outsw(unsigned port, void *addr, unsigned long count);
void insl(unsigned port, void *addr, unsigned long count);
,
void outsl(unsigned port, void *addr, unsigned long count);
[23] As a matter of fact, sometimes I/O ports are arranged like memory, and you can (for example) bind two 8-bit writes into a single 16-bit operation. This applies, for instance, to PC video boards, but in general you can’t count on this feature.
18.220.137.164