1491 lines
45 KiB
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(®ion);
|
|
}
|
|
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(®ion);
|
|
}
|
|
_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;
|
|
}
|