pub(crate) const TIMER_VECTOR: u32 = 0x20;
pub(crate) const MIN_TIMER_PERIOD_US: u64 = 100;
pub(crate) const MAX_TIMER_PERIOD_US: u64 = 10_000_000;
pub(crate) fn handle_io_in(port: u16) -> Option<u64> {
match port {
0x21 | 0xA1 => Some(0xFF),
0x20 | 0xA0 => Some(0),
0x40 => Some(0),
_ => None,
}
}
pub(crate) fn handle_common_io_out(
port: u16,
data: &[u8],
timer_active: bool,
eoi_callback: impl FnOnce(),
) -> bool {
if port == 0x20 || port == 0x21 || port == 0xA0 || port == 0xA1 {
if port == 0x20 && !data.is_empty() && (data[0] & 0xE0) == 0x20 && timer_active {
eoi_callback();
}
return true;
}
if port == 0x43 || port == 0x40 {
return true;
}
if port == 0x61 {
return true;
}
if port == 0x80 {
return true;
}
false
}
pub(crate) fn write_lapic_u32(state: &mut [u8], offset: usize, val: u32) {
state[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
}
pub(crate) fn read_lapic_u32(state: &[u8], offset: usize) -> u32 {
u32::from_le_bytes([
state[offset],
state[offset + 1],
state[offset + 2],
state[offset + 3],
])
}
pub(crate) fn init_lapic_registers(state: &mut [u8]) {
write_lapic_u32(state, 0xF0, 0x1FF);
write_lapic_u32(state, 0x80, 0);
write_lapic_u32(state, 0xE0, 0xFFFF_FFFF);
write_lapic_u32(state, 0xD0, 1 << 24);
write_lapic_u32(state, 0x350, 0x0001_0000);
write_lapic_u32(state, 0x360, 0x400);
write_lapic_u32(state, 0x320, 0x0001_0000);
write_lapic_u32(state, 0x370, 0x0001_0000);
}
pub(crate) fn lapic_eoi(state: &mut [u8]) {
for i in (0u32..8).rev() {
let offset = 0x100 + (i as usize) * 0x10;
let isr_val = read_lapic_u32(state, offset);
if isr_val != 0 {
let bit = 31 - isr_val.leading_zeros();
write_lapic_u32(state, offset, isr_val & !(1u32 << bit));
break;
}
}
}
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::JoinHandle;
use std::time::Duration;
pub(crate) fn handle_pv_timer_config(
timer: &mut Option<TimerThread>,
data: &[u8],
inject_fn: impl Fn() + Send + 'static,
) -> bool {
if data.len() >= 4 {
let period_us = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
if let Some(mut t) = timer.take() {
t.stop();
}
if period_us > 0 {
let clamped = (period_us as u64).clamp(MIN_TIMER_PERIOD_US, MAX_TIMER_PERIOD_US);
let period = Duration::from_micros(clamped);
*timer = Some(TimerThread::start(period, inject_fn));
}
true
} else {
false
}
}
#[derive(Debug)]
pub(crate) struct TimerThread {
stop: Arc<AtomicBool>,
handle: Option<JoinHandle<()>>,
}
impl TimerThread {
pub(crate) fn start(period: Duration, inject_fn: impl Fn() + Send + 'static) -> Self {
let stop = Arc::new(AtomicBool::new(false));
let stop_clone = stop.clone();
let handle = std::thread::spawn(move || {
while !stop_clone.load(Ordering::Relaxed) {
std::thread::sleep(period);
if stop_clone.load(Ordering::Relaxed) {
break;
}
inject_fn();
}
});
Self {
stop,
handle: Some(handle),
}
}
pub(crate) fn stop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
if let Some(h) = self.handle.take() {
let _ = h.join();
}
}
pub(crate) fn is_active(&self) -> bool {
self.handle.is_some()
}
}
impl Drop for TimerThread {
fn drop(&mut self) {
self.stop();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn write_read_lapic_roundtrip() {
let mut state = vec![0u8; 1024];
write_lapic_u32(&mut state, 0xF0, 0xDEAD_BEEF);
assert_eq!(read_lapic_u32(&state, 0xF0), 0xDEAD_BEEF);
}
#[test]
fn write_read_lapic_multiple_offsets() {
let mut state = vec![0u8; 1024];
write_lapic_u32(&mut state, 0x80, 0x1234_5678);
write_lapic_u32(&mut state, 0xF0, 0xABCD_EF01);
write_lapic_u32(&mut state, 0xE0, 0xFFFF_FFFF);
assert_eq!(read_lapic_u32(&state, 0x80), 0x1234_5678);
assert_eq!(read_lapic_u32(&state, 0xF0), 0xABCD_EF01);
assert_eq!(read_lapic_u32(&state, 0xE0), 0xFFFF_FFFF);
}
#[test]
fn write_read_lapic_zero() {
let mut state = vec![0xFFu8; 1024];
write_lapic_u32(&mut state, 0x80, 0);
assert_eq!(read_lapic_u32(&state, 0x80), 0);
}
#[test]
fn write_does_not_clobber_neighbors() {
let mut state = vec![0u8; 1024];
write_lapic_u32(&mut state, 0x80, 0xAAAA_BBBB);
assert_eq!(state[0x7F], 0);
assert_eq!(state[0x84], 0);
}
#[test]
fn lapic_eoi_clears_highest_isr_bit() {
let mut state = vec![0u8; 1024];
write_lapic_u32(&mut state, 0x100, 1 << 5);
lapic_eoi(&mut state);
assert_eq!(read_lapic_u32(&state, 0x100), 0);
}
#[test]
fn lapic_eoi_clears_only_highest() {
let mut state = vec![0u8; 1024];
write_lapic_u32(&mut state, 0x100, 0b11); write_lapic_u32(&mut state, 0x110, 1 << 2); lapic_eoi(&mut state);
assert_eq!(read_lapic_u32(&state, 0x110), 0);
assert_eq!(read_lapic_u32(&state, 0x100), 0b11);
}
#[test]
fn init_lapic_registers_sets_svr() {
let mut state = vec![0u8; 1024];
init_lapic_registers(&mut state);
let svr = read_lapic_u32(&state, 0xF0);
assert_ne!(svr & 0x100, 0, "APIC enable bit should be set");
assert_eq!(svr & 0xFF, 0xFF, "spurious vector should be 0xFF");
}
#[test]
fn handle_io_in_pic_ports() {
assert_eq!(handle_io_in(0x21), Some(0xFF));
assert_eq!(handle_io_in(0xA1), Some(0xFF));
assert_eq!(handle_io_in(0x20), Some(0));
assert_eq!(handle_io_in(0xA0), Some(0));
assert_eq!(handle_io_in(0x40), Some(0));
assert_eq!(handle_io_in(0x42), None);
}
#[test]
fn handle_common_io_out_pic_eoi() {
let mut eoi_called = false;
let handled = handle_common_io_out(0x20, &[0x20], true, || eoi_called = true);
assert!(handled);
assert!(eoi_called);
}
#[test]
fn handle_common_io_out_no_eoi_when_timer_inactive() {
let mut eoi_called = false;
let handled = handle_common_io_out(0x20, &[0x20], false, || eoi_called = true);
assert!(handled);
assert!(!eoi_called);
}
#[test]
fn handle_common_io_out_pit_ports() {
assert!(handle_common_io_out(0x40, &[], false, || {}));
assert!(handle_common_io_out(0x43, &[], false, || {}));
}
#[test]
fn handle_common_io_out_speaker_and_diag() {
assert!(handle_common_io_out(0x61, &[], false, || {}));
assert!(handle_common_io_out(0x80, &[], false, || {}));
}
#[test]
fn handle_common_io_out_unknown_port() {
assert!(!handle_common_io_out(0x100, &[], false, || {}));
}
#[test]
#[should_panic]
fn write_lapic_u32_panics_on_short_buffer() {
let mut state = vec![0u8; 4];
write_lapic_u32(&mut state, 2, 0xDEAD); }
#[test]
#[should_panic]
fn read_lapic_u32_panics_on_short_buffer() {
let state = vec![0u8; 4];
read_lapic_u32(&state, 2); }
}