-->
Previous Table of Contents Next


An example of device register’s 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.

Opening the Device

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 */

Closing the Device

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

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 device’s 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 */

        }

write() Functions

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.

read() Functions

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