Merge commit 'c31013768eb4ed052c54df50c398f70f2d7ef36d' into 14542.0.0

BUG=b/222349736
TEST=local BE run
RELEASE_NOTE=None

Signed-off-by: Rayan Dasoriya <dasoriya@google.com>
Change-Id: Ie0b27e4c165dec7db1bcca809d14e859894babe1
diff --git a/.gitignore b/.gitignore
index 6ac51f7..9edc243 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
 /build
 /build-au
 /build-main
+Cargo.lock
 ID
 scripts/newbitmaps/default_source/*.bmp
 scripts/newbitmaps/images/out_*
@@ -8,3 +9,5 @@
 scripts/newbitmaps/strings/font
 scripts/newbitmaps/strings/*.png
 scripts/newbitmaps/strings/localized_text/*/*.png
+target
+.idea
diff --git a/Makefile b/Makefile
index 82d6a44..6340fb2 100644
--- a/Makefile
+++ b/Makefile
@@ -56,6 +56,7 @@
 #  US_DIR = shared data directory (for static content like devkeys)
 #  DF_DIR = utility defaults directory
 #  VB_DIR = vboot binary directory for dev-mode-only scripts
+#  DUT_TEST_DIR = vboot dut tests binary directory
 UB_DIR=${DESTDIR}/usr/bin
 UL_DIR=${DESTDIR}/usr/${LIBDIR}
 ULP_DIR=${UL_DIR}/pkgconfig
@@ -63,6 +64,7 @@
 US_DIR=${DESTDIR}/usr/share/vboot
 DF_DIR=${DESTDIR}/etc/default
 VB_DIR=${US_DIR}/bin
+DUT_TEST_DIR=${US_DIR}/tests
 
 # Where to install the (exportable) executables for testing?
 TEST_INSTALL_DIR = ${BUILD}/install_for_test
@@ -71,12 +73,12 @@
 SDK_BUILD ?=
 
 # Verbose? Use V=1
-ifeq (${V},)
+ifeq ($(filter-out 0,${V}),)
 Q := @
 endif
 
 # Quiet? Use QUIET=1
-ifeq (${QUIET},)
+ifeq ($(filter-out 0,${QUIET}),)
 PRINTF := printf
 else
 PRINTF := :
@@ -123,7 +125,7 @@
 
 # Provide default CC and CFLAGS for firmware builds; if you have any -D flags,
 # please add them after this point (e.g., -DVBOOT_DEBUG).
-DEBUG_FLAGS := $(if ${DEBUG},-g -Og,-g -Os)
+DEBUG_FLAGS := $(if $(filter-out 0,${DEBUG}),-g -Og,-g -Os)
 WERROR := -Werror
 FIRMWARE_FLAGS := -nostdinc -ffreestanding -fno-builtin -fno-stack-protector
 COMMON_FLAGS := -pipe ${WERROR} -Wall -Wstrict-prototypes -Wtype-limits \
@@ -171,35 +173,28 @@
 # Needs -Wl because LD is actually set to CC by default.
 LDFLAGS += -Wl,--gc-sections
 
-ifneq (${DEBUG}$(filter-out 0,${TEST_PRINT}),)
+ifneq ($(filter-out 0,${DEBUG})$(filter-out 0,${TEST_PRINT}),)
 CFLAGS += -DVBOOT_DEBUG
 endif
 
-ifeq (${DISABLE_NDEBUG},)
+ifeq ($(filter-out 0,${DISABLE_NDEBUG}),)
 CFLAGS += -DNDEBUG
 endif
 
-ifneq (${FORCE_LOGGING_ON},)
+ifneq ($(filter-out 0,${FORCE_LOGGING_ON}),)
 CFLAGS += -DFORCE_LOGGING_ON=${FORCE_LOGGING_ON}
 endif
 
-ifneq (${TPM2_MODE},)
+ifneq ($(filter-out 0,${TPM2_MODE}),)
 CFLAGS += -DTPM2_MODE
 endif
 
 # Support devices with GPT in SPI-NOR (for nand device)
 # TODO(b:184812319): Consider removing this code if nobody uses it.
-ifneq (${GPT_SPI_NOR},)
+ifneq ($(filter-out 0,${GPT_SPI_NOR}),)
 CFLAGS += -DGPT_SPI_NOR
 endif
 
-# Enable boot from external disk when switching to dev mode
-ifneq ($(filter-out 0,${BOOT_EXTERNAL_ON_DEV}),)
-CFLAGS += -DBOOT_EXTERNAL_ON_DEV=1
-else
-CFLAGS += -DBOOT_EXTERNAL_ON_DEV=0
-endif
-
 # Enable EC early firmware selection.
 ifneq ($(filter-out 0,${EC_EFS}),)
 CFLAGS += -DEC_EFS=1
@@ -208,7 +203,7 @@
 endif
 
 # Some tests need to be disabled when using mocked_secdata_tpm.
-ifneq (${MOCK_TPM},)
+ifneq ($(filter-out 0,${MOCK_TPM}),)
 CFLAGS += -DMOCK_TPM
 endif
 
@@ -219,29 +214,6 @@
 CFLAGS += -DTPM2_SIMULATOR=0
 endif
 
-# VTPM_PROXY indicates whether the TPM driver simulator feature
-# is enable or not.
-# This flag only takes effect when TPM2_SIMULATOR is enabled.
-ifneq ($(filter-out 0,${VTPM_PROXY}),)
-CFLAGS += -DVTPM_PROXY=1
-else
-CFLAGS += -DVTPM_PROXY=0
-endif
-
-# DETACHABLE indicates whether the device is a detachable or not.
-ifneq ($(filter-out 0,${DETACHABLE}),)
-CFLAGS += -DDETACHABLE=1
-else
-CFLAGS += -DDETACHABLE=0
-endif
-
-# Confirm physical presence using keyboard
-ifneq ($(filter-out 0,${PHYSICAL_PRESENCE_KEYBOARD}),)
-CFLAGS += -DPHYSICAL_PRESENCE_KEYBOARD=1
-else
-CFLAGS += -DPHYSICAL_PRESENCE_KEYBOARD=0
-endif
-
 # NOTE: We don't use these files but they are useful for other packages to
 # query about required compiling/linking flags.
 PC_IN_FILES = vboot_host.pc.in
@@ -264,7 +236,7 @@
 CFLAGS += $(cflags_use_64bits)
 
 # Code coverage
-ifneq (${COV},)
+ifneq ($(filter-out 0,${COV}),)
   COV_FLAGS = -O0 --coverage -DCOVERAGE
   CFLAGS += ${COV_FLAGS}
   LDFLAGS += ${COV_FLAGS}
@@ -286,7 +258,7 @@
 PKG_CONFIG ?= pkg-config
 
 # Static?
-ifneq (${STATIC},)
+ifneq ($(filter-out 0,${STATIC}),)
 LDFLAGS += -static
 PKG_CONFIG += --static
 endif
@@ -298,11 +270,17 @@
 # Optional Libraries
 LIBZIP_VERSION := $(shell ${PKG_CONFIG} --modversion libzip 2>/dev/null)
 HAVE_LIBZIP := $(if ${LIBZIP_VERSION},1)
-ifneq (${HAVE_LIBZIP},)
+ifneq ($(filter-out 0,${HAVE_LIBZIP}),)
   CFLAGS += -DHAVE_LIBZIP $(shell ${PKG_CONFIG} --cflags libzip)
   LIBZIP_LIBS := $(shell ${PKG_CONFIG} --libs libzip)
 endif
 
+HAVE_CROSID := $(shell ${PKG_CONFIG} --exists crosid && echo 1)
+ifeq ($(HAVE_CROSID),1)
+  CFLAGS += -DHAVE_CROSID $(shell ${PKG_CONFIG} --cflags crosid)
+  CROSID_LIBS := $(shell ${PKG_CONFIG} --libs crosid)
+endif
+
 # Determine QEMU architecture needed, if any
 ifeq (${ARCH},${HOST_ARCH})
   # Same architecture; no need for QEMU
@@ -344,9 +322,9 @@
 # Default target.
 .PHONY: all
 all: fwlib futil utillib hostlib cgpt tlcl \
-	$(if ${SDK_BUILD},utils_sdk,utils_board) \
+	$(if ${SDK_BUILD},${UTIL_FILES_SDK},${UTIL_FILES_BOARD}) \
 	$(if $(filter x86_64,${ARCH}),$(if $(filter clang,${CC}),fuzzers)) \
-	$(if ${COV},coverage)
+	$(if $(filter-out 0,${COV}),coverage)
 
 ##############################################################################
 # Now we need to describe everything we might want or need to build
@@ -416,7 +394,7 @@
 	firmware/lib20/kernel.c
 
 # TPM lightweight command library
-ifeq (${TPM2_MODE},)
+ifeq ($(filter-out 0,${TPM2_MODE}),)
 TLCL_SRCS = \
 	firmware/lib/tpm_lite/tlcl.c
 else
@@ -427,7 +405,7 @@
 endif
 
 # Support real TPM unless MOCK_TPM is set
-ifneq (${MOCK_TPM},)
+ifneq ($(filter-out 0,${MOCK_TPM}),)
 FWLIB_SRCS += \
 	firmware/lib/tpm_lite/mocked_tlcl.c
 endif
@@ -455,6 +433,19 @@
 TLCL_OBJS = ${TLCL_SRCS:%.c=${BUILD}/%.o}
 ALL_OBJS += ${FWLIB_OBJS} ${TLCL_OBJS}
 
+# Maintain behaviour of default on.
+USE_FLASHROM ?= 1
+
+ifneq ($(filter-out 0,${USE_FLASHROM}),)
+$(info building with libflashrom support)
+FLASHROM_LIBS := $(shell ${PKG_CONFIG} --libs flashrom)
+COMMONLIB_SRCS = \
+	host/lib/flashrom.c \
+	host/lib/flashrom_drv.c \
+	host/lib/subprocess.c
+CFLAGS += -DUSE_FLASHROM
+endif
+
 # Intermediate library for the vboot_reference utilities to link against.
 UTILLIB = ${BUILD}/libvboot_util.a
 
@@ -473,7 +464,7 @@
 	host/lib/crossystem.c \
 	host/lib/crypto.c \
 	host/lib/file_keys.c \
-	host/lib/flashrom.c \
+	$(COMMONLIB_SRCS) \
 	host/lib/fmap.c \
 	host/lib/host_common.c \
 	host/lib/host_key2.c \
@@ -482,7 +473,6 @@
 	host/lib/host_signature.c \
 	host/lib/host_signature2.c \
 	host/lib/signature_digest.c \
-	host/lib/subprocess.c \
 	host/lib/util_misc.c \
 	host/lib21/host_common.c \
 	host/lib21/host_key.c \
@@ -533,14 +523,13 @@
 	host/lib/crossystem.c \
 	host/lib/crypto.c \
 	host/lib/extract_vmlinuz.c \
-	host/lib/flashrom.c \
+	$(COMMONLIB_SRCS) \
 	host/lib/fmap.c \
 	host/lib/host_misc.c \
-	host/lib/subprocess.c \
 	host/lib21/host_misc.c \
 	${TLCL_SRCS}
 
-ifneq (${GPT_SPI_NOR},)
+ifneq ($(filter-out 0,${GPT_SPI_NOR}),)
 HOSTLIB_SRCS += cgpt/cgpt_nor.c
 endif
 
@@ -574,7 +563,7 @@
 	cgpt/cmd_repair.c \
 	cgpt/cmd_show.c
 
-ifneq (${GPT_SPI_NOR},)
+ifneq ($(filter-out 0,${GPT_SPI_NOR}),)
 CGPT_SRCS += cgpt/cgpt_nor.c
 endif
 
@@ -596,27 +585,32 @@
 UTIL_DEFAULTS = ${BUILD}/default/vboot_reference
 
 # Scripts to install directly (not compiled)
-UTIL_SCRIPTS_SDK = \
+UTIL_SCRIPT_NAMES_SDK = \
 	utility/dev_make_keypair \
 	utility/vbutil_what_keys
-UTIL_SCRIPTS_BOARD = \
+UTIL_SCRIPT_NAMES_BOARD = \
 	utility/chromeos-tpm-recovery \
 	utility/dev_debug_vboot \
 	utility/enable_dev_usb_boot \
 	utility/tpm-nvsize
 
-UTIL_NAMES_SDK = \
+UTIL_BIN_NAMES_SDK = \
+	utility/dumpRSAPublicKey \
 	utility/load_kernel_test \
 	utility/pad_digest_utility \
 	utility/signature_digest_utility \
 	utility/verify_data
-UTIL_NAMES_BOARD = \
+UTIL_BIN_NAMES_BOARD = \
 	utility/crossystem \
 	utility/dumpRSAPublicKey \
 	utility/tpmc
 
-UTIL_BINS_SDK = $(addprefix ${BUILD}/,${UTIL_NAMES_SDK})
-UTIL_BINS_BOARD = $(addprefix ${BUILD}/,${UTIL_NAMES_BOARD})
+UTIL_SCRIPTS_SDK = $(addprefix ${BUILD}/,${UTIL_SCRIPT_NAMES_SDK})
+UTIL_SCRIPTS_BOARD = $(addprefix ${BUILD}/,${UTIL_SCRIPT_NAMES_BOARD})
+UTIL_BINS_SDK = $(addprefix ${BUILD}/,${UTIL_BIN_NAMES_SDK})
+UTIL_BINS_BOARD = $(addprefix ${BUILD}/,${UTIL_BIN_NAMES_BOARD})
+UTIL_FILES_SDK = ${UTIL_BINS_SDK} ${UTIL_SCRIPTS_SDK}
+UTIL_FILES_BOARD = ${UTIL_BINS_BOARD} ${UTIL_SCRIPTS_BOARD}
 ALL_OBJS += $(addsuffix .o,${UTIL_BINS_SDK})
 ALL_OBJS += $(addsuffix .o,${UTIL_BINS_BOARD})
 
@@ -654,6 +648,7 @@
 	futility/cmd_dump_fmap.c \
 	futility/cmd_dump_kernel_config.c \
 	futility/cmd_gbb_utility.c \
+	futility/cmd_gscvd.c \
 	futility/cmd_load_fmap.c \
 	futility/cmd_pcr.c \
 	futility/cmd_show.c \
@@ -663,20 +658,24 @@
 	futility/cmd_vbutil_firmware.c \
 	futility/cmd_vbutil_firmware.c \
 	futility/cmd_vbutil_kernel.c \
+	futility/cmd_vbutil_key.c \
 	futility/cmd_vbutil_keyblock.c \
-	futility/cmd_vbutil_key.c \
-	futility/cmd_vbutil_key.c \
 	futility/file_type_bios.c \
 	futility/file_type.c \
 	futility/file_type_rwsig.c \
 	futility/file_type_usbpd1.c \
 	futility/misc.c \
-	futility/updater.c \
+	futility/vb1_helper.c \
+	futility/vb2_helper.c
+
+ifneq ($(filter-out 0,${USE_FLASHROM}),)
+FUTIL_SRCS += host/lib/flashrom_drv.c \
+	futility/flashrom_wp_drv.c \
 	futility/updater_archive.c \
 	futility/updater_quirks.c \
 	futility/updater_utils.c \
-	futility/vb1_helper.c \
-	futility/vb2_helper.c
+	futility/updater.c
+endif
 
 # List of commands built in futility.
 FUTIL_CMD_LIST = ${BUILD}/gen/futility_cmds.c
@@ -713,7 +712,7 @@
 	tests/vboot_kernel2_tests \
 	tests/verify_kernel
 
-ifeq (${MOCK_TPM}${TPM2_MODE},)
+ifeq ($(filter-out 0,${MOCK_TPM})$(filter-out 0,${TPM2_MODE}),)
 # tlcl_tests only works when MOCK_TPM is disabled
 # TODO(apronin): tests for TPM2 case?
 TEST_NAMES += \
@@ -768,13 +767,15 @@
 
 TEST_NAMES += ${TEST2X_NAMES} ${TEST20_NAMES} ${TEST21_NAMES}
 
-# This is build-only test since we can't run this without
-# sha-ni extension on x86. To run this test, you have to
-# manually copy executable into compatible machine and run it.
-TEST_NAMES += tests/vb2_sha256_x86_tests
+# Tests which should be run on dut
+ifeq (${ARCH}, x86_64)
+DUT_TEST_NAMES += tests/vb2_sha256_x86_tests
+endif
+
+TEST_NAMES += ${DUT_TEST_NAMES}
 
 # And a few more...
-ifeq (${TPM2_MODE},)
+ifeq ($(filter-out 0,${TPM2_MODE}),)
 TLCL_TEST_NAMES = \
 	tests/tpm_lite/tpmtest_earlyextend \
 	tests/tpm_lite/tpmtest_earlynvram \
@@ -832,7 +833,9 @@
 
 .PHONY: install
 install: cgpt_install signing_install futil_install pc_files_install \
-	lib_install $(if ${SDK_BUILD},utils_install_sdk,utils_install_board)
+	lib_install $(if ${SDK_BUILD},,util_install_defaults) \
+	$(foreach f,$(if ${SDK_BUILD},${UTIL_FILES_SDK},${UTIL_FILES_BOARD}), \
+		util_install-$(patsubst ${BUILD}/%,%,${f}))
 
 .PHONY: install_dev
 install_dev: devkeys_install headers_install
@@ -842,8 +845,9 @@
 
 .PHONY: install_for_test
 install_for_test: override DESTDIR = ${TEST_INSTALL_DIR}
-install_for_test: test_setup
-install_for_test: install utils_install_sdk utils_install_board
+install_for_test: test_setup install \
+	$(foreach f,${UTIL_FILES_SDK} ${UTIL_FILES_BOARD}, \
+		util_install-$(patsubst ${BUILD}/%,%,${f}))
 
 # Don't delete intermediate object files
 .SECONDARY:
@@ -963,7 +967,7 @@
 	${Q}${LD} -o ${CGPT_WRAPPER} ${LDFLAGS} $^ ${LDLIBS}
 
 .PHONY: cgpt
-cgpt: ${CGPT} $(if ${GPT_SPI_NOR},cgpt_wrapper)
+cgpt: ${CGPT} $(if $(filter-out 0,${GPT_SPI_NOR}),cgpt_wrapper)
 
 # on FreeBSD: install misc/e2fsprogs-libuuid from ports,
 # or e2fsprogs-libuuid from its binary package system.
@@ -1001,27 +1005,23 @@
 ${UTIL_BINS_BOARD}: ${UTILLIB}
 ${UTIL_BINS_BOARD}: LIBS = ${UTILLIB}
 
-.PHONY: utils_sdk
-utils_sdk: ${UTIL_BINS_SDK} ${UTIL_SCRIPTS_SDK}
-	${Q}cp -f ${UTIL_SCRIPTS_SDK} ${BUILD}/utility
-	${Q}chmod a+rx $(patsubst %,${BUILD}/%,${UTIL_SCRIPTS_SDK})
+${UTIL_SCRIPTS_SDK} ${UTIL_SCRIPTS_BOARD}: ${BUILD}/%: %
+	${Q}cp -f $< $@
+	${Q}chmod a+rx $@
 
-.PHONY: utils_board
-utils_board: ${UTIL_BINS_BOARD} ${UTIL_SCRIPTS_BOARD}
-	${Q}cp -f ${UTIL_SCRIPTS_BOARD} ${BUILD}/utility
-	${Q}chmod a+rx $(patsubst %,${BUILD}/%,${UTIL_SCRIPTS_BOARD})
+define UTIL_INSTALL_template
+.PHONY: util_install-$(1)
+util_install-$(1): $$(addprefix $${BUILD}/,$(1))
+	@${PRINTF} "    INSTALL       $(1)\n"
+	${Q}mkdir -p $${UB_DIR}
+	${Q}${INSTALL} -t $${UB_DIR} $$<
+endef
 
-.PHONY: utils_install_sdk
-utils_install_sdk: utils_sdk
-	@${PRINTF} "    INSTALL       UTILS\n"
-	${Q}mkdir -p ${UB_DIR}
-	${Q}${INSTALL} -t ${UB_DIR} ${UTIL_BINS_SDK} ${UTIL_SCRIPTS_SDK}
+$(foreach f, $(sort ${UTIL_FILES_SDK} ${UTIL_FILES_BOARD}), \
+	$(eval $(call UTIL_INSTALL_template,$(patsubst ${BUILD}/%,%,${f}))))
 
-.PHONY: utils_install_board
-utils_install_board: utils_board ${UTIL_DEFAULTS}
-	@${PRINTF} "    INSTALL       UTILS\n"
-	${Q}mkdir -p ${UB_DIR}
-	${Q}${INSTALL} -t ${UB_DIR} ${UTIL_BINS_BOARD} ${UTIL_SCRIPTS_BOARD}
+.PHONY: util_install_defaults
+util_install_defaults: ${UTIL_DEFAULTS}
 	${Q}mkdir -p ${DF_DIR}
 	${Q}${INSTALL} -t ${DF_DIR} -m 'u=rw,go=r,a-s' ${UTIL_DEFAULTS}
 
@@ -1040,7 +1040,7 @@
 futil: ${FUTIL_BIN}
 
 # FUTIL_LIBS is shared by FUTIL_BIN and TEST_FUTIL_BINS.
-FUTIL_LIBS = ${CRYPTO_LIBS} ${LIBZIP_LIBS}
+FUTIL_LIBS = ${CROSID_LIBS} ${CRYPTO_LIBS} ${LIBZIP_LIBS} ${FLASHROM_LIBS}
 
 ${FUTIL_BIN}: LDLIBS += ${FUTIL_LIBS}
 ${FUTIL_BIN}: ${FUTIL_OBJS} ${UTILLIB} ${FWLIB}
@@ -1094,17 +1094,28 @@
 ${TEST20_BINS}: LIBS += ${FWLIB}
 ${TEST20_BINS}: LDLIBS += ${CRYPTO_LIBS}
 
-# Special build for sha256_x86 test
-X86_SHA256_TEST = ${BUILD_RUN}/tests/vb2_sha256_x86_tests
-${X86_SHA256_TEST}: ${BUILD}/firmware/2lib/2sha256_x86.o
-${X86_SHA256_TEST}: LIBS += ${BUILD}/firmware/2lib/2sha256_x86.o
-
 ${TESTLIB}: ${TESTLIB_OBJS}
 	@${PRINTF} "    RM            $(subst ${BUILD}/,,$@)\n"
 	${Q}rm -f $@
 	@${PRINTF} "    AR            $(subst ${BUILD}/,,$@)\n"
 	${Q}ar qc $@ $^
 
+DUT_TEST_BINS = $(addprefix ${BUILD}/,${DUT_TEST_NAMES})
+
+# Special build for sha256_x86 test
+${BUILD}/tests/vb2_sha256_x86_tests: \
+	${BUILD}/firmware/2lib/2sha256_x86.o
+${BUILD}/tests/vb2_sha256_x86_tests: \
+	LIBS += ${BUILD}/firmware/2lib/2sha256_x86.o
+
+.PHONY: install_dut_test
+install_dut_test: ${DUT_TEST_BINS}
+ifneq ($(strip ${DUT_TEST_BINS}),)
+	@${PRINTF} "    INSTALL       DUT TESTS\n"
+	${Q}mkdir -p ${DUT_TEST_DIR}
+	${Q}${INSTALL} -t ${DUT_TEST_DIR} $^
+endif
+
 # ----------------------------------------------------------------------------
 # Fuzzers
 
@@ -1171,7 +1182,7 @@
 ${BUILD}/tests/%: LDLIBS += -lrt -luuid
 ${BUILD}/tests/%: LIBS += ${TESTLIB}
 
-ifeq (${TPM2_MODE},)
+ifeq ($(filter-out 0,${TPM2_MODE}),)
 # TODO(apronin): tests for TPM2 case?
 TLCL_TEST_BINS = $(addprefix ${BUILD}/,${TLCL_TEST_NAMES})
 ${TLCL_TEST_BINS}: OBJS += ${BUILD}/tests/tpm_lite/tlcl_tests.o
@@ -1208,7 +1219,7 @@
 # Targets that exist just to run tests
 
 .PHONY: test_setup
-test_setup:: cgpt utils_sdk utils_board futil tests
+test_setup:: cgpt ${UTIL_FILES_SDK} ${UTIL_FILES_BOARD} futil tests
 
 # Qemu setup for cross-compiled tests.  Need to copy qemu binary into the
 # sysroot.
@@ -1261,7 +1272,7 @@
 runmisctests: install_for_test
 	${RUNTEST} ${BUILD_RUN}/tests/gpt_misc_tests
 	${RUNTEST} ${BUILD_RUN}/tests/subprocess_tests
-ifeq (${MOCK_TPM}${TPM2_MODE},)
+ifeq ($(filter-out 0,${MOCK_TPM})$(filter-out 0,${TPM2_MODE}),)
 # tlcl_tests only works when MOCK_TPM is disabled
 	${RUNTEST} ${BUILD_RUN}/tests/tlcl_tests
 endif
@@ -1345,7 +1356,7 @@
 		-o ${COV_INFO}.firmware
 
 .PHONY: coverage
-ifeq (${COV},)
+ifeq ($(filter-out 0,${COV}),)
 coverage:
 	$(error Build coverage like this: make clean && COV=1 make coverage)
 else
diff --git a/OWNERS b/OWNERS
index 74ba71a..21ecb22 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,5 +1,5 @@
 jwerner@chromium.org
 yupingso@chromium.org
-furquan@chromium.org
+twawrzynczak@chromium.org
 hungte@chromium.org
 *
diff --git a/PRESUBMIT.cfg b/PRESUBMIT.cfg
index 54d538d..9f66545 100644
--- a/PRESUBMIT.cfg
+++ b/PRESUBMIT.cfg
@@ -1,5 +1,6 @@
 [Hook Overrides]
 branch_check: true
+cargo_clippy_check: true
 checkpatch_check: true
 long_line_check: false
 signoff_check: true
@@ -10,3 +11,6 @@
 
 [Hook Overrides Options]
 cros_license_check: --exclude_regex=^\.checkpatch\.conf$
+
+cargo_clippy_check:
+  --project=rust/vboot_reference-sys
diff --git a/README b/README
index 04ab0c9..1787d4d 100644
--- a/README
+++ b/README
@@ -48,6 +48,9 @@
   Tools and scripts used to generate and use new signing keypairs. These are
   typically used only on a secure machine.
 
+rust/
+
+  Rust bindings for vboot_reference. See rust/README.md for more details.
 
 --------------------
 Building and testing
diff --git a/cgpt/cgpt_nor.c b/cgpt/cgpt_nor.c
index 2e43918..1530271 100644
--- a/cgpt/cgpt_nor.c
+++ b/cgpt/cgpt_nor.c
@@ -70,7 +70,7 @@
   return status;
 }
 
-int ForkExecL(const char *cwd, const char *cmd, ...) {
+static int ForkExecL(const char *cwd, const char *cmd, ...) {
   int argc;
   va_list ap;
   va_start(ap, cmd);
@@ -199,6 +199,10 @@
   return nftw(dir, remove_file_or_dir, 20, FTW_DEPTH | FTW_PHYS);
 }
 
+#define FLASHROM_RW_GPT_PRI "RW_GPT_PRIMARY:rw_gpt_1",
+#define FLASHROM_RW_GPT_SEC "RW_GPT_SECONDARY:rw_gpt_2"
+#define FLASHROM_RW_GPT "RW_GPT:rw_gpt"
+
 // Read RW_GPT from NOR flash to "rw_gpt" in a temp dir |temp_dir_template|.
 // |temp_dir_template| is passed to mkdtemp() so it must satisfy all
 // requirements by mkdtemp.
@@ -225,7 +229,7 @@
     Error("Cannot change directory.\n");
     goto out_free;
   }
-  const char *const argv[] = {FLASHROM_PATH, "-i", "RW_GPT:rw_gpt", "-r"};
+  const char *const argv[] = {FLASHROM_PATH, "-i", FLASHROM_RW_GPT, "-r"};
   // Redirect stdout to /dev/null so that flashrom does not muck up cgpt's
   // output.
   if (subprocess_run(argv, &subprocess_null, &subprocess_null, NULL) != 0) {
@@ -266,7 +270,7 @@
     Error("Cannot change directory.\n");
     goto out_free;
   }
-  const char *const argv1[] = {FLASHROM_PATH, "-i", "RW_GPT_PRIMARY:rw_gpt_1",
+  const char *const argv1[] = {FLASHROM_PATH, "-i", FLASHROM_RW_GPT_PRI,
                 "-w", "--noverify-all"};
   // Redirect stdout to /dev/null so that flashrom does not muck up cgpt's
   // output.
@@ -274,7 +278,7 @@
     Warning("Cannot write the 1st half of rw_gpt back with flashrom.\n");
     nr_fails++;
   }
-  const char *const argv2[] = {FLASHROM_PATH, "-i", "RW_GPT_SECONDARY:rw_gpt_2",
+  const char *const argv2[] = {FLASHROM_PATH, "-i", FLASHROM_RW_GPT_SEC,
                 "-w", "--noverify-all"};
   // Redirect stdout to /dev/null so that flashrom does not muck up cgpt's
   // output.
diff --git a/firmware/2lib/2api.c b/firmware/2lib/2api.c
index 593a12c..c041d8a 100644
--- a/firmware/2lib/2api.c
+++ b/firmware/2lib/2api.c
@@ -67,8 +67,8 @@
 	/*
 	 * Check for recovery.  Note that this function returns void, since any
 	 * errors result in requesting recovery.  That's also why we don't
-	 * return error from failures in the preceding two steps; those
-	 * failures simply cause us to detect recovery mode here.
+	 * return error from failures in the preceding steps; those failures
+	 * simply cause us to detect recovery mode here.
 	 */
 	vb2_check_recovery(ctx);
 
@@ -89,6 +89,9 @@
 	if (ctx->flags & VB2_CONTEXT_DISPLAY_INIT)
 		sd->flags |= VB2_SD_FLAG_DISPLAY_AVAILABLE;
 
+	/* Decide the boot mode */
+	vb2_set_boot_mode(ctx);
+
 	/* Return error if recovery is needed */
 	if (ctx->flags & VB2_CONTEXT_RECOVERY_MODE) {
 		/* Always clear RAM when entering recovery mode */
diff --git a/firmware/2lib/2auxfw_sync.c b/firmware/2lib/2auxfw_sync.c
index da7bc97..eaea1d4 100644
--- a/firmware/2lib/2auxfw_sync.c
+++ b/firmware/2lib/2auxfw_sync.c
@@ -28,40 +28,6 @@
 }
 
 /**
- * Update the specified auxfw and verify the update succeeded.
- *
- * @param ctx		Vboot2 context
- * @return VB2_SUCCESS, or non-zero error code.
- */
-static vb2_error_t update_auxfw(struct vb2_context *ctx)
-{
-	vb2_error_t rv;
-
-	VB2_DEBUG("Updating auxfw\n");
-
-	/*
-	 * The underlying platform is expected to know how and where to find the
-	 * firmware image for all auxfw devices.
-	 */
-	rv = vb2ex_auxfw_update();
-	if (rv != VB2_SUCCESS) {
-		VB2_DEBUG("vb2ex_auxfw_update() returned %d\n", rv);
-
-		/*
-		 * The device may need 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 != VB2_REQUEST_REBOOT_EC_TO_RO)
-			vb2api_fail(ctx, VB2_RECOVERY_AUXFW_UPDATE, rv);
-	}
-
-	return rv;
-}
-
-/**
  * Decides if auxfw sync is allowed to be performed.
  *
  * If sync is allowed, invokes the external callback,
@@ -90,7 +56,8 @@
 	VB2_TRY(auxfw_sync_check_update(ctx, &fw_update));
 
 	if (fw_update > VB2_AUXFW_NO_UPDATE) {
-		VB2_TRY(update_auxfw(ctx));
+		VB2_DEBUG("Updating auxfw\n");
+		VB2_TRY(vb2ex_auxfw_update(), ctx, VB2_RECOVERY_AUXFW_UPDATE);
 		/*
 		 * auxfw update is applied successfully. Request EC reboot to
 		 * RO, so that the chips that had FW update get reset to a
diff --git a/firmware/2lib/2ec_sync.c b/firmware/2lib/2ec_sync.c
index 20490e0..e75313b 100644
--- a/firmware/2lib/2ec_sync.c
+++ b/firmware/2lib/2ec_sync.c
@@ -160,10 +160,10 @@
 	struct vb2_shared_data *sd = vb2_get_sd(ctx);
 	int in_rw = 0;
 	/*
-	 * We don't use vb2ex_ec_trusted, which checks EC_IN_RW. It is
-	 * controlled by cr50 but on some platforms, cr50 can't know when a EC
-	 * resets. So, we trust what EC-RW says. If it lies it's in RO, we'll
-	 * flash RW while it's in RW.
+	 * We don't use VB2_CONTEXT_EC_TRUSTED, which checks if not EC_IN_RW.
+	 * It is controlled by cr50 but on some platforms, cr50 can't know when
+	 * a EC resets. So, we trust what EC-RW says. If it lies it's in RO,
+	 * we'll flash RW while it's in RW.
 	 */
 	/* If we couldn't determine where the EC was, reboot to recovery. */
 	VB2_TRY(vb2ex_ec_running_rw(&in_rw),
diff --git a/firmware/2lib/2kernel.c b/firmware/2lib/2kernel.c
index 86fb286..5b18cad 100644
--- a/firmware/2lib/2kernel.c
+++ b/firmware/2lib/2kernel.c
@@ -164,7 +164,7 @@
 		/* Load recovery key from GBB. */
 		rv = vb2_gbb_read_recovery_key(ctx, &packed_key, NULL, &wb);
 		if (rv) {
-			if (vb2api_allow_recovery(ctx))
+			if (ctx->boot_mode != VB2_BOOT_MODE_BROKEN_SCREEN)
 				VB2_DIE("GBB read recovery key failed.\n");
 			else
 				/*
diff --git a/firmware/2lib/2misc.c b/firmware/2lib/2misc.c
index efa33a8..6e92690 100644
--- a/firmware/2lib/2misc.c
+++ b/firmware/2lib/2misc.c
@@ -154,7 +154,6 @@
 		else
 			/* Recovery was forced. Override recovery reason */
 			sd->recovery_reason = VB2_RECOVERY_RO_MANUAL;
-		sd->flags |= VB2_SD_FLAG_MANUAL_RECOVERY;
 	}
 
 	/* If recovery reason is non-zero, tell caller we need recovery mode */
@@ -164,6 +163,8 @@
 			  sd->recovery_reason,
 			  vb2_nv_get(ctx, VB2_NV_RECOVERY_SUBCODE));
 	}
+
+	sd->status |= VB2_SD_STATUS_RECOVERY_DECIDED;
 }
 
 vb2_error_t vb2_fw_init_gbb(struct vb2_context *ctx)
@@ -377,7 +378,7 @@
 
 vb2_error_t vb2api_enable_developer_mode(struct vb2_context *ctx)
 {
-	if (!vb2api_allow_recovery(ctx)) {
+	if (ctx->boot_mode != VB2_BOOT_MODE_MANUAL_RECOVERY) {
 		VB2_DEBUG("ERROR: Can only enable developer mode from manual "
 			  "recovery mode\n");
 		return VB2_ERROR_API_ENABLE_DEV_NOT_ALLOWED;
@@ -391,9 +392,6 @@
 	flags |= VB2_SECDATA_FIRMWARE_FLAG_DEV_MODE;
 	vb2_secdata_firmware_set(ctx, VB2_SECDATA_FIRMWARE_FLAGS, flags);
 
-	if (BOOT_EXTERNAL_ON_DEV)
-		vb2_nv_set(ctx, VB2_NV_DEV_BOOT_EXTERNAL, 1);
-
 	VB2_DEBUG("Mode change will take effect on next reboot\n");
 
 	return VB2_SUCCESS;
@@ -416,30 +414,6 @@
 	VB2_DEBUG("Diagnostics requested\n");
 }
 
-test_mockable
-int vb2api_allow_recovery(struct vb2_context *ctx)
-{
-	if (ctx->flags & VB2_CONTEXT_NO_BOOT)
-		return 0;
-
-	/* VB2_GBB_FLAG_FORCE_MANUAL_RECOVERY forces this to always return
-	   true. */
-	if (vb2_get_gbb(ctx)->flags & VB2_GBB_FLAG_FORCE_MANUAL_RECOVERY)
-		return 1;
-
-	/*
-	 * If EC is in RW, it implies recovery wasn't manually requested.
-	 * On some platforms, EC_IN_RW can't be reset by the EC, thus, this may
-	 * return false (=RW). That's ok because if recovery is manual, we will
-	 * get the right signal and that's the case we care about.
-	 */
-	if (!(ctx->flags & VB2_CONTEXT_EC_TRUSTED) && !vb2ex_ec_trusted())
-		return 0;
-
-	/* Now we confidently check the recovery switch state at boot */
-	return !!(vb2_get_sd(ctx)->flags & VB2_SD_FLAG_MANUAL_RECOVERY);
-}
-
 void vb2_clear_recovery(struct vb2_context *ctx)
 {
 	struct vb2_shared_data *sd = vb2_get_sd(ctx);
@@ -451,13 +425,13 @@
 			  reason, subcode,
 			  vb2_get_recovery_reason_string(reason));
 
-	/* Clear recovery request for both manual and non-manual. */
+	/* Clear recovery request for both the manual recovery and the broken
+	   screen. */
 	vb2_nv_set(ctx, VB2_NV_RECOVERY_REQUEST, VB2_RECOVERY_NOT_REQUESTED);
 	vb2_nv_set(ctx, VB2_NV_RECOVERY_SUBCODE, 0);
 
-	/* But stow recovery reason as subcode for non-manual recovery. */
-	if ((ctx->flags & VB2_CONTEXT_RECOVERY_MODE) &&
-	    !vb2api_allow_recovery(ctx)) {
+	/* But stow recovery reason as subcode for the broken screen. */
+	if (ctx->boot_mode == VB2_BOOT_MODE_BROKEN_SCREEN) {
 		VB2_DEBUG("Stow recovery reason as subcode (%#x)\n",
 			  sd->recovery_reason);
 		vb2_nv_set(ctx, VB2_NV_RECOVERY_SUBCODE, sd->recovery_reason);
@@ -734,3 +708,35 @@
 	buf[DEBUG_INFO_MAX_LENGTH] = '\0';
 	return buf;
 }
+
+void vb2_set_boot_mode(struct vb2_context *ctx)
+{
+	struct vb2_shared_data *sd = vb2_get_sd(ctx);
+	struct vb2_gbb_header *gbb = vb2_get_gbb(ctx);
+
+	/* Cast boot mode to non-constant and assign */
+	enum vb2_boot_mode *boot_mode = (enum vb2_boot_mode *)&ctx->boot_mode;
+	*boot_mode = VB2_BOOT_MODE_NORMAL;
+
+	/*
+	 * The only way to pass this check and proceed to the recovery process
+	 * is to physically request a recovery (a.k.a. manual recovery).  All
+	 * other recovery requests including manual recovery requested by a
+	 * (compromised) host will end up with 'broken' screen.
+	 */
+	if ((ctx->flags & VB2_CONTEXT_FORCE_RECOVERY_MODE) &&
+	    !(ctx->flags & VB2_CONTEXT_NO_BOOT) &&
+	    (ctx->flags & VB2_CONTEXT_EC_TRUSTED)) {
+		*boot_mode = VB2_BOOT_MODE_MANUAL_RECOVERY;
+	} else if (sd->recovery_reason) {
+		if (gbb->flags & VB2_GBB_FLAG_FORCE_MANUAL_RECOVERY)
+			*boot_mode = VB2_BOOT_MODE_MANUAL_RECOVERY;
+		else
+			*boot_mode = VB2_BOOT_MODE_BROKEN_SCREEN;
+	} else if (vb2api_diagnostic_ui_enabled(ctx) &&
+		   vb2_nv_get(ctx, VB2_NV_DIAG_REQUEST)) {
+		*boot_mode = VB2_BOOT_MODE_DIAGNOSTICS;
+	} else if (ctx->flags & VB2_CONTEXT_DEVELOPER_MODE) {
+		*boot_mode = VB2_BOOT_MODE_DEVELOPER;
+	}
+}
diff --git a/firmware/2lib/2stub.c b/firmware/2lib/2stub.c
index 956b4bb..da754ad 100644
--- a/firmware/2lib/2stub.c
+++ b/firmware/2lib/2stub.c
@@ -75,12 +75,6 @@
 /* auxfw and EC-related stubs */
 
 __attribute__((weak))
-int vb2ex_ec_trusted(void)
-{
-	return 1;
-}
-
-__attribute__((weak))
 vb2_error_t vb2ex_ec_running_rw(int *in_rw)
 {
 	*in_rw = 0;
@@ -180,13 +174,6 @@
 }
 
 __attribute__((weak))
-uint32_t vb2ex_prepare_log_screen(enum vb2_screen screen, uint32_t locale_id,
-				  const char *str)
-{
-	return 1;
-}
-
-__attribute__((weak))
 vb2_error_t vb2ex_diag_get_storage_health(const char **out)
 {
 	*out = "mock";
diff --git a/firmware/2lib/include/2api.h b/firmware/2lib/include/2api.h
index 1f9d70f..1430111 100644
--- a/firmware/2lib/include/2api.h
+++ b/firmware/2lib/include/2api.h
@@ -259,6 +259,58 @@
 	VB2_CONTEXT_DEV_BOOT_ALTFW_ALLOWED = (1 << 27),
 };
 
+/* Boot mode decided in vb2api_fw_phase1.
+ *
+ * Boot mode is a constant set by verified boot and may be read (but should not
+ * be set or cleared) by the caller.
+ * The boot modes are mutually exclusive. If a boot fulfill more than one
+ * constraints of the listing boot modes, it will be set to the most important
+ * one. The priority is the same as the listing order.
+ */
+enum vb2_boot_mode {
+	/* Undefined, The boot mode is not set. */
+	VB2_BOOT_MODE_UNDEFINED = 0,
+
+	/*
+	 * Manual recovery boot, regardless of dev mode state.
+	 *
+	 * VB2_CONTEXT_RECOVERY_MODE is set and the recovery is physically
+	 * requested (a.k.a. Manual recovery).  All other recovery requests
+	 * including manual recovery requested by a (compromised) host will end
+	 * up with a broken screen.
+	 */
+	VB2_BOOT_MODE_MANUAL_RECOVERY = 1,
+
+	/*
+	 * Broken screen.
+	 *
+	 * If a recovery boot is not a manual recovery (a.k.a. not requested
+	 * physically), the recovery is not allowed and will end up with
+	 * broken screen.
+	 */
+	VB2_BOOT_MODE_BROKEN_SCREEN = 2,
+
+	/*
+	 * Diagnostic boot.
+	 *
+	 * If diagnostic boot is enabled (a.k.a. vb2api_diagnostic_ui_enabled)
+	 * and the nvdata contains VB2_NV_DIAG_REQUEST from previous boot, it
+	 * will boot to diagnostic mode.
+	 */
+	VB2_BOOT_MODE_DIAGNOSTICS = 3,
+
+	/*
+	 * Developer boot: self-signed kernel okay.
+	 *
+	 * The developer mode switch is set (a.k.a. VB2_CONTEXT_DEVELOPER_MODE)
+	 * and we are in the developer boot mode.
+	 */
+	VB2_BOOT_MODE_DEVELOPER = 4,
+
+	/* Normal boot: kernel must be verified. */
+	VB2_BOOT_MODE_NORMAL = 5,
+};
+
 /* Helper for aligning fields in vb2_context. */
 #define VB2_PAD_STRUCT3(size, align, count) \
 	uint8_t _pad##count[align - (((size - 1) % align) + 1)]
@@ -333,6 +385,16 @@
 	 */
 	uint8_t secdata_fwmp[VB2_SECDATA_FWMP_MAX_SIZE];
 	VB2_PAD_STRUCT(VB2_SECDATA_FWMP_MAX_SIZE, 8);
+
+	/**********************************************************************
+	 * Fields below added in struct version 3.1.
+	 */
+
+	/*
+	 * Mutually exclusive boot mode.
+	 * This constant is initialized after calling vb2api_fw_phase1().
+	 */
+	const enum vb2_boot_mode boot_mode;
 };
 
 /* Resource index for vb2ex_read_resource() */
@@ -950,19 +1012,6 @@
 int vb2api_use_short_dev_screen_delay(struct vb2_context *ctx);
 
 /**
- * Check whether recovery is allowed or not.
- *
- * The only way to pass this check and proceed to the recovery process is to
- * physically request a recovery (a.k.a. manual recovery).  All other recovery
- * requests including manual recovery requested by a (compromised) host will
- * end up with 'broken' screen.
- *
- * @param ctx		Vboot context
- * @return 1 if recovery is allowed; 0 if no or uncertain.
- */
-int vb2api_allow_recovery(struct vb2_context *ctx);
-
-/**
  * Request to enable developer mode.
  *
  * Enables the developer flag in vb2_context firmware secdata.  Note that
@@ -1303,14 +1352,6 @@
 vb2_error_t vb2api_ec_sync(struct vb2_context *ctx);
 
 /**
- * This is called only if the system implements a keyboard-based (virtual)
- * developer switch. It must return true only if the system has an embedded
- * controller which is provably running in its RO firmware at the time the
- * function is called.
- */
-int vb2ex_ec_trusted(void);
-
-/**
  * Check if the EC is currently running rewritable code.
  *
  * If the EC is in RO code, sets *in_rw=0.
@@ -1446,113 +1487,6 @@
 #define VB2_CLR_BIT(mask, index) ((mask) &= ~((uint32_t)1 << (index)))
 #define VB2_GET_BIT(mask, index) ((mask) & ((uint32_t)1 << (index)))
 
-/* Screens. */
-enum vb2_screen {
-	/* Wait screen for EC sync and AUXFW sync */
-	VB2_SCREEN_FIRMWARE_SYNC		= 0x100,
-	/* Broken screen */
-	VB2_SCREEN_RECOVERY_BROKEN		= 0x110,
-	/* Advanced options */
-	VB2_SCREEN_ADVANCED_OPTIONS		= 0x120,
-	/* Language selection screen */
-	VB2_SCREEN_LANGUAGE_SELECT		= 0x130,
-	/* Debug info */
-	VB2_SCREEN_DEBUG_INFO			= 0x140,
-	/* Firmware log */
-	VB2_SCREEN_FIRMWARE_LOG			= 0x150,
-	/* First recovery screen to select recovering from disk or phone */
-	VB2_SCREEN_RECOVERY_SELECT		= 0x200,
-	/* Invalid recovery media inserted */
-	VB2_SCREEN_RECOVERY_INVALID		= 0x201,
-	/* Confirm transition to developer mode */
-	VB2_SCREEN_RECOVERY_TO_DEV		= 0x202,
-	/* Recovery using phone */
-	VB2_SCREEN_RECOVERY_PHONE_STEP1		= 0x210,
-	VB2_SCREEN_RECOVERY_PHONE_STEP2		= 0x211,
-	/* Recovery using disk */
-	VB2_SCREEN_RECOVERY_DISK_STEP1		= 0x220,
-	VB2_SCREEN_RECOVERY_DISK_STEP2		= 0x221,
-	VB2_SCREEN_RECOVERY_DISK_STEP3		= 0x222,
-	/* Developer mode screen */
-	VB2_SCREEN_DEVELOPER_MODE		= 0x300,
-	/* Confirm transition to normal mode */
-	VB2_SCREEN_DEVELOPER_TO_NORM		= 0x310,
-	/* Developer boot from external disk */
-	VB2_SCREEN_DEVELOPER_BOOT_EXTERNAL	= 0x320,
-	/* Invalid external disk inserted */
-	VB2_SCREEN_DEVELOPER_INVALID_DISK	= 0x330,
-	/* Select alternate bootloader ("altfw") */
-	VB2_SCREEN_DEVELOPER_SELECT_ALTFW	= 0x340,
-	/* Diagnostic tools */
-	VB2_SCREEN_DIAGNOSTICS			= 0x400,
-	/* Storage diagnostic screen */
-	VB2_SCREEN_DIAGNOSTICS_STORAGE_HEALTH	= 0x410,
-	VB2_SCREEN_DIAGNOSTICS_STORAGE_TEST_SHORT	= 0x411,
-	VB2_SCREEN_DIAGNOSTICS_STORAGE_TEST_EXTENDED	= 0x412,
-	/* Memory diagnostic screens */
-	VB2_SCREEN_DIAGNOSTICS_MEMORY_QUICK    	= 0x420,
-	VB2_SCREEN_DIAGNOSTICS_MEMORY_FULL     	= 0x421,
-};
-
-enum vb2_ui_error {
-	/* No error */
-	VB2_UI_ERROR_NONE = 0,
-	/* Dev mode already enabled */
-	VB2_UI_ERROR_DEV_MODE_ALREADY_ENABLED,
-	/* Untrusted confirmation */
-	VB2_UI_ERROR_UNTRUSTED_CONFIRMATION,
-	/* To-norm not allowed */
-	VB2_UI_ERROR_TO_NORM_NOT_ALLOWED,
-	/* Internal boot failed */
-	VB2_UI_ERROR_INTERNAL_BOOT_FAILED,
-	/* External boot is disabled */
-	VB2_UI_ERROR_EXTERNAL_BOOT_DISABLED,
-	/* Alternate bootloader is disabled */
-	VB2_UI_ERROR_ALTFW_DISABLED,
-	/* No alternate bootloader was found */
-	VB2_UI_ERROR_ALTFW_EMPTY,
-	/* Alternate bootloader failed */
-	VB2_UI_ERROR_ALTFW_FAILED,
-	/* Debug info screen initialization failed */
-	VB2_UI_ERROR_DEBUG_LOG,
-	/* Firmware log screen initialization failed */
-	VB2_UI_ERROR_FIRMWARE_LOG,
-	/* Diagnostics internal failure */
-	VB2_UI_ERROR_DIAGNOSTICS,
-};
-
-/**
- * Display UI screen.
- *
- * @param screen		Screen to display.
- * @param locale_id		Id of current locale.
- * @param selected_item		Index of the selected menu item. If the screen
- *				doesn't have a menu, this value will be ignored.
- * @param disabled_item_mask	Mask for disabled menu items. Bit (1 << idx)
- *				indicates whether item 'idx' is disabled.
- *				A disabled menu item is visible and selectable,
- *				with a different button style.
- * @param hidden_item_mask	Mask for hidden menu items. Bit (1 << idx)
- *				indicates whether item 'idx' is hidden.
- *				A hidden menu item is neither visible nor
- *				selectable.
- * @param timer_disabled	Whether timer is disabled or not. Some screen
- *				descriptions will depend on this value.
- * @param current_page		Current page number for a log screen. If the
- *				screen doesn't show logs, this value will be
- *				ignored.
- * @param error_code		Error code if an error occurred.
- * @return VB2_SUCCESS, or error code on error.
- */
-vb2_error_t vb2ex_display_ui(enum vb2_screen screen,
-			     uint32_t locale_id,
-			     uint32_t selected_item,
-			     uint32_t disabled_item_mask,
-			     uint32_t hidden_item_mask,
-			     int timer_disabled,
-			     uint32_t current_page,
-			     enum vb2_ui_error error_code);
-
 /**
  * Check that physical presence button is currently pressed by the user.
  *
@@ -1645,22 +1579,6 @@
 const char *vb2ex_get_firmware_log(int reset);
 
 /**
- * Specify the string to be used for an upcoming log screen display.
- *
- * Before a vb2ex_display_ui() call is made for a screen which displays logs,
- * the log string should be provided via this function.  The total number of
- * pages in the log string is returned.  If the log string ever changes, this
- * function should be called again before the next vb2ex_display_ui() call.
- *
- * @param screen	Screen to display the log.
- * @param locale_id	Id of current locale.
- * @param str		The log string to display.
- * @return The number of pages after pagination.  0 if none or error.
- */
-uint32_t vb2ex_prepare_log_screen(enum vb2_screen screen, uint32_t locale_id,
-				  const char *str);
-
-/**
  * Get the health info of the storage.
  *
  * @param out	For returning a read-only pointer of full log string which is
diff --git a/firmware/2lib/include/2common.h b/firmware/2lib/include/2common.h
index 695f50d..d43f18a 100644
--- a/firmware/2lib/include/2common.h
+++ b/firmware/2lib/include/2common.h
@@ -10,6 +10,7 @@
 
 #include "2api.h"
 #include "2gbb.h"
+#include "2misc.h"
 #include "2packed_key.h"
 #include "2return_codes.h"
 #include "2sha.h"
@@ -66,7 +67,8 @@
 
 #define VB2_REC_OR_DIE(ctx, format, args...) do { \
 	VB2_DEBUG(format, ## args); \
-	if (!(ctx->flags & VB2_CONTEXT_RECOVERY_MODE)) { \
+	if ((vb2_get_sd(ctx)->status & VB2_SD_STATUS_RECOVERY_DECIDED) && \
+	    !(ctx->flags & VB2_CONTEXT_RECOVERY_MODE)) { \
 		vb2ex_abort(); \
 		for (;;); \
 	} \
diff --git a/firmware/2lib/include/2misc.h b/firmware/2lib/include/2misc.h
index b36e127..3d29287 100644
--- a/firmware/2lib/include/2misc.h
+++ b/firmware/2lib/include/2misc.h
@@ -191,4 +191,19 @@
  */
 void vb2_fill_dev_boot_flags(struct vb2_context *ctx);
 
+/**
+ * Determine and set a mutually exclusive boot mode in the vboot context.
+ *
+ * Determine the most relevant boot mode for current boot, store into
+ * ctx->boot_mode, which is a ctx field introduced in struct version 3.1.
+ *
+ * This function should be only called by vb2api_fw_phase1.
+ * The vb2api_fw_phase1 should call this function at its end phase once and all
+ * the following steps should directly access ctx->boot_mode to retrieve the
+ * most relevant boot mode.
+ *
+ * @param ctx		Vboot context.
+ */
+void vb2_set_boot_mode(struct vb2_context *ctx);
+
 #endif  /* VBOOT_REFERENCE_2MISC_H_ */
diff --git a/firmware/2lib/include/2return_codes.h b/firmware/2lib/include/2return_codes.h
index 0dc7c44..176c2c6 100644
--- a/firmware/2lib/include/2return_codes.h
+++ b/firmware/2lib/include/2return_codes.h
@@ -29,7 +29,7 @@
 	 */
 	VB2_REQUEST = 0x1000,
 
-	/* Calling firmware requested shutdown via VbExIsShutdownRequested() */
+	/* Calling firmware requested shutdown */
 	VB2_REQUEST_SHUTDOWN = 0x1001,
 
 	/* Calling firmware needs to perform a reboot */
diff --git a/firmware/2lib/include/2struct.h b/firmware/2lib/include/2struct.h
index ea193d7..3e2c422 100644
--- a/firmware/2lib/include/2struct.h
+++ b/firmware/2lib/include/2struct.h
@@ -23,8 +23,14 @@
 
 /* Flags for vb2_shared_data.flags */
 enum vb2_shared_data_flags {
-	/* User has explicitly and physically requested recovery */
-	VB2_SD_FLAG_MANUAL_RECOVERY = (1 << 0),
+	/*
+	 * VB2_SD_FLAG_MANUAL_RECOVERY (1 << 0) is deprecated. This flag is not
+	 * necessary since the introduction of persistent context (CL:1716351).
+	 * With introducing vb2_boot_mode and differentiating between manual
+	 * recovery and broken screen (CL:3274699), it is suggested to leverage
+	 * the vb2_boot_mode instead to determine if the user has physically
+	 * requested recovery.
+	 */
 
 	/* Developer mode is enabled */
 	VB2_SD_FLAG_DEV_MODE_ENABLED = (1 << 1),
@@ -85,6 +91,8 @@
 	/* EC Sync completed successfully */
 	VB2_SD_STATUS_EC_SYNC_COMPLETE = (1 << 6),
 
+	/* Have checked whether we are booting into recovery mode or not. */
+	VB2_SD_STATUS_RECOVERY_DECIDED = (1 << 7),
 };
 
 /* "V2SD" = vb2_shared_data.magic */
@@ -92,7 +100,7 @@
 
 /* Current version of vb2_shared_data struct */
 #define VB2_SHARED_DATA_VERSION_MAJOR 3
-#define VB2_SHARED_DATA_VERSION_MINOR 0
+#define VB2_SHARED_DATA_VERSION_MINOR 1
 
 /* MAX_SIZE should not be changed without bumping up DATA_VERSION_MAJOR. */
 #define VB2_CONTEXT_MAX_SIZE 384
diff --git a/firmware/include/vboot_api.h b/firmware/include/vboot_api.h
index f19ee74..ce3ac2d 100644
--- a/firmware/include/vboot_api.h
+++ b/firmware/include/vboot_api.h
@@ -89,6 +89,11 @@
  */
 vb2_error_t VbTryLoadKernel(struct vb2_context *ctx, uint32_t disk_flags);
 
+/* miniOS flags */
+
+/* Boot from non-active miniOS partition only */
+#define VB_MINIOS_FLAG_NON_ACTIVE (1 << 0)
+
 /**
  * Attempt loading a miniOS kernel from internal disk.
  *
@@ -100,9 +105,11 @@
  * VB2_SUCCESS.
  *
  * @param ctx			Vboot context
+ * @param minios_flags		Flags for miniOS
  * @return VB2_SUCCESS or the most specific VB2_ERROR_LK error.
  */
-vb2_error_t VbTryLoadMiniOsKernel(struct vb2_context *ctx);
+vb2_error_t VbTryLoadMiniOsKernel(struct vb2_context *ctx,
+				  uint32_t minios_flags);
 
 /*****************************************************************************/
 /* Disk access (previously in boot_device.h) */
@@ -281,33 +288,6 @@
  */
 void VbExStreamClose(VbExStream_t stream);
 
-/*****************************************************************************/
-/* Misc */
-
-/**
- * Check if the firmware needs to shut down the system.
- *
- * Returns a non-zero VB_SHUTDOWN_REQUEST mask indicating the reason(s) for
- * shutdown if a shutdown is being requested (see VB_SHUTDOWN_REQUEST_*), or 0
- * if a shutdown is not being requested.
- *
- * NOTE: When we're displaying a screen, pressing the power button should shut
- * down the computer.  We need a way to break out of our control loop so this
- * can occur cleanly.
- */
-uint32_t VbExIsShutdownRequested(void);
-
-/*
- * Shutdown requested for a reason which is not defined among other
- * VB_SHUTDOWN_REQUEST_* values. This must be defined as 1 for backward
- * compatibility with old versions of the API.
- */
-#define VB_SHUTDOWN_REQUEST_OTHER		0x00000001
-/* Shutdown requested due to a lid switch being closed. */
-#define VB_SHUTDOWN_REQUEST_LID_CLOSED		0x00000002
-/* Shutdown requested due to a power button being pressed. */
-#define VB_SHUTDOWN_REQUEST_POWER_BUTTON	0x00000004
-
 #ifdef __cplusplus
 }
 #endif  /* __cplusplus */
diff --git a/firmware/lib/include/load_kernel_fw.h b/firmware/lib/include/load_kernel_fw.h
index af45470..76d2556 100644
--- a/firmware/lib/include/load_kernel_fw.h
+++ b/firmware/lib/include/load_kernel_fw.h
@@ -30,11 +30,12 @@
  * @param ctx		Vboot context
  * @param params	Params specific to loading the kernel
  * @param disk_info	Disk from which to read kernel
+ * @param minios_flags	Flags for miniOS
  *
  * Returns VB2_SUCCESS if successful.  If unsuccessful, returns an error code.
  */
 vb2_error_t LoadMiniOsKernel(struct vb2_context *ctx,
 			     VbSelectAndLoadKernelParams *params,
-			     VbDiskInfo *disk_info);
+			     VbDiskInfo *disk_info, uint32_t minios_flags);
 
 #endif  /* VBOOT_REFERENCE_LOAD_KERNEL_FW_H_ */
diff --git a/firmware/lib/vboot_api_kernel.c b/firmware/lib/vboot_api_kernel.c
index 2abd57e..9e89620 100644
--- a/firmware/lib/vboot_api_kernel.c
+++ b/firmware/lib/vboot_api_kernel.c
@@ -62,7 +62,8 @@
 }
 
 static vb2_error_t VbTryLoadKernelImpl(struct vb2_context *ctx,
-				       uint32_t disk_flags, int minios)
+				       uint32_t disk_flags, int minios,
+				       uint32_t minios_flags)
 {
 	vb2_error_t rv = VB2_ERROR_LK_NO_DISK_FOUND;
 	VbDiskInfo* disk_info = NULL;
@@ -96,7 +97,7 @@
 
 		if (minios) {
 			new_rv = LoadMiniOsKernel(ctx, kparams_ptr,
-						  &disk_info[i]);
+						  &disk_info[i], minios_flags);
 			VB2_DEBUG("LoadMiniOsKernel() = %#x\n", new_rv);
 		} else {
 			new_rv = LoadKernel(ctx, kparams_ptr, &disk_info[i]);
@@ -142,13 +143,14 @@
 test_mockable
 vb2_error_t VbTryLoadKernel(struct vb2_context *ctx, uint32_t disk_flags)
 {
-	return VbTryLoadKernelImpl(ctx, disk_flags, 0);
+	return VbTryLoadKernelImpl(ctx, disk_flags, 0, 0);
 }
 
 test_mockable
-vb2_error_t VbTryLoadMiniOsKernel(struct vb2_context *ctx)
+vb2_error_t VbTryLoadMiniOsKernel(struct vb2_context *ctx,
+				  uint32_t minios_flags)
 {
-	return VbTryLoadKernelImpl(ctx, VB_DISK_FLAG_FIXED, 1);
+	return VbTryLoadKernelImpl(ctx, VB_DISK_FLAG_FIXED, 1, minios_flags);
 }
 
 vb2_error_t VbSelectAndLoadKernel(struct vb2_context *ctx,
@@ -180,13 +182,15 @@
 	}
 
 	/*
-	 * If in non-manual recovery mode, save the recovery reason as subcode.
+	 * If in the broken screen, save the recovery reason as subcode.
 	 * Otherwise, clear any leftover recovery requests or subcodes.
 	 */
 	vb2_clear_recovery(ctx);
 
 	/* Select boot path */
-	if (ctx->flags & VB2_CONTEXT_RECOVERY_MODE) {
+	switch (ctx->boot_mode) {
+	case VB2_BOOT_MODE_MANUAL_RECOVERY:
+	case VB2_BOOT_MODE_BROKEN_SCREEN:
 		/* If we're in recovery mode just to do memory retraining, all
 		   we need to do is reboot. */
 		if (sd->recovery_reason == VB2_RECOVERY_TRAIN_AND_REBOOT) {
@@ -211,12 +215,12 @@
 			VB2_DEBUG("NO_BOOT in RECOVERY mode\n");
 
 		/* Recovery boot.  This has UI. */
-		if (vb2api_allow_recovery(ctx))
+		if (ctx->boot_mode == VB2_BOOT_MODE_MANUAL_RECOVERY)
 			VB2_TRY(vb2ex_manual_recovery_ui(ctx));
 		else
 			VB2_TRY(vb2ex_broken_screen_ui(ctx));
-	} else if (vb2api_diagnostic_ui_enabled(ctx) &&
-		   vb2_nv_get(ctx, VB2_NV_DIAG_REQUEST)) {
+		break;
+	case VB2_BOOT_MODE_DIAGNOSTICS:
 		/*
 		 * Need to clear the request flag and commit nvdata changes
 		 * immediately to avoid booting back into diagnostic tool when a
@@ -232,12 +236,16 @@
 		 * return either of reboot or shutdown.
 		 */
 		return VB2_REQUEST_REBOOT;
-	} else if (ctx->flags & VB2_CONTEXT_DEVELOPER_MODE) {
+	case VB2_BOOT_MODE_DEVELOPER:
 		/* Developer boot.  This has UI. */
 		VB2_TRY(vb2ex_developer_ui(ctx));
-	} else {
+		break;
+	case VB2_BOOT_MODE_NORMAL:
 		/* Normal boot */
 		VB2_TRY(vb2_normal_boot(ctx));
+		break;
+	default:
+		return VB2_ERROR_ESCAPE_NO_BOOT;
 	}
 
 	/*
@@ -245,6 +253,7 @@
 	 * GBB flag disables software sync.
 	 */
 	if (!(gbb_flags & VB2_GBB_FLAG_DISABLE_EC_SOFTWARE_SYNC)
+	    && (ctx->flags & VB2_CONTEXT_EC_SYNC_SUPPORTED)
 	    && (ctx->flags & VB2_CONTEXT_NO_BOOT)) {
 		VB2_DEBUG("Blocking escape from NO_BOOT mode.\n");
 		vb2api_fail(ctx, VB2_RECOVERY_ESCAPE_NO_BOOT, 0);
diff --git a/firmware/lib/vboot_kernel.c b/firmware/lib/vboot_kernel.c
index 34a8a42..1edf4a5 100644
--- a/firmware/lib/vboot_kernel.c
+++ b/firmware/lib/vboot_kernel.c
@@ -6,6 +6,7 @@
  * (Firmware portion)
  */
 
+#include "2api.h"
 #include "2common.h"
 #include "2misc.h"
 #include "2nvstorage.h"
@@ -31,34 +32,6 @@
 
 #define LOWEST_TPM_VERSION 0xffffffff
 
-enum vb2_boot_mode {
-	/* Normal boot: kernel must be verified. */
-	VB2_BOOT_MODE_NORMAL = 0,
-
-	/* Recovery boot, regardless of dev mode state. */
-	VB2_BOOT_MODE_RECOVERY = 1,
-
-	/* Developer boot: self-signed kernel okay. */
-	VB2_BOOT_MODE_DEVELOPER = 2,
-};
-
-/**
- * Return the current boot mode (normal, recovery, or dev).
- *
- * @param ctx          Vboot context
- * @return Current boot mode (see vb2_boot_mode enum).
- */
-static enum vb2_boot_mode get_boot_mode(struct vb2_context *ctx)
-{
-	if (ctx->flags & VB2_CONTEXT_RECOVERY_MODE)
-		return VB2_BOOT_MODE_RECOVERY;
-
-	if (ctx->flags & VB2_CONTEXT_DEVELOPER_MODE)
-		return VB2_BOOT_MODE_DEVELOPER;
-
-	return VB2_BOOT_MODE_NORMAL;
-}
-
 /**
  * Check if a valid keyblock is required.
  *
@@ -69,7 +42,7 @@
 static int need_valid_keyblock(struct vb2_context *ctx)
 {
 	/* Normal and recovery modes always require official OS */
-	if (get_boot_mode(ctx) != VB2_BOOT_MODE_DEVELOPER)
+	if (ctx->boot_mode != VB2_BOOT_MODE_DEVELOPER)
 		return 1;
 
 	/* FWMP can require developer mode to use signed kernels */
@@ -264,9 +237,8 @@
 	}
 
 	/* Check for rollback of key version except in recovery mode. */
-	enum vb2_boot_mode boot_mode = get_boot_mode(ctx);
 	uint32_t key_version = keyblock->data_key.key_version;
-	if (boot_mode != VB2_BOOT_MODE_RECOVERY) {
+	if (ctx->boot_mode != VB2_BOOT_MODE_MANUAL_RECOVERY) {
 		if (key_version < (sd->kernel_version_secdata >> 16)) {
 			keyblock_valid = 0;
 			if (need_keyblock_valid) {
@@ -288,7 +260,7 @@
 	}
 
 	/* If in developer mode and using key hash, check it. */
-	if (boot_mode == VB2_BOOT_MODE_DEVELOPER &&
+	if (ctx->boot_mode == VB2_BOOT_MODE_DEVELOPER &&
 	    vb2_secdata_fwmp_get_flag(ctx, VB2_SECDATA_FWMP_DEV_USE_KEY_HASH)) {
 		VB2_TRY(vb2_verify_kernel_dev_key_hash(ctx, keyblock));
 	}
@@ -356,7 +328,7 @@
 
 	/* If not in recovery mode, check for rollback of the kernel version. */
 	if (need_keyblock_valid &&
-	    boot_mode != VB2_BOOT_MODE_RECOVERY &&
+	    ctx->boot_mode != VB2_BOOT_MODE_MANUAL_RECOVERY &&
 	    sd->kernel_version < sd->kernel_version_secdata) {
 		VB2_DEBUG("Kernel version too low.\n");
 		return VB2_ERROR_KERNEL_PREAMBLE_VERSION_ROLLBACK;
@@ -607,12 +579,17 @@
  */
 vb2_error_t LoadMiniOsKernel(struct vb2_context *ctx,
 			     VbSelectAndLoadKernelParams *params,
-			     VbDiskInfo *disk_info)
+			     VbDiskInfo *disk_info, uint32_t minios_flags)
 {
 	vb2_error_t rv;
 	int end_region_first = vb2_nv_get(ctx, VB2_NV_MINIOS_PRIORITY);
 
-	rv = try_minios_sector_region(ctx, params, disk_info, end_region_first);
+	if (minios_flags & VB_MINIOS_FLAG_NON_ACTIVE)
+		rv = VB2_ERROR_UNKNOWN;  /* Ignore active partition */
+	else
+		rv = try_minios_sector_region(ctx, params, disk_info,
+					      end_region_first);
+
 	if (rv)
 		rv = try_minios_sector_region(ctx, params, disk_info,
 					      !end_region_first);
@@ -739,7 +716,7 @@
 		 * non-officially-signed kernel, there's no rollback
 		 * protection, so we can stop at the first valid kernel.
 		 */
-		if (get_boot_mode(ctx) == VB2_BOOT_MODE_RECOVERY ||
+		if (ctx->boot_mode == VB2_BOOT_MODE_MANUAL_RECOVERY ||
 		    !keyblock_valid) {
 			VB2_DEBUG("In recovery mode or dev-signed kernel\n");
 			break;
diff --git a/firmware/stub/vboot_api_stub.c b/firmware/stub/vboot_api_stub.c
index 5e08556..9335c8b 100644
--- a/firmware/stub/vboot_api_stub.c
+++ b/firmware/stub/vboot_api_stub.c
@@ -19,12 +19,6 @@
 #include "vboot_test.h"
 
 __attribute__((weak))
-uint32_t VbExIsShutdownRequested(void)
-{
-	return 0;
-}
-
-__attribute__((weak))
 vb2_error_t vb2ex_run_altfw(uint32_t altfw_id)
 {
 	return VB2_SUCCESS;
diff --git a/futility/cmd_gscvd.c b/futility/cmd_gscvd.c
new file mode 100644
index 0000000..7fff293
--- /dev/null
+++ b/futility/cmd_gscvd.c
@@ -0,0 +1,1050 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <openssl/bn.h>
+#include <openssl/pem.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "fmap.h"
+#include "futility.h"
+#include "gsc_ro.h"
+#include "host_key21.h"
+#include "host_keyblock.h"
+#include "host_signature.h"
+
+/*
+ * for testing purposes let's use
+ * - tests/devkeys/arv_root.vbprivk as the root private key
+ * - tests/devkeys/arv_root.vbpubk as the root public key
+ *   used for signing of the platform public key
+ * - tests/devkeys/arv_platform.vbprivk signing platform key
+ * - tests/devkeys/arv_platform.vbpubk - public key used for signature
+ *       verification
+ *------------
+ * Command to create the signed public key block in ~/tmp/packed:
+ *
+  ./build/futility/futility vbutil_keyblock --pack ~/tmp/packed \
+      --datapubkey  tests/devkeys/arv_platform.vbpubk \
+      --signprivate tests/devkeys/arv_root.vbprivk
+ *------------
+ * Command to fill RO_GSCVD FMAP area in an AP firmware file. The input AP
+ *   firmware file is ~/tmp/image-guybrush.serial.bin, the output signed
+ *   AP firmware file is ~/tmp/guybrush-signed:
+ *
+  ./build/futility/futility gscvd --outfile ~/tmp/guybrush-signed \
+     -R 818100:10000,f00000:100,f80000:2000,f8c000:1000,0x00804000:0x00000800 \
+     -k ~/tmp/packed -p tests/devkeys/arv_platform.vbprivk -b 5a5a4352  \
+     -r tests/devkeys/arv_root.vbpubk ~/tmp/image-guybrush.serial.bin
+ *------------
+ * Command to validate a previously signed AP firmware file. The hash is the
+ *  sha256sum of tests/devkeys/kernel_subkey.vbpubk:
+ *
+  build/futility/futility gscvd ~/tmp/guybrush-signed \
+   3d74429f35be8d34bcb425d4397e2218e6961afed456a78ce30047f5b54ed158
+ */
+
+/* Command line options processing support. */
+enum no_short_opts {
+	OPT_OUTFILE = 1000,
+};
+
+static const struct option long_opts[] = {
+	/* name       hasarg *flag  val */
+	{"outfile",       1, NULL, OPT_OUTFILE},
+	{"ranges",        1, NULL, 'R'},
+	{"board_id",      1, NULL, 'b'},
+	{"root_pub_key",  1, NULL, 'r'},
+	{"keyblock",      1, NULL, 'k'},
+	{"platform_priv", 1, NULL, 'p'},
+	{"help",          0, NULL, 'h'},
+	{}
+};
+
+static const char *short_opts = "R:b:hk:p:r:";
+
+static const char usage[] =
+	"\n"
+	"This utility creates an RO verification space in the Chrome OS AP\n"
+	"firmware image or allows to validate a previously prepared image\n"
+	"containing the RO verification space.\n\n"
+	"Usage: " MYNAME " gscvd PARAMS <AP FIRMWARE FILE> [<root key hash>]\n"
+	"\n\nCreation of RO Verification space:\n\n"
+	"Required PARAMS:\n"
+	"  -R|--ranges        STRING        Comma separated colon delimited\n"
+	"                                     hex tuples <offset>:<size>, the\n"
+	"                                     areas of the RO covered by the\n"
+	"                                     signature\n"
+	"  -b|--board_id  <hex value>      The Board ID of the board for which\n"
+	"                                     the image is being signed\n"
+	"  -r|--root_pub_key  <file>        The main public key, in .vbpubk\n"
+	"                                     format, used to verify platform\n"
+	"                                     key\n"
+	"  -k|--keyblock      <file>        Signed platform public key in\n"
+	"                                     .keyblock format, used for run\n"
+	"                                     time RO verifcation\n"
+	"  -p|--platform_priv <file>        Private platform key in .vbprivk\n"
+	"                                     format, used for signing RO\n"
+	"                                     verification data\n"
+	"Optional PARAMS:\n"
+	"  [--outfile]        OUTFILE       Output firmware image containing\n"
+	"                                     RO verification information\n"
+	"\n\n"
+	"Validation of RO Verification space:\n\n"
+	"   The only required parameter is <AP FIRMWARE FILE>, if optional\n"
+	"   <root key hash> is given, it is compared to the hash\n"
+	"   of the root key found in <AP_FIRMWARE_FILE>.\n"
+	"\n\n"
+	"  -h|--help                        Print this message\n\n";
+
+/* Structure helping to keep track of the file mapped into memory. */
+struct file_buf {
+	uint32_t len;
+	uint8_t *data;
+	int fd;
+	FmapAreaHeader *ro_gscvd;
+};
+
+/*
+ * Max number of RO ranges to cover. 32 is more than enough, this must be kept
+ * in sync with APRO_MAX_NUM_RANGES declaration in
+ * common/ap_ro_integrity_check.c in the Cr50 tree.
+ */
+#define MAX_RANGES 32
+
+/*
+ * Container keeping track of the set of ranges to include in hash
+ * calculation.
+ */
+struct gscvd_ro_ranges {
+	size_t range_count;
+	struct gscvd_ro_range ranges[MAX_RANGES];
+};
+
+/**
+ * Load the AP firmware file into memory.
+ *
+ * Map the requested file into memory, find RO_GSCVD area in the file, and
+ * cache the information in the passed in file_buf structure.
+ *
+ * @param file_name  name of the AP firmware file
+ * @param file_buf   pointer to the helper structure keeping information about
+ *                   the file
+ *
+ * @return 0 on success 1 on failure.
+ */
+static int load_ap_firmware(const char *file_name, struct file_buf *file)
+{
+	int fd;
+	int rv;
+
+	fd = open(file_name, O_RDWR);
+	if (fd < 0) {
+		ERROR("Can't open %s: %s\n", file_name,
+		      strerror(errno));
+		return 1;
+	}
+
+	file->fd = fd;
+	do {
+		rv = 1;
+
+		if (futil_map_file(fd, MAP_RW, &file->data, &file->len)) {
+			file->data = NULL;
+			break;
+		}
+
+		if (!fmap_find_by_name(file->data, file->len, NULL, "RO_GSCVD",
+				       &file->ro_gscvd)) {
+			ERROR("Could not find RO_GSCVD in the FMAP\n");
+			break;
+		}
+		rv = 0;
+	} while (false);
+
+	return rv;
+}
+
+/**
+ * Check if the passed in offset falls into the passed in FMAP area.
+ */
+static bool in_range(uint32_t offset, const FmapAreaHeader *ah)
+{
+	return (offset >= ah->area_offset) &&
+	       (offset <= (ah->area_offset + ah->area_size));
+}
+
+/**
+ * Check if the passed in range fits into the passed in FMAP area.
+ */
+static bool range_fits(const struct gscvd_ro_range *range,
+		       const FmapAreaHeader *ah)
+{
+	if (in_range(range->offset, ah) &&
+	    in_range(range->offset + range->size, ah))
+		return true;
+
+	ERROR("Range %#x..+%#x does not fit in %s\n", range->offset,
+	      range->size, ah->area_name);
+
+	return false;
+}
+
+/**
+ * Check if the passed in range overlaps with the area.
+ *
+ * @param range  pointer to the range to check
+ * @param offset  offset of the area to check against
+ * @param size  size of the area to check against
+ *
+ * @return true if range overlaps with the area, false otherwise.
+ */
+static bool range_overlaps(const struct gscvd_ro_range *range, uint32_t offset,
+			   size_t size)
+{
+	if (((range->offset + range->size) <= offset) ||
+	    (offset + size) <= range->offset)
+		return false;
+
+	ERROR("Range %x..+%x overlaps with %x..+%zx\n", range->offset,
+	      range->size, offset, size);
+
+	return true;
+}
+
+/*
+ * Check validity of the passed in ranges.
+ *
+ * All ranges must
+ * - fit into the WP_RO FMAP area
+ * - not overlap with the RO_GSCVD FMAP area
+ * - not overlap with each other
+ *
+ * @param ranges - pointer to the container of ranges to check
+ * @param file - pointer to the file layout descriptor
+ *
+ * @return zero on success, -1 on failures
+ */
+static int verify_ranges(const struct gscvd_ro_ranges *ranges,
+			 const struct file_buf *file)
+{
+	size_t i;
+	FmapAreaHeader *wp_ro;
+	int errorcount;
+
+	if (!fmap_find_by_name(file->data, file->len, NULL, "WP_RO", &wp_ro)) {
+		ERROR("Could not find WP_RO in the FMAP\n");
+		return 1;
+	}
+
+	errorcount = 0;
+	for (i = 0; i < ranges->range_count; i++) {
+		size_t j;
+
+		/* Must fit into WP_RO. */
+		if (!range_fits(ranges->ranges + i, wp_ro))
+			errorcount++;
+
+		/* Must not overlap with RO_GSCVD. */
+		if (range_overlaps(ranges->ranges + i,
+				   file->ro_gscvd->area_offset,
+				   file->ro_gscvd->area_size))
+			errorcount++;
+
+		/* The last range is nothing to compare against. */
+		if (i == ranges->range_count - 1)
+			break;
+
+		/* Must not overlap with all following ranges. */
+		for (j = i + 1; j < ranges->range_count; j++)
+			if (range_overlaps(ranges->ranges + i,
+					   ranges->ranges[j].offset,
+					   ranges->ranges[j].size))
+				errorcount++;
+	}
+
+	return errorcount ? -1 : 0;
+}
+
+/**
+ * Parse range specification supplied by the user.
+ *
+ * The input is a string of the following format:
+ * <hex base>:<hex size>[,<hex base>:<hex size>[,...]]
+ *
+ * @param input  user input, part of the command line
+ * @param output  pointer to the ranges container
+ *
+ * @return zero on success, -1 on failure
+ */
+static int parse_ranges(const char *input, struct gscvd_ro_ranges *output)
+{
+	char *cursor;
+	char *delim;
+	char *str = strdup(input);
+	int rv = 0;
+
+	if (!str) {
+		ERROR("Failed to allocate memory for ranges string copy!\n");
+		return -1;
+	}
+
+	output->range_count = 0;
+	cursor = str;
+	do {
+		char *colon;
+		char *e;
+
+		if (output->range_count >= ARRAY_SIZE(output->ranges)) {
+			ERROR("Too many ranges!\n");
+			rv = -1;
+			break;
+		}
+
+		delim = strchr(cursor, ',');
+		if (delim)
+			*delim = '\0';
+		colon = strchr(cursor, ':');
+		if (!colon) {
+			rv = -1;
+			break;
+		}
+		*colon = '\0';
+
+		errno = 0;
+		output->ranges[output->range_count].offset =
+			strtol(cursor, &e, 16);
+		if (errno || *e) {
+			rv = -1;
+			break;
+		}
+
+		output->ranges[output->range_count].size =
+			strtol(colon + 1, &e, 16);
+		if (errno || *e) {
+			rv = -1;
+			break;
+		}
+
+		output->range_count++;
+		cursor = delim + 1;
+		/* Iterate until there is no more commas. */
+	} while (delim);
+
+	free(str);
+	if (rv)
+		ERROR("Misformatted ranges string\n");
+
+	return rv;
+}
+
+/**
+ * Calculate hash of the RO ranges.
+ *
+ * @param ap_firmware_file  pointer to the AP firmware file layout descriptor
+ * @param ranges  pointer to the container of ranges to include in hash
+ *		  calculation
+ * @param hash_alg  algorithm to use for hashing
+ * @param digest  memory to copy the calculated hash to
+ * @param digest_ size requested size of the digest, padded with zeros if the
+ *	          SHA digest size is smaller than digest_size
+ *
+ * @return zero on success, -1 on failure.
+ */
+static int calculate_ranges_digest(const struct file_buf *ap_firmware_file,
+				   const struct gscvd_ro_ranges *ranges,
+				   enum vb2_hash_algorithm hash_alg,
+				   void *digest, size_t digest_size)
+{
+	struct vb2_digest_context dc;
+	size_t i;
+
+	/* Calculate the ranges digest. */
+	if (vb2_digest_init(&dc, hash_alg) != VB2_SUCCESS) {
+		ERROR("Failed to init digest!\n");
+		return 1;
+	}
+
+	for (i = 0; i < ranges->range_count; i++) {
+		if (vb2_digest_extend(&dc,
+				      ap_firmware_file->data +
+					      ranges->ranges[i].offset,
+				      ranges->ranges[i].size) != VB2_SUCCESS) {
+			ERROR("Failed to extend digest!\n");
+			return -1;
+		}
+	}
+
+	memset(digest, 0, digest_size);
+	if (vb2_digest_finalize(&dc, digest, digest_size) != VB2_SUCCESS) {
+		ERROR("Failed to finalize digest!\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+/**
+ * Build GSC verification data.
+ *
+ * Calculate size of the structure including the signature and the root key,
+ * allocate memory, fill up the structure, calculate AP RO ranges digest and
+ * then the GVD signature.
+ *
+ * @param ap_firmware_file  pointer to the AP firmware file layout descriptor
+ * @param ranges  pointer to the container of ranges to include in verification
+ * @param root_pubk  pointer to the root pubk container
+ * @param privk   pointer to the private key to use for signing
+ * @param board_id  Board ID value to use.
+ *
+ * @return pointer to the created GVD (to be freed by the caller) on success,
+ *         NULL on failure.
+ */
+static
+struct gsc_verification_data *create_gvd(struct file_buf *ap_firmware_file,
+					 struct gscvd_ro_ranges *ranges,
+					 const struct vb2_packed_key *root_pubk,
+					 const struct vb2_private_key *privk,
+					 uint32_t board_id)
+{
+	struct gsc_verification_data *gvd;
+	size_t total_size;
+	size_t sig_size;
+	size_t ranges_size;
+	struct vb2_signature *sig;
+	const FmapHeader *fmh;
+
+	sig_size = vb2_rsa_sig_size(privk->sig_alg);
+	ranges_size = ranges->range_count * sizeof(struct gscvd_ro_range);
+	total_size = sizeof(struct gsc_verification_data) +
+		root_pubk->key_size + sig_size + ranges_size;
+
+	gvd = calloc(total_size, 1);
+
+	if (!gvd) {
+		ERROR("Failed to allocate %zd bytes for gvd\n", total_size);
+		return NULL;
+	}
+
+	gvd->gv_magic = GSC_VD_MAGIC;
+	gvd->size = total_size;
+	gvd->gsc_board_id = board_id;
+	gvd->rollback_counter = GSC_VD_ROLLBACK_COUNTER;
+
+	/* Guaranteed to succeed. */
+	fmh = fmap_find(ap_firmware_file->data, ap_firmware_file->len);
+
+	gvd->fmap_location = (uintptr_t)fmh - (uintptr_t)ap_firmware_file->data;
+
+	gvd->hash_alg = VB2_HASH_SHA256;
+
+	if (calculate_ranges_digest(ap_firmware_file, ranges, gvd->hash_alg,
+				    gvd->ranges_digest,
+				    sizeof(gvd->ranges_digest))) {
+		free(gvd);
+		return NULL;
+	}
+
+	/* Prepare signature header. */
+	vb2_init_signature(&gvd->sig_header,
+			   (uint8_t *)(gvd + 1) + ranges_size,
+			   sig_size,
+			   sizeof(struct gsc_verification_data) + ranges_size);
+
+	/* Copy root key into the structure. */
+	vb2_init_packed_key(&gvd->root_key_header,
+			    (uint8_t *)(gvd + 1) + ranges_size + sig_size,
+			    root_pubk->key_size);
+	vb2_copy_packed_key(&gvd->root_key_header, root_pubk);
+
+	/* Copy ranges into the ranges section. */
+	gvd->range_count = ranges->range_count;
+	memcpy(gvd->ranges, ranges->ranges, ranges_size);
+
+	sig = vb2_calculate_signature((uint8_t *)gvd,
+				      sizeof(struct gsc_verification_data) +
+				      ranges_size, privk);
+	if (!sig) {
+		ERROR("Failed to calculate signature\n");
+		free(gvd);
+		return NULL;
+	}
+
+	/* Copy signature body into GVD after some basic checks. */
+	if ((sig_size == sig->sig_size) &&
+	    (gvd->sig_header.data_size == sig->data_size)) {
+		vb2_copy_signature(&gvd->sig_header, sig);
+	} else {
+		ERROR("Inconsistent signature headers\n");
+		free(sig);
+		free(gvd);
+		return NULL;
+	}
+
+	free(sig);
+
+	return gvd;
+}
+
+/**
+ * Fill RO_GSCVD FMAP area.
+ *
+ * All trust chain components have been verified, AP RO sections digest
+ * calculated, and GVD signature created; put it all together in the dedicated
+ * FMAP area.
+ *
+ * @param ap_firmware_file  pointer to the AP firmware file layout descriptor
+ * @param gvd  pointer to the GVD header
+ * @param keyblock  pointer to the keyblock container
+ *
+ * @return zero on success, -1 on failure
+ */
+static int fill_gvd_area(struct file_buf *ap_firmware_file,
+			 struct gsc_verification_data *gvd,
+			 struct vb2_keyblock *keyblock)
+{
+	size_t total;
+	uint8_t *cursor;
+
+	/* How much room is needed for the whole thing? */
+	total = gvd->size + keyblock->keyblock_size;
+
+	if (total > ap_firmware_file->ro_gscvd->area_size) {
+		ERROR("GVD section does not fit, %zd > %d\n",
+		      total, ap_firmware_file->ro_gscvd->area_size);
+		return -1;
+	}
+
+	cursor = ap_firmware_file->data +
+		 ap_firmware_file->ro_gscvd->area_offset;
+
+	/* Copy GSC verification data */
+	memcpy(cursor, gvd, gvd->size);
+	cursor += gvd->size;
+
+	/* Keyblock, size includes everything. */
+	memcpy(cursor, keyblock, keyblock->keyblock_size);
+
+	return 0;
+}
+
+/**
+ * Initialize a work buffer structure.
+ *
+ * Embedded vboot reference code does not use malloc/free, it uses the so
+ * called work buffer structure to provide a poor man's memory management
+ * tool. This program uses some of the embedded library functions, let's
+ * implement work buffer support to keep the embedded code happy.
+ *
+ * @param wb  pointer to the workubffer structure to initialize
+ * @param size  size of the buffer to allocate
+ *
+ * @return pointer to the allocated buffer on success, NULL on failure.
+ */
+static void *init_wb(struct vb2_workbuf *wb, size_t size)
+{
+	void *buf = malloc(size);
+
+	if (!buf)
+		ERROR("Failed to allocate workblock of %zd\n", size);
+	else
+		vb2_workbuf_init(wb, buf, size);
+
+	return buf;
+}
+
+/**
+ * Validate that platform key keyblock was signed by the root key.
+ *
+ * This function performs the same step the GSC is supposed to perform:
+ * validate the platform key keyblock signature using the root public key.
+ *
+ * @param root_pubk  pointer to the root public key container
+ * @param kblock  pointer to the platform public key keyblock
+ *
+ * @return 0 on success, -1 on failure
+ */
+static int validate_pubk_signature(const struct vb2_packed_key *root_pubk,
+				   struct vb2_keyblock *kblock)
+{
+	struct vb2_public_key pubk;
+	struct vb2_workbuf wb;
+	uint32_t kbsize;
+	int rv;
+	void *buf;
+
+	if (vb2_unpack_key(&pubk, root_pubk) != VB2_SUCCESS) {
+		ERROR("Failed to unpack public key\n");
+		return -1;
+	}
+
+	/* Let's create an ample sized work buffer. */
+	buf = init_wb(&wb, 8192);
+	if (!buf)
+		return -1;
+
+	rv = -1;
+	do {
+		void *work;
+
+		kbsize = kblock->keyblock_size;
+		work = vb2_workbuf_alloc(&wb, kbsize);
+		if (!work) {
+			ERROR("Failed to allocate workblock space %d\n",
+			      kbsize);
+			break;
+		}
+
+		memcpy(work, kblock, kbsize);
+
+		if (vb2_verify_keyblock(work, kbsize, &pubk, &wb) !=
+		    VB2_SUCCESS) {
+			ERROR("Root and keyblock mismatch\n");
+			break;
+		}
+
+		rv = 0;
+	} while (false);
+
+	free(buf);
+
+	return rv;
+}
+
+/**
+ * Validate that private and public parts of the platform key match.
+ *
+ * This is a fairly routine validation, the N components of the private and
+ * public RSA keys are compared.
+ *
+ * @param keyblock  pointer to the keyblock containing the public key
+ * @param plat_privk  pointer to the matching private key
+ *
+ * @return 0 on success, nonzero on failure
+ */
+static int validate_privk(struct vb2_keyblock *kblock,
+			  struct vb2_private_key *plat_privk)
+{
+	const BIGNUM *privn;
+	BIGNUM *pubn;
+	struct vb2_public_key pubk;
+	int rv;
+
+	privn = pubn = NULL;
+
+	RSA_get0_key(plat_privk->rsa_private_key, &privn, NULL, NULL);
+
+	if (vb2_unpack_key(&pubk, &kblock->data_key) != VB2_SUCCESS) {
+		ERROR("Failed to unpack public key\n");
+		return -1;
+	}
+
+	pubn = BN_new();
+	pubn = BN_lebin2bn((uint8_t *)pubk.n, vb2_rsa_sig_size(pubk.sig_alg),
+			   pubn);
+	rv = BN_cmp(pubn, privn);
+	if (rv)
+		ERROR("Public/private key N mismatch!\n");
+
+	BN_free(pubn);
+	return rv;
+}
+
+/**
+ * Copy ranges from AP firmware file into gscvd_ro_ranges container
+ *
+ * While copying the ranges verify that they do not overlap.
+ *
+ * @param ap_firmware_file  pointer to the AP firmware file layout descriptor
+ * @param gvd  pointer to the GVD header followed by the ranges
+ * @param ranges  pointer to the ranges container to copy ranges to
+ *
+ * @return 0 on successful copy nonzero on errors.
+ */
+static int copy_ranges(const struct file_buf *ap_firmware_file,
+		       const struct gsc_verification_data *gvd,
+		       struct gscvd_ro_ranges *ranges)
+{
+	ranges->range_count = gvd->range_count;
+	memcpy(ranges->ranges, gvd->ranges,
+	       sizeof(ranges->ranges[0]) * ranges->range_count);
+
+	return verify_ranges(ranges, ap_firmware_file);
+}
+
+/**
+ * Basic validation of GVD included in a AP firmware file.
+ *
+ * This is not a cryptographic verification, just a check that the structure
+ * makes sense and the expected values are found in certain fields.
+ *
+ * @param gvd  pointer to the GVD header followed by the ranges
+ * @param ap_firmware_file  pointer to the AP firmware file layout descriptor
+ *
+ * @return zero on success, -1 on failure.
+ */
+static int validate_gvd(const struct gsc_verification_data *gvd,
+			const struct file_buf *ap_firmware_file)
+{
+	const FmapHeader *fmh;
+
+	if (gvd->gv_magic != GSC_VD_MAGIC) {
+		ERROR("Incorrect gscvd magic %x\n", gvd->gv_magic);
+		return -1;
+	}
+
+	if (!gvd->range_count || (gvd->range_count > MAX_RANGES)) {
+		ERROR("Incorrect gscvd range count %d\n", gvd->range_count);
+		return -1;
+	}
+
+	/* Guaranteed to succeed. */
+	fmh = fmap_find(ap_firmware_file->data, ap_firmware_file->len);
+
+	if (gvd->fmap_location !=
+	    ((uintptr_t)fmh - (uintptr_t)ap_firmware_file->data)) {
+		ERROR("Incorrect gscvd fmap offset %x\n", gvd->fmap_location);
+		return -1;
+	}
+
+	/* Make sure signature and root key fit. */
+	if (vb2_verify_signature_inside(gvd, gvd->size, &gvd->sig_header) !=
+	    VB2_SUCCESS) {
+		ERROR("Corrupted signature header in GVD\n");
+		return -1;
+	}
+
+	if (vb2_verify_packed_key_inside(gvd, gvd->size,
+					 &gvd->root_key_header) !=
+	    VB2_SUCCESS) {
+		ERROR("Corrupted root key header in GVD\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+/**
+ * Validate GVD signature.
+ *
+ * Given the entire GVD space (header plus ranges array), the signature and
+ * the public key, verify that the signature matches.
+ *
+ * @param gvd  pointer to gsc_verification_data followed by the ranges array
+ * @param gvd_signature  pointer to the vb2 signature container
+ * @param packedk  pointer to the keyblock containing the public key
+ *
+ * @return zero on success, non-zero on failure
+ */
+static int validate_gvd_signature(struct gsc_verification_data *gvd,
+				  const struct vb2_packed_key *packedk)
+{
+	struct vb2_workbuf wb;
+	void *buf;
+	int rv;
+	struct vb2_public_key pubk;
+	size_t signed_size;
+
+	/* Extract public key from the public key keyblock. */
+	if (vb2_unpack_key(&pubk, packedk) != VB2_SUCCESS) {
+		ERROR("Failed to unpack public key\n");
+		return -1;
+	}
+
+	/* Let's create an ample sized work buffer. */
+	buf = init_wb(&wb, 8192);
+	if (!buf)
+		return -1;
+
+	signed_size = sizeof(struct gsc_verification_data) +
+		gvd->range_count * sizeof(gvd->ranges[0]);
+
+	rv = vb2_verify_data((const uint8_t *)gvd, signed_size,
+			     &gvd->sig_header,
+			     &pubk, &wb);
+
+	free(buf);
+	return rv;
+}
+
+/*
+ * Validate GVD of the passed in AP firmware file and possibly the root key hash
+ *
+ * The input parameters are the subset of the command line, the first argv
+ * string is the AP firmware file name, the second string, if present, is the
+ * hash of the root public key included in the RO_GSCVD area of the AP
+ * firmware file.
+ *
+ * @return zero on success, -1 on failure.
+ */
+static int validate_gscvd(int argc, char *argv[])
+{
+	struct file_buf ap_firmware_file;
+	int rv;
+	struct gscvd_ro_ranges ranges;
+	struct gsc_verification_data *gvd;
+	const char *file_name;
+	uint8_t digest[sizeof(gvd->ranges_digest)];
+	struct vb2_hash root_key_digest = { .algo = VB2_HASH_SHA256 };
+
+	/* Guaranteed to be available. */
+	file_name = argv[0];
+
+	if (argc > 1)
+		parse_digest_or_die(root_key_digest.sha256,
+				    sizeof(root_key_digest.sha256),
+				    argv[1]);
+
+	do {
+		struct vb2_keyblock *kblock;
+
+		rv = -1; /* Speculative, will be cleared on success. */
+
+		if (load_ap_firmware(file_name, &ap_firmware_file))
+			break;
+
+		/* Copy ranges from gscvd to local structure. */
+		gvd = (struct gsc_verification_data
+			       *)(ap_firmware_file.data +
+				  ap_firmware_file.ro_gscvd->area_offset);
+
+		if (validate_gvd(gvd, &ap_firmware_file))
+			break;
+
+		if (copy_ranges(&ap_firmware_file, gvd, &ranges))
+			break;
+
+		if (calculate_ranges_digest(&ap_firmware_file, &ranges,
+					    gvd->hash_alg, digest,
+					    sizeof(digest)))
+			break;
+
+		if (memcmp(digest, gvd->ranges_digest, sizeof(digest))) {
+			ERROR("Ranges digest mismatch\n");
+			break;
+		}
+
+		/* Find the keyblock. */
+		kblock = (struct vb2_keyblock *)((uintptr_t)gvd + gvd->size);
+
+		if ((argc > 1) && (vb2_hash_verify
+				   (vb2_packed_key_data(&gvd->root_key_header),
+				    gvd->root_key_header.key_size,
+				    &root_key_digest) != VB2_SUCCESS)) {
+			ERROR("Sha256 mismatch\n");
+			break;
+		}
+
+		if (validate_pubk_signature(&gvd->root_key_header,
+					    kblock))
+			break;
+
+		if (validate_gvd_signature(gvd, &kblock->data_key))
+			break;
+
+		rv = 0;
+	} while (false);
+
+	return rv;
+}
+
+/**
+ * Calculate and report sha256 hash of the public key body.
+ *
+ * The hash will be incorporated into GVC firmware to allow it to validate the
+ * root key.
+ *
+ * @param pubk pointer to the public key to process.
+ */
+static void dump_pubk_hash(const struct vb2_packed_key *pubk)
+{
+	struct vb2_hash hash;
+	size_t i;
+
+	vb2_hash_calculate(vb2_packed_key_data(pubk), pubk->key_size,
+			   VB2_HASH_SHA256, &hash);
+
+	printf("Root key body sha256 hash:\n");
+
+	for (i = 0; i < sizeof(hash.sha256); i++)
+		printf("%02x", hash.sha256[i]);
+
+	printf("\n");
+}
+
+/**
+ * The main function of this futilty option.
+ *
+ * See the usage string for input details.
+ *
+ * @return zero on success, nonzero on failure.
+ */
+static int do_gscvd(int argc, char *argv[])
+{
+	int i;
+	int longindex;
+	char *infile = NULL;
+	char *outfile = NULL;
+	char *work_file = NULL;
+	struct gscvd_ro_ranges ranges;
+	int errorcount = 0;
+	struct vb2_packed_key *root_pubk = NULL;
+	struct vb2_keyblock *kblock = NULL;
+	struct vb2_private_key *plat_privk = NULL;
+	struct gsc_verification_data *gvd = NULL;
+	struct file_buf ap_firmware_file = { .fd = -1 };
+	uint32_t board_id = UINT32_MAX;
+	int rv = 0;
+
+	ranges.range_count = 0;
+
+	while ((i = getopt_long(argc, argv, short_opts, long_opts,
+				&longindex)) != -1) {
+		switch (i) {
+		case OPT_OUTFILE:
+			outfile = optarg;
+			break;
+		case 'R':
+			if (parse_ranges(optarg, &ranges)) {
+				ERROR("Could not parse ranges\n");
+				/* Error message has been already printed. */
+				errorcount++;
+			}
+			break;
+		case 'b': {
+			char *e;
+			long long bid;
+
+			bid = strtoull(optarg, &e, 16);
+			if (*e || (bid >= UINT32_MAX)) {
+				ERROR("Board ID value '%s' is invalid\n",
+				      optarg);
+				errorcount++;
+			} else {
+				board_id = (uint32_t)bid;
+			}
+			break;
+		}
+		case 'r':
+			root_pubk = vb2_read_packed_key(optarg);
+			if (!root_pubk) {
+				ERROR("Could not read %s\n", optarg);
+				errorcount++;
+			}
+			break;
+		case 'k':
+			kblock = vb2_read_keyblock(optarg);
+			if (!kblock) {
+				ERROR("Could not read %s\n", optarg);
+				errorcount++;
+			}
+			break;
+		case 'p':
+			plat_privk = vb2_read_private_key(optarg);
+			if (!plat_privk) {
+				ERROR("Could not read %s\n", optarg);
+				errorcount++;
+			}
+			break;
+		case 'h':
+			printf("%s", usage);
+			return 0;
+		case '?':
+			if (optopt)
+				ERROR("Unrecognized option: -%c\n", optopt);
+			else
+				ERROR("Unrecognized option: %s\n",
+				      argv[optind - 1]);
+			errorcount++;
+			break;
+		case ':':
+			ERROR("Missing argument to -%c\n", optopt);
+			errorcount++;
+			break;
+		case 0: /* handled option */
+			break;
+		default:
+			FATAL("Unrecognized getopt output: %d\n", i);
+		}
+	}
+
+	if ((optind == 1) && (argc > 1))
+		/* This must be a validation request. */
+		return validate_gscvd(argc - 1, argv + 1);
+
+	if (optind != (argc - 1)) {
+		ERROR("Misformatted command line\n%s\n", usage);
+		return 1;
+	}
+
+	if (errorcount || !ranges.range_count || !root_pubk || !kblock ||
+	    !plat_privk || (board_id == UINT32_MAX)) {
+		/* Error message(s) should have been printed by now. */
+		ERROR("%s\n", usage);
+		return 1;
+	}
+
+	infile = argv[optind];
+
+	if (outfile) {
+		futil_copy_file_or_die(infile, outfile);
+		work_file = outfile;
+	} else {
+		work_file = infile;
+	}
+
+	do {
+		rv = 1; /* Speculative, will be cleared on success. */
+
+		if (validate_pubk_signature(root_pubk, kblock))
+			break;
+
+		if (validate_privk(kblock, plat_privk))
+			break;
+
+		if (load_ap_firmware(work_file, &ap_firmware_file))
+			break;
+
+		if (verify_ranges(&ranges, &ap_firmware_file))
+			break;
+
+		gvd = create_gvd(&ap_firmware_file, &ranges,
+				 root_pubk, plat_privk, board_id);
+		if (!gvd)
+			break;
+
+		if (fill_gvd_area(&ap_firmware_file, gvd, kblock))
+			break;
+
+		dump_pubk_hash(root_pubk);
+
+		rv = 0;
+	} while (false);
+
+	free(gvd);
+	free(root_pubk);
+	free(kblock);
+	vb2_private_key_free(plat_privk);
+
+	/* Now flush the file. */
+	if (ap_firmware_file.data) {
+		rv |= futil_unmap_file(ap_firmware_file.fd, true,
+				       ap_firmware_file.data,
+				       ap_firmware_file.len);
+	}
+
+	if (ap_firmware_file.fd != -1)
+		close(ap_firmware_file.fd);
+
+	return rv;
+}
+
+DECLARE_FUTIL_COMMAND(gscvd, do_gscvd, VBOOT_VERSION_2_1,
+		      "Create RO verification structure");
diff --git a/futility/cmd_pcr.c b/futility/cmd_pcr.c
index dc4c3ff..e267b24 100644
--- a/futility/cmd_pcr.c
+++ b/futility/cmd_pcr.c
@@ -40,57 +40,6 @@
 	printf(usage, argv[0], argv[0], argv[0]);
 }
 
-static int parse_hex(uint8_t *val, const char *str)
-{
-	uint8_t v = 0;
-	char c;
-	int digit;
-
-	for (digit = 0; digit < 2; digit++) {
-		c = *str;
-		if (!c)
-			return 0;
-		if (!isxdigit(c))
-			return 0;
-		c = tolower(c);
-		if (c >= '0' && c <= '9')
-			v += c - '0';
-		else
-			v += 10 + c - 'a';
-		if (!digit)
-			v <<= 4;
-		str++;
-	}
-
-	*val = v;
-	return 1;
-}
-
-static void parse_digest_or_die(uint8_t *buf, int len, const char *str)
-{
-	const char *s = str;
-	int i;
-
-	for (i = 0; i < len; i++) {
-		/* skip whitespace */
-		while (*s && isspace(*s))
-			s++;
-		if (!*s)
-			break;
-		if (!parse_hex(buf, s))
-			break;
-
-		/* on to the next byte */
-		s += 2;
-		buf++;
-	}
-
-	if (i != len) {
-		fprintf(stderr, "Invalid DIGEST \"%s\"\n", str);
-		exit(1);
-	}
-}
-
 static void print_digest(const uint8_t *buf, int len)
 {
 	int i;
diff --git a/futility/cmd_update.c b/futility/cmd_update.c
index 57fa083..e4b3765 100644
--- a/futility/cmd_update.c
+++ b/futility/cmd_update.c
@@ -13,6 +13,8 @@
 #include "futility.h"
 #include "updater.h"
 
+#ifdef USE_FLASHROM
+
 enum {
 	OPT_DUMMY = 0x100,
 
@@ -358,6 +360,17 @@
 	updater_delete_config(cfg);
 	return !!errorcnt;
 }
+#define CMD_HELP_STR "Update system firmware"
 
-DECLARE_FUTIL_COMMAND(update, do_update, VBOOT_VERSION_ALL,
-		      "Update system firmware");
+#else /* USE_FLASHROM */
+
+static int do_update(int argc, char *argv[])
+{
+	FATAL(MYNAME " was built without flashrom support, `update` command unavailable!\n");
+	return -1;
+}
+#define CMD_HELP_STR "Update system firmware (unavailable in this build)"
+
+#endif /* !USE_FLASHROM */
+
+DECLARE_FUTIL_COMMAND(update, do_update, VBOOT_VERSION_ALL, CMD_HELP_STR);
diff --git a/futility/flashrom_wp_drv.c b/futility/flashrom_wp_drv.c
new file mode 100644
index 0000000..ff7a821
--- /dev/null
+++ b/futility/flashrom_wp_drv.c
@@ -0,0 +1,50 @@
+/* Copyright 2021 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.
+ *
+ * The utility functions for firmware updater.
+ */
+
+#include <libflashrom.h>
+
+#include "updater.h"
+
+#define FLASHROM_OUTPUT_WP_PATTERN "write protect is "
+
+/* System environment values. */
+static const char * const FLASHROM_OUTPUT_WP_ENABLED =
+			  FLASHROM_OUTPUT_WP_PATTERN "enabled",
+		  * const FLASHROM_OUTPUT_WP_DISABLED =
+			  FLASHROM_OUTPUT_WP_PATTERN "disabled";
+
+
+/* Helper function to return write protection status via given programmer. */
+enum wp_state flashrom_get_wp(const char *programmer)
+{
+	char *command, *result;
+	const char *postfix;
+	int r;
+
+	/* grep is needed because host_shell only returns 1 line. */
+	postfix = " 2>/dev/null | grep \"" FLASHROM_OUTPUT_WP_PATTERN "\"";
+
+
+	/* TODO(b/203715651): link with flashrom directly. */
+	ASPRINTF(&command, "flashrom --wp-status -p %s %s", programmer, postfix);
+
+	/* invokes flashrom(8) with non-zero result if error. */
+	result = host_shell(command);
+	strip_string(result, NULL);
+	free(command);
+	VB2_DEBUG("wp-status: %s\n", result);
+
+	if (strstr(result, FLASHROM_OUTPUT_WP_ENABLED))
+		r = WP_ENABLED;
+	else if (strstr(result, FLASHROM_OUTPUT_WP_DISABLED))
+		r = WP_DISABLED;
+	else
+		r = WP_ERROR;
+	free(result);
+
+	return r;
+}
diff --git a/futility/futility.h b/futility/futility.h
index 1f34713..9d40ba7 100644
--- a/futility/futility.h
+++ b/futility/futility.h
@@ -140,6 +140,12 @@
 enum futil_file_err futil_unmap_file(int fd, int writeable,
 				     uint8_t *buf, uint32_t len);
 
+/*
+ * Parse input string as a hex representation of size len, exit with error if
+ *  the string is not a valid hex string or is of a wrongs size.
+ */
+void parse_digest_or_die(uint8_t *buf, int len, const char *str);
+
 /* The CPU architecture is occasionally important */
 enum arch_t {
 	ARCH_UNSPECIFIED,
diff --git a/futility/misc.c b/futility/misc.c
index f3e7748..76b8ad1 100644
--- a/futility/misc.c
+++ b/futility/misc.c
@@ -352,3 +352,54 @@
 
 	return FILE_TYPE_CHROMIUMOS_DISK;
 }
+
+static int parse_hex(uint8_t *val, const char *str)
+{
+	uint8_t v = 0;
+	char c;
+	int digit;
+
+	for (digit = 0; digit < 2; digit++) {
+		c = *str;
+		if (!c)
+			return 0;
+		if (!isxdigit(c))
+			return 0;
+		c = tolower(c);
+		if (c >= '0' && c <= '9')
+			v += c - '0';
+		else
+			v += 10 + c - 'a';
+		if (!digit)
+			v <<= 4;
+		str++;
+	}
+
+	*val = v;
+	return 1;
+}
+
+void parse_digest_or_die(uint8_t *buf, int len, const char *str)
+{
+	const char *s = str;
+	int i;
+
+	for (i = 0; i < len; i++) {
+		/* skip whitespace */
+		while (*s && isspace(*s))
+			s++;
+		if (!*s)
+			break;
+		if (!parse_hex(buf, s))
+			break;
+
+		/* on to the next byte */
+		s += 2;
+		buf++;
+	}
+
+	if ((i != len) || *s) {
+		fprintf(stderr, "Invalid DIGEST \"%s\"\n", str);
+		exit(1);
+	}
+}
diff --git a/futility/updater.c b/futility/updater.c
index 87ac6fd..06a696c 100644
--- a/futility/updater.c
+++ b/futility/updater.c
@@ -275,7 +275,7 @@
 static int set_try_cookies(struct updater_config *cfg, const char *target,
 			   int has_update, int is_vboot2)
 {
-	int tries = 6;
+	int tries = 8;
 	const char *slot;
 
 	/* EC Software Sync needs few more reboots. */
@@ -334,6 +334,10 @@
 	struct firmware_section from, to;
 	int errorcnt = 0;
 
+	INFO("(emulation) Writing %s from %s to %s (emu=%s).\n",
+	     section_name ? section_name : "the whole image",
+	     image->file_name, image->programmer, filename);
+
 	from.data = image->data;
 	from.size = image->size;
 
@@ -384,30 +388,80 @@
 }
 
 /*
- * Writes a section from given firmware image to system firmware.
- * If section_name is NULL, write whole image.
+ * Returns the number of retries when reading or writing to flash.
+ */
+static int get_io_retries(struct updater_config *cfg)
+{
+	return 1 + get_config_quirk(QUIRK_EXTRA_RETRIES, cfg);
+}
+
+/*
+ * Returns 1 if the programmers in image1 and image2 are the same.
+ */
+static int is_the_same_programmer(const struct firmware_image *image1,
+				  const struct firmware_image *image2)
+{
+	assert(image1 && image2);
+
+	/* Including if both are NULL. */
+	if (image1->programmer == image2->programmer)
+		return 1;
+
+	/* Not the same if either one is NULL. */
+	if (!image1->programmer || !image2->programmer)
+		return 0;
+
+	return strcmp(image1->programmer, image2->programmer) == 0;
+}
+
+/*
+ * Writes multiple sections from the given firmware image to the system.
+ * The 'sections' should be NULL (write the whole image) or a list of section
+ * names to write, and ended with NULL.
+ * Returns 0 if success, non-zero if error.
+ */
+static int write_firmware_sections(struct updater_config *cfg,
+				   const struct firmware_image *image,
+				   const char * const sections[])
+{
+	int r = 0;
+	struct firmware_image *diff_image = NULL;
+
+	if (cfg->emulation) {
+		int i;
+		if (!sections)
+			return emulate_write_firmware(
+					cfg->emulation, image, NULL);
+		for (i = 0; sections[i] && !r; i++) {
+			r |= emulate_write_firmware(
+					cfg->emulation, image, sections[i]);
+		}
+		return r;
+	}
+
+	if (cfg->use_diff_image && cfg->image_current.data &&
+	    is_the_same_programmer(&cfg->image_current, image))
+		diff_image = &cfg->image_current;
+
+	return write_system_firmware(image, diff_image, sections,
+				     &cfg->tempfiles, cfg->do_verify,
+				     get_io_retries(cfg), cfg->verbosity + 1);
+}
+
+/*
+ * Writes a single section from the given firmware image to the system.
+ * Writes the whole firmware image if the section_name is NULL.
  * Returns 0 if success, non-zero if error.
  */
 static int write_firmware(struct updater_config *cfg,
 			  const struct firmware_image *image,
 			  const char *section_name)
 {
-	struct firmware_image *diff_image = NULL;
+	const char *sections[2] = {0};
 
-	if (cfg->emulation) {
-		INFO("(emulation) Writing %s from %s to %s (emu=%s).\n",
-		     section_name ? section_name : "whole image",
-		     image->file_name, image->programmer, cfg->emulation);
-
-		return emulate_write_firmware(
-				cfg->emulation, image, section_name);
-	}
-
-	if (cfg->fast_update && image == &cfg->image && cfg->image_current.data)
-		diff_image = &cfg->image_current;
-
-	return write_system_firmware(image, diff_image, section_name,
-				     &cfg->tempfiles, cfg->verbosity + 1);
+	sections[0] = section_name;
+	return write_firmware_sections(cfg, image,
+				       section_name ? sections : NULL);
 }
 
 /*
@@ -458,7 +512,7 @@
 	 */
 	if (check_programmer_wp &&
 	    get_system_property(SYS_PROP_WP_HW, cfg) == WP_ENABLED &&
-	    host_get_wp(image->programmer) == WP_ENABLED) {
+	    flashrom_get_wp(image->programmer) == WP_ENABLED) {
 		ERROR("Target %s is write protected, skip updating.\n",
 		      image->programmer);
 		return 0;
@@ -481,19 +535,27 @@
 	const struct vb2_gbb_header *gbb_from;
 	struct vb2_gbb_header *gbb_to;
 
-	gbb_from = find_gbb(image_from);
-	/* We do want to change GBB contents later. */
+	/* Cast to non-const because we do want to change GBB contents later. */
 	gbb_to = (struct vb2_gbb_header *)find_gbb(image_to);
 
-	if (!gbb_from || !gbb_to)
+	/*
+	 * For all cases, we need a valid gbb_to. Note for 'override GBB flags
+	 * on a erased device', we only need gbb_to, not gbb_from.
+	 */
+	if (!gbb_to)
 		return -1;
 
+	gbb_from = find_gbb(image_from);
+
 	/* Preserve (for non-factory mode) or override flags. */
 	if (override_flags)
 		gbb_to->flags = override_value;
-	else if (preserve_flags)
+	else if (preserve_flags && gbb_from)
 		gbb_to->flags = gbb_from->flags;
 
+	if (!gbb_from)
+		return -1;
+
 	/* Preserve HWID. */
 	return futil_set_gbb_hwid(
 			gbb_to, (const char *)gbb_from + gbb_from->hwid_offset);
@@ -520,11 +582,16 @@
 				image_from, image_to, FMAP_SI_DESC);
 	}
 
-	if (try_apply_quirk(QUIRK_PRESERVE_ME, cfg) > 0) {
-		VB2_DEBUG("ME needs to be preserved - preserving %s.\n",
-			  FMAP_SI_ME);
-		return preserve_firmware_section(
-				image_from, image_to, FMAP_SI_ME);
+	if (!strcmp(image_from->programmer, PROG_HOST)) {
+		if (try_apply_quirk(QUIRK_PRESERVE_ME, cfg) > 0) {
+			VB2_DEBUG("ME needs to be preserved - preserving %s.\n",
+				  FMAP_SI_ME);
+			return preserve_firmware_section(image_from, image_to,
+							 FMAP_SI_ME);
+		}
+	} else {
+		VB2_DEBUG("Flashing via non-host programmer %s - no need to "
+			  "preserve ME.\n", image_from->programmer);
 	}
 
 	return try_apply_quirk(QUIRK_UNLOCK_ME_FOR_UPDATE, cfg);
@@ -866,16 +933,18 @@
 	int has_from, has_to;
 	const char * const tag = "cros_allow_auto_update";
 	const char *section = FMAP_RW_LEGACY;
-	const char *tmp_path;
+	const char *tmp_to, *tmp_from;
 
 	VB2_DEBUG("Checking %s contents...\n", FMAP_RW_LEGACY);
 
-	tmp_path = get_firmware_image_temp_file(&cfg->image, &cfg->tempfiles);
-	if (!tmp_path)
+	tmp_to = get_firmware_image_temp_file(&cfg->image, &cfg->tempfiles);
+	tmp_from = get_firmware_image_temp_file(&cfg->image_current,
+						&cfg->tempfiles);
+	if (!tmp_from || !tmp_to)
 		return 0;
 
-	has_to = cbfs_file_exists(tmp_path, section, tag);
-	has_from = cbfs_file_exists(tmp_path, section, tag);
+	has_to = cbfs_file_exists(tmp_to, section, tag);
+	has_from = cbfs_file_exists(tmp_from, section, tag);
 
 	if (!has_from || !has_to) {
 		VB2_DEBUG("Current legacy firmware has%s updater tag (%s) and "
@@ -1075,6 +1144,15 @@
 		struct firmware_image *image_from,
 		struct firmware_image *image_to)
 {
+	int sections_start = 0;
+	static const char * const sections[] = {
+		FMAP_RW_LEGACY,
+		FMAP_RW_SECTION_A,
+		FMAP_RW_SECTION_B,
+		FMAP_RW_SHARED,
+		NULL,
+	};
+
 	STATUS("RW UPDATE: Updating RW sections (%s, %s, %s, and %s).\n",
 	       FMAP_RW_SECTION_A, FMAP_RW_SECTION_B, FMAP_RW_SHARED,
 	       FMAP_RW_LEGACY);
@@ -1084,14 +1162,18 @@
 		return UPDATE_ERR_ROOT_KEY;
 	if (check_compatible_tpm_keys(cfg, image_to))
 		return UPDATE_ERR_TPM_ROLLBACK;
+
 	/*
-	 * TODO(hungte) Speed up by flashing multiple sections in one
-	 * command, or provide diff file.
+	 * We may also consider only updating legacy if legacy_needs_update()
+	 * returns true. However, given this is for 'recovery', it is probably
+	 * better to restore everything to the default states. We may revisit
+	 * this if a new scenario is found.
 	 */
-	if (write_firmware(cfg, image_to, FMAP_RW_SECTION_A) ||
-	    write_firmware(cfg, image_to, FMAP_RW_SECTION_B) ||
-	    write_firmware(cfg, image_to, FMAP_RW_SHARED) ||
-	    write_optional_firmware(cfg, image_to, FMAP_RW_LEGACY, 0, 1))
+	if (!firmware_section_exists(image_from, sections[sections_start]) ||
+	    !firmware_section_exists(image_to, sections[sections_start]))
+		sections_start++;
+
+	if (write_firmware_sections(cfg, image_to, &sections[sections_start]))
 		return UPDATE_ERR_WRITE_FIRMWARE;
 
 	return UPDATE_ERR_DONE;
@@ -1187,6 +1269,7 @@
 	     image_to->file_name, image_to->ro_version,
 	     image_to->rw_version_a, image_to->rw_version_b);
 
+	try_apply_quirk(QUIRK_NO_VERIFY, cfg);
 	if (try_apply_quirk(QUIRK_MIN_PLATFORM_VERSION, cfg)) {
 		if (!cfg->force_update) {
 			ERROR("Add --force to waive checking the version.\n");
@@ -1195,9 +1278,10 @@
 	}
 	if (!image_from->data) {
 		int ret;
+
 		INFO("Loading current system firmware...\n");
 		ret = load_system_firmware(image_from, &cfg->tempfiles,
-					   cfg->verbosity);
+					   get_io_retries(cfg), cfg->verbosity);
 		if (ret == IMAGE_PARSE_FAILURE && cfg->force_update) {
 			WARN("No compatible firmware in system.\n");
 			cfg->check_platform = 0;
@@ -1272,6 +1356,7 @@
 	cfg->pd_image.programmer = PROG_PD;
 
 	cfg->check_platform = 1;
+	cfg->do_verify = 1;
 
 	init_system_properties(&cfg->system_properties[0],
 			       ARRAY_SIZE(cfg->system_properties));
@@ -1376,17 +1461,15 @@
 
 	assert(model->is_white_label);
 	if (!signature_id) {
-		if (cfg->image_current.data) {
-			tmp_image = get_firmware_image_temp_file(
-					&cfg->image_current, &cfg->tempfiles);
-			if (!tmp_image)
-				return 1;
-		} else {
+		if (!cfg->image_current.data) {
 			INFO("Loading system firmware for white label...\n");
 			load_system_firmware(&cfg->image_current,
-					     &cfg->tempfiles, cfg->verbosity);
-			tmp_image = cfg->image_current.file_name;
+					     &cfg->tempfiles,
+					     get_io_retries(cfg),
+					     cfg->verbosity);
 		}
+		tmp_image = get_firmware_image_temp_file(
+				&cfg->image_current, &cfg->tempfiles);
 		if (!tmp_image) {
 			ERROR("Failed to get system current firmware\n");
 			return 1;
@@ -1475,7 +1558,8 @@
 
 	/* Setup values that may change output or decision of other argument. */
 	cfg->verbosity = arg->verbosity;
-	cfg->fast_update = arg->fast_update;
+	cfg->use_diff_image = arg->fast_update;
+	cfg->do_verify = !arg->fast_update;
 	cfg->factory_update = arg->is_factory;
 	if (arg->force_update)
 		cfg->force_update = 1;
@@ -1626,7 +1710,7 @@
 		errorcnt += !!setup_config_quirks(arg->quirks, cfg);
 
 	/* Additional checks. */
-	if (check_single_image && (cfg->ec_image.data || cfg->pd_image.data)) {
+	if (check_single_image && !do_output && (cfg->ec_image.data || cfg->pd_image.data)) {
 		errorcnt++;
 		ERROR("EC/PD images are not supported in current mode.\n");
 	}
diff --git a/futility/updater.h b/futility/updater.h
index 69ba5e2..6dda928 100644
--- a/futility/updater.h
+++ b/futility/updater.h
@@ -14,6 +14,7 @@
 /* FMAP section names. */
 static const char * const FMAP_RO_FRID = "RO_FRID",
 		  * const FMAP_RO_SECTION = "RO_SECTION",
+		  * const FMAP_RO_CBFS = "COREBOOT",
 		  * const FMAP_RO_GBB = "GBB",
 		  * const FMAP_RW_VBLOCK_A = "VBLOCK_A",
 		  * const FMAP_RW_VBLOCK_B = "VBLOCK_B",
@@ -46,6 +47,8 @@
 	QUIRK_OVERRIDE_SIGNATURE_ID,
 	QUIRK_PRESERVE_ME,
 	QUIRK_NO_CHECK_PLATFORM,
+	QUIRK_NO_VERIFY,
+	QUIRK_EXTRA_RETRIES,
 	QUIRK_MAX,
 };
 
@@ -68,7 +71,8 @@
 	int legacy_update;
 	int factory_update;
 	int check_platform;
-	int fast_update;
+	int use_diff_image;
+	int do_verify;
 	int verbosity;
 	const char *emulation;
 	int override_gbb_flags;
diff --git a/futility/updater_archive.c b/futility/updater_archive.c
index 4ad5287..9c3a608 100644
--- a/futility/updater_archive.c
+++ b/futility/updater_archive.c
@@ -30,6 +30,10 @@
 #include <zip.h>
 #endif
 
+#ifdef HAVE_CROSID
+#include <crosid.h>
+#endif
+
 #include "host_misc.h"
 #include "updater.h"
 #include "util_misc.h"
@@ -815,6 +819,30 @@
 	return !manifest_add_model(manifest, &model);
 }
 
+/**
+ * get_manifest_key() - Wrapper to get the firmware manifest key from crosid
+ *
+ * @manifest_key_out - Output parameter of the firmware manifest key.
+ *
+ * Returns:
+ * - <0 if libcrosid is unavailable or there was an error reading
+ *   device data
+ * - >=0 (the matched device index) success
+ */
+static int get_manifest_key(char **manifest_key_out)
+{
+#ifdef HAVE_CROSID
+	return crosid_get_firmware_manifest_key(manifest_key_out);
+#else
+	ERROR("This version of futility was compiled without libcrosid "
+	      "(perhaps compiled outside of the Chrome OS build system?) and "
+	      "the update command is not fully supported.  Either compile "
+	      "from the Chrome OS build, or pass --model to manually specify "
+	      "the machine model.\n");
+	return -1;
+#endif
+}
+
 /*
  * Finds the existing model_config from manifest that best matches current
  * system (as defined by model_name).
@@ -823,9 +851,10 @@
 const struct model_config *manifest_find_model(const struct manifest *manifest,
 					       const char *model_name)
 {
-	char *sys_model_name = NULL;
+	char *manifest_key = NULL;
 	const struct model_config *model = NULL;
 	int i;
+	int matched_index;
 
 	/*
 	 * For manifest with single model defined, we should just return because
@@ -836,9 +865,18 @@
 		return &manifest->models[0];
 
 	if (!model_name) {
-		sys_model_name = host_shell("mosys platform model");
-		VB2_DEBUG("System model name: '%s'\n", sys_model_name);
-		model_name = sys_model_name;
+		matched_index = get_manifest_key(&manifest_key);
+		if (matched_index < 0) {
+			ERROR("Failed to get device identity.  "
+			      "Run \"crosid -v\" for explanation.\n");
+			return NULL;
+		}
+
+		INFO("Identified the device using libcrosid, "
+		     "matched chromeos-config index: %d, "
+		     "manifest key (model): %s\n",
+		     matched_index, manifest_key);
+		model_name = manifest_key;
 	}
 
 	for (i = 0; !model && i < manifest->num; i++) {
@@ -846,27 +884,27 @@
 			model = &manifest->models[i];
 	}
 	if (!model) {
-		if (!*model_name)
-			ERROR("Cannot get model name.\n");
-		else
-			ERROR("Unsupported model: '%s'.\n", model_name);
+		ERROR("Unsupported model: '%s'.\n", model_name);
 
 		fprintf(stderr,
-			"You are probably running an image for wrong board, or "
-			"a device in early stage that 'mosys' command is not "
-			"ready, or image from old (or factory) branches that "
-			"Unified Build config is not updated yet for 'mosys'.\n"
-			"Please check command 'mosys platform model', "
-			"which should output one of the supported models below:"
-			"\n");
+			"The firmware manifest key '%s' is not present in this "
+			"updater archive. The known keys to this updater "
+			"archive are:\n", model_name);
 
 		for (i = 0; i < manifest->num; i++)
 			fprintf(stderr, " %s", manifest->models[i].name);
-		fprintf(stderr, "\n");
+		fprintf(stderr, "\n\n");
+		fprintf(stderr,
+			"Perhaps you are trying to use an updater archive for "
+			"the wrong board, or designed for an older OS version "
+			"before this model was supported.\n");
+		fprintf(stderr,
+			"Hint: Read the FIRMWARE_MANIFEST_KEY from the output "
+			"of the crosid command.\n");
 	}
 
 
-	free(sys_model_name);
+	free(manifest_key);
 	return model;
 }
 
diff --git a/futility/updater_quirks.c b/futility/updater_quirks.c
index 1d1ba62..c8140c1 100644
--- a/futility/updater_quirks.c
+++ b/futility/updater_quirks.c
@@ -21,6 +21,14 @@
 	const char * const quirks;
 };
 
+/*
+ * The 'match by firmware name' is now deprecated. Please do not add any
+ * new records below. We now support reading quirks from CBFS, which is
+ * easier and more reliable. To do that, create a text file 'updater_quirks'
+ * and install to the CBFS.
+ *
+ * Examples: CL:*3365287, CL:*3351831, CL:*4441527
+ */
 static const struct quirks_record quirks_records[] = {
 	{ .match = "Google_Whirlwind.", .quirks = "enlarge_image" },
 	{ .match = "Google_Arkham.", .quirks = "enlarge_image" },
@@ -116,10 +124,10 @@
 		ERROR("EC image has invalid section '%s'.\n", "EC_RO");
 		return 1;
 	}
-	ec_ro_path = cbfs_extract_file(tmp_path, FMAP_RO_SECTION, "ecro",
+	ec_ro_path = cbfs_extract_file(tmp_path, FMAP_RO_CBFS, "ecro",
 				       &cfg->tempfiles);
 	if (!ec_ro_path ||
-	    !cbfs_file_exists(tmp_path, FMAP_RO_SECTION, "ecro.hash")) {
+	    !cbfs_file_exists(tmp_path, FMAP_RO_CBFS, "ecro.hash")) {
 		INFO("No valid EC RO for software sync in AP firmware.\n");
 		return 1;
 	}
@@ -331,6 +339,11 @@
  * only RO and expect EC software sync to update RW later, or perform EC RO
  * software sync.
  *
+ * Note: EC RO software sync was not fully tested and may cause problems
+ *       (b/218612817, b/187789991).
+ *       RO-update (without extra sysjump) needs support from flashrom and is
+ *       currently disabled.
+ *
  * Returns:
  *  EC_RECOVERY_FULL to indicate a full recovery is needed.
  *  EC_RECOVERY_RO to indicate partial update (WP_RO) is needed.
@@ -345,23 +358,10 @@
 	 */
 	const char *ec_ro = "WP_RO";
 	struct firmware_image *ec_image = &cfg->ec_image;
-
 	int do_partial = get_config_quirk(QUIRK_EC_PARTIAL_RECOVERY, cfg);
-	if (do_partial == -1) {
-		char arch[VB_MAX_STRING_PROPERTY];
-		/*
-		 * Don't do partial update if can't decide arch (usually implies
-		 * running outside DUT).
-		 */
-		do_partial = 0;
-		if (VbGetSystemPropertyString("arch", arch, sizeof(arch)) > 0) {
-			/* By default disabled for x86, otherwise enabled. */
-			do_partial = !!strcmp(arch, "x86");
-		}
-	}
 
 	if (!do_partial) {
-		return EC_RECOVERY_FULL;
+		/* Need full update. */
 	} else if (!firmware_section_exists(ec_image, ec_ro)) {
 		INFO("EC image does not have section '%s'.\n", ec_ro);
 		/* Need full update. */
@@ -411,6 +411,15 @@
 		return 0;
 	}
 
+	/*
+	 * b/213706510: subratabanik@ confirmed CSE may modify itself while we
+	 * are doing system update, and currently the 'preserve' is done by
+	 * flashing the same (e.g., "previously read") contents to skip erasing
+	 * and writing; so we have to use the diff image to prevent contents
+	 * being changed when writing.
+	 */
+	cfg->use_diff_image = 1;
+
 	return 1;
 }
 
@@ -425,6 +434,16 @@
 }
 
 /*
+ * Disable verifying contents after flashing.
+ */
+static int quirk_no_verify(struct updater_config *cfg)
+{
+	WARN("Disabled verifying flashed contents. You are on your own.\n");
+	cfg->do_verify = 0;
+	return 0;
+}
+
+/*
  * Registers known quirks to a updater_config object.
  */
 void updater_register_quirks(struct updater_config *cfg)
@@ -470,7 +489,6 @@
 	quirks->name = "ec_partial_recovery";
 	quirks->help = "chromium/1024401; recover EC by partial RO update.";
 	quirks->apply = quirk_ec_partial_recovery;
-	quirks->value = -1;  /* Decide at runtime. */
 
 	quirks = &cfg->quirks[QUIRK_OVERRIDE_SIGNATURE_ID];
 	quirks->name = "override_signature_id";
@@ -488,6 +506,16 @@
 	quirks->name = "no_check_platform";
 	quirks->help = "Do not check platform name.";
 	quirks->apply = quirk_no_check_platform;
+
+	quirks = &cfg->quirks[QUIRK_NO_VERIFY];
+	quirks->name = "no_verify";
+	quirks->help = "Do not verify when flashing.";
+	quirks->apply = quirk_no_verify;
+
+	quirks = &cfg->quirks[QUIRK_EXTRA_RETRIES];
+	quirks->name = "extra_retries";
+	quirks->help = "Extra retries when writing to system firmware.";
+	quirks->apply = NULL;  /* Simple config. */
 }
 
 /*
diff --git a/futility/updater_utils.c b/futility/updater_utils.c
index 70ed396..18e4000 100644
--- a/futility/updater_utils.c
+++ b/futility/updater_utils.c
@@ -9,6 +9,7 @@
 #include <limits.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <string.h>
 #include <unistd.h>
 #if defined (__FreeBSD__) || defined(__OpenBSD__)
 #include <sys/wait.h>
@@ -21,20 +22,9 @@
 #include "updater.h"
 
 #define COMMAND_BUFFER_SIZE 256
-#define FLASHROM_OUTPUT_WP_PATTERN "write protect is "
-
-enum flashrom_ops {
-	FLASHROM_READ,
-	FLASHROM_WRITE,
-	FLASHROM_WP_STATUS,
-};
 
 /* System environment values. */
-static const char * const STR_REV = "rev",
-		  * const FLASHROM_OUTPUT_WP_ENABLED =
-			  FLASHROM_OUTPUT_WP_PATTERN "enabled",
-		  * const FLASHROM_OUTPUT_WP_DISABLED =
-			  FLASHROM_OUTPUT_WP_PATTERN "disabled";
+static const char * const STR_REV = "rev";
 
 /*
  * Strips a string (usually from shell execution output) by removing all the
@@ -172,6 +162,45 @@
 	return 0;
 }
 
+static int parse_firmware_image(struct firmware_image *image)
+{
+	int ret = IMAGE_LOAD_SUCCESS;
+	const char *section_a = NULL, *section_b = NULL;
+
+	VB2_DEBUG("Image size: %d\n", image->size);
+	assert(image->data);
+
+	image->fmap_header = fmap_find(image->data, image->size);
+
+	if (!image->fmap_header) {
+		ERROR("Invalid image file (missing FMAP): %s\n", image->file_name);
+		ret = IMAGE_PARSE_FAILURE;
+	}
+
+	if (load_firmware_version(image, FMAP_RO_FRID, &image->ro_version))
+		ret = IMAGE_PARSE_FAILURE;
+
+	if (firmware_section_exists(image, FMAP_RW_FWID_A)) {
+		section_a = FMAP_RW_FWID_A;
+		section_b = FMAP_RW_FWID_B;
+	} else if (firmware_section_exists(image, FMAP_RW_FWID)) {
+		section_a = FMAP_RW_FWID;
+		section_b = FMAP_RW_FWID;
+	} else if (!ret) {
+		ERROR("Unsupported VBoot firmware (no RW ID): %s\n", image->file_name);
+		ret = IMAGE_PARSE_FAILURE;
+	}
+
+	/*
+	 * Load and initialize both RW A and B sections.
+	 * Note some unit tests will create only RW A.
+	 */
+	load_firmware_version(image, section_a, &image->rw_version_a);
+	load_firmware_version(image, section_b, &image->rw_version_b);
+
+	return ret;
+}
+
 /*
  * Loads a firmware image from file.
  * If archive is provided and file_name is a relative path, read the file from
@@ -182,9 +211,6 @@
 int load_firmware_image(struct firmware_image *image, const char *file_name,
 			struct archive *archive)
 {
-	int ret = IMAGE_LOAD_SUCCESS;
-	const char *section_a = NULL, *section_b = NULL;
-
 	if (!file_name) {
 		ERROR("No file name given\n");
 		return IMAGE_READ_FAILURE;
@@ -202,38 +228,9 @@
 		return IMAGE_READ_FAILURE;
 	}
 
-	VB2_DEBUG("Image size: %d\n", image->size);
-	assert(image->data);
 	image->file_name = strdup(file_name);
-	image->fmap_header = fmap_find(image->data, image->size);
 
-	if (!image->fmap_header) {
-		ERROR("Invalid image file (missing FMAP): %s\n", file_name);
-		ret = IMAGE_PARSE_FAILURE;
-	}
-
-	if (load_firmware_version(image, FMAP_RO_FRID, &image->ro_version))
-		ret = IMAGE_PARSE_FAILURE;
-
-	if (firmware_section_exists(image, FMAP_RW_FWID_A)) {
-		section_a = FMAP_RW_FWID_A;
-		section_b = FMAP_RW_FWID_B;
-	} else if (firmware_section_exists(image, FMAP_RW_FWID)) {
-		section_a = FMAP_RW_FWID;
-		section_b = FMAP_RW_FWID;
-	} else if (!ret) {
-		ERROR("Unsupported VBoot firmware (no RW ID): %s\n", file_name);
-		ret = IMAGE_PARSE_FAILURE;
-	}
-
-	/*
-	 * Load and initialize both RW A and B sections.
-	 * Note some unit tests will create only RW A.
-	 */
-	load_firmware_version(image, section_a, &image->rw_version_a);
-	load_firmware_version(image, section_b, &image->rw_version_b);
-
-	return ret;
+	return parse_firmware_image(image);
 }
 
 /*
@@ -462,6 +459,7 @@
 char *host_detect_servo(int *need_prepare_ptr)
 {
 	const char *servo_port = getenv(ENV_SERVOD_PORT);
+	const char *servo_name = getenv(ENV_SERVOD_NAME);
 	char *servo_type = host_shell("dut-control -o servo_type 2>/dev/null");
 	const char *programmer = NULL;
 	char *ret = NULL;
@@ -469,10 +467,11 @@
 	char *servo_serial = NULL;
 
 	/* Get serial name if servo port is provided. */
-	if (servo_port && *servo_port) {
+	if ((servo_port && *servo_port) || (servo_name && *servo_name)) {
 		const char *cmd = "dut-control -o serialname 2>/dev/null";
 
-		VB2_DEBUG("Select servod using port: %s\n", servo_port);
+		VB2_DEBUG("Select servod using port: %s or name: %s\n",
+			  servo_port, servo_name);
 		if (strstr(servo_type, "with_servo_micro"))
 			cmd = ("dut-control -o servo_micro_serialname"
 			       " 2>/dev/null");
@@ -491,8 +490,9 @@
 		VB2_DEBUG("Selected Servo Micro.\n");
 		programmer = "raiden_debug_spi";
 		need_prepare = 1;
-	} else if (strstr(servo_type, "ccd_cr50")) {
-		VB2_DEBUG("Selected CCD CR50.\n");
+	} else if (strstr(servo_type, "ccd_cr50") ||
+		   strstr(servo_type, "ccd_gsc")) {
+		VB2_DEBUG("Selected CCD.\n");
 		programmer = "raiden_debug_spi:target=AP";
 	} else {
 		VB2_DEBUG("Selected Servo V2.\n");
@@ -519,175 +519,86 @@
 }
 
 /*
- * A helper function to invoke flashrom(8) command.
+ * Loads the active system firmware image (usually from SPI flash chip).
  * Returns 0 if success, non-zero if error.
  */
-static int host_flashrom(enum flashrom_ops op, const char *image_path,
-			 const char *programmer, int verbose,
-			 const char *section_name, const char *extra)
+int load_system_firmware(struct firmware_image *image,
+			 struct tempfile *tempfiles,
+			 int retries, int verbosity)
 {
-	char *command, *result;
-	const char *op_cmd, *dash_i = "-i", *postfix = "";
-	int r;
+	int r, i;
 
-	switch (verbose) {
-	case 0:
-		postfix = " >/dev/null 2>&1";
-		break;
-	case 1:
-		break;
-	case 2:
-		postfix = "-V";
-		break;
-	case 3:
-		postfix = "-V -V";
-		break;
-	default:
-		postfix = "-V -V -V";
-		break;
+	INFO("flasrhom -r <IMAGE> -p %s%s\n",
+	     image->programmer,
+	     verbosity ? " -V" : "");
+
+	for (i = 1, r = -1; i <= retries && r != 0; i++) {
+		if (i > 1)
+			WARN("Retry reading firmware (%d/%d)...\n", i, retries);
+		r = flashrom_read_image(image, NULL, verbosity + 1);
 	}
-
-	if (!section_name || !*section_name) {
-		dash_i = "";
-		section_name = "";
-	}
-
-	switch (op) {
-	case FLASHROM_READ:
-		op_cmd = "-r";
-		assert(image_path);
-		break;
-
-	case FLASHROM_WRITE:
-		op_cmd = "-w";
-		assert(image_path);
-		break;
-
-	case FLASHROM_WP_STATUS:
-		op_cmd = "--wp-status";
-		assert(image_path == NULL);
-		image_path = "";
-		/* grep is needed because host_shell only returns 1 line. */
-		postfix = " 2>/dev/null | grep \"" \
-			   FLASHROM_OUTPUT_WP_PATTERN "\"";
-		break;
-
-	default:
-		assert(0);
-		return -1;
-	}
-
-	if (!extra)
-		extra = "";
-
-	/* TODO(hungte) In future we should link with flashrom directly. */
-	ASPRINTF(&command, "flashrom %s %s -p %s %s %s %s %s", op_cmd,
-		 image_path, programmer, dash_i, section_name, extra,
-		 postfix);
-
-	if (verbose)
-		INFO("Executing: %s\n", command);
-
-	if (op != FLASHROM_WP_STATUS) {
-		r = system(command);
-		free(command);
-		if (r)
-			ERROR("Error code: %d\n", r);
-		return r;
-	}
-
-	result = host_shell(command);
-	strip_string(result, NULL);
-	free(command);
-	VB2_DEBUG("wp-status: %s\n", result);
-
-	if (strstr(result, FLASHROM_OUTPUT_WP_ENABLED))
-		r = WP_ENABLED;
-	else if (strstr(result, FLASHROM_OUTPUT_WP_DISABLED))
-		r = WP_DISABLED;
-	else
-		r = WP_ERROR;
-	free(result);
+	if (!r)
+		r = parse_firmware_image(image);
 	return r;
 }
 
-/* Helper function to return write protection status via given programmer. */
-enum wp_state host_get_wp(const char *programmer)
+/*
+ * Writes sections from a given firmware image to the system firmware.
+ * Regions should be NULL for writing the whole image, or a list of
+ * FMAP section names (and ended with a NULL).
+ * Returns 0 if success, non-zero if error.
+ */
+int write_system_firmware(const struct firmware_image *image,
+			  const struct firmware_image *diff_image,
+			  const char * const sections[],
+			  struct tempfile *tempfiles,
+			  int do_verify, int retries, int verbosity)
 {
-	return host_flashrom(FLASHROM_WP_STATUS, NULL, programmer, 0, NULL,
-			     NULL);
+	int r, i, len = 0;
+	char *partial = NULL;
+
+	for (i = 0; sections && sections[i]; i++)
+		len += strlen(sections[i]) + strlen(" -i ");
+	if (len) {
+		partial = (char *)malloc(len + 1);
+		if (!partial) {
+			ERROR("Failed to allocate a string buffer.\n");
+			return -1;
+		}
+		partial[0] = '\0';
+		for (i = 0; sections[i]; i++) {
+			strcat(partial, " -i ");
+			strcat(partial, sections[i]);
+		}
+		assert(strlen(partial) == len);
+	}
+
+	INFO("flashrom -w <IMAGE> -p %s%s%s%s%s\n",
+	     image->programmer,
+	     diff_image ? " --flash-contents <DIFF_IMAGE>" : "",
+	     do_verify ? "" : " --noverify",
+	     verbosity > 1 ? " -V" : "",
+	     partial ? partial : "");
+	free(partial);
+
+	for (i = 1, r = -1; i <= retries && r != 0; i++) {
+		if (i > 1)
+			WARN("Retry writing firmware (%d/%d)...\n", i, retries);
+		r = flashrom_write_image(image, sections, diff_image, do_verify,
+					 verbosity + 1);
+		/*
+		 * Force a newline to flush stdout in case if
+		 * flashrom_write_image left some messages in the buffer.
+		 */
+		fprintf(stdout, "\n");
+	}
+	return r;
 }
 
 /* Helper function to return host software write protection status. */
 static int host_get_wp_sw(void)
 {
-	return host_get_wp(PROG_HOST);
-}
-
-/*
- * Loads the active system firmware image (usually from SPI flash chip).
- * Returns 0 if success, non-zero if error.
- */
-int load_system_firmware(struct firmware_image *image,
-			 struct tempfile *tempfiles, int verbosity)
-{
-	int r;
-	const char *tmp_path = create_temp_file(tempfiles);
-
-	if (!tmp_path)
-		return -1;
-
-	r = host_flashrom(FLASHROM_READ, tmp_path, image->programmer,
-			  verbosity, NULL, NULL);
-	/*
-	 * The verbosity for host_flashrom will be translated to
-	 * (verbosity-1)*'-V', and usually 3*'-V' is enough for debugging.
-	 */
-	const int debug_verbosity = 4;
-	if (r && verbosity < debug_verbosity) {
-		/* Read again, with verbose messages for debugging. */
-		WARN("Failed reading system firmware (%d), try again...\n", r);
-		r = host_flashrom(FLASHROM_READ, tmp_path, image->programmer,
-				  debug_verbosity, NULL, NULL);
-	}
-	if (!r)
-		r = load_firmware_image(image, tmp_path, NULL);
-	return r;
-}
-
-/*
- * Writes a section from given firmware image to system firmware.
- * If section_name is NULL, write whole image.
- * Returns 0 if success, non-zero if error.
- */
-int write_system_firmware(const struct firmware_image *image,
-			  const struct firmware_image *diff_image,
-			  const char *section_name,
-			  struct tempfile *tempfiles,
-			  int verbosity)
-{
-	const char *tmp_path = get_firmware_image_temp_file(image, tempfiles);
-	const char *tmp_diff = NULL;
-
-	const char *programmer = image->programmer;
-	char *extra = NULL;
-	int r;
-
-	if (!tmp_path)
-		return -1;
-
-	if (diff_image) {
-		tmp_diff = get_firmware_image_temp_file(
-				diff_image, tempfiles);
-		if (!tmp_diff)
-			return -1;
-		ASPRINTF(&extra, "--noverify --flash-contents=%s", tmp_diff);
-	}
-
-	r = host_flashrom(FLASHROM_WRITE, tmp_path, programmer, verbosity,
-			  section_name, extra);
-	free(extra);
-	return r;
+	return flashrom_get_wp(PROG_HOST);
 }
 
 /* Helper function to configure all properties. */
diff --git a/futility/updater_utils.h b/futility/updater_utils.h
index 2531675..3eb1d33 100644
--- a/futility/updater_utils.h
+++ b/futility/updater_utils.h
@@ -54,15 +54,8 @@
  */
 void remove_all_temp_files(struct tempfile *head);
 
-/* Utilities for firmware images and (FMAP) sections */
-struct firmware_image {
-	const char *programmer;
-	uint32_t size;
-	uint8_t *data;
-	char *file_name;
-	char *ro_version, *rw_version_a, *rw_version_b;
-	FmapHeader *fmap_header;
-};
+/* Include definition of 'struct firmware_image;' */
+#include "flashrom.h"
 
 enum {
 	IMAGE_LOAD_SUCCESS = 0,
@@ -85,7 +78,8 @@
  * Returns 0 if success, non-zero if error.
  */
 int load_system_firmware(struct firmware_image *image,
-			 struct tempfile *tempfiles, int verbosity);
+			 struct tempfile *tempfiles,
+			 int retries, int verbosity);
 
 /* Frees the allocated resource from a firmware image object. */
 void free_firmware_image(struct firmware_image *image);
@@ -99,15 +93,16 @@
 					 struct tempfile *tempfiles);
 
 /*
- * Writes a section from given firmware image to system firmware.
- * If section_name is NULL, write whole image.
+ * Writes sections from a given firmware image to the system firmware.
+ * Regions should be NULL for writing the whole image, or a list of
+ * FMAP section names (and ended with a NULL).
  * Returns 0 if success, non-zero if error.
  */
 int write_system_firmware(const struct firmware_image *image,
 			  const struct firmware_image *diff_image,
-			  const char *section_name,
+			  const char * const sections[],
 			  struct tempfile *tempfiles,
-			  int verbosity);
+			  int do_verify, int retries, int verbosity);
 
 struct firmware_section {
 	uint8_t *data;
@@ -175,11 +170,14 @@
 };
 
 /* Helper function to return write protection status via given programmer. */
-enum wp_state host_get_wp(const char *programmer);
+enum wp_state flashrom_get_wp(const char *programmer);
 
 /* The environment variable name for setting servod port. */
 #define ENV_SERVOD_PORT	"SERVOD_PORT"
 
+/* The environment variable name for setting servod name. */
+#define ENV_SERVOD_NAME	"SERVOD_NAME"
+
 /*
  * Helper function to detect type of Servo board attached to host.
  * Returns a string as programmer parameter on success, otherwise NULL.
diff --git a/host/arch/x86/lib/crossystem_arch.c b/host/arch/x86/lib/crossystem_arch.c
index e805e2a..d48cf87 100644
--- a/host/arch/x86/lib/crossystem_arch.c
+++ b/host/arch/x86/lib/crossystem_arch.c
@@ -733,6 +733,7 @@
 	/* INTC105x are for Alderlake */
 	{ "INTC1055:00", FindGpioChipOffsetByLabel },
 	{ "INTC1056:00", FindGpioChipOffsetByLabel },
+	{ "INTC1057:00", FindGpioChipOffsetByLabel },
 	/* INT3453 are for GLK */
 	{ "INT3453:00", FindGpioChipOffsetByLabel },
 	{ "INT3453:01", FindGpioChipOffsetByLabel },
diff --git a/host/lib/crossystem.c b/host/lib/crossystem.c
index 02443b1..a5427c2 100644
--- a/host/lib/crossystem.c
+++ b/host/lib/crossystem.c
@@ -4,9 +4,11 @@
  */
 
 #include <ctype.h>
+#include <fcntl.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <string.h>
+#include <sys/file.h>
 #include <unistd.h>
 
 #include "2api.h"
@@ -22,14 +24,12 @@
 #include "subprocess.h"
 #include "vboot_struct.h"
 
+/* Filename for crossystem lock */
+#define CROSSYSTEM_LOCK_PATH "/run/lock/crossystem.lock"
+
 /* Filename for kernel command line */
 #define KERNEL_CMDLINE_PATH "/proc/cmdline"
 
-/* Filename for the mount-encrypted key */
-/* TODO(b/174807059): Remove this after we land driver-level TPM simulator on
- * all VM boards */
-#define MOUNT_ENCRYPTED_KEY_PATH "/mnt/stateful_partition/encrypted.key"
-
 /* Filename for the TPM simulator NV data */
 #define TPM_SIMULATOR_NVCHIP_PATH \
 	"/mnt/stateful_partition/unencrypted/tpm2-simulator/NVChip"
@@ -92,6 +92,32 @@
 	return 0 == strncmp(fwid, start, strlen(start));
 }
 
+/* Acquire the lock for crossystem SetSystemProperty call. */
+static int AcquireCrossystemLock(void)
+{
+	int lock_fd;
+
+	lock_fd = open(CROSSYSTEM_LOCK_PATH, O_RDWR | O_CREAT, 0600);
+	if (lock_fd < 0)
+		return -1;
+
+	if (flock(lock_fd, LOCK_EX) < 0)
+		return -1;
+
+	return lock_fd;
+}
+
+/* Release the lock for crossystem SetSystemProperty call. */
+static int ReleaseCrossystemLock(int lock_fd)
+{
+	if (flock(lock_fd, F_UNLCK) < 0)
+		return -1;
+
+	close(lock_fd);
+
+	return 0;
+}
+
 static struct vb2_context *get_fake_context(void)
 {
 	static uint8_t fake_workbuf[sizeof(struct vb2_shared_data) + 16]
@@ -381,12 +407,9 @@
 	} else if (!strcasecmp(name,"disable_dev_request")) {
 		value = vb2_get_nv_storage(VB2_NV_DISABLE_DEV_REQUEST);
 	} else if (!strcasecmp(name,"clear_tpm_owner_request")) {
-		if (TPM2_SIMULATOR && VTPM_PROXY)
+		if (TPM2_SIMULATOR)
 			/* Check TPM simulator NVChip status */
 			value = access(TPM_SIMULATOR_NVCHIP_PATH, F_OK) != 0;
-		else if (TPM2_SIMULATOR)
-			/* Check mount-encrypted key status */
-			value = access(MOUNT_ENCRYPTED_KEY_PATH, F_OK) != 0;
 		else
 			value = vb2_get_nv_storage(
 				VB2_NV_CLEAR_TPM_OWNER_REQUEST);
@@ -488,8 +511,7 @@
 	return value;
 }
 
-const char *VbGetSystemPropertyString(const char *name, char *dest,
-				      size_t size)
+const char *VbGetSystemPropertyString(const char *name, char *dest, size_t size)
 {
 	/* Check for HWID override via cros_config */
 	if (!strcasecmp(name, "hwid")) {
@@ -552,8 +574,7 @@
 	return NULL;
 }
 
-
-int VbSetSystemPropertyInt(const char *name, int value)
+static int VbSetSystemPropertyIntInternal(const char *name, int value)
 {
 	/* Check architecture-dependent properties first */
 
@@ -581,13 +602,10 @@
 			 * on simulator */
 			if (value == 0)
 				return -1;
-			const char *tpm_path =
-				VTPM_PROXY ? TPM_SIMULATOR_NVCHIP_PATH
-					   : MOUNT_ENCRYPTED_KEY_PATH;
 			/* Check TPM simulator data status */
-			if (!access(tpm_path, F_OK)) {
+			if (!access(TPM_SIMULATOR_NVCHIP_PATH, F_OK)) {
 				/* Remove the TPM2.0 simulator data */
-				return remove(tpm_path);
+				return remove(TPM_SIMULATOR_NVCHIP_PATH);
 			} else {
 				/* Return success when the file is already
 				 * removed */
@@ -673,7 +691,25 @@
 	return -1;
 }
 
-int VbSetSystemPropertyString(const char* name, const char* value)
+int VbSetSystemPropertyInt(const char *name, int value)
+{
+	int result = -1;
+	int lock_fd;
+
+	lock_fd = AcquireCrossystemLock();
+	if (lock_fd < 0)
+		return -1;
+
+	result = VbSetSystemPropertyIntInternal(name, value);
+
+	if (ReleaseCrossystemLock(lock_fd) < 0)
+		return -1;
+
+	return result;
+}
+
+static int VbSetSystemPropertyStringInternal(const char *name,
+					     const char *value)
 {
 	/* Chain to architecture-dependent properties */
 	if (0 == VbSetArchPropertyString(name, value))
@@ -725,6 +761,23 @@
 	return -1;
 }
 
+int VbSetSystemPropertyString(const char *name, const char *value)
+{
+	int result = -1;
+	int lock_fd;
+
+	lock_fd = AcquireCrossystemLock();
+	if (lock_fd < 0)
+		return -1;
+
+	result = VbSetSystemPropertyStringInternal(name, value);
+
+	if (ReleaseCrossystemLock(lock_fd) < 0)
+		return -1;
+
+	return result;
+}
+
 /**
  * Get index of the last valid VBNV entry in an EEPROM.
  *
@@ -768,21 +821,21 @@
 {
 	int index;
 	int vbnv_size = vb2_nv_get_size(ctx);
-	uint8_t *flash_buf;
-	uint32_t flash_size;
 
-	if (flashrom_read(FLASHROM_PROGRAMMER_INTERNAL_AP, VBNV_FMAP_REGION,
-			  &flash_buf, &flash_size))
+	struct firmware_image image = {
+		.programmer = FLASHROM_PROGRAMMER_INTERNAL_AP,
+	};
+	if (flashrom_read(&image, VBNV_FMAP_REGION))
 		return -1;
 
-	index = vb2_nv_index(flash_buf, flash_size, vbnv_size);
+	index = vb2_nv_index(image.data, image.size, vbnv_size);
 	if (index < 0) {
-		free(flash_buf);
+		free(image.data);
 		return -1;
 	}
 
-	memcpy(ctx->nvdata, &flash_buf[index * vbnv_size], vbnv_size);
-	free(flash_buf);
+	memcpy(ctx->nvdata, &image.data[index * vbnv_size], vbnv_size);
+	free(image.data);
 	return 0;
 }
 
@@ -792,34 +845,33 @@
 	int current_index;
 	int next_index;
 	int vbnv_size = vb2_nv_get_size(ctx);
-	uint8_t *flash_buf;
-	uint32_t flash_size;
 
-	if (flashrom_read(FLASHROM_PROGRAMMER_INTERNAL_AP, VBNV_FMAP_REGION,
-			  &flash_buf, &flash_size))
+	struct firmware_image image = {
+		.programmer = FLASHROM_PROGRAMMER_INTERNAL_AP,
+	};
+	if (flashrom_read(&image, VBNV_FMAP_REGION))
 		return -1;
 
-	current_index = vb2_nv_index(flash_buf, flash_size, vbnv_size);
+	current_index = vb2_nv_index(image.data, image.size, vbnv_size);
 	if (current_index < 0) {
 		rv = -1;
 		goto exit;
 	}
 
 	next_index = current_index + 1;
-	if (next_index * vbnv_size == flash_size) {
+	if (next_index * vbnv_size == image.size) {
 		/* VBNV is full.  Erase and write at beginning. */
-		memset(flash_buf, 0xff, flash_size);
+		memset(image.data, 0xff, image.size);
 		next_index = 0;
 	}
 
-	memcpy(&flash_buf[next_index * vbnv_size], ctx->nvdata, vbnv_size);
-	if (flashrom_write(FLASHROM_PROGRAMMER_INTERNAL_AP, VBNV_FMAP_REGION,
-			   flash_buf, flash_size)) {
+	memcpy(&image.data[next_index * vbnv_size], ctx->nvdata, vbnv_size);
+	if (flashrom_write(&image, VBNV_FMAP_REGION)) {
 		rv = -1;
 		goto exit;
 	}
 
  exit:
-	free(flash_buf);
+	free(image.data);
 	return rv;
 }
diff --git a/host/lib/flashrom.c b/host/lib/flashrom.c
index e83dfb2..6ee5971 100644
--- a/host/lib/flashrom.c
+++ b/host/lib/flashrom.c
@@ -103,15 +103,14 @@
 	return VB2_SUCCESS;
 }
 
-vb2_error_t flashrom_read(const char *programmer, const char *region,
-			  uint8_t **data_out, uint32_t *size_out)
+vb2_error_t flashrom_read(struct firmware_image *image, const char *region)
 {
 	char *tmpfile;
 	char region_param[PATH_MAX];
 	vb2_error_t rv;
 
-	*data_out = NULL;
-	*size_out = 0;
+	image->data = NULL;
+	image->size = 0;
 
 	VB2_TRY(write_temp_file(NULL, 0, &tmpfile));
 
@@ -122,7 +121,7 @@
 	const char *const argv[] = {
 		FLASHROM_EXEC_NAME,
 		"-p",
-		programmer,
+		image->programmer,
 		"-r",
 		region ? "-i" : tmpfile,
 		region ? region_param : NULL,
@@ -131,21 +130,20 @@
 
 	rv = run_flashrom(argv);
 	if (rv == VB2_SUCCESS)
-		rv = vb2_read_file(tmpfile, data_out, size_out);
+		rv = vb2_read_file(tmpfile, &image->data, &image->size);
 
 	unlink(tmpfile);
 	free(tmpfile);
 	return rv;
 }
 
-vb2_error_t flashrom_write(const char *programmer, const char *region,
-			   uint8_t *data, uint32_t size)
+vb2_error_t flashrom_write(struct firmware_image *image, const char *region)
 {
 	char *tmpfile;
 	char region_param[PATH_MAX];
 	vb2_error_t rv;
 
-	VB2_TRY(write_temp_file(data, size, &tmpfile));
+	VB2_TRY(write_temp_file(image->data, image->size, &tmpfile));
 
 	if (region)
 		snprintf(region_param, sizeof(region_param), "%s:%s", region,
@@ -154,7 +152,7 @@
 	const char *const argv[] = {
 		FLASHROM_EXEC_NAME,
 		"-p",
-		programmer,
+		image->programmer,
 		"--noverify-all",
 		"-w",
 		region ? "-i" : tmpfile,
diff --git a/host/lib/flashrom_drv.c b/host/lib/flashrom_drv.c
new file mode 100644
index 0000000..1d9e3fd
--- /dev/null
+++ b/host/lib/flashrom_drv.c
@@ -0,0 +1,212 @@
+/* Copyright 2021 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.
+ *
+ * The utility functions for firmware updater.
+ */
+
+#include <libflashrom.h>
+
+#include "2common.h"
+#include "crossystem.h"
+#include "host_misc.h"
+#include "util_misc.h"
+//#include "updater.h"
+#include "../../futility/futility.h"
+#include "flashrom.h"
+
+// global to allow verbosity level to be injected into callback.
+static enum flashrom_log_level g_verbose_screen = FLASHROM_MSG_INFO;
+
+static int flashrom_print_cb(enum flashrom_log_level level, const char *fmt,
+			     va_list ap)
+{
+	int ret = 0;
+	FILE *output_type = (level < FLASHROM_MSG_INFO) ? stderr : stdout;
+
+	if (level > g_verbose_screen)
+		return ret;
+
+	ret = vfprintf(output_type, fmt, ap);
+	/* msg_*spew often happens inside chip accessors
+	 * in possibly time-critical operations.
+	 * Don't slow them down by flushing.
+	 */
+	if (level != FLASHROM_MSG_SPEW)
+		fflush(output_type);
+
+	return ret;
+}
+
+static char *flashrom_extract_params(const char *str, char **prog, char **params)
+{
+	char *tmp = strdup(str);
+	*prog = strtok(tmp, ":");
+	*params = strtok(NULL, "");
+	return tmp;
+}
+
+int flashrom_read_image(struct firmware_image *image, const char *region,
+			int verbosity)
+{
+	int r = 0;
+	size_t len = 0;
+
+	g_verbose_screen = (verbosity == -1) ? FLASHROM_MSG_INFO : verbosity;
+
+	char *programmer, *params;
+	char *tmp = flashrom_extract_params(image->programmer, &programmer, &params);
+
+	struct flashrom_programmer *prog = NULL;
+	struct flashrom_flashctx *flashctx = NULL;
+	struct flashrom_layout *layout = NULL;
+
+	flashrom_set_log_callback((flashrom_log_callback *)&flashrom_print_cb);
+
+	if (flashrom_init(1)
+		|| flashrom_programmer_init(&prog, programmer, params)) {
+		r = -1;
+		goto err_init;
+	}
+	if (flashrom_flash_probe(&flashctx, prog, NULL)) {
+		r = -1;
+		goto err_probe;
+	}
+
+	len = flashrom_flash_getsize(flashctx);
+
+	if (region) {
+		r = flashrom_layout_read_fmap_from_buffer(
+			&layout, flashctx, (const uint8_t *)image->data,
+			image->size);
+		if (r > 0) {
+			WARN("could not read fmap from image, r=%d, "
+				"falling back to read from rom\n", r);
+			r = flashrom_layout_read_fmap_from_rom(
+				&layout, flashctx, 0, len);
+			if (r > 0) {
+				ERROR("could not read fmap from rom, r=%d\n", r);
+				r = -1;
+				goto err_cleanup;
+			}
+		}
+		// empty region causes seg fault in API.
+		r |= flashrom_layout_include_region(layout, region);
+		if (r > 0) {
+			ERROR("could not include region = '%s'\n", region);
+			r = -1;
+			goto err_cleanup;
+		}
+		flashrom_layout_set(flashctx, layout);
+	}
+
+	image->data = calloc(1, len);
+	image->size = len;
+	image->file_name = strdup("<sys-flash>");
+
+	r |= flashrom_image_read(flashctx, image->data, len);
+
+err_cleanup:
+	flashrom_layout_release(layout);
+	flashrom_flash_release(flashctx);
+
+err_probe:
+	r |= flashrom_programmer_shutdown(prog);
+
+err_init:
+	free(tmp);
+	return r;
+}
+
+int flashrom_write_image(const struct firmware_image *image,
+			const char * const regions[],
+			const struct firmware_image *diff_image,
+			int do_verify, int verbosity)
+{
+	int r = 0;
+	size_t len = 0;
+
+	g_verbose_screen = (verbosity == -1) ? FLASHROM_MSG_INFO : verbosity;
+
+	char *programmer, *params;
+	char *tmp = flashrom_extract_params(image->programmer, &programmer, &params);
+
+	struct flashrom_programmer *prog = NULL;
+	struct flashrom_flashctx *flashctx = NULL;
+	struct flashrom_layout *layout = NULL;
+
+	flashrom_set_log_callback((flashrom_log_callback *)&flashrom_print_cb);
+
+	if (flashrom_init(1)
+		|| flashrom_programmer_init(&prog, programmer, params)) {
+		r = -1;
+		goto err_init;
+	}
+	if (flashrom_flash_probe(&flashctx, prog, NULL)) {
+		r = -1;
+		goto err_probe;
+	}
+
+	len = flashrom_flash_getsize(flashctx);
+	if (len == 0) {
+		ERROR("zero sized flash detected\n");
+		r = -1;
+		goto err_cleanup;
+	}
+
+	if (diff_image) {
+		if (diff_image->size != image->size) {
+			ERROR("diff_image->size != image->size");
+			r = -1;
+			goto err_cleanup;
+		}
+	}
+
+	if (regions) {
+		int i;
+		r = flashrom_layout_read_fmap_from_buffer(
+			&layout, flashctx, (const uint8_t *)image->data,
+			image->size);
+		if (r > 0) {
+			WARN("could not read fmap from image, r=%d, "
+				"falling back to read from rom\n", r);
+			r = flashrom_layout_read_fmap_from_rom(
+				&layout, flashctx, 0, len);
+			if (r > 0) {
+				ERROR("could not read fmap from rom, r=%d\n", r);
+				r = -1;
+				goto err_cleanup;
+			}
+		}
+		for (i = 0; regions[i]; i++) {
+			// empty region causes seg fault in API.
+			r |= flashrom_layout_include_region(layout, regions[i]);
+			if (r > 0) {
+				ERROR("could not include region = '%s'\n",
+				      regions[i]);
+				r = -1;
+				goto err_cleanup;
+			}
+		}
+		flashrom_layout_set(flashctx, layout);
+	}
+
+	flashrom_flag_set(flashctx, FLASHROM_FLAG_VERIFY_WHOLE_CHIP, true);
+	flashrom_flag_set(flashctx, FLASHROM_FLAG_VERIFY_AFTER_WRITE, true);
+	if (!do_verify)
+		flashrom_flag_set(flashctx, FLASHROM_FLAG_VERIFY_AFTER_WRITE, false);
+
+	r |= flashrom_image_write(flashctx, image->data, image->size,
+				  diff_image ? diff_image->data : NULL);
+
+err_cleanup:
+	flashrom_layout_release(layout);
+	flashrom_flash_release(flashctx);
+
+err_probe:
+	r |= flashrom_programmer_shutdown(prog);
+
+err_init:
+	free(tmp);
+	return r;
+}
diff --git a/host/lib/include/flashrom.h b/host/lib/include/flashrom.h
index 560fbb0..6058d64 100644
--- a/host/lib/include/flashrom.h
+++ b/host/lib/include/flashrom.h
@@ -8,43 +8,54 @@
 #include <stdint.h>
 
 #include "2return_codes.h"
+#include "fmap.h"
 
 #define FLASHROM_PROGRAMMER_INTERNAL_AP "host"
 #define FLASHROM_PROGRAMMER_INTERNAL_EC "ec"
 
+/* Utilities for firmware images and (FMAP) sections */
+struct firmware_image {
+	/**
+	 * programmer	The name of the programmer to use. Use either
+	 *		FLASHROM_PROGRAMMER_INTERNAL_AP or,
+	 *		FLASHROM_PROGRAMMER_INTERNAL_EC
+	 *		for the AP and EC respectively.
+	 */
+	const char *programmer;
+	uint32_t size; /* buffer size. */
+	uint8_t *data; /* data allocated buffer to read/write with. */
+	char *file_name;
+	char *ro_version, *rw_version_a, *rw_version_b;
+	FmapHeader *fmap_header;
+};
+
 /**
  * Read using flashrom into an allocated buffer.
  *
- * @param programmer	The name of the programmer to use.  There are
- *			named constants FLASHROM_PROGRAMMER_INTERNAL_AP
- *			and FLASHROM_PROGRAMMER_INTERNAL_EC available
- *			for the AP and EC respectively, or a custom
- *			programmer string can be provided.
+ * @param image		The parameter that contains the programmer, buffer and
+ *			size to use in the read operation.
  * @param region	The name of the fmap region to read, or NULL to
  *			read the entire flash chip.
- * @param data_out	Output parameter of allocated buffer to read into.
- *			The caller should free the buffer.
- * @param size_out	Output parameter of buffer size.
  *
  * @return VB2_SUCCESS on success, or a relevant error.
  */
-vb2_error_t flashrom_read(const char *programmer, const char *region,
-			  uint8_t **data_out, uint32_t *size_out);
+vb2_error_t flashrom_read(struct firmware_image *image, const char *region);
+int flashrom_read_image(struct firmware_image *image, const char *region,
+			 int verbosity);
 
 /**
  * Write using flashrom from a buffer.
  *
- * @param programmer	The name of the programmer to use.  There are
- *			named constants FLASHROM_PROGRAMMER_INTERNAL_AP
- *			and FLASHROM_PROGRAMMER_INTERNAL_EC available
- *			for the AP and EC respectively, or a custom
- *			programmer string can be provided.
- * @param region	The name of the fmap region to write, or NULL to
- *			write the entire flash chip.
- * @param data		The buffer to write.
- * @param size		The size of the buffer to write.
+ * @param image		The parameter that contains the programmer, buffer and
+ *			size to use in the write operation.
+ * @param regions	A list of the names of the fmap regions to write, or
+ *			NULL to write the entire flash chip. The list must be
+ *			ended with a NULL pointer.
  *
  * @return VB2_SUCCESS on success, or a relevant error.
  */
-vb2_error_t flashrom_write(const char *programmer, const char *region,
-			   uint8_t *data, uint32_t size);
+vb2_error_t flashrom_write(struct firmware_image *image, const char *region);
+int flashrom_write_image(const struct firmware_image *image,
+			const char * const regions[],
+			const struct firmware_image *diff_image,
+			int do_verify, int verbosity);
diff --git a/host/lib/include/gsc_ro.h b/host/lib/include/gsc_ro.h
new file mode 100644
index 0000000..00a4011
--- /dev/null
+++ b/host/lib/include/gsc_ro.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#ifndef __VBOOT_REFERENCE_HOST_LIB_INCLUDE_GSC_RO_H
+#define __VBOOT_REFERENCE_HOST_LIB_INCLUDE_GSC_RO_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "2sha.h"
+
+struct gscvd_ro_range {
+	uint32_t offset;
+	uint32_t size; /* Use uint32_t as opposed to size_to be portable. */
+};
+
+#define GSC_VD_MAGIC 0x65666135 /* Little endian '5 a f e' */
+#define GSC_VD_ROLLBACK_COUNTER 1
+
+struct gsc_verification_data {
+	uint32_t gv_magic;
+	/*
+	 * Size of this structure in bytes, including the ranges array,
+	 * signature and root key bodies.
+	 */
+	uint16_t size;
+	uint16_t major_version; /* Version of this struct layout. Starts at 0 */
+	uint16_t minor_version;
+	/*
+	 * GSC will cache the counter value and will not accept verification
+	 * data blobs with a lower value.
+	 */
+	uint16_t rollback_counter;
+	uint32_t gsc_board_id; /* Locks blob to certain platform. */
+	uint32_t gsc_flags; /* A field for future enhancements. */
+	/*
+	 * The location of fmap that points to this blob. This location must
+	 * also be in one of the verified sections, expressed as offset in
+	 * flash
+	 */
+	uint32_t fmap_location;
+	uint32_t hash_alg; /* one of enum vb2_hash_algorithm alg. */
+	struct vb2_signature sig_header;
+	struct vb2_packed_key root_key_header;
+	/*
+	 * SHAxxx(ranges[0].offset..ranges[0].size || ... ||
+	 *        ranges[n].offset..ranges[n].size)
+	 *
+	 * Let the digest space allow to accommodate the largest possible one.
+	 */
+	uint8_t ranges_digest[VB2_SHA512_DIGEST_SIZE];
+	uint32_t range_count; /* Number of gscvd_ro_range entries. */
+	struct gscvd_ro_range ranges[0];
+};
+
+#endif /* ! __VBOOT_REFERENCE_HOST_LIB_INCLUDE_GSC_RO_H */
diff --git a/rust/OWNERS b/rust/OWNERS
new file mode 100644
index 0000000..fe3921f
--- /dev/null
+++ b/rust/OWNERS
@@ -0,0 +1 @@
+allenwebb@chromium.org
diff --git a/rust/README.md b/rust/README.md
new file mode 100644
index 0000000..3a899ce
--- /dev/null
+++ b/rust/README.md
@@ -0,0 +1,18 @@
+# Rust bindings for vboot_reference
+
+This path contains the vboot_reference-sys crate which uses bindgen to generate
+Rust bindings for the vboot_reference C library.
+
+Each header is included as its own submodule. To use these bindings:
+ * Add `vboot_reference-sys` to your `Cargo.toml` for example:
+```toml
+[dependencies]
+vboot_reference-sys = { path = "../../vboot_reference/rust/vboot_reference-sys" }
+```
+ * Include the symbols you need for example:
+```rust
+use vboot_reference_sys::crossystem::*;
+```
+
+The `build.rs` in `vboot_reference-sys` takes care of adding the necessary
+includes and linker flags for `vboot_host` through the `pkg-config` crate.
diff --git a/rust/vboot_reference-sys/Cargo.toml b/rust/vboot_reference-sys/Cargo.toml
new file mode 100644
index 0000000..59769ee
--- /dev/null
+++ b/rust/vboot_reference-sys/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "vboot_reference-sys"
+version = "1.0.0"
+description = "Provides raw (unsafe) bindings to the vboot_reference C library."
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+build = "build.rs"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+libc = "0.2.44"
+
+[build-dependencies]
+pkg-config = "0.3"
+which = "4.0.0"
diff --git a/rust/vboot_reference-sys/build.rs b/rust/vboot_reference-sys/build.rs
new file mode 100644
index 0000000..523602f
--- /dev/null
+++ b/rust/vboot_reference-sys/build.rs
@@ -0,0 +1,54 @@
+// Copyright 2019 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.
+
+/// Minijail's build script invoked by cargo.
+///
+/// This script prefers linking against a pkg-config provided libminijail, but will fall back to
+/// building libminijail statically.
+use std::env;
+use std::fs::remove_file;
+use std::io;
+use std::path::Path;
+use std::process::Command;
+
+fn bindings_generation() -> io::Result<()> {
+    let bindgen = which::which("bindgen").unwrap();
+
+    let out_dir = env::var("OUT_DIR").unwrap();
+    let gen_file = Path::new(&out_dir).join("./crossystem.rs");
+    if gen_file.exists() {
+        remove_file(&gen_file).expect("Failed to remove generated file.");
+    }
+    let header_dir = Path::new(".");
+    let header_path = header_dir.join("crossystem.h");
+    println!("cargo:rerun-if-changed={}", header_path.display());
+    let status = Command::new(&bindgen)
+        .args(&["--default-enum-style", "rust"])
+        .args(&["--blacklist-type", "__rlim64_t"])
+        .args(&["--raw-line", "pub type __rlim64_t = u64;"])
+        .args(&["--blacklist-type", "__u\\d{1,2}"])
+        .args(&["--raw-line", "pub type __u8 = u8;"])
+        .args(&["--raw-line", "pub type __u16 = u16;"])
+        .args(&["--raw-line", "pub type __u32 = u32;"])
+        .args(&["--blacklist-type", "__uint64_t"])
+        .arg("--no-layout-tests")
+        .arg("--disable-header-comment")
+        .args(&["--output", gen_file.to_str().unwrap()])
+        .arg(header_path.to_str().unwrap())
+        .args(&[
+            "--",
+            "-DUSE_BINDGEN",
+            "-D_FILE_OFFSET_BITS=64",
+            "-D_LARGEFILE_SOURCE",
+            "-D_LARGEFILE64_SOURCE",
+        ])
+        .status()?;
+    assert!(status.success());
+    Ok(())
+}
+
+fn main() -> io::Result<()> {
+    pkg_config::Config::new().probe("vboot_host").unwrap();
+    bindings_generation()
+}
diff --git a/rust/vboot_reference-sys/crossystem.h b/rust/vboot_reference-sys/crossystem.h
new file mode 120000
index 0000000..9672639
--- /dev/null
+++ b/rust/vboot_reference-sys/crossystem.h
@@ -0,0 +1 @@
+../../host/include/crossystem.h
\ No newline at end of file
diff --git a/rust/vboot_reference-sys/lib.rs b/rust/vboot_reference-sys/lib.rs
new file mode 100644
index 0000000..85b6f2a
--- /dev/null
+++ b/rust/vboot_reference-sys/lib.rs
@@ -0,0 +1,15 @@
+// Copyright 2022 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.
+
+/// vboot_reference bindings for Rust.
+
+#[allow(
+    clippy::all,
+    non_camel_case_types,
+    non_snake_case,
+    non_upper_case_globals
+)]
+pub mod crossystem {
+    include!(concat!(env!("OUT_DIR"), "/crossystem.rs"));
+}
diff --git a/scripts/image_signing/gbb_flags_common.sh b/scripts/image_signing/gbb_flags_common.sh
index 63c3f12..ed51f15 100755
--- a/scripts/image_signing/gbb_flags_common.sh
+++ b/scripts/image_signing/gbb_flags_common.sh
@@ -51,9 +51,50 @@
   ${GBBFLAGS_DESCRIPTION}"
 
 flashrom_read() {
-  flashrom -p host -i GBB -r "$@"
+  local file="$1"
+  local programmer="$2"
+  flashrom -p "${programmer}" -i GBB -i FMAP -r "${file}"
 }
 
 flashrom_write() {
-  flashrom -p host -i GBB --noverify-all -w "$@"
+  local file="$1"
+  local programmer="$2"
+  flashrom -p "${programmer}"  -i GBB --noverify-all -w "${file}"
+}
+
+get_programmer_for_servo() {
+  local servo_type
+  local serial
+  local programmer
+  servo_type=$(dut-control -o servo_type 2>/dev/null) || \
+    die "Failed to get servo information. Is servod running?"
+  case "${servo_type}" in
+    *with_servo_micro*)
+      serial=$(dut-control -o servo_micro_serialname 2>/dev/null)
+      ;;
+    *with_c2d2*)
+      serial=$(dut-control -o c2d2_serialname 2>/dev/null)
+      ;;
+    *with_ccd*)
+      serial=$(dut-control -o ccd_serialname 2>/dev/null)
+      ;;
+    *)
+      serial=$(dut-control -o serialname 2>/dev/null)
+      ;;
+  esac
+  case "${servo_type}" in
+    *servo_micro*|*c2d2*)
+      # TODO(sammc): Support servo micro, servo v2 and C2D2. This requires
+      # toggling cpu_fw_spi via dut-control before and after running flashrom.
+      # C2D2 additionally requires a working cpu_fw_spi implementation.
+      die "Unsupported servo type ${servo_type}"
+      ;;
+    *ccd_cr50*|*ccd_gsc*)
+      programmer="raiden_debug_spi:target=AP,serial=${serial}"
+      ;;
+    *)
+      die "Unsupported servo type ${servo_type}"
+      ;;
+  esac
+  echo "${programmer}"
 }
diff --git a/scripts/image_signing/get_gbb_flags.sh b/scripts/image_signing/get_gbb_flags.sh
index 2b78af6..1191e9f 100755
--- a/scripts/image_signing/get_gbb_flags.sh
+++ b/scripts/image_signing/get_gbb_flags.sh
@@ -13,6 +13,8 @@
 # DEFINE_string name default_value description flag
 DEFINE_string file "" "Path to firmware image. Default to system firmware." "f"
 DEFINE_boolean explicit ${FLAGS_FALSE} "Print list of what flags are set." "e"
+DEFINE_string programmer "host" "Programmer to use when setting GBB flags" "p"
+DEFINE_boolean servo "${FLAGS_FALSE}"  "Determine programmer using servo" ""
 
 set -e
 
@@ -23,10 +25,14 @@
   fi
 
   local image_file="${FLAGS_file}"
+  local programmer="${FLAGS_programmer}"
 
   if [ -z "${FLAGS_file}" ]; then
     image_file="$(make_temp_file)"
-    flashrom_read "${image_file}"
+    if [ "${FLAGS_servo}" = "${FLAGS_TRUE}" ]; then
+      programmer=$(get_programmer_for_servo)
+    fi
+    flashrom_read "${image_file}" "${programmer}"
   fi
 
   # Process file.
diff --git a/scripts/image_signing/make_dev_ssd.sh b/scripts/image_signing/make_dev_ssd.sh
index ed102a6..d9df52a 100755
--- a/scripts/image_signing/make_dev_ssd.sh
+++ b/scripts/image_signing/make_dev_ssd.sh
@@ -35,7 +35,15 @@
 DEFINE_string image "$ROOTDEV_DISK" "Path to device or image file" "i"
 DEFINE_string keys "$DEFAULT_KEYS_FOLDER" "Path to folder of dev keys" "k"
 DEFINE_boolean remove_rootfs_verification \
-  $FLAGS_FALSE "Modify kernel boot config to disable rootfs verification" ""
+  "${FLAGS_FALSE}" "Modify kernel boot config to disable rootfs verification" ""
+DEFINE_boolean enable_earlycon "${FLAGS_FALSE}" \
+  "Enable earlycon from stdout-path (ARM/ARM64) or SPCR (x86)." ""
+DEFINE_boolean disable_earlycon "${FLAGS_FALSE}" \
+  "Disable earlycon." ""
+DEFINE_boolean enable_console "${FLAGS_FALSE}" \
+  "Enable serial console." ""
+DEFINE_boolean disable_console "${FLAGS_FALSE}" \
+  "Disable serial console." ""
 DEFINE_string backup_dir \
   "$DEFAULT_BACKUP_FOLDER" "Path of directory to store kernel backups" ""
 DEFINE_string save_config "" \
@@ -98,14 +106,37 @@
   debug_msg "Removing rootfs verification for legacy boot configuration."
   mount_image_partition "$image" 12 "$mount_point" || return $FLAGS_FALSE
   config_file="$mount_point/efi/boot/grub.cfg"
-  [ ! -f "$config_file" ] ||
-    sudo sed -i 's/^ *set default=2 *$/set default=0/g' "$config_file"
+  [ ! -f "${config_file}" ] ||
+    sudo sed -i -e 's/^ *defaultA=2 *$/defaultA=0/g' \
+                -e 's/^ *defaultB=3 *$/defaultB=1/g' "${config_file}"
   config_file="$mount_point/syslinux/default.cfg"
   [ ! -f "$config_file" ] ||
     sudo sed -i 's/-vusb/-usb/g; s/-vhd/-hd/g' "$config_file"
   sudo umount "$mount_point"
 }
 
+# Enable/Disable earlycon or serial console
+insert_parameter() {
+  local cmdline="$1"
+  local param="$2"
+
+  if [ -n "${cmdline##*${param}*}" ]; then
+    cmdline="${param} ${cmdline}"
+  fi
+
+  echo "${cmdline}"
+}
+
+remove_parameter() {
+  local cmdline="$1"
+  local param="$2"
+
+  cmdline=$(echo "${cmdline}" | sed '
+    s/'"${param} "'//g')
+
+  echo "${cmdline}"
+}
+
 # Wrapped version of dd
 mydd() {
   # oflag=sync is safer, but since we need bs=512, syncing every block would be
@@ -244,6 +275,26 @@
       remove_legacy_boot_rootfs_verification "$ssd_device"
     fi
 
+    if [ "${FLAGS_enable_earlycon}" = "${FLAGS_TRUE}" ]; then
+      debug_msg "Enabling earlycon"
+      kernel_config="$(insert_parameter "${kernel_config}" "earlycon")"
+      debug_msg "New kernel config: ${kernel_config}"
+    elif [ "${FLAGS_disable_earlycon}" = "${FLAGS_TRUE}" ]; then
+      debug_msg "Disabling earlycon"
+      kernel_config="$(remove_parameter "${kernel_config}" "earlycon")"
+      debug_msg "New kernel config: ${kernel_config}"
+    fi
+
+    if [ "${FLAGS_enable_console}" = "${FLAGS_TRUE}" ]; then
+      debug_msg "Enabling serial console"
+      kernel_config="$(remove_parameter "${kernel_config}" "console=")"
+      debug_msg "New kernel config: ${kernel_config}"
+    elif [ "${FLAGS_disable_console}" = "${FLAGS_TRUE}" ]; then
+      debug_msg "Disabling serial console"
+      kernel_config="$(insert_parameter "${kernel_config}" "console=")"
+      debug_msg "New kernel config: ${kernel_config}"
+    fi
+
     local new_kernel_config_file="$(make_temp_file)"
     echo -n "$kernel_config"  >"$new_kernel_config_file"
 
diff --git a/scripts/image_signing/set_gbb_flags.sh b/scripts/image_signing/set_gbb_flags.sh
index 7a22b85..3057da5 100755
--- a/scripts/image_signing/set_gbb_flags.sh
+++ b/scripts/image_signing/set_gbb_flags.sh
@@ -13,6 +13,8 @@
 # DEFINE_string name default_value description flag
 DEFINE_string file "" "Path to firmware image. Default to system firmware." "f"
 DEFINE_boolean check_wp ${FLAGS_TRUE} "Check write protection states first." ""
+DEFINE_string programmer "host" "Programmer to use when setting GBB flags" "p"
+DEFINE_boolean servo "${FLAGS_FALSE}"  "Determine programmer using servo" ""
 
 set -e
 
@@ -20,12 +22,13 @@
 # ----------------------------------------------------------------------------
 check_write_protection() {
   local hw_wp="" sw_wp=""
-  if ! crossystem "wpsw_cur?0"; then
+  local programmer="$1"
+  if [ "${programmer}" = "host" ] && ! crossystem "wpsw_cur?0"; then
     hw_wp="on"
   fi
   # Keep 'local' declaration split from assignment so return code is checked.
   local wp_states
-  wp_states="$(flashrom -p host --wp-status 2>/dev/null | grep WP)"
+  wp_states="$(flashrom -p "${programmer}" --wp-status 2>/dev/null | grep WP)"
   local wp_disabled="$(echo "${wp_states}" | grep "WP:.*is disabled.")"
   local wp_zero_len="$(echo "${wp_states}" | grep "WP:.*, len=0x00000000")"
   if [ -z "${wp_disabled}" -a -z "${wp_zero_len}" ]; then
@@ -47,10 +50,15 @@
 
   local value="$(($1))"
   local image_file="${FLAGS_file}"
+  local programmer="${FLAGS_programmer}"
 
   if [ -z "${FLAGS_file}" ]; then
     image_file="$(make_temp_file)"
-    flashrom_read "${image_file}"
+    if [ "${FLAGS_servo}" = "${FLAGS_TRUE}" ]; then
+      programmer=$(get_programmer_for_servo)
+    fi
+
+    flashrom_read "${image_file}" "${programmer}"
   fi
 
   # Process file
@@ -62,14 +70,14 @@
 
   if [ -z "${FLAGS_file}" ]; then
     if [ "${FLAGS_check_wp}" = "${FLAGS_TRUE}" ]; then
-      if ! check_write_protection; then
+      if ! check_write_protection "${programmer}"; then
         echo ""
         echo "WARNING: System GBB Flags are NOT changed!!!"
         echo "ERROR: You must disable write protection before setting flags."
         exit 1
       fi
     fi
-    flashrom_write "$image_file"
+    flashrom_write "${image_file}" "${programmer}"
   fi
 }
 
diff --git a/scripts/image_signing/sign_android_image.sh b/scripts/image_signing/sign_android_image.sh
index a7d0fc6..5af1aa6 100755
--- a/scripts/image_signing/sign_android_image.sh
+++ b/scripts/image_signing/sign_android_image.sh
@@ -315,7 +315,7 @@
   elif [[ "${compression}" == "Compression lz4" ]]; then
     compression_flags="-comp lz4 -Xhc -b 256K"
   elif [[ "${compression}" == "Compression zstd" ]]; then
-    compression_flags="-comp zstd"
+    compression_flags="-comp zstd -b 256K"
   else
     die "Unexpected compression type: ${compression}"
   fi
diff --git a/scripts/image_signing/sign_official_build.sh b/scripts/image_signing/sign_official_build.sh
index 53a9264..98c8610 100755
--- a/scripts/image_signing/sign_official_build.sh
+++ b/scripts/image_signing/sign_official_build.sh
@@ -110,28 +110,30 @@
 get_hash_from_config() {
   local kernel_config=$1
   local dm_config=$(get_dmparams_from_config "${kernel_config}")
-  local vroot_dev=$(get_dm_slave "${dm_config}" vroot)
+  local vroot_dev=$(get_dm_device "${dm_config}" vroot)
   echo $(get_verity_arg "${vroot_dev}" root_hexdigest)
 }
 
-# Get the slave device and its args
-# get_dm_ags $dm_config [vboot|vroot]
-# Assumes we have only one slave device per device
-get_dm_slave() {
+# Get the mapped device and its args.
+# Usage:
+#   get_dm_device $dm_config [vboot|vroot]
+# Assumes we have only one mapped device per device.
+get_dm_device() {
   local dm=$1
   local device=$2
   echo $(echo "${dm}" | sed -nre "s/.*${device}[^,]*,([^,]*).*/\1/p")
 }
 
-# Set the slave device and its args for a device
-# get_dm_ags $dm_config [vboot|vroot] args
-# Assumes we have only one slave device per device
-set_dm_slave() {
+# Set the mapped device and its args for a device.
+# Usage:
+#   set_dm_device $dm_config [vboot|vroot] args
+# Assumes we have only one mapped device per device.
+set_dm_device() {
   local dm=$1
   local device=$2
-  local slave=$3
+  local args=$3
   echo $(echo "${dm}" |
-    sed -nre "s#(.*${device}[^,]*,)([^,]*)(.*)#\1${slave}\3#p")
+    sed -nre "s#(.*${device}[^,]*,)([^,]*)(.*)#\1${args}\3#p")
 }
 
 CALCULATED_KERNEL_CONFIG=
@@ -154,7 +156,7 @@
     warn "Couldn't grab dm_config. Aborting rootfs hash calculation."
     return 1
   fi
-  local vroot_dev=$(get_dm_slave "${dm_config}" vroot)
+  local vroot_dev=$(get_dm_device "${dm_config}" vroot)
 
   # Extract the key-value parameters from the kernel command line.
   local rootfs_sectors=$(get_verity_arg "${vroot_dev}" hashstart)
@@ -169,15 +171,15 @@
   fi
 
   # Run the verity tool on the rootfs partition.
-  local slave=$(sudo verity mode=create \
+  local table=$(sudo verity mode=create \
     alg=${verity_algorithm} \
     payload="${rootfs_image}" \
     payload_blocks=$((rootfs_sectors / 8)) \
     hashtree="${hash_image}" ${salt_arg})
   # Reconstruct new kernel config command line and replace placeholders.
-  slave="$(echo "${slave}" |
+  table="$(echo "${table}" |
     sed -s "s|ROOT_DEV|${root_dev}|g;s|HASH_DEV|${hash_dev}|")"
-  CALCULATED_DM_ARGS="$(set_dm_slave "${dm_config}" vroot "${slave}")"
+  CALCULATED_DM_ARGS="$(set_dm_device "${dm_config}" vroot "${table}")"
   CALCULATED_KERNEL_CONFIG="$(echo "${kernel_config}" |
     sed -e 's#\(.*dm="\)\([^"]*\)\(".*\)'"#\1${CALCULATED_DM_ARGS}\3#g")"
 }
@@ -709,6 +711,7 @@
 # Args: LOOPDEV
 sign_uefi_binaries() {
   local loopdev="$1"
+  local efi_glob="*.efi"
 
   if [[ ! -d "${KEY_DIR}/uefi" ]]; then
     return 0
@@ -725,13 +728,18 @@
   # in the signing repo. This is a temporary fix to unblock reven-release.
   if [[ "${KEY_DIR}" != *"Reven"* ]]; then
     "${SCRIPT_DIR}/install_gsetup_certs.sh" "${esp_dir}" "${KEY_DIR}/uefi"
+  else
+    # b/205145491: the reven board's boot*.efi files are already signed,
+    # change the glob so that they don't get resigned.
+    efi_glob="grub*.efi"
   fi
-  "${SCRIPT_DIR}/sign_uefi.sh" "${esp_dir}" "${KEY_DIR}/uefi"
+  "${SCRIPT_DIR}/sign_uefi.sh" "${esp_dir}" "${KEY_DIR}/uefi" "${efi_glob}"
   sudo umount "${esp_dir}"
 
   local rootfs_dir="$(make_temp_dir)"
   mount_loop_image_partition "${loopdev}" 3 "${rootfs_dir}"
-  "${SCRIPT_DIR}/sign_uefi.sh" "${rootfs_dir}/boot" "${KEY_DIR}/uefi"
+  "${SCRIPT_DIR}/sign_uefi.sh" "${rootfs_dir}/boot" "${KEY_DIR}/uefi" \
+                               "${efi_glob}"
   sudo umount "${rootfs_dir}"
 
   info "Signed UEFI binaries"
@@ -1145,6 +1153,9 @@
            --version "${FIRMWARE_VERSION}" "${OUTPUT_IMAGE}"
 elif [[ "${TYPE}" == "gsc_firmware" ]]; then
   sign_gsc_firmware "${INPUT_IMAGE}" "${KEY_DIR}" "${OUTPUT_IMAGE}"
+elif [[ "${TYPE}" == "hps_firmware" ]]; then
+  hps-sign-rom --input "${INPUT_IMAGE}" --output "${OUTPUT_IMAGE}" \
+    --private-key "${KEY_DIR}/key_hps.priv.pem"
 else
   die "Invalid type ${TYPE}"
 fi
diff --git a/scripts/image_signing/sign_uefi.sh b/scripts/image_signing/sign_uefi.sh
index 14c328e..d0f9e51 100755
--- a/scripts/image_signing/sign_uefi.sh
+++ b/scripts/image_signing/sign_uefi.sh
@@ -130,7 +130,8 @@
   local working_dir="$(make_temp_dir)"
 
   local efi_file
-  for efi_file in "${bootloader_dir}"/*.efi; do
+  # Leave ${efi_glob} unquoted so that globbing occurs.
+  for efi_file in "${bootloader_dir}"/${efi_glob}; do
     if [[ ! -f "${efi_file}" ]]; then
       continue
     fi
diff --git a/scripts/keygeneration/accessory/common_leverage_hammer.sh b/scripts/keygeneration/accessory/common_leverage_hammer.sh
index 1e0f712..7c51af1 100644
--- a/scripts/keygeneration/accessory/common_leverage_hammer.sh
+++ b/scripts/keygeneration/accessory/common_leverage_hammer.sh
@@ -7,13 +7,27 @@
 # Load common constants and functions.
 . "$(dirname "$0")/../common.sh"
 
+: "${HAS_ARG_KEYNAME:=}"
+
 usage() {
-  cat <<EOF
+  if [[ -n "${HAS_ARG_KEYNAME}" ]]; then
+    cat <<EOF
+Usage: ${PROG} <keyname> [options]
+
+Arguments:
+  keyname:                   Name of the hammer device (e.g. Staff, Wand).
+
+Options:
+  -o, --output_dir <dir>:    Where to write the keys (default is cwd)
+EOF
+  else
+    cat <<EOF
 Usage: ${PROG} [options]
 
 Options:
   -o, --output_dir <dir>:    Where to write the keys (default is cwd)
 EOF
+  fi
 
   if [[ $# -ne 0 ]]; then
     die "$*"
@@ -40,8 +54,7 @@
 # to specific accessory's name.
 leverage_hammer_to_create_key() {
   local output_dir="${PWD}"
-  local key_name="$1"
-  shift
+  local key_name=""
 
   while [[ $# -gt 0 ]]; do
     case "$1" in
@@ -51,19 +64,26 @@
     -o|--output_dir)
       output_dir="$2"
       if [[ ! -d "${output_dir}" ]]; then
-        die "output dir ("${output_dir}") doesn't exist."
+        die "output dir (\"${output_dir}\") doesn't exist."
       fi
       shift
       ;;
     -*)
-      usage "Unknown option: "$1""
+      usage "Unknown option: \"$1\""
       ;;
     *)
-      usage "Unknown argument "$1""
+      if [[ -n "${key_name}" ]]; then
+        usage "Unknown argument \"$1\""
+      fi
+      key_name="$1"
       ;;
     esac
     shift
   done
 
+  if [[ -z "${key_name}" ]]; then
+    usage "Missing key name"
+  fi
+
   generate_rsa3072_exp3_key "${output_dir}" "${key_name}"
 }
diff --git a/scripts/keygeneration/accessory/create_new_hammer_like_keys.sh b/scripts/keygeneration/accessory/create_new_hammer_like_keys.sh
index 3d5c96e..96d2da0 100755
--- a/scripts/keygeneration/accessory/create_new_hammer_like_keys.sh
+++ b/scripts/keygeneration/accessory/create_new_hammer_like_keys.sh
@@ -5,12 +5,13 @@
 # found in the LICENSE file.
 
 # Load common constants and functions.
+export HAS_ARG_KEYNAME=1
 . "$(dirname "$0")/common_leverage_hammer.sh"
 
 main() {
   set -e
 
-  leverage_hammer_to_create_key "hammerlike" "$@"
+  leverage_hammer_to_create_key "$@"
 }
 
 main "$@"
diff --git a/scripts/keygeneration/accessory/create_new_hps_key.sh b/scripts/keygeneration/accessory/create_new_hps_key.sh
new file mode 100755
index 0000000..6d175c8
--- /dev/null
+++ b/scripts/keygeneration/accessory/create_new_hps_key.sh
@@ -0,0 +1,64 @@
+#!/bin/bash
+
+# Copyright 2022 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.
+
+# Load common constants and functions.
+. "$(dirname "$0")/../common.sh"
+
+usage() {
+  cat <<EOF
+Usage: ${PROG} [options]
+
+Options:
+  -o, --output_dir <dir>:    Where to write the keys (default is cwd)
+EOF
+
+  if [[ $# -ne 0 ]]; then
+    die "$*"
+  else
+    exit 0
+  fi
+}
+
+generate_ed25519_key() {
+  local output_dir="$1"
+
+  # Generate ed25519 private and public key.
+  openssl genpkey -algorithm Ed25519 -out "${output_dir}/key_hps.priv.pem"
+  openssl pkey -in "${output_dir}/key_hps.priv.pem" -pubout -text_pub \
+    -out "${output_dir}/key_hps.pub.pem"
+}
+
+main() {
+  set -euo pipefail
+
+  local output_dir="${PWD}"
+
+  while [[ $# -gt 0 ]]; do
+    case "$1" in
+    -h|--help)
+      usage
+      ;;
+    -o|--output_dir)
+      output_dir="$2"
+      if [[ ! -d "${output_dir}" ]]; then
+        die "output dir (${output_dir}) doesn't exist."
+      fi
+      shift
+      ;;
+    -*)
+      usage "Unknown option: $1"
+      ;;
+    *)
+      usage "Unknown argument $1"
+      ;;
+    esac
+    shift
+  done
+
+  generate_ed25519_key "${output_dir}"
+}
+
+main "$@"
diff --git a/scripts/keygeneration/common.sh b/scripts/keygeneration/common.sh
index da06f3c..af6cd71 100644
--- a/scripts/keygeneration/common.sh
+++ b/scripts/keygeneration/common.sh
@@ -63,6 +63,10 @@
 KERNEL_SUBKEY_ALGOID=${RSA4096_SHA256_ALGOID}
 KERNEL_DATAKEY_ALGOID=${RSA2048_SHA256_ALGOID}
 
+# AP RO Verification.
+ARV_ROOT_ALGOID=${RSA4096_SHA256_ALGOID}
+ARV_PLATFORM_ALGOID=${RSA4096_SHA256_ALGOID}
+
 # Keyblock modes determine which boot modes a signing key is valid for use
 # in verification.
 #    !DEV 0x1      DEV 0x2
diff --git a/scripts/keygeneration/create_new_keys.sh b/scripts/keygeneration/create_new_keys.sh
index 11aedc1..2e1fd22 100755
--- a/scripts/keygeneration/create_new_keys.sh
+++ b/scripts/keygeneration/create_new_keys.sh
@@ -169,6 +169,8 @@
   make_pair recovery_kernel_data_key ${recovery_kernel_algoid}
   make_pair minios_kernel_data_key   ${minios_kernel_algoid}
   make_pair installer_kernel_data_key ${installer_kernel_algoid}
+  make_pair arv_root ${ARV_ROOT_ALGOID}
+  make_pair arv_platform ${ARV_PLATFORM_ALGOID}
 
   # Create the firmware keyblock for use only in Normal mode. This is redundant,
   # since it's never even checked during Recovery mode.
diff --git a/tests/devkeys/arv_platform.vbprivk b/tests/devkeys/arv_platform.vbprivk
new file mode 100644
index 0000000..e0bc670
--- /dev/null
+++ b/tests/devkeys/arv_platform.vbprivk
Binary files differ
diff --git a/tests/devkeys/arv_platform.vbpubk b/tests/devkeys/arv_platform.vbpubk
new file mode 100644
index 0000000..2ac3bde
--- /dev/null
+++ b/tests/devkeys/arv_platform.vbpubk
Binary files differ
diff --git a/tests/devkeys/arv_root.vbprivk b/tests/devkeys/arv_root.vbprivk
new file mode 100644
index 0000000..7747717
--- /dev/null
+++ b/tests/devkeys/arv_root.vbprivk
Binary files differ
diff --git a/tests/devkeys/arv_root.vbpubk b/tests/devkeys/arv_root.vbpubk
new file mode 100644
index 0000000..aebe2a4
--- /dev/null
+++ b/tests/devkeys/arv_root.vbpubk
Binary files differ
diff --git a/tests/futility/test_update.sh b/tests/futility/test_update.sh
index 31f3ecb..25c5543 100755
--- a/tests/futility/test_update.sh
+++ b/tests/futility/test_update.sh
@@ -141,6 +141,9 @@
 cp -f "${TMP}.expected.full" "${TMP}.expected.me_unlocked"
 patch_file "${TMP}.expected.me_unlocked" SI_DESC 128 \
 	"\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff"
+cp -f "${TMP}.expected.full" "${TMP}.expected.me_preserved"
+"${FUTILITY}" load_fmap "${TMP}.expected.me_preserved" \
+	"SI_ME:${TMP}.from/SI_ME"
 
 # A special set of images that only RO_VPD is preserved (RW_VPD is wiped) using
 # FMAP_AREA_PRESERVE (\010=0x08).
@@ -357,6 +360,17 @@
 	--quirks no_check_platform \
 	-i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1
 
+test_update "Full update (--quirks preserve_me with non-host programmer)" \
+	"${FROM_IMAGE}" "${TMP}.expected.full" \
+	--quirks preserve_me \
+	-i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1 \
+	-p raiden_debug_spi:target=AP
+
+test_update "Full update (--quirks preserve_me)" \
+	"${FROM_IMAGE}" "${TMP}.expected.me_preserved" \
+	--quirks preserve_me \
+	-i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1
+
 # Test archive and manifest.
 A="${TMP}.archive"
 mkdir -p "${A}/bin"
@@ -471,8 +485,8 @@
 if type flashrom >/dev/null 2>&1; then
 	echo "TEST: Full update (dummy programmer)"
 	cp -f "${FROM_IMAGE}" "${TMP}.emu"
-	sudo "${FUTILITY}" update --programmer \
-		dummy:emulate=VARIABLE_SIZE,image=${TMP}.emu,size=8388608 \
+	"${FUTILITY}" update --programmer \
+		dummy:emulate=VARIABLE_SIZE,image="${TMP}".emu,size=8388608 \
 		-i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1,3 >&2
 	cmp "${TMP}.emu" "${TMP}.expected.full"
 fi
diff --git a/tests/vb20_api_kernel_tests.c b/tests/vb20_api_kernel_tests.c
index 893cd4e..b835251 100644
--- a/tests/vb20_api_kernel_tests.c
+++ b/tests/vb20_api_kernel_tests.c
@@ -59,6 +59,7 @@
 		  "vb2api_init failed");
 
 	sd = vb2_get_sd(ctx);
+	sd->status |= VB2_SD_STATUS_RECOVERY_DECIDED;
 	vb2_nv_init(ctx);
 
 	vb2api_secdata_kernel_create(ctx);
diff --git a/tests/vb2_api_tests.c b/tests/vb2_api_tests.c
index beab239..15c9bd1 100644
--- a/tests/vb2_api_tests.c
+++ b/tests/vb2_api_tests.c
@@ -374,6 +374,8 @@
 		 0, "  secdata firmware initialized");
 	TEST_NEQ(sd->status & VB2_SD_STATUS_SECDATA_KERNEL_INIT,
 		 0, "  secdata kernel initialized");
+	TEST_NEQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		 0, "  recovery decided");
 
 	reset_common_data(FOR_MISC);
 	retval_vb2_fw_init_gbb = VB2_ERROR_GBB_MAGIC;
@@ -383,6 +385,8 @@
 		"  recovery reason");
 	TEST_NEQ(ctx->flags & VB2_CONTEXT_RECOVERY_MODE, 0, "  recovery flag");
 	TEST_NEQ(ctx->flags & VB2_CONTEXT_CLEAR_RAM, 0, "  clear ram flag");
+	TEST_NEQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		 0, "  recovery decided");
 
 	/* Dev switch error proceeds to a recovery boot */
 	reset_common_data(FOR_MISC);
@@ -398,6 +402,8 @@
 		 0, "  display init context flag");
 	TEST_NEQ(sd->flags & VB2_SD_FLAG_DISPLAY_AVAILABLE,
 		 0, "  display available SD flag");
+	TEST_NEQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		 0, "  recovery decided");
 
 	reset_common_data(FOR_MISC);
 	ctx->secdata_firmware[0] ^= 0x42;
@@ -407,6 +413,8 @@
 		"  recovery reason");
 	TEST_NEQ(ctx->flags & VB2_CONTEXT_RECOVERY_MODE, 0, "  recovery flag");
 	TEST_NEQ(ctx->flags & VB2_CONTEXT_CLEAR_RAM, 0, "  clear ram flag");
+	TEST_NEQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		 0, "  recovery decided");
 
 	/* Bad secdata_kernel causes recovery mode */
 	reset_common_data(FOR_MISC);
@@ -417,6 +425,8 @@
 		"  recovery reason");
 	TEST_NEQ(ctx->flags & VB2_CONTEXT_RECOVERY_MODE, 0, "  recovery flag");
 	TEST_NEQ(ctx->flags & VB2_CONTEXT_CLEAR_RAM, 0, "  clear ram flag");
+	TEST_NEQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		 0, "  recovery decided");
 
 	/* Test secdata_firmware-requested reboot */
 	reset_common_data(FOR_MISC);
@@ -428,6 +438,8 @@
 		1, "  tpm reboot request");
 	TEST_EQ(vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST),
 		0, "  recovery request");
+	TEST_EQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		0, "  recovery decided");
 
 	reset_common_data(FOR_MISC);
 	vb2_nv_set(ctx, VB2_NV_TPM_REQUESTED_REBOOT, 1);
@@ -438,6 +450,8 @@
 		0, "  tpm reboot request");
 	TEST_EQ(vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST),
 		0, "  recovery request");
+	TEST_NEQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		 0, "  recovery decided");
 
 	reset_common_data(FOR_MISC);
 	ctx->flags |= VB2_CONTEXT_SECDATA_WANTS_REBOOT;
@@ -450,6 +464,8 @@
 		1, "  tpm reboot request");
 	TEST_EQ(vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST),
 		0, "  recovery request");
+	TEST_EQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		0, "  recovery decided");
 
 	reset_common_data(FOR_MISC);
 	ctx->flags |= VB2_CONTEXT_SECDATA_WANTS_REBOOT;
@@ -462,6 +478,8 @@
 		1, "  tpm reboot request");
 	TEST_EQ(vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST),
 		VB2_RECOVERY_RO_TPM_REBOOT, "  recovery request");
+	TEST_NEQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		 0, "  recovery decided");
 
 	reset_common_data(FOR_MISC);
 	ctx->flags |= VB2_CONTEXT_SECDATA_WANTS_REBOOT;
@@ -474,6 +492,8 @@
 		1, "  tpm reboot request");
 	TEST_EQ(vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST),
 		VB2_RECOVERY_RO_UNSPECIFIED, "  recovery request");
+	TEST_EQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		0, "  recovery decided");
 
 	reset_common_data(FOR_MISC);
 	vb2_nv_set(ctx, VB2_NV_TPM_REQUESTED_REBOOT, 1);
@@ -486,6 +506,8 @@
 		0, "  tpm reboot request");
 	TEST_EQ(vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST),
 		VB2_RECOVERY_RO_UNSPECIFIED, "  recovery request");
+	TEST_NEQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		 0, "  recovery decided");
 
 	reset_common_data(FOR_MISC);
 	ctx->flags |= VB2_CONTEXT_SECDATA_WANTS_REBOOT;
@@ -499,6 +521,8 @@
 		1, "  tpm reboot request");
 	TEST_EQ(vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST),
 		VB2_RECOVERY_RO_UNSPECIFIED, "  recovery request");
+	TEST_NEQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		 0, "  recovery decided");
 
 	/* Cases for checking DISPLAY_INIT and DISPLAY_AVAILABLE. */
 	reset_common_data(FOR_MISC);
@@ -508,6 +532,8 @@
 		 0, "  display init context flag");
 	TEST_NEQ(sd->flags & VB2_SD_FLAG_DISPLAY_AVAILABLE,
 		 0, "  display available SD flag");
+	TEST_NEQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		 0, "  recovery decided");
 
 	reset_common_data(FOR_MISC);
 	vb2_nv_set(ctx, VB2_NV_DISPLAY_REQUEST, 1);
@@ -516,6 +542,8 @@
 		 0, "  display init context flag");
 	TEST_NEQ(sd->flags & VB2_SD_FLAG_DISPLAY_AVAILABLE,
 		 0, "  display available SD flag");
+	TEST_NEQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		 0, "  recovery decided");
 
 	reset_common_data(FOR_MISC);
 	force_dev_mode = 1;
@@ -524,6 +552,8 @@
 		 0, "  display init context flag");
 	TEST_NEQ(sd->flags & VB2_SD_FLAG_DISPLAY_AVAILABLE,
 		 0, "  display available SD flag");
+	TEST_NEQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		 0, "  recovery decided");
 }
 
 static void phase2_tests(void)
diff --git a/tests/vb2_auxfw_sync_tests.c b/tests/vb2_auxfw_sync_tests.c
index 3987d64..bcdd67a 100644
--- a/tests/vb2_auxfw_sync_tests.c
+++ b/tests/vb2_auxfw_sync_tests.c
@@ -65,14 +65,15 @@
 {
 	*severity = auxfw_mock_severity;
 	auxfw_update_severity = auxfw_mock_severity;
-	if (*severity == VB2_AUXFW_SLOW_UPDATE)
-		if (!auxfw_mock_display_available)
-			return VB2_REQUEST_REBOOT;
 	return VB2_SUCCESS;
 }
 
 vb2_error_t vb2ex_auxfw_update(void)
 {
+	if (auxfw_update_severity == VB2_AUXFW_SLOW_UPDATE)
+		if (!auxfw_mock_display_available)
+			return VB2_REQUEST_REBOOT;
+
 	if (auxfw_update_severity != VB2_AUXFW_NO_DEVICE &&
 	    auxfw_update_severity != VB2_AUXFW_NO_UPDATE)
 		auxfw_update_req = 1;
diff --git a/tests/vb2_ec_sync_tests.c b/tests/vb2_ec_sync_tests.c
index 16496e4..f4631b7 100644
--- a/tests/vb2_ec_sync_tests.c
+++ b/tests/vb2_ec_sync_tests.c
@@ -111,21 +111,6 @@
 	return &gbb;
 }
 
-uint32_t VbExIsShutdownRequested(void)
-{
-	if (shutdown_request_calls_left == 0)
-		return 1;
-	else if (shutdown_request_calls_left > 0)
-		shutdown_request_calls_left--;
-
-	return 0;
-}
-
-int vb2ex_ec_trusted(void)
-{
-	return !ec_run_image;
-}
-
 vb2_error_t vb2ex_ec_running_rw(int *in_rw)
 {
 	*in_rw = ec_run_image;
diff --git a/tests/vb2_host_flashrom_tests.c b/tests/vb2_host_flashrom_tests.c
index 8552d0a..815657d 100644
--- a/tests/vb2_host_flashrom_tests.c
+++ b/tests/vb2_host_flashrom_tests.c
@@ -145,10 +145,11 @@
 
 static void test_read_whole_chip(void)
 {
-	uint8_t *buf;
-	uint32_t buf_sz;
+	struct firmware_image image = {
+		.programmer = "someprog",
+	};
 
-	TEST_SUCC(flashrom_read("someprog", NULL, &buf, &buf_sz),
+	TEST_SUCC(flashrom_read(&image, NULL),
 		  "Flashrom read succeeds");
 	TEST_STR_EQ(captured_programmer, "someprog",
 		    "Using specified programmer");
@@ -158,19 +159,20 @@
 	TEST_STR_EQ(captured_op_filename, MOCK_TMPFILE_NAME,
 		    "Reading to correct file");
 	TEST_PTR_EQ(captured_region_param, NULL, "Not operating on a region");
-	TEST_EQ(buf_sz, strlen(MOCK_ROM_CONTENTS), "Contents correct size");
-	TEST_SUCC(memcmp(buf, MOCK_ROM_CONTENTS, buf_sz),
+	TEST_EQ(image.size, strlen(MOCK_ROM_CONTENTS), "Contents correct size");
+	TEST_SUCC(memcmp(image.data, MOCK_ROM_CONTENTS, image.size),
 		  "Buffer has correct contents");
 
-	free(buf);
+	free(image.data);
 }
 
 static void test_read_region(void)
 {
-	uint8_t *buf;
-	uint32_t buf_sz;
+	struct firmware_image image = {
+		.programmer = "someprog",
+	};
 
-	TEST_SUCC(flashrom_read("someprog", "SOME_REGION", &buf, &buf_sz),
+	TEST_SUCC(flashrom_read(&image, "SOME_REGION"),
 		  "Flashrom read succeeds");
 	TEST_STR_EQ(captured_programmer, "someprog",
 		    "Using specified programmer");
@@ -181,20 +183,21 @@
 		    "Not doing a read of the whole ROM");
 	TEST_STR_EQ(captured_region_param, "SOME_REGION:" MOCK_TMPFILE_NAME,
 		    "Reading to correct file and from correct region");
-	TEST_EQ(buf_sz, strlen(MOCK_ROM_CONTENTS), "Contents correct size");
-	TEST_SUCC(memcmp(buf, MOCK_ROM_CONTENTS, buf_sz),
+	TEST_EQ(image.size, strlen(MOCK_ROM_CONTENTS), "Contents correct size");
+	TEST_SUCC(memcmp(image.data, MOCK_ROM_CONTENTS, image.size),
 		  "Buffer has correct contents");
 
-	free(buf);
+	free(image.data);
 }
 
 static void test_read_failure(void)
 {
-	uint8_t *buf;
-	uint32_t buf_sz;
+	struct firmware_image image = {
+		.programmer = "someprog",
+	};
 
 	flashrom_mock_success = false;
-	TEST_NEQ(flashrom_read("someprog", "SOME_REGION", &buf, &buf_sz),
+	TEST_NEQ(flashrom_read(&image, "SOME_REGION"),
 		 VB2_SUCCESS, "Flashrom read fails");
 	flashrom_mock_success = true;
 }
@@ -202,10 +205,15 @@
 static void test_write_whole_chip(void)
 {
 	uint8_t buf[sizeof(MOCK_ROM_CONTENTS) - 1];
+	struct firmware_image image = {
+		.programmer = "someprog",
+		.data = buf,
+		.size = sizeof(buf),
+	};
 
 	memcpy(buf, MOCK_ROM_CONTENTS, sizeof(buf));
 
-	TEST_SUCC(flashrom_write("someprog", NULL, buf, sizeof(buf)),
+	TEST_SUCC(flashrom_write(&image, NULL),
 		  "Flashrom write succeeds");
 	TEST_STR_EQ(captured_programmer, "someprog",
 		    "Using specified programmer");
@@ -224,10 +232,15 @@
 static void test_write_region(void)
 {
 	uint8_t buf[sizeof(MOCK_ROM_CONTENTS) - 1];
+	struct firmware_image image = {
+		.programmer = "someprog",
+		.data = buf,
+		.size = sizeof(buf),
+	};
 
 	memcpy(buf, MOCK_ROM_CONTENTS, sizeof(buf));
 
-	TEST_SUCC(flashrom_write("someprog", "SOME_REGION", buf, sizeof(buf)),
+	TEST_SUCC(flashrom_write(&image, "SOME_REGION"),
 		  "Flashrom write succeeds");
 	TEST_STR_EQ(captured_programmer, "someprog",
 		    "Using specified programmer");
@@ -247,9 +260,14 @@
 static void test_write_failure(void)
 {
 	uint8_t buf[20] = { 0 };
+	struct firmware_image image = {
+		.programmer = "someprog",
+		.data = buf,
+		.size = sizeof(buf),
+	};
 
 	flashrom_mock_success = false;
-	TEST_NEQ(flashrom_write("someprog", "SOME_REGION", buf, sizeof(buf)),
+	TEST_NEQ(flashrom_write(&image, "SOME_REGION"),
 		 VB2_SUCCESS, "Flashrom write fails");
 	flashrom_mock_success = true;
 }
diff --git a/tests/vb2_host_nvdata_flashrom_tests.c b/tests/vb2_host_nvdata_flashrom_tests.c
index 33b435d..068b23a 100644
--- a/tests/vb2_host_nvdata_flashrom_tests.c
+++ b/tests/vb2_host_nvdata_flashrom_tests.c
@@ -81,35 +81,33 @@
 }
 
 /* Mocked flashrom_read for tests. */
-vb2_error_t flashrom_read(const char *programmer, const char *region,
-			  uint8_t **data_out, uint32_t *size_out)
+vb2_error_t flashrom_read(struct firmware_image *image, const char *region)
 {
 	if (mock_flashrom_fail) {
-		*data_out = NULL;
-		*size_out = 0;
+		image->data = NULL;
+		image->size = 0;
 		return VB2_ERROR_FLASHROM;
 	}
 
-	assert_mock_params(programmer, region);
+	assert_mock_params(image->programmer, region);
 
-	*data_out = malloc(sizeof(fake_flash_region));
-	*size_out = sizeof(fake_flash_region);
-	memcpy(*data_out, fake_flash_region, sizeof(fake_flash_region));
+	image->data = malloc(sizeof(fake_flash_region));
+	image->size = sizeof(fake_flash_region);
+	memcpy(image->data, fake_flash_region, sizeof(fake_flash_region));
 	return VB2_SUCCESS;
 }
 
 /* Mocked flashrom_write for tests. */
-vb2_error_t flashrom_write(const char *programmer, const char *region,
-			   uint8_t *data, uint32_t data_size)
+vb2_error_t flashrom_write(struct firmware_image *image, const char *region)
 {
 	if (mock_flashrom_fail)
 		return VB2_ERROR_FLASHROM;
 
-	assert_mock_params(programmer, region);
+	assert_mock_params(image->programmer, region);
 
-	TEST_EQ(data_size, sizeof(fake_flash_region),
+	TEST_EQ(image->size, sizeof(fake_flash_region),
 		"The flash size is correct");
-	memcpy(fake_flash_region, data, data_size);
+	memcpy(fake_flash_region, image->data, image->size);
 	return VB2_SUCCESS;
 }
 
diff --git a/tests/vb2_kernel_tests.c b/tests/vb2_kernel_tests.c
index 0dc0e74..0b3e94c 100644
--- a/tests/vb2_kernel_tests.c
+++ b/tests/vb2_kernel_tests.c
@@ -24,6 +24,7 @@
 static struct vb2_shared_data *sd;
 static struct vb2_fw_preamble *fwpre;
 static const char fw_kernel_key_data[36] = "Test kernel key data";
+static enum vb2_boot_mode *boot_mode;
 
 /* Mocked function data */
 
@@ -82,6 +83,14 @@
 		mock_gbb.recovery_key.key_offset +
 		mock_gbb.recovery_key.key_size;
 
+	/* For boot_mode */
+	boot_mode = (enum vb2_boot_mode *)&ctx->boot_mode;
+	if (t == FOR_PHASE1)
+		*boot_mode = VB2_BOOT_MODE_BROKEN_SCREEN;
+	else if (t == FOR_NORMAL_BOOT)
+		*boot_mode = VB2_BOOT_MODE_NORMAL;
+	else
+		*boot_mode = VB2_BOOT_MODE_UNDEFINED;
 
 	if (t == FOR_PHASE1) {
 		uint8_t *kdata;
@@ -274,6 +283,7 @@
 	TEST_EQ(sd->kernel_key_offset, 0, "  workbuf key offset");
 	TEST_EQ(sd->kernel_key_size, 0, "  workbuf key size");
 	mock_gbb.h.flags |= VB2_GBB_FLAG_FORCE_MANUAL_RECOVERY;
+	*boot_mode = VB2_BOOT_MODE_MANUAL_RECOVERY;
 	TEST_ABORT(vb2api_kernel_phase1(ctx), "  fatal for manual recovery");
 
 	reset_common_data(FOR_PHASE1);
@@ -284,6 +294,7 @@
 	TEST_EQ(sd->kernel_key_offset, 0, "  workbuf key offset");
 	TEST_EQ(sd->kernel_key_size, 0, "  workbuf key size");
 	mock_gbb.h.flags |= VB2_GBB_FLAG_FORCE_MANUAL_RECOVERY;
+	*boot_mode = VB2_BOOT_MODE_MANUAL_RECOVERY;
 	mock_read_res_fail_on_call = 1;
 	TEST_ABORT(vb2api_kernel_phase1(ctx), "  fatal for manual recovery");
 
diff --git a/tests/vb2_misc_tests.c b/tests/vb2_misc_tests.c
index c4b3ce4..99f8bae 100644
--- a/tests/vb2_misc_tests.c
+++ b/tests/vb2_misc_tests.c
@@ -22,6 +22,7 @@
 static struct vb2_shared_data *sd;
 static struct vb2_gbb_header gbb;
 static struct vb2_secdata_fwmp *fwmp;
+static enum vb2_boot_mode *boot_mode;
 
 /* Mocked function data */
 static enum vb2_resource_index mock_resource_index;
@@ -29,7 +30,6 @@
 static uint32_t mock_resource_size;
 static int mock_tpm_clear_called;
 static int mock_tpm_clear_retval;
-static int allow_recovery_retval;
 
 static void reset_common_data(void)
 {
@@ -40,7 +40,7 @@
 		  "vb2api_init failed");
 
 	sd = vb2_get_sd(ctx);
-	sd->status = VB2_SD_STATUS_SECDATA_FWMP_INIT;
+	sd->status |= VB2_SD_STATUS_SECDATA_FWMP_INIT;
 
 	memset(&gbb, 0, sizeof(gbb));
 
@@ -53,16 +53,13 @@
 
 	mock_tpm_clear_called = 0;
 	mock_tpm_clear_retval = VB2_SUCCESS;
-	allow_recovery_retval = 0;
+
+	boot_mode = (enum vb2_boot_mode *)&ctx->boot_mode;
+	*boot_mode = VB2_BOOT_MODE_NORMAL;
 };
 
 /* Mocked functions */
 
-int vb2api_allow_recovery(struct vb2_context *c)
-{
-	return allow_recovery_retval;
-}
-
 struct vb2_gbb_header *vb2_get_gbb(struct vb2_context *c)
 {
 	return &gbb;
@@ -268,12 +265,17 @@
 		"vb_workbuf_from_ctx() size");
 
 	reset_common_data();
+	sd->status |= VB2_SD_STATUS_RECOVERY_DECIDED;
 	TEST_ABORT(VB2_REC_OR_DIE(ctx, "die\n"), "REC_OR_DIE in normal mode");
 
 	reset_common_data();
+	sd->status |= VB2_SD_STATUS_RECOVERY_DECIDED;
 	ctx->flags |= VB2_CONTEXT_RECOVERY_MODE;
 	VB2_REC_OR_DIE(ctx, "VB2_REC_OR_DIE() test in recovery mode\n");
 	/* Would exit here if it didn't work as intended. */
+
+	reset_common_data();
+	VB2_REC_OR_DIE(ctx, "VB2_REC_OR_DIE() test in fw_phase1\n");
 }
 
 static void gbb_tests(void)
@@ -421,12 +423,14 @@
 {
 	/* No recovery */
 	reset_common_data();
+	TEST_EQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		0, "recovery not yet decided before testing check_recovery()");
 	vb2_check_recovery(ctx);
 	TEST_EQ(sd->recovery_reason, 0, "No recovery reason");
-	TEST_EQ(sd->flags & VB2_SD_FLAG_MANUAL_RECOVERY,
-		0, "Not manual recovery");
 	TEST_EQ(ctx->flags & VB2_CONTEXT_RECOVERY_MODE,
 		0, "Not recovery mode");
+	TEST_NEQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		 0, "Recovery decided");
 
 	/* From request */
 	reset_common_data();
@@ -434,10 +438,10 @@
 	vb2_check_recovery(ctx);
 	TEST_EQ(sd->recovery_reason, 3, "Recovery reason from request");
 	TEST_EQ(vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST), 3, "NV not cleared");
-	TEST_EQ(sd->flags & VB2_SD_FLAG_MANUAL_RECOVERY,
-		0, "Not manual recovery");
 	TEST_NEQ(ctx->flags & VB2_CONTEXT_RECOVERY_MODE,
 		 0, "Recovery mode");
+	TEST_NEQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		 0, "Recovery decided");
 
 	/* From request, but already failed */
 	reset_common_data();
@@ -447,6 +451,8 @@
 	TEST_EQ(sd->recovery_reason, 5, "Recovery reason already failed");
 	TEST_EQ(vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST),
 		4, "NV not cleared");
+	TEST_NEQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		 0, "Recovery decided");
 
 	/* Override */
 	reset_common_data();
@@ -455,8 +461,8 @@
 	vb2_check_recovery(ctx);
 	TEST_EQ(sd->recovery_reason, VB2_RECOVERY_RO_MANUAL,
 		"Recovery reason forced");
-	TEST_NEQ(sd->flags & VB2_SD_FLAG_MANUAL_RECOVERY,
-		 0, "SD flag set");
+	TEST_NEQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		 0, "Recovery decided");
 
 	/* Override subcode TRAIN_AND_REBOOT */
 	reset_common_data();
@@ -465,8 +471,8 @@
 	vb2_check_recovery(ctx);
 	TEST_EQ(sd->recovery_reason, VB2_RECOVERY_RO_MANUAL,
 		"Recovery reason forced");
-	TEST_NEQ(sd->flags & VB2_SD_FLAG_MANUAL_RECOVERY,
-		 0, "SD flag set");
+	TEST_NEQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		 0, "Recovery decided");
 
 	/* Promote subcode from BROKEN screen*/
 	reset_common_data();
@@ -475,8 +481,8 @@
 	vb2_check_recovery(ctx);
 	TEST_EQ(sd->recovery_reason, VB2_RECOVERY_US_TEST,
 		"Recovery reason forced from BROKEN");
-	TEST_NEQ(sd->flags & VB2_SD_FLAG_MANUAL_RECOVERY,
-		 0, "SD flag set");
+	TEST_NEQ(sd->status & VB2_SD_STATUS_RECOVERY_DECIDED,
+		 0, "Recovery decided");
 }
 
 static void dev_switch_tests(void)
@@ -589,13 +595,15 @@
 
 	/*
 	 * secdata_firmware failure in normal mode fails and shows dev=0 even
-	 * if dev mode was on in the (inaccessible) secdata_firmware.
+	 * if dev mode was on in the (inaccessible) secdata_firmware. Since this
+	 * happens in fw_phase1, we do not abort -- we know that when secdata
+	 * is uninitialized here, we must be headed for recovery mode.
 	 */
 	reset_common_data();
 	vb2_secdata_firmware_set(ctx, VB2_SECDATA_FIRMWARE_FLAGS,
 				 VB2_SECDATA_FIRMWARE_FLAG_DEV_MODE);
 	sd->status &= ~VB2_SD_STATUS_SECDATA_FIRMWARE_INIT;
-	TEST_ABORT(vb2_check_dev_switch(ctx), "secdata_firmware fail normal");
+	TEST_SUCC(vb2_check_dev_switch(ctx), "secdata_firmware fail normal");
 	TEST_EQ(sd->flags & VB2_SD_FLAG_DEV_MODE_ENABLED, 0, "  sd not in dev");
 	TEST_EQ(ctx->flags & VB2_CONTEXT_DEVELOPER_MODE, 0, "  ctx not in dev");
 
@@ -640,7 +648,6 @@
 static void enable_dev_tests(void)
 {
 	reset_common_data();
-	allow_recovery_retval = 0;
 	TEST_FAIL(vb2api_enable_developer_mode(ctx),
 		 "vb2api_enable_developer_mode - failed");
 	TEST_EQ(vb2_secdata_firmware_get(ctx, VB2_SECDATA_FIRMWARE_FLAGS) &
@@ -648,19 +655,18 @@
 		"  dev mode flag not set");
 
 	reset_common_data();
-	allow_recovery_retval = 1;
+	*boot_mode = VB2_BOOT_MODE_MANUAL_RECOVERY;
 	TEST_SUCC(vb2api_enable_developer_mode(ctx),
 		  "vb2api_enable_developer_mode - success");
 	TEST_NEQ(vb2_secdata_firmware_get(ctx, VB2_SECDATA_FIRMWARE_FLAGS) &
 	         VB2_SECDATA_FIRMWARE_FLAG_DEV_MODE, 0,
 		 "  dev mode flag set");
-	TEST_EQ(vb2_nv_get(ctx, VB2_NV_DEV_BOOT_EXTERNAL), BOOT_EXTERNAL_ON_DEV,
-		"  NV_DEV_BOOT_EXTERNAL set according to compile-time flag");
 
 	/* secdata_firmware not initialized, aborts */
 	reset_common_data();
-	allow_recovery_retval = 1;
+	*boot_mode = VB2_BOOT_MODE_MANUAL_RECOVERY;
 	sd->status &= ~VB2_SD_STATUS_SECDATA_FIRMWARE_INIT;
+	sd->status |= VB2_SD_STATUS_RECOVERY_DECIDED;
 	TEST_ABORT(vb2api_enable_developer_mode(ctx),
 		   "secdata_firmware no init, enable dev mode aborted");
 	sd->status |= VB2_SD_STATUS_SECDATA_FIRMWARE_INIT;
@@ -810,7 +816,7 @@
 
 	/* Manual recovery */
 	reset_common_data();
-	allow_recovery_retval = 1;
+	*boot_mode = VB2_BOOT_MODE_MANUAL_RECOVERY;
 	sd->recovery_reason = 4;
 	ctx->flags |= VB2_CONTEXT_RECOVERY_MODE;
 	vb2_nv_set(ctx, VB2_NV_RECOVERY_REQUEST, 5);
@@ -821,9 +827,9 @@
 	TEST_EQ(vb2_nv_get(ctx, VB2_NV_RECOVERY_SUBCODE),
 		0, "  subcode cleared");
 
-	/* BROKEN recovery */
+	/* Broken screen */
 	reset_common_data();
-	allow_recovery_retval = 0;
+	*boot_mode = VB2_BOOT_MODE_BROKEN_SCREEN;
 	sd->recovery_reason = 4;
 	ctx->flags |= VB2_CONTEXT_RECOVERY_MODE;
 	vb2_nv_set(ctx, VB2_NV_RECOVERY_REQUEST, 5);
diff --git a/tests/vb2_secdata_firmware_tests.c b/tests/vb2_secdata_firmware_tests.c
index 387cda3..c1b0a73 100644
--- a/tests/vb2_secdata_firmware_tests.c
+++ b/tests/vb2_secdata_firmware_tests.c
@@ -28,6 +28,9 @@
 
 	sd = vb2_get_sd(ctx);
 
+	/* Most tests assume we have passed fw_phase1() */
+	sd->status |= VB2_SD_STATUS_RECOVERY_DECIDED;
+
 	sec = (struct vb2_secdata_firmware *)ctx->secdata_firmware;
 }
 
@@ -136,6 +139,25 @@
 					    0x123456ff),
 		   "Set uninitialized");
 	test_changed(ctx, 0, "Set uninitialized doesn't change data");
+
+	/* Read/write uninitialized in recovery mode */
+	ctx->flags |= VB2_CONTEXT_RECOVERY_MODE;
+	TEST_EQ(vb2_secdata_firmware_get(ctx, VB2_SECDATA_FIRMWARE_VERSIONS), 0,
+		"Get uninitialized (recmode)");
+	test_changed(ctx, 0, "Get uninitialized (recmode) doesn't change data");
+	vb2_secdata_firmware_set(ctx, VB2_SECDATA_FIRMWARE_VERSIONS,
+				 0x123456ff);
+	test_changed(ctx, 0, "Set uninitialized (recmode) doesn't change data");
+
+	/* Read/write early in fw_phase1 */
+	ctx->flags &= ~VB2_CONTEXT_RECOVERY_MODE;
+	sd->status &= ~VB2_SD_STATUS_RECOVERY_DECIDED;
+	TEST_EQ(vb2_secdata_firmware_get(ctx, VB2_SECDATA_FIRMWARE_VERSIONS), 0,
+		"Get uninitialized (phase1)");
+	test_changed(ctx, 0, "Get uninitialized (phase1) doesn't change data");
+	vb2_secdata_firmware_set(ctx, VB2_SECDATA_FIRMWARE_VERSIONS,
+				 0x123456ff);
+	test_changed(ctx, 0, "Set uninitialized (phase1) doesn't change data");
 }
 
 int main(int argc, char* argv[])
diff --git a/tests/vb2_secdata_fwmp_tests.c b/tests/vb2_secdata_fwmp_tests.c
index 699f3fa..79b0f55 100644
--- a/tests/vb2_secdata_fwmp_tests.c
+++ b/tests/vb2_secdata_fwmp_tests.c
@@ -25,7 +25,8 @@
 		  "vb2api_init failed");
 
 	sd = vb2_get_sd(ctx);
-	sd->status = VB2_SD_STATUS_SECDATA_FWMP_INIT;
+	sd->status |= VB2_SD_STATUS_SECDATA_FWMP_INIT;
+	sd->status |= VB2_SD_STATUS_RECOVERY_DECIDED;
 
 	memset(&gbb, 0, sizeof(gbb));
 
@@ -199,6 +200,13 @@
 	sd->status &= ~VB2_SD_STATUS_SECDATA_FWMP_INIT;
 	TEST_ABORT(vb2_secdata_fwmp_get_flag(ctx, 0),
 		   "non-init in normal mode triggers abort");
+
+	/* FWMP hasn't been initialized (before recovery decision) */
+	reset_common_data();
+	sd->status &= ~VB2_SD_STATUS_SECDATA_FWMP_INIT;
+	sd->status &= ~VB2_SD_STATUS_RECOVERY_DECIDED;
+	TEST_EQ(vb2_secdata_fwmp_get_flag(ctx, 0), 0,
+		"non-init in fw_phase1 forces default flag value");
 }
 
 static void get_dev_key_hash_test(void)
@@ -222,6 +230,13 @@
 	TEST_ABORT(vb2_secdata_fwmp_get_dev_key_hash(ctx),
 		   "non-init in normal mode triggers abort");
 
+	/* FWMP hasn't been initialized (before recovery decision) */
+	reset_common_data();
+	sd->status &= ~VB2_SD_STATUS_SECDATA_FWMP_INIT;
+	sd->status &= ~VB2_SD_STATUS_RECOVERY_DECIDED;
+	TEST_TRUE(vb2_secdata_fwmp_get_dev_key_hash(ctx) == NULL,
+		  "non-init in fw_phase1 forces NULL pointer");
+
 	/* Success case */
 	reset_common_data();
 	TEST_TRUE(vb2_secdata_fwmp_get_dev_key_hash(ctx) ==
diff --git a/tests/vb2_secdata_kernel_tests.c b/tests/vb2_secdata_kernel_tests.c
index 327018d..dc41f1a 100644
--- a/tests/vb2_secdata_kernel_tests.c
+++ b/tests/vb2_secdata_kernel_tests.c
@@ -30,6 +30,9 @@
 
 	sd = vb2_get_sd(ctx);
 
+	/* Most tests assume we have passed fw_phase1() */
+	sd->status |= VB2_SD_STATUS_RECOVERY_DECIDED;
+
 	sec02 = (struct vb2_secdata_kernel_v0 *)ctx->secdata_kernel;
 	sec10 = (struct vb2_secdata_kernel_v1 *)ctx->secdata_kernel;
 }
@@ -245,7 +248,27 @@
 		   "Set uninitialized");
 	test_changed(ctx, 0, "Set uninitialized doesn't change data");
 
+	/* Read/write uninitialized in recovery mode */
+	ctx->flags |= VB2_CONTEXT_RECOVERY_MODE;
+	TEST_EQ(vb2_secdata_kernel_get(ctx, VB2_SECDATA_KERNEL_VERSIONS), 0,
+		"Get uninitialized (recmode)");
+	test_changed(ctx, 0, "Get uninitialized (recmode) doesn't change data");
+	vb2_secdata_kernel_set(ctx, VB2_SECDATA_KERNEL_VERSIONS,
+				 0x123456ff);
+	test_changed(ctx, 0, "Set uninitialized (recmode) doesn't change data");
+
+	/* Read/write early in fw_phase1 */
+	ctx->flags &= ~VB2_CONTEXT_RECOVERY_MODE;
+	sd->status &= ~VB2_SD_STATUS_RECOVERY_DECIDED;
+	TEST_EQ(vb2_secdata_kernel_get(ctx, VB2_SECDATA_KERNEL_VERSIONS), 0,
+		"Get uninitialized (phase1)");
+	test_changed(ctx, 0, "Get uninitialized (phase1) doesn't change data");
+	vb2_secdata_kernel_set(ctx, VB2_SECDATA_KERNEL_VERSIONS,
+				 0x123456ff);
+	test_changed(ctx, 0, "Set uninitialized (phase1) doesn't change data");
+
 	/* Test EC hash set */
+	reset_common_data();
 	vb2api_secdata_kernel_create(ctx);
 	vb2_secdata_kernel_init(ctx);
 	memset(ec_hash, 0xaa, sizeof(ec_hash));
diff --git a/tests/vboot_api_kernel4_tests.c b/tests/vboot_api_kernel4_tests.c
index f122eb3..60d87c6 100644
--- a/tests/vboot_api_kernel4_tests.c
+++ b/tests/vboot_api_kernel4_tests.c
@@ -80,6 +80,9 @@
 		ctx->flags |= VB2_CONTEXT_RECOVERY_MODE;
 
 	expected_recovery_reason = recovery_reason;
+	/* The VbSelectAndLoadKernel directly leverages the value at
+	   ctx->boot_mode, so we have to call vb2_set_boot_mode first. */
+	vb2_set_boot_mode(ctx);
 	TEST_EQ(VbSelectAndLoadKernel(ctx, &kparams), retval, desc);
 	TEST_EQ(vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST),
 		recovery_reason, "  recovery reason");
@@ -259,12 +262,14 @@
 	/* Recovery - VB2_ERROR_ESCAPE_NO_BOOT */
 	reset_common_data();
 	ctx->flags |= VB2_CONTEXT_NO_BOOT;
+	ctx->flags |= VB2_CONTEXT_EC_SYNC_SUPPORTED;
 	test_slk(VB2_ERROR_ESCAPE_NO_BOOT,
 		 VB2_RECOVERY_ESCAPE_NO_BOOT, "Recovery for NO_BOOT escape");
 
 	/* Boot normal - VB2_ERROR_ESCAPE_NO_BOOT */
 	reset_common_data();
 	ctx->flags |= VB2_CONTEXT_NO_BOOT;
+	ctx->flags |= VB2_CONTEXT_EC_SYNC_SUPPORTED;
 	gbb.flags |= VB2_GBB_FLAG_DISABLE_EC_SOFTWARE_SYNC;
 	test_slk(VB2_SUCCESS, 0, "DISABLE_EC_SOFTWARE_SYNC ignores NO_BOOT");
 
@@ -325,7 +330,7 @@
 	reset_common_data();
 	sd->recovery_reason = VB2_RECOVERY_RO_MANUAL;
 	vb2_nv_set(ctx, VB2_NV_RECOVERY_SUBCODE, 13);
-	sd->flags |= VB2_SD_FLAG_MANUAL_RECOVERY;
+	ctx->flags &= VB2_CONTEXT_FORCE_RECOVERY_MODE;
 	test_slk(0, 0, "Manual recovery");
 	TEST_TRUE(commit_data_called, "  commit data");
 }
diff --git a/tests/vboot_api_kernel_tests.c b/tests/vboot_api_kernel_tests.c
index 51f0ddb..b66c432 100644
--- a/tests/vboot_api_kernel_tests.c
+++ b/tests/vboot_api_kernel_tests.c
@@ -563,7 +563,7 @@
 
 vb2_error_t LoadMiniOsKernel(struct vb2_context *c,
 			     VbSelectAndLoadKernelParams *params,
-			     VbDiskInfo *disk_info)
+			     VbDiskInfo *disk_info, uint32_t minios_flags)
 {
 	lk_minios_calls++;
 	return LoadKernelImpl(c, params, disk_info);
@@ -616,7 +616,7 @@
 		printf("Test case: %s ...\n", minios_tests[i].name);
 		ResetMocks(&minios_tests[i]);
 		ctx->flags = t->ctx_flags;
-		TEST_EQ(VbTryLoadMiniOsKernel(ctx),
+		TEST_EQ(VbTryLoadMiniOsKernel(ctx, 0),
 			t->expected_return_val, "  return value");
 		TEST_EQ(got_recovery_request_val,
 			t->expected_recovery_request_val, "  recovery_request");
diff --git a/tests/vboot_kernel2_tests.c b/tests/vboot_kernel2_tests.c
index 5424e86..5fc66f1 100644
--- a/tests/vboot_kernel2_tests.c
+++ b/tests/vboot_kernel2_tests.c
@@ -47,6 +47,7 @@
 static struct vb2_workbuf wb;
 static uint8_t workbuf[VB2_KERNEL_WORKBUF_RECOMMENDED_SIZE]
 	__attribute__((aligned(VB2_WORKBUF_ALIGN)));
+static enum vb2_boot_mode *boot_mode;
 
 static VbSelectAndLoadKernelParams lkp;
 static VbDiskInfo disk_info;
@@ -82,6 +83,9 @@
 	vb2_secdata_kernel_init(ctx);
 	ctx->flags = VB2_CONTEXT_RECOVERY_MODE;
 
+	boot_mode = (enum vb2_boot_mode *)&ctx->boot_mode;
+	*boot_mode = VB2_BOOT_MODE_MANUAL_RECOVERY;
+
 	sd = vb2_get_sd(ctx);
 	sd->kernel_version_secdata = 0xabcdef | (1 << 24);
 
@@ -264,7 +268,7 @@
 	disk_info.bytes_per_lba = KBUF_SIZE;
 	disk_info.lba_count = 1;
 	add_mock_kernel(0, VB2_SUCCESS);
-	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		  "{valid kernel}");
 	TEST_EQ(mock_tpm_set_mode_calls, 1,
 		"  TPM disabled");
@@ -272,7 +276,7 @@
 	reset_common_data();
 	disk_info.bytes_per_lba = KBUF_SIZE;
 	disk_info.lba_count = 1;
-	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		VB2_ERROR_LK_NO_KERNEL_FOUND, "{no kernel}");
 	TEST_EQ(mock_tpm_set_mode_calls, 0,
 		"  TPM not disabled");
@@ -281,7 +285,7 @@
 	disk_info.bytes_per_lba = KBUF_SIZE;
 	disk_info.lba_count = 2;
 	add_mock_kernel(1, VB2_SUCCESS);
-	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		  "{no kernel, valid kernel}");
 	TEST_EQ(cur_kernel->sector, 1, "  select kernel");
 
@@ -290,7 +294,7 @@
 	disk_info.lba_count = 2;
 	add_mock_kernel(0, VB2_ERROR_MOCK);
 	add_mock_kernel(1, VB2_SUCCESS);
-	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		  "{invalid kernel, valid kernel}");
 	TEST_EQ(cur_kernel->sector, 1, "  select second kernel");
 
@@ -299,7 +303,7 @@
 	disk_info.lba_count = 2;
 	add_mock_kernel(0, VB2_ERROR_MOCK);
 	add_mock_kernel(1, VB2_ERROR_MOCK);
-	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		VB2_ERROR_LK_NO_KERNEL_FOUND,
 		"{invalid kernel, invalid kernel}");
 	TEST_EQ(mock_tpm_set_mode_calls, 0,
@@ -310,7 +314,7 @@
 	disk_info.lba_count = 2;
 	add_mock_kernel(0, VB2_SUCCESS);
 	add_mock_kernel(1, VB2_SUCCESS);
-	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		  "{valid kernel, valid kernel} minios_priority=0");
 	TEST_EQ(cur_kernel->sector, 0, "  select first kernel");
 
@@ -320,15 +324,36 @@
 	add_mock_kernel(0, VB2_SUCCESS);
 	add_mock_kernel(1, VB2_SUCCESS);
 	vb2_nv_set(ctx, VB2_NV_MINIOS_PRIORITY, 1);
-	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		  "{valid kernel, valid kernel} minios_priority=1");
 	TEST_EQ(cur_kernel->sector, 1, "  select second kernel");
 
 	reset_common_data();
+	disk_info.bytes_per_lba = KBUF_SIZE;
+	disk_info.lba_count = 2;
+	add_mock_kernel(0, VB2_SUCCESS);
+	add_mock_kernel(1, VB2_SUCCESS);
+	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info,
+				   VB_MINIOS_FLAG_NON_ACTIVE),
+		  "{valid kernel, valid kernel} minios_priority=0 non-active");
+	TEST_EQ(cur_kernel->sector, 1, "  select second kernel");
+
+	reset_common_data();
+	disk_info.bytes_per_lba = KBUF_SIZE;
+	disk_info.lba_count = 2;
+	add_mock_kernel(0, VB2_ERROR_MOCK);
+	add_mock_kernel(1, VB2_SUCCESS);
+	vb2_nv_set(ctx, VB2_NV_MINIOS_PRIORITY, 1);
+	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info,
+				 VB_MINIOS_FLAG_NON_ACTIVE),
+		VB2_ERROR_LK_NO_KERNEL_FOUND,
+		"{invalid kernel, valid kernel} minios_priority=1 non-active");
+
+	reset_common_data();
 	disk_info.bytes_per_lba = VB2_KEYBLOCK_MAGIC_SIZE;
 	disk_info.lba_count = 4;
 	add_mock_kernel(1, VB2_SUCCESS);
-	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		VB2_ERROR_LK_NO_KERNEL_FOUND,
 		"valid kernel header near start of disk (disk too small)");
 
@@ -336,7 +361,7 @@
 	disk_info.bytes_per_lba = VB2_KEYBLOCK_MAGIC_SIZE;
 	disk_info.lba_count = 1000;
 	add_mock_kernel(999, VB2_SUCCESS);
-	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		VB2_ERROR_LK_NO_KERNEL_FOUND,
 		"valid kernel header near end of disk");
 
@@ -344,35 +369,35 @@
 	disk_info.bytes_per_lba = 1024;
 	disk_info.lba_count = 128;
 	add_mock_kernel(63, VB2_SUCCESS);
-	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		  "start/end overlap assuming >128 MB search range (start)");
 
 	reset_common_data();
 	disk_info.bytes_per_lba = 1024;
 	disk_info.lba_count = 128;
 	add_mock_kernel(64, VB2_SUCCESS);
-	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		  "start/end overlap assuming >128 MB search range (end)");
 
 	reset_common_data();
 	disk_info.bytes_per_lba = 128;
 	disk_info.lba_count = 1024;
 	add_mock_kernel(3, VB2_SUCCESS);
-	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		  "kernel at last sector in batch assuming 512 KB batches");
 
 	reset_common_data();
 	disk_info.bytes_per_lba = 256;
 	disk_info.lba_count = 1024;
 	add_mock_kernel(3, VB2_SUCCESS);
-	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		  "kernel at last sector in batch assuming 1 MB batches");
 
 	reset_common_data();
 	disk_info.bytes_per_lba = 512;
 	disk_info.lba_count = 1024;
 	add_mock_kernel(3, VB2_SUCCESS);
-	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		  "kernel at last sector in batch assuming 2 MB batches");
 
 	reset_common_data();
@@ -382,7 +407,7 @@
 	disk_info.bytes_per_lba = KBUF_SIZE;
 	disk_info.lba_count = 2;
 	add_mock_kernel(0, VB2_SUCCESS);
-	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		  "kernel with minios keyblock flag");
 
 	reset_common_data();
@@ -392,7 +417,7 @@
 	disk_info.bytes_per_lba = KBUF_SIZE;
 	disk_info.lba_count = 2;
 	add_mock_kernel(0, VB2_SUCCESS);
-	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		VB2_ERROR_LK_NO_KERNEL_FOUND,
 		"kernel with !minios keyblock flag");
 
@@ -402,7 +427,7 @@
 	add_mock_kernel(0, VB2_SUCCESS);
 	sd->kernel_version_secdata = 5 << 24;
 	kph.kernel_version = 4;
-	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		VB2_ERROR_LK_NO_KERNEL_FOUND,
 		"kernel version too old");
 
@@ -412,7 +437,7 @@
 	add_mock_kernel(0, VB2_SUCCESS);
 	sd->kernel_version_secdata = 5 << 24;
 	kph.kernel_version = 0x100;
-	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		VB2_ERROR_LK_NO_KERNEL_FOUND,
 		"kernel version greater than 0xff");
 
@@ -422,7 +447,7 @@
 	add_mock_kernel(0, VB2_SUCCESS);
 	sd->kernel_version_secdata = 5 << 24;
 	kph.kernel_version = 6;
-	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
+	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
 		 "newer kernel version");
 }
 
diff --git a/tests/vboot_kernel_tests.c b/tests/vboot_kernel_tests.c
index 14e7e1f..1f77cc5 100644
--- a/tests/vboot_kernel_tests.c
+++ b/tests/vboot_kernel_tests.c
@@ -51,6 +51,7 @@
 static struct vb2_context *ctx;
 static struct vb2_shared_data *sd;
 static struct vb2_packed_key mock_key;
+static enum vb2_boot_mode *boot_mode;
 
 /**
  * Reset mock data (for use before each test)
@@ -114,6 +115,9 @@
 	fwmp = (struct vb2_secdata_fwmp *)ctx->secdata_fwmp;
 	memcpy(&fwmp->dev_key_hash, mock_digest, sizeof(fwmp->dev_key_hash));
 
+	boot_mode = (enum vb2_boot_mode *)&ctx->boot_mode;
+	*boot_mode = VB2_BOOT_MODE_NORMAL;
+
 	// TODO: more workbuf fields - flags, secdata_firmware
 
 	vb2api_secdata_kernel_create(ctx);
@@ -337,6 +341,7 @@
 	/* In dev mode, fail if hash is bad too */
 	ResetMocks();
 	ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE;
+	*boot_mode = VB2_BOOT_MODE_DEVELOPER;
 	keyblock_verify_fail = 2;
 	TestLoadKernel(VB2_ERROR_LK_INVALID_KERNEL_FOUND,
 		       "Fail key block dev hash");
@@ -344,6 +349,7 @@
 	/* But just bad sig is ok */
 	ResetMocks();
 	ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE;
+	*boot_mode = VB2_BOOT_MODE_DEVELOPER;
 	keyblock_verify_fail = 1;
 	TestLoadKernel(0, "Succeed keyblock dev sig");
 	TEST_EQ(sd->flags & VB2_SD_FLAG_KERNEL_SIGNED, 0, "  use hash");
@@ -351,6 +357,7 @@
 	/* In dev mode and requiring signed kernel, fail if sig is bad */
 	ResetMocks();
 	ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE;
+	*boot_mode = VB2_BOOT_MODE_DEVELOPER;
 	vb2_nv_set(ctx, VB2_NV_DEV_BOOT_SIGNED_ONLY, 1);
 	keyblock_verify_fail = 1;
 	TestLoadKernel(VB2_ERROR_LK_INVALID_KERNEL_FOUND,
@@ -358,6 +365,7 @@
 
 	ResetMocks();
 	ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE;
+	*boot_mode = VB2_BOOT_MODE_DEVELOPER;
 	fwmp->flags |= VB2_SECDATA_FWMP_DEV_ENABLE_OFFICIAL_ONLY;
 	keyblock_verify_fail = 1;
 	TestLoadKernel(VB2_ERROR_LK_INVALID_KERNEL_FOUND,
@@ -387,6 +395,7 @@
 
 	ResetMocks();
 	ctx->flags |= VB2_CONTEXT_RECOVERY_MODE;
+	*boot_mode = VB2_BOOT_MODE_MANUAL_RECOVERY;
 	kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_1
 		| VB2_KEYBLOCK_FLAG_DEVELOPER_1
 		| VB2_KEYBLOCK_FLAG_MINIOS_0;
@@ -395,6 +404,7 @@
 
 	ResetMocks();
 	ctx->flags |= VB2_CONTEXT_RECOVERY_MODE;
+	*boot_mode = VB2_BOOT_MODE_MANUAL_RECOVERY;
 	kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_1
 		| VB2_KEYBLOCK_FLAG_DEVELOPER_0
 		| VB2_KEYBLOCK_FLAG_MINIOS_0;
@@ -402,6 +412,7 @@
 
 	ResetMocks();
 	ctx->flags |= VB2_CONTEXT_RECOVERY_MODE | VB2_CONTEXT_DEVELOPER_MODE;
+	*boot_mode = VB2_BOOT_MODE_MANUAL_RECOVERY;
 	kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_1
 		| VB2_KEYBLOCK_FLAG_DEVELOPER_0
 		| VB2_KEYBLOCK_FLAG_MINIOS_0;
@@ -410,6 +421,7 @@
 
 	ResetMocks();
 	ctx->flags |= VB2_CONTEXT_RECOVERY_MODE | VB2_CONTEXT_DEVELOPER_MODE;
+	*boot_mode = VB2_BOOT_MODE_MANUAL_RECOVERY;
 	kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_1
 		| VB2_KEYBLOCK_FLAG_DEVELOPER_1
 		| VB2_KEYBLOCK_FLAG_MINIOS_0;
@@ -418,6 +430,7 @@
 	/* Check keyblock flags (dev mode + signed kernel required) */
 	ResetMocks();
 	ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE;
+	*boot_mode = VB2_BOOT_MODE_DEVELOPER;
 	vb2_nv_set(ctx, VB2_NV_DEV_BOOT_SIGNED_ONLY, 1);
 	kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_1
 		| VB2_KEYBLOCK_FLAG_DEVELOPER_0
@@ -427,6 +440,7 @@
 
 	ResetMocks();
 	ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE;
+	*boot_mode = VB2_BOOT_MODE_DEVELOPER;
 	fwmp->flags |= VB2_SECDATA_FWMP_DEV_ENABLE_OFFICIAL_ONLY;
 	kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_1
 		| VB2_KEYBLOCK_FLAG_DEVELOPER_0
@@ -436,6 +450,7 @@
 
 	ResetMocks();
 	ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE;
+	*boot_mode = VB2_BOOT_MODE_DEVELOPER;
 	fwmp->flags |= VB2_SECDATA_FWMP_DEV_ENABLE_OFFICIAL_ONLY;
 	kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_0
 		| VB2_KEYBLOCK_FLAG_DEVELOPER_0
@@ -445,6 +460,7 @@
 
 	ResetMocks();
 	ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE;
+	*boot_mode = VB2_BOOT_MODE_DEVELOPER;
 	vb2_nv_set(ctx, VB2_NV_DEV_BOOT_SIGNED_ONLY, 1);
 	kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_0
 		| VB2_KEYBLOCK_FLAG_DEVELOPER_1
@@ -477,12 +493,12 @@
 
 	ResetMocks();
 	kbh.data_key.key_version = 1;
-	ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE;
+	*boot_mode = VB2_BOOT_MODE_DEVELOPER;
 	TestLoadKernel(0, "Key version ignored in dev mode");
 
 	ResetMocks();
 	kbh.data_key.key_version = 1;
-	ctx->flags |= VB2_CONTEXT_RECOVERY_MODE;
+	*boot_mode = VB2_BOOT_MODE_MANUAL_RECOVERY;
 	TestLoadKernel(0, "Key version ignored in rec mode");
 
 	ResetMocks();
@@ -500,18 +516,18 @@
 
 	ResetMocks();
 	kph.kernel_version = 0;
-	ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE;
+	*boot_mode = VB2_BOOT_MODE_DEVELOPER;
 	TestLoadKernel(0, "Kernel version ignored in dev mode");
 
 	ResetMocks();
 	kph.kernel_version = 0;
-	ctx->flags |= VB2_CONTEXT_RECOVERY_MODE;
+	*boot_mode = VB2_BOOT_MODE_MANUAL_RECOVERY;
 	TestLoadKernel(0, "Kernel version ignored in rec mode");
 
 	/* Check kernel version (dev mode + signed kernel required) */
 	ResetMocks();
 	kbh.data_key.key_version = 0;
-	ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE;
+	*boot_mode = VB2_BOOT_MODE_DEVELOPER;
 	vb2_nv_set(ctx, VB2_NV_DEV_BOOT_SIGNED_ONLY, 1);
 	TestLoadKernel(VB2_ERROR_LK_INVALID_KERNEL_FOUND,
 		       "Keyblock key version checked in dev mode "
@@ -519,7 +535,7 @@
 
 	ResetMocks();
 	kbh.data_key.key_version = 0;
-	ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE;
+	*boot_mode = VB2_BOOT_MODE_DEVELOPER;
 	fwmp->flags |= VB2_SECDATA_FWMP_DEV_ENABLE_OFFICIAL_ONLY;
 	TestLoadKernel(VB2_ERROR_LK_INVALID_KERNEL_FOUND,
 		       "Keyblock key version checked in dev mode "
@@ -527,7 +543,7 @@
 
 	/* Check developer key hash - bad */
 	ResetMocks();
-	ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE;
+	*boot_mode = VB2_BOOT_MODE_DEVELOPER;
 	fwmp->flags |= VB2_SECDATA_FWMP_DEV_USE_KEY_HASH;
 	fwmp->dev_key_hash[0]++;
 	TestLoadKernel(VB2_ERROR_LK_INVALID_KERNEL_FOUND,
@@ -535,15 +551,14 @@
 
 	/* Check developer key hash - bad (recovery mode) */
 	ResetMocks();
-	ctx->flags |= VB2_CONTEXT_RECOVERY_MODE;
-	ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE;
+	*boot_mode = VB2_BOOT_MODE_MANUAL_RECOVERY;
 	fwmp->flags |= VB2_SECDATA_FWMP_DEV_USE_KEY_HASH;
 	fwmp->dev_key_hash[0]++;
 	TestLoadKernel(0, "Bad keyblock dev fwmp hash ignored in rec mode");
 
 	/* Check developer key hash - good */
 	ResetMocks();
-	ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE;
+	*boot_mode = VB2_BOOT_MODE_DEVELOPER;
 	fwmp->flags |= VB2_SECDATA_FWMP_DEV_USE_KEY_HASH;
 	TestLoadKernel(0, "Good keyblock dev fwmp hash");
 
diff --git a/unblocked_terms.txt b/unblocked_terms.txt
index c54d786..e9ad8c2 100644
--- a/unblocked_terms.txt
+++ b/unblocked_terms.txt
@@ -1,3 +1,2 @@
 dummy
-master
-slave
\ No newline at end of file
+master
\ No newline at end of file
diff --git a/utility/chromeos-tpm-recovery b/utility/chromeos-tpm-recovery
index ac7dfcc..12616b4 100755
--- a/utility/chromeos-tpm-recovery
+++ b/utility/chromeos-tpm-recovery
@@ -30,9 +30,9 @@
 }
 
 use_v0_secdata_kernel() {
-  local fwid=$(crossystem ro_fwid)
-  local major=$(printf "$fwid" | cut -d. -f2)
-  local minor=$(printf "$fwid" | cut -d. -f3)
+  local fwid="$(crossystem ro_fwid)"
+  local major="$(printf "$fwid" | cut -d. -f2)"
+  local minor="$(printf "$fwid" | cut -d. -f3)"
 
   # TPM1 firmware never supports the v1 kernel space format.
   if ! tpm2_target; then