diff --git a/10.02_devices/2_src/massbus_device.hpp b/10.02_devices/2_src/massbus_device.hpp new file mode 100644 index 0000000..6545eb9 --- /dev/null +++ b/10.02_devices/2_src/massbus_device.hpp @@ -0,0 +1,51 @@ +/* + massbus_device.hpp: Generic interface for MASSBUS devices. + + Copyright Vulcan Inc. 2020 via Living Computers: Museum + Labs, Seattle, WA. + Contributed under the BSD 2-clause license. +*/ + +#pragma once + +// +// Provides a virtual interface connecting a MASSBUS device implementation +// to the RH11 Unibus device implementation. Provides metadata about +// MASSBUS registers, defines methods for reading and writing them, and +// provides methods for transferring blocks of data. +// +class massbus_device_c +{ +public: + + // + // MASSBUS Register Metadata: + // + // Indicates whether this device implements the specified register. + virtual bool ImplementsRegister(uint32_t register) = 0; + + // Provides the mnemonic name for this register + virtual std::string RegisterName(uint32_t register) = 0; + + // Unibus DATI/DATO activity bits + virtual bool RegisterActiveOnDATI(uint32_t register) = 0; + virtual bool RegisterActiveOnDATO(uint32_t register) = 0; + + // Reset value + virtual uint16_t RegisterResetValue(uint32_t register) = 0; + + // Writable bits + virtual uint16_t RegisterWritableBits(uint32_t register) = 0; + + + // + // MASSBUS Register reads and writes + // + virtual void WriteRegister(uint32_t unit, uint32_t register, uint16_t value) = 0; + virtual uint16_t ReadRegister(uint32_t unit, uint32_t register) = 0; + + // + // Block transfers + // TODO: define these + // + +}; diff --git a/10.02_devices/2_src/massbus_rp.cpp b/10.02_devices/2_src/massbus_rp.cpp new file mode 100644 index 0000000..2784c8f --- /dev/null +++ b/10.02_devices/2_src/massbus_rp.cpp @@ -0,0 +1,168 @@ +/* + massbus_rp_c.cpp: Implements MASSBUS device logic for moving-head RP04/05/06 drives. + + Copyright Vulcan Inc. 2020 via Living Computers: Museum + Labs, Seattle, WA. + Contributed under the BSD 2-clause license. + +*/ +#include +#include +using namespace std; + +#include "storagedrive.hpp" +#include "rh11.hpp" +#include "massbus_device.hpp" +#include "massbus_rp.hpp" + +massbus_rp_c::massbus_rp_c( + rh11_c* controller) : + device_c(), + _controller(controller) +{ + name.value="MASSBUS_RP"; + type_name.value = "massbus_rp_c"; + log_label = "MASSBUS_RP"; + +} + +massbus_rp_c::~massbus_rp_c() +{ + +} + +bool +massbus_rp_c::ImplementsRegister( + uint32_t register) +{ + return false; +} + +std::string +massbus_rp_c::RegisterName( + uint32_t register) +{ + std::string name("foo"); + + return name; +} + +bool +massbus_rp_c::RegisterActiveOnDATI( + uint32_t reg) +{ + return _registerMetadata[reg].ActiveOnDATI; +} + +bool +massbus_rp_c::RegisterActiveOnDATO( + uint32_t reg) +{ + return _registerMetadata[reg].ActiveOnDATO; +} + +uint16_t +massbus_rp_c::RegisterResetValue( + uint32_t reg) +{ + return _registerMetadata[reg].ResetValue; +} + +uint16_t +massbus_rp_c::RegisterWritableBits( + uint32_t reg) +{ + return _registerMetadata[reg].WritableBits; +} + +void +massbus_rp_c::WriteRegister( + uint32_t unit, + uint32_t reg, + uint16_t value) +{ + INFO("RP reg write: unit %d register 0%o value 0%o", unit, reg, value); + + switch(reg) + { + case 0: + DoCommand(unit, value); + break; + + default: + FATAL("Unimplemented RP register write."); + break; + } +} + +void massbus_rp_c::DoCommand( + uint32_t unit, + uint16_t command) +{ + // check for GO bit; if unset we have nothing to do here. + if ((command & RP_GO) == 0) + { + return; + } + + uint32_t function = (command & RP_FUNC) >> 1; + + INFO("RP function 0%o", function); + + switch(function) + { + case FunctionCode::Nop: + // Nothing. + 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." + // + set_bit + break; + + case FunctionCode::ReadData: + INFO("RP Read Data"); + break; + + default: + FATAL("Unimplemented RP function."); + break; + } + +} + +uint16_t +massbus_rp_c::ReadRegister( + uint32_t unit, + uint32_t reg) +{ + INFO("RP reg read: unit %d register 0%o", unit, reg); + + FATAL("Unimplemented RP register read."); + + return 0; +} + +// background worker function +void +massbus_rp_c::worker( + unsigned instance) +{ + +} + +void +massbus_rp_c::on_power_changed(void) +{ + +} + +void +massbus_rp_c::on_init_changed(void) +{ + +} diff --git a/10.02_devices/2_src/massbus_rp.hpp b/10.02_devices/2_src/massbus_rp.hpp new file mode 100644 index 0000000..638e502 --- /dev/null +++ b/10.02_devices/2_src/massbus_rp.hpp @@ -0,0 +1,132 @@ +/* + massbus_rp_c.hpp: Implements MASSBUS device logic for moving-head RP04/05/06 drives. + + Copyright Vulcan Inc. 2020 via Living Computers: Museum + Labs, Seattle, WA. + Contributed under the BSD 2-clause license. + +*/ +#ifndef _MASSBUS_RP_HPP_ +#define _MASSBUS_RP_HPP_ + +#include +#include +using namespace std; + +#include "storagedrive.hpp" +#include "rh11.hpp" +#include "massbus_device.hpp" + +// Registers +enum class Registers +{ + Control = 00, + Status = 01, + Error1 = 02, + Maintenance = 03, + AttentionSummary = 04, + DesiredAddress = 05, + LookAhead = 07, + DriveType = 06, + SerialNo = 014, + Offset = 011, + DesiredCylinderAddress = 012, + CurrentCylinderAddress = 013, + Error2 = 010, + Error3 = 015, + ECCPosition = 016, + 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 +// +struct rp_register_data +{ + bool ActiveOnDATI; + bool ActiveOnDATO; + uint16_t ResetValue; + uint16_t WritableBits; +}; + +// +// Encapsulates MASSBUS control logic for RP04/05/06 moving-head disks. +// Talks to one or more rp_drive units. +// +class massbus_rp_c: public device_c, public massbus_device_c +{ +public: + massbus_rp_c(rh11_c* controller); + virtual ~massbus_rp_c(); + +public: + bool ImplementsRegister(uint32_t register) override; + std::string RegisterName(uint32_t register) override; + + bool RegisterActiveOnDATI(uint32_t register) override; + bool RegisterActiveOnDATO(uint32_t register) override; + + uint16_t RegisterResetValue(uint32_t register) override; + uint16_t RegisterWritableBits(uint32_t register) override; + + void WriteRegister(uint32_t unit, uint32_t register, uint16_t value) override; + uint16_t ReadRegister(uint32_t unit, uint32_t register) override; + +private: + void DoCommand(uint32_t unit, uint16_t command); + + // background worker function + void worker(unsigned instance) override; + + void on_power_changed(void) override; + void on_init_changed(void) override; +private: + rh11_c* _controller; + + rp_register_data _registerMetadata[16] = + { + { false, false, 0, 0 }, // 0, not used + { false, false, 0, 0177700 }, // Status + { false, true , 0, 0177777 }, // Error #1 - writable by diagnostics + { false, true , 0, 0177777 }, // Maintenance + { false, false, 0, 0 }, // Attention summary + { false, false, 0, 0017437 }, // Desired Sector/Track + { false, false, 0, 0 }, // Look Ahead + { false, false, 0, 0 }, // Drive Type + { false, false, 0, 0 }, // Serial Number + { false, false, 0, 0177777 }, // Offset + { false, false, 0, 0001777 }, // Desired Cylinder + { false, false, 0, 0 }, // Current Cylinder + { false, false, 0, 0 }, // Error #2 + { false, false, 0, 0 }, // Error #3 + { false, false, 0, 0 }, // ECC Position + { false, false, 0, 0 }, // ECC Pattern + }; + +}; + +#endif diff --git a/10.02_devices/2_src/mscp_drive.hpp b/10.02_devices/2_src/mscp_drive.hpp index 1b7200a..ce3cf52 100644 --- a/10.02_devices/2_src/mscp_drive.hpp +++ b/10.02_devices/2_src/mscp_drive.hpp @@ -17,7 +17,8 @@ // // Implements the backing store for MSCP disk images // -class mscp_drive_c: public storagedrive_c { +class mscp_drive_c: public storagedrive_c +{ public: mscp_drive_c(storagecontroller_c *controller, uint32_t driveNumber); ~mscp_drive_c(void); @@ -55,7 +56,8 @@ public: private: - struct DriveInfo { + struct DriveInfo + { char TypeName[16]; size_t BlockCount; uint32_t MediaID; diff --git a/10.02_devices/2_src/rh11.cpp b/10.02_devices/2_src/rh11.cpp new file mode 100755 index 0000000..e1ff99f --- /dev/null +++ b/10.02_devices/2_src/rh11.cpp @@ -0,0 +1,291 @@ +/* + rh11_cpp: RH11 UNIBUS-MASSBUS controller + + Copyright Vulcan Inc. 2020 via Living Computers: Museum + Labs, Seattle, WA. + Contributed under the BSD 2-clause license. + + */ + +#include +#include +#include + +#include "unibus.h" +#include "unibusadapter.hpp" +#include "unibusdevice.hpp" +#include "storagecontroller.hpp" +#include "rh11.hpp" +#include "massbus_rp.hpp" + +rh11_c::rh11_c() : + storagecontroller_c(), + _massbus(nullptr), + _interruptEnable(false), + _busAddress(0), + _unit(0) +{ + // static config + name.value = "rh"; + type_name.value = "RH11"; + log_label = "rh"; + + // base addr, intr-vector, intr level + // TODO: make configurable based on type (fixed, tape, moving-head disk) + // right now it is hardcoded for moving-head disks. + set_default_bus_params(0776700, 11, 0254, 5); + + // TODO: allow configuration for different device types + _massbus.reset(new massbus_rp_c(this)); + + // The RH11 controller exposes up to 32 registers, not all are used + // and use depends on the devices attached to the MASSBUS. + // TODO: What does an RH11 do when an unimplemented register is accessed? + register_count = 32; + + for (int i=0; iregisters[i]); + + switch (i) + { + case RHCS1: + // Control & Status Reg 1 + strcpy(RH_reg[i]->name, "RHCS1"); + RH_reg[i]->active_on_dati = false; + RH_reg[i]->active_on_dato = true; + RH_reg[i]->reset_value = 0x0008; + RH_reg[i]->writable_bits = 0x037f; + break; + + case RHWC: + // Word count + strcpy(RH_reg[i]->name, "RHWC"); + RH_reg[i]->active_on_dati = false; + RH_reg[i]->active_on_dato = false; + RH_reg[i]->reset_value = 0; + RH_reg[i]->writable_bits = 0xffff; + break; + + case RHBA: + // Bus address + strcpy(RH_reg[i]->name, "RHBA"); + RH_reg[i]->active_on_dati = false; + RH_reg[i]->active_on_dato = false; + RH_reg[i]->reset_value = 0; + RH_reg[i]->writable_bits = 0xffff; + break; + + case RHCS2: + // Control & Status Reg 2 + strcpy(RH_reg[i]->name, "RHCS2"); + RH_reg[i]->active_on_dati = false; + RH_reg[i]->active_on_dato = true; + RH_reg[i]->reset_value = 0; + RH_reg[i]->writable_bits = 0x003f; + break; + + case RHDB: + // Data Buffer (maintenance only) + strcpy(RH_reg[i]->name, "RHDB"); + RH_reg[i]->active_on_dati = true; + RH_reg[i]->active_on_dato = true; + RH_reg[i]->reset_value = 0; + RH_reg[i]->writable_bits = 0xffff; + break; + + default: + // + // This is a "REMOTE" register implemented by the device(s) + // attached to the massbus. + // + if (_massbus->ImplementsRegister(i)) + { + strcpy(RH_reg[i]->name, _massbus->RegisterName(i).c_str()); + RH_reg[i]->active_on_dati = _massbus->RegisterActiveOnDATI(i); + RH_reg[i]->active_on_dato = _massbus->RegisterActiveOnDATO(i); + RH_reg[i]->reset_value = _massbus->RegisterResetValue(i); + RH_reg[i]->writable_bits = _massbus->RegisterWritableBits(i); + } + else + { + // Not implemented by this device type + RH_reg[i] = nullptr; + } + break; + } + } + +} + +rh11_c::~rh11_c() +{ +} + +void +rh11_c::write_register( + uint32_t reg, + uint16_t value) +{ + set_register_dati_value( + RH_reg[_massbusToUnibusRegisterMap[reg]], + value, + "write_register"); +} + +uint16_t +rh11_c::read_register( + uint32_t reg) +{ + return get_register_dato_value( + RH_reg[_massbusToUnibusRegisterMap[reg]]); +} + +// return false, if illegal parameter value. +// verify "new_value", must output error messages +bool rh11_c::on_param_changed(parameter_c *param) +{ + // no own parameter or "enable" logic + if (param == &priority_slot) + { + dma_request.set_priority_slot(priority_slot.new_value); + intr_request.set_priority_slot(priority_slot.new_value); + } + else if (param == &intr_level) + { + intr_request.set_level(intr_level.new_value); + } + else if + (param == &intr_vector) + { + intr_request.set_vector(intr_vector.new_value); + } + return storagecontroller_c::on_param_changed(param); // more actions (for enable) +} + +// Background worker. +// Handle device operations. +void rh11_c::worker(unsigned instance) +{ + UNUSED(instance); // only one + + worker_init_realtime_priority(rt_device); + + bool do_interrupt = true; + timeout_c timeout; + + /* + while (!workers_terminate) + { + } + */ +} + +// +// process DATI/DATO access to the RK11's "active" registers. +// !! called asynchronuously by PRU, with SSYN asserted and blocking UNIBUS. +// The time between PRU event and program flow into this callback +// is determined by ARM Linux context switch +// +// UNIBUS DATO cycles let dati_flipflops "flicker" outside of this proc: +// do not read back dati_flipflops. +void rh11_c::on_after_register_access( + unibusdevice_register_t *device_reg, + uint8_t unibus_control) +{ + UNUSED(unibus_control); + + uint16_t value = device_reg->active_dato_flipflops; + + switch(device_reg->index) + { + case RHCS1: // Control & Status 1 + { + if (UNIBUS_CONTROL_DATO == unibus_control) + { + // IE bit + _interruptEnable = !!(value & 0x40); + + // Extended bus address bits + _busAddress = (_busAddress & 0xffff) | ((value & 0x300) << 8); + + // Let the massbus device take a crack at the shared bits + _massbus->WriteRegister(_unit, RHCS1, value & 0x3f); + + INFO("RHCS1: IE %x BA 0%o", _interruptEnable, _busAddress); + } + } + break; + + case RHCS2: // Control & Status 2 + { + if (UNIBUS_CONTROL_DATO == unibus_control) + { + _unit = (value & 0x7); + _busAddressIncrementProhibit = !!(value & 0x8); + _parityTest = !!(value & 0x10); + + // TODO: handle System Register Clear (bit 5) + INFO("RHCS2: unit %d", _unit); + } + } + break; + + default: + // See if a massbus device wishes to answer + if (RH_reg[device_reg->index] != nullptr) + { + if (UNIBUS_CONTROL_DATO == unibus_control) + { + _massbus->WriteRegister(_unit, _unibusToMassbusRegisterMap[device_reg->index], value); + } + else + { + set_register_dati_value( + device_reg, + _massbus->ReadRegister(_unit, _unibusToMassbusRegisterMap[device_reg->index]), + "on_after_register_access"); + } + } + break; + } + +} + +void rh11_c::invoke_interrupt(void) +{ +} + +void rh11_c::reset_controller(void) +{ + reset_unibus_registers(); + +} + +void rh11_c::on_power_changed(void) +{ + storagecontroller_c::on_power_changed(); + + if (power_down) + { + // power-on defaults + reset_controller(); + } +} + +// UNIBUS INIT: clear all registers +void rh11_c::on_init_changed(void) +{ + // write all registers to "reset-values" + if (init_asserted) + { + reset_controller(); + } + + storagecontroller_c::on_init_changed(); +} + +void +rh11_c::on_drive_status_changed(storagedrive_c *drive) +{ + +} diff --git a/10.02_devices/2_src/rh11.hpp b/10.02_devices/2_src/rh11.hpp new file mode 100755 index 0000000..64677cc --- /dev/null +++ b/10.02_devices/2_src/rh11.hpp @@ -0,0 +1,158 @@ +/* + rh11.hpp: RH11 UNIBUS-MASSBUS controller + + Copyright Vulcan Inc. 2020 via Living Computers: Museum + Labs, Seattle, WA. + Contributed under the BSD 2-clause license. + + */ +#ifndef _RH11_HPP_ +#define _RH11_HPP_ + +#include +#include +#include + +using namespace std; + +#include "utils.hpp" +#include "unibusadapter.hpp" +#include "unibusdevice.hpp" +#include "storagecontroller.hpp" +#include "massbus_device.hpp" + +// Maps the unibus register index to the MASSBUS register number. +// -1 entries are local to the rh11. +int32_t _unibusToMassbusRegisterMap[] = +{ + 00, // 776700 + -1, // 776702 + -1, // 776704 + 05, // 776706 + -1, // 776710 + 01, // 776712 + 02, // 776714 + 04, // 776716 + 07, // 776720 + -1, // 776722 + 03, // 776724 + 06, // 776726 + 010, // 776730 + 011, // 776732 + 012, // 776734 + 013, // 776736 + 014, // 776740 + 015, // 776742 + 016, // etc. + 017, + 020, + 021, + 022, + 023, + 024, + 025, + 026, + 027, + 030, + 031, + 032, + 033 +}; + +int32_t _massbusToUnibusRegisterMap[] = +{ + 00, // 0 + 05, + 06, + 012, + 07, + 03, + 013, + 010, + 014, // 10 + 015, + 016, + 017, + 020, + 021, + 022, + 023, + 024, // 20 + 025, + 026, + 027, + 030, + 031, + 032, + 033, + 034, // 30 + 035, + 036, + 037, + -1, + -1, + -1, + -1, +}; + +class rh11_c: public storagecontroller_c +{ +private: + + // 32 RH11 Registers: + unibusdevice_register_t* RH_reg[32]; + + // "Local" registers (or mostly local anyway) + #define RHCS1 0 + #define RHWC 1 + #define RHBA 2 + #define RHCS2 4 + #define RHDB 011 + + // Unibusadapter: RH11 has one INTR and DMA + // should be merged with RH11::DMARequest + dma_request_c dma_request = dma_request_c(this); // operated by unibusadapter + intr_request_c intr_request = intr_request_c(this); + + void invoke_interrupt(void); + void reset_controller(void); + +private: + + std::unique_ptr _massbus; + + // Control & Status reg 1 bits + bool _interruptEnable; + uint32_t _busAddress; + + // Control & Status reg 2 bits + uint16_t _unit; + bool _busAddressIncrementProhibit; + bool _parityTest; + +public: + + rh11_c(); + virtual ~rh11_c(); + + // Unibus register access (for devices on massbus) + void write_register(uint32_t reg, uint16_t value); + uint16_t read_register(uint32_t reg); + +public: + + // background worker function + 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) override; + + bool on_param_changed(parameter_c *param) override; + void on_power_changed(void) override; + void on_init_changed(void) override; + + void on_drive_status_changed(storagedrive_c *drive) override; +}; + +#endif diff --git a/10.03_app_demo/2_src/makefile b/10.03_app_demo/2_src/makefile index 2a02e38..6c07e1a 100644 --- a/10.03_app_demo/2_src/makefile +++ b/10.03_app_demo/2_src/makefile @@ -104,6 +104,8 @@ OBJECTS = $(OBJDIR)/application.o \ $(OBJDIR)/uda.o \ $(OBJDIR)/mscp_server.o \ $(OBJDIR)/mscp_drive.o \ + $(OBJDIR)/rh11.o \ + $(OBJDIR)/massbus_rp.o \ $(OBJDIR)/rs232.o \ $(OBJDIR)/rs232adapter.o \ $(OBJDIR)/dl11w.o \ @@ -239,6 +241,15 @@ $(OBJDIR)/mscp_server.o : $(DEVICE_SRC_DIR)/mscp_server.cpp $(DEVICE_SRC_DIR)/ $(OBJDIR)/mscp_drive.o : $(DEVICE_SRC_DIR)/mscp_drive.cpp $(DEVICE_SRC_DIR)/mscp_drive.hpp $(CC) $(CCFLAGS) $< -o $@ +$(OBJDIR)/rh11.o : $(DEVICE_SRC_DIR)/rh11.cpp $(DEVICE_SRC_DIR)/rh11.hpp + $(CC) $(CCFLAGS) $< -o $@ + +$(OBJDIR)/massbus_device.o : $(DEVICE_SRC_DIR)/massbus_device.cpp $(DEVICE_SRC_DIR)/massbus_device.hpp + $(CC) $(CCFLAGS) $< -o $@ + +$(OBJDIR)/massbus_rp.o : $(DEVICE_SRC_DIR)/massbus_rp.cpp $(DEVICE_SRC_DIR)/massbus_rp.hpp + $(CC) $(CCFLAGS) $< -o $@ + $(OBJDIR)/rs232.o : $(DEVICE_SRC_DIR)/rs232.cpp $(DEVICE_SRC_DIR)/rs232.hpp $(CC) $(CCFLAGS) $< -o $@ diff --git a/10.03_app_demo/2_src/menu_devices.cpp b/10.03_app_demo/2_src/menu_devices.cpp index 6f03755..71f3b79 100644 --- a/10.03_app_demo/2_src/menu_devices.cpp +++ b/10.03_app_demo/2_src/menu_devices.cpp @@ -52,6 +52,7 @@ #include "rl11.hpp" #include "rk11.hpp" #include "uda.hpp" +#include "rh11.hpp" #include "dl11w.hpp" #include "cpu.hpp" @@ -156,6 +157,8 @@ void application_c::menu_devices(const char *menu_code, bool with_emulated_CPU) rk11_c *RK11 = new rk11_c(); // Create UDA50 uda_c *UDA50 = new uda_c(); + // Create RH11 + rh11_c *RH11 = new rh11_c(); // Create SLU+ LTC slu_c *DL11 = new slu_c(); // to inject characters into DL11 receiver @@ -567,6 +570,9 @@ void application_c::menu_devices(const char *menu_code, bool with_emulated_CPU) UDA50->enabled.set(false); delete UDA50; + RH11->enabled.set(false); + delete RH11; + //test_controller->enabled.set(false); //delete test_controller;