From 631f6305499abd99c29c109824ed42679f01c322 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 24 Sep 2017 20:31:19 -0400 Subject: [PATCH] Severs the MFM parser from the overweight single MFM.hpp. --- .../Clock Signal.xcodeproj/project.pbxproj | 12 +- StaticAnalyser/Acorn/Disk.cpp | 2 +- StaticAnalyser/AmstradCPC/StaticAnalyser.cpp | 2 +- Storage/Disk/DiskImage/Formats/AcornADF.cpp | 1 + Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp | 1 + Storage/Disk/DiskImage/Formats/SSD.cpp | 1 + Storage/Disk/Encodings/MFM/MFM.cpp | 297 ----------------- Storage/Disk/Encodings/MFM/MFM.hpp | 68 +--- Storage/Disk/Encodings/MFM/Parser.cpp | 314 ++++++++++++++++++ Storage/Disk/Encodings/MFM/Parser.hpp | 75 +++++ Storage/Disk/Encodings/MFM/Sector.hpp | 38 +++ Storage/Disk/Parsers/CPM.cpp | 2 +- 12 files changed, 444 insertions(+), 369 deletions(-) create mode 100644 Storage/Disk/Encodings/MFM/Parser.cpp create mode 100644 Storage/Disk/Encodings/MFM/Parser.hpp create mode 100644 Storage/Disk/Encodings/MFM/Sector.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index c6b02dab1..b26d8d425 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -96,6 +96,7 @@ 4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */; }; 4B7136861F78724F008B8ED9 /* MFM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136841F78724F008B8ED9 /* MFM.cpp */; }; 4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136871F78725F008B8ED9 /* Shifter.cpp */; }; + 4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368C1F788112008B8ED9 /* Parser.cpp */; }; 4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; }; 4B79E4441E3AF38600141F11 /* cassette.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4411E3AF38600141F11 /* cassette.png */; }; 4B79E4451E3AF38600141F11 /* floppy35.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4421E3AF38600141F11 /* floppy35.png */; }; @@ -653,6 +654,9 @@ 4B7136871F78725F008B8ED9 /* Shifter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Shifter.cpp; sourceTree = ""; }; 4B7136881F78725F008B8ED9 /* Shifter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Shifter.hpp; sourceTree = ""; }; 4B71368A1F787349008B8ED9 /* Constants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Constants.hpp; sourceTree = ""; }; + 4B71368B1F7880D1008B8ED9 /* Sector.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sector.hpp; sourceTree = ""; }; + 4B71368C1F788112008B8ED9 /* Parser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Parser.cpp; sourceTree = ""; }; + 4B71368D1F788112008B8ED9 /* Parser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Parser.hpp; sourceTree = ""; }; 4B77069C1EC904570053B588 /* Z80.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Z80.hpp; path = Z80/Z80.hpp; sourceTree = ""; }; 4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = ""; }; 4B7913CB1DFCD80E00175A82 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Electron/Video.hpp; sourceTree = ""; }; @@ -1659,10 +1663,13 @@ isa = PBXGroup; children = ( 4B7136841F78724F008B8ED9 /* MFM.cpp */, - 4B7136851F78724F008B8ED9 /* MFM.hpp */, + 4B71368C1F788112008B8ED9 /* Parser.cpp */, 4B7136871F78725F008B8ED9 /* Shifter.cpp */, - 4B7136881F78725F008B8ED9 /* Shifter.hpp */, 4B71368A1F787349008B8ED9 /* Constants.hpp */, + 4B7136851F78724F008B8ED9 /* MFM.hpp */, + 4B71368D1F788112008B8ED9 /* Parser.hpp */, + 4B71368B1F7880D1008B8ED9 /* Sector.hpp */, + 4B7136881F78725F008B8ED9 /* Shifter.hpp */, ); name = MFM; path = Encodings/MFM; @@ -2925,6 +2932,7 @@ 4B8334841F5DA0360097E338 /* Z80Storage.cpp in Sources */, 4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */, 4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */, + 4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */, 4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */, 4B14978B1EE4AC5E00CE2596 /* StaticAnalyser.cpp in Sources */, 4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */, diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp index 9cbb215dd..26942cfe0 100644 --- a/StaticAnalyser/Acorn/Disk.cpp +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -8,7 +8,7 @@ #include "Disk.hpp" #include "../../Storage/Disk/Controller/DiskController.hpp" -#include "../../Storage/Disk/Encodings/MFM/MFM.hpp" +#include "../../Storage/Disk/Encodings/MFM/Parser.hpp" #include "../../NumberTheory/CRC.hpp" #include diff --git a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp index 779bc4c75..2926ec9a6 100644 --- a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp +++ b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp @@ -9,7 +9,7 @@ #include "StaticAnalyser.hpp" #include "../../Storage/Disk/Parsers/CPM.hpp" -#include "../../Storage/Disk/Encodings/MFM/MFM.hpp" +#include "../../Storage/Disk/Encodings/MFM/Parser.hpp" static bool strcmp_insensitive(const char *a, const char *b) { if(strlen(a) != strlen(b)) return false; diff --git a/Storage/Disk/DiskImage/Formats/AcornADF.cpp b/Storage/Disk/DiskImage/Formats/AcornADF.cpp index 6066699d3..9bfba8286 100644 --- a/Storage/Disk/DiskImage/Formats/AcornADF.cpp +++ b/Storage/Disk/DiskImage/Formats/AcornADF.cpp @@ -9,6 +9,7 @@ #include "AcornADF.hpp" #include +#include "../../Encodings/MFM/Parser.hpp" #include "../../Encodings/MFM/MFM.hpp" namespace { diff --git a/Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp b/Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp index db18a8b15..d494b00d4 100644 --- a/Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp +++ b/Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp @@ -10,6 +10,7 @@ #include "../../Track/PCMTrack.hpp" #include "../../Encodings/MFM/MFM.hpp" +#include "../../Encodings/MFM/Parser.hpp" using namespace Storage::Disk; diff --git a/Storage/Disk/DiskImage/Formats/SSD.cpp b/Storage/Disk/DiskImage/Formats/SSD.cpp index a3c9adff4..c4557c9d1 100644 --- a/Storage/Disk/DiskImage/Formats/SSD.cpp +++ b/Storage/Disk/DiskImage/Formats/SSD.cpp @@ -9,6 +9,7 @@ #include "SSD.hpp" #include "../../Encodings/MFM/MFM.hpp" +#include "../../Encodings/MFM/Parser.hpp" using namespace Storage::Disk; diff --git a/Storage/Disk/Encodings/MFM/MFM.cpp b/Storage/Disk/Encodings/MFM/MFM.cpp index d4f2096da..64f614a77 100644 --- a/Storage/Disk/Encodings/MFM/MFM.cpp +++ b/Storage/Disk/Encodings/MFM/MFM.cpp @@ -234,301 +234,4 @@ std::unique_ptr Storage::Encodings::MFM::GetFMEncoder(std::vectorset_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(std::make_shared>(track)); -} - -void Parser::seek_to_track(uint8_t track) { - 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++) drive_->step(direction); - } -} - -std::shared_ptr Parser::get_sector(uint8_t head, uint8_t track, uint8_t sector) { - // Switch head and track if necessary. - if(head_ != head) { - drive_->set_head(head); - } - seek_to_track(track); - int track_index = get_index(head, track, 0); - - // Populate the sector cache if it's not already populated by asking for sectors unless and until - // one is returned that has already been seen. - if(decoded_tracks_.find(track_index) == decoded_tracks_.end()) { - std::shared_ptr first_sector = get_next_sector(); - std::set visited_sectors; - if(first_sector) { - while(1) { - std::shared_ptr next_sector = get_next_sector(); - if(next_sector) { - if(visited_sectors.find(next_sector->sector) != visited_sectors.end()) { - break; - } - visited_sectors.insert(next_sector->sector); - } - } - } - decoded_tracks_.insert(track_index); - } - - // Check cache for sector. - int index = get_index(head, track, sector); - auto cached_sector = sectors_by_index_.find(index); - if(cached_sector != sectors_by_index_.end()) { - return cached_sector->second; - } - - // If it wasn't found, it doesn't exist. - return nullptr; -} - -std::vector Parser::get_track(uint8_t track) { - seek_to_track(track); - return get_track(); -} - -void Parser::process_input_bit(int value) { - shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0xffff; - bit_count_++; -} - -void Parser::process_index_hole() { - index_count_++; -} - -uint8_t Parser::get_byte_for_shift_value(uint16_t value) { - return (uint8_t)( - ((value&0x0001) >> 0) | - ((value&0x0004) >> 1) | - ((value&0x0010) >> 2) | - ((value&0x0040) >> 3) | - ((value&0x0100) >> 4) | - ((value&0x0400) >> 5) | - ((value&0x1000) >> 6) | - ((value&0x4000) >> 7)); -} - -uint8_t Parser::get_next_byte() { - bit_count_ = 0; - // Archetypal MFM is 500,000 bps given that the drive has an RPM of 300. Clock rate was - // specified at 4,000,000. So that's an idealised 8 cycles per bit, Jump ahead 14 - // times that... - run_for(Cycles(14 * 8)); - - // ... and proceed at half-idealised-bit intervals to get the next bit. Then proceed very gingerly indeed. - while(bit_count_ < 15) run_for(Cycles(4)); - while(bit_count_ < 16) run_for(Cycles(2)); - - uint8_t byte = get_byte_for_shift_value((uint16_t)shift_register_); - crc_generator_.add(byte); - return byte; -} - -std::vector Parser::get_track() { - std::vector result; - int distance_until_permissible_sync = 0; - uint8_t last_id[6] = {0, 0, 0, 0, 0, 0}; - int last_id_pointer = 0; - bool next_is_type = false; - - // align to the next index hole - index_count_ = 0; - while(!index_count_) run_for(Cycles(1)); - - // capture every other bit until the next index hole - index_count_ = 0; - while(1) { - // wait until either another bit or the index hole arrives - bit_count_ = 0; - bool found_sync = false; - while(!index_count_ && !found_sync && bit_count_ < 16) { - int previous_bit_count = bit_count_; - run_for(Cycles(1)); - - if(!distance_until_permissible_sync && bit_count_ != previous_bit_count) { - uint16_t low_shift_register = (shift_register_&0xffff); - if(is_mfm_) { - found_sync = (low_shift_register == MFMIndexSync) || (low_shift_register == MFMSync); - } else { - found_sync = - (low_shift_register == FMIndexAddressMark) || - (low_shift_register == FMIDAddressMark) || - (low_shift_register == FMDataAddressMark) || - (low_shift_register == FMDeletedDataAddressMark); - } - } - } - - // if that was the index hole then finish - if(index_count_) { - if(bit_count_) result.push_back(get_byte_for_shift_value((uint16_t)(shift_register_ << (16 - bit_count_)))); - break; - } - - // store whatever the current byte is - uint8_t byte_value = get_byte_for_shift_value((uint16_t)shift_register_); - result.push_back(byte_value); - if(last_id_pointer < 6) last_id[last_id_pointer++] = byte_value; - - // if no syncs are permissible here, decrement the waiting period and perform no further contemplation - bool found_id = false, found_data = false; - if(distance_until_permissible_sync) { - distance_until_permissible_sync--; - } else { - if(found_sync) { - if(is_mfm_) { - next_is_type = true; - } else { - switch(shift_register_&0xffff) { - case FMIDAddressMark: found_id = true; break; - case FMDataAddressMark: - case FMDeletedDataAddressMark: found_data = true; break; - } - } - } else if(next_is_type) { - switch(byte_value) { - case IDAddressByte: found_id = true; break; - case DataAddressByte: - case DeletedDataAddressByte: found_data = true; break; - } - } - } - - if(found_id) { - distance_until_permissible_sync = 6; - last_id_pointer = 0; - } - - if(found_data) { - distance_until_permissible_sync = 128 << last_id[3]; - } - } - - return result; -} - -std::shared_ptr Parser::get_next_sector() { - std::shared_ptr sector(new 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_ == MFMSync) { - uint8_t mark = get_next_byte(); - if(mark == IDAddressByte) { - crc_generator_.set_value(MFMPostSyncCRCValue); - id_found = true; - break; - } - } - } else { - if(shift_register_ == FMIDAddressMark) { - crc_generator_.reset(); - id_found = true; - } - } - if(index_count_ >= 2) return nullptr; - } - - crc_generator_.add(IDAddressByte); - sector->track = get_next_byte(); - sector->side = get_next_byte(); - sector->sector = get_next_byte(); - sector->size = get_next_byte(); - uint16_t header_crc = crc_generator_.get_value(); - if((header_crc >> 8) != get_next_byte()) sector->has_header_crc_error = true; - if((header_crc & 0xff) != get_next_byte()) sector->has_header_crc_error = true; - - // look for data mark - bool data_found = false; - while(!data_found) { - run_for(Cycles(1)); - if(is_mfm_) { - while(shift_register_ == MFMSync) { - uint8_t mark = get_next_byte(); - if(mark == DataAddressByte) { - crc_generator_.set_value(MFMPostSyncCRCValue); - data_found = true; - break; - } - if(mark == IDAddressByte) return nullptr; - } - } else { - if(shift_register_ == FMDataAddressMark) { - crc_generator_.reset(); - data_found = true; - } - if(shift_register_ == FMIDAddressMark) return nullptr; - } - if(index_count_ >= 2) return nullptr; - } - crc_generator_.add(DataAddressByte); - - size_t data_size = (size_t)(128 << sector->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()) sector->has_data_crc_error = true; - if((data_crc & 0xff) != get_next_byte()) sector->has_data_crc_error = true; - - // Put this sector into the cache. - int index = get_index(head_, track_, sector->sector); - sectors_by_index_[index] = sector; - - 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 nullptr; - 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; - } -} - -int Parser::get_index(uint8_t head, uint8_t track, uint8_t sector) { - return head | (track << 8) | (sector << 16); -} diff --git a/Storage/Disk/Encodings/MFM/MFM.hpp b/Storage/Disk/Encodings/MFM/MFM.hpp index 2431e4f97..77b6c72b0 100644 --- a/Storage/Disk/Encodings/MFM/MFM.hpp +++ b/Storage/Disk/Encodings/MFM/MFM.hpp @@ -12,29 +12,14 @@ #include #include #include "Constants.hpp" +#include "Sector.hpp" #include "../../Disk.hpp" -#include "../../Controller/DiskController.hpp" #include "../../../../NumberTheory/CRC.hpp" namespace Storage { namespace Encodings { namespace MFM { -/*! - Represents a single [M]FM sector, identified by its track, side and sector records, a blob of data - and a few extra flags of metadata. -*/ -struct Sector { - uint8_t track, side, sector, size; - std::vector data; - - bool has_data_crc_error; - bool has_header_crc_error; - bool is_deleted; - - Sector() : track(0), side(0), sector(0), size(0), has_data_crc_error(false), has_header_crc_error(false), is_deleted(false) {} -}; - extern const size_t DefaultSectorGapLength; /*! Converts a vector of sectors into a properly-encoded MFM track. @@ -77,57 +62,6 @@ 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 head, uint8_t track, uint8_t sector); - - /*! - Attempts to read the track at @c track, starting from the index hole. - - Decodes data bits only; clocks are omitted. Synchronisation values begin a new - byte. If a synchronisation value begins partway through a byte then - synchronisation-contributing bits will appear both in the preceding byte and - in the next. - - @returns a vector of data found. - */ - std::vector get_track(uint8_t track); - - private: - Parser(bool is_mfm); - - std::shared_ptr drive_; - unsigned int shift_register_; - int index_count_; - uint8_t track_, head_; - int bit_count_; - NumberTheory::CRC16 crc_generator_; - bool is_mfm_; - - void seek_to_track(uint8_t track); - void process_input_bit(int value); - void process_index_hole(); - uint8_t get_next_byte(); - - uint8_t get_byte_for_shift_value(uint16_t value); - - std::shared_ptr get_next_sector(); - std::shared_ptr get_sector(uint8_t sector); - std::vector get_track(); - - std::map> sectors_by_index_; - std::set decoded_tracks_; - int get_index(uint8_t head, uint8_t track, uint8_t sector); -}; - } } diff --git a/Storage/Disk/Encodings/MFM/Parser.cpp b/Storage/Disk/Encodings/MFM/Parser.cpp new file mode 100644 index 000000000..d35a9439d --- /dev/null +++ b/Storage/Disk/Encodings/MFM/Parser.cpp @@ -0,0 +1,314 @@ +// +// Parser.cpp +// Clock Signal +// +// Created by Thomas Harte on 24/09/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#include "Parser.hpp" + +#include "Constants.hpp" +#include "../../DiskImage/DiskImage.hpp" +#include "../../SingleTrackDisk/SingleTrackDisk.hpp" + +using namespace Storage::Encodings::MFM; + +Parser::Parser(bool is_mfm) : + Storage::Disk::Controller(4000000), + crc_generator_(0x1021, 0xffff), + shift_register_(0), is_mfm_(is_mfm), + track_(0), head_(0) { + 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(4000000, 300, 2)); + set_drive(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(std::make_shared>(track)); +} + +void Parser::seek_to_track(uint8_t track) { + 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++) drive_->step(direction); + } +} + +std::shared_ptr Parser::get_sector(uint8_t head, uint8_t track, uint8_t sector) { + // Switch head and track if necessary. + if(head_ != head) { + drive_->set_head(head); + } + seek_to_track(track); + int track_index = get_index(head, track, 0); + + // Populate the sector cache if it's not already populated by asking for sectors unless and until + // one is returned that has already been seen. + if(decoded_tracks_.find(track_index) == decoded_tracks_.end()) { + std::shared_ptr first_sector = get_next_sector(); + std::set visited_sectors; + if(first_sector) { + while(1) { + std::shared_ptr next_sector = get_next_sector(); + if(next_sector) { + if(visited_sectors.find(next_sector->sector) != visited_sectors.end()) { + break; + } + visited_sectors.insert(next_sector->sector); + } + } + } + decoded_tracks_.insert(track_index); + } + + // Check cache for sector. + int index = get_index(head, track, sector); + auto cached_sector = sectors_by_index_.find(index); + if(cached_sector != sectors_by_index_.end()) { + return cached_sector->second; + } + + // If it wasn't found, it doesn't exist. + return nullptr; +} + +std::vector Parser::get_track(uint8_t track) { + seek_to_track(track); + return get_track(); +} + +void Parser::process_input_bit(int value) { + shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0xffff; + bit_count_++; +} + +void Parser::process_index_hole() { + index_count_++; +} + +uint8_t Parser::get_byte_for_shift_value(uint16_t value) { + return (uint8_t)( + ((value&0x0001) >> 0) | + ((value&0x0004) >> 1) | + ((value&0x0010) >> 2) | + ((value&0x0040) >> 3) | + ((value&0x0100) >> 4) | + ((value&0x0400) >> 5) | + ((value&0x1000) >> 6) | + ((value&0x4000) >> 7)); +} + +uint8_t Parser::get_next_byte() { + bit_count_ = 0; + // Archetypal MFM is 500,000 bps given that the drive has an RPM of 300. Clock rate was + // specified at 4,000,000. So that's an idealised 8 cycles per bit, Jump ahead 14 + // times that... + run_for(Cycles(14 * 8)); + + // ... and proceed at half-idealised-bit intervals to get the next bit. Then proceed very gingerly indeed. + while(bit_count_ < 15) run_for(Cycles(4)); + while(bit_count_ < 16) run_for(Cycles(2)); + + uint8_t byte = get_byte_for_shift_value((uint16_t)shift_register_); + crc_generator_.add(byte); + return byte; +} + +std::vector Parser::get_track() { + std::vector result; + int distance_until_permissible_sync = 0; + uint8_t last_id[6] = {0, 0, 0, 0, 0, 0}; + int last_id_pointer = 0; + bool next_is_type = false; + + // align to the next index hole + index_count_ = 0; + while(!index_count_) run_for(Cycles(1)); + + // capture every other bit until the next index hole + index_count_ = 0; + while(1) { + // wait until either another bit or the index hole arrives + bit_count_ = 0; + bool found_sync = false; + while(!index_count_ && !found_sync && bit_count_ < 16) { + int previous_bit_count = bit_count_; + run_for(Cycles(1)); + + if(!distance_until_permissible_sync && bit_count_ != previous_bit_count) { + uint16_t low_shift_register = (shift_register_&0xffff); + if(is_mfm_) { + found_sync = (low_shift_register == MFMIndexSync) || (low_shift_register == MFMSync); + } else { + found_sync = + (low_shift_register == FMIndexAddressMark) || + (low_shift_register == FMIDAddressMark) || + (low_shift_register == FMDataAddressMark) || + (low_shift_register == FMDeletedDataAddressMark); + } + } + } + + // if that was the index hole then finish + if(index_count_) { + if(bit_count_) result.push_back(get_byte_for_shift_value((uint16_t)(shift_register_ << (16 - bit_count_)))); + break; + } + + // store whatever the current byte is + uint8_t byte_value = get_byte_for_shift_value((uint16_t)shift_register_); + result.push_back(byte_value); + if(last_id_pointer < 6) last_id[last_id_pointer++] = byte_value; + + // if no syncs are permissible here, decrement the waiting period and perform no further contemplation + bool found_id = false, found_data = false; + if(distance_until_permissible_sync) { + distance_until_permissible_sync--; + } else { + if(found_sync) { + if(is_mfm_) { + next_is_type = true; + } else { + switch(shift_register_&0xffff) { + case FMIDAddressMark: found_id = true; break; + case FMDataAddressMark: + case FMDeletedDataAddressMark: found_data = true; break; + } + } + } else if(next_is_type) { + switch(byte_value) { + case IDAddressByte: found_id = true; break; + case DataAddressByte: + case DeletedDataAddressByte: found_data = true; break; + } + } + } + + if(found_id) { + distance_until_permissible_sync = 6; + last_id_pointer = 0; + } + + if(found_data) { + distance_until_permissible_sync = 128 << last_id[3]; + } + } + + return result; +} + +std::shared_ptr Parser::get_next_sector() { + std::shared_ptr sector(new 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_ == MFMSync) { + uint8_t mark = get_next_byte(); + if(mark == IDAddressByte) { + crc_generator_.set_value(MFMPostSyncCRCValue); + id_found = true; + break; + } + } + } else { + if(shift_register_ == FMIDAddressMark) { + crc_generator_.reset(); + id_found = true; + } + } + if(index_count_ >= 2) return nullptr; + } + + crc_generator_.add(IDAddressByte); + sector->track = get_next_byte(); + sector->side = get_next_byte(); + sector->sector = get_next_byte(); + sector->size = get_next_byte(); + uint16_t header_crc = crc_generator_.get_value(); + if((header_crc >> 8) != get_next_byte()) sector->has_header_crc_error = true; + if((header_crc & 0xff) != get_next_byte()) sector->has_header_crc_error = true; + + // look for data mark + bool data_found = false; + while(!data_found) { + run_for(Cycles(1)); + if(is_mfm_) { + while(shift_register_ == MFMSync) { + uint8_t mark = get_next_byte(); + if(mark == DataAddressByte) { + crc_generator_.set_value(MFMPostSyncCRCValue); + data_found = true; + break; + } + if(mark == IDAddressByte) return nullptr; + } + } else { + if(shift_register_ == FMDataAddressMark) { + crc_generator_.reset(); + data_found = true; + } + if(shift_register_ == FMIDAddressMark) return nullptr; + } + if(index_count_ >= 2) return nullptr; + } + crc_generator_.add(DataAddressByte); + + size_t data_size = (size_t)(128 << sector->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()) sector->has_data_crc_error = true; + if((data_crc & 0xff) != get_next_byte()) sector->has_data_crc_error = true; + + // Put this sector into the cache. + int index = get_index(head_, track_, sector->sector); + sectors_by_index_[index] = sector; + + 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 nullptr; + 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; + } +} + +int Parser::get_index(uint8_t head, uint8_t track, uint8_t sector) { + return head | (track << 8) | (sector << 16); +} diff --git a/Storage/Disk/Encodings/MFM/Parser.hpp b/Storage/Disk/Encodings/MFM/Parser.hpp new file mode 100644 index 000000000..d71f5c096 --- /dev/null +++ b/Storage/Disk/Encodings/MFM/Parser.hpp @@ -0,0 +1,75 @@ +// +// Parser.hpp +// Clock Signal +// +// Created by Thomas Harte on 24/09/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef Parser_hpp +#define Parser_hpp + +#include "Sector.hpp" +#include "../../Controller/DiskController.hpp" +#include "../../../../NumberTheory/CRC.hpp" + +namespace Storage { +namespace Encodings { +namespace MFM { + +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 head, uint8_t track, uint8_t sector); + + /*! + Attempts to read the track at @c track, starting from the index hole. + + Decodes data bits only; clocks are omitted. Synchronisation values begin a new + byte. If a synchronisation value begins partway through a byte then + synchronisation-contributing bits will appear both in the preceding byte and + in the next. + + @returns a vector of data found. + */ + std::vector get_track(uint8_t track); + + private: + Parser(bool is_mfm); + + std::shared_ptr drive_; + unsigned int shift_register_; + int index_count_; + uint8_t track_, head_; + int bit_count_; + NumberTheory::CRC16 crc_generator_; + bool is_mfm_; + + void seek_to_track(uint8_t track); + void process_input_bit(int value); + void process_index_hole(); + uint8_t get_next_byte(); + + uint8_t get_byte_for_shift_value(uint16_t value); + + std::shared_ptr get_next_sector(); + std::shared_ptr get_sector(uint8_t sector); + std::vector get_track(); + + std::map> sectors_by_index_; + std::set decoded_tracks_; + int get_index(uint8_t head, uint8_t track, uint8_t sector); +}; + +} +} +} + +#endif /* Parser_hpp */ diff --git a/Storage/Disk/Encodings/MFM/Sector.hpp b/Storage/Disk/Encodings/MFM/Sector.hpp new file mode 100644 index 000000000..3345d1627 --- /dev/null +++ b/Storage/Disk/Encodings/MFM/Sector.hpp @@ -0,0 +1,38 @@ +// +// Sector.hpp +// Clock Signal +// +// Created by Thomas Harte on 24/09/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef Sector_h +#define Sector_h + +#include +#include + +namespace Storage { +namespace Encodings { +namespace MFM { + +/*! + Represents a single [M]FM sector, identified by its track, side and sector records, a blob of data + and a few extra flags of metadata. +*/ +struct Sector { + uint8_t track, side, sector, size; + std::vector data; + + bool has_data_crc_error; + bool has_header_crc_error; + bool is_deleted; + + Sector() : track(0), side(0), sector(0), size(0), has_data_crc_error(false), has_header_crc_error(false), is_deleted(false) {} +}; + +} +} +} + +#endif /* Sector_h */ diff --git a/Storage/Disk/Parsers/CPM.cpp b/Storage/Disk/Parsers/CPM.cpp index 5bc7f650b..ceaffb8a8 100644 --- a/Storage/Disk/Parsers/CPM.cpp +++ b/Storage/Disk/Parsers/CPM.cpp @@ -8,7 +8,7 @@ #include "CPM.hpp" -#include "../Encodings/MFM/MFM.hpp" +#include "../Encodings/MFM/Parser.hpp" using namespace Storage::Disk::CPM;