/*************************************************************************
**
** bl_Serial.c
** Copyright (c) 1995,1996 Daniel Kahlin <tlr@stacken.kth.se>
** based on the serial routines of 'Term' by Olaf Barthel
**
******/


#include <stdlib.h>
#include <stdio.h>
#include <error.h>
#include <string.h>
#include <devices/serial.h>
#include <devices/timer.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/devices.h>
#include <exec/errors.h>
#include <exec/tasks.h>
#include <dos/dos.h>
#include <dos/dostags.h>
#include <proto/exec.h>
#include <proto/dos.h>

#include "Block.h"
#include "main.h"
#include "mach.h"

#define SER_BAUDRATE 38400
#define SER_BREAKLENGTH 250000
#define SER_BITSPERCHAR 8
#define SER_STOPBITS 2
#define SER_QUANTUM 256
#define SERBUFFERSIZE 256
#define SERDEVICE "serial.device"
#define SERUNITNUM 0

/*** serial globals ***/
struct IOExtSer *ReadRequest=NULL;
struct IOExtSer *WriteRequest=NULL;
struct MsgPort *ReadPort=NULL;
UBYTE *ReadBuffer=NULL;
UBYTE *SerialMessage=NULL;

/*** timer globals ***/
struct timerequest	*timerreq=NULL;
struct timerequest	*timerreq2=NULL;
struct MsgPort			*timerport=NULL;
UWORD								gotTIMER=NULL;
struct Library			*TimerBase=NULL;
struct Task					*SigTask=NULL;
ULONG								sigf_timeout=NULL;

void MicroWait(ULONG microsecs);
void StartTimeout(ULONG secs);
void StopTimeout(void);
BOOL CheckTimeout(void);

void breakhandler(void);

/*
** Set Baud rate etc...
*/
void SetFlags(struct IOExtSer *SomeRequest)
{
	SomeRequest -> io_Baud		= SER_BAUDRATE;
	SomeRequest -> io_BrkTime	= SER_BREAKLENGTH;
	SomeRequest -> io_ReadLen	= SER_BITSPERCHAR;
	SomeRequest -> io_WriteLen	= SER_BITSPERCHAR;
	SomeRequest -> io_StopBits	= SER_STOPBITS;

	SomeRequest -> io_ExtFlags	&= ~(SEXTF_MSPON | SEXTF_MARK);
	SomeRequest -> io_SerFlags	&= ~(SERF_PARTY_ON | SERF_PARTY_ODD | SERF_7WIRE | SERF_RAD_BOOGIE);

	SomeRequest -> io_SerFlags |= SERF_RAD_BOOGIE;
	SomeRequest -> io_SerFlags |= SERF_XDISABLED;
}


/*
** Set Baud rate etc...
*/
void Setbaud(int baudrate)
{
	WriteRequest -> IOSer . io_Command = SDCMD_SETPARAMS;
	WriteRequest -> io_Baud		= baudrate;
	WriteRequest -> io_BrkTime	= SER_BREAKLENGTH;
	WriteRequest -> io_ReadLen	= SER_BITSPERCHAR;
	WriteRequest -> io_WriteLen	= SER_BITSPERCHAR;
	WriteRequest -> io_StopBits	= SER_STOPBITS;

	WriteRequest -> io_ExtFlags	&= ~(SEXTF_MSPON | SEXTF_MARK);
	WriteRequest -> io_SerFlags	&= ~(SERF_PARTY_ON | SERF_PARTY_ODD | SERF_7WIRE | SERF_RAD_BOOGIE);

	WriteRequest -> io_SerFlags |= SERF_RAD_BOOGIE;
	WriteRequest -> io_SerFlags |= SERF_XDISABLED;
	DoIO((struct IORequest *)WriteRequest);
}


/*
** Sendbreak
*/
void SendBreak(void)
{
	if (WriteRequest) {
		WriteRequest -> IOSer . io_Command	= SDCMD_BREAK;
		DoIO((struct IORequest *)WriteRequest);
	}
}

/*
** clear
*/
void ClearSerial(void)
{
	if (WriteRequest) {
		WriteRequest -> IOSer . io_Command	= CMD_FLUSH;
		DoIO((struct IORequest *)WriteRequest);
	}
	if (ReadRequest) {
		ReadRequest -> IOSer . io_Command	= CMD_CLEAR;
		DoIO((struct IORequest *)ReadRequest);
	}
}


/*
** Write buffer to serport   (Size=-1) => zero terminated string
*/
void SerWrite(APTR Buffer,LONG Size)
{
	if(Size < 0)
		Size = strlen(Buffer);

	if(WriteRequest && Size) {
		WriteRequest -> IOSer . io_Command	= CMD_WRITE;
		WriteRequest -> IOSer . io_Data		= Buffer;
		WriteRequest -> IOSer . io_Length	= Size;

		DoIO((struct IORequest *)WriteRequest);

		/*		BytesOut += Size; */
	}
}


/*
** Read from serport to buffer (timeout=-1 => no timeout)
*/
int SerRead(APTR Buffer,ULONG Size,LONG timeout)
{
	ULONG	readsig=0,tempsig;

	readsig=1<<ReadPort->mp_SigBit;

	/*** clear signals (read and timeout) ***/
	SetSignal(0L,readsig|sigf_timeout);

	if(ReadRequest && Size) {
		if (timeout>0) StartTimeout(timeout);
		ReadRequest -> IOSer . io_Command	= CMD_READ;
		ReadRequest -> IOSer . io_Data		= Buffer;
		ReadRequest -> IOSer . io_Length	= Size;
		SendIO((struct IORequest *)ReadRequest);

		if (timeout>0) {
		/* handle read WITH timeout */
			tempsig=Wait(readsig|sigf_timeout|SIGBREAKF_CTRL_C|SIGBREAKF_CTRL_D);
			if (CheckTimeout()) {
				StopTimeout();
				AbortIO((struct IORequest *)ReadRequest);
				WaitIO((struct IORequest *)ReadRequest);
				return(1);
			}
			StopTimeout();
		} else {
		/* handle read WITHOUT timeout */
			tempsig=Wait(readsig|SIGBREAKF_CTRL_C|SIGBREAKF_CTRL_D);
		}

		/* break? */
		if (tempsig&(SIGBREAKF_CTRL_C|SIGBREAKF_CTRL_D)) {
			AbortIO((struct IORequest *)ReadRequest);
			WaitIO((struct IORequest *)ReadRequest);
			breakhandler();
		}

		WaitIO((struct IORequest *)ReadRequest);
	}
	return(0);
}


/*
** Deallocate all serial stuffs
*/
void DeleteSerial()
{
	BYTE Closed = FALSE;

	if(ReadRequest)	{
		if(ReadRequest -> IOSer . io_Device) {
			AbortIO((struct IORequest *)ReadRequest);
			WaitIO((struct IORequest *)ReadRequest);
			if(WriteRequest) {
				AbortIO((struct IORequest *)WriteRequest);
				WaitIO((struct IORequest *)WriteRequest);
			}
		//	ReadRequest -> IOSer . io_Command = CMD_RESET;
		//	DoIO((struct IORequest *)ReadRequest);
			CloseDevice((struct IORequest *)ReadRequest);
			Closed = TRUE;
		}
		DeleteIORequest(ReadRequest);
		ReadRequest = NULL;
	}

	if(WriteRequest) {
		if(WriteRequest -> IOSer . io_Device && (!Closed)) {
			AbortIO((struct IORequest *)WriteRequest);
			WaitIO((struct IORequest *)WriteRequest);
		//	WriteRequest -> IOSer . io_Command = CMD_RESET;
		//	DoIO((struct IORequest *)WriteRequest);
			CloseDevice((struct IORequest *)WriteRequest);
		}
		if(WriteRequest -> IOSer . io_Message . mn_ReplyPort)
			DeleteMsgPort(WriteRequest -> IOSer . io_Message . mn_ReplyPort);

		DeleteIORequest(WriteRequest);
		WriteRequest = NULL;
	}

	if(ReadPort) {
		DeleteMsgPort(ReadPort);
		ReadPort = NULL;
	}

	if(ReadBuffer) {
		FreeVec(ReadBuffer);
		ReadBuffer = NULL;
	}

/*** free timerstuff ***/
	if (timerreq2) FreeMem(timerreq2,sizeof(struct timerequest));
	if (gotTIMER) CloseDevice((struct IORequest *)timerreq);
	if (timerreq) DeleteIORequest((struct IORequest *)timerreq);
	if (timerport) DeleteMsgPort(timerport);

}


/*
** Return string for serial error
*/
STRPTR GetSerialError(LONG Error)
{

	switch(Error)	{
		case IOERR_BADLENGTH:
		case IOERR_BADADDRESS:
		case IOERR_SELFTEST:
		case IOERR_OPENFAIL:
			return("(ser) couldn't open serial device");

		case SerErr_DevBusy:
			return("(ser) serial device busy");

		case SerErr_BaudMismatch:
			return("(ser) baud mismatch");

		case SerErr_BufErr:
			return("(ser) buffer error");

		case SerErr_InvParam:
			return("(ser) invalid parameter");

		case SerErr_LineErr:
			return("(ser) line error");

		case SerErr_ParityErr:
			return("(ser) parity error");

		case SerErr_TimerErr:
			return("(ser) timer error");

		case SerErr_BufOverflow:
			return("(ser) buffer overflow");

		case SerErr_NoDSR:
			return("(ser) no DSR");

	/*	case SerErr_UnitBusy:	*/
		case IOERR_UNITBUSY:
		case 16:
			return("(ser) serial unit busy");

		default:
			return(NULL);
	}
}


/*
** Allocate all serial stuffs
*/
STRPTR CreateSerial()
{
	struct MsgPort *WritePort;

	/*** timer stuff ***/
	SigTask=FindTask(NULL);

	if (!(timerport=CreateMsgPort()))
		return("(ser) couldn't create timerport");

	sigf_timeout=1<<timerport->mp_SigBit;

	if (!(timerreq=(struct timerequest *)CreateIORequest(timerport,sizeof(struct timerequest))))
		{ DeleteSerial(); return("(ser) couldn't create timerreq"); }

	if (!(gotTIMER=(!OpenDevice(TIMERNAME,UNIT_MICROHZ,(struct IORequest *)timerreq, NULL))))
		{ DeleteSerial(); return("(ser) couldn't open timer.device"); }

	TimerBase=(struct Library *)timerreq->tr_node.io_Device;

	if (!(timerreq2=(struct timerequest *)AllocMem(sizeof(struct timerequest),MEMF_CLEAR|MEMF_PUBLIC)))
		{ DeleteSerial(); return("(ser) couldn't alloc timerreq2"); }

	*(timerreq2)=*(timerreq);


	/*** serial stuff ***/
	if(ReadBuffer = AllocVec(SERBUFFERSIZE,MEMF_PUBLIC)) {
			if(ReadPort = (struct MsgPort *)CreateMsgPort()) {
				if(ReadRequest = (struct IOExtSer *)CreateIORequest(ReadPort,sizeof(struct IOExtSer))) {
					LONG Error;

					SetFlags(ReadRequest);

					ReadRequest -> io_RBufLen = SERBUFFERSIZE;

					if(!(Error = OpenDevice(o5config.serdevice,o5config.serunitnum,(struct IORequest *)ReadRequest,0))) {
						if(WritePort = (struct MsgPort *)CreateMsgPort())	{
							if(WriteRequest = (struct IOExtSer *)CreateIORequest(WritePort,sizeof(struct IOExtSer))) {
								CopyMem(ReadRequest,WriteRequest,sizeof(struct IOExtSer));

								ReadRequest -> IOSer . io_Command	= CMD_RESET;
								DoIO((struct IORequest *)ReadRequest);

								WriteRequest -> IOSer . io_Message . mn_ReplyPort = WritePort;
								WriteRequest -> IOSer . io_Command = SDCMD_SETPARAMS;
								SetFlags(WriteRequest);
								DoIO((struct IORequest *)WriteRequest);

								return(NULL);
							}
						}
						else
						{
							SerialMessage = NULL;

							DeleteSerial();

							return("(ser) couldn't create write port");
						}
					}
					else
					{
						STRPTR String;

						ReadRequest -> IOSer . io_Device = NULL;

						DeleteSerial();

						SerialMessage = NULL;

						if(!(String = GetSerialError(Error)))
							String = "(ser) serial device busy";
						return(String);
					}
				}
			}
			else
			{
				SerialMessage = NULL;

				DeleteSerial();

				return("(ser) couldn't create read port");
			}
		
	}

	SerialMessage = NULL;

	DeleteSerial();

	return("(ser) not enough memory");
}



/* HandleSerial():
**
**	Handle the data coming in from the serial line.
*/
BYTE HandleSerial()
{
	BYTE MoreData = FALSE;

	if(ReadPort) {
		/* Any news? */

		if(CheckIO((struct IORequest *)ReadRequest)) {
			MoreData = TRUE;
			if(!WaitIO((struct IORequest *)ReadRequest)) {
				ULONG Length;

				SerWrite(ReadBuffer,1);
				/*(* ConTransfer)(ReadBuffer,1); */
				

				/* Check how many bytes are still in
				 * the serial buffer.
				 */

				WriteRequest -> IOSer . io_Command = SDCMD_QUERY;
				DoIO((struct IORequest *)WriteRequest);

				if(Length = WriteRequest -> IOSer . io_Actual) {
					ULONG Max = SERBUFFERSIZE;

					if(Max > SER_QUANTUM)
						Max = SER_QUANTUM;

					if(Length > Max)
						Length = Max;

					ReadRequest -> IOSer . io_Command	= CMD_READ;
					ReadRequest -> IOSer . io_Data		= ReadBuffer;
					ReadRequest -> IOSer . io_Length	= Length;

					if(!DoIO((struct IORequest *)ReadRequest)) {
						/*BytesIn += Length;
						(* ConTransfer)(ReadBuffer,Length); */
						SerWrite(ReadBuffer,Length);
					}
				}
			}

		}
	}
	return(MoreData);
}


/*************************************************************************
**
** microwait.
**
******/
void MicroWait(ULONG microsecs)
{
	timerreq->tr_node.io_Command=TR_ADDREQUEST;
	timerreq->tr_time.tv_secs=0;
	timerreq->tr_time.tv_micro=microsecs;
	DoIO((struct IORequest *)timerreq);
}



/*************************************************************************
**
** starttimeout.
**
******/
void StartTimeout(ULONG secs)
{
	timerreq2->tr_node.io_Command=TR_ADDREQUEST;
	timerreq2->tr_time.tv_secs=secs;
	timerreq2->tr_time.tv_micro=0;
	SendIO((struct IORequest *)timerreq2);
}



/*************************************************************************
**
** stoptimeout.
**
******/
void StopTimeout(void)
{
	if (!(CheckIO((struct IORequest *)timerreq2))) {
		AbortIO((struct IORequest *)timerreq2);
	}
	WaitIO((struct IORequest *)timerreq2);
}



/*************************************************************************
**
** Checkimeout.
**
******/
BOOL CheckTimeout(void)
{
	return((BOOL)(GetMsg(timerport)==(struct Message *)timerreq2));
}

