LIBRARY name stays COMM for import resolution. Debug strings updated to KPCOMM. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1641 lines
46 KiB
C
1641 lines
46 KiB
C
// commdrv.c - KPCOMM.DRV -- High-speed COMM.DRV replacement
|
|
//
|
|
// All exported COMM.DRV functions, ring buffer management, port
|
|
// initialization, 16550 FIFO detection, and flow control.
|
|
//
|
|
// Drop-in replacement for Windows 3.1 stock COMM.DRV with proper
|
|
// FIFO management for reliable operation at 57600 and 115200 baud.
|
|
|
|
#include "commdrv.h"
|
|
#include <dos.h>
|
|
#include <string.h>
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Debug logging via OutputDebugString
|
|
// -----------------------------------------------------------------------
|
|
#define DBG_ENABLED 0
|
|
|
|
#if DBG_ENABLED
|
|
static void dbgStr(const char FAR *msg)
|
|
{
|
|
OutputDebugString(msg);
|
|
}
|
|
|
|
// Log a label and a 16-bit hex value: "label: 0xNNNN\r\n"
|
|
static void dbgHex16(const char FAR *label, uint16_t val)
|
|
{
|
|
static const char hex[] = "0123456789ABCDEF";
|
|
char buf[48];
|
|
int i;
|
|
|
|
for (i = 0; i < 40 && label[i]; i++) {
|
|
buf[i] = label[i];
|
|
}
|
|
buf[i++] = ':';
|
|
buf[i++] = ' ';
|
|
buf[i++] = '0';
|
|
buf[i++] = 'x';
|
|
buf[i++] = hex[(val >> 12) & 0xF];
|
|
buf[i++] = hex[(val >> 8) & 0xF];
|
|
buf[i++] = hex[(val >> 4) & 0xF];
|
|
buf[i++] = hex[val & 0xF];
|
|
buf[i++] = '\r';
|
|
buf[i++] = '\n';
|
|
buf[i] = '\0';
|
|
OutputDebugString(buf);
|
|
}
|
|
|
|
// Log a label and a signed 16-bit value: "label: -NNNN\r\n"
|
|
static void dbgInt16(const char FAR *label, int16_t val)
|
|
{
|
|
char buf[48];
|
|
int i;
|
|
uint16_t uv;
|
|
|
|
for (i = 0; i < 40 && label[i]; i++) {
|
|
buf[i] = label[i];
|
|
}
|
|
buf[i++] = ':';
|
|
buf[i++] = ' ';
|
|
|
|
if (val < 0) {
|
|
buf[i++] = '-';
|
|
uv = (uint16_t)(-(int)val);
|
|
} else {
|
|
uv = (uint16_t)val;
|
|
}
|
|
|
|
// Print decimal (up to 5 digits)
|
|
{
|
|
char digits[6];
|
|
int n = 0;
|
|
do {
|
|
digits[n++] = (char)('0' + (uv % 10));
|
|
uv /= 10;
|
|
} while (uv > 0);
|
|
while (n > 0) {
|
|
buf[i++] = digits[--n];
|
|
}
|
|
}
|
|
|
|
buf[i++] = '\r';
|
|
buf[i++] = '\n';
|
|
buf[i] = '\0';
|
|
OutputDebugString(buf);
|
|
}
|
|
|
|
// Log a FAR pointer as "label: SSSS:OOOO\r\n"
|
|
static void dbgFarPtr(const char FAR *label, void FAR *ptr)
|
|
{
|
|
static const char hex[] = "0123456789ABCDEF";
|
|
char buf[48];
|
|
int i;
|
|
uint16_t seg;
|
|
uint16_t off;
|
|
|
|
seg = _FP_SEG(ptr);
|
|
off = _FP_OFF(ptr);
|
|
|
|
for (i = 0; i < 32 && label[i]; i++) {
|
|
buf[i] = label[i];
|
|
}
|
|
buf[i++] = ':';
|
|
buf[i++] = ' ';
|
|
buf[i++] = hex[(seg >> 12) & 0xF];
|
|
buf[i++] = hex[(seg >> 8) & 0xF];
|
|
buf[i++] = hex[(seg >> 4) & 0xF];
|
|
buf[i++] = hex[seg & 0xF];
|
|
buf[i++] = ':';
|
|
buf[i++] = hex[(off >> 12) & 0xF];
|
|
buf[i++] = hex[(off >> 8) & 0xF];
|
|
buf[i++] = hex[(off >> 4) & 0xF];
|
|
buf[i++] = hex[off & 0xF];
|
|
buf[i++] = '\r';
|
|
buf[i++] = '\n';
|
|
buf[i] = '\0';
|
|
OutputDebugString(buf);
|
|
}
|
|
|
|
// Hex dump first 'count' bytes at a FAR pointer
|
|
static void dbgDump(const char FAR *label, void FAR *ptr, int count)
|
|
{
|
|
static const char hex[] = "0123456789ABCDEF";
|
|
char buf[80];
|
|
uint8_t FAR *p;
|
|
int i;
|
|
int bi;
|
|
|
|
p = (uint8_t FAR *)ptr;
|
|
|
|
// Print label
|
|
for (bi = 0; bi < 32 && label[bi]; bi++) {
|
|
buf[bi] = label[bi];
|
|
}
|
|
buf[bi++] = ':';
|
|
buf[bi++] = ' ';
|
|
|
|
for (i = 0; i < count && bi < 70; i++) {
|
|
buf[bi++] = hex[(p[i] >> 4) & 0xF];
|
|
buf[bi++] = hex[p[i] & 0xF];
|
|
buf[bi++] = ' ';
|
|
}
|
|
buf[bi++] = '\r';
|
|
buf[bi++] = '\n';
|
|
buf[bi] = '\0';
|
|
OutputDebugString(buf);
|
|
}
|
|
#else
|
|
#define dbgStr(msg) ((void)0)
|
|
#define dbgHex16(label, val) ((void)0)
|
|
#define dbgInt16(label, val) ((void)0)
|
|
#define dbgFarPtr(label, p) ((void)0)
|
|
#define dbgDump(l, p, n) ((void)0)
|
|
#endif
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Prototypes
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export inicom(DCB FAR *dcb);
|
|
int16_t FAR PASCAL _export setcom(DCB FAR *dcb);
|
|
int16_t FAR PASCAL _export setque(int16_t commId, int16_t rxSize, int16_t txSize);
|
|
int16_t FAR PASCAL _export reccom(int16_t commId, void FAR *buf, int16_t len);
|
|
int16_t FAR PASCAL _export sndcom(int16_t commId, void FAR *buf, int16_t len);
|
|
int16_t FAR PASCAL _export ctx(int16_t commId, int16_t ch);
|
|
int16_t FAR PASCAL _export trmcom(int16_t commId);
|
|
int16_t FAR PASCAL _export stacom(int16_t commId, COMSTAT FAR *stat);
|
|
int32_t FAR PASCAL _export cevt(int16_t commId, int16_t evtMask);
|
|
uint16_t FAR PASCAL _export cevtget(int16_t commId, int16_t evtMask);
|
|
int16_t FAR PASCAL _export cextfcn(int16_t commId, int16_t func);
|
|
int16_t FAR PASCAL _export cflush(int16_t commId, int16_t queue);
|
|
int16_t FAR PASCAL _export csetbrk(int16_t commId);
|
|
int16_t FAR PASCAL _export cclrbrk(int16_t commId);
|
|
DCB FAR * FAR PASCAL _export getdcb(int16_t commId);
|
|
void FAR PASCAL _export suspendOpenCommPorts(void);
|
|
void FAR PASCAL _export reactivateOpenCommPorts(void);
|
|
int16_t FAR PASCAL _export commWriteString(int16_t commId, void FAR *buf, int16_t len);
|
|
int16_t FAR PASCAL _export readCommString(int16_t commId, void FAR *buf, int16_t len);
|
|
int16_t FAR PASCAL _export enableNotification(int16_t commId, HWND hwnd, int16_t rxThresh, int16_t txThresh);
|
|
|
|
int FAR PASCAL _export commNotifyWndProc(void);
|
|
int FAR PASCAL LibMain(HANDLE hInstance, WORD wDataSeg, WORD wHeapSize, LPSTR lpCmdLine);
|
|
int FAR PASCAL _export WEP(int nParam);
|
|
|
|
void applyBaudRate(PortStateT *port, uint16_t baud);
|
|
void applyLineParams(PortStateT *port, uint8_t byteSize, uint8_t parity, uint8_t stopBits);
|
|
static uint32_t cbrToBaud(uint16_t cbr);
|
|
int16_t detect16550(uint16_t baseAddr);
|
|
void enableFifo(PortStateT *port);
|
|
static int16_t freeBuffers(PortStateT *port);
|
|
static int16_t initBuffers(PortStateT *port, uint16_t rxSz, uint16_t txSz);
|
|
static void initPortState(PortStateT *port, int16_t commId);
|
|
void primeTx(PortStateT *port);
|
|
static void readPortConfig(int16_t commId, uint16_t FAR *baseAddr, uint8_t FAR *irq);
|
|
static uint16_t readSystemIni(const char FAR *section, const char FAR *key, uint16_t defVal);
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Port base addresses and IRQ table
|
|
// -----------------------------------------------------------------------
|
|
static const uint16_t portBase[MAX_PORTS] = {
|
|
COM1_BASE, COM2_BASE, COM3_BASE, COM4_BASE
|
|
};
|
|
|
|
static const uint8_t portIrq[MAX_PORTS] = {
|
|
COM1_IRQ, COM2_IRQ, COM3_IRQ, COM4_IRQ
|
|
};
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Global port state array
|
|
// -----------------------------------------------------------------------
|
|
PortStateT ports[MAX_PORTS];
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Global instance handle
|
|
// -----------------------------------------------------------------------
|
|
static HANDLE ghInstance = NULL;
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Dynamically resolved PostMessage
|
|
//
|
|
// comm.drv is loaded from [boot] before USER.EXE, so we can't have a
|
|
// static import for PostMessage. Resolve it on first use via
|
|
// GetProcAddress(GetModuleHandle("USER"), "PostMessage").
|
|
// -----------------------------------------------------------------------
|
|
PostMessageProcT pfnPostMessage = NULL;
|
|
|
|
// ISR hit counter for diagnostics
|
|
volatile uint16_t isrHitCount = 0;
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// applyBaudRate - Set the baud rate divisor on the UART
|
|
//
|
|
// The DCB BaudRate field is a 16-bit UINT. For rates that fit in 16 bits
|
|
// (up to 57600), the raw value is used directly. For higher rates like
|
|
// 115200, CBR_* index constants (0xFF00+) encode the rate. We detect
|
|
// these and translate via cbrToBaud().
|
|
// -----------------------------------------------------------------------
|
|
void applyBaudRate(PortStateT *port, uint16_t baud)
|
|
{
|
|
uint32_t actualBaud;
|
|
uint16_t divisor;
|
|
uint8_t lcr;
|
|
uint16_t base;
|
|
|
|
if (baud == 0) {
|
|
return;
|
|
}
|
|
|
|
// Translate CBR_* index constants to actual baud rate
|
|
if ((baud & 0xFF00) == 0xFF00) {
|
|
actualBaud = cbrToBaud(baud);
|
|
} else {
|
|
actualBaud = (uint32_t)baud;
|
|
}
|
|
|
|
if (actualBaud == 0) {
|
|
return;
|
|
}
|
|
|
|
base = port->baseAddr;
|
|
divisor = (uint16_t)(BAUD_DIVISOR_BASE / actualBaud);
|
|
|
|
// Set DLAB to access divisor latch
|
|
lcr = (uint8_t)_inp(base + UART_LCR);
|
|
_outp(base + UART_LCR, lcr | LCR_DLAB);
|
|
|
|
_outp(base + UART_DLL, (uint8_t)(divisor & 0xFF));
|
|
_outp(base + UART_DLM, (uint8_t)(divisor >> 8));
|
|
|
|
// Clear DLAB
|
|
_outp(base + UART_LCR, lcr);
|
|
|
|
port->baudRate = baud;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// applyLineParams - Set word length, parity, and stop bits on UART
|
|
// -----------------------------------------------------------------------
|
|
void applyLineParams(PortStateT *port, uint8_t byteSize, uint8_t parity, uint8_t stopBits)
|
|
{
|
|
uint8_t lcr;
|
|
uint16_t base;
|
|
|
|
base = port->baseAddr;
|
|
lcr = 0;
|
|
|
|
// Word length
|
|
if (byteSize >= 5 && byteSize <= 8) {
|
|
lcr |= (byteSize - 5);
|
|
} else {
|
|
lcr |= LCR_WLS_8;
|
|
}
|
|
|
|
// Stop bits
|
|
if (stopBits == 2) {
|
|
lcr |= LCR_STB;
|
|
}
|
|
|
|
// Parity
|
|
switch (parity) {
|
|
case ODDPARITY:
|
|
lcr |= LCR_PEN;
|
|
break;
|
|
case EVENPARITY:
|
|
lcr |= LCR_PEN | LCR_EPS;
|
|
break;
|
|
case MARKPARITY:
|
|
lcr |= LCR_PEN | LCR_SPAR;
|
|
break;
|
|
case SPACEPARITY:
|
|
lcr |= LCR_PEN | LCR_EPS | LCR_SPAR;
|
|
break;
|
|
case NOPARITY:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
_outp(base + UART_LCR, lcr);
|
|
|
|
port->byteSize = byteSize;
|
|
port->parity = parity;
|
|
port->stopBits = stopBits;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// cbrToBaud - Translate CBR_* index constant to actual baud rate
|
|
//
|
|
// Returns baud rate as uint32_t (needed for 115200 which exceeds 16 bits).
|
|
// Returns 0 for unrecognized constants.
|
|
// -----------------------------------------------------------------------
|
|
static uint32_t cbrToBaud(uint16_t cbr)
|
|
{
|
|
switch (cbr) {
|
|
case CBR_110:
|
|
return 110UL;
|
|
case CBR_300:
|
|
return 300UL;
|
|
case CBR_600:
|
|
return 600UL;
|
|
case CBR_1200:
|
|
return 1200UL;
|
|
case CBR_2400:
|
|
return 2400UL;
|
|
case CBR_4800:
|
|
return 4800UL;
|
|
case CBR_9600:
|
|
return 9600UL;
|
|
case CBR_14400:
|
|
return 14400UL;
|
|
case CBR_19200:
|
|
return 19200UL;
|
|
case CBR_38400:
|
|
return 38400UL;
|
|
case CBR_56000:
|
|
return 56000UL;
|
|
case CBR_115200:
|
|
return 115200UL;
|
|
default:
|
|
return 0UL;
|
|
}
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// cclrbrk - Clear break signal (ordinal 14)
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export cclrbrk(int16_t commId)
|
|
{
|
|
PortStateT *port;
|
|
uint8_t lcr;
|
|
|
|
dbgInt16("KPCOMM: cclrbrk Id", commId);
|
|
|
|
if (commId < 0 || commId >= MAX_PORTS) {
|
|
return -1;
|
|
}
|
|
|
|
port = &ports[commId];
|
|
if (!port->isOpen) {
|
|
return -1;
|
|
}
|
|
|
|
lcr = (uint8_t)_inp(port->baseAddr + UART_LCR);
|
|
_outp(port->baseAddr + UART_LCR, lcr & ~LCR_SBRK);
|
|
|
|
port->breakState = FALSE;
|
|
return 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// cevt - Set event mask and return pointer to event word (ordinal 11)
|
|
//
|
|
// Returns a FAR pointer to the event word (packed into int32_t).
|
|
// This matches the stock COMM.DRV behavior -- the event word is a
|
|
// volatile uint16_t that accumulates event bits until cleared.
|
|
// -----------------------------------------------------------------------
|
|
int32_t FAR PASCAL _export cevt(int16_t commId, int16_t evtMask)
|
|
{
|
|
PortStateT *port;
|
|
uint16_t FAR *ptr;
|
|
int32_t retVal;
|
|
|
|
#if DBG_ENABLED
|
|
{
|
|
uint16_t ssSeg;
|
|
uint16_t bpVal;
|
|
_asm mov ssSeg, ss
|
|
_asm mov bpVal, bp
|
|
dbgHex16("KPCOMM: cevt SS", ssSeg);
|
|
dbgHex16("KPCOMM: cevt BP", bpVal);
|
|
dbgDump("KPCOMM: cevt stk", (void FAR *)((uint32_t)ssSeg << 16 | (bpVal + 6)), 16);
|
|
}
|
|
#endif
|
|
|
|
dbgInt16("KPCOMM: cevt Id", commId);
|
|
dbgHex16("KPCOMM: cevt mask", (uint16_t)evtMask);
|
|
|
|
if (commId < 0 || commId >= MAX_PORTS) {
|
|
return 0L;
|
|
}
|
|
|
|
port = &ports[commId];
|
|
if (!port->isOpen) {
|
|
dbgStr("KPCOMM: cevt not open\r\n");
|
|
return 0L;
|
|
}
|
|
|
|
port->comDeb.evtMask = (uint16_t)evtMask;
|
|
|
|
ptr = &port->comDeb.evtWord;
|
|
retVal = (int32_t)(void FAR *)ptr;
|
|
dbgHex16("KPCOMM: cevt retSeg", (uint16_t)(retVal >> 16));
|
|
dbgHex16("KPCOMM: cevt retOff", (uint16_t)(retVal & 0xFFFF));
|
|
dbgStr("KPCOMM: cevt OK\r\n");
|
|
return retVal;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// cevtget - Read and clear event word (ordinal 12)
|
|
//
|
|
// Returns current event bits masked by evtMask, then clears those bits.
|
|
// -----------------------------------------------------------------------
|
|
uint16_t FAR PASCAL _export cevtget(int16_t commId, int16_t evtMask)
|
|
{
|
|
PortStateT *port;
|
|
uint16_t events;
|
|
|
|
dbgInt16("KPCOMM: cevtget Id", commId);
|
|
|
|
if (commId < 0 || commId >= MAX_PORTS) {
|
|
return 0;
|
|
}
|
|
|
|
port = &ports[commId];
|
|
if (!port->isOpen) {
|
|
return 0;
|
|
}
|
|
|
|
_disable();
|
|
events = port->comDeb.evtWord & (uint16_t)evtMask;
|
|
port->comDeb.evtWord &= ~(uint16_t)evtMask;
|
|
_enable();
|
|
|
|
return events;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// cextfcn - Escape function for DTR/RTS/break control (ordinal 9)
|
|
//
|
|
// Note: 16-bit API returns 0 on success (unlike Win32 nonzero=success).
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export cextfcn(int16_t commId, int16_t func)
|
|
{
|
|
PortStateT *port;
|
|
uint8_t mcr;
|
|
uint16_t base;
|
|
|
|
dbgInt16("KPCOMM: cextfcn Id", commId);
|
|
dbgInt16("KPCOMM: cextfcn func", func);
|
|
|
|
if (commId < 0 || commId >= MAX_PORTS) {
|
|
return -1;
|
|
}
|
|
|
|
port = &ports[commId];
|
|
if (!port->isOpen) {
|
|
return -1;
|
|
}
|
|
|
|
base = port->baseAddr;
|
|
mcr = (uint8_t)_inp(base + UART_MCR);
|
|
|
|
switch (func) {
|
|
case SETDTR:
|
|
mcr |= MCR_DTR;
|
|
port->dtrState = TRUE;
|
|
break;
|
|
case CLRDTR:
|
|
mcr &= ~MCR_DTR;
|
|
port->dtrState = FALSE;
|
|
break;
|
|
case SETRTS:
|
|
mcr |= MCR_RTS;
|
|
port->rtsState = TRUE;
|
|
break;
|
|
case CLRRTS:
|
|
mcr &= ~MCR_RTS;
|
|
port->rtsState = FALSE;
|
|
break;
|
|
case ESC_SETBREAK:
|
|
return csetbrk(commId);
|
|
case ESC_CLRBREAK:
|
|
return cclrbrk(commId);
|
|
case RESETDEV:
|
|
// No-op for serial ports
|
|
break;
|
|
case SETXON:
|
|
// Resume TX as if XON received
|
|
_disable();
|
|
port->txStopped = FALSE;
|
|
if (port->txCount > 0) {
|
|
_outp(base + UART_IER, (uint8_t)(_inp(base + UART_IER) | IER_THRE));
|
|
}
|
|
_enable();
|
|
return 0;
|
|
case SETXOFF:
|
|
// Stop TX as if XOFF received
|
|
port->txStopped = TRUE;
|
|
return 0;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
_outp(base + UART_MCR, mcr);
|
|
return 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// cflush - Flush receive or transmit buffer (ordinal 10)
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export cflush(int16_t commId, int16_t queue)
|
|
{
|
|
PortStateT *port;
|
|
|
|
dbgInt16("KPCOMM: cflush Id", commId);
|
|
|
|
if (commId < 0 || commId >= MAX_PORTS) {
|
|
return -1;
|
|
}
|
|
|
|
port = &ports[commId];
|
|
if (!port->isOpen) {
|
|
return -1;
|
|
}
|
|
|
|
_disable();
|
|
if (queue == FLUSH_RX) {
|
|
port->rxHead = 0;
|
|
port->rxTail = 0;
|
|
port->rxCount = 0;
|
|
port->comDeb.qInHead = 0;
|
|
port->comDeb.qInTail = 0;
|
|
port->comDeb.qInCount = 0;
|
|
// Reset FIFO to clear hardware buffer too
|
|
if (port->is16550 && port->fifoEnabled) {
|
|
_outp(port->baseAddr + UART_FCR, FCR_ENABLE | FCR_RX_RESET | port->fifoTrigger);
|
|
}
|
|
} else {
|
|
port->txHead = 0;
|
|
port->txTail = 0;
|
|
port->txCount = 0;
|
|
port->comDeb.qOutHead = 0;
|
|
port->comDeb.qOutTail = 0;
|
|
port->comDeb.qOutCount = 0;
|
|
if (port->is16550 && port->fifoEnabled) {
|
|
_outp(port->baseAddr + UART_FCR, FCR_ENABLE | FCR_TX_RESET | port->fifoTrigger);
|
|
}
|
|
}
|
|
_enable();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// commWriteString - Write data (ordinal 19, alias for sndcom)
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export commWriteString(int16_t commId, void FAR *buf, int16_t len)
|
|
{
|
|
dbgInt16("KPCOMM: commWriteStr Id", commId);
|
|
return sndcom(commId, buf, len);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// commNotifyWndProc - Ordinal 101 stub (undocumented, Win 3.1 compat)
|
|
//
|
|
// The stock COMM.DRV and CyberCom export an unnamed function at ordinal
|
|
// 101. Purpose is undocumented; may be an internal callback used by
|
|
// USER.EXE. CyberCom's implementation uses plain RETF (no params).
|
|
// Return 0 (not handled).
|
|
// -----------------------------------------------------------------------
|
|
int FAR PASCAL _export commNotifyWndProc(void)
|
|
{
|
|
dbgStr("KPCOMM: ord101 called\r\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// csetbrk - Assert break signal (ordinal 13)
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export csetbrk(int16_t commId)
|
|
{
|
|
PortStateT *port;
|
|
uint8_t lcr;
|
|
|
|
dbgInt16("KPCOMM: csetbrk Id", commId);
|
|
|
|
if (commId < 0 || commId >= MAX_PORTS) {
|
|
return -1;
|
|
}
|
|
|
|
port = &ports[commId];
|
|
if (!port->isOpen) {
|
|
return -1;
|
|
}
|
|
|
|
lcr = (uint8_t)_inp(port->baseAddr + UART_LCR);
|
|
_outp(port->baseAddr + UART_LCR, lcr | LCR_SBRK);
|
|
|
|
port->breakState = TRUE;
|
|
return 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// ctx - Send single character with priority (ordinal 6)
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export ctx(int16_t commId, int16_t ch)
|
|
{
|
|
PortStateT *port;
|
|
|
|
dbgInt16("KPCOMM: ctx Id", commId);
|
|
|
|
if (commId < 0 || commId >= MAX_PORTS) {
|
|
return -1;
|
|
}
|
|
|
|
port = &ports[commId];
|
|
if (!port->isOpen) {
|
|
return -1;
|
|
}
|
|
|
|
_disable();
|
|
port->txImmediate = ch;
|
|
// Enable THRE interrupt to get it sent
|
|
_outp(port->baseAddr + UART_IER, (uint8_t)(_inp(port->baseAddr + UART_IER) | IER_THRE));
|
|
_enable();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// detect16550 - Test if the UART at baseAddr is a 16550 with working FIFOs
|
|
//
|
|
// Returns 1 if 16550A (working FIFO), 0 otherwise.
|
|
// -----------------------------------------------------------------------
|
|
int16_t detect16550(uint16_t baseAddr)
|
|
{
|
|
uint8_t iir;
|
|
|
|
// Try to enable FIFOs
|
|
_outp(baseAddr + UART_FCR, FCR_ENABLE);
|
|
|
|
// Read IIR -- bits 7:6 should be set if FIFOs are enabled
|
|
iir = (uint8_t)_inp(baseAddr + UART_IIR);
|
|
|
|
// Disable FIFOs for now (we'll re-enable properly later)
|
|
_outp(baseAddr + UART_FCR, 0);
|
|
|
|
// 16550A has both bits 7:6 set when FIFOs enabled
|
|
// Plain 16550 (broken FIFO) only sets bit 7
|
|
return ((iir & IIR_FIFO_MASK) == IIR_FIFO_MASK) ? 1 : 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// enableFifo - Enable 16550 FIFOs with configurable trigger level
|
|
//
|
|
// Uses port->fifoTrigger (set from SYSTEM.INI COMnRxTRIGGER, default 8).
|
|
// Respects port->fifoEnabled (set from SYSTEM.INI COMnFIFO, default 1).
|
|
// -----------------------------------------------------------------------
|
|
void enableFifo(PortStateT *port)
|
|
{
|
|
if (!port->is16550 || !port->fifoEnabled) {
|
|
return;
|
|
}
|
|
|
|
// Enable + reset both FIFOs + configured RX trigger level
|
|
_outp(port->baseAddr + UART_FCR, FCR_ENABLE | FCR_RX_RESET | FCR_TX_RESET | port->fifoTrigger);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// enableNotification - Register window for WM_COMMNOTIFY (ordinal 100)
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export enableNotification(int16_t commId, HWND hwnd, int16_t rxThresh, int16_t txThresh)
|
|
{
|
|
PortStateT *port;
|
|
|
|
dbgInt16("KPCOMM: enableNotif Id", commId);
|
|
dbgHex16("KPCOMM: enableNotif hwnd", (uint16_t)hwnd);
|
|
dbgInt16("KPCOMM: enableNotif rxTh", rxThresh);
|
|
dbgInt16("KPCOMM: enableNotif txTh", txThresh);
|
|
|
|
if (commId < 0 || commId >= MAX_PORTS) {
|
|
return FALSE;
|
|
}
|
|
|
|
port = &ports[commId];
|
|
if (!port->isOpen) {
|
|
dbgStr("KPCOMM: enableNotif not open\r\n");
|
|
return FALSE;
|
|
}
|
|
|
|
// Lazily resolve PostMessage from USER.EXE
|
|
// (not available at boot when comm.drv is loaded from [boot])
|
|
if (!pfnPostMessage) {
|
|
HMODULE hUser = GetModuleHandle("USER");
|
|
if (hUser) {
|
|
pfnPostMessage = (PostMessageProcT)GetProcAddress(hUser, "PostMessage");
|
|
}
|
|
}
|
|
|
|
port->hwndNotify = hwnd;
|
|
port->rxNotifyThresh = rxThresh;
|
|
port->txNotifyThresh = txThresh;
|
|
|
|
dbgStr("KPCOMM: enableNotif OK\r\n");
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// freeBuffers - Free ring buffers for a port
|
|
// -----------------------------------------------------------------------
|
|
static int16_t freeBuffers(PortStateT *port)
|
|
{
|
|
HGLOBAL hMem;
|
|
|
|
if (port->rxBuf) {
|
|
hMem = (HGLOBAL)LOWORD(GlobalHandle(SELECTOROF(port->rxBuf)));
|
|
if (hMem) {
|
|
GlobalUnlock(hMem);
|
|
GlobalFree(hMem);
|
|
}
|
|
port->rxBuf = NULL;
|
|
}
|
|
|
|
if (port->txBuf) {
|
|
hMem = (HGLOBAL)LOWORD(GlobalHandle(SELECTOROF(port->txBuf)));
|
|
if (hMem) {
|
|
GlobalUnlock(hMem);
|
|
GlobalFree(hMem);
|
|
}
|
|
port->txBuf = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// getdcb - Return pointer to internal DCB (ordinal 15)
|
|
//
|
|
// Takes int16_t commId (2 bytes, RETF 2). Returns DCB FAR * in DX:AX.
|
|
// The caller (USER.EXE / COMMTASK.DLL) copies from this pointer.
|
|
// CyberCom confirms: RETF 2, return pointer.
|
|
// -----------------------------------------------------------------------
|
|
DCB FAR * FAR PASCAL _export getdcb(int16_t commId)
|
|
{
|
|
PortStateT *port;
|
|
|
|
dbgInt16("KPCOMM: getdcb Id", commId);
|
|
|
|
if (commId < 0 || commId >= MAX_PORTS) {
|
|
dbgStr("KPCOMM: getdcb bad id\r\n");
|
|
return NULL;
|
|
}
|
|
|
|
port = &ports[commId];
|
|
if (!port->isOpen) {
|
|
dbgStr("KPCOMM: getdcb not open\r\n");
|
|
return NULL;
|
|
}
|
|
|
|
dbgHex16("KPCOMM: getdcb isrHits", isrHitCount);
|
|
dbgStr("KPCOMM: getdcb OK\r\n");
|
|
return &port->dcb;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// inicom - Open a COM port (ordinal 1)
|
|
//
|
|
// Opens the port specified in dcb->Id, allocates ring buffers, hooks ISR,
|
|
// enables interrupts, and applies DCB settings.
|
|
//
|
|
// rxBuf and rxSize are ignored (legacy params); we allocate our own buffers.
|
|
//
|
|
// Returns commId (0-3) on success, negative error code on failure.
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export inicom(DCB FAR *dcb)
|
|
{
|
|
int16_t commId;
|
|
PortStateT *port;
|
|
uint16_t rxBufSize;
|
|
uint16_t txBufSize;
|
|
uint8_t mcr;
|
|
|
|
dbgStr("KPCOMM: inicom enter\r\n");
|
|
|
|
// Dump raw stack to see actual parameters passed by caller
|
|
{
|
|
uint16_t bpVal;
|
|
uint16_t ssVal;
|
|
void FAR *stkPtr;
|
|
_asm mov bpVal, bp
|
|
_asm mov ssVal, ss
|
|
stkPtr = (void FAR *)((uint32_t)ssVal << 16 | (bpVal + 6));
|
|
dbgHex16("KPCOMM: inicom SS", ssVal);
|
|
dbgHex16("KPCOMM: inicom BP", bpVal);
|
|
dbgDump("KPCOMM: inicom stk", stkPtr, 24);
|
|
}
|
|
|
|
if (!dcb) {
|
|
dbgStr("KPCOMM: inicom NULL dcb\r\n");
|
|
return IE_DEFAULT;
|
|
}
|
|
|
|
dbgFarPtr("KPCOMM: inicom dcb", (void FAR *)dcb);
|
|
dbgDump("KPCOMM: inicom raw", (void FAR *)dcb, 16);
|
|
dbgHex16("KPCOMM: sizeof(DCB)", (uint16_t)sizeof(DCB));
|
|
|
|
commId = dcb->Id;
|
|
dbgInt16("KPCOMM: inicom Id", commId);
|
|
dbgHex16("KPCOMM: inicom BaudRate", dcb->BaudRate);
|
|
dbgHex16("KPCOMM: inicom ByteSize", (uint16_t)dcb->ByteSize);
|
|
dbgHex16("KPCOMM: inicom Parity", (uint16_t)dcb->Parity);
|
|
dbgHex16("KPCOMM: inicom StopBits", (uint16_t)dcb->StopBits);
|
|
|
|
if (commId < 0 || commId >= MAX_PORTS) {
|
|
dbgStr("KPCOMM: inicom IE_BADID\r\n");
|
|
return IE_BADID;
|
|
}
|
|
|
|
port = &ports[commId];
|
|
if (port->isOpen) {
|
|
dbgStr("KPCOMM: inicom IE_OPEN\r\n");
|
|
return IE_OPEN;
|
|
}
|
|
|
|
// Initialize port state
|
|
initPortState(port, commId);
|
|
dbgHex16("KPCOMM: inicom baseAddr", port->baseAddr);
|
|
dbgHex16("KPCOMM: inicom irq", (uint16_t)port->irq);
|
|
|
|
// Read buffer sizes from SYSTEM.INI if available (COMnBuffer key)
|
|
{
|
|
char bufKey[12];
|
|
bufKey[0] = 'C';
|
|
bufKey[1] = 'O';
|
|
bufKey[2] = 'M';
|
|
bufKey[3] = (char)('1' + commId);
|
|
bufKey[4] = 'B';
|
|
bufKey[5] = 'u';
|
|
bufKey[6] = 'f';
|
|
bufKey[7] = 'f';
|
|
bufKey[8] = 'e';
|
|
bufKey[9] = 'r';
|
|
bufKey[10] = '\0';
|
|
rxBufSize = readSystemIni("386Enh", bufKey, DEFAULT_RX_SIZE);
|
|
}
|
|
txBufSize = rxBufSize;
|
|
dbgHex16("KPCOMM: inicom rxBufSize", rxBufSize);
|
|
|
|
// Allocate ring buffers
|
|
if (initBuffers(port, rxBufSize, txBufSize) != 0) {
|
|
dbgStr("KPCOMM: inicom IE_MEMORY\r\n");
|
|
return IE_MEMORY;
|
|
}
|
|
dbgStr("KPCOMM: inicom buffers OK\r\n");
|
|
|
|
// Detect 16550 FIFO
|
|
port->is16550 = (uint8_t)detect16550(port->baseAddr);
|
|
dbgHex16("KPCOMM: inicom is16550", (uint16_t)port->is16550);
|
|
|
|
// Hook ISR
|
|
if (hookIsr(port) != 0) {
|
|
dbgStr("KPCOMM: inicom hookIsr FAIL\r\n");
|
|
freeBuffers(port);
|
|
return IE_HARDWARE;
|
|
}
|
|
dbgStr("KPCOMM: inicom ISR hooked\r\n");
|
|
|
|
port->isOpen = TRUE;
|
|
|
|
// Enable FIFOs if 16550 detected
|
|
enableFifo(port);
|
|
|
|
// Apply DCB settings
|
|
_fmemcpy(&port->dcb, dcb, sizeof(DCB));
|
|
applyBaudRate(port, dcb->BaudRate);
|
|
applyLineParams(port, dcb->ByteSize, dcb->Parity, dcb->StopBits);
|
|
|
|
// Set flow control from DCB
|
|
if (dcb->fOutxCtsFlow && dcb->fInX) {
|
|
port->hsMode = HS_BOTH;
|
|
} else if (dcb->fOutxCtsFlow) {
|
|
port->hsMode = HS_RTSCTS;
|
|
} else if (dcb->fInX) {
|
|
port->hsMode = HS_XONXOFF;
|
|
} else {
|
|
port->hsMode = HS_NONE;
|
|
}
|
|
|
|
port->xonChar = dcb->XonChar;
|
|
port->xoffChar = dcb->XoffChar;
|
|
port->xonLim = dcb->XonLim;
|
|
port->xoffLim = dcb->XoffLim;
|
|
|
|
// Raise DTR and RTS, enable OUT2 (master interrupt gate)
|
|
mcr = MCR_DTR | MCR_RTS | MCR_OUT2;
|
|
_outp(port->baseAddr + UART_MCR, mcr);
|
|
port->dtrState = TRUE;
|
|
port->rtsState = TRUE;
|
|
|
|
// Clear any pending status
|
|
_inp(port->baseAddr + UART_LSR);
|
|
port->comDeb.msrShadow = (uint8_t)_inp(port->baseAddr + UART_MSR);
|
|
_inp(port->baseAddr + UART_RBR);
|
|
|
|
// Populate ComDEB for third-party compatibility
|
|
port->comDeb.port = port->baseAddr;
|
|
port->comDeb.baudRate = port->baudRate;
|
|
port->comDeb.qInSize = port->rxSize;
|
|
port->comDeb.qOutSize = port->txSize;
|
|
|
|
// Enable receive and line status interrupts
|
|
_outp(port->baseAddr + UART_IER, IER_RDA | IER_LSI | IER_MSI);
|
|
|
|
dbgInt16("KPCOMM: inicom OK, ret", commId);
|
|
return commId;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// initBuffers - Allocate ring buffers via GlobalAlloc
|
|
// -----------------------------------------------------------------------
|
|
static int16_t initBuffers(PortStateT *port, uint16_t rxSz, uint16_t txSz)
|
|
{
|
|
HGLOBAL hRx;
|
|
HGLOBAL hTx;
|
|
|
|
hRx = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, (DWORD)rxSz);
|
|
if (!hRx) {
|
|
return -1;
|
|
}
|
|
|
|
hTx = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, (DWORD)txSz);
|
|
if (!hTx) {
|
|
GlobalFree(hRx);
|
|
return -1;
|
|
}
|
|
|
|
port->rxBuf = (uint8_t FAR *)GlobalLock(hRx);
|
|
port->rxSize = rxSz;
|
|
port->rxHead = 0;
|
|
port->rxTail = 0;
|
|
port->rxCount = 0;
|
|
|
|
port->txBuf = (uint8_t FAR *)GlobalLock(hTx);
|
|
port->txSize = txSz;
|
|
port->txHead = 0;
|
|
port->txTail = 0;
|
|
port->txCount = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// initPortState - Initialize a port state structure with defaults
|
|
//
|
|
// Reads COMnBase, COMnIRQ, COMnFIFO, and COMnRxTRIGGER from
|
|
// SYSTEM.INI [386Enh] to support non-standard port configurations.
|
|
// -----------------------------------------------------------------------
|
|
static void initPortState(PortStateT *port, int16_t commId)
|
|
{
|
|
uint16_t baseAddr;
|
|
uint8_t irq;
|
|
uint16_t fifoEnabled;
|
|
uint16_t rxTrigger;
|
|
|
|
_fmemset(port, 0, sizeof(PortStateT));
|
|
|
|
port->commId = commId;
|
|
|
|
// Read port address and IRQ from SYSTEM.INI (or use defaults)
|
|
readPortConfig(commId, &baseAddr, &irq);
|
|
port->baseAddr = baseAddr;
|
|
port->irq = irq;
|
|
|
|
// Read FIFO enable setting (default: enabled)
|
|
{
|
|
char fifoKey[10];
|
|
fifoKey[0] = 'C';
|
|
fifoKey[1] = 'O';
|
|
fifoKey[2] = 'M';
|
|
fifoKey[3] = (char)('1' + commId);
|
|
fifoKey[4] = 'F';
|
|
fifoKey[5] = 'I';
|
|
fifoKey[6] = 'F';
|
|
fifoKey[7] = 'O';
|
|
fifoKey[8] = '\0';
|
|
fifoEnabled = readSystemIni("386Enh", fifoKey, 1);
|
|
}
|
|
port->fifoEnabled = (uint8_t)(fifoEnabled ? 1 : 0);
|
|
|
|
// Read RX trigger level from SYSTEM.INI (default: 8)
|
|
{
|
|
char trigKey[16];
|
|
trigKey[0] = 'C';
|
|
trigKey[1] = 'O';
|
|
trigKey[2] = 'M';
|
|
trigKey[3] = (char)('1' + commId);
|
|
trigKey[4] = 'R';
|
|
trigKey[5] = 'x';
|
|
trigKey[6] = 'T';
|
|
trigKey[7] = 'R';
|
|
trigKey[8] = 'I';
|
|
trigKey[9] = 'G';
|
|
trigKey[10] = 'G';
|
|
trigKey[11] = 'E';
|
|
trigKey[12] = 'R';
|
|
trigKey[13] = '\0';
|
|
rxTrigger = readSystemIni("386Enh", trigKey, 8);
|
|
}
|
|
switch (rxTrigger) {
|
|
case 1:
|
|
port->fifoTrigger = FCR_TRIG_1;
|
|
break;
|
|
case 4:
|
|
port->fifoTrigger = FCR_TRIG_4;
|
|
break;
|
|
case 14:
|
|
port->fifoTrigger = FCR_TRIG_14;
|
|
break;
|
|
case 8:
|
|
default:
|
|
port->fifoTrigger = FCR_TRIG_8;
|
|
break;
|
|
}
|
|
|
|
port->xonChar = 0x11; // DC1
|
|
port->xoffChar = 0x13; // DC3
|
|
port->xonLim = DEFAULT_RX_SIZE / 4;
|
|
port->xoffLim = DEFAULT_RX_SIZE / 4;
|
|
|
|
port->txImmediate = -1;
|
|
port->rxNotifyThresh = -1;
|
|
port->txNotifyThresh = -1;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// primeTx - Prime the transmitter by enabling THRE interrupt
|
|
//
|
|
// Called after writing data to the TX ring buffer to kick off
|
|
// transmission if it's not already running.
|
|
// -----------------------------------------------------------------------
|
|
void primeTx(PortStateT *port)
|
|
{
|
|
uint8_t ier;
|
|
|
|
ier = (uint8_t)_inp(port->baseAddr + UART_IER);
|
|
if (!(ier & IER_THRE)) {
|
|
_outp(port->baseAddr + UART_IER, ier | IER_THRE);
|
|
}
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// reactivateOpenCommPorts - Reactivate all ports after task switch (ordinal 18)
|
|
//
|
|
// Called by Windows when switching back to this VM.
|
|
// Re-enables interrupts and restores MCR state.
|
|
// -----------------------------------------------------------------------
|
|
void FAR PASCAL _export reactivateOpenCommPorts(void)
|
|
{
|
|
int16_t i;
|
|
PortStateT *port;
|
|
uint8_t mcr;
|
|
|
|
dbgStr("KPCOMM: reactivate\r\n");
|
|
|
|
for (i = 0; i < MAX_PORTS; i++) {
|
|
port = &ports[i];
|
|
if (!port->isOpen) {
|
|
continue;
|
|
}
|
|
|
|
// Restore MCR (DTR, RTS, OUT2)
|
|
mcr = MCR_OUT2;
|
|
if (port->dtrState) {
|
|
mcr |= MCR_DTR;
|
|
}
|
|
if (port->rtsState) {
|
|
mcr |= MCR_RTS;
|
|
}
|
|
_outp(port->baseAddr + UART_MCR, mcr);
|
|
|
|
// Re-enable FIFOs
|
|
enableFifo(port);
|
|
|
|
// Re-enable interrupts
|
|
_outp(port->baseAddr + UART_IER, IER_RDA | IER_LSI | IER_MSI);
|
|
if (port->txCount > 0) {
|
|
_outp(port->baseAddr + UART_IER, (uint8_t)(_inp(port->baseAddr + UART_IER) | IER_THRE));
|
|
}
|
|
|
|
// Unmask IRQ at PIC
|
|
{
|
|
uint8_t picMask = (uint8_t)_inp(PIC_DATA);
|
|
picMask &= ~port->irqMask;
|
|
_outp(PIC_DATA, picMask);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// readCommString - Read data (ordinal 20, alias for reccom)
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export readCommString(int16_t commId, void FAR *buf, int16_t len)
|
|
{
|
|
dbgInt16("KPCOMM: readCommStr Id", commId);
|
|
return reccom(commId, buf, len);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// readPortConfig - Read port base address and IRQ from SYSTEM.INI
|
|
//
|
|
// Checks [386Enh] for COMnBase (hex) and COMnIRQ overrides.
|
|
// Falls back to standard defaults if not present.
|
|
// -----------------------------------------------------------------------
|
|
static void readPortConfig(int16_t commId, uint16_t FAR *baseAddr, uint8_t FAR *irq)
|
|
{
|
|
char key[10];
|
|
char buf[16];
|
|
|
|
// Defaults
|
|
*baseAddr = portBase[commId];
|
|
*irq = portIrq[commId];
|
|
|
|
// Check COMnBase (e.g., COM3Base=3E8)
|
|
key[0] = 'C';
|
|
key[1] = 'O';
|
|
key[2] = 'M';
|
|
key[3] = (char)('1' + commId);
|
|
key[4] = 'B';
|
|
key[5] = 'a';
|
|
key[6] = 's';
|
|
key[7] = 'e';
|
|
key[8] = '\0';
|
|
|
|
GetPrivateProfileString("386Enh", key, "", buf, sizeof(buf), "SYSTEM.INI");
|
|
if (buf[0] != '\0') {
|
|
// Parse hex string manually (no strtoul in small model CRT)
|
|
uint16_t val = 0;
|
|
int16_t i = 0;
|
|
char ch;
|
|
|
|
while (buf[i] != '\0') {
|
|
ch = buf[i];
|
|
val <<= 4;
|
|
if (ch >= '0' && ch <= '9') {
|
|
val |= (uint16_t)(ch - '0');
|
|
} else if (ch >= 'A' && ch <= 'F') {
|
|
val |= (uint16_t)(ch - 'A' + 10);
|
|
} else if (ch >= 'a' && ch <= 'f') {
|
|
val |= (uint16_t)(ch - 'a' + 10);
|
|
}
|
|
i++;
|
|
}
|
|
if (val != 0) {
|
|
*baseAddr = val;
|
|
}
|
|
}
|
|
|
|
// Check COMnIRQ (e.g., COM3Irq=5)
|
|
key[4] = 'I';
|
|
key[5] = 'r';
|
|
key[6] = 'q';
|
|
key[7] = '\0';
|
|
|
|
{
|
|
uint16_t irqVal = readSystemIni("386Enh", key, (uint16_t)*irq);
|
|
if (irqVal > 0 && irqVal <= 7) {
|
|
*irq = (uint8_t)irqVal;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// readSystemIni - Read an unsigned integer value from SYSTEM.INI
|
|
// -----------------------------------------------------------------------
|
|
static uint16_t readSystemIni(const char FAR *section, const char FAR *key, uint16_t defVal)
|
|
{
|
|
return (uint16_t)GetPrivateProfileInt(section, key, (int)defVal, "SYSTEM.INI");
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// reccom - Read from RX ring buffer (ordinal 4)
|
|
//
|
|
// Copies up to len bytes from the receive ring buffer into buf.
|
|
// Returns number of bytes read, or negative error code.
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export reccom(int16_t commId, void FAR *buf, int16_t len)
|
|
{
|
|
PortStateT *port;
|
|
uint8_t FAR *dst;
|
|
int16_t bytesRead;
|
|
|
|
dbgInt16("KPCOMM: reccom Id", commId);
|
|
|
|
if (commId < 0 || commId >= MAX_PORTS) {
|
|
return -1;
|
|
}
|
|
|
|
port = &ports[commId];
|
|
if (!port->isOpen) {
|
|
return -1;
|
|
}
|
|
|
|
dst = (uint8_t FAR *)buf;
|
|
bytesRead = 0;
|
|
|
|
_disable();
|
|
while (bytesRead < len && port->rxCount > 0) {
|
|
*dst++ = port->rxBuf[port->rxTail];
|
|
port->rxTail++;
|
|
if (port->rxTail >= port->rxSize) {
|
|
port->rxTail = 0;
|
|
}
|
|
port->rxCount--;
|
|
bytesRead++;
|
|
}
|
|
_enable();
|
|
|
|
// If flow control was asserted and buffer has drained, deassert
|
|
if (port->rxStopped && port->rxCount <= port->xonLim) {
|
|
_disable();
|
|
port->rxStopped = FALSE;
|
|
|
|
if (port->hsMode == HS_XONXOFF || port->hsMode == HS_BOTH) {
|
|
port->txImmediate = port->xonChar;
|
|
_outp(port->baseAddr + UART_IER, (uint8_t)(_inp(port->baseAddr + UART_IER) | IER_THRE));
|
|
}
|
|
if (port->hsMode == HS_RTSCTS || port->hsMode == HS_BOTH) {
|
|
_outp(port->baseAddr + UART_MCR, (uint8_t)(_inp(port->baseAddr + UART_MCR) | MCR_RTS));
|
|
}
|
|
_enable();
|
|
}
|
|
|
|
// Sync ComDEB queue counts
|
|
port->comDeb.qInCount = port->rxCount;
|
|
port->comDeb.qInTail = port->rxTail;
|
|
|
|
return bytesRead;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// setcom - Apply DCB settings to hardware (ordinal 2)
|
|
//
|
|
// Takes DCB FAR * (4 bytes, RETF 4). CommId is in dcb->Id.
|
|
// Both USER.EXE and COMMTASK.DLL push 4 bytes for this call.
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export setcom(DCB FAR *dcb)
|
|
{
|
|
int16_t commId;
|
|
PortStateT *port;
|
|
|
|
dbgStr("KPCOMM: setcom enter\r\n");
|
|
|
|
if (!dcb) {
|
|
dbgStr("KPCOMM: setcom NULL dcb\r\n");
|
|
return -1;
|
|
}
|
|
|
|
commId = dcb->Id;
|
|
dbgInt16("KPCOMM: setcom Id", commId);
|
|
dbgHex16("KPCOMM: setcom BaudRate", dcb->BaudRate);
|
|
dbgHex16("KPCOMM: setcom ByteSize", (uint16_t)dcb->ByteSize);
|
|
dbgHex16("KPCOMM: setcom Parity", (uint16_t)dcb->Parity);
|
|
dbgHex16("KPCOMM: setcom StopBits", (uint16_t)dcb->StopBits);
|
|
|
|
if (commId < 0 || commId >= MAX_PORTS) {
|
|
dbgStr("KPCOMM: setcom IE_BADID\r\n");
|
|
return IE_BADID;
|
|
}
|
|
|
|
port = &ports[commId];
|
|
if (!port->isOpen) {
|
|
dbgStr("KPCOMM: setcom port not open\r\n");
|
|
return -1;
|
|
}
|
|
|
|
// Save DCB
|
|
_fmemcpy(&port->dcb, dcb, sizeof(DCB));
|
|
|
|
// Apply hardware settings
|
|
_disable();
|
|
|
|
applyBaudRate(port, dcb->BaudRate);
|
|
applyLineParams(port, dcb->ByteSize, dcb->Parity, dcb->StopBits);
|
|
|
|
// Update flow control
|
|
if (dcb->fOutxCtsFlow && dcb->fInX) {
|
|
port->hsMode = HS_BOTH;
|
|
} else if (dcb->fOutxCtsFlow) {
|
|
port->hsMode = HS_RTSCTS;
|
|
} else if (dcb->fInX) {
|
|
port->hsMode = HS_XONXOFF;
|
|
} else {
|
|
port->hsMode = HS_NONE;
|
|
}
|
|
|
|
port->xonChar = dcb->XonChar;
|
|
port->xoffChar = dcb->XoffChar;
|
|
port->xonLim = dcb->XonLim;
|
|
port->xoffLim = dcb->XoffLim;
|
|
|
|
// Sync ComDEB shadow fields
|
|
port->comDeb.baudRate = port->baudRate;
|
|
port->comDeb.lcrShadow = (uint8_t)_inp(port->baseAddr + UART_LCR);
|
|
port->comDeb.mcrShadow = (uint8_t)_inp(port->baseAddr + UART_MCR);
|
|
|
|
_enable();
|
|
|
|
dbgStr("KPCOMM: setcom OK\r\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// setque - Resize RX/TX buffers (ordinal 3)
|
|
//
|
|
// Caller passes port ID and requested RX/TX buffer sizes.
|
|
// We manage our own GlobalAlloc'd buffers.
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export setque(int16_t commId, int16_t rxSz, int16_t txSz)
|
|
{
|
|
PortStateT *port;
|
|
|
|
dbgInt16("KPCOMM: setque Id", commId);
|
|
dbgInt16("KPCOMM: setque rxSz", rxSz);
|
|
dbgInt16("KPCOMM: setque txSz", txSz);
|
|
|
|
if (commId < 0 || commId >= MAX_PORTS) {
|
|
dbgStr("KPCOMM: setque bad id\r\n");
|
|
return -1;
|
|
}
|
|
|
|
port = &ports[commId];
|
|
if (!port->isOpen) {
|
|
dbgStr("KPCOMM: setque port not open\r\n");
|
|
return -1;
|
|
}
|
|
|
|
_disable();
|
|
freeBuffers(port);
|
|
if (initBuffers(port, (uint16_t)rxSz, (uint16_t)txSz) != 0) {
|
|
_enable();
|
|
dbgStr("KPCOMM: setque IE_MEMORY\r\n");
|
|
return IE_MEMORY;
|
|
}
|
|
// Sync ComDEB queue sizes
|
|
port->comDeb.qInSize = port->rxSize;
|
|
port->comDeb.qInCount = 0;
|
|
port->comDeb.qInHead = 0;
|
|
port->comDeb.qInTail = 0;
|
|
port->comDeb.qOutSize = port->txSize;
|
|
port->comDeb.qOutCount = 0;
|
|
port->comDeb.qOutHead = 0;
|
|
port->comDeb.qOutTail = 0;
|
|
_enable();
|
|
|
|
dbgStr("KPCOMM: setque OK\r\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// sndcom - Write to TX ring buffer and prime transmitter (ordinal 5)
|
|
//
|
|
// Copies up to len bytes from buf into the transmit ring buffer.
|
|
// Returns number of bytes written, or negative error code.
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export sndcom(int16_t commId, void FAR *buf, int16_t len)
|
|
{
|
|
PortStateT *port;
|
|
uint8_t FAR *src;
|
|
int16_t bytesWritten;
|
|
|
|
dbgInt16("KPCOMM: sndcom Id", commId);
|
|
|
|
if (commId < 0 || commId >= MAX_PORTS) {
|
|
return -1;
|
|
}
|
|
|
|
port = &ports[commId];
|
|
if (!port->isOpen) {
|
|
return -1;
|
|
}
|
|
|
|
src = (uint8_t FAR *)buf;
|
|
bytesWritten = 0;
|
|
|
|
_disable();
|
|
while (bytesWritten < len) {
|
|
if (port->txCount >= port->txSize) {
|
|
port->errorFlags |= CE_TXFULL;
|
|
break;
|
|
}
|
|
|
|
port->txBuf[port->txHead] = *src++;
|
|
port->txHead++;
|
|
if (port->txHead >= port->txSize) {
|
|
port->txHead = 0;
|
|
}
|
|
port->txCount++;
|
|
bytesWritten++;
|
|
}
|
|
_enable();
|
|
|
|
// Sync ComDEB queue counts
|
|
port->comDeb.qOutCount = port->txCount;
|
|
port->comDeb.qOutHead = port->txHead;
|
|
|
|
// Prime transmitter if we wrote anything
|
|
if (bytesWritten > 0 && !port->txStopped) {
|
|
primeTx(port);
|
|
}
|
|
|
|
return bytesWritten;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// stacom - Return and clear error flags plus COMSTAT (ordinal 8)
|
|
//
|
|
// Returns accumulated error flags (CE_*) and fills the COMSTAT
|
|
// structure with current buffer counts and status.
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export stacom(int16_t commId, COMSTAT FAR *stat)
|
|
{
|
|
PortStateT *port;
|
|
int16_t errors;
|
|
|
|
dbgInt16("KPCOMM: stacom Id", commId);
|
|
|
|
if (commId < 0 || commId >= MAX_PORTS) {
|
|
dbgStr("KPCOMM: stacom bad id\r\n");
|
|
return -1;
|
|
}
|
|
|
|
port = &ports[commId];
|
|
if (!port->isOpen) {
|
|
dbgStr("KPCOMM: stacom not open\r\n");
|
|
return -1;
|
|
}
|
|
|
|
_disable();
|
|
errors = (int16_t)port->errorFlags;
|
|
port->errorFlags = 0;
|
|
_enable();
|
|
|
|
if (stat) {
|
|
stat->status = 0;
|
|
if (port->txStopped) {
|
|
stat->status |= CSTF_XOFFHOLD;
|
|
}
|
|
stat->cbInQue = port->rxCount;
|
|
stat->cbOutQue = port->txCount;
|
|
}
|
|
|
|
dbgHex16("KPCOMM: stacom errors", (uint16_t)errors);
|
|
return errors;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// suspendOpenCommPorts - Suspend all ports during task switch (ordinal 17)
|
|
//
|
|
// Called by Windows when switching away from this VM.
|
|
// Disables interrupts on all open ports to prevent ISR problems.
|
|
// -----------------------------------------------------------------------
|
|
void FAR PASCAL _export suspendOpenCommPorts(void)
|
|
{
|
|
int16_t i;
|
|
PortStateT *port;
|
|
|
|
dbgStr("KPCOMM: suspend\r\n");
|
|
|
|
for (i = 0; i < MAX_PORTS; i++) {
|
|
port = &ports[i];
|
|
if (!port->isOpen) {
|
|
continue;
|
|
}
|
|
|
|
// Disable UART interrupts
|
|
_outp(port->baseAddr + UART_IER, 0);
|
|
|
|
// Mask IRQ at PIC
|
|
{
|
|
uint8_t picMask = (uint8_t)_inp(PIC_DATA);
|
|
picMask |= port->irqMask;
|
|
_outp(PIC_DATA, picMask);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// trmcom - Close a COM port (ordinal 7)
|
|
//
|
|
// Disables interrupts, drops DTR/RTS, unhooks ISR, frees buffers.
|
|
// Returns 0 on success.
|
|
// -----------------------------------------------------------------------
|
|
int16_t FAR PASCAL _export trmcom(int16_t commId)
|
|
{
|
|
PortStateT *port;
|
|
|
|
dbgInt16("KPCOMM: trmcom Id", commId);
|
|
|
|
if (commId < 0 || commId >= MAX_PORTS) {
|
|
return -1;
|
|
}
|
|
|
|
port = &ports[commId];
|
|
if (!port->isOpen) {
|
|
dbgStr("KPCOMM: trmcom port not open\r\n");
|
|
return -1;
|
|
}
|
|
|
|
// Disable UART interrupts
|
|
_outp(port->baseAddr + UART_IER, 0);
|
|
|
|
// Disable FIFOs
|
|
if (port->is16550) {
|
|
_outp(port->baseAddr + UART_FCR, 0);
|
|
}
|
|
|
|
// Drop DTR, RTS, OUT2
|
|
_outp(port->baseAddr + UART_MCR, 0);
|
|
|
|
// Unhook ISR
|
|
unhookIsr(port);
|
|
|
|
// Free buffers
|
|
freeBuffers(port);
|
|
|
|
// Clear notification
|
|
port->hwndNotify = NULL;
|
|
|
|
port->isOpen = FALSE;
|
|
|
|
dbgHex16("KPCOMM: trmcom isrHits", isrHitCount);
|
|
dbgStr("KPCOMM: trmcom OK\r\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// LibMain - DLL entry point
|
|
// -----------------------------------------------------------------------
|
|
int FAR PASCAL LibMain(HANDLE hInstance, WORD wDataSeg, WORD wHeapSize, LPSTR lpCmdLine)
|
|
{
|
|
int16_t i;
|
|
|
|
(void)wDataSeg;
|
|
(void)lpCmdLine;
|
|
|
|
ghInstance = hInstance;
|
|
|
|
dbgStr("KPCOMM: LibMain\r\n");
|
|
|
|
if (wHeapSize > 0) {
|
|
UnlockData(0);
|
|
}
|
|
|
|
// Zero all port states
|
|
for (i = 0; i < MAX_PORTS; i++) {
|
|
_fmemset(&ports[i], 0, sizeof(PortStateT));
|
|
ports[i].txImmediate = -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// WEP - DLL exit procedure
|
|
// -----------------------------------------------------------------------
|
|
int FAR PASCAL _export WEP(int nParam)
|
|
{
|
|
int16_t i;
|
|
|
|
(void)nParam;
|
|
|
|
// Close any ports still open
|
|
for (i = 0; i < MAX_PORTS; i++) {
|
|
if (ports[i].isOpen) {
|
|
trmcom(i);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|