blob: 5cf00f325b6acb275d3778cc7afb636cd36b64d3 [file]
// Copyright 2024, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! This file provides some utilities built on EFI APIs.
use crate::{EfiEntry, Event, EventType};
use core::{future::Future, time::Duration};
use efi_types::EFI_TIMER_DELAY_TIMER_RELATIVE;
use gbl_async::{select, yield_now};
use liberror::Result;
use safemath::SafeNum;
/// Converts 1 ms to number of 100 nano seconds
pub fn ms_to_100ns(ms: u64) -> Result<u64> {
Ok((SafeNum::from(ms) * 10 * 1000).try_into()?)
}
/// `Timeout` provide APIs for checking timeout.
pub struct Timeout<'a> {
efi_entry: &'a EfiEntry,
timer: Event<'a, 'static>,
}
impl<'a> Timeout<'a> {
/// Creates a new instance and starts the timeout timer.
pub fn new(efi_entry: &'a EfiEntry, timeout: Duration) -> Result<Self> {
let bs = efi_entry.system_table().boot_services();
let timer = bs.create_event(EventType::Timer)?;
bs.set_timer(&timer, EFI_TIMER_DELAY_TIMER_RELATIVE, timeout)?;
Ok(Self { efi_entry, timer })
}
/// Checks if it has timeout.
pub fn check(&self) -> Result<bool> {
Ok(self.efi_entry.system_table().boot_services().check_event(&self.timer)?)
}
/// Resets the timeout.
pub fn reset(&self, timeout: Duration) -> Result<()> {
let bs = self.efi_entry.system_table().boot_services();
bs.set_timer(&self.timer, EFI_TIMER_DELAY_TIMER_RELATIVE, timeout)?;
Ok(())
}
}
/// Waits for a given amount of time.
pub async fn wait(efi_entry: &EfiEntry, duration: Duration) -> Result<()> {
// EFI boot service has a `stall` API. But it's not async.
let timeout = Timeout::new(efi_entry, duration)?;
while !timeout.check()? {
yield_now().await;
}
Ok(())
}
/// Runs a future with timeout.
///
/// # Returns
///
/// * Returns Ok(Some(R)) if the future finishes before timeout.
/// * Returns Ok(None) if the future didn't finish before timeout.
/// * Returns Err if internal error occurs while handling EFI timer event.
pub async fn with_timeout<F: Future<Output = R>, R>(
efi_entry: &EfiEntry,
fut: F,
timeout: Duration,
) -> Result<Option<R>> {
let (timeout_res, res) = select(wait(efi_entry, timeout), fut).await;
match timeout_res {
Some(Err(e)) => return Err(e),
_ => Ok(res),
}
}