blob: 8cf1e019a8011db7fc306208bd27d6886658b275 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Verified boot stages
*
* Copyright 2018 Google LLC
*/
#define LOG_CATEGORY LOGC_VBOOT
#include <common.h>
#include <errno.h>
#include <log.h>
#include <spl.h>
#include <sysreset.h>
#include <cros/nvdata.h>
#include <cros/stages.h>
#include <cros/cros_common.h>
#include <cros/vboot.h>
struct vboot_stage {
const char *name;
int (*run)(struct vboot_info *vboot);
};
#if CONFIG_IS_ENABLED(CHROMEOS_VBOOT_A) || \
CONFIG_IS_ENABLED(CHROMEOS_VBOOT_B) || \
CONFIG_IS_ENABLED(CHROMEOS_VBOOT_C)
/*
* There are three groups here. TPL or VPL runs the early firmware-selection
* process, then SPL sets up SDRAM and jumps to U-Boot proper, which does the
* kernel-selection process. We only build in the code that is actually needed
* by each stage.
*
* For testing it might be convenient to include all stages in one binary, but
* so far it seems easy enough to run gdb and have it follow the processes.
*/
struct vboot_stage stages[VBOOT_STAGE_COUNT] = {
#if CONFIG_IS_ENABLED(CHROMEOS_VBOOT_A)
/* Verification stage: figures out which firmware to run */
[VBOOT_STAGE_VER_INIT] = {"ver_init", vboot_ver_init},
[VBOOT_STAGE_VER1_VBINIT] = {"ver1_vbinit", vboot_ver1_vbinit},
[VBOOT_STAGE_VER2_SELECTFW] = {"ver2_selectfw", vboot_ver2_select_fw,},
[VBOOT_STAGE_VER3_TRYFW] = {"ver3_tryfw", vboot_ver3_try_fw,},
[VBOOT_STAGE_VER4_LOCATEFW] = {"ver4_locatefw", vboot_ver4_locate_fw,},
[VBOOT_STAGE_VER_FINISH] = {"ver5_finishfw", vboot_ver5_finish_fw,},
[VBOOT_STAGE_VER_JUMP] = {"ver_jump", vboot_ver6_jump_fw,},
#endif
#if CONFIG_IS_ENABLED(CHROMEOS_VBOOT_B)
/* SPL stage: Sets up SDRAM and jumps to U-Boot proper */
[VBOOT_STAGE_RW_SPL_INIT] = {"spl_init", vboot_spl_init,},
[VBOOT_STAGE_RW_SPL_JUMP_U_BOOT] =
{"spl_jump_u_boot", vboot_spl_jump_u_boot,},
[VBOOT_STAGE_RW_INIT] = {},
#endif
#if CONFIG_IS_ENABLED(CHROMEOS_VBOOT_C)
/* U-Boot stage: Boots the kernel */
[VBOOT_STAGE_RW_INIT] = {"rw_init", vboot_rw_init,},
[VBOOT_STAGE_RW_SELECTKERNEL] =
{"rw_selectkernel", vboot_rw_select_kernel,},
[VBOOT_STAGE_RW_BOOTKERNEL] = {"rw_bootkernel", vboot_rw_boot_kernel,},
#endif
#if 0
/* For VB2, when supported */
[VBOOT_STAGE_RW_KERNEL_PHASE1] =
{"rw_kernel_phase1", vboot_rw_kernel_phase1,},
[VBOOT_STAGE_RW_KERNEL_PHASE2] =
{"rw_kernel_phase2", vboot_rw_kernel_phase2,},
[VBOOT_STAGE_RW_KERNEL_PHASE3] =
{"rw_kernel_phase3", vboot_rw_kernel_phase3,},
[VBOOT_STAGE_RW_KERNEL_BOOT] =
{ "rw_kernel_boot", vboot_rw_kernel_boot, },
#endif
};
const char *vboot_get_stage_name(enum vboot_stage_t stagenum)
{
if (stagenum >= VBOOT_STAGE_FIRST && stagenum < VBOOT_STAGE_COUNT)
return stages[stagenum].name ?: "(unknown)";
return "(invalid)";
}
enum vboot_stage_t vboot_find_stage(const char *name)
{
enum vboot_stage_t stagenum;
for (stagenum = VBOOT_STAGE_FIRST; stagenum < VBOOT_STAGE_COUNT;
stagenum++) {
struct vboot_stage *stage = &stages[stagenum];
if (!strcmp(name, stage->name))
return stagenum;
}
return VBOOT_STAGE_NONE;
}
int vboot_run_stage(struct vboot_info *vboot, enum vboot_stage_t stagenum)
{
struct vboot_stage *stage = &stages[stagenum];
int ret;
log_info("--\n");
log_info("* Running stage '%s'\n", stage->name);
if (!stage->run) {
log_debug(" - Stage '%s' not available\n", stage->name);
return -EPERM;
}
bootstage_mark_name(BOOTSTAGE_VBOOT_FIRST + stagenum, stage->name);
ret = (*stage->run)(vboot);
if (ret)
log_err("Error: stage '%s' returned %x (%d)\n", stage->name,
ret, ret);
return ret;
}
/**
* save_if_needed() - Save non-volatile and/or secure data if it has changed
*
* @vboot: vboot context
* @return 0 if OK, -ve if save failed
*/
int vboot_save_if_needed(struct vboot_info *vboot, vb2_error_t *vberrp)
{
struct vb2_context *ctx = vboot_get_ctx(vboot);
int ret;
*vberrp = VB2_SUCCESS;
if (!ctx)
return -ENOENT;
if (ctx->flags & VB2_CONTEXT_SECDATA_KERNEL_CHANGED) {
log_info("Saving secdatak\n");
if (spl_phase() != PHASE_SPL)
vboot_secdatak_dump(ctx->secdata_kernel,
sizeof(ctx->secdata_kernel));
ret = cros_nvdata_write_walk(CROS_NV_SECDATAK,
ctx->secdata_kernel,
sizeof(ctx->secdata_kernel));
if (ret) {
*vberrp = VB2_ERROR_SECDATA_KERNEL_WRITE;
return log_msg_ret("secdatak", ret);
}
ctx->flags &= ~VB2_CONTEXT_SECDATA_KERNEL_CHANGED;
}
if (ctx->flags & VB2_CONTEXT_SECDATA_FIRMWARE_CHANGED) {
log_info("Saving secdataf\n");
if (spl_phase() != PHASE_SPL)
vboot_secdataf_dump(ctx->secdata_firmware,
sizeof(ctx->secdata_firmware));
ret = cros_nvdata_write_walk(CROS_NV_SECDATAF, ctx->secdata_firmware,
sizeof(ctx->secdata_firmware));
if (ret) {
*vberrp = VB2_ERROR_SECDATA_FIRMWARE_WRITE;
return log_msg_ret("secdata", ret);
}
ctx->flags &= ~VB2_CONTEXT_SECDATA_FIRMWARE_CHANGED;
}
if (ctx->flags & VB2_CONTEXT_NVDATA_CHANGED) {
log_info("Saving nvdata\n");
log_buffer(LOGC_VBOOT, LOGL_DEBUG, 0, ctx->nvdata, 1,
sizeof(ctx->nvdata), 0);
if (spl_phase() != PHASE_SPL)
vboot_nvdata_dump(ctx->nvdata, sizeof(ctx->nvdata));
ret = cros_nvdata_write_walk(CROS_NV_DATA, ctx->nvdata,
sizeof(ctx->nvdata));
if (ret) {
*vberrp = VB2_ERROR_NV_WRITE;
return log_msg_ret("save nvdata", ret);
}
ctx->flags &= ~VB2_CONTEXT_NVDATA_CHANGED;
}
return 0;
}
int vboot_run_stages(struct vboot_info *vboot, enum vboot_stage_t start,
uint flags)
{
enum vboot_stage_t stagenum;
int ret = 0;
for (stagenum = start; !ret && stagenum < VBOOT_STAGE_COUNT;
stagenum++) {
enum vb2_return_code vberr;
if (!stages[stagenum].name)
break;
ret = vboot_run_stage(vboot, stagenum);
vboot_save_if_needed(vboot, &vberr);
if (stagenum == VBOOT_STAGE_VER1_VBINIT &&
ret == VB2_ERROR_API_PHASE1_RECOVERY) {
struct fmap_section *fw = &vboot->fmap.readonly;
struct vb2_context *ctx = vboot_get_ctx(vboot);
vboot_set_selected_region(vboot, &fw->spl_rec,
&fw->boot_rec);
log_warning("flags %llx recovery=%d\n", ctx->flags,
vboot_is_recovery(vboot));
ret = 0;
}
}
/* Success - ready to continue */
if (!ret)
return 0;
if (CONFIG_IS_ENABLED(SYS_MALLOC_SIMPLE))
malloc_simple_info();
/* Allow dropping to the command line here for debugging */
if (flags & VBOOT_FLAG_CMDLINE)
return -EPERM;
if (ret == VB2_REQUEST_REBOOT) {
log_warning("Cold reboot\n");
sysreset_walk_halt(SYSRESET_COLD);
} else {
switch (vboot->vb_error) {
default:
log_warning("Cold reboot\n");
sysreset_walk_halt(SYSRESET_COLD);
break;
}
}
/* Not reached */
sysreset_walk_halt(SYSRESET_COLD);
return 0;
}
int vboot_run_auto(struct vboot_info *vboot, uint flags)
{
enum vboot_stage_t stage;
log_debug("start\n");
if (CONFIG_IS_ENABLED(CHROMEOS_VBOOT_A))
stage = VBOOT_STAGE_FIRST_VER;
else if (CONFIG_IS_ENABLED(CHROMEOS_VBOOT_B))
stage = VBOOT_STAGE_RW_FIRST_SPL;
else
stage = VBOOT_STAGE_RW_FIRST_U_BOOT;
return vboot_run_stages(vboot, stage, flags);
}
/**
* board_boot_order() - Provide a boot order for SPL
*
* This tells U-Boot which boot device to use. For now we allow falling back to
* a secondary boot for testing / development purposes.
*/
void board_boot_order(u32 *spl_boot_list)
{
spl_boot_list[0] = BOOT_DEVICE_CROS_VBOOT;
#ifdef CONFIG_X86
spl_boot_list[1] = BOOT_DEVICE_SPI_MMAP;
#else
spl_boot_list[1] = BOOT_DEVICE_BOARD;
#endif
}
int cros_do_stage(void)
{
struct vboot_info *vboot;
int ret;
log_info("start\n");
if (!CONFIG_IS_ENABLED(CHROMEOS_VBOOT_A))
return 0;
ret = vboot_alloc(&vboot);
if (ret)
return ret;
ret = vboot_run_auto(vboot, 0);
if (ret)
log_err("VPL error %d\n", ret);
return 0;
}
#endif /* CHROMEOS_VBOOT_A B or C */
#ifdef CONFIG_VPL_BUILD
/* loader for VPL if enabled */
static int cros_load_image_vpl(struct spl_image_info *spl_image,
struct spl_boot_device *bootdev)
{
struct vboot_info *vboot;
int ret;
ret = vboot_alloc(&vboot);
if (ret)
return ret;
vboot->spl_image = spl_image;
ret = vboot_run_auto(vboot, 0);
if (ret)
log_err("VPL error %d\n", ret);
log_info("Completed loading image\n");
return 0;
}
SPL_LOAD_IMAGE_METHOD("chromium_vboot_vpl", 0, BOOT_DEVICE_CROS_VBOOT,
cros_load_image_vpl);
#elif defined(CONFIG_TPL_BUILD)
/* Nothing in TPL at present */
#elif defined(CONFIG_SPL_BUILD)
/* loader for SPL */
static int cros_load_image_spl(struct spl_image_info *spl_image,
struct spl_boot_device *bootdev)
{
struct vboot_info *vboot;
int ret;
ret = vboot_alloc(&vboot);
if (ret)
return ret;
vboot->spl_image = spl_image;
ret = vboot_run_auto(vboot, 0);
if (ret)
return log_msg_ret("vboot", ret);
log_info("Completed loading image\n");
return 0;
}
SPL_LOAD_IMAGE_METHOD("chromium_vboot_spl", 0, BOOT_DEVICE_CROS_VBOOT,
cros_load_image_spl);
#endif /* CONFIG_SPL_BUILD */