use alloc::boxed::Box;
use alloc::format;
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::{AccessKind, LazyHandle};
use crate::core::space::{AccessConstraints, MemAttrs, MemOps, MemResult, Region, RegionRef};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{AtomicU64, LockRank, Mutex, Ordering};
use crate::core::value::{Endian, Width};
use crate::core::wire::{Level, WireSource};
use crate::machine::realize::Instance;
use crate::machine::validate::ClassSchema;
pub const CLASS_NAME: &str = "pc.hpet";
const STATE_VERSION: u32 = 1;
pub const REGISTER_WINDOW_LEN: u64 = 0x400;
pub const DEFAULT_BASE: u64 = 0xfed0_0000;
pub const TIMERS: usize = 3;
const REV_ID: u64 = 0x01;
pub const MAX_PERIOD_FS: u64 = 0x05F5_E100;
pub const DEFAULT_PERIOD_FS: u64 = 100_000_000;
const REG_CAP: u64 = 0x000;
const REG_CONF: u64 = 0x010;
const REG_STATUS: u64 = 0x020;
const REG_COUNTER: u64 = 0x0f0;
const REG_TIMER_BASE: u64 = 0x100;
const REG_TIMER_STRIDE: u64 = 0x20;
const CONF_ENABLE: u64 = 1 << 0;
const CONF_LEGACY: u64 = 1 << 1;
const TIMER_LEVEL: u64 = 1 << 1;
const TIMER_ENABLE: u64 = 1 << 2;
const TIMER_PERIODIC: u64 = 1 << 3;
const TIMER_PERIODIC_CAP: u64 = 1 << 4;
const TIMER_SIZE_CAP: u64 = 1 << 5;
const TIMER_VAL_SET: u64 = 1 << 6;
const TIMER_32BIT: u64 = 1 << 8;
const TIMER_WRITABLE: u64 =
TIMER_LEVEL | TIMER_ENABLE | TIMER_PERIODIC | TIMER_VAL_SET | TIMER_32BIT | (0x1f << 9);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
struct Timer {
conf: u64,
comp: u64,
period: u64,
route: u32,
output: bool,
}
impl Timer {
fn narrow(&self) -> bool {
self.conf & TIMER_32BIT != 0
}
fn now(&self, counter: u64) -> u64 {
if self.narrow() {
counter & 0xffff_ffff
} else {
counter
}
}
fn until(&self, counter: u64) -> u64 {
let now = self.now(counter);
let comp = self.now(self.comp);
if self.narrow() {
let delta = (comp as u32).wrapping_sub(now as u32);
if delta == 0 {
1 << 32
} else {
u64::from(delta)
}
} else {
let delta = comp.wrapping_sub(now);
if delta == 0 { u64::MAX } else { delta }
}
}
fn reload(&mut self, counter: u64) {
if self.period == 0 {
return;
}
let now = self.now(counter);
let comp = self.now(self.comp);
let period = self.now(self.period).max(1);
let behind = if self.narrow() {
u64::from((now as u32).wrapping_sub(comp as u32))
} else {
now.wrapping_sub(comp)
};
let steps = behind / period + 1;
let advance = steps.wrapping_mul(period);
self.comp = if self.narrow() {
(self.comp & !0xffff_ffff) | (comp.wrapping_add(advance) & 0xffff_ffff)
} else {
self.comp.wrapping_add(advance)
};
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
struct State {
conf: u64,
status: u64,
counter: u64,
timers: [Timer; TIMERS],
tick: u64,
}
impl State {
fn running(&self) -> bool {
self.conf & CONF_ENABLE != 0
}
fn next_event(&self) -> Option<u64> {
if !self.running() {
return None;
}
self.timers
.iter()
.filter(|t| t.conf & TIMER_ENABLE != 0)
.map(|t| t.until(self.counter))
.min()
}
fn step(&mut self, span: u64) -> [bool; TIMERS] {
let mut fired = [false; TIMERS];
if !self.running() || span == 0 {
return fired;
}
let counter = self.counter;
let after = counter.wrapping_add(span);
for (index, timer) in self.timers.iter_mut().enumerate() {
if timer.conf & TIMER_ENABLE == 0 {
continue;
}
if timer.until(counter) > span {
continue;
}
fired[index] = true;
if timer.conf & TIMER_PERIODIC != 0 {
timer.reload(after);
}
}
self.counter = after;
fired
}
}
struct Registers {
state: Mutex<State>,
outs: Mutex<[Option<WireSource>; TIMERS]>,
lazy: Mutex<Option<LazyHandle>>,
period_fs: u64,
vendor: u16,
tick: AtomicU64,
next_event: AtomicU64,
}
impl fmt::Debug for Registers {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Registers");
s.field("period_fs", &self.period_fs);
match self.state.try_lock() {
Some(state) => s.field("state", &*state).finish(),
None => s.field("state", &"<in use>").finish(),
}
}
}
impl Registers {
fn publish(&self, state: &State) {
self.tick.store(state.tick, Ordering::Relaxed);
let at = match state.next_event() {
Some(d) => state.tick.saturating_add(d),
None => u64::MAX,
};
self.next_event.store(at, Ordering::Relaxed);
}
fn drive(&self, levels: [bool; TIMERS], pulse: [bool; TIMERS]) {
let sources = self.outs.lock().clone();
for ((source, level), pulse) in sources.iter().zip(levels).zip(pulse) {
let Some(source) = source else { continue };
if pulse {
source.set(Level::High);
source.set(Level::Low);
} else {
source.set(Level::from_bool(level));
}
}
}
fn capabilities(&self) -> u64 {
REV_ID
| ((TIMERS as u64 - 1) << 8)
| (1 << 13)
| (1 << 15)
| (u64::from(self.vendor) << 16)
| (self.period_fs << 32)
}
fn timer_conf(&self, timer: &Timer) -> u64 {
timer.conf
| TIMER_PERIODIC_CAP
| TIMER_SIZE_CAP
| (u64::from(1u32 << (timer.route & 31)) << 32)
}
fn read_register(&self, state: &State, offset: u64) -> u64 {
match offset {
REG_CAP => self.capabilities(),
REG_CONF => state.conf,
REG_STATUS => state.status,
REG_COUNTER => state.counter,
_ if offset >= REG_TIMER_BASE => {
let index = ((offset - REG_TIMER_BASE) / REG_TIMER_STRIDE) as usize;
let within = (offset - REG_TIMER_BASE) % REG_TIMER_STRIDE;
let Some(timer) = state.timers.get(index) else {
return 0;
};
match within {
0x00 => self.timer_conf(timer),
0x08 => timer.comp,
_ => 0,
}
}
_ => 0,
}
}
fn write_register(&self, state: &mut State, offset: u64, value: u64) {
match offset {
REG_CONF => state.conf = value & (CONF_ENABLE | CONF_LEGACY),
REG_STATUS => {
state.status &= !value;
for (index, timer) in state.timers.iter_mut().enumerate() {
if value & (1 << index) != 0 {
timer.output = false;
}
}
}
REG_COUNTER => state.counter = value,
_ if offset >= REG_TIMER_BASE => {
let index = ((offset - REG_TIMER_BASE) / REG_TIMER_STRIDE) as usize;
let within = (offset - REG_TIMER_BASE) % REG_TIMER_STRIDE;
let counter = state.counter;
let Some(timer) = state.timers.get_mut(index) else {
return;
};
match within {
0x00 => timer.conf = value & TIMER_WRITABLE,
0x08 => {
if timer.conf & TIMER_PERIODIC == 0 {
timer.comp = value;
timer.period = value;
} else {
timer.period = value;
if timer.conf & TIMER_VAL_SET != 0 {
timer.comp = value;
timer.conf &= !TIMER_VAL_SET;
}
}
let _ = counter;
}
_ => {}
}
}
_ => {}
}
}
fn sync(&self, attrs: MemAttrs) {
let handle = self.lazy.lock().clone();
let Some(handle) = handle else {
return;
};
let kind = if attrs.debug {
AccessKind::Debug
} else {
AccessKind::Guest
};
let _ = handle.sync(kind);
}
fn advance_to(&self, target: u64) {
let (levels, pulse) = {
let mut state = self.state.lock();
if target <= state.tick {
return;
}
let span = target - state.tick;
state.tick = target;
let fired = state.step(span);
let mut pulse = [false; TIMERS];
for (index, fired) in fired.into_iter().enumerate() {
if !fired {
continue;
}
let level = state.timers[index].conf & TIMER_LEVEL != 0;
if level {
state.status |= 1 << index;
state.timers[index].output = true;
} else {
pulse[index] = true;
}
}
self.publish(&state);
(state.timers.map(|t| t.output), pulse)
};
self.drive(levels, pulse);
}
}
impl MemOps for Registers {
fn read(&self, offset: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
if !attrs.debug {
self.sync(attrs);
}
let state = self.state.lock();
let aligned = offset & !7;
let value = self.read_register(&state, aligned);
match dst.len() {
4 => {
let half = if offset & 4 == 0 {
value as u32
} else {
(value >> 32) as u32
};
dst.copy_from_slice(&half.to_le_bytes());
Ok(())
}
8 => {
dst.copy_from_slice(&value.to_le_bytes());
Ok(())
}
_ => Err(BusError::BadAccess),
}
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
if attrs.debug {
return Err(BusError::BadAccess);
}
self.sync(attrs);
let aligned = offset & !7;
let (levels, drive) = {
let mut state = self.state.lock();
let old = self.read_register(&state, aligned);
let value = match src.len() {
4 => {
let half = u32::from_le_bytes([src[0], src[1], src[2], src[3]]);
if offset & 4 == 0 {
(old & 0xffff_ffff_0000_0000) | u64::from(half)
} else {
(old & 0xffff_ffff) | (u64::from(half) << 32)
}
}
8 => u64::from_le_bytes([
src[0], src[1], src[2], src[3], src[4], src[5], src[6], src[7],
]),
_ => return Err(BusError::BadAccess),
};
self.write_register(&mut state, aligned, value);
self.publish(&state);
(state.timers.map(|t| t.output), aligned == REG_STATUS)
};
if drive {
self.drive(levels, [false; TIMERS]);
}
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::word(Width::U64, Endian::Little).with_widths(Width::U32, Width::U64)
}
}
#[derive(Debug)]
pub struct Hpet {
regs: Arc<Registers>,
region: RegionRef,
routes: [u32; TIMERS],
}
impl Hpet {
pub fn new(props: &Props) -> Result<Hpet> {
let mut r = props.reader();
let period_fs = r.or_range::<u64>("period", DEFAULT_PERIOD_FS, 1..=MAX_PERIOD_FS)?;
let vendor = u16::try_from(r.or_range::<u64>("vendor", 0x8086, 0..=0xffff)?).unwrap_or(0);
let mut routes = [0u32; TIMERS];
for (index, route) in routes.iter_mut().enumerate() {
let default = match index {
0 => 2,
1 => 8,
_ => 0,
};
*route = r.or_range::<u64>(&format!("route{index}"), default, 0..=31)? as u32;
}
r.finish()?;
Ok(Hpet::with_config(period_fs, vendor, routes))
}
#[must_use]
pub fn default_device() -> Hpet {
Hpet::with_config(DEFAULT_PERIOD_FS, 0x8086, [2, 8, 0])
}
#[must_use]
pub fn with_config(period_fs: u64, vendor: u16, routes: [u32; TIMERS]) -> Hpet {
let mut state = State::default();
for (timer, route) in state.timers.iter_mut().zip(routes) {
timer.route = route;
}
let regs = Arc::new(Registers {
state: Mutex::with_rank(LockRank::DEVICE, state),
outs: Mutex::with_rank(LockRank::LEAF, [const { None }; TIMERS]),
lazy: Mutex::with_rank(LockRank::LEAF, None),
period_fs,
vendor,
tick: AtomicU64::new(0),
next_event: AtomicU64::new(u64::MAX),
});
let region: RegionRef = Arc::new(Region::io(
CLASS_NAME,
REGISTER_WINDOW_LEN,
Arc::clone(®s) as Arc<dyn MemOps>,
));
Hpet {
regs,
region,
routes,
}
}
#[must_use]
pub fn period_fs(&self) -> u64 {
self.regs.period_fs
}
#[must_use]
pub fn counter(&self) -> u64 {
self.regs.state.lock().counter
}
pub fn advance_to(&self, tick: u64) {
self.regs.advance_to(tick);
}
#[must_use]
pub fn tick(&self) -> u64 {
self.regs.tick.load(Ordering::Relaxed)
}
fn out_pin(port: &str) -> Option<usize> {
let index: usize = port.strip_prefix('t')?.parse().ok()?;
(index < TIMERS).then_some(index)
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "IA-PC high precision event timer",
properties: &[
PropertySpec {
name: "period",
kind: ValueKind::Uint,
required: false,
summary: "the main counter's period in femtoseconds, at most 10^8 (default 10^8)",
},
PropertySpec {
name: "vendor",
kind: ValueKind::Uint,
required: false,
summary: "the vendor identification the capability register reports (default 0x8086)",
},
PropertySpec {
name: "route0",
kind: ValueKind::Uint,
required: false,
summary: "which interrupt input timer 0's pin is wired to, 0-31 (default 2)",
},
PropertySpec {
name: "route1",
kind: ValueKind::Uint,
required: false,
summary: "which interrupt input timer 1's pin is wired to, 0-31 (default 8)",
},
PropertySpec {
name: "route2",
kind: ValueKind::Uint,
required: false,
summary: "which interrupt input timer 2's pin is wired to, 0-31 (default 0)",
},
],
construct: |props| Ok(Box::new(Hpet::new(props)?)),
};
impl Device for Hpet {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, _kind: ResetKind) {
let levels = {
let mut state = self.regs.state.lock();
*state = State::default();
for (timer, route) in state.timers.iter_mut().zip(self.routes) {
timer.route = route;
}
self.regs.publish(&state);
[false; TIMERS]
};
self.regs.drive(levels, [false; TIMERS]);
}
fn region(&self, name: &str) -> Option<RegionRef> {
matches!(name, "" | "regs").then(|| Arc::clone(&self.region))
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
let index = Hpet::out_pin(port).ok_or_else(|| Error::Config {
at: port.to_string(),
message: String::from("an HPET drives one pin per timer, `t0` to `t2`"),
})?;
self.regs.outs.lock()[index] = Some(source);
Ok(())
}
fn announce(&self, port: &str) {
let Some(index) = Hpet::out_pin(port) else {
return;
};
let level = self.regs.state.lock().timers[index].output;
let source = self.regs.outs.lock()[index].clone();
if let Some(source) = source {
source.set(Level::from_bool(level));
}
}
fn is_lazy(&self) -> bool {
true
}
fn current_tick(&self) -> u64 {
self.regs.tick.load(Ordering::Relaxed)
}
fn advance_to(&self, tick: u64) {
self.regs.advance_to(tick);
}
fn next_event_tick(&self) -> Option<u64> {
match self.regs.next_event.load(Ordering::Relaxed) {
u64::MAX => None,
at => Some(at),
}
}
fn attach_lazy(&self, handle: LazyHandle) {
*self.regs.lazy.lock() = Some(handle);
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let state = self.regs.state.lock();
w.write_u64(state.conf)?;
w.write_u64(state.status)?;
w.write_u64(state.counter)?;
w.write_seq_len(TIMERS as u64)?;
for timer in &state.timers {
w.write_u64(timer.conf)?;
w.write_u64(timer.comp)?;
w.write_u64(timer.period)?;
w.write_bool(timer.output)?;
}
w.write_u64(state.tick)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let mut state = State {
conf: r.read_u64()?,
status: r.read_u64()?,
counter: r.read_u64()?,
..State::default()
};
let count = r.read_seq_len(25)? as usize;
if count != TIMERS {
return Err(Error::State(format!(
"snapshot has {count} HPET comparators, this part has {TIMERS}"
)));
}
for (timer, route) in state.timers.iter_mut().zip(self.routes) {
timer.conf = r.read_u64()?;
timer.comp = r.read_u64()?;
timer.period = r.read_u64()?;
timer.output = r.read_bool()?;
timer.route = route;
}
state.tick = r.read_u64()?;
let levels = {
let mut current = self.regs.state.lock();
*current = state;
self.regs.publish(¤t);
current.timers.map(|t| t.output)
};
self.regs.drive(levels, [false; TIMERS]);
Ok(())
}
}
impl Instance for Hpet {}
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(Hpet::new(props)?)))
}
#[must_use]
pub fn schema() -> ClassSchema {
use crate::machine::validate::{PortDir, PropSchema};
let mut schema = ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("period", ValueKind::Uint).range(1, MAX_PERIOD_FS))
.prop(PropSchema::new("vendor", ValueKind::Uint).range(0, 0xffff))
.region("")
.region("regs");
for index in 0..TIMERS {
schema = schema
.prop(PropSchema::new(format!("route{index}"), ValueKind::Uint).range(0, 31))
.port(format!("t{index}"), PortDir::Out);
}
schema
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::state::{MachineShape, Migrations, StateReader, StateWriter};
use crate::core::sync::{AtomicU32, Ordering as AtomicOrdering};
use crate::core::wire::{Wire, WireId, WireIdAllocator, WireSink};
struct Bench {
hpet: Hpet,
probes: [Arc<Probe>; TIMERS],
}
#[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()), AtomicOrdering::Relaxed);
if level.is_high() {
self.edges.fetch_add(1, AtomicOrdering::Relaxed);
}
}
}
impl Probe {
fn high(&self) -> bool {
self.level.load(AtomicOrdering::Relaxed) != 0
}
fn edges(&self) -> u32 {
self.edges.load(AtomicOrdering::Relaxed)
}
}
fn bench() -> Bench {
let hpet = Hpet::default_device();
let ids = WireIdAllocator::new();
let probes: [Arc<Probe>; TIMERS] = core::array::from_fn(|_| Arc::new(Probe::default()));
for (index, probe) in probes.iter().enumerate() {
let src = ids.alloc();
let wire = Wire::builder()
.source(src)
.sink(Arc::clone(probe) as Arc<dyn WireSink>, 0)
.build_shared();
hpet.connect(&format!("t{index}"), WireSource::new(wire, src))
.expect("every timer has an output pin");
}
Bench { hpet, probes }
}
impl Bench {
fn poke(&self, offset: u64, value: u64) {
self.hpet
.regs
.write(offset, &value.to_le_bytes(), MemAttrs::DEFAULT)
.expect("an aligned 64-bit write is legal");
}
fn peek(&self, offset: u64) -> u64 {
self.peek_with(offset, MemAttrs::DEFAULT)
}
fn peek_with(&self, offset: u64, attrs: MemAttrs) -> u64 {
let mut bytes = [0u8; 8];
self.hpet
.regs
.read(offset, &mut bytes, attrs)
.expect("an aligned 64-bit read is legal");
u64::from_le_bytes(bytes)
}
fn timer(&self, index: u64) -> u64 {
REG_TIMER_BASE + index * REG_TIMER_STRIDE
}
fn enable(&self) {
self.poke(REG_CONF, CONF_ENABLE);
}
}
#[test]
fn the_capability_register_describes_the_part() {
let b = bench();
let cap = b.peek(REG_CAP);
assert_eq!(cap & 0xff, REV_ID);
assert_eq!((cap >> 8) & 0x1f, TIMERS as u64 - 1, "three timers");
assert_ne!(cap & (1 << 13), 0, "a 64-bit main counter");
assert_ne!(cap & (1 << 15), 0, "and the legacy replacement route");
assert_eq!((cap >> 16) & 0xffff, 0x8086);
assert_eq!(
cap >> 32,
DEFAULT_PERIOD_FS,
"100 ns in femtoseconds, exactly as declared and never derived"
);
const { assert!(DEFAULT_PERIOD_FS <= MAX_PERIOD_FS) };
}
#[test]
fn the_counter_stands_still_until_it_is_enabled() {
let b = bench();
b.hpet.advance_to(1_000);
assert_eq!(b.peek(REG_COUNTER), 0, "halted means halted");
assert_eq!(Device::next_event_tick(&b.hpet), None);
b.enable();
b.hpet.advance_to(1_500);
assert_eq!(
b.peek(REG_COUNTER),
500,
"and it counts from where the enable happened, not from the epoch"
);
}
#[test]
fn a_one_shot_comparator_fires_on_the_tick_the_scheduler_was_told_about() {
let b = bench();
b.enable();
b.poke(b.timer(0), TIMER_ENABLE | TIMER_LEVEL);
b.poke(b.timer(0) + 8, 100);
assert_eq!(Device::next_event_tick(&b.hpet), Some(100));
b.hpet.advance_to(99);
assert!(!b.probes[0].high());
assert_eq!(b.peek(REG_STATUS), 0);
b.hpet.advance_to(100);
assert!(b.probes[0].high(), "the line went up on the match");
assert_eq!(b.peek(REG_STATUS) & 1, 1, "and the status bit with it");
b.poke(REG_STATUS, 1);
assert!(!b.probes[0].high());
assert_eq!(b.peek(REG_STATUS), 0);
}
#[test]
fn the_counters_position_is_a_function_of_its_tick_and_nothing_else() {
let b = bench();
b.enable();
b.hpet.advance_to(42);
for _ in 0..1_000 {
assert_eq!(b.peek(REG_COUNTER), 42);
}
}
#[test]
fn a_periodic_timer_takes_its_accumulator_then_its_period() {
let b = bench();
b.enable();
b.poke(
b.timer(1),
TIMER_ENABLE | TIMER_LEVEL | TIMER_PERIODIC | TIMER_VAL_SET,
);
b.poke(b.timer(1) + 8, 100);
assert_eq!(
b.peek(b.timer(1)) & TIMER_VAL_SET,
0,
"the write clears the value-set bit"
);
b.poke(b.timer(1) + 8, 50);
assert_eq!(
b.peek(b.timer(1) + 8),
100,
"the comparator stands where it was"
);
assert_eq!(Device::next_event_tick(&b.hpet), Some(100));
b.hpet.advance_to(100);
assert!(b.probes[1].high());
assert_eq!(
b.peek(b.timer(1) + 8),
150,
"and hardware added the last value written"
);
assert_eq!(Device::next_event_tick(&b.hpet), Some(150));
b.poke(REG_STATUS, 1 << 1);
b.hpet.advance_to(487);
assert_eq!(b.peek(b.timer(1) + 8), 500);
}
#[test]
fn an_edge_triggered_timer_pulses_and_sets_no_status_bit() {
let b = bench();
b.enable();
b.poke(b.timer(2), TIMER_ENABLE);
b.poke(b.timer(2) + 8, 10);
b.hpet.advance_to(10);
assert_eq!(b.probes[2].edges(), 1, "one edge");
assert!(!b.probes[2].high(), "and the line came back down");
assert_eq!(
b.peek(REG_STATUS),
0,
"the status bit is a level-triggered timer's, not an edge one's"
);
}
#[test]
fn a_thirty_two_bit_timer_compares_only_the_low_half() {
let b = bench();
b.enable();
b.poke(b.timer(0), TIMER_ENABLE | TIMER_LEVEL | TIMER_32BIT);
b.poke(b.timer(0) + 8, 0xdead_beef_0000_0020);
assert_eq!(Device::next_event_tick(&b.hpet), Some(0x20));
b.hpet.advance_to(0x20);
assert!(b.probes[0].high());
}
#[test]
fn a_thirty_two_bit_access_reaches_one_half_of_a_sixty_four_bit_register() {
let b = bench();
b.enable();
b.hpet.advance_to(0x1_0000_0005);
let mut low = [0u8; 4];
let mut high = [0u8; 4];
b.hpet
.regs
.read(REG_COUNTER, &mut low, MemAttrs::DEFAULT)
.unwrap();
b.hpet
.regs
.read(REG_COUNTER + 4, &mut high, MemAttrs::DEFAULT)
.unwrap();
assert_eq!(u32::from_le_bytes(low), 5);
assert_eq!(u32::from_le_bytes(high), 1);
b.poke(b.timer(0) + 8, 0);
b.hpet
.regs
.write(b.timer(0) + 12, &7u32.to_le_bytes(), MemAttrs::DEFAULT)
.unwrap();
assert_eq!(b.peek(b.timer(0) + 8), 7 << 32);
}
#[test]
fn a_debug_read_moves_nothing_and_a_debug_write_is_refused() {
let b = bench();
b.enable();
b.poke(b.timer(0), TIMER_ENABLE | TIMER_LEVEL);
b.poke(b.timer(0) + 8, 10);
assert_eq!(b.peek_with(REG_COUNTER, MemAttrs::DEBUG), 0);
assert_eq!(b.hpet.tick(), 0);
assert!(
b.hpet
.regs
.write(REG_STATUS, &1u64.to_le_bytes(), MemAttrs::DEBUG)
.is_err(),
"and there is no harmless write on this part"
);
}
#[test]
fn a_snapshot_round_trips_the_whole_part() {
let saved = bench();
saved.enable();
saved.poke(
saved.timer(0),
TIMER_ENABLE | TIMER_LEVEL | TIMER_PERIODIC | TIMER_VAL_SET,
);
saved.poke(saved.timer(0) + 8, 64);
saved.poke(saved.timer(0) + 8, 64);
saved.poke(saved.timer(2), TIMER_ENABLE);
saved.poke(saved.timer(2) + 8, 1_000);
saved.hpet.advance_to(70);
let mut shape = MachineShape::new();
shape.add_device("hpet", CLASS.name).unwrap();
let mut w = StateWriter::new(shape);
{
let mut chunk = w.chunk("hpet", CLASS.name, CLASS.version).unwrap();
saved.hpet.save(&mut chunk).unwrap();
}
let bytes = w.to_vec().unwrap();
let restored = bench();
let reader = StateReader::new(&bytes).unwrap();
let chunk = reader
.load("hpet", CLASS.name, CLASS.version, &Migrations::new())
.unwrap();
restored.hpet.load(&mut chunk.reader()).unwrap();
let after = restored.hpet.regs.state.lock().clone();
let before = saved.hpet.regs.state.lock().clone();
assert_eq!(after, before, "every field came back");
assert_eq!(restored.hpet.tick(), 70, "the position in its domain too");
assert_eq!(
Device::next_event_tick(&restored.hpet),
Device::next_event_tick(&saved.hpet)
);
assert!(
restored.probes[0].high(),
"and a level-triggered timer that was asserting still is"
);
let mut shape = MachineShape::new();
shape.add_device("hpet", CLASS.name).unwrap();
let mut w = StateWriter::new(shape);
{
let mut chunk = w.chunk("hpet", CLASS.name, CLASS.version).unwrap();
restored.hpet.save(&mut chunk).unwrap();
}
assert_eq!(w.to_vec().unwrap(), bytes);
}
#[test]
fn a_reset_halts_the_counter_and_drops_every_line() {
let b = bench();
b.enable();
b.poke(b.timer(0), TIMER_ENABLE | TIMER_LEVEL);
b.poke(b.timer(0) + 8, 5);
b.hpet.advance_to(5);
assert!(b.probes[0].high());
b.hpet.reset(crate::core::device::ResetKind::Cold);
assert!(!b.probes[0].high());
assert_eq!(b.peek(REG_COUNTER), 0);
assert_eq!(Device::next_event_tick(&b.hpet), None);
}
#[test]
fn a_period_longer_than_a_hundred_nanoseconds_is_refused() {
let props = Props::new().with("period", MAX_PERIOD_FS + 1);
assert!(Hpet::new(&props).is_err());
let props = Props::new().with("period", MAX_PERIOD_FS);
assert!(Hpet::new(&props).is_ok());
}
}