mirror of
https://github.com/TomHarte/CLK.git
synced 2025-04-21 18:37:11 +00:00
Attempt to offer expanded DMA top bytes.
This commit is contained in:
parent
0eef2c0d04
commit
367c2b568a
@ -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 <int address>
|
||||
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 <int address>
|
||||
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<uint16_t, AccessResult> 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<Channel, 4> channels_;
|
||||
CPU::RegisterPair16 address, count;
|
||||
};
|
||||
|
||||
template <bool is_pair>
|
||||
class i8237 {
|
||||
using Channel = i8237Channel;
|
||||
|
||||
public:
|
||||
//
|
||||
// CPU-facing interface.
|
||||
//
|
||||
|
||||
template <int address>
|
||||
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 <int address>
|
||||
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<uint16_t, AccessResult> 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<Channel, 4> channels_;
|
||||
};
|
||||
|
||||
template <bool is_pair>
|
||||
class DMAPages {
|
||||
public:
|
||||
template <int index>
|
||||
void set_page(uint8_t value) {
|
||||
pages_[page_for_index(index)] = value;
|
||||
}
|
||||
|
||||
template <int index>
|
||||
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 <int index>
|
||||
void set_page(const uint8_t value) {
|
||||
pages_[page_for_index(index)] = value;
|
||||
}
|
||||
|
||||
template <int index>
|
||||
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 <Analyser::Static::PCCompatible::Model model>
|
||||
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<model> *memory) {
|
||||
memory_ = memory;
|
||||
}
|
||||
public:
|
||||
i8237<has_second_dma> controller;
|
||||
DMAPages<has_second_dma> 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<model> *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<model> *memory_;
|
||||
const uint32_t address = uint32_t(pages.channel_page(channel) << 16) | access.first;
|
||||
*memory_->at(address) = value;
|
||||
return access.second;
|
||||
}
|
||||
|
||||
private:
|
||||
Memory<model> *memory_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -627,18 +627,27 @@ class IO {
|
||||
) :
|
||||
pit_(pit), dma_(dma), ppi_(ppi), pic_(pic), video_(card), fdc_(fdc), rtc_(rtc) {}
|
||||
|
||||
template <typename IntT> void out(uint16_t port, IntT value) {
|
||||
template <typename IntT> 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<IntT, uint8_t>) {
|
||||
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<IntT, uint8_t>) {
|
||||
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 <typename IntT> IntT in([[maybe_unused]] uint16_t port) {
|
||||
template <typename IntT> 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 <Target::VideoAdaptor video>
|
||||
std::unique_ptr<Machine> machine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user