Accessing the Parallel Port

For a kernel driver, the parallel port is accessed by calling on the kernel's parport functions. For a user-space program there are two options: use Tim Waugh's PPDEV generic parallel port driver via ioctl functions, or use direct low level port IO through the inb/outb family of macros provided with the GNU C library. This HOWTO will cover both approaches in detail. In the future most programmers will probably take the PPDEV route as it is safer and probably faster. Unfortunately, for some systems the PPDEV code does not work for 2.2 kernels, and the developers are understandably concentrating their efforts on the newer 2.4 series. Very old kernels will never support PPDEV. There may also be circumstances where the direct IO approach is more efficient. There are equivalents of the inb/outb functions in all operating systems, whereas PPDEV is Linux specific (although there is something similar available for newer versions of FreeBSD), so it may be easier to write cross-platform code using that approach. Since PPDEV is brand-new, virtually all existing drivers use the direct IO approach; so if you're modifying an existing program, or using an older program as a model, you will be using that approach by default.

Opening a port using the PPDEV generic parallel port driver

To open a port using PPDEV, use the standard "open" call on the /dev/parportX device.

#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int parportfd = open(“/dev/parport0”, O_RDWR);
Opening the device provides the file descriptor, but does not automatically claim access to the port for the driver. To get that access, one must use the first of the ioctl commands that call on PPDEV to control the port.
if (result != 0) result = ioctl(parportfd,PPCLAIM);
When the driver is through with the port, this action can be reversed with the PPRELEASE call.
result = ioctl(parportfd,PPRELEASE);
close(parportfd);

Preparing to access a port using direct IO

Before a port may be accessed directly, the driver must claim access to the address space. Normally one uses the iopl() call.

#include <sys/io.h>

int result = iopl(3); /* Returns 0 on success */

This call requires root access and will return -1 if it is not available. The function iopl(3) allows access to the entire address space with the associated risks. It is necessary, however, if one wants to write to the Extended Control Register (ECR) of an ECP capable port. One must do so if one wants to use one of the more advanced transfer modes available to a modern port. If this is unneeded, or if one is willing to make the user select EPP or an older protocol in BIOS instead, one can use the ioperm() call. The latter function allows the driver to select a specific address region. It also requires root privileges.

addr = 0x378;
int result = ioperm(addr,5,1);

This allows access to the address space from 0x378 to 0x37C, covering all address space through the EPP addresses for the most common base address (0x378) for the first parallel port /dev/parport0. Ioperm is restricted to addresses up to 0x3FF, which is why it can't be used when one needs to control an ECP port. Both ioperm() and iopl() are Linux specific.

Once you've acquired address access you can direct the port using the inb/outb family of functions. These are actually macros that must be inlined by compiling with the -O2 or greater optimization flag set. These take the form:

unsigned char data;
data = inb(addr);
outb(data,addr);

short data;
data = inw(addr);
outw(data,addr);

long data;
data = inl(addr);
outl(data,addr);

For the short or long word versions, the low byte is written to the address addr, and the upper byte or bytes are written to addr + 1, addr + 2, etc. A string of bytes, or of short or long words, can be read or written to an address with the insb(), insw(), insl(), outsb(), outsl() functions.

int numbytes = 100;
unsigned char buf[100];
insb(addr,buf,numbytes);
outsb(addr,buf,numbytes);

In addition, the functions outb_p(), outw_p(), outl_p(), inb_p(), inw_p(), inl_p() introduce a small pause to the IO calls. This is useful when the peripheral is thinking more slowly than the computer. If you or another user have problems communicating within the peripheral reliably you should try these. It's sometimes worthwhile to use the paused versions of these functions for sending and receiving control commands to the peripheral, and using the fast versions for the main data transfers.