Presented here are two demonstrations of how to write a simple
printer driver for ppdev. Firstly we will
use the write
function, and after that we
will drive the control and data lines directly.
The first thing to do is to actually open the device.
int drive_printer (const char *name) { int fd; int mode; /* We'll need this later. */ fd = open (name, O_RDWR); if (fd == -1) { perror ("open"); return 1; }
Here name
should be something along the lines
of "/dev/parport0". (If you don't have any
/dev/parport files, you can make them with
mknod; they are character special device nodes
with major 99.)
In order to do anything with the port we need to claim access to it.
if (ioctl (fd, PPCLAIM)) { perror ("PPCLAIM"); close (fd); return 1; }
Our printer driver will copy its input (from
stdin
) to the printer, and it can do that it
one of two ways. The first way is to hand it all off to the
kernel driver, with the knowledge that the protocol that the
printer speaks is IEEE 1284's "compatibility"
mode.
/* Switch to compatibility mode. (In fact we don't need * to do this, since we start off in compatibility mode * anyway, but this demonstrates PPNEGOT.) mode = IEEE1284_MODE_COMPAT; if (ioctl (fd, PPNEGOT, &mode)) { perror ("PPNEGOT"); close (fd); return 1; } for (;;) { char buffer[1000]; char *ptr = buffer; size_t got; got = read (0 /* stdin */, buffer, 1000); if (got < 0) { perror ("read"); close (fd); return 1; } if (got == 0) /* End of input */ break; while (got > 0) { int written = write_printer (fd, ptr, got); if (written < 0) { perror ("write"); close (fd); return 1; } ptr += written; got -= written; } }
The write_printer
function is not pictured
above. This is because the main loop that is shown can be used
for both methods of driving the printer. Here is one
implementation of write_printer
:
ssize_t write_printer (int fd, const void *ptr, size_t count) { return write (fd, ptr, count); }
We hand the data to the kernel-level driver (using
write
) and it handles the printer
protocol.
Now let's do it the hard way! In this particular example there is
no practical reason to do anything other than just call
write
, because we know that the printer talks
an IEEE 1284 protocol. On the other hand, this particular example
does not even need a user-land driver since there is already a
kernel-level one; for the purpose of this discussion, try to
imagine that the printer speaks a protocol that is not already
implemented under Linux.
So, here is the alternative implementation of
write_printer
(for brevity, error checking
has been omitted):
ssize_t write_printer (int fd, const void *ptr, size_t count) { ssize_t wrote = 0; while (wrote < count) { unsigned char status, control, data; unsigned char mask = (PARPORT_STATUS_ERROR | PARPORT_STATUS_BUSY); unsigned char val = (PARPORT_STATUS_ERROR | PARPORT_STATUS_BUSY); struct ppdev_frob_struct frob; struct timespec ts; /* Wait for printer to be ready */ for (;;) { ioctl (fd, PPRSTATUS, &status); if ((status & mask) == val) break; ioctl (fd, PPRELEASE); sleep (1); ioctl (fd, PPCLAIM); } /* Set the data lines */ data = * ((char *) ptr)++; ioctl (fd, PPWDATA, &data); /* Delay for a bit */ ts.tv_sec = 0; ts.tv_nsec = 1000; nanosleep (&ts, NULL); /* Pulse strobe */ frob.mask = PARPORT_CONTROL_STROBE; frob.val = PARPORT_CONTROL_STROBE; ioctl (fd, PPFCONTROL, &frob); nanosleep (&ts, NULL); /* End the pulse */ frob.val = 0; ioctl (fd, PPFCONTROL, &frob); nanosleep (&ts, NULL); wrote++; } return wrote; }
To show a bit more of the ppdev interface, here is a small piece of code that is intended to mimic the printer's side of printer protocol.
for (;;) { int irqc; int busy = nAck | nFault; int acking = nFault; int ready = Busy | nAck | nFault; char ch; /* Set up the control lines when an interrupt happens. */ ioctl (fd, PPWCTLONIRQ, &busy); /* Now we're ready. */ ioctl (fd, PPWCONTROL, &ready); /* Wait for an interrupt. */ { fd_set rfds; FD_ZERO (&rfds); FD_SET (fd, &rfds); if (!select (fd + 1, &rfds, NULL, NULL, NULL)) /* Caught a signal? */ continue; } /* We are now marked as busy. */ /* Fetch the data. */ ioctl (fd, PPRDATA, &ch); /* Clear the interrupt. */ ioctl (fd, PPCLRIRQ, &irqc); if (irqc > 1) fprintf (stderr, "Arghh! Missed %d interrupt%s!\n", irqc - 1, irqc == 2 ? "s" : ""); /* Ack it. */ ioctl (fd, PPWCONTROL, &acking); usleep (2); ioctl (fd, PPWCONTROL, &busy); putchar (ch); }
And here is an example (with no error checking at all) to show how to read data from the port, using ECP mode, with optional negotiation to ECP mode first.
{ int fd, mode; fd = open ("/dev/parport0", O_RDONLY | O_NOCTTY); ioctl (fd, PPCLAIM); mode = IEEE1284_MODE_ECP; if (negotiate_first) { ioctl (fd, PPNEGOT, &mode); /* no need for PPSETMODE */ } else { ioctl (fd, PPSETMODE, &mode); } /* Now do whatever we want with fd */ close (0); dup2 (fd, 0); if (!fork()) { /* child */ execlp ("cat", "cat", NULL); exit (1); } else { /* parent */ wait (NULL); } /* Okay, finished */ ioctl (fd, PPRELEASE); close (fd); }