From eec42aa7ae250f37e2010d04c6d9f04a79924bfd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Aug 2017 11:50:49 -0400 Subject: [PATCH 01/22] Entrusted further status to drives; also adjusted them to report read only if diskless, which I now believe to be correct. --- Components/8272/i8272.cpp | 4 ++-- Storage/Disk/Drive.cpp | 3 +-- Storage/Disk/Drive.hpp | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 285ec755d..facc29dc7 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -532,8 +532,8 @@ void i8272::posit_event(int event_type) { (command_[1] & 7) | // drive and head number 0x08 | // single sided (drives_[drive].drive->get_is_track_zero() ? 0x10 : 0x00) | - (drives_[drive].drive->has_disk() ? 0x20 : 0x00) | // ready, approximately (TODO) - 0x40 // write protected + (drives_[drive].drive->get_is_ready() ? 0x20 : 0x00) | + (drives_[drive].drive->get_is_read_only() ? 0x40 : 0x00) ); } goto post_result; diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index 1911d7916..1d5a80d69 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -42,8 +42,7 @@ 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; + return true; } bool Drive::get_is_ready() { diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index c67f5d1b6..d65cb5435 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -51,7 +51,7 @@ class Drive { void set_head(unsigned int head); /*! - @returns @c true if the inserted disk is read-only; @c false otherwise. + @returns @c true if the inserted disk is read-only or no disk is inserted; @c false otherwise. */ bool get_is_read_only(); From b0a7208cc7784f54f94efb4c2b09e5c42abe01fd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Aug 2017 12:50:07 -0400 Subject: [PATCH 02/22] Strung together a very basic version of 8272 write [/deleted] data. Lots of cases as-yet unhandled. --- Components/8272/i8272.cpp | 128 ++++++++++++++++++++++------- Components/8272/i8272.hpp | 3 + Storage/Disk/MFMDiskController.cpp | 22 +++++ Storage/Disk/MFMDiskController.hpp | 20 +++++ 4 files changed, 145 insertions(+), 28 deletions(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index facc29dc7..4fa230b80 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -7,6 +7,7 @@ // #include "i8272.hpp" +#include "../../Storage/Disk/Encodings/MFM.hpp" #include @@ -57,7 +58,8 @@ i8272::i8272(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_m interesting_event_mask_((int)Event8272::CommandByte), resume_point_(0), delay_time_(0), - head_timers_running_(0) { + head_timers_running_(0), + expects_input_(false) { posit_event((int)Event8272::CommandByte); } @@ -124,9 +126,15 @@ void i8272::set_register(int address, uint8_t value) { // if not ready for commands, do nothing if(!DataRequest() || DataDirectionToProcessor()) return; - // accumulate latest byte in the command byte sequence - command_.push_back(value); - posit_event((int)Event8272::CommandByte); + if(expects_input_) { + input_ = value; + has_input_ = true; + ResetDataRequest(); + } else { + // accumulate latest byte in the command byte sequence + command_.push_back(value); + posit_event((int)Event8272::CommandByte); + } } uint8_t i8272::get_register(int address) { @@ -218,6 +226,7 @@ void i8272::posit_event(int event_type) { // Resets busy and non-DMA execution, clears the command buffer, sets the data mode to scanning and flows // into wait_for_complete_command_sequence. wait_for_command: + expects_input_ = false; set_data_mode(Storage::Disk::MFMController::DataMode::Scanning); ResetBusy(); ResetNonDMAExecution(); @@ -240,14 +249,10 @@ void i8272::posit_event(int event_type) { goto read_data; case 0x05: // write data - if(command_.size() < 9) goto wait_for_complete_command_sequence; - ResetDataRequest(); - goto write_data; - case 0x09: // write deleted data if(command_.size() < 9) goto wait_for_complete_command_sequence; ResetDataRequest(); - goto write_deleted_data; + goto write_data; case 0x02: // read track if(command_.size() < 9) goto wait_for_complete_command_sequence; @@ -308,10 +313,10 @@ void i8272::posit_event(int event_type) { goto invalid; } - // Performs the read data or read deleted data command. - read_data: - printf("Read [deleted?] data, sector %02x %02x %02x %02x\n", command_[2], command_[3], command_[4], command_[5]); - + // 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. if(!dma_mode_) SetNonDMAExecution(); @@ -323,8 +328,6 @@ void i8272::posit_event(int event_type) { sector_ = command_[4]; size_ = command_[5]; - read_next_data: - // 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. @@ -334,7 +337,7 @@ void i8272::posit_event(int event_type) { if(!index_hole_limit_) { // Two index holes have passed wihout finding the header sought. SetNoData(); - goto abort_read; + goto abort_read_write; } READ_HEADER(); if(get_crc_generator().get_value()) { @@ -343,8 +346,30 @@ void i8272::posit_event(int event_type) { } 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 + switch(command_[0] & 0x1f) { + case 0x06: // read data + case 0x0b: // read deleted data + goto read_data_found_header; + + case 0x05: // write data + case 0x09: // 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: + read_next_data: + goto read_write_find_header; + // Finds the next data block and sets data mode to reading, setting an error flag if the on-disk deleted // flag doesn't match the sort the command was looking for. + read_data_found_header: FIND_DATA(); distance_into_section_ = 0; if((get_latest_token().type == Token::Data) != ((command_[0]&0xf) == 0x6)) { @@ -377,7 +402,7 @@ 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; + goto abort_read_write; break; case (int)Event::IndexHole: break; @@ -390,7 +415,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; + goto abort_read_write; } // check whether that's it: either the final requested sector has been read, or because @@ -403,17 +428,64 @@ void i8272::posit_event(int event_type) { // For a final result phase, post the standard ST0, ST1, ST2, C, H, R, N goto post_st012chrn; - abort_read: - SetAbnormalTermination(); - goto post_st012chrn; - write_data: - printf("Write data unimplemented!!\n"); - goto wait_for_command; + printf("Write [deleted] data\n"); - write_deleted_data: - printf("Write deleted data unimplemented!!\n"); - goto wait_for_command; + write_next_data: + goto read_write_find_header; + + write_data_found_header: + begin_writing(); + + // Write out the requested gap between ID and data. + for(int c = 0; c < command_[7]; c++) { + write_byte(0x4e); + } + WAIT_FOR_EVENT(Event::DataWritten); + + { + bool is_deleted = (command_[0] & 0x1f) == 0x09; + if(get_is_double_density()) { + get_crc_generator().set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue); + write_raw_short(Storage::Encodings::MFM::MFMSync); + write_byte(is_deleted ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte); + } else { + get_crc_generator().reset(); + get_crc_generator().add(is_deleted ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte); + write_raw_short(is_deleted ? Storage::Encodings::MFM::FMDeletedDataAddressMark : Storage::Encodings::MFM::FMDataAddressMark); + } + } + + SetDataDirectionFromProcessor(); + SetDataRequest(); + expects_input_ = true; + distance_into_section_ = 0; + + write_loop: + WAIT_FOR_EVENT(Event::DataWritten); + if(!has_input_) { + SetOverrun(); + end_writing(); + goto abort_read_write; + } + write_byte(input_); + has_input_ = false; + distance_into_section_++; + if(distance_into_section_ < (128 << size_)) { + SetDataRequest(); + goto write_loop; + } + + { + uint16_t crc = get_crc_generator().get_value(); + write_byte(crc >> 8); + write_byte(crc & 0xff); + } + expects_input_ = false; + WAIT_FOR_EVENT(Event::DataWritten); + end_writing(); + + goto post_st012chrn; read_track: printf("Read track unimplemented!!\n"); @@ -433,7 +505,7 @@ void i8272::posit_event(int event_type) { FIND_HEADER(); if(!index_hole_limit_) { SetNoData(); - goto abort_read; + goto abort_read_write; } READ_HEADER(); diff --git a/Components/8272/i8272.hpp b/Components/8272/i8272.hpp index 137e56b3f..00810ea7b 100644 --- a/Components/8272/i8272.hpp +++ b/Components/8272/i8272.hpp @@ -36,6 +36,9 @@ class i8272: public Storage::Disk::MFMController { // A buffer for accumulating the incoming command, and one for accumulating the result. std::vector command_; std::vector result_stack_; + uint8_t input_; + bool has_input_; + bool expects_input_; // Event stream: the 8272-specific events, plus the current event state. enum class Event8272: int { diff --git a/Storage/Disk/MFMDiskController.cpp b/Storage/Disk/MFMDiskController.cpp index d9663f398..0b42b0249 100644 --- a/Storage/Disk/MFMDiskController.cpp +++ b/Storage/Disk/MFMDiskController.cpp @@ -153,3 +153,25 @@ void MFMController::process_input_bit(int value, unsigned int cycles_since_index return; } } + +void MFMController::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 MFMController::write_byte(uint8_t byte) { + for(int c = 0; c < 8; c++) write_bit((byte << c)&0x80); + crc_generator_.add(byte); +} + +void MFMController::write_raw_short(uint16_t value) { + for(int c = 0; c < 16; c++) { + Controller::write_bit(!!((value << c)&0x8000)); + } +} diff --git a/Storage/Disk/MFMDiskController.hpp b/Storage/Disk/MFMDiskController.hpp index e32e697a2..7c4ba2743 100644 --- a/Storage/Disk/MFMDiskController.hpp +++ b/Storage/Disk/MFMDiskController.hpp @@ -90,6 +90,23 @@ class MFMController: public Controller { */ virtual void posit_event(int type) = 0; + /*! + Encodes @c bit according to the current single/double density mode and adds it + to the controller's write buffer. + */ + void write_bit(int bit); + + /*! + Encodes @c byte according to the current single/double density mode and adds it + to the controller's write buffer. + */ + void write_byte(uint8_t byte); + + /*! + Serialises @c value into the controller's write buffer without adjustment. + */ + void write_raw_short(uint16_t value); + private: // Storage::Disk::Controller virtual void process_input_bit(int value, unsigned int cycles_since_index_hole); @@ -108,6 +125,9 @@ class MFMController: public Controller { // output Token latest_token_; + // writing + int last_bit_; + // CRC generator NumberTheory::CRC16 crc_generator_; }; From 6e36f8ffa47c1e81070f8ff81846269d7dfeffaa Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Aug 2017 12:50:24 -0400 Subject: [PATCH 03/22] Removed index-hole announcement. --- Storage/Disk/DiskController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Storage/Disk/DiskController.cpp b/Storage/Disk/DiskController.cpp index 5429da94f..c07eea966 100644 --- a/Storage/Disk/DiskController.cpp +++ b/Storage/Disk/DiskController.cpp @@ -103,7 +103,7 @@ void Controller::process_next_event() if(is_reading_) pll_->add_pulse(); break; case Track::Event::IndexHole: - printf("%p %d [/%d = %d]\n", this, cycles_since_index_hole_, clock_rate_multiplier_, cycles_since_index_hole_ / clock_rate_multiplier_); +// printf("%p %d [/%d = %d]\n", this, cycles_since_index_hole_, clock_rate_multiplier_, cycles_since_index_hole_ / clock_rate_multiplier_); cycles_since_index_hole_ = 0; process_index_hole(); break; From e1e9a0671257ed0a23201cd43d8be639d7f6f45f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Aug 2017 18:05:19 -0400 Subject: [PATCH 04/22] Made an attempt at format a track. --- Components/8272/i8272.cpp | 106 +++++++++++++++++++++++++++-- Storage/Disk/MFMDiskController.cpp | 6 ++ Storage/Disk/MFMDiskController.hpp | 2 + 3 files changed, 107 insertions(+), 7 deletions(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 4fa230b80..0f42e37e0 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -476,11 +476,7 @@ void i8272::posit_event(int event_type) { goto write_loop; } - { - uint16_t crc = get_crc_generator().get_value(); - write_byte(crc >> 8); - write_byte(crc & 0xff); - } + write_crc(); expects_input_ = false; WAIT_FOR_EVENT(Event::DataWritten); end_writing(); @@ -518,8 +514,104 @@ void i8272::posit_event(int event_type) { goto post_st012chrn; format_track: - printf("Fromat track unimplemented!!\n"); - goto wait_for_command; + printf("Format track\n"); + SET_DRIVE_HEAD_MFM(); + LOAD_HEAD(); + CLEAR_STATUS(); + + // Wait for the index hole. + WAIT_FOR_EVENT(Event::IndexHole); + begin_writing(); + + // Write start-of-track. + // TODO: single density. + for(int c = 0; c < 80; c++) { + write_byte(0x4e); + } + for(int c = 0; c < 12; c++) { + write_byte(0x00); + } + for(int c = 0; c < 3; c++) { + write_raw_short(Storage::Encodings::MFM::MFMIndexSync); + } + write_byte(Storage::Encodings::MFM::IndexAddressByte); + for(int c = 0; c < 50; c++) { + write_byte(0x4e); + } + WAIT_FOR_EVENT(Event::DataWritten); + sector_ = 0; + + format_track_write_sector: + for(int c = 0; c < 12; c++) { + write_byte(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(Storage::Encodings::MFM::IDAddressByte); + + // Write the sector header, obtaining its contents + // from the processor. + SetDataDirectionFromProcessor(); + SetDataRequest(); + 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; + } + write_crc(); + + // Write the sector body. + for(int c = 0; c < 22; c++) { + write_byte(0x4e); + } + for(int c = 0; c < 12; c++) { + write_byte(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(Storage::Encodings::MFM::DataAddressByte); + for(int c = 0; c < (128 << command_[2]); c++) { + write_byte(command_[5]); + } + write_crc(); + + // Write the prescribed gap. + for(int c = 0; c < command_[4]; c++) { + write_byte(0x4e); + } + + // Consider repeating. + sector_++; + if(sector_ < command_[3]) { + goto format_track_write_sector; + } + + // Otherwise, pad out to the index hole. + format_track_pad: + write_byte(0x4e); + WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole); + if(event_type != (int)Event::IndexHole) goto format_track_pad; + + end_writing(); + + cylinder_ = header_[0]; + head_ = header_[1]; + sector_ = header_[2] + 1; + size_ = header_[3]; + + goto post_st012chrn; scan_low: printf("Scan low unimplemented!!\n"); diff --git a/Storage/Disk/MFMDiskController.cpp b/Storage/Disk/MFMDiskController.cpp index 0b42b0249..51f4ac3b2 100644 --- a/Storage/Disk/MFMDiskController.cpp +++ b/Storage/Disk/MFMDiskController.cpp @@ -175,3 +175,9 @@ void MFMController::write_raw_short(uint16_t value) { Controller::write_bit(!!((value << c)&0x8000)); } } + +void MFMController::write_crc() { + uint16_t crc = get_crc_generator().get_value(); + write_byte(crc >> 8); + write_byte(crc & 0xff); +} diff --git a/Storage/Disk/MFMDiskController.hpp b/Storage/Disk/MFMDiskController.hpp index 7c4ba2743..d512ff525 100644 --- a/Storage/Disk/MFMDiskController.hpp +++ b/Storage/Disk/MFMDiskController.hpp @@ -107,6 +107,8 @@ class MFMController: public Controller { */ void write_raw_short(uint16_t value); + void write_crc(); + private: // Storage::Disk::Controller virtual void process_input_bit(int value, unsigned int cycles_since_index_hole); From 1576b4500bb2719bc64cbf91d0e61f0bf9b3f643 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Aug 2017 18:27:00 -0400 Subject: [PATCH 05/22] Added documentation. --- Storage/Disk/MFMDiskController.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Storage/Disk/MFMDiskController.hpp b/Storage/Disk/MFMDiskController.hpp index d512ff525..4ace6665e 100644 --- a/Storage/Disk/MFMDiskController.hpp +++ b/Storage/Disk/MFMDiskController.hpp @@ -107,6 +107,10 @@ class MFMController: public Controller { */ void write_raw_short(uint16_t value); + /*! + Gets the current value of the CRC generator and makes two calls to @c write_byte, to + write first its higher-value byte and then its lower. + */ void write_crc(); private: From 5221837be8a4fc858d186013bde7209eafeec746 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Aug 2017 18:51:06 -0400 Subject: [PATCH 06/22] Fixed Non-DMA flag for the format track execution phase. The emulated machine now provides sector details. --- Components/8272/i8272.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 0f42e37e0..f857cd632 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -515,6 +515,7 @@ void i8272::posit_event(int event_type) { format_track: printf("Format track\n"); + if(!dma_mode_) SetNonDMAExecution(); SET_DRIVE_HEAD_MFM(); LOAD_HEAD(); CLEAR_STATUS(); @@ -726,6 +727,12 @@ 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: "); + for(size_t c = 0; c < result_stack_.size(); c++) { + printf("%02x ", result_stack_[result_stack_.size() - 1 - c]); + } + printf("\n"); + // Set ready to send data to the processor, no longer in non-DMA execution phase. ResetNonDMAExecution(); SetDataRequest(); From 750f2cb88369ccf8f9c5e5b5f4dbdc12739c2777 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Aug 2017 18:54:39 -0400 Subject: [PATCH 07/22] Flagged as not read-only, at least for now, to allow 8272 writing tests definitively to function. --- Storage/Disk/Formats/CPCDSK.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Storage/Disk/Formats/CPCDSK.cpp b/Storage/Disk/Formats/CPCDSK.cpp index f1a9b1570..ba57c539b 100644 --- a/Storage/Disk/Formats/CPCDSK.cpp +++ b/Storage/Disk/Formats/CPCDSK.cpp @@ -47,7 +47,7 @@ unsigned int CPCDSK::get_head_count() { bool CPCDSK::get_is_read_only() { // TODO: allow writing. - return true; + return false; } std::shared_ptr CPCDSK::get_uncached_track_at_position(unsigned int head, unsigned int position) { From 9ace6e1f7111598c79f3c7477e0d99092653add9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Aug 2017 19:25:57 -0400 Subject: [PATCH 08/22] Applied minimum constraints for specified parameters. --- Components/8272/i8272.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index f857cd632..30542dad1 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -364,6 +364,7 @@ void i8272::posit_event(int event_type) { // Performs the read data or read deleted data command. read_data: + printf("Read data\n"); read_next_data: goto read_write_find_header; @@ -684,9 +685,14 @@ void i8272::posit_event(int event_type) { // Performs specify. specify: // Just store the values, and terminate the command. + printf("Specify\n"); step_rate_time_ = command_[1] &0xf0; // i.e. 16 to 240m head_unload_time_ = command_[1] & 0x0f; // i.e. 1 to 16ms head_load_time_ = command_[2] & ~1; // i.e. 2 to 254 ms in increments of 2ms + + if(!step_rate_time_) step_rate_time_ = 16; + if(!head_unload_time_) head_unload_time_ = 1; + if(!head_load_time_) head_load_time_ = 2; dma_mode_ = !(command_[2] & 1); goto wait_for_command; From 1011143dbe608d5c39b6bb1c23738f052cef5d9e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Aug 2017 21:52:48 -0400 Subject: [PATCH 09/22] Sought to correct my interpretation of 'gap 3'. --- Components/8272/i8272.cpp | 25 ++++++++++++++++++++----- Storage/Disk/Encodings/MFM.cpp | 21 ++++++++++----------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 30542dad1..78a24a89a 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -92,6 +92,7 @@ void i8272::run_for(Cycles cycles) { // Check for completion. if(drives_[c].seek_is_satisfied()) { drives_[c].phase = Drive::CompletedSeeking; + main_status_ &= ~(1 << c); if(drives_[c].target_head_position == -1) drives_[c].head_position = 0; break; } @@ -438,17 +439,29 @@ void i8272::posit_event(int event_type) { write_data_found_header: begin_writing(); - // Write out the requested gap between ID and data. - for(int c = 0; c < command_[7]; c++) { - write_byte(0x4e); + if(get_is_double_density()) { + for(int c = 0; c < 50; c++) { + write_byte(0x4e); + } + for(int c = 0; c < 12; c++) { + write_byte(0x00); + } + } else { + for(int c = 0; c < 11; c++) { + write_byte(0xff); + } + for(int c = 0; c < 6; c++) { + write_byte(0x00); + } } + WAIT_FOR_EVENT(Event::DataWritten); { bool is_deleted = (command_[0] & 0x1f) == 0x09; if(get_is_double_density()) { get_crc_generator().set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue); - write_raw_short(Storage::Encodings::MFM::MFMSync); + for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMSync); write_byte(is_deleted ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte); } else { get_crc_generator().reset(); @@ -644,6 +657,7 @@ void i8272::posit_event(int event_type) { drives_[drive].target_head_position = (command_.size() > 2) ? command_[2] : -1; drives_[drive].step_rate_counter = 0; drives_[drive].seek_failed = false; + printf("Accepted seek to %d\n", drives_[drive].target_head_position); // Check whether any steps are even needed. if(drives_[drive].seek_is_satisfied()) { @@ -651,6 +665,8 @@ void i8272::posit_event(int event_type) { } else { main_status_ |= 1 << (command_[1]&3); } + } else { + printf("Rejected seek to %d\n", (command_.size() > 2) ? command_[2] : -1); } goto wait_for_command; @@ -672,7 +688,6 @@ void i8272::posit_event(int event_type) { drives_[found_drive].phase = Drive::NotSeeking; status_[0] = (uint8_t)found_drive; SetSeekEnd(); - main_status_ &= ~(1 << found_drive); result_stack_.push_back(drives_[found_drive].head_position); result_stack_.push_back(status_[0]); diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index aeb47404a..3808baff2 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -120,8 +120,8 @@ template std::shared_ptr size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value, size_t pre_address_mark_bytes, size_t post_address_mark_bytes, uint8_t post_address_mark_value, - size_t pre_data_mark_bytes, size_t post_data_bytes, - size_t inter_sector_gap, + size_t pre_data_mark_bytes, + size_t post_data_bytes, uint8_t post_data_value, size_t expected_track_bytes) { Storage::Disk::PCMSegment segment; segment.data.reserve(expected_track_bytes); @@ -169,8 +169,7 @@ template std::shared_ptr } // gap - for(size_t c = 0; c < post_data_bytes; c++) shifter.add_byte(0x00); - for(size_t c = 0; c < inter_sector_gap; c++) shifter.add_byte(0x4e); + for(size_t c = 0; c < post_data_bytes; c++) shifter.add_byte(post_data_value); } while(segment.data.size() < expected_track_bytes) shifter.add_byte(0x00); @@ -199,11 +198,11 @@ const size_t Storage::Encodings::MFM::DefaultSectorGapLength = (size_t)~0; std::shared_ptr Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector §ors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) { return GetTrackWithSectors( sectors, - 16, 0x00, + 26, 0xff, 6, - (sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 0, sector_gap_filler_byte, - (sector_gap_length != DefaultSectorGapLength) ? 0 : 17, 14, - 0, + 11, 0xff, + 6, + (sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 27, 0xff, 6250); // i.e. 250kbps (including clocks) * 60 = 15000kpm, at 300 rpm => 50 kbits/rotation => 6250 bytes/rotation } @@ -212,9 +211,9 @@ std::shared_ptr Storage::Encodings::MFM::GetMFMTrackWithSe sectors, 50, 0x4e, 12, - (sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 22, sector_gap_filler_byte, - (sector_gap_length != DefaultSectorGapLength) ? 0 : 12, 18, - 32, + 22, 0x4e, + 12, + (sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 54, 0xff, 12500); // unintelligently: double the single-density bytes/rotation (or: 500kps @ 300 rpm) } From ea64125124b7bf329c6580ecc9a6951b311ff660 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Aug 2017 22:15:25 -0400 Subject: [PATCH 10/22] Added an explicit nilling, to help with debugging. --- Concurrency/AsyncTaskQueue.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Concurrency/AsyncTaskQueue.cpp b/Concurrency/AsyncTaskQueue.cpp index 6edd4055b..4202cd275 100644 --- a/Concurrency/AsyncTaskQueue.cpp +++ b/Concurrency/AsyncTaskQueue.cpp @@ -46,6 +46,7 @@ AsyncTaskQueue::AsyncTaskQueue() AsyncTaskQueue::~AsyncTaskQueue() { #ifdef __APPLE__ dispatch_release(serial_dispatch_queue_); + serial_dispatch_queue_ = nullptr; #else should_destruct_ = true; enqueue([](){}); From 7ea703f15030dc237025aca4c17588ea1cbd8be5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 14 Aug 2017 08:38:00 -0400 Subject: [PATCH 11/22] Started making provisions for a DMA-compatible implementation. Re: the CPC, it sounds like DMA acknowledge might be permanently wired, causing DMA mode seemingly to work from the 8272's point of view. --- Components/8272/i8272.cpp | 11 +++++++++-- Components/8272/i8272.hpp | 20 ++++++++++++++++++-- Machines/AmstradCPC/AmstradCPC.cpp | 9 ++++++--- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 78a24a89a..194b8595b 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -11,7 +11,7 @@ #include -using namespace Intel; +using namespace Intel::i8272; #define SetDataRequest() (main_status_ |= 0x80) #define ResetDataRequest() (main_status_ &= ~0x80) @@ -52,8 +52,9 @@ using namespace Intel; #define SetBadCylinder() (status_[2] |= 0x02) #define SetMissingDataAddressMark() (status_[2] |= 0x01) -i8272::i8272(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) : +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), main_status_(0), interesting_event_mask_((int)Event8272::CommandByte), resume_point_(0), @@ -773,3 +774,9 @@ bool i8272::Drive::seek_is_satisfied() { return (target_head_position == head_position) || (target_head_position == -1 && drive->get_is_track_zero()); } + +void i8272::set_dma_acknowledge(bool dack) { +} + +void i8272::set_terminal_count(bool tc) { +} diff --git a/Components/8272/i8272.hpp b/Components/8272/i8272.hpp index 00810ea7b..26766abd3 100644 --- a/Components/8272/i8272.hpp +++ b/Components/8272/i8272.hpp @@ -13,22 +13,38 @@ #include "../../Storage/Disk/Drive.hpp" #include +#include #include namespace Intel { +namespace i8272 { + +class BusHandler { + public: + virtual void set_dma_data_request(bool drq) {} + virtual void set_interrupt(bool irq) {} +}; class i8272: public Storage::Disk::MFMController { public: - i8272(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute); + i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute); void run_for(Cycles); void set_register(int address, uint8_t value); uint8_t get_register(int address); + void set_dma_acknowledge(bool dack); + void set_terminal_count(bool tc); + void set_disk(std::shared_ptr disk, int drive); private: + + // The bus handler, for interrupt and DMA-driven usage. + BusHandler &bus_handler_; + std::unique_ptr allocated_bus_handler_; + // Status registers. uint8_t main_status_; uint8_t status_[3]; @@ -110,6 +126,6 @@ class i8272: public Storage::Disk::MFMController { }; } - +} #endif /* i8272_hpp */ diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 328c233c3..92b5a786e 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -438,12 +438,15 @@ struct KeyboardState { Wraps the 8272 so as to provide proper clocking and RPM counts, and just directly exposes motor control, applying the same value to all drives. */ -class FDC: public Intel::i8272 { +class FDC: public Intel::i8272::i8272 { + private: + Intel::i8272::BusHandler bus_handler_; + public: - FDC() : i8272(Cycles(8000000), 16, 300) {} + FDC() : i8272(bus_handler_, Cycles(8000000), 16, 300) {} void set_motor_on(bool on) { - Intel::i8272::set_motor_on(on); + Intel::i8272::i8272::set_motor_on(on); } }; From b7065575f3b23d0c15bcebba1025cde52ff3910c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 14 Aug 2017 09:04:22 -0400 Subject: [PATCH 12/22] Added (empty) call-ins for DMA usage; switched to having the 'is seeking' bit in the status register stay high until sense interrupt status, but now it goes high even for seeks that don't actually go anywhere, and corrected interpretation of the specify command, with a positive result: the received step rate time, now that it's being interpreted correctly, is much shorter. --- Components/8272/i8272.cpp | 24 +++++++++++++++--------- Components/8272/i8272.hpp | 4 +++- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 194b8595b..a7d1f0754 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -93,7 +93,6 @@ void i8272::run_for(Cycles cycles) { // Check for completion. if(drives_[c].seek_is_satisfied()) { drives_[c].phase = Drive::CompletedSeeking; - main_status_ &= ~(1 << c); if(drives_[c].target_head_position == -1) drives_[c].head_position = 0; break; } @@ -658,13 +657,12 @@ void i8272::posit_event(int event_type) { drives_[drive].target_head_position = (command_.size() > 2) ? command_[2] : -1; drives_[drive].step_rate_counter = 0; drives_[drive].seek_failed = false; + main_status_ |= 1 << (command_[1]&3); printf("Accepted seek to %d\n", drives_[drive].target_head_position); // Check whether any steps are even needed. if(drives_[drive].seek_is_satisfied()) { drives_[drive].phase = Drive::CompletedSeeking; - } else { - main_status_ |= 1 << (command_[1]&3); } } else { printf("Rejected seek to %d\n", (command_.size() > 2) ? command_[2] : -1); @@ -688,6 +686,7 @@ void i8272::posit_event(int event_type) { if(found_drive != -1) { drives_[found_drive].phase = Drive::NotSeeking; status_[0] = (uint8_t)found_drive; + main_status_ &= ~(1 << found_drive); SetSeekEnd(); result_stack_.push_back(drives_[found_drive].head_position); @@ -702,17 +701,17 @@ void i8272::posit_event(int event_type) { specify: // Just store the values, and terminate the command. printf("Specify\n"); - step_rate_time_ = command_[1] &0xf0; // i.e. 16 to 240m - head_unload_time_ = command_[1] & 0x0f; // i.e. 1 to 16ms - head_load_time_ = command_[2] & ~1; // i.e. 2 to 254 ms in increments of 2ms + step_rate_time_ = 16 - (command_[1] >> 4); // i.e. 1 to 16ms + head_unload_time_ = (command_[1] & 0x0f) << 4; // i.e. 16 to 240ms + head_load_time_ = command_[2] & ~1; // i.e. 2 to 254 ms in increments of 2ms - if(!step_rate_time_) step_rate_time_ = 16; - if(!head_unload_time_) head_unload_time_ = 1; + if(!head_unload_time_) head_unload_time_ = 16; if(!head_load_time_) head_load_time_ = 2; dma_mode_ = !(command_[2] & 1); goto wait_for_command; sense_drive_status: + printf("Sense drive status\n"); { int drive = command_[1] & 3; result_stack_.push_back( @@ -749,7 +748,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: "); + printf("Result to %02x: ", command_[0] & 0x1f); for(size_t c = 0; c < result_stack_.size(); c++) { printf("%02x ", result_stack_[result_stack_.size() - 1 - c]); } @@ -780,3 +779,10 @@ void i8272::set_dma_acknowledge(bool dack) { void i8272::set_terminal_count(bool tc) { } + +void i8272::set_data_input(uint8_t value) { +} + +uint8_t i8272::get_data_output() { + return 0xff; +} diff --git a/Components/8272/i8272.hpp b/Components/8272/i8272.hpp index 26766abd3..a0734cb05 100644 --- a/Components/8272/i8272.hpp +++ b/Components/8272/i8272.hpp @@ -31,6 +31,9 @@ class i8272: public Storage::Disk::MFMController { void run_for(Cycles); + void set_data_input(uint8_t value); + uint8_t get_data_output(); + void set_register(int address, uint8_t value); uint8_t get_register(int address); @@ -40,7 +43,6 @@ class i8272: public Storage::Disk::MFMController { void set_disk(std::shared_ptr disk, int drive); private: - // The bus handler, for interrupt and DMA-driven usage. BusHandler &bus_handler_; std::unique_ptr allocated_bus_handler_; From 8a7b23dc9e7ff5683ba5e24eb3a17afd9fb5f4e3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 14 Aug 2017 09:45:39 -0400 Subject: [PATCH 13/22] Ensured data-accessing commands cancel seeks on their drives. Also introduced a count of drives currently seeking in order to make for a slightly better broad-phase test in run_for. --- Components/8272/i8272.cpp | 43 +++++++++++++++++++++++++++------------ Components/8272/i8272.hpp | 2 ++ 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index a7d1f0754..ef6a318ce 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -60,7 +60,8 @@ i8272::i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multipli resume_point_(0), delay_time_(0), head_timers_running_(0), - expects_input_(false) { + expects_input_(false), + drives_seeking_(0) { posit_event((int)Event8272::CommandByte); } @@ -78,7 +79,7 @@ void i8272::run_for(Cycles cycles) { } // update seek status of any drives presently seeking - if(main_status_ & 0xf) { + if(drives_seeking_) { for(int c = 0; c < 4; c++) { if(drives_[c].phase == Drive::Seeking) { drives_[c].step_rate_counter += cycles.as_int(); @@ -93,6 +94,7 @@ void i8272::run_for(Cycles cycles) { // Check for completion. if(drives_[c].seek_is_satisfied()) { drives_[c].phase = Drive::CompletedSeeking; + drives_seeking_--; if(drives_[c].target_head_position == -1) drives_[c].head_position = 0; break; } @@ -197,6 +199,10 @@ void i8272::set_disk(std::shared_ptr disk, int drive) { set_drive(drives_[active_drive_].drive); \ drives_[active_drive_].drive->set_head((unsigned int)active_head_); \ set_is_double_density(command_[0] & 0x40); \ + if(drives_[active_drive_].phase == Drive::Seeking) { \ + drives_[active_drive_].phase = Drive::NotSeeking; \ + drives_seeking_--; \ + }\ invalidate_track(); #define LOAD_HEAD() \ @@ -646,26 +652,37 @@ void i8272::posit_event(int event_type) { seek: printf((command_.size() > 2) ? "Seek\n" : "Recalibrate\n"); - // Declines to act if a seek is already ongoing; otherwise resets all status registers, sets the drive - // into seeking mode, sets the drive's main status seeking bit, and sets the target head position: for - // a recalibrate the target is -1 and ::run_for knows that -1 means the terminal condition is the drive - // returning that its at track zero, and that it should reset the drive's current position once reached. - if(drives_[command_[1]&3].phase != Drive::Seeking) { + { int drive = command_[1]&3; + + // Increment the seeking count if this drive wasn't already seeking. + if(drives_[drive].phase != Drive::Seeking) { + drives_seeking_++; + } + + // Set currently seeking, with a step to occur right now (yes, it sounds like jamming these + // in could damage your drive motor). drives_[drive].phase = Drive::Seeking; + drives_[drive].step_rate_counter = 8000 * step_rate_time_; drives_[drive].steps_taken = 0; - drives_[drive].target_head_position = (command_.size() > 2) ? command_[2] : -1; - drives_[drive].step_rate_counter = 0; drives_[drive].seek_failed = false; main_status_ |= 1 << (command_[1]&3); - printf("Accepted seek to %d\n", drives_[drive].target_head_position); - // Check whether any steps are even needed. + // If this is a seek, set the processor-supplied target location; otherwise it is a recalibrate, + // which means resetting the current state now but aiming to hit '-1' (which the stepping code + // up in run_for understands to mean 'keep going until track 0 is active'). + if(command_.size() > 2) { + drives_[drive].target_head_position = command_[2]; + } else { + drives_[drive].target_head_position = -1; + drives_[drive].head_position = 0; + } + + // Check whether any steps are even needed; if not then mark as completed already. if(drives_[drive].seek_is_satisfied()) { drives_[drive].phase = Drive::CompletedSeeking; + drives_seeking_--; } - } else { - printf("Rejected seek to %d\n", (command_.size() > 2) ? command_[2] : -1); } goto wait_for_command; diff --git a/Components/8272/i8272.hpp b/Components/8272/i8272.hpp index a0734cb05..61729874a 100644 --- a/Components/8272/i8272.hpp +++ b/Components/8272/i8272.hpp @@ -81,6 +81,7 @@ class i8272: public Storage::Disk::MFMController { Seeking, CompletedSeeking } phase; + bool did_seek; bool seek_failed; // Seeking: transient state. @@ -103,6 +104,7 @@ class i8272: public Storage::Disk::MFMController { drive(new Storage::Disk::Drive), head_is_loaded{false, false} {}; } drives_[4]; + int drives_seeking_; // User-supplied parameters; as per the specify command. int step_rate_time_; From 0e083e9dc6983164c28de953e62de104a9b3f20b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 14 Aug 2017 09:48:56 -0400 Subject: [PATCH 14/22] Factored composition of a run command out, as I think I need to worry about extensions, and can trim spaces. --- StaticAnalyser/AmstradCPC/StaticAnalyser.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp index da34bfabe..8d76afe68 100644 --- a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp +++ b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp @@ -21,12 +21,16 @@ static bool strcmp_insensitive(const char *a, const char *b) { return true; } +static std::string RunCommandFor(const Storage::Disk::CPM::File &file) { + return "run\"" + file.name + "\n"; +} + static void InspectDataCatalogue( const std::unique_ptr &data_catalogue, StaticAnalyser::Target &target) { // If there's just one file, run that. if(data_catalogue->files.size() == 1) { - target.loadingCommand = "run\"" + data_catalogue->files[0].name + "\n"; + target.loadingCommand = RunCommandFor(data_catalogue->files[0]); return; } @@ -61,7 +65,7 @@ static void InspectDataCatalogue( } if(basic_files == 1 || implicit_suffixed_files == 1) { size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file; - target.loadingCommand = "run\"" + data_catalogue->files[selected_file].name + "\n"; + target.loadingCommand = RunCommandFor(data_catalogue->files[selected_file]); return; } From 7264fbb3d2c64cb181e038498d03845d67169933 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 14 Aug 2017 09:58:55 -0400 Subject: [PATCH 15/22] read_id now clears status. I probably need to find a way to generalise this. --- Components/8272/i8272.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index ef6a318ce..322664324 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -513,6 +513,7 @@ void i8272::posit_event(int event_type) { printf("Read ID\n"); SET_DRIVE_HEAD_MFM(); LOAD_HEAD(); + CLEAR_STATUS(); // 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. @@ -650,8 +651,6 @@ void i8272::posit_event(int event_type) { // occurs in ::run_for; this merely establishes that seeking should be ongoing. recalibrate: seek: - printf((command_.size() > 2) ? "Seek\n" : "Recalibrate\n"); - { int drive = command_[1]&3; @@ -673,9 +672,11 @@ void i8272::posit_event(int event_type) { // up in run_for understands to mean 'keep going until track 0 is active'). if(command_.size() > 2) { drives_[drive].target_head_position = command_[2]; + printf("Seek to %02x\n", command_[2]); } else { drives_[drive].target_head_position = -1; drives_[drive].head_position = 0; + printf("Recalibrate\n"); } // Check whether any steps are even needed; if not then mark as completed already. From cefec7a19ffeb91427ab7aaf2d556abc3c32443a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 14 Aug 2017 10:37:39 -0400 Subject: [PATCH 16/22] Sought more robustly (i.e. less repetitively) to handle dispatch, including cancelling seeks where appropriate. --- Components/8272/i8272.cpp | 117 +++++++++++++++----------------------- Storage/Disk/Drive.cpp | 1 + 2 files changed, 46 insertions(+), 72 deletions(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 322664324..9f259137c 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -88,14 +88,14 @@ void i8272::run_for(Cycles cycles) { while(steps--) { // Perform a step. int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1; + printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position); drives_[c].drive->step(direction); - drives_[c].head_position += direction; + if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction; // Check for completion. if(drives_[c].seek_is_satisfied()) { drives_[c].phase = Drive::CompletedSeeking; drives_seeking_--; - if(drives_[c].target_head_position == -1) drives_[c].head_position = 0; break; } } @@ -190,19 +190,12 @@ void i8272::set_disk(std::shared_ptr disk, int drive) { if(distance_into_section_ < 6) goto CONCAT(read_header, __LINE__); \ set_data_mode(Scanning); -#define CLEAR_STATUS() \ - status_[0] = status_[1] = status_[2] = 0; - #define SET_DRIVE_HEAD_MFM() \ active_drive_ = command_[1]&3; \ active_head_ = (command_[1] >> 2)&1; \ set_drive(drives_[active_drive_].drive); \ drives_[active_drive_].drive->set_head((unsigned int)active_head_); \ set_is_double_density(command_[0] & 0x40); \ - if(drives_[active_drive_].phase == Drive::Seeking) { \ - drives_[active_drive_].phase = Drive::NotSeeking; \ - drives_seeking_--; \ - }\ invalidate_track(); #define LOAD_HEAD() \ @@ -248,76 +241,59 @@ void i8272::posit_event(int event_type) { WAIT_FOR_EVENT(Event8272::CommandByte) SetBusy(); + static const size_t required_lengths[32] = { + 0, 0, 9, 3, 2, 9, 9, 2, + 1, 9, 2, 9, 0, 6, 0, 3, + 0, 9, 0, 0, 0, 0, 0, 0, + 0, 9, 0, 0, 0, 9, 0, 0, + }; + + if(command_.size() < required_lengths[command_[0] & 0x1f]) goto wait_for_complete_command_sequence; + ResetDataRequest(); + status_[0] = status_[1] = status_[2] = 0; + + // 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 + break; + + default: + for(int c = 0; c < 4; c++) { + if(drives_[c].phase == Drive::Seeking) { + drives_[c].phase = Drive::NotSeeking; + drives_seeking_--; + } + } + break; + } + + // Jump to the proper place. switch(command_[0] & 0x1f) { case 0x06: // read data case 0x0b: // read deleted data - if(command_.size() < 9) goto wait_for_complete_command_sequence; - ResetDataRequest(); goto read_data; case 0x05: // write data case 0x09: // write deleted data - if(command_.size() < 9) goto wait_for_complete_command_sequence; - ResetDataRequest(); goto write_data; - case 0x02: // read track - if(command_.size() < 9) goto wait_for_complete_command_sequence; - ResetDataRequest(); - goto read_track; + 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 0x0a: // read ID - if(command_.size() < 2) goto wait_for_complete_command_sequence; - ResetDataRequest(); - goto read_id; - - case 0x0d: // format track - if(command_.size() < 6) goto wait_for_complete_command_sequence; - ResetDataRequest(); - goto format_track; - - case 0x11: // scan low - if(command_.size() < 9) goto wait_for_complete_command_sequence; - ResetDataRequest(); - goto scan_low; - - case 0x19: // scan low or equal - if(command_.size() < 9) goto wait_for_complete_command_sequence; - ResetDataRequest(); - goto scan_low_or_equal; - - case 0x1d: // scan high or equal - if(command_.size() < 9) goto wait_for_complete_command_sequence; - ResetDataRequest(); - goto scan_high_or_equal; - - case 0x07: // recalibrate - if(command_.size() < 2) goto wait_for_complete_command_sequence; - ResetDataRequest(); - goto recalibrate; - - case 0x08: // sense interrupt status - ResetDataRequest(); - goto sense_interrupt_status; - - case 0x03: // specify - if(command_.size() < 3) goto wait_for_complete_command_sequence; - ResetDataRequest(); - goto specify; - - case 0x04: // sense drive status - if(command_.size() < 2) goto wait_for_complete_command_sequence; - ResetDataRequest(); - goto sense_drive_status; - - case 0x0f: // seek - if(command_.size() < 3) goto wait_for_complete_command_sequence; - ResetDataRequest(); - goto seek; - - default: // invalid - ResetDataRequest(); - goto invalid; + default: goto invalid; } // Decodes drive, head and density, loads the head, loads the internal cylinder, head, sector and size registers, @@ -329,7 +305,6 @@ void i8272::posit_event(int event_type) { if(!dma_mode_) SetNonDMAExecution(); SET_DRIVE_HEAD_MFM(); LOAD_HEAD(); - CLEAR_STATUS(); cylinder_ = command_[2]; head_ = command_[3]; sector_ = command_[4]; @@ -513,7 +488,6 @@ void i8272::posit_event(int event_type) { printf("Read ID\n"); SET_DRIVE_HEAD_MFM(); LOAD_HEAD(); - CLEAR_STATUS(); // 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. @@ -539,7 +513,6 @@ void i8272::posit_event(int event_type) { if(!dma_mode_) SetNonDMAExecution(); SET_DRIVE_HEAD_MFM(); LOAD_HEAD(); - CLEAR_STATUS(); // Wait for the index hole. WAIT_FOR_EVENT(Event::IndexHole); diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index 1d5a80d69..b8124dc90 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -34,6 +34,7 @@ bool Drive::get_is_track_zero() { void Drive::step(int direction) { head_position_ = std::max(head_position_ + direction, 0); + printf("Head -> %d\n", head_position_); } void Drive::set_head(unsigned int head) { From a10389a22c8a439599a4d5e08a522f066ec7c4d7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 14 Aug 2017 12:42:22 -0400 Subject: [PATCH 17/22] Factored out the stuff of stuffing the bus. --- Components/6845/CRTC6845.hpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Components/6845/CRTC6845.hpp b/Components/6845/CRTC6845.hpp index 1fe909a51..79cff13f4 100644 --- a/Components/6845/CRTC6845.hpp +++ b/Components/6845/CRTC6845.hpp @@ -140,9 +140,7 @@ template class CRTC6845 { character_is_visible_ = true; } - bus_state_.display_enable = character_is_visible_ && line_is_visible_; - bus_state_.refresh_address &= 0x3fff; - bus_handler_.perform_bus_cycle(bus_state_); + perform_bus_cycle(); } } @@ -175,6 +173,12 @@ template class CRTC6845 { } private: + inline void perform_bus_cycle() { + bus_state_.display_enable = character_is_visible_ && line_is_visible_; + bus_state_.refresh_address &= 0x3fff; + bus_handler_.perform_bus_cycle(bus_state_); + } + Personality personality_; T &bus_handler_; BusState bus_state_; From 2e5ad19fe1e574e01fa60ed6eb6885d194c980ed Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 14 Aug 2017 12:42:48 -0400 Subject: [PATCH 18/22] Minor tidying. --- Components/8272/i8272.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 9f259137c..4c6989f7b 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -346,7 +346,7 @@ void i8272::posit_event(int event_type) { // Performs the read data or read deleted data command. read_data: - printf("Read data\n"); + printf("Read data [%02x %02x %02x %02x ... %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[8]); read_next_data: goto read_write_find_header; @@ -354,7 +354,6 @@ void i8272::posit_event(int event_type) { // flag doesn't match the sort the command was looking for. read_data_found_header: FIND_DATA(); - distance_into_section_ = 0; 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. @@ -364,13 +363,14 @@ void i8272::posit_event(int event_type) { goto read_next_data; } } + + distance_into_section_ = 0; set_data_mode(Reading); // Waits for the next token, then supplies it to the CPU by: (i) setting data request and direction; and (ii) resetting // data request once the byte has been taken. Continues until all bytes have been read. // - // TODO: signal if the CPU is too slow and missed a byte; at the minute it'll just silently miss. Also allow for other - // ways that sector size might have been specified. + // TODO: consider DTL. get_byte: WAIT_FOR_EVENT(Event::Token); result_stack_.push_back(get_latest_token().byte_value); From 334872d374f40cc126622282b30d0eafc7fefae6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 14 Aug 2017 12:47:11 -0400 Subject: [PATCH 19/22] Clarified, slightly. --- Machines/AmstradCPC/AmstradCPC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 92b5a786e..0c7b71fc2 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -537,7 +537,7 @@ class ConcreteMachine: public: ConcreteMachine() : z80_(*this), - crtc_counter_(HalfCycles(4)), // This starts the CRTC exactly out of phase with the memory accesses + crtc_counter_(HalfCycles(4)), // This starts the CRTC exactly out of phase with the CPU's memory accesses crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_), crtc_bus_handler_(ram_, interrupt_timer_), i8255_(i8255_port_handler_), From 0da02d3902a028aca256b4675d9b896aebb45af1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 14 Aug 2017 12:53:18 -0400 Subject: [PATCH 20/22] Added read/write escape clauses if faced with a read-only disk. --- Components/8272/i8272.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 4c6989f7b..001330f7b 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -302,8 +302,6 @@ void i8272::posit_event(int event_type) { 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. - if(!dma_mode_) SetNonDMAExecution(); - SET_DRIVE_HEAD_MFM(); LOAD_HEAD(); cylinder_ = command_[2]; head_ = command_[3]; @@ -347,6 +345,8 @@ void i8272::posit_event(int event_type) { // 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(); read_next_data: goto read_write_find_header; @@ -412,7 +412,14 @@ void i8272::posit_event(int event_type) { goto post_st012chrn; write_data: - printf("Write [deleted] data\n"); + printf("Write [deleted] data\n"); + if(!dma_mode_) SetNonDMAExecution(); + SET_DRIVE_HEAD_MFM(); + + if(drives_[active_drive_].drive->get_is_read_only()) { + SetNotWriteable(); + goto abort_read_write; + } write_next_data: goto read_write_find_header; @@ -512,6 +519,11 @@ void i8272::posit_event(int event_type) { 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; + } + LOAD_HEAD(); // Wait for the index hole. From 7b8bb0297a86c6b4d891748d95f05cb69307b557 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 14 Aug 2017 13:03:17 -0400 Subject: [PATCH 21/22] Implemented single density version of format track. --- Components/8272/i8272.cpp | 72 ++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 001330f7b..87b18f5fa 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -531,32 +531,32 @@ void i8272::posit_event(int event_type) { begin_writing(); // Write start-of-track. - // TODO: single density. - for(int c = 0; c < 80; c++) { - write_byte(0x4e); - } - for(int c = 0; c < 12; c++) { - write_byte(0x00); - } - for(int c = 0; c < 3; c++) { - write_raw_short(Storage::Encodings::MFM::MFMIndexSync); - } - write_byte(Storage::Encodings::MFM::IndexAddressByte); - for(int c = 0; c < 50; c++) { - write_byte(0x4e); + if(get_is_double_density()) { + for(int c = 0; c < 80; c++) write_byte(0x4e); + for(int c = 0; c < 12; c++) write_byte(0x00); + for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMIndexSync); + write_byte(Storage::Encodings::MFM::IndexAddressByte); + for(int c = 0; c < 50; c++) write_byte(0x4e); + } else { + for(int c = 0; c < 40; c++) write_byte(0xff); + for(int c = 0; c < 6; c++) write_byte(0x00); + write_raw_short(Storage::Encodings::MFM::FMIndexAddressMark); + for(int c = 0; c < 26; c++) write_byte(0xff); } WAIT_FOR_EVENT(Event::DataWritten); sector_ = 0; format_track_write_sector: - for(int c = 0; c < 12; c++) { - write_byte(0x00); + if(get_is_double_density()) { + for(int c = 0; c < 12; c++) write_byte(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(Storage::Encodings::MFM::IDAddressByte); + } else { + for(int c = 0; c < 6; c++) write_byte(0x00); + get_crc_generator().reset(); + write_raw_short(Storage::Encodings::MFM::FMIDAddressMark); } - 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(Storage::Encodings::MFM::IDAddressByte); // Write the sector header, obtaining its contents // from the processor. @@ -578,36 +578,38 @@ void i8272::posit_event(int event_type) { write_crc(); // Write the sector body. - for(int c = 0; c < 22; c++) { - write_byte(0x4e); + if(get_is_double_density()) { + for(int c = 0; c < 22; c++) write_byte(0x4e); + for(int c = 0; c < 12; c++) write_byte(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(Storage::Encodings::MFM::DataAddressByte); + } else { + for(int c = 0; c < 11; c++) write_byte(0xff); + for(int c = 0; c < 6; c++) write_byte(0x00); + get_crc_generator().reset(); + write_raw_short(Storage::Encodings::MFM::FMDataAddressMark); } - for(int c = 0; c < 12; c++) { - write_byte(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(Storage::Encodings::MFM::DataAddressByte); for(int c = 0; c < (128 << command_[2]); c++) { write_byte(command_[5]); } write_crc(); // Write the prescribed gap. - for(int c = 0; c < command_[4]; c++) { - write_byte(0x4e); + if(get_is_double_density()) { + for(int c = 0; c < command_[4]; c++) write_byte(0x4e); + } else { + for(int c = 0; c < command_[4]; c++) write_byte(0xff); } // Consider repeating. sector_++; - if(sector_ < command_[3]) { + if(sector_ < command_[3]) goto format_track_write_sector; - } // Otherwise, pad out to the index hole. format_track_pad: - write_byte(0x4e); + write_byte(get_is_double_density() ? 0x4e : 0xff); WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole); if(event_type != (int)Event::IndexHole) goto format_track_pad; From 9038ba622e7f4b428cbfe5de2db11406fd9b96a5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 14 Aug 2017 14:34:56 -0400 Subject: [PATCH 22/22] Added a quick version of read track. --- Components/8272/i8272.cpp | 52 ++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 87b18f5fa..eb23cf694 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -371,7 +371,7 @@ void i8272::posit_event(int event_type) { // data request once the byte has been taken. Continues until all bytes have been read. // // TODO: consider DTL. - get_byte: + read_data_get_byte: WAIT_FOR_EVENT(Event::Token); result_stack_.push_back(get_latest_token().byte_value); distance_into_section_++; @@ -381,7 +381,7 @@ void i8272::posit_event(int event_type) { switch(event_type) { case (int)Event8272::ResultEmpty: // The caller read the byte in time; proceed as normal. ResetDataRequest(); - if(distance_into_section_ < (128 << size_)) goto get_byte; + if(distance_into_section_ < (128 << size_)) goto read_data_get_byte; break; case (int)Event::Token: // The caller hasn't read the old byte yet and a new one has arrived SetOverrun(); @@ -485,10 +485,6 @@ void i8272::posit_event(int event_type) { goto post_st012chrn; - read_track: - printf("Read track unimplemented!!\n"); - goto wait_for_command; - // Performs the read ID command. read_id: // Establishes the drive and head being addressed, and whether in double density mode. @@ -515,6 +511,50 @@ void i8272::posit_event(int event_type) { goto post_st012chrn; + // 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); + + sector_ = 0; + index_hole_limit_ = 2; + + // While not index hole again, stream all sector contents until EOT sectors have been read. + read_track_next_sector: + FIND_HEADER(); + if(!index_hole_limit_) { + if(!sector_) { + SetMissingAddressMark(); + goto abort_read_write; + } else { + goto post_st012chrn; + } + } + READ_HEADER(); + + FIND_DATA(); + distance_into_section_ = 0; + SetDataDirectionToProcessor(); + read_track_get_byte: + WAIT_FOR_EVENT(Event::Token); + result_stack_.push_back(get_latest_token().byte_value); + distance_into_section_++; + SetDataRequest(); + // TODO: other possible exit conditions; find a way to merge with the read_data version of this. + WAIT_FOR_EVENT((int)Event8272::ResultEmpty); + ResetDataRequest(); + if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte; + + sector_++; + if(sector_ < command_[6]) goto read_track_next_sector; + + goto post_st012chrn; + + // Performs format [/write] track. format_track: printf("Format track\n"); if(!dma_mode_) SetNonDMAExecution();