/*
 *  Copyright (C) 1987
 *  Louis A. Mamakos  WA3YMH
 *  All rights reserved.
 *
 *  This code may not be redistributed, sold, included on any collection of
 *  software which is sold.  Use of this software is restricted to inclusion
 *  in the KA9Q TCP/IP software package for use on a Commodore-Amiga system.
 *  Commercial use is prohibited.  Only educational and Amateur Packet Radio
 *  use is allowed.
 */

#ifdef	AMIGADEVDRV
/*
 *  This module is the meat of the Amiga 'internet.device' device driver.  There
 *  are assembly language stubs in devstub.asm that call this module when user
 *  program access the device driver.  Remember: the tasks running this code are
 *  not our own!
 */

#include <stdio.h>

/* Amiga system definitions */

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/tasks.h>
#include <exec/ports.h>
#include <exec/libraries.h>
#include <exec/io.h>
#include <exec/devices.h>
#include <exec/errors.h>


/* get definitions of KA9Q TCP/IP protocol stuff... */

#include "machdep.h"
#include "timer.h"
#include "mbuf.h"
#include "netuser.h"
#include "internet.h"
#include "icmp.h"
#include "ip.h"
#include "tcp.h"
#include "trace.h"
#include "session.h"
/* device driver specific definitions */
#define ListEmpty(x)	(! ((x)->lh_Head->ln_Succ))

#include "inetdev.h"
#ifdef TRACE 
#define tracedev(x) \
	if (trace & TRACE_DEVICE) printf(x)

#define tracedev2(x,y) \
	if (trace & TRACE_DEVICE) printf(x,y)

#define tracedev3(x,y,z) \
	if (trace & TRACE_DEVICE) printf(x,y,z)

#define tracedev4(x,y,z,zz) \
	if (trace & TRACE_DEVICE) printf(x,y,z,zz)
#endif
char *malloc();

extern void DSClose(), DSBeginIO(), DSAbortIO();
extern struct InternetBase *DSOpen();
extern long DSExpunge();

void indev_tcp_r_upcall(), indev_tcp_t_upcall(), indev_s_upcall();
struct SignalSemaphore INLock;
struct Library *MakeLibrary();

/* for open requests */
int nopens; /* from iface */
struct IOINETReq *iob = NULL;
int unit_spec;
struct InternetBase * dev;
int OpenIt = 0, 
    CN1 = 0,
    IOpenedIt = 0;

extern int DeviceSignal;
extern struct Process *mytask;

printlist(l)
struct List *l;
{
  printf("head %x tail %x tailpred %x\n", l->lh_Head, l->lh_Tail, 
		l->lh_TailPred);
}
/*
 *  Initialize and install the Amiga 'internet.device'.
 */
void
DriverInit()
{
	char *foo[10];
	int success;
	static int WeWereHere;
	int x;
	tracedev("DriverInit");
	x = WeWereHere; 
	WeWereHere = 1;
	if (x)
	  {
		printf("you tried to add the driver twice!!!\n");
		return;
	  }
	foo[0] = (char *) &DSOpen;
	foo[1] = (char *) &DSClose;
	foo[2] = (char *) &DSExpunge;
	foo[3] = (char *) NULL;
	foo[4] = (char *) &DSBeginIO;
	foo[5] = (char *) &DSAbortIO;
		/* add any other custom routines here */
	foo[6] = (char *) -1;

	InternetBase = (struct InternetBase *)
		MakeLibrary(&foo[0], (char *) NULL, (char *) NULL,
			(long) sizeof(struct InternetBase), (char *) NULL);

	if (InternetBase == (struct InternetBase *) 0) {
		/* display alert? */
		return;
	}

	InitSemaphore(&(INLock));
	ObtainSemaphore(&(INLock));
	InitSemaphore(&(InternetBase->ib_lock));
	InternetBase->ib_lock.ss_Link.ln_Pri = 0;
	InternetBase->ib_lock.ss_Link.ln_Name = "internet.device lock";

	NewList(&InternetBase->ib_Units);
	InternetBase->ib_Units.lh_Type = NT_UNKNOWN;

	InternetBase->lib.lib_Node.ln_Type = NT_DEVICE;
	InternetBase->lib.lib_Node.ln_Pri = 0;
	InternetBase->lib.lib_Node.ln_Name = "internet.device";
	InternetBase->lib.lib_Flags = LIBF_CHANGED | LIBF_SUMUSED;
	InternetBase->lib.lib_Version = IN_VERSION;
	InternetBase->lib.lib_Revision = IN_REVISION;
	InternetBase->lib.lib_IdString =
			 (APTR) "internet.device   23 May 1987\r\n";

	success = AddDevice(InternetBase);
	Savea4();
	OpenIt = 0;
	IOpenedIt = 0;
	nopens = 1;
	CN1 = 5;
	if (success != 0)
          myoserr("driver open");
	printf("driver added returned %d\n",success);

}

void
DriverShutdown()
{
	long error;
	extern long *RemoveDevice();

	if (!InternetBase)
		return;

	if (error = RemDevice(InternetBase))
		printf("Can't remove device: error %ld\n", error);

}

struct InternetBase *
NetDevOpen(mdev, munit_spec, miob, mflags)
	struct InternetBase *mdev;
	struct IOINETReq *miob;
	ULONG munit_spec, mflags;
{

	iob = miob;
	dev = mdev;
	unit_spec = munit_spec;

	if (IOpenedIt == 1)
	  {
	    CN1++;
	    return NULL;
	  }
	OpenIt = 1;
	Permit();
	while (IOpenedIt == 0);
	Forbid();
	IOpenedIt = 0;
	return dev;
}
check_driver()
{
	register struct INET_Unit *unit;
	register struct tcb *tcb;
	if (OpenIt != 0)
        {

	printf("open request!\n");
	
	switch (unit_spec) {
		case INET_UNIT_TCP:
		case INET_UNIT_UDP:
			break;
		default:
			iob->io_Error = IOERR_OPENFAIL;
			iob->io_Device = NULL;
			iob->io_Unit = NULL;
			OpenIt = 0;
			IOpenedIt = 1;
			goto doneopen;
	}

	if ((unit = (struct INET_Unit *)
			malloc(sizeof(struct INET_Unit))) == NULL) {
		iob->io_Error = IOERR_OPENFAIL;
		OpenIt = 0;
		IOpenedIt = 1;
		goto doneopen;
	}
	tracedev2("malloc ok %x\n", unit);
	iob->io_Unit = unit;
	iob->io_Device = (struct Device *)dev;
	dev->lib.lib_OpenCnt++;

	unit->iu_Unit.ln_Type = NT_UNKNOWN;
	unit->iu_Unit.ln_Pri = 0;

	NewList(&unit->iu_Input);
	NewList(&unit->iu_Output);
printf("newlist ok\n");
	unit->iu_Input.lh_Type = NT_UNKNOWN;	/* gee, what do we really */
	unit->iu_Output.lh_Type = NT_UNKNOWN;	/* call these... */
	unit->iu_user = iob->io_Offset;		/* always returned in Offset */
	unit->iu_Act_Input = NULL;
	unit->iu_Act_Output = NULL;
	iob->io_lsocket.address = ip_addr;
	iob->io_lsocket.port = lport++;	
	AddTail(&InternetBase->ib_Units, &unit->iu_Unit);
	/* perform protocol specific open functions */
	printf("addtail\n");

	switch (unit_spec) {
	case INET_UNIT_TCP:
/*		Forbid(); */
		tcb = open_tcp(&(iob->io_lsocket), &(iob->io_fsocket),
			(USHORT) iob->io_Offset, (USHORT) iob->io_TCP_Window,
			indev_tcp_r_upcall, indev_tcp_t_upcall, indev_s_upcall,
			iob->io_INET_TOS, (char *)unit);

/*		Permit();*/
		if (tcb == NULL)
			goto fail;
		unit->iu_Unit.ln_Name = "TCP Connection";
		unit->iu_type = INET_UNIT_TCP;
		unit->iu_ccb = tcb;
printf("cpopen is %d\n", tcb);
		break;

	default:
	   fail:
		iob->io_Error = IOERR_OPENFAIL;
		dev->lib.lib_OpenCnt--;
		Remove(unit);
		free(unit);
		OpenIt = 0;
		IOpenedIt = 1;
		goto doneopen;
	}
        tracedev2("dev is %d\n", dev);
	OpenIt = 0;
	IOpenedIt = 1;
  }
doneopen:
  /* spin until that other guy is all done. We will not get through
   * this spin until the other guy has done a Forbid() and
   * then a Permit(), since the IopenedIt gets cleared 
   * AFTER the Forbid(). Sorry i do not use semaphores but
   * i do not have 1.2 autodocs so am not totally up on their
   * use.
   */
  while (IOpenedIt);
}
CheckTcp()
{
  struct Node *head = InternetBase->ib_Units.lh_Head;

  struct INET_Unit *unit = (struct INET_Unit *) head;
  struct tcb *tcb;
  /* let the other guys in */
  ReleaseSemaphore(&(INLock));
  eihalt();
  ObtainSemaphore(&(INLock));
  tracedev("start checktcp\n");
  tracedev4("heda %x Pred is %x Succ is %x\n", head,head->ln_Pred, head->ln_Succ);
  for (;unit->iu_Unit.ln_Succ;unit = unit->iu_Unit.ln_Succ)
    {
      tracedev3("checktcp: %x Succ %d\n", unit, unit->iu_Unit.ln_Succ);
      if (unit->iu_type != INET_UNIT_TCP)
        {
          tracedev("not a tcp\n");
          continue;
        }      
      tcb = (struct tcb *) unit->iu_ccb;
      if (tcb == NULL)
        {
          tracedev("NULL tcb in unit\n");  
          continue;
        }
      if (tcb->state == ESTABLISHED)
        {
          tracedev("unit state is established!\n");
/*          continue;*/
        }
     tracedev("do the upcall\n");
     do_tupcall(tcb, 512); /* for now- it wil do the right thing */
     if (tcb->rcvcnt > 0)
       do_rupcall(tcb, tcb->rcvcnt);
  }
  tracedev("done checktcp\n");
/*  ReleaseSemaphore(&(INLock));*/
}
void DevClose(dev, iob)
	struct InternetBase *dev;
	struct IOINETReq *iob;
{
	register struct INET_Unit *unit;
        struct tcb *tcb;

        unit = iob->io_Unit;
        tcb = unit->iu_ccb;
	del_tcp(tcb);
	Remove(unit);
	free(unit);
	iob->io_Unit = NULL;

	iob->io_Device = (struct Device *)dev;
	dev->lib.lib_OpenCnt--;

	/* remove iu_Unit from ib_Units list */
	/* decrement library use count */
	/* free unit structure */
	/* delete TCP/UDP connection del_tcp()/del_udp() */
}

long DevExpunge(dev)
	struct InternetBase *dev;
{
	register char *m;
	register long len;


	if (InternetBase->lib.lib_OpenCnt) {
		InternetBase->lib.lib_Flags |= LIBF_DELEXP;
		return 0;
	}
	Remove(InternetBase);	/* remove from library list */
	len = InternetBase->lib.lib_NegSize + InternetBase->lib.lib_PosSize;
	m = (char *) ((ULONG)InternetBase - InternetBase->lib.lib_NegSize);
	FreeMem(m, len);
	return 0;
}

#define	C_IMMED		(1<<0)
#define	C_READ		(1<<1)
#define	C_WRITE		(1<<2)
void cmd_Invalid(), cmd_Reset(), cmd_Read(), cmd_Write(), cmd_Update(),
	cmd_Clear(), cmd_Stop(), cmd_Start(), cmd_Flush(), PerformIO();

struct Commands {
	void	(*cmd_func)();
	int	cmd_flags;
} commands [] = {
	{ cmd_Invalid, 	C_IMMED		},	/* invalid	*/
	{ cmd_Reset,	C_IMMED		},	/* CMD_RESET	*/
	{ cmd_Read,	C_READ		},	/* CMD_READ	*/
	{ cmd_Write,	C_WRITE		},	/* CMD_WRITE	*/
	{ cmd_Update,	C_WRITE 	},	/* CMD_UPDATE	*/
	{ cmd_Clear,	C_IMMED		},	/* CMD_CLEAR	*/
	{ cmd_Stop,	C_IMMED		},	/* CMD_STOP	*/
	{ cmd_Start,	C_IMMED		},	/* CMD_START	*/
	{ cmd_Flush,	C_IMMED		},	/* CMD_FLUSH	*/
};

/*  define last valid command */
#define	MAX_IO_COMMAND	CMD_FLUSH


/* BeginIO is called to begin processing of the I/O request */

void DevBeginIO(iob, dev)
	struct IOINETReq *iob;
	struct InternetBase *dev;
{
	register struct Commands *cmd;
	register struct INET_Unit *unit = iob->io_Unit;
	ObtainSemaphore(&(INLock));
	if (iob->io_Command > MAX_IO_COMMAND) {
		cmd_Invalid(iob, iob->io_Unit);
		goto done;
	}
	tracedev("io. ObtainSme\n");

	tracedev("got it\n");
	cmd = &commands[iob->io_Command];
	tracedev2("cmd is %d\n",iob->io_Command);
	tracedev2("flags %d\n", iob->io_Flags);
	if ((cmd->cmd_flags & C_IMMED) == 0) {

		/*
		 *  Code for commands which can queue
		 */

		if ((cmd->cmd_flags & C_READ)/* && (unit->iu_Act_Input)*/) {
			AddTail(&unit->iu_Input, iob);
			iob->io_Flags &= ~IOF_QUICK;
			iob->io_Message.mn_Node.ln_Type = NT_MESSAGE;
			tracedev3("added %d to read queue of unit %d\n", iob, unit);
			goto done;
		}

		if ((cmd->cmd_flags & C_WRITE)/* && (unit->iu_Act_Output)*/) {
			AddTail(&unit->iu_Output, iob);
			iob->io_Flags &= ~IOF_QUICK;
			iob->io_Message.mn_Node.ln_Type = NT_MESSAGE;
			tracedev3("added %d to write queue of unit %d\n", iob, unit);
			goto done;
		}
	}
	PerformIO(iob, unit);
done: 	tracedev4("flags QUI %x ~QUI %x  %d\n", IOF_QUICK, ~IOF_QUICK,
			iob->io_Flags);
  Signal(mytask, DeviceSignal);
  ReleaseSemaphore(&(INLock));
}

void DevAbortIO(iob, dev)
	struct IOINETReq *iob;
	struct InternetBase *dev;
{
	printf("DevAbortIo\n");
}

void
PerformIO(iob, unit)
	struct IOINETReq *iob;
	struct INET_Unit *unit;
{
	iob->io_Error = 0;
	iob->io_Actual = 0;
	(*commands[iob->io_Command].cmd_func)(iob, unit);
}

void
TermIO(iob, unit)
	struct IOINETReq *iob;
	struct INET_Unit *unit;
{
	struct tcb *tcb;
        tcb = (struct tcb *) unit->iu_ccb;
	iob->io_OldState = iob->io_State;
	iob->io_State = tcb->state;
	if ((iob->io_Flags & IOF_QUICK) == 0)
		/* not quick I/O */
		ReplyMsg(&iob->io_Message);
}

void
cmd_Invalid(iob, unit)
	struct IOINETReq *iob;
	struct INET_Unit *unit;
{
	iob->io_Error = IOERR_NOCMD;
	TermIO(iob, unit);
}

void
cmd_Reset(iob, unit)
	struct IOINETReq *iob;
	struct INET_Unit *unit;
{
	iob->io_Error = IOERR_NOCMD;
	TermIO(iob, unit);
}

void
cmd_Read(iob, unit)
	struct IOINETReq *iob;
	struct INET_Unit *unit;
{
	iob->io_Error = IOERR_NOCMD;
	TermIO(iob, unit);
}

void
cmd_Write(iob, unit)
	struct IOINETReq *iob;
	struct INET_Unit *unit;
{
	iob->io_Error = IOERR_NOCMD;
	TermIO(iob, unit);
}

void
cmd_Update(iob, unit)
	struct IOINETReq *iob;
	struct INET_Unit *unit;
{
	iob->io_Error = IOERR_NOCMD;
	TermIO(iob, unit);
}

void
cmd_Clear(iob, unit)
	struct IOINETReq *iob;
	struct INET_Unit *unit;
{
	iob->io_Error = IOERR_NOCMD;
	TermIO(iob, unit);
}


void
cmd_Stop(iob, unit)
	struct IOINETReq *iob;
	struct INET_Unit *unit;
{
	iob->io_Error = IOERR_NOCMD;
	TermIO(iob, unit);
}


void
cmd_Start(iob, unit)
	struct IOINETReq *iob;
	struct INET_Unit *unit;
{
	iob->io_Error = IOERR_NOCMD;
	TermIO(iob, unit);
}


void
cmd_Flush(iob, unit)
	struct IOINETReq *iob;
	struct INET_Unit *unit;
{
	iob->io_Error = IOERR_NOCMD;
	TermIO(iob, unit);
}


/* TCP receiver upcall routine.  Called with TCB pointer and number of bytes
   available */


do_rupcall(tcb, cnt)
	struct tcb *tcb;
	int16 cnt;
{
	struct mbuf *bp;
	register struct INET_Unit *unit = (struct INET_Unit *) tcb->user;
	int amount, recamount;
	struct IOINETReq *iob;
/*	ObtainSemaphore(&(INLock));*/
	if (ListEmpty(&(unit->iu_Input)))
	  goto done;
	iob = unit->iu_Act_Input = unit->iu_Input.lh_Head;
	tracedev4("dev rupcall iob %d unit %d tcb%d\n",iob, unit, tcb);
	if (iob != NULL)
	  {
	    Remove(iob);
            amount = min(cnt, iob->io_Length);
	    tracedev3("call recv_tcp %d bytes avail %d\n", amount, cnt);
	    recamount = recv_tcp(tcb, &bp, amount);
	    iob->io_Actual = dqdata(bp, iob->io_Data, recamount);
	    tracedev2("recv_tcp after got %d bytes\n", iob->io_Actual);
	    TermIO(iob, unit);
/*	    ReplyMsg(&(iob->io_Message));*/
	  }
done:	
/*  ReleaseSemaphore(&(INLock));        */
}
/* TCP receiver upcall routine.  Called with TCB pointer and number of bytes
   available */

void
indev_tcp_r_upcall(tcb, cnt)
	struct tcb *tcb;
	int16 cnt;
{
/*	ObtainSemaphore(&(INLock));*/
	do_rupcall(tcb, 512);
/*  ReleaseSemaphore(&(INLock));        */
}

/* TCP transmitter upcall routine.  Called with TCB pointer and number of bytes
   free in send window */


do_tupcall(tcb, avail)
	struct tcb *tcb;
	int16 avail;
{
	struct mbuf *bp, *qdata();
	register struct INET_Unit *unit = (struct INET_Unit *) tcb->user;
	int amount;
	struct IOINETReq *iob;

	if (ListEmpty(&(unit->iu_Output)))
	  goto done;
	tracedev("non-empty Output\n");
	iob = unit->iu_Act_Output = unit->iu_Output.lh_Head;
	tracedev4("dev tupcall iob %d unit %d tcb%d\n",iob, unit, tcb);
	if (iob != NULL)
	  {
	    Remove(iob);

            amount = min(avail, iob->io_Length);
	    tracedev3("t_upcall- send_tcp for addr %x %d bytes\n",iob->io_Data,
			amount);
	    bp = qdata(iob->io_Data, amount);
	    iob->io_Actual = send_tcp(tcb, bp);
	    tracedev2("send_tcp after got %d bytes\n", iob->io_Actual);
	    TermIO(iob, unit);
/*	    ReplyMsg(&(iob->io_Message));*/
	    unit->iu_Act_Output = NULL;
	  }
done:


}
void
indev_tcp_t_upcall(tcb, avail)
	struct tcb *tcb;
	int16 avail;
{
/*	ObtainSemaphore(&(INLock));*/
	do_tupcall(tcb, avail);
/*  ReleaseSemaphore(&(INLock));        */

}

void
indev_s_upcall(tcb, old, new)
	struct tcb *tcb;
	char old, new;
{
	register struct INET_Unit *unit = (struct INET_Unit *) tcb->user;
	char notify = 0;
	extern char *tcpstates[];
	extern char *reasons[];
	extern char *unreach[];
	extern char *exceed[];

	/* Can't add a check for unknown connection here, it would loop
	 * on a close upcall! We're just careful later on.
	 */

	if(unit != NULL)
		notify = 1;

	switch(new){
	case CLOSE_WAIT:
		if(notify)
			printf("%s\r\n",tcpstates[new]);
		close_tcp(tcb);
		break;
	case CLOSED:	/* court adjourned */
		if(notify){
			printf("%s (%s",tcpstates[new],reasons[tcb->reason]);
			if(tcb->reason == NETWORK){
				switch(tcb->type){
				case DEST_UNREACH:
					printf(": %s unreachable",unreach[tcb->code]);
					break;
				case TIME_EXCEED:
					printf(": %s time exceeded",exceed[tcb->code]);
					break;
				}
			}
			printf(")\r\n");
		}
		del_tcp(tcb);
		break;
	default:
		if(notify)
			printf("%s\r\n",tcpstates[new]);
		break;
	}
	fflush(stdout);

}
#endif
