diff --git a/alioth/src/arch/x86_64/layout.rs b/alioth/src/arch/x86_64/layout.rs index 3cc685cc..699ee1b2 100644 --- a/alioth/src/arch/x86_64/layout.rs +++ b/alioth/src/arch/x86_64/layout.rs @@ -70,6 +70,7 @@ pub const PORT_FW_CFG_DMA_LO: u16 = 0x518; pub const PORT_ACPI_SLEEP_CONTROL: u16 = 0x600; pub const PORT_ACPI_SLEEP_STATUS: u16 = 0x601; pub const PORT_ACPI_RESET: u16 = 0x604; +pub const PORT_ACPI_TIMER: u16 = 0x608; pub const PORT_PCI_ADDRESS: u16 = 0xcf8; pub const PORT_PCI_DATA: u16 = 0xcfc; diff --git a/alioth/src/board/board_x86_64/board_x86_64.rs b/alioth/src/board/board_x86_64/board_x86_64.rs index 2f3f0ff3..3f8a9e97 100644 --- a/alioth/src/board/board_x86_64/board_x86_64.rs +++ b/alioth/src/board/board_x86_64/board_x86_64.rs @@ -29,7 +29,7 @@ use zerocopy::{FromZeros, IntoBytes}; use crate::arch::cpuid::CpuidIn; use crate::arch::layout::{ BIOS_DATA_END, EBDA_END, EBDA_START, IOAPIC_START, MEM_64_START, PORT_ACPI_RESET, - PORT_ACPI_SLEEP_CONTROL, RAM_32_SIZE, + PORT_ACPI_SLEEP_CONTROL, PORT_ACPI_TIMER, RAM_32_SIZE, }; use crate::arch::msr::{IA32_MISC_ENABLE, MiscEnable}; use crate::board::{Board, BoardConfig, CpuTopology, PCIE_MMIO_64_SIZE, Result, VcpuGuard, error}; @@ -37,7 +37,7 @@ use crate::device::ioapic::IoApic; use crate::firmware::acpi::bindings::{ AcpiTableFadt, AcpiTableHeader, AcpiTableRsdp, AcpiTableXsdt3, }; -use crate::firmware::acpi::reg::{FadtReset, FadtSleepControl}; +use crate::firmware::acpi::reg::{AcpiPmTimer, FadtReset, FadtSleepControl}; use crate::firmware::acpi::{ AcpiTable, create_fadt, create_madt, create_mcfg, create_rsdp, create_xsdt, }; @@ -431,6 +431,7 @@ where let memory = &self.memory; memory.add_io_dev(PORT_ACPI_RESET, Arc::new(FadtReset))?; memory.add_io_dev(PORT_ACPI_SLEEP_CONTROL, Arc::new(FadtSleepControl))?; + memory.add_io_dev(PORT_ACPI_TIMER, Arc::new(AcpiPmTimer::new()))?; if self.config.coco.is_none() { let ram = memory.ram_bus(); acpi_table.relocate(EBDA_START + size_of::() as u64); diff --git a/alioth/src/firmware/acpi/acpi.rs b/alioth/src/firmware/acpi/acpi.rs index fd37bb58..83481385 100644 --- a/alioth/src/firmware/acpi/acpi.rs +++ b/alioth/src/firmware/acpi/acpi.rs @@ -19,11 +19,12 @@ use std::mem::{offset_of, size_of}; use zerocopy::{FromBytes, IntoBytes, transmute}; -use crate::arch::layout::PCIE_CONFIG_START; #[cfg(target_arch = "x86_64")] use crate::arch::layout::{ APIC_START, IOAPIC_START, PORT_ACPI_RESET, PORT_ACPI_SLEEP_CONTROL, PORT_ACPI_SLEEP_STATUS, }; +use crate::arch::layout::{PCIE_CONFIG_START, PORT_ACPI_TIMER}; +use crate::firmware::acpi::bindings::AcpiFadtFlag; use crate::utils::wrapping_sum; use self::bindings::{ @@ -93,6 +94,13 @@ pub fn create_fadt(dsdt_addr: u64) -> AcpiTableFadt { address: transmute!(PORT_ACPI_RESET as u64), }, reset_value: FADT_RESET_VAL, + xpm_timer_block: AcpiGenericAddress { + space_id: 1, + bit_width: 32, + bit_offset: 0, + access_width: 3, + address: transmute!(PORT_ACPI_TIMER as u64), + }, sleep_control: AcpiGenericAddress { space_id: 1, bit_width: 8, @@ -107,7 +115,9 @@ pub fn create_fadt(dsdt_addr: u64) -> AcpiTableFadt { access_width: 1, address: transmute!(PORT_ACPI_SLEEP_STATUS as u64), }, - flags: (1 << 20) | (1 << 10), + flags: AcpiFadtFlag::HW_REDUCED_ACPI + | AcpiFadtFlag::RESET_REG_SUP + | AcpiFadtFlag::TMR_VAL_EXT, minor_revision: FADT_MINOR_VERSION, hypervisor_id: *b"ALIOTH ", xdsdt: transmute!(dsdt_addr), diff --git a/alioth/src/firmware/acpi/bindings.rs b/alioth/src/firmware/acpi/bindings.rs index 2eb00208..3ffbb741 100644 --- a/alioth/src/firmware/acpi/bindings.rs +++ b/alioth/src/firmware/acpi/bindings.rs @@ -13,7 +13,9 @@ // limitations under the License. use bitfield::bitfield; -use zerocopy::{FromBytes, Immutable, IntoBytes}; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use crate::bitflags; pub const SIG_RSDP: [u8; 8] = *b"RSD PTR "; pub const SIG_XSDT: [u8; 4] = *b"XSDT"; @@ -82,6 +84,15 @@ pub struct AcpiGenericAddress { pub const FADT_MAJOR_VERSION: u8 = 6; pub const FADT_MINOR_VERSION: u8 = 4; +bitflags! { + #[derive(Default, KnownLayout, Immutable, FromBytes, IntoBytes)] + pub struct AcpiFadtFlag(u32) { + TMR_VAL_EXT = 1 << 8; + RESET_REG_SUP = 1 << 10; + HW_REDUCED_ACPI = 1 << 20; + } +} + #[repr(C, align(4))] #[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] pub struct AcpiTableFadt { @@ -124,7 +135,7 @@ pub struct AcpiTableFadt { pub boot_flags: u8, pub boot_flags_hi: u8, pub reserved: u8, - pub flags: u32, + pub flags: AcpiFadtFlag, pub reset_register: AcpiGenericAddress, pub reset_value: u8, pub arm_boot_flags: u8, diff --git a/alioth/src/firmware/acpi/reg.rs b/alioth/src/firmware/acpi/reg.rs index 04d93e10..d8e9de49 100644 --- a/alioth/src/firmware/acpi/reg.rs +++ b/alioth/src/firmware/acpi/reg.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::time::Instant; + use crate::firmware::acpi::bindings::FadtSleepControlReg; use crate::mem::Result; use crate::mem::emulated::{Action, Mmio}; @@ -60,3 +62,47 @@ impl Mmio for FadtSleepControl { } } } + +/// Power Management Timer +/// +/// ACPI v6.5, Sec. 4.8.2.1 +#[derive(Debug)] +pub struct AcpiPmTimer { + start: Instant, +} + +const PM_TIMER_FREQUENCY_HZ: u128 = 3_579_545; + +impl AcpiPmTimer { + pub fn new() -> Self { + Self { + start: Instant::now(), + } + } +} + +impl Default for AcpiPmTimer { + fn default() -> Self { + Self::new() + } +} + +impl Mmio for AcpiPmTimer { + fn read(&self, _offset: u64, _size: u8) -> Result { + let nanos = Instant::now().duration_since(self.start).as_nanos(); + let counter = nanos * PM_TIMER_FREQUENCY_HZ / 1_000_000_000; + Ok(counter as u32 as u64) + } + + fn write(&self, _offset: u64, _size: u8, _val: u64) -> Result { + Ok(Action::None) + } + + fn size(&self) -> u64 { + 4 + } +} + +#[cfg(test)] +#[path = "reg_test.rs"] +mod tests; diff --git a/alioth/src/firmware/acpi/reg_test.rs b/alioth/src/firmware/acpi/reg_test.rs new file mode 100644 index 00000000..3fbac961 --- /dev/null +++ b/alioth/src/firmware/acpi/reg_test.rs @@ -0,0 +1,24 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::firmware::acpi::reg::AcpiPmTimer; +use crate::mem::emulated::Mmio; + +#[test] +fn test_pm_timer() { + let timer = AcpiPmTimer::default(); + let v1 = timer.read(0, 4).unwrap(); + let v2 = timer.read(0, 4).unwrap(); + assert!(v2 > v1); +}