diff --git a/firmware/include/vboot_nvstorage.h b/firmware/include/vboot_nvstorage.h index 845a48a936..2b80cafcca 100644 --- a/firmware/include/vboot_nvstorage.h +++ b/firmware/include/vboot_nvstorage.h @@ -97,6 +97,10 @@ typedef enum VbNvParam { /* Recovery mode TPM initialization requires a system reboot. The system was * already in recovery mode for some other reason when this happened. */ #define VBNV_RECOVERY_RO_TPM_REBOOT 0x21 +/* Other EC software sync error */ +#define VBNV_RECOVERY_EC_SOFTWARE_SYNC 0x22 +/* Unable to determine active EC image */ +#define VBNV_RECOVERY_EC_UNKNOWN_IMAGE 0x23 /* Unspecified/unknown error in read-only firmware */ #define VBNV_RECOVERY_RO_UNSPECIFIED 0x3F /* User manually requested recovery by pressing a key at developer diff --git a/firmware/include/vboot_struct.h b/firmware/include/vboot_struct.h index d894e23d28..7000f6e86e 100644 --- a/firmware/include/vboot_struct.h +++ b/firmware/include/vboot_struct.h @@ -231,8 +231,10 @@ typedef struct VbKernelPreambleHeader { #define VBSD_BOOT_S3_RESUME 0x00000100 /* Read-only firmware supports the normal/developer code path */ #define VBSD_BOOT_RO_NORMAL_SUPPORT 0x00000200 -/* VbInit was told that the system has a virtual dev-switch */ +/* VbInit() was told that the system has a virtual dev-switch */ #define VBSD_HONOR_VIRT_DEV_SWITCH 0x00000400 +/* VbInit() was told the system supports EC software sync */ +#define VBSD_EC_SOFTWARE_SYNC 0x00000800 /* Result codes for VbSharedDataHeader.check_fw_a_result (and b_result) */ #define VBSD_LF_CHECK_NOT_DONE 0 diff --git a/firmware/lib/vboot_api_init.c b/firmware/lib/vboot_api_init.c index 229849e434..dbeb1e66ce 100644 --- a/firmware/lib/vboot_api_init.c +++ b/firmware/lib/vboot_api_init.c @@ -49,6 +49,7 @@ VbError_t VbInit(VbCommonParams* cparams, VbInitParams* iparams) { shared->timer_vb_init_enter = VbExGetTimer(); /* Copy some boot switch flags */ + /* TODO: in next refactor, just save in/out flags in VbSharedData */ shared->flags = 0; if (iparams->flags & VB_INIT_FLAG_REC_BUTTON_PRESSED) shared->flags |= VBSD_BOOT_REC_SWITCH_ON; @@ -58,6 +59,8 @@ VbError_t VbInit(VbCommonParams* cparams, VbInitParams* iparams) { shared->flags |= VBSD_BOOT_S3_RESUME; if (iparams->flags & VB_INIT_FLAG_RO_NORMAL_SUPPORT) shared->flags |= VBSD_BOOT_RO_NORMAL_SUPPORT; + if (iparams->flags & VB_INIT_FLAG_EC_SOFTWARE_SYNC) + shared->flags |= VBSD_EC_SOFTWARE_SYNC; is_s3_resume = (iparams->flags & VB_INIT_FLAG_S3_RESUME ? 1 : 0); diff --git a/firmware/lib/vboot_api_kernel.c b/firmware/lib/vboot_api_kernel.c index df9f57ceb9..a17c02435b 100644 --- a/firmware/lib/vboot_api_kernel.c +++ b/firmware/lib/vboot_api_kernel.c @@ -348,6 +348,103 @@ VbError_t VbBootRecovery(VbCommonParams* cparams, LoadKernelParams* p) { } +VbError_t VbEcSoftwareSync(VbSharedDataHeader *shared) { + int in_rw = 0; + int rv = VbExEcRunningRW(&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 + * preseve 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")); + VbSetRecoveryRequest(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() - VbEcSoftwareSync() returned %d\n", rv)); + VbSetRecoveryRequest(VBNV_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 = VbExEcProtectRW(); + if (rv != VBERROR_SUCCESS) { + VBDEBUG(("VbEcSoftwareSync() - VbExEcProtectRW() returned %d\n", rv)); + VbSetRecoveryRequest(VBNV_RECOVERY_EC_SOFTWARE_SYNC); + return VBERROR_EC_REBOOT_TO_RO_REQUIRED; + } + + rv = VbExEcStayInRO(); + if (rv != VBERROR_SUCCESS) { + VBDEBUG(("VbEcSoftwareSync() - VbExEcStayInRO() returned %d\n", rv)); + VbSetRecoveryRequest(VBNV_RECOVERY_EC_SOFTWARE_SYNC); + return VBERROR_EC_REBOOT_TO_RO_REQUIRED; + } + + VBDEBUG(("VbEcSoftwareSync() in RO-Normal; EC-RO\n")); + + /* TODO: If EC-RW wasn't protected when we started, then this boot was + * simply to verify the EC. Shut down instead of continuing. */ + + return VBERROR_SUCCESS; + } + + /* TODO: verify EC-RW hash vs. expected code */ + + if (in_rw) { + /* TODO: if hash doesn't verify, reboot EC so we can reflash it + * with the expected code. */ + VBDEBUG(("VbEcSoftwareSync() in RW; EC-RW\n")); + return VBERROR_SUCCESS; + + } else { + /* TODO: if hash doesn't verify, reflash it with expected code. */ + + /* Protect EC-RW flash */ + rv = VbExEcProtectRW(); + if (rv != VBERROR_SUCCESS) { + VBDEBUG(("VbEcSoftwareSync() - VbExEcProtectRW() returned %d\n", rv)); + VbSetRecoveryRequest(VBNV_RECOVERY_EC_SOFTWARE_SYNC); + return VBERROR_EC_REBOOT_TO_RO_REQUIRED; + } + + /* Tell EC to jump to its RW code */ + VBDEBUG(("VbEcSoftwareSync() jumping to EC-RW\n")); + rv = VbExEcJumpToRW(); + if (rv != VBERROR_SUCCESS) { + VBDEBUG(("VbEcSoftwareSync() - VbExEcJumpToRW() returned %d\n", rv)); + VbSetRecoveryRequest(VBNV_RECOVERY_EC_SOFTWARE_SYNC); + return VBERROR_EC_REBOOT_TO_RO_REQUIRED; + } + + /* TODO: If there was no wake event from the EC (such as power button or + * lid-open), shut down. The AP was powered on simply to verify the EC. */ + VBDEBUG(("VbEcSoftwareSync() in RW; done jumping to EC-RW\n")); + return VBERROR_SUCCESS; + } +} + + VbError_t VbSelectAndLoadKernel(VbCommonParams* cparams, VbSelectAndLoadKernelParams* kparams) { VbSharedDataHeader* shared = (VbSharedDataHeader*)cparams->shared_data_blob; @@ -368,6 +465,13 @@ VbError_t VbSelectAndLoadKernel(VbCommonParams* cparams, kparams->bootloader_size = 0; Memset(kparams->partition_guid, 0, sizeof(kparams->partition_guid)); + /* Do EC software sync if necessary */ + if (shared->flags & VBSD_EC_SOFTWARE_SYNC) { + retval = VbEcSoftwareSync(shared); + if (retval != VBERROR_SUCCESS) + goto VbSelectAndLoadKernel_exit; + } + /* Read the kernel version from the TPM. Ignore errors in recovery mode. */ tpm_status = RollbackKernelRead(&shared->kernel_version_tpm); if (0 != tpm_status) { diff --git a/firmware/lib/vboot_display.c b/firmware/lib/vboot_display.c index e48623b5fa..ffaa7071a2 100644 --- a/firmware/lib/vboot_display.c +++ b/firmware/lib/vboot_display.c @@ -443,6 +443,10 @@ static const char *RecoveryReasonString(uint8_t code) { return "Firmware problem outside of verified boot"; case VBNV_RECOVERY_RO_TPM_REBOOT: return "TPM requires a system reboot (should be transient)"; + case VBNV_RECOVERY_EC_SOFTWARE_SYNC: + return "EC software sync error"; + case VBNV_RECOVERY_EC_UNKNOWN_IMAGE: + return "Unable to determine active EC image"; case VBNV_RECOVERY_RO_UNSPECIFIED: return "Unspecified/unknown error in RO firmware"; case VBNV_RECOVERY_RW_DEV_SCREEN: