// 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 // ----------------------------------------------------------------------- // 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); }