blob: a32c296fe966903a98fa087980007a0820ff34a9 [file] [log] [blame]
/* 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;
}