WinComm/drv/isr.c
2026-02-23 20:53:02 -06:00

507 lines
14 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->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);
}