// commdrv.c - KPCOMM.DRV -- High-speed COMM.DRV replacement // // All exported COMM.DRV functions, ring buffer management, port // initialization, 16550 FIFO detection, and flow control. // // Drop-in replacement for Windows 3.1 stock COMM.DRV with proper // FIFO management for reliable operation at 57600 and 115200 baud. #include "commdrv.h" #include #include // ----------------------------------------------------------------------- // 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("KPCOMM: cclrbrk Id", commId); if (commId < 0 || commId >= MAX_PORTS) { return -1; } port = &ports[commId]; if (!port->isOpen) { return -1; } lcr = (uint8_t)_inp(port->baseAddr + UART_LCR); _outp(port->baseAddr + UART_LCR, lcr & ~LCR_SBRK); port->breakState = FALSE; return 0; } // ----------------------------------------------------------------------- // cevt - Set event mask and return pointer to event word (ordinal 11) // // Returns a FAR pointer to the event word (packed into int32_t). // This matches the stock COMM.DRV behavior -- the event word is a // volatile uint16_t that accumulates event bits until cleared. // ----------------------------------------------------------------------- int32_t FAR PASCAL _export cevt(int16_t commId, int16_t evtMask) { PortStateT *port; uint16_t FAR *ptr; int32_t retVal; #if DBG_ENABLED { uint16_t ssSeg; uint16_t bpVal; _asm mov ssSeg, ss _asm mov bpVal, bp dbgHex16("KPCOMM: cevt SS", ssSeg); dbgHex16("KPCOMM: cevt BP", bpVal); dbgDump("KPCOMM: cevt stk", (void FAR *)((uint32_t)ssSeg << 16 | (bpVal + 6)), 16); } #endif dbgInt16("KPCOMM: cevt Id", commId); dbgHex16("KPCOMM: cevt mask", (uint16_t)evtMask); if (commId < 0 || commId >= MAX_PORTS) { return 0L; } port = &ports[commId]; if (!port->isOpen) { dbgStr("KPCOMM: cevt not open\r\n"); return 0L; } port->comDeb.evtMask = (uint16_t)evtMask; ptr = &port->comDeb.evtWord; retVal = (int32_t)(void FAR *)ptr; dbgHex16("KPCOMM: cevt retSeg", (uint16_t)(retVal >> 16)); dbgHex16("KPCOMM: cevt retOff", (uint16_t)(retVal & 0xFFFF)); dbgStr("KPCOMM: cevt OK\r\n"); return retVal; } // ----------------------------------------------------------------------- // cevtget - Read and clear event word (ordinal 12) // // Returns current event bits masked by evtMask, then clears those bits. // ----------------------------------------------------------------------- uint16_t FAR PASCAL _export cevtget(int16_t commId, int16_t evtMask) { PortStateT *port; uint16_t events; dbgInt16("KPCOMM: cevtget Id", commId); if (commId < 0 || commId >= MAX_PORTS) { return 0; } port = &ports[commId]; if (!port->isOpen) { return 0; } _disable(); events = port->comDeb.evtWord & (uint16_t)evtMask; port->comDeb.evtWord &= ~(uint16_t)evtMask; _enable(); return events; } // ----------------------------------------------------------------------- // cextfcn - Escape function for DTR/RTS/break control (ordinal 9) // // Note: 16-bit API returns 0 on success (unlike Win32 nonzero=success). // ----------------------------------------------------------------------- int16_t FAR PASCAL _export cextfcn(int16_t commId, int16_t func) { PortStateT *port; uint8_t mcr; uint16_t base; dbgInt16("KPCOMM: cextfcn Id", commId); dbgInt16("KPCOMM: cextfcn func", func); if (commId < 0 || commId >= MAX_PORTS) { return -1; } port = &ports[commId]; if (!port->isOpen) { return -1; } base = port->baseAddr; mcr = (uint8_t)_inp(base + UART_MCR); switch (func) { case SETDTR: mcr |= MCR_DTR; port->dtrState = TRUE; break; case CLRDTR: mcr &= ~MCR_DTR; port->dtrState = FALSE; break; case SETRTS: mcr |= MCR_RTS; port->rtsState = TRUE; break; case CLRRTS: mcr &= ~MCR_RTS; port->rtsState = FALSE; break; case ESC_SETBREAK: return csetbrk(commId); case ESC_CLRBREAK: return cclrbrk(commId); case RESETDEV: // No-op for serial ports break; case SETXON: // Resume TX as if XON received _disable(); port->txStopped = FALSE; if (port->txCount > 0) { _outp(base + UART_IER, (uint8_t)(_inp(base + UART_IER) | IER_THRE)); } _enable(); return 0; case SETXOFF: // Stop TX as if XOFF received port->txStopped = TRUE; return 0; default: return -1; } _outp(base + UART_MCR, mcr); return 0; } // ----------------------------------------------------------------------- // cflush - Flush receive or transmit buffer (ordinal 10) // ----------------------------------------------------------------------- int16_t FAR PASCAL _export cflush(int16_t commId, int16_t queue) { PortStateT *port; dbgInt16("KPCOMM: cflush Id", commId); if (commId < 0 || commId >= MAX_PORTS) { return -1; } port = &ports[commId]; if (!port->isOpen) { return -1; } _disable(); if (queue == FLUSH_RX) { port->rxHead = 0; port->rxTail = 0; port->rxCount = 0; port->comDeb.qInHead = 0; port->comDeb.qInTail = 0; port->comDeb.qInCount = 0; // Reset FIFO to clear hardware buffer too if (port->is16550 && port->fifoEnabled) { _outp(port->baseAddr + UART_FCR, FCR_ENABLE | FCR_RX_RESET | port->fifoTrigger); } } else { port->txHead = 0; port->txTail = 0; port->txCount = 0; port->comDeb.qOutHead = 0; port->comDeb.qOutTail = 0; port->comDeb.qOutCount = 0; if (port->is16550 && port->fifoEnabled) { _outp(port->baseAddr + UART_FCR, FCR_ENABLE | FCR_TX_RESET | port->fifoTrigger); } } _enable(); return 0; } // ----------------------------------------------------------------------- // commWriteString - Write data (ordinal 19, alias for sndcom) // ----------------------------------------------------------------------- int16_t FAR PASCAL _export commWriteString(int16_t commId, void FAR *buf, int16_t len) { dbgInt16("KPCOMM: commWriteStr Id", commId); return sndcom(commId, buf, len); } // ----------------------------------------------------------------------- // commNotifyWndProc - Ordinal 101 stub (undocumented, Win 3.1 compat) // // The stock COMM.DRV and CyberCom export an unnamed function at ordinal // 101. Purpose is undocumented; may be an internal callback used by // USER.EXE. CyberCom's implementation uses plain RETF (no params). // Return 0 (not handled). // ----------------------------------------------------------------------- int FAR PASCAL _export commNotifyWndProc(void) { dbgStr("KPCOMM: ord101 called\r\n"); return 0; } // ----------------------------------------------------------------------- // csetbrk - Assert break signal (ordinal 13) // ----------------------------------------------------------------------- int16_t FAR PASCAL _export csetbrk(int16_t commId) { PortStateT *port; uint8_t lcr; dbgInt16("KPCOMM: csetbrk Id", commId); if (commId < 0 || commId >= MAX_PORTS) { return -1; } port = &ports[commId]; if (!port->isOpen) { return -1; } lcr = (uint8_t)_inp(port->baseAddr + UART_LCR); _outp(port->baseAddr + UART_LCR, lcr | LCR_SBRK); port->breakState = TRUE; return 0; } // ----------------------------------------------------------------------- // ctx - Send single character with priority (ordinal 6) // ----------------------------------------------------------------------- int16_t FAR PASCAL _export ctx(int16_t commId, int16_t ch) { PortStateT *port; dbgInt16("KPCOMM: ctx Id", commId); if (commId < 0 || commId >= MAX_PORTS) { return -1; } port = &ports[commId]; if (!port->isOpen) { return -1; } _disable(); port->txImmediate = ch; // Enable THRE interrupt to get it sent _outp(port->baseAddr + UART_IER, (uint8_t)(_inp(port->baseAddr + UART_IER) | IER_THRE)); _enable(); return 0; } // ----------------------------------------------------------------------- // detect16550 - Test if the UART at baseAddr is a 16550 with working FIFOs // // Returns 1 if 16550A (working FIFO), 0 otherwise. // ----------------------------------------------------------------------- int16_t detect16550(uint16_t baseAddr) { uint8_t iir; // Try to enable FIFOs _outp(baseAddr + UART_FCR, FCR_ENABLE); // Read IIR -- bits 7:6 should be set if FIFOs are enabled iir = (uint8_t)_inp(baseAddr + UART_IIR); // Disable FIFOs for now (we'll re-enable properly later) _outp(baseAddr + UART_FCR, 0); // 16550A has both bits 7:6 set when FIFOs enabled // Plain 16550 (broken FIFO) only sets bit 7 return ((iir & IIR_FIFO_MASK) == IIR_FIFO_MASK) ? 1 : 0; } // ----------------------------------------------------------------------- // enableFifo - Enable 16550 FIFOs with configurable trigger level // // Uses port->fifoTrigger (set from SYSTEM.INI COMnRxTRIGGER, default 8). // Respects port->fifoEnabled (set from SYSTEM.INI COMnFIFO, default 1). // ----------------------------------------------------------------------- void enableFifo(PortStateT *port) { if (!port->is16550 || !port->fifoEnabled) { return; } // Enable + reset both FIFOs + configured RX trigger level _outp(port->baseAddr + UART_FCR, FCR_ENABLE | FCR_RX_RESET | FCR_TX_RESET | port->fifoTrigger); } // ----------------------------------------------------------------------- // enableNotification - Register window for WM_COMMNOTIFY (ordinal 100) // ----------------------------------------------------------------------- int16_t FAR PASCAL _export enableNotification(int16_t commId, HWND hwnd, int16_t rxThresh, int16_t txThresh) { PortStateT *port; dbgInt16("KPCOMM: enableNotif Id", commId); dbgHex16("KPCOMM: enableNotif hwnd", (uint16_t)hwnd); dbgInt16("KPCOMM: enableNotif rxTh", rxThresh); dbgInt16("KPCOMM: enableNotif txTh", txThresh); if (commId < 0 || commId >= MAX_PORTS) { return FALSE; } port = &ports[commId]; if (!port->isOpen) { dbgStr("KPCOMM: 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("KPCOMM: 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("KPCOMM: getdcb Id", commId); if (commId < 0 || commId >= MAX_PORTS) { dbgStr("KPCOMM: getdcb bad id\r\n"); return NULL; } port = &ports[commId]; if (!port->isOpen) { dbgStr("KPCOMM: getdcb not open\r\n"); return NULL; } dbgHex16("KPCOMM: getdcb isrHits", isrHitCount); dbgStr("KPCOMM: getdcb OK\r\n"); return &port->dcb; } // ----------------------------------------------------------------------- // inicom - Open a COM port (ordinal 1) // // Opens the port specified in dcb->Id, allocates ring buffers, hooks ISR, // enables interrupts, and applies DCB settings. // // rxBuf and rxSize are ignored (legacy params); we allocate our own buffers. // // Returns commId (0-3) on success, negative error code on failure. // ----------------------------------------------------------------------- int16_t FAR PASCAL _export inicom(DCB FAR *dcb) { int16_t commId; PortStateT *port; uint16_t rxBufSize; uint16_t txBufSize; uint8_t mcr; dbgStr("KPCOMM: inicom enter\r\n"); // Dump raw stack to see actual parameters passed by caller { uint16_t bpVal; uint16_t ssVal; void FAR *stkPtr; _asm mov bpVal, bp _asm mov ssVal, ss stkPtr = (void FAR *)((uint32_t)ssVal << 16 | (bpVal + 6)); dbgHex16("KPCOMM: inicom SS", ssVal); dbgHex16("KPCOMM: inicom BP", bpVal); dbgDump("KPCOMM: inicom stk", stkPtr, 24); } if (!dcb) { dbgStr("KPCOMM: inicom NULL dcb\r\n"); return IE_DEFAULT; } dbgFarPtr("KPCOMM: inicom dcb", (void FAR *)dcb); dbgDump("KPCOMM: inicom raw", (void FAR *)dcb, 16); dbgHex16("KPCOMM: sizeof(DCB)", (uint16_t)sizeof(DCB)); commId = dcb->Id; dbgInt16("KPCOMM: inicom Id", commId); dbgHex16("KPCOMM: inicom BaudRate", dcb->BaudRate); dbgHex16("KPCOMM: inicom ByteSize", (uint16_t)dcb->ByteSize); dbgHex16("KPCOMM: inicom Parity", (uint16_t)dcb->Parity); dbgHex16("KPCOMM: inicom StopBits", (uint16_t)dcb->StopBits); if (commId < 0 || commId >= MAX_PORTS) { dbgStr("KPCOMM: inicom IE_BADID\r\n"); return IE_BADID; } port = &ports[commId]; if (port->isOpen) { dbgStr("KPCOMM: inicom IE_OPEN\r\n"); return IE_OPEN; } // Initialize port state initPortState(port, commId); dbgHex16("KPCOMM: inicom baseAddr", port->baseAddr); dbgHex16("KPCOMM: inicom irq", (uint16_t)port->irq); // Read buffer sizes from SYSTEM.INI if available (COMnBuffer key) { char bufKey[12]; bufKey[0] = 'C'; bufKey[1] = 'O'; bufKey[2] = 'M'; bufKey[3] = (char)('1' + commId); bufKey[4] = 'B'; bufKey[5] = 'u'; bufKey[6] = 'f'; bufKey[7] = 'f'; bufKey[8] = 'e'; bufKey[9] = 'r'; bufKey[10] = '\0'; rxBufSize = readSystemIni("386Enh", bufKey, DEFAULT_RX_SIZE); } txBufSize = rxBufSize; dbgHex16("KPCOMM: inicom rxBufSize", rxBufSize); // Allocate ring buffers if (initBuffers(port, rxBufSize, txBufSize) != 0) { dbgStr("KPCOMM: inicom IE_MEMORY\r\n"); return IE_MEMORY; } dbgStr("KPCOMM: inicom buffers OK\r\n"); // Detect 16550 FIFO port->is16550 = (uint8_t)detect16550(port->baseAddr); dbgHex16("KPCOMM: inicom is16550", (uint16_t)port->is16550); // Hook ISR if (hookIsr(port) != 0) { dbgStr("KPCOMM: inicom hookIsr FAIL\r\n"); freeBuffers(port); return IE_HARDWARE; } dbgStr("KPCOMM: inicom ISR hooked\r\n"); port->isOpen = TRUE; // Enable FIFOs if 16550 detected enableFifo(port); // Apply DCB settings _fmemcpy(&port->dcb, dcb, sizeof(DCB)); applyBaudRate(port, dcb->BaudRate); applyLineParams(port, dcb->ByteSize, dcb->Parity, dcb->StopBits); // Set flow control from DCB if (dcb->fOutxCtsFlow && dcb->fInX) { port->hsMode = HS_BOTH; } else if (dcb->fOutxCtsFlow) { port->hsMode = HS_RTSCTS; } else if (dcb->fInX) { port->hsMode = HS_XONXOFF; } else { port->hsMode = HS_NONE; } port->xonChar = dcb->XonChar; port->xoffChar = dcb->XoffChar; port->xonLim = dcb->XonLim; port->xoffLim = dcb->XoffLim; // Raise DTR and RTS, enable OUT2 (master interrupt gate) mcr = MCR_DTR | MCR_RTS | MCR_OUT2; _outp(port->baseAddr + UART_MCR, mcr); port->dtrState = TRUE; port->rtsState = TRUE; // Clear any pending status _inp(port->baseAddr + UART_LSR); port->comDeb.msrShadow = (uint8_t)_inp(port->baseAddr + UART_MSR); _inp(port->baseAddr + UART_RBR); // Populate ComDEB for third-party compatibility port->comDeb.port = port->baseAddr; port->comDeb.baudRate = port->baudRate; port->comDeb.qInSize = port->rxSize; port->comDeb.qOutSize = port->txSize; // Enable receive and line status interrupts _outp(port->baseAddr + UART_IER, IER_RDA | IER_LSI | IER_MSI); dbgInt16("KPCOMM: inicom OK, ret", commId); return commId; } // ----------------------------------------------------------------------- // initBuffers - Allocate ring buffers via GlobalAlloc // ----------------------------------------------------------------------- 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("KPCOMM: 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("KPCOMM: readCommStr Id", commId); return reccom(commId, buf, len); } // ----------------------------------------------------------------------- // readPortConfig - Read port base address and IRQ from SYSTEM.INI // // Checks [386Enh] for COMnBase (hex) and COMnIRQ overrides. // Falls back to standard defaults if not present. // ----------------------------------------------------------------------- static void readPortConfig(int16_t commId, uint16_t FAR *baseAddr, uint8_t FAR *irq) { char key[10]; char buf[16]; // Defaults *baseAddr = portBase[commId]; *irq = portIrq[commId]; // Check COMnBase (e.g., COM3Base=3E8) key[0] = 'C'; key[1] = 'O'; key[2] = 'M'; key[3] = (char)('1' + commId); key[4] = 'B'; key[5] = 'a'; key[6] = 's'; key[7] = 'e'; key[8] = '\0'; GetPrivateProfileString("386Enh", key, "", buf, sizeof(buf), "SYSTEM.INI"); if (buf[0] != '\0') { // Parse hex string manually (no strtoul in small model CRT) uint16_t val = 0; int16_t i = 0; char ch; while (buf[i] != '\0') { ch = buf[i]; val <<= 4; if (ch >= '0' && ch <= '9') { val |= (uint16_t)(ch - '0'); } else if (ch >= 'A' && ch <= 'F') { val |= (uint16_t)(ch - 'A' + 10); } else if (ch >= 'a' && ch <= 'f') { val |= (uint16_t)(ch - 'a' + 10); } i++; } if (val != 0) { *baseAddr = val; } } // Check COMnIRQ (e.g., COM3Irq=5) key[4] = 'I'; key[5] = 'r'; key[6] = 'q'; key[7] = '\0'; { uint16_t irqVal = readSystemIni("386Enh", key, (uint16_t)*irq); if (irqVal > 0 && irqVal <= 7) { *irq = (uint8_t)irqVal; } } } // ----------------------------------------------------------------------- // readSystemIni - Read an unsigned integer value from SYSTEM.INI // ----------------------------------------------------------------------- static uint16_t readSystemIni(const char FAR *section, const char FAR *key, uint16_t defVal) { return (uint16_t)GetPrivateProfileInt(section, key, (int)defVal, "SYSTEM.INI"); } // ----------------------------------------------------------------------- // reccom - Read from RX ring buffer (ordinal 4) // // Copies up to len bytes from the receive ring buffer into buf. // Returns number of bytes read, or negative error code. // ----------------------------------------------------------------------- int16_t FAR PASCAL _export reccom(int16_t commId, void FAR *buf, int16_t len) { PortStateT *port; uint8_t FAR *dst; int16_t bytesRead; dbgInt16("KPCOMM: reccom Id", commId); if (commId < 0 || commId >= MAX_PORTS) { return -1; } port = &ports[commId]; if (!port->isOpen) { return -1; } dst = (uint8_t FAR *)buf; bytesRead = 0; _disable(); while (bytesRead < len && port->rxCount > 0) { *dst++ = port->rxBuf[port->rxTail]; port->rxTail++; if (port->rxTail >= port->rxSize) { port->rxTail = 0; } port->rxCount--; bytesRead++; } _enable(); // If flow control was asserted and buffer has drained, deassert if (port->rxStopped && port->rxCount <= port->xonLim) { _disable(); port->rxStopped = FALSE; if (port->hsMode == HS_XONXOFF || port->hsMode == HS_BOTH) { port->txImmediate = port->xonChar; _outp(port->baseAddr + UART_IER, (uint8_t)(_inp(port->baseAddr + UART_IER) | IER_THRE)); } if (port->hsMode == HS_RTSCTS || port->hsMode == HS_BOTH) { _outp(port->baseAddr + UART_MCR, (uint8_t)(_inp(port->baseAddr + UART_MCR) | MCR_RTS)); } _enable(); } // Sync ComDEB queue counts port->comDeb.qInCount = port->rxCount; port->comDeb.qInTail = port->rxTail; return bytesRead; } // ----------------------------------------------------------------------- // setcom - Apply DCB settings to hardware (ordinal 2) // // Takes DCB FAR * (4 bytes, RETF 4). CommId is in dcb->Id. // Both USER.EXE and COMMTASK.DLL push 4 bytes for this call. // ----------------------------------------------------------------------- int16_t FAR PASCAL _export setcom(DCB FAR *dcb) { int16_t commId; PortStateT *port; dbgStr("KPCOMM: setcom enter\r\n"); if (!dcb) { dbgStr("KPCOMM: setcom NULL dcb\r\n"); return -1; } commId = dcb->Id; dbgInt16("KPCOMM: setcom Id", commId); dbgHex16("KPCOMM: setcom BaudRate", dcb->BaudRate); dbgHex16("KPCOMM: setcom ByteSize", (uint16_t)dcb->ByteSize); dbgHex16("KPCOMM: setcom Parity", (uint16_t)dcb->Parity); dbgHex16("KPCOMM: setcom StopBits", (uint16_t)dcb->StopBits); if (commId < 0 || commId >= MAX_PORTS) { dbgStr("KPCOMM: setcom IE_BADID\r\n"); return IE_BADID; } port = &ports[commId]; if (!port->isOpen) { dbgStr("KPCOMM: setcom port not open\r\n"); return -1; } // Save DCB _fmemcpy(&port->dcb, dcb, sizeof(DCB)); // Apply hardware settings _disable(); applyBaudRate(port, dcb->BaudRate); applyLineParams(port, dcb->ByteSize, dcb->Parity, dcb->StopBits); // Update flow control if (dcb->fOutxCtsFlow && dcb->fInX) { port->hsMode = HS_BOTH; } else if (dcb->fOutxCtsFlow) { port->hsMode = HS_RTSCTS; } else if (dcb->fInX) { port->hsMode = HS_XONXOFF; } else { port->hsMode = HS_NONE; } port->xonChar = dcb->XonChar; port->xoffChar = dcb->XoffChar; port->xonLim = dcb->XonLim; port->xoffLim = dcb->XoffLim; // Sync ComDEB shadow fields port->comDeb.baudRate = port->baudRate; port->comDeb.lcrShadow = (uint8_t)_inp(port->baseAddr + UART_LCR); port->comDeb.mcrShadow = (uint8_t)_inp(port->baseAddr + UART_MCR); _enable(); dbgStr("KPCOMM: setcom OK\r\n"); return 0; } // ----------------------------------------------------------------------- // setque - Resize RX/TX buffers (ordinal 3) // // Caller passes port ID and requested RX/TX buffer sizes. // We manage our own GlobalAlloc'd buffers. // ----------------------------------------------------------------------- int16_t FAR PASCAL _export setque(int16_t commId, int16_t rxSz, int16_t txSz) { PortStateT *port; dbgInt16("KPCOMM: setque Id", commId); dbgInt16("KPCOMM: setque rxSz", rxSz); dbgInt16("KPCOMM: setque txSz", txSz); if (commId < 0 || commId >= MAX_PORTS) { dbgStr("KPCOMM: setque bad id\r\n"); return -1; } port = &ports[commId]; if (!port->isOpen) { dbgStr("KPCOMM: setque port not open\r\n"); return -1; } _disable(); freeBuffers(port); if (initBuffers(port, (uint16_t)rxSz, (uint16_t)txSz) != 0) { _enable(); dbgStr("KPCOMM: 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("KPCOMM: setque OK\r\n"); return 0; } // ----------------------------------------------------------------------- // sndcom - Write to TX ring buffer and prime transmitter (ordinal 5) // // Copies up to len bytes from buf into the transmit ring buffer. // Returns number of bytes written, or negative error code. // ----------------------------------------------------------------------- int16_t FAR PASCAL _export sndcom(int16_t commId, void FAR *buf, int16_t len) { PortStateT *port; uint8_t FAR *src; int16_t bytesWritten; dbgInt16("KPCOMM: sndcom Id", commId); if (commId < 0 || commId >= MAX_PORTS) { return -1; } port = &ports[commId]; if (!port->isOpen) { return -1; } src = (uint8_t FAR *)buf; bytesWritten = 0; _disable(); while (bytesWritten < len) { if (port->txCount >= port->txSize) { port->errorFlags |= CE_TXFULL; break; } port->txBuf[port->txHead] = *src++; port->txHead++; if (port->txHead >= port->txSize) { port->txHead = 0; } port->txCount++; bytesWritten++; } _enable(); // Sync ComDEB queue counts port->comDeb.qOutCount = port->txCount; port->comDeb.qOutHead = port->txHead; // Prime transmitter if we wrote anything if (bytesWritten > 0 && !port->txStopped) { primeTx(port); } return bytesWritten; } // ----------------------------------------------------------------------- // stacom - Return and clear error flags plus COMSTAT (ordinal 8) // // Returns accumulated error flags (CE_*) and fills the COMSTAT // structure with current buffer counts and status. // ----------------------------------------------------------------------- int16_t FAR PASCAL _export stacom(int16_t commId, COMSTAT FAR *stat) { PortStateT *port; int16_t errors; dbgInt16("KPCOMM: stacom Id", commId); if (commId < 0 || commId >= MAX_PORTS) { dbgStr("KPCOMM: stacom bad id\r\n"); return -1; } port = &ports[commId]; if (!port->isOpen) { dbgStr("KPCOMM: stacom not open\r\n"); return -1; } _disable(); errors = (int16_t)port->errorFlags; port->errorFlags = 0; _enable(); if (stat) { stat->status = 0; if (port->txStopped) { stat->status |= CSTF_XOFFHOLD; } stat->cbInQue = port->rxCount; stat->cbOutQue = port->txCount; } dbgHex16("KPCOMM: stacom errors", (uint16_t)errors); return errors; } // ----------------------------------------------------------------------- // suspendOpenCommPorts - Suspend all ports during task switch (ordinal 17) // // Called by Windows when switching away from this VM. // Disables interrupts on all open ports to prevent ISR problems. // ----------------------------------------------------------------------- void FAR PASCAL _export suspendOpenCommPorts(void) { int16_t i; PortStateT *port; dbgStr("KPCOMM: suspend\r\n"); for (i = 0; i < MAX_PORTS; i++) { port = &ports[i]; if (!port->isOpen) { continue; } // Disable UART interrupts _outp(port->baseAddr + UART_IER, 0); // Mask IRQ at PIC { uint8_t picMask = (uint8_t)_inp(PIC_DATA); picMask |= port->irqMask; _outp(PIC_DATA, picMask); } } } // ----------------------------------------------------------------------- // trmcom - Close a COM port (ordinal 7) // // Disables interrupts, drops DTR/RTS, unhooks ISR, frees buffers. // Returns 0 on success. // ----------------------------------------------------------------------- int16_t FAR PASCAL _export trmcom(int16_t commId) { PortStateT *port; dbgInt16("KPCOMM: trmcom Id", commId); if (commId < 0 || commId >= MAX_PORTS) { return -1; } port = &ports[commId]; if (!port->isOpen) { dbgStr("KPCOMM: trmcom port not open\r\n"); return -1; } // Disable UART interrupts _outp(port->baseAddr + UART_IER, 0); // 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("KPCOMM: trmcom isrHits", isrHitCount); dbgStr("KPCOMM: trmcom OK\r\n"); return 0; } // ----------------------------------------------------------------------- // LibMain - DLL entry point // ----------------------------------------------------------------------- int FAR PASCAL LibMain(HANDLE hInstance, WORD wDataSeg, WORD wHeapSize, LPSTR lpCmdLine) { int16_t i; (void)wDataSeg; (void)lpCmdLine; ghInstance = hInstance; dbgStr("KPCOMM: LibMain\r\n"); if (wHeapSize > 0) { UnlockData(0); } // Zero all port states for (i = 0; i < MAX_PORTS; i++) { _fmemset(&ports[i], 0, sizeof(PortStateT)); ports[i].txImmediate = -1; } return 1; } // ----------------------------------------------------------------------- // WEP - DLL exit procedure // ----------------------------------------------------------------------- int FAR PASCAL _export WEP(int nParam) { int16_t i; (void)nParam; // Close any ports still open for (i = 0; i < MAX_PORTS; i++) { if (ports[i].isOpen) { trmcom(i); } } return 1; }