diff --git a/10.02_devices/2_src/massbus_device.hpp b/10.02_devices/2_src/massbus_device.hpp index 6545eb9..85f7125 100644 --- a/10.02_devices/2_src/massbus_device.hpp +++ b/10.02_devices/2_src/massbus_device.hpp @@ -17,6 +17,9 @@ class massbus_device_c { public: + // Resets the bus + virtual void Reset() = 0; + // // MASSBUS Register Metadata: // diff --git a/10.02_devices/2_src/massbus_rp.cpp b/10.02_devices/2_src/massbus_rp.cpp index 2784c8f..59e93c4 100644 --- a/10.02_devices/2_src/massbus_rp.cpp +++ b/10.02_devices/2_src/massbus_rp.cpp @@ -10,19 +10,51 @@ using namespace std; #include "storagedrive.hpp" +#include "rp_drive.hpp" #include "rh11.hpp" #include "massbus_device.hpp" #include "massbus_rp.hpp" +void* WorkerInit( + void* context) +{ + massbus_rp_c* rp = reinterpret_cast(context); + rp->Worker(); + return nullptr; +} + massbus_rp_c::massbus_rp_c( rh11_c* controller) : device_c(), - _controller(controller) + _controller(controller), + _selectedUnit(0), + _desiredSector(0), + _desiredTrack(0), + _desiredCylinder(0), + _workerState(WorkerState::Idle), + _workerWakeupCond(PTHREAD_COND_INITIALIZER), + _workerMutex(PTHREAD_MUTEX_INITIALIZER), + _vv(false), + _err(false), + _ata(false), + _ready(true), + _ned(false) { name.value="MASSBUS_RP"; type_name.value = "massbus_rp_c"; log_label = "MASSBUS_RP"; + _newCommand = { 0 }; + + // Start the worker thread + pthread_attr_t attribs; + pthread_attr_init(&attribs); + + int status = pthread_create( + &_workerThread, + &attribs, + &WorkerInit, + reinterpret_cast(this)); } massbus_rp_c::~massbus_rp_c() @@ -32,16 +64,16 @@ massbus_rp_c::~massbus_rp_c() bool massbus_rp_c::ImplementsRegister( - uint32_t register) + uint32_t reg) { - return false; + return (reg > 0) && (reg < 16); } std::string massbus_rp_c::RegisterName( - uint32_t register) + uint32_t reg) { - std::string name("foo"); + std::string name(_registerMetadata[reg].Name); return name; } @@ -82,12 +114,29 @@ massbus_rp_c::WriteRegister( { INFO("RP reg write: unit %d register 0%o value 0%o", unit, reg, value); - switch(reg) + switch(static_cast(reg)) { - case 0: + case Registers::Control: DoCommand(unit, value); break; + case Registers::DesiredSectorTrackAddress: + _desiredTrack = (value & 0x1f00) >> 8; + _desiredSector = (value & 0x1f); + UpdateDesiredSectorTrack(); + + INFO("Desired Sector Track Address: track %d, sector %d", + _desiredTrack, + _desiredSector); + break; + + case Registers::DesiredCylinderAddress: + _desiredCylinder = value & 0x3ff; + UpdateDesiredCylinder(); + + INFO("Desired Cylinder Address o%o (o%o)", _desiredCylinder, value); + break; + default: FATAL("Unimplemented RP register write."); break; @@ -104,11 +153,25 @@ void massbus_rp_c::DoCommand( return; } - uint32_t function = (command & RP_FUNC) >> 1; + FunctionCode function = static_cast((command & RP_FUNC) >> 1); + _selectedUnit = unit; + + INFO("RP function 0%o, unit %o", function, _selectedUnit); - INFO("RP function 0%o", function); + if (!SelectedDrive()->IsAvailable()) + { + // Early return for disconnected drives; + // set NED and ERR bits + _err = true; + _ned = true; // TODO: should be done at RH11 level! + _vv = false; + UpdateStatus(); + return; + } - switch(function) + _ned = false; + + switch(static_cast(function)) { case FunctionCode::Nop: // Nothing. @@ -121,11 +184,60 @@ void massbus_rp_c::DoCommand( // sector/track address register, and clears the FMT, HCI, and ECI // bits in the offset register. It is used to bootstrap the device." // - set_bit + _vv = true; + _desiredSector = 0; + _desiredTrack = 0; + _offset = 0; + UpdateStatus(); + UpdateDesiredSectorTrack(); + UpdateOffset(); break; case FunctionCode::ReadData: - INFO("RP Read Data"); + case FunctionCode::WriteData: + case FunctionCode::WriteHeaderAndData: + case FunctionCode::ReadHeaderAndData: + case FunctionCode::Search: + INFO("RP Read/Write Data or Search"); + + // Ensure the drive is ready, error if not + if (!SelectedDrive()->IsDriveReady()) + { + _vv = false; + _err = true; + _ata = true; + _ready = true; + UpdateStatus(); + } + else + { + // Clear the unit's DRY bit + SelectedDrive()->ClearDriveReady(); + + if (function == FunctionCode::Search) + { + SelectedDrive()->SetPositioningInProgress(); + } + + // Clear ATA and READY + _ata = false; + _ready = false; + UpdateStatus(); + + pthread_mutex_lock(&_workerMutex); + + // Save a copy of command data for the worker to consume + _newCommand.unit = _selectedUnit; + _newCommand.function = function; + _newCommand.bus_address = _controller->GetBusAddress(); + _newCommand.word_count = _controller->GetWordCount(); + _newCommand.ready = true; + + + // Wake the worker + pthread_cond_signal(&_workerWakeupCond); + pthread_mutex_unlock(&_workerMutex); + } break; default: @@ -147,12 +259,255 @@ massbus_rp_c::ReadRegister( return 0; } -// background worker function -void -massbus_rp_c::worker( - unsigned instance) +// +// Register update functions +// +void +massbus_rp_c::UpdateStatus() { + _error1 = + (SelectedDrive()->GetAddressOverflow() ? 01000 : 0) | + (SelectedDrive()->GetInvalidAddress() ? 02000 : 0) | + (SelectedDrive()->GetWriteLockError() ? 04000 : 0); + + if (_error1 != 0) + { + _err = true; + } + else + { + _err = false; + } + + _status = + (_vv ? 0100 : 0) | + (SelectedDrive()->IsDriveReady() ? 0200 : 0) | + (0400) | // Drive preset -- always set for a single-controller disk + (SelectedDrive()->GetReadLastSector() ? 02000 : 0) | // Last sector read + (SelectedDrive()->IsWriteLocked() ? 04000 : 0) | // Write lock + (SelectedDrive()->IsAvailable() ? 010000 : 0) | // Medium online + (SelectedDrive()->IsPositioningInProgress() ? 020000 : 0) | // PIP + (_err ? 040000 : 0) | // Composite error + (_ata ? 0100000 : 0); + + _controller->WriteRegister(static_cast(Registers::Status), _status); + _controller->WriteRegister(static_cast(Registers::Error1), _error1); + + // Inform controller of status update. + _controller->BusStatus(_ready, _ata, _err, _ned); +} + +void +massbus_rp_c::UpdateDesiredSectorTrack() +{ + uint16_t desiredSectorTrack = (_desiredSector | (_desiredTrack << 8)); + _controller->WriteRegister(static_cast(Registers::DesiredSectorTrackAddress), desiredSectorTrack); +} + +void +massbus_rp_c::UpdateDesiredCylinder() +{ + _controller->WriteRegister(static_cast(Registers::DesiredCylinderAddress), _desiredCylinder); +} + +void +massbus_rp_c::UpdateOffset() +{ + _controller->WriteRegister(static_cast(Registers::Offset), _offset); +} + +void +massbus_rp_c::Reset() +{ + // TODO: reset all drives + + // Reset registers to their defaults + _ata = false; + _ready = true; + UpdateStatus(); + + _desiredSector = 0; + _desiredTrack = 0; + UpdateDesiredSectorTrack(); + + _desiredCylinder = 0; + UpdateDesiredCylinder(); + + _offset = 0; + UpdateOffset(); + + _selectedUnit = 0; + _newCommand.ready = false; + +} + +rp_drive_c* +massbus_rp_c::SelectedDrive() +{ + return _controller->GetDrive(_selectedUnit); +} + +// Background worker function +void +massbus_rp_c::Worker() +{ + worker_init_realtime_priority(rt_device); + + _workerState = WorkerState::Idle; + WorkerCommand command = { 0 }; + + timeout_c timeout; + + INFO("massbus worker started."); + while (!workers_terminate) + { + switch(_workerState) + { + case WorkerState::Idle: + // Wait for work. + pthread_mutex_lock(&_workerMutex); + while (!_newCommand.ready) + { + pthread_cond_wait( + &_workerWakeupCond, + &_workerMutex); + } + + // Make a local copy of the new command + command = _newCommand; + _newCommand.ready = false; + pthread_mutex_unlock(&_workerMutex); + + _workerState = WorkerState::Execute; + INFO("worker awoken."); + break; + + case WorkerState::Execute: + switch(command.function) + { + case FunctionCode::ReadData: + { + INFO("READ CHS %d/%d/%d, %d words to address o%o", + _desiredCylinder, + _desiredTrack, + _desiredSector, + _newCommand.word_count, + _newCommand.bus_address); + + uint16_t* buffer = nullptr; + if (SelectedDrive()->Read( + _desiredCylinder, + _desiredTrack, + _desiredSector, + _newCommand.word_count, + &buffer)) + { + // + // Data read: do DMA transfer to memory. + // + _controller->DMAWrite( + _newCommand.bus_address, + _newCommand.word_count, + buffer); + + // Free buffer + delete buffer; + } + else + { + // Read failed: + INFO("Read failed."); + } + + // Return drive to ready state + SelectedDrive()->SetDriveReady(); + + // Signal attention and update status. + _ata = true; + _ready = true; + UpdateStatus(); + _workerState = WorkerState::Finish; + } + break; + + case FunctionCode::WriteData: + { + INFO("WRITE CHS %d/%d/%d, %d words from address o%o", + _desiredCylinder, + _desiredTrack, + _desiredSector, + _newCommand.word_count, + _newCommand.bus_address); + + + uint16_t* buffer = new uint16_t[_newCommand.word_count]; + assert(buffer); + + if (!SelectedDrive()->Write( + _desiredCylinder, + _desiredTrack, + _desiredSector, + _newCommand.word_count, + buffer)) + { + // Write failed: + INFO("Write failed."); + } + + delete buffer; + + // Return drive to ready state + SelectedDrive()->SetDriveReady(); + + // Signal attention and update status. + _ata = true; + _ready = true; + UpdateStatus(); + _workerState = WorkerState::Finish; + } + break; + + case FunctionCode::Search: + { + INFO("SEARCH CHS %d/%d/%d", + _desiredCylinder, + _desiredTrack, + _desiredSector); + + if (!SelectedDrive()->Search( + _desiredCylinder, + _desiredTrack, + _desiredSector)) + { + // Search failed + INFO("Search failed"); + } + + // Return to ready state + SelectedDrive()->SetDriveReady(); + _ata = true; + _ready = true; + UpdateStatus(); + _workerState = WorkerState::Finish; + } + break; + + default: + FATAL("Unimplemented drive function %d", command.function); + break; + } + + break; + + case WorkerState::Finish: + _workerState = WorkerState::Idle; + SelectedDrive()->SetDriveReady(); + UpdateStatus(); + break; + + } + } } void @@ -164,5 +519,5 @@ massbus_rp_c::on_power_changed(void) void massbus_rp_c::on_init_changed(void) { - + Reset(); } diff --git a/10.02_devices/2_src/massbus_rp.hpp b/10.02_devices/2_src/massbus_rp.hpp index 638e502..fef7006 100644 --- a/10.02_devices/2_src/massbus_rp.hpp +++ b/10.02_devices/2_src/massbus_rp.hpp @@ -24,7 +24,7 @@ enum class Registers Error1 = 02, Maintenance = 03, AttentionSummary = 04, - DesiredAddress = 05, + DesiredSectorTrackAddress = 05, LookAhead = 07, DriveType = 06, SerialNo = 014, @@ -67,6 +67,7 @@ enum class FunctionCode // struct rp_register_data { + char Name[16]; bool ActiveOnDATI; bool ActiveOnDATO; uint16_t ResetValue; @@ -84,6 +85,8 @@ public: virtual ~massbus_rp_c(); public: + void Reset(); + bool ImplementsRegister(uint32_t register) override; std::string RegisterName(uint32_t register) override; @@ -96,37 +99,90 @@ public: void WriteRegister(uint32_t unit, uint32_t register, uint16_t value) override; uint16_t ReadRegister(uint32_t unit, uint32_t register) override; + // Background worker function + void Worker(); private: - void DoCommand(uint32_t unit, uint16_t command); + struct WorkerCommand + { + volatile uint32_t unit; + volatile uint32_t bus_address; + volatile uint32_t word_count; + volatile FunctionCode function; + volatile bool ready; + } _newCommand; - // background worker function - void worker(unsigned instance) override; + enum WorkerState + { + Idle = 0, + Execute = 1, + Finish = 2, + } _workerState; + + void DoCommand(uint32_t unit, uint16_t command); void on_power_changed(void) override; void on_init_changed(void) override; + + void UpdateStatus(); + void UpdateDesiredSectorTrack(); + void UpdateDesiredCylinder(); + void UpdateOffset(); + + rp_drive_c* SelectedDrive(); + private: rh11_c* _controller; rp_register_data _registerMetadata[16] = { - { false, false, 0, 0 }, // 0, not used - { false, false, 0, 0177700 }, // Status - { false, true , 0, 0177777 }, // Error #1 - writable by diagnostics - { false, true , 0, 0177777 }, // Maintenance - { false, false, 0, 0 }, // Attention summary - { false, false, 0, 0017437 }, // Desired Sector/Track - { false, false, 0, 0 }, // Look Ahead - { false, false, 0, 0 }, // Drive Type - { false, false, 0, 0 }, // Serial Number - { false, false, 0, 0177777 }, // Offset - { false, false, 0, 0001777 }, // Desired Cylinder - { false, false, 0, 0 }, // Current Cylinder - { false, false, 0, 0 }, // Error #2 - { false, false, 0, 0 }, // Error #3 - { false, false, 0, 0 }, // ECC Position - { false, false, 0, 0 }, // ECC Pattern + // Name DATI DATO + { "INV", false, false, 0, 0 }, // 0, not used + { "CS1", false, 0, 0177700 }, // Status + { "ER1", false, true , 0, 0177777 }, // Error #1 - writable by diagnostics + { "MR" , false, true , 0, 0177777 }, // Maintenance + { "ATN", false, false, 0, 0 }, // Attention summary + { "DA" , false, true, 0, 0017437 }, // Desired Sector/Track + { "LA" , false, false, 0, 0 }, // Look Ahead + { "DT" , false, false, 0, 0 }, // Drive Type + { "SN" , false, false, 0, 0 }, // Serial Number + { "OFF", false, false, 0, 0177777 }, // Offset + { "DCY", false, true, 0, 0001777 }, // Desired Cylinder + { "CCY", false, false, 0, 0 }, // Current Cylinder + { "ER2", false, false, 0, 0 }, // Error #2 + { "ER3", false, false, 0, 0 }, // Error #3 + { "EPO", false, false, 0, 0 }, // ECC Position + { "EPA", false, false, 0, 0 }, // ECC Pattern }; + // Unit selected by last command register write + uint32_t _selectedUnit; + + // Register data + uint16_t _status; + uint16_t _error1; + uint16_t _maint; + uint16_t _attn; + uint16_t _desiredSector; + uint16_t _desiredTrack; + uint16_t _offset; + uint16_t _desiredCylinder; + uint16_t _currentCylinder; + uint16_t _error2; + uint16_t _error3; + + // Status bits that we track + bool _vv; + bool _err; + bool _ata; + + // RH11 ready signal (ugly: this should be in the rh11 code!) + bool _ready; + bool _ned; // ditto + + // Worker thread + pthread_t _workerThread; + pthread_cond_t _workerWakeupCond; + pthread_mutex_t _workerMutex; }; #endif diff --git a/10.02_devices/2_src/mscp_drive.hpp b/10.02_devices/2_src/mscp_drive.hpp index ce3cf52..b088176 100644 --- a/10.02_devices/2_src/mscp_drive.hpp +++ b/10.02_devices/2_src/mscp_drive.hpp @@ -10,7 +10,7 @@ #include #include -#include // unique_ptr +#include #include "parameter.hpp" #include "storagedrive.hpp" @@ -20,90 +20,96 @@ class mscp_drive_c: public storagedrive_c { public: - mscp_drive_c(storagecontroller_c *controller, uint32_t driveNumber); - ~mscp_drive_c(void); + mscp_drive_c(storagecontroller_c *controller, uint32_t driveNumber); + ~mscp_drive_c(void); - bool on_param_changed(parameter_c *param) override; + bool on_param_changed(parameter_c *param) override; - uint32_t GetBlockSize(void); - uint32_t GetBlockCount(void); - uint32_t GetRCTBlockCount(void); - uint32_t GetMediaID(void); - uint32_t GetDeviceNumber(void); - uint16_t GetClassModel(void); - uint16_t GetRCTSize(void); - uint8_t GetRBNs(void); - uint8_t GetRCTCopies(void); + uint32_t GetBlockSize(void); + uint32_t GetBlockCount(void); + uint32_t GetRCTBlockCount(void); + uint32_t GetMediaID(void); + uint32_t GetDeviceNumber(void); + uint16_t GetClassModel(void); + uint16_t GetRCTSize(void); + uint8_t GetRBNs(void); + uint8_t GetRCTCopies(void); - void SetOnline(void); - void SetOffline(void);bool IsOnline(void);bool IsAvailable(void); + void SetOnline(void); + void SetOffline(void);bool IsOnline(void);bool IsAvailable(void); - void Write(uint32_t blockNumber, size_t lengthInBytes, uint8_t* buffer); + void Write(uint32_t blockNumber, size_t lengthInBytes, uint8_t* buffer); - uint8_t* Read(uint32_t blockNumber, size_t lengthInBytes); + uint8_t* Read(uint32_t blockNumber, size_t lengthInBytes); - void WriteRCTBlock(uint32_t rctBlockNumber, uint8_t* buffer); + void WriteRCTBlock(uint32_t rctBlockNumber, uint8_t* buffer); - uint8_t* ReadRCTBlock(uint32_t rctBlockNumber); + uint8_t* ReadRCTBlock(uint32_t rctBlockNumber); public: - void on_power_changed(void) override; - void on_init_changed(void) override; + void on_power_changed(void) override; + void on_init_changed(void) override; public: - parameter_bool_c use_image_size = parameter_bool_c(this, "useimagesize", "uis", false, - "Determine unit size from image file instead of drive type"); + parameter_bool_c use_image_size = parameter_bool_c(this, "useimagesize", "uis", false, + "Determine unit size from image file instead of drive type"); private: - struct DriveInfo + struct DriveInfo { - char TypeName[16]; - size_t BlockCount; - uint32_t MediaID; - uint8_t Model; - uint16_t RCTSize;bool Removable;bool ReadOnly; + char TypeName[16]; + size_t BlockCount; + uint32_t MediaID; + uint8_t Model; + uint16_t RCTSize;bool Removable;bool ReadOnly; }; - // - // TODO: add a lot more drive types. - // Does it make sense to support drive types not native to Unibus machines (SCSI types, for example?) - // Need to add a ClassID table entry in that eventuality... - // Also TODO: RCTSize parameters taken from SIMH rq source; how valid are these? - DriveInfo g_driveTable[21] { -// Name Blocks MediaID Model RCTSize Removable ReadOnly - { "RX50", 800, 0x25658032, 7, 0, true, false }, { "RX33", 2400, 0x25658021, 10, 0, - true, false }, { "RD51", 21600, 0x25644033, 6, 36, false, false }, { "RD31", - 41560, 0x2564401f, 12, 3, false, false }, { "RC25", 50902, 0x20643019, 2, 0, - true, false }, { "RC25F", 50902, 0x20643319, 3, 0, true, false }, { "RD52", - 60480, 0x25644034, 8, 4, false, false }, { "RD32", 83236, 0x25641047, 15, 4, - false, false }, { "RD53", 138672, 0x25644035, 9, 5, false, false }, { - "RA80", 237212, 0x20643019, 1, 0, false, false }, { "RD54", 311200, - 0x25644036, 13, 7, false, false }, { "RA60", 400176, 0x22a4103c, 4, 1008, - true, false }, { "RA70", 547041, 0x20643019, 18, 198, false, false }, { - "RA81", 891072, 0x25641051, 5, 2856, false, false }, { "RA82", 1216665, - 0x25641052, 11, 3420, false, false }, { "RA71", 1367310, 0x25641047, 40, - 1428, false, false }, - { "RA72", 1953300, 0x25641048, 37, 2040, false, false }, { "RA90", 2376153, - 0x2564105a, 19, 1794, false, false }, { "RA92", 2940951, 0x2564105c, 29, - 949, false, false }, { "RA73", 3920490, 0x25641049, 47, 198, false, false }, - { "", 0, 0, 0, 0, false, false } }; + // + // TODO: add a lot more drive types. + // Does it make sense to support drive types not native to Unibus machines (SCSI types, for example?) + // Need to add a ClassID table entry in that eventuality... + // Also TODO: RCTSize parameters taken from SIMH rq source; how valid are these? + DriveInfo g_driveTable[21] { + // Name Blocks MediaID Model RCTSize Removable ReadOnly + { "RX50", 800, 0x25658032, 7, 0, true, false }, + { "RX33", 2400, 0x25658021, 10, 0, true, false }, + { "RD51", 21600, 0x25644033, 6, 36, false, false }, + { "RD31", 41560, 0x2564401f, 12, 3, false, false }, + { "RC25", 50902, 0x20643019, 2, 0, true, false }, + { "RC25F", 50902, 0x20643319, 3, 0, true, false }, + { "RD52", 60480, 0x25644034, 8, 4, false, false }, + { "RD32", 83236, 0x25641047, 15, 4, false, false }, + { "RD53", 138672, 0x25644035, 9, 5, false, false }, + { "RA80", 237212, 0x20643019, 1, 0, false, false }, + { "RD54", 311200, 0x25644036, 13, 7, false, false }, + { "RA60", 400176, 0x22a4103c, 4, 1008, true, false }, + { "RA70", 547041, 0x20643019, 18, 198, false, false }, + { "RA81", 891072, 0x25641051, 5, 2856, false, false }, + { "RA82", 1216665, 0x25641052, 11, 3420, false, false }, + { "RA71", 1367310, 0x25641047, 40, 1428, false, false }, + { "RA72", 1953300, 0x25641048, 37, 2040, false, false }, + { "RA90", 2376153, 0x2564105a, 19, 1794, false, false }, + { "RA92", 2940951, 0x2564105c, 29, 949, false, false }, + { "RA73", 3920490, 0x25641049, 47, 198, false, false }, + { "", 0, 0, 0, 0, false, false } + }; - bool SetDriveType(const char* typeName); - void UpdateCapacity(void); - void UpdateMetadata(void); - DriveInfo _driveInfo;bool _online; - uint32_t _unitDeviceNumber; - uint16_t _unitClassModel; - bool _useImageSize; + bool SetDriveType(const char* typeName); + void UpdateCapacity(void); + void UpdateMetadata(void); + DriveInfo _driveInfo;bool _online; + uint32_t _unitDeviceNumber; + uint16_t _unitClassModel; + bool _useImageSize; - // - // RCT ("Replacement and Caching Table") data: - // The size of this area varies depending on the drive. This is - // provided only to appease software that expects the RCT to exist -- - // since there will never be any bad sectors in our disk images - // there is no other purpose. - // This data is not persisted to disk as it is unnecessary. - // - unique_ptr _rctData; + // + // RCT ("Replacement and Caching Table") data: + // The size of this area varies depending on the drive. This is + // provided only to appease software that expects the RCT to exist -- + // since there will never be any bad sectors in our disk images + // there is no other purpose. + // This data is not persisted to disk as it is unnecessary. + // + unique_ptr _rctData; }; diff --git a/10.02_devices/2_src/rh11.cpp b/10.02_devices/2_src/rh11.cpp index e1ff99f..5ffc02e 100755 --- a/10.02_devices/2_src/rh11.cpp +++ b/10.02_devices/2_src/rh11.cpp @@ -14,9 +14,84 @@ #include "unibusadapter.hpp" #include "unibusdevice.hpp" #include "storagecontroller.hpp" +#include "rp_drive.hpp" #include "rh11.hpp" #include "massbus_rp.hpp" +// Maps the unibus register index to the MASSBUS register number. +// -1 entries are local to the rh11. +int32_t _unibusToMassbusRegisterMap[] = +{ + 00, // 776700 + -1, // 776702 + -1, // 776704 + 05, // 776706 + -1, // 776710 + 01, // 776712 + 02, // 776714 + 04, // 776716 + 07, // 776720 + -1, // 776722 + 03, // 776724 + 06, // 776726 + 010, // 776730 + 011, // 776732 + 012, // 776734 + 013, // 776736 + 014, // 776740 + 015, // 776742 + 016, // etc. + 017, + 020, + 021, + 022, + 023, + 024, + 025, + 026, + 027, + 030, + 031, + 032, + 033 +}; + +int32_t _massbusToUnibusRegisterMap[] = +{ + 00, // 0 + 05, + 06, + 012, + 07, + 03, + 013, + 010, + 014, // 10 + 015, + 016, + 017, + 020, + 021, + 022, + 023, + 024, // 20 + 025, + 026, + 027, + 030, + 031, + 032, + 033, + 034, // 30 + 035, + 036, + 037, + -1, + -1, + -1, + -1, +}; + rh11_c::rh11_c() : storagecontroller_c(), _massbus(nullptr), @@ -34,7 +109,6 @@ rh11_c::rh11_c() : // right now it is hardcoded for moving-head disks. set_default_bus_params(0776700, 11, 0254, 5); - // TODO: allow configuration for different device types _massbus.reset(new massbus_rp_c(this)); // The RH11 controller exposes up to 32 registers, not all are used @@ -53,7 +127,7 @@ rh11_c::rh11_c() : strcpy(RH_reg[i]->name, "RHCS1"); RH_reg[i]->active_on_dati = false; RH_reg[i]->active_on_dato = true; - RH_reg[i]->reset_value = 0x0008; + RH_reg[i]->reset_value = 0x0080; RH_reg[i]->writable_bits = 0x037f; break; @@ -61,7 +135,7 @@ rh11_c::rh11_c() : // Word count strcpy(RH_reg[i]->name, "RHWC"); RH_reg[i]->active_on_dati = false; - RH_reg[i]->active_on_dato = false; + RH_reg[i]->active_on_dato = true; RH_reg[i]->reset_value = 0; RH_reg[i]->writable_bits = 0xffff; break; @@ -70,7 +144,7 @@ rh11_c::rh11_c() : // Bus address strcpy(RH_reg[i]->name, "RHBA"); RH_reg[i]->active_on_dati = false; - RH_reg[i]->active_on_dato = false; + RH_reg[i]->active_on_dato = true; RH_reg[i]->reset_value = 0; RH_reg[i]->writable_bits = 0xffff; break; @@ -81,14 +155,14 @@ rh11_c::rh11_c() : RH_reg[i]->active_on_dati = false; RH_reg[i]->active_on_dato = true; RH_reg[i]->reset_value = 0; - RH_reg[i]->writable_bits = 0x003f; + RH_reg[i]->writable_bits = 0x223f; break; case RHDB: // Data Buffer (maintenance only) strcpy(RH_reg[i]->name, "RHDB"); - RH_reg[i]->active_on_dati = true; - RH_reg[i]->active_on_dato = true; + RH_reg[i]->active_on_dati = false; + RH_reg[i]->active_on_dato = false; RH_reg[i]->reset_value = 0; RH_reg[i]->writable_bits = 0xffff; break; @@ -98,13 +172,14 @@ rh11_c::rh11_c() : // This is a "REMOTE" register implemented by the device(s) // attached to the massbus. // - if (_massbus->ImplementsRegister(i)) + int32_t ma = _unibusToMassbusRegisterMap[i]; + if (ma >= 0 && _massbus->ImplementsRegister(ma)) { - strcpy(RH_reg[i]->name, _massbus->RegisterName(i).c_str()); - RH_reg[i]->active_on_dati = _massbus->RegisterActiveOnDATI(i); - RH_reg[i]->active_on_dato = _massbus->RegisterActiveOnDATO(i); - RH_reg[i]->reset_value = _massbus->RegisterResetValue(i); - RH_reg[i]->writable_bits = _massbus->RegisterWritableBits(i); + strcpy(RH_reg[i]->name, _massbus->RegisterName(ma).c_str()); + RH_reg[i]->active_on_dati = _massbus->RegisterActiveOnDATI(ma); + RH_reg[i]->active_on_dato = _massbus->RegisterActiveOnDATO(ma); + RH_reg[i]->reset_value = _massbus->RegisterResetValue(ma); + RH_reg[i]->writable_bits = _massbus->RegisterWritableBits(ma); } else { @@ -115,6 +190,22 @@ rh11_c::rh11_c() : } } + // + // We can have up to eight attached drives. + // Really drives should "belong" to massbus_c... + // but that complicates configuration, etc. + // + drivecount = RH_DRIVE_COUNT; + for (uint32_t i=0; iunitno.value = i; + drive->name.value = name.value + std::to_string(i); + drive->log_label = drive->name.value; + drive->parent = this; + storagedrives.push_back(drive); + } + } rh11_c::~rh11_c() @@ -122,7 +213,55 @@ rh11_c::~rh11_c() } void -rh11_c::write_register( +rh11_c::BusStatus( + bool ready, + bool attention, + bool error, + bool ned) +{ + INFO("Massbus status update attn %d, error %d, ned %d", attention, error, ned); + + uint16_t currentStatus = get_register_dato_value( + RH_reg[RHCS1]); + + INFO("RHCS1 is currently o%o", currentStatus); + + currentStatus &= ~(0140200); // move to constant + currentStatus |= + (attention ? 0100000 : 0) | // check when this actually gets set? + (error ? 0040000 : 0) | + (ready ? 0000200 : 0); // controller ready bit (should clear when busy) + + set_register_dati_value( + RH_reg[RHCS1], + currentStatus, + "BusStatus"); + + INFO("RHCS1 is now o%o", currentStatus); + + currentStatus = get_register_dato_value(RH_reg[RHCS2]); + + INFO("RHCS2 is currently o%o", currentStatus); + + currentStatus &= ~(010000); + currentStatus |= + (ned ? 010000 : 0); // non-existent drive + + set_register_dati_value( + RH_reg[RHCS2], + currentStatus, + "BusStatus"); + + INFO("RHCS2 is now o%o", currentStatus); + + if (attention) + { + Interrupt(); + } +} + +void +rh11_c::WriteRegister( uint32_t reg, uint16_t value) { @@ -133,13 +272,94 @@ rh11_c::write_register( } uint16_t -rh11_c::read_register( +rh11_c::ReadRegister( uint32_t reg) { return get_register_dato_value( RH_reg[_massbusToUnibusRegisterMap[reg]]); } +rp_drive_c* +rh11_c::GetDrive( + uint32_t driveNumber) +{ + assert (driveNumber < RH_DRIVE_COUNT); + + return dynamic_cast(storagedrives[driveNumber]); +} + +uint32_t +rh11_c::GetBusAddress() +{ + return _busAddress; +} + +uint16_t +rh11_c::GetWordCount() +{ + return -get_register_dato_value(RH_reg[RHWC]); +} + +bool +rh11_c::DMAWrite( + uint32_t address, + size_t lengthInWords, + uint16_t* buffer) +{ + assert (address < 0x40000); + + for(size_t i=0;iDMA( + dma_request, + true, + UNIBUS_CONTROL_DATO, + address, + buffer, + lengthInWords); + + INFO("Success: %d", dma_request.success); + + return dma_request.success; +} + +uint16_t* +rh11_c::DMARead( + uint32_t address, + size_t lengthInWords) +{ + assert (address < 0x40000); + + INFO("DMA Read o%o, length %d", address, lengthInWords); + + uint16_t* buffer = new uint16_t[lengthInWords]; + assert (buffer); + + memset(reinterpret_cast(buffer), 0xc3, lengthInWords * 2); + + unibusadapter->DMA( + dma_request, + true, + UNIBUS_CONTROL_DATI, + address, + buffer, + lengthInWords); + + if (dma_request.success) + { + return buffer; + } + else + { + return nullptr; + } +} + + // return false, if illegal parameter value. // verify "new_value", must output error messages bool rh11_c::on_param_changed(parameter_c *param) @@ -224,12 +444,41 @@ void rh11_c::on_after_register_access( _busAddressIncrementProhibit = !!(value & 0x8); _parityTest = !!(value & 0x10); - // TODO: handle System Register Clear (bit 5) + // TODO: handle System Register Clear (bit 5) + if (value & 040) // controller clear + { + // clear error bits + value &= ~(010040); + set_register_dati_value( + RH_reg[RHCS2], + value, + "Reset"); + } INFO("RHCS2: unit %d", _unit); } } break; + case RHWC: // Word count + { + if (UNIBUS_CONTROL_DATO == unibus_control) + { + INFO("RHWC: o%o", value); + } + + } + break; + + case RHBA: // Bus address + { + if (UNIBUS_CONTROL_DATO == unibus_control) + { + _busAddress = (_busAddress & 0x30000) | value; + INFO("RHBA: o%o", _busAddress); + } + } + break; + default: // See if a massbus device wishes to answer if (RH_reg[device_reg->index] != nullptr) @@ -246,13 +495,22 @@ void rh11_c::on_after_register_access( "on_after_register_access"); } } + else + { + INFO("Unhandled register write o%o", device_reg->index); + } break; } } -void rh11_c::invoke_interrupt(void) +void rh11_c::Interrupt(void) { + if(_interruptEnable) + { + INFO("Interrupt!"); + unibusadapter->INTR(intr_request, nullptr, 0); + } } void rh11_c::reset_controller(void) diff --git a/10.02_devices/2_src/rh11.hpp b/10.02_devices/2_src/rh11.hpp index 64677cc..aa4cc44 100755 --- a/10.02_devices/2_src/rh11.hpp +++ b/10.02_devices/2_src/rh11.hpp @@ -19,80 +19,9 @@ using namespace std; #include "unibusdevice.hpp" #include "storagecontroller.hpp" #include "massbus_device.hpp" +#include "rp_drive.hpp" -// Maps the unibus register index to the MASSBUS register number. -// -1 entries are local to the rh11. -int32_t _unibusToMassbusRegisterMap[] = -{ - 00, // 776700 - -1, // 776702 - -1, // 776704 - 05, // 776706 - -1, // 776710 - 01, // 776712 - 02, // 776714 - 04, // 776716 - 07, // 776720 - -1, // 776722 - 03, // 776724 - 06, // 776726 - 010, // 776730 - 011, // 776732 - 012, // 776734 - 013, // 776736 - 014, // 776740 - 015, // 776742 - 016, // etc. - 017, - 020, - 021, - 022, - 023, - 024, - 025, - 026, - 027, - 030, - 031, - 032, - 033 -}; - -int32_t _massbusToUnibusRegisterMap[] = -{ - 00, // 0 - 05, - 06, - 012, - 07, - 03, - 013, - 010, - 014, // 10 - 015, - 016, - 017, - 020, - 021, - 022, - 023, - 024, // 20 - 025, - 026, - 027, - 030, - 031, - 032, - 033, - 034, // 30 - 035, - 036, - 037, - -1, - -1, - -1, - -1, -}; +#define RH_DRIVE_COUNT 8 class rh11_c: public storagecontroller_c { @@ -113,7 +42,7 @@ private: dma_request_c dma_request = dma_request_c(this); // operated by unibusadapter intr_request_c intr_request = intr_request_c(this); - void invoke_interrupt(void); + void Interrupt(void); void reset_controller(void); private: @@ -134,9 +63,18 @@ public: rh11_c(); virtual ~rh11_c(); + void BusStatus(bool ready, bool attention, bool error, bool ned); + // Unibus register access (for devices on massbus) - void write_register(uint32_t reg, uint16_t value); - uint16_t read_register(uint32_t reg); + void WriteRegister(uint32_t reg, uint16_t value); + uint16_t ReadRegister(uint32_t reg); + + rp_drive_c* GetDrive(uint32_t driveNumber); + uint32_t GetBusAddress(); + uint16_t GetWordCount(); + + bool DMAWrite(uint32_t address, size_t lengthInWords, uint16_t* buffer); + uint16_t* DMARead(uint32_t address, size_t lengthInWords); public: diff --git a/10.02_devices/2_src/rp_drive.cpp b/10.02_devices/2_src/rp_drive.cpp new file mode 100644 index 0000000..930c71c --- /dev/null +++ b/10.02_devices/2_src/rp_drive.cpp @@ -0,0 +1,305 @@ +/* + rp_drive.cpp: Implementation of RP0X disks. + + Copyright Vulcan Inc. 2020 via Living Computers: Museum + Labs, Seattle, WA. + Contributed under the BSD 2-clause license. + + */ + +#include +#include + +using namespace std; + +#include "logger.hpp" +#include "utils.hpp" +#include "rp_drive.hpp" + +rp_drive_c::rp_drive_c(storagecontroller_c *controller, uint32_t driveNumber) : + storagedrive_c(controller), + _driveNumber(driveNumber), + _ready(true), + _lst(false), + _aoe(false), + _iae(false), + _wle(false), + _pip(false) +{ + set_workers_count(0) ; // needs no worker() + log_label = "RP"; + SetDriveType("RP06"); +} + +rp_drive_c::~rp_drive_c() +{ + if (file_is_open()) + { + file_close(); + } +} + +// on_param_changed(): +// Handles configuration parameter changes. +bool +rp_drive_c::on_param_changed(parameter_c *param) +{ + // no own "enable" logic + if (&type_name == param) + { + return SetDriveType(type_name.new_value.c_str()); + } + else if (&image_filepath == param) + { + // Try to open the image file. + if (file_open(image_filepath.new_value, true)) + { + image_filepath.value = image_filepath.new_value; + return true; + } + + return false; + } + + return device_c::on_param_changed(param); // more actions (for enable)false; +} + +// +// GetBlockSize(): +// Returns the size, in bytes, of a single sector on this drive. +// This is either 512 or 576 bytes. +// +uint32_t +rp_drive_c::GetSectorSize() +{ + // + // For the time being this is always 512 bytes. + // + return 512; +} + +// +// IsAvailable(): +// Indicates whether this drive is available (i.e. has an image +// assigned to it and can thus be used by the controller.) +// +bool +rp_drive_c::IsAvailable() +{ + return file_is_open(); +} + +bool +rp_drive_c::SeekTo( + uint32_t destinationCylinder) +{ + // TODO: delay by appropriate amount + + _iae = !(destinationCylinder < _driveInfo.Cylinders); + + if (IsAvailable() && !_iae) + { + _currentCylinder = destinationCylinder; + return true; + } + else + { + return false; // no good + } +} + +uint32_t +rp_drive_c::GetCurrentCylinder() +{ + return _currentCylinder; +} + + +// +// TODO: on all reads/writes, an implied seek takes place if the +// specified cylinder is not the current cylinder. +// + +// +// Writes the specified number of words from the provided buffer, +// starting at the specified C/H/S address. +// +bool +rp_drive_c::Write( + uint32_t cylinder, + uint32_t track, + uint32_t sector, + size_t countInWords, + uint16_t* buffer) +{ + _iae = !ValidateCHS(cylinder, track, sector); + _wle = IsWriteLocked(); + + // TODO: handle address overflow + + if (!IsAvailable() || _iae || _wle) + { + return false; + } + else + { + uint32_t offset = GetSectorForCHS(cylinder, track, sector); + file_write(reinterpret_cast(buffer), offset * GetSectorSize(), countInWords * 2); + return true; + } +} + +// +// Reads the specifed number of words starting at the specified logical +// block. Returns a pointer to a buffer containing the data read. +// Caller is responsible for freeing this buffer. +// +bool +rp_drive_c::Read( + uint32_t cylinder, + uint32_t track, + uint32_t sector, + size_t countInWords, + uint16_t** buffer) +{ + + _iae = !ValidateCHS(cylinder, track, sector); + _wle = false; + + if (!IsAvailable() || _iae) + { + *buffer = nullptr; + INFO("Failure: avail %d valid %d", IsAvailable(), ValidateCHS(cylinder, track, sector)); + return false; + } + else + { + *buffer = new uint16_t[countInWords]; + + assert(nullptr != *buffer); + + uint32_t offset = GetSectorForCHS(cylinder, track, sector); + INFO("Read from sector offset o%o", offset); + file_read(reinterpret_cast(*buffer), offset * GetSectorSize(), countInWords * 2); + return true; + } +} + +bool +rp_drive_c::Search( + uint32_t cylinder, + uint32_t track, + uint32_t sector) +{ + _iae = !ValidateCHS(cylinder, track, sector); + + if (!IsAvailable() || _iae) + { + INFO("Failure: avail %d valid %d", IsAvailable(), ValidateCHS(cylinder, track, sector)); + return false; + } + else + { + // This is just a no-op, as we don't emulate read errors. We just delay a tiny bit. + timeout_c timeout; + + INFO("Search commencing."); + _pip = true; + + timeout.wait_ms(50); + _pip = false; + + return true; + } + +} + +uint32_t +rp_drive_c::GetSectorForCHS( + uint32_t cylinder, + uint32_t track, + uint32_t sector) +{ + return (cylinder * _driveInfo.Tracks * _driveInfo.Sectors) + + (track * _driveInfo.Sectors) + + sector; +} + +// +// Updates the capacity parameter of the drive based on the block count and block size. +// +void +rp_drive_c::UpdateCapacity() +{ + // TODO: implement +} + +bool +rp_drive_c::ValidateCHS( + uint32_t cylinder, + uint32_t track, + uint32_t sector) +{ + if (cylinder >= _driveInfo.Cylinders || + track >= _driveInfo.Tracks || + sector >= _driveInfo.Sectors) + { + return false; // Out of range + } + else + { + return true; + } +} + +// +// +// SetDriveType(): +// Updates this drive's type to the specified type (i.e. +// RP05 or RM80). +// If the specified type is not found in our list of known +// drive types, the drive's type is not changed and false +// is returned. +// +bool +rp_drive_c::SetDriveType( + const char* typeName) +{ + // + // Search through drive data table for name, + // and if valid, set the type appropriately. + // + int index = 0; + while (g_driveTable[index].Cylinders != 0) + { + if (!strcasecmp(typeName, g_driveTable[index].TypeName)) + { + _driveInfo = g_driveTable[index]; + type_name.value = _driveInfo.TypeName; + UpdateCapacity(); + return true; + } + + index++; + } + + // Not found + return false; +} + +// +// on_power_changed(): +// Handle power change notifications. +// +void +rp_drive_c::on_power_changed(void) +{ +} + +// +// on_init_changed(): +// Handle INIT signal. +void +rp_drive_c::on_init_changed(void) +{ +} + diff --git a/10.02_devices/2_src/rp_drive.hpp b/10.02_devices/2_src/rp_drive.hpp new file mode 100644 index 0000000..e35a881 --- /dev/null +++ b/10.02_devices/2_src/rp_drive.hpp @@ -0,0 +1,91 @@ +/* + rp_drive.hpp: Implementation of RP0X drives, used with RH11 controller. + + Copyright Vulcan Inc. 2020 via Living Computers: Museum + Labs, Seattle, WA. + Contributed under the BSD 2-clause license. + + */ + +#pragma once + +#include +#include +#include // unique_ptr +#include "parameter.hpp" +#include "storagedrive.hpp" + +// +// Implements the backing store for RP0X disk images +// +class rp_drive_c: public storagedrive_c +{ +public: + rp_drive_c(storagecontroller_c *controller, uint32_t driveNumber); + ~rp_drive_c(void); + + bool on_param_changed(parameter_c *param) override; + + uint32_t GetSectorSize(void); + uint32_t GetType(void); + + bool IsAvailable(void); + bool IsDriveReady(void) { return _ready; } + bool IsWriteLocked(void) { return false; /* for now */ } + bool IsPositioningInProgress(void) { return _pip; } + void SetPositioningInProgress(void) { _pip = true; } // TODO: THIS IS A HACK + bool GetReadLastSector(void) { return _lst; } + bool GetAddressOverflow(void) { return _aoe; } + bool GetInvalidAddress(void) { return _iae; } + bool GetWriteLockError(void) { return _wle; } + void SetDriveReady(void) { _ready = true; } + void ClearDriveReady(void) { _ready = false; } + + bool SeekTo(uint32_t cylinder); + uint32_t GetCurrentCylinder(); + + bool Write(uint32_t cylinder, uint32_t track, uint32_t sector, uint32_t countInWords, uint16_t* buffer); + bool Read(uint32_t cylinder, uint32_t track, uint32_t sector, uint32_t countInWords, uint16_t** outBuffer); + bool Search(uint32_t cylinder, uint32_t track, uint32_t sector); + +public: + void on_power_changed(void) override; + void on_init_changed(void) override; + +private: + uint32_t _currentCylinder; + uint32_t _driveNumber; + bool _ready; + bool _lst; + bool _aoe; + bool _iae; + bool _wle; + bool _pip; + + bool ValidateCHS(uint32_t cylinder, uint32_t track, uint32_t sector); + uint32_t GetSectorForCHS(uint32_t cylinder, uint32_t track, uint32_t sector); + + uint32_t GetBlockSize(); + + bool SetDriveType(const char* typeName); + void UpdateCapacity(); + + struct DriveInfo + { + char TypeName[16]; + uint16_t TypeNumber; + uint16_t Cylinders; + uint16_t Tracks; + uint16_t Sectors; + } _driveInfo; + + + DriveInfo g_driveTable[5] + { + // Name Type Cylinders Tracks Sectors + { "RP04", 020, 411, 19, 22 }, + { "RP05", 021, 411, 19, 22 }, + { "RP06", 022, 815, 19, 22 }, + { "RP07", 023, 630, 32, 50 }, // TODO: verify + { "", 0, 0, 0, 0 }, + }; +}; diff --git a/10.03_app_demo/2_src/makefile b/10.03_app_demo/2_src/makefile index 6c07e1a..02eb6ef 100644 --- a/10.03_app_demo/2_src/makefile +++ b/10.03_app_demo/2_src/makefile @@ -106,6 +106,7 @@ OBJECTS = $(OBJDIR)/application.o \ $(OBJDIR)/mscp_drive.o \ $(OBJDIR)/rh11.o \ $(OBJDIR)/massbus_rp.o \ + $(OBJDIR)/rp_drive.o \ $(OBJDIR)/rs232.o \ $(OBJDIR)/rs232adapter.o \ $(OBJDIR)/dl11w.o \ @@ -250,6 +251,9 @@ $(OBJDIR)/massbus_device.o : $(DEVICE_SRC_DIR)/massbus_device.cpp $(DEVICE_SRC $(OBJDIR)/massbus_rp.o : $(DEVICE_SRC_DIR)/massbus_rp.cpp $(DEVICE_SRC_DIR)/massbus_rp.hpp $(CC) $(CCFLAGS) $< -o $@ +$(OBJDIR)/rp_drive.o : $(DEVICE_SRC_DIR)/rp_drive.cpp $(DEVICE_SRC_DIR)/rp_drive.hpp + $(CC) $(CCFLAGS) $< -o $@ + $(OBJDIR)/rs232.o : $(DEVICE_SRC_DIR)/rs232.cpp $(DEVICE_SRC_DIR)/rs232.hpp $(CC) $(CCFLAGS) $< -o $@