
/*
 *  TAPE-HANDLER.C
 *
 *  tape interface
 *
 *  TAPE:flags
 *
 *     r	rewind
 *     a	append
 */

#include "defs.h"

#define SwapBufs(chan) { char *t1 = chan->ch_Buf1; long l1 = chan->ch_BufLen1; \
			 chan->ch_Buf1 = chan->ch_Buf2; chan->ch_BufLen1 = chan->ch_BufLen2; \
			 chan->ch_Buf2 = t1;  chan->ch_BufLen2 = l1;			     \
			}

Prototype int main(short, char **);
Prototype void	myexit(void);
Prototype void	MkDevice(char *);
Prototype void	DelDevice(void);
Prototype void	*DosAllocMem(long);
Prototype void	DosFree(void *);
Prototype void	HandleDosPacket(DosPacket *, short);
Prototype void	HandleReturnedRequest(Chan *);
Prototype void	ReturnPacket(DosPacket *);
Prototype void	HoldPacket(List *, DosPacket *);
Prototype void	RetryWaitingPacket(List *);

Prototype MsgPort   *IoSink;
Prototype MsgPort   *DosRetry;
Prototype short     DDebug;
Prototype List	    ChanList;

MsgPort *IoSink;
MsgPort *DosRetry;
char	*DeviceName = "scsi.device";
long	UnitNo = -1;
short	DDebug;
List	ChanList;

ubyte rewind_cmd[]   = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 };
ubyte append_cmd[]   = { 0x11, 0x03, 0x00, 0x00, 0x00, 0x00 };
ubyte filemark_cmd[] = { 0x10, 0x00, 0x00, 0x00, 0x01, 0x00 };

extern struct DosLibrary *DOSBase;

main(ac, av)
short ac;
char *av[];
{
    long sinkMask;
    long retryMask;
    long mask;

    NewList(&ChanList);
    if (ac == 1) {
	printf("tape-handler device_name [-D scsi.device] -U unit\n");
	exit(0);
    }
    IoSink = CreatePort(NULL, 0);
    DosRetry = CreatePort(NULL, 0);

    sinkMask = 1 << IoSink->mp_SigBit;
    retryMask = 1 << DosRetry->mp_SigBit;

    {
	short i;
	for (i = 2; i < ac; ++i) {
	    char *ptr = av[i];
	    if (*ptr != '-')
		continue;
	    ptr += 2;
	    switch(ptr[-1]) {
	    case 'D':
		DeviceName = (*ptr) ? ptr : av[++i];
		break;
	    case 'U':
		UnitNo = strtol((*ptr) ? ptr : av[++i], NULL, 0);
		break;
	    }
	}
    }
    if (DeviceName == NULL || UnitNo == -1) {
	puts("run <nil: >nil: tape-handler <devname> -D<device> -U<unit>");
	exit(20);
    }
    MkDevice(av[1]);

    for (;;) {
	Message *msg;

	while (msg = GetMsg(DosRetry))
	    HandleDosPacket((DosPacket *)msg->mn_Node.ln_Name, 1);
	while (msg = GetMsg(IoSink)) {
	    if (msg->mn_Node.ln_Type = NT_MESSAGE)
		HandleDosPacket((DosPacket *)msg->mn_Node.ln_Name, 0);
	    else
		HandleReturnedRequest((Chan *)msg->mn_Node.ln_Name);
	}
	mask = Wait(SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D | SIGBREAKF_CTRL_E | sinkMask | retryMask);
	if (mask & (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D)) {
	    if (GetHead(&ChanList) == NULL)
		break;
	}
    }
    return(0);
}

void
myexit()
{
    void *z = NULL;

    DelDevice();
    if (IoSink) {
	DeletePort(IoSink);
	IoSink = z;
    }
    if (DosRetry) {
	DeletePort(DosRetry);
	DosRetry = z;
    }
}

void
HandleDosPacket(pkt, retry)
DosPacket *pkt;
short retry;
{
    if (retry == 0) {
	pkt->dp_Res1 = 0;
	pkt->dp_Res2 = 0;
    }

    switch(pkt->dp_Type) {
    case ACTION_FINDUPDATE:	/*  FileHandle,Lock,Name	Bool	    */
    case ACTION_FINDINPUT:	/*  FileHandle,Lock,Name	Bool	    */
    case ACTION_FINDOUTPUT:	/*  FileHandle,Lock,Name	Bool	    */
	/*
	 *  attempt to open tape scsi device
	 */

	{
	    FileHandle *fh = (FileHandle *)BADDR(pkt->dp_Arg1);
	    Chan *chan = AllocMem(sizeof(Chan), MEMF_PUBLIC | MEMF_CLEAR);
	    int error;

	    NewList(&chan->ch_PktList);
	    {
		ubyte *ptr = (ubyte *)BADDR(pkt->dp_Arg3);
		int len;

		for (len = *ptr; len > 0; --len) {
		    if (ptr[len] == ':')
			break;
		    switch(ptr[len] | 0x20) {
		    case 'a':   /*  append  */
			chan->ch_Flags |= CHANF_APPEND | CHANF_REWIND;
			break;
		    case 'r':   /*  rewind  */
			chan->ch_Flags |= CHANF_REWIND;
			break;
		    }
		}
	    }
	    chan->ch_DeviceName = DeviceName;
	    chan->ch_UnitNo	= UnitNo;
	    chan->ch_BufSize	= 32768;
	    chan->ch_Buf1	= AllocMem(chan->ch_BufSize, MEMF_PUBLIC);
	    chan->ch_Buf2	= AllocMem(chan->ch_BufSize, MEMF_PUBLIC);

	    error = SCSIOpen(chan);

	    if (error == 0 && chan->ch_Flags & CHANF_REWIND)
		error = DoSCSI(chan, rewind_cmd, NULL, 0, SCSIF_READ, NULL);

	    if (error == 0 && chan->ch_Flags & CHANF_APPEND)
		error = DoSCSI(chan, append_cmd, NULL, 0, SCSIF_READ, NULL);

	    if (error == 0) {
		fh->fh_Port = (MsgPort *)DOS_FALSE;
		fh->fh_Arg1 = chan;
		pkt->dp_Res1 = DOS_TRUE;
		pkt->dp_Res2 = 0;
		AddTail(&ChanList, &chan->ch_Node);
	    } else {
		SCSIClose(chan);
		fh->fh_Port = (MsgPort *)DOS_FALSE;
		fh->fh_Arg1 = NULL;
		pkt->dp_Res2 = error;
		pkt->dp_Res1 = DOS_FALSE;
		FreeMem(chan->ch_Buf1, chan->ch_BufSize);
		FreeMem(chan->ch_Buf2, chan->ch_BufSize);
		FreeMem(chan, sizeof(Chan));
	    }
	}
	ReturnPacket(pkt);
	break;
    case ACTION_READ:		/*  FHArg1,CPTRBuffer,Length	ActLength   */
	/*
	 *  read data
	 */

	{
	    Chan *chan = (Chan *)pkt->dp_Arg1;
	    long n;
	    long bytes = pkt->dp_Arg3 - pkt->dp_Res1;


	    /*
	     *	copy from Buf1
	     */

	    if (n = chan->ch_BufLen1) {
		if (n > bytes)
		    n = bytes;
		CopyMem(chan->ch_Buf1 + chan->ch_BufIdx1, (void *)((long)pkt->dp_Arg2 + pkt->dp_Res1), n);
		pkt->dp_Res1 += n;
		bytes	     -= n;
		chan->ch_BufLen1 -= n;
		chan->ch_BufIdx1 += n;
	    }

	    /*
	     *	return or requeue DOS request as indicated
	     */

	    if (bytes == 0 || (chan->ch_Flags & CHANF_EOF))
		ReturnPacket(pkt);
	    else
		HoldPacket(&chan->ch_PktList, pkt);

	    /*
	     *	if Buf1 empty and Buf2 not, move'm in
	     */

	    if (chan->ch_BufLen1 == 0 && chan->ch_BufLen2) {
		SwapBufs(chan);
		chan->ch_BufIdx1 = 0;
		RetryWaitingPacket(&chan->ch_PktList);
	    }

	    /*
	     *	attempt to read next buffer in while program processing
	     *	first.	Or, if buffer is full, swap into buf1
	     */

	    if ((chan->ch_Flags & (CHANF_EOF|CHANF_IOSIP)) == 0 && chan->ch_BufLen2 == 0) {
		char read_cmd[6] = { 0x08, 0x01, 0x00, 0x00, 0x00, 0x00 };
		SendSCSI(chan, read_cmd, chan->ch_Buf2, chan->ch_BufSize, SCSIF_READ);
	    }
	}
	break;
    case ACTION_WRITE:
	/*
	 *  write data
	 */

	{
	    Chan *chan = (Chan *)pkt->dp_Arg1;
	    long n;
	    long bytes = pkt->dp_Arg3 - pkt->dp_Res1;

	    /*
	     *	copy as much as possible to Buf1
	     */

	    chan->ch_Flags |= CHANF_WRITE;
	    if (n = chan->ch_BufSize - chan->ch_BufLen1) {
		if (n > bytes)
		    n = bytes;
		CopyMem((void *)((long)pkt->dp_Arg2 + pkt->dp_Res1), (void *)(chan->ch_Buf1 + chan->ch_BufLen1), n);
		pkt->dp_Res1 += n;
		bytes	     -= n;
		chan->ch_BufLen1 += n;
	    }

	    /*
	     *	return or requeue DOS request as indicated
	     */

	    if (bytes == 0 || (chan->ch_Flags & CHANF_EOF))
		ReturnPacket(pkt);
	    else
		HoldPacket(&chan->ch_PktList, pkt);

	    /*
	     *	If buffer is full then transfer to Buf2 and start write
	     */

	    if (chan->ch_BufLen1 == chan->ch_BufSize && chan->ch_BufLen2 == 0) {
		char write_cmd[6] = { 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00 };
		SendSCSI(chan, write_cmd, chan->ch_Buf1, chan->ch_BufLen1, SCSIF_WRITE);
		SwapBufs(chan);
		RetryWaitingPacket(&chan->ch_PktList);
	    }
	}
	break;
    case ACTION_END:
	{
	    Chan *chan = (Chan *)pkt->dp_Arg1;

	    pkt->dp_Res2 = 0;
	    pkt->dp_Res1 = DOS_TRUE;
	    ReturnPacket(pkt);

	    Remove(&chan->ch_Node);
	    if (chan->ch_Flags & CHANF_WRITE) {
		char write_cmd[6] = { 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00 };

		if (chan->ch_Flags & CHANF_IOSIP)
		    WaitSCSI(chan, NULL);
		if (chan->ch_BufLen1) {
		    long extra = 512 - (chan->ch_BufLen1 & 511);

		    if (extra == 512)
			extra = 0;
		    if (extra)
			clrmem(chan->ch_Buf1 + chan->ch_BufLen1, extra);
		    DoSCSI(chan, write_cmd, chan->ch_Buf1, chan->ch_BufLen1 + extra, SCSIF_WRITE, NULL);
		}
		DoSCSI(chan, filemark_cmd, NULL, 0, SCSIF_READ, NULL);
	    }
	    SCSIClose(chan);
	    FreeMem(chan->ch_Buf1, chan->ch_BufSize);
	    FreeMem(chan->ch_Buf2, chan->ch_BufSize);
	    FreeMem(chan, sizeof(Chan));
	}
	break;
    case ACTION_DIE:
    default:
	pkt->dp_Res2 = ERROR_ACTION_NOT_KNOWN;
	pkt->dp_Res1 = DOS_FALSE;
	ReturnPacket(pkt);
	break;
    }
}

void
HandleReturnedRequest(chan)
Chan *chan;
{
    long len;
    int error;

    chan->ch_Flags &= ~CHANF_IOSIP;
    error = WaitSCSI(chan, &len);

    if (chan->ch_Flags & CHANF_WRITE) {
	/*
	 *  if write returned buffer space now freed up
	 */

	chan->ch_BufLen2 = 0;
	RetryWaitingPacket(&chan->ch_PktList);
    } else {
	/*
	 *  if read returned, data now available and can queue up another
	 *  read, in fact!
	 */

	switch(error) {
	case 0x80:
	    chan->ch_Flags |= CHANF_EOF;
	    /* fall through */
	case 0:
	    break;
	default:
	    len = 0;
	}
	chan->ch_BufLen2 = len;

	if (chan->ch_BufLen1 == 0 && chan->ch_BufLen2) {
	    SwapBufs(chan);
	    chan->ch_BufIdx1 = 0;
	    RetryWaitingPacket(&chan->ch_PktList);
	}
	RetryWaitingPacket(&chan->ch_PktList);
    }
}

DosList *Dl;

void
MkDevice(devName)
char *devName;
{
    DosList *dl;
    RootNode *root;
    DosInfo *info;

    Dl = dl = (struct DosList *)DosAllocMem(sizeof(struct DosList)+strlen(devName)+2);
    strcpy((char *)(dl+1) + 1, devName);
    *(char *)(dl + 1) = strlen(devName);
    dl->dol_Type = DLT_DEVICE;
    dl->dol_Task = IoSink;
    dl->dol_Name = MKBADDR((char *)(dl+1));

    Forbid();
    root  = (struct RootNode *)DOSBase->dl_Root;
    info  = (struct DosInfo  *)BADDR(root->rn_Info);
    dl->dol_Next = info->di_DevInfo;
    info->di_DevInfo = MKBADDR(dl);
    Permit();
}

void
DelDevice()
{
    DosList *dl;
    DosInfo *info;
    RootNode *root;
    DosList *dls;
    BPTR    *bpp;

    if (dl = Dl) {
	Forbid();
	root  = (struct RootNode *)DOSBase->dl_Root;
	info  = (struct DosInfo  *)BADDR(root->rn_Info);

	for (bpp = &info->di_DevInfo; dls = BADDR(*bpp); bpp = &dls->dol_Next) {
	    if (dls == dl)
		break;
	}
	if (dls == dl) {
	    *bpp = dls->dol_Next;
	} else {
	    ;
	}
	Permit();
	DosFree(dl);
	Dl = NULL;
    }
}

void *
DosAllocMem(bytes)
long bytes;
{
    long *ptr;

    bytes += 4;

    if (ptr = AllocMem(bytes, MEMF_PUBLIC | MEMF_CLEAR)) {
	*ptr++ = bytes;
	return((void *)ptr);
    }
}

void
DosFree(vptr)
void *vptr;
{
    long *ptr = vptr;
    --ptr;
    FreeMem(ptr, *ptr);
}

void
ReturnPacket(pkt)
DosPacket *pkt;
{
    Message *msg;
    MsgPort *replyPort;

    replyPort	 = pkt->dp_Port;
    msg 	 = pkt->dp_Link;
    pkt->dp_Port = IoSink;
    msg->mn_Node.ln_Name = (char *)pkt;
    PutMsg(replyPort, msg);
}

void
HoldPacket(list, pkt)
List *list;
DosPacket *pkt;
{
    AddTail(list, &pkt->dp_Link->mn_Node);
}

void
RetryWaitingPacket(list)
List *list;
{
    Message *msg;

    while (msg = RemHead(list))
	PutMsg(DosRetry, msg);
}

