WinComm/drv/commdrv.c
Scott Duensing d68405550d Fix COMM.DRV calling conventions and add ComDEB compatibility
Resolve GPFs in both USER.EXE and COMMTASK.DLL by correcting RETF
sizes for SETCOM and GETDCB, confirmed by disassembling CyberCom.DRV.
Add stock-compatible ComDEB structure so third-party code (ProComm
COMMTASK.DLL) can safely access internal fields at known offsets per
Microsoft KB Q101417.

COMM.DRV changes:
- SETCOM (ord 2): RETF 4, takes DCB FAR * only (not commId + DCB*)
- GETDCB (ord 15): RETF 2, takes commId, returns DCB FAR * in DX:AX
- Add 40-byte ComDebT matching stock COMM.DRV internal layout
  (evtWord at +0, MSR shadow at +35, queue counts at +8/+18)
- cevt returns pointer to ComDebT for third-party compatibility
- Sync ComDEB fields in ISR dispatch, reccom, sndcom, cflush, setque
- Move WEP to ordinal 16, add ordinal 101 stub (match stock/CyberCom)
- Default DBG_ENABLED to 0 (set to 1 to re-enable debug logging)

VBX control fixes:
- Fix SETBREAK/CLRBREAK constants (use numeric 8/9, not undefined macros)
- Fix serialEnableNotify return type (BOOL, not int16_t)
- Fix mscomm.h to work with RC compiler (#ifndef RC_INVOKED)
- Fix vbapi.h USHORT typedef and MODEL.flWndStyle type
- Add vbapi.lib dependency to makefile

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 20:03:34 -06:00

1641 lines
46 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>
// -----------------------------------------------------------------------
// 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 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 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 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;
// -----------------------------------------------------------------------
// Dynamically resolved PostMessage
//
// comm.drv is loaded from [boot] before USER.EXE, so we can't have a
// static import for PostMessage. Resolve it on first use via
// GetProcAddress(GetModuleHandle("USER"), "PostMessage").
// -----------------------------------------------------------------------
PostMessageProcT pfnPostMessage = NULL;
// ISR hit counter for diagnostics
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);
// 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;
dbgInt16("COMM.DRV: 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("COMM.DRV: cevt SS", ssSeg);
dbgHex16("COMM.DRV: cevt BP", bpVal);
dbgDump("COMM.DRV: cevt stk", (void FAR *)((uint32_t)ssSeg << 16 | (bpVal + 6)), 16);
}
#endif
dbgInt16("COMM.DRV: cevt Id", commId);
dbgHex16("COMM.DRV: cevt mask", (uint16_t)evtMask);
if (commId < 0 || commId >= MAX_PORTS) {
return 0L;
}
port = &ports[commId];
if (!port->isOpen) {
dbgStr("COMM.DRV: cevt not open\r\n");
return 0L;
}
port->comDeb.evtMask = (uint16_t)evtMask;
ptr = &port->comDeb.evtWord;
retVal = (int32_t)(void FAR *)ptr;
dbgHex16("COMM.DRV: cevt retSeg", (uint16_t)(retVal >> 16));
dbgHex16("COMM.DRV: cevt retOff", (uint16_t)(retVal & 0xFFFF));
dbgStr("COMM.DRV: 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("COMM.DRV: 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("COMM.DRV: cextfcn Id", commId);
dbgInt16("COMM.DRV: 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("COMM.DRV: 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("COMM.DRV: 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("COMM.DRV: 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("COMM.DRV: 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("COMM.DRV: 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)
// -----------------------------------------------------------------------
int16_t FAR PASCAL _export enableNotification(int16_t commId, HWND hwnd, int16_t rxThresh, int16_t txThresh)
{
PortStateT *port;
dbgInt16("COMM.DRV: enableNotif Id", commId);
dbgHex16("COMM.DRV: enableNotif hwnd", (uint16_t)hwnd);
dbgInt16("COMM.DRV: enableNotif rxTh", rxThresh);
dbgInt16("COMM.DRV: enableNotif txTh", txThresh);
if (commId < 0 || commId >= MAX_PORTS) {
return FALSE;
}
port = &ports[commId];
if (!port->isOpen) {
dbgStr("COMM.DRV: enableNotif not open\r\n");
return FALSE;
}
// Lazily resolve PostMessage from USER.EXE
// (not available at boot when comm.drv is loaded from [boot])
if (!pfnPostMessage) {
HMODULE hUser = GetModuleHandle("USER");
if (hUser) {
pfnPostMessage = (PostMessageProcT)GetProcAddress(hUser, "PostMessage");
}
}
port->hwndNotify = hwnd;
port->rxNotifyThresh = rxThresh;
port->txNotifyThresh = txThresh;
dbgStr("COMM.DRV: enableNotif OK\r\n");
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 - 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("COMM.DRV: getdcb Id", commId);
if (commId < 0 || commId >= MAX_PORTS) {
dbgStr("COMM.DRV: getdcb bad id\r\n");
return NULL;
}
port = &ports[commId];
if (!port->isOpen) {
dbgStr("COMM.DRV: getdcb not open\r\n");
return NULL;
}
dbgHex16("COMM.DRV: getdcb isrHits", isrHitCount);
dbgStr("COMM.DRV: 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("COMM.DRV: 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("COMM.DRV: inicom SS", ssVal);
dbgHex16("COMM.DRV: inicom BP", bpVal);
dbgDump("COMM.DRV: inicom stk", stkPtr, 24);
}
if (!dcb) {
dbgStr("COMM.DRV: inicom NULL dcb\r\n");
return IE_DEFAULT;
}
dbgFarPtr("COMM.DRV: inicom dcb", (void FAR *)dcb);
dbgDump("COMM.DRV: inicom raw", (void FAR *)dcb, 16);
dbgHex16("COMM.DRV: sizeof(DCB)", (uint16_t)sizeof(DCB));
commId = dcb->Id;
dbgInt16("COMM.DRV: inicom Id", commId);
dbgHex16("COMM.DRV: inicom BaudRate", dcb->BaudRate);
dbgHex16("COMM.DRV: inicom ByteSize", (uint16_t)dcb->ByteSize);
dbgHex16("COMM.DRV: inicom Parity", (uint16_t)dcb->Parity);
dbgHex16("COMM.DRV: inicom StopBits", (uint16_t)dcb->StopBits);
if (commId < 0 || commId >= MAX_PORTS) {
dbgStr("COMM.DRV: inicom IE_BADID\r\n");
return IE_BADID;
}
port = &ports[commId];
if (port->isOpen) {
dbgStr("COMM.DRV: inicom IE_OPEN\r\n");
return IE_OPEN;
}
// Initialize port state
initPortState(port, commId);
dbgHex16("COMM.DRV: inicom baseAddr", port->baseAddr);
dbgHex16("COMM.DRV: 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("COMM.DRV: inicom rxBufSize", rxBufSize);
// Allocate ring buffers
if (initBuffers(port, rxBufSize, txBufSize) != 0) {
dbgStr("COMM.DRV: inicom IE_MEMORY\r\n");
return IE_MEMORY;
}
dbgStr("COMM.DRV: inicom buffers OK\r\n");
// Detect 16550 FIFO
port->is16550 = (uint8_t)detect16550(port->baseAddr);
dbgHex16("COMM.DRV: inicom is16550", (uint16_t)port->is16550);
// Hook ISR
if (hookIsr(port) != 0) {
dbgStr("COMM.DRV: inicom hookIsr FAIL\r\n");
freeBuffers(port);
return IE_HARDWARE;
}
dbgStr("COMM.DRV: 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;
// Enable receive and line status interrupts
_outp(port->baseAddr + UART_IER, IER_RDA | IER_LSI | IER_MSI);
dbgInt16("COMM.DRV: inicom OK, ret", commId);
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;
dbgStr("COMM.DRV: reactivate\r\n");
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)
{
dbgInt16("COMM.DRV: 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("COMM.DRV: 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("COMM.DRV: setcom enter\r\n");
if (!dcb) {
dbgStr("COMM.DRV: setcom NULL dcb\r\n");
return -1;
}
commId = dcb->Id;
dbgInt16("COMM.DRV: setcom Id", commId);
dbgHex16("COMM.DRV: setcom BaudRate", dcb->BaudRate);
dbgHex16("COMM.DRV: setcom ByteSize", (uint16_t)dcb->ByteSize);
dbgHex16("COMM.DRV: setcom Parity", (uint16_t)dcb->Parity);
dbgHex16("COMM.DRV: setcom StopBits", (uint16_t)dcb->StopBits);
if (commId < 0 || commId >= MAX_PORTS) {
dbgStr("COMM.DRV: setcom IE_BADID\r\n");
return IE_BADID;
}
port = &ports[commId];
if (!port->isOpen) {
dbgStr("COMM.DRV: 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("COMM.DRV: 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("COMM.DRV: setque Id", commId);
dbgInt16("COMM.DRV: setque rxSz", rxSz);
dbgInt16("COMM.DRV: setque txSz", txSz);
if (commId < 0 || commId >= MAX_PORTS) {
dbgStr("COMM.DRV: setque bad id\r\n");
return -1;
}
port = &ports[commId];
if (!port->isOpen) {
dbgStr("COMM.DRV: setque port not open\r\n");
return -1;
}
_disable();
freeBuffers(port);
if (initBuffers(port, (uint16_t)rxSz, (uint16_t)txSz) != 0) {
_enable();
dbgStr("COMM.DRV: setque IE_MEMORY\r\n");
return IE_MEMORY;
}
// Sync ComDEB queue sizes
port->comDeb.qInSize = port->rxSize;
port->comDeb.qInCount = 0;
port->comDeb.qInHead = 0;
port->comDeb.qInTail = 0;
port->comDeb.qOutSize = port->txSize;
port->comDeb.qOutCount = 0;
port->comDeb.qOutHead = 0;
port->comDeb.qOutTail = 0;
_enable();
dbgStr("COMM.DRV: 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("COMM.DRV: 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("COMM.DRV: stacom Id", commId);
if (commId < 0 || commId >= MAX_PORTS) {
dbgStr("COMM.DRV: stacom bad id\r\n");
return -1;
}
port = &ports[commId];
if (!port->isOpen) {
dbgStr("COMM.DRV: 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("COMM.DRV: 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("COMM.DRV: 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("COMM.DRV: trmcom Id", commId);
if (commId < 0 || commId >= MAX_PORTS) {
return -1;
}
port = &ports[commId];
if (!port->isOpen) {
dbgStr("COMM.DRV: trmcom port not open\r\n");
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;
dbgHex16("COMM.DRV: trmcom isrHits", isrHitCount);
dbgStr("COMM.DRV: 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("COMM.DRV: 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;
}