diff options
author | Joel Kitching <kitching@google.com> | 2020-08-03 23:52:27 +0800 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2020-09-14 18:31:44 +0000 |
commit | 3add2cf145d515656e2656fcbc1cbf211785834a (patch) | |
tree | eec2aad114f233a440dfa6bc80d395c157baa236 | |
parent | 3839d6bcafb4b389766bba2e9fae36384b6ddeb4 (diff) | |
download | vboot-3add2cf145d515656e2656fcbc1cbf211785834a.tar.gz |
vboot: Introduce alternate boot functionality
Introduce alternate boot functionality both via keyboard shortcut
("Ctrl+L") to directly boot into the default alternate bootloader,
and via menu ("Alternate bootloader" on dev screen) to show a screen
listing available bootloaders.
BUG=b:146399181, b:161092974
TEST=make clean && make runtests
BRANCH=puff, zork
Cq-Depend: chromium:2339040
Signed-off-by: Joel Kitching <kitching@google.com>
Change-Id: I28f157936017719dc95656db147967f5e61a1407
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/2335017
Commit-Queue: Yu-Ping Wu <yupingso@chromium.org>
Tested-by: Hsuan Ting Chen <roccochen@chromium.org>
Reviewed-by: Yu-Ping Wu <yupingso@chromium.org>
Reviewed-by: Joel Kitching <kitching@chromium.org>
(cherry picked from commit f573cf6669b5f2972f29f511efead068b8c3b38e)
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/2407537
Reviewed-by: Hsuan Ting Chen <roccochen@chromium.org>
Commit-Queue: Furquan Shaikh <furquan@chromium.org>
-rw-r--r-- | firmware/2lib/2ui.c | 2 | ||||
-rw-r--r-- | firmware/2lib/2ui_screens.c | 117 | ||||
-rw-r--r-- | firmware/2lib/include/2api.h | 9 | ||||
-rw-r--r-- | firmware/2lib/include/2ui.h | 5 | ||||
-rw-r--r-- | firmware/stub/vboot_api_stub.c | 9 | ||||
-rw-r--r-- | tests/vb2_ui_action_tests.c | 97 | ||||
-rw-r--r-- | tests/vb2_ui_tests.c | 48 |
7 files changed, 281 insertions, 6 deletions
diff --git a/firmware/2lib/2ui.c b/firmware/2lib/2ui.c index d80002d5..4ddbcef0 100644 --- a/firmware/2lib/2ui.c +++ b/firmware/2lib/2ui.c @@ -410,6 +410,8 @@ vb2_error_t developer_action(struct vb2_ui_context *ui) if (ui->key == VB_KEY_CTRL('D') || (DETACHABLE && ui->key == VB_BUTTON_VOL_DOWN_LONG_PRESS)) return vb2_ui_developer_mode_boot_internal_action(ui); + if (ui->key == VB_KEY_CTRL('L')) + return vb2_ui_developer_mode_boot_alternate_action(ui); if (ui->key == '\t') return vb2_ui_screen_change(ui, VB2_SCREEN_DEBUG_INFO); diff --git a/firmware/2lib/2ui_screens.c b/firmware/2lib/2ui_screens.c index a962eb21..e163f43f 100644 --- a/firmware/2lib/2ui_screens.c +++ b/firmware/2lib/2ui_screens.c @@ -642,6 +642,7 @@ static const struct vb2_screen_info recovery_disk_step3_screen = { #define DEVELOPER_MODE_ITEM_RETURN_TO_SECURE 1 #define DEVELOPER_MODE_ITEM_BOOT_INTERNAL 2 #define DEVELOPER_MODE_ITEM_BOOT_EXTERNAL 3 +#define DEVELOPER_MODE_ITEM_SELECT_BOOTLOADER 4 vb2_error_t developer_mode_init(struct vb2_ui_context *ui) { @@ -662,11 +663,20 @@ vb2_error_t developer_mode_init(struct vb2_ui_context *ui) ui->state->disabled_item_mask |= 1 << DEVELOPER_MODE_ITEM_BOOT_EXTERNAL; + /* Don't show "Select alternate bootloader" button if not allowed. */ + if (!vb2_dev_boot_legacy_allowed(ui->ctx)) + ui->state->disabled_item_mask |= + 1 << DEVELOPER_MODE_ITEM_SELECT_BOOTLOADER; + /* Choose the default selection. */ switch (default_boot) { case VB2_DEV_DEFAULT_BOOT_TARGET_EXTERNAL: ui->state->selected_item = DEVELOPER_MODE_ITEM_BOOT_EXTERNAL; break; + case VB2_DEV_DEFAULT_BOOT_TARGET_LEGACY: + ui->state->selected_item = + DEVELOPER_MODE_ITEM_SELECT_BOOTLOADER; + break; default: ui->state->selected_item = DEVELOPER_MODE_ITEM_BOOT_INTERNAL; break; @@ -781,6 +791,10 @@ static const struct vb2_menu_item developer_mode_items[] = { .text = "Boot from external disk", .action = vb2_ui_developer_mode_boot_external_action, }, + [DEVELOPER_MODE_ITEM_SELECT_BOOTLOADER] = { + .text = "Select alternate bootloader", + .target = VB2_SCREEN_DEVELOPER_SELECT_BOOTLOADER, + }, ADVANCED_OPTIONS_ITEM, POWER_OFF_ITEM, }; @@ -876,6 +890,108 @@ static const struct vb2_screen_info developer_invalid_disk_screen = { }; /******************************************************************************/ +/* VB2_SCREEN_DEVELOPER_SELECT_BOOTLOADER */ + +const struct vb2_menu_item developer_select_bootloader_items_before[] = { + LANGUAGE_SELECT_ITEM, +}; + +const struct vb2_menu_item developer_select_bootloader_items_after[] = { + BACK_ITEM, + POWER_OFF_ITEM, +}; + +vb2_error_t vb2_ui_developer_mode_boot_alternate_action( + struct vb2_ui_context *ui) +{ + uint32_t altfw_num; + const size_t menu_before_len = + ARRAY_SIZE(developer_select_bootloader_items_before); + + if (!(ui->ctx->flags & VB2_CONTEXT_DEVELOPER_MODE) || + !vb2_dev_boot_allowed(ui->ctx) || + !vb2_dev_boot_legacy_allowed(ui->ctx)) { + VB2_DEBUG("ERROR: Dev mode alternate bootloader not allowed\n"); + ui->error_beep = 1; + return VB2_REQUEST_UI_CONTINUE; + } + + if (vb2ex_get_bootloader_count() == 0) { + VB2_DEBUG("ERROR: No bootloader was found\n"); + ui->error_beep = 1; + return VB2_REQUEST_UI_CONTINUE; + } + + if (ui->key == VB_KEY_CTRL('L')) { + altfw_num = 0; + VB2_DEBUG("Try booting from default bootloader\n"); + } else { + altfw_num = ui->state->selected_item - menu_before_len + 1; + VB2_DEBUG("Try booting from bootloader #%u\n", altfw_num); + } + + /* VbExLegacy will not return if successful */ + VbExLegacy(altfw_num); + + VB2_DEBUG("ERROR: Alternate bootloader failed\n"); + /* TODO(b/161092974): Leverage the error dialog on error. */ + ui->error_beep = 1; + return VB2_REQUEST_UI_CONTINUE; +} + +static const struct vb2_menu *get_bootloader_menu(struct vb2_ui_context *ui) +{ + int i; + uint32_t num_bootloaders, num_items; + struct vb2_menu_item *items; + const size_t menu_before_len = + ARRAY_SIZE(developer_select_bootloader_items_before); + const size_t menu_after_len = + ARRAY_SIZE(developer_select_bootloader_items_after); + + if (ui->bootloader_menu.num_items > 0) + return &ui->bootloader_menu; + + /* TODO(b/161092974): Show error dialog if no bootloader. */ + num_bootloaders = vb2ex_get_bootloader_count(); + VB2_DEBUG("num_bootloaders: %u\n", num_bootloaders); + num_items = num_bootloaders + menu_before_len + menu_after_len; + items = malloc(num_items * sizeof(struct vb2_menu_item)); + if (!items) { + VB2_DEBUG("ERROR: malloc failed for bootloader items\n"); + return NULL; + } + + /* Copy prefix items to the begin. */ + memcpy(&items[0], + developer_select_bootloader_items_before, + menu_before_len * sizeof(struct vb2_menu_item)); + + /* Copy bootloaders. */ + for (i = 0; i < num_bootloaders; i++) { + items[i + menu_before_len].text = "Some bootloader"; + items[i + menu_before_len].action = + vb2_ui_developer_mode_boot_alternate_action; + } + + /* Copy postfix items to the end. */ + memcpy(&items[num_items - menu_after_len], + developer_select_bootloader_items_after, + menu_after_len * sizeof(struct vb2_menu_item)); + + ui->bootloader_menu.num_items = num_items; + ui->bootloader_menu.items = items; + + return &ui->bootloader_menu; +} + +static const struct vb2_screen_info developer_select_bootloader_screen = { + .id = VB2_SCREEN_DEVELOPER_SELECT_BOOTLOADER, + .name = "Select alternate bootloader", + .get_menu = get_bootloader_menu, +}; + +/******************************************************************************/ /* VB2_SCREEN_DIAGNOSTICS */ static const struct vb2_menu_item diagnostics_items[] = { @@ -925,6 +1041,7 @@ static const struct vb2_screen_info *screens[] = { &developer_to_norm_screen, &developer_boot_external_screen, &developer_invalid_disk_screen, + &developer_select_bootloader_screen, &diagnostics_screen, }; diff --git a/firmware/2lib/include/2api.h b/firmware/2lib/include/2api.h index 93ff1e33..3a6bc27b 100644 --- a/firmware/2lib/include/2api.h +++ b/firmware/2lib/include/2api.h @@ -1316,6 +1316,8 @@ enum vb2_screen { VB2_SCREEN_DEVELOPER_BOOT_EXTERNAL = 0x320, /* Invalid external disk inserted */ VB2_SCREEN_DEVELOPER_INVALID_DISK = 0x330, + /* Select alternate bootloader ("legacy boot") */ + VB2_SCREEN_DEVELOPER_SELECT_BOOTLOADER = 0x340, /* Diagnostic tools */ VB2_SCREEN_DIAGNOSTICS = 0x400, }; @@ -1375,6 +1377,13 @@ int vb2ex_physical_presence_pressed(void); uint32_t vb2ex_get_locale_count(void); /** + * Return the number of available alternate bootloaders. + * + * @returns Number of alternate bootloaders. 0 if none or on error. + */ +uint32_t vb2ex_get_bootloader_count(void); + +/** * Delay for at least the specified number of milliseconds. * * @param msec Duration in milliseconds. diff --git a/firmware/2lib/include/2ui.h b/firmware/2lib/include/2ui.h index cc21f580..2fefaf32 100644 --- a/firmware/2lib/include/2ui.h +++ b/firmware/2lib/include/2ui.h @@ -103,6 +103,9 @@ struct vb2_ui_context { /* For language selection screen. */ struct vb2_menu language_menu; + /* For bootloader selection screen. */ + struct vb2_menu bootloader_menu; + /* For error beep sound. */ int error_beep; @@ -118,6 +121,8 @@ vb2_error_t vb2_ui_developer_mode_boot_internal_action( struct vb2_ui_context *ui); vb2_error_t vb2_ui_developer_mode_boot_external_action( struct vb2_ui_context *ui); +vb2_error_t vb2_ui_developer_mode_boot_alternate_action( + struct vb2_ui_context *ui); /** * Get info struct of a screen. diff --git a/firmware/stub/vboot_api_stub.c b/firmware/stub/vboot_api_stub.c index e7ee1368..5678521c 100644 --- a/firmware/stub/vboot_api_stub.c +++ b/firmware/stub/vboot_api_stub.c @@ -49,9 +49,14 @@ uint32_t vb2ex_get_locale_count(void) return 0; } -vb2_error_t VbExGetAltFwIdxMask(void) +uint32_t vb2ex_get_bootloader_count(void) { - return VB2_SUCCESS; + return 0; +} + +uint32_t VbExGetAltFwIdxMask(void) +{ + return 0; } uint32_t VbExKeyboardRead(void) diff --git a/tests/vb2_ui_action_tests.c b/tests/vb2_ui_action_tests.c index 493162f0..79f82caa 100644 --- a/tests/vb2_ui_action_tests.c +++ b/tests/vb2_ui_action_tests.c @@ -59,6 +59,13 @@ static int mock_get_screen_info_called; static vb2_error_t mock_vbtlk_retval; static uint32_t mock_vbtlk_expected_flag; +static int mock_dev_boot_allowed; +static int mock_dev_boot_legacy_allowed; + +static int mock_vbexlegacy_called; +static enum VbAltFwIndex_t mock_altfw_num_last; +static uint32_t mock_bootloader_count; + /* Mock actions */ static uint32_t mock_action_called; static uint32_t mock_action_countdown_limit; @@ -334,6 +341,15 @@ static void reset_common_data(void) /* For VbTryLoadKernel */ mock_vbtlk_retval = VB2_ERROR_MOCK; mock_vbtlk_expected_flag = MOCK_IGNORE; + + /* For dev_boot* in 2misc.h */ + mock_dev_boot_allowed = 1; + mock_dev_boot_legacy_allowed = 0; + + /* For VbExLegacy */ + mock_vbexlegacy_called = 0; + mock_altfw_num_last = -100; + mock_bootloader_count = 2; } /* Mock functions */ @@ -445,6 +461,32 @@ vb2_error_t VbTryLoadKernel(struct vb2_context *c, uint32_t get_info_flags) return mock_vbtlk_retval; } +int vb2_dev_boot_allowed(struct vb2_context *c) +{ + return mock_dev_boot_allowed; +} + +int vb2_dev_boot_legacy_allowed(struct vb2_context *c) +{ + return mock_dev_boot_legacy_allowed; +} + +vb2_error_t VbExLegacy(enum VbAltFwIndex_t altfw_num) +{ + mock_vbexlegacy_called++; + mock_altfw_num_last = altfw_num; + + if (altfw_num <= mock_bootloader_count) + return VB2_SUCCESS; + else + return VB2_ERROR_UNKNOWN; +} + +uint32_t vb2ex_get_bootloader_count(void) +{ + return mock_bootloader_count; +} + /* Tests */ static void menu_prev_tests(void) { @@ -630,6 +672,58 @@ static void menu_select_tests(void) VB2_DEBUG("...done.\n"); } +static void vb2_ui_developer_mode_boot_alternate_action_tests(void) +{ + VB2_DEBUG("Test developer mode boot alternate action...\n"); + + /* Not allowed: not in dev mode */ + reset_common_data(); + mock_dev_boot_legacy_allowed = 1; + TEST_EQ(vb2_ui_developer_mode_boot_alternate_action(&mock_ui_context), + VB2_REQUEST_UI_CONTINUE, "not allowed: not in dev mode"); + TEST_EQ(mock_vbexlegacy_called, 0, " VbExLegacy not called"); + + /* Not allowed: dev boot not allowed */ + reset_common_data(); + ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE; + mock_dev_boot_allowed = 0; + mock_dev_boot_legacy_allowed = 1; + TEST_EQ(vb2_ui_developer_mode_boot_alternate_action(&mock_ui_context), + VB2_REQUEST_UI_CONTINUE, "not allowed: dev boot not allowed"); + TEST_EQ(mock_vbexlegacy_called, 0, " VbExLegacy not called"); + + /* Not allowed: boot legacy not allowed */ + reset_common_data(); + ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE; + TEST_EQ(vb2_ui_developer_mode_boot_alternate_action(&mock_ui_context), + VB2_REQUEST_UI_CONTINUE, + "not allowed: boot legacy not allowed"); + TEST_EQ(mock_vbexlegacy_called, 0, " VbExLegacy not called"); + + /* Allowed */ + reset_common_data(); + ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE; + mock_dev_boot_legacy_allowed = 1; + mock_ui_context.state->selected_item = 2; + TEST_EQ(vb2_ui_developer_mode_boot_alternate_action(&mock_ui_context), + VB2_REQUEST_UI_CONTINUE, "allowed"); + TEST_EQ(mock_vbexlegacy_called, 1, " VbExLegacy called once"); + TEST_EQ(mock_altfw_num_last, 2, " select bootloader #2"); + + /* CTRL+L = default bootloader */ + reset_common_data(); + ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE; + mock_dev_boot_legacy_allowed = 1; + mock_ui_context.key = VB_KEY_CTRL('L'); + mock_ui_context.state->selected_item = 4; /* Ignored */ + TEST_EQ(vb2_ui_developer_mode_boot_alternate_action(&mock_ui_context), + VB2_REQUEST_UI_CONTINUE, "allowed: ctrl+l"); + TEST_EQ(mock_vbexlegacy_called, 1, " VbExLegacy called once"); + TEST_EQ(mock_altfw_num_last, 0, " select bootloader #0"); + + VB2_DEBUG("...done.\n"); +} + static void manual_recovery_action_tests(void) { VB2_DEBUG("Testing manual recovery action...\n"); @@ -831,6 +925,9 @@ int main(void) menu_next_tests(); menu_select_tests(); + /* Screen actions */ + vb2_ui_developer_mode_boot_alternate_action_tests(); + /* Global actions */ manual_recovery_action_tests(); diff --git a/tests/vb2_ui_tests.c b/tests/vb2_ui_tests.c index d3b371c9..3fa9526f 100644 --- a/tests/vb2_ui_tests.c +++ b/tests/vb2_ui_tests.c @@ -77,6 +77,7 @@ static int mock_dev_boot_external_allowed; static int mock_vbexlegacy_called; static enum VbAltFwIndex_t mock_altfw_num_last; +static uint32_t mock_bootloader_count; static vb2_error_t mock_vbtlk_retval[32]; static uint32_t mock_vbtlk_expected_flag[32]; @@ -302,6 +303,7 @@ static void reset_common_data(enum reset_type t) /* For VbExLegacy */ mock_vbexlegacy_called = 0; mock_altfw_num_last = -100; + mock_bootloader_count = 2; /* For VbTryLoadKernel */ memset(mock_vbtlk_retval, 0, sizeof(mock_vbtlk_retval)); @@ -482,6 +484,11 @@ vb2_error_t VbExLegacy(enum VbAltFwIndex_t altfw_num) return VB2_SUCCESS; } +uint32_t vb2ex_get_bootloader_count(void) +{ + return mock_bootloader_count; +} + vb2_error_t VbTryLoadKernel(struct vb2_context *c, uint32_t get_info_flags) { int i = mock_iters; @@ -669,6 +676,25 @@ static void developer_tests(void) TEST_TRUE(mock_get_timer_last - mock_time_start < 30 * VB2_MSEC_PER_SEC, " delay aborted"); + /* Ctrl+L = boot legacy (allowed) */ + reset_common_data(FOR_DEVELOPER); + mock_dev_boot_legacy_allowed = 1; + add_mock_keypress(VB_KEY_CTRL('L')); + TEST_EQ(vb2_developer_menu(ctx), VB2_REQUEST_SHUTDOWN, + "ctrl+l = boot legacy"); + TEST_EQ(mock_vbexlegacy_called, 1, " VbExLegacy called"); + TEST_TRUE(mock_get_timer_last - mock_time_start < + 30 * VB2_MSEC_PER_SEC, " delay aborted"); + + /* Ctrl+L = boot legacy (disallowed) */ + reset_common_data(FOR_DEVELOPER); + add_mock_keypress(VB_KEY_CTRL('L')); + TEST_EQ(vb2_developer_menu(ctx), VB2_REQUEST_SHUTDOWN, + "ctrl+l = boot legacy"); + TEST_EQ(mock_vbexlegacy_called, 0, " VbExLegacy not called"); + TEST_TRUE(mock_get_timer_last - mock_time_start < + 30 * VB2_MSEC_PER_SEC, " delay aborted"); + /* VB_BUTTON_VOL_UP_LONG_PRESS = boot external */ if (DETACHABLE) { reset_common_data(FOR_DEVELOPER); @@ -1164,6 +1190,7 @@ static void developer_screen_tests(void) /* Dev mode: disabled item mask */ reset_common_data(FOR_DEVELOPER); + mock_dev_boot_legacy_allowed = 1; add_mock_vbtlk(VB2_SUCCESS, VB_DISK_FLAG_FIXED); TEST_EQ(vb2_developer_menu(ctx), VB2_SUCCESS, "dev mode screen: no disabled item mask"); @@ -1171,6 +1198,7 @@ static void developer_screen_tests(void) MOCK_IGNORE, MOCK_IGNORE, 0x0, MOCK_IGNORE); reset_common_data(FOR_DEVELOPER); + mock_dev_boot_legacy_allowed = 1; gbb.flags |= VB2_GBB_FLAG_FORCE_DEV_SWITCH_ON; add_mock_vbtlk(VB2_SUCCESS, VB_DISK_FLAG_FIXED); TEST_EQ(vb2_developer_menu(ctx), VB2_SUCCESS, @@ -1181,6 +1209,7 @@ static void developer_screen_tests(void) reset_common_data(FOR_DEVELOPER); add_mock_vbtlk(VB2_SUCCESS, VB_DISK_FLAG_FIXED); mock_dev_boot_external_allowed = 0; + mock_dev_boot_legacy_allowed = 1; TEST_EQ(vb2_developer_menu(ctx), VB2_SUCCESS, "dev mode screen: disable boot external"); DISPLAYED_EQ("dev mode screen", VB2_SCREEN_DEVELOPER_MODE, @@ -1237,8 +1266,19 @@ static void developer_screen_tests(void) DISPLAYED_NO_EXTRA(); reset_common_data(FOR_DEVELOPER); /* Select #2 by default */ + mock_dev_boot_legacy_allowed = 1; + /* #4: Alternate boot */ + add_mock_keypress(VB_KEY_DOWN); + add_mock_keypress(VB_KEY_DOWN); + add_mock_keypress(VB_KEY_ENTER); + add_mock_keypress(VB_KEY_ENTER); + TEST_EQ(vb2_developer_menu(ctx), VB2_REQUEST_SHUTDOWN, + "dev mode screen"); + TEST_EQ(mock_vbexlegacy_called, 1, " VbExLegacy called"); + + reset_common_data(FOR_DEVELOPER); /* Select #2 by default */ add_mock_vbtlk(VB2_SUCCESS, VB_DISK_FLAG_FIXED); - /* #4: Advanced options */ + /* #5: Advanced options */ add_mock_keypress(VB_KEY_DOWN); add_mock_keypress(VB_KEY_DOWN); add_mock_keypress(VB_KEY_ENTER); @@ -1248,17 +1288,17 @@ static void developer_screen_tests(void) add_mock_keypress(VB_KEY_DOWN); /* Blocked */ TEST_EQ(vb2_developer_menu(ctx), VB2_REQUEST_SHUTDOWN, "dev mode screen"); - /* #4: Advanced options */ + /* #5: Advanced options */ DISPLAYED_PASS(); DISPLAYED_PASS(); DISPLAYED_EQ("dev mode", VB2_SCREEN_DEVELOPER_MODE, - MOCK_IGNORE, 4, MOCK_IGNORE, MOCK_IGNORE); + MOCK_IGNORE, 5, MOCK_IGNORE, MOCK_IGNORE); DISPLAYED_EQ("#4: advanced options", VB2_SCREEN_ADVANCED_OPTIONS, MOCK_IGNORE, MOCK_IGNORE, MOCK_IGNORE, MOCK_IGNORE); /* End of menu */ DISPLAYED_PASS(); DISPLAYED_EQ("end of menu", VB2_SCREEN_DEVELOPER_MODE, - MOCK_IGNORE, 5, MOCK_IGNORE, MOCK_IGNORE); + MOCK_IGNORE, 6, MOCK_IGNORE, MOCK_IGNORE); DISPLAYED_NO_EXTRA(); /* Advanced options screen */ |