mirror of
https://github.com/AppleWin/AppleWin.git
synced 2024-12-28 02:32:08 +00:00
4ab60cebf8
* SerialComms: compilation in linux. 1) static variables must be defined (otherwise it becomes optimisation-dependent) 2) ensure the socket is closed if an error is detected * SerialComms: avoid issues with order of global variables. GetFrame() and the GetCardMgr() use global variables and the order of their destructor is not well defined. CSuperSerialCard::CommTcpSerialCleanup() is called as part of the CardManager's destructor.
1509 lines
45 KiB
C++
1509 lines
45 KiB
C++
/*
|
|
AppleWin : An Apple //e emulator for Windows
|
|
|
|
Copyright (C) 1994-1996, Michael O'Brien
|
|
Copyright (C) 1999-2001, Oliver Schmidt
|
|
Copyright (C) 2002-2005, Tom Charlesworth
|
|
Copyright (C) 2006-2009, Tom Charlesworth, Michael Pohoreski, Nick Westgate
|
|
|
|
AppleWin is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
AppleWin is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with AppleWin; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
/* Description: Super Serial Card emulation
|
|
*
|
|
* Author: Various
|
|
*/
|
|
|
|
// Refs:
|
|
// [Ref.1] AppleWin\docs\SSC Memory Locations for Programmers.txt
|
|
// [Ref.2] SSC recv IRQ example: https://sites.google.com/site/drjohnbmatthews/apple2/ssc - John B. Matthews, 5/13/87
|
|
// [Ref.3] SY6551 info: http://users.axess.com/twilight/sock/rs232pak.html
|
|
//
|
|
// SSC-pg is an abbreviation for pages references to "Super Serial Card, Installation and Operating Manual" by Apple
|
|
|
|
#include "StdAfx.h"
|
|
|
|
#include "SerialComms.h"
|
|
#include "CPU.h"
|
|
#include "Interface.h"
|
|
#include "Log.h"
|
|
#include "Memory.h"
|
|
#include "Registry.h"
|
|
#include "YamlHelper.h"
|
|
|
|
#include "../resource/resource.h"
|
|
|
|
#define TCP_SERIAL_PORT 1977
|
|
|
|
const UINT CSuperSerialCard::SERIALPORTITEM_INVALID_COM_PORT = 0;
|
|
|
|
// Default: 9600-8-N-1
|
|
SSC_DIPSW CSuperSerialCard::m_DIPSWDefault =
|
|
{
|
|
// DIPSW1:
|
|
CBR_9600, // Use 9600, as a 1MHz Apple II can only handle up to 9600 bps [Ref.1]
|
|
FWMODE_CIC,
|
|
|
|
// DIPSW2:
|
|
ONESTOPBIT,
|
|
8, // ByteSize
|
|
NOPARITY,
|
|
false, // SW2-5: LF(0x0A). SSC-24: In Comms mode, SSC automatically discards LF immediately following CR
|
|
true, // SW2-6: Interrupts. SSC-47: Passes interrupt requests from ACIA to the Apple II. NB. Can't be read from software
|
|
};
|
|
|
|
//===========================================================================
|
|
|
|
CSuperSerialCard::CSuperSerialCard(UINT slot) :
|
|
Card(CT_SSC, slot),
|
|
m_strSerialPortChoices(1, '\0'), // Combo box friendly, just in case.
|
|
m_uTCPChoiceItemIdx(0),
|
|
m_bCfgSupportDCD(false),
|
|
m_pExpansionRom(NULL),
|
|
m_hFrameWindow(NULL)
|
|
{
|
|
if (m_slot != 2) // fixme
|
|
ThrowErrorInvalidSlot();
|
|
|
|
m_dwSerialPortItem = 0;
|
|
|
|
m_hCommHandle = INVALID_HANDLE_VALUE;
|
|
m_hCommListenSocket = INVALID_SOCKET;
|
|
m_hCommAcceptSocket = INVALID_SOCKET;
|
|
|
|
m_hCommThread = NULL;
|
|
|
|
for (UINT i=0; i<COMMEVT_MAX; i++)
|
|
m_hCommEvent[i] = NULL;
|
|
|
|
memset(&m_o, 0, sizeof(m_o));
|
|
|
|
InternalReset();
|
|
|
|
//
|
|
|
|
const size_t SERIALCHOICE_ITEM_LENGTH = 12;
|
|
char serialPortName[SERIALCHOICE_ITEM_LENGTH];
|
|
std::string regSection = RegGetConfigSlotSection(m_slot);
|
|
RegLoadString(regSection.c_str(), REGVALUE_SERIAL_PORT_NAME, TRUE, serialPortName, sizeof(serialPortName), TEXT(""));
|
|
|
|
SetSerialPortName(serialPortName);
|
|
}
|
|
|
|
void CSuperSerialCard::InternalReset()
|
|
{
|
|
GetDIPSW();
|
|
|
|
// SY6551 datasheet: Hardware reset sets Command register to 0
|
|
// . NB. MOS6551 datasheet: Hardware reset: b#00000010 (so ACIA not init'd on IN#2!)
|
|
// SY6551 datasheet: Hardware reset sets Control register to 0 - the DIPSW settings are not used by h/w to setup this register
|
|
UpdateCommandAndControlRegs(0, 0); // Baud=External clock! 8-N-1
|
|
|
|
//
|
|
|
|
m_vbTxIrqPending = false;
|
|
m_vbRxIrqPending = false;
|
|
m_vbTxEmpty = true;
|
|
|
|
m_vuRxCurrBuffer = 0;
|
|
m_qComSerialBuffer[0].clear();
|
|
m_qComSerialBuffer[1].clear();
|
|
m_qTcpSerialBuffer.clear();
|
|
|
|
m_uDTR = DTR_CONTROL_DISABLE;
|
|
m_uRTS = RTS_CONTROL_DISABLE;
|
|
m_dwModemStatus = m_kDefaultModemStatus;
|
|
}
|
|
|
|
CSuperSerialCard::~CSuperSerialCard()
|
|
{
|
|
CloseComm();
|
|
|
|
delete [] m_pExpansionRom;
|
|
m_pExpansionRom = NULL;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
// TODO: Serial Comms - UI Property Sheet Page:
|
|
// . Ability to config the 2x DIPSWs - only takes affect after next Apple2 reset
|
|
// . 'Default' button that resets DIPSWs to DIPSWDefaults
|
|
// . Must respect IRQ disable dipswitch (cannot be overridden or read by software)
|
|
|
|
void CSuperSerialCard::GetDIPSW()
|
|
{
|
|
// TODO: Read settings from Registry(?). In the meantime, use the defaults:
|
|
SetDIPSWDefaults();
|
|
}
|
|
|
|
void CSuperSerialCard::SetDIPSWDefaults()
|
|
{
|
|
// Default DIPSW settings (comms mode)
|
|
|
|
// DIPSW1:
|
|
m_DIPSWCurrent.uBaudRate = m_DIPSWDefault.uBaudRate;
|
|
m_DIPSWCurrent.eFirmwareMode = m_DIPSWDefault.eFirmwareMode;
|
|
|
|
// DIPSW2:
|
|
m_DIPSWCurrent.uStopBits = m_DIPSWDefault.uStopBits;
|
|
m_DIPSWCurrent.uByteSize = m_DIPSWDefault.uByteSize;
|
|
m_DIPSWCurrent.uParity = m_DIPSWDefault.uParity;
|
|
m_DIPSWCurrent.bLinefeed = m_DIPSWDefault.bLinefeed;
|
|
m_DIPSWCurrent.bInterrupts = m_DIPSWDefault.bInterrupts;
|
|
}
|
|
|
|
UINT CSuperSerialCard::BaudRateToIndex(UINT uBaudRate)
|
|
{
|
|
switch (uBaudRate)
|
|
{
|
|
case CBR_110: return 0x05;
|
|
case CBR_300: return 0x06;
|
|
case CBR_600: return 0x07;
|
|
case CBR_1200: return 0x08;
|
|
case CBR_2400: return 0x0A;
|
|
case CBR_4800: return 0x0C;
|
|
case CBR_9600: return 0x0E;
|
|
case CBR_19200: return 0x0F;
|
|
case CBR_115200: return 0x00;
|
|
}
|
|
|
|
_ASSERT(0);
|
|
LogFileOutput("SSC: BaudRateToIndex(): unsupported rate: %d\n", uBaudRate);
|
|
return BaudRateToIndex(m_kDefaultBaudRate); // nominally use AppleWin default
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
void CSuperSerialCard::UpdateCommState()
|
|
{
|
|
if (m_hCommHandle == INVALID_HANDLE_VALUE)
|
|
return;
|
|
|
|
DCB dcb;
|
|
memset(&dcb, 0, sizeof(DCB));
|
|
dcb.DCBlength = sizeof(DCB);
|
|
GetCommState(m_hCommHandle,&dcb);
|
|
dcb.BaudRate = m_uBaudRate;
|
|
dcb.ByteSize = m_uByteSize;
|
|
dcb.Parity = m_uParity;
|
|
dcb.StopBits = m_uStopBits;
|
|
|
|
// Specifies the DTR (data-terminal-ready) input flow control (use dcb.fOutxCtsFlow for output)
|
|
dcb.fDtrControl = m_uDTR; // GH#386
|
|
|
|
// Specifies the RTS (request-to-send) input flow control (use dcb.fOutxDsrFlow for output)
|
|
dcb.fRtsControl = m_uRTS; // GH#311
|
|
|
|
SetCommState(m_hCommHandle,&dcb);
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
bool CSuperSerialCard::CheckComm()
|
|
{
|
|
// check for COM or TCP socket handle, and setup if invalid
|
|
if (IsActive())
|
|
return true;
|
|
|
|
if (m_dwSerialPortItem == m_uTCPChoiceItemIdx)
|
|
{
|
|
WSADATA wsaData;
|
|
if (WSAStartup(MAKEWORD(2, 2), &wsaData) == 0) // Winsock 2.2
|
|
{
|
|
if (wsaData.wVersion != 0x0202)
|
|
{
|
|
WSACleanup();
|
|
return false;
|
|
}
|
|
|
|
// initialized, so try to create a socket
|
|
m_hCommListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (m_hCommListenSocket == INVALID_SOCKET)
|
|
{
|
|
WSACleanup();
|
|
return false;
|
|
}
|
|
|
|
// have socket so attempt to bind it
|
|
SOCKADDR_IN saAddress;
|
|
memset(&saAddress, 0, sizeof(SOCKADDR_IN));
|
|
saAddress.sin_family = AF_INET;
|
|
saAddress.sin_port = htons(TCP_SERIAL_PORT); // TODO: get from registry / GUI
|
|
saAddress.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
if (bind(m_hCommListenSocket, (LPSOCKADDR)&saAddress, sizeof(saAddress)) == SOCKET_ERROR)
|
|
{
|
|
closesocket(m_hCommListenSocket);
|
|
m_hCommListenSocket = INVALID_SOCKET;
|
|
WSACleanup();
|
|
return false;
|
|
}
|
|
|
|
// bound, so listen
|
|
if (listen(m_hCommListenSocket, 1) == SOCKET_ERROR)
|
|
{
|
|
closesocket(m_hCommListenSocket);
|
|
m_hCommListenSocket = INVALID_SOCKET;
|
|
WSACleanup();
|
|
return false;
|
|
}
|
|
|
|
// now send async events to our app's message handler
|
|
m_hFrameWindow = GetFrame().g_hFrameWindow;
|
|
if (WSAAsyncSelect(
|
|
/* SOCKET s */ m_hCommListenSocket,
|
|
/* HWND hWnd */ m_hFrameWindow,
|
|
/* unsigned int wMsg */ WM_USER_TCP_SERIAL,
|
|
/* long lEvent */ (FD_ACCEPT | FD_CONNECT | FD_READ | FD_CLOSE)) != 0)
|
|
{
|
|
closesocket(m_hCommListenSocket);
|
|
m_hCommListenSocket = INVALID_SOCKET;
|
|
WSACleanup();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if (m_dwSerialPortItem)
|
|
{
|
|
_ASSERT(m_dwSerialPortItem < m_vecSerialPortsItems.size()-1); // size()-1 is TCP item
|
|
std::string portname = StrFormat("\\\\.\\COM%u", m_vecSerialPortsItems[m_dwSerialPortItem]);
|
|
|
|
m_hCommHandle = CreateFile(portname.c_str(),
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
0, // exclusive access
|
|
(LPSECURITY_ATTRIBUTES)NULL, // default security attributes
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_OVERLAPPED, // required for WaitCommEvent()
|
|
NULL);
|
|
|
|
if (m_hCommHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
GetCommModemStatus(m_hCommHandle, const_cast<DWORD*>(&m_dwModemStatus));
|
|
|
|
//BOOL bRes = SetupComm(m_hCommHandle, 8192, 8192);
|
|
//_ASSERT(bRes);
|
|
|
|
UpdateCommState();
|
|
|
|
// ReadIntervalTimeout=MAXDWORD; ReadTotalTimeoutConstant=ReadTotalTimeoutMultiplier=0:
|
|
// Read operation is to return immediately with the bytes that have already been received,
|
|
// even if no bytes have been received.
|
|
COMMTIMEOUTS ct;
|
|
memset(&ct, 0, sizeof(COMMTIMEOUTS));
|
|
ct.ReadIntervalTimeout = MAXDWORD;
|
|
SetCommTimeouts(m_hCommHandle,&ct);
|
|
|
|
CommThInit();
|
|
}
|
|
else
|
|
{
|
|
DWORD uError = GetLastError();
|
|
}
|
|
}
|
|
|
|
return IsActive();
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
void CSuperSerialCard::CloseComm()
|
|
{
|
|
CommTcpSerialCleanup(); // Shut down Winsock
|
|
|
|
CommThUninit(); // Kill CommThread before closing COM handle
|
|
|
|
if (m_hCommHandle != INVALID_HANDLE_VALUE)
|
|
CloseHandle(m_hCommHandle);
|
|
|
|
m_hCommHandle = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
void CSuperSerialCard::CommTcpSerialCleanup()
|
|
{
|
|
if (m_hCommListenSocket != INVALID_SOCKET)
|
|
{
|
|
WSAAsyncSelect(m_hCommListenSocket, m_hFrameWindow, 0, 0); // Stop event messages
|
|
closesocket(m_hCommListenSocket);
|
|
m_hCommListenSocket = INVALID_SOCKET;
|
|
|
|
CommTcpSerialClose();
|
|
|
|
WSACleanup();
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
void CSuperSerialCard::CommTcpSerialClose()
|
|
{
|
|
if (m_hCommAcceptSocket != INVALID_SOCKET)
|
|
{
|
|
shutdown(m_hCommAcceptSocket, 2 /* SD_BOTH */); // In case the client is waiting for data
|
|
closesocket(m_hCommAcceptSocket);
|
|
m_hCommAcceptSocket = INVALID_SOCKET;
|
|
}
|
|
|
|
m_qTcpSerialBuffer.clear();
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
void CSuperSerialCard::CommTcpSerialAccept()
|
|
{
|
|
// Valid listener socket and invalid accept socket?
|
|
if ((m_hCommListenSocket != INVALID_SOCKET) && (m_hCommAcceptSocket == INVALID_SOCKET))
|
|
{
|
|
// Y: accept the connection
|
|
m_hCommAcceptSocket = accept(m_hCommListenSocket, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
// Called when there's a TCP event via the message pump
|
|
// . Because it's via the message pump, then this call is synchronous to CommReceive(), so there's no need for a critical section
|
|
void CSuperSerialCard::CommTcpSerialReceive()
|
|
{
|
|
if (m_hCommAcceptSocket != INVALID_SOCKET)
|
|
{
|
|
char Data[0x80];
|
|
int nReceived = 0;
|
|
while ((nReceived = recv(m_hCommAcceptSocket, Data, sizeof(Data), 0)) > 0)
|
|
{
|
|
for (int i = 0; i < nReceived; i++)
|
|
{
|
|
m_qTcpSerialBuffer.push_back(Data[i]);
|
|
}
|
|
}
|
|
|
|
if (m_bRxIrqEnabled && !m_qTcpSerialBuffer.empty())
|
|
{
|
|
CpuIrqAssert(IS_SSC);
|
|
m_vbRxIrqPending = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
BYTE __stdcall CSuperSerialCard::SSC_IORead(WORD PC, WORD uAddr, BYTE bWrite, BYTE uValue, ULONG nExecutedCycles)
|
|
{
|
|
UINT uSlot = ((uAddr & 0xff) >> 4) - 8;
|
|
CSuperSerialCard* pSSC = (CSuperSerialCard*) MemGetSlotParameters(uSlot);
|
|
|
|
switch (uAddr & 0xf)
|
|
{
|
|
case 0x0: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0x1: return pSSC->CommDipSw(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0x2: return pSSC->CommDipSw(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0x3: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0x4: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0x5: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0x6: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0x7: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0x8: return pSSC->CommReceive(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0x9: return pSSC->CommStatus(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0xA: return pSSC->CommCommand(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0xB: return pSSC->CommControl(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0xC: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0xD: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0xE: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0xF: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
BYTE __stdcall CSuperSerialCard::SSC_IOWrite(WORD PC, WORD uAddr, BYTE bWrite, BYTE uValue, ULONG nExecutedCycles)
|
|
{
|
|
UINT uSlot = ((uAddr & 0xff) >> 4) - 8;
|
|
CSuperSerialCard* pSSC = (CSuperSerialCard*) MemGetSlotParameters(uSlot);
|
|
|
|
switch (uAddr & 0xf)
|
|
{
|
|
case 0x0: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0x1: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0x2: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0x3: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0x4: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0x5: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0x6: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0x7: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0x8: return pSSC->CommTransmit(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0x9: return pSSC->CommProgramReset(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0xA: return pSSC->CommCommand(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0xB: return pSSC->CommControl(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0xC: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0xD: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0xE: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
case 0xF: return IO_Null(PC, uAddr, bWrite, uValue, nExecutedCycles);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
// 6551 ACIA Command Register ($C08A+s0)
|
|
// . EG. 0x09 = "no parity, enable IRQ" [Ref.2] - b7:5(No parity), b4 (No echo), b3:1(Enable TX,RX IRQs), b0(DTR: Enable receiver and all interrupts)
|
|
enum { CMD_PARITY_MASK = 3<<6,
|
|
CMD_PARITY_ODD = 0<<6, // Odd parity
|
|
CMD_PARITY_EVEN = 1<<6, // Even parity
|
|
CMD_PARITY_MARK = 2<<6, // Mark parity
|
|
CMD_PARITY_SPACE = 3<<6, // Space parity
|
|
CMD_PARITY_ENA = 1<<5,
|
|
CMD_ECHO_MODE = 1<<4,
|
|
CMD_TX_MASK = 3<<2,
|
|
CMD_TX_IRQ_DIS_RTS_HIGH = 0<<2,
|
|
CMD_TX_IRQ_ENA_RTS_LOW = 1<<2,
|
|
CMD_TX_IRQ_DIS_RTS_LOW = 2<<2,
|
|
CMD_TX_IRQ_DIS_RTS_LOW_BRK = 3<<2, // Transmit BRK
|
|
CMD_RX_IRQ_DIS = 1<<1, // 1=IRQ interrupt disabled
|
|
CMD_DTR = 1<<0, // Data Terminal Ready: Enable(1) or disable(0) receiver and all interrupts (!DTR low)
|
|
};
|
|
|
|
BYTE __stdcall CSuperSerialCard::CommProgramReset(WORD, WORD, BYTE, BYTE, ULONG)
|
|
{
|
|
// Command: top-3 parity bits unaffected
|
|
UpdateCommandReg( m_uCommandByte & (CMD_PARITY_MASK|CMD_PARITY_ENA) );
|
|
|
|
// Control: all bits unaffected
|
|
// Status: all bits unaffects, except Overrun(bit2) is cleared
|
|
|
|
return 0;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
void CSuperSerialCard::UpdateCommandAndControlRegs(BYTE uCommandByte, BYTE uControlByte)
|
|
{
|
|
// UpdateCommandReg() first to initialise m_uParity, before calling UpdateControlReg()
|
|
UpdateCommandReg(uCommandByte);
|
|
UpdateControlReg(uControlByte);
|
|
}
|
|
|
|
void CSuperSerialCard::UpdateCommandReg(BYTE command)
|
|
{
|
|
m_uCommandByte = command;
|
|
|
|
if (m_uCommandByte & CMD_PARITY_ENA)
|
|
{
|
|
switch (m_uCommandByte & CMD_PARITY_MASK)
|
|
{
|
|
case CMD_PARITY_ODD: m_uParity = ODDPARITY; break;
|
|
case CMD_PARITY_EVEN: m_uParity = EVENPARITY; break;
|
|
case CMD_PARITY_MARK: m_uParity = MARKPARITY; break;
|
|
case CMD_PARITY_SPACE: m_uParity = SPACEPARITY; break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_uParity = NOPARITY;
|
|
}
|
|
|
|
if (m_uCommandByte & CMD_ECHO_MODE) // Receiver mode echo (0=no echo, 1=echo)
|
|
{
|
|
_ASSERT(0);
|
|
LogFileOutput("SSC: CommCommand(): unsupported Echo mode. Command=0x%02X\n", m_uCommandByte);
|
|
}
|
|
|
|
switch (m_uCommandByte & CMD_TX_MASK) // transmitter interrupt control
|
|
{
|
|
// Note: the RTS signal must be set 'low' in order to receive any incoming data from the serial device [Ref.1]
|
|
case CMD_TX_IRQ_DIS_RTS_HIGH: // set RTS high and transmit no interrupts (transmitter is off [Ref.3])
|
|
m_uRTS = RTS_CONTROL_DISABLE;
|
|
break;
|
|
case CMD_TX_IRQ_ENA_RTS_LOW: // set RTS low and transmit interrupts
|
|
m_uRTS = RTS_CONTROL_ENABLE;
|
|
break;
|
|
case CMD_TX_IRQ_DIS_RTS_LOW: // set RTS low and transmit no interrupts
|
|
m_uRTS = RTS_CONTROL_ENABLE;
|
|
break;
|
|
case CMD_TX_IRQ_DIS_RTS_LOW_BRK: // set RTS low and transmit break signals instead of interrupts
|
|
m_uRTS = RTS_CONTROL_ENABLE;
|
|
_ASSERT(0);
|
|
LogFileOutput("SSC: CommCommand(): unsupported TX mode. Command=0x%02X\n", m_uCommandByte);
|
|
break;
|
|
}
|
|
|
|
if (m_DIPSWCurrent.bInterrupts && m_uCommandByte & CMD_DTR)
|
|
{
|
|
// Assume enabling Rx IRQ if STATUS.ST_RX_FULL *does not* trigger an IRQ
|
|
// . EG. Disable Rx IRQ, receive a byte (don't read STATUS or RX_DATA register), enable Rx IRQ
|
|
// Assume enabling Tx IRQ if STATUS.ST_TX_EMPTY *does not* trigger an IRQ
|
|
// . otherwise there'd be a "false" TX Empty IRQ even if nothing had ever been transferred!
|
|
m_bTxIrqEnabled = (m_uCommandByte & CMD_TX_MASK) == CMD_TX_IRQ_ENA_RTS_LOW;
|
|
m_bRxIrqEnabled = (m_uCommandByte & CMD_RX_IRQ_DIS) == 0;
|
|
}
|
|
else
|
|
{
|
|
m_bTxIrqEnabled = false;
|
|
m_bRxIrqEnabled = false;
|
|
}
|
|
|
|
// Data Terminal Ready (DTR) setting (0=set DTR high (indicates 'not ready')) (GH#386)
|
|
m_uDTR = (m_uCommandByte & CMD_DTR) ? DTR_CONTROL_ENABLE : DTR_CONTROL_DISABLE;
|
|
}
|
|
|
|
BYTE __stdcall CSuperSerialCard::CommCommand(WORD, WORD, BYTE write, BYTE value, ULONG)
|
|
{
|
|
if (!CheckComm())
|
|
return 0;
|
|
|
|
if (write && (value != m_uCommandByte))
|
|
{
|
|
UpdateCommandReg(value);
|
|
UpdateCommState();
|
|
}
|
|
|
|
return m_uCommandByte;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
void CSuperSerialCard::UpdateControlReg(BYTE control)
|
|
{
|
|
m_uControlByte = control;
|
|
|
|
// UPDATE THE BAUD RATE
|
|
switch (m_uControlByte & 0x0F)
|
|
{
|
|
// Note that 1 MHz Apples (everything other than the Apple IIgs and //c
|
|
// Plus running in "fast" mode) cannot handle 19.2 kbps, and even 9600
|
|
// bps on these machines requires either some highly optimised code or
|
|
// a decent buffer in the device being accessed. The faster Apples
|
|
// have no difficulty with this speed, however. [Ref.1]
|
|
|
|
case 0x00: m_uBaudRate = CBR_115200; break; // Internal clk: undoc'd 115.2K (or 16x external clock)
|
|
case 0x01: // fall through [50 bps]
|
|
case 0x02: // fall through [75 bps]
|
|
case 0x03: // fall through [109.92 bps]
|
|
case 0x04: // fall through [134.58 bps]
|
|
case 0x05: m_uBaudRate = CBR_110; break; // [150 bps]
|
|
case 0x06: m_uBaudRate = CBR_300; break;
|
|
case 0x07: m_uBaudRate = CBR_600; break;
|
|
case 0x08: m_uBaudRate = CBR_1200; break;
|
|
case 0x09: // fall through [1800 bps]
|
|
case 0x0A: m_uBaudRate = CBR_2400; break;
|
|
case 0x0B: // fall through [3600 bps]
|
|
case 0x0C: m_uBaudRate = CBR_4800; break;
|
|
case 0x0D: // fall through [7200 bps]
|
|
case 0x0E: m_uBaudRate = CBR_9600; break;
|
|
case 0x0F: m_uBaudRate = CBR_19200; break;
|
|
}
|
|
|
|
if (m_uControlByte & 0x10)
|
|
{
|
|
// receiver clock source [0= external, 1= internal]
|
|
}
|
|
|
|
// UPDATE THE BYTE SIZE
|
|
switch (m_uControlByte & 0x60)
|
|
{
|
|
case 0x00: m_uByteSize = 8; break;
|
|
case 0x20: m_uByteSize = 7; break;
|
|
case 0x40: m_uByteSize = 6; break;
|
|
case 0x60: m_uByteSize = 5; break;
|
|
}
|
|
|
|
// UPDATE THE NUMBER OF STOP BITS
|
|
if (m_uControlByte & 0x80)
|
|
{
|
|
if ((m_uByteSize == 8) && (m_uParity != NOPARITY))
|
|
m_uStopBits = ONESTOPBIT;
|
|
else if ((m_uByteSize == 5) && (m_uParity == NOPARITY))
|
|
m_uStopBits = ONE5STOPBITS;
|
|
else
|
|
m_uStopBits = TWOSTOPBITS;
|
|
}
|
|
else
|
|
{
|
|
m_uStopBits = ONESTOPBIT;
|
|
}
|
|
}
|
|
|
|
BYTE __stdcall CSuperSerialCard::CommControl(WORD, WORD, BYTE write, BYTE value, ULONG)
|
|
{
|
|
if (!CheckComm())
|
|
return 0;
|
|
|
|
if (write && (value != m_uControlByte))
|
|
{
|
|
UpdateControlReg(value);
|
|
UpdateCommState();
|
|
}
|
|
|
|
return m_uControlByte;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
BYTE __stdcall CSuperSerialCard::CommReceive(WORD, WORD, BYTE, BYTE, ULONG)
|
|
{
|
|
if (!CheckComm())
|
|
return 0;
|
|
|
|
BYTE result = 0;
|
|
|
|
if (!m_qTcpSerialBuffer.empty())
|
|
{
|
|
// NB. See CommTcpSerialReceive() above, for a note explaining why there's no need for a critical section here
|
|
|
|
// If receiver is disabled then transmitting device should not send data
|
|
// . For COM serial connection this is handled by DTR/DTS flow-control (which enables the receiver)
|
|
if ((m_uCommandByte & CMD_DTR) == 0) // Receiver disable, so prevent receiving data
|
|
return 0;
|
|
|
|
result = m_qTcpSerialBuffer.front();
|
|
m_qTcpSerialBuffer.pop_front();
|
|
|
|
if (m_bRxIrqEnabled && !m_qTcpSerialBuffer.empty())
|
|
{
|
|
CpuIrqAssert(IS_SSC);
|
|
m_vbRxIrqPending = true;
|
|
}
|
|
}
|
|
else if (m_hCommHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
EnterCriticalSection(&m_CriticalSection);
|
|
{
|
|
const UINT uCOMIdx = m_vuRxCurrBuffer;
|
|
const UINT uSSCIdx = uCOMIdx ^ 1;
|
|
if (!m_qComSerialBuffer[uSSCIdx].empty())
|
|
{
|
|
result = m_qComSerialBuffer[uSSCIdx].front();
|
|
m_qComSerialBuffer[uSSCIdx].pop_front();
|
|
|
|
UINT uNewSSCIdx = uSSCIdx;
|
|
if ( m_qComSerialBuffer[uSSCIdx].empty() && // Current SSC buffer is empty
|
|
!m_qComSerialBuffer[uCOMIdx].empty() ) // Current COM buffer has data
|
|
{
|
|
m_vuRxCurrBuffer = uSSCIdx; // Flip buffers
|
|
uNewSSCIdx = uCOMIdx;
|
|
}
|
|
|
|
if (m_bRxIrqEnabled && !m_qComSerialBuffer[uNewSSCIdx].empty())
|
|
{
|
|
CpuIrqAssert(IS_SSC);
|
|
m_vbRxIrqPending = true;
|
|
}
|
|
}
|
|
}
|
|
LeaveCriticalSection(&m_CriticalSection);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
void CSuperSerialCard::TransmitDone(void)
|
|
{
|
|
if (m_hCommHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
// Use CriticalSection to ensure that write to m_vbTxEmpty is atomic w.r.t CommTransmit() (GH#707)
|
|
EnterCriticalSection(&m_CriticalSection);
|
|
|
|
_ASSERT(m_vbTxEmpty == false);
|
|
m_vbTxEmpty = true; // Transmit done (COM)
|
|
|
|
LeaveCriticalSection(&m_CriticalSection);
|
|
}
|
|
else
|
|
{
|
|
_ASSERT(m_vbTxEmpty == false);
|
|
m_vbTxEmpty = true; // Transmit done (TCP)
|
|
}
|
|
|
|
if (m_bTxIrqEnabled) // GH#522
|
|
{
|
|
CpuIrqAssert(IS_SSC);
|
|
m_vbTxIrqPending = true;
|
|
}
|
|
}
|
|
|
|
BYTE __stdcall CSuperSerialCard::CommTransmit(WORD, WORD, BYTE, BYTE value, ULONG)
|
|
{
|
|
if (!CheckComm())
|
|
return 0;
|
|
|
|
// If transmitter is disabled then: Is data just discarded or does it get transmitted if transmitter is later enabled?
|
|
if ((m_uCommandByte & CMD_TX_MASK) == CMD_TX_IRQ_DIS_RTS_HIGH) // Transmitter disable, so just discard for now
|
|
return 0;
|
|
|
|
if (m_hCommAcceptSocket != INVALID_SOCKET)
|
|
{
|
|
BYTE data = value;
|
|
if (m_uByteSize < 8)
|
|
{
|
|
data &= ~(1 << m_uByteSize);
|
|
}
|
|
int sent = send(m_hCommAcceptSocket, (const char*)&data, 1, 0);
|
|
_ASSERT(sent == 1);
|
|
if (sent == 1)
|
|
{
|
|
m_vbTxEmpty = false;
|
|
// Assume that send() completes immediately
|
|
TransmitDone();
|
|
}
|
|
}
|
|
else if (m_hCommHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
BOOL res = false;
|
|
DWORD error = 0;
|
|
|
|
// Use CriticalSection to keep WriteFile() & m_vbTxEmpty in sync (GH#707)
|
|
EnterCriticalSection(&m_CriticalSection);
|
|
|
|
_ASSERT(m_vbTxEmpty == true);
|
|
DWORD uBytesWritten;
|
|
res = WriteFile(m_hCommHandle, &value, 1, &uBytesWritten, &m_o);
|
|
_ASSERT(res);
|
|
if (res)
|
|
{
|
|
m_vbTxEmpty = false;
|
|
// NB. Now CommThread() determines when transmit buffer is empty and calls TransmitDone()
|
|
}
|
|
else
|
|
{
|
|
error = GetLastError();
|
|
}
|
|
|
|
LeaveCriticalSection(&m_CriticalSection);
|
|
|
|
if (!res)
|
|
LogFileOutput("SSC: CommTransmit(): WriteFile() failed: 0x%08X\n", error);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
// 6551 ACIA Status Register ($C089+s0)
|
|
// ------------------------------------
|
|
// Bit Value Meaning
|
|
// 7 1 Interrupt (IRQ) true (cleared by reading status reg [Ref.3])
|
|
// 6 0 Data Set Ready (DSR) true [0=DSR low (ready), 1=DSR high (not ready)]
|
|
// 5 0 Data Carrier Detect (DCD) true [0=DCD low (detected), 1=DCD high (not detected)]
|
|
// 4 1 Transmit register empty
|
|
// 3 1 Receive register full
|
|
// 2 1 Overrun error
|
|
// 1 1 Framing error
|
|
// 0 1 Parity error
|
|
|
|
enum { ST_IRQ = 1<<7,
|
|
ST_DSR = 1<<6,
|
|
ST_DCD = 1<<5,
|
|
ST_TX_EMPTY = 1<<4,
|
|
ST_RX_FULL = 1<<3,
|
|
ST_OVERRUN_ERR = 1<<2,
|
|
ST_FRAMING_ERR = 1<<1,
|
|
ST_PARITY_ERR = 1<<0,
|
|
};
|
|
|
|
BYTE __stdcall CSuperSerialCard::CommStatus(WORD, WORD, BYTE, BYTE, ULONG)
|
|
{
|
|
if (!CheckComm())
|
|
return ST_DSR | ST_DCD | ST_TX_EMPTY;
|
|
|
|
DWORD modemStatus = m_kDefaultModemStatus;
|
|
if (m_hCommHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
modemStatus = m_dwModemStatus; // Take a copy of this volatile variable
|
|
if (!m_bCfgSupportDCD) // Default: DSR state is mirrored to DCD (GH#553)
|
|
{
|
|
modemStatus &= ~MS_RLSD_ON;
|
|
if (modemStatus & MS_DSR_ON)
|
|
modemStatus |= MS_RLSD_ON;
|
|
}
|
|
}
|
|
else if (m_hCommListenSocket != INVALID_SOCKET && m_hCommAcceptSocket != INVALID_SOCKET)
|
|
{
|
|
modemStatus = MS_RLSD_ON | MS_DSR_ON | MS_CTS_ON;
|
|
}
|
|
|
|
//
|
|
|
|
bool bComSerialBufferEmpty = true; // Assume true, so if using TCP then logic below works
|
|
|
|
if (m_hCommHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
EnterCriticalSection(&m_CriticalSection);
|
|
const UINT uSSCIdx = m_vuRxCurrBuffer ^ 1;
|
|
bComSerialBufferEmpty = m_qComSerialBuffer[uSSCIdx].empty();
|
|
}
|
|
|
|
BYTE IRQ = 0;
|
|
if (m_bTxIrqEnabled)
|
|
{
|
|
IRQ |= m_vbTxIrqPending ? ST_IRQ : 0;
|
|
m_vbTxIrqPending = false; // Ensure 2 reads of STATUS reg only return ST_IRQ for first read
|
|
}
|
|
if (m_bRxIrqEnabled)
|
|
{
|
|
IRQ |= m_vbRxIrqPending ? ST_IRQ : 0;
|
|
m_vbRxIrqPending = false; // Ensure 2 reads of STATUS reg only return ST_IRQ for first read
|
|
}
|
|
|
|
//
|
|
|
|
BYTE DSR = (modemStatus & MS_DSR_ON) ? 0x00 : ST_DSR; // DSR is active low (see SY6551 datasheet) (GH#386)
|
|
BYTE DCD = (modemStatus & MS_RLSD_ON) ? 0x00 : ST_DCD; // DCD is active low (see SY6551 datasheet) (GH#386)
|
|
|
|
//
|
|
|
|
BYTE TX_EMPTY = m_vbTxEmpty ? ST_TX_EMPTY : 0;
|
|
BYTE RX_FULL = (!bComSerialBufferEmpty || !m_qTcpSerialBuffer.empty()) ? ST_RX_FULL : 0;
|
|
|
|
//
|
|
|
|
BYTE uStatus =
|
|
IRQ
|
|
| DSR
|
|
| DCD
|
|
| TX_EMPTY
|
|
| RX_FULL;
|
|
|
|
if (m_hCommHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
LeaveCriticalSection(&m_CriticalSection);
|
|
}
|
|
|
|
CpuIrqDeassert(IS_SSC); // Read status reg always clears IRQ
|
|
|
|
return uStatus;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
// NB. Some DIPSW settings can't be read:
|
|
// SSC-47: Three switches are not connected to the LS365s:
|
|
// . SW2-6: passes interrupt requests from ACIA to the Apple II
|
|
// . SW1-7 ON and SW2-7 OFF: connects DCD to the DCD input of the ACIA
|
|
// . SW1-7 OFF and SW2-7 ON: splices SCTS to the DCD input of the ACIA (when jumper is in TERMINAL position)
|
|
BYTE __stdcall CSuperSerialCard::CommDipSw(WORD, WORD addr, BYTE, BYTE, ULONG)
|
|
{
|
|
BYTE sw = 0;
|
|
|
|
switch (addr & 0xf)
|
|
{
|
|
case 1: // DIPSW1
|
|
sw = (BaudRateToIndex(m_DIPSWCurrent.uBaudRate)<<4) | m_DIPSWCurrent.eFirmwareMode;
|
|
break;
|
|
|
|
case 2: // DIPSW2
|
|
// Comms mode: SSC-23
|
|
BYTE SW2_1 = m_DIPSWCurrent.uStopBits == TWOSTOPBITS ? 1 : 0; // SW2-1 (Stop bits: 1-ON(0); 2-OFF(1))
|
|
BYTE SW2_2 = m_DIPSWCurrent.uByteSize == 7 ? 1 : 0; // SW2-2 (Data bits: 8-ON(0); 7-OFF(1))
|
|
|
|
// SW2-3 (Parity: odd-ON(0); even-OFF(1))
|
|
// SW2-4 (Parity: none-ON(0); SW2-3 don't care)
|
|
BYTE SW2_3,SW2_4;
|
|
switch (m_DIPSWCurrent.uParity)
|
|
{
|
|
case ODDPARITY:
|
|
SW2_3 = 0; SW2_4 = 1;
|
|
break;
|
|
case EVENPARITY:
|
|
SW2_3 = 1; SW2_4 = 1;
|
|
break;
|
|
default:
|
|
_ASSERT(0);
|
|
// fall through...
|
|
case NOPARITY:
|
|
SW2_3 = 0; SW2_4 = 0;
|
|
break;
|
|
}
|
|
|
|
BYTE SW2_5 = m_DIPSWCurrent.bLinefeed ? 0 : 1; // SW2-5 (LF: yes-ON(0); no-OFF(1))
|
|
|
|
BYTE CTS = 1; // Default to CTS being false. (Support CTS in DIPSW: GH#311)
|
|
if (CheckComm() && m_hCommHandle != INVALID_HANDLE_VALUE)
|
|
CTS = (m_dwModemStatus & MS_CTS_ON) ? 0 : 1; // CTS active low (see SY6551 datasheet)
|
|
else if (m_hCommListenSocket != INVALID_SOCKET)
|
|
CTS = (m_hCommAcceptSocket != INVALID_SOCKET) ? 0 : 1;
|
|
|
|
// SSC-54:
|
|
sw = SW2_1<<7 | // b7 : SW2-1
|
|
0<<6 | // b6 : -
|
|
SW2_2<<5 | // b5 : SW2-2
|
|
0<<4 | // b4 : -
|
|
SW2_3<<3 | // b3 : SW2-3
|
|
SW2_4<<2 | // b2 : SW2-4
|
|
SW2_5<<1 | // b1 : SW2-5
|
|
CTS<<0; // b0 : CTS
|
|
break;
|
|
}
|
|
|
|
return sw;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
void CSuperSerialCard::InitializeIO(LPBYTE pCxRomPeripheral)
|
|
{
|
|
const UINT SSC_FW_SIZE = 2*1024;
|
|
const UINT SSC_SLOT_FW_SIZE = 256;
|
|
const UINT SSC_SLOT_FW_OFFSET = 7*256;
|
|
|
|
BYTE* pData = GetFrame().GetResource(IDR_SSC_FW, "FIRMWARE", SSC_FW_SIZE);
|
|
if(pData == NULL)
|
|
return;
|
|
|
|
memcpy(pCxRomPeripheral + m_slot*SSC_SLOT_FW_SIZE, pData+SSC_SLOT_FW_OFFSET, SSC_SLOT_FW_SIZE);
|
|
|
|
// Expansion ROM
|
|
if (m_pExpansionRom == NULL)
|
|
{
|
|
m_pExpansionRom = new BYTE [SSC_FW_SIZE];
|
|
memcpy(m_pExpansionRom, pData, SSC_FW_SIZE);
|
|
}
|
|
|
|
RegisterIoHandler(m_slot, &CSuperSerialCard::SSC_IORead, &CSuperSerialCard::SSC_IOWrite, NULL, NULL, this, m_pExpansionRom);
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
void CSuperSerialCard::Reset(const bool /* powerCycle */)
|
|
{
|
|
CloseComm();
|
|
|
|
InternalReset();
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
// dwNewSerialPortItem is the drop-down list item
|
|
void CSuperSerialCard::CommSetSerialPort(DWORD dwNewSerialPortItem)
|
|
{
|
|
if (m_dwSerialPortItem == dwNewSerialPortItem)
|
|
return;
|
|
|
|
_ASSERT(!IsActive());
|
|
if (IsActive())
|
|
return;
|
|
|
|
m_dwSerialPortItem = dwNewSerialPortItem;
|
|
|
|
if (m_dwSerialPortItem == m_uTCPChoiceItemIdx)
|
|
{
|
|
m_currentSerialPortName = TEXT_SERIAL_TCP;
|
|
}
|
|
else if (m_dwSerialPortItem != 0)
|
|
{
|
|
m_currentSerialPortName = StrFormat(TEXT_SERIAL_COM "%d", m_vecSerialPortsItems[m_dwSerialPortItem]);
|
|
}
|
|
else
|
|
{
|
|
m_currentSerialPortName.clear(); // "None"
|
|
}
|
|
|
|
SetRegistrySerialPortName();
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
// Had this error when sizeof(m_RecvBuffer)==1 was used
|
|
// UPDATE: Fixed by using double-buffered queue
|
|
//
|
|
// ERROR_OPERATION_ABORTED: CE_RXOVER
|
|
//
|
|
// Config:
|
|
// . DOS Box (laptop) -> ZLink (PC)
|
|
// . Baud = 300/4800/9600/19200
|
|
// . InQueue size = 0x1000
|
|
// . AppleII speed = 1MHz/2MHz/Unthrottled
|
|
// . TYPE AW-PascalCrash.txt >COM7
|
|
// . NB. AW-PascalCrash.txt is 10020 bytes
|
|
//
|
|
// Error:
|
|
// . Always get ERROR_OPERATION_ABORTED after reading 0x555 total bytes
|
|
// . dwErrors = 1 (CE_RXOVER)
|
|
// . COMSTAT::InQueue = 0x1000
|
|
//
|
|
|
|
void CSuperSerialCard::CheckCommEvent(DWORD dwEvtMask)
|
|
{
|
|
if (dwEvtMask & EV_RXCHAR)
|
|
{
|
|
char Data[0x80];
|
|
DWORD dwReceived = 0;
|
|
bool bGotData = false;
|
|
|
|
// Read COM buffer until empty
|
|
// NB. Potentially dangerous, as Apple read rate might be too slow, so could run out of memory on PC!
|
|
do
|
|
{
|
|
if (!ReadFile(m_hCommHandle, Data, sizeof(Data), &dwReceived, &m_o) || !dwReceived)
|
|
break;
|
|
|
|
bGotData = true;
|
|
|
|
EnterCriticalSection(&m_CriticalSection);
|
|
{
|
|
const UINT uCOMIdx = m_vuRxCurrBuffer;
|
|
for (DWORD i = 0; i < dwReceived; i++)
|
|
m_qComSerialBuffer[uCOMIdx].push_back(Data[i]);
|
|
}
|
|
LeaveCriticalSection(&m_CriticalSection);
|
|
}
|
|
while(sizeof(Data) == dwReceived);
|
|
|
|
//
|
|
|
|
if (bGotData)
|
|
{
|
|
EnterCriticalSection(&m_CriticalSection);
|
|
{
|
|
// NB. m_vuRxCurrBuffer may've changed since ReadFile() above -- can change in CommReceive()
|
|
// - Maybe buffers have already been flipped
|
|
|
|
const UINT uCOMIdx = m_vuRxCurrBuffer;
|
|
const UINT uSSCIdx = uCOMIdx ^ 1;
|
|
if ( m_qComSerialBuffer[uSSCIdx].empty() && // Current SSC buffer is empty
|
|
!m_qComSerialBuffer[uCOMIdx].empty() ) // Current COM buffer has data
|
|
{
|
|
m_vuRxCurrBuffer = uSSCIdx; // Flip buffers
|
|
|
|
if (m_bRxIrqEnabled)
|
|
{
|
|
CpuIrqAssert(IS_SSC);
|
|
m_vbRxIrqPending = true;
|
|
}
|
|
}
|
|
}
|
|
LeaveCriticalSection(&m_CriticalSection);
|
|
}
|
|
}
|
|
|
|
if (dwEvtMask & EV_TXEMPTY)
|
|
{
|
|
TransmitDone();
|
|
}
|
|
|
|
if (dwEvtMask & (EV_RLSD|EV_DSR|EV_CTS))
|
|
{
|
|
// For Win7-64: takes 1-2msecs!
|
|
// Don't read from main emulation thread, otherwise a tight 6502 polling loop can kill emulator performance!
|
|
GetCommModemStatus(m_hCommHandle, const_cast<DWORD*>(&m_dwModemStatus));
|
|
}
|
|
}
|
|
|
|
DWORD WINAPI CSuperSerialCard::CommThread(LPVOID lpParameter)
|
|
{
|
|
CSuperSerialCard* pSSC = (CSuperSerialCard*) lpParameter;
|
|
|
|
BOOL bRes = SetCommMask(pSSC->m_hCommHandle, EV_RLSD | EV_DSR | EV_CTS | EV_TXEMPTY | EV_RXCHAR);
|
|
if (!bRes)
|
|
{
|
|
LogOutput("SSC: CommThread(): SetCommMask() failed\n");
|
|
LogFileOutput("SSC: CommThread(): SetCommMask() failed\n");
|
|
return -1;
|
|
}
|
|
|
|
//
|
|
|
|
const UINT nNumEvents = 2;
|
|
HANDLE hCommEvent_Wait[nNumEvents] = {pSSC->m_hCommEvent[COMMEVT_WAIT], pSSC->m_hCommEvent[COMMEVT_TERM]};
|
|
|
|
while(1)
|
|
{
|
|
DWORD dwEvtMask = 0;
|
|
DWORD dwWaitResult;
|
|
|
|
bRes = WaitCommEvent(pSSC->m_hCommHandle, &dwEvtMask, &pSSC->m_o); // Will return immediately (probably with ERROR_IO_PENDING)
|
|
if (!bRes)
|
|
{
|
|
DWORD dwRet = GetLastError();
|
|
_ASSERT(dwRet == ERROR_IO_PENDING);
|
|
if (dwRet != ERROR_IO_PENDING)
|
|
{
|
|
// Probably: ERROR_OPERATION_ABORTED
|
|
DWORD dwErrors;
|
|
COMSTAT Stat;
|
|
ClearCommError(pSSC->m_hCommHandle, &dwErrors, &Stat);
|
|
if (dwErrors & CE_RXOVER)
|
|
{
|
|
LogOutput("SSC: CommThread(): LastError=0x%08X, CommError=CE_RXOVER (0x%08X): InQueue=0x%08X\n", dwRet, dwErrors, Stat.cbInQue);
|
|
LogFileOutput("SSC: CommThread(): LastError=0x%08X, CommError=CE_RXOVER (0x%08X): InQueue=0x%08X\n", dwRet, dwErrors, Stat.cbInQue);
|
|
}
|
|
else
|
|
{
|
|
LogOutput("SSC: CommThread(): LastError=0x%08X, CommError=Other (0x%08X): InQueue=0x%08X, OutQueue=0x%08X\n", dwRet, dwErrors, Stat.cbInQue, Stat.cbOutQue);
|
|
LogFileOutput("SSC: CommThread(): LastError=0x%08X, CommError=Other (0x%08X): InQueue=0x%08X, OutQueue=0x%08X\n", dwRet, dwErrors, Stat.cbInQue, Stat.cbOutQue);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//
|
|
// Wait for comm event
|
|
//
|
|
|
|
while(1)
|
|
{
|
|
//OutputDebugString("CommThread: Wait1\n");
|
|
dwWaitResult = WaitForMultipleObjects(
|
|
nNumEvents, // number of handles in array
|
|
hCommEvent_Wait, // array of event handles
|
|
FALSE, // wait until any one is signaled
|
|
INFINITE);
|
|
|
|
// On very 1st wait *only*: get a false signal (when not running via debugger)
|
|
if ((dwWaitResult == WAIT_OBJECT_0) && (dwEvtMask == 0))
|
|
continue;
|
|
|
|
if ((dwWaitResult >= WAIT_OBJECT_0) && (dwWaitResult <= WAIT_OBJECT_0+nNumEvents-1))
|
|
break;
|
|
}
|
|
|
|
dwWaitResult -= WAIT_OBJECT_0; // Determine event # that signaled
|
|
//LogOutput("CommThread: GotEvent1: %d\n", dwWaitResult);
|
|
|
|
if (dwWaitResult == (nNumEvents-1))
|
|
break; // Termination event
|
|
}
|
|
|
|
// Comm event
|
|
pSSC->CheckCommEvent(dwEvtMask);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool CSuperSerialCard::CommThInit()
|
|
{
|
|
_ASSERT(m_hCommThread == NULL);
|
|
_ASSERT(m_hCommHandle);
|
|
|
|
if ((m_hCommEvent[0] == NULL) && (m_hCommEvent[1] == NULL) && (m_hCommEvent[2] == NULL))
|
|
{
|
|
// Create an event object for use by WaitCommEvent
|
|
|
|
m_o.hEvent = CreateEvent(
|
|
NULL, // default security attributes
|
|
FALSE, // auto reset event (bManualReset)
|
|
FALSE, // not signaled (bInitialState)
|
|
NULL // no name
|
|
);
|
|
|
|
// Initialize the rest of the OVERLAPPED structure to zero
|
|
m_o.Internal = 0;
|
|
m_o.InternalHigh = 0;
|
|
m_o.Offset = 0;
|
|
m_o.OffsetHigh = 0;
|
|
|
|
//
|
|
|
|
m_hCommEvent[COMMEVT_WAIT] = m_o.hEvent;
|
|
m_hCommEvent[COMMEVT_ACK] = CreateEvent(NULL, // lpEventAttributes
|
|
FALSE, // bManualReset (FALSE = auto-reset)
|
|
FALSE, // bInitialState (FALSE = non-signaled)
|
|
NULL); // lpName
|
|
m_hCommEvent[COMMEVT_TERM] = CreateEvent(NULL, // lpEventAttributes
|
|
FALSE, // bManualReset (FALSE = auto-reset)
|
|
FALSE, // bInitialState (FALSE = non-signaled)
|
|
NULL); // lpName
|
|
|
|
if ((m_hCommEvent[0] == NULL) || (m_hCommEvent[1] == NULL) || (m_hCommEvent[2] == NULL))
|
|
{
|
|
if(g_fh) fprintf(g_fh, "Comm: CreateEvent failed\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
if (m_hCommThread == NULL)
|
|
{
|
|
InitializeCriticalSection(&m_CriticalSection);
|
|
|
|
DWORD dwThreadId;
|
|
m_hCommThread = CreateThread(NULL, // lpThreadAttributes
|
|
0, // dwStackSize
|
|
(LPTHREAD_START_ROUTINE) &CSuperSerialCard::CommThread,
|
|
this, // lpParameter
|
|
0, // dwCreationFlags : 0 = Run immediately
|
|
&dwThreadId); // lpThreadId
|
|
|
|
SetThreadPriority(m_hCommThread, THREAD_PRIORITY_TIME_CRITICAL);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CSuperSerialCard::CommThUninit()
|
|
{
|
|
if (m_hCommThread)
|
|
{
|
|
SetEvent(m_hCommEvent[COMMEVT_TERM]); // Signal to thread that it should exit
|
|
|
|
do
|
|
{
|
|
DWORD dwExitCode;
|
|
if(GetExitCodeThread(m_hCommThread, &dwExitCode))
|
|
{
|
|
if(dwExitCode == STILL_ACTIVE)
|
|
Sleep(10);
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
while(1);
|
|
|
|
CloseHandle(m_hCommThread);
|
|
m_hCommThread = NULL;
|
|
|
|
DeleteCriticalSection(&m_CriticalSection);
|
|
}
|
|
|
|
//
|
|
|
|
for (UINT i=0; i<COMMEVT_MAX; i++)
|
|
{
|
|
if(m_hCommEvent[i])
|
|
{
|
|
CloseHandle(m_hCommEvent[i]);
|
|
m_hCommEvent[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
void CSuperSerialCard::ScanCOMPorts()
|
|
{
|
|
m_vecSerialPortsItems.clear();
|
|
m_vecSerialPortsItems.push_back(SERIALPORTITEM_INVALID_COM_PORT); // "None"
|
|
|
|
for (UINT i=1; i<32; i++) // Arbitrary upper limit
|
|
{
|
|
std::string portname = StrFormat("\\\\.\\COM%u", i);
|
|
|
|
HANDLE hCommHandle = CreateFile(portname.c_str(),
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
0, // exclusive access
|
|
(LPSECURITY_ATTRIBUTES)NULL, // default security attributes
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_OVERLAPPED, // required for WaitCommEvent()
|
|
NULL);
|
|
|
|
if (hCommHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
CloseHandle(hCommHandle);
|
|
m_vecSerialPortsItems.push_back(i);
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
m_vecSerialPortsItems.push_back(SERIALPORTITEM_INVALID_COM_PORT); // "TCP"
|
|
m_uTCPChoiceItemIdx = m_vecSerialPortsItems.size()-1;
|
|
}
|
|
|
|
std::string const& CSuperSerialCard::GetSerialPortChoices()
|
|
{
|
|
if (IsActive())
|
|
return m_strSerialPortChoices;
|
|
|
|
ScanCOMPorts(); // Do this every time in case news ones available (eg. for USB COM ports)
|
|
|
|
m_strSerialPortChoices = "None";
|
|
m_strSerialPortChoices += '\0'; // NULL char for combo box selection.
|
|
|
|
for (UINT i = 1; i < m_uTCPChoiceItemIdx; i++)
|
|
{
|
|
m_strSerialPortChoices += StrFormat("COM%u", m_vecSerialPortsItems[i]);
|
|
m_strSerialPortChoices += '\0'; // NULL char for combo box selection.
|
|
}
|
|
|
|
m_strSerialPortChoices += "TCP";
|
|
m_strSerialPortChoices += '\0'; // NULL char for combo box selection.
|
|
|
|
// std::string()'s implicit nul terminator becomes combo box end of list marker.
|
|
|
|
return m_strSerialPortChoices;
|
|
}
|
|
|
|
// Called by ctor & LoadSnapshot()
|
|
void CSuperSerialCard::SetSerialPortName(const char* pSerialPortName)
|
|
{
|
|
m_currentSerialPortName = pSerialPortName;
|
|
|
|
// Init m_aySerialPortChoices, so that we have choices to show if serial is active when we 1st open Config dialog
|
|
GetSerialPortChoices();
|
|
|
|
if (strncmp(TEXT_SERIAL_COM, pSerialPortName, sizeof(TEXT_SERIAL_COM)-1) == 0)
|
|
{
|
|
const char* p = &pSerialPortName[ sizeof(TEXT_SERIAL_COM)-1 ];
|
|
const int nCOMPort = atoi(p);
|
|
m_dwSerialPortItem = 0;
|
|
for (UINT i=0; i<m_vecSerialPortsItems.size(); i++)
|
|
{
|
|
if (m_vecSerialPortsItems[i] == nCOMPort)
|
|
{
|
|
m_dwSerialPortItem = i;
|
|
break;
|
|
}
|
|
}
|
|
//_ASSERT(m_dwSerialPortItem); // EG. Switched a USB COM port from COM7 to COM8 between AppleWin sessions
|
|
|
|
if (m_dwSerialPortItem >= GetNumSerialPortChoices())
|
|
{
|
|
_ASSERT(0);
|
|
m_dwSerialPortItem = 0;
|
|
}
|
|
}
|
|
else if (strncmp(TEXT_SERIAL_TCP, pSerialPortName, sizeof(TEXT_SERIAL_TCP)-1) == 0)
|
|
{
|
|
m_dwSerialPortItem = m_uTCPChoiceItemIdx;
|
|
}
|
|
else
|
|
{
|
|
m_currentSerialPortName.clear(); // "None"
|
|
m_dwSerialPortItem = 0;
|
|
}
|
|
}
|
|
|
|
void CSuperSerialCard::SetRegistrySerialPortName(void)
|
|
{
|
|
std::string regSection = RegGetConfigSlotSection(m_slot);
|
|
RegSaveString(regSection.c_str(), REGVALUE_SERIAL_PORT_NAME, TRUE, GetSerialPortName());
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
// Unit version history:
|
|
// 2: Added: Support DCD flag
|
|
// Removed: redundant data (encapsulated in Command & Control bytes)
|
|
static const UINT kUNIT_VERSION = 2;
|
|
|
|
#define SS_YAML_VALUE_CARD_SSC "Super Serial Card"
|
|
|
|
#define SS_YAML_KEY_DIPSWDEFAULT "DIPSW Default"
|
|
#define SS_YAML_KEY_DIPSWCURRENT "DIPSW Current"
|
|
|
|
#define SS_YAML_KEY_BAUDRATE "Baud Rate"
|
|
#define SS_YAML_KEY_FWMODE "Firmware mode"
|
|
#define SS_YAML_KEY_STOPBITS "Stop Bits"
|
|
#define SS_YAML_KEY_BYTESIZE "Byte Size"
|
|
#define SS_YAML_KEY_PARITY "Parity"
|
|
#define SS_YAML_KEY_LINEFEED "Linefeed"
|
|
#define SS_YAML_KEY_INTERRUPTS "Interrupts"
|
|
#define SS_YAML_KEY_CONTROL "Control Byte"
|
|
#define SS_YAML_KEY_COMMAND "Command Byte"
|
|
#define SS_YAML_KEY_INACTIVITY "Comm Inactivity"
|
|
#define SS_YAML_KEY_TXIRQENABLED "TX IRQ Enabled"
|
|
#define SS_YAML_KEY_RXIRQENABLED "RX IRQ Enabled"
|
|
#define SS_YAML_KEY_TXIRQPENDING "TX IRQ Pending"
|
|
#define SS_YAML_KEY_RXIRQPENDING "RX IRQ Pending"
|
|
#define SS_YAML_KEY_WRITTENTX "Written TX"
|
|
#define SS_YAML_KEY_SERIALPORTNAME "Serial Port Name"
|
|
#define SS_YAML_KEY_SUPPORT_DCD "Support DCD"
|
|
|
|
const std::string& CSuperSerialCard::GetSnapshotCardName(void)
|
|
{
|
|
static const std::string name(SS_YAML_VALUE_CARD_SSC);
|
|
return name;
|
|
}
|
|
|
|
void CSuperSerialCard::SaveSnapshotDIPSW(YamlSaveHelper& yamlSaveHelper, std::string key, SSC_DIPSW& dipsw)
|
|
{
|
|
YamlSaveHelper::Label label(yamlSaveHelper, "%s:\n", key.c_str());
|
|
yamlSaveHelper.SaveUint(SS_YAML_KEY_BAUDRATE, dipsw.uBaudRate);
|
|
yamlSaveHelper.SaveUint(SS_YAML_KEY_FWMODE, dipsw.eFirmwareMode);
|
|
yamlSaveHelper.SaveUint(SS_YAML_KEY_STOPBITS, dipsw.uStopBits);
|
|
yamlSaveHelper.SaveUint(SS_YAML_KEY_BYTESIZE, dipsw.uByteSize);
|
|
yamlSaveHelper.SaveUint(SS_YAML_KEY_PARITY, dipsw.uParity);
|
|
yamlSaveHelper.SaveBool(SS_YAML_KEY_LINEFEED, dipsw.bLinefeed);
|
|
yamlSaveHelper.SaveBool(SS_YAML_KEY_INTERRUPTS, dipsw.bInterrupts);
|
|
}
|
|
|
|
void CSuperSerialCard::SaveSnapshot(YamlSaveHelper& yamlSaveHelper)
|
|
{
|
|
YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardName(), m_slot, kUNIT_VERSION);
|
|
|
|
YamlSaveHelper::Label unit(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE);
|
|
SaveSnapshotDIPSW(yamlSaveHelper, SS_YAML_KEY_DIPSWDEFAULT, m_DIPSWDefault);
|
|
SaveSnapshotDIPSW(yamlSaveHelper, SS_YAML_KEY_DIPSWCURRENT, m_DIPSWCurrent);
|
|
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_CONTROL, m_uControlByte);
|
|
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_COMMAND, m_uCommandByte);
|
|
yamlSaveHelper.SaveBool(SS_YAML_KEY_TXIRQPENDING, m_vbTxIrqPending);
|
|
yamlSaveHelper.SaveBool(SS_YAML_KEY_RXIRQPENDING, m_vbRxIrqPending);
|
|
yamlSaveHelper.SaveBool(SS_YAML_KEY_WRITTENTX, m_vbTxEmpty);
|
|
yamlSaveHelper.SaveBool(SS_YAML_KEY_SUPPORT_DCD, m_bCfgSupportDCD);
|
|
yamlSaveHelper.SaveString(SS_YAML_KEY_SERIALPORTNAME, GetSerialPortName());
|
|
}
|
|
|
|
void CSuperSerialCard::LoadSnapshotDIPSW(YamlLoadHelper& yamlLoadHelper, std::string key, SSC_DIPSW& dipsw)
|
|
{
|
|
if (!yamlLoadHelper.GetSubMap(key))
|
|
throw std::runtime_error("Card: Expected key: " + key);
|
|
|
|
dipsw.uBaudRate = yamlLoadHelper.LoadUint(SS_YAML_KEY_BAUDRATE);
|
|
dipsw.eFirmwareMode = (eFWMODE) yamlLoadHelper.LoadUint(SS_YAML_KEY_FWMODE);
|
|
dipsw.uStopBits = yamlLoadHelper.LoadUint(SS_YAML_KEY_STOPBITS);
|
|
dipsw.uByteSize = yamlLoadHelper.LoadUint(SS_YAML_KEY_BYTESIZE);
|
|
dipsw.uParity = yamlLoadHelper.LoadUint(SS_YAML_KEY_PARITY);
|
|
dipsw.bLinefeed = yamlLoadHelper.LoadBool(SS_YAML_KEY_LINEFEED);
|
|
dipsw.bInterrupts = yamlLoadHelper.LoadBool(SS_YAML_KEY_INTERRUPTS);
|
|
|
|
yamlLoadHelper.PopMap();
|
|
}
|
|
|
|
bool CSuperSerialCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version)
|
|
{
|
|
if (version < 1 || version > kUNIT_VERSION)
|
|
ThrowErrorInvalidVersion(version);
|
|
|
|
LoadSnapshotDIPSW(yamlLoadHelper, SS_YAML_KEY_DIPSWDEFAULT, m_DIPSWDefault);
|
|
LoadSnapshotDIPSW(yamlLoadHelper, SS_YAML_KEY_DIPSWCURRENT, m_DIPSWCurrent);
|
|
|
|
if (version == 1) // Consume redundant/obsolete data
|
|
{
|
|
yamlLoadHelper.LoadUint(SS_YAML_KEY_PARITY); // Redundant: derived from uCommandByte in UpdateCommandReg()
|
|
yamlLoadHelper.LoadBool(SS_YAML_KEY_TXIRQENABLED); // Redundant: derived from uCommandByte in UpdateCommandReg()
|
|
yamlLoadHelper.LoadBool(SS_YAML_KEY_RXIRQENABLED); // Redundant: derived from uCommandByte in UpdateCommandReg()
|
|
|
|
yamlLoadHelper.LoadUint(SS_YAML_KEY_BAUDRATE); // Redundant: derived from uControlByte in UpdateControlReg()
|
|
yamlLoadHelper.LoadUint(SS_YAML_KEY_STOPBITS); // Redundant: derived from uControlByte in UpdateControlReg()
|
|
yamlLoadHelper.LoadUint(SS_YAML_KEY_BYTESIZE); // Redundant: derived from uControlByte in UpdateControlReg()
|
|
|
|
yamlLoadHelper.LoadUint(SS_YAML_KEY_INACTIVITY); // Obsolete (so just consume)
|
|
}
|
|
else if (version >= 2)
|
|
{
|
|
SupportDCD( yamlLoadHelper.LoadBool(SS_YAML_KEY_SUPPORT_DCD) );
|
|
}
|
|
|
|
UINT uCommandByte = yamlLoadHelper.LoadUint(SS_YAML_KEY_COMMAND);
|
|
UINT uControlByte = yamlLoadHelper.LoadUint(SS_YAML_KEY_CONTROL);
|
|
UpdateCommandAndControlRegs(uCommandByte, uControlByte);
|
|
|
|
m_vbTxIrqPending = yamlLoadHelper.LoadBool(SS_YAML_KEY_TXIRQPENDING);
|
|
m_vbRxIrqPending = yamlLoadHelper.LoadBool(SS_YAML_KEY_RXIRQPENDING);
|
|
m_vbTxEmpty = yamlLoadHelper.LoadBool(SS_YAML_KEY_WRITTENTX);
|
|
|
|
if (m_vbTxIrqPending || m_vbRxIrqPending) // GH#677
|
|
CpuIrqAssert(IS_SSC);
|
|
|
|
std::string serialPortName = yamlLoadHelper.LoadString(SS_YAML_KEY_SERIALPORTNAME);
|
|
SetSerialPortName(serialPortName.c_str());
|
|
SetRegistrySerialPortName();
|
|
|
|
return true;
|
|
}
|