WinComm/drv/isr.c
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

530 lines
15 KiB
C

// 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 <dos.h>
// -----------------------------------------------------------------------
// 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->comDeb.evtWord & port->comDeb.evtMask) {
notifyBits |= CN_EVENT;
}
if (notifyBits && pfnPostMessage) {
pfnPostMessage(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->comDeb.evtWord |= EV_BREAK;
}
if (lsr & (LSR_OE | LSR_PE | LSR_FE)) {
port->comDeb.evtWord |= EV_ERR;
}
}
// -----------------------------------------------------------------------
// handleMsr - Process modem status changes
// -----------------------------------------------------------------------
static void handleMsr(PortStateT *port)
{
uint8_t msr;
msr = (uint8_t)_inp(port->baseAddr + UART_MSR);
// Update ComDEB MSR shadow (offset 35) for third-party code
port->comDeb.msrShadow = msr;
if (msr & MSR_DCTS) {
port->comDeb.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->comDeb.evtWord |= EV_DSR;
}
if (msr & MSR_TERI) {
port->comDeb.evtWord |= EV_RING;
}
if (msr & MSR_DDCD) {
port->comDeb.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->comDeb.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->comDeb.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;
}
}
// Sync queue counts into ComDEB for third-party code
port->comDeb.qInCount = port->rxCount;
port->comDeb.qInHead = port->rxHead;
port->comDeb.qInTail = port->rxTail;
port->comDeb.qOutCount = port->txCount;
port->comDeb.qOutHead = port->txHead;
port->comDeb.qOutTail = port->txTail;
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;
// Load DLL's data segment -- _interrupt saves/restores DS but doesn't
// set it. Without this, DS belongs to whatever code was interrupted
// and all global accesses (ports[], ring buffers) read garbage.
_asm mov ax, seg ports
_asm mov ds, ax
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;
// Load DLL's data segment -- see comment in isr3
_asm mov ax, seg ports
_asm mov ds, ax
isrHitCount++;
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);
}