summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNamyoon Woo <namyoon@chromium.org>2019-12-09 14:34:55 -0800
committerCommit Bot <commit-bot@chromium.org>2020-02-22 01:46:39 +0000
commit5a8a856b0a942d3b982ef28324a924db3c7d9f40 (patch)
treeac82cccef343c83adc300d1e7c2c9bb41df095e0
parent9d9c4ca2342d09ccf241173d36d3cf2b16ed3c41 (diff)
downloadchrome-ec-5a8a856b0a942d3b982ef28324a924db3c7d9f40.tar.gz
EC-CR50 communication
This patch supports EC-CR50 communication. EC activates EC-CR50 communication by setting high DIOB3, and send a command packet to CR50 through UART_EC_TX_CR50_RX. Cr50 processes the packet, and sends a response packet back to EC. EC deactivates EC-CR50 communication by putting low DIOB3. This patch supports two kinds of EC-CR50 commands: - CR50_COMM_CMD_SET_BOOT_MODE - CR50_COMM_CMD_VERIFY_HASH Cr50 stores some of EC-EFS context in a powerdown register before deep sleep and restores it after wakeup. This patch increases flash usage by 1456 bytes. BUG=b:119329144 BRANCH=cr50 TEST=Checked "ec_comm" console command on Octopus and reworked Helios. Checked uart_stress_tester.py running without character loss. Change-Id: I23e90b9f3e860a3d198dcee718d7d11080d06e40 Signed-off-by: Namyoon Woo <namyoon@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/1961145 Reviewed-by: Daisuke Nojiri <dnojiri@chromium.org>
-rw-r--r--board/cr50/ec_comm.c258
-rw-r--r--board/cr50/ec_comm.h2
-rw-r--r--board/cr50/ec_efs.c79
-rw-r--r--chip/g/init_chip.h1
-rw-r--r--chip/g/usart.c35
5 files changed, 361 insertions, 14 deletions
diff --git a/board/cr50/ec_comm.c b/board/cr50/ec_comm.c
index c4a3603c82..c2e7bdb26d 100644
--- a/board/cr50/ec_comm.c
+++ b/board/cr50/ec_comm.c
@@ -6,21 +6,55 @@
*/
#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;
@@ -45,18 +79,200 @@ static void ec_comm_init_(void)
}
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)
{
- /* TODO(b:141143112): Implement this */
- return 0;
+ 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);
- /* TODO: Initialize packet context */
-
+ /* 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();
}
@@ -96,3 +312,37 @@ void ec_comm_block(int 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/board/cr50/ec_comm.h b/board/cr50/ec_comm.h
index bec2deb57b..263d311e63 100644
--- a/board/cr50/ec_comm.h
+++ b/board/cr50/ec_comm.h
@@ -39,5 +39,7 @@ uint16_t ec_efs_verify_hash(const char *hash_data, const uint8_t size);
/* Re-load EC Hash code from TPM Kernel Secdata */
void ec_efs_refresh(void);
+/* print EC-EFS status */
+void ec_efs_print_status(void);
#endif /* __CROS_EC_COMM_H */
diff --git a/board/cr50/ec_efs.c b/board/cr50/ec_efs.c
index d7919e105c..3b9856544f 100644
--- a/board/cr50/ec_efs.c
+++ b/board/cr50/ec_efs.c
@@ -163,6 +163,71 @@ 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 (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;
@@ -176,3 +241,17 @@ void ec_efs_refresh(void)
}
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
+}
diff --git a/chip/g/init_chip.h b/chip/g/init_chip.h
index 506dfeab21..a5c08cd3d9 100644
--- a/chip/g/init_chip.h
+++ b/chip/g/init_chip.h
@@ -23,6 +23,7 @@
* SCRATCH17 - deep sleep count
* SCRATCH18 - Preserving USB_DCFG through deep sleep
* SCRATCH19 - Preserving USB data sequencing PID through deep sleep
+ * SCRATCH20 - Preserving EC-EFS context
*
* PWRDN_SCRATCH 28 - 31 - Reserved for boot rom
*/
diff --git a/chip/g/usart.c b/chip/g/usart.c
index b597cb3f93..4329a673bc 100644
--- a/chip/g/usart.c
+++ b/chip/g/usart.c
@@ -167,7 +167,7 @@ void get_data_from_usb(struct usart_config const *config)
#ifdef BOARD_CR50
if (config->uart == UART_EC) {
/*
- * If USB-to-UART bridging is disabled, drop all input data.
+ * If USB-to-UART bridging is disabled, do not forward data.
* Otherwise, data could be pushed into UART TX FIFO, and
* transferred to EC eventually once EC-CR50 communication
* enables EC UART.
@@ -204,14 +204,35 @@ void send_data_to_usb(struct usart_config const *config)
q_room = queue_space(uart_in);
- if (!q_room)
- return;
-
mask = uart_in->buffer_units_mask;
tail = uart_in->state->tail & mask;
count = 0;
#ifdef BOARD_CR50
+ if (ec_comm_is_uart_in_packet_mode(uart)) {
+ /*
+ * Even if UART-to-USB data queue is full (count == q_room),
+ * It should drain UART queue, so that an EC packet
+ * can be processed. In this case, EC console data
+ * shall be lost anyway.
+ */
+ while (uartn_rx_available(uart)) {
+ uint8_t ch = uartn_read_char(uart);
+
+ if (ec_comm_process_packet(ch))
+ continue;
+
+ if ((count != q_room) && ec_bridge_enabled_) {
+ uart_in->buffer[tail] = ch;
+ tail = (tail + 1) & mask;
+ count++;
+ }
+ }
+ if (count)
+ queue_advance_tail(uart_in, count);
+ return;
+ }
+
/*
* If UART-to-USB bridging is not allowed, do not put any output
* data to uart_in queue.
@@ -219,12 +240,6 @@ void send_data_to_usb(struct usart_config const *config)
if ((uart == UART_EC) && !ec_bridge_enabled_)
return;
#endif /* BOARD_CR50 */
- /*
- * TODO(b/119329144): Process packet data separately,
- * and filter console data based on ccd capability.
- * if (ec_comm_is_uart_in_packet_mode(uart))
- * ...
- */
while ((count != q_room) && uartn_rx_available(uart)) {
uart_in->buffer[tail] = uartn_read_char(uart);