Tim Wawrzynczak has uploaded this change for review. ( 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.
BUG=b:112198832 BRANCH=none TEST=Successful EC software sync
Change-Id: I9b1458a45ab3ed5623af50f78036c4f88461b226 Signed-off-by: Tim Wawrzynczak twawrzynczak@chromium.org --- A src/security/vboot/sync_ec.c A src/security/vboot/sync_ec.h 2 files changed, 468 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/08/36208/1
diff --git a/src/security/vboot/sync_ec.c b/src/security/vboot/sync_ec.c new file mode 100644 index 0000000..f40934b --- /dev/null +++ b/src/security/vboot/sync_ec.c @@ -0,0 +1,443 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2019 Google LLC + * + * 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 <2constants.h> +#include <cbfs.h> +#include <console/console.h> +#include <delay.h> +#include <ec/google/chromeec/ec.h> +#include <ec_sync.h> +#include <security/vboot/misc.h> +#include <security/vboot/sync_ec.h> +#include <security/vboot/vbnv.h> +#include <timer.h> +#include <timestamp.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") + +#define EC_PRIMARY_IDX 0 + +static const int CROS_EC_HASH_CHECK_DELAY_MS = 10; +static const int CROS_EC_HASH_TIMEOUT_MS = 2000; + +/* Required for ec_update_image */ +static uint8_t read_buffer[EC_LPC_HOST_PACKET_SIZE]; +static struct region_device fw_image_region; + +/* + * 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. + */ +int vboot_sync_ec(void) +{ + static uint8_t temp_workbuf[VB2_FIRMWARE_WORKBUF_RECOMMENDED_SIZE]; + struct vb2_context ctx; + struct vboot_working_data *wd; + vb2_error_t retval = 0; + uint8_t *p; + + /* Init workspace, VBNB, etc. */ + ctx.workbuf_size = VB2_FIRMWARE_WORKBUF_RECOMMENDED_SIZE; + ctx.workbuf = temp_workbuf; + wd = vboot_get_working_data(); + + p = (uint8_t *)wd + wd->buffer_offset; + memcpy(ctx.workbuf, (void *)p, wd->buffer_size); + + ctx.flags |= VB2_CONTEXT_EC_SYNC_SUPPORTED; + ctx.flags &= ~VB2_CONTEXT_EC_SYNC_SLOW; + vbnv_init(ctx.nvdata); + + /* Perform software sync */ + retval = vb2api_ec_software_sync(&ctx); + if (retval != VB2_SUCCESS) + printk(BIOS_ERR, "Software sync on EC failed (%d)\n", (int)retval); + + vboot_finalize_work_context(&ctx); + + return retval; +} + +/* Convert firmware image type into a Flash offset */ +static uint32_t get_vboot_hash_offset(enum VbSelectFirmware_t 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 VbSelectFirmware_t 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; + int rv; + + hash_offset = get_vboot_hash_offset(select); + + stopwatch_init_msecs_expire(&sw, CROS_EC_HASH_TIMEOUT_MS); + do { + rv = google_chromeec_get_vboot_hash(hash_offset, &resp); + if (rv) + return VB2_ERROR_UNKNOWN; + + switch (resp.status) { + case EC_VBOOT_HASH_STATUS_NONE: + /* We have no valid hash - let's request a recalc + * if we haven't done so yet. */ + if (recalc_requested != 0) + break; + + printk(BIOS_WARNING, + "%s: No valid hash (status=%d size=%d). " + "Computing...\n", __func__, resp.status, + resp.size); + + rv = google_chromeec_start_vboot_hash( + EC_VBOOT_HASH_TYPE_SHA256, hash_offset, &resp); + if (rv) + return VB2_ERROR_UNKNOWN; + + recalc_requested = 1; + /* Expect status to be busy (and don't break while) + * since we just sent a recalc request. */ + resp.status = EC_VBOOT_HASH_STATUS_BUSY; + break; + case EC_VBOOT_HASH_STATUS_BUSY: + /* Hash is still calculating. */ + mdelay(CROS_EC_HASH_CHECK_DELAY_MS); + printk(BIOS_INFO, "."); + break; + case EC_VBOOT_HASH_STATUS_DONE: + default: + /* We have a valid hash. */ + 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; + } + + *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 VbSelectFirmware_t 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 VbSelectFirmware_t 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 int ec_flash_write(const uint8_t *image, uint32_t region_offset, + int image_size) +{ + u32 burst = google_chromeec_flash_write_burst_size(); + u32 end, off; + int ret; + + if (!burst) + return -1; + + end = region_offset + image_size; + for (off = region_offset; off < end; off += burst) { + uint32_t todo = MIN(end - off, burst); + + /* Read todo bytes into the buffer */ + if (rdev_readat(&fw_image_region, read_buffer, + off - region_offset, todo) == todo) { + ret = google_chromeec_flash_write_block(read_buffer, off, todo); + if (ret) + return ret; + } else { + printk(BIOS_ERR, "Failed to read EC FW image!\n"); + return -1; + } + } + + return 0; +} + +/* + * The logic for updating an EC firmware image. + */ +static vb2_error_t ec_update_image(enum VbSelectFirmware_t select, + const uint8_t *image, int image_size) +{ + uint32_t region_offset, region_size; + enum ec_flash_region region; + vb2_error_t rv; + + /* Un-protect the flash region */ + rv = ec_protect_flash(select, 0); + if (rv == VBERROR_EC_REBOOT_TO_RO_REQUIRED || 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; + + /* Bail if our 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_offset, image_size)) + return VB2_ERROR_UNKNOWN; + + /* Verify the image */ + if (google_chromeec_efs_verify(region)) + return VB2_ERROR_UNKNOWN; + + return VB2_SUCCESS; +} + +/*********************************************************************** + * Vboot Callbacks + ***********************************************************************/ + +/* Unsupported */ +vb2_error_t VbExDisplayScreen(uint32_t screen_type, uint32_t locale, + const VbScreenData *data) +{ + return VB2_ERROR_UNKNOWN; +} + +/* Unsupported */ +vb2_error_t VbExNvStorageWrite(const uint8_t *buf) +{ + return VB2_ERROR_UNKNOWN; +} + +/* Report whether the EC is in RW or not */ +vb2_error_t VbExEcRunningRW(int devidx, int *in_rw) +{ + assert(devidx == 0); + + /* Re-init to get RW/RO information */ + google_chromeec_init(); + if (google_ec_running_ro()) { + *in_rw = 0; + } else { + *in_rw = 1; + } + + return VB2_SUCCESS; +} + +/* Callback for when Vboot is finished */ +vb2_error_t VbExEcVbootDone(int in_recovery) +{ + return VB2_SUCCESS; +} + +/* Unsupported */ +vb2_error_t VbExEcBatteryCutOff(void) +{ + return VB2_ERROR_UNKNOWN; +} + +/* Vboot callback for EC image hash */ +vb2_error_t VbExEcHashImage(int devidx, enum VbSelectFirmware_t select, + const uint8_t **hash, int *hash_size) +{ + assert(devidx == 0); + return ec_hash_image(select, hash, hash_size); +} + +/* Vboot callback for EC flash protection */ +vb2_error_t VbExEcProtect(int devidx, enum VbSelectFirmware_t select) +{ + assert(devidx == 0); + return ec_protect_flash(select, 1); +} + +/* Get hash for image */ +vb2_error_t VbExEcGetExpectedImageHash(int devidx, + enum VbSelectFirmware_t select, + const uint8_t **hash, int *hash_size) +{ + size_t size; + const char *filename = EC_HASH_FILENAME(select); + 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; +} + +/* Disable sysjump */ +vb2_error_t VbExEcDisableJump(int devidx) +{ + int rv; + assert(devidx == 0); + + rv = google_chromeec_reboot(EC_PRIMARY_IDX, EC_REBOOT_DISABLE_JUMP, 0); + if (rv) + return VB2_ERROR_UNKNOWN; + + return VB2_SUCCESS; +} + +/* Update EC image */ +vb2_error_t VbExEcUpdateImage(int devidx, enum VbSelectFirmware_t select, + const uint8_t *image, int image_size) +{ + assert(devidx == 0); + + return ec_update_image(select, image, image_size); +} + +/* Vboot callback for retrieving the expected image */ +vb2_error_t VbExEcGetExpectedImage(int devidx, enum VbSelectFirmware_t select, + const uint8_t **image, int *image_size) +{ + const char *filename = EC_IMAGE_FILENAME(select); + struct cbfsf fh; + int rv = cbfs_boot_locate(&fh, filename, NULL); + if (rv) + return VB2_ERROR_UNKNOWN; + + cbfs_file_data(&fw_image_region, &fh); + + *image = read_buffer; + *image_size = (int)region_device_sz(&fh.data); + return VB2_SUCCESS; +} + +/* Vboot callback for commanding EC to sysjump to RW */ +vb2_error_t VbExEcJumpToRW(int devidx) +{ + struct stopwatch outer; + + assert(devidx == 0); + if (google_chromeec_reboot(EC_PRIMARY_IDX, EC_REBOOT_JUMP_RW, 0)) { + return VB2_ERROR_UNKNOWN; + } + + /* Give the EC 3 seconds to sysjump */ + stopwatch_init_msecs_expire(&outer, 3000); + + /* Default delay to wait after EC reboot */ + mdelay(50); + while (google_chromeec_test()) { + if (stopwatch_expired(&outer)) { + printk(BIOS_ERR, "EC did not return from reboot after %lus\n", + stopwatch_duration_usecs(&outer)); + return -1; + } + + mdelay(5); + } + + printk(BIOS_INFO, "\nEC returned from reboot after %lus, and ", + stopwatch_duration_usecs(&outer)); + + return VB2_SUCCESS; +} diff --git a/src/security/vboot/sync_ec.h b/src/security/vboot/sync_ec.h new file mode 100644 index 0000000..038f179 --- /dev/null +++ b/src/security/vboot/sync_ec.h @@ -0,0 +1,25 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2019 Google LLC + * + * 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. + */ + +#ifndef __VBOOT_SECURITY_SYNC_EC_H__ +#define __VBOOT_SECURITY_SYNC_EC_H__ + +/* + * The external API for performing EC software sync. The return code + * comes directly from vboot, coerced to an int. + */ +int vboot_sync_ec(void); + +#endif /* __VBOOT_SECURITY_SYNC_EC_H__ */