| /* 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. |
| * |
| * EC software sync routines for vboot |
| */ |
| |
| #include "2sysincludes.h" |
| #include "2common.h" |
| #include "2nvstorage.h" |
| #include "sysincludes.h" |
| #include "utility.h" |
| #include "vb2_common.h" |
| #include "vboot_api.h" |
| #include "vboot_common.h" |
| #include "vboot_display.h" |
| #include "vboot_kernel.h" |
| |
| static void request_recovery(struct vb2_context *ctx, uint32_t recovery_request) |
| { |
| VB2_DEBUG("request_recovery(%u)\n", recovery_request); |
| |
| vb2_nv_set(ctx, VB2_NV_RECOVERY_REQUEST, recovery_request); |
| } |
| |
| /** |
| * Wrapper around VbExEcProtect() which sets recovery reason on error. |
| */ |
| static VbError_t EcProtect(struct vb2_context *ctx, int devidx, |
| enum VbSelectFirmware_t select) |
| { |
| int rv = VbExEcProtect(devidx, select); |
| |
| if (rv == VBERROR_EC_REBOOT_TO_RO_REQUIRED) { |
| VBDEBUG(("VbExEcProtect() needs reboot\n")); |
| } else if (rv != VBERROR_SUCCESS) { |
| VBDEBUG(("VbExEcProtect() returned %d\n", rv)); |
| request_recovery(ctx, VB2_RECOVERY_EC_PROTECT); |
| } |
| return rv; |
| } |
| |
| static VbError_t EcUpdateImage(struct vb2_context *ctx, int devidx, |
| VbCommonParams *cparams, |
| enum VbSelectFirmware_t select, |
| int *need_update, int in_rw) |
| { |
| VbSharedDataHeader *shared = |
| (VbSharedDataHeader *)cparams->shared_data_blob; |
| int rv; |
| int hash_size; |
| int ec_hash_size; |
| const uint8_t *hash = NULL; |
| const uint8_t *expected = NULL; |
| const uint8_t *ec_hash = NULL; |
| int expected_size; |
| int i; |
| int rw_request = select != VB_SELECT_FIRMWARE_READONLY; |
| |
| *need_update = 0; |
| VBDEBUG(("EcUpdateImage() - " |
| "Check for %s update\n", rw_request ? "RW" : "RO")); |
| |
| /* Get current EC hash. */ |
| rv = VbExEcHashImage(devidx, select, &ec_hash, &ec_hash_size); |
| if (rv) { |
| VBDEBUG(("EcUpdateImage() - " |
| "VbExEcHashImage() returned %d\n", rv)); |
| request_recovery(ctx, VB2_RECOVERY_EC_HASH_FAILED); |
| return VBERROR_EC_REBOOT_TO_RO_REQUIRED; |
| } |
| VBDEBUG(("EC-%s hash: ", rw_request ? "RW" : "RO")); |
| for (i = 0; i < ec_hash_size; i++) |
| VBDEBUG(("%02x",ec_hash[i])); |
| VBDEBUG(("\n")); |
| |
| /* Get expected EC hash. */ |
| rv = VbExEcGetExpectedImageHash(devidx, select, &hash, &hash_size); |
| if (rv) { |
| VBDEBUG(("EcUpdateImage() - " |
| "VbExEcGetExpectedImageHash() returned %d\n", rv)); |
| request_recovery(ctx, VB2_RECOVERY_EC_EXPECTED_HASH); |
| return VBERROR_EC_REBOOT_TO_RO_REQUIRED; |
| } |
| if (ec_hash_size != hash_size) { |
| VBDEBUG(("EcUpdateImage() - " |
| "EC uses %d-byte hash, but AP-RW contains %d bytes\n", |
| ec_hash_size, hash_size)); |
| request_recovery(ctx, VB2_RECOVERY_EC_HASH_SIZE); |
| return VBERROR_EC_REBOOT_TO_RO_REQUIRED; |
| } |
| |
| VBDEBUG(("Expected hash: ")); |
| for (i = 0; i < hash_size; i++) |
| VBDEBUG(("%02x", hash[i])); |
| VBDEBUG(("\n")); |
| *need_update = vb2_safe_memcmp(ec_hash, hash, hash_size); |
| |
| if (!*need_update) |
| return VBERROR_SUCCESS; |
| |
| /* Get expected EC image */ |
| rv = VbExEcGetExpectedImage(devidx, select, &expected, &expected_size); |
| if (rv) { |
| VBDEBUG(("EcUpdateImage() - " |
| "VbExEcGetExpectedImage() returned %d\n", rv)); |
| request_recovery(ctx, VB2_RECOVERY_EC_EXPECTED_IMAGE); |
| return VBERROR_EC_REBOOT_TO_RO_REQUIRED; |
| } |
| VBDEBUG(("EcUpdateImage() - image len = %d\n", expected_size)); |
| |
| if (in_rw && rw_request) { |
| /* |
| * Check if BIOS should also load VGA Option ROM when |
| * rebooting to save another reboot if possible. |
| */ |
| if ((shared->flags & VBSD_EC_SLOW_UPDATE) && |
| (shared->flags & VBSD_OPROM_MATTERS) && |
| !(shared->flags & VBSD_OPROM_LOADED)) { |
| VBDEBUG(("EcUpdateImage() - Reboot to " |
| "load VGA Option ROM\n")); |
| vb2_nv_set(ctx, VB2_NV_OPROM_NEEDED, 1); |
| } |
| |
| /* |
| * EC is running the wrong RW image. Reboot the EC to |
| * RO so we can update it on the next boot. |
| */ |
| VBDEBUG(("EcUpdateImage() - " |
| "in RW, need to update RW, so reboot\n")); |
| return VBERROR_EC_REBOOT_TO_RO_REQUIRED; |
| } |
| |
| VBDEBUG(("EcUpdateImage() updating EC-%s...\n", |
| rw_request ? "RW" : "RO")); |
| |
| if (shared->flags & VBSD_EC_SLOW_UPDATE) { |
| VBDEBUG(("EcUpdateImage() - EC is slow. Show WAIT screen.\n")); |
| |
| /* Ensure the VGA Option ROM is loaded */ |
| if ((shared->flags & VBSD_OPROM_MATTERS) && |
| !(shared->flags & VBSD_OPROM_LOADED)) { |
| VBDEBUG(("EcUpdateImage() - Reboot to " |
| "load VGA Option ROM\n")); |
| vb2_nv_set(ctx, VB2_NV_OPROM_NEEDED, 1); |
| return VBERROR_VGA_OPROM_MISMATCH; |
| } |
| |
| VbDisplayScreen(ctx, cparams, VB_SCREEN_WAIT, 0); |
| } |
| |
| rv = VbExEcUpdateImage(devidx, select, expected, expected_size); |
| if (rv != VBERROR_SUCCESS) { |
| VBDEBUG(("EcUpdateImage() - " |
| "VbExEcUpdateImage() returned %d\n", rv)); |
| |
| /* |
| * The EC may know it needs a reboot. It may need to |
| * unprotect the region before updating, or may need to |
| * reboot after updating. Either way, it's not an error |
| * requiring recovery mode. |
| * |
| * If we fail for any other reason, trigger recovery |
| * mode. |
| */ |
| if (rv != VBERROR_EC_REBOOT_TO_RO_REQUIRED) |
| request_recovery(ctx, VB2_RECOVERY_EC_UPDATE); |
| |
| return VBERROR_EC_REBOOT_TO_RO_REQUIRED; |
| } |
| |
| /* Verify the EC was updated properly */ |
| rv = VbExEcHashImage(devidx, select, &ec_hash, &ec_hash_size); |
| if (rv) { |
| VBDEBUG(("EcUpdateImage() - " |
| "VbExEcHashImage() returned %d\n", rv)); |
| request_recovery(ctx, VB2_RECOVERY_EC_HASH_FAILED); |
| return VBERROR_EC_REBOOT_TO_RO_REQUIRED; |
| } |
| if (hash_size != ec_hash_size) { |
| VBDEBUG(("EcUpdateImage() - " |
| "VbExEcHashImage() says size %d, not %d\n", |
| ec_hash_size, hash_size)); |
| request_recovery(ctx, VB2_RECOVERY_EC_HASH_SIZE); |
| return VBERROR_EC_REBOOT_TO_RO_REQUIRED; |
| } |
| VBDEBUG(("Updated EC-%s hash: ", rw_request ? "RW" : "RO")); |
| for (i = 0; i < ec_hash_size; i++) |
| VBDEBUG(("%02x",ec_hash[i])); |
| VBDEBUG(("\n")); |
| |
| if (vb2_safe_memcmp(ec_hash, hash, hash_size)){ |
| VBDEBUG(("EcUpdateImage() - " |
| "Failed to update EC-%s\n", rw_request ? |
| "RW" : "RO")); |
| request_recovery(ctx, VB2_RECOVERY_EC_UPDATE); |
| return VBERROR_EC_REBOOT_TO_RO_REQUIRED; |
| } |
| |
| return VBERROR_SUCCESS; |
| } |
| |
| VbError_t VbEcSoftwareSync(struct vb2_context *ctx, int devidx, |
| VbCommonParams *cparams) |
| { |
| VbSharedDataHeader *shared = |
| (VbSharedDataHeader *)cparams->shared_data_blob; |
| enum VbSelectFirmware_t select_rw = |
| shared->firmware_index ? VB_SELECT_FIRMWARE_B : |
| VB_SELECT_FIRMWARE_A; |
| enum VbSelectFirmware_t select_ro = VB_SELECT_FIRMWARE_READONLY; |
| int in_rw = 0; |
| int ro_try_count = 2; |
| int num_tries = 0; |
| uint32_t recovery_request; |
| int rv, updated_rw, updated_ro; |
| |
| VBDEBUG(("VbEcSoftwareSync(devidx=%d)\n", devidx)); |
| |
| /* Determine whether the EC is in RO or RW */ |
| rv = VbExEcRunningRW(devidx, &in_rw); |
| |
| if (shared->recovery_reason) { |
| /* Recovery mode; just verify the EC is in RO code */ |
| if (rv == VBERROR_SUCCESS && in_rw == 1) { |
| /* |
| * EC is definitely in RW firmware. We want it in |
| * read-only code, so preserve the current recovery |
| * reason and reboot. |
| * |
| * We don't reboot on error or unknown EC code, because |
| * we could end up in an endless reboot loop. If we |
| * had some way to track that we'd already rebooted for |
| * this reason, we could retry only once. |
| */ |
| VBDEBUG(("VbEcSoftwareSync() - " |
| "want recovery but got EC-RW\n")); |
| request_recovery(ctx, shared->recovery_reason); |
| return VBERROR_EC_REBOOT_TO_RO_REQUIRED; |
| } |
| |
| VBDEBUG(("VbEcSoftwareSync() in recovery; EC-RO\n")); |
| return VBERROR_SUCCESS; |
| } |
| |
| /* |
| * Not in recovery. If we couldn't determine where the EC was, |
| * reboot to recovery. |
| */ |
| if (rv != VBERROR_SUCCESS) { |
| VBDEBUG(("VbEcSoftwareSync() - " |
| "VbExEcRunningRW() returned %d\n", rv)); |
| request_recovery(ctx, VB2_RECOVERY_EC_UNKNOWN_IMAGE); |
| return VBERROR_EC_REBOOT_TO_RO_REQUIRED; |
| } |
| |
| /* If AP is read-only normal, EC should be in its RO code also. */ |
| if (shared->flags & VBSD_LF_USE_RO_NORMAL) { |
| /* If EC is in RW code, request reboot back to RO */ |
| if (in_rw == 1) { |
| VBDEBUG(("VbEcSoftwareSync() - " |
| "want RO-normal but got EC-RW\n")); |
| return VBERROR_EC_REBOOT_TO_RO_REQUIRED; |
| } |
| |
| /* Protect the RW flash and stay in EC-RO */ |
| rv = EcProtect(ctx, devidx, select_rw); |
| if (rv != VBERROR_SUCCESS) |
| return rv; |
| |
| rv = VbExEcDisableJump(devidx); |
| if (rv != VBERROR_SUCCESS) { |
| VBDEBUG(("VbEcSoftwareSync() - " |
| "VbExEcDisableJump() returned %d\n", rv)); |
| request_recovery(ctx, VB2_RECOVERY_EC_SOFTWARE_SYNC); |
| return VBERROR_EC_REBOOT_TO_RO_REQUIRED; |
| } |
| |
| VBDEBUG(("VbEcSoftwareSync() in RO-Normal; EC-RO\n")); |
| return VBERROR_SUCCESS; |
| } |
| |
| VBDEBUG(("VbEcSoftwareSync() check for RW update.\n")); |
| |
| /* Update the RW Image. */ |
| rv = EcUpdateImage(ctx, devidx, cparams, select_rw, &updated_rw, in_rw); |
| |
| if (rv != VBERROR_SUCCESS) { |
| VBDEBUG(("VbEcSoftwareSync() - " |
| "EcUpdateImage() returned %d\n", rv)); |
| return rv; |
| } |
| |
| /* Tell EC to jump to its RW image */ |
| if (!in_rw) { |
| VBDEBUG(("VbEcSoftwareSync() jumping to EC-RW\n")); |
| rv = VbExEcJumpToRW(devidx); |
| if (rv != VBERROR_SUCCESS) { |
| VBDEBUG(("VbEcSoftwareSync() - " |
| "VbExEcJumpToRW() returned %x\n", rv)); |
| |
| /* |
| * If the EC booted RO-normal and a previous AP boot |
| * has called VbExEcStayInRO(), we need to reboot the EC |
| * to unlock the ability to jump to the RW firmware. |
| * |
| * All other errors trigger recovery mode. |
| */ |
| if (rv != VBERROR_EC_REBOOT_TO_RO_REQUIRED) |
| request_recovery(ctx, VB2_RECOVERY_EC_JUMP_RW); |
| |
| return VBERROR_EC_REBOOT_TO_RO_REQUIRED; |
| } |
| } |
| |
| uint32_t try_ro_sync = vb2_nv_get(ctx, VB2_NV_TRY_RO_SYNC); |
| if (!devidx && try_ro_sync && |
| !(shared->flags & VBSD_BOOT_FIRMWARE_WP_ENABLED)) { |
| /* Reset RO Software Sync NV flag */ |
| vb2_nv_set(ctx, VB2_NV_TRY_RO_SYNC, 0); |
| |
| recovery_request = vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST); |
| |
| /* Update the RO Image. */ |
| while (num_tries < ro_try_count) { |
| VBDEBUG(("VbEcSoftwareSync() RO Software Sync\n")); |
| |
| /* Get expected EC-RO Image. */ |
| rv = EcUpdateImage(ctx, devidx, cparams, select_ro, |
| &updated_ro, in_rw); |
| if (rv == VBERROR_SUCCESS) { |
| /* |
| * If the RO update had failed, reset the |
| * recovery request. |
| */ |
| if (num_tries) |
| request_recovery(ctx, recovery_request); |
| break; |
| } else |
| VBDEBUG(("VbEcSoftwareSync() - " |
| "EcUpdateImage() returned %d\n", rv)); |
| |
| num_tries++; |
| } |
| } |
| if (rv != VBERROR_SUCCESS) |
| return rv; |
| |
| /* Protect RO flash */ |
| rv = EcProtect(ctx, devidx, select_ro); |
| if (rv != VBERROR_SUCCESS) |
| return rv; |
| |
| /* Protect RW flash */ |
| rv = EcProtect(ctx, devidx, select_rw); |
| if (rv != VBERROR_SUCCESS) |
| return rv; |
| |
| rv = VbExEcDisableJump(devidx); |
| if (rv != VBERROR_SUCCESS) { |
| VBDEBUG(("VbEcSoftwareSync() - " |
| "VbExEcDisableJump() returned %d\n", rv)); |
| request_recovery(ctx, VB2_RECOVERY_EC_SOFTWARE_SYNC); |
| return VBERROR_EC_REBOOT_TO_RO_REQUIRED; |
| } |
| |
| /* |
| * Reboot to unload VGA Option ROM if: |
| * - RW update was done |
| * - the system is NOT in developer mode |
| * - the system has slow EC update flag set |
| * - the VGA Option ROM was needed and loaded |
| */ |
| if (updated_rw && |
| !(shared->flags & VBSD_BOOT_DEV_SWITCH_ON) && |
| (shared->flags & VBSD_EC_SLOW_UPDATE) && |
| (shared->flags & VBSD_OPROM_MATTERS) && |
| (shared->flags & VBSD_OPROM_LOADED)) { |
| VBDEBUG(("VbEcSoftwareSync() - Reboot to " |
| "unload VGA Option ROM\n")); |
| vb2_nv_set(ctx, VB2_NV_OPROM_NEEDED, 0); |
| return VBERROR_VGA_OPROM_MISMATCH; |
| } |
| |
| |
| return rv; |
| } |