This chapter is the second case study in this book. In this chapter, we’ll go through lpt(4)
, the parallel port printer driver. lpt(4)
, by default, is configured to be interrupt-driven, which gives us an opportunity to go through a nontrivial interrupt handler. Aside from this, I chose to profile lpt(4)
because it uses almost every topic described in the previous chapters. It’s also relatively short.
To improve readability, some of the variables and functions presented in this chapter have been renamed and restructured from their counterparts in the FreeBSD source.
Example 9-1 provides a terse, source-level overview of lpt(4)
.
Example 9-1. lpt.c
#include <sys/param.h> #include <sys/module.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/conf.h> #include <sys/uio.h> #include <sys/bus.h> #include <sys/malloc.h> #include <sys/syslog.h> #include <machine/bus.h> #include <sys/rman.h> #include <machine/resource.h> #include <dev/ppbus/ppbconf.h> #include "ppbus_if.h" #include <dev/ppbus/ppbio.h> #include <dev/ppbus/ppb_1284.h> #include <dev/ppbus/lpt.h> #include <dev/ppbus/lptio.h> #define LPT_NAME "lpt" /* official driver name. */ #define LPT_INIT_READY 4 /* wait up to 4 seconds. */ #define LPT_PRI (PZERO + 8) /* priority. */ #define BUF_SIZE 1024 /* sc_buf size. */ #define BUF_STAT_SIZE 32 /* sc_buf_stat size. */ struct lpt_data { short sc_state; char sc_primed; struct callout sc_callout; u_char sc_ticks; int sc_irq_rid; struct resource *sc_irq_resource; void *sc_irq_cookie; u_short sc_irq_status; void *sc_buf; void *sc_buf_stat; char *sc_cp; device_t sc_dev; struct cdev *sc_cdev; struct cdev *sc_cdev_bypass; char sc_flags; u_char sc_control; short sc_transfer_count; }; /* bits for sc_state. */ #define LP_OPEN (1 << 0) /* device is open. */ #define LP_ERROR (1 << 2) /* error received from printer. */ #define LP_BUSY (1 << 3) /* printer is busy writing. */ #define LP_TIMEOUT (1 << 5) /* timeout enabled. */ #define LP_INIT (1 << 6) /* initializing in lpt_open. */ #define LP_INTERRUPTED (1 << 7) /* write call was interrupted. */ #define LP_HAVEBUS (1 << 8) /* driver owns the bus. */ /* bits for sc_ticks. */ #define LP_TOUT_INIT 10 /* initial timeout: 1/10 sec. */ #define LP_TOUT_MAX 1 /* max timeout: 1/1 sec. */ /* bits for sc_irq_status. */ #define LP_HAS_IRQ 0x01 /* we have an IRQ available. */ #define LP_USE_IRQ 0x02 /* our IRQ is in use. */ #define LP_ENABLE_IRQ 0x04 /* enable our IRQ on open. */ #define LP_ENABLE_EXT 0x10 /* enable extended mode. */ /* bits for sc_flags. */ #define LP_NO_PRIME 0x10 /* don't prime the printer. */ #define LP_PRIME_OPEN 0x20 /* prime on every open. */ #define LP_AUTO_LF 0x40 /* automatic line feed. */ #define LP_BYPASS 0x80 /* bypass printer ready checks. */ /* masks to interrogate printer status. */ #define LP_READY_MASK (LPS_NERR | LPS_SEL | LPS_OUT | LPS_NBSY) #define LP_READY (LPS_NERR | LPS_SEL | LPS_NBSY) /* used in polling code. */ #define LPS_INVERT (LPS_NERR | LPS_SEL | LPS_NACK | LPS_NBSY) #define LPS_MASK (LPS_NERR | LPS_SEL | LPS_OUT | LPS_NACK | LPS_NBSY) #define NOT_READY(bus) ((ppb_rstr(bus) ^ LPS_INVERT) & LPS_MASK) #define MAX_SPIN 20 /* wait up to 20 usec. */ #define MAX_SLEEP (hz * 5) /* timeout while waiting. */ static d_open_t lpt_open; static d_close_t lpt_close; static d_read_t lpt_read; static d_write_t lpt_write; static d_ioctl_t lpt_ioctl; static struct cdevsw lpt_cdevsw = { .d_version = D_VERSION, .d_open = lpt_open, .d_close = lpt_close, .d_read = lpt_read, .d_write = lpt_write, .d_ioctl = lpt_ioctl, .d_name = LPT_NAME }; static devclass_t lpt_devclass; static void lpt_identify(driver_t *driver, device_t parent) { ... } static int lpt_request_ppbus(device_t dev, int how) { ... } static int lpt_release_ppbus(device_t dev) { ... } static int lpt_port_test(device_t ppbus, u_char data, u_char mask) { ... } static int lpt_detect(device_t dev) { ... } static int lpt_probe(device_t dev) { ... } static void lpt_intr(void *arg) { ... } static int lpt_attach(device_t dev) { ... } static int lpt_detach(device_t dev) { ... } static void lpt_timeout(void *arg) { ... } static int lpt_open(struct cdev *dev, int oflags, int devtype, struct thread *td) { ... } static int lpt_close(struct cdev *dev, int fflag, int devtype, struct thread *td) { ... } static int lpt_read(struct cdev *dev, struct uio *uio, int ioflag) { ... } static int lpt_push_bytes(struct lpt_data *sc) { ... } static int lpt_write(struct cdev *dev, struct uio *uio, int ioflag) { ... } static int lpt_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { ... } static device_method_t lpt_methods[] = { DEVMETHOD(device_identify, lpt_identify), DEVMETHOD(device_probe, lpt_probe), DEVMETHOD(device_attach, lpt_attach), DEVMETHOD(device_detach, lpt_detach), { 0, 0 } }; static driver_t lpt_driver = { LPT_NAME, lpt_methods, sizeof(struct lpt_data) }; DRIVER_MODULE(lpt, ppbus, lpt_driver, lpt_devclass, 0, 0); MODULE_DEPEND(lpt, ppbus, 1, 1, 1);
Example 9-1 is provided as a convenience; as I go through the code for lpt(4) you can refer to it to see how lpt(4)
’s functions and structures are laid out.
To make things easier to follow, I’ll analyze the functions in lpt(4)
in the approximate order they would execute in (rather than in the order they appear). To that end, I’ll begin with the lpt_identify
function.
The lpt_identify
function is the device_identify
implementation for lpt(4)
. Logically, this function is required because the parallel port cannot identify its children unaided.
Here is the function definition for lpt_identify
:
static void lpt_identify(driver_t *driver, device_t parent) { device_t dev; dev = device_find_child(parent, LPT_NAME, −1); if (!dev) BUS_ADD_CHILD(parent, 0, LPT_NAME, −1); }
This function first determines whether the parallel port has (ever) identified a child device named LPT_NAME
. If it has not, then lpt_identify
adds LPT_NAME
to the parallel port’s list of identified children.
The lpt_probe
function is the device_probe
implementation for lpt(4)
. Here is its function definition:
static int lpt_probe(device_t dev) { if (!lpt_detect(dev)) return (ENXIO); device_set_desc(dev, "Printer"); return (BUS_PROBE_SPECIFIC); }
This function simply calls lpt_detect
to detect (that is, probe for) the presence of a printer.
As mentioned in the previous section, lpt_detect
detects the presence of a printer. It works by writing to the parallel port’s data register. If a printer is present, it can read back the value just written.
Here is the function definition for lpt_detect
:
static int lpt_detect(device_t dev) { device_t ppbus = device_get_parent(dev); static u_char test[18] = { 0x55, /* alternating zeros. */ 0xaa, /* alternating ones. */ 0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f, /* walking zero. */ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 /* walking one. */ }; int i, error, success = 1; /* assume success. */ ppb_lock(ppbus); error = lpt_request_ppbus(dev, PPB_DONTWAIT); if (error) { ppb_unlock(ppbus); device_printf(dev, "cannot allocate ppbus (%d)! ", error); return (0); } for (i = 0; i < 18; i++) if (!lpt_port_test(ppbus, test[i], 0xff)) { success = 0; break; } ppb_wdtr(ppbus, 0); ppb_wctr(ppbus, 0); lpt_release_ppbus(dev); ppb_unlock(ppbus); return (success); }
This function first acquires the parallel port mutex. Next, lpt(4)
is assigned ownership of the parallel port. Then lpt_port_test
is called to write to and read from the parallel port’s data register. The values written to this 8-bit register are housed in test[]
and are designed to toggle all 8 bits.
Once this is done, the parallel port’s data and control registers are cleared, ownership of the parallel port is relinquished, and the parallel port mutex is released.
The lpt_port_test
function is called by lpt_detect
to determine whether a printer is present. Here is its function definition:
static int lpt_port_test(device_t ppbus, u_char data, u_char mask) { int temp, timeout = 10000; data &= mask; ppb_wdtr(ppbus, data); do { DELAY(10); temp = ppb_rdtr(ppbus) & mask; } while (temp != data && --timeout); return (temp == data); }
This function takes an 8-bit value and writes it to the parallel port’s data register. Then it reads from that register and returns whether the value written and read match.
The lpt_attach
function is the device_attach
implementation for lpt(4)
. Here is its function definition:
static int lpt_attach(device_t dev) { device_t ppbus = device_get_parent(dev); struct lpt_data *sc = device_get_softc(dev); int error, unit = device_get_unit(dev); sc->sc_primed = 0; ppb_init_callout(ppbus, &sc->sc_callout, 0); ppb_lock(ppbus); error = lpt_request_ppbus(dev, PPB_DONTWAIT); if (error) { ppb_unlock(ppbus); device_printf(dev, "cannot allocate ppbus (%d)! ", error); return (0); } ppb_wctr(ppbus, LPC_NINIT); lpt_release_ppbus(dev); ppb_unlock(ppbus); /* Declare our interrupt handler. */ sc->sc_irq_rid = 0; sc->sc_irq_resource = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->sc_irq_rid, RF_ACTIVE | RF_SHAREABLE); /* Register our interrupt handler. */ if (sc->sc_irq_resource) { error = bus_setup_intr(dev, sc->sc_irq_resource, INTR_TYPE_TTY | INTR_MPSAFE, NULL, lpt_intr, sc, &sc->sc_irq_cookie); if (error) { bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, sc->sc_irq_resource); device_printf(dev, "unable to register interrupt handler "); return (error); } sc->sc_irq_status = LP_HAS_IRQ | LP_USE_IRQ | LP_ENABLE_IRQ; device_printf(dev, "interrupt-driven port "); } else { sc->sc_irq_status = 0; device_printf(dev, "polled port "); } sc->sc_buf = malloc(BUF_SIZE, M_DEVBUF, M_WAITOK); sc->sc_buf_stat = malloc(BUF_STAT_SIZE, M_DEVBUF, M_WAITOK); sc->sc_dev = dev; sc->sc_cdev = make_dev(&lpt_cdevsw, unit, UID_ROOT, GID_WHEEL, 0600, LPT_NAME "%d", unit); sc->sc_cdev->si_drv1 = sc; sc->sc_cdev->si_drv2 = 0; sc->sc_cdev_bypass = make_dev(&lpt_cdevsw, unit, UID_ROOT, GID_WHEEL, 0600, LPT_NAME "%d.ctl", unit); sc->sc_cdev_bypass->si_drv1 = sc; sc->sc_cdev_bypass->si_drv2 = (void *)LP_BYPASS; return (0); }
This function can be split into five parts. The first sets sc->sc_primed
to 0
to indicate that the printer needs to be primed. It also initializes lpt(4)
’s callout
structure. The second part essentially changes the electrical signal at pin 16, dubbed nINIT, from high to low causing the printer to initiate an internal reset.
The third part registers the function lpt_intr
as the interrupt handler. If successful, the variable sc->sc_irq_status
is assigned LP_HAS_IRQ
, LP_USE_IRQ
, and LP_ENABLE_IRQ
to indicate that the printer is interrupt-driven. The fourth part allocates memory for two buffers: sc->sc_buf
(which will maintain the data to be printed) and sc->sc_buf_stat
(which will maintain the printer’s status). Finally, the fifth part creates lpt(4)
’s device nodes: lpt%d
and lpt%d.ctl
, where %d
is the unit number. Note that lpt%d.ctl
contains the LP_BYPASS
flag, while lpt%d
does not. In the d_foo
functions, LP_BYPASS
is used to tell lpt%d.ctl
from lpt%d
. As you’ll see, the lpt%d
device node represents the printer, while lpt%d.ctl
is used solely to change the printer’s mode of operation (via lpt(4)
’s d_ioctl
routine).
The lpt_detach
function is the device_detach
implementation for lpt(4)
. Here is its function definition:
static int lpt_detach(device_t dev) { device_t ppbus = device_get_parent(dev); struct lpt_data *sc = device_get_softc(dev); destroy_dev(sc->sc_cdev_bypass); destroy_dev(sc->sc_cdev); ppb_lock(ppbus); lpt_release_ppbus(dev); ppb_unlock(ppbus); callout_drain(&sc->sc_callout); if (sc->sc_irq_resource) { bus_teardown_intr(dev, sc->sc_irq_resource, sc->sc_irq_cookie); bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, sc->sc_irq_resource); } free(sc->sc_buf_stat, M_DEVBUF); free(sc->sc_buf, M_DEVBUF); return (0); }
This function begins by destroying lpt(4)
’s device nodes. Once this is done, it relinquishes ownership of the parallel port, drains lpt(4)
’s callout function, tears down lpt(4)
’s interrupt handler, releases lpt(4)
’s IRQ, and frees the allocated memory.
The lpt_open
function is defined in lpt_cdevsw
(that is, lpt(4)
’s character device switch table) as the d_open
operation. Recall that d_open
operations prepare the device for I/O.
Here is the function definition for lpt_open
:
static int lpt_open(struct cdev *dev, int oflags, int devtype, struct thread *td) { struct lpt_data *sc = dev->si_drv1; device_t lpt_dev = sc->sc_dev; device_t ppbus = device_get_parent(lpt_dev); int try, error; if (!sc) return (ENXIO); ppb_lock(ppbus); if (sc->sc_state) { ppb_unlock(ppbus); return (EBUSY); } else sc->sc_state |= LP_INIT; sc->sc_flags = (uintptr_t)dev->si_drv2; if (sc->sc_flags & LP_BYPASS) { sc->sc_state = LP_OPEN; ppb_unlock(ppbus); return (0); } error = lpt_request_ppbus(lpt_dev, PPB_WAIT | PPB_INTR); if (error) { sc->sc_state = 0; ppb_unlock(ppbus); return (error); } /* Use our IRQ? */ if (sc->sc_irq_status & LP_ENABLE_IRQ) sc->sc_irq_status |= LP_USE_IRQ; else sc->sc_irq_status &= ˜LP_USE_IRQ; /* Reset printer. */ if ((sc->sc_flags & LP_NO_PRIME) == 0) if ((sc->sc_flags & LP_PRIME_OPEN) || sc->sc_primed == 0) { ppb_wctr(ppbus, 0); sc->sc_primed++; DELAY(500); } ppb_wctr(ppbus, LPC_SEL | LPC_NINIT); /* Wait until ready--printer should be running diagnostics. */ try = 0; do { /* Give up? */ if (try++ >= (LPT_INIT_READY * 4)) { lpt_release_ppbus(lpt_dev); sc->sc_state = 0; ppb_unlock(ppbus); return (EBUSY); } /* Wait 1/4 second. Give up if we get a signal. */ if (ppb_sleep(ppbus, lpt_dev, LPT_PRI | PCATCH, "lpt_open", hz / 4) != EWOULDBLOCK) { lpt_release_ppbus(lpt_dev); sc->sc_state = 0; ppb_unlock(ppbus); return (EBUSY); } } while ((ppb_rstr(ppbus) & LP_READY_MASK) != LP_READY); sc->sc_control = LPC_SEL | LPC_NINIT; if (sc->sc_flags & LP_AUTO_LF) sc->sc_control |= LPC_AUTOL; if (sc->sc_irq_status & LP_USE_IRQ) sc->sc_control |= LPC_ENA; ppb_wctr(ppbus, sc->sc_control); sc->sc_state &= ˜LP_INIT; sc->sc_state |= LP_OPEN; sc->sc_transfer_count = 0; if (sc->sc_irq_status & LP_USE_IRQ) { sc->sc_state |= LP_TIMEOUT; sc->sc_ticks = hz / LP_TOUT_INIT; callout_reset(&sc->sc_callout, sc->sc_ticks, lpt_timeout, sc); } lpt_release_ppbus(lpt_dev); ppb_unlock(ppbus); return (0); }
This function can be split into six parts. The first checks the value of sc->sc_state
. If it does not equal 0
, which implies that another process has opened the printer, the error code EBUSY
is returned; otherwise, sc->sc_state
is assigned LP_INIT
. The second part checks the value of dev->si_drv2
.
If it contains the LP_BYPASS
flag, which indicates that the device node is lpt%d.ctl, sc->sc_state
is set to LP_OPEN
and lpt_open
exits. Recall that lpt%d.ctl
is used solely to change the printer’s mode of operation, hence the minute amount of preparatory work. The third part primes the printer and then selects and resets the printer (a printer prepares to receive data when it’s selected, which occurs when the electrical signal at pin 17, dubbed nSELIN, changes from high to low). The fourth part waits for the printer to finish its internal reset. The fifth part selects and resets the printer, enables automatic line feed if requested,[8] and enables interrupts if the printer is interrupt-driven. The fifth part also assigns LP_OPEN
to sc->sc_state
and zeroes the variable sc->sc_transfer_count
.
Automatic line feed is enabled when the electrical signal at pin 14, dubbed nAUTOF, changes from high to low. As you would expect, this causes the printer to automatically insert a line feed after each line.
Finally, the sixth part causes lpt_timeout
to execute one time after sc->sc_ticks
/ hz
seconds. The lpt_timeout
function is used alongside the interrupt handler lpt_intr
. I’ll discuss these functions shortly.
The lpt_read
function retrieves the printer’s status. Users can get the printer’s status by applying the cat(1)
command to the device node lpt%d
.
Here is the function definition for lpt_read
:
static int lpt_read(struct cdev *dev, struct uio *uio, int ioflag) { struct lpt_data *sc = dev->si_drv1; device_t lpt_dev = sc->sc_dev; device_t ppbus = device_get_parent(lpt_dev); int num, error = 0; if (sc->sc_flags & LP_BYPASS) return (EPERM); ppb_lock(ppbus); error = ppb_1284_negociate(ppbus, PPB_NIBBLE, 0); if (error) { ppb_unlock(ppbus); return (error); } num = 0; while (uio->uio_resid) { error = ppb_1284_read(ppbus, PPB_NIBBLE, sc->sc_buf_stat, min(BUF_STAT_SIZE, uio->uio_resid), &num); if (error) goto end_read; if (!num) goto end_read; ppb_unlock(ppbus); error = uiomove(sc->sc_buf_stat, num, uio); ppb_lock(ppbus); if (error) goto end_read; } end_read: ppb_1284_terminate(ppbus); ppb_unlock(ppbus); return (error); }
This function first checks the value of sc->sc_flags
. If it contains the LP_BYPASS
flag, which indicates that the device node is lpt%d.ctl
, the error code EPERM
(which stands for error: operation not permitted) is returned. Next, the function ppb_1284_negociate
is called to put the parallel port interface into nibble mode.
Nibble mode is the most common way to retrieve data from a printer. Normally, pins 10, 11, 12, 13, and 15 are used by the printer as external status indicators; however, in nibble mode these pins are used to send data to the host (4 bits at a time).
The remainder of this function transfers data from the printer to user space. The data in this case is the printer’s status. Here, ppb_1284_read
transfers data from the printer to kernel space. The number of bytes transferred is saved in num
. If num
equals 0
, lpt_read
exits. The uiomove
function then moves the data from kernel space to user space.
The lpt_write
function acquires data from user space and stores it in sc->sc_buf
. This data is then sent to the printer to be printed.
Here is the function definition for lpt_write
:
static int lpt_write(struct cdev *dev, struct uio *uio, int ioflag) { struct lpt_data *sc = dev->si_drv1; device_t lpt_dev = sc->sc_dev; device_t ppbus = device_get_parent(lpt_dev); register unsigned num; int error; if (sc->sc_flags & LP_BYPASS) return (EPERM); ppb_lock(ppbus); error = lpt_request_ppbus(lpt_dev, PPB_WAIT | PPB_INTR); if (error) { ppb_unlock(ppbus); return (error); } sc->sc_state &= ˜LP_INTERRUPTED; while ((num = min(BUF_SIZE, uio->uio_resid))) { sc->sc_cp = sc->sc_buf; ppb_unlock(ppbus); error = uiomove(sc->sc_cp, num, uio); ppb_lock(ppbus); if (error) break; sc->sc_transfer_count = num; if (sc->sc_irq_status & LP_ENABLE_EXT) { error = ppb_write(ppbus, sc->sc_cp, sc->sc_transfer_count, 0); switch (error) { case 0: sc->sc_transfer_count = 0; break; case EINTR: sc->sc_state |= LP_INTERRUPTED; ppb_unlock(ppbus); return (error); case EINVAL: log(LOG_NOTICE, "%s: extended mode not available ", device_get_nameunit(lpt_dev)); break; default: ppb_unlock(ppbus); return (error); } } else while ((sc->sc_transfer_count > 0) && (sc->sc_irq_status & LP_USE_IRQ)) { if (!(sc->sc_state & LP_BUSY)) lpt_intr(sc); if (sc->sc_state & LP_BUSY) { error = ppb_sleep(ppbus, lpt_dev, LPT_PRI | PCATCH, "lpt_write", 0); if (error) { sc->sc_state |= LP_INTERRUPTED; ppb_unlock(ppbus); return (error); } } } if (!(sc->sc_irq_status & LP_USE_IRQ) && (sc->sc_transfer_count)) { error = lpt_push_bytes(sc); if (error) { ppb_unlock(ppbus); return (error); } } } lpt_release_ppbus(lpt_dev); ppb_unlock(ppbus); return (error); }
Like lpt_read
, this function starts by checking the value of sc->sc_flags
. If it contains the LP_BYPASS
flag, the error code EPERM
is returned. Next, the LP_INTERRUPTED
flag is removed from sc->sc_state
(as you’ll see, LP_INTERRUPTED
is added to sc->sc_state
whenever a write operation is interrupted). The following while
loop contains the bulk of lpt_write
. Note that its expression determines the amount of data to copy from user space to kernel space. This amount is saved in sc->sc_transfer_count
, which is decremented each time a byte is sent to the printer.
Now, there are three ways to transfer data from kernel space to the printer. First, if extended mode is enabled, lpt_write
can write directly to the printer.
Extended mode refers to either Enhanced Parallel Port (EPP) or Extended Capabilities Port (ECP) mode. EPP and ECP modes are designed to transmit data faster and with less CPU overhead than normal parallel port communications. Most parallel ports support one or both of these modes.
Second, if the printer is interrupt-driven and the LP_BUSY
flag is cleared in sc->sc_state
, lpt_write
can call lpt_intr
to transfer data to the printer. Looking at the function definition for lpt_intr
in the following section, you’ll see that LP_BUSY
is set during lpt_intr
’s execution, and that LP_BUSY
is not cleared until sc->sc_transfer_count
is 0
. This prevents lpt_write
from issuing another interrupt-driven transfer until the current one completes, which is why lpt_write
sleeps.
Finally, if the first and second options are unavailable, lpt_write
can issue a polled transfer by calling lpt_push_bytes
, which is described in lpt_push_bytes Function in lpt_close Function.
The lpt_intr
function is lpt(4)
’s interrupt handler. This function transfers 1 byte from sc->sc_buf
to the printer and then it exits. When the printer is ready for another byte, it will send an interrupt. Note that in lpt_intr
, sc->sc_buf
is accessed via sc->sc_cp
.
Here is the function definition for lpt_intr
:
static void lpt_intr(void *arg) { struct lpt_data *sc = arg; device_t lpt_dev = sc->sc_dev; device_t ppbus = device_get_parent(lpt_dev); int i, status = 0; for (i = 0; i < 100 && ((status = ppb_rstr(ppbus)) & LP_READY_MASK) != LP_READY; i++) ; /* nothing. */ if ((status & LP_READY_MASK) == LP_READY) { sc->sc_state = (sc->sc_state | LP_BUSY) & ˜LP_ERROR; sc->sc_ticks = hz / LP_TOUT_INIT; if (sc->sc_transfer_count) { ppb_wdtr(ppbus, *sc->sc_cp++); ppb_wctr(ppbus, sc->sc_control | LPC_STB); ppb_wctr(ppbus, sc->sc_control); if (--(sc->sc_transfer_count) > 0) return; } sc->sc_state &= ˜LP_BUSY; if (!(sc->sc_state & LP_INTERRUPTED)) wakeup(lpt_dev); return; } else { if (((status & (LPS_NERR | LPS_OUT)) != LPS_NERR) && (sc->sc_state & LP_OPEN)) sc->sc_state |= LP_ERROR; } }
This function first checks ad nauseam that the printer is online and ready for output. If it is, the LP_BUSY
flag is added to sc->sc_state
and the LP_ERROR
flag, which denotes a printer error, is removed. Next, sc->sc_ticks
is reset. Then 1 byte from sc->sc_buf
is written to the parallel port’s data register and subsequently sent to the printer (data on the parallel port interface is sent to the printer when the electrical signal at pin 1, dubbed nSTROBE, changes from high to low). If there is more data to send (that is, sc->sc_transfer_count
is greater than 0
), lpt_intr
exits, because it is protocol to wait for an interrupt before sending another byte. If there is no more data to send, LP_BUSY
is cleared from sc->sc_state
and lpt_write
is woken up.
The lpt_timeout
function is the callout function for lpt(4)
. It is designed to deal with missed or unhandled interrupts. Here is its function definition:
static void lpt_timeout(void *arg) { struct lpt_data *sc = arg; device_t lpt_dev = sc->sc_dev; if (sc->sc_state & LP_OPEN) { sc->sc_ticks++; if (sc->sc_ticks > hz / LP_TOUT_MAX) sc->sc_ticks = hz / LP_TOUT_MAX; callout_reset(&sc->sc_callout, sc->sc_ticks, lpt_timeout, sc); } else sc->sc_state &= ˜LP_TIMEOUT; if (sc->sc_state & LP_ERROR) sc->sc_state &= ˜LP_ERROR; if (sc->sc_transfer_count) lpt_intr(sc); else { sc->sc_state &= ˜LP_BUSY; wakeup(lpt_dev); } }
This function first checks whether lpt%d
is open. If so, lpt_timeout
reschedules itself to execute. Next, LP_ERROR
is removed from sc->sc_state
. Now if lpt(4)
has missed an interrupt, lpt_intr
is called to restart transferring data to the printer.
Note that without the if
block at , lpt(4)
would hang waiting for an interrupt that’s been sent and lost.
The lpt_push_bytes
function uses polling to transfer data to the printer. This function is called (by lpt_write
) only if extended mode is disabled and the printer is not interrupt-driven.
Here is the function definition for lpt_push_bytes
:
static int lpt_push_bytes(struct lpt_data *sc) { device_t lpt_dev = sc->sc_dev; device_t ppbus = device_get_parent(lpt_dev); int error, spin, tick; char ch; while (sc->sc_transfer_count > 0) { ch = *sc->sc_cp; sc->sc_cp++; sc->sc_transfer_count--; for (spin = 0; NOT_READY(ppbus) && spin < MAX_SPIN; spin++) DELAY(1); if (spin >= MAX_SPIN) { tick = 0; while (NOT_READY(ppbus)) { tick = tick + tick + 1; if (tick > MAX_SLEEP) tick = MAX_SLEEP; error = ppb_sleep(ppbus, lpt_dev, LPT_PRI, "lpt_poll", tick); if (error != EWOULDBLOCK) return (error); } } ppb_wdtr(ppbus, ch); ppb_wctr(ppbus, sc->sc_control | LPC_STB); ppb_wctr(ppbus, sc->sc_control); } return (0); }
This function first verifies that there is data to transfer. Then it polls the printer to see if it is online and ready for output. If the printer is not ready, lpt_push_bytes
sleeps for a short period of time and then repolls the printer when it wakes up. This cycle of sleeping and polling is repeated until the printer is ready. If the printer is ready, 1 byte from sc->sc_buf
is written to the parallel port’s data register and then sent to the printer. This entire process is repeated until all of the data in sc->sc_buf
is transferred.
The lpt_close
function is defined in lpt_cdevsw
as the d_close
operation. Here is its function definition:
static int lpt_close(struct cdev *dev, int fflag, int devtype, struct thread *td) { struct lpt_data *sc = dev->si_drv1; device_t lpt_dev = sc->sc_dev; device_t ppbus = device_get_parent(lpt_dev); int error; ppb_lock(ppbus); if (sc->sc_flags & LP_BYPASS) goto end_close; error = lpt_request_ppbus(lpt_dev, PPB_WAIT | PPB_INTR); if (error) { ppb_unlock(ppbus); return (error); } if (!(sc->sc_state & LP_INTERRUPTED) && (sc->sc_irq_status & LP_USE_IRQ)) while ((ppb_rstr(ppbus) & LP_READY_MASK) != LP_READY || sc->sc_transfer_count) if (ppb_sleep(ppbus, lpt_dev, LPT_PRI | PCATCH, "lpt_close", hz) != EWOULDBLOCK) break; sc->sc_state &= ˜LP_OPEN; callout_stop(&sc->sc_callout); ppb_wctr(ppbus, LPC_NINIT); lpt_release_ppbus(lpt_dev); end_close: sc->sc_state = 0; sc->sc_transfer_count = 0; ppb_unlock(ppbus); return (0); }
Like lpt_read
and lpt_write
, this function first checks the value of sc->sc_flags
. If it contains the LP_BYPASS flag
, lpt_close jumps
to end_close
. Next, lpt(4)
is assigned ownership of the parallel port. The following if
block ensures that if there is still data to transfer and the printer is interrupt-driven, the transfer is completed before closing lpt%d
. Then, LP_OPEN
is removed from sc->sc_state
, lpt_timeout is
stopped, the printer is reset, and ownership of the parallel port is relinquished. Lastly, sc->sc_state
and sc->sc_transfer_count
are zeroed.
The lpt_ioctl
function is defined in lpt_cdevsw
as the d_ioctl
operation. Before I describe this function, an explanation of its ioctl command, LPT_IRQ
, is needed. LPT_IRQ
is defined in the <dev/ppbus/lptio.h>
header as follows:
#define LPT_IRQ _IOW('p', 1, long)
As you can see, LPT_IRQ
requires a long int
value.
static int lpt_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { struct lpt_data *sc = dev->si_drv1; device_t lpt_dev = sc->sc_dev; device_t ppbus = device_get_parent(lpt_dev); u_short old_irq_status; int error = 0; switch (cmd) { case LPT_IRQ: ppb_lock(ppbus); if (sc->sc_irq_status & LP_HAS_IRQ) { old_irq_status = sc->sc_irq_status; switch (*(int *)data) { case 0: sc->sc_irq_status &= ˜LP_ENABLE_IRQ; break; case 1: sc->sc_irq_status &= ˜LP_ENABLE_EXT; sc->sc_irq_status |= LP_ENABLE_IRQ; break; case 2: sc->sc_irq_status &= ˜LP_ENABLE_IRQ; sc->sc_irq_status |= LP_ENABLE_EXT; break; case 3: sc->sc_irq_status &= ˜LP_ENABLE_EXT; break; default: break; } if (old_irq_status != sc->sc_irq_status) log(LOG_NOTICE, "%s: switched to %s %s mode ", device_get_nameunit(lpt_dev), (sc->sc_irq_status & LP_ENABLE_IRQ) ? "interrupt-driven" : "polled", (sc->sc_irq_status & LP_ENABLE_EXT) ? "extended" : "standard"); } else error = EOPNOTSUPP; ppb_unlock(ppbus); break; default: error = ENODEV; break; } return (error); }
Based on the argument given to LPT_IRQ
, lpt_ioctl
either disables interrupt-driven mode (which enables polled mode), enables interrupt-driven mode, enables extended mode, or disables extended mode (which enables standard mode). Note that interrupt-driven mode and extended mode conflict with each other, so if one is enabled, the other is disabled.
To run this function, you’d use the lptcontrol(8)
utility, whose source code I suggest you take a quick look at.
The lpt_request_ppbus
function sets lpt(4)
as the owner of the parallel port. Recall that owning the parallel port lets a device (such as lpt%d
) transfer data to and from it.
Here is the function definition for lpt_request_ppbus
:
static int lpt_request_ppbus(device_t dev, int how) { device_t ppbus = device_get_parent(dev); struct lpt_data *sc = device_get_softc(dev); int error; ppb_assert_locked(ppbus); if (sc->sc_state & LP_HAVEBUS) return (0); error = ppb_request_bus(ppbus, dev, how); if (!error) sc->sc_state |= LP_HAVEBUS; return (error); }
This function begins by checking the value of sc->sc_state
. If it contains LP_HAVEBUS
, which indicates that lpt(4)
currently owns the parallel port, lpt_request_ppbus
exits. Otherwise, ppb_request_bus
is called to set lpt(4)
as the owner of the parallel port and sc->sc_state
is assigned LP_HAVEBUS
.
The lpt_release_ppbus
function causes lpt(4)
to relinquish ownership of the parallel port. Here is its function definition:
static int lpt_release_ppbus(device_t dev) { device_t ppbus = device_get_parent(dev); struct lpt_data *sc = device_get_softc(dev); int error = 0; ppb_assert_locked(ppbus); if (sc->sc_state & LP_HAVEBUS) { error = ppb_release_bus(ppbus, dev); if (!error) sc->sc_state &= ˜LP_HAVEBUS; } return (error); }
This function first verifies that lpt(4)
currently owns the parallel port. Next, it calls ppb_release_bus
to relinquish ownership of the parallel port. Then LP_HAVEBUS
is removed from sc->sc_state
.
3.14.252.56