diff --git a/Machines/PCCompatible/DMA.hpp b/Machines/PCCompatible/DMA.hpp index 955e2fc8e..f4a66af04 100644 --- a/Machines/PCCompatible/DMA.hpp +++ b/Machines/PCCompatible/DMA.hpp @@ -23,296 +23,301 @@ enum class AccessResult { NotAccepted, }; -class i8237 { - public: - // - // CPU-facing interface. - // +// Per-channel state. +struct i8237Channel { + bool mask = false; + enum class Transfer { + Verify, Write, Read, Invalid + } transfer = Transfer::Verify; + bool autoinitialise = false; + bool address_decrement = false; + enum class Mode { + Demand, Single, Block, Cascade + } mode = Mode::Demand; - template - void write(uint8_t value) { -// printf("DMA: Write %02x to %d\n", value, address); + bool request = false; + bool transfer_complete = false; - switch(address) { - default: { - constexpr int channel = (address >> 1) & 3; - constexpr bool is_count = address & 1; - - next_access_low_ ^= true; - if(next_access_low_) { - if constexpr (is_count) { - channels_[channel].count.halves.high = value; - } else { - channels_[channel].address.halves.high = value; - } - } else { - if constexpr (is_count) { - channels_[channel].count.halves.low = value; - } else { - channels_[channel].address.halves.low = value; - } - } - } break; - case 0x8: set_command(value); break; - case 0x9: set_reset_request(value); break; - case 0xa: set_reset_mask(value); break; - case 0xb: set_mode(value); break; - case 0xc: flip_flop_reset(); break; - case 0xd: master_reset(); break; - case 0xe: mask_reset(); break; - case 0xf: set_mask(value); break; - } - } - - template - uint8_t read() { -// printf("DMA: Read %d\n", address); - switch(address) { - default: { - constexpr int channel = (address >> 1) & 3; - constexpr bool is_count = address & 1; - - next_access_low_ ^= true; - if(next_access_low_) { - if constexpr (is_count) { - return channels_[channel].count.halves.high; - } else { - return channels_[channel].address.halves.high; - } - } else { - if constexpr (is_count) { - return channels_[channel].count.halves.low; - } else { - return channels_[channel].address.halves.low; - } - } - } break; - case 0x8: return status(); break; - case 0xd: return temporary_register(); break; - } - } - - // - // Interface for reading/writing via DMA. - // - - /// Provides the next target address for @c channel if performing either a write (if @c is_write is @c true) or read (otherwise). - /// - /// @returns A combined address and @c AccessResult. - std::pair access(size_t channel, bool is_write) { - if(is_write && channels_[channel].transfer != Channel::Transfer::Write) { - return std::make_pair(0, AccessResult::NotAccepted); - } - if(!is_write && channels_[channel].transfer != Channel::Transfer::Read) { - return std::make_pair(0, AccessResult::NotAccepted); - } - - const auto address = channels_[channel].address.full; - channels_[channel].address.full += channels_[channel].address_decrement ? -1 : 1; - - --channels_[channel].count.full; - - const bool was_complete = channels_[channel].transfer_complete; - channels_[channel].transfer_complete = (channels_[channel].count.full == 0xffff); - if(channels_[channel].transfer_complete) { - // TODO: _something_ with mode. - } - - auto result = AccessResult::Accepted; - if(!was_complete && channels_[channel].transfer_complete) { - result = AccessResult::AcceptedWithEOP; - } - return std::make_pair(address, result); - } - - void set_complete(size_t channel) { - channels_[channel].transfer_complete = true; - } - - private: - uint8_t status() { - const uint8_t result = - (channels_[0].transfer_complete ? 0x01 : 0x00) | - (channels_[1].transfer_complete ? 0x02 : 0x00) | - (channels_[2].transfer_complete ? 0x04 : 0x00) | - (channels_[3].transfer_complete ? 0x08 : 0x00) | - - (channels_[0].request ? 0x10 : 0x00) | - (channels_[1].request ? 0x20 : 0x00) | - (channels_[2].request ? 0x40 : 0x00) | - (channels_[3].request ? 0x80 : 0x00); - - for(auto &channel : channels_) { - channel.transfer_complete = false; - } - -// printf("DMA: status is %02x\n", result); - - return result; - } - - uint8_t temporary_register() const { - // Not actually implemented, so... - return 0xff; - } - - void flip_flop_reset() { -// printf("DMA: Flip flop reset\n"); - next_access_low_ = true; - } - - void mask_reset() { -// printf("DMA: Mask reset\n"); - for(auto &channel : channels_) { - channel.mask = false; - } - } - - void master_reset() { -// printf("DMA: Master reset\n"); - flip_flop_reset(); - for(auto &channel : channels_) { - channel.mask = true; - channel.transfer_complete = false; - channel.request = false; - } - - // This is a bit of a hack; DMA channel 0 is supposed to be linked to the PIT, - // performing DRAM refresh. It isn't yet. So hack this, and hack that. - channels_[0].transfer_complete = true; - } - - void set_reset_mask(uint8_t value) { -// printf("DMA: Set/reset mask %02x\n", value); - channels_[value & 3].mask = value & 4; - } - - void set_reset_request(uint8_t value) { -// printf("DMA: Set/reset request %02x\n", value); - channels_[value & 3].request = value & 4; - } - - void set_mask(uint8_t value) { -// printf("DMA: Set mask %02x\n", value); - channels_[0].mask = value & 1; - channels_[1].mask = value & 2; - channels_[2].mask = value & 4; - channels_[3].mask = value & 8; - } - - void set_mode(uint8_t value) { -// printf("DMA: Set mode %02x\n", value); - channels_[value & 3].transfer = Channel::Transfer((value >> 2) & 3); - channels_[value & 3].autoinitialise = value & 0x10; - channels_[value & 3].address_decrement = value & 0x20; - channels_[value & 3].mode = Channel::Mode(value >> 6); - } - - void set_command(uint8_t value) { -// printf("DMA: Set command %02x\n", value); - enable_memory_to_memory_ = value & 0x01; - enable_channel0_address_hold_ = value & 0x02; - enable_controller_ = value & 0x04; - compressed_timing_ = value & 0x08; - rotating_priority_ = value & 0x10; - extended_write_selection_ = value & 0x20; - dreq_active_low_ = value & 0x40; - dack_sense_active_high_ = value & 0x80; - } - - // Low/high byte latch. - bool next_access_low_ = true; - - // Various fields set by the command register. - bool enable_memory_to_memory_ = false; - bool enable_channel0_address_hold_ = false; - bool enable_controller_ = false; - bool compressed_timing_ = false; - bool rotating_priority_ = false; - bool extended_write_selection_ = false; - bool dreq_active_low_ = false; - bool dack_sense_active_high_ = false; - - // Per-channel state. - struct Channel { - bool mask = false; - enum class Transfer { - Verify, Write, Read, Invalid - } transfer = Transfer::Verify; - bool autoinitialise = false; - bool address_decrement = false; - enum class Mode { - Demand, Single, Block, Cascade - } mode = Mode::Demand; - - bool request = false; - bool transfer_complete = false; - - CPU::RegisterPair16 address, count; - }; - std::array channels_; + CPU::RegisterPair16 address, count; }; +template +class i8237 { + using Channel = i8237Channel; + +public: + // + // CPU-facing interface. + // + + template + void write(const uint8_t value) { + switch(address) { + default: { + constexpr int channel = (address >> 1) & 3; + constexpr bool is_count = address & 1; + + next_access_low_ ^= true; + if(next_access_low_) { + if constexpr (is_count) { + channels_[channel].count.halves.high = value; + } else { + channels_[channel].address.halves.high = value; + } + } else { + if constexpr (is_count) { + channels_[channel].count.halves.low = value; + } else { + channels_[channel].address.halves.low = value; + } + } + } break; + case 0x8: set_command(value); break; + case 0x9: set_reset_request(value); break; + case 0xa: set_reset_mask(value); break; + case 0xb: set_mode(value); break; + case 0xc: flip_flop_reset(); break; + case 0xd: master_reset(); break; + case 0xe: mask_reset(); break; + case 0xf: set_mask(value); break; + } + } + + template + uint8_t read() { + switch(address) { + default: { + constexpr int channel = (address >> 1) & 3; + constexpr bool is_count = address & 1; + + next_access_low_ ^= true; + if(next_access_low_) { + if constexpr (is_count) { + return channels_[channel].count.halves.high; + } else { + return channels_[channel].address.halves.high; + } + } else { + if constexpr (is_count) { + return channels_[channel].count.halves.low; + } else { + return channels_[channel].address.halves.low; + } + } + } + case 0x8: return status(); + case 0xd: return temporary_register(); + } + } + + // + // Interface for reading/writing via DMA. + // + + /// Provides the next target address for @c channel if performing either a write (if @c is_write is @c true) or read (otherwise). + /// + /// @returns A combined address and @c AccessResult. + std::pair access(size_t channel, bool is_write) { + if(is_write && channels_[channel].transfer != Channel::Transfer::Write) { + return std::make_pair(0, AccessResult::NotAccepted); + } + if(!is_write && channels_[channel].transfer != Channel::Transfer::Read) { + return std::make_pair(0, AccessResult::NotAccepted); + } + + const auto address = channels_[channel].address.full; + channels_[channel].address.full += channels_[channel].address_decrement ? -1 : 1; + + --channels_[channel].count.full; + + const bool was_complete = channels_[channel].transfer_complete; + channels_[channel].transfer_complete = (channels_[channel].count.full == 0xffff); + if(channels_[channel].transfer_complete) { + // TODO: _something_ with mode. + } + + auto result = AccessResult::Accepted; + if(!was_complete && channels_[channel].transfer_complete) { + result = AccessResult::AcceptedWithEOP; + } + return std::make_pair(address, result); + } + + void set_complete(size_t channel) { + channels_[channel].transfer_complete = true; + } + +private: + uint8_t status() { + const uint8_t result = + (channels_[0].transfer_complete ? 0x01 : 0x00) | + (channels_[1].transfer_complete ? 0x02 : 0x00) | + (channels_[2].transfer_complete ? 0x04 : 0x00) | + (channels_[3].transfer_complete ? 0x08 : 0x00) | + + (channels_[0].request ? 0x10 : 0x00) | + (channels_[1].request ? 0x20 : 0x00) | + (channels_[2].request ? 0x40 : 0x00) | + (channels_[3].request ? 0x80 : 0x00); + + for(auto &channel : channels_) { + channel.transfer_complete = false; + } + + return result; + } + + uint8_t temporary_register() const { + // Not actually implemented, so... + return 0xff; + } + + void flip_flop_reset() { + next_access_low_ = true; + } + + void mask_reset() { + for(auto &channel : channels_) { + channel.mask = false; + } + } + + void master_reset() { + flip_flop_reset(); + for(auto &channel : channels_) { + channel.mask = true; + channel.transfer_complete = false; + channel.request = false; + } + + // This is a bit of a hack; DMA channel 0 is supposed to be linked to the PIT, + // performing DRAM refresh. It isn't yet. So hack this, and hack that. + channels_[0].transfer_complete = true; + } + + void set_reset_mask(uint8_t value) { + channels_[value & 3].mask = value & 4; + } + + void set_reset_request(uint8_t value) { + channels_[value & 3].request = value & 4; + } + + void set_mask(uint8_t value) { + channels_[0].mask = value & 1; + channels_[1].mask = value & 2; + channels_[2].mask = value & 4; + channels_[3].mask = value & 8; + } + + void set_mode(const uint8_t value) { + channels_[value & 3].transfer = Channel::Transfer((value >> 2) & 3); + channels_[value & 3].autoinitialise = value & 0x10; + channels_[value & 3].address_decrement = value & 0x20; + channels_[value & 3].mode = Channel::Mode(value >> 6); + } + + void set_command(uint8_t value) { + enable_memory_to_memory_ = value & 0x01; + enable_channel0_address_hold_ = value & 0x02; + enable_controller_ = value & 0x04; + compressed_timing_ = value & 0x08; + rotating_priority_ = value & 0x10; + extended_write_selection_ = value & 0x20; + dreq_active_low_ = value & 0x40; + dack_sense_active_high_ = value & 0x80; + } + + // Low/high byte latch. + bool next_access_low_ = true; + + // Various fields set by the command register. + bool enable_memory_to_memory_ = false; + bool enable_channel0_address_hold_ = false; + bool enable_controller_ = false; + bool compressed_timing_ = false; + bool rotating_priority_ = false; + bool extended_write_selection_ = false; + bool dreq_active_low_ = false; + bool dack_sense_active_high_ = false; + + std::array channels_; +}; + +template class DMAPages { - public: - template - void set_page(uint8_t value) { - pages_[page_for_index(index)] = value; - } - - template - uint8_t page() { - return pages_[page_for_index(index)]; - } - - uint8_t channel_page(size_t channel) { - return pages_[channel]; - } - - private: - uint8_t pages_[8]; - - constexpr int page_for_index(int index) { - switch(index) { - case 7: return 0; - case 3: return 1; - case 1: return 2; - case 2: return 3; - - default: - case 0: return 4; - case 4: return 5; - case 5: return 6; - case 6: return 7; - } +public: + template + void set_page(const uint8_t value) { + pages_[page_for_index(index)] = value; + } + + template + uint8_t page() const { + return pages_[page_for_index(index)]; + } + + uint8_t channel_page(const size_t channel) const { + return pages_[channel]; + } + +private: + uint8_t pages_[16]{}; + + static constexpr int page_for_index(const int index) { + switch(index) { + // Channels the PC architecture uses. + case 0x7: return 0; + case 0x3: return 1; + case 0x1: return 2; + case 0x2: return 3; + + case 0xb: return 5; + case 0x9: return 6; + case 0xa: return 7; + + // Spare storage. + default: + case 0x0: return 4; + case 0x4: return 8; + case 0x5: return 9; + case 0x6: return 10; + case 0x8: return 11; + case 0xc: return 12; + case 0xd: return 13; + case 0xe: return 14; + case 0xf: return 15; } + } }; template class DMA { - public: - i8237 controller; - DMAPages pages; + static constexpr bool has_second_dma = model >= Analyser::Static::PCCompatible::Model::AT; - // Memory is set posthoc to resolve a startup time. - void set_memory(Memory *memory) { - memory_ = memory; - } +public: + i8237 controller; + DMAPages pages; - // TODO: this permits only 8-bit DMA. Fix that. - AccessResult write(size_t channel, uint8_t value) { - auto access = controller.access(channel, true); - if(access.second == AccessResult::NotAccepted) { - return access.second; - } + // Memory is set posthoc to resolve a startup time. + void set_memory(Memory *memory) { + memory_ = memory; + } - const uint32_t address = uint32_t(pages.channel_page(channel) << 16) | access.first; - *memory_->at(address) = value; + // TODO: this permits only 8-bit DMA. Fix that. + AccessResult write(size_t channel, uint8_t value) { + auto access = controller.access(channel, true); + if(access.second == AccessResult::NotAccepted) { return access.second; } - private: - Memory *memory_; + const uint32_t address = uint32_t(pages.channel_page(channel) << 16) | access.first; + *memory_->at(address) = value; + return access.second; + } + +private: + Memory *memory_; }; } diff --git a/Machines/PCCompatible/PCCompatible.cpp b/Machines/PCCompatible/PCCompatible.cpp index 51b0bd70a..69522c16f 100644 --- a/Machines/PCCompatible/PCCompatible.cpp +++ b/Machines/PCCompatible/PCCompatible.cpp @@ -627,18 +627,27 @@ class IO { ) : pit_(pit), dma_(dma), ppi_(ppi), pic_(pic), video_(card), fdc_(fdc), rtc_(rtc) {} - template void out(uint16_t port, IntT value) { + template void out(const uint16_t port, const IntT value) { + static const auto log_unhandled = [](const uint16_t port, const IntT value) { + if constexpr (std::is_same_v) { + log.error().append("Unhandled out: %02x to %04x", value, port); + } else { + log.error().append("Unhandled out: %04x to %04x", value, port); + } + }; + static const auto has_second_dma = [](const uint16_t port, const IntT value) { + if constexpr (model >= Analyser::Static::PCCompatible::Model::AT) { + return true; + } + log_unhandled(port, value); + return false; + }; + static constexpr uint16_t crtc_base = video == Target::VideoAdaptor::MDA ? 0x03b0 : 0x03d0; switch(port) { - default: - if constexpr (std::is_same_v) { - log.error().append("Unhandled out: %02x to %04x", value, port); - } else { - log.error().append("Unhandled out: %04x to %04x", value, port); - } - break; + default: log_unhandled(port, value); break; case 0x0070: rtc_.write<0>(uint8_t(value)); break; case 0x0071: rtc_.write<1>(uint8_t(value)); break; @@ -689,6 +698,15 @@ class IO { case 0x0086: dma_.pages.template set_page<6>(uint8_t(value)); break; case 0x0087: dma_.pages.template set_page<7>(uint8_t(value)); break; + case 0x0088: if(has_second_dma(port, value)) dma_.pages.template set_page<0x8>(uint8_t(value)); break; + case 0x0089: if(has_second_dma(port, value)) dma_.pages.template set_page<0x9>(uint8_t(value)); break; + case 0x008a: if(has_second_dma(port, value)) dma_.pages.template set_page<0xa>(uint8_t(value)); break; + case 0x008b: if(has_second_dma(port, value)) dma_.pages.template set_page<0xb>(uint8_t(value)); break; + case 0x008c: if(has_second_dma(port, value)) dma_.pages.template set_page<0xc>(uint8_t(value)); break; + case 0x008d: if(has_second_dma(port, value)) dma_.pages.template set_page<0xd>(uint8_t(value)); break; + case 0x008e: if(has_second_dma(port, value)) dma_.pages.template set_page<0xe>(uint8_t(value)); break; + case 0x008f: if(has_second_dma(port, value)) dma_.pages.template set_page<0xf>(uint8_t(value)); break; + // // CRTC access block, with slightly laboured 16-bit to 8-bit mapping. // @@ -742,11 +760,20 @@ class IO { break; } } - template IntT in([[maybe_unused]] uint16_t port) { + template IntT in(const uint16_t port) { + static const auto log_unhandled = [](const uint16_t port) { + log.error().append("Unhandled in: %04x", port); + }; + static const auto has_second_dma = [](const uint16_t port) { + if constexpr (model >= Analyser::Static::PCCompatible::Model::AT) { + return true; + } + log_unhandled(port); + return false; + }; + switch(port) { - default: - log.error().append("Unhandled in: %04x", port); - break; + default: log_unhandled(port); break; case 0x0000: return dma_.controller.template read<0x0>(); case 0x0001: return dma_.controller.template read<0x1>(); @@ -788,6 +815,15 @@ class IO { case 0x0086: return dma_.pages.template page<6>(); case 0x0087: return dma_.pages.template page<7>(); + case 0x0088: if(has_second_dma(port)) return dma_.pages.template page<0x8>(); break; + case 0x0089: if(has_second_dma(port)) return dma_.pages.template page<0x9>(); break; + case 0x008a: if(has_second_dma(port)) return dma_.pages.template page<0xa>(); break; + case 0x008b: if(has_second_dma(port)) return dma_.pages.template page<0xb>(); break; + case 0x008c: if(has_second_dma(port)) return dma_.pages.template page<0xc>(); break; + case 0x008d: if(has_second_dma(port)) return dma_.pages.template page<0xd>(); break; + case 0x008e: if(has_second_dma(port)) return dma_.pages.template page<0xe>(); break; + case 0x008f: if(has_second_dma(port)) return dma_.pages.template page<0xf>(); break; + case 0x0201: break; // Ignore game port. case 0x0278: case 0x0279: case 0x027a: @@ -1254,7 +1290,7 @@ class ConcreteMachine: using namespace PCCompatible; namespace { -static constexpr bool ForceAT = false; +static constexpr bool ForceAT = true; template std::unique_ptr machine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) {