/*
Copyright 2001-2018 John Wiseman G8BPQ

This file is part of LinBPQ/BPQ32.

LinBPQ/BPQ32 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

LinBPQ/BPQ32 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with LinBPQ/BPQ32.  If not, see http://www.gnu.org/licenses
*/	

//
//	Module to provide HDLC Card (DRSI, Baycom etc) support for
//	G8BPQ switch in a 32bit environment

//
//	Win95 - Uses BPQHDLC.VXD to drive card
//  NT -Uses BPQHDLC.DRV to drive card
//
#define _CRT_SECURE_NO_DEPRECATE 

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include "CHeaders.h"
#include "bpq32.h"


extern VOID * TRACE_Q;

#ifdef WIN32

_CRT_OBSOLETE(GetVersionEx) errno_t __cdecl _get_winmajor(__out unsigned int * _Value);
_CRT_OBSOLETE(GetVersionEx) errno_t __cdecl _get_winminor(__out unsigned int * _Value);


#define FILE_DEVICE_BPQHDLC			0x00008421

#define IOCTL_BPQHDLC_SEND			CTL_CODE(FILE_DEVICE_BPQHDLC,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_BPQHDLC_POLL			CTL_CODE(FILE_DEVICE_BPQHDLC,0x801,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_BPQHDLC_TIMER			CTL_CODE(FILE_DEVICE_BPQHDLC,0x802,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_BPQHDLC_ADDCHANNEL	CTL_CODE(FILE_DEVICE_BPQHDLC,0x803,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_BPQHDLC_CHECKTX		CTL_CODE(FILE_DEVICE_BPQHDLC,0x804,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_BPQHDLC_IOREAD		CTL_CODE(FILE_DEVICE_BPQHDLC,0x805,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_BPQHDLC_IOWRITE		CTL_CODE(FILE_DEVICE_BPQHDLC,0x806,METHOD_BUFFERED,FILE_ANY_ACCESS)


VOID __cdecl Debugprintf(const char * format, ...);


//	Info to pass to Kernel HDLC Driver to define an SCC Subchannel

typedef struct _BPQHDLC_ADDCHANNEL_INPUT {

	ULONG   IOBASE;			// IO Base Address
    ULONG	IOLEN;			// Number of Addresses
    UCHAR   Interrupt;		// Interrupt
	UCHAR	Channel;

	ULONG	ASIOC;			// A CHAN ADDRESSES
	ULONG	SIO;			// OUR ADDRESSES (COULD BE A OR B) 
	ULONG	SIOC;			// Our Control Channel 
	ULONG	BSIOC;			//  B CHAN CONTROL

	VOID * OtherChannel;		// Kernel Channel record for first channel if this is 2nd channel

	UCHAR SOFTDCDFLAG;			// Use SoftDCD flag

	int TXBRG;				// FOR CARDS WITHOUT /32 DIVIDER
	int RXBRG;

	UCHAR WR10;				// NRZ/NRZI FLAG
 
	USHORT TXDELAY;			//TX KEYUP DELAY TIMER
	UCHAR PERSISTANCE;

}  BPQHDLC_ADDCHANNEL_INPUT, *PBPQHDLC_ADDCHANNEL_INPUT;


DWORD n;

HANDLE hDevice=0;
BYTE bOutput[4]=" ";
DWORD cb=0;
int fResult=0;

BOOL Win98 = FALSE;


extern int QCOUNT;
int Init98(HDLCDATA * PORTVEC);
int Init2K(HDLCDATA * PORTVEC);
int INITPORT(PHDLCDATA PORTVEC);



int HDLCRX2K(PHDLCDATA PORTVEC, UCHAR * buff)
{
	ULONG Param;
	DWORD len=0;

	if (hDevice == 0)
		return (0);


	if (PORTVEC->DRIVERPORTTABLE == 0) return 0;

	memcpy(&Param, &PORTVEC->DRIVERPORTTABLE,4);

	fResult = DeviceIoControl(
			hDevice,   // device handle
			(Win98) ? 'G' : IOCTL_BPQHDLC_POLL,		   // control code
			&Param, (Win98) ? (rand() & 0xff) : 4, //Input Params
	        buff,360,&len, // output parameters
			0);

	return (len);
}

int HDLCTIMER2K(PHDLCDATA  PORTVEC)
{
	DWORD len=0;

	if (hDevice == 0)
		return (0);

	if (PORTVEC->DRIVERPORTTABLE == 0) return 0;

	fResult = DeviceIoControl(
			hDevice,   // device handle
			(Win98) ? 'T' : IOCTL_BPQHDLC_TIMER,		   // control code
			&PORTVEC->DRIVERPORTTABLE,4, //Input Params
	        0,0,&len, // output parameters
			0);

	return (0);
}
 int HDLCTXCHECK2K(PHDLCDATA PORTVEC)
 {
	DWORD Buff;
	DWORD len=0;

	if (hDevice == 0)
		return (0);

	if (Win98)
		return 0;

	if (PORTVEC->DRIVERPORTTABLE == 0) return 0;

	fResult = DeviceIoControl(
			hDevice,   // device handle
			IOCTL_BPQHDLC_CHECKTX,		   // control code
			&PORTVEC->DRIVERPORTTABLE,4, //Input Params
	        &Buff,4,&len, // output parameters
			0);

	return (Buff);
}



int HDLCTX2K(PHDLCDATA  PORTVEC,UCHAR * buff)
{
	DWORD txlen=0;

	if (hDevice == 0)
		return (0);

	txlen=(buff[6]<<8) + buff[5];
	
	memcpy(buff,&PORTVEC->DRIVERPORTTABLE,4);
	
	fResult = DeviceIoControl(
			hDevice,   // device handle
			(Win98) ? 'S' : IOCTL_BPQHDLC_SEND,	   // control code
		   // control code
			buff,txlen,	// input parameters
	        NULL,0,&cb, // output parameters
			0);

	return (0);
}


int HDLCCLOSE(PHDLCDATA PORTVEC)
{
	if (hDevice)
	{
		CloseHandle(hDevice);
		hDevice = 0;
	}

	return 0;
}

int HDLCRX98(PHDLCDATA PORTVEC, UCHAR * buff)
{
	DWORD len=0;

	if (hDevice == 0)
		return (0);

	fResult = DeviceIoControl(
			hDevice,   // device handle
			'G',		   // control code
			PORTVEC->DRIVERPORTTABLE,rand() & 0xff, //Input Params
	        buff,360,&len, // output parameters
			0);

	return (len);
}

int HDLCTIMER98(PHDLCDATA PORTVEC)
{
	DWORD len=0;

	if (hDevice == 0)
		return (0);

	fResult = DeviceIoControl(
			hDevice,   // device handle
			'T',		   // control code
			PORTVEC->DRIVERPORTTABLE,4, //Input Params
	        0,0,&len, // output parameters
			0);

	return (0);
}

 int HDLCTXCHECK98(PHDLCDATA PORTVEC)
 {
	 return 0;
 }

int HDLCTX98(PHDLCDATA PORTVEC,UCHAR * buff)
{
	DWORD txlen=0;

	if (hDevice == 0)
		return (0);

	txlen=(buff[6]<<8) + buff[5];
	
	memcpy(buff,&PORTVEC->DRIVERPORTTABLE,4);
	
	fResult = DeviceIoControl(
			hDevice,   // device handle
			'S',		   // control code
			buff,txlen,// input parameters
	        NULL,0,&cb, // output parameters
			0);

	return (0);
}

int IntHDLCRX(PHDLCDATA PORTVEC, UCHAR * buff)
{
	if (Win98)
		return HDLCRX98(PORTVEC, buff);
	else
		return HDLCRX2K(PORTVEC, buff);
}

VOID HDLCRX(PHDLCDATA PORTVEC)
{
	struct _MESSAGE * Message;
	int Len;
	struct PORTCONTROL * PORT = (struct PORTCONTROL *)PORTVEC;

	if (QCOUNT < 10)
		return;

	Message = GetBuff();

	if (Message == NULL)
		return;

	Len = IntHDLCRX(PORTVEC, (UCHAR *)Message);
	
	if (Len == 0)
	{
		ReleaseBuffer((UINT *)Message);
		return;
	}

	C_Q_ADD(&PORT->PORTRX_Q, (UINT *)Message);

	return;
}

int HDLCTIMER(PHDLCDATA PORTVEC)
{
	if (Win98)
		return HDLCTIMER98(PORTVEC);
	else
		return HDLCTIMER2K(PORTVEC);
}

int HDLCTXCHECK(PHDLCDATA PORTVEC)
{
	if (Win98)
		return HDLCTXCHECK98(PORTVEC);
	else
		return HDLCTXCHECK2K(PORTVEC);
}



VOID HDLCTX(PHDLCDATA PORTVEC, PMESSAGE Buffer)
{
	struct _LINKTABLE * LINK;
	
	LINK = Buffer->Linkptr;

	if (LINK)
	{
		if (LINK->L2TIMER)
			LINK->L2TIMER = LINK->L2TIME;

		Buffer->Linkptr = 0;	// CLEAR FLAG FROM BUFFER
	}


	if (Win98)
		HDLCTX98(PORTVEC, (UCHAR *)Buffer);
	else
		HDLCTX2K(PORTVEC, (UCHAR *)Buffer);
	
	C_Q_ADD(&TRACE_Q, (UINT *)Buffer);

}

int HDLCINIT(HDLCDATA * PORTVEC)
{
	int WinVer = 0x0602, WinMinor = 0x02;

	WritetoConsole("HDLC\n");

#pragma warning(push)
#pragma warning(disable : 4996)

#ifndef _winver

#define _winmajor 6
#define _winminor 0

#endif

#pragma warning(pop)
	
	if (WinVer >= 5)		// Win 2000 or above
		return Init2K(PORTVEC);
	else
	{
		Init98(PORTVEC);
		OutputDebugString("HDLC Win98 Return from Init98\n");
		return 0;
	}

}

int Init98(HDLCDATA * PORTVEC)
{
	char msg[255];
	int err;

	Win98 = TRUE;
	
	OutputDebugString("Init HDLC 98\n");

	//
	//	Open HDLC Driver, send send config params
	//

	if (hDevice == 0)		// Not already loaded
	{
		//
		//	Load VXD
		//

		hDevice = CreateFile("\\\\.\\BPQHDLC.VXD",
			0, 0, NULL, 0, FILE_FLAG_DELETE_ON_CLOSE, NULL);

		if (hDevice == INVALID_HANDLE_VALUE)
		{
			hDevice=0;

			err=GetLastError();
	
			sprintf(msg,"Error loading Driver \\\\.\\BPQHDLC.VXD - Error code %d\n",err);
			OutputDebugString(msg);
			
			MessageBox(NULL,msg,"BPQ32",MB_ICONSTOP);

			WritetoConsole("Initialisation Failed");

			return (FALSE);
		}

//		OutputDebugString("Calling GetVersion\n");

//		fResult = DeviceIoControl(
//			hDevice,          // device handle
//			10,//DIOC_GETVERSION,  // control code
//			NULL,0,// input parameters
//			bOutput, 4, &cb,  // output parameters
//			0);

		srand( (unsigned)time( NULL ) );  //Prime random no generator	
	}

	OutputDebugString("Calling Initialize\n");

	//
	//	Initialize Driver for this card and channel
	//

	fResult = DeviceIoControl(
		hDevice,						// device handle
		'I',							// control code
		PORTVEC, sizeof (struct PORTCONTROL),	// input parameters
		bOutput, 4, &cb,				// output parameters
        0);


	memcpy(&PORTVEC->DRIVERPORTTABLE,bOutput,4);

	Debugprintf("BPQ32 HDLC Driver Table ADDR %X", PORTVEC->DRIVERPORTTABLE);

	OutputDebugString("Initialize Returned\n");

	return (TRUE);
		
}

int PC120INIT(PHDLCDATA PORTVEC)
{
	return (HDLCINIT(PORTVEC));
}

int DRSIINIT(PHDLCDATA PORTVEC)
{
	return (HDLCINIT(PORTVEC));
}

int TOSHINIT(PHDLCDATA PORTVEC)
{
	return (HDLCINIT(PORTVEC));
}

int RLC100INIT(PHDLCDATA PORTVEC)
{
	return (HDLCINIT(PORTVEC));
}

int BAYCOMINIT(PHDLCDATA PORTVEC)
{
	return (HDLCINIT(PORTVEC));
}

int PA0INIT(PHDLCDATA PORTVEC)		// 14 PA0HZP OPTO-SCC
{
	return (HDLCINIT(PORTVEC));
}

// W2K/XP Routines

#define IOTXCA VECTOR[0]
#define IOTXEA VECTOR[1]
#define IORXCA VECTOR[2]
#define IORXEA VECTOR[3]

#define SIOR READ_PORT_UCHAR(PORTVEC->SIO)
#define SIOW(A) WRITE_PORT_UCHAR(PORTVEC->SIO,A)

#define SIOCR READ_PORT_UCHAR(PORTVEC->SIOC)
#define SIOCW(A) WRITE_PORT_UCHAR(PORTVEC->SIOC, A)

//#define SETRVEC	PORTVEC->IORXCA =
//#define SETTVEC	PORTVEC->IOTXCA =


int CLOCKFREQ = 76800;			// 4,915,200 HZ /(32*2)

int TOSHCLOCKFREQ =	57600;

UCHAR SDLCCMD[]	= {
	0,0,
	2,0,			// BASE VECTOR 
	4,0x20,			// SDLC MODE
	3,0xc8,			// 8BIT,  CRC ENABLE, RX DISABLED

	7,0x7e,			// STANDARD FLAGS
	1,0x13,			// INT ON ALL RX, TX INT EN, EXT INT EN
	5,0xe1,			// DTR, 8BIT, SDLC CRC,TX CRC EN

	10,0xa0,		// CRC PRESET TO 1

	9,0x09,			// ENABLE INTS

	11,0x66,		// NO XTAL, RXC = DPLL, TXC = RTXC, TRXC = BRG (NEEDS /32 BETWEEN TRXC AND RTXC)

	14,0x83,
	14,0x23,
	15,0xc0			// EXT INT ONLY ON TX UND AND ABORT RX
};

#define SDLCLEN	26

UCHAR TOSHR11 = 0x68;	// NO XTAL, RXC = DPLL, TXC = DPLL, NO CLK OUTPUT

UCHAR CIOPARAMS[] = {

	0x2B,0xFF,		// B DIRECTION - ALL IN
	0x23,0xFF,		// A DIRECTION - ALL IN

	0x1D,0x0E2,		// C/T 2 MODE - CONT, EXT IN, EXT O, SQUARE
	0x1C,0x0E2,		// C/T 1 MODE   		"" 

	0x19,0x10,		// C/T 2 LSB - 16 = /32 FOR SQUARE WAVE
	0x18,0,			//       MSB

	0x17,0x10,		// C/T 1 LSB
	0x16,0,			//       MSB

	0x0B,0x04,		// CT2   ""    - GATE
	0x0A,0x04,		// CT1   ""    - GATE

	0x06,0x0F,		// PORT C DIRECTION - INPUTS

	1,0x84,			// ENABLE PORTS A AND B

	0,0				// INTERRUPT CONTROL
};

#define CIOLEN	26


VOID WRITE_PORT_UCHAR(UINT Port, UINT Value)
{
  	ULONG buff[3];
	
	buff[0] = Port;
	buff[1] = Value;
	
	fResult = DeviceIoControl(
			hDevice,   // device handle
			IOCTL_BPQHDLC_IOWRITE,	   // control code
			buff, 8,	// input parameters
	        NULL,0,&cb, // output parameters
			0);
}

UCHAR READ_PORT_UCHAR(ULONG Port)
{
	ULONG buff[3];
	
	buff[0] = Port;
	
	fResult = DeviceIoControl(
			hDevice,   // device handle
			IOCTL_BPQHDLC_IOREAD,	   // control code
			buff, 4,	// input parameters
	        buff, 4,&cb, // output parameters
			0);

	Debugprintf("BPQ32 HDLC READ_PORT_UCHAR Returned  %X", LOBYTE(buff[0]));

	return LOBYTE(buff[0]);

}

int Init2K(HDLCDATA * PORTVEC)
{
	char msg[255];
	int err;

	if (hDevice == 0)		// Not already loaded
	{
		//
		//	Open HDLC Driver
		//
		
		hDevice  = CreateFile(
                    "\\\\.\\BPQHDLC",           // Open the Device "file"
                    GENERIC_WRITE,
                    FILE_SHARE_WRITE,
                    NULL,
                    OPEN_EXISTING,
                    0,
                    NULL);


		if (hDevice == INVALID_HANDLE_VALUE)
		{
			hDevice=0;

			err=GetLastError();
	
			sprintf(msg,"Error Opening Driver \\device\\BPQHDLC - Error code %d\n", err);
			OutputDebugString(msg);

			WritetoConsole(msg);

			return (FALSE);
		}
	}

	INITPORT(PORTVEC);

	return 0;
}

PHDLCDATA See_if_First_On_Card(PHDLCDATA PORTVEC)
{
//	SEE IF ANOTHER PORT IS ALREADY USING THE OTHER CHANNEL ON THIS CARD
	int i;

	PHDLCDATA PreviousPort = (PHDLCDATA)PORTTABLE;

	for (i = 0; i < NUMBEROFPORTS; i++)
	{
		if (PORTVEC == PreviousPort)
		{
			// NONE BEFORE OURS

			return NULL;
		}

		if (PORTVEC->PORTCONTROL.IOBASE == PreviousPort->PORTCONTROL.IOBASE)
		{
			//	ENSURE ENTRIES ARE FOR DIFFERENT CHANNELS

			if (PORTVEC->PORTCONTROL.CHANNELNUM == PreviousPort->PORTCONTROL.CHANNELNUM)
		
				//	CHANNEL DEFINITION ERROR

				return (PHDLCDATA) -1;
			else
				return PreviousPort;
		}
	
		PreviousPort = (PHDLCDATA)PreviousPort->PORTCONTROL.PORTPOINTER;
	}
	
	return NULL;			// FLAG NOT FOUND




}

VOID INITPART2(PHDLCDATA PORTVEC, USHORT SCCOffset, PHDLCDATA PreviousPort)
{
//	SCCOffset is address of SCC relative to Card Base Address

	int i;
	USHORT SCCBase=PORTVEC->PORTCONTROL.IOBASE + SCCOffset;
	int BRG;

//	SET UP ADDRESS LIST - THIS PATH FOR CARDS WITH 'NORMAL'
//	ADDRESSING - C/D=A0, A/B=A1, SO ORDER IS BCTRL BDATA ACTRL ADATA
//	OR DE, WHICH USES WORD ADDRESSES C/D=A1, A/B=A2 

	PORTVEC->BSIOC = SCCBase;			// B CHAN ADDR
	PORTVEC->ASIOC = SCCBase+2;		// A CHAN ADDR
 
//	SEE WHICH CHANNEL TO USE

	if (PORTVEC->PORTCONTROL.CHANNELNUM == 'A')
	{
		PORTVEC->A_PTR = PORTVEC;		// POINT TO OUR ENTRY
		PORTVEC->SIOC = SCCBase+2;
		PORTVEC->SIO = SCCBase+3;			// DATA 1 ABOVE CONTROL

		if (PreviousPort)			// Another Channel is first on Card
			PORTVEC->B_PTR = PreviousPort;	// CROSSLINK CHANNELS
	}
	else
	{
		// MUST BE B - CHECKED EARLIER

		PORTVEC->B_PTR = PORTVEC;		// POINT TO OUR ENTRY
		PORTVEC->SIOC = SCCBase;
		PORTVEC->SIO = SCCBase+1;	// DATA 1 ABOVE CONTROL

		if (PreviousPort)			// Another Channel is first on Card
			PORTVEC->A_PTR = PreviousPort;	// CROSSLINK CHANNELS

	}

//	INITIALISE COMMS CHIP

	if (PreviousPort == 0)					// OTHER CHAN ALREADY SET UP?
	{
		// DO A HARD RESET OF THE SCC

		WRITE_PORT_UCHAR(PORTVEC->ASIOC, 0);		// Make Sure WR0
		WRITE_PORT_UCHAR(PORTVEC->ASIOC, 0);

		WRITE_PORT_UCHAR(PORTVEC->ASIOC, 9);		// WR9

		WRITE_PORT_UCHAR(PORTVEC->ASIOC, 0xC0);	// Hard Reset

		Sleep(2);

	}

	for (i=0; i< SDLCLEN; i++)
	{
		WRITE_PORT_UCHAR(PORTVEC->SIOC, SDLCCMD[i]);
	}

	PORTVEC->WR10 = 0x20;			// NRZI

//	SET UP BRG FOR REQUIRED SPEED

	if (PORTVEC->PORTCONTROL.BAUDRATE == 0)
	{
		//	SET EXTERNAL CLOCK

		SIOCW(11);			// WR11
		SIOCW(0x20);		// RX = TRXC TX = RTXC

		return;
	}

	if (PORTVEC->PORTCONTROL.PORTTYPE == 12)	//	RLC 400 USES SAME CLOCK AS TOSH

		BRG = TOSHCLOCKFREQ;
	else
		BRG = CLOCKFREQ;

	BRG=(BRG/PORTVEC->PORTCONTROL.BAUDRATE)-2;

	SIOCW(12);			// Select WR12
	SIOCW(BRG & 0xff);	// SET LSB
	SIOCW(13);			// Select WR13
	SIOCW(BRG >> 8);	// SET MSB

	return;
}

VOID INITCIO(PHDLCDATA PORTVEC)
{
//	INITIALISE CIO - DRSI ONLY
	int i;
	ULONG CIOAddr = PORTVEC->PORTCONTROL.IOBASE + 7;  // TO CIO PORT

	READ_PORT_UCHAR(CIOAddr);
	WRITE_PORT_UCHAR(CIOAddr, 0);

	READ_PORT_UCHAR(CIOAddr);
	WRITE_PORT_UCHAR(CIOAddr, 0);

	WRITE_PORT_UCHAR(CIOAddr, 1);	// FORCE RESET
	WRITE_PORT_UCHAR(CIOAddr, 0);	//  CLEAR RESET

	for (i=0; i< CIOLEN; i++)
	{
		WRITE_PORT_UCHAR(CIOAddr, CIOPARAMS[i] );
	}

	return;

}
VOID STARTCIO(PHDLCDATA PORTVEC)
{
	USHORT CIOAddr = PORTVEC->PORTCONTROL.IOBASE + 7;  // TO CIO PORT
	UCHAR Reg;

//	B CHANNEL

//	SET COUNTER OUTPUT BIT ACTIVE

	WRITE_PORT_UCHAR(CIOAddr, 0x2B);	// PORT B DIRECTION
	Reg = READ_PORT_UCHAR(CIOAddr);

	if (PORTVEC->PORTCONTROL.CHANNELNUM == 'B')
	{

		Reg &= 0xEF;						// SET BIT 4 AS OUTPUT

		WRITE_PORT_UCHAR(CIOAddr, 0x2B);	// PORT B DIRECTION
		WRITE_PORT_UCHAR(CIOAddr, Reg);		// UPDATE PORT B DIRECTION

		//	ENABLE COUNTER

		WRITE_PORT_UCHAR(CIOAddr,1);		// MASTER CONFIG
		Reg = READ_PORT_UCHAR(CIOAddr);		// GET IT

		Reg |= 0x40;						// ENABLE CT1

		WRITE_PORT_UCHAR(CIOAddr,1);		// MASTER CONFIG
		WRITE_PORT_UCHAR(CIOAddr, Reg);		// Set it

		//	START COUNTER

		WRITE_PORT_UCHAR(CIOAddr,0x0A);		// CT1 CONTROL
		WRITE_PORT_UCHAR(CIOAddr,6);		// START CT1

		return;
	}

	Reg &= 0xFE;						// SET BIT 0 AS OUTPUT

	WRITE_PORT_UCHAR(CIOAddr, 0x2B);	// PORT B DIRECTION
	WRITE_PORT_UCHAR(CIOAddr, Reg);		// UPDATE PORT B DIRECTION

	//	ENABLE COUNTER

	WRITE_PORT_UCHAR(CIOAddr,1);		// MASTER CONFIG
	Reg = READ_PORT_UCHAR(CIOAddr);		// GET IT

	Reg |= 0x20;						// ENABLE CT2

	WRITE_PORT_UCHAR(CIOAddr,1);		// MASTER CONFIG
	WRITE_PORT_UCHAR(CIOAddr, Reg);		// Set it

//	START COUNTER

	WRITE_PORT_UCHAR(CIOAddr,0x0B);		// CT2 CONTROL
	WRITE_PORT_UCHAR(CIOAddr,6);		// START CT2

	return;
}

VOID INITMODEM(PHDLCDATA PORTVEC)
{
	//	SETUP MODEM - PC120 ONLY

	WRITE_PORT_UCHAR(PORTVEC->PORTCONTROL.IOBASE, 0x0a);
}


VOID CHECKCHAN(PHDLCDATA PORTVEC, USHORT CDOffset)
{
	// CDoffset contains offset to Second SCC

	//	IF CHANNEL =  C OR D SET TO SECOND SCC ADDRESS, AND CHANGE TO A OR B

	if (PORTVEC->PORTCONTROL.CHANNELNUM > 'B')
	{
		//	SECOND SCC

		PORTVEC->PORTCONTROL.CHANNELNUM -=2;
		PORTVEC->PORTCONTROL.IOBASE+=CDOffset;
	}
}

//	BAYCOM CARD

VOID BINITPART2(PHDLCDATA PORTVEC, USHORT SCCOffset, PHDLCDATA PreviousPort)
{
	// ORDER IS 0		1		2		3		4		5		6		7
	//			ADATA	BDATA	CDATA	DDATA	ACTRL	BCTRL	CCTRL	DCTRL 

	//	Before entering here IOBASE and Chan have been updated if Chan were C or D

	//	SET UP ADDRESS LIST

	int i;
	USHORT SCCBase=PORTVEC->PORTCONTROL.IOBASE + SCCOffset;
	int BRG;

//	SET UP ADDRESS LIST - THIS PATH FOR CARDS WITH 'NORMAL'
//	ADDRESSING - C/D=A0, A/B=A1, SO ORDER IS BCTRL BDATA ACTRL ADATA
//	OR DE, WHICH USES WORD ADDRESSES C/D=A1, A/B=A2 

	PORTVEC->ASIOC = SCCBase+4;		// A CHAN ADDR
	PORTVEC->BSIOC = SCCBase+5;		// B CHAN ADDR
 
//	SEE WHICH CHANNEL TO USE

	if (PORTVEC->PORTCONTROL.CHANNELNUM == 'A')
	{
		PORTVEC->A_PTR = PORTVEC;		// POINT TO OUR ENTRY
		PORTVEC->SIOC = SCCBase+4;
		PORTVEC->SIO = SCCBase;

		if (PreviousPort)			// Another Channel is first on Card
			PORTVEC->B_PTR = PreviousPort;	// CROSSLINK CHANNELS
	}
	else
	{
		// MUST BE B - CHECKED EARLIER

		PORTVEC->B_PTR = PORTVEC;		// POINT TO OUR ENTRY
		PORTVEC->SIOC = SCCBase+5;
		PORTVEC->SIO = SCCBase+1;

		if (PreviousPort)			// Another Channel is first on Card
			PORTVEC->A_PTR = PreviousPort;	// CROSSLINK CHANNELS

	}


//	INITIALISE COMMS CHIP

	if (PreviousPort == 0)					// OTHER CHAN ALREADY SET UP?
	{
		// DO A HARD RESET OF THE SCC

		WRITE_PORT_UCHAR(PORTVEC->ASIOC, 0);		// Make Sure WR0
		WRITE_PORT_UCHAR(PORTVEC->ASIOC, 0);

		WRITE_PORT_UCHAR(PORTVEC->ASIOC, 9);		// WR9

		WRITE_PORT_UCHAR(PORTVEC->ASIOC, 0xC0);	// Hard Reset

		Sleep(2);

	}

	for (i=0; i< SDLCLEN; i++)
	{
		WRITE_PORT_UCHAR(PORTVEC->SIOC, SDLCCMD[i]);
	}

//	SET UP BRG FOR REQUIRED SPEED

	if (PORTVEC->PORTCONTROL.BAUDRATE == 0)
	{
		//	SET EXTERNAL CLOCK

		SIOCW(11);			// WR11
		SIOCW(0x20);		// RX = TRXC TX = RTXC

		//	BAYCOM RUH PORT USES NRZ

		PORTVEC->WR10 = 0x0;			// NRZ

		return;
	}

	PORTVEC->WR10 = 0x20;			// NRZI

//	THERE IS NO /32 ON THE BAYCOM BOARD, SO FOR THE MOMENT WILL USE BRG
//	FOR TRANSMIT. THIS REQUIRES IT TO BE REPROGRAMMED BETWEEN TX AND RX,
//	AND SO PREVENTS LOOPBACK OR FULLDUP OPERATION

	BRG=(CLOCKFREQ/PORTVEC->PORTCONTROL.BAUDRATE)-2;

	SIOCW(12);			// Select WR12
	SIOCW(BRG & 0xff);	// SET LSB
	SIOCW(13);			// Select WR13
	SIOCW(BRG >> 8);	// SET MSB

	SIOCW(11);			// WR11
	SIOCW(0x70);		// RXC=DPLL, TXC=BRG

	PORTVEC->RXBRG = BRG;

//	CALC TX RATE

	PORTVEC->TXBRG = ((BRG+2)/32)-2;

	SIOCW(12);			// Select WR12
	SIOCW(BRG & 0xff);	// SET LSB
	SIOCW(13);			// Select WR13
	SIOCW(BRG >> 8);	// SET MSB

//	IF 7910/3105 PORTS, SET TXC=BRG, RXC=DPLL

//	IT SEEMS THE 3RD PORT IS MORE LIKELY TO BE USED WITH A SIMPLE
//	MODEM WITHOUT CLOCK GERERATION (EG BAYCOM MODEM), SO SET ALL 
//	PORTS THE SAME

	SIOCW(11);			// WR11
	SIOCW(0x70);		// RXC=DPLL, TXC=BRG
}

BOOLEAN INITREST(PHDLCDATA PORTVEC, PHDLCDATA PrevPort)
{
	BPQHDLC_ADDCHANNEL_INPUT AddParams;
	VOID * Return = NULL;
	int cb;

/*	mov     IRQHand[EBX],0	; in case already hooked by another port

	MOV	PORTINTERRUPT[EBX],OFFSET32 SIOINT

	CMP	EDI,0
	JNE SHORT INTDONE		; ALREADY SET UP

	CALL	HOOKINT			; INTERRUPT

INTDONE:

	CALL	RXAINIT
;
*/


//	Pass Params to the driver

	AddParams.IOBASE = PORTVEC->PORTCONTROL.IOBASE;
	AddParams.IOLEN = PORTVEC->IOLEN;
	AddParams.Interrupt = PORTVEC->PORTCONTROL.INTLEVEL;
	AddParams.ASIOC = PORTVEC->ASIOC;
	AddParams.BSIOC = PORTVEC->BSIOC;
	AddParams.SIOC = PORTVEC->SIOC;
	AddParams.SIO = PORTVEC->SIO;
	AddParams.TXBRG = PORTVEC->TXBRG;
	AddParams.RXBRG = PORTVEC->RXBRG;
	AddParams.WR10 = PORTVEC->WR10;
	AddParams.Channel = PORTVEC->PORTCONTROL.CHANNELNUM;
	AddParams.SOFTDCDFLAG = PORTVEC->PORTCONTROL.SOFTDCDFLAG;
	AddParams.TXDELAY = PORTVEC->PORTCONTROL.PORTTXDELAY;
	AddParams.PERSISTANCE = PORTVEC->PORTCONTROL.PORTPERSISTANCE;
	if (PrevPort)
		AddParams.OtherChannel = PrevPort->DRIVERPORTTABLE;
	else
		AddParams.OtherChannel = 0;

	fResult = DeviceIoControl(
			hDevice,									// device handle
			IOCTL_BPQHDLC_ADDCHANNEL,					// control code
			&AddParams,sizeof(BPQHDLC_ADDCHANNEL_INPUT),	// input parameters
	        &Return,4,&cb,								// output parameters
			0);

	PORTVEC->DRIVERPORTTABLE = Return;

	if (Return == NULL)
	{
		// Init Failed, probably because resources were not alllocated to driver

		WritetoConsole("Kernel Driver Init Failed - Check Resource Allocation");
		return (FALSE);
	}

	PORTVEC->RR0 = SIOCR;	//GET INITIAL RR0


	return TRUE;
}


int INITPORT(PHDLCDATA PORTVEC)
{
 	PHDLCDATA PreviousPort;

	//	SEE IF C OR D. If so, Adjust IOBASE and change to A/B

	switch (PORTVEC->PORTCONTROL.PORTTYPE)
	{
		case 10:				// RLC100
		case 12:				// RLC400
		case 20:				// PA0HZP OPTO-SCC
			
			CHECKCHAN(PORTVEC, 4);			// Channels are 4 apart
			break;

		case 18:				// Baycom
			
			CHECKCHAN(PORTVEC, 2);			//Channels are 2 apart
			break;
	}

	//	By now Channel Should be only A or B

	if ((PORTVEC->PORTCONTROL.CHANNELNUM != 'A') && (PORTVEC->PORTCONTROL.CHANNELNUM != 'B'))
	{
			WritetoConsole("Invalid Channel\n");
			return FALSE;
	}

	PreviousPort = See_if_First_On_Card(PORTVEC);

	if (PreviousPort == (PHDLCDATA)-1)
	{
		// Two ports on same card have same Channel

			WritetoConsole("Duplicate Channels\n");
			return FALSE;
	}

	switch (PORTVEC->PORTCONTROL.PORTTYPE)
	{
	case 2:			// PC120
		
		PORTVEC->IOLEN = 8;					// I think! - Modem is at offfset 0, SCC at 4
		INITPART2(PORTVEC, 4, PreviousPort);	// SCC ADDRESS 4 Above Base Address
		if (PreviousPort == NULL) INITMODEM(PORTVEC);
		INITREST(PORTVEC, PreviousPort);

		return 0;

	case 4:			// DRSIINIT
		
		PORTVEC->IOLEN = 8;					// SCC at 0, CIO at 7
		if (PreviousPort == NULL) INITCIO(PORTVEC);	// SET UP CIO FOR /32 UNLESS Already Done
		INITPART2(PORTVEC, 0, PreviousPort);
		if (PORTVEC->PORTCONTROL.BAUDRATE) STARTCIO(PORTVEC);
		INITREST(PORTVEC, PreviousPort);

		return TRUE;
	
	case 6:

		WritetoConsole("TYPE=TOSH Not Supported\n");
		return FALSE;

	case 10:				// RLC100
	case 12:				// RLC400

		PORTVEC->IOLEN = 4;						// 2 * SCC, but each SCC can be on it's own
		INITPART2(PORTVEC, 0, PreviousPort);
		INITREST(PORTVEC, PreviousPort);

		return TRUE;

	case 18:				// Baycom

		PORTVEC->IOLEN = 8;						// 2 * SCC, but Need at least 6 Addresses
		BINITPART2(PORTVEC, 0, PreviousPort);
		INITREST(PORTVEC, PreviousPort);

		return 0;
	
	case 20:				// PA0HZP OPTO-SCC

		PORTVEC->IOLEN = 4;						// 2 * SCC, but each SCC can be on it's own
		INITPART2(PORTVEC, 0, PreviousPort);
		INITREST(PORTVEC, PreviousPort);
		return TRUE;
	}

	return FALSE;
}
#endif

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>


#ifdef WIN32
#include <io.h>
#define read _read
#define write _write
#define close _close
#define open _open
#else
#endif


// Linux HDLC Kernel Module Support

#define TIOCMGET   0x5415

PHDLCDATA FIRSTHDLCPORT = 0;

int KHDLCINIT(PHDLCDATA PORTVEC)
{
	int ret, status = 65;
	char Msg[64] = "HDLC Params";

	// Only open device for first port - all ports share a kernel driver

	if (FIRSTHDLCPORT == 0)
	{
		// for now just open /dev/bpqhdlc. Needs to be a param somewhere

		PORTVEC->fd = open("/dev/bpqhdlc", O_RDWR);             // Open the device with read/write access

		FIRSTHDLCPORT = PORTVEC;

		if (PORTVEC->fd < 0)
		{
			WritetoConsole("HDLC - Failed to open /dev/bpqhdlc\n");
			return errno;
		}

	}

	ret =  ioctl(FIRSTHDLCPORT->fd, 1, Msg);

	Consoleprintf("HDLC Channel %c", PORTVEC->PORTCONTROL.CHANNELNUM);

	return 0;
}

void KHDLCTX(PHDLCDATA PORTVEC, PMESSAGE Buffer)
{
	int fd = FIRSTHDLCPORT->fd;
	struct _LINKTABLE * LINK;
	unsigned char Message[512];
	
	if (fd != -1)
	{
		int len = GetLengthfromBuffer((PDATAMESSAGE)Buffer) - (3 + sizeof(void *));
		int ret;

		Message[0] = (PORTVEC->PORTCONTROL.CHANNELNUM - ('A' << 4));		// KISS Control
		memcpy(&Message[1], Buffer->DEST, len);

		ret = write(fd, Message, len + 1);

		if (ret < 0)
		{
			Debugprintf("Failed to write the message to the device.");
			return;
		}
	}

	LINK = Buffer->Linkptr;

	if (LINK)
	{
		if (LINK->L2TIMER)
			LINK->L2TIMER = LINK->L2TIME;

		Buffer->Linkptr = 0;	// CLEAR FLAG FROM BUFFER
	}


	// Pass buffer to trace routines

	C_Q_ADD(&TRACE_Q, Buffer);
}

int KHDLCRX(PHDLCDATA PORTVEC)
{
	int len;
	PMESSAGE Buffer;
	unsigned char packet[512] = "";
	int fd = FIRSTHDLCPORT->fd;

	if (fd == -1)
		return 0;

	packet[0] = (PORTVEC->PORTCONTROL.CHANNELNUM - ('A' << 4));		// KISS Control

	len = read(fd, packet, 512);        // Read the response from the LKM

	while (len)
	{
		if (len < 0)
		{
			Debugprintf("bpqhdlc read failed");
			return errno;
		}
		Buffer = GetBuff();

		if (Buffer)
		{
			memcpy(&Buffer->DEST, packet + 1, len - 1);	// Has KISS control on front
			len += (3 + sizeof(void *));

			PutLengthinBuffer((PDATAMESSAGE)Buffer, len - 1);		// Needed for arm5 portability

			C_Q_ADD(&PORTVEC->PORTCONTROL.PORTRX_Q, Buffer);
		}
		len = read(fd, packet, 512);        // Read the response from the LKM
	}
	return 0;
}

void KHDLCTIMER(PHDLCDATA PORTVEC)
{}

void KHDLCCLOSE(PHDLCDATA PORTVEC)
{
	int fd = PORTVEC->fd;

	if (fd != -1)
		close(fd);
}

BOOL KHDLCTXCHECK(PHDLCDATA PORTVEC)
{
	return 0;
}


