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