LIBRARY name stays COMM for import resolution. Debug strings updated to KPCOMM. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
530 lines
15 KiB
C
530 lines
15 KiB
C
// isr.c - Interrupt service routines for KPCOMM.DRV
|
|
//
|
|
// 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);
|
|
}
|