diff options
author | Bill Richardson <wfrichar@chromium.org> | 2016-11-16 15:37:44 -0800 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2016-11-19 00:14:24 -0800 |
commit | 93388bc758cf7c2cfc70f63fbe42c25b1ed7fb38 (patch) | |
tree | 49310e932f37812d771166b9f3778906f37f232f | |
parent | 7f8ad649dd1a417a746739e86bce7dc3f0a40c1b (diff) | |
download | chrome-ec-93388bc758cf7c2cfc70f63fbe42c25b1ed7fb38.tar.gz |
Cr50: Prevent rebooting when unlocking the console
When the console is unlocked, the function nvmem_wipe_or_reboot()
is called. This holds the EC in reset, clears nvmem, resets the
TPM task, then releases the EC. Nothing about that should cause
the Cr50 to reboot, but it was happening anyway.
This CL addresses several subtle problems.
First, holding the EC in reset invoked the sys_rst_asserted()
interrupt handler, triggering extra (and early) calls to
tpm_reset(). That should wait until after nvmem is cleared, and
only be called once.
Second, the intentional call to tpm_reset() caused the current
(HOOKS) task to wait for the operation to finish, but it didn't
wait long enough (recreating the endorsement certs can take over
a second). When the task_wake_event() returned, a timeout was
indicated in addition to the completion event.
Third, because we checked for the timeout first, we reported an
error even though tpm_reset() completed successfully, just slower
than we expected. We didn't get the timeout event before it
completed because the TPM task runs at a higher priority.
This CL addresses all of these cases, and makes wiping nvmem the
responsibility of the TPM task as well, so that it can do it when it's
ready.
Note that the EC (and thus AP too) will be held in reset while nvmem is
erased.
BUG=chrome-os-partner:59902
BRANCH=none
TEST=make buildall, manual tests
From the Cr50 console, run the "lock on" and "lock off" commands.
Try it both with and without the battery present. Observe that
the Cr50 no longer reboots just because the console unlocks.
Change-Id: I65a342502718acc5b9bda8c6f28dcd27e8f027f7
Signed-off-by: Bill Richardson <wfrichar@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/411379
Reviewed-by: Vadim Bendebury <vbendeb@chromium.org>
-rw-r--r-- | board/cr50/board.c | 23 | ||||
-rw-r--r-- | board/cr50/wp.c | 17 | ||||
-rw-r--r-- | common/tpm_registers.c | 84 | ||||
-rw-r--r-- | include/nvmem.h | 7 | ||||
-rw-r--r-- | include/tpm_registers.h | 21 |
5 files changed, 92 insertions, 60 deletions
diff --git a/board/cr50/board.c b/board/cr50/board.c index 6f753559ee..7ce2633e76 100644 --- a/board/cr50/board.c +++ b/board/cr50/board.c @@ -359,7 +359,7 @@ void sys_rst_asserted(enum gpio_signal signal) system_reset(SYSTEM_RESET_HARD); /* This will never return. */ /* Re-initialize the TPM software state */ - tpm_reset(); + tpm_reset(0, 0); } void assert_sys_rst(void) @@ -414,27 +414,6 @@ int is_ec_rst_asserted(void) return GREAD(RBOX, ASSERT_EC_RST); } -void nvmem_wipe_or_reboot(void) -{ - /* - * Blindly zapping the TPM space while the AP is awake and poking at it - * will bork the TPM task and the AP itself, so force the whole system - * off by holding the EC in reset. - */ - assert_ec_rst(); - - /* - * If we can't clear the NVMEM or can't reset the TPM task, something - * is unexpectedly wrong. To be safe, let's reboot the Cr50 (which also - * reboots the EC and AP). - */ - if (nvmem_setup(0) != EC_SUCCESS || tpm_reset() != 1) - system_reset(SYSTEM_RESET_HARD); - - /* Wipe & reset is complete. Allow the EC and AP to reboot */ - deassert_ec_rst(); -} - void nvmem_compute_sha(uint8_t *p_buf, int num_bytes, uint8_t *p_sha, int sha_len) { diff --git a/board/cr50/wp.c b/board/cr50/wp.c index d667ecc74e..69b88c4a71 100644 --- a/board/cr50/wp.c +++ b/board/cr50/wp.c @@ -13,6 +13,7 @@ #include "system.h" #include "task.h" #include "timer.h" +#include "tpm_registers.h" #define CPRINTS(format, args...) cprints(CC_RBOX, format, ## args) #define CPRINTF(format, args...) cprintf(CC_RBOX, format, ## args) @@ -58,7 +59,21 @@ static void lock_the_console(void) static void unlock_the_console(void) { - nvmem_wipe_or_reboot(); + int rc; + + /* Wipe the TPM's memory and reset the TPM task. */ + rc = tpm_reset(1, 1); + if (rc != EC_SUCCESS) { + /* + * If anything goes wrong (which is unlikely), we REALLY don't + * want to unlock the console. It's possible to fail without + * the TPM task ever running, so rebooting is probably our best + * bet for fixing the problem. + */ + CPRINTS("%s: Couldn't wipe nvmem! (rc %d)", __func__, rc); + system_reset(SYSTEM_RESET_HARD); + } + CPRINTS("TPM is erased, console is unlocked"); console_restricted_state = 0; } diff --git a/common/tpm_registers.c b/common/tpm_registers.c index 1dbd79f299..62e4414a52 100644 --- a/common/tpm_registers.c +++ b/common/tpm_registers.c @@ -640,33 +640,55 @@ static void call_extension_command(struct tpm_cmd_header *tpmh, #endif /* Event (to TPM task) to request reset, or (from TPM task) on completion. */ -#define TPM_EVENT_RESET (TASK_EVENT_CUSTOM(1)) +#define TPM_EVENT_RESET TASK_EVENT_CUSTOM(1 << 0) -/* Calling task to notify when the TPM reset has completed */ +/* Calling task (singular) to notify when the TPM reset has completed */ static __initialized task_id_t waiting_for_reset = TASK_ID_INVALID; -int tpm_reset(void) +/* Return value from blocking tpm_reset() call */ +static __preserved int wipe_result; + +/* Did tpm_reset() request nvmem wipe? (intentionally cleared on reset) */ +static int wipe_requested; + +int tpm_reset(int wait_until_done, int wipe_nvmem_first) { uint32_t evt; - cprints(CC_TASK, "%s", __func__); + cprints(CC_TASK, "%s(%d, %d)", __func__, + wait_until_done, wipe_nvmem_first); + + if (reset_in_progress) { + cprints(CC_TASK, "%s: already scheduled", __func__); + return EC_ERROR_BUSY; + } + + reset_in_progress = 1; + wipe_result = EC_SUCCESS; + + /* We can't change our minds about wiping. */ + wipe_requested |= wipe_nvmem_first; + /* Ask the TPM task to reset itself */ task_set_event(TASK_ID_TPM, TPM_EVENT_RESET, 0); + if (!wait_until_done) + return EC_SUCCESS; + if (in_interrupt_context() || task_get_current() == TASK_ID_TPM) - return 0; /* Can't sleep. Clown'll eat me. */ + return EC_ERROR_BUSY; /* Can't sleep. Clown'll eat me. */ - /* Try to wait until the TPM is reset, but timeout eventually */ + /* Completion could take a while, if other things have priority */ waiting_for_reset = task_get_current(); - evt = task_wait_event_mask(TPM_EVENT_RESET, SECOND); + evt = task_wait_event_mask(TPM_EVENT_RESET, 5 * SECOND); - /* Timeout is bad */ - if (evt & TASK_EVENT_TIMER) - return -1; + /* We were notified of completion */ + if (evt & TPM_EVENT_RESET) + return wipe_result; - /* Otherwise, good */ - return 1; + /* Timeout is bad */ + return EC_ERROR_TIMEOUT; } int tpm_is_resetting(void) @@ -674,12 +696,25 @@ int tpm_is_resetting(void) return reset_in_progress; } -static void tpm_reset_now(void) +static void tpm_reset_now(int wipe_first) { - reset_in_progress = 1; - /* This is more related to TPM task activity than TPM transactions */ - cprints(CC_TASK, "%s", __func__); + cprints(CC_TASK, "%s(%d)", __func__, wipe_first); + + if (wipe_first) { + /* + * Blindly zapping the TPM space while the AP is awake and + * poking at it will bork the TPM task and the AP itself, so + * force the whole system off by holding the EC in reset. + */ + cprints(CC_TASK, "%s: force EC off", __func__); + assert_ec_rst(); + + /* Now wipe nvmem */ + wipe_result = nvmem_setup(0); + } else { + wipe_result = EC_SUCCESS; + } /* * Clear the TPM library's zero-init data. Note that the linker script @@ -691,10 +726,11 @@ static void tpm_reset_now(void) (uintptr_t)(&__bss_libtpm2_start)); /* - * NOTE: Any initialized variables in this file must be placed in a - * separate section (NOT .data). If they need resetting, do so here. + * NOTE: If any __initialized variables need reinitializing after + * reset, this is the place to do it. */ + /* Re-initialize our registers */ tpm_init(); @@ -703,12 +739,20 @@ static void tpm_reset_now(void) task_set_event(waiting_for_reset, TPM_EVENT_RESET, 0); waiting_for_reset = TASK_ID_INVALID; } + + if (wipe_first) { + /* Allow AP & EC to boot again */ + cprints(CC_TASK, "%s: allow EC to boot", __func__); + deassert_ec_rst(); + } + + cprints(CC_TASK, "%s: done", __func__); reset_in_progress = 0; } void tpm_task(void) { - tpm_reset_now(); + tpm_reset_now(0); while (1) { uint8_t *response; unsigned response_size; @@ -719,7 +763,7 @@ void tpm_task(void) /* Wait for the next command event */ evt = task_wait_event(-1); if (evt & TPM_EVENT_RESET) { - tpm_reset_now(); + tpm_reset_now(wipe_requested); continue; } tpmh = (struct tpm_cmd_header *)tpm_.regs.data_fifo; diff --git a/include/nvmem.h b/include/nvmem.h index 354e763c8d..740c845d9d 100644 --- a/include/nvmem.h +++ b/include/nvmem.h @@ -177,11 +177,4 @@ int nvmem_setup(uint8_t version); void nvmem_compute_sha(uint8_t *p_buf, int num_bytes, uint8_t *p_sha, int sha_len); -/* - * Erase and reformat the entire nvmem storage space. This returns only if it - * was successful. If it fails, we can't be certain of the state of the system, - * so it should do a hard reboot to be safe. - */ -void nvmem_wipe_or_reboot(void); - #endif /* __CROS_EC_NVMEM_UTILS_H */ diff --git a/include/tpm_registers.h b/include/tpm_registers.h index dfdfefdc34..887cc163cd 100644 --- a/include/tpm_registers.h +++ b/include/tpm_registers.h @@ -31,20 +31,21 @@ typedef void (*interface_restart_func)(void); void tpm_register_interface(interface_restart_func interface_restart); /* - * Reset the TPM. This sends a request to the TPM task, so that the reset can - * happen when the TPM task finishes whatever it's doing at the moment. + * This requests the TPM task to reset itself. * - * Returns 0 if the request was made, but we can't wait for it to complete - * because we're in interrupt context or something similar. Otherwise, it - * blocks and returns 1 after the TPM has been cleared, or returns -1 if the - * request timed out. + * If wait_until_done is false, it returns EC_SUCCESS immediately. Otherwise it + * returns EC_SUCCESS after the reset has completed, or an error code on + * failure. + * + * If wipe_nvmem_first is true, the EC and AP will be forced off and TPM memory + * will be erased before the TPM task is reset. */ -int tpm_reset(void); +int tpm_reset(int wait_until_done, int wipe_nvmem_first); /* - * Return true if tpm is being reset. Usually this helps to avoid unnecessary - * extra reset early at startup time, when TPM could be busy installing - * endorsement certificates. + * Return true if the TPM is being reset. Usually this helps to avoid + * unnecessary extra reset early at startup time, when TPM could be busy + * installing endorsement certificates. */ int tpm_is_resetting(void); |