emu48-mirror/Sources/Emu48/SERIAL.C

383 lines
9.9 KiB
C
Raw Normal View History

2024-03-19 22:24:30 +01:00
/*
* Serial.c
*
* This file is part of Emu48
*
* Copyright (C) 1998 Christoph Gie<EFBFBD>elink
*
*/
#include "pch.h"
#include "Emu48.h"
2024-03-19 22:36:03 +01:00
#include "io.h"
2024-03-19 22:24:30 +01:00
#define INTERRUPT ((void)(Chipset.SoftInt=TRUE,bInterrupt=TRUE))
2024-03-19 22:36:03 +01:00
// state of USRQ
2024-03-19 22:37:03 +01:00
#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)
2024-03-19 22:25:45 +01:00
#define NINT2USRQ (NINT2ERBZ || NINT2ERBF || NINT2ETBE)
2024-03-19 22:24:30 +01:00
static HANDLE hComm = NULL;
2024-03-19 22:37:54 +01:00
static HANDLE hCThreadTxd;
static HANDLE hCThreadEv;
static HANDLE hEventTxd;
static BOOL bWriting;
static BYTE tbr;
2024-03-19 22:36:03 +01:00
static BOOL bReading;
2024-03-19 22:37:54 +01:00
static BYTE cBuffer[32];
2024-03-19 22:25:45 +01:00
static WORD nRp;
2024-03-19 22:37:03 +01:00
static DWORD dwBytesRead;
2024-03-19 22:25:45 +01:00
2024-03-19 22:37:54 +01:00
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)
2024-03-19 22:24:30 +01:00
{
2024-03-19 22:37:03 +01:00
DWORD dwEvent;
2024-03-19 22:36:03 +01:00
bReading = TRUE; // flag for SerialThread started
2024-03-19 22:24:30 +01:00
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)
2024-03-19 22:25:45 +01:00
{
Chipset.bShutdnWake = TRUE; // wake up from SHUTDN mode
SetEvent(hEventShutdn); // wake up emulation thread
}
2024-03-19 22:24:30 +01:00
}
2024-03-19 22:37:54 +01:00
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
}
2024-03-19 22:24:30 +01:00
}
return 0;
UNREFERENCED_PARAMETER(pParam);
}
2024-03-19 22:37:03 +01:00
BOOL CommOpen(LPTSTR strWirePort,LPTSTR strIrPort)
2024-03-19 22:24:30 +01:00
{
COMMTIMEOUTS CommTimeouts = { MAXDWORD, 0L, 0L, 0L, 0L };
LPCTSTR strPort = (Chipset.IORam[IR_CTRL] & EIRU) ? strIrPort : strWirePort;
2024-03-19 22:24:30 +01:00
2024-03-19 22:25:45 +01:00
_ASSERT(Chipset.IORam[IOC] & SON); // UART on
CommClose(); // close port if already open
2024-03-19 22:24:30 +01:00
2024-03-19 22:37:03 +01:00
if (lstrcmp(strPort, _T(NO_SERIAL))) // port defined
2024-03-19 22:24:30 +01:00
{
TCHAR szDevice[256] = _T("\\\\.\\");
2024-03-19 22:37:54 +01:00
// check if device buffer is big enough
_ASSERT(lstrlen(szDevice) + lstrlen(strPort) < (INT) ARRAYSIZEOF(szDevice));
if (lstrlen(szDevice) + lstrlen(strPort) >= (INT) ARRAYSIZEOF(szDevice))
2024-03-19 22:37:54 +01:00
return hComm != NULL;
_tcscat(szDevice,strPort); // device name
hComm = CreateFile(szDevice,
GENERIC_READ | GENERIC_WRITE,
2024-03-19 22:24:30 +01:00
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
2024-03-19 22:24:30 +01:00
NULL);
if (hComm != INVALID_HANDLE_VALUE)
2024-03-19 22:24:30 +01:00
{
2024-03-19 22:37:54 +01:00
DWORD dwThreadId;
2024-03-19 22:37:03 +01:00
nRp = 0; // reset receiver state
dwBytesRead = 0L;
2024-03-19 22:24:30 +01:00
SetCommTimeouts(hComm,&CommTimeouts);
CommSetBaud();
2024-03-19 22:37:54 +01:00
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
2024-03-19 22:37:54 +01:00
// create Comm event handler
2024-03-19 22:36:03 +01:00
bReading = FALSE;
2024-03-19 22:37:54 +01:00
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
2024-03-19 22:36:03 +01:00
while (!bReading) Sleep(0); // wait for SerialThread started
2024-03-19 22:24:30 +01:00
}
else
hComm = NULL;
}
#if defined DEBUG_SERIAL
{
2024-03-19 22:37:03 +01:00
TCHAR buffer[256];
wsprintf(buffer,_T("COM port %s.\n"),hComm ? _T("opened"): _T("open error"));
2024-03-19 22:24:30 +01:00
OutputDebugString(buffer);
}
#endif
2024-03-19 22:37:03 +01:00
return hComm != NULL;
2024-03-19 22:24:30 +01:00
}
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
2024-03-19 22:37:54 +01:00
bReading = FALSE; // kill event thread
2024-03-19 22:24:30 +01:00
SetCommMask(hComm,0L); // clear all events and force WaitCommEvent to return
2024-03-19 22:37:54 +01:00
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
2024-03-19 22:24:30 +01:00
CloseHandle(hComm); // close port
hComm = NULL;
#if defined DEBUG_SERIAL
2024-03-19 22:37:03 +01:00
OutputDebugString(_T("COM port closed.\n"));
2024-03-19 22:24:30 +01:00
#endif
}
2024-03-19 22:25:45 +01:00
return;
2024-03-19 22:24:30 +01:00
}
VOID CommSetBaud(VOID)
{
if (hComm != NULL)
{
const DWORD dwBaudrates[] = { 1200, 1920, 2400, 3840, 4800, 7680, 9600, 15360 };
DCB dcb;
2024-03-19 22:37:54 +01:00
ZeroMemory(&dcb,sizeof(dcb));
dcb.DCBlength = sizeof(dcb);
2024-03-19 22:24:30 +01:00
dcb.BaudRate = dwBaudrates[Chipset.IORam[BAUD] & 0x7];
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
2024-03-19 22:38:33 +01:00
dcb.StopBits = TWOSTOPBITS;
2024-03-19 22:24:30 +01:00
#if defined DEBUG_SERIAL
{
2024-03-19 22:37:03 +01:00
TCHAR buffer[256];
wsprintf(buffer,_T("CommsetBaud: %ld\n"),dcb.BaudRate);
2024-03-19 22:24:30 +01:00
OutputDebugString(buffer);
}
#endif
SetCommState(hComm,&dcb);
}
2024-03-19 22:25:45 +01:00
return;
}
2024-03-19 22:37:03 +01:00
BOOL UpdateUSRQ(VOID) // USRQ handling
2024-03-19 22:25:45 +01:00
{
2024-03-19 22:37:03 +01:00
BOOL bUSRQ = NINT2USRQ;
IOBit(SRQ1,USRQ,bUSRQ); // update USRQ bit
return bUSRQ;
2024-03-19 22:24:30 +01:00
}
2024-03-19 22:37:54 +01:00
VOID CommTxBRK(VOID)
2024-03-19 22:24:30 +01:00
{
2024-03-19 22:37:54 +01:00
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
}
2024-03-19 22:24:30 +01:00
2024-03-19 22:37:54 +01:00
// 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
2024-03-19 22:24:30 +01:00
{
2024-03-19 22:37:54 +01:00
if (hComm != NULL) // com port open
{
ClearCommBreak(hComm); // clear BRK state
}
2024-03-19 22:24:30 +01:00
}
2024-03-19 22:37:54 +01:00
return;
}
2024-03-19 22:24:30 +01:00
2024-03-19 22:37:54 +01:00
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
2024-03-19 22:25:45 +01:00
{
2024-03-19 22:37:54 +01:00
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
2024-03-19 22:25:45 +01:00
2024-03-19 22:37:54 +01:00
bTxChar = TRUE;
2024-03-19 22:25:45 +01:00
}
2024-03-19 22:37:54 +01:00
LeaveCriticalSection(&csTxdLock);
2024-03-19 22:25:45 +01:00
2024-03-19 22:37:54 +01:00
if (bTxChar) // character to transmit
2024-03-19 22:36:03 +01:00
{
2024-03-19 22:37:54 +01:00
#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
}
2024-03-19 22:24:30 +01:00
2024-03-19 22:37:54 +01:00
if (hComm != NULL) // com port open
{
SetEvent(hEventTxd); // write TBR byte
}
else
{
IOBit(TCS,TBZ,FALSE); // clear transmitter busy bit
}
}
2024-03-19 22:37:03 +01:00
if (UpdateUSRQ()) // update USRQ bit
2024-03-19 22:24:30 +01:00
INTERRUPT;
2024-03-19 22:25:45 +01:00
return;
2024-03-19 22:24:30 +01:00
}
VOID CommReceive(VOID)
{
2024-03-19 22:36:03 +01:00
OVERLAPPED os = { 0 };
2024-03-19 22:25:45 +01:00
if (!(Chipset.IORam[IOC] & SON)) // UART off
2024-03-19 22:24:30 +01:00
{
dwBytesRead = 0L; // no bytes received
return;
}
2024-03-19 22:25:45 +01:00
EnterCriticalSection(&csRecvLock);
2024-03-19 22:24:30 +01:00
do
{
if (Chipset.IORam[RCS] & RBF) // receive buffer full
break;
2024-03-19 22:36:03 +01:00
// reject reading if com port is closed and not whole operation
2024-03-19 22:25:45 +01:00
if (hComm && dwBytesRead == 0L) // com port open and buffer empty
2024-03-19 22:24:30 +01:00
{
if (ReadFile(hComm,&cBuffer,sizeof(cBuffer),&dwBytesRead,&os) == FALSE)
2024-03-19 22:24:30 +01:00
dwBytesRead = 0L;
else // bytes received
nRp = 0; // reset read pointer
}
if (dwBytesRead == 0L) // receive buffer empty
2024-03-19 22:24:30 +01:00
break;
#if defined DEBUG_SERIAL
{
2024-03-19 22:37:03 +01:00
TCHAR buffer[256];
2024-03-19 22:24:30 +01:00
if (isprint(cBuffer[nRp]))
2024-03-19 22:37:03 +01:00
wsprintf(buffer,_T("<- '%c'\n"),cBuffer[nRp]);
2024-03-19 22:24:30 +01:00
else
2024-03-19 22:37:03 +01:00
wsprintf(buffer,_T("<- %02X\n"),cBuffer[nRp]);
2024-03-19 22:24:30 +01:00
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
2024-03-19 22:24:30 +01:00
INTERRUPT;
}
while (FALSE);
2024-03-19 22:25:45 +01:00
LeaveCriticalSection(&csRecvLock);
return;
2024-03-19 22:24:30 +01:00
}