From 004e3e40825027f803225876b96617d6129766cb Mon Sep 17 00:00:00 2001 From: Igor Opaniuk Date: Mon, 24 Mar 2025 15:30:49 +0100 Subject: [PATCH 1/3] sd-boot: add support for a sysfail entry Add support for a sysfail boot entry. Sysfail boot entries can be used for optional tweaking the automatic selection order in case a failure state of the system in some form is detected (boot firmware failure etc). The EFI variable `LoaderEntrySysFail` holds the boot loader entry to be used in the event of a system failure. If a failure occurs, the reason will be stored in the `LoaderSysFailReason` EFI variable. sysfail_check() expected to be extented to support possible conditions when we should boot sysfail("recovery") boot entry. Signed-off-by: Igor Opaniuk --- docs/BOOT.md | 2 ++ docs/BOOT_LOADER_INTERFACE.md | 12 ++++++++++++ man/systemd-boot.xml | 1 + src/boot/boot.c | 36 +++++++++++++++++++++++++++++++++++ src/boot/sysfail.c | 12 ++++++++++++ src/boot/sysfail.h | 12 ++++++++++++ src/shared/bootspec.c | 7 +++++++ src/shared/bootspec.h | 1 + 8 files changed, 83 insertions(+) create mode 100644 src/boot/sysfail.c create mode 100644 src/boot/sysfail.h diff --git a/docs/BOOT.md b/docs/BOOT.md index a9666583a1..c72b31456b 100644 --- a/docs/BOOT.md +++ b/docs/BOOT.md @@ -102,6 +102,8 @@ Some EFI variables control the loader or exported the loaders state to the start | EFI Variables | |---------------|------------------------|-------------------------------| | LoaderEntryDefault | entry identifier to select as default at bootup | non-volatile | +| LoaderEntrySysFail | sysfail entry identifier | non-volatile | +| LoaderSysFailReason | system failure reason | volatile | | LoaderConfigTimeout | timeout in seconds to show the menu | non-volatile | | LoaderEntryOneShot | entry identifier to select at the next and only the next bootup | non-volatile | | LoaderDeviceIdentifier | list of identifiers of the volume the loader was started from | volatile | diff --git a/docs/BOOT_LOADER_INTERFACE.md b/docs/BOOT_LOADER_INTERFACE.md index e264c2cc3c..d4d9a77370 100644 --- a/docs/BOOT_LOADER_INTERFACE.md +++ b/docs/BOOT_LOADER_INTERFACE.md @@ -58,6 +58,18 @@ variables. All EFI variables use the vendor UUID * The EFI variable `LoaderEntryDefault` contains the default boot loader entry to use. It contains a NUL-terminated boot loader entry identifier. +* The EFI variable `LoaderEntrySysFail` specifies the boot loader entry to be + used in case of a system failure. System failure (SysFail) boot entries can + optionally modify the automatic selection order in the event of a failure, + such as a boot firmware update failure with the failure status recorded in + the EFI system table. If a system failure occurs and `LoaderEntrySysFail` is set, + systemd-boot will use this boot entry and store the actual SysFail reason in + the `LoaderSysFailReason` EFI variable. + +* The EFI variable `LoaderSysFailReason` contains the system failure reason. + This variable is used in cooperation with `LoaderEntrySysFail` boot entry. + If system failure doesn't occur `LoaderSysFailReason` is not set at all. + * Similarly, the EFI variable `LoaderEntryOneShot` contains the default boot loader entry to use for a single following boot. It is set by the OS in order to request booting into a specific menu entry on the following boot. When set diff --git a/man/systemd-boot.xml b/man/systemd-boot.xml index 6da2714581..d0912739d1 100644 --- a/man/systemd-boot.xml +++ b/man/systemd-boot.xml @@ -461,6 +461,7 @@ LoaderEntryDefault + LoaderEntrySysFail LoaderEntryOneShot The identifier of the default boot loader entry. Set primarily by the OS and read by the boot diff --git a/src/boot/boot.c b/src/boot/boot.c index ab043deca9..133f16acfe 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -30,6 +30,7 @@ #include "shim.h" #include "smbios.h" #include "strv-fundamental.h" +#include "sysfail.h" #include "ticks.h" #include "tpm2-pcr.h" #include "uki.h" @@ -136,6 +137,7 @@ typedef struct { char16_t *entry_default_efivar; char16_t *entry_oneshot; char16_t *entry_saved; + char16_t *entry_sysfail; bool editor; bool auto_entries; bool auto_firmware; @@ -149,6 +151,7 @@ typedef struct { bool use_saved_entry; bool use_saved_entry_efivar; bool beep; + bool sysfail_occured; int64_t console_mode; int64_t console_mode_efivar; } Config; @@ -328,6 +331,8 @@ static void print_status(Config *config, char16_t *loaded_image_path) { printf(" default (EFI var): %ls\n", config->entry_default_efivar); if (config->entry_oneshot) printf(" default (one-shot): %ls\n", config->entry_oneshot); + if (config->entry_sysfail) + printf(" sysfail: %ls\n", config->entry_sysfail); if (config->entry_saved) printf(" saved entry: %ls\n", config->entry_saved); printf(" editor: %ls\n", yes_no(config->editor)); @@ -1499,11 +1504,13 @@ static void config_load_defaults(Config *config, EFI_FILE *root_dir) { (void) efivar_unset(MAKE_GUID_PTR(LOADER), u"LoaderEntryOneShot", EFI_VARIABLE_NON_VOLATILE); (void) efivar_get_str16(MAKE_GUID_PTR(LOADER), u"LoaderEntryDefault", &config->entry_default_efivar); + (void) efivar_get_str16(MAKE_GUID_PTR(LOADER), u"LoaderEntrySysFail", &config->entry_sysfail); strtolower16(config->entry_default_config); strtolower16(config->entry_default_efivar); strtolower16(config->entry_oneshot); strtolower16(config->entry_saved); + strtolower16(config->entry_sysfail); config->use_saved_entry = streq16(config->entry_default_config, u"@saved"); config->use_saved_entry_efivar = streq16(config->entry_default_efivar, u"@saved"); @@ -1686,11 +1693,38 @@ static size_t config_find_entry(Config *config, const char16_t *pattern) { return IDX_INVALID; } +static bool sysfail_process(Config *config) { + SysFailType sysfail_type; + + assert(config); + + sysfail_type = sysfail_check(); + if (sysfail_type == SYSFAIL_NO_FAILURE) + return false; + + /* Store reason string in LoaderSysFailReason EFI variable */ + const char16_t *reason_str = sysfail_get_error_str(sysfail_type); + if (reason_str) + (void) efivar_set_str16(MAKE_GUID_PTR(LOADER), u"LoaderSysFailReason", reason_str, 0); + + config->sysfail_occured = true; + + return true; +} + static void config_select_default_entry(Config *config) { size_t i; assert(config); + if (config->sysfail_occured) { + i = config_find_entry(config, config->entry_sysfail); + if (i != IDX_INVALID) { + config->idx_default = i; + return; + } + } + i = config_find_entry(config, config->entry_oneshot); if (i != IDX_INVALID) { config->idx_default = i; @@ -2685,6 +2719,7 @@ static void config_free(Config *config) { free(config->entry_default_efivar); free(config->entry_oneshot); free(config->entry_saved); + free(config->entry_sysfail); } static void config_write_entries_to_variable(Config *config) { @@ -2983,6 +3018,7 @@ static EFI_STATUS run(EFI_HANDLE image) { _cleanup_free_ char16_t *loaded_image_path = NULL; (void) device_path_to_str(loaded_image->FilePath, &loaded_image_path); config_load_all_entries(&config, loaded_image, loaded_image_path, root_dir); + (void) sysfail_process(&config); if (config.n_entries == 0) return log_error_status( diff --git a/src/boot/sysfail.c b/src/boot/sysfail.c new file mode 100644 index 0000000000..6bbbffe98d --- /dev/null +++ b/src/boot/sysfail.c @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sysfail.h" +#include "util.h" + +SysFailType sysfail_check(void) { + return SYSFAIL_NO_FAILURE; +} + +const char16_t* sysfail_get_error_str(SysFailType fail_type) { + return NULL; +} diff --git a/src/boot/sysfail.h b/src/boot/sysfail.h new file mode 100644 index 0000000000..4ccb54828b --- /dev/null +++ b/src/boot/sysfail.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efivars-fundamental.h" + +typedef enum SysFailType { + SYSFAIL_NO_FAILURE, + _SYSFAIL_MAX, +} SysFailType; + +SysFailType sysfail_check(void); +const char16_t* sysfail_get_error_str(SysFailType fail_type); diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index e5ca2ccbb3..5dd8d452ab 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -457,6 +457,7 @@ void boot_config_free(BootConfig *config) { free(config->entry_oneshot); free(config->entry_default); free(config->entry_selected); + free(config->entry_sysfail); FOREACH_ARRAY(i, config->entries, config->n_entries) boot_entry_free(i); @@ -1437,6 +1438,12 @@ static int boot_load_efi_entry_pointers(BootConfig *config, bool skip_efivars) { if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) log_warning_errno(r, "Failed to read EFI variable \"LoaderEntrySelected\", ignoring: %m"); + r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"), &config->entry_sysfail); + if (r == -ENOMEM) + return log_oom(); + if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) + log_warning_errno(r, "Failed to read EFI variable \"LoaderEntrySysFail\", ignoring: %m"); + return 1; } diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index 95675214b2..1d114e10d4 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -80,6 +80,7 @@ typedef struct BootConfig { char *entry_oneshot; char *entry_default; char *entry_selected; + char *entry_sysfail; BootEntry *entries; size_t n_entries; From 2857a8397507ca3decb01c188b4d79ee36185424 Mon Sep 17 00:00:00 2001 From: Igor Opaniuk Date: Mon, 24 Mar 2025 15:33:16 +0100 Subject: [PATCH 2/3] bootctl: configure a sysfail entry You can configure the sysfail boot entry using the bootctl command: $ bootctl set-sysfail sysfail.conf The value will be stored in the `LoaderEntrySysFail` EFI variable. The `LoaderEntrySysFail` EFI variable would be unset automatically during next boot by `systemd-boot-clear-sysfail.service` if no system failure occured, otherwise it would be kept as it is and a system failure reason will be saved to `LoaderSysFailReason` EFI variable. Signed-off-by: Igor Opaniuk --- man/bootctl.xml | 11 +++-- man/systemd-boot-clear-sysfail.service.xml | 52 ++++++++++++++++++++++ presets/90-systemd.preset | 1 + src/boot/meson.build | 1 + src/bootctl/bootctl-install.c | 1 + src/bootctl/bootctl-set-efivar.c | 8 ++++ src/bootctl/bootctl-status.c | 25 +++++++---- src/bootctl/bootctl.c | 2 + units/meson.build | 4 ++ units/systemd-boot-clear-sysfail.service | 29 ++++++++++++ 10 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 man/systemd-boot-clear-sysfail.service.xml create mode 100644 units/systemd-boot-clear-sysfail.service diff --git a/man/bootctl.xml b/man/bootctl.xml index 91d572e643..f1968a0ba7 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -134,10 +134,14 @@ ID ID + ID Sets the default boot loader entry. Takes a single boot loader entry ID string or a glob pattern as argument. The command will set the default entry only for the next boot, - the will set it persistently for all future boots. + the will set it persistently for all future boots. The command + will set the boot loader entry to be used in case of a system failure. System failure (SysFail) boot entries can + optionally modify the automatic selection order in the event of a failure, such as a boot firmware update failure with + the failure status recorded in the EFI system table. bootctl list can be used to list available boot loader entries and their IDs. @@ -146,8 +150,9 @@ or , which correspond to the current default boot loader entry for all future boots, the current default boot loader entry for the next boot, and the currently booted boot loader entry. These special IDs are resolved to the current values of the EFI variables - LoaderEntryDefault, LoaderEntryOneShot and LoaderEntrySelected, - see Boot Loader Specification for details. + LoaderEntryDefault, LoaderEntrySysFail, LoaderEntryOneShot + and LoaderEntrySelected, see + Boot Loader Specification for details. These special IDs are primarily useful as a quick way to persistently make the currently booted boot loader entry the default choice, or to upgrade the default boot loader entry for the next boot to the default boot loader entry for all future boots, but may be used for other operations too. diff --git a/man/systemd-boot-clear-sysfail.service.xml b/man/systemd-boot-clear-sysfail.service.xml new file mode 100644 index 0000000000..2249486377 --- /dev/null +++ b/man/systemd-boot-clear-sysfail.service.xml @@ -0,0 +1,52 @@ + + + + + + + + systemd-boot-clear-sysfail.service + systemd + + + + systemd-boot-clear-sysfail.service + 8 + + + + systemd-boot-clear-sysfail.service + Clear LoaderEntrySysFail entry + + + + systemd-boot-clear-sysfail.service + + + + Description + + systemd-boot-clear-sysfail.service is a system service that automatically clears the + 'LoaderEntrySysFail' boot loader entry if the boot was successful and the 'LoaderSysFailReason' EFI variable, + which indicates the reason for the system failure, is not set. + + The systemd-boot-random-seed.service unit invokes the bootctl --graceful + set-sysfail "" command, which clears the LoaderEntrySysFail entry. The service is conditionalized + so that it is run only when a LoaderSysFailReason entry is not set.For further details see + bootctl1, regarding + the command this service invokes. + + + + + See Also + + systemd1 + bootctl1 + systemd-boot7 + + + + diff --git a/presets/90-systemd.preset b/presets/90-systemd.preset index 9c13e9c3de..56f9e93706 100644 --- a/presets/90-systemd.preset +++ b/presets/90-systemd.preset @@ -19,6 +19,7 @@ enable machines.target enable getty@.service +enable systemd-boot-clear-sysfail.service enable systemd-boot-update.service enable systemd-confext.service enable systemd-homed.service diff --git a/src/boot/meson.build b/src/boot/meson.build index c2a894d323..fb0e393de9 100644 --- a/src/boot/meson.build +++ b/src/boot/meson.build @@ -307,6 +307,7 @@ libefi_sources = files( 'secure-boot.c', 'shim.c', 'smbios.c', + 'sysfail.c', 'ticks.c', 'url-discovery.c', 'util.c', diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index 8927464c46..7b083c4ad9 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -1224,6 +1224,7 @@ static int remove_loader_variables(void) { EFI_LOADER_VARIABLE_STR("LoaderConfigTimeout"), EFI_LOADER_VARIABLE_STR("LoaderConfigTimeoutOneShot"), EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), + EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"), EFI_LOADER_VARIABLE_STR("LoaderEntryLastBooted"), EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), EFI_LOADER_VARIABLE_STR("LoaderSystemToken")) { diff --git a/src/bootctl/bootctl-set-efivar.c b/src/bootctl/bootctl-set-efivar.c index d53458c491..ead81ff3e0 100644 --- a/src/bootctl/bootctl-set-efivar.c +++ b/src/bootctl/bootctl-set-efivar.c @@ -89,6 +89,11 @@ static int parse_loader_entry_target_arg(const char *arg1, char16_t **ret_target if (r < 0) return log_error_errno(r, "Failed to get EFI variable 'LoaderEntryDefault': %m"); + } else if (streq(arg1, "@sysfail")) { + r = efi_get_variable(EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"), NULL, (void *) ret_target, ret_target_size); + if (r < 0) + return log_error_errno(r, "Failed to get EFI variable 'LoaderEntrySysFail': %m"); + } else if (arg1[0] == '@' && !streq(arg1, "@saved")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unsupported special entry identifier: %s", arg1); else { @@ -144,6 +149,9 @@ int verb_set_efivar(int argc, char *argv[], void *userdata) { if (streq(argv[0], "set-default")) { variable = EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"); arg_parser = parse_loader_entry_target_arg; + } else if (streq(argv[0], "set-sysfail")) { + variable = EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"); + arg_parser = parse_loader_entry_target_arg; } else if (streq(argv[0], "set-oneshot")) { variable = EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"); arg_parser = parse_loader_entry_target_arg; diff --git a/src/bootctl/bootctl-status.c b/src/bootctl/bootctl-status.c index 38fe5273be..081f5d48f0 100644 --- a/src/bootctl/bootctl-status.c +++ b/src/bootctl/bootctl-status.c @@ -420,7 +420,7 @@ int verb_status(int argc, char *argv[], void *userdata) { { EFI_STUB_FEATURE_MULTI_PROFILE_UKI, "Stub understands profile selector" }, }; _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL, *stub = NULL, *stub_path = NULL, - *current_entry = NULL, *oneshot_entry = NULL, *default_entry = NULL; + *current_entry = NULL, *oneshot_entry = NULL, *default_entry = NULL, *sysfail_entry = NULL, *sysfail_reason = NULL; uint64_t loader_features = 0, stub_features = 0; int have; @@ -435,6 +435,8 @@ int verb_status(int argc, char *argv[], void *userdata) { (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntrySelected"), ¤t_entry); (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), &oneshot_entry); (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), &default_entry); + (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"), &sysfail_entry); + (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderSysFailReason"), &sysfail_reason); SecureBootMode secure = efi_get_secure_boot_mode(); printf("%sSystem:%s\n", ansi_underline(), ansi_normal()); @@ -484,7 +486,7 @@ int verb_status(int argc, char *argv[], void *userdata) { if (loader) { printf("%sCurrent Boot Loader:%s\n", ansi_underline(), ansi_normal()); - printf(" Product: %s%s%s\n", ansi_highlight(), loader, ansi_normal()); + printf(" Product: %s%s%s\n", ansi_highlight(), loader, ansi_normal()); for (size_t i = 0; i < ELEMENTSOF(loader_flags); i++) print_yes_no_line(i == 0, FLAGS_SET(loader_features, loader_flags[i].flag), loader_flags[i].name); @@ -502,23 +504,28 @@ int verb_status(int argc, char *argv[], void *userdata) { SD_ID128_FORMAT_VAL(loader_partition_uuid), SD_ID128_FORMAT_VAL(esp_uuid)); - printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", + printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(loader_partition_uuid)); } else if (loader_path) - printf(" Partition: n/a\n"); + printf(" Partition: n/a\n"); if (loader_path) - printf(" Loader: %s%s\n", glyph(GLYPH_TREE_RIGHT), strna(loader_path)); + printf(" Loader: %s%s\n", glyph(GLYPH_TREE_RIGHT), strna(loader_path)); if (loader_url) - printf(" Net Boot URL: %s\n", loader_url); + printf(" Net Boot URL: %s\n", loader_url); + + if (sysfail_entry) + printf("SysFail Reason: %s\n", sysfail_reason); if (current_entry) - printf("Current Entry: %s\n", current_entry); + printf(" Current Entry: %s\n", current_entry); if (default_entry) - printf("Default Entry: %s\n", default_entry); + printf(" Default Entry: %s\n", default_entry); if (oneshot_entry && !streq_ptr(oneshot_entry, default_entry)) - printf("OneShot Entry: %s\n", oneshot_entry); + printf(" OneShot Entry: %s\n", oneshot_entry); + if (sysfail_entry) + printf(" SysFail Entry: %s\n", sysfail_entry); printf("\n"); } diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 047b79254d..790ab2b653 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -259,6 +259,7 @@ static int help(int argc, char *argv[], void *userdata) { "\n%3$sBoot Loader Interface Commands:%4$s\n" " set-default ID Set default boot loader entry\n" " set-oneshot ID Set default boot loader entry, for next boot only\n" + " set-sysfail ID Set boot loader entry used in case of a system failure\n" " set-timeout SECONDS Set the menu timeout\n" " set-timeout-oneshot SECONDS\n" " Set the menu timeout for the next boot only\n" @@ -660,6 +661,7 @@ static int bootctl_main(int argc, char *argv[]) { { "set-oneshot", 2, 2, 0, verb_set_efivar }, { "set-timeout", 2, 2, 0, verb_set_efivar }, { "set-timeout-oneshot", 2, 2, 0, verb_set_efivar }, + { "set-sysfail", 2, 2, 0, verb_set_efivar }, { "random-seed", VERB_ANY, 1, 0, verb_random_seed }, { "reboot-to-firmware", VERB_ANY, 2, 0, verb_reboot_to_firmware }, {} diff --git a/units/meson.build b/units/meson.build index 3dd135942d..a8b0040562 100644 --- a/units/meson.build +++ b/units/meson.build @@ -286,6 +286,10 @@ units = [ 'conditions' : ['ENABLE_BOOTLOADER'], 'symlinks' : ['sysinit.target.wants/'], }, + { + 'file' : 'systemd-boot-clear-sysfail.service', + 'conditions' : ['ENABLE_BOOTLOADER'], + }, { 'file' : 'systemd-boot-update.service', 'conditions' : ['ENABLE_BOOTLOADER'], diff --git a/units/systemd-boot-clear-sysfail.service b/units/systemd-boot-clear-sysfail.service new file mode 100644 index 0000000000..c80735970d --- /dev/null +++ b/units/systemd-boot-clear-sysfail.service @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Clear SysFail Entry If The Boot Is Successful +Documentation=man:systemd-boot-sysfail.service(8) + +DefaultDependencies=no +Conflicts=shutdown.target +Before=sysinit.target shutdown.target + +ConditionPathExists=!/etc/initrd-release +# If LoaderSysFailReason is set we should not clear SysFail entry +ConditionPathExists=!/sys/firmware/efi/efivars/LoaderSysFailReason-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f +ConditionPathExists=/sys/firmware/efi/efivars/LoaderEntrySysFail-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=bootctl --graceful set-sysfail "" + +[Install] +WantedBy=sysinit.target From 9c56688e8740b1141252902360bebecde7800d60 Mon Sep 17 00:00:00 2001 From: Igor Opaniuk Date: Thu, 23 Jan 2025 13:31:04 +0100 Subject: [PATCH 3/3] sd-boot: use sysfail entry for UEFI firmware update failure Add support for using a sysfail boot entry in case of UEFI firmware capsule update failure [1]. The status of a firmware update is obtained from the EFI System Resource Table (ESRT), which provides an optional mechanism for identifying device and system firmware resources for the purposes of targeting firmware updates to those resources. Current implementation uses the value of LastAttemptStatus field from ESRT, which describes the result of the last firmware update attempt for the firmware resource entry. The field is updated each time an UpdateCapsule() is attempted for an ESRT entry and is preserved across reboots (non-volatile). This can be be used in setups with support for A/B OTA updates, where the boot firmware and Linux/RootFS might be updated synchronously. [1] https://uefi.org/specs/UEFI/2.10/23_Firmware_Update_and_Reporting.html Signed-off-by: Igor Opaniuk --- src/boot/efi.h | 27 +++++++++++++++++++++++++++ src/boot/sysfail.c | 29 ++++++++++++++++++++++++++++- src/boot/sysfail.h | 1 + 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/boot/efi.h b/src/boot/efi.h index 9cac172072..f35d0692bf 100644 --- a/src/boot/efi.h +++ b/src/boot/efi.h @@ -126,6 +126,16 @@ typedef uint64_t EFI_PHYSICAL_ADDRESS; #define EFI_CUSTOM_MODE_ENABLE_GUID \ GUID_DEF(0xc076ec0c, 0x7028, 0x4399, 0xa0, 0x72, 0x71, 0xee, 0x5c, 0x44, 0x8b, 0x9f) +#define EFI_SYSTEM_RESOURCE_TABLE_GUID \ + GUID_DEF(0xb122a263, 0x3661, 0x4f68, 0x99, 0x29, 0x78, 0xf8, 0xb0, 0xd6, 0x21, 0x80) + +/* EFI System Resource Table (ESRT) Firmware Type Definitions */ +#define ESRT_FW_TYPE_UNKNOWN 0x00000000U +#define ESRT_FW_TYPE_SYSTEMFIRMWARE 0x00000001U +#define ESRT_FW_TYPE_DEVICEFIRMWARE 0x00000002U +#define ESRT_FW_TYPE_UEFIDRIVER 0x00000003U + +#define LAST_ATTEMPT_STATUS_SUCCESS 0x00000000U #define EVT_TIMER 0x80000000U #define EVT_RUNTIME 0x40000000U @@ -426,6 +436,23 @@ typedef struct { } *ConfigurationTable; } EFI_SYSTEM_TABLE; +typedef struct { + EFI_GUID FwClass; + uint32_t FwType; + uint32_t FwVersion; + uint32_t LowestSupportedFwVersion; + uint32_t CapsuleFlags; + uint32_t LastAttemptVersion; + uint32_t LastAttemptStatus; +} EFI_SYSTEM_RESOURCE_ENTRY; + +typedef struct { + uint32_t FwResourceCount; + uint32_t FwResourceCountMax; + uint64_t FwResourceVersion; + EFI_SYSTEM_RESOURCE_ENTRY Entries[]; +} EFI_SYSTEM_RESOURCE_TABLE; + extern EFI_SYSTEM_TABLE *ST; extern EFI_BOOT_SERVICES *BS; extern EFI_RUNTIME_SERVICES *RT; diff --git a/src/boot/sysfail.c b/src/boot/sysfail.c index 6bbbffe98d..02e36bae7f 100644 --- a/src/boot/sysfail.c +++ b/src/boot/sysfail.c @@ -3,10 +3,37 @@ #include "sysfail.h" #include "util.h" +static bool firmware_update_has_failed(void) { + const EFI_SYSTEM_RESOURCE_TABLE *esrt_table; + const EFI_SYSTEM_RESOURCE_ENTRY *esrt_entries; + + esrt_table = find_configuration_table(MAKE_GUID_PTR(EFI_SYSTEM_RESOURCE_TABLE)); + if (!esrt_table) + return false; + + esrt_entries = esrt_table->Entries; + + FOREACH_ARRAY(esrt_entry, esrt_entries, esrt_table->FwResourceCount) + if (esrt_entry->FwType == ESRT_FW_TYPE_SYSTEMFIRMWARE) + return esrt_entry->LastAttemptStatus != LAST_ATTEMPT_STATUS_SUCCESS; + + return false; +} + SysFailType sysfail_check(void) { + if (firmware_update_has_failed()) + return SYSFAIL_FIRMWARE_UPDATE; + return SYSFAIL_NO_FAILURE; } const char16_t* sysfail_get_error_str(SysFailType fail_type) { - return NULL; + switch (fail_type) { + case SYSFAIL_NO_FAILURE: + return NULL; + case SYSFAIL_FIRMWARE_UPDATE: + return u"firmware-updare-failure"; + default: + assert_not_reached(); + } } diff --git a/src/boot/sysfail.h b/src/boot/sysfail.h index 4ccb54828b..aafdf790be 100644 --- a/src/boot/sysfail.h +++ b/src/boot/sysfail.h @@ -5,6 +5,7 @@ typedef enum SysFailType { SYSFAIL_NO_FAILURE, + SYSFAIL_FIRMWARE_UPDATE, _SYSFAIL_MAX, } SysFailType;