From b4dd58141ba7d003029bec0ad3391327f32a8a39 Mon Sep 17 00:00:00 2001 From: Hsuan Ting Chen Date: Tue, 24 Mar 2020 16:51:42 +0800 Subject: vboot: Implement common UI loop Add config DETACHABLE to control the navigation in menu UI. Implement 4 screens: - VB2_SCREEN_RECOVERY_SELECT - VB2_SCREEN_RECOVERY_INVALID - VB2_SCREEN_RECOVERY_PHONE_STEP1 - VB2_SCREEN_RECOVERY_DISK_STEP1 Handling user inputs. - Shutdown request through VbExIsShutdownRequested. - Navigate with up, down, and enter key. - Navigate with volume up, volume down, and power button in DETACHABLE. Implement common UI loop, currently used for manual and non-manual recovery (developer forthcoming). BRANCH=none BUG=b:146399181 TEST=USE="menu_ui" emerge-nami depthcharge TEST=USE="menu_ui detachable" emerge-nami depthcharge TEST=make clean && make runtests TEST=DETACHABLE=1; make clean && make runtests Cq-Depend: chromium:2152212 Signed-off-by: Hsuan Ting Chen Change-Id: I4e0f2cdf053f75935529826df215b06c8a9af4cc Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/2117810 Reviewed-by: Yu-Ping Wu --- firmware/2lib/2ui.c | 309 ++++++++++++++++++++++++++++++++++-- firmware/2lib/2ui_screens.c | 107 +++++++++++++ firmware/2lib/include/2api.h | 4 + firmware/2lib/include/2ui.h | 50 ++++++ firmware/2lib/include/2ui_private.h | 41 +++++ 5 files changed, 498 insertions(+), 13 deletions(-) create mode 100644 firmware/2lib/2ui_screens.c create mode 100644 firmware/2lib/include/2ui_private.h (limited to 'firmware') diff --git a/firmware/2lib/2ui.c b/firmware/2lib/2ui.c index 5b38e08a..eab7c58d 100644 --- a/firmware/2lib/2ui.c +++ b/firmware/2lib/2ui.c @@ -12,18 +12,284 @@ #include "2return_codes.h" #include "2secdata.h" #include "2ui.h" +#include "2ui_private.h" +#include "vboot_api.h" /* For VB_SHUTDOWN_REQUEST_POWER_BUTTON */ #include "vboot_kernel.h" +#define KEY_DELAY_MS 20 /* Delay between key scans in UI loops */ + +/*****************************************************************************/ +/* Global variables */ + +enum power_button_state power_button; +int invalid_disk_last = -1; + +/*****************************************************************************/ +/* Utility functions */ + +/** + * Checks GBB flags against VbExIsShutdownRequested() shutdown request to + * determine if a shutdown is required. + * + * @param ctx Context pointer + * @param key Pressed key (VB_BUTTON_POWER_SHORT_PRESS) + * @return true if a shutdown is required, or false otherwise. + */ +int shutdown_required(struct vb2_context *ctx, uint32_t key) +{ + struct vb2_gbb_header *gbb = vb2_get_gbb(ctx); + uint32_t shutdown_request = VbExIsShutdownRequested(); + + /* + * Ignore power button push until after we have seen it released. + * This avoids shutting down immediately if the power button is still + * being held on startup. After we've recognized a valid power button + * push then don't report the event until after the button is released. + */ + if (shutdown_request & VB_SHUTDOWN_REQUEST_POWER_BUTTON) { + shutdown_request &= ~VB_SHUTDOWN_REQUEST_POWER_BUTTON; + if (power_button == POWER_BUTTON_RELEASED) + power_button = POWER_BUTTON_PRESSED; + } else { + if (power_button == POWER_BUTTON_PRESSED) + shutdown_request |= VB_SHUTDOWN_REQUEST_POWER_BUTTON; + power_button = POWER_BUTTON_RELEASED; + } + + if (key == VB_BUTTON_POWER_SHORT_PRESS) + shutdown_request |= VB_SHUTDOWN_REQUEST_POWER_BUTTON; + + /* If desired, ignore shutdown request due to lid closure. */ + if (gbb->flags & VB2_GBB_FLAG_DISABLE_LID_SHUTDOWN) + shutdown_request &= ~VB_SHUTDOWN_REQUEST_LID_CLOSED; + + /* + * In detachables, disable shutdown due to power button. + * It is used for menu selection instead. + */ + if (DETACHABLE) + shutdown_request &= ~VB_SHUTDOWN_REQUEST_POWER_BUTTON; + + return !!shutdown_request; +} + /*****************************************************************************/ -/* Entry points */ +/* Menu navigation actions */ + +/** + * Update selected_item, taking into account disabled indices (from + * disabled_item_mask). The selection does not wrap, meaning that we block + * on the 0 or max index when we hit the top or bottom of the menu. + */ +vb2_error_t menu_up_action(struct vb2_ui_context *ui) +{ + int item; + + if (!DETACHABLE && ui->key == VB_BUTTON_VOL_UP_SHORT_PRESS) + return VB2_REQUEST_UI_CONTINUE; + + item = ui->state.selected_item - 1; + while (item >= 0 && + ((1 << item) & ui->state.disabled_item_mask)) + item--; + /* Only update if item is valid */ + if (item >= 0) + ui->state.selected_item = item; + + return VB2_REQUEST_UI_CONTINUE; +} + +vb2_error_t menu_down_action(struct vb2_ui_context *ui) +{ + int item; + + if (!DETACHABLE && ui->key == VB_BUTTON_VOL_DOWN_SHORT_PRESS) + return VB2_REQUEST_UI_CONTINUE; + + item = ui->state.selected_item + 1; + while (item < ui->state.screen->num_items && + ((1 << item) & ui->state.disabled_item_mask)) + item++; + /* Only update if item is valid */ + if (item < ui->state.screen->num_items) + ui->state.selected_item = item; + + return VB2_REQUEST_UI_CONTINUE; +} + +/** + * Navigate to the target screen of the current menu item selection. + */ +vb2_error_t menu_select_action(struct vb2_ui_context *ui) +{ + const struct vb2_menu_item *menu_item; + + if (!DETACHABLE && ui->key == VB_BUTTON_POWER_SHORT_PRESS) + return VB2_REQUEST_UI_CONTINUE; + + if (ui->state.screen->num_items == 0) + return VB2_REQUEST_UI_CONTINUE; + + menu_item = &ui->state.screen->items[ui->state.selected_item]; + + VB2_DEBUG("Select <%s> menu item <%s>\n", + ui->state.screen->name, menu_item->text); + + if (menu_item->target) { + VB2_DEBUG("Changing to target screen %#x for menu item <%s>\n", + menu_item->target, menu_item->text); + change_screen(ui, menu_item->target); + } else { + VB2_DEBUG("No target set for menu item <%s>\n", + menu_item->text); + } + + return VB2_REQUEST_UI_CONTINUE; +} + +/** + * Return back to the previous screen. + */ +vb2_error_t menu_back_action(struct vb2_ui_context *ui) +{ + change_screen(ui, ui->root_screen->id); + return VB2_REQUEST_UI_CONTINUE; +} + +/*****************************************************************************/ +/* Action lookup tables */ + +static struct input_action action_table[] = { + { VB_KEY_UP, menu_up_action }, + { VB_KEY_DOWN, menu_down_action }, + { VB_KEY_ENTER, menu_select_action }, + { VB_BUTTON_VOL_UP_SHORT_PRESS, menu_up_action }, + { VB_BUTTON_VOL_DOWN_SHORT_PRESS, menu_down_action }, + { VB_BUTTON_POWER_SHORT_PRESS, menu_select_action }, + { VB_KEY_ESC, menu_back_action }, +}; + +vb2_error_t (*input_action_lookup(int key))(struct vb2_ui_context *ui) +{ + int i; + for (i = 0; i < ARRAY_SIZE(action_table); i++) + if (action_table[i].key == key) + return action_table[i].action; + return NULL; +} + +/*****************************************************************************/ +/* Core UI functions */ + +void change_screen(struct vb2_ui_context *ui, enum vb2_screen id) +{ + const struct vb2_screen_info *new_screen_info = vb2_get_screen_info(id); + int locale_id; + if (new_screen_info == NULL) { + VB2_DEBUG("ERROR: Screen entry %#x not found; ignoring\n", id); + } else { + locale_id = ui->state.locale_id; + memset(&ui->state, 0, sizeof(ui->state)); + ui->state.screen = new_screen_info; + ui->state.locale_id = locale_id; + } +} + +void validate_selection(struct vb2_screen_state *state) +{ + if ((state->selected_item == 0 && state->screen->num_items == 0) || + (state->selected_item < state->screen->num_items && + !((1 << state->selected_item) & state->disabled_item_mask))) + return; + + /* Selection invalid; select the first available non-disabled item. */ + state->selected_item = 0; + while (((1 << state->selected_item) & state->disabled_item_mask) && + state->selected_item < state->screen->num_items) + state->selected_item++; + + /* No non-disabled items available; just choose 0. */ + if (state->selected_item >= state->screen->num_items) + state->selected_item = 0; +} + +vb2_error_t ui_loop(struct vb2_context *ctx, enum vb2_screen root_screen_id, + vb2_error_t (*global_action)(struct vb2_ui_context *ui)) +{ + struct vb2_ui_context ui; + struct vb2_screen_state prev_state; + uint32_t key; + uint32_t key_flags; + vb2_error_t (*action)(struct vb2_ui_context *ui); + vb2_error_t rv; + + ui.ctx = ctx; + ui.root_screen = vb2_get_screen_info(root_screen_id); + if (ui.root_screen == NULL) + VB2_DIE("Root screen not found.\n"); + change_screen(&ui, ui.root_screen->id); + memset(&prev_state, 0, sizeof(prev_state)); + + while (1) { + /* Draw if there are state changes. */ + if (memcmp(&prev_state, &ui.state, sizeof(ui.state))) { + memcpy(&prev_state, &ui.state, sizeof(ui.state)); + + VB2_DEBUG("<%s> menu item <%s>\n", + ui.state.screen->name, + ui.state.screen->num_items ? + ui.state.screen->items[ + ui.state.selected_item].text : "null"); + + /* TODO: Stop hard-coding the locale. */ + vb2ex_display_ui(ui.state.screen->id, 0, + ui.state.selected_item, + ui.state.disabled_item_mask); + } + + /* Check for shutdown request. */ + key = VbExKeyboardReadWithFlags(&key_flags); + if (shutdown_required(ctx, key)) { + VB2_DEBUG("Shutdown required!\n"); + return VB2_REQUEST_SHUTDOWN; + } + + /* Run input action function if found. */ + action = input_action_lookup(key); + if (action) { + ui.key = key; + rv = action(&ui); + ui.key = 0; + if (rv != VB2_REQUEST_UI_CONTINUE) + return rv; + validate_selection(&ui.state); + } else if (key) { + VB2_DEBUG("Pressed key %#x, trusted? %d\n", key, + !!(key_flags & VB_KEY_FLAG_TRUSTED_KEYBOARD)); + } + + /* Run global action function if available. */ + if (global_action) { + rv = global_action(&ui); + validate_selection(&ui.state); + if (rv != VB2_REQUEST_UI_CONTINUE) + return rv; + } + + /* Delay. */ + VbExSleepMs(KEY_DELAY_MS); + } + + return VB2_SUCCESS; +} + +/*****************************************************************************/ +/* Developer mode */ vb2_error_t vb2_developer_menu(struct vb2_context *ctx) { enum vb2_dev_default_boot default_boot; - /* TODO(roccochen): Init, wait for user, and boot. */ - vb2ex_display_ui(VB2_SCREEN_BLANK, 0, 0, 0); - /* If dev mode was disabled, loop forever. */ if (!vb2_dev_boot_allowed(ctx)) while (1); @@ -45,22 +311,39 @@ vb2_error_t vb2_developer_menu(struct vb2_context *ctx) return VbTryLoadKernel(ctx, VB_DISK_FLAG_FIXED); } +/*****************************************************************************/ +/* Broken recovery */ + vb2_error_t vb2_broken_recovery_menu(struct vb2_context *ctx) { - /* TODO(roccochen): Init and wait for user to reset or shutdown. */ - vb2ex_display_ui(VB2_SCREEN_BLANK, 0, 0, 0); + return ui_loop(ctx, VB2_SCREEN_RECOVERY_BROKEN, NULL); +} - while (1); +/*****************************************************************************/ +/* Manual recovery */ - return VB2_SUCCESS; +vb2_error_t vb2_manual_recovery_menu(struct vb2_context *ctx) +{ + return ui_loop(ctx, VB2_SCREEN_RECOVERY_SELECT, try_recovery_action); } -vb2_error_t vb2_manual_recovery_menu(struct vb2_context *ctx) +vb2_error_t try_recovery_action(struct vb2_ui_context *ui) { - /* TODO(roccochen): Init and wait for user. */ - vb2ex_display_ui(VB2_SCREEN_BLANK, 0 ,0, 0); + int invalid_disk; + vb2_error_t rv = VbTryLoadKernel(ui->ctx, VB_DISK_FLAG_REMOVABLE); - while (1); + if (rv == VB2_SUCCESS) + return rv; - return VB2_SUCCESS; + /* If disk validity state changed, switch to appropriate screen. */ + invalid_disk = rv != VB2_ERROR_LK_NO_DISK_FOUND; + if (invalid_disk_last != invalid_disk) { + invalid_disk_last = invalid_disk; + if (invalid_disk) + change_screen(ui, VB2_SCREEN_RECOVERY_INVALID); + else + change_screen(ui, VB2_SCREEN_RECOVERY_SELECT); + } + + return VB2_REQUEST_UI_CONTINUE; } diff --git a/firmware/2lib/2ui_screens.c b/firmware/2lib/2ui_screens.c new file mode 100644 index 00000000..5b235c24 --- /dev/null +++ b/firmware/2lib/2ui_screens.c @@ -0,0 +1,107 @@ +/* Copyright 2020 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Firmware screen definitions. + */ + +#include "2common.h" +#include "2ui.h" + +#define MENU_ITEMS(a) \ + .num_items = ARRAY_SIZE(a), \ + .items = a + +static const struct vb2_menu_item empty_menu[] = { }; + +/******************************************************************************/ +/* VB2_SCREEN_BLANK */ + +static const struct vb2_screen_info blank_screen = { + .id = VB2_SCREEN_BLANK, + .name = "Blank", + MENU_ITEMS(empty_menu), +}; + +/******************************************************************************/ +/* VB2_SCREEN_RECOVERY_BROKEN */ + +static const struct vb2_screen_info recovery_broken_screen = { + .id = VB2_SCREEN_RECOVERY_BROKEN, + .name = "Recover broken device", + MENU_ITEMS(empty_menu), +}; + +/******************************************************************************/ +/* VB2_SCREEN_RECOVERY_SELECT */ + +static const struct vb2_menu_item recovery_select_items[] = { + { + .text = "Recovery using phone", + .target = VB2_SCREEN_RECOVERY_PHONE_STEP1, + }, + { + .text = "Recovery using external disk", + .target = VB2_SCREEN_RECOVERY_DISK_STEP1, + }, +}; + +static const struct vb2_screen_info recovery_select_screen = { + .id = VB2_SCREEN_RECOVERY_SELECT, + .name = "Recovery method selection", + MENU_ITEMS(recovery_select_items), +}; + +/******************************************************************************/ +/* VB2_SCREEN_RECOVERY_INVALID */ + +static const struct vb2_screen_info recovery_invalid_screen = { + .id = VB2_SCREEN_RECOVERY_INVALID, + .name = "Invalid recovery inserted", + MENU_ITEMS(empty_menu), +}; + +/******************************************************************************/ +/* VB2_SCREEN_RECOVERY_PHONE_STEP1 */ + +static const struct vb2_screen_info recovery_phone_step1_screen = { + .id = VB2_SCREEN_RECOVERY_PHONE_STEP1, + .name = "Phone recovery step 1", + MENU_ITEMS(empty_menu), +}; + +/******************************************************************************/ +/* VB2_SCREEN_RECOVERY_DISK_STEP1 */ + +static const struct vb2_screen_info recovery_disk_step1_screen = { + .id = VB2_SCREEN_RECOVERY_DISK_STEP1, + .name = "Disk recovery step 1", + MENU_ITEMS(empty_menu), +}; + +/******************************************************************************/ +/* + * TODO(chromium:1035800): Refactor UI code across vboot and depthcharge. + * Currently vboot and depthcharge maintain their own copies of menus/screens. + * vboot detects keyboard input and controls the navigation among different menu + * items and screens, while depthcharge performs the actual rendering of each + * screen, based on the menu information passed from vboot. + */ +static const struct vb2_screen_info *screens[] = { + &blank_screen, + &recovery_broken_screen, + &recovery_select_screen, + &recovery_invalid_screen, + &recovery_phone_step1_screen, + &recovery_disk_step1_screen, +}; + +const struct vb2_screen_info *vb2_get_screen_info(enum vb2_screen id) +{ + int i; + for (i = 0; i < ARRAY_SIZE(screens); i++) { + if (screens[i]->id == id) + return screens[i]; + } + return NULL; +} diff --git a/firmware/2lib/include/2api.h b/firmware/2lib/include/2api.h index 13d24ca9..dc6f19ab 100644 --- a/firmware/2lib/include/2api.h +++ b/firmware/2lib/include/2api.h @@ -1177,10 +1177,14 @@ enum vb2_screen { VB2_SCREEN_RECOVERY_BROKEN = 0x110, /* First recovery screen to select recovering from disk or phone */ VB2_SCREEN_RECOVERY_SELECT = 0x200, + /* Invalid recovery media inserted */ + VB2_SCREEN_RECOVERY_INVALID = 0x201, /* Recovery using disk */ VB2_SCREEN_RECOVERY_DISK_STEP1 = 0x210, VB2_SCREEN_RECOVERY_DISK_STEP2 = 0x211, VB2_SCREEN_RECOVERY_DISK_STEP3 = 0x212, + /* Recovery using phone */ + VB2_SCREEN_RECOVERY_PHONE_STEP1 = 0x220, }; /** diff --git a/firmware/2lib/include/2ui.h b/firmware/2lib/include/2ui.h index c5fdc1c6..f179024f 100644 --- a/firmware/2lib/include/2ui.h +++ b/firmware/2lib/include/2ui.h @@ -8,6 +8,56 @@ #ifndef VBOOT_REFERENCE_2UI_H_ #define VBOOT_REFERENCE_2UI_H_ +#include <2api.h> +#include <2sysincludes.h> + +/*****************************************************************************/ +/* Data structures */ + +struct vb2_screen_info { + /* Screen id */ + enum vb2_screen id; + /* Screen name for printing to console only */ + const char *name; + /* Number of menu items */ + uint16_t num_items; + /* List of menu items */ + const struct vb2_menu_item *items; +}; + +struct vb2_menu_item { + /* Text description */ + const char *text; + /* Target screen */ + enum vb2_screen target; +}; + +struct vb2_screen_state { + const struct vb2_screen_info *screen; + uint32_t locale_id; + uint32_t selected_item; + uint32_t disabled_item_mask; +}; + +struct vb2_ui_context { + struct vb2_context *ctx; + const struct vb2_screen_info *root_screen; + struct vb2_screen_state state; + uint32_t key; +}; + +/** + * Get info struct of a screen. + * + * @param screen Screen from enum vb2_screen + * + * @return screen info struct on success, NULL on error. + */ +const struct vb2_screen_info *vb2_get_screen_info(enum vb2_screen id); + +/*****************************************************************************/ +/* UI loops */ + /** * UI for a developer-mode boot. * diff --git a/firmware/2lib/include/2ui_private.h b/firmware/2lib/include/2ui_private.h new file mode 100644 index 00000000..40ee6b51 --- /dev/null +++ b/firmware/2lib/include/2ui_private.h @@ -0,0 +1,41 @@ +/* Copyright 2020 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Private declarations for 2ui.c. Defined here for testing purposes. + */ + +#include "2api.h" + +#ifndef VBOOT_REFERENCE_2UI_PRIVATE_H_ +#define VBOOT_REFERENCE_2UI_PRIVATE_H_ + +enum power_button_state { + POWER_BUTTON_HELD_SINCE_BOOT = 0, + POWER_BUTTON_RELEASED, + POWER_BUTTON_PRESSED, /* Must have been previously released */ +}; +extern enum power_button_state power_button; +int shutdown_required(struct vb2_context *ctx, uint32_t key); + +extern int invalid_disk_last; + +struct input_action { + int key; + vb2_error_t (*action)(struct vb2_ui_context *ui); +}; + +vb2_error_t menu_up_action(struct vb2_ui_context *ui); +vb2_error_t menu_down_action(struct vb2_ui_context *ui); +vb2_error_t menu_select_action(struct vb2_ui_context *ui); +vb2_error_t menu_back_action(struct vb2_ui_context *ui); +vb2_error_t (*input_action_lookup(int key))(struct vb2_ui_context *ui); + +void change_screen(struct vb2_ui_context *ui, enum vb2_screen id); +void validate_selection(struct vb2_screen_state *state); +vb2_error_t ui_loop(struct vb2_context *ctx, enum vb2_screen root_screen_id, + vb2_error_t (*global_action)(struct vb2_ui_context *ui)); + +vb2_error_t try_recovery_action(struct vb2_ui_context *ui); + +#endif /* VBOOT_REFERENCE_2UI_PRIVATE_H_ */ -- cgit v1.2.1