/* 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 */ // TO DO: // . Enable & test Tx IRQ // . DIP switch read values // // Refs: // (1) "Super Serial Card (SSC) Memory Locations for Programmers" - Aaron Heiss // (2) SSC recv IRQ example: http://www.wright.edu/~john.matthews/ssc.html#lst - John B. Matthews, 5/13/87 // (3) WaitCommEvent, etc: http://mail.python.org/pipermail/python-list/2002-November/131437.html // (4) SY6551 info: http://www.axess.com/twilight/sock/rs232pak.html // #include "StdAfx.h" #include "AppleWin.h" #include "CPU.h" #include "Disk.h" // DiskIsSpinning() #include "Frame.h" #include "Memory.h" #include "SerialComms.h" #include "YamlHelper.h" #include "..\resource\resource.h" //#define SUPPORT_MODEM #define TCP_SERIAL_PORT 1977 // Default: 19200-8-N-1 // Maybe a better default is: 9600-7-N-1 (for HyperTrm) SSC_DIPSW CSuperSerialCard::m_DIPSWDefault = { // DIPSW1: CBR_19200, FWMODE_CIC, // DIPSW2: ONESTOPBIT, 8, // ByteSize NOPARITY, true, // LF false, // INT }; //=========================================================================== CSuperSerialCard::CSuperSerialCard() : m_aySerialPortChoices(NULL), m_uTCPChoiceItemIdx(0), m_uSlot(0), m_bCfgSupportDTR(false), // GH#386 - don't support by default until we have confirmed it works m_bCfgInvertDTR(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_dwCommInactivity = 0; m_hCommThread = NULL; for (UINT i=0; i [0,1,2,3] _ASSERT(bmByteSize <= 3); UINT StopBit; if ( ((m_uByteSize == 8) && (m_uParity != NOPARITY)) || ( m_uStopBits != ONESTOPBIT ) ) StopBit = 1; else StopBit = 0; return (StopBit<<7) | (bmByteSize<<5) | (CLK<<4) | BaudRateToIndex(m_uBaudRate); } 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); return BaudRateToIndex(CBR_9600); } //=========================================================================== void CSuperSerialCard::UpdateCommState() { if (m_hCommHandle == INVALID_HANDLE_VALUE) return; DCB dcb; ZeroMemory(&dcb,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; dcb.fDtrControl = m_uDTR; SetCommState(m_hCommHandle,&dcb); } //=========================================================================== BOOL CSuperSerialCard::CheckComm() { m_dwCommInactivity = 0; // check for COM or TCP socket handle, and setup if invalid if (IsActive()) return true; if (m_dwSerialPortItem == m_uTCPChoiceItemIdx) { // init Winsock 1.1 (for Win95, otherwise could use 2.2) WSADATA wsaData; if (WSAStartup(MAKEWORD(1, 1), &wsaData) == 0) // or (2, 2) for Winsock 2.2 { if (wsaData.wVersion != 0x0101) // or 0x0202 for Winsock 2.2 { 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; 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) { m_hCommListenSocket = INVALID_SOCKET; WSACleanup(); return FALSE; } // bound, so listen if (listen(m_hCommListenSocket, 1) == SOCKET_ERROR) { m_hCommListenSocket = INVALID_SOCKET; WSACleanup(); return FALSE; } // now send async events to our app's message handler if (WSAAsyncSelect( /* SOCKET s */ m_hCommListenSocket, /* HWND hWnd */ g_hFrameWindow, /* unsigned int wMsg */ WM_USER_TCP_SERIAL, /* long lEvent */ (FD_ACCEPT | FD_CONNECT | FD_READ | FD_CLOSE)) != 0) { m_hCommListenSocket = INVALID_SOCKET; WSACleanup(); return FALSE; } } } else if (m_dwSerialPortItem) { _ASSERT(m_dwSerialPortItem < m_vecSerialPortsItems.size()-1); // size()-1 is TCP item TCHAR portname[SIZEOF_SERIALCHOICE_ITEM]; wsprintf(portname, TEXT("COM%u"), m_vecSerialPortsItems[m_dwSerialPortItem]); m_hCommHandle = CreateFile(portname, 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) { //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; ZeroMemory(&ct,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; m_dwCommInactivity = 0; } //=========================================================================== void CSuperSerialCard::CommTcpSerialCleanup() { if (m_hCommListenSocket != INVALID_SOCKET) { WSAAsyncSelect(m_hCommListenSocket, g_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 ); } } //=========================================================================== 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); } } } //=========================================================================== BYTE __stdcall CSuperSerialCard::SSC_IORead(WORD PC, WORD uAddr, BYTE bWrite, BYTE uValue, ULONG nCyclesLeft) { UINT uSlot = ((uAddr & 0xff) >> 4) - 8; CSuperSerialCard* pSSC = (CSuperSerialCard*) MemGetSlotParameters(uSlot); switch (uAddr & 0xf) { case 0x0: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0x1: return pSSC->CommDipSw(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0x2: return pSSC->CommDipSw(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0x3: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0x4: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0x5: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0x6: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0x7: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0x8: return pSSC->CommReceive(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0x9: return pSSC->CommStatus(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0xA: return pSSC->CommCommand(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0xB: return pSSC->CommControl(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0xC: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0xD: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0xE: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0xF: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); } return 0; } BYTE __stdcall CSuperSerialCard::SSC_IOWrite(WORD PC, WORD uAddr, BYTE bWrite, BYTE uValue, ULONG nCyclesLeft) { UINT uSlot = ((uAddr & 0xff) >> 4) - 8; CSuperSerialCard* pSSC = (CSuperSerialCard*) MemGetSlotParameters(uSlot); switch (uAddr & 0xf) { case 0x0: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0x1: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0x2: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0x3: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0x4: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0x5: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0x6: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0x7: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0x8: return pSSC->CommTransmit(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0x9: return pSSC->CommStatus(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0xA: return pSSC->CommCommand(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0xB: return pSSC->CommControl(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0xC: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0xD: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0xE: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); case 0xF: return IO_Null(PC, uAddr, bWrite, uValue, nCyclesLeft); } return 0; } //=========================================================================== // EG. 0x09 = Enable IRQ, No parity [Ref.2] BYTE __stdcall CSuperSerialCard::CommCommand(WORD, WORD, BYTE write, BYTE value, ULONG) { if (!CheckComm()) return 0; if (write && (value != m_uCommandByte)) { m_uCommandByte = value; // UPDATE THE PARITY if (m_uCommandByte & 0x20) { switch (m_uCommandByte & 0xC0) { case 0x00 : m_uParity = ODDPARITY; break; case 0x40 : m_uParity = EVENPARITY; break; case 0x80 : m_uParity = MARKPARITY; break; case 0xC0 : m_uParity = SPACEPARITY; break; } } else { m_uParity = NOPARITY; } if (m_uCommandByte & 0x10) // Receiver mode echo [0=no echo, 1=echo] { } switch (m_uCommandByte & 0x0C) // transmitter interrupt control { // Note: the RTS signal must be set 'low' in order to receive any // incoming data from the serial device case 0<<2: // set RTS high and transmit no interrupts m_bTxIrqEnabled = false; break; case 1<<2: // set RTS low and transmit interrupts m_bTxIrqEnabled = true; break; case 2<<2: // set RTS low and transmit no interrupts m_bTxIrqEnabled = false; break; case 3<<2: // set RTS low and transmit break signals instead of interrupts m_bTxIrqEnabled = false; break; } // interrupt request disable [0=enable receiver interrupts] - NOTE: SSC docs get this wrong! m_bRxIrqEnabled = ((m_uCommandByte & 0x02) == 0); if (m_bCfgSupportDTR) // GH#386 { // Legacy comment - inaccurate? (see docs\SSC Memory Locations for Programmers.txt) // . Note that, although the DTR is generally not used in the SSC (it may actually not be connected!), // it must be set to 'low' in order for the 6551 to function correctly. // Data Terminal Ready (DTR) setting [0=set DTR high (indicates 'not ready')] const bool bDTR_Ready = (m_uCommandByte & 0x01) ? true : false; if (bDTR_Ready) m_uDTR = !m_bCfgInvertDTR ? DTR_CONTROL_ENABLE : DTR_CONTROL_DISABLE; else m_uDTR = !m_bCfgInvertDTR ? DTR_CONTROL_DISABLE : DTR_CONTROL_ENABLE; } 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; } //=========================================================================== static UINT g_uDbgTotalSSCRx = 0; BYTE __stdcall CSuperSerialCard::CommReceive(WORD, WORD, BYTE, BYTE, ULONG) { if (!CheckComm()) return 0; BYTE result = 0; if (!m_qTcpSerialBuffer.empty()) { result = m_qTcpSerialBuffer.front(); m_qTcpSerialBuffer.pop_front(); } 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); g_uDbgTotalSSCRx++; } return result; } //=========================================================================== 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); } send(m_hCommAcceptSocket, (const char*)&data, 1, 0); m_bWrittenTx = true; // Transmit done } else if (m_hCommHandle != INVALID_HANDLE_VALUE) { DWORD uBytesWritten; WriteFile(m_hCommHandle, &value, 1, &uBytesWritten, &m_o); m_bWrittenTx = true; // Transmit done } // TO DO: // 1) Use CommThread determine when transmit is complete // 2) OR do this: //if (m_bTxIrqEnabled) // CpuIrqAssert(IS_SSC); return 0; } //=========================================================================== // 6551 ACIA Status Register ($C089+s0) // ------------------------------------ // Bit Value Meaning // 0 1 Parity error // 1 1 Framing error // 2 1 Overrun error // 3 1 Receive register full // 4 1 Transmit register empty // 5 0 Data Carrier Detect (DCD) true [0=DCD low (detected), 1=DCD high (not detected)] // 6 0 Data Set Ready (DSR) true [0=DSR low (ready), 1=DSR high (not ready)] // 7 1 Interrupt (IRQ) true (cleared by reading status reg [Ref.4]) enum { ST_PARITY_ERR = 1<<0, ST_FRAMING_ERR = 1<<1, ST_OVERRUN_ERR = 1<<2, ST_RX_FULL = 1<<3, ST_TX_EMPTY = 1<<4, ST_DCD = 1<<5, ST_DSR = 1<<6, ST_IRQ = 1<<7 }; BYTE __stdcall CSuperSerialCard::CommStatus(WORD, WORD, BYTE, BYTE, ULONG) { if (!CheckComm()) return ST_DSR | ST_DCD | ST_TX_EMPTY; #ifdef SUPPORT_MODEM DWORD modemstatus = 0; GetCommModemStatus(m_hCommHandle,&modemstatus); // Returns 0x30 = MS_DSR_ON|MS_CTS_ON #endif // // TO DO - ST_TX_EMPTY: // . IRQs enabled : set after WaitCommEvent has signaled that TX has completed // . IRQs disabled : always set it [Currently done] // 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(); } bool bIRQ = false; if (m_bTxIrqEnabled && m_bWrittenTx) { bIRQ = true; } if (m_bRxIrqEnabled) { bIRQ = m_vbRxIrqPending; m_vbRxIrqPending = false; // Ensure 2 reads of STATUS reg only return ST_IRQ for first read } m_bWrittenTx = false; // Read status reg always clears IRQ // BYTE uStatus = ST_TX_EMPTY | ((!bComSerialBufferEmpty || !m_qTcpSerialBuffer.empty()) ? ST_RX_FULL : 0x00) #ifdef SUPPORT_MODEM | ((modemstatus & MS_RLSD_ON) ? 0x00 : ST_DCD) // Need 0x00 to allow ZLink to start up | ((modemstatus & MS_DSR_ON) ? 0x00 : ST_DSR) #endif | (bIRQ ? ST_IRQ : 0x00); if (m_hCommHandle != INVALID_HANDLE_VALUE) { LeaveCriticalSection(&m_CriticalSection); } CpuIrqDeassert(IS_SSC); return uStatus; } //=========================================================================== 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 manual, pg23/24 BYTE INT = m_DIPSWCurrent.uStopBits == TWOSTOPBITS ? 1 : 0; // SW2-1 (Stop bits: 1-ON(0); 2-OFF(1)) BYTE DSR = 0; // Always zero BYTE DCD = m_DIPSWCurrent.uByteSize == 7 ? 1 : 0; // SW2-2 (Data bits: 8-ON(0); 7-OFF(1)) BYTE TDR = 0; // Always zero // SW2-3 (Parity: odd-ON(0); even-OFF(1)) // SW2-4 (Parity: none-ON(0); SW2-3-OFF(1)) BYTE RDR,OVR; switch (m_DIPSWCurrent.uParity) { case ODDPARITY: RDR = 0; OVR = 1; break; case EVENPARITY: RDR = 1; OVR = 1; break; default: _ASSERT(0); case NOPARITY: RDR = 0; OVR = 0; break; } BYTE FE = m_DIPSWCurrent.bLinefeed ? 1 : 0; // SW2-5 (LF: yes-ON(0); no-OFF(1)) BYTE PE = m_DIPSWCurrent.bInterrupts ? 1 : 0; // SW2-6 (Interrupts: yes-ON(0); no-OFF(1)) sw = (INT<<7) | (DSR<<6) | (DCD<<5) | (TDR<<4) | (RDR<<3) | (OVR<<2) | (FE<<1) | (PE<<0); 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" } //=========================================================================== void CSuperSerialCard::CommUpdate(DWORD totalcycles) { if (!IsActive()) return; if ((m_dwCommInactivity += totalcycles) > 1000000) { static DWORD lastcheck = 0; if ((m_dwCommInactivity > 2000000) || (m_dwCommInactivity-lastcheck > 99950)) { #ifdef SUPPORT_MODEM DWORD modemstatus = 0; GetCommModemStatus(m_hCommHandle,&modemstatus); if ((modemstatus & MS_RLSD_ON) || DiskIsSpinning()) m_dwCommInactivity = 0; #else if (DiskIsSpinning()) m_dwCommInactivity = 0; #endif } //if (m_dwCommInactivity > 2000000) // CloseComm(); } } //=========================================================================== // 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) //{ // if (m_bTxIrqEnabled) // { // m_vbTxIrqPending = true; // CpuIrqAssert(IS_SSC); // } //} } 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) 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) { if (dwErrors & CE_RXOVER) sprintf(szDbg, "CommThread: Err=CE_RXOVER (0x%08X): InQueue=0x%08X\n", dwErrors, Stat.cbInQue); else sprintf(szDbg, "CommThread: Err=Other (0x%08X): InQueue=0x%08X, OutQueue=0x%08X\n", dwErrors, Stat.cbInQue, Stat.cbOutQue); OutputDebugString(szDbg); if (g_fh) fprintf(g_fh, "%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) { 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); InitializeCriticalSection(&m_CriticalSection); } 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_uByteSize = bytesize; m_uCommandByte = commandbyte; m_dwCommInactivity = comminactivity; m_uControlByte = controlbyte; m_uParity = parity; // memcpy(m_RecvBuffer, pSS->recvbuffer, uRecvBufferSize); // m_vRecvBytes = recvbytes; m_uStopBits = stopbits; } //=========================================================================== #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); yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_CONTROL, m_uControlByte); yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_COMMAND, m_uCommandByte); yamlSaveHelper.SaveUint(SS_YAML_KEY_INACTIVITY, m_dwCommInactivity); yamlSaveHelper.SaveBool(SS_YAML_KEY_TXIRQENABLED, m_bTxIrqEnabled); yamlSaveHelper.SaveBool(SS_YAML_KEY_RXIRQENABLED, m_bRxIrqEnabled); yamlSaveHelper.SaveBool(SS_YAML_KEY_TXIRQPENDING, m_vbTxIrqPending); yamlSaveHelper.SaveBool(SS_YAML_KEY_RXIRQPENDING, m_vbRxIrqPending); yamlSaveHelper.SaveBool(SS_YAML_KEY_WRITTENTX, m_bWrittenTx); 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); m_uParity = yamlLoadHelper.LoadUint(SS_YAML_KEY_PARITY); m_uControlByte = yamlLoadHelper.LoadUint(SS_YAML_KEY_CONTROL); m_uCommandByte = yamlLoadHelper.LoadUint(SS_YAML_KEY_COMMAND); m_dwCommInactivity = yamlLoadHelper.LoadUint(SS_YAML_KEY_INACTIVITY); m_bTxIrqEnabled = yamlLoadHelper.LoadBool(SS_YAML_KEY_TXIRQENABLED); m_bRxIrqEnabled = yamlLoadHelper.LoadBool(SS_YAML_KEY_RXIRQENABLED); m_vbTxIrqPending = yamlLoadHelper.LoadBool(SS_YAML_KEY_TXIRQPENDING); m_vbRxIrqPending = yamlLoadHelper.LoadBool(SS_YAML_KEY_RXIRQPENDING); m_bWrittenTx = yamlLoadHelper.LoadBool(SS_YAML_KEY_WRITTENTX); std::string serialPortName = yamlLoadHelper.LoadString(SS_YAML_KEY_SERIALPORTNAME); SetSerialPortName(serialPortName.c_str()); return true; }