Communicating with SCSI devices via Win32 application

[This is preliminary documentation and subject to change.]

SUMMARY

Win32 applications can communicate directly with SCSI devices using the Win32 API DeviceIoControl and specifying the appropriate I/O control code (IOCTL).  Before DeviceIoControl can be used, a valid handle to the device must be obtained.  The handle is obtained using the Win32 API CreateFile.

New for Windows 2000:  When using the SCSI pass through IOCTLs (described below), you must specify an access mode of  (GENERIC_READ | GENERIC_WRITE) in CreateFile or the DeviceIoControl call will fail with ERROR_ACCESS_DENIED (5L).  The access mode is specified in the second parameter to CreateFile, dwDesiredAccess.  Refer to the MSDN documentation for specific information about the Win32 APIs CreateFile and DeviceIoControl.

There are several IOCTLs that the SCSI port driver supports.  Refer to the DDK file NTDDSCSI.H for the most current information regarding the supported IOCTLs.  This sample will demonstrate the following IOCTLs: IOCTL_SCSI_GET_INQUIRY_DATA, IOCTL_SCSI_GET_CAPABILITIES, IOCTL_SCSI_PASS_THROUGH, and IOCTL_SCSI_PASS_THROUGH_DIRECT.

To issue these IOCTLs, the user must be either the administrator or a member of the Administrator's group.  If the user does not have the correct privilege level, the CreateFile call will fail with ERROR_ACCESS_DENIED (5L). 

·         If CreateFile fails with ERROR_ACCESS_DENIED, the user doesn't have the correct privilege level. 

·         If DeviceIoControl fails with ERROR_ACCESS_DENIED, the access mode (GENERIC_READ | GENERIC_WRITE) was not specified in the CreateFile call.

IOCTL_SCSI_GET_INQUIRY_DATA

IOCTL_SCSI_GET_INQUIRY_DATA returns a SCSI_ADAPTER_BUS_INFO structure for all devices that are on the SCSI bus.  The structure member, BusData, is a structure of type SCSI_BUS_DATA.  It contains an offset to the SCSI Inquiry data, which is also stored as a structure, SCSI_INQUIRY_DATA.  See NTDDSCSI.H for more details about these structures. 

Within the SCSI_INQUIRY_DATA structure is a member named DeviceClaimed.  DeviceClaimed indicates whether or not a class driver has claimed this particular SCSI device.  If a device is claimed, all SCSI pass through requests must be sent first through the class driver, which will typically pass the request unmodified to the SCSI port driver.  The class driver is allowed to fail the SCSI pass through request if it desires.  However, the drivers currently shipped by Microsoft simply pass the SCSI pass through requests on to the SCSI port driver.  If the device is unclaimed, the SCSI pass through requests are sent directly to the SCSI port driver.  See Obtaining a handle to a device for more details.

IOCTL_SCSI_GET_CAPABILITIES

IOCTL_SCSI_GET_CAPABILITIES returns an IO_SCSI_CAPABILITIES structure (also found in NTDDSCSI.H).  This structure contains valuable information about the capabilities of the SCSI adapter.  Two items of note are the MaximumTransferLength, which is a byte value indicating the largest data block that can be transferred in a single SRB, and the MaximumPhysicalPages, which is the maximum number of physical pages that a data buffer can span.  The MaximumPhysicalPages indicates how many Scatter/Gather entries the SCSI miniport driver and adapter can support.

To determine the largest transfer that an adapter can handle, you have to first convert the MaximumPhysicalPages into a byte value that we’ll refer to here as MaximumPhysicalPages_in_bytes.  Subtract one from MaximumPhysicalPages and then multiply this value by the system PAGE_SIZE (defined in NTDDK.H) to get the MaximumPhysicalPages_in_bytes.  Then the largest transfer that can be issued on an adapter is the smaller of MaximumTransferLength and MaximumPhysicalPages_in_bytes.

For example, assume a system has PAGE_SIZE = 4KB, and the SCSI capabilities indicate MaximumTransferLength = 16MB and MaximumPhysicalPages = 17.  Then, the largest transfer on this adapter would be 64KB based on the following calculations:

MaximumPhysicalPages – 1 = 17 – 1 = 16 * PAGE_SIZE = 16 * 4096 = 64KB

largest transfer = min(MaximumTransferLength, MaximumPhysicalPages_in_bytes) = min(16MB, 64KB) = 64KB

IOCTL_SCSI_PASS_THROUGH and IOCTL_SCSI_PASS_THROUGH_DIRECT

The two IOCTLs IOCTL_SCSI_PASS_THROUGH and IOCTL_SCSI_PASS_THROUGH_DIRECT allow SCSI CDBs (Command Descriptor Blocks) to be sent from a Win32 application to a SCSI device.  Depending on which IOCTL is sent, a corresponding pass through structure is filled out by the Win32 application.  IOCTL_SCSI_PASS_THROUGH uses the structure SCSI_PASS_THROUGH.  IOCTL_SCSI_PASS_THROUGH_DIRECT uses the structure SCSI_PASS_THROUGH_DIRECT.  See NTDDSCSI.H for more details about these structures.

The structures SCSI_PASS_THROUGH and SCSI_PASS_THROUGH_DIRECT are virtually identical.  The only difference is that the data buffer for the SCSI_PASS_THROUGH structure must be contiguous with the structure.  This structure member is called DataBufferOffset and is of type ULONG.  The data buffer for the SCSI_PASS_THROUGH_DIRECT structure does not have to be contiguous with the structure.  This structure member is called DataBuffer and is of type PVOID.  This is the only difference between the two structures.

IOCTL_SCSI_PASS_THROUGH and IOCTL_SCSI_PASS_THROUGH_DIRECT are not substitutes for a SCSI class driver and should only be used for infrequent I/O processing.  For situations in which frequent SCSI commands need to be sent to a device, with strict error processing and high performance requirements, the best solution is to write a SCSI class driver that will send these CDBs to the respective devices.  SCSI pass through requests are handled synchronously by the SCSI port driver, even if the Win32 application has the proper code to handle the requests as overlapped I/O.

Obtaining a handle to a device

Before any IOCTLs can be sent to a SCSI device, a handle for the device must be obtained.  The Win32 API CreateFile is used to obtain this handle and to define the sharing mode and the access mode.  The access mode must be specified as (GENERIC_READ | GENERIC_WRITE).  The key to obtaining a valid handle is to supply the proper filename for the device that is to be opened.  It is possible to obtain a handle to the device via either the SCSI port driver or the appropriate SCSI class driver, depending on whether the device was claimed (see the DeviceClaimed information returned from IOCTL_SCSI_GET_INQUIRY_DATA). 

If the device is claimed by a class driver, then you have to specify a filename that will route requests through the class driver.  In the case of fixed disks and optical storage devices, the filename is typically the corresponding drive letter.  "\\.\I:" can be used to obtain a handle to a CD-ROM drive that has been mapped to driver "I:".  In the case of SCSI printers, the filename is "LPTn", where n = 1, 2, etc.  For all remaining SCSI devices, the SCSI class driver defines the appropriate name.  Typically the name is related to the device (e.g. - "\\.\Scanner0", "\\.\Tape1").  Except for COMn and LPTn, all device filenames must be prepended with a \\.\ to inform the I/O Manager that this is a device and not a standard file name.

If the device is unclaimed by a SCSI class driver, then a handle to the SCSI port driver is required.  The filename in this case is "\\.\ScsiN:", where N = 0, 1, 2, etc.  The number N corresponds to the SCSI host adapter card number that controls the desired SCSI device.  When the SCSI port name is used, the Win32 application must set the proper PathId, TargetId, and Lun in the SCSI pass through structure.  Failure to set this SCSI address information will cause the SCSI request to fail (if directed to a target device claimed by a class driver) or possibly cause data corruption (if the unexpected target device processed the I/O request).

If the device is claimed by a class driver but a handle to the SCSI port driver is used, the DeviceIoControl call will fail the IOCTL_SCSI_PASS_THROUGH and IOCTL_SCSI_PASS_THROUGH_DIRECT with ERROR_INVALID_PARAMETER (87L).  The Win32 API QueryDosDevice can be used to display the symbolic links between the Win32 device names and their corresponding kernel mode names.  If you are unsure of which device name to specify for CreateFile, one should be able to examine the output from QueryDosDevice to determine the correct name.

Initializing the structures

Once a valid handle is obtained to a SCSI device, then appropriate input and output buffers for the requested IOCTL must be allocated and, in some cases, filled in correctly.  This section describes general guidelines for setting up the parameters for the DeviceIoControl call. 

For IOCTL_SCSI_GET_INQUIRY_DATA, no data is sent to the device - data is only read from the device.  Set lpInBuffer to NULL and nInBufferSize to zero.  The output buffer might be quite large, as each SCSI device on the bus will provide data that will fill three structures for each device:  SCSI_ADAPTER_BUS_INFO, SCSI_BUS_DATA, and SCSI_INQUIRY_DATA.  Allocate a buffer that will hold the information for all the devices on that particular SCSI adapter.  Set lpOutBuffer to point to this allocated buffer and nOutBufferSize to the size of the allocated buffer.

For IOCTL_SCSI_GET_CAPABILITIES, no data is sent to the device - data is only read from the device.  Set lpInBuffer to NULL and nInBufferSize to zero.  Set lpOutBuffer to a pointer to hold the IO_SCSI_CAPABILITIES structure, and nOutBufferSize to sizeof(IO_SCSI_CAPABILITIES).  A larger output buffer can be used, but the output buffer length must be set as described here.

For the two SCSI pass through IOCTLs, IOCTL_SCSI_PASS_THROUGH and IOCTL_SCSI_PASS_THROUGH_DIRECT, both lpInBuffer and lpOutBuffer can vary in size depending on the Request Sense buffer size and the data buffer size.  In all cases, nInBufferSize and nOutBufferSize must be at least the size of the SCSI_PASS_THROUGH (or SCSI_PASS_THROUGH_DIRECT) structure.  If the SCSI port driver detects that one of the two buffers is too small, then the DeviceIoControl API will fail with ERROR_INVALID_PARAMETER. 

Once the appropriate input and output buffers have been allocated, then the appropriate structure must be initialized.  The SCSI_PASS_THROUGH structure is defined in NTDDSCSI.H as follows:

typedef struct _SCSI_PASS_THROUGH {

    USHORT Length;

    UCHAR ScsiStatus;

    UCHAR PathId;

    UCHAR TargetId;

    UCHAR Lun;

    UCHAR CdbLength;

    UCHAR SenseInfoLength;

    UCHAR DataIn;

    ULONG DataTransferLength;

    ULONG TimeOutValue;

    ULONG DataBufferOffset;

    ULONG SenseInfoOffset;

    UCHAR Cdb[16];

}SCSI_PASS_THROUGH, *PSCSI_PASS_THROUGH;

The Length is the size of the SCSI_PASS_THROUGH structure.  The ScsiStatus should be initialized to 0.  The SCSI status of the requested SCSI operation is returned in this structure member.  The possible SCSI statuses are defined in SCSI.H and are of the form SCSISTAT_xxx. The PathId is the bus number for the SCSI host adapter that controls the SCSI device in question.  Typically, this value will be 0, but there are SCSI host adapters that have more than one SCSI bus on the adapter.  The TargetId and Lun are the SCSI ID number and logical unit number for the device.  If the handle was obtained for a claimed device, then the PathId, TargetId and Lun as defined in this structure will be ignored and the appropriate class driver will provide this SCSI address information.  If the handle was obtained for the SCSI port driver, then the PathId, TargetId and Lun must be correct for the device intended. 

The CdbLength is the length of the CDB. Typical values are 6, 10, and 12 up to the maximum of 16.  The SenseInfoLength is the length of the SenseInfo buffer.  DataIn has three possible values which are defined in NTDDSCSI.H;  SCSI_IOCTL_DATA_OUT, SCSI_IOCTL_DATA_IN and SCSI_IOCTL_DATA_UNSPECIFIED.  SCSI_IOCTL_DATA_UNSPECIFIED should be used only if the appropriate SCSI miniport driver supports its usage.  The DataTransferLength is the byte size of the data buffer.  The TimeOutValue is the length of time, in seconds, until a time-out error should occur.  This can range from 0 to a maximum of 30 minutes (108000 seconds). 

The DataBufferOffset is the offset of the data buffer from the beginning of the pass through structure.  For the SCSI_PASS_THROUGH_DIRECT structure, this value is not an offset, but rather is a pointer to a data buffer.  The SenseInfoOffset is similarly an offset to the SenseInfo buffer from the beginning of the pass through structure. Finally, the sixteen remaining bytes are for the CDB data.  The format of this data must conform to the SCSI-2 standard as defined by ANSI.

Buffer Alignment

The AlignmentMask member returned in the structure via the IOCTL_SCSI_GET_CAPABILITIES indicates the buffer alignment requirements of the SCSI adapter.  Buffer alignment is handled using two methods for assuring that buffers are aligned on the appropriate boundaries. 

The first method uses the compiler to align the buffer on the correct boundary.  The structure SCSI_PASS_THROUGH_WITH_BUFFERS contains a member, Filler that is of type ULONG.  The compiler aligns Filler on a ULONG (double word) boundary.  The structure member that follows Filler, ucSenseBuf, is also aligned on a double word boundary.  The ucSenseBuf array is of a size that is a multiple of a double word, and so this makes the last structure member, ucDataBuf, also begin on a double word boundary.

A second method to ensure buffer alignment is demonstrated in the AllocateAlignedBuffer procedure.  This procedure depends on the fact that a buffer aligned on a certain boundary will have 0's in its least significant bits indicating the buffer alignment.  A buffer allocation request is made using the C runtime call (malloc) that is the size of the requested buffer plus the AlignmentMask value as returned by IOCTL_SCSI_GET_CAPABILITIES.  A pointer is manipulated so that it is pointing to the first possible address in the buffer that meets the alignment requirements.

Direct versus Buffered

Here's the big difference between IOCTL_SCSI_PASS_THROUGH versus IOCTL_SCSI_PASS_THROUGH_DIRECT: the SCSI data is transferred directly into the specified ucDataBuf for IOCTL_SCSI_PASS_THROUGH_DIRECT while the data will be buffered in an intermediate I/O manager allocated buffer for IOCTL_SCSI_PASS_THROUGH.

The various IOCTLs that are demonstrated in this sample are defined by the SCSI port driver (see NTDDSCSI.H for details).  The memory buffering for these IOCTLs is METHOD_BUFFERED.  What this means is that the I/O manager examines the length lpInBuffer and the length of lpOutBuffer as defined by the DeviceIoControl parameters and allocates a contiguous buffer that is the size of the larger of the two buffers.  On entry to the I/O manager (i.e. the call to DeviceIoControl), the contents of the Win32 application's lpInBuffer is copied to the buffer that was allocated by the I/O manager.  The nInBufferSize controls the amount of data copied.  The I/O manager then calls the appropriate SCSI class driver's (or port driver's) DeviceIoControl routine passing it the new buffer.  On exit, the reverse process occurs and the data in the new buffer is copied back into the Win32 application's lpOutBuffer.  This behavior is not user configurable.

When using IOCTL_SCSI_PASS_THROUGH, the SCSI data buffer (ucDataBuf) is part of lpInBuffer.  Therefore, the data in ucDataBuf is copied into the new buffer (allocated by the I/O manager) and presented to the SCSI port driver and the SCSI miniport driver.  On completion, the new buffer is copied back to the ucDataBuf.

When using IOCTL_SCSI_PASS_THROUGH_DIRECT, the SCSI data buffer (ucDataBuf) is not part of lpInBuffer.  Recall that the SCSI_PASS_THROUGH_DIRECT has a PVOID DataBuffer member.  That is, DataBuffer is a pointer to the data buffer, and the data buffer does not have to be contiguous with the SCSI_PASS_THROUGH_DIRECT structure.  When the Win32 application calls DeviceIoControl, the lpInBuffer is copied into an I/O manager allocated buffer, and this new buffer is passed to the SCSI port driver.  In the SCSI port driver, the SCSI_PASS_THROUGH_DIRECT structure is examined, the pointer to the DataBuffer is retrieved from the structure, and the user's buffer is probed and locked for direct access. 

Running the SPTI.EXE sample

Two command line parameters can be used with SPTI.EXE.

The first parameter is mandatory.  It is the name of the device to be opened.  Typical values for this are drive letters such as "C:", or device names as defined by a class driver such as Scanner0, or the SCSI port driver name, ScsiN:, where N = 0, 1, 2, etc.  See Obtaining a handle for a device for details on this first parameter.

The second parameter is optional and is used to set the share mode (note that access mode and share mode are different things) and sector size.  The default share mode is (FILE_SHARE_READ | FILE_SHARE_WRITE) and the default sector size is 512.  A parameter of "r" changes the share mode to only FILE_SHARE_READ.  A parameter of "w" changes the share mode to only FILE_SHARE_WRITE.  A parameter of "c" changes the share mode to only FILE_SHARE_READ and also changes the sector size to 2048.  Typically, a CD-ROM device would use the "c" parameter. 

The examples below assume that there is a system with a disk drive "c:", CD-ROM "d:", and an unclaimed SCSI device on the second SCSI adapter (scsi1:).

spti c:

To send SCSI commands to the disk drive.

spti d: c

To send SCSI commands to the CD-ROM device.  Remember that CD-ROM devices typically use 2048 sector size, so the "c" parameter should be used.

spti scsi1:

To send SCSI commands to the unclaimed SCSI device.  Make sure that the SCSI pass through structure has the correct PathId, TargetId and Lun, filled in.  If the SCSI address information is incorrectly set, the request might fail or be directed towards the wrong device. 

BUILDING THE SAMPLE

Click the Free Build Environment or Checked Build Environment icon under your Development Kits program group to set basic environment variables needed by the build utility.

Change to the directory containing the device source code.

To build the sample, run build -ceZ, or use the macro BLD.  The "e" option produces a log file (buildXXX.log), a warning file (buildXXX.wrn), and an error file (buildXXX.err) where XXX means either fre or chk, depending on the environment chosen.  If there are no warnings, then the warning file will not be built and if there are no errors, there will be no error file built.    If the build succeeds, the Win32 application spti.exe will be placed in a subdirectory of either the objfre (for free build) or objchk (for checked build) directories.

No INF file is needed to install this application.

CODE TOUR

File Manifest

File           Description
 
 
spti.htm       The documentation for these samples (this file)
spti.c         Implements the Win32 application to communicate with the SCSI devices.
spti.h         Header file for spti.c
Sources        DDK build instructions

 

 

Top of page

 

 

 
 

© 1999 Microsoft Corporation