use super::*;
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_io_out_i8042_reset_is_shutdown_signal() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
assert!(
dispatch_io_out(&com1, &com2, None, I8042_CMD_PORT, &[I8042_CMD_RESET_CPU]),
"I8042 reset (0xFE to port 0x64) must signal shutdown"
);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_io_out_i8042_non_reset() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
assert!(!dispatch_io_out(
&com1,
&com2,
None,
I8042_CMD_PORT,
&[0x00]
));
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_io_out_serial_com1() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
assert!(!dispatch_io_out(
&com1,
&com2,
None,
console::COM1_BASE,
b"A"
));
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_io_out_serial_com2() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
assert!(!dispatch_io_out(
&com1,
&com2,
None,
console::COM2_BASE,
b"B"
));
let output = com2.lock().output();
assert!(output.contains('B'));
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_io_out_unknown_port() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
assert!(!dispatch_io_out(&com1, &com2, None, 0x1234, &[0xFF]));
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_io_in_i8042_status() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
let mut data = [0xFFu8; 1];
dispatch_io_in(&com1, &com2, None, I8042_CMD_PORT, &mut data);
assert_eq!(data[0], 0);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_io_in_i8042_data() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
let mut data = [0xFFu8; 1];
dispatch_io_in(&com1, &com2, None, I8042_DATA_PORT, &mut data);
assert_eq!(data[0], 0);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_io_in_unknown_port() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
let mut data = [0x42u8; 2];
dispatch_io_in(&com1, &com2, None, 0x1234, &mut data);
assert_eq!(
data,
[0xFF, 0xFF],
"unknown IN port must return all-ones, not stale buffer bytes",
);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn classify_exit_io_out_i8042_reset_is_shutdown() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
let data = [I8042_CMD_RESET_CPU];
let mut exit = VcpuExit::IoOut(I8042_CMD_PORT, &data);
let action = classify_exit(&com1, &com2, None, None, None, None, None, &mut exit);
assert!(
matches!(action, Some(ExitAction::Shutdown)),
"IoOut(0x64, [0xFE]) — i8042 reset — must classify as Shutdown"
);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn classify_exit_io_out_serial_is_continue() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
let data = [b'Z'];
let mut exit = VcpuExit::IoOut(console::COM1_BASE, &data);
let action = classify_exit(&com1, &com2, None, None, None, None, None, &mut exit);
assert!(
matches!(action, Some(ExitAction::Continue)),
"IoOut to COM1 must classify as Continue (no reboot)"
);
assert!(com1.lock().output().contains('Z'));
}
#[test]
#[cfg(target_arch = "x86_64")]
fn classify_exit_io_in_serial_is_continue() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
let mut data = [0xFFu8; 1];
let mut exit = VcpuExit::IoIn(console::COM1_BASE, &mut data);
let action = classify_exit(&com1, &com2, None, None, None, None, None, &mut exit);
assert!(
matches!(action, Some(ExitAction::Continue)),
"IoIn to COM1 must classify as Continue"
);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn classify_exit_x86_mmio_read_unmapped_returns_0xff() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
let mut buf = [0u8; 4];
let mut exit = VcpuExit::MmioRead(0x1000, &mut buf);
let action = classify_exit(&com1, &com2, None, None, None, None, None, &mut exit);
assert!(
matches!(action, Some(ExitAction::Continue)),
"Unmapped MMIO read must classify as Continue (not Fatal)"
);
assert_eq!(
buf,
[0xff, 0xff, 0xff, 0xff],
"Unmapped MMIO read must fill the data buffer with 0xFF — \
leaving stale bytes would surface as phantom guest reads"
);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn classify_exit_x86_mmio_write_unmapped_is_continue() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
let data = [0xAAu8, 0xBB];
let mut exit = VcpuExit::MmioWrite(0x1000, &data);
let action = classify_exit(&com1, &com2, None, None, None, None, None, &mut exit);
assert!(
matches!(action, Some(ExitAction::Continue)),
"Unmapped MMIO write must classify as Continue"
);
}
#[cfg(target_arch = "x86_64")]
fn test_pci_bus() -> PiMutex<pci::PciBus> {
PiMutex::new(pci::PciBus::new(0xE000_0000, pci::ECAM_BYTES_PER_BUS))
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_cam_latch_then_data_reads_host_bridge_id() {
let bus = test_pci_bus();
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
assert!(!dispatch_io_out(
&com1,
&com2,
Some(&bus),
PCI_CONFIG_ADDRESS,
&0x8000_0000u32.to_le_bytes(),
));
let mut addr = [0u8; 4];
dispatch_io_in(&com1, &com2, Some(&bus), PCI_CONFIG_ADDRESS, &mut addr);
assert_eq!(u32::from_le_bytes(addr), 0x8000_0000);
let mut data = [0u8; 4];
dispatch_io_in(&com1, &com2, Some(&bus), PCI_CONFIG_DATA, &mut data);
let id = u32::from_le_bytes(data);
assert_eq!(id & 0xFFFF, 0x8086, "host-bridge vendor id via CAM");
assert_eq!(id >> 16, 0x0D57, "host-bridge device id via CAM");
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_cam_subdword_config_address_does_not_latch() {
let bus = test_pci_bus();
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
dispatch_io_out(
&com1,
&com2,
Some(&bus),
PCI_CONFIG_ADDRESS,
&0x8000_0000u32.to_le_bytes(),
);
dispatch_io_out(&com1, &com2, Some(&bus), PCI_CONFIG_ADDRESS, &[0x01]);
let mut addr = [0u8; 4];
dispatch_io_in(&com1, &com2, Some(&bus), PCI_CONFIG_ADDRESS, &mut addr);
assert_eq!(
u32::from_le_bytes(addr),
0x8000_0000,
"sub-dword CONFIG_ADDRESS write must not re-latch",
);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_pm1_cnt_reports_sci_en() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
let mut data = [0xFFu8; 2];
dispatch_io_in(&com1, &com2, None, kvm::ACPI_PM1_CNT_PORT, &mut data);
assert_eq!(
u16::from_le_bytes(data),
0x0001,
"PM1_CNT must report SCI_EN (bit0) set",
);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_pm1_evt_reads_zero_and_write_is_noop() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
let mut data = [0xFFu8; 4];
dispatch_io_in(&com1, &com2, None, kvm::ACPI_PM1_EVT_PORT, &mut data);
assert_eq!(
data,
[0, 0, 0, 0],
"PM1 status/enable read 0 (no event armed)"
);
assert!(!dispatch_io_out(
&com1,
&com2,
None,
kvm::ACPI_PM1_EVT_PORT,
&[0xFF; 4],
));
let mut after = [0xFFu8; 4];
dispatch_io_in(&com1, &com2, None, kvm::ACPI_PM1_EVT_PORT, &mut after);
assert_eq!(after, [0, 0, 0, 0], "PM1_EVT still 0 after a write");
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_pm_timer_is_24bit_and_advances() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
let mut t = [0xFFu8; 4];
dispatch_io_in(&com1, &com2, None, kvm::ACPI_PM_TMR_PORT, &mut t);
assert!(
u32::from_le_bytes(t) <= 0x00FF_FFFF,
"PM timer is a 24-bit value (TMR_VAL_EXT clear)",
);
let v0 = acpi_pm_timer_value();
let mut advanced = false;
for _ in 0..1_000_000u32 {
if acpi_pm_timer_value() != v0 {
advanced = true;
break;
}
}
assert!(advanced, "PM timer must advance (moving liveness counter)");
}
#[test]
#[cfg(target_arch = "x86_64")]
fn acpi_pm_port_claims_only_advertised_blocks() {
for p in [
kvm::ACPI_PM1_EVT_PORT,
0x603,
kvm::ACPI_PM1_CNT_PORT,
0x605,
kvm::ACPI_PM_TMR_PORT,
0x60B,
] {
assert!(acpi_pm_port(p), "{p:#x} is an advertised PM register port");
}
for p in [0x5FFu16, 0x606, 0x607, 0x60C] {
assert!(
!acpi_pm_port(p),
"{p:#x} is NOT an advertised PM register port"
);
}
}