1
0
mirror of synced 2026-01-17 08:32:10 +00:00
2020-09-09 15:11:45 -07:00

1470 lines
57 KiB
C++

#include "ui.h"
#include "iop_expander.h"
#include "cray_mainframe.h"
#include <algorithm>
#include <time.h>
#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/bind.hpp>
#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<uint16_t>("ChannelIdx")),
mReads(0),
mWrites(0),
mIsReadOnly(aConfig.get<bool>("ReadOnly",false)),
mTapeImage(mLogger, aConfig)
{
mLogger.SetParent(mParent.GetLogger());
mTapeImage.open(aConfig.get<std::string>("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<uint8_t> 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<TmpBuf.size();i+=2) {
uint16_t Data = uint16_t(TmpBuf[i]) << 8 | uint16_t(TmpBuf[i+1]);
mParent.GetDma().WriteWord(mRegB,Data);
++mRegB;
++mRegC;
}
if (BytesRead / 2 != size_t(WordsToRead)) {
mLogger << setloglevel(LogLevel_IoActivity) << "partial buffer returned. Only " << DecPrinter(BytesRead) << " bytes are read. RegB is: " << HexPrinter(mRegB) << std::endl;
}
switch (mTapeImage.GetState()) {
case TapFile_c::State_e::EndOfTape: FinalizeControl(Status_Ready | Status_Error); break;
case TapFile_c::State_e::EndOfFile: FinalizeControl(Status_Ready | Status_Error); break; // TODO: Should we set the error bit here?
default: FinalizeControl(Status_Ready); break;
}
} break;
// 8 - rewind - RegB: not set RegC: not set
case Command_Rewind: {
mTapeImage.ReOpen();
mLogger << setloglevel(LogLevel_IoActivity) << "Rewiding" << std::endl;
mTapeImage.SeekToBeginningOfTape();
FinalizeControl(Status_Ready);
mTapeImage.close();
} break;
// 24 - space forward - RegB: not set RegC: set to SpaceSize*-1
case Command_SpaceForward: {
mTapeImage.ReOpen();
int RecordsToSkip = -int16_t(mRegC);
CRAY_ASSERT(RecordsToSkip > 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<uint8_t> TmpBuf(WordsToWrite*sizeof(IopInt_t));
// Swap all words and copy to target buffer
for(size_t i=0;i<TmpBuf.size();i+=2) {
uint16_t Data = mParent.GetDma().ReadWord(mRegB);
TmpBuf[i] = (Data >> 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<PokedTapFile_c*>(&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() << " <file name>" << 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<CmdMount_c>(*this));
/* UIHook_s Hook;
Hook.Callback = boost::bind(&IopExpTape_c::ExecUI, const_cast<IopExpTape_c*>(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<uint16_t>("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<std::string>("Name").c_str()),
mStatus(0),
mChannelIdx(aConfig.get<uint16_t>("ChannelIdx")),
mInGraphicsMode(false),
mWrites(0)
{
mLogger.SetParent(mParent.GetLogger());
mPrintFileName = aConfig.get<std::string>("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<std::string>("Name").c_str()),
mResponseTimer(0),
mResponseTimeout(aConfig.get<size_t>("ResponseTimeout", 100)),
mPrimaryChannelIdx(aConfig.get<uint16_t>("PrimaryChannelIdx")),
mRequestChannelIdx(aConfig.get<uint16_t>("RequestChannelIdx", mPrimaryChannelIdx+1)),
mResponseChannelIdx(aConfig.get<uint16_t>("ResponseChannelIdx", mPrimaryChannelIdx+0)),
mYearOffset(aConfig.get<uint16_t>("YearOffset", 20))
{
for(size_t i=0;i<sizeof(mInterruptActive)/sizeof(bool);++i) mInterruptActive[i] = false;
mLogger.SetParent(mParent.GetLogger());
}
void IopExpClockDevice_c::SetIntEnabled(uint16_t aDeviceAddr, bool aInterruptEnabled) {
mInterruptEnabled = aInterruptEnabled;
mParent.IntStatusChanged();
}
void IopExpClockDevice_c::SetIntActive(uint16_t aDeviceAddr, bool aActive) {
CRAY_ASSERT(aDeviceAddr < sizeof(mInterruptActive) / sizeof(bool));
mInterruptActive[aDeviceAddr] = aActive;
mParent.IntStatusChanged();
}
void IopExpClockDevice_c::SetRegA(uint16_t aDeviceAddr, uint16_t aValue) {
mLogger << setloglevel(LogLevel_IoActivity) << "Recevied RegA: " << HexPrinter(aValue,4) << " on address " << OctPrinter(aDeviceAddr) << std::endl;
mRegA = aValue;
CRAY_ASSERT(aDeviceAddr < sizeof(mInterruptActive)/sizeof(bool));
if (mInterruptActive[aDeviceAddr]) {
mLogger << setloglevel(LogLevel_IoActivity) << "Clearing interrupt while updating RegA" << std::endl;
SetIntActive(aDeviceAddr, false); //TODO: do we need to do this?
}
}
uint16_t IopExpClockDevice_c::GetRegA(uint16_t aDeviceAddr) {
IopInt_t RetVal = 0;
if (aDeviceAddr == mResponseChannelIdx) {
if (mResponse.empty()) {
mLogger << setloglevel(LogLevel_IoActivity) << "Returning RegA, but empty response string: " << HexPrinter(RetVal,4) << " on address " << OctPrinter(aDeviceAddr) << std::endl;
} else {
RetVal = mResponse.front();
mResponse = &mResponse[0]+1;
mLogger << setloglevel(LogLevel_IoActivity) << "Returning RegA: " << HexPrinter(RetVal,4) << " on address " << OctPrinter(aDeviceAddr) << std::endl;
SetIntActive(aDeviceAddr, true);
}
}
return RetVal;
}
uint16_t IopExpClockDevice_c::GetStatus(uint16_t aDeviceAddr) {
uint16_t RetVal = 0;
RetVal |= Status_DmaEnabled;
CRAY_ASSERT(aDeviceAddr < sizeof(mInterruptActive)/sizeof(bool));
if (mInterruptEnabled) RetVal |= Status_IntEnabled;
if (mInterruptActive[aDeviceAddr]) RetVal |= Status_Int;
if (mBusy) RetVal |= Status_DeviceBusy;
if (mDone) RetVal |= Status_DeviceDone;
return RetVal;
}
void IopExpClockDevice_c::ProcessRequest() {
if (mRequest == "ATLC") {
mLogger << setloglevel(LogLevel_IoActivity) << "Received LC" << std::endl;
mResponse = "0\x0d";
} else if (mRequest == "ATVD") {
mLogger << setloglevel(LogLevel_IoActivity) << "Received VD" << std::endl;
mResponse = "0\x0d";
} else if (mRequest == "ATVT") {
mLogger << setloglevel(LogLevel_IoActivity) << "Received VT" << std::endl;
mResponse = "0\x0d";
} else if (mRequest == "ATRT") {
mLogger << setloglevel(LogLevel_IoActivity) << "Received RT" << std::endl;
boost::local_time::local_date_time CurrentTime(boost::posix_time::second_clock::local_time(), boost::local_time::time_zone_ptr());
auto Time = CurrentTime.local_time().time_of_day();
mResponse = (boost::format("%02d%02d%02d\x0d") % Time.hours() % Time.minutes() % Time.seconds()).str();
} else if (mRequest == "ATRD") {
mLogger << setloglevel(LogLevel_IoActivity) << "Received RD" << std::endl;
boost::local_time::local_date_time CurrentTime(boost::posix_time::second_clock::local_time(), boost::local_time::time_zone_ptr());
auto Date = CurrentTime.local_time().date().year_month_day();
uint16_t Year = Date.year;
Year = (Year - mYearOffset) % 100; // Correct with offset and keep only last two digits
mResponse = (boost::format("%02d%02d%02d\x0d") % Year % Date.month.as_number() % Date.day.as_number()).str();
} else if (boost::starts_with(mRequest, "ATSD")) {
mLogger << setloglevel(LogLevel_IoActivity) << "Received SD, full request:" << mRequest << std::endl;
mResponse = "0\x0d";
} else if (boost::starts_with(mRequest, "ATST")) {
mLogger << setloglevel(LogLevel_IoActivity) << "Received ST, full request:" << mRequest << std::endl;
mResponse = "0\x0d";
} else {
mLogger << setloglevel(LogLevel_IoActivity) << "Received unknown command:" << mRequest << std::endl;
mResponse = "0\x0d";
}
//CRAY_ASSERT(mResponseTimer == 0);
mResponseTimer = 1; // Start the response timer
}
void IopExpClockDevice_c::Control(uint16_t aDeviceAddr, uint16_t aControl) {
//mParent.GetParent().GetLogger().SetDisplayLogLevel(LogLevel_All);
CRAY_ASSERT((aControl & ~(Control_Start | Control_Clear | Control_Pulse)) == 0);
if ((aControl & Control_Clear) != 0) {
mLogger << setloglevel(LogLevel_IoActivity) << "'CLEAR' recevied on address " << OctPrinter(aDeviceAddr) << ". Clearing pending interrupt" << std::endl;
SetIntActive(aDeviceAddr, false);
if (aDeviceAddr == mResponseChannelIdx && !mResponse.empty()) {
// Schedule another interrupt
mResponseTimer = 1;
}
}
if ((aControl & Control_Start) != 0) {
mLogger << setloglevel(LogLevel_IoActivity) << "'START' recevied on address " << OctPrinter(aDeviceAddr) << " with RegA: " << HexPrinter(mRegA,4) << " RegB: " << HexPrinter(mRegB,4) << " RegC: " << HexPrinter(mRegC,4) << std::endl;
if ((mRegA & 0xff) == '\x0d') {
ProcessRequest();
mRequest.clear();
} else {
mRequest += (char)(mRegA & 0xff);
}
SetIntActive(aDeviceAddr, true);
}
if ((aControl & Control_Pulse) != 0) {
mLogger << setloglevel(LogLevel_IoActivity) << "'PULSE' recevied on address " << OctPrinter(aDeviceAddr) << " with RegA: " << HexPrinter(mRegA,4) << " RegB: " << HexPrinter(mRegB,4) << " RegC: " << HexPrinter(mRegC,4) << std::endl;
}
}
void IopExpClockDevice_c::Tick() {
if (mResponseTimer > 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 <Track> RegC 5
// RegB <Head> RegC 1
// RegB <StartSector> RegC 2
// RegB <SectorCnt> RegC 3
// RegB <buffer ptr> 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<uint16_t>("ChannelIdx")),
mReads(0),
mWrites(0)
{
mLogger.SetParent(mParent.GetLogger());
mImageFileName = aConfig.get<std::string>("ImageFileName");
mReadOnly = aConfig.get<bool>("ReadOnly",false);
mNumHeads = aConfig.get<size_t>("Heads", 5);
if (mNumHeads == 0) throw InvalidParameter_x("Heads must be greater than 0");
mNumSectors = aConfig.get<size_t>("Sectors", 35);
if (mNumSectors == 0) throw InvalidParameter_x("Sectors must be greater than 0");
mNumTracks = aConfig.get<size_t>("Tracks", 823);
if (mNumTracks == 0) throw InvalidParameter_x("Tracks must be greater than 0");
mSectorSize = aConfig.get<size_t>("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<char> 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<mSectorSize*mSectorCnt/sizeof(Data);++i) {
File.read((char*)(&Data),sizeof(Data));
if (i % 4 == 0) TraceLine << "Transfered ";
TraceLine << HexPrinter(Data, 4) << " ";
if (i % 4 == 3) TraceLine << std::endl;
Data = SwapBytes(Data);
mParent.GetDma().WriteWord(mRegB, Data);
CRAY_ASSERT(!File.fail());
++mRegB;
}
mDone = true;
mBusy = false;
mRegA &= ~1;
SetIntActive(true);
} break;
case Ampex_Write: // Write operation
case Ampex_Format: { // Format operation
Create();
mWrites += mSectorCnt * mSectorSize / 1024;
mLogger << setloglevel(LogLevel_IoActivity) << "Writing sector " << DecPrinter(SectorIdx)
<< " (SHT-L:" << DecPrinter(mStartSector) << "," << DecPrinter(mHead) << "," << DecPrinter(mTrack) << "," << DecPrinter(mSectorCnt) << ")"
<< " to file offset " << HexPrinter(SectorIdx * mSectorSize,8) << " from buffer at " << HexPrinter(mRegB,4) << std::endl;
std::fstream File;
File.open(mImageFileName, std::ios::out | std::ios::in | std::ios::binary);
File.seekp(SectorIdx * mSectorSize);
CRAY_ASSERT(!File.fail());
uint16_t Data = 0;
for(size_t i=0;i<mSectorSize*mSectorCnt/sizeof(Data);++i) {
if (mRegC == Ampex_Write) {
Data = mParent.GetDma().ReadWord(mRegB);
Data = SwapBytes(Data);
}
File.write((char*)(&Data),sizeof(Data));
CRAY_ASSERT(!File.fail());
++mRegB;
}
mDone = true;
mBusy = false;
mRegA &= ~1;
SetIntActive(true);
} break;
case Ampex_ReturnToZero: {
mLogger << setloglevel(LogLevel_IoActivity) << "RETURN TO ZERO recevied with RegA: " << HexPrinter(mRegA, 4) << " RegB: " << HexPrinter(mRegB, 4) << " RegC: " << HexPrinter(mRegC, 4) << std::endl;
mTrack = 0;
mHead = 0;
mStartSector = 0;
mDone = true;
mBusy = false;
mRegA &= ~1;
SetIntActive(true);
} break;
case Ampex_Checksum: {
mDone = true;
mBusy = false;
mRegA &= ~1;
SetIntActive(true);
} break;
case Ampex_StartSeek:
case Ampex_SelectDrive:
case Ampex_AttemptCorrection:
mLogger << setloglevel(LogLevel_Error) << "UNIMPLEMENTED 'START' recevied with RegA: " << HexPrinter(mRegA, 4) << " RegB: " << HexPrinter(mRegB, 4) << " RegC: " << HexPrinter(mRegC, 4) << std::endl;
break;
default:
mLogger << setloglevel(LogLevel_Error) << "UNKNOWN 'START' recevied with RegA: " << HexPrinter(mRegA, 4) << " RegB: " << HexPrinter(mRegB, 4) << " RegC: " << HexPrinter(mRegC, 4) << std::endl;
break;
}
}
}
void IopExpDiskAmpexDM980_c::Tick() {
}
void IopExpDiskAmpexDM980_c::GetStatus(StatusReport_c &aStatus, PeripheralType_e aFilter, bool aLongFormat) const {
aStatus.put(aLongFormat ? "Cylinder" : "C", mTrack);
aStatus.put(aLongFormat ? "Head" : "H", mHead);
aStatus.put(aLongFormat ? "Sector" : "S", mStartSector);
aStatus.put(aLongFormat ? "Reads" : "R", mReads);
aStatus.put(aLongFormat ? "Writes" : "W", mWrites);
// aStatus.put("res", mMounted);
// aStatus.put("fname", mImageFileName);
}
//////////////////////////////////////////////////////////////////////////////////////////
// IopExpDiskCDC9448_96_c
//////////////////////////////////////////////////////////////////////////////////////////
// Must be a CDC9448-96 from this: http://bitsavers.trailing-edge.com/pdf/dilog/30006-1_DQ202A_Sep81.pdf
// It's a 96MB drive, but formatted as a 64MB one (leaving the last few sectors on each track empty)
// Protocol seems to be:
// RegA: 0x100
// RegC: cylinder
// PULSE
// RegA: command (0 for read 0x700 for write)
// RegC: bits 9-5: high order bits of sector; bits 4-0: high order bits of (twos complement of) sector count
// RegC: bits 14-10: head bits 9-5: low order bits of sector; bits 4-0: low order bits of (twos complement of) sector count
// RegB: buffer address
// START
// CDC device function codes(DOA)
const uint16_t CDC_Read = 00000;
const uint16_t CDC_Recalibrate = 00200;
const uint16_t CDC_Seek = 00400;
const uint16_t CDC_StopDisk = 00600;
const uint16_t CDC_OffsetForward = 01000;
const uint16_t CDC_OffsetReverse = 01200;
const uint16_t CDC_WriteDisable = 01400;
const uint16_t CDC_ReleaseDrive = 01600;
const uint16_t CDC_Treoass = 02000;
const uint16_t CDC_SetAltMode1 = 02200;
const uint16_t CDC_SetAltMode2 = 02400;
const uint16_t CDC_ExamineRam = 02600;
const uint16_t CDC_DataVerify = 03000;
const uint16_t CDC_ReadBuffers = 03200;
const uint16_t CDC_Write = 03400;
const uint16_t CDC_Format = 03600;
// CDC accumulator offsets(DOC2)
const uint16_t CDC_CountShiftFactor = 0;
const uint16_t CDC_SectorShiftFactor = 5;
const uint16_t CDC_HeadShiftFactor = 10;
IopExpDiskCDC9448_96_c::IopExpDiskCDC9448_96_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<uint16_t>("ChannelIdx")),
mReads(0),
mWrites(0),
mState(State_Seek)
{
mLogger.SetParent(mParent.GetLogger());
mImageFileName = aConfig.get<std::string>("ImageFileName");
mReadOnly = aConfig.get<bool>("ReadOnly", false);
mNumHeads = aConfig.get<size_t>("Heads", 5);
if (mNumHeads == 0) throw InvalidParameter_x("Heads must be greater than 0");
mNumSectors = aConfig.get<size_t>("Sectors", 35);
if (mNumSectors == 0) throw InvalidParameter_x("Sectors must be greater than 0");
mNumTracks = aConfig.get<size_t>("Tracks", 823);
if (mNumTracks == 0) throw InvalidParameter_x("Tracks must be greater than 0");
mSectorSize = aConfig.get<size_t>("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<char> 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<mSectorSize*mSectorCnt / sizeof(Data); ++i) {
File.read((char*)(&Data), sizeof(Data));
if (i % 4 == 0) TraceLine << "Transfered ";
TraceLine << HexPrinter(Data, 4) << " ";
if (i % 4 == 3) TraceLine << std::endl;
Data = SwapBytes(Data);
mParent.GetDma().WriteWord(mRegB, Data);
CRAY_ASSERT(!File.fail());
++mRegB;
}
mDone = true;
mBusy = false;
mRegA &= ~0x8000;
SetIntActive(true);
} break;
case CDC_Format:
case CDC_Write: { // Write operation
Create();
mWrites += mSectorCnt * mSectorSize / 1024;
mLogger << setloglevel(LogLevel_IoActivity) << "Writing sector " << DecPrinter(SectorIdx)
<< " (SHT-L:" << DecPrinter(mStartSector) << "," << DecPrinter(mHead) << "," << DecPrinter(mTrack) << "," << DecPrinter(mSectorCnt) << ")"
<< " to file offset " << HexPrinter(SectorIdx * mSectorSize, 8) << " from buffer at " << HexPrinter(mRegB, 4) << std::endl;
std::fstream File;
File.open(mImageFileName, std::ios::out | std::ios::in | std::ios::binary);
File.seekp(SectorIdx * mSectorSize);
CRAY_ASSERT(!File.fail());
uint16_t Data = 0;
for (size_t i = 0; i<mSectorSize*mSectorCnt / sizeof(Data); ++i) {
if (mRegA == CDC_Write) {
Data = mParent.GetDma().ReadWord(mRegB);
Data = SwapBytes(Data);
}
File.write((char*)(&Data), sizeof(Data));
CRAY_ASSERT(!File.fail());
++mRegB;
}
mDone = true;
mBusy = false;
mRegA &= ~0x8000;
SetIntActive(true);
} break;
default:
CRAY_ASSERT(false);
break;
}
}
}
void IopExpDiskCDC9448_96_c::Tick() {
}
void IopExpDiskCDC9448_96_c::GetStatus(StatusReport_c &aStatus, PeripheralType_e aFilter, bool aLongFormat) const {
aStatus.put(aLongFormat ? "Cylinder" : "C", mTrack);
aStatus.put(aLongFormat ? "Head" : "H", mHead);
aStatus.put(aLongFormat ? "Sector" : "S", mStartSector);
aStatus.put(aLongFormat ? "Reads" : "R", mReads);
aStatus.put(aLongFormat ? "Writes" : "W", mWrites);
// aStatus.put("res", mMounted);
// aStatus.put("fname", mImageFileName);
}
//////////////////////////////////////////////////////////////////////////////////////////
// IopChannelExp_c
//////////////////////////////////////////////////////////////////////////////////////////
void IopChannelExp_c::Setup(const Configuration_c &aConfig) {
mDevices.clear();
mDeviceIntMapping.clear();
mDevices.resize(aConfig.get<size_t>("DeviceCount", 64));
mDeviceIntMapping.resize(64);
for(const auto &DeviceConfig: aConfig.get_child_safe("Devices")) {
std::string DeviceType = DeviceConfig.first;
std::shared_ptr<IopExpDevice_i> Device;
const Configuration_c &DeviceParams = DeviceConfig.second;
uint8_t DeviceInt = DeviceParams.get<uint8_t>("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<IopExpTape_c>(DeviceParams, *this);
} else if (DeviceType == "AmpexDM980Disk") {
// Create a tape device
Device = std::make_shared<IopExpDiskAmpexDM980_c>(DeviceParams, *this);
} else if (DeviceType == "CDC9448Disk") {
// Create a tape device
Device = std::make_shared<IopExpDiskCDC9448_96_c>(DeviceParams, *this);
} else if (DeviceType == "Dummy") {
// Create a dummy device
std::string DeviceName = DeviceParams.get<std::string>("Name");
Device = std::make_shared<IopExpDummyDevice_c>(DeviceParams, *this, DeviceName);
} else if (DeviceType == "Printer") {
// Create a printer device
Device = std::make_shared<IopExpPrinterDevice_c>(DeviceParams, *this);
} else if (DeviceType == "Clock") {
// Create a clock device
Device = std::make_shared<IopExpClockDevice_c>(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;i<Device->GetChannelCnt();++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;i<mDeviceIntMapping.size();++i) {
if (mDeviceIntMapping[i] == DeviceInt && mDevices[i] != nullptr) {
throw InvalidParameter_x(boost::format("device interrupt %1% is already in use") % DeviceInt);
}
}
// Register our interrupt for all channels
for(size_t i=0;i<Device->GetChannelCnt();++i) {
uint16_t ChannelIdx = Device->GetChannelIdx(i);
mDeviceIntMapping[ChannelIdx] = DeviceInt;
}
}
// Fill-in unused devices with dummies if asked for
bool UseDummyDevices = aConfig.get<bool>("UseDummyDevices", false);
if (UseDummyDevices) {
for(size_t Device=0;Device<mDevices.size();++Device) {
std::stringstream DeviceName;
DeviceName << "DUMMY" << OctPrinter(Device,3);
if (mDevices[Device] == nullptr) {
mDevices[Device] = std::make_shared<IopExpDummyDevice_c>(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;Device<mDevices.size();++Device) {
if (mDevices[Device] != nullptr) {
mDevices[Device]->SetIntEnabled(mActiveChannel, aData == 0);
}
}
} else {
for(size_t Int = 0;Int<16;++Int) {
for(size_t Device=0;Device<mDevices.size();++Device) {
if (mDevices[Device] != nullptr && mDeviceIntMapping[Device] == Int) {
mDevices[Device]->SetIntEnabled(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;i<uint16_t(mDevices.size());++i) {
if (mDevices[i] != nullptr && (mDevices[i]->GetStatus(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; Device<uint16_t(mDevices.size()); ++Device) {
if (mDevices[Device] != nullptr) {
uint16_t DevStatus = mDevices[Device]->GetStatus(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;Device<uint16_t(mDevices.size());++Device) {
if (mDevices[Device] != nullptr) {
uint16_t DevStatus = mDevices[Device]->GetStatus(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;Device<uint16_t(mDevices.size());++Device) {
if (mDevices[Device] != nullptr) {
mDevices[Device]->Tick(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);
}
}
}