Add proper tracking of last attempt version and status for firmware
updates to enable accurate ESRT (EFI System Resource Table) reporting.
This allows the OS to query firmware update history and status.

Introduce helper functions to manage last attempt information and map
internal error codes to UEFI-compliant status codes. The fw_version
is now only updated on successful updates, preserving the previous
version on failures to maintain accurate ESRT state.

Update last attempt information at all failure points in the update
flow, including verification failures, resource allocation errors,
and device errors. This ensures the OS always has visibility into
the most recent update attempt and its outcome.

Signed-off-by: Balaji Selvanathan <[email protected]>
---
 lib/efi_loader/efi_firmware.c | 129 ++++++++++++++++++++++++++++++++++++++----
 1 file changed, 118 insertions(+), 11 deletions(-)

diff --git a/lib/efi_loader/efi_firmware.c b/lib/efi_loader/efi_firmware.c
index b41969c70fd..dff8de8cf8f 100644
--- a/lib/efi_loader/efi_firmware.c
+++ b/lib/efi_loader/efi_firmware.c
@@ -52,9 +52,9 @@ struct fmp_payload_header {
  */
 struct fmp_state {
        u32 fw_version;
-       u32 lowest_supported_version; /* not used */
-       u32 last_attempt_version; /* not used */
-       u32 last_attempt_status; /* not used */
+       u32 lowest_supported_version; /* not used - read from DTB for security 
*/
+       u32 last_attempt_version;     /* used for esrt tracking */
+       u32 last_attempt_status;      /* used for esrt tracking */
 };
 
 /**
@@ -190,6 +190,67 @@ static void efi_firmware_get_lsv_from_dtb(u8 image_index,
        }
 }
 
+/**
+ * efi_firmware_set_last_attempt - set last attempt information
+ * @state:             Pointer to fmp state
+ * @attempt_version:   Version that was attempted
+ * @attempt_status:    Status of the attempt
+ *
+ * Set the last attempt version and status in the fmp_state structure.
+ */
+static void efi_firmware_set_last_attempt(struct fmp_state *state,
+                                         u32 attempt_version,
+                                         u32 attempt_status)
+{
+       state->last_attempt_version = attempt_version;
+       state->last_attempt_status = attempt_status;
+}
+
+/**
+ * efi_firmware_get_last_attempt - get last attempt information
+ * @state:             Pointer to fmp state
+ * @attempt_version:   Pointer to store last attempt version
+ * @attempt_status:    Pointer to store last attempt status
+ *
+ * Retrieve the last attempt version and status from fmp_state structure.
+ */
+static void efi_firmware_get_last_attempt(const struct fmp_state *state,
+                                         u32 *attempt_version,
+                                         u32 *attempt_status)
+{
+       if (attempt_version)
+               *attempt_version = state->last_attempt_version;
+       if (attempt_status)
+               *attempt_status = state->last_attempt_status;
+}
+
+/**
+ * efi_firmware_map_error_to_status - map internal errors to UEFI status codes
+ * @error:     Internal error code
+ *
+ * Map U-Boot internal error codes to UEFI-compliant last attempt status codes.
+ *
+ * Return: UEFI last attempt status code
+ */
+static u32 efi_firmware_map_error_to_status(efi_status_t error)
+{
+       switch (error) {
+       case EFI_SUCCESS:
+               return LAST_ATTEMPT_STATUS_SUCCESS;
+       case EFI_OUT_OF_RESOURCES:
+               return LAST_ATTEMPT_STATUS_ERROR_INSUFFICIENT_RESOURCES;
+       case EFI_INVALID_PARAMETER:
+               return LAST_ATTEMPT_STATUS_ERROR_INCORRECT_VERSION;
+       case EFI_SECURITY_VIOLATION:
+               return LAST_ATTEMPT_STATUS_ERROR_AUTH_ERROR;
+       case EFI_UNSUPPORTED:
+               return LAST_ATTEMPT_STATUS_ERROR_INVALID_FORMAT;
+       case EFI_DEVICE_ERROR:
+       default:
+               return LAST_ATTEMPT_STATUS_ERROR_UNSUCCESSFUL;
+       }
+}
+
 /**
  * efi_firmware_fill_version_info - fill the version information
  * @image_info:                Image information
@@ -237,8 +298,15 @@ void efi_firmware_fill_version_info(struct 
efi_firmware_image_descriptor *image_
 
        ret = efi_get_variable_int(varname, &fw_array->image_type_id,
                                   NULL, &size, var_state, NULL);
-       if (ret == EFI_SUCCESS && expected_size == size)
+       if (ret == EFI_SUCCESS && expected_size == size) {
                image_info->version = var_state[active_index].fw_version;
+               image_info->last_attempt_version = 
var_state[active_index].last_attempt_version;
+               image_info->last_attempt_status = 
var_state[active_index].last_attempt_status;
+       } else {
+               /* Default values if no previous state exists */
+               image_info->last_attempt_version = 0;
+               image_info->last_attempt_status = LAST_ATTEMPT_STATUS_SUCCESS;
+       }
 
        free(var_state);
 }
@@ -427,6 +495,10 @@ efi_status_t efi_firmware_capsule_authenticate(const void 
**p_image,
  * @image_index:       image index
  *
  * Update the FmpStateXXXX variable with the firmware update state.
+ * On successful update (last_attempt_status == LAST_ATTEMPT_STATUS_SUCCESS),
+ * updates fw_version to the new version.
+ * On failed update, preserves the old fw_version.
+ * Always updates last_attempt_version and last_attempt_status.
  *
  * Return:             status code
  */
@@ -471,11 +543,19 @@ efi_status_t efi_firmware_set_fmp_state_var(struct 
fmp_state *state, u8 image_in
                memset(var_state, 0, num_banks * sizeof(*var_state));
 
        /*
-        * Only the fw_version is set here.
+        * Set fw_version and last attempt information.
         * lowest_supported_version in FmpState variable is ignored since
         * it can be tampered if the file based EFI variable storage is used.
+        *
+        * Only update fw_version if the update succeeded.
+        * On failure, preserve the old fw_version to maintain accurate ESRT 
state.
         */
-       var_state[update_bank].fw_version = state->fw_version;
+       if (state->last_attempt_status == LAST_ATTEMPT_STATUS_SUCCESS)
+               var_state[update_bank].fw_version = state->fw_version;
+       /* else: keep existing fw_version (don't update on failure) */
+
+       var_state[update_bank].last_attempt_version = 
state->last_attempt_version;
+       var_state[update_bank].last_attempt_status = state->last_attempt_status;
 
        size = num_banks * sizeof(*var_state);
        ret = efi_set_variable_int(varname, image_type_id,
@@ -661,21 +741,40 @@ efi_status_t EFIAPI efi_firmware_fit_set_image(
 
        status = efi_firmware_verify_image(&image, &image_size, image_index,
                                           &state);
-       if (status != EFI_SUCCESS)
+       if (status != EFI_SUCCESS) {
+               /* Set last attempt information for failed verification */
+               efi_firmware_set_last_attempt(&state, state.fw_version,
+                                             
efi_firmware_map_error_to_status(status));
+               efi_firmware_set_fmp_state_var(&state, image_index);
                return EFI_EXIT(status);
+       }
+
+       /* Set last attempt version before starting the update */
+       efi_firmware_set_last_attempt(&state, state.fw_version,
+                                     LAST_ATTEMPT_STATUS_SUCCESS);
 
        orig_dfu_env = env_get("dfu_alt_info");
+
        if (orig_dfu_env) {
                orig_dfu_env = strdup(orig_dfu_env);
                if (!orig_dfu_env) {
                        log_err("strdup() failed!\n");
-                       return EFI_EXIT(EFI_OUT_OF_RESOURCES);
+                       status = EFI_OUT_OF_RESOURCES;
+                       efi_firmware_set_last_attempt(&state, state.fw_version,
+                                                     
efi_firmware_map_error_to_status(status));
+                       efi_firmware_set_fmp_state_var(&state, image_index);
+                       return EFI_EXIT(status);
                }
        }
+
        if (env_set("dfu_alt_info", update_info.dfu_string)) {
                log_err("Unable to set env variable \"dfu_alt_info\"!\n");
                free(orig_dfu_env);
-               return EFI_EXIT(EFI_DEVICE_ERROR);
+               status = EFI_DEVICE_ERROR;
+               efi_firmware_set_last_attempt(&state, state.fw_version,
+                                             
efi_firmware_map_error_to_status(status));
+               efi_firmware_set_fmp_state_var(&state, image_index);
+               return EFI_EXIT(status);
        }
 
        /* Make sure the update fitImage is properly aligned to 8-bytes */
@@ -698,9 +797,17 @@ efi_status_t EFIAPI efi_firmware_fit_set_image(
 
        free(orig_dfu_env);
 
-       if (ret)
-               return EFI_EXIT(EFI_DEVICE_ERROR);
+       if (ret) {
+               status = EFI_DEVICE_ERROR;
+               efi_firmware_set_last_attempt(&state, state.fw_version,
+                                             
efi_firmware_map_error_to_status(status));
+               efi_firmware_set_fmp_state_var(&state, image_index);
+               return EFI_EXIT(status);
+       }
 
+       /* Update successful - set success status */
+       efi_firmware_set_last_attempt(&state, state.fw_version,
+                                     LAST_ATTEMPT_STATUS_SUCCESS);
        efi_firmware_set_fmp_state_var(&state, image_index);
 
        return EFI_EXIT(EFI_SUCCESS);

-- 
2.34.1

Reply via email to