PHD Computer Consultants Ltd
SCSI Device Drivers notes
Last modified: 8 February 1999.

PHD's Windows Device Driver resources


These programming notes cover the following topics:

SCSI Miniport Drivers

NT 5 SCSI miniports are either run as legacy drivers or as PnP drivers. A PnP miniport must have a HwScsiStopAdaptor routine and have a PnPInterface entry in the registry.

Initialisation happens different, ie in PnP manner.

Miniport called by the system port driver to process SCSI Request Blocks (SRBs). These are translated by NT into Command Descriptor Blocks (CDBs). Non-SCSI drivers can fit into this scheme and pretend to be SCSI device.

A miniport has a DriverEntry and various HwScsi... routines. Various other call-backs used. Called by port driver.

A driver has a device extension for each HBA. Also has a luextension for each logical unit. Can specify the size of SrbExtensions.

DriverEntry initialises an HW_INITIALIZATION_DATA structure with pointers to its routines. It then calls ScsiPortInitialize for each bus it might be attached to.

In legacy drivers, ScsiPortInitialize calls back the driver’s HwScsiFindAdapter routine. HwScsiFindAdapter sees if its hardware is present on the given bus and gets access to it. It allocates a DMA buffer if appropriate.

HwScsiInitialize is then called for each found HBA. This sets any registers and enables interrupts.

HwScsiStartIo called to process SRBs. The miniport driver owns the request and must complete it. The miniport calls ScsiPortNotification with notification codes. For each SRB, a miniport usually issues a CompleteRequest notification and then a NextRequest or NextLuRequest notification.

Most SRBs are SRB_FUNCTION_EXECUTE_SCSI.

A miniport can say that it is busy and ask that the SRB be queued again for processing later.

To perform DMA, call ScsiPortIoMapTransfer. When the DMA controller is ready, the miniport’s HwScsiDmaStarted routine is called to set up the HBA for the DMA transfer. Call ScsiPortFlushDma to flush any cached data from DMA controllers.

The HwScsiInterrupt routine is to service a HBA interrupt. Note that normally there is no deferred procedure call. So service the interrupt and call ScsiPortNotification to complete the request and start the next one.

Some HwScsiInterrupt routines might need to run for a long time, eg more than 50 microseconds if doing polling programmed I/O. This is best handled by scheduling a DPC call-back using ScsiPortNotification CallEnableInterrupts with a HwScsiEnableInterruptsCallback routine. HwScsiEnableInterruptsCallback can carry on interacting with the HBA safe from its own interrupts, although all other system interrupts are enabled. After completing the request, as per usual, it makes a ScsiPortNotification CallDisableInterrupts with a HwScsiDisableInterruptsCallback routine; this should reenable interrupts from the HBA.

HwScsiResetBus is called to reset a bus. ScsiPortCompleteRequest can be used instead to complete all relevant requests after a bus or device reset.

A timer routine might be used to poll the HBA. To set up a one-shot timer, make a ScsiPortNotification RequestTimer call with a HwScsiTimer routine and the required interval. The port driver ensures that HwScsiTimer does not run at the same time as a HwScsiInterrupt.

HwScsiStopAdaptor is called when the HBA is about to be stopped or removed. Disable all interrupts and release all resources.

Use ScsiPortLogError to report errors.

Use ScsiPortStallExecution to wait for small time intervals. Never delay for more than 1 millisecond in any miniport routine.

Miniport drivers of HBAs that are initialised in x86 real mode and that are sensitive to x86-specific processor mode transitions must have a HwScsiAdapterState routine.

The use can set values which disable certain features of an HBA: synchronous transfers, bus-disconnect operations, tagged queuing and internal queuing. How?

SRBs have:

The PathId value

This identifies the SCSI bus on which the device is attached. It is a required value in SRBs.

The TargetId value

This identifies the TID of the controller or device. It is a required value in SRBs.

The Lun value

CDB

SCSI Class Drivers

Write a SCSI class driver if you have a totally new type of device. A SCSI class driver uses the SCSI port driver. It calls port driver with Device I/O Control IRPs.

A SCSI driver is a standard driver, ie it processes IRPs, creates devices, etc. It maps IRPs to SRBs, limiting the size of transfers for underlying HBAs.

DriverEntry

The DriverEntry routine layers itself over the SCSI port driver and typically has GetInquiryData, ClaimDevice and GetCapabilities routines.

The generic port driver is found using a IoGetDeviceObjectPointer call. Note that this is not an HBA device object.

GetInquiryData uses IOCTL_SCSI_GET_INQUIRY_DATA to get a SCSI_ADAPTER_BUS_INFO structure filled by the port driver. ClaimDevices makes the appropriate physical, logical or virtual devices. It makes a port driver IOCTL_SCSI_EXECUTE_NONE call with an SRB_FUNCTION_CLAIM_DEVICE SRB. It should store the returned pointer to the port driver’s HBA specific device. The driver should use this device pointer to issue all subsequent requests to this device via the port driver.

A GetCapabilities routine then issues a IOCTL_SCSI_GET_CAPABILITIES to get a IO_SCSI_CAPABILITIES structure filled. This reveals various details of the HBA, such as the number of bytes that it can transfer in one operation.

Unload

The unload routine must make an SRB_FUNCTION_RELEASE_DEVICE SRB call to release the device.

Don’t forget to dereference the generic port driver handle.

Dispatch routines

A class driver’s IRP_MJ_CREATE and IRP_MJ_CLOSE handlers usually just return TRUE.

Don’t forget to handle IRP_MJ_SHUTDOWN and IRP_MJ_FLUSH_BUFFERS if you buffer data internally.

You can pass IRP_MJ_INTERNAL_DEVICE_CONTROL and IOCTL_SCSI_PASS_THROUGH requests through to the port driver as long as you set up the PathId, TargetId and Lun fields in the SRB and set the minor function code to IRP_MN_SCSI_CLASS.

Usually class drivers do not have internal queues as the port driver has queues for each LU.

To process read and write requests, have a BuildSRV routine to set up an IRP with an appropriate SRB containing a CDB. The IRP has a IRP_MJ_SCSI function code (actually the same as IRP_MJ_INTERNAL_DEVICE_CONTROL) and Parameters.Scsi.Srb points to the SRB. Allocate nonpaged memory for the SRB either using ExAllocatePool or use lookaside lists.

For request-sense information requests or where retries might be needed, set an IRP completion routine.

Most class drivers need to have a SplitTransferRequest to ensure that read and write requests do not exceed the underlying HBA capabilities. SplitTransferRequest registers an completion routine for each driver-allocated IRP it sends down to the port driver. The completion routine maintains a count of completed partial transfer requests in the original IRP and protects the count with a spin lock.

The completion routine should translate error codes into NTSTATUS values. It should release the SRB memory if appropriate.

If the port driver freezes an LU’s queue following an error, the completion routine calls a ReleaseQueue routine. It should determine the cause of the error and release the queue. Alternatively, if the device media has changed, ReleaseQueue might also clear the queue of any stacked up requests. Use auto-sense requests or SRBs with a SRB_FLAGS_BYPASS_FROZEN_QUEUE flag if you need to bypass a frozen queue. The release or flush IRP must be allocated with from NonPagedPoolMustSucceed memory - this is one of the few times such memory can be requested.

The port driver can handle time-outs requests for the class driver.

SCSI Filter Drivers

Rather than write a whole new SCSI class driver for your slightly weird device, you can write a SCSI Filter Device (SFD) which layers itself over a system SCSI driver.

A filter driver does any special handling of existing IRPs or implementing new IRP_MJ_DEVICE_CONTROL requests.

An SFD must support all the requests that its underlying class driver supports. Usually it just passes these straight through to the underlying driver. If appropriate, it can set a completion routine to do any post-processing.

An SFD can use IoGetConfigurationInformation to find how many devices to intercept. Otherwise grovelling around in the registry might be necessary. It then attaches to the underlying device and sees if it is one that it supports. If not, it should detach itself from the device.

SCSI Tape Miniclass Drivers

To support a new tape device or family of tape devices, write a device-specific tape miniclass driver that calls the system-supplied tape class driver. See the DDK for full details.

Changer Drivers

Changer drivers control jukebox type devices, ie with interchangeable media.

A changer has transporters for moving media from storage slots to data transfer elements. It may also have an import/export element where media can be physically changed, and a door to access all media.

Write a changer miniclass driver to interface with the system changer class driver. It reports the above changer characteristics to the class driver.

See the DDK for full details of the required interface. It ought to support Plug and Play and Power Management requests as well.

User applications need to call the user-mode NT Media Services (NTMS) to change media.