summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVadim Bendebury <vbendeb@chromium.org>2017-03-20 21:30:11 -0700
committerChromeOS Commit Bot <chromeos-commit-bot@chromium.org>2018-03-13 23:50:26 +0000
commit0e8e382e22bcd7e35a25a25b9ef83df36e8615be (patch)
treebf8605333950a04f9ee347b02cb291a1499aecfe
parent332807476c4e14e83c94a0360afaef4f58a493b9 (diff)
downloadchrome-ec-0e8e382e22bcd7e35a25a25b9ef83df36e8615be.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. Original Change-Id: Ia1edee67b6aa8f458810d5dc2931477cfaab1566 Original Signed-off-by: Vadim Bendebury <vbendeb@chromium.org> Original Reviewed-on: https://chromium-review.googlesource.com/457676 Original Reviewed-by: Aaron Durbin <adurbin@chromium.org> Conflicts: common/extension.c Change-Id: Id76ec8355ccfe9cec7ee796405d94cf2433ae250 Reviewed-on: https://chromium-review.googlesource.com/958883 Reviewed-by: Vadim Bendebury <vbendeb@chromium.org> Tested-by: Vadim Bendebury <vbendeb@chromium.org> Tested-by: Marco Chen <marcochen@chromium.org> Commit-Queue: Marco Chen <marcochen@chromium.org>
-rw-r--r--board/cr50/tpm2/upgrade.c141
-rw-r--r--chip/g/signed_header.h3
-rw-r--r--common/extension.c41
-rw-r--r--include/tpm_vendor_cmds.h1
4 files changed, 185 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 d28bfad337..acc084127f 100644
--- a/common/extension.c
+++ b/common/extension.c
@@ -34,3 +34,44 @@ uint32_t extension_route_command(uint16_t command_code,
*out_size = 0;
return VENDOR_RC_NO_SUCH_COMMAND;
}
+
+uint32_t usb_extension_route_command(uint16_t command_code,
+ void *buffer,
+ size_t in_size,
+ size_t *out_size)
+{
+ int is_allowed = 0;
+
+ 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;
+ break;
+
+ default:
+ break;
+ }
+
+ if (is_allowed)
+ return extension_route_command(command_code, buffer, in_size,
+ out_size);
+
+ /* Otherwise, we don't allow this command. */
+ CPRINTF("%s: ignoring vendor cmd %d\n", __func__, command_code);
+ *out_size = 0;
+ return VENDOR_RC_NO_SUCH_COMMAND;
+}
+
+uint32_t tpm_extension_route_command(uint16_t command_code,
+ void *buffer,
+ size_t in_size,
+ size_t *out_size)
+{
+ /*
+ * TODO(aaboagye): Determine what commands (if any) should be filtered.
+ */
+ return extension_route_command(command_code, buffer, in_size, out_size);
+}
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,
};