Writing Device Drivers

HBA Driver Dependency and Configuration Issues

In addition to incorporating SCSA HBA entry points, structures and functions into a driver, HBA driver developers must also concern themselves with issues surrounding driver dependency and configuration. These issues are summarized in the following list:

HBA Configuration Properties

When attaching an instance of an HBA device, scsi_hba_attach_setup(9F) creates a number of SCSI configuration parameter properties for that HBA instance. A particular property is created only if there is no existing property of the same name already attached to the HBA instance, permitting a default property value to be overridden in an HBA configuration file.

An HBA driver must use ddi_prop_get_int(9F) to retrieve each property. The HBA driver then modifies (or accepts the default value of) the properties to configure its specific operation.

scsi-reset-delay Property

The scsi-reset-delay property is an integer specifying the SCSI bus or device reset delay recovery time in milliseconds.

scsi-options Property

The scsi-options property is an integer specifying a number of options through individually defined bits. The bits in scsi_options are:

Per-target scsi-options

An HBA driver may support a per-target scsi-options feature in the following format:

	target<n>-scsi-options=<hex value>

In this example, < n> is the target ID. If the per-target scsi-options property is defined for a particular target, the HBA driver uses the value of the per-target scsi-options property for that target rather than the per-HBA driver instance scsi-options property. This can provide more precise control if, for example, synchronous data transfer needs to be disabled for just one particular target device. The per-target scsi-options property may be defined in the driver.conf(4) file.

Here is an example of a per-target scsi-options property definition to disable synchronous data transfer for target device 3:

	target3-scsi-options=0x2d8

Declarations and Structures

HBA drivers must include the following header files, along with a declaration of dependency upon the scsi module:

	#include <sys/scsi/scsi.h>
 	#include <sys/ddi.h>
 	#include <sys/sunddi.h>

 	char _depends_on[] = "misc/scsi";

This declaration informs the system that the module depends on SCSA routines (see "SCSA HBA Interfaces" for more information). This construct is used only for SCSI drivers and should not be used elsewhere.

Per-Command Structure

An HBA driver will usually need to define a structure to maintain state for each command submitted by a target driver. The layout of this per-command structure is entirely up to the device driver writer and needs to reflect the capabilities and features of the hardware and the software algorithms used in the driver.

The following structure is an example of a per-command structure. The remaining code fragments of this chapter use this structure to illustrate the HBA interfaces.

struct isp_cmd {
 	struct isp_request			cmd_isp_request;
 	struct isp_response			cmd_isp_response;
 	struct scsi_pkt				*cmd_pkt;
 	struct isp_cmd					*cmd_forw;
 	uint32_t							cmd_dmacount;
 	ddi_dma_handle_t				cmd_dmahandle;
 	uint_t							cmd_cookie;
 	uint_t							cmd_ncookies;
 	uint_t							cmd_cookiecnt;
 	uint_t							cmd_nwin;
 	uint_t							cmd_curwin;
 	off_t								cmd_dma_offset;
 	uint_t							cmd_dma_len;
 	ddi_dma_cookie_t 				cmd_dmacookies[ISP_NDATASEGS];
 	u_int								cmd_flags;
 	u_short							cmd_slot;
 	u_int								cmd_cdblen;
 	u_int								cmd_scblen;
 };

HBA Additions to the State Structure

This chapter adds the following fields to the state structure. See "Software State Structure" for more information.

		scsi_hba_tran_t			*isp_tran;
 	dev_info_t					*isp_dip;
 	ddi_iblock_cookie_t		isp_iblock;
 	int							isp_target_scsi_options[N_ISP_TARGETS_WIDE];
 	int							isp_scsi_tag_age_limit;
 	u_int							isp_scsi_reset_delay;
 	u_short						isp_cap[N_ISP_TARGETS_WIDE];
 	u_short						isp_synch[N_ISP_TARGETS_WIDE];

 	struct ispregs				*isp_reg;
 	ddi_acc_handle_t			isp_acc_handle;

Module Initialization Entry Points

Drivers for different types of devices have different sets of entry points, depending on the operations they perform. Some operations, however, are common to all drivers, such as the as _init(9E), _info(9E) and _fini(9E) entry points for module initialization. Chapter 3, Overview of SunOS Device Drivers, gives a complete description of these loadable module routines. This section describes only those entry points associated with operations performed by SCSI HBA drivers.

The following code for a SCSI HBA driver illustrates a representative dev_ops(9S) structure. The driver must initialize the devo_bus_ops field in this structure to NULL. A SCSI HBA driver may provide leaf driver interfaces for special purposes, in which case the devo_cb_ops field may point to a cb_ops(9S) structure. In this example, no leaf driver interfaces are exported, so the devo_cb_ops field is initialized to NULL.

	
			static struct dev_ops isp_dev_ops = {
 		DEVO_REV,			/* devo_rev */
 		0,						/* refcnt  */
 		isp_getinfo,		/* getinfo */
 		nulldev,				/* probe */
 		isp_attach,			/* attach */
 		isp_detach,			/* detach */
 		nodev,				/* reset */
 		NULL,					/* driver operations */
 		NULL,					/* bus operations */
 		isp_power,			/* power management */
 	};

_init(9E)

The _init(9E) function initializes a loadable module and is called before any other routine in the loadable module.

In a SCSI HBA, the _init(9E) function must call scsi_hba_init(9F) to inform the framework of the existence of the HBA driver before calling mod_install(9F). If scsi_hba_init(9F) returns a nonzero value, _init(9E) should return this value. Otherwise, _init(9E) must return the value returned by mod_install(9F).

The driver should initialize any required global state before calling mod_install(9F).

If mod_install(9F) fails, the _init(9E) function must free any global resources allocated and must call scsi_hba_fini(9F) before returning.

The following code sample uses a global mutex to show how to allocate data that is global to all instances of a driver. The code declares global mutex and soft-state structure information. The global mutex and soft state are initialized during _init(9E).

	/*
 	 * Local static data
 	 */
 	static kmutex_t			isp_global_mutex;
 	static void					*isp_state;

The _init(9E) function in Example 14-1 shows how a SCSI HBA driver initializes a global mutex.


Example 14-1 SCSI HBA _init(9E) Function

	int
	_init(void)
	{
	  	  int     err;
	
	  	  if ((err = ddi_soft_state_init(&isp_state,
							sizeof (struct isp), 0)) != 0) {
					return (err);
		  }
	     if ((err = scsi_hba_init(&modlinkage)) == 0) {
	        			mutex_init(&isp_global_mutex, "isp global mutex",
				    	MUTEX_DRIVER, NULL);
	        			if ((err = mod_install(&modlinkage)) != 0) {
	            mutex_destroy(&isp_global_mutex);
	            scsi_hba_fini(&modlinkage);
					 ddi_soft_state_fini(&isp_state);	
	        		 }
			}
	    	return (err);
	}

_fini(9E)

The _fini(9E) function is called when the system is about to try to unload the SCSI HBA driver. The _fini(9E) function must call mod_remove(9F) to determine if the driver can be unloaded. If mod_remove(9F) returns 0, the module can be unloaded, and the HBA driver must deallocate any global resources allocated in _init(9E) and must call scsi_hba_fini(9F).

_fini(9E) must return the value returned by mod_remove(9F).


Note -

The HBA driver must not free any resources or call scsi_hba_fini(9F) unless mod_remove(9F) returns 0.


The _fini(9E) function in Example 14-2 shows how a SCSI HBA driver deallocates a global mutex initialized in _init(9E).


Example 14-2 SCSI HBA _fini(9E) Function

int
_fini(void)
{
  	int     err;
	
 	if ((err = mod_remove(&modlinkage)) == 0) {
			mutex_destroy(&isp_global_mutex);
			scsi_hba_fini(&modlinkage);
			ddi_soft_state_fini(&isp_state);
 	}
	  return (err);
}

Autoconfiguration Entry Points

Associated with each device driver is a dev_ops(9S) structure, which allows the kernel to locate the autoconfiguration entry points of the driver. A complete description of these autoconfiguration routines is given in Chapter 5, Autoconfiguration. This section describes only those entry points associated with operations performed by SCSI HBA drivers. These include attach(9E) and detach(9E).

attach(9E)

The attach(9E) entry point for a SCSI HBA driver must perform a number of tasks to configure and attach an instance of the driver for the device. For a typical driver of real devices, the following operating system and hardware concerns must be addressed:

Soft State Structure

The driver should allocate the per-device-instance soft state structure, being careful to clean up properly if an error occurs.

instance = ddi_get_instance(dip);
 if (ddi_soft_state_zalloc(isp_state,
 		instance) != DDI_SUCCESS) {
 	return (DDI_FAILURE);
 }
 isp = ddi_get_soft_state(isp_state, instance);

DMA

If the driver provides DMA, for example, it must specify DMA attributes describing the capabilities and limitations of its DMA engine.


Note -

In the Solaris 7 system, the HBA driver must provide DMA.


	
			static ddi_dma_attr_t isp_dma_attr = {
 		DMA_ATTR_V0,		/* ddi_dma_attr version */
 		0,						/* low address */
 		0xffffffff,			/* high address */
 		0x00ffffff,			/* counter upper bound */
 		1,						/* alignment requirements */
 		0x3f,					/* burst sizes */
 		1,						/* minimum DMA access */
 		0xffffffff,			/* maximum DMA access */
 		(1<<24)-1,			/* segment boundary restrictions */
 		1,						/* scatter/gather list length */
 		512,					/* device granularity */
 		0						/* DMA flags */
 	};

The driver, if providing DMA, should also check that its hardware is installed in a DMA-capable slot:

		if (ddi_slaveonly(dip) == DDI_SUCCESS) {
 			return (DDI_FAILURE);
 		}

Transport Structure

The driver should further allocate and initialize a transport structure for this instance. The tran_hba_private field is set to point to this instance's soft-state structure. tran_tgt_probe may be set to NULL to achieve the default behavior, if no special probe customization is needed.

	tran = scsi_hba_tran_alloc(dip, SCSI_HBA_CANSLEEP);

 	isp->isp_tran							= tran;
 	isp->isp_dip							= dip;

 	tran->tran_hba_private				= isp;
 	tran->tran_tgt_private				= NULL;
 	tran->tran_tgt_init					= isp_tran_tgt_init;
 	tran->tran_tgt_probe				= scsi_hba_probe;
 	tran->tran_tgt_free					= (void (*)())NULL;

 	tran->tran_start						= isp_scsi_start;
 	tran->tran_abort						= isp_scsi_abort;
 	tran->tran_reset						= isp_scsi_reset;
 	tran->tran_getcap						= isp_scsi_getcap;
 	tran->tran_setcap						= isp_scsi_setcap;
 	tran->tran_init_pkt					= isp_scsi_init_pkt;
 	tran->tran_destroy_pkt				= isp_scsi_destroy_pkt;
 	tran->tran_dmafree					= isp_scsi_dmafree;
 	tran->tran_sync_pkt					= isp_scsi_sync_pkt;
 	tran->tran_reset_notify			= isp_scsi_reset_notify;

Attaching an HBA Driver

The driver should attach this instance of the device and perform error cleanup if necessary.

	i = scsi_hba_attach_setup(dip, &isp_dma_attr, tran, 0);
 	if (i != DDI_SUCCESS) {
 		do error recovery 	
			return (DDI_FAILURE);
 	}

Register Mapping

The driver should map in its device's registers, specifying the index of the register set, the data access characteristics of the device and the size of the register set to be mapped.

		ddi_device_acc_attr_t						dev_attributes;

 	dev_attributes.devacc_attr_version = DDI_DEVICE_ATTR_V0;
 	dev_attributes.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
 	dev_attributes.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;

 	if (ddi_regs_map_setup(dip, 0, (caddr_t *)&isp->isp_reg,
 	    0, sizeof (struct ispregs), &dev_attributes,
 	    &isp->isp_acc_handle) != DDI_SUCCESS) {
 		do error recovery
			return (DDI_FAILURE);
 	}

Adding an Interrupt Handler

The driver should determine if a high-level interrupt handler is required. If a high-level handler is required and the driver is not coded to provide one, the driver must be rewritten to either include a high-level interrupt or fail the attach.

In the following example, a high-level interrupt is required but not provided by the driver. Consequently, the driver fails the attach.

	if (ddi_intr_hilevel(dip, 0) != 0) {
 		return (DDI_FAILURE);
 	}

The driver must first obtain the iblock cookie to initialize mutexes used in the driver handler. Only after those mutexes have been initialized can the interrupt handler be added.

	i = ddi_get_iblock_cookie(dip, 0, &isp->iblock_cookie};
 	if (i != DDI_SUCCESS) {
 		do error recovery
			return (DDI_FAILURE);
 	}

 	mutex_init(&isp->mutex, "isp_mutex", MUTEX_DRIVER,
 		(void *)isp->iblock_cookie);
 	i = ddi_add_intr(dip, 0, &isp->iblock_cookie,
 		0, isp_intr, (caddr_t)isp);
 	if (i != DDI_SUCCESS) {
 		do error recovery
			return (DDI_FAILURE);
 	}

Report Attachment Status

Finally, the driver should report that this instance of the device is attached and return success.

	ddi_report_dev(dip);
 	return (DDI_SUCCESS);

detach(9E)

The Solaris 7 DDI/DKI does not support detaching an HBA driver, although target driver children of an HBA can detach. For best results, the HBA driver should fail a detach request. It's better to fail the detach than to include code that cannot be tested.

Example 14-3 provides an example of the xx_detach() function.


Example 14-3 isp_detach

static int
isp_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	switch (cmd) {
	case DDI_DETACH:
	   	/*
		    * At present, detaching HBA drivers is not supported
		    */
	    	return (DDI_FAILURE);
	case DDI_PM_RESUME:
	    	For information, see Chapter 8, Power Management	case DDI_RESUME:
	     	For information, see Chapter 8, Power Management	default:
	    	return (DDI_FAILURE);
	}
}