diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 6c5d43834..45545f7ef 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -502,7 +502,7 @@ void WD1770::posit_event(int new_event_type) { } set_data_mode(DataMode::Writing); - begin_writing(); + begin_writing(false); for(int c = 0; c < (get_is_double_density() ? 12 : 6); c++) { write_byte(0); } @@ -694,7 +694,7 @@ void WD1770::posit_event(int new_event_type) { } WAIT_FOR_EVENT(Event1770::IndexHoleTarget); - begin_writing(); + begin_writing(true); index_hole_count_ = 0; write_track_write_loop: diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 03a565f16..e6b79f217 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -52,6 +52,29 @@ using namespace Intel::i8272; #define SetBadCylinder() (status_[2] |= 0x02) #define SetMissingDataAddressMark() (status_[2] |= 0x01) +namespace { + const uint8_t CommandReadData = 0x06; + const uint8_t CommandReadDeletedData = 0x0c; + + const uint8_t CommandWriteData = 0x05; + const uint8_t CommandWriteDeletedData = 0x09; + + const uint8_t CommandReadTrack = 0x02; + const uint8_t CommandReadID = 0x0a; + const uint8_t CommandFormatTrack = 0x0d; + + const uint8_t CommandScanLow = 0x11; + const uint8_t CommandScanLowOrEqual = 0x19; + const uint8_t CommandScanHighOrEqual = 0x1d; + + const uint8_t CommandRecalibrate = 0x07; + const uint8_t CommandSeek = 0x0f; + + const uint8_t CommandSenseInterruptStatus = 0x08; + const uint8_t CommandSpecify = 0x03; + const uint8_t CommandSenseDriveStatus = 0x04; +} + i8272::i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) : Storage::Disk::MFMController(clock_rate, clock_rate_multiplier, revolutions_per_minute), bus_handler_(bus_handler), @@ -170,25 +193,28 @@ void i8272::set_disk(std::shared_ptr disk, int drive) { #define CONCAT(x, y) PASTE(x, y) #define FIND_HEADER() \ + set_data_mode(DataMode::Scanning); \ CONCAT(find_header, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \ - if(event_type == (int)Event::IndexHole) index_hole_limit_--; \ + if(event_type == (int)Event::IndexHole) { index_hole_limit_--; } \ else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__); \ \ if(index_hole_limit_) goto CONCAT(find_header, __LINE__); \ CONCAT(header_found, __LINE__): 0;\ #define FIND_DATA() \ + set_data_mode(DataMode::Scanning); \ CONCAT(find_data, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \ - if(event_type == (int)Event::Token && get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) goto CONCAT(find_data, __LINE__); + if(event_type == (int)Event::Token) { \ + if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__); \ + } #define READ_HEADER() \ distance_into_section_ = 0; \ - set_data_mode(Reading); \ + set_data_mode(DataMode::Reading); \ CONCAT(read_header, __LINE__): WAIT_FOR_EVENT(Event::Token); \ header_[distance_into_section_] = get_latest_token().byte_value; \ distance_into_section_++; \ if(distance_into_section_ < 6) goto CONCAT(read_header, __LINE__); \ - set_data_mode(Scanning); #define SET_DRIVE_HEAD_MFM() \ active_drive_ = command_[1]&3; \ @@ -198,6 +224,12 @@ void i8272::set_disk(std::shared_ptr disk, int drive) { set_is_double_density(command_[0] & 0x40); \ invalidate_track(); +#define WAIT_FOR_BYTES(n) \ + distance_into_section_ = 0; \ + CONCAT(wait_bytes, __LINE__): WAIT_FOR_EVENT(Event::Token); \ + if(get_latest_token().type == Token::Byte) distance_into_section_++; \ + if(distance_into_section_ < (n)) goto CONCAT(wait_bytes, __LINE__); + #define LOAD_HEAD() \ if(!drives_[active_drive_].head_is_loaded[active_head_]) { \ drives_[active_drive_].head_is_loaded[active_head_] = true; \ @@ -218,6 +250,7 @@ void i8272::set_disk(std::shared_ptr disk, int drive) { } void i8272::posit_event(int event_type) { + if(event_type == (int)Event::IndexHole) index_hole_count_++; if(!(interesting_event_mask_ & event_type)) return; interesting_event_mask_ &= ~event_type; @@ -260,61 +293,75 @@ void i8272::posit_event(int event_type) { // If this is not clearly a command that's safe to carry out in parallel to a seek, end all seeks. switch(command_[0] & 0x1f) { - case 0x03: // specify - case 0x04: // sense drive status - case 0x07: // recalibrate - case 0x08: // sense interrupt status - case 0x0f: // seek + case CommandReadData: + case CommandReadDeletedData: + case CommandWriteData: + case CommandWriteDeletedData: + case CommandReadTrack: + case CommandReadID: + case CommandFormatTrack: + case CommandScanLow: + case CommandScanLowOrEqual: + case CommandScanHighOrEqual: + is_access_command_ = true; break; default: - for(int c = 0; c < 4; c++) { - if(drives_[c].phase == Drive::Seeking) { - drives_[c].phase = Drive::NotSeeking; - drives_seeking_--; - } - } + is_access_command_ = false; break; } + if(is_access_command_) { + for(int c = 0; c < 4; c++) { + if(drives_[c].phase == Drive::Seeking) { + drives_[c].phase = Drive::NotSeeking; + drives_seeking_--; + } + } + // Establishes the drive and head being addressed, and whether in double density mode; populates the internal + // cylinder, head, sector and size registers from the command stream. + if(!dma_mode_) SetNonDMAExecution(); + SET_DRIVE_HEAD_MFM(); + LOAD_HEAD(); + } + // Jump to the proper place. switch(command_[0] & 0x1f) { - case 0x06: // read data - case 0x0c: // read deleted data + case CommandReadData: + case CommandReadDeletedData: goto read_data; - case 0x05: // write data - case 0x09: // write deleted data + case CommandWriteData: + case CommandWriteDeletedData: goto write_data; - case 0x02: goto read_track; - case 0x0a: goto read_id; - case 0x0d: goto format_track; - case 0x11: goto scan_low; - case 0x19: goto scan_low_or_equal; - case 0x1d: goto scan_high_or_equal; - case 0x07: goto recalibrate; - case 0x08: goto sense_interrupt_status; - case 0x03: goto specify; - case 0x04: goto sense_drive_status; - case 0x0f: goto seek; + case CommandReadTrack: goto read_track; + case CommandReadID: goto read_id; + case CommandFormatTrack: goto format_track; - default: goto invalid; + case CommandScanLow: goto scan_low; + case CommandScanLowOrEqual: goto scan_low_or_equal; + case CommandScanHighOrEqual: goto scan_high_or_equal; + + case CommandRecalibrate: goto recalibrate; + case CommandSeek: goto seek; + + case CommandSenseInterruptStatus: goto sense_interrupt_status; + case CommandSpecify: goto specify; + case CommandSenseDriveStatus: goto sense_drive_status; + + default: goto invalid; } // Decodes drive, head and density, loads the head, loads the internal cylinder, head, sector and size registers, // and searches for a sector that meets those criteria. If one is found, inspects the instruction in use and // jumps to an appropriate handler. read_write_find_header: - // Establishes the drive and head being addressed, and whether in double density mode; populates the internal - // cylinder, head, sector and size registers from the command stream. - LOAD_HEAD(); // Sets a maximum index hole limit of 2 then performs a find header/read header loop, continuing either until // the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the // values in the internal registers. index_hole_limit_ = 2; - set_data_mode(DataMode::Scanning); // printf("Seeking %02x %02x %02x %02x\n", cylinder_, head_, sector_, size_); find_next_sector: FIND_HEADER(); @@ -322,38 +369,39 @@ void i8272::posit_event(int event_type) { // Two index holes have passed wihout finding the header sought. // printf("Not found\n"); SetNoData(); - goto abort_read_write; + goto abort; } + index_hole_count_ = 0; +// printf("Header\n"); READ_HEADER(); + if(index_hole_count_) { + // This implies an index hole was sighted within the header. Error out. + SetEndOfCylinder(); + goto abort; + } if(get_crc_generator().get_value()) { // This implies a CRC error in the header; mark as such but continue. SetDataError(); } -// printf("Considering %02x %02x %02x %02x\n", header_[0], header_[1], header_[2], header_[3]); +// printf("Considering %02x %02x %02x %02x [%04x]\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value()); if(header_[0] != cylinder_ || header_[1] != head_ || header_[2] != sector_ || header_[3] != size_) goto find_next_sector; // Branch to whatever is supposed to happen next // printf("Proceeding\n"); switch(command_[0] & 0x1f) { - case 0x06: // read data - case 0x0c: // read deleted data + case CommandReadData: + case CommandReadDeletedData: goto read_data_found_header; - case 0x05: // write data - case 0x09: // write deleted data + case CommandWriteData: // write data + case CommandWriteDeletedData: // write deleted data goto write_data_found_header; } - // Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase. - abort_read_write: - SetAbnormalTermination(); - goto post_st012chrn; // Performs the read data or read deleted data command. read_data: - printf("Read data [%02x %02x %02x %02x ... %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[8]); - if(!dma_mode_) SetNonDMAExecution(); - SET_DRIVE_HEAD_MFM(); + printf("Read [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]); read_next_data: goto read_write_find_header; @@ -361,14 +409,27 @@ void i8272::posit_event(int event_type) { // flag doesn't match the sort the command was looking for. read_data_found_header: FIND_DATA(); - if((get_latest_token().type == Token::Data) != ((command_[0]&0xf) == 0x6)) { - if(!(command_[0]&0x20)) { - // SK is not set; set the error flag but read this sector before finishing. - SetControlMark(); + if(event_type == (int)Event::Token) { + if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) { + // Something other than a data mark came next — impliedly an ID or index mark. + SetMissingAddressMark(); + SetMissingDataAddressMark(); + goto abort; // TODO: or read_next_data? } else { - // SK is set; skip this sector. - goto read_next_data; + if((get_latest_token().type == Token::Data) != ((command_[0] & 0x1f) == CommandReadData)) { + if(!(command_[0]&0x20)) { + // SK is not set; set the error flag but read this sector before finishing. + SetControlMark(); + } else { + // SK is set; skip this sector. + goto read_next_data; + } + } } + } else { + // An index hole appeared before the data mark. + SetEndOfCylinder(); + goto abort; // TODO: or read_next_data? } distance_into_section_ = 0; @@ -379,12 +440,14 @@ void i8272::posit_event(int event_type) { // // TODO: consider DTL. read_data_get_byte: - WAIT_FOR_EVENT(Event::Token); - result_stack_.push_back(get_latest_token().byte_value); - distance_into_section_++; - SetDataRequest(); - SetDataDirectionToProcessor(); - WAIT_FOR_EVENT((int)Event8272::ResultEmpty | (int)Event::Token | (int)Event::IndexHole); + WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); + if(event_type == (int)Event::Token) { + result_stack_.push_back(get_latest_token().byte_value); + distance_into_section_++; + SetDataRequest(); + SetDataDirectionToProcessor(); + WAIT_FOR_EVENT((int)Event8272::ResultEmpty | (int)Event::Token | (int)Event::IndexHole); + } switch(event_type) { case (int)Event8272::ResultEmpty: // The caller read the byte in time; proceed as normal. ResetDataRequest(); @@ -392,9 +455,11 @@ void i8272::posit_event(int event_type) { break; case (int)Event::Token: // The caller hasn't read the old byte yet and a new one has arrived SetOverrun(); - goto abort_read_write; + goto abort; break; case (int)Event::IndexHole: + SetEndOfCylinder(); + goto abort; break; } @@ -405,7 +470,7 @@ void i8272::posit_event(int event_type) { // This implies a CRC error in the sector; mark as such and temrinate. SetDataError(); SetDataFieldDataError(); - goto abort_read_write; + goto abort; } // check whether that's it: either the final requested sector has been read, or because @@ -419,26 +484,24 @@ void i8272::posit_event(int event_type) { goto post_st012chrn; write_data: - printf("Write [deleted] data\n"); - if(!dma_mode_) SetNonDMAExecution(); - SET_DRIVE_HEAD_MFM(); + printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]); if(drives_[active_drive_].drive->get_is_read_only()) { SetNotWriteable(); - goto abort_read_write; + goto abort; } write_next_data: goto read_write_find_header; write_data_found_header: - begin_writing(); + WAIT_FOR_BYTES(get_is_double_density() ? 22 : 11); + begin_writing(true); - write_id_data_joiner((command_[0] & 0x1f) == 0x09); + write_id_data_joiner((command_[0] & 0x1f) == CommandWriteDeletedData, true); SetDataDirectionFromProcessor(); SetDataRequest(); - WAIT_FOR_EVENT(Event::DataWritten); expects_input_ = true; distance_into_section_ = 0; @@ -447,7 +510,7 @@ void i8272::posit_event(int event_type) { if(!has_input_) { SetOverrun(); end_writing(); - goto abort_read_write; + goto abort; } write_byte(input_); has_input_ = false; @@ -457,19 +520,23 @@ void i8272::posit_event(int event_type) { goto write_loop; } + printf("Wrote %d bytes\n", distance_into_section_); write_crc(); expects_input_ = false; WAIT_FOR_EVENT(Event::DataWritten); end_writing(); + if(sector_ != command_[6]) { + sector_++; + goto write_next_data; + } + goto post_st012chrn; // Performs the read ID command. read_id: // Establishes the drive and head being addressed, and whether in double density mode. - printf("Read ID\n"); - SET_DRIVE_HEAD_MFM(); - LOAD_HEAD(); + printf("Read ID [%02x %02x]\n", command_[0], command_[1]); // Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes. // If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found. @@ -477,8 +544,8 @@ void i8272::posit_event(int event_type) { read_id_find_next_sector: FIND_HEADER(); if(!index_hole_limit_) { - SetNoData(); - goto abort_read_write; + SetMissingAddressMark(); + goto abort; } READ_HEADER(); @@ -493,8 +560,6 @@ void i8272::posit_event(int event_type) { // Performs read track. read_track: printf("Read track [%02x %02x %02x %02x]\n", command_[2], command_[3], command_[4], command_[5]); - if(!dma_mode_) SetNonDMAExecution(); - SET_DRIVE_HEAD_MFM(); // Wait for the index hole. WAIT_FOR_EVENT(Event::IndexHole); @@ -508,7 +573,7 @@ void i8272::posit_event(int event_type) { if(!index_hole_limit_) { if(!sector_) { SetMissingAddressMark(); - goto abort_read_write; + goto abort; } else { goto post_st012chrn; } @@ -536,18 +601,15 @@ void i8272::posit_event(int event_type) { // Performs format [/write] track. format_track: printf("Format track\n"); - if(!dma_mode_) SetNonDMAExecution(); - SET_DRIVE_HEAD_MFM(); if(drives_[active_drive_].drive->get_is_read_only()) { SetNotWriteable(); - goto abort_read_write; + goto abort; } - LOAD_HEAD(); - // Wait for the index hole. WAIT_FOR_EVENT(Event::IndexHole); - begin_writing(); + index_hole_count_ = 0; + begin_writing(true); // Write start-of-track. write_start_of_track(); @@ -564,20 +626,30 @@ void i8272::posit_event(int event_type) { expects_input_ = true; distance_into_section_ = 0; format_track_write_header: - WAIT_FOR_EVENT(Event::DataWritten); - // TODO: overrun? - header_[distance_into_section_] = input_; - write_byte(input_); - has_input_ = false; - distance_into_section_++; - if(distance_into_section_ < 4) { - SetDataRequest(); - goto format_track_write_header; + WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole); + switch(event_type) { + case (int)Event::IndexHole: + SetOverrun(); + end_writing(); + goto abort; + break; + case (int)Event::DataWritten: + header_[distance_into_section_] = input_; + write_byte(input_); + has_input_ = false; + distance_into_section_++; + if(distance_into_section_ < 4) { + SetDataRequest(); + goto format_track_write_header; + } + break; } + + printf("W: %02x %02x %02x %02x, %04x\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value()); write_crc(); // Write the sector body. - write_id_data_joiner(false); + write_id_data_joiner(false, false); write_n_bytes(128 << command_[2], command_[5]); write_crc(); @@ -586,7 +658,7 @@ void i8272::posit_event(int event_type) { // Consider repeating. sector_++; - if(sector_ < command_[3]) + if(sector_ < command_[3] && !index_hole_count_) goto format_track_write_sector; // Otherwise, pad out to the index hole. @@ -717,6 +789,11 @@ void i8272::posit_event(int event_type) { result_stack_.push_back(0x80); goto post_result; + // Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase. + abort: + SetAbnormalTermination(); + goto post_st012chrn; + // Posts ST0, ST1, ST2, C, H, R and N as a result phase. post_st012chrn: SCHEDULE_HEAD_UNLOAD(); @@ -735,7 +812,7 @@ void i8272::posit_event(int event_type) { // Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack — the // last thing in it will be returned first. post_result: - printf("Result to %02x: ", command_[0] & 0x1f); + printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_); for(size_t c = 0; c < result_stack_.size(); c++) { printf("%02x ", result_stack_[result_stack_.size() - 1 - c]); } diff --git a/Components/8272/i8272.hpp b/Components/8272/i8272.hpp index deae4e56c..44dc899af 100644 --- a/Components/8272/i8272.hpp +++ b/Components/8272/i8272.hpp @@ -66,6 +66,7 @@ class i8272: public Storage::Disk::MFMController { void posit_event(int type); int interesting_event_mask_; int resume_point_; + bool is_access_command_; // The counter used for ::Timer events. int delay_time_; @@ -117,7 +118,7 @@ class i8272: public Storage::Disk::MFMController { // Transient storage and counters used while reading the disk. uint8_t header_[6]; int distance_into_section_; - int index_hole_limit_; + int index_hole_count_, index_hole_limit_; // Keeps track of the drive and head in use during commands. int active_drive_; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index b77bb0dd8..10b16ea4c 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -121,6 +121,7 @@ 4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B31D327F7E00DF5BA0 /* G64.cpp */; }; 4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */; }; 4BACC5B11F3DFF7C0037C015 /* CharacterMapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BACC5AF1F3DFF7C0037C015 /* CharacterMapper.cpp */; }; + 4BAD9B961F43D7E900724854 /* UnformattedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAD9B941F43D7E900724854 /* UnformattedTrack.cpp */; }; 4BB17D4E1ED7909F00ABD1E1 /* tests.expected.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */; }; 4BB17D4F1ED7909F00ABD1E1 /* tests.in.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */; }; 4BB298F11B587D8400A49093 /* start in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E51B587D8300A49093 /* start */; }; @@ -673,6 +674,8 @@ 4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMTrack.hpp; sourceTree = ""; }; 4BACC5AF1F3DFF7C0037C015 /* CharacterMapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CharacterMapper.cpp; path = AmstradCPC/CharacterMapper.cpp; sourceTree = ""; }; 4BACC5B01F3DFF7C0037C015 /* CharacterMapper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CharacterMapper.hpp; path = AmstradCPC/CharacterMapper.hpp; sourceTree = ""; }; + 4BAD9B941F43D7E900724854 /* UnformattedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UnformattedTrack.cpp; sourceTree = ""; }; + 4BAD9B951F43D7E900724854 /* UnformattedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = UnformattedTrack.hpp; sourceTree = ""; }; 4BB06B211F316A3F00600C7A /* ForceInline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ForceInline.h; sourceTree = ""; }; 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = ""; }; 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = ""; }; @@ -1552,6 +1555,7 @@ 4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */, 4B121F961E060CF000BFDA12 /* PCMSegment.cpp */, 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */, + 4BAD9B941F43D7E900724854 /* UnformattedTrack.cpp */, 4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */, 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */, 4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */, @@ -1560,6 +1564,7 @@ 4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */, 4B121F971E060CF000BFDA12 /* PCMSegment.hpp */, 4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */, + 4BAD9B951F43D7E900724854 /* UnformattedTrack.hpp */, 4BB697CF1D4BA44900248BDF /* Encodings */, 4BAB62B21D327F7E00DF5BA0 /* Formats */, 4B3FE75F1F3CF6BA00448EE4 /* Parsers */, @@ -2709,6 +2714,7 @@ 4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */, 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */, 4B95FA9D1F11893B0008E395 /* ZX8081OptionsPanel.swift in Sources */, + 4BAD9B961F43D7E900724854 /* UnformattedTrack.cpp in Sources */, 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */, 4B8378DC1F336631005CA9E4 /* CharacterMapper.cpp in Sources */, 4B8378E51F3378C4005CA9E4 /* CharacterMapper.cpp in Sources */, diff --git a/Storage/Disk/Disk.cpp b/Storage/Disk/Disk.cpp index 406e99312..5844e1bea 100644 --- a/Storage/Disk/Disk.cpp +++ b/Storage/Disk/Disk.cpp @@ -28,6 +28,9 @@ void Disk::set_track_at_position(unsigned int head, unsigned int position, const } std::shared_ptr Disk::get_track_at_position(unsigned int head, unsigned int position) { + if(head >= get_head_count()) return nullptr; + if(position >= get_head_position_count()) return nullptr; + int address = get_id_for_track_at_position(head, position); std::map>::iterator cached_track = cached_tracks_.find(address); if(cached_track != cached_tracks_.end()) return cached_track->second; diff --git a/Storage/Disk/DiskController.cpp b/Storage/Disk/DiskController.cpp index c07eea966..6a7ae1f85 100644 --- a/Storage/Disk/DiskController.cpp +++ b/Storage/Disk/DiskController.cpp @@ -7,7 +7,9 @@ // #include "DiskController.hpp" +#include "UnformattedTrack.hpp" #include "../../NumberTheory/Factors.hpp" +#include using namespace Storage::Disk; @@ -29,13 +31,17 @@ Controller::Controller(Cycles clock_rate, int clock_rate_multiplier, int revolut void Controller::setup_track() { track_ = drive_->get_track(); + if(!track_) { + track_.reset(new UnformattedTrack); + } Time offset; Time track_time_now = get_time_into_track(); - if(track_) { - Time time_found = track_->seek_to(track_time_now); - offset = track_time_now - time_found; - } + assert(track_time_now >= Time(0) && current_event_.length <= Time(1)); + + Time time_found = track_->seek_to(track_time_now); + assert(time_found >= Time(0) && time_found <= track_time_now); + offset = track_time_now - time_found; get_next_event(offset); } @@ -93,7 +99,9 @@ void Controller::get_next_event(const Time &duration_already_passed) { // divide interval, which is in terms of a single rotation of the disk, by rotation speed to // convert it into revolutions per second; this is achieved by multiplying by rotational_multiplier_ - set_next_event_time_interval((current_event_.length - duration_already_passed) * rotational_multiplier_); + assert(current_event_.length <= Time(1) && current_event_.length >= Time(0)); + Time interval = (current_event_.length - duration_already_passed) * rotational_multiplier_; + set_next_event_time_interval(interval); } void Controller::process_next_event() @@ -121,8 +129,9 @@ Storage::Time Controller::get_time_into_track() { #pragma mark - Writing -void Controller::begin_writing() { +void Controller::begin_writing(bool clamp_to_index_hole) { is_reading_ = false; + clamp_writing_to_index_hole_ = clamp_to_index_hole; write_segment_.length_of_a_bit = bit_length_ / rotational_multiplier_; write_segment_.data.clear(); @@ -150,7 +159,8 @@ void Controller::end_writing() { patched_track_.reset(new PCMPatchedTrack(track_)); } } - patched_track_->add_segment(write_start_time_, write_segment_); + patched_track_->add_segment(write_start_time_, write_segment_, clamp_writing_to_index_hole_); + cycles_since_index_hole_ %= 8000000 * clock_rate_multiplier_; invalidate_track(); // TEMPORARY: to force a seek } @@ -205,8 +215,7 @@ bool Controller::get_motor_on() { } void Controller::set_drive(std::shared_ptr drive) { - if(drive_ != drive) - { + if(drive_ != drive) { invalidate_track(); drive_ = drive; } diff --git a/Storage/Disk/DiskController.hpp b/Storage/Disk/DiskController.hpp index 59c111f59..399a92d84 100644 --- a/Storage/Disk/DiskController.hpp +++ b/Storage/Disk/DiskController.hpp @@ -72,8 +72,11 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop @c write_bit. They will be written with the length set via @c set_expected_bit_length. It is acceptable to supply a backlog of bits. Flux transition events will not be reported while writing. + + @param clamp_to_index_hole If @c true then writing will automatically be truncated by + the index hole. Writing will continue over the index hole otherwise. */ - void begin_writing(); + void begin_writing(bool clamp_to_index_hole); /*! Writes the bit @c value as the next in the PCM stream initiated by @c begin_writing. @@ -129,6 +132,7 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop bool motor_is_on_; bool is_reading_; + bool clamp_writing_to_index_hole_; std::shared_ptr patched_track_; PCMSegment write_segment_; Time write_start_time_; diff --git a/Storage/Disk/MFMDiskController.cpp b/Storage/Disk/MFMDiskController.cpp index 4b30c8ff6..1ae81df9a 100644 --- a/Storage/Disk/MFMDiskController.cpp +++ b/Storage/Disk/MFMDiskController.cpp @@ -199,15 +199,15 @@ void MFMController::write_id_joiner() { } } -void MFMController::write_id_data_joiner(bool is_deleted) { +void MFMController::write_id_data_joiner(bool is_deleted, bool skip_first_gap) { if(get_is_double_density()) { - write_n_bytes(22, 0x4e); + if(!skip_first_gap) write_n_bytes(22, 0x4e); write_n_bytes(12, 0x00); for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMSync); get_crc_generator().set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue); write_byte(is_deleted ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte); } else { - write_n_bytes(11, 0xff); + if(!skip_first_gap) write_n_bytes(11, 0xff); write_n_bytes(6, 0x00); get_crc_generator().reset(); get_crc_generator().add(is_deleted ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte); @@ -227,7 +227,7 @@ void MFMController::write_start_of_track() { if(get_is_double_density()) { write_n_bytes(80, 0x4e); write_n_bytes(12, 0x00); - for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMIndexSync); + for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMIndexSync); write_byte(Storage::Encodings::MFM::IndexAddressByte); write_n_bytes(50, 0x4e); } else { diff --git a/Storage/Disk/MFMDiskController.hpp b/Storage/Disk/MFMDiskController.hpp index b5bef5bd2..206246ba2 100644 --- a/Storage/Disk/MFMDiskController.hpp +++ b/Storage/Disk/MFMDiskController.hpp @@ -125,11 +125,12 @@ class MFMController: public Controller { void write_id_joiner(); /*! - Writes everything that should, per the spec, appear after the ID's CRC, up to and + Writes at most what should, per the spec, appear after the ID's CRC, up to and including the mark that indicates the beginning of data, appropriately seeding - the CRC generator. + the CRC generator; if @c skip_first_gap is set then the initial gap after the + CRC isn't written. */ - void write_id_data_joiner(bool is_deleted); + void write_id_data_joiner(bool is_deleted, bool skip_first_gap); /*! Writes the gap expected after a sector's data CRC and before the beginning of the diff --git a/Storage/Disk/PCMPatchedTrack.cpp b/Storage/Disk/PCMPatchedTrack.cpp index db865b331..d1cb9eed7 100644 --- a/Storage/Disk/PCMPatchedTrack.cpp +++ b/Storage/Disk/PCMPatchedTrack.cpp @@ -8,6 +8,8 @@ #include "PCMPatchedTrack.hpp" +#include + using namespace Storage::Disk; PCMPatchedTrack::PCMPatchedTrack(std::shared_ptr underlying_track) : @@ -29,20 +31,25 @@ Track *PCMPatchedTrack::clone() { return new PCMPatchedTrack(*this); } -void PCMPatchedTrack::add_segment(const Time &start_time, const PCMSegment &segment) { +void PCMPatchedTrack::add_segment(const Time &start_time, const PCMSegment &segment, bool clamp_to_index_hole) { std::shared_ptr event_source(new PCMSegmentEventSource(segment)); Time zero(0); + Time one(1); Time end_time = start_time + event_source->get_length(); + if(clamp_to_index_hole && end_time > one) { + end_time = one; + } Period insertion_period(start_time, end_time, zero, event_source); // the new segment may wrap around, so divide it up into track-length parts if required - Time one = Time(1); + assert(insertion_period.start_time <= one); while(insertion_period.end_time > one) { Time next_end_time = insertion_period.end_time - one; insertion_period.end_time = one; insert_period(insertion_period); + insertion_period.segment_start_time += one; insertion_period.start_time = zero; insertion_period.end_time = next_end_time; } @@ -127,8 +134,7 @@ void PCMPatchedTrack::insert_period(const Period &period) { } } -Track::Event PCMPatchedTrack::get_next_event() -{ +Track::Event PCMPatchedTrack::get_next_event() { const Time one(1); const Time zero(0); Time extra_time(0); @@ -184,13 +190,19 @@ 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 end before reaching the time sought active_period_ = periods_.begin(); - while(active_period_->end_time < time_since_index_hole) active_period_++; + while(active_period_->end_time < time_since_index_hole) { + assert(active_period_ != periods_.end()); + active_period_++; + } - // allow whatever storage represents the period found to perform its seek + // allow whatever storage represents the period found to perform its seek; calculation for periods + // with an event source is, in effect: seek_to(offset_into_segment + distance_into_period) - offset_into_segment. if(active_period_->event_source) - current_time_ = 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(active_period_->segment_start_time + time_since_index_hole - active_period_->start_time) + active_period_->start_time - active_period_->segment_start_time; else current_time_ = underlying_track_->seek_to(time_since_index_hole); + + assert(current_time_ <= time_since_index_hole); return current_time_; } diff --git a/Storage/Disk/PCMPatchedTrack.hpp b/Storage/Disk/PCMPatchedTrack.hpp index 23e7bd3a1..842f16d0f 100644 --- a/Storage/Disk/PCMPatchedTrack.hpp +++ b/Storage/Disk/PCMPatchedTrack.hpp @@ -9,7 +9,7 @@ #ifndef PCMPatchedTrack_hpp #define PCMPatchedTrack_hpp -#include "PCMTrack.hpp" +#include "Disk.hpp" #include "PCMSegment.hpp" namespace Storage { @@ -34,8 +34,13 @@ class PCMPatchedTrack: public Track { /*! Replaces whatever is currently on the track from @c start_position to @c start_position + segment length with the contents of @c segment. + + @param start_time The time at which this segment begins. Must be in the range [0, 1). + @param segment The PCM segment to add. + @param clamp_to_index_hole If @c true then the new segment will be truncated if it overruns the index hole; + it will otherwise write over the index hole and continue. */ - void add_segment(const Time &start_time, const PCMSegment &segment); + void add_segment(const Time &start_time, const PCMSegment &segment, bool clamp_to_index_hole); // To satisfy Storage::Disk::Track Event get_next_event(); diff --git a/Storage/Disk/UnformattedTrack.cpp b/Storage/Disk/UnformattedTrack.cpp new file mode 100644 index 000000000..b501bc8a0 --- /dev/null +++ b/Storage/Disk/UnformattedTrack.cpp @@ -0,0 +1,26 @@ +// +// UnformattedTrack.cpp +// Clock Signal +// +// Created by Thomas Harte on 15/08/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#include "UnformattedTrack.hpp" + +using namespace Storage::Disk; + +Track::Event UnformattedTrack::get_next_event() { + Track::Event event; + event.type = Event::IndexHole; + event.length = Time(1); + return event; +} + +Storage::Time UnformattedTrack::seek_to(const Time &time_since_index_hole) { + return Time(0); +} + +Track *UnformattedTrack::clone() { + return new UnformattedTrack; +} diff --git a/Storage/Disk/UnformattedTrack.hpp b/Storage/Disk/UnformattedTrack.hpp new file mode 100644 index 000000000..cdddbc8c1 --- /dev/null +++ b/Storage/Disk/UnformattedTrack.hpp @@ -0,0 +1,30 @@ +// +// UnformattedTrack.hpp +// Clock Signal +// +// Created by Thomas Harte on 15/08/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef UnformattedTrack_hpp +#define UnformattedTrack_hpp + +#include "Disk.hpp" + +namespace Storage { +namespace Disk { + +/*! + A subclass of @c Track with no contents. Just an index hole. +*/ +class UnformattedTrack: public Track { + public: + Event get_next_event(); + Time seek_to(const Time &time_since_index_hole); + Track *clone(); +}; + +} +} + +#endif /* UnformattedTrack_hpp */