/* 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 "Applewin.h" #include "CPU.h" #include "Frame.h" #include "Log.h" #include "Memory.h" #include "SerialComms.h" #include "YamlHelper.h" #include "../resource/resource.h" #define TCP_SERIAL_PORT 1977 // Default: 19200-8-N-1 SSC_DIPSW CSuperSerialCard::m_DIPSWDefault = { // DIPSW1: CBR_19200, 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() : m_aySerialPortChoices(NULL), m_uTCPChoiceItemIdx(0), m_uSlot(0), m_bCfgSupportDCD(false), m_bCfgSupportDSR(false), m_bCfgSupportDTR(false) { memset(m_ayCurrentSerialPortName, 0, sizeof(m_ayCurrentSerialPortName)); m_dwSerialPortItem = 0; m_hCommHandle = INVALID_HANDLE_VALUE; m_hCommListenSocket = INVALID_SOCKET; m_hCommAcceptSocket = INVALID_SOCKET; m_hCommThread = NULL; for (UINT i=0; i 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" - b7:5(No parity), b4 (No echo), b3:1(Enable TX,RX IRQs), b0(DTR: Enable Rx/Tx) [Ref.2] 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: 1=Enable Rx/Tx (!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::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_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; } if (m_bCfgSupportDTR) // GH#386 { // Data Terminal Ready (DTR) setting (0=set DTR high (indicates 'not ready')) 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; } //=========================================================================== BYTE __stdcall CSuperSerialCard::CommControl(WORD, WORD, BYTE write, BYTE value, ULONG) { if (!CheckComm()) return 0; if (write && (value != m_uControlByte)) { m_uControlByte = value; // 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. 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; } 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 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) // COM { 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) { m_vbTxEmpty = true; // Transmit done 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 (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); if (sent == 1) { m_vbTxEmpty = false; // Assume that send() completes immediately TransmitDone(); } } else if (m_hCommHandle != INVALID_HANDLE_VALUE) { DWORD uBytesWritten; WriteFile(m_hCommHandle, &value, 1, &uBytesWritten, &m_o); m_vbTxEmpty = false; // NB. Now CommThread() determines when transmit buffer is empty and calls TransmitDone() } 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 = 0; if ((m_hCommHandle != INVALID_HANDLE_VALUE) && (m_bCfgSupportDCD || m_bCfgSupportDSR)) { // Do this outside of the critical section (don't know how long it takes) GetCommModemStatus(m_hCommHandle, &modemStatus); // Returns 0x30 = 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 = 0; BYTE DCD = 0; if ((m_hCommHandle != INVALID_HANDLE_VALUE) && (m_bCfgSupportDCD || m_bCfgSupportDSR)) // GH#386 { if (m_bCfgSupportDSR) DSR = (modemStatus & MS_DSR_ON) ? 0x00 : ST_DSR; if (m_bCfgSupportDCD) DCD = (modemStatus & MS_RLSD_ON) ? 0x00 : ST_DCD; } // 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 // Need 0x00 to allow ZLink to start up | 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 = 0; // GH#311 if (CheckComm() && m_hCommHandle != INVALID_HANDLE_VALUE) { DWORD modemStatus = 0; if (GetCommModemStatus(m_hCommHandle, &modemStatus)) CTS = (modemStatus & MS_CTS_ON) ? 0 : 1; // CTS is true when 0 } // 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::CommInitialize(LPBYTE pCxRomPeripheral, UINT uSlot) { const UINT SSC_FW_SIZE = 2*1024; const UINT SSC_SLOT_FW_SIZE = 256; const UINT SSC_SLOT_FW_OFFSET = 7*256; HRSRC hResInfo = FindResource(NULL, MAKEINTRESOURCE(IDR_SSC_FW), "FIRMWARE"); if(hResInfo == NULL) return; DWORD dwResSize = SizeofResource(NULL, hResInfo); if(dwResSize != SSC_FW_SIZE) return; HGLOBAL hResData = LoadResource(NULL, hResInfo); if(hResData == NULL) return; BYTE* pData = (BYTE*) LockResource(hResData); // NB. Don't need to unlock resource if(pData == NULL) return; memcpy(pCxRomPeripheral + uSlot*256, pData+SSC_SLOT_FW_OFFSET, SSC_SLOT_FW_SIZE); m_uSlot = uSlot; // Expansion ROM if (m_pExpansionRom == NULL) { m_pExpansionRom = new BYTE [SSC_FW_SIZE]; if (m_pExpansionRom) memcpy(m_pExpansionRom, pData, SSC_FW_SIZE); } // RegisterIoHandler(uSlot, &CSuperSerialCard::SSC_IORead, &CSuperSerialCard::SSC_IOWrite, NULL, NULL, this, m_pExpansionRom); } //=========================================================================== void CSuperSerialCard::CommReset() { CloseComm(); InternalReset(); } //=========================================================================== void CSuperSerialCard::CommDestroy() { CommReset(); delete [] m_pExpansionRom; m_pExpansionRom = NULL; } //=========================================================================== // dwNewSerialPortItem is the drop-down list item void CSuperSerialCard::CommSetSerialPort(HWND hWindow, DWORD dwNewSerialPortItem) { if (m_dwSerialPortItem == dwNewSerialPortItem) return; _ASSERT(!IsActive()); if (IsActive()) return; m_dwSerialPortItem = dwNewSerialPortItem; if (m_dwSerialPortItem == m_uTCPChoiceItemIdx) strcpy(m_ayCurrentSerialPortName, TEXT_SERIAL_TCP); else if (m_dwSerialPortItem != 0) sprintf(m_ayCurrentSerialPortName, TEXT_SERIAL_COM"%d", m_vecSerialPortsItems[m_dwSerialPortItem]); else m_ayCurrentSerialPortName[0] = 0; // "None" } //=========================================================================== // 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 // static UINT g_uDbgTotalCOMRx = 0; 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; g_uDbgTotalCOMRx += dwReceived; 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); } } else if (dwEvtMask & EV_TXEMPTY) { TransmitDone(); } } DWORD WINAPI CSuperSerialCard::CommThread(LPVOID lpParameter) { CSuperSerialCard* pSSC = (CSuperSerialCard*) lpParameter; char szDbg[100]; BOOL bRes = SetCommMask(pSSC->m_hCommHandle, EV_TXEMPTY | EV_RXCHAR); // BOOL bRes = SetCommMask(pSSC->m_hCommHandle, EV_RXCHAR); // Just RX if (!bRes) { sprintf(szDbg, "SSC: CommThread(): SetCommMask() failed\n"); LogOutput("%s", szDbg); LogFileOutput("%s", szDbg); return -1; } // const UINT nNumEvents = 2; HANDLE hCommEvent_Wait[nNumEvents] = {pSSC->m_hCommEvent[COMMEVT_WAIT], pSSC->m_hCommEvent[COMMEVT_TERM]}; HANDLE hCommEvent_Ack[nNumEvents] = {pSSC->m_hCommEvent[COMMEVT_ACK], 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) _ASSERT(!bRes); 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) sprintf(szDbg, "SSC: CommThread(): LastError=0x%08X, CommError=CE_RXOVER (0x%08X): InQueue=0x%08X\n", dwRet, dwErrors, Stat.cbInQue); else sprintf(szDbg, "SSC: CommThread(): LastError=0x%08X, CommError=Other (0x%08X): InQueue=0x%08X, OutQueue=0x%08X\n", dwRet, dwErrors, Stat.cbInQue, Stat.cbOutQue); LogOutput("%s", szDbg); LogFileOutput("%s", szDbg); 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 //sprintf(szDbg, "CommThread: GotEvent1: %d\n", dwWaitResult); OutputDebugString(szDbg); 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_ayCurrentSerialPortName[0] = 0; // "None" m_dwSerialPortItem = 0; } } //=========================================================================== void CSuperSerialCard::SetSnapshot_v1( const DWORD baudrate, const BYTE bytesize, const BYTE commandbyte, const DWORD comminactivity, const BYTE controlbyte, const BYTE parity, const BYTE stopbits) { m_uBaudRate = baudrate; m_uStopBits = stopbits; m_uByteSize = bytesize; // m_dwCommInactivity = comminactivity; // Obsolete m_uControlByte = controlbyte; // m_uParity = parity; // Redundant: derived from commandbyte in UpdateCommandReg() // memcpy(m_RecvBuffer, pSS->recvbuffer, uRecvBufferSize); // m_vRecvBytes = recvbytes; UpdateCommandReg(commandbyte); } //=========================================================================== #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" 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_uSlot, 1); 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.SaveUint(SS_YAML_KEY_BAUDRATE, m_uBaudRate); yamlSaveHelper.SaveUint(SS_YAML_KEY_STOPBITS, m_uStopBits); yamlSaveHelper.SaveUint(SS_YAML_KEY_BYTESIZE, m_uByteSize); yamlSaveHelper.SaveUint(SS_YAML_KEY_PARITY, m_uParity); // Redundant yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_CONTROL, m_uControlByte); yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_COMMAND, m_uCommandByte); yamlSaveHelper.SaveUint(SS_YAML_KEY_INACTIVITY, 0); // Obsolete yamlSaveHelper.SaveBool(SS_YAML_KEY_TXIRQENABLED, m_bTxIrqEnabled); // Redundant yamlSaveHelper.SaveBool(SS_YAML_KEY_RXIRQENABLED, m_bRxIrqEnabled); // Redundant 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.SaveString(SS_YAML_KEY_SERIALPORTNAME, GetSerialPortName()); } void CSuperSerialCard::LoadSnapshotDIPSW(YamlLoadHelper& yamlLoadHelper, std::string key, SSC_DIPSW& dipsw) { if (!yamlLoadHelper.GetSubMap(key)) throw std::string("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 slot, UINT version) { if (slot != 2) // fixme throw std::string("Card: wrong slot"); if (version != 1) throw std::string("Card: wrong version"); LoadSnapshotDIPSW(yamlLoadHelper, SS_YAML_KEY_DIPSWDEFAULT, m_DIPSWDefault); LoadSnapshotDIPSW(yamlLoadHelper, SS_YAML_KEY_DIPSWCURRENT, m_DIPSWCurrent); m_uBaudRate = yamlLoadHelper.LoadUint(SS_YAML_KEY_BAUDRATE); m_uStopBits = yamlLoadHelper.LoadUint(SS_YAML_KEY_STOPBITS); m_uByteSize = yamlLoadHelper.LoadUint(SS_YAML_KEY_BYTESIZE); yamlLoadHelper.LoadUint(SS_YAML_KEY_PARITY); // Redundant: derived from uCommandByte in UpdateCommandReg() m_uControlByte = yamlLoadHelper.LoadUint(SS_YAML_KEY_CONTROL); UINT uCommandByte = yamlLoadHelper.LoadUint(SS_YAML_KEY_COMMAND); yamlLoadHelper.LoadUint(SS_YAML_KEY_INACTIVITY); // Obsolete (so just consume) yamlLoadHelper.LoadBool(SS_YAML_KEY_TXIRQENABLED); // Redundant: derived from uCommandByte in UpdateCommandReg() yamlLoadHelper.LoadBool(SS_YAML_KEY_RXIRQENABLED); // Redundant: derived from uCommandByte in UpdateCommandReg() m_vbTxIrqPending = yamlLoadHelper.LoadBool(SS_YAML_KEY_TXIRQPENDING); m_vbRxIrqPending = yamlLoadHelper.LoadBool(SS_YAML_KEY_RXIRQPENDING); m_vbTxEmpty = yamlLoadHelper.LoadBool(SS_YAML_KEY_WRITTENTX); UpdateCommandReg(uCommandByte); std::string serialPortName = yamlLoadHelper.LoadString(SS_YAML_KEY_SERIALPORTNAME); SetSerialPortName(serialPortName.c_str()); return true; }