// 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 #include #include #include // 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