/* 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(&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(&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= 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; }