mirror of
https://github.com/edera-dev/sprout.git
synced 2026-11-04 19:52:22 +00:00
fix(menu): keep the countdown going after a spurious key event
Some firmware signals the key event without a key being available. The simple menu then skipped the countdown for that tick, and the basic menu started waiting for the full timeout again, so firmware that kept signaling the key event would never boot the default entry. Read the key inside the wait, and when no key is available keep waiting on the same timer.
This commit is contained in:
1 parent
11736ccabb
commit
78bcaa79ff
3 files changed
+34
-37
No files matched your search
+30
-20
@@ -10,7 +10,7 @@ use eficore::platform::timer::PlatformTimer;
|
|||||||
use log::warn;
|
use log::warn;
|
||||||
use uefi::ResultExt;
|
use uefi::ResultExt;
|
||||||
use uefi::boot::TimerTrigger;
|
use uefi::boot::TimerTrigger;
|
||||||
use uefi::proto::console::text::Input;
|
use uefi::proto::console::text::{Input, Key};
|
||||||
use uefi_raw::table::boot::{EventType, Tpl};
|
use uefi_raw::table::boot::{EventType, Tpl};
|
||||||
|
|
||||||
/// basic: A menu that prints the entries and selects them by number.
|
/// basic: A menu that prints the entries and selects them by number.
|
||||||
@@ -34,11 +34,9 @@ pub trait BootMenu {
|
|||||||
) -> Result<&'a BootableEntry>;
|
) -> Result<&'a BootableEntry>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait for the key event of `input` to be signaled, giving up once `timeout` passes.
|
/// Read a key from `input`, giving up once `timeout` passes.
|
||||||
/// Returns true if the key event was signaled, or false if the timeout passed.
|
/// Returns the key that was pressed, or [None] if the timeout passed.
|
||||||
/// Some firmware signals the key event without a key being available, so reading
|
pub fn read_key(input: &mut Input, timeout: Duration) -> Result<Option<Key>> {
|
||||||
/// the key afterward may still produce no key.
|
|
||||||
pub fn wait_for_key(input: &mut Input, timeout: Duration) -> Result<bool> {
|
|
||||||
// The event to wait for a key press.
|
// The event to wait for a key press.
|
||||||
let key_event = input
|
let key_event = input
|
||||||
.wait_for_key_event()
|
.wait_for_key_event()
|
||||||
@@ -52,19 +50,35 @@ pub fn wait_for_key(input: &mut Input, timeout: Duration) -> Result<bool> {
|
|||||||
.context("unable to create timer event")?
|
.context("unable to create timer event")?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let events = vec![timer_event, key_event];
|
||||||
|
|
||||||
|
// Wait for a key until the timer triggers.
|
||||||
|
// Store the result so that we can free the timer event.
|
||||||
|
let result = (|| {
|
||||||
// Set a timer to trigger after the specified duration.
|
// Set a timer to trigger after the specified duration.
|
||||||
// The timer is limited to what the firmware can represent, as a longer timeout
|
// The timer is limited to what the firmware can represent, as a longer timeout
|
||||||
// can't be converted into a timer trigger.
|
// can't be converted into a timer trigger.
|
||||||
let trigger = TimerTrigger::Relative(timeout.min(MAX_TIMER_DURATION));
|
let trigger = TimerTrigger::Relative(timeout.min(MAX_TIMER_DURATION));
|
||||||
uefi::boot::set_timer(&timer_event, trigger).context("unable to set timeout timer")?;
|
uefi::boot::set_timer(&events[0], trigger).context("unable to set timeout timer")?;
|
||||||
|
|
||||||
let events = vec![timer_event, key_event];
|
|
||||||
|
|
||||||
|
loop {
|
||||||
// Wait for either the timer event or the key event to trigger.
|
// Wait for either the timer event or the key event to trigger.
|
||||||
// Store the result so that we can free the timer event.
|
let event = uefi::boot::wait_for_event(&events)
|
||||||
let event_result = uefi::boot::wait_for_event(&events)
|
|
||||||
.discard_errdata()
|
.discard_errdata()
|
||||||
.context("unable to wait for event");
|
.context("unable to wait for event")?;
|
||||||
|
|
||||||
|
// The first event is the timer event, so the timeout has passed.
|
||||||
|
if event == 0 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some firmware signals the key event without a key being available.
|
||||||
|
// In that case, keep waiting on the same timer so the timeout is not extended.
|
||||||
|
if let Some(key) = input.read_key().context("unable to read key")? {
|
||||||
|
return Ok(Some(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
// Close the timer event that we acquired.
|
// Close the timer event that we acquired.
|
||||||
// We don't need to close the key event because it is owned globally.
|
// We don't need to close the key event because it is owned globally.
|
||||||
@@ -73,24 +87,20 @@ pub fn wait_for_key(input: &mut Input, timeout: Duration) -> Result<bool> {
|
|||||||
// Store the result of the close event so we can determine if we can safely assert it.
|
// Store the result of the close event so we can determine if we can safely assert it.
|
||||||
let close_event_result =
|
let close_event_result =
|
||||||
uefi::boot::close_event(timer_event).context("unable to close timer event");
|
uefi::boot::close_event(timer_event).context("unable to close timer event");
|
||||||
if event_result.is_err()
|
if result.is_err()
|
||||||
&& let Err(ref close_event_error) = close_event_result
|
&& let Err(ref close_event_error) = close_event_result
|
||||||
{
|
{
|
||||||
// Log a warning if we failed to close the timer event.
|
// 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.
|
// This is done to ensure we don't mask the error from reading the key.
|
||||||
warn!("unable to close timer event: {}", close_event_error);
|
warn!("unable to close timer event: {}", close_event_error);
|
||||||
} else {
|
} else {
|
||||||
// If we reach here, we can safely assert that the close event succeeded without
|
// If we reach here, we can safely assert that the close event succeeded without
|
||||||
// masking the wait_for_event error.
|
// masking the error from reading the key.
|
||||||
close_event_result?;
|
close_event_result?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Acquire the event that triggered.
|
result
|
||||||
let event = event_result?;
|
|
||||||
|
|
||||||
// The first event is the timer event, so any other event is the key event.
|
|
||||||
Ok(event != 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shows a boot menu of the specified `style` to select a bootable entry to boot.
|
/// Shows a boot menu of the specified `style` to select a bootable entry to boot.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::entries::BootableEntry;
|
use crate::entries::BootableEntry;
|
||||||
use crate::menu::{BootMenu, wait_for_key};
|
use crate::menu::{BootMenu, read_key};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use core::time::Duration;
|
use core::time::Duration;
|
||||||
use log::info;
|
use log::info;
|
||||||
@@ -30,15 +30,8 @@ enum MenuOperation {
|
|||||||
/// performed.
|
/// performed.
|
||||||
fn read(input: &mut Input, timeout: &Duration) -> Result<MenuOperation> {
|
fn read(input: &mut Input, timeout: &Duration) -> Result<MenuOperation> {
|
||||||
// If the timer triggered, the user did not select a numbered entry.
|
// If the timer triggered, the user did not select a numbered entry.
|
||||||
if !wait_for_key(input, *timeout)? {
|
let Some(key) = read_key(input, *timeout)? else {
|
||||||
return Ok(MenuOperation::Timeout);
|
return Ok(MenuOperation::Timeout);
|
||||||
}
|
|
||||||
|
|
||||||
// If we reach here, there is a key event.
|
|
||||||
// Some firmware signals the key event without a key being available,
|
|
||||||
// in which case there is nothing to do.
|
|
||||||
let Some(key) = input.read_key().context("unable to read key")? else {
|
|
||||||
return Ok(MenuOperation::Nop);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match key {
|
match key {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::entries::BootableEntry;
|
use crate::entries::BootableEntry;
|
||||||
use crate::menu::{BootMenu, wait_for_key};
|
use crate::menu::{BootMenu, read_key};
|
||||||
use alloc::format;
|
use alloc::format;
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
use anyhow::{Context, Result, anyhow};
|
use anyhow::{Context, Result, anyhow};
|
||||||
@@ -193,7 +193,7 @@ fn run(
|
|||||||
|
|
||||||
// Wait for a key, waking up every tick to update the countdown.
|
// Wait for a key, waking up every tick to update the countdown.
|
||||||
let tick = remaining.map_or(TICK, |remaining| remaining.min(TICK));
|
let tick = remaining.map_or(TICK, |remaining| remaining.min(TICK));
|
||||||
if !wait_for_key(input, tick)? {
|
let Some(key) = read_key(input, tick)? else {
|
||||||
// Without a countdown there's nothing to do until a key is pressed.
|
// Without a countdown there's nothing to do until a key is pressed.
|
||||||
let Some(left) = remaining else {
|
let Some(left) = remaining else {
|
||||||
continue;
|
continue;
|
||||||
@@ -205,12 +205,6 @@ fn run(
|
|||||||
}
|
}
|
||||||
remaining = Some(left);
|
remaining = Some(left);
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
// Some firmware signals the key event without a key being available,
|
|
||||||
// in which case there is nothing to do.
|
|
||||||
let Some(key) = input.read_key().context("unable to read key")? else {
|
|
||||||
continue;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Any key stops the countdown, which leaves escape with nothing else to do.
|
// Any key stops the countdown, which leaves escape with nothing else to do.
|
||||||
|
|||||||
Reference in new issue
Block a user