/**** BTNtape Handler for SCSI tape drives ****/ /**** Author: Bob Rethemeyer (DrBob@cup.portal.com) ****/ #define TVERSION "-BTNTAPE V2.0 RAR-" ## __DATE__ /* (c) Copyright 1990, 1991 Robert Rethemeyer. * This software may be freely distributed and redistributed, * for non-commercial purposes, provided this notice is included. *----------------------------------------------------------------------- * BTNtape is an AmigaDOS device handler to make a simple DOS TAPE: device. * It converts DOS packets for the device into I/O requests to a * "SCSI-direct" compatible device driver. It is based on "my.handler" * by Phillip Lindsay and a SCSI-direct program by Robert Mitchell. * Source is ANSI C compliant. Compile with SAS/C v5 or Manx v5. * * This handler works in conjunction with the accompanying TapeMon program. *---------------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined AZTEC_C #include /*#define strtoul strtol */ #elif defined LATTICE #include #include #endif #include "tape.h" #include "tplink.h" /* sense keys */ #define NOS 0x00 /* no sense */ #define RCV 0x01 /* recovered error */ #define UAT 0x06 /* unit attention */ #define VOF 0x0d /* volume overflow */ /* pseudo sense keys returned by DoSense */ #define FMK 0x10 /* filemark */ #define EOM 0x11 /* end of tape */ #define ILI 0x12 /* incorr leng */ #define SERR 0x13 /* SCSI error */ struct things { /* a collection of things we will have to alloc */ UBYTE cmdbuff[16]; /* SCSI command buff */ UBYTE snsarea[64]; /* sense data buffer */ UBYTE inqdata[40]; /* inquiry data buff */ struct SCSICmd scsicmd; /* SCSIdirect cmd buff */ UBYTE pad[16]; /* SCSICmd is smaller in old include files */ } ; /*======== Global data */ struct IntuitionBase *IntuitionBase; UBYTE *cdb; /* pointer to tape command buffer */ UBYTE *sns; /* pointer to sense data buffer */ UBYTE *inq; /* pointer to inquiry data */ struct SCSICmd *cmd; /* pointer to scsidirect command */ struct IOStdReq *ior; /* pointer to io request structure*/ UBYTE *TapeBuff[2] /* pointers to 2 tape buffers */ ={NULL,NULL}; struct tplink *linktp; /* pointer to link structure */ ULONG blknum; /* block number for io operation */ ULONG blksize = 512; /* bytes per tape block */ ULONG numblks = 1; /* number of blocks per io oper */ ULONG TBSize; /* bytes in a tape buffer */ ULONG rwlen; /* bytes in a tape read/write */ ULONG tranlen; /* bytes/blks in a read/write */ ULONG bugmask = 0; /* 2090A bug circumvention */ ULONG fmarks = 1; /* # file marks for write/skip */ long tpsize ; /* tape size in blocks */ long reserved = 0; /* number of reserved blks at BOT */ int tapenum; /* tape volume number */ int inprog = FALSE; /* io operation in progress flag */ char *z; /* scratch */ char *devname, nbuf[32]; /* file name reference from Open()*/ char dbb[80]; /* buffer for monitor messages */ UBYTE Lun = 0; /* Logical Unit number << 5 */ UBYTE fixedbit = 0; /* fixed-block mode bit for SEQ */ /*********************** Main program ********************************/ #ifdef AZTEC_C #pragma intfunc(_main()) #endif void _main(void) { struct tplink tpl; /* structure to link hndlr & mon */ struct Process *myproc; /* ptr to handler's process struct */ struct DosPacket *mypkt; /* ptr to dos packet sent */ struct DeviceNode *mynode; /* ptr to devnode passed in pkt Arg3 */ struct things *xarea; /* ptr to dynamic misc. areas */ ULONG dvnode; /* ptr to devnode passed in pkt Arg3 */ ULONG unit=99999; /* device SCSI unit address */ ULONG bufmemtype=0; /* type of mem for dynamic buffers */ ULONG gotopos=0; /* position to open at (blk/filemark)*/ char *driver; /* name of SCSI device driver */ UBYTE *dptr; /* ptr to next byte in dos buffer */ long dflags=0; /* device flags */ long iostat; /* status of previous read */ long dcnt; /* count of dos packet bytes to move */ long mcnt; /* count of bytes to move */ long Boff; /* current offset in tape buffer */ long remain; /* bytes remaining in tape buffer */ long Supra=FALSE; /* flag for Supra circumvention */ long pkcnt; /* flag to indicate data written */ long x; int y=0; int opmode=0; /* how has the handler been opened */ #define CLOSED 0 /* not doing anything */ #define READING 1 /* reading tape */ #define WRITING 2 /* writing tape */ #define RAWMODE 3 /* raw command mode */ #define UMODSEL 4 /* user mode select */ int Bn; /* current buffer number, 0 or 1 */ int dirty=FALSE; /* buffer has unwritten data in it */ int Ctl=CTLIMM; /* Controls overlapped dos/scsi I/O */ BYTE acksig; /* monitor acknowledge signal number */ UBYTE varblk = 0; /* variable-block flag for SEQ */ /*======== Startup */ IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",0); myproc = (struct Process *) FindTask(0L); /* find this process */ mypkt = taskwait(); /* wait for startup packet */ /* packet: Arg1=BSTR to name, Arg2=BSTR to startup string, Arg3=BPTR devnode*/ mynode = (struct DeviceNode *) BADDR(mypkt->dp_Arg3); dvnode = (ULONG) mypkt->dp_Arg3; /*======== Create linkage for the tape monitor: install pointer to tplink ======== structure in the free pointer of the task block, so the tape ======== monitor can find it after FindTask(). */ tpl.keyword = "TapeHandler"; tpl.version = TVERSION; tpl.devnode = (void *)mynode; tpl.dbb = dbb; tpl.unit = &unit; tpl.Lun = &Lun; linktp = &tpl; ((struct Task *)myproc)->tc_UserData = (APTR) linktp; /*======== Extract info from mountlist Startup parameter. It may be ======== enclosed in quotes, and each item is separated by a single '/'. ======== First item is always the driver name, others may be in any order. */ z = (char *)BADDR(mypkt->dp_Arg2)+1 ; /* Arg2= BSTR to mountlist 'Startup'*/ if(*z=='\"') { /* remove quotes if any */ z++; z[strlen(z)-1]= '\0' ; } driver = tpl.driver = z; for(; *z != '/'; z++); *(z++) = '\0'; toUPPER(z); while (y!=100) { switch(y=getstart(&x)) { case 0: /* UN */ unit = (ULONG) x; break; case 1: /* LU */ Lun = ((UBYTE)x & 7) << 5 ; break; case 2: /* BS */ blksize = (ULONG) x; break; case 3: /* NB */ numblks = (ULONG) x; break; case 4: /* RB */ reserved = x; break; case 5: /* BT */ bufmemtype = (ULONG) x; break; case 6: /* FM */ fmarks = x & 0xff; break; case 7: /* VB */ if(x) varblk = 1; break; case 8: /* OV */ if(!x) Ctl = CTLWAIT; break; case 9: /* DF */ dflags = x; break; case 10: /* SU */ Supra = x; break; case 11: /* C9 */ if(x) bugmask = 0x01000000; break; case 99: /* ?? */ tpl.badparm=TRUE; break; default: ; } } rwlen = TBSize = numblks * blksize; /* size of a tape buffer */ if(Supra) rwlen = 0; /*======== Allocate some memory for non-data buffers */ if( !(xarea = (struct things *) AllocMem(sizeof(struct things), bufmemtype | MEMF_CLEAR) )) { returnpkt(mypkt,DOSFALSE,ERROR_NO_FREE_STORE); CloseLibrary((struct Library *)IntuitionBase); return; } cdb = &xarea->cmdbuff[0]; sns = &xarea->snsarea[0]; inq = tpl.inquiry = &xarea->inqdata[0]; cmd = &xarea->scsicmd; ior = (struct IOStdReq *) CreateExtIO( CreatePort(0,0), sizeof(struct IOStdReq)); /*======== Open the SCSIdirect device */ if ( OpenDevice(driver,unit,(struct IORequest *)ior,dflags) ) { returnpkt(mypkt,DOSFALSE,ERROR_INVALID_COMPONENT_NAME); CloseLibrary((struct Library *)IntuitionBase); FreeMem(xarea,sizeof(struct things)); return; } mynode->dn_Task = &myproc->pr_MsgPort; /* install handler taskid */ returnpkt(mypkt,DOSTRUE,mypkt->dp_Res2); /* reply to initial packet */ /*======== Allocate the signal that TapeMon will ======== use to acknowledge MPR requests. */ acksig = AllocSignal(-1); if(acksig != -1) tpl.handsig = 1UL << acksig; /* else { monitor will not attempt to signal us } */ /*======= Find the SCSI device type by INQUIRY. Sequential or direct access? */ if(TapeIO(INQUIRY,0,CTLWAIT)) TapeIO(TSENSE,0,CTLWAIT); inq[36] = 0; /* null-terminate vendor info */ if(SEQ) { reserved = 0; fixedbit = varblk ^ 0x01; /* fixed or variable block mode */ tranlen = (fixedbit) ? numblks : TBSize; } else /* DAC */ tranlen = numblks; /* =========== The main packet processing loop =============== */ for (;;) { mypkt = taskwait(); /* wait for a packet */ switch(mypkt->dp_Type) { case ACTION_FINDINPUT: /*----------- Open() ------------*/ case ACTION_FINDOUTPUT: if(opmode != CLOSED) { returnpkt(mypkt,DOSFALSE,ERROR_OBJECT_IN_USE); break; } /* Allocate storage for buffers */ TapeBuff[0] = (UBYTE *) AllocMem(TBSize, bufmemtype | MEMF_CLEAR); TapeBuff[1] = (UBYTE *) AllocMem(TBSize, bufmemtype | MEMF_CLEAR); if (!TapeBuff[0] || !TapeBuff[1]) { freestuff(); returnpkt(mypkt,DOSFALSE,ERROR_NO_FREE_STORE); MPR0("Can't get memory for tape buffers\n"); break; } /* Determine open mode */ z = (char *)BADDR(mypkt->dp_Arg3); x = (UBYTE)(*z); devname = (char *)memcpy(nbuf, z+1, x); *(devname+x) = '\0'; toUPPER(devname); for(z=devname; *(z++)!=':' ;); if (!strcmp(z,"RAWCMD")) opmode=RAWMODE; else if(!strcmp(z,"MODESEL")) opmode=UMODSEL; else { /* normal read/write */ opmode = (mypkt->dp_Type==ACTION_FINDINPUT) ? READING : WRITING; if(!strcmp(z,"*")) gotopos=blknum; /* current position */ else if(*z>='0' && *z<='9') /* specific position */ gotopos = (ULONG) strtoul (z,NULL,0); else if(*z=='\0') gotopos=0; /* beginning of tape */ else goto BADNAME; } if(opmode>=RAWMODE && mypkt->dp_Type==ACTION_FINDINPUT) { BADNAME: freestuff(); /* can't read from raw/ms */ opmode=CLOSED; returnpkt(mypkt,DOSFALSE,ERROR_ACTION_NOT_KNOWN); break; } /* Check device ready */ if(x=TapeIO(TREADY,0,CTLWAIT)) { y=DoSense(x); if (y==UAT) blknum = reserved; else if(y==NOS) ; else { freestuff(); opmode=CLOSED; returnpkt(mypkt,DOSFALSE,ERROR_DEVICE_NOT_MOUNTED); break; } } if(opmode<=WRITING) { /* Check write-prot, block length */ if(TapeIO(MDSNS,0,CTLWAIT)) TapeIO(TSENSE,0,CTLWAIT); else { if(opmode==WRITING && (sns[2] & 0x80)) { freestuff(); opmode=CLOSED; returnpkt(mypkt,DOSFALSE,ERROR_DISK_WRITE_PROTECTED); break; } if(sns[3] >= 8) { x = *((long *) &sns[8]) & 0x00ffffff; /* get block len */ if(x != blksize) { /* set block size with Mode Select */ TapeIO(MDSET,(int)varblk,CTLWAIT); TapeIO(TSENSE,0,CTLWAIT); } } } /* Position tape */ if(gotopos==0) { /* tape: */ TapeIO(TREWIND,0,CTLWAIT); blknum = reserved; } else if(gotopos != blknum) { /* tape:n */ if(DAC) blknum = gotopos; else { TapeIO(TREWIND,0,CTLWAIT); if(x=TapeIO(TSKIP,(int)gotopos,CTLWAIT)) DoSense(x); blknum = 0; } } else if(SEQ) blknum = 0; /* tape:* */ /* Get capacity for 3M tape */ if(DAC) { tpsize = 0x7fffffff; if(TapeIO(RDCAP,0,CTLWAIT)) TapeIO(TSENSE,0,CTLWAIT); else { tpsize = ((sns[2] << 8) | sns[3]) + 1; MPR1("Capacity: %u blocks\n", tpsize); } } } dirty=FALSE; inprog=FALSE; Boff=0; pkcnt=0; tapenum=1; MPR2("%s Opened at block %u\n",devname,blknum); if(opmode==READING) { /* for reads, prefetch 1st buffer */ iostat = TapeIO(TREAD,0,CTLWAIT); remain=0; Bn=1; } else { remain=TBSize; Bn=0; iostat=0; } returnpkt(mypkt,DOSTRUE,mypkt->dp_Res2); break; case ACTION_END: /*----------- Close() -----------*/ switch(opmode) { case CLOSED: returnpkt(mypkt,DOSFALSE,ERROR_ACTION_NOT_KNOWN); break; case RAWMODE: if(x=TapeIO(RAWCMD,Bn,CTLWAIT)) DoSense(x); break; case UMODSEL: if(x=TapeIO(USRMODE,Bn,CTLWAIT)) DoSense(x); break; case READING: if(inprog) iostat = TapeIO(TFINISH,0,CTLWAIT); if(iostat) DoSense(iostat); break; case WRITING: if(inprog) iostat = TapeIO(TFINISH,0,CTLWAIT); if(iostat) iostat = wrteot(Bn^1,1,iostat); if(iostat==0) { if(dirty) { memset(&TapeBuff[Bn][Boff],0,remain); iostat = TapeIO(TWRITE,Bn,CTLWAIT); } if(iostat) iostat = wrteot(Bn,0,iostat); } if(iostat) { MPR0("Error writing final block\n"); } else if(pkcnt) { blknum += numblks; x=0; if(SEQ) x=TapeIO(WFMARK,0,CTLWAIT); if(x) DoSense(x); } } returnpkt(mypkt,DOSTRUE,mypkt->dp_Res2); opmode=CLOSED; freestuff(); MPR1("Closed at block %u\n",blknum); break; case ACTION_READ: /*----------- Read() ------------*/ if(opmode != READING) { MPR0("Function/mode error\n"); mypkt->dp_Arg3 = -1; goto RDEND; } dptr = (UBYTE *) mypkt->dp_Arg2; dcnt = mypkt->dp_Arg3; while(dcnt) { if(remain==0) { if(inprog) iostat = TapeIO(TFINISH,0,CTLWAIT); if(iostat) { /* check status of previous read */ y=DoSense(iostat); iostat=0; switch(y) { case RCV: y=0; break; case FMK: mypkt->dp_Arg3 = 0; break; case VOF: case EOM: /* End of Tape: (sequential) */ if(NewTape()) { /* ask for new tape */ y=0; /* and reread buffer */ iostat=TapeIO(TREAD,(Bn^1),CTLWAIT); if(iostat) y=DoSense(iostat); /* and fall thru */ else break; } default: MPR0("Error during read\n"); mypkt->dp_Arg3 = -1; } if(y) goto RDEND; } blknum += numblks; iostat = TapeIO(TREAD,Bn,Ctl); /* start refilling this buffer */ Bn ^= 1; /* switch to other (full) buffer */ remain = TBSize; Boff = 0; } mcnt = (dcnt>remain) ? remain : dcnt; memcpy (dptr, &TapeBuff[Bn][Boff], mcnt); dcnt -= mcnt ; Boff += mcnt ; dptr += mcnt ; remain -= mcnt ; } RDEND: returnpkt(mypkt,mypkt->dp_Arg3,mypkt->dp_Arg2); break; case ACTION_WRITE: /*----------- Write() -----------*/ if(opmode < WRITING) { MPR0("Function/mode error\n"); mypkt->dp_Arg3 = -1; goto WRTEND; } pkcnt++; dptr = (UBYTE *) mypkt->dp_Arg2; dcnt = mypkt->dp_Arg3; while (dcnt) { if(dcnt >= remain) { memcpy (&TapeBuff[Bn][Boff], dptr, remain); if(inprog) iostat = TapeIO(TFINISH,0,CTLWAIT); if(iostat) { /* check status of previous write */ if(iostat = wrteot(Bn^1,1,iostat)) { /* possible EOT */ mypkt->dp_Arg3 = -1; MPR0("Error during write\n"); iostat=0; dirty=FALSE; pkcnt=0; goto WRTEND; } } iostat = TapeIO(TWRITE,Bn,Ctl); /* start writing full buffer */ blknum += numblks; dcnt -= remain; dptr += remain; Boff = 0; remain= TBSize; Bn ^= 1; /* switch to other (empty) buffer */ dirty = FALSE; } else { memcpy (&TapeBuff[Bn][Boff], dptr, dcnt); remain -= dcnt; Boff += dcnt; dcnt = 0; dirty = TRUE; } } WRTEND: returnpkt(mypkt,mypkt->dp_Arg3,mypkt->dp_Res2); break; case ACTION_CURRENT_VOLUME: /* info not supported */ returnpkt(mypkt,dvnode,DOSFALSE); break; case ACTION_LOCATE_OBJECT: /* lock */ returnpkt(mypkt,mypkt->dp_Arg1,mypkt->dp_Res2); break; case ACTION_FREE_LOCK: /* unlock */ returnpkt(mypkt,DOSTRUE,mypkt->dp_Res2); break; case ACTION_FLUSH: /* flush buffers, NOOP */ returnpkt(mypkt,DOSTRUE,mypkt->dp_Res2); break; default: /* others not supported */ returnpkt(mypkt,DOSFALSE,ERROR_ACTION_NOT_KNOWN); MPR1("Unsupported_Pkt=%d\n",mypkt->dp_Type); } /* end of switch */ } /* end of loop */ } /* end of _main() */ /**************************************************************************/ int DoSense(long iocode) { /* iocode is (ioerr<<8 + cmdstatus) */ UBYTE snskey; static char *snstext[] = { "NONE" , "RECOVERED ERROR", "NOT READY", "MEDIUM ERROR", "HARDWARE ERROR", "ILLEGAL REQUEST", "UNIT ATTENTION", "DATA PROTECT", "BLANK CHECK", "KEY=9", "COPY ABORTED", "ABORTED COMMAND", "KEY=C", "VOLUME OVERFLOW", "KEY=E", "KEY=F", "FILEMARK", "END-OF-MEDIUM", "INCORRECT BLOCK LENGTH" }; static char *sderror[] = { "SELF-UNIT", "DMA", "PHASE", "PARITY", "SELECT-TIMEOUT" }; iocode=iocode>>8; if(iocode==0 || iocode==HFERR_BadStatus) { TapeIO(TSENSE,0,CTLWAIT); if((sns[0] & 0x70)==0x70) { /* extended */ if (sns[2] & 0x80) snskey = FMK; /* pseudo sense keys */ else if(sns[2] & 0x40) snskey = EOM; else if(sns[2] & 0x20) snskey = ILI; else snskey = sns[2] & 0x0f; /* real sense key */ } else snskey = sns[0] & 0x0f; /* non-extended */ linktp->sense = snstext[snskey]; /* keep last error info */ linktp->xsns1 = sns[12]; linktp->xsns2 = sns[13]; linktp->sns = sns; /* flag for tapemon to print all data */ MPR3("Sense: %s, other= %02X,%02X\n", snstext[snskey], sns[12],sns[13]); linktp->sns = NULL; return((int)snskey); } else { MPR1("SCSI %s ERROR\n",sderror[iocode-40]); return(SERR); } } /**************************************************************************/ /* wrteot: handle messiness that happens when writing at end of tape. Returns non-zero status if not EOT or tape swap unsuccessful. */ long wrteot(int bfn, int dec, long stat) { long ios = stat; int s = DoSense(ios); if(s==EOM || s==VOF) { /* EOT? */ if(NewTape()) { /* ask for new tape */ if(dec) blknum -= numblks; /* and rewrite old buffer */ ios=TapeIO(TWRITE,bfn,CTLWAIT); if(ios) s=DoSense(ios); if(dec) blknum += numblks; } } if(s==RCV) ios=0; /* ignore recoveries */ return(ios); } /**************************************************************************/ void freestuff(void) { if(TapeBuff[0]) FreeMem(TapeBuff[0],TBSize); if(TapeBuff[1]) FreeMem(TapeBuff[1],TBSize); TapeBuff[0] = TapeBuff[1] = NULL; return; } /**************************************************************************/ /* getstart: Given pointer (z) to a string of the form "SS-nn/..." returns a number corresponding to SS, the binary value of nn, and the pointer is updated to point after the slash. */ int getstart(long *num) { extern char *z; #define NOTFND 99 #define ENDLST 100 #define NKEYS 12 /* number of keywords */ static char *keys[NKEYS] = { "UN","LU","BS","NB","RB","BT","FM","VB","OV","DF","SU","C9" }; /* 0 1 2 3 4 5 6 7 8 9 10 11 */ char *ii, *jj; int kk; if (*z == '\0') return(ENDLST); /* return if end of string */ for (ii=z; *ii != '-'; ii++); /* find the dash */ for (jj=z; *jj != '/' && *jj !='\0'; jj++); /* find the slash */ *ii = '\0'; *jj = '\0'; /* null-terminate name & number */ *num = (long) strtoul (++ii,NULL,0); /* return converted number */ ii = z; z = ++jj; /* update ptr to next item */ for (kk=0; kk <= NKEYS; kk++) { /* search for keyword */ if (*ii == *keys[kk] && *(ii+1) == *(keys[kk]+1) ) return(kk); /* return index of keyword if found */ } return(NOTFND); /* didn't find keyword */ } /************************************************************************** MonPrint requests that the TapeMon program print the message in 'dbb'. Since this handler cannot do DOS I/O, it must beg the TapeMon program, possibly running in a CLI somewhere, to do the printf for it. If the TapeMon is running, it will have installed a pointer to its task in the link structure, and we can Signal() it. */ void MonPrint(void) { if(linktp->montask) { Signal(linktp->montask, linktp->monsig); Wait (linktp->handsig); Signal(linktp->montask, linktp->monsig); Wait (linktp->handsig); } return; } /**************************************************************************/ /* toUPPER: convert string to upper case */ void toUPPER(char *zz) { for(; *zz != '\0'; zz++) if(*zz >= 'a') *zz -= 0x20; return; } /************************************************************************** * NewTape() Displays a requester asking user to insert a new tape. */ long NewTape(void) { long choice; static char rqm[40]; static struct IntuiText rtxt[] = { { AUTOFRONTPEN,AUTOBACKPEN,AUTODRAWMODE,AUTOLEFTEDGE,AUTOTOPEDGE, AUTOITEXTFONT, (UBYTE *) &rqm[0], AUTONEXTTEXT }, { AUTOFRONTPEN,AUTOBACKPEN,AUTODRAWMODE,AUTOLEFTEDGE,AUTOTOPEDGE, AUTOITEXTFONT, (UBYTE *) "Continue", AUTONEXTTEXT }, { AUTOFRONTPEN,AUTOBACKPEN,AUTODRAWMODE,AUTOLEFTEDGE,AUTOTOPEDGE, AUTOITEXTFONT, (UBYTE *) "Abort", AUTONEXTTEXT } }; MPR1("Time to insert tape# %d\n",++tapenum); sprintf(rqm," Insert tape %d for %s",tapenum,devname); choice=AutoRequest(NULL,&rtxt[0],&rtxt[1],&rtxt[2],NULL,NULL,250,50); if(choice) { DoSense(TapeIO(TREADY,0,CTLWAIT)); /* eat unit-atten status */ if(TapeIO(TREWIND,0,CTLWAIT)) TapeIO(TSENSE,0,CTLWAIT); if(DAC) blknum=reserved; /* reset block number */ } return(choice); }