I found a very interesting ISA-bus device in the trash and I wanted to play with it. No drivers exist for it in any modern operating system. In fact, it isn't clear that drivers ever existed. The manual, which I found online, documents the procedure for using the device in your program. They tell you what bytes to read and write to what I/O addresses and they do a pretty good job of documenting the expected behavior.

To use this card in FreeBSD, I had two options. First, I could write a driver for the card. This is probably the right way, but it is too much hassle for an ISA-bus card that might not even work. It was, after all, in the trash. The second option is to write a userland program that talks to the card directly. I used to do this sort of thing in assembly in DOS. There is and "inb" opcode in the x86 instruction set. It means "read in a byte." Typically it is invoked like this:

inb edx, eax
You put the I/O address in the EDX register. The EAX register will contain the value read from that I/O address. In Linux, there is a little bit of bureaucracy that you have to go through before doing low-level I/O from userspace. After getting permission, you can call the inb() and outb() functions that are declared in some Linux-specific header file. I'm pretty sure that inb() and outb() are just wrappers for the underlying x86 instructions. Maybe they are macros, not even function calls.

In FreeBSD I could not figure out what I was supposed to call to do low-level I/O from userspace. I could easily believe that FreeBSD eliminated the possibility since, after all, it is an extremely hacky and dangerous way to use the computer. Actually it is quite simple and elegant in FreeBSD. Your process just opens /dev/io and then it can use the inb and outb instructions. So here is my low-level I/O in FreeBSD test program:


/* Kurt Rosenfeld 2008 */
/* Read a byte from I/O and print the value. */
/* Tested on FreeBSD 7.0 amd64 with gcc 4.2.1 */
/* Opening /dev/io normally requires root privilege. */

#include
#include
#include
#include

#define ADDR_MAX 0x1000

int main(int argc, char **argv) {
	int fd;
	unsigned int io_addr;
	unsigned char c;
	assert(2 == argc);;
	fd = open("/dev/io", O_RDONLY); assert(-1 != fd);
	io_addr = atoi(argv[1]); assert(io_addr < ADDR_MAX);
	/* The following asm line copies the value of io_addr
	into the EDX register, executes the inb instruction,
	and then copies the value of the EAX register to the memory
	location of the variable c.  Nothing else is clobbered. */
	__asm __volatile("inb %%dx,%0" : "=a" (c) : "d" (io_addr));
	printf("%x\n", c);
	return(0);
}