Merge pull request #29441 from evelikov/no-input-delay

sd-boot: add "menu-disabled" option to "timeout", disabling the 100ms input polling
This commit is contained in:
Luca Boccassi
2023-10-17 23:45:44 +01:00
committed by GitHub
7 changed files with 89 additions and 32 deletions
+6 -3
View File
@@ -31,11 +31,13 @@ variables. All EFI variables use the vendor UUID
* The EFI variable `LoaderConfigTimeout` contains the boot menu timeout
currently in use. It may be modified both by the boot loader and by the
host. The value should be formatted as numeric, NUL-terminated, decimal
string, in UTF-16. The time is specified in seconds. A value of `menu-force`
string, in UTF-16. The time is specified in seconds. In addition some
non-numeric string values are also accepted. A value of `menu-force`
will disable the timeout and show the menu indefinitely. If set to `0` or
`menu-hidden` the default entry is booted immediately without showing a menu.
The boot loader should provide a way to interrupt this by for example
listening for key presses for a brief moment before booting.
Unless a value of `menu-disabled` is set, the boot loader should provide a
way to interrupt this by for example listening for key presses for a brief
moment before booting.
* Similarly, the EFI variable `LoaderConfigTimeoutOneShot` contains a boot menu
timeout for a single following boot. It is set by the OS in order to request
@@ -79,6 +81,7 @@ variables. All EFI variables use the vendor UUID
* `1 << 4` → The boot loader supports boot counting as described in [Automatic Boot Assessment](AUTOMATIC_BOOT_ASSESSMENT.md).
* `1 << 5` → The boot loader supports looking for boot menu entries in the Extended Boot Loader Partition.
* `1 << 6` → The boot loader supports passing a random seed to the OS.
* `1 << 13` → The boot loader honours `menu-disabled` option when set.
* The EFI variable `LoaderSystemToken` contains binary random data,
persistently set by the OS installer. Boot loaders that support passing
+5 -4
View File
@@ -177,10 +177,10 @@
<citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>
for details about the syntax of time spans.</para>
<para>If this is set to <option>menu-hidden</option> or <option>0</option> no menu is shown and
the default entry will be booted immediately, while setting this to <option>menu-force</option>
disables the timeout while always showing the menu. When an empty string ("") is specified the
bootloader will revert to its default menu timeout.</para>
<para>If this is set to <option>menu-disabled</option> or <option>menu-hidden</option> or
<option>0</option>, no menu is shown and the default entry will be booted immediately, while
setting this to <option>menu-force</option> disables the timeout while always showing the menu.
When an empty string ("") is specified the bootloader will revert to its default menu timeout.</para>
<xi:include href="version-info.xml" xpointer="v250"/></listitem>
</varlistentry>
@@ -555,6 +555,7 @@ Current Boot Loader: ← details about sd-boot or anothe
✓ Support for passing random seed to OS
✓ Load drop-in drivers
✓ Boot loader sets ESP information
✓ Menu can be disabled
ESP: /dev/disk/by-partuuid/01234567-89ab-cdef-dead-beef00000000
File: └─/EFI/systemd/systemd-bootx64.efi
+3 -2
View File
@@ -138,8 +138,9 @@
will be stored as an EFI variable in that case, overriding this option.
</para>
<para>If set to <literal>menu-hidden</literal> or <literal>0</literal> (the default) no menu
is shown and the default entry will be booted immediately. The menu can be shown
<para>If set to <literal>menu-disabled</literal> or <literal>menu-hidden</literal> or <literal>0</literal>
(the default), no menu is shown and the default entry will be booted immediately. Unless
<literal>menu-disabled</literal> is used, the menu can be shown
by pressing and holding a key before systemd-boot is launched. Setting this to
<literal>menu-force</literal> disables the timeout while always showing the menu.</para>
+24 -2
View File
@@ -6,6 +6,7 @@
#include "bootctl.h"
#include "bootctl-set-efivar.h"
#include "efivars.h"
#include "efi-loader.h"
#include "stdio-util.h"
#include "utf8.h"
#include "virt.h"
@@ -14,17 +15,35 @@ static int parse_timeout(const char *arg1, char16_t **ret_timeout, size_t *ret_t
char utf8[DECIMAL_STR_MAX(usec_t)];
char16_t *encoded;
usec_t timeout;
bool menu_disabled = false;
int r;
assert(arg1);
assert(ret_timeout);
assert(ret_timeout_size);
assert_cc(STRLEN("menu-disabled") < ELEMENTSOF(utf8));
/* Note: Since there is no way to query if the booloader supports the string tokens, we explicitly
* set their numerical value(s) instead. This means that some of the sd-boot internal ABI has leaked
* although the ship has sailed and the side-effects are self-contained.
*/
if (streq(arg1, "menu-force"))
timeout = USEC_INFINITY;
else if (streq(arg1, "menu-hidden"))
timeout = 0;
else {
else if (streq(arg1, "menu-disabled")) {
uint64_t loader_features = 0;
(void) efi_loader_get_features(&loader_features);
if (!(loader_features & EFI_LOADER_FEATURE_MENU_DISABLE)) {
if (!arg_graceful)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Loader does not support 'menu-disabled': %m");
log_warning("Loader does not support 'menu-disabled', setting anyway.");
}
menu_disabled = true;
} else {
r = parse_time(arg1, &timeout, USEC_PER_SEC);
if (r < 0)
return log_error_errno(r, "Failed to parse timeout '%s': %m", arg1);
@@ -32,7 +51,10 @@ static int parse_timeout(const char *arg1, char16_t **ret_timeout, size_t *ret_t
log_warning("Timeout is too long and will be treated as 'menu-force' instead.");
}
xsprintf(utf8, USEC_FMT, MIN(timeout / USEC_PER_SEC, UINT32_MAX));
if (menu_disabled)
xsprintf(utf8, "menu-disabled");
else
xsprintf(utf8, USEC_FMT, MIN(timeout / USEC_PER_SEC, UINT32_MAX));
encoded = utf8_to_utf16(utf8, SIZE_MAX);
if (!encoded)
+1
View File
@@ -372,6 +372,7 @@ int verb_status(int argc, char *argv[], void *userdata) {
{ EFI_LOADER_FEATURE_DEVICETREE, "Support Type #1 devicetree field" },
{ EFI_LOADER_FEATURE_SECUREBOOT_ENROLL, "Enroll SecureBoot keys" },
{ EFI_LOADER_FEATURE_RETAIN_SHIM, "Retain SHIM protocols" },
{ EFI_LOADER_FEATURE_MENU_DISABLE, "Menu can be disabled" },
};
static const struct {
uint64_t flag;
+49 -21
View File
@@ -79,9 +79,9 @@ typedef struct {
size_t n_entries;
size_t idx_default;
size_t idx_default_efivar;
uint32_t timeout_sec; /* Actual timeout used (efi_main() override > efivar > config). */
uint32_t timeout_sec_config;
uint32_t timeout_sec_efivar;
uint64_t timeout_sec; /* Actual timeout used (efi_main() override > efivar > config). */
uint64_t timeout_sec_config;
uint64_t timeout_sec_efivar;
char16_t *entry_default_config;
char16_t *entry_default_efivar;
char16_t *entry_oneshot;
@@ -101,16 +101,27 @@ typedef struct {
int64_t console_mode_efivar;
} Config;
/* These values have been chosen so that the transitions the user sees could
* employ unsigned over-/underflow like this:
* efivar unset ↔ force menu ↔ no timeout/skip menu ↔ 1 s ↔ 2 s ↔ … */
/* These values have been chosen so that the transitions the user sees could employ unsigned over-/underflow
* like this:
* efivar unset ↔ force menu ↔ no timeout/skip menu ↔ 1 s ↔ 2 s ↔ …
*
* Note: all the values below are ABI, so they are not allowed to change. The bootctl tool sets the numerical
* value of TIMEOUT_MENU_FORCE and TIMEOUT_MENU_HIDDEN, instead of the string for compatibility reasons.
*
* The other values may be set by systemd-boot itself and changing those will lead to functional regression
* when new version of systemd-boot is installed.
*
* All the 64bit values are not ABI and will never be written to an efi variable.
*/
enum {
TIMEOUT_MIN = 1,
TIMEOUT_MAX = UINT32_MAX - 2U,
TIMEOUT_UNSET = UINT32_MAX - 1U,
TIMEOUT_MENU_FORCE = UINT32_MAX,
TIMEOUT_MENU_HIDDEN = 0,
TIMEOUT_TYPE_MAX = UINT32_MAX,
TIMEOUT_MIN = 1,
TIMEOUT_MAX = UINT32_MAX - 2U,
TIMEOUT_UNSET = UINT32_MAX - 1U,
TIMEOUT_MENU_FORCE = UINT32_MAX,
TIMEOUT_MENU_HIDDEN = 0,
TIMEOUT_TYPE_MAX = UINT32_MAX,
TIMEOUT_MENU_DISABLED = (uint64_t)UINT32_MAX + 1U,
TIMEOUT_TYPE_MAX64 = UINT64_MAX,
};
enum {
@@ -403,6 +414,9 @@ static char16_t* update_timeout_efivar(Config *config, bool inc) {
case TIMEOUT_UNSET:
config->timeout_sec = inc ? TIMEOUT_MENU_FORCE : TIMEOUT_UNSET;
break;
case TIMEOUT_MENU_DISABLED:
config->timeout_sec = inc ? TIMEOUT_MIN : TIMEOUT_MENU_FORCE;
break;
case TIMEOUT_MENU_FORCE:
config->timeout_sec = inc ? TIMEOUT_MENU_HIDDEN : TIMEOUT_MENU_FORCE;
break;
@@ -418,12 +432,14 @@ static char16_t* update_timeout_efivar(Config *config, bool inc) {
switch (config->timeout_sec) {
case TIMEOUT_UNSET:
return xstrdup16(u"Menu timeout defined by configuration file.");
case TIMEOUT_MENU_DISABLED:
assert_not_reached();
case TIMEOUT_MENU_FORCE:
return xstrdup16(u"Timeout disabled, menu will always be shown.");
case TIMEOUT_MENU_HIDDEN:
return xstrdup16(u"Menu disabled. Hold down key at bootup to show menu.");
return xstrdup16(u"Menu hidden. Hold down key at bootup to show menu.");
default:
return xasprintf("Menu timeout set to %u s.", config->timeout_sec_efivar);
return xasprintf("Menu timeout set to %u s.", (uint32_t)config->timeout_sec_efivar);
}
}
@@ -447,16 +463,18 @@ static bool ps_continue(void) {
!IN_SET(key, KEYPRESS(0, SCAN_ESC, 0), KEYPRESS(0, 0, 'q'), KEYPRESS(0, 0, 'Q'));
}
static void print_timeout_status(const char *label, uint32_t t) {
static void print_timeout_status(const char *label, uint64_t t) {
switch (t) {
case TIMEOUT_UNSET:
return;
case TIMEOUT_MENU_DISABLED:
return (void) printf("%s: menu-disabled\n", label);
case TIMEOUT_MENU_FORCE:
return (void) printf("%s: menu-force\n", label);
case TIMEOUT_MENU_HIDDEN:
return (void) printf("%s: menu-hidden\n", label);
default:
return (void) printf("%s: %u s\n", label, t);
return (void) printf("%s: %u s\n", label, (uint32_t)t);
}
}
@@ -651,7 +669,7 @@ static bool menu_run(
size_t x_start = 0, y_start = 0, y_status = 0, x_max, y_max;
_cleanup_(strv_freep) char16_t **lines = NULL;
_cleanup_free_ char16_t *clearline = NULL, *separator = NULL, *status = NULL;
uint32_t timeout_efivar_saved = config->timeout_sec_efivar;
uint64_t timeout_efivar_saved = config->timeout_sec_efivar;
uint32_t timeout_remain = config->timeout_sec == TIMEOUT_MENU_FORCE ? 0 : config->timeout_sec;
int64_t console_mode_initial = ST->ConOut->Mode->Mode, console_mode_efivar_saved = config->console_mode_efivar;
size_t default_efivar_saved = config->idx_default_efivar;
@@ -1097,6 +1115,8 @@ static bool menu_run(
case TIMEOUT_UNSET:
efivar_unset(MAKE_GUID_PTR(LOADER), u"LoaderConfigTimeout", EFI_VARIABLE_NON_VOLATILE);
break;
case TIMEOUT_MENU_DISABLED:
assert_not_reached();
case TIMEOUT_MENU_FORCE:
efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderConfigTimeout", u"menu-force", EFI_VARIABLE_NON_VOLATILE);
break;
@@ -1104,6 +1124,7 @@ static bool menu_run(
efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderConfigTimeout", u"menu-hidden", EFI_VARIABLE_NON_VOLATILE);
break;
default:
assert(config->timeout_sec_efivar < UINT32_MAX);
efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"LoaderConfigTimeout",
config->timeout_sec_efivar, EFI_VARIABLE_NON_VOLATILE);
}
@@ -1176,7 +1197,9 @@ static void config_defaults_load_from_file(Config *config, char *content) {
while ((line = line_get_key_value(content, " \t", &pos, &key, &value)))
if (streq8(key, "timeout")) {
if (streq8( value, "menu-force"))
if (streq8(value, "menu-disabled"))
config->timeout_sec_config = TIMEOUT_MENU_DISABLED;
else if (streq8(value, "menu-force"))
config->timeout_sec_config = TIMEOUT_MENU_FORCE;
else if (streq8(value, "menu-hidden"))
config->timeout_sec_config = TIMEOUT_MENU_HIDDEN;
@@ -1492,7 +1515,7 @@ static void boot_entry_add_type1(
TAKE_PTR(entry);
}
static EFI_STATUS efivar_get_timeout(const char16_t *var, uint32_t *ret_value) {
static EFI_STATUS efivar_get_timeout(const char16_t *var, uint64_t *ret_value) {
_cleanup_free_ char16_t *value = NULL;
EFI_STATUS err;
@@ -1503,6 +1526,10 @@ static EFI_STATUS efivar_get_timeout(const char16_t *var, uint32_t *ret_value) {
if (err != EFI_SUCCESS)
return err;
if (streq16(value, u"menu-disabled")) {
*ret_value = TIMEOUT_MENU_DISABLED;
return EFI_SUCCESS;
}
if (streq16(value, u"menu-force")) {
*ret_value = TIMEOUT_MENU_FORCE;
return EFI_SUCCESS;
@@ -2503,6 +2530,7 @@ static void export_variables(
EFI_LOADER_FEATURE_DEVICETREE |
EFI_LOADER_FEATURE_SECUREBOOT_ENROLL |
EFI_LOADER_FEATURE_RETAIN_SHIM |
EFI_LOADER_FEATURE_MENU_DISABLE |
0;
_cleanup_free_ char16_t *infostr = NULL, *typestr = NULL;
@@ -2659,9 +2687,9 @@ static EFI_STATUS run(EFI_HANDLE image) {
"No loader found. Configuration files in \\loader\\entries\\*.conf are needed.");
/* select entry or show menu when key is pressed or timeout is set */
if (config.force_menu || config.timeout_sec > 0)
if (config.force_menu || !IN_SET(config.timeout_sec, TIMEOUT_MENU_HIDDEN, TIMEOUT_MENU_DISABLED))
menu = true;
else {
else if (config.timeout_sec != TIMEOUT_MENU_DISABLED) {
uint64_t key;
/* Block up to 100ms to give firmware time to get input working. */
+1
View File
@@ -22,6 +22,7 @@
#define EFI_LOADER_FEATURE_DEVICETREE (UINT64_C(1) << 10)
#define EFI_LOADER_FEATURE_SECUREBOOT_ENROLL (UINT64_C(1) << 11)
#define EFI_LOADER_FEATURE_RETAIN_SHIM (UINT64_C(1) << 12)
#define EFI_LOADER_FEATURE_MENU_DISABLE (UINT64_C(1) << 13)
/* Features of the stub, i.e. systemd-stub */
#define EFI_STUB_FEATURE_REPORT_BOOT_PARTITION (UINT64_C(1) << 0)