/*
 * PLIP.device - Parallel Line Internet Protocol
 *
 * A SANA2 networking device driver for two Amigas connected via their
 * parallel ports.
 *
 * By Oliver Wagner with a little assistance of Michael Balzer.
 * Copyright (c) 1993-1994 Oliver Wagner and Michael Balzer.
 *
 */

#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/cia.h>
#include <proto/misc.h>
#include <proto/utility.h>
#include <proto/timer.h>
#include <devices/sana2.h>
#include <hardware/cia.h>
#include <resources/misc.h>
#include <exec/memory.h>
#include <dos/dostags.h>
#include <string.h>

#define min __builtin_min
#define max __builtin_max
#define abs __builtin_abs

#define SERVER_TASK		"PLIP Server Task"

#define PLIP_MTU		8192
#define PLIP_DEFMTU		1024
#define PLIP_BPS		15000
#define PLIP_HWTYPE		13
#define PLIP_RETRIES	63

LONG plip_bps     = PLIP_BPS;
LONG plip_mtu     = PLIP_DEFMTU;
LONG plip_retries = PLIP_RETRIES;

#define ios2_Error ios2_Req.io_Error
#define ios2_Flags ios2_Req.io_Flags

#define PKTFRAMESIZE 8

struct intdata {
/*0*/	USHORT mode,pad1;
/*4*/	ULONG recsig;			/* signals for receive */
/*8*/	struct Task *sigtask;	/* device task to signal */
/*12*/  struct ExecBase *SysBase; /* local sysbase copy for interrupt */
};

struct sendframe {
	long sync;
	short size;
	unsigned short crc;
	long pkt;
	unsigned char data[PLIP_MTU];
} sendframe, receiveframe;

#define SYNCBYTE 0x42

struct trackrec {
	struct MinNode n;
	ULONG packettype;
	struct Sana2PacketTypeStats s2ps;
};	

struct MyDev {
	struct Library l;
	UBYTE flags, pad;
	APTR seglist;
};

struct BufRoutines {
	long __asm (*CopyToBuff)(register __a0 void*,register __a1 void*,register __d0 long);
	long __asm (*CopyFromBuff)(register __a0 void*,register __a1 void*,register __d0 long);
};

extern far volatile struct CIA ciaa,ciab;

__aligned struct Library *MiscBase, *CIAABase;
struct Library *UtilityBase, *TimerBase;
struct DosLibrary *DOSBase;
struct ExecBase *SysBase;

static __aligned struct intdata intdata;
static __aligned struct Interrupt intstruc;
static long allocflags;
static struct MsgPort *serverport, *timerport;
static __aligned struct Sana2DeviceStats devstats; /* device stats */
static __aligned struct timerequest treq;
static __aligned struct List readlist, writelist, eventlist, readorphanlist, tracklist;
static __aligned struct SignalSemaphore eventlistsem, writelistsem, tracklistsem;

#define FLG_CLIENT			1	/* client mode */
#define FLG_EXCLUSIVE		2	/* current opener is exclusive */
#define FLG_NOTCONFIGURED	4	/* not configured */
#define FLG_OFFLINE			8
#define ISOFFLINE			(devflags & (FLG_OFFLINE | FLG_NOTCONFIGURED))
static long					devflags = FLG_NOTCONFIGURED;	/* current flags */

#define IM_CLIENT			1	/* client mode */
#define IM_SEND				2	/* sending pkt */
#define IM_REC				4	/* receiving */
#define IM_GETHEADER		8	/* got 1 char */
#define IM_BUFF2			16	/* just filled second buffer */
#define IM_RXERROR			32	/* receive error */
#define IM_GOTSYNC			64

extern void intcode(void);
extern void __asm settimeout(register __d0 ULONG timeout);
extern void __asm parinit(register __d0 int);
extern long __asm testbusy(void), testpout(void);
extern void __asm setbusy(register __d0 long), setpout(register __d0 long);

extern long __asm waitinputtoggle(void);
extern void __asm setciaoutput(void);
extern void __asm outputtoggle(void);
extern void __asm sampleinput(void);

extern USHORT __asm CRC16( register __a0 UBYTE *, register __d0 SHORT );

/***********************************************************
 * TRACKTYPE support
 */

static struct trackrec * __inline findtracktype( ULONG type )
{
	struct trackrec * tr = ( struct trackrec * ) tracklist.lh_Head;

	while( tr->n.mln_Succ )
	{
		if( tr->packettype == type )
			return( tr );
		tr = ( struct trackrec * ) tr->n.mln_Succ;
	}

	return( NULL );
}

static int addtracktype( ULONG type )
{
	struct trackrec *tr;

	ObtainSemaphore( &tracklistsem );
	tr = findtracktype( type );
	if( tr )
	{
		ReleaseSemaphore( &tracklistsem );
		return( -1 );
	}
	else
	{
		tr = AllocMem( sizeof( *tr ), MEMF_CLEAR );
		if( !tr )
		{
			ReleaseSemaphore( &tracklistsem );
			return( -2 );
		}
		tr->packettype = type;
		AddTail( &tracklist, ( struct Node * ) tr );
	}

	ReleaseSemaphore( &tracklistsem );
	return( 0 );
}

static int remtracktype( ULONG type )
{
	struct trackrec *tr;

	ObtainSemaphore( &tracklistsem );

	tr = findtracktype( type );
	if( tr )
	{
		Remove( ( struct Node * ) tr );
		FreeMem( tr, sizeof( *tr ) );
		ReleaseSemaphore( &tracklistsem );
		return( 0 );
	}
	ReleaseSemaphore( &tracklistsem );
	return( -1 );
}

static void __inline dotracktype( ULONG type, ULONG ps, ULONG pr, ULONG bs, ULONG br, ULONG pd )
{
	struct trackrec * tr;

	ObtainSemaphoreShared( &tracklistsem );
	tr = findtracktype( type );	
	if( tr )
	{
		tr->s2ps.PacketsSent += ps;
		tr->s2ps.PacketsReceived += pr;
		tr->s2ps.BytesSent += bs;
		tr->s2ps.BytesReceived += br;
		tr->s2ps.PacketsDropped += pd;
	}
	ReleaseSemaphore( &tracklistsem );
}

static int gettrackrec( ULONG type, APTR info )
{
	struct trackrec * tr;

	ObtainSemaphoreShared( &tracklistsem );
	tr = findtracktype( type );	
	if( tr )
	{
		memcpy( info, &tr->s2ps, sizeof( tr->s2ps ) );
		ReleaseSemaphore( &tracklistsem );
		return( 0 );
	}
	ReleaseSemaphore( &tracklistsem );
	return( -1 );
}

/***********************************************************
 * CIA Zugriff
 */

static void __inline writecia( unsigned char val )
{
	ciaa.ciaprb = val;
}

static unsigned char __inline readciabyte( void )
{
	return( ciaa.ciaprb );
}


/***********************************************************
 * Event-Handling: Alle Event-Requests zurückschicken,
 * die auf das eingetretene Ereignis passen.
 */

void DoEvent( long event )
{
	struct IOSana2Req *ior, *ior2;

	ObtainSemaphore( &eventlistsem );
	
	for( ior = (struct IOSana2Req *) eventlist.lh_Head;
		 ior2 = (struct IOSana2Req *) ior->ios2_Req.io_Message.mn_Node.ln_Succ;
		 ior = ior2 )
	{
		if( ior->ios2_WireError & event )
		{
			Remove( ior );
			ReplyMsg( ior );
		}
	}
	
	ReleaseSemaphore( &eventlistsem );
}


/***********************************************************
 * Interrupt Support
 */

static void __inline disableint( void )
{
	AbleICR( CIAABase, CIAICRF_FLG );
}

static void __inline enableint( void )
{
	AbleICR( CIAABase, CIAICRF_FLG | CIAICRF_SETCLR );
}


static long setupint( void )
{
	MiscBase = OpenResource( "misc.resource" );
	CIAABase = OpenResource( "ciaa.resource" );

	/* Parallel-Port allokieren */
	if( AllocMiscResource( MR_PARALLELPORT, "plip.device" ) ) return 1;
	allocflags |= 1;
	if( AllocMiscResource( MR_PARALLELBITS, "plip.device" ) ) return 2;
	allocflags |= 2;

	intstruc.is_Node.ln_Type = NT_INTERRUPT;
	intstruc.is_Node.ln_Pri  = 0;
	intstruc.is_Node.ln_Name = "PLIP PORTS Interrupt";
	intstruc.is_Data         = &intdata;
	intstruc.is_Code         = intcode;

	/* Interrupt einfügen */
	Disable();
	{
		/* Parallel-Port initialisieren */
		parinit( intdata.mode & IM_CLIENT );
		
		if( AddICRVector( CIAABase, CIAICRB_FLG, &intstruc ) )
		{
			Enable();
			return 3;
		}
		
		allocflags |= 4;
	}
	Enable();

	enableint();
	
	return 0;
}

static void cleanupint( void )
{
	if( allocflags & 4 ) RemICRVector( CIAABase, CIAICRB_FLG, &intstruc );
	if( allocflags & 2 ) FreeMiscResource( MR_PARALLELBITS );
	if( allocflags & 1 ) FreeMiscResource( MR_PARALLELPORT );
	allocflags = 0;
}


/***********************************************************
 * WRITE
 *
 * Der Leser steuert über POUT (bzw. BUSY) die Übertragung.
 * Init: Schreiber schreibt Byte 1, was beim Leser einen Interrupt
 * auslöst. Der Leser quittiert den Empfang jedes Bytes mit einem Wechsel
 * der Handshake-Leitung, der Schreiber zeigt dem Leser durch Wechsel
 * der anderen Handshake-Leitung an, wenn ein Byte bereit steht.
 *
 * Um Deadlocks zu vermeiden (race condition) wird _einmal_ _vor_ der
 * Übertragung der Zustand des Handshakes eingelesen und dann immer
 * nur noch ohne vorherigen Lesezugriff auf den Wechsel gewartet.
 */

/* Falls beim Warten auf die Lesebestätigung ein Timeout passiert: */
#define ABORTWRITE { \
	DoEvent( S2EVENT_ERROR | S2EVENT_TX ); \
	intdata.mode &= ~IM_SEND; \
	parinit( intdata.mode & IM_CLIENT ); \
	enableint(); \
	return(-1); }

static long procwrite( unsigned char *data, long size )
{
	/* Init */
	disableint();
	setciaoutput();
	
	/* aktuellen Stand der Handshake-Leitung samplen */
	sampleinput();
	
	/* Übertragung anstoßen */
	writecia( *data++ );

	while( --size )
	{
		if( !waitinputtoggle() ) ABORTWRITE;
		writecia( *data++ );
		outputtoggle();
	}

	/* Bestätigung für letztes Byte abwarten */
	if( !waitinputtoggle() ) ABORTWRITE;
	
	/* Ok. */
	intdata.mode &= ~IM_SEND;
	parinit( intdata.mode & IM_CLIENT );
	enableint();
	devstats.PacketsSent++;

	return 0;
}


/***********************************************************
 * READ
 *
 * Wird vom Interrupt aus angestoßen.
 */

/* Falls beim Warten auf das nächste Byte ein Timeout passiert: */
#define ABORTREAD { \
	devstats.BadData++; \
	DoEvent( S2EVENT_ERROR | S2EVENT_RX ); \
	intdata.mode &= ~IM_REC; \
	parinit( intdata.mode & IM_CLIENT ); \
	enableint(); \
	return; }

static void procread( void )
{
	unsigned short crc;
	short len;
	int l;
	long bufferror,pkttyp;

	struct IOSana2Req *got, *ro;
	unsigned char *d, ch;

	/* Init */
	disableint();
	sampleinput();
	
	/* SYNC? */
	if( readciabyte() != 0x42 ) ABORTREAD;
	
	/* restliche SYNC-Bytes überlesen */
	for(;;)
	{
		outputtoggle();
		if( !waitinputtoggle() ) ABORTREAD;
		
		if( (ch = readciabyte()) != 0x42 )
			break;
	}

	/* Größe des Packets einlesen */
	d = (unsigned char *) &receiveframe.size;
	*d++ = ch;
	outputtoggle();
	
	if( !waitinputtoggle() ) ABORTREAD;
	*d++ = readciabyte();
	outputtoggle();

	/* Packetgröße sinnvoll? */
	len = receiveframe.size;
	if( len < 6 || len > plip_mtu+6 ) ABORTREAD;

	/* len Bytes einlesen */
	while( len-- )
	{
		if( !waitinputtoggle() ) ABORTREAD;
		*d++ = readciabyte();
		outputtoggle();
	}
	
	/* fertig mit Lesen */
	intdata.mode &= ~IM_REC;

	/* Exit */
	parinit( intdata.mode & IM_CLIENT );
	enableint();
	
	/* CRC check */
	pkttyp = receiveframe.pkt;
	l = receiveframe.size - 6;
	d = receiveframe.data;

	crc = CRC16( d, l );

	if( crc == receiveframe.crc )
	{
		/* CRC OK! */

		devstats.PacketsReceived++;

		dotracktype( pkttyp, 0, 1, 0, l, 0 );
		
		for( got = (struct IOSana2Req *) readlist.lh_Head;
			 got && got->ios2_Req.io_Message.mn_Node.ln_Succ;
			 got = (struct IOSana2Req *) got->ios2_Req.io_Message.mn_Node.ln_Succ )
		{
			if( got->ios2_PacketType == pkttyp )
			{
				/* Hurra, der will's haben! */
				Remove( got );
				
				/* Daten kopieren */
				bufferror = ((struct BufRoutines *) got->ios2_BufferManagement)
					-> CopyToBuff( got->ios2_Data, receiveframe.data, receiveframe.size - 6 );
				
				/* Fehler? */
				if( !bufferror )
				{
					got->ios2_Error     = S2ERR_SOFTWARE;
					got->ios2_WireError = S2WERR_BUFF_ERROR;
				}
				else
				{
					got->ios2_Error = got->ios2_WireError = 0;
				}

				/* Request befriedigen */
				got->ios2_Flags      = 0;
				got->ios2_SrcAddr[0] = (devflags & FLG_CLIENT) ?  0     : (1<<7);
				got->ios2_DstAddr[0] = (devflags & FLG_CLIENT) ? (1<<7) :  0    ;
				got->ios2_DataLength = receiveframe.size - 6;
				ReplyMsg( got );
				
				/* und 'raus */
				got=0;
				break;
			}
		}
	}
	else
	{
		/* CRC ERROR! */

		/* Clients Bescheid sagen, daß Müll ankam */
		DoEvent( S2EVENT_ERROR | S2EVENT_RX );
		
		got = 0;
		devstats.BadData++;
	}
	
	if( got )
	{
		/* Hm... wir haben ein Packet bekommen, das keiner haben will... */
		devstats.UnknownTypesReceived++;
		
		/* ...aber vielleicht ist da ja jemand, der genau solche Packets auffängt: */
		if( ro = (struct IOSana2Req *) RemHead( &readorphanlist ) )
		{
			/* Packet komplett kopieren */
			bufferror = ((struct BufRoutines *) ro->ios2_BufferManagement)
				-> CopyToBuff( ro->ios2_Data, &receiveframe.pkt, receiveframe.size - 2 );
			
			/* Fehler? */
			if( !bufferror )
			{
				ro->ios2_Error     = S2ERR_SOFTWARE;
				ro->ios2_WireError = S2WERR_BUFF_ERROR;
			}
			else
			{
				ro->ios2_Error = ro->ios2_WireError = 0;
			}
			
			/* Request befriedigen */
			ro->ios2_Flags      = 0;
			ro->ios2_SrcAddr[0] = (devflags & FLG_CLIENT) ?  0     : (1<<7);
			ro->ios2_DstAddr[0] = (devflags & FLG_CLIENT) ? (1<<7) :  0    ;
			ro->ios2_DataLength = receiveframe.size - 2;
			ReplyMsg( ro );
		}

		/* Noe, also entsorgen */
		dotracktype( pkttyp, 0, 0, 0, 0, 1 );

	}
	
}

/***********************************************************
 * READARGS - reads ENV:PLIP
 */

static void __saveds readargs( void )
{
	struct RDArgs *rda;
	struct args {
		ULONG *timeout;
		LONG *priority;
		ULONG *mtu;
		ULONG *bps;
		ULONG *retries;
	} args = { 0 };
	BPTR plipvar = Open( "ENV:SANA2/PLIP.config", MODE_OLDFILE );
	BPTR oldinput, infowin, oldoutput;

	infowin = Open( "con://320/128/PLIP V38.11/NOSIZE/INACTIVE", MODE_NEWFILE );
	
	if( infowin )
	{
		FPrintf( infowin,
			"\x1b[0 p\n"
			"\x1b[1mPLIP V38.11\x1b[0m (" __DATE__ " " __TIME__ ")\n"
			"by \x1b[3mOliver Wagner\x1b[0m & \x1b[3mMichael Balzer\x1b[0m\n"
			"Freeware -- see manual!\n\n" );
	}

	if( !plipvar )
		goto xit;

	oldinput = SelectInput( plipvar );
	
	rda = ReadArgs( "TIMEOUT/K/N,PRIORITY=PRI/K/N,MTU/K/N,BPS/K/N,RETRIES/K/N" , (LONG *)&args, NULL );
	
	if(rda)
	{
		if(args.timeout)
		{
			settimeout( *args.timeout );
			if( infowin ) FPrintf(infowin, " Timeout.: %lx/%ld\n", *args.timeout, *args.timeout);
		}
		if(args.priority)
		{
			SetTaskPri( FindTask(SERVER_TASK), *args.priority);
			if( infowin ) FPrintf(infowin, " Priority: %ld\n", *args.priority);
		}
		if( args.mtu )
		{
			if( *args.mtu <= PLIP_MTU )
			{
				plip_mtu = *args.mtu;
				if( infowin ) FPrintf(infowin, " MTU: %ld\n", *args.mtu);
			}
		}
		if( args.bps )
		{
			plip_bps = *args.bps;
			if( infowin ) FPrintf(infowin, " BPS: %ld\n", *args.bps);
		}
		if( args.retries )
		{
			if( *args.retries <= 127 )
			{
				plip_retries = *args.retries;
				if( infowin ) FPrintf(infowin, " Max Retries: %ld\n", *args.retries);
			}
		}
		FreeArgs( rda );
	}
	else if(infowin)
	{
		oldoutput = SelectOutput( infowin );
		PrintFault( IoErr(), "\x07 PLIP");
		SelectOutput( oldoutput );
	}

	Close( SelectInput( oldinput ));

xit:
	/* Wake up Main server */
	Signal( intdata.sigtask, SIGBREAKF_CTRL_E );

	if( infowin )
	{
		Delay( 10*50 );
		Close( infowin );
	}

}



/***********************************************************
 * MAINLOOP - Device Dispatcher
 */

static void __saveds servertask( void )
{
	long portsig, s, timersig;
	struct IOSana2Req *currentwrite = 0, *got, *nextwrite;
	long timerqueued = 0, readstart = 0x42, bufferror;

	/*
	 * Setup
	 */
	
	s = AllocSignal( -1 );
	intdata.recsig = 1L << s;
	
	serverport = CreateMsgPort();
	timerport  = CreateMsgPort();
	portsig  = 1 << serverport->mp_SigBit;
	timersig = 1 << timerport->mp_SigBit;
	
	NewList( &readlist );	
	NewList( &writelist );
	NewList( &eventlist );
	NewList( &readorphanlist );
	NewList( &tracklist );
	
	InitSemaphore( &eventlistsem );
	InitSemaphore( &writelistsem );
	InitSemaphore( &tracklistsem );
	
	treq.tr_node.io_Message.mn_ReplyPort = timerport;
	OpenDevice( "timer.device", UNIT_VBLANK, &treq, 0 );
	TimerBase = (struct Library *) treq.tr_node.io_Device;
	GetSysTime( &devstats.LastStart );

	CreateNewProcTags(
		NP_Entry, readargs,
		NP_Name, "PLIP Arg Parser Process",
		NP_Priority, 5,
		NP_WindowPtr, -1,
		TAG_DONE
	);

	/* Wait for reply from Arg Parser */
	Wait( SIGBREAKF_CTRL_E );

	for(;;)
	{

		/* Wir lassen uns regelmäßig vom timer.device aufwecken, damit
		 * keine Senderequests liegen bleiben, wenn wir nicht sofort
		 * die Leitung bekommen können
		 */
		if( !timerqueued )
		{
			treq.tr_time.tv_secs    = 2 + (devflags & FLG_CLIENT);
			treq.tr_time.tv_micro   = 0;
			treq.tr_node.io_Command = TR_ADDREQUEST;
			SendIO( &treq );
			timerqueued = 1;
		}
		
		s = Wait( intdata.recsig | portsig | timersig );

		if( s & intdata.recsig )
		{
			/* Interrupt: Packet empfangen */
			procread();
		}
		
		if( s & timersig )
		{
			/* Aufwecksignal */
			AbortIO( &treq );
			WaitIO( &treq );
			timerqueued = 0;
		}
		
		if( s & portsig )
		{
			/* Kommandos (Requests) entgegennehmen */
			
			while( got = (struct IOSana2Req *) GetMsg( serverport ) )
			{

				if( got->ios2_Req.io_Command == CMD_READ )
					AddTail( &readlist, got );
				else if( got->ios2_Req.io_Command == S2_READORPHAN )
					AddTail( &readorphanlist, got );
				else
				{
					ObtainSemaphore( &writelistsem );
					AddTail( &writelist, got );
					ReleaseSemaphore( &writelistsem );
				}
			}
		}

		/*
		 * So, Signale alle erledigt; haben wir 'was zu schreiben?
		 */
		
		ObtainSemaphore( &writelistsem );
		
		for( currentwrite = (struct IOSana2Req *) writelist.lh_Head;
			 nextwrite = (struct IOSana2Req *) currentwrite->ios2_Req.io_Message.mn_Node.ln_Succ;
			 currentwrite = nextwrite )
		{

			/* Inzwischen ein Read-Interrupt? */
			if( SetSignal(0,0) & intdata.recsig) break;

			/* Arbitration */
			if( devflags & FLG_CLIENT )
			{
				/* Leitung frei? */
				if( !testbusy() )
					break;		/* später nochmal versuchen */
				
				/* Sende-Status setzen */
				setpout( 1 );
				
				/* Leitung immer noch frei? */
				if( !testbusy() )
				{
					setpout( 0 );
					break;		/* später nochmal versuchen */
				}
			}
			else
			{
				/* Leitung frei? */
				if( testpout() )
					break;		/* später nochmal versuchen */
				
				/* Sende-Status setzen */
				setbusy( 0 );
				
				/* Leitung immer noch frei? */
				if( testpout() )
				{
					/*setbusy( 1 );*/
					break;		/* später nochmal versuchen */
				}
			}
			
			/* Gut, wir können beginnen zu senden */
			
			/* Daten kopieren */
			bufferror = ((struct BufRoutines *) currentwrite->ios2_BufferManagement)
				-> CopyFromBuff( sendframe.data, currentwrite->ios2_Data, currentwrite->ios2_DataLength );
			
			/* Fehler? */
			if( !bufferror )
			{
				currentwrite->ios2_Error     = S2ERR_SOFTWARE;
				currentwrite->ios2_WireError = S2WERR_BUFF_ERROR;
				Remove( currentwrite );
				ReplyMsg( currentwrite );
				continue;
				/* weiter mit nächstem Write-Req */
			}
			
			/* Packet-Header füllen */
			sendframe.sync = 0x42424242;
			sendframe.pkt  = currentwrite->ios2_PacketType;
			sendframe.size = currentwrite->ios2_DataLength + 6;
			
			/* CRC berechnen und einsetzen */
			{
				int l = currentwrite->ios2_DataLength;
				unsigned char *d = sendframe.data;
				unsigned short crc = 0;

				crc = CRC16( d, l );

				sendframe.crc = crc;
			}
			
			/* Packet Senden */

			readstart = procwrite( (char *) &sendframe, sendframe.size + 6 );

			if( !readstart )
			{
				/* Packet erfolgreich gesendet */
				dotracktype( sendframe.pkt, 1, 0, currentwrite->ios2_DataLength, 0, 0 );
				currentwrite->ios2_Error = currentwrite->ios2_WireError = 0;
				Remove( currentwrite );
				ReplyMsg( currentwrite );
			}
			else
			{
				/* Fehler; WriteRetryCounter erhöhen */
				if( ( currentwrite->ios2_Error++ ) > plip_retries )
				{
					/* Maximale Fehleranzahl erreicht */
					currentwrite->ios2_Error 		= S2ERR_BAD_STATE;
					currentwrite->ios2_WireError	= S2WERR_GENERIC_ERROR;
					Remove( currentwrite );
					ReplyMsg( currentwrite );
				}
				break;
			}
		}
		
		ReleaseSemaphore( &writelistsem );
		
		/* Alle Signale bearbeitet */
	}
}


/************************************************************
 * DEVICE-Management und -Support
 */

long __asm __saveds DevOpen( register __a1 struct IOSana2Req *ior,
	register __d0 long unit, register __d1 long flags,
	register __a6 struct MyDev *devnode )
{
	struct TagItem *ti;
	struct BufRoutines *br;

	devnode->l.lib_OpenCnt++;	/* Expunge-Schutz */

	if( unit < 0 || unit > 1 )
	{
		devnode->l.lib_OpenCnt--;
		ior->ios2_Req.io_Error  = -1;
		ior->ios2_Req.io_Device = (struct Device *) -1;
		return( -1 );
		/* openfailed */ 
	}

	/* already open? */
	if( devnode->l.lib_OpenCnt > 1 )
	{
		if( (unit && !(devflags & FLG_CLIENT)) ||
			(flags & SANA2OPF_MINE) )
		{
			/* busy/open */
			devnode->l.lib_OpenCnt--;
			ior->ios2_Req.io_Error  = -1;
			ior->ios2_Req.io_Device = (struct Device *) -1;
			return( -1 );
		}
	}
	
	if( flags & SANA2OPF_MINE )
		devflags     |= FLG_EXCLUSIVE;
	
	if( unit )
	{
		devflags     |= FLG_CLIENT;
		intdata.mode |= IM_CLIENT;
	}

	intdata.SysBase=SysBase; /* Clone SysBase */

	/* Start Server Process */
	if( !intdata.sigtask ) 
		intdata.sigtask = CreateTask( SERVER_TASK, 15, servertask, 4000);
	
	if( !intdata.sigtask )
	{
		/* kein Task */
		devnode->l.lib_OpenCnt--; 
		ior->ios2_Req.io_Error  = -1;
		ior->ios2_Req.io_Device = (struct Device *) -1;
		return( -1 );
	}

	/* Task Setup Done; convert Taglist */
	br = AllocMem( 8, MEMF_CLEAR );
	
	if( ti = FindTagItem(S2_CopyToBuff, ior->ios2_BufferManagement) )
		br->CopyToBuff = ti->ti_Data;
	
	if( ti = FindTagItem(S2_CopyFromBuff, ior->ios2_BufferManagement) )
		br->CopyFromBuff = ti->ti_Data;

	if( !br->CopyToBuff || !br->CopyFromBuff )
	{
		FreeMem( br, 8 );
		devnode->l.lib_OpenCnt--; 
		ior->ios2_Req.io_Error  = -1;
		ior->ios2_Req.io_Device = (struct Device *) -1;
		return( -1 );
	}
	
	devnode->l.lib_Flags &= ~LIBF_DELEXP;
	
	ior->ios2_BufferManagement = br;
	ior->ios2_Req.io_Unit = (struct Unit *) br;
	ior->ios2_Req.io_Message.mn_Node.ln_Type = NT_REPLYMSG;
	ior->ios2_Req.io_Error = 0;
	return( 0 );
}


static long goonline( void )
{
	long x;

	if( ISOFFLINE )
	{
		if( x = setupint() )
			return( -1 );
		
		devflags &= ~(FLG_OFFLINE | FLG_NOTCONFIGURED);
	}

	return( 0 );	
}


static void gooffline( void )
{
	if( ! (ISOFFLINE) )
	{
		cleanupint();
		devflags |= FLG_OFFLINE;
	}
}


long __asm __saveds DevExpunge( register __a6 struct MyDev *devnode )
{
	long seglist;
	struct trackrec *trackrec;

	if( devnode->l.lib_OpenCnt )
	{
		devnode->l.lib_Flags |= LIBF_DELEXP;
		return( 0 );
	}
	
	gooffline();
	
	if( intdata.sigtask ) RemTask( intdata.sigtask );
	
	if( serverport ) FreeMem( serverport, sizeof(struct MsgPort) );
	if( timerport  ) FreeMem( timerport,  sizeof(struct MsgPort) );
	
	Remove( devnode );
	seglist = (long) devnode->seglist;
	FreeMem( ((char *) devnode) - devnode->l.lib_NegSize,
		devnode->l.lib_PosSize + devnode->l.lib_NegSize );

	/* Free tracktype records */
	while( ( trackrec = ( struct trackrec * ) RemHead( &tracklist ) ) )
		FreeMem( trackrec, sizeof( *trackrec ) );
	
	return( seglist );
}


long __asm __saveds DevClose( register __a1 struct IOSana2Req *ior,
	register __a6 struct MyDev *devnode )
{
	FreeMem( ior->ios2_Req.io_Unit, 8 );
	
	ior->ios2_Req.io_Device = (struct Device *) -1;
	ior->ios2_Req.io_Unit   = (struct Unit   *) -1;

	if( !(--devnode->l.lib_OpenCnt) ) /* ERROR! */
	{
		devflags &= ~FLG_EXCLUSIVE;
		if( devnode->l.lib_Flags & LIBF_DELEXP )
			return( DevExpunge(devnode) );
	}
	
	return( 0 );
}


void __saveds __asm DevBeginIO( register __a1 struct IOSana2Req *ior )
{
	struct Sana2DeviceQuery *devquery;

	ior->ios2_Error = IOERR_NOCMD;
	
	switch( ior->ios2_Req.io_Command )
	{
		case S2_GETSTATIONADDRESS:
			ior->ios2_SrcAddr[0] = ior->ios2_DstAddr[0] = (devflags & FLG_CLIENT) ? 1<<7 : 0;
			ior->ios2_Error = ior->ios2_WireError = 0;
			break;

		case S2_TRACKTYPE:
			if( addtracktype( ior->ios2_PacketType ) )
			{
				ior->ios2_Error = S2ERR_BAD_STATE;
				ior->ios2_WireError = S2WERR_ALREADY_TRACKED;
			}
			else
				ior->ios2_Error = ior->ios2_WireError = 0;
			break;

		case S2_UNTRACKTYPE:
			if( remtracktype( ior->ios2_PacketType ) )
			{
				ior->ios2_Error = S2ERR_BAD_STATE;
				ior->ios2_WireError = S2WERR_NOT_TRACKED;
			}
			else
				ior->ios2_Error = ior->ios2_WireError = 0;
			break;


		case S2_GETTYPESTATS:
			if( gettrackrec( ior->ios2_PacketType, ior->ios2_StatData ) )
			{
				ior->ios2_Error = S2ERR_BAD_STATE;
				ior->ios2_WireError = S2WERR_NOT_TRACKED;
			}
			else
				ior->ios2_Error = ior->ios2_WireError = 0;
			break;

		case S2_GETGLOBALSTATS:
			memcpy( ior->ios2_StatData, &devstats, sizeof(struct Sana2DeviceStats) );
			ior->ios2_Error = ior->ios2_WireError = 0;
			break;
			
		case S2_DEVICEQUERY:
			ior->ios2_Error = ior->ios2_WireError = 0;
			devquery = ior->ios2_StatData;
			devquery->DevQueryFormat = devquery->DeviceLevel = 0;
			
			if( devquery->SizeAvailable >  3 ) devquery->AddrFieldSize = 1;
			if( devquery->SizeAvailable >  5 ) devquery->MTU           = plip_mtu;
			if( devquery->SizeAvailable >  9 ) devquery->BPS           = plip_bps;
			if( devquery->SizeAvailable > 13 ) devquery->HardwareType  = PLIP_HWTYPE;
			
			devquery->SizeSupplied = min( devquery->SizeAvailable, 18 );
			break;
			
		case S2_ONLINE:
			if( goonline() )
				ior->ios2_Error = S2ERR_NO_RESOURCES;
			else
				ior->ios2_Error = ior->ios2_WireError = 0;
			break;
			
		case S2_OFFLINE:
			gooffline();
			ior->ios2_Error = ior->ios2_WireError = 0;
			break;

		case S2_ONEVENT:
			ObtainSemaphore( &eventlistsem );
			AddTail( &eventlist, ior );
			ReleaseSemaphore( &eventlistsem );
			return;

		case CMD_READ:
		case S2_READORPHAN:
			if( ISOFFLINE )
			{
				ior->ios2_Error     = S2ERR_OUTOFSERVICE;
				ior->ios2_WireError = S2WERR_UNIT_OFFLINE;
				break;
			}
			
			PutMsg( serverport, ior );
			return;
			
		case CMD_WRITE:
		case S2_BROADCAST:
			if( ISOFFLINE )
			{
				ior->ios2_Error     = S2ERR_OUTOFSERVICE;
				ior->ios2_WireError = S2WERR_UNIT_OFFLINE;
				break;
			}
			
			if( ior->ios2_DataLength > plip_mtu )
			{
				ior->ios2_Error     = S2ERR_MTU_EXCEEDED;
				ior->ios2_WireError = 0;
				break;
			}

			/* 38.10: ios2_Error wird als WriteRetryCounter benutzt */

			ior->ios2_Error = 0;
			
			PutMsg( serverport, ior );
			return;
			
		case S2_CONFIGINTERFACE:
			if( devflags & FLG_NOTCONFIGURED )
			{
				ior->ios2_SrcAddr[0] = (devflags & FLG_CLIENT) ? (1<<7) : 0;
				
				if( goonline() )
				{
					ior->ios2_Error     = S2ERR_NO_RESOURCES;
					ior->ios2_WireError = 0;
				}
				else
				{
					ior->ios2_Error = ior->ios2_WireError = 0;
				}
				
				break;
			}
			
			ior->ios2_Error     = S2ERR_SOFTWARE;
			ior->ios2_WireError = S2WERR_IS_CONFIGURED;
			break;
	}

	ior->ios2_Req.io_Message.mn_Node.ln_Type = NT_REPLYMSG;
	return;
}


static long isinlist( struct Node *n, struct List *l )
{
	struct Node *cmp = l->lh_Head;

	while( cmp != n && cmp->ln_Succ )
		cmp = cmp->ln_Succ;
	
	return( cmp == n );
}


long __asm __saveds DevAbortIO( register __a1 struct IOSana2Req *ior )
{
	Forbid();
	
	if( isinlist(ior, &eventlist) ||
		isinlist(ior, &readlist)  ||
		isinlist(ior, &readorphanlist) )
	{
		Remove( ior );
		ior->ios2_Error     = IOERR_ABORTED;
		ior->ios2_WireError = 0;
		ReplyMsg( ior );
		Permit();
		return( 0 );
	}
	else
	{
		ObtainSemaphore( &writelistsem );
		
		if( isinlist(ior, &writelist) )
		{
			Remove( ior );
			ReleaseSemaphore( &writelistsem );
			
			ior->ios2_Error     = IOERR_ABORTED;
			ior->ios2_WireError = 0;
			
			ReplyMsg( ior );
			Permit();
			return( 0 );
		}
		
		ReleaseSemaphore( &writelistsem );
	}
	
	Permit();
	
	return( -1 );
}

/* EOF */
