DVX_GUI/rs232/rs232.c

1491 lines
45 KiB
C

// RS-232 Serial Port Library for DJGPP
// Ported from DOS Serial Library 1.4 by Karl Stenerud (MIT License)
#include <stdint.h>
#include <stdbool.h>
#include <pc.h>
#include <sys/farptr.h>
#include <go32.h>
#include <dpmi.h>
#include "rs232.h"
// ========================================================================
// Defines and macros
// ========================================================================
// Port I/O helpers that return the written value. outportb/outportw are void
// in DJGPP, but our macros (UART_WRITE_IER, etc.) use comma expressions to
// cache the written value in the port state struct for later reads without
// hitting the hardware again.
#define OUTP(a, b) (outportb((a), (b)), (b))
#define OUTPW(a, w) (outportw((a), (w)), (w))
// Buffer sizes must be power of 2 so (index & MASK) wraps correctly.
// 2048 bytes balances memory usage (8KB per port for both buffers) against
// burst tolerance at 115200 bps.
#define RX_BUFFER_BITS 11 // 2048
#define TX_BUFFER_BITS 11 // 2048
#define RX_BUFFER_SIZE (1L << RX_BUFFER_BITS)
#define TX_BUFFER_SIZE (1L << TX_BUFFER_BITS)
#define RX_BUFFER_MASK ((1U << RX_BUFFER_BITS) - 1)
#define TX_BUFFER_MASK ((1U << TX_BUFFER_BITS) - 1)
// Flow control watermarks (percentage of buffer)
#define RX_HIGH_PERCENT 80
#define RX_LOW_PERCENT 20
#define TX_HIGH_PERCENT 80
#define TX_LOW_PERCENT 20
#define RX_HIGH_WATER (RX_BUFFER_SIZE * RX_HIGH_PERCENT / 100UL)
#define RX_LOW_WATER (RX_BUFFER_SIZE * RX_LOW_PERCENT / 100UL)
#define TX_HIGH_WATER (TX_BUFFER_SIZE * TX_HIGH_PERCENT / 100UL)
#define TX_LOW_WATER (TX_BUFFER_SIZE * TX_LOW_PERCENT / 100UL)
// Ring buffer macros. These advance head/tail BEFORE accessing the slot
// (pre-increment style). The +1-before-mask pattern means slot 0 is never
// used and the full/empty distinction works: head==tail means empty,
// (head+1)&mask==tail means full (one slot wasted to distinguish states).
#define RX_READ(C) (C)->rxBuff[(C)->rxTail = ((C)->rxTail + 1) & RX_BUFFER_MASK]
#define RX_WRITE(C, D) (C)->rxBuff[(C)->rxHead = ((C)->rxHead + 1) & RX_BUFFER_MASK] = (D)
#define RX_INIT(C) (C)->rxHead = (C)->rxTail = 0
#define RX_EMPTY(C) ((C)->rxHead == (C)->rxTail)
#define RX_FULL(C) ((((C)->rxHead + 1) & RX_BUFFER_MASK) == (C)->rxTail)
#define RX_COUNT(C) (((C)->rxHead - (C)->rxTail) & RX_BUFFER_MASK)
#define RX_LOWATER(C) (RX_COUNT(C) < RX_LOW_WATER)
#define RX_HIWATER(C) (RX_COUNT(C) > RX_HIGH_WATER)
// TX buffer operations
#define TX_READ(C) (C)->txBuff[(C)->txTail = ((C)->txTail + 1) & TX_BUFFER_MASK]
#define TX_WRITE(C, D) (C)->txBuff[(C)->txHead = ((C)->txHead + 1) & TX_BUFFER_MASK] = (D)
#define TX_INIT(C) (C)->txHead = (C)->txTail = 0
#define TX_EMPTY(C) ((C)->txHead == (C)->txTail)
#define TX_FULL(C) ((((C)->txHead + 1) & TX_BUFFER_MASK) == (C)->txTail)
#define TX_COUNT(C) (((C)->txHead - (C)->txTail) & TX_BUFFER_MASK)
#define TX_LOWATER(C) (TX_COUNT(C) < TX_LOW_WATER)
#define TX_HIWATER(C) (TX_COUNT(C) > TX_HIGH_WATER)
// XON/XOFF
#define XON 0x11
#define XOFF 0x13
// Default IRQs
#define COM1_DEFAULT_IRQ 4
#define COM2_DEFAULT_IRQ 3
#define COM3_DEFAULT_IRQ 4
#define COM4_DEFAULT_IRQ 3
#define COM_MIN RS232_COM1
#define COM_MAX RS232_COM4
#define IRQ_MIN 3
#define IRQ_MAX 15
#define IRQ_NONE 0xFF
// PIC registers
#define INT_VECTOR_OFFSET 8
#define PIC_MASTER 0x20
#define PIC_SLAVE 0xA0
#define PIC_EOI 0x20
#define PIC_RR 0x02
#define PIC_WRITE_IMR(P, D) outportb((P) | 1, (D))
#define PIC_WRITE_OCW2(P, D) outportb((P), (D))
#define PIC_WRITE_OCW3(P, D) outportb((P), (D) | 8)
#define PIC_READ_IMR(P) inportb((P) | 1)
#define PIC_END_IRQ(P) PIC_WRITE_OCW2((P), PIC_EOI)
#define PIC_ENABLE_IRQ(I) \
{ if ((I) <= 7) { PIC_WRITE_IMR(PIC_MASTER, PIC_READ_IMR(PIC_MASTER) & ~(1 << (I))); } \
else { PIC_WRITE_IMR(PIC_SLAVE, PIC_READ_IMR(PIC_SLAVE) & ~(1 << ((I) - 7))); } }
#define PIC_DISABLE_IRQ(I) \
{ if ((I) <= 7) { PIC_WRITE_IMR(PIC_MASTER, PIC_READ_IMR(PIC_MASTER) | (1 << (I))); } \
else { PIC_WRITE_IMR(PIC_SLAVE, PIC_READ_IMR(PIC_SLAVE) | (1 << ((I) - 7))); } }
// UART register offsets
#define UART_RX 0
#define UART_TX 0
#define UART_IER 1
#define UART_DLL 0 // divisor latch LSB (when DLAB=1)
#define UART_DLM 1 // divisor latch MSB (when DLAB=1)
#define UART_DLW 0 // divisor latch word (when DLAB=1)
#define UART_IIR 2
#define UART_FCR 2
#define UART_LCR 3
#define UART_MCR 4
#define UART_LSR 5
#define UART_MSR 6
#define UART_SCR 7 // scratch register (not present on 8250)
// UART read macros
#define UART_READ_DATA(C) inportb((C)->base + UART_RX)
#define UART_READ_IER(C) inportb((C)->base + UART_IER)
#define UART_READ_IIR(C) inportb((C)->base + UART_IIR)
#define UART_READ_LCR(C) inportb((C)->base + UART_LCR)
#define UART_READ_MCR(C) inportb((C)->base + UART_MCR)
#define UART_READ_LSR(C) ((C)->lsr = inportb((C)->base + UART_LSR))
#define UART_READ_MSR(C) ((C)->msr = inportb((C)->base + UART_MSR))
#define UART_READ_BPS(C) ((OUTP((C)->base + UART_LCR, inportb((C)->base + UART_LCR) | LCR_DLAB) & 0) | inportw((C)->base + UART_DLW) | (OUTP((C)->base + UART_LCR, inportb((C)->base + UART_LCR) & ~LCR_DLAB) & 0))
// UART write macros
#define UART_WRITE_DATA(C, D) outportb((C)->base + UART_TX, (D))
#define UART_WRITE_IER(C, D) ((C)->ier = OUTP((C)->base + UART_IER, (D)))
#define UART_WRITE_FCR(C, D) ((C)->fcr = OUTP((C)->base + UART_FCR, (D)))
#define UART_WRITE_LCR(C, D) ((C)->lcr = OUTP((C)->base + UART_LCR, (D)))
#define UART_WRITE_MCR(C, D) ((C)->mcr = OUTP((C)->base + UART_MCR, (D)))
#define UART_WRITE_BPS(C, D) \
{ outportb((C)->base + UART_LCR, inportb((C)->base + UART_LCR) | LCR_DLAB); \
(C)->dlatch = OUTPW((C)->base + UART_DLW, (D)); \
outportb((C)->base + UART_LCR, inportb((C)->base + UART_LCR) & ~LCR_DLAB); }
// IER bits
#define IER_DATA_READY 0x01
#define IER_TX_HOLD_EMPTY 0x02
#define IER_ERRORS 0x04
#define IER_MODEM_STATUS 0x08
// IIR bits
#define IIR_NO_INT_PENDING 0x01
#define IIR_ID0 0x02
#define IIR_ID1 0x04
#define IIR_ID2 0x08
#define IIR_MASK (IIR_NO_INT_PENDING | IIR_ID0 | IIR_ID1)
#define IIR_MODEM_STATUS 0x00
#define IIR_TX_HOLD_EMPTY IIR_ID0
#define IIR_DATA_READY IIR_ID1
#define IIR_LINE_STATUS (IIR_ID0 | IIR_ID1)
#define IIR_NO_INTERRUPT IIR_NO_INT_PENDING
// FCR bits
#define FCR_ENABLE 0x01
#define FCR_RX_RESET 0x02
#define FCR_TX_RESET 0x04
#define FCR_DMA_SELECT 0x08
#define FCR_TRIGGER_0 0x40
#define FCR_TRIGGER_1 0x80
#define FCR_TRIGGER_AT_1 0x00
#define FCR_TRIGGER_AT_4 FCR_TRIGGER_0
#define FCR_TRIGGER_AT_8 FCR_TRIGGER_1
#define FCR_TRIGGER_AT_14 (FCR_TRIGGER_0 | FCR_TRIGGER_1)
#define FIFO_DEFAULT_THRESHOLD 14
// LCR bits
#define LCR_DLAB 0x80
#define LCR_BREAK 0x40
#define LCR_STICK_PARITY 0x20
#define LCR_EVEN_PARITY 0x10
#define LCR_PARITY_ENABLE 0x08
#define LCR_STOP 0x04
#define LCR_WORD1 0x02
#define LCR_WORD0 0x01
#define DATA_5 0x00
#define DATA_6 LCR_WORD0
#define DATA_7 LCR_WORD1
#define DATA_8 (LCR_WORD0 | LCR_WORD1)
#define DATA_MASK (LCR_WORD0 | LCR_WORD1)
#define PARITY_NONE 0x00
#define PARITY_ODD LCR_PARITY_ENABLE
#define PARITY_EVEN (LCR_PARITY_ENABLE | LCR_EVEN_PARITY)
#define PARITY_MARK (LCR_PARITY_ENABLE | LCR_STICK_PARITY)
#define PARITY_SPACE (LCR_PARITY_ENABLE | LCR_EVEN_PARITY | LCR_STICK_PARITY)
#define PARITY_MASK (LCR_PARITY_ENABLE | LCR_EVEN_PARITY | LCR_STICK_PARITY)
#define STOP_1 0x00
#define STOP_2 LCR_STOP
#define STOP_MASK LCR_STOP
// MCR bits
#define MCR_DTR 0x01
#define MCR_RTS 0x02
#define MCR_OUT1 0x04
#define MCR_OUT2 0x08
#define MCR_LOOPBACK 0x10
#define MCR_MASK (MCR_DTR | MCR_RTS | MCR_OUT1 | MCR_OUT2 | MCR_LOOPBACK)
// LSR bits
#define LSR_DATA_READY 0x01
#define LSR_OVERRUN 0x02
#define LSR_PARITY_ERR 0x04
#define LSR_FRAME_ERR 0x08
#define LSR_BREAK 0x10
#define LSR_TX_HOLD_EMPTY 0x20
#define LSR_TX_EMPTY 0x40
#define LSR_FIFO_ERR 0x80
// MSR bits
#define MSR_DLT_CTS 0x01
#define MSR_DLT_DSR 0x02
#define MSR_NEW_RING 0x04
#define MSR_DLT_DCD 0x08
#define MSR_CTS 0x10
#define MSR_DSR 0x20
#define MSR_RI 0x40
#define MSR_DCD 0x80
// BPS divisors (1.8432 MHz crystal)
#define BPS_50 2304
#define BPS_75 1536
#define BPS_110 1047
#define BPS_150 768
#define BPS_300 384
#define BPS_600 192
#define BPS_1200 96
#define BPS_1800 64
#define BPS_2400 48
#define BPS_3800 32
#define BPS_4800 24
#define BPS_7200 16
#define BPS_9600 12
#define BPS_19200 6
#define BPS_38400 3
#define BPS_57600 2
#define BPS_115200 1
#define FIFO_SIZE 16
#define ISR_SIZE 2048
// ========================================================================
// Types
// ========================================================================
typedef struct {
uint8_t port;
uint8_t defaultIrq;
uint8_t irq;
uint8_t isOpen;
uint8_t ier;
uint8_t fcr;
uint8_t lcr;
uint8_t mcr;
uint8_t lsr;
uint8_t msr;
uint8_t flowMode;
uint8_t rxFlowOn;
uint8_t txFlowOn;
uint8_t rxBuff[(uint16_t)RX_BUFFER_SIZE];
uint8_t txBuff[(uint16_t)TX_BUFFER_SIZE];
uint32_t base;
uint32_t dlatch;
uint32_t rxHead;
uint32_t txHead;
uint32_t rxTail;
uint32_t txTail;
} Rs232StateT;
// ========================================================================
// Static globals
// ========================================================================
static _go32_dpmi_seginfo sOldIsrs[16];
static uint8_t sIsrsTaken[16] = {0};
static _go32_dpmi_seginfo sIsrAddr;
static uint32_t sIsrsCount = 0;
static Rs232StateT sComPorts[COM_MAX + 1] = {
{ .port = 0, .defaultIrq = COM1_DEFAULT_IRQ, .irq = IRQ_NONE },
{ .port = 1, .defaultIrq = COM2_DEFAULT_IRQ, .irq = IRQ_NONE },
{ .port = 2, .defaultIrq = COM3_DEFAULT_IRQ, .irq = IRQ_NONE },
{ .port = 3, .defaultIrq = COM4_DEFAULT_IRQ, .irq = IRQ_NONE }
};
// ========================================================================
// Static prototypes (alphabetical)
// ========================================================================
static void comGeneralIsr(void);
static void dpmiGetPvect(int vector, _go32_dpmi_seginfo *info);
static int dpmiLockMemory(void);
static void dpmiSetPvect(int vector, _go32_dpmi_seginfo *info);
static void dpmiUnlockMemory(void);
static int findIrq(int com);
static void freeIrq(int com);
static int installIrqHandler(int irq);
static uint8_t picReadIrr(uint16_t port);
static void removeIrqHandler(int irq);
// ========================================================================
// Interrupt service routine
// ========================================================================
// Single shared ISR for all COM ports. ISR sharing is necessary because
// COM1/COM3 typically share IRQ4 and COM2/COM4 share IRQ3. Having one ISR
// that polls all ports avoids the complexity of per-IRQ handlers.
//
// The ISR design follows a careful protocol:
// 1. Mask all COM IRQs on the PIC to prevent re-entry
// 2. STI to allow higher-priority interrupts (timer, keyboard) through
// 3. Loop over all open ports, draining each UART's pending interrupts
// 4. CLI, send EOI to PIC, re-enable COM IRQs, STI before IRET
//
// This "mask-then-STI" pattern is standard for slow device ISRs on PC
// hardware -- it prevents the same IRQ from re-entering while still allowing
// the timer tick and keyboard to function during potentially long UART
// processing.
static void comGeneralIsr(void) {
Rs232StateT *comMin = &sComPorts[0];
Rs232StateT *comMax = &sComPorts[COM_MAX];
uint8_t intId;
uint8_t data;
uint8_t slaveTriggered = 0;
Rs232StateT *com;
for (com = comMin; com <= comMax; com++) {
if (com->isOpen) {
if (com->irq > 7) {
slaveTriggered = 1;
}
PIC_DISABLE_IRQ(com->irq);
}
}
asm("STI");
// Process all pending interrupts on all open ports
for (com = comMin; com <= comMax; com++) {
if (com->isOpen) {
while ((intId = UART_READ_IIR(com) & IIR_MASK) != IIR_NO_INTERRUPT) {
switch (intId) {
case IIR_DATA_READY:
// Read all available data from the UART
while (UART_READ_LSR(com) & LSR_DATA_READY) {
data = UART_READ_DATA(com);
// Handle XON/XOFF flow control (TX direction)
if (com->flowMode == RS232_HANDSHAKE_XONXOFF && (data == XOFF || data == XON)) {
com->txFlowOn = (data == XON);
if (!TX_EMPTY(com) && com->txFlowOn) {
UART_WRITE_IER(com, UART_READ_IER(com) | IER_TX_HOLD_EMPTY);
}
} else if (!RX_FULL(com)) {
// Store data if room in buffer
RX_WRITE(com, data);
// RX flow control: turn off if buffer almost full
if (com->rxFlowOn && RX_HIWATER(com)) {
com->rxFlowOn = 0;
switch (com->flowMode) {
case RS232_HANDSHAKE_XONXOFF:
UART_WRITE_DATA(com, XOFF);
break;
case RS232_HANDSHAKE_RTSCTS:
UART_WRITE_MCR(com, UART_READ_MCR(com) & ~MCR_RTS);
break;
case RS232_HANDSHAKE_DTRDSR:
UART_WRITE_MCR(com, UART_READ_MCR(com) & ~MCR_DTR);
break;
case RS232_HANDSHAKE_NONE:
break;
}
}
}
}
break;
case IIR_LINE_STATUS:
UART_READ_LSR(com);
break;
case IIR_MODEM_STATUS:
UART_READ_MSR(com);
// Handle RTS/CTS or DTR/DSR flow control (TX direction)
if (com->flowMode == RS232_HANDSHAKE_RTSCTS) {
com->txFlowOn = (com->msr & MSR_CTS) != 0;
} else if (com->flowMode == RS232_HANDSHAKE_DTRDSR) {
com->txFlowOn = (com->msr & MSR_DSR) != 0;
}
if (!TX_EMPTY(com) && com->txFlowOn) {
UART_WRITE_IER(com, UART_READ_IER(com) | IER_TX_HOLD_EMPTY);
}
break;
case IIR_TX_HOLD_EMPTY: {
// Drain up to FIFO_SIZE bytes from TX ring buffer to UART.
// Writing more than one byte is only safe on 16550A+ (has FIFO);
// on 8250/16450 FIFO_SIZE effectively limits to 1 byte anyway.
int cnt;
for (cnt = 0; cnt < FIFO_SIZE && com->txFlowOn && !TX_EMPTY(com); cnt++) {
UART_WRITE_DATA(com, TX_READ(com));
}
if (TX_EMPTY(com) || !com->txFlowOn) {
UART_WRITE_IER(com, UART_READ_IER(com) & ~IER_TX_HOLD_EMPTY);
}
break;
}
}
}
}
}
asm("CLI");
// End the interrupt on the PIC
if (slaveTriggered) {
PIC_END_IRQ(PIC_SLAVE);
}
PIC_END_IRQ(PIC_MASTER);
// Re-enable all COM port interrupts
for (com = comMin; com <= comMax; com++) {
if (com->isOpen) {
PIC_ENABLE_IRQ(com->irq);
}
}
// Must explicitly STI before IRET because under DPMI (and some
// virtualizers like DOSBox/86Box), IRET's implicit IF restore from
// the flags on the stack doesn't always work as expected.
asm("STI");
}
// ========================================================================
// DPMI utility functions
// ========================================================================
// These functions wrap DJGPP's DPMI interface for installing protected-mode
// interrupt handlers. Under DPMI, the ISR code and data must be locked in
// physical memory to prevent page faults during interrupt handling -- a page
// fault inside an ISR would be fatal. The IRET wrapper is also allocated
// by DPMI to handle the real-mode-to-protected-mode transition.
static void dpmiGetPvect(int vector, _go32_dpmi_seginfo *info) {
_go32_dpmi_get_protected_mode_interrupt_vector(vector, info);
}
// Lock the ISR code and its data (sComPorts array) in physical memory.
// The IRET wrapper handles the stack frame that DPMI uses to dispatch
// hardware interrupts to protected-mode code.
static int dpmiLockMemory(void) {
unsigned long dataAddr;
unsigned long codeAddr;
__dpmi_meminfo dataRegion;
__dpmi_meminfo codeRegion;
if (__dpmi_get_segment_base_address(_my_cs(), &codeAddr) == 0 &&
__dpmi_get_segment_base_address(_my_ds(), &dataAddr) == 0) {
codeRegion.handle = 0;
codeRegion.size = ISR_SIZE;
codeRegion.address = codeAddr + (unsigned long)comGeneralIsr;
dataRegion.handle = 0;
dataRegion.size = sizeof(sComPorts);
dataRegion.address = dataAddr + (unsigned long)sComPorts;
if (__dpmi_lock_linear_region(&codeRegion) == 0) {
if (__dpmi_lock_linear_region(&dataRegion) == 0) {
sIsrAddr.pm_offset = (unsigned long)comGeneralIsr;
sIsrAddr.pm_selector = _go32_my_cs();
if (_go32_dpmi_allocate_iret_wrapper(&sIsrAddr) == 0) {
return 1;
}
__dpmi_unlock_linear_region(&dataRegion);
}
__dpmi_unlock_linear_region(&codeRegion);
}
}
return 0;
}
static void dpmiSetPvect(int vector, _go32_dpmi_seginfo *info) {
_go32_dpmi_set_protected_mode_interrupt_vector(vector, info);
}
static void dpmiUnlockMemory(void) {
unsigned long baseAddr;
__dpmi_meminfo region;
if (__dpmi_get_segment_base_address(_my_ds(), &baseAddr) == 0) {
region.handle = 0;
region.size = sizeof(sComPorts);
region.address = baseAddr + (unsigned long)sComPorts;
__dpmi_unlock_linear_region(&region);
}
if (__dpmi_get_segment_base_address(_my_cs(), &baseAddr) == 0) {
region.handle = 0;
region.size = ISR_SIZE;
region.address = baseAddr + (unsigned long)comGeneralIsr;
__dpmi_unlock_linear_region(&region);
}
_go32_dpmi_free_iret_wrapper(&sIsrAddr);
}
// ========================================================================
// IRQ management
// ========================================================================
// Auto-detect which IRQ a UART is wired to by generating a TX hold empty
// interrupt and reading the PIC's IRR (Interrupt Request Register) to see
// which line went high. Uses a double-check pattern: enable interrupt,
// read IRR, disable, read IRR again, mask out persistent bits, then
// enable once more to confirm. Falls back to the default IRQ if detection
// fails (e.g., on virtualized hardware that doesn't model IRR accurately).
static int findIrq(int comport) {
Rs232StateT *com = &sComPorts[comport];
uint8_t imrM = PIC_READ_IMR(PIC_MASTER);
uint8_t imrS = PIC_READ_IMR(PIC_SLAVE);
uint8_t irrM;
uint8_t irrS;
// Set up the UART
UART_WRITE_MCR(com, MCR_OUT2);
UART_WRITE_FCR(com, 0);
// Wait until TX hold register is empty
while ((UART_READ_LSR(com) & LSR_TX_HOLD_EMPTY) == 0) {
}
asm("CLI");
// Allow any interrupt on PIC
PIC_WRITE_IMR(PIC_MASTER, 0);
PIC_WRITE_IMR(PIC_SLAVE, 0);
// Initial polls to let things settle
UART_WRITE_IER(com, IER_TX_HOLD_EMPTY);
picReadIrr(PIC_MASTER);
picReadIrr(PIC_SLAVE);
UART_WRITE_IER(com, 0);
picReadIrr(PIC_MASTER);
picReadIrr(PIC_SLAVE);
// Generate an interrupt and record all active IRQs
UART_WRITE_IER(com, IER_TX_HOLD_EMPTY);
irrM = picReadIrr(PIC_MASTER);
irrS = picReadIrr(PIC_SLAVE);
// Remove the interrupt and mask out all IRQs still active
UART_WRITE_IER(com, 0);
irrM &= ~picReadIrr(PIC_MASTER);
irrS &= ~picReadIrr(PIC_SLAVE);
// Interrupt again to confirm
UART_WRITE_IER(com, IER_TX_HOLD_EMPTY);
irrM &= picReadIrr(PIC_MASTER);
irrS &= picReadIrr(PIC_SLAVE);
// Restore everything
PIC_WRITE_IMR(PIC_MASTER, imrM);
PIC_WRITE_IMR(PIC_SLAVE, imrS);
UART_WRITE_IER(com, 0);
asm("STI");
switch (irrM) {
case 0x01: return 0;
case 0x02: return 1;
case 0x04:
switch (irrS) {
case 0x01: return 8;
case 0x02: return 9;
case 0x04: return 10;
case 0x08: return 11;
case 0x10: return 12;
case 0x20: return 13;
case 0x40: return 14;
case 0x80: return 15;
default: return 2;
}
case 0x08: return 3;
case 0x10: return 4;
case 0x20: return 5;
case 0x40: return 6;
case 0x80: return 7;
}
return RS232_ERR_IRQ_NOT_FOUND;
}
// Release this port's IRQ. If another port shares the same IRQ line,
// the handler stays installed -- only the last user removes it. This is
// critical for COM1/COM3 IRQ sharing.
static void freeIrq(int comport) {
Rs232StateT *com = &sComPorts[comport];
Rs232StateT *comMin = &sComPorts[0];
Rs232StateT *comMax = &sComPorts[COM_MAX];
uint32_t irq = com->irq;
Rs232StateT *ptr;
if (irq == IRQ_NONE) {
return;
}
asm("CLI");
// Disable interrupts from the UART
UART_WRITE_IER(com, 0);
UART_WRITE_MCR(com, MCR_OUT2);
// Clear the FIFO and RX registers
UART_WRITE_FCR(com, 0);
for (int i = 0; i < 14; i++) {
UART_READ_DATA(com);
}
UART_READ_IIR(com);
UART_READ_LSR(com);
UART_READ_DATA(com);
UART_READ_IIR(com);
UART_READ_MSR(com);
UART_READ_IIR(com);
UART_READ_LSR(com);
UART_READ_IIR(com);
com->irq = IRQ_NONE;
asm("STI");
// Check if any other port shares this IRQ
for (ptr = comMin; ptr <= comMax; ptr++) {
if (ptr != com && ptr->irq == irq) {
return;
}
}
// No other port uses this IRQ, so restore the old handler
PIC_DISABLE_IRQ(irq);
removeIrqHandler(irq);
}
static int installIrqHandler(int irq) {
if (!sIsrsTaken[irq]) {
if (sIsrsCount++ == 0) {
// Lock memory used by interrupt handler in DPMI mode
if (!dpmiLockMemory()) {
--sIsrsCount;
return RS232_ERR_LOCK_MEM;
}
}
dpmiGetPvect(irq + INT_VECTOR_OFFSET, &sOldIsrs[irq]);
dpmiSetPvect(irq + INT_VECTOR_OFFSET, &sIsrAddr);
sIsrsTaken[irq] = 1;
}
return RS232_SUCCESS;
}
static uint8_t picReadIrr(uint16_t port) {
PIC_WRITE_OCW3(port, PIC_RR);
return inportb(port);
}
static void removeIrqHandler(int irq) {
if (sIsrsTaken[irq]) {
dpmiSetPvect(irq + INT_VECTOR_OFFSET, &sOldIsrs[irq]);
sIsrsTaken[irq] = 0;
if (--sIsrsCount == 0) {
dpmiUnlockMemory();
}
}
}
// ========================================================================
// Public functions (alphabetical)
// ========================================================================
int rs232ClearRxBuffer(int com) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
asm("CLI");
RX_INIT(port);
asm("STI");
return RS232_SUCCESS;
}
int rs232ClearTxBuffer(int com) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
asm("CLI");
TX_INIT(port);
asm("STI");
return RS232_SUCCESS;
}
int rs232Close(int com) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
// Restore old interrupt handler
freeIrq(com);
// Turn everything off
UART_WRITE_IER(port, 0);
UART_WRITE_MCR(port, 0);
rs232SetFifoThreshold(com, 0);
port->isOpen = 0;
return RS232_SUCCESS;
}
int rs232GetBase(int com) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (port->base < 1 || port->base > 0xFFF) {
return RS232_ERR_INVALID_BASE;
}
return port->base;
}
int32_t rs232GetBps(int com) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
switch (UART_READ_BPS(port)) {
case BPS_115200: return 115200L;
case BPS_57600: return 57600L;
case BPS_38400: return 38400L;
case BPS_19200: return 19200L;
case BPS_9600: return 9600L;
case BPS_7200: return 7200L;
case BPS_4800: return 4800L;
case BPS_3800: return 3800L;
case BPS_2400: return 2400L;
case BPS_1800: return 1800L;
case BPS_1200: return 1200L;
case BPS_600: return 600L;
case BPS_300: return 300L;
case BPS_150: return 150L;
case BPS_110: return 110L;
case BPS_75: return 75L;
case BPS_50: return 50L;
}
return RS232_ERR_INVALID_BPS;
}
int rs232GetCts(int com) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
return (port->msr & MSR_CTS) != 0;
}
int rs232GetData(int com) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
switch (UART_READ_LCR(port) & DATA_MASK) {
case DATA_5: return 5;
case DATA_6: return 6;
case DATA_7: return 7;
case DATA_8: return 8;
}
return RS232_ERR_INVALID_DATA;
}
int rs232GetDsr(int com) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
return (port->msr & MSR_DSR) != 0;
}
int rs232GetDtr(int com) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
return (port->mcr & MCR_DTR) != 0;
}
int rs232GetHandshake(int com) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
return port->flowMode;
}
int rs232GetIrq(int com) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (port->irq < IRQ_MIN || port->irq > IRQ_MAX) {
return RS232_ERR_INVALID_IRQ;
}
return port->irq;
}
int rs232GetLsr(int com) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
return port->lsr;
}
int rs232GetMcr(int com) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
return port->mcr;
}
int rs232GetMsr(int com) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
return port->msr;
}
char rs232GetParity(int com) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
switch (UART_READ_LCR(port) & PARITY_MASK) {
case PARITY_NONE: return 'n';
case PARITY_EVEN: return 'e';
case PARITY_ODD: return 'o';
case PARITY_MARK: return 'm';
case PARITY_SPACE: return 's';
}
return RS232_ERR_INVALID_PARITY;
}
int rs232GetRts(int com) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
return (port->mcr & MCR_RTS) != 0;
}
int rs232GetRxBuffered(int com) {
Rs232StateT *port = &sComPorts[com];
int count;
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
asm("CLI");
count = RX_COUNT(port);
asm("STI");
return count;
}
int rs232GetStop(int com) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
switch (UART_READ_LCR(port) & STOP_MASK) {
case STOP_1: return 1;
case STOP_2: return 2;
}
return RS232_ERR_INVALID_STOP;
}
int rs232GetTxBuffered(int com) {
Rs232StateT *port = &sComPorts[com];
int count;
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
asm("CLI");
count = TX_COUNT(port);
asm("STI");
return count;
}
// Detect UART type by probing hardware features. The detection sequence:
// 1. Write/read scratch register -- 8250 doesn't have one
// 2. Enable FIFO and check IIR bits 7:6 -- distinguishes 16450/16550/16550A
// This matters because only 16550A has a reliable 16-byte FIFO; the
// original 16550 FIFO is buggy and should not be enabled.
int rs232GetUartType(int com) {
Rs232StateT *port = &sComPorts[com];
uint8_t scratch;
uint8_t iir;
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
// Test scratch register -- 8250 lacks it
outportb(port->base + UART_SCR, 0xA5);
scratch = inportb(port->base + UART_SCR);
if (scratch != 0xA5) {
return RS232_UART_8250;
}
outportb(port->base + UART_SCR, 0x5A);
scratch = inportb(port->base + UART_SCR);
if (scratch != 0x5A) {
return RS232_UART_8250;
}
// Has scratch register -- at least 16450. Try enabling FIFO.
outportb(port->base + UART_FCR, FCR_ENABLE);
iir = inportb(port->base + UART_IIR);
// Restore original FCR
outportb(port->base + UART_FCR, port->fcr);
// Check IIR bits 7:6 for FIFO status
switch (iir & 0xC0) {
case 0xC0:
return RS232_UART_16550A;
case 0x80:
return RS232_UART_16550;
default:
return RS232_UART_16450;
}
}
int rs232Open(int com, int32_t bps, int dataBits, char parity, int stopBits, int handshake) {
Rs232StateT *port = &sComPorts[com];
int rc = RS232_SUCCESS;
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (port->isOpen) {
return RS232_ERR_ALREADY_OPEN;
}
port->isOpen = 1;
port->rxFlowOn = 1;
port->txFlowOn = 1;
RX_INIT(port);
TX_INIT(port);
// Read COM base address from BIOS data area at 0040:0000. The BIOS
// stores up to 4 COM port base addresses as 16-bit words at 40:00-40:07.
// This is more reliable than hardcoding 0x3F8/0x2F8 because BIOS setup
// may have remapped ports or detected them in a different order.
if (rs232SetBase(com, _farpeekw(_dos_ds, 0x400 + (com << 1))) != RS232_SUCCESS) {
return RS232_ERR_NO_UART;
}
// Turn off UART interrupts
UART_WRITE_IER(port, 0);
// Auto-detect IRQ
rc = findIrq(com);
if (rc < 0) {
rc = port->defaultIrq;
}
rc = rs232SetIrq(com, rc);
if (rc != RS232_SUCCESS) {
return rc;
}
// Turn off PIC interrupts for this IRQ
PIC_DISABLE_IRQ(port->irq);
// Configure the port
rc = rs232Set(com, bps, dataBits, parity, stopBits, handshake);
if (rc == RS232_SUCCESS) {
UART_WRITE_MCR(port, MCR_DTR | MCR_RTS | MCR_OUT1 | MCR_OUT2);
rc = rs232SetFifoThreshold(com, FIFO_DEFAULT_THRESHOLD);
// Read initial status
UART_READ_LSR(port);
UART_READ_MSR(port);
}
// Enable interrupts
UART_WRITE_IER(port, IER_DATA_READY | IER_MODEM_STATUS | IER_ERRORS);
PIC_ENABLE_IRQ(port->irq);
if (rc != RS232_SUCCESS) {
rs232Close(com);
}
return rc;
}
int rs232Read(int com, char *data, int len) {
Rs232StateT *port = &sComPorts[com];
int i;
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
if (data == 0) {
return RS232_ERR_NULL_PTR;
}
asm("CLI");
for (i = 0; !RX_EMPTY(port) && i < len; i++) {
data[i] = RX_READ(port);
}
// RX flow control: turn back on if buffer almost empty
if (!port->rxFlowOn && RX_LOWATER(port)) {
port->rxFlowOn = 1;
if (port->flowMode == RS232_HANDSHAKE_XONXOFF) {
UART_WRITE_DATA(port, XON);
} else if (port->flowMode == RS232_HANDSHAKE_RTSCTS) {
UART_WRITE_MCR(port, UART_READ_MCR(port) | MCR_RTS);
} else if (port->flowMode == RS232_HANDSHAKE_DTRDSR) {
UART_WRITE_MCR(port, UART_READ_MCR(port) | MCR_DTR);
}
}
asm("STI");
return i;
}
int rs232Set(int com, int32_t bps, int dataBits, char parity, int stopBits, int handshake) {
int rc;
rc = rs232SetBps(com, bps);
if (rc == RS232_SUCCESS) {
rc = rs232SetData(com, dataBits);
}
if (rc == RS232_SUCCESS) {
rc = rs232SetParity(com, parity);
}
if (rc == RS232_SUCCESS) {
rc = rs232SetStop(com, stopBits);
}
if (rc == RS232_SUCCESS) {
rc = rs232SetHandshake(com, handshake);
}
return rc;
}
int rs232SetBase(int com, int base) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (base < 1 || base > 0xFFF) {
return RS232_ERR_INVALID_BASE;
}
port->base = base;
return RS232_SUCCESS;
}
int rs232SetBps(int com, int32_t bps) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
switch (bps) {
case 115200L: UART_WRITE_BPS(port, BPS_115200); return RS232_SUCCESS;
case 57600L: UART_WRITE_BPS(port, BPS_57600); return RS232_SUCCESS;
case 38400L: UART_WRITE_BPS(port, BPS_38400); return RS232_SUCCESS;
case 19200L: UART_WRITE_BPS(port, BPS_19200); return RS232_SUCCESS;
case 9600L: UART_WRITE_BPS(port, BPS_9600); return RS232_SUCCESS;
case 7200L: UART_WRITE_BPS(port, BPS_7200); return RS232_SUCCESS;
case 4800L: UART_WRITE_BPS(port, BPS_4800); return RS232_SUCCESS;
case 3800L: UART_WRITE_BPS(port, BPS_3800); return RS232_SUCCESS;
case 2400L: UART_WRITE_BPS(port, BPS_2400); return RS232_SUCCESS;
case 1800L: UART_WRITE_BPS(port, BPS_1800); return RS232_SUCCESS;
case 1200L: UART_WRITE_BPS(port, BPS_1200); return RS232_SUCCESS;
case 600L: UART_WRITE_BPS(port, BPS_600); return RS232_SUCCESS;
case 300L: UART_WRITE_BPS(port, BPS_300); return RS232_SUCCESS;
case 150L: UART_WRITE_BPS(port, BPS_150); return RS232_SUCCESS;
case 110L: UART_WRITE_BPS(port, BPS_110); return RS232_SUCCESS;
case 75L: UART_WRITE_BPS(port, BPS_75); return RS232_SUCCESS;
case 50L: UART_WRITE_BPS(port, BPS_50); return RS232_SUCCESS;
}
return RS232_ERR_INVALID_BPS;
}
int rs232SetData(int com, int dataBits) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
switch (dataBits) {
case 5: UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~DATA_MASK) | DATA_5); return RS232_SUCCESS;
case 6: UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~DATA_MASK) | DATA_6); return RS232_SUCCESS;
case 7: UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~DATA_MASK) | DATA_7); return RS232_SUCCESS;
case 8: UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~DATA_MASK) | DATA_8); return RS232_SUCCESS;
}
return RS232_ERR_INVALID_DATA;
}
int rs232SetDtr(int com, bool dtr) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
if (dtr) {
UART_WRITE_MCR(port, port->mcr | MCR_DTR);
} else {
UART_WRITE_MCR(port, port->mcr & ~MCR_DTR);
}
return RS232_SUCCESS;
}
int rs232SetFifoThreshold(int com, int threshold) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
switch (threshold) {
case 14:
UART_WRITE_FCR(port, FCR_ENABLE | FCR_RX_RESET | FCR_TX_RESET | FCR_TRIGGER_AT_14);
break;
case 8:
UART_WRITE_FCR(port, FCR_ENABLE | FCR_RX_RESET | FCR_TX_RESET | FCR_TRIGGER_AT_8);
break;
case 4:
UART_WRITE_FCR(port, FCR_ENABLE | FCR_RX_RESET | FCR_TX_RESET | FCR_TRIGGER_AT_4);
break;
case 1:
UART_WRITE_FCR(port, FCR_ENABLE | FCR_RX_RESET | FCR_TX_RESET | FCR_TRIGGER_AT_1);
break;
case 0:
UART_WRITE_FCR(port, 0);
break;
default:
return RS232_ERR_INVALID_FIFO;
}
// Clear any garbage in UART FIFO
for (int i = 0; i < 16; i++) {
UART_READ_DATA(port);
}
UART_READ_LSR(port);
return RS232_SUCCESS;
}
int rs232SetHandshake(int com, int handshake) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
if (handshake < RS232_HANDSHAKE_NONE || handshake > RS232_HANDSHAKE_DTRDSR) {
return RS232_ERR_INVALID_HANDSHAKE;
}
port->txFlowOn = 1;
port->rxFlowOn = 1;
port->flowMode = handshake;
return RS232_SUCCESS;
}
int rs232SetIrq(int com, int irq) {
Rs232StateT *port = &sComPorts[com];
int rc;
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (irq < IRQ_MIN || irq > IRQ_MAX) {
return RS232_ERR_INVALID_IRQ;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
// Remove any ISRs on this port's current IRQ
freeIrq(com);
rc = installIrqHandler(irq);
if (rc == RS232_SUCCESS) {
port->irq = irq;
PIC_ENABLE_IRQ(port->irq);
}
return rc;
}
int rs232SetMcr(int com, int mcr) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
UART_WRITE_MCR(port, mcr & MCR_MASK);
return RS232_SUCCESS;
}
int rs232SetParity(int com, char parity) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
switch (parity) {
case 'n': case 'N': UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~PARITY_MASK) | PARITY_NONE); return RS232_SUCCESS;
case 'e': case 'E': UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~PARITY_MASK) | PARITY_EVEN); return RS232_SUCCESS;
case 'o': case 'O': UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~PARITY_MASK) | PARITY_ODD); return RS232_SUCCESS;
case 'm': case 'M': UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~PARITY_MASK) | PARITY_MARK); return RS232_SUCCESS;
case 's': case 'S': UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~PARITY_MASK) | PARITY_SPACE); return RS232_SUCCESS;
}
return RS232_ERR_INVALID_PARITY;
}
int rs232SetRts(int com, bool rts) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
if (rts) {
UART_WRITE_MCR(port, port->mcr | MCR_RTS);
} else {
UART_WRITE_MCR(port, port->mcr & ~MCR_RTS);
}
return RS232_SUCCESS;
}
int rs232SetStop(int com, int stopBits) {
Rs232StateT *port = &sComPorts[com];
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
switch (stopBits) {
case 1: UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~STOP_MASK) | STOP_1); return RS232_SUCCESS;
case 2: UART_WRITE_LCR(port, (UART_READ_LCR(port) & ~STOP_MASK) | STOP_2); return RS232_SUCCESS;
}
return RS232_ERR_INVALID_STOP;
}
// Blocking polled write: busy-waits on LSR_TX_HOLD_EMPTY for each byte.
// This bypasses the TX ring buffer entirely and writes directly to the UART.
// Used by the packet layer for frame transmission where we want guaranteed
// delivery order and don't want ISR-driven TX reordering complications.
// The trade-off is CPU burn during the wait, but at 115200 bps each byte
// takes only ~87us, and frame sizes are small (< 520 bytes worst case).
int rs232Write(int com, const char *data, int len) {
Rs232StateT *port = &sComPorts[com];
int i;
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
if (data == 0) {
return RS232_ERR_NULL_PTR;
}
for (i = 0; i < len; i++) {
// Wait until we can write
while (!(UART_READ_LSR(port) & LSR_TX_HOLD_EMPTY)) {
}
if (port->txFlowOn) {
UART_WRITE_DATA(port, data[i]);
} else {
break;
}
}
return i;
}
// Non-blocking buffered write: pushes data into the TX ring buffer and
// enables the TX_HOLD_EMPTY interrupt so the ISR drains it asynchronously.
// Returns the number of bytes actually buffered (may be less than len if
// the buffer is full). This is the ISR-driven counterpart to rs232Write.
int rs232WriteBuf(int com, const char *data, int len) {
Rs232StateT *port = &sComPorts[com];
int i;
if (com < COM_MIN || com > COM_MAX) {
return RS232_ERR_INVALID_PORT;
}
if (!port->isOpen) {
return RS232_ERR_NOT_OPEN;
}
if (data == 0) {
return RS232_ERR_NULL_PTR;
}
asm("CLI");
for (i = 0; i < len; i++) {
if (TX_FULL(port)) {
break;
}
TX_WRITE(port, data[i]);
}
// If there's data to send, enable TX_HOLD_EMPTY interrupt
if (!TX_EMPTY(port)) {
UART_WRITE_IER(port, UART_READ_IER(port) | IER_TX_HOLD_EMPTY);
}
asm("STI");
return i;
}