1302 lines
35 KiB
C
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;
|
|
}
|