Channels ▼
RSS

Design

The Advanced SCSI Programming Interface

Source Code Accompanies This Article. Download It Now.


MAR94: The Advanced SCSI Programming Interface

Building SCSIsupport into your code

Brian is an independent programmer specializing in device drivers and software for SCSI peripherals. He can be reached at bsawert@grdpnt.flagstaff.az.us or on CompuServe at 72027,2143.


In the near future, as CD-ROM drives, optical drives, and scanners become standard fare, SCSI devices will likely dominate the PC peripheral market. And as the market for SCSI peripherals grows, so will the demand for software that supports them.

But SCSI is not without its downside. SCSI protocol has generally been complex, a clear-cut standard for SCSI PC hardware has yet to emerge, and DOS doesn't offer direct support for SCSI. The Advanced SCSI Programming Interface (ASPI), however, offers a straightforward way to build SCSI capability into your code. In this article, I'll examine the ASPI standard and discuss how to use ASPI to solve SCSI-support problems.

The Problem with SCSI

Although the SCSI standard defines a communications protocol and a command set, the missing link for the DOS programmer is the interface to the SCSI bus. If you tackle the problem through low-level hardware programming, you restrict your software to a particular type or class of SCSI host adapter.

ASPI, proposed by Adaptec, defines a set of high-level functions for communicating with devices attached to the SCSI bus. It's designed to protect you from the pitfalls of hardware-level programming and provide a standard interface to SCSI host adapters.

The Advanced SCSI Programming Interface

Adaptec, Trantor, and Future Domain all offer ASPI compatibility with their host adapters, ensuring hardware independence and a broader market for software written to the standard. Even unusual host adapters such as Trantor's MiniSCSI parallel-port adapter offer ASPI support.

ASPI offers many advantages to the SCSI programmer. Because it is widely supported, it provides hardware and platform independence. Furthermore, ASPI manager software is available for DOS, OS/2, Novell NetWare, and other operating systems. Best of all, ASPI provides a high-level function set that's easy to use and can dramatically shorten your development time. ASPI hides the inner workings of SCSI protocol from the programmer. The ASPI manager handles the gritty details of arbitration, selection, and message passing, returning status codes and sense data when appropriate. You still need a basic knowledge of SCSI structures and protocol to use ASPI effectively, but it can shorten your learning curve by making experimentation easy.

ASPI does have its shortcomings. For example, the ASPI manager does not return a transfer count. If your device does not support the illegal-length indicator (ILI) and residual count in the sense data, there's no way to check how much data was transferred. Some ASPI managers fail to recognize certain devices, restricting the operations you can perform on them. And the number of different status codes returned by an ASPI call requires additional error checking by the calling program.

How ASPI Works

Under DOS, ASPI takes the form of a character device driver. The driver installs the ASPI manager--a device named $SCSIMGR. At startup, the ASPI manager polls the SCSI bus, looking for attached devices. A device must be turned on before bootup in order for ASPI to recognize it. The only time you access the ASPI driver through DOS is when you initialize it. At other times, a far call to the ASPI-manager entry point gives access to the ASPI function set. This makes ASPI functions safe to use from within a device driver or memory-resident program.

Using ASPI requires knowledge of the SCSI command set and capabilities for the device you wish to support. Keep in mind that ASPI provides interface functions, not high-level SCSI functions. The ASPI manager merely passes data through to the SCSI device without modifying or inspecting it. A point of confusion sometimes arises because ASPI parameters such as transfer length are in Intel order, while SCSI command-descriptor block (CDB) parameters appear in order of MSB to LSB.

The ASPI function set is small, but powerful. Some of the functions return information about the driver and environment, while others actually communicate with the SCSI device. The ASPI specification defines the functions in Table 1. Probably the most useful function in the ASPI set is Function 2 (execute SCSI I/O request). This function passes a SCSI CDB to the device, handles data transfer, and returns messages and status codes. If the request results in a Check Condition status, ASPI requests sense data from the device, making it available to the calling program. Consider this function a direct channel to the SCSI bus, as you will use it for most of your SCSI requests.

To initialize the ASPI manager, get a file handle to the ASPI driver by issuing a DOS open call to the $SCSIMGR device. Next, use a DOS IOCTL read to obtain the ASPI entry-point address. This is a far address you call to execute any of the ASPI functions. Last of all, close the file handle. You won't need to access the driver through DOS again. Listing One (page 159) illustrates these steps.

All ASPI functions use a SCSI request block (SRB). The SRB has a common 8-byte header for each function, containing an ASPI command code (see Table 2), host-adapter number, and request flags. The header also contains a status byte that the ASPI manager uses to return the outcome of the ASPI request. Calling an ASPI function requires filling in the proper SRB for the function, pushing the far address of the SRB on the stack, and making a far call to the ASPI entry point.

Functions that only access the ASPI manager return immediately. Functions that communicate with the SCSI device may return with the status byte set to 0, indicating that the request is still in progress. ASPI offers two methods for determining whether a SCSI request has completed: polling and posting. Polling, as the name implies, simply means periodically checking the status byte for a nonzero value. Posting, on the other hand, tells the ASPI manager to execute a post-processing routine when the SCSI request has completed. You enable posting by setting the Post bit in the request flags and passing the address of the routine in the SRB. The ASPI documentation recommends that applications use polling, reserving posting for device drivers or TSR programs, which may be interrupt driven.

SCSI I/O Under ASPI

The most complex function in the ASPI specification is also the most powerful. Function 2 (execute SCSI I/O request) is the gateway to the SCSI bus. The SRB for this function contains the data-buffer address and size, the target SCSI ID, and the SCSI CDB. Function 2 is also the most awkward to use. In addition to the ASPI status byte, it returns status codes for both the host and the target device. It also returns sense data if the SCSI request produces any. Finding the sense data can be tricky. It does not reside at a fixed offset in the SRB, but appears immediately after the SCSI CDB. You must keep track of the size of the CDB to locate the sense data. Once you are aware of these issues, using this function becomes quite simple. Just fill in the SRB, call the ASPI entry point, and watch the status byte until the request completes. Listing Two (page 159) demonstrates how to call the ASPI I/O function using polling.

ASPI Under Windows

Using ASPI under Windows is a bit more complex. Because the ASPI manager expects real-mode addresses in segment:offset form, the protected-mode selector:offset type of address will not work properly. You must pass real-mode addresses for the SRB and data buffers.

The entry-point address the ASPI manager returns is also a real-mode address. This means that under protected mode, you cannot call this address directly.

The solution to both problems is to use Windows' DPMI services, but there is a catch here that may not be obvious: These services are not available to a Windows application; you can only use them from within a DLL.

If you're starting to get discouraged, take heart. Accessing the ASPI manager requires only three DPMI functions. Function 100h (allocate DOS memory block) and function 101h (free DOS memory block) manage real-mode memory for the ASPI SRB and data buffers. The Windows functions GlobalDOSAlloc and GlobalDOSFree use these services, and they are easier to work with than the corresponding DPMI functions. Be careful, however, that you don't use real-mode buffers too freely. Allocating real-mode memory means taking it from the address space below 1 megabyte, where memory is a limited resource.

The only DPMI function you must use directly is function 301h (call real-mode procedure with far-return frame). Since the ASPI entry point is a real-mode address, you risk a protection fault if you try to call it from within Windows. Function 301h lets you call the entry point from protected mode, although the procedure is somewhat cumbersome. The calling function must fill a structure with register values for the real-mode procedure and set up a stack for passing parameters. The DPMI server can provide a default stack, but the size is limited. Stack usage may vary between ASPI drivers, so you may wish to provide your own.

Listing Three (page 160) recasts the call to the ASPI entry point as a DLL function. The call requires some inline assembly code and uses the DPMI default stack for simplicity. The real-mode call structure duplicates the 32- and 16-bit CPU registers. Setting up the call requires filling the CS:IP fields in the structure with the real-mode address of the ASPI entry point. Pass the pointer to the SRB on the protected-mode stack, setting CX to the number of words pushed. The DPMI server will create a real-mode stack with these parameters. Last of all, execute an INT 31h to call the procedure.

If you want to know more about DPMI functions, download the DPMI spec from the CompuServe Intel forum. For examples of using Windows DPMI services, look in the Microsoft Software Library on CompuServe.

Conclusion

The complete listings (available electronically, see "Availability," page 3) for these examples include source code for an ASPI library and an application to inventory the SCSI bus. For Windows programmers, the listings also include source code for an ASPI DLL.

If you want to investigate ASPI further, Adaptec sells the ASPI Software Developer's Kit, which contains the specification and programming guides for DOS, Windows, OS/2, and NetWare, as well as utilities and sample code. (Call Adaptec at 800-442-7274 or call their BBS at 408-945-7727.)

Table 1: ASPI functions.

Function       Description
-----------------------------------
Function 0     Host-adapter inquiry
Function 1     Get device type
Function 2     Execute SCSI I/O request
Function 3     Abort SCSI I/O request
Function 4     Reset SCSI device
Function 5     Set host-adapter parameters
Function 6     Get disk-drive parameters

Table 2: The ASPI DOS specification.

Command Code                Description
------------------------------------------------------------------
Command Code 0              Returns information about installed host
Host-adapter Inquiry          adapters: the number of adapters installed
                              and strings identifying the ASPI manager and
                              host adapter.

Command Code 1              Returns the peripheral device type code for
Get Device Type              the device at a given SCSI target ID.

Command Code 2              Executes a SCSI command, passing a CDB to a
Execute SCSI I/O Request      device and managing data transfer and status
                              return. Requests sense data if a Check
                              Condition status occurs.

Command Code 3              Aborts a pending SCSI operation.
Abort SCSI I/O Request

Command Code 4              Resets the device at a given SCSI target ID.
Reset SCSI Device

Command Code 5              Sets unique parameters for a host adapter. Few
Set Host-adapter              manufacturers implement this function.
Parameters

Command Code 6              Returns information for a SCSI disk drive.
Get Disk-drive                Returns preferred head and sector
Information                   translations and flags for INT 13h support.
                              This is a recent addition to the ASPI spec 
                              and is not widely supported.

[LISTING ONE]


// ------ Initializing the ASPI driver. -------
#include "aspi.h"                       // ASPI definitions and constants
#include "scsi.h"                       // SCSI definitions and constants
// -------------------- defines and macros --------------------
#define IOCTL_READ  2                   // IOCTL read function
#define ASPI_NAME   "SCSIMGR$"          // SCSI manager driver name
// -------------------- global variables --------------------
void (far *ASPIRoutine) (aspi_req_t far *); // far address of ASPI routine
BYTE f_installed;                       // flag for ASPI existence

aspi_req_t *srb;                        // SCSI Request Block pointers
// ----------------------------------------------------------------------
// Routine to check for and initialize ASPI driver.
// Usage:   int aspi_open(void);
// Returns nonzero on success, 0 if ASPI driver not found or init failed.
int aspi_open(void)
    {
    int dh;                             // ASPI driver handle
    if (!f_installed)
        {                               // not initialized
        if ((dh = open(ASPI_NAME, O_RDONLY | O_BINARY)) != -1)
            {                           // open device driver
            if (ioctl(dh, IOCTL_READ, (void *) &ASPIRoutine,
                sizeof(ASPIRoutine)) == sizeof(ASPIRoutine))
                {                       // got ASPI entry point
                srb = (aspi_req_t *) malloc(sizeof(aspi_req_t));
                if (srb != NULL)
                    {                   // allocated SRB buffers
                    f_installed++;      // set installed flag
                    }
                }
            close(dh);                  // close device driver
            }
        }
    return(f_installed);
    }
// ----------------------------------------------------------------------
// Routine to close and clean up.
// Usage:   void aspi_close(void);
// Called with nothing. Returns nothing.
void aspi_close(void)
    {
    if (f_installed)
        {                               // already initialized
        free(srb);                      // deallocate buffers
        f_installed = 0;                // clear installed flag
        }
    return;
    }


[LISTING TWO]
<a name="00d4_000f">
// ------ Calling the ASPI entry point and ASPI I/O. -------
#include "aspi.h"                       // ASPI definitions and constants
#include "scsi.h"                       // SCSI definitions and constants

// -------------------- defines and macros --------------------
#define BUSY_WAIT   0x1000              // repeat count for busy check
#define MIN_GRP_1   0x20                // minimum SCSI group 1 command
// -------------------- global variables --------------------
void (far *ASPIRoutine) (aspi_req_t far *); // far address of ASPI routine
BYTE f_installed;                       // flag for ASPI existence
BYTE aspi_stat;                         // ASPI status byte
BYTE host_stat;                         // host status byte
BYTE targ_stat;                         // target status byte
BYTE host_num;                          // host adapter number (0 default)

aspi_req_t *srb;                        // SCSI Request Block pointers
sense_block_t *srb_sense;               // pointer to SRB sense data
// ----------------------------------------------------------------------
// Routine to call ASPI driver.
// Usage:   int aspi_func(aspi_req_t *ar);
// Called with pointer to SCSI Request Block (SRB).
// Returns nonzero on success, 0 on error.
int aspi_func(aspi_req_t *ar)
    {
    int retval = 0;

    if (f_installed)
        {                               // ASPI manager initialized
        ASPIRoutine((aspi_req_t far *) ar); // call ASPI manager
        retval++;
        }
    return(retval);
    }
// ----------------------------------------------------------------------
// Execute SCSI I/O through ASPI interface.
// Usage:   int aspi_io(BYTE *cdb, BYTE far *dbuff, WORD dbytes,
//              BYTE flags, BYTE id, WORD *stat);
// Called with pointer to data buffer, data buffer size, pointer to CDB,
//  request flags, and target ID. Returns ASPI status on success, -1 on error.
//  Fills stat variable with host status in high byte, target in low byte.
int aspi_io(BYTE *cdb, BYTE far *dbuff, WORD dbytes, BYTE flags,
    BYTE id, WORD *stat)
    {
    int cdbsize;
    int timeout;                        // timeout counter for polling
    int retval = -1;

    memset(srb, 0, sizeof(aspi_req_t)); // clear SRB
    srb->command = SCSI_IO;             // set command byte
    srb->hostnum = host_num;            // set host adapter number
    srb->su.s2.targid = id;             // set target SCSI ID
    srb->reqflags = flags;              // set request flags
    srb->su.s2.databufptr = dbuff;      // set pointer to data buffer
    srb->su.s2.datalength = dbytes;     // set data buffer length
    srb->su.s2.senselength = sizeof(sense_block_t);
                                        // set sense data buffer length
    cdbsize = sizeof(group_0_t);        // assume 6 byte CDB
    if (*((BYTE *) cdb) >= MIN_GRP_1 && *((BYTE *) cdb) < MIN_GRP_6)
        {                               // CDB size is 10 bytes
        cdbsize = sizeof(group_1_t);
        }
    srb->su.s2.cdblength = cdbsize;     // set CDB length
    srb->su.s2.senselength = MAX_SENSE; // sense sense data length
    memcpy(srb->su.s2.scsicdb, cdb, cdbsize);   // copy CDB to SRB
    srb_sense = srb->su.s2.scsicdb + cdbsize;   // point to sense data buffer
    if (aspi_func(srb))
        {                               // ASPI call succeeded
        timeout = BUSY_WAIT;            // set timeout counter

        while (srb->status == REQ_INPROG && timeout > 0)
            {                           // request in progress - keep polling
            timeout--;                  // decrement timeout counter
            }
        retval = aspi_stat = srb->status;   // save ASPI status
        if (aspi_stat != REQ_INPROG)
            {                           // request completed
            host_stat = srb->su.s2.hoststat;    // save host status
            targ_stat = srb->su.s2.targstat;    // save target status
            *stat = ((WORD) host_stat << 8) | targ_stat;
                                        // return combined SCSI status
            }
        }
    return(retval);
    }

<a name="00d4_0010"><a name="00d4_0011">
[LISTING THREE]
<a name="00d4_0011">
// ------  Initializing and using ASPI in a DLL. ------
#include "aspi.h"                       // ASPI definitions and constants
#include "scsi.h"                       // SCSI definitions and constants
// -------------------- defines and macros -------------------
#define IOCTL_READ  2                   // IOCTL read function
#define ASPI_NAME   "SCSIMGR$"          // SCSI manager driver name
#define DPMI_INT    0x31                // DPMI interrupt number
#define REAL_CALL   0x301               // real mode call function

typedef struct RealCall
    {                                   // struct for real mode call
    DWORD edi, esi, ebp, reserved, ebx, edx, ecx, eax;
    WORD flags, es, ds, fs, gs, ip, cs, sp, ss;
    } RealCall_t ;
// -------------------- global variables -------------------
void (far *ASPIRoutine) (aspi_req_t far *); // far address of ASPI routine
BYTE f_installed;                       // flag for ASPI existence
aspi_req_t _FAR *srb;                   // SCSI Request Block pointers
DWORD dwPtr;                            // return from GlobalDOSAlloc
// -------------------- local functions -------------------
void far *MaptoReal(void far *pptr);    // map to real mode address
int AspiCall(void far *aspiproc, aspi_req_t far *ar);   // DPMI function
// ----------------------------------------------------------------------
// Routine to check for and initialize ASPI driver.
// Usage:   int FUNC aspi_open(void);
// Returns nonzero on success, 0 if ASPI driver not found or init failed.
int FUNC aspi_open(void)
    {
    int dh;                             // ASPI driver handle
    UINT wSRB;                          // selector for buffer memory
    if (!f_installed)
        {                               // not initialized
        if ((dh = open(ASPI_NAME, O_RDONLY | O_BINARY)) != -1)
            {                           // open device driver
            if (ioctl(dh, IOCTL_READ, (void far *) &ASPIRoutine,
                sizeof(ASPIRoutine)) == sizeof(ASPIRoutine))
                {                       // got ASPI entry point
                dwPtr = GlobalDosAlloc(sizeof(aspi_req_t));

                if (dwPtr[0] != NULL)
                    {                   // allocated SRB buffer
                    wSRB = LOWORD(dwPtr[0]);    // extract selector
                    GlobalPageLock(wSRB);   // lock memory
                    srb = (aspi_req_t _FAR *) MAKELP(wSRB, 0);
                    f_installed++;      // set installed flag
                    }
                }
            close(dh);                  // close device driver
            }
        }
    return(f_installed);
    }
// ----------------------------------------------------------------------
// Routine to close and clean up.
// Usage:   void FUNC aspi_close(void);
// Called with nothing. Returns nothing.
void FUNC aspi_close(void)
    {
    UINT wSRB;                          // selector for buffer memory
    if (f_installed)
        {                               // already initialized
        wSRB = FP_SEG(srb);             // extract selector from pointer
        GlobalPageUnlock(wSRB);         // unlock buffer
        GlobalDosFree(wSRB);            // deallocate buffer
        dwPtr = NULL;
        srb = NULL;
        f_installed = 0;                // clear installed flag
        }
    return;
    }
// ----------------------------------------------------------------------

// Routine to call ASPI driver.
// Usage:   int aspi_func(aspi_req_t _FAR *ar);
// Called with pointer to SCSI Request Block (SRB).
// Returns nonzero on success, 0 on error.
int aspi_func(aspi_req_t _FAR *ar)
    {
    aspi_req_t far *arptr;              // real mode pointer
    int retval = 0;
    if (f_installed)
        {                               // ASPI manager initialized
        if ((arptr = (aspi_req_t far *) MaptoReal(ar)) != NULL)
            {                           // got a valid real mode pointer
            retval = AspiCall(ASPIRoutine, arptr); // call ASPI through DPMI
            }
        }
    return(retval);
    }
// ----------------------------------------------------------------------
// Routine to map protected mode pointer to real mode.
// Usage:   void far *MaptoReal(void far *pptr);
// Returns real mode pointer on success, NULL on error.
void far *MaptoReal(void far *pptr)
    {
    WORD sel;                           // protected mode selector
    void far *ptr = NULL;               // real mode pointer
    sel = FP_SEG(pptr);                 // get selector value
    if (sel == LOWORD(dwPtr))
        {                               // found matching selector
        ptr = MAKELP(HIWORD(dwPtr), FP_OFF(pptr));
                                        // build real mode pointer
        }
    return(ptr);
    }
// ----------------------------------------------------------------------
// Call ASPI manager through DPMI server.
// Usage:   int AspiCall(void far *aspiproc, aspi_req_t far *ar);
// Returns 1 on success, 0 otherwise.
int AspiCall(void far *aspiproc, aspi_req_t far *ar)
    {
    RealCall_t rcs;                     // real mode call struct
    int retval = 0;
    int npush;
    void far *sptr;
    memset(&rcs, 0, sizeof(RealCall_t));    // clear call structure
    rcs.cs = HIWORD(aspiproc);          // point to real mode proc
    rcs.ip = LOWORD(aspiproc);
    npush = sizeof(aspi_req_t far *) / sizeof(WORD); // words to pass on stack
    sptr = (void far *) &rcs;           // far pointer to call structure
    asm {
        push di                         // save registers
        push es
        sub bx, bx                      // don't reset A20 line
        mov cx, npush;                  // number of words to copy to stack
        les di, sptr                    // point ES:DI to call structure
        mov ax, REAL_CALL               // load function number
        push WORD PTR [ar + 2]          // push SRB address
        push WORD PTR [ar]
        int DPMI_INT                    // call DPMI server
        pop ax                          // restore stack count
        pop ax
        pop es                          // restore registers
        pop di
        jc asm_exit                     // DPMI error
        mov retval, 1                   // return 1 for success
        }
    asm_exit:
    return(retval);
    }

End Listings



Copyright © 1994, 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