summaryrefslogtreecommitdiff
path: root/common/ec_efs.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/ec_efs.c')
-rw-r--r--common/ec_efs.c257
1 files changed, 257 insertions, 0 deletions
diff --git a/common/ec_efs.c b/common/ec_efs.c
new file mode 100644
index 0000000000..8f5bece989
--- /dev/null
+++ b/common/ec_efs.c
@@ -0,0 +1,257 @@
+/* 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.
+ *
+ * EC-EFS (Early Firmware Selection)
+ */
+#include "common.h"
+#include "console.h"
+#include "ec_comm.h"
+#include "crc8.h"
+#include "ec_commands.h"
+#include "extension.h"
+#include "hooks.h"
+#include "registers.h"
+#include "system.h"
+#include "tpm_nvmem.h"
+#include "tpm_nvmem_ops.h"
+#include "vboot.h"
+
+#ifdef CR50_DEV
+#define CPRINTS(format, args...) cprints(CC_TASK, "EC-EFS: " format, ## args)
+#else
+#define CPRINTS(format, args...) do { } while (0)
+#endif
+
+/*
+ * Context of EC-EFS
+ */
+static struct ec_efs_context_ {
+ uint32_t boot_mode:8; /* enum ec_efs_boot_mode */
+ uint32_t hash_is_loaded:1; /* Is EC hash loaded from nvmem */
+ uint32_t reserved:23;
+
+ uint32_t secdata_error_code;
+
+ uint8_t hash[SHA256_DIGEST_SIZE]; /* EC-RW digest */
+} ec_efs_ctx;
+
+/*
+ * Change the boot mode
+ *
+ * @param mode_val New boot mode value to change
+ */
+static void set_boot_mode_(uint8_t mode_val)
+{
+ CPRINTS("boot_mode: 0x%02x -> 0x%02x", ec_efs_ctx.boot_mode, mode_val);
+
+ ec_efs_ctx.boot_mode = mode_val;
+
+ /* Backup some ec_efs context to scratch register */
+ GREG32(PMU, PWRDN_SCRATCH20) &= ~0xff;
+ GREG32(PMU, PWRDN_SCRATCH20) |= mode_val;
+}
+
+static int load_ec_hash_(uint8_t * const ec_hash)
+{
+ struct vb2_secdata_kernel secdata;
+ const uint8_t secdata_size = sizeof(struct vb2_secdata_kernel);
+ uint8_t size_to_crc;
+ uint8_t struct_size;
+ uint8_t crc;
+
+ if (read_tpm_nvmem(KERNEL_NV_INDEX, secdata_size,
+ (void *)&secdata) != TPM_READ_SUCCESS)
+ return EC_ERROR_VBOOT_DATA_UNDERSIZED;
+
+ /*
+ * Check struct version. CRC offset may be different with old struct
+ * version
+ */
+ if (secdata.struct_version < VB2_SECDATA_KERNEL_STRUCT_VERSION_MIN)
+ return EC_ERROR_VBOOT_DATA_INCOMPATIBLE;
+
+ /* Check struct size. */
+ struct_size = secdata.struct_size;
+ if (struct_size != secdata_size)
+ return EC_ERROR_VBOOT_DATA;
+
+ /* Check CRC */
+ size_to_crc = struct_size -
+ offsetof(struct vb2_secdata_kernel, crc8) -
+ sizeof(secdata.crc8);
+ crc = crc8((uint8_t *)&secdata.reserved0, size_to_crc);
+ if (crc != secdata.crc8)
+ return EC_ERROR_CRC;
+
+ /* Read hash and copy to hash */
+ memcpy(ec_hash, secdata.ec_hash, sizeof(secdata.ec_hash));
+
+ return EC_SUCCESS;
+}
+
+/*
+ * Initialize EC-EFS context.
+ */
+static void ec_efs_init_(void)
+{
+ if (!board_has_ec_cr50_comm_support())
+ return;
+
+ /*
+ * If it is a wakeup from deep sleep, then recover some core EC-EFS
+ * context values, including the boot_mode value, from a PWRD_SCRATCH
+ * register. Otherwise, reset boot_mode.
+ */
+ if (system_get_reset_flags() & EC_RESET_FLAG_HIBERNATE)
+ set_boot_mode_(GREG32(PMU, PWRDN_SCRATCH20) & 0xff);
+ else
+ ec_efs_reset();
+
+ /* Read an EC hash in kernel secdata (TPM kernel NV index). */
+ if (ec_efs_ctx.hash_is_loaded)
+ return;
+
+ ec_efs_refresh();
+}
+DECLARE_HOOK(HOOK_INIT, ec_efs_init_, HOOK_PRIO_DEFAULT);
+
+/**
+ * TPM vendor command handler to respond with EC Boot Mode.
+ *
+ * @return VENDOR_RC_SUCCESS
+ *
+ */
+static enum vendor_cmd_rc vc_get_boot_mode_(struct vendor_cmd_params *p)
+{
+ uint8_t *buffer;
+
+ if (!board_has_ec_cr50_comm_support())
+ return VENDOR_RC_NO_SUCH_SUBCOMMAND;
+
+ buffer = (uint8_t *)p->buffer;
+ buffer[0] = (uint8_t)ec_efs_ctx.boot_mode;
+
+ p->out_size = 1;
+
+ return VENDOR_RC_SUCCESS;
+}
+DECLARE_VENDOR_COMMAND_P(VENDOR_CC_GET_BOOT_MODE, vc_get_boot_mode_);
+
+/**
+ * TPM vendor command handler to reset EC.
+ *
+ * @return VEDOR_RC_SUCCESS
+ */
+static enum vendor_cmd_rc vc_reset_ec_(struct vendor_cmd_params *p)
+{
+ if (!board_has_ec_cr50_comm_support())
+ return VENDOR_RC_NO_SUCH_SUBCOMMAND;
+
+ /*
+ * Let's reset EC a little later so that CR50 can send a TPM command
+ * to AP.
+ */
+ board_reboot_ec_deferred(50 * MSEC);
+
+ return VENDOR_RC_SUCCESS;
+}
+DECLARE_VENDOR_COMMAND_P(VENDOR_CC_RESET_EC, vc_reset_ec_);
+
+void ec_efs_reset(void)
+{
+ set_boot_mode_(EC_EFS_BOOT_MODE_NORMAL);
+}
+
+/*
+ * Change the EC boot mode value.
+ *
+ * @param data Pointer to the EC-CR50 packet
+ * @param size Data (payload) size in EC-CR50 packet
+ * @return CR50_COMM_SUCCESS if the packet has been processed successfully,
+ * CR50_COMM_ERROR_SIZE if data size is not as expected, or
+ * 0 if it does not respond to EC.
+ */
+uint16_t ec_efs_set_boot_mode(const char * const data, const uint8_t size)
+{
+ uint8_t boot_mode;
+
+ if (size != 1)
+ return CR50_COMM_ERROR_SIZE;
+
+ boot_mode = data[0];
+
+ if (ec_efs_ctx.boot_mode != EC_EFS_BOOT_MODE_NORMAL) {
+ board_reboot_ec_deferred(0);
+ return 0;
+ }
+
+ set_boot_mode_(boot_mode);
+ return CR50_COMM_SUCCESS;
+}
+
+/*
+ * Verify the given EC-FW hash against one in kernel secdata.
+ *
+ * @param data Pointer to the EC-CR50 packet
+ * @param size Data (payload) size in EC-CR50 packet
+ * @return CR50_COMM_SUCCESS if the packet has been processed successfully,
+ * CR50_COMM_ERROR_SIZE if data size is not as expected, or
+ * CR50_COMM_ERROR_BAD_PAYLOAD if the given hash and the hash in NVM
+ * are not same.
+ * 0 if it deosn't have to respond to EC.
+ */
+uint16_t ec_efs_verify_hash(const char *hash_data, const uint8_t size)
+{
+ if (size != SHA256_DIGEST_SIZE)
+ return CR50_COMM_ERROR_SIZE;
+
+ if (!ec_efs_ctx.hash_is_loaded) {
+ if (ec_efs_ctx.secdata_error_code == EC_SUCCESS)
+ ec_efs_refresh();
+
+ if (ec_efs_ctx.secdata_error_code != EC_SUCCESS)
+ return CR50_COMM_ERROR_NVMEM;
+ }
+
+ if (safe_memcmp(hash_data, ec_efs_ctx.hash, SHA256_DIGEST_SIZE)) {
+ /* Verification failed */
+ set_boot_mode_(EC_EFS_BOOT_MODE_NO_BOOT);
+ return CR50_COMM_ERROR_BAD_PAYLOAD;
+ }
+
+ if (ec_efs_ctx.boot_mode != EC_EFS_BOOT_MODE_NORMAL) {
+ board_reboot_ec_deferred(0);
+ return 0;
+ }
+
+ return CR50_COMM_SUCCESS;
+}
+
+void ec_efs_refresh(void)
+{
+ int rv;
+
+ rv = load_ec_hash_(ec_efs_ctx.hash);
+ if (rv == EC_SUCCESS) {
+ ec_efs_ctx.hash_is_loaded = 1;
+ } else {
+ ec_efs_ctx.hash_is_loaded = 0;
+ cprints(CC_SYSTEM, "load_ec_hash error: 0x%x\n", rv);
+ }
+ ec_efs_ctx.secdata_error_code = rv;
+}
+
+void ec_efs_print_status(void)
+{
+ /* EC-EFS Context */
+ ccprintf("ec_hash : %sLOADED\n",
+ ec_efs_ctx.hash_is_loaded ? "" : "UN");
+ ccprintf("secdata_error_code : 0x%08x\n",
+ ec_efs_ctx.secdata_error_code);
+
+#ifdef CR50_RELAXED
+ ccprintf("ec_hash_secdata : %ph\n",
+ HEX_BUF(ec_efs_ctx.hash, SHA256_DIGEST_SIZE));
+#endif
+}