Next | Prev | Up | Top | Contents | Index

SCSI Device Driver Example

The following example shows how a driver can communicate with a direct access SCSI device, such as a disk. Note the use of scsi_info[]() to determine that the device is actually present and of the appropriate type.

This driver is simplified and does not do as much error checking as a real driver would do. Also, this example uses a global SCSI request structure that does not work in real drivers, since multiple reads or writes would overwrite a command in progress.

#include "sys/param.h"
#include "sys/types.h"
#include "sys/user.h"
#include "sys/buf.h"
#include "sys/errno.h"
#include "sys/cmn_err.h"
#include "sys/cred.h"
#include "sys/ddi.h"
#include "sys/systm.h"
#include "sys/scsi.h"

int sdk_devflag = 0;

#define ADAPT    0     /* SCSI host adapter */
#define TARGET   7     /* the disk will have target ID #7 */
#define LU       0     /* and logical unit  #0 */
#define TIMEOUT (30*HZ)/* wait 30 secs for SCSI device to
                          respond */
#define DIRECTACCESS 0 /* First byte of inqry cmnd */

unchar scsi_read[]    = {0x28, 0, 0, 0, 0, 0, 0, 0, 0, 0};
unchar scsi_write[]   = {0x2a, 0, 0, 0, 0, 0, 0, 0, 0, 0};

int    sdk_inuse = 0;
int    sdk_driver;
struct scsi_target_info *sdk_info;
struct scsi_request sdk_req;
u_char sdk_sensebuf[SCSI_SENSE_LEN];  /* SCSI_SENSE_LEN
                                         from scsi.h */

/* forward definitions*/
int sdk_strategy(struct buf *bp);
void sdk_notify(struct scsi_request *req);

/*
 * sdk_open - Open the SCSI device exclusively.
 *
 * Issue a SCSI inquiry command upon device and ensure
 * it is a direct access device.
 */
int
sdk_open(dev_t *devp, int flag, int otyp, cred_t *crp)
{
   if (sdk_inuse)
      return EBUSY;

   /* Get driver number */
   sdk_driver = scsi_driver_table[ADAPT];

   /*
    * Call through scsi_info to get inquiry data and to
    * find out if a device is at the address we want.
    */
   sdk_info = (*scsi_info[sdk_driver])(ADAPT, TARGET, LU);
   if (sdk_info == NULL)
      return ENODEV;

   /*
    * Is it a direct access device?  We could check the
    * entire inquiry buffer to ensure it is actually the
    * correct device.
    */
   if (sdk_info->si_inq[0] != DIRECTACCESS)
      return ENXIO;

   /*
    * It's a direct access device (disk drive).  Initialize
    * the connection to the host adapter driver.
    */
   if ((*scsi_alloc[sdk_driver])
      (ADAPT, TARGET, LU, 1, NULL) == 0)
      return EBUSY;

   /*
    * We have successfully allocated a connection between
    * sdk and the host adapter driver.  Initialize the
    * scsi_request structure, and mark the driver as being
    * in use.
    */
   sdk_inuse = 1;
   bzero(&sdk_req, sizeof(sdk_req));
   sdk_req.sr_ctlr = ADAPT;
   sdk_req.sr_target = TARGET;
   sdk_req.sr_lun = LU;
   sdk_req.sr_timeout = TIMEOUT;
   sdk_req.sr_sense = sdk_sensebuf;
   sdk_req.sr_senselen = sizeof(sdk_sensebuf);
   sdk_req.sr_notify = sdk_notify;

   return 0;
}

/* sdk_close - close the device and free the subchannel. */
int
sdk_close(dev_t dev, int flag, int otyp, cred_t *crp)
{
   (*scsi_free[sdk_driver])(ADAPT, TARGET, LU, NULL);
   sdk_inuse = 0;
   return 0;
}

/*
 * sdk_read - read from the SCSI device, ensuring an even
 * block count and a word-aligned address.
 */
sdk_read(dev_t dev, uio_t *uiop, cred_t *crp)

/*
 * sdk_write - write to the SCSI device, ensuring an even
 * block count and a word-aligned address.
 */
sdk_write(dev_t dev, uio_t *uiop, cred_t *crp)

/*
 * sdk_strategy - do the dirty work of the I/O.
 * Use either the SCSI read or write command as
 * appropriate.  Modify the block number and block counts
 * within the command buffer. Simply return here;
 * physio( ) will wait for an iodone( ).
 */
int
sdk_strategy(struct buf *bp)
{
   int blkno, blkcount;

   /* Prime the subchannel communication block. */

   blkno = bp->b_blkno;
   blkcount = BTOBB(bp->b_bcount);

   sdk_req.sr_command = bp->b_flags & B_READ ?
                        scsi_read : scsi_write;
   sdk_req.sr_command[2] = (char)(blkno>>24);
   sdk_req.sr_command[3] = (char)(blkno>>16);
   sdk_req.sr_command[4] = (char)(blkno>>8);
   sdk_req.sr_command[5] = (char) blkno;
   sdk_req.sr_command[7] = (char)(blkcount>>8);
   sdk_req.sr_command[8] = (char) blkcount;

   sdk_req.sr_cmdlen = SC_CLASS1_SZ;
   sdk_req.sr_flags = bp->b_flags & B_READ ? SRF_DIR_IN : 0;

   if (BP_ISMAPPED(bp)) {
      sdk_req.sr_buffer = bp->b_dmaaddr;
      sdk_req.sr_buflen = bp->b_bcount;
      sdk_req.sr_flags |= SRF_MAP;
   }
   else {
      sdk_req.sr_buffer = NULL;
      sdk_req.sr_buflen = bp->b_bcount;
      sdk_req.sr_flags = SRF_MAPBP;
   }
   sdk_req.sr_bp = bp;   /* required for SRF_MAPBP, but a
                          * convenience in all cases */

   /* Perform the SCSI operation. */
   (*scsi_command[sdk_driver])(&sdk_req);
}

/*
 * sdk_notify - SCSI command completion notification routine
 *
 * Simply check for errors and wake up physio( ) with
 * an iodone( ) on the buffer.
 * Note that a more robust driver would be more thorough
 * about error handling by retrying errors, giving more
 * information about error types, etc.
 */
void
sdk_notify(struct scsi_request *req)
{
   register struct buf *bp = req->sr_bp;

   if ((req->sr_status != SC_GOOD) ||
       (req->sr_scsi_status != ST_GOOD) ||
       (req->sr_sensegotten < 0))
   {
      cmn_err(CE_NOTE,
         "sdk: Error: driver stat 0x%x, scsi stat 0x%x"
         " sensegotten %d\n", req->sr_status,
         req->sr_scsi_status, req->sr_sensegotten);
      bioerror(bp, EIO);
         }
   else if (req->sr_sensegotten > 0) {
      cmn_err(CE_NOTE, "sdk: Error: sensekey 0x%x\n",
         sdk_sensebuf[2] & 0x0F);
      bioerror(bp, EIO);
   }
   bp->b_resid = req->sr_resid;
   biodone(bp);
}


Next | Prev | Up | Top | Contents | Index