mirror of
https://github.com/TomHarte/CLK.git
synced 2025-09-12 02:24:31 +00:00
Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
035713b4d3 | ||
|
54b7dc56b5 | ||
|
7fd39f44d0 | ||
|
691292501a | ||
|
a58158ae08 | ||
|
ef09b971fa | ||
|
e07dee380d | ||
|
125bc5baa6 | ||
|
995444b11b | ||
|
8f2384dbfc | ||
|
0cdd1c23ce | ||
|
4765a39759 | ||
|
7f4047772c | ||
|
45c4ca6bec | ||
|
4a573a5aae | ||
|
5125ff6a8c | ||
|
482d3301ce | ||
|
cdeec8ac47 | ||
|
3cef12b53b | ||
|
dd098a16a8 | ||
|
61a175e84a | ||
|
a5bcd38fe8 | ||
|
5a57958639 | ||
|
260336c1e5 | ||
|
889cb9c78f | ||
|
b90e8f5af3 | ||
|
12361d2854 | ||
|
d307ddfa8e | ||
|
e0917dc734 |
@@ -96,7 +96,7 @@ public:
|
||||
target = (target & 0xff00) | value;
|
||||
};
|
||||
const auto load_high = [value](uint16_t &target) {
|
||||
constexpr uint8_t mask = RefreshMask >> 8;
|
||||
static constexpr uint8_t mask = RefreshMask >> 8;
|
||||
target = uint16_t((target & 0x00ff) | ((value & mask) << 8));
|
||||
};
|
||||
|
||||
|
@@ -18,7 +18,7 @@ struct Vector {
|
||||
int v[2]{};
|
||||
|
||||
template <int offset, bool high> void set(const uint8_t value) {
|
||||
constexpr uint8_t mask = high ? (offset ? 0x3 : 0x1) : 0xff;
|
||||
static constexpr uint8_t mask = high ? (offset ? 0x3 : 0x1) : 0xff;
|
||||
static constexpr int shift = high ? 8 : 0;
|
||||
v[offset] = (v[offset] & ~(mask << shift)) | ((value & mask) << shift);
|
||||
}
|
||||
|
@@ -217,14 +217,21 @@ public:
|
||||
if(!sector) {
|
||||
status_.set(Intel::i8272::Status1::EndOfCylinder);
|
||||
status_.set(Intel::i8272::Status0::AbnormalTermination);
|
||||
}
|
||||
|
||||
results_.serialise(
|
||||
status_,
|
||||
sector->address.track,
|
||||
sector->address.side,
|
||||
sector->address.sector,
|
||||
sector->size);
|
||||
results_.serialise(
|
||||
status_,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0);
|
||||
} else {
|
||||
results_.serialise(
|
||||
status_,
|
||||
sector->address.track,
|
||||
sector->address.side,
|
||||
sector->address.sector,
|
||||
sector->size);
|
||||
}
|
||||
|
||||
drive.status = decoder_.drive_head();
|
||||
drive.raised_interrupt = true;
|
||||
|
@@ -120,7 +120,7 @@ private:
|
||||
uint8_t controller_data_;
|
||||
uint8_t controller_status_;
|
||||
|
||||
[[no_unique_address]] Log::Logger<Log::Source::IDE> log_;
|
||||
mutable Log::Logger<Log::Source::IDE> log_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -93,8 +93,8 @@ public:
|
||||
return key;
|
||||
}
|
||||
|
||||
auto &keyboard() {
|
||||
return *this;
|
||||
void post_keyboard(const uint8_t value) {
|
||||
post(value);
|
||||
}
|
||||
|
||||
void post(const uint8_t value) {
|
||||
@@ -126,18 +126,66 @@ private:
|
||||
*/
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
class KeyboardController<model, typename std::enable_if_t<is_at(model)>> {
|
||||
private:
|
||||
template <int delay>
|
||||
struct ByteQueue {
|
||||
public:
|
||||
void append(const std::initializer_list<uint8_t> values) {
|
||||
if(delay && queue_.empty()) {
|
||||
restart_delay();
|
||||
}
|
||||
// Insert in reverse order, at the start of the vector. All outgoing values
|
||||
// are popped from the back. So inserts are expensive, reads are cheap.
|
||||
queue_.insert(queue_.begin(), std::rbegin(values), std::rend(values));
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
if(delay && delay_count_) {
|
||||
return true;
|
||||
}
|
||||
return queue_.empty();
|
||||
}
|
||||
|
||||
void restart_delay() {
|
||||
delay_count_ = delay;
|
||||
}
|
||||
|
||||
uint8_t next() {
|
||||
const auto next = queue_.back();
|
||||
queue_.pop_back();
|
||||
restart_delay();
|
||||
return next;
|
||||
}
|
||||
|
||||
bool run_for(const int ticks) {
|
||||
if(!delay_count_) return false;
|
||||
delay_count_ = std::max(delay_count_ - ticks, 0);
|
||||
return !delay_count_ && !queue_.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> queue_;
|
||||
int delay_count_ = 0;
|
||||
};
|
||||
public:
|
||||
KeyboardController(
|
||||
PICs<model> &pics,
|
||||
Speaker &speaker,
|
||||
const Analyser::Static::PCCompatible::Target::VideoAdaptor adaptor
|
||||
) : pics_(pics), speaker_(speaker), keyboard_(*this) {
|
||||
) : pics_(pics), speaker_(speaker) {
|
||||
if(adaptor == Analyser::Static::PCCompatible::Target::VideoAdaptor::MDA) {
|
||||
switches_ |= 0x40;
|
||||
}
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) {
|
||||
const bool output_advanced = output_.run_for(cycles.as<int>());
|
||||
const bool keyboard_advanced = keyboard_.run_for(cycles.as<int>());
|
||||
if(output_advanced || keyboard_advanced) {
|
||||
log_.info().append("Advancing output");
|
||||
check_irqs();
|
||||
}
|
||||
|
||||
instruction_count_ += cycles.as<int>();
|
||||
|
||||
if(!perform_delay_) {
|
||||
@@ -151,8 +199,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
auto &keyboard() {
|
||||
return keyboard_;
|
||||
void post_keyboard(const uint8_t value) {
|
||||
keyboard_.post(value);
|
||||
check_irqs();
|
||||
}
|
||||
|
||||
void write(const uint16_t port, const uint8_t value) {
|
||||
@@ -162,7 +211,7 @@ public:
|
||||
break;
|
||||
|
||||
case 0x0060:
|
||||
log_.error().append("Keyboard parameter set to %02x", value);
|
||||
log_.info().append("Keyboard parameter set to %02x", value);
|
||||
phase_ = Phase::Data;
|
||||
input_ = value;
|
||||
has_input_ = true;
|
||||
@@ -170,6 +219,7 @@ public:
|
||||
break;
|
||||
|
||||
case 0x0061:
|
||||
// log_.info().append("Port 61: %02x", value);
|
||||
// TODO:
|
||||
// b7: 1 = reset IRQ 0
|
||||
// b3: enable channel check
|
||||
@@ -178,12 +228,11 @@ public:
|
||||
break;
|
||||
|
||||
case 0x0064:
|
||||
phase_ = Phase::Command;
|
||||
log_.info().append("Command byte: %02x", value);
|
||||
command_ = Command(value);
|
||||
has_command_ = true;
|
||||
has_input_ = false;
|
||||
perform_delay_ = performance_delay(command_);
|
||||
|
||||
perform_command();
|
||||
break;
|
||||
}
|
||||
@@ -197,14 +246,12 @@ public:
|
||||
|
||||
case 0x0060: {
|
||||
if(has_output()) {
|
||||
advance_output_queue_pointer(output_read_);
|
||||
|
||||
if(!has_output()) {
|
||||
pics_.pic[0].template apply_edge<1>(false);
|
||||
}
|
||||
last_output_ = next_output();
|
||||
keyboard_.output().restart_delay();
|
||||
check_irqs();
|
||||
}
|
||||
log_.error().append("Read from keyboard controller of %02x", output_queue_[output_read_]);
|
||||
return output_queue_[output_read_];
|
||||
log_.info().append("Read from keyboard controller of %02x", last_output_);
|
||||
return last_output_;
|
||||
}
|
||||
|
||||
case 0x0061:
|
||||
@@ -220,18 +267,18 @@ public:
|
||||
// b7 = 1 => parity error on transmission;
|
||||
// b6 = 1 => receive timeout;
|
||||
// b5 = 1 => transmit timeout;
|
||||
// b4 = 1 => keyboard active;
|
||||
// b4 = 1 => keyboard enabled via physical key;
|
||||
// b3 = 1 = data at 0060 is command, 0 = data;
|
||||
// b2 = 1 = selftest OK; 0 = just powered up or reset;
|
||||
// b1 = 1 => 'input' buffer full (i.e. don't write 0x60 or 0x64 now — this is input to the controller);
|
||||
// b0 = 1 => 'output' data is full (i.e. reading from 0x60 now makes sense — output is to PC).
|
||||
const uint8_t status =
|
||||
(enabled_ ? 0x10 : 0x00) |
|
||||
0x10 |
|
||||
(phase_ == Phase::Command ? 0x08 : 0x00) |
|
||||
(is_tested_ ? 0x04 : 0x00) |
|
||||
(has_input_ ? 0x02 : 0x00) |
|
||||
(has_output() ? 0x01 : 0x00);
|
||||
// log_.error().append("Reading status: %02x", status);
|
||||
log_.info().append("Reading status: %02x", status);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
@@ -262,6 +309,11 @@ private:
|
||||
ResetBlockBegin = 0xf0,
|
||||
};
|
||||
|
||||
enum Control: uint8_t {
|
||||
AllowKeyboardInterrupts = 0x01,
|
||||
InhibitKeyboard = 0x10,
|
||||
};
|
||||
|
||||
static constexpr bool requires_parameter(const Command command) {
|
||||
return
|
||||
(command >= 0x60 && command < 0x80) ||
|
||||
@@ -282,12 +334,13 @@ private:
|
||||
|
||||
void transmit(const uint8_t value) {
|
||||
log_.info().append("Enquing %02x", value);
|
||||
advance_output_queue_pointer(output_write_);
|
||||
output_queue_[output_write_] = value;
|
||||
pics_.pic[0].template apply_edge<1>(true); // TODO: verify.
|
||||
output_.append({value});
|
||||
check_irqs();
|
||||
}
|
||||
|
||||
void perform_command() {
|
||||
phase_ = Phase::Data;
|
||||
|
||||
// Don't do anything until perform_delay_ is 0 and a command and/or other input is ready.
|
||||
if(perform_delay_ || (!has_input_ && !has_command_)) {
|
||||
return;
|
||||
@@ -295,43 +348,42 @@ private:
|
||||
|
||||
// No command => input only, which is a direct-to-device communication.
|
||||
if(!has_command_) {
|
||||
if(!enabled_) {
|
||||
log_.info().append("Storing device command for later: %02x", input_);
|
||||
keyboard_command_ = input_;
|
||||
has_input_ = false;
|
||||
return;
|
||||
}
|
||||
log_.info().append("Device command: %02x", input_);
|
||||
control_ &= ~Control::InhibitKeyboard;
|
||||
keyboard_.perform(input_);
|
||||
// TODO: mouse?
|
||||
has_input_ = false;
|
||||
|
||||
check_irqs();
|
||||
return;
|
||||
}
|
||||
|
||||
// There is a command, but stop anyway if it requires a parameter and doesn't yet have one.
|
||||
if(requires_parameter(command_) && !has_input_) {
|
||||
phase_ = Phase::Command;
|
||||
return;
|
||||
}
|
||||
|
||||
log_.info().append("Controller command: %02x", command_).append_if(has_input_, " / %02x", input_);
|
||||
log_.info().append("Performing: %02x", command_).append_if(has_input_, " / %02x", input_);
|
||||
|
||||
// Consume command and parameter, and execute.
|
||||
has_command_ = false;
|
||||
if(requires_parameter(command_)) has_input_ = false;
|
||||
|
||||
if(command_ >= Command::ResetBlockBegin) {
|
||||
log_.error().append("Should reset: %x", command_ & 0x0f);
|
||||
log_.info().append("Should reset: %x", command_ & 0x0f);
|
||||
|
||||
if(!(command_ & 1)) {
|
||||
cpu_control_->reset();
|
||||
}
|
||||
} else switch(command_) {
|
||||
default:
|
||||
log_.info().append("Keyboard command unimplemented", command_);
|
||||
log_.info().append("Unimplemented keyboard controller command: %02x", command_);
|
||||
break;
|
||||
|
||||
case Command::WriteCommandByte:
|
||||
is_tested_ = input_ & 0x4;
|
||||
control_ = input_;
|
||||
check_irqs();
|
||||
// TODO:
|
||||
// b0: 1 = enable first PS/2 port interrupt;
|
||||
// b1: 1 = enable second port interrupt;
|
||||
@@ -349,22 +401,20 @@ private:
|
||||
break;
|
||||
case Command::InterfaceTest:
|
||||
transmit(0); // i.e. no issues uncovered.
|
||||
// should_log = false;
|
||||
break;
|
||||
case Command::ReadTestInputs:
|
||||
transmit(enabled_ ? 0x01 : 0x00);
|
||||
// b0 is the keyboard clock; ensure it's inhibited when asked but otherwise don't attempt realism.
|
||||
transmit((control_ & Control::InhibitKeyboard) ? 0x00 : 0x01);
|
||||
break;
|
||||
|
||||
case Command::DisableKeyboard: enabled_ = false; break;
|
||||
case Command::DisableKeyboard:
|
||||
control_ |= Control::InhibitKeyboard;
|
||||
check_irqs();
|
||||
break;
|
||||
case Command::EnableKeyboard:
|
||||
enabled_ = true;
|
||||
|
||||
// If a keybaord command was enqueued, post it now.
|
||||
if(keyboard_command_.has_value()) {
|
||||
input_ = *keyboard_command_;
|
||||
keyboard_command_ = std::nullopt;
|
||||
has_input_ = true;
|
||||
perform_command();
|
||||
}
|
||||
control_ &= ~Control::InhibitKeyboard;
|
||||
check_irqs();
|
||||
break;
|
||||
|
||||
case Command::SetOutputByte:
|
||||
@@ -383,6 +433,7 @@ private:
|
||||
PICs<model> &pics_;
|
||||
Speaker &speaker_;
|
||||
CPUControl<model> *cpu_control_ = nullptr;
|
||||
uint8_t control_ = 0;
|
||||
|
||||
// Strongly coupled to specific code in the 5170 BIOS, this provides a grossly-inaccurate
|
||||
// linkage between execution speed (-ish) and DRAM refresh. An unambguous nonsense.
|
||||
@@ -390,21 +441,13 @@ private:
|
||||
|
||||
uint8_t input_;
|
||||
Command command_;
|
||||
std::optional<uint8_t> keyboard_command_;
|
||||
|
||||
ByteQueue<0> output_;
|
||||
uint8_t last_output_ = 0xff;
|
||||
|
||||
bool has_input_ = false;
|
||||
bool has_command_ = false;
|
||||
|
||||
std::array<uint8_t, 8> output_queue_;
|
||||
void advance_output_queue_pointer(size_t &pointer) {
|
||||
pointer = (pointer + 1) % output_queue_.size();
|
||||
}
|
||||
|
||||
size_t output_read_ = 0, output_write_ = 0;
|
||||
bool has_output() const {
|
||||
return output_read_ != output_write_;
|
||||
}
|
||||
|
||||
// bit 7 = 0 keyboard inhibited
|
||||
// bit 6 = 0 CGA, else MDA
|
||||
// bit 5 = 0 manufacturing jumper installed
|
||||
@@ -415,42 +458,83 @@ private:
|
||||
int perform_delay_ = 0;
|
||||
|
||||
bool is_tested_ = false;
|
||||
bool enabled_ = false;
|
||||
|
||||
enum class Phase {
|
||||
Command,
|
||||
Data,
|
||||
} phase_ = Phase::Command;
|
||||
} phase_ = Phase::Data;
|
||||
|
||||
struct Keyboard {
|
||||
Keyboard(KeyboardController<model> &controller) : controller_(controller) {}
|
||||
|
||||
void post(const uint8_t value) {
|
||||
controller_.post_keyboard({value});
|
||||
// TODO: this is the aped interface for receiving key events from the underlying PC,
|
||||
// hastily added to align with that for the XT controller. A better interface is needed.
|
||||
// Not least because of the nonsense fiction here: delivering XT-converted keypresses
|
||||
// directly from an AT keyboard.
|
||||
void post(const uint8_t key_change) {
|
||||
output_.append({key_change});
|
||||
}
|
||||
|
||||
void perform(const uint8_t command) {
|
||||
// TODO: delay needed here.
|
||||
|
||||
switch(command) {
|
||||
default:
|
||||
log_.error().append("Unimplemented keyboard command: %02x", command);
|
||||
return;
|
||||
|
||||
// case 0xed:
|
||||
// // TODO: use next incoming byte to set LEDs.
|
||||
// output_.append({0xfa});
|
||||
// break;
|
||||
|
||||
case 0xf2:
|
||||
controller_.post_keyboard({0xfa});
|
||||
output_.append({0xfa, 0x83, 0xab});
|
||||
break;
|
||||
|
||||
case 0xff:
|
||||
controller_.post_keyboard({0xfa, 0xaa});
|
||||
output_.append({0xfa, 0xaa});
|
||||
// should_log = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto &output() {
|
||||
return output_;
|
||||
}
|
||||
|
||||
const auto &output() const {
|
||||
return output_;
|
||||
}
|
||||
|
||||
bool run_for(const int instructions) {
|
||||
return output_.run_for(instructions);
|
||||
}
|
||||
|
||||
private:
|
||||
KeyboardController<model> &controller_;
|
||||
Log::Logger<Log::Source::Keyboard> log_;
|
||||
ByteQueue<50> output_;
|
||||
} keyboard_;
|
||||
|
||||
friend Keyboard;
|
||||
void post_keyboard(const std::initializer_list<uint8_t> values) {
|
||||
if(!enabled_) return;
|
||||
for(const auto value : values) {
|
||||
transmit(value);
|
||||
bool has_output() const {
|
||||
return !output_.empty();
|
||||
}
|
||||
|
||||
uint8_t next_output() {
|
||||
if(!output_.empty()) {
|
||||
return output_.next();
|
||||
}
|
||||
|
||||
// Should be unreachable.
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void check_irqs() {
|
||||
bool new_irq1 = false;
|
||||
if(output_.empty() && !(control_ & Control::InhibitKeyboard) && !keyboard_.output().empty()) {
|
||||
output_.append({keyboard_.output().next()});
|
||||
new_irq1 = control_ & Control::AllowKeyboardInterrupts;
|
||||
}
|
||||
pics_.pic[0].template apply_edge<1>(new_irq1);
|
||||
log_.info().append("IRQ1: %d", new_irq1);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -908,7 +908,12 @@ public:
|
||||
|
||||
// Signal interrupt.
|
||||
context_.flow_controller.unhalt();
|
||||
InstructionSet::x86::fault(Exception::interrupt(pics_.pic[0].acknowledge()), context_);
|
||||
const auto interrupt_id = pics_.pic[0].acknowledge();
|
||||
InstructionSet::x86::fault(Exception::interrupt(interrupt_id), context_);
|
||||
|
||||
if(should_log) {
|
||||
log.info().append("Taking interrupt vector %d", interrupt_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Do nothing if currently halted.
|
||||
@@ -951,24 +956,25 @@ public:
|
||||
// should_log = (decoded_ip_ >= 0x21d0 && decoded_ip_ < 0x221c);
|
||||
|
||||
if(should_log) {
|
||||
const auto next = to_string(decoded_, InstructionSet::x86::Model::i8086);
|
||||
static std::string previous;
|
||||
if(next != previous) {
|
||||
std::cout << std::hex << decoded_ip_ << " " << next;
|
||||
|
||||
if(decoded_.second.operation() == InstructionSet::x86::Operation::INT) {
|
||||
std::cout << " dl:" << std::hex << +context_.registers.dl() << "; ";
|
||||
std::cout << "ah:" << std::hex << +context_.registers.ah() << "; ";
|
||||
std::cout << "ch:" << std::hex << +context_.registers.ch() << "; ";
|
||||
std::cout << "cl:" << std::hex << +context_.registers.cl() << "; ";
|
||||
std::cout << "dh:" << std::hex << +context_.registers.dh() << "; ";
|
||||
std::cout << "es:" << std::hex << +context_.registers.es() << "; ";
|
||||
std::cout << "bx:" << std::hex << +context_.registers.bx();
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
previous = next;
|
||||
}
|
||||
log.info().append(
|
||||
"%04x %s \t\t[ds:6Bh]:%02x",
|
||||
decoded_ip_,
|
||||
to_string(decoded_, InstructionSet::x86::Model::i80286).c_str(),
|
||||
// context_.registers.bl()
|
||||
context_.memory.template access<uint8_t, InstructionSet::x86::AccessType::PreauthorisedRead>(
|
||||
InstructionSet::x86::Source::DS,
|
||||
uint16_t(0x6b)
|
||||
)
|
||||
).append_if(decoded_.second.operation() == InstructionSet::x86::Operation::INT,
|
||||
" dl:%02x ah:%02x ch:%02x cl:%02x dh:%02x es:%04x bx:%04x",
|
||||
context_.registers.dl(),
|
||||
context_.registers.ah(),
|
||||
context_.registers.ch(),
|
||||
context_.registers.cl(),
|
||||
context_.registers.dh(),
|
||||
context_.registers.es(),
|
||||
context_.registers.bx()
|
||||
);
|
||||
}
|
||||
|
||||
if(decoded_.second.operation() == InstructionSet::x86::Operation::Invalid) {
|
||||
@@ -1020,7 +1026,7 @@ public:
|
||||
}
|
||||
|
||||
void set_key_state(const uint16_t key, const bool is_pressed) final {
|
||||
keyboard_.keyboard().post(uint8_t(key | (is_pressed ? 0x00 : 0x80)));
|
||||
keyboard_.post_keyboard(uint8_t(key | (is_pressed ? 0x00 : 0x80)));
|
||||
}
|
||||
|
||||
// MARK: - Activity::Source.
|
||||
|
@@ -9,11 +9,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "Analyser/Static/PCCompatible/Target.hpp"
|
||||
#include "Outputs/Log.hpp"
|
||||
|
||||
namespace PCCompatible {
|
||||
|
||||
// Cf. https://helppc.netcore2k.net/hardware/pic
|
||||
class PIC {
|
||||
using Log = Log::Logger<Log::Source::PIC>;
|
||||
public:
|
||||
template <int address>
|
||||
void write(const uint8_t value) {
|
||||
@@ -42,6 +44,7 @@ public:
|
||||
}
|
||||
} else {
|
||||
mask_ = value;
|
||||
Log::info().append("Mask set to %02x; requests now %02x", mask_, requests_);
|
||||
}
|
||||
} else {
|
||||
if(value & 0x10) {
|
||||
@@ -59,6 +62,8 @@ public:
|
||||
single_pic_ = value & 2;
|
||||
four_byte_vectors_ = value & 4;
|
||||
level_triggered_ = value & 8;
|
||||
|
||||
Log::info().append("Level triggered: %d", level_triggered_);
|
||||
} else if(value & 0x08) {
|
||||
//
|
||||
// Operation Control Word 3.
|
||||
@@ -78,7 +83,7 @@ public:
|
||||
// b2, b1, b0: interrupt level to acknowledge.
|
||||
switch(value >> 5) {
|
||||
default:
|
||||
printf("PIC: TODO EOI type %d\n", value >> 5);
|
||||
Log::error().append("PIC: TODO EOI type %d\n", value >> 5);
|
||||
[[fallthrough]];
|
||||
case 0b010: // No-op.
|
||||
break;
|
||||
@@ -109,21 +114,26 @@ public:
|
||||
if(address) {
|
||||
return mask_;
|
||||
}
|
||||
return 0;
|
||||
|
||||
Log::error().append("Reading address 0");
|
||||
return requests_;
|
||||
}
|
||||
|
||||
template <int input>
|
||||
void apply_edge(const bool final_level) {
|
||||
constexpr uint8_t input_mask = 1 << input;
|
||||
static constexpr uint8_t input_mask = 1 << input;
|
||||
|
||||
const auto old_levels = levels_;
|
||||
const uint8_t new_bit = final_level ? input_mask : 0;
|
||||
levels_ = (levels_ & ~input_mask) | new_bit;
|
||||
|
||||
// Guess: level triggered means the request can be forwarded only so long as the
|
||||
// relevant input is actually high. Whereas edge triggered implies capturing state.
|
||||
if(level_triggered_) {
|
||||
requests_ &= ~input_mask;
|
||||
}
|
||||
if(final_level) {
|
||||
requests_ |= input_mask;
|
||||
requests_ = levels_;
|
||||
} else {
|
||||
requests_ |= (levels_ ^ old_levels) & new_bit;
|
||||
}
|
||||
|
||||
Log::info().append("%d to %d => requests now %02x", input, final_level, requests_);
|
||||
}
|
||||
|
||||
bool pending() const {
|
||||
@@ -143,6 +153,7 @@ public:
|
||||
eoi_target_ = id;
|
||||
awaiting_eoi_ = !auto_eoi_;
|
||||
requests_ &= ~in_service_;
|
||||
Log::info().append("Implicitly acknowledging: %d; requests now: %02x", id, requests_);
|
||||
return uint8_t(vector_base_ + id);
|
||||
}
|
||||
|
||||
@@ -163,6 +174,7 @@ private:
|
||||
|
||||
uint8_t requests_ = 0;
|
||||
uint8_t in_service_ = 0;
|
||||
uint8_t levels_ = 0;
|
||||
|
||||
struct ConfgurationState {
|
||||
int word;
|
||||
|
@@ -62,7 +62,7 @@
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableASanStackUseAfterReturn = "YES"
|
||||
|
@@ -56,6 +56,8 @@ enum class Source {
|
||||
OpenGL,
|
||||
PCCompatible,
|
||||
PCPOST,
|
||||
PIC,
|
||||
PIT,
|
||||
Plus4,
|
||||
PCMTrack,
|
||||
SCC,
|
||||
@@ -96,19 +98,20 @@ constexpr EnabledLevel enabled_level(const Source source) {
|
||||
case Source::SCC:
|
||||
case Source::SCSI:
|
||||
case Source::I2C:
|
||||
// case Source::PCPOST:
|
||||
return EnabledLevel::None;
|
||||
|
||||
case Source::Floppy:
|
||||
// case Source::Keyboard:
|
||||
return EnabledLevel::Errors;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr const char *prefix(const Source source) {
|
||||
switch(source) {
|
||||
default: return nullptr;
|
||||
|
||||
case Source::ADBDevice: return "ADB device";
|
||||
case Source::ADBGLU: return "ADB GLU";
|
||||
case Source::Amiga: return "Amiga";
|
||||
case Source::AmigaBlitter: return "Blitter";
|
||||
case Source::AmigaChipset: return "Chipset";
|
||||
case Source::AmigaCopper: return "Copper";
|
||||
@@ -143,31 +146,80 @@ constexpr const char *prefix(const Source source) {
|
||||
case Source::Plus4: return "Plus4";
|
||||
case Source::PCCompatible: return "PC";
|
||||
case Source::PCPOST: return "POST";
|
||||
case Source::PIC: return "PIC";
|
||||
case Source::PIT: return "PIT";
|
||||
case Source::PCMTrack: return "PCM Track";
|
||||
case Source::SCSI: return "SCSI";
|
||||
case Source::SCC: return "SCC";
|
||||
case Source::SZX: return "SZX";
|
||||
case Source::TapeUEF: return "UEF";
|
||||
case Source::TMS9918: return "TMS9918";
|
||||
case Source::TZX: return "TZX";
|
||||
case Source::Vic20: return "Vic20";
|
||||
case Source::WDFDC: return "WD FDC";
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <Source source, bool enabled>
|
||||
struct LogLine;
|
||||
|
||||
struct RepeatAccumulator {
|
||||
std::string last;
|
||||
Source source;
|
||||
|
||||
size_t count = 0;
|
||||
FILE *stream;
|
||||
};
|
||||
|
||||
struct AccumulatingLog {
|
||||
inline static thread_local RepeatAccumulator accumulator_;
|
||||
};
|
||||
|
||||
template <Source source>
|
||||
struct LogLine<source, true> {
|
||||
struct LogLine<source, true>: private AccumulatingLog {
|
||||
public:
|
||||
explicit LogLine(FILE *const stream) noexcept : stream_(stream) {
|
||||
static constexpr auto source_prefix = prefix(source);
|
||||
if(!source_prefix) return;
|
||||
append("[%s] ", source_prefix);
|
||||
}
|
||||
explicit LogLine(FILE *const stream) noexcept :
|
||||
stream_(stream) {}
|
||||
|
||||
~LogLine() {
|
||||
fprintf(stream_, "%s\n", output_.c_str());
|
||||
if(output_ == accumulator_.last && source == accumulator_.source && stream_ == accumulator_.stream) {
|
||||
++accumulator_.count;
|
||||
return;
|
||||
}
|
||||
|
||||
if(!accumulator_.last.empty()) {
|
||||
const char *const unadorned_prefix = prefix(accumulator_.source);
|
||||
std::string prefix;
|
||||
if(unadorned_prefix) {
|
||||
prefix = "[";
|
||||
prefix += unadorned_prefix;
|
||||
prefix += "] ";
|
||||
}
|
||||
|
||||
if(accumulator_.count > 1) {
|
||||
fprintf(
|
||||
accumulator_.stream,
|
||||
"%s%s [* %zu]\n",
|
||||
prefix.c_str(),
|
||||
accumulator_.last.c_str(),
|
||||
accumulator_.count
|
||||
);
|
||||
} else {
|
||||
fprintf(
|
||||
accumulator_.stream,
|
||||
"%s%s\n",
|
||||
prefix.c_str(),
|
||||
accumulator_.last.c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
accumulator_.count = 1;
|
||||
accumulator_.last = output_;
|
||||
accumulator_.source = source;
|
||||
accumulator_.stream = stream_;
|
||||
}
|
||||
|
||||
template <size_t size, typename... Args>
|
||||
@@ -190,8 +242,8 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
std::string output_;
|
||||
FILE *stream_;
|
||||
std::string output_;
|
||||
};
|
||||
|
||||
template <Source source>
|
||||
|
Reference in New Issue
Block a user