mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2025-11-25 10:45:02 +00:00
Passing the vb2 context around allows using more of the vb2 functions in future changes, and prepares for a future where we directly use the context as it was set up in firmware verification. BUG=chromium:611535 BRANCH=none TEST=make runtests; emerge-kevin coreboot depthcharge Change-Id: I8efa606dbdec5d195b66eb899e76fdc84337ad36 Signed-off-by: Randall Spangler <rspangler@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/404997 Reviewed-by: Shelley Chen <shchen@chromium.org>
388 lines
11 KiB
C
388 lines
11 KiB
C
/* Copyright (c) 2013 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 software sync routines for vboot
|
|
*/
|
|
|
|
#include "2sysincludes.h"
|
|
#include "2common.h"
|
|
#include "2nvstorage.h"
|
|
#include "sysincludes.h"
|
|
#include "utility.h"
|
|
#include "vb2_common.h"
|
|
#include "vboot_api.h"
|
|
#include "vboot_common.h"
|
|
#include "vboot_display.h"
|
|
#include "vboot_kernel.h"
|
|
|
|
static void request_recovery(struct vb2_context *ctx, uint32_t recovery_request)
|
|
{
|
|
VB2_DEBUG("request_recovery(%u)\n", recovery_request);
|
|
|
|
vb2_nv_set(ctx, VB2_NV_RECOVERY_REQUEST, recovery_request);
|
|
}
|
|
|
|
/**
|
|
* Wrapper around VbExEcProtect() which sets recovery reason on error.
|
|
*/
|
|
static VbError_t EcProtect(struct vb2_context *ctx, int devidx,
|
|
enum VbSelectFirmware_t select)
|
|
{
|
|
int rv = VbExEcProtect(devidx, select);
|
|
|
|
if (rv == VBERROR_EC_REBOOT_TO_RO_REQUIRED) {
|
|
VBDEBUG(("VbExEcProtect() needs reboot\n"));
|
|
} else if (rv != VBERROR_SUCCESS) {
|
|
VBDEBUG(("VbExEcProtect() returned %d\n", rv));
|
|
request_recovery(ctx, VB2_RECOVERY_EC_PROTECT);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
static VbError_t EcUpdateImage(struct vb2_context *ctx, int devidx,
|
|
VbCommonParams *cparams,
|
|
enum VbSelectFirmware_t select,
|
|
int *need_update, int in_rw)
|
|
{
|
|
VbSharedDataHeader *shared =
|
|
(VbSharedDataHeader *)cparams->shared_data_blob;
|
|
int rv;
|
|
int hash_size;
|
|
int ec_hash_size;
|
|
const uint8_t *hash = NULL;
|
|
const uint8_t *expected = NULL;
|
|
const uint8_t *ec_hash = NULL;
|
|
int expected_size;
|
|
int i;
|
|
int rw_request = select != VB_SELECT_FIRMWARE_READONLY;
|
|
|
|
*need_update = 0;
|
|
VBDEBUG(("EcUpdateImage() - "
|
|
"Check for %s update\n", rw_request ? "RW" : "RO"));
|
|
|
|
/* Get current EC hash. */
|
|
rv = VbExEcHashImage(devidx, select, &ec_hash, &ec_hash_size);
|
|
if (rv) {
|
|
VBDEBUG(("EcUpdateImage() - "
|
|
"VbExEcHashImage() returned %d\n", rv));
|
|
request_recovery(ctx, VB2_RECOVERY_EC_HASH_FAILED);
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
VBDEBUG(("EC-%s hash: ", rw_request ? "RW" : "RO"));
|
|
for (i = 0; i < ec_hash_size; i++)
|
|
VBDEBUG(("%02x",ec_hash[i]));
|
|
VBDEBUG(("\n"));
|
|
|
|
/* Get expected EC hash. */
|
|
rv = VbExEcGetExpectedImageHash(devidx, select, &hash, &hash_size);
|
|
if (rv) {
|
|
VBDEBUG(("EcUpdateImage() - "
|
|
"VbExEcGetExpectedImageHash() returned %d\n", rv));
|
|
request_recovery(ctx, VB2_RECOVERY_EC_EXPECTED_HASH);
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
if (ec_hash_size != hash_size) {
|
|
VBDEBUG(("EcUpdateImage() - "
|
|
"EC uses %d-byte hash, but AP-RW contains %d bytes\n",
|
|
ec_hash_size, hash_size));
|
|
request_recovery(ctx, VB2_RECOVERY_EC_HASH_SIZE);
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
|
|
VBDEBUG(("Expected hash: "));
|
|
for (i = 0; i < hash_size; i++)
|
|
VBDEBUG(("%02x", hash[i]));
|
|
VBDEBUG(("\n"));
|
|
*need_update = vb2_safe_memcmp(ec_hash, hash, hash_size);
|
|
|
|
if (!*need_update)
|
|
return VBERROR_SUCCESS;
|
|
|
|
/* Get expected EC image */
|
|
rv = VbExEcGetExpectedImage(devidx, select, &expected, &expected_size);
|
|
if (rv) {
|
|
VBDEBUG(("EcUpdateImage() - "
|
|
"VbExEcGetExpectedImage() returned %d\n", rv));
|
|
request_recovery(ctx, VB2_RECOVERY_EC_EXPECTED_IMAGE);
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
VBDEBUG(("EcUpdateImage() - image len = %d\n", expected_size));
|
|
|
|
if (in_rw && rw_request) {
|
|
/*
|
|
* Check if BIOS should also load VGA Option ROM when
|
|
* rebooting to save another reboot if possible.
|
|
*/
|
|
if ((shared->flags & VBSD_EC_SLOW_UPDATE) &&
|
|
(shared->flags & VBSD_OPROM_MATTERS) &&
|
|
!(shared->flags & VBSD_OPROM_LOADED)) {
|
|
VBDEBUG(("EcUpdateImage() - Reboot to "
|
|
"load VGA Option ROM\n"));
|
|
vb2_nv_set(ctx, VB2_NV_OPROM_NEEDED, 1);
|
|
}
|
|
|
|
/*
|
|
* EC is running the wrong RW image. Reboot the EC to
|
|
* RO so we can update it on the next boot.
|
|
*/
|
|
VBDEBUG(("EcUpdateImage() - "
|
|
"in RW, need to update RW, so reboot\n"));
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
|
|
VBDEBUG(("EcUpdateImage() updating EC-%s...\n",
|
|
rw_request ? "RW" : "RO"));
|
|
|
|
if (shared->flags & VBSD_EC_SLOW_UPDATE) {
|
|
VBDEBUG(("EcUpdateImage() - EC is slow. Show WAIT screen.\n"));
|
|
|
|
/* Ensure the VGA Option ROM is loaded */
|
|
if ((shared->flags & VBSD_OPROM_MATTERS) &&
|
|
!(shared->flags & VBSD_OPROM_LOADED)) {
|
|
VBDEBUG(("EcUpdateImage() - Reboot to "
|
|
"load VGA Option ROM\n"));
|
|
vb2_nv_set(ctx, VB2_NV_OPROM_NEEDED, 1);
|
|
return VBERROR_VGA_OPROM_MISMATCH;
|
|
}
|
|
|
|
VbDisplayScreen(ctx, cparams, VB_SCREEN_WAIT, 0);
|
|
}
|
|
|
|
rv = VbExEcUpdateImage(devidx, select, expected, expected_size);
|
|
if (rv != VBERROR_SUCCESS) {
|
|
VBDEBUG(("EcUpdateImage() - "
|
|
"VbExEcUpdateImage() returned %d\n", rv));
|
|
|
|
/*
|
|
* The EC may know it needs a reboot. It may need to
|
|
* unprotect the region before updating, or may need to
|
|
* reboot after updating. Either way, it's not an error
|
|
* requiring recovery mode.
|
|
*
|
|
* If we fail for any other reason, trigger recovery
|
|
* mode.
|
|
*/
|
|
if (rv != VBERROR_EC_REBOOT_TO_RO_REQUIRED)
|
|
request_recovery(ctx, VB2_RECOVERY_EC_UPDATE);
|
|
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
|
|
/* Verify the EC was updated properly */
|
|
rv = VbExEcHashImage(devidx, select, &ec_hash, &ec_hash_size);
|
|
if (rv) {
|
|
VBDEBUG(("EcUpdateImage() - "
|
|
"VbExEcHashImage() returned %d\n", rv));
|
|
request_recovery(ctx, VB2_RECOVERY_EC_HASH_FAILED);
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
if (hash_size != ec_hash_size) {
|
|
VBDEBUG(("EcUpdateImage() - "
|
|
"VbExEcHashImage() says size %d, not %d\n",
|
|
ec_hash_size, hash_size));
|
|
request_recovery(ctx, VB2_RECOVERY_EC_HASH_SIZE);
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
VBDEBUG(("Updated EC-%s hash: ", rw_request ? "RW" : "RO"));
|
|
for (i = 0; i < ec_hash_size; i++)
|
|
VBDEBUG(("%02x",ec_hash[i]));
|
|
VBDEBUG(("\n"));
|
|
|
|
if (vb2_safe_memcmp(ec_hash, hash, hash_size)){
|
|
VBDEBUG(("EcUpdateImage() - "
|
|
"Failed to update EC-%s\n", rw_request ?
|
|
"RW" : "RO"));
|
|
request_recovery(ctx, VB2_RECOVERY_EC_UPDATE);
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
|
|
return VBERROR_SUCCESS;
|
|
}
|
|
|
|
VbError_t VbEcSoftwareSync(struct vb2_context *ctx, int devidx,
|
|
VbCommonParams *cparams)
|
|
{
|
|
VbSharedDataHeader *shared =
|
|
(VbSharedDataHeader *)cparams->shared_data_blob;
|
|
enum VbSelectFirmware_t select_rw =
|
|
shared->firmware_index ? VB_SELECT_FIRMWARE_B :
|
|
VB_SELECT_FIRMWARE_A;
|
|
enum VbSelectFirmware_t select_ro = VB_SELECT_FIRMWARE_READONLY;
|
|
int in_rw = 0;
|
|
int ro_try_count = 2;
|
|
int num_tries = 0;
|
|
uint32_t recovery_request;
|
|
int rv, updated_rw, updated_ro;
|
|
|
|
VBDEBUG(("VbEcSoftwareSync(devidx=%d)\n", devidx));
|
|
|
|
/* Determine whether the EC is in RO or RW */
|
|
rv = VbExEcRunningRW(devidx, &in_rw);
|
|
|
|
if (shared->recovery_reason) {
|
|
/* Recovery mode; just verify the EC is in RO code */
|
|
if (rv == VBERROR_SUCCESS && in_rw == 1) {
|
|
/*
|
|
* EC is definitely in RW firmware. We want it in
|
|
* read-only code, so preserve the current recovery
|
|
* reason and reboot.
|
|
*
|
|
* We don't reboot on error or unknown EC code, because
|
|
* we could end up in an endless reboot loop. If we
|
|
* had some way to track that we'd already rebooted for
|
|
* this reason, we could retry only once.
|
|
*/
|
|
VBDEBUG(("VbEcSoftwareSync() - "
|
|
"want recovery but got EC-RW\n"));
|
|
request_recovery(ctx, shared->recovery_reason);
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
|
|
VBDEBUG(("VbEcSoftwareSync() in recovery; EC-RO\n"));
|
|
return VBERROR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Not in recovery. If we couldn't determine where the EC was,
|
|
* reboot to recovery.
|
|
*/
|
|
if (rv != VBERROR_SUCCESS) {
|
|
VBDEBUG(("VbEcSoftwareSync() - "
|
|
"VbExEcRunningRW() returned %d\n", rv));
|
|
request_recovery(ctx, VB2_RECOVERY_EC_UNKNOWN_IMAGE);
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
|
|
/* If AP is read-only normal, EC should be in its RO code also. */
|
|
if (shared->flags & VBSD_LF_USE_RO_NORMAL) {
|
|
/* If EC is in RW code, request reboot back to RO */
|
|
if (in_rw == 1) {
|
|
VBDEBUG(("VbEcSoftwareSync() - "
|
|
"want RO-normal but got EC-RW\n"));
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
|
|
/* Protect the RW flash and stay in EC-RO */
|
|
rv = EcProtect(ctx, devidx, select_rw);
|
|
if (rv != VBERROR_SUCCESS)
|
|
return rv;
|
|
|
|
rv = VbExEcDisableJump(devidx);
|
|
if (rv != VBERROR_SUCCESS) {
|
|
VBDEBUG(("VbEcSoftwareSync() - "
|
|
"VbExEcDisableJump() returned %d\n", rv));
|
|
request_recovery(ctx, VB2_RECOVERY_EC_SOFTWARE_SYNC);
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
|
|
VBDEBUG(("VbEcSoftwareSync() in RO-Normal; EC-RO\n"));
|
|
return VBERROR_SUCCESS;
|
|
}
|
|
|
|
VBDEBUG(("VbEcSoftwareSync() check for RW update.\n"));
|
|
|
|
/* Update the RW Image. */
|
|
rv = EcUpdateImage(ctx, devidx, cparams, select_rw, &updated_rw, in_rw);
|
|
|
|
if (rv != VBERROR_SUCCESS) {
|
|
VBDEBUG(("VbEcSoftwareSync() - "
|
|
"EcUpdateImage() returned %d\n", rv));
|
|
return rv;
|
|
}
|
|
|
|
/* Tell EC to jump to its RW image */
|
|
if (!in_rw) {
|
|
VBDEBUG(("VbEcSoftwareSync() jumping to EC-RW\n"));
|
|
rv = VbExEcJumpToRW(devidx);
|
|
if (rv != VBERROR_SUCCESS) {
|
|
VBDEBUG(("VbEcSoftwareSync() - "
|
|
"VbExEcJumpToRW() returned %x\n", rv));
|
|
|
|
/*
|
|
* If the EC booted RO-normal and a previous AP boot
|
|
* has called VbExEcStayInRO(), we need to reboot the EC
|
|
* to unlock the ability to jump to the RW firmware.
|
|
*
|
|
* All other errors trigger recovery mode.
|
|
*/
|
|
if (rv != VBERROR_EC_REBOOT_TO_RO_REQUIRED)
|
|
request_recovery(ctx, VB2_RECOVERY_EC_JUMP_RW);
|
|
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
}
|
|
|
|
uint32_t try_ro_sync = vb2_nv_get(ctx, VB2_NV_TRY_RO_SYNC);
|
|
if (!devidx && try_ro_sync &&
|
|
!(shared->flags & VBSD_BOOT_FIRMWARE_WP_ENABLED)) {
|
|
/* Reset RO Software Sync NV flag */
|
|
vb2_nv_set(ctx, VB2_NV_TRY_RO_SYNC, 0);
|
|
|
|
recovery_request = vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST);
|
|
|
|
/* Update the RO Image. */
|
|
while (num_tries < ro_try_count) {
|
|
VBDEBUG(("VbEcSoftwareSync() RO Software Sync\n"));
|
|
|
|
/* Get expected EC-RO Image. */
|
|
rv = EcUpdateImage(ctx, devidx, cparams, select_ro,
|
|
&updated_ro, in_rw);
|
|
if (rv == VBERROR_SUCCESS) {
|
|
/*
|
|
* If the RO update had failed, reset the
|
|
* recovery request.
|
|
*/
|
|
if (num_tries)
|
|
request_recovery(ctx, recovery_request);
|
|
break;
|
|
} else
|
|
VBDEBUG(("VbEcSoftwareSync() - "
|
|
"EcUpdateImage() returned %d\n", rv));
|
|
|
|
num_tries++;
|
|
}
|
|
}
|
|
if (rv != VBERROR_SUCCESS)
|
|
return rv;
|
|
|
|
/* Protect RO flash */
|
|
rv = EcProtect(ctx, devidx, select_ro);
|
|
if (rv != VBERROR_SUCCESS)
|
|
return rv;
|
|
|
|
/* Protect RW flash */
|
|
rv = EcProtect(ctx, devidx, select_rw);
|
|
if (rv != VBERROR_SUCCESS)
|
|
return rv;
|
|
|
|
rv = VbExEcDisableJump(devidx);
|
|
if (rv != VBERROR_SUCCESS) {
|
|
VBDEBUG(("VbEcSoftwareSync() - "
|
|
"VbExEcDisableJump() returned %d\n", rv));
|
|
request_recovery(ctx, VB2_RECOVERY_EC_SOFTWARE_SYNC);
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
|
|
/*
|
|
* Reboot to unload VGA Option ROM if:
|
|
* - RW update was done
|
|
* - the system is NOT in developer mode
|
|
* - the system has slow EC update flag set
|
|
* - the VGA Option ROM was needed and loaded
|
|
*/
|
|
if (updated_rw &&
|
|
!(shared->flags & VBSD_BOOT_DEV_SWITCH_ON) &&
|
|
(shared->flags & VBSD_EC_SLOW_UPDATE) &&
|
|
(shared->flags & VBSD_OPROM_MATTERS) &&
|
|
(shared->flags & VBSD_OPROM_LOADED)) {
|
|
VBDEBUG(("VbEcSoftwareSync() - Reboot to "
|
|
"unload VGA Option ROM\n"));
|
|
vb2_nv_set(ctx, VB2_NV_OPROM_NEEDED, 0);
|
|
return VBERROR_VGA_OPROM_MISMATCH;
|
|
}
|
|
|
|
|
|
return rv;
|
|
}
|