2025-10-26 23:59:50 -04:00
|
|
|
use crate::entries::BootableEntry;
|
2026-09-26 19:31:50 -07:00
|
|
|
use crate::menu::basic::BasicMenu;
|
|
|
|
|
use crate::menu::simple::SimpleMenu;
|
2025-11-03 02:04:21 -05:00
|
|
|
use alloc::vec;
|
2026-09-26 18:12:21 -07:00
|
|
|
use anyhow::{Context, Result};
|
2025-11-03 02:04:21 -05:00
|
|
|
use core::time::Duration;
|
2026-09-26 19:31:50 -07:00
|
|
|
use edera_sprout_config::MenuStyle;
|
2025-11-03 20:47:21 -05:00
|
|
|
use eficore::bootloader_interface::BootloaderInterface;
|
|
|
|
|
use eficore::platform::timer::PlatformTimer;
|
2026-09-26 19:31:50 -07:00
|
|
|
use log::warn;
|
2025-10-26 23:59:50 -04:00
|
|
|
use uefi::ResultExt;
|
|
|
|
|
use uefi::boot::TimerTrigger;
|
2026-09-26 19:31:50 -07:00
|
|
|
use uefi::proto::console::text::Input;
|
2025-10-26 23:59:50 -04:00
|
|
|
use uefi_raw::table::boot::{EventType, Tpl};
|
|
|
|
|
|
2026-09-26 19:31:50 -07:00
|
|
|
/// basic: A menu that prints the entries and selects them by number.
|
|
|
|
|
pub mod basic;
|
2025-10-26 23:59:50 -04:00
|
|
|
|
2026-09-26 19:31:50 -07:00
|
|
|
/// simple: A full-screen menu that selects entries with the arrow keys.
|
|
|
|
|
pub mod simple;
|
|
|
|
|
|
2026-09-26 20:09:51 -07:00
|
|
|
/// The longest duration a single timer is set for. This is well within the range of
|
|
|
|
|
/// the 100ns units that timers are set in, and still over a century long.
|
|
|
|
|
const MAX_TIMER_DURATION: Duration = Duration::from_secs(u32::MAX as u64);
|
|
|
|
|
|
2026-09-26 19:31:50 -07:00
|
|
|
/// A boot menu that can be shown to select an entry to boot.
|
|
|
|
|
pub trait BootMenu {
|
|
|
|
|
/// Select an entry from `entries` to boot. If no entry is chosen before `timeout` passes,
|
|
|
|
|
/// the default entry is selected.
|
|
|
|
|
fn select<'a>(
|
|
|
|
|
&self,
|
|
|
|
|
timeout: Duration,
|
|
|
|
|
entries: &'a [BootableEntry],
|
|
|
|
|
) -> Result<&'a BootableEntry>;
|
2025-10-26 23:59:50 -04:00
|
|
|
}
|
|
|
|
|
|
2026-09-26 19:31:50 -07:00
|
|
|
/// Wait for the key event of `input` to be signaled, giving up once `timeout` passes.
|
|
|
|
|
/// Returns true if the key event was signaled, or false if the timeout passed.
|
|
|
|
|
/// Some firmware signals the key event without a key being available, so reading
|
|
|
|
|
/// the key afterward may still produce no key.
|
|
|
|
|
pub fn wait_for_key(input: &mut Input, timeout: Duration) -> Result<bool> {
|
2025-10-26 23:59:50 -04:00
|
|
|
// The event to wait for a key press.
|
|
|
|
|
let key_event = input
|
|
|
|
|
.wait_for_key_event()
|
|
|
|
|
.context("unable to acquire key event")?;
|
|
|
|
|
|
|
|
|
|
// Timer event for timeout.
|
|
|
|
|
// SAFETY: The timer event creation allocated a timer pointer on the UEFI heap.
|
|
|
|
|
// This is validated safe as long as we are in boot services.
|
|
|
|
|
let timer_event = unsafe {
|
|
|
|
|
uefi::boot::create_event_ex(EventType::TIMER, Tpl::CALLBACK, None, None, None)
|
|
|
|
|
.context("unable to create timer event")?
|
|
|
|
|
};
|
2025-10-27 23:10:05 -04:00
|
|
|
|
|
|
|
|
// Set a timer to trigger after the specified duration.
|
2026-09-26 20:09:51 -07:00
|
|
|
// The timer is limited to what the firmware can represent, as a longer timeout
|
|
|
|
|
// can't be converted into a timer trigger.
|
|
|
|
|
let trigger = TimerTrigger::Relative(timeout.min(MAX_TIMER_DURATION));
|
2025-10-26 23:59:50 -04:00
|
|
|
uefi::boot::set_timer(&timer_event, trigger).context("unable to set timeout timer")?;
|
|
|
|
|
|
2026-09-25 11:28:32 -07:00
|
|
|
let events = vec![timer_event, key_event];
|
2025-11-01 18:37:07 -04:00
|
|
|
|
|
|
|
|
// Wait for either the timer event or the key event to trigger.
|
|
|
|
|
// Store the result so that we can free the timer event.
|
2026-09-25 11:28:32 -07:00
|
|
|
let event_result = uefi::boot::wait_for_event(&events)
|
2025-10-26 23:59:50 -04:00
|
|
|
.discard_errdata()
|
2025-11-01 18:37:07 -04:00
|
|
|
.context("unable to wait for event");
|
2025-10-26 23:59:50 -04:00
|
|
|
|
2025-10-27 16:03:25 -04:00
|
|
|
// Close the timer event that we acquired.
|
2025-10-27 22:59:00 -04:00
|
|
|
// We don't need to close the key event because it is owned globally.
|
2025-11-03 02:35:01 -05:00
|
|
|
// This should always be called in practice as events are not modified by wait_for_event.
|
2025-10-27 16:03:25 -04:00
|
|
|
if let Some(timer_event) = events.into_iter().next() {
|
2025-11-01 18:58:24 -04:00
|
|
|
// Store the result of the close event so we can determine if we can safely assert it.
|
|
|
|
|
let close_event_result =
|
|
|
|
|
uefi::boot::close_event(timer_event).context("unable to close timer event");
|
|
|
|
|
if event_result.is_err()
|
|
|
|
|
&& let Err(ref close_event_error) = close_event_result
|
|
|
|
|
{
|
|
|
|
|
// Log a warning if we failed to close the timer event.
|
|
|
|
|
// This is done to ensure we don't mask the wait_for_event error.
|
|
|
|
|
warn!("unable to close timer event: {}", close_event_error);
|
|
|
|
|
} else {
|
|
|
|
|
// If we reach here, we can safely assert that the close event succeeded without
|
|
|
|
|
// masking the wait_for_event error.
|
|
|
|
|
close_event_result?;
|
|
|
|
|
}
|
2025-10-27 16:03:25 -04:00
|
|
|
}
|
|
|
|
|
|
2025-11-01 18:37:07 -04:00
|
|
|
// Acquire the event that triggered.
|
|
|
|
|
let event = event_result?;
|
|
|
|
|
|
2026-09-26 19:31:50 -07:00
|
|
|
// The first event is the timer event, so any other event is the key event.
|
|
|
|
|
Ok(event != 0)
|
2025-10-26 23:59:50 -04:00
|
|
|
}
|
|
|
|
|
|
2026-09-26 19:31:50 -07:00
|
|
|
/// Shows a boot menu of the specified `style` to select a bootable entry to boot.
|
2025-10-30 13:27:58 -04:00
|
|
|
pub fn select<'live>(
|
|
|
|
|
timer: &'live PlatformTimer,
|
|
|
|
|
timeout: Duration,
|
2026-09-26 19:31:50 -07:00
|
|
|
style: MenuStyle,
|
2025-10-30 13:27:58 -04:00
|
|
|
entries: &'live [BootableEntry],
|
|
|
|
|
) -> Result<&'live BootableEntry> {
|
|
|
|
|
// Notify the bootloader interface that we are about to display the menu.
|
|
|
|
|
BootloaderInterface::mark_menu(timer)
|
|
|
|
|
.context("unable to mark menu display in bootloader interface")?;
|
|
|
|
|
|
2026-09-26 19:31:50 -07:00
|
|
|
// Pick the menu that implements the requested style.
|
|
|
|
|
let menu: &dyn BootMenu = match style {
|
|
|
|
|
MenuStyle::Basic => &BasicMenu,
|
|
|
|
|
MenuStyle::Simple => &SimpleMenu,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
menu.select(timeout, entries)
|
2025-10-26 23:59:50 -04:00
|
|
|
}
|