summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVadim Bendebury <vbendeb@chromium.org>2017-03-20 21:30:11 -0700
committerchrome-bot <chrome-bot@chromium.org>2017-03-22 18:03:48 -0700
commitdc66986d0a7fd182ddcafbee00045a25709edcb4 (patch)
treeb9cb818b5539de701bc1386db03960661042073d
parente9a079f1f145fd07e45aeab9911764c8b45a0b10 (diff)
downloadchrome-ec-dc66986d0a7fd182ddcafbee00045a25709edcb4.tar.gz
cr50: add vendor command to restore corrupted header
The upcoming move of the Cr50 firmware update to the background requires postponing the activation of the newly uploaded Cr50 image to a later point in time, when the AP is ready to switch to start using the new Cr50 image. The suggested way of achieving it is as follows: when downloading the new image, the current Cr50 code modifies the header's 'image_size' field, setting its top bit to 1. This both makes the size invalid and guarantees that the new image would not verify on the following Cr50 restarts. When the AP is ready to switch to running the new Cr50 image, it will send a vendor command, which would trigger the currently running Cr50 image to restore the other image's size field. This vendor command would also communicate the timeout for the Cr50 to wait before rebooting, if there has been at least one header (ro or rw) restored. Rebooting the Cr50 would trigger rebooting the AP, resulting in the entire system running the updated firmware. Response sent to the AP will indicate if there has been a header restored and the reboot is indeed upcoming, this would allow the AP to quiesce the state of the device to handle the reboot gracefully. BRANCH=cr50 BUG=b:35580805 TEST=with the rest of the patches applied observed the system properly after the new header version was restored. Change-Id: Ia1edee67b6aa8f458810d5dc2931477cfaab1566 Signed-off-by: Vadim Bendebury <vbendeb@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/457676 Reviewed-by: Aaron Durbin <adurbin@chromium.org>
-rw-r--r--board/cr50/tpm2/upgrade.c141
-rw-r--r--chip/g/signed_header.h3
-rw-r--r--common/extension.c1
-rw-r--r--include/tpm_vendor_cmds.h1
4 files changed, 145 insertions, 1 deletions
diff --git a/board/cr50/tpm2/upgrade.c b/board/cr50/tpm2/upgrade.c
index b5d3692467..c51faf401e 100644
--- a/board/cr50/tpm2/upgrade.c
+++ b/board/cr50/tpm2/upgrade.c
@@ -3,7 +3,148 @@
* found in the LICENSE file.
*/
+#include "config.h"
+#include "console.h"
+#include "endian.h"
#include "extension.h"
+#include "flash.h"
+#include "flash_info.h"
+#include "hooks.h"
+#include "signed_header.h"
+#include "system.h"
#include "upgrade_fw.h"
+#include "util.h"
+#define CPRINTF(format, args...) cprintf(CC_EXTENSION, format, ## args)
+
+static void deferred_reboot(void)
+{
+ system_reset(SYSTEM_RESET_MANUALLY_TRIGGERED | SYSTEM_RESET_HARD);
+}
+DECLARE_DEFERRED(deferred_reboot);
+
+#define MAX_REBOOT_TIMEOUT_MS 1000
+
+/*
+ * Verify if the header at the passed in flash offset needs to be restored,
+ * and restore it if so. If this is an RO header - enable writing into that RO
+ * section (the currently active RO writes can not be enabled).
+ *
+ * Return true if a corruption was detected and restored.
+ */
+static int header_restored(uint32_t offset)
+{
+ struct SignedHeader *header;
+ uint32_t new_size;
+
+ header = (struct SignedHeader *)(CONFIG_PROGRAM_MEMORY_BASE + offset);
+
+ new_size = header->image_size;
+ if (!(new_size & TOP_IMAGE_SIZE_BIT))
+ return 0;
+
+ new_size &= ~TOP_IMAGE_SIZE_BIT;
+ /*
+ * Clear only in case the size is sensible (i.e. not set to all
+ * ones).
+ */
+ if (new_size > CONFIG_RW_SIZE)
+ return 0;
+
+ if ((offset == CONFIG_RO_MEM_OFF) || (offset == CHIP_RO_B_MEM_OFF))
+ flash_open_ro_window(offset, sizeof(struct SignedHeader));
+
+ return flash_physical_write(offset + offsetof(struct SignedHeader,
+ image_size),
+ sizeof(header->image_size),
+ (char *)&new_size) == EC_SUCCESS;
+}
+
+/*
+ * Try restoring inactive RO and RW headers, Return the number of restored
+ * headers.
+ */
+static uint8_t headers_restored(void)
+{
+ uint8_t total_restored;
+
+ /* Examine the RO first. */
+ if (system_get_ro_image_copy() == SYSTEM_IMAGE_RO)
+ total_restored = header_restored(CHIP_RO_B_MEM_OFF);
+ else
+ total_restored = header_restored(CONFIG_RO_MEM_OFF);
+
+ /* Now the RW */
+ if (system_get_image_copy() == SYSTEM_IMAGE_RW)
+ total_restored += header_restored(CONFIG_RW_B_MEM_OFF);
+ else
+ total_restored += header_restored(CONFIG_RW_MEM_OFF);
+
+ return total_restored;
+}
+
+/*
+ * The TURN_UPDATE_ON command comes with a single parameter, which is a 16 bit
+ * integer value of the number of milliseconds to wait before reboot in case
+ * there has been an update waiting.
+ *
+ * Maximum wait time is 1000 ms.
+ *
+ * If the requested timeout exceeds the allowed maximum, or the command is
+ * malformed, a protocol error is returned.
+ *
+ * If there was no errors, the number of restored headers is returned to the
+ * host in a single byte.
+ *
+ * If at least one header was restored AND the host supplied a nonzero timeout
+ * value, the H1 will be reset after this many milliseconds.
+ *
+ * Sending this command with the zero timeout value results in reporting to
+ * the host how many headers were restored, the reset is not triggered.
+ */
+static enum vendor_cmd_rc turn_update_on(enum vendor_cmd_cc code,
+ void *buf,
+ size_t input_size,
+ size_t *response_size)
+{
+ uint16_t timeout;
+ uint8_t *response;
+
+ /* Just in case. */
+ *response_size = 0;
+
+ if (input_size < sizeof(uint16_t)) {
+ CPRINTF("%s: incorrect request size %d\n",
+ __func__, input_size);
+ return VENDOR_RC_BOGUS_ARGS;
+ }
+
+ /* Retrieve the requested timeout. */
+ memcpy(&timeout, buf, sizeof(timeout));
+ timeout = be16toh(timeout);
+
+ if (timeout > MAX_REBOOT_TIMEOUT_MS) {
+ CPRINTF("%s: incorrect timeout value %d\n",
+ __func__, timeout);
+ return VENDOR_RC_BOGUS_ARGS;
+ }
+
+ *response_size = 1;
+ response = buf;
+
+ *response = headers_restored();
+ if (*response && timeout) {
+ /*
+ * At least one header was restored, and timeout is not zero,
+ * set up the reboot.
+ */
+ CPRINTF("%s: rebooting in %d ms\n", __func__, timeout);
+ hook_call_deferred(&deferred_reboot_data, timeout * MSEC);
+ }
+
+ return VENDOR_RC_SUCCESS;
+}
+DECLARE_VENDOR_COMMAND(VENDOR_CC_TURN_UPDATE_ON, turn_update_on);
+
+/* This command's implementation is shared with USB updater. */
DECLARE_EXTENSION_COMMAND(EXTENSION_FW_UPGRADE, fw_upgrade_command_handler);
diff --git a/chip/g/signed_header.h b/chip/g/signed_header.h
index 2da327c10d..c4e6726c40 100644
--- a/chip/g/signed_header.h
+++ b/chip/g/signed_header.h
@@ -50,5 +50,6 @@ struct SignedHeader {
BUILD_ASSERT(sizeof(struct SignedHeader) == 1024);
BUILD_ASSERT(offsetof(struct SignedHeader, info_chk_) == 1020);
-
+#define TOP_IMAGE_SIZE_BIT (1 << \
+ (sizeof(((struct SignedHeader *)0)->image_size) * 8 - 1))
#endif /* __CROS_EC_SIGNED_HEADER_H */
diff --git a/common/extension.c b/common/extension.c
index 63ae8f0495..f71d4557a3 100644
--- a/common/extension.c
+++ b/common/extension.c
@@ -45,6 +45,7 @@ uint32_t usb_extension_route_command(uint16_t command_code,
switch (command_code) {
#ifdef CR50_DEV
case VENDOR_CC_IMMEDIATE_RESET:
+ case VENDOR_CC_TURN_UPDATE_ON:
#endif /* defined(CR50_DEV) */
case EXTENSION_POST_RESET: /* Always need to be able to reset. */
is_allowed = 1;
diff --git a/include/tpm_vendor_cmds.h b/include/tpm_vendor_cmds.h
index 89d0b8e770..c6a04f17f2 100644
--- a/include/tpm_vendor_cmds.h
+++ b/include/tpm_vendor_cmds.h
@@ -37,6 +37,7 @@ enum vendor_cmd_cc {
/* A gap left for the deep sleep control command. */
VENDOR_CC_REPORT_TPM_STATE = 23,
+ VENDOR_CC_TURN_UPDATE_ON = 24,
LAST_VENDOR_COMMAND = 65535,
};