// Part of KPCOMM, Copyright (c) 2026 Scott Duensing, under the MIT License. // See LICENSE.md for license information. // SPDX-License-Identifier: MIT // // isr.c - Interrupt-time code for KPCOMM.DRV // // Hardware interrupt entry points, DPMI vector hooking with a per-IRQ // table (shared IRQs, slave PIC, chaining), UART interrupt dispatch, and // the transmit, flow-control, and notification helpers that the // application-context exports share with the ISR. #include "commdrv.h" #define MAX_IIR_LOOPS 64 // Bound on IIR reads per port per interrupt (stuck hardware guard) #define MAX_IRQ_PASSES 32 // Bound on full passes over ports sharing one IRQ #define MAX_RX_BURST (FIFO_DEPTH * 4) // Bound on bytes drained per RX interrupt source typedef void (_far _interrupt *IsrProcT)(void); // Per-IRQ hook state typedef struct { IsrProcT handler; // Our entry point for this IRQ (NULL = unsupported) IsrProcT prevIsr; // Vector installed before ours int16_t refCount; // Open ports using this IRQ bool wasMasked; // PIC mask bit before our first hook } IrqHookT; // ----------------------------------------------------------------------- // Prototypes (public functions are declared in commdrv.h) // ----------------------------------------------------------------------- static void chainIsr(IsrProcT prevIsr); static void eoiIrq(uint8_t irq); static bool getPmVector(uint8_t intNum, IsrProcT FAR *isr); 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); static bool irqInService(uint8_t irq); static void _far _interrupt isrIrq03(void); static void _far _interrupt isrIrq04(void); static void _far _interrupt isrIrq05(void); static void _far _interrupt isrIrq07(void); static void _far _interrupt isrIrq09(void); static void _far _interrupt isrIrq10(void); static void _far _interrupt isrIrq11(void); static void _far _interrupt isrIrq12(void); static void _far _interrupt isrIrq15(void); static bool maskIrq(uint8_t irq, bool masked); static uint8_t pmVector(uint8_t irq); static void raiseEvents(PortStateT *port, uint16_t events); static void serviceIrq(uint8_t irq); static bool servicePort(PortStateT *port); static bool setPmVector(uint8_t intNum, IsrProcT isr); static bool txSendable(PortStateT *port); // ----------------------------------------------------------------------- // USER's PostMessage, resolved by enableNotification (COMM.DRV loads // before USER, so it cannot be imported statically). PostMessage is one // of the functions Windows 3.1 allows at interrupt time. // ----------------------------------------------------------------------- PostMessageProcT postMessageProc = NULL; // ----------------------------------------------------------------------- // IRQ hook table. IRQs with no handler belong to system devices. An // ISA card jumpered to IRQ 2 interrupts on IRQ 9 (see readPortConfig). // ----------------------------------------------------------------------- static IrqHookT irqHooks[IRQ_COUNT] = { {NULL, NULL, 0, false}, // 0: system timer {NULL, NULL, 0, false}, // 1: keyboard {NULL, NULL, 0, false}, // 2: cascade from slave PIC {isrIrq03, NULL, 0, false}, // 3: COM2/COM4 {isrIrq04, NULL, 0, false}, // 4: COM1/COM3 {isrIrq05, NULL, 0, false}, // 5 {NULL, NULL, 0, false}, // 6: floppy {isrIrq07, NULL, 0, false}, // 7 {NULL, NULL, 0, false}, // 8: real-time clock {isrIrq09, NULL, 0, false}, // 9: ISA IRQ 2 {isrIrq10, NULL, 0, false}, // 10 {isrIrq11, NULL, 0, false}, // 11 {isrIrq12, NULL, 0, false}, // 12 {NULL, NULL, 0, false}, // 13: coprocessor {NULL, NULL, 0, false}, // 14: primary disk {isrIrq15, NULL, 0, false} // 15 }; // ----------------------------------------------------------------------- // chainIsr - Pass the current interrupt to the handler we replaced // // Simulates an interrupt (PUSHF + far CALL) so the previous handler's // IRET returns here. The previous handler sends its own EOI. // ----------------------------------------------------------------------- static void chainIsr(IsrProcT prevIsr) { _asm { pushf call dword ptr prevIsr } } // ----------------------------------------------------------------------- // checkNotify - Post WM_COMMNOTIFY for conditions that have become true // // Each notification is edge-triggered: once posted it stays quiet until // refreshNotify re-arms it. CN_RECEIVE: the DDAG allows fewer than // wInTrigger bytes once an internal timeout expires; KPCOMM has no timer, // so the timeout is zero and any waiting data qualifies. A failed post // (application queue full) is not recorded, so the next call retries. // Interrupts disabled. // ----------------------------------------------------------------------- void checkNotify(PortStateT *port) { uint16_t bits; if (!port->hwndNotify || !postMessageProc) { return; } bits = 0; if (port->rxNotifyTrigger != NOTIFY_OFF && !(port->notifySent & CN_RECEIVE) && port->comDeb.rxCount > 0) { bits |= CN_RECEIVE; } if (port->txNotifyTrigger != NOTIFY_OFF && !(port->notifySent & CN_TRANSMIT)) { if (port->comDeb.txCount < (uint16_t)port->txNotifyTrigger) { bits |= CN_TRANSMIT; } } if (!(port->notifySent & CN_EVENT) && (port->comDeb.evtWord & port->comDeb.evtMask)) { bits |= CN_EVENT; } if (bits && postMessageProc(port->hwndNotify, WM_COMMNOTIFY, (WPARAM)(port - ports), MAKELONG(bits, 0))) { port->notifySent = (uint8_t)(port->notifySent | bits); } } // ----------------------------------------------------------------------- // eoiIrq - Acknowledge an IRQ at the PIC(s) // ----------------------------------------------------------------------- static void eoiIrq(uint8_t irq) { if (irq >= IRQ_SLAVE_FIRST) { _outp(PIC2_CMD, PIC_EOI); } _outp(PIC1_CMD, PIC_EOI); } // ----------------------------------------------------------------------- // getPmVector - Read a protected-mode interrupt vector (DPMI 0204h) // ----------------------------------------------------------------------- static bool getPmVector(uint8_t intNum, IsrProcT FAR *isr) { uint16_t sel; uint16_t off; int16_t failed; _asm { mov ax, DPMI_GET_PM_VECTOR mov bl, intNum int DPMI_INT mov sel, cx mov off, dx sbb ax, ax mov failed, ax } if (failed) { return false; } *isr = (IsrProcT)MAKELP(sel, off); return true; } // ----------------------------------------------------------------------- // handleLsr - Record line status errors and their events // ----------------------------------------------------------------------- static void handleLsr(PortStateT *port, uint8_t lsr) { uint16_t errors; uint16_t events; errors = 0; events = 0; if (lsr & LSR_OE) { errors |= CE_OVERRUN; } if (lsr & LSR_PE) { errors |= CE_RXPARITY; } if (lsr & LSR_FE) { errors |= CE_FRAME; } if (lsr & (LSR_OE | LSR_PE | LSR_FE)) { events |= EV_ERR; } if (lsr & LSR_BI) { errors |= CE_BREAK; events |= EV_BREAK; } port->errorFlags |= errors; raiseEvents(port, events); } // ----------------------------------------------------------------------- // handleMsr - Record modem line changes and re-evaluate TX holds // ----------------------------------------------------------------------- static void handleMsr(PortStateT *port) { uint16_t events; uint8_t msr; msr = (uint8_t)_inp(port->comDeb.baseAddr + UART_MSR); port->comDeb.msrShadow = msr; events = 0; if (msr & MSR_DCTS) { events |= EV_CTS; } if (msr & MSR_DDSR) { events |= EV_DSR; } if (msr & MSR_TERI) { events |= EV_RING; } if (msr & MSR_DDCD) { events |= EV_RLSD; } raiseEvents(port, events); updateTxHold(port); } // ----------------------------------------------------------------------- // handleRx - Drain the UART into the RX ring buffer // // Applies the DCB receive options per byte: parity replacement (fParity + // fPeChar), XON/XOFF recognition (fOutX), null stripping (fNull), the // event character (fChEvt), and EOF detection in non-binary mode. // rxHead/rxCount are cached locally: the application only changes them // with interrupts disabled, so they cannot move under the ISR. The loop // is bounded and stops on an all-ones LSR (a vanished UART reads 0xFF, // which looks like endless data); leftovers keep IIR pending for the next // servicePort pass. // ----------------------------------------------------------------------- static void handleRx(PortStateT *port, uint8_t lsr) { DCB *dcb; uint8_t FAR *buf; uint16_t base; uint16_t size; uint16_t count; uint16_t head; uint16_t events; uint16_t burst; uint8_t ch; dcb = &port->dcb; buf = port->rxBuf.data; base = port->comDeb.baseAddr; size = port->comDeb.rxSize; count = port->comDeb.rxCount; head = port->comDeb.rxHead; events = 0; for (burst = 0; burst < MAX_RX_BURST && (lsr & LSR_DR) && lsr != UART_ABSENT; burst++) { if (lsr & LSR_ERRORS) { handleLsr(port, lsr); } ch = (uint8_t)_inp(base + UART_RBR); if ((lsr & LSR_PE) && dcb->fParity && dcb->fPeChar) { ch = (uint8_t)dcb->PeChar; } if (dcb->fOutX && ch == (uint8_t)dcb->XonChar) { port->txHold = (uint8_t)(port->txHold & ~TX_HOLD_XOFF); startTx(port); } else if (dcb->fOutX && ch == (uint8_t)dcb->XoffChar) { port->txHold = (uint8_t)(port->txHold | TX_HOLD_XOFF); } else if (!(dcb->fNull && ch == 0)) { if (dcb->fChEvt && ch == (uint8_t)dcb->EvtChar) { events |= EV_RXFLAG; } if (!dcb->fBinary && ch == (uint8_t)dcb->EofChar) { port->eofSeen = true; } if (count < size) { buf[head] = ch; head++; if (head >= size) { head = 0; } count++; events |= EV_RXCHAR; } else { port->errorFlags |= CE_RXOVER; } } lsr = (uint8_t)_inp(base + UART_LSR); } port->comDeb.rxHead = head; port->comDeb.rxCount = count; raiseEvents(port, events); updateRxFlow(port); } // ----------------------------------------------------------------------- // handleTx - Refill the transmitter on a THRE interrupt // // THRE means the holding register (and FIFO) is empty, so a full burst // is safe here -- and only here; every other path goes through startTx. // Order: our XON/XOFF (ignores the remote's XOFF, respects CTS/DSR), the // ctx() character, then queued data. When nothing may be sent the THRE // interrupt is disabled, so the next startTx is a clean 0 -> 1 enable. // ----------------------------------------------------------------------- static void handleTx(PortStateT *port) { uint8_t FAR *buf; uint16_t base; uint16_t room; uint16_t sent; uint16_t burst; uint16_t tail; uint16_t size; uint16_t i; base = port->comDeb.baseAddr; room = port->useFifo ? FIFO_DEPTH : 1; sent = 0; if (port->flowChar != NO_CHAR && !(port->txHold & TX_HOLD_HW)) { _outp(base + UART_THR, (uint8_t)port->flowChar); port->flowChar = NO_CHAR; sent++; } if (port->txHold == 0) { if (sent < room && port->txImmediate != NO_CHAR) { _outp(base + UART_THR, (uint8_t)port->txImmediate); port->txImmediate = NO_CHAR; sent++; } burst = port->comDeb.txCount; if (burst > room - sent) { burst = room - sent; } if (burst > 0) { buf = port->txBuf.data; tail = port->comDeb.txTail; size = port->comDeb.txSize; for (i = 0; i < burst; i++) { _outp(base + UART_THR, buf[tail]); tail++; if (tail >= size) { tail = 0; } } port->comDeb.txTail = tail; port->comDeb.txCount -= burst; sent += burst; } } if (sent == 0) { writeIer(port, (uint8_t)(port->comDeb.ierShadow & ~IER_THRE)); if (port->comDeb.txCount == 0) { raiseEvents(port, EV_TXEMPTY); } } } // ----------------------------------------------------------------------- // hookIsr - Route a port's IRQ to our handler // // The vector is hooked (DPMI 0205h) and the IRQ unmasked only for the // first port on an IRQ. The prior mask state is recorded so unhookIsr // can restore it and so serviceIrq knows whether another device may // share the line. The line is held masked while the hook state is // published, so the ISR never sees a stale prevIsr or wasMasked; an edge // in that window stays latched in the PIC. Returns 0, or DRV_ERROR for // an unsupported IRQ or a DPMI failure. // ----------------------------------------------------------------------- int16_t hookIsr(PortStateT *port) { IrqHookT *hook; IsrProcT prevIsr; uint8_t intNum; if (port->irq >= IRQ_COUNT || !irqHooks[port->irq].handler) { return DRV_ERROR; } hook = &irqHooks[port->irq]; if (hook->refCount == 0) { intNum = pmVector(port->irq); if (!getPmVector(intNum, &prevIsr)) { return DRV_ERROR; } hook->wasMasked = maskIrq(port->irq, true); hook->prevIsr = prevIsr; if (!setPmVector(intNum, hook->handler)) { hook->prevIsr = NULL; if (!hook->wasMasked) { maskIrq(port->irq, false); } return DRV_ERROR; } maskIrq(port->irq, false); } hook->refCount++; return 0; } // ----------------------------------------------------------------------- // irqInService - True if the PIC has an IRQ in service (OCW3 ISR read) // // Restores the default IRR read mode afterwards. Interrupts disabled. // ----------------------------------------------------------------------- static bool irqInService(uint8_t irq) { uint16_t cmdPort; uint8_t inService; cmdPort = (irq >= IRQ_SLAVE_FIRST) ? PIC2_CMD : PIC1_CMD; _outp(cmdPort, PIC_READ_ISR); inService = (uint8_t)_inp(cmdPort); _outp(cmdPort, PIC_READ_IRR); return (inService & (1 << (irq % IRQ_SLAVE_FIRST))) != 0; } // ----------------------------------------------------------------------- // isrIrq03 - Hardware interrupt entry point for IRQ 3 // // _interrupt saves and restores DS but does not load it, so point DS at // the driver's data segment before touching any global. CLD protects // string instructions from a direction flag left set by interrupted code. // The other isrIrqNN entry points are identical apart from the IRQ. // ----------------------------------------------------------------------- static void _far _interrupt isrIrq03(void) { _asm mov ax, seg ports _asm mov ds, ax _asm cld serviceIrq(3); } // ----------------------------------------------------------------------- // isrIrq04 - Hardware interrupt entry point for IRQ 4 (see isrIrq03) // ----------------------------------------------------------------------- static void _far _interrupt isrIrq04(void) { _asm mov ax, seg ports _asm mov ds, ax _asm cld serviceIrq(4); } // ----------------------------------------------------------------------- // isrIrq05 - Hardware interrupt entry point for IRQ 5 (see isrIrq03) // ----------------------------------------------------------------------- static void _far _interrupt isrIrq05(void) { _asm mov ax, seg ports _asm mov ds, ax _asm cld serviceIrq(5); } // ----------------------------------------------------------------------- // isrIrq07 - Hardware interrupt entry point for IRQ 7 (see isrIrq03) // ----------------------------------------------------------------------- static void _far _interrupt isrIrq07(void) { _asm mov ax, seg ports _asm mov ds, ax _asm cld serviceIrq(7); } // ----------------------------------------------------------------------- // isrIrq09 - Hardware interrupt entry point for IRQ 9 (see isrIrq03) // ----------------------------------------------------------------------- static void _far _interrupt isrIrq09(void) { _asm mov ax, seg ports _asm mov ds, ax _asm cld serviceIrq(9); } // ----------------------------------------------------------------------- // isrIrq10 - Hardware interrupt entry point for IRQ 10 (see isrIrq03) // ----------------------------------------------------------------------- static void _far _interrupt isrIrq10(void) { _asm mov ax, seg ports _asm mov ds, ax _asm cld serviceIrq(10); } // ----------------------------------------------------------------------- // isrIrq11 - Hardware interrupt entry point for IRQ 11 (see isrIrq03) // ----------------------------------------------------------------------- static void _far _interrupt isrIrq11(void) { _asm mov ax, seg ports _asm mov ds, ax _asm cld serviceIrq(11); } // ----------------------------------------------------------------------- // isrIrq12 - Hardware interrupt entry point for IRQ 12 (see isrIrq03) // ----------------------------------------------------------------------- static void _far _interrupt isrIrq12(void) { _asm mov ax, seg ports _asm mov ds, ax _asm cld serviceIrq(12); } // ----------------------------------------------------------------------- // isrIrq15 - Hardware interrupt entry point for IRQ 15 (see isrIrq03) // ----------------------------------------------------------------------- static void _far _interrupt isrIrq15(void) { _asm mov ax, seg ports _asm mov ds, ax _asm cld serviceIrq(15); } // ----------------------------------------------------------------------- // maskIrq - Mask or unmask an IRQ at its PIC; returns the previous state // // Called from application context only (re-enables interrupts). // ----------------------------------------------------------------------- static bool maskIrq(uint8_t irq, bool masked) { uint16_t maskPort; uint8_t bit; uint8_t mask; maskPort = (irq >= IRQ_SLAVE_FIRST) ? PIC2_DATA : PIC1_DATA; bit = (uint8_t)(1 << (irq % IRQ_SLAVE_FIRST)); _disable(); mask = (uint8_t)_inp(maskPort); if (masked) { _outp(maskPort, mask | bit); } else { _outp(maskPort, mask & ~bit); } _enable(); return (mask & bit) != 0; } // ----------------------------------------------------------------------- // pmVector - DPMI protected-mode interrupt number for an IRQ // ----------------------------------------------------------------------- static uint8_t pmVector(uint8_t irq) { if (irq >= IRQ_SLAVE_FIRST) { return (uint8_t)(PM_VECTOR_SLAVE + irq - IRQ_SLAVE_FIRST); } return (uint8_t)(PM_VECTOR_MASTER + irq); } // ----------------------------------------------------------------------- // raiseEvents - Record events the application enabled with cevt // ----------------------------------------------------------------------- static void raiseEvents(PortStateT *port, uint16_t events) { port->comDeb.evtWord |= events & port->comDeb.evtMask; } // ----------------------------------------------------------------------- // reactivateIrqs - Unmask every IRQ we have hooked // // A DOS application run between Suspend/ReactivateOpenCommPorts may have // masked them. // ----------------------------------------------------------------------- void reactivateIrqs(void) { uint8_t irq; for (irq = 0; irq < IRQ_COUNT; irq++) { if (irqHooks[irq].refCount > 0) { maskIrq(irq, false); } } } // ----------------------------------------------------------------------- // refreshNotify - Re-arm notifications after an application call, then // post anything that is already true // // acked holds the CN_* bits the call acknowledged: CN_RECEIVE when the // application read or discarded input, CN_EVENT when it fetched events. // Re-arming on acknowledgment rather than on a level means data that // arrived while the application was reading, or events it did not fetch, // are announced again instead of stalling. CN_TRANSMIT re-arms once the // output queue is refilled to its trigger. Interrupts disabled. // ----------------------------------------------------------------------- void refreshNotify(PortStateT *port, uint8_t acked) { uint8_t sent; sent = (uint8_t)(port->notifySent & ~acked); if (port->txNotifyTrigger != NOTIFY_OFF && port->comDeb.txCount >= (uint16_t)port->txNotifyTrigger) { sent = (uint8_t)(sent & ~CN_TRANSMIT); } port->notifySent = sent; checkNotify(port); } // ----------------------------------------------------------------------- // releaseRxFlow - Undo whatever was done to pause the remote sender // // Uses rxThrottle (what was actually done) and the current DCB's XON // character, so applyDcb can call it before installing new settings. // Interrupts disabled. // ----------------------------------------------------------------------- void releaseRxFlow(PortStateT *port) { uint8_t throttle; throttle = port->rxThrottle; if (throttle == 0) { return; } port->rxThrottle = 0; if (throttle & RX_THROTTLE_XOFF) { port->flowChar = (uint8_t)port->dcb.XonChar; startTx(port); } if (throttle & (RX_THROTTLE_RTS | RX_THROTTLE_DTR)) { writeMcr(port); } } // ----------------------------------------------------------------------- // serviceIrq - Service every open port on an IRQ, then EOI or chain // // With several UARTs on one edge-triggered line, passes repeat until a // full pass finds nothing pending; otherwise an edge raised mid-service // would be lost. An interrupt none of our ports claims is passed to the // previous handler when another device may own the line (the IRQ was // unmasked before we hooked it, or the ports are suspended for a DOS // application). Otherwise it is a late edge from one of our own UARTs // and only needs an EOI -- unless it is a spurious IRQ 7/15 that the PIC // never put in service, which must not be acknowledged (a stray EOI // would end some other interrupt early). // ----------------------------------------------------------------------- static void serviceIrq(uint8_t irq) { IrqHookT *hook; PortStateT *port; uint16_t pass; int16_t i; bool handled; bool found; hook = &irqHooks[irq]; handled = false; if (!portsSuspended) { for (pass = 0; pass < MAX_IRQ_PASSES; pass++) { found = false; for (i = 0; i < MAX_PORTS; i++) { port = &ports[i]; if (port->isOpen && port->irq == irq && servicePort(port)) { found = true; } } if (!found) { break; } handled = true; if (hook->refCount < 2) { break; } } } if (handled) { eoiIrq(irq); return; } if (hook->prevIsr && (portsSuspended || !hook->wasMasked)) { chainIsr(hook->prevIsr); return; } if ((irq == IRQ_SPURIOUS_PIC1 || irq == IRQ_SPURIOUS_PIC2) && !irqInService(irq)) { if (irq == IRQ_SPURIOUS_PIC2) { // The master did put the cascade input in service _outp(PIC1_CMD, PIC_EOI); } return; } eoiIrq(irq); } // ----------------------------------------------------------------------- // servicePort - Handle every pending interrupt source on one UART // // Returns true if the UART had anything pending. // ----------------------------------------------------------------------- static bool servicePort(PortStateT *port) { uint16_t base; uint16_t loops; uint8_t iir; uint8_t lsr; bool handled; base = port->comDeb.baseAddr; handled = false; for (loops = 0; loops < MAX_IIR_LOOPS; loops++) { iir = (uint8_t)_inp(base + UART_IIR); if (iir & IIR_NO_INT) { break; } handled = true; switch (iir & IIR_ID_MASK) { case IIR_LSR: lsr = (uint8_t)_inp(base + UART_LSR); if (lsr & LSR_DR) { handleRx(port, lsr); } else { handleLsr(port, lsr); } break; case IIR_RDA: case IIR_TIMEOUT: handleRx(port, (uint8_t)_inp(base + UART_LSR)); break; case IIR_THRE: handleTx(port); break; case IIR_MSR: handleMsr(port); break; default: // Undefined source on a non-standard clone: clear status (void)_inp(base + UART_LSR); (void)_inp(base + UART_MSR); break; } } if (handled) { checkNotify(port); } return handled; } // ----------------------------------------------------------------------- // setPmVector - Install a protected-mode interrupt vector (DPMI 0205h) // ----------------------------------------------------------------------- static bool setPmVector(uint8_t intNum, IsrProcT isr) { uint16_t sel; uint16_t off; int16_t failed; sel = _FP_SEG(isr); off = _FP_OFF(isr); _asm { mov ax, DPMI_SET_PM_VECTOR mov bl, intNum mov cx, sel mov dx, off int DPMI_INT sbb ax, ax mov failed, ax } return !failed; } // ----------------------------------------------------------------------- // startTx - Make sure a THRE interrupt will come if anything can be sent // // Never writes THR: if THRE interrupts are already enabled, the one in // progress will pick up the new data; if not, enabling them (0 -> 1 with // the transmitter idle) raises one immediately. Interrupts disabled. // ----------------------------------------------------------------------- void startTx(PortStateT *port) { if (!(port->comDeb.ierShadow & IER_THRE) && txSendable(port)) { writeIer(port, (uint8_t)(port->comDeb.ierShadow | IER_THRE)); } } // ----------------------------------------------------------------------- // txSendable - True if handleTx would transmit something right now // ----------------------------------------------------------------------- static bool txSendable(PortStateT *port) { if (port->txHold & TX_HOLD_HW) { return false; } if (port->flowChar != NO_CHAR) { return true; } return port->txHold == 0 && (port->txImmediate != NO_CHAR || port->comDeb.txCount > 0); } // ----------------------------------------------------------------------- // unhookIsr - Release a port's IRQ // // The last port on an IRQ restores the PIC mask bit to its original state // and puts the previous vector back; other ports on the IRQ keep running. // ----------------------------------------------------------------------- void unhookIsr(PortStateT *port) { IrqHookT *hook; hook = &irqHooks[port->irq]; if (hook->refCount == 0) { return; } hook->refCount--; if (hook->refCount == 0) { if (hook->wasMasked) { maskIrq(port->irq, true); } setPmVector(pmVector(port->irq), hook->prevIsr); hook->prevIsr = NULL; } } // ----------------------------------------------------------------------- // updateRxFlow - Pause or resume the remote sender from the RX fill level // // DCB limits: pause when no more than XoffLim bytes are free, resume once // no more than XonLim bytes remain. Limits that do not fit the buffer // are clamped. Each mechanism the DCB enables is applied (fInX sends // XOFF, fRtsflow drops RTS, fDtrflow drops DTR) and recorded in // rxThrottle so releaseRxFlow can undo exactly that. Interrupts disabled. // ----------------------------------------------------------------------- void updateRxFlow(PortStateT *port) { uint16_t size; uint16_t count; uint16_t highWater; uint16_t lowWater; uint8_t throttle; size = port->comDeb.rxSize; if (size == 0) { return; } count = port->comDeb.rxCount; highWater = (port->dcb.XoffLim < size) ? (uint16_t)(size - port->dcb.XoffLim) : size; lowWater = port->dcb.XonLim; if (lowWater >= highWater) { lowWater = highWater / 2; } if (port->rxThrottle == 0) { if (count < highWater) { return; } throttle = 0; if (port->dcb.fInX) { port->flowChar = (uint8_t)port->dcb.XoffChar; throttle |= RX_THROTTLE_XOFF; } if (port->dcb.fRtsflow) { throttle |= RX_THROTTLE_RTS; } if (port->dcb.fDtrflow) { throttle |= RX_THROTTLE_DTR; } port->rxThrottle = throttle; if (throttle & (RX_THROTTLE_RTS | RX_THROTTLE_DTR)) { writeMcr(port); } if (throttle & RX_THROTTLE_XOFF) { startTx(port); } } else if (count <= lowWater) { releaseRxFlow(port); } } // ----------------------------------------------------------------------- // updateTxHold - Recompute the modem-line transmit holds from the MSR shadow // // A line holds transmission while low if the DCB uses it for output flow // control: fOutxCtsFlow/fOutxDsrFlow, or an INFINITE Cts/Dsr/RlsTimeout // (DDAG: an infinite timeout is how a DCB asks for CTS, DSR, or RLSD flow // control). Finite timeouts are waited out at write time instead (see // waitTxLines in commdrv.c). The XOFF hold is left alone (only XON, // SETXON, or applyDcb clear it). Restarts the transmitter if a hold was // released. Interrupts disabled. // ----------------------------------------------------------------------- void updateTxHold(PortStateT *port) { uint8_t msr; uint8_t hold; msr = port->comDeb.msrShadow; hold = (uint8_t)(port->txHold & TX_HOLD_XOFF); if ((port->dcb.fOutxCtsFlow || port->dcb.CtsTimeout == INFINITE) && !(msr & MSR_CTS)) { hold |= TX_HOLD_CTS; } if ((port->dcb.fOutxDsrFlow || port->dcb.DsrTimeout == INFINITE) && !(msr & MSR_DSR)) { hold |= TX_HOLD_DSR; } if (port->dcb.RlsTimeout == INFINITE && !(msr & MSR_DCD)) { hold |= TX_HOLD_RLSD; } port->txHold = hold; startTx(port); } // ----------------------------------------------------------------------- // writeIer - Program IER and its shadow (interrupts disabled) // ----------------------------------------------------------------------- void writeIer(PortStateT *port, uint8_t ier) { port->comDeb.ierShadow = ier; _outp(port->comDeb.baseAddr + UART_IER, ier); } // ----------------------------------------------------------------------- // writeMcr - Program MCR from the wanted DTR/RTS state and RX throttling // // OUT2 stays on while the port is open (it gates the UART interrupt). // Interrupts disabled. // ----------------------------------------------------------------------- void writeMcr(PortStateT *port) { uint8_t mcr; mcr = MCR_OUT2; if (port->dtrOn && !(port->rxThrottle & RX_THROTTLE_DTR)) { mcr |= MCR_DTR; } if (port->rtsOn && !(port->rxThrottle & RX_THROTTLE_RTS)) { mcr |= MCR_RTS; } port->comDeb.mcrShadow = mcr; _outp(port->comDeb.baseAddr + UART_MCR, mcr); }