mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2025-11-24 02:05:01 +00:00
Chromebox ECs perform EFS: verifying firmware before the AP boots. This patch adds support for EC EFS. EC EFS uses two slots: one is active slot and one is used for update. AP is agnostic about which slot is active and which slot is for update. Instead, AP recognizes one slot as 'active' and the other as 'update' (or non active) slot. After update is successful, AP issues a cold reboot command to activate the new slot. BUG=b:65028930,b:65264494 BRANCH=none CQ-DEPEND=CL:616248 TEST=buildall. On Fizz, verify: 1. RW_B is old and updated by soft sync. RW_B is activated and executed after reboot. System continues to boot to OS. 2. RW_A is old and updated by soft sync. RW_A is activated and executed after reboot. System continues to boot to OS. Change-Id: I6ca7686eb28b0b548785cf2c02eca1b67018f469 Signed-off-by: Daisuke Nojiri <dnojiri@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/616346
523 lines
15 KiB
C
523 lines
15 KiB
C
/* 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 "2misc.h"
|
|
#include "2nvstorage.h"
|
|
|
|
#include "sysincludes.h"
|
|
#include "ec_sync.h"
|
|
#include "gbb_header.h"
|
|
#include "vboot_api.h"
|
|
#include "vboot_common.h"
|
|
#include "vboot_kernel.h"
|
|
|
|
#define VB2_SD_FLAG_ECSYNC_RW \
|
|
(VB2_SD_FLAG_ECSYNC_EC_RW | VB2_SD_FLAG_ECSYNC_PD_RW)
|
|
#define VB2_SD_FLAG_ECSYNC_ANY \
|
|
(VB2_SD_FLAG_ECSYNC_EC_RO | VB2_SD_FLAG_ECSYNC_RW)
|
|
#define VB2_SD_FLAG_ECSYNC_IN_RW \
|
|
(VB2_SD_FLAG_ECSYNC_EC_IN_RW | VB2_SD_FLAG_ECSYNC_PD_IN_RW)
|
|
|
|
#define IN_RW(devidx) \
|
|
((devidx) ? VB2_SD_FLAG_ECSYNC_PD_IN_RW : VB2_SD_FLAG_ECSYNC_EC_IN_RW)
|
|
|
|
#define WHICH_EC(devidx, select) \
|
|
((select) == VB_SELECT_FIRMWARE_READONLY ? VB2_SD_FLAG_ECSYNC_EC_RO : \
|
|
((devidx) ? VB2_SD_FLAG_ECSYNC_PD_RW : VB2_SD_FLAG_ECSYNC_EC_RW))
|
|
|
|
/* PD doesn't support RW A/B */
|
|
#define RW_AB(devidx) ((devidx) ? 0 : VBSD_EC_EFS)
|
|
|
|
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 protect_ec(struct vb2_context *ctx, int devidx,
|
|
enum VbSelectFirmware_t select)
|
|
{
|
|
int rv = VbExEcProtect(devidx, select);
|
|
|
|
if (rv == VBERROR_EC_REBOOT_TO_RO_REQUIRED) {
|
|
VB2_DEBUG("VbExEcProtect() needs reboot\n");
|
|
} else if (rv != VBERROR_SUCCESS) {
|
|
VB2_DEBUG("VbExEcProtect() returned %d\n", rv);
|
|
request_recovery(ctx, VB2_RECOVERY_EC_PROTECT);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* Print a hash to debug output
|
|
*
|
|
* @param hash Pointer to the hash
|
|
* @param hash_size Size of the hash in bytes
|
|
* @param desc Description of what's being hashed
|
|
*/
|
|
static void print_hash(const uint8_t *hash, uint32_t hash_size,
|
|
const char *desc)
|
|
{
|
|
int i;
|
|
|
|
VB2_DEBUG("%s hash: ", desc);
|
|
for (i = 0; i < hash_size; i++)
|
|
VB2_DEBUG_RAW("%02x", hash[i]);
|
|
VB2_DEBUG_RAW("\n");
|
|
}
|
|
|
|
static const char *image_name_to_string(enum VbSelectFirmware_t select)
|
|
{
|
|
switch (select) {
|
|
case VB_SELECT_FIRMWARE_READONLY:
|
|
return "RO";
|
|
case VB_SELECT_FIRMWARE_EC_ACTIVE:
|
|
return "RW(active)";
|
|
case VB_SELECT_FIRMWARE_EC_UPDATE:
|
|
return "RW(update)";
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the hash of the EC code matches the expected hash.
|
|
*
|
|
* @param ctx Vboot2 context
|
|
* @param devidx Index of EC device to check
|
|
* @param select Which firmware image to check
|
|
* @return VB2_SUCCESS, or non-zero error code.
|
|
*/
|
|
static int check_ec_hash(struct vb2_context *ctx, int devidx,
|
|
enum VbSelectFirmware_t select)
|
|
{
|
|
struct vb2_shared_data *sd = vb2_get_sd(ctx);
|
|
|
|
/* Get current EC hash. */
|
|
const uint8_t *ec_hash = NULL;
|
|
int ec_hash_size;
|
|
int rv = VbExEcHashImage(devidx, select, &ec_hash, &ec_hash_size);
|
|
if (rv) {
|
|
VB2_DEBUG("VbExEcHashImage() returned %d\n", rv);
|
|
request_recovery(ctx, VB2_RECOVERY_EC_HASH_FAILED);
|
|
return VB2_ERROR_EC_HASH_IMAGE;
|
|
}
|
|
print_hash(ec_hash, ec_hash_size, image_name_to_string(select));
|
|
|
|
/* Get expected EC hash. */
|
|
const uint8_t *hash = NULL;
|
|
int hash_size;
|
|
rv = VbExEcGetExpectedImageHash(devidx, select, &hash, &hash_size);
|
|
if (rv) {
|
|
VB2_DEBUG("VbExEcGetExpectedImageHash() returned %d\n", rv);
|
|
request_recovery(ctx, VB2_RECOVERY_EC_EXPECTED_HASH);
|
|
return VB2_ERROR_EC_HASH_EXPECTED;
|
|
}
|
|
if (ec_hash_size != hash_size) {
|
|
VB2_DEBUG("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 VB2_ERROR_EC_HASH_SIZE;
|
|
}
|
|
|
|
if (vb2_safe_memcmp(ec_hash, hash, hash_size)) {
|
|
print_hash(hash, hash_size, "Expected");
|
|
sd->flags |= WHICH_EC(devidx, select);
|
|
}
|
|
|
|
return VB2_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Update the specified EC and verify the update succeeded
|
|
*
|
|
* @param ctx Vboot2 context
|
|
* @param devidx Index of EC device to check
|
|
* @param select Which firmware image to check
|
|
* @return VBERROR_SUCCESS, or non-zero error code.
|
|
*/
|
|
static VbError_t update_ec(struct vb2_context *ctx, int devidx,
|
|
enum VbSelectFirmware_t select)
|
|
{
|
|
struct vb2_shared_data *sd = vb2_get_sd(ctx);
|
|
|
|
VB2_DEBUG("Updating %s...\n", image_name_to_string(select));
|
|
|
|
/* Get expected EC image */
|
|
const uint8_t *want = NULL;
|
|
int want_size;
|
|
int rv = VbExEcGetExpectedImage(devidx, select, &want, &want_size);
|
|
if (rv) {
|
|
VB2_DEBUG("VbExEcGetExpectedImage() returned %d\n", rv);
|
|
request_recovery(ctx, VB2_RECOVERY_EC_EXPECTED_IMAGE);
|
|
return rv;
|
|
}
|
|
VB2_DEBUG("image len = %d\n", want_size);
|
|
|
|
rv = VbExEcUpdateImage(devidx, select, want, want_size);
|
|
if (rv != VBERROR_SUCCESS) {
|
|
VB2_DEBUG("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 rv;
|
|
}
|
|
|
|
/* Verify the EC was updated properly */
|
|
sd->flags &= ~WHICH_EC(devidx, select);
|
|
if (check_ec_hash(ctx, devidx, select) != VB2_SUCCESS)
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
if (sd->flags & WHICH_EC(devidx, select)) {
|
|
VB2_DEBUG("Failed to update\n");
|
|
request_recovery(ctx, VB2_RECOVERY_EC_UPDATE);
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
|
|
return VBERROR_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Check if the EC has the correct image active.
|
|
*
|
|
* @param ctx Vboot2 context
|
|
* @param devidx Which device (EC=0, PD=1)
|
|
* @return VBERROR_SUCCESS, or non-zero if error.
|
|
*/
|
|
static VbError_t check_ec_active(struct vb2_context *ctx, int devidx)
|
|
{
|
|
struct vb2_shared_data *sd = vb2_get_sd(ctx);
|
|
|
|
/* Determine whether the EC is in RO or RW */
|
|
int in_rw = 0;
|
|
int rv = VbExEcRunningRW(devidx, &in_rw);
|
|
if (in_rw) {
|
|
sd->flags |= IN_RW(devidx);
|
|
}
|
|
|
|
if (sd->recovery_reason) {
|
|
/*
|
|
* Recovery mode; just verify the EC is in RO code. Don't do
|
|
* software sync, since we don't have a RW image.
|
|
*/
|
|
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.
|
|
*/
|
|
VB2_DEBUG("want recovery but got EC-RW\n");
|
|
request_recovery(ctx, sd->recovery_reason);
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
|
|
VB2_DEBUG("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) {
|
|
VB2_DEBUG("VbExEcRunningRW() returned %d\n", rv);
|
|
request_recovery(ctx, VB2_RECOVERY_EC_UNKNOWN_IMAGE);
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
|
|
return VBERROR_SUCCESS;
|
|
}
|
|
|
|
#define RO_RETRIES 2 /* Maximum times to retry flashing RO */
|
|
|
|
/**
|
|
* Sync, jump, and protect one EC device
|
|
*
|
|
* @param ctx Vboot2 context
|
|
* @param devidx Which device (EC=0, PD=1)
|
|
* @return VBERROR_SUCCESS, or non-zero if error.
|
|
*/
|
|
static VbError_t sync_one_ec(struct vb2_context *ctx, int devidx,
|
|
VbCommonParams *cparams)
|
|
{
|
|
VbSharedDataHeader *shared =
|
|
(VbSharedDataHeader *)cparams->shared_data_blob;
|
|
struct vb2_shared_data *sd = vb2_get_sd(ctx);
|
|
int is_rw_ab = shared->flags & RW_AB(devidx);
|
|
int rv;
|
|
|
|
const enum VbSelectFirmware_t select_rw = is_rw_ab ?
|
|
VB_SELECT_FIRMWARE_EC_UPDATE :
|
|
VB_SELECT_FIRMWARE_EC_ACTIVE;
|
|
VB2_DEBUG("devidx=%d select_rw=%d\n", devidx, select_rw);
|
|
|
|
/* Update the RW Image */
|
|
if (sd->flags & WHICH_EC(devidx, select_rw)) {
|
|
if (VB2_SUCCESS != update_ec(ctx, devidx, select_rw))
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
/* Updated successfully. Cold reboot to switch to the new RW.
|
|
* TODO: Switch slot and proceed if EC is still in RO. */
|
|
if (is_rw_ab) {
|
|
VB2_DEBUG("Rebooting to jump to new EC-RW\n");
|
|
return VBERROR_EC_REBOOT_TO_SWITCH_RW;
|
|
}
|
|
}
|
|
|
|
/* Tell EC to jump to its RW image */
|
|
if (!(sd->flags & IN_RW(devidx))) {
|
|
VB2_DEBUG("jumping to EC-RW\n");
|
|
rv = VbExEcJumpToRW(devidx);
|
|
if (rv != VBERROR_SUCCESS) {
|
|
VB2_DEBUG("VbExEcJumpToRW() returned %x\n", rv);
|
|
|
|
/*
|
|
* If 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;
|
|
}
|
|
}
|
|
|
|
/* Might need to update EC-RO (but not PD-RO) */
|
|
if (sd->flags & VB2_SD_FLAG_ECSYNC_EC_RO) {
|
|
VB2_DEBUG("RO Software Sync\n");
|
|
|
|
/* Reset RO Software Sync NV flag */
|
|
vb2_nv_set(ctx, VB2_NV_TRY_RO_SYNC, 0);
|
|
|
|
/*
|
|
* Get the current recovery request (if any). This gets
|
|
* overwritten by a failed try. If a later try succeeds, we'll
|
|
* need to restore this request (or the lack of a request), or
|
|
* else we'll end up in recovery mode even though RO software
|
|
* sync did eventually succeed.
|
|
*/
|
|
uint32_t recovery_request =
|
|
vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST);
|
|
|
|
/* Update the RO Image. */
|
|
int num_tries;
|
|
for (num_tries = 0; num_tries < RO_RETRIES; num_tries++) {
|
|
if (VB2_SUCCESS ==
|
|
update_ec(ctx, devidx, VB_SELECT_FIRMWARE_READONLY))
|
|
break;
|
|
}
|
|
if (num_tries == RO_RETRIES) {
|
|
/* Ran out of tries */
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
} else if (num_tries) {
|
|
/*
|
|
* Update succeeded after a failure, so we've polluted
|
|
* the recovery request. Restore it.
|
|
*/
|
|
request_recovery(ctx, recovery_request);
|
|
}
|
|
}
|
|
|
|
/* Protect RO flash */
|
|
rv = protect_ec(ctx, devidx, VB_SELECT_FIRMWARE_READONLY);
|
|
if (rv != VBERROR_SUCCESS)
|
|
return rv;
|
|
|
|
/* Protect RW flash */
|
|
rv = protect_ec(ctx, devidx, select_rw);
|
|
if (rv != VBERROR_SUCCESS)
|
|
return rv;
|
|
|
|
rv = VbExEcDisableJump(devidx);
|
|
if (rv != VBERROR_SUCCESS) {
|
|
VB2_DEBUG("VbExEcDisableJump() returned %d\n", rv);
|
|
request_recovery(ctx, VB2_RECOVERY_EC_SOFTWARE_SYNC);
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
VbError_t ec_sync_phase1(struct vb2_context *ctx, VbCommonParams *cparams)
|
|
{
|
|
VbSharedDataHeader *shared =
|
|
(VbSharedDataHeader *)cparams->shared_data_blob;
|
|
struct vb2_shared_data *sd = vb2_get_sd(ctx);
|
|
|
|
/* Reasons not to do sync at all */
|
|
if (!(shared->flags & VBSD_EC_SOFTWARE_SYNC))
|
|
return VBERROR_SUCCESS;
|
|
if (cparams->gbb->flags & GBB_FLAG_DISABLE_EC_SOFTWARE_SYNC)
|
|
return VBERROR_SUCCESS;
|
|
|
|
#ifdef PD_SYNC
|
|
const int do_pd_sync = !(cparams->gbb->flags &
|
|
GBB_FLAG_DISABLE_PD_SOFTWARE_SYNC);
|
|
#else
|
|
const int do_pd_sync = 0;
|
|
#endif
|
|
|
|
/* Make sure the EC is running the correct image */
|
|
if (check_ec_active(ctx, 0))
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
if (do_pd_sync && check_ec_active(ctx, 1))
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
|
|
/*
|
|
* In recovery mode; just verify the EC is in RO code. Don't do
|
|
* software sync, since we don't have a RW image.
|
|
*/
|
|
if (sd->recovery_reason)
|
|
return VBERROR_SUCCESS;
|
|
|
|
/* Check if we need to update RW. Failures trigger recovery mode. */
|
|
if (check_ec_hash(ctx, 0, VB_SELECT_FIRMWARE_EC_ACTIVE))
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
if (do_pd_sync && check_ec_hash(ctx, 1, VB_SELECT_FIRMWARE_EC_ACTIVE))
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
/*
|
|
* See if we need to update EC-RO (devidx=0).
|
|
*
|
|
* If we want to extend this in the future to update PD-RO, we'll use a
|
|
* different NV flag so we can track EC-RO and PD-RO updates
|
|
* separately.
|
|
*/
|
|
if (vb2_nv_get(ctx, VB2_NV_TRY_RO_SYNC) &&
|
|
!(shared->flags & VBSD_BOOT_FIRMWARE_WP_ENABLED) &&
|
|
check_ec_hash(ctx, 0, VB_SELECT_FIRMWARE_READONLY)) {
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
|
|
/*
|
|
* If we're in RW, we need to reboot back to RO because RW can't be
|
|
* updated while we're running it.
|
|
*
|
|
* If EC supports RW-A/B slots, we can proceed but we need
|
|
* to jump to the new RW version later.
|
|
*/
|
|
if ((sd->flags & VB2_SD_FLAG_ECSYNC_RW) &&
|
|
(sd->flags & VB2_SD_FLAG_ECSYNC_IN_RW) &&
|
|
!(shared->flags & VBSD_EC_EFS)) {
|
|
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
|
|
}
|
|
|
|
return VBERROR_SUCCESS;
|
|
}
|
|
|
|
int ec_will_update_slowly(struct vb2_context *ctx, VbCommonParams *cparams)
|
|
{
|
|
VbSharedDataHeader *shared =
|
|
(VbSharedDataHeader *)cparams->shared_data_blob;
|
|
struct vb2_shared_data *sd = vb2_get_sd(ctx);
|
|
|
|
return ((sd->flags & VB2_SD_FLAG_ECSYNC_ANY) &&
|
|
(shared->flags & VBSD_EC_SLOW_UPDATE));
|
|
}
|
|
|
|
/**
|
|
* determine if we can update the EC
|
|
*
|
|
* @param ctx Vboot2 context
|
|
* @param cparams Vboot common params
|
|
* @return boolean (true iff we can update the EC)
|
|
*/
|
|
|
|
static int ec_sync_allowed(struct vb2_context *ctx, VbCommonParams *cparams)
|
|
{
|
|
VbSharedDataHeader *shared =
|
|
(VbSharedDataHeader *)cparams->shared_data_blob;
|
|
struct vb2_shared_data *sd = vb2_get_sd(ctx);
|
|
|
|
/* Reasons not to do sync at all */
|
|
if (!(shared->flags & VBSD_EC_SOFTWARE_SYNC))
|
|
return 0;
|
|
if (cparams->gbb->flags & GBB_FLAG_DISABLE_EC_SOFTWARE_SYNC)
|
|
return 0;
|
|
if (sd->recovery_reason)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
VbError_t ec_sync_check_aux_fw(struct vb2_context *ctx,
|
|
VbCommonParams *cparams,
|
|
VbAuxFwUpdateSeverity_t *severity)
|
|
{
|
|
/* If we're not updating the EC, skip aux fw syncs as well */
|
|
if (!ec_sync_allowed(ctx, cparams) ||
|
|
(cparams->gbb->flags & GBB_FLAG_DISABLE_PD_SOFTWARE_SYNC)) {
|
|
*severity = VB_AUX_FW_NO_UPDATE;
|
|
return VBERROR_SUCCESS;
|
|
}
|
|
return VbExCheckAuxFw(severity);
|
|
}
|
|
|
|
VbError_t ec_sync_phase2(struct vb2_context *ctx, VbCommonParams *cparams)
|
|
{
|
|
if (!ec_sync_allowed(ctx, cparams))
|
|
return VBERROR_SUCCESS;
|
|
|
|
/* Handle updates and jumps for EC */
|
|
VbError_t retval = sync_one_ec(ctx, 0, cparams);
|
|
if (retval != VBERROR_SUCCESS)
|
|
return retval;
|
|
|
|
#ifdef PD_SYNC
|
|
/* Handle updates and jumps for PD */
|
|
if (!(cparams->gbb->flags & GBB_FLAG_DISABLE_PD_SOFTWARE_SYNC)) {
|
|
retval = sync_one_ec(ctx, 1, cparams);
|
|
if (retval != VBERROR_SUCCESS)
|
|
return retval;
|
|
}
|
|
#endif
|
|
|
|
return VBERROR_SUCCESS;
|
|
}
|
|
|
|
VbError_t ec_sync_phase3(struct vb2_context *ctx, VbCommonParams *cparams)
|
|
{
|
|
VbSharedDataHeader *shared =
|
|
(VbSharedDataHeader *)cparams->shared_data_blob;
|
|
|
|
/* EC verification (and possibly updating / jumping) is done */
|
|
VbError_t rv = VbExEcVbootDone(!!shared->recovery_reason);
|
|
if (rv)
|
|
return rv;
|
|
|
|
/* Check if we need to cut-off battery. This must be done after EC
|
|
* firmware updating and before kernel started. */
|
|
if (vb2_nv_get(ctx, VB2_NV_BATTERY_CUTOFF_REQUEST)) {
|
|
VB2_DEBUG("Request to cut-off battery\n");
|
|
vb2_nv_set(ctx, VB2_NV_BATTERY_CUTOFF_REQUEST, 0);
|
|
VbExEcBatteryCutOff();
|
|
return VBERROR_SHUTDOWN_REQUESTED;
|
|
}
|
|
|
|
return VBERROR_SUCCESS;
|
|
}
|