However, sometimes you must write a driver for a device that does not support DMA. Even if a device does support DMA, you may not want to use DMA to transfer amounts of data so small that the DMA overhead is not warranted.
In these non-DMA cases, the processor is used to copy data from user space to the device. Some device make additional operations on the data. These operations may then be triggered by writing to a register on the device, such as a printer or disk controller. Most such devices have a status register that is used to verify completion. Polling on an interrupt can be used, but it is expensive and should be used sparingly.
Listed below is part of a mythical GIO device driver for a printer controller that does not support DMA. To print data from the user, the driver copies a number of bytes (as specified by uio_resid) from the uio_iov array to an on-board memory buffer of size GBD_MEMSIZE. Following the copy of each chunk, the driver programs the device registers to indicate the size of valid data in the memory and to tell the controller to start the printing.
Note: Beginning with IRIX 5.0, direct access of the user structure u is not allowed. Instead, user data may be accessed only through the uio structure. For example, note that the field u.u_count is not found in this driver, and references are made to the uio_resid field only. The driver then sleeps, waiting for an interrupt to indicate that the printing is complete and that the on-board memory buffer is available again. To prevent a race condition, in which the interrupt responds before the calling process can sleep, the driver uses the splgio1() routine.
/* device write routine entry point (for character devices)*/ volatile int gbdwrite(dev_t dev, uio_t *uio) { int unit = geteminor(dev)&1; int size, err=0, s; /* while there is data to transfer */ while((size=uio->uio_resid) > 0) { /* Transfer no more than GBD_MEMSIZE bytes * to the device */ size = size < GBD_MEMSIZE ? size : GBD_MEMSIZE; /* decrements size, updates uio fields, copies data */ if(err=uiomove(gbd_memory[unit], size, UIO_WRITE, uio)) break; /* prevent interrupts until we sleep */ s = splgio1(); /* Transfer is complete; start output */ gbd_device[unit]->count = size; gbd_device[unit]->command = GBD_GO; gbd_state[unit] = GBD_SLEEPING; while (gbd_state[unit] != GBD_DONE) { sleep(&gbd_state[unit], PRIBIO); } /* restore the interrupt level after waking up */ splx(s); } return err; }The driver's use of the volatile declaration informs the optimizer that this register points to a hardware value that may change. Otherwise, the optimizer may determine that one write to gbd_device->command is sufficient.
Note: If your driver uses the sleep() and wakeup() kernel routines to sleep and awaken, it is a good idea for the drvintr() to verify that the actual event has occurred before actually awakening the sleeping process. (See sleep() for details on the sleep/wakeup process synchronization mechanism.) If your driver uses the iowait()/iodone() routines or the psema()/vsema() routines to sleep and awaken, you need not worry about it awakening by accident. However, the routines psema() and vsema() are specific to IRIX and are probably not supported on other operating systems. The uiomove() kernel routine is a useful procedure to call in these situations because it automatically updates the fields in the uio structure and uses copyout() (or copyin()) to check for invalid user addresses. Recall that uio_resid must be left with the number of bytes left untransferred.