WinComm/drv/commdrv.c
Scott Duensing c8ccedf569 Change default FIFO trigger from 8 to 1, yield CPU when idle
Default RX FIFO trigger level of 8 (FCR_TRIG_8) caused exactly 8
characters to buffer in hardware before the ISR fired. Change default
to 1 (FCR_TRIG_1) for responsive single-character interrupts while
keeping the 16-byte FIFO enabled as a safety buffer. COMnRxTRIGGER
SYSTEM.INI key still allows override.

The PM_NOYIELD polling loop never yielded the CPU timeslice, starving
other Windows applications. Add Yield call when no serial data is
flowing so other apps get CPU time. During bulk data flow HasData
stays true and the loop runs at full speed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:44:14 -06:00

1695 lines
48 KiB
C

// commdrv.c - KPCOMM.DRV -- High-speed COMM.DRV replacement
//
// All exported COMM.DRV functions, ring buffer management, port
// initialization, 16550 FIFO detection, and flow control.
//
// Drop-in replacement for Windows 3.1 stock COMM.DRV with proper
// FIFO management for reliable operation at 57600 and 115200 baud.
#include "commdrv.h"
#include <dos.h>
#include <string.h>
// -----------------------------------------------------------------------
// Debug logging via OutputDebugString
// -----------------------------------------------------------------------
#define DBG_ENABLED 0
#if DBG_ENABLED
static void dbgStr(const char FAR *msg)
{
OutputDebugString(msg);
}
// Log a label and a 16-bit hex value: "label: 0xNNNN\r\n"
static void dbgHex16(const char FAR *label, uint16_t val)
{
static const char hex[] = "0123456789ABCDEF";
char buf[48];
int i;
for (i = 0; i < 40 && label[i]; i++) {
buf[i] = label[i];
}
buf[i++] = ':';
buf[i++] = ' ';
buf[i++] = '0';
buf[i++] = 'x';
buf[i++] = hex[(val >> 12) & 0xF];
buf[i++] = hex[(val >> 8) & 0xF];
buf[i++] = hex[(val >> 4) & 0xF];
buf[i++] = hex[val & 0xF];
buf[i++] = '\r';
buf[i++] = '\n';
buf[i] = '\0';
OutputDebugString(buf);
}
// Log a label and a signed 16-bit value: "label: -NNNN\r\n"
static void dbgInt16(const char FAR *label, int16_t val)
{
char buf[48];
int i;
uint16_t uv;
for (i = 0; i < 40 && label[i]; i++) {
buf[i] = label[i];
}
buf[i++] = ':';
buf[i++] = ' ';
if (val < 0) {
buf[i++] = '-';
uv = (uint16_t)(-(int)val);
} else {
uv = (uint16_t)val;
}
// Print decimal (up to 5 digits)
{
char digits[6];
int n = 0;
do {
digits[n++] = (char)('0' + (uv % 10));
uv /= 10;
} while (uv > 0);
while (n > 0) {
buf[i++] = digits[--n];
}
}
buf[i++] = '\r';
buf[i++] = '\n';
buf[i] = '\0';
OutputDebugString(buf);
}
// Log a FAR pointer as "label: SSSS:OOOO\r\n"
static void dbgFarPtr(const char FAR *label, void FAR *ptr)
{
static const char hex[] = "0123456789ABCDEF";
char buf[48];
int i;
uint16_t seg;
uint16_t off;
seg = _FP_SEG(ptr);
off = _FP_OFF(ptr);
for (i = 0; i < 32 && label[i]; i++) {
buf[i] = label[i];
}
buf[i++] = ':';
buf[i++] = ' ';
buf[i++] = hex[(seg >> 12) & 0xF];
buf[i++] = hex[(seg >> 8) & 0xF];
buf[i++] = hex[(seg >> 4) & 0xF];
buf[i++] = hex[seg & 0xF];
buf[i++] = ':';
buf[i++] = hex[(off >> 12) & 0xF];
buf[i++] = hex[(off >> 8) & 0xF];
buf[i++] = hex[(off >> 4) & 0xF];
buf[i++] = hex[off & 0xF];
buf[i++] = '\r';
buf[i++] = '\n';
buf[i] = '\0';
OutputDebugString(buf);
}
// Hex dump first 'count' bytes at a FAR pointer
static void dbgDump(const char FAR *label, void FAR *ptr, int count)
{
static const char hex[] = "0123456789ABCDEF";
char buf[80];
uint8_t FAR *p;
int i;
int bi;
p = (uint8_t FAR *)ptr;
// Print label
for (bi = 0; bi < 32 && label[bi]; bi++) {
buf[bi] = label[bi];
}
buf[bi++] = ':';
buf[bi++] = ' ';
for (i = 0; i < count && bi < 70; i++) {
buf[bi++] = hex[(p[i] >> 4) & 0xF];
buf[bi++] = hex[p[i] & 0xF];
buf[bi++] = ' ';
}
buf[bi++] = '\r';
buf[bi++] = '\n';
buf[bi] = '\0';
OutputDebugString(buf);
}
#else
#define dbgStr(msg) ((void)0)
#define dbgHex16(label, val) ((void)0)
#define dbgInt16(label, val) ((void)0)
#define dbgFarPtr(label, p) ((void)0)
#define dbgDump(l, p, n) ((void)0)
#endif
// -----------------------------------------------------------------------
// Prototypes
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export inicom(DCB FAR *dcb);
int16_t FAR PASCAL _export setcom(DCB FAR *dcb);
int16_t FAR PASCAL _export setque(int16_t commId, int16_t rxSize, int16_t txSize);
int16_t FAR PASCAL _export reccom(int16_t commId, void FAR *buf, int16_t len);
int16_t FAR PASCAL _export sndcom(int16_t commId, void FAR *buf, int16_t len);
int16_t FAR PASCAL _export ctx(int16_t commId, int16_t ch);
int16_t FAR PASCAL _export trmcom(int16_t commId);
int16_t FAR PASCAL _export stacom(int16_t commId, COMSTAT FAR *stat);
int32_t FAR PASCAL _export cevt(int16_t commId, int16_t evtMask);
uint16_t FAR PASCAL _export cevtget(int16_t commId, int16_t evtMask);
int16_t FAR PASCAL _export cextfcn(int16_t commId, int16_t func);
int16_t FAR PASCAL _export cflush(int16_t commId, int16_t queue);
int16_t FAR PASCAL _export csetbrk(int16_t commId);
int16_t FAR PASCAL _export cclrbrk(int16_t commId);
DCB FAR * FAR PASCAL _export getdcb(int16_t commId);
void FAR PASCAL _export suspendOpenCommPorts(void);
void FAR PASCAL _export reactivateOpenCommPorts(void);
int16_t FAR PASCAL _export commWriteString(int16_t commId, void FAR *buf, int16_t len);
int16_t FAR PASCAL _export readCommString(int16_t commId, void FAR *buf, int16_t len);
int16_t FAR PASCAL _export enableNotification(int16_t commId, HWND hwnd, int16_t rxThresh, int16_t txThresh);
int FAR PASCAL _export commNotifyWndProc(void);
int FAR PASCAL LibMain(HANDLE hInstance, WORD wDataSeg, WORD wHeapSize, LPSTR lpCmdLine);
int FAR PASCAL _export WEP(int nParam);
void applyBaudRate(PortStateT *port, uint16_t baud);
void applyLineParams(PortStateT *port, uint8_t byteSize, uint8_t parity, uint8_t stopBits);
static uint32_t cbrToBaud(uint16_t cbr);
int16_t detect16550(uint16_t baseAddr);
void enableFifo(PortStateT *port);
static void freeBuffers(PortStateT *port);
static int16_t initBuffers(PortStateT *port, uint16_t rxSz, uint16_t txSz);
static void initPortState(PortStateT *port, int16_t commId);
void primeTx(PortStateT *port);
static void readPortConfig(int16_t commId, uint16_t FAR *baseAddr, uint8_t FAR *irq);
static uint16_t readSystemIni(const char FAR *section, const char FAR *key, uint16_t defVal);
// -----------------------------------------------------------------------
// Port base addresses and IRQ defaults (overridden by SYSTEM.INI)
// -----------------------------------------------------------------------
static const uint16_t portBase[MAX_PORTS] = {
COM1_BASE, COM2_BASE, COM3_BASE, COM4_BASE
};
static const uint8_t portIrq[MAX_PORTS] = {
COM1_IRQ, COM2_IRQ, COM3_IRQ, COM4_IRQ
};
// -----------------------------------------------------------------------
// Global port state array -- one PortStateT per COM port.
// Accessed from both application context and ISR context.
// Segment address loaded into DS by ISR entry points (isr3/isr4).
// -----------------------------------------------------------------------
PortStateT ports[MAX_PORTS];
// -----------------------------------------------------------------------
// DLL instance handle (saved in LibMain for resource loading)
// -----------------------------------------------------------------------
static HANDLE ghInstance = NULL;
// ISR hit counter for diagnostics (incremented in isr4, wraps at 65535)
volatile uint16_t isrHitCount = 0;
// -----------------------------------------------------------------------
// applyBaudRate - Set the baud rate divisor on the UART
//
// The DCB BaudRate field is a 16-bit UINT. For rates that fit in 16 bits
// (up to 57600), the raw value is used directly. For higher rates like
// 115200, CBR_* index constants (0xFF00+) encode the rate. We detect
// these and translate via cbrToBaud().
// -----------------------------------------------------------------------
void applyBaudRate(PortStateT *port, uint16_t baud)
{
uint32_t actualBaud;
uint16_t divisor;
uint8_t lcr;
uint16_t base;
if (baud == 0) {
return;
}
// Translate CBR_* index constants to actual baud rate
if ((baud & 0xFF00) == 0xFF00) {
actualBaud = cbrToBaud(baud);
} else {
actualBaud = (uint32_t)baud;
}
if (actualBaud == 0) {
return;
}
base = port->baseAddr;
divisor = (uint16_t)(BAUD_DIVISOR_BASE / actualBaud);
// Guard: divisor 0 means the UART treats it as 65536, giving ~1.76 baud.
// This can happen when BuildCommDCB stores a raw truncated value for
// 115200 (e.g. 0xE101 = 57601) and a future rate exceeds 115200.
if (divisor == 0) {
divisor = 1;
}
// Set DLAB to access divisor latch
lcr = (uint8_t)_inp(base + UART_LCR);
_outp(base + UART_LCR, lcr | LCR_DLAB);
_outp(base + UART_DLL, (uint8_t)(divisor & 0xFF));
_outp(base + UART_DLM, (uint8_t)(divisor >> 8));
// Clear DLAB
_outp(base + UART_LCR, lcr);
port->baudRate = baud;
}
// -----------------------------------------------------------------------
// applyLineParams - Set word length, parity, and stop bits on UART
// -----------------------------------------------------------------------
void applyLineParams(PortStateT *port, uint8_t byteSize, uint8_t parity, uint8_t stopBits)
{
uint8_t lcr;
uint16_t base;
base = port->baseAddr;
lcr = 0;
// Word length
if (byteSize >= 5 && byteSize <= 8) {
lcr |= (byteSize - 5);
} else {
lcr |= LCR_WLS_8;
}
// Stop bits
if (stopBits == 2) {
lcr |= LCR_STB;
}
// Parity
switch (parity) {
case ODDPARITY:
lcr |= LCR_PEN;
break;
case EVENPARITY:
lcr |= LCR_PEN | LCR_EPS;
break;
case MARKPARITY:
lcr |= LCR_PEN | LCR_SPAR;
break;
case SPACEPARITY:
lcr |= LCR_PEN | LCR_EPS | LCR_SPAR;
break;
case NOPARITY:
default:
break;
}
dbgHex16("KPCOMM: applyLine byteSize", (uint16_t)byteSize);
dbgHex16("KPCOMM: applyLine parity", (uint16_t)parity);
dbgHex16("KPCOMM: applyLine stopBits", (uint16_t)stopBits);
dbgHex16("KPCOMM: applyLine LCR", (uint16_t)lcr);
_outp(base + UART_LCR, lcr);
port->byteSize = byteSize;
port->parity = parity;
port->stopBits = stopBits;
}
// -----------------------------------------------------------------------
// cbrToBaud - Translate CBR_* index constant to actual baud rate
//
// Returns baud rate as uint32_t (needed for 115200 which exceeds 16 bits).
// Returns 0 for unrecognized constants.
// -----------------------------------------------------------------------
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 CBR_115200:
return 115200UL;
default:
return 0UL;
}
}
// -----------------------------------------------------------------------
// cclrbrk - Clear break signal (ordinal 14)
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export cclrbrk(int16_t commId)
{
PortStateT *port;
uint8_t lcr;
dbgInt16("KPCOMM: cclrbrk Id", commId);
if (commId < 0 || commId >= MAX_PORTS) {
return -1;
}
port = &ports[commId];
if (!port->isOpen) {
return -1;
}
lcr = (uint8_t)_inp(port->baseAddr + UART_LCR);
_outp(port->baseAddr + UART_LCR, lcr & ~LCR_SBRK);
port->breakState = FALSE;
return 0;
}
// -----------------------------------------------------------------------
// cevt - Set event mask and return pointer to event word (ordinal 11)
//
// Returns a FAR pointer to the event word (packed into int32_t).
// This matches the stock COMM.DRV behavior -- the event word is a
// volatile uint16_t that accumulates event bits until cleared.
// -----------------------------------------------------------------------
int32_t FAR PASCAL _export cevt(int16_t commId, int16_t evtMask)
{
PortStateT *port;
uint16_t FAR *ptr;
int32_t retVal;
#if DBG_ENABLED
{
uint16_t ssSeg;
uint16_t bpVal;
_asm mov ssSeg, ss
_asm mov bpVal, bp
dbgHex16("KPCOMM: cevt SS", ssSeg);
dbgHex16("KPCOMM: cevt BP", bpVal);
dbgDump("KPCOMM: cevt stk", (void FAR *)((uint32_t)ssSeg << 16 | (bpVal + 6)), 16);
}
#endif
dbgInt16("KPCOMM: cevt Id", commId);
dbgHex16("KPCOMM: cevt mask", (uint16_t)evtMask);
if (commId < 0 || commId >= MAX_PORTS) {
return 0L;
}
port = &ports[commId];
if (!port->isOpen) {
dbgStr("KPCOMM: cevt not open\r\n");
return 0L;
}
port->comDeb.evtMask = (uint16_t)evtMask;
ptr = &port->comDeb.evtWord;
retVal = (int32_t)(void FAR *)ptr;
dbgHex16("KPCOMM: cevt retSeg", (uint16_t)(retVal >> 16));
dbgHex16("KPCOMM: cevt retOff", (uint16_t)(retVal & 0xFFFF));
dbgStr("KPCOMM: cevt OK\r\n");
return retVal;
}
// -----------------------------------------------------------------------
// cevtget - Read and clear event word (ordinal 12)
//
// Returns current event bits masked by evtMask, then clears those bits.
// -----------------------------------------------------------------------
uint16_t FAR PASCAL _export cevtget(int16_t commId, int16_t evtMask)
{
PortStateT *port;
uint16_t events;
dbgInt16("KPCOMM: cevtget Id", commId);
if (commId < 0 || commId >= MAX_PORTS) {
return 0;
}
port = &ports[commId];
if (!port->isOpen) {
return 0;
}
_disable();
events = port->comDeb.evtWord & (uint16_t)evtMask;
port->comDeb.evtWord &= ~(uint16_t)evtMask;
_enable();
return events;
}
// -----------------------------------------------------------------------
// cextfcn - Escape function for DTR/RTS/break control (ordinal 9)
//
// Note: 16-bit API returns 0 on success (unlike Win32 nonzero=success).
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export cextfcn(int16_t commId, int16_t func)
{
PortStateT *port;
uint8_t mcr;
uint16_t base;
dbgInt16("KPCOMM: cextfcn Id", commId);
dbgInt16("KPCOMM: cextfcn func", func);
if (commId < 0 || commId >= MAX_PORTS) {
return -1;
}
port = &ports[commId];
if (!port->isOpen) {
return -1;
}
base = port->baseAddr;
mcr = (uint8_t)_inp(base + UART_MCR);
switch (func) {
case SETDTR:
mcr |= MCR_DTR;
port->dtrState = TRUE;
break;
case CLRDTR:
mcr &= ~MCR_DTR;
port->dtrState = FALSE;
break;
case SETRTS:
mcr |= MCR_RTS;
port->rtsState = TRUE;
break;
case CLRRTS:
mcr &= ~MCR_RTS;
port->rtsState = FALSE;
break;
case ESC_SETBREAK:
return csetbrk(commId);
case ESC_CLRBREAK:
return cclrbrk(commId);
case RESETDEV:
// No-op for serial ports
break;
case SETXON:
// Resume TX as if XON received
_disable();
port->txStopped = FALSE;
if (port->txCount > 0) {
_outp(base + UART_IER, (uint8_t)(_inp(base + UART_IER) | IER_THRE));
}
_enable();
return 0;
case SETXOFF:
// Stop TX as if XOFF received
port->txStopped = TRUE;
return 0;
default:
return -1;
}
_outp(base + UART_MCR, mcr);
return 0;
}
// -----------------------------------------------------------------------
// cflush - Flush receive or transmit buffer (ordinal 10)
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export cflush(int16_t commId, int16_t queue)
{
PortStateT *port;
dbgInt16("KPCOMM: cflush Id", commId);
if (commId < 0 || commId >= MAX_PORTS) {
return -1;
}
port = &ports[commId];
if (!port->isOpen) {
return -1;
}
_disable();
if (queue == FLUSH_RX) {
port->rxHead = 0;
port->rxTail = 0;
port->rxCount = 0;
port->comDeb.qInHead = 0;
port->comDeb.qInTail = 0;
port->comDeb.qInCount = 0;
// Reset FIFO to clear hardware buffer too
if (port->is16550 && port->fifoEnabled) {
_outp(port->baseAddr + UART_FCR, FCR_ENABLE | FCR_RX_RESET | port->fifoTrigger);
}
} else {
port->txHead = 0;
port->txTail = 0;
port->txCount = 0;
port->comDeb.qOutHead = 0;
port->comDeb.qOutTail = 0;
port->comDeb.qOutCount = 0;
if (port->is16550 && port->fifoEnabled) {
_outp(port->baseAddr + UART_FCR, FCR_ENABLE | FCR_TX_RESET | port->fifoTrigger);
}
}
_enable();
return 0;
}
// -----------------------------------------------------------------------
// commWriteString - Write data (ordinal 19, alias for sndcom)
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export commWriteString(int16_t commId, void FAR *buf, int16_t len)
{
dbgInt16("KPCOMM: commWriteStr Id", commId);
return sndcom(commId, buf, len);
}
// -----------------------------------------------------------------------
// commNotifyWndProc - Ordinal 101 stub (undocumented, Win 3.1 compat)
//
// The stock COMM.DRV and CyberCom export an unnamed function at ordinal
// 101. Purpose is undocumented; may be an internal callback used by
// USER.EXE. CyberCom's implementation uses plain RETF (no params).
// Return 0 (not handled).
// -----------------------------------------------------------------------
int FAR PASCAL _export commNotifyWndProc(void)
{
dbgStr("KPCOMM: ord101 called\r\n");
return 0;
}
// -----------------------------------------------------------------------
// csetbrk - Assert break signal (ordinal 13)
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export csetbrk(int16_t commId)
{
PortStateT *port;
uint8_t lcr;
dbgInt16("KPCOMM: csetbrk Id", commId);
if (commId < 0 || commId >= MAX_PORTS) {
return -1;
}
port = &ports[commId];
if (!port->isOpen) {
return -1;
}
lcr = (uint8_t)_inp(port->baseAddr + UART_LCR);
_outp(port->baseAddr + UART_LCR, lcr | LCR_SBRK);
port->breakState = TRUE;
return 0;
}
// -----------------------------------------------------------------------
// ctx - Send single character with priority (ordinal 6)
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export ctx(int16_t commId, int16_t ch)
{
PortStateT *port;
dbgInt16("KPCOMM: ctx Id", commId);
if (commId < 0 || commId >= MAX_PORTS) {
return -1;
}
port = &ports[commId];
if (!port->isOpen) {
return -1;
}
_disable();
port->txImmediate = ch;
// Enable THRE interrupt to get it sent
_outp(port->baseAddr + UART_IER, (uint8_t)(_inp(port->baseAddr + UART_IER) | IER_THRE));
_enable();
return 0;
}
// -----------------------------------------------------------------------
// detect16550 - Test if the UART at baseAddr is a 16550 with working FIFOs
//
// Returns 1 if 16550A (working FIFO), 0 otherwise.
// -----------------------------------------------------------------------
int16_t detect16550(uint16_t baseAddr)
{
uint8_t iir;
// Try to enable FIFOs
_outp(baseAddr + UART_FCR, FCR_ENABLE);
// Read IIR -- bits 7:6 should be set if FIFOs are enabled
iir = (uint8_t)_inp(baseAddr + UART_IIR);
// Disable FIFOs for now (we'll re-enable properly later)
_outp(baseAddr + UART_FCR, 0);
// 16550A has both bits 7:6 set when FIFOs enabled
// Plain 16550 (broken FIFO) only sets bit 7
return ((iir & IIR_FIFO_MASK) == IIR_FIFO_MASK) ? 1 : 0;
}
// -----------------------------------------------------------------------
// enableFifo - Enable 16550 FIFOs with configurable trigger level
//
// Uses port->fifoTrigger (set from SYSTEM.INI COMnRxTRIGGER, default 8).
// Respects port->fifoEnabled (set from SYSTEM.INI COMnFIFO, default 1).
// -----------------------------------------------------------------------
void enableFifo(PortStateT *port)
{
if (!port->is16550 || !port->fifoEnabled) {
return;
}
// Enable + reset both FIFOs + configured RX trigger level
_outp(port->baseAddr + UART_FCR, FCR_ENABLE | FCR_RX_RESET | FCR_TX_RESET | port->fifoTrigger);
}
// -----------------------------------------------------------------------
// enableNotification - Register window for WM_COMMNOTIFY (ordinal 100)
//
// Retained as a no-op stub for API compatibility. Stores threshold
// values but nothing reads them -- polling replaces notifications.
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export enableNotification(int16_t commId, HWND hwnd, int16_t rxThresh, int16_t txThresh)
{
PortStateT *port;
if (commId < 0 || commId >= MAX_PORTS) {
return FALSE;
}
port = &ports[commId];
if (!port->isOpen) {
return FALSE;
}
port->hwndNotify = hwnd;
port->rxNotifyThresh = rxThresh;
port->txNotifyThresh = txThresh;
return TRUE;
}
// -----------------------------------------------------------------------
// freeBuffers - Free ring buffers for a port
// -----------------------------------------------------------------------
static void freeBuffers(PortStateT *port)
{
if (port->rxBufH) {
GlobalUnlock(port->rxBufH);
GlobalFree(port->rxBufH);
port->rxBufH = 0;
port->rxBuf = NULL;
}
if (port->txBufH) {
GlobalUnlock(port->txBufH);
GlobalFree(port->txBufH);
port->txBufH = 0;
port->txBuf = NULL;
}
}
// -----------------------------------------------------------------------
// getdcb - Return pointer to internal DCB (ordinal 15)
//
// Takes int16_t commId (2 bytes, RETF 2). Returns DCB FAR * in DX:AX.
// The caller (USER.EXE / COMMTASK.DLL) copies from this pointer.
// CyberCom confirms: RETF 2, return pointer.
// -----------------------------------------------------------------------
DCB FAR * FAR PASCAL _export getdcb(int16_t commId)
{
PortStateT *port;
dbgInt16("KPCOMM: getdcb Id", commId);
if (commId < 0 || commId >= MAX_PORTS) {
dbgStr("KPCOMM: getdcb bad id\r\n");
return NULL;
}
port = &ports[commId];
if (!port->isOpen) {
dbgStr("KPCOMM: getdcb not open\r\n");
return NULL;
}
dbgHex16("KPCOMM: getdcb isrHits", isrHitCount);
dbgStr("KPCOMM: getdcb OK\r\n");
return &port->dcb;
}
// -----------------------------------------------------------------------
// inicom - Open a COM port (ordinal 1)
//
// Opens the port specified in dcb->Id, allocates ring buffers, hooks ISR,
// enables interrupts, and applies DCB settings.
//
// rxBuf and rxSize are ignored (legacy params); we allocate our own buffers.
//
// Returns commId (0-3) on success, negative error code on failure.
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export inicom(DCB FAR *dcb)
{
int16_t commId;
PortStateT *port;
uint16_t rxBufSize;
uint16_t txBufSize;
uint8_t mcr;
dbgStr("KPCOMM: inicom enter\r\n");
// Dump raw stack to see actual parameters passed by caller
{
uint16_t bpVal;
uint16_t ssVal;
void FAR *stkPtr;
_asm mov bpVal, bp
_asm mov ssVal, ss
stkPtr = (void FAR *)((uint32_t)ssVal << 16 | (bpVal + 6));
dbgHex16("KPCOMM: inicom SS", ssVal);
dbgHex16("KPCOMM: inicom BP", bpVal);
dbgDump("KPCOMM: inicom stk", stkPtr, 24);
}
if (!dcb) {
dbgStr("KPCOMM: inicom NULL dcb\r\n");
return IE_DEFAULT;
}
dbgFarPtr("KPCOMM: inicom dcb", (void FAR *)dcb);
dbgDump("KPCOMM: inicom raw", (void FAR *)dcb, 16);
dbgHex16("KPCOMM: sizeof(DCB)", (uint16_t)sizeof(DCB));
commId = dcb->Id;
dbgInt16("KPCOMM: inicom Id", commId);
dbgHex16("KPCOMM: inicom BaudRate", dcb->BaudRate);
dbgHex16("KPCOMM: inicom ByteSize", (uint16_t)dcb->ByteSize);
dbgHex16("KPCOMM: inicom Parity", (uint16_t)dcb->Parity);
dbgHex16("KPCOMM: inicom StopBits", (uint16_t)dcb->StopBits);
if (commId < 0 || commId >= MAX_PORTS) {
dbgStr("KPCOMM: inicom IE_BADID\r\n");
return IE_BADID;
}
port = &ports[commId];
if (port->isOpen) {
dbgStr("KPCOMM: inicom IE_OPEN\r\n");
return IE_OPEN;
}
// Initialize port state
initPortState(port, commId);
dbgHex16("KPCOMM: inicom baseAddr", port->baseAddr);
dbgHex16("KPCOMM: inicom irq", (uint16_t)port->irq);
// Read buffer sizes from SYSTEM.INI if available (COMnBuffer key)
{
char bufKey[12];
bufKey[0] = 'C';
bufKey[1] = 'O';
bufKey[2] = 'M';
bufKey[3] = (char)('1' + commId);
bufKey[4] = 'B';
bufKey[5] = 'u';
bufKey[6] = 'f';
bufKey[7] = 'f';
bufKey[8] = 'e';
bufKey[9] = 'r';
bufKey[10] = '\0';
rxBufSize = readSystemIni("386Enh", bufKey, DEFAULT_RX_SIZE);
}
txBufSize = rxBufSize;
dbgHex16("KPCOMM: inicom rxBufSize", rxBufSize);
// Allocate ring buffers
if (initBuffers(port, rxBufSize, txBufSize) != 0) {
dbgStr("KPCOMM: inicom IE_MEMORY\r\n");
return IE_MEMORY;
}
dbgStr("KPCOMM: inicom buffers OK\r\n");
// Detect 16550 FIFO
port->is16550 = (uint8_t)detect16550(port->baseAddr);
dbgHex16("KPCOMM: inicom is16550", (uint16_t)port->is16550);
// Hook ISR
if (hookIsr(port) != 0) {
dbgStr("KPCOMM: inicom hookIsr FAIL\r\n");
freeBuffers(port);
return IE_HARDWARE;
}
dbgStr("KPCOMM: inicom ISR hooked\r\n");
port->isOpen = TRUE;
// Enable FIFOs if 16550 detected
enableFifo(port);
// Apply DCB settings
_fmemcpy(&port->dcb, dcb, sizeof(DCB));
applyBaudRate(port, dcb->BaudRate);
applyLineParams(port, dcb->ByteSize, dcb->Parity, dcb->StopBits);
// Set flow control from DCB
if (dcb->fOutxCtsFlow && dcb->fInX) {
port->hsMode = HS_BOTH;
} else if (dcb->fOutxCtsFlow) {
port->hsMode = HS_RTSCTS;
} else if (dcb->fInX) {
port->hsMode = HS_XONXOFF;
} else {
port->hsMode = HS_NONE;
}
port->xonChar = dcb->XonChar;
port->xoffChar = dcb->XoffChar;
port->xonLim = dcb->XonLim;
port->xoffLim = dcb->XoffLim;
// Raise DTR and RTS, enable OUT2 (master interrupt gate)
mcr = MCR_DTR | MCR_RTS | MCR_OUT2;
_outp(port->baseAddr + UART_MCR, mcr);
port->dtrState = TRUE;
port->rtsState = TRUE;
// Clear any pending status
_inp(port->baseAddr + UART_LSR);
port->comDeb.msrShadow = (uint8_t)_inp(port->baseAddr + UART_MSR);
_inp(port->baseAddr + UART_RBR);
// Populate ComDEB for third-party compatibility
port->comDeb.port = port->baseAddr;
port->comDeb.baudRate = port->baudRate;
port->comDeb.qInSize = port->rxSize;
port->comDeb.qOutSize = port->txSize;
port->comDeb.lcrShadow = (uint8_t)_inp(port->baseAddr + UART_LCR);
port->comDeb.mcrShadow = (uint8_t)_inp(port->baseAddr + UART_MCR);
// Enable receive and line status interrupts
_outp(port->baseAddr + UART_IER, IER_RDA | IER_LSI | IER_MSI);
dbgInt16("KPCOMM: inicom OK, ret", commId);
return commId;
}
// -----------------------------------------------------------------------
// initBuffers - Allocate ring buffers via GlobalAlloc
//
// Uses GMEM_FIXED so buffer addresses are stable at interrupt time
// without requiring GlobalLock. Saves handles in PortStateT for
// clean freeing (no GlobalHandle recovery hack).
// -----------------------------------------------------------------------
static int16_t initBuffers(PortStateT *port, uint16_t rxSz, uint16_t txSz)
{
HGLOBAL hRx;
HGLOBAL hTx;
hRx = GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, (DWORD)rxSz);
if (!hRx) {
return -1;
}
hTx = GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, (DWORD)txSz);
if (!hTx) {
GlobalFree(hRx);
return -1;
}
port->rxBufH = hRx;
port->rxBuf = (uint8_t FAR *)GlobalLock(hRx);
port->rxSize = rxSz;
port->rxHead = 0;
port->rxTail = 0;
port->rxCount = 0;
port->txBufH = hTx;
port->txBuf = (uint8_t FAR *)GlobalLock(hTx);
port->txSize = txSz;
port->txHead = 0;
port->txTail = 0;
port->txCount = 0;
return 0;
}
// -----------------------------------------------------------------------
// initPortState - Initialize a port state structure with defaults
//
// Reads COMnBase, COMnIRQ, COMnFIFO, and COMnRxTRIGGER from
// SYSTEM.INI [386Enh] to support non-standard port configurations.
// -----------------------------------------------------------------------
static void initPortState(PortStateT *port, int16_t commId)
{
uint16_t baseAddr;
uint8_t irq;
uint16_t fifoEnabled;
uint16_t rxTrigger;
_fmemset(port, 0, sizeof(PortStateT));
port->commId = commId;
// Read port address and IRQ from SYSTEM.INI (or use defaults)
readPortConfig(commId, &baseAddr, &irq);
port->baseAddr = baseAddr;
port->irq = irq;
// Read FIFO enable setting (default: enabled)
{
char fifoKey[10];
fifoKey[0] = 'C';
fifoKey[1] = 'O';
fifoKey[2] = 'M';
fifoKey[3] = (char)('1' + commId);
fifoKey[4] = 'F';
fifoKey[5] = 'I';
fifoKey[6] = 'F';
fifoKey[7] = 'O';
fifoKey[8] = '\0';
fifoEnabled = readSystemIni("386Enh", fifoKey, 1);
}
port->fifoEnabled = (uint8_t)(fifoEnabled ? 1 : 0);
// Read RX trigger level from SYSTEM.INI (default: 8)
{
char trigKey[16];
trigKey[0] = 'C';
trigKey[1] = 'O';
trigKey[2] = 'M';
trigKey[3] = (char)('1' + commId);
trigKey[4] = 'R';
trigKey[5] = 'x';
trigKey[6] = 'T';
trigKey[7] = 'R';
trigKey[8] = 'I';
trigKey[9] = 'G';
trigKey[10] = 'G';
trigKey[11] = 'E';
trigKey[12] = 'R';
trigKey[13] = '\0';
rxTrigger = readSystemIni("386Enh", trigKey, 1);
}
switch (rxTrigger) {
case 4:
port->fifoTrigger = FCR_TRIG_4;
break;
case 8:
port->fifoTrigger = FCR_TRIG_8;
break;
case 14:
port->fifoTrigger = FCR_TRIG_14;
break;
case 1:
default:
port->fifoTrigger = FCR_TRIG_1;
break;
}
port->xonChar = 0x11; // DC1
port->xoffChar = 0x13; // DC3
port->xonLim = DEFAULT_RX_SIZE / 4;
port->xoffLim = DEFAULT_RX_SIZE / 4;
port->txImmediate = -1;
port->rxNotifyThresh = -1;
port->txNotifyThresh = -1;
}
// -----------------------------------------------------------------------
// primeTx - Prime the transmitter by enabling THRE interrupt
//
// Called after writing data to the TX ring buffer to kick off
// transmission if it's not already running.
// -----------------------------------------------------------------------
void primeTx(PortStateT *port)
{
uint8_t ier;
ier = (uint8_t)_inp(port->baseAddr + UART_IER);
if (!(ier & IER_THRE)) {
_outp(port->baseAddr + UART_IER, ier | IER_THRE);
}
}
// -----------------------------------------------------------------------
// reactivateOpenCommPorts - Reactivate all ports after task switch (ordinal 18)
//
// Called by Windows when switching back to this VM.
// Restores full UART state: baud rate, line params (LCR), MCR, FIFOs,
// and re-enables interrupts. A DOS fullscreen app or VM switch may
// have reprogrammed the UART, so we must restore everything.
// -----------------------------------------------------------------------
void FAR PASCAL _export reactivateOpenCommPorts(void)
{
int16_t i;
PortStateT *port;
uint8_t mcr;
dbgStr("KPCOMM: reactivate\r\n");
for (i = 0; i < MAX_PORTS; i++) {
port = &ports[i];
if (!port->isOpen) {
continue;
}
// Restore baud rate (sets DLAB, writes divisor, clears DLAB)
applyBaudRate(port, port->baudRate);
// Restore line parameters (word length, parity, stop bits)
applyLineParams(port, port->byteSize, port->parity, port->stopBits);
// Restore MCR (DTR, RTS, OUT2)
mcr = MCR_OUT2;
if (port->dtrState) {
mcr |= MCR_DTR;
}
if (port->rtsState) {
mcr |= MCR_RTS;
}
_outp(port->baseAddr + UART_MCR, mcr);
// Re-enable FIFOs
enableFifo(port);
// Re-enable interrupts
_outp(port->baseAddr + UART_IER, IER_RDA | IER_LSI | IER_MSI);
if (port->txCount > 0) {
_outp(port->baseAddr + UART_IER, (uint8_t)(_inp(port->baseAddr + UART_IER) | IER_THRE));
}
// Unmask IRQ at PIC
{
uint8_t picMask = (uint8_t)_inp(PIC_DATA);
picMask &= ~port->irqMask;
_outp(PIC_DATA, picMask);
}
}
}
// -----------------------------------------------------------------------
// readCommString - Read data (ordinal 20, alias for reccom)
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export readCommString(int16_t commId, void FAR *buf, int16_t len)
{
dbgInt16("KPCOMM: readCommStr Id", commId);
return reccom(commId, buf, len);
}
// -----------------------------------------------------------------------
// readPortConfig - Read port base address and IRQ from SYSTEM.INI
//
// Checks [386Enh] for COMnBase (hex) and COMnIRQ overrides.
// Falls back to standard defaults if not present.
// -----------------------------------------------------------------------
static void readPortConfig(int16_t commId, uint16_t FAR *baseAddr, uint8_t FAR *irq)
{
char key[10];
char buf[16];
// Defaults
*baseAddr = portBase[commId];
*irq = portIrq[commId];
// Check COMnBase (e.g., COM3Base=3E8)
key[0] = 'C';
key[1] = 'O';
key[2] = 'M';
key[3] = (char)('1' + commId);
key[4] = 'B';
key[5] = 'a';
key[6] = 's';
key[7] = 'e';
key[8] = '\0';
GetPrivateProfileString("386Enh", key, "", buf, sizeof(buf), "SYSTEM.INI");
if (buf[0] != '\0') {
// Parse hex string manually (no strtoul in small model CRT)
uint16_t val = 0;
int16_t i = 0;
char ch;
while (buf[i] != '\0') {
ch = buf[i];
val <<= 4;
if (ch >= '0' && ch <= '9') {
val |= (uint16_t)(ch - '0');
} else if (ch >= 'A' && ch <= 'F') {
val |= (uint16_t)(ch - 'A' + 10);
} else if (ch >= 'a' && ch <= 'f') {
val |= (uint16_t)(ch - 'a' + 10);
}
i++;
}
if (val != 0) {
*baseAddr = val;
}
}
// Check COMnIRQ (e.g., COM3Irq=5)
key[4] = 'I';
key[5] = 'r';
key[6] = 'q';
key[7] = '\0';
{
uint16_t irqVal = readSystemIni("386Enh", key, (uint16_t)*irq);
if (irqVal > 0 && irqVal <= 7) {
*irq = (uint8_t)irqVal;
}
}
}
// -----------------------------------------------------------------------
// readSystemIni - Read an unsigned integer value from SYSTEM.INI
// -----------------------------------------------------------------------
static uint16_t readSystemIni(const char FAR *section, const char FAR *key, uint16_t defVal)
{
return (uint16_t)GetPrivateProfileInt(section, key, (int)defVal, "SYSTEM.INI");
}
// -----------------------------------------------------------------------
// reccom - Read from RX ring buffer (ordinal 4)
//
// Copies up to len bytes from the receive ring buffer into buf.
// Returns number of bytes read, or negative error code.
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export reccom(int16_t commId, void FAR *buf, int16_t len)
{
PortStateT *port;
uint8_t FAR *dst;
int16_t bytesRead;
dbgInt16("KPCOMM: reccom Id", commId);
if (commId < 0 || commId >= MAX_PORTS) {
return -1;
}
port = &ports[commId];
if (!port->isOpen) {
return -1;
}
dst = (uint8_t FAR *)buf;
bytesRead = 0;
_disable();
while (bytesRead < len && port->rxCount > 0) {
*dst++ = port->rxBuf[port->rxTail];
port->rxTail++;
if (port->rxTail >= port->rxSize) {
port->rxTail = 0;
}
port->rxCount--;
bytesRead++;
}
_enable();
// If flow control was asserted and buffer has drained, deassert
if (port->rxStopped && port->rxCount <= port->xonLim) {
_disable();
port->rxStopped = FALSE;
if (port->hsMode == HS_XONXOFF || port->hsMode == HS_BOTH) {
port->txImmediate = port->xonChar;
_outp(port->baseAddr + UART_IER, (uint8_t)(_inp(port->baseAddr + UART_IER) | IER_THRE));
}
if (port->hsMode == HS_RTSCTS || port->hsMode == HS_BOTH) {
_outp(port->baseAddr + UART_MCR, (uint8_t)(_inp(port->baseAddr + UART_MCR) | MCR_RTS));
}
_enable();
}
// Sync ComDEB queue counts
port->comDeb.qInCount = port->rxCount;
port->comDeb.qInTail = port->rxTail;
return bytesRead;
}
// -----------------------------------------------------------------------
// setcom - Apply DCB settings to hardware (ordinal 2)
//
// Takes DCB FAR * (4 bytes, RETF 4). CommId is in dcb->Id.
// Both USER.EXE and COMMTASK.DLL push 4 bytes for this call.
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export setcom(DCB FAR *dcb)
{
int16_t commId;
PortStateT *port;
dbgStr("KPCOMM: setcom enter\r\n");
if (!dcb) {
dbgStr("KPCOMM: setcom NULL dcb\r\n");
return -1;
}
commId = dcb->Id;
dbgInt16("KPCOMM: setcom Id", commId);
dbgHex16("KPCOMM: setcom BaudRate", dcb->BaudRate);
dbgHex16("KPCOMM: setcom ByteSize", (uint16_t)dcb->ByteSize);
dbgHex16("KPCOMM: setcom Parity", (uint16_t)dcb->Parity);
dbgHex16("KPCOMM: setcom StopBits", (uint16_t)dcb->StopBits);
if (commId < 0 || commId >= MAX_PORTS) {
dbgStr("KPCOMM: setcom IE_BADID\r\n");
return IE_BADID;
}
port = &ports[commId];
if (!port->isOpen) {
dbgStr("KPCOMM: setcom port not open\r\n");
return -1;
}
// Save DCB
_fmemcpy(&port->dcb, dcb, sizeof(DCB));
// Apply hardware settings
_disable();
applyBaudRate(port, dcb->BaudRate);
applyLineParams(port, dcb->ByteSize, dcb->Parity, dcb->StopBits);
// Update flow control
if (dcb->fOutxCtsFlow && dcb->fInX) {
port->hsMode = HS_BOTH;
} else if (dcb->fOutxCtsFlow) {
port->hsMode = HS_RTSCTS;
} else if (dcb->fInX) {
port->hsMode = HS_XONXOFF;
} else {
port->hsMode = HS_NONE;
}
port->xonChar = dcb->XonChar;
port->xoffChar = dcb->XoffChar;
port->xonLim = dcb->XonLim;
port->xoffLim = dcb->XoffLim;
// Sync ComDEB shadow fields
port->comDeb.baudRate = port->baudRate;
port->comDeb.lcrShadow = (uint8_t)_inp(port->baseAddr + UART_LCR);
port->comDeb.mcrShadow = (uint8_t)_inp(port->baseAddr + UART_MCR);
_enable();
dbgStr("KPCOMM: setcom OK\r\n");
return 0;
}
// -----------------------------------------------------------------------
// setque - Resize RX/TX buffers (ordinal 3)
//
// Caller passes port ID and requested RX/TX buffer sizes.
// We manage our own GlobalAlloc'd buffers.
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export setque(int16_t commId, int16_t rxSz, int16_t txSz)
{
PortStateT *port;
dbgInt16("KPCOMM: setque Id", commId);
dbgInt16("KPCOMM: setque rxSz", rxSz);
dbgInt16("KPCOMM: setque txSz", txSz);
if (commId < 0 || commId >= MAX_PORTS) {
dbgStr("KPCOMM: setque bad id\r\n");
return -1;
}
port = &ports[commId];
if (!port->isOpen) {
dbgStr("KPCOMM: setque port not open\r\n");
return -1;
}
// Allocate new buffers BEFORE disabling interrupts.
// GlobalAlloc/GlobalFree can deadlock under CLI if the heap
// needs compaction or DPMI services.
{
HGLOBAL newRxH;
HGLOBAL newTxH;
uint8_t FAR *newRxBuf;
uint8_t FAR *newTxBuf;
HGLOBAL oldRxH;
HGLOBAL oldTxH;
newRxH = GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, (DWORD)(uint16_t)rxSz);
if (!newRxH) {
dbgStr("KPCOMM: setque IE_MEMORY\r\n");
return IE_MEMORY;
}
newTxH = GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, (DWORD)(uint16_t)txSz);
if (!newTxH) {
GlobalFree(newRxH);
dbgStr("KPCOMM: setque IE_MEMORY\r\n");
return IE_MEMORY;
}
newRxBuf = (uint8_t FAR *)GlobalLock(newRxH);
newTxBuf = (uint8_t FAR *)GlobalLock(newTxH);
// Swap buffers with interrupts disabled -- ISR sees
// consistent pointers at all times
_disable();
oldRxH = port->rxBufH;
oldTxH = port->txBufH;
port->rxBufH = newRxH;
port->rxBuf = newRxBuf;
port->rxSize = (uint16_t)rxSz;
port->rxHead = 0;
port->rxTail = 0;
port->rxCount = 0;
port->txBufH = newTxH;
port->txBuf = newTxBuf;
port->txSize = (uint16_t)txSz;
port->txHead = 0;
port->txTail = 0;
port->txCount = 0;
port->comDeb.qInSize = (uint16_t)rxSz;
port->comDeb.qInCount = 0;
port->comDeb.qInHead = 0;
port->comDeb.qInTail = 0;
port->comDeb.qOutSize = (uint16_t)txSz;
port->comDeb.qOutCount = 0;
port->comDeb.qOutHead = 0;
port->comDeb.qOutTail = 0;
_enable();
// Free old buffers AFTER re-enabling interrupts
if (oldRxH) {
GlobalUnlock(oldRxH);
GlobalFree(oldRxH);
}
if (oldTxH) {
GlobalUnlock(oldTxH);
GlobalFree(oldTxH);
}
}
dbgStr("KPCOMM: setque OK\r\n");
return 0;
}
// -----------------------------------------------------------------------
// sndcom - Write to TX ring buffer and prime transmitter (ordinal 5)
//
// Copies up to len bytes from buf into the transmit ring buffer.
// Returns number of bytes written, or negative error code.
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export sndcom(int16_t commId, void FAR *buf, int16_t len)
{
PortStateT *port;
uint8_t FAR *src;
int16_t bytesWritten;
dbgInt16("KPCOMM: sndcom Id", commId);
if (commId < 0 || commId >= MAX_PORTS) {
return -1;
}
port = &ports[commId];
if (!port->isOpen) {
return -1;
}
src = (uint8_t FAR *)buf;
bytesWritten = 0;
_disable();
while (bytesWritten < len) {
if (port->txCount >= port->txSize) {
port->errorFlags |= CE_TXFULL;
break;
}
port->txBuf[port->txHead] = *src++;
port->txHead++;
if (port->txHead >= port->txSize) {
port->txHead = 0;
}
port->txCount++;
bytesWritten++;
}
_enable();
// Sync ComDEB queue counts
port->comDeb.qOutCount = port->txCount;
port->comDeb.qOutHead = port->txHead;
// Prime transmitter if we wrote anything
if (bytesWritten > 0 && !port->txStopped) {
primeTx(port);
}
return bytesWritten;
}
// -----------------------------------------------------------------------
// stacom - Return and clear error flags plus COMSTAT (ordinal 8)
//
// Returns accumulated error flags (CE_*) and fills the COMSTAT
// structure with current buffer counts and status.
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export stacom(int16_t commId, COMSTAT FAR *stat)
{
PortStateT *port;
int16_t errors;
dbgInt16("KPCOMM: stacom Id", commId);
if (commId < 0 || commId >= MAX_PORTS) {
dbgStr("KPCOMM: stacom bad id\r\n");
return -1;
}
port = &ports[commId];
if (!port->isOpen) {
dbgStr("KPCOMM: stacom not open\r\n");
return -1;
}
_disable();
errors = (int16_t)port->errorFlags;
port->errorFlags = 0;
_enable();
if (stat) {
stat->status = 0;
if (port->txStopped) {
stat->status |= CSTF_XOFFHOLD;
}
stat->cbInQue = port->rxCount;
stat->cbOutQue = port->txCount;
}
dbgHex16("KPCOMM: stacom errors", (uint16_t)errors);
return errors;
}
// -----------------------------------------------------------------------
// suspendOpenCommPorts - Suspend all ports during task switch (ordinal 17)
//
// Called by Windows when switching away from this VM.
// Disables interrupts on all open ports to prevent ISR problems.
// -----------------------------------------------------------------------
void FAR PASCAL _export suspendOpenCommPorts(void)
{
int16_t i;
PortStateT *port;
dbgStr("KPCOMM: suspend\r\n");
for (i = 0; i < MAX_PORTS; i++) {
port = &ports[i];
if (!port->isOpen) {
continue;
}
// Disable UART interrupts
_outp(port->baseAddr + UART_IER, 0);
// Mask IRQ at PIC
{
uint8_t picMask = (uint8_t)_inp(PIC_DATA);
picMask |= port->irqMask;
_outp(PIC_DATA, picMask);
}
}
}
// -----------------------------------------------------------------------
// trmcom - Close a COM port (ordinal 7)
//
// Disables interrupts, drops DTR/RTS, unhooks ISR, frees buffers.
// Returns 0 on success.
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export trmcom(int16_t commId)
{
PortStateT *port;
dbgInt16("KPCOMM: trmcom Id", commId);
if (commId < 0 || commId >= MAX_PORTS) {
return -1;
}
port = &ports[commId];
if (!port->isOpen) {
dbgStr("KPCOMM: trmcom port not open\r\n");
return -1;
}
// Disable UART interrupts
_outp(port->baseAddr + UART_IER, 0);
// Reset FIFOs to drain pending data, then disable
if (port->is16550) {
_outp(port->baseAddr + UART_FCR, FCR_ENABLE | FCR_RX_RESET | FCR_TX_RESET);
_outp(port->baseAddr + UART_FCR, 0);
}
// Drop DTR, RTS, OUT2
_outp(port->baseAddr + UART_MCR, 0);
// Drain all pending UART conditions so the hardware is clean
// for the next open. Each read clears the corresponding
// interrupt source inside the UART.
_inp(port->baseAddr + UART_LSR);
_inp(port->baseAddr + UART_MSR);
_inp(port->baseAddr + UART_RBR);
_inp(port->baseAddr + UART_IIR);
// Unhook ISR
unhookIsr(port);
// Free buffers
freeBuffers(port);
// Clear notification
port->hwndNotify = NULL;
port->isOpen = FALSE;
dbgHex16("KPCOMM: trmcom isrHits", isrHitCount);
dbgStr("KPCOMM: trmcom OK\r\n");
return 0;
}
// -----------------------------------------------------------------------
// LibMain - DLL entry point
// -----------------------------------------------------------------------
int FAR PASCAL LibMain(HANDLE hInstance, WORD wDataSeg, WORD wHeapSize, LPSTR lpCmdLine)
{
int16_t i;
(void)wDataSeg;
(void)lpCmdLine;
ghInstance = hInstance;
dbgStr("KPCOMM: LibMain\r\n");
if (wHeapSize > 0) {
UnlockData(0);
}
// Zero all port states
for (i = 0; i < MAX_PORTS; i++) {
_fmemset(&ports[i], 0, sizeof(PortStateT));
ports[i].txImmediate = -1;
}
return 1;
}
// -----------------------------------------------------------------------
// WEP - DLL exit procedure
// -----------------------------------------------------------------------
int FAR PASCAL _export WEP(int nParam)
{
int16_t i;
(void)nParam;
// Close any ports still open
for (i = 0; i < MAX_PORTS; i++) {
if (ports[i].isOpen) {
trmcom(i);
}
}
return 1;
}