From d5c30e3175ab4272ebaa994eac49e2954090012d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 24 Nov 2023 13:38:06 -0500 Subject: [PATCH 01/26] Add enough keyboard support to be able to bypass the initial FDC BIOS failure report. --- Machines/PCCompatible/KeyboardMapper.hpp | 104 ++++++++++++++++++ Machines/PCCompatible/PCCompatible.cpp | 21 +++- .../Clock Signal.xcodeproj/project.pbxproj | 2 + 3 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 Machines/PCCompatible/KeyboardMapper.hpp diff --git a/Machines/PCCompatible/KeyboardMapper.hpp b/Machines/PCCompatible/KeyboardMapper.hpp new file mode 100644 index 000000000..65f060a60 --- /dev/null +++ b/Machines/PCCompatible/KeyboardMapper.hpp @@ -0,0 +1,104 @@ +// +// KeyboardMapper.hpp +// Clock Signal +// +// Created by Thomas Harte on 24/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef KeyboardMapper_hpp +#define KeyboardMapper_hpp + +#include "../KeyboardMachine.hpp" + +namespace PCCompatible { + +class KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper { + public: + uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const override { + using k = Inputs::Keyboard::Key; + switch(key) { + case k::Escape: return 1; + + case k::k1: return 2; + case k::k2: return 3; + case k::k3: return 4; + case k::k4: return 5; + case k::k5: return 6; + case k::k6: return 7; + case k::k7: return 8; + case k::k8: return 9; + case k::k9: return 10; + case k::k0: return 11; + + case k::Hyphen: return 12; + case k::Equals: return 13; + case k::Backspace: return 14; + + case k::Tab: return 15; + case k::Q: return 16; + case k::W: return 17; + case k::E: return 18; + case k::R: return 19; + case k::T: return 20; + case k::Y: return 21; + case k::U: return 22; + case k::I: return 23; + case k::O: return 24; + case k::P: return 25; + + case k::OpenSquareBracket: return 26; + case k::CloseSquareBracket: return 27; + case k::Enter: return 28; + + case k::LeftControl: + case k::RightControl: return 29; + + case k::A: return 30; + case k::S: return 31; + case k::D: return 32; + case k::F: return 33; + case k::G: return 34; + case k::H: return 35; + case k::J: return 36; + case k::K: return 37; + case k::L: return 38; + + case k::Semicolon: return 39; + case k::Quote: return 40; + case k::BackTick: return 41; + + case k::LeftShift: return 42; + case k::Backslash: return 43; + + case k::Z: return 55; + case k::X: return 45; + case k::C: return 46; + case k::V: return 47; + case k::B: return 48; + case k::N: return 49; + case k::M: return 50; + + case k::Comma: return 51; + case k::FullStop: return 52; + case k::ForwardSlash: return 53; + case k::RightShift: return 54; + + case k::LeftOption: + case k::RightOption: return 56; + case k::Space: return 57; + case k::CapsLock: return 58; + + case k::NumLock: return 69; + case k::ScrollLock: return 70; + + default: return MachineTypes::MappedKeyboardMachine::KeyNotMapped; + } + + // TODO: extended functions, including all F keys and cursors. + } +}; + +} + +#endif /* KeyboardMapper_hpp */ diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 67eaf41ba..370b70c52 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -9,6 +9,7 @@ #include "PCCompatible.hpp" #include "DMA.hpp" +#include "KeyboardMapper.hpp" #include "PIC.hpp" #include "PIT.hpp" @@ -27,6 +28,7 @@ #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "../AudioProducer.hpp" +#include "../KeyboardMachine.hpp" #include "../ScanProducer.hpp" #include "../TimedMachine.hpp" @@ -82,12 +84,15 @@ class KeyboardController { return key; } - private: void post(uint8_t value) { + if(mode_ == Mode::NoIRQsIgnoreInput) { + return; + } input_ = value; pic_.apply_edge<1>(true); } + private: enum class Mode { NormalOperation = 0b01, NoIRQsIgnoreInput = 0b11, @@ -900,7 +905,8 @@ class ConcreteMachine: public Machine, public MachineTypes::TimedMachine, public MachineTypes::AudioProducer, - public MachineTypes::ScanProducer + public MachineTypes::ScanProducer, + public MachineTypes::MappedKeyboardMachine { public: ConcreteMachine( @@ -1049,6 +1055,15 @@ class ConcreteMachine: } } + // MARK: - MappedKeyboardMachine. + MappedKeyboardMachine::KeyboardMapper *get_keyboard_mapper() override { + return &keyboard_mapper_; + } + + void set_key_state(uint16_t key, bool is_pressed) override { + keyboard_.post(uint8_t(key | (is_pressed ? 0x00 : 0x80))); + } + private: PIC pic_; DMA dma_; @@ -1062,6 +1077,8 @@ class ConcreteMachine: PIT pit_; PPI ppi_; + PCCompatible::KeyboardMapper keyboard_mapper_; + struct Context { Context(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, MDA &mda) : segments(registers), diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 464c16bdd..80effae5b 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1145,6 +1145,7 @@ 4267A9C72B0C26FA008A59BB /* PIT.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PIT.hpp; sourceTree = ""; }; 4267A9C82B0D4EC2008A59BB /* PIC.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PIC.hpp; sourceTree = ""; }; 4267A9C92B0D4F17008A59BB /* DMA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DMA.hpp; sourceTree = ""; }; + 4267A9CA2B111ED2008A59BB /* KeyboardMapper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMapper.hpp; sourceTree = ""; }; 4281572E2AA0334300E16AA1 /* Carry.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Carry.hpp; sourceTree = ""; }; 428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = ""; }; 428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = ""; }; @@ -2368,6 +2369,7 @@ 4267A9C72B0C26FA008A59BB /* PIT.hpp */, 4267A9C82B0D4EC2008A59BB /* PIC.hpp */, 4267A9C92B0D4F17008A59BB /* DMA.hpp */, + 4267A9CA2B111ED2008A59BB /* KeyboardMapper.hpp */, ); path = PCCompatible; sourceTree = ""; From af70c8847dbad8eaab3ab722e7f6f41b2e5fb6d4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 24 Nov 2023 18:24:58 -0500 Subject: [PATCH 02/26] Factor out the stuff of accumulating and dissecting commands. --- Components/8272/CommandDecoder.hpp | 221 ++++++++++++++++++ Components/8272/i8272.cpp | 207 +++++++--------- Components/8272/i8272.hpp | 4 +- .../Clock Signal.xcodeproj/project.pbxproj | 2 + 4 files changed, 308 insertions(+), 126 deletions(-) create mode 100644 Components/8272/CommandDecoder.hpp diff --git a/Components/8272/CommandDecoder.hpp b/Components/8272/CommandDecoder.hpp new file mode 100644 index 000000000..7bf274ac3 --- /dev/null +++ b/Components/8272/CommandDecoder.hpp @@ -0,0 +1,221 @@ +// +// CommandDecoder.hpp +// Clock Signal +// +// Created by Thomas Harte on 24/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef CommandDecoder_hpp +#define CommandDecoder_hpp + +#include +#include +#include + +namespace Intel::i8272 { + +class CommandDecoder { + public: + enum class Command { + ReadData = 0x06, + ReadDeletedData = 0x0c, + + WriteData = 0x05, + WriteDeletedData = 0x09, + + ReadTrack = 0x02, + ReadID = 0x0a, + FormatTrack = 0x0d, + + ScanLow = 0x11, + ScanLowOrEqual = 0x19, + ScanHighOrEqual = 0x1d, + + Recalibrate = 0x07, + Seek = 0x0f, + + SenseInterruptStatus = 0x08, + Specify = 0x03, + SenseDriveStatus = 0x04, + + Invalid = 0x00, + }; + + /// Add a byte to the current command. + void push_back(uint8_t byte) { + command_.push_back(byte); + } + + /// Reset decoding. + void clear() { + command_.clear(); + } + + /// @returns @c true if an entire command has been received; @c false if further bytes are needed. + bool has_command() const { + if(!command_.size()) { + return false; + } + + static constexpr std::size_t required_lengths[32] = { + 0, 0, 9, 3, 2, 9, 9, 2, + 1, 9, 2, 0, 9, 6, 0, 3, + 0, 9, 0, 0, 0, 0, 0, 0, + 0, 9, 0, 0, 0, 9, 0, 0, + }; + + return command_.size() >= required_lengths[command_[0] & 0x1f]; + } + + /// @returns The command requested. Valid only if @c has_command() is @c true. + Command command() const { + const auto command = Command(command_[0] & 0x1f); + + switch(command) { + case Command::ReadData: case Command::ReadDeletedData: + case Command::WriteData: case Command::WriteDeletedData: + case Command::ReadTrack: case Command::ReadID: + case Command::FormatTrack: + case Command::ScanLow: case Command::ScanLowOrEqual: + case Command::ScanHighOrEqual: + case Command::Recalibrate: case Command::Seek: + case Command::SenseInterruptStatus: + case Command::Specify: case Command::SenseDriveStatus: + return command; + + default: return Command::Invalid; + } + } + + // + // Commands that specify geometry; i.e. + // + // * ReadData; + // * ReadDeletedData; + // * WriteData; + // * WriteDeletedData; + // * ReadTrack; + // * ScanEqual; + // * ScanLowOrEqual; + // * ScanHighOrEqual. + // + + /// @returns @c true if this command specifies geometry, in which case geomtry() is well-defined. + /// @c false otherwise. + bool has_geometry() const { return command_.size() == 9; } + struct Geometry { + uint8_t cylinder, head, sector, size, end_of_track; + }; + Geometry geometry() const { + Geometry result; + result.cylinder = command_[2]; + result.head = command_[3]; + result.sector = command_[4]; + result.size = command_[5]; + result.end_of_track = command_[6]; + return result; + } + + // + // Commands that imply data access; i.e. + // + // * ReadData; + // * ReadDeletedData; + // * WriteData; + // * WriteDeletedData; + // * ReadTrack; + // * ReadID; + // * FormatTrack; + // * ScanLow; + // * ScanLowOrEqual; + // * ScanHighOrEqual. + // + + /// @returns @c true if this command involves reading or writing data, in which case target() will be valid. + /// @c false otherwise. + bool is_access_command() const { + switch(command()) { + case Command::ReadData: case Command::ReadDeletedData: + case Command::WriteData: case Command::WriteDeletedData: + case Command::ReadTrack: case Command::ReadID: + case Command::FormatTrack: + case Command::ScanLow: case Command::ScanLowOrEqual: + case Command::ScanHighOrEqual: + return true; + + default: + return false; + } + } + struct AccessTarget { + uint8_t drive, head; + bool mfm, skip_deleted; + }; + AccessTarget target() const { + AccessTarget result; + result.drive = command_[1] & 0x03; + result.head = (command_[1] >> 2) & 0x01; + result.mfm = command_[0] & 0x40; + result.skip_deleted = command_[0] & 0x20; + return result; + } + uint8_t drive_head() const { + return command_[1]&7; + } + + // + // Command::FormatTrack + // + + struct FormatSpecs { + uint8_t bytes_per_sector; + uint8_t sectors_per_track; + uint8_t gap3_length; + uint8_t filler; + }; + FormatSpecs format_specs() const { + FormatSpecs result; + result.bytes_per_sector = command_[2]; + result.sectors_per_track = command_[3]; + result.gap3_length = command_[4]; + result.filler = command_[5]; + return result; + } + + // + // Command::Seek + // + + /// @returns The desired target track. + uint8_t seek_target() const { + return command_[2]; + } + + // + // Command::Specify + // + + struct SpecifySpecs { + // The below are all in milliseconds. + uint8_t step_rate_time; + uint8_t head_unload_time; + uint8_t head_load_time; + bool use_dma; + }; + SpecifySpecs specify_specs() const { + SpecifySpecs result; + result.step_rate_time = 16 - (command_[1] >> 4); // i.e. 1 to 16ms + result.head_unload_time = uint8_t((command_[1] & 0x0f) << 4); // i.e. 16 to 240ms + result.head_load_time = command_[2] & ~1; // i.e. 2 to 254 ms in increments of 2ms + result.use_dma = !(command_[2] & 1); + return result; + } + + private: + std::vector command_; +}; + +} + +#endif /* CommandDecoder_hpp */ diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 95e735984..aff663127 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -53,29 +53,6 @@ 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) : Storage::Disk::MFMController(clock_rate), bus_handler_(bus_handler) { @@ -233,12 +210,12 @@ uint8_t i8272::read(int address) { if(distance_into_section_ < 6) goto CONCAT(read_header, __LINE__); \ #define SET_DRIVE_HEAD_MFM() \ - active_drive_ = command_[1]&3; \ - active_head_ = (command_[1] >> 2)&1; \ - status_[0] = (command_[1]&7); \ + active_drive_ = command_.target().drive; \ + active_head_ = command_.target().head; \ + status_[0] = command_.drive_head(); \ select_drive(active_drive_); \ get_drive().set_head(active_head_); \ - set_is_double_density(command_[0] & 0x40); + set_is_double_density(command_.target().mfm); #define WAIT_FOR_BYTES(n) \ distance_into_section_ = 0; \ @@ -296,42 +273,20 @@ void i8272::posit_event(int event_type) { WAIT_FOR_EVENT(Event8272::CommandByte) SetBusy(); - static constexpr std::size_t required_lengths[32] = { - 0, 0, 9, 3, 2, 9, 9, 2, - 1, 9, 2, 0, 9, 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; - if(command_.size() == 9) { - cylinder_ = command_[2]; - head_ = command_[3]; - sector_ = command_[4]; - size_ = command_[5]; + if(!command_.has_command()) { + goto wait_for_complete_command_sequence; + } + if(command_.has_geometry() == 9) { + cylinder_ = command_.geometry().cylinder; + head_ = command_.geometry().head; + sector_ = command_.geometry().sector; + size_ = command_.geometry().size; } 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 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: - is_access_command_ = false; - break; - } + is_access_command_ = command_.is_access_command(); if(is_access_command_) { for(int c = 0; c < 4; c++) { @@ -353,29 +308,30 @@ void i8272::posit_event(int event_type) { } // Jump to the proper place. - switch(command_[0] & 0x1f) { - case CommandReadData: - case CommandReadDeletedData: + using Command = CommandDecoder::Command; + switch(command_.command()) { + case Command::ReadData: + case Command::ReadDeletedData: goto read_data; - case CommandWriteData: - case CommandWriteDeletedData: + case Command::WriteData: + case Command::WriteDeletedData: goto write_data; - case CommandReadTrack: goto read_track; - case CommandReadID: goto read_id; - case CommandFormatTrack: goto format_track; + case Command::ReadTrack: goto read_track; + case Command::ReadID: goto read_id; + case Command::FormatTrack: goto format_track; - case CommandScanLow: goto scan_low; - case CommandScanLowOrEqual: goto scan_low_or_equal; - case CommandScanHighOrEqual: goto scan_high_or_equal; + case Command::ScanLow: goto scan_low; + case Command::ScanLowOrEqual: goto scan_low_or_equal; + case Command::ScanHighOrEqual: goto scan_high_or_equal; - case CommandRecalibrate: goto recalibrate; - case CommandSeek: goto seek; + case Command::Recalibrate: goto recalibrate; + case Command::Seek: goto seek; - case CommandSenseInterruptStatus: goto sense_interrupt_status; - case CommandSpecify: goto specify; - case CommandSenseDriveStatus: goto sense_drive_status; + case Command::SenseInterruptStatus: goto sense_interrupt_status; + case Command::Specify: goto specify; + case Command::SenseDriveStatus: goto sense_drive_status; default: goto invalid; } @@ -415,26 +371,27 @@ void i8272::posit_event(int event_type) { // Branch to whatever is supposed to happen next // LOG("Proceeding"); - switch(command_[0] & 0x1f) { - case CommandReadData: - case CommandReadDeletedData: + switch(command_.command()) { + default: + case Command::ReadData: + case Command::ReadDeletedData: goto read_data_found_header; - case CommandWriteData: // write data - case CommandWriteDeletedData: // write deleted data + case Command::WriteData: // write data + case Command::WriteDeletedData: // write deleted data goto write_data_found_header; } // Performs the read data or read deleted data command. read_data: - LOG(PADHEX(2) << "Read [deleted] data [" - << int(command_[2]) << " " - << int(command_[3]) << " " - << int(command_[4]) << " " - << int(command_[5]) << " ... " - << int(command_[6]) << " " - << int(command_[8]) << "]"); +// LOG(PADHEX(2) << "Read [deleted] data [" +// << int(command_[2]) << " " +// << int(command_[3]) << " " +// << int(command_[4]) << " " +// << int(command_[5]) << " ... " +// << int(command_[6]) << " " +// << int(command_[8]) << "]"); read_next_data: goto read_write_find_header; @@ -450,8 +407,8 @@ void i8272::posit_event(int event_type) { SetMissingDataAddressMark(); goto abort; // TODO: or read_next_data? } else { - if((get_latest_token().type == Token::Data) != ((command_[0] & 0x1f) == CommandReadData)) { - if(!(command_[0]&0x20)) { + if((get_latest_token().type == Token::Data) != (command_.command() == Command::ReadData)) { + if(!command_.target().skip_deleted) { // SK is not set; set the error flag but read this sector before finishing. SetControlMark(); } else { @@ -509,7 +466,7 @@ void i8272::posit_event(int event_type) { // check whether that's it: either the final requested sector has been read, or because // a sector that was [/wasn't] marked as deleted when it shouldn't [/should] have been - if(sector_ != command_[6] && !ControlMark()) { + if(sector_ != command_.geometry().end_of_track && !ControlMark()) { sector_++; goto read_next_data; } @@ -518,13 +475,13 @@ void i8272::posit_event(int event_type) { goto post_st012chrn; write_data: - LOG(PADHEX(2) << "Write [deleted] data [" - << int(command_[2]) << " " - << int(command_[3]) << " " - << int(command_[4]) << " " - << int(command_[5]) << " ... " - << int(command_[6]) << " " - << int(command_[8]) << "]"); +// LOG(PADHEX(2) << "Write [deleted] data [" +// << int(command_[2]) << " " +// << int(command_[3]) << " " +// << int(command_[4]) << " " +// << int(command_[5]) << " ... " +// << int(command_[6]) << " " +// << int(command_[8]) << "]"); if(get_drive().get_is_read_only()) { SetNotWriteable(); @@ -538,7 +495,7 @@ void i8272::posit_event(int event_type) { WAIT_FOR_BYTES(get_is_double_density() ? 22 : 11); begin_writing(true); - write_id_data_joiner((command_[0] & 0x1f) == CommandWriteDeletedData, true); + write_id_data_joiner(command_.command() == Command::WriteDeletedData, true); SetDataDirectionFromProcessor(); SetDataRequest(); @@ -565,7 +522,7 @@ void i8272::posit_event(int event_type) { WAIT_FOR_EVENT(Event::DataWritten); end_writing(); - if(sector_ != command_[6]) { + if(sector_ != command_.geometry().end_of_track) { sector_++; goto write_next_data; } @@ -575,7 +532,7 @@ void i8272::posit_event(int event_type) { // Performs the read ID command. read_id: // Establishes the drive and head being addressed, and whether in double density mode. - LOG(PADHEX(2) << "Read ID [" << int(command_[0]) << " " << int(command_[1]) << "]"); +// LOG(PADHEX(2) << "Read ID [" << int(command_[0]) << " " << int(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. @@ -597,11 +554,11 @@ void i8272::posit_event(int event_type) { // Performs read track. read_track: - LOG(PADHEX(2) << "Read track [" - << int(command_[2]) << " " - << int(command_[3]) << " " - << int(command_[4]) << " " - << int(command_[5]) << "]"); +// LOG(PADHEX(2) << "Read track [" +// << int(command_[2]) << " " +// << int(command_[3]) << " " +// << int(command_[4]) << " " +// << int(command_[5]) << "]"); // Wait for the index hole. WAIT_FOR_EVENT(Event::IndexHole); @@ -636,7 +593,7 @@ void i8272::posit_event(int event_type) { if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte; sector_++; - if(sector_ < command_[6]) goto read_track_next_sector; + if(sector_ < command_.geometry().end_of_track) goto read_track_next_sector; goto post_st012chrn; @@ -696,15 +653,15 @@ void i8272::posit_event(int event_type) { // Write the sector body. write_id_data_joiner(false, false); - write_n_bytes(128 << command_[2], command_[5]); + write_n_bytes(128 << command_.format_specs().bytes_per_sector, command_.format_specs().filler); write_crc(); // Write the prescribed gap. - write_n_bytes(command_[4], get_is_double_density() ? 0x4e : 0xff); + write_n_bytes(command_.format_specs().gap3_length, get_is_double_density() ? 0x4e : 0xff); // Consider repeating. sector_++; - if(sector_ < command_[3] && !index_hole_count_) + if(sector_ < command_.format_specs().sectors_per_track && !index_hole_count_) goto format_track_write_sector; // Otherwise, pad out to the index hole. @@ -739,7 +696,7 @@ void i8272::posit_event(int event_type) { recalibrate: seek: { - int drive = command_[1]&3; + const int drive = command_.target().drive; select_drive(drive); // Increment the seeking count if this drive wasn't already seeking. @@ -755,14 +712,14 @@ void i8272::posit_event(int event_type) { drives_[drive].step_rate_counter = 8000 * step_rate_time_; drives_[drive].steps_taken = 0; drives_[drive].seek_failed = false; - main_status_ |= 1 << (command_[1]&3); + main_status_ |= 1 << command_.target().drive; // 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]; - LOG(PADHEX(2) << "Seek to " << int(command_[2])); + if(command_.command() != Command::Recalibrate) { + drives_[drive].target_head_position = command_.seek_target(); + LOG(PADHEX(2) << "Seek to " << int(command_.seek_target())); } else { drives_[drive].target_head_position = -1; drives_[drive].head_position = 0; @@ -808,24 +765,24 @@ void i8272::posit_event(int event_type) { specify: // Just store the values, and terminate the command. LOG("Specify"); - 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 + step_rate_time_ = command_.specify_specs().step_rate_time; + head_unload_time_ = command_.specify_specs().head_unload_time; + head_load_time_ = command_.specify_specs().head_load_time; if(!head_unload_time_) head_unload_time_ = 16; if(!head_load_time_) head_load_time_ = 2; - dma_mode_ = !(command_[2] & 1); + dma_mode_ = command_.specify_specs().use_dma; goto wait_for_command; sense_drive_status: LOG("Sense drive status"); { - int drive = command_[1] & 3; + int drive = command_.target().drive; select_drive(drive); result_stack_= { uint8_t( - (command_[1] & 7) | // drive and head number - 0x08 | // single sided + (command_.drive_head()) | // drive and head number + 0x08 | // single sided (get_drive().get_is_track_zero() ? 0x10 : 0x00) | (get_drive().get_is_ready() ? 0x20 : 0x00) | (get_drive().get_is_read_only() ? 0x40 : 0x00) @@ -857,11 +814,11 @@ void i8272::posit_event(int event_type) { // Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack, so the // last thing in it will be returned first. post_result: - LOGNBR(PADHEX(2) << "Result to " << int(command_[0] & 0x1f) << ", main " << int(main_status_) << "; "); - for(std::size_t c = 0; c < result_stack_.size(); c++) { - LOGNBR(" " << int(result_stack_[result_stack_.size() - 1 - c])); - } - LOGNBR(std::endl); +// LOGNBR(PADHEX(2) << "Result to " << int(command_[0] & 0x1f) << ", main " << int(main_status_) << "; "); +// for(std::size_t c = 0; c < result_stack_.size(); c++) { +// LOGNBR(" " << int(result_stack_[result_stack_.size() - 1 - c])); +// } +// LOGNBR(std::endl); // Set ready to send data to the processor, no longer in non-DMA execution phase. is_executing_ = false; diff --git a/Components/8272/i8272.hpp b/Components/8272/i8272.hpp index 0c502e3c8..98bd3c960 100644 --- a/Components/8272/i8272.hpp +++ b/Components/8272/i8272.hpp @@ -9,6 +9,8 @@ #ifndef i8272_hpp #define i8272_hpp +#include "CommandDecoder.hpp" + #include "../../Storage/Disk/Controller/MFMDiskController.hpp" #include @@ -54,7 +56,7 @@ class i8272 : public Storage::Disk::MFMController { uint8_t status_[3] = {0, 0, 0}; // A buffer for accumulating the incoming command, and one for accumulating the result. - std::vector command_; + CommandDecoder command_; std::vector result_stack_; uint8_t input_ = 0; bool has_input_ = false; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 80effae5b..cea443d17 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1146,6 +1146,7 @@ 4267A9C82B0D4EC2008A59BB /* PIC.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PIC.hpp; sourceTree = ""; }; 4267A9C92B0D4F17008A59BB /* DMA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DMA.hpp; sourceTree = ""; }; 4267A9CA2B111ED2008A59BB /* KeyboardMapper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMapper.hpp; sourceTree = ""; }; + 4267A9CB2B113958008A59BB /* CommandDecoder.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CommandDecoder.hpp; sourceTree = ""; }; 4281572E2AA0334300E16AA1 /* Carry.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Carry.hpp; sourceTree = ""; }; 428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = ""; }; 428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = ""; }; @@ -4552,6 +4553,7 @@ children = ( 4BBC951C1F368D83008F4C34 /* i8272.cpp */, 4BBC951D1F368D83008F4C34 /* i8272.hpp */, + 4267A9CB2B113958008A59BB /* CommandDecoder.hpp */, ); path = 8272; sourceTree = ""; From 2efb5236f709ff66d439cc7e4c5133659324ceb1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 24 Nov 2023 22:19:39 -0500 Subject: [PATCH 03/26] Add an agent for floppy control. --- Machines/PCCompatible/PCCompatible.cpp | 58 ++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 370b70c52..7355e4e1b 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -20,6 +20,7 @@ #include "../../Components/6845/CRTC6845.hpp" #include "../../Components/8255/i8255.hpp" +#include "../../Components/8272/CommandDecoder.hpp" #include "../../Components/AudioToggle/AudioToggle.hpp" #include "../../Numeric/RegisterSizes.hpp" @@ -37,6 +38,41 @@ namespace PCCompatible { +class FloppyController { + public: + FloppyController(PIC &pic, DMA &dma) : pic_(pic), dma_(dma) {} + + void set_digital_output(uint8_t control) { + // b7, b6, b5, b4: enable motor for drive 4, 3, 2, 1; + // b3: 1 => enable DMA; 0 => disable; + // b2: 1 => enable FDC; 0 => hold at reset; + // b1, b0: drive select. + + enable_dma_ = control & 0x08; + + const bool hold_reset = !(control & 0x04); + if(!hold_reset && hold_reset_) { + reset(); + } + hold_reset_ = hold_reset; + } + + private: + void reset() { + decoder_.clear(); + + // TODO: And? +// pic_.apply_edge<6>(true); + } + + PIC &pic_; + DMA &dma_; + + bool hold_reset_ = false; + bool enable_dma_ = false; + Intel::i8272::CommandDecoder decoder_; +}; + class KeyboardController { public: KeyboardController(PIC &pic) : pic_(pic) {} @@ -347,7 +383,8 @@ using PIT = i8237; class i8255PortHandler : public Intel::i8255::PortHandler { // Likely to be helpful: https://github.com/tmk/tmk_keyboard/wiki/IBM-PC-XT-Keyboard-Protocol public: - i8255PortHandler(PCSpeaker &speaker, KeyboardController &keyboard) : speaker_(speaker), keyboard_(keyboard) {} + i8255PortHandler(PCSpeaker &speaker, KeyboardController &keyboard) : + speaker_(speaker), keyboard_(keyboard) {} void set_value(int port, uint8_t value) { switch(port) { @@ -684,8 +721,8 @@ struct Memory { class IO { public: - IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, MDA &mda) : - pit_(pit), dma_(dma), ppi_(ppi), pic_(pic), mda_(mda) {} + IO(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, MDA &mda, FloppyController &fdc) : + pit_(pit), dma_(dma), ppi_(ppi), pic_(pic), mda_(mda), fdc_(fdc) {} template void out(uint16_t port, IntT value) { switch(port) { @@ -772,7 +809,11 @@ class IO { // Ignore CGA accesses. break; - case 0x03f0: case 0x03f1: case 0x03f2: case 0x03f3: + case 0x03f2: + fdc_.set_digital_output(uint8_t(value)); + break; + + case 0x03f3: case 0x03f4: case 0x03f5: case 0x03f6: case 0x03f7: printf("TODO: FDC write of %02x at %04x\n", value, port); break; @@ -862,6 +903,7 @@ class IO { PPI &ppi_; PIC &pic_; MDA &mda_; + FloppyController &fdc_; }; class FlowController { @@ -914,11 +956,12 @@ class ConcreteMachine: const ROMMachine::ROMFetcher &rom_fetcher ) : keyboard_(pic_), + fdc_(pic_, dma_), pit_observer_(pic_, speaker_), ppi_handler_(speaker_, keyboard_), pit_(pit_observer_), ppi_(ppi_handler_), - context(pit_, dma_, ppi_, pic_, mda_) + context(pit_, dma_, ppi_, pic_, mda_, fdc_) { // Use clock rate as a MIPS count; keeping it as a multiple or divisor of the PIT frequency is easy. static constexpr int pit_frequency = 1'193'182; @@ -1071,6 +1114,7 @@ class ConcreteMachine: MDA mda_; KeyboardController keyboard_; + FloppyController fdc_; PITObserver pit_observer_; i8255PortHandler ppi_handler_; @@ -1080,11 +1124,11 @@ class ConcreteMachine: PCCompatible::KeyboardMapper keyboard_mapper_; struct Context { - Context(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, MDA &mda) : + Context(PIT &pit, DMA &dma, PPI &ppi, PIC &pic, MDA &mda, FloppyController &fdc) : segments(registers), memory(registers, segments), flow_controller(registers, segments), - io(pit, dma, ppi, pic, mda) + io(pit, dma, ppi, pic, mda, fdc) { reset(); } From dd135bf3fe6d8115f963b03a096820df14f1c036 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 24 Nov 2023 22:41:33 -0500 Subject: [PATCH 04/26] Start experimenting with a possible end-of-reset interrupt? --- Machines/PCCompatible/PCCompatible.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 7355e4e1b..d720c717f 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -55,13 +55,14 @@ class FloppyController { reset(); } hold_reset_ = hold_reset; + if(hold_reset_) { + pic_.apply_edge<6>(false); + } } private: void reset() { decoder_.clear(); - - // TODO: And? // pic_.apply_edge<6>(true); } From 8c70317d312d9dc36f438e0f5805aadc904211c0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 24 Nov 2023 23:06:52 -0500 Subject: [PATCH 05/26] Introduce interrupt. --- Machines/PCCompatible/PCCompatible.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index d720c717f..6594ea45f 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -63,7 +63,7 @@ class FloppyController { private: void reset() { decoder_.clear(); -// pic_.apply_edge<6>(true); + pic_.apply_edge<6>(true); } PIC &pic_; From 0bb048e24b266e6fa0b96f88fb0f5f0192d6fc88 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 25 Nov 2023 18:10:49 -0500 Subject: [PATCH 06/26] Start formalising/extracting 8272 status. --- Components/8272/Status.hpp | 45 +++++++++++++++++++ Components/8272/i8272.cpp | 2 +- Machines/PCCompatible/PCCompatible.cpp | 25 +++++++++-- .../Clock Signal.xcodeproj/project.pbxproj | 4 +- 4 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 Components/8272/Status.hpp diff --git a/Components/8272/Status.hpp b/Components/8272/Status.hpp new file mode 100644 index 000000000..49a4276e2 --- /dev/null +++ b/Components/8272/Status.hpp @@ -0,0 +1,45 @@ +// +// Status.hpp +// Clock Signal +// +// Created by Thomas Harte on 25/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef Status_hpp +#define Status_hpp + +namespace Intel::i8272 { + +class Status { + public: + uint8_t main() const { + return main_status_; + } + + void reset() { + main_status_ = DataReady; + status_[0] = status_[1] = status_[2] = 0; + } + + private: + uint8_t main_status_; + uint8_t status_[3]; + + enum MainStatus: uint8_t { + FDD0Seeking = 0x01, + FDD1Seeking = 0x02, + FDD2Seeking = 0x04, + FDD3Seeking = 0x08, + + ReadOrWriteOngoing = 0x10, + InNonDMAExecution = 0x20, + DataIsToProcessor = 0x40, + DataReady = 0x80, + }; + +}; + +} + +#endif /* Status_hpp */ diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index aff663127..77bd8f254 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -276,7 +276,7 @@ void i8272::posit_event(int event_type) { if(!command_.has_command()) { goto wait_for_complete_command_sequence; } - if(command_.has_geometry() == 9) { + if(command_.has_geometry()) { cylinder_ = command_.geometry().cylinder; head_ = command_.geometry().head; sector_ = command_.geometry().sector; diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 6594ea45f..6a0d3d0e8 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -21,6 +21,7 @@ #include "../../Components/6845/CRTC6845.hpp" #include "../../Components/8255/i8255.hpp" #include "../../Components/8272/CommandDecoder.hpp" +#include "../../Components/8272/Status.hpp" #include "../../Components/AudioToggle/AudioToggle.hpp" #include "../../Numeric/RegisterSizes.hpp" @@ -52,6 +53,7 @@ class FloppyController { const bool hold_reset = !(control & 0x04); if(!hold_reset && hold_reset_) { + // TODO: add a delay mechanism. reset(); } hold_reset_ = hold_reset; @@ -60,9 +62,14 @@ class FloppyController { } } + uint8_t status() const { + return status_.main(); + } + private: void reset() { decoder_.clear(); + status_.reset(); pic_.apply_edge<6>(true); } @@ -71,7 +78,9 @@ class FloppyController { bool hold_reset_ = false; bool enable_dma_ = false; + Intel::i8272::CommandDecoder decoder_; + Intel::i8272::Status status_; }; class KeyboardController { @@ -815,7 +824,10 @@ class IO { break; case 0x03f3: - case 0x03f4: case 0x03f5: case 0x03f6: case 0x03f7: + case 0x03f4: + case 0x03f5: + case 0x03f6: + case 0x03f7: printf("TODO: FDC write of %02x at %04x\n", value, port); break; @@ -879,8 +891,15 @@ class IO { // Ignore parallel port accesses. break; - case 0x03f0: case 0x03f1: case 0x03f2: case 0x03f3: - case 0x03f4: case 0x03f5: case 0x03f6: case 0x03f7: + case 0x03f4: return fdc_.status(); + + case 0x03f0: + case 0x03f1: + case 0x03f2: + case 0x03f3: + case 0x03f5: + case 0x03f6: + case 0x03f7: printf("TODO: FDC read from %04x\n", port); break; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index cea443d17..ac2b4a45e 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1126,6 +1126,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 4238200B2B1295AD00964EFE /* Status.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Status.hpp; sourceTree = ""; }; 423BDC492AB24699008E37B6 /* 8088Tests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 8088Tests.mm; sourceTree = ""; }; 42437B342ACF02A9006DFED1 /* Flags.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Flags.hpp; sourceTree = ""; }; 42437B352ACF0AA2006DFED1 /* Perform.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Perform.hpp; sourceTree = ""; }; @@ -4552,8 +4553,9 @@ isa = PBXGroup; children = ( 4BBC951C1F368D83008F4C34 /* i8272.cpp */, - 4BBC951D1F368D83008F4C34 /* i8272.hpp */, 4267A9CB2B113958008A59BB /* CommandDecoder.hpp */, + 4BBC951D1F368D83008F4C34 /* i8272.hpp */, + 4238200B2B1295AD00964EFE /* Status.hpp */, ); path = 8272; sourceTree = ""; From 9bd75464b5a20edb5f1c829209e79a4cac623908 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 25 Nov 2023 18:15:37 -0500 Subject: [PATCH 07/26] Proceed to receiving a sense interrupt status. --- Components/8272/Status.hpp | 4 ++++ Machines/PCCompatible/PCCompatible.cpp | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Components/8272/Status.hpp b/Components/8272/Status.hpp index 49a4276e2..0ed22b4e1 100644 --- a/Components/8272/Status.hpp +++ b/Components/8272/Status.hpp @@ -13,6 +13,10 @@ namespace Intel::i8272 { class Status { public: + Status() { + reset(); + } + uint8_t main() const { return main_status_; } diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 6a0d3d0e8..a564376f2 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -66,6 +66,10 @@ class FloppyController { return status_.main(); } + void write(uint8_t value) { + decoder_.push_back(value); + } + private: void reset() { decoder_.clear(); @@ -822,10 +826,12 @@ class IO { case 0x03f2: fdc_.set_digital_output(uint8_t(value)); break; + case 0x03f5: + fdc_.write(uint8_t(value)); + break; case 0x03f3: case 0x03f4: - case 0x03f5: case 0x03f6: case 0x03f7: printf("TODO: FDC write of %02x at %04x\n", value, port); From a6a464c240bbe47bf31ba5b751597aac0cf1bc09 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 25 Nov 2023 21:40:13 -0500 Subject: [PATCH 08/26] Add printed TODO. --- Machines/PCCompatible/PCCompatible.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index a564376f2..6b92bbcca 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -68,6 +68,18 @@ class FloppyController { void write(uint8_t value) { decoder_.push_back(value); + + if(decoder_.has_command()) { + using Command = Intel::i8272::CommandDecoder::Command; + switch(decoder_.command()) { + default: + printf("TODO: implement FDC command %d\n", uint8_t(decoder_.command())); + break; + +// case Command::Invalid: +// break; + } + } } private: From 003c494aac814ff2cfb021a95c6f0ce3429eccbb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 26 Nov 2023 15:04:01 -0500 Subject: [PATCH 09/26] Factor out a large number of status-related facts. --- Components/8272/CommandDecoder.hpp | 2 +- Components/8272/Status.hpp | 125 ++++++++++++++++++++---- Components/8272/i8272.cpp | 147 +++++++++++------------------ Components/8272/i8272.hpp | 8 +- 4 files changed, 166 insertions(+), 116 deletions(-) diff --git a/Components/8272/CommandDecoder.hpp b/Components/8272/CommandDecoder.hpp index 7bf274ac3..681264c84 100644 --- a/Components/8272/CommandDecoder.hpp +++ b/Components/8272/CommandDecoder.hpp @@ -161,7 +161,7 @@ class CommandDecoder { return result; } uint8_t drive_head() const { - return command_[1]&7; + return command_[1] & 7; } // diff --git a/Components/8272/Status.hpp b/Components/8272/Status.hpp index 0ed22b4e1..a9e534036 100644 --- a/Components/8272/Status.hpp +++ b/Components/8272/Status.hpp @@ -11,37 +11,126 @@ namespace Intel::i8272 { +enum class MainStatus: uint8_t { + FDD0Seeking = 0x01, + FDD1Seeking = 0x02, + FDD2Seeking = 0x04, + FDD3Seeking = 0x08, + + ReadOrWriteOngoing = 0x10, + InNonDMAExecution = 0x20, + DataIsToProcessor = 0x40, + DataReady = 0x80, +}; + +enum class Status0: uint8_t { + NormalTermination = 0x00, + AbnormalTermination = 0x80, + InvalidCommand = 0x40, + BecameNotReady = 0xc0, + + SeekEnded = 0x20, + EquipmentFault = 0x10, + NotReady = 0x08, + + HeadAddress = 0x04, + UnitSelect = 0x03, +}; + +enum class Status1: uint8_t { + EndOfCylinder = 0x80, + DataError = 0x20, + OverRun = 0x10, + NoData = 0x04, + NotWriteable = 0x02, + MissingAddressMark = 0x01, +}; + +enum class Status2: uint8_t { + DeletedControlMark = 0x40, + DataCRCError = 0x20, + WrongCyinder = 0x10, + ScanEqualHit = 0x08, + ScanNotSatisfied = 0x04, + BadCylinder = 0x02, + MissingDataAddressMark = 0x01, +}; + +enum class Status3: uint8_t { + Fault = 0x80, + WriteProtected = 0x40, + Ready = 0x20, + Track9 = 0x10, + TwoSided = 0x08, + + HeadAddress = 0x04, + UnitSelect = 0x03, +}; + class Status { public: Status() { reset(); } - uint8_t main() const { - return main_status_; - } - void reset() { - main_status_ = DataReady; + main_status_ = 0; + set(MainStatus::DataReady, true); status_[0] = status_[1] = status_[2] = 0; } + /// @returns The main status register value. + uint8_t main() const { + return main_status_; + } + uint8_t operator [](int index) const { + return status_[index]; + } + + // + // Flag setters. + // + void set(MainStatus flag, bool value) { + set(uint8_t(flag), value, main_status_); + } + void start_seek(int drive) { main_status_ |= 1 << drive; } + void set(Status0 flag) { set(uint8_t(flag), true, status_[0]); } + void set(Status1 flag) { set(uint8_t(flag), true, status_[1]); } + void set(Status2 flag) { set(uint8_t(flag), true, status_[2]); } + + // + // Flag getters. + // + bool get(MainStatus flag) { return main_status_ & uint8_t(flag); } + bool get(Status2 flag) { return status_[2] & uint8_t(flag); } + + /// Begin execution of whatever @c CommandDecoder currently describes, setting internal + /// state appropriately. + void begin(const CommandDecoder &command) { + set(MainStatus::DataReady, false); + + if(command.is_access_command()) { + set(MainStatus::ReadOrWriteOngoing, true); + status_[0] = command.drive_head(); + } + } + + void end_sense_interrupt_status(int drive) { + status_[0] = uint8_t(drive); + main_status_ &= ~(1 << drive); + } + private: + void set(uint8_t flag, bool value, uint8_t &target) { + if(value) { + target |= flag; + } else { + target &= ~flag; + } + } + uint8_t main_status_; uint8_t status_[3]; - - enum MainStatus: uint8_t { - FDD0Seeking = 0x01, - FDD1Seeking = 0x02, - FDD2Seeking = 0x04, - FDD3Seeking = 0x08, - - ReadOrWriteOngoing = 0x10, - InNonDMAExecution = 0x20, - DataIsToProcessor = 0x40, - DataReady = 0x80, - }; - }; } diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 77bd8f254..8588fd2d7 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -12,47 +12,6 @@ using namespace Intel::i8272; -#define SetDataRequest() (main_status_ |= 0x80) -#define ResetDataRequest() (main_status_ &= ~0x80) -#define DataRequest() (main_status_ & 0x80) - -#define SetDataDirectionToProcessor() (main_status_ |= 0x40) -#define SetDataDirectionFromProcessor() (main_status_ &= ~0x40) -#define DataDirectionToProcessor() (main_status_ & 0x40) - -#define SetNonDMAExecution() (main_status_ |= 0x20) -#define ResetNonDMAExecution() (main_status_ &= ~0x20) - -#define SetBusy() (main_status_ |= 0x10) -#define ResetBusy() (main_status_ &= ~0x10) -#define Busy() (main_status_ & 0x10) - -#define SetAbnormalTermination() (status_[0] |= 0x40) -#define SetInvalidCommand() (status_[0] |= 0x80) -#define SetReadyChanged() (status_[0] |= 0xc0) -#define SetSeekEnd() (status_[0] |= 0x20) -#define SetEquipmentCheck() (status_[0] |= 0x10) -#define SetNotReady() (status_[0] |= 0x08) -#define SetSide2() (status_[0] |= 0x04) - -#define SetEndOfCylinder() (status_[1] |= 0x80) -#define SetDataError() (status_[1] |= 0x20) -#define SetOverrun() (status_[1] |= 0x10) -#define SetNoData() (status_[1] |= 0x04) -#define SetNotWriteable() (status_[1] |= 0x02) -#define SetMissingAddressMark() (status_[1] |= 0x01) - -#define SetControlMark() (status_[2] |= 0x40) -#define ClearControlMark() (status_[2] &= ~0x40) -#define ControlMark() (status_[2] & 0x40) - -#define SetDataFieldDataError() (status_[2] |= 0x20) -#define SetWrongCyinder() (status_[2] |= 0x10) -#define SetScanEqualHit() (status_[2] |= 0x08) -#define SetScanNotSatisfied() (status_[2] |= 0x04) -#define SetBadCylinder() (status_[2] |= 0x02) -#define SetMissingDataAddressMark() (status_[2] |= 0x01) - i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) : Storage::Disk::MFMController(clock_rate), bus_handler_(bus_handler) { @@ -149,12 +108,12 @@ void i8272::write(int address, uint8_t value) { if(!address) return; // if not ready for commands, do nothing - if(!DataRequest() || DataDirectionToProcessor()) return; + if(!status_.get(MainStatus::DataReady) || status_.get(MainStatus::DataIsToProcessor)) return; if(expects_input_) { input_ = value; has_input_ = true; - ResetDataRequest(); + status_.set(MainStatus::DataReady, false); } else { // accumulate latest byte in the command byte sequence command_.push_back(value); @@ -171,7 +130,7 @@ uint8_t i8272::read(int address) { return result; } else { - return main_status_; + return status_.main(); } } @@ -212,7 +171,6 @@ uint8_t i8272::read(int address) { #define SET_DRIVE_HEAD_MFM() \ active_drive_ = command_.target().drive; \ active_head_ = command_.target().head; \ - status_[0] = command_.drive_head(); \ select_drive(active_drive_); \ get_drive().set_head(active_head_); \ set_is_double_density(command_.target().mfm); @@ -247,7 +205,7 @@ uint8_t i8272::read(int address) { void i8272::posit_event(int event_type) { if(event_type == int(Event::IndexHole)) index_hole_count_++; if(event_type == int(Event8272::NoLongerReady)) { - SetNotReady(); + status_.set(Status0::NotReady); goto abort; } if(!(interesting_event_mask_ & event_type)) return; @@ -260,35 +218,35 @@ void i8272::posit_event(int event_type) { wait_for_command: expects_input_ = false; set_data_mode(Storage::Disk::MFMController::DataMode::Scanning); - ResetBusy(); - ResetNonDMAExecution(); + status_.set(MainStatus::ReadOrWriteOngoing, false); + status_.set(MainStatus::InNonDMAExecution, false); command_.clear(); // Sets the data request bit, and waits for a byte. Then sets the busy bit. Continues accepting bytes // until it has a quantity that make up an entire command, then resets the data request bit and // branches to that command. wait_for_complete_command_sequence: - SetDataRequest(); - SetDataDirectionFromProcessor(); + status_.set(MainStatus::DataReady, true); + status_.set(MainStatus::DataIsToProcessor, false); WAIT_FOR_EVENT(Event8272::CommandByte) - SetBusy(); if(!command_.has_command()) { goto wait_for_complete_command_sequence; } + + status_.begin(command_); if(command_.has_geometry()) { cylinder_ = command_.geometry().cylinder; head_ = command_.geometry().head; sector_ = command_.geometry().sector; size_ = command_.geometry().size; } - 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. is_access_command_ = command_.is_access_command(); if(is_access_command_) { + status_.set(MainStatus::ReadOrWriteOngoing, true); for(int c = 0; c < 4; c++) { if(drives_[c].phase == Drive::Seeking) { drives_[c].phase = Drive::NotSeeking; @@ -298,11 +256,13 @@ void i8272::posit_event(int event_type) { // 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. is_executing_ = true; - if(!dma_mode_) SetNonDMAExecution(); + if(!dma_mode_) { + status_.set(MainStatus::InNonDMAExecution, true); + } SET_DRIVE_HEAD_MFM(); LOAD_HEAD(); if(!get_drive().get_is_ready()) { - SetNotReady(); + status_.set(Status0::NotReady); goto abort; } } @@ -351,7 +311,7 @@ void i8272::posit_event(int event_type) { if(!index_hole_limit_) { // Two index holes have passed wihout finding the header sought. // LOG("Not found"); - SetNoData(); + status_.set(Status1::NoData); goto abort; } index_hole_count_ = 0; @@ -359,12 +319,12 @@ void i8272::posit_event(int event_type) { READ_HEADER(); if(index_hole_count_) { // This implies an index hole was sighted within the header. Error out. - SetEndOfCylinder(); + status_.set(Status1::EndOfCylinder); goto abort; } if(get_crc_generator().get_value()) { // This implies a CRC error in the header; mark as such but continue. - SetDataError(); + status_.set(Status1::DataError); } // LOG("Considering << PADHEX(2) << 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; @@ -399,18 +359,18 @@ void i8272::posit_event(int event_type) { // flag doesn't match the sort the command was looking for. read_data_found_header: FIND_DATA(); - ClearControlMark(); + // TODO: should Status2::DeletedControlMark be cleared? 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(); + status_.set(Status1::MissingAddressMark); + status_.set(Status2::MissingDataAddressMark); goto abort; // TODO: or read_next_data? } else { if((get_latest_token().type == Token::Data) != (command_.command() == Command::ReadData)) { if(!command_.target().skip_deleted) { // SK is not set; set the error flag but read this sector before finishing. - SetControlMark(); + status_.set(Status2::DeletedControlMark); } else { // SK is set; skip this sector. goto read_next_data; @@ -419,7 +379,7 @@ void i8272::posit_event(int event_type) { } } else { // An index hole appeared before the data mark. - SetEndOfCylinder(); + status_.set(Status1::EndOfCylinder); goto abort; // TODO: or read_next_data? } @@ -435,21 +395,21 @@ void i8272::posit_event(int event_type) { if(event_type == int(Event::Token)) { result_stack_.push_back(get_latest_token().byte_value); distance_into_section_++; - SetDataRequest(); - SetDataDirectionToProcessor(); + status_.set(MainStatus::DataReady, true); + status_.set(MainStatus::DataIsToProcessor, true); 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(); + status_.set(MainStatus::DataReady, false); 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(); + status_.set(Status1::OverRun); goto abort; break; case int(Event::IndexHole): - SetEndOfCylinder(); + status_.set(Status1::EndOfCylinder); goto abort; break; } @@ -459,14 +419,14 @@ void i8272::posit_event(int event_type) { WAIT_FOR_EVENT(Event::Token); if(get_crc_generator().get_value()) { // This implies a CRC error in the sector; mark as such and temrinate. - SetDataError(); - SetDataFieldDataError(); + status_.set(Status1::DataError); + status_.set(Status2::DataCRCError); goto abort; } // check whether that's it: either the final requested sector has been read, or because // a sector that was [/wasn't] marked as deleted when it shouldn't [/should] have been - if(sector_ != command_.geometry().end_of_track && !ControlMark()) { + if(sector_ != command_.geometry().end_of_track && !status_.get(Status2::DeletedControlMark)) { sector_++; goto read_next_data; } @@ -484,7 +444,7 @@ void i8272::posit_event(int event_type) { // << int(command_[8]) << "]"); if(get_drive().get_is_read_only()) { - SetNotWriteable(); + status_.set(Status1::NotWriteable); goto abort; } @@ -497,22 +457,22 @@ void i8272::posit_event(int event_type) { write_id_data_joiner(command_.command() == Command::WriteDeletedData, true); - SetDataDirectionFromProcessor(); - SetDataRequest(); + status_.set(MainStatus::DataIsToProcessor, false); + status_.set(MainStatus::DataReady, true); expects_input_ = true; distance_into_section_ = 0; write_loop: WAIT_FOR_EVENT(Event::DataWritten); if(!has_input_) { - SetOverrun(); + status_.set(Status1::OverRun); goto abort; } write_byte(input_); has_input_ = false; distance_into_section_++; if(distance_into_section_ < (128 << size_)) { - SetDataRequest(); + status_.set(MainStatus::DataReady, true); goto write_loop; } @@ -539,7 +499,7 @@ void i8272::posit_event(int event_type) { index_hole_limit_ = 2; FIND_HEADER(); if(!index_hole_limit_) { - SetMissingAddressMark(); + status_.set(Status1::MissingAddressMark); goto abort; } READ_HEADER(); @@ -571,7 +531,7 @@ void i8272::posit_event(int event_type) { FIND_HEADER(); if(!index_hole_limit_) { if(!sector_) { - SetMissingAddressMark(); + status_.set(Status1::MissingAddressMark); goto abort; } else { goto post_st012chrn; @@ -581,15 +541,15 @@ void i8272::posit_event(int event_type) { FIND_DATA(); distance_into_section_ = 0; - SetDataDirectionToProcessor(); + status_.set(MainStatus::DataIsToProcessor, true); read_track_get_byte: WAIT_FOR_EVENT(Event::Token); result_stack_.push_back(get_latest_token().byte_value); distance_into_section_++; - SetDataRequest(); + status_.set(MainStatus::DataReady, true); // TODO: other possible exit conditions; find a way to merge with the read_data version of this. WAIT_FOR_EVENT(int(Event8272::ResultEmpty)); - ResetDataRequest(); + status_.set(MainStatus::DataReady, false); if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte; sector_++; @@ -601,7 +561,7 @@ void i8272::posit_event(int event_type) { format_track: LOG("Format track"); if(get_drive().get_is_read_only()) { - SetNotWriteable(); + status_.set(Status1::NotWriteable); goto abort; } @@ -620,15 +580,15 @@ void i8272::posit_event(int event_type) { // Write the sector header, obtaining its contents // from the processor. - SetDataDirectionFromProcessor(); - SetDataRequest(); + status_.set(MainStatus::DataIsToProcessor, false); + status_.set(MainStatus::DataReady, true); expects_input_ = true; distance_into_section_ = 0; format_track_write_header: WAIT_FOR_EVENT(int(Event::DataWritten) | int(Event::IndexHole)); switch(event_type) { case int(Event::IndexHole): - SetOverrun(); + status_.set(Status1::OverRun); goto abort; break; case int(Event::DataWritten): @@ -637,7 +597,7 @@ void i8272::posit_event(int event_type) { has_input_ = false; distance_into_section_++; if(distance_into_section_ < 4) { - SetDataRequest(); + status_.set(MainStatus::DataReady, true); goto format_track_write_header; } break; @@ -712,7 +672,7 @@ void i8272::posit_event(int event_type) { drives_[drive].step_rate_counter = 8000 * step_rate_time_; drives_[drive].steps_taken = 0; drives_[drive].seek_failed = false; - main_status_ |= 1 << command_.target().drive; + status_.start_seek(command_.target().drive); // 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 @@ -750,9 +710,8 @@ void i8272::posit_event(int event_type) { // If a drive was found, return its results. Otherwise return a single 0x80. if(found_drive != -1) { drives_[found_drive].phase = Drive::NotSeeking; - status_[0] = uint8_t(found_drive); - main_status_ &= ~(1 << found_drive); - SetSeekEnd(); + status_.end_sense_interrupt_status(found_drive); + status_.set(Status0::SeekEnded); result_stack_ = { drives_[found_drive].head_position, status_[0]}; } else { @@ -800,7 +759,7 @@ void i8272::posit_event(int event_type) { // Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase. abort: end_writing(); - SetAbnormalTermination(); + status_.set(Status0::AbnormalTermination); goto post_st012chrn; // Posts ST0, ST1, ST2, C, H, R and N as a result phase. @@ -822,9 +781,9 @@ void i8272::posit_event(int event_type) { // Set ready to send data to the processor, no longer in non-DMA execution phase. is_executing_ = false; - ResetNonDMAExecution(); - SetDataRequest(); - SetDataDirectionToProcessor(); + status_.set(MainStatus::InNonDMAExecution, false); + status_.set(MainStatus::DataReady, true); + status_.set(MainStatus::DataIsToProcessor, true); // The actual stuff of unwinding result_stack_ is handled by ::read; wait // until the processor has read all result bytes. diff --git a/Components/8272/i8272.hpp b/Components/8272/i8272.hpp index 98bd3c960..55d381d20 100644 --- a/Components/8272/i8272.hpp +++ b/Components/8272/i8272.hpp @@ -10,6 +10,7 @@ #define i8272_hpp #include "CommandDecoder.hpp" +#include "Status.hpp" #include "../../Storage/Disk/Controller/MFMDiskController.hpp" @@ -52,11 +53,12 @@ class i8272 : public Storage::Disk::MFMController { std::unique_ptr allocated_bus_handler_; // Status registers. - uint8_t main_status_ = 0; - uint8_t status_[3] = {0, 0, 0}; + Status status_; - // A buffer for accumulating the incoming command, and one for accumulating the result. + // The incoming command. CommandDecoder command_; + + // A buffer to accumulate the result. std::vector result_stack_; uint8_t input_ = 0; bool has_input_ = false; From d2203484cc913a3ee655d3c76188657499e9a4ab Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 26 Nov 2023 15:29:08 -0500 Subject: [PATCH 10/26] Avoid name duplication. --- Components/8272/CommandDecoder.hpp | 2 +- Components/8272/Status.hpp | 2 +- Components/8272/i8272.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Components/8272/CommandDecoder.hpp b/Components/8272/CommandDecoder.hpp index 681264c84..f745723d1 100644 --- a/Components/8272/CommandDecoder.hpp +++ b/Components/8272/CommandDecoder.hpp @@ -134,7 +134,7 @@ class CommandDecoder { /// @returns @c true if this command involves reading or writing data, in which case target() will be valid. /// @c false otherwise. - bool is_access_command() const { + bool is_access() const { switch(command()) { case Command::ReadData: case Command::ReadDeletedData: case Command::WriteData: case Command::WriteDeletedData: diff --git a/Components/8272/Status.hpp b/Components/8272/Status.hpp index a9e534036..f758f7ee4 100644 --- a/Components/8272/Status.hpp +++ b/Components/8272/Status.hpp @@ -109,7 +109,7 @@ class Status { void begin(const CommandDecoder &command) { set(MainStatus::DataReady, false); - if(command.is_access_command()) { + if(command.is_access()) { set(MainStatus::ReadOrWriteOngoing, true); status_[0] = command.drive_head(); } diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 8588fd2d7..374637042 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -243,7 +243,7 @@ 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. - is_access_command_ = command_.is_access_command(); + is_access_command_ = command_.is_access(); if(is_access_command_) { status_.set(MainStatus::ReadOrWriteOngoing, true); From 291723e85e3a39768124927fb8c92712f5e32dd6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 27 Nov 2023 10:27:36 -0500 Subject: [PATCH 11/26] Insert notes to self, trying to tie down FloppyController interface. --- Machines/PCCompatible/PCCompatible.cpp | 38 +++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 6b92bbcca..2f6d3294b 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -82,6 +82,11 @@ class FloppyController { } } + uint8_t read() { + // TODO: is anything being serialised? + return 0x80; + } + private: void reset() { decoder_.clear(); @@ -823,9 +828,8 @@ class IO { } break; - case 0x03b8: /* case 0x03b9: case 0x03ba: case 0x03bb: - case 0x03bc: case 0x03bd: case 0x03be: case 0x03bf: */ - mda_.write<8>(value); + case 0x03b8: + mda_.write<8>(uint8_t(value)); break; case 0x03d0: case 0x03d1: case 0x03d2: case 0x03d3: @@ -838,17 +842,13 @@ class IO { case 0x03f2: fdc_.set_digital_output(uint8_t(value)); break; + case 0x03f4: + printf("TODO: FDC write of %02x at %04x\n", value, port); + break; case 0x03f5: fdc_.write(uint8_t(value)); break; - case 0x03f3: - case 0x03f4: - case 0x03f6: - case 0x03f7: - printf("TODO: FDC write of %02x at %04x\n", value, port); - break; - case 0x0278: case 0x0279: case 0x027a: case 0x0378: case 0x0379: case 0x037a: case 0x03bc: case 0x03bd: case 0x03be: @@ -910,16 +910,16 @@ class IO { break; case 0x03f4: return fdc_.status(); + case 0x03f5: return fdc_.read(); - case 0x03f0: - case 0x03f1: - case 0x03f2: - case 0x03f3: - case 0x03f5: - case 0x03f6: - case 0x03f7: - printf("TODO: FDC read from %04x\n", port); - break; +// 3F0-3F7 Floppy disk controller (except PCjr) +// 3F0 Diskette controller status A +// 3F1 Diskette controller status B +// 3F2 controller control port +// 3F4 controller status register +// 3F5 data register (write 1-9 byte command, see INT 13) +// 3F6 Diskette controller data +// 3F7 Diskette digital input case 0x02e8: case 0x02e9: case 0x02ea: case 0x02eb: case 0x02ec: case 0x02ed: case 0x02ee: case 0x02ef: From bffe3ffa25e4f321c40117074ae1d5f51d9368f9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 27 Nov 2023 23:05:37 -0500 Subject: [PATCH 12/26] Add an 8272 results phase. --- Components/8272/CommandDecoder.hpp | 50 +++++++------- Components/8272/Results.hpp | 65 +++++++++++++++++++ Components/8272/Status.hpp | 3 +- Components/8272/i8272.cpp | 1 - Machines/PCCompatible/PCCompatible.cpp | 29 +++++++-- .../Clock Signal.xcodeproj/project.pbxproj | 2 + 6 files changed, 118 insertions(+), 32 deletions(-) create mode 100644 Components/8272/Results.hpp diff --git a/Components/8272/CommandDecoder.hpp b/Components/8272/CommandDecoder.hpp index f745723d1..362d210ed 100644 --- a/Components/8272/CommandDecoder.hpp +++ b/Components/8272/CommandDecoder.hpp @@ -15,33 +15,33 @@ namespace Intel::i8272 { +enum class Command { + ReadData = 0x06, + ReadDeletedData = 0x0c, + + WriteData = 0x05, + WriteDeletedData = 0x09, + + ReadTrack = 0x02, + ReadID = 0x0a, + FormatTrack = 0x0d, + + ScanLow = 0x11, + ScanLowOrEqual = 0x19, + ScanHighOrEqual = 0x1d, + + Recalibrate = 0x07, + Seek = 0x0f, + + SenseInterruptStatus = 0x08, + Specify = 0x03, + SenseDriveStatus = 0x04, + + Invalid = 0x00, +}; + class CommandDecoder { public: - enum class Command { - ReadData = 0x06, - ReadDeletedData = 0x0c, - - WriteData = 0x05, - WriteDeletedData = 0x09, - - ReadTrack = 0x02, - ReadID = 0x0a, - FormatTrack = 0x0d, - - ScanLow = 0x11, - ScanLowOrEqual = 0x19, - ScanHighOrEqual = 0x1d, - - Recalibrate = 0x07, - Seek = 0x0f, - - SenseInterruptStatus = 0x08, - Specify = 0x03, - SenseDriveStatus = 0x04, - - Invalid = 0x00, - }; - /// Add a byte to the current command. void push_back(uint8_t byte) { command_.push_back(byte); diff --git a/Components/8272/Results.hpp b/Components/8272/Results.hpp new file mode 100644 index 000000000..0241b7e6d --- /dev/null +++ b/Components/8272/Results.hpp @@ -0,0 +1,65 @@ +// +// Results.hpp +// Clock Signal +// +// Created by Thomas Harte on 27/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef Results_hpp +#define Results_hpp + +#include "CommandDecoder.hpp" +#include "Status.hpp" + +namespace Intel::i8272 { + +class Results { + public: + /// Serialises the response to Command::Invalid and Command::SenseInterruptStatus when no interrupt source was found. + void serialise_none() { + result_ = { 0x80 }; + } + + /// Serialises the response to Command::SenseInterruptStatus for a found drive. + void serialise(const Status &status, uint8_t cylinder) { + result_ = { cylinder, status[0] }; + } + + /// Serialises the seven-byte response to Command::SenseDriveStatus. + void serialise(uint8_t flags, uint8_t drive_side) { + result_ = { uint8_t(flags | drive_side) }; + } + + /// Serialises the response to: + /// + /// * Command::ReadData; + /// * Command::ReadDeletedData; + /// * Command::WriteData; + /// * Command::WriteDeletedData; + /// * Command::ReadID; + /// * Command::ReadTrack; + /// * Command::FormatTrack; + /// * Command::ScanLow; and + /// * Command::ScanHighOrEqual. + void serialise(const Status &status, uint8_t cylinder, uint8_t head, uint8_t sector, uint8_t size) { + result_ = { size, sector, head, cylinder, status[2], status[1], status[0] }; + } + + /// @returns @c true if all result bytes are exhausted; @c false otherwise. + bool empty() const { return result_.empty(); } + + /// @returns The next byte of the result. + uint8_t next() { + const uint8_t next = result_.back(); + result_.pop_back(); + return next; + } + + private: + std::vector result_; +}; + +} + +#endif /* Results_hpp */ diff --git a/Components/8272/Status.hpp b/Components/8272/Status.hpp index f758f7ee4..e0b216615 100644 --- a/Components/8272/Status.hpp +++ b/Components/8272/Status.hpp @@ -60,9 +60,8 @@ enum class Status3: uint8_t { Fault = 0x80, WriteProtected = 0x40, Ready = 0x20, - Track9 = 0x10, + Track0 = 0x10, TwoSided = 0x08, - HeadAddress = 0x04, UnitSelect = 0x03, }; diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 374637042..519a435fd 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -268,7 +268,6 @@ void i8272::posit_event(int event_type) { } // Jump to the proper place. - using Command = CommandDecoder::Command; switch(command_.command()) { case Command::ReadData: case Command::ReadDeletedData: diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 2f6d3294b..065c4ecbc 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -21,6 +21,7 @@ #include "../../Components/6845/CRTC6845.hpp" #include "../../Components/8255/i8255.hpp" #include "../../Components/8272/CommandDecoder.hpp" +#include "../../Components/8272/Results.hpp" #include "../../Components/8272/Status.hpp" #include "../../Components/AudioToggle/AudioToggle.hpp" @@ -70,20 +71,39 @@ class FloppyController { decoder_.push_back(value); if(decoder_.has_command()) { - using Command = Intel::i8272::CommandDecoder::Command; + using Command = Intel::i8272::Command; switch(decoder_.command()) { default: printf("TODO: implement FDC command %d\n", uint8_t(decoder_.command())); break; -// case Command::Invalid: -// break; + case Command::Invalid: + results_.serialise_none(); + break; + + case Command::SenseInterruptStatus: + results_.serialise_none(); + break; + } + + if(!results_.empty()) { + using MainStatus = Intel::i8272::MainStatus; + status_.set(MainStatus::DataIsToProcessor, true); + status_.set(MainStatus::DataReady, true); } } } uint8_t read() { - // TODO: is anything being serialised? + using MainStatus = Intel::i8272::MainStatus; + if(status_.get(MainStatus::DataIsToProcessor)) { + const uint8_t result = results_.next(); + if(results_.empty()) { + status_.set(MainStatus::DataIsToProcessor, false); + } + return result; + } + return 0x80; } @@ -102,6 +122,7 @@ class FloppyController { Intel::i8272::CommandDecoder decoder_; Intel::i8272::Status status_; + Intel::i8272::Results results_; }; class KeyboardController { diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index ac2b4a45e..9085363c4 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1127,6 +1127,7 @@ /* Begin PBXFileReference section */ 4238200B2B1295AD00964EFE /* Status.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Status.hpp; sourceTree = ""; }; + 4238200C2B15998800964EFE /* Results.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Results.hpp; sourceTree = ""; }; 423BDC492AB24699008E37B6 /* 8088Tests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 8088Tests.mm; sourceTree = ""; }; 42437B342ACF02A9006DFED1 /* Flags.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Flags.hpp; sourceTree = ""; }; 42437B352ACF0AA2006DFED1 /* Perform.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Perform.hpp; sourceTree = ""; }; @@ -4556,6 +4557,7 @@ 4267A9CB2B113958008A59BB /* CommandDecoder.hpp */, 4BBC951D1F368D83008F4C34 /* i8272.hpp */, 4238200B2B1295AD00964EFE /* Status.hpp */, + 4238200C2B15998800964EFE /* Results.hpp */, ); path = 8272; sourceTree = ""; From 8fec9bef11ed1b340f09dc1cc6c7dae463e4ea07 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 27 Nov 2023 23:16:24 -0500 Subject: [PATCH 13/26] Attempt IRQ logic. --- Machines/PCCompatible/PCCompatible.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 065c4ecbc..a625a2d6e 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -82,10 +82,18 @@ class FloppyController { break; case Command::SenseInterruptStatus: + pic_.apply_edge<6>(false); results_.serialise_none(); break; } + // Set interrupt upon the end of any valid command other than sense interrupt status. + if(decoder_.command() != Command::SenseInterruptStatus && decoder_.command() != Command::Invalid) { + pic_.apply_edge<6>(true); + } + decoder_.clear(); + + // If there are any results to provide, set data direction and data ready. if(!results_.empty()) { using MainStatus = Intel::i8272::MainStatus; status_.set(MainStatus::DataIsToProcessor, true); @@ -112,6 +120,10 @@ class FloppyController { decoder_.clear(); status_.reset(); pic_.apply_edge<6>(true); + + using MainStatus = Intel::i8272::MainStatus; + status_.set(MainStatus::DataReady, true); + status_.set(MainStatus::DataIsToProcessor, false); } PIC &pic_; From c19c356c109ba04cb49faddb9743ef90fc4cfe7a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 27 Nov 2023 23:23:00 -0500 Subject: [PATCH 14/26] Add disabled longer serialisation. --- Machines/PCCompatible/PCCompatible.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index a625a2d6e..d538ca3e1 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -83,6 +83,7 @@ class FloppyController { case Command::SenseInterruptStatus: pic_.apply_edge<6>(false); +// results_.serialise(status_, 0); results_.serialise_none(); break; } From b860fba0a3513006546c3717c0f44d57ddd2a1cf Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 28 Nov 2023 14:12:39 -0500 Subject: [PATCH 15/26] Make an attempt at providing varied sense interrupt statuses. --- Components/8272/Status.hpp | 4 ++-- Components/8272/i8272.cpp | 2 +- Machines/PCCompatible/PCCompatible.cpp | 17 +++++++++++++++-- .../xcschemes/Clock Signal.xcscheme | 2 +- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Components/8272/Status.hpp b/Components/8272/Status.hpp index e0b216615..071b1962d 100644 --- a/Components/8272/Status.hpp +++ b/Components/8272/Status.hpp @@ -114,8 +114,8 @@ class Status { } } - void end_sense_interrupt_status(int drive) { - status_[0] = uint8_t(drive); + void end_sense_interrupt_status(int drive, int head) { + status_[0] = uint8_t(drive | (head << 2)); main_status_ &= ~(1 << drive); } diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 519a435fd..70a810687 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -709,7 +709,7 @@ void i8272::posit_event(int event_type) { // If a drive was found, return its results. Otherwise return a single 0x80. if(found_drive != -1) { drives_[found_drive].phase = Drive::NotSeeking; - status_.end_sense_interrupt_status(found_drive); + status_.end_sense_interrupt_status(found_drive, 0); status_.set(Status0::SeekEnded); result_stack_ = { drives_[found_drive].head_position, status_[0]}; diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index d538ca3e1..13abb98ae 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -83,8 +83,19 @@ class FloppyController { case Command::SenseInterruptStatus: pic_.apply_edge<6>(false); -// results_.serialise(status_, 0); - results_.serialise_none(); + + if(interrupting_drives_) { + int drive = 3; + while(!(interrupting_drives_ & (1 << drive))) { + --drive; + } + interrupting_drives_ &= ~(1 << drive); + + status_.end_sense_interrupt_status(drive, 0); + results_.serialise(status_, 10); + } else { + results_.serialise_none(); + } break; } @@ -121,6 +132,7 @@ class FloppyController { decoder_.clear(); status_.reset(); pic_.apply_edge<6>(true); + interrupting_drives_ = 0xf; using MainStatus = Intel::i8272::MainStatus; status_.set(MainStatus::DataReady, true); @@ -136,6 +148,7 @@ class FloppyController { Intel::i8272::CommandDecoder decoder_; Intel::i8272::Status status_; Intel::i8272::Results results_; + int interrupting_drives_ = 0; }; class KeyboardController { diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 19b54b90e..474fa352e 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -62,7 +62,7 @@ Date: Tue, 28 Nov 2023 15:09:57 -0500 Subject: [PATCH 16/26] Load up on debugging logs. --- Machines/PCCompatible/PCCompatible.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 13abb98ae..447302e53 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -45,6 +45,8 @@ class FloppyController { FloppyController(PIC &pic, DMA &dma) : pic_(pic), dma_(dma) {} void set_digital_output(uint8_t control) { + printf("FDC DOR: %02x\n", control); + // b7, b6, b5, b4: enable motor for drive 4, 3, 2, 1; // b3: 1 => enable DMA; 0 => disable; // b2: 1 => enable FDC; 0 => hold at reset; @@ -64,6 +66,7 @@ class FloppyController { } uint8_t status() const { + printf("FDC: read status %02x\n", status_.main()); return status_.main(); } @@ -78,10 +81,12 @@ class FloppyController { break; case Command::Invalid: + printf("FDC: Invalid\n"); results_.serialise_none(); break; case Command::SenseInterruptStatus: + printf("FDC: SenseInterruptStatus\n"); pic_.apply_edge<6>(false); if(interrupting_drives_) { @@ -121,14 +126,17 @@ class FloppyController { if(results_.empty()) { status_.set(MainStatus::DataIsToProcessor, false); } + printf("FDC read: %02x\n", result); return result; } + printf("FDC read?\n"); return 0x80; } private: void reset() { + printf("FDC reset\n"); decoder_.clear(); status_.reset(); pic_.apply_edge<6>(true); From 301442a0b1e2139496775c8fac17bd646cf44f59 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 28 Nov 2023 22:34:34 -0500 Subject: [PATCH 17/26] Fix meaning of flag, use correctly. --- Components/8272/Status.hpp | 4 ++-- Components/8272/i8272.cpp | 3 +-- Machines/PCCompatible/PCCompatible.cpp | 8 ++++++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Components/8272/Status.hpp b/Components/8272/Status.hpp index 071b1962d..cc2f249b0 100644 --- a/Components/8272/Status.hpp +++ b/Components/8272/Status.hpp @@ -17,7 +17,7 @@ enum class MainStatus: uint8_t { FDD2Seeking = 0x04, FDD3Seeking = 0x08, - ReadOrWriteOngoing = 0x10, + CommandInProgress = 0x10, InNonDMAExecution = 0x20, DataIsToProcessor = 0x40, DataReady = 0x80, @@ -107,9 +107,9 @@ class Status { /// state appropriately. void begin(const CommandDecoder &command) { set(MainStatus::DataReady, false); + set(MainStatus::CommandInProgress, true); if(command.is_access()) { - set(MainStatus::ReadOrWriteOngoing, true); status_[0] = command.drive_head(); } } diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 70a810687..97963fa37 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -218,7 +218,7 @@ void i8272::posit_event(int event_type) { wait_for_command: expects_input_ = false; set_data_mode(Storage::Disk::MFMController::DataMode::Scanning); - status_.set(MainStatus::ReadOrWriteOngoing, false); + status_.set(MainStatus::CommandInProgress, false); status_.set(MainStatus::InNonDMAExecution, false); command_.clear(); @@ -246,7 +246,6 @@ void i8272::posit_event(int event_type) { is_access_command_ = command_.is_access(); if(is_access_command_) { - status_.set(MainStatus::ReadOrWriteOngoing, true); for(int c = 0; c < 4; c++) { if(drives_[c].phase == Drive::Seeking) { drives_[c].phase = Drive::NotSeeking; diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 447302e53..e35357479 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -40,6 +40,9 @@ namespace PCCompatible { +//bool log = false; +//std::string previous; + class FloppyController { public: FloppyController(PIC &pic, DMA &dma) : pic_(pic), dma_(dma) {} @@ -58,6 +61,7 @@ class FloppyController { if(!hold_reset && hold_reset_) { // TODO: add a delay mechanism. reset(); +// log = true; } hold_reset_ = hold_reset; if(hold_reset_) { @@ -115,6 +119,7 @@ class FloppyController { using MainStatus = Intel::i8272::MainStatus; status_.set(MainStatus::DataIsToProcessor, true); status_.set(MainStatus::DataReady, true); + status_.set(MainStatus::CommandInProgress, true); } } } @@ -125,6 +130,7 @@ class FloppyController { const uint8_t result = results_.next(); if(results_.empty()) { status_.set(MainStatus::DataIsToProcessor, false); + status_.set(MainStatus::CommandInProgress, false); } printf("FDC read: %02x\n", result); return result; @@ -1084,8 +1090,6 @@ class ConcreteMachine: } // MARK: - TimedMachine. -// bool log = false; -// std::string previous; void run_for(const Cycles duration) override { const auto pit_ticks = duration.as_integral(); cpu_divisor_ += pit_ticks; From 3827a084ad85911b9609f95263ab0aa49c10d692 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 28 Nov 2023 23:18:22 -0500 Subject: [PATCH 18/26] Code to GlaBIOS expectations. --- Components/8272/Status.hpp | 5 ----- Components/8272/i8272.cpp | 2 +- Machines/PCCompatible/PCCompatible.cpp | 21 ++++++--------------- 3 files changed, 7 insertions(+), 21 deletions(-) diff --git a/Components/8272/Status.hpp b/Components/8272/Status.hpp index cc2f249b0..0a42d95ad 100644 --- a/Components/8272/Status.hpp +++ b/Components/8272/Status.hpp @@ -114,11 +114,6 @@ class Status { } } - void end_sense_interrupt_status(int drive, int head) { - status_[0] = uint8_t(drive | (head << 2)); - main_status_ &= ~(1 << drive); - } - private: void set(uint8_t flag, bool value, uint8_t &target) { if(value) { diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 97963fa37..638034e75 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -708,7 +708,7 @@ void i8272::posit_event(int event_type) { // If a drive was found, return its results. Otherwise return a single 0x80. if(found_drive != -1) { drives_[found_drive].phase = Drive::NotSeeking; - status_.end_sense_interrupt_status(found_drive, 0); +// status_.end_sense_interrupt_status(found_drive, 0); status_.set(Status0::SeekEnded); result_stack_ = { drives_[found_drive].head_position, status_[0]}; diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index e35357479..fec083f72 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -92,19 +92,7 @@ class FloppyController { case Command::SenseInterruptStatus: printf("FDC: SenseInterruptStatus\n"); pic_.apply_edge<6>(false); - - if(interrupting_drives_) { - int drive = 3; - while(!(interrupting_drives_ & (1 << drive))) { - --drive; - } - interrupting_drives_ &= ~(1 << drive); - - status_.end_sense_interrupt_status(drive, 0); - results_.serialise(status_, 10); - } else { - results_.serialise_none(); - } + results_.serialise(status_, 0); // TODO: get track of relevant drive. break; } @@ -146,7 +134,11 @@ class FloppyController { decoder_.clear(); status_.reset(); pic_.apply_edge<6>(true); - interrupting_drives_ = 0xf; + + // Necessary to pass GlaBIOS [if this is called after reset?], but Why? + // + // Cf. INT_13_0_2 and the CMP AL, 11000000B following a CALL FDC_WAIT_SENSE. + status_.set(Intel::i8272::Status0::BecameNotReady); using MainStatus = Intel::i8272::MainStatus; status_.set(MainStatus::DataReady, true); @@ -162,7 +154,6 @@ class FloppyController { Intel::i8272::CommandDecoder decoder_; Intel::i8272::Status status_; Intel::i8272::Results results_; - int interrupting_drives_ = 0; }; class KeyboardController { From 6e2e67fd469e7a72aa7a020ef26be0ad5a48bab3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 29 Nov 2023 09:42:43 -0500 Subject: [PATCH 19/26] Sculpt out enough to get to a read data command. --- Components/8272/Status.hpp | 2 + Machines/PCCompatible/PCCompatible.cpp | 71 ++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/Components/8272/Status.hpp b/Components/8272/Status.hpp index 0a42d95ad..5a9330ab0 100644 --- a/Components/8272/Status.hpp +++ b/Components/8272/Status.hpp @@ -97,6 +97,8 @@ class Status { void set(Status1 flag) { set(uint8_t(flag), true, status_[1]); } void set(Status2 flag) { set(uint8_t(flag), true, status_[2]); } + void set_status0(uint8_t value) { status_[0] = value; } + // // Flag getters. // diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index fec083f72..81e8076fe 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -48,7 +48,7 @@ class FloppyController { FloppyController(PIC &pic, DMA &dma) : pic_(pic), dma_(dma) {} void set_digital_output(uint8_t control) { - printf("FDC DOR: %02x\n", control); +// printf("FDC DOR: %02x\n", control); // b7, b6, b5, b4: enable motor for drive 4, 3, 2, 1; // b3: 1 => enable DMA; 0 => disable; @@ -70,7 +70,7 @@ class FloppyController { } uint8_t status() const { - printf("FDC: read status %02x\n", status_.main()); +// printf("FDC: read status %02x\n", status_.main()); return status_.main(); } @@ -89,17 +89,55 @@ class FloppyController { results_.serialise_none(); break; - case Command::SenseInterruptStatus: + case Command::SenseInterruptStatus: { printf("FDC: SenseInterruptStatus\n"); - pic_.apply_edge<6>(false); - results_.serialise(status_, 0); // TODO: get track of relevant drive. + + int c = 0; + for(; c < 4; c++) { + if(drives_[c].raised_interrupt) { + drives_[c].raised_interrupt = false; + status_.set_status0(drives_[c].status); + results_.serialise(status_, drives_[0].track); + } + } + + bool any_remaining_interrupts = false; + for(; c < 4; c++) { + any_remaining_interrupts |= drives_[c].raised_interrupt; + } + if(!any_remaining_interrupts) { + pic_.apply_edge<6>(any_remaining_interrupts); + } + } break; + + case Command::Specify: + printf("FDC: Specify\n"); + specify_specs_ = decoder_.specify_specs(); + break; + + case Command::Recalibrate: + printf("FDC: Recalibrate\n"); + drives_[decoder_.target().drive].track = 0; + drives_[decoder_.target().drive].raised_interrupt = true; + drives_[decoder_.target().drive].status = decoder_.target().drive; + pic_.apply_edge<6>(true); + break; + + case Command::Seek: + printf("FDC: Seek %d:%d to %d\n", decoder_.target().drive, decoder_.target().head, decoder_.seek_target()); + drives_[decoder_.target().drive].track = decoder_.seek_target(); + drives_[decoder_.target().drive].side = decoder_.target().head; + + drives_[decoder_.target().drive].raised_interrupt = true; + drives_[decoder_.target().drive].status = decoder_.drive_head(); + pic_.apply_edge<6>(true); break; } // Set interrupt upon the end of any valid command other than sense interrupt status. - if(decoder_.command() != Command::SenseInterruptStatus && decoder_.command() != Command::Invalid) { - pic_.apply_edge<6>(true); - } +// if(decoder_.command() != Command::SenseInterruptStatus && decoder_.command() != Command::Invalid) { +// pic_.apply_edge<6>(true); +// } decoder_.clear(); // If there are any results to provide, set data direction and data ready. @@ -120,7 +158,7 @@ class FloppyController { status_.set(MainStatus::DataIsToProcessor, false); status_.set(MainStatus::CommandInProgress, false); } - printf("FDC read: %02x\n", result); +// printf("FDC read: %02x\n", result); return result; } @@ -133,12 +171,15 @@ class FloppyController { printf("FDC reset\n"); decoder_.clear(); status_.reset(); - pic_.apply_edge<6>(true); // Necessary to pass GlaBIOS [if this is called after reset?], but Why? // // Cf. INT_13_0_2 and the CMP AL, 11000000B following a CALL FDC_WAIT_SENSE. - status_.set(Intel::i8272::Status0::BecameNotReady); + for(int c = 0; c < 4; c++) { + drives_[c].raised_interrupt = true; + drives_[c].status = uint8_t(Intel::i8272::Status0::BecameNotReady); + } + pic_.apply_edge<6>(true); using MainStatus = Intel::i8272::MainStatus; status_.set(MainStatus::DataReady, true); @@ -154,6 +195,14 @@ class FloppyController { Intel::i8272::CommandDecoder decoder_; Intel::i8272::Status status_; Intel::i8272::Results results_; + + Intel::i8272::CommandDecoder::SpecifySpecs specify_specs_; + struct DriveStatus { + bool raised_interrupt = false; + uint8_t status = 0; + uint8_t track = 0; + bool side = false; + } drives_[4]; }; class KeyboardController { From fbbe3ab7f171a9094c15c6db27ee83c7e13fc725 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 29 Nov 2023 09:45:45 -0500 Subject: [PATCH 20/26] Include seek ended flag. --- Components/8272/i8272.cpp | 3 ++- Machines/PCCompatible/PCCompatible.cpp | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 638034e75..cd908999b 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -708,8 +708,9 @@ void i8272::posit_event(int event_type) { // If a drive was found, return its results. Otherwise return a single 0x80. if(found_drive != -1) { drives_[found_drive].phase = Drive::NotSeeking; + status_.set_status0(uint8_t(found_drive | uint8_t(Status0::SeekEnded))); // status_.end_sense_interrupt_status(found_drive, 0); - status_.set(Status0::SeekEnded); +// status_.set(Status0::SeekEnded); result_stack_ = { drives_[found_drive].head_position, status_[0]}; } else { diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 81e8076fe..0d3235b4e 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -119,7 +119,7 @@ class FloppyController { printf("FDC: Recalibrate\n"); drives_[decoder_.target().drive].track = 0; drives_[decoder_.target().drive].raised_interrupt = true; - drives_[decoder_.target().drive].status = decoder_.target().drive; + drives_[decoder_.target().drive].status = decoder_.target().drive | uint8_t(Intel::i8272::Status0::SeekEnded); pic_.apply_edge<6>(true); break; @@ -129,7 +129,7 @@ class FloppyController { drives_[decoder_.target().drive].side = decoder_.target().head; drives_[decoder_.target().drive].raised_interrupt = true; - drives_[decoder_.target().drive].status = decoder_.drive_head(); + drives_[decoder_.target().drive].status = decoder_.drive_head() | uint8_t(Intel::i8272::Status0::SeekEnded); pic_.apply_edge<6>(true); break; } From 439104c73a9460d5bb4fd54f47fb96ead795e4dc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 29 Nov 2023 09:49:05 -0500 Subject: [PATCH 21/26] Add missing space. --- Components/8272/i8272.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index cd908999b..c6ae10bd2 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -737,7 +737,7 @@ void i8272::posit_event(int event_type) { { int drive = command_.target().drive; select_drive(drive); - result_stack_= { + result_stack_ = { uint8_t( (command_.drive_head()) | // drive and head number 0x08 | // single sided From a992ae37b17fd257054a3e6257f430f3d91bec32 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 29 Nov 2023 09:49:15 -0500 Subject: [PATCH 22/26] Mildly rearrange, to match enum order. --- Machines/PCCompatible/PCCompatible.cpp | 40 +++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 0d3235b4e..4c8e03487 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -84,9 +84,21 @@ class FloppyController { printf("TODO: implement FDC command %d\n", uint8_t(decoder_.command())); break; - case Command::Invalid: - printf("FDC: Invalid\n"); - results_.serialise_none(); + case Command::Seek: + printf("FDC: Seek %d:%d to %d\n", decoder_.target().drive, decoder_.target().head, decoder_.seek_target()); + drives_[decoder_.target().drive].track = decoder_.seek_target(); + drives_[decoder_.target().drive].side = decoder_.target().head; + + drives_[decoder_.target().drive].raised_interrupt = true; + drives_[decoder_.target().drive].status = decoder_.drive_head() | uint8_t(Intel::i8272::Status0::SeekEnded); + pic_.apply_edge<6>(true); + break; + case Command::Recalibrate: + printf("FDC: Recalibrate\n"); + drives_[decoder_.target().drive].track = 0; + drives_[decoder_.target().drive].raised_interrupt = true; + drives_[decoder_.target().drive].status = decoder_.target().drive | uint8_t(Intel::i8272::Status0::SeekEnded); + pic_.apply_edge<6>(true); break; case Command::SenseInterruptStatus: { @@ -109,28 +121,16 @@ class FloppyController { pic_.apply_edge<6>(any_remaining_interrupts); } } break; - case Command::Specify: printf("FDC: Specify\n"); specify_specs_ = decoder_.specify_specs(); break; +// case Command::SenseDriveStatus: { +// } break; - case Command::Recalibrate: - printf("FDC: Recalibrate\n"); - drives_[decoder_.target().drive].track = 0; - drives_[decoder_.target().drive].raised_interrupt = true; - drives_[decoder_.target().drive].status = decoder_.target().drive | uint8_t(Intel::i8272::Status0::SeekEnded); - pic_.apply_edge<6>(true); - break; - - case Command::Seek: - printf("FDC: Seek %d:%d to %d\n", decoder_.target().drive, decoder_.target().head, decoder_.seek_target()); - drives_[decoder_.target().drive].track = decoder_.seek_target(); - drives_[decoder_.target().drive].side = decoder_.target().head; - - drives_[decoder_.target().drive].raised_interrupt = true; - drives_[decoder_.target().drive].status = decoder_.drive_head() | uint8_t(Intel::i8272::Status0::SeekEnded); - pic_.apply_edge<6>(true); + case Command::Invalid: + printf("FDC: Invalid\n"); + results_.serialise_none(); break; } From ce4bcf90642a3f4b90322a6680fae31c23e3c909 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 29 Nov 2023 09:50:08 -0500 Subject: [PATCH 23/26] Improve comment. --- Machines/PCCompatible/PCCompatible.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 4c8e03487..b29f04c9d 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -172,7 +172,7 @@ class FloppyController { decoder_.clear(); status_.reset(); - // Necessary to pass GlaBIOS [if this is called after reset?], but Why? + // Necessary to pass GlaBIOS' POST test, but: why? // // Cf. INT_13_0_2 and the CMP AL, 11000000B following a CALL FDC_WAIT_SENSE. for(int c = 0; c < 4; c++) { From e034daa6c806c01cd955ea28456d015efee84a27 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 29 Nov 2023 09:52:16 -0500 Subject: [PATCH 24/26] Capture motor state. --- Machines/PCCompatible/PCCompatible.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index b29f04c9d..47e78db5b 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -53,7 +53,12 @@ class FloppyController { // b7, b6, b5, b4: enable motor for drive 4, 3, 2, 1; // b3: 1 => enable DMA; 0 => disable; // b2: 1 => enable FDC; 0 => hold at reset; - // b1, b0: drive select. + // b1, b0: drive select (usurps FDC?) + + drives_[0].motor = control & 0x10; + drives_[1].motor = control & 0x20; + drives_[2].motor = control & 0x40; + drives_[3].motor = control & 0x80; enable_dma_ = control & 0x08; @@ -202,6 +207,7 @@ class FloppyController { uint8_t status = 0; uint8_t track = 0; bool side = false; + bool motor = false; } drives_[4]; }; From be842ee2f153469b7e53f4670355c041fd30c71a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 29 Nov 2023 11:31:37 -0500 Subject: [PATCH 25/26] Add drive indicator lights. --- Machines/PCCompatible/PCCompatible.cpp | 64 ++++++++++++-------------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 47e78db5b..6a6372db7 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -60,6 +60,12 @@ class FloppyController { drives_[2].motor = control & 0x40; drives_[3].motor = control & 0x80; + if(observer_) { + for(int c = 0; c < 4; c++) { + if(drives_[c].exists) observer_->set_led_status(drive_name(c), drives_[c].motor); + } + } + enable_dma_ = control & 0x08; const bool hold_reset = !(control & 0x04); @@ -171,6 +177,15 @@ class FloppyController { return 0x80; } + void set_activity_observer(Activity::Observer *observer) { + observer_ = observer; + for(int c = 0; c < 4; c++) { + if(drives_[c].exists) { + observer_->register_led(drive_name(c), 0); + } + } + } + private: void reset() { printf("FDC reset\n"); @@ -208,7 +223,14 @@ class FloppyController { uint8_t track = 0; bool side = false; bool motor = false; + bool exists = true; } drives_[4]; + + std::string drive_name(int c) const { + return std::string("Drive ") + std::to_string(c + 1); + } + + Activity::Observer *observer_ = nullptr; }; class KeyboardController { @@ -713,21 +735,8 @@ struct Memory { } // Accesses an address based on physical location. -// int mda_delay = -1; // HACK. template typename InstructionSet::x86::Accessor::type access(uint32_t address) { - - // TEMPORARY HACK. -// if(mda_delay > 0) { -// --mda_delay; -// if(!mda_delay) { -// print_mda(); -// } -// } -// if(address >= 0xb'0000 && is_writeable(type)) { -// mda_delay = 100; -// } - // Dispense with the single-byte case trivially. if constexpr (std::is_same_v) { return memory[address]; @@ -803,18 +812,6 @@ struct Memory { return &memory[address]; } - // TEMPORARY HACK. -// void print_mda() { -// uint32_t pointer = 0xb'0000; -// for(int y = 0; y < 25; y++) { -// for(int x = 0; x < 80; x++) { -// printf("%c", memory[pointer]); -// pointer += 2; // MDA goes [character, attributes]...; skip the attributes. -// } -// printf("\n"); -// } -// } - private: std::array memory{0xff}; Registers ®isters_; @@ -1019,15 +1016,6 @@ class IO { case 0x03f4: return fdc_.status(); case 0x03f5: return fdc_.read(); -// 3F0-3F7 Floppy disk controller (except PCjr) -// 3F0 Diskette controller status A -// 3F1 Diskette controller status B -// 3F2 controller control port -// 3F4 controller status register -// 3F5 data register (write 1-9 byte command, see INT 13) -// 3F6 Diskette controller data -// 3F7 Diskette digital input - case 0x02e8: case 0x02e9: case 0x02ea: case 0x02eb: case 0x02ec: case 0x02ed: case 0x02ee: case 0x02ef: case 0x02f8: case 0x02f9: case 0x02fa: case 0x02fb: @@ -1093,7 +1081,8 @@ class ConcreteMachine: public MachineTypes::TimedMachine, public MachineTypes::AudioProducer, public MachineTypes::ScanProducer, - public MachineTypes::MappedKeyboardMachine + public MachineTypes::MappedKeyboardMachine, + public Activity::Source { public: ConcreteMachine( @@ -1250,6 +1239,11 @@ class ConcreteMachine: keyboard_.post(uint8_t(key | (is_pressed ? 0x00 : 0x80))); } + // MARK: - Activity::Source. + void set_activity_observer(Activity::Observer *observer) final { + fdc_.set_activity_observer(observer); + } + private: PIC pic_; DMA dma_; From 8d01829fa7c09ea1d6a42041f0b80d838d48d7a4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 29 Nov 2023 11:35:21 -0500 Subject: [PATCH 26/26] Adopt PC-style naming, limit to one drive. --- Machines/PCCompatible/PCCompatible.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 6a6372db7..17a96f10a 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -45,7 +45,13 @@ namespace PCCompatible { class FloppyController { public: - FloppyController(PIC &pic, DMA &dma) : pic_(pic), dma_(dma) {} + FloppyController(PIC &pic, DMA &dma) : pic_(pic), dma_(dma) { + // Default: one floppy drive only. + drives_[0].exists = true; + drives_[1].exists = false; + drives_[2].exists = false; + drives_[3].exists = false; + } void set_digital_output(uint8_t control) { // printf("FDC DOR: %02x\n", control); @@ -227,7 +233,9 @@ class FloppyController { } drives_[4]; std::string drive_name(int c) const { - return std::string("Drive ") + std::to_string(c + 1); + char name[3] = "A"; + name[0] += c; + return std::string("Drive ") + name; } Activity::Observer *observer_ = nullptr;