|
1 |
| -//! Time is implemeted using `EFI_RUNTIME_SERVICES.GetTime()` |
2 |
| -//! While this is not technically monotonic, the single-threaded nature of UEFI might make it fine |
3 |
| -//! to use for Instant. Still, maybe revisit this in future. |
4 |
| -
|
| 1 | +use crate::mem::MaybeUninit; |
5 | 2 | use crate::os::uefi;
|
| 3 | +use crate::ptr::NonNull; |
| 4 | +use crate::sys_common::mul_div_u64; |
6 | 5 | use crate::time::Duration;
|
7 | 6 |
|
| 7 | +use r_efi::protocols::timestamp; |
| 8 | + |
8 | 9 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
|
9 | 10 | pub struct Instant(Duration);
|
10 | 11 |
|
11 | 12 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
|
12 | 13 | pub struct SystemTime(Duration);
|
13 | 14 |
|
14 | 15 | pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::ZERO);
|
| 16 | +const NS_PER_SEC: u64 = 1_000_000_000; |
15 | 17 |
|
16 | 18 | impl Instant {
|
17 | 19 | pub fn now() -> Instant {
|
| 20 | + if let Ok(handles) = crate::os::uefi::env::locate_handles(timestamp::PROTOCOL_GUID) { |
| 21 | + // First try using `EFI_TIMESTAMP_PROTOCOL` if present |
| 22 | + for handle in handles { |
| 23 | + let protocol: NonNull<timestamp::Protocol> = |
| 24 | + match uefi::env::open_protocol(handle, timestamp::PROTOCOL_GUID) { |
| 25 | + Ok(x) => x, |
| 26 | + Err(_) => continue, |
| 27 | + }; |
| 28 | + let mut properties: MaybeUninit<timestamp::Properties> = MaybeUninit::uninit(); |
| 29 | + let r = unsafe { ((*protocol.as_ptr()).get_properties)(properties.as_mut_ptr()) }; |
| 30 | + if r.is_error() { |
| 31 | + continue; |
| 32 | + } else { |
| 33 | + let properties = unsafe { properties.assume_init() }; |
| 34 | + let ts = unsafe { ((*protocol.as_ptr()).get_timestamp)() }; |
| 35 | + let frequency = properties.frequency; |
| 36 | + let ns = mul_div_u64(ts, NS_PER_SEC, frequency); |
| 37 | + return Instant(Duration::from_nanos(ns)); |
| 38 | + } |
| 39 | + } |
| 40 | + } |
| 41 | + |
| 42 | + // Try using raw CPU Registers |
| 43 | + // Currently only implemeted for x86_64 using CPUID (0x15) and TSC register |
| 44 | + #[cfg(target_arch = "x86_64")] |
| 45 | + if let Some(ns) = get_timestamp() { |
| 46 | + return Instant(Duration::from_nanos(ns)); |
| 47 | + } |
| 48 | + |
18 | 49 | if let Some(runtime_services) = uefi::env::get_runtime_services() {
|
| 50 | + // Finally just use `EFI_RUNTIME_SERVICES.GetTime()` |
19 | 51 | let mut t = r_efi::efi::Time::default();
|
20 | 52 | let r =
|
21 | 53 | unsafe { ((*runtime_services.as_ptr()).get_time)(&mut t, crate::ptr::null_mut()) };
|
22 | 54 |
|
23 | 55 | if r.is_error() {
|
24 | 56 | panic!("time not implemented on this platform")
|
25 | 57 | } else {
|
26 |
| - Instant(uefi_time_to_duration(t)) |
| 58 | + return Instant(uefi_time_to_duration(t)); |
27 | 59 | }
|
28 |
| - } else { |
29 |
| - panic!("Runtime Services are needed for Time to work") |
30 | 60 | }
|
| 61 | + |
| 62 | + // Panic if nothing works |
| 63 | + panic!("Failed to create Instant") |
31 | 64 | }
|
32 | 65 |
|
33 | 66 | pub fn checked_sub_instant(&self, other: &Instant) -> Option<Duration> {
|
@@ -100,3 +133,43 @@ fn uefi_time_to_duration(t: r_efi::system::Time) -> Duration {
|
100 | 133 |
|
101 | 134 | Duration::new(utc_epoch, t.nanosecond)
|
102 | 135 | }
|
| 136 | + |
| 137 | +// Returns the Frequency in Mhz |
| 138 | +// Mostly based on [`edk2/UefiCpuPkg/Library/CpuTimerLib/CpuTimerLib.c`](https://github.com/tianocore/edk2/blob/master/UefiCpuPkg/Library/CpuTimerLib/CpuTimerLib.c) |
| 139 | +// Currently implemented only for x86_64 but can be extended for other arch if they ever support |
| 140 | +// std. |
| 141 | +#[cfg(target_arch = "x86_64")] |
| 142 | +fn frequency() -> Option<u64> { |
| 143 | + use crate::sync::atomic::{AtomicU64, Ordering}; |
| 144 | + |
| 145 | + static FREQUENCY: AtomicU64 = AtomicU64::new(0); |
| 146 | + |
| 147 | + let cached = FREQUENCY.load(Ordering::Relaxed); |
| 148 | + if cached != 0 { |
| 149 | + return Some(cached); |
| 150 | + } |
| 151 | + |
| 152 | + if crate::arch::x86_64::has_cpuid() { |
| 153 | + let cpuid = unsafe { crate::arch::x86_64::__cpuid(0x15) }; |
| 154 | + |
| 155 | + if cpuid.eax == 0 || cpuid.ebx == 0 || cpuid.ecx == 0 { |
| 156 | + return None; |
| 157 | + } |
| 158 | + |
| 159 | + let freq = mul_div_u64(cpuid.ecx as u64, cpuid.ebx as u64, cpuid.eax as u64); |
| 160 | + FREQUENCY.store(freq, Ordering::Relaxed); |
| 161 | + return Some(freq); |
| 162 | + } |
| 163 | + |
| 164 | + None |
| 165 | +} |
| 166 | + |
| 167 | +// Currently implemented only for x86_64 but can be extended for other arch if they ever support |
| 168 | +// std. |
| 169 | +#[cfg(target_arch = "x86_64")] |
| 170 | +fn get_timestamp() -> Option<u64> { |
| 171 | + let freq = frequency()?; |
| 172 | + let ts = unsafe { crate::arch::x86_64::_rdtsc() }; |
| 173 | + let ns = mul_div_u64(ts, 1000, freq); |
| 174 | + Some(ns) |
| 175 | +} |
0 commit comments