From 06b2a6ebc97b2c660116817f9ef51ee3093cd311 Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Wed, 1 Apr 2020 01:56:11 +0200 Subject: [PATCH] Major refactoring for overlapped-seeks. Moved most of the RP drive logic to rp_drive_c, where I should have put it to begin with. Working better, still a few glitches to track down and a number of register bits that need to be hooked up properly. --- 10.02_devices/2_src/massbus_rp.cpp | 468 ++++------------------------- 10.02_devices/2_src/massbus_rp.hpp | 64 +--- 10.02_devices/2_src/rh11.cpp | 6 +- 10.02_devices/2_src/rh11.hpp | 3 +- 10.02_devices/2_src/rp_drive.cpp | 389 +++++++++++++++++++++++- 10.02_devices/2_src/rp_drive.hpp | 109 +++++-- 6 files changed, 523 insertions(+), 516 deletions(-) diff --git a/10.02_devices/2_src/massbus_rp.cpp b/10.02_devices/2_src/massbus_rp.cpp index 9f08fe2..7b4dfb4 100644 --- a/10.02_devices/2_src/massbus_rp.cpp +++ b/10.02_devices/2_src/massbus_rp.cpp @@ -15,14 +15,6 @@ using namespace std; #include "massbus_device.hpp" #include "massbus_rp.hpp" -void* WorkerInit( - void* context) -{ - massbus_rp_c* rp = reinterpret_cast(context); - rp->Worker(); - return nullptr; -} - void* SpinInit( void* context) { @@ -36,40 +28,22 @@ massbus_rp_c::massbus_rp_c( device_c(), _controller(controller), _selectedUnit(0), - _workerState(WorkerState::Idle), - _workerWakeupCond(PTHREAD_COND_INITIALIZER), - _workerMutex(PTHREAD_MUTEX_INITIALIZER), - _err(false), - _ata(false), _ned(false), _attnSummary(0), - _rmr(false) + _updateLock(PTHREAD_MUTEX_INITIALIZER) + { 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)); - pthread_attr_t attribs2; pthread_attr_init(&attribs2); - status = pthread_create( + uint32_t status = pthread_create( &_spinThread, &attribs2, &SpinInit, reinterpret_cast(this)); - - } massbus_rp_c::~massbus_rp_c() @@ -125,9 +99,15 @@ void massbus_rp_c::SelectUnit( uint32_t unit) { - _selectedUnit = unit; - UpdateStatus(_selectedUnit, false, false); - UpdateDriveRegisters(); + if (_selectedUnit != unit) + { + _selectedUnit = unit; + + // Let the drive know it's been selected, this + // may cause status register updates. + SelectedDrive()->Select(); + UpdateDriveRegisters(); + } } void @@ -135,28 +115,17 @@ massbus_rp_c::WriteRegister( uint32_t reg, uint16_t value) { - DEBUG("RP reg write: unit %d register 0%o value 0%o", _selectedUnit, reg, value); + INFO ("RP reg write: unit %d register 0%o value 0%o", _selectedUnit, reg, value); rp_drive_c* drive = SelectedDrive(); - if (!drive->IsDriveReady() && - reg != 0 && // CS1 is allowed as long as GO isn't set (will be checked in DoCommand) - reg != (uint32_t)Registers::AttentionSummary) - { - // Any attempt to modify a drive register other than Attention Summary - // while the drive is busy is invalid. - // For now treat this as fatal (it's not but real code shouldn't be doing it so this - // is a diagnostic at the moment.) - FATAL("Register modification while drive busy."); - _rmr = true; - UpdateStatus(_selectedUnit, false, false); - return; - } - switch(static_cast(reg)) { case Registers::Control: - DoCommand(value); + // + // Select unit as necessary. + // + drive->DoCommand(value); break; case Registers::DesiredSectorTrackAddress: @@ -185,8 +154,7 @@ massbus_rp_c::WriteRegister( // // Based on diagnostic (ZRJGE0) behavior, writing ANY value here forces an error. // - DEBUG("Error 1 Reg write o%o, value is now o%o", value, _error1); - UpdateStatus(_selectedUnit, false, true); // Force composite error. + drive->ForceDiagnosticError(); break; default: @@ -195,159 +163,6 @@ massbus_rp_c::WriteRegister( } } -void massbus_rp_c::DoCommand( - uint16_t command) -{ - FunctionCode function = static_cast((command & RP_FUNC) >> 1); - rp_drive_c* drive = SelectedDrive(); - - // check for GO bit; if unset we have nothing to do here, - if ((command & RP_GO) == 0) - { - return; - } - - DEBUG("RP function 0%o, unit %o", function, _selectedUnit); - - if (!drive->IsConnected()) - { - // Early return for disconnected drives; - // set NED and ERR bits - _err = true; - _ata = true; - _ned = true; // TODO: should be done at RH11 level! - drive->ClearVolumeValid(); - UpdateStatus(_selectedUnit, true, false); - return; - } - - if (!SelectedDrive()->IsDriveReady()) - { - // This should never happen. - FATAL("Command sent while not ready!"); - } - - _ned = false; - _ata = false; - - switch(static_cast(function)) - { - case FunctionCode::Nop: - // Nothing. - UpdateStatus(_selectedUnit, true, false); - break; - - case FunctionCode::Unload: - // Unsure how best to implement this: - // This puts the drive in STANDBY state, meaning the heads are - // retracted and the spindle powers down. The command doesn't actually *finish* - // until the pack is manually spun back up. This could be implemented - // by unloading the disk image and waiting until a new one is loaded. - // Right now I'm just treating it as a no-op, at least until I can find a good - // way to test it using real software. - DEBUG("RP Unload"); - _ata = true; - UpdateStatus(_selectedUnit, true, false); - break; - - - case FunctionCode::DriveClear: - // p. 2-11 of EK-RP056-MM-01: - // "The following registers and conditions within the DCL are cleared - // by this command: - // - Status Register (ATA and ERR status bits) - // - All three Error registers - // - Attention Summary Register - // - ECC Position and Pattern Registers - // - The Diagnostic Mode Bit - - DEBUG("RP Drive Clear"); - _ata = false; - _attnSummary = 0; - _error1 = 0; - _error2 = 0; - _error3 = 0; - _rmr = false; - _ned = false; - _maint = false; - UpdateStatus(_selectedUnit, false, false); - break; - - case FunctionCode::Release: - DEBUG("RP Release"); - // This is a no-op, this only applies to dual-ported configurations, - // which we are not. - break; - - case FunctionCode::ReadInPreset: - DEBUG("RP Read-In Preset"); - // - // "This command sets the VV (volume valid) bit, clears the desired - // sector/track address register, and clears the FMT, HCI, and ECI - // bits in the offset register. It is used to bootstrap the device." - // - drive->SetVolumeValid(); - drive->SetDriveReady(); - drive->SetDesiredSector(0); - drive->SetDesiredTrack(0);; - drive->SetOffset(0); - UpdateDriveRegisters(); - UpdateStatus(_selectedUnit, false, false); /* do not interrupt */ - break; - - case FunctionCode::PackAcknowledge: - DEBUG("RP Pack Acknowledge"); - drive->SetVolumeValid(); - UpdateStatus(_selectedUnit, false, false); - break; - - case FunctionCode::ReadData: - case FunctionCode::WriteData: - case FunctionCode::WriteHeaderAndData: - case FunctionCode::ReadHeaderAndData: - case FunctionCode::Search: - case FunctionCode::Seek: - case FunctionCode::Recalibrate: - case FunctionCode::Offset: - case FunctionCode::ReturnToCenterline: - DEBUG("RP Read/Write Data or head-motion command"); - { - // Clear the unit's DRY bit - drive->ClearDriveReady(); - - if (function == FunctionCode::Search || - function == FunctionCode::Seek || - function == FunctionCode::Recalibrate || - function == FunctionCode::Offset || - function == FunctionCode::ReturnToCenterline) - { - drive->SetPositioningInProgress(); - } - - UpdateStatus(_selectedUnit, false, false); - - 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: - FATAL("Unimplemented RP function."); - break; - } - -} - uint16_t massbus_rp_c::ReadRegister( uint32_t reg) @@ -377,65 +192,49 @@ massbus_rp_c::ReadRegister( // Register update functions // void -massbus_rp_c::UpdateStatus( +massbus_rp_c::DriveStatus( uint32_t unit, - bool complete, - bool diagForceError) + uint16_t status, + uint16_t error1, + bool ata, + bool complete) { + pthread_mutex_lock(&_updateLock); - // Most of these status bits (except possibly ATTN) - // are for the currently selected drive. - rp_drive_c* drive = SelectedDrive(); - - _error1 = - (_rmr ? 04 : 0) | - (drive->GetAddressOverflow() ? 01000 : 0) | - (drive->GetInvalidAddress() ? 02000 : 0) | - (drive->GetWriteLockError() ? 04000 : 0); - - if (_error1 != 0) + // Update status registers only for the selected drive. + if (unit == _selectedUnit) { - _err = true; - _ata = true; + _controller->WriteRegister(static_cast(Registers::Status), status); + _controller->WriteRegister(static_cast(Registers::Error1), error1); } - else if (diagForceError) + + // Update the Attention Summary register for the reporting drive: + if (ata) { - _err = false; - _ata = true; - } - else - { - _err = false; - } - - _status = - (drive->GetVolumeValid() ? 0100 : 0) | - (drive->IsDriveReady() ? 0200 : 0) | - (0400) | // Drive preset -- always set for a single-controller disk - (drive->GetReadLastSector() ? 02000 : 0) | // Last sector read - (drive->IsWriteLocked() ? 04000 : 0) | // Write lock - (drive->IsPackLoaded() ? 010000 : 0) | // Medium online - (drive->IsPositioningInProgress() ? 020000 : 0) | // PIP - (_err ? 040000 : 0) | // Composite error - (_ata ? 0100000 : 0); - - DEBUG("Unit %d Status: o%o", _selectedUnit, _status); - _controller->WriteRegister(static_cast(Registers::Status), _status); - _controller->WriteRegister(static_cast(Registers::Error1), _error1); - - // Update the Attention Summary register if the requested disk is online: - // Note that we may be setting ATTN for a drive other than the currently selected one. - if (_ata && GetDrive(unit)->IsConnected()) - { - _attnSummary |= (0x1 << unit); // TODO: these only get set, and are latched until - // manually cleared? - DEBUG("Attention Summary is now o%o", _attnSummary); + _attnSummary |= (0x1 << unit); + INFO ("Attention Summary is now o%o", _attnSummary); } _controller->WriteRegister(static_cast(Registers::AttentionSummary), _attnSummary); // Inform controller of status update. - _controller->BusStatus(complete, drive->IsDriveReady(), _ata, _err, drive->IsConnected(), _ned); + // TODO: ready status is a hack that serializes drive commands; + // a real RH11/RP06 setup allows overlapped seeks, we do not (yet). + + bool ready = true; + + for(int i=0;i<8;i++) + { + if (GetDrive(i)->IsConnected() && !GetDrive(i)->IsDriveReady()) + { + ready = false;// We're ready if there isn't a command in progress right now. + break; + } + } + + _controller->BusStatus(complete, ready, ata, (error1 != 0), SelectedDrive()->IsConnected(), _ned); + + pthread_mutex_unlock(&_updateLock); } void @@ -460,17 +259,8 @@ void massbus_rp_c::Reset() { // Reset registers to their defaults - _ata = false; _attnSummary = 0; - _error1 = 0; - _rmr = false; - _selectedUnit = 0; - UpdateStatus(_selectedUnit, false, false); - - UpdateDriveRegisters(); - - _newCommand.ready = false; - + SelectUnit(0); } rp_drive_c* @@ -486,162 +276,6 @@ massbus_rp_c::GetDrive(uint16_t unit) return _controller->GetDrive(unit); } -// Background worker function -void -massbus_rp_c::Worker() -{ - worker_init_realtime_priority(rt_device); - - _workerState = WorkerState::Idle; - WorkerCommand command = { 0 }; - - timeout_c timeout; - - DEBUG("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; - break; - - case WorkerState::Execute: - { - rp_drive_c* drive = GetDrive(command.unit); - switch(command.function) - { - case FunctionCode::ReadData: - { - uint16_t* buffer = nullptr; - if (drive->Read( - command.word_count, - &buffer)) - { - // - // Data read: do DMA transfer to memory. - // - _controller->DiskReadTransfer( - command.bus_address, - command.word_count, - buffer); - - // Free buffer - delete buffer; - } - else - { - // Read failed: - DEBUG("Read failed."); - _ata = true; - } - - // Return drive to ready state - drive->SetDriveReady(); - - _workerState = WorkerState::Finish; - } - break; - - case FunctionCode::WriteData: - { - // - // Data write: do DMA transfer from memory. - // - uint16_t* buffer = _controller->DiskWriteTransfer( - command.bus_address, - command.word_count); - - if (!buffer || !drive->Write( - command.word_count, - buffer)) - { - // Write failed: - DEBUG("Write failed."); - _ata = true; - } - - delete buffer; - - // Return drive to ready state - drive->SetDriveReady(); - - _workerState = WorkerState::Finish; - } - break; - - case FunctionCode::Search: - { - if (!drive->Search()) - { - // Search failed - DEBUG("Search failed"); - } - - - // Return to ready state, set attention bit. - drive->SetDriveReady(); - _ata = true; - _workerState = WorkerState::Finish; - } - break; - - case FunctionCode::Seek: - if (!drive->SeekTo()) - { - // Seek failed - DEBUG("Seek failed"); - } - drive->SetDriveReady(); - _ata = true; - _workerState = WorkerState::Finish; - break; - - case FunctionCode::Offset: - case FunctionCode::ReturnToCenterline: - // We don't support adjusting the head position between - // cylinders so these are both effectively no-ops, but - // they're no-ops that need to take a small amount of time - // to complete. - DEBUG("OFFSET/RETURN TO CL"); - timeout.wait_ms(10); - drive->SetDriveReady(); - _ata = true; - _workerState = WorkerState::Finish; - break; - - default: - FATAL("Unimplemented drive function %d", command.function); - break; - } - } - break; - - case WorkerState::Finish: - _workerState = WorkerState::Idle; - GetDrive(_newCommand.unit)->SetDriveReady(); - UpdateStatus(_newCommand.unit, true, false); - UpdateDriveRegisters(); - break; - - } - } -} - void massbus_rp_c::Spin(void) { diff --git a/10.02_devices/2_src/massbus_rp.hpp b/10.02_devices/2_src/massbus_rp.hpp index 5d32830..6818b6b 100644 --- a/10.02_devices/2_src/massbus_rp.hpp +++ b/10.02_devices/2_src/massbus_rp.hpp @@ -16,6 +16,8 @@ using namespace std; #include "rh11.hpp" #include "massbus_device.hpp" +class rp_drive_c; + // Registers enum class Registers { @@ -37,31 +39,6 @@ enum class Registers ECCPattern = 017, }; -// Control Function codes -#define RP_GO 01 -#define RP_FUNC 076 - -enum class FunctionCode -{ - Nop = 00, - Unload = 01, - Recalibrate = 03, - DriveClear = 04, - Release = 05, - Search = 014, - WriteCheckData = 024, - WriteCheckHeaderAndData = 025, - WriteData = 030, - WriteHeaderAndData = 031, - ReadData = 034, - ReadHeaderAndData = 035, - Seek = 02, - Offset = 06, - ReturnToCenterline = 07, - PackAcknowledge = 011, - ReadInPreset = 010, -}; - // // Register metadata // @@ -105,35 +82,17 @@ public: void WriteRegister(uint32_t register, uint16_t value) override; uint16_t ReadRegister(uint32_t register) override; + void DriveStatus(uint32_t unit, uint16_t status, uint16_t error1, bool ata, bool complete); + void SelectUnit(uint32_t unit); // Background worker functions - void Worker(); void Spin(); private: - struct WorkerCommand - { - uint16_t unit; - volatile uint32_t bus_address; - volatile uint32_t word_count; - volatile FunctionCode function; - volatile bool ready; - } _newCommand; - - enum WorkerState - { - Idle = 0, - Execute = 1, - Finish = 2, - } _workerState; - - void DoCommand(uint16_t command); - void on_power_changed(void) override; void on_init_changed(void) override; - void UpdateStatus(uint32_t unit, bool completion, bool diagForceError); void UpdateDriveRegisters(); rp_drive_c* SelectedDrive(); @@ -167,26 +126,17 @@ private: volatile uint32_t _selectedUnit; // Register data - volatile uint16_t _status; - volatile uint16_t _error1; volatile uint16_t _maint; volatile uint16_t _attnSummary; volatile uint16_t _error2; volatile uint16_t _error3; - // Status bits that we track - volatile bool _err; - volatile bool _ata; - volatile bool _rmr; - // RH11 ready signal (ugly: this should be in the rh11 code!) volatile bool _ned; // ditto - // Worker thread - pthread_t _workerThread; - pthread_cond_t _workerWakeupCond; - pthread_mutex_t _workerMutex; - + // Drive status update lock + pthread_mutex_t _updateLock; + // Spin thread pthread_t _spinThread; }; diff --git a/10.02_devices/2_src/rh11.cpp b/10.02_devices/2_src/rh11.cpp index efb3686..4449ee4 100755 --- a/10.02_devices/2_src/rh11.cpp +++ b/10.02_devices/2_src/rh11.cpp @@ -14,9 +14,9 @@ #include "unibusadapter.hpp" #include "unibusdevice.hpp" #include "storagecontroller.hpp" -#include "rp_drive.hpp" #include "rh11.hpp" #include "massbus_rp.hpp" +#include "rp_drive.hpp" // Maps the unibus register index to the MASSBUS register number. // -1 entries are local to the rh11. @@ -214,7 +214,7 @@ rh11_c::rh11_c() : drivecount = RH_DRIVE_COUNT; for (uint32_t i=0; i(_massbus.get()), i); drive->unitno.value = i; drive->name.value = name.value + std::to_string(i); drive->log_label = drive->name.value; @@ -606,7 +606,7 @@ void rh11_c::on_after_register_access( { DEBUG("Forced interrupt."); unibusadapter->INTR(intr_request, nullptr, 0); - } + } } DEBUG("RHCS1: IE %d BA o%o func o%o go %d", _interruptEnable, _busAddress, _function, _go); diff --git a/10.02_devices/2_src/rh11.hpp b/10.02_devices/2_src/rh11.hpp index 3cac18b..a9a2806 100755 --- a/10.02_devices/2_src/rh11.hpp +++ b/10.02_devices/2_src/rh11.hpp @@ -19,7 +19,8 @@ using namespace std; #include "unibusdevice.hpp" #include "storagecontroller.hpp" #include "massbus_device.hpp" -#include "rp_drive.hpp" + +class rp_drive_c; #define RH_DRIVE_COUNT 8 diff --git a/10.02_devices/2_src/rp_drive.cpp b/10.02_devices/2_src/rp_drive.cpp index 9ee77dc..ee1d997 100644 --- a/10.02_devices/2_src/rp_drive.cpp +++ b/10.02_devices/2_src/rp_drive.cpp @@ -13,26 +13,39 @@ using namespace std; #include "logger.hpp" #include "utils.hpp" +#include "rh11.hpp" +#include "massbus_rp.hpp" #include "rp_drive.hpp" -rp_drive_c::rp_drive_c(storagecontroller_c *controller, uint32_t driveNumber) : +rp_drive_c::rp_drive_c(rh11_c *controller, massbus_rp_c* bus, uint32_t driveNumber) : storagedrive_c(controller), _driveNumber(driveNumber), _desiredCylinder(0), _currentCylinder(0), _desiredTrack(0), _offset(0), + _ata(false), + _ned(false), + _rmr(false), _ready(true), _lst(false), _aoe(false), _iae(false), _wle(false), _pip(false), - _vv(false) + _vv(false), + _workerState(WorkerState::Idle), + _workerWakeupCond(PTHREAD_COND_INITIALIZER), + _workerMutex(PTHREAD_MUTEX_INITIALIZER) { - set_workers_count(0) ; // needs no worker() + set_workers_count(1); log_label = "RP"; SetDriveType("RP06"); + + _newCommand = { 0 }; + + _controller = controller; + _bus = bus; } rp_drive_c::~rp_drive_c() @@ -68,9 +81,8 @@ rp_drive_c::on_param_changed(parameter_c *param) // New image; this is analogous to a new pack being spun up. // Set the Volume Valid bit so software knows things have changed. - // TODO: should alert massbus_rp so that it can update status regs immediately. - SetVolumeValid(); - + _vv = true; + UpdateStatus(false, false); return true; } @@ -81,7 +93,7 @@ rp_drive_c::on_param_changed(parameter_c *param) } // -// GetBlockSize(): +// GetSectorSize(): // Returns the size, in bytes, of a single sector on this drive. // This is either 512 or 576 bytes. // @@ -105,6 +117,364 @@ rp_drive_c::IsPackLoaded() return file_is_open(); } +void +rp_drive_c::Select() +{ + // Just force an update of status registers for this disk. + UpdateStatus(false, false); +} + +void rp_drive_c::DoCommand( + uint16_t command) +{ + FunctionCode function = static_cast((command & RP_FUNC) >> 1); + + // check for GO bit; if unset we have nothing to do here, + if ((command & RP_GO) == 0) + { + return; + } + + INFO ("RP function 0%o, unit %o", function, _driveNumber); + + if (!_ready) + { + // For now, halt -- This should never happen with valid code. + // After things are stable, this should set error bits. + FATAL("Unit %d - Command sent while not ready!", _driveNumber); + } + + _ned = false; + _ata = false; + + switch(static_cast(function)) + { + case FunctionCode::Nop: + // Nothing. + UpdateStatus(true, false); + break; + + case FunctionCode::Unload: + // Unsure how best to implement this: + // This puts the drive in STANDBY state, meaning the heads are + // retracted and the spindle powers down. The command doesn't actually *finish* + // until the pack is manually spun back up. This could be implemented + // by unloading the disk image and waiting until a new one is loaded. + // Right now I'm just treating it as a no-op, at least until I can find a good + // way to test it using real software. + DEBUG("RP Unload"); + _ata = true; + UpdateStatus(true, false); + break; + + + case FunctionCode::DriveClear: + // p. 2-11 of EK-RP056-MM-01: + // "The following registers and conditions within the DCL are cleared + // by this command: + // - Status Register (ATA and ERR status bits) + // - All three Error registers + // - Attention Summary Register + // - ECC Position and Pattern Registers + // - The Diagnostic Mode Bit + INFO ("RP Drive Clear"); + _ata = false; + _rmr = false; + _ned = false; + UpdateStatus(false, false); + break; + + case FunctionCode::Release: + DEBUG("RP Release"); + // This is a no-op, this only applies to dual-ported configurations, + // which we are not. + break; + + case FunctionCode::ReadInPreset: + INFO ("RP Read-In Preset"); + // + // "This command sets the VV (volume valid) bit, clears the desired + // sector/track address register, and clears the FMT, HCI, and ECI + // bits in the offset register. It is used to bootstrap the device." + // + _vv = true; + _ready = true; + _desiredSector = 0; + _desiredTrack = 0; + _offset = 0; + UpdateStatus(false, false); + break; + + case FunctionCode::PackAcknowledge: + DEBUG("RP Pack Acknowledge"); + _vv = true; + UpdateStatus(false, false); + break; + + case FunctionCode::ReadData: + case FunctionCode::WriteData: + case FunctionCode::WriteHeaderAndData: + case FunctionCode::ReadHeaderAndData: + case FunctionCode::Search: + case FunctionCode::Seek: + case FunctionCode::Recalibrate: + case FunctionCode::Offset: + case FunctionCode::ReturnToCenterline: + INFO ("RP Read/Write Data or head-motion command"); + { + // If this drive is not connected, head-motion commands cannot be + // executed. + if (!IsConnected()) + { + _ata = true; + _ned = true; + UpdateStatus(true, false); + return; + } + + // Clear DRY. + _ready = false; + + if (function == FunctionCode::Search || + function == FunctionCode::Seek || + function == FunctionCode::Recalibrate || + function == FunctionCode::Offset || + function == FunctionCode::ReturnToCenterline) + { + // Positioning in progress. + _pip = true; + } + + UpdateStatus(false, false); + + pthread_mutex_lock(&_workerMutex); + + // Sanity check: no other command should be executing on this drive + // right now. + if (_newCommand.ready) + { + FATAL("Command o%o sent while worker for drive %d is busy!", + function, _driveNumber); + } + + // Save a copy of command data for the worker to consume + _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: + FATAL("Unimplemented RP function."); + break; + } +} + +// Background worker function +void +rp_drive_c::worker(unsigned instance) +{ + UNUSED(instance); + + worker_init_realtime_priority(rt_device); + + _workerState = WorkerState::Idle; + WorkerCommand command = { 0 }; + + timeout_c timeout; + + INFO ("rp_drive 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; + pthread_mutex_unlock(&_workerMutex); + + _workerState = WorkerState::Execute; + break; + + case WorkerState::Execute: + { + switch(command.function) + { + case FunctionCode::ReadData: + { + uint16_t* buffer = nullptr; + if (Read( + command.word_count, + &buffer)) + { + // + // Data read: do DMA transfer to memory. + // + _controller->DiskReadTransfer( + command.bus_address, + command.word_count, + buffer); + + // Free buffer + delete buffer; + } + else + { + // Read failed: + DEBUG("Read failed."); + _ata = true; + } + + _workerState = WorkerState::Finish; + } + break; + + case FunctionCode::WriteData: + { + // + // Data write: do DMA transfer from memory. + // + uint16_t* buffer = _controller->DiskWriteTransfer( + command.bus_address, + command.word_count); + + if (!buffer || !Write( + command.word_count, + buffer)) + { + // Write failed: + DEBUG("Write failed."); + _ata = true; + } + + delete buffer; + + _workerState = WorkerState::Finish; + } + break; + + case FunctionCode::Search: + { + if (!Search()) + { + // Search failed + DEBUG("Search failed"); + } + + // Return to ready state, set attention bit. + _ata = true; + _workerState = WorkerState::Finish; + } + break; + + case FunctionCode::Seek: + if (!SeekTo()) + { + // Seek failed + DEBUG("Seek failed"); + } + _ata = true; + _workerState = WorkerState::Finish; + break; + + case FunctionCode::Offset: + case FunctionCode::ReturnToCenterline: + // We don't support adjusting the head position between + // cylinders so these are both effectively no-ops, but + // they're no-ops that need to take a small amount of time + // to complete. + DEBUG("OFFSET/RETURN TO CL"); + timeout.wait_ms(10); + _ata = true; + _workerState = WorkerState::Finish; + break; + + default: + FATAL("Unimplemented drive function %d", command.function); + break; + } + } + break; + + case WorkerState::Finish: + _workerState = WorkerState::Idle; + // Drive is ready again. + _ready = true; + _pip = false; + pthread_mutex_lock(&_workerMutex); + _newCommand.ready = false; + pthread_mutex_unlock(&_workerMutex); + UpdateStatus(true, false); + break; + + } + } +} + +void +rp_drive_c::ForceDiagnosticError() +{ + UpdateStatus(false, true); +} + +// +// Register update functions +// +void +rp_drive_c::UpdateStatus( + bool complete, + bool diagForceError) +{ + uint16_t error1 = + (_rmr ? 04 : 0) | + (_aoe ? 01000 : 0) | + (_iae ? 02000 : 0) | + (_wle ? 04000 : 0); + + bool err = false; + + if (error1 != 0) + { + err = true; + _ata = true; + } + else if (diagForceError) + { + _ata = true; + } + + uint16_t status = + (_vv ? 0100 : 0) | + (_ready ? 0200 : 0) | + (0400) | // Drive preset -- always set for a single-controller disk + (_lst ? 02000 : 0) | // Last sector read + (IsWriteLocked() ? 04000 : 0) | // Write lock + (IsPackLoaded() ? 010000 : 0) | // Medium online + (_pip ? 020000 : 0) | // PIP + (err ? 040000 : 0) | // Composite error + (_ata ? 0100000 : 0); + + INFO ("Unit %d error1: o%o", _driveNumber, error1); + INFO ("Unit %d Status: o%o", _driveNumber, status); + + // Inform controller of status update. + _bus->DriveStatus(_driveNumber, status, error1, _ata, complete); +} + bool rp_drive_c::SeekTo() { @@ -157,7 +527,7 @@ rp_drive_c::Write( uint32_t offset = GetSectorForCHS(_currentCylinder, _desiredTrack, _desiredSector); file_write(reinterpret_cast(buffer), offset * GetSectorSize(), countInWords * 2); timeout_c timeout; - timeout.wait_us(500); + timeout.wait_us(2500); return true; } @@ -195,7 +565,7 @@ rp_drive_c::Read( DEBUG("Read from sector offset o%o", offset); file_read(reinterpret_cast(*buffer), offset * GetSectorSize(), countInWords * 2); timeout_c timeout; - timeout.wait_us(500); + timeout.wait_us(2500); return true; } @@ -218,7 +588,6 @@ rp_drive_c::Search(void) DEBUG("Search commencing."); timeout.wait_ms(20); - _pip = false; DEBUG("Search completed."); _currentCylinder = _desiredCylinder; diff --git a/10.02_devices/2_src/rp_drive.hpp b/10.02_devices/2_src/rp_drive.hpp index 38119a2..4987042 100644 --- a/10.02_devices/2_src/rp_drive.hpp +++ b/10.02_devices/2_src/rp_drive.hpp @@ -13,6 +13,34 @@ #include // unique_ptr #include "parameter.hpp" #include "storagedrive.hpp" +#include "massbus_rp.hpp" +#include "rh11.hpp" + +// Control Function codes +#define RP_GO 01 +#define RP_FUNC 076 + +enum class FunctionCode +{ + Nop = 00, + Unload = 01, + Recalibrate = 03, + DriveClear = 04, + Release = 05, + Search = 014, + WriteCheckData = 024, + WriteCheckHeaderAndData = 025, + WriteData = 030, + WriteHeaderAndData = 031, + ReadData = 034, + ReadHeaderAndData = 035, + Seek = 02, + Offset = 06, + ReturnToCenterline = 07, + PackAcknowledge = 011, + ReadInPreset = 010, +}; + // // Implements the backing store for RP0X disk images @@ -20,16 +48,16 @@ class rp_drive_c: public storagedrive_c { public: - rp_drive_c(storagecontroller_c *controller, uint32_t driveNumber); + rp_drive_c(rh11_c *controller, massbus_rp_c* bus, uint32_t driveNumber); ~rp_drive_c(void); void Reset(); - bool on_param_changed(parameter_c *param) override; - - uint32_t GetSectorSize(void); - uint32_t GetType(void); + void Select(); + void DoCommand(uint16_t command); + void ForceDiagnosticError(); + // Register data reported via RH11 void SetDesiredCylinder(uint32_t cylinder) { _desiredCylinder = cylinder; } void SetDesiredTrack(uint32_t track) { _desiredTrack = track; } void SetDesiredSector(uint32_t sector) { _desiredSector = sector; } @@ -39,41 +67,53 @@ public: uint32_t GetDesiredSector(void) { return _desiredSector; } uint16_t GetOffset(void) { return _offset; } uint32_t GetCurrentCylinder(void) { return _currentCylinder; } - - bool IsConnected(void) { return true; /* todo: make config. parameter */ } - bool IsPackLoaded(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; } - void SetVolumeValid(void) { _vv = true; } - void ClearVolumeValid(void) { _vv = false; } - bool GetVolumeValid(void) { return _vv; } + uint16_t GetDriveType(void) { return _driveInfo.TypeNumber; } uint16_t GetSerialNumber(void) { return 012345; } // TODO: Make configurable parameter + uint32_t GetSectorSize(void); - bool SeekTo(); - bool Write(uint32_t countInWords, uint16_t* buffer); - bool Read(uint32_t countInWords, uint16_t** outBuffer); - bool Search(); - + // Useful public status bits + bool IsConnected(void) { return true; /* todo: make config. parameter */ } + bool IsDriveReady(void) { return _ready; } + bool IsWriteLocked(void) { return false; /* also make config. parameter */ } + bool IsPackLoaded(void); + public: + + // storagedrive_c methods + bool on_param_changed(parameter_c *param) override; void on_power_changed(void) override; void on_init_changed(void) override; + // background worker function + void worker(unsigned instance) override; private: + + struct WorkerCommand + { + volatile uint32_t bus_address; + volatile uint32_t word_count; + volatile FunctionCode function; + volatile bool ready; + } _newCommand; + + enum WorkerState + { + Idle = 0, + Execute = 1, + Finish = 2, + } _workerState; + + uint32_t _desiredCylinder; uint32_t _desiredTrack; uint32_t _desiredSector; uint16_t _offset; uint32_t _currentCylinder; uint32_t _driveNumber; + bool _ata; + bool _ned; + bool _rmr; bool _ready; bool _lst; bool _aoe; @@ -82,14 +122,27 @@ private: bool _pip; bool _vv; + + rh11_c* _controller; + massbus_rp_c* _bus; + + // Worker mutex + pthread_cond_t _workerWakeupCond; + pthread_mutex_t _workerMutex; + + bool SeekTo(); + bool Write(uint32_t countInWords, uint16_t* buffer); + bool Read(uint32_t countInWords, uint16_t** outBuffer); + bool Search(); + 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(); + void UpdateStatus(bool complete, bool diagForceError); + struct DriveInfo { char TypeName[16];