-->
Previous | Table of Contents | Next |
An example of device registers initialization is shown in the device driver for a standard screen terminal (UART) device:
/* define the registers */ #define RRDATA 0x01 /* receive */ #define RTDATA 0x02 /* transmit */ #define RSTATUS 0x03 /* status */ #define RCONTRL 0x04 /* control */ etc /* define the status registers */ #define SRRDY 0x01 /* received data ready */ #define STRDY 0x02 /* transmitter ready */ #define SPERR 0x08 /* parity error */ #define SCTS 0x40 /* clear to send status */ etc
The functions the device driver must perform are dependent on the nature of the device. All devices have an open() and close() routine that allows the device to perform I/O.
The open() routine must check to ensure a valid device is specified, validate the device request (permission to access the device or device not ready), then initialize the device. The open() routine is run every time a process uses the device.
The open() routine presented here is for a generic terminal device, td.
tdopen(device,flag) int device,flag; { /* definitions for local variables ignored */ /* details and definitions ignored in code */ /* check device number */ if (UNMODEM(device) >= NTDEVS) { seterror(ENXIO); return; } /* check if device in use */ /* if so, see if superuser (suser) for override */ tp = &td_tty[UNMODEM(device)]; address = td_address[UNMODEM(device)]; if((tp->t_lflag & XCLUDE) && !suser()) { seterror(EBBUSY); return; } /* if not open, initialize by calling ttinit() */ if((tp->t_state & (ISOPEN|WOPEN)) == 0) { ttinit(tp); /* initialize flags, and call tdparam() to set line */ tdparam(device); } /* if a modem is used, check carrier status */ /* if direct, set carrier detect flags */ /* set interrupt priority to avoid overwrite */ /* wait for carrier detect signal */ /* code eliminated from example */
The close() routine is used only after the process is finished with the device. The routine disables interrupts from the device and issues any shut-down commands. All internal references to the device are reset. close() routines are not usually required in many device drivers because the device is treated as being available throughout. Exceptions are removable media and exclusive-use devices. Some modems require closing (close()) to allow the line to be hung up.
Again, the terminal device example is used for the close() routine sample:
tdclose(device) { register struct tty *tp; tp = &td_tty[UNMODEM(device)]; (*linesw[tp->t_line].l_close)(tp); if(tp->t_cflag & HUPCL) tdmodem(device,TURNOFF); /* turn off exclusive flag bit */ ip->t_lflag & =~XCLUDE }
strategy functions (block mode devices only) are issued with a parameter to the kernel buffer header. The buffer header contains the instructions for a read or write along with a memory location for the operation to occur to or from. The size of the buffer is usually fixed at installation and varies from 512 to 1024 bytes. It can be examined in the file param.h as the BSIZE variable. A devices block size may be smaller than the buffer block size, in which case the driver executes multiple reads or writes.
The strategy function can be illustrated in a sample device driver for a hard disk. No code is supplied, but the skeleton explains the functions of the device driver in order:
int hdstrategy(bp) register struct buf *bp; { /* initialize drive and partition numbers */ /* set local variables */ /* check for valid drive & partition */ /* compute target cylinder */ /* disable interrupts */ /* push request into the queue */ /* check controller: if not active, start it */ /* reset interrupt level */ }
Character mode devices employ a write() instruction which checks the arguments of the instruction for validity and then copies the data from the process memory to the device driver buffer. When all data is copied, or the buffer is full, I/O is initiated to the device until the buffer is empty, at which point the process is repeated. Data is read from the process memory using a simple function (cpass) that returns a -1 when end of memory is reached. The data is written to process memory using a complementary function (passc). The write() routine is illustrated for the terminal device:
tdwrite(device) { register struct tty *tp; tp=&td_tty[UNMODEM(device)]; (*linesw[tp->t_line].l_write)(tp); }
Large amounts of data are handled by a process called copyio, which takes the addresses of source and destination, a byte count, and a status flag as arguments.
The read() operation for character mode devices transfers data from the device to the process memory. The operation is analogous to that of the write() procedure. For the terminal device, the read() code becomes:
tdread(device) { register struct tty *tp; tp=&td_tty[UNMODEM(device)]; (*linesw[tp->t_line].l_read)(tp); }
A small buffer is used when several characters are to be copied at once by read() or write(), rather than continually copying single characters. clist implements a small buffer used by character mode devices as a series of linked lists that use getc and putc to move characters on and off the buffer, respectively. A header for clist maintains a count of the contents.
Previous | Table of Contents | Next |