#include "ui.h" #include "iop_expander.h" #include "cray_mainframe.h" #include #include #include #include #include "commands.h" ////////////////////////////////////////////////////////////////////////////////////////// // IopExpTape_c ////////////////////////////////////////////////////////////////////////////////////////// IopExpTape_c::IopExpTape_c(const Configuration_c &aConfig, IopChannelExp_c &aParent): mParent(aParent), mBusy(false), mDone(true), mRegA(0), mRegAStat(0), mRegB(0), mRegC(0), mInterruptActive(false), mInterruptEnabled(false), mLogger(aConfig, "MT"), mCurrentTapeImage(-1), mChannelIdx(aConfig.get("ChannelIdx")), mReads(0), mWrites(0), mIsReadOnly(aConfig.get("ReadOnly",false)), mTapeImage(mLogger, aConfig) { mLogger.SetParent(mParent.GetLogger()); mTapeImage.open(aConfig.get("Tape")); } uint16_t IopExpTape_c::GetStatus(uint16_t aDeviceAddr) { uint16_t RetVal = 0; RetVal |= Status_DmaEnabled; if (mInterruptEnabled) RetVal |= Status_IntEnabled; if (mInterruptActive) RetVal |= Status_Int; if (mBusy) RetVal |= Status_DeviceBusy; if (mDone) RetVal |= Status_DeviceDone; return RetVal; } void IopExpTape_c::SetIntEnabled(uint16_t aDeviceAddr, bool aInterruptEnabled) { mInterruptEnabled = aInterruptEnabled; mParent.IntStatusChanged(); } void IopExpTape_c::SetIntActive(bool aActive) { mInterruptActive = aActive; mParent.IntStatusChanged(); } // Status bits - returned in mRegA // 0x8000 - ???? Tested first sometimes for 0x5422 mask tests, but not always // 0x4000 - DATA LATE // 0x2000 - REWINDING (if this bit is set when XTAPEC is entered, it waits until it clears) // 0x1000 - ???? // 0x0800 - --------------- // 0x0400 - DATA PARITY ERROR // 0x0200 - UNKOWN ERROR // 0x0100 - END OF FILE // 0x0080 - LOAD POINT DETECTED // 0x0040 - --------------- // 0x0020 - BAD TYPE // 0x0010 - --------------- // 0x0008 - --------------- // 0x0004 - NO WRITE RING - set to 1 for read-only // 0x0002 - WORD COUNT ERROR // 0x0001 - READY uint16_t IopExpTape_c::GetRegA(uint16_t aDeviceAddr) { uint16_t RetVal = mRegAStat; if (mIsReadOnly) RetVal |= Status_ReadOnly; switch (mTapeImage.GetState()) { case TapFile_c::State_e::BeginningOfTape: RetVal |= Status_BeginOfTape; break; case TapFile_c::State_e::EndOfTape: RetVal |= Status_EndOfTape; break; case TapFile_c::State_e::EndOfFile: RetVal |= Status_EndOfFile; break; // TODO: Should we set the error bit here? default: break; } mLogger << setloglevel(LogLevel_IoActivity) << "returning status with RegA: " << HexPrinter(RetVal, 4) << std::endl; return RetVal; } void IopExpTape_c::FinalizeControl(uint16_t aStatus) { mDone = true; mBusy = false; mRegAStat |= aStatus; SetIntActive(true); } // NOTE: rewind closes the tape so the tape image can be manipulated outside the simulator. void IopExpTape_c::Control(uint16_t aDeviceAddr, uint16_t aControl) { CRAY_ASSERT((aControl & ~(Control_Start | Control_Clear)) == 0); if ((aControl & Control_Clear) != 0) { mLogger << setloglevel(LogLevel_IoActivity) << "'CLEAR' recevied. Clearing pending interrupt" << std::endl; SetIntActive(false); } if ((aControl & Control_Start) != 0) { mRegAStat = 0; mLogger << setloglevel(LogLevel_IoActivity) << "'START' recevied with RegA: " << HexPrinter(mRegA,4) << " RegB: " << HexPrinter(mRegB,4) << " RegC: " << HexPrinter(mRegC,4) << std::endl; switch ((mRegA >> 3) & 7) { // 52 - Erase - RegB: not set RegC: not set // TODO: implement erase command - note: it's never issued by IOS it seems // // Read and space operations stop if they encounter an EOF mark. To get to the beginning of the next file, you have to issue another space forward with 1 as the SpaceSize // Operation 8 (File search backward) and probably all other backward operations stop when they encounter the LOAD POINT, that is the front of the tape. They set the LOAD_POINT_DETECT status bit // // The READY bit is set to indicate that the device is finished processing the current command // EOF is a special mark on the tape, that you can position to. The read of a block that stops at the last byte of the file does *NOT* set the EOF bit. // When you SPACE or READ, the head will stop on an EOF // To position to the start of a file, issue a SPACE_FORWARD '1' after the SPACE or READ stopped on an EOF // NOTE: Space commands count in 4096-byte (record) increments. Read and write count in 16-bit word increments. // On the real tape reads would stop at the end of a record, and each write command would write a record on the tape // This behavior is not simulated at the moment: record size is fixed at 4096 bytes, except for the last partial block of a file. // Since this is the only operation the IOS is using, it is fine for now at least. // 0 - read forward - RegB: set to destination buffer RegC: set to TransferSize*-1 case Command_ReadForward: { mTapeImage.ReOpen(); int WordsToRead = -int16_t(mRegC); CRAY_ASSERT(WordsToRead > 0); mLogger << setloglevel(LogLevel_IoActivity) << "Loading " << DecPrinter(WordsToRead) << " words to address " << HexPrinter(mRegB) << " from tape file " << mTapeImage.GetFileName() << " offset " << HexPrinter(mTapeImage.tellg()) << std::endl; mReads += WordsToRead / 2048; std::vector TmpBuf = mTapeImage.Read(WordsToRead * sizeof(IopInt_t)); size_t BytesRead = TmpBuf.size(); if (BytesRead % 2 != 0) TmpBuf.resize(BytesRead + 1); // Swap all words and copy to target buffer for(size_t i=0;i 0); mLogger << setloglevel(LogLevel_IoActivity) << "Spacing " << DecPrinter(RecordsToSkip) << " records forward in tape file " << mTapeImage.GetFileName() << " offset " << HexPrinter(mTapeImage.tellg()) << std::endl; for (int i = 0; i < RecordsToSkip; ++i) { mTapeImage.SeekToNextRecord(); if (mTapeImage.GetState() == TapFile_c::State_e::EndOfTape) { mLogger << setloglevel(LogLevel_IoActivity) << "spacing cut short by EOT condition after " << DecPrinter(i) << " records." << std::endl; FinalizeControl(Status_Ready | Status_Error); break; } if (mTapeImage.GetState() == TapFile_c::State_e::EndOfFile) { mLogger << setloglevel(LogLevel_IoActivity) << "spacing cut short by EOF condition after " << DecPrinter(i) << " records." << std::endl; FinalizeControl(Status_Ready | Status_Error); break; } // TODO: Should we set the error bit here? ++mRegC; } if (mRegC == 0) FinalizeControl(Status_Ready); } break; // 32 - space backward - RegB: not set RegC: set to SpaceSize*-1 case Command_SpaceBackward: { mTapeImage.ReOpen(); int RecordsToSkip = -int16_t(mRegC); CRAY_ASSERT(RecordsToSkip > 0); mLogger << setloglevel(LogLevel_IoActivity) << "Spacing " << DecPrinter(RecordsToSkip) << " records backward in tape file " << mTapeImage.GetFileName() << " offset " << HexPrinter(mTapeImage.tellg()) << std::endl; for (int i = 0; i < RecordsToSkip; ++i) { mTapeImage.SeekToPrevRecord(); if (mTapeImage.GetState() == TapFile_c::State_e::EndOfTape) { mLogger << setloglevel(LogLevel_IoActivity) << "spacing cut short by EOT condition after " << DecPrinter(i) << " records." << std::endl; FinalizeControl(Status_Ready | Status_Error); break; } if (mTapeImage.GetState() == TapFile_c::State_e::EndOfFile) { mLogger << setloglevel(LogLevel_IoActivity) << "spacing cut short by EOF condition after " << DecPrinter(i) << " records." << std::endl; FinalizeControl(Status_Ready | Status_Error); break; } // TODO: Should we set the error bit here? ++mRegC; } if (mRegC == 0) FinalizeControl(Status_Ready); } break; // 40 - write forward - RegB: set to destination buffer RegC: set to TransferSize*-1 case Command_WriteForward : { CRAY_ASSERT(!mIsReadOnly); mTapeImage.ReOpen(); int WordsToWrite = -int16_t(mRegC); CRAY_ASSERT(WordsToWrite > 0); mLogger << setloglevel(LogLevel_IoActivity) << "Writing " << DecPrinter(WordsToWrite) << " words from address " << HexPrinter(mRegB) << " to tape file " << mTapeImage.GetFileName() << " offset " << HexPrinter(mTapeImage.tellg()) << std::endl; mWrites += WordsToWrite / 2048; std::vector TmpBuf(WordsToWrite*sizeof(IopInt_t)); // Swap all words and copy to target buffer for(size_t i=0;i> 8) & 0xff; TmpBuf[i+1] = (Data >> 0) & 0xff; ++mRegB; ++mRegC; } mTapeImage.WriteRecord(TmpBuf); FinalizeControl(Status_Ready); } break; // 48 - write EOF forward - RegB: not set RegC: not set case Command_WriteEOF: { CRAY_ASSERT(!mIsReadOnly); mTapeImage.ReOpen(); mLogger << setloglevel(LogLevel_IoActivity) << "Writing EOF to tape file " << mTapeImage.GetFileName() << " offset " << HexPrinter(mTapeImage.tellg()) << std::endl; mTapeImage.WriteEndOfFile(); FinalizeControl(Status_Ready); } break; case Command_Erase: { mLogger << setloglevel(LogLevel_Error) << "ERASE command to tape file " << mTapeImage.GetFileName() << " offset " << HexPrinter(mTapeImage.tellg()) << " command not implemented" << std::endl; CRAY_ASSERT(false); } break; default: CRAY_ASSERT(false); } } } void IopExpTape_c::Tick() { } void IopExpTape_c::MountTape(const std::string &aTapeFileName) { if (mTapeImage.is_open()) throw Generic_x("Can't unmount tape while it's in use."); mTapeImage.open(aTapeFileName); mTapeImage.close(); } void IopExpTape_c::GetStatus(StatusReport_c &aStatus, PeripheralType_e aFilter, bool aLongFormat) const { aStatus.put(aLongFormat ? "Reads" : "R", mReads); aStatus.put(aLongFormat ? "Write" : "W", mWrites); if (mTapeImage.is_open()) aStatus.put(aLongFormat ? "Position" : "Pos", const_cast(&mTapeImage)->tellg()); else aStatus.put(aLongFormat ? "Position" : "Pos", "-"); aStatus.put(aLongFormat ? "File Name" : "FName", mTapeImage.GetFileName()); } void IopExpTape_c::RegisterCommands(CommandHooks_t &aHooks) { class CmdMount_c: public CmdFactoryBase_i { public: explicit CmdMount_c(IopExpTape_c &aParent) : mParent(aParent) {} virtual bool ParseAndExec(TokenStream_t::const_iterator aBegin, TokenStream_t::const_iterator aEnd) override { bool DoHelp = false; if (aBegin->mValue == "help") { DoHelp = true; ++aBegin; if (aBegin == aEnd) return false; } if (aBegin->mValue != "mount") return false; ++aBegin; if (aBegin == aEnd) return false; if (aBegin->mValue != mParent.GetName()) return false; if (DoHelp) { std::cout << "mount " << mParent.GetName() << " " << std::endl; std::cout << " Mounts a new virtual tape on the tape drive" << std::endl; return true; } else { ++aBegin; if (aBegin == aEnd) return false; std::string FileName = aBegin->mValue; ++aBegin; if (aBegin != aEnd && aBegin->mTokenId != TokenTypes_e::EndOfLine) return false; // TODO: add warning about existing file try { mParent.MountTape(FileName); std::cout << "New virtual tape " << FileName << " mounted" << std::endl; } catch (std::exception &Ex) { std::cout << "Mount failed" << std::endl; std::cout << Ex.what() << std::endl; } return true; } } virtual std::string GetCommandName() const override { return "mount"; } virtual std::string GetDeviceName() const override { return mParent.GetName(); } protected: IopExpTape_c &mParent; }; aHooks.emplace_back(std::make_unique(*this)); /* UIHook_s Hook; Hook.Callback = boost::bind(&IopExpTape_c::ExecUI, const_cast(this)); Hook.TriggerKey = 0x14; // ^T aHooks.emplace_back(Hook);*/ } ////////////////////////////////////////////////////////////////////////////////////////// // IopExpDummyDevice_c ////////////////////////////////////////////////////////////////////////////////////////// IopExpDummyDevice_c::IopExpDummyDevice_c(const Configuration_c &aConfig, IopChannelExp_c &aParent, const std::string &aLogName): mParent(aParent), mBusy(false), mDone(true), mRegA(0), mRegB(0), mRegC(0), mInterruptActive(false), mInterruptEnabled(false), mLogger(aConfig, aLogName.c_str()), mChannelIdx(aConfig.get("ChannelIdx")) { mLogger.SetParent(mParent.GetLogger()); } IopExpDummyDevice_c::IopExpDummyDevice_c(const Configuration_c &aConfig, class IopChannelExp_c &aParent, const std::string &aLogName, uint16_t aChannelIdx) : mParent(aParent), mBusy(false), mDone(true), mRegA(0), mRegB(0), mRegC(0), mInterruptActive(false), mInterruptEnabled(false), mLogger(aConfig, aLogName.c_str()), mChannelIdx(aChannelIdx) { mLogger.SetParent(mParent.GetLogger()); } void IopExpDummyDevice_c::SetIntEnabled(uint16_t aDeviceAddr, bool aInterruptEnabled) { mInterruptEnabled = aInterruptEnabled; mParent.IntStatusChanged(); } void IopExpDummyDevice_c::SetIntActive(bool aActive) { mInterruptActive = aActive; mParent.IntStatusChanged(); } uint16_t IopExpDummyDevice_c::GetStatus(uint16_t aDeviceAddr) { uint16_t RetVal = 0; RetVal |= Status_DmaEnabled; if (mInterruptEnabled) RetVal |= Status_IntEnabled; if (mInterruptActive) RetVal |= Status_Int; if (mBusy) RetVal |= Status_DeviceBusy; if (mDone) RetVal |= Status_DeviceDone; return RetVal; } void IopExpDummyDevice_c::Control(uint16_t aDeviceAddr, uint16_t aControl) { CRAY_ASSERT((aControl & ~(Control_Start | Control_Clear | Control_Pulse)) == 0); if ((aControl & Control_Clear) != 0) { mLogger << setloglevel(LogLevel_IoActivity) << "'CLEAR' recevied. Clearing pending interrupt" << std::endl; SetIntActive(false); } if ((aControl & Control_Start) != 0) { mLogger << setloglevel(LogLevel_IoActivity) << "'START' recevied with RegA: " << HexPrinter(mRegA,4) << " RegB: " << HexPrinter(mRegB,4) << " RegC: " << HexPrinter(mRegC,4) << std::endl; } if ((aControl & Control_Pulse) != 0) { mLogger << setloglevel(LogLevel_IoActivity) << "'PULSE' recevied with RegA: " << HexPrinter(mRegA,4) << " RegB: " << HexPrinter(mRegB,4) << " RegC: " << HexPrinter(mRegC,4) << std::endl; } } ////////////////////////////////////////////////////////////////////////////////////////// // IopExpPrinterDevice_c ////////////////////////////////////////////////////////////////////////////////////////// IopExpPrinterDevice_c::IopExpPrinterDevice_c(const Configuration_c &aConfig, IopChannelExp_c &aParent): mParent(aParent), mBusy(false), mDone(true), mRegA(0), mRegB(0), mRegC(0), mInterruptActive(false), mInterruptEnabled(false), mLogger(aConfig, aConfig.get("Name").c_str()), mStatus(0), mChannelIdx(aConfig.get("ChannelIdx")), mInGraphicsMode(false), mWrites(0) { mLogger.SetParent(mParent.GetLogger()); mPrintFileName = aConfig.get("PrintFileName"); } void IopExpPrinterDevice_c::GetStatus(StatusReport_c &aStatus, PeripheralType_e aFilter, bool aLongFormat) const { aStatus.put(aLongFormat ? "Writes" : "W", mWrites); aStatus.put(aLongFormat ? "Mode" : "M", mInGraphicsMode ? "Gr" : "Txt"); aStatus.put(aLongFormat ? "File Name" : "FName", mPrintFileName); } void IopExpPrinterDevice_c::SetIntEnabled(uint16_t aDeviceAddr, bool aInterruptEnabled) { mInterruptEnabled = aInterruptEnabled; mParent.IntStatusChanged(); } void IopExpPrinterDevice_c::SetIntActive(bool aActive) { mInterruptActive = aActive; mParent.IntStatusChanged(); } uint16_t IopExpPrinterDevice_c::GetStatus(uint16_t aDeviceAddr) { uint16_t RetVal = 0; RetVal |= Status_DmaEnabled; if (mInterruptEnabled) RetVal |= Status_IntEnabled; if (mInterruptActive) RetVal |= Status_Int; if (mBusy) RetVal |= Status_DeviceBusy; if (mDone) RetVal |= Status_DeviceDone; // mLogger << setloglevel(LogLevel_IoActivity) << "returning status: " << OctPrinter(RetVal) << " with mInterruptEnabled: " << (mInterruptEnabled?"true":"false") << " mInterruptActive: " << (_InterruptActive?"true":"false") << std::endl; return RetVal; } void IopExpPrinterDevice_c::SetRegC(uint16_t aDeviceAddr, uint16_t aValue) { mRegC = aValue; mLogger << setloglevel(LogLevel_IoActivity) << "Recevied RegC: " << HexPrinter(mRegC,4) << std::endl; mBusy = false; mDone = true; SetIntActive(true); mPrintFile.open(mPrintFileName, std::ios::app); LogLine_c LogLine = mLogger << setloglevel(LogLevel_IoActivity); LogLine << "Printing: "; while (mRegB != 0) { IopInt_t Data = mParent.GetDma().ReadWord(mRegC); LogLine << " " << HexPrinter(Data); if (mInGraphicsMode) { for(int i=0;i<16;++i) { mPrintFile << ((Data & 0x8000) == 0?' ':'X'); Data <<= 1; } mWrites++; } else { mPrintFile << (char)(Data >> 8) << (char)(Data & 0xff); mWrites++; } ++mRegC; ++mRegB; } mPrintFile.close(); LogLine << std::endl; mStatus = StatusCode_Done; } uint16_t IopExpPrinterDevice_c::GetRegA(uint16_t aDeviceAddr) { mLogger << setloglevel(LogLevel_IoActivity) << "Returning RegA: " << HexPrinter(mStatus,4) << std::endl; return mStatus; } void IopExpPrinterDevice_c::Control(uint16_t aDeviceAddr, uint16_t aControl) { CRAY_ASSERT((aControl & ~(Control_Start | Control_Clear | Control_Pulse)) == 0); if ((aControl & Control_Clear) != 0) { mLogger << setloglevel(LogLevel_IoActivity) << "'CLEAR' recevied. Clearing pending interrupt" << std::endl; SetIntActive(false); } if ((aControl & Control_Start) != 0) { mLogger << setloglevel(LogLevel_IoActivity) << "'START' recevied with RegA: " << HexPrinter(mRegA,4) << " RegB: " << HexPrinter(mRegB,4) << " RegC: " << HexPrinter(mRegC,4) << std::endl; } if ((aControl & Control_Pulse) != 0) { mLogger << setloglevel(LogLevel_IoActivity) << "'PULSE' recevied with RegA: " << HexPrinter(mRegA,4) << " RegB: " << HexPrinter(mRegB,4) << " RegC: " << HexPrinter(mRegC,4) << std::endl; mDone = true; mBusy = false; switch (mRegA) { case CommandCode_ClearInt: mDone = true; mBusy = false; SetIntActive(false); mStatus = 0; break; case CommandCode_4: mDone = true; mBusy = false; mStatus = 0; mInGraphicsMode = false; break; case CommandCode_GraphicsMode: mDone = true; mBusy = false; mStatus = 0; mInGraphicsMode = true; break; case CommandCode_NewLine: mDone = true; SetIntActive(true); mPrintFile.open(mPrintFileName, std::ios::app); mPrintFile << std::endl; mPrintFile.close(); mStatus = StatusCode_Done; break; case CommandCode_NewPage: mDone = true; mBusy = false; SetIntActive(true); mPrintFile.open(mPrintFileName, std::ios::app); mPrintFile << '\f'; mPrintFile.close(); mStatus = StatusCode_Done; break; default: mDone = true; mBusy = false; mPrintFile.open(mPrintFileName, std::ios::app); mPrintFile << "------------------------- UNKOWN CODE " << mRegA << " --------------------------" << std::endl; mPrintFile.close(); mStatus = 0; break; } // mLogger << setloglevel(LogLevel_IoActivity) << "new status: mInterruptEnabled: " << (mInterruptEnabled?"true":"false") << " mInterruptActive: " << (_InterruptActive?"true":"false") << std::endl; } } ////////////////////////////////////////////////////////////////////////////////////////// // IopExpClockDevice_c ////////////////////////////////////////////////////////////////////////////////////////// // Hayes communication protocol: // Read Date // --> ATRD\x0d // <-- YYMMDD\x0d (each being one digit) // Read Time // --> ATRT\x0d // <-- HHMMSS\x0d (each being one digit) // Set Date // --> ATSDYYMMDD\x0d // <-- 0\x0d // Set Time // --> ATSTHHMMSS\x0d // <-- 0\x0d // ??? // --> ATLC\x0d // <-- 0\x0d // ??? // --> ATVD\x0d // <-- 0\x0d // ??? // --> ATVT\x0d // <-- 0\x0d // Each byte sent to the clock in RegA with a 'START' pulse. The clock generates an interrupt on the reception of each byte // Each byte sent by the clock in RegA with an interrupt. // Actually, this is a Hayes Chronograph, attached to an RS232 port. Documentation: http://tinymicros.com/mediawiki/images/7/7a/Hayes_Chronograph.pdf IopExpClockDevice_c::IopExpClockDevice_c(const Configuration_c &aConfig, IopChannelExp_c &aParent): mParent(aParent), mBusy(false), mDone(false), mRegA(0), mRegB(0), mRegC(0), mInterruptEnabled(false), mLogger(aConfig, aConfig.get("Name").c_str()), mResponseTimer(0), mResponseTimeout(aConfig.get("ResponseTimeout", 100)), mPrimaryChannelIdx(aConfig.get("PrimaryChannelIdx")), mRequestChannelIdx(aConfig.get("RequestChannelIdx", mPrimaryChannelIdx+1)), mResponseChannelIdx(aConfig.get("ResponseChannelIdx", mPrimaryChannelIdx+0)), mYearOffset(aConfig.get("YearOffset", 20)) { for(size_t i=0;i 0) { mResponseTimer++; if (mResponseTimer > mResponseTimeout) { //mLogger << setloglevel(LogLevel_IoActivity) << "Sending interrupt on channel:" << DecPrinter(mResponseAddr) << std::endl; SetIntActive(mResponseChannelIdx, true); mResponseTimer = 0; } } } uint16_t IopExpClockDevice_c::GetChannelIdx(size_t aIdx) const { switch (aIdx) { case 0: return mPrimaryChannelIdx; case 1: return mRequestChannelIdx; // case 2: return mResponseChannelIdx; default: CRAY_ASSERT(false); } throw Generic_x("Unreachable code"); } ////////////////////////////////////////////////////////////////////////////////////////// // IopExpDiskAmpexDM980_c ////////////////////////////////////////////////////////////////////////////////////////// // Must be an Ampex DM980 from this: http://bitsavers.trailing-edge.com/pdf/dilog/30006-1_DQ202A_Sep81.pdf - though as far as format goes, it seems identical to the CDC SMD9762 // It's a 80MB drive, but formatted as a 64MB one (leaving the last sector on each track empty) // Protocol seems to be: // RegA 0x4000 // RegB RegC 5 // RegB RegC 1 // RegB RegC 2 // RegB RegC 3 // RegB RegC 8 - write sector; 0 - read sector // START // AMPEX commands (DOC) const uint16_t Ampex_Read = 000; const uint16_t Ampex_Write = 010; const uint16_t Ampex_Format = 020; const uint16_t Ampex_Checksum = 040; const uint16_t Ampex_StartSeek = 060; const uint16_t Ampex_SelectDrive = 070; const uint16_t Ampex_AttemptCorrection = 0110; const uint16_t Ampex_ReturnToZero = 0120; // Starts format // AMPEX register steering codes (DOC) const uint16_t Ampex_RegSelHead = 1; const uint16_t Ampex_RegSelSector = 2; const uint16_t Ampex_RegSelCount = 3; const uint16_t Ampex_RegSelDrive = 4; const uint16_t Ampex_RegSelCylinder = 5; // AMPEX option codes (DOA) const uint16_t Ampex_OptionNoSeek = 040000; const uint16_t Ampex_OptionEnableRetry = 0100000; IopExpDiskAmpexDM980_c::IopExpDiskAmpexDM980_c(const Configuration_c &aConfig, IopChannelExp_c &aParent): mParent(aParent), mBusy(false), mDone(true), mRegA(0), mRegB(0), mRegC(0), mInterruptActive(false), mInterruptEnabled(false), mLogger(aConfig, "DK"), mHead(0), mStartSector(0), mSectorCnt(0), mTrack(0), mChannelIdx(aConfig.get("ChannelIdx")), mReads(0), mWrites(0) { mLogger.SetParent(mParent.GetLogger()); mImageFileName = aConfig.get("ImageFileName"); mReadOnly = aConfig.get("ReadOnly",false); mNumHeads = aConfig.get("Heads", 5); if (mNumHeads == 0) throw InvalidParameter_x("Heads must be greater than 0"); mNumSectors = aConfig.get("Sectors", 35); if (mNumSectors == 0) throw InvalidParameter_x("Sectors must be greater than 0"); mNumTracks = aConfig.get("Tracks", 823); if (mNumTracks == 0) throw InvalidParameter_x("Tracks must be greater than 0"); mSectorSize = aConfig.get("SectorSize",512); if (mSectorSize == 0) throw InvalidParameter_x("SectorSize must be greater than 0"); } void IopExpDiskAmpexDM980_c::SetIntActive(bool aActive) { mInterruptActive = aActive; mParent.IntStatusChanged(); } void IopExpDiskAmpexDM980_c::SetIntEnabled(uint16_t aDeviceAddr, bool aInterruptEnabled) { mInterruptEnabled = aInterruptEnabled; mParent.IntStatusChanged(); } void IopExpDiskAmpexDM980_c::SetRegA(uint16_t aDeviceAddr, uint16_t aValue) { mRegA = aValue; mLogger << setloglevel(LogLevel_IoActivity) << "Recevied RegA: " << HexPrinter(mRegA,4) << std::endl; } void IopExpDiskAmpexDM980_c::SetRegB(uint16_t aDeviceAddr, uint16_t aValue) { mRegB = aValue; mLogger << setloglevel(LogLevel_IoActivity) << "Recevied RegB: " << HexPrinter(mRegB,4) << std::endl; } void IopExpDiskAmpexDM980_c::SetRegC(uint16_t aDeviceAddr, uint16_t aValue) { mLogger << setloglevel(LogLevel_IoActivity) << "Recevied RegC: " << HexPrinter(mRegC,4) << std::endl; mRegC = aValue; switch (aValue) { // These are register select operations - load RegB to the appropriate place case Ampex_RegSelCylinder: mTrack = mRegB; break; case Ampex_RegSelHead: mHead = mRegB; break; case Ampex_RegSelSector: mStartSector = mRegB; break; case Ampex_RegSelCount: mSectorCnt = mRegB; break; case Ampex_RegSelDrive: // Not implemented for now break; // These are actions - wait for the START to actually do anything about it case Ampex_Read: case Ampex_Write: case Ampex_Format: case Ampex_Checksum: case Ampex_StartSeek: case Ampex_SelectDrive: case Ampex_AttemptCorrection: case Ampex_ReturnToZero: break; default: mLogger << setloglevel(LogLevel_Error) << "ERROR: unknown mRegC value: " << HexPrinter(mRegC,4) << std::endl; // CRAY_ASSERT(false); break; } } uint16_t IopExpDiskAmpexDM980_c::GetStatus(uint16_t aDeviceAddr) { uint16_t RetVal = 0; RetVal |= Status_DmaEnabled; if (mInterruptEnabled) RetVal |= Status_IntEnabled; if (mInterruptActive) RetVal |= Status_Int; if (mBusy) RetVal |= Status_DeviceBusy; if (mDone) RetVal |= Status_DeviceDone; return RetVal; } void IopExpDiskAmpexDM980_c::Create() { if (boost::filesystem::exists(mImageFileName)) return; std::fstream File(mImageFileName, std::ios::out | std::ios::binary); std::vector Sector(mSectorSize); for (size_t i = 0; i < mNumSectors*mNumHeads*mNumTracks; ++i) File.write(&(Sector[0]), mSectorSize); CRAY_ASSERT(File.good()); } void IopExpDiskAmpexDM980_c::Control(uint16_t aDeviceAddr, uint16_t aControl) { CRAY_ASSERT((aControl & ~(Control_Start | Control_Clear)) == 0); if ((aControl & Control_Clear) != 0) { mLogger << setloglevel(LogLevel_IoActivity) << "'CLEAR' recevied. Clearing pending interrupt" << std::endl; SetIntActive(false); } if ((aControl & Control_Start) != 0) { mLogger << setloglevel(LogLevel_IoActivity) << "'START' recevied with RegA: " << HexPrinter(mRegA,4) << " RegB: " << HexPrinter(mRegB,4) << " RegC: " << HexPrinter(mRegC,4) << std::endl; size_t SectorIdx = mTrack * mNumSectors * mNumHeads + mHead * mNumSectors + mStartSector; switch (mRegC) { // AMPEX commands (DOC) case Ampex_Read: { // Read operation Create(); mReads += mSectorCnt * mSectorSize / 1024; LogLine_c LogLine = mLogger << setloglevel(LogLevel_IoActivity); LogLine << "Reading sector " << DecPrinter(SectorIdx) << " (SHT-L:" << DecPrinter(mStartSector) << "," << DecPrinter(mHead) << "," << DecPrinter(mTrack) << "," << DecPrinter(mSectorCnt) << ")" << " from file offset " << HexPrinter(SectorIdx * mSectorSize,8) << " to buffer at " << HexPrinter(mRegB,4) << std::endl; std::fstream File(mImageFileName, std::ios::in | std::ios::binary); File.seekg(SectorIdx * mSectorSize); CRAY_ASSERT(!File.fail()); uint16_t Data; LogLine_c TraceLine = mLogger << setloglevel(LogLevel_IoTrace); for(size_t i=0;i("ChannelIdx")), mReads(0), mWrites(0), mState(State_Seek) { mLogger.SetParent(mParent.GetLogger()); mImageFileName = aConfig.get("ImageFileName"); mReadOnly = aConfig.get("ReadOnly", false); mNumHeads = aConfig.get("Heads", 5); if (mNumHeads == 0) throw InvalidParameter_x("Heads must be greater than 0"); mNumSectors = aConfig.get("Sectors", 35); if (mNumSectors == 0) throw InvalidParameter_x("Sectors must be greater than 0"); mNumTracks = aConfig.get("Tracks", 823); if (mNumTracks == 0) throw InvalidParameter_x("Tracks must be greater than 0"); mSectorSize = aConfig.get("SectorSize", 4096); if (mSectorSize == 0) throw InvalidParameter_x("SectorSize must be greater than 0"); } void IopExpDiskCDC9448_96_c::SetIntActive(bool aActive) { mInterruptActive = aActive; mParent.IntStatusChanged(); } void IopExpDiskCDC9448_96_c::SetIntEnabled(uint16_t aDeviceAddr, bool aInterruptEnabled) { mInterruptEnabled = aInterruptEnabled; mParent.IntStatusChanged(); } void IopExpDiskCDC9448_96_c::SetRegA(uint16_t aDeviceAddr, uint16_t aValue) { mRegA = aValue; mLogger << setloglevel(LogLevel_IoActivity) << "Recevied RegA: " << HexPrinter(mRegA, 4) << std::endl; } void IopExpDiskCDC9448_96_c::SetRegB(uint16_t aDeviceAddr, uint16_t aValue) { mRegB = aValue; mLogger << setloglevel(LogLevel_IoActivity) << "Recevied RegB: " << HexPrinter(mRegB, 4) << std::endl; } void IopExpDiskCDC9448_96_c::SetRegC(uint16_t aDeviceAddr, uint16_t aValue) { mRegC = aValue; mLogger << setloglevel(LogLevel_IoActivity) << "Recevied RegC: " << HexPrinter(mRegC, 4) << std::endl; switch (mState) { case State_Seek: mTrack = mRegC; break; case State_GetHighHeadSector: mStartSector = (mRegC >> 5) & 32; mSectorCnt = mRegC & 32; mState = State_GetLowHeadSector; break; case State_GetLowHeadSector: mStartSector |= (mRegC >> 5) & 31; mSectorCnt |= (mRegC >> 0) & 31; mHead = (mRegC >> 10) & 31; break; } } uint16_t IopExpDiskCDC9448_96_c::GetStatus(uint16_t aDeviceAddr) { uint16_t RetVal = 0; RetVal |= Status_DmaEnabled; if (mInterruptEnabled) RetVal |= Status_IntEnabled; if (mInterruptActive) RetVal |= Status_Int; if (mBusy) RetVal |= Status_DeviceBusy; if (mDone) RetVal |= Status_DeviceDone; return RetVal; } void IopExpDiskCDC9448_96_c::Create() { if (boost::filesystem::exists(mImageFileName)) return; std::fstream File(mImageFileName, std::ios::out | std::ios::binary); std::vector Sector(mSectorSize); for (size_t i = 0; i < mNumSectors*mNumHeads*mNumTracks; ++i) File.write(&(Sector[0]), mSectorSize); CRAY_ASSERT(File.good()); } void IopExpDiskCDC9448_96_c::Control(uint16_t aDeviceAddr, uint16_t aControl) { if ((aControl & Control_Clear) != 0) { mLogger << setloglevel(LogLevel_IoActivity) << "'CLEAR' recevied. Clearing pending interrupt" << std::endl; SetIntActive(false); mState = State_Seek; } if ((aControl & Control_Pulse) != 0) { mLogger << setloglevel(LogLevel_IoActivity) << "'PULSE' recevied. Switching mode" << std::endl; mState = State_GetHighHeadSector; } if ((aControl & Control_Start) != 0) { mLogger << setloglevel(LogLevel_IoActivity) << "'START' recevied with RegA: " << HexPrinter(mRegA, 4) << " RegB: " << HexPrinter(mRegB, 4) << " RegC: " << HexPrinter(mRegC, 4) << std::endl; CRAY_ASSERT(mState == State_GetLowHeadSector); mState = State_Seek; size_t SectorIdx = mTrack * mNumSectors * mNumHeads + mHead * mNumSectors + mStartSector; switch (mRegA) { case CDC_Read: { // Read operation Create(); mLogger << setloglevel(LogLevel_IoActivity) << "mSectorCnt before: " << HexPrinter(mSectorCnt) << std::endl; // Adjust sector count: it's a 6-bit 2-s complement value... if ((mSectorCnt & 32) != 0) mSectorCnt |= ~63; mSectorCnt = -mSectorCnt; mLogger << setloglevel(LogLevel_IoActivity) << "mSectorCnt after: " << HexPrinter(mSectorCnt) << std::endl; mReads += mSectorCnt * mSectorSize / 1024; LogLine_c LogLine = mLogger << setloglevel(LogLevel_IoActivity); LogLine << "Reading sector " << DecPrinter(SectorIdx) << " (SHT-L:" << DecPrinter(mStartSector) << "," << DecPrinter(mHead) << "," << DecPrinter(mTrack) << "," << DecPrinter(mSectorCnt) << ")" << " from file offset " << HexPrinter(SectorIdx * mSectorSize, 8) << " to buffer at " << HexPrinter(mRegB, 4) << std::endl; std::fstream File(mImageFileName, std::ios::in | std::ios::binary); File.seekg(SectorIdx * mSectorSize); CRAY_ASSERT(!File.fail()); uint16_t Data; LogLine_c TraceLine = mLogger << setloglevel(LogLevel_IoTrace); for (size_t i = 0; i("DeviceCount", 64)); mDeviceIntMapping.resize(64); for(const auto &DeviceConfig: aConfig.get_child_safe("Devices")) { std::string DeviceType = DeviceConfig.first; std::shared_ptr Device; const Configuration_c &DeviceParams = DeviceConfig.second; uint8_t DeviceInt = DeviceParams.get("Interrupt"); if (DeviceInt >= 16) { throw InvalidParameter_x(boost::format("device interrupt %1% too large") % DeviceInt); } // Create device if (DeviceType == "Tape") { // Create a tape device Device = std::make_shared(DeviceParams, *this); } else if (DeviceType == "AmpexDM980Disk") { // Create a tape device Device = std::make_shared(DeviceParams, *this); } else if (DeviceType == "CDC9448Disk") { // Create a tape device Device = std::make_shared(DeviceParams, *this); } else if (DeviceType == "Dummy") { // Create a dummy device std::string DeviceName = DeviceParams.get("Name"); Device = std::make_shared(DeviceParams, *this, DeviceName); } else if (DeviceType == "Printer") { // Create a printer device Device = std::make_shared(DeviceParams, *this); } else if (DeviceType == "Clock") { // Create a clock device Device = std::make_shared(DeviceParams, *this); } else { throw InvalidParameter_x(boost::format("Unknown device type: %1%") % DeviceType); } // Add device to all channels (these are smart pointers, so adding a device multiple times is OK) for(size_t i=0;iGetChannelCnt();++i) { uint16_t ChannelIdx = Device->GetChannelIdx(i); if (mDevices[ChannelIdx] != nullptr) { throw InvalidParameter_x(boost::format("expander channel index %1% is already in use") % ChannelIdx); } if (ChannelIdx >= mDevices.size()) { throw InvalidParameter_x(boost::format("expander channel index %1% is too large") % ChannelIdx); } mDevices[ChannelIdx] = Device; } // Make sure that noone used up our interrupt already for(size_t i=0;iGetChannelCnt();++i) { uint16_t ChannelIdx = Device->GetChannelIdx(i); mDeviceIntMapping[ChannelIdx] = DeviceInt; } } // Fill-in unused devices with dummies if asked for bool UseDummyDevices = aConfig.get("UseDummyDevices", false); if (UseDummyDevices) { for(size_t Device=0;Device(aConfig.get_child_safe("DefaultDummy"), *this, DeviceName.str(), uint16_t(Device)); } } } for (auto &Device : mDevices) { if (Device != nullptr && Device->NeedsTick()) { // Make sure the device doesn't already exist in the list. bool Found = false; for (auto &TickedDevice : mTickedDevices) { if (TickedDevice == Device.get()) { Found = true; break; } } if (!Found) mTickedDevices.emplace_back(Device.get()); } } IntStatusChanged(); } IopInt_t IopChannelExp_c::DoIo(IopIoFunction_t aFunction, IopInt_t aData) { uint16_t Status = 0; if ((aFunction != 005 && mActiveChannel < 0) || (mActiveChannel >= 0 && mActiveChannel < (int)mDevices.size() && mDevices[mActiveChannel] == nullptr)) { mLogger << setloglevel(LogLevel_Warning) << "Issuing command " << OctPrinter(aFunction) << " to non-existent channel: " << OctPrinter(mActiveChannel) << std::endl; } switch (aFunction) { case 000: mBusy = false; mDone = false; mInterruptEnabled = false; //TODO: this should disable DMAs too, but there's no function to enabled it again?!!! return 0; case 001: CRAY_ASSERT(mActiveChannel != -1); mOperation = Operation_ReadA; mBusy = false; mDone = true; return 0; case 002: CRAY_ASSERT(mActiveChannel != -1); mOperation = Operation_ReadB; mBusy = false; mDone = true; return 0; case 003: CRAY_ASSERT(mActiveChannel != -1); mOperation = Operation_ReadC; mBusy = false; mDone = true; return 0; case 004: CRAY_ASSERT(mActiveChannel != -1); mOperation = Operation_ReadInterrupt; mBusy = false; mDone = true; return 0; case 005: mActiveChannel = aData & 63; return 0; case 006: mLogger << setloglevel(LogLevel_IoActivity) << "Setting channel interrupt mask to " << HexPrinter(aData) << std::endl; if (aData == 0 || aData == 0xffff) { for(size_t Device=0;DeviceSetIntEnabled(mActiveChannel, aData == 0); } } } else { for(size_t Int = 0;Int<16;++Int) { for(size_t Device=0;DeviceSetIntEnabled(mActiveChannel, (aData & (1 << Int)) == 0); } } } } return 0; case 007: mInterruptEnabled = (aData & 1) != 0; mDeviceInterruptEnabled = (aData & 2) != 0; mLogger << setloglevel(LogLevel_IoActivity) << "Interrupts " << (mInterruptEnabled?"enabled":"disabled") << ", device interrupts " << (mDeviceInterruptEnabled?"enabled":"disabled") << std::endl; return 0; case 010: CRAY_ASSERT(mActiveChannel != -1); if (mDevices[mActiveChannel] == nullptr) { std::cout << "operation 010 to non-existent EXP channel: " << OctPrinter(mActiveChannel) << std::endl; mParent.GetLogger().SetDisplayLogLevel(LogLevel_All); } if (mDevices[mActiveChannel] != nullptr) { switch (mOperation) { case Operation_ReadA: return mDevices[mActiveChannel]->GetRegA(mActiveChannel); case Operation_ReadB: return mDevices[mActiveChannel]->GetRegB(mActiveChannel); case Operation_ReadC: return mDevices[mActiveChannel]->GetRegC(mActiveChannel); default: CRAY_ASSERT(false); } } else { return 0; } return 0; case 011: CRAY_ASSERT(mActiveChannel != -1); mLogger << setloglevel(LogLevel_IoActivity) << "Querying status with operation code: " << DecPrinter(mOperation) << std::endl; Status = 0; switch (mOperation) { default: // I bet this is a bug in XCLOCK, but it seems it doesn't request for the status, it just queries it... case Operation_ReadInterrupt: if (mDevices[mActiveChannel] != nullptr) Status = mDevices[mActiveChannel]->GetStatus(mActiveChannel); if (mInterruptEnabled) Status |= Status_ExpanderIntEnabled; if (mBusy) Status |= Status_ExpanderBusy; if (mDone) Status |= Status_ExpanderDone; // Check for the highest priority device with an interrupt enabled for(uint16_t i=0;iGetStatus(i) & IopExpDevice_i::Status_Int) != 0) { Status |= i; break; } } mLogger << setloglevel(LogLevel_IoActivity) << "Returning status 1: " << HexPrinter(Status) << " with active channel: " << OctPrinter(mActiveChannel) << std::endl; return Status; // default: CRAY_ASSERT(false); } return 0; case 013: CRAY_ASSERT(mActiveChannel != -1); Status = 0; if (mDevices[mActiveChannel] != nullptr) Status = mDevices[mActiveChannel]->GetStatus(mActiveChannel); if (mInterruptEnabled) Status |= Status_ExpanderIntEnabled; if (mBusy) Status |= Status_ExpanderBusy; if (mDone) Status |= Status_ExpanderDone; Status |= mActiveChannel; mLogger << setloglevel(LogLevel_IoActivity) << "Returning status 2: " << HexPrinter(Status) << " with active channel: " << OctPrinter(mActiveChannel) << std::endl; return Status; case 014: CRAY_ASSERT(mActiveChannel != -1); if (mDevices[mActiveChannel] == nullptr) std::cout << "operation 014 to non-existent EXP channel: " << OctPrinter(mActiveChannel) << std::endl; //CRAY_ASSERT(mDevices[mActiveChannel] != nullptr); if (mDevices[mActiveChannel] != nullptr) mDevices[mActiveChannel]->SetRegA(mActiveChannel, aData); mBusy = false; mDone = true; return 0; case 015: CRAY_ASSERT(mActiveChannel != -1); if (mDevices[mActiveChannel] == nullptr) std::cout << "operation 015 to non-existent EXP channel: " << OctPrinter(mActiveChannel) << std::endl; //CRAY_ASSERT(mDevices[mActiveChannel] != nullptr); if (mDevices[mActiveChannel] != nullptr) mDevices[mActiveChannel]->SetRegB(mActiveChannel, aData); mBusy = false; mDone = true; return 0; case 016: CRAY_ASSERT(mActiveChannel != -1); if (mDevices[mActiveChannel] == nullptr) std::cout << "operation 016 to non-existent EXP channel: " << OctPrinter(mActiveChannel) << std::endl; //CRAY_ASSERT(mDevices[mActiveChannel] != nullptr); if (mDevices[mActiveChannel] != nullptr) mDevices[mActiveChannel]->SetRegC(mActiveChannel, aData); mBusy = false; mDone = true; return 0; case 017: CRAY_ASSERT(mActiveChannel != -1); if (mDevices[mActiveChannel] == nullptr) std::cout << "operation 017 to non-existent EXP channel: " << OctPrinter(mActiveChannel) << std::endl; //CRAY_ASSERT(mDevices[mActiveChannel] != nullptr); if (mDevices[mActiveChannel] != nullptr) mDevices[mActiveChannel]->Control(mActiveChannel, aData); mBusy = false; mDone = true; return 0; default: mLogger << setloglevel(LogLevel_Error) << SideEffectIndent << "ERROR: Invalid function code for channel" << std::endl; return 0; } } void IopChannelExp_c::IntStatusChanged() { mDeviceInterruptPending = false; for (uint16_t Device = 0; DeviceGetStatus(Device); //mLogger << setloglevel(LogLevel_IoActivity) << "Device " << OctPrinter(Device) << " returning status: " << HexPrinter(DevStatus) << std::endl; if (((DevStatus & IopExpDevice_i::Status_IntEnabled) != 0) && ((DevStatus & IopExpDevice_i::Status_Int) != 0)) { mDeviceInterruptPending = true; break; } } } } IopBit_t IopChannelExp_c::GetInterrupt() { if (mInterruptEnabled && mDone) return 1; /* if (mDeviceInterruptEnabled) { for(uint16_t Device=0;DeviceGetStatus(Device); //mLogger << setloglevel(LogLevel_IoActivity) << "Device " << OctPrinter(Device) << " returning status: " << HexPrinter(DevStatus) << std::endl; if (((DevStatus & IopExpDevice_i::Status_IntEnabled) != 0) && ((DevStatus & IopExpDevice_i::Status_Int) != 0)) { return 1; } } } }*/ if (mDeviceInterruptEnabled && mDeviceInterruptPending) return 1; return 0; } void IopChannelExp_c::Tick() { /* for(uint16_t Device=0;DeviceTick(Device); } }*/ for (auto &Device : mTickedDevices) { Device->Tick(); } } void IopChannelExp_c::GetStatus(StatusReport_c &aStatus, PeripheralType_e aFilter, bool aLongFormat) const { for (auto &Device : mDevices) { if (Device != nullptr && Device->GetType() == aFilter) { StatusReport_c Status; Device->GetStatus(Status, aFilter, aLongFormat); aStatus.put_child(Device->GetName(), Status); } } } void IopChannelExp_c::RegisterCommands(CommandHooks_t &aHooks) { for (auto &Device : mDevices) { if (Device != nullptr) { Device->RegisterCommands(aHooks); } } }