1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-09-12 02:24:31 +00:00

Compare commits

..

29 Commits

Author SHA1 Message Date
Thomas Harte
035713b4d3 Remove logging. 2025-09-10 23:00:42 -04:00
Thomas Harte
54b7dc56b5 Resolve risk of acknowledged interrupt refiring. 2025-09-10 22:59:57 -04:00
Thomas Harte
7fd39f44d0 Add some logging, take a stab at returning requests. 2025-09-10 21:46:58 -04:00
Thomas Harte
691292501a Promote constexprs to static. 2025-09-10 21:46:44 -04:00
Thomas Harte
a58158ae08 Add PIT and PIC. 2025-09-10 21:45:51 -04:00
Thomas Harte
ef09b971fa Watch software interrupt flags.
Now tracking: issue seems to be reaching TEST4.ASM:D11 with an interrupt that it believes to be software-originating.
2025-09-10 15:47:19 -04:00
Thomas Harte
e07dee380d Experiment with further delays. 2025-09-10 14:18:30 -04:00
Thomas Harte
125bc5baa6 Install communication delay. 2025-09-10 13:48:51 -04:00
Thomas Harte
995444b11b Add TODO on what seems to be the current issue. 2025-09-10 11:33:38 -04:00
Thomas Harte
8f2384dbfc Fix log entry interleaving. 2025-09-10 09:52:55 -04:00
Thomas Harte
0cdd1c23ce Guess at another ID.
Cf. https://stanislavs.org/helppc/keyboard_commands.html
2025-09-09 23:40:55 -04:00
Thomas Harte
4765a39759 New guess: writing to the keyboard implicitly enables communications. 2025-09-09 23:38:21 -04:00
Thomas Harte
7f4047772c Continue naming things. 2025-09-09 15:36:02 -04:00
Thomas Harte
45c4ca6bec Attempt further to elide storage. 2025-09-09 13:58:37 -04:00
Thomas Harte
4a573a5aae Clarify one magic constant. 2025-09-09 13:44:31 -04:00
Thomas Harte
5125ff6a8c Combine enables, silence port 61 for now. 2025-09-09 11:16:42 -04:00
Thomas Harte
482d3301ce Avoid faulty sector access. 2025-09-08 23:14:50 -04:00
Thomas Harte
cdeec8ac47 Take various more failed swings at which bits do what. 2025-09-08 22:54:10 -04:00
Thomas Harte
3cef12b53b Reintroduce proper ordering of log comments. 2025-09-08 22:27:40 -04:00
Thomas Harte
dd098a16a8 Log more. 2025-09-08 21:54:56 -04:00
Thomas Harte
61a175e84a Merge branch 'master' into Keyboard 2025-09-08 21:43:25 -04:00
Thomas Harte
a5bcd38fe8 Slightly reformat. 2025-09-08 21:43:18 -04:00
Thomas Harte
5a57958639 Reduce log repetition. 2025-09-08 17:22:53 -04:00
Thomas Harte
260336c1e5 Adopt phase as communicative of whether more bytes are expected. 2025-09-08 17:13:27 -04:00
Thomas Harte
889cb9c78f Attempt a dual-queue solution to enabling/disabling keyboard. 2025-09-08 14:40:08 -04:00
Thomas Harte
b90e8f5af3 Further tweak reporting. 2025-09-06 23:16:10 -04:00
Thomas Harte
12361d2854 Adopt proper error/info distinction. 2025-09-06 23:13:33 -04:00
Thomas Harte
d307ddfa8e Merge branch 'master' into Keyboard 2025-09-05 23:21:41 -04:00
Thomas Harte
e0917dc734 Switch focus back to keyboard. 2025-09-04 22:21:05 -04:00
9 changed files with 278 additions and 117 deletions

View File

@@ -96,7 +96,7 @@ public:
target = (target & 0xff00) | value; target = (target & 0xff00) | value;
}; };
const auto load_high = [value](uint16_t &target) { 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)); target = uint16_t((target & 0x00ff) | ((value & mask) << 8));
}; };

View File

@@ -18,7 +18,7 @@ struct Vector {
int v[2]{}; int v[2]{};
template <int offset, bool high> void set(const uint8_t value) { 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; static constexpr int shift = high ? 8 : 0;
v[offset] = (v[offset] & ~(mask << shift)) | ((value & mask) << shift); v[offset] = (v[offset] & ~(mask << shift)) | ((value & mask) << shift);
} }

View File

@@ -217,14 +217,21 @@ public:
if(!sector) { if(!sector) {
status_.set(Intel::i8272::Status1::EndOfCylinder); status_.set(Intel::i8272::Status1::EndOfCylinder);
status_.set(Intel::i8272::Status0::AbnormalTermination); status_.set(Intel::i8272::Status0::AbnormalTermination);
}
results_.serialise(
status_,
0,
0,
0,
0);
} else {
results_.serialise( results_.serialise(
status_, status_,
sector->address.track, sector->address.track,
sector->address.side, sector->address.side,
sector->address.sector, sector->address.sector,
sector->size); sector->size);
}
drive.status = decoder_.drive_head(); drive.status = decoder_.drive_head();
drive.raised_interrupt = true; drive.raised_interrupt = true;

View File

@@ -120,7 +120,7 @@ private:
uint8_t controller_data_; uint8_t controller_data_;
uint8_t controller_status_; uint8_t controller_status_;
[[no_unique_address]] Log::Logger<Log::Source::IDE> log_; mutable Log::Logger<Log::Source::IDE> log_;
}; };
} }

View File

@@ -93,8 +93,8 @@ public:
return key; return key;
} }
auto &keyboard() { void post_keyboard(const uint8_t value) {
return *this; post(value);
} }
void post(const uint8_t value) { void post(const uint8_t value) {
@@ -126,18 +126,66 @@ private:
*/ */
template <Analyser::Static::PCCompatible::Model model> template <Analyser::Static::PCCompatible::Model model>
class KeyboardController<model, typename std::enable_if_t<is_at(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: public:
KeyboardController( KeyboardController(
PICs<model> &pics, PICs<model> &pics,
Speaker &speaker, Speaker &speaker,
const Analyser::Static::PCCompatible::Target::VideoAdaptor adaptor 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) { if(adaptor == Analyser::Static::PCCompatible::Target::VideoAdaptor::MDA) {
switches_ |= 0x40; switches_ |= 0x40;
} }
} }
void run_for(const Cycles cycles) { 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>(); instruction_count_ += cycles.as<int>();
if(!perform_delay_) { if(!perform_delay_) {
@@ -151,8 +199,9 @@ public:
} }
} }
auto &keyboard() { void post_keyboard(const uint8_t value) {
return keyboard_; keyboard_.post(value);
check_irqs();
} }
void write(const uint16_t port, const uint8_t value) { void write(const uint16_t port, const uint8_t value) {
@@ -162,7 +211,7 @@ public:
break; break;
case 0x0060: case 0x0060:
log_.error().append("Keyboard parameter set to %02x", value); log_.info().append("Keyboard parameter set to %02x", value);
phase_ = Phase::Data; phase_ = Phase::Data;
input_ = value; input_ = value;
has_input_ = true; has_input_ = true;
@@ -170,6 +219,7 @@ public:
break; break;
case 0x0061: case 0x0061:
// log_.info().append("Port 61: %02x", value);
// TODO: // TODO:
// b7: 1 = reset IRQ 0 // b7: 1 = reset IRQ 0
// b3: enable channel check // b3: enable channel check
@@ -178,12 +228,11 @@ public:
break; break;
case 0x0064: case 0x0064:
phase_ = Phase::Command; log_.info().append("Command byte: %02x", value);
command_ = Command(value); command_ = Command(value);
has_command_ = true; has_command_ = true;
has_input_ = false; has_input_ = false;
perform_delay_ = performance_delay(command_); perform_delay_ = performance_delay(command_);
perform_command(); perform_command();
break; break;
} }
@@ -197,14 +246,12 @@ public:
case 0x0060: { case 0x0060: {
if(has_output()) { if(has_output()) {
advance_output_queue_pointer(output_read_); last_output_ = next_output();
keyboard_.output().restart_delay();
if(!has_output()) { check_irqs();
pics_.pic[0].template apply_edge<1>(false);
} }
} log_.info().append("Read from keyboard controller of %02x", last_output_);
log_.error().append("Read from keyboard controller of %02x", output_queue_[output_read_]); return last_output_;
return output_queue_[output_read_];
} }
case 0x0061: case 0x0061:
@@ -220,18 +267,18 @@ public:
// b7 = 1 => parity error on transmission; // b7 = 1 => parity error on transmission;
// b6 = 1 => receive timeout; // b6 = 1 => receive timeout;
// b5 = 1 => transmit 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; // b3 = 1 = data at 0060 is command, 0 = data;
// b2 = 1 = selftest OK; 0 = just powered up or reset; // 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); // 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). // b0 = 1 => 'output' data is full (i.e. reading from 0x60 now makes sense — output is to PC).
const uint8_t status = const uint8_t status =
(enabled_ ? 0x10 : 0x00) | 0x10 |
(phase_ == Phase::Command ? 0x08 : 0x00) | (phase_ == Phase::Command ? 0x08 : 0x00) |
(is_tested_ ? 0x04 : 0x00) | (is_tested_ ? 0x04 : 0x00) |
(has_input_ ? 0x02 : 0x00) | (has_input_ ? 0x02 : 0x00) |
(has_output() ? 0x01 : 0x00); (has_output() ? 0x01 : 0x00);
// log_.error().append("Reading status: %02x", status); log_.info().append("Reading status: %02x", status);
return status; return status;
} }
} }
@@ -262,6 +309,11 @@ private:
ResetBlockBegin = 0xf0, ResetBlockBegin = 0xf0,
}; };
enum Control: uint8_t {
AllowKeyboardInterrupts = 0x01,
InhibitKeyboard = 0x10,
};
static constexpr bool requires_parameter(const Command command) { static constexpr bool requires_parameter(const Command command) {
return return
(command >= 0x60 && command < 0x80) || (command >= 0x60 && command < 0x80) ||
@@ -282,12 +334,13 @@ private:
void transmit(const uint8_t value) { void transmit(const uint8_t value) {
log_.info().append("Enquing %02x", value); log_.info().append("Enquing %02x", value);
advance_output_queue_pointer(output_write_); output_.append({value});
output_queue_[output_write_] = value; check_irqs();
pics_.pic[0].template apply_edge<1>(true); // TODO: verify.
} }
void perform_command() { void perform_command() {
phase_ = Phase::Data;
// Don't do anything until perform_delay_ is 0 and a command and/or other input is ready. // 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_)) { if(perform_delay_ || (!has_input_ && !has_command_)) {
return; return;
@@ -295,43 +348,42 @@ private:
// No command => input only, which is a direct-to-device communication. // No command => input only, which is a direct-to-device communication.
if(!has_command_) { 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_); log_.info().append("Device command: %02x", input_);
control_ &= ~Control::InhibitKeyboard;
keyboard_.perform(input_); keyboard_.perform(input_);
// TODO: mouse? // TODO: mouse?
has_input_ = false; has_input_ = false;
check_irqs();
return; return;
} }
// There is a command, but stop anyway if it requires a parameter and doesn't yet have one. // There is a command, but stop anyway if it requires a parameter and doesn't yet have one.
if(requires_parameter(command_) && !has_input_) { if(requires_parameter(command_) && !has_input_) {
phase_ = Phase::Command;
return; 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. // Consume command and parameter, and execute.
has_command_ = false; has_command_ = false;
if(requires_parameter(command_)) has_input_ = false; if(requires_parameter(command_)) has_input_ = false;
if(command_ >= Command::ResetBlockBegin) { if(command_ >= Command::ResetBlockBegin) {
log_.error().append("Should reset: %x", command_ & 0x0f); log_.info().append("Should reset: %x", command_ & 0x0f);
if(!(command_ & 1)) { if(!(command_ & 1)) {
cpu_control_->reset(); cpu_control_->reset();
} }
} else switch(command_) { } else switch(command_) {
default: default:
log_.info().append("Keyboard command unimplemented", command_); log_.info().append("Unimplemented keyboard controller command: %02x", command_);
break; break;
case Command::WriteCommandByte: case Command::WriteCommandByte:
is_tested_ = input_ & 0x4; control_ = input_;
check_irqs();
// TODO: // TODO:
// b0: 1 = enable first PS/2 port interrupt; // b0: 1 = enable first PS/2 port interrupt;
// b1: 1 = enable second port interrupt; // b1: 1 = enable second port interrupt;
@@ -349,22 +401,20 @@ private:
break; break;
case Command::InterfaceTest: case Command::InterfaceTest:
transmit(0); // i.e. no issues uncovered. transmit(0); // i.e. no issues uncovered.
// should_log = false;
break; break;
case Command::ReadTestInputs: 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; break;
case Command::DisableKeyboard: enabled_ = false; break; case Command::DisableKeyboard:
control_ |= Control::InhibitKeyboard;
check_irqs();
break;
case Command::EnableKeyboard: case Command::EnableKeyboard:
enabled_ = true; control_ &= ~Control::InhibitKeyboard;
check_irqs();
// 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();
}
break; break;
case Command::SetOutputByte: case Command::SetOutputByte:
@@ -383,6 +433,7 @@ private:
PICs<model> &pics_; PICs<model> &pics_;
Speaker &speaker_; Speaker &speaker_;
CPUControl<model> *cpu_control_ = nullptr; CPUControl<model> *cpu_control_ = nullptr;
uint8_t control_ = 0;
// Strongly coupled to specific code in the 5170 BIOS, this provides a grossly-inaccurate // 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. // linkage between execution speed (-ish) and DRAM refresh. An unambguous nonsense.
@@ -390,21 +441,13 @@ private:
uint8_t input_; uint8_t input_;
Command command_; Command command_;
std::optional<uint8_t> keyboard_command_;
ByteQueue<0> output_;
uint8_t last_output_ = 0xff;
bool has_input_ = false; bool has_input_ = false;
bool has_command_ = 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 7 = 0 keyboard inhibited
// bit 6 = 0 CGA, else MDA // bit 6 = 0 CGA, else MDA
// bit 5 = 0 manufacturing jumper installed // bit 5 = 0 manufacturing jumper installed
@@ -415,42 +458,83 @@ private:
int perform_delay_ = 0; int perform_delay_ = 0;
bool is_tested_ = false; bool is_tested_ = false;
bool enabled_ = false;
enum class Phase { enum class Phase {
Command, Command,
Data, Data,
} phase_ = Phase::Command; } phase_ = Phase::Data;
struct Keyboard { struct Keyboard {
Keyboard(KeyboardController<model> &controller) : controller_(controller) {} // 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.
void post(const uint8_t value) { // Not least because of the nonsense fiction here: delivering XT-converted keypresses
controller_.post_keyboard({value}); // directly from an AT keyboard.
void post(const uint8_t key_change) {
output_.append({key_change});
} }
void perform(const uint8_t command) { void perform(const uint8_t command) {
// TODO: delay needed here.
switch(command) { 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: case 0xf2:
controller_.post_keyboard({0xfa}); output_.append({0xfa, 0x83, 0xab});
break; break;
case 0xff: case 0xff:
controller_.post_keyboard({0xfa, 0xaa}); output_.append({0xfa, 0xaa});
// should_log = true;
break; break;
} }
} }
auto &output() {
return output_;
}
const auto &output() const {
return output_;
}
bool run_for(const int instructions) {
return output_.run_for(instructions);
}
private: private:
KeyboardController<model> &controller_; Log::Logger<Log::Source::Keyboard> log_;
ByteQueue<50> output_;
} keyboard_; } keyboard_;
friend Keyboard; bool has_output() const {
void post_keyboard(const std::initializer_list<uint8_t> values) { return !output_.empty();
if(!enabled_) return;
for(const auto value : values) {
transmit(value);
} }
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);
} }
}; };

View File

@@ -908,7 +908,12 @@ public:
// Signal interrupt. // Signal interrupt.
context_.flow_controller.unhalt(); 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. // Do nothing if currently halted.
@@ -951,24 +956,25 @@ public:
// should_log = (decoded_ip_ >= 0x21d0 && decoded_ip_ < 0x221c); // should_log = (decoded_ip_ >= 0x21d0 && decoded_ip_ < 0x221c);
if(should_log) { if(should_log) {
const auto next = to_string(decoded_, InstructionSet::x86::Model::i8086); log.info().append(
static std::string previous; "%04x %s \t\t[ds:6Bh]:%02x",
if(next != previous) { decoded_ip_,
std::cout << std::hex << decoded_ip_ << " " << next; to_string(decoded_, InstructionSet::x86::Model::i80286).c_str(),
// context_.registers.bl()
if(decoded_.second.operation() == InstructionSet::x86::Operation::INT) { context_.memory.template access<uint8_t, InstructionSet::x86::AccessType::PreauthorisedRead>(
std::cout << " dl:" << std::hex << +context_.registers.dl() << "; "; InstructionSet::x86::Source::DS,
std::cout << "ah:" << std::hex << +context_.registers.ah() << "; "; uint16_t(0x6b)
std::cout << "ch:" << std::hex << +context_.registers.ch() << "; "; )
std::cout << "cl:" << std::hex << +context_.registers.cl() << "; "; ).append_if(decoded_.second.operation() == InstructionSet::x86::Operation::INT,
std::cout << "dh:" << std::hex << +context_.registers.dh() << "; "; " dl:%02x ah:%02x ch:%02x cl:%02x dh:%02x es:%04x bx:%04x",
std::cout << "es:" << std::hex << +context_.registers.es() << "; "; context_.registers.dl(),
std::cout << "bx:" << std::hex << +context_.registers.bx(); context_.registers.ah(),
} context_.registers.ch(),
context_.registers.cl(),
std::cout << std::endl; context_.registers.dh(),
previous = next; context_.registers.es(),
} context_.registers.bx()
);
} }
if(decoded_.second.operation() == InstructionSet::x86::Operation::Invalid) { 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 { 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. // MARK: - Activity::Source.

View File

@@ -9,11 +9,13 @@
#pragma once #pragma once
#include "Analyser/Static/PCCompatible/Target.hpp" #include "Analyser/Static/PCCompatible/Target.hpp"
#include "Outputs/Log.hpp"
namespace PCCompatible { namespace PCCompatible {
// Cf. https://helppc.netcore2k.net/hardware/pic // Cf. https://helppc.netcore2k.net/hardware/pic
class PIC { class PIC {
using Log = Log::Logger<Log::Source::PIC>;
public: public:
template <int address> template <int address>
void write(const uint8_t value) { void write(const uint8_t value) {
@@ -42,6 +44,7 @@ public:
} }
} else { } else {
mask_ = value; mask_ = value;
Log::info().append("Mask set to %02x; requests now %02x", mask_, requests_);
} }
} else { } else {
if(value & 0x10) { if(value & 0x10) {
@@ -59,6 +62,8 @@ public:
single_pic_ = value & 2; single_pic_ = value & 2;
four_byte_vectors_ = value & 4; four_byte_vectors_ = value & 4;
level_triggered_ = value & 8; level_triggered_ = value & 8;
Log::info().append("Level triggered: %d", level_triggered_);
} else if(value & 0x08) { } else if(value & 0x08) {
// //
// Operation Control Word 3. // Operation Control Word 3.
@@ -78,7 +83,7 @@ public:
// b2, b1, b0: interrupt level to acknowledge. // b2, b1, b0: interrupt level to acknowledge.
switch(value >> 5) { switch(value >> 5) {
default: default:
printf("PIC: TODO EOI type %d\n", value >> 5); Log::error().append("PIC: TODO EOI type %d\n", value >> 5);
[[fallthrough]]; [[fallthrough]];
case 0b010: // No-op. case 0b010: // No-op.
break; break;
@@ -109,21 +114,26 @@ public:
if(address) { if(address) {
return mask_; return mask_;
} }
return 0;
Log::error().append("Reading address 0");
return requests_;
} }
template <int input> template <int input>
void apply_edge(const bool final_level) { 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_) { if(level_triggered_) {
requests_ &= ~input_mask; requests_ = levels_;
} } else {
if(final_level) { requests_ |= (levels_ ^ old_levels) & new_bit;
requests_ |= input_mask;
} }
Log::info().append("%d to %d => requests now %02x", input, final_level, requests_);
} }
bool pending() const { bool pending() const {
@@ -143,6 +153,7 @@ public:
eoi_target_ = id; eoi_target_ = id;
awaiting_eoi_ = !auto_eoi_; awaiting_eoi_ = !auto_eoi_;
requests_ &= ~in_service_; requests_ &= ~in_service_;
Log::info().append("Implicitly acknowledging: %d; requests now: %02x", id, requests_);
return uint8_t(vector_base_ + id); return uint8_t(vector_base_ + id);
} }
@@ -163,6 +174,7 @@ private:
uint8_t requests_ = 0; uint8_t requests_ = 0;
uint8_t in_service_ = 0; uint8_t in_service_ = 0;
uint8_t levels_ = 0;
struct ConfgurationState { struct ConfgurationState {
int word; int word;

View File

@@ -62,7 +62,7 @@
</Testables> </Testables>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Release" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableASanStackUseAfterReturn = "YES" enableASanStackUseAfterReturn = "YES"

View File

@@ -56,6 +56,8 @@ enum class Source {
OpenGL, OpenGL,
PCCompatible, PCCompatible,
PCPOST, PCPOST,
PIC,
PIT,
Plus4, Plus4,
PCMTrack, PCMTrack,
SCC, SCC,
@@ -96,19 +98,20 @@ constexpr EnabledLevel enabled_level(const Source source) {
case Source::SCC: case Source::SCC:
case Source::SCSI: case Source::SCSI:
case Source::I2C: case Source::I2C:
// case Source::PCPOST:
return EnabledLevel::None; return EnabledLevel::None;
case Source::Floppy: case Source::Floppy:
// case Source::Keyboard:
return EnabledLevel::Errors; return EnabledLevel::Errors;
} }
} }
constexpr const char *prefix(const Source source) { constexpr const char *prefix(const Source source) {
switch(source) { switch(source) {
default: return nullptr;
case Source::ADBDevice: return "ADB device"; case Source::ADBDevice: return "ADB device";
case Source::ADBGLU: return "ADB GLU"; case Source::ADBGLU: return "ADB GLU";
case Source::Amiga: return "Amiga";
case Source::AmigaBlitter: return "Blitter"; case Source::AmigaBlitter: return "Blitter";
case Source::AmigaChipset: return "Chipset"; case Source::AmigaChipset: return "Chipset";
case Source::AmigaCopper: return "Copper"; case Source::AmigaCopper: return "Copper";
@@ -143,31 +146,80 @@ constexpr const char *prefix(const Source source) {
case Source::Plus4: return "Plus4"; case Source::Plus4: return "Plus4";
case Source::PCCompatible: return "PC"; case Source::PCCompatible: return "PC";
case Source::PCPOST: return "POST"; case Source::PCPOST: return "POST";
case Source::PIC: return "PIC";
case Source::PIT: return "PIT";
case Source::PCMTrack: return "PCM Track"; case Source::PCMTrack: return "PCM Track";
case Source::SCSI: return "SCSI"; case Source::SCSI: return "SCSI";
case Source::SCC: return "SCC"; case Source::SCC: return "SCC";
case Source::SZX: return "SZX"; case Source::SZX: return "SZX";
case Source::TapeUEF: return "UEF"; case Source::TapeUEF: return "UEF";
case Source::TMS9918: return "TMS9918";
case Source::TZX: return "TZX"; case Source::TZX: return "TZX";
case Source::Vic20: return "Vic20"; case Source::Vic20: return "Vic20";
case Source::WDFDC: return "WD FDC"; case Source::WDFDC: return "WD FDC";
} }
return nullptr;
} }
template <Source source, bool enabled> template <Source source, bool enabled>
struct LogLine; 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> template <Source source>
struct LogLine<source, true> { struct LogLine<source, true>: private AccumulatingLog {
public: public:
explicit LogLine(FILE *const stream) noexcept : stream_(stream) { explicit LogLine(FILE *const stream) noexcept :
static constexpr auto source_prefix = prefix(source); stream_(stream) {}
if(!source_prefix) return;
append("[%s] ", source_prefix);
}
~LogLine() { ~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> template <size_t size, typename... Args>
@@ -190,8 +242,8 @@ public:
} }
private: private:
std::string output_;
FILE *stream_; FILE *stream_;
std::string output_;
}; };
template <Source source> template <Source source>