Examples

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);
    }