/****     BTNtape Handler for SCSI tape drives        ****/
/**** Author: Bob Rethemeyer  (DrBob@cup.portal.com)  ****/

#define TVERSION "-BTNTAPE V1.0 RAR-" ## __DATE__

/*  (c) Copyright 1990, 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 Lattice v5 or Manx v5.
 *
 *  This handler works in conjunction with the accompanying TapeMon program.
 *----------------------------------------------------------------------------
 * Install this handler in your L: directory.
 * Make a devs:mountlist entry similar to this:
 *
 *   TAPE:  Handler   = L:tape-handler
 *          Stacksize = 4000
 *          Priority  = 5     (use 11 for Supra)
 *          GlobVec   = -1
 *          Startup   = "4/5/8192/1/0/yourscsi.device"
 *                    ( unit/BufMemType/blocksize/Buffers/Reserved/Driver )
 *   #
 * Then use "MOUNT TAPE:" to make the device known to DOS.
 *
 * This handler can circumvent the "write phase" problem in the CBM 2090A
 * driver.  To invoke the circumvention, prefix the name of the driver
 * in the Startup mountlist parameter with a dollar sign ("$").
 * Example:    Startup = "4/5/8192/1/0/$hddisk.device"
 *
 * This handler can circumvent a byte count problem in the Supra v1.10
 * driver.  To invoke the circumvention, prefix the name of the driver
 * in the Startup mountlist parameter with a plus sign ("+").
 * Example:    Startup = "4/5/8192/1/0/+supradirect.device"
 *
 * ----------------------------------------------------------------------------
 */

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/ports.h>
#include <exec/tasks.h>
#include <exec/libraries.h>
#include <exec/io.h>
#include <exec/memory.h>
#include <devices/scsidisk.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <libraries/filehandler.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>

#if defined AZTEC_C
  #include <functions.h>
#elif defined LATTICE
  #include <proto/exec.h>
#endif

#include "tape.h"
#include "tplink.h"

struct things {  /* a collection of things we will have to alloc */
         UBYTE cmdbuff[32];
         UBYTE snsarea[32];
         struct SCSICmd scsicmd;
         UBYTE pad[256];  /* SCSICmd may be larger than include? */
         } ;

/*======== Global data */

struct IntuitionBase *IntuitionBase;
UBYTE           *cdb;          /* pointer to tape command buffer */
UBYTE           *sns;          /* pointer to sense data buffer   */
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  numblks;                /* number of blocks per io operation */
ULONG  rwlen;                  /* bytes in a tape read/write     */
ULONG  bugmask = 0;            /* 2090A bug circumvention        */
long   tpsize;                 /* tape size in blocks            */
short  reserved;               /* number of reserved blocks at BOT */
short  inprog = FALSE;         /* io operation in progress flag  */
char   *z;                     /* scratch                        */
char   dbb[80];                /* buffer for monitor messages    */

#define RAWNAME "$$RAWCMD$$"
#define RAWLEN 10

/***********************  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 */
 ULONG                  dvnode;     /* ptr to devnode passed in pkt Arg3 */
 struct things         *xarea;      /* ptr to dynamic misc. areas        */
 ULONG                 unit;        /* device SCSI unit address          */
 ULONG                 bufmemtype;  /* type of mem for dynamic buffers   */
 ULONG                 blksize;     /* bytes per tape block              */
 ULONG                 TBSize;      /* bytes in a tape buffer            */
 char                  *driver;     /* name of SCSI device driver        */
 UBYTE                 *dptr;       /* ptr to next byte in dos buffer    */
 long                  dcnt;        /* count of dos packet bytes to move */
 long                  mcnt;        /* count of bytes to move            */
 long                  Boff;        /* current offset in tape buffer     */
 long                  rem;         /* bytes remaining in tape buffer    */
 long                  x;           /* scratch                           */
 short                 Bn;          /* current buffer number, 0 or 1     */
 short                 raw;         /* raw command mode flag             */
 short                 rdmode;      /* flag indicating open for reading  */
 short                 norw;        /* no-rewind flag                    */
 short                 open= FALSE; /* tape file open flag               */
 short                 dirty=FALSE; /* buffer has unwritten data in it   */
 BYTE                  acksig;      /* monitor acknowledge signal number */

/*======== 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;
 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 '/'
*/
 z = (char *)BADDR(mypkt->dp_Arg2)+1 ;  /* Arg2= BSTR to mountlist 'Startup'*/
 if(z[0]=='\"')  {                      /* remove quotes if any */
     z++;
     z[strlen(z)-1]= '\0' ;
 }
 unit       = Nextnum(0);
 bufmemtype = Nextnum(1);
 blksize    = Nextnum(1);
 numblks    = Nextnum(1);
 reserved   = Nextnum(1);
 driver     = (char *) Nextnum(-1);
 rwlen = TBSize = numblks * blksize;   /* size of a tape buffer */

/*======== Kludges to work around various SCSIdirect driver software problems */

 if (driver[0] == '$') {               /* Activate 2090A bug circumvention */
     bugmask = 0x01000000;            /* if driver name starts with '$'   */
     driver++ ;
 }
 else if (driver[0] == '+') {          /* Activate Supra bug circumvention */
     rwlen = 0;                       /* if driver name starts with '+'   */
     driver++ ;
 }
 tpl.driver  = driver;

/*======== 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];
 cmd = &xarea->scsicmd;
 ior = (struct IOStdReq *) CreateExtIO( CreatePort(0,0),
                                         sizeof(struct IOStdReq));

/*======== Open the SCSIdirect device */

 if ( OpenDevice(driver,unit,(struct IORequest *)ior,0L)  )  {
    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 } */

 DoSense(0);

/* =========== The main packet processing loop =============== */

 for (;;)  {
   mypkt = taskwait();          /* wait for a packet */
   switch(mypkt->dp_Type) {

     case ACTION_FINDINPUT:  /*----------- Open() ------------*/
     case ACTION_FINDOUTPUT:
          if(open) returnpkt(mypkt,DOSFALSE,ERROR_OBJECT_IN_USE);
          else {
             TapeBuff[0] = (UBYTE *) AllocMem(TBSize, bufmemtype | MEMF_CLEAR);
             TapeBuff[1] = (UBYTE *) AllocMem(TBSize, bufmemtype | MEMF_CLEAR);
             if (!TapeBuff[0] || !TapeBuff[1]) {
                  FreeStuff(TBSize);
                  returnpkt(mypkt,DOSFALSE,ERROR_NO_FREE_STORE);
                  MPR0("Can't get memory for tape buffers\n")
             }
             else {
                  /* Detect open modes: raw command, continue, goto-block */
                  raw=norw=rdmode=FALSE;
                  for( z=(char *)BADDR(mypkt->dp_Arg3)+1 ; z[0]!=':' ; z++ );
                  if(!memcmp(z+1,RAWNAME,RAWLEN)) raw=norw=TRUE;
                  else if( z[1] == '*' ) norw=TRUE;
                  else if( isdigit((int)z[1]) ) {
                         blknum = (ULONG) strtol(z+1,NULL,0);
                         norw=TRUE;
                         }

                  DoSense(0);  /* eat tape-change status */
                  if(norw) x=0;
                  else x=TapeIO(TREWIND,0,CTLWAIT);
                  if(x) {
                     DoSense(x);
                     FreeStuff(TBSize);
                     returnpkt(mypkt,DOSFALSE,ERROR_DEVICE_NOT_MOUNTED);
                  }
                  else {
                     tpsize = TapeIO(RDCAP,0,CTLWAIT) ? /* get tape capacity in sns */
                                LONG_MAX : ((sns[2] << 8) | sns[3]) + 1;
                     MPR3("%d * %d = %d\n", tpsize,blksize,tpsize*blksize)
                     open=TRUE;
                     dirty=FALSE;
                     inprog=FALSE;
                     Boff=0;
                     Bn=0;
                     rem = TBSize;
                     if(!norw) blknum = reserved;
                     MPR1("Opened at block %d\n",blknum)
                     if (mypkt->dp_Type==ACTION_FINDINPUT)  {
                        rdmode=TRUE;
                        x= TapeIO(TREAD,0,CTLWAIT);      /* fill 1st buffer   */
                        blknum += numblks;
                        if(!x) x=TapeIO(TREAD,1,CTLIMM); /* start reading 2nd */
                     }
                     returnpkt(mypkt,DOSTRUE,mypkt->dp_Res2);
                  }
             }
          }
          break;

     case ACTION_END:        /*----------- Close() -----------*/
          if(open) {
            if(dirty) {
               if(raw) TapeIO(RAWCMD,Bn,CTLWAIT);  /* send user command */
               else    TapeIO(TWRITE,Bn,CTLWAIT);  /* write last block */
            }
            else if(inprog) TapeIO(TFINISH,0,CTLWAIT); /* wait for last one */
            open=FALSE;
            FreeStuff(TBSize);
          }
          returnpkt(mypkt,DOSTRUE,mypkt->dp_Res2);
          if(!rdmode) blknum += numblks;
          MPR1("Closed at block %d\n",blknum)
          break;

     case ACTION_READ:       /*----------- Read() ------------*/
          if(x) {
              DoSense(x);
              mypkt->dp_Arg3 = -1;
              goto RDERR;
          }
          dptr = (UBYTE *) mypkt->dp_Arg2;
          dcnt =           mypkt->dp_Arg3;
          while(dcnt)   {
            if(!rem)     {
               blknum += numblks;   /* start reading next buffer */
               if(x=TapeIO(TREAD,Bn,CTLIMM))  {
                   DoSense(x);
                   mypkt->dp_Arg3 = -1;
                   goto RDERR;
               }
               Bn ^= 1;             /* switch to other (filled) buffer */
               rem = TBSize;
               Boff = 0;
            }
            mcnt = (dcnt>rem) ? rem : dcnt;
            memcpy (dptr, &TapeBuff[Bn][Boff], mcnt);
            dcnt -= mcnt ;
            Boff += mcnt ;
            rem  -= mcnt ;
            dptr += mcnt ;
          }
          RDERR:
          returnpkt(mypkt,mypkt->dp_Arg3,mypkt->dp_Arg2);
          break;

     case ACTION_WRITE:      /*----------- Write() -----------*/
          dptr = (UBYTE *) mypkt->dp_Arg2;
          dcnt =           mypkt->dp_Arg3;
          while (dcnt)    {
            if (dcnt >= rem) {
               memcpy (&TapeBuff[Bn][Boff], dptr, rem);
               if( x=TapeIO(TWRITE,Bn,CTLIMM) ) {
                       DoSense(x);
                       mypkt->dp_Arg3 = -1;
                       goto WRTERR;
               }
               blknum += numblks;
               dcnt -= rem;
               dptr += rem;
               Boff  = 0;
               rem   = TBSize;
               Bn   ^= 1;
               dirty = FALSE;
            }
            else {
               memcpy (&TapeBuff[Bn][Boff], dptr, dcnt);
               rem  -= dcnt;
               Boff += dcnt;
               dcnt  = 0;
               dirty = TRUE;
            }
          }
          WRTERR:
          returnpkt(mypkt,mypkt->dp_Arg3,mypkt->dp_Res2);
          break;

     case ACTION_CURRENT_VOLUME:
          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;

     default:    /* say what? */
          returnpkt(mypkt,DOSFALSE,ERROR_ACTION_NOT_KNOWN);
          MPR1("Unsupported_Pkt=%d\n",mypkt->dp_Type)

     } /* end of switch */
 } /* end of loop */
} /* end of _main() */

/**************************************************************************/

void DoSense(long x)
{
   sns[2] = sns[12] = 0;
   x=x>>8;
   if(x==0) TapeIO(TSENSE,0,CTLWAIT);
   else if(x==HFERR_BadStatus)  {
       TapeIO(TSENSE,0,CTLWAIT);
       if(!(sns[0] & 0x70)) sns[2]=sns[0] & 0x0f; /* non-extended sense */
       linktp->sense = sns[2];    /* keep last error info */
       linktp->xsense = sns[12];
   }
   MPR3("io_Error=%d  Sense=%X,%02X\n", x, sns[2], sns[12])
   return;
}

/**************************************************************************/

void FreeStuff(ULONG bs)
{
   if(inprog) TapeIO(TFINISH,0,CTLWAIT); /* just in case */
   if(TapeBuff[0]) FreeMem(TapeBuff[0],bs);
   if(TapeBuff[1]) FreeMem(TapeBuff[1],bs);
   TapeBuff[0] = TapeBuff[1] = NULL;
   return;
}

/**************************************************************************/
/* Nextnum parses and returns the next number in the startup string */

ULONG Nextnum(short kk)            /* kk=0 for first number */
{                                  /* kk=1 for other numbers */
char *zz;                          /* kk=-1 for pointer to last token */
   zz = (kk) ? NULL : z;
   z = strtok(zz,"/");
   if(kk==-1) return( (ULONG) z);
   else       return( (ULONG) strtol(z,NULL,0) );
}

/**************************************************************************
   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;
}

