summaryrefslogtreecommitdiff
path: root/common/vboot
diff options
context:
space:
mode:
authorDaisuke Nojiri <dnojiri@chromium.org>2019-10-21 10:07:48 -0700
committerCommit Bot <commit-bot@chromium.org>2020-02-09 08:31:43 +0000
commit37151225f4d791204a75173eec64ebfdaf2cbdec (patch)
treea9680e1fd0897c1ea6e0461055760c42944fdee7 /common/vboot
parenta4f7c8ef7c0b9b3206f67299414c2cbd2a5fe84a (diff)
downloadchrome-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.c297
-rw-r--r--common/vboot/vboot.c44
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();
}