summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKarthikeyan Ramasubramanian <kramasub@google.com>2022-11-29 13:20:45 -0700
committerChromeos LUCI <chromeos-scoped@luci-project-accounts.iam.gserviceaccount.com>2022-12-09 19:12:36 +0000
commitd19765a9507e33c697bfd18038f860e62bd99161 (patch)
treee227ef9cb5b711fe7472c36d98f725c4ec45d4c6
parent05b81fcc18c905613ef56e09ca100ef79a6ee5d2 (diff)
downloadvboot-d19765a9507e33c697bfd18038f860e62bd99161.tar.gz
firmware/2lib: Introduce API to report previous boot failure
Currently when failures are reported before a slot is selected, vboot directly requests for recovery. Add a new API to report previous boot failure before a slot is selected. This will allow coreboot verstage to report any failures that happened in the previous boot such that verified boot can select the appropriate FW slot instead of booting into recovery mode directly. BUG=b:242825052 BRANCH=None TEST=Build Skyrim BIOS image. Run the unit test built for this API. Boot to OS in Skyrim. Corrupt certain sections in flashmap and report boot failures and ensured that vboot selected the appropriate FW slot. Change-Id: I3b1fe8e28fc754919cd4067eeed5029e7dbae7a4 Signed-off-by: Karthikeyan Ramasubramanian <kramasub@google.com> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/4064425 Reviewed-by: Julius Werner <jwerner@chromium.org>
-rw-r--r--firmware/2lib/2misc.c44
-rw-r--r--firmware/2lib/include/2api.h20
-rw-r--r--tests/vb2_misc_tests.c67
3 files changed, 119 insertions, 12 deletions
diff --git a/firmware/2lib/2misc.c b/firmware/2lib/2misc.c
index c14aab55..92a3452e 100644
--- a/firmware/2lib/2misc.c
+++ b/firmware/2lib/2misc.c
@@ -82,17 +82,31 @@ vb2_error_t vb2_read_gbb_header(struct vb2_context *ctx,
return VB2_SUCCESS;
}
-test_mockable
-void vb2api_fail(struct vb2_context *ctx, uint8_t reason, uint8_t subcode)
+static void fail_impl(struct vb2_context *ctx,
+ uint8_t reason, uint8_t subcode, bool previous_boot)
{
struct vb2_shared_data *sd = vb2_get_sd(ctx);
+ uint32_t last_fw_slot, last_fw_result, fw_slot;
/* If NV data hasn't been initialized, initialize it now */
if (!(sd->status & VB2_SD_STATUS_NV_INIT))
vb2_nv_init(ctx);
+ /*
+ * Donot overwrite any existing failure with a new failure reported
+ * through vb2api_previous_boot_fail(). Existing failure might have
+ * been set through vb2api_fail() in the previous boot and the new
+ * failure can stand.
+ */
+ if (previous_boot &&
+ vb2_nv_get(ctx, VB2_NV_FW_RESULT) == VB2_FW_RESULT_FAILURE)
+ return;
+
/* See if we were far enough in the boot process to choose a slot */
- if (sd->status & VB2_SD_STATUS_CHOSE_SLOT) {
+ if (previous_boot || (sd->status & VB2_SD_STATUS_CHOSE_SLOT)) {
+ last_fw_slot = vb2_nv_get(ctx, VB2_NV_FW_PREV_TRIED);
+ last_fw_result = vb2_nv_get(ctx, VB2_NV_FW_PREV_RESULT);
+ fw_slot = vb2_nv_get(ctx, VB2_NV_FW_TRIED);
/* Boot failed */
vb2_nv_set(ctx, VB2_NV_FW_RESULT, VB2_FW_RESULT_FAILURE);
@@ -105,14 +119,14 @@ void vb2api_fail(struct vb2_context *ctx, uint8_t reason, uint8_t subcode)
* between slots, which may help if one or both slots is
* flaky.
*/
- vb2_nv_set(ctx, VB2_NV_TRY_NEXT, 1 - sd->fw_slot);
+ vb2_nv_set(ctx, VB2_NV_TRY_NEXT, 1 - fw_slot);
/*
* If we didn't try the other slot last boot, or we tried it
* and it didn't fail, try it next boot.
*/
- if (sd->last_fw_slot != 1 - sd->fw_slot ||
- sd->last_fw_result != VB2_FW_RESULT_FAILURE)
+ if (last_fw_slot != 1 - fw_slot ||
+ last_fw_result != VB2_FW_RESULT_FAILURE)
return;
}
@@ -132,6 +146,24 @@ void vb2api_fail(struct vb2_context *ctx, uint8_t reason, uint8_t subcode)
}
}
+test_mockable
+void vb2api_fail(struct vb2_context *ctx, uint8_t reason, uint8_t subcode)
+{
+ fail_impl(ctx, reason, subcode, false);
+}
+
+test_mockable
+void vb2api_previous_boot_fail(struct vb2_context *ctx,
+ uint8_t reason, uint8_t subcode)
+{
+ struct vb2_shared_data *sd = vb2_get_sd(ctx);
+
+ VB2_ASSERT(!(sd->status & VB2_SD_STATUS_NV_INIT) &&
+ !(sd->status & VB2_SD_STATUS_CHOSE_SLOT));
+
+ fail_impl(ctx, reason, subcode, true);
+}
+
void vb2_check_recovery(struct vb2_context *ctx)
{
struct vb2_shared_data *sd = vb2_get_sd(ctx);
diff --git a/firmware/2lib/include/2api.h b/firmware/2lib/include/2api.h
index 643bc986..dd8af7b3 100644
--- a/firmware/2lib/include/2api.h
+++ b/firmware/2lib/include/2api.h
@@ -404,6 +404,26 @@ vb2_error_t vb2api_secdata_fwmp_check(struct vb2_context *ctx, uint8_t *size);
void vb2api_fail(struct vb2_context *ctx, uint8_t reason, uint8_t subcode);
/**
+ * Report firmware failure from previous boot to vboot.
+ *
+ * This function can only be called before vb2api_fw_phase1 (nvdata is
+ * initialized). Otherwise an assert is raised. This function is required to be
+ * called in the following environment:
+ * - Context has to be initialized using vb2api_init
+ * - NV data has to be read into context
+ * - Secdata may or may not have been read
+ * - vb2api_fw_phase1 must not have been called.
+ *
+ * If the other slot is not known bad then try the other firmware slot.
+ * If both the slots are known bad, then request recovery.
+ *
+ * @param reason Recovery reason
+ * @param subcode Recovery subcode
+ */
+void vb2api_previous_boot_fail(struct vb2_context *ctx,
+ uint8_t reason, uint8_t subcode);
+
+/**
* Entry point for setting up a context that can only load and verify a kernel.
*
* The only allowed usage is to call vb2api_init, then this entry point,
diff --git a/tests/vb2_misc_tests.c b/tests/vb2_misc_tests.c
index 0365f008..b73c0478 100644
--- a/tests/vb2_misc_tests.c
+++ b/tests/vb2_misc_tests.c
@@ -390,10 +390,10 @@ static void fail_tests(void)
reset_common_data();
vb2_nv_set(ctx, VB2_NV_TRY_COUNT, 3);
vb2_nv_set(ctx, VB2_NV_FW_RESULT, VB2_FW_RESULT_UNKNOWN);
+ vb2_nv_set(ctx, VB2_NV_FW_TRIED, 0);
+ vb2_nv_set(ctx, VB2_NV_FW_PREV_TRIED, 1);
+ vb2_nv_set(ctx, VB2_NV_FW_PREV_RESULT, VB2_FW_RESULT_UNKNOWN);
sd->status |= VB2_SD_STATUS_CHOSE_SLOT;
- sd->fw_slot = 0;
- sd->last_fw_slot = 1;
- sd->last_fw_result = VB2_FW_RESULT_UNKNOWN;
vb2api_fail(ctx, 5, 6);
TEST_EQ(vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST), 0, "vb2_failover");
TEST_EQ(vb2_nv_get(ctx, VB2_NV_FW_RESULT),
@@ -405,10 +405,10 @@ static void fail_tests(void)
/* Fail with other slot already failing triggers recovery */
reset_common_data();
+ vb2_nv_set(ctx, VB2_NV_FW_PREV_TRIED, 0);
+ vb2_nv_set(ctx, VB2_NV_FW_PREV_RESULT, VB2_FW_RESULT_FAILURE);
+ vb2_nv_set(ctx, VB2_NV_FW_TRIED, 1);
sd->status |= VB2_SD_STATUS_CHOSE_SLOT;
- sd->fw_slot = 1;
- sd->last_fw_slot = 0;
- sd->last_fw_result = VB2_FW_RESULT_FAILURE;
vb2api_fail(ctx, 7, 8);
TEST_EQ(vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST), 7,
"vb2api_fail both slots bad");
@@ -418,6 +418,60 @@ static void fail_tests(void)
"vb2api_fail try other slot");
}
+static void previous_boot_fail_tests(void)
+{
+ /* Previous boot fail (before even NV init) */
+ /* Fail with other slot good doesn't trigger recovery */
+ reset_common_data();
+ vb2_nv_set(ctx, VB2_NV_FW_TRIED, VB2_FW_SLOT_A);
+ vb2_nv_set(ctx, VB2_NV_TRY_COUNT, 3);
+ vb2_nv_set(ctx, VB2_NV_FW_RESULT, VB2_FW_RESULT_UNKNOWN);
+ sd->status &= ~VB2_SD_STATUS_NV_INIT;
+ vb2api_previous_boot_fail(ctx, 1, 2);
+ TEST_NEQ(sd->status & VB2_SD_STATUS_NV_INIT,
+ 0, "vb2api_previous_boot_fail inits NV");
+ TEST_EQ(vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST),
+ 0, "vb2_previous_boot_fail over");
+ TEST_EQ(vb2_nv_get(ctx, VB2_NV_FW_RESULT),
+ VB2_FW_RESULT_FAILURE, "vb2api_previous_boot_fail result");
+ TEST_EQ(vb2_nv_get(ctx, VB2_NV_TRY_COUNT),
+ 0, "vb2api_previous_boot_fail try count");
+ TEST_EQ(vb2_nv_get(ctx, VB2_NV_TRY_NEXT),
+ 1, "vb2api_previous_boot_fail FW tried");
+
+ /* Fail with other slot already failing triggers recovery */
+ reset_common_data();
+ vb2_nv_set(ctx, VB2_NV_FW_PREV_TRIED, VB2_FW_SLOT_A);
+ vb2_nv_set(ctx, VB2_NV_FW_PREV_RESULT, VB2_FW_RESULT_FAILURE);
+ vb2_nv_set(ctx, VB2_NV_FW_TRIED, VB2_FW_SLOT_B);
+ vb2_nv_set(ctx, VB2_NV_TRY_COUNT, 3);
+ vb2_nv_set(ctx, VB2_NV_FW_RESULT, VB2_FW_RESULT_UNKNOWN);
+ sd->status &= ~VB2_SD_STATUS_NV_INIT;
+ vb2api_previous_boot_fail(ctx, 3, 4);
+ TEST_EQ(vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST),
+ 3, "vb2api_previous_boot_fail both slots bad");
+ TEST_EQ(vb2_nv_get(ctx, VB2_NV_FW_RESULT),
+ VB2_FW_RESULT_FAILURE, "vb2api_previous_boot_fail result");
+ TEST_EQ(vb2_nv_get(ctx, VB2_NV_TRY_COUNT),
+ 0, "vb2api_previous_boot_fail try count");
+ TEST_EQ(vb2_nv_get(ctx, VB2_NV_TRY_NEXT),
+ 0, "vb2api_previous_boot_fail FW tried");
+
+ /* Repeated fail doesn't overwrite the error code */
+ reset_common_data();
+ vb2_nv_set(ctx, VB2_NV_TRY_NEXT, VB2_FW_SLOT_A);
+ vb2_nv_set(ctx, VB2_NV_TRY_COUNT, 3);
+ vb2_nv_set(ctx, VB2_NV_FW_RESULT, VB2_FW_RESULT_FAILURE);
+ sd->status &= ~VB2_SD_STATUS_NV_INIT;
+ vb2api_previous_boot_fail(ctx, 5, 6);
+ TEST_EQ(vb2_nv_get(ctx, VB2_NV_FW_RESULT),
+ VB2_FW_RESULT_FAILURE, "vb2api_previous_boot_fail result");
+ TEST_EQ(vb2_nv_get(ctx, VB2_NV_TRY_COUNT),
+ 3, "vb2api_previous_boot_fail try count");
+ TEST_EQ(vb2_nv_get(ctx, VB2_NV_TRY_NEXT),
+ VB2_FW_SLOT_A, "vb2api_previous_boot_fail try next");
+}
+
static void recovery_tests(void)
{
/* No recovery */
@@ -1085,6 +1139,7 @@ int main(int argc, char* argv[])
misc_tests();
gbb_tests();
fail_tests();
+ previous_boot_fail_tests();
recovery_tests();
dev_switch_tests();
enable_dev_tests();