From 87d608366f6f1091f88ce3c0cd7b6b89295953ff Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Thu, 30 Oct 2025 02:36:14 -0400 Subject: [PATCH] feat(bootloader-interface): add support for loader boot times --- src/actions/chainload.rs | 2 +- src/context.rs | 19 +++++- src/integrations/bootloader_interface.rs | 11 ++-- src/main.rs | 10 ++- src/platform.rs | 2 + src/platform/timer.rs | 81 ++++++++++++++++++++++++ src/platform/timer/aarch64.rs | 33 ++++++++++ src/platform/timer/x86_64.rs | 66 +++++++++++++++++++ 8 files changed, 214 insertions(+), 10 deletions(-) create mode 100644 src/platform.rs create mode 100644 src/platform/timer.rs create mode 100644 src/platform/timer/aarch64.rs create mode 100644 src/platform/timer/x86_64.rs diff --git a/src/actions/chainload.rs b/src/actions/chainload.rs index 8223058..5341c4c 100644 --- a/src/actions/chainload.rs +++ b/src/actions/chainload.rs @@ -104,7 +104,7 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfigurat } // Mark execution of an entry in the bootloader interface. - BootloaderInterface::mark_exec() + BootloaderInterface::mark_exec(context.root().timer()) .context("unable to mark execution of boot entry in bootloader interface")?; // Start the loaded image. diff --git a/src/context.rs b/src/context.rs index 22998bc..cc15ab2 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,5 +1,6 @@ use crate::actions::ActionDeclaration; use crate::options::SproutOptions; +use crate::platform::timer::PlatformTimer; use anyhow::anyhow; use anyhow::{Result, bail}; use std::cmp::Reverse; @@ -12,22 +13,29 @@ const CONTEXT_FINALIZE_ITERATION_LIMIT: usize = 100; /// Declares a root context for Sprout. /// This contains data that needs to be shared across Sprout. -#[derive(Default)] pub struct RootContext { /// The actions that are available in Sprout. actions: BTreeMap, /// The device path of the loaded Sprout image. loaded_image_path: Option>, + /// Platform timer started at the beginning of the boot process. + timer: PlatformTimer, /// The global options of Sprout. options: SproutOptions, } impl RootContext { /// Creates a new root context with the `loaded_image_device_path` which will be stored - /// in the context for easy access. - pub fn new(loaded_image_device_path: Box, options: SproutOptions) -> Self { + /// in the context for easy access. We also provide a `timer` which is used to measure elapsed + /// time for the bootloader. + pub fn new( + loaded_image_device_path: Box, + timer: PlatformTimer, + options: SproutOptions, + ) -> Self { Self { actions: BTreeMap::new(), + timer, loaded_image_path: Some(loaded_image_device_path), options, } @@ -43,6 +51,11 @@ impl RootContext { &mut self.actions } + /// Access the platform timer that is started at the beginning of the boot process. + pub fn timer(&self) -> &PlatformTimer { + &self.timer + } + /// Access the device path of the loaded Sprout image. pub fn loaded_image_path(&self) -> Result<&DevicePath> { self.loaded_image_path diff --git a/src/integrations/bootloader_interface.rs b/src/integrations/bootloader_interface.rs index 4053ec9..b291d70 100644 --- a/src/integrations/bootloader_interface.rs +++ b/src/integrations/bootloader_interface.rs @@ -1,3 +1,4 @@ +use crate::platform::timer::PlatformTimer; use anyhow::{Context, Result}; use uefi::{CString16, Guid, guid}; use uefi_raw::table::runtime::{VariableAttributes, VariableVendor}; @@ -11,14 +12,14 @@ impl BootloaderInterface { /// Tell the system that Sprout was initialized at the current time. pub fn mark_init() -> Result<()> { - // TODO(azenla): Implement support for LoaderTimeInitUSec here. - Ok(()) + Self::set_cstr16("LoaderTimeInitUSec", "0") } /// Tell the system that Sprout is about to execute the boot entry. - pub fn mark_exec() -> Result<()> { - // TODO(azenla): Implement support for LoaderTimeExecUSec here. - Ok(()) + pub fn mark_exec(timer: &PlatformTimer) -> Result<()> { + // Measure the elapsed time since the bootloader was started. + let elapsed = timer.elapsed(); + Self::set_cstr16("LoaderTimeExecUSec", &elapsed.as_micros().to_string()) } /// Tell the system what the partition GUID of the ESP Sprout was booted from is. diff --git a/src/main.rs b/src/main.rs index f936654..7a62242 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ #![doc = include_str!("../README.md")] #![feature(uefi_std)] +extern crate core; use crate::config::RootConfiguration; use crate::context::{RootContext, SproutContext}; @@ -8,6 +9,7 @@ use crate::integrations::bootloader_interface::BootloaderInterface; use crate::options::SproutOptions; use crate::options::parser::OptionsRepresentable; use crate::phases::phase; +use crate::platform::timer::PlatformTimer; use crate::utils::PartitionGuidForm; use anyhow::{Context, Result, bail}; use log::{error, info}; @@ -40,6 +42,9 @@ pub mod extractors; /// generators: Runtime code that can generate entries with specific values. pub mod generators; +/// platform: Integration or support code for specific hardware platforms. +pub mod platform; + /// menu: Display a boot menu to select an entry to boot. pub mod menu; @@ -60,6 +65,9 @@ pub mod utils; /// Run Sprout, returning an error if one occurs. fn run() -> Result<()> { + // Start the platform timer. + let timer = PlatformTimer::start(); + // Mark the initialization of Sprout in the bootloader interface. BootloaderInterface::mark_init() .context("unable to mark initialization in bootloader interface")?; @@ -101,7 +109,7 @@ fn run() -> Result<()> { } // Create the root context. - let mut root = RootContext::new(loaded_image_path, options); + let mut root = RootContext::new(loaded_image_path, timer, options); // Insert the configuration actions into the root context. root.actions_mut().extend(config.actions.clone()); diff --git a/src/platform.rs b/src/platform.rs new file mode 100644 index 0000000..25b5b1f --- /dev/null +++ b/src/platform.rs @@ -0,0 +1,2 @@ +/// timer: Platform timer support code. +pub mod timer; diff --git a/src/platform/timer.rs b/src/platform/timer.rs new file mode 100644 index 0000000..80e151a --- /dev/null +++ b/src/platform/timer.rs @@ -0,0 +1,81 @@ +use std::time::Duration; + +/// Support for aarch64 timers. +#[cfg(target_arch = "aarch64")] +pub mod aarch64; + +/// Support for x86_64 timers. +#[cfg(target_arch = "x86_64")] +pub mod x86_64; + +/// The tick frequency of the platform. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum TickFrequency { + /// The platform provides the tick frequency. + Hardware(u64), + /// The tick frequency is measured internally. + Measured(u64, Duration), +} + +impl TickFrequency { + /// Acquire the tick frequency reported by the platform. + fn ticks(&self) -> u64 { + match self { + TickFrequency::Hardware(frequency) => *frequency, + TickFrequency::Measured(frequency, _) => *frequency, + } + } + + /// Calculate the nanoseconds represented by a tick. + fn nanos(&self) -> f64 { + 1.0e9_f64 / (self.ticks() as f64) + } + + /// Produce a duration from the provided elapsed `ticks` value. + fn duration(&self, ticks: u64) -> Duration { + let accuracy = self.nanos(); + let nanos = ticks as f64 * accuracy; + Duration::from_nanos(nanos as u64) + } +} + +/// Acquire the tick value reported by the platform. +fn arch_ticks() -> u64 { + #[cfg(target_arch = "aarch64")] + return aarch64::ticks(); + #[cfg(target_arch = "x86_64")] + return x86_64::ticks(); +} + +/// Acquire the tick frequency reported by the platform. +fn arch_frequency() -> TickFrequency { + #[cfg(target_arch = "aarch64")] + return aarch64::frequency(); + #[cfg(target_arch = "x86_64")] + return x86_64::frequency(); +} + +/// Platform timer that allows measurement of the elapsed time. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PlatformTimer { + /// The start tick value. + start: u64, + /// The tick frequency of the platform. + frequency: TickFrequency, +} + +impl PlatformTimer { + /// Start a platform timer at the current instant. + pub fn start() -> Self { + Self { + start: arch_ticks(), + frequency: arch_frequency(), + } + } + + /// Measure the elapsed duration since the timer was started. + pub fn elapsed(&self) -> Duration { + let duration = arch_ticks() - self.start; + self.frequency.duration(duration) + } +} diff --git a/src/platform/timer/aarch64.rs b/src/platform/timer/aarch64.rs new file mode 100644 index 0000000..46a57ca --- /dev/null +++ b/src/platform/timer/aarch64.rs @@ -0,0 +1,33 @@ +use crate::platform::timer::TickFrequency; +use std::arch::asm; + +/// Reads the cntvct_el0 counter and returns the value. +pub fn ticks() -> u64 { + let counter: u64; + unsafe { + asm!("mrs x0, cntvct_el0", out("x0") counter); + } + counter +} + +/// We can use the actual ticks value as our start value. +pub fn start() -> u64 { + ticks() +} + +/// We can use the actual ticks value as our stop value. +pub fn stop() -> u64 { + ticks() +} + +/// Our frequency is provided by cntfrq_el0 on the platform. +pub fn frequency() -> TickFrequency { + let frequency: u64; + unsafe { + asm!( + "mrs x0, cntfrq_el0", + out("x0") frequency + ); + } + TickFrequency::Hardware(frequency) +} diff --git a/src/platform/timer/x86_64.rs b/src/platform/timer/x86_64.rs new file mode 100644 index 0000000..96eb38c --- /dev/null +++ b/src/platform/timer/x86_64.rs @@ -0,0 +1,66 @@ +use crate::platform::timer::TickFrequency; +use core::arch::asm; +use std::time::Duration; + +/// We will measure the frequency of the timer based on 1000 microseconds. +/// This will result in a call to BS->Stall(1000) in the end. +const MEASURE_FREQUENCY_DURATION: Duration = Duration::from_micros(1000); + +/// Read the number of ticks from the platform timer. +pub fn ticks() -> u64 { + let mut eax: u32; + let mut edx: u32; + + unsafe { + asm!("rdtsc", out("eax") eax, out("edx") edx); + } + + (edx as u64) << 32 | eax as u64 +} + +/// Read the starting number of ticks from the platform timer. +pub fn start() -> u64 { + let rax: u64; + unsafe { + asm!( + "mfence", + "lfence", + "rdtsc", + "shl rdx, 32", + "or rax, rdx", + out("rax") rax + ); + } + rax +} + +/// Read the ending number of ticks from the platform timer. +pub fn stop() -> u64 { + let rax: u64; + unsafe { + asm!( + "rdtsc", + "lfence", + "shl rdx, 32", + "or rax, rdx", + out("rax") rax + ); + } + rax +} + +/// Measure the frequency of the platform timer. +fn measure_frequency(duration: &Duration) -> u64 { + let start = start(); + uefi::boot::stall(*duration); + let stop = stop(); + let elapsed = (stop - start) as f64; + (elapsed / duration.as_secs_f64()) as u64 +} + +/// Acquire the platform timer frequency. +/// On x86_64, this is slightly expensive, so it should be done once. +pub fn frequency() -> TickFrequency { + let frequency = measure_frequency(&MEASURE_FREQUENCY_DURATION); + TickFrequency::Measured(frequency, MEASURE_FREQUENCY_DURATION) +}