Chapter 6. The printer driver

The printer driver, lp is a character special device driver and a parport client. As a character special device driver it registers a struct file_operations using register_chrdev, with pointers filled in for write, ioctl, open and release. As a client of parport, it registers a struct parport_driver using parport_register_driver, so that parport knows to call lp_attach when a new parallel port is discovered (and lp_detach when it goes away).

The parallel port console functionality is also implemented in drivers/char/lp.c, but that won't be covered here (it's quite simple though).

The initialisation of the driver is quite easy to understand (see lp_init). The lp_table is an array of structures that contain information about a specific device (the struct pardevice associated with it, for example). That array is initialised to sensible values first of all.

Next, the printer driver calls register_chrdev passing it a pointer to lp_fops, which contains function pointers for the printer driver's implementation of open, write, and so on. This part is the same as for any character special device driver.

After successfully registering itself as a character special device driver, the printer driver registers itself as a parport client using parport_register_driver. It passes a pointer to this structure:

   
static struct parport_driver lp_driver = {
        "lp",
        lp_attach,
        lp_detach,
        NULL
};
   

The lp_detach function is not very interesting (it does nothing); the interesting bit is lp_attach. What goes on here depends on whether the user supplied any parameters. The possibilities are: no parameters supplied, in which case the printer driver uses every port that is detected; the user supplied the parameter "auto", in which case only ports on which the device ID string indicates a printer is present are used; or the user supplied a list of parallel port numbers to try, in which case only those are used.

For each port that the printer driver wants to use (see lp_register), it calls parport_register_device and stores the resulting struct pardevice pointer in the lp_table. If the user told it to do so, it then resets the printer.

The other interesting piece of the printer driver, from the point of view of parport, is lp_write. In this function, the user space process has data that it wants printed, and the printer driver hands it off to the parport code to deal with.

The parport functions it uses that we have not seen yet are parport_negotiate, parport_set_timeout, and parport_write. These functions are part of the IEEE 1284 implementation.

The way the IEEE 1284 protocol works is that the host tells the peripheral what transfer mode it would like to use, and the peripheral either accepts that mode or rejects it; if the mode is rejected, the host can try again with a different mode. This is the negotation phase. Once the peripheral has accepted a particular transfer mode, data transfer can begin that mode.

The particular transfer mode that the printer driver wants to use is named in IEEE 1284 as "compatibility" mode, and the function to request a particular mode is called parport_negotiate.

#include <parport.h>
   

int parport_negotiate(struct parport *port, int mode);

The modes parameter is a symbolic constant representing an IEEE 1284 mode; in this instance, it is IEEE1284_MODE_COMPAT. (Compatibility mode is slightly different to the other modes---rather than being specifically requested, it is the default until another mode is selected.)

Back to lp_write then. First, access to the parallel port is secured with parport_claim_or_block. At this point the driver might sleep, waiting for another driver (perhaps a Zip drive driver, for instance) to let the port go. Next, it goes to compatibility mode using parport_negotiate.

The main work is done in the write-loop. In particular, the line that hands the data over to parport reads:

        written = parport_write (port, kbuf, copy_size);

The parport_write function writes data to the peripheral using the currently selected transfer mode (compatibility mode, in this case). It returns the number of bytes successfully written:

#include <parport.h>
   

ssize_t parport_write(struct parport *port, const void *buf, size_t len);

ssize_t parport_read(struct parport *port, void *buf, size_t len);

(parport_read does what it sounds like, but only works for modes in which reverse transfer is possible. Of course, parport_write only works in modes in which forward transfer is possible, too.)

The buf pointer should be to kernel space memory, and obviously the len parameter specifies the amount of data to transfer.

In fact what parport_write does is call the appropriate block transfer function from the struct parport_operations:

   
struct parport_operations {
        [...]

        /* Block read/write */
        size_t (*epp_write_data) (struct parport *port,
                                  const void *buf,
                                  size_t len, int flags);
        size_t (*epp_read_data) (struct parport *port,
                                 void *buf, size_t len,
                                 int flags);
        size_t (*epp_write_addr) (struct parport *port,
                                  const void *buf,
                                  size_t len, int flags);
        size_t (*epp_read_addr) (struct parport *port,
                                 void *buf, size_t len,
                                 int flags);

        size_t (*ecp_write_data) (struct parport *port,
                                  const void *buf,
                                  size_t len, int flags);
        size_t (*ecp_read_data) (struct parport *port,
                                 void *buf, size_t len,
                                 int flags);
        size_t (*ecp_write_addr) (struct parport *port,
                                  const void *buf,
                                  size_t len, int flags);

        size_t (*compat_write_data) (struct parport *port,
                                     const void *buf,
                                     size_t len, int flags);
        size_t (*nibble_read_data) (struct parport *port,
                                    void *buf, size_t len,
                                    int flags);
        size_t (*byte_read_data) (struct parport *port,
                                  void *buf, size_t len,
                                  int flags);
};
   

The transfer code in parport will tolerate a data transfer stall only for so long, and this timeout can be specified with parport_set_timeout, which returns the previous timeout:

#include <parport.h>
   

long parport_set_timeout(struct pardevice *dev, long inactivity);

This timeout is specific to the device, and is restored on parport_claim.

The next function to look at is the one that allows processes to read from /dev/lp0: lp_read. It's short, like lp_write.

The semantics of reading from a line printer device are as follows:

  • Switch to reverse nibble mode.

  • Try to read data from the peripheral using reverse nibble mode, until either the user-provided buffer is full or the peripheral indicates that there is no more data.

  • If there was data, stop, and return it.

  • Otherwise, we tried to read data and there was none. If the user opened the device node with the O_NONBLOCK flag, return. Otherwise wait until an interrupt occurs on the port (or a timeout elapses).