Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Instant for UEFI #120889

Merged
merged 1 commit into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions library/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@
#![cfg_attr(any(windows, target_os = "uefi"), feature(round_char_boundary))]
#![cfg_attr(target_os = "xous", feature(slice_ptr_len))]
#![cfg_attr(target_family = "wasm", feature(stdarch_wasm_atomic_wait))]
#![cfg_attr(
all(any(target_arch = "x86_64", target_arch = "x86"), target_os = "uefi"),
feature(stdarch_x86_has_cpuid)
)]
//
// Language features:
// tidy-alphabetical-start
Expand Down
116 changes: 116 additions & 0 deletions library/std/src/sys/pal/uefi/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::from_secs(0));

impl Instant {
pub fn now() -> Instant {
// If we have a timestamp protocol, use it.
if let Some(x) = instant_internal::timestamp_protocol() {
return x;
}

if let Some(x) = instant_internal::platform_specific() {
return x;
}

panic!("time not implemented on this platform")
}

Expand Down Expand Up @@ -103,3 +112,110 @@ pub(crate) mod system_time_internal {
Duration::new(utc_epoch, t.nanosecond)
}
}

pub(crate) mod instant_internal {
use super::super::helpers;
use super::*;
use crate::mem::MaybeUninit;
use crate::ptr::NonNull;
use crate::sync::atomic::{AtomicPtr, Ordering};
use crate::sys_common::mul_div_u64;
use r_efi::protocols::timestamp;

const NS_PER_SEC: u64 = 1_000_000_000;

pub fn timestamp_protocol() -> Option<Instant> {
fn try_handle(handle: NonNull<crate::ffi::c_void>) -> Option<u64> {
let protocol: NonNull<timestamp::Protocol> =
helpers::open_protocol(handle, timestamp::PROTOCOL_GUID).ok()?;
let mut properties: MaybeUninit<timestamp::Properties> = MaybeUninit::uninit();

let r = unsafe { ((*protocol.as_ptr()).get_properties)(properties.as_mut_ptr()) };
if r.is_error() {
return None;
}

let freq = unsafe { properties.assume_init().frequency };
let ts = unsafe { ((*protocol.as_ptr()).get_timestamp)() };
Some(mul_div_u64(ts, NS_PER_SEC, freq))
}

static LAST_VALID_HANDLE: AtomicPtr<crate::ffi::c_void> =
AtomicPtr::new(crate::ptr::null_mut());

if let Some(handle) = NonNull::new(LAST_VALID_HANDLE.load(Ordering::Acquire)) {
if let Some(ns) = try_handle(handle) {
return Some(Instant(Duration::from_nanos(ns)));
}
}

if let Ok(handles) = helpers::locate_handles(timestamp::PROTOCOL_GUID) {
for handle in handles {
if let Some(ns) = try_handle(handle) {
LAST_VALID_HANDLE.store(handle.as_ptr(), Ordering::Release);
return Some(Instant(Duration::from_nanos(ns)));
}
}
}

None
}

pub fn platform_specific() -> Option<Instant> {
cfg_if::cfg_if! {
if #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] {
timestamp_rdtsc().map(Instant)
} else {
None
}
}
}

#[cfg(target_arch = "x86_64")]
fn timestamp_rdtsc() -> Option<Duration> {
if !crate::arch::x86_64::has_cpuid() {
return None;
}

static FREQUENCY: crate::sync::OnceLock<u64> = crate::sync::OnceLock::new();

// Get Frequency in Mhz
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels surprising to me that frequency is a constant (i.e., only read from cpu id once).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On any modern x86 platform, the TSC is independent of frequency changes, and continues running in sleep states. The former has been true for longer than UEFI has existed; the latter for less time, but having it not hold true in UEFI firmware would require a UEFI platform that enters a CPU C-state (sleep state) while user code is running.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(There is also complexity associated with attempting to use it in an SMP system, since it may not be synchronzed across CPUs, but that's not typically an issue in UEFI.)

// Inspired by [`edk2/UefiCpuPkg/Library/CpuTimerLib/CpuTimerLib.c`](https://github.com/tianocore/edk2/blob/master/UefiCpuPkg/Library/CpuTimerLib/CpuTimerLib.c)
let freq = FREQUENCY
.get_or_try_init(|| {
let cpuid = unsafe { crate::arch::x86_64::__cpuid(0x15) };
if cpuid.eax == 0 || cpuid.ebx == 0 || cpuid.ecx == 0 {
return Err(());
}
Ok(mul_div_u64(cpuid.ecx as u64, cpuid.ebx as u64, cpuid.eax as u64))
})
.ok()?;

let ts = unsafe { crate::arch::x86_64::_rdtsc() };
let ns = mul_div_u64(ts, 1000, *freq);
Some(Duration::from_nanos(ns))
}

#[cfg(target_arch = "x86")]
fn timestamp_rdtsc() -> Option<Duration> {
if !crate::arch::x86::has_cpuid() {
return None;
}

static FREQUENCY: crate::sync::OnceLock<u64> = crate::sync::OnceLock::new();

let freq = FREQUENCY
.get_or_try_init(|| {
let cpuid = unsafe { crate::arch::x86::__cpuid(0x15) };
if cpuid.eax == 0 || cpuid.ebx == 0 || cpuid.ecx == 0 {
return Err(());
}
Ok(mul_div_u64(cpuid.ecx as u64, cpuid.ebx as u64, cpuid.eax as u64))
})
.ok()?;

let ts = unsafe { crate::arch::x86::_rdtsc() };
let ns = mul_div_u64(ts, 1000, *freq);
Some(Duration::from_nanos(ns))
}
}
Loading