1846 lines
58 KiB
C
1846 lines
58 KiB
C
// commdrv.c - KPCOMM.DRV -- High-speed COMM.DRV replacement
|
|
//
|
|
// Exported COMM.DRV entry points (Windows 3.1 DDAG signatures), port
|
|
// configuration, ring buffer management, and DLL entry/exit.
|
|
//
|
|
// Drop-in replacement for the Windows 3.1 COMM.DRV with proper 16550
|
|
// FIFO management for reliable operation at 57600 and 115200 baud.
|
|
|
|
#include "commdrv.h"
|
|
#include <stddef.h>
|
|
|
|
// -----------------------------------------------------------------------
|
|
// SYSTEM.INI [386Enh] keys, named COMn<suffix>
|
|
// -----------------------------------------------------------------------
|
|
#define INI_FILE "SYSTEM.INI"
|
|
#define INI_SECTION "386Enh"
|
|
#define INI_KEY_BASE "Base" // COMnBase: hex I/O address, used only if the BIOS has none
|
|
#define INI_KEY_BUFFER "Buffer" // COMnBuffer: minimum queue size
|
|
#define INI_KEY_FIFO "FIFO" // COMnFIFO: Boolean, 16550A FIFOs enabled by default
|
|
#define INI_KEY_IRQ "IRQ" // COMnIRQ: interrupt line
|
|
#define INI_KEY_KEEPDTR "KeepDTR" // COMnKeepDTR: Boolean, CloseComm leaves DTR up (default off)
|
|
#define INI_KEY_TRIGGER "RxTRIGGER" // COMnRxTRIGGER: RX FIFO trigger level in bytes (1/4/8/14)
|
|
#define INI_KEY_MAX 16 // "COMn" + longest suffix + NUL
|
|
#define INI_VALUE_MAX 16
|
|
#define COM_KEY_PREFIX "COM1"
|
|
#define COM_KEY_DIGIT 3 // Index of the port digit in COM_KEY_PREFIX
|
|
#define DEFAULT_TRIGGER 4 // COMnRxTRIGGER default (bytes): ~1 ms slack at 115200, 1/4 the interrupts of 1
|
|
#define HEX_PREFIX_LEN 2 // "0x"
|
|
#define HEX_DIGIT_BITS 4
|
|
|
|
// trmcom gives up when the transmitter makes no progress for this many BIOS ticks (~5 s)
|
|
#define DRAIN_TIMEOUT_TICKS 91
|
|
|
|
// Modem lines a DCB timeout can make output wait for (see waitTxLines)
|
|
#define TX_LINE_COUNT 3
|
|
|
|
typedef struct {
|
|
uint16_t timeoutOffset; // offsetof(DCB, <line>Timeout)
|
|
uint8_t msrBit; // Line state in the MSR
|
|
uint16_t error; // CE_* bit reported when the wait times out
|
|
} TxLineT;
|
|
|
|
#if DBG_ENABLED
|
|
#define DBG_LABEL_MAX 32 // Longest label dbgHex16 copies
|
|
#define DBG_LINE_MAX 48 // Label + ": 0x" + 4 hex digits + CRLF + NUL
|
|
#endif
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Prototypes -- exported entry points
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export cclrbrk(int16_t commId);
|
|
uint16_t FAR * FAR PASCAL _export cevt(int16_t commId, int16_t evtMask);
|
|
uint16_t FAR PASCAL _export cevtget(int16_t commId, int16_t evtMask);
|
|
int32_t FAR PASCAL _export cextfcn(int16_t commId, int16_t func);
|
|
int16_t FAR PASCAL _export cflush(int16_t commId, int16_t queue);
|
|
int FAR PASCAL _export commNotifyWndProc(void);
|
|
int16_t FAR PASCAL _export commWriteString(int16_t commId, void FAR *buf, int16_t len);
|
|
int16_t FAR PASCAL _export csetbrk(int16_t commId);
|
|
uint16_t FAR PASCAL _export ctx(int16_t commId, int16_t ch);
|
|
int16_t FAR PASCAL _export enableNotification(int16_t commId, HWND hwnd, int16_t rxTrigger, int16_t txTrigger);
|
|
DCB FAR * FAR PASCAL _export getdcb(int16_t commId);
|
|
int16_t FAR PASCAL _export inicom(DCB FAR *dcb);
|
|
void FAR PASCAL _export reactivateOpenCommPorts(void);
|
|
int16_t FAR PASCAL _export readCommString(int16_t commId, void FAR *buf, int16_t len);
|
|
int16_t FAR PASCAL _export reccom(int16_t commId);
|
|
int16_t FAR PASCAL _export setcom(DCB FAR *dcb);
|
|
int16_t FAR PASCAL _export setque(int16_t commId, QdbT FAR *qdb);
|
|
uint16_t FAR PASCAL _export sndcom(int16_t commId, int16_t ch);
|
|
uint16_t FAR PASCAL _export stacom(int16_t commId, COMSTAT FAR *stat);
|
|
void FAR PASCAL _export suspendOpenCommPorts(void);
|
|
int16_t FAR PASCAL _export trmcom(int16_t commId);
|
|
|
|
int FAR PASCAL LibMain(HANDLE hInstance, WORD wDataSeg, WORD wHeapSize, LPSTR lpCmdLine);
|
|
int FAR PASCAL _export WEP(int nParam);
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Prototypes -- internal helpers (shared ones are declared in commdrv.h)
|
|
// -----------------------------------------------------------------------
|
|
static void addErrors(PortStateT *port, uint16_t errors);
|
|
static bool allocBuffer(BufferT FAR *buf, uint16_t size);
|
|
static int16_t applyDcb(PortStateT *port, const DCB FAR *dcb, bool opening);
|
|
static uint16_t baudDivisor(uint16_t baud);
|
|
static uint16_t biosDataSelector(void);
|
|
static uint32_t cbrToBaud(uint16_t cbr);
|
|
static void clearUartStatus(uint16_t base);
|
|
static void closePort(PortStateT *port);
|
|
static int32_t comBaseIrq(int16_t commId);
|
|
static void comIniKey(char FAR *key, int16_t commId, const char FAR *suffix);
|
|
static int16_t dcbToLine(const DCB FAR *dcb, uint16_t FAR *divisor, uint8_t FAR *lcr);
|
|
static bool detect16550(uint16_t base);
|
|
static bool drainTx(PortStateT *port);
|
|
static void enableFifo(PortStateT *port);
|
|
static void freeBuffer(BufferT FAR *buf);
|
|
static PortStateT *getOpenPort(int16_t commId);
|
|
static int16_t hexDigit(char ch);
|
|
static void initPortState(PortStateT *port, int16_t commId, uint16_t base, uint8_t irq);
|
|
static uint16_t queueSize(int16_t commId, uint16_t requested);
|
|
static uint16_t readComIni(int16_t commId, const char FAR *suffix, uint16_t defVal);
|
|
static bool readComIniBool(int16_t commId, const char FAR *suffix, bool defVal);
|
|
static uint16_t readComIniHex(int16_t commId, const char FAR *suffix, uint16_t defVal);
|
|
static void readComIniString(int16_t commId, const char FAR *suffix, char FAR *value);
|
|
static void readPortConfig(int16_t commId, uint16_t FAR *base, uint8_t FAR *irq);
|
|
static uint16_t readRx(PortStateT *port, uint8_t FAR *dst, uint16_t len);
|
|
static void resetRx(PortStateT *port);
|
|
static void resetTx(PortStateT *port);
|
|
static bool resolvePostMessage(void);
|
|
static uint8_t rxTriggerBits(int16_t commId);
|
|
static int16_t setBreak(int16_t commId, bool on);
|
|
static int16_t triggerCode(uint16_t bytes);
|
|
static bool uartPresent(uint16_t base);
|
|
static uint16_t waitTxLines(PortStateT *port);
|
|
static void writeLine(PortStateT *port, uint16_t divisor, uint8_t lcr);
|
|
static uint16_t writeTx(PortStateT *port, const uint8_t FAR *src, uint16_t len);
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Default port resources, used when neither the BIOS nor SYSTEM.INI says
|
|
// -----------------------------------------------------------------------
|
|
static const uint16_t defaultBase[MAX_PORTS] = {
|
|
COM1_BASE, COM2_BASE, COM3_BASE, COM4_BASE
|
|
};
|
|
|
|
static const uint8_t defaultIrq[MAX_PORTS] = {
|
|
COM1_IRQ, COM2_IRQ, COM3_IRQ, COM4_IRQ
|
|
};
|
|
|
|
// RX FIFO trigger level in bytes, indexed by the FCR trigger code
|
|
static const uint8_t fifoTriggerBytes[] = { 1, 4, 8, 14 };
|
|
|
|
static const TxLineT txLines[TX_LINE_COUNT] = {
|
|
{ offsetof(DCB, CtsTimeout), MSR_CTS, CE_CTSTO },
|
|
{ offsetof(DCB, DsrTimeout), MSR_DSR, CE_DSRTO },
|
|
{ offsetof(DCB, RlsTimeout), MSR_DCD, CE_RLSDTO }
|
|
};
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Global state
|
|
// -----------------------------------------------------------------------
|
|
PortStateT ports[MAX_PORTS]; // One per COM port, indexed by cid; shared with the ISR
|
|
volatile bool portsSuspended = false; // Set while a DOS application owns the hardware
|
|
static uint16_t biosSelector = 0; // DPMI selector for segment 0040h (0 = not created yet)
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// addErrors - Record CE_* bits from application context
|
|
//
|
|
// The ISR also updates errorFlags, so the read-modify-write is guarded.
|
|
// -----------------------------------------------------------------------
|
|
static void addErrors(PortStateT *port, uint16_t errors) {
|
|
_disable();
|
|
port->errorFlags |= errors;
|
|
_enable();
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// allocBuffer - Allocate a fixed, page-locked ring buffer
|
|
//
|
|
// GMEM_FIXED keeps the address stable and GlobalPageLock keeps it
|
|
// resident in enhanced mode, so the ISR can never fault on it.
|
|
// GMEM_SHARE makes the driver own it rather than the calling task, so an
|
|
// application that dies without CloseComm cannot free memory the ISR is
|
|
// still filling. Never call with interrupts disabled: the global heap may
|
|
// need compaction or DPMI.
|
|
// -----------------------------------------------------------------------
|
|
static bool allocBuffer(BufferT FAR *buf, uint16_t size) {
|
|
HGLOBAL handle;
|
|
|
|
handle = GlobalAlloc(GMEM_FIXED | GMEM_SHARE, (DWORD)size);
|
|
if (!handle) {
|
|
return false;
|
|
}
|
|
GlobalPageLock(handle);
|
|
|
|
buf->handle = handle;
|
|
buf->data = (uint8_t FAR *)GlobalLock(handle);
|
|
return true;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// applyDcb - Validate a DCB and make it the port's live configuration
|
|
//
|
|
// Shared by inicom and setcom. Throttling done under the old settings is
|
|
// released first, then line settings, DTR/RTS, transmit holds, and receive
|
|
// throttling are re-derived from the new DCB. fDtrDisable/fRtsDisable
|
|
// drive the lines at open and when they change, so an unrelated SetCommState
|
|
// does not undo an EscapeCommFunction. Returns 0 or an IE_* code.
|
|
// -----------------------------------------------------------------------
|
|
static int16_t applyDcb(PortStateT *port, const DCB FAR *dcb, bool opening) {
|
|
uint16_t divisor;
|
|
uint8_t lcr;
|
|
int16_t err;
|
|
bool dtrChanged;
|
|
bool rtsChanged;
|
|
|
|
err = dcbToLine(dcb, &divisor, &lcr);
|
|
if (err != 0) {
|
|
return err;
|
|
}
|
|
|
|
dtrChanged = opening || dcb->fDtrDisable != port->dcb.fDtrDisable;
|
|
rtsChanged = opening || dcb->fRtsDisable != port->dcb.fRtsDisable;
|
|
|
|
_disable();
|
|
releaseRxFlow(port);
|
|
_fmemcpy(&port->dcb, dcb, sizeof(DCB));
|
|
if (dtrChanged) {
|
|
port->dtrOn = !dcb->fDtrDisable;
|
|
}
|
|
if (rtsChanged) {
|
|
port->rtsOn = !dcb->fRtsDisable;
|
|
}
|
|
writeLine(port, divisor, lcr);
|
|
port->comDeb.baudRate = dcb->BaudRate;
|
|
if (!dcb->fOutX) {
|
|
port->txHold = (uint8_t)(port->txHold & ~TX_HOLD_XOFF);
|
|
}
|
|
writeMcr(port);
|
|
updateTxHold(port);
|
|
updateRxFlow(port);
|
|
_enable();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// baudDivisor - Convert a DCB BaudRate value to a UART divisor
|
|
//
|
|
// A high byte of 0xFF selects a CBR_* index; anything else is a raw rate
|
|
// (DDAG). Returns 0 for unsupported rates. The division truncates on
|
|
// purpose: raw values 57601-65279 give divisor 1 (115200 baud), which is
|
|
// how a 16-bit BaudRate can ask for 115200 (BuildCommDCB has been seen to
|
|
// store 0xE101 = 57601 for it).
|
|
// -----------------------------------------------------------------------
|
|
static uint16_t baudDivisor(uint16_t baud) {
|
|
uint32_t rate;
|
|
|
|
if ((baud & CBR_INDEX_MASK) == CBR_INDEX_MASK) {
|
|
rate = cbrToBaud(baud);
|
|
} else {
|
|
rate = baud;
|
|
}
|
|
|
|
if (rate < MIN_RAW_BAUD) {
|
|
return 0;
|
|
}
|
|
return (uint16_t)(UART_CLOCK_DIV / rate);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// biosDataSelector - Selector for the BIOS data area (segment 0040h)
|
|
//
|
|
// Cached after the first DPMI call. Returns 0 if DPMI refuses.
|
|
// -----------------------------------------------------------------------
|
|
static uint16_t biosDataSelector(void) {
|
|
uint16_t sel;
|
|
int16_t failed;
|
|
|
|
if (biosSelector == 0) {
|
|
_asm {
|
|
mov ax, DPMI_SEG_TO_DESC
|
|
mov bx, BIOS_DATA_SEG
|
|
int DPMI_INT
|
|
mov sel, ax
|
|
sbb ax, ax
|
|
mov failed, ax
|
|
}
|
|
if (!failed) {
|
|
biosSelector = sel;
|
|
}
|
|
}
|
|
return biosSelector;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// cbrToBaud - Translate a CBR_* index to a rate (0 = unsupported)
|
|
//
|
|
// CBR_128000 and CBR_256000 are beyond a 1.8432 MHz UART and are rejected.
|
|
// -----------------------------------------------------------------------
|
|
static uint32_t cbrToBaud(uint16_t cbr) {
|
|
switch (cbr) {
|
|
case CBR_110:
|
|
return 110UL;
|
|
case CBR_300:
|
|
return 300UL;
|
|
case CBR_600:
|
|
return 600UL;
|
|
case CBR_1200:
|
|
return 1200UL;
|
|
case CBR_2400:
|
|
return 2400UL;
|
|
case CBR_4800:
|
|
return 4800UL;
|
|
case CBR_9600:
|
|
return 9600UL;
|
|
case CBR_14400:
|
|
return 14400UL;
|
|
case CBR_19200:
|
|
return 19200UL;
|
|
case CBR_38400:
|
|
return 38400UL;
|
|
case CBR_56000:
|
|
return 56000UL;
|
|
case KPCBR_115200:
|
|
return 115200UL;
|
|
default:
|
|
return 0UL;
|
|
}
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// cclrbrk - Clear break (ordinal 14)
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export cclrbrk(int16_t commId) {
|
|
return setBreak(commId, false);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// cevt - Set the event mask; return a pointer to the event word (ordinal 11)
|
|
//
|
|
// Only events enabled here are recorded in the event word (DDAG).
|
|
// -----------------------------------------------------------------------
|
|
uint16_t FAR * FAR PASCAL _export cevt(int16_t commId, int16_t evtMask) {
|
|
PortStateT *port;
|
|
LptStateT *lpt;
|
|
|
|
lpt = getOpenLpt(commId);
|
|
if (lpt) {
|
|
lpt->evtMask = (uint16_t)evtMask;
|
|
return (uint16_t FAR *)&lpt->evtWord;
|
|
}
|
|
|
|
port = getOpenPort(commId);
|
|
if (!port) {
|
|
return NULL;
|
|
}
|
|
|
|
port->comDeb.evtMask = (uint16_t)evtMask;
|
|
return (uint16_t FAR *)&port->comDeb.evtWord;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// cevtget - Return and clear the requested event bits (ordinal 12)
|
|
// -----------------------------------------------------------------------
|
|
uint16_t FAR PASCAL _export cevtget(int16_t commId, int16_t evtMask) {
|
|
PortStateT *port;
|
|
LptStateT *lpt;
|
|
uint16_t events;
|
|
|
|
lpt = getOpenLpt(commId);
|
|
if (lpt) {
|
|
events = lpt->evtWord & (uint16_t)evtMask;
|
|
lpt->evtWord &= ~(uint16_t)evtMask;
|
|
return events;
|
|
}
|
|
|
|
port = getOpenPort(commId);
|
|
if (!port) {
|
|
return 0;
|
|
}
|
|
|
|
_disable();
|
|
events = port->comDeb.evtWord & (uint16_t)evtMask;
|
|
port->comDeb.evtWord &= ~(uint16_t)evtMask;
|
|
refreshNotify(port, CN_EVENT);
|
|
_enable();
|
|
|
|
return events;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// cextfcn - Extended functions / EscapeCommFunction (ordinal 9)
|
|
//
|
|
// Returns 0 on success (16-bit API), a negative value for an unknown
|
|
// function or port, or the requested value for the GET* queries. Also
|
|
// accepts the KPCOMM extensions KPESC_KEEP_DTR_ON_CLOSE and
|
|
// KPESC_DROP_DTR_ON_CLOSE (see commdrv.h).
|
|
// -----------------------------------------------------------------------
|
|
int32_t FAR PASCAL _export cextfcn(int16_t commId, int16_t func) {
|
|
PortStateT *port;
|
|
LptStateT *lpt;
|
|
int32_t result;
|
|
|
|
// Queries that do not need an open port
|
|
switch (func) {
|
|
case GETMAXCOM:
|
|
return MAX_PORTS - 1;
|
|
case GETMAXLPT:
|
|
return lptMaxId();
|
|
case GETBASEIRQ:
|
|
return isLptId(commId) ? lptBaseIrq(commId) : comBaseIrq(commId);
|
|
}
|
|
|
|
lpt = getOpenLpt(commId);
|
|
if (lpt) {
|
|
if (func == RESETDEV) {
|
|
lptReset(lpt);
|
|
return 0;
|
|
}
|
|
return DRV_ERROR;
|
|
}
|
|
|
|
port = getOpenPort(commId);
|
|
if (!port) {
|
|
return DRV_ERROR;
|
|
}
|
|
|
|
result = 0;
|
|
_disable();
|
|
switch (func) {
|
|
case SETXOFF:
|
|
port->txHold = (uint8_t)(port->txHold | TX_HOLD_XOFF);
|
|
break;
|
|
case SETXON:
|
|
port->txHold = (uint8_t)(port->txHold & ~TX_HOLD_XOFF);
|
|
startTx(port);
|
|
break;
|
|
case SETRTS:
|
|
port->rtsOn = true;
|
|
writeMcr(port);
|
|
break;
|
|
case CLRRTS:
|
|
port->rtsOn = false;
|
|
writeMcr(port);
|
|
break;
|
|
case SETDTR:
|
|
port->dtrOn = true;
|
|
writeMcr(port);
|
|
break;
|
|
case CLRDTR:
|
|
port->dtrOn = false;
|
|
writeMcr(port);
|
|
break;
|
|
case RESETDEV:
|
|
// Printer reset; nothing to do for a serial port
|
|
break;
|
|
case KPESC_KEEP_DTR_ON_CLOSE:
|
|
port->keepDtrOnClose = true;
|
|
break;
|
|
case KPESC_DROP_DTR_ON_CLOSE:
|
|
port->keepDtrOnClose = false;
|
|
break;
|
|
default:
|
|
result = DRV_ERROR;
|
|
break;
|
|
}
|
|
_enable();
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// cflush - Discard the transmit (0) or receive (1) queue (ordinal 10)
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export cflush(int16_t commId, int16_t queue) {
|
|
PortStateT *port;
|
|
|
|
if (getOpenLpt(commId)) {
|
|
return 0;
|
|
}
|
|
|
|
port = getOpenPort(commId);
|
|
if (!port || (queue != FLUSH_TX && queue != FLUSH_RX)) {
|
|
return DRV_ERROR;
|
|
}
|
|
|
|
_disable();
|
|
if (queue == FLUSH_RX) {
|
|
resetRx(port);
|
|
if (port->useFifo) {
|
|
_outp(port->comDeb.baseAddr + UART_FCR, FCR_ENABLE | FCR_RX_RESET | port->comDeb.fifoTrigger);
|
|
}
|
|
} else {
|
|
resetTx(port);
|
|
if (port->useFifo) {
|
|
_outp(port->comDeb.baseAddr + UART_FCR, FCR_ENABLE | FCR_TX_RESET | port->comDeb.fifoTrigger);
|
|
}
|
|
}
|
|
refreshNotify(port, (uint8_t)((queue == FLUSH_RX) ? CN_RECEIVE : 0));
|
|
_enable();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// clearUartStatus - Read every UART status source so nothing is pending
|
|
// -----------------------------------------------------------------------
|
|
static void clearUartStatus(uint16_t base) {
|
|
(void)_inp(base + UART_LSR);
|
|
(void)_inp(base + UART_MSR);
|
|
(void)_inp(base + UART_RBR);
|
|
(void)_inp(base + UART_IIR);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// closePort - Release a COM port's hardware, IRQ, and buffers
|
|
//
|
|
// Does not wait for output; trmcom drains first. Drops RTS and OUT2, and
|
|
// DTR as well unless keepDtrOnClose asks for the connection to survive
|
|
// (then a modem stays online for the next program, and with RTS/CTS flow
|
|
// control it holds incoming data until RTS is raised again).
|
|
// -----------------------------------------------------------------------
|
|
static void closePort(PortStateT *port) {
|
|
uint16_t base;
|
|
|
|
base = port->comDeb.baseAddr;
|
|
|
|
_disable();
|
|
port->isOpen = false;
|
|
writeIer(port, 0);
|
|
_enable();
|
|
|
|
if (port->useFifo) {
|
|
_outp(base + UART_FCR, FCR_ENABLE | FCR_RX_RESET | FCR_TX_RESET);
|
|
_outp(base + UART_FCR, 0);
|
|
}
|
|
|
|
_outp(base + UART_MCR, (port->keepDtrOnClose && port->dtrOn) ? MCR_DTR : 0);
|
|
clearUartStatus(base);
|
|
|
|
unhookIsr(port);
|
|
freeBuffer(&port->rxBuf);
|
|
freeBuffer(&port->txBuf);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// comBaseIrq - GETBASEIRQ for a COM port: base in the low word, IRQ in
|
|
// the high word
|
|
// -----------------------------------------------------------------------
|
|
static int32_t comBaseIrq(int16_t commId) {
|
|
uint16_t base;
|
|
uint8_t irq;
|
|
|
|
if (commId < 0 || commId >= MAX_PORTS) {
|
|
return BASEIRQ_NO_PORT;
|
|
}
|
|
|
|
if (ports[commId].isOpen) {
|
|
base = ports[commId].comDeb.baseAddr;
|
|
irq = ports[commId].irq;
|
|
} else {
|
|
readPortConfig(commId, &base, &irq);
|
|
}
|
|
return MAKELONG(base, irq);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// comIniKey - Build "COMn<suffix>" for a port
|
|
// -----------------------------------------------------------------------
|
|
static void comIniKey(char FAR *key, int16_t commId, const char FAR *suffix) {
|
|
lstrcpy(key, COM_KEY_PREFIX);
|
|
key[COM_KEY_DIGIT] = (char)(key[COM_KEY_DIGIT] + commId);
|
|
lstrcat(key, suffix);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// commNotifyWndProc - Ordinal 101 stub
|
|
//
|
|
// The stock COMM.DRV and CyberCom export an undocumented function at
|
|
// ordinal 101; CyberCom's is a plain RETF with no parameters.
|
|
// -----------------------------------------------------------------------
|
|
int FAR PASCAL _export commNotifyWndProc(void) {
|
|
return 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// commWriteString - Queue a block of bytes (ordinal 19)
|
|
//
|
|
// Returns the number of bytes queued; CE_TXFULL is set if not all fit.
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export commWriteString(int16_t commId, void FAR *buf, int16_t len) {
|
|
PortStateT *port;
|
|
LptStateT *lpt;
|
|
|
|
lpt = getOpenLpt(commId);
|
|
if (lpt) {
|
|
return lptWrite(lpt, (const uint8_t FAR *)buf, len);
|
|
}
|
|
|
|
port = getOpenPort(commId);
|
|
if (!port) {
|
|
return DRV_ERROR;
|
|
}
|
|
if (len <= 0) {
|
|
return 0;
|
|
}
|
|
return (int16_t)writeTx(port, (const uint8_t FAR *)buf, (uint16_t)len);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// csetbrk - Set break (ordinal 13)
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export csetbrk(int16_t commId) {
|
|
return setBreak(commId, true);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// ctx - Send one character ahead of the queue (ordinal 6)
|
|
//
|
|
// Returns CTX_BUSY if the previous one has not gone out yet (DDAG), or if
|
|
// a modem line guarded by a finite DCB timeout stayed low (CE_*TO set).
|
|
// -----------------------------------------------------------------------
|
|
uint16_t FAR PASCAL _export ctx(int16_t commId, int16_t ch) {
|
|
PortStateT *port;
|
|
LptStateT *lpt;
|
|
uint16_t result;
|
|
uint16_t lineErrors;
|
|
uint8_t data;
|
|
|
|
data = (uint8_t)ch;
|
|
|
|
lpt = getOpenLpt(commId);
|
|
if (lpt) {
|
|
return (lptWrite(lpt, &data, 1) == 1) ? 0 : CTX_BUSY;
|
|
}
|
|
|
|
port = getOpenPort(commId);
|
|
if (!port) {
|
|
return CE_MODE;
|
|
}
|
|
|
|
lineErrors = waitTxLines(port);
|
|
if (lineErrors) {
|
|
addErrors(port, lineErrors);
|
|
return CTX_BUSY;
|
|
}
|
|
|
|
_disable();
|
|
if (port->txImmediate != NO_CHAR) {
|
|
result = CTX_BUSY;
|
|
} else {
|
|
port->txImmediate = data;
|
|
startTx(port);
|
|
result = 0;
|
|
}
|
|
_enable();
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
#if DBG_ENABLED
|
|
// -----------------------------------------------------------------------
|
|
// dbgHex16 - Log "label: 0xNNNN"
|
|
// -----------------------------------------------------------------------
|
|
void dbgHex16(const char FAR *label, uint16_t val) {
|
|
static const char hex[] = "0123456789ABCDEF";
|
|
char buf[DBG_LINE_MAX];
|
|
int16_t i;
|
|
int16_t shift;
|
|
|
|
for (i = 0; i < DBG_LABEL_MAX && label[i]; i++) {
|
|
buf[i] = label[i];
|
|
}
|
|
buf[i++] = ':';
|
|
buf[i++] = ' ';
|
|
buf[i++] = '0';
|
|
buf[i++] = 'x';
|
|
for (shift = 12; shift >= 0; shift -= HEX_DIGIT_BITS) {
|
|
buf[i++] = hex[(val >> shift) & 0xF];
|
|
}
|
|
buf[i++] = '\r';
|
|
buf[i++] = '\n';
|
|
buf[i] = '\0';
|
|
OutputDebugString(buf);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// dbgStr - Log a string
|
|
// -----------------------------------------------------------------------
|
|
void dbgStr(const char FAR *msg) {
|
|
OutputDebugString(msg);
|
|
}
|
|
#endif
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// dcbToLine - Validate a DCB's line settings and compute divisor and LCR
|
|
//
|
|
// Returns 0 or IE_BAUDRATE / IE_BYTESIZE / IE_DEFAULT. LCR_STB gives
|
|
// 1.5 stop bits with 5 data bits and 2 otherwise.
|
|
// -----------------------------------------------------------------------
|
|
static int16_t dcbToLine(const DCB FAR *dcb, uint16_t FAR *divisor, uint8_t FAR *lcr) {
|
|
uint8_t value;
|
|
|
|
*divisor = baudDivisor(dcb->BaudRate);
|
|
if (*divisor == 0) {
|
|
return IE_BAUDRATE;
|
|
}
|
|
|
|
if (dcb->ByteSize < MIN_BYTE_SIZE || dcb->ByteSize > MAX_BYTE_SIZE) {
|
|
return IE_BYTESIZE;
|
|
}
|
|
value = (uint8_t)(dcb->ByteSize - MIN_BYTE_SIZE);
|
|
|
|
switch (dcb->StopBits) {
|
|
case ONESTOPBIT:
|
|
break;
|
|
case ONE5STOPBITS:
|
|
case TWOSTOPBITS:
|
|
value |= LCR_STB;
|
|
break;
|
|
default:
|
|
return IE_DEFAULT;
|
|
}
|
|
|
|
switch (dcb->Parity) {
|
|
case NOPARITY:
|
|
break;
|
|
case ODDPARITY:
|
|
value |= LCR_PEN;
|
|
break;
|
|
case EVENPARITY:
|
|
value |= LCR_PEN | LCR_EPS;
|
|
break;
|
|
case MARKPARITY:
|
|
value |= LCR_PEN | LCR_SPAR;
|
|
break;
|
|
case SPACEPARITY:
|
|
value |= LCR_PEN | LCR_EPS | LCR_SPAR;
|
|
break;
|
|
default:
|
|
return IE_DEFAULT;
|
|
}
|
|
|
|
*lcr = value;
|
|
return 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// detect16550 - True if the UART is a 16550A with working FIFOs
|
|
//
|
|
// Leaves the FIFOs disabled; enableFifo turns them on with the trigger.
|
|
// -----------------------------------------------------------------------
|
|
static bool detect16550(uint16_t base) {
|
|
uint8_t iir;
|
|
|
|
_outp(base + UART_FCR, FCR_ENABLE);
|
|
iir = (uint8_t)_inp(base + UART_IIR);
|
|
_outp(base + UART_FCR, 0);
|
|
|
|
// 16550A sets both IIR bits 7:6; the original 16550 (broken FIFO) only bit 7
|
|
return (iir & IIR_FIFO_MASK) == IIR_FIFO_MASK;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// drainTx - Wait for queued output to leave the UART (for trmcom)
|
|
//
|
|
// Fails at once if a modem line guarded by a finite DCB timeout stays low
|
|
// past it; otherwise gives up after DRAIN_TIMEOUT_TICKS without progress,
|
|
// e.g. when held by XOFF or flow control. Counts BIOS tick changes rather
|
|
// than comparing tick values, so midnight rollover cannot cause a false
|
|
// timeout. GetTickCount lives in USER, which COMM.DRV cannot import.
|
|
// Returns false on timeout.
|
|
// -----------------------------------------------------------------------
|
|
static bool drainTx(PortStateT *port) {
|
|
uint16_t base;
|
|
uint16_t lastCount;
|
|
uint16_t lastTick;
|
|
uint16_t idleTicks;
|
|
uint16_t count;
|
|
uint16_t tick;
|
|
|
|
if (biosDataSelector() == 0) {
|
|
return port->comDeb.txCount == 0;
|
|
}
|
|
if (port->comDeb.txCount > 0 && waitTxLines(port) != 0) {
|
|
return false;
|
|
}
|
|
|
|
base = port->comDeb.baseAddr;
|
|
lastCount = port->comDeb.txCount;
|
|
lastTick = readBiosWord(BDA_TICK_COUNT);
|
|
idleTicks = 0;
|
|
|
|
for (;;) {
|
|
count = port->comDeb.txCount;
|
|
if (count == 0 && port->flowChar == NO_CHAR && port->txImmediate == NO_CHAR && (_inp(base + UART_LSR) & LSR_TEMT)) {
|
|
return true;
|
|
}
|
|
|
|
if (count != lastCount) {
|
|
lastCount = count;
|
|
idleTicks = 0;
|
|
}
|
|
|
|
tick = readBiosWord(BDA_TICK_COUNT);
|
|
if (tick != lastTick) {
|
|
lastTick = tick;
|
|
idleTicks++;
|
|
if (idleTicks > DRAIN_TIMEOUT_TICKS) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// enableFifo - Enable and reset the FIFOs with the configured RX trigger
|
|
// -----------------------------------------------------------------------
|
|
static void enableFifo(PortStateT *port) {
|
|
if (port->useFifo) {
|
|
_outp(port->comDeb.baseAddr + UART_FCR, FCR_ENABLE | FCR_RX_RESET | FCR_TX_RESET | port->comDeb.fifoTrigger);
|
|
}
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// enableNotification - Register a window for WM_COMMNOTIFY (ordinal 100)
|
|
//
|
|
// hwnd NULL disables notifications; a trigger of -1 disables that kind.
|
|
// Posts at once if a condition already holds. Returns TRUE/FALSE.
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export enableNotification(int16_t commId, HWND hwnd, int16_t rxTrigger, int16_t txTrigger) {
|
|
PortStateT *port;
|
|
|
|
port = getOpenPort(commId);
|
|
if (!port) {
|
|
return FALSE;
|
|
}
|
|
if (hwnd && !resolvePostMessage()) {
|
|
return FALSE;
|
|
}
|
|
|
|
_disable();
|
|
port->hwndNotify = hwnd;
|
|
port->rxNotifyTrigger = rxTrigger;
|
|
port->txNotifyTrigger = txTrigger;
|
|
port->notifySent = 0;
|
|
checkNotify(port);
|
|
_enable();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// freeBuffer - Release a buffer from allocBuffer (no-op if empty)
|
|
// -----------------------------------------------------------------------
|
|
static void freeBuffer(BufferT FAR *buf) {
|
|
if (!buf->handle) {
|
|
return;
|
|
}
|
|
|
|
GlobalPageUnlock(buf->handle);
|
|
GlobalUnlock(buf->handle);
|
|
GlobalFree(buf->handle);
|
|
buf->handle = NULL;
|
|
buf->data = NULL;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// getdcb - Return a pointer to the port's DCB (ordinal 15)
|
|
// -----------------------------------------------------------------------
|
|
DCB FAR * FAR PASCAL _export getdcb(int16_t commId) {
|
|
PortStateT *port;
|
|
LptStateT *lpt;
|
|
|
|
lpt = getOpenLpt(commId);
|
|
if (lpt) {
|
|
return &lpt->dcb;
|
|
}
|
|
|
|
port = getOpenPort(commId);
|
|
if (!port) {
|
|
return NULL;
|
|
}
|
|
return &port->dcb;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// getOpenPort - Port state for an open COM cid, or NULL
|
|
// -----------------------------------------------------------------------
|
|
static PortStateT *getOpenPort(int16_t commId) {
|
|
if (commId < 0 || commId >= MAX_PORTS || !ports[commId].isOpen) {
|
|
return NULL;
|
|
}
|
|
return &ports[commId];
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// hexDigit - Value of a hex digit, or -1
|
|
// -----------------------------------------------------------------------
|
|
static int16_t hexDigit(char ch) {
|
|
if (ch >= '0' && ch <= '9') {
|
|
return ch - '0';
|
|
}
|
|
if (ch >= 'A' && ch <= 'F') {
|
|
return ch - 'A' + 10;
|
|
}
|
|
if (ch >= 'a' && ch <= 'f') {
|
|
return ch - 'a' + 10;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// inicom - Open a port (ordinal 1)
|
|
//
|
|
// The cid is dcb->Id (0-3 = COM1-COM4, 0x80-0x82 = LPT1-LPT3). Settings
|
|
// are validated before any hardware is touched. The queues get a default
|
|
// size here; USER calls setque right after with the application's sizes.
|
|
// Returns 0 or an IE_* code (DDAG).
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export inicom(DCB FAR *dcb) {
|
|
PortStateT *port;
|
|
int16_t commId;
|
|
int16_t err;
|
|
uint16_t base;
|
|
uint16_t divisor;
|
|
uint16_t size;
|
|
uint8_t irq;
|
|
uint8_t lcr;
|
|
|
|
if (!dcb) {
|
|
return IE_DEFAULT;
|
|
}
|
|
|
|
commId = dcb->Id;
|
|
dbgHex16("KPCOMM: inicom id", (uint16_t)commId);
|
|
dbgHex16("KPCOMM: inicom CtsTimeout", dcb->CtsTimeout);
|
|
dbgHex16("KPCOMM: inicom DsrTimeout", dcb->DsrTimeout);
|
|
dbgHex16("KPCOMM: inicom RlsTimeout", dcb->RlsTimeout);
|
|
|
|
if (isLptId(commId)) {
|
|
return lptOpen(dcb);
|
|
}
|
|
if (commId >= MAX_PORTS) {
|
|
return IE_BADID;
|
|
}
|
|
|
|
port = &ports[commId];
|
|
if (port->isOpen) {
|
|
return IE_OPEN;
|
|
}
|
|
|
|
err = dcbToLine(dcb, &divisor, &lcr);
|
|
if (err != 0) {
|
|
return err;
|
|
}
|
|
|
|
readPortConfig(commId, &base, &irq);
|
|
if (!uartPresent(base)) {
|
|
return IE_HARDWARE;
|
|
}
|
|
|
|
initPortState(port, commId, base, irq);
|
|
|
|
size = queueSize(commId, DEFAULT_QUEUE_SIZE);
|
|
if (!allocBuffer(&port->rxBuf, size) || !allocBuffer(&port->txBuf, size)) {
|
|
freeBuffer(&port->rxBuf);
|
|
return IE_MEMORY;
|
|
}
|
|
port->comDeb.rxSize = size;
|
|
port->comDeb.txSize = size;
|
|
|
|
// Quiesce the UART before the IRQ is unmasked: a condition left over
|
|
// from an earlier session would otherwise interrupt before the port is
|
|
// marked open and could never be cleared.
|
|
_outp(base + UART_IER, 0);
|
|
clearUartStatus(base);
|
|
|
|
if (hookIsr(port) != 0) {
|
|
freeBuffer(&port->rxBuf);
|
|
freeBuffer(&port->txBuf);
|
|
return IE_HARDWARE;
|
|
}
|
|
|
|
enableFifo(port);
|
|
port->comDeb.msrShadow = (uint8_t)_inp(base + UART_MSR);
|
|
port->isOpen = true;
|
|
(void)applyDcb(port, dcb, true);
|
|
|
|
// Raising DTR/RTS may have changed the modem lines (RTS loops back to
|
|
// CTS on a null-modem cable). Take a fresh MSR snapshot in the same
|
|
// guarded step that enables interrupts so no transition is missed.
|
|
_disable();
|
|
clearUartStatus(base);
|
|
port->comDeb.msrShadow = (uint8_t)_inp(base + UART_MSR);
|
|
writeIer(port, (uint8_t)(port->comDeb.ierShadow | IER_RX));
|
|
updateTxHold(port);
|
|
_enable();
|
|
|
|
dbgStr("KPCOMM: inicom OK\r\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// initPortState - Reset a port's state for a fresh open
|
|
// -----------------------------------------------------------------------
|
|
static void initPortState(PortStateT *port, int16_t commId, uint16_t base, uint8_t irq) {
|
|
_fmemset(port, 0, sizeof(PortStateT));
|
|
|
|
port->comDeb.baseAddr = base;
|
|
port->comDeb.fifoTrigger = rxTriggerBits(commId);
|
|
port->irq = irq;
|
|
port->useFifo = detect16550(base) && readComIniBool(commId, INI_KEY_FIFO, true);
|
|
port->keepDtrOnClose = readComIniBool(commId, INI_KEY_KEEPDTR, false);
|
|
port->flowChar = NO_CHAR;
|
|
port->txImmediate = NO_CHAR;
|
|
port->rxNotifyTrigger = NOTIFY_OFF;
|
|
port->txNotifyTrigger = NOTIFY_OFF;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// queueSize - Queue size for a request, raised to the COMnBuffer floor
|
|
// -----------------------------------------------------------------------
|
|
static uint16_t queueSize(int16_t commId, uint16_t requested) {
|
|
uint16_t minSize;
|
|
|
|
minSize = readComIni(commId, INI_KEY_BUFFER, MIN_QUEUE_SIZE);
|
|
if (minSize < MIN_QUEUE_SIZE) {
|
|
minSize = MIN_QUEUE_SIZE;
|
|
}
|
|
return (requested > minSize) ? requested : minSize;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// reactivateOpenCommPorts - Take the hardware back after a DOS application
|
|
// (ordinal 18)
|
|
//
|
|
// The DOS application may have reprogrammed everything, so line settings,
|
|
// FIFOs, modem lines, interrupt enables, and PIC masks are all restored
|
|
// from the saved state. Every UART is silenced and reprogrammed before
|
|
// the ISR takes the ports back, so it never services a UART still in the
|
|
// DOS application's configuration. Queued data is kept.
|
|
// -----------------------------------------------------------------------
|
|
void FAR PASCAL _export reactivateOpenCommPorts(void) {
|
|
PortStateT *port;
|
|
int16_t i;
|
|
|
|
dbgStr("KPCOMM: reactivate\r\n");
|
|
|
|
for (i = 0; i < MAX_PORTS; i++) {
|
|
port = &ports[i];
|
|
if (port->isOpen) {
|
|
_disable();
|
|
writeIer(port, 0);
|
|
writeLine(port, baudDivisor(port->dcb.BaudRate), port->comDeb.lcrShadow);
|
|
enableFifo(port);
|
|
writeMcr(port);
|
|
_enable();
|
|
}
|
|
}
|
|
|
|
portsSuspended = false;
|
|
|
|
for (i = 0; i < MAX_PORTS; i++) {
|
|
port = &ports[i];
|
|
if (port->isOpen) {
|
|
_disable();
|
|
clearUartStatus(port->comDeb.baseAddr);
|
|
port->comDeb.msrShadow = (uint8_t)_inp(port->comDeb.baseAddr + UART_MSR);
|
|
writeIer(port, IER_RX);
|
|
updateTxHold(port);
|
|
_enable();
|
|
}
|
|
}
|
|
|
|
reactivateIrqs();
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// readBiosWord - Read a word from the BIOS data area (0 if unavailable)
|
|
// -----------------------------------------------------------------------
|
|
uint16_t readBiosWord(uint16_t offset) {
|
|
uint16_t sel;
|
|
|
|
sel = biosDataSelector();
|
|
if (sel == 0) {
|
|
return 0;
|
|
}
|
|
return *(volatile uint16_t FAR *)MAKELP(sel, offset);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// readComIni - Read COMn<suffix> from SYSTEM.INI [386Enh] as an integer
|
|
// -----------------------------------------------------------------------
|
|
static uint16_t readComIni(int16_t commId, const char FAR *suffix, uint16_t defVal) {
|
|
char key[INI_KEY_MAX];
|
|
|
|
comIniKey(key, commId, suffix);
|
|
return (uint16_t)GetPrivateProfileInt(INI_SECTION, key, (int)defVal, INI_FILE);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// readComIniBool - Read COMn<suffix> as a SYSTEM.INI Boolean
|
|
//
|
|
// Accepts 1-9/true/yes/on and 0/false/no/off (any case) by hand, since
|
|
// lstrcmpi lives in USER. Returns defVal when missing or unrecognized.
|
|
// -----------------------------------------------------------------------
|
|
static bool readComIniBool(int16_t commId, const char FAR *suffix, bool defVal) {
|
|
char value[INI_VALUE_MAX];
|
|
|
|
readComIniString(commId, suffix, value);
|
|
|
|
switch (value[0]) {
|
|
case 't':
|
|
case 'T':
|
|
case 'y':
|
|
case 'Y':
|
|
return true;
|
|
case '0':
|
|
case 'f':
|
|
case 'F':
|
|
case 'n':
|
|
case 'N':
|
|
return false;
|
|
case 'o':
|
|
case 'O':
|
|
return value[1] == 'n' || value[1] == 'N';
|
|
default:
|
|
return (value[0] >= '1' && value[0] <= '9') ? true : defVal;
|
|
}
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// readComIniHex - Read COMn<suffix> as hex ("3E8", "0x3E8", or "3E8h")
|
|
//
|
|
// Parsing stops at the first non-hex character. Returns defVal when the
|
|
// key is missing or holds no nonzero value.
|
|
// -----------------------------------------------------------------------
|
|
static uint16_t readComIniHex(int16_t commId, const char FAR *suffix, uint16_t defVal) {
|
|
char value[INI_VALUE_MAX];
|
|
uint16_t result;
|
|
int16_t digit;
|
|
int16_t i;
|
|
|
|
readComIniString(commId, suffix, value);
|
|
|
|
i = 0;
|
|
if (value[0] == '0' && (value[1] == 'x' || value[1] == 'X')) {
|
|
i = HEX_PREFIX_LEN;
|
|
}
|
|
|
|
result = 0;
|
|
for (;;) {
|
|
digit = hexDigit(value[i]);
|
|
if (digit < 0) {
|
|
break;
|
|
}
|
|
result = (uint16_t)((result << HEX_DIGIT_BITS) | (uint16_t)digit);
|
|
i++;
|
|
}
|
|
|
|
return (result != 0) ? result : defVal;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// readComIniString - Read COMn<suffix> into value (INI_VALUE_MAX bytes;
|
|
// empty string if missing)
|
|
// -----------------------------------------------------------------------
|
|
static void readComIniString(int16_t commId, const char FAR *suffix, char FAR *value) {
|
|
char key[INI_KEY_MAX];
|
|
|
|
comIniKey(key, commId, suffix);
|
|
GetPrivateProfileString(INI_SECTION, key, "", value, INI_VALUE_MAX, INI_FILE);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// readCommString - Read a block from the receive queue (ordinal 20)
|
|
//
|
|
// Returns the number of bytes read (0 if none).
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export readCommString(int16_t commId, void FAR *buf, int16_t len) {
|
|
PortStateT *port;
|
|
|
|
if (getOpenLpt(commId)) {
|
|
return 0;
|
|
}
|
|
|
|
port = getOpenPort(commId);
|
|
if (!port) {
|
|
return DRV_ERROR;
|
|
}
|
|
if (len <= 0) {
|
|
return 0;
|
|
}
|
|
return (int16_t)readRx(port, (uint8_t FAR *)buf, (uint16_t)len);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// readPortConfig - Base address and IRQ for a COM port
|
|
//
|
|
// DDAG: the BIOS data area wins; COMnBase only fills a missing entry.
|
|
// COMnIRQ overrides the standard IRQ. "IRQ 2" means IRQ 9 on AT
|
|
// hardware. Out-of-range IRQs become 0, which hookIsr rejects.
|
|
// -----------------------------------------------------------------------
|
|
static void readPortConfig(int16_t commId, uint16_t FAR *base, uint8_t FAR *irq) {
|
|
uint16_t irqVal;
|
|
|
|
*base = readBiosWord(BDA_COM_BASES + commId * sizeof(uint16_t));
|
|
if (*base == 0) {
|
|
*base = readComIniHex(commId, INI_KEY_BASE, defaultBase[commId]);
|
|
}
|
|
|
|
irqVal = readComIni(commId, INI_KEY_IRQ, defaultIrq[commId]);
|
|
if (irqVal == IRQ_CASCADE) {
|
|
irqVal = IRQ_CASCADE_TARGET;
|
|
}
|
|
*irq = (irqVal < IRQ_COUNT) ? (uint8_t)irqVal : 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// readRx - Move up to len bytes from the receive queue to dst
|
|
//
|
|
// Single consumer: only this side moves rxTail, and the ISR only grows
|
|
// rxCount, so the snapshot taken here stays valid. The copy runs with
|
|
// interrupts enabled; only the count update is guarded.
|
|
// -----------------------------------------------------------------------
|
|
static uint16_t readRx(PortStateT *port, uint8_t FAR *dst, uint16_t len) {
|
|
uint8_t FAR *buf;
|
|
uint16_t size;
|
|
uint16_t tail;
|
|
uint16_t avail;
|
|
uint16_t chunk;
|
|
|
|
avail = port->comDeb.rxCount;
|
|
if (avail > len) {
|
|
avail = len;
|
|
}
|
|
if (avail == 0) {
|
|
return 0;
|
|
}
|
|
|
|
buf = port->rxBuf.data;
|
|
size = port->comDeb.rxSize;
|
|
tail = port->comDeb.rxTail;
|
|
chunk = size - tail;
|
|
if (chunk > avail) {
|
|
chunk = avail;
|
|
}
|
|
_fmemcpy(dst, buf + tail, chunk);
|
|
if (chunk < avail) {
|
|
_fmemcpy(dst + chunk, buf, avail - chunk);
|
|
tail = avail - chunk;
|
|
} else {
|
|
// tail + chunk <= size, so this cannot overflow 16 bits
|
|
tail += chunk;
|
|
if (tail == size) {
|
|
tail = 0;
|
|
}
|
|
}
|
|
port->comDeb.rxTail = tail;
|
|
|
|
_disable();
|
|
port->comDeb.rxCount -= avail;
|
|
updateRxFlow(port);
|
|
refreshNotify(port, CN_RECEIVE);
|
|
_enable();
|
|
|
|
return avail;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// reccom - Read one byte (ordinal 4)
|
|
//
|
|
// Returns the byte, RECCOM_NO_DATA (-2) if the queue is empty, or -1 on
|
|
// error (DDAG).
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export reccom(int16_t commId) {
|
|
PortStateT *port;
|
|
uint8_t data;
|
|
|
|
if (getOpenLpt(commId)) {
|
|
return RECCOM_NO_DATA;
|
|
}
|
|
|
|
port = getOpenPort(commId);
|
|
if (!port) {
|
|
return DRV_ERROR;
|
|
}
|
|
if (readRx(port, &data, 1) == 0) {
|
|
return RECCOM_NO_DATA;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// resetRx - Empty the receive queue and release RX throttling
|
|
// (interrupts disabled)
|
|
// -----------------------------------------------------------------------
|
|
static void resetRx(PortStateT *port) {
|
|
port->comDeb.rxHead = 0;
|
|
port->comDeb.rxTail = 0;
|
|
port->comDeb.rxCount = 0;
|
|
port->eofSeen = false;
|
|
updateRxFlow(port);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// resetTx - Empty the transmit queue (interrupts disabled)
|
|
// -----------------------------------------------------------------------
|
|
static void resetTx(PortStateT *port) {
|
|
port->comDeb.txHead = 0;
|
|
port->comDeb.txTail = 0;
|
|
port->comDeb.txCount = 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// resolvePostMessage - Look up USER's PostMessage on first use
|
|
//
|
|
// COMM.DRV loads before USER, so it cannot import USER statically.
|
|
// -----------------------------------------------------------------------
|
|
static bool resolvePostMessage(void) {
|
|
HMODULE user;
|
|
|
|
if (!postMessageProc) {
|
|
user = GetModuleHandle("USER");
|
|
if (user) {
|
|
postMessageProc = (PostMessageProcT)GetProcAddress((HINSTANCE)user, "PostMessage");
|
|
}
|
|
}
|
|
return postMessageProc != NULL;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// rxTriggerBits - FCR trigger bits for COMnRxTRIGGER (1, 4, 8, or 14 bytes)
|
|
//
|
|
// Values the 16550A cannot do fall back to DEFAULT_TRIGGER.
|
|
// -----------------------------------------------------------------------
|
|
static uint8_t rxTriggerBits(int16_t commId) {
|
|
int16_t code;
|
|
|
|
code = triggerCode(readComIni(commId, INI_KEY_TRIGGER, DEFAULT_TRIGGER));
|
|
if (code < 0) {
|
|
code = triggerCode(DEFAULT_TRIGGER);
|
|
}
|
|
return (uint8_t)(code << FCR_TRIG_SHIFT);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// setBreak - Set or clear the break condition (csetbrk/cclrbrk)
|
|
// -----------------------------------------------------------------------
|
|
static int16_t setBreak(int16_t commId, bool on) {
|
|
PortStateT *port;
|
|
|
|
port = getOpenPort(commId);
|
|
if (!port) {
|
|
return DRV_ERROR;
|
|
}
|
|
|
|
if (on) {
|
|
port->comDeb.lcrShadow = (uint8_t)(port->comDeb.lcrShadow | LCR_SBRK);
|
|
} else {
|
|
port->comDeb.lcrShadow = (uint8_t)(port->comDeb.lcrShadow & ~LCR_SBRK);
|
|
}
|
|
_outp(port->comDeb.baseAddr + UART_LCR, port->comDeb.lcrShadow);
|
|
return 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// setcom - Apply a new DCB (ordinal 2)
|
|
//
|
|
// Takes only the DCB (RETF 4); the cid is dcb->Id. Returns 0 or IE_*.
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export setcom(DCB FAR *dcb) {
|
|
LptStateT *lpt;
|
|
int16_t commId;
|
|
|
|
if (!dcb) {
|
|
return IE_DEFAULT;
|
|
}
|
|
|
|
commId = dcb->Id;
|
|
dbgHex16("KPCOMM: setcom baud", dcb->BaudRate);
|
|
|
|
lpt = getOpenLpt(commId);
|
|
if (lpt) {
|
|
_fmemcpy(&lpt->dcb, dcb, sizeof(DCB));
|
|
return 0;
|
|
}
|
|
|
|
if (commId >= MAX_PORTS && !isLptId(commId)) {
|
|
return IE_BADID;
|
|
}
|
|
if (!getOpenPort(commId)) {
|
|
return IE_NOPEN;
|
|
}
|
|
return applyDcb(&ports[commId], dcb, false);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// setque - Size the receive and transmit queues (ordinal 3)
|
|
//
|
|
// Takes (cid, qdb FAR *) -- RETF 6. Only the requested sizes are used
|
|
// (raised to the COMnBuffer floor); USER's queue memory is ignored in
|
|
// favor of our own page-locked buffers. Buffers are replaced only if
|
|
// the size changes, and allocation happens before interrupts are
|
|
// disabled. Both queues are emptied. Returns 0 or IE_*.
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export setque(int16_t commId, QdbT FAR *qdb) {
|
|
PortStateT *port;
|
|
BufferT newRx;
|
|
BufferT newTx;
|
|
BufferT oldRx;
|
|
BufferT oldTx;
|
|
uint16_t rxSize;
|
|
uint16_t txSize;
|
|
|
|
if (isLptId(commId)) {
|
|
return getOpenLpt(commId) ? 0 : IE_NOPEN;
|
|
}
|
|
if (commId < 0 || commId >= MAX_PORTS) {
|
|
return IE_BADID;
|
|
}
|
|
|
|
port = &ports[commId];
|
|
if (!port->isOpen) {
|
|
return IE_NOPEN;
|
|
}
|
|
if (!qdb) {
|
|
return IE_DEFAULT;
|
|
}
|
|
|
|
rxSize = queueSize(commId, (uint16_t)qdb->rxSize);
|
|
txSize = queueSize(commId, (uint16_t)qdb->txSize);
|
|
dbgHex16("KPCOMM: setque rx", rxSize);
|
|
dbgHex16("KPCOMM: setque tx", txSize);
|
|
|
|
newRx = port->rxBuf;
|
|
newTx = port->txBuf;
|
|
if (rxSize != port->comDeb.rxSize && !allocBuffer(&newRx, rxSize)) {
|
|
return IE_MEMORY;
|
|
}
|
|
if (txSize != port->comDeb.txSize && !allocBuffer(&newTx, txSize)) {
|
|
if (newRx.handle != port->rxBuf.handle) {
|
|
freeBuffer(&newRx);
|
|
}
|
|
return IE_MEMORY;
|
|
}
|
|
|
|
_disable();
|
|
oldRx = port->rxBuf;
|
|
oldTx = port->txBuf;
|
|
port->rxBuf = newRx;
|
|
port->txBuf = newTx;
|
|
port->comDeb.rxSize = rxSize;
|
|
port->comDeb.txSize = txSize;
|
|
resetRx(port);
|
|
resetTx(port);
|
|
refreshNotify(port, CN_RECEIVE);
|
|
_enable();
|
|
|
|
if (oldRx.handle != newRx.handle) {
|
|
freeBuffer(&oldRx);
|
|
}
|
|
if (oldTx.handle != newTx.handle) {
|
|
freeBuffer(&oldTx);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// sndcom - Queue one byte (ordinal 5)
|
|
//
|
|
// Returns 0, or nonzero if it could not be queued (DDAG).
|
|
// -----------------------------------------------------------------------
|
|
uint16_t FAR PASCAL _export sndcom(int16_t commId, int16_t ch) {
|
|
PortStateT *port;
|
|
LptStateT *lpt;
|
|
uint8_t data;
|
|
|
|
data = (uint8_t)ch;
|
|
|
|
lpt = getOpenLpt(commId);
|
|
if (lpt) {
|
|
return (lptWrite(lpt, &data, 1) == 1) ? 0 : SNDCOM_FAILED;
|
|
}
|
|
|
|
port = getOpenPort(commId);
|
|
if (!port) {
|
|
return SNDCOM_FAILED;
|
|
}
|
|
return (writeTx(port, &data, 1) == 1) ? 0 : SNDCOM_FAILED;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// stacom - Return and clear the error flags; fill COMSTAT (ordinal 8)
|
|
//
|
|
// Returns CE_MODE for an invalid cid.
|
|
// -----------------------------------------------------------------------
|
|
uint16_t FAR PASCAL _export stacom(int16_t commId, COMSTAT FAR *stat) {
|
|
PortStateT *port;
|
|
LptStateT *lpt;
|
|
uint16_t errors;
|
|
uint16_t inQue;
|
|
uint16_t outQue;
|
|
uint8_t status;
|
|
|
|
lpt = getOpenLpt(commId);
|
|
if (lpt) {
|
|
errors = lpt->errorFlags;
|
|
lpt->errorFlags = 0;
|
|
if (stat) {
|
|
_fmemset(stat, 0, sizeof(COMSTAT));
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
port = getOpenPort(commId);
|
|
if (!port) {
|
|
return CE_MODE;
|
|
}
|
|
|
|
status = 0;
|
|
|
|
_disable();
|
|
errors = port->errorFlags;
|
|
port->errorFlags = 0;
|
|
if (port->txHold & TX_HOLD_CTS) {
|
|
status |= CSTF_CTSHOLD;
|
|
}
|
|
if (port->txHold & TX_HOLD_DSR) {
|
|
status |= CSTF_DSRHOLD;
|
|
}
|
|
if (port->txHold & TX_HOLD_RLSD) {
|
|
status |= CSTF_RLSDHOLD;
|
|
}
|
|
if (port->txHold & TX_HOLD_XOFF) {
|
|
status |= CSTF_XOFFHOLD;
|
|
}
|
|
if (port->rxThrottle & RX_THROTTLE_XOFF) {
|
|
status |= CSTF_XOFFSENT;
|
|
}
|
|
if (port->eofSeen) {
|
|
status |= CSTF_EOF;
|
|
}
|
|
if (port->txImmediate != NO_CHAR) {
|
|
status |= CSTF_TXIM;
|
|
}
|
|
inQue = port->comDeb.rxCount;
|
|
outQue = port->comDeb.txCount;
|
|
_enable();
|
|
|
|
if (stat) {
|
|
stat->status = status;
|
|
stat->cbInQue = inQue;
|
|
stat->cbOutQue = outQue;
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// suspendOpenCommPorts - Hand the hardware to a DOS application
|
|
// (ordinal 17)
|
|
//
|
|
// Called by WINOLDAP. Our UARTs are silenced, and the ISR passes every
|
|
// interrupt on our IRQs to the previous handlers until reactivation. DTR
|
|
// and RTS are left alone so a modem connection survives the switch.
|
|
// -----------------------------------------------------------------------
|
|
void FAR PASCAL _export suspendOpenCommPorts(void) {
|
|
int16_t i;
|
|
|
|
dbgStr("KPCOMM: suspend\r\n");
|
|
|
|
for (i = 0; i < MAX_PORTS; i++) {
|
|
if (ports[i].isOpen) {
|
|
_disable();
|
|
writeIer(&ports[i], 0);
|
|
_enable();
|
|
}
|
|
}
|
|
portsSuspended = true;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// triggerCode - FCR trigger code (0-3) for a trigger level in bytes, or -1
|
|
// -----------------------------------------------------------------------
|
|
static int16_t triggerCode(uint16_t bytes) {
|
|
int16_t code;
|
|
|
|
for (code = 0; code < (int16_t)sizeof(fifoTriggerBytes); code++) {
|
|
if (fifoTriggerBytes[code] == bytes) {
|
|
return code;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// trmcom - Close a port (ordinal 7)
|
|
//
|
|
// Transmits the output queue first (DDAG). DTR is dropped unless the port
|
|
// was told to keep it (COMnKeepDTR / KPESC_KEEP_DTR_ON_CLOSE). Returns 0,
|
|
// TRMCOM_TIMEOUT if the output could not be sent, or 0x8000 (CE_MODE) for
|
|
// an invalid cid.
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export trmcom(int16_t commId) {
|
|
PortStateT *port;
|
|
bool drained;
|
|
|
|
dbgHex16("KPCOMM: trmcom id", (uint16_t)commId);
|
|
|
|
if (isLptId(commId)) {
|
|
return lptClose(commId);
|
|
}
|
|
|
|
port = getOpenPort(commId);
|
|
if (!port) {
|
|
return (int16_t)CE_MODE;
|
|
}
|
|
|
|
drained = drainTx(port);
|
|
closePort(port);
|
|
return drained ? 0 : TRMCOM_TIMEOUT;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// uartPresent - True if a UART answers at base
|
|
//
|
|
// LCR reads back what was written; an empty I/O address floats to 0xFF.
|
|
// -----------------------------------------------------------------------
|
|
static bool uartPresent(uint16_t base) {
|
|
_outp(base + UART_LCR, UART_PRESENT_LCR);
|
|
return (uint8_t)_inp(base + UART_LCR) == UART_PRESENT_LCR;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// waitTxLines - Wait for the modem lines that finite DCB timeouts guard
|
|
//
|
|
// Cts/Dsr/RlsTimeout: IGNORE (0) skips the line, INFINITE makes it flow
|
|
// control (the ISR holds output through txHold), and anything else is how
|
|
// many milliseconds output waits for the line to go high (DDAG). Called
|
|
// before output is accepted, with interrupts enabled, watching the MSR
|
|
// shadow the ISR keeps current. The wait blocks the caller, as the stock
|
|
// driver's did. Returns the CE_CTSTO/CE_DSRTO/CE_RLSDTO bits of lines
|
|
// still low when their time ran out (0 = go ahead).
|
|
// -----------------------------------------------------------------------
|
|
static uint16_t waitTxLines(PortStateT *port) {
|
|
uint16_t elapsed;
|
|
uint16_t errors;
|
|
uint16_t timeout;
|
|
uint16_t lastTick;
|
|
uint16_t tick;
|
|
uint16_t i;
|
|
uint8_t msr;
|
|
bool waiting;
|
|
|
|
// Without the BIOS tick there is no clock: time out at once
|
|
elapsed = (biosDataSelector() != 0) ? 0 : UINT16_MAX;
|
|
lastTick = readBiosWord(BDA_TICK_COUNT);
|
|
|
|
for (;;) {
|
|
msr = port->comDeb.msrShadow;
|
|
errors = 0;
|
|
waiting = false;
|
|
|
|
for (i = 0; i < TX_LINE_COUNT; i++) {
|
|
timeout = *(const uint16_t *)((const uint8_t *)&port->dcb + txLines[i].timeoutOffset);
|
|
if (timeout == IGNORE || timeout == INFINITE || (msr & txLines[i].msrBit)) {
|
|
continue;
|
|
}
|
|
if (elapsed >= timeout) {
|
|
errors |= txLines[i].error;
|
|
} else {
|
|
waiting = true;
|
|
}
|
|
}
|
|
|
|
if (!waiting) {
|
|
return errors;
|
|
}
|
|
|
|
tick = readBiosWord(BDA_TICK_COUNT);
|
|
if (tick != lastTick) {
|
|
lastTick = tick;
|
|
elapsed = (elapsed < UINT16_MAX - MS_PER_BIOS_TICK) ? (uint16_t)(elapsed + MS_PER_BIOS_TICK) : UINT16_MAX;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// writeLine - Program the divisor and LCR, keeping the break state
|
|
//
|
|
// Interrupts must be disabled: while DLAB is set, offsets 0/1 are the
|
|
// divisor latch and the ISR would read garbage from "RBR".
|
|
// -----------------------------------------------------------------------
|
|
static void writeLine(PortStateT *port, uint16_t divisor, uint8_t lcr) {
|
|
uint16_t base;
|
|
|
|
base = port->comDeb.baseAddr;
|
|
lcr = (uint8_t)((lcr & ~LCR_SBRK) | (port->comDeb.lcrShadow & LCR_SBRK));
|
|
|
|
_outp(base + UART_LCR, lcr | LCR_DLAB);
|
|
_outp(base + UART_DLL, LOBYTE(divisor));
|
|
_outp(base + UART_DLM, HIBYTE(divisor));
|
|
_outp(base + UART_LCR, lcr);
|
|
|
|
port->comDeb.lcrShadow = lcr;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// writeTx - Copy up to len bytes into the transmit queue and start TX
|
|
//
|
|
// First waits for any modem lines a finite DCB timeout guards; if one
|
|
// stays low, nothing is queued and its CE_*TO bit is set. Single
|
|
// producer: only this side moves txHead, and the ISR only shrinks txCount,
|
|
// so the free space seen here can only grow. The copy runs with
|
|
// interrupts enabled; publishing the new count is the only guarded step.
|
|
// Sets CE_TXFULL if not everything fit. Returns the bytes queued.
|
|
// -----------------------------------------------------------------------
|
|
static uint16_t writeTx(PortStateT *port, const uint8_t FAR *src, uint16_t len) {
|
|
uint8_t FAR *buf;
|
|
uint16_t size;
|
|
uint16_t head;
|
|
uint16_t room;
|
|
uint16_t chunk;
|
|
uint16_t lineErrors;
|
|
|
|
lineErrors = waitTxLines(port);
|
|
if (lineErrors) {
|
|
addErrors(port, lineErrors);
|
|
return 0;
|
|
}
|
|
|
|
size = port->comDeb.txSize;
|
|
room = size - port->comDeb.txCount;
|
|
if (room > len) {
|
|
room = len;
|
|
}
|
|
|
|
if (room > 0) {
|
|
buf = port->txBuf.data;
|
|
head = port->comDeb.txHead;
|
|
chunk = size - head;
|
|
if (chunk > room) {
|
|
chunk = room;
|
|
}
|
|
_fmemcpy(buf + head, src, chunk);
|
|
if (chunk < room) {
|
|
_fmemcpy(buf, src + chunk, room - chunk);
|
|
head = room - chunk;
|
|
} else {
|
|
// head + chunk <= size, so this cannot overflow 16 bits
|
|
head += chunk;
|
|
if (head == size) {
|
|
head = 0;
|
|
}
|
|
}
|
|
port->comDeb.txHead = head;
|
|
}
|
|
|
|
_disable();
|
|
port->comDeb.txCount += room;
|
|
if (room < len) {
|
|
port->errorFlags |= CE_TXFULL;
|
|
}
|
|
startTx(port);
|
|
refreshNotify(port, 0);
|
|
_enable();
|
|
|
|
return room;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// LibMain - DLL entry point
|
|
//
|
|
// Static data is zero-filled by the loader, which is the closed state for
|
|
// every port.
|
|
// -----------------------------------------------------------------------
|
|
int FAR PASCAL LibMain(HANDLE hInstance, WORD wDataSeg, WORD wHeapSize, LPSTR lpCmdLine) {
|
|
(void)hInstance;
|
|
(void)wDataSeg;
|
|
(void)wHeapSize;
|
|
(void)lpCmdLine;
|
|
|
|
dbgStr("KPCOMM: LibMain\r\n");
|
|
return 1;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// WEP - DLL exit procedure
|
|
//
|
|
// Releases hardware without waiting for output: Windows is going away.
|
|
// Ports set to keep DTR leave it up, so a modem connection can outlive
|
|
// Windows itself.
|
|
// -----------------------------------------------------------------------
|
|
int FAR PASCAL _export WEP(int nParam) {
|
|
int16_t i;
|
|
|
|
(void)nParam;
|
|
|
|
for (i = 0; i < MAX_PORTS; i++) {
|
|
if (ports[i].isOpen) {
|
|
closePort(&ports[i]);
|
|
}
|
|
}
|
|
lptCloseAll();
|
|
|
|
return 1;
|
|
}
|