diff --git a/10.02_devices/2_src/massbus_rp.cpp b/10.02_devices/2_src/massbus_rp.cpp index 7b4dfb4..b25e2a1 100644 --- a/10.02_devices/2_src/massbus_rp.cpp +++ b/10.02_devices/2_src/massbus_rp.cpp @@ -107,6 +107,7 @@ massbus_rp_c::SelectUnit( // may cause status register updates. SelectedDrive()->Select(); UpdateDriveRegisters(); + UpdateDriveInfo(); } } @@ -122,9 +123,6 @@ massbus_rp_c::WriteRegister( switch(static_cast(reg)) { case Registers::Control: - // - // Select unit as necessary. - // drive->DoCommand(value); break; @@ -142,9 +140,16 @@ massbus_rp_c::WriteRegister( case Registers::AttentionSummary: // Clear bits in the Attention Summary register specified in the // written value: - _attnSummary &= ~(value & 0xff); - _controller->WriteRegister(reg, _attnSummary); - DEBUG("Attention Summary write o%o, value is now o%o", value, _attnSummary); + for(int i=0;i<8;i++) + { + if (value & (0x1 << i)) + { + GetDrive(i)->ClearAttention(); + } + } + + UpdateAttentionSummary(); + INFO ("Attention Summary write o%o, value is now o%o", value, _attnSummary); break; case Registers::Error1: @@ -167,19 +172,10 @@ uint16_t massbus_rp_c::ReadRegister( uint32_t reg) { - DEBUG("RP reg read: unit %d register 0%o", _selectedUnit, reg); + INFO ("RP reg read: unit %d register 0%o", _selectedUnit, reg); switch(static_cast(reg)) { - - case Registers::DriveType: - return SelectedDrive()->GetDriveType() | 020000; // Moving head (MOVE TO CONSTANT) - break; - - case Registers::SerialNo: - return SelectedDrive()->GetSerialNumber(); - break; - default: FATAL("Unimplemented register read %o", reg); break; @@ -206,37 +202,54 @@ massbus_rp_c::DriveStatus( { _controller->WriteRegister(static_cast(Registers::Status), status); _controller->WriteRegister(static_cast(Registers::Error1), error1); + UpdateDriveRegisters(); } // Update the Attention Summary register for the reporting drive: - if (ata) + // if (ata) // TODO: be nice to avoid doing this every time. { - _attnSummary |= (0x1 << unit); + UpdateAttentionSummary(); INFO ("Attention Summary is now o%o", _attnSummary); } - _controller->WriteRegister(static_cast(Registers::AttentionSummary), _attnSummary); - // Inform controller of status update. - // 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); + _controller->BusStatus(complete, ata, (error1 != 0), SelectedDrive()->IsConnected(), _ned); pthread_mutex_unlock(&_updateLock); } +void +massbus_rp_c::UpdateAttentionSummary() +{ + // + // Collect summary bits from all drives + // + _attnSummary = 0; + for(int i=0;i<8;i++) + { + bool attn = GetDrive(i)->GetAttention(); + if (attn) + { + _attnSummary |= (0x1 << i); + } + } + + _controller->WriteRegister(static_cast(Registers::AttentionSummary), _attnSummary); +} + +void +massbus_rp_c::UpdateDriveInfo() +{ + rp_drive_c* drive = SelectedDrive(); + + _controller->WriteRegister(static_cast(Registers::DriveType), + drive->GetDriveType() | 020000); + + _controller->WriteRegister(static_cast(Registers::SerialNo), + drive->GetSerialNumber()); +} + void massbus_rp_c::UpdateDriveRegisters() { diff --git a/10.02_devices/2_src/massbus_rp.hpp b/10.02_devices/2_src/massbus_rp.hpp index 6818b6b..05a8464 100644 --- a/10.02_devices/2_src/massbus_rp.hpp +++ b/10.02_devices/2_src/massbus_rp.hpp @@ -92,9 +92,11 @@ public: private: void on_power_changed(void) override; void on_init_changed(void) override; - + + void UpdateAttentionSummary(); + void UpdateDriveInfo(); void UpdateDriveRegisters(); - + rp_drive_c* SelectedDrive(); rp_drive_c* GetDrive(uint16_t unit); @@ -110,13 +112,13 @@ private: { "MR" , false, true, 0, 0177777 }, // 3, Maintenance { "ATN", false, true, 0, 0377 }, // 4, Attention summary { "DA" , false, true, 0, 0017437 }, // 5, Desired Sector/Track - { "DT" , true, true, 0, 0 }, // 6, Drive Type + { "DT" , false, false, 020022, 0 }, // 6, Drive Type { "LA" , false, false, 0, 0 }, // 7, Look Ahead { "ER2", false, false, 0, 0 }, // 10, Error #2 { "OFF", false, false, 0, 0177777 }, // 11, Offset { "DCY", false, true, 0, 0001777 }, // 12, Desired Cylinder { "CCY", false, false, 0, 0 }, // 13, Current Cylinder - { "SN" , true, true, 0, 0 }, // 14, Serial Number + { "SN" , false, false, 012345, 0 }, // 14, Serial Number { "ER3", false, false, 0, 0 }, // 15, Error #3 { "EPO", false, false, 0, 0 }, // 16, ECC Position { "EPA", false, false, 0, 0 }, // 17, ECC Pattern diff --git a/10.02_devices/2_src/rh11.cpp b/10.02_devices/2_src/rh11.cpp index 4449ee4..6a70999 100755 --- a/10.02_devices/2_src/rh11.cpp +++ b/10.02_devices/2_src/rh11.cpp @@ -221,34 +221,48 @@ rh11_c::rh11_c() : drive->parent = this; storagedrives.push_back(drive); } - + + _massbus->SelectUnit(0); } rh11_c::~rh11_c() { } +void +rh11_c::StartDataTransfer() +{ + _ready = false; + UpdateCS1(false); +} + // TODO: RENAME! This is invoked when the drive is finished with the given command. void rh11_c::BusStatus( bool completion, - bool ready, bool attention, bool error, bool avail, bool ned) { - DEBUG("Massbus status update attn %d, error %d, ned %d", attention, error, ned); + INFO ("Massbus status update attn %d, error %d, ned %d", attention, error, ned); _attention = attention; _error = error; _avail = avail; - _ready = ready; - + + bool ready = false; if (completion) { // clear the GO bit from the CS word if the drive is finished. - _go = false; + _go = false; + + // If the controller was busy due to a data transfer, + // on completion, we are ready again and may need to interrupt. + if (!_ready & completion) + { + _ready = ready = true; + } } UpdateCS1(false /* no interrupt, yet */); @@ -280,7 +294,7 @@ rh11_c::UpdateCS1(bool interrupt) (_function << 1) | (_go ? 0000001 : 0); - DEBUG("RHCS1 is now o%o", newStatus); + INFO ("RHCS1 is now o%o", newStatus); if (interrupt) { @@ -308,7 +322,7 @@ void rh11_c::UpdateCS2() (_busAddressIncrementProhibit ? 0000010 : 0) | (_unit); - DEBUG("RHCS2 is now o%o", newStatus); + INFO ("RHCS2 is now o%o", newStatus); set_register_dati_value( RH_reg[RHCS2], @@ -566,7 +580,7 @@ void rh11_c::on_after_register_access( // We thus update only the affected bits, and only send the function // code to the Massbus when the LSB is written. - DEBUG("RHCS1: DATO o%o MASK o%o", value, dato_mask); + INFO ("RHCS1: DATO o%o MASK o%o", value, dato_mask); if ((dato_mask & 0xff00) == 0xff00) { @@ -604,12 +618,12 @@ void rh11_c::on_after_register_access( // if (ready && _interruptEnable) { - DEBUG("Forced interrupt."); + INFO ("Forced interrupt."); unibusadapter->INTR(intr_request, nullptr, 0); } } - DEBUG("RHCS1: IE %d BA o%o func o%o go %d", _interruptEnable, _busAddress, _function, _go); + INFO ("RHCS1: IE %d BA o%o func o%o go %d", _interruptEnable, _busAddress, _function, _go); UpdateCS1(false /* no interrupt */); if ((dato_mask & 0x00ff) == 0x00ff) @@ -631,8 +645,8 @@ void rh11_c::on_after_register_access( _controllerClear = !!(value & 040); _parityError = !!(value & 0020000); - DEBUG("RHCS2 write: o%o", value); - DEBUG("RHCS2: perror %d, unit %d inc %d ptest %d clear %d", + INFO ("RHCS2 write: o%o", value); + INFO ("RHCS2: perror %d, unit %d inc %d ptest %d clear %d", _parityError, _unit, _busAddressIncrementProhibit, _parityTest, _controllerClear); // On unit change, select new drive @@ -645,7 +659,7 @@ void rh11_c::on_after_register_access( // TODO: handle System Register Clear (bit 5) if (_controllerClear) { - DEBUG("Controller Clear"); + INFO ("Controller Clear"); _interruptEnable = false; for (uint32_t i=0; iStartDataTransfer(); + } UpdateStatus(false, false); @@ -266,6 +287,7 @@ void rp_drive_c::DoCommand( // Wake the worker pthread_cond_signal(&_workerWakeupCond); pthread_mutex_unlock(&_workerMutex); + INFO ("Command queued for worker."); } break; @@ -281,14 +303,14 @@ rp_drive_c::worker(unsigned instance) { UNUSED(instance); - worker_init_realtime_priority(rt_device); + // worker_init_realtime_priority(rt_device); _workerState = WorkerState::Idle; WorkerCommand command = { 0 }; timeout_c timeout; - INFO ("rp_drive worker started."); + INFO ("rp_drive %d worker started.", _driveNumber); while (!workers_terminate) { switch(_workerState) @@ -312,31 +334,69 @@ rp_drive_c::worker(unsigned instance) case WorkerState::Execute: { + INFO ("Worker executing function o%o", command.function); switch(command.function) { + case FunctionCode::ReadHeaderAndData: case FunctionCode::ReadData: { - uint16_t* buffer = nullptr; - if (Read( - command.word_count, - &buffer)) + INFO ("Read wc %d", command.word_count); + if (FunctionCode::ReadHeaderAndData == command.function) { - // - // Data read: do DMA transfer to memory. - // + // Per EK-RP056-MM-01 a READ HEADER AND DATA command + // is functionally identical to a READ DATA command + // except four extra words are sent prior to the sector data. + // These are: + // 1. Cylinder address and format bit: + // TODO: where is this format bit? + // 2. Sector/Track address: + // bits 0-4 specify sector, bits 8-12 indicate track. + // (i.e identical to the DA register format) + // 3, 4. Key Field - programmer defined, at format time. + // We return a header that matches the expected disk address. + uint16_t* header = new uint16_t[4]; + assert(header); + + header[0] = _desiredCylinder; + header[1] = _desiredSector | (_desiredTrack << 8); + header[2] = 0; + header[3] = 0; + _controller->DiskReadTransfer( command.bus_address, - command.word_count, - buffer); - - // Free buffer - delete buffer; + std::min(4, command.word_count), + header); + + command.bus_address += 8; + command.word_count -= 4; + delete[] header; } - else + + // Any words left? + if (command.word_count > 0) { - // Read failed: - DEBUG("Read failed."); - _ata = true; + 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: + INFO ("Read failed."); + _ata = true; + } } _workerState = WorkerState::Finish; @@ -357,11 +417,11 @@ rp_drive_c::worker(unsigned instance) buffer)) { // Write failed: - DEBUG("Write failed."); + INFO ("Write failed."); _ata = true; } - delete buffer; + delete[] buffer; _workerState = WorkerState::Finish; } @@ -372,7 +432,7 @@ rp_drive_c::worker(unsigned instance) if (!Search()) { // Search failed - DEBUG("Search failed"); + INFO ("Search failed"); } // Return to ready state, set attention bit. @@ -381,11 +441,16 @@ rp_drive_c::worker(unsigned instance) } break; + case FunctionCode::Recalibrate: + INFO ("RECALIBRATE"); + // Treat a Recal as a seek to zero. + _desiredCylinder = 0; + // Fall through to seek. case FunctionCode::Seek: if (!SeekTo()) { // Seek failed - DEBUG("Seek failed"); + INFO ("Seek failed"); } _ata = true; _workerState = WorkerState::Finish; @@ -397,7 +462,7 @@ rp_drive_c::worker(unsigned instance) // 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"); + INFO ("OFFSET/RETURN TO CL"); timeout.wait_ms(10); _ata = true; _workerState = WorkerState::Finish; @@ -418,7 +483,7 @@ rp_drive_c::worker(unsigned instance) pthread_mutex_lock(&_workerMutex); _newCommand.ready = false; pthread_mutex_unlock(&_workerMutex); - UpdateStatus(true, false); + UpdateStatus(true, false); break; } @@ -550,7 +615,7 @@ rp_drive_c::Read( if (!IsConnected() || !IsPackLoaded() || _iae) { *buffer = nullptr; - DEBUG("Failure: connected %d loaded %d valid %d", IsConnected(), IsPackLoaded(), _iae); + INFO ("Failure: connected %d loaded %d valid %d", IsConnected(), IsPackLoaded(), _iae); return false; } else @@ -562,7 +627,7 @@ rp_drive_c::Read( assert(nullptr != *buffer); uint32_t offset = GetSectorForCHS(_currentCylinder, _desiredTrack, _desiredSector); - DEBUG("Read from sector offset o%o", offset); + INFO ("Read from sector offset o%o", offset); file_read(reinterpret_cast(*buffer), offset * GetSectorSize(), countInWords * 2); timeout_c timeout; timeout.wait_us(2500); @@ -578,7 +643,7 @@ rp_drive_c::Search(void) if (!IsConnected() || !IsPackLoaded() || _iae) { - DEBUG("Failure: connected &d loaded %d valid %d", IsConnected(), IsPackLoaded(), _iae); + INFO ("Failure: connected &d loaded %d valid %d", IsConnected(), IsPackLoaded(), _iae); return false; } else @@ -586,9 +651,9 @@ rp_drive_c::Search(void) // This is just a no-op, as we don't emulate read errors. We just delay a tiny bit. timeout_c timeout; - DEBUG("Search commencing."); + INFO ("Search commencing."); timeout.wait_ms(20); - DEBUG("Search completed."); + INFO ("Search completed."); _currentCylinder = _desiredCylinder; return true; diff --git a/10.02_devices/2_src/rp_drive.hpp b/10.02_devices/2_src/rp_drive.hpp index 4987042..7df7ca9 100644 --- a/10.02_devices/2_src/rp_drive.hpp +++ b/10.02_devices/2_src/rp_drive.hpp @@ -62,13 +62,15 @@ public: void SetDesiredTrack(uint32_t track) { _desiredTrack = track; } void SetDesiredSector(uint32_t sector) { _desiredSector = sector; } void SetOffset(uint16_t offset) { _offset = offset; } + void ClearAttention() { _ata = false; } uint32_t GetDesiredCylinder(void) { return _desiredCylinder; } uint32_t GetDesiredTrack(void) { return _desiredTrack; } uint32_t GetDesiredSector(void) { return _desiredSector; } uint16_t GetOffset(void) { return _offset; } uint32_t GetCurrentCylinder(void) { return _currentCylinder; } - - uint16_t GetDriveType(void) { return _driveInfo.TypeNumber; } + bool GetAttention() { return _ata; } + + uint16_t GetDriveType(void) { return _driveInfo.TypeNumber; } uint16_t GetSerialNumber(void) { return 012345; } // TODO: Make configurable parameter uint32_t GetSectorSize(void); @@ -91,9 +93,9 @@ private: struct WorkerCommand { - volatile uint32_t bus_address; - volatile uint32_t word_count; - volatile FunctionCode function; + uint32_t bus_address; + int32_t word_count; + FunctionCode function; volatile bool ready; } _newCommand;