/* 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);
/*
* If VbInit() is run before Option ROMs are run it
* can still respond to the VbNv flag and does not
* need to reboot here.
*/
if (!(iparams->flags & VB_INIT_FLAG_BEFORE_OPROM_LOAD))
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 VbInit() is run before Option ROMs are run it
* can still respond to the VbNv flag and does not
* need to reboot here.
*/
if (!(iparams->flags & VB_INIT_FLAG_BEFORE_OPROM_LOAD))
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;
}