Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Database

A DOS Redirector for SCSI CD-ROM


MAR93: A DOS REDIRECTOR FOR SCSI CD-ROM

Putting the pieces together

This article contains the following executables: CDROM.ARC

Jim Harper

Jim has a BSEE and an MBA from Union College in Schenectady, New York. He is a member of the technical staff at Sun Microsystems' Rocky Mountain Technology Center in Colorado Springs, Colorado. You can reach Jim via the internet at [email protected] or through CompuServe at 72440,171.


The article, "Inside the ISO 9660 Filesystem Format," by Bill and Lynne Jolitz (DDJ, December 1992) described how information is stored on a CD-ROM using the ISO-9660 format. However, the ISO-9660 file format is just one aspect of the entire process of getting the data from the CD-ROM disc over to where your application can use it. On the PC/DOS platform, this is a somewhat complicated affair, involving multiple software components that sometimes interact in obscure ways.

This article describes how this process happens under DOS and presents the code for an MSCDEX-like extension to DOS (a redirector) that allows access to either High Sierra or ISO-9660 CD-ROMs. My redirector works in conjunction with a TSR-based driver for SCSI devices which I also wrote and whose code is presented here.

My SCSI TSR supports the Seagate ST-01 host adapter, a dumb but inexpensive (about $35.00 retail) SCSI adapter card, and can be modified to support other adapters. The SCSI TSR is independent of, and loads separately from, the redirector. The two communicate via the INT 2F software interrupt. The redirector is device independent; to work with another host adapter, you just need to write a TSR that uses the same INT 2F protocol. In addition, I've also included a small C program that exercises the SCSI TSR so you can read any sector on the CD-ROM, displaying it in hex and ASCII format. These various software components total about 2000 lines of code. The complete code is available electronically (see "Availability," page 5), and excerpts are shown in Listings One and Two (discussed in the following sections).

Although I wrote these programs some time ago--way before the current multimedia explosion--to my knowledge no complete system has been published anywhere. It's one thing to write code that follows the SCSI spec or the ISO-9660 spec, and quite another to glue all the pieces together into a working whole. The code here works under DOS 3.3, 4.0, and 5.0.

About Redirectors

MS-DOS has built-in support for networks, via its redirector protocol, and you can use this feature to install a CD-ROM file system as a "foreign" file system. The protocol is seamless enough such that if you run Microsoft Windows, the redirected CD-ROM drive will appear as a network drive.

Several layers of software mediate between the data on CD-ROM and the point where it gets used by your application. Looking at these layers from the top down, the first layer is where your application code makes an open() or read() call on your system's CD-ROM drive, say, drive K:. This library call turns into an INT 21 request for DOS services. In DOS versions 3.x, 4, and 5, the operating system checks if the network bit is set for that particular drive letter. If so, the drive is not one of your conventional hard drives, but is either a network device, CD-ROM, or other source of data. At this point, a redirector can take over. The redirector does the necessary magic to make the network or foreign file system appear as DOS-like as possible. The redirector uses a lower-level component--in my case a TSR--to communicate with the physical device, using the appropriate protocol--in my case, SCSI.

The best known redirector is Microsoft's MSCDEX, created as part of Microsoft's big push for CD-ROM a few years ago. Even though data is stored in High Sierra or ISO-9660 format on the CD-ROM drive, MSCDEX allows it to appear as a DOS file system to your application program.

When a drive is declared as a network drive (by setting the network flag bit in DOS corresponding to the drive we want to redirect), DOS handles high-level I/O requests differently than for native drives. All the details for opening, reading, writing, searching and parsing pathnames, and so on, are handed off to the redirector via the "Multiplex Interrupt," a fancy name for INT 2F. Your redirector must hook into this 2F chain, examining all interrupts, looking for those in which AH equals 11H. In such cases, AL contains a value identifying a specific redirector function. For example, a value of 15H in AL represents an OPEN system call. In my code, the header file REDIR.H contains #defines for most of the system calls for redirectors. A redirector signifies an error to DOS by returning with the carry bit set in the flag register, and with an error code in AX.

To successfully implement a redirector, you need to access some undocumented structures within DOS. The important one is the List of Lists (LoL), which points to other essential structures, such as the Current Directory Structure (CDS) array, the System File Table (SFT), and the Last Drive variable. (For more about the CDS array, see the letter entitled "Delving into Drive Paths" in the February 1990 DDJ.) I also use the DOS Transient Area (DTA) and a filename buffer (FN1) within DOS. In the early days of DOS, these undocumented structures were mysterious, but in recent years they have been well-explored by a number of authors. For further information, I recommend reading Chapter 4 of Undocumented DOS by Andrew Schulman et al. (Addison Wesley, 1990).

My redirector isn't a complete MSCDEX replacement, because MSCDEX supports additional functions such as the playing of audio tracks (there are special calls for music start/stop and so on). My redirector limits itself to file redirection only. CD-ROM applications that test if MSCDEX is present via the "test for presence" call will not work here. Actually, if your CD-ROM application works with audio data in the form of digitized DOS files, as opposed to the standard CD-ROM audio format, then your application should work fine. There are a few applications that rely on standard CD-ROM audio tracks but will still manage to work without MSCDEX. For example, the "Mammals" CD-ROM from the National Geographic Society works in this manner: If MSCDEX is absent, the program still functions, but does not allow you to select the audio portions.

About ISO-9660

ISO-9660 is a hierarchical file system and therefore not totally different from DOS. The file-system hierarchy is anchored by a "volume descriptor," which is usually at block 16 (10H) on the disc. Copies of this block (for redundancy) may also be present. The volume descriptor contains, among other things, the volume name, the date, and the location of the root directory.

Directories and files are almost always contiguous, for performance reasons. A directory consists of a sequence of directory records, as many as will fit in a sector. Each directory record consists of fields specifying record length, file size, flag byte, starting block, date, and filename (referred to as a File ID). Each multibyte integer such as file size is stored twice, once in the MSB and again in LSB. Macintosh programmers will be pleased to note the existence of the ASSOCIATED FILE bit in the flag byte of a directory entry; this bit supports bifurcated files (files consisting of both a resource fork and a data fork).

For more detail, see my header file CD-ROM.H in the electronic version of the listings. This file contains C structures defining ISO-9660 and High Sierra directory entries.

The Redirector Code

The file CD-ROM.C can be divided into roughly three parts. Part one is main(), where the TSR installs itself into the multiplex interrupt chain (INT 2F), finds the required structures within DOS using undocumented DOS calls, and makes itself resident. Part two is New2F(), where a check is made to handle the call or pass it on. The third part is everything else, where most of the real work is done.

Listing One (page 103) shows main() and New2F(). The main() function first collects the command argument specifying which drive to redirect, saving it in a global for access by the interrupt handler New2F(). It then checks that the SCSI TSR is loaded and reads block 10H, the volume descriptor. Then it looks for the string "CDROM" or "CD001" within the descriptor, identifying a High Sierra or ISO-9660 disk, respectively. Depending on the format found, it sets certain important offset values. Next, the program gets the address of the LoL and uses it to set pointers to other structures, such as the CDS array and the SFT.

Then, main() sets a bit within the CDS which turns on the redirector, so New2F() will get drive calls normally handled by DOS. Last, it initializes its own structures, closes all open file handles, hooks into the 2F interrupt vector, and calls DOS to terminate and stay resident.

After main() has completed its work, New2F() is part of the multiplex interrupt chain. When called, New2F() checks if the interrupt is a redirector call, signified by AX=11xx. If not, control is passed to the next interrupt handler in the chain. If New2F() is supposed to handle the interrupt, it switches to its own stack and calls particular subroutines, based upon the value in AL. These different subroutines handle such file-system functions as Open, Close, Read, Seek, Change Directory, Find First, and Find Next. In each case, the data on the CD-ROM disc must be navigated, and a reasonable mapping made so that this data is intelligible to DOS. These functions call lower-level routines to read and write to the SCSI device. For example, the DoRead() routine calls ScsiRead(), sets up a SCSI command descriptor block (CDB) for the SCSI read command (CDBs are part of the SCSI protocol), and then uses INT 2F to communicate with the TSR-based device driver (discussed in the following section). When the DoRead() subroutine finishes, New2F() switches stacks back and returns.

Because multiple redirectors may be installed (as in the case of a network), each subroutine must determine in its own way (usually by checking the global DriveNo) whether the call is for itself or for another redirector.

The SCSI Transport Layer

SCSI is an interface standard defined in 1986 by the American National Standards Institute (ANSI). This standard evolved from an earlier effort by Seagate to develop a "high-level" interface for transferring data between CPUs and disk drives. Although in use on the Macintosh and Sun platforms for a long time SCSI has only recently begun to take off on the PC platform, supplanting ESDI in the marketplace as the high-end interface of choice. Eventually, even IDE may be pushed out of the mainstream by the versatility of SCSI, which allows scanners, printers, tape drives, and even graphics terminals to be connected simply and at high bandwidths. Newer flavors of SCSI include SCSI-2 and SCSI-3.

I implemented the SCSI transport layer using a TSR called ST01.C. My code manipulates the hardware directly, setting up the CDBs required by the SCSI protocol, and then reading and writing memory-mapped registers specific to the Seagate ST01 SCSI host adapter. If you know SCSI, you'll understand the comments. It is beyond the scope of this article to delve into the details of the SCSI protocol, defined in ANSI document X3.131-1986, "Small Computer System Interface," available from ANSI. A good introduction is the slim volume SCSI by NCR Corp. (Prentice Hall, 1990). Basically, hosts (CPUs) and peripheral devices (such as CD ROMs or scanners) follow a bus-oriented protocol broken up into a series of phases, beginning with the bus-free phase, and moving to the arbitration phase, the selection phase, and finally the command phase. During the command phase, a CDB is sent from host to peripheral. A command can be rather powerful, such as "format the device" or "read 1000 blocks." In my implementation, I use only the most basic commands.

The heart of the module is the Do-Scsi() routine, shown in Listing Two (page 104). Basically, it tracks the dialogue between host and peripheral through the various phases, sending the bytes in the CDB during the COMMAND phase, and transferring data bytes during the DATAIN or DATAOUT phases. You may notice my optimization in the data-transfer portion, in which inline assembler is used to maximize speed.

Conclusion

Each of the subject areas in this article--redirectors, CD-ROM file systems, TSRs, and SCSI protocols--can consume many hours of study. The working code I've presented here should give you a head start on your own projects.



_A DOS REDIRECTOR FOR SCSI CD-ROM_
by Jim Harper


[LISTING ONE]
<a name="00bb_000a">

/***************************************************************************
 *  CDROM.C -- by Jim Harper  (EXCERPTED LISTING)
 *  A CD-ROM redirector for High Sierra and ISO 9660 disks.
 **************************************************************************/

/*...#include directives removed...*/
#define SetC(X)      (X) |=  0x01
#define ClrC(X)      (X) &= ~0x01

extern unsigned _psp,       /* Runtime gives us these variables */
                 end;
char *IOBuf;                /* I/O Buffer ptr */

/* Table of saved open SystemFileTable's (SFT's) for DoCloseAll() */
struct SFT _far *CloseTab[MAXCLOSEALL];
unsigned StkSeg,         DataSeg,
         DriveNo,        DriveFlags,
         TsrStkSeg,      TsrStkPtr,
         AppStkSeg,      AppStkPtr,
         CDType,         FIDoff,
         Nameoff,        Dateoff,
         Flagsoff,       Blkoff,
         Sizeoff,        BlkSize,
         ChainFlag,
         MyStack[STACKSIZE];
unsigned _AX,_BX,_CX,_DX,_DS,_ES,_DI,_FLAGS;
struct IntRegs {
    unsigned ES;    unsigned DS;
    unsigned DI;    unsigned SI;
    unsigned BP;    unsigned SP;
    unsigned BX;    unsigned DX;
    unsigned CX;    unsigned AX;
    unsigned IP;    unsigned CS;
    unsigned FLAGS;
};
int Active = 0;
struct isoVolDesc   *isoVolDescp;
struct isoDirRec    *isoDp;
struct hsVolDesc    *hsVolDescp;
struct hsDirRec     *hsDp;
struct Cmd           Cmd;
struct DirEnt        RootEnt,
                     DirCache[CACHESIZE];
                                      /* Important pointers            */
struct SDB _far *SDBp;                /* Ptr to Dos Search Data Blk    */
struct FDB _far *FDBp;                /* Ptr to Dos Found Data Blk     */
struct LOL _far *LOLp;                /* Ptr to list of lists          */
struct CDS _far *CDSp;                /* Ptr to cur dir tab entry      */
/* These pointers are set according to DOS 3.xx or 4.xx                */
char _far  *SWAPp,                    /* Ptr to Dos swap area          */
     _far  *FN1p,                     /* Ptr to Dos resolved name      */
     _far  * _far *DTApp,             /* Ptr to Ptr to Current DTA     */
     _far  * _far *SFTpp,             /* Ptr to Ptr to Current SFT     */
     _far  *DosDp,                    /* Ptr to dir ent for file       */
     _far  *Sattrp,                   /* Ptr to search attr            */
     _far  *OpenModep;                /* Ptr to open mode              */
unsigned _far  *PSPp;                 /* Ptr to current PSP            */
char *HiSierra = "HISIERRA   ",
     *Iso9660  = "ISO9660    ";
/*...function prototypes removed...*/
/***********************************************************************/
main(int argc, char *argv[])
{   union REGS      regs;
    struct SREGS    sregs;
    unsigned _far  *EnvBlkp;
    int             i, Junk, CdsLen, ProgSize;
    DriveNo = 999;
    if (argc > 1) {
        if (!strcmp(argv[1],"-u") || ! strcmp(argv[1],"-U")) {
            regs.h.ah = 0x11;
            regs.h.al = DEINSTALL;
            int86x(INT2F,®s,®s,&sregs);
            exit(0);
        }
        if (argv[1][0] >= 'A' && argv[1][0] <= 'Z' &&
            argv[1][1] == ':' && argv[1][2] == '\0') {
            DriveNo = argv[1][0] - 'A';
        }
        if (argv[1][0] >= 'a' && argv[1][0] <= 'z' &&
            argv[1][1] == ':' && argv[1][2] == '\0') {
            DriveNo = argv[1][0] - 'a';
        }
    }
    if (DriveNo > 26) {
        MsgOut("usage: cdrom [A-Z]:\r\n");
        exit(1);
    }
    segread(&sregs);            /* Get our stack and data segments */
    StkSeg  = sregs.ss;
    DataSeg = sregs.ds;
    regs.h.ah = FUNCID;         /* Check if SCSI TSR is present */
    regs.h.al = INSTALLCHK;
    int86x(INT2F,®s,®s,&sregs);
    if (regs.h.al != 0xff) {
        MsgOut("Scsi tsr not found!\r\n");
        exit(1);
    }
    /* Check if there's a High Sierra or ISO9660 disk in the drive.  */
    if (ScsiRead(0x10L)) {
        MsgOut("IO error.\r\n");
        exit(1);
    }
    hsVolDescp  = (struct  hsVolDesc *) IOBuf;
    isoVolDescp = (struct isoVolDesc *) IOBuf;
    CDType  = UNKNOWN;
    Blkoff  = 2;
    Sizeoff = 10;
    Dateoff = 18;
    FIDoff  = 32;
    Nameoff = 33;
    strcpy(RootEnt.FName,"ROOT-CDROM ");
    if (strncmp(hsVolDescp->ID,"CDROM",5) == 0) {  /* it's High Sierra */
        CDType = HIGHSIERRA;
        Flagsoff = 24;
        hsDp = (struct hsDirRec *)hsVolDescp->DirRec;
        RootEnt.Fattr = _A_SUBDIR;
        RootEnt.FTime = ToDosTime(hsDp->Date);
        RootEnt.FDate = ToDosDate(hsDp->Date);
        RootEnt.BlkNo = hsDp->ExtLocLSB;
        RootEnt.FSize = hsDp->DataLenLSB;
        RootEnt.ParentBlk = hsDp->ExtLocLSB;
        BlkSize = hsVolDescp->BlkSizeLSB;
        MsgOut("High Sierra disk...\r\n");
    }
    if (strncmp(isoVolDescp->ID,"CD001",5) == 0) {  /* it's ISO 9660 */
        CDType = ISO9660;
        Flagsoff = 25;
        isoDp = (struct isoDirRec *)isoVolDescp->DirRec;
        RootEnt.Fattr = _A_SUBDIR;
        RootEnt.FTime = ToDosTime(isoDp->Date);
        RootEnt.FDate = ToDosDate(isoDp->Date);
        RootEnt.BlkNo = isoDp->ExtLocLSB;
        RootEnt.FSize = isoDp->DataLenLSB;
        RootEnt.ParentBlk = isoDp->ExtLocLSB;
        BlkSize = isoVolDescp->BlkSizeLSB;
        MsgOut("ISO 9660 disk...\r\n");
    }
    if (CDType == UNKNOWN) {
        MsgOut("Unknown disk type..\r\n");
        exit(1);
    }
    regs.h.ah = 0x52;                /* Get Address of List of Lists */
    int86x(0x21,®s,®s,&sregs);
    FP_SEG(LOLp) = sregs.es;
    FP_OFF(LOLp) = regs.x.bx;
    regs.x.ax = 0x5d06;               /* Get address of Dos Swap area */
    int86x(0x21,®s,®s,&sregs);
    FP_SEG(SWAPp) = sregs.ds;
    FP_OFF(SWAPp) = regs.x.si;
    if (DriveNo > LOLp->LastDrive) {
        MsgOut("Drive # to high.\r\n");
        exit(1);
    }
    MsgOut("DOS version "); ToHex(_osmajor); MsgOut("."); ToHex(_osminor);
    /* Now set the offsets within Dos according to 3.3x, 4.xx or 5.xx */
    if ( _osmajor == 3 && _osminor >= 30) {
        CdsLen = 0x51;
        PSPp   = (unsigned _far *)(SWAPp + 0x10U);
        FN1p   = SWAPp + 0x0092U;
        Sattrp = SWAPp + 0x023aU;
        DosDp  = SWAPp + 0x01a7U;
        SDBp   = (struct SDB _far *) (SWAPp + 0x0192U);
        DTApp  = (char _far * _far *)(SWAPp + 0x000cU);
        SFTpp  = (char _far * _far *)(SWAPp + 0x0268U);
    } else if (_osmajor == 4 || _osmajor == 5) {
        CdsLen = 0x58;
        PSPp   = (unsigned _far *)(SWAPp + 0x10U);
        FN1p   = SWAPp + 0x009eU;
        Sattrp = SWAPp + 0x024dU;
        DosDp  = SWAPp + 0x01b3U;
        SDBp   = (struct SDB _far *) (SWAPp + 0x019eU);
        DTApp  = (char _far * _far *)(SWAPp + 0x000cU);
        SFTpp  = (char _far * _far *)(SWAPp + 0x027eU);
    } else {
        MsgOut("Not DOS 3.3x, 4.xx or 5.xx\r\n");
        exit(1);
    }
    /* Cast ptr to table entry pointer */
    CDSp = (struct CDS _far *) (LOLp->CDS + DriveNo * CdsLen);
    DriveFlags = CDSp->Flags;            /* Turn on network & physical bits */
    CDSp->Flags = 0xC000;
    CDSp->RootOff = 2;

    CDSp->CurDir[0] = 'A' + DriveNo;         /* Set to root */
    CDSp->CurDir[1] = ':';
    CDSp->CurDir[2] = '\\';
    CDSp->CurDir[3] = '\0';

    for (i = 0; i < STACKSIZE; i++)    /* Initialize our stack */
        MyStack[i] = 0x4141;
    TsrStkSeg = DataSeg;        /* Initialize stack and bottom of program. */
    TsrStkPtr = (unsigned)&MyStack[STACKSIZE];
    if (TsrStkPtr & 0x1U)    /* Make sure stack is on a word boundry */
        TsrStkPtr--;
    /* Program size in paragraphs w/o a heap */
    ProgSize = (StkSeg + (((unsigned)&end) >> 4)) - _psp + 1;
    for (i = 1; i < CACHESIZE - 1; i++) {    /* Initialize cache */
        DirCache[i].Forw = &DirCache[i+1];
        DirCache[i].Back = &DirCache[i-1];
    }
    DirCache[0].Forw = &DirCache[1];
    DirCache[0].Back = &RootEnt;
    DirCache[CACHESIZE-1].Forw = &RootEnt;
    DirCache[CACHESIZE-1].Back = &DirCache[CACHESIZE-2];

    /* Root dirent provides anchor into the cache */
    RootEnt.Forw = &DirCache[0];
    RootEnt.Back = &DirCache[CACHESIZE-1];
                          /* Close files */
    _dos_close(0);        /* stdin       */
    _dos_close(1);        /* stdout      */
    _dos_close(2);        /* stderr      */
    Old2F = _dos_getvect(INT2F);    /* Grab multiplex interrupt */
    _dos_setvect(INT2F,New2F);
    FP_SEG(EnvBlkp) = _psp;    /* Free the environment */
    FP_OFF(EnvBlkp) = 0x2c;
    _dos_freemem(*EnvBlkp);
    _dos_setblock(ProgSize,_psp,&Junk);    /* Shrink our program size */
    _dos_keep(0,ProgSize);                 /* TSR ourself */
}
/***** New2F(struct IntRegs IntRegs) -- our interrupt 2F handler. *****/
void _interrupt _far New2F(struct IntRegs IntRegs)
{
    /* See if we handle this function */
    if ((IntRegs.AX >> 8U) != 0x11 || Active)
        _chain_intr(Old2F);
    if ((IntRegs.AX & 0xff) == INSTALLCHK) {    /* Install check?? */
        IntRegs.AX = 0x00ff;
        return;
    }
    Active++;            /* Set flag saying we're active */
    ChainFlag = 0;       /* Don't chain out by default   */
    /* Save needed regs from stack  */
    _AX = IntRegs.AX;    _BX = IntRegs.BX;
    _CX = IntRegs.CX;    _DX = IntRegs.DX;
    _DS = IntRegs.DS;    _ES = IntRegs.ES;
    _DI = IntRegs.DI;    _FLAGS = IntRegs.FLAGS;
    _asm                      /* Switch to own stack */
    {
        cli                           ; Interrupts off
        mov  WORD PTR AppStkPtr,sp    ; Save app stack
        mov  WORD PTR AppStkSeg,ss
        mov  sp,WORD PTR TsrStkPtr    ; Load new stack
        mov  ss,WORD PTR TsrStkSeg
        sti                           ; Interrupts on
    }
    switch(_AX & 0xff)        /* handle the command */
    {
        case DEINSTALL:        DeInstall();      break;
        case CHDIR:            DoChDir();        break;
        case CLOSE:            DoClose();        break;
        case READ:             DoRead();         break;
        case GETSPACE:         DoGetSpace();     break;
        case GETATTR:          DoGetAttr();      break;
        case OPEN:             DoOpen();         break;
        case FINDFIRST:        DoFindFirst();    break;
        case FINDNEXT:         DoFindNext();     break;
        case SEEK:             DoSeek();         break;
        case CLOSEALL:         DoCloseAll();     break;
        case PATHNAME:         Spoof();         /* hack */

        case 0x25:
        default:               ChainFlag = 1;    break;
    }
    _asm           /* Switch back to app stack */
    {
        cli                            ; Interrupts off
        mov  sp,WORD PTR AppStkPtr     ; Load app stack
        mov  ss,WORD PTR AppStkSeg
        sti                            ; Interrupts on
    }
    if (ChainFlag) {    /* If anyone set the chain flag, chain out */
        Active = 0;
        _chain_intr(Old2F);
    }
    /* Restore (possibly modifed) registers */
    IntRegs.AX = _AX;    IntRegs.BX = _BX;
    IntRegs.CX = _CX;    IntRegs.DX = _DX;
    IntRegs.FLAGS = _FLAGS;
    Active = 0;    /* Clear Active Flag */
}




<a name="00bb_000b">
<a name="00bb_000c">
[LISTING TWO]
<a name="00bb_000c">


/**************************************************************************
 * ST01.C -- by Jim Harper.  (EXCERPTED LISTING) -- A simple SCSI transport TSR
 * that communicates with CDROM.C module via INT2F. The DoScsi() routine below
 * handles the actual work of of transferring data to/from the SCSI device.
 **************************************************************************/
void DoScsi(void)
{
    struct Cmd _far *Cmdp;
    unsigned Phase,
             NumBytes,
             Byte = 0,
             i;
    FP_SEG(Cmdp) = _DS;
    FP_OFF(Cmdp) = _DX;

    FP_SEG(Datap) = Cmdp->DSeg;
    FP_OFF(Datap) = Cmdp->DOff;

    NumBytes = 512;

    Endp = Datap;
    Cmdp->Count = 0L;

    /* Clear control reg */
    *RegPort = 0x00;

    /* Bus has gotta be free */
    if ((*RegPort & BUSYBIT) != 0x00) {
        Cmdp->Stat = 0x80;
        return;
    }
    /* Clear control reg */
    *RegPort = 0x00;
    /* Assert HBA's address */
    *DataPort = 0x80;
    /* Set the arbitration bit */
    *RegPort = (ARBITSTART | PENABLE);
    /* Wait for arbitration to complete */
    for (Timer1 = HZ * 3; (*RegPort & ARBITDONE) == 0x00;)
        if (!Timer1) {
            Cmdp->Stat = 0x81;
            return;
        }
    /* OR the target & our ID bits into the data reg */
    *DataPort = 0x80 | (0x01 << (unsigned)Cmdp->ID);

    /* Assert SELect, bus enable, deassert arbitration */
    *RegPort = (SEL | PENABLE | BUSENABLE);
    for (Timer1 = 2; Timer1;)
        ;
    /* Wait for BUSY */
    for (Timer1 = HZ * 3; (*RegPort & BUSYBIT) == 0x00;)
        if (!Timer1) {
            *RegPort = 0x00;
            Cmdp->Stat = 0x82;
            return;
        }
    /* Drop Select */
    *RegPort = (PENABLE | BUSENABLE);

    /* Wait for command phase */
    for (Timer1 = HZ * 3; ((Phase = (*RegPort & PHASEMASK)) != COMMAND);)
        if (!Timer1) {
            *RegPort = 0x00;
            Cmdp->Stat = 0x83;
            return;
        }
    Cmdp->Stat = 0x00;
    for (Timer1 = HZ * 10;;) {        /* Cmd must complete in 10s */
        if (!Timer1) {
            Cmdp->Stat = 0x84;
            return;
        }
        Phase = *RegPort & PHASEMASK;
        switch(Phase) {
            case COMMAND:
                while ((*RegPort & PHASEMASK) == COMMAND)
                    *DataPort = Cmdp->CDB[Byte++];
                break;
            case DATAIN:
                _asm
                {
                    push    es
                    push    ds
                    mov        cx,NumBytes
                    les        di,Datap
                    lds        si,DataPort
                    cld
            repeat1:
                    movsb
                    dec        si
                    loop    repeat1
                    pop        ds
                    pop        es
                }
                Datap += NumBytes;
                break;
            case DATAOUT:
                _asm
                {
                    push    es
                    push    ds
                    mov        cx,NumBytes
                    les        di,DataPort
                    lds        si,Datap
                    cld
            repeat2:
                    movsb
                    dec        di
                    loop    repeat2
                    pop        ds
                    pop        es
                }
                break;
            case STATUS:   Cmdp->Stat = *DataPort;            break;
            case MSGIN:    Cmdp->Sense = *DataPort;           break;
            case BUSFREE:  *RegPort = 0x00;
                           Cmdp->Count += Datap - Endp;
                           return;
        }
        /* Delay long enough */
        for (i = 0; (*RegPort & REQ) && i < 5; i++)
            ;
    }
}












Copyright © 1993, 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.