/* This is a C version of the Port Handler
 * 	Andy Finkel
 * Copyright (c) 1988 Commodore-Amiga, Inc.
 *
 * Executables based on this information may be used in software
 * for Commodore Amiga computers.  All other rights reserved.
 *
 * This information is provided "as is"; no warranties are made.
 * All use is at your own risk, and no liability or
 * responsibility is assumed.
 *
 * This version is Mounted, rather than automatically called, since
 * DOS.Library seems to call the Port-handler assuming it is a
 * BCPL program, for some reason.
 *
 * The mountlist entries for this handler are as follows:
 * cser:	Handler= L:CPort-Handler
 *		Priority=5
 *		StackSize=2000
 *		GlobVec = -1
 *#
 * cpar:	Handler= L:CPort-Handler
 *		Priority=5
 *		StackSize=2000
 *		GlobVec = -1
 *#
 * cprt:	Handler= L:CPort-Handler
 *		Priority=5
 *		StackSize=2000
 *		GlobVec = -1
 *#
 */

#include "exec/types.h"
#include "exec/nodes.h"
#include "exec/lists.h"
#include "exec/ports.h"
#include "exec/libraries.h"
#include "exec/devices.h"
#include "exec/io.h"
#include "exec/memory.h"
#include "libraries/dos.h"
#include "libraries/dosextens.h"
#include "libraries/filehandler.h"
#include "devices/serial.h"
#include "devices/parallel.h"
#include "devices/printer.h"

union pio {
    struct IOStdReq ios;
    struct IODRPReq iodrp;
    struct IOPrtCmdReq iopc;
    struct IOExtSer ioser;
    struct IOExtPar iopar;
};


#define DOS_TRUE        -1
#define IOSIZE	sizeof(union pio)

#define QTOUPPER(c)      ((c)>='a'&&(c)<='z'?(c)-'a'+'A':(c))

/* internal packets */
#define action_read             1001
#define action_write            1002

#define ACTION_FIND_INPUT       1005
#define ACTION_FIND_OUTPUT      1006
#define ACTION_END              1007

#undef  BADDR
#define BADDR(x)        ((CPTR)((LONG)x << 2))

extern struct DosPacket *taskwait();
extern VOID setIO();
extern VOID sendIO();
extern VOID returnpkt();

extern struct ExecBase *AbsExecBase;
struct ExecBase *SysBase;
struct DOSLibrary *DOSBase;

main()
{

    ULONG i;
    UBYTE *openstring;
    UBYTE *devname;
    BOOL error;
    BOOL printer=FALSE;
    BOOL OpenForInput  = FALSE, OpenForOutput = FALSE;

    struct DeviceNode       *node;  /* our device node is passed in parm pkt Arg3 */

    struct DosPacket        *packet;
    struct FileHandle	    *scb;

    struct DosPacket 	inp;
    struct DosPacket 	*inpkt;
    struct DosPacket  	out;
    struct DosPacket  	*outpkt;

    struct DosPacket    *readPkt;
    struct DosPacket    *writePkt;

    struct   IOStdReq	*iob;
    struct   IOStdReq	*iobo;

    struct   MsgPort *devport;
    struct Process *process;
    
    SysBase = AbsExecBase;

    process=(struct Process *) FindTask(NULL);
    devport = &process->pr_MsgPort;
    inpkt = &inp;
    outpkt = &out;

    packet = taskwait();    /* wait for parm. packet */

    /* openstring tells which we are supposed to be...prt, par, or ser */
    openstring = (UBYTE *)BADDR(packet->dp_Arg1);
    node = (struct DeviceNode *) BADDR(packet->dp_Arg3);

    /* we need DOS.library to unload ourselves later */
    DOSBase = (struct DosLibrary *) OpenLibrary("dos.library",0);

    if(!strcmpi(openstring,"cprt:")) {
	printer = DOS_TRUE;
	devname = "printer.device";
    }
    else if (!strcmpi(openstring,"cpar:"))devname = "parallel.device";
    else devname = "serial.device";

	/* set up the input IOR and the output IOR */
    iob=(struct IOStdReq *)CreateExtIO(devport,(LONG)IOSIZE);
    iobo=(struct IOStdReq *)CreateExtIO(devport,(LONG)IOSIZE);
    if(!iob || !iobo) goto exit;

	/* open the device */
    if(error=OpenDevice(devname,0,iob, 0)) {
    	returnpkt(packet,FALSE,ERROR_OBJECT_IN_USE);
	goto exit;
    }
	*iobo = *iob; /* copy input req to output req */

/*     Turn CR conversion off if raw mode is selected for printer */
    if (printer) { 	/* must be cprt: or cprt:raw */
       if(strlen(openstring) > 5) {
	  setIO(iobo,CMD_WRITE,"\033[20l",5);
	  DoIO(iobo);
       }
    }

   /* patch outselves into the system */

    outpkt->dp_Type = action_write;
    inpkt->dp_Type  = action_read;
    node->dn_Task = devport;

/*    Finished with parameter packet...send back...*/
    returnpkt(packet,DOS_TRUE,packet->dp_Res2);

/*    Main Event Loop */
    do { 
        packet = taskwait();
	switch (packet->dp_Type) {

            case ACTION_FIND_INPUT:
		scb = (struct FileHandle *) BADDR(packet->dp_Arg1);
	    	    if (OpenForInput) {
	        	returnpkt(packet,FALSE,ERROR_OBJECT_IN_USE);
	        	continue;
		    }
	    	OpenForInput = DOS_TRUE;
   			/* now mark as interactive */
            	scb->fh_Port   = (struct MsgPort *)DOS_TRUE;
	    	scb->fh_Arg1 = ACTION_FIND_INPUT;
            	returnpkt(packet,DOS_TRUE,0);
            	continue;

      	    case ACTION_FIND_OUTPUT:
          	scb = (struct FileHandle *) BADDR(packet->dp_Arg1);
	 	if(OpenForOutput) {
	 	    returnpkt(packet,FALSE,ERROR_OBJECT_IN_USE);
	    	    continue;
		}
		OpenForOutput = DOS_TRUE;
		/* set interactive flag */
         	scb->fh_Port = (struct MsgPort *)DOS_TRUE;
	 	scb->fh_Arg1 = ACTION_FIND_OUTPUT;
         	returnpkt(packet,DOS_TRUE,0);
         	continue;

	    case ACTION_END:          /* Close request */
	 	if (packet->dp_Arg1 == ACTION_FIND_INPUT) OpenForInput  = FALSE;
	   	else OpenForOutput = FALSE;
	 	if(!OpenForInput && !OpenForOutput)node->dn_Task = 0;
         	returnpkt(packet,DOS_TRUE,0);
		continue;

            case action_read:         	/* Read request returning */
         	inpkt = packet;
         	handleReturn(iob,readPkt);
		continue;

            case action_write:        	/* Write request returning */
  	       	outpkt = packet;
 	       	handleReturn(iobo,writePkt);
		continue;

            case 'R':            	/* Read Request */
         	readPkt = packet;
  	       	handleRequest(iob,CMD_READ,packet,inpkt);
	        inpkt = 0;
		continue;

            case 'W':            	/* Write Request */
 	        writePkt = packet;
         	handleRequest(iobo,CMD_WRITE,packet,outpkt);
	        outpkt = 0;
		continue;

      	    default:
	 	if(!OpenForInput && !OpenForOutput) node->dn_Task = 0;
         	returnpkt(packet,FALSE,ERROR_ACTION_NOT_KNOWN);
	}
    } while (OpenForInput || OpenForOutput || (outpkt==0) || (inpkt==0));

exit:
    if(!error)CloseDevice(iob);
    if(iob)DeleteExtIO(iob,(ULONG)IOSIZE);
    if(iobo)DeleteExtIO(iobo,(ULONG)IOSIZE);

/* This looks funny, but works because DOS is not part of the code being unloaded */
    Forbid();
    UnLoadSeg(node->dn_SegList);
    CloseLibrary(DOSBase);
    node->dn_SegList=0;
}

/* Handle an IO request. Passed command, transmission packet (tp)
   and request packet (rp). rp contains buffer and length in arg2/3. */

handleRequest(iob, command, rp, tp )
struct IOStdReq  *iob;
ULONG command;
struct DosPacket *rp;
struct DosPacket *tp;
{
   UBYTE  *buff = (UBYTE *)rp->dp_Arg2;
   int len  = (int)rp->dp_Arg3;

   setIO( iob, command, 0, rp->dp_Arg3, 0 );
   iob->io_Data = buff;
   sendIO( iob, tp );
}

/* Handle a returning IO request. The user request packet is
   passed as packet, and must be returned with success/failure message. */

handleReturn(iob, packet )
struct IOStdReq  *iob;
struct DosPacket *packet;
{
int errcode = iob->io_Error;
int len=iob->io_Actual;

   if( errcode==0) returnpkt(packet, len ,0);
   else returnpkt(packet, -1, errcode );
}

/* Initialize IO request block */

VOID setIO(iob,cmd,data,len)
struct IOStdReq *iob;
UWORD           cmd;
APTR            data;
ULONG           len;
{
    iob->io_Command = cmd;
    iob->io_Data    = data;
    iob->io_Length  = len;
}

VOID sendIO(iob,packet)
struct IOStdReq  *iob;
struct DosPacket *packet;
{
     iob->io_Message.mn_Node.ln_Name = (char *) packet;
     packet->dp_Link = (struct Message *) iob;
     SendIO(iob);
}

VOID returnpkt(packet,res1,res2)
struct DosPacket *packet;
ULONG  res1,res2;
{
 struct Message *message;
 struct MsgPort *replyport;
 struct Process *process;
 
 packet->dp_Res1          = res1;
 packet->dp_Res2          = res2; 
 replyport                = packet->dp_Port;
 message                  = packet->dp_Link;
 process                  = (struct Process *) FindTask(NULL);
 packet->dp_Port          = &process->pr_MsgPort;
 message->mn_Node.ln_Name    = (char *) packet;
 message->mn_Node.ln_Succ    = NULL;
 message->mn_Node.ln_Pred    = NULL;
 
 PutMsg(replyport,message); 
}

 /* taskwait()  waits for a message to arrive at the port and 
    returns the packet address  */

struct DosPacket *taskwait()
{
 struct Process *process;
 struct MsgPort *port;
 struct Message *message;

 process = (struct Process *) FindTask(0L);
 port = &process->pr_MsgPort;

 /* wait for packet */
 SetSignal(FALSE,1<<8);	/* clear sigbit 8 */
 while( !(message = (struct Message *) GetMsg(port)) )  Wait(1<<8);

/* return the pointer to the packet */
return((struct DosPacket *) message->mn_Node.ln_Name);
}

strlen( s )
UBYTE *s;
{
    int i = 0;
    while( *s++ ) i++;
    return( i );
}

strcmpi(str1, str2)
char *str1,*str2;
{
    UBYTE *astr =str1;
    UBYTE *bstr =str2;
    UBYTE c;

    while ( (c = QTOUPPER(*astr)) && (c == QTOUPPER(*bstr))) astr++, bstr++;
    if( ! c ) return ( 0 );
    if( c < *bstr ) return( -1 );
    return( 1 );
}
