// 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 #include // ----------------------------------------------------------------------- // 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; }