Next Previous Contents

## 2. Using I/O ports in C programs

## 2.1 The normal method

Routines for accessing I/O ports are in `/usr/include/asm/io.h` (or `linux/include/asm-i386/io.h` in the kernel source distribution). The routines there are inline macros, so it is enough to `#include <asm/io.h>`; you do not need any additional libraries.

Because of a limitation in gcc (present in all versions I know of, including egcs), you have to compile any source code that uses these routines with optimisation turned on (`gcc -O1` or higher), or alternatively use `#define extern static` before you `#include <asm/io.h>` (remember to `#undef extern`afterwards).

For debugging, you can use `gcc -g -O` (at least with modern versions of gcc), though optimisation can sometimes make the debugger behave a bit strangely. If this bothers you, put the routines that use I/O port access in a separate source file and compile only that with optimisation turned on.

### Permissions

Before you access any ports, you must give your program permission to do so. This is done by calling the `ioperm()` function (declared in `unistd.h`, and defined in the kernel) somewhere near the start of your program (before any I/O port accesses). The syntax is `ioperm(from, num, turn_on)`, where `from` is the first port number to give access to, and `num` the number of consecutive ports to give access to. For example, `ioperm(0x300, 5, 1)` would give access to ports 0x300 through 0x304 (a total of 5 ports). The last argument is a Boolean value specifying whether to give access to the program to the ports (true (1)) or to remove access (false (0)). You can call `ioperm()` multiple times to enable multiple non-consecutive ports. See the `ioperm(2)` manual page for details on the syntax.

The `ioperm()` call requires your program to have root privileges; thus you need to either run it as the root user, or make it setuid root. You can drop the root privileges after you have called `ioperm()` to enable the ports you want to use. You are not required to explicitly drop your port access privileges with `ioperm(..., 0)` at the end of your program; this is done automatically as the process exits.

A `setuid()` to a non-root user does not disable the port access granted by `ioperm()`, but a `fork()` does (the child process does not get access, but the parent retains it).

`ioperm()` can only give access to ports 0x000 through 0x3ff; for higher ports, you need to use `iopl()` (which gives you access to all ports at once). Use the level argument 3 (i.e., `iopl(3)`) to give your program access to all I/O ports (so be careful --- accessing the wrong ports can do all sorts of nasty things to your computer). Again, you need root privileges to call `iopl()`. See the `iopl(2)` manual page for details.

### Accessing the ports

To input a byte (8 bits) from a port, call `inb(port)`, it returns the byte it got. To output a byte, call `outb(value, port)` (please note the order of the parameters). To input a word (16 bits) from ports `x` and `x+1` (one byte from each to form the word, using the assembler instruction `inw`), call `inw(x)`. To output a word to the two ports, use `outw(value, x)`. If you're unsure of which port instructions (byte or word) to use, you probably want `inb()` and `outb()` --- most devices are designed for bytewise port access. Note that all port access instructions take at least about a microsecond to execute.

The `inb_p()`, `outb_p()`, `inw_p()`, and `outw_p()` macros work otherwise identically to the ones above, but they do an additional short (about one microsecond) delay after the port access; you can make the delay about four microseconds with `#define REALLY_SLOW_IO` before you `#include <asm/io.h>`. These macros normally (unless you `#define SLOW_IO_BY_JUMPING`, which is probably less accurate) use a port output to port 0x80 for their delay, so you need to give access to port 0x80 with `ioperm()` first (outputs to port 0x80 should not affect any part of the system). For more versatile methods of delaying, read on.

There are manual pages for `ioperm(2)`, `iopl(2)`, and the above macros in reasonably recent releases of the Linux manual page collection.

## 2.2 An alternate method: `/dev/port`

Another way to access I/O ports is to `open()` `/dev/port` (a character device, major number 1, minor 4) for reading and/or writing (the stdio `f*()` functions have internal buffering, so avoid them). Then `lseek()` to the appropriate byte in the file (file position 0 = port 0x00, file position 1 = port 0x01, and so on), and `read()` or `write()` a byte or word from or to it.

Naturally, for this to work your program needs read/write access to `/dev/port`. This method is probably slower than the normal method above, but does not need compiler optimisation nor `ioperm()`. It doesn't need root access either, if you give a non-root user or group access to `/dev/port` --- but this is a very bad thing to do in terms of system security, since it is possible to hurt the system, perhaps even gain root access, by using `/dev/port` to access hard disks, network cards, etc. directly.

You cannot use `select(2)` or `poll(2)` to read /dev/port, because the hardware does not have a facility for notifying the CPU when a value in an input port changes.

Next Previous Contents