[This is
preliminary documentation and subject to change.]
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 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 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
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.
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.
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
|
© 1999 Microsoft Corporation