/******
 *
 *	F O S _ I S D N . C          by Jan Ceuleers, The Experimenter Board, 1992.
 *
 *
 *	FOSSIL driver for the Duo-ISDN board from Alcatel Bell.
 *
 ******/

#define FOS_ISDN
#include <dos.h>
#include <malloc.h>
#include <memory.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "fos_isdn.h"

#pragma check_stack(off)
#pragma check_pointer(off)

port_t *ports = NULL;
unsigned number_of_ports;
void (interrupt cdecl far *old_FOSSIL_ISR)() = NULL;
void (interrupt cdecl far *old_clock_tick_ISR)() = NULL;

void far cdecl interrupt FOSSIL(unsigned _es, unsigned _ds, unsigned _di,
    unsigned _si, unsigned _bp, unsigned _sp, unsigned _bx, unsigned _dx,
    unsigned _cx, unsigned _ax, unsigned _ip, unsigned _cs, unsigned flags);

const struct
{
	unsigned char jmpfar[2];
	void (interrupt cdecl far *FOSSILadr)();
	unsigned dw1954;
	unsigned char db1b;
	unsigned char signature[16];
} fossilEntryPoint =
{
	{ 0x90, 0xEA }		// NOP, JMP FAR
,	FOSSIL				// ... FOSSIL
,	0x1954				// DW 1954h			; FOSSIL signature byte @ offset 6!!
,	0x1B				// DB 1Bh			; Highest function supported
,	"Duo-ISDN FOSSIL"	// DB ...			; Our own signature
};

incomingCallFilter_t incomingCallFilter[NUMBER_OF_FILTERS] =
{
	{ -1,                 AM_PROT_V110_ASYNC, AM_HLC_CCITT_STANDARD, 126, 127, { 0, 0 } }
,	{ -1,                 AM_PROT_V120_SYNC,  AM_HLC_CCITT_STANDARD, 125, 127, { 0, 0 } }
,	{ -1,                 AM_PROT_V120_ASYNC, AM_HLC_CCITT_STANDARD, 124, 127, { 0, 0 } }
,	{ AM_PROT_V110_ASYNC, AM_PROT_V110_ASYNC, AM_HLC_CCITT_STANDARD, -1,  127, { 0, 0 } }
,	{ AM_PROT_V110_SYNC,  AM_PROT_V110_SYNC,  AM_HLC_CCITT_STANDARD, -1,  127, { 0, 0 } }
,	{ AM_PROT_V120_ASYNC, AM_PROT_V120_ASYNC, AM_HLC_CCITT_STANDARD, -1,  127, { 0, 0 } }
,	{ AM_PROT_V120_SYNC,  AM_PROT_V120_SYNC,  AM_HLC_CCITT_STANDARD, -1,  127, { 0, 0 } }
};

struct
{
	unsigned minPortNo;
	unsigned numberOfPorts;
} cfg =
{
	0, 0
};


void raiseOrLowerDTR(port_t *pport, unsigned state);
void purgeOutputBuffer(port_t *pport);
void purgeInputBuffer(port_t *pport);


/* ננננננננננננננננננננננננננ Buffer manipulation ננננננננננננננננננננננננננ */

#define rxBytesFree (P(rxBufSize)-P(rxBufChars))
#define txBytesFree (P(txBufSize)-P(txBufChars))
#define rxBytesUsed (P(rxBufChars))
#define txBytesUsed (P(txBufChars))

/* This function appends a character to the transmit buffer. It returns 0 *
 * if the operation succeeded, 1 if the buffer is full.                   */
int charIntoTxBuf(port_t *pport, unsigned ch)
{
	int retval;

	LED_ON(LT_CHARINTOTXBUF);
	_disable();

	if (0 == txBytesFree)
		retval = 1;
	else
	{
		*(P(txBufHead)++) = ch;
		P(txBufChars)++;

		if (P(txBufHead) >= (P(txBuf) + P(txBufSize)))
		{
			P(txBufHead) = P(txBuf);
		}

		if (!txBytesFree)
		{
			P(status) &= ~0x2000;		// Transmit buffer is now full
			LED_OFF(LT_STAT_THRE);
		}

		P(status) &= ~0x4000;			// Transmit buffer is not empty
		LED_OFF(LT_STAT_TSRE);

		retval = 0;
	}

	_enable();
	LED_OFF(LT_CHARINTOTXBUF);

	return retval;
}

/* This function appends a character to the receive buffer. It returns 0 *
 * if the operation succeeded, 1 if the buffer is full.                  */
int charIntoRxBuf(port_t *pport, unsigned ch)
{
	int retval;

	LED_ON(LT_CHARINTORXBUF);
	_disable();

	if (0 == rxBytesFree)
		retval = 1;
	else
	{
		*(P(rxBufHead)++) = ch;
		P(rxBufChars)++;

		if (P(rxBufHead) >= (P(rxBuf) + P(rxBufSize)))
		{
			P(rxBufHead) = P(rxBuf);
		}

		P(status) |= 0x0100; LED_ON(LT_STAT_RDA);

		retval = 0;
	}

	_enable();
	LED_OFF(LT_CHARINTORXBUF);
	return retval;
}

/* This function reads a character from the receive buffer. If none is *
 * available, -1 is returned.                                          */
unsigned charFromRxBuf(port_t *pport)
{
	unsigned retval;

	LED_ON(LT_CHARFROMRXBUF);
	_disable();

	if (!rxBytesUsed) retval = 0xffff;
	else
	{
		retval = (unsigned)*(P(rxBufTail));

		P(rxBufTail)++;
		P(rxBufChars)--;

		if (P(rxBufTail) >= P(rxBuf) + P(rxBufSize))
			P(rxBufTail) = P(rxBuf);

		if (!rxBytesUsed)
		{
			P(status) &= ~0x0100;
			LED_OFF(LT_STAT_RDA);
		}
	}

	if ((P(flowControl.restart))
	     && (rxBytesFree >= A_MD_DATA_BUF_LENGTH))
	{
		P(flowControl.restart) = 0;
		initializeIncomingData(pport);
	}

	_enable();
	LED_OFF(LT_CHARFROMRXBUF);

	return retval;
}

/* This function moves as many characters into the receive buffer as    *
 * possible. It returns the number of characters that were successfully *
 * moved.                                                               */
int blockIntoRxBuf(port_t *pport, charFarPtr_t buffer, int size)
{
	int retval;
	unsigned available;

	LED_ON(LT_BLOCKINTORXBUF);
	_disable();

	// First find out how much space there is
	available = rxBytesFree;

	// Check if the user buffer is the limit
	if (available < size) size = available;

	retval = size;

	// We can either move in one go, or we must move in two
	if (P(rxBufHead) + size >= P(rxBuf) + P(rxBufSize))
	{
		_fmemcpy(P(rxBufHead), buffer, P(rxBuf) + P(rxBufSize) - P(rxBufHead));
		size -= P(rxBuf) + P(rxBufSize) - P(rxBufHead);
		buffer += P(rxBuf) + P(rxBufSize) - P(rxBufHead);
		P(rxBufHead) = P(rxBuf);
	}

	_fmemcpy(P(rxBufHead), buffer, size);
	P(rxBufHead) += size;
	P(rxBufChars) += retval;

	if (rxBytesUsed)
	{
		P(status) |= 0x0100;			// Data is available in rx buffer
		LED_ON(LT_STAT_RDA);
	}

	_enable();
	LED_OFF(LT_BLOCKINTORXBUF);
	return retval;
}

/* This function removes the specified number of characters from the tail *
 * of the transmit buffer.                                                */
void removeFromTxBuf(port_t *pport, unsigned byteCount)
{
	unsigned size = byteCount;

	LED_ON(LT_REMOVEFROMTXBUF);
	_disable();

	if (size)
	{
		P(status) |= 0x2000;			// Transmit buffer is not full
		LED_ON(LT_STAT_THRE);
	}

	if (P(txBufTail) + size >= P(txBuf) + P(txBufSize))
	{
		size -= P(txBuf) + P(txBufSize) - P(txBufTail);
		P(txBufTail) = P(txBuf);
	}

	P(txBufTail) += size;
	P(txBufChars) -= byteCount;

	if (!txBytesUsed)
	{
		P(status) |= 0x4000;			// Transmit buffer is now empty
		LED_ON(LT_STAT_TSRE);
	}

	_enable();
	LED_OFF(LT_REMOVEFROMTXBUF);
}

/* This function copies as many characters from the transmit buffer as    *
 * possible. It returns the number of characters that were successfully   *
 * copied. The characters are left in the buffer, they must be removed by *
 * a subsequent call to removeFromTxBuf.                                  */
int blockFromTxBufNonDestructive(port_t *pport, charFarPtr_t buffer, int size)
{
	int retval;
	unsigned available;
	charFarPtr_t source;

	LED_ON(LT_BLOCKFROMTXBUFND);
	_disable();

	// First find out how many characters there are
	available = txBytesUsed;

	// Check if the user buffer is the limit
	if (available < size) size = available;

	retval = size;
	source = P(txBufTail);

	// We can either move in one go, or we must move in two
	if (source + size >= P(txBuf) + P(txBufSize))
	{
		_fmemcpy(buffer, source, (charFarPtr_t)(P(txBuf)) + P(txBufSize) - source);
		size -= (charFarPtr_t)P(txBuf) + P(txBufSize) - source;
		buffer += (charFarPtr_t)P(txBuf) + P(txBufSize) - source;
		source = P(txBuf);
	}

	_fmemcpy(buffer, source, size);

	_enable();
	LED_OFF(LT_BLOCKFROMTXBUFND);
	return retval;
}

/* This function moves as many characters from the receive buffer as    *
 * possible. It returns the number of characters that were successfully *
 * moved.                                                               */
int blockFromRxBuf(port_t *pport, charFarPtr_t buffer, int size)
{
	int retval;
	unsigned available;

	LED_ON(LT_BLOCKFROMRXBUF);
	_disable();

	// First find out how many characters there are
	available = rxBytesUsed;

	// Check whether the user buffer is the limit
	if (available < size) size = available;

	retval = size;

	// We can either move in one go, or we must move in two
	if (P(rxBufTail) + size >= P(rxBuf) + P(rxBufSize))
	{
		_fmemcpy(buffer, P(rxBufTail), P(rxBuf) + P(rxBufSize) - P(rxBufTail));
		size -= P(rxBuf) + P(rxBufSize) - P(rxBufTail);
		buffer += P(rxBuf) + P(rxBufSize) - P(rxBufTail);
		P(rxBufTail) = P(rxBuf);
	}

	_fmemcpy(buffer, P(rxBufTail), size);
	P(rxBufTail) += size;
	P(rxBufChars) -= retval;

	if ((P(flowControl.restart))
	     && (rxBytesFree >= A_MD_DATA_BUF_LENGTH))
	{
		P(flowControl.restart) = 0;
		initializeIncomingData(pport);
	}

	if (!rxBytesUsed)
	{
		P(status) &= ~0x0100;		// Rx buffer is now empty
		LED_OFF(LT_STAT_RDA);
	}

	_enable();
	LED_OFF(LT_BLOCKFROMRXBUF);

	return retval;
}

/* This function copies as many characters into the transmit buffer as  *
 * possible. It returns the number of characters that were successfully *
 * copied.                                                              */
int blockIntoTxBuf(port_t *pport, charFarPtr_t buffer, int size)
{
	int retval;
	unsigned available;

	LED_ON(LT_BLOCKINTOTXBUF);
	_disable();

	// First find out how much space there is
	available = txBytesFree;

	// Check whether the user buffer is the limit
	if (available < size) size = available;

	retval = size;

	if (size)
	{
		P(status) &= ~0x4000;			// Transmit buffer is not empty
		LED_OFF(LT_STAT_TSRE);
	}

	// We can either move in one go, or we must move in two
	if (P(txBufHead) + size >= P(txBuf) + P(txBufSize))
	{
		_fmemcpy(P(txBufHead), buffer, P(txBuf) + P(txBufSize) - P(txBufHead));
		size -= P(txBuf) + P(txBufSize) - P(txBufHead);
		buffer += P(txBuf) + P(txBufSize) - P(txBufHead);
		P(txBufHead) = P(txBuf);
	}

	_fmemcpy(P(txBufHead), buffer, size);
	P(txBufHead) += size;
	P(txBufChars) += retval;

	if (!txBytesFree)
	{
		P(status) &= ~0x2000;			// Transmit buffer is now full
		LED_OFF(LT_STAT_THRE);
	}

	_enable();
	LED_OFF(LT_BLOCKINTOTXBUF);
	return retval;
}


/* נננננננננננננננננננננננננננננ Flow control ננננננננננננננננננננננננננננננ */

/* This function will cause initializeIncomingData to be called when the
 * receive buffer has been sufficiently emptied to contain a complete data
 * packet (of size A_MD_DATA_BUF_LENGTH). */
void restartRxWhenReadyTo(port_t *pport)
{
	LED_ON(LT_RESTARTRXWHENRDY);

	if (rxBytesFree >= A_MD_DATA_BUF_LENGTH)
	{
		initializeIncomingData(pport);
	}
	else
	{
		P(flowControl.restart) = 1;
	}

	LED_OFF(LT_RESTARTRXWHENRDY);
}


/* נננננננננננננננננננננננננננ FOSSIL functions ננננננננננננננננננננננננננננ */

void setBaudrate (port_t *pport, unsigned baudRateCode)
{
	// This function is entirely ignored. Use modem registers 16 and 17.
	LED_INVERT(LT_SETBAUDRATE);
}

int transmitChar(port_t *pport, unsigned char txchar, int wait)
{
	int retval;

	LED_ON(LT_TRANSMITCHAR);

	if (P(modem.hayesState) < 0x80)
	{
		// Only send character in data mode, not in command mode
		while ((0 == (retval = !charIntoTxBuf(pport, txchar))) && (wait))
		{
			releaseTimeSlice();
		}

		if (retval != 0)
			processHayesChar(pport, txchar);
	}
	else	// Command mode
	{
		retval = 1;

		if ((P(modem.E)) && ((P(modem.hayesState) != 0x83) || (txchar == P(modem.S[3]))))
		{
			// Echo the character back
			if (0 != charIntoRxBuf(pport, txchar))
			{
				P(status) |= 0x0200;	// Rx buffer overrun
				LED_ON(LT_STAT_OVRN);
			}
		}

		processHayesChar(pport, txchar);

		if (txchar == P(modem.S[3]))
		{
			if (P(modem.hayesState) == 0x82)
			{
				executeHayes(pport);
			}
			else
			{
				P(modem.hayesState) = 0x80;
			}
		}
	}

	LED_OFF(LT_TRANSMITCHAR);
	return retval;
}


void receiveCharWithWait(port_t *pport, unsigned far *rcvChar)
{
	LED_ON(LT_RECVCHARWITHWAIT);

	_enable();

	while (!rxBytesUsed)
	{
		releaseTimeSlice();
	}

	*rcvChar = charFromRxBuf(pport);

/*	if ((P(flowControl.mask) & 0x08)	// receive Xon/Xoff flow control
		&& (rxBytesFree >= (P(rxBufSize) / 3)))
	{
		// Send Xon *now* (i.e. don't queue it)
		skipQueue(XON, pport);
	}
*/
	LED_OFF(LT_RECVCHARWITHWAIT);
}


void initializeDriver(port_t *pport, unsigned far *ptrAX, unsigned far *ptrBX)
{
	LED_INVERT(LT_INITIALIZEDRIVER);

	P(flowControl.mask) = P(flowControl.restart) = 0;
	P(flowControl.rxXonXoffState) = P(flowControl.txXonXoffState) = 0;
	purgeOutputBuffer(pport); purgeInputBuffer(pport);
	P(ticksSinceLastTx) = P(ticksSinceLastPoll) = 0;
	P(call.refuseAllCalls) = P(call.checkAutoAnswer) =
	P(call.hangupRequested) = 0;
	P(charToSendNow) = -1;
	P(status) |= 0x0008;		// bit AL.3 is always set (see FSC-0015)
	defineIncomingCall(1);
	P(initialized) = 1; LED_ON(LT_INITIALIZED);
	raiseOrLowerDTR(pport, 1);

	// I choose not to support the Ctrl-C flag byte until I get some response
	// in the NET_DEV conference about it.
	
	*ptrAX = 0x1954;
	*ptrBX = 0x051B;
}

void deInitializeDriver(port_t *pport)
{
	// DTR not affected, so I shouldn't hang up.

	LED_INVERT(LT_DEINITIALIZEDRVR);

	P(initialized) = 0; LED_OFF(LT_INITIALIZED);
	defineIncomingCall(0);
}

void raiseOrLowerDTR(port_t *pport, unsigned state)
{
	LED_INVERT(LT_RAISEORLOWERDTR);

	if (state)
	{
		P(call.refuseAllCalls) = 0;
		LED_ON(LT_DTR);
	}
	else
	{
		// Lower DTR --> hang up
		hangup(pport);
		P(call.refuseAllCalls) = 1;
		P(modem.hayesState) = 0x80;
		resetModemEmul(pport);

		newlineRx(pport);
		blockIntoRxBuf(pport, (charFarPtr_t)"FORWARD RELEASE", 15);
		newlineRx(pport);
		LED_OFF(LT_DTR);
	}
}

void flushOutputBuffer(port_t *pport)
{
	LED_ON(LT_FLUSHOUTPUTBUF);

	_enable();

	while ((txBytesUsed) && (P(call.state) == 2))
	{
		releaseTimeSlice();
	}

	// If carrier was lost before or during this function, we still want the
	// buffer cleared.
	P(txBufHead) = P(txBufTail) = P(txBuf);
	P(txBufChars) = 0;
	P(status) |= (0x2000 | 0x4000);		// Transmit buffer is empty; room is available
	LED_ON(LT_STAT_THRE);
	LED_ON(LT_STAT_TSRE);

	LED_OFF(LT_FLUSHOUTPUTBUF);
}

void purgeOutputBuffer(port_t *pport)
{
	LED_INVERT(LT_PURGEOUTPUTBUF);

	_disable();

	P(txBufHead) = P(txBufTail) = P(txBuf);
	P(txBufChars) = 0;
	P(status) |= (0x2000 | 0x4000);		// Transmit buffer is empty; room is available
	LED_ON(LT_STAT_THRE);
	LED_ON(LT_STAT_TSRE);

	_enable();
}

void purgeInputBuffer(port_t *pport)
{
	LED_INVERT(LT_PURGEINPUTBUF);

	_disable();

	P(rxBufHead) = P(rxBufTail) = P(rxBuf);
	P(rxBufChars) = 0;

	P(status) &= ~0x0100; LED_OFF(LT_STAT_RDA);

	_enable();
}

void nonDestructiveReadAhead(port_t *pport, unsigned far *pCharRead)
{
	LED_ON(LT_NDREADAHEAD);

	if (rxBytesUsed)
		*pCharRead = *P(rxBufTail);		// THIS WAS INCORRECT IN 0.04ג
	else
		*pCharRead = 0xffff;

	LED_OFF(LT_NDREADAHEAD);
}

void enableOrDisableFlowControl(port_t *pport, unsigned mask)
{
/*	 AH = 0Fh	 Enable or disable flow control

            Parameters:
                Entry:  AL = Bit mask describing requested flow control
                        DX = Port number
                Exit:   None

    TRANSMIT flow control allows the "other end" to restrain the transmitter
	
    when you are  over-running it.  RECEIVE flow control tells the FOSSIL to
    attempt to do just that if it is being overwhelmed.

    Two kinds of basic flow control are supported:

                Bit 0 = 1       Xon/Xoff on transmit
                Bit 1 = 1       CTS/RTS (CTS on transmit, RTS on receive)
                Bit 2           Reserved
|               Bit 3 = 1       Xon/Xoff on Receive

    Flow control is enabled, or disabled, by setting the appropriate bits in
    AL  for the types of flow control we want to ENABLE (value = 1),  and/or
    DISABLE  (value = 0),  and calling this function.  Bit 2 is reserved for
    DSR/DTR,  but is not currently supported in any implementation.

    Enabling  transmit  Xon/Xoff will cause the FOSSIL  to stop transmitting
    upon receiving an Xoff.  The FOSSIL will resume transmitting when an Xon
    is received.

    Enabling CTS/RTS will cause the FOSSIL to cease transmitting when CTS is
    lowered.  Transmission will resume  when CTS is raised.  The FOSSIL will
    drop RTS when the receive buffer reaches a predetermined percentage full
    The FOSSIL will  raise RTS  when the  receive buffer  empties below  the
    predetermined  percentage full.  The  point(s)  at which  this occurs is
    left to the individual FOSSIL implementor.

|   Enabling receive  Xon/Xoff will cause the FOSSIL to send a Xoff when the
|   receive buffer reaches a pre-determined percentage full.  An Xon will be
|   sent when the receive buffer empties below the pre-determined percentage
|   full. The point(s) at which this occurs is left to the individual FOSSIL
|   implementor.

    Applications  using this  function  should set all bits  ON  in the high
    nibble of AL as well.  There is a compatible  (but not identical) FOSSIL
    driver implementation that uses the  high nibble as a control mask.   If
    your application sets the high nibble to all ones,  it will always work,
    regardless of the method used by any given driver.
*/
}

void fossilFunction10h(port_t *pport, unsigned mask, unsigned far *pRetval)
{
/*	 AH = 10h	 Extended Control-C / Control-K checking and transmit on/off

            Parameters:
                Entry:  AL = Bit mask (see below)
                        DX = Port number
                Exit:   AX = 0001h - Control-C/K has been received
                           = 0000h - Control-C/K has not been received

    This is used for BBS  operation,  primarily.  A bit mask is passed in AL
    with the following flags:

                Bit 0   Enable/disable Control-C / Control-K checking
                Bit 1   Disable/enable the transmitter

    The Enable (bit 0 = 1) and Disable (Bit 0 = 0) Control-C/Control-K check
    function is meant primarily for BBS use. When the checking is enabled, a
    Control-C or Control-K received  from the communications port will set a
    flag internal to the FOSSIL driver,  but will not be stored in the input
    buffer. The next use of this function will return the value of this flag
    in register AX then clear the flag for the next occurrence. The returned
    value is used by the BBS  software to determine whether output should be
    halted or not.

    The Disable (Bit 1 = 1) and Enable (Bit 1 = 0) Transmitter function lets
    the application restrain the asynchronous driver from output in much the
    same way as XON/XOFF would.
*/

	*pRetval = 0;
}

void enableOrDisableWatchdog(port_t *pport, unsigned command)
{
/*	 AH = 14h	 Enable or disable watchdog processing

            Parameters:
                Entry:  AL = 01h - Enable watchdog
                           = 00h - Disable watchdog
                        DX = Port number
                Exit:   None

    When watchdog is enabled,   the state of the carrier detect (CD) line on
    the comm port specified in DX should be constantly monitored. Should the
    state of that line become FALSE (carrier lost), the system should be re-
    booted, to enable the BBS (or other application) to start up again. This
    monitor is not affected by Init/Uninit etc.
*/
}

void readBlock(port_t *pport, unsigned bufSize, unsigned bufSeg,
               unsigned bufOfs, unsigned far *pCharsTransferred)
{
	charFarPtr_t buf;

	LED_ON(LT_READBLOCK);

	buf = (charFarPtr_t)((((long)bufSeg) << 16L) + (((long)bufOfs)));

	*pCharsTransferred = blockFromRxBuf(pport, buf, bufSize);

	if ((P(flowControl.mask) & 0x08)	// receive Xon/Xoff flow control
		&& (rxBytesFree >= (P(rxBufSize) / 3)))
	{
		// Send Xon *now* (i.e. don't queue it)
		skipQueue(XON, pport);
	}

	LED_OFF(LT_READBLOCK);
}

// This function is not a pretty sight. The problem is that we're trying to
// shift as many characters as possible as quickly as possible. Meaning that
// we don't want to look at each character individually (which would be the
// case if we put them all through the Hayes interpreter).
// We therefore make some assumptions as to whether the Hayes interpreter
// might be interested in the present character. Once we're sure the rest of
// this block is of no interest to it, we simply stuff it in the transmit
// buffer without paying further attention to its contents.
void writeBlock(port_t *pport, unsigned bufSize, unsigned bufSeg,
                unsigned bufOfs, unsigned far *pCharsTransferred)
{
	charFarPtr_t buf;

	LED_ON(LT_WRITEBLOCK);

	buf = (charFarPtr_t)((((long)bufSeg) << 16L) + (((long)bufOfs)));

	*pCharsTransferred = 0;

	(*pCharsTransferred) += blockIntoTxBuf(pport, buf, bufSize);

	LED_OFF(LT_WRITEBLOCK);
}

void startOrStopBreak(port_t *pport, unsigned command)
{
/*
|	AH = 1Ah	Break begin or end

|           Parameters:
|               Entry:  AL = 01h - Start sending 'break'
                           = 00h - Stop sending 'break'
|                       DX = port number
|               Exit:   None

|   Send a break signal to the modem. If AL=01h the driver will commence the
|   transmission of a break.  If AL=00h the driver will end the break.  This
|   is useful for communications with devices that can only go into 'command
|   mode' when a BREAK is received. Note: the application is responsible for
|   the timing of the BREAK.  Also,  if the FOSSIL has been restrained by an
|   Xoff received from the modem, the flag will be cleared.   An Init or Un-
|   Init will stop an in-progress BREAK.
*/
}

void informationAboutDriver(port_t *pport, unsigned bufSize, unsigned bufSeg,
                            unsigned bufOfs, unsigned far *pBytesTransferred)
{
	#pragma pack(1)
	struct
	{
		unsigned strsiz;
		unsigned char majver;
		unsigned char minver;
		charFarPtr_t id_string;
		unsigned ibsize, ibfree, obsize, obfree;
		unsigned char swidth, sheight;
		unsigned char baud;
	} fossilInfo;

	LED_ON(LT_INFOABOUTDRIVER);

	fossilInfo.strsiz = sizeof(fossilInfo);
	fossilInfo.majver = 5;
	fossilInfo.minver = 0;
	fossilInfo.id_string = "FOS_ISDN " VERSION " by Jan Ceuleers";
	fossilInfo.ibsize = P(rxBufSize);
	fossilInfo.ibfree = rxBytesFree;
	fossilInfo.obsize = P(txBufSize);
	fossilInfo.obfree = txBytesFree;
	fossilInfo.swidth = *(unsigned far *)(0x0040004AL);
	fossilInfo.sheight = (*(charFarPtr_t)(0x00400084L)) + 1;
	fossilInfo.baud = 2;	// 300 baud...

	if (bufSize <= sizeof(fossilInfo))
	{
		void far *dest;

		FP_SEG(dest) = bufSeg;
		FP_OFF(dest) = bufOfs;
		_fmemcpy(dest, &fossilInfo, sizeof(fossilInfo));
		*pBytesTransferred = sizeof(fossilInfo);
	}
	else
	{
		*pBytesTransferred = 0;
	}

	LED_OFF(LT_INFOABOUTDRIVER);
}


/* נננננננננננננננננננ FOSSIL interrupt service routine ננננננננננננננננננננ */

void far cdecl interrupt FOSSIL(unsigned _es, unsigned _ds, unsigned _di,
    unsigned _si, unsigned _bp, unsigned _sp, unsigned _bx, unsigned _dx,
    unsigned _cx, unsigned _ax, unsigned _ip, unsigned _cs, unsigned flags)
{
	unsigned portIndex;
	port_t *pport;

	// The function number is in AH. Establish whether this function has the
	// port number among its input parameters. If not, it's a generic request,
	// to be dealt with by the regular FOSSIL driver.
	switch ((_ax >> 8) & 0xff)
	{
		default:
		{
			int ours = 0;

			if (_ax <= 0x1bff)
			{
				// A port number is expected. Check whether it's our call...
				if ((portIndex = _dx - cfg.minPortNo) < cfg.numberOfPorts)
				{
					ours = 1;
					pport = ports + portIndex;
					break;
				}
			}
			else
			{
				// Unknown function, let the regular FOSSIL deal with it.
			}

			if (ours) break;
		}

		case 0x0d:
		case 0x0e:
		case 0x07:
		case 0x11:
		case 0x12:
		case 0x13:
		case 0x15:
		case 0x16:
		case 0x17:
		case 0x7e:
		case 0x7f:
			// This is not for us.
			LED_INVERT(LT_CHAININGTOBNU);
			_chain_intr(old_FOSSIL_ISR);
	}

	LED_ON(LT_FOSSILACTIVE);

	// Only call our routines if the port is initialized or if this is an
	// initialize call.
	if ((P(initialized)) || ((_ax & 0xFF00) == 0x0400))
	{
		if ((_ax & 0xff00) == 0x0300)		// Status request
		{
			if ((P(ticksSinceLastPoll)) || (P(numberOfPollsThisTick)++ <= 2))
			{
				pollIsdnDriver(pport);
			}
		}

		if ((!(P(status) & 0x0100)) && (P(status) & 0x4000))
		{
			if (!P(idleCount)--)
			{
				releaseTimeSlice();
				P(idleCount) = 1;
			}
		}
		else P(idleCount) = 1;

		switch ((_ax >> 8) & 0xff)
		{
			case    3:		// Request status
				_ax = P(status);
				P(status) &= ~0x0200;	// Switch off overrun flag
				LED_OFF(LT_STAT_OVRN);
				break;

			case    0:		// Set Baudrate
				setBaudrate(pport, _ax & 0xff);
				_ax = P(status);
				break;

			case    1:		// Transmit character with wait
				transmitChar(pport, (char)(_ax & 0xff), 1);
				_ax = P(status);
				break;

			case    2:		// Receive character with wait
				receiveCharWithWait(pport, &_ax);
				break;

			case    4:		// Initialize driver
				initializeDriver(pport, &_ax, &_bx);
				break;

			case    5:		// Deinitialize driver
				deInitializeDriver(pport);
				break;

			case    6:		// Raise-lower DTR
				raiseOrLowerDTR(pport, _ax & 0x00FF);
				break;

			case    8:		// Flush output buffer
				flushOutputBuffer(pport);
				break;

			case    9:		// Purge output buffer
				purgeOutputBuffer(pport);
				break;

			case 0x0a:		// Purge input buffer
				purgeInputBuffer(pport);
				break;

			case 0x0b:		// Transmit no wait
				_ax = transmitChar(pport, (unsigned char)(_ax & 0xff), 0);
				break;

			case 0x0c:		// Non-destructive read-ahead
				nonDestructiveReadAhead(pport, &_ax);
				break;

			case 0x0f:		// Enable or disable flow control
				enableOrDisableFlowControl(pport, _ax & 0x00FF);
				break;

			case 0x10:		// Extended Ctrl-C/Ctrl-K checking and transmit on/off
				fossilFunction10h(pport, _ax & 0x00FF, &_ax);
				break;

			case 0x14:		// Enable or disable watchdog processing
				enableOrDisableWatchdog(pport, _ax & 0x00FF);
				break;

			case 0x18:		// Read block (transfer from FOSSIL to user buffer)
				readBlock(pport, _cx, _es, _di, &_ax);
				break;

			case 0x19:		// Write block (transfer from user buffer to FOSSIL)
				writeBlock(pport, _cx, _es, _di, &_ax);
				break;

			case 0x1a:		// Break begin or end
				startOrStopBreak(pport, _ax & 0x00FF);
				break;

			case 0x1b:		// Return information about the driver
				informationAboutDriver(pport, _cx, _es, _di, &_ax);
				break;

//			case    7:		// Return timer tick parameters
//			case 0x0d:		// Keyboard read without wait
//			case 0x0e:		// Keyboard read with wait
//			case 0x11:		// Set current cursor location
//			case 0x12:		// Read current cursor location
//			case 0x13:		// Single character ANSI write to screen
//			case 0x15:		// Write character to screen using BIOS support routines
//			case 0x16:		// Insert or delete a function from the timer tick chain
//			case 0x17:		// Reboot system
//			case 0x7e:		// Install an "external application" function
//			case 0x7f:		// Remove an "external application" function
			default:
				// This should never occur. Provided only for debugging purposes.
				LED_ON(LT_FOSSIL_ERROR);
		}
	}

	LED_OFF(LT_FOSSILACTIVE);
}

unsigned fossilSignature()
{
	_asm
	{
		mov   ah,4
		mov   dx,00ffh
		mov   bx,0
		int   14h
	}
}

int initialize(unsigned no_ports, unsigned minPortNo, unsigned rxBufKBytes, unsigned txBufKBytes)
{
	unsigned portIndex;
	port_t *pport;

	// Make sure a 'real' FOSSIL is already installed
	if (fossilSignature() != 0x1954)
	{
		fprintf(stderr, "Unable to find a 'real' FOSSIL driver\n");
		return 0;
	}

	// Make sure we're not installed ourselves yet

	if (0 == _fmemcmp((charFarPtr_t)((*(unsigned long far *)(0x14L << 2L)) + 9),
					  (charFarPtr_t)fossilEntryPoint.signature,
					  sizeof(fossilEntryPoint.signature)))
	{
		fprintf(stderr, "Already installed\n");
		return 0;
	}

	// Make sure the API is loaded
	if (-1 == (isdnIntNo = apiSoftwareInterrupt()))
	{
		fprintf(stderr, "Duo-ISDN API not installed\n");
		return 0;
	}

	// Allocate memory and initialize
	number_of_ports = no_ports;

	cfg.minPortNo = minPortNo;
	cfg.numberOfPorts = no_ports;

	if (NULL == (ports = _fmalloc(no_ports * sizeof(port_t))))
	{
		fprintf(stderr, "Out of memory allocating base tables\n");
		return 0;
	}

	for (portIndex = 0, pport = ports;
	     portIndex < number_of_ports;
	     portIndex++, minPortNo++, pport++)
	{
		P(number) = minPortNo;
		P(initialized) = 0;
		P(rxBufSize) = 1024 * rxBufKBytes;      P(txBufSize) = 1024 * txBufKBytes;

		if (NULL == (P(rxBuf) = _fmalloc(P(rxBufSize) + P(txBufSize))))
		{
			fprintf(stderr, "Out of memory allocating buffers for port %d\n", minPortNo);
			return 0;
		}

		P(txBuf) = P(rxBuf) + P(rxBufSize);
		P(rxBufHead) = P(rxBufTail) = P(rxBuf); P(txBufHead) = P(txBufTail) = P(txBuf);
		P(rxBufChars) = P(txBufChars) = 0;
		P(ticksSinceLastTx) = 0;
		P(status) &= ~0x0100;	// No data available in rx buffer
		LED_OFF(LT_STAT_RDA);
		P(status) &= ~0x0200;	// Clear overrun bit
		LED_OFF(LT_STAT_OVRN);
		P(status) |= 0x2000;	// Room is available in transmit buffer
		LED_ON(LT_STAT_THRE);
		P(status) |= 0x4000;	// Transmit buffer is empty
		LED_ON(LT_STAT_TSRE);
		P(charToSendNow) = -1;
		P(flowControl.mask) = P(flowControl.rxXonXoffState) = P(flowControl.txXonXoffState) = 0;
		P(call.state) = P(call.refuseAllCalls) = 0;
		P(status) &= ~0x0080;	// no carrier detected
		LED_OFF(LT_STAT_DCD);
		
		resetModemEmul(pport);
		initializeLed(pport);
	}

	// Install our interrupt handlers
	old_FOSSIL_ISR = _dos_getvect(0x14);
	old_clock_tick_ISR = _dos_getvect(0x1C);
	_dos_setvect(0x14, &fossilEntryPoint);		// Dirty trick: entry point within data
	_dos_setvect(0x1C, timer_isr);
	return 1;
}


/*
void uninitialize()
{
	unsigned portIndex;
	port_t *pport;

	if (NULL != old_FOSSIL_ISR)
	{
		_dos_setvect(0x08, old_clock_tick_ISR);
		_dos_setvect(0x14, old_FOSSIL_ISR);

		for (portIndex = 0, pport = ports;
		     portIndex < number_of_ports;
		     portIndex++, pport++)
		{
			deInitializeDriver(pport);
			_ffree(P(rxBuf));
		}

		free(ports);
		ports = NULL;
	}
}
*/
