I just solved the greatest mystery of the Universe: how do you access I/O ports in OpenBSD? x)
I'll start from far away.
There's this HUEG, multi-volume book, called "Intel 64 and IA-32 Architectures Software Developer's Manual". It's freely available on Intel's website:
http://www.intel.com/products/processor/manuals/.
You can find everything you'll ever need for programming i386 processors there.
There are several modes in which i386 microprocessor can work: "real" mode, "protected" mode and real mode emulation mode "vm86".
All modern operating systems work in protected mode. MS-DOS used to work in real mode. In real mode - you're basically free to do anything you want, for example, you can erase the whole hard-disk and no one will stop you. Obviously, this is not very secure, that's why protected mode was introduced.
In protected mode, there are 4 (I think) rings of protection. The smaller the number of the ring, the more you are allowed to do. OS's kernel runs in ring 0, drivers run in ring 1, drivers which don't need high privileges run in ring 2 and user applications are run in ring 3, which is the most restrictive ring.
Every running application, has several data structures associated with it in memory. These data structures define what privileges does the application have and, uh... Well, other stuff x)
These data structures are maintained by the operating system, and can't be modified by the application directly, but the application can ask the OS to do this.
So. According to the Intel's Software Developer Manual Volume 1, Chapter 13, when the processor is running in protected mode, there are 2 mechanisms which regulate the access to the I/O ports: the I/O privilege level (IOPL) field in the EFLAGS register and the I/O permission bit map of a task state segment (TSS).
There are two ways to gain access to I/O port(s): either raise the IOPL field to be greater or equal to the Current Privilege Level (CPL) of the current process, either modify the bit map to enable access individual ports.
In OpenBSD, IOPL can be changed with the help of
i386_iopl(2) system call, and the permission bit map can be modified with the help of
i386_set_ioperm(2) system call.
As I understand, the i386_iopl call changes the IOPL field permanently, and doesn't change it back to the initial value after the process ends. So it's better to use i386_set_ioperm, because it modifies the permission bit map only for the current process.
For i386_iopl call to succeed the caller process must be run with root privileges or the system's
securelevel (7) must be set to less than 1 and the machdep.allowaperture sysctl must be set to more than 0.
Here's a little test program, which can read and write an I/O port (checked it by connecting leds to LPT port - it works =)
Usage:
./portio out
./portio in
/**************************/
#include
#include
#include
#include
#include
#include
extern int errno;
/*
* Enable access to ALL I/O ports, by modifying the I/O Privilege Level
* (IOPL) field in the EFLAGS register.
*/
void
enable_all_io_ports(void)
{
if (i386_iopl(3) != 0)
err(errno, "i386_iopl");
}
/*
* Modifies the I/O permission bitmap for the current process, so that the
* process could read/write the I/O port 'port'.
*/
void
enable_io_port(int port)
{
u_long iomap[32];
if (i386_get_ioperm(iomap) != 0)
err(errno, "i386_get_permio");
iomap[((unsigned short) port) / (4 * 8)] &= ~(((u_long) 1) << (((unsigned short) port) % (4 * 8)));
if (i386_set_ioperm(iomap) != 0)
err(errno, "i386_set_permio");
}
void
usage(char *progname)
{
printf("Usage:\n" \
"\t%s out
\n" \
"\t%s in
\n" \
"Example:\n" \
"\t%s out 378 255 -- write decimal number 255 to port 0x378 (LPT data lines)\n" \
"\t%s in 379 -- read from port 0x379 (LPT status lines)\n",
progname, progname, progname, progname);
}
int
main(int argc, char **argv)
{
int port, val;
port = val = 0;
if (argc < 3) {
usage(argv[0]);
return 0;
}
sscanf(argv[2], "%x", &port);
enable_io_port(port);
/* enable_all_io_ports(); */
if (argc > 3 && strcmp(argv[1], "out") == 0) {
sscanf(argv[3], "%d", &val);
outb((unsigned short) port, (unsigned char) val);
} else if (strcmp(argv[1], "in") == 0) {
val = inb((unsigned short) port);
printf("%d\n", val);
} else
usage(argv[0]);
return 0;
}
/**************************/
Compiles like this: cc portio.c -o portio -li386
P.S. One of the reasons why I wanted to learn how to access IO ports under unices, is that they have a
nanosleep(2) system call which might come in very handy when writing a program which will communicate with high-speed electronic devices.