/******
 *
 *	C A L L H A N D . C          by Jan Ceuleers, The Experimenter Board, 1992
 *	                                           part of the ISDN FOSSIL project
 *
 ******/

#define CALLHAND

#include <string.h>
#include <stdlib.h>
#include <dos.h>
#include "fos_isdn.h"

unsigned isdnIntNo = 0x20;			// Terminates program if not initialized
volatile int isdnDriverBusy = 0;
volatile int ticksSinceLastIncomingCallPoll = 0;


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננ apiSoftwareInterrupt
//
// This function finds out which software interrupt number to use when calling
// the API.
//
#pragma check_pointer(off)
#pragma check_stack(off)
unsigned apiSoftwareInterrupt()
{
	int found;
	char far * far *pvector;
	unsigned intNo;

	// Look for string "$isdn$" located at offset -6 into the code pointed to
	// by the interrupt vector.
	for (intNo = 0x60, found = 0, pvector = (char far * far *)(0x60L * 4L);
	     (intNo < 0x67) && (!found);
	     intNo++, pvector++)
	{
		found = (0 == _fmemcmp((char far *)(*pvector - 6), (char far *)"$isdn$", 6));
	}

	return found ? intNo-1 : 0xffff;
}


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ isdnDriver
//
// This function calls the ISDN driver. It double-checks another task isn't
// already in the driver, because the driver would otherwise go into an
// infinite loop.
//
static void isdnDriver(void far *msg, unsigned len)
{
	union REGS regs;
	struct SREGS sregs;

	LED_ON_NP(LT_ISDNDRIVER);
	beginCriticalSection();
	isdnDriverBusy++;

	while (isdnDriverBusy > 1)
	{
		// Be kind to your fellow-man
		releaseTimeSlice();
	}

	regs.x.cx = len;
	sregs.es = FP_SEG(msg);
	regs.x.bx = FP_OFF(msg);

	int86x(isdnIntNo, &regs, &regs, &sregs);

	isdnDriverBusy--;
	endCriticalSection();
	LED_OFF_NP(LT_ISDNDRIVER);
}


// נננננננננננננננננננננננננננננננננננננננננננננננננננננננ prepareIncomingCall
//
// This function sends a CHECK_INCOMING_CALL message to the ISDN driver, with
// the NO WAIT option. The return code isn't checked here; this is done by
// polling checkIncomingCall.
//
#define CHK_CALL(f) (incomingCallFilter[filterNumber].message.f)
void prepareIncomingCall(unsigned filterNumber)
{
	memset(&CHK_CALL(command), 0, sizeof(incomingCallFilter[filterNumber].message));

	CHK_CALL(command)     = A_MD_CHECK_INCOMING_CALL_NW;
	CHK_CALL(filter_nmbr) = incomingCallFilter[filterNumber].filter_nmbr;

	isdnDriver(&(incomingCallFilter[filterNumber].message), sizeof(incomingCallFilter[0].message));
}
#undef CHK_CALL


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ initializeAnswer
//
// This function initializes the incoming call filters. It's data-driven: the
// filters are stored in incomingCallFilter[], whose type is in fos_isdn.h
// and whose contents are defined in fos_isdn.c. The size of the array
// (NUMBER_OF_FILTERS) is also defined in fos_isdn.h.
//
#define DEF_INC_CALL(f) (P0(call.message.def_inc_call.f))
void initializeAnswer()
{
	unsigned filterNumber;

	for (filterNumber = 0; filterNumber<NUMBER_OF_FILTERS; filterNumber++)
	{
		memset(&(DEF_INC_CALL(command)), 0, sizeof(P0(call.message.def_inc_call)));

		DEF_INC_CALL(command)           = A_MD_DEFINE_INCOMING_CALL;
		DEF_INC_CALL(api_version)       = AM_API_RELEASE_V1;
		DEF_INC_CALL(filter_nmbr)       = 0;	// create new filter
		DEF_INC_CALL(protocol)          = incomingCallFilter[filterNumber].protocol;
		DEF_INC_CALL(protocol_override) = incomingCallFilter[filterNumber].protocol_override;
		DEF_INC_CALL(hlc_coding)        = incomingCallFilter[filterNumber].hlc_coding;
		DEF_INC_CALL(hlc_id)            = incomingCallFilter[filterNumber].hlc_id;
		DEF_INC_CALL(user_info_length)  = 0;	// no password-checking
		DEF_INC_CALL(inc_action)        = AM_INC_CALL_PROC;
		DEF_INC_CALL(s_union.define_call_proc.ring_info) = AM_RING;

		isdnDriver(&P0(call.message.def_inc_call), sizeof(P0(call.message.def_inc_call)));

		if (DEF_INC_CALL(ret_status) > 0)
		{
			incomingCallFilter[filterNumber].filter_nmbr = DEF_INC_CALL(ret_status);
			prepareIncomingCall(filterNumber);
		}
	}
}
#undef DEF_INC_CALL


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננ deinitializeAnswer
//
// This function removes the incoming call filters that were defined by
// initializeAnswer(). It's called when the last active port is deinitialized.
//
#define REM_INC_CALL(f) (P0(call.message.rem_inc_call.f))
void deinitializeAnswer()
{
	unsigned filterNumber;

	for (filterNumber = 0; filterNumber<NUMBER_OF_FILTERS; filterNumber++)
	{
		memset(&REM_INC_CALL(command), 0, sizeof(P0(call.message.rem_inc_call)));

		REM_INC_CALL(command)     = A_MD_REMOVE_INCOMING_CALL;
		REM_INC_CALL(api_version) = AM_API_RELEASE_V1;
		REM_INC_CALL(filter_nmbr) = incomingCallFilter[filterNumber].filter_nmbr;

		isdnDriver(&P0(call.message.rem_inc_call), sizeof(P0(call.message.rem_inc_call)));
	}
}
#undef REM_INC_CALL


// ננננננננננננננננננננננננננננננננננננננננננננננננננננ initializeIncomingData
//
// This function is called when we're ready to receive a data string of
// maximum size. This is done by sending a RECEIVE_DATA_STRING message to the
// driver, with the NO WAIT option. The driver will return immediately and
// will write data to the message buffer as soon as it comes in. We must check
// the return code regularly in order to find out whether data has already
// arrived.
//
// This function only initializes the NO WAIT process; the polling part of the
// described procedure is performed by checkIncomingData().
//
#define INCOMING_DATA(f) (P(call.no_wait.incoming_data.f))
void initializeIncomingData(port_t *pport)
{
	LED_ON(LT_INITINCOMINGDATA);

	INCOMING_DATA(command)  = A_MD_RECEIVE_DATA_STRING_NW;
	INCOMING_DATA(ref_nmbr) = P(call.referenceNumber);

	isdnDriver(&P(call.no_wait.incoming_data), sizeof(P(call.no_wait.incoming_data)));

	LED_OFF(LT_INITINCOMINGDATA);
}
#undef INCOMING_DATA


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ answerCall
//
// This function accepts an incoming call. The port must be in state 1 (i.e.
// incoming call is present) for this to make sense.
//
#define ACCEPT(f) (P(call.message.accept.f))
#define RN_INFO(f) (P(call.message.rn_info.f))
#define INCOMING_DATA(f) (P(call.no_wait.incoming_data.f))
void answerCall(port_t *pport)
{
	LED_ON(LT_ANSWERCALL);

	if (P(call.state) == 1)		// Incoming call ringing
	{
		ACCEPT(command)          = A_MD_ACCEPT_INCOMING_CALL;
		ACCEPT(ref_nmbr)         = P(call.referenceNumber);		// was filled in by checkIncomingCall
		ACCEPT(user_info_length) = 0;
		memset(ACCEPT(user_info), '\0', sizeof(ACCEPT(user_info)));
		ACCEPT(s_union.data_call_accept.multiplex)		 = AM_NO_MULTIPLEX_EXCL;
		ACCEPT(s_union.data_call_accept.speed)			 = AM_SPEED_19_2_K;		// if not specified by caller, use 19200
		ACCEPT(s_union.data_call_accept.datab_stopb_par) = AM_8B_NP_1S;

		isdnDriver(&P(call.message), sizeof(P(call.message.accept)));

		if (ACCEPT(ret_status) > 0)					// Did we connect OK?
		{
			initializeIncomingData(pport);			// Start receiving data
			P(flowControl.restart) = 0;				// Indicate that an incoming data message is pending

//			if (INCOMING_DATA(ret_status) >= 0)
			{
				// We need to find out about the protocol
				RN_INFO(command)  = A_MD_GET_INFO_OF_RN;
				RN_INFO(ref_nmbr) = P(call.referenceNumber);

				isdnDriver(&P(call.message), sizeof(P(call.message.rn_info)));

				if (RN_INFO(ret_status) > 0)
				{
					P(call.protocol) = RN_INFO(protocol);
//					P(call.speed) = RN_INFO(speed);	// API version 2.1 and up
					P(call.state) = 2;				// Switch to call proceeding state
					P(status) |= 0x0080;			// Turn on Carrier Detect bit
					LED_ON(LT_STAT_DCD);
				}
				else
				{
					hangup(pport);					// Something went wrong; terminate call
				}
			}
		}
	}

	LED_OFF(LT_ANSWERCALL);
}
#undef INCOMING_DATA
#undef RN_INFO
#undef ACCEPT


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ hangup
//
// This function releases the ongoing call.
//
#define RELEASE(f) (P(call.message.release.f))
void hangup(port_t *pport)
{
	LED_ON(LT_HANGUP);

	memset(&P(call.message.release), 0, sizeof(P(call.message.release)));

	RELEASE(command) = A_MD_RELEASE_CALL;
	RELEASE(ref_nmbr) = P(call.referenceNumber);
	RELEASE(user_cause) = AM_NORMAL_CLEARING;

	isdnDriver(&P(call.message), sizeof(P(call.message.release)));

	P(call.state) = 0;
	P(status) &= ~0x0080; LED_OFF(LT_STAT_DCD);
	P(modem.S[1]) = 0;

	LED_OFF(LT_HANGUP);
}
#undef RELEASE


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננ defineIncomingCall
//
// This function defines or removes the incoming call filters (by calling
// initializeAnswer or deinitializeAnswer) depending on whether this is
// the first port to be initialized or the last port to be deinitialized.
//
void defineIncomingCall(int onOrOff)
{
	int initializedPorts = 0;
	unsigned portIndex;
	port_t *pport;

	// First find out if this is the first/last port to be (de)initialized
	for (portIndex = 0, pport = ports;
	     portIndex < number_of_ports;
	     portIndex++, pport++)
	{
		if (P(initialized))
		{
			initializedPorts++;
			break;
		}
	}

	if (!initializedPorts)
	{
		if (onOrOff) initializeAnswer();
		else deinitializeAnswer();
	}
}


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננ initializeCallInfo
//
// This function submits a NO WAIT request to obtain D channel information.
// The return value of the GET_CALL_INFO message is polled by the
// checkCallState function.
//
#define CALL_INFO(f) (P(call.no_wait.call_info.f))
void initializeCallInfo(port_t *pport)
{
	LED_ON(LT_INITCALLINFO);

	CALL_INFO(command)  = A_MD_GET_CALL_INFO_NW;
	CALL_INFO(ref_nmbr) = P(call.referenceNumber);

	isdnDriver(&P(call.no_wait.call_info), sizeof(P(call.no_wait.call_info)));

	LED_OFF(LT_INITCALLINFO);
}
#undef CALL_INFO


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ checkCallState
//
// This function checks the return value of the GET_CALL_INFO message for
// D channel information.
//
#define INCOMING_DATA(f) (P(call.no_wait.incoming_data.f))
#define CALL_INFO(f) (P(call.no_wait.call_info.f))
#define RN_INFO(f) (P(call.message.rn_info.f))
void checkCallState(port_t *pport)
{
	LED_ON(LT_CHECKCALLSTATE);

	switch (CALL_INFO(ret_status))
	{
		case AM_COMMAND_PENDING:        // ignore
			LED_INVERT(LT_CI_PENDING);
			break;

		case AM_USER_INFO_RECEIVED:     // ignore
			LED_INVERT(LT_CI_USERINFORECVD);
			break;

		case AM_CHARGING_INFO:          // ignore
			LED_INVERT(LT_CI_CHARGINGINFO);
			break;

		case AM_REMINDER:               // ignore
			LED_INVERT(LT_CI_REMINDER);
			break;

		case AM_CALL_CONNECTED:
			LED_INVERT(LT_CI_CALLCONNECTED);

			if (P(call.state) == 3)		// Outgoing call pending
			{
				initializeIncomingData(pport);

				if (INCOMING_DATA(ret_status) >= 0)
				{
					P(call.state) = 2;		// call proceeding
					P(status) |= 0x0080;	// Turn on Carrier Detect bit
					LED_ON(LT_STAT_DCD);

					// We need to find out about the protocol
					RN_INFO(command)  = A_MD_GET_INFO_OF_RN;
					RN_INFO(ref_nmbr) = P(call.referenceNumber);

					isdnDriver(&P(call.message), sizeof(P(call.message.rn_info)));

					P(call.protocol) = RN_INFO(protocol);
//					P(call.speed)    = RN_INFO(speed);		// API 2.1

					hayesCONNECT(pport);	// Tell modem emulator we're connected
				}
				else
				{
					hangup(pport);
				}
			}
			else
			{
				// Error condition
				;
			}

			break;

		case AM_CALL_RELEASED:
		case AM_B_CHANNEL_OUT_OF_SERVICE:
		default:
			// All this LED stuff is optimized out if ENABLE_LED is not defined
			if (CALL_INFO(ret_status) == AM_CALL_RELEASED)
			{
				LED_INVERT(LT_CI_CALLRELEASED);
			}
			else if (CALL_INFO(ret_status) == AM_B_CHANNEL_OUT_OF_SERVICE)
			{
				LED_INVERT(LT_CI_BCHANNELOOS);
			}
			else
			{
				LED_INVERT(LT_CI_ERROR);
				LED_ON(LT_ISDNERROR);
			}

			{
				unsigned previousCallState = P(call.state);

				hangup(pport);				// release call
				P(modem.hayesState) = 0x80;	// command mode

				if (previousCallState != 1)
				{
					if (CALL_INFO(ret_status) == AM_CALL_RELEASED)
					{
						char numBuf[4];
					
						// Tell user why call was released
						newlineRx(pport);
						blockIntoRxBuf(pport, (charFarPtr_t)"CAUSE: ", 7);
						itoa(CALL_INFO(cause), numBuf, 10);
						blockIntoRxBuf(pport, (charFarPtr_t)numBuf, strlen(numBuf));
					}

					P(modem.responseCode) = RSP_NO_CARRIER;
					P(modem.extraResponseInfo) = ERSP_NONE;
					emitResponseCode(pport);
				}
			}

			break;

		case AM_SUSP_ACK:		// ignored
			LED_INVERT(LT_CI_SUSPACK);
			break;

		case AM_SUSPEND_REJ:	// ignored
			LED_INVERT(LT_CI_SUSPREJ);
			break;

		case AM_RES_ACK:		// ignored
			LED_INVERT(LT_CI_RESACK);
			break;

		case AM_RESUME_REJ:		// ignored
			LED_INVERT(LT_CI_RESREJ);
			break;
	}

	if ((P(call.state) != 0) && (CALL_INFO(ret_status) != AM_COMMAND_PENDING))
	{
		// Re-schedule the GET_CALL_INFO message
		initializeCallInfo(pport);
	}

	LED_OFF(LT_CHECKCALLSTATE);
}
#undef CALL_INFO
#undef RN_INFO
#undef INCOMING_DATA


// נננננננננננננננננננננננננננננננננננננננננננננננננננננננננ checkIncomingCall
//
// This function checks each of the incoming call filters. If there is an
// incoming call, it tries to find an idle port that could accept the call.
// If none is found, the call is rejected with cause USER_BUSY.
//
#define CHK_CALL(f) (incomingCallFilter[filterNumber].message.f)
#define REJ_CALL(f) (P0(call.message.rej_call.f))
void checkIncomingCall()
{
	unsigned portIndex, filterNumber;
	port_t *pport;
	int allBusy = 1;

	for (filterNumber = 0; filterNumber < NUMBER_OF_FILTERS; filterNumber++)
	{
		if ((CHK_CALL(filter_nmbr) <= 16) && (CHK_CALL(ret_status) > 0))	// We have an incoming call
		{
			for (portIndex = 0, pport = ports;
			     portIndex < number_of_ports;
			     portIndex++, pport++)
			{
				if (P(initialized) && (P(call.state) == 0) && (!P(call.refuseAllCalls)))
				{
					P(call.referenceNumber) = CHK_CALL(ret_status);
					P(call.state) = 1;		// incoming call;
					P(status) &= ~0x0080;	// make sure Carrier Detect is off
					LED_OFF(LT_STAT_DCD);
					memcpy(P(call.address), CHK_CALL(calling_address), sizeof(P(call.address)));
					memcpy(P(call.subaddress), CHK_CALL(calling_subaddress), sizeof(P(call.subaddress)));

					P(modem.responseCode) = RSP_RING;	// Send a RING to the terminal
					emitResponseCode(pport);
					initializeCallInfo(pport);

					P(call.timer) = 73;		// Send the next RING in 4 seconds
					P(modem.S[1]) = 0;		// Number of rings so far
					checkAutoAnswer(pport);
					allBusy = 0;
					break;
				}
			}

			if (allBusy)	// No ports available to take the call
			{
				REJ_CALL(ref_nmbr)         = CHK_CALL(ret_status);
				REJ_CALL(command)          = A_MD_REJECT_INCOMING_CALL;
				REJ_CALL(user_info_length) = 0;
				REJ_CALL(user_cause)       = AM_USER_BUSY;

				isdnDriver(&P0(call.message), sizeof(P0(call.message.rej_call)));
			}

			if (CHK_CALL(ret_status) != 0)
				prepareIncomingCall(filterNumber);
		}
		else if (CHK_CALL(ret_status) < 0)	// Error condition
		{
			LED_INVERT(LT_CC_ERROR);
			LED_ON(LT_ISDNERROR);
		}
	}
}
#undef CHK_CALL
#undef REJ_CALL


// נננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ checkAutoAnswer
//
// This function compares the number of RINGs so far with modem register S0.
// If auto-answering has been turned on (S[0] != 0) and the number of RINGs
// at least equals the predetermined threshold (S[1] >= S[0]), the call is
// answered.
//
void checkAutoAnswer(port_t *pport)
{
	LED_ON(LT_CHECKAUTOANSWER);

	P(modem.S[1])++;		// Number of rings so far

	if (P(modem.S[0]) != 0)		// If we're in auto-answer mode
	{
		if (P(modem.S[1]) >= P(modem.S[0]))		// If we've already had so many RINGs
		{
			hayesAnswerCall(pport);
		}
	}

	LED_OFF(LT_CHECKAUTOANSWER);
}


// נננננננננננננננננננננננננננננננננננננננננננננננננננננננננ checkIncomingData
//
// This function finds out if any data has been received. If it has, the
// characters are moved to the FOSSIL's receive buffer.
//
#define INCOMING_DATA(f) (P(call.no_wait.incoming_data.f))
void checkIncomingData(port_t *pport)
{
	int quitLoop = !P(call.state);
	
	LED_ON(LT_CHECKINCOMINGDATA);

	while (!quitLoop)
	{
		switch (INCOMING_DATA(ret_status))
		{
			case 2:		// break character received, clear receive buffer
				LED_INVERT(LT_ID_BREAKRECVD);

				purgeInputBuffer(pport);
				INCOMING_DATA(ret_status) = 0;
				restartRxWhenReadyTo(pport);
				break;

			case 1:		// string received
				LED_INVERT(LT_ID_STRINGRECVD);

				_asm cli;

				if (INCOMING_DATA(string_length)
				    != blockIntoRxBuf(pport, INCOMING_DATA(data_string), INCOMING_DATA(string_length)))
				{
					// Input buffer overrun; should never occur
					P(status) |= 0x0200;
					LED_ON(LT_STAT_OVRN);
					quitLoop++;
				}

/********** !!!!!!!!! Incorrect: XON and XOFF characters must be stripped from data stream */


/*				if (P(flowControl.mask) & 0x01)		// transmit Xon/Xoff flow control
				{
					// Scan the incoming data for XON and XOFF
					unsigned char *searchPtr;
					unsigned counter;

					for (counter = INCOMING_DATA(string_length), searchPtr = INCOMING_DATA(data_string);
					     counter;
					     counter--, searchPtr++)
					{
						if (*searchPtr == XON)
						{
							P(flowControl.txXonXoffState) = 0;
						}
						else if (*searchPtr == XOFF)
						{
							P(flowControl.txXonXoffState) = 1;
						}
					}
				}

				if ((P(flowControl.mask) & 0x08)	// receive Xon/Xoff flow control
					&& (rxBytesFree < (P(rxBufSize) / 4)))
				{
					// Send Xoff *now* (i.e. don't queue it)
					skipQueue(XOFF, pport);
				}
*/
				INCOMING_DATA(ret_status) = 0;
				restartRxWhenReadyTo(pport);

				_asm sti;

				break;

			default:	// error condition
				LED_INVERT(LT_ID_ERROR);
				LED_ON(LT_ISDNERROR);
				quitLoop++;
				break;

			case 0:		// nothing received
				LED_INVERT(LT_ID_PENDING);
				quitLoop++;
		}
	}

	LED_OFF(LT_CHECKINCOMINGDATA);
}
#undef INCOMING_DATA


// נננננננננננננננננננננננננננננננננננננננננננננננננננננננננ checkOutgoingData
//
// This function checks whether we have data to send. 
//
#define OUTGOING_DATA(f) (P(call.message.outgoing_data.f))
void checkOutgoingData(port_t *pport)
{
	unsigned byteCount;
	int quitLoop = 0;

	LED_ON(LT_CHECKOUTGOINGDATA);

	if (P(charToSendNow) != -1)
	{
		// We have an urgent character to be sent *now*, without bothering
		// about our transmitter being inhibited by flow control.
		OUTGOING_DATA(command)       = A_MD_SEND_DATA_STRING;
		OUTGOING_DATA(ref_nmbr)      = P(call.referenceNumber);
		OUTGOING_DATA(string_type)   = AM_STP_DATA_STRING;
		OUTGOING_DATA(string_length) = 1;
		*OUTGOING_DATA(data_string)  = (char)P(charToSendNow);

		isdnDriver(&P(call.message), sizeof(P(call.message.outgoing_data)));

		if (OUTGOING_DATA(ret_status) == 1)
		{
			LED_INVERT(LT_URGENTCHARSENT);
			P(charToSendNow) = -1;
		}
	}

	while (!quitLoop)
	{
		LED_INVERT(LT_TRANSMITLOOP);

		byteCount = P(txBufChars);

		if (byteCount != 0)
		{
			if (byteCount > sizeof(OUTGOING_DATA(data_string)))
			{
				byteCount = sizeof(OUTGOING_DATA(data_string));
			}
			else
			{
				quitLoop++;

				if (P(ticksSinceLastTx) < 1)
				{
					/* We're not sending anything now if we can't fill an entire    *
					 * frame unless it's been a while since we last sent something. *
					 * We do this in order to maximize the frame size, thus         *
					 * hopefully reducing the protocol overhead.                    */
					byteCount = 0;
					LED_INVERT(LT_POSTPONINGTX);
				}
			}

			if (byteCount)		// We have something to send
			{
				OUTGOING_DATA(command)       = A_MD_SEND_DATA_STRING;
				OUTGOING_DATA(ref_nmbr)      = P(call.referenceNumber);
				OUTGOING_DATA(string_type)   = AM_STP_DATA_STRING;
				byteCount = 
				OUTGOING_DATA(string_length) = blockFromTxBufNonDestructive(pport,
				                                                            OUTGOING_DATA(data_string),
				                                                            byteCount);

				isdnDriver(&P(call.message), sizeof(P(call.message.outgoing_data)));

				if (OUTGOING_DATA(ret_status) >= 0)
				{
					removeFromTxBuf(pport, OUTGOING_DATA(ret_status));
					P(ticksSinceLastTx) = 0;

					LED_INVERT(LT_STRINGSENT);

					if (OUTGOING_DATA(ret_status) < byteCount)
					{
						LED_INVERT(LT_TXFLOWCONTROL);
						quitLoop++;
					}
				}
				else
				{
					if (OUTGOING_DATA(ret_status) < 0)
					{
						LED_INVERT(LT_OD_ERROR);
//						LED_ON(LT_ISDNERROR);
					}

					quitLoop++;
				}
			}
			else quitLoop++;
		}
		else quitLoop++;
	}

	LED_OFF(LT_CHECKOUTGOINGDATA);
}
#undef OUTGOING_DATA


// נננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ skipQueue
//
// This function makes a character skip the queue by storing it in a unique
// variable. This variable is checked by the transmission function
// checkOutgoingData before checking the transmit buffer.
//
// This is used for software flow control: we don't want our XON and XOFF
// characters to be queued along with the rest of the data stream.
//
void skipQueue(char charToSend, port_t *pport)
{
	P(charToSendNow) = charToSend;
}


// נננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ setupCall
//
// This function initiates an outbound call.
//
#define CALL_INFO(f) (P(call.no_wait.call_info.f))
#define SETUP(f) (P(call.message.setup.f))
#define INCOMING_DATA(f) (P(call.no_wait.incoming_data.f))
void setupCall(port_t *pport)
{
	LED_ON(LT_SETUPCALL);

	switch (P(call.state))
	{
		case 1:		// incoming call ringing
		case 3:		// outgoing call pending
			LED_INVERT(LT_ABORTINGSETUP);
			break;

		case 2:		// call proceeding
			LED_INVERT(LT_HANGUPSETUP);
			hangup(pport);
			if (P(call.state) != 0) break;
			// fall thru

		case 0:
			memset(&SETUP(command), 0, sizeof(P(call.message.setup)));

			SETUP(command)           = A_MD_SETUP_CALL;
			SETUP(api_version)       = AM_API_RELEASE_V1;
			SETUP(protocol)          = P(modem.S[18]);
//			SETUP(protocol_override) = P(modem.S[19]);
			SETUP(hlc_coding)        = P(modem.S[14]);
			SETUP(hlc_id)            = P(modem.S[13]);
			SETUP(channel)			 = P(modem.S[15]);
			strcpy(SETUP(called_address),	 P(call.address));
			strcpy(SETUP(called_subaddress), P(call.subaddress));
			SETUP(s_union.data_call_setup.speed)           = P(modem.S[16]);
			SETUP(s_union.data_call_setup.datab_stopb_par) = P(modem.S[17]);

			isdnDriver(&P(call.message), sizeof(P(call.message.setup)));

			if (SETUP(ret_status) > 0)
			{
				LED_INVERT(LT_SETUPOK);

				P(call.referenceNumber) = SETUP(ret_status);
				P(call.timer) = (182 * P(modem.S[7])) / 10;	// Initialize the no-response timer
				initializeCallInfo(pport);

				if (CALL_INFO(ret_status) >= 0)
				{
					P(call.state) = 3;		// Outgoing call pending
					P(status) &= ~0x0080;	// Switch off Carrier Detect
					LED_OFF(LT_STAT_DCD);
				}
				else
				{
					hangup(pport);
					P(modem.responseCode) = RSP_NO_CARRIER;
					P(call.state) = 0;
					P(status) &= ~0x0080; LED_OFF(LT_STAT_DCD);
				}
			}
			else
			{
				LED_INVERT(LT_SU_ERROR);
				P(modem.responseCode) = RSP_NO_CARRIER;
			}

			if ((P(call.state) == 0) && (P(modem.responseCode) != RSP_OK))
			{
				emitResponseCode(pport);
			}

			break;

		default:
			break;
	}

	LED_OFF(LT_SETUPCALL);
}
#undef SETUP
#undef CALL_INFO
#undef INCOMING_DATA


// ננננננננננננננננננננננננננננננננננננננננננננננננננננננננננננ pollIsdnDriver
//
// This function is called regularly. It calls the various polling functions
// needing to be called depending on the port state.
//
// Due to complications which can lead to deadlocks in multi-tasking
// environments, these functions cannot be performed from the timer hardware
// interrupt service routine.
//
void pollIsdnDriver(port_t *pport)
{
	if (!isdnDriverBusy)
	{
		LED_ON(LT_POLLISDNDRIVER);

		if (P(ticksSinceLastPoll))
		{
			P(ticksSinceLastPoll) = P(numberOfPollsThisTick) = 0;
		}

		if (P(call.hangupRequested))
		{
			hangup(pport);
			P(call.hangupRequested) = 0;
		}
		else
		{
			if (ticksSinceLastIncomingCallPoll > 9)		// Twice per second
			{
				checkIncomingCall();
				ticksSinceLastIncomingCallPoll = 0;
			}

			switch (P(call.state))
			{
				case 1:
					if (P(call.checkAutoAnswer))
					{
						checkAutoAnswer(pport);
						P(call.checkAutoAnswer) = 0;
					}
					break;

				case 2:
					checkOutgoingData(pport);
					// fall thru

				case 3:
					checkIncomingData(pport);
					break;
			}
		}

		if (P(call.state) != 0)
		{
			checkCallState(pport);
		}

		LED_OFF(LT_POLLISDNDRIVER);
	}
}
