Channels ▼
RSS

.NET

Writing Portable Win32 SCSI Applications

Source Code Accompanies This Article. Download It Now.


Dr. Dobb's Journal December 1997: Writing Portable Win32 SCSI Applications

The WinASPI 32-bit DLL emulator module makes it possible

Thomas is a software engineer for Sequoia Advanced Technologies, a supplier of SCSI and IEEE-1394 system software. He can be reached at thomas.tewell@seqadvtech.com or http://www.seqadvtech.com/.


During the course of developing our "WinSCSI-32 API," a developer's tool that complements and enhances the SCSI programming interfaces of Windows 95 and Windows NT, we discovered that the native SCSI APIs for the two platforms are very different from one another, making portable Win32 SCSI programming rather difficult. Windows 95 ships with a WinASPI 32-bit DLL while Windows NT does not. While some SCSI host adapter vendors do provide a WinASPI DLL for Windows NT, many more do not. Instead, Windows NT SCSI application developers must rely on the native SCSI Pass Through Interface (SPTI) mechanism. In response, we developed ASPIEMU, the WinASPI 32-bit DLL emulator module presented here.

Win32 SCSI Programming

Windows 95 provides you with a variety of options for SCSI programming. For MS-DOS applications, you can use either ASPI for DOS or the ANSI standard Common Access Method (CAM). For 16-bit Windows applications, you use the 16-bit WINASPI.DLL. For 32-bit Windows applications (Win32), you use the 32-bit WNASPI32.DLL. Since this interface is provided under Windows 95 as the 32-bit mechanism for accessing SCSI devices from the application level, you would naturally think that this mechanism would also be available for Windows NT. Indeed, if you read the Win32 ASPI specification provided with the Windows 95 DDK (not SDK!), you'll find the following paragraph:

With the arrival of Windows NT and Windows 95, ASPI must be extended to allow applications to take full advantage of these new, 32-bit operating systems. This specification describes the Advanced SCSI Programming Interface (ASPI) for the Win32 environment.

For Windows NT, however, the WNASPI32.DLL is conspicuously absent. In its place is a set of DeviceIoControl() function calls described in SPTI.C, an obscure sample application provided in the Windows NT DDK. The documentation for the functions invoked by the SCSI Pass Through Interface (SPTI) is a file called README.TXT that resides in the same directory. SPTI is difficult to follow and even led us off course in a couple of areas.

The differences between the calls to the ASPI DLL and the NT SPTI are substantial enough to warrant the development of an ASPI emulation module so that WinSCSI32 can be made portable. Since WinSCSI32 is a tool used by programmers to speed SCSI software development, we felt that two versions of the WinSCSI-32 DLL -- one for Windows 95 and another for Windows NT -- was unacceptable. Thus ASPIEMU was born.

The WinASPI-32 DLL Emulation Module

WinSCSI-32 issues SCSI requests through the WinASPI DLL mechanism. Since we did not want to rewrite every single function (there are over 150), we decided to emulate the WinASPI-32 DLL under NT.

This involved creating SendASPI32Command(), a master SCSI-command execution function. Notice that this is the same name used by the "real" WinASPI DLL. SendASPI32Command() dynamically determines whether the machine is running Windows 95 or NT, and automatically routes the ASPI request to WNASPI32.DLL or the SCSI Pass Through Interface (SPTI), respectively.

The first time the ASPIEMU module is called, the code in Listing One determines whether Windows 95 or NT is running. If it detects the Windows 95 environment, the program loads WNASPI32.DLL via LoadLibrary("WNASPI32.DLL"). The code fpAspiAddress= (WinAspiCommand)GetProcAddress(hWinAspi, "SendASPI32Command"); then gets the address of the ASPI SendASPI32Command(). We then set a flag to True indicating that all subsequent ASPI requests will be routed to the WNASPI32.DLL.

Under Windows NT, we emulate the ASPI DLL function SendASPI32Command by converting the ASPI request to SPTI and sending it off to SPTI. This job of converting the ASPI request to a SPTI request belongs to the function Aspi2Spti(). The SPTI command is executed via the function called, logically, ExecuteScsiRequest(). Once SPTI executes the SCSI command, the status must be reflected back to the original ASPI request packet. This is the job of Spti2Aspi(). The core of the ASPIEMU module, however, are FindDevices() and ReconcileDevices().

ASPIEMU Initialization

If the ASPIEMU module is running under Windows NT, the first time the ASPIEMU SendASPI32Command() function is called, it in turn calls the initialization functions FindDevices() and ReconcileDevices().

These functions build the local memory structures used by ASPIEMU to execute emulated requests. The first step during initialization is to determine how many SCSI devices NT has enumerated. Typically, you would issue SCSI-inquiry commands in path, target ID, and LUN succession requesting the SCSI devices to report their identities. SPTI, however, provides a convenient IOCTL function (IOCTL_SCSI_GET_INQUIRY_DATA) that instructs the SCSI-port driver to fill in a user-supplied buffer containing a list of all the SCSI devices that that specific port is supporting. Remember that there is often more than one SCSI adapter installed in an NT system. In fact, since NT treats IDE and ATAPI devices as SCSI devices, there will be at least two SCSI "ports" in a system with only a single genuine SCSI host adapter.

The SPTI workhorse is DeviceIoControl(). Since DeviceIoControl() requires a file handle to direct the request to the appropriate device driver, what file handle do you provide? To get the handle to the SCSI-port driver, you open a file called "\\.\SCSIx:", where "x" is the SCSI-port number, which is a zero-based index specifying a SCSI-miniport driver that the SCSI-port driver routes the request to. A SCSI-miniport driver is the lowest level of the SCSI-driver stack under Windows NT. It directly programs the SCSI host adapter to perform the request. Consequently, opening the SCSI port to send a command looks like Listing Two, which is in a loop where AdapterCount is incremented on each iteration.

Listing Three shows the code used to issue the inquiry data IOCTL request, while the data returned by IOCTL_SCSI_GET_INQUIRY_ DATA is formatted by the code in Listing Four.

Since all of the inquiry data for all of the devices are lumped together in the InquiryBuffer, we decided to write FetchInquiryData(), which returns a pointer to the specific Inquiry data structure indicated by a passed device index. This zero-based index allows us to form a device loop that parses the SCSI_INQUIRY_DATA device-by-device until there are no more SCSI_INQUIRY_DATA structures in the buffer. ASPIEMU then uses these individualized SCSI_INQUIRY_DATA structures to build its internal ASPI_ENTRY structures. SCSI target IDs and LUNs that are not recognized by the SCSI-port driver as connected to (or powered on) the SCSI bus do not have a SCSI_INQUIRY_DATA structure allocated to them.

Once a SCSI device is identified, we build a structure called an ASPI_ENTRY_STRUCTURE -- the master structure ASPIEMU uses to manage a request to and from a SCSI device. Each new ASPI_ENTRY structure is filled in by the CreateNewAspiEntry() function; see Listing Five. The SCSI-port and SCSI-device looping in Listing Five is performed (with an ASPI_ENTRY structure being created for each SCSI device) until there are no more SCSI ports. The hFileHandle entry in the ASPI_ENTRY structure represents the file handle for the SCSI port that manages the SCSI device. At this point, it would seem logical that we could start issuing SPTI SCSI requests to the SCSI-port driver using hFileHandle as the DeviceIoControl() file handle.

But, of course, it isn't quite that simple. If a class driver (such as a disk or CD-ROM driver) is controlling the SCSI device, then you will find that the fDeviceClaimed Boolean in the ASPI_ENTRY structure will be True. This value comes directly out of the SCSI port's SCSI_INQUIRY_DATA structure and is reflected into our ASPI_ENTRY structure. If the SCSI device is claimed by a class driver, the SCSI-port driver will refuse all SPTI SCSI requests by terminating the request with an error.

At this point, we were about ready to throw our hands up and write a low-level device driver for WinSCSI-32 to communicate with when we noticed an obscure Win32 function (NT specific) called QueryDosDevice(). This function returns a list of symbolic name links created by device drivers from the Windows NT object namespace. (For example, recall that when we opened the SCSI port driver using the CreateFile() function, we used the symbolic name link "SCSIx".)

Typical symbolic names are "C:", "CdWriterx", "Scannerx", "Tapex", and so on, where "x" is the zero-based unit number designating a particular unit. How do we correlate these symbolic names with a file handle that we will use to execute SCSI requests? We wrote ReconcileDevices() and FindFileHandle() to do just that.

To get the symbolic name list, we use size=QueryDosDevice(NULL,buffer,1024);, where 1024 is the number of bytes of symbolic name information to return. A buffer size of 1024 was more than enough to hold the symbolic name list on all of the NT systems we tested.

For each SCSI device ASPIEMU supports, we march through the list of symbolic names, open the device drivers specified by each symbolic name, issue the IOCTL_SCSI_GET_ADDRESS request to the driver specified by the symbolic name, and compare the port number, target ID, and LUN with the returned data. If the values match, we have the handle to the class driver that is claiming the device. We then store that file handle in the ASPI_ENTRY structure. All "unclaimed" SCSI devices are obviously managed by the already existing SCSI-port file handle.

Listing Six illustrates the process of opening the class driver. The reason for opening the device Read/Write first and then Read Only in Listing Six is pretty clear: Some devices, most notably CD-ROM, cannot be written to, so the open Read/Write function fails. This is also why CD-writer devices and CD-ROM devices have different symbolic names.

Once we have opened a device, we ask it to return the address of the SCSI device it is managing via the specified symbolic-name file handle; see Listing Seven. For most of the symbolic names in the list, there are no SCSI devices associated with the driver. Another interesting note about ReconcileDevices() is the use of SetErrorMode(). One of our customers noticed that sometimes a blue system box appeared on the screen under Windows NT when issuing SCSI commands to the WinSCSI-32 API DLL. This happened because there were no media in the floppy drives "A:" or "B:" when CreateFile() was called to open the device driver. By setting the error mode to SEM_NOOPENFILEERRORBOX, the blue box disappeared. We set the error mode back to its original value using SetErrorMode().

Executing SCSI Requests

Now that we have the file handles to execute SCSI requests, we are ready to start issuing SCSI requests. Then we will convert the ASPI packet to an SPTI packet. The conversion is straightforward and is handled by Aspi2Spti(); see Listing Eight. As Listing Nine shows, the execution of the SCSI request is also straightforward.

Once the command has been executed, determining the status of the ASPI packet is handled by Spti2Aspi(). We convert the return codes from SPTI to an appropriate ASPI code and copy the Sense data to the ASPI packet, if necessary.

Upon studying the ASPIEMU source code (available electronically; see "Availability," page 3), you should find that once you get by the file-handle logic, the execution is quite simple. To use this code in your applications, simply link the ASPIEMU module in with your application and call SendAspi32Command(). ASPIEMU will automatically dispatch the request to the appropriate interface, and your SCSI application becomes portable across Windows 95 and Windows NT. This module is used in exactly this form in the commercially shipping WinSCSI-32 API library.

Calling SendAspi32Command() is identical to calling the WinASPI32 DLL: Build the ASPI request packet and call SendAspi32Command(). (See Listing Ten.)

For improvements, you might try adding multithreaded support, common registry functions, and the like. In any case, we hope you find ASPIEMU as useful as we have.


Listing One

if(GetVersion() < 0x8000000){
    Windows NT
}
else
{
    Windows 95
}

Back to Article

Listing Two

//-------------------------------------------------------------------// Open SCSI Port Driver
//-------------------------------------------------------------------
wsprintf(adapter_name,"\\\\.\\SCSI%d:",AdapterCount);
fileHandle = CreateFile(adapter_name, GENERIC_WRITE | GENERIC_READ,
          FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);

Back to Article

Listing Three

status = DeviceIoControl(fileHandle, IOCTL_SCSI_GET_INQUIRY_DATA, NULL, 0,                                    InquiryBuffer, 2048, &returned, FALSE);

Back to Article

Listing Four

UCHAR NumberOfBuses;

</p>
// Each structure allocated contiguously for each bus 
// indicated in NumberOfBuses
SCSI_BUS_DATA BusData[x]; 


</p>
// Each structure allocated contiguously for each bus indicated in
// BusData[x].NumberOfLogicalUnits
SCSI_INQUIRY_DATA DeviceData[x];  // one structure for each device indicated 
                                  // in SCSI_BUS_DATA

Back to Article

Listing Five

typedef struct _ASPI_ENTRY {    SCSI_PASS_THROUGH_DIRECT Srb;    
    ULONG Filler;
    BYTE ucSenseBuf[32];
    HANDLE hFileHandle;
    BYTE byDeviceType;
    BYTE byPath;
    BYTE byTarget;
    BYTE byLun;
    BOOL fDeviceClaimed;
    struct _ASPI_ENTRY *AEForwardPtr;
    struct _ASPI_ENTRY *AEBackwardPtr;
} ASPI_ENTRY, *PASPI_ENTRY;


</p>


</p>

Back to Article

Listing Six

strcpy(devname,"\\\\.\\");strcat(devname,bufptr);
bufptr += strlen(bufptr);
++bufptr;


</p>
fileHandle = CreateFile(devname, GENERIC_WRITE | GENERIC_READ,
           FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
//---------------------------------------
// if device won't open, try read only...
//---------------------------------------
if(fileHandle == INVALID_HANDLE_VALUE)
{
    fileHandle = CreateFile(devname, GENERIC_READ, FILE_SHARE_READ, NULL,
                                                     OPEN_EXISTING, 0, NULL);
}

Back to Article

Listing Seven

status = DeviceIoControl(fileHandle, IOCTL_SCSI_GET_ADDRESS, NULL, 0, &sa,                                 sizeof(SCSI_ADDRESS), &returned, FALSE);

Back to Article

Listing Eight

typedef struct _SCSI_PASS_THROUGH_DIRECT {    USHORT Length;
    UCHAR ScsiStatus;
    UCHAR PathId;
    UCHAR TargetId;
    UCHAR Lun;
    UCHAR CdbLength;
    UCHAR SenseInfoLength;
    UCHAR DataIn;
    ULONG DataTransferLength;
    ULONG TimeOutValue;
    PVOID DataBuffer;
    ULONG SenseInfoOffset;
    UCHAR Cdb[16];
}SCSI_PASS_THROUGH_DIRECT, *PSCSI_PASS_THROUGH_DIRECT;

Back to Article

Listing Nine

return(DeviceIoControl(pae->hFileHandle, IOCTL_SCSI_PASS_THROUGH_DIRECT,                &pae->Srb, sizeof(ASPI_ENTRY), &pae->Srb,
                sizeof(ASPI_ENTRY), &returned, FALSE) ? 0 : GetLastError());

Back to Article

Listing Ten

if((ASPICompletionEvent = CreateEvent(NULL,FALSE,FALSE,NULL)) == NULL){
    return(ERROR_RESOURCE_FAILURE);
}
de->AspiSrb.SRB_Flags      = SRB_DIR_IN;
de->AspiSrb.SRB_BufLen     = INQUIRY_DATA_SIZE;
de->AspiSrb.SRB_BufPointer = (BYTE *)&de->InquiryData;
de->AspiSrb.SRB_CDBLen     = 6;
de->AspiSrb.CDBByte[0]     = 0x12;
de->AspiSrb.CDBByte[1]     = (de->AspiSrb.SRB_Lun << 5);
de->AspiSrb.CDBByte[2]     = 0;
de->AspiSrb.CDBByte[3]     = 0;
de->AspiSrb.CDBByte[4]     = INQUIRY_DATA_SIZE;
de->AspiSrb.CDBByte[5]     = 0;
de->AspiSrb.SRB_Status     = SS_PENDING;
de->AspiSrb.SRB_Flags     |= SRB_EVENT_NOTIFY;
de->AspiSrb.SRB_PostProc   = ASPICompletionEvent;
de->AspiSrb.SRB_Time_Out   = de->TimeOut;


</p>
SendASPI32Command(&de->AspiSrb);


</p>
if(de->AspiSrb.SRB_Status == SS_PENDING)
{
    WaitForSingleObject(ASPICompletionEvent, INFINITE);
}
    CloseHandle(ASPICompletionEvent);

Back to Article

DDJ


Copyright © 1997, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video