blob: 5a7eab9031ecf55bf1fc8e2cc784229265685ab8 [file] [log] [blame] [edit]
use crate::sys::pal::system_time;
use crate::time::Duration;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct Instant(Duration);
/// When a Timezone is specified, the stored Duration is in UTC. If timezone is unspecified, then
/// the timezone is assumed to be in UTC.
///
/// UEFI SystemTime is stored as Duration from 1900-01-01-00:00:00 with timezone -1440 as anchor
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct SystemTime(Duration);
pub const UNIX_EPOCH: SystemTime = SystemTime::from_uefi(r_efi::efi::Time {
year: 1970,
month: 1,
day: 1,
hour: 0,
minute: 0,
second: 0,
nanosecond: 0,
timezone: 0,
daylight: 0,
pad1: 0,
pad2: 0,
})
.unwrap();
const MAX_UEFI_TIME: SystemTime = SystemTime::from_uefi(r_efi::efi::Time {
year: 9999,
month: 12,
day: 31,
hour: 23,
minute: 59,
second: 59,
nanosecond: 999_999_999,
timezone: 1440,
daylight: 0,
pad1: 0,
pad2: 0,
})
.unwrap();
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")
}
pub fn checked_sub_instant(&self, other: &Instant) -> Option<Duration> {
self.0.checked_sub(other.0)
}
pub fn checked_add_duration(&self, other: &Duration) -> Option<Instant> {
Some(Instant(self.0.checked_add(*other)?))
}
pub fn checked_sub_duration(&self, other: &Duration) -> Option<Instant> {
Some(Instant(self.0.checked_sub(*other)?))
}
}
impl SystemTime {
pub const MAX: SystemTime = MAX_UEFI_TIME;
pub const MIN: SystemTime = SystemTime::from_uefi(r_efi::efi::Time {
year: 1900,
month: 1,
day: 1,
hour: 0,
minute: 0,
second: 0,
nanosecond: 0,
timezone: -1440,
daylight: 0,
pad1: 0,
pad2: 0,
})
.unwrap();
pub(crate) const fn from_uefi(t: r_efi::efi::Time) -> Option<Self> {
match system_time::from_uefi(&t) {
Some(x) => Some(Self(x)),
None => None,
}
}
pub(crate) const fn to_uefi(
self,
timezone: i16,
daylight: u8,
) -> Result<r_efi::efi::Time, i16> {
// system_time::to_uefi requires a valid timezone. In case of unspecified timezone,
// we just pass 0 since it is assumed that no timezone related adjustments are required.
if timezone == r_efi::efi::UNSPECIFIED_TIMEZONE {
system_time::to_uefi(&self.0, 0, daylight)
} else {
system_time::to_uefi(&self.0, timezone, daylight)
}
}
/// Create UEFI Time with the closest timezone (minute offset) that still allows the time to be
/// represented.
pub(crate) fn to_uefi_loose(self, timezone: i16, daylight: u8) -> r_efi::efi::Time {
match self.to_uefi(timezone, daylight) {
Ok(x) => x,
Err(tz) => self.to_uefi(tz, daylight).unwrap(),
}
}
pub fn now() -> SystemTime {
Self::from_uefi(system_time::now()).expect("time incorrectly implemented on this platform")
}
pub fn sub_time(&self, other: &SystemTime) -> Result<Duration, Duration> {
self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0)
}
pub fn checked_add_duration(&self, other: &Duration) -> Option<SystemTime> {
let temp = Self(self.0.checked_add(*other)?);
// Check if can be represented in UEFI
if temp <= MAX_UEFI_TIME { Some(temp) } else { None }
}
pub fn checked_sub_duration(&self, other: &Duration) -> Option<SystemTime> {
self.0.checked_sub(*other).map(Self)
}
}
mod instant_internal {
use r_efi::protocols::timestamp;
use super::*;
use crate::mem::MaybeUninit;
use crate::ptr::NonNull;
use crate::sync::atomic::{Atomic, AtomicPtr, Ordering};
use crate::sys::helpers::mul_div_u64;
use crate::sys::pal::helpers;
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: Atomic<*mut 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_select! {
any(target_arch = "x86_64", target_arch = "x86") => timestamp_rdtsc().map(Instant),
_ => None,
}
}
#[cfg(target_arch = "x86_64")]
fn timestamp_rdtsc() -> Option<Duration> {
static FREQUENCY: crate::sync::OnceLock<u64> = crate::sync::OnceLock::new();
// Get Frequency in Mhz
// 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 = 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> {
static FREQUENCY: crate::sync::OnceLock<u64> = crate::sync::OnceLock::new();
let freq = FREQUENCY
.get_or_try_init(|| {
let cpuid = 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))
}
}