I/O ports are the means by which drivers communicate with many devices out there—at least part of the time. This section covers the various functions available for making use of I/O ports; we also touch on some portability issues.
Let us start with a quick reminder that I/O ports must be allocated before being used by your driver. As we discussed in Section 2.5.1 in Chapter 2, the functions used to allocate and free ports are:
#include <linux/ioport.h> int check_region(unsigned long start, unsigned long len); struct resource *request_region(unsigned long start, unsigned long len, char *name); void release_region(unsigned long start, unsigned long len);
After a driver has requested the range of I/O ports it needs to use in its activities, it must read and/or write to those ports. To this aim, most hardware differentiates between 8-bit, 16-bit, and 32-bit ports. Usually you can’t mix them like you normally do with system memory access.[32]
A C program, therefore, must call different functions to access
different size ports. As suggested in the previous section, computer
architectures that support only memory-mapped I/O registers fake port
I/O by remapping port addresses to memory addresses, and the kernel
hides the details from the driver in order to ease portability. The
Linux kernel headers (specifically, the architecture-dependent header
<asm/io.h>
) define the following inline
functions to access I/O ports.
Note
From now on, when we use unsigned
without further
type specifications, we are referring to an architecture-dependent
definition whose exact nature is not relevant. The functions are
almost always portable because the compiler automatically casts the
values during assignment—their being unsigned helps prevent
compile-time warnings. No information is lost with such casts as long
as the programmer assigns sensible values to avoid overflow. We’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 (eight bits wide). The
port
argument is defined asunsigned long
for some platforms andunsigned 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 when compiling for the M68k and S390 platforms, which support only byte I/O.
-
unsigned inl(unsigned port);
,void outl(unsigned longword, unsigned port);
These functions access 32-bit ports.
longword
is either declared asunsigned long
orunsigned int
, according to the platform. Like word I/O, “long” I/O is not available on M68k and S390.
Note that no 64-bit port I/O operations are defined. Even on 64-bit architectures, the port address space uses a 32-bit (maximum) data path.
The functions just described are primarily meant to be used by device
drivers, but they can also be used from user space, at least on
PC-class computers.
The GNU C library defines them in <sys/io.h>
.
The following conditions should 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.
The ioperm or iopl system calls 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 or iopl [33] Alternatively, one of its ancestors must have gained port access running as root.
If the host platform has no ioperm and no
iopl system calls, user space can still access
I/O ports by using the /dev/port
device
file. Note, though, that the meaning of the file is very platform
specific, and most likely not useful for anything but the PC.
The sample sources misc-progs/inp.c
and
misc-progs/outp.c
are a minimal tool for reading
and writing ports from the command line, in user space. They expect to be installed under multiple names (i.e., inpb
, inpw
, and inpl
and will manipulate byte, word, or long ports depending on which name was invoked by the user. They use /dev/port
if ioperm is not present.
The programs can be made setuid root, if you want to live dangerously and play with your hardware without acquiring explicit privileges.
In addition to the single-shot in and out operations, some processors implement special instructions to transfer a sequence of bytes, words, or longs to and from a single I/O port or the same size. These are the so-called string instructions, and they perform the task more quickly than a C-language loop can do. The following macros implement the concept of string I/O by either using a single machine instruction or by executing a tight loop if the target processor has no instruction that performs string I/O. The macros are not defined at all when compiling for the M68k and S390 platforms. This should not be a portability problem, since these platforms don’t usually share device drivers with other platforms, because their peripheral buses are different.
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 addressaddr
. Data is read from or written to the single portport
.-
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);
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 overclocked 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 each 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 previously, but their names end in
_p
; they are called inb_p,
outb_p, and so on. The functions are defined for
most supported architectures, although they often expand to the same
code as nonpausing I/O, because there is no need for the extra pause
if the architecture runs with a nonobsolete peripheral bus.
I/O instructions are, by their nature, highly processor dependent. Because they work with the details of how the processor handles moving data in and out, it is very hard to hide the differences between systems. As a consequence, much of the source code related to port I/O is platform dependent.
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 64-KB I/O space), but unsigned long
on other platforms, whose ports are just special
locations in the same address space as memory.
Other platform dependencies arise from basic structural differences in the processors and thus are unavoidable. We won’t go into detail about the differences, because we 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 architectures that are supported by version 2.4 of the kernel:
- IA-32 (x86)
The architecture supports all the functions described in this chapter. Port numbers are of type
unsigned short
.- IA-64 (Itanium)
All functions are supported; ports are
unsigned long
(and memory-mapped). String functions are implemented in C.- Alpha
All the functions are supported, and ports are memory-mapped. The implementation of port I/O is different in different Alpha platforms, according to the chipset they use. String functions are implemented in C and defined in
arch/alpha/lib/io.c
. Ports areunsigned long
.- ARM
Ports are memory-mapped, and all functions are supported; string functions are implemented in C. Ports are of type
unsigned int
.- M68k
Ports are memory-mapped, and only byte functions are supported. No string functions are supported, and the port type is
unsigned char *
.- MIPS , MIPS64
The MIPS port supports all the functions. String operations are implemented with tight assembly loops, because the processor lacks machine-level string I/O. Ports are memory-mapped; they are
unsigned int
in 32-bit processors andunsigned long
in 64-bit ones.- PowerPC
All the functions are supported; ports have type
unsigned char *
.- S390
Similar to the M68k, the header for this platform supports only byte-wide port I/O with no string operations. Ports are
char
pointers and are memory-mapped.- Super-H
Ports are
unsigned int
(memory-mapped), and all the functions are supported.- SPARC , SPARC64
Once again, I/O space is memory-mapped. Versions of the port functions are defined to work with
unsigned long
ports.
The curious reader can extract more information from the
io.h
files, which sometimes define a few
architecture-specific functions in addition to those we describe in
this chapter. Be warned that some of these files are rather difficult
reading, however.
It’s interesting to note that no processor outside the x86 family features a different address space for ports, even though several of the supported families are shipped with ISA and/or PCI slots (and both buses implement different I/O and memory address spaces).
Moreover, some processors (most notably the early Alphas) lack
instructions that move one or two bytes at a
time.[34] Therefore, their peripheral chipsets
simulate 8-bit and 16-bit I/O accesses by mapping them to special
address ranges in the memory address space. Thus, an
inb and an inw instruction
that act on the same port are implemented by two 32-bit memory reads
that operate on different addresses. Fortunately, all of this is
hidden from the device driver writer by the internals of the macros
described in this section, but we feel it’s an interesting feature to
note. If you want to probe further, look for examples in
include/asm-alpha/core_lca.h
.
How I/O operations are performed on each platform is well described in the programmer’s manual for each platform; those manuals are usually available for download as PDF files on the Web.
[32] 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.
[33] Technically, it must have the
CAP_SYS_RAWIO
capability, but that is the same as
running as root on current systems.
[34] Single-byte I/O is not as important as one may imagine, because it is a rare operation. In order to read/write a single byte to any address space, you need to implement a data path connecting the low bits of the register-set data bus to any byte position in the external data bus. These data paths require additional logic gates that get in the way of every data transfer. Dropping byte-wide loads and stores can benefit overall system performance.
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.