Files
OpenCellular/firmware/lib/vboot_api_init.c
Duncan Laurie 731f8e8a1d vboot: Support SLOW_EC_UPDATE with OPROM_MATTERS
In order to display the slow EC update screen on x86 devices
in normal mode it is necessary to request a reboot where the
VGA Option ROM is loaded.

This needs a bit of plumbing to pass the OPROM_MATTERS and
OPROM_LOADED flags into shared data so they can be consumed
in the VbEcSoftwareSync() function.  It also needs the VbInit()
function to not immediately request a reboot if the VGA Option
ROM was loaded in normal mode and the SLOW_EC flag is set as it
will still need to be used during software sync.

A FIXME in VbEcSoftwareSync() is implemented and the comment is
removed, and two extra checks are done.  First, if rebooting to
RO then also check if the VGA Option ROM is needed to save an
extra reboot, and second when exiting the software sync function
request a reboot without the VGA Option ROM if it was done in
normal mode and the option rom was needed+loaded.

The request for a reboot from VbEcSoftwareSync() is saved when
doing EC update in case there is an (optional) PD software sync
that may also need to display the screen.

BUG=chrome-os-partner:12257,chrome-os-partner:32379
BRANCH=samus
TEST=all tests pass, manual testing:
1) in normal mode, with EC/PD in RW, ensure that they are rebooted
to RO and the VGA Option ROM is loaded and the wait screen is
displayed, and then the system is rebooted at the end and the
VGA Option ROM is not loaded.
2) same as #1 with EC/PD in RO already, same result
3) same as #1 with system in developer mode, same result except
there is no reboot at the end of software sync
4) same as #1 with system in developer mode and EC/PD in RO,
ensure that there is no extra reboot at the beginning or end of
software sync.

Change-Id: Id592181efd640f4cd37a986cd1dcc29f3ca45104
Signed-off-by: Duncan Laurie <dlaurie@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/223718
Reviewed-by: Bill Richardson <wfrichar@chromium.org>
Reviewed-by: Randall Spangler <rspangler@chromium.org>
2014-10-17 01:14:21 +00:00

369 lines
12 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.
*
* High-level firmware wrapper API - entry points for init, firmware selection
*/
#include "sysincludes.h"
#include "region.h"
#include "gbb_access.h"
#include "gbb_header.h"
#include "load_firmware_fw.h"
#include "rollback_index.h"
#include "utility.h"
#include "vboot_api.h"
#include "vboot_common.h"
#include "vboot_nvstorage.h"
VbError_t VbInit(VbCommonParams *cparams, VbInitParams *iparams)
{
VbSharedDataHeader *shared =
(VbSharedDataHeader *)cparams->shared_data_blob;
GoogleBinaryBlockHeader gbb;
VbNvContext vnc;
VbError_t retval = VBERROR_SUCCESS;
uint32_t recovery = VBNV_RECOVERY_NOT_REQUESTED;
int is_s3_resume = 0;
uint32_t s3_debug_boot = 0;
uint32_t require_official_os = 0;
uint32_t tpm_version = 0;
uint32_t tpm_status = 0;
int has_virt_dev_switch = 0;
int is_hw_dev = 0;
int is_virt_dev = 0;
uint32_t disable_dev_request = 0;
uint32_t clear_tpm_owner_request = 0;
int is_dev = 0;
uint32_t backup_requested = 0;
uint32_t backup_for_safety = 0;
int lost_nvram;
/* Initialize output flags */
iparams->out_flags = 0;
retval = VbGbbReadHeader_static(cparams, &gbb);
if (retval)
return retval;
VBDEBUG(("VbInit() input flags 0x%x gbb flags 0x%x\n", iparams->flags,
gbb.flags));
/* Set up NV storage */
VbExNvStorageRead(vnc.raw);
VbNvSetup(&vnc);
lost_nvram = vnc.regenerate_crc;
/* Initialize shared data structure */
if (0 != VbSharedDataInit(shared, cparams->shared_data_size)) {
VBDEBUG(("Shared data init error\n"));
return VBERROR_INIT_SHARED_DATA;
}
shared->timer_vb_init_enter = VbExGetTimer();
/* Copy some boot switch flags */
/* TODO: in next refactor, just save in/out flags in VbSharedData */
shared->flags = 0;
if (iparams->flags & VB_INIT_FLAG_REC_BUTTON_PRESSED)
shared->flags |= VBSD_BOOT_REC_SWITCH_ON;
if (iparams->flags & VB_INIT_FLAG_WP_ENABLED)
shared->flags |= VBSD_BOOT_FIRMWARE_WP_ENABLED;
if (iparams->flags & VB_INIT_FLAG_SW_WP_ENABLED)
shared->flags |= VBSD_BOOT_FIRMWARE_SW_WP_ENABLED;
if (iparams->flags & VB_INIT_FLAG_S3_RESUME)
shared->flags |= VBSD_BOOT_S3_RESUME;
if (iparams->flags & VB_INIT_FLAG_RO_NORMAL_SUPPORT)
shared->flags |= VBSD_BOOT_RO_NORMAL_SUPPORT;
if (iparams->flags & VB_INIT_FLAG_EC_SOFTWARE_SYNC)
shared->flags |= VBSD_EC_SOFTWARE_SYNC;
if (iparams->flags & VB_INIT_FLAG_EC_SLOW_UPDATE)
shared->flags |= VBSD_EC_SLOW_UPDATE;
if (iparams->flags & VB_INIT_FLAG_VIRTUAL_REC_SWITCH)
shared->flags |= VBSD_BOOT_REC_SWITCH_VIRTUAL;
if (iparams->flags & VB_INIT_FLAG_OPROM_MATTERS)
shared->flags |= VBSD_OPROM_MATTERS;
if (iparams->flags & VB_INIT_FLAG_OPROM_LOADED)
shared->flags |= VBSD_OPROM_LOADED;
is_s3_resume = (iparams->flags & VB_INIT_FLAG_S3_RESUME ? 1 : 0);
/* Check if the OS is requesting a debug S3 reset */
VbNvGet(&vnc, VBNV_DEBUG_RESET_MODE, &s3_debug_boot);
if (s3_debug_boot) {
if (is_s3_resume) {
VBDEBUG(("VbInit() requesting S3 debug boot\n"));
iparams->out_flags |= VB_INIT_OUT_S3_DEBUG_BOOT;
is_s3_resume = 0; /* Proceed as if normal boot */
}
/*
* Clear the request even if this is a normal boot, since we
* don't want the NEXT S3 resume to be a debug reset unless the
* OS asserts the request again.
*/
VbNvSet(&vnc, VBNV_DEBUG_RESET_MODE, 0);
}
/*
* If this isn't a S3 resume, read the current recovery request, then
* clear it so we don't get stuck in recovery mode.
*/
if (!is_s3_resume) {
VbNvGet(&vnc, VBNV_RECOVERY_REQUEST, &recovery);
VBDEBUG(("VbInit sees recovery request = %d\n", recovery));
if (VBNV_RECOVERY_NOT_REQUESTED != recovery)
VbNvSet(&vnc, VBNV_RECOVERY_REQUEST,
VBNV_RECOVERY_NOT_REQUESTED);
}
/*
* If the previous boot failed in the firmware somewhere outside of
* verified boot, and recovery is not requested for our own reasons,
* request recovery mode. This gives the calling firmware a way to
* request recovery if it finds something terribly wrong.
*/
if (VBNV_RECOVERY_NOT_REQUESTED == recovery &&
iparams->flags & VB_INIT_FLAG_PREVIOUS_BOOT_FAIL) {
recovery = VBNV_RECOVERY_RO_FIRMWARE;
}
/*
* If recovery button is pressed, override recovery reason. Note that
* we do this in the S3 resume path also.
*/
if (iparams->flags & VB_INIT_FLAG_REC_BUTTON_PRESSED)
recovery = VBNV_RECOVERY_RO_MANUAL;
/*
* Copy current recovery reason to shared data. If we fail later on, it
* won't matter, since we'll just reboot.
*/
shared->recovery_reason = (uint8_t)recovery;
VBDEBUG(("VbInit now sets shared->recovery_reason = %d\n", recovery));
/*
* If this is a S3 resume, resume the TPM.
*
* FIXME: I think U-Boot won't ever ask us to do this. Can we remove
* it?
*/
if (is_s3_resume) {
if (TPM_SUCCESS != RollbackS3Resume()) {
/*
* If we can't resume, just do a full reboot. No need
* to go to recovery mode here, since if the TPM is
* really broken we'll catch it on the next boot.
*/
retval = VBERROR_TPM_S3_RESUME;
}
} else {
/* Should we pay attention to the TPM's virtual dev-switch? */
if (iparams->flags & VB_INIT_FLAG_VIRTUAL_DEV_SWITCH) {
shared->flags |= VBSD_HONOR_VIRT_DEV_SWITCH;
has_virt_dev_switch = 1;
}
/*
* We always believe the HW dev-switch, since there's one
* attached to servo which may be active even on systems
* without a physical switch. The EC may also implement a fake
* dev-switch for testing.
*/
if (iparams->flags & VB_INIT_FLAG_DEV_SWITCH_ON)
is_hw_dev = 1;
/* We may be asked to clear the virtual dev-switch at boot. */
VbNvGet(&vnc, VBNV_DISABLE_DEV_REQUEST, &disable_dev_request);
/* Allow GBB flag to override dev switch */
if (gbb.flags & GBB_FLAG_FORCE_DEV_SWITCH_ON)
is_hw_dev = 1;
/* Have we been explicitly asked to clear the TPM owner? */
VbNvGet(&vnc, VBNV_CLEAR_TPM_OWNER_REQUEST,
&clear_tpm_owner_request);
/*
* Initialize the TPM. If the developer mode state has changed
* since the last boot, we need to clear TPM ownership. If the
* TPM space is initialized by this call, the virtual
* dev-switch will be disabled by default)
*/
VBDEBUG(("TPM: Call RollbackFirmwareSetup(r%d, d%d)\n",
recovery, is_hw_dev));
tpm_status = RollbackFirmwareSetup(is_hw_dev,
disable_dev_request,
clear_tpm_owner_request,
/* two outputs on success */
&is_virt_dev, &tpm_version);
if (0 != tpm_status) {
VBDEBUG(("Unable to setup TPM and read "
"firmware version (0x%x)\n", tpm_status));
if (TPM_E_MUST_REBOOT == tpm_status) {
/*
* TPM wants to reboot into the same mode we're
* in now
*/
VBDEBUG(("TPM requires a reboot.\n"));
if (!recovery) {
/*
* Not recovery mode. Just reboot (not
* into recovery).
*/
retval = VBERROR_TPM_REBOOT_REQUIRED;
goto VbInit_exit;
} else if (VBNV_RECOVERY_RO_TPM_REBOOT !=
shared->recovery_reason) {
/*
* In recovery mode now, and we haven't
* requested a TPM reboot yet, so
* request one.
*/
VbNvSet(&vnc, VBNV_RECOVERY_REQUEST,
VBNV_RECOVERY_RO_TPM_REBOOT);
retval = VBERROR_TPM_REBOOT_REQUIRED;
goto VbInit_exit;
}
}
if (!recovery) {
VbNvSet(&vnc, VBNV_RECOVERY_REQUEST,
VBNV_RECOVERY_RO_TPM_S_ERROR);
VbNvSet(&vnc, VBNV_RECOVERY_SUBCODE,
tpm_status);
retval = VBERROR_TPM_FIRMWARE_SETUP;
goto VbInit_exit;
}
}
/* TPM setup succeeded, or we're in recovery mode and ignoring
* errors. What did we learn? */
shared->fw_version_tpm_start = tpm_version;
shared->fw_version_tpm = tpm_version;
if (is_hw_dev || (has_virt_dev_switch && is_virt_dev)) {
is_dev = 1;
shared->flags |= VBSD_BOOT_DEV_SWITCH_ON;
}
if (disable_dev_request && !is_virt_dev)
VbNvSet(&vnc, VBNV_DISABLE_DEV_REQUEST, 0);
if (clear_tpm_owner_request) {
VbNvSet(&vnc, VBNV_CLEAR_TPM_OWNER_REQUEST, 0);
VbNvSet(&vnc, VBNV_CLEAR_TPM_OWNER_DONE, 1);
}
}
/*
* If the nvram state was lost, try to restore the bits we care about
* from the backup in the TPM. It's okay if we can't, though.
* Note: None of the bits that we back up should have been referenced
* before this point. Otherwise, they'll just be overwritten here.
* All the other bits will be unchanged from whatever has happened to
* them since VbNvSetup() reinitialized the VbNvContext.
*/
if (lost_nvram)
RestoreNvFromBackup(&vnc);
/* Allow BIOS to load arbitrary option ROMs? */
if (gbb.flags & GBB_FLAG_LOAD_OPTION_ROMS)
iparams->out_flags |= VB_INIT_OUT_ENABLE_OPROM;
/* Factory may need to boot custom OSes when the dev-switch is on */
if (is_dev && (gbb.flags & GBB_FLAG_ENABLE_ALTERNATE_OS))
iparams->out_flags |= VB_INIT_OUT_ENABLE_ALTERNATE_OS;
/* Set output flags */
if (VBNV_RECOVERY_NOT_REQUESTED != recovery) {
/* Requesting recovery mode */
iparams->out_flags |= (VB_INIT_OUT_ENABLE_RECOVERY |
VB_INIT_OUT_CLEAR_RAM |
VB_INIT_OUT_ENABLE_DISPLAY |
VB_INIT_OUT_ENABLE_USB_STORAGE);
} else if (is_dev) {
/* Developer switch is on, so need to support dev mode */
iparams->out_flags |= (VB_INIT_OUT_ENABLE_DEVELOPER |
VB_INIT_OUT_CLEAR_RAM |
VB_INIT_OUT_ENABLE_DISPLAY |
VB_INIT_OUT_ENABLE_USB_STORAGE);
/* ... which may or may not include custom OSes */
VbNvGet(&vnc, VBNV_DEV_BOOT_SIGNED_ONLY, &require_official_os);
if (!require_official_os)
iparams->out_flags |= VB_INIT_OUT_ENABLE_ALTERNATE_OS;
/*
* Dev-mode needs the VGA option ROM to be loaded so it can
* display the scary boot screen. If we don't have it, we need
* to request it and reboot so it can be loaded.
*/
if ((iparams->flags & VB_INIT_FLAG_OPROM_MATTERS) &&
!(iparams->flags & VB_INIT_FLAG_OPROM_LOADED)) {
VbNvSet(&vnc, VBNV_OPROM_NEEDED, 1);
retval = VBERROR_VGA_OPROM_MISMATCH;
VBDEBUG(("VbInit() needs oprom, doesn't have it\n"));
}
} else {
/*
* Normal mode, so disable dev_boot_* flags. This ensures they
* will be initially disabled if the user later transitions
* back into developer mode.
*/
VbNvSet(&vnc, VBNV_DEV_BOOT_USB, 0);
VbNvSet(&vnc, VBNV_DEV_BOOT_LEGACY, 0);
VbNvSet(&vnc, VBNV_DEV_BOOT_SIGNED_ONLY, 0);
/*
* Back up any changes now, so these values can't be forgotten
* by draining the battery. We really only care about these
* three fields, but it's uncommon for any others to change so
* this is an easier test than checking each one.
*/
if (vnc.regenerate_crc)
backup_for_safety = 1;
/*
* If we don't need the VGA option ROM but got it anyway, stop
* asking for it and reboot in case there's some vulnerability
* in using it.
*/
if ((iparams->flags & VB_INIT_FLAG_OPROM_MATTERS) &&
(iparams->flags & VB_INIT_FLAG_OPROM_LOADED)) {
VbNvSet(&vnc, VBNV_OPROM_NEEDED, 0);
/*
* If this is a system with slow EC update then don't
* reboot immediately in case VGA option ROM is still
* needed to display an update screen. The reboot will
* be requested after EC software sync is complete.
*/
if (!(iparams->flags & VB_INIT_FLAG_EC_SLOW_UPDATE))
retval = VBERROR_VGA_OPROM_MISMATCH;
VBDEBUG(("VbInit() has oprom, doesn't need it\n"));
}
}
VbInit_exit:
/*
* If we successfully backup the NV storage, it will clear the
* VBNV_BACKUP_NVRAM_REQUEST field, so we want to do it before
* calling VbNvTeardown(). It's okay if we can't backup, though.
*/
VbNvGet(&vnc, VBNV_BACKUP_NVRAM_REQUEST, &backup_requested);
if (backup_requested || backup_for_safety)
SaveNvToBackup(&vnc);
/* Tear down NV storage */
VbNvTeardown(&vnc);
if (vnc.raw_changed)
VbExNvStorageWrite(vnc.raw);
VBDEBUG(("VbInit() output flags 0x%x\n", iparams->out_flags));
shared->timer_vb_init_exit = VbExGetTimer();
VBDEBUG(("VbInit() returning 0x%x\n", retval));
return retval;
}