[comp.sys.ibm.pc] Finally, a C SCSI driver and MSDOS device driver package

dbraun@cadev4.intel.com (Doug Braun ~) (02/11/89)

Here is something I promised a while ago.  This package contains
driver software for the Ampex Megastore SCSI host adapter, as
well as a MSDOS device driver that can easily be modified for 
other devices.  All of this is written in Turbo C 1.5, with a little
assembler thrown in.  Look at the files README and driver.doc for
more info.  This is in shar format, so run it through sh to extract the 
contents.  If it got truncated, let me know, and I will re-release it in
two parts.



#!/bin/sh
#
#Run this file through sh to get:
#    README
#    dheader.s
#    driasm.s
#    driver.h
#    driver3.c
#    dtest.c
#    makefile
#    null.c
#    scsi.c
#    scsi.doc
#    scsi.h
echo -n 'Extracting README ... '
sed 's/^X//' > README << 'EOF_README'
XThis package contians the following files:s 
X
XREADME
X
Xscsi.c		A SCSI driver software package for PCs, written in Turbo C.
Xscsi.doc
Xscsi.h
X
Xdheader.s	A MSDOS Device Driver package.
Xdriasm.s
Xdriver.h
Xdtest.c
Xmakefile
Xnull.c
X
Xdriver3.c	A disk driver that uses the above.
X
X
XRead scsi.doc for more info.
X
X
XAll this was written by Doug Braun, 7976 W. Zayante Rd., Felton, CA 95018
XIt is freely distributable.
EOF_README
echo 'Done'

echo -n 'Extracting dheader.s ... '
sed 's/^X//' > dheader.s << 'EOF_dheader.s'
X; This describes one non-ibm block device
X
Xpublic header
Xheader dd -1
X	   dw 2000h
X	   dw _strat
X	   dw _intr
X	   db 1,0,0,0,0,0,0,0
X
EOF_dheader.s
echo 'Done'

echo -n 'Extracting driasm.s ... '
sed 's/^X//' > driasm.s << 'EOF_driasm.s'
Xname driasm
X
X
XSTACKSIZ equ 512
X
X_TEXT	segment	public 'CODE'
X
X;This forces everything into the same segment
XDGROUP	GROUP	_TEXT, _DATA, _EMUSEG, _CVTSEG, _SCNSEG, _BSS, _BSSEND
X
XASSUME	CS:_TEXT, DS:DGROUP, SS:DGROUP
X
X;This must be first
Xinclude dheader.s
X
X; This is the pointer to the request header
X; Together they are a far pointer
Xpublic _request_off
X_request_off 	dw ?		;must be in code segment
X_request_seg	dw ?
X
X;This is to keep turbo C happy
Xpublic DGROUP@
XDGROUP@ dd ?		;must be in code segment
X
X;These are local storage for the callers sp and ss
Xoldss	dw	?
Xoldsp	dw ?
X
X; This is the local stack
Xdb STACKSIZ dup (?)
Xlocalstk_top label word
X
X
X; This is the "strategy" entry point
X_strat	proc	far
X	mov	word ptr cs:_request_off,bx
X	mov	word ptr cs:_request_seg,es
X	mov cs:DGROUP@, cs		;Turbo C uses this for interrupt functions
X	ret	
X_strat	endp
X
X
X; This is the "interrupt" entry point
X_intr	proc	far
X    cli
X	push ds
X	push es
X	push ax
X	push bx
X	push cx
X	push dx
X	push si
X	push di
X	push bp
X	pushf
X
X	mov cs:oldss, ss
X	mov cs:oldsp, sp
X
X	mov ax, cs ;make local data addressable
X	mov ds, ax
X	mov ss, ax
X	mov es, ax
X	mov ax, offset localstk_top
X	mov sp, ax
X	mov bp, ax
X	sti
X
X	mov	ax, cs:_request_seg
X	push ax
X	mov	ax, cs:_request_off
X	push ax
X	call _dointr	;call dointr() with far pointer to header
X	pop ax
X	pop ax
X
X	cli
X	mov ax, cs:oldss
X	mov ss, ax
X	mov ax, cs:oldsp
X	mov sp, ax
X	popf
X	pop bp
X	pop di
X	pop si
X	pop dx
X	pop cx
X	pop bx
X	pop ax
X	pop es
X	pop ds
X	sti
X	ret
X_intr	endp
X
X
X	public	_intr
X	public	_strat
X
X
X
X    extrn _dointr:near
X
X_TEXT	ends
X
X_BSSEND segment byte public 'BSSEND'
Xpublic _edata
X_edata label byte
X_BSSEND ends
X
X	end
EOF_driasm.s
echo 'Done'

echo -n 'Extracting driver.h ... '
sed 's/^X//' > driver.h << 'EOF_driver.h'
X
X/* This is the driver header. It must be at the very beginning */
Xstruct bheader {
X    int link1;
X	int link2;
X	int attribute;
X	void (*strat_func)();
X	void (*intr_func)();
X	char nunits;
X	char reserved[7];
X};
X
Xstruct cheader {
X    int link1;
X	int link2;
X	int attribute;
X	void (*strat_func)();
X	void (*intr_func)();
X	char name[8];
X};
X
X/* A BIOS parameter block */
Xstruct bpb {
X    unsigned secsiz;
X	char clustersize;
X	unsigned nreserved;
X	char nfats;
X	unsigned nrootdirs;
X	unsigned nsectors;
X	char mdescriptor;
X	unsigned secsperfat;
X	char scratch[512];
X};
X
X
Xstruct boot_sector {
X	char filler1[3];
X	char disk_name[8];
X	struct bpb disk_bpb;
X	unsigned spt;
X	unsigned nheads;
X	unsigned nhidden;
X	char filler2[512-30];
X};
X
X
X/* The request header */
Xstruct reqhdr {
X    char rlength;
X	char unit;
X	char command;
X	int status;
X	char reserve[8];
X	char media;
X	char far *address;
X	unsigned count;
X	unsigned sector;
X	char padding[4];
X};
X
EOF_driver.h
echo 'Done'

echo -n 'Extracting driver3.c ... '
sed 's/^X//' > driver3.c << 'EOF_driver3.c'
X#include <dos.h>
X#include "driver.h"
X#include "scsi.h"
X
X
X#define NUNITS 2
X
Xstruct reqhdr far *request;
X
X/* command dispatch table */
Xextern int (*(dispatch[]))();
X
X#define max_command 16
X
Xchar msg[100];
X
Xchar *get_sense();
X
Xvoid dointr(req)
Xstruct reqhdr far *req;
X{
X	static int x;
X
X	request = req;
X
X    x = request->command;
X	if (x < 0 || x > max_command)
X	{
X	    request->status = 0x8103;
X		return;
X	}
X
X	request->status = (*(dispatch[x]))();
X	request->status |= 0x0100;
X}
X
X
Xbiosputs(s)
Xchar *s;
X{
X	static char c;
X	static int oldbp;
X
X    while (*s)
X	{
X	    c = *s++;
X		if (c == '\n')
X		{
X			oldbp = _BP;
X			_AL = '\r';
X			_AH = 0x0e;
X			_BH = 0;
X			geninterrupt(0x10);
X			_BP = oldbp;
X		}
X
X		oldbp = _BP;
X		_AL = c;
X		_AH = 0x0e;
X		_BH = 0;
X		geninterrupt(0x10);
X		_BP = oldbp;
X	}
X}
X
X/* Following are the individual routines for each function */
X
X
Xbadfun()
X{
X    return (0x8003);
X}
X
Xnullfun()
X{
X    return (0);
X}
X
X
X
X/* This is returned by the initialization function */
X
Xstruct bpb bpb_list[NUNITS];
X
Xstruct bpb *bpb_array[NUNITS] = { &bpb_list[0], &bpb_list[1] };
X
X
Xstatic struct boot_sector init_buf;
X
Xinit()
X{
X	extern char edata;
X	static struct scsireq s;
X	int j;
X	static char cmd[6];
X
X
X	sprintf(msg,
X			"\nHard disk driver ver. 3 compiled %s %s installed at %X:0000\n",
X			__DATE__, __TIME__, _CS);
X	biosputs(msg);
X
X	for (j=0; j < NUNITS; j++)
X	{
X
X	    cmd[0] = 0x08;
X	    cmd[1] = (j&01) << 5;
X	    cmd[2] = 0;
X	    cmd[3] = 0;
X	    cmd[4] = 1;
X		cmd[5] = 0;
X
X		s.busid = 1;
X		s.dptr = (char far *)&init_buf;
X		s.dlen = 512;
X		s.cptr = (char far *)cmd;
X
X		scsiop(&s);
X
X		if (s.error == 0)
X		{
X		    *bpb_array[j] = init_buf.disk_bpb;
X			sprintf(msg, "LUN %d, name %s with %u blocks online as %c:\n", j,
X					init_buf.disk_name,
X				    bpb_array[j]->nsectors,
X					'A' + j + request->padding[0]);
X			biosputs(msg);
X		}
X		else
X		{
X			sprintf(msg, "LUN %d is not available\n", j);
X			biosputs(msg);
X		    break;
X		}
X	}
X
X	biosputs("\n");
X
X	request->media = j;   /* # of drives  */
X	request->address = (char far *)&edata;
X	request->count = (unsigned)bpb_array;
X	request->sector = (unsigned)_CS;
X	return (0);
X}
X
X
Xmedia_chk()
X{
X    request->address = (char far *)1;
X	return (0);
X}
X
X
Xbuild_bpb()
X{
X	request->count = (unsigned)(bpb_array[request->unit]);
X	request->sector = (unsigned)_CS;
X	return (0);
X}
X
X
X
Xread()
X{
X	static struct scsireq s;
X	int lun;
X	static char cmd[6];
X
X	/*
X	sprintf(msg, "Read of %u sectors from %u to %Fp\n", request->count,
X		request->sector, request->address);
X	biosputs(msg);
X	*/
X
X	lun = request->unit;
X
X    cmd[0] = 0x08;
X    cmd[1] = (lun&01) << 5;
X    cmd[2] = request->sector >> 8;
X    cmd[3] = request->sector & 0xff;
X    cmd[4] = request->count;
X	cmd[5] = 0;
X
X	s.busid = 1;
X	s.dptr = (char far *)request->address;
X	s.dlen = request->count * 512;
X	s.cptr = (char far *)cmd;
X
X	scsiop(&s);
X
X	if (s.error != 0)
X	{
X		sprintf(msg, "Bad read of %u sectors from %u to %Fp\n", request->count,
X			request->sector, request->address);
X		biosputs(msg);
X		sprintf(msg, "LUN %d. SCSI error code 0x%x.\n", lun, s.error);
X		biosputs(msg);
X	    perr(lun);
X	}
X
X    return (s.error? 0x8004 : 0);
X}
X
X
Xwrite_vfy()
X{
X    return (write());
X}
X
Xwrite()
X{
X	static struct scsireq s;
X	int lun;
X	static char cmd[6];
X
Xretry:
X
X	/* 
X	sprintf(msg, "Write of %u sectors from %u to %Fp\n", request->count,
X		request->sector, request->address);
X	biosputs(msg);
X	*/
X
X	lun = request->unit;
X
X    cmd[0] = 0x0a;
X    cmd[1] = (lun&01) << 5;
X    cmd[2] = request->sector >> 8;
X    cmd[3] = request->sector & 0xff;
X    cmd[4] = request->count;
X	cmd[5] = 0;
X
X	s.busid = 1;
X	s.dptr = (char far *)request->address;
X	s.dlen = request->count * 512;
X	s.cptr = (char far *)cmd;
X
X	scsiop(&s);
X
X	if (s.error != 0)
X	{
X		sprintf(msg, "Bad write of %u sectors from %u to %Fp\n", request->count,
X			request->sector, request->address);
X		biosputs(msg);
X		sprintf(msg, "LUN %d. SCSI error code 0x%x.\n", lun, s.error);
X		biosputs(msg);
X	    if (perr(lun) == 0x12)
X		    goto retry;
X	}
X
X    return (s.error? 0x8004 : 0);
X}
X
X
Xint (*(dispatch[]))() = {
X	init,
X	media_chk,
X	build_bpb,
X	badfun,
X	read,
X	badfun,
X	badfun,
X	nullfun,
X	write,
X	write_vfy,
X	badfun,
X	nullfun,
X	badfun,
X	badfun,
X	badfun,
X	nullfun,
X	badfun,
X};
X
X
Xabort()
X{
X	biosputs("Driver aborted\n");
X	for(;;);
X}
X
X
X
X
Xchar sensedata[10];
X
Xunsigned
Xlogadr()
X{
X    int ladr,hadr;
X
X    hadr = sensedata[2];
X    ladr = sensedata[3];
X    return ((hadr<<8)+(ladr&0xff));
X}
X
X
Xperr(lun)
Xint lun;
X{
X    int code;
X    unsigned phadr;
X
X	getsense(lun);
X
X    code = sensedata[0];
X
X	biosputs("\nDISK ERROR\n");
X    prerror(code & 0x7f);
X    biosputs("\n");
X    if (code & 0x80)
X    {
X        code &= 0x7f;
X        sprintf(msg,  "Logical address: %x\n",logadr());
X		biosputs(msg);
X        if (code != 0x14 && code != 0x10)
X        {
X            phadr = xlate(lun, logadr());
X            sprintf(msg,  "Physical address: %x\n",phadr);
X			biosputs(msg);
X        }
X    }
X    else
X        biosputs("No address\n");
X
X	return (code);
X}
X
X
X
Xstatic char *errcodes[] = {
X
X        "No error",
X        "No index signal",
X        "No seek complete",
X        "Drive fault",
X        "Drive not ready",
X        0,
X        "No track 00",
X        0,
X        0,
X        0,
X        0,
X        0,
X        "Write fault",
X        0,
X        0,
X        0,
X        "ID CRC error",
X        "Uncorrectable data error",
X        0,
X        "Data address mark not found",
X        "Record not found",
X        "Seek error",
X        0,
X        0,
X        "Corrected data error",
X        0,
X        "Format error",
X        0,
X        0,
X        0,
X        0,
X        0,
X        "Invalid command",
X        "Illegal block address",
X        "Command aborted",
X        "Invalid parameters",
X        0,
X        "Controller error",
X        0,
X        0,
X        0,
X        0,
X        0,
X        0,
X        0,
X        0,
X        0,
X        0  };
X
Xstatic prerror(n)
Xint n;
X{
X    if (n<0 || n >= 48)
X        biosputs("Invalid error code");
X    else if (errcodes[n] == 0)
X	{
X        sprintf(msg, "Unknown error code %.2x",n);
X		biosputs(msg);
X	}
X    else
X	{
X        sprintf(msg, "Error %.2x: %s",n,errcodes[n]);
X		biosputs(msg);
X	}
X}
X
X
Xstatic xlate(lun,blk)
Xint lun;
Xunsigned blk;
X{
X    static char cmd[4];
X    static char data[4];
X	static struct scsireq s;
X
X    cmd[0] = 0x0f;
X    cmd[1] = (lun&01) << 5;
X    cmd[2] = blk >> 8;
X    cmd[3] = blk & 0xff;
X    cmd[4] = cmd[5] = 0;
X
X
X    s.busid = 1;
X    s.cptr = (char far *)cmd;
X    s.dptr = (char far *)data;
X    s.dlen = 4;
X
X	scsiop(&s);
X
X    if (s.error != 0)
X        return(-1);
X    else
X        return(data[2] * 256 + (data[3] & 0xff));
X
X}
X
X
Xgetsense(lun)
Xint lun;
X{
X    static char cmd[] = { 3, 0, 0, 0, 4, 0 };
X	static struct scsireq s;
X
X    cmd[1] = (lun&01) << 5;
X
X    s.busid = 1;
X    s.cptr = (char far *)cmd;
X    s.dptr = (char far *)sensedata;
X    s.dlen = 4;
X
X	scsiop(&s);
X
X    return (s.error);
X}
X
X
Xint _realcvtvector;
X
EOF_driver3.c
echo 'Done'

echo -n 'Extracting dtest.c ... '
sed 's/^X//' > dtest.c << 'EOF_dtest.c'
X#include "driver.h"
X
Xstruct reqhdr req;
Xmain()
X{
X    strat((struct reqhdr far *)&req);
X	intr();
X}
EOF_dtest.c
echo 'Done'

echo -n 'Extracting makefile ... '
sed 's/^X//' > makefile << 'EOF_makefile'
Xdriver3.bin: null.obj driasm.obj driver3.obj scsi.obj makefile
X	tlink /m null.obj driasm.obj driver3.obj scsi.obj ,driver3.exe,,\turboc\lib\cs
X	exe2bin driver3.exe driver3.bin
X	del driver3.exe
X
Xdriver3.obj:	driver3.c driver.h scsi.h makefile
X	tcc -c -mt -r- driver3.c
X
Xdriver3.asm:	driver3.c driver.h scsi.h makefile
X	tcc -S -mt -r- driver3.c
X
Xscsi.obj:	scsi.c scsi.h makefile
X	tcc -c -mt scsi.c
X
Xnull.obj:	null.c driver.h makefile
X	tcc -c -mt null.c
X
Xdriasm.obj:		driasm.s dheader.s
X	masm driasm.s,driasm.obj,nul.lst,nul.crf
X
Xdtest:  null.obj driasm.obj driver3.obj makefile
X    tcc -mt -odtest null.obj driasm.obj driver3.obj dtest.c
EOF_makefile
echo 'Done'

echo -n 'Extracting null.c ... '
sed 's/^X//' > null.c << 'EOF_null.c'
X
X
EOF_null.c
echo 'Done'

echo -n 'Extracting scsi.c ... '
sed 's/^X//' > scsi.c << 'EOF_scsi.c'
X
X
X#include <stdio.h>
X#include <dos.h>
X#include "scsi.h"
X
X
X/* This is the host adapter address */
X#define SCSIPORT 0x320
X
X/* These are the variables that hold the data for the scsiop routine*/
X
Xstatic char busid;
Xstatic char far *cptr; 
Xstatic char far *dptr;
Xstatic unsigned dlen;
Xstatic unsigned adlen;
X
Xstatic struct scsibuf *curbuf;
X
Xstatic char rwflag;
Xstatic int status;
Xstatic int message;
X
X	char scsimsg[80];
X
X/* Following are the commands for SCSI operations */
X
Xvoid
Xscsiop(req)
Xstruct scsireq *req;
X{
X    int s;
X	long timeout;
X	long maxtimeout;
X	long contimeout;
X	int statuscnt;
X
X	cptr = req->cptr;
X	dptr = req->dptr;
X	dlen = req->dlen;
X	busid = req->busid;
X
X	req->error = 0;
X
X	if (req->timeout > 0)
X	{
X		maxtimeout = req->timeout * 5000L;
X		contimeout = req->timeout * 5000L;
X	}
X	else
X	{
X	    maxtimeout = 100 * 5000L;  /* 10 second default */
X		contimeout = 100 * 5000L;
X	}
X
X	dma_connect();   /* tell controller to obey dma chip */
X
X	if (connect(contimeout) != 0)	 /* select the controller on the scsi bus */
X	{
X		req->error |= S_BUSERROR | S_NOCONNECT;
X		return;
X	}
X
X	statuscnt = 0;
X
X	for (;;)
X	{
X		timeout = maxtimeout;
X	    while (((s = inportb(SCSIPORT+1)) & 0x80) == 0)
X		{
X			if (timeout-- == 0)
X			    goto timedout;
X
X			if ((s & 0x10) == 0)
X			    goto nomore;		/* No longer connected */
X		}
X
X		switch (s & 0x68)
X		{
X		case 0x40:    /* Get data */
X			if (dlen <= 0)
X			{
X				req->error |= S_BUSERROR | S_OVERRUN;
X				inportb(SCSIPORT);
X			}
X			else
X			{
X				rwflag = 0;
X				do_dma();
X			}
X			break;
X
X		case 0x00:    /* Put data */
X			if (dlen <= 0)
X			{
X				req->error |= S_BUSERROR | S_OVERRUN;
X				outportb(SCSIPORT, 0);
X			}
X			else
X			{
X				rwflag = 1;
X				do_dma();
X			}
X			break;
X
X		case 0x20:    /* Put command */
X		    outportb(SCSIPORT, *cptr++);
X		    short_delay();
X			break;
X
X		case 0x60:    /* Get status */
X		    status = inportb(SCSIPORT);
X			statuscnt++;
X			req->error |= (status & 0xff);
X		    short_delay();
X			break;
X
X		case 0x68:    /* Get message */
X		    message = inportb(SCSIPORT);
X			if (message != 0)
X			    req->error |= S_BUSERROR | S_BADMESSAGE;
X		    short_delay();
X			break;
X
X		default:
X		    req->error |= S_BUSERROR | S_BADTRANS;
X		    if (s & 0x40)
X			    inportb(SCSIPORT);
X			else
X			    outportb(SCSIPORT, 0);
X		    short_delay();
X			break;
X		}
X
X	}
X
Xnomore:
X	if (statuscnt != 1)
X	    req->error |= S_BUSERROR | S_BADSTATUS;
X	outportb(SCSIPORT, 0);	/* Turn off data bus lines */
X	return;
X
Xtimedout:
X	req->error |= S_BUSERROR | S_TIMEOUT;
X	outportb(SCSIPORT, 0);	/* Turn off data bus lines */
X	return;
X}
X
X
X
X/* This waits until REQ is deasserted.  It will time out after a while */
Xshort_delay()
X{
X    int j;
X
X	j = 200;
X	while (j--)
X	{
X	    inportb(SCSIPORT+1);
X	}
X}
X
X
Xdo_dma()
X{
X	/* If the data buffer crosses a 64k boundary, it may take
X	more than 1 operation to get it. Thus the loop */
X
X	while (dlen > 0)
X	{
X		setup_dma();
X		arm_dma();
X		if (dma_wait() < 0)		/* wait for actual i/o */
X		    break;
X		disarm_dma();
X	}
X}
X
X
Xdo_vdma()
X{
X	/* If the data buffer crosses a 64k boundary, it may take
X	more than 1 operation to get it. Thus the loop */
X
X	/* printf((CHARPTR)"        Doing vdma with curbuf: %Fp curbuf->dptr: %Fp\n", curbuf,  curbuf->dptr); */
X
X	while (curbuf->dptr != NULL)
X	{
X	    dptr = curbuf->dptr;
X		dlen = curbuf->dlen;
X		curbuf++;
X
X		/* printf((CHARPTR)"        Doing a VDMA iteration from %Fp of %d bytes\n", dptr, dlen); */
X
X		while (dlen > 0)
X		{
X			setup_dma();
X			arm_dma();
X			if (dma_wait() < 0)		/* wait for actual i/o */
X			    break;
X			disarm_dma();
X		}
X	}
X}
X
X
X/* This resets the scsi bus: */
X
Xreset_scsi()
X{
X	static int cnt;
X
X	cnt = 256;
X	while (cnt-- > 0)
X	{
X		outportb(SCSIPORT+1,0);  /* was al */
X		inportb(SCSIPORT+2);
X		if ((inportb(SCSIPORT+1) & 0xfc) == 0)
X		    return (0);
X	};
X	return (-1);
X}
X
X
X
X
X/* This sets up the dma based on dptr, dlen and rwflag */
X
Xsetup_dma()
X{
X    unsigned pseg, poff;  /* physical address segment and offset */
X
X	if (dlen <= 0)
X	    return;
X
X	poff = (unsigned)((FP_SEG(dptr) << 4) + FP_OFF(dptr));
X	pseg = (unsigned)((FP_SEG(dptr) + (FP_OFF(dptr) >> 4)) >> 12);
X
X    if ( poff + dlen < poff) /* wraparound */
X	    adlen = -poff;  /* # of bytes in current 64k chunk */
X	else
X	    adlen = dlen;
X
X	disable();
X	outportb(0x0a, 0x07); /* ;clear channel 3 mask */
X
X	/* write to mode register for: single mode, increment,
X		auto. init. disable, read or write, channel 3. */
X
X	outportb( 0x0b, rwflag ? 0x4b : 0x47); /* see if read or write */
X
X	/* 4 high addr bits go in special i/o port */
X	outportb (0x82, pseg);
X
X	outportb(0x0c, 0); /* clear byte pointer flip-flop */
X
X	/* send both bytes of address to address register 3 */
X	outportb(0x06, poff & 0xff);
X	outportb(0x06, poff >> 8);
X
X	outportb(0x07, (adlen-1) & 0xff);
X	outportb(0x07, (adlen-1) >> 8);  	/* send byte count - 1 to dma count reg 3 */
X
X	enable();  /* restore interrupts */
X
X	dptr += adlen;
X	dlen -= adlen;
X
X}
X
X
X
X/* This connects to the correct address on the scsi bus */
X
Xconnect(count)
Xlong count;
X{
X
X	if (free_wait(count) != 0)
X	    return (-2);
X
X	outportb(SCSIPORT, busid);	/* output bus id for connect */
X	outportb(SCSIPORT+2, 0); /* assert select */
X
X	while (count--)
X	{
X  	    if ((inportb(SCSIPORT+1) & 0x10) == 0x10)
X		{
X			/* Clear select */
X			inportb(SCSIPORT+2);
X			return(0);
X		}
X	}
X
X	/* Timed out */
X    return (-1);
X}
X
X
X/* This waits until the bus is free before making a connection */
X
Xfree_wait(count)
Xlong count;
X{
X	while (count--)
X	{
X  	    if ((inportb(SCSIPORT+1) & 0xfc) == 0)
X		    return (0);
X	}
X
X	return (-1);  /* time out */
X}
X
X
X
X/* This tests the bus status against ah, and returns whether or
Xnot the condition is met. We check twice. */
X
Xscsi_test(cond)
Xint cond;
X{
X    if ((inportb(SCSIPORT+1) & 0xf8) == cond && (inportb(SCSIPORT+1) & 0xf8) == cond)
X	    return (0);
X	else
X	    return(-1);
X}
X
X
X
X/* This loops, waiting for the DMA finish, for the data to run out.
XIt has an extra-long timeout. dmatimeout gives the timeout length */
X
Xdma_wait()
X{
X    static long cnt;
X#ifdef DEBUG
X	static int lobyte;
X#endif
X
X	cnt = 200000L;
X	while (cnt--)
X	{
X		/* See if we have reached the status phase */
X	    /* if ((inportb(SCSIPORT+1) & 0xf8) == 0xf0) */
X		if (scsi_test(0xf0) == 0)
X		{
X#ifdef DEBUG
X			outportb(0x0c, 0); /* clear byte pointer flip-flop */
X			lobyte = inportb(0x07);
X			printf((CHARPTR)"DMA residue at status: %d bytes\n",
X				256 * inportb(0x07) + lobyte + 1);
X#endif
X			return (1);
X		}
X
X		/* See if DMA has reached Terminal Count */
X		if (inportb(0x08) & 0x08)
X		{
X#ifdef DEBUG
X			outportb(0x0c, 0); /* clear byte pointer flip-flop */
X			lobyte = inportb(0x07);
X			printf((CHARPTR)"DMA residue at TC: %d bytes\n",
X				256 * inportb(0x07) + lobyte + 1);
X#endif
X			return (2);
X		}
X		
X	}
X#ifdef DEBUG
X	printf((CHARPTR)"dma_wait times out\n");
X	outportb(0x0c, 0); /* clear byte pointer flip-flop */
X	lobyte = inportb(0x07);
X	printf((CHARPTR)"DMA residue: %d bytes\n", 256 * inportb(0x07) + lobyte + 1);
X#endif
X
X	return (-1);
X}
X
X
Xarm_dma()
X{
X	/* clear mask bit of channel 3, allowing dma to start */
X	outportb(0x0a, 0x03);
X}
X
X
Xdisarm_dma()
X{
X    /* set mask bit of channel 3 */
X	outportb(0x0a, 0x07);
X}
X
X
X/* These either enable or disable the host adapter's DRQ line
X(for the DMA) or its INT line (to the interrupt controller). */
X
Xdma_connect()
X{
X	outportb(SCSIPORT+3, 0x01);
X}
X
X
Xdma_disconnect()
X{
X    outportb(SCSIPORT+3, 0);
X}
X
X
EOF_scsi.c
echo 'Done'

echo -n 'Extracting scsi.doc ... '
sed 's/^X//' > scsi.doc << 'EOF_scsi.doc'
XINTRODUCTION:
X
XThis package contains three things:
X
X1. A library of routines to interface to an Ampex Megastore PC SCSI
Xhost adapter, written in Turbo C 1.5.
X
X2. A generic MSDOS device driver, written in C and a little assembler.
X
X3. An actual device driver for SCSI disks that uses 1 and 2 above.
X
XFeel free to distribute this however you like.
X
XTHE AMPEX MEGASTORE HOST ADAPTOR:
X
XThe Ampex Megastore SCSI host adapter is a relatively "dumb" adapter.
XIts virtue is that you can get one for $15 or so from Halted Specialities,
Xin Santa Clara, CA.  It also has supporting software (what you have here)
Xthat will let you use it to talk to SCSI disks, tapes, etc., hooked up to
Xyour PC.  I use one to communicate with two hard disks and a tape drive on
Xmy AT&T PC6300.
X
XThis board comes with no documentation.  Here is what I have learned:
X
XThe board uses I/O ports 0x320 to 0x323.  These addresses clash with
Xthe standard PC hard disk adaptor addresses.  However, you can set
Xa switch or two on the board to relocate these addresses to 0X620 to
X0x623.  You will have to change a #define in scsi.c if you do this.
XUnfortunately, I forget which switches on the board change the address.
XTry them and find out.
X
XThe board also uses DMA channel 3, and an interrupt line.  However,
Xthe included code does not use the interrupt, or allow the board to
Xgenerate it.  There are jumpers on the board to change the DMA or interrupt
Xnumbers, if necessary.  If the DMA channel is changed, code in scsi.c
Xwill need a little bit of surgery.
X
XRemove the EPROM on the board!  The code in it seems to require a special
Xnon-standard SCSI controller to work.
X
XHere is a description of what the I/O ports do, as deduced by me:
X
X
XAddress		Read			Write
X----------------------------------------------------------------------
X0x320:  	Data			Data
X0x321:		SCSI Control Lines	Activate SCSI RESET line
X0x322:		Clear SCSI SEL, RESET	Activate SCSI SEL line
X0x323:		XXXXX			Enable DMA
X
X
XHere is more info on reading port 321:
X
XBit	Signal
X-----------------------------
X
X0	A switch on the board (I forget which)
X1	Another switch
X2	???
X3	SCSI MSG line
X4	SCSI BSY line
X5	SCSI C/D line
X6	SCSI R/W line
X7	SCSI REQ line
X
X
XWriting any value to ports 321 and 322 seems to trigger the desired function.
XSee the code in scsi.c for how these signals are used.  The functionality
Xprovided in scsi.c should complete enough so that you do not have to worry
Xabout the above information.
X
X
X
X
X
XTHE SCSI CODE:
X
X    Scsi.c contains two routines:
X
X	void scsiop(struct scsireq *req);
X	void scsiopv(struct scsivreq *req);
X
XThe first one is passed a pointer to a structure of data describing
Xa SCSI operation, and does it.  The second one does the same thing,
Xbut allows you to specify multiple data buffers in separate segments.
XThis allows you to send or receive more than 64K of data in one operation.
X
XThese is the structure that is passed to scsiop(): 
X
X	struct scsireq {
X	    char far *dptr;
X	    char far *cptr;
X	    unsigned dlen;
X	    char busid;
X	    int error;
X	    int timeout;
X	};
X
X
XDptr points to your data buffer.  If the command you are executing
Xreads or writes no data, this can be NULL.
X
XCptr points to  your command bytes for the SCSI operation.
XThe code will read as many command bytes from here as the SCSI
Xdevice asks for.
X
XDlen is the length of the data buffer.  If you have more than 64K-1
Xbytes of data, use scsiopv() instead.  The code will not read or write
Xpast the end of the buffer.  If the SCSI device tries to, an overrun
Xerror will be generated.
X
XBusid is the SCSI bus ID that the command will be sent to.
X
XUpon return, error will be filled in with an error code.
X
XTimeout is the number of .1 second units that the code should wait
Xbefore giving up and generating a timeout error.  Specifying zero
Xwill give a default five-second timeout.  This timing depends on
Xloops in the code, and will have to be adjusted for extra fast or
Xslow PCs.  It is correct for a 8 MHz V30.
X
X
XHere is the meaning of the error word:
X
XThe low 8 bits contain the SCSI Status byte generated by the
XSCSI device at the end of the transaction.  The upper 8 bits
Xare as follows:
X
X	/* scsireq error bits */
X	#define S_BUSERROR		0x8000
X	#define S_BADSTATUS		0x4000
X	#define S_BADMESSAGE		0x2000
X	#define S_NOCONNECT		0x1000
X	#define S_TIMEOUT		0x0800
X	#define S_OVERRUN		0x0400
X	#define S_BADTRANS		0x0200
X
XNote that these bits represent hardware errors or errors in the SCSI
Xprotocol itself.  Errors in the device, such as a bad block,
Xwill cause a non-zero Status byte to be generated.  The SCSI
XGet Sense command will have to be used to find out more.
X
XThese are the arguments to scsiopv():
X
X	struct scsivreq {
X	    struct scsibuf far *bufptr;
X	    char far *cptr;
X	    char busid;
X	    int error;
X	    int timeout;
X	};
X
X	struct scsibuf {
X	    char far *dptr;
X		unsigned dlen;
X	};
X
X
XScsiopv() works the same as scsiop(), except that instead of taking
Xa single pointer to a data buffer, it takes a pointer to a list
Xof data buffers of varying lengths.  The last buffer in the list
Xshould have a length of zero and a NULL address.   These buffers
Xwill be read or written in order.
X
X
X
XTHE MSDOS DEVICE DRIVER CODE:
X
XThe makefile included describes how to make the SCSI disk device driver
Xin this package.  The tricky code is generic, and by changing a couple of,
Xfiles, you can make your own device driver.
X    The files driasm.s, driver.h, and null.c, and they way the makefile
Xcompiles and links them, should not be changed.  The file dheader.s
Xshould have to be modified only if you need to make a character device
Xinstead of a block device.  The file driver3.c should be modified
Xto do what you need your driver to do.  The only procedure called externally 
Xin this file is dointr().  It  is passed a pointer to the request header
Xthat describes what should be done.  The current version basically
Xvectors to a seperate procedure for each request type.  You should probably
Xkeep this arrangement, and change as little code as possible.  The file
Xscsi.c is part of this driver, but would of course be omitted if you
Xwere not using a SCSI device.  Note that making "dtest" with the makefile
Xshould produce error-free compiles and link if everything is written correctly.
XThe file dtest.c can be added to to make a program that will test your driver
Xwithout having to load it at boot time.
X    All of the above assumes you know enough about how device drivers are
Xsupposed to be written.  I recommend the book "Advanced MS-DOS" by Microsoft
XPress as a good reference for learning this.
X
X
XGood Luck,
X
XDoug Braun
X7976 W. Zayante Rd.
XFelton, CA 95018
X
EOF_scsi.doc
echo 'Done'

echo -n 'Extracting scsi.h ... '
sed 's/^X//' > scsi.h << 'EOF_scsi.h'
X
X
X/* scsireq error bits */
X#define S_BUSERROR		0x8000
X#define S_BADSTATUS		0x4000
X#define S_BADMESSAGE	0x2000
X#define S_NOCONNECT		0x1000
X#define S_TIMEOUT		0x0800
X#define S_OVERRUN		0x0400
X#define S_BADTRANS		0x0200
X
X
Xstruct scsireq {
X    char far *dptr;
X    char far *cptr;
X	unsigned dlen;
X	char busid;
X	int error;
X	int timeout;
X};
X
Xstruct scsivreq {
X    struct scsibuf far *bufptr;
X    char far *cptr;
X	char busid;
X	int error;
X	int timeout;
X};
X
Xstruct scsibuf {
X    char far *dptr;
X	unsigned dlen;
X};
X
X
Xvoid scsiop(struct scsireq *req);
Xvoid scsiopv(struct scsivreq *req);
X
EOF_scsi.h
echo 'Done'

exit 0

Doug Braun				Intel Corp CAD
					408 765-4279

 / decwrl \
 | hplabs |
-| oliveb |- !intelca!mipos3!cadev4!dbraun
 | amd    |
 \ qantel /