diff options
author | Namyoon Woo <namyoon@google.com> | 2020-03-07 21:08:56 -0800 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2020-03-10 23:19:07 +0000 |
commit | 72e5fc940ab60c5b3ea659a27033f74786c45323 (patch) | |
tree | 21382849d4983e7fb417d080d7d5ea8bb1337e81 /common | |
parent | c2aa02c9118a32d6ff4b0d5d55345bfd667ab6b8 (diff) | |
download | chrome-ec-72e5fc940ab60c5b3ea659a27033f74786c45323.tar.gz |
move ec_comm implementation to common directory
This patch moves ec_comm.c and ec_efs.c from board/cr50 to common/,
so that they can be shared with other board configuration (like host).
This is to build unittest for those files.
BUG=none
BRANCH=cr50
TEST=make buildall -j
Signed-off-by: Namyoon Woo <namyoon@chromium.org>
Change-Id: I67ac313054ebe4604848a176f0a42e3483957e74
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2094076
Reviewed-by: Vadim Bendebury <vbendeb@chromium.org>
Diffstat (limited to 'common')
-rw-r--r-- | common/build.mk | 1 | ||||
-rw-r--r-- | common/ec_comm.c | 348 | ||||
-rw-r--r-- | common/ec_efs.c | 257 |
3 files changed, 606 insertions, 0 deletions
diff --git a/common/build.mk b/common/build.mk index 4aa8dc3abd..5e803b970b 100644 --- a/common/build.mk +++ b/common/build.mk @@ -65,6 +65,7 @@ common-$(CONFIG_DEDICATED_RECOVERY_BUTTON)+=button.o common-$(CONFIG_DEVICE_EVENT)+=device_event.o common-$(CONFIG_DEVICE_STATE)+=device_state.o common-$(CONFIG_DPTF)+=dptf.o +common-$(CONFIG_EC_EFS_SUPPORT)+=ec_comm.o ec_efs.o common-$(CONFIG_EC_EC_COMM_MASTER)+=ec_ec_comm_master.o common-$(CONFIG_EC_EC_COMM_SLAVE)+=ec_ec_comm_slave.o common-$(CONFIG_HOSTCMD_ESPI)+=espi.o diff --git a/common/ec_comm.c b/common/ec_comm.c new file mode 100644 index 0000000000..c2e7bdb26d --- /dev/null +++ b/common/ec_comm.c @@ -0,0 +1,348 @@ +/* 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-CR50 communication + */ +#include "common.h" +#include "console.h" +#include "crc8.h" +#include "ec_comm.h" +#include "gpio.h" +#include "hooks.h" +#include "system.h" +#include "task.h" +#include "timer.h" +#include "uartn.h" +#include "usart.h" +#include "vboot.h" + +#define CPRINTS(format, args...) cprints(CC_TASK, "EC-COMM: " format, ## args) + +/* + * EC communications state machine (FSM) is supposed to be active between TPM + * reset (including power on) and the moment EC_IN_RW transition is latched. + * + * Each packet is supposed to be prepended by at least one synchronization + * character (0xEC). If packet does not verify, the entire stream contents, + * including all sync characters is delivered to the USB bridge. + * + * Meaning of the states included in the enum below is as follows: + */ +enum ec_comm_phase { + PHASE_READY_COMM = 0, + PHASE_RECEIVING_PREAMBLE, + PHASE_RECEIVING_HEADER, + PHASE_RECEIVING_DATA, +}; + +/* + * Context of EC-CR50 communication + */ +static struct ec_comm_context_ { + uint8_t uart; /* Current UART ID in packet mode. */ + /* UART_NULL if no UART is in packet mode */ + uint8_t phase; /* enum ec_comm_phase */ + uint8_t preamble_count; + uint8_t bytes_received; + + uint8_t bytes_expected; + uint8_t reserved[1]; + + uint16_t last_resp; + + union { + struct cr50_comm_packet ph; + uint8_t packet[CR50_COMM_MAX_PACKET_SIZE]; + }; +} ec_comm_ctx; + + +/* + * Initialize EC-CR50 communication context. + */ +static void ec_comm_init_(void) +{ + ec_comm_ctx.uart = UART_NULL; + + if (!board_has_ec_cr50_comm_support()) + return; + + CPRINTS("Initializtion"); + + gpio_enable_interrupt(GPIO_EC_PACKET_MODE_EN); + gpio_enable_interrupt(GPIO_EC_PACKET_MODE_DIS); + + /* If DIOB3 is already high, then enable the packet mode. */ + if (gpio_get_level(GPIO_EC_PACKET_MODE_EN)) + ec_comm_packet_mode_en(GPIO_EC_PACKET_MODE_EN); +} +DECLARE_HOOK(HOOK_INIT, ec_comm_init_, HOOK_PRIO_DEFAULT + 1); + +/* + * Process the received packet. + * + * @param ph Pointer to the received EC-Cr50 packet. + * @param bytes Total bytes of the received EC-Cr50 packet. + * @return CR50_COMM_SUCCESS if the packet has been processed successfully, + * CR50_COMM_ERROR_CRC if CRC is incorrect, + * CR50_COMM_ERROR_UNDEFINED_CMD if the cmd is unknown, + * 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. + */ +static uint16_t decode_packet_(const struct cr50_comm_packet *ph, int bytes) +{ + const int offset_cmd = offsetof(struct cr50_comm_packet, cmd); + uint8_t crc8_calc; + uint16_t response; + + /* Verify CRC. */ + crc8_calc = crc8((const uint8_t *)&ph->cmd, bytes - offset_cmd); + if (crc8_calc != ph->crc) + return CR50_COMM_ERROR_CRC; + + /* Execute the command. */ + switch (ph->cmd) { + case CR50_COMM_CMD_SET_BOOT_MODE: + response = ec_efs_set_boot_mode(ph->data, ph->size); + break; + + case CR50_COMM_CMD_VERIFY_HASH: + response = ec_efs_verify_hash(ph->data, ph->size); + break; + + default: + response = CR50_COMM_ERROR_UNDEFINED_CMD; + } + + return response; +} + +/* + * Transfer a 'response' to 'uart' port using DIOB3 pin. + * + * @param response Response code to return to EC. Should be one of + * CR50_COMM_RESPONSE codes in include/vboot.h. + */ +static void transfer_response_to_ec_(uint16_t response) +{ + uint8_t *ptr_resp = (uint8_t *)&response; + int uart = ec_comm_ctx.uart; + + /* Send the response to EC in little endian. */ + uartn_write_char(uart, ptr_resp[0]); + uartn_write_char(uart, ptr_resp[1]); + + uartn_tx_flush(uart); /* Flush from UART2_TX to DIOB3 */ + + ec_comm_ctx.last_resp = response; +} + +int ec_comm_process_packet(uint8_t ch) +{ + uint16_t response = 0; + + switch (ec_comm_ctx.phase) { + case PHASE_READY_COMM: + /* + * if it is not the preamble, then return 0 so that ch can be + * forwarded to USB. + */ + if (ch != CR50_COMM_PREAMBLE) { + ec_comm_ctx.preamble_count = 0; + return 0; + } + + /* + * Forward ch to USB even if it is CR50_COMM_PREAMBLE, because + * it is not yet sure whether it is a preamble or not. + * Forwarding 0xec to USB is not harmful anyway. + */ + if (++ec_comm_ctx.preamble_count < MIN_LENGTH_PREAMBLE) + return 0; + + ec_comm_ctx.phase = PHASE_RECEIVING_PREAMBLE; + break; + + case PHASE_RECEIVING_PREAMBLE: + if (ch == CR50_COMM_PREAMBLE) { + ++ec_comm_ctx.preamble_count; + break; + } + + /* + * First non-preamble character. Reset received bytes and + * fall through to receive header. + */ + ec_comm_ctx.bytes_received = 0; + ec_comm_ctx.bytes_expected = sizeof(struct cr50_comm_packet); + ec_comm_ctx.phase = PHASE_RECEIVING_HEADER; + /* FALLTHROUGH */ + + case PHASE_RECEIVING_HEADER: + /* + * Note: EC-CR50 communication is designed to perform in + * little endian. + */ + ec_comm_ctx.packet[ec_comm_ctx.bytes_received++] = ch; + + if (ec_comm_ctx.bytes_received < ec_comm_ctx.bytes_expected) + break; + + /* The header has been received. Let's parse it. */ + if (ec_comm_ctx.ph.magic != CR50_COMM_MAGIC_WORD) { + response = CR50_COMM_ERROR_MAGIC; + break; + } + + /* Check struct_version */ + /* + * Note: if CR50_COMM_VERSION gets bigger than 0x00, + * you should implement how to handle backward + * compatibility. + */ + if (ec_comm_ctx.ph.version != CR50_COMM_VERSION) { + response = CR50_COMM_ERROR_STRUCT_VERSION; + break; + } + + if (ec_comm_ctx.ph.size == 0) { + /* Data size zero, then process the packet now. */ + response = decode_packet_(&ec_comm_ctx.ph, + ec_comm_ctx.bytes_received); + } else if ((ec_comm_ctx.ph.size + ec_comm_ctx.bytes_expected) > + CR50_COMM_MAX_PACKET_SIZE) { + response = CR50_COMM_ERROR_SIZE; + } else { + ec_comm_ctx.bytes_expected += ec_comm_ctx.ph.size; + ec_comm_ctx.phase = PHASE_RECEIVING_DATA; + } + break; + + case PHASE_RECEIVING_DATA: + ec_comm_ctx.packet[ec_comm_ctx.bytes_received++] = ch; + + /* The EC is done sending the packet, let's process it. */ + if (ec_comm_ctx.bytes_received >= ec_comm_ctx.bytes_expected) + response = decode_packet_(&ec_comm_ctx.ph, + ec_comm_ctx.bytes_received); + break; + + default: + /* + * It is in the unknown phase. + * Let's turn the phase back to READY_COMM, and + * return 0 so that ch can be forwarded to USB. + */ + ec_comm_ctx.phase = PHASE_READY_COMM; + ec_comm_ctx.preamble_count = 0; + return 0; + } + + if (response) { + transfer_response_to_ec_(response); + +#ifdef CR50_RELAXED + CPRINTS("decoded a packet"); + CPRINTS("header : 0x%ph", + HEX_BUF((uint8_t *)&ec_comm_ctx.ph, + sizeof(struct cr50_comm_packet))); + CPRINTS("body : 0x%ph", + HEX_BUF((uint8_t *)ec_comm_ctx.ph.data, + ec_comm_ctx.ph.size)); + /* Let's response to EC */ + CPRINTS("response: 0x%04x", response); +#endif + /* + * If it reaches here, EC comm is either broken or one packet + * was well-processed. Let's turn the phase back to READY_COMM. + */ + ec_comm_ctx.phase = PHASE_READY_COMM; + ec_comm_ctx.preamble_count = 0; + } + + return 1; +} + +void ec_comm_packet_mode_en(enum gpio_signal unsed) +{ + disable_sleep(SLEEP_MASK_EC_CR50_COMM); + + /* Initialize packet context */ + ec_comm_ctx.phase = PHASE_READY_COMM; + ec_comm_ctx.preamble_count = 0; + ec_comm_ctx.uart = UART_EC; /* Enable Packet Mode */ + ccd_update_state(); +} + +void ec_comm_packet_mode_dis(enum gpio_signal unsed) +{ + ec_comm_ctx.uart = UART_NULL; /* Disable Packet Mode. */ + ccd_update_state(); + + enable_sleep(SLEEP_MASK_EC_CR50_COMM); +} + +int ec_comm_is_uart_in_packet_mode(int uart) +{ + return uart == ec_comm_ctx.uart; +} + +void ec_comm_block(int block) +{ + static int is_blocked; + + if (is_blocked == block) + return; + + if (block) { + gpio_disable_interrupt(GPIO_EC_PACKET_MODE_EN); + gpio_disable_interrupt(GPIO_EC_PACKET_MODE_DIS); + + if (ec_comm_is_uart_in_packet_mode(UART_EC)) + ec_comm_packet_mode_dis(GPIO_EC_PACKET_MODE_DIS); + } else { + gpio_enable_interrupt(GPIO_EC_PACKET_MODE_EN); + gpio_enable_interrupt(GPIO_EC_PACKET_MODE_DIS); + } + + is_blocked = block; + + /* Note: ccd_update_state() should be called to change UART status. */ +} + +/* + * A console command, printing EC-CR50-Comm status. + */ +static int command_ec_comm(int argc, char **argv) +{ + if (!board_has_ec_cr50_comm_support()) { + ccprintf("No EC-CR50 comm support\n"); + return EC_ERROR_INVAL; + } + + /* + * EC Packet Context + */ + ccprintf("uart : 0x%02x\n", ec_comm_ctx.uart); + ccprintf("packet mode : %sABLED\n", + ec_comm_is_uart_in_packet_mode(UART_EC) ? "EN" : "DIS"); + + ccprintf("phase : %d\n", ec_comm_ctx.phase); + ccprintf("preamble_count : %d\n", ec_comm_ctx.preamble_count); + ccprintf("bytes_received : %d\n", ec_comm_ctx.bytes_received); + ccprintf("bytes_expected : %d\n", ec_comm_ctx.bytes_expected); +#ifdef CR50_RELAXED + ccprintf("packet:\n"); + hexdump((uint8_t *)ec_comm_ctx.packet, CR50_COMM_MAX_PACKET_SIZE); +#endif /* CR50_RELAXED */ + ccprintf("response : 0x%04x\n", ec_comm_ctx.last_resp); + ccprintf("\n"); + ec_efs_print_status(); + + return EC_SUCCESS; +} +DECLARE_SAFE_CONSOLE_COMMAND(ec_comm, command_ec_comm, NULL, + "Dump EC-CR50-comm status"); 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 +} |