363 lines
17 KiB
C
363 lines
17 KiB
C
// Part of KPCOMM, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
// See LICENSE.md for license information.
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
// commdrv.h - KPCOMM.DRV -- High-speed COMM.DRV replacement
|
|
//
|
|
// Types, UART/PIC/DPMI/BIOS constants, port state, and the prototypes
|
|
// shared between commdrv.c, isr.c, and lpt.c.
|
|
//
|
|
// Export signatures and semantics follow the Windows 3.1 Device Driver
|
|
// Adaptation Guide (DDAG) communication-driver chapter.
|
|
|
|
#ifndef COMMDRV_H
|
|
#define COMMDRV_H
|
|
|
|
#include <windows.h>
|
|
#include <conio.h>
|
|
#include <dos.h>
|
|
#include <string.h>
|
|
|
|
// Generate inline code for port I/O, CLI/STI, and far memory helpers
|
|
// instead of C runtime calls (matters most inside the ISR).
|
|
#pragma intrinsic(_inp, _outp, _disable, _enable, _fmemcpy, _fmemset)
|
|
|
|
// -----------------------------------------------------------------------
|
|
// stdint/stdbool types for MSVC 1.52 (neither header is available)
|
|
// -----------------------------------------------------------------------
|
|
#ifndef _STDINT_DEFINED
|
|
typedef short int16_t;
|
|
typedef unsigned short uint16_t;
|
|
typedef long int32_t;
|
|
typedef unsigned long uint32_t;
|
|
typedef unsigned char uint8_t;
|
|
typedef signed char int8_t;
|
|
#define _STDINT_DEFINED
|
|
#endif
|
|
|
|
#ifndef UINT16_MAX
|
|
#define UINT16_MAX 0xFFFFU
|
|
#endif
|
|
|
|
#ifndef __bool_true_false_are_defined
|
|
typedef uint8_t bool;
|
|
#define true 1
|
|
#define false 0
|
|
#define __bool_true_false_are_defined 1
|
|
#endif
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Debug logging via OutputDebugString (1 = enabled)
|
|
// -----------------------------------------------------------------------
|
|
#define DBG_ENABLED 0
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Ports and queues
|
|
// -----------------------------------------------------------------------
|
|
#define MAX_PORTS 4 // COM1-COM4 (GETMAXCOM reports MAX_PORTS - 1)
|
|
#define MAX_LPTS 3 // LPT1-LPT3
|
|
#define LPT_ID_FLAG 0x80 // cid bit marking a parallel port (DDAG: LPT1 = 0x80)
|
|
#define LPT_INDEX_MASK 0x7F // cid bits holding the parallel port index
|
|
#define DEFAULT_QUEUE_SIZE 4096 // Queue size used between inicom and setque
|
|
#define MIN_QUEUE_SIZE 64 // Smallest queue setque will allocate
|
|
#define NO_CHAR (-1) // Empty flowChar/txImmediate slot
|
|
#define NOTIFY_OFF (-1) // rx/txNotifyTrigger value disabling that notification
|
|
|
|
// -----------------------------------------------------------------------
|
|
// UART register offsets from base address
|
|
// -----------------------------------------------------------------------
|
|
#define UART_RBR 0 // Receive Buffer Register (read, DLAB=0)
|
|
#define UART_THR 0 // Transmit Holding Register (write, DLAB=0)
|
|
#define UART_DLL 0 // Divisor Latch Low (DLAB=1)
|
|
#define UART_IER 1 // Interrupt Enable Register (DLAB=0)
|
|
#define UART_DLM 1 // Divisor Latch High (DLAB=1)
|
|
#define UART_IIR 2 // Interrupt Identification Register (read)
|
|
#define UART_FCR 2 // FIFO Control Register (write)
|
|
#define UART_LCR 3 // Line Control Register
|
|
#define UART_MCR 4 // Modem Control Register
|
|
#define UART_LSR 5 // Line Status Register
|
|
#define UART_MSR 6 // Modem Status Register
|
|
|
|
// IER bits
|
|
#define IER_RDA 0x01 // Received Data Available
|
|
#define IER_THRE 0x02 // Transmitter Holding Register Empty
|
|
#define IER_LSI 0x04 // Line Status Interrupt
|
|
#define IER_MSI 0x08 // Modem Status Interrupt
|
|
#define IER_RX (IER_RDA | IER_LSI | IER_MSI) // Everything except THRE
|
|
|
|
// IIR bits
|
|
#define IIR_NO_INT 0x01 // Set when no interrupt is pending
|
|
#define IIR_ID_MASK 0x0E // Interrupt source mask
|
|
#define IIR_MSR 0x00 // Modem status change
|
|
#define IIR_THRE 0x02 // Transmitter holding register empty
|
|
#define IIR_RDA 0x04 // Received data available
|
|
#define IIR_LSR 0x06 // Line status (error/break)
|
|
#define IIR_TIMEOUT 0x0C // Character timeout (16550 FIFO)
|
|
#define IIR_FIFO_MASK 0xC0 // Both set = 16550A FIFOs enabled
|
|
|
|
// FCR bits
|
|
#define FCR_ENABLE 0x01 // Enable FIFOs
|
|
#define FCR_RX_RESET 0x02 // Reset RX FIFO
|
|
#define FCR_TX_RESET 0x04 // Reset TX FIFO
|
|
#define FCR_TRIG_SHIFT 6 // RX trigger code in bits 7:6 (0-3 = 1/4/8/14 bytes)
|
|
|
|
// LCR bits
|
|
#define LCR_STB 0x04 // Extra stop bits (1.5 with 5 data bits, else 2)
|
|
#define LCR_PEN 0x08 // Parity Enable
|
|
#define LCR_EPS 0x10 // Even Parity Select
|
|
#define LCR_SPAR 0x20 // Stick Parity
|
|
#define LCR_SBRK 0x40 // Set Break
|
|
#define LCR_DLAB 0x80 // Divisor Latch Access Bit
|
|
|
|
// MCR bits
|
|
#define MCR_DTR 0x01 // Data Terminal Ready
|
|
#define MCR_RTS 0x02 // Request To Send
|
|
#define MCR_OUT2 0x08 // Output 2 (gates the UART interrupt line onto the bus)
|
|
|
|
// LSR bits
|
|
#define LSR_DR 0x01 // Data Ready
|
|
#define LSR_OE 0x02 // Overrun Error
|
|
#define LSR_PE 0x04 // Parity Error
|
|
#define LSR_FE 0x08 // Framing Error
|
|
#define LSR_BI 0x10 // Break Interrupt
|
|
#define LSR_THRE 0x20 // Transmitter holding register (FIFO) empty
|
|
#define LSR_TEMT 0x40 // Transmitter completely empty (shift register too)
|
|
#define LSR_ERRORS (LSR_OE | LSR_PE | LSR_FE | LSR_BI)
|
|
|
|
// MSR bits
|
|
#define MSR_DCTS 0x01 // Delta CTS
|
|
#define MSR_DDSR 0x02 // Delta DSR
|
|
#define MSR_TERI 0x04 // Trailing Edge Ring Indicator
|
|
#define MSR_DDCD 0x08 // Delta DCD
|
|
#define MSR_CTS 0x10 // Clear To Send
|
|
#define MSR_DSR 0x20 // Data Set Ready
|
|
#define MSR_DCD 0x80 // Data Carrier Detect (RLSD)
|
|
|
|
// UART geometry and baud rates
|
|
#define FIFO_DEPTH 16 // 16550A TX/RX FIFO size
|
|
#define UART_PRESENT_LCR 0x1B // LCR test pattern for presence detection
|
|
#define UART_ABSENT 0xFF // Register reads from an empty I/O address float high
|
|
#define UART_CLOCK_DIV 115200UL // 1.8432 MHz / 16
|
|
#define MIN_RAW_BAUD 2 // DDAG: raw rates are 2..0xFEFF
|
|
#define CBR_INDEX_MASK 0xFF00 // High byte 0xFF marks a CBR_* index
|
|
#define KPCBR_115200 0xFF24 // KPCOMM extension: CBR-style index for 115200
|
|
#define MIN_BYTE_SIZE 5 // Data bits; LCR word length = ByteSize - 5
|
|
#define MAX_BYTE_SIZE 8
|
|
|
|
// -----------------------------------------------------------------------
|
|
// PIC, DPMI, and BIOS
|
|
// -----------------------------------------------------------------------
|
|
#define PIC1_CMD 0x20 // Master PIC command port
|
|
#define PIC1_DATA 0x21 // Master PIC mask port
|
|
#define PIC2_CMD 0xA0 // Slave PIC command port
|
|
#define PIC2_DATA 0xA1 // Slave PIC mask port
|
|
#define PIC_EOI 0x20 // Non-specific End Of Interrupt
|
|
#define PIC_READ_IRR 0x0A // OCW3: command port reads the request register (default)
|
|
#define PIC_READ_ISR 0x0B // OCW3: command port reads the in-service register
|
|
#define IRQ_COUNT 16 // IRQ 0-15
|
|
#define IRQ_SPURIOUS_PIC1 7 // Spurious interrupts from the master PIC appear as IRQ 7
|
|
#define IRQ_SPURIOUS_PIC2 15 // ...and from the slave PIC as IRQ 15
|
|
#define IRQ_SLAVE_FIRST 8 // First IRQ on the slave PIC
|
|
#define IRQ_CASCADE 2 // ISA "IRQ2" is delivered as IRQ9 on AT systems
|
|
#define IRQ_CASCADE_TARGET 9
|
|
#define PM_VECTOR_MASTER 0x08 // DPMI protected-mode vector for IRQ0
|
|
#define PM_VECTOR_SLAVE 0x70 // DPMI protected-mode vector for IRQ8
|
|
|
|
#define DPMI_INT 0x31
|
|
#define DPMI_SEG_TO_DESC 0x0002 // Map real-mode segment to selector
|
|
#define DPMI_GET_PM_VECTOR 0x0204
|
|
#define DPMI_SET_PM_VECTOR 0x0205
|
|
|
|
#define BIOS_DATA_SEG 0x0040
|
|
#define BDA_COM_BASES 0x0000 // COM1-COM4 base addresses (words)
|
|
#define BDA_LPT_BASES 0x0008 // LPT1-LPT3 base addresses (words)
|
|
#define BDA_TICK_COUNT 0x006C // Timer ticks since midnight (low word used, ~18.2 Hz)
|
|
#define MS_PER_BIOS_TICK 55 // Milliseconds per BIOS timer tick (rounded)
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Default COM port resources (SYSTEM.INI [386Enh] COMnBase/COMnIRQ override
|
|
// only when the BIOS data area has no entry, per DDAG)
|
|
// -----------------------------------------------------------------------
|
|
#define COM1_BASE 0x03F8
|
|
#define COM2_BASE 0x02F8
|
|
#define COM3_BASE 0x03E8
|
|
#define COM4_BASE 0x02E8
|
|
|
|
#define COM1_IRQ 4
|
|
#define COM2_IRQ 3
|
|
#define COM3_IRQ 4
|
|
#define COM4_IRQ 3
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Return codes not defined by windows.h
|
|
// -----------------------------------------------------------------------
|
|
#define DRV_ERROR (-1) // Generic failure for exports without a documented code
|
|
#define RECCOM_NO_DATA (-2) // DDAG reccom: no data available
|
|
#define TRMCOM_TIMEOUT (-2) // DDAG trmcom: transmit queue did not drain
|
|
#define CTX_BUSY 0x4000 // DDAG ctx: immediate character already pending
|
|
#define SNDCOM_FAILED 1 // DDAG sndcom: any nonzero value is an error
|
|
#define BASEIRQ_NO_PORT MAKELONG(0, 0xFFFF) // DDAG GETBASEIRQ: high word -1 = no such port
|
|
|
|
// cflush queue selectors (DDAG: 0 = transmit, 1 = receive)
|
|
#define FLUSH_TX 0
|
|
#define FLUSH_RX 1
|
|
|
|
// -----------------------------------------------------------------------
|
|
// KPCOMM EscapeCommFunction extensions (stock drivers reject them)
|
|
// -----------------------------------------------------------------------
|
|
#define KPESC_KEEP_DTR_ON_CLOSE 0x4B50 // CloseComm leaves DTR asserted (connection stays up)
|
|
#define KPESC_DROP_DTR_ON_CLOSE 0x4B51 // CloseComm drops DTR (the default; hangs up)
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Flow control state bits
|
|
// -----------------------------------------------------------------------
|
|
#define TX_HOLD_XOFF 0x01 // Remote sent XOFF (fOutX)
|
|
#define TX_HOLD_CTS 0x02 // CTS low (fOutxCtsFlow or CtsTimeout INFINITE)
|
|
#define TX_HOLD_DSR 0x04 // DSR low (fOutxDsrFlow or DsrTimeout INFINITE)
|
|
#define TX_HOLD_RLSD 0x08 // RLSD/DCD low (RlsTimeout INFINITE)
|
|
#define TX_HOLD_HW (TX_HOLD_CTS | TX_HOLD_DSR | TX_HOLD_RLSD)
|
|
|
|
#define RX_THROTTLE_XOFF 0x01 // We sent XOFF (fInX)
|
|
#define RX_THROTTLE_RTS 0x02 // We dropped RTS (fRtsflow)
|
|
#define RX_THROTTLE_DTR 0x04 // We dropped DTR (fDtrflow)
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Stock COMM.DRV compatible ComDEB structure
|
|
//
|
|
// cevt (SetCommEventMask) returns a FAR pointer to offset 0. Third-party
|
|
// code reads the MSR shadow at offset 35 (KB Q101417; Wine uses the same
|
|
// offset). The queue fields are the driver's live ring-buffer state, so
|
|
// readers at +8/+18 always see current counts without any syncing.
|
|
// -----------------------------------------------------------------------
|
|
typedef struct {
|
|
volatile uint16_t evtWord; // +0: Accumulated events (only those in evtMask)
|
|
uint16_t evtMask; // +2: Events to record (set by cevt)
|
|
uint16_t reserved4; // +4: Stock RX queue address (unused)
|
|
uint16_t rxSize; // +6: RX ring buffer capacity
|
|
volatile uint16_t rxCount; // +8: RX bytes queued (ISR adds, app removes)
|
|
uint16_t rxHead; // +10: RX write index (ISR)
|
|
uint16_t rxTail; // +12: RX read index (app)
|
|
uint16_t reserved14; // +14: Stock TX queue address (unused)
|
|
uint16_t txSize; // +16: TX ring buffer capacity
|
|
volatile uint16_t txCount; // +18: TX bytes queued (app adds, ISR removes)
|
|
uint16_t txHead; // +20: TX write index (app)
|
|
uint16_t txTail; // +22: TX read index (ISR)
|
|
uint16_t baseAddr; // +24: UART base I/O address
|
|
uint16_t baudRate; // +26: Mirror of dcb.BaudRate for third-party readers (written only by applyDcb)
|
|
uint8_t lcrShadow; // +28: Last LCR value written (including break bit)
|
|
volatile uint8_t mcrShadow; // +29: Last MCR value written
|
|
volatile uint8_t ierShadow; // +30: Last IER value written
|
|
uint8_t reserved31; // +31
|
|
uint8_t reserved32; // +32
|
|
uint8_t reserved33; // +33
|
|
uint8_t fifoTrigger; // +34: RX FIFO trigger level (FCR_TRIG_* bits)
|
|
volatile uint8_t msrShadow; // +35: Last MSR value read (KB Q101417)
|
|
uint8_t reserved36[4]; // +36: Pad to 40 bytes
|
|
} ComDebT;
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Queue definition block passed to setque (DDAG "qdb"). USER allocates
|
|
// pqRx/pqTx, but KPCOMM keeps its own page-locked buffers and uses only
|
|
// the requested sizes.
|
|
// -----------------------------------------------------------------------
|
|
typedef struct {
|
|
uint8_t FAR *rxQueue; // pqRx
|
|
int16_t rxSize; // cbqRx
|
|
uint8_t FAR *txQueue; // pqTx
|
|
int16_t txSize; // cbqTx
|
|
} QdbT;
|
|
|
|
// GlobalAlloc'd, page-locked ring buffer storage
|
|
typedef struct {
|
|
HGLOBAL handle;
|
|
uint8_t FAR *data;
|
|
} BufferT;
|
|
|
|
// -----------------------------------------------------------------------
|
|
// COM port state -- one per port, shared by application and ISR context.
|
|
// Fields the ISR also writes are volatile; multi-field updates from
|
|
// application context are made with interrupts disabled.
|
|
// -----------------------------------------------------------------------
|
|
typedef struct {
|
|
ComDebT comDeb; // Must be first: cevt returns &comDeb.evtWord
|
|
DCB dcb; // Current settings (getdcb); source of truth for line and flow parameters
|
|
BufferT rxBuf; // RX ring buffer storage
|
|
BufferT txBuf; // TX ring buffer storage
|
|
HWND hwndNotify; // WM_COMMNOTIFY target (NULL = notifications off)
|
|
int16_t rxNotifyTrigger; // CN_RECEIVE threshold (-1 = off)
|
|
int16_t txNotifyTrigger; // CN_TRANSMIT threshold (-1 = off)
|
|
volatile int16_t flowChar; // XON/XOFF to send ahead of everything (NO_CHAR = none)
|
|
volatile int16_t txImmediate; // ctx() character (NO_CHAR = none)
|
|
volatile uint16_t errorFlags; // Sticky CE_* bits, cleared by stacom
|
|
uint8_t irq; // IRQ line (0-15)
|
|
volatile bool isOpen; // Port open (guards ISR dispatch)
|
|
bool useFifo; // 16550A detected and COMnFIFO enabled
|
|
volatile uint8_t txHold; // TX_HOLD_* reasons transmission is paused
|
|
volatile uint8_t rxThrottle; // RX_THROTTLE_* actions taken to pause the remote
|
|
bool dtrOn; // DTR wanted by DCB/EscapeCommFunction (flow control may hold it low)
|
|
bool rtsOn; // RTS wanted by DCB/EscapeCommFunction (flow control may hold it low)
|
|
bool keepDtrOnClose; // Close leaves DTR up (COMnKeepDTR, KPESC_*_DTR_ON_CLOSE)
|
|
volatile bool eofSeen; // EofChar received in non-binary mode (CSTF_EOF)
|
|
volatile uint8_t notifySent; // CN_* notifications posted and not yet re-armed
|
|
} PortStateT;
|
|
|
|
// Parallel port state (polled output through BIOS INT 17h)
|
|
typedef struct {
|
|
volatile uint16_t evtWord; // cevt target (printers raise no events)
|
|
uint16_t evtMask; // Set by cevt
|
|
uint16_t errorFlags; // CE_PTO/CE_IOE/CE_DNS/CE_OOP, cleared by stacom
|
|
bool isOpen;
|
|
DCB dcb; // Settings handed to inicom/setcom (getdcb)
|
|
} LptStateT;
|
|
|
|
typedef BOOL (FAR PASCAL *PostMessageProcT)(HWND, UINT, WPARAM, LPARAM);
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Shared globals
|
|
// -----------------------------------------------------------------------
|
|
extern PortStateT ports[MAX_PORTS]; // commdrv.c
|
|
extern volatile bool portsSuspended; // commdrv.c: true between Suspend/ReactivateOpenCommPorts
|
|
extern PostMessageProcT postMessageProc; // isr.c: resolved from USER by enableNotification
|
|
|
|
// -----------------------------------------------------------------------
|
|
// commdrv.c
|
|
// -----------------------------------------------------------------------
|
|
uint16_t readBiosWord(uint16_t offset);
|
|
|
|
#if DBG_ENABLED
|
|
void dbgHex16(const char FAR *label, uint16_t val);
|
|
void dbgStr(const char FAR *msg);
|
|
#else
|
|
#define dbgHex16(label, val) ((void)0)
|
|
#define dbgStr(msg) ((void)0)
|
|
#endif
|
|
|
|
// -----------------------------------------------------------------------
|
|
// isr.c (functions marked "CLI" require interrupts disabled)
|
|
// -----------------------------------------------------------------------
|
|
void checkNotify(PortStateT *port); // CLI
|
|
int16_t hookIsr(PortStateT *port);
|
|
void reactivateIrqs(void);
|
|
void refreshNotify(PortStateT *port, uint8_t acked); // CLI
|
|
void releaseRxFlow(PortStateT *port); // CLI
|
|
void startTx(PortStateT *port); // CLI
|
|
void unhookIsr(PortStateT *port);
|
|
void updateRxFlow(PortStateT *port); // CLI
|
|
void updateTxHold(PortStateT *port); // CLI
|
|
void writeIer(PortStateT *port, uint8_t ier); // CLI
|
|
void writeMcr(PortStateT *port); // CLI
|
|
|
|
// -----------------------------------------------------------------------
|
|
// lpt.c
|
|
// -----------------------------------------------------------------------
|
|
LptStateT *getOpenLpt(int16_t commId);
|
|
bool isLptId(int16_t commId);
|
|
int32_t lptBaseIrq(int16_t commId);
|
|
int16_t lptClose(int16_t commId);
|
|
void lptCloseAll(void);
|
|
int16_t lptMaxId(void);
|
|
int16_t lptOpen(const DCB FAR *dcb);
|
|
void lptReset(LptStateT *lpt);
|
|
int16_t lptWrite(LptStateT *lpt, const uint8_t FAR *buf, int16_t len);
|
|
|
|
#endif // COMMDRV_H
|