From e037b0d36d1202fcb0b6bba1b86b7d50af6fa601 Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Wed, 25 Mar 2020 07:06:54 +0100 Subject: [PATCH] Added dato_mask to allow discerning DATOB operations to emulated registers. This allows the RH11's RHCS1 register to function properly. 2.11BSD now boots! --- 10.01_base/2_src/arm/unibusadapter.cpp | 11 +- 10.01_base/2_src/arm/unibusdevice.hpp | 2 +- 10.02_devices/2_src/cpu.cpp | 2 +- 10.02_devices/2_src/cpu.hpp | 2 +- 10.02_devices/2_src/demo_io.cpp | 2 +- 10.02_devices/2_src/demo_io.hpp | 2 +- 10.02_devices/2_src/dl11w.cpp | 5 +- 10.02_devices/2_src/dl11w.hpp | 4 +- 10.02_devices/2_src/massbus_rp.cpp | 92 ++- 10.02_devices/2_src/massbus_rp.hpp | 11 +- 10.02_devices/2_src/rh11.cpp | 147 ++-- 10.02_devices/2_src/rh11.hpp | 3 +- 10.02_devices/2_src/rk11.cpp | 4 +- 10.02_devices/2_src/rk11.hpp | 3 +- 10.02_devices/2_src/rl11.cpp | 4 +- 10.02_devices/2_src/rl11.hpp | 2 +- 10.02_devices/2_src/rp_drive.cpp | 17 +- 10.02_devices/2_src/testcontroller.cpp | 3 +- 10.02_devices/2_src/testcontroller.hpp | 2 +- 10.02_devices/2_src/uda.cpp | 6 +- 10.02_devices/2_src/uda.cpp.old | 984 +++++++++++++++++++++++++ 10.02_devices/2_src/uda.hpp | 3 +- 22 files changed, 1185 insertions(+), 126 deletions(-) create mode 100644 10.02_devices/2_src/uda.cpp.old diff --git a/10.01_base/2_src/arm/unibusadapter.cpp b/10.01_base/2_src/arm/unibusadapter.cpp index 2ed4207..85af590 100644 --- a/10.01_base/2_src/arm/unibusadapter.cpp +++ b/10.01_base/2_src/arm/unibusadapter.cpp @@ -841,6 +841,7 @@ void unibusadapter_c::worker_deviceregister_event() { uint16_t evt_data = mailbox->events.deviceregister.data; unibusdevice_register_t *device_reg = &(device->registers[evt_idx]); uint8_t unibus_control = mailbox->events.deviceregister.unibus_control; + uint16_t dato_mask = 0xffff; /* call device event callback @@ -860,7 +861,7 @@ void unibusadapter_c::worker_deviceregister_event() { // signal: changed by UNIBUS device->log_register_event("DATI", device_reg); - device->on_after_register_access(device_reg, unibus_control); + device->on_after_register_access(device_reg, unibus_control, 0); } else if (device_reg->active_on_dato && UNIBUS_CONTROL_IS_DATO(unibus_control)) { // uint16_t reg_value_written = device_reg->shared_register->value; // restore value accessible by DATI @@ -882,18 +883,24 @@ void unibusadapter_c::worker_deviceregister_event() { evt_data &= device_reg->writable_bits; // clear unused bits // save written value if (evt_addr & 1) // odd address: bits 15:8 written + { device_reg->active_dato_flipflops = (device_reg->active_dato_flipflops & 0x00ff) | (evt_data & 0xff00); + dato_mask = 0xff00; + } else + { // even address : bits 7:0 written device_reg->active_dato_flipflops = (device_reg->active_dato_flipflops & 0xff00) | (evt_data & 0x00ff); + dato_mask = 0x00ff; + } unibus_control = UNIBUS_CONTROL_DATO; // simulate 16 bit access // signal: changed by UNIBUS device->log_register_event("DATOB", device_reg); break; } - device->on_after_register_access(device_reg, unibus_control); + device->on_after_register_access(device_reg, unibus_control, dato_mask); /* DEBUG(LL_DEBUG, LC_UNIBUS, "dev.reg=%d.%d, %s, addr %06o, data %06o->%06o", device_handle, evt_idx, diff --git a/10.01_base/2_src/arm/unibusdevice.hpp b/10.01_base/2_src/arm/unibusdevice.hpp index 4c9f51c..7d4ff5f 100644 --- a/10.01_base/2_src/arm/unibusdevice.hpp +++ b/10.01_base/2_src/arm/unibusdevice.hpp @@ -156,7 +156,7 @@ public: // INTR/DMA as task to separate INTR/DMA scheduler // -> orders INTR/DMA of different devices, wait for UNIBUS idle. virtual void on_after_register_access(unibusdevice_register_t *device_reg, - uint8_t unibus_control) = 0; + uint8_t unibus_control, uint16_t dato_mask) = 0; // communication between on_after_register_access() and device_c::worker() // see pthread_cond_wait()* examples pthread_cond_t on_after_register_access_cond = PTHREAD_COND_INITIALIZER; diff --git a/10.02_devices/2_src/cpu.cpp b/10.02_devices/2_src/cpu.cpp index 3a2554f..61f33c2 100644 --- a/10.02_devices/2_src/cpu.cpp +++ b/10.02_devices/2_src/cpu.cpp @@ -260,7 +260,7 @@ void cpu_c::worker(unsigned instance) { // UNIBUS DATO cycles let dati_flipflops "flicker" outside of this proc: // do not read back dati_flipflops. void cpu_c::on_after_register_access(unibusdevice_register_t *device_reg, - uint8_t unibus_control) { + uint8_t unibus_control, uint16_t dato_mask) { // nothing todo UNUSED(device_reg); UNUSED(unibus_control); diff --git a/10.02_devices/2_src/cpu.hpp b/10.02_devices/2_src/cpu.hpp index fc16730..5c2bf5a 100644 --- a/10.02_devices/2_src/cpu.hpp +++ b/10.02_devices/2_src/cpu.hpp @@ -67,7 +67,7 @@ public: void worker(unsigned instance) override; // called by unibusadapter on emulated register access - void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control) + void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control, uint16_t dato_mask) override; void on_interrupt(uint16_t vector); diff --git a/10.02_devices/2_src/demo_io.cpp b/10.02_devices/2_src/demo_io.cpp index 2aac192..8a0f236 100644 --- a/10.02_devices/2_src/demo_io.cpp +++ b/10.02_devices/2_src/demo_io.cpp @@ -222,7 +222,7 @@ void demo_io_c::worker(unsigned instance) { // UNIBUS DATO cycles let dati_flipflops "flicker" outside of this proc: // do not read back dati_flipflops. void demo_io_c::on_after_register_access(unibusdevice_register_t *device_reg, - uint8_t unibus_control) { + uint8_t unibus_control, uint16_t dato_mask) { // nothing todo UNUSED(device_reg); UNUSED(unibus_control); diff --git a/10.02_devices/2_src/demo_io.hpp b/10.02_devices/2_src/demo_io.hpp index 0bc4e71..8939f21 100644 --- a/10.02_devices/2_src/demo_io.hpp +++ b/10.02_devices/2_src/demo_io.hpp @@ -61,7 +61,7 @@ public: void worker(unsigned instance) override; // called by unibusadapter on emulated register access - void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control) + void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control, uint16_t dato_mask) override; void on_power_changed(void) override; diff --git a/10.02_devices/2_src/dl11w.cpp b/10.02_devices/2_src/dl11w.cpp index 3cc1300..158e980 100644 --- a/10.02_devices/2_src/dl11w.cpp +++ b/10.02_devices/2_src/dl11w.cpp @@ -264,8 +264,9 @@ void slu_c::eval_xbuf_dato_value(void) { // UNIBUS DATO cycles let dati_flipflops "flicker" outside of this proc: // do not read back dati_flipflops. void slu_c::on_after_register_access(unibusdevice_register_t *device_reg, - uint8_t unibus_control) { + uint8_t unibus_control, uint16_t dato_mask) { + UNUSED(dato_mask); // if (unibus_control == UNIBUS_CONTROL_DATO) // bus write // set_register_dati_value(device_reg, device_reg->active_dato_flipflops, __func__); @@ -510,7 +511,7 @@ void ltc_c::set_lks_dati_value_and_INTR(bool do_intr) { // process DATI/DATO access to one of my "active" registers void ltc_c::on_after_register_access(unibusdevice_register_t *device_reg, - uint8_t unibus_control) { + uint8_t unibus_control, uint16_t dato_mask) { pthread_mutex_lock(&on_after_register_access_mutex); // not necessary, not harmful? if (unibus_control == UNIBUS_CONTROL_DATO) // bus write diff --git a/10.02_devices/2_src/dl11w.hpp b/10.02_devices/2_src/dl11w.hpp index 1176c39..5e8b33f 100644 --- a/10.02_devices/2_src/dl11w.hpp +++ b/10.02_devices/2_src/dl11w.hpp @@ -174,7 +174,7 @@ public: void worker_xmt(void); // called by unibusadapter on emulated register access - void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control) + void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control, uint16_t dato_mask) override; bool on_param_changed(parameter_c *param) override; // must implement @@ -213,7 +213,7 @@ public: void worker(unsigned instance) override; // called by unibusadapter on emulated register access - void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control) + void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control, uint16_t dato_mask) override; bool on_param_changed(parameter_c *param) override; // must implement diff --git a/10.02_devices/2_src/massbus_rp.cpp b/10.02_devices/2_src/massbus_rp.cpp index 7f02e3f..cd07c36 100644 --- a/10.02_devices/2_src/massbus_rp.cpp +++ b/10.02_devices/2_src/massbus_rp.cpp @@ -23,6 +23,14 @@ void* WorkerInit( return nullptr; } +void* SpinInit( + void* context) +{ + massbus_rp_c* rp = reinterpret_cast(context); + rp->Spin(); + return nullptr; +} + massbus_rp_c::massbus_rp_c( rh11_c* controller) : device_c(), @@ -55,6 +63,16 @@ massbus_rp_c::massbus_rp_c( &attribs, &WorkerInit, reinterpret_cast(this)); + + pthread_attr_t attribs2; + pthread_attr_init(&attribs2); + status = pthread_create( + &_spinThread, + &attribs2, + &SpinInit, + reinterpret_cast(this)); + + } massbus_rp_c::~massbus_rp_c() @@ -112,7 +130,7 @@ massbus_rp_c::WriteRegister( uint32_t reg, uint16_t value) { - INFO("RP reg write: unit %d register 0%o value 0%o", unit, reg, value); + DEBUG("RP reg write: unit %d register 0%o value 0%o", unit, reg, value); if (!SelectedDrive()->IsDriveReady() && reg != 0 && // CS1 is allowed as long as GO isn't set (will be checked in DoCommand) @@ -120,14 +138,12 @@ massbus_rp_c::WriteRegister( { // Any attempt to modify a drive register other than Attention Summary // while the drive is busy is invalid. - INFO("Register modification while drive busy."); + DEBUG("Register modification while drive busy."); _rmr = true; UpdateStatus(false, false); return; } - static uint32_t frup = 0; - switch(static_cast(reg)) { case Registers::Control: @@ -139,7 +155,7 @@ massbus_rp_c::WriteRegister( _desiredSector = (value & 0x1f); UpdateDesiredSectorTrack(); - INFO("Desired Sector Track Address: track %d, sector %d", + DEBUG("Desired Sector Track Address: track %d, sector %d", _desiredTrack, _desiredSector); break; @@ -148,7 +164,7 @@ massbus_rp_c::WriteRegister( _desiredCylinder = value & 0x3ff; UpdateDesiredCylinder(); - INFO("Desired Cylinder Address o%o (o%o)", _desiredCylinder, value); + DEBUG("Desired Cylinder Address o%o (o%o)", _desiredCylinder, value); break; case Registers::AttentionSummary: @@ -156,7 +172,7 @@ massbus_rp_c::WriteRegister( // written value: _attnSummary &= ~(value & 0xff); _controller->WriteRegister(reg, _attnSummary); - INFO("Attention Summary write o%o, value is now o%o", value, _attnSummary); + DEBUG("Attention Summary write o%o, value is now o%o", value, _attnSummary); break; case Registers::Error1: @@ -166,7 +182,7 @@ massbus_rp_c::WriteRegister( // // Based on diagnostic (ZRJGE0) behavior, writing ANY value here forces an error. // - INFO("Error 1 Reg write o%o, value is now o%o", value, _error1); + DEBUG("Error 1 Reg write o%o, value is now o%o", value, _error1); UpdateStatus(false, true); // Force composite error. break; @@ -189,7 +205,7 @@ void massbus_rp_c::DoCommand( FunctionCode function = static_cast((command & RP_FUNC) >> 1); _selectedUnit = unit; - INFO("RP function 0%o, unit %o", function, _selectedUnit); + DEBUG("RP function 0%o, unit %o", function, _selectedUnit); if (!SelectedDrive()->IsConnected()) { @@ -220,7 +236,7 @@ void massbus_rp_c::DoCommand( break; case FunctionCode::ReadInPreset: - INFO("RP Read-In Preset"); + 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 @@ -241,7 +257,7 @@ void massbus_rp_c::DoCommand( case FunctionCode::WriteHeaderAndData: case FunctionCode::ReadHeaderAndData: case FunctionCode::Search: - INFO("RP Read/Write Data or Search"); + DEBUG("RP Read/Write Data or Search"); { // Clear the unit's DRY bit SelectedDrive()->ClearDriveReady(); @@ -284,7 +300,7 @@ massbus_rp_c::ReadRegister( uint32_t unit, uint32_t reg) { - INFO("*** RP reg read: unit %d register 0%o", unit, reg); + DEBUG("RP reg read: unit %d register 0%o", unit, reg); switch(static_cast(reg)) { @@ -297,11 +313,6 @@ massbus_rp_c::ReadRegister( return SelectedDrive()->GetSerialNumber(); break; - case Registers::AttentionSummary: - INFO("attn: o%o", _attnSummary); - return _attnSummary; - break; - default: FATAL("Unimplemented register read %o", reg); break; @@ -351,7 +362,7 @@ massbus_rp_c::UpdateStatus( (_err ? 040000 : 0) | // Composite error (_ata ? 0100000 : 0); - INFO("Unit %d Status: o%o", _selectedUnit, _status); + DEBUG("Unit %d Status: o%o", _selectedUnit, _status); _controller->WriteRegister(static_cast(Registers::Status), _status); _controller->WriteRegister(static_cast(Registers::Error1), _error1); @@ -361,7 +372,7 @@ massbus_rp_c::UpdateStatus( { _attnSummary |= (0x1 << _selectedUnit); // TODO: these only get set, and are latched until // manually cleared? - INFO("Attention Summary is now o%o", _attnSummary); + DEBUG("Attention Summary is now o%o", _attnSummary); } _controller->WriteRegister(static_cast(Registers::AttentionSummary), _attnSummary); @@ -375,10 +386,6 @@ massbus_rp_c::UpdateDesiredSectorTrack() { uint16_t desiredSectorTrack = (_desiredSector | (_desiredTrack << 8)); _controller->WriteRegister(static_cast(Registers::DesiredSectorTrackAddress), desiredSectorTrack); - - // Fudge: We update the look-ahead sector value to be the last-requested sector - 1 - uint16_t lookAhead = (((_desiredSector - 1) % 22) << 6); - _controller->WriteRegister(static_cast(Registers::LookAhead), lookAhead); } void @@ -446,7 +453,7 @@ massbus_rp_c::Worker() timeout_c timeout; - INFO("massbus worker started."); + DEBUG("massbus worker started."); while (!workers_terminate) { switch(_workerState) @@ -474,7 +481,7 @@ massbus_rp_c::Worker() { case FunctionCode::ReadData: { - INFO("READ CHS %d/%d/%d, %d words to address o%o", + DEBUG("READ CHS %d/%d/%d, %d words to address o%o", _newCommand.cylinder, _newCommand.track, _newCommand.sector, @@ -503,7 +510,7 @@ massbus_rp_c::Worker() else { // Read failed: - INFO("Read failed."); + DEBUG("Read failed."); _ata = true; } @@ -516,7 +523,7 @@ massbus_rp_c::Worker() case FunctionCode::WriteData: { - INFO("WRITE CHS %d/%d/%d, %d words from address o%o", + DEBUG("WRITE CHS %d/%d/%d, %d words from address o%o", _newCommand.cylinder, _newCommand.track, _newCommand.sector, @@ -538,7 +545,7 @@ massbus_rp_c::Worker() buffer)) { // Write failed: - INFO("Write failed."); + DEBUG("Write failed."); _ata = true; } @@ -553,7 +560,7 @@ massbus_rp_c::Worker() case FunctionCode::Search: { - INFO("SEARCH CHS %d/%d/%d", + DEBUG("SEARCH CHS %d/%d/%d", _newCommand.cylinder, _newCommand.track, _newCommand.sector); @@ -564,7 +571,7 @@ massbus_rp_c::Worker() _newCommand.sector)) { // Search failed - INFO("Search failed"); + DEBUG("Search failed"); } @@ -593,6 +600,31 @@ massbus_rp_c::Worker() } } +void +massbus_rp_c::Spin(void) +{ + // + // All this worker does is simulate the spinning of the disk by + // updating the LookAhead register periodically. In reality there'd be a + // different value for every drive but also in reality there'd be a gigantic + // washing-machine-sized drive spinning aluminum disks plated with rust, so... + // + + uint16_t lookAhead = 0; + timeout_c timer; + + + timer.wait_ms(2500); + while(true) + { + timer.wait_ms(10); + + // We update only the sector count portion of the register. + lookAhead = (lookAhead + 1) % 22; + _controller->WriteRegister(static_cast(Registers::LookAhead), lookAhead << 6); + } +} + void massbus_rp_c::on_power_changed(void) { diff --git a/10.02_devices/2_src/massbus_rp.hpp b/10.02_devices/2_src/massbus_rp.hpp index 4eaeb85..5f86027 100644 --- a/10.02_devices/2_src/massbus_rp.hpp +++ b/10.02_devices/2_src/massbus_rp.hpp @@ -105,8 +105,10 @@ 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 + // Background worker functions void Worker(); + void Spin(); + private: struct WorkerCommand { @@ -147,10 +149,10 @@ private: { // Name DATI DATO { "INV", false, false, 0, 0 }, // 0, not used - { "CS1", false, true, 0, 0041577 }, // 1, Status + { "CS1", false, true, 0, 0041777 }, // 1, Status { "ER1", false, true, 0, 0177777 }, // 2, Error #1 - writable by diagnostics { "MR" , false, true, 0, 0177777 }, // 3, Maintenance - { "ATN", true, true, 0, 0377 }, // 4, Attention summary + { "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 { "LA" , false, false, 0, 0 }, // 7, Look Ahead @@ -192,6 +194,9 @@ private: pthread_t _workerThread; pthread_cond_t _workerWakeupCond; pthread_mutex_t _workerMutex; + + // Spin thread + pthread_t _spinThread; }; #endif diff --git a/10.02_devices/2_src/rh11.cpp b/10.02_devices/2_src/rh11.cpp index 0322651..3450d1d 100755 --- a/10.02_devices/2_src/rh11.cpp +++ b/10.02_devices/2_src/rh11.cpp @@ -144,7 +144,7 @@ rh11_c::rh11_c() : RH_reg[i]->active_on_dati = false; RH_reg[i]->active_on_dato = true; RH_reg[i]->reset_value = 0000200; - RH_reg[i]->writable_bits = 041577; + RH_reg[i]->writable_bits = 041777; break; case RHWC: @@ -238,7 +238,7 @@ rh11_c::BusStatus( bool avail, bool ned) { - INFO("Massbus status update attn %d, error %d, ned %d", attention, error, ned); + DEBUG("Massbus status update attn %d, error %d, ned %d", attention, error, ned); _attention = attention; _error = error; @@ -279,7 +279,7 @@ rh11_c::UpdateCS1() (_function << 1) | (_go ? 0000001 : 0); - INFO("RHCS1 is now o%o", newStatus); + DEBUG("RHCS1 is now o%o", newStatus); set_register_dati_value( RH_reg[RHCS1], @@ -300,7 +300,7 @@ void rh11_c::UpdateCS2() (_busAddressIncrementProhibit ? 0000010 : 0) | (_unit); - INFO("RHCS2 is now o%o", newStatus); + DEBUG("RHCS2 is now o%o", newStatus); set_register_dati_value( RH_reg[RHCS2], @@ -402,23 +402,14 @@ rh11_c::IncrementBusAddress(uint32_t delta) { _busAddress += delta; - uint16_t currentStatus = get_register_dato_value(RH_reg[RHCS1]); - - // Clear extended address bits - currentStatus &= ~(0x300); - - currentStatus |= ((_busAddress & 0x30000) >> 8); - set_register_dati_value( - RH_reg[RHCS1], - currentStatus, - "SetBusAddress"); + UpdateCS1(); set_register_dati_value( RH_reg[RHBA], (_busAddress & 0xffff), "IncrementBusAddress"); - INFO("BA Reg incr: o%o", _busAddress); + DEBUG("BA Reg incr: o%o", _busAddress); } uint16_t @@ -440,7 +431,7 @@ rh11_c::DecrementWordCount(uint16_t delta) "DecrementWordCount"); - INFO("WC Reg decr: o%o", currentWordCount); + DEBUG("WC Reg decr: o%o", currentWordCount); } bool @@ -451,12 +442,7 @@ rh11_c::DMAWrite( { assert (address < 0x40000); - for(size_t i=0;iDMA( dma_request, true, @@ -465,7 +451,7 @@ rh11_c::DMAWrite( buffer, lengthInWords); - INFO("Success: %d", dma_request.success); + DEBUG("Success: %d", dma_request.success); assert(dma_request.success); return dma_request.success; @@ -478,7 +464,7 @@ rh11_c::DMARead( { assert (address < 0x40000); - INFO("DMA Read o%o, length %d", address, lengthInWords); + DEBUG("DMA Read o%o, length %d", address, lengthInWords); uint16_t* buffer = new uint16_t[lengthInWords]; assert (buffer); @@ -553,7 +539,8 @@ void rh11_c::worker(unsigned instance) // do not read back dati_flipflops. void rh11_c::on_after_register_access( unibusdevice_register_t *device_reg, - uint8_t unibus_control) + uint8_t unibus_control, + uint16_t dato_mask) { uint16_t value = device_reg->active_dato_flipflops; @@ -563,35 +550,65 @@ void rh11_c::on_after_register_access( { if (UNIBUS_CONTROL_DATO == unibus_control) { - INFO("RHCS1: DATO o%o", value); - _error = !!(value & 0040000); - _busAddress = (_busAddress & 0177777) | ((value & 01400) << 8); - _interruptEnable = !!(value & 0100); - _function = (value & 076) >> 1; - _go = (value & 01); - - // Bit 14 (Transfer Error) is writeable; the function of this is not documented - // In the handbook or engineering manuals. The schematics are eluding me as well. - // 2.11bsd writes this bit, as do diagnostics (and they expect it to be read back..) - // SIMH suggests this is used to clear error bits, so we'll do that here. - if (_error) - { - _error = false; - _parityError = false; - _mcbParityError = false; - _ned = false; - _nxm = false; - } - - INFO("RHCS1: IE %d BA o%o func o%o go %d", _interruptEnable, _busAddress, _function, _go); - // Let the massbus device take a crack at the shared bits - _massbus->WriteRegister(_unit, RHCS1, value & 077); - - // TODO: Per RH11-AB_OptionDescr.pdf (p 4-17): - // "The program can force an interrupt by loading bits D06 H (IE) and D07 H (RDY) in the CS1 - // register which direct sets the INTR flip-flop." // - UpdateCS1(); + // Byte-oriented writes are allowed to RH11 registers; thus far + // CS1 is the only one where distinguishing DATO from DATOB actually + // makes any difference: if the MSB is written separately from the + // LSB and the GO bit is in play, unintended operations may ensue. + // 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); + + if ((dato_mask & 0xff00) == 0xff00) + { + // MSB + _error = !!(value & 0040000); + _busAddress = (_busAddress & 0177777) | ((value & 01400) << 8); + + // Bit 14 (Transfer Error) is writeable; the function of this is not documented + // In the handbook or engineering manuals. The schematics are eluding me as well. + // 2.11bsd writes this bit. + // SIMH suggests this is used to clear error bits, so we'll do that here. + if (_error) + { + _error = false; + _parityError = false; + _mcbParityError = false; + _ned = false; + _nxm = false; + } + + } + + if ((dato_mask & 0x00ff) == 0x00ff) + { + bool ready = !!(value & 0200); + _interruptEnable = !!(value & 0100); + _function = (value & 076) >> 1; + _go = (value & 01); + + // + // Per RH11-AB_OptionDescr.pdf (p 4-17): + // "The program can force an interrupt by loading bits D06 H (IE) and D07 H (RDY) in the CS1 + // register which direct sets the INTR flip-flop." + // 2.11bsd's autoconfig routines do this. + // + if (ready && _interruptEnable) + { + 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); + UpdateCS1(); + + if ((dato_mask & 0x00ff) == 0x00ff) + { + // Let the massbus device take a crack at the shared bits. + _massbus->WriteRegister(_unit, RHCS1, value & 077); + } } } break; @@ -606,14 +623,14 @@ void rh11_c::on_after_register_access( _controllerClear = !!(value & 040); _parityError = !!(value & 0020000); - INFO("RHCS2 write: o%o", value); - INFO("RHCS2: perror %d, unit %d inc %d ptest %d clear %d", + DEBUG("RHCS2 write: o%o", value); + DEBUG("RHCS2: perror %d, unit %d inc %d ptest %d clear %d", _parityError, _unit, _busAddressIncrementProhibit, _parityTest, _controllerClear); // TODO: handle System Register Clear (bit 5) if (_controllerClear) { - INFO("Controller Clear"); + DEBUG("Controller Clear"); _interruptEnable = false; _massbus->Reset(); @@ -622,7 +639,7 @@ void rh11_c::on_after_register_access( _ned = false; _nxm = false; - INFO("RHCS2: is now o%o", value); + DEBUG("RHCS2: is now o%o", value); _controllerClear = false; } @@ -635,7 +652,7 @@ void rh11_c::on_after_register_access( { if (UNIBUS_CONTROL_DATO == unibus_control) { - INFO("RHWC: o%o", value); + DEBUG("RHWC: o%o", value); set_register_dati_value( RH_reg[RHWC], @@ -650,7 +667,7 @@ void rh11_c::on_after_register_access( if (UNIBUS_CONTROL_DATO == unibus_control) { _busAddress = (_busAddress & 0x30000) | value; - INFO("RHBA: o%o", _busAddress); + DEBUG("RHBA: o%o", _busAddress); set_register_dati_value( RH_reg[RHWC], @@ -671,7 +688,7 @@ void rh11_c::on_after_register_access( } else { - INFO("massbus reg read %o", device_reg->index); + DEBUG("massbus reg read %o", device_reg->index); set_register_dati_value( device_reg, _massbus->ReadRegister(_unit, _unibusToMassbusRegisterMap[device_reg->index]), @@ -680,7 +697,7 @@ void rh11_c::on_after_register_access( } else { - INFO("Unhandled register write o%o", device_reg->index); + DEBUG("Unhandled register write o%o", device_reg->index); } break; } @@ -691,13 +708,13 @@ void rh11_c::Interrupt(void) { if(_interruptEnable) { - INFO("Interrupt!"); - unibusadapter->INTR(intr_request, nullptr, 0); + DEBUG("Interrupt!"); - // IE is cleared after the interrupt is raised - // Actual bit is cleared in BusStatus, this should be fixed. + // IE is cleared after the interrupt is raised (probably should be at the same time.) _interruptEnable = false; UpdateCS1(); + + unibusadapter->INTR(intr_request, nullptr, 0); } } diff --git a/10.02_devices/2_src/rh11.hpp b/10.02_devices/2_src/rh11.hpp index d5530a1..afdfc9f 100755 --- a/10.02_devices/2_src/rh11.hpp +++ b/10.02_devices/2_src/rh11.hpp @@ -108,7 +108,8 @@ public: // called by unibusadapter on emulated register access void on_after_register_access( unibusdevice_register_t *device_reg, - uint8_t unibus_control) override; + uint8_t unibus_control, + uint16_t dato_mask) override; bool on_param_changed(parameter_c *param) override; void on_power_changed(void) override; diff --git a/10.02_devices/2_src/rk11.cpp b/10.02_devices/2_src/rk11.cpp index 374c6f4..e515134 100755 --- a/10.02_devices/2_src/rk11.cpp +++ b/10.02_devices/2_src/rk11.cpp @@ -636,9 +636,11 @@ void rk11_c::increment_RKDA() // do not read back dati_flipflops. void rk11_c::on_after_register_access( unibusdevice_register_t *device_reg, - uint8_t unibus_control) + uint8_t unibus_control, + uint16_t dato_mask) { UNUSED(unibus_control); + UNUSED(dato_mask); // The RK11 has only one "active" register, RKCS. // When "GO" bit is set, kick off an operation and clear the diff --git a/10.02_devices/2_src/rk11.hpp b/10.02_devices/2_src/rk11.hpp index 1dea632..adc78d5 100755 --- a/10.02_devices/2_src/rk11.hpp +++ b/10.02_devices/2_src/rk11.hpp @@ -167,7 +167,8 @@ public: // called by unibusadapter on emulated register access void on_after_register_access( unibusdevice_register_t *device_reg, - uint8_t unibus_control) override; + uint8_t unibus_control, + uint16_t dato_mask) override; bool on_param_changed(parameter_c *param) override; diff --git a/10.02_devices/2_src/rl11.cpp b/10.02_devices/2_src/rl11.cpp index 4bafd18..2e0a22f 100644 --- a/10.02_devices/2_src/rl11.cpp +++ b/10.02_devices/2_src/rl11.cpp @@ -345,7 +345,9 @@ void RL11_c::set_MP_dati_silo(const char *debug_info) { // UNIBUS DATO cycles let dati_flipflops "flicker" outside of this proc: // do not read back dati_flipflops. void RL11_c::on_after_register_access(unibusdevice_register_t *device_reg, - uint8_t unibus_control) { + uint8_t unibus_control, uint16_t dato_mask) { + UNUSED(dato_mask); + // on drive select: // move status of new drive to controller status register // on command: signal worker thread diff --git a/10.02_devices/2_src/rl11.hpp b/10.02_devices/2_src/rl11.hpp index 1c24e48..43a10b1 100644 --- a/10.02_devices/2_src/rl11.hpp +++ b/10.02_devices/2_src/rl11.hpp @@ -114,7 +114,7 @@ public: // called by unibusadapter after DATI/DATO access to active emulated register // Runs at 100% RT priority, UNIBUS is stopped by SSYN while this is running. - void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control) + void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control, uint16_t dato_mask) override; void on_power_changed(void) override; diff --git a/10.02_devices/2_src/rp_drive.cpp b/10.02_devices/2_src/rp_drive.cpp index b7d8504..b69ab06 100644 --- a/10.02_devices/2_src/rp_drive.cpp +++ b/10.02_devices/2_src/rp_drive.cpp @@ -95,10 +95,14 @@ rp_drive_c::SeekTo( { // TODO: delay by appropriate amount + timeout_c timeout; + _iae = !(destinationCylinder < _driveInfo.Cylinders); if (IsConnected() && IsPackLoaded() && !_iae) { + timeout.wait_ms(50); + _currentCylinder = destinationCylinder; return true; } @@ -170,7 +174,7 @@ rp_drive_c::Read( if (!IsConnected() || !IsPackLoaded() || _iae) { *buffer = nullptr; - INFO("Failure: connected %d loaded %d valid %d", IsConnected(), IsPackLoaded(), ValidateCHS(cylinder, track, sector)); + DEBUG("Failure: connected %d loaded %d valid %d", IsConnected(), IsPackLoaded(), ValidateCHS(cylinder, track, sector)); return false; } else @@ -182,9 +186,8 @@ rp_drive_c::Read( assert(nullptr != *buffer); uint32_t offset = GetSectorForCHS(cylinder, track, sector); - INFO("Read from sector offset o%o", offset); + DEBUG("Read from sector offset o%o", offset); file_read(reinterpret_cast(*buffer), offset * GetSectorSize(), countInWords * 2); - return true; } } @@ -199,7 +202,7 @@ rp_drive_c::Search( if (!IsConnected() || !IsPackLoaded() || _iae) { - INFO("Failure: connected &d loaded %d valid %d", IsConnected(), IsPackLoaded(), ValidateCHS(cylinder, track, sector)); + DEBUG("Failure: connected &d loaded %d valid %d", IsConnected(), IsPackLoaded(), ValidateCHS(cylinder, track, sector)); return false; } else @@ -207,10 +210,10 @@ rp_drive_c::Search( // 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."); - timeout.wait_ms(250); + DEBUG("Search commencing."); + timeout.wait_ms(20); _pip = false; - INFO("Search completed."); + DEBUG("Search completed."); _currentCylinder = cylinder; return true; diff --git a/10.02_devices/2_src/testcontroller.cpp b/10.02_devices/2_src/testcontroller.cpp index 87bf27d..5284b97 100644 --- a/10.02_devices/2_src/testcontroller.cpp +++ b/10.02_devices/2_src/testcontroller.cpp @@ -160,8 +160,9 @@ bool testcontroller_c::on_param_changed(parameter_c *param) { // UNIBUS DATO cycles let dati_flipflops "flicker" outside of this proc: // do not read back dati_flipflops. void testcontroller_c::on_after_register_access(unibusdevice_register_t *device_reg, - uint8_t unibus_control) { + uint8_t unibus_control, uint16_t dato_mask) { + UNUSED(dato_mask); // emulate a plain memory cell: written values can be read unchanged if (unibus_control == UNIBUS_CONTROL_DATI) { } diff --git a/10.02_devices/2_src/testcontroller.hpp b/10.02_devices/2_src/testcontroller.hpp index ff3d434..115664c 100644 --- a/10.02_devices/2_src/testcontroller.hpp +++ b/10.02_devices/2_src/testcontroller.hpp @@ -61,7 +61,7 @@ public: void worker(unsigned instance) override; // called by unibusadapter on emulated register access - void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control) + void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control, uint16_t dato_mask) override; void on_power_changed(void) override; diff --git a/10.02_devices/2_src/uda.cpp b/10.02_devices/2_src/uda.cpp index d720fe1..cd71c19 100644 --- a/10.02_devices/2_src/uda.cpp +++ b/10.02_devices/2_src/uda.cpp @@ -296,9 +296,11 @@ void uda_c::worker(unsigned instance) void uda_c::on_after_register_access( unibusdevice_register_t *device_reg, - uint8_t unibus_control -) + uint8_t unibus_control, + uint16_t dato_mask) { + UNUSED(dato_mask); + switch (device_reg->index) { case 0: // IP - read / write diff --git a/10.02_devices/2_src/uda.cpp.old b/10.02_devices/2_src/uda.cpp.old new file mode 100644 index 0000000..57f25a8 --- /dev/null +++ b/10.02_devices/2_src/uda.cpp.old @@ -0,0 +1,984 @@ +/* + uda.cpp: Implementation of the MSCP port (unibus interface). + + Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA. + Contributed under the BSD 2-clause license. + + This provides logic for the UDA50's SA and IP registers, + the four-step initialization handshake, DMA transfers to and + from the Unibus, and the command/response ring protocols. + + While the name "UDA" is used here, this is not a strict emulation + of a real UDA50 -- it is a general MSCP implementation and can be + thought of as the equivalent of the third-party MSCP controllers + from Emulex, CMD, etc. that were available. + + At this time this class acts as the port for an MSCP controller. + It would be trivial to extend this to TMSCP at a future date. +*/ + +#include +#include + +#include "unibus.h" +#include "unibusadapter.hpp" +#include "unibusdevice.hpp" +#include "storagecontroller.hpp" +#include "mscp_drive.hpp" +#include "uda.hpp" + +uda_c::uda_c() : + storagecontroller_c(), + _server(nullptr), + _ringBase(0), + _commandRingLength(0), + _responseRingLength(0), + _commandRingPointer(0), + _responseRingPointer(0), + _interruptVector(0), + _interruptEnable(false), + _purgeInterruptEnable(false), + _step1Value(0), + _initStep(InitializationStep::Uninitialized), + _next_step(false) +{ + name.value = "uda"; + type_name.value = "UDA50"; + log_label = "uda"; + + default_base_addr = 0772150; + default_intr_vector = 0154; + default_intr_level = 5; + + // The UDA50 controller has two registers. + register_count = 2; + + IP_reg = &(this->registers[0]); // @ base addr + strcpy(IP_reg->name, "IP"); + IP_reg->active_on_dati = true; + IP_reg->active_on_dato = true; + IP_reg->reset_value = 0; + IP_reg->writable_bits = 0xffff; + + SA_reg = &(this->registers[1]); // @ base addr + 2 + strcpy(SA_reg->name, "SA"); + SA_reg->active_on_dati = false; + SA_reg->active_on_dato = true; + SA_reg->reset_value = 0; + SA_reg->writable_bits = 0xffff; + + _server.reset(new mscp_server(this)); + + // + // Initialize drives. We support up to eight attached drives. + // + drivecount = 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); + } +} + +uda_c::~uda_c() +{ + for(uint32_t i=0; iReset(); + + _ringBase = 0; + _commandRingLength = 0; + _responseRingLength = 0; + _commandRingPointer = 0; + _responseRingPointer = 0; + _interruptVector = 0; + intr_vector.value = 0; + _interruptEnable = false; + _purgeInterruptEnable = false; +} + +// +// GetDriveCount(): +// Returns the number of drives that can be attached to this controller. +// +uint32_t uda_c::GetDriveCount(void) +{ + return drivecount; +} + +// +// GetDrive(): +// Returns a pointer to an mscp_drive_c object for the specified drive number. +// This pointer is owned by the UDA class. +// +mscp_drive_c* uda_c::GetDrive( + uint32_t driveNumber) +{ + assert(driveNumber < drivecount); + + return dynamic_cast(storagedrives[driveNumber]); +} + +// +// StateTransition(): +// Transitions the UDA initialization state machine to the specified step, +// atomically. +// +void uda_c::StateTransition( + InitializationStep nextStep) +{ + pthread_mutex_lock(&on_after_register_access_mutex); + _initStep = nextStep; + _next_step = true; + pthread_cond_signal(&on_after_register_access_cond); + pthread_mutex_unlock(&on_after_register_access_mutex); +} + +// +// worker(): +// Implements the initialization state machine. +// +void uda_c::worker(void) +{ + worker_init_realtime_priority(rt_device); + + timeout_c timeout; + + while (!worker_terminate) + { + // + // Wait to be awoken. + // + pthread_mutex_lock(&on_after_register_access_mutex); + while (!_next_step) + { + pthread_cond_wait( + &on_after_register_access_cond, + &on_after_register_access_mutex); + } + + _next_step = false; + pthread_mutex_unlock(&on_after_register_access_mutex); + + switch (_initStep) + { + case InitializationStep::Uninitialized: + INFO ("Transition to Init state Uninitialized."); + // SA should already be zero but we'll be extra sure here. + update_SA(0x0); + + // Reset the controller: This may take some time as we must + // wait for the MSCP server to wrap up its current workitem. + Reset(); + StateTransition(InitializationStep::Step1); + break; + + case InitializationStep::Step1: + timeout.wait_ms(500); + + INFO ("Transition to Init state S1."); + // + // S1 is set, all other bits zero. This indicates that we + // support a host-settable interrupt vector, that we do not + // implement enhanced diagnostics, and that no errors have + // occurred. + // + update_SA(0x0800); + break; + + case InitializationStep::Step2: + timeout.wait_ms(1500); + INFO ("Transition to Init state S2."); + + // Update the SA read value for step 2: + // S2 is set, unibus port type (0), SA bits 15-8 written + // by the host in step 1. + Interrupt(); + update_SA(0x1000 | ((_step1Value >> 8) & 0xff)); + //Interrupt(); + break; + + case InitializationStep::Step3: + timeout.wait_ms(500); + + INFO ("Transition to Init state S3."); + // Update the SA read value for step 3: + // S3 set, plus SA bits 7-0 written by the host in step 1. + update_SA(0x2000 | (_step1Value & 0xff)); + Interrupt(); + break; + + case InitializationStep::Step4: + timeout.wait_us(100); + + // Clear communications area, set SA + INFO ("Clearing comm area at 0x%x. Purge header: %d", _ringBase, _purgeInterruptEnable); + INFO ("resp 0x%x comm 0x%x", _responseRingLength, _commandRingLength); + + { + int headerSize = _purgeInterruptEnable ? 8 : 4; + for(uint32_t i = 0; + i < (_responseRingLength + _commandRingLength) * sizeof(Descriptor) + headerSize; + i += 2) + { + DMAWriteWord(_ringBase + i - headerSize, 0x0); + } + } + + // + // Set the ownership bit on all descriptors in the response ring + // to indicate that the port owns them. + // + Descriptor blankDescriptor; + blankDescriptor.Word0.Word0 = 0; + blankDescriptor.Word1.Word1 = 0; + blankDescriptor.Word1.Fields.Ownership = 1; + + for(uint32_t i = 0; i < _responseRingLength; i++) + { + DMAWrite( + GetResponseDescriptorAddress(i), + sizeof(Descriptor), + reinterpret_cast(&blankDescriptor)); + } + + INFO ("Transition to Init state S4, comm area initialized."); + // Update the SA read value for step 4: + // Bits 7-0 indicating our control microcode version. + update_SA(UDA50_ID); // UDA50 ID, makes RSTS happy + Interrupt(); + break; + + case InitializationStep::Complete: + INFO ("Initialization complete."); + break; + } + } +} + + +// +// on_after_register_access(): +// Handles register accesses for the IP and SA registers. +// +void +uda_c::on_after_register_access( + unibusdevice_register_t *device_reg, + uint8_t unibus_control +) +{ + switch (device_reg->index) + { + case 0: // IP - read / write + if (UNIBUS_CONTROL_DATO == unibus_control) + { + // "When written with any value, it causes a hard initialization + // of the port and the device controller." + DEBUG("Reset due to IP read"); + StateTransition(InitializationStep::Uninitialized); + } + else + { + // "When read while the port is operating, it causes the controller + // to initiate polling..." + if (_initStep == InitializationStep::Complete) + { + DEBUG("Request to start polling."); + _server->InitPolling(); + } + } + break; + + case 1: // SA - write only + uint16_t value = SA_reg->active_dato_flipflops; + + switch (_initStep) + { + case InitializationStep::Uninitialized: + // Should not occur, we treat it like step1 here. + DEBUG("Write to SA in Uninitialized state."); + + case InitializationStep::Step1: + // Host writes the following: + // 15 13 11 10 8 7 6 0 + // +-+-+-----+-----+-+-------------+ + // |1|W|c rng|r rng|I| int vector | + // | |R| lng | lng |E|(address / 4)| + // +-+-+-----+-----+-+-------------+ + // WR = 1 tells the port to enter diagnostic wrap + // mode (which we ignore). + // + // c rng lng is the number of slots (32 bits each) + // in the command ring, expressed as a power of two. + // + // r rng lng is as above, but for the response ring. + // + // IE=1 means the host is requesting an interrupt + // at the end of the completion of init steps 1-3. + // + // int vector determines if interrupts will be generated + // by the port. If this field is non-zero, interupts will + // be generated during normal operation and, if IE=1, + // during initialization. + _step1Value = value; + + intr_vector.value = _interruptVector = ((value & 0x7f) << 2); + + _interruptEnable = !!(value & 0x80); + _responseRingLength = (1 << ((value & 0x700) >> 8)); + _commandRingLength = (1 << ((value & 0x3800) >> 11)); + + INFO ("Step1: 0x%x", value); + INFO ("resp ring 0x%x", _responseRingLength); + INFO ("cmd ring 0x%x", _commandRingLength); + INFO ("vector 0x%x", _interruptVector); + INFO ("ie %d", _interruptEnable); + + // Move to step 2. + StateTransition(InitializationStep::Step2); + break; + + case InitializationStep::Step2: + // Host writes the following: + // 15 1 0 + // +-----------------------------+-+ + // | ringbase low |P| + // | (address) |I| + // +-----------------------------+-+ + // ringbase low is the low-order portion of word + // [ringbase+0] of the communications area. This is a + // 16-bit byte address whose low-order bit is zero implicitly. + // + _ringBase = value & 0xfffe; + _purgeInterruptEnable = !!(value & 0x1); + + INFO ("Step2: rb 0x%x pi %d", _ringBase, _purgeInterruptEnable); + // Move to step 3 and interrupt as necessary. + StateTransition(InitializationStep::Step3); + break; + + case InitializationStep::Step3: + // Host writes the following: + // 15 0 + // +-+-----------------------------+ + // |P| ringbase hi | + // |P| (address) | + // +-+-----------------------------+ + // PP = 1 means the host is requesting execution of + // purge and poll tests, which we ignore because we can. + // + // ringbase hi is the high-order portion of the address + // [ringbase+0]. + _ringBase |= ((value & 0x7fff) << 16); + + INFO ("Step3: ringbase 0x%x", _ringBase); + // Move to step 4 and interrupt as necessary. + StateTransition(InitializationStep::Step4); + break; + + case InitializationStep::Step4: + // Host writes the following: + // 15 8 7 1 0 + // +---------------+-----------+-+-+ + // | reserved | burst |L|G| + // | | |F|O| + // +---------------+-----------+-+-+ + // Burst is one less than the max. number of longwords + // the host is willing to allow per DMA transfer. + // If zero, the port uses its default burst count. + // + // LF=1 means that the host wants a "last fail" response + // packet when initialization is complete. + // + // GO=1 means that the controller should enter its functional + // microcode as soon as initialization completes. + // + // Note that if GO=0 when initialization completes, the port + // will continue to read SA until the host forces SA bit 0 to + // make the transition 0->1. + // + // There is no explicit interrupt at the end of Step 4. + // + // TODO: For now we ignore burst settings. + // We also ignore Last Fail report requests since we aren't + // supporting onboard diagnostics and there's nothing to + // report. + // + INFO ("Step4: 0x%x", value); + if (value & 0x1) + { + // + // GO is set, move to the Complete state. The worker will + // start the controller running. + // + StateTransition(InitializationStep::Complete); + // The VMS bootstrap expects SA to be zero IMMEDIATELY + // after completion. + update_SA(0x0); + } + else + { + // GO unset, wait until it is. + } + break; + + case InitializationStep::Complete: + // "When zeroed by the host during both initialization and normal + // operation, it signals the port that the host has successfully + // completed a bus adapter purge in response to a port-initiated + // purge request. + // We don't deal with bus adapter purges, yet. + break; + } + break; + + } +} + +// +// update_SA(): +// Updates the SA register value exposed by the Unibone. +// +void +uda_c::update_SA(uint16_t value) +{ + set_register_dati_value( + SA_reg, + value, + "update_SA"); +} + +// +// GetNextCommand(): +// Attempts to pull the next command from the command ring, if any +// are available. +// If successful, returns a pointer to a Message struct; this pointer +// is owned by the caller. +// On failure, nullptr is returned. This indicates that the ring is +// empty or that an attempt to access non-existent memory occurred. +// TODO: Need to handle NXM cases properly. +// +Message* +uda_c::GetNextCommand(void) +{ + timeout_c timer; + + // Grab the next descriptor being pointed to + uint32_t descriptorAddress = + GetCommandDescriptorAddress(_commandRingPointer); + + DEBUG("Next descriptor (ring ptr 0x%x) address is o%o", + _commandRingPointer, + descriptorAddress); + + std::unique_ptr cmdDescriptor( + reinterpret_cast( + DMARead( + descriptorAddress, + sizeof(Descriptor), + sizeof(Descriptor)))); + + // TODO: if NULL is returned after retry assume a bus error and handle it appropriately. + assert(cmdDescriptor != nullptr); + + // Check owner bit: if set, ownership has been passed to us, in which case + // we can attempt to pull the actual message from memory. + if (cmdDescriptor->Word1.Fields.Ownership) + { + bool doInterrupt = false; + + uint32_t messageAddress = + cmdDescriptor->Word0.EnvelopeLow | + (cmdDescriptor->Word1.Fields.EnvelopeHigh << 16); + + DEBUG("Next message address is o%o, flag %d", + messageAddress, cmdDescriptor->Word1.Fields.Flag); + + // + // Grab the message length; this is at messageAddress - 4 + // + bool success = false; + uint16_t messageLength = + DMAReadWord( + messageAddress - 4, + success); + + assert(messageLength > 0 && messageLength < MAX_MESSAGE_LENGTH); + + std::unique_ptr cmdMessage( + reinterpret_cast( + DMARead( + messageAddress - 4, + messageLength + 4, + sizeof(Message)))); + + // + // Handle Ring Transitions (from full to not-full) and associated + // interrupts. + // If the previous entry in the ring is owned by the Port then that indicates + // that the ring was previously full (i.e. the descriptor we're now returning + // is the first free entry.) + // + if (cmdDescriptor->Word1.Fields.Flag) + { + // + // Flag is set, host is requesting a transition interrupt. + // Check the previous entry in the ring. + // + if (_commandRingLength == 1) + { + // Degenerate case: If the ring is of size 1 we always interrupt. + doInterrupt = true; + } + else + { + uint32_t previousDescriptorAddress = + GetCommandDescriptorAddress( + (_commandRingPointer - 1) % _commandRingLength); + + std::unique_ptr previousDescriptor( + reinterpret_cast( + DMARead( + previousDescriptorAddress, + sizeof(Descriptor), + sizeof(Descriptor)))); + + if (previousDescriptor->Word1.Fields.Ownership) + { + // We own the previous descriptor, so the ring was previously + // full. + doInterrupt = true; + } + } + } + + // + // Message retrieved; reset the Owner bit of the command descriptor, + // set the Flag bit (to indicate that we've processed it) + // and return a pointer to the message. + // + cmdDescriptor->Word1.Fields.Ownership = 0; + cmdDescriptor->Word1.Fields.Flag = 1; + DMAWrite( + descriptorAddress, + sizeof(Descriptor), + reinterpret_cast(cmdDescriptor.get())); + + // + // Move to the next descriptor in the ring for next time. + _commandRingPointer = (_commandRingPointer + 1) % _commandRingLength; + + // Post an interrupt as necessary. + if (doInterrupt) + { + // + // Set ring base - 4 to non-zero to indicate a transition. + // + DMAWriteWord(_ringBase - 4, 0x1); + Interrupt(); + } + + return cmdMessage.release(); + } + + DEBUG("No descriptor found. 0x%x 0x%x", cmdDescriptor->Word0.Word0, cmdDescriptor->Word1.Word1); + + // No descriptor available. + return nullptr; +} + +// +// PostResponse(): +// Posts the provided Message to the response ring. +// Returns true on success, false otherwise. +// TODO: Need to handle NXM, as above. +// +bool +uda_c::PostResponse( + Message* response +) +{ + bool res = false; + + // Grab the next descriptor. + uint32_t descriptorAddress = GetResponseDescriptorAddress(_responseRingPointer); + std::unique_ptr cmdDescriptor( + reinterpret_cast( + DMARead( + descriptorAddress, + sizeof(Descriptor), + sizeof(Descriptor)))); + + // TODO: if NULL is returned assume a bus error and handle it appropriately. + + // + // Check owner bit: if set, ownership has been passed to us, in which case + // we can use this descriptor and fill in the response buffer it points to. + // If not, we return false to indicate to the caller the need to try again later. + // + if (cmdDescriptor->Word1.Fields.Ownership) + { + bool doInterrupt = false; + + uint32_t messageAddress = + cmdDescriptor->Word0.EnvelopeLow | + (cmdDescriptor->Word1.Fields.EnvelopeHigh << 16); + + // + // Read the buffer length the host has allocated for this response. + // + // TODO: + // If it is shorter than the buffer we're writing then we will need to + // split the response into multiple responses. I have never seen this happen, + // however and I'm curious if the documentation (AA-L621A-TK) is simply incorrect: + // "Note that if a controller's responses are less than or equal to 60 bytes, + // then the controller need not check the size of the response slot." + // All of the MSCP response messages are shorter than 60 bytes, so this is always + // the case. I'll also note that the spec states "The minimum acceptable size + // is 60 bytes of message text" for the response buffer set up by the host and this + // is *definitely* not followed by host drivers. + // + // The doc is also not exactly clear what a fragmented set of responses looks like... + // + // Message length is at messageAddress - 4 -- this is the size of the command + // not including the two header words. + // + bool success = false; + uint16_t messageLength = + DMAReadWord( + messageAddress - 4, + success); + + DEBUG("response address o%o length o%o", messageAddress, response->MessageLength); + + assert(reinterpret_cast(response)[0] > 0); + + if (messageLength == 0) + { + // A lot of bootstraps appear to set up response buffers of length 0. + // We just log the behavior. + DEBUG("Host response buffer size is zero."); + } + else if (messageLength < response->MessageLength) + { + // + // If this happens it's likely fatal since we're not fragmenting responses (see the big comment + // block above). So eat flaming death. + // Note: the VMS bootstrap does this, so we'll just log the issue. + // + DEBUG("Response buffer 0x%x > host buffer length 0x%x", response->MessageLength, messageLength); + } + + // + // This will fit; simply copy the response message over the top + // of the buffer allocated on the host -- this updates the header fields + // as necessary and provides the actual response data to the host. + // + DMAWrite( + messageAddress - 4, + response->MessageLength + 4, + reinterpret_cast(response)); + + // + // Check if a transition from empty to non-empty occurred, interrupt if requested. + // + // If the previous entry in the ring is owned by the Port then that indicates + // that the ring was previously empty (i.e. the descriptor we're now returning + // is the first entry returned to the ring by the Port.) + // + if (cmdDescriptor->Word1.Fields.Flag) + { + // + // Flag is set, host is requesting a transition interrupt. + // Check the previous entry in the ring. + // + if (_responseRingLength == 1) + { + // Degenerate case: If the ring is of size 1 we always interrupt. + doInterrupt = true; + } + else + { + uint32_t previousDescriptorAddress = + GetResponseDescriptorAddress( + (_responseRingPointer - 1) % _responseRingLength); + + std::unique_ptr previousDescriptor( + reinterpret_cast( + DMARead( + previousDescriptorAddress, + sizeof(Descriptor), + sizeof(Descriptor)))); + + if (previousDescriptor->Word1.Fields.Ownership) + { + // We own the previous descriptor, so the ring was previously + // full. + doInterrupt = true; + } + } + } + + // + // Message posted; reset the Owner bit of the response descriptor, + // and set the Flag bit (to indicate that we've processed it). + // + cmdDescriptor->Word1.Fields.Ownership = 0; + cmdDescriptor->Word1.Fields.Flag = 1; + DMAWrite( + descriptorAddress, + sizeof(Descriptor), + reinterpret_cast(cmdDescriptor.get())); + + // Post an interrupt as necessary. + if (doInterrupt) + { + DEBUG("Response ring no longer empty, interrupting."); + // + // Set ring base - 2 to non-zero to indicate a transition. + // + DMAWriteWord(_ringBase - 2, 0x1); + Interrupt(); + } + + res = true; + + // Move to the next descriptor in the ring for next time. + _responseRingPointer = (_responseRingPointer + 1) % _responseRingLength; + } + + return res; +} + +// +// GetControllerIdentifier(): +// Returns the ID used by SET CONTROLLER CHARACTERISTICS. +// This should be unique per controller. +// +uint32_t +uda_c::GetControllerIdentifier() +{ + // TODO: make this not hardcoded + // ID 0x12345678 + return 0x12345678; +} + +// +// GetControllerClassModel(): +// Returns the Class and Model information used by SET CONTROLLER CHARACTERISTICS. +// +uint16_t +uda_c::GetControllerClassModel() +{ + return 0x0102; // Class 1 (mass storage), model 2 (UDA50) +} + +// +// Interrupt(): +// Invokes a Unibus interrupt if interrupts are enabled and the interrupt +// vector is non-zero. +// +void +uda_c::Interrupt(void) +{ + if ((_interruptEnable || _initStep == InitializationStep::Complete) && _interruptVector != 0) + { + interrupt(); + } +} + +// +// on_power_changed(): +// Resets the controller and all attached drives. +// +void +uda_c::on_power_changed(void) +{ + storagecontroller_c::on_power_changed(); + + if (power_down) + { + DEBUG("Reset due to power change"); + StateTransition(InitializationStep::Uninitialized); + } +} + +// +// on_init_changed(): +// Resets the controller and all attached drives. +// +void +uda_c::on_init_changed(void) +{ + if (init_asserted) + { + DEBUG("Reset due to INIT"); + StateTransition(InitializationStep::Uninitialized); + } + + storagecontroller_c::on_init_changed(); +} + +// +// on_drive_status_changed(): +// A no-op. The controller doesn't require any drive notifications. +// +void +uda_c::on_drive_status_changed(storagedrive_c *drive) +{ + UNUSED(drive); +} + +// +// GetCommandDescriptorAddress(): +// Returns the address of the given command descriptor in the command ring. +// +uint32_t +uda_c::GetCommandDescriptorAddress( + size_t index +) +{ + return _ringBase + _responseRingLength * sizeof(Descriptor) + + index * sizeof(Descriptor); +} + +// +// GetResponseDescriptorAddress(): +// Returns the address of the given response descriptor in the response ring. +// +uint32_t +uda_c::GetResponseDescriptorAddress( + size_t index +) +{ + return _ringBase + index * sizeof(Descriptor); +} + +// +// DMAWriteWord(): +// Writes a single word to Unibus memory. Returns true +// on success; if false is returned this is due to an NXM condition. +// +bool +uda_c::DMAWriteWord( + uint32_t address, + uint16_t word) +{ + return DMAWrite( + address, + sizeof(uint16_t), + reinterpret_cast(&word)); +} + +// +// DMAReadWord(): +// Read a single word from Unibus memory. Returns the word read on success. +// the success field indicates the success or failure of the read. +// +uint16_t +uda_c::DMAReadWord( + uint32_t address, + bool& success) +{ + uint8_t* buffer = DMARead( + address, + sizeof(uint16_t), + sizeof(uint16_t)); + + if (buffer) + { + success = true; + uint16_t retval = *reinterpret_cast(buffer); + delete[] buffer; + return retval; + } + else + { + success = false; + return 0; + } +} + + +// +// DMAWrite(): +// Write data from the provided buffer to Unibus memory. Returns true +// on success; if false is returned this is due to an NXM condition. +// The address specified in 'address' must be word-aligned and the +// length must be even. +// +bool +uda_c::DMAWrite( + uint32_t address, + size_t lengthInBytes, + uint8_t* buffer) +{ + assert ((lengthInBytes % 2) == 0); + assert (address < 0x40000); + + return unibusadapter->request_DMA( + UNIBUS_CONTROL_DATO, + address, + reinterpret_cast(buffer), + lengthInBytes >> 1); +} + +// +// DMARead(): +// Read data from Unibus memory into the returned buffer. +// Buffer returned is nullptr if memory could not be read. +// Caller is responsible for freeing the buffer when done. +// The address specified in 'address' must be word-aligned +// and the length must be even. +// +uint8_t* +uda_c::DMARead( + uint32_t address, + size_t lengthInBytes, + size_t bufferSize) +{ + assert (bufferSize >= lengthInBytes); + assert((lengthInBytes % 2) == 0); + assert (address < 0x40000); + + uint16_t* buffer = new uint16_t[bufferSize >> 1]; + + assert(buffer); + + memset(reinterpret_cast(buffer), 0xc3, bufferSize); + + bool success = unibusadapter->request_DMA( + UNIBUS_CONTROL_DATI, + address, + buffer, + lengthInBytes >> 1); + + if (success) + { + return reinterpret_cast(buffer); + } + else + { + return nullptr; + } +} diff --git a/10.02_devices/2_src/uda.hpp b/10.02_devices/2_src/uda.hpp index 9c46b6a..0e08944 100644 --- a/10.02_devices/2_src/uda.hpp +++ b/10.02_devices/2_src/uda.hpp @@ -68,7 +68,8 @@ public: void on_after_register_access( unibusdevice_register_t *device_reg, - uint8_t unibus_control) override; + uint8_t unibus_control, + uint16_t dato_mask) override; void on_power_changed(void) override; void on_init_changed(void) override;