=== 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)