vsta

SCSI
Login
=== SCSI drivers writers guide ===

                                 Introduction

   The CAM (Common Access Method) for SCSI provides an architecture for
   supporting multiple peripheral device classes across multiple host
   adaptors. Within this architecture, there are specifications for   
   peripheral drivers, which handle classes of devices, SIM (SCSI
   Interface Module) drivers, which handle host adaptors, and a mechanism
   for routing peripheral device requests to the appropriate SIM.

   CAM operates within various operating system enviroments. In this
   respect, there are implementation differences on how the driver hooks 
   into the operating system, how initialization is done, etc. This
   document covers the Operating System specific details of the VSTA
   SCSI/CAM driver.
 
   CAM also provides applications with a common way to access SCSI
   peripherals. VSTa applications communicate with CAM via the standard
   messaging interface, which includes read, write, read dir, read stat,
   and write stat messages. An interface that allows CAM CCB's to be sent
   indirectly to a peripheral device is currently under constructon.
   
                    Application View of the VSTa CAM Driver
   
Device Names
 
   Device names composed of an optional device prefix letter followed by
   a 2 letter device ID followed by address ID followed by an optional
   '_' and device specific parameter string.

        device prefix letter    - "n" for no-rewind tapes
  
        device ID               - "sd" for hard disks and magneto-
                                  optical disks, "sr" for CDROM drives,
                                  and "st" for tapes.
 
        device address          - BUS/TARGET/LUN identifier (see below).

        device specific         - optinal string that encodes
                                  parameters specific to a device or a  
                                  mode that the device supports (see
                                  below).

   The device ID has the following structure: BBTT[L], where B = BUS, T =
   TARGET, L = optional LUN designator (a = LUN 0, b = LUN 1, etc.),
   leading 0's are stripped. For example, st4 is the tape unit at BUS 0,
   TARGET 4, LUN 0 and st104b is the tape unit at BUS 1, TARGET 4, LUN 1.
   
   The device specific string is used as follows: for "sd" devices, the
   device specific string is a partition type name (eg, "dos") followed
   by a partition index (0 to number of partitions minus 1). For "st"
   devices, device specific string is an optional mode selector (tape
   modes are described later). Currently, this is a number that ranges
   from 0 - 3. For example, sd2_dos2 is the third disk partition on the
   disk at BUS 0, TARGET 2, LUN 0 and st104b_1 the tape unit at BUS 1,   
   TARGET 4, LUN 1 with parameters described by mode 1.
   
FS_WSTAT Syntax

Syntax:
   
        : COMMAND
        | COMMAND '=' PRMNAME
        | COMMAND '=' PRMNAME prmval
        | COMMAND '=' prmval
        ;

   where COMMAND and PRMNAME are identifiers and prmval is a literal (eg,
   1, 3.14) or an identifier.
   
Tape Device Interfaces
   
  Tape Device FS_STAT Message Fields

        size            - an attempt at determining the number of
                          bytes on a tape medium.
        type            - "s" (for special file)
        owner           - "1/1" (sys/sys)
        dev             - CAM device number
        id              - vendor + product + revision ID fields

  Tape Device FS_WSTAT Message Fields

        Command         PrmName         PrmVal
        -------         -------         ------
 
        MTIOCTOP        mt_op (*1)      count
   
   *1) the SCSI/CAM driver uses the following MTIOCTOP operations defined
   in the FreeBSD mtio.h:
   
        MTWEOF          - write an end-of-file record
        MTFSF           - forward space file
        MTBSF           - backward space file
        MTFSR           - forward space record
        MTBSR           - backward space record
        MTREW           - rewind
        MTOFFL          - rewind and put the drive offline
        MTNOP           - no operation, sets status only
        MTCACHE         - enable controller cache
        MTNOCACHE       - disable controller cache
        MTSETBSIZ       - set block size
        MTSETDNSTY      - set density

                        The Peripheral Driver Interface
        
   The VSTa CAM driver supports disk, tape, CDROM, and generic peripheral
   device classes.
         
   TBD adding new peripheral drivers.
   
                           The SIM Driver Interface
   
Overview
   
   According to the CAM specification, SIM drivers are called from the
   XPT layer via indirect function calls to two driver entry points:
   sim_init() and sim_action(). A driver's sim_init() function is called
   once per HBA and is an adaptor initialization function. All CCB
   requests from the XPT to a particular SIM are placed through the SIM's
   sim_action() function.
 
   Also in the CAM specification is an option for initializing 
   dynamically loaded SIM drivers. The VSTa SCSI/CAM SIM driver
   initialization is modeled after this specification, even though all
   SIM drivers are currently statically linked. When the SIM module is
   "loaded", an initialization routine defined at link time is called.
   For statically linked SIM drivers, the VSTa SCSI/CAM server uses a
   table of driver initialization entry points.
        
   In addition to the entry points defined by the CAM specification,
   there are two other important SIM functions: start and interrupt. The 
   start function is called by SIM library to start I/O. The interrupt
   function, registered by the HBA initialization function, fields
   adaptor interrupts.
        
   The VSTa SCSI/CAM server includes a library of functions callable by
   SIM drivers. These functions handle I/O initiation and completion,
   etc.
        
Driver Initialization
        
   An array of per SIM driver structures is used by the CAM configuration
   code to dispatch to various SIM driver's initialization functions. The
   per driver initialization structure has the following format:

/* 
 * Per driver initialization structure.
 */     
        struct  sim_driver {
                void    (*dvrinit)();                   /* driver init fcn */
                int     max_adaptors;                   /* MAX adaptors */
                struct  sim_hba_params *hba_params;     /* HW parameters */
        };

   The 'dvrinit' parameter is a pointer to a SIM driver initialization
   function. The 'hba_params' field is a pointer to an array of
   'max_adaptors' structures that contain hardware specific information.
   This information is optional, as hardware configuration information
   can be read from the adaptor itself in some cases (eg, the Adaptec
   1542). The structure may also be used to override information stored
   in the SIM driver. The hardware parameters structure has the following
   format:

        /*
         * Adaptor parameters. The exact meaning of these fields is
         * driver specific.
         */
        struct  sim_hba_params {
                void    *ioaddr;                        /* base I/O address */
                int     irq;                            /* interrupt level */
                int     ivect;                          /* interrupt vector */
                int     dmarq;                          /* DMA level */
        };

   A simple version of the 'dvrinit' function looks something like:   

        /*
         * simxxx_dvrinit - driver initialization function.
         */
        void    simxxx_dvrinit(sdp)
        struct  sim_driver *sdp;
        {
        /*
         * This is only done once.
         */
                if(already_initialized)
                        return;
        
                allocate storage
 
                wire down buffers
        
                enable DMA
         
        /*
         * Try to register each possible HBA.
         */                
                foreach possible HBA address or ioport
                        (void)xpt_bus_register(&simxxx_entry); 
        }

   The function xpt_bus_register() takes pointer to a CAM_SIM_ENTRY  
   structure (see) below. The purpose of this function is to register a
   new instance of an HBA.
   
HBA Initialization and Action Functions
          
   The CAM specification defines a structure that contains the addresses
   of a per HBA initialization function and an "action" function. The
   structure is declared as follows:

        typedef struct cam_sim_entry {
                long    (*sim_init)();          /* SIM init routine */
                long    (*sim_action)();        /* SIM CCB go routine */
        } CAM_SIM_ENTRY;

   A SIM driver's sim_init() and sim_action() have the following
   prototypes:

        long    simxxx_init(long path_id);
        long    simxxx_action(CCB *ccb);

   where "xxx" is a string that refers to the SIM driver's name, HBA
   type, etc.
         
   A pointer to a SIM driver's CAM_SIM_ENTRY structure is passed to the
   xpt_bus_register() function. The XPT, in turn, calls the sim_init()
   function with a newly allocated PATH ID. The sim_init() function
   returns CAM_SUCCESS if a HBA was successfully initialized. Otherwise,
   it returns CAM_FAILURE.
        
   A simple version of a SIM driver's simxxx_init() function looks
   something like:

        long    simxxx_init(path_id)
        long    path_id;  
        {
                look for the next uninitialized HBA
         
                get a pointer to the HBA's per adaptor structure
                
                initialize the adaptor's sim_bus structure:    
         
                        adaptor->ioport = ioport;
                        adaptor->bus_info.path_id = path_id;
                        adaptor->bus_info.phase = SCSI_BUS_FREE;
                        adaptor->bus_info.bus_flags = 0;
                        adaptor->bus_info.last_target = CAM_MAX_TARGET;
                        adaptor->bus_info.start = simxxx_start;
          
                initialize the target queues:
   
                        target_info = adaptor->bus_info.target_info;
                        for(i = 0; i < CAM_NTARGETS; i++, target_info++) {
                                CAM_INITQUE(&target_info->head);
                                target_info->nactive = 0;
                        }
                initialize the adaptor
   
                get the following adaptor configuration information:
   
                        interrupt level   
                        DMA channel
                        etc.
   
                enable interrupt handling for this adaptor:
         
                        status = cam_enable_isr(intr_level, adn, simxxx_intr);
   
                print out information about this adaptor
        }

   The XPT calls a SIM driver's sim_action() with a CCB from a peripheral
   driver. The type of action taken is based on the CCB's
   'header.fcn_code' (for reads and writes, this field is set to
   XPT_SCSI_IO). The sim_action() function returns a CAM status code.
        
   Currently, the sim154x.c driver's sim_action() is very simple - it
   dispatches off to the SIM library's sim_action() function (see below)
   to schedule I/O. In the future, the driver will also need to handle
   non-I/O requests such as BUS RESET.
                
HBA Start and Interrupt Functions
                
   The CAM libraries recognize two additional SIM driver functions:
   start() and interrupt(). The start() function is called by the SIM
   library to an I/O queued by sim_action. The start() function takes a
   CCB (assumed to be a SCSI I/O CCB) and returns a CAM status code.
                        
   A SIM driver's interrupt function is registered with the CAM library
   via a call to the cam_enable_isr() function. This function takes 3
   parameters: an interrupt (IRQ) level, a user defined argument to be
   passed to the interrupt handler, (typically an adaptor number), and
   the address of the driver's interrupt function. cam_enable_isr()
   returns either CAM_SUCCESS or a CAM error number code.
        
   When a interrupt messages (M_ISR) message is received by the CAM
   library, it dispatches to the interrupt function associated with the
   interrupt level found in the message's m_arg field. The parameters to
   the interrupt function are the interrupt level and the user defined
   argument passed to cam_enable_isr().
   
SIM Library Interfaces
   
   Data shared between the SIM library and the SIM drivers is kept in the
   following per-bus structure:

        struct  sim_bus {
                long    path_id;                /* CAM path ID */
                enum    scsi_bus_phases phase;
                uint32  bus_flags;
                uint32  last_target;            /* last target scheduled  */
                long    (*start)();             /* SIM driver start I/O */
                struct  sim_target target_info[CAM_NTARGETS];
        };

   An instance of this structure should be initialized in SIM driver's
   HBA initialization function (simxxx_init()). See the sample HBA
   initialization code for an example of how to initialize this
   structure.
   
                            CAM Library Interfaces

/*
 * xpt_bus_register - register a SIM driver. This function is called
 * from SIM driver initialization functions. xpt_bus_register()
 * calls the sim_init() function referenced in the input 'cse'
 * structure with a newly allocated path ID.
 */
long    xpt_bus_register(CAM_SIM_ENTRY *cse)
   
/*                      
 * sim_action - initiate I/O on the input CCB. Queue the input CCB
 * on its B/T/L I/O queue and schedule the next I/O for the bus
 * associated with 'bus_info'.
 */
long    sim_action(CCB *ccb, struct sim_bus *bus_info)
   
/*      
 * sim_complete - per-target completion. Remove the CCB from its I/O
 * queue, call the peripheral driver's completion function, and
 * schedule another I/O for the bus associated with 'bus_info'.
 */
void    sim_complete(struct sim_bus *bus_info, CCB *ccb)
   
/*
 * Cam_alloc_mem - CAM memory allocator/reallocator. If 'ptr' is
 * non-null, try to reallocate 'size' bytes of data starting
 * at 'ptr'. If 'ptr' is NULL and 'align' is zero, allocate
 * 'size' bytes of data. If 'ptr' is NULL and 'align' is NBPG,
 * allocate 'size' bytes of page aligned memory. In all cases,
 * a pointer to newly allocated memory is returned on success and
 * NULL is return on failure.
 */
void    *cam_alloc_mem(size_t size, void *ptr, unsigned int align)
                
/*
 * Cam_free_mem - free memory allocated by cam_alloc_mem().
 */
void    cam_free_mem(void *ptr, unsigned int align)
   
/*
 * Cam_enable_io - enable I/O access for the input range of addresses.
 */
long    cam_enable_io(int low, int high)
   
/*
 * Cam_enable_isr - enable and set up an interrupt handler for the  
 * input interrupt.
 */
long    cam_enable_isr(int intr, long arg, void (*handler)())
   
/*
 * Cam_page_wire - wire down the page associated with the input
 * virtual and return the corresponding physical address.
 */
long    cam_page_wire(void *va, void **pa, int *handle)
 
/* 
 * Cam_page_release - unwire the page slot associated w/ 'handle'.
 */
long    cam_page_release(int handle)
 
/*
 * cam_msleep
 *      Wait for the specified number of milli-seconds.
 */
void    cam_msleep(int msecs)