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;
|
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));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
@@ -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_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -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.
|
||||||
|
@@ -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;
|
||||||
|
@@ -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"
|
||||||
|
@@ -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>
|
||||||
|
Reference in New Issue
Block a user