Next | Prev | Up | Top | Contents | Index

Device Driver Example

The following pages contain the complete driver code for the mythical gbd GIO device. Note that it includes strategy routines for devices that have hardware support for scatter/gather as well as for those devices that have no hardware scatter/gather support.

Commonly, a single set of source files is used for multiple target machines. C preprocessor defines are used to define differences conditionally. Command line compile options expose the correct values. The following examples are interesting:

For an Indigo (R3000) system:

% cc -DIP12 -DR3000 -cckr -c gbd.c

For an Indigo (R4000) system:

% cc -DIP20 -DR4000 -cckr -c gbd.c

For an Indigo2 (R4000) or Indy system:

% cc -DIP22 -DR4000 -cckr -c gbd.c

Note: For R8000 systems, omit the -cckr argument. For more information on compile directives, see /var/sysgen/Makefile.kernio.

/* Source for a mythical GIO board device; it can be compiled
 * for devices that support DMA (with or without scatter/
 * gather support), or for PIO mode only. This version is
 * designed for IRIX 5.1 or later.
 * Dave Olson, 5/93
 */

/* defines for compilation; would normally be passed on compilation
 * line via Makefile */
#define _K32U32   1
#define _KERNEL   1

#define IP20   1   /* define cpu type */

#if IP20 || IP22
#define R4000   1
#elif IP12
#define R3000   1
#endif
/* end of 'normal' compilation definitions */
/* The following definitions choose between PIO vs DMA
 * supporting boards, and if DMA is supported, whether
 * hardware scatter/gather is supported. */
#define GBD_NODMA 0  /* non-zero for PIO version of driver */
#define GBD_NUM_DMA_PGS 4 /* 0 for no hardware scatter/gather 
                           * support, else number of pages of
                           * scatter/gather supported per
                           * request */

#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/systm.h>
#include <sys/cpu.h>
#include <sys/buf.h>
#include <sys/cred.h>
#include <sys/uio.h>
#include <sys/ddi.h>
#include <sys/errno.h>
#include <sys/cmn_err.h>
#include <sys/edt.h>

   /* NOTE: This sample driver ignores the possiblity that
    * the board might be busy handling some earlier request.
    * Any real device must deal with that possiblity, of
    * course, before changing the board registers.
    */

/* these defines and structures would normally be in
 * a separate header file */

#define GBD_BOARD_ID   0x75
#define GBD_MASK   0xff  /* Use 0xff if using only first byte
                          * of ID word; use 0xffff if using
                          * whole ID word.
                          */

#define GBD_MEMSIZE 0x8000

/* command definitions */
#define GBD_GO 1

/* state definitions */
#define GBD_SLEEPING 1
#define GBD_DONE 2

/* direction of DMA definitions */
#define GBD_READ 0
#define GBD_WRITE 1

/* status defines */
#define   GBD_INTR_PEND   0x80

/* "gbd" is device prefix; also in master.d/xxx file */

/* devices interface to the board */
struct gbd_device {
   int command;
   int count;
   int direction;
   off_t offset;
   unsigned *sgregisters; /* if scatter/gather supported */
   caddr_t startaddr; /* if no scatter/gather on board */
   unsigned status;   /* errors, interrupt pending, etc. */
};

/* These are used for no scatter/gather case only, and assume
 * (since they aren't protected!) that the driver is
 * completely single threaded. */
struct buf   *gbd_curbp[2];  /* current buffer */
caddr_t      gbd_curaddr[2]; /* current address to transfer */
int          gbd_curcount[2];
int          gbd_totcount[2];

/* pointer to on-board registers */
volatile struct gbd_device *gbd_device[2];

char *gbd_memory[2];   /* pointer to on-board memory */

static int gbd_state[2];    /* flag for transfer state
             * (PIO driver) */

void gbdintr(int);
extern int splgio1(void);

/* equipped device table initialization routine.  The edt
 * structure is defined in edt.h.
 */
void
gbdedtinit(struct edt *e)
{
   int slot, val;

   /* Check to see if the device is present */
   if(badaddr_val(e->e_base, sizeof(int), &val) ||
         (val && GBD_MASK) != GBD_BOARD_ID) {
      if (showconfig)
         cmn_err (CE_CONT,
            "gbdedtinit: board not installed.");
         return;
   }

/* figure out slot from base on VECTOR line in
    * system file */
   if(e->e_base == (caddr_t)PHYS_TO_K1(0x1f400000))
      slot = GIO_SLOT_0;
   else if(e->e_base == (caddr_t)0xBF600000)
      slot = GIO_SLOT_1;
   else {
      cmn_err (CE_NOTE,
      "ERROR from edtinit: Bad base address %x\n", e->e_base);
      return;
   }

#if IP12      /* For Indigo R3000 system, set up board as a
               * realtime bus master.
               */

   setgioconfig(slot,0);

#endif

#if IP20 /* For Indigo R4000 system, set up board as a
          * realtime bus master.
          */

   setgioconfig(slot,GIO64_ARB_EXP0_RT | GIO64_ARB_EXP0_MST);
#endif

#if IP22  /* for Indigo2 system, set up board as a pipelined,
          * realtime bus master */

   setgioconfig(slot,GIO64_ARB_EXP0_RT |
                 GIO64_ARB_EXP0_PIPED);

#endif

   /* Save the device addresses, because
    * they won't be available later. */

   gbd_device[slot == GIO_SLOT_0 ? 0 : 1] =
            (struct gbd_device *)e->e_base;
   gbd_memory[slot == GIO_SLOT_0 ? 0 : 1] =
            (char *)e->e_base2;
   setgiovector(GIO_INTERRUPT_1,slot,gbdintr,unit_#);
}
/* minor number used to indicate which slot; open does 
 * nothing but check that board is present. */
/* ARGSUSED */
gbdopen(dev_t *devp, int flag, int otyp, cred_t *crp)
{
   if(!gbd_device[geteminor(*devp)&1])
      return ENXIO;   /* board not present */
   return 0;   /* OK */
}

/* ARGSUSED */
gbdclose(dev_t dev, int flag, int otyp, cred_t *crp)
{
   return 0;   /* nothing to do */
}

#ifdef GBD_NODMA

/* device write routine entry point (for character devices) */
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 count and updates uio fields, 
       * and 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 process level after waking up */
      splx(s);
   }
   return err;
}

/* interrupt routine for PIO only board, just wake up
 * upper half of driver
 */
/* ARGSUSED1 */
void
gbdintr(int unit)
{
   /* Read your board's registers to determine if there are
    * any errors or interrupts pending. If no interrupts 
    * are pending, return without doing anything.
    */
   if(!gbd_device[unit]->status & GBD_INTR_PEND)
      return;

   if (gbd_state[unit] == GBD_SLEEPING) {
      /* Output is complete; wake up top half
       * of driver, if it is waiting. */
      gbd_state[unit] = GBD_DONE;
      wakeup(&gbd_state[unit]);
   }

   /* Do anything else to board to tell it we are done
    * with transfer and interrupt here. */
return;    /* could just fall through */
}

#else   /* DMA version of driver */

void gbd_strategy(struct buf *);

/* device write routine entry point (for character devices).
 * Does nothing but call uiophysio to setup passing a pointer
 * to the gbd_strategy routine, which does most of the work.
 */
int
gbdwrite(dev_t dev, uio_t *uiop)
{
   return uiophysio((int (*)())gbd_strategy, 0, dev, B_WRITE, uiop);
}

#if GBD_NUM_DMA_PGS > 0

/* Actual device setup for DMA, etc., if your board has
 * hardware scatter/gather DMA support.
 * Called from the gbdwrite() routine via physio().
 */
void
gbd_strategy(struct buf *bp)
{
   int unit = geteminor(bp->b_dev)&1;
   int npages;
   volatile unsigned *sgregisters;
   int i, v_addr;

   /* Get address of the scatter/gather registers */
    sgregisters = gbd_device[unit]->sgregisters;

   /* Get the kernel virtual address of the data; note
    * b_dmaaddr may be NULL if the BP_ISMAPPED(bp) macro
    * indicates false; in that case, the field bp->b_pages
    * is a pointer to a linked list of pfdat structure
    * pointers; that saves creating a virtual mapping and
    * then decoding that mapping back to physical addresses.
    * BP_ISMAPPED will never be false for character devices,
    * only block devices.
    */
    if(!BP_ISMAPPED(bp)) {
      cmn_err(CE_WARN,
         "gbd driver can't handle unmapped buffers");
      bioerror(bp, EIO);
      biodone(bp);
      return;
   }

   v_addr = bp->b_dmaaddr;

   /* Compute number of pages received.
    * The dma_len field provides the number of pages to
    * map. Note that this may be larger than the actual
    * number of bytes involved in the transfer. This is
    * because the transfer may cross page boundaries,
    * requiring an extra page to be mapped. Limit to
    * number of scatter/gather registers on board.
    * Note that this sample driver doesn't handle the
    * case of requests > than # of registers!
    */
   npages = numpages (v_addr, bp->b_dmalen);
 
/*
    * Provide the beginning byte offset and count to the
    * device.
    */
   gbd_device[unit]->offset =
         (unsigned int)bp->b_dmaaddr & (NBPC-1);
   if(npages > GBD_NUM_DMA_PGS) {
      npages = GBD_NUM_DMA_PGS;
      cmn_err(CE_WARN,
          "request too large, only %d pages max", npages);
      if(gbd_device[unit]->offset)
         gbd_device[unit]->count = NBPC -
             gbd_device[unit]->offset + (npages-1)*NBPC;
      else
         gbd_device[unit]->count = npages*NBPC;
      bp->b_resid = bp->b_count - gbd_device[unit]->count;
   }
   else
      gbd_device[unit]->count = bp->b_count;

   /* Translate the virtual address of each page to a
    * physical page number and load it into the next
    * scatter/gather register. btop() 
    * converts the byte value to a page value after
    * rounding down the byte value to a full page.
    */
    for (i = 0; i < npages; i++) {
      *sgregisters++ = btop(kvtophys(v_addr));

      /*
      /* Get the next virtual address to translate.
       * (NBPC is a symbolic constant for the page
       * size in bytes)
       */

      v_addr += NBPC;
   }

   if ((bp->b_flags & B_READ) == 0)
      gbd_device[unit]->direction = GBD_WRITE;
   else
      gbd_device[unit]->direction = GBD_READ;
   gbd_device[unit]->command = GBD_GO;   /* start DMA */
  /* and return; upper layers of kernel wait for iodone(bp)*/
}
/* not much to do in this interrupt routine, since we are
 * assuming for this driver that we can never have to do
 * multiple DMA's to handle the number of bytes requested...
 */
void
gbdintr(int unit)
{
   int error;

   /* Read your board's registers to determine if
    * there are any errors or interrupts pending.
    * If no interrupts are pending, return without
    * doing anything.
    */
   if(!gbd_device[unit]->status & GBD_INTR_PEND)
      return;

   if(error)
      bioerror(bp, EIO);

   biodone(bp);   /* we are done, tell upper layers */

   /* do anything else to board to tell it we are done
    * with transfer and interrupt here */
}

#else /*  GBD_NUM_DMA_PGS == 0; no hardware
       *  scatter/gather support */

/* Actual device setup for DMA, etc., if your board
 * does NOT have hardware scatter/gather DMA support.
 * Called from the gbdwrite() routine via physio().
 */
void
gbd_strategy(struct buf *bp)
{
   int unit = geteminor(bp->b_dev)&1;

   /* any checking for initial state here. */

   /* Get the kernel virtual address of the data; note
    * b_dmaaddr may be NULL if the  BP_ISMAPPED(bp) macro
    * indicates false; in that case, the field bp->b_pages
    * is a pointer to a linked list of pfdat structure
    * pointers; that saves creating a virtual mapping and
    * then decoding that mapping back to physical addresses.
    * BP_ISMAPPED will never be false for character devices,
    * only block devices.
    */
    if(!BP_ISMAPPED(bp)) {
      cmn_err(CE_WARN,
         "gbd driver can't handle unmapped buffers");
      bioerror(bp, EIO);
      biodone(bp);
      return;
   }

   gbd_curbp[unit] = bp;
   /*
    * Initialize the current transfer address and count.
    * The first transfer should finish the rest of the
    * page, but do no more than the total byte count.
    */
   gbd_curaddr[unit] = bp->b_dmaaddr;
   gbd_totcount[unit] = bp->b_count;
   gbd_curcount[unit] = NBPC -
      ((unsigned int)gbd_curaddr[unit] & (NBPC-1));
   if (bp->b_count < gbd_curcount[unit])
      gbd_curcount[unit] = bp->b_count;
   /* Tell the device starting physical address, count,
    * and direction */
   gbd_device[unit]->startaddr = kvtophys(gbd_curaddr[unit]);
   gbd_device[unit]->count = gbd_curcount[unit];
   if (bp->b_flags & B_READ) == 0)
      gbd_device[unit]->direction = GBD_WRITE;
   else
      gbd_device[unit]->direction = GBD_READ;
   gbd_device[unit]->command = GBD_GO;   /* start DMA */

   /* and return; upper layers of kernel wait for iodone(bp) */
}

/* more complicated interrupt routine, not necessarily because
 * board has DMA, but more typical of boards that do have
 * DMA, since they are typically more complicated.
 * Also more typical of devices that support block i/o, as
 * opposed to character i/o.
 */
void
gbdintr(int unit)
{
   int error;
   register struct buf *bp = gbd_curbp[unit];

   /* read your board's registers to determine if
    * there are any errors or interrupts pending.
    * If no interrupts are pending, return without
    * doing anything.
    */
   if(!gbd_device[unit]->status & GBD_INTR_PEND)
      return;

   if(error) {
      bioerror(bp, EIO);
      biodone(bp);   /* we are done, tell upper layers */
   }
   else {
      /* On successful transfer of last chunk, continue
       * if necessary */
      gbd_curaddr[unit] += gbd_curcount[unit];
      gbd_totcount[unit] -= gbd_curcount[unit];
      if(gbd_totcount[unit] <= 0)
         biodone(bp);
            /* we are done, tell upper layers */
      else {
      /* else more to do, reprogram board and
       * start next dma */
          gbd_curcount[unit] =
             (gbd_totcount[unit] < NBPC
                   ? gbd_totcount[unit] : NBPC);
          gbd_device[unit]->startaddr =
                   kvtophys(gbd_curaddr[unit]);
          gbd_device[unit]->count = gbd_curcount[unit];
          if (bp->b_flags & B_READ) == 0)
             gbd_device[unit]->direction = GBD_WRITE;
          else
             gbd_device[unit]->direction = GBD_READ;
          gbd_device[unit]->command = GBD_GO;
               /* start next DMA */
      }
   }

   /* Do anything else to board to tell it we are done
    * with transfer and interrupt here. */
}
#endif /*  GBD_NUM_DMA_PGS */

#endif /* GBD_NODMA */


Next | Prev | Up | Top | Contents | Index