use alloc::boxed::Box;
use alloc::collections::VecDeque;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use core::fmt;
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind};
use crate::core::error::{BusError, Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::sched::{Budget, Consumed};
use crate::core::space::{AccessConstraints, MemAttrs, MemOps, MemResult, Region, RegionRef};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{LockRank, Mutex};
use crate::core::value::{Endian, Width};
use crate::core::wire::{Level, WireSource};
use crate::host::chardev::{CharDevice, ports};
use crate::machine::realize::Instance;
use crate::machine::validate::ClassSchema;
pub const CLASS_NAME: &str = "pc.kbc";
const STATE_VERSION: u32 = 1;
pub const REGISTER_WINDOW_LEN: u64 = 1;
const DEFAULT_PORT: &str = "keyboard";
pub const RAM_LEN: usize = 32;
pub const KEYBOARD_BUFFER: usize = 16;
const ST_OBF: u8 = 0x01;
const ST_SYS: u8 = 0x04;
const ST_A2: u8 = 0x08;
const ST_INH: u8 = 0x10;
const ST_AUX_OBF: u8 = 0x20;
const CB_KBD_INT: u8 = 0x01;
const CB_AUX_INT: u8 = 0x02;
const CB_SYS: u8 = 0x04;
const CB_KBD_CLOCK_OFF: u8 = 0x10;
const CB_AUX_CLOCK_OFF: u8 = 0x20;
const CB_TRANSLATE: u8 = 0x40;
const OP_RESET: u8 = 0x01;
const OP_A20: u8 = 0x02;
const OP_KBD_OBF: u8 = 0x10;
const OP_AUX_OBF: u8 = 0x20;
const OUTPUT_PORT_RESET: u8 = OP_RESET;
const INPUT_PORT: u8 = 0b1011_0000;
const TEST_INPUTS: u8 = 0b0000_0011;
const SELF_TEST_OK: u8 = 0x55;
const KB_ACK: u8 = 0xfa;
const KB_RESEND: u8 = 0xfe;
const KB_BAT_OK: u8 = 0xaa;
const KB_ID_HIGH: u8 = 0xab;
const KB_ID_LOW: u8 = 0x83;
const SET2_BREAK: u8 = 0xf0;
const SET2_EXTEND: u8 = 0xe0;
const DEFAULT_TYPEMATIC: u8 = 0x2b;
const DEFAULT_SCAN_SET: u8 = 2;
pub const TRANSLATE: [u8; 256] = build_translation();
const fn build_translation() -> [u8; 256] {
const HEAD: [u8; 0x87] = [
0xff, 0x43, 0x41, 0x3f, 0x3d, 0x3b, 0x3c, 0x58, 0x64, 0x44, 0x42, 0x40, 0x3e, 0x0f, 0x29,
0x59, 0x65, 0x38, 0x2a, 0x70, 0x1d, 0x10, 0x02, 0x5a, 0x66, 0x71, 0x2c, 0x1f, 0x1e, 0x11, 0x03,
0x5b, 0x67, 0x2e, 0x2d, 0x20, 0x12, 0x05, 0x04, 0x5c, 0x68, 0x39, 0x2f, 0x21, 0x14, 0x13, 0x06,
0x5d, 0x69, 0x31, 0x30, 0x23, 0x22, 0x15, 0x07, 0x5e, 0x6a, 0x72, 0x32, 0x24, 0x16, 0x08, 0x09,
0x5f, 0x6b, 0x33, 0x25, 0x17, 0x18, 0x0b, 0x0a, 0x60, 0x6c, 0x34, 0x35, 0x26, 0x27, 0x19, 0x0c,
0x61, 0x6d, 0x73, 0x28, 0x74, 0x1a, 0x0d, 0x62, 0x6e, 0x3a, 0x36, 0x1c, 0x1b, 0x75, 0x2b, 0x63,
0x76, 0x55, 0x56, 0x77, 0x78, 0x79, 0x7a, 0x0e, 0x7b, 0x7c, 0x4f, 0x7d, 0x4b, 0x47, 0x7e, 0x7f,
0x6f, 0x52, 0x53, 0x50, 0x4c, 0x4d, 0x48, 0x01, 0x45, 0x57, 0x4e, 0x51, 0x4a, 0x37, 0x49, 0x46,
0x00, 0x00, 0x00, 0x00, 0x41, 0x54, 0x5b, 0x5f,
];
let mut table = [0u8; 256];
let mut i = 0;
while i < 256 {
table[i] = i as u8;
i += 1;
}
let mut i = 0;
while i < HEAD.len() {
table[i] = HEAD[i];
i += 1;
}
table
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
enum Poll {
#[default]
None,
Low,
High,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
enum Pending {
#[default]
Keyboard,
RamWrite(u8),
OutputPort,
InjectKeyboard,
InjectAux,
ToAux,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
enum KbPending {
#[default]
Command,
Leds,
Typematic,
ScanSet,
}
#[derive(Debug)]
struct Keyboard {
queue: VecDeque<u8>,
enabled: bool,
scan_set: u8,
leds: u8,
typematic: u8,
pending: KbPending,
last_sent: u8,
}
impl Default for Keyboard {
fn default() -> Keyboard {
Keyboard {
queue: VecDeque::new(),
enabled: true,
scan_set: DEFAULT_SCAN_SET,
leds: 0,
typematic: DEFAULT_TYPEMATIC,
pending: KbPending::Command,
last_sent: KB_ACK,
}
}
}
impl Keyboard {
fn send(&mut self, byte: u8) {
if self.queue.len() < KEYBOARD_BUFFER {
self.queue.push_back(byte);
self.last_sent = byte;
}
}
fn accepting(&self) -> bool {
self.enabled && self.queue.len() < KEYBOARD_BUFFER
}
fn set_defaults(&mut self) {
self.leds = 0;
self.typematic = DEFAULT_TYPEMATIC;
self.pending = KbPending::Command;
}
fn write(&mut self, byte: u8) {
match core::mem::take(&mut self.pending) {
KbPending::Leds => {
self.leds = byte & 0x07;
self.send(KB_ACK);
return;
}
KbPending::Typematic => {
self.typematic = byte;
self.send(KB_ACK);
return;
}
KbPending::ScanSet => {
self.send(KB_ACK);
if byte == 0 {
let set = self.scan_set;
self.send(set);
} else {
self.scan_set = byte;
}
return;
}
KbPending::Command => {}
}
match byte {
0xff => {
self.queue.clear();
*self = Keyboard::default();
self.send(KB_ACK);
self.send(KB_BAT_OK);
}
0xfe => {
let last = self.last_sent;
self.send(last);
}
0xf6 => {
self.set_defaults();
self.send(KB_ACK);
}
0xf5 => {
self.enabled = false;
self.set_defaults();
self.send(KB_ACK);
}
0xf4 => {
self.enabled = true;
self.send(KB_ACK);
}
0xf3 => {
self.pending = KbPending::Typematic;
self.send(KB_ACK);
}
0xf2 => {
self.send(KB_ACK);
self.send(KB_ID_HIGH);
self.send(KB_ID_LOW);
}
0xf0 => {
self.pending = KbPending::ScanSet;
self.send(KB_ACK);
}
0xed => {
self.pending = KbPending::Leds;
self.send(KB_ACK);
}
_ => self.send(KB_RESEND),
}
}
}
#[derive(Debug)]
struct State {
ram: [u8; RAM_LEN],
obuf: u8,
obf: bool,
from_aux: bool,
a2: bool,
poll: Poll,
pending: Pending,
break_pending: bool,
outport: u8,
kbd: Keyboard,
}
impl Default for State {
fn default() -> State {
State {
ram: [0; RAM_LEN],
obuf: 0,
obf: false,
from_aux: false,
a2: false,
poll: Poll::None,
pending: Pending::Keyboard,
break_pending: false,
outport: OUTPUT_PORT_RESET,
kbd: Keyboard::default(),
}
}
}
#[derive(Debug, Default, Clone, Copy)]
struct Outward {
reset: bool,
}
struct Shared {
state: Mutex<State>,
irq1: Mutex<Option<WireSource>>,
irq12: Mutex<Option<WireSource>>,
a20: Mutex<Option<WireSource>>,
reset: Mutex<Option<WireSource>>,
port: Arc<dyn CharDevice>,
port_name: String,
}
impl fmt::Debug for Shared {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Shared");
s.field("port", &self.port_name);
match self.state.try_lock() {
Some(state) => s.field("state", &*state).finish(),
None => s.field("state", &"<in use>").finish(),
}
}
}
impl Shared {
fn drive(holder: &Mutex<Option<WireSource>>, level: Level) {
let out = holder.lock().clone();
if let Some(out) = out {
out.set(level);
}
}
fn refresh(&self) {
let (irq1, irq12, a20) = {
let state = self.state.lock();
let (a, b) = Self::interrupts(&state);
(a, b, state.outport & OP_A20 != 0)
};
Self::drive(&self.irq1, Level::from_bool(irq1));
Self::drive(&self.irq12, Level::from_bool(irq12));
Self::drive(&self.a20, Level::from_bool(a20));
}
fn interrupts(state: &State) -> (bool, bool) {
let kbd = state.obf && !state.from_aux && state.ram[0] & CB_KBD_INT != 0;
let aux = state.obf && state.from_aux && state.ram[0] & CB_AUX_INT != 0;
(kbd, aux)
}
fn pulse_reset(&self) {
let out = self.reset.lock().clone();
if let Some(out) = out {
out.pulse(Level::High);
}
}
fn settle(&self, out: Outward) {
if out.reset {
self.pulse_reset();
}
self.refresh();
}
fn transfer(state: &mut State) {
if state.ram[0] & CB_KBD_CLOCK_OFF != 0 {
return;
}
let translate = state.ram[0] & CB_TRANSLATE != 0;
while !state.obf {
let Some(raw) = state.kbd.queue.pop_front() else {
break;
};
let byte = if !translate {
raw
} else if raw == SET2_BREAK {
state.break_pending = true;
continue;
} else if raw == SET2_EXTEND {
raw
} else {
let mut b = TRANSLATE[raw as usize];
if state.break_pending {
b |= 0x80;
state.break_pending = false;
}
b
};
state.obuf = byte;
state.obf = true;
state.from_aux = false;
}
}
fn reply(state: &mut State, byte: u8) {
state.obuf = byte;
state.obf = true;
state.from_aux = false;
}
fn pump(&self) {
{
let mut state = self.state.lock();
while state.kbd.accepting() {
let Some(byte) = self.port.read_byte() else {
break;
};
state.kbd.send(byte);
}
Self::transfer(&mut state);
}
self.refresh();
}
fn read_data(&self, debug: bool) -> u8 {
let mut state = self.state.lock();
if debug {
return state.obuf;
}
let byte = state.obuf;
state.obf = false;
state.from_aux = false;
Self::transfer(&mut state);
byte
}
fn write_data(&self, value: u8) {
let out = {
let mut state = self.state.lock();
let mut out = Outward::default();
state.a2 = false;
match core::mem::take(&mut state.pending) {
Pending::Keyboard => state.kbd.write(value),
Pending::RamWrite(index) => state.ram[index as usize] = value,
Pending::OutputPort => {
state.outport = value;
if value & OP_RESET == 0 {
out.reset = true;
}
}
Pending::InjectKeyboard => {
Self::reply(&mut state, value);
}
Pending::InjectAux => {
Self::reply(&mut state, value);
state.from_aux = true;
}
Pending::ToAux => {}
}
Self::transfer(&mut state);
out
};
self.settle(out);
}
fn read_status(&self) -> u8 {
let state = self.state.lock();
let mut status = 0;
if state.obf {
status |= ST_OBF;
if state.from_aux {
status |= ST_AUX_OBF;
}
}
if state.ram[0] & CB_SYS != 0 {
status |= ST_SYS;
}
if state.a2 {
status |= ST_A2;
}
if state.ram[0] & CB_KBD_CLOCK_OFF != 0 {
status |= ST_INH;
}
match state.poll {
Poll::None => status,
Poll::Low => (status & 0x0f) | ((INPUT_PORT & 0x0f) << 4),
Poll::High => (status & 0x0f) | (INPUT_PORT & 0xf0),
}
}
fn output_port_readback(state: &State) -> u8 {
let mut value = state.outport & !(OP_KBD_OBF | OP_AUX_OBF);
if state.obf {
value |= if state.from_aux {
OP_AUX_OBF
} else {
OP_KBD_OBF
};
}
value
}
fn write_command(&self, value: u8) {
let out = {
let mut state = self.state.lock();
let mut out = Outward::default();
state.a2 = true;
state.poll = Poll::None;
state.pending = Pending::Keyboard;
match value {
0x20..=0x3f => {
let byte = state.ram[(value & 0x1f) as usize];
Self::reply(&mut state, byte);
}
0x60..=0x7f => state.pending = Pending::RamWrite(value & 0x1f),
0xa7 => state.ram[0] |= CB_AUX_CLOCK_OFF,
0xa8 => state.ram[0] &= !CB_AUX_CLOCK_OFF,
0xa9 => Self::reply(&mut state, 0x00),
0xaa => {
state.ram[0] |= CB_SYS;
Self::reply(&mut state, SELF_TEST_OK);
}
0xab => Self::reply(&mut state, 0x00),
0xad => state.ram[0] |= CB_KBD_CLOCK_OFF,
0xae => state.ram[0] &= !CB_KBD_CLOCK_OFF,
0xc0 => Self::reply(&mut state, INPUT_PORT),
0xc1 => state.poll = Poll::Low,
0xc2 => state.poll = Poll::High,
0xd0 => {
let byte = Self::output_port_readback(&state);
Self::reply(&mut state, byte);
}
0xd1 => state.pending = Pending::OutputPort,
0xd2 => state.pending = Pending::InjectKeyboard,
0xd3 => state.pending = Pending::InjectAux,
0xd4 => state.pending = Pending::ToAux,
0xe0 => Self::reply(&mut state, TEST_INPUTS),
0xf0..=0xff => {
let pulsed = !value & 0x0f;
out.reset = pulsed & OP_RESET != 0;
}
_ => {}
}
Self::transfer(&mut state);
out
};
self.settle(out);
}
}
#[derive(Debug)]
struct DataPort(Arc<Shared>);
#[derive(Debug)]
struct CommandPort(Arc<Shared>);
impl MemOps for DataPort {
fn read(&self, _offset: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
let [byte] = dst else {
return Err(BusError::BadAccess);
};
*byte = self.0.read_data(attrs.debug);
if !attrs.debug {
self.0.refresh();
}
Ok(())
}
fn write(&self, _offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
let [value] = src else {
return Err(BusError::BadAccess);
};
if attrs.debug {
return Err(BusError::BadAccess);
}
self.0.write_data(*value);
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::word(Width::U8, Endian::Little)
}
}
impl MemOps for CommandPort {
fn read(&self, _offset: u64, dst: &mut [u8], _attrs: MemAttrs) -> MemResult {
let [byte] = dst else {
return Err(BusError::BadAccess);
};
*byte = self.0.read_status();
Ok(())
}
fn write(&self, _offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
let [value] = src else {
return Err(BusError::BadAccess);
};
if attrs.debug {
return Err(BusError::BadAccess);
}
self.0.write_command(*value);
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::word(Width::U8, Endian::Little)
}
}
#[derive(Debug)]
pub struct Kbc8042 {
shared: Arc<Shared>,
data: RegionRef,
cmd: RegionRef,
}
impl Kbc8042 {
pub fn new(props: &Props) -> Result<Kbc8042> {
let mut r = props.reader();
let port_name = r.or("port", String::from(DEFAULT_PORT))?;
r.finish()?;
Ok(Kbc8042::with_port(
ports::attach(props, &port_name)?,
port_name,
))
}
#[must_use]
pub fn default_device() -> Kbc8042 {
Kbc8042::with_port(
Arc::new(crate::host::chardev::CharPort::new()),
String::from(DEFAULT_PORT),
)
}
#[must_use]
pub fn with_port(port: Arc<dyn CharDevice>, port_name: String) -> Kbc8042 {
let shared = Arc::new(Shared {
state: Mutex::with_rank(LockRank::DEVICE, State::default()),
irq1: Mutex::with_rank(LockRank::LEAF, None),
irq12: Mutex::with_rank(LockRank::LEAF, None),
a20: Mutex::with_rank(LockRank::LEAF, None),
reset: Mutex::with_rank(LockRank::LEAF, None),
port,
port_name,
});
let data: RegionRef = Arc::new(Region::io(
"pc.kbc.data",
REGISTER_WINDOW_LEN,
Arc::new(DataPort(Arc::clone(&shared))) as Arc<dyn MemOps>,
));
let cmd: RegionRef = Arc::new(Region::io(
"pc.kbc.cmd",
REGISTER_WINDOW_LEN,
Arc::new(CommandPort(Arc::clone(&shared))) as Arc<dyn MemOps>,
));
Kbc8042 { shared, data, cmd }
}
#[must_use]
pub fn port_name(&self) -> &str {
&self.shared.port_name
}
pub fn pump(&self) {
self.shared.pump();
}
#[must_use]
pub fn a20_enabled(&self) -> bool {
self.shared.state.lock().outport & OP_A20 != 0
}
#[must_use]
pub fn irq1_asserted(&self) -> bool {
Shared::interrupts(&self.shared.state.lock()).0
}
#[must_use]
pub fn irq12_asserted(&self) -> bool {
Shared::interrupts(&self.shared.state.lock()).1
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "Intel 8042 keyboard controller, with the A20 gate",
properties: &[PropertySpec {
name: "port",
kind: ValueKind::Str,
required: false,
summary: "the character port scan codes arrive on (default \"keyboard\")",
}],
construct: |props| Ok(Box::new(Kbc8042::new(props)?)),
};
impl Device for Kbc8042 {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, _kind: ResetKind) {
{
let mut state = self.shared.state.lock();
*state = State::default();
}
self.shared.refresh();
}
fn region(&self, name: &str) -> Option<RegionRef> {
match name {
"" | "data" => Some(Arc::clone(&self.data)),
"cmd" => Some(Arc::clone(&self.cmd)),
_ => None,
}
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
let holder = match port {
"irq1" => &self.shared.irq1,
"irq12" => &self.shared.irq12,
"a20" => &self.shared.a20,
"reset" => &self.shared.reset,
_ => {
return Err(Error::Config {
at: port.to_string(),
message: String::from("an 8042 drives `irq1`, `irq12`, `a20` and `reset`"),
});
}
};
*holder.lock() = Some(source);
Ok(())
}
fn announce(&self, port: &str) {
match port {
"irq1" | "irq12" | "a20" => self.shared.refresh(),
_ => {}
}
}
fn is_runnable(&self) -> bool {
true
}
fn run(&self, budget: Budget) -> Consumed {
self.shared.pump();
Consumed::new(budget.ticks)
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let state = self.shared.state.lock();
w.write_all(&state.ram)?;
w.write_u8(state.obuf)?;
w.write_bool(state.obf)?;
w.write_bool(state.from_aux)?;
w.write_bool(state.a2)?;
w.write_u8(match state.poll {
Poll::None => 0,
Poll::Low => 1,
Poll::High => 2,
})?;
let (tag, arg) = match state.pending {
Pending::Keyboard => (0, 0),
Pending::RamWrite(index) => (1, index),
Pending::OutputPort => (2, 0),
Pending::InjectKeyboard => (3, 0),
Pending::InjectAux => (4, 0),
Pending::ToAux => (5, 0),
};
w.write_u8(tag)?;
w.write_u8(arg)?;
w.write_bool(state.break_pending)?;
w.write_u8(state.outport)?;
w.write_seq_len(state.kbd.queue.len() as u64)?;
for byte in &state.kbd.queue {
w.write_u8(*byte)?;
}
w.write_bool(state.kbd.enabled)?;
w.write_u8(state.kbd.scan_set)?;
w.write_u8(state.kbd.leds)?;
w.write_u8(state.kbd.typematic)?;
w.write_u8(match state.kbd.pending {
KbPending::Command => 0,
KbPending::Leds => 1,
KbPending::Typematic => 2,
KbPending::ScanSet => 3,
})?;
w.write_u8(state.kbd.last_sent)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let mut state = State::default();
for byte in &mut state.ram {
*byte = r.read_u8()?;
}
state.obuf = r.read_u8()?;
state.obf = r.read_bool()?;
state.from_aux = r.read_bool()?;
state.a2 = r.read_bool()?;
state.poll = match r.read_u8()? {
0 => Poll::None,
1 => Poll::Low,
2 => Poll::High,
other => return Err(bad(alloc::format!("poll state {other}"))),
};
let tag = r.read_u8()?;
let arg = r.read_u8()?;
state.pending = match tag {
0 => Pending::Keyboard,
1 if (arg as usize) < RAM_LEN => Pending::RamWrite(arg),
1 => return Err(bad(alloc::format!("controller RAM byte {arg}"))),
2 => Pending::OutputPort,
3 => Pending::InjectKeyboard,
4 => Pending::InjectAux,
5 => Pending::ToAux,
other => return Err(bad(alloc::format!("pending-command tag {other}"))),
};
state.break_pending = r.read_bool()?;
state.outport = r.read_u8()?;
let count = r.read_seq_len(1)? as usize;
if count > KEYBOARD_BUFFER {
return Err(bad(alloc::format!(
"{count} byte(s) in a {KEYBOARD_BUFFER}-byte keyboard buffer"
)));
}
state.kbd.queue.clear();
for _ in 0..count {
state.kbd.queue.push_back(r.read_u8()?);
}
state.kbd.enabled = r.read_bool()?;
state.kbd.scan_set = r.read_u8()?;
state.kbd.leds = r.read_u8()?;
state.kbd.typematic = r.read_u8()?;
state.kbd.pending = match r.read_u8()? {
0 => KbPending::Command,
1 => KbPending::Leds,
2 => KbPending::Typematic,
3 => KbPending::ScanSet,
other => return Err(bad(alloc::format!("keyboard command tag {other}"))),
};
state.kbd.last_sent = r.read_u8()?;
*self.shared.state.lock() = state;
self.shared.refresh();
Ok(())
}
}
fn bad(what: String) -> Error {
Error::State(alloc::format!("8042 snapshot has an impossible {what}"))
}
impl Instance for Kbc8042 {}
pub fn register(registry: &mut crate::core::Registry) -> Result<()> {
registry.add(&CLASS)
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS_NAME, |props| Ok(Arc::new(Kbc8042::new(props)?)))
}
#[must_use]
pub fn schema() -> ClassSchema {
use crate::machine::validate::{PortDir, PropSchema};
ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("port", ValueKind::Str))
.region("")
.region("data")
.region("cmd")
.port("irq1", PortDir::Out)
.port("irq12", PortDir::Out)
.port("a20", PortDir::Out)
.port("reset", PortDir::Out)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::state::{MachineShape, Migrations, StateReader, StateWriter};
use crate::core::sync::{AtomicU32, Ordering};
use crate::core::wire::{Wire, WireId, WireIdAllocator, WireSink};
use crate::host::chardev::CharPort;
use alloc::vec::Vec;
fn wired() -> (Kbc8042, Arc<CharPort>) {
let port = Arc::new(CharPort::new());
let kbc = Kbc8042::with_port(
Arc::clone(&port) as Arc<dyn CharDevice>,
String::from("test"),
);
(kbc, port)
}
fn data(k: &Kbc8042) -> u8 {
k.shared.read_data(false)
}
fn poke_data(k: &Kbc8042, value: u8) {
k.shared.write_data(value);
}
fn status(k: &Kbc8042) -> u8 {
k.shared.read_status()
}
fn command(k: &Kbc8042, value: u8) {
k.shared.write_command(value);
}
fn set_command_byte(k: &Kbc8042, value: u8) {
command(k, 0x60);
poke_data(k, value);
}
#[derive(Debug, Default)]
struct Probe {
level: AtomicU32,
edges: AtomicU32,
}
impl WireSink for Probe {
fn set_level(&self, _src: WireId, _line: u32, level: Level) {
self.level
.store(u32::from(level.is_high()), Ordering::Relaxed);
self.edges.fetch_add(1, Ordering::Relaxed);
}
}
impl Probe {
fn high(&self) -> bool {
self.level.load(Ordering::Relaxed) == 1
}
fn edges(&self) -> u32 {
self.edges.load(Ordering::Relaxed)
}
}
fn with_pins() -> (Kbc8042, Arc<CharPort>, Vec<Arc<Probe>>) {
let (kbc, port) = wired();
let ids = WireIdAllocator::new();
let mut probes = Vec::new();
for pin in ["irq1", "irq12", "a20", "reset"] {
let id = ids.alloc();
let probe = Arc::new(Probe::default());
let wire = Wire::builder()
.source(id)
.sink(Arc::clone(&probe) as Arc<dyn WireSink>, 0)
.build_shared();
kbc.connect(pin, WireSource::new(wire, id))
.expect("an 8042 drives all four");
probes.push(probe);
}
(kbc, port, probes)
}
#[test]
fn the_self_test_answers_0x55_and_sets_the_system_flag() {
let (kbc, _port) = wired();
assert_eq!(status(&kbc) & ST_SYS, 0, "not yet");
command(&kbc, 0xaa);
assert_eq!(status(&kbc) & ST_OBF, ST_OBF);
assert_eq!(status(&kbc) & ST_SYS, ST_SYS);
assert_eq!(status(&kbc) & ST_A2, ST_A2, "the last write was 0x64");
assert_eq!(data(&kbc), SELF_TEST_OK);
assert_eq!(status(&kbc) & ST_OBF, 0, "and the read emptied it");
}
#[test]
fn the_command_byte_round_trips_through_controller_ram() {
let (kbc, _port) = wired();
set_command_byte(&kbc, CB_KBD_INT | CB_TRANSLATE);
assert_eq!(status(&kbc) & ST_A2, 0, "the last write was 0x60");
command(&kbc, 0x20);
assert_eq!(data(&kbc), CB_KBD_INT | CB_TRANSLATE);
command(&kbc, 0x60 | 0x17);
poke_data(&kbc, 0x5a);
command(&kbc, 0x20 | 0x17);
assert_eq!(data(&kbc), 0x5a);
}
#[test]
fn a_scan_code_from_the_host_fills_the_buffer_and_raises_irq1() {
let (kbc, port, probes) = with_pins();
set_command_byte(&kbc, CB_KBD_INT);
port.feed(&[0x1c]);
kbc.pump();
assert_eq!(status(&kbc) & ST_OBF, ST_OBF);
assert_eq!(status(&kbc) & ST_AUX_OBF, 0, "it came from the keyboard");
assert!(probes[0].high(), "IRQ1");
assert!(!probes[1].high(), "and not IRQ12");
let mut byte = [0u8; 1];
DataPort(Arc::clone(&kbc.shared))
.read(0, &mut byte, MemAttrs::DEFAULT)
.expect("a byte read is legal");
assert_eq!(byte[0], 0x1c, "untranslated, since bit 6 is clear");
assert_eq!(status(&kbc) & ST_OBF, 0);
assert!(!probes[0].high(), "and the read dropped the interrupt");
}
#[test]
fn a_scan_code_raises_nothing_while_the_interrupt_is_disabled() {
let (kbc, port, probes) = with_pins();
port.feed(&[0x1c]);
kbc.pump();
assert_eq!(status(&kbc) & ST_OBF, ST_OBF, "the byte is still there");
assert!(!probes[0].high(), "but nothing asked for an interrupt");
}
#[test]
fn a_debug_read_of_the_data_port_pops_nothing() {
let (kbc, port, probes) = with_pins();
set_command_byte(&kbc, CB_KBD_INT);
port.feed(&[0x2a]);
kbc.pump();
let ops = DataPort(Arc::clone(&kbc.shared));
let mut byte = [0u8; 1];
ops.read(0, &mut byte, MemAttrs::DEBUG)
.expect("a debug read is legal");
assert_eq!(byte[0], 0x2a);
assert_eq!(status(&kbc) & ST_OBF, ST_OBF, "still full");
assert!(probes[0].high(), "and still interrupting");
assert_eq!(data(&kbc), 0x2a, "the guest gets the same byte");
assert!(ops.write(0, &[0xff], MemAttrs::DEBUG).is_err());
let cmd = CommandPort(Arc::clone(&kbc.shared));
assert!(cmd.write(0, &[0xaa], MemAttrs::DEBUG).is_err());
assert!(cmd.read(0, &mut byte, MemAttrs::DEBUG).is_ok());
}
#[test]
fn writing_the_output_port_drives_the_a20_gate() {
let (kbc, _port, probes) = with_pins();
let a20 = &probes[2];
assert!(!a20.high(), "shut out of reset, as on a real AT");
command(&kbc, 0xd1);
poke_data(&kbc, OP_RESET | OP_A20);
assert!(a20.high());
assert!(kbc.a20_enabled());
command(&kbc, 0xd0);
assert_eq!(data(&kbc) & OP_A20, OP_A20);
command(&kbc, 0xd1);
poke_data(&kbc, OP_RESET);
assert!(!a20.high());
assert!(!kbc.a20_enabled());
}
#[test]
fn command_0xfe_pulses_the_reset_line() {
let (kbc, _port, probes) = with_pins();
let reset = &probes[3];
let before = reset.edges();
assert!(!reset.high());
command(&kbc, 0xfe);
assert!(reset.edges() > before, "the line moved");
assert!(!reset.high(), "and came back: a pulse, not a level");
let before = reset.edges();
command(&kbc, 0xd1);
poke_data(&kbc, 0x00);
assert!(reset.edges() > before);
let before = reset.edges();
command(&kbc, 0xff);
assert_eq!(reset.edges(), before);
}
#[test]
fn the_keyboard_acknowledges_what_it_knows_and_refuses_what_it_does_not() {
let (kbc, _port) = wired();
poke_data(&kbc, 0xf4);
assert_eq!(data(&kbc), KB_ACK, "enable scanning");
poke_data(&kbc, 0x99);
assert_eq!(data(&kbc), KB_RESEND, "and a command it never heard of");
poke_data(&kbc, 0xed);
assert_eq!(data(&kbc), KB_ACK);
poke_data(&kbc, 0x07);
assert_eq!(data(&kbc), KB_ACK);
poke_data(&kbc, 0xf2);
assert_eq!(data(&kbc), KB_ACK);
assert_eq!(data(&kbc), KB_ID_HIGH);
assert_eq!(data(&kbc), KB_ID_LOW);
}
#[test]
fn a_keyboard_reset_acknowledges_and_then_passes_its_self_test() {
let (kbc, _port) = wired();
poke_data(&kbc, 0xff);
assert_eq!(data(&kbc), KB_ACK);
assert_eq!(data(&kbc), KB_BAT_OK);
assert_eq!(status(&kbc) & ST_OBF, 0, "and nothing follows");
}
#[test]
fn translation_happens_only_when_the_command_byte_asks_for_it() {
let (kbc, port) = wired();
port.feed(&[0x1c]);
kbc.pump();
assert_eq!(data(&kbc), 0x1c, "untranslated");
set_command_byte(&kbc, CB_TRANSLATE);
port.feed(&[0x1c]);
kbc.pump();
assert_eq!(data(&kbc), 0x1e, "translated");
}
#[test]
fn a_set_2_break_sequence_becomes_one_set_1_byte() {
let (kbc, port) = wired();
set_command_byte(&kbc, CB_TRANSLATE);
port.feed(&[0xf0, 0x1c]);
kbc.pump();
assert_eq!(data(&kbc), 0x1e | 0x80, "one byte, with bit 7 set");
assert_eq!(status(&kbc) & ST_OBF, 0, "the prefix was consumed");
port.feed(&[0xe0, 0xf0, 0x14]);
kbc.pump();
assert_eq!(data(&kbc), 0xe0);
assert_eq!(data(&kbc), 0x1d | 0x80, "right control, coming up");
set_command_byte(&kbc, 0);
port.feed(&[0xf0, 0x1c]);
kbc.pump();
assert_eq!(data(&kbc), 0xf0);
assert_eq!(data(&kbc), 0x1c);
}
#[test]
fn disabling_the_keyboard_clock_stops_scan_codes_at_the_keyboard() {
let (kbc, port) = wired();
command(&kbc, 0xad);
assert_eq!(status(&kbc) & ST_INH, ST_INH, "and it says so");
port.feed(&[0x1c]);
kbc.pump();
assert_eq!(status(&kbc) & ST_OBF, 0, "nothing reached the buffer");
command(&kbc, 0xae);
kbc.pump();
assert_eq!(status(&kbc) & ST_OBF, ST_OBF);
assert_eq!(data(&kbc), 0x1c);
}
#[test]
fn a_byte_can_be_injected_from_either_side_and_lands_on_its_own_interrupt() {
let (kbc, _port, probes) = with_pins();
set_command_byte(&kbc, CB_KBD_INT | CB_AUX_INT);
command(&kbc, 0xd2);
poke_data(&kbc, 0x5a);
assert!(probes[0].high(), "0xD2 is the keyboard's side");
assert_eq!(status(&kbc) & ST_AUX_OBF, 0);
assert_eq!(data(&kbc), 0x5a);
command(&kbc, 0xd3);
poke_data(&kbc, 0x5b);
assert_eq!(status(&kbc) & ST_AUX_OBF, ST_AUX_OBF, "0xD3 is the mouse's");
assert!(probes[1].high(), "IRQ12");
assert!(!probes[0].high());
assert_eq!(data(&kbc), 0x5b);
}
#[test]
fn the_interface_tests_and_the_input_port_answer_what_firmware_expects() {
let (kbc, _port) = wired();
command(&kbc, 0xab);
assert_eq!(data(&kbc), 0x00, "keyboard interface: no error");
command(&kbc, 0xa9);
assert_eq!(data(&kbc), 0x00, "auxiliary interface: no error");
command(&kbc, 0xc0);
assert_eq!(data(&kbc), INPUT_PORT);
command(&kbc, 0xe0);
assert_eq!(data(&kbc), TEST_INPUTS);
command(&kbc, 0xc2);
assert_eq!(status(&kbc) & 0xf0, INPUT_PORT & 0xf0);
command(&kbc, 0xc1);
assert_eq!(status(&kbc) & 0xf0, (INPUT_PORT & 0x0f) << 4);
command(&kbc, 0xaa);
assert_eq!(status(&kbc) & 0xf0, 0, "and any command ends the poll");
}
#[test]
fn an_access_that_is_not_a_single_byte_is_refused() {
let (kbc, _port) = wired();
let data_ops = DataPort(Arc::clone(&kbc.shared));
let cmd_ops = CommandPort(Arc::clone(&kbc.shared));
assert!(data_ops.read(0, &mut [0u8; 2], MemAttrs::DEFAULT).is_err());
assert!(data_ops.write(0, &[0u8; 4], MemAttrs::DEFAULT).is_err());
assert!(cmd_ops.read(0, &mut [0u8; 2], MemAttrs::DEFAULT).is_err());
assert!(cmd_ops.write(0, &[0u8; 4], MemAttrs::DEFAULT).is_err());
}
#[test]
fn the_two_ports_are_separate_regions_and_the_empty_name_is_the_data_port() {
let (kbc, _port) = wired();
assert!(kbc.region("").is_some());
assert!(kbc.region("data").is_some());
assert!(kbc.region("cmd").is_some());
assert!(kbc.region("regs").is_none(), "there is no combined block");
assert_eq!(kbc.region("").unwrap().len(), REGISTER_WINDOW_LEN);
let same = Arc::ptr_eq(&kbc.region("").unwrap(), &kbc.region("data").unwrap());
assert!(same);
}
#[test]
fn a_pin_this_chip_does_not_drive_is_a_configuration_error() {
let (kbc, _port) = wired();
let ids = WireIdAllocator::new();
let id = ids.alloc();
let wire = Wire::builder().source(id).build_shared();
assert!(kbc.connect("irq", WireSource::new(wire, id)).is_err());
}
#[test]
fn a_snapshot_round_trips_every_bit_of_architectural_state() {
let (saved, port) = wired();
set_command_byte(&saved, CB_KBD_INT | CB_TRANSLATE);
command(&saved, 0x60 | 0x05);
poke_data(&saved, 0xc3);
command(&saved, 0xd1);
poke_data(&saved, OP_RESET | OP_A20);
poke_data(&saved, 0xf3);
poke_data(&saved, 0x20);
poke_data(&saved, 0xed);
command(&saved, 0xad);
port.feed(&[0x1c, 0xf0]);
saved.pump();
command(&saved, 0xd4);
let image = |dev: &Kbc8042| {
let mut shape = MachineShape::new();
shape.add_device("kbc", CLASS.name).unwrap();
let mut w = StateWriter::new(shape);
{
let mut chunk = w.chunk("kbc", CLASS.name, CLASS.version).unwrap();
dev.save(&mut chunk).unwrap();
}
w.to_vec().unwrap()
};
let first = image(&saved);
let (restored, _other) = wired();
let reader = StateReader::new(&first).unwrap();
let chunk = reader
.load("kbc", CLASS.name, CLASS.version, &Migrations::new())
.unwrap();
restored.load(&mut chunk.reader()).unwrap();
assert_eq!(image(&restored), first, "the two images are identical");
assert!(restored.a20_enabled(), "and A20 came back open");
assert!(
restored.irq1_asserted(),
"and the interrupt came back with it"
);
command(&restored, 0xae);
restored.pump();
for _ in 0..3 {
assert_eq!(data(&restored), KB_ACK, "an acknowledge the keyboard owed");
}
assert_eq!(data(&restored), 0x1e);
assert_eq!(
status(&restored) & ST_OBF,
0,
"and the trailing break prefix is still waiting for its code"
);
}
#[test]
fn properties_are_checked_rather_than_ignored() {
let kbc = Kbc8042::new(&Props::new()).expect("no properties is legal");
assert_eq!(kbc.port_name(), DEFAULT_PORT);
let named = Kbc8042::new(&Props::new().with("port", "test.kbc.props"))
.expect("a port name is legal");
assert_eq!(named.port_name(), "test.kbc.props");
assert!(Kbc8042::new(&Props::new().with("prot", "x")).is_err());
}
#[test]
fn the_translation_table_is_the_documented_one() {
for (set2, set1) in [
(0x76u8, 0x01u8), (0x16, 0x02), (0x1c, 0x1e), (0x5a, 0x1c), (0x29, 0x39), (0x66, 0x0e), (0x12, 0x2a), (0x14, 0x1d), (0x11, 0x38), (0x58, 0x3a), (0x05, 0x3b), (0x83, 0x41), ] {
assert_eq!(TRANSLATE[set2 as usize], set1, "set 2 {set2:#04x}");
}
for byte in [KB_ACK, KB_BAT_OK, KB_RESEND, KB_ID_HIGH, SET2_EXTEND] {
assert_eq!(TRANSLATE[byte as usize], byte);
}
}
}