WinComm/drv/isr.c
2026-10-12 16:22:16 -05:00

977 lines
31 KiB
C

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