emu48plus-mirror/source/serial.c
Gwenhael Le Moine bdab3ead6a
fd -e c -e h -e C -e H -x sed -i 's|^M||g'
Signed-off-by: Gwenhael Le Moine <gwenhael.le.moine@gmail.com>
2024-04-01 17:27:54 +02:00

407 lines
10 KiB
C

/*
* Serial.c
*
* This file is part of Emu48
*
* Copyright (C) 1998 Christoph Gießelink
*
*/
#include "pch.h"
#include "emu48.h"
#include "io.h"
#define INTERRUPT ((void)(Chipset.SoftInt=TRUE,bInterrupt=TRUE))
// state of USRQ
#define NINT2ERBZ ((Chipset.IORam[IOC] & (SON | ERBZ)) == (SON | ERBZ) && (Chipset.IORam[RCS] & RBZ) != 0)
#define NINT2ERBF ((Chipset.IORam[IOC] & (SON | ERBF)) == (SON | ERBF) && (Chipset.IORam[RCS] & RBF) != 0)
#define NINT2ETBE ((Chipset.IORam[IOC] & (SON | ETBE)) == (SON | ETBE) && (Chipset.IORam[TCS] & TBF) == 0)
#define NINT2USRQ (NINT2ERBZ || NINT2ERBF || NINT2ETBE)
static HANDLE hComm = NULL;
static HANDLE hCThreadTxd;
static HANDLE hCThreadEv;
static HANDLE hEventTxd;
static BOOL bWriting;
static BYTE tbr;
static BOOL bReading;
static BYTE cBuffer[32];
static WORD nRp;
static DWORD dwBytesRead;
static DWORD WINAPI TransmitThread(LPVOID pParam)
{
OVERLAPPED osWr = { 0 };
osWr.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
while (bWriting)
{
WaitForSingleObject(hEventTxd,INFINITE);
if (bWriting)
{
DWORD dwWritten;
if (!WriteFile(hComm,(LPCVOID) &tbr,1,&dwWritten,&osWr))
if (GetLastError() == ERROR_IO_PENDING)
GetOverlappedResult(hComm,&osWr,&dwWritten,TRUE);
}
}
CloseHandle(osWr.hEvent); // close write event handle
return 0;
UNREFERENCED_PARAMETER(pParam);
}
static DWORD WINAPI EventThread(LPVOID pParam)
{
DWORD dwEvent;
bReading = TRUE; // flag for SerialThread started
while (bReading)
{
_ASSERT(hComm != NULL);
WaitCommEvent(hComm,&dwEvent,NULL); // wait for serial event
if (dwEvent & EV_RXCHAR) // signal char received
{
CommReceive(); // get data
// interrupt request and emulation thread down
if (Chipset.SoftInt && Chipset.Shutdn)
{
Chipset.bShutdnWake = TRUE; // wake up from SHUTDN mode
SetEvent(hEventShutdn); // wake up emulation thread
}
}
if (dwEvent & EV_TXEMPTY) // signal transmit buffer empty
{
IOBit(TCS,TBZ,FALSE); // clear transmitter busy bit
CommTransmit(); // check for new char to transmit
}
if (dwEvent & EV_ERR) // signal error received
{
DWORD dwError;
ClearCommError(hComm,&dwError,NULL);
if (dwError & (CE_FRAME | CE_OVERRUN | CE_BREAK))
IOBit(RCS,RER,TRUE); // receiver error
}
}
return 0;
UNREFERENCED_PARAMETER(pParam);
}
BOOL CommIsOpen(VOID)
{
return hComm != NULL; // have COM port handle
}
BOOL CommOpen(LPTSTR strWirePort,LPTSTR strIrPort)
{
COMMTIMEOUTS CommTimeouts = { MAXDWORD, 0L, 0L, 0L, 0L };
LPCTSTR strPort = (Chipset.IORam[IR_CTRL] & EIRU) ? strIrPort : strWirePort;
_ASSERT(Chipset.IORam[IOC] & SON); // UART on
CommClose(); // close port if already open
dwBytesRead = 0L; // no bytes received
if (lstrcmp(strPort, _T(NO_SERIAL))) // port defined
{
TCHAR szDevice[256] = _T("\\\\.\\");
// check if device buffer is big enough
_ASSERT(lstrlen(szDevice) + lstrlen(strPort) < (INT) ARRAYSIZEOF(szDevice));
if (lstrlen(szDevice) + lstrlen(strPort) >= (INT) ARRAYSIZEOF(szDevice))
return hComm != NULL;
_tcscat(szDevice,strPort); // device name
hComm = CreateFile(szDevice,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (hComm != INVALID_HANDLE_VALUE)
{
DWORD dwThreadId;
nRp = 0; // reset receiver state
SetCommTimeouts(hComm,&CommTimeouts);
CommSetBaud();
CommTxBRK(); // update BRK condition
// event to transmit character
hEventTxd = CreateEvent(NULL,FALSE,FALSE,NULL);
// create char transmit handler
bWriting = TRUE;
hCThreadTxd = CreateThread(NULL,0,&TransmitThread,NULL,CREATE_SUSPENDED,&dwThreadId);
_ASSERT(hCThreadTxd);
SetThreadPriority(hCThreadTxd,THREAD_PRIORITY_ABOVE_NORMAL);
ResumeThread(hCThreadTxd); // start thread
// create Comm event handler
bReading = FALSE;
SetCommMask(hComm,EV_RXCHAR | EV_TXEMPTY | EV_ERR); // event on RX, TX, error
hCThreadEv = CreateThread(NULL,0,&EventThread,NULL,CREATE_SUSPENDED,&dwThreadId);
_ASSERT(hCThreadEv);
SetThreadPriority(hCThreadEv,THREAD_PRIORITY_ABOVE_NORMAL);
ResumeThread(hCThreadEv); // start thread
while (!bReading) Sleep(0); // wait for SerialThread started
}
else
{
hComm = NULL;
}
}
#if defined DEBUG_SERIAL
{
TCHAR buffer[256];
wsprintf(buffer,_T("COM port %s.\n"),hComm ? _T("opened"): _T("open error"));
OutputDebugString(buffer);
}
#endif
return hComm != NULL;
}
VOID CommClose(VOID)
{
if (hComm != NULL) // port open
{
// workaround to fix problems with some Kermit server programs
// reason: on one hand we have the character transmitting time base on the
// selected baudrate, on the other hand the time between sending the last
// character and closing the port. The last time is much longer on the real
// calculator than on the emulator running at full speed, therefore the
// slow down time on the emulator
Sleep(25); // slow down time
bReading = FALSE; // kill event thread
SetCommMask(hComm,0L); // clear all events and force WaitCommEvent to return
WaitForSingleObject(hCThreadEv,INFINITE);
CloseHandle(hCThreadEv);
bWriting = FALSE; // kill write thread
SetEvent(hEventTxd); // continue write thread
WaitForSingleObject(hCThreadTxd,INFINITE);
CloseHandle(hCThreadTxd);
CloseHandle(hEventTxd); // close Txd event
CloseHandle(hComm); // close port
hComm = NULL;
#if defined DEBUG_SERIAL
OutputDebugString(_T("COM port closed.\n"));
#endif
}
return;
}
VOID CommSetBaud(VOID)
{
if (hComm != NULL)
{
const DWORD dwBaudrates[] = { 1200, 1920, 2400, 3840, 4800, 7680, 9600, 15360,
14400, 19200, 38400, 57600, 115200, 115200, 115200, 115200 };
DCB dcb;
UINT uBaudIndex;
uBaudIndex = isModelApple(cCurrentRomType)
? Chipset.IORam[BAUD]
: Chipset.IORam[BAUD] & 0x7;
ZeroMemory(&dcb,sizeof(dcb));
dcb.DCBlength = sizeof(dcb);
dcb.BaudRate = dwBaudrates[uBaudIndex];
dcb.fBinary = TRUE;
dcb.fParity = TRUE;
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fDtrControl = DTR_CONTROL_DISABLE;
dcb.fDsrSensitivity = FALSE;
dcb.fOutX = FALSE;
dcb.fErrorChar = FALSE;
dcb.fNull = FALSE;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fAbortOnError = FALSE; // may changed in further implementations
dcb.ByteSize = 8;
dcb.Parity = NOPARITY; // no parity check, emulated by software
dcb.StopBits = TWOSTOPBITS;
#if defined DEBUG_SERIAL
{
TCHAR buffer[256];
wsprintf(buffer,_T("CommsetBaud: %ld\n"),dcb.BaudRate);
OutputDebugString(buffer);
}
#endif
SetCommState(hComm,&dcb);
}
return;
}
BOOL UpdateUSRQ(VOID) // USRQ handling
{
BOOL bUSRQ = NINT2USRQ;
IOBit(SRQ1,USRQ,bUSRQ); // update USRQ bit
return bUSRQ;
}
VOID CommTxBRK(VOID)
{
if (Chipset.IORam[TCS] & BRK) // BRK condition
{
if (hComm != NULL) // com port open
{
// abort data transfer
PurgeComm(hComm,PURGE_TXABORT | PURGE_TXCLEAR);
SetCommBreak(hComm); // set into BRK state
}
// TBF and TBZ bits of TCS are undefined
if (Chipset.IORam[TCS] & LPB) // is loopback bit set
{
dwBytesRead = nRp = 0; // clear receive buffer
cBuffer[dwBytesRead++] = 0; // save character in receive buffer
CommReceive(); // receive available byte
IOBit(RCS,RER,TRUE); // receiver error (no stop bit)
}
}
else
{
if (hComm != NULL) // com port open
{
ClearCommBreak(hComm); // clear BRK state
}
}
return;
}
VOID CommTransmit(VOID)
{
BOOL bTxChar = FALSE;
EnterCriticalSection(&csTxdLock);
if ( (Chipset.IORam[TCS] & TBZ) == 0 // transmitter not busy
&& (Chipset.IORam[TCS] & TBF) != 0) // transmit buffer full
{
tbr = (Chipset.IORam[TBR_MSB] << 4) | Chipset.IORam[TBR_LSB];
IOBit(TCS,TBF,FALSE); // clear transmit buffer full bit
IOBit(TCS,TBZ,TRUE); // set transmitter busy bit
bTxChar = TRUE;
}
LeaveCriticalSection(&csTxdLock);
if (bTxChar) // character to transmit
{
#if defined DEBUG_SERIAL
{
TCHAR buffer[256];
if (isprint(tbr))
wsprintf(buffer,_T("-> '%c'\n"),tbr);
else
wsprintf(buffer,_T("-> %02X\n"),tbr);
OutputDebugString(buffer);
}
#endif
if (Chipset.IORam[TCS] & LPB) // is loopback bit set
{
if (dwBytesRead == 0) nRp = 0; // no character received, reset read pointer
cBuffer[nRp+dwBytesRead] = tbr; // save character in receive buffer
++dwBytesRead;
CommReceive(); // receive available byte
}
if (hComm != NULL) // com port open
{
SetEvent(hEventTxd); // write TBR byte
}
else
{
IOBit(TCS,TBZ,FALSE); // clear transmitter busy bit
}
}
if (UpdateUSRQ()) // update USRQ bit
INTERRUPT;
return;
}
VOID CommReceive(VOID)
{
OVERLAPPED os = { 0 };
if (!(Chipset.IORam[IOC] & SON)) // UART off
{
dwBytesRead = 0L; // no bytes received
return;
}
EnterCriticalSection(&csRecvLock);
do
{
if (Chipset.IORam[RCS] & RBF) // receive buffer full
break;
// reject reading if com port is closed and not whole operation
if (hComm) // com port open
{
UINT uCnt = 0; // retry counter
while (dwBytesRead == 0L) // buffer empty
{
if (ReadFile(hComm,cBuffer,sizeof(cBuffer),&dwBytesRead,&os) == FALSE)
dwBytesRead = 0L;
else // bytes received
nRp = 0; // reset read pointer
// something received or passed 1st retry
if (dwBytesRead != 0L || ++uCnt > 1)
break; // quit
Sleep(1); // workaround, retry 1ms later
}
}
if (dwBytesRead == 0L) // receive buffer empty
break;
#if defined DEBUG_SERIAL
{
TCHAR buffer[256];
if (isprint(cBuffer[nRp]))
wsprintf(buffer,_T("<- '%c'\n"),cBuffer[nRp]);
else
wsprintf(buffer,_T("<- %02X\n"),cBuffer[nRp]);
OutputDebugString(buffer);
}
#endif
Chipset.IORam[RBR_MSB] = (cBuffer[nRp] >> 4);
Chipset.IORam[RBR_LSB] = (cBuffer[nRp] & 0x0f);
++nRp;
--dwBytesRead;
Chipset.IORam[RCS] |= RBF; // receive buffer full
if (UpdateUSRQ()) // update USRQ bit
INTERRUPT;
}
while (FALSE);
LeaveCriticalSection(&csRecvLock);
return;
}