diff --git a/src/actions/chainload.rs b/src/actions/chainload.rs index b14015f..5341c4c 100644 --- a/src/actions/chainload.rs +++ b/src/actions/chainload.rs @@ -1,4 +1,5 @@ use crate::context::SproutContext; +use crate::integrations::bootloader_interface::BootloaderInterface; use crate::utils; use crate::utils::media_loader::MediaLoaderHandle; use crate::utils::media_loader::constants::linux::LINUX_EFI_INITRD_MEDIA_GUID; @@ -102,6 +103,10 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfigurat initrd_handle = Some(handle); } + // Mark execution of an entry in the bootloader interface. + BootloaderInterface::mark_exec(context.root().timer()) + .context("unable to mark execution of boot entry in bootloader interface")?; + // Start the loaded image. // This call might return, or it may pass full control to another image that will never return. // Capture the result to ensure we can return an error if the image fails to start, but only 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/entries.rs b/src/entries.rs index d624c77..eed0ece 100644 --- a/src/entries.rs +++ b/src/entries.rs @@ -28,6 +28,7 @@ pub struct BootableEntry { context: Rc, declaration: EntryDeclaration, default: bool, + pin_name: bool, } impl BootableEntry { @@ -44,6 +45,7 @@ impl BootableEntry { context, declaration, default: false, + pin_name: false, } } @@ -72,6 +74,11 @@ impl BootableEntry { self.default } + /// Fetch whether the entry is pinned, which prevents prefixing. + pub fn is_pin_name(&self) -> bool { + self.pin_name + } + /// Swap out the context of the entry. pub fn swap_context(&mut self, context: Rc) { self.context = context; @@ -87,6 +94,11 @@ impl BootableEntry { self.default = true; } + /// Mark this entry as being pinned, which prevents prefixing. + pub fn mark_pin_name(&mut self) { + self.pin_name = true; + } + /// Prepend the name of the entry with `prefix`. pub fn prepend_name_prefix(&mut self, prefix: &str) { self.name.insert_str(0, prefix); diff --git a/src/extractors/filesystem_device_match.rs b/src/extractors/filesystem_device_match.rs index 6a6a75a..5bfb0be 100644 --- a/src/extractors/filesystem_device_match.rs +++ b/src/extractors/filesystem_device_match.rs @@ -9,9 +9,7 @@ use uefi::fs::{FileSystem, Path}; use uefi::proto::device_path::DevicePath; use uefi::proto::media::file::{File, FileSystemVolumeLabel}; use uefi::proto::media::fs::SimpleFileSystem; -use uefi::proto::media::partition::PartitionInfo; -use uefi::{CString16, Guid, Handle}; -use uefi_raw::Status; +use uefi::{CString16, Guid}; /// The filesystem device match extractor. /// This extractor finds a filesystem using some search criteria and returns @@ -41,48 +39,6 @@ pub struct FilesystemDeviceMatchExtractor { pub fallback: Option, } -/// Represents the partition UUIDs for a filesystem. -struct PartitionIds { - /// The UUID of the partition. - partition_uuid: Guid, - /// The type UUID of the partition. - type_uuid: Guid, -} - -/// Fetches the partition UUIDs for the specified filesystem handle. -fn fetch_partition_uuids(handle: Handle) -> Result> { - // Open the partition info protocol for this handle. - let partition_info = uefi::boot::open_protocol_exclusive::(handle); - - match partition_info { - Ok(partition_info) => { - // GPT partitions have a unique partition GUID. - // MBR does not. - if let Some(gpt) = partition_info.gpt_partition_entry() { - let uuid = gpt.unique_partition_guid; - let type_uuid = gpt.partition_type_guid; - Ok(Some(PartitionIds { - partition_uuid: uuid, - type_uuid: type_uuid.0, - })) - } else { - Ok(None) - } - } - - Err(error) => { - // If the filesystem does not have a partition, that is okay. - if error.status() == Status::NOT_FOUND || error.status() == Status::UNSUPPORTED { - Ok(None) - } else { - // We should still handle other errors gracefully. - Err(error).context("unable to open filesystem partition info")?; - unreachable!() - } - } - } -} - /// Extract a filesystem device path using the specified `context` and `extractor` configuration. pub fn extract( context: Rc, @@ -106,30 +62,49 @@ pub fn extract( // This defines whether a match has been found. let mut has_match = false; - // Extract the partition info for this filesystem. - // There is no guarantee that the filesystem has a partition. - let partition_info = - fetch_partition_uuids(handle).context("unable to fetch partition info")?; - // Check if the partition info matches partition uuid criteria. - if let Some(ref partition_info) = partition_info - && let Some(ref has_partition_uuid) = extractor.has_partition_uuid - { + if let Some(ref has_partition_uuid) = extractor.has_partition_uuid { + // Parse the partition uuid from the extractor. let parsed_uuid = Guid::from_str(has_partition_uuid) .map_err(|e| anyhow!("unable to parse has-partition-uuid: {}", e))?; - if partition_info.partition_uuid != parsed_uuid { + + // Fetch the root of the device. + let root = uefi::boot::open_protocol_exclusive::(handle) + .context("unable to fetch the device path of the filesystem")? + .deref() + .to_boxed(); + + // Fetch the partition uuid for this filesystem. + let partition_uuid = utils::partition_guid(&root, utils::PartitionGuidForm::Partition) + .context("unable to fetch the partition uuid of the filesystem")?; + + // Compare the partition uuid to the parsed uuid. + // If it does not match, continue to the next filesystem. + if partition_uuid != Some(parsed_uuid) { continue; } has_match = true; } // Check if the partition info matches partition type uuid criteria. - if let Some(ref partition_info) = partition_info - && let Some(ref has_partition_type_uuid) = extractor.has_partition_type_uuid - { + if let Some(ref has_partition_type_uuid) = extractor.has_partition_type_uuid { + // Parse the partition type uuid from the extractor. let parsed_uuid = Guid::from_str(has_partition_type_uuid) .map_err(|e| anyhow!("unable to parse has-partition-type-uuid: {}", e))?; - if partition_info.type_uuid != parsed_uuid { + + // Fetch the root of the device. + let root = uefi::boot::open_protocol_exclusive::(handle) + .context("unable to fetch the device path of the filesystem")? + .deref() + .to_boxed(); + + // Fetch the partition uuid for this filesystem. + let partition_type_uuid = + utils::partition_guid(&root, utils::PartitionGuidForm::Partition) + .context("unable to fetch the partition uuid of the filesystem")?; + // Compare the partition type uuid to the parsed uuid. + // If it does not match, continue to the next filesystem. + if partition_type_uuid != Some(parsed_uuid) { continue; } has_match = true; diff --git a/src/generators/bls.rs b/src/generators/bls.rs index 9dc8d7d..4133427 100644 --- a/src/generators/bls.rs +++ b/src/generators/bls.rs @@ -83,13 +83,16 @@ pub fn generate(context: Rc, bls: &BlsConfiguration) -> Result, bls: &BlsConfiguration) -> Result Result<()> { + Self::mark_time("LoaderTimeInitUSec", timer) + } + + /// Tell the system that Sprout is about to execute the boot entry. + pub fn mark_exec(timer: &PlatformTimer) -> Result<()> { + Self::mark_time("LoaderTimeExecUSec", timer) + } + + /// Tell the system that Sprout is about to display the menu. + pub fn mark_menu(timer: &PlatformTimer) -> Result<()> { + Self::mark_time("LoaderTimeMenuUsec", timer) + } + + /// Tell the system about the current time as measured by the platform timer. + /// Sets the variable specified by `key` to the number of microseconds. + fn mark_time(key: &str, timer: &PlatformTimer) -> Result<()> { + // Measure the elapsed time since the hardware timer was started. + let elapsed = timer.elapsed_since_lifetime(); + Self::set_cstr16(key, &elapsed.as_micros().to_string()) + } + + /// Tell the system what loader is being used. + pub fn set_loader_info() -> Result<()> { + Self::set_cstr16("LoaderInfo", LOADER_NAME) + } + + /// Tell the system the relative path to the partition root of the current bootloader. + pub fn set_loader_path(path: &DevicePath) -> Result<()> { + let subpath = device_path_subpath(path).context("unable to get loader path subpath")?; + Self::set_cstr16("LoaderImageIdentifier", &subpath) + } + + /// Tell the system what the partition GUID of the ESP Sprout was booted from is. + pub fn set_partition_guid(guid: &Guid) -> Result<()> { + Self::set_cstr16("LoaderDevicePartUUID", &guid.to_string()) + } + + /// Tell the system what boot entries are available. + pub fn set_entries>(entries: impl Iterator) -> Result<()> { + // Entries are stored as a null-terminated list of CString16 strings back to back. + // Iterate over the entries and convert them to CString16 placing them into data. + let mut data = Vec::new(); + for entry in entries { + // Convert the entry to CString16 little endian. + let encoded = entry + .as_ref() + .encode_utf16() + .flat_map(|c| c.to_le_bytes()) + .collect::>(); + // Write the bytes (including the null terminator) into the data buffer. + data.extend_from_slice(&encoded); + } + Self::set("LoaderEntries", &data) + } + + /// Tell the system what the default boot entry is. + pub fn set_default_entry(entry: String) -> Result<()> { + Self::set_cstr16("LoaderEntryDefault", &entry) + } + + /// Tell the system what the selected boot entry is. + pub fn set_selected_entry(entry: String) -> Result<()> { + Self::set_cstr16("LoaderEntrySelected", &entry) + } + + /// Tell the system about the UEFI firmware we are running on. + pub fn set_firmware_info() -> Result<()> { + // Format the firmware information string into something human-readable. + let firmware_info = format!( + "{} {}.{:02}", + uefi::system::firmware_vendor(), + uefi::system::firmware_revision() >> 16, + uefi::system::firmware_revision() & 0xFFFFF, + ); + Self::set_cstr16("LoaderFirmwareInfo", &firmware_info)?; + + // Format the firmware revision into something human-readable. + let firmware_type = format!("UEFI {:02}", uefi::system::firmware_revision()); + Self::set_cstr16("LoaderFirmwareType", &firmware_type) + } + + /// The [VariableAttributes] for bootloader interface variables. + fn attributes() -> VariableAttributes { + VariableAttributes::BOOTSERVICE_ACCESS | VariableAttributes::RUNTIME_ACCESS + } + + /// Set a bootloader interface variable specified by `key` to `value`. + fn set(key: &str, value: &[u8]) -> Result<()> { + let name = + CString16::try_from(key).context("unable to convert variable name to CString16")?; + uefi::runtime::set_variable(&name, &Self::VENDOR, Self::attributes(), value) + .with_context(|| format!("unable to set efi variable {}", key))?; + Ok(()) + } + + /// Set a bootloader interface variable specified by `key` to `value`, converting the value to + /// a [CString16]. + fn set_cstr16(key: &str, value: &str) -> Result<()> { + // Encode the value as a CString16 little endian. + let encoded = value + .encode_utf16() + .flat_map(|c| c.to_le_bytes()) + .collect::>(); + Self::set(key, &encoded) + } +} diff --git a/src/main.rs b/src/main.rs index 40caa9e..13d030b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,16 @@ #![doc = include_str!("../README.md")] #![feature(uefi_std)] +extern crate core; use crate::config::RootConfiguration; use crate::context::{RootContext, SproutContext}; use crate::entries::BootableEntry; +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}; use std::collections::BTreeMap; @@ -38,9 +42,15 @@ 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; +/// integrations: Code that interacts with other systems. +pub mod integrations; + /// phases: Hooks into specific parts of the boot process. pub mod phases; @@ -55,6 +65,21 @@ 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(&timer) + .context("unable to mark initialization in bootloader interface")?; + + // Tell the bootloader interface what firmware we are running on. + BootloaderInterface::set_firmware_info() + .context("unable to set firmware info in bootloader interface")?; + + // Tell the bootloader interface what loader is being used. + BootloaderInterface::set_loader_info() + .context("unable to set loader info in bootloader interface")?; + // Parse the options to the sprout executable. let options = SproutOptions::parse().context("unable to parse options")?; @@ -69,17 +94,35 @@ fn run() -> Result<()> { config::loader::load(&options)? }; - // Load the root context. + // Grab the sprout.efi loaded image path. // This is done in a block to ensure the release of the LoadedImageDevicePath protocol. - let mut root = { + let loaded_image_path = { let current_image_device_path_protocol = uefi::boot::open_protocol_exclusive::< LoadedImageDevicePath, >(uefi::boot::image_handle()) .context("unable to get loaded image device path")?; - let loaded_image_path = current_image_device_path_protocol.deref().to_boxed(); - RootContext::new(loaded_image_path, options) + current_image_device_path_protocol.deref().to_boxed() }; + // Grab the partition GUID of the ESP that sprout was loaded from. + let loaded_image_partition_guid = + utils::partition_guid(&loaded_image_path, PartitionGuidForm::Partition) + .context("unable to retrieve loaded image partition guid")?; + + // Set the partition GUID of the ESP that sprout was loaded from in the bootloader interface. + if let Some(loaded_image_partition_guid) = loaded_image_partition_guid { + // Tell the system about the partition GUID. + BootloaderInterface::set_partition_guid(&loaded_image_partition_guid) + .context("unable to set partition guid in bootloader interface")?; + } + + // Tell the bootloader interface what the loaded image path is. + BootloaderInterface::set_loader_path(&loaded_image_path) + .context("unable to set loader path in bootloader interface")?; + + // Create the root context. + 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()); @@ -159,13 +202,17 @@ fn run() -> Result<()> { for (name, generator) in config.generators { let context = context.fork().freeze(); - // We will prefix all entries with [name]-. + // We will prefix all entries with [name]-, provided the name is not pinned. let prefix = format!("{}-", name); // Add all the entries generated by the generator to the entry list. // The generator specifies the context associated with the entry. for mut entry in generators::generate(context.clone(), &generator)? { - entry.prepend_name_prefix(&prefix); + // If the entry name is not pinned, prepend the name prefix. + if !entry.is_pin_name() { + entry.prepend_name_prefix(&prefix); + } + entries.push(entry); } } @@ -200,6 +247,21 @@ fn run() -> Result<()> { entry.mark_default(); } + // Iterate over all the entries and tell the bootloader interface what the entries are. + for entry in &entries { + // If the entry is the default entry, tell the bootloader interface it is the default. + if entry.is_default() { + // Tell the bootloader interface what the default entry is. + BootloaderInterface::set_default_entry(entry.name().to_string()) + .context("unable to set default entry in bootloader interface")?; + break; + } + } + + // Tell the bootloader interface what entries are available. + BootloaderInterface::set_entries(entries.iter().map(|entry| entry.name())) + .context("unable to set entries in bootloader interface")?; + // Execute the late phase. phase(context.clone(), &config.phases.late).context("unable to execute late phase")?; @@ -223,9 +285,14 @@ fn run() -> Result<()> { .context(format!("unable to find entry: {force_boot_entry}"))? } else { // Delegate to the menu to select an entry to boot. - menu::select(menu_timeout, &entries).context("unable to select entry via boot menu")? + menu::select(&timer, menu_timeout, &entries) + .context("unable to select entry via boot menu")? }; + // Tell the bootloader interface what the selected entry is. + BootloaderInterface::set_selected_entry(entry.name().to_string()) + .context("unable to set selected entry in bootloader interface")?; + // Execute all the actions for the selected entry. for action in &entry.declaration().actions { let action = entry.context().stamp(action); diff --git a/src/menu.rs b/src/menu.rs index baa7ea0..a735bb1 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -1,4 +1,6 @@ use crate::entries::BootableEntry; +use crate::integrations::bootloader_interface::BootloaderInterface; +use crate::platform::timer::PlatformTimer; use anyhow::{Context, Result, bail}; use log::info; use std::time::Duration; @@ -162,7 +164,15 @@ fn select_with_input<'a>( /// Shows a boot menu to select a bootable entry to boot. /// The actual work is done internally in [select_with_input] which is called /// within the context of the standard input device. -pub fn select(timeout: Duration, entries: &[BootableEntry]) -> Result<&BootableEntry> { +pub fn select<'live>( + timer: &'live PlatformTimer, + timeout: Duration, + entries: &'live [BootableEntry], +) -> Result<&'live BootableEntry> { + // Notify the bootloader interface that we are about to display the menu. + BootloaderInterface::mark_menu(timer) + .context("unable to mark menu display in bootloader interface")?; + // Acquire the standard input device and run the boot menu. uefi::system::with_stdin(move |input| select_with_input(input, timeout, entries)) } diff --git a/src/platform.rs b/src/platform.rs new file mode 100644 index 0000000..66d17b4 --- /dev/null +++ b/src/platform.rs @@ -0,0 +1,2 @@ +/// timer: Platform timer support. +pub mod timer; diff --git a/src/platform/timer.rs b/src/platform/timer.rs new file mode 100644 index 0000000..e546e9c --- /dev/null +++ b/src/platform/timer.rs @@ -0,0 +1,89 @@ +// Referenced https://github.com/sheroz/tick_counter (MIT license) as a baseline. +// Architecturally modified to support UEFI and remove x86 (32-bit) support. + +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 hardware started ticking upwards. + pub fn elapsed_since_lifetime(&self) -> Duration { + self.frequency.duration(arch_ticks()) + } + + /// Measure the elapsed duration since the timer was started. + pub fn elapsed_since_start(&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) +} diff --git a/src/utils.rs b/src/utils.rs index e988b8c..936a508 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,7 +4,9 @@ use uefi::fs::{FileSystem, Path}; use uefi::proto::device_path::text::{AllowShortcuts, DevicePathFromText, DisplayOnly}; use uefi::proto::device_path::{DevicePath, PoolDevicePath}; use uefi::proto::media::fs::SimpleFileSystem; -use uefi::{CString16, Handle}; +use uefi::proto::media::partition::PartitionInfo; +use uefi::{CString16, Guid, Handle}; +use uefi_raw::Status; /// Support code for the EFI framebuffer. pub mod framebuffer; @@ -181,3 +183,49 @@ pub fn combine_options>(options: impl Iterator) -> Strin pub fn unique_hash(input: &str) -> String { sha256::digest(input.as_bytes()) } + +/// Represents the type of partition GUID that can be retrieved. +#[derive(PartialEq, Eq)] +pub enum PartitionGuidForm { + Partition, + PartitionType, +} + +/// Retrieve the partition / partition type GUID of the device root `path`. +/// This only works on GPT partitions. If the root is not a GPT partition, None is returned. +pub fn partition_guid(path: &DevicePath, form: PartitionGuidForm) -> Result> { + // Clone the path so we can pass it to the UEFI stack. + let path = path.to_boxed(); + let result = uefi::boot::locate_device_path::(&mut &*path); + let handle = match result { + Ok(handle) => Ok(Some(handle)), + Err(error) => { + // If the error is NOT_FOUND or UNSUPPORTED, we can return None. + // These are non-fatal errors. + if error.status() == Status::NOT_FOUND || error.status() == Status::UNSUPPORTED { + Ok(None) + } else { + Err(error) + } + } + } + .context("unable to locate device path")?; + + // If we have the handle, we can try to open the partition info protocol. + if let Some(handle) = handle { + // Open the partition info protocol. + let partition_info = uefi::boot::open_protocol_exclusive::(handle) + .context("unable to open partition info protocol")?; + // Find the unique partition GUID. + // If this is not a GPT partition, this will produce None. + Ok(partition_info + .gpt_partition_entry() + .map(|entry| match form { + // Match the form of the partition GUID. + PartitionGuidForm::Partition => entry.unique_partition_guid, + PartitionGuidForm::PartitionType => entry.partition_type_guid.0, + })) + } else { + Ok(None) + } +}