diff options
Diffstat (limited to 'common/ec_efs.c')
-rw-r--r-- | common/ec_efs.c | 257 |
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 +} |