977 lines
31 KiB
C
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);
|
|
}
|