From 8c8a18311681a3821e9f3970e6ccd0b61aa45f20 Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Fri, 5 Apr 2019 21:04:53 +0200 Subject: [PATCH 1/2] Added RK11-D / RK05 emulation to the app demo. --- 10.02_devices/2_src/rk05.cpp | 318 ++++++++++ 10.02_devices/2_src/rk05.hpp | 102 ++++ 10.02_devices/2_src/rk11.cpp | 1066 ++++++++++++++++++++++++++++++++++ 10.02_devices/2_src/rk11.hpp | 167 ++++++ 4 files changed, 1653 insertions(+) create mode 100755 10.02_devices/2_src/rk05.cpp create mode 100755 10.02_devices/2_src/rk05.hpp create mode 100755 10.02_devices/2_src/rk11.cpp create mode 100755 10.02_devices/2_src/rk11.hpp diff --git a/10.02_devices/2_src/rk05.cpp b/10.02_devices/2_src/rk05.cpp new file mode 100755 index 0000000..aaed33f --- /dev/null +++ b/10.02_devices/2_src/rk05.cpp @@ -0,0 +1,318 @@ +/* rk05.cpp: implementation of RK05 disk drive, attached to RK11D controller + +*/ + +#include + +using namespace std; + +#include "logger.hpp" +#include "utils.hpp" +#include "rk11.hpp" +#include "rk05.hpp" + +rk05_c::rk05_c(storagecontroller_c *controller) : + storagedrive_c(controller), + _current_cylinder(0), + _seek_count(0), + _sectorCount(0), + _wps(false), + _rwsrdy(true), + _dry(false), + _sok(false), + _sin(false), + _dru(false), + _rk05(true), + _dpl(false), + _scp(false) +{ + log_label = "RK05"; + _geometry.Cylinders = 203; // Standard RK05 + _geometry.Heads = 2; + _geometry.Sectors = 12; + _geometry.Sector_Size_Bytes = 512; +} + +// +// Status registers +// + +uint32_t rk05_c::get_sector_counter(void) +{ + return _sectorCount; +} + +bool rk05_c::get_write_protect(void) +{ + return _wps; +} + +bool rk05_c::get_rws_ready(void) +{ + return _rwsrdy; +} + +bool rk05_c::get_drive_ready(void) +{ + return _dry; +} + +bool rk05_c::get_sector_counter_ok(void) +{ + return _sok; +} + +bool rk05_c::get_seek_incomplete(void) +{ + return _sin; +} + +bool rk05_c::get_drive_unsafe(void) +{ + return _dru; +} + +bool rk05_c::get_rk05_disk_online(void) +{ + return _rk05; +} + +bool rk05_c::get_drive_power_low(void) +{ + return _dpl; +} + +bool rk05_c::get_search_complete(void) +{ + bool scp = _scp; + _scp = false; + return scp; +} + +// +// Reset / Power handlers +// + +void rk05_c::on_power_changed(void) +{ + // called at high priority. + if (power_down) + { + // power-on defaults + drive_reset(); + } +} + +void rk05_c::on_init_changed(void) +{ + // called at high priority. + + if (init_asserted) + { + drive_reset(); + + if (!file_is_open()) + { + load_image(image_filepath.value); + + file_open(image_filepath.value, true); + _dry = file_is_open(); + controller->on_drive_status_changed(this); + } + } +} + +// +// Disk actions (read/write/seek/reset) +// + +void rk05_c::read_sector( + uint32_t cylinder, + uint32_t surface, + uint32_t sector, + uint16_t* out_buffer) +{ + assert(cylinder < _geometry.Cylinders); + assert(surface < _geometry.Heads); + assert(sector < _geometry.Sectors); + + _current_cylinder = cylinder; + + // SCP is cleared at the start of any function. + _scp = false; + + // + // reset Read/Write/Seek Ready flag while we do this operation + // + _rwsrdy = false; + controller->on_drive_status_changed(this); + + timeout_c delay; + + // Delay for seek / read. + // TODO: maybe base this on real drive specs. + delay.wait_ms(10); + + // Read the sector into the buffer passed to us. + file_read( + reinterpret_cast(out_buffer), + get_disk_byte_offset(cylinder, surface, sector), + _geometry.Sector_Size_Bytes); + + // Set RWS ready now that we're done. + _rwsrdy = true; + + controller->on_drive_status_changed(this); +} + +void rk05_c::write_sector( + uint32_t cylinder, + uint32_t surface, + uint32_t sector, + uint16_t* in_buffer) +{ + assert(cylinder < _geometry.Cylinders); + assert(surface < _geometry.Heads); + assert(sector < _geometry.Sectors); + + _current_cylinder = cylinder; + + // SCP is cleared at the start of any function. + _scp = false; + + // + // reset Read/Write/Seek Ready flag while we do this operation + // + _rwsrdy = false; + controller->on_drive_status_changed(this); + + timeout_c delay; + + // Delay for seek / read. + // TODO: maybe base this on real drive specs. + delay.wait_ms(10); + + // Read the sector into the buffer passed to us. + file_write( + reinterpret_cast(in_buffer), + get_disk_byte_offset(cylinder, surface, sector), + _geometry.Sector_Size_Bytes); + + // Set RWS ready now that we're done. + _rwsrdy = true; + + controller->on_drive_status_changed(this); +} + +void rk05_c::seek( + uint32_t cylinder) +{ + assert(cylinder < _geometry.Cylinders); + + _seek_count = abs((int32_t)_current_cylinder - (int32_t)cylinder) + 1; + _current_cylinder = cylinder; + + if (_seek_count > 0) + { + // We'll be busy for awhile: + _rwsrdy = false; + _scp = false; + } + else + { + _rwsrdy = true; + _scp = true; + } + controller->on_drive_status_changed(this); +} + +void rk05_c::set_write_protect(bool protect) +{ + // Not implemented at the moment. + _scp = false; +} + +void rk05_c::drive_reset(void) +{ + // + // "The controller directs the selected disk drive to move its + // head mechanism to cylinder address 000 and reset all active + // error status lines." + // + // This is basically the same as a seek to cylinder 0 plus + // a reset of error status. + // + _sin = false; + _dru = false; + _dpl = false; + controller->on_drive_status_changed(this); + + seek(0); + // SCP change will be posted when the seek instigated above is completed. +} + +void rk05_c::worker(void) +{ + timeout_c timeout; + + while(true) + { + if (_seek_count > 0) + { + // A seek is active. Wait at least 10ms and decrement + // The seek count by a certain amount. This is completely fudged. + timeout.wait_ms(3); + _seek_count -= 25; + // since simultaneous interrupts + // confuse me right now + + if (_seek_count < 0) + { + // Out of seeks to do, let the controller know we're done. + _scp = true; + controller->on_drive_status_changed(this); + + // Set RWSRDY only after posting status change / interrupt... + _rwsrdy = true; + } + } + else + { + // Move SectorCounter to next sector + // every 1/300th of a second (or so). + // (1600 revs/min = 25 revs / sec = 300 sectors / sec) + timeout.wait_ms(3); + if (file_is_open()) + { + _sectorCount = (_sectorCount + 1) % 12; + _sok = true; + controller->on_drive_status_changed(this); + } + + // Crude: Check for disk image change; if changed we load the new image. + if (image_filepath.value != _currentFilePath) + { + load_image(image_filepath.value); + } + } + } +} + +uint64_t rk05_c::get_disk_byte_offset( + uint32_t cylinder, + uint32_t surface, + uint32_t sector) +{ + return _geometry.Sector_Size_Bytes * + ((cylinder * _geometry.Heads * _geometry.Sectors) + + (surface * _geometry.Sectors) + + sector); +} + +void rk05_c::load_image(std::string path) +{ + file_open(image_filepath.value, true); + _dry = file_is_open(); + controller->on_drive_status_changed(this); + _currentFilePath = path; +} diff --git a/10.02_devices/2_src/rk05.hpp b/10.02_devices/2_src/rk05.hpp new file mode 100755 index 0000000..055971f --- /dev/null +++ b/10.02_devices/2_src/rk05.hpp @@ -0,0 +1,102 @@ +/* rk05.cpp: implementation of RK05 disk drive, used with RK11D controller + +*/ +#ifndef _RK05_HPP_ +#define _RK05_HPP_ + +#include +#include +using namespace std; + +#include "storagedrive.hpp" +#include "rk11.hpp" + +enum DriveType +{ + RK05 = 0, + RK05f = 1, +}; + +struct Geometry +{ + uint32_t Cylinders; + uint32_t Heads; + uint32_t Sectors; + uint32_t Sector_Size_Bytes; + uint32_t Sector_Size_Words; +}; + +class rk05_c: public storagedrive_c +{ +private: + // Drive geometry details + Geometry _geometry; + + // Current position of the heads + volatile uint32_t _current_cylinder; + volatile int32_t _seek_count; + + // Current sector under the heads (used to satisfy RKDS register, + // incremented by worker thread, unrelated to sector reads/writes) + volatile uint32_t _sectorCount; + + // Status bits + volatile bool _wps; // Write Protect status + volatile bool _rwsrdy; // Indicates that the drive is ready to accept a new function. + volatile bool _dry; // Indicates that the drive is powered, loaded, running, rotating, and not unsafe. + volatile bool _sok; // Indicates that the _sectorCount value is not in a state of flux. + volatile bool _sin; // Indicates that the seek could not be completed. + volatile bool _dru; // Indicates that an unusual condition has occurred in the disk drive and is unsafe. + volatile bool _rk05; // Always set, identifies the drive as an RK05 + volatile bool _dpl; // Set when an attempt to initiate a new function (or a function is in progress) when power is low. + + volatile bool _scp; // Indicates the completion of a seek + + std::string _currentFilePath; // Currently loaded disk image if any + + uint64_t get_disk_byte_offset( + uint32_t cylinder, + uint32_t surface, + uint32_t sector); + + void load_image(std::string path); + +public: + Geometry get_geometry(void); + uint32_t get_cylinder(void); + + // Status bits + uint32_t get_sector_counter(void); + bool get_write_protect(void); + bool get_rws_ready(void); + bool get_drive_ready(void); + bool get_sector_counter_ok(void); + bool get_seek_incomplete(void); + bool get_drive_unsafe(void); + bool get_rk05_disk_online(void); + bool get_drive_power_low(void); + + // Not a status bit per-se, indicates whether a seek has completed since the last status change. + bool get_search_complete(void); + + // Commands + void read_sector(uint32_t cylinder, uint32_t surface, uint32_t sector, uint16_t* out_buffer); + void write_sector(uint32_t cylinder, uint32_t surface, uint32_t sector, uint16_t* in_buffer); + void seek(uint32_t cylinder); + void set_write_protect(bool protect); + void drive_reset(void); + +public: + DriveType _drivetype; + + rk05_c(storagecontroller_c *controller); + + void on_power_changed(void) override; + + void on_init_changed(void) override; + + // background worker function + void worker(void) override; +}; + +#endif diff --git a/10.02_devices/2_src/rk11.cpp b/10.02_devices/2_src/rk11.cpp new file mode 100755 index 0000000..659d062 --- /dev/null +++ b/10.02_devices/2_src/rk11.cpp @@ -0,0 +1,1066 @@ +/* rk11_cpp: RK11 UNIBUS controller + + */ + +#include +#include +#include + +#include "unibus.h" +#include "unibusadapter.hpp" +#include "unibusdevice.hpp" +#include "storagecontroller.hpp" +#include "rk11.hpp" +#include "rk05.hpp" + +rk11_c::rk11_c() : + storagecontroller_c(), + _new_command_ready(false) +{ + // static config + name.value = "rk"; + type_name.value = "RK11"; + log_label = "rk"; + default_base_addr = 0777400; // overwritten in install()? + default_intr_vector = 0220; // TODO: make configurable + default_intr_level = 5; // TODO: make configurable + + + // The RK11 controller has seven registers, + // We allocate 8 because one address in the address space is unused. + register_count = 8; + + // Drive Status register (read-only) + RKDS_reg = &(this->registers[0]); // @ base addr + strcpy(RKDS_reg->name, "RKDS"); + RKDS_reg->active_on_dati = false; // no controller state change + RKDS_reg->active_on_dato = false; + RKDS_reg->reset_value = 0; + RKDS_reg->writable_bits = 0x0000; // read only + + // Error Register (read-only) + RKER_reg = &(this->registers[1]); // @ base addr + 2 + strcpy(RKER_reg->name, "RKER"); + RKER_reg->active_on_dati = false; // no controller state change + RKER_reg->active_on_dato = false; + RKER_reg->reset_value = 0; + RKER_reg->writable_bits = 0x0000; // read only + + // Control Status Register (read/write) + RKCS_reg = &(this->registers[2]); // @ base addr + 4 + strcpy(RKCS_reg->name, "RKCS"); + RKCS_reg->active_on_dati = false; + RKCS_reg->active_on_dato = true; + RKCS_reg->reset_value = 0x0080; // RDY high after INIT + RKCS_reg->writable_bits = 0x0f7f; + + // Word Count Register (read/write) + RKWC_reg = &(this->registers[3]); // @ base addr + 6 + strcpy(RKWC_reg->name, "RKWC"); + RKWC_reg->active_on_dati = false; + RKWC_reg->active_on_dato = false; + RKWC_reg->reset_value = 0; + RKWC_reg->writable_bits = 0xffff; + + // Current Bus Address Register (read/write) + RKBA_reg = &(this->registers[4]); // @ base addr + 10 + strcpy(RKBA_reg->name, "RKBA"); + RKBA_reg->active_on_dati = false; + RKBA_reg->active_on_dato = false; + RKBA_reg->reset_value = 0; + RKBA_reg->writable_bits = 0xffff; + + // Disk Address Register (write only?) + RKDA_reg = &(this->registers[5]); // @ base addr + 12 + strcpy(RKDA_reg->name, "RKDA"); + RKDA_reg->active_on_dati = false; + RKDA_reg->active_on_dato = true; // To allow writes only when controller is in READY state. + RKDA_reg->reset_value = 0; + RKDA_reg->writable_bits = 0xffff; + + // Unused entry (@ Base addr + 14) + + // Data Buffer Register (read only) + RKDB_reg = &(this->registers[7]); // @ base addr + 16 + strcpy(RKDB_reg->name, "RKDB"); + RKDB_reg->active_on_dati = false; + RKDB_reg->active_on_dato = false; + RKDB_reg->reset_value = 0; + RKDB_reg->writable_bits = 0x0000; // read only + + // + // Drive configuration: up to eight drives. + // + drivecount = 8; + 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); + } +} + +rk11_c::~rk11_c() +{ + for (uint32_t i=0; irequest_DMA( + this, + UNIBUS_CONTROL_DATO, + request.address, + request.buffer + request.count - 1, + 1); + } + else + { + // Read FROM unibus memory TO buffer, IBA on: + // We read a single word from the unibus and fill the + // entire buffer with this value. + unibusadapter->request_DMA( + this, + UNIBUS_CONTROL_DATI, + request.address, + request.buffer, + 1); + } + } + else + { + // Just a normal DMA transfer. + if (request.write) + { + // Write FROM buffer TO unibus memory + unibusadapter->request_DMA( + this, + UNIBUS_CONTROL_DATO, + request.address, + request.buffer, + request.count); + } + else + { + // Read FROM unibus memory TO buffer + unibusadapter->request_DMA( + this, + UNIBUS_CONTROL_DATI, + request.address, + request.buffer, + request.count); + } + } + + // And wait for completion. + while(true) + { + timeout.wait_us(50); // Stolen from RL11 + uint32_t last_address = 0; + if (unibusadapter->complete_DMA( + this, + &last_address, + &request.timeout)) + { + break; + } + } + + // If an IBA DMA read from memory, we need to fill the request buffer + // with the single word returned from memory by the DMA operation. + if (request.iba && !request.write) + { + for(uint32_t i = 1; i < request.count; i++) + { + request.buffer[i] = request.buffer[0]; + } + } +} + +// Background worker. +// Handle device operations. +void rk11_c::worker(void) +{ + + worker_init_realtime_priority(rt_device); + + _worker_state = Worker_Idle; + WorkerCommand command = { 0 }; + + bool do_interrupt = true; + timeout_c timeout; + while (!worker_terminate) + { + switch (_worker_state) + { + case Worker_Idle: + { + pthread_mutex_lock(&on_after_register_access_mutex); + while (!_new_command_ready) + { + pthread_cond_wait( + &on_after_register_access_cond, + &on_after_register_access_mutex); + } + + // + // Make a local copy of the new command to execute. + // + command = _new_command; + _new_command_ready = false; + pthread_mutex_unlock(&on_after_register_access_mutex); + + // + // Clear GO now that we've accepted the command. + // + _go = false; + update_RKCS(); + + // + // We will interrupt after completion of a command except + // in certain error cases. + // + do_interrupt = true; + + // + // Move to the execution state to start executing the command. + // + _worker_state = Worker_Execute; + } + break; + + case Worker_Execute: + switch(command.function) + { + case Write: + case Read: + case Write_Check: + { + bool write = command.function == Write; + bool read_format = command.function == Read && command.format; + bool read = command.function == Read && !command.format; + bool write_check = command.function == Write_Check; + + // We loop over the requested address range + // and submit DMA requests one at a time, waiting for + // their completion. + + uint16_t sectorBuffer[256]; + uint16_t checkBuffer[256]; + + uint32_t current_address = command.address; + int16_t current_count = -(int16_t)(get_register_dato_value(RKWC_reg)); + bool abort = false; + while(current_count > 0 && !abort) + { + // If a new command has been written in the CS register, abandon + // this one ASAP. + if (_new_command_ready) + { + DEBUG("Command canceled."); + abort = true; + continue; + } + + // Validate that the current disk address is valid. + if (!validate_seek()) + { + // The above check set error bits accordingly. + // Just abort the transfer now. + // Set OVR if we've gone off the end of the disk. + if (_rkda_cyl > 202) + { + _ovr = true; + // update_RKER(); + } + abort = true; + continue; + } + + // + // Clear the buffer. This is only necessary because short writes + // and reads expect the rest of the sector to be filled with zeroes. + // + memset(sectorBuffer, 0, sizeof(sectorBuffer)); + + if (read) + { + // Doing a normal read from disk: Grab the sector data and then + // DMA it into memory. + selected_drive()->read_sector( + _rkda_cyl, + _rkda_surface, + _rkda_sector, + sectorBuffer); + } + else if (read_format) + { + // Doing a Read Format: Read only the header word from the disk + // and DMA that single word into memory. + // We don't actually read the header word from disk since we don't + // store header data in the disk image; we just fake it up -- + // since we always seek correctly this is all that is required. + // + // The header is just the cylinder address, as in RKDA 05-12 (p. 3-9) + sectorBuffer[0] = (_rkda_cyl << 5); + } + else if (write_check) + { + // Doing a Write Check: Grab the sector data from the disk into + // the check buffer. + selected_drive()->read_sector( + _rkda_cyl, + _rkda_surface, + _rkda_sector, + checkBuffer); + } + + // + // Normal Read, or Write/Write Format: DMA the data to/from memory, + // etc. + // + // build the DMA transfer request: + DMARequest request = { 0 }; + request.address = current_address; + request.count = (!read_format) ? + min(static_cast(256) , current_count) : + 1; + request.write = !(write || write_check); // Inverted sense from disk action + request.timeout = false; + request.buffer = sectorBuffer; + request.iba = command.iba; + + // And actually do the transfer. + dma_transfer(request); + + // Check completion status -- if there was an error, + // we'll abort and set the appropriate flags. + if (request.timeout) + { + _nxm = true; + _he = true; + _err = true; + // update_RKCS(); + // update_RKER(); + abort = true; + } + else + { + if (write) + { + // Doing a write to disk: Write the buffer DMA'd from + // memory to the disk. + selected_drive()->write_sector( + _rkda_cyl, + _rkda_surface, + _rkda_sector, + sectorBuffer); + } + else if (write_check) + { + // Compare check buffer with sector buffer, if there are + // any discrepancies, set WCE and interrupt as necessary. + for (int i = 0; i < request.count; i++) + { + if (sectorBuffer[i] != checkBuffer[i]) + { + _wce = true; + _err = true; + // update_RKER(); + // update_RKCS(); + + if (_sse) + { + // Finish this transfer and abort. + abort = true; + break; + } + } + } + + if (_wce && _sse) + { + // Control action stops on error after this transfer + // if SSE (Stop on Soft Error) is set. + abort = true; + } + } + else // Read + { + // After read complete, set RKDB to the last word + // read (this satisfies ZRKK): + set_register_dati_value( + RKDB_reg, + sectorBuffer[request.count - 1], + "RK11 READ"); + } + } + + // Transfer completed. Move to next and update registers. + current_count -= request.count; + + set_register_dati_value( + RKWC_reg, + (uint16_t)(-current_count), + __func__); + + if (!command.iba) + { + current_address += (request.count * 2); // Byte address + set_register_dati_value( + RKBA_reg, + (uint16_t)current_address, + __func__); + _mex = ((current_address) >> 16) & 0x3; + // update_RKCS(); + } + + // Move to next disk address + increment_RKDA(); + + // And go around, do it again. + } + + // timeout.wait_us(100); + DEBUG("R/W: Complete."); + _worker_state = Worker_Finish; + } + break; + + case Read_Check: + // + // "...is identical to a normal Read function, except that no + // NPRs occur. Only the checksum is calculated and compared + // with the checksum read from the disk drive... it must be + // performed on a whole-sector basis only." + // Since all data is error free in our emulation, the only + // thing we do here is increment RKDA the requisite number + // of times and clear RKWC. + // + { + uint32_t current_address = command.address; + int16_t current_count = -(int16_t)(get_register_dato_value(RKWC_reg)); + // TODO: could do all this without going through the motions. + while (current_count > 0) + { + if (!validate_seek()) + { + // TODO: set RKER, etc. + break; + } + + current_count -= 256; // TODO: remove hardcoding. + if (!command.iba) + { + current_address += (256 * 2); // Byte address + set_register_dati_value( + RKWC_reg, + (uint16_t)(-current_count), + __func__); + _mex = ((current_address) >> 16) & 0x3; + // update_RKCS(); + } + + // Move to next disk address. + increment_RKDA(); + + // And go around, do it again. + } + + _worker_state = Worker_Finish; + } + break; + + case Seek: + // + // Per ZRKK, if IDE is set, an interrupt is raised at the beginning + // of the seek, and another is raised when the seek is complete. + // + if (validate_seek()) + { + // Per ZRKK, ID bits in RKDS get cleared at the beginning of a seek. + _id = 0; + update_RKDS(); + + // Start the seek; this will complete asynchronously. + selected_drive()->seek( + _rkda_cyl); + } + else + { + do_interrupt = false; + } + _worker_state = Worker_Finish; + break; + + case Drive_Reset: + if (check_drive_present()) + { + selected_drive()->drive_reset(); + } + else + { + do_interrupt = false; + } + _worker_state = Worker_Finish; + break; + + default: + INFO("Unhandled function %d.", command.function); + // For now we just move to the Finish state so that future + // commands can execute. + _worker_state = Worker_Finish; + break; + } + break; + + case Worker_Finish: + // Set RDY, interrupt (if enabled) and go back to the Idle state. + // We do this so that the update of ER, CS regs and the interrupt + // are atomic w.r.t. RKCS access (diagnostic code polls CS and will + // start a new operation immediately, lowering RDY before we invoke + // the interrupt, causing behavior diagnostics do not expect.) + pthread_mutex_lock(&on_after_register_access_mutex); + _rdy = true; + update_RKER(); + update_RKCS(); + + // Interrupt unless specified not to. + if (do_interrupt) + { + assert(_rdy); + invoke_interrupt(); + } + pthread_mutex_unlock(&on_after_register_access_mutex); + _worker_state = Worker_Idle; + break; + } + } +} + +bool rk11_c::validate_seek(void) +{ + bool error = false; + + if (_rkda_sector > 11) + { + _nxs = true; + error = true; + } + + if (_rkda_cyl > 202) + { + _nxc = true; + error = true; + } + + if (!check_drive_present()) + { + _nxd = true; + error = true; + } + + if (error) + { + // Update the RKER register + // update_RKER(); + + // Set the Control/Status register HE and ERR registers. + _he = true; + _err = true; + // update_RKCS(); + + // Do not interrupt here, caller will do so based on + // our return value. + // invoke_interrupt(); + } + + return !error; +} + +void rk11_c::increment_RKDA() +{ + _rkda_sector++; + + if (_rkda_sector > 11) + { + _rkda_sector = 0; + _rkda_surface++; + + if (_rkda_surface > 1) + { + _rkda_surface = 0; + _rkda_cyl++; + } + } + update_RKDA(); +} + +// +// 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 rk11_c::on_after_register_access( + unibusdevice_register_t *device_reg, + uint8_t unibus_control) +{ + UNUSED(unibus_control); + + // The RK11 has only one "active" register, RKCS. + // When "GO" bit is set, kick off an operation and clear the + // RDY bit. + switch(device_reg->index) + { + + case 2: // RKCS + _go = !!(RKCS_reg->active_dato_flipflops & 0x1); + _function = (RKCS_reg->active_dato_flipflops & 0xe) >> 1; + _mex = (RKCS_reg->active_dato_flipflops & 0x30) >> 4; + _ide = !!(RKCS_reg->active_dato_flipflops & 0x40); + _sse = !!(RKCS_reg->active_dato_flipflops & 0x100); + _exb = !!(RKCS_reg->active_dato_flipflops & 0x200); + _fmt = !!(RKCS_reg->active_dato_flipflops & 0x400); + _iba = !!(RKCS_reg->active_dato_flipflops & 0x800); + + // + // GO bit is set: + // "Causes the control to carry out the function contained in bits 01 through 03 of the + // "RKCS (Function). Remains set until the control actually begins to respond to GO, which + // may take from 1us to 3.3ms, depending on the current operation of the selected disk drive..." + // TODO: what happens if RDY is low and GO is high? + if (_go) + { + bool error = false; + + // + // Check for PGE (Programming Error): use of FMT with a function other than Read or Write. + // + if (_fmt && (_function != Read && _function != Write && _function != Control_Reset)) + { + _pge = true; + _he = true; + _err = true; + + // GO gets cleared + _go = false; + // update_RKER(); + // update_RKCS(); + // error = true; + } + else + { + switch(_function) + { + case Control_Reset: + // + // "The Control Reset function initializes all internal registers and flip-flops + // and clears all the bits of the seven programmable registers except RKCS 07 (RDY) + // which it sets, and RKDS 01 through 11, which are not affected. + // + reset_controller(); + break; + + case Write_Lock: + // + // "Write-protects a selected disk drive until the condition is overridden by the + // operation of the corresponding WT PROT switch on the disk drive..." + // + selected_drive()->set_write_protect(true); + _scp = false; + // update_RKCS(); + break; + + default: + // All other commands take significant time and happen on the worker thread. + // First check: + // If this is not a Drive Reset command and the drive is not ready or has taken + // a fault, we will abort, set the appropriate error bits and interrupt. + // + if (_function != Drive_Reset && !check_drive_ready()) + { + _dre = true; + // update_RKER(); + _he = true; + _err = true; + _scp = false; + _go = false; + // update_RKCS(); + // INFO("setting DRE, fn %o dr rdy %d rws rdy %d", _function, + // selected_drive()->get_drive_ready(), + // selected_drive()->get_rws_ready()); + + // invoke_interrupt(); + error = true; + break; + } + + // If this is an attempt to address a nonexistent (not present or not + // loaded) drive, this triggers an NXD error. + if (!check_drive_present()) + { + _nxd = true; + _he = true; + _err = true; + _scp = false; + _go = false; + // update_RKCS(); + // update_RKER(); + + // invoke_interrupt(); + error = true; + break; + } + + // Clear RDY, SCP bits: + pthread_mutex_lock(&on_after_register_access_mutex); + _rdy = false; + _scp = false; + // update_RKCS(); + + // + // Stow command data for this operation so that if RKCS gets changed + // mid-op weird things won't happen. + // + _new_command.address = get_register_dato_value(RKBA_reg) | (_mex << 16); + _new_command.drive = selected_drive(); + _new_command.function = _function; + _new_command.interrupt = _ide; + _new_command.stop_on_soft_error = _sse; + _new_command.format = _fmt; + _new_command.iba = _iba; + _new_command_ready = true; + + // + // Wake the worker. + // + pthread_cond_signal(&on_after_register_access_cond); + pthread_mutex_unlock(&on_after_register_access_mutex); + break; + } + } + + update_RKER(); + update_RKCS(); + + if (error) + { + assert(_rdy); + invoke_interrupt(); + } + } + else + { + // Stow the value in the register. + update_RKCS(); + + // If IDE is set (interrupt on done) and RDY is set, we will interrupt. + if (_rdy) + { + invoke_interrupt(); + } + } + break; + + case 5: // RKDA + // + // Writes are only accepted when RDY is high. + // + if (_rdy) + { + uint32_t oldDrive = _rkda_drive; + + _rkda_sector = (RKDA_reg->active_dato_flipflops & 0xf); + _rkda_surface = (RKDA_reg->active_dato_flipflops & 0x10) >> 4; + _rkda_cyl = (RKDA_reg->active_dato_flipflops & 0x1fe0) >> 5; + _rkda_drive = (RKDA_reg->active_dato_flipflops & 0xe000) >> 13; + update_RKDA(); + + // Pick up new drive's status if drive ID changed. + if (_rkda_drive != oldDrive) + { + update_RKDS(); + } + } + break; + + default: + // This should never happen. + assert(false); + break; + } + +} + +bool rk11_c::check_drive_ready(void) +{ + if (!selected_drive()->get_drive_ready() || + selected_drive()->get_seek_incomplete() || + selected_drive()->get_drive_unsafe() || + selected_drive()->get_drive_power_low()) + { + return false; + } + else + { + return true; + } +} + +bool rk11_c::check_drive_present(void) +{ + return (selected_drive()->get_drive_ready()); +} + +void rk11_c::on_drive_status_changed(storagedrive_c *drive) +{ + // only update if drive reporting a change + // is the same as currently selected drive in RKDA. + if (drive->unitno.value == selected_drive()->unitno.value) + { + update_RKDS(); + } + + // If interrupts are enabled and the drive has completed a seek, + // interrupt now. Note that the call to get_search_complete() has + // the side effect (eww) of resetting the drive's SCP bit, so we do it + // first (so it always gets cleared even if we're not interrupting.) + if (dynamic_cast(drive)->get_search_complete() && + _ide) + { + // Set SCP to indicate that this interrupt was due to a previous + // Seek or Drive Reset function. + _scp = true; + _id = drive->unitno.value; + update_RKDS(); + update_RKCS(); + invoke_interrupt(); + } +} + +void rk11_c::update_RKER(void) +{ + unsigned short new_RKER = + (_wce ? 0x0001 : 0x0000) | + (_cse ? 0x0002 : 0x0000) | + (_nxs ? 0x0020 : 0x0000) | + (_nxc ? 0x0040 : 0x0000) | + (_nxd ? 0x0080 : 0x0000) | + (_te ? 0x0100 : 0x0000) | + (_dlt ? 0x0200 : 0x0000) | + (_nxm ? 0x0400 : 0x0000) | + (_pge ? 0x0800 : 0x0000) | + (_ske ? 0x1000 : 0x0000) | + (_wlo ? 0x2000 : 0x0000) | + (_ovr ? 0x4000 : 0x0000) | + (_dre ? 0x8000 : 0x0000); + + set_register_dati_value( + RKER_reg, + new_RKER, + "update_RKER"); +} + +void rk11_c::update_RKCS(void) +{ + unsigned short new_RKCS = + (_go ? 0x0001 : 0x0000) | + (_function << 1) | + (_mex << 4) | + (_ide ? 0x0040 : 0x0000) | + (_rdy ? 0x0080 : 0x0000) | + (_sse ? 0x0100 : 0x0000) | + (_exb ? 0x0200 : 0x0000) | + (_fmt ? 0x0400 : 0x0000) | + (_iba ? 0x0800 : 0x0000) | + (_scp ? 0x2000 : 0x0000) | + (_he ? 0x4000 : 0x0000) | + (_err ? 0x8000 : 0x0000); + + set_register_dati_value( + RKCS_reg, + new_RKCS, + "update_RKCS"); +} + +void rk11_c::update_RKDS(void) +{ + rk05_c* drive = selected_drive(); + uint32_t sectorCounter = drive->get_sector_counter(); + bool sceqsa = sectorCounter == _rkda_sector; + bool wps = drive->get_write_protect(); + bool rwsrdy = drive->get_rws_ready(); + bool dry = drive->get_drive_ready(); + bool sok = drive->get_sector_counter_ok(); + bool sin = drive->get_seek_incomplete(); + bool dru = drive->get_drive_unsafe(); + bool rk05 = drive->get_rk05_disk_online(); + bool dpl = drive->get_drive_power_low(); + + unsigned short new_RKDS = + sectorCounter | + (sceqsa ? 0x0010 : 0x0000) | + (wps ? 0x0020 : 0x0000) | + (rwsrdy ? 0x0040 : 0x0000) | + (dry ? 0x0080 : 0x0000) | + (sok ? 0x0100 : 0x0000) | + (sin ? 0x0200 : 0x0000) | + (dru ? 0x0400 : 0x0000) | + (rk05 ? 0x0800 : 0x0000) | + (dpl ? 0x1000 : 0x0000) | + (_id << 13); + + set_register_dati_value( + RKDS_reg, + new_RKDS, + "update_RKDS"); +} + +void rk11_c::update_RKDA(void) +{ + unsigned short new_RKDA = + _rkda_sector | + (_rkda_surface << 4) | + (_rkda_cyl << 5) | + (_rkda_drive << 13); + + set_register_dati_value( + RKDA_reg, + new_RKDA, + "update_RKDA"); +} + +void rk11_c::invoke_interrupt(void) +{ + // + // Invoke an interrupt if the IDE bit of RKCS is set. + // + if (_ide) + { + interrupt(); + } +} + +void rk11_c::reset_controller(void) +{ + // This will reset the DATI values to their defaults. + // We then need to reset our copy of the values to correspond. + reset_unibus_registers(); + + // + // Clear all RKER bits. + // + _wce = false; + _cse = false; + _nxs = false; + _nxc = false; + _nxd = false; + _te = false; + _dlt = false; + _nxm = false; + _pge = false; + _ske = false; + _wlo = false; + _ovr = false; + _dre = false; + update_RKER(); + + // + // Clear all RKCS bits except RDY. + // + _go = false; + _function = 0; + _mex = 0; + _ide = false; + _rdy = true; + _sse = false; + _exb = false; + _fmt = false; + _iba = false; + _scp = false; + _he = false; + _err = false; + update_RKCS(); + + // + // Clear all RKDA bits. + // + _rkda_sector = 0; + _rkda_surface = 0; + _rkda_cyl = 0; + _rkda_drive = 0; + update_RKDA(); + + // + // Update RKDS bits to match the newly selected drive (drive 0) + // + _id = 0; + update_RKDS(); + + // + // Clear RKWC + // + set_register_dati_value( + RKWC_reg, + 0, + "reset_controller"); + + // + // Clear RKBA + // + set_register_dati_value( + RKBA_reg, + 0, + "reset_controller"); +} + +void rk11_c::on_power_changed(void) +{ + storagecontroller_c::on_power_changed(); + + if (power_down) + { + // power-on defaults + reset_controller(); + } +} + +// UNIBUS INIT: clear all registers +void rk11_c::on_init_changed(void) +{ + // write all registers to "reset-values" + if (init_asserted) + { + reset_controller(); + } + + storagecontroller_c::on_init_changed(); +} + +rk05_c* rk11_c::selected_drive(void) +{ + return dynamic_cast(storagedrives[_rkda_drive]); +} diff --git a/10.02_devices/2_src/rk11.hpp b/10.02_devices/2_src/rk11.hpp new file mode 100755 index 0000000..580d565 --- /dev/null +++ b/10.02_devices/2_src/rk11.hpp @@ -0,0 +1,167 @@ +/* rk11.hpp: RK11 UNIBUS controller + + */ +#ifndef _RK11_HPP_ +#define _RK11_HPP_ + +#include +#include + +using namespace std; + +#include "utils.hpp" +#include "unibusdevice.hpp" +#include "storagecontroller.hpp" +#include "rk05.hpp" + +class rk11_c: public storagecontroller_c +{ +private: + + // RK11 Registers (see Table 1-1 of manual): + unibusdevice_register_t *RKDS_reg; // Drive Status Register + unibusdevice_register_t *RKER_reg; // Error Register + unibusdevice_register_t *RKCS_reg; // Control Status Register + unibusdevice_register_t *RKWC_reg; // Word Count Register + unibusdevice_register_t *RKBA_reg; // Bus Address Register + unibusdevice_register_t *RKDA_reg; // Disk Address Register + unibusdevice_register_t *RKDB_reg; // Data Buffer Register + + // Drive Status Register (RKDS) bits (those not belonging to the RK05 drive itself) + volatile uint16_t _id; // On interrupt, contains the ID of the interrupting drive (3 bits). + + // Updates the RKDS DATI value based on the above bits + void update_RKDS(void); + + // Error Register (RKER) bits + bool _wce; // Write Check error + bool _cse; // Checksum error + bool _nxs; // Nonexistent Sector error + bool _nxc; // Nonexistent Cylinder error + bool _nxd; // Nonexistent Drive error + bool _te; // Timing error + bool _dlt; // Data late error + bool _nxm; // Nonexistent memory error + bool _pge; // Programming Error + bool _ske; // Seek error + bool _wlo; // Write Lockout Violation + bool _ovr; // Overrun + bool _dre; // Drive Error + + // Updates the RKER DATI value based on the above bits + void update_RKER(void); + + // Control/Status Register (RKCS) bits + bool _go; // Causes controller to execute function in _function when set + uint16_t _function; // The function to perform (3 bits) + uint16_t _mex; // Extended address bits for transfer (2 bits) + bool _ide; // Interrupt on done when set + bool _rdy; // Whether the controller is ready to process a command + bool _sse; // Whether to stop on a soft error + bool _exb; // Extra bit (unused, but diags expect it to be R/W) + bool _fmt; // Modifies read/write operations to allow formatting / header verification + bool _iba; // Inhibits incrementing RKBA + bool _scp; // Indicates that the last interrupt was due to a seek or reset function. + bool _he; // Indicates when a hard error has occurred. + bool _err; // Iindicates when any error has occurred + + // Updates the RKCS DATI value based on above Status bits + void update_RKCS(void); + + // Disk Address Register (RKDA) bits + volatile uint16_t _rkda_sector; + volatile uint16_t _rkda_surface; + volatile uint16_t _rkda_cyl; + volatile uint16_t _rkda_drive; + + // Updates the RKDA DATI value based on the above bits. + void update_RKDA(void); + + struct WorkerCommand + { + volatile rk05_c* drive; + volatile uint32_t address; + volatile uint16_t function; + volatile bool interrupt; + volatile bool stop_on_soft_error; + volatile bool format; + volatile bool iba; + } _new_command; + + struct DMARequest + { + uint32_t address; + uint16_t count; + bool write; + bool iba; + uint16_t *buffer; + bool timeout; + }; + + volatile bool _new_command_ready; // Used in sync. between C/S register updates and worker thread. + // If set, causes worker to abandon any current command and + // pick up a new one. + + + // Validates the given disk address, returns true if valid. + // Sets the appropriate error flags if invalid (and returns false). + bool validate_seek(void); + + // Increments RKDA to point to the next sector + void increment_RKDA(void); + + // Causes an interrupt if IDE is set + void invoke_interrupt(void); + + // Resets all register values on BUS INIT or Control Reset functions + // and any other relevant local state. + void reset_controller(void); + + rk05_c* selected_drive(void); + + bool check_drive_ready(void); + + bool check_drive_present(void); + + void dma_transfer(DMARequest &request); + + // Drive functions: + enum Function + { + Control_Reset = 0, + Write = 1, + Read = 2, + Write_Check = 3, + Seek = 4, + Read_Check = 5, + Drive_Reset = 6, + Write_Lock = 7, + }; + + enum WorkerState + { + Worker_Idle = 0, + Worker_Execute = 1, + Worker_Finish = 2, + } _worker_state; + +public: + + rk11_c(); + virtual ~rk11_c(); + + // background worker function + void worker(void) override; + + // called by unibusadapter on emulated register access + void on_after_register_access( + unibusdevice_register_t *device_reg, + uint8_t unibus_control) override; + + void on_power_changed(void) override; + void on_init_changed(void) override; + + void on_drive_status_changed(storagedrive_c* drive); +}; + +#endif From f0c33c6549dcc74341bbdbc1a230a7225a232467 Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Fri, 5 Apr 2019 21:09:26 +0200 Subject: [PATCH 2/2] Adding makefile and menu changes for RK11/RK05 additions. --- 10.03_app_demo/2_src/makefile | 22 +++++++++++++++------- 10.03_app_demo/2_src/menu_devices.cpp | 12 +++++++++++- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/10.03_app_demo/2_src/makefile b/10.03_app_demo/2_src/makefile index 8012444..f7f6704 100644 --- a/10.03_app_demo/2_src/makefile +++ b/10.03_app_demo/2_src/makefile @@ -88,14 +88,16 @@ OBJECTS = $(OBJDIR)/application.o \ $(OBJDIR)/cpu.o \ $(OBJDIR)/ka11.o \ $(OBJDIR)/rl0102.o \ - $(OBJDIR)/rl11.o \ + $(OBJDIR)/rl11.o \ + $(OBJDIR)/rk11.o \ + $(OBJDIR)/rk05.o \ $(OBJDIR)/storagedrive.o \ - $(OBJDIR)/storagecontroller.o \ - $(OBJDIR)/demo_io.o \ - $(OBJDIR)/demo_regs.o \ - $(OBJDIR)/unibusdevice.o \ - $(OBJDIR)/device.o \ - $(OBJDIR)/parameter.o \ + $(OBJDIR)/storagecontroller.o \ + $(OBJDIR)/demo_io.o \ + $(OBJDIR)/demo_regs.o \ + $(OBJDIR)/unibusdevice.o \ + $(OBJDIR)/device.o \ + $(OBJDIR)/parameter.o \ $(OBJDIR)/panel.o \ $(OBJDIR)/unibusadapter.o \ $(OBJDIR)/unibus.o \ @@ -187,6 +189,12 @@ $(OBJDIR)/rl0102.o : $(DEVICE_SRC_DIR)/rl0102.cpp $(DEVICE_SRC_DIR)/rl0102.hpp $(OBJDIR)/rl11.o : $(DEVICE_SRC_DIR)/rl11.cpp $(DEVICE_SRC_DIR)/rl11.hpp $(CC) $(CCFLAGS) $< -o $@ +$(OBJDIR)/rk05.o : $(DEVICE_SRC_DIR)/rk05.cpp $(DEVICE_SRC_DIR)/rk05.hpp + $(CC) $(CCFLAGS) $< -o $@ + +$(OBJDIR)/rk11.o : $(DEVICE_SRC_DIR)/rk11.cpp $(DEVICE_SRC_DIR)/rk11.hpp + $(CC) $(CCFLAGS) $< -o $@ + $(OBJDIR)/storagedrive.o : $(BASE_SRC_DIR)/storagedrive.cpp $(BASE_SRC_DIR)/storagedrive.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 86468fc..fb7dcd0 100644 --- a/10.03_app_demo/2_src/menu_devices.cpp +++ b/10.03_app_demo/2_src/menu_devices.cpp @@ -48,6 +48,7 @@ #include "demo_io.hpp" #include "demo_regs.hpp" #include "rl11.hpp" +#include "rk11.hpp" #include "cpu.hpp" @@ -80,7 +81,10 @@ void menus_c::menu_devices(void) { cur_device = NULL; paneldriver->reset(); // reset I2C, restart worker() - + + // create RK11 + drives + rk11_c RK05; + demo_io.install(); demo_io.worker_start(); @@ -91,6 +95,9 @@ void menus_c::menu_devices(void) { RL11.connect_to_panel(); RL11.worker_start(); + RK05.install(); + RK05.worker_start(); + cpu.install(); cpu.worker_start(); @@ -330,6 +337,9 @@ void menus_c::menu_devices(void) { RL11.disconnect_from_panel(); RL11.uninstall(); + RK05.worker_stop(); + RK05.uninstall(); + //demo_regs.worker_stop(); //demo_regs.uninstall();