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

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

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

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

448 lines
18 KiB
C

// 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 <windows.h>
#include <conio.h>
// -----------------------------------------------------------------------
// 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.
// -----------------------------------------------------------------------
// Most CBR_* constants defined by windows.h; add any missing ones
#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
#endif
#ifndef CBR_14400
#define CBR_14400 0xFF17
#endif
#ifndef CBR_19200
#define CBR_19200 0xFF18
#endif
#ifndef CBR_38400
#define CBR_38400 0xFF1B
#endif
#ifndef CBR_56000
#define CBR_56000 0xFF1F
#endif
#ifndef CBR_115200
#define CBR_115200 0xFF24
#endif
// -----------------------------------------------------------------------
// 16550 FIFO depth
// -----------------------------------------------------------------------
#define FIFO_DEPTH 16
// -----------------------------------------------------------------------
// Comm error flags (CE_* -- most defined by windows.h, fill any gaps)
// -----------------------------------------------------------------------
#ifndef CE_RXOVER
#define CE_RXOVER 0x0001
#endif
#ifndef CE_OVERRUN
#define CE_OVERRUN 0x0002
#endif
#ifndef CE_RXPARITY
#define CE_RXPARITY 0x0004
#endif
#ifndef CE_FRAME
#define CE_FRAME 0x0008
#endif
#ifndef CE_BREAK
#define CE_BREAK 0x0010
#endif
#ifndef CE_TXFULL
#define CE_TXFULL 0x0020
#endif
#ifndef CE_MODE
#define CE_MODE 0x8000
#endif
// -----------------------------------------------------------------------
// 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
//
// SETXON through RESETDEV defined by windows.h.
// SETBREAK/CLRBREAK are our extensions for routing through cextfcn.
// -----------------------------------------------------------------------
#ifndef SETXON
#define SETXON 1
#define SETXOFF 2
#define SETRTS 3
#define CLRRTS 4
#define SETDTR 5
#define CLRDTR 6
#define RESETDEV 7
#endif
#define ESC_SETBREAK 8
#define ESC_CLRBREAK 9
// -----------------------------------------------------------------------
// Flush queue selectors
// -----------------------------------------------------------------------
#define FLUSH_RX 0
#define FLUSH_TX 1
// -----------------------------------------------------------------------
// Stock COMM.DRV compatible ComDEB structure
//
// SetCommEventMask (cevt) returns a FAR pointer to offset 0 of this
// structure. Third-party code (ProComm COMMTASK.DLL, etc.) accesses
// fields at known offsets -- most importantly offset 35 (MSR shadow)
// per Microsoft KB Q101417. Our layout must match the stock driver.
// -----------------------------------------------------------------------
typedef struct {
uint16_t evtWord; // +0: Accumulated event flags (EV_*)
uint16_t evtMask; // +2: Event enable mask
uint16_t qInAddr; // +4: RX queue offset (compat, unused)
uint16_t qInSize; // +6: RX buffer size
uint16_t qInCount; // +8: RX bytes in buffer
uint16_t qInHead; // +10: RX write position
uint16_t qInTail; // +12: RX read position
uint16_t qOutAddr; // +14: TX queue offset (compat, unused)
uint16_t qOutSize; // +16: TX buffer size
uint16_t qOutCount; // +18: TX bytes in buffer
uint16_t qOutHead; // +20: TX write position
uint16_t qOutTail; // +22: TX read position
uint16_t port; // +24: UART base I/O address
uint16_t baudRate; // +26: Current baud rate
uint8_t lcrShadow; // +28: LCR shadow
uint8_t mcrShadow; // +29: MCR shadow
uint8_t ierShadow; // +30: IER shadow
uint8_t commErr; // +31: Error flags (low byte)
uint8_t flags1; // +32: Internal flags
uint8_t flags2; // +33: More internal flags
uint8_t recvTrigger; // +34: FIFO trigger level
uint8_t msrShadow; // +35: MSR shadow (documented KB Q101417)
uint8_t padding[4]; // +36: Pad to 40 bytes
} ComDebT;
// -----------------------------------------------------------------------
// Port state structure
// -----------------------------------------------------------------------
typedef struct {
// Stock-compatible ComDEB -- must be kept synchronized.
// cevt returns a FAR pointer to this.
ComDebT comDeb;
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
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];
// -----------------------------------------------------------------------
// Dynamically resolved PostMessage (USER.EXE not loaded at boot)
// -----------------------------------------------------------------------
typedef BOOL (FAR PASCAL *PostMessageProcT)(HWND, UINT, WPARAM, LPARAM);
extern PostMessageProcT pfnPostMessage;
// ISR hit counter for diagnostics
extern volatile uint16_t isrHitCount;
// -----------------------------------------------------------------------
// 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);
int16_t FAR PASCAL _export setcom(DCB FAR *dcb);
int16_t FAR PASCAL _export setque(int16_t commId, int16_t rxSize, int16_t txSize);
int16_t FAR PASCAL _export reccom(int16_t commId, void FAR *buf, int16_t len);
int16_t FAR PASCAL _export sndcom(int16_t commId, void FAR *buf, int16_t len);
int16_t FAR PASCAL _export ctx(int16_t commId, int16_t ch);
int16_t FAR PASCAL _export trmcom(int16_t commId);
int16_t FAR PASCAL _export stacom(int16_t commId, COMSTAT FAR *stat);
int32_t FAR PASCAL _export cevt(int16_t commId, int16_t evtMask);
uint16_t FAR PASCAL _export cevtget(int16_t commId, int16_t evtMask);
int16_t FAR PASCAL _export cextfcn(int16_t commId, int16_t func);
int16_t FAR PASCAL _export cflush(int16_t commId, int16_t queue);
int16_t FAR PASCAL _export csetbrk(int16_t commId);
int16_t FAR PASCAL _export cclrbrk(int16_t commId);
DCB FAR * FAR PASCAL _export getdcb(int16_t commId);
void FAR PASCAL _export suspendOpenCommPorts(void);
void FAR PASCAL _export reactivateOpenCommPorts(void);
int16_t FAR PASCAL _export commWriteString(int16_t commId, void FAR *buf, int16_t len);
int16_t FAR PASCAL _export readCommString(int16_t commId, void FAR *buf, int16_t len);
int16_t FAR PASCAL _export enableNotification(int16_t commId, HWND hwnd, int16_t rxThresh, int16_t txThresh);
int FAR PASCAL _export commNotifyWndProc(void);
// -----------------------------------------------------------------------
// 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