diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 45545f7ef..43f59df80 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -25,7 +25,7 @@ WD1770::Status::Status() : busy(false) {} WD1770::WD1770(Personality p) : - Storage::Disk::MFMController(8000000, 16, 300), + Storage::Disk::MFMController(8000000), interesting_event_mask_((int)Event1770::Command), resume_point_(0), delay_time_(0), @@ -75,7 +75,7 @@ uint8_t WD1770::get_register(int address) { switch(status_.type) { case Status::One: status |= - (get_is_track_zero() ? Flag::TrackZero : 0) | + (get_drive().get_is_track_zero() ? Flag::TrackZero : 0) | (status_.seek_error ? Flag::SeekError : 0); // TODO: index hole break; @@ -91,11 +91,11 @@ uint8_t WD1770::get_register(int address) { } if(!has_motor_on_line()) { - status |= get_drive_is_ready() ? 0 : Flag::NotReady; + status |= get_drive().get_is_ready() ? 0 : Flag::NotReady; if(status_.type == Status::One) status |= (head_is_loaded_ ? Flag::HeadLoaded : 0); } else { - status |= (get_motor_on() ? Flag::MotorOn : 0); + status |= (get_drive().get_motor_on() ? Flag::MotorOn : 0); if(status_.type == Status::One) status |= (status_.spin_up ? Flag::SpinUp : 0); } @@ -257,7 +257,7 @@ void WD1770::posit_event(int new_event_type) { goto test_type1_type; begin_type1_spin_up: - if((command_&0x08) || get_motor_on()) goto test_type1_type; + if((command_&0x08) || get_drive().get_motor_on()) goto test_type1_type; SPIN_UP(); test_type1_type: @@ -280,11 +280,11 @@ void WD1770::posit_event(int new_event_type) { if(step_direction_) track_++; else track_--; perform_step: - if(!step_direction_ && get_is_track_zero()) { + if(!step_direction_ && get_drive().get_is_track_zero()) { track_ = 0; goto verify; } - step(step_direction_ ? 1 : -1); + get_drive().step(step_direction_ ? 1 : -1); unsigned int time_to_wait; switch(command_ & 3) { default: @@ -376,7 +376,7 @@ void WD1770::posit_event(int new_event_type) { goto test_type2_delay; begin_type2_spin_up: - if(get_motor_on()) goto test_type2_delay; + if(get_drive().get_motor_on()) goto test_type2_delay; // Perform spin up. SPIN_UP(); @@ -386,7 +386,7 @@ void WD1770::posit_event(int new_event_type) { WAIT_FOR_TIME(30); test_type2_write_protection: - if(command_&0x20 && get_drive_is_read_only()) { + if(command_&0x20 && get_drive().get_is_read_only()) { update_status([] (Status &status) { status.write_protect = true; }); @@ -594,7 +594,7 @@ void WD1770::posit_event(int new_event_type) { goto type3_test_delay; begin_type3_spin_up: - if((command_&0x08) || get_motor_on()) goto type3_test_delay; + if((command_&0x08) || get_drive().get_motor_on()) goto type3_test_delay; SPIN_UP(); type3_test_delay: @@ -675,7 +675,7 @@ void WD1770::posit_event(int new_event_type) { }); write_track_test_write_protect: - if(get_drive_is_read_only()) { + if(get_drive().get_is_read_only()) { update_status([] (Status &status) { status.write_protect = true; }); @@ -781,6 +781,7 @@ void WD1770::update_status(std::function updater) { } void WD1770::set_head_load_request(bool head_load) {} +void WD1770::set_motor_on(bool motor_on) {} void WD1770::set_head_loaded(bool head_loaded) { head_is_loaded_ = head_loaded; diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index 9d3f46617..f68054f02 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -76,6 +76,7 @@ class WD1770: public Storage::Disk::MFMController { protected: virtual void set_head_load_request(bool head_load); + virtual void set_motor_on(bool motor_on); void set_head_loaded(bool head_loaded); private: diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 664bf54b8..30cbb0cb1 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -75,8 +75,8 @@ namespace { const uint8_t CommandSenseDriveStatus = 0x04; } -i8272::i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) : - Storage::Disk::MFMController(clock_rate, clock_rate_multiplier, revolutions_per_minute), +i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) : + Storage::Disk::MFMController(clock_rate), bus_handler_(bus_handler), main_status_(0), interesting_event_mask_((int)Event8272::CommandByte), @@ -119,11 +119,12 @@ void i8272::run_for(Cycles cycles) { // Perform a step. int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1; printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position); - drives_[c].drive->step(direction); + select_drive(c); + get_drive().step(direction); if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction; // Check for completion. - if(drives_[c].seek_is_satisfied()) { + if(seek_is_satisfied(c)) { drives_[c].phase = Drive::CompletedSeeking; drives_seeking_--; break; @@ -192,12 +193,6 @@ uint8_t i8272::get_register(int address) { } } -void i8272::set_disk(std::shared_ptr disk, int drive) { - if(drive < 4 && drive >= 0) { - drives_[drive].drive->set_disk(disk); - } -} - #define BEGIN_SECTION() switch(resume_point_) { default: #define END_SECTION() } @@ -235,10 +230,9 @@ void i8272::set_disk(std::shared_ptr disk, int drive) { #define SET_DRIVE_HEAD_MFM() \ active_drive_ = command_[1]&3; \ active_head_ = (command_[1] >> 2)&1; \ - set_drive(drives_[active_drive_].drive); \ - drives_[active_drive_].drive->set_head((unsigned int)active_head_); \ - set_is_double_density(command_[0] & 0x40); \ - invalidate_track(); + select_drive(active_drive_); \ + get_drive().set_head((unsigned int)active_head_); \ + set_is_double_density(command_[0] & 0x40); #define WAIT_FOR_BYTES(n) \ distance_into_section_ = 0; \ @@ -341,6 +335,10 @@ void i8272::posit_event(int event_type) { if(!dma_mode_) SetNonDMAExecution(); SET_DRIVE_HEAD_MFM(); LOAD_HEAD(); + if(!get_drive().get_is_ready()) { + SetNotReady(); + goto abort; + } } // Jump to the proper place. @@ -504,7 +502,7 @@ void i8272::posit_event(int event_type) { write_data: printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]); - if(drives_[active_drive_].drive->get_is_read_only()) { + if(get_drive().get_is_read_only()) { SetNotWriteable(); goto abort; } @@ -619,7 +617,7 @@ void i8272::posit_event(int event_type) { // Performs format [/write] track. format_track: printf("Format track\n"); - if(drives_[active_drive_].drive->get_is_read_only()) { + if(get_drive().get_is_read_only()) { SetNotWriteable(); goto abort; } @@ -712,6 +710,7 @@ void i8272::posit_event(int event_type) { seek: { int drive = command_[1]&3; + select_drive(drive); // Increment the seeking count if this drive wasn't already seeking. if(drives_[drive].phase != Drive::Seeking) { @@ -741,7 +740,7 @@ void i8272::posit_event(int event_type) { } // Check whether any steps are even needed; if not then mark as completed already. - if(drives_[drive].seek_is_satisfied()) { + if(seek_is_satisfied(drive)) { drives_[drive].phase = Drive::CompletedSeeking; drives_seeking_--; } @@ -793,12 +792,13 @@ void i8272::posit_event(int event_type) { printf("Sense drive status\n"); { int drive = command_[1] & 3; + select_drive(drive); result_stack_.push_back( (command_[1] & 7) | // drive and head number 0x08 | // single sided - (drives_[drive].drive->get_is_track_zero() ? 0x10 : 0x00) | - (drives_[drive].drive->get_is_ready() ? 0x20 : 0x00) | - (drives_[drive].drive->get_is_read_only() ? 0x40 : 0x00) + (get_drive().get_is_track_zero() ? 0x10 : 0x00) | + (get_drive().get_is_ready() ? 0x20 : 0x00) | + (get_drive().get_is_read_only() ? 0x40 : 0x00) ); } goto post_result; @@ -853,9 +853,9 @@ void i8272::posit_event(int event_type) { END_SECTION() } -bool i8272::Drive::seek_is_satisfied() { - return (target_head_position == head_position) || - (target_head_position == -1 && drive->get_is_track_zero()); +bool i8272::seek_is_satisfied(int drive) { + return (drives_[drive].target_head_position == drives_[drive].head_position) || + (drives_[drive].target_head_position == -1 && get_drive().get_is_track_zero()); } void i8272::set_dma_acknowledge(bool dack) { diff --git a/Components/8272/i8272.hpp b/Components/8272/i8272.hpp index 6c31a9517..2edab4e24 100644 --- a/Components/8272/i8272.hpp +++ b/Components/8272/i8272.hpp @@ -26,7 +26,7 @@ class BusHandler { class i8272: public Storage::Disk::MFMController { public: - i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute); + i8272(BusHandler &bus_handler, Cycles clock_rate); void run_for(Cycles); @@ -39,10 +39,11 @@ class i8272: public Storage::Disk::MFMController { void set_dma_acknowledge(bool dack); void set_terminal_count(bool tc); - void set_disk(std::shared_ptr disk, int drive); - bool is_sleeping(); + protected: + virtual void select_drive(int number) = 0; + private: // The bus handler, for interrupt and DMA-driven usage. BusHandler &bus_handler_; @@ -91,24 +92,20 @@ class i8272: public Storage::Disk::MFMController { int steps_taken; int target_head_position; // either an actual number, or -1 to indicate to step until track zero - /// @returns @c true if the currently queued-up seek or recalibrate has reached where it should be. - bool seek_is_satisfied(); - // Head state. int head_unload_delay[2]; bool head_is_loaded[2]; - // The connected drive. - std::shared_ptr drive; - Drive() : head_position(0), phase(NotSeeking), - drive(new Storage::Disk::Drive), head_is_loaded{false, false}, head_unload_delay{0, 0} {}; } drives_[4]; int drives_seeking_; + /// @returns @c true if the selected drive, which is number @c drive, can stop seeking. + bool seek_is_satisfied(int drive); + // User-supplied parameters; as per the specify command. int step_rate_time_; int head_unload_time_; diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 0244ddb41..fe46a63c7 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -582,12 +582,25 @@ class KeyboardState: public GI::AY38910::PortHandler { class FDC: public Intel::i8272::i8272 { private: Intel::i8272::BusHandler bus_handler_; + std::shared_ptr drive_; public: - FDC() : i8272(bus_handler_, Cycles(8000000), 16, 300) {} + FDC() : + i8272(bus_handler_, Cycles(8000000)), + drive_(new Storage::Disk::Drive(8000000, 300)) { + set_drive(drive_); + } void set_motor_on(bool on) { - Intel::i8272::i8272::set_motor_on(on); + drive_->set_motor_on(on); + } + + void select_drive(int c) { + // TODO: support more than one drive. + } + + void set_disk(std::shared_ptr disk, int drive) { + drive_->set_disk(disk); } }; diff --git a/Machines/Commodore/1540/Implementation/C1540.cpp b/Machines/Commodore/1540/Implementation/C1540.cpp index a6008c51f..1944e8f7c 100644 --- a/Machines/Commodore/1540/Implementation/C1540.cpp +++ b/Machines/Commodore/1540/Implementation/C1540.cpp @@ -18,11 +18,12 @@ using namespace Commodore::C1540; MachineBase::MachineBase() : m6502_(*this), shift_register_(0), - Storage::Disk::Controller(1000000, 4, 300), + Storage::Disk::Controller(1000000), serial_port_(new SerialPort), serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)), drive_VIA_(drive_VIA_port_handler_), - serial_port_VIA_(*serial_port_VIA_port_handler_) { + serial_port_VIA_(*serial_port_VIA_port_handler_), + drive_(new Storage::Disk::Drive(1000000, 300)) { // attach the serial port to its VIA and vice versa serial_port_->set_serial_port_via(serial_port_VIA_port_handler_); serial_port_VIA_port_handler_->set_serial_port(serial_port_); @@ -34,6 +35,9 @@ MachineBase::MachineBase() : // set a bit rate set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3)); + + // attach the only drive there is + set_drive(drive_); } void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) { @@ -82,16 +86,14 @@ void Machine::set_rom(const std::vector &rom) { } void Machine::set_disk(std::shared_ptr disk) { - std::shared_ptr drive(new Storage::Disk::Drive); - drive->set_disk(disk); - set_drive(drive); + drive_->set_disk(disk); } void Machine::run_for(const Cycles cycles) { m6502_.run_for(cycles); bool drive_motor = drive_VIA_port_handler_.get_motor_enabled(); - set_motor_on(drive_motor); + drive_->set_motor_on(drive_motor); if(drive_motor) Storage::Disk::Controller::run_for(cycles); } @@ -105,7 +107,7 @@ void MachineBase::mos6522_did_change_interrupt_status(void *mos6522) { #pragma mark - Disk drive -void MachineBase::process_input_bit(int value, unsigned int cycles_since_index_hole) { +void MachineBase::process_input_bit(int value) { shift_register_ = (shift_register_ << 1) | value; if((shift_register_ & 0x3ff) == 0x3ff) { drive_VIA_port_handler_.set_sync_detected(true); @@ -130,7 +132,7 @@ void MachineBase::process_index_hole() {} #pragma mak - Drive VIA delegate void MachineBase::drive_via_did_step_head(void *driveVIA, int direction) { - step(direction); + drive_->step(direction); } void MachineBase::drive_via_did_set_data_density(void *driveVIA, int density) { diff --git a/Machines/Commodore/1540/Implementation/C1540Base.hpp b/Machines/Commodore/1540/Implementation/C1540Base.hpp index d08165ea7..a179a3f58 100644 --- a/Machines/Commodore/1540/Implementation/C1540Base.hpp +++ b/Machines/Commodore/1540/Implementation/C1540Base.hpp @@ -135,6 +135,7 @@ class MachineBase: protected: CPU::MOS6502::Processor m6502_; + std::shared_ptr drive_; uint8_t ram_[0x800]; uint8_t rom_[0x4000]; @@ -147,7 +148,7 @@ class MachineBase: MOS::MOS6522::MOS6522 serial_port_VIA_; int shift_register_, bit_window_offset_; - virtual void process_input_bit(int value, unsigned int cycles_since_index_hole); + virtual void process_input_bit(int value); virtual void process_index_hole(); }; diff --git a/Machines/Electron/Plus3.cpp b/Machines/Electron/Plus3.cpp index 99a580e2b..c6b4fa9a6 100644 --- a/Machines/Electron/Plus3.cpp +++ b/Machines/Electron/Plus3.cpp @@ -16,7 +16,7 @@ Plus3::Plus3() : WD1770(P1770) { void Plus3::set_disk(std::shared_ptr disk, int drive) { if(!drives_[drive]) { - drives_[drive].reset(new Storage::Disk::Drive); + drives_[drive].reset(new Storage::Disk::Drive(8000000, 300)); if(drive == selected_drive_) set_drive(drives_[drive]); } drives_[drive]->set_disk(disk); @@ -42,9 +42,14 @@ void Plus3::set_control_register(uint8_t control, uint8_t changes) { } } if(changes & 0x04) { - invalidate_track(); if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0); if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0); } if(changes & 0x08) set_is_double_density(!(control & 0x08)); } + +void Plus3::set_motor_on(bool on) { + // TODO: this status should transfer if the selected drive changes. But the same goes for + // writing state, so plenty of work to do in general here. + get_drive().set_motor_on(on); +} diff --git a/Machines/Electron/Plus3.hpp b/Machines/Electron/Plus3.hpp index 054056211..71b8b1c4b 100644 --- a/Machines/Electron/Plus3.hpp +++ b/Machines/Electron/Plus3.hpp @@ -25,6 +25,8 @@ class Plus3 : public WD::WD1770 { std::shared_ptr drives_[2]; int selected_drive_ = 0; uint8_t last_control_ = 0; + + void set_motor_on(bool on); }; } diff --git a/Machines/Oric/Microdisc.cpp b/Machines/Oric/Microdisc.cpp index dd7cb8610..fb8265c87 100644 --- a/Machines/Oric/Microdisc.cpp +++ b/Machines/Oric/Microdisc.cpp @@ -30,7 +30,7 @@ Microdisc::Microdisc() : void Microdisc::set_disk(std::shared_ptr disk, int drive) { if(!drives_[drive]) { - drives_[drive].reset(new Storage::Disk::Drive); + drives_[drive].reset(new Storage::Disk::Drive(8000000, 300)); if(drive == selected_drive_) set_drive(drives_[drive]); } drives_[drive]->set_disk(disk); @@ -95,7 +95,14 @@ uint8_t Microdisc::get_data_request_register() { } void Microdisc::set_head_load_request(bool head_load) { - set_motor_on(head_load); + // The drive motors (at present: I believe **all drive motors** regardless of the selected drive) receive + // the current head load request state. + for(int c = 0; c < 4; c++) { + if(drives_[c]) drives_[c]->set_motor_on(head_load); + } + + // A request to load the head results in a delay until the head is confirmed loaded. This delay is handled + // in ::run_for. A request to unload the head results in an instant answer that the head is unloaded. if(head_load) { head_load_request_counter_ = 0; } else { diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index f34b69870..19fd4ff96 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -434,6 +434,7 @@ 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; }; 4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD69F921D98760000243FE1 /* AcornADF.cpp */; }; 4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */; }; + 4BE4144C1F65E439006A8D7C /* SingleTrackDisk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE4144A1F65E439006A8D7C /* SingleTrackDisk.cpp */; }; 4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; }; 4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE7C9161E3D397100A5496D /* TIA.cpp */; }; 4BE9A6B11EDE293000CBCB47 /* zexdoc.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BE9A6B01EDE293000CBCB47 /* zexdoc.com */; }; @@ -1042,6 +1043,8 @@ 4BD69F931D98760000243FE1 /* AcornADF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AcornADF.hpp; sourceTree = ""; }; 4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8255.hpp; path = 8255/i8255.hpp; sourceTree = ""; }; 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = ""; }; + 4BE4144A1F65E439006A8D7C /* SingleTrackDisk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SingleTrackDisk.cpp; sourceTree = ""; }; + 4BE4144B1F65E439006A8D7C /* SingleTrackDisk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SingleTrackDisk.hpp; sourceTree = ""; }; 4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = ""; }; 4BE77A2D1D84ADFB00BC3827 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Commodore/File.hpp; sourceTree = ""; }; 4BE7C9161E3D397100A5496D /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = ""; }; @@ -1641,6 +1644,7 @@ 4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */, 4B121F961E060CF000BFDA12 /* PCMSegment.cpp */, 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */, + 4BE4144A1F65E439006A8D7C /* SingleTrackDisk.cpp */, 4BAD9B941F43D7E900724854 /* UnformattedTrack.cpp */, 4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */, 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */, @@ -1650,6 +1654,7 @@ 4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */, 4B121F971E060CF000BFDA12 /* PCMSegment.hpp */, 4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */, + 4BE4144B1F65E439006A8D7C /* SingleTrackDisk.hpp */, 4BAD9B951F43D7E900724854 /* UnformattedTrack.hpp */, 4BB697CF1D4BA44900248BDF /* Encodings */, 4BAB62B21D327F7E00DF5BA0 /* Formats */, @@ -2850,6 +2855,7 @@ 4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */, 4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */, 4B5FADBD1DE31D1500AEC565 /* OricMFMDSK.cpp in Sources */, + 4BE4144C1F65E439006A8D7C /* SingleTrackDisk.cpp in Sources */, 4B14978B1EE4AC5E00CE2596 /* StaticAnalyser.cpp in Sources */, 4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */, 4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */, diff --git a/StaticAnalyser/Commodore/Disk.cpp b/StaticAnalyser/Commodore/Disk.cpp index 37c89c015..b576afe5b 100644 --- a/StaticAnalyser/Commodore/Disk.cpp +++ b/StaticAnalyser/Commodore/Disk.cpp @@ -21,10 +21,10 @@ class CommodoreGCRParser: public Storage::Disk::Controller { public: std::shared_ptr drive; - CommodoreGCRParser() : Storage::Disk::Controller(4000000, 1, 300), shift_register_(0), track_(1) { - drive.reset(new Storage::Disk::Drive); + CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) { + drive.reset(new Storage::Disk::Drive(4000000, 300)); set_drive(drive); - set_motor_on(true); + drive->set_motor_on(true); } struct Sector { @@ -47,7 +47,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller { int direction = difference < 0 ? -1 : 1; difference *= 2 * direction; - for(int c = 0; c < difference; c++) step(direction); + for(int c = 0; c < difference; c++) get_drive().step(direction); unsigned int zone = 3; if(track >= 18) zone = 2; @@ -66,7 +66,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller { uint8_t track_; std::shared_ptr sector_cache_[65536]; - void process_input_bit(int value, unsigned int cycles_since_index_hole) { + void process_input_bit(int value) { shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0x3ff; bit_count_++; } diff --git a/Storage/Disk/DiskController.cpp b/Storage/Disk/DiskController.cpp index 772750e88..429f94695 100644 --- a/Storage/Disk/DiskController.cpp +++ b/Storage/Disk/DiskController.cpp @@ -7,43 +7,19 @@ // #include "DiskController.hpp" -#include "UnformattedTrack.hpp" + #include "../../NumberTheory/Factors.hpp" -#include using namespace Storage::Disk; -Controller::Controller(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) : - clock_rate_(clock_rate.as_int() * clock_rate_multiplier), - clock_rate_multiplier_(clock_rate_multiplier), - rotational_multiplier_(60, revolutions_per_minute), - - cycles_since_index_hole_(0), - motor_is_on_(false), - - is_reading_(true), - - TimedEventLoop((unsigned int)(clock_rate.as_int() * clock_rate_multiplier)) { +Controller::Controller(Cycles clock_rate) : + clock_rate_multiplier_(128000000 / clock_rate.as_int()), + clock_rate_(clock_rate.as_int() * clock_rate_multiplier_), + empty_drive_(new Drive((unsigned int)clock_rate.as_int(), 1)) { // seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later Time one(1); set_expected_bit_length(one); -} - -void Controller::setup_track() { - track_ = drive_->get_track(); - if(!track_) { - track_.reset(new UnformattedTrack); - } - - Time offset; - Time track_time_now = get_time_into_track(); - assert(track_time_now >= Time(0) && current_event_.length <= Time(1)); - - Time time_found = track_->seek_to(track_time_now); - assert(time_found >= Time(0) && time_found <= track_time_now); - offset = track_time_now - time_found; - - get_next_event(offset); + set_drive(empty_drive_); } void Controller::set_component_is_sleeping(void *component, bool is_sleeping) { @@ -51,125 +27,32 @@ void Controller::set_component_is_sleeping(void *component, bool is_sleeping) { } bool Controller::is_sleeping() { - return !(drive_ && drive_->has_disk() && motor_is_on_); + return !drive_ || drive_->is_sleeping(); } void Controller::run_for(const Cycles cycles) { - Time zero(0); + if(drive_) drive_->run_for(cycles); +} - if(drive_ && drive_->has_disk() && motor_is_on_) { - if(!track_) setup_track(); +Drive &Controller::get_drive() { + return *drive_.get(); +} - int number_of_cycles = clock_rate_multiplier_ * cycles.as_int(); - while(number_of_cycles) { - int cycles_until_next_event = (int)get_cycles_until_next_event(); - int cycles_to_run_for = std::min(cycles_until_next_event, number_of_cycles); - if(!is_reading_ && cycles_until_bits_written_ > zero) { - int write_cycles_target = (int)cycles_until_bits_written_.get_unsigned_int(); - if(cycles_until_bits_written_.length % cycles_until_bits_written_.clock_rate) write_cycles_target++; - cycles_to_run_for = std::min(cycles_to_run_for, write_cycles_target); - } +#pragma mark - Drive::EventDelegate - cycles_since_index_hole_ += (unsigned int)cycles_to_run_for; - - number_of_cycles -= cycles_to_run_for; - if(is_reading_) { - pll_->run_for(Cycles(cycles_to_run_for)); - } else { - if(cycles_until_bits_written_ > zero) { - Storage::Time cycles_to_run_for_time(cycles_to_run_for); - if(cycles_until_bits_written_ <= cycles_to_run_for_time) { - process_write_completed(); - if(cycles_until_bits_written_ <= cycles_to_run_for_time) - cycles_until_bits_written_.set_zero(); - else - cycles_until_bits_written_ -= cycles_to_run_for_time; - } else { - cycles_until_bits_written_ -= cycles_to_run_for_time; - } - } - } - TimedEventLoop::run_for(Cycles(cycles_to_run_for)); - } +void Controller::process_event(const Track::Event &event) { + switch(event.type) { + case Track::Event::FluxTransition: pll_->add_pulse(); break; + case Track::Event::IndexHole: process_index_hole(); break; } } -#pragma mark - Track timed event loop - -void Controller::get_next_event(const Time &duration_already_passed) { - if(track_) { - current_event_ = track_->get_next_event(); - } else { - current_event_.length.length = 1; - current_event_.length.clock_rate = 1; - current_event_.type = Track::Event::IndexHole; - } - - // divide interval, which is in terms of a single rotation of the disk, by rotation speed to - // convert it into revolutions per second; this is achieved by multiplying by rotational_multiplier_ - assert(current_event_.length <= Time(1) && current_event_.length >= Time(0)); - Time interval = (current_event_.length - duration_already_passed) * rotational_multiplier_; - set_next_event_time_interval(interval); +void Controller::advance(const Cycles cycles) { + if(is_reading_) pll_->run_for(Cycles(cycles.as_int() * clock_rate_multiplier_)); } -void Controller::process_next_event() -{ - switch(current_event_.type) { - case Track::Event::FluxTransition: - if(is_reading_) pll_->add_pulse(); - break; - case Track::Event::IndexHole: -// printf("%p %d [/%d = %d]\n", this, cycles_since_index_hole_, clock_rate_multiplier_, cycles_since_index_hole_ / clock_rate_multiplier_); - cycles_since_index_hole_ = 0; - process_index_hole(); - break; - } - get_next_event(Time(0)); -} - -Storage::Time Controller::get_time_into_track() { - // this is proportion of a second - Time result(cycles_since_index_hole_, 8000000 * clock_rate_multiplier_); - result /= rotational_multiplier_; - result.simplify(); - return result; -} - -#pragma mark - Writing - -void Controller::begin_writing(bool clamp_to_index_hole) { - is_reading_ = false; - clamp_writing_to_index_hole_ = clamp_to_index_hole; - - write_segment_.length_of_a_bit = bit_length_ / rotational_multiplier_; - write_segment_.data.clear(); - write_segment_.number_of_bits = 0; - - write_start_time_ = get_time_into_track(); -} - -void Controller::write_bit(bool value) { - bool needs_new_byte = !(write_segment_.number_of_bits&7); - if(needs_new_byte) write_segment_.data.push_back(0); - if(value) write_segment_.data[write_segment_.number_of_bits >> 3] |= 0x80 >> (write_segment_.number_of_bits & 7); - write_segment_.number_of_bits++; - - cycles_until_bits_written_ += cycles_per_bit_; -} - -void Controller::end_writing() { - is_reading_ = true; - - if(!patched_track_) { - // Avoid creating a new patched track if this one is already patched - patched_track_ = std::dynamic_pointer_cast(track_); - if(!patched_track_) { - patched_track_.reset(new PCMPatchedTrack(track_)); - } - } - patched_track_->add_segment(write_start_time_, write_segment_, clamp_writing_to_index_hole_); - cycles_since_index_hole_ %= 8000000 * clock_rate_multiplier_; - invalidate_track(); // TEMPORARY: to force a seek +void Controller::process_write_completed() { + // Provided for subclasses to override. } #pragma mark - PLL control and delegate @@ -178,66 +61,53 @@ void Controller::set_expected_bit_length(Time bit_length) { bit_length_ = bit_length; bit_length_.simplify(); - cycles_per_bit_ = Storage::Time(clock_rate_) * bit_length; - cycles_per_bit_.simplify(); + Time cycles_per_bit = Storage::Time(clock_rate_) * bit_length; + cycles_per_bit.simplify(); // this conversion doesn't need to be exact because there's a lot of variation to be taken // account of in rotation speed, air turbulence, etc, so a direct conversion will do - int clocks_per_bit = (int)cycles_per_bit_.get_unsigned_int(); + int clocks_per_bit = (int)cycles_per_bit.get_unsigned_int(); pll_.reset(new DigitalPhaseLockedLoop(clocks_per_bit, 3)); pll_->set_delegate(this); } void Controller::digital_phase_locked_loop_output_bit(int value) { - process_input_bit(value, (unsigned int)cycles_since_index_hole_); -} - -#pragma mark - Drive actions - -bool Controller::get_is_track_zero() { - if(!drive_) return false; - return drive_->get_is_track_zero(); -} - -bool Controller::get_drive_is_ready() { - if(!drive_) return false; - return drive_->has_disk(); -} - -bool Controller::get_drive_is_read_only() { - if(!drive_) return false; - return drive_->get_is_read_only(); -} - -void Controller::step(int direction) { - invalidate_track(); - if(drive_) drive_->step(direction); -} - -void Controller::set_motor_on(bool motor_on) { - motor_is_on_ = motor_on; - update_sleep_observer(); -} - -bool Controller::get_motor_on() { - return motor_is_on_; + if(is_reading_) process_input_bit(value); } void Controller::set_drive(std::shared_ptr drive) { if(drive_ != drive) { - invalidate_track(); + bool was_sleeping = is_sleeping(); +// invalidate_track(); + + if(drive_) { + drive_->set_event_delegate(nullptr); + drive_->set_sleep_observer(nullptr); + } drive_ = drive; - drive->set_sleep_observer(this); - update_sleep_observer(); + if(drive_) { + drive_->set_event_delegate(this); + drive_->set_sleep_observer(this); + } else { + drive_ = empty_drive_; + } + + if(is_sleeping() != was_sleeping) { + update_sleep_observer(); + } } } -void Controller::invalidate_track() { - track_ = nullptr; - if(patched_track_) { - drive_->set_track(patched_track_); - patched_track_ = nullptr; - } +void Controller::begin_writing(bool clamp_to_index_hole) { + is_reading_ = false; + get_drive().begin_writing(bit_length_, clamp_to_index_hole); } -void Controller::process_write_completed() {} +void Controller::end_writing() { + is_reading_ = true; + get_drive().end_writing(); +} + +bool Controller::is_reading() { + return is_reading_; +} diff --git a/Storage/Disk/DiskController.hpp b/Storage/Disk/DiskController.hpp index 4ab4e009c..47a3b248f 100644 --- a/Storage/Disk/DiskController.hpp +++ b/Storage/Disk/DiskController.hpp @@ -13,7 +13,6 @@ #include "DigitalPhaseLockedLoop.hpp" #include "PCMSegment.hpp" #include "PCMPatchedTrack.hpp" -#include "../TimedEventLoop.hpp" #include "../../ClockReceiver/ClockReceiver.hpp" #include "../../ClockReceiver/Sleeper.hpp" @@ -30,13 +29,12 @@ namespace Disk { TODO: communication of head size and permissible stepping extents, appropriate simulation of gain. */ -class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop, public Sleeper, public Sleeper::SleepObserver { +class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDelegate, public Sleeper, public Sleeper::SleepObserver { protected: /*! - Constructs a @c DiskDrive that will be run at @c clock_rate and runs its PLL at @c clock_rate*clock_rate_multiplier, - spinning inserted disks at @c revolutions_per_minute. + Constructs a @c Controller that will be run at @c clock_rate. */ - Controller(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute); + Controller(Cycles clock_rate); /*! Communicates to the PLL the expected length of a bit as a fraction of a second. @@ -47,54 +45,16 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop Advances the drive by @c number_of_cycles cycles. */ void run_for(const Cycles cycles); - using TimedEventLoop::run_for; /*! - Sets the current drive. + Sets the current drive. This drive is the one the PLL listens to. */ void set_drive(std::shared_ptr drive); /*! - Announces that the track the drive sees is about to change for a reason unknownt to the controller. + Should be implemented by subclasses; communicates each bit that the PLL recognises. */ - void invalidate_track(); - - /*! - Enables or disables the disk motor. - */ - void set_motor_on(bool motor_on); - - /*! - @returns @c true if the motor is on; @c false otherwise. - */ - bool get_motor_on(); - - /*! - Begins write mode, initiating a PCM sampled region of data. Bits should be written via - @c write_bit. They will be written with the length set via @c set_expected_bit_length. - It is acceptable to supply a backlog of bits. Flux transition events will not be reported - while writing. - - @param clamp_to_index_hole If @c true then writing will automatically be truncated by - the index hole. Writing will continue over the index hole otherwise. - */ - void begin_writing(bool clamp_to_index_hole); - - /*! - Writes the bit @c value as the next in the PCM stream initiated by @c begin_writing. - */ - void write_bit(bool value); - - /*! - Ends write mode, switching back to read mode. The drive will stop overwriting events. - */ - void end_writing(); - - /*! - Should be implemented by subclasses; communicates each bit that the PLL recognises, also specifying - the amount of time since the index hole was last seen. - */ - virtual void process_input_bit(int value, unsigned int cycles_since_index_hole) = 0; + virtual void process_input_bit(int value) = 0; /*! Should be implemented by subclasses; communicates that the index hole has been reached. @@ -107,47 +67,60 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop */ virtual void process_write_completed(); - // for TimedEventLoop - virtual void process_next_event(); + /*! + Puts the controller and the drive returned by get_drive() into write mode, supplying to + the drive the current bit length. - // to satisfy DigitalPhaseLockedLoop::Delegate - void digital_phase_locked_loop_output_bit(int value); + While the controller is in write mode it disconnects the PLL. So subclasses will not + receive any calls to @c process_input_bit. - bool get_is_track_zero(); - void step(int direction); - virtual bool get_drive_is_ready(); - bool get_drive_is_read_only(); + @param clamp_to_index_hole If @c true then writing will automatically be truncated by + the index hole. Writing will continue over the index hole otherwise. + */ + void begin_writing(bool clamp_to_index_hole); + /*! + Puts the drive returned by get_drive() out of write mode, and marks the controller + as no longer being in write mode. + */ + void end_writing(); + + /*! + @returns @c true if the controller is in reading mode; @c false otherwise. + */ + bool is_reading(); + + /*! + Returns the connected drive or, if none is connected, an invented one. No guarantees are + made about the lifetime or the exclusivity of the invented drive. + */ + Drive &get_drive(); + + /*! + As per Sleeper. + */ bool is_sleeping(); private: Time bit_length_; - int clock_rate_; - int clock_rate_multiplier_; - Time rotational_multiplier_; + int clock_rate_multiplier_ = 1; + int clock_rate_ = 1; + + bool is_reading_ = true; std::shared_ptr pll_; std::shared_ptr drive_; - std::shared_ptr track_; - int cycles_since_index_hole_; - inline void get_next_event(const Time &duration_already_passed); - Track::Event current_event_; - bool motor_is_on_; - - bool is_reading_; - bool clamp_writing_to_index_hole_; - std::shared_ptr patched_track_; - PCMSegment write_segment_; - Time write_start_time_; - - Time cycles_until_bits_written_; - Time cycles_per_bit_; - - void setup_track(); - Time get_time_into_track(); + std::shared_ptr empty_drive_; void set_component_is_sleeping(void *component, bool is_sleeping); + + // for Drive::EventDelegate + void process_event(const Track::Event &event); + void advance(const Cycles cycles); + + // to satisfy DigitalPhaseLockedLoop::Delegate + void digital_phase_locked_loop_output_bit(int value); }; } diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index 817bdd994..14f379c8d 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -7,24 +7,24 @@ // #include "Drive.hpp" + +#include "UnformattedTrack.hpp" + #include +#include using namespace Storage::Disk; -Drive::Drive() - : head_position_(0), head_(0), has_disk_(false) {} +Drive::Drive(unsigned int input_clock_rate, int revolutions_per_minute): + Storage::TimedEventLoop(input_clock_rate), + rotational_multiplier_(60, revolutions_per_minute) { +} void Drive::set_disk(const std::shared_ptr &disk) { disk_ = disk; - track_ = nullptr; has_disk_ = !!disk_; - update_sleep_observer(); -} -void Drive::set_disk_with_track(const std::shared_ptr &track) { - disk_ = nullptr; - track_ = track; - has_disk_ = !!track_; + invalidate_track(); update_sleep_observer(); } @@ -33,7 +33,7 @@ bool Drive::has_disk() { } bool Drive::is_sleeping() { - return !has_disk_; + return !motor_is_on_ || !has_disk_; } bool Drive::get_is_track_zero() { @@ -41,12 +41,30 @@ bool Drive::get_is_track_zero() { } void Drive::step(int direction) { + int old_head_position = head_position_; head_position_ = std::max(head_position_ + direction, 0); - printf("Head -> %d\n", head_position_); + + // If the head moved, flush the old track. + if(head_position_ != old_head_position) { + track_ = nullptr; + } } void Drive::set_head(unsigned int head) { - head_ = head; + if(head != head_) { + head_ = head; + track_ = nullptr; + } +} + +Storage::Time Drive::get_time_into_track() { + // `result` will initially be amount of time since the index hole was seen as a + // proportion of a second; convert it into proportion of a rotation, simplify and return. + Time result(cycles_since_index_hole_, (int)get_input_clock_rate()); + result /= rotational_multiplier_; + result.simplify(); + assert(result <= Time(1)); + return result; } bool Drive::get_is_read_only() { @@ -55,16 +73,177 @@ bool Drive::get_is_read_only() { } bool Drive::get_is_ready() { - // TODO: a real test for this. - return disk_ != nullptr; + return ready_index_count_ == 2; } +void Drive::set_motor_on(bool motor_is_on) { + motor_is_on_ = motor_is_on; + if(!motor_is_on) { + ready_index_count_ = 0; + } + update_sleep_observer(); +} + +bool Drive::get_motor_on() { + return motor_is_on_; +} + +void Drive::set_event_delegate(Storage::Disk::Drive::EventDelegate *delegate) { + event_delegate_ = delegate; +} + +void Drive::advance(const Cycles cycles) { + cycles_since_index_hole_ += (unsigned int)cycles.as_int(); + if(event_delegate_) event_delegate_->advance(cycles); +} + +void Drive::run_for(const Cycles cycles) { + if(has_disk_ && motor_is_on_) { + Time zero(0); + + int number_of_cycles = cycles.as_int(); + while(number_of_cycles) { + int cycles_until_next_event = (int)get_cycles_until_next_event(); + int cycles_to_run_for = std::min(cycles_until_next_event, number_of_cycles); + if(!is_reading_ && cycles_until_bits_written_ > zero) { + int write_cycles_target = (int)cycles_until_bits_written_.get_unsigned_int(); + if(cycles_until_bits_written_.length % cycles_until_bits_written_.clock_rate) write_cycles_target++; + cycles_to_run_for = std::min(cycles_to_run_for, write_cycles_target); + } + + number_of_cycles -= cycles_to_run_for; + if(!is_reading_) { + if(cycles_until_bits_written_ > zero) { + Storage::Time cycles_to_run_for_time(cycles_to_run_for); + if(cycles_until_bits_written_ <= cycles_to_run_for_time) { + if(event_delegate_) event_delegate_->process_write_completed(); + if(cycles_until_bits_written_ <= cycles_to_run_for_time) + cycles_until_bits_written_.set_zero(); + else + cycles_until_bits_written_ -= cycles_to_run_for_time; + } else { + cycles_until_bits_written_ -= cycles_to_run_for_time; + } + } + } + TimedEventLoop::run_for(Cycles(cycles_to_run_for)); + } + } +} + +#pragma mark - Track timed event loop + +void Drive::get_next_event(const Time &duration_already_passed) { + // Grab a new track if not already in possession of one. This will recursively call get_next_event, + // supplying a proper duration_already_passed. + if(!track_) { + setup_track(); + return; + } + + if(track_) { + current_event_ = track_->get_next_event(); + } else { + current_event_.length.length = 1; + current_event_.length.clock_rate = 1; + current_event_.type = Track::Event::IndexHole; + } + + // divide interval, which is in terms of a single rotation of the disk, by rotation speed to + // convert it into revolutions per second; this is achieved by multiplying by rotational_multiplier_ + assert(current_event_.length <= Time(1) && current_event_.length >= Time(0)); + Time interval = (current_event_.length - duration_already_passed) * rotational_multiplier_; + set_next_event_time_interval(interval); +} + +void Drive::process_next_event() { + // TODO: ready test here. + if(current_event_.type == Track::Event::IndexHole) { + assert(get_time_into_track() == Time(1) || get_time_into_track() == Time(0)); + if(ready_index_count_ < 2) ready_index_count_++; + cycles_since_index_hole_ = 0; + } + if( + event_delegate_ && + (current_event_.type == Track::Event::IndexHole || is_reading_) + ){ + event_delegate_->process_event(current_event_); + } + get_next_event(Time(0)); +} + +#pragma mark - Track management + std::shared_ptr Drive::get_track() { if(disk_) return disk_->get_track_at_position(head_, (unsigned int)head_position_); - if(track_) return track_; return nullptr; } void Drive::set_track(const std::shared_ptr &track) { if(disk_) disk_->set_track_at_position(head_, (unsigned int)head_position_, track); } + +void Drive::setup_track() { + track_ = get_track(); + if(!track_) { + track_.reset(new UnformattedTrack); + } + + Time offset; + Time track_time_now = get_time_into_track(); + assert(track_time_now >= Time(0) && current_event_.length <= Time(1)); + + Time time_found = track_->seek_to(track_time_now); + assert(time_found >= Time(0) && time_found < Time(1) && time_found <= track_time_now); + + offset = track_time_now - time_found; + get_next_event(offset); +} + +void Drive::invalidate_track() { + track_ = nullptr; + if(patched_track_) { + set_track(patched_track_); + patched_track_ = nullptr; + } +} + +#pragma mark - Writing + +void Drive::begin_writing(Time bit_length, bool clamp_to_index_hole) { + is_reading_ = false; + clamp_writing_to_index_hole_ = clamp_to_index_hole; + + cycles_per_bit_ = Storage::Time(get_input_clock_rate()) * bit_length; + cycles_per_bit_.simplify(); + + write_segment_.length_of_a_bit = bit_length / rotational_multiplier_; + write_segment_.data.clear(); + write_segment_.number_of_bits = 0; + + write_start_time_ = get_time_into_track(); +} + +void Drive::write_bit(bool value) { + bool needs_new_byte = !(write_segment_.number_of_bits&7); + if(needs_new_byte) write_segment_.data.push_back(0); + if(value) write_segment_.data[write_segment_.number_of_bits >> 3] |= 0x80 >> (write_segment_.number_of_bits & 7); + write_segment_.number_of_bits++; + + cycles_until_bits_written_ += cycles_per_bit_; +} + +void Drive::end_writing() { + is_reading_ = true; + + if(!patched_track_) { + // Avoid creating a new patched track if this one is already patched + patched_track_ = std::dynamic_pointer_cast(track_); + if(!patched_track_) { + patched_track_.reset(new PCMPatchedTrack(track_)); + } + } + patched_track_->add_segment(write_start_time_, write_segment_, clamp_writing_to_index_hole_); + cycles_since_index_hole_ %= get_input_clock_rate(); + invalidate_track(); +} diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index 2ed27ea71..81e01c48c 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -9,28 +9,27 @@ #ifndef Drive_hpp #define Drive_hpp -#include - #include "Disk.hpp" +#include "PCMSegment.hpp" +#include "PCMPatchedTrack.hpp" + +#include "../TimedEventLoop.hpp" #include "../../ClockReceiver/Sleeper.hpp" +#include + namespace Storage { namespace Disk { -class Drive: public Sleeper { +class Drive: public Sleeper, public TimedEventLoop { public: - Drive(); + Drive(unsigned int input_clock_rate, int revolutions_per_minute); /*! Replaces whatever is in the drive with @c disk. */ void set_disk(const std::shared_ptr &disk); - /*! - Replaces whatever is in the drive with a disk that contains endless copies of @c track. - */ - void set_disk_with_track(const std::shared_ptr &track); - /*! @returns @c true if a disk is currently inserted; @c false otherwise. */ @@ -57,6 +56,125 @@ class Drive: public Sleeper { */ bool get_is_read_only(); + /*! + @returns @c true if the drive is ready; @c false otherwise. + */ + bool get_is_ready(); + + /*! + Sets whether the disk motor is on. + */ + void set_motor_on(bool); + + /*! + @returns @c true if the motor is on; @c false otherwise. + */ + bool get_motor_on(); + + /*! + Begins write mode, initiating a PCM sampled region of data. Bits should be written via + @c write_bit. They will be written with the length set via @c set_expected_bit_length. + It is acceptable to supply a backlog of bits. Flux transition events will not be reported + while writing. + + @param clamp_to_index_hole If @c true then writing will automatically be truncated by + the index hole. Writing will continue over the index hole otherwise. + */ + void begin_writing(Time bit_length, bool clamp_to_index_hole); + + /*! + Writes the bit @c value as the next in the PCM stream initiated by @c begin_writing. + */ + void write_bit(bool value); + + /*! + Ends write mode, switching back to read mode. The drive will stop overwriting events. + */ + void end_writing(); + + /*! + Advances the drive by @c number_of_cycles cycles. + */ + void run_for(const Cycles cycles); + + /*! + Provides a mechanism to receive track events as they occur, including the synthetic + event of "you told me to output the following data, and I've done that now". + */ + struct EventDelegate { + /// Informs the delegate that @c event has been reached. + virtual void process_event(const Track::Event &event) = 0; + + /*! + If the drive is in write mode, announces that all queued bits have now been written. + If the controller provides further bits now then there will be no gap in written data. + */ + virtual void process_write_completed() = 0; + + /// Informs the delegate of the passing of @c cycles. + virtual void advance(const Cycles cycles) = 0; + }; + + /// Sets the current event delegate. + void set_event_delegate(EventDelegate *); + + // As per Sleeper. + bool is_sleeping(); + + private: + // Drives contain an entire disk; from that a certain track + // will be currently under the head. + std::shared_ptr disk_; + std::shared_ptr track_; + bool has_disk_ = false; + + // Contains the multiplier that converts between track-relative lengths + // to real-time lengths — so it's the reciprocal of rotation speed. + Time rotational_multiplier_; + + // A count of time since the index hole was last seen. Which is used to + // determine how far the drive is into a full rotation when switching to + // a new track. + int cycles_since_index_hole_ = 0; + + // A record of head position and active head. + int head_position_ = 0; + unsigned int head_ = 0; + + // Motor control state. + bool motor_is_on_ = false; + + // If the drive is not currently reading then it is writing. While writing + // it can optionally be told to clamp to the index hole. + bool is_reading_ = true; + bool clamp_writing_to_index_hole_ = false; + + // If writing is occurring then the drive will be accumulating a write segment, + // for addition to a patched track. + std::shared_ptr patched_track_; + PCMSegment write_segment_; + Time write_start_time_; + + // Indicates progress towards drive ready state. + int ready_index_count_ = 0; + + // Maintains appropriate counting to know when to indicate that writing + // is complete. + Time cycles_until_bits_written_; + Time cycles_per_bit_; + + // TimedEventLoop call-ins and state. + void process_next_event(); + void get_next_event(const Time &duration_already_passed); + void advance(const Cycles cycles); + Track::Event current_event_; + + // Helper for track changes. + Time get_time_into_track(); + + // The target (if any) for track events. + EventDelegate *event_delegate_ = nullptr; + /*! @returns the track underneath the current head at the location now stepped to. */ @@ -67,20 +185,8 @@ class Drive: public Sleeper { */ void set_track(const std::shared_ptr &track); - /*! - @returns @c true if the drive is ready; @c false otherwise. - */ - bool get_is_ready(); - - // As per Sleeper. - bool is_sleeping(); - - private: - std::shared_ptr track_; - std::shared_ptr disk_; - bool has_disk_; - int head_position_; - unsigned int head_; + void setup_track(); + void invalidate_track(); }; diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index 6b69af9c1..62eef12d7 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -9,6 +9,7 @@ #include "MFM.hpp" #include "../PCMTrack.hpp" +#include "../SingleTrackDisk.hpp" #include "../../../NumberTheory/CRC.hpp" #include @@ -234,7 +235,7 @@ std::unique_ptr Storage::Encodings::MFM::GetFMEncoder(std::vectorset_motor_on(true); } Parser::Parser(bool is_mfm, const std::shared_ptr &disk) : @@ -255,7 +256,7 @@ Parser::Parser(bool is_mfm, const std::shared_ptr &disk) : Parser::Parser(bool is_mfm, const std::shared_ptr &track) : Parser(is_mfm) { - drive_->set_disk_with_track(track); + drive_->set_disk(std::make_shared(track)); } void Parser::seek_to_track(uint8_t track) { @@ -266,7 +267,7 @@ void Parser::seek_to_track(uint8_t track) { int direction = difference < 0 ? -1 : 1; difference *= direction; - for(int c = 0; c < difference; c++) step(direction); + for(int c = 0; c < difference; c++) drive_->step(direction); } } @@ -274,7 +275,6 @@ std::shared_ptr Parser::get_sector(uint8_t head, uint8_t track, uint8_t // Switch head and track if necessary. if(head_ != head) { drive_->set_head(head); - invalidate_track(); } seek_to_track(track); int track_index = get_index(head, track, 0); @@ -314,7 +314,7 @@ std::vector Parser::get_track(uint8_t track) { return get_track(); } -void Parser::process_input_bit(int value, unsigned int cycles_since_index_hole) { +void Parser::process_input_bit(int value) { shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0xffff; bit_count_++; } @@ -426,7 +426,6 @@ std::vector Parser::get_track() { return result; } - std::shared_ptr Parser::get_next_sector() { std::shared_ptr sector(new Sector); index_count_ = 0; diff --git a/Storage/Disk/Encodings/MFM.hpp b/Storage/Disk/Encodings/MFM.hpp index 2ae651555..be521f2a2 100644 --- a/Storage/Disk/Encodings/MFM.hpp +++ b/Storage/Disk/Encodings/MFM.hpp @@ -129,7 +129,7 @@ class Parser: public Storage::Disk::Controller { bool is_mfm_; void seek_to_track(uint8_t track); - void process_input_bit(int value, unsigned int cycles_since_index_hole); + void process_input_bit(int value); void process_index_hole(); uint8_t get_next_byte(); diff --git a/Storage/Disk/MFMDiskController.cpp b/Storage/Disk/MFMDiskController.cpp index 1ae81df9a..08d0ea185 100644 --- a/Storage/Disk/MFMDiskController.cpp +++ b/Storage/Disk/MFMDiskController.cpp @@ -12,8 +12,8 @@ using namespace Storage::Disk; -MFMController::MFMController(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) : - Storage::Disk::Controller(clock_rate, clock_rate_multiplier, revolutions_per_minute), +MFMController::MFMController(Cycles clock_rate) : + Storage::Disk::Controller(clock_rate), crc_generator_(0x1021, 0xffff), data_mode_(DataMode::Scanning), is_awaiting_marker_value_(false) { @@ -53,7 +53,7 @@ NumberTheory::CRC16 &MFMController::get_crc_generator() { return crc_generator_; } -void MFMController::process_input_bit(int value, unsigned int cycles_since_index_hole) { +void MFMController::process_input_bit(int value) { if(data_mode_ == DataMode::Writing) return; shift_register_ = (shift_register_ << 1) | value; @@ -156,12 +156,12 @@ void MFMController::process_input_bit(int value, unsigned int cycles_since_index void MFMController::write_bit(int bit) { if(is_double_density_) { - Controller::write_bit(!bit && !last_bit_); - Controller::write_bit(!!bit); + get_drive().write_bit(!bit && !last_bit_); + get_drive().write_bit(!!bit); last_bit_ = bit; } else { - Controller::write_bit(true); - Controller::write_bit(!!bit); + get_drive().write_bit(true); + get_drive().write_bit(!!bit); } } @@ -172,7 +172,7 @@ void MFMController::write_byte(uint8_t byte) { void MFMController::write_raw_short(uint16_t value) { for(int c = 0; c < 16; c++) { - Controller::write_bit(!!((value << c)&0x8000)); + get_drive().write_bit(!!((value << c)&0x8000)); } } diff --git a/Storage/Disk/MFMDiskController.hpp b/Storage/Disk/MFMDiskController.hpp index 206246ba2..fd2b44f5b 100644 --- a/Storage/Disk/MFMDiskController.hpp +++ b/Storage/Disk/MFMDiskController.hpp @@ -22,7 +22,7 @@ namespace Disk { */ class MFMController: public Controller { public: - MFMController(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute); + MFMController(Cycles clock_rate); protected: /// Indicates whether the controller should try to decode double-density MFM content, or single-density FM content. @@ -146,7 +146,7 @@ class MFMController: public Controller { private: // Storage::Disk::Controller - virtual void process_input_bit(int value, unsigned int cycles_since_index_hole); + virtual void process_input_bit(int value); virtual void process_index_hole(); virtual void process_write_completed(); diff --git a/Storage/Disk/SingleTrackDisk.cpp b/Storage/Disk/SingleTrackDisk.cpp new file mode 100644 index 000000000..c40775c91 --- /dev/null +++ b/Storage/Disk/SingleTrackDisk.cpp @@ -0,0 +1,22 @@ +// +// SingleTrackDisk.cpp +// Clock Signal +// +// Created by Thomas Harte on 10/09/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#include "SingleTrackDisk.hpp" + +using namespace Storage::Disk; + +SingleTrackDisk::SingleTrackDisk(const std::shared_ptr &track) : + track_(track) {} + +unsigned int SingleTrackDisk::get_head_position_count() { + return 1; +} + +std::shared_ptr SingleTrackDisk::get_uncached_track_at_position(unsigned int head, unsigned int position) { + return track_; +} diff --git a/Storage/Disk/SingleTrackDisk.hpp b/Storage/Disk/SingleTrackDisk.hpp new file mode 100644 index 000000000..139a8f165 --- /dev/null +++ b/Storage/Disk/SingleTrackDisk.hpp @@ -0,0 +1,35 @@ +// +// SingleTrackDisk.hpp +// Clock Signal +// +// Created by Thomas Harte on 10/09/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef SingleTrackDisk_hpp +#define SingleTrackDisk_hpp + +#include "Disk.hpp" + +namespace Storage { +namespace Disk { + +/*! + Provides a disk that has houses a single track. +*/ +class SingleTrackDisk: public Disk { + public: + /// Constructs a single-track disk with the track @c track. + SingleTrackDisk(const std::shared_ptr &track); + + private: + std::shared_ptr track_; + + unsigned int get_head_position_count(); + std::shared_ptr get_uncached_track_at_position(unsigned int head, unsigned int position); +}; + +} +} + +#endif /* SingleTrackDisk_hpp */ diff --git a/Storage/TimedEventLoop.cpp b/Storage/TimedEventLoop.cpp index 377af80fd..43d1aee86 100644 --- a/Storage/TimedEventLoop.cpp +++ b/Storage/TimedEventLoop.cpp @@ -8,7 +8,9 @@ #include "TimedEventLoop.hpp" #include "../NumberTheory/Factors.hpp" + #include +#include using namespace Storage; @@ -16,16 +18,41 @@ TimedEventLoop::TimedEventLoop(unsigned int input_clock_rate) : input_clock_rate_(input_clock_rate) {} void TimedEventLoop::run_for(const Cycles cycles) { - cycles_until_event_ -= cycles.as_int(); - while(cycles_until_event_ <= 0) { + int remaining_cycles = cycles.as_int(); +#ifndef NDEBUG + int cycles_advanced = 0; +#endif + + while(cycles_until_event_ <= remaining_cycles) { +#ifndef NDEBUG + cycles_advanced += cycles_until_event_; +#endif + advance(cycles_until_event_); + remaining_cycles -= cycles_until_event_; + cycles_until_event_ = 0; process_next_event(); } + + if(remaining_cycles) { + cycles_until_event_ -= remaining_cycles; +#ifndef NDEBUG + cycles_advanced += remaining_cycles; +#endif + advance(remaining_cycles); + } + + assert(cycles_advanced == cycles.as_int()); + assert(cycles_until_event_ > 0); } unsigned int TimedEventLoop::get_cycles_until_next_event() { return (unsigned int)std::max(cycles_until_event_, 0); } +unsigned int TimedEventLoop::get_input_clock_rate() { + return input_clock_rate_; +} + void TimedEventLoop::reset_timer() { subcycles_until_event_.set_zero(); cycles_until_event_ = 0; @@ -52,9 +79,12 @@ void TimedEventLoop::set_next_event_time_interval(Time interval) { // So this event will fire in the integral number of cycles from now, putting us at the remainder // number of subcycles - cycles_until_event_ = (int)(numerator / denominator); + assert(cycles_until_event_ == 0); + cycles_until_event_ += (int)(numerator / denominator); + assert(cycles_until_event_ >= 0); subcycles_until_event_.length = (unsigned int)(numerator % denominator); subcycles_until_event_.clock_rate = (unsigned int)denominator; + subcycles_until_event_.simplify(); } Time TimedEventLoop::get_time_into_next_event() { diff --git a/Storage/TimedEventLoop.hpp b/Storage/TimedEventLoop.hpp index 660767beb..8f6549916 100644 --- a/Storage/TimedEventLoop.hpp +++ b/Storage/TimedEventLoop.hpp @@ -54,6 +54,11 @@ namespace Storage { */ unsigned int get_cycles_until_next_event(); + /*! + @returns the input clock rate. + */ + unsigned int get_input_clock_rate(); + protected: /*! Sets the time interval, as a proportion of a second, until the next event should be triggered. @@ -66,6 +71,15 @@ namespace Storage { */ virtual void process_next_event() = 0; + /*! + Optionally allows a subclass to track time within run_for periods; if a subclass implements + advnace then it will receive advance increments that add up to the number of cycles supplied + to run_for, but calls to process_next_event will be precisely interspersed. No time will carry + forward between calls into run_for; a subclass can receive arbitrarily many instructions to + advance before receiving a process_next_event. + */ + virtual void advance(const Cycles cycles) {}; + /*! Resets timing, throwing away any current internal state. So clears any fractional ticks that the event loop is currently tracking. @@ -86,8 +100,8 @@ namespace Storage { Time get_time_into_next_event(); private: - unsigned int input_clock_rate_; - int cycles_until_event_; + unsigned int input_clock_rate_ = 0; + int cycles_until_event_ = 0; Time subcycles_until_event_; };