diff options
Diffstat (limited to 'src/vendorcode/google/chromeos/vboot2/vboot_loader.c')
-rw-r--r-- | src/vendorcode/google/chromeos/vboot2/vboot_loader.c | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/src/vendorcode/google/chromeos/vboot2/vboot_loader.c b/src/vendorcode/google/chromeos/vboot2/vboot_loader.c new file mode 100644 index 0000000000..929f0cbb2b --- /dev/null +++ b/src/vendorcode/google/chromeos/vboot2/vboot_loader.c @@ -0,0 +1,244 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2015 Google, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc. + */ + +#include <cbfs.h> +#include <console/console.h> +#include <program_loading.h> +#include <rules.h> +#include <string.h> +#include "misc.h" +#include "../symbols.h" + +/* The stage loading code is compiled and entered from multiple stages. The + * helper functions below attempt to provide more clarity on when certain + * code should be called. */ + +static int verification_should_run(void) +{ + if (ENV_VERSTAGE && IS_ENABLED(CONFIG_SEPARATE_VERSTAGE)) + return 1; + + if (!IS_ENABLED(CONFIG_SEPARATE_VERSTAGE)) { + if (ENV_ROMSTAGE && + IS_ENABLED(CONFIG_VBOOT_STARTS_IN_ROMSTAGE)) + return 1; + if (ENV_BOOTBLOCK && + IS_ENABLED(CONFIG_VBOOT_STARTS_IN_BOOTBLOCK)) + return 1; + } + + return 0; +} + +static int verstage_should_load(void) +{ + if (!IS_ENABLED(CONFIG_SEPARATE_VERSTAGE)) + return 0; + + if (ENV_ROMSTAGE && IS_ENABLED(CONFIG_VBOOT_STARTS_IN_ROMSTAGE)) + return 1; + + if (ENV_BOOTBLOCK && IS_ENABLED(CONFIG_VBOOT_STARTS_IN_BOOTBLOCK)) + return 1; + + return 0; +} + +static void init_vb2_working_data(void) +{ + struct vb2_working_data *wd; + + wd = vboot_get_working_data(); + memset(wd, 0, _vboot2_work_size); + /* + * vboot prefers 16-byte alignment. This takes away 16 bytes + * from the VBOOT2_WORK region, but the vboot devs said that's okay. + */ + wd->buffer_offset = ALIGN_UP(sizeof(*wd), 16); + wd->buffer_size = _vboot2_work_size - wd->buffer_offset; +} + +static int vboot_loader_active(struct prog *prog) +{ + struct vb2_working_data *wd; + int run_verification; + + run_verification = verification_should_run(); + + if (run_verification) { + init_vb2_working_data(); + verstage_main(); + } else if (verstage_should_load()) { + struct prog verstage = { + .type = PROG_VERSTAGE, + .name = CONFIG_CBFS_PREFIX "/verstage", + }; + + /* load verstage from RO */ + if (cbfs_load_prog_stage(CBFS_DEFAULT_MEDIA, &verstage)) + die("failed to load verstage"); + + /* verify and select a slot */ + prog_run(&verstage); + + /* This is not actually possible to hit this condition at + * runtime, but this provides a hint to the compiler for dead + * code elimination below. */ + if (!IS_ENABLED(CONFIG_RETURN_FROM_VERSTAGE)) + return 0; + } + + /* Fill in vboot handoff structure before moving to ramstage so all + * downstream users have access to vboot results. */ + if (ENV_ROMSTAGE) + vboot_fill_handoff(); + + wd = vboot_get_working_data(); + + if (vboot_is_slot_selected(wd)) { + if (IS_ENABLED(CONFIG_MULTIPLE_CBFS_INSTANCES) && + run_verification) { + /* RW A or B */ + struct vboot_region fw_main; + + vb2_get_selected_region(wd, &fw_main); + cbfs_set_header_offset(fw_main.offset_addr); + } + return 1; + } + + return 0; +} + +static uintptr_t vboot_fw_region(int fw_index, struct vboot_region *fw_main, + struct vboot_components *fw_info, size_t *size) +{ + uintptr_t fc_addr; + uint32_t fc_size; + + if (fw_index >= fw_info->num_components) { + printk(BIOS_INFO, "invalid stage index: %d\n", fw_index); + return 0; + } + + fc_addr = fw_main->offset_addr + fw_info->entries[fw_index].offset; + fc_size = fw_info->entries[fw_index].size; + if (fc_size == 0 || + fc_addr + fc_size > fw_main->offset_addr + fw_main->size) { + printk(BIOS_INFO, "invalid stage address or size\n"); + return 0; + } + + *size = fc_size; + return fc_addr; +} + +/* This function is only called when vboot_loader_active() returns 1. That + * means we are taking vboot paths. */ +static int vboot_prepare(struct prog *prog) +{ + struct vb2_working_data *wd; + struct vboot_region fw_main; + struct vboot_components *fw_info; + + /* Code size optimization. We'd never actually get called under the + * followin cirumstances because verstage was loaded and ran -- never + * returning. */ + if (verstage_should_load() && !IS_ENABLED(CONFIG_RETURN_FROM_VERSTAGE)) + return 0; + + /* In the multi cbfs case the cbfs offset pointer has already been + * updated after firmware verification. */ + if (IS_ENABLED(CONFIG_MULTIPLE_CBFS_INSTANCES)) { + if (!ENV_RAMSTAGE && + cbfs_load_prog_stage(CBFS_DEFAULT_MEDIA, prog) != 0) + return -1; + + /* Need to load payload. */ + if (ENV_RAMSTAGE) { + void *payload; + size_t size; + + payload = cbfs_get_file_content(CBFS_DEFAULT_MEDIA, + prog->name, + CBFS_TYPE_PAYLOAD, + &size); + + if (payload == NULL) + die("Couldn't load payload\n"); + + prog_set_area(prog, payload, size); + } + return 0; + } + + wd = vboot_get_working_data(); + vb2_get_selected_region(wd, &fw_main); + fw_info = vboot_locate_components(&fw_main); + if (fw_info == NULL) + die("failed to locate firmware components\n"); + + /* Load payload in ramstage. */ + if (ENV_RAMSTAGE) { + uintptr_t payload; + void *payload_ptr; + size_t size; + + payload = vboot_fw_region(CONFIG_VBOOT_BOOT_LOADER_INDEX, + &fw_main, fw_info, &size); + + if (payload == 0) + die("Couldn't load payload."); + + payload_ptr = vboot_get_region(payload, size, NULL); + + if (payload_ptr == NULL) + die("Couldn't load payload."); + + prog_set_area(prog, payload_ptr, size); + } else { + uintptr_t stage; + size_t size; + int stage_index = 0; + + if (prog->type == PROG_ROMSTAGE) + stage_index = CONFIG_VBOOT_ROMSTAGE_INDEX; + else if (prog->type == PROG_RAMSTAGE) + stage_index = CONFIG_VBOOT_RAMSTAGE_INDEX; + else + die("Invalid program type for vboot."); + + stage = vboot_fw_region(stage_index, &fw_main, fw_info, &size); + + if (stage == 0) + die("Vboot stage load failed."); + + if (cbfs_load_prog_stage_by_offset(CBFS_DEFAULT_MEDIA, + prog, stage) < 0) + die("Vboot couldn't load stage"); + } + + return 0; +} + +const struct prog_loader_ops vboot_loader = { + .name = "VBOOT", + .is_loader_active = vboot_loader_active, + .prepare = vboot_prepare, +}; |