diff options
author | Daisuke Nojiri <dnojiri@chromium.org> | 2019-10-21 10:07:48 -0700 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2020-02-09 08:31:43 +0000 |
commit | 37151225f4d791204a75173eec64ebfdaf2cbdec (patch) | |
tree | a9680e1fd0897c1ea6e0461055760c42944fdee7 /common/vboot | |
parent | a4f7c8ef7c0b9b3206f67299414c2cbd2a5fe84a (diff) | |
download | chrome-ec-37151225f4d791204a75173eec64ebfdaf2cbdec.tar.gz |
EFS2: Implement Early Firmware Selection ver.2
EFS v1 allowed Chromeboxes to verify RW without AP. EFS v2 will bring
the benefts to Chromebooks, which are:
- Reduce RO dependency and presence. Allow more code to be updated
in the fields.
- Remove jumptag and workarounds needed for late sysjump.
Major imporvements over v1 are:
- No A/B slot required.
- No signature in RW or public key in RO.
- Rollback-attack protection.
- Verifies only RW being used instead of whole RW section.
For battery-equipped devices, additional benefts are:
- Immediate boot on drained battery.
- Support recovery mode regardless of battery condition.
- Faster charge in S5/G3.
EC-Cr50 communication is based on the shared UART (go/ec-cr50-comm).
EFS2 is documented in go/ec-efs2.
Signed-off-by: Daisuke Nojiri <dnojiri@chromium.org>
BUG=chromium:1045217,chromium:141143112
BRANCH=none
TEST=Boot Helios in NORMAL/NO_BOOT/NO_BOOT_RECOVERY/RECOVERY mode.
TEST=Wake up EC from hibernate.
TEST=Make EC assert PACKET_MODE to wake up Cr50 from deepsleep.
Change-Id: I98a4fe1ecc59d106810a75daec3c424f953ff880
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2015357
Reviewed-by: Daisuke Nojiri <dnojiri@chromium.org>
Commit-Queue: Daisuke Nojiri <dnojiri@chromium.org>
Tested-by: Daisuke Nojiri <dnojiri@chromium.org>
Auto-Submit: Daisuke Nojiri <dnojiri@chromium.org>
Diffstat (limited to 'common/vboot')
-rw-r--r-- | common/vboot/efs2.c | 297 | ||||
-rw-r--r-- | common/vboot/vboot.c | 44 |
2 files changed, 307 insertions, 34 deletions
diff --git a/common/vboot/efs2.c b/common/vboot/efs2.c new file mode 100644 index 0000000000..6cc2bc4692 --- /dev/null +++ b/common/vboot/efs2.c @@ -0,0 +1,297 @@ +/* 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. + */ + +/* + * Early Firmware Selection ver.2. + * + * Verify and jump to a RW image. Register boot mode to Cr50. + */ + +#include "battery.h" +#include "chipset.h" +#include "clock.h" +#include "compile_time_macros.h" +#include "console.h" +#include "crc8.h" +#include "flash.h" +#include "sha256.h" +#include "system.h" +#include "task.h" +#include "usb_pd.h" +#include "uart.h" +#include "vboot.h" +#include "vboot_hash.h" + +#define CPRINTS(format, args...) cprints(CC_VBOOT,"VB " format, ## args) +#define CPRINTF(format, args...) cprintf(CC_VBOOT,"VB " format, ## args) + +static const char *boot_mode_to_string(uint8_t mode) +{ + static const char *boot_mode_str[] = { + [BOOT_MODE_NORMAL] = "NORMAL", + [BOOT_MODE_NO_BOOT] = "NO_BOOT", + }; + if (mode < ARRAY_SIZE(boot_mode_str)) + return boot_mode_str[mode]; + return "UNDEF"; +} + +/* + * Check whether the session has successfully ended or not. ERR_TIMEOUT is + * excluded because it's an internal error produced by EC itself. + */ +static bool is_valid_cr50_response(enum cr50_comm_err code) +{ + return code != CR50_COMM_ERR_TIMEOUT + && (code >> 8) == CR50_COMM_ERR_PREFIX; +} + +static void enable_packet_mode(bool enable) +{ + /* + * This can be done by set_flags(INPUT|PULL_UP). We don't need it now + * because Cr50 never initiates communication. + */ + gpio_set_level(GPIO_PACKET_MODE_EN, enable ? 1 : 0); +} + +static enum cr50_comm_err send_to_cr50(const uint8_t *data, size_t size) +{ + timestamp_t until; + int i, timeout = 0; + struct cr50_comm_response res = {}; + + /* This will wake up (if it's sleeping) and interrupt Cr50. */ + enable_packet_mode(true); + + uart_flush_output(); + uart_clear_input(); + + /* + * Send packet. No traffic control, assuming Cr50 consumes stream much + * faster. TX buffer shouldn't overflow because it's cleared above and + * much bigger than the max packet size. + * + * Disable interrupts so that the data frame will be stored in the Tx + * buffer in one piece. + */ + interrupt_disable(); + uart_put_raw(data, size); + interrupt_enable(); + + uart_flush_output(); + + until.val = get_time().val + CR50_COMM_TIMEOUT; + + /* Make sure console task won't steal the response (in case in the + * future we should exchange packets after tasks start). */ + task_disable_task(TASK_ID_CONSOLE); + + /* Wait for response from Cr50 */ + for (i = 0; i < sizeof(res); i++) { + while (!timeout) { + int c = uart_getc(); + if (c != -1) { + res.error = res.error | c << (i*8); + break; + } + msleep(1); + timeout = timestamp_expired(until, NULL); + } + } + + task_enable_task(TASK_ID_CONSOLE); + + /* Exit packet mode */ + enable_packet_mode(false); + + if (timeout) { + CPRINTS("Timeout"); + return CR50_COMM_ERR_TIMEOUT; + } + + CPRINTS("Received 0x%04x", res.error); + + return res.error; +} + +static enum cr50_comm_err cmd_to_cr50(enum cr50_comm_cmd cmd, + const uint8_t *data, size_t size) +{ + /* + * This is on the stack instead of .bss because vboot_main currently is + * called only once (from main). Keeping the space unused in .bss would + * be wasteful. + */ + struct { + uint8_t preamble[CR50_UART_RX_BUFFER_SIZE]; + uint8_t packet[CR50_COMM_MAX_REQUEST_SIZE]; + } __packed s; + struct cr50_comm_request *p = (struct cr50_comm_request *)s.packet; + int retry = CR50_COMM_MAX_RETRY; + enum cr50_comm_err rv; + + /* compose a frame = preamble + packet */ + memset(s.preamble, CR50_COMM_PREAMBLE, sizeof(s.preamble)); + p->magic = CR50_PACKET_MAGIC; + p->struct_version = CR50_COMM_PACKET_VERSION; + p->type = cmd; + p->size = size; + memcpy(p->data, data, size); + p->crc = crc8((uint8_t *)&p->type, + sizeof(p->type) + sizeof(p->size) + size); + + do { + rv = send_to_cr50((uint8_t *)&s, + sizeof(s.preamble) + sizeof(*p) + p->size); + if (is_valid_cr50_response(rv)) + break; + msleep(5); + } while (--retry); + + return rv; +} + +static enum cr50_comm_err verify_hash(void) +{ + const uint8_t *hash; + int rv; + + rv = vboot_get_rw_hash(&hash); + if (rv) + return rv; + + CPRINTS("Verifying hash"); + return cmd_to_cr50(CR50_COMM_CMD_VERIFY_HASH, hash, SHA256_DIGEST_SIZE); +} + +static enum cr50_comm_err set_boot_mode(uint8_t mode) +{ + enum cr50_comm_err rv; + + CPRINTS("Setting boot mode to %s(%d)", boot_mode_to_string(mode), mode); + rv = cmd_to_cr50(CR50_COMM_CMD_SET_BOOT_MODE, + &mode, sizeof(enum boot_mode)); + if (rv != CR50_COMM_SUCCESS) + CPRINTS("Failed to set boot mode"); + return rv; +} + +static bool pd_comm_enabled; + +static void enable_pd(void) +{ + CPRINTS("Enable USB-PD"); + pd_comm_enabled = true; +} + +bool vboot_allow_usb_pd(void) +{ + return pd_comm_enabled; +} + +__overridable void show_critical_error(void) +{ + CPRINTS("%s", __func__); +} + +static void verify_and_jump(void) +{ + enum cr50_comm_err rv = verify_hash(); + + switch (rv) { + case CR50_COMM_ERR_BAD_PAYLOAD: + /* Cr50 should have set NO_BOOT. */ + CPRINTS("Hash mismatch"); + enable_pd(); + break; + case CR50_COMM_SUCCESS: + rv = system_run_image_copy(SYSTEM_IMAGE_RW); + CPRINTS("Failed to jump (0x%x)", rv); + show_critical_error(); + break; + default: + CPRINTS("Failed to verify RW (0x%x)", rv); + show_critical_error(); + } +} + +__overridable void show_power_shortage(void) +{ + CPRINTS("%s", __func__); +} + +static int is_manual_recovery(void) +{ + return host_is_event_set(EC_HOST_EVENT_KEYBOARD_RECOVERY); +} + +static bool is_battery_ready(void) +{ + /* TODO: Add battery check (https://crbug.com/1045216) */ + return true; +} + +void vboot_main(void) +{ + CPRINTS("Main"); + + if (system_is_in_rw()) { + /* + * We come here and immediately return. LED shows power shortage + * but it will be immediately corrected if the adapter can + * provide enough power. + */ + CPRINTS("Already in RW"); + show_power_shortage(); + return; + } + + if (is_manual_recovery()) { + CPRINTS("In recovery mode"); + if (!IS_ENABLED(CONFIG_BATTERY) + && !IS_ENABLED(HAS_TASK_KEYSCAN)) { + /* + * For Chromeboxes, we relax security by allowing PD in + * RO. Attackers don't gain meaningful advantage on + * built-in-keyboard-less systems. + * + * Alternatively, we can use NO_BOOT to show a firmware + * screen, strictly requiring BJ adapter and keeping PD + * disabled. + */ + enable_pd(); + return; + } + + /* + * If battery is drained or bad, we will boot in NO_BOOT mode to + * inform the user of the problem. + */ + if (!is_battery_ready()) { + CPRINTS("Battery not ready or bad"); + if (set_boot_mode(BOOT_MODE_NO_BOOT) == + CR50_COMM_SUCCESS) + enable_pd(); + } + + /* We'll enter recovery mode immediately, later, or never. */ + return; + } + + verify_and_jump(); + + /* + * EFS failed. EC-RO may be able to boot AP if: + * + * - Battery is charged or + * - AC adapter supply in RO >= Boot threshold or + * - BJ adapter is plugged. + * + * Once AP boots, software sync will fix the mismatch. If that's the + * reason of the failure, we won't come back here next time. + */ + CPRINTS("Exit"); +} diff --git a/common/vboot/vboot.c b/common/vboot/vboot.c index fad338229f..93557e7529 100644 --- a/common/vboot/vboot.c +++ b/common/vboot/vboot.c @@ -17,6 +17,7 @@ #include "host_command.h" #include "rsa.h" #include "rwsig.h" +#include "stdbool.h" #include "sha256.h" #include "shared_mem.h" #include "system.h" @@ -32,20 +33,6 @@ static int has_matrix_keyboard(void) return 0; } -static int is_efs_supported(void) -{ -#ifdef CONFIG_VBOOT_EFS - return 1; -#else - return 0; -#endif -} - -static int is_low_power_ap_boot_supported(void) -{ - return 0; -} - static int verify_slot(enum system_image_copy_t slot) { const struct vb21_packed_key *vb21_key; @@ -168,15 +155,14 @@ static int verify_and_jump(void) } /* Request more power: charging battery or more powerful AC adapter */ -static void request_power(void) +__overridable void show_power_shortage(void) { CPRINTS("%s", __func__); } -static void request_recovery(void) +__overridable void show_critical_error(void) { CPRINTS("%s", __func__); - led_critical(); } static int is_manual_recovery(void) @@ -184,9 +170,9 @@ static int is_manual_recovery(void) return host_is_event_set(EC_HOST_EVENT_KEYBOARD_RECOVERY); } -static int pd_comm_enabled; +static bool pd_comm_enabled; -int vboot_need_pd_comm(void) +bool vboot_allow_usb_pd(void) { return pd_comm_enabled; } @@ -202,7 +188,7 @@ void vboot_main(void) * provide enough power. */ CPRINTS("Already in RW. Wait for power..."); - request_power(); + show_power_shortage(); return; } @@ -214,14 +200,14 @@ void vboot_main(void) * though PD communication is enabled. */ CPRINTS("HW-WP not asserted."); - request_power(); + show_power_shortage(); return; } if (is_manual_recovery()) { CPRINTS("Manual recovery"); if (battery_is_present() || has_matrix_keyboard()) { - request_power(); + show_power_shortage(); return; } /* We don't request_power because we don't want to assume all @@ -230,17 +216,7 @@ void vboot_main(void) * don't gain meaningful advantage on devices without a matrix * keyboard */ CPRINTS("Enable PD comm"); - pd_comm_enabled = 1; - return; - } - - if (!is_efs_supported()) { - if (is_low_power_ap_boot_supported()) - /* If a device supports this feature, AP's boot power - * threshold should be set low. That will let EC-RO - * boot AP and softsync take care of RW verification. */ - return; - request_power(); + pd_comm_enabled = true; return; } @@ -250,5 +226,5 @@ void vboot_main(void) clock_enable_module(MODULE_FAST_CPU, 0); /* Failed to jump. Need recovery. */ - request_recovery(); + show_critical_error(); } |