diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 26434abb6..668fff112 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -27,12 +27,13 @@ WD1770::Status::Status() : WD1770::WD1770(Personality p) : Storage::Disk::Controller(8000000, 16, 300), + crc_generator_(0x1021, 0xffff), interesting_event_mask_(Event::Command), resume_point_(0), delay_time_(0), index_hole_count_target_(-1), is_awaiting_marker_value_(false), - is_reading_data_(false), + data_mode_(DataMode::Scanning), delegate_(nullptr), personality_(p), head_is_loaded_(false) @@ -74,7 +75,12 @@ void WD1770::set_register(int address, uint8_t value) break; case 1: track_ = value; break; case 2: sector_ = value; break; - case 3: data_ = value; break; + case 3: + data_ = value; + update_status([] (Status &status) { + status.data_request = false; + }); + break; } } @@ -154,11 +160,13 @@ void WD1770::run_for_cycles(unsigned int number_of_cycles) void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) { + if(data_mode_ == DataMode::Writing) return; + shift_register_ = (shift_register_ << 1) | value; bits_since_token_++; Token::Type token_type = Token::Byte; - if(!is_reading_data_) + if(data_mode_ == DataMode::Scanning) { if(!is_double_density_) { @@ -166,15 +174,23 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) { case Storage::Encodings::MFM::FMIndexAddressMark: token_type = Token::Index; + crc_generator_.reset(); + crc_generator_.add(Storage::Encodings::MFM::MFMIndexAddressByte); break; case Storage::Encodings::MFM::FMIDAddressMark: token_type = Token::ID; + crc_generator_.reset(); + crc_generator_.add(Storage::Encodings::MFM::MFMIDAddressByte); break; case Storage::Encodings::MFM::FMDataAddressMark: token_type = Token::Data; + crc_generator_.reset(); + crc_generator_.add(Storage::Encodings::MFM::MFMDataAddressByte); break; case Storage::Encodings::MFM::FMDeletedDataAddressMark: token_type = Token::DeletedData; + crc_generator_.reset(); + crc_generator_.add(Storage::Encodings::MFM::MFMDeletedDataAddressByte); break; default: break; @@ -184,13 +200,14 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) { switch(shift_register_ & 0xffff) { - case Storage::Encodings::MFM::MFMIndexAddressMark: + case Storage::Encodings::MFM::MFMIndexSync: bits_since_token_ = 0; is_awaiting_marker_value_ = true; return; - case Storage::Encodings::MFM::MFMAddressMark: + case Storage::Encodings::MFM::MFMSync: bits_since_token_ = 0; is_awaiting_marker_value_ = true; + crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue); return; default: break; @@ -241,6 +258,7 @@ void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) } } + crc_generator_.add(latest_token_.byte_value); posit_event(Event::Token); return; } @@ -269,6 +287,12 @@ void WD1770::process_index_hole() } } +void WD1770::process_write_completed() +{ + posit_event(Event::DataWritten); +} + + // +------+----------+-------------------------+ // ! ! ! BITS ! // ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 ! @@ -288,13 +312,14 @@ void WD1770::process_index_hole() #define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = mask; return; case __LINE__: #define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = Event::Timer; delay_time_ = ms * 8000; if(delay_time_) return; case __LINE__: +#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; interesting_event_mask_ = Event::Token; distance_into_section_ = 0; return; case __LINE__: if(latest_token_.type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = Event::Token; return; } #define BEGIN_SECTION() switch(resume_point_) { default: #define END_SECTION() 0; } #define READ_ID() \ if(new_event_type == Event::Token) \ { \ - if(!distance_into_section_ && latest_token_.type == Token::ID) {is_reading_data_ = true; distance_into_section_++; } \ + if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; } \ else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) \ { \ header_[distance_into_section_ - 1] = latest_token_.byte_value; \ @@ -325,7 +350,7 @@ void WD1770::posit_event(Event new_event_type) // Wait for a new command, branch to the appropriate handler. wait_for_command: printf("Idle...\n"); - is_reading_data_ = false; + data_mode_ = DataMode::Scanning; index_hole_count_ = 0; update_status([] (Status &status) { @@ -445,7 +470,7 @@ void WD1770::posit_event(Event new_event_type) } if(distance_into_section_ == 7) { - is_reading_data_ = false; + data_mode_ = DataMode::Scanning; // TODO: CRC check if(header_[0] == track_) { @@ -498,7 +523,7 @@ void WD1770::posit_event(Event new_event_type) WAIT_FOR_TIME(30); test_type2_write_protection: - if(command_&0x20) // TODO:: && is_write_protected + if(command_&0x20 && get_drive_is_read_only()) { update_status([] (Status &status) { status.write_protect = true; @@ -520,11 +545,24 @@ void WD1770::posit_event(Event new_event_type) } if(distance_into_section_ == 7) { - is_reading_data_ = false; + printf("Considering %d/%d\n", header_[0], header_[2]); + data_mode_ = DataMode::Scanning; if(header_[0] == track_ && header_[2] == sector_ && (has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) { - // TODO: test CRC + printf("Found %d/%d\n", header_[0], header_[2]); + if(crc_generator_.get_value()) + { + printf("CRC error; back to searching\n"); + update_status([] (Status &status) { + status.crc_error = true; + }); + goto type2_get_header; + } + + update_status([] (Status &status) { + status.crc_error = false; + }); goto type2_read_or_write_data; } distance_into_section_ = 0; @@ -545,7 +583,7 @@ void WD1770::posit_event(Event new_event_type) status.record_type = (latest_token_.type == Token::DeletedData); }); distance_into_section_ = 0; - is_reading_data_ = true; + data_mode_ = DataMode::Reading; goto type2_read_byte; } goto type2_read_data; @@ -573,7 +611,15 @@ void WD1770::posit_event(Event new_event_type) distance_into_section_++; if(distance_into_section_ == 2) { - // TODO: check CRC + if(crc_generator_.get_value()) + { + printf("CRC error; terminating\n"); + update_status([this] (Status &status) { + status.crc_error = true; + }); + goto wait_for_command; + } + if(command_ & 0x10) { sector_++; @@ -586,7 +632,95 @@ void WD1770::posit_event(Event new_event_type) type2_write_data: - printf("!!!TODO: data portion of sector!!!\n"); + WAIT_FOR_BYTES(2); + update_status([] (Status &status) { + status.data_request = true; + }); + WAIT_FOR_BYTES(9); + if(status_.data_request) + { + update_status([] (Status &status) { + status.lost_data = true; + }); + goto wait_for_command; + } + WAIT_FOR_BYTES(1); + if(is_double_density_) + { + WAIT_FOR_BYTES(11); + } + + data_mode_ = DataMode::Writing; + begin_writing(); + for(int c = 0; c < (is_double_density_ ? 12 : 6); c++) + { + write_byte(0); + } + WAIT_FOR_EVENT(Event::DataWritten); + + if(is_double_density_) + { + crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue); + for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMSync); + write_byte((command_&0x01) ? Storage::Encodings::MFM::MFMDeletedDataAddressByte : Storage::Encodings::MFM::MFMDataAddressByte); + } + else + { + crc_generator_.reset(); + crc_generator_.add((command_&0x01) ? Storage::Encodings::MFM::MFMDeletedDataAddressByte : Storage::Encodings::MFM::MFMDataAddressByte); + write_raw_short((command_&0x01) ? Storage::Encodings::MFM::FMDeletedDataAddressMark : Storage::Encodings::MFM::FMDataAddressMark); + } + + WAIT_FOR_EVENT(Event::DataWritten); + distance_into_section_ = 0; + + type2_write_loop: + /* + This deviates from the data sheet slightly since that would prima facie request one more byte + of data than is actually written — the last time around the loop it has transferred from the + data register to the data shift register, set data request, written the byte, checked that data + request has been satified, then finally considers whether all bytes are done. Based on both + natural expectations and the way that emulated machines responded, I believe that to be a + documentation error. + */ + write_byte(data_); + distance_into_section_++; + if(distance_into_section_ == 128 << header_[3]) + { + goto type2_write_crc; + } + + update_status([] (Status &status) { + status.data_request = true; + }); + WAIT_FOR_EVENT(Event::DataWritten); + if(status_.data_request) + { + update_status([] (Status &status) { + status.lost_data = true; + }); + goto wait_for_command; + } + + goto type2_write_loop; + + type2_write_crc: + { + uint16_t crc = crc_generator_.get_value(); + write_byte(crc >> 8); + write_byte(crc & 0xff); + } + write_byte(0xff); + WAIT_FOR_EVENT(Event::DataWritten); + end_writing(); + + if(command_ & 0x10) + { + sector_++; + goto test_type2_write_protection; + } + printf("Wrote sector %d\n", sector_); + goto wait_for_command; begin_type_3: update_status([] (Status &status) { @@ -619,3 +753,32 @@ void WD1770::set_head_loaded(bool head_loaded) head_is_loaded_ = head_loaded; if(head_loaded) posit_event(Event::HeadLoad); } + +void WD1770::write_bit(int bit) +{ + if(is_double_density_) + { + Controller::write_bit(!bit && !last_bit_); + Controller::write_bit(!!bit); + last_bit_ = bit; + } + else + { + Controller::write_bit(true); + Controller::write_bit(!!bit); + } +} + +void WD1770::write_byte(uint8_t byte) +{ + for(int c = 0; c < 8; c++) write_bit((byte << c)&0x80); + crc_generator_.add(byte); +} + +void WD1770::write_raw_short(uint16_t value) +{ + for(int c = 0; c < 16; c++) + { + Controller::write_bit(!!((value << c)&0x8000)); + } +} diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index edfe809e3..e06bcac8b 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -10,6 +10,7 @@ #define _770_hpp #include "../../Storage/Disk/DiskController.hpp" +#include "../../NumberTheory/CRC.hpp" namespace WD { @@ -94,7 +95,11 @@ class WD1770: public Storage::Disk::Controller { void update_status(std::function updater); // Tokeniser - bool is_reading_data_; + enum DataMode { + Scanning, + Reading, + Writing + } data_mode_; bool is_double_density_; int shift_register_; struct Token { @@ -110,18 +115,28 @@ class WD1770: public Storage::Disk::Controller { Token = (1 << 1), // Indicates recognition of a new token in the flux stream. Interrogate latest_token_ for details. IndexHole = (1 << 2), // Indicates the passing of a physical index hole. HeadLoad = (1 << 3), // Indicates the head has been loaded (1973 only). + DataWritten = (1 << 4), // Indicates that all queued bits have been written - Timer = (1 << 4), // Indicates that the delay_time_-powered timer has timed out. - IndexHoleTarget = (1 << 5) // Indicates that index_hole_count_ has reached index_hole_count_target_. + Timer = (1 << 5), // Indicates that the delay_time_-powered timer has timed out. + IndexHoleTarget = (1 << 6) // Indicates that index_hole_count_ has reached index_hole_count_target_. }; void posit_event(Event type); int interesting_event_mask_; int resume_point_; int delay_time_; + // Output + int last_bit_; + void write_bit(int bit); + void write_byte(uint8_t byte); + void write_raw_short(uint16_t value); + // ID buffer uint8_t header_[6]; + // CRC generator + NumberTheory::CRC16 crc_generator_; + // 1793 head-loading logic bool head_is_loaded_; @@ -131,6 +146,7 @@ class WD1770: public Storage::Disk::Controller { // Storage::Disk::Controller virtual void process_input_bit(int value, unsigned int cycles_since_index_hole); virtual void process_index_hole(); + virtual void process_write_completed(); }; } diff --git a/Machines/Electron/Plus3.cpp b/Machines/Electron/Plus3.cpp index 52035dd3d..d026f6bf9 100644 --- a/Machines/Electron/Plus3.cpp +++ b/Machines/Electron/Plus3.cpp @@ -10,7 +10,10 @@ using namespace Electron; -Plus3::Plus3() : WD1770(P1770) {} +Plus3::Plus3() : WD1770(P1770), last_control_(0) +{ + set_control_register(last_control_, 0xff); +} void Plus3::set_disk(std::shared_ptr disk, int drive) { @@ -24,18 +27,32 @@ void Plus3::set_disk(std::shared_ptr disk, int drive) void Plus3::set_control_register(uint8_t control) { - // TODO: // bit 0 => enable or disable drive 1 // bit 1 => enable or disable drive 2 // bit 2 => side select // bit 3 => single density select - switch(control&3) - { - case 0: selected_drive_ = -1; set_drive(nullptr); break; - default: selected_drive_ = 0; set_drive(drives_[0]); break; - case 2: selected_drive_ = 1; set_drive(drives_[1]); break; - } - if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0); - if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0); - set_is_double_density(!(control & 0x08)); + + uint8_t changes = control ^ last_control_; + last_control_ = control; + set_control_register(control, changes); +} + +void Plus3::set_control_register(uint8_t control, uint8_t changes) +{ + if(changes&3) + { + switch(control&3) + { + case 0: selected_drive_ = -1; set_drive(nullptr); break; + default: selected_drive_ = 0; set_drive(drives_[0]); break; + case 2: selected_drive_ = 1; set_drive(drives_[1]); break; + } + } + 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)); } diff --git a/Machines/Electron/Plus3.hpp b/Machines/Electron/Plus3.hpp index cd04b3877..37cf5b324 100644 --- a/Machines/Electron/Plus3.hpp +++ b/Machines/Electron/Plus3.hpp @@ -21,8 +21,10 @@ class Plus3 : public WD::WD1770 { void set_control_register(uint8_t control); private: + void set_control_register(uint8_t control, uint8_t changes); std::shared_ptr drives_[2]; int selected_drive_; + uint8_t last_control_; }; } diff --git a/Machines/Oric/Microdisc.cpp b/Machines/Oric/Microdisc.cpp index 25844d6cd..79d16ebd4 100644 --- a/Machines/Oric/Microdisc.cpp +++ b/Machines/Oric/Microdisc.cpp @@ -23,8 +23,11 @@ Microdisc::Microdisc() : delegate_(nullptr), paging_flags_(BASICDisable), head_load_request_counter_(-1), - WD1770(P1793) -{} + WD1770(P1793), + last_control_(0) +{ + set_control_register(last_control_, 0xff); +} void Microdisc::set_disk(std::shared_ptr disk, int drive) { @@ -38,47 +41,55 @@ void Microdisc::set_disk(std::shared_ptr disk, int drive) void Microdisc::set_control_register(uint8_t control) { - printf("control: %d%d%d%d%d%d%d%d\n", - (control >> 7)&1, - (control >> 6)&1, - (control >> 5)&1, - (control >> 4)&1, - (control >> 3)&1, - (control >> 2)&1, - (control >> 1)&1, - (control >> 0)&1); + uint8_t changes = last_control_ ^ control; + last_control_ = control; + set_control_register(control, changes); +} +void Microdisc::set_control_register(uint8_t control, uint8_t changes) +{ // b2: data separator clock rate select (1 = double) [TODO] // b65: drive select - selected_drive_ = (control >> 5)&3; - set_drive(drives_[selected_drive_]); + if((changes >> 5)&3) + { + selected_drive_ = (control >> 5)&3; + set_drive(drives_[selected_drive_]); + } // b4: side select - unsigned int head = (control & 0x10) ? 1 : 0; - for(int c = 0; c < 4; c++) + if(changes & 0x10) { - if(drives_[c]) drives_[c]->set_head(head); + unsigned int head = (control & 0x10) ? 1 : 0; + for(int c = 0; c < 4; c++) + { + if(drives_[c]) drives_[c]->set_head(head); + } } // b3: double density select (0 = double) - set_is_double_density(!(control & 0x08)); + if(changes & 0x08) + { + set_is_double_density(!(control & 0x08)); + } // b0: IRQ enable - bool had_irq = get_interrupt_request_line(); - irq_enable_ = !!(control & 0x01); - bool has_irq = get_interrupt_request_line(); - if(has_irq != had_irq && delegate_) + if(changes & 0x01) { - delegate_->wd1770_did_change_output(this); + bool had_irq = get_interrupt_request_line(); + irq_enable_ = !!(control & 0x01); + bool has_irq = get_interrupt_request_line(); + if(has_irq != had_irq && delegate_) + { + delegate_->wd1770_did_change_output(this); + } } // b7: EPROM select (0 = select) // b1: ROM disable (0 = disable) - int new_paging_flags = ((control & 0x02) ? 0 : BASICDisable) | ((control & 0x80) ? MicrodscDisable : 0); - if(new_paging_flags != paging_flags_) + if(changes & 0x82) { - paging_flags_ = new_paging_flags; + paging_flags_ = ((control & 0x02) ? 0 : BASICDisable) | ((control & 0x80) ? MicrodscDisable : 0); if(delegate_) delegate_->microdisc_did_change_paging_flags(this); } } diff --git a/Machines/Oric/Microdisc.hpp b/Machines/Oric/Microdisc.hpp index 521ab2e7e..d320c7721 100644 --- a/Machines/Oric/Microdisc.hpp +++ b/Machines/Oric/Microdisc.hpp @@ -39,6 +39,7 @@ class Microdisc: public WD::WD1770 { inline int get_paging_flags() { return paging_flags_; } private: + void set_control_register(uint8_t control, uint8_t changes); void set_head_load_request(bool head_load); bool get_drive_is_ready(); std::shared_ptr drives_[4]; @@ -47,6 +48,7 @@ class Microdisc: public WD::WD1770 { int paging_flags_; int head_load_request_counter_; Delegate *delegate_; + uint8_t last_control_; }; } diff --git a/NumberTheory/CRC.hpp b/NumberTheory/CRC.hpp index a705e3d4c..65bb0477e 100644 --- a/NumberTheory/CRC.hpp +++ b/NumberTheory/CRC.hpp @@ -28,10 +28,11 @@ class CRC16 { value_ = (uint16_t)(value_ << 1) ^ exclusive_or; } } - inline uint16_t get_value() { return value_; } + inline uint16_t get_value() const { return value_; } + inline void set_value(uint16_t value) { value_ = value; } private: - uint16_t reset_value_, polynomial_; + const uint16_t reset_value_, polynomial_; uint16_t value_; }; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 631e4e8eb..64b8747f6 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -354,6 +354,7 @@ 4BB299F71B587D8400A49093 /* txan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EB1B587D8400A49093 /* txan */; }; 4BB299F81B587D8400A49093 /* txsn in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EC1B587D8400A49093 /* txsn */; }; 4BB299F91B587D8400A49093 /* tyan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298ED1B587D8400A49093 /* tyan */; }; + 4BB2A9AF1E13367E001A5C23 /* CRCTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */; }; 4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */; }; 4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */; }; 4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */; }; @@ -841,6 +842,7 @@ 4BB298EB1B587D8400A49093 /* txan */ = {isa = PBXFileReference; lastKnownFileType = file; path = txan; sourceTree = ""; }; 4BB298EC1B587D8400A49093 /* txsn */ = {isa = PBXFileReference; lastKnownFileType = file; path = txsn; sourceTree = ""; }; 4BB298ED1B587D8400A49093 /* tyan */ = {isa = PBXFileReference; lastKnownFileType = file; path = tyan; sourceTree = ""; }; + 4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CRCTests.mm; sourceTree = ""; }; 4BB697C61D4B558F00248BDF /* Factors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Factors.hpp; path = ../../NumberTheory/Factors.hpp; sourceTree = ""; }; 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimedEventLoop.cpp; sourceTree = ""; }; 4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimedEventLoop.hpp; sourceTree = ""; }; @@ -1691,6 +1693,7 @@ isa = PBXGroup; children = ( 4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */, + 4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */, 4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */, 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */, 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */, @@ -2465,6 +2468,7 @@ 4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */, 4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */, 4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */, + 4BB2A9AF1E13367E001A5C23 /* CRCTests.mm in Sources */, 4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */, 4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */, 4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */, diff --git a/OSBindings/Mac/Clock SignalTests/CRCTests.mm b/OSBindings/Mac/Clock SignalTests/CRCTests.mm new file mode 100644 index 000000000..a370149d6 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/CRCTests.mm @@ -0,0 +1,72 @@ +// +// CRCTests.m +// Clock Signal +// +// Created by Thomas Harte on 27/12/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import +#include "CRC.hpp" + +@interface CRCTests : XCTestCase +@end + +@implementation CRCTests + +- (NumberTheory::CRC16)mfmCRCGenerator +{ + return NumberTheory::CRC16(0x1021, 0xffff); +} + +- (uint16_t)crcOfData:(uint8_t *)data length:(size_t)length generator:(NumberTheory::CRC16 &)generator +{ + generator.reset(); + for(size_t c = 0; c < length; c++) + generator.add(data[c]); + return generator.get_value(); +} + +- (void)testIDMark +{ + uint8_t IDMark[] = + { + 0xa1, 0xa1, 0xa1, 0xfe, 0x00, 0x00, 0x01, 0x01 + }; + uint16_t crc = 0xfa0c; + NumberTheory::CRC16 crcGenerator = self.mfmCRCGenerator; + + uint16_t computedCRC = [self crcOfData:IDMark length:sizeof(IDMark) generator:crcGenerator]; + XCTAssert(computedCRC == crc, @"Calculated CRC should have been %04x, was %04x", crc, computedCRC); +} + +- (void)testData +{ + uint8_t sectorData[] = + { + 0xa1, 0xa1, 0xa1, 0xfb, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x53, 0x45, 0x44, 0x4f, + 0x52, 0x49, 0x43, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x53, 0x45, 0x44, 0x4f, 0x52, 0x49, 0x43, 0x20, 0x56, 0x31, 0x2e, 0x30, + 0x30, 0x36, 0x20, 0x30, 0x31, 0x2f, 0x30, 0x31, 0x2f, 0x38, 0x36, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20 + }; + uint16_t crc = 0x4de7; + NumberTheory::CRC16 crcGenerator = self.mfmCRCGenerator; + + uint16_t computedCRC = [self crcOfData:sectorData length:sizeof(sectorData) generator:crcGenerator]; + XCTAssert(computedCRC == crc, @"Calculated CRC should have been %04x, was %04x", crc, computedCRC); +} + +@end diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp index 20fa8a0a6..00c77328e 100644 --- a/StaticAnalyser/Acorn/Disk.cpp +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -14,180 +14,11 @@ using namespace StaticAnalyser::Acorn; -class FMParser: public Storage::Disk::Controller { - public: - std::shared_ptr drive; - - FMParser(bool is_mfm) : - Storage::Disk::Controller(4000000, 1, 300), - crc_generator_(0x1021, 0xffff), - shift_register_(0), track_(0), is_mfm_(is_mfm) - { - Storage::Time bit_length; - bit_length.length = 1; - bit_length.clock_rate = is_mfm ? 500000 : 250000; // i.e. 250 kbps (including clocks) - set_expected_bit_length(bit_length); - - drive.reset(new Storage::Disk::Drive); - set_drive(drive); - set_motor_on(true); - } - - /*! - Attempts to read the sector located at @c track and @c sector. - - @returns a sector if one was found; @c nullptr otherwise. - */ - std::shared_ptr get_sector(uint8_t track, uint8_t sector) - { - int difference = (int)track - (int)track_; - track_ = track; - - if(difference) - { - int direction = difference < 0 ? -1 : 1; - difference *= direction; - - for(int c = 0; c < difference; c++) step(direction); - } - - return get_sector(sector); - } - - private: - unsigned int shift_register_; - int index_count_; - uint8_t track_; - int bit_count_; - std::shared_ptr sector_cache_[65536]; - NumberTheory::CRC16 crc_generator_; - bool is_mfm_; - - void process_input_bit(int value, unsigned int cycles_since_index_hole) - { - shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0xffff; - bit_count_++; - } - - void process_index_hole() - { - index_count_++; - } - - uint8_t get_next_byte() - { - bit_count_ = 0; - while(bit_count_ < 16) run_for_cycles(1); - uint8_t byte = (uint8_t)( - ((shift_register_&0x0001) >> 0) | - ((shift_register_&0x0004) >> 1) | - ((shift_register_&0x0010) >> 2) | - ((shift_register_&0x0040) >> 3) | - ((shift_register_&0x0100) >> 4) | - ((shift_register_&0x0400) >> 5) | - ((shift_register_&0x1000) >> 6) | - ((shift_register_&0x4000) >> 7)); - crc_generator_.add(byte); - return byte; - } - - std::shared_ptr get_next_sector() - { - std::shared_ptr sector(new Storage::Encodings::MFM::Sector); - index_count_ = 0; - - while(index_count_ < 2) - { - // look for an ID address mark - while(1) - { - run_for_cycles(1); - if(is_mfm_) - { - if(shift_register_ == Storage::Encodings::MFM::MFMAddressMark) - { - uint8_t mark = get_next_byte(); - if(mark == Storage::Encodings::MFM::MFMIDAddressByte) break; - } - } - else - { - if(shift_register_ == Storage::Encodings::MFM::FMIDAddressMark) break; - } - if(index_count_ >= 2) return nullptr; - } - - crc_generator_.reset(); - sector->track = get_next_byte(); - sector->side = get_next_byte(); - sector->sector = get_next_byte(); - uint8_t size = get_next_byte(); - uint16_t header_crc = crc_generator_.get_value(); - if((header_crc >> 8) != get_next_byte()) continue; - if((header_crc & 0xff) != get_next_byte()) continue; - - // look for data mark - while(1) - { - run_for_cycles(1); - if(is_mfm_) - { - if(shift_register_ == Storage::Encodings::MFM::MFMAddressMark) - { - uint8_t mark = get_next_byte(); - if(mark == Storage::Encodings::MFM::MFMDataAddressByte) break; - if(mark == Storage::Encodings::MFM::MFMIDAddressByte) return nullptr; - } - } - else - { - if(shift_register_ == Storage::Encodings::MFM::FMDataAddressMark) break; - if(shift_register_ == Storage::Encodings::MFM::FMIDAddressMark) return nullptr; - } - if(index_count_ >= 2) return nullptr; - } - - size_t data_size = (size_t)(128 << size); - sector->data.reserve(data_size); - crc_generator_.reset(); - for(size_t c = 0; c < data_size; c++) - { - sector->data.push_back(get_next_byte()); - } - uint16_t data_crc = crc_generator_.get_value(); - if((data_crc >> 8) != get_next_byte()) continue; - if((data_crc & 0xff) != get_next_byte()) continue; - - return sector; - } - - return nullptr; - } - - std::shared_ptr get_sector(uint8_t sector) - { -// uint16_t sector_address = (uint16_t)((track_ << 8) | sector); -// if(sector_cache_[sector_address]) return sector_cache_[sector_address]; - - std::shared_ptr first_sector = get_next_sector(); - if(!first_sector) return first_sector; - if(first_sector->sector == sector) return first_sector; - - while(1) - { - std::shared_ptr next_sector = get_next_sector(); - if(next_sector->sector == first_sector->sector) return nullptr; - if(next_sector->sector == sector) return next_sector; - } - } -}; - std::unique_ptr StaticAnalyser::Acorn::GetDFSCatalogue(const std::shared_ptr &disk) { // c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format std::unique_ptr catalogue(new Catalogue); - FMParser parser(false); - parser.drive->set_disk(disk); + Storage::Encodings::MFM::Parser parser(false, disk); std::shared_ptr names = parser.get_sector(0, 0); std::shared_ptr details = parser.get_sector(0, 1); @@ -197,6 +28,7 @@ std::unique_ptr StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha uint8_t final_file_offset = details->data[5]; if(final_file_offset&7) return nullptr; + if(final_file_offset < 8) return nullptr; char disk_name[13]; snprintf(disk_name, 13, "%.8s%.4s", &names->data[0], &details->data[0]); @@ -248,8 +80,7 @@ std::unique_ptr StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha std::unique_ptr StaticAnalyser::Acorn::GetADFSCatalogue(const std::shared_ptr &disk) { std::unique_ptr catalogue(new Catalogue); - FMParser parser(true); - parser.drive->set_disk(disk); + Storage::Encodings::MFM::Parser parser(true, disk); std::shared_ptr free_space_map_second_half = parser.get_sector(0, 1); if(!free_space_map_second_half) return nullptr; diff --git a/Storage/Disk/Disk.cpp b/Storage/Disk/Disk.cpp index c6b04e0ee..fe7d1cea4 100644 --- a/Storage/Disk/Disk.cpp +++ b/Storage/Disk/Disk.cpp @@ -17,11 +17,16 @@ int Disk::get_id_for_track_at_position(unsigned int head, unsigned int position) void Disk::set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track) { - if(!get_is_read_only()) return; + if(get_is_read_only()) return; int address = get_id_for_track_at_position(head, position); cached_tracks_[address] = track; - modified_tracks_.insert(address); + + if(!update_queue_) update_queue_.reset(new Concurrency::AsyncTaskQueue); + std::shared_ptr track_copy(track->clone()); + update_queue_->enqueue([this, head, position, track_copy] { + store_updated_track_at_position(head, position, track_copy, file_access_mutex_); + }); } std::shared_ptr Disk::get_track_at_position(unsigned int head, unsigned int position) @@ -30,21 +35,15 @@ std::shared_ptr Disk::get_track_at_position(unsigned int head, unsigned i std::map>::iterator cached_track = cached_tracks_.find(address); if(cached_track != cached_tracks_.end()) return cached_track->second; + std::lock_guard lock_guard(file_access_mutex_); std::shared_ptr track = get_uncached_track_at_position(head, position); cached_tracks_[address] = track; return track; } -std::shared_ptr Disk::get_modified_track_at_position(unsigned int head, unsigned int position) -{ - int address = get_id_for_track_at_position(head, position); - if(modified_tracks_.find(address) == modified_tracks_.end()) return nullptr; - std::map>::iterator cached_track = cached_tracks_.find(address); - if(cached_track == cached_tracks_.end()) return nullptr; - return cached_track->second; -} +void Disk::store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track, std::mutex &file_access_mutex) {} -bool Disk::get_is_modified() +void Disk::flush_updates() { - return !modified_tracks_.empty(); + if(update_queue_) update_queue_->flush(); } diff --git a/Storage/Disk/Disk.hpp b/Storage/Disk/Disk.hpp index 514bc0c61..f574f2c28 100644 --- a/Storage/Disk/Disk.hpp +++ b/Storage/Disk/Disk.hpp @@ -9,10 +9,13 @@ #ifndef Disk_hpp #define Disk_hpp -#include #include +#include +#include #include + #include "../Storage.hpp" +#include "../../Concurrency/AsyncTaskQueue.hpp" namespace Storage { namespace Disk { @@ -50,6 +53,11 @@ class Track { @returns the time jumped to. */ virtual Time seek_to(const Time &time_since_index_hole) = 0; + + /*! + The virtual copy constructor pattern; returns a copy of the Track. + */ + virtual Track *clone() = 0; }; /*! @@ -66,6 +74,7 @@ class Track { */ class Disk { public: + virtual ~Disk() {} /*! @returns the number of discrete positions that this disk uses to model its complete surface area. @@ -106,20 +115,15 @@ class Disk { */ virtual std::shared_ptr get_uncached_track_at_position(unsigned int head, unsigned int position) = 0; - /*! - @returns @c true if any calls to set_track_at_position occurred; @c false otherwise. - */ - bool get_is_modified(); - /*! - @returns the @c Track at @c position underneath @c head if a modification was written there. - */ - std::shared_ptr get_modified_track_at_position(unsigned int head, unsigned int position); + virtual void store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track, std::mutex &file_access_mutex); + void flush_updates(); private: std::map> cached_tracks_; - std::set modified_tracks_; int get_id_for_track_at_position(unsigned int head, unsigned int position); + std::mutex file_access_mutex_; + std::unique_ptr update_queue_; }; } diff --git a/Storage/Disk/DiskController.cpp b/Storage/Disk/DiskController.cpp index 07c655f47..47c971a22 100644 --- a/Storage/Disk/DiskController.cpp +++ b/Storage/Disk/DiskController.cpp @@ -7,6 +7,7 @@ // #include "DiskController.hpp" +#include "../../NumberTheory/Factors.hpp" using namespace Storage::Disk; @@ -19,7 +20,6 @@ Controller::Controller(unsigned int clock_rate, unsigned int clock_rate_multipli motor_is_on_(false), is_reading_(true), - track_is_dirty_(false), TimedEventLoop(clock_rate * clock_rate_multiplier) { @@ -30,31 +30,23 @@ Controller::Controller(unsigned int clock_rate, unsigned int clock_rate_multipli void Controller::setup_track() { - if(patched_track_) - { - drive_->set_track(patched_track_); - } - track_ = drive_->get_track(); - track_is_dirty_ = false; Time offset; Time track_time_now = get_time_into_track(); - if(track_ && track_time_now > Time(0)) + if(track_) { Time time_found = track_->seek_to(track_time_now); offset = track_time_now - time_found; } - else - { - offset = track_time_now; - } get_next_event(offset); } void Controller::run_for_cycles(int number_of_cycles) { + Time zero(0); + if(drive_ && drive_->has_disk() && motor_is_on_) { if(!track_) setup_track(); @@ -64,11 +56,39 @@ void Controller::run_for_cycles(int 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); + } 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); + 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); } } @@ -123,7 +143,7 @@ void Controller::begin_writing() { is_reading_ = false; - write_segment_.length_of_a_bit = bit_length_ * rotational_multiplier_; + write_segment_.length_of_a_bit = bit_length_ / rotational_multiplier_; write_segment_.data.clear(); write_segment_.number_of_bits = 0; @@ -136,6 +156,8 @@ void Controller::write_bit(bool value) 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() @@ -144,9 +166,15 @@ void Controller::end_writing() if(!patched_track_) { - patched_track_.reset(new PCMPatchedTrack(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_); + invalidate_track(); // TEMPORARY: to force a seek } #pragma mark - PLL control and delegate @@ -154,10 +182,14 @@ void Controller::end_writing() 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(); // 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)((bit_length.length * clock_rate_) / bit_length.clock_rate); + int clocks_per_bit = (int)cycles_per_bit_.get_unsigned_int(); pll_.reset(new DigitalPhaseLockedLoop(clocks_per_bit, clocks_per_bit / 5, 3)); pll_->set_delegate(this); } @@ -181,10 +213,16 @@ bool Controller::get_drive_is_ready() 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) { - if(drive_) drive_->step(direction); invalidate_track(); + if(drive_) drive_->step(direction); } void Controller::set_motor_on(bool motor_on) @@ -199,11 +237,23 @@ bool Controller::get_motor_on() void Controller::set_drive(std::shared_ptr drive) { - drive_ = drive; - invalidate_track(); + if(drive_ != drive) + { + invalidate_track(); + drive_ = drive; + } } void Controller::invalidate_track() { track_ = nullptr; + if(patched_track_) + { + drive_->set_track(patched_track_); + patched_track_ = nullptr; + } +} + +void Controller::process_write_completed() +{ } diff --git a/Storage/Disk/DiskController.hpp b/Storage/Disk/DiskController.hpp index ca856fa99..a39b74b48 100644 --- a/Storage/Disk/DiskController.hpp +++ b/Storage/Disk/DiskController.hpp @@ -49,6 +49,10 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop Sets the current drive. */ 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. + */ void invalidate_track(); /*! @@ -86,10 +90,16 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop virtual void process_input_bit(int value, unsigned int cycles_since_index_hole) = 0; /*! - Should be implemented by subcalsses; communicates that the index hole has been reached. + Should be implemented by subclasses; communicates that the index hole has been reached. */ virtual void process_index_hole() = 0; + /*! + Should be implemented by subclasses if they implement writing; communicates that + all bits supplied to write_bit have now been written. + */ + virtual void process_write_completed(); + // for TimedEventLoop virtual void process_next_event(); @@ -99,6 +109,7 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop bool get_is_track_zero(); void step(int direction); virtual bool get_drive_is_ready(); + bool get_drive_is_read_only(); private: Time bit_length_; @@ -116,11 +127,13 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop bool motor_is_on_; bool is_reading_; - bool track_is_dirty_; 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(); }; diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index 0b9fb34cb..3b58d8fbb 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -14,14 +14,21 @@ using namespace Storage::Disk; Drive::Drive() : head_position_(0), head_(0) {} -void Drive::set_disk(std::shared_ptr disk) +void Drive::set_disk(const std::shared_ptr &disk) { disk_ = disk; + track_ = nullptr; +} + +void Drive::set_disk_with_track(const std::shared_ptr &track) +{ + disk_ = nullptr; + track_ = track; } bool Drive::has_disk() { - return (bool)disk_; + return (bool)disk_ || (bool)track_; } bool Drive::get_is_track_zero() @@ -42,12 +49,14 @@ void Drive::set_head(unsigned int head) bool Drive::get_is_read_only() { if(disk_) return disk_->get_is_read_only(); + if(track_) return true; return false; } 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; } diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index 1506e8a2c..82e34fcd6 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -20,9 +20,14 @@ class Drive { Drive(); /*! - Inserts @c disk into the drive. + Replaces whatever is in the drive with @c disk. */ - void set_disk(std::shared_ptr 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. @@ -61,6 +66,7 @@ class Drive { void set_track(const std::shared_ptr &track); private: + std::shared_ptr track_; std::shared_ptr disk_; int head_position_; unsigned int head_; diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index 8c864ff53..d1740baeb 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -18,6 +18,7 @@ class MFMEncoder: public Encoder { MFMEncoder(std::vector &target) : Encoder(target) {} void add_byte(uint8_t input) { + crc_generator_.add(input); uint16_t spread_value = (uint16_t)( ((input & 0x01) << 0) | @@ -29,33 +30,42 @@ class MFMEncoder: public Encoder { ((input & 0x40) << 6) | ((input & 0x80) << 7) ); - uint16_t or_bits = (uint16_t)((spread_value << 1) | (spread_value >> 1) | (output_ << 15)); - output_ = spread_value | ((~or_bits) & 0xaaaa); - output_short(output_); + uint16_t or_bits = (uint16_t)((spread_value << 1) | (spread_value >> 1) | (last_output_ << 15)); + uint16_t output = spread_value | ((~or_bits) & 0xaaaa); + output_short(output); } void add_index_address_mark() { - output_short(output_ = MFMIndexAddressMark); + for(int c = 0; c < 3; c++) output_short(MFMIndexSync); add_byte(MFMIndexAddressByte); } void add_ID_address_mark() { - output_short(output_ = MFMAddressMark); + output_sync(); add_byte(MFMIDAddressByte); } void add_data_address_mark() { - output_short(output_ = MFMAddressMark); + output_sync(); add_byte(MFMDataAddressByte); } void add_deleted_data_address_mark() { - output_short(output_ = MFMAddressMark); + output_sync(); add_byte(MFMDeletedDataAddressByte); } private: - uint16_t output_; + uint16_t last_output_; + void output_short(uint16_t value) { + last_output_ = value; + Encoder::output_short(value); + } + + void output_sync() { + for(int c = 0; c < 3; c++) output_short(MFMSync); + crc_generator_.set_value(MFMPostSyncCRCValue); + } }; class FMEncoder: public Encoder { @@ -64,6 +74,7 @@ class FMEncoder: public Encoder { FMEncoder(std::vector &target) : Encoder(target) {} void add_byte(uint8_t input) { + crc_generator_.add(input); output_short( (uint16_t)( ((input & 0x01) << 0) | @@ -78,10 +89,33 @@ class FMEncoder: public Encoder { )); } - void add_index_address_mark() { output_short(FMIndexAddressMark); } - void add_ID_address_mark() { output_short(FMIDAddressMark); } - void add_data_address_mark() { output_short(FMDataAddressMark); } - void add_deleted_data_address_mark() { output_short(FMDeletedDataAddressMark); } + void add_index_address_mark() + { + crc_generator_.reset(); + crc_generator_.add(MFMIndexAddressByte); + output_short(FMIndexAddressMark); + } + + void add_ID_address_mark() + { + crc_generator_.reset(); + crc_generator_.add(MFMIDAddressByte); + output_short(FMIDAddressMark); + } + + void add_data_address_mark() + { + crc_generator_.reset(); + crc_generator_.add(MFMDataAddressByte); + output_short(FMDataAddressMark); + } + + void add_deleted_data_address_mark() + { + crc_generator_.reset(); + crc_generator_.add(MFMDeletedDataAddressByte); + output_short(FMDeletedDataAddressMark); + } }; static uint8_t logarithmic_size_for_size(size_t size) @@ -109,7 +143,6 @@ template std::shared_ptr Storage::Disk::PCMSegment segment; segment.data.reserve(expected_track_bytes); T shifter(segment.data); - NumberTheory::CRC16 crc_generator(0x1021, 0xffff); // output the index mark shifter.add_index_address_mark(); @@ -130,16 +163,7 @@ template std::shared_ptr shifter.add_byte(sector.sector); uint8_t size = logarithmic_size_for_size(sector.data.size()); shifter.add_byte(size); - - // header CRC - crc_generator.reset(); - crc_generator.add(sector.track); - crc_generator.add(sector.side); - crc_generator.add(sector.sector); - crc_generator.add(size); - uint16_t crc_value = crc_generator.get_value(); - shifter.add_byte(crc_value >> 8); - shifter.add_byte(crc_value & 0xff); + shifter.add_crc(); // gap for(int c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(0x4e); @@ -147,17 +171,11 @@ template std::shared_ptr // data shifter.add_data_address_mark(); - crc_generator.reset(); for(size_t c = 0; c < sector.data.size(); c++) { shifter.add_byte(sector.data[c]); - crc_generator.add(sector.data[c]); } - - // data CRC - crc_value = crc_generator.get_value(); - shifter.add_byte(crc_value >> 8); - shifter.add_byte(crc_value & 0xff); + shifter.add_crc(); // gap for(int c = 0; c < post_data_bytes; c++) shifter.add_byte(0x00); @@ -171,6 +189,7 @@ template std::shared_ptr } Encoder::Encoder(std::vector &target) : + crc_generator_(0x1021, 0xffff), target_(target) {} @@ -180,6 +199,13 @@ void Encoder::output_short(uint16_t value) target_.push_back(value & 0xff); } +void Encoder::add_crc() +{ + uint16_t crc_value = crc_generator_.get_value(); + add_byte(crc_value >> 8); + add_byte(crc_value & 0xff); +} + std::shared_ptr Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector §ors) { return GetTrackWithSectors( @@ -211,3 +237,186 @@ std::unique_ptr Storage::Encodings::MFM::GetFMEncoder(std::vector(new FMEncoder(target)); } + +#pragma mark - Parser + +Parser::Parser(bool is_mfm) : + Storage::Disk::Controller(4000000, 1, 300), + crc_generator_(0x1021, 0xffff), + shift_register_(0), track_(0), is_mfm_(is_mfm) +{ + Storage::Time bit_length; + bit_length.length = 1; + bit_length.clock_rate = is_mfm ? 500000 : 250000; // i.e. 250 kbps (including clocks) + set_expected_bit_length(bit_length); + + drive.reset(new Storage::Disk::Drive); + set_drive(drive); + set_motor_on(true); +} + +Parser::Parser(bool is_mfm, const std::shared_ptr &disk) : + Parser(is_mfm) +{ + drive->set_disk(disk); +} + +Parser::Parser(bool is_mfm, const std::shared_ptr &track) : + Parser(is_mfm) +{ + drive->set_disk_with_track(track); +} + +std::shared_ptr Parser::get_sector(uint8_t track, uint8_t sector) +{ + int difference = (int)track - (int)track_; + track_ = track; + + if(difference) + { + int direction = difference < 0 ? -1 : 1; + difference *= direction; + + for(int c = 0; c < difference; c++) step(direction); + } + + return get_sector(sector); +} + +void Parser::process_input_bit(int value, unsigned int cycles_since_index_hole) +{ + shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0xffff; + bit_count_++; +} + +void Parser::process_index_hole() +{ + index_count_++; +} + +uint8_t Parser::get_next_byte() +{ + bit_count_ = 0; + while(bit_count_ < 16) run_for_cycles(1); + uint8_t byte = (uint8_t)( + ((shift_register_&0x0001) >> 0) | + ((shift_register_&0x0004) >> 1) | + ((shift_register_&0x0010) >> 2) | + ((shift_register_&0x0040) >> 3) | + ((shift_register_&0x0100) >> 4) | + ((shift_register_&0x0400) >> 5) | + ((shift_register_&0x1000) >> 6) | + ((shift_register_&0x4000) >> 7)); + crc_generator_.add(byte); + return byte; +} + +std::shared_ptr Parser::get_next_sector() +{ + std::shared_ptr sector(new Storage::Encodings::MFM::Sector); + index_count_ = 0; + + while(index_count_ < 2) + { + // look for an ID address mark + bool id_found = false; + while(!id_found) + { + run_for_cycles(1); + if(is_mfm_) + { + while(shift_register_ == Storage::Encodings::MFM::MFMSync) + { + uint8_t mark = get_next_byte(); + if(mark == Storage::Encodings::MFM::MFMIDAddressByte) + { + crc_generator_.set_value(MFMPostSyncCRCValue); + id_found = true; + break; + } + } + } + else + { + if(shift_register_ == Storage::Encodings::MFM::FMIDAddressMark) + { + crc_generator_.reset(); + id_found = true; + } + } + if(index_count_ >= 2) return nullptr; + } + + crc_generator_.add(MFMIDAddressByte); + sector->track = get_next_byte(); + sector->side = get_next_byte(); + sector->sector = get_next_byte(); + uint8_t size = get_next_byte(); + uint16_t header_crc = crc_generator_.get_value(); + if((header_crc >> 8) != get_next_byte()) continue; + if((header_crc & 0xff) != get_next_byte()) continue; + + // look for data mark + bool data_found = false; + while(!data_found) + { + run_for_cycles(1); + if(is_mfm_) + { + while(shift_register_ == Storage::Encodings::MFM::MFMSync) + { + uint8_t mark = get_next_byte(); + if(mark == Storage::Encodings::MFM::MFMDataAddressByte) + { + crc_generator_.set_value(MFMPostSyncCRCValue); + data_found = true; + break; + } + if(mark == Storage::Encodings::MFM::MFMIDAddressByte) return nullptr; + } + } + else + { + if(shift_register_ == Storage::Encodings::MFM::FMDataAddressMark) + { + crc_generator_.reset(); + data_found = true; + } + if(shift_register_ == Storage::Encodings::MFM::FMIDAddressMark) return nullptr; + } + if(index_count_ >= 2) return nullptr; + } + crc_generator_.add(MFMDataAddressByte); + + size_t data_size = (size_t)(128 << size); + sector->data.reserve(data_size); + for(size_t c = 0; c < data_size; c++) + { + sector->data.push_back(get_next_byte()); + } + uint16_t data_crc = crc_generator_.get_value(); + if((data_crc >> 8) != get_next_byte()) continue; + if((data_crc & 0xff) != get_next_byte()) continue; + + return sector; + } + + return nullptr; +} + +std::shared_ptr Parser::get_sector(uint8_t sector) +{ + std::shared_ptr first_sector; + index_count_ = 0; + while(!first_sector && index_count_ < 2) first_sector = get_next_sector(); + if(!first_sector) return first_sector; + if(first_sector->sector == sector) return first_sector; + + while(1) + { + std::shared_ptr next_sector = get_next_sector(); + if(!next_sector) continue; + if(next_sector->sector == first_sector->sector) return nullptr; + if(next_sector->sector == sector) return next_sector; + } +} diff --git a/Storage/Disk/Encodings/MFM.hpp b/Storage/Disk/Encodings/MFM.hpp index 57e4b11da..f1545407d 100644 --- a/Storage/Disk/Encodings/MFM.hpp +++ b/Storage/Disk/Encodings/MFM.hpp @@ -12,6 +12,8 @@ #include #include #include "../Disk.hpp" +#include "../DiskController.hpp" +#include "../../../NumberTheory/CRC.hpp" namespace Storage { namespace Encodings { @@ -22,12 +24,13 @@ const uint16_t FMIDAddressMark = 0xf57e; // data 0xfe, with clock 0xc7 => 1111 const uint16_t FMDataAddressMark = 0xf56f; // data 0xfb, with clock 0xc7 => 1111 1011 with clock 1100 0111 => 1111 0101 0110 1111 const uint16_t FMDeletedDataAddressMark = 0xf56a; // data 0xf8, with clock 0xc7 => 1111 1000 with clock 1100 0111 => 1111 0101 0110 1010 -const uint16_t MFMIndexAddressMark = 0x5224; -const uint16_t MFMAddressMark = 0x4489; +const uint16_t MFMIndexSync = 0x5224; +const uint16_t MFMSync = 0x4489; const uint8_t MFMIndexAddressByte = 0xfc; const uint8_t MFMIDAddressByte = 0xfe; const uint8_t MFMDataAddressByte = 0xfb; const uint8_t MFMDeletedDataAddressByte = 0xf8; +const uint16_t MFMPostSyncCRCValue = 0xcdb4; struct Sector { uint8_t track, side, sector; @@ -45,9 +48,11 @@ class Encoder { virtual void add_ID_address_mark() = 0; virtual void add_data_address_mark() = 0; virtual void add_deleted_data_address_mark() = 0; + virtual void output_short(uint16_t value); + void add_crc(); protected: - void output_short(uint16_t value); + NumberTheory::CRC16 crc_generator_; private: std::vector &target_; @@ -56,6 +61,37 @@ class Encoder { std::unique_ptr GetMFMEncoder(std::vector &target); std::unique_ptr GetFMEncoder(std::vector &target); +class Parser: public Storage::Disk::Controller { + public: + Parser(bool is_mfm, const std::shared_ptr &disk); + Parser(bool is_mfm, const std::shared_ptr &track); + + /*! + Attempts to read the sector located at @c track and @c sector. + + @returns a sector if one was found; @c nullptr otherwise. + */ + std::shared_ptr get_sector(uint8_t track, uint8_t sector); + + private: + Parser(bool is_mfm); + + std::shared_ptr drive; + unsigned int shift_register_; + int index_count_; + uint8_t track_; + int bit_count_; + NumberTheory::CRC16 crc_generator_; + bool is_mfm_; + + void process_input_bit(int value, unsigned int cycles_since_index_hole); + void process_index_hole(); + uint8_t get_next_byte(); + std::shared_ptr get_next_sector(); + std::shared_ptr get_sector(uint8_t sector); +}; + + } } } diff --git a/Storage/Disk/Formats/AcornADF.cpp b/Storage/Disk/Formats/AcornADF.cpp index 5eb7d7dce..1568fbb86 100644 --- a/Storage/Disk/Formats/AcornADF.cpp +++ b/Storage/Disk/Formats/AcornADF.cpp @@ -37,6 +37,11 @@ AcornADF::AcornADF(const char *file_name) : if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF; } +AcornADF::~AcornADF() +{ + flush_updates(); +} + unsigned int AcornADF::get_head_position_count() { return 80; @@ -47,12 +52,22 @@ unsigned int AcornADF::get_head_count() return 1; } +bool AcornADF::get_is_read_only() +{ + return is_read_only_; +} + +long AcornADF::get_file_offset_for_position(unsigned int head, unsigned int position) +{ + return (position * 1 + head) * bytes_per_sector * sectors_per_track; +} + std::shared_ptr AcornADF::get_uncached_track_at_position(unsigned int head, unsigned int position) { std::shared_ptr track; if(head >= 2) return track; - long file_offset = (position * 1 + head) * bytes_per_sector * sectors_per_track; + long file_offset = get_file_offset_for_position(head, position); fseek(file_, file_offset, SEEK_SET); std::vector sectors; @@ -75,3 +90,29 @@ std::shared_ptr AcornADF::get_uncached_track_at_position(unsigned int hea return track; } + +void AcornADF::store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track, std::mutex &file_access_mutex) +{ + std::vector parsed_track; + Storage::Encodings::MFM::Parser parser(true, track); + for(unsigned int c = 0; c < sectors_per_track; c++) + { + std::shared_ptr sector = parser.get_sector((uint8_t)position, (uint8_t)c); + if(sector) + { + parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end()); + } + else + { + // TODO: what's correct here? Warn the user that whatever has been written to the disk, + // it can no longer be stored as an SSD? If so, warn them by what route? + parsed_track.resize(parsed_track.size() + bytes_per_sector); + } + } + + std::lock_guard lock_guard(file_access_mutex); + long file_offset = get_file_offset_for_position(head, position); + ensure_file_is_at_least_length(file_offset); + fseek(file_, file_offset, SEEK_SET); + fwrite(parsed_track.data(), 1, parsed_track.size(), file_); +} diff --git a/Storage/Disk/Formats/AcornADF.hpp b/Storage/Disk/Formats/AcornADF.hpp index 11109d5ea..0100a4d2c 100644 --- a/Storage/Disk/Formats/AcornADF.hpp +++ b/Storage/Disk/Formats/AcornADF.hpp @@ -27,6 +27,7 @@ class AcornADF: public Disk, public Storage::FileHolder { @throws ErrorNotAcornADF if the file doesn't appear to contain an Acorn .ADF format image. */ AcornADF(const char *file_name); + ~AcornADF(); enum { ErrorNotAcornADF, @@ -35,8 +36,12 @@ class AcornADF: public Disk, public Storage::FileHolder { // implemented to satisfy @c Disk unsigned int get_head_position_count(); unsigned int get_head_count(); + bool get_is_read_only(); + private: + void store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track, std::mutex &file_access_mutex); std::shared_ptr get_uncached_track_at_position(unsigned int head, unsigned int position); + long get_file_offset_for_position(unsigned int head, unsigned int position); }; } diff --git a/Storage/Disk/Formats/OricMFMDSK.cpp b/Storage/Disk/Formats/OricMFMDSK.cpp index 7ab373bd7..c0de30f9e 100644 --- a/Storage/Disk/Formats/OricMFMDSK.cpp +++ b/Storage/Disk/Formats/OricMFMDSK.cpp @@ -57,6 +57,7 @@ std::shared_ptr OricMFMDSK::get_uncached_track_at_position(unsigned int h size_t track_offset = 0; uint8_t last_header[6]; std::unique_ptr encoder = Encodings::MFM::GetMFMEncoder(segment.data); + bool did_sync = false; while(track_offset < 6250) { uint8_t next_byte = (uint8_t)fgetc(file_); @@ -65,32 +66,46 @@ std::shared_ptr OricMFMDSK::get_uncached_track_at_position(unsigned int h switch(next_byte) { default: - encoder->add_byte(next_byte); - break; - - case 0xfe: // an ID synchronisation { - encoder->add_ID_address_mark(); - - for(int byte = 0; byte < 6; byte++) + encoder->add_byte(next_byte); + if(did_sync) { - last_header[byte] = (uint8_t)fgetc(file_); - encoder->add_byte(last_header[byte]); - track_offset++; - if(track_offset == 6250) break; + switch(next_byte) + { + default: break; + + case 0xfe: + for(int byte = 0; byte < 6; byte++) + { + last_header[byte] = (uint8_t)fgetc(file_); + encoder->add_byte(last_header[byte]); + track_offset++; + if(track_offset == 6250) break; + } + break; + + case 0xfb: + for(int byte = 0; byte < (128 << last_header[3]) + 2; byte++) + { + encoder->add_byte((uint8_t)fgetc(file_)); + track_offset++; + if(track_offset == 6250) break; + } + break; + } } + + did_sync = false; } break; - case 0xfb: // a data synchronisation - encoder->add_data_address_mark(); + case 0xa1: // a synchronisation mark that implies a sector or header coming + encoder->output_short(Storage::Encodings::MFM::MFMSync); + did_sync = true; + break; - for(int byte = 0; byte < (128 << last_header[3]) + 2; byte++) - { - encoder->add_byte((uint8_t)fgetc(file_)); - track_offset++; - if(track_offset == 6250) break; - } + case 0xc2: // an 'ordinary' synchronisation mark + encoder->output_short(Storage::Encodings::MFM::MFMIndexSync); break; } } diff --git a/Storage/Disk/Formats/SSD.cpp b/Storage/Disk/Formats/SSD.cpp index af4661dc1..0c7f23984 100644 --- a/Storage/Disk/Formats/SSD.cpp +++ b/Storage/Disk/Formats/SSD.cpp @@ -30,6 +30,11 @@ SSD::SSD(const char *file_name) : else if(track_count_ < 80) track_count_ = 80; } +SSD::~SSD() +{ + flush_updates(); +} + unsigned int SSD::get_head_position_count() { return track_count_; @@ -40,13 +45,22 @@ unsigned int SSD::get_head_count() return head_count_; } +bool SSD::get_is_read_only() +{ + return is_read_only_; +} + +long SSD::get_file_offset_for_position(unsigned int head, unsigned int position) +{ + return (position * head_count_ + head) * 256 * 10; +} + std::shared_ptr SSD::get_uncached_track_at_position(unsigned int head, unsigned int position) { std::shared_ptr track; if(head >= head_count_) return track; - long file_offset = (position * head_count_ + head) * 256 * 10; - fseek(file_, file_offset, SEEK_SET); + fseek(file_, get_file_offset_for_position(head, position), SEEK_SET); std::vector sectors; for(int sector = 0; sector < 10; sector++) @@ -57,9 +71,12 @@ std::shared_ptr SSD::get_uncached_track_at_position(unsigned int head, un new_sector.sector = (uint8_t)sector; new_sector.data.resize(256); - fread(&new_sector.data[0], 1, 256, file_); - if(feof(file_)) - break; + fread(new_sector.data.data(), 1, 256, file_); + + // zero out if this wasn't present in the disk image; it's still appropriate to put a sector + // on disk because one will have been placed during formatting, but there's no reason to leak + // information from outside the emulated machine's world + if(feof(file_)) memset(new_sector.data.data(), 0, 256); sectors.push_back(std::move(new_sector)); } @@ -68,3 +85,29 @@ std::shared_ptr SSD::get_uncached_track_at_position(unsigned int head, un return track; } + +void SSD::store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track, std::mutex &file_access_mutex) +{ + std::vector parsed_track; + Storage::Encodings::MFM::Parser parser(false, track); + for(unsigned int c = 0; c < 10; c++) + { + std::shared_ptr sector = parser.get_sector((uint8_t)position, (uint8_t)c); + if(sector) + { + parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end()); + } + else + { + // TODO: what's correct here? Warn the user that whatever has been written to the disk, + // it can no longer be stored as an SSD? If so, warn them by what route? + parsed_track.resize(parsed_track.size() + 256); + } + } + + std::lock_guard lock_guard(file_access_mutex); + long file_offset = get_file_offset_for_position(head, position); + ensure_file_is_at_least_length(file_offset); + fseek(file_, file_offset, SEEK_SET); + fwrite(parsed_track.data(), 1, parsed_track.size(), file_); +} diff --git a/Storage/Disk/Formats/SSD.hpp b/Storage/Disk/Formats/SSD.hpp index d75475370..a26432c10 100644 --- a/Storage/Disk/Formats/SSD.hpp +++ b/Storage/Disk/Formats/SSD.hpp @@ -27,6 +27,7 @@ class SSD: public Disk, public Storage::FileHolder { @throws ErrorNotSSD if the file doesn't appear to contain a .SSD format image. */ SSD(const char *file_name); + ~SSD(); enum { ErrorNotSSD, @@ -35,9 +36,13 @@ class SSD: public Disk, public Storage::FileHolder { // implemented to satisfy @c Disk unsigned int get_head_position_count(); unsigned int get_head_count(); + bool get_is_read_only(); private: + void store_updated_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr &track, std::mutex &file_access_mutex); std::shared_ptr get_uncached_track_at_position(unsigned int head, unsigned int position); + long get_file_offset_for_position(unsigned int head, unsigned int position); + unsigned int head_count_; unsigned int track_count_; }; diff --git a/Storage/Disk/PCMPatchedTrack.cpp b/Storage/Disk/PCMPatchedTrack.cpp index c25b00f3b..7befd23c1 100644 --- a/Storage/Disk/PCMPatchedTrack.cpp +++ b/Storage/Disk/PCMPatchedTrack.cpp @@ -20,6 +20,18 @@ PCMPatchedTrack::PCMPatchedTrack(std::shared_ptr underlying_track) : underlying_track_->seek_to(zero); } +PCMPatchedTrack::PCMPatchedTrack(const PCMPatchedTrack &original) +{ + underlying_track_.reset(original.underlying_track_->clone()); + periods_ = original.periods_; + active_period_ = periods_.begin(); +} + +Track *PCMPatchedTrack::clone() +{ + return new PCMPatchedTrack(*this); +} + void PCMPatchedTrack::add_segment(const Time &start_time, const PCMSegment &segment) { std::shared_ptr event_source(new PCMSegmentEventSource(segment)); @@ -43,8 +55,7 @@ void PCMPatchedTrack::add_segment(const Time &start_time, const PCMSegment &segm // the vector may have been resized, potentially invalidating active_period_ even if // the thing it pointed to is still the same thing. So work it out afresh. - active_period_ = periods_.begin(); - while(active_period_->start_time > current_time_) active_period_++; + insertion_error_ = current_time_ - seek_to(current_time_); } void PCMPatchedTrack::insert_period(const Period &period) @@ -155,13 +166,18 @@ Track::Event PCMPatchedTrack::get_next_event() else event = underlying_track_->get_next_event(); // see what time that gets us to. If it's still within the current period, return the found event - Time event_time = current_time_ + event.length - period_error; + Time event_time = current_time_ + event.length - period_error - insertion_error_; if(event_time < active_period_->end_time) { current_time_ = event_time; - event.length += extra_time - period_error; + // TODO: this is spelt out in three steps because times don't necessarily do the sensible + // thing when 'negative' if intermediate result get simplified in the meantime. So fix Time. + event.length += extra_time; + event.length -= period_error; + event.length -= insertion_error_; return event; } + insertion_error_.set_zero(); // otherwise move time back to the end of the outgoing period, accumulating the error into // extra_time, and advance the extra period @@ -196,15 +212,22 @@ Track::Event PCMPatchedTrack::get_next_event() Storage::Time PCMPatchedTrack::seek_to(const Time &time_since_index_hole) { - // start at the beginning and continue while segments start after the time sought + // start at the beginning and continue while segments end before reaching the time sought active_period_ = periods_.begin(); - while(active_period_->start_time > time_since_index_hole) active_period_++; + while(active_period_->end_time < time_since_index_hole) active_period_++; // allow whatever storage represents the period found to perform its seek if(active_period_->event_source) - return active_period_->event_source->seek_to(time_since_index_hole - active_period_->start_time) + active_period_->start_time; + current_time_ = active_period_->event_source->seek_to(time_since_index_hole - active_period_->start_time) + active_period_->start_time; else - return underlying_track_->seek_to(time_since_index_hole); + current_time_ = underlying_track_->seek_to(time_since_index_hole); + return current_time_; +} + +PCMPatchedTrack::Period::Period(const Period &original) : + start_time(original.start_time), end_time(original.end_time), segment_start_time(original.segment_start_time) +{ + if(original.event_source) event_source.reset(new PCMSegmentEventSource(*original.event_source)); } void PCMPatchedTrack::Period::push_start_to_time(const Storage::Time &new_start_time) diff --git a/Storage/Disk/PCMPatchedTrack.hpp b/Storage/Disk/PCMPatchedTrack.hpp index 83182b39b..23e7bd3a1 100644 --- a/Storage/Disk/PCMPatchedTrack.hpp +++ b/Storage/Disk/PCMPatchedTrack.hpp @@ -26,6 +26,11 @@ class PCMPatchedTrack: public Track { */ PCMPatchedTrack(std::shared_ptr underlying_track); + /*! + Copy constructor, for Track. + */ + PCMPatchedTrack(const PCMPatchedTrack &); + /*! Replaces whatever is currently on the track from @c start_position to @c start_position + segment length with the contents of @c segment. @@ -35,6 +40,7 @@ class PCMPatchedTrack: public Track { // To satisfy Storage::Disk::Track Event get_next_event(); Time seek_to(const Time &time_since_index_hole); + Track *clone(); private: std::shared_ptr underlying_track_; @@ -49,10 +55,11 @@ class PCMPatchedTrack: public Track { Period(const Time &start_time, const Time &end_time, const Time &segment_start_time, std::shared_ptr event_source) : start_time(start_time), end_time(end_time), segment_start_time(segment_start_time), event_source(event_source) {} + Period(const Period &); }; std::vector periods_; std::vector::iterator active_period_; - Time current_time_; + Time current_time_, insertion_error_; void insert_period(const Period &period); }; diff --git a/Storage/Disk/PCMSegment.cpp b/Storage/Disk/PCMSegment.cpp index ed4335e8e..59953ee6a 100644 --- a/Storage/Disk/PCMSegment.cpp +++ b/Storage/Disk/PCMSegment.cpp @@ -11,24 +11,34 @@ using namespace Storage::Disk; PCMSegmentEventSource::PCMSegmentEventSource(const PCMSegment &segment) : - segment_(segment) + segment_(new PCMSegment(segment)) { // add an extra bit of storage at the bottom if one is going to be needed; // events returned are going to be in integral multiples of the length of a bit // other than the very first and very last which will include a half bit length - if(segment_.length_of_a_bit.length&1) + if(segment_->length_of_a_bit.length&1) { - segment_.length_of_a_bit.length <<= 1; - segment_.length_of_a_bit.clock_rate <<= 1; + segment_->length_of_a_bit.length <<= 1; + segment_->length_of_a_bit.clock_rate <<= 1; } // load up the clock rate once only - next_event_.length.clock_rate = segment_.length_of_a_bit.clock_rate; + next_event_.length.clock_rate = segment_->length_of_a_bit.clock_rate; // set initial conditions reset(); } +PCMSegmentEventSource::PCMSegmentEventSource(const PCMSegmentEventSource &original) +{ + // share underlying data with the original + segment_ = original.segment_; + + // load up the clock rate and set initial conditions + next_event_.length.clock_rate = segment_->length_of_a_bit.clock_rate; + reset(); +} + void PCMSegmentEventSource::reset() { // start with the first bit to be considered the zeroth, and assume that it'll be @@ -45,15 +55,15 @@ Storage::Disk::Track::Event PCMSegmentEventSource::get_next_event() // if starting from the beginning, pull half a bit backward, as if the initial bit // is set, it should be in the centre of its window - next_event_.length.length = bit_pointer_ ? 0 : -(segment_.length_of_a_bit.length >> 1); + next_event_.length.length = bit_pointer_ ? 0 : -(segment_->length_of_a_bit.length >> 1); // search for the next bit that is set, if any - const uint8_t *segment_data = segment_.data.data(); - while(bit_pointer_ < segment_.number_of_bits) + const uint8_t *segment_data = segment_->data.data(); + while(bit_pointer_ < segment_->number_of_bits) { int bit = segment_data[bit_pointer_ >> 3] & (0x80 >> (bit_pointer_&7)); bit_pointer_++; // so this always points one beyond the most recent bit returned - next_event_.length.length += segment_.length_of_a_bit.length; + next_event_.length.length += segment_->length_of_a_bit.length; // if this bit is set, return the event if(bit) return next_event_; @@ -66,9 +76,9 @@ Storage::Disk::Track::Event PCMSegmentEventSource::get_next_event() // allow an extra half bit's length to run from the position of the potential final transition // event to the end of the segment. Otherwise don't allow any extra time, as it's already // been consumed - if(initial_bit_pointer <= segment_.number_of_bits) + if(initial_bit_pointer <= segment_->number_of_bits) { - next_event_.length.length += (segment_.length_of_a_bit.length >> 1); + next_event_.length.length += (segment_->length_of_a_bit.length >> 1); bit_pointer_++; } return next_event_; @@ -76,7 +86,7 @@ Storage::Disk::Track::Event PCMSegmentEventSource::get_next_event() Storage::Time PCMSegmentEventSource::get_length() { - return segment_.length_of_a_bit * segment_.number_of_bits; + return segment_->length_of_a_bit * segment_->number_of_bits; } Storage::Time PCMSegmentEventSource::seek_to(const Time &time_from_start) @@ -86,7 +96,7 @@ Storage::Time PCMSegmentEventSource::seek_to(const Time &time_from_start) if(time_from_start >= length) { next_event_.type = Track::Event::IndexHole; - bit_pointer_ = segment_.number_of_bits+1; + bit_pointer_ = segment_->number_of_bits+1; return length; } @@ -94,7 +104,7 @@ Storage::Time PCMSegmentEventSource::seek_to(const Time &time_from_start) next_event_.type = Track::Event::FluxTransition; // test for requested time being before the first bit - Time half_bit_length = segment_.length_of_a_bit; + Time half_bit_length = segment_->length_of_a_bit; half_bit_length.length >>= 1; if(time_from_start < half_bit_length) { @@ -107,8 +117,8 @@ Storage::Time PCMSegmentEventSource::seek_to(const Time &time_from_start) // bit_pointer_ always records _the next bit_ that might trigger an event, // so should be one beyond the one reached by a seek. Time relative_time = time_from_start - half_bit_length; - bit_pointer_ = 1 + (relative_time / segment_.length_of_a_bit).get_unsigned_int(); + bit_pointer_ = 1 + (relative_time / segment_->length_of_a_bit).get_unsigned_int(); // map up to the correct amount of time - return half_bit_length + segment_.length_of_a_bit * (unsigned int)(bit_pointer_ - 1); + return half_bit_length + segment_->length_of_a_bit * (unsigned int)(bit_pointer_ - 1); } diff --git a/Storage/Disk/PCMSegment.hpp b/Storage/Disk/PCMSegment.hpp index 4abc84293..c753d194c 100644 --- a/Storage/Disk/PCMSegment.hpp +++ b/Storage/Disk/PCMSegment.hpp @@ -42,7 +42,13 @@ class PCMSegmentEventSource { Constructs a @c PCMSegmentEventSource that will derive events from @c segment. The event source is initially @c reset. */ - PCMSegmentEventSource(const PCMSegment &segment); + PCMSegmentEventSource(const PCMSegment &); + + /*! + Copy constructor; produces a segment event source with the same underlying segment + but a unique pointer into it. + */ + PCMSegmentEventSource(const PCMSegmentEventSource &); /*! @returns the next event that will occur in this event stream. @@ -69,7 +75,7 @@ class PCMSegmentEventSource { Time get_length(); private: - PCMSegment segment_; + std::shared_ptr segment_; size_t bit_pointer_; Track::Event next_event_; }; diff --git a/Storage/Disk/PCMTrack.cpp b/Storage/Disk/PCMTrack.cpp index f89967ef4..9a8067f31 100644 --- a/Storage/Disk/PCMTrack.cpp +++ b/Storage/Disk/PCMTrack.cpp @@ -47,6 +47,16 @@ PCMTrack::PCMTrack(const PCMSegment &segment) : PCMTrack() segment_event_sources_.emplace_back(length_adjusted_segment); } +PCMTrack::PCMTrack(const PCMTrack &original) : PCMTrack() +{ + segment_event_sources_ = original.segment_event_sources_; +} + +Track *PCMTrack::clone() +{ + return new PCMTrack(*this); +} + Track::Event PCMTrack::get_next_event() { // ask the current segment for a new event diff --git a/Storage/Disk/PCMTrack.hpp b/Storage/Disk/PCMTrack.hpp index 255bed80f..ad1d7093a 100644 --- a/Storage/Disk/PCMTrack.hpp +++ b/Storage/Disk/PCMTrack.hpp @@ -28,17 +28,23 @@ class PCMTrack: public Track { /*! Creates a @c PCMTrack consisting of multiple segments of data, permitting multiple clock rates. */ - PCMTrack(const std::vector &segments); + PCMTrack(const std::vector &); /*! Creates a @c PCMTrack consisting of a single continuous run of data, implying a constant clock rate. The segment's @c length_of_a_bit will be ignored and therefore need not be filled in. */ - PCMTrack(const PCMSegment &segment); + PCMTrack(const PCMSegment &); + + /*! + Copy constructor; required for Tracks in order to support modifiable disks. + */ + PCMTrack(const PCMTrack &); // as per @c Track Event get_next_event(); Time seek_to(const Time &time_since_index_hole); + Track *clone(); private: // storage for the segments that describe this track diff --git a/Storage/FileHolder.cpp b/Storage/FileHolder.cpp index b5bd16bb3..1395da998 100644 --- a/Storage/FileHolder.cpp +++ b/Storage/FileHolder.cpp @@ -20,7 +20,13 @@ FileHolder::~FileHolder() FileHolder::FileHolder(const char *file_name) : file_(nullptr) { stat(file_name, &file_stats_); - file_ = fopen(file_name, "rb"); + is_read_only_ = false; + file_ = fopen(file_name, "rb+"); + if(!file_) + { + is_read_only_ = true; + file_ = fopen(file_name, "rb"); + } if(!file_) throw ErrorCantOpen; } @@ -79,3 +85,16 @@ uint16_t FileHolder::fgetc16be() return result; } + +void FileHolder::ensure_file_is_at_least_length(long length) +{ + fseek(file_, 0, SEEK_END); + long bytes_to_write = length - ftell(file_); + if(bytes_to_write > 0) + { + uint8_t *empty = new uint8_t[bytes_to_write]; + memset(empty, 0, bytes_to_write); + fwrite(empty, sizeof(uint8_t), (size_t)bytes_to_write, file_); + delete[] empty; + } +} diff --git a/Storage/FileHolder.hpp b/Storage/FileHolder.hpp index ef853a844..458927608 100644 --- a/Storage/FileHolder.hpp +++ b/Storage/FileHolder.hpp @@ -65,8 +65,15 @@ class FileHolder { */ uint16_t fgetc16be(); + /*! + Ensures the file is at least @c length bytes long, appending 0s until it is + if necessary. + */ + void ensure_file_is_at_least_length(long length); + FILE *file_; struct stat file_stats_; + bool is_read_only_; }; } diff --git a/Storage/Storage.hpp b/Storage/Storage.hpp index a0456870f..ea05faebc 100644 --- a/Storage/Storage.hpp +++ b/Storage/Storage.hpp @@ -89,30 +89,78 @@ struct Time { inline Time operator + (const Time &other) const { - uint64_t result_length = (uint64_t)length * (uint64_t)other.clock_rate + (uint64_t)other.length * (uint64_t)clock_rate; - uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate; + if(!other.length) return *this; + + uint64_t result_length; + uint64_t result_clock_rate; + if(clock_rate == other.clock_rate) + { + result_length = (uint64_t)length + (uint64_t)other.length; + result_clock_rate = clock_rate; + } + else + { + result_length = (uint64_t)length * (uint64_t)other.clock_rate + (uint64_t)other.length * (uint64_t)clock_rate; + result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate; + } return Time(result_length, result_clock_rate); } inline Time &operator += (const Time &other) { - uint64_t result_length = (uint64_t)length * (uint64_t)other.clock_rate + (uint64_t)other.length * (uint64_t)clock_rate; - uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate; + if(!other.length) return *this; + + uint64_t result_length; + uint64_t result_clock_rate; + if(clock_rate == other.clock_rate) + { + result_length = (uint64_t)length + (uint64_t)other.length; + result_clock_rate = (uint64_t)clock_rate; + } + else + { + result_length = (uint64_t)length * (uint64_t)other.clock_rate + (uint64_t)other.length * (uint64_t)clock_rate; + result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate; + } install_result(result_length, result_clock_rate); return *this; } inline Time operator - (const Time &other) const { - uint64_t result_length = (uint64_t)length * (uint64_t)other.clock_rate - (uint64_t)other.length * (uint64_t)clock_rate; - uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate; + if(!other.length) return *this; + + uint64_t result_length; + uint64_t result_clock_rate; + if(clock_rate == other.clock_rate) + { + result_length = (uint64_t)length - (uint64_t)other.length; + result_clock_rate = clock_rate; + } + else + { + result_length = (uint64_t)length * (uint64_t)other.clock_rate - (uint64_t)other.length * (uint64_t)clock_rate; + result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate; + } return Time(result_length, result_clock_rate); } inline Time operator -= (const Time &other) { - uint64_t result_length = (uint64_t)length * (uint64_t)other.clock_rate - (uint64_t)other.length * (uint64_t)clock_rate; - uint64_t result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate; + if(!other.length) return *this; + + uint64_t result_length; + uint64_t result_clock_rate; + if(clock_rate == other.clock_rate) + { + result_length = (uint64_t)length - (uint64_t)other.length; + result_clock_rate = (uint64_t)clock_rate; + } + else + { + result_length = (uint64_t)length * (uint64_t)other.clock_rate - (uint64_t)other.length * (uint64_t)clock_rate; + result_clock_rate = (uint64_t)clock_rate * (uint64_t)other.clock_rate; + } install_result(result_length, result_clock_rate); return *this; } @@ -198,6 +246,12 @@ struct Time { inline void install_result(uint64_t long_length, uint64_t long_clock_rate) { // TODO: switch to appropriate values if the result is too large or small to fit, even with trimmed accuracy. + if(!long_length) + { + length = 0; + clock_rate = 1; + return; + } while(!(long_length&1) && !(long_clock_rate&1)) {