WinComm/drv/commdrv.c
2026-02-23 20:53:02 -06:00

1302 lines
35 KiB
C

// commdrv.c - 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>
// -----------------------------------------------------------------------
// Prototypes
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export inicom(DCB FAR *dcb, char FAR *rxBuf, int16_t rxSize);
int16_t FAR PASCAL _export setcom(DCB FAR *dcb);
int16_t FAR PASCAL _export setque(int16_t commId, char FAR *rxBuf, int16_t rxSize, char FAR *txBuf, 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);
int16_t FAR PASCAL _export getdcb(int16_t commId, DCB FAR *dcb);
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 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 int16_t 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 *baseAddr, uint8_t *irq);
static uint16_t readSystemIni(const char FAR *section, const char FAR *key, uint16_t defVal);
// -----------------------------------------------------------------------
// Port base addresses and IRQ table
// -----------------------------------------------------------------------
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
// -----------------------------------------------------------------------
PortStateT ports[MAX_PORTS];
// -----------------------------------------------------------------------
// Global instance handle
// -----------------------------------------------------------------------
static HANDLE ghInstance = NULL;
// -----------------------------------------------------------------------
// 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);
// 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;
}
_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;
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;
if (commId < 0 || commId >= MAX_PORTS) {
return 0L;
}
port = &ports[commId];
if (!port->isOpen) {
return 0L;
}
port->evtMask = (uint16_t)evtMask;
ptr = &port->evtWord;
return (int32_t)(void FAR *)ptr;
}
// -----------------------------------------------------------------------
// 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;
if (commId < 0 || commId >= MAX_PORTS) {
return 0;
}
port = &ports[commId];
if (!port->isOpen) {
return 0;
}
_disable();
events = port->evtWord & (uint16_t)evtMask;
port->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;
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 SETBREAK:
return csetbrk(commId);
case 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;
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;
// 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;
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)
{
return sndcom(commId, buf, len);
}
// -----------------------------------------------------------------------
// csetbrk - Assert break signal (ordinal 13)
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export csetbrk(int16_t commId)
{
PortStateT *port;
uint8_t lcr;
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;
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)
// -----------------------------------------------------------------------
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 int16_t freeBuffers(PortStateT *port)
{
HGLOBAL hMem;
if (port->rxBuf) {
hMem = (HGLOBAL)LOWORD(GlobalHandle(SELECTOROF(port->rxBuf)));
if (hMem) {
GlobalUnlock(hMem);
GlobalFree(hMem);
}
port->rxBuf = NULL;
}
if (port->txBuf) {
hMem = (HGLOBAL)LOWORD(GlobalHandle(SELECTOROF(port->txBuf)));
if (hMem) {
GlobalUnlock(hMem);
GlobalFree(hMem);
}
port->txBuf = NULL;
}
return 0;
}
// -----------------------------------------------------------------------
// getdcb - Copy current DCB settings (ordinal 15)
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export getdcb(int16_t commId, DCB FAR *dcb)
{
PortStateT *port;
if (commId < 0 || commId >= MAX_PORTS) {
return -1;
}
port = &ports[commId];
if (!port->isOpen) {
return -1;
}
_fmemcpy(dcb, &port->dcb, sizeof(DCB));
return 0;
}
// -----------------------------------------------------------------------
// 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, char FAR *rxBuf, int16_t rxSize)
{
int16_t commId;
PortStateT *port;
uint16_t rxBufSize;
uint16_t txBufSize;
uint8_t mcr;
(void)rxBuf;
(void)rxSize;
if (!dcb) {
return IE_DEFAULT;
}
commId = dcb->Id;
if (commId < 0 || commId >= MAX_PORTS) {
return IE_BADID;
}
port = &ports[commId];
if (port->isOpen) {
return IE_OPEN;
}
// Initialize port state
initPortState(port, commId);
// 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;
// Allocate ring buffers
if (initBuffers(port, rxBufSize, txBufSize) != 0) {
return IE_MEMORY;
}
// Detect 16550 FIFO
port->is16550 = (uint8_t)detect16550(port->baseAddr);
// Hook ISR
if (hookIsr(port) != 0) {
freeBuffers(port);
return IE_HARDWARE;
}
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);
_inp(port->baseAddr + UART_MSR);
_inp(port->baseAddr + UART_RBR);
// Enable receive and line status interrupts
_outp(port->baseAddr + UART_IER, IER_RDA | IER_LSI | IER_MSI);
return commId;
}
// -----------------------------------------------------------------------
// initBuffers - Allocate ring buffers via GlobalAlloc
// -----------------------------------------------------------------------
static int16_t initBuffers(PortStateT *port, uint16_t rxSz, uint16_t txSz)
{
HGLOBAL hRx;
HGLOBAL hTx;
hRx = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, (DWORD)rxSz);
if (!hRx) {
return -1;
}
hTx = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, (DWORD)txSz);
if (!hTx) {
GlobalFree(hRx);
return -1;
}
port->rxBuf = (uint8_t FAR *)GlobalLock(hRx);
port->rxSize = rxSz;
port->rxHead = 0;
port->rxTail = 0;
port->rxCount = 0;
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, 8);
}
switch (rxTrigger) {
case 1:
port->fifoTrigger = FCR_TRIG_1;
break;
case 4:
port->fifoTrigger = FCR_TRIG_4;
break;
case 14:
port->fifoTrigger = FCR_TRIG_14;
break;
case 8:
default:
port->fifoTrigger = FCR_TRIG_8;
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.
// Re-enables interrupts and restores MCR state.
// -----------------------------------------------------------------------
void FAR PASCAL _export reactivateOpenCommPorts(void)
{
int16_t i;
PortStateT *port;
uint8_t mcr;
for (i = 0; i < MAX_PORTS; i++) {
port = &ports[i];
if (!port->isOpen) {
continue;
}
// 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)
{
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 *baseAddr, uint8_t *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;
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();
}
return bytesRead;
}
// -----------------------------------------------------------------------
// setcom - Apply DCB settings to hardware (ordinal 2)
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export setcom(DCB FAR *dcb)
{
int16_t commId;
PortStateT *port;
if (!dcb) {
return -1;
}
commId = dcb->Id;
if (commId < 0 || commId >= MAX_PORTS) {
return IE_BADID;
}
port = &ports[commId];
if (!port->isOpen) {
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;
_enable();
return 0;
}
// -----------------------------------------------------------------------
// setque - Resize RX/TX buffers (ordinal 3)
//
// The stock driver ignores the buffer pointers passed by the caller
// and manages its own. We do the same: free old buffers, alloc new.
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export setque(int16_t commId, char FAR *rxBufArg, int16_t rxSz, char FAR *txBufArg, int16_t txSz)
{
PortStateT *port;
(void)rxBufArg;
(void)txBufArg;
if (commId < 0 || commId >= MAX_PORTS) {
return -1;
}
port = &ports[commId];
if (!port->isOpen) {
return -1;
}
_disable();
freeBuffers(port);
if (initBuffers(port, (uint16_t)rxSz, (uint16_t)txSz) != 0) {
_enable();
return IE_MEMORY;
}
_enable();
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;
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();
// 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;
if (commId < 0 || commId >= MAX_PORTS) {
return -1;
}
port = &ports[commId];
if (!port->isOpen) {
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;
}
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;
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;
if (commId < 0 || commId >= MAX_PORTS) {
return -1;
}
port = &ports[commId];
if (!port->isOpen) {
return -1;
}
// Disable UART interrupts
_outp(port->baseAddr + UART_IER, 0);
// Disable FIFOs
if (port->is16550) {
_outp(port->baseAddr + UART_FCR, 0);
}
// Drop DTR, RTS, OUT2
_outp(port->baseAddr + UART_MCR, 0);
// Unhook ISR
unhookIsr(port);
// Free buffers
freeBuffers(port);
// Clear notification
port->hwndNotify = NULL;
port->isOpen = FALSE;
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;
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;
}