xref: /aosp_15_r20/bootable/libbootloader/gbl/libefi/src/utils.rs (revision 5225e6b173e52d2efc6bcf950c27374fd72adabc)
1 // Copyright 2024, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! This file provides some utilities built on EFI APIs.
16 
17 use crate::{EfiEntry, Event, EventType};
18 use core::future::Future;
19 use efi_types::EFI_TIMER_DELAY_TIMER_RELATIVE;
20 use gbl_async::{select, yield_now};
21 use liberror::Result;
22 use safemath::SafeNum;
23 
24 /// Converts 1 ms to number of 100 nano seconds
ms_to_100ns(ms: u64) -> Result<u64>25 pub fn ms_to_100ns(ms: u64) -> Result<u64> {
26     Ok((SafeNum::from(ms) * 10 * 1000).try_into()?)
27 }
28 
29 /// `Timeout` provide APIs for checking timeout.
30 pub struct Timeout<'a> {
31     efi_entry: &'a EfiEntry,
32     timer: Event<'a, 'static>,
33 }
34 
35 impl<'a> Timeout<'a> {
36     /// Creates a new instance and starts the timeout timer.
new(efi_entry: &'a EfiEntry, timeout_ms: u64) -> Result<Self>37     pub fn new(efi_entry: &'a EfiEntry, timeout_ms: u64) -> Result<Self> {
38         let bs = efi_entry.system_table().boot_services();
39         let timer = bs.create_event(EventType::Timer)?;
40         bs.set_timer(&timer, EFI_TIMER_DELAY_TIMER_RELATIVE, ms_to_100ns(timeout_ms)?)?;
41         Ok(Self { efi_entry, timer })
42     }
43 
44     /// Checks if it has timeout.
check(&self) -> Result<bool>45     pub fn check(&self) -> Result<bool> {
46         Ok(self.efi_entry.system_table().boot_services().check_event(&self.timer)?)
47     }
48 
49     /// Resets the timeout.
reset(&self, timeout_ms: u64) -> Result<()>50     pub fn reset(&self, timeout_ms: u64) -> Result<()> {
51         let bs = self.efi_entry.system_table().boot_services();
52         bs.set_timer(&self.timer, EFI_TIMER_DELAY_TIMER_RELATIVE, ms_to_100ns(timeout_ms)?)?;
53         Ok(())
54     }
55 }
56 
57 /// Waits for a given amount of time.
wait(efi_entry: &EfiEntry, duration_ms: u64) -> Result<()>58 pub async fn wait(efi_entry: &EfiEntry, duration_ms: u64) -> Result<()> {
59     // EFI boot service has a `stall` API. But it's not async.
60     let timeout = Timeout::new(efi_entry, duration_ms)?;
61     while !timeout.check()? {
62         yield_now().await;
63     }
64     Ok(())
65 }
66 
67 /// Runs a future with timeout.
68 ///
69 /// # Returns
70 ///
71 /// * Returns Ok(Some(R)) if the future finishes before timeout.
72 /// * Returns Ok(None) if the future didn't finish before timeout.
73 /// * Returns Err if internal error occurs while handling EFI timer event.
with_timeout<F: Future<Output = R>, R>( efi_entry: &EfiEntry, fut: F, timeout_ms: u64, ) -> Result<Option<R>>74 pub async fn with_timeout<F: Future<Output = R>, R>(
75     efi_entry: &EfiEntry,
76     fut: F,
77     timeout_ms: u64,
78 ) -> Result<Option<R>> {
79     let (timeout_res, res) = select(wait(efi_entry, timeout_ms), fut).await;
80     match timeout_res {
81         Some(Err(e)) => return Err(e),
82         _ => Ok(res),
83     }
84 }
85