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)

Author: Mike A. Larson
Page maintainer: Erik Dalén