Programming Conventions

Before we begin, I'm going to be using some notations for 32 bit architectures that could be intuitive, or not. Here's a table of them:

Type Size in Bytes Description
Void N/A Nothing; No return or function arguments
Byte 1 Unsigned 8-bit value
Word 2 Unsigned 16-bit value
DWord 4 Double Word; Unsigned 32-bit value
LPVoid 4 Generic 32-bit Pointer
LPByte 4 32-bit Pointer to an array/buffer of BYTEs.
LPSRB 4 Generic SRB Pointer. SRB is a SCSI Request Block.
*Note: The 'LP' notation represents a "Long Pointer".

ASPI Functions -- WNASPI32.DLL

Once we know that, we can get into ASPI functions. As you most likely already know, any PC with a SCSI card needs to have the ASPI Layer installed for proper working action with many SCSI applications, like CDRWin and Fireburner. Once this ASPI Layer is installed, you'll have a WNASPI32.DLL in your windows/system. For those of you still using Win 3.1 and will be using winaspi.dll, you're on your own. There's many tricks for buffer locking that need to take place that probably isn't worth my time going into. So, I'll be concentrating on 32-bit windows, which includes the Win9x and Win2k like of OSs.

WNASPI32.DLL has 5 functions that your program can create hooks into (also called "Entry Points"). These and their uses are:

Entry Point Description
GetASPI32SupportInfo Will initialize ASPI, and return basic information
SendASPI32Command Submits SCSI Request Block (SRB) to ASPI for execution.
GetASPI32Buffer Allocates a buffer for data
FreeASPI32Buffer Releases buffers allocated by GetASPI32Buffer
TranslateASPI32Address Translates the standard ASPI HostAdapter/ID/LUN address triples to/from Win95 DEVNODEs.

Those descriptions are severely lacking, but they fit nicely into a table so I thought I'd use them. Now I'll step through each one and explain what the function arguments are and what their return values are.

DWORD GetASPI32SupportInfo ( VOID );

This function needs no parameters, and returns a single 32-bit Double Word in response.

Response Field 31.....16|15......8|7.......0 _____________________________ | MBZ | Status | Host | | | Code | Adapter| | | | Count | \--------+---------+--------/

MBZ is Must Be Zero. Any properly working ASPI layer will set bits [31:16] to 0x0h. To understand the Status Code, we must go back to one of the header files I mentioned before, wnaspi32.h. If the call is successful, the Status Code returned will either be SS_COMP or SS_NO_ADAPTERS. If SS_NO_ADAPTERS is returned, then the function call properly initialized ASPI, but no Host Adapters were found.

Now, if the function called failed, there could be any number of errors. Peruse the "SRB Status" section of wnaspi32.h for the list. So far, so good!

This next function is where the real work happens. SendASPI32Command( LPSRB ) is the means of telling the SCSI device what to do. Before you continue, take a moment to look at the "ASPI Command Definitions" section of wnaspi32.h.

SendASPI32Command() requires a pointer to a SCSI Request Block, or SRB, which it will use to decode what exactly you're passing along to the SCSI Device. These SRBs are defined exactly, so they must be byte packed. Often, compilers such as Microsoft Visual C++ and Borland/Inprise C++ won't pack them in to help with speed. Here's why:

Let's say you've got a C struct that contains 2 BYTEs and 1 DWORD.

typedef struct
BYTE atapiBYTEs;
BYTE firewireBYTEs;
} MassStorage;

When this struct is used in a program, Visual C++ will optimize for speed and put everything on DWORD (32-bit) boundaries, since Intel processors (386 and up) are 32-bit machines. It will pad the actual data with junk so the processor can use it quicker: (the |, or pipes, are on 8-bit or Byte boundaries)

| junk0 | junk1 | junk2 | atapiBYTEs |
| junk0 | junk1 | junk2 | fireiwireBYTEs |
| DWordUp |

Why is this quicker? Well, because of the way that ia32 has evolved from the 16-bit 8086, and Intel's desire for programming and binary compatibility, the original 16-bit 8086 AX register is the lower 16-bits of the 32-bit register, EAX. Because a lot of flow control was done on byte boundaries, the 8086 could access the high 8-bits or the lower 8-bits of the AX register by checking out the AH and AL registers. Since 32-bit programs can still access the AH and AL registers, MSVC++ will align them with those register locations. Your Pentium III will still load the entire DWORD, but the program will only use what lines up with the AL register. Clever, huh? If the compiler didn't do this, in order to read junk1, the processor would have to perform a right shift by 16-bits of the register for it to line up with an 8-bit boundary.

Unfortunately, ASPI doesn't like this. If it expects the first BYTE to be the command, and the next byte to be the status, and the 3rd byte to be the Host Adapter's ID (see struct SRB_ExecSCSICmd in wnaspi32.h), and the compiler decided to seperate those, the ASPI Layer would die a horrible death. ASPI can't guess what the compiler is going to do on the binary level, so it has to specify it. So, be sure to tell the compiler to byte-pack the SRB definitions with the following code:

// Pack(1) in MSVC++
#pragma pack(1)
// Then turn off packing
#pragma pack()

Piece of cake. In fact, depending on the completeness of a wnaspi32.h you have, all of this is already setup for you in the header file, and surrounded by #ifdef statements depending on which C/C++ compiler you're using.

Anyway, back to SendASPI32Command(). You must first determine what kind of command you'd like to send. According to wnaspi32.h, you can:

1) Query ASPI for Host Adapter Information.
2) Get device type for a SCSI target.
3) Send a SCSI Command (CDB - Command Descriptor Block) to a SCSI target.
4) Abort a SRB.
5) Send a Bus Device Reset to a SCSI target.
6) Return BIOS information for a SCSI target (Win9x only).
7) Rescan the SCSI Bus.
8) Change the Timeout values for commands.

Each of these different functions takes a different command struct. I could examine them all, but I have a feeling that those who are really interested would only read a couple then try to do the rest on their own, and those that aren't interested would only skip over the rest... so I'll do number 1, Query ASPI for Host Adapter Information, then number 2, getting device type for a SCSI target.

But, I'm getting ahead of myself. We still have 3 more WNASPI32.DLL functions to discover!


This function will allocate some storage space for us in Virtual Memory. Normally we could just use VirtualAlloc, but sometimes the space is so physically fragmented that it'll make transfer on bus-mastering host adapters impossible. So, we can use this function. PASPI32BUFF is a pointer to the ASPI32BUFF struct in wnaspi32.h. It needs a DWORD containing the length in bytes of the buffer, a ZeroFill flag that ASPI will use to determine if it should zero fill the buffer (otherwise the buffer will hold whatever data was last put in that spot), and a Reserved DWORD which MBZ. If the function call returns a TRUE (1), then there's a section in the PASPI32BUFF struct that will have the address to the block/array of bytes reserved for your use. If you were writing a CD burning program, you'd create ASPI32Buffers to hold the data you wish to write to CD.


This function takes the same struct as GetASPI32Buffer, but the ZeroFill and Reserved are both MBZ. Now you must set the address that you were originally given plus the size of the buffer/array in bytes that you requested so ASPI can release it back to the OS. If the address or the size is not exactly correct, the function will fail. You've got to keep track of that information in your program!

BOOL TranslateASPI32Address( PDWORD pTriple, PDWORD pDEVNODE )

Here we're getting into some Windows-specific Plug-n-Play topics. In Windows 9x (and I would assume Win2k, but not NT), DEVNODEs are associated with WM_DEVICECHANGE messages. So, it's possible to associate ASPI target addresses with plug-n-play events.

pTriple is an encoding of the standard SCSI HostAdapter/SCSI_ID/LUN triple, stored as 0x00HHIILL, where HH is the Host Adapter, II is the ID, and LL is the LUN. Make pDEVNODE 0x0 if you which to translate the Path to a DEVNODE. After you call the function and it returns TRUE (1), the pDEVNODE will now contain the DEVNODE (even though you originally set it to a 0). If DEVNODE is non-zero, the function will assume that you're giving it a valid DEVNODE to translate to the 0x00HHIILL Triple, and will transform the first argument accordingly. It's messy, but efficient. Want to use DEVNODEs with Plug-n-play events? Go read a Windows plug-n-play book. I only care about the pure SCSI aspects.   Smile