From f666825417c589cbba7dd911156f79a65bce3151 Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Mon, 23 Feb 2026 20:53:02 -0600 Subject: [PATCH] Initial commit. --- .gitattributes | 11 + .gitignore | 20 + drivers/CYBERCOM.DRV | 3 + drivers/CYBERCOM.TXT | 101 ++++ drv/commdrv.c | 1302 ++++++++++++++++++++++++++++++++++++++++++ drv/commdrv.def | 27 + drv/commdrv.h | 373 ++++++++++++ drv/isr.c | 507 ++++++++++++++++ drv/makefile | 72 +++ vbx/makefile | 90 +++ vbx/mscomm.bmp | 3 + vbx/mscomm.c | 935 ++++++++++++++++++++++++++++++ vbx/mscomm.def | 15 + vbx/mscomm.h | 141 +++++ vbx/mscomm.rc | 7 + vbx/serial.c | 253 ++++++++ vbx/serial.h | 106 ++++ vbx/vbapi.def | 29 + vbx/vbapi.h | 266 +++++++++ 19 files changed, 4261 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 drivers/CYBERCOM.DRV create mode 100644 drivers/CYBERCOM.TXT create mode 100644 drv/commdrv.c create mode 100644 drv/commdrv.def create mode 100644 drv/commdrv.h create mode 100644 drv/isr.c create mode 100644 drv/makefile create mode 100644 vbx/makefile create mode 100644 vbx/mscomm.bmp create mode 100644 vbx/mscomm.c create mode 100644 vbx/mscomm.def create mode 100644 vbx/mscomm.h create mode 100644 vbx/mscomm.rc create mode 100644 vbx/serial.c create mode 100644 vbx/serial.h create mode 100644 vbx/vbapi.def create mode 100644 vbx/vbapi.h diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8c31031 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# Git LFS - binary files +*.bmp filter=lfs diff=lfs merge=lfs -text +*.DRV filter=lfs diff=lfs merge=lfs -text + +# Force LF line endings on source files +*.c text eol=lf +*.h text eol=lf +*.rc text eol=lf +*.def text eol=lf +makefile text eol=lf +*.TXT text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f1f2124 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# MSVC 1.52 build artifacts +*.obj +*.res +*.map +*.lib +*.pdb +*.sbr +*.bsc +*.pch +*.ilk + +# Build outputs +*.vbx +*.drv +*.dll +*.exe + +# Backup files +*.bak +*~ diff --git a/drivers/CYBERCOM.DRV b/drivers/CYBERCOM.DRV new file mode 100644 index 0000000..87b3b4a --- /dev/null +++ b/drivers/CYBERCOM.DRV @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:168792609a46054b46247941eda877d51c728f4e3d1439be180eb20ae2b888db +size 9264 diff --git a/drivers/CYBERCOM.TXT b/drivers/CYBERCOM.TXT new file mode 100644 index 0000000..e586edc --- /dev/null +++ b/drivers/CYBERCOM.TXT @@ -0,0 +1,101 @@ +CyberCom V1.1.0.0P + +CYBERCOM.DRV 9264 11-26-93 1:06p + +The High Speed Serial Communications Driver for Windows 3.1 +Designed for 386 (and above) systems using the 16550 serial port +chip. (C) CyberSoft Corp 1993 + +Requires Windows 3.1, Enhanced Mode, a 16550 UART + + +INTRODUCTION. +------------- +CyberCom is a direct replacement for the standard Windows +Communications Driver (COMM.DRV). + +* Transfer at up to 115,200 KB with a 16550 serial port chip +* Great for V.FAST and Voice modems that require 57,600 Kb +* More reliable Transfer with less overhead on your system - Better + background operation. +* Fewer (if any) over/under runs. + + +HOW DOES IT WORK? +----------------- +Don't worry if the following sounds too complicated - just skip over +it and move on to the Installation... + +* CyberCom enables the FIFO buffer on the 16550 to be enabled for both +receive AND transmit - COMM.DRV only enables the FIFO for receive. + +* The 'interrupt trigger level' has been set to 8 for both transmit and +receive. This gives your applications a lot more time to process +incoming information. COMM.DRV sets the trigger level to 14 which +means that your application only has 2 characters in which read from +the FIFO buffer. + +What this means is that your communications applications will get far +fewer (if any) 'under runs' or 'over runs' when sending or receiving. + + +INSTALLATION +------------ +1. Copy CYBERCOM.DRV into your Windows\System directory. + +Edit the Windows\SYSTEM.INI file and change the following line: + +From comm.drv=comm.drv +To comm.drv=cybercom.drv + +2. If you previously have not taken advantage of the 16550 installed +in your computer then ensure that the Windows\SYSTEM.INI file has the +following information: + +[386Enh] +COMnFIFO=1 + +where n is the number of the COM port. + +if your 16550 is installed on, say, COM 1 then + +[386Enh] +COM1FIFO=1 + +3. Start or Restart windows. + + +LICENCE +------- +CyberCom is provided free for non-commercial use. + +You may not distribute CyberCom for profit. If you wish to include +CyberCom with your applications then contact CyberSoft at the address +below for details about royalty-free licences. + +---------------------------------------------------------------------- +For further information, upgrades, problems etc please contact us at: + +Attention: Douglas I. Scadlock +CyberSoft Corporation Pty Ltd, +PO BOX 407 Vaucluse, +New South Wales +2030 Australia +Phone +61 2 805 1077 +Fax +61 2 805 0897 +CIS 100033,1723 + + +CAVEAT +------ +THE INFORMATION AND COMMUNICATIONS DRIVER PROVIDED HEREUNDER +(COLLECTIVELY REFERRED TO AS "SOFTWARE") IS PROVIDED AS IS WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE. + +(C) CyberSoft Corp 1993 +(C) Microsoft Corp + +********************************************************************** + Sydney 2000 - Home of the Millennium Games diff --git a/drv/commdrv.c b/drv/commdrv.c new file mode 100644 index 0000000..a614d96 --- /dev/null +++ b/drv/commdrv.c @@ -0,0 +1,1302 @@ +// 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; +} diff --git a/drv/commdrv.def b/drv/commdrv.def new file mode 100644 index 0000000..8bb0682 --- /dev/null +++ b/drv/commdrv.def @@ -0,0 +1,27 @@ +LIBRARY COMM +DESCRIPTION 'High-Speed Serial Communications Driver' +EXETYPE WINDOWS +CODE PRELOAD FIXED +DATA PRELOAD FIXED SINGLE +HEAPSIZE 1024 +EXPORTS + INICOM @1 + SETCOM @2 + SETQUE @3 + RECCOM @4 + SNDCOM @5 + CTX @6 + TRMCOM @7 + STACOM @8 + CEXTFCN @9 + CFLUSH @10 + CEVT @11 + CEVTGET @12 + CSETBRK @13 + CCLRBRK @14 + GETDCB @15 + SUSPENDOPENCOMMPORTS @17 + REACTIVATEOPENCOMMPORTS @18 + COMMWRITESTRING @19 + READCOMMSTRING @20 + ENABLENOTIFICATION @100 diff --git a/drv/commdrv.h b/drv/commdrv.h new file mode 100644 index 0000000..5892361 --- /dev/null +++ b/drv/commdrv.h @@ -0,0 +1,373 @@ +// commdrv.h - High-speed COMM.DRV replacement +// +// Types, UART register definitions, port state structure, and prototypes. +// Drop-in replacement for Windows 3.1 stock COMM.DRV with proper 16550 +// FIFO management for reliable operation at 57600 and 115200 baud. + +#ifndef COMMDRV_H +#define COMMDRV_H + +#include + +// ----------------------------------------------------------------------- +// stdint types for MSVC 1.52 (no stdint.h available) +// ----------------------------------------------------------------------- +#ifndef _STDINT_DEFINED +typedef short int16_t; +typedef unsigned short uint16_t; +typedef long int32_t; +typedef unsigned long uint32_t; +typedef unsigned char uint8_t; +typedef signed char int8_t; +#define _STDINT_DEFINED +#endif + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +// ----------------------------------------------------------------------- +// Maximum ports supported +// ----------------------------------------------------------------------- +#define MAX_PORTS 4 + +// ----------------------------------------------------------------------- +// Default buffer sizes +// ----------------------------------------------------------------------- +#define DEFAULT_RX_SIZE 4096 +#define DEFAULT_TX_SIZE 4096 + +// ----------------------------------------------------------------------- +// UART register offsets from base address +// ----------------------------------------------------------------------- +#define UART_RBR 0 // Receive Buffer Register (read, DLAB=0) +#define UART_THR 0 // Transmit Holding Register (write, DLAB=0) +#define UART_DLL 0 // Divisor Latch Low (DLAB=1) +#define UART_IER 1 // Interrupt Enable Register (DLAB=0) +#define UART_DLM 1 // Divisor Latch High (DLAB=1) +#define UART_IIR 2 // Interrupt Identification Register (read) +#define UART_FCR 2 // FIFO Control Register (write) +#define UART_LCR 3 // Line Control Register +#define UART_MCR 4 // Modem Control Register +#define UART_LSR 5 // Line Status Register +#define UART_MSR 6 // Modem Status Register +#define UART_SCR 7 // Scratch Register + +// ----------------------------------------------------------------------- +// IER bits - Interrupt Enable Register +// ----------------------------------------------------------------------- +#define IER_RDA 0x01 // Received Data Available +#define IER_THRE 0x02 // Transmitter Holding Register Empty +#define IER_LSI 0x04 // Line Status Interrupt +#define IER_MSI 0x08 // Modem Status Interrupt + +// ----------------------------------------------------------------------- +// IIR bits - Interrupt Identification Register +// ----------------------------------------------------------------------- +#define IIR_PENDING 0x01 // 0=interrupt pending, 1=no interrupt +#define IIR_ID_MASK 0x0E // Interrupt ID mask +#define IIR_MSR 0x00 // Modem Status change +#define IIR_THRE 0x02 // Transmitter Holding Register Empty +#define IIR_RDA 0x04 // Received Data Available +#define IIR_LSR 0x06 // Line Status (error/break) +#define IIR_TIMEOUT 0x0C // Character Timeout (16550 FIFO) +#define IIR_FIFO_MASK 0xC0 // FIFO enabled bits + +// ----------------------------------------------------------------------- +// FCR bits - FIFO Control Register (write-only) +// ----------------------------------------------------------------------- +#define FCR_ENABLE 0x01 // Enable FIFOs +#define FCR_RX_RESET 0x02 // Reset RX FIFO +#define FCR_TX_RESET 0x04 // Reset TX FIFO +#define FCR_DMA_MODE 0x08 // DMA mode select +#define FCR_TRIG_1 0x00 // RX trigger level: 1 byte +#define FCR_TRIG_4 0x40 // RX trigger level: 4 bytes +#define FCR_TRIG_8 0x80 // RX trigger level: 8 bytes +#define FCR_TRIG_14 0xC0 // RX trigger level: 14 bytes + +// ----------------------------------------------------------------------- +// LCR bits - Line Control Register +// ----------------------------------------------------------------------- +#define LCR_WLS_MASK 0x03 // Word Length Select mask +#define LCR_WLS_5 0x00 // 5 data bits +#define LCR_WLS_6 0x01 // 6 data bits +#define LCR_WLS_7 0x02 // 7 data bits +#define LCR_WLS_8 0x03 // 8 data bits +#define LCR_STB 0x04 // Number of Stop Bits (0=1, 1=2) +#define LCR_PEN 0x08 // Parity Enable +#define LCR_EPS 0x10 // Even Parity Select +#define LCR_SPAR 0x20 // Stick Parity +#define LCR_SBRK 0x40 // Set Break +#define LCR_DLAB 0x80 // Divisor Latch Access Bit + +// ----------------------------------------------------------------------- +// MCR bits - Modem Control Register +// ----------------------------------------------------------------------- +#define MCR_DTR 0x01 // Data Terminal Ready +#define MCR_RTS 0x02 // Request To Send +#define MCR_OUT1 0x04 // Output 1 +#define MCR_OUT2 0x08 // Output 2 (master interrupt enable) +#define MCR_LOOP 0x10 // Loopback mode + +// ----------------------------------------------------------------------- +// LSR bits - Line Status Register +// ----------------------------------------------------------------------- +#define LSR_DR 0x01 // Data Ready +#define LSR_OE 0x02 // Overrun Error +#define LSR_PE 0x04 // Parity Error +#define LSR_FE 0x08 // Framing Error +#define LSR_BI 0x10 // Break Interrupt +#define LSR_THRE 0x20 // Transmitter Holding Register Empty +#define LSR_TEMT 0x40 // Transmitter Empty (shift register too) +#define LSR_FIFO 0x80 // Error in RX FIFO (16550) + +// ----------------------------------------------------------------------- +// MSR bits - Modem Status Register +// ----------------------------------------------------------------------- +#define MSR_DCTS 0x01 // Delta CTS +#define MSR_DDSR 0x02 // Delta DSR +#define MSR_TERI 0x04 // Trailing Edge Ring Indicator +#define MSR_DDCD 0x08 // Delta DCD +#define MSR_CTS 0x10 // Clear To Send +#define MSR_DSR 0x20 // Data Set Ready +#define MSR_RI 0x40 // Ring Indicator +#define MSR_DCD 0x80 // Data Carrier Detect + +// ----------------------------------------------------------------------- +// PIC constants +// ----------------------------------------------------------------------- +#define PIC_CMD 0x20 // PIC command port +#define PIC_DATA 0x21 // PIC data (mask) port +#define PIC_EOI 0x20 // End of Interrupt command + +// ----------------------------------------------------------------------- +// Standard port base addresses and IRQs +// ----------------------------------------------------------------------- +#define COM1_BASE 0x03F8 +#define COM2_BASE 0x02F8 +#define COM3_BASE 0x03E8 +#define COM4_BASE 0x02E8 + +#define COM1_IRQ 4 +#define COM2_IRQ 3 +#define COM3_IRQ 4 +#define COM4_IRQ 3 + +// ----------------------------------------------------------------------- +// Baud rate divisor (115200 / baud) +// ----------------------------------------------------------------------- +#define BAUD_DIVISOR_BASE 115200UL + +// ----------------------------------------------------------------------- +// CBR_* baud rate index constants +// +// The 16-bit DCB BaudRate field is a UINT (16 bits). Since 115200 +// exceeds 65535, high baud rates use CBR_* index constants instead +// of raw values. Values >= 0xFF00 are indices, not raw rates. +// ----------------------------------------------------------------------- +#ifndef CBR_110 +#define CBR_110 0xFF10 +#define CBR_300 0xFF11 +#define CBR_600 0xFF12 +#define CBR_1200 0xFF13 +#define CBR_2400 0xFF14 +#define CBR_4800 0xFF15 +#define CBR_9600 0xFF16 +#define CBR_14400 0xFF17 +#define CBR_19200 0xFF18 +#define CBR_38400 0xFF1B +#define CBR_56000 0xFF1F +#define CBR_115200 0xFF24 +#endif + +// ----------------------------------------------------------------------- +// 16550 FIFO depth +// ----------------------------------------------------------------------- +#define FIFO_DEPTH 16 + +// ----------------------------------------------------------------------- +// Comm error flags (CE_* -- matches Windows SDK definitions) +// ----------------------------------------------------------------------- +#define CE_RXOVER 0x0001 // Receive Queue overflow +#define CE_OVERRUN 0x0002 // Hardware overrun +#define CE_RXPARITY 0x0004 // Parity error +#define CE_FRAME 0x0008 // Framing error +#define CE_BREAK 0x0010 // Break detected +#define CE_TXFULL 0x0020 // TX Queue is full +#define CE_MODE 0x8000 // Requested mode unsupported + +// ----------------------------------------------------------------------- +// Comm event flags (EV_* -- matches Windows SDK definitions) +// ----------------------------------------------------------------------- +#define EV_RXCHAR 0x0001 // Any character received +#define EV_RXFLAG 0x0002 // Event character received +#define EV_TXEMPTY 0x0004 // TX buffer empty +#define EV_CTS 0x0008 // CTS changed +#define EV_DSR 0x0010 // DSR changed +#define EV_RLSD 0x0020 // DCD/RLSD changed +#define EV_BREAK 0x0040 // Break received +#define EV_ERR 0x0080 // Line status error +#define EV_RING 0x0100 // Ring indicator + +// ----------------------------------------------------------------------- +// CN_* notification codes (lParam for WM_COMMNOTIFY) +// ----------------------------------------------------------------------- +#ifndef CN_RECEIVE +#define CN_RECEIVE 0x0001 +#define CN_TRANSMIT 0x0002 +#define CN_EVENT 0x0004 +#endif + +// ----------------------------------------------------------------------- +// WM_COMMNOTIFY message +// ----------------------------------------------------------------------- +#ifndef WM_COMMNOTIFY +#define WM_COMMNOTIFY 0x0044 +#endif + +// ----------------------------------------------------------------------- +// Handshaking modes +// ----------------------------------------------------------------------- +#define HS_NONE 0 +#define HS_XONXOFF 1 +#define HS_RTSCTS 2 +#define HS_BOTH 3 + +// ----------------------------------------------------------------------- +// EscapeCommFunction codes (matches Windows SDK) +// ----------------------------------------------------------------------- +#ifndef SETXON +#define SETXON 1 +#define SETXOFF 2 +#define SETRTS 3 +#define CLRRTS 4 +#define SETDTR 5 +#define CLRDTR 6 +#define RESETDEV 7 +#define SETBREAK 8 +#define CLRBREAK 9 +#endif + +// ----------------------------------------------------------------------- +// Flush queue selectors +// ----------------------------------------------------------------------- +#define FLUSH_RX 0 +#define FLUSH_TX 1 + +// ----------------------------------------------------------------------- +// Port state structure +// ----------------------------------------------------------------------- +typedef struct { + uint16_t baseAddr; // UART base I/O address + uint8_t irq; // IRQ number (3 or 4) + int16_t commId; // Port ID (0=COM1, 1=COM2, ...) + uint8_t isOpen; // Port open flag + uint8_t is16550; // 16550 FIFO detected + uint8_t fifoEnabled; // FIFO enabled (COMnFIFO setting) + uint8_t fifoTrigger; // RX FIFO trigger FCR bits (FCR_TRIG_*) + + // Ring buffers (GlobalAlloc'd) + uint8_t FAR *rxBuf; // Receive ring buffer + uint16_t rxSize; // Buffer size + uint16_t rxHead; // Write position (ISR writes) + uint16_t rxTail; // Read position (app reads) + uint16_t rxCount; // Bytes in buffer + + uint8_t FAR *txBuf; // Transmit ring buffer + uint16_t txSize; // Buffer size + uint16_t txHead; // Write position (app writes) + uint16_t txTail; // Read position (ISR reads) + uint16_t txCount; // Bytes in buffer + + // DCB shadow + uint16_t baudRate; // Current baud rate + uint8_t byteSize; // Data bits (5-8) + uint8_t parity; // Parity mode + uint8_t stopBits; // Stop bits + + // Flow control state + uint8_t hsMode; // Handshaking mode + uint8_t txStopped; // TX halted by flow control + uint8_t rxStopped; // We sent XOFF / dropped RTS + uint8_t xonChar; // XON character (default 0x11) + uint8_t xoffChar; // XOFF character (default 0x13) + uint16_t xoffLim; // Send XOFF when rxCount > rxSize - xoffLim + uint16_t xonLim; // Send XON when rxCount < xonLim + + // Modem control shadow + uint8_t dtrState; // DTR line state + uint8_t rtsState; // RTS line state (when not flow-controlled) + + // Error accumulator + uint16_t errorFlags; // CE_* error flags (sticky until read) + + // Event notification + uint16_t evtMask; // Event enable mask + uint16_t evtWord; // Accumulated events + HWND hwndNotify; // Window for WM_COMMNOTIFY + int16_t rxNotifyThresh; // CN_RECEIVE threshold (-1=disabled) + int16_t txNotifyThresh; // CN_TRANSMIT threshold (-1=disabled) + + // ISR state + void (FAR *prevIsr)(void); // Previous ISR in chain + uint8_t irqMask; // PIC mask bit for this IRQ + uint8_t breakState; // Break signal active + + // Priority transmit + int16_t txImmediate; // -1=none, else char to send immediately + + // DCB copy for GetCommState + DCB dcb; // Full DCB for GETDCB/SETCOM +} PortStateT; + +// ----------------------------------------------------------------------- +// Global port state array +// ----------------------------------------------------------------------- +extern PortStateT ports[MAX_PORTS]; + +// ----------------------------------------------------------------------- +// Exported function prototypes (COMM.DRV API) +// +// These use the Windows COMM.DRV calling convention: FAR PASCAL +// ----------------------------------------------------------------------- +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); + +// ----------------------------------------------------------------------- +// ISR prototypes (isr.c) +// ----------------------------------------------------------------------- +int16_t hookIsr(PortStateT *port); +void unhookIsr(PortStateT *port); +void isrDispatch(PortStateT *port); + +// ----------------------------------------------------------------------- +// Internal helpers +// ----------------------------------------------------------------------- +int16_t detect16550(uint16_t baseAddr); +void applyBaudRate(PortStateT *port, uint16_t baud); +void applyLineParams(PortStateT *port, uint8_t byteSize, uint8_t parity, uint8_t stopBits); +void enableFifo(PortStateT *port); +void primeTx(PortStateT *port); + +#endif // COMMDRV_H diff --git a/drv/isr.c b/drv/isr.c new file mode 100644 index 0000000..df9af8f --- /dev/null +++ b/drv/isr.c @@ -0,0 +1,507 @@ +// isr.c - Interrupt service routines for COMM.DRV replacement +// +// ISR entry points for IRQ3 (COM2/4) and IRQ4 (COM1/3), DPMI interrupt +// vector hooking/unhooking, and interrupt dispatch with 16550 FIFO support. + +#include "commdrv.h" +#include + +// ----------------------------------------------------------------------- +// Prototypes +// ----------------------------------------------------------------------- +static void checkFlowRx(PortStateT *port); +static void checkNotify(PortStateT *port); +static void handleLsr(PortStateT *port, uint8_t lsr); +static void handleMsr(PortStateT *port); +static void handleRx(PortStateT *port, uint8_t lsr); +static void handleTx(PortStateT *port); + +void _far _interrupt isr3(void); +void _far _interrupt isr4(void); + +// ----------------------------------------------------------------------- +// Saved previous ISR vectors for IRQ3 and IRQ4 +// ----------------------------------------------------------------------- +static void (_far _interrupt *prevIsr3)(void) = NULL; +static void (_far _interrupt *prevIsr4)(void) = NULL; + +// Track how many ports are using each IRQ so we know when to unhook +static int16_t irq3RefCount = 0; +static int16_t irq4RefCount = 0; + + +// ----------------------------------------------------------------------- +// checkFlowRx - Check RX buffer level and assert/deassert flow control +// ----------------------------------------------------------------------- +static void checkFlowRx(PortStateT *port) +{ + uint16_t base; + + base = port->baseAddr; + + if (port->hsMode == HS_NONE) { + return; + } + + // Check if we need to stop remote sender + if (!port->rxStopped && port->rxCount >= (port->rxSize - port->xoffLim)) { + port->rxStopped = TRUE; + + if (port->hsMode == HS_XONXOFF || port->hsMode == HS_BOTH) { + // Send XOFF - queue as immediate character + port->txImmediate = port->xoffChar; + // Enable THRE to get it sent + _outp(base + UART_IER, (uint8_t)(_inp(base + UART_IER) | IER_THRE)); + } + if (port->hsMode == HS_RTSCTS || port->hsMode == HS_BOTH) { + // Drop RTS + _outp(base + UART_MCR, (uint8_t)(_inp(base + UART_MCR) & ~MCR_RTS)); + } + } + + // Check if we can resume remote sender + if (port->rxStopped && port->rxCount <= port->xonLim) { + port->rxStopped = FALSE; + + if (port->hsMode == HS_XONXOFF || port->hsMode == HS_BOTH) { + // Send XON + port->txImmediate = port->xonChar; + _outp(base + UART_IER, (uint8_t)(_inp(base + UART_IER) | IER_THRE)); + } + if (port->hsMode == HS_RTSCTS || port->hsMode == HS_BOTH) { + // Raise RTS + _outp(base + UART_MCR, (uint8_t)(_inp(base + UART_MCR) | MCR_RTS)); + } + } +} + + +// ----------------------------------------------------------------------- +// checkNotify - Post WM_COMMNOTIFY if threshold conditions met +// +// Called at end of ISR dispatch, outside the IIR loop. +// Uses PostMessage to avoid reentrancy issues. +// ----------------------------------------------------------------------- +static void checkNotify(PortStateT *port) +{ + uint16_t notifyBits; + + if (!port->hwndNotify) { + return; + } + + notifyBits = 0; + + // CN_RECEIVE: rxCount crossed threshold from below + if (port->rxNotifyThresh >= 0 && port->rxCount >= (uint16_t)port->rxNotifyThresh) { + notifyBits |= CN_RECEIVE; + } + + // CN_TRANSMIT: space available crossed threshold + if (port->txNotifyThresh >= 0) { + uint16_t txFree = port->txSize - port->txCount; + if (txFree >= (uint16_t)port->txNotifyThresh) { + notifyBits |= CN_TRANSMIT; + } + } + + // CN_EVENT: any event bits accumulated + if (port->evtWord & port->evtMask) { + notifyBits |= CN_EVENT; + } + + if (notifyBits) { + PostMessage(port->hwndNotify, WM_COMMNOTIFY, (WPARAM)port->commId, (LPARAM)notifyBits); + } +} + + +// ----------------------------------------------------------------------- +// handleLsr - Process line status errors +// ----------------------------------------------------------------------- +static void handleLsr(PortStateT *port, uint8_t lsr) +{ + if (lsr & LSR_OE) { + port->errorFlags |= CE_OVERRUN; + } + if (lsr & LSR_PE) { + port->errorFlags |= CE_RXPARITY; + } + if (lsr & LSR_FE) { + port->errorFlags |= CE_FRAME; + } + if (lsr & LSR_BI) { + port->errorFlags |= CE_BREAK; + port->evtWord |= EV_BREAK; + } + if (lsr & (LSR_OE | LSR_PE | LSR_FE)) { + port->evtWord |= EV_ERR; + } +} + + +// ----------------------------------------------------------------------- +// handleMsr - Process modem status changes +// ----------------------------------------------------------------------- +static void handleMsr(PortStateT *port) +{ + uint8_t msr; + + msr = (uint8_t)_inp(port->baseAddr + UART_MSR); + + if (msr & MSR_DCTS) { + port->evtWord |= EV_CTS; + + // RTS/CTS flow control: stop/start TX based on CTS + if (port->hsMode == HS_RTSCTS || port->hsMode == HS_BOTH) { + if (msr & MSR_CTS) { + port->txStopped = FALSE; + // Resume TX + if (port->txCount > 0) { + handleTx(port); + } + } else { + port->txStopped = TRUE; + } + } + } + + if (msr & MSR_DDSR) { + port->evtWord |= EV_DSR; + } + + if (msr & MSR_TERI) { + port->evtWord |= EV_RING; + } + + if (msr & MSR_DDCD) { + port->evtWord |= EV_RLSD; + } +} + + +// ----------------------------------------------------------------------- +// handleRx - Read all available bytes from UART into RX ring buffer +// ----------------------------------------------------------------------- +static void handleRx(PortStateT *port, uint8_t lsr) +{ + uint16_t base; + uint8_t ch; + + base = port->baseAddr; + + while (lsr & LSR_DR) { + // Check for line errors on this byte + if (lsr & (LSR_OE | LSR_PE | LSR_FE | LSR_BI)) { + handleLsr(port, lsr); + } + + ch = (uint8_t)_inp(base + UART_RBR); + + // Check for XON/XOFF if software flow control enabled + if (port->hsMode == HS_XONXOFF || port->hsMode == HS_BOTH) { + if (ch == port->xonChar) { + port->txStopped = FALSE; + // Resume TX if we have data + if (port->txCount > 0) { + handleTx(port); + } + lsr = (uint8_t)_inp(base + UART_LSR); + continue; + } + if (ch == port->xoffChar) { + port->txStopped = TRUE; + lsr = (uint8_t)_inp(base + UART_LSR); + continue; + } + } + + // Store in ring buffer + if (port->rxCount < port->rxSize) { + port->rxBuf[port->rxHead] = ch; + port->rxHead++; + if (port->rxHead >= port->rxSize) { + port->rxHead = 0; + } + port->rxCount++; + } else { + // Buffer overflow + port->errorFlags |= CE_RXOVER; + } + + // Set event bit + port->evtWord |= EV_RXCHAR; + + // Read LSR again for next byte + lsr = (uint8_t)_inp(base + UART_LSR); + } + + // Check if we need to assert flow control + checkFlowRx(port); +} + + +// ----------------------------------------------------------------------- +// handleTx - Transmit bytes from TX ring buffer to UART +// +// For 16550: burst up to 16 bytes per THRE interrupt. +// For 8250: send 1 byte at a time. +// ----------------------------------------------------------------------- +static void handleTx(PortStateT *port) +{ + uint16_t base; + uint16_t burst; + uint16_t i; + + if (port->txStopped) { + return; + } + + base = port->baseAddr; + + // Priority character first + if (port->txImmediate >= 0) { + _outp(base + UART_THR, (uint8_t)port->txImmediate); + port->txImmediate = -1; + return; + } + + if (port->txCount == 0) { + // Nothing to send -- disable THRE interrupt + _outp(base + UART_IER, (uint8_t)(_inp(base + UART_IER) & ~IER_THRE)); + // TX empty event + port->evtWord |= EV_TXEMPTY; + return; + } + + // Burst size: 16 for FIFO, 1 for non-FIFO or FIFO disabled + burst = (port->is16550 && port->fifoEnabled) ? FIFO_DEPTH : 1; + if (burst > port->txCount) { + burst = port->txCount; + } + + for (i = 0; i < burst; i++) { + _outp(base + UART_THR, port->txBuf[port->txTail]); + port->txTail++; + if (port->txTail >= port->txSize) { + port->txTail = 0; + } + port->txCount--; + } +} + + +// ----------------------------------------------------------------------- +// hookIsr - Hook the interrupt vector for a port's IRQ via DPMI +// +// Uses INT 31h AX=0204h to get and AX=0205h to set protected-mode +// interrupt vectors. +// +// Returns 0 on success, -1 on error. +// ----------------------------------------------------------------------- +int16_t hookIsr(PortStateT *port) +{ + uint8_t intNum; + uint8_t picMask; + int16_t *refCount; + void (_far _interrupt **prevPtr)(void); + void (_far _interrupt *newIsr)(void); + void _far *oldVector; + uint16_t oldSeg; + uint16_t oldOff; + + intNum = port->irq + 8; + port->irqMask = (uint8_t)(1 << port->irq); + + if (port->irq == 3) { + prevPtr = &prevIsr3; + refCount = &irq3RefCount; + newIsr = isr3; + } else { + prevPtr = &prevIsr4; + refCount = &irq4RefCount; + newIsr = isr4; + } + + // Only hook the vector on first use of this IRQ + if (*refCount == 0) { + // INT 31h AX=0204h: Get Protected Mode Interrupt Vector + // BL = interrupt number + // Returns: CX:DX = selector:offset of handler + _asm { + mov ax, 0204h + mov bl, intNum + int 31h + mov oldSeg, cx + mov oldOff, dx + } + + oldVector = (void _far *)((uint32_t)oldSeg << 16 | oldOff); + *prevPtr = (void (_far _interrupt *)(void))oldVector; + + // INT 31h AX=0205h: Set Protected Mode Interrupt Vector + // BL = interrupt number + // CX:DX = selector:offset of new handler + { + uint16_t newSeg = _FP_SEG(newIsr); + uint16_t newOff = _FP_OFF(newIsr); + + _asm { + mov ax, 0205h + mov bl, intNum + mov cx, newSeg + mov dx, newOff + int 31h + } + } + } + + (*refCount)++; + port->prevIsr = (void (FAR *)(void))*prevPtr; + + // Unmask IRQ at PIC + picMask = _inp(PIC_DATA); + picMask &= ~port->irqMask; + _outp(PIC_DATA, picMask); + + return 0; +} + + +// ----------------------------------------------------------------------- +// isrDispatch - Main interrupt dispatch loop +// +// Called from the ISR entry points with a pointer to the port state. +// Reads IIR in a loop until no more interrupts are pending. +// Handles in priority order: LSR > RX > TX > MSR. +// ----------------------------------------------------------------------- +void isrDispatch(PortStateT *port) +{ + uint8_t iir; + uint8_t lsr; + uint16_t base; + + base = port->baseAddr; + + for (;;) { + iir = (uint8_t)_inp(base + UART_IIR); + + // Bit 0 set = no interrupt pending + if (iir & IIR_PENDING) { + break; + } + + switch (iir & IIR_ID_MASK) { + case IIR_LSR: + // Line status: read LSR, accumulate errors + lsr = (uint8_t)_inp(base + UART_LSR); + handleLsr(port, lsr); + // If data ready, also handle RX + if (lsr & LSR_DR) { + handleRx(port, lsr); + } + break; + + case IIR_RDA: + case IIR_TIMEOUT: + // Received data available or FIFO timeout + lsr = (uint8_t)_inp(base + UART_LSR); + handleRx(port, lsr); + break; + + case IIR_THRE: + // Transmitter holding register empty + handleTx(port); + break; + + case IIR_MSR: + // Modem status change + handleMsr(port); + break; + } + } + + checkNotify(port); +} + + +// ----------------------------------------------------------------------- +// unhookIsr - Restore previous interrupt vector via DPMI +// ----------------------------------------------------------------------- +void unhookIsr(PortStateT *port) +{ + uint8_t intNum; + uint8_t picMask; + int16_t *refCount; + void (_far _interrupt **prevPtr)(void); + + intNum = port->irq + 8; + + if (port->irq == 3) { + prevPtr = &prevIsr3; + refCount = &irq3RefCount; + } else { + prevPtr = &prevIsr4; + refCount = &irq4RefCount; + } + + // Mask IRQ at PIC + picMask = _inp(PIC_DATA); + picMask |= port->irqMask; + _outp(PIC_DATA, picMask); + + (*refCount)--; + + // Only restore vector when last user of this IRQ unhooks + if (*refCount <= 0) { + uint16_t oldSeg = _FP_SEG(*prevPtr); + uint16_t oldOff = _FP_OFF(*prevPtr); + + _asm { + mov ax, 0205h + mov bl, intNum + mov cx, oldSeg + mov dx, oldOff + int 31h + } + + *prevPtr = NULL; + *refCount = 0; + } +} + + +// ----------------------------------------------------------------------- +// isr3 - ISR entry point for IRQ3 (COM2 and COM4) +// ----------------------------------------------------------------------- +void _far _interrupt isr3(void) +{ + int16_t i; + + for (i = 0; i < MAX_PORTS; i++) { + if (ports[i].isOpen && ports[i].irq == 3) { + isrDispatch(&ports[i]); + } + } + + // Send EOI to PIC + _outp(PIC_CMD, PIC_EOI); +} + + +// ----------------------------------------------------------------------- +// isr4 - ISR entry point for IRQ4 (COM1 and COM3) +// ----------------------------------------------------------------------- +void _far _interrupt isr4(void) +{ + int16_t i; + + for (i = 0; i < MAX_PORTS; i++) { + if (ports[i].isOpen && ports[i].irq == 4) { + isrDispatch(&ports[i]); + } + } + + // Send EOI to PIC + _outp(PIC_CMD, PIC_EOI); +} diff --git a/drv/makefile b/drv/makefile new file mode 100644 index 0000000..06e7484 --- /dev/null +++ b/drv/makefile @@ -0,0 +1,72 @@ +# makefile - High-speed COMM.DRV replacement for MSVC 1.52 +# +# Build: nmake +# Clean: nmake clean +# +# Prerequisites: +# - MSVC 1.52 (cl, link in PATH) +# +# Output is COMM.DRV -- drop-in replacement for stock Windows 3.1 driver. +# +# Install: +# 1. Copy COMM.DRV to \WINDOWS\SYSTEM +# 2. Edit SYSTEM.INI [boot] section: comm.drv=comm.drv +# 3. Add to [386Enh] section: COM1FIFO=1 (for each port in use) +# 4. Restart Windows +# +# Key improvements over stock COMM.DRV: +# - Both RX and TX FIFOs enabled (stock: RX only) +# - RX trigger level 8 (stock: 14 -- only 2 bytes headroom) +# - TX burst writes up to 16 bytes per THRE interrupt (stock: 1 byte) + +CC = cl +LINK = link + +# Compiler flags: +# -c Compile only +# -W3 Warning level 3 +# -ASw Small model, SS!=DS (Windows DLL) +# -Gsw No stack probes, Windows prolog/epilog +# -Ow Safe optimizations for Windows +# -Zp1 Pack structures on 1-byte boundaries (hardware register layouts) +# -Ze Enable Microsoft extensions +CFLAGS = -c -W3 -ASw -Gsw -Ow -Zp1 -Ze + +# Linker flags: +# /NOD No default libraries +# /NOE No extended dictionary search +# /AL:16 Segment alignment 16 +LFLAGS = /NOD /NOE /AL:16 + +# Libraries +# sdllcew Small model DLL C runtime (emulated math, Windows) +# libw Windows API import library +LIBS = sdllcew libw + +# Output +TARGET = comm.drv + +# Objects +OBJS = commdrv.obj isr.obj + +# ----------------------------------------------------------------------- +# Build rules +# ----------------------------------------------------------------------- +all: $(TARGET) + +$(TARGET): $(OBJS) commdrv.def + $(LINK) $(LFLAGS) $(OBJS), $(TARGET),,$(LIBS), commdrv.def + +commdrv.obj: commdrv.c commdrv.h + $(CC) $(CFLAGS) commdrv.c + +isr.obj: isr.c commdrv.h + $(CC) $(CFLAGS) isr.c + +# ----------------------------------------------------------------------- +# Clean +# ----------------------------------------------------------------------- +clean: + -del *.obj + -del *.drv + -del *.map diff --git a/vbx/makefile b/vbx/makefile new file mode 100644 index 0000000..285a004 --- /dev/null +++ b/vbx/makefile @@ -0,0 +1,90 @@ +# makefile - MSComm VBX control for MSVC 1.52 +# +# Build: nmake +# Clean: nmake clean +# +# Prerequisites: +# - MSVC 1.52 (cl, link, rc in PATH) +# - VBAPI.LIB (from VBX CDK, or generate with: implib vbapi.lib vbapi.def) +# +# High-speed serial note: +# The stock Windows 3.1 COMM.DRV enables the 16550 FIFO for receive only, +# with a trigger level of 14 (leaving only 2 bytes of headroom). This causes +# overruns at baud rates above 9600 under load. For reliable operation at +# 57600 or 115200, install CyberCom V1.1.0.0P -- a freeware drop-in +# replacement included in ..\drivers\CYBERCOM.DRV. It enables both RX and TX +# FIFOs with a trigger level of 8, dramatically reducing interrupt overhead. +# +# Install: +# 1. Copy ..\drivers\CYBERCOM.DRV to \WINDOWS\SYSTEM +# 2. Edit SYSTEM.INI [boot] section: comm.drv=cybercom.drv +# 3. Add to [386Enh] section: COM1FIFO=1 (for each port in use) +# 4. Restart Windows +# +# CyberCom is free for non-commercial use. (C) CyberSoft Corp 1993 + +CC = cl +LINK = link +RC = rc + +# Compiler flags: +# -c Compile only +# -W3 Warning level 3 +# -ASw Small model, SS!=DS (Windows DLL) +# -Gsw No stack probes, Windows prolog/epilog +# -Ow Safe optimizations for Windows +# -Zp2 Pack structures on 2-byte boundaries +# -Ze Enable Microsoft extensions +CFLAGS = -c -W3 -ASw -Gsw -Ow -Zp2 -Ze + +# Linker flags: +# /NOD No default libraries +# /NOE No extended dictionary search +# /AL:16 Segment alignment 16 +LFLAGS = /NOD /NOE /AL:16 + +# Libraries +# sdllcew Small model DLL C runtime (emulated math, Windows) +# libw Windows API import library +# commdlg Common dialog import library +# vbapi VB API import library +LIBS = sdllcew libw commdlg vbapi + +# Output +TARGET = mscomm.vbx + +# Objects +OBJS = mscomm.obj serial.obj + +# ----------------------------------------------------------------------- +# Build rules +# ----------------------------------------------------------------------- +all: $(TARGET) + +$(TARGET): $(OBJS) mscomm.def mscomm.res + $(LINK) $(LFLAGS) $(OBJS), $(TARGET),,$(LIBS), mscomm.def + $(RC) mscomm.res $(TARGET) + +mscomm.obj: mscomm.c mscomm.h vbapi.h serial.h + $(CC) $(CFLAGS) mscomm.c + +serial.obj: serial.c serial.h + $(CC) $(CFLAGS) serial.c + +mscomm.res: mscomm.rc mscomm.h mscomm.bmp + $(RC) -r mscomm.rc + +# ----------------------------------------------------------------------- +# Generate VBAPI.LIB from vbapi.def if not present +# ----------------------------------------------------------------------- +vbapi.lib: vbapi.def + implib vbapi.lib vbapi.def + +# ----------------------------------------------------------------------- +# Clean +# ----------------------------------------------------------------------- +clean: + -del *.obj + -del *.res + -del *.vbx + -del *.map diff --git a/vbx/mscomm.bmp b/vbx/mscomm.bmp new file mode 100644 index 0000000..303e6fd --- /dev/null +++ b/vbx/mscomm.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c876f98c830c8c3e17bba2418a421699b77f391507583406e74058071e067b46 +size 174 diff --git a/vbx/mscomm.c b/vbx/mscomm.c new file mode 100644 index 0000000..b709189 --- /dev/null +++ b/vbx/mscomm.c @@ -0,0 +1,935 @@ +// mscomm.c - MSComm VBX control implementation +// +// Provides high-speed serial communications for 16-bit Visual Basic 4. +// Implements MODEL, control procedure, property/event handling, and +// WM_COMMNOTIFY dispatch via hidden notification windows. +// +// IMPORTANT: VBDerefControl() returns a pointer into VB's compacting heap. +// Any VB API call (VBCreateHsz, VBDestroyHsz, VBFireEvent, etc.) can +// trigger heap compaction and invalidate the pointer. All code must +// re-deref after such calls before accessing control data again. + +#include +#include +#include "vbapi.h" +#include "serial.h" +#include "mscomm.h" + +// ----------------------------------------------------------------------- +// Global data +// ----------------------------------------------------------------------- +HANDLE ghInstance = NULL; + +// ----------------------------------------------------------------------- +// Convenience macro for re-dereferencing control data after VB API calls +// ----------------------------------------------------------------------- +#define DEREF(hctl) ((MsCommDataT FAR *)VBDerefControl(hctl)) + +// ----------------------------------------------------------------------- +// Forward declarations - Control procedure +// ----------------------------------------------------------------------- +LONG FAR PASCAL _export MsCommCtlProc(HCTL hctl, HWND hwnd, USHORT msg, USHORT wp, LONG lp); +LRESULT FAR PASCAL _export NotifyWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); + +// ----------------------------------------------------------------------- +// Forward declarations - Internal functions (alphabetical) +// ----------------------------------------------------------------------- +static void closePort(HCTL hctl, MsCommDataT FAR *pData); +static void fireOnComm(HCTL hctl, MsCommDataT FAR *pData, int16_t eventCode); +static LONG handleGetProperty(HCTL hctl, MsCommDataT FAR *pData, USHORT iProp); +static LONG handleSetProperty(HCTL hctl, MsCommDataT FAR *pData, USHORT iProp, LONG lp); +static void initControlData(HCTL hctl); +static int16_t openPort(HCTL hctl, MsCommDataT FAR *pData); +static void processEventNotify(HCTL hctl, int16_t commId); +static void processReceiveNotify(HCTL hctl, int16_t commId); +static void processTransmitNotify(HCTL hctl, int16_t commId); +static BOOL registerNotifyClass(HANDLE hInstance); + +// ----------------------------------------------------------------------- +// Forward declarations - DLL entry points +// ----------------------------------------------------------------------- +int FAR PASCAL LibMain(HANDLE hInstance, WORD wDataSeg, WORD wHeapSize, LPSTR lpszCmdLine); +BOOL FAR PASCAL _export VBINITCC(USHORT usVersion, BOOL fRuntime); +void FAR PASCAL _export VBTERMCC(void); +void FAR PASCAL _export WEP(int nParam); + +// ----------------------------------------------------------------------- +// Enum value lists (null-separated, double-null terminated by compiler) +// ----------------------------------------------------------------------- +static char szHandshaking[] = "None\0XonXoff\0RtsCts\0Both"; +static char szInputMode[] = "Text\0Binary"; + +// ----------------------------------------------------------------------- +// Property descriptors +// ----------------------------------------------------------------------- +// offset default enum list enumMax +static PROPINFO propCommPort = { "CommPort", PF_fSetMsg | PF_fGetData | DT_SHORT, OFFSETIN(MsCommDataT, commPort), 0, 1L, NULL, 0 }; +static PROPINFO propSettings = { "Settings", PF_fSetMsg | PF_fGetMsg | DT_HSZ, 0, 0, 0L, NULL, 0 }; +static PROPINFO propPortOpen = { "PortOpen", PF_fSetMsg | PF_fGetData | DT_BOOL, OFFSETIN(MsCommDataT, portOpen), 0, 0L, NULL, 0 }; +static PROPINFO propInput = { "Input", PF_fGetMsg | PF_fNoShow | DT_HSZ, 0, 0, 0L, NULL, 0 }; +static PROPINFO propOutput = { "Output", PF_fSetMsg | PF_fNoShow | DT_HSZ, 0, 0, 0L, NULL, 0 }; +static PROPINFO propInBufferSize = { "InBufferSize", PF_fSetMsg | PF_fGetData | DT_SHORT, OFFSETIN(MsCommDataT, inBufferSize), 0, 4096L, NULL, 0 }; +static PROPINFO propOutBufferSize = { "OutBufferSize", PF_fSetMsg | PF_fGetData | DT_SHORT, OFFSETIN(MsCommDataT, outBufferSize), 0, 4096L, NULL, 0 }; +static PROPINFO propInBufferCount = { "InBufferCount", PF_fGetMsg | PF_fNoShow | DT_SHORT, 0, 0, 0L, NULL, 0 }; +static PROPINFO propOutBufferCount= { "OutBufferCount",PF_fGetMsg | PF_fNoShow | DT_SHORT, 0, 0, 0L, NULL, 0 }; +static PROPINFO propRThreshold = { "RThreshold", PF_fSetData| PF_fGetData | DT_SHORT, OFFSETIN(MsCommDataT, rThreshold), 0, 0L, NULL, 0 }; +static PROPINFO propSThreshold = { "SThreshold", PF_fSetData| PF_fGetData | DT_SHORT, OFFSETIN(MsCommDataT, sThreshold), 0, 0L, NULL, 0 }; +static PROPINFO propHandshaking = { "Handshaking", PF_fSetMsg | PF_fGetData | DT_ENUM, OFFSETIN(MsCommDataT, handshaking), 0, 0L, szHandshaking, 3 }; +static PROPINFO propInputLen = { "InputLen", PF_fSetData| PF_fGetData | DT_SHORT, OFFSETIN(MsCommDataT, inputLen), 0, 0L, NULL, 0 }; +static PROPINFO propInputMode = { "InputMode", PF_fSetData| PF_fGetData | DT_ENUM, OFFSETIN(MsCommDataT, inputMode), 0, 0L, szInputMode, 1 }; +static PROPINFO propDTREnable = { "DTREnable", PF_fSetMsg | PF_fGetData | DT_BOOL, OFFSETIN(MsCommDataT, dtrEnable), 0, 1L, NULL, 0 }; +static PROPINFO propRTSEnable = { "RTSEnable", PF_fSetMsg | PF_fGetData | DT_BOOL, OFFSETIN(MsCommDataT, rtsEnable), 0, 1L, NULL, 0 }; +static PROPINFO propCDHolding = { "CDHolding", PF_fGetMsg | PF_fNoShow | DT_BOOL, 0, 0, 0L, NULL, 0 }; +static PROPINFO propCTSHolding = { "CTSHolding", PF_fGetMsg | PF_fNoShow | DT_BOOL, 0, 0, 0L, NULL, 0 }; +static PROPINFO propDSRHolding = { "DSRHolding", PF_fGetMsg | PF_fNoShow | DT_BOOL, 0, 0, 0L, NULL, 0 }; +static PROPINFO propBreak = { "Break", PF_fSetMsg | PF_fGetData | DT_BOOL, OFFSETIN(MsCommDataT, breakState), 0, 0L, NULL, 0 }; +static PROPINFO propCommEvent = { "CommEvent", PF_fGetData| PF_fNoShow | DT_SHORT, OFFSETIN(MsCommDataT, commEvent), 0, 0L, NULL, 0 }; +static PROPINFO propNullDiscard = { "NullDiscard", PF_fSetMsg | PF_fGetData | DT_BOOL, OFFSETIN(MsCommDataT, nullDiscard), 0, 0L, NULL, 0 }; +static PROPINFO propEOFEnable = { "EOFEnable", PF_fSetData| PF_fGetData | DT_BOOL, OFFSETIN(MsCommDataT, eofEnable), 0, 0L, NULL, 0 }; +static PROPINFO propParityReplace = { "ParityReplace", PF_fSetMsg | PF_fGetMsg | DT_HSZ, 0, 0, 0L, NULL, 0 }; + +// ----------------------------------------------------------------------- +// Property list (NULL-terminated, order must match IPROP_* indices) +// ----------------------------------------------------------------------- +static PPROPINFO propList[] = { + PPROPINFO_STD_CTLNAME, // 0 - Name + PPROPINFO_STD_INDEX, // 1 - Index + PPROPINFO_STD_TAG, // 2 - Tag + &propCommPort, // 3 + &propSettings, // 4 + &propPortOpen, // 5 + &propInput, // 6 + &propOutput, // 7 + &propInBufferSize, // 8 + &propOutBufferSize, // 9 + &propInBufferCount, // 10 + &propOutBufferCount, // 11 + &propRThreshold, // 12 + &propSThreshold, // 13 + &propHandshaking, // 14 + &propInputLen, // 15 + &propInputMode, // 16 + &propDTREnable, // 17 + &propRTSEnable, // 18 + &propCDHolding, // 19 + &propCTSHolding, // 20 + &propDSRHolding, // 21 + &propBreak, // 22 + &propCommEvent, // 23 + &propNullDiscard, // 24 + &propEOFEnable, // 25 + &propParityReplace, // 26 + NULL +}; + +// ----------------------------------------------------------------------- +// Event descriptors +// ----------------------------------------------------------------------- +static EVENTINFO eventOnComm = { "OnComm", 0, 0, NULL, "", 0 }; + +// ----------------------------------------------------------------------- +// Event list (NULL-terminated) +// ----------------------------------------------------------------------- +static PEVENTINFO eventList[] = { + &eventOnComm, // 0 - OnComm + NULL +}; + +// ----------------------------------------------------------------------- +// MODEL definition +// ----------------------------------------------------------------------- +static char szCtlName[] = "MSComm"; + +MODEL modelMsComm = { + VB300_VERSION, // usVersion + MODEL_fInitMsg | MODEL_fInvisAtRun,// fl + (PCTLPROC)MsCommCtlProc, // pctlproc + 0, // fsClassStyle + WS_CHILD, // flWndStyle + sizeof(MsCommDataT), // cbCtlExtra + IDB_MSCOMM, // idBmpPalette + szCtlName, // npszDefCtlName + NULL, // npszClassName + NULL, // npszParentClassName + propList, // npproplist + eventList, // npeventlist + IPROP_COMMPORT, // nDefProp + IEVENT_ONCOMM, // nDefEvent + 0, // nValueProp + 0x0100 // usCtlVersion (1.00) +}; + + +// ======================================================================= +// Internal helper functions (alphabetical) +// ======================================================================= + +// ----------------------------------------------------------------------- +// closePort - Close the serial port and destroy notification window +// +// No VB API calls here, so pData remains valid throughout. +// ----------------------------------------------------------------------- +static void closePort(HCTL hctl, MsCommDataT FAR *pData) +{ + if (pData->commId >= 0) { + if (pData->breakState) { + serialEscape(pData->commId, SERIAL_CLRBREAK); + pData->breakState = FALSE; + } + + serialClose(pData->commId); + pData->commId = -1; + } + + if (pData->hwndNotify != NULL) { + DestroyWindow(pData->hwndNotify); + pData->hwndNotify = NULL; + } + + pData->portOpen = FALSE; + pData->ctsState = FALSE; + pData->dsrState = FALSE; + pData->cdState = FALSE; +} + + +// ----------------------------------------------------------------------- +// fireOnComm - Set commEvent and fire OnComm event to VB +// +// WARNING: After this function returns, any previously obtained pData +// pointer is INVALID. Callers must re-deref via DEREF(hctl). +// ----------------------------------------------------------------------- +static void fireOnComm(HCTL hctl, MsCommDataT FAR *pData, int16_t eventCode) +{ + pData->commEvent = eventCode; + VBFireEvent(hctl, IEVENT_ONCOMM, NULL); + // pData is now stale - caller must re-deref +} + + +// ----------------------------------------------------------------------- +// handleGetProperty - Process VBM_GETPROPERTY for message-based props +// ----------------------------------------------------------------------- +static LONG handleGetProperty(HCTL hctl, MsCommDataT FAR *pData, USHORT iProp) +{ + switch (iProp) { + case IPROP_SETTINGS: + return (LONG)pData->hszSettings; + + case IPROP_INPUT: { + COMSTAT stat; + int16_t bytesToRead; + int16_t bytesRead; + int16_t commId; + int16_t inputLen; + HGLOBAL hMem; + char FAR *buf; + HSZ hsz; + + if (!pData->portOpen || pData->commId < 0) { + return (LONG)VBCreateHsz((_segment)0, ""); + } + + // Save values to locals before any VB API calls + commId = pData->commId; + inputLen = pData->inputLen; + + serialGetStatus(commId, &stat); + bytesToRead = (int16_t)stat.cbInQue; + + if (inputLen > 0 && bytesToRead > inputLen) { + bytesToRead = inputLen; + } + + if (bytesToRead <= 0) { + return (LONG)VBCreateHsz((_segment)0, ""); + } + + // Use GlobalAlloc for potentially large buffers (DLL local heap is small) + hMem = GlobalAlloc(GMEM_MOVEABLE, (DWORD)bytesToRead + 1); + if (hMem == NULL) { + return (LONG)VBCreateHsz((_segment)0, ""); + } + + buf = (char FAR *)GlobalLock(hMem); + bytesRead = serialRead(commId, buf, bytesToRead); + if (bytesRead <= 0) { + GlobalUnlock(hMem); + GlobalFree(hMem); + return (LONG)VBCreateHsz((_segment)0, ""); + } + + buf[bytesRead] = '\0'; + hsz = VBCreateHsz((_segment)0, buf); + GlobalUnlock(hMem); + GlobalFree(hMem); + // pData is stale after VBCreateHsz, but we just return the HSZ + return (LONG)hsz; + } + + case IPROP_INBUFFERCOUNT: { + COMSTAT stat; + if (!pData->portOpen || pData->commId < 0) { + return 0L; + } + serialGetStatus(pData->commId, &stat); + return (LONG)(int16_t)stat.cbInQue; + } + + case IPROP_OUTBUFFERCOUNT: { + COMSTAT stat; + if (!pData->portOpen || pData->commId < 0) { + return 0L; + } + serialGetStatus(pData->commId, &stat); + return (LONG)(int16_t)stat.cbOutQue; + } + + // Modem line status: return shadow state maintained by processEventNotify. + // The 16-bit comm API only provides transition events (GetCommEventMask), + // not current line levels. Shadow state is toggled on each transition. + case IPROP_CDHOLDING: + return (LONG)(BOOL)pData->cdState; + + case IPROP_CTSHOLDING: + return (LONG)(BOOL)pData->ctsState; + + case IPROP_DSRHOLDING: + return (LONG)(BOOL)pData->dsrState; + + case IPROP_PARITYREPLACE: + return (LONG)pData->hszParityReplace; + } + + return 0L; +} + + +// ----------------------------------------------------------------------- +// handleSetProperty - Process VBM_SETPROPERTY for message-based props +// ----------------------------------------------------------------------- +static LONG handleSetProperty(HCTL hctl, MsCommDataT FAR *pData, USHORT iProp, LONG lp) +{ + int16_t val; + + switch (iProp) { + case IPROP_COMMPORT: + val = (int16_t)lp; + if (val < 1 || val > 16) { + return (LONG)ERR_InvPropVal; + } + if (pData->portOpen) { + return (LONG)ERR_InvPropSet; + } + pData->commPort = val; + return 0L; + + case IPROP_SETTINGS: { + HSZ oldHsz; + HSZ newHsz; + LPSTR lpstr; + int16_t commId; + int16_t port; + + // Deref the incoming HSZ (VBDerefHsz does not compact) + lpstr = VBDerefHsz((HSZ)lp); + if (lpstr == NULL) { + return (LONG)ERR_InvPropVal; + } + + // Save old HSZ, create new one + oldHsz = pData->hszSettings; + newHsz = VBCreateHsz((_segment)0, lpstr); + // pData may be stale after VBCreateHsz - re-deref + pData = DEREF(hctl); + pData->hszSettings = newHsz; + + if (oldHsz) { + VBDestroyHsz(oldHsz); + // pData may be stale after VBDestroyHsz - re-deref + pData = DEREF(hctl); + } + + // If port is open, reconfigure immediately + if (pData->portOpen && pData->commId >= 0) { + commId = pData->commId; + port = pData->commPort; + lpstr = VBDerefHsz(pData->hszSettings); + if (serialConfigure(commId, port, lpstr) != 0) { + return (LONG)ERR_InvPropVal; + } + } + return 0L; + } + + case IPROP_PORTOPEN: + if ((BOOL)lp) { + if (pData->portOpen) { + return 0L; + } + if (openPort(hctl, pData) != 0) { + return (LONG)ERR_InvPropVal; + } + } else { + if (!pData->portOpen) { + return 0L; + } + closePort(hctl, pData); + } + return 0L; + + case IPROP_OUTPUT: { + int16_t commId; + LPSTR lpstr; + + if (!pData->portOpen || pData->commId < 0) { + return (LONG)ERR_InvPropSet; + } + + // Save commId before VB API call + commId = pData->commId; + lpstr = VBDerefHsz((HSZ)lp); + if (lpstr != NULL) { + int16_t len = (int16_t)lstrlen(lpstr); + if (len > 0) { + int16_t written = serialWrite(commId, lpstr, len); + if (written < 0) { + // Re-deref before fireOnComm since pData may be stale + pData = DEREF(hctl); + fireOnComm(hctl, pData, COM_EVT_TXFULL); + // pData stale after fireOnComm, but we just return + } + } + } + return 0L; + } + + case IPROP_INBUFFERSIZE: + val = (int16_t)lp; + if (val < 64) { + return (LONG)ERR_InvPropVal; + } + if (pData->portOpen) { + return (LONG)ERR_InvPropSet; + } + pData->inBufferSize = val; + return 0L; + + case IPROP_OUTBUFFERSIZE: + val = (int16_t)lp; + if (val < 64) { + return (LONG)ERR_InvPropVal; + } + if (pData->portOpen) { + return (LONG)ERR_InvPropSet; + } + pData->outBufferSize = val; + return 0L; + + case IPROP_HANDSHAKING: + pData->handshaking = (int16_t)lp; + if (pData->portOpen && pData->commId >= 0) { + serialSetHandshaking(pData->commId, pData->handshaking); + } + return 0L; + + case IPROP_DTRENABLE: + pData->dtrEnable = (BOOL)lp; + if (pData->portOpen && pData->commId >= 0) { + serialEscape(pData->commId, pData->dtrEnable ? SERIAL_SETDTR : SERIAL_CLRDTR); + } + return 0L; + + case IPROP_RTSENABLE: + pData->rtsEnable = (BOOL)lp; + if (pData->portOpen && pData->commId >= 0) { + serialEscape(pData->commId, pData->rtsEnable ? SERIAL_SETRTS : SERIAL_CLRRTS); + } + return 0L; + + case IPROP_BREAK: + pData->breakState = (BOOL)lp; + if (pData->portOpen && pData->commId >= 0) { + serialEscape(pData->commId, pData->breakState ? SERIAL_SETBREAK : SERIAL_CLRBREAK); + } + return 0L; + + case IPROP_NULLDISCARD: { + int16_t commId; + BOOL nullDiscard; + LPSTR lpstr; + + pData->nullDiscard = (BOOL)lp; + if (pData->portOpen && pData->commId >= 0) { + // Save locals before VBDerefHsz + commId = pData->commId; + nullDiscard = pData->nullDiscard; + lpstr = VBDerefHsz(pData->hszParityReplace); + serialSetOptions(commId, nullDiscard, lpstr); + } + return 0L; + } + + case IPROP_PARITYREPLACE: { + HSZ oldHsz; + HSZ newHsz; + int16_t commId; + BOOL nullDiscard; + LPSTR lpstr; + + lpstr = VBDerefHsz((HSZ)lp); + oldHsz = pData->hszParityReplace; + newHsz = VBCreateHsz((_segment)0, lpstr ? lpstr : ""); + // Re-deref after VBCreateHsz + pData = DEREF(hctl); + pData->hszParityReplace = newHsz; + + if (oldHsz) { + VBDestroyHsz(oldHsz); + // Re-deref after VBDestroyHsz + pData = DEREF(hctl); + } + + if (pData->portOpen && pData->commId >= 0) { + commId = pData->commId; + nullDiscard = pData->nullDiscard; + lpstr = VBDerefHsz(pData->hszParityReplace); + serialSetOptions(commId, nullDiscard, lpstr); + } + return 0L; + } + } + + return 0L; +} + + +// ----------------------------------------------------------------------- +// initControlData - Initialize per-instance control data +// +// Re-derefs after each VBCreateHsz to avoid stale pointers. +// ----------------------------------------------------------------------- +static void initControlData(HCTL hctl) +{ + MsCommDataT FAR *pData; + HSZ hsz; + + pData = DEREF(hctl); + pData->commId = -1; + pData->hwndNotify = NULL; + pData->commPort = 1; + pData->inBufferSize = 4096; + pData->outBufferSize = 4096; + pData->rThreshold = 0; + pData->sThreshold = 0; + pData->handshaking = HS_NONE; + pData->inputLen = 0; + pData->inputMode = INPUT_TEXT; + pData->commEvent = 0; + pData->portOpen = FALSE; + pData->dtrEnable = TRUE; + pData->rtsEnable = TRUE; + pData->breakState = FALSE; + pData->nullDiscard = FALSE; + pData->eofEnable = FALSE; + pData->ctsState = FALSE; + pData->dsrState = FALSE; + pData->cdState = FALSE; + + // VBCreateHsz may compact VB's heap - re-deref after each call + hsz = VBCreateHsz((_segment)0, "9600,N,8,1"); + pData = DEREF(hctl); + pData->hszSettings = hsz; + + hsz = VBCreateHsz((_segment)0, "?"); + pData = DEREF(hctl); + pData->hszParityReplace = hsz; +} + + +// ----------------------------------------------------------------------- +// openPort - Open serial port and set up notification window +// +// Saves all needed control data to locals before VB API calls to avoid +// stale pointer issues. Re-derefs when writing results back. +// ----------------------------------------------------------------------- +static int16_t openPort(HCTL hctl, MsCommDataT FAR *pData) +{ + int16_t commId; + int16_t port; + int16_t inBufSize; + int16_t outBufSize; + int16_t handshaking; + int16_t rThreshold; + int16_t sThreshold; + BOOL dtrEnable; + BOOL rtsEnable; + BOOL nullDiscard; + HSZ hszSettings; + HSZ hszParityReplace; + HWND hwndNotify; + LPSTR lpstr; + + // Snapshot all values we need (pData may become stale after VB API calls) + port = pData->commPort; + inBufSize = pData->inBufferSize; + outBufSize = pData->outBufferSize; + handshaking = pData->handshaking; + rThreshold = pData->rThreshold; + sThreshold = pData->sThreshold; + dtrEnable = pData->dtrEnable; + rtsEnable = pData->rtsEnable; + nullDiscard = pData->nullDiscard; + hszSettings = pData->hszSettings; + hszParityReplace = pData->hszParityReplace; + + // Open the comm port + commId = serialOpen(port, inBufSize, outBufSize); + if (commId < 0) { + return -1; + } + + // Configure baud/parity/data/stop + lpstr = VBDerefHsz(hszSettings); + if (serialConfigure(commId, port, lpstr) != 0) { + serialClose(commId); + return -1; + } + + // Apply handshaking + serialSetHandshaking(commId, handshaking); + + // Apply null-discard and parity-replace + lpstr = VBDerefHsz(hszParityReplace); + serialSetOptions(commId, nullDiscard, lpstr); + + // Set DTR and RTS lines + serialEscape(commId, dtrEnable ? SERIAL_SETDTR : SERIAL_CLRDTR); + serialEscape(commId, rtsEnable ? SERIAL_SETRTS : SERIAL_CLRRTS); + + // Create hidden notification window + hwndNotify = CreateWindow( + NOTIFY_CLASS, + "", + WS_POPUP, + 0, 0, 0, 0, + NULL, + NULL, + ghInstance, + NULL + ); + + if (hwndNotify == NULL) { + serialClose(commId); + return -1; + } + + // Store HCTL in notification window for message dispatch + SetWindowWord(hwndNotify, 0, (WORD)hctl); + + // Enable comm notifications + serialEnableNotify(commId, hwndNotify, + rThreshold > 0 ? rThreshold : -1, + sThreshold > 0 ? sThreshold : -1); + + // Re-deref to write results back to control data + pData = DEREF(hctl); + pData->commId = commId; + pData->hwndNotify = hwndNotify; + pData->portOpen = TRUE; + pData->ctsState = FALSE; + pData->dsrState = FALSE; + pData->cdState = FALSE; + return 0; +} + + +// ----------------------------------------------------------------------- +// processEventNotify - Handle CN_EVENT notification +// +// Takes commId as a stable local value. Re-derefs pData after every +// fireOnComm call since VBFireEvent can trigger heap compaction. +// Updates modem line shadow state on CTS/DSR/CD transitions. +// ----------------------------------------------------------------------- +static void processEventNotify(HCTL hctl, int16_t commId) +{ + MsCommDataT FAR *pData; + UINT evtMask; + + evtMask = serialGetEventMask(commId, EV_CTS | EV_DSR | EV_RLSD | EV_RING | EV_ERR | EV_BREAK); + + if (evtMask & EV_BREAK) { + pData = DEREF(hctl); + fireOnComm(hctl, pData, COM_EVT_BREAK); + } + + if (evtMask & EV_CTS) { + pData = DEREF(hctl); + pData->ctsState = !pData->ctsState; + fireOnComm(hctl, pData, COM_EV_CTS); + } + + if (evtMask & EV_DSR) { + pData = DEREF(hctl); + pData->dsrState = !pData->dsrState; + fireOnComm(hctl, pData, COM_EV_DSR); + } + + if (evtMask & EV_RLSD) { + pData = DEREF(hctl); + pData->cdState = !pData->cdState; + fireOnComm(hctl, pData, COM_EV_CD); + } + + if (evtMask & EV_RING) { + pData = DEREF(hctl); + fireOnComm(hctl, pData, COM_EV_RING); + } + + if (evtMask & EV_ERR) { + COMSTAT stat; + int16_t errFlags; + + errFlags = serialGetStatus(commId, &stat); + + if (errFlags & CE_FRAME) { + pData = DEREF(hctl); + fireOnComm(hctl, pData, COM_EVT_FRAME); + } + if (errFlags & CE_OVERRUN) { + pData = DEREF(hctl); + fireOnComm(hctl, pData, COM_EVT_OVERRUN); + } + if (errFlags & CE_RXOVER) { + pData = DEREF(hctl); + fireOnComm(hctl, pData, COM_EVT_RXOVER); + } + if (errFlags & CE_RXPARITY) { + pData = DEREF(hctl); + fireOnComm(hctl, pData, COM_EVT_RXPARITY); + } + if (errFlags & CE_TXFULL) { + pData = DEREF(hctl); + fireOnComm(hctl, pData, COM_EVT_TXFULL); + } + } +} + + +// ----------------------------------------------------------------------- +// processReceiveNotify - Handle CN_RECEIVE notification +// ----------------------------------------------------------------------- +static void processReceiveNotify(HCTL hctl, int16_t commId) +{ + MsCommDataT FAR *pData; + COMSTAT stat; + + pData = DEREF(hctl); + + if (pData->rThreshold <= 0) { + return; + } + + serialGetStatus(commId, &stat); + + if ((int16_t)stat.cbInQue >= pData->rThreshold) { + fireOnComm(hctl, pData, COM_EV_RECEIVE); + // pData stale after fireOnComm, but we just return + } +} + + +// ----------------------------------------------------------------------- +// processTransmitNotify - Handle CN_TRANSMIT notification +// ----------------------------------------------------------------------- +static void processTransmitNotify(HCTL hctl, int16_t commId) +{ + MsCommDataT FAR *pData; + COMSTAT stat; + + pData = DEREF(hctl); + + if (pData->sThreshold <= 0) { + return; + } + + serialGetStatus(commId, &stat); + + if ((int16_t)stat.cbOutQue <= pData->sThreshold) { + fireOnComm(hctl, pData, COM_EV_SEND); + // pData stale after fireOnComm, but we just return + } +} + + +// ----------------------------------------------------------------------- +// registerNotifyClass - Register hidden notification window class +// ----------------------------------------------------------------------- +static BOOL registerNotifyClass(HANDLE hInstance) +{ + WNDCLASS wc; + + wc.style = 0; + wc.lpfnWndProc = NotifyWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = sizeof(WORD); // Room for HCTL + wc.hInstance = hInstance; + wc.hIcon = NULL; + wc.hCursor = NULL; + wc.hbrBackground = NULL; + wc.lpszMenuName = NULL; + wc.lpszClassName = NOTIFY_CLASS; + + return RegisterClass(&wc); +} + + +// ======================================================================= +// Hidden notification window procedure +// +// Receives WM_COMMNOTIFY from the comm driver. Saves commId to a local +// variable and passes it to process* functions, which independently +// deref the control data (avoiding stale pointer chains across VBFireEvent). +// ======================================================================= + +LRESULT FAR PASCAL _export NotifyWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) +{ + if (msg == WM_COMMNOTIFY) { + HCTL hctl; + MsCommDataT FAR *pData; + int16_t commId; + UINT notifyCode; + + hctl = (HCTL)GetWindowWord(hwnd, 0); + if (hctl == 0) { + return 0L; + } + + pData = DEREF(hctl); + if (pData == NULL || pData->commId < 0) { + return 0L; + } + + // Save commId to local - stable across VBFireEvent calls + commId = pData->commId; + notifyCode = LOWORD(lp); + + // Each process* function independently derefs and re-derefs as needed + if (notifyCode & CN_RECEIVE) { + processReceiveNotify(hctl, commId); + } + if (notifyCode & CN_TRANSMIT) { + processTransmitNotify(hctl, commId); + } + if (notifyCode & CN_EVENT) { + processEventNotify(hctl, commId); + } + + return 0L; + } + + return DefWindowProc(hwnd, msg, wp, lp); +} + + +// ======================================================================= +// VBX Control procedure +// ======================================================================= + +LONG FAR PASCAL _export MsCommCtlProc(HCTL hctl, HWND hwnd, USHORT msg, USHORT wp, LONG lp) +{ + MsCommDataT FAR *pData; + + switch (msg) { + case VBM_INITIALIZE: + initControlData(hctl); + return 0L; + + case VBM_SETPROPERTY: + pData = DEREF(hctl); + return handleSetProperty(hctl, pData, wp, lp); + + case VBM_GETPROPERTY: + pData = DEREF(hctl); + return handleGetProperty(hctl, pData, wp); + + case WM_DESTROY: { + HSZ hszSettings; + HSZ hszParityReplace; + + pData = DEREF(hctl); + + // Close port if open + if (pData->portOpen) { + closePort(hctl, pData); + } + + // Save HSZ handles to locals, then destroy them. + // VBDestroyHsz may compact the heap, so don't access pData after. + hszSettings = pData->hszSettings; + hszParityReplace = pData->hszParityReplace; + pData->hszSettings = 0; + pData->hszParityReplace = 0; + + if (hszSettings) { + VBDestroyHsz(hszSettings); + } + if (hszParityReplace) { + VBDestroyHsz(hszParityReplace); + } + break; + } + } + + return VBDefControlProc(hctl, hwnd, msg, wp, lp); +} + + +// ======================================================================= +// DLL entry points +// ======================================================================= + +// ----------------------------------------------------------------------- +// LibMain - DLL initialization +// ----------------------------------------------------------------------- +int FAR PASCAL LibMain(HANDLE hInstance, WORD wDataSeg, WORD wHeapSize, LPSTR lpszCmdLine) +{ + ghInstance = hInstance; + + if (wHeapSize > 0) { + UnlockData(0); + } + + return 1; +} + + +// ----------------------------------------------------------------------- +// VBINITCC - Called by VB to register the control +// ----------------------------------------------------------------------- +BOOL FAR PASCAL _export VBINITCC(USHORT usVersion, BOOL fRuntime) +{ + if (!registerNotifyClass(ghInstance)) { + return FALSE; + } + + return VBRegisterModel(ghInstance, &modelMsComm); +} + + +// ----------------------------------------------------------------------- +// VBTERMCC - Called by VB when unloading the control +// ----------------------------------------------------------------------- +void FAR PASCAL _export VBTERMCC(void) +{ + UnregisterClass(NOTIFY_CLASS, ghInstance); +} + + +// ----------------------------------------------------------------------- +// WEP - DLL termination (required for 16-bit Windows DLLs) +// ----------------------------------------------------------------------- +void FAR PASCAL _export WEP(int nParam) +{ + (void)nParam; +} diff --git a/vbx/mscomm.def b/vbx/mscomm.def new file mode 100644 index 0000000..1bbf55e --- /dev/null +++ b/vbx/mscomm.def @@ -0,0 +1,15 @@ +; mscomm.def - Module definition for MSComm VBX control + +LIBRARY MSCOMM +DESCRIPTION 'MSComm Serial Communications Control for VB4' +EXETYPE WINDOWS + +CODE PRELOAD MOVEABLE DISCARDABLE +DATA PRELOAD MOVEABLE SINGLE + +HEAPSIZE 1024 + +EXPORTS + WEP @1 RESIDENTNAME + VBINITCC @2 + VBTERMCC @3 diff --git a/vbx/mscomm.h b/vbx/mscomm.h new file mode 100644 index 0000000..cbda195 --- /dev/null +++ b/vbx/mscomm.h @@ -0,0 +1,141 @@ +// mscomm.h - MSComm VBX control definitions +// +// Property/event IDs, comm event constants, and control instance data. + +#ifndef MSCOMM_H +#define MSCOMM_H + +#include "vbapi.h" +#include "serial.h" + +// ----------------------------------------------------------------------- +// stdint types for MSVC 1.52 (shared with serial.h) +// ----------------------------------------------------------------------- +#ifndef _STDINT_DEFINED +typedef short int16_t; +typedef unsigned short uint16_t; +typedef long int32_t; +typedef unsigned long uint32_t; +typedef unsigned char uint8_t; +typedef signed char int8_t; +#define _STDINT_DEFINED +#endif + +// ----------------------------------------------------------------------- +// Resource IDs +// ----------------------------------------------------------------------- +#define IDB_MSCOMM 8000 + +// ----------------------------------------------------------------------- +// Property indices (position in npproplist array) +// ----------------------------------------------------------------------- +#define IPROP_CTLNAME 0 +#define IPROP_INDEX 1 +#define IPROP_TAG 2 +#define IPROP_COMMPORT 3 +#define IPROP_SETTINGS 4 +#define IPROP_PORTOPEN 5 +#define IPROP_INPUT 6 +#define IPROP_OUTPUT 7 +#define IPROP_INBUFFERSIZE 8 +#define IPROP_OUTBUFFERSIZE 9 +#define IPROP_INBUFFERCOUNT 10 +#define IPROP_OUTBUFFERCOUNT 11 +#define IPROP_RTHRESHOLD 12 +#define IPROP_STHRESHOLD 13 +#define IPROP_HANDSHAKING 14 +#define IPROP_INPUTLEN 15 +#define IPROP_INPUTMODE 16 +#define IPROP_DTRENABLE 17 +#define IPROP_RTSENABLE 18 +#define IPROP_CDHOLDING 19 +#define IPROP_CTSHOLDING 20 +#define IPROP_DSRHOLDING 21 +#define IPROP_BREAK 22 +#define IPROP_COMMEVENT 23 +#define IPROP_NULLDISCARD 24 +#define IPROP_EOFENABLE 25 +#define IPROP_PARITYREPLACE 26 + +// ----------------------------------------------------------------------- +// Event indices (position in npeventlist array) +// ----------------------------------------------------------------------- +#define IEVENT_ONCOMM 0 + +// ----------------------------------------------------------------------- +// CommEvent constants - Communication events +// ----------------------------------------------------------------------- +#define COM_EV_RECEIVE 1 // Received RThreshold bytes +#define COM_EV_SEND 2 // Transmit buffer has SThreshold space +#define COM_EV_CTS 3 // CTS line changed +#define COM_EV_DSR 4 // DSR line changed +#define COM_EV_CD 5 // CD (RLSD) line changed +#define COM_EV_RING 6 // Ring indicator detected +#define COM_EV_EOF 7 // EOF character received + +// ----------------------------------------------------------------------- +// CommEvent constants - Error events +// ----------------------------------------------------------------------- +#define COM_EVT_BREAK 1001 // Break signal received +#define COM_EVT_FRAME 1004 // Framing error +#define COM_EVT_OVERRUN 1006 // Hardware overrun +#define COM_EVT_RXOVER 1008 // Receive buffer overflow +#define COM_EVT_RXPARITY 1009 // Parity error +#define COM_EVT_TXFULL 1010 // Transmit buffer full + +// ----------------------------------------------------------------------- +// Handshaking enum values +// ----------------------------------------------------------------------- +#define HS_NONE 0 +#define HS_XONXOFF 1 +#define HS_RTSCTS 2 +#define HS_BOTH 3 + +// ----------------------------------------------------------------------- +// InputMode enum values +// ----------------------------------------------------------------------- +#define INPUT_TEXT 0 +#define INPUT_BINARY 1 + +// ----------------------------------------------------------------------- +// Control instance data +// +// Allocated by VB as cbCtlExtra bytes per control instance. +// Accessed via VBDerefControl(hctl). +// ----------------------------------------------------------------------- +typedef struct { + int16_t commId; // OpenComm handle (-1 = closed) + HWND hwndNotify; // Hidden notification window + int16_t commPort; // COM port number (1-16) + HSZ hszSettings; // Settings string "baud,parity,data,stop" + int16_t inBufferSize; // Receive buffer size in bytes + int16_t outBufferSize; // Transmit buffer size in bytes + int16_t rThreshold; // Receive event threshold (0=disabled) + int16_t sThreshold; // Send event threshold (0=disabled) + int16_t handshaking; // Handshaking mode (HS_*) + int16_t inputLen; // Bytes to read per Input (0=all) + int16_t inputMode; // Input mode (INPUT_TEXT/INPUT_BINARY) + int16_t commEvent; // Last comm event code + BOOL portOpen; // Port open state + BOOL dtrEnable; // DTR line enable + BOOL rtsEnable; // RTS line enable + BOOL breakState; // Break signal active + BOOL nullDiscard; // Discard null characters + BOOL eofEnable; // Watch for EOF character + BOOL ctsState; // Shadow state: CTS line level + BOOL dsrState; // Shadow state: DSR line level + BOOL cdState; // Shadow state: CD (RLSD) line level + HSZ hszParityReplace; // Parity error replacement character +} MsCommDataT; + +// ----------------------------------------------------------------------- +// Hidden notification window class name +// ----------------------------------------------------------------------- +#define NOTIFY_CLASS "MsCommNotify" + +// ----------------------------------------------------------------------- +// Global instance handle (set in LibMain) +// ----------------------------------------------------------------------- +extern HANDLE ghInstance; + +#endif // MSCOMM_H diff --git a/vbx/mscomm.rc b/vbx/mscomm.rc new file mode 100644 index 0000000..8af4455 --- /dev/null +++ b/vbx/mscomm.rc @@ -0,0 +1,7 @@ +// mscomm.rc - MSComm VBX resource script +// +// Defines the toolbox bitmap displayed in the VB IDE custom controls palette. + +#include "mscomm.h" + +IDB_MSCOMM BITMAP mscomm.bmp diff --git a/vbx/serial.c b/vbx/serial.c new file mode 100644 index 0000000..05cc0fe --- /dev/null +++ b/vbx/serial.c @@ -0,0 +1,253 @@ +// serial.c - Serial port abstraction layer +// +// Wraps Windows 3.1 comm API for use by the MSComm VBX control. +// All functions operate on a comm ID returned by serialOpen(). + +#include +#include +#include "serial.h" + +// ----------------------------------------------------------------------- +// Prototypes +// ----------------------------------------------------------------------- +int16_t serialClose(int16_t commId); +int16_t serialConfigure(int16_t commId, int16_t port, const char FAR *settings); +int16_t serialEnableNotify(int16_t commId, HWND hwnd, int16_t rxThreshold, int16_t txThreshold); +int16_t serialEscape(int16_t commId, int16_t func); +int16_t serialFlush(int16_t commId, int16_t queue); +UINT serialGetEventMask(int16_t commId, UINT mask); +int16_t serialGetStatus(int16_t commId, COMSTAT FAR *stat); +int16_t serialOpen(int16_t port, int16_t inBufSize, int16_t outBufSize); +int16_t serialRead(int16_t commId, char FAR *buf, int16_t len); +int16_t serialSetHandshaking(int16_t commId, int16_t mode); +int16_t serialSetOptions(int16_t commId, BOOL nullDiscard, const char FAR *parityReplace); +int16_t serialWrite(int16_t commId, const char FAR *buf, int16_t len); + + +// ----------------------------------------------------------------------- +// serialClose - Close an open comm port +// ----------------------------------------------------------------------- +int16_t serialClose(int16_t commId) +{ + // Drop DTR and RTS before closing + EscapeCommFunction(commId, CLRDTR); + EscapeCommFunction(commId, CLRRTS); + + return (int16_t)CloseComm(commId); +} + + +// ----------------------------------------------------------------------- +// serialConfigure - Set baud, parity, data bits, stop bits +// ----------------------------------------------------------------------- +int16_t serialConfigure(int16_t commId, int16_t port, const char FAR *settings) +{ + DCB dcb; + char buf[64]; + int rc; + + // Get current state to preserve existing settings + rc = GetCommState(commId, &dcb); + if (rc != 0) { + return -1; + } + + // Build DCB from "COMn:baud,parity,data,stop" string + wsprintf(buf, "COM%d:%s", (int)port, (LPSTR)settings); + rc = BuildCommDCB(buf, &dcb); + if (rc != 0) { + return -1; + } + + // Ensure binary mode for reliable operation + dcb.fBinary = 1; + + // Ensure comm ID matches our open port + dcb.Id = (BYTE)commId; + + rc = SetCommState(&dcb); + if (rc != 0) { + return -1; + } + + return 0; +} + + +// ----------------------------------------------------------------------- +// serialEnableNotify - Enable WM_COMMNOTIFY messages +// ----------------------------------------------------------------------- +BOOL serialEnableNotify(int16_t commId, HWND hwnd, int16_t rxThreshold, int16_t txThreshold) +{ + // Enable event mask for modem status line changes, errors, and breaks + SetCommEventMask(commId, EV_CTS | EV_DSR | EV_RLSD | EV_RING | EV_ERR | EV_BREAK | EV_RXCHAR); + + return EnableCommNotification(commId, hwnd, rxThreshold, txThreshold); +} + + +// ----------------------------------------------------------------------- +// serialEscape - Execute escape function (DTR, RTS, break control) +// ----------------------------------------------------------------------- +int16_t serialEscape(int16_t commId, int16_t func) +{ + LONG rc; + + rc = EscapeCommFunction(commId, func); + return (rc == 0) ? 0 : -1; +} + + +// ----------------------------------------------------------------------- +// serialFlush - Flush receive or transmit buffer +// ----------------------------------------------------------------------- +int16_t serialFlush(int16_t commId, int16_t queue) +{ + return (int16_t)FlushComm(commId, queue); +} + + +// ----------------------------------------------------------------------- +// serialGetEventMask - Get and clear event mask bits +// ----------------------------------------------------------------------- +UINT serialGetEventMask(int16_t commId, UINT mask) +{ + return GetCommEventMask(commId, mask); +} + + +// ----------------------------------------------------------------------- +// serialGetStatus - Get error status and buffer counts +// ----------------------------------------------------------------------- +int16_t serialGetStatus(int16_t commId, COMSTAT FAR *stat) +{ + return (int16_t)GetCommError(commId, stat); +} + + +// ----------------------------------------------------------------------- +// serialOpen - Open a COM port with specified buffer sizes +// ----------------------------------------------------------------------- +int16_t serialOpen(int16_t port, int16_t inBufSize, int16_t outBufSize) +{ + char name[8]; + int commId; + + wsprintf(name, "COM%d", (int)port); + commId = OpenComm(name, (UINT)inBufSize, (UINT)outBufSize); + + return (int16_t)commId; +} + + +// ----------------------------------------------------------------------- +// serialRead - Read data from receive buffer +// ----------------------------------------------------------------------- +int16_t serialRead(int16_t commId, char FAR *buf, int16_t len) +{ + return (int16_t)ReadComm(commId, (LPSTR)buf, len); +} + + +// ----------------------------------------------------------------------- +// serialSetHandshaking - Apply handshaking mode +// ----------------------------------------------------------------------- +int16_t serialSetHandshaking(int16_t commId, int16_t mode) +{ + DCB dcb; + int rc; + + rc = GetCommState(commId, &dcb); + if (rc != 0) { + return -1; + } + + // Clear all flow control settings + dcb.fOutxCtsFlow = 0; + dcb.fOutxDsrFlow = 0; + dcb.fOutX = 0; + dcb.fInX = 0; + dcb.fRtsDisable = 0; + dcb.fRtsflow = 0; + + switch (mode) { + case HANDSHAKE_XONXOFF: + dcb.fOutX = 1; + dcb.fInX = 1; + dcb.XonChar = 0x11; // Ctrl-Q + dcb.XoffChar = 0x13; // Ctrl-S + dcb.XonLim = 256; + dcb.XoffLim = 256; + break; + + case HANDSHAKE_RTSCTS: + dcb.fOutxCtsFlow = 1; + dcb.fRtsflow = 1; + break; + + case HANDSHAKE_BOTH: + dcb.fOutxCtsFlow = 1; + dcb.fRtsflow = 1; + dcb.fOutX = 1; + dcb.fInX = 1; + dcb.XonChar = 0x11; + dcb.XoffChar = 0x13; + dcb.XonLim = 256; + dcb.XoffLim = 256; + break; + + case HANDSHAKE_NONE: + default: + break; + } + + dcb.Id = (BYTE)commId; + rc = SetCommState(&dcb); + if (rc != 0) { + return -1; + } + + return 0; +} + + +// ----------------------------------------------------------------------- +// serialSetOptions - Apply null-discard and parity-replace settings +// ----------------------------------------------------------------------- +int16_t serialSetOptions(int16_t commId, BOOL nullDiscard, const char FAR *parityReplace) +{ + DCB dcb; + int rc; + + rc = GetCommState(commId, &dcb); + if (rc != 0) { + return -1; + } + + dcb.fNull = nullDiscard ? 1 : 0; + + if (parityReplace != NULL && parityReplace[0] != '\0') { + dcb.fParity = 1; + dcb.PeChar = parityReplace[0]; + } else { + dcb.fParity = 0; + dcb.PeChar = '\0'; + } + + dcb.Id = (BYTE)commId; + rc = SetCommState(&dcb); + if (rc != 0) { + return -1; + } + + return 0; +} + + +// ----------------------------------------------------------------------- +// serialWrite - Write data to transmit buffer +// ----------------------------------------------------------------------- +int16_t serialWrite(int16_t commId, const char FAR *buf, int16_t len) +{ + return (int16_t)WriteComm(commId, (LPSTR)buf, len); +} diff --git a/vbx/serial.h b/vbx/serial.h new file mode 100644 index 0000000..2abe8a7 --- /dev/null +++ b/vbx/serial.h @@ -0,0 +1,106 @@ +// serial.h - Serial port abstraction header +// +// Wraps Windows 3.1 comm API (OpenComm, ReadComm, WriteComm, etc.) +// for use by the MSComm VBX control. + +#ifndef SERIAL_H +#define SERIAL_H + +#include + +// ----------------------------------------------------------------------- +// stdint types for MSVC 1.52 (no stdint.h available) +// ----------------------------------------------------------------------- +#ifndef _STDINT_DEFINED +typedef short int16_t; +typedef unsigned short uint16_t; +typedef long int32_t; +typedef unsigned long uint32_t; +typedef unsigned char uint8_t; +typedef signed char int8_t; +#define _STDINT_DEFINED +#endif + +// ----------------------------------------------------------------------- +// Handshaking modes +// ----------------------------------------------------------------------- +#define HANDSHAKE_NONE 0 +#define HANDSHAKE_XONXOFF 1 +#define HANDSHAKE_RTSCTS 2 +#define HANDSHAKE_BOTH 3 + +// ----------------------------------------------------------------------- +// Flush queue selectors +// ----------------------------------------------------------------------- +#define FLUSH_RX 0 +#define FLUSH_TX 1 + +// ----------------------------------------------------------------------- +// Escape function constants (mirrors EscapeCommFunction values) +// ----------------------------------------------------------------------- +#define SERIAL_SETDTR SETDTR +#define SERIAL_CLRDTR CLRDTR +#define SERIAL_SETRTS SETRTS +#define SERIAL_CLRRTS CLRRTS +#define SERIAL_SETBREAK SETBREAK +#define SERIAL_CLRBREAK CLRBREAK + +// ----------------------------------------------------------------------- +// Function prototypes +// ----------------------------------------------------------------------- + +// Open a COM port with specified buffer sizes. +// Returns comm ID (>= 0) on success, negative on error. +int16_t serialOpen(int16_t port, int16_t inBufSize, int16_t outBufSize); + +// Close an open comm port. Drops DTR and RTS before closing. +// Returns 0 on success, negative on error. +int16_t serialClose(int16_t commId); + +// Configure baud, parity, data bits, stop bits from a settings string. +// settings format: "baud,parity,data,stop" (e.g., "9600,N,8,1") +// Returns 0 on success, negative on error. +int16_t serialConfigure(int16_t commId, int16_t port, const char FAR *settings); + +// Apply handshaking mode to an open port. +// mode: HANDSHAKE_NONE, HANDSHAKE_XONXOFF, HANDSHAKE_RTSCTS, HANDSHAKE_BOTH +// Returns 0 on success, negative on error. +int16_t serialSetHandshaking(int16_t commId, int16_t mode); + +// Apply null-discard and parity-replace settings to an open port. +// Returns 0 on success, negative on error. +int16_t serialSetOptions(int16_t commId, BOOL nullDiscard, const char FAR *parityReplace); + +// Read data from receive buffer. +// Returns number of bytes read, or negative on error. +int16_t serialRead(int16_t commId, char FAR *buf, int16_t len); + +// Write data to transmit buffer. +// Returns number of bytes written, or negative on error. +int16_t serialWrite(int16_t commId, const char FAR *buf, int16_t len); + +// Get comm error status and buffer counts. Clears the error state. +// Returns error flags, fills stat with buffer counts. +int16_t serialGetStatus(int16_t commId, COMSTAT FAR *stat); + +// Execute an escape function (DTR, RTS, break control). +// func: SERIAL_SETDTR, SERIAL_CLRDTR, SERIAL_SETRTS, etc. +// Returns 0 on success, negative on error. +int16_t serialEscape(int16_t commId, int16_t func); + +// Get modem status lines via GetCommEventMask. +// Returns event mask bits (EV_CTS, EV_DSR, EV_RLSD, EV_RING). +UINT serialGetEventMask(int16_t commId, UINT mask); + +// Enable WM_COMMNOTIFY messages to the specified window. +// rxThreshold: fire CN_RECEIVE when this many bytes available (-1=disable) +// txThreshold: fire CN_TRANSMIT when this much space free (-1=disable) +// Returns TRUE on success. +BOOL serialEnableNotify(int16_t commId, HWND hwnd, int16_t rxThreshold, int16_t txThreshold); + +// Flush receive and/or transmit buffers. +// queue: FLUSH_RX or FLUSH_TX +// Returns 0 on success, negative on error. +int16_t serialFlush(int16_t commId, int16_t queue); + +#endif // SERIAL_H diff --git a/vbx/vbapi.def b/vbx/vbapi.def new file mode 100644 index 0000000..458d00d --- /dev/null +++ b/vbx/vbapi.def @@ -0,0 +1,29 @@ +; vbapi.def - VB API import library definition +; +; Defines the VB API functions exported by the VBAPI module. +; Use with IMPLIB to generate the import library: +; +; implib vbapi.lib vbapi.def +; +; The VBAPI module is the VB runtime that exports these functions. +; When VB loads a VBX, it resolves imports against this module. + +LIBRARY VBAPI + +EXPORTS + VBRegisterModel @100 + VBDefControlProc @101 + VBFireEvent @102 + VBCreateHsz @103 + VBDestroyHsz @104 + VBDerefHsz @105 + VBCreateHlstr @106 + VBDestroyHlstr @107 + VBGetHlstr @108 + VBSetHlstr @109 + VBDerefControl @110 + VBGetMode @111 + VBSetControlProperty @112 + VBGetControlProperty @113 + VBGetControlHwnd @114 + VBGetHInstance @115 diff --git a/vbx/vbapi.h b/vbx/vbapi.h new file mode 100644 index 0000000..a40a5d6 --- /dev/null +++ b/vbx/vbapi.h @@ -0,0 +1,266 @@ +// vbapi.h - Reconstructed VBX CDK header +// +// Reconstructed from the published Visual Basic Custom Control (VBX) +// specification for use with MSVC 1.52 targeting 16-bit Visual Basic 4. +// Types, structures, constants, and API prototypes match the VBX CDK. + +#ifndef VBAPI_H +#define VBAPI_H + +#include + +// ----------------------------------------------------------------------- +// Packing: VBX CDK structures use byte packing +// ----------------------------------------------------------------------- +#pragma pack(1) + +// ----------------------------------------------------------------------- +// Core types +// ----------------------------------------------------------------------- +typedef WORD HCTL; // Handle to a VBX control instance +typedef WORD HSZ; // Handle to a VB-managed string +typedef LONG HLSTR; // Handle to a VB long string +typedef USHORT ERR; // Error code + +// Segment type for MSVC 1.52 +#ifndef _SEGMENT_DEFINED +typedef unsigned short _segment; +#define _SEGMENT_DEFINED +#endif + +// Flag type +typedef DWORD FL; + +// ----------------------------------------------------------------------- +// Forward declarations and pointer types +// ----------------------------------------------------------------------- +typedef struct tagMODEL MODEL; +typedef struct tagPROPINFO PROPINFO; +typedef struct tagEVENTINFO EVENTINFO; +typedef struct tagPARMINFO PARMINFO; + +typedef MODEL FAR *LPMODEL; +typedef PROPINFO *PPROPINFO; // Near pointer (DLL data segment) +typedef EVENTINFO *PEVENTINFO; // Near pointer (DLL data segment) +typedef PARMINFO *PPARMINFO; // Near pointer (DLL data segment) + +// ----------------------------------------------------------------------- +// Control procedure type +// ----------------------------------------------------------------------- +typedef LONG (FAR PASCAL *PCTLPROC)(HCTL, HWND, USHORT, USHORT, LONG); + +// ----------------------------------------------------------------------- +// VB version constants +// ----------------------------------------------------------------------- +#define VB_VERSION 0x0300 +#define VB300_VERSION 0x0300 + +// ----------------------------------------------------------------------- +// Data types (bits 0-7 of PROPINFO.fl) +// ----------------------------------------------------------------------- +#define DT_HSZ 0x01 +#define DT_SHORT 0x02 +#define DT_LONG 0x03 +#define DT_BOOL 0x04 +#define DT_COLOR 0x05 +#define DT_ENUM 0x06 +#define DT_REAL 0x07 +#define DT_XPOS 0x08 +#define DT_XSIZE 0x09 +#define DT_YPOS 0x0A +#define DT_YSIZE 0x0B +#define DT_PICTURE 0x0C +#define DT_HLSTR 0x0D + +// ----------------------------------------------------------------------- +// Property flags (bits 8+ of PROPINFO.fl) +// ----------------------------------------------------------------------- +#define PF_datatype 0x000000FFL // Mask for data type field +#define PF_fPropArray 0x00000100L +#define PF_fSetData 0x00000200L +#define PF_fSetMsg 0x00000400L +#define PF_fNoShow 0x00000800L +#define PF_fNoRuntimeW 0x00001000L +#define PF_fGetData 0x00002000L +#define PF_fGetMsg 0x00004000L +#define PF_fGetHszMsg 0x00008000L +#define PF_fUpdateOnEdit 0x00010000L +#define PF_fEditable 0x00020000L +#define PF_fPreHwnd 0x00040000L +#define PF_fDefVal 0x00080000L +#define PF_fNoInitDef 0x00100000L +#define PF_fNoRuntimeR 0x00200000L +#define PF_fSaveData 0x00400000L +#define PF_fSaveMsg 0x00800000L +#define PF_fLoadDataOnly 0x01000000L +#define PF_fLoadMsgOnly 0x02000000L + +// ----------------------------------------------------------------------- +// MODEL flags +// ----------------------------------------------------------------------- +#define MODEL_fArrows 0x00000001L +#define MODEL_fFocusOk 0x00000002L +#define MODEL_fMnemonic 0x00000004L +#define MODEL_fDesInteract 0x00000008L +#define MODEL_fInitMsg 0x00000010L +#define MODEL_fLoadMsg 0x00000020L +#define MODEL_fInvisAtRun 0x00000040L +#define MODEL_fGraphical 0x00000080L + +// ----------------------------------------------------------------------- +// VBX control messages +// ----------------------------------------------------------------------- +#define VBM__BASE (WM_USER + 0x0600) +#define VBM_INITIALIZE (VBM__BASE + 0) +#define VBM_SETPROPERTY (VBM__BASE + 1) +#define VBM_GETPROPERTY (VBM__BASE + 2) +#define VBM_CHECKPROPERTY (VBM__BASE + 3) +#define VBM_MNEMONIC (VBM__BASE + 4) +#define VBM_CREATED (VBM__BASE + 5) +#define VBM_LOADED (VBM__BASE + 6) +#define VBM_SAVEPROPERTY (VBM__BASE + 7) +#define VBM_LOADPROPERTY (VBM__BASE + 8) +#define VBM_METHOD (VBM__BASE + 9) + +// ----------------------------------------------------------------------- +// Error codes +// ----------------------------------------------------------------------- +#define ERR_None 0 +#define ERR_InvPropVal 380 // Invalid property value +#define ERR_InvPropSet 383 // Can't set property at this time + +// ----------------------------------------------------------------------- +// Standard property pointers +// +// Magic values recognized by VB as built-in properties. +// ----------------------------------------------------------------------- +#define PPROPINFO_STD_CTLNAME ((PPROPINFO)0) +#define PPROPINFO_STD_INDEX ((PPROPINFO)1) +#define PPROPINFO_STD_HWND ((PPROPINFO)2) +#define PPROPINFO_STD_TAG ((PPROPINFO)5) +#define PPROPINFO_STD_LEFT ((PPROPINFO)6) +#define PPROPINFO_STD_TOP ((PPROPINFO)7) +#define PPROPINFO_STD_WIDTH ((PPROPINFO)8) +#define PPROPINFO_STD_HEIGHT ((PPROPINFO)9) +#define PPROPINFO_STD_ENABLED ((PPROPINFO)17) +#define PPROPINFO_STD_VISIBLE ((PPROPINFO)18) +#define PPROPINFO_STD_PARENT ((PPROPINFO)21) +#define PPROPINFO_STD_DRAGMODE ((PPROPINFO)22) +#define PPROPINFO_STD_DRAGICON ((PPROPINFO)23) +#define PPROPINFO_STD_NONE ((PPROPINFO)NULL) + +// Aliases matching the plan's naming convention +#define PPROPINFO_STD_NAME PPROPINFO_STD_CTLNAME + +// ----------------------------------------------------------------------- +// PROPINFO - Property descriptor +// ----------------------------------------------------------------------- +struct tagPROPINFO { + PSTR npszName; // Property name (near pointer, DLL DS) + FL fl; // Data type (bits 0-7) | Property flags (bits 8+) + BYTE offsetData; // Byte offset in control's cbCtlExtra data + BYTE infoData; // Type-specific info (e.g., max string length) + LONG dataDefault; // Default value + PSTR npszEnumList; // DT_ENUM: null-separated, double-null terminated + BYTE enumMax; // DT_ENUM: maximum valid value +}; + +// ----------------------------------------------------------------------- +// PARMINFO - Event parameter descriptor +// ----------------------------------------------------------------------- +struct tagPARMINFO { + PSTR npszName; // Parameter name + FL fl; // Data type +}; + +// ----------------------------------------------------------------------- +// EVENTINFO - Event descriptor +// ----------------------------------------------------------------------- +struct tagEVENTINFO { + PSTR npszName; // Event name + USHORT cParms; // Number of parameters + USHORT cwParms; // Size of parameters in words + PPARMINFO npParmInfo; // Parameter info array (NULL if no params) + PSTR npszParmProf; // Parameter profile string (e.g., "value As Integer") + FL fl; // Event flags +}; + +// ----------------------------------------------------------------------- +// MODEL - Control model definition +// +// VB3+ version (usVersion = VB300_VERSION) for VB4 compatibility. +// Registered with VBRegisterModel() in VBINITCC. +// ----------------------------------------------------------------------- +struct tagMODEL { + USHORT usVersion; // VB_VERSION (0x0300) + FL fl; // MODEL_* flags + PCTLPROC pctlproc; // Control procedure + USHORT fsClassStyle; // Window class style bits + USHORT flWndStyle; // Window style bits + USHORT cbCtlExtra; // Bytes of per-instance data + USHORT idBmpPalette; // Toolbox bitmap resource ID + PSTR npszDefCtlName; // Default control name (near) + PSTR npszClassName; // Window class name (NULL = default) + PSTR npszParentClassName; // Parent class name (NULL = default) + PPROPINFO *npproplist; // Property list (NULL-terminated array) + PEVENTINFO *npeventlist; // Event list (NULL-terminated array) + BYTE nDefProp; // Default property index + BYTE nDefEvent; // Default event index + BYTE nValueProp; // Value property index + USHORT usCtlVersion; // Control version (BCD, user-defined) +}; + +// ----------------------------------------------------------------------- +// Utility macro: byte offset of a field within a structure +// ----------------------------------------------------------------------- +#define OFFSETIN(type, field) ((BYTE)&(((type *)0)->field)) + +// ----------------------------------------------------------------------- +// VB API function prototypes +// +// These functions are exported by the VB runtime (VBAPI module) and +// resolved at load time when VB loads the VBX. +// ----------------------------------------------------------------------- + +// Control registration +BOOL FAR PASCAL VBRegisterModel(HANDLE hmodule, MODEL FAR *lpmodel); + +// Default control procedure +LONG FAR PASCAL VBDefControlProc(HCTL hctl, HWND hwnd, USHORT msg, USHORT wp, LONG lp); + +// Event firing +ERR FAR PASCAL VBFireEvent(HCTL hctl, USHORT iEvent, LPVOID lpparams); + +// String handle management +HSZ FAR PASCAL VBCreateHsz(_segment seg, LPSTR lpsz); +void FAR PASCAL VBDestroyHsz(HSZ hsz); +LPSTR FAR PASCAL VBDerefHsz(HSZ hsz); + +// Long string handle management +HLSTR FAR PASCAL VBCreateHlstr(LPVOID lpdata, USHORT cbLen); +void FAR PASCAL VBDestroyHlstr(HLSTR hlstr); +LPSTR FAR PASCAL VBGetHlstr(HLSTR hlstr, USHORT FAR *lpcbLen); +ERR FAR PASCAL VBSetHlstr(HLSTR FAR *phlstr, LPVOID lpdata, USHORT cbLen); + +// Control data access +LPVOID FAR PASCAL VBDerefControl(HCTL hctl); + +// Runtime mode query (TRUE = runtime, FALSE = design) +BOOL FAR PASCAL VBGetMode(void); + +// Control property access +ERR FAR PASCAL VBSetControlProperty(HCTL hctl, USHORT iProp, LONG data); +LONG FAR PASCAL VBGetControlProperty(HCTL hctl, USHORT iProp); + +// Control window handle +HWND FAR PASCAL VBGetControlHwnd(HCTL hctl); + +// Instance handle +HANDLE FAR PASCAL VBGetHInstance(void); + +// ----------------------------------------------------------------------- +// Restore default packing +// ----------------------------------------------------------------------- +#pragma pack() + +#endif // VBAPI_H