Patrick Georgi has submitted this change. ( https://review.coreboot.org/c/coreboot/+/36208 )
Change subject: security/vboot: Add vboot callbacks to support EC software sync ......................................................................
security/vboot: Add vboot callbacks to support EC software sync
Use the new functions introduced into the EC driver to support performing EC software sync via vboot callbacks.
NOTE: This patch assumes that the EC image is added to CBFS uncompressed. Streaming decompression of the image will be added in a future patch.
Also adds a new Kconfig option VBOOT_EARLY_EC_SYNC. The new Kconfig option compiles EC software sync into romstage, dependent upon having a CrOS EC.
BUG=b:112198832 BRANCH=none TEST=Successful EC software sync
Change-Id: I9b1458a45ab3ed5623af50f78036c4f88461b226 Signed-off-by: Tim Wawrzynczak twawrzynczak@chromium.org Reviewed-on: https://review.coreboot.org/c/coreboot/+/36208 Reviewed-by: Julius Werner jwerner@chromium.org Tested-by: build bot (Jenkins) no-reply@coreboot.org --- M src/security/vboot/Kconfig M src/security/vboot/Makefile.inc A src/security/vboot/ec_sync.c M src/security/vboot/vboot_common.h M src/security/vboot/vboot_logic.c 5 files changed, 590 insertions(+), 12 deletions(-)
Approvals: build bot (Jenkins): Verified Julius Werner: Looks good to me, approved
diff --git a/src/security/vboot/Kconfig b/src/security/vboot/Kconfig index 89e1232..df1b7e4 100644 --- a/src/security/vboot/Kconfig +++ b/src/security/vboot/Kconfig @@ -242,6 +242,18 @@ When this option is enabled cbfs_boot_locate will look for a file in the RO (COREBOOT) region if it isn't available in the active RW region.
+config VBOOT_EARLY_EC_SYNC + bool + default n + depends on EC_GOOGLE_CHROMEEC + help + Enables CrOS EC software sync in romstage, before memory training + runs. This is useful mainly as a way to achieve full USB-PD + negotiation earlier in the boot flow, as the EC will only do this once + it has made the sysjump to its RW firmware. It should not + significantly impact boot time, as this operation will be performed + later in the boot flow if it is disabled here. + menu "GBB configuration"
config GBB_HWID diff --git a/src/security/vboot/Makefile.inc b/src/security/vboot/Makefile.inc index 3e5956c..87cd91c 100644 --- a/src/security/vboot/Makefile.inc +++ b/src/security/vboot/Makefile.inc @@ -37,6 +37,8 @@ romstage-y += vbnv.c ramstage-y += vbnv.c
+romstage-$(CONFIG_VBOOT_EARLY_EC_SYNC) += ec_sync.c + bootblock-$(CONFIG_VBOOT_VBNV_CMOS) += vbnv_cmos.c verstage-$(CONFIG_VBOOT_VBNV_CMOS) += vbnv_cmos.c romstage-$(CONFIG_VBOOT_VBNV_CMOS) += vbnv_cmos.c diff --git a/src/security/vboot/ec_sync.c b/src/security/vboot/ec_sync.c new file mode 100644 index 0000000..ec048aa --- /dev/null +++ b/src/security/vboot/ec_sync.c @@ -0,0 +1,549 @@ +/* + * This file is part of the coreboot project. + * + * 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. + */ + +#include <assert.h> +#include <cbfs.h> +#include <console/console.h> +#include <delay.h> +#include <ec/google/chromeec/ec.h> +#include <security/vboot/misc.h> +#include <security/vboot/vbnv.h> +#include <security/vboot/vboot_common.h> +#include <timer.h> +#include <vb2_api.h> + +#define _EC_FILENAME(select, suffix) \ + (select == VB_SELECT_FIRMWARE_READONLY ? "ecro" suffix : "ecrw" suffix) +#define EC_IMAGE_FILENAME(select) _EC_FILENAME(select, "") +#define EC_HASH_FILENAME(select) _EC_FILENAME(select, ".hash") + +/* Wait 10 ms between attempts to check if EC's hash is ready */ +#define CROS_EC_HASH_CHECK_DELAY_MS 10 +/* Give the EC 2 seconds to finish calculating its hash */ +#define CROS_EC_HASH_TIMEOUT_MS 2000 + +/* Wait 3 seconds after software sync for EC to clear the limit power flag. */ +#define LIMIT_POWER_WAIT_TIMEOUT_MS 3000 +/* Check the limit power flag every 10 ms while waiting. */ +#define LIMIT_POWER_POLL_SLEEP_MS 10 + +/* Wait 3 seconds for EC to sysjump to RW */ +#define CROS_EC_SYSJUMP_TIMEOUT_MS 3000 + +/* + * The external API for EC software sync. This function calls into + * vboot, which kicks off the process. Vboot runs the verified boot + * logic, and requires the client program to provide callbacks which + * perform the work. + */ +void vboot_sync_ec(void) +{ + vb2_error_t retval = VB2_SUCCESS; + struct vb2_context *ctx; + + ctx = vboot_get_context(); + ctx->flags |= VB2_CONTEXT_EC_SYNC_SUPPORTED; + + retval = vb2api_ec_sync(ctx); + vboot_save_nvdata_only(ctx); + + if (retval != VB2_SUCCESS) { + printk(BIOS_ERR, "EC software sync failed (%#x), rebooting\n", retval); + vboot_reboot(); + } +} + +/* Convert firmware image type into a flash offset */ +static uint32_t get_vboot_hash_offset(enum vb2_firmware_selection select) +{ + switch (select) { + case VB_SELECT_FIRMWARE_READONLY: + return EC_VBOOT_HASH_OFFSET_RO; + case VB_SELECT_FIRMWARE_EC_UPDATE: + return EC_VBOOT_HASH_OFFSET_UPDATE; + default: + return EC_VBOOT_HASH_OFFSET_ACTIVE; + } +} + +/* + * Asks the EC to calculate a hash of the specified firmware image, and + * returns the information in **hash and *hash_size. + */ +static vb2_error_t ec_hash_image(enum vb2_firmware_selection select, + const uint8_t **hash, int *hash_size) +{ + static struct ec_response_vboot_hash resp; + uint32_t hash_offset; + int recalc_requested = 0; + struct stopwatch sw; + + hash_offset = get_vboot_hash_offset(select); + + stopwatch_init_msecs_expire(&sw, CROS_EC_HASH_TIMEOUT_MS); + do { + if (google_chromeec_get_vboot_hash(hash_offset, &resp)) + return VB2_ERROR_UNKNOWN; + + switch (resp.status) { + case EC_VBOOT_HASH_STATUS_NONE: + /* + * There is no hash available right now. + * Request a recalc if it hasn't been done yet. + */ + if (recalc_requested) + break; + + printk(BIOS_WARNING, + "%s: No valid hash (status=%d size=%d). " + "Computing...\n", __func__, resp.status, + resp.size); + + if (google_chromeec_start_vboot_hash( + EC_VBOOT_HASH_TYPE_SHA256, hash_offset, &resp)) + return VB2_ERROR_UNKNOWN; + + recalc_requested = 1; + + /* + * Expect status to be busy since we just sent + * a recalc request. + */ + resp.status = EC_VBOOT_HASH_STATUS_BUSY; + + /* Hash just started calculating, let it go for a bit */ + mdelay(CROS_EC_HASH_CHECK_DELAY_MS); + break; + + case EC_VBOOT_HASH_STATUS_BUSY: + /* Hash is still calculating. */ + mdelay(CROS_EC_HASH_CHECK_DELAY_MS); + break; + + case EC_VBOOT_HASH_STATUS_DONE: /* intentional fallthrough */ + default: + /* Hash is ready! */ + break; + } + } while (resp.status == EC_VBOOT_HASH_STATUS_BUSY && + !stopwatch_expired(&sw)); + + if (resp.status != EC_VBOOT_HASH_STATUS_DONE) { + printk(BIOS_ERR, "%s: Hash status not done: %d\n", __func__, + resp.status); + return VB2_ERROR_UNKNOWN; + } + if (resp.hash_type != EC_VBOOT_HASH_TYPE_SHA256) { + printk(BIOS_ERR, "EC hash was the wrong type.\n"); + return VB2_ERROR_UNKNOWN; + } + + printk(BIOS_INFO, "EC took %luus to calculate image hash\n", + stopwatch_duration_usecs(&sw)); + + *hash = resp.hash_digest; + *hash_size = resp.digest_size; + + return VB2_SUCCESS; +} + +/* + * Asks the EC to protect or unprotect the specified flash region. + */ +static vb2_error_t ec_protect_flash(enum vb2_firmware_selection select, int enable) +{ + struct ec_response_flash_protect resp; + uint32_t protected_region = EC_FLASH_PROTECT_ALL_NOW; + const uint32_t mask = EC_FLASH_PROTECT_ALL_NOW | EC_FLASH_PROTECT_ALL_AT_BOOT; + + if (select == VB_SELECT_FIRMWARE_READONLY) + protected_region = EC_FLASH_PROTECT_RO_NOW; + + if (google_chromeec_flash_protect(mask, enable ? mask : 0, &resp) != 0) + return VB2_ERROR_UNKNOWN; + + if (!enable) { + /* If protection is still enabled, need reboot */ + if (resp.flags & protected_region) + return VBERROR_EC_REBOOT_TO_RO_REQUIRED; + + return VB2_SUCCESS; + } + + /* + * If write protect and ro-at-boot aren't both asserted, don't expect + * protection enabled. + */ + if ((~resp.flags) & (EC_FLASH_PROTECT_GPIO_ASSERTED | + EC_FLASH_PROTECT_RO_AT_BOOT)) + return VB2_SUCCESS; + + /* If flash is protected now, success */ + if (resp.flags & EC_FLASH_PROTECT_ALL_NOW) + return VB2_SUCCESS; + + /* If RW will be protected at boot but not now, need a reboot */ + if (resp.flags & EC_FLASH_PROTECT_ALL_AT_BOOT) + return VBERROR_EC_REBOOT_TO_RO_REQUIRED; + + /* Otherwise, it's an error */ + return VB2_ERROR_UNKNOWN; +} + +/* Convert a firmware image type to an EC flash region */ +static enum ec_flash_region vboot_to_ec_region(enum vb2_firmware_selection select) +{ + switch (select) { + case VB_SELECT_FIRMWARE_READONLY: + return EC_FLASH_REGION_WP_RO; + case VB_SELECT_FIRMWARE_EC_UPDATE: + return EC_FLASH_REGION_UPDATE; + default: + return EC_FLASH_REGION_ACTIVE; + } +} + +/* + * Read the EC's burst size bytes at a time from CBFS, and then send + * the chunk to the EC for it to write into its flash. + */ +static vb2_error_t ec_flash_write(struct region_device *image_region, + uint32_t region_offset, int image_size) +{ + struct ec_response_get_protocol_info resp_proto; + struct ec_response_flash_info resp_flash; + ssize_t pdata_max_size; + ssize_t burst; + uint8_t *file_buf; + struct ec_params_flash_write *params; + uint32_t end, off; + + /* + * Get EC's protocol information, so that we can figure out how much + * data can be sent in one message. + */ + if (google_chromeec_get_protocol_info(&resp_proto)) { + printk(BIOS_ERR, "Failed to get EC protocol information; " + "skipping flash write\n"); + return VB2_ERROR_UNKNOWN; + } + + /* + * Determine burst size. This must be a multiple of the write block + * size, and must also fit into the host parameter buffer. + */ + if (google_chromeec_flash_info(&resp_flash)) { + printk(BIOS_ERR, "Failed to get EC flash information; " + "skipping flash write\n"); + return VB2_ERROR_UNKNOWN; + } + + /* Limit the potential buffer stack allocation to 1K */ + pdata_max_size = MIN(1024, resp_proto.max_request_packet_size - + sizeof(struct ec_host_request)); + + /* Round burst to a multiple of the flash write block size */ + burst = pdata_max_size - sizeof(*params); + burst = (burst / resp_flash.write_block_size) * + resp_flash.write_block_size; + + /* Buffer too small */ + if (burst <= 0) { + printk(BIOS_ERR, "Flash write buffer too small! skipping " + "flash write\n"); + return VB2_ERROR_UNKNOWN; + } + + /* Allocate buffer on the stack */ + params = alloca(burst + sizeof(*params)); + + /* Fill up the buffer */ + end = region_offset + image_size; + for (off = region_offset; off < end; off += burst) { + uint32_t todo = MIN(end - off, burst); + uint32_t xfer_size = todo + sizeof(*params); + + /* Map 'todo' bytes into memory */ + file_buf = rdev_mmap(image_region, off - region_offset, todo); + if (file_buf == NULL) + return VB2_ERROR_UNKNOWN; + + params->offset = off; + params->size = todo; + + /* Read todo bytes into the buffer */ + memcpy(params + 1, file_buf, todo); + + if (rdev_munmap(image_region, file_buf)) + return VB2_ERROR_UNKNOWN; + + /* Make sure to add back in the size of the parameters */ + if (google_chromeec_flash_write_block( + (const uint8_t *)params, xfer_size)) { + printk(BIOS_ERR, "EC failed flash write command, " + "relative offset %u!\n", off - region_offset); + return VB2_ERROR_UNKNOWN; + } + } + + return VB2_SUCCESS; +} + +/* + * The logic for updating an EC firmware image. + */ +static vb2_error_t ec_update_image(enum vb2_firmware_selection select) +{ + uint32_t region_offset, region_size; + enum ec_flash_region region; + vb2_error_t rv; + size_t image_size; + struct cbfsf fh; + const char *filename; + struct region_device image_region; + + /* Un-protect the flash region */ + rv = ec_protect_flash(select, 0); + if (rv != VB2_SUCCESS) + return rv; + + /* Convert vboot region into an EC region */ + region = vboot_to_ec_region(select); + + /* Get information about the flash region */ + if (google_chromeec_flash_region_info(region, ®ion_offset, + ®ion_size)) + return VB2_ERROR_UNKNOWN; + + /* Locate the CBFS file */ + filename = EC_IMAGE_FILENAME(select); + if (cbfs_boot_locate(&fh, filename, NULL)) + return VB2_ERROR_UNKNOWN; + + /* Get the file size and the region struct */ + image_size = region_device_sz(&fh.data); + cbfs_file_data(&image_region, &fh); + + /* Bail if the image is too large */ + if (image_size > region_size) + return VB2_ERROR_INVALID_PARAMETER; + + /* Erase the region */ + if (google_chromeec_flash_erase(region_offset, region_size)) + return VB2_ERROR_UNKNOWN; + + /* Write the image into the region */ + if (ec_flash_write(&image_region, region_offset, image_size)) + return VB2_ERROR_UNKNOWN; + + /* Verify the image */ + if (google_chromeec_efs_verify(region)) + return VB2_ERROR_UNKNOWN; + + return VB2_SUCCESS; +} + +static vb2_error_t ec_get_expected_hash(enum vb2_firmware_selection select, + const uint8_t **hash, + int *hash_size) +{ + size_t size; + const char *filename = EC_HASH_FILENAME(select); + const uint8_t *file = cbfs_boot_map_with_leak(filename, CBFS_TYPE_RAW, &size); + + if (file == NULL) + return VB2_ERROR_UNKNOWN; + + *hash = file; + *hash_size = (int)size; + + return VB2_SUCCESS; +} + +/*********************************************************************** + * Vboot Callbacks + ***********************************************************************/ + +/* + * Unsupported. + * + * coreboot does not support the graphics initialization needed to + * display the vboot "wait" screens, etc., because the use case for + * supporting software sync early in the boot flow is to be able to + * quickly update the EC and/or sysjump to RW earlier so that USB-PD + * power (> 15 W) can be negotiated for earlier. + */ +vb2_error_t VbExDisplayScreen(uint32_t screen_type, uint32_t locale, + const VbScreenData *data) +{ + return VB2_ERROR_UNKNOWN; +} + +/* + * Write opaque data into NV storage region. + */ +vb2_error_t VbExNvStorageWrite(const uint8_t *buf) +{ + save_vbnv(buf); + return VB2_SUCCESS; +} + +/* + * Report whether the EC is in RW or not. + */ +vb2_error_t vb2ex_ec_running_rw(int *in_rw) +{ + *in_rw = !google_ec_running_ro(); + return VB2_SUCCESS; +} + +/* + * Callback for when Vboot is finished. + */ +vb2_error_t vb2ex_ec_vboot_done(struct vb2_context *ctx) +{ + int limit_power = 0; + bool message_printed = false; + struct stopwatch sw; + vb2_error_t rv = VB2_SUCCESS; + int in_recovery = !!(ctx->flags & VB2_CONTEXT_RECOVERY_MODE); + + /* + * Do not wait for the limit power flag to be cleared in + * recovery mode since we didn't just sysjump. + */ + if (in_recovery) + return VB2_SUCCESS; + + stopwatch_init_msecs_expire(&sw, LIMIT_POWER_WAIT_TIMEOUT_MS); + + /* Ensure we have enough power to continue booting */ + while (1) { + if (google_chromeec_read_limit_power_request(&limit_power)) { + printk(BIOS_ERR, "Failed to check EC limit power" + "flag.\n"); + rv = VB2_ERROR_UNKNOWN; + break; + } + + if (!limit_power || stopwatch_expired(&sw)) + break; + + if (!message_printed) { + printk(BIOS_SPEW, + "Waiting for EC to clear limit power flag.\n"); + message_printed = true; + } + + mdelay(LIMIT_POWER_POLL_SLEEP_MS); + } + + if (limit_power) { + printk(BIOS_INFO, + "EC requests limited power usage. Request shutdown.\n"); + rv = VBERROR_SHUTDOWN_REQUESTED; + } else { + printk(BIOS_INFO, "Waited %luus to clear limit power flag.\n", + stopwatch_duration_usecs(&sw)); + } + + return rv; +} + +/* + * Support battery cutoff if required. + */ +vb2_error_t vb2ex_ec_battery_cutoff(void) +{ + if (google_chromeec_battery_cutoff(EC_BATTERY_CUTOFF_FLAG_AT_SHUTDOWN)) + return VB2_ERROR_UNKNOWN; + + return VB2_SUCCESS; +} + +/* + * Vboot callback for calculating an EC image's hash. + */ +vb2_error_t vb2ex_ec_hash_image(enum vb2_firmware_selection select, + const uint8_t **hash, int *hash_size) +{ + return ec_hash_image(select, hash, hash_size); +} + +/* + * Vboot callback for EC flash protection. + */ +vb2_error_t vb2ex_ec_protect(enum vb2_firmware_selection select) +{ + return ec_protect_flash(select, 1); +} + +/* + * Get hash for image. + */ +vb2_error_t vb2ex_ec_get_expected_image_hash(enum vb2_firmware_selection select, + const uint8_t **hash, + int *hash_size) +{ + return ec_get_expected_hash(select, hash, hash_size); +} + +/* + * Disable further sysjumps (i.e., stay in RW until next reboot) + */ +vb2_error_t vb2ex_ec_disable_jump(void) +{ + if (google_chromeec_reboot(0, EC_REBOOT_DISABLE_JUMP, 0)) + return VB2_ERROR_UNKNOWN; + + return VB2_SUCCESS; +} + +/* + * Update EC image. + */ +vb2_error_t vb2ex_ec_update_image(enum vb2_firmware_selection select) +{ + return ec_update_image(select); +} + +/* + * Vboot callback for commanding EC to sysjump to RW. + */ +vb2_error_t vb2ex_ec_jump_to_rw(void) +{ + struct stopwatch sw; + + if (google_chromeec_reboot(0, EC_REBOOT_JUMP_RW, 0)) + return VB2_ERROR_UNKNOWN; + + /* Give the EC 3 seconds to sysjump */ + stopwatch_init_msecs_expire(&sw, CROS_EC_SYSJUMP_TIMEOUT_MS); + + /* Default delay to wait after EC reboot */ + mdelay(50); + while (google_chromeec_hello()) { + if (stopwatch_expired(&sw)) { + printk(BIOS_ERR, "EC did not return from reboot after %luus\n", + stopwatch_duration_usecs(&sw)); + return VB2_ERROR_UNKNOWN; + } + + mdelay(5); + } + + printk(BIOS_INFO, "\nEC returned from reboot after %luus\n", + stopwatch_duration_usecs(&sw)); + + return VB2_SUCCESS; +} diff --git a/src/security/vboot/vboot_common.h b/src/security/vboot/vboot_common.h index a20ab62..d296574 100644 --- a/src/security/vboot/vboot_common.h +++ b/src/security/vboot/vboot_common.h @@ -80,4 +80,13 @@ static inline void vboot_run_logic(void) {} #endif
+void vboot_save_nvdata_only(struct vb2_context *ctx); +void vboot_save_data(struct vb2_context *ctx); + +/* + * The API for performing EC software sync. Does not support + * "slow" updates or Auxiliary FW sync. + */ +void vboot_sync_ec(void); + #endif /* __VBOOT_VBOOT_COMMON_H__ */ diff --git a/src/security/vboot/vboot_logic.c b/src/security/vboot/vboot_logic.c index 6ee4d94..ccce148 100644 --- a/src/security/vboot/vboot_logic.c +++ b/src/security/vboot/vboot_logic.c @@ -251,21 +251,27 @@ return VB2_SUCCESS; }
-/** - * Save non-volatile and/or secure data if needed. - */ -static void save_if_needed(struct vb2_context *ctx) +void vboot_save_nvdata_only(struct vb2_context *ctx) { + assert(!(ctx->flags & (VB2_CONTEXT_SECDATA_FIRMWARE_CHANGED | + VB2_CONTEXT_SECDATA_KERNEL_CHANGED))); + if (ctx->flags & VB2_CONTEXT_NVDATA_CHANGED) { printk(BIOS_INFO, "Saving nvdata\n"); save_vbnv(ctx->nvdata); ctx->flags &= ~VB2_CONTEXT_NVDATA_CHANGED; } +} + +void vboot_save_data(struct vb2_context *ctx) +{ if (ctx->flags & VB2_CONTEXT_SECDATA_CHANGED) { printk(BIOS_INFO, "Saving secdata\n"); antirollback_write_space_firmware(ctx); ctx->flags &= ~VB2_CONTEXT_SECDATA_CHANGED; } + + vboot_save_nvdata_only(ctx); }
static uint32_t extend_pcrs(struct vb2_context *ctx) @@ -368,13 +374,13 @@ */ if (rv == VB2_ERROR_API_PHASE1_RECOVERY) { printk(BIOS_INFO, "Recovery requested (%x)\n", rv); - save_if_needed(ctx); + vboot_save_data(ctx); extend_pcrs(ctx); /* ignore failures */ goto verstage_main_exit; }
printk(BIOS_INFO, "Reboot requested (%x)\n", rv); - save_if_needed(ctx); + vboot_save_data(ctx); vboot_reboot(); }
@@ -383,7 +389,7 @@ rv = vb2api_fw_phase2(ctx); if (rv) { printk(BIOS_INFO, "Reboot requested (%x)\n", rv); - save_if_needed(ctx); + vboot_save_data(ctx); vboot_reboot(); }
@@ -394,7 +400,7 @@ timestamp_add_now(TS_END_VERIFY_SLOT); if (rv) { printk(BIOS_INFO, "Reboot requested (%x)\n", rv); - save_if_needed(ctx); + vboot_save_data(ctx); vboot_reboot(); }
@@ -405,7 +411,7 @@ "Failed to read FMAP to locate firmware");
rv = hash_body(ctx, &fw_main); - save_if_needed(ctx); + vboot_save_data(ctx); if (rv) { printk(BIOS_INFO, "Reboot requested (%x)\n", rv); vboot_reboot(); @@ -419,7 +425,7 @@ printk(BIOS_WARNING, "Failed to extend TPM PCRs (%#x)\n", rv); vb2api_fail(ctx, VB2_RECOVERY_RO_TPM_U_ERROR, rv); - save_if_needed(ctx); + vboot_save_data(ctx); vboot_reboot(); } timestamp_add_now(TS_END_TPMPCR); @@ -432,7 +438,7 @@ if (rv) { printk(BIOS_INFO, "Failed to lock TPM (%x)\n", rv); vb2api_fail(ctx, VB2_RECOVERY_RO_TPM_L_ERROR, 0); - save_if_needed(ctx); + vboot_save_data(ctx); vboot_reboot(); } timestamp_add_now(TS_END_TPMLOCK); @@ -445,7 +451,7 @@ rv); vb2api_fail(ctx, VB2_RECOVERY_RO_TPM_REC_HASH_L_ERROR, 0); - save_if_needed(ctx); + vboot_save_data(ctx); vboot_reboot(); } }