From 6e1909647b9eb02b42151b4754112bea1fa1e70a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 5 Mar 2025 21:08:53 -0500 Subject: [PATCH] Reformat; hatch separate AT keyboard controller; print POST codes. --- Analyser/Static/PCCompatible/Target.hpp | 8 + Machines/PCCompatible/DMA.hpp | 6 + Machines/PCCompatible/PCCompatible.cpp | 2013 ++++++++++++----------- Outputs/Log.hpp | 2 + 4 files changed, 1031 insertions(+), 998 deletions(-) diff --git a/Analyser/Static/PCCompatible/Target.hpp b/Analyser/Static/PCCompatible/Target.hpp index 10a113992..affec28ce 100644 --- a/Analyser/Static/PCCompatible/Target.hpp +++ b/Analyser/Static/PCCompatible/Target.hpp @@ -19,6 +19,14 @@ ReflectableEnum(Model, AT ); +constexpr bool is_xt(const Model model) { + return model <= Model::TurboXT; +} + +constexpr bool is_at(const Model model) { + return model >= Model::AT; +} + struct Target: public Analyser::Static::Target, public Reflection::StructImpl { ReflectableEnum(VideoAdaptor, MDA, diff --git a/Machines/PCCompatible/DMA.hpp b/Machines/PCCompatible/DMA.hpp index 8f1e82f1e..6c3cb272c 100644 --- a/Machines/PCCompatible/DMA.hpp +++ b/Machines/PCCompatible/DMA.hpp @@ -10,6 +10,7 @@ #include "Analyser/Static/PCCompatible/Target.hpp" #include "Numeric/RegisterSizes.hpp" +#include "Outputs/Log.hpp" #include "Memory.hpp" @@ -250,6 +251,9 @@ public: template void set_page(const uint8_t value) { pages_[page_for_index(index)] = value; + if(index == 0x00) { + log_.info().append("%02x", value); + } } template @@ -264,6 +268,8 @@ public: private: uint8_t pages_[16]{}; + Log::Logger log_; + static constexpr int page_for_index(const int index) { switch(index) { // Channels the PC architecture uses. diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 62fc61a23..7c77d5e92 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -65,379 +65,396 @@ template <> struct Adaptor { using type = CGA; }; template class FloppyController { - public: - FloppyController( - PIC &pic, - DMA &dma, - int drive_count - ) : pic_(pic), dma_(dma) { - // Default: one floppy drive only. +public: + FloppyController( + PIC &pic, + DMA &dma, + int drive_count + ) : pic_(pic), dma_(dma) { + // Default: one floppy drive only. + for(int c = 0; c < 4; c++) { + drives_[c].exists = drive_count > c; + } + } + + void set_digital_output(const 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 (usurps FDC?) + + drives_[0].motor = control & 0x10; + drives_[1].motor = control & 0x20; + drives_[2].motor = control & 0x40; + drives_[3].motor = control & 0x80; + + if(observer_) { for(int c = 0; c < 4; c++) { - drives_[c].exists = drive_count > c; + if(drives_[c].exists) observer_->set_led_status(drive_name(c), drives_[c].motor); } } - 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 (usurps FDC?) + enable_dma_ = control & 0x08; - drives_[0].motor = control & 0x10; - drives_[1].motor = control & 0x20; - 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); - if(!hold_reset && hold_reset_) { - // TODO: add a delay mechanism. - reset(); - } - hold_reset_ = hold_reset; - if(hold_reset_) { - pic_.template apply_edge<6>(false); - } + const bool hold_reset = !(control & 0x04); + if(!hold_reset && hold_reset_) { + // TODO: add a delay mechanism. + reset(); } - - uint8_t status() const { - return status_.main(); + hold_reset_ = hold_reset; + if(hold_reset_) { + pic_.template apply_edge<6>(false); } + } - void write(uint8_t value) { - decoder_.push_back(value); + uint8_t status() const { + return status_.main(); + } - if(decoder_.has_command()) { - using Command = Intel::i8272::Command; - switch(decoder_.command()) { - default: - log.error().append("TODO: implement FDC command %d", uint8_t(decoder_.command())); - break; + void write(const uint8_t value) { + decoder_.push_back(value); - case Command::WriteDeletedData: - case Command::WriteData: { - status_.begin(decoder_); + if(decoder_.has_command()) { + using Command = Intel::i8272::Command; + switch(decoder_.command()) { + default: + log.error().append("TODO: implement FDC command %d", uint8_t(decoder_.command())); + break; - // Just decline to write, for now. - status_.set(Intel::i8272::Status1::NotWriteable); - status_.set(Intel::i8272::Status0::BecameNotReady); + case Command::WriteDeletedData: + case Command::WriteData: { + status_.begin(decoder_); - results_.serialise( - status_, - decoder_.geometry().cylinder, - decoder_.geometry().head, - decoder_.geometry().sector, - decoder_.geometry().size); + // Just decline to write, for now. + status_.set(Intel::i8272::Status1::NotWriteable); + status_.set(Intel::i8272::Status0::BecameNotReady); - // TODO: what if head has changed? - drives_[decoder_.target().drive].status = decoder_.drive_head(); - drives_[decoder_.target().drive].raised_interrupt = true; - pic_.template apply_edge<6>(true); - } break; + results_.serialise( + status_, + decoder_.geometry().cylinder, + decoder_.geometry().head, + decoder_.geometry().sector, + decoder_.geometry().size); - case Command::ReadDeletedData: - case Command::ReadData: { -// printf("FDC: Read from drive %d / head %d / track %d of head %d / track %d / sector %d\n", -// decoder_.target().drive, -// decoder_.target().head, -// drives_[decoder_.target().drive].track, -// decoder_.geometry().head, -// decoder_.geometry().cylinder, -// decoder_.geometry().sector); + // TODO: what if head has changed? + drives_[decoder_.target().drive].status = decoder_.drive_head(); + drives_[decoder_.target().drive].raised_interrupt = true; + pic_.template apply_edge<6>(true); + } break; - status_.begin(decoder_); + case Command::ReadDeletedData: + case Command::ReadData: { +// printf("FDC: Read from drive %d / head %d / track %d of head %d / track %d / sector %d\n", +// decoder_.target().drive, +// decoder_.target().head, +// drives_[decoder_.target().drive].track, +// decoder_.geometry().head, +// decoder_.geometry().cylinder, +// decoder_.geometry().sector); - // Search for a matching sector. - auto target = decoder_.geometry(); - bool complete = false; - while(!complete) { - const auto sector = drives_[decoder_.target().drive].sector(target.head, target.sector); + status_.begin(decoder_); - if(sector) { - // TODO: I _think_ I'm supposed to validate the rest of the address here? + // Search for a matching sector. + auto target = decoder_.geometry(); + bool complete = false; + while(!complete) { + const auto sector = drives_[decoder_.target().drive].sector(target.head, target.sector); - for(int c = 0; c < 128 << target.size; c++) { - const auto access_result = dma_.write(2, sector->samples[0].data()[c]); - switch(access_result) { - // Default: keep going. - default: continue; + if(sector) { + // TODO: I _think_ I'm supposed to validate the rest of the address here? - // Anything else: update flags and exit. - case AccessResult::NotAccepted: - complete = true; - status_.set(Intel::i8272::Status1::OverRun); - status_.set(Intel::i8272::Status0::AbnormalTermination); - break; - case AccessResult::AcceptedWithEOP: - complete = true; - break; - } + for(int c = 0; c < 128 << target.size; c++) { + const auto access_result = dma_.write(2, sector->samples[0].data()[c]); + switch(access_result) { + // Default: keep going. + default: continue; + + // Anything else: update flags and exit. + case AccessResult::NotAccepted: + complete = true; + status_.set(Intel::i8272::Status1::OverRun); + status_.set(Intel::i8272::Status0::AbnormalTermination); + break; + case AccessResult::AcceptedWithEOP: + complete = true; break; } - - ++target.sector; // TODO: multitrack? - } else { - status_.set(Intel::i8272::Status1::EndOfCylinder); - status_.set(Intel::i8272::Status0::AbnormalTermination); break; } + + ++target.sector; // TODO: multitrack? + } else { + status_.set(Intel::i8272::Status1::EndOfCylinder); + status_.set(Intel::i8272::Status0::AbnormalTermination); + break; } + } - results_.serialise( - status_, - decoder_.geometry().cylinder, - decoder_.geometry().head, - decoder_.geometry().sector, - decoder_.geometry().size); + results_.serialise( + status_, + decoder_.geometry().cylinder, + decoder_.geometry().head, + decoder_.geometry().sector, + decoder_.geometry().size); - // TODO: what if head has changed? - drives_[decoder_.target().drive].status = decoder_.drive_head(); - drives_[decoder_.target().drive].raised_interrupt = true; - pic_.template apply_edge<6>(true); - } break; + // TODO: what if head has changed? + drives_[decoder_.target().drive].status = decoder_.drive_head(); + drives_[decoder_.target().drive].raised_interrupt = true; + pic_.template apply_edge<6>(true); + } break; - case Command::Recalibrate: - drives_[decoder_.target().drive].track = 0; + case Command::Recalibrate: + 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_.template apply_edge<6>(true); - break; - case Command::Seek: - drives_[decoder_.target().drive].track = decoder_.seek_target(); + drives_[decoder_.target().drive].raised_interrupt = true; + drives_[decoder_.target().drive].status = decoder_.target().drive | uint8_t(Intel::i8272::Status0::SeekEnded); + pic_.template apply_edge<6>(true); + break; + case Command::Seek: + drives_[decoder_.target().drive].track = decoder_.seek_target(); - drives_[decoder_.target().drive].raised_interrupt = true; - drives_[decoder_.target().drive].status = decoder_.drive_head() | uint8_t(Intel::i8272::Status0::SeekEnded); - pic_.template apply_edge<6>(true); - break; + drives_[decoder_.target().drive].raised_interrupt = true; + drives_[decoder_.target().drive].status = decoder_.drive_head() | uint8_t(Intel::i8272::Status0::SeekEnded); + pic_.template apply_edge<6>(true); + break; - case Command::SenseInterruptStatus: { - 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); - } + case Command::SenseInterruptStatus: { + 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_.template apply_edge<6>(false); - } - } break; - case Command::Specify: - specify_specs_ = decoder_.specify_specs(); - break; -// case Command::SenseDriveStatus: { -// } break; + bool any_remaining_interrupts = false; + for(; c < 4; c++) { + any_remaining_interrupts |= drives_[c].raised_interrupt; + } + if(!any_remaining_interrupts) { + pic_.template apply_edge<6>(false); + } + } break; + case Command::Specify: + specify_specs_ = decoder_.specify_specs(); + break; +// case Command::SenseDriveStatus: { +// } break; - case Command::Invalid: - results_.serialise_none(); - break; - } - - 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); - status_.set(MainStatus::DataReady, true); - status_.set(MainStatus::CommandInProgress, true); - } - } - } - - uint8_t read() { - using MainStatus = Intel::i8272::MainStatus; - if(status_.get(MainStatus::DataIsToProcessor)) { - const uint8_t result = results_.next(); - if(results_.empty()) { - status_.set(MainStatus::DataIsToProcessor, false); - status_.set(MainStatus::CommandInProgress, false); - } - return result; + case Command::Invalid: + results_.serialise_none(); + break; } - return 0x80; - } + decoder_.clear(); - 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); - } + // 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); + status_.set(MainStatus::DataReady, true); + status_.set(MainStatus::CommandInProgress, true); } } + } - void set_disk(std::shared_ptr disk, int drive) { -// if(drives_[drive].has_disk()) { -// // TODO: drive should only transition to unready if it was ready in the first place. -// drives_[drive].status = uint8_t(Intel::i8272::Status0::BecameNotReady); -// drives_[drive].raised_interrupt = true; -// pic_.apply_edge<6>(true); -// } - drives_[drive].set_disk(disk); + uint8_t read() { + using MainStatus = Intel::i8272::MainStatus; + if(status_.get(MainStatus::DataIsToProcessor)) { + const uint8_t result = results_.next(); + if(results_.empty()) { + status_.set(MainStatus::DataIsToProcessor, false); + status_.set(MainStatus::CommandInProgress, false); + } + return result; + } + + return 0x80; + } + + void set_activity_observer(Activity::Observer *const observer) { + observer_ = observer; + for(int c = 0; c < 4; c++) { + if(drives_[c].exists) { + observer_->register_led(drive_name(c), 0); + } + } + } + + void set_disk(std::shared_ptr disk, const int drive) { +// if(drives_[drive].has_disk()) { +// // TODO: drive should only transition to unready if it was ready in the first place. +// drives_[drive].status = uint8_t(Intel::i8272::Status0::BecameNotReady); +// drives_[drive].raised_interrupt = true; +// pic_.apply_edge<6>(true); +// } + drives_[drive].set_disk(disk); + } + +private: + void reset() { +// printf("FDC reset\n"); + decoder_.clear(); + status_.reset(); + + // 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++) { + drives_[c].raised_interrupt = true; + drives_[c].status = uint8_t(Intel::i8272::Status0::BecameNotReady); + } + pic_.template apply_edge<6>(true); + + using MainStatus = Intel::i8272::MainStatus; + status_.set(MainStatus::DataReady, true); + status_.set(MainStatus::DataIsToProcessor, false); + } + + PIC &pic_; + DMA &dma_; + + bool hold_reset_ = false; + bool enable_dma_ = false; + + Intel::i8272::CommandDecoder decoder_; + Intel::i8272::Status status_; + Intel::i8272::Results results_; + + Intel::i8272::CommandDecoder::SpecifySpecs specify_specs_; + struct DriveStatus { + public: + bool raised_interrupt = false; + uint8_t status = 0; + uint8_t track = 0; + bool motor = false; + bool exists = true; + + bool has_disk() const { + return static_cast(parser_); + } + + void set_disk(std::shared_ptr image) { + parser_ = std::make_unique(image); + } + + const Storage::Encodings::MFM::Sector *sector(const int head, const uint8_t sector) { + return parser_ ? parser_->sector(head, track, sector) : nullptr; } private: - void reset() { -// printf("FDC reset\n"); - decoder_.clear(); - status_.reset(); + std::unique_ptr parser_; + } drives_[4]; - // 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++) { - drives_[c].raised_interrupt = true; - drives_[c].status = uint8_t(Intel::i8272::Status0::BecameNotReady); - } - pic_.template apply_edge<6>(true); + static std::string drive_name(int c) { + char name[3] = "A"; + name[0] += c; + return std::string("Drive ") + name; + } - using MainStatus = Intel::i8272::MainStatus; - status_.set(MainStatus::DataReady, true); - status_.set(MainStatus::DataIsToProcessor, false); + Activity::Observer *observer_ = nullptr; +}; + +template +class KeyboardController; + +template +class KeyboardController> { +public: + KeyboardController(PIC &pic) : pic_(pic) {} + + // KB Status Port 61h high bits: + //; 01 - normal operation. wait for keypress, when one comes in, + //; force data line low (forcing keyboard to buffer additional + //; keypresses) and raise IRQ1 high + //; 11 - stop forcing data line low. lower IRQ1 and don't raise it again. + //; drop all incoming keypresses on the floor. + //; 10 - lower IRQ1 and force clock line low, resetting keyboard + //; 00 - force clock line low, resetting keyboard, but on a 01->00 transition, + //; IRQ1 would remain high + void set_mode(const uint8_t mode) { + const auto last_mode = mode_; + mode_ = Mode(mode); + switch(mode_) { + case Mode::NormalOperation: break; + case Mode::NoIRQsIgnoreInput: + pic_.template apply_edge<1>(false); + break; + case Mode::Reset: + input_.clear(); + [[fallthrough]]; + case Mode::ClearIRQReset: + pic_.template apply_edge<1>(false); + break; } - PIC &pic_; - DMA &dma_; + // If the reset condition ends, start a counter through until reset is complete. + if(last_mode == Mode::Reset && mode_ != Mode::Reset) { + reset_delay_ = 15; // Arbitrarily. + } + } - bool hold_reset_ = false; - bool enable_dma_ = false; + void run_for(const Cycles cycles) { + if(reset_delay_ <= 0) { + return; + } + reset_delay_ -= cycles.as(); + if(reset_delay_ <= 0) { + input_.clear(); + post(0xaa); + } + } - Intel::i8272::CommandDecoder decoder_; - Intel::i8272::Status status_; - Intel::i8272::Results results_; - - Intel::i8272::CommandDecoder::SpecifySpecs specify_specs_; - struct DriveStatus { - public: - bool raised_interrupt = false; - uint8_t status = 0; - uint8_t track = 0; - bool motor = false; - bool exists = true; - - bool has_disk() const { - return static_cast(parser_); - } - - void set_disk(std::shared_ptr image) { - parser_ = std::make_unique(image); - } - - const Storage::Encodings::MFM::Sector *sector(int head, uint8_t sector) { - return parser_ ? parser_->sector(head, track, sector) : nullptr; - } - - private: - std::unique_ptr parser_; - - } drives_[4]; - - static std::string drive_name(int c) { - char name[3] = "A"; - name[0] += c; - return std::string("Drive ") + name; + uint8_t read() { + pic_.template apply_edge<1>(false); + if(input_.empty()) { + return 0; } - Activity::Observer *observer_ = nullptr; + const uint8_t key = input_.front(); + input_.erase(input_.begin()); + if(!input_.empty()) { + pic_.template apply_edge<1>(true); + } + return key; + } + + void post(const uint8_t value) { + if(mode_ != Mode::NormalOperation || reset_delay_) { + return; + } + input_.push_back(value); + pic_.template apply_edge<1>(true); + } + +private: + enum class Mode { + NormalOperation = 0b01, + NoIRQsIgnoreInput = 0b11, + ClearIRQReset = 0b10, + Reset = 0b00, + } mode_; + + std::vector input_; + PIC &pic_; + + int reset_delay_ = 0; }; template -class KeyboardController { - public: - KeyboardController(PIC &pic) : pic_(pic) {} +class KeyboardController> { +public: + KeyboardController(PIC &pic) : pic_(pic) {} - // KB Status Port 61h high bits: - //; 01 - normal operation. wait for keypress, when one comes in, - //; force data line low (forcing keyboard to buffer additional - //; keypresses) and raise IRQ1 high - //; 11 - stop forcing data line low. lower IRQ1 and don't raise it again. - //; drop all incoming keypresses on the floor. - //; 10 - lower IRQ1 and force clock line low, resetting keyboard - //; 00 - force clock line low, resetting keyboard, but on a 01->00 transition, - //; IRQ1 would remain high - void set_mode(uint8_t mode) { - const auto last_mode = mode_; - mode_ = Mode(mode); - switch(mode_) { - case Mode::NormalOperation: break; - case Mode::NoIRQsIgnoreInput: - pic_.template apply_edge<1>(false); - break; - case Mode::Reset: - input_.clear(); - [[fallthrough]]; - case Mode::ClearIRQReset: - pic_.template apply_edge<1>(false); - break; - } + void run_for([[maybe_unused]] const Cycles cycles) { + } - // If the reset condition ends, start a counter through until reset is complete. - if(last_mode == Mode::Reset && mode_ != Mode::Reset) { - reset_delay_ = 15; // Arbitrarily. - } - } + void post([[maybe_unused]] const uint8_t value) { + } - void run_for(Cycles cycles) { - if(reset_delay_ <= 0) { - return; - } - reset_delay_ -= cycles.as(); - if(reset_delay_ <= 0) { - input_.clear(); - post(0xaa); - } - } - - uint8_t read() { - pic_.template apply_edge<1>(false); - if(input_.empty()) { - return 0; - } - - const uint8_t key = input_.front(); - input_.erase(input_.begin()); - if(!input_.empty()) { - pic_.template apply_edge<1>(true); - } - return key; - } - - void post(uint8_t value) { - if(mode_ != Mode::NormalOperation || reset_delay_) { - return; - } - input_.push_back(value); - pic_.template apply_edge<1>(true); - } - - private: - enum class Mode { - NormalOperation = 0b01, - NoIRQsIgnoreInput = 0b11, - ClearIRQReset = 0b10, - Reset = 0b00, - } mode_; - - std::vector input_; - PIC &pic_; - - int reset_delay_ = 0; +private: + PIC &pic_; }; struct PCSpeaker { @@ -450,12 +467,12 @@ struct PCSpeaker { cycles_since_update = 0; } - void set_pit(bool pit_input) { + void set_pit(const bool pit_input) { pit_input_ = pit_input; set_level(); } - void set_control(bool pit_mask, bool level) { + void set_control(const bool pit_mask, const bool level) { pit_mask_ = pit_mask; level_ = level; set_level(); @@ -485,27 +502,24 @@ struct PCSpeaker { template class PITObserver { - public: - PITObserver(PIC &pic, PCSpeaker &speaker) : pic_(pic), speaker_(speaker) {} +public: + PITObserver(PIC &pic, PCSpeaker &speaker) : pic_(pic), speaker_(speaker) {} - template - void update_output(bool new_level) { - switch(channel) { - default: break; - case 0: pic_.template apply_edge<0>(new_level); break; - case 2: speaker_.set_pit(new_level); break; - } + template + void update_output(const bool new_level) { + // channel 0 is connected to IRQ 0; + // channel 1 is used for DRAM refresh (presumably connected to DMA?); + // channel 2 is gated by a PPI output and feeds into the speaker. + switch(channel) { + default: break; + case 0: pic_.template apply_edge<0>(new_level); break; + case 2: speaker_.set_pit(new_level); break; } + } - private: - PIC &pic_; - PCSpeaker &speaker_; - - // TODO: - // - // channel 0 is connected to IRQ 0; - // channel 1 is used for DRAM refresh (presumably connected to DMA?); - // channel 2 is gated by a PPI output and feeds into the speaker. +private: + PIC &pic_; + PCSpeaker &speaker_; }; template @@ -513,386 +527,389 @@ using PIT = i8253>; template class i8255PortHandler : public Intel::i8255::PortHandler { - public: - i8255PortHandler( - PCSpeaker &speaker, - KeyboardController &keyboard, - const Target::VideoAdaptor adaptor, - const int drive_count - ) : - speaker_(speaker), keyboard_(keyboard) { - // High switches: - // - // b3, b2: drive count; 00 = 1, 01 = 2, etc - // b1, b0: video mode (00 = ROM; 01 = CGA40; 10 = CGA80; 11 = MDA) - switch(adaptor) { - default: break; - case Target::VideoAdaptor::MDA: high_switches_ |= 0b11; break; - case Target::VideoAdaptor::CGA: high_switches_ |= 0b10; break; // Assume 80 columns. - } - high_switches_ |= uint8_t(drive_count << 2); - - // Low switches: - // - // b3, b2: RAM on motherboard (64 * bit pattern) - // b1: 1 => FPU present; 0 => absent; - // b0: 1 => floppy drive present; 0 => absent. - low_switches_ |= 0b1100; // Assume maximum RAM. - if(drive_count) low_switches_ |= 0xb0001; +public: + i8255PortHandler( + PCSpeaker &speaker, + KeyboardController &keyboard, + const Target::VideoAdaptor adaptor, + const int drive_count + ) : + speaker_(speaker), keyboard_(keyboard) { + // High switches: + // + // b3, b2: drive count; 00 = 1, 01 = 2, etc + // b1, b0: video mode (00 = ROM; 01 = CGA40; 10 = CGA80; 11 = MDA) + switch(adaptor) { + default: break; + case Target::VideoAdaptor::MDA: high_switches_ |= 0b11; break; + case Target::VideoAdaptor::CGA: high_switches_ |= 0b10; break; // Assume 80 columns. } + high_switches_ |= uint8_t(drive_count << 2); - /// Supplies a hint about the user's display choice. If the high switches haven't been read yet and this is a CGA device, - /// this hint will be used to select between 40- and 80-column default display. - void hint_is_composite(bool composite) { - if(high_switches_observed_) { - return; - } - - switch(high_switches_ & 3) { - // Do nothing if a non-CGA card is in use. - case 0b00: case 0b11: - break; - - default: - high_switches_ &= ~0b11; - high_switches_ |= composite ? 0b01 : 0b10; - break; - } + // Low switches: + // + // b3, b2: RAM on motherboard (64 * bit pattern) + // b1: 1 => FPU present; 0 => absent; + // b0: 1 => floppy drive present; 0 => absent. + low_switches_ |= 0b1100; // Assume maximum RAM. + if(drive_count) low_switches_ |= 0xb0001; } - void set_value(int port, uint8_t value) { - switch(port) { - case 1: - // b7: 0 => enable keyboard read (and IRQ); 1 => don't; - // b6: 0 => hold keyboard clock low; 1 => don't; - // b5: 1 => disable IO check; 0 => don't; - // b4: 1 => disable memory parity check; 0 => don't; - // b3: [5150] cassette motor control; [5160] high or low switches select; - // b2: [5150] high or low switches select; [5160] 1 => disable turbo mode; - // b1, b0: speaker control. - enable_keyboard_ = !(value & 0x80); - keyboard_.set_mode(value >> 6); - - use_high_switches_ = value & 0x08; - speaker_.set_control(value & 0x01, value & 0x02); - break; - } + /// Supplies a hint about the user's display choice. If the high switches haven't been read yet and this is a CGA device, + /// this hint will be used to select between 40- and 80-column default display. + void hint_is_composite(bool composite) { + if(high_switches_observed_) { + return; } - uint8_t get_value(int port) { - switch(port) { - case 0: - high_switches_observed_ = true; - return enable_keyboard_ ? keyboard_.read() : uint8_t((high_switches_ << 4) | low_switches_); - // Guesses that switches is high and low combined as below. + switch(high_switches_ & 3) { + // Do nothing if a non-CGA card is in use. + case 0b00: case 0b11: + break; - case 2: - // b7: 1 => memory parity error; 0 => none; - // b6: 1 => IO channel error; 0 => none; - // b5: timer 2 output; [TODO] - // b4: cassette data input; [TODO] - // b3...b0: whichever of the high and low switches is selected. - high_switches_observed_ |= use_high_switches_; - return - use_high_switches_ ? high_switches_ : low_switches_; - } - return 0; - }; + default: + high_switches_ &= ~0b11; + high_switches_ |= composite ? 0b01 : 0b10; + break; + } + } - private: - bool high_switches_observed_ = false; - uint8_t high_switches_ = 0; - uint8_t low_switches_ = 0; + void set_value(int port, uint8_t value) { + switch(port) { + case 1: + // b7: 0 => enable keyboard read (and IRQ); 1 => don't; + // b6: 0 => hold keyboard clock low; 1 => don't; + // b5: 1 => disable IO check; 0 => don't; + // b4: 1 => disable memory parity check; 0 => don't; + // b3: [5150] cassette motor control; [5160] high or low switches select; + // b2: [5150] high or low switches select; [5160] 1 => disable turbo mode; + // b1, b0: speaker control. + enable_keyboard_ = !(value & 0x80); + keyboard_.set_mode(value >> 6); - bool use_high_switches_ = false; - PCSpeaker &speaker_; - KeyboardController &keyboard_; + use_high_switches_ = value & 0x08; + speaker_.set_control(value & 0x01, value & 0x02); + break; + } + } - bool enable_keyboard_ = false; + uint8_t get_value(int port) { + switch(port) { + case 0: + high_switches_observed_ = true; + return enable_keyboard_ ? keyboard_.read() : uint8_t((high_switches_ << 4) | low_switches_); + // Guesses that switches is high and low combined as below. + + case 2: + // b7: 1 => memory parity error; 0 => none; + // b6: 1 => IO channel error; 0 => none; + // b5: timer 2 output; [TODO] + // b4: cassette data input; [TODO] + // b3...b0: whichever of the high and low switches is selected. + high_switches_observed_ |= use_high_switches_; + return + use_high_switches_ ? high_switches_ : low_switches_; + } + return 0; + }; + +private: + bool high_switches_observed_ = false; + uint8_t high_switches_ = 0; + uint8_t low_switches_ = 0; + + bool use_high_switches_ = false; + PCSpeaker &speaker_; + KeyboardController &keyboard_; + + bool enable_keyboard_ = false; }; template using PPI = Intel::i8255::i8255>; template class IO { - public: - IO( - PIT &pit, - DMA &dma, - PPI &ppi, - PIC &pic, - typename Adaptor