vboot: Implement dev mode boot options for menu UI

Add dev default boot retrieval and dev boot related allowance check api in 2misc
along with their tests in vb2_misc_tests:
- vb2_get_dev_boot_target
- vb2_dev_boot_allowed
- vb2_dev_boot_legacy_allowed
- vb2_dev_boot_usb_allowed

Implement parts of vb2_developer_menu functionalities along with tests:
- Retrieve the default boot option.
- Try to boot with the specified option.

BRANCH=none
BUG=b:146399181, chromium:1033815
TEST=USE="menu_ui" emerge-nami vboot_reference depthcharge
TEST=make runtests

Change-Id: Ie82076f93b86ba5abe26a9e3e25076892684855d
Signed-off-by: Hsuan Ting Chen <roccochen@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/2094508
Reviewed-by: Joel Kitching <kitching@chromium.org>
diff --git a/firmware/2lib/2misc.c b/firmware/2lib/2misc.c
index faf2da7..ac9d0f2 100644
--- a/firmware/2lib/2misc.c
+++ b/firmware/2lib/2misc.c
@@ -495,3 +495,43 @@
 }
 _Static_assert(VB2_VBSD_SIZE == sizeof(VbSharedDataHeader),
 	       "VB2_VBSD_SIZE incorrect");
+
+enum vb2_dev_default_boot vb2_get_dev_boot_target(
+	struct vb2_context *ctx)
+{
+	struct vb2_gbb_header *gbb = vb2_get_gbb(ctx);
+
+	if (gbb->flags & VB2_GBB_FLAG_DEFAULT_DEV_BOOT_LEGACY)
+		return VB2_DEV_DEFAULT_BOOT_LEGACY;
+
+	return vb2_nv_get(ctx, VB2_NV_DEV_DEFAULT_BOOT);
+}
+
+int vb2_dev_boot_allowed(struct vb2_context *ctx)
+{
+	struct vb2_gbb_header *gbb = vb2_get_gbb(ctx);
+
+	if (vb2_secdata_fwmp_get_flag(ctx, VB2_SECDATA_FWMP_DEV_DISABLE_BOOT))
+		return !!(gbb->flags & VB2_GBB_FLAG_FORCE_DEV_SWITCH_ON);
+
+	return 1;
+}
+
+int vb2_dev_boot_legacy_allowed(struct vb2_context *ctx)
+{
+	struct vb2_gbb_header *gbb = vb2_get_gbb(ctx);
+
+	return vb2_nv_get(ctx, VB2_NV_DEV_BOOT_LEGACY) ||
+	       (gbb->flags & VB2_GBB_FLAG_FORCE_DEV_BOOT_LEGACY) ||
+	       vb2_secdata_fwmp_get_flag(ctx,
+					 VB2_SECDATA_FWMP_DEV_ENABLE_LEGACY);
+}
+
+int vb2_dev_boot_usb_allowed(struct vb2_context *ctx)
+{
+	struct vb2_gbb_header *gbb = vb2_get_gbb(ctx);
+
+	return vb2_nv_get(ctx, VB2_NV_DEV_BOOT_USB) ||
+	       (gbb->flags & VB2_GBB_FLAG_FORCE_DEV_BOOT_USB) ||
+	       vb2_secdata_fwmp_get_flag(ctx, VB2_SECDATA_FWMP_DEV_ENABLE_USB);
+}
diff --git a/firmware/2lib/2ui.c b/firmware/2lib/2ui.c
index b6892cb..25037c9 100644
--- a/firmware/2lib/2ui.c
+++ b/firmware/2lib/2ui.c
@@ -7,19 +7,42 @@
 
 #include "2api.h"
 #include "2common.h"
+#include "2misc.h"
+#include "2nvstorage.h"
+#include "2return_codes.h"
+#include "2secdata.h"
 #include "2ui.h"
+#include "vboot_kernel.h"
 
 /*****************************************************************************/
 /* Entry points */
 
 vb2_error_t vb2_developer_menu(struct vb2_context *ctx)
 {
+	enum vb2_dev_default_boot default_boot;
+
 	/* TODO(roccochen): Init, wait for user, and boot. */
 	vb2ex_display_ui(VB2_SCREEN_BLANK, 0);
 
-	while (1);
+	/* If dev mode was disabled, loop forever. */
+	if (!vb2_dev_boot_allowed(ctx))
+		while (1);
 
-	return VB2_SUCCESS;
+	/* Boot from the default option. */
+	default_boot = vb2_get_dev_boot_target(ctx);
+
+	/* Boot legacy does not return on success */
+	if (default_boot == VB2_DEV_DEFAULT_BOOT_LEGACY &&
+	    vb2_dev_boot_legacy_allowed(ctx) &&
+	    VbExLegacy(VB_ALTFW_DEFAULT) == VB2_SUCCESS)
+		return VB2_SUCCESS;
+
+	if (default_boot == VB2_DEV_DEFAULT_BOOT_USB &&
+	    vb2_dev_boot_usb_allowed(ctx) &&
+	    VbTryLoadKernel(ctx, VB_DISK_FLAG_REMOVABLE) == VB2_SUCCESS)
+		return VB2_SUCCESS;
+
+	return VbTryLoadKernel(ctx, VB_DISK_FLAG_FIXED);
 }
 
 vb2_error_t vb2_broken_recovery_menu(struct vb2_context *ctx)
diff --git a/firmware/2lib/include/2misc.h b/firmware/2lib/include/2misc.h
index b80f690..94a93d4 100644
--- a/firmware/2lib/include/2misc.h
+++ b/firmware/2lib/include/2misc.h
@@ -210,4 +210,48 @@
  */
 void vb2_clear_recovery(struct vb2_context *ctx);
 
+/**
+ * Return the developer mode default boot option; see vb2_dev_default_boot.
+ *
+ * @param ctx		Vboot context
+ * @return The developer mode default boot option, or
+ *  VB2_DEV_DEFAULT_BOOT_DISK if not specified.
+ */
+enum vb2_dev_default_boot vb2_get_dev_boot_target(struct vb2_context *ctx);
+
+/**
+ * Determine if developer mode is allowed.
+ *
+ * Developer boot is not allowed if and only if FWMP_DEV_DISABLE_BOOT is set and
+ * GBB_FORCE_DEV_SWITCH_ON is not set.
+ *
+ * @param ctx		Vboot context
+ * @return 1 if allowed, or 0 otherwise.
+ */
+int vb2_dev_boot_allowed(struct vb2_context *ctx);
+
+/**
+ * Determine if booting from legacy BIOS is allowed.
+ *
+ * Legacy BIOS is allowed if one of the legacy-related flags is set:
+ * VB2_NV_DEV_BOOT_LEGACY, VB2_GBB_FLAG_FORCE_DEV_BOOT_LEGACY, and
+ * VB2_SECDATA_FWMP_DEV_ENABLE_LEGACY.
+ *
+ * @param ctx		Vboot context
+ * @return 1 if allowed, or 0 otherwise.
+ */
+int vb2_dev_boot_legacy_allowed(struct vb2_context *ctx);
+
+/**
+ * Determine if booting from USB or SD card is allowed.
+ *
+ * Booting from USB is allowed if one of the USB-related flags is set:
+ * VB2_NV_DEV_BOOT_USB, VB2_GBB_FLAG_FORCE_DEV_BOOT_USB, and
+ * VB2_SECDATA_FWMP_DEV_ENABLE_USB.
+ *
+ * @param ctx		Vboot context
+ * @return 1 if allowed, or 0 otherwise.
+ */
+int vb2_dev_boot_usb_allowed(struct vb2_context *ctx);
+
 #endif  /* VBOOT_REFERENCE_2MISC_H_ */
diff --git a/tests/vb2_misc_tests.c b/tests/vb2_misc_tests.c
index 89f534d..1d8b42e 100644
--- a/tests/vb2_misc_tests.c
+++ b/tests/vb2_misc_tests.c
@@ -21,6 +21,7 @@
 static struct vb2_context *ctx;
 static struct vb2_shared_data *sd;
 static struct vb2_gbb_header gbb;
+static struct vb2_secdata_fwmp *fwmp;
 
 /* Mocked function data */
 static enum vb2_resource_index mock_resource_index;
@@ -39,6 +40,7 @@
 		  "vb2api_init failed");
 
 	sd = vb2_get_sd(ctx);
+	sd->status = VB2_SD_STATUS_SECDATA_FWMP_INIT;
 
 	memset(&gbb, 0, sizeof(gbb));
 
@@ -47,6 +49,8 @@
 	vb2api_secdata_firmware_create(ctx);
 	vb2_secdata_firmware_init(ctx);
 
+	fwmp = (struct vb2_secdata_fwmp *)&ctx->secdata_fwmp;
+
 	mock_tpm_clear_called = 0;
 	mock_tpm_clear_retval = VB2_SUCCESS;
 	allow_recovery_retval = 0;
@@ -792,6 +796,119 @@
 	TEST_EQ(vb2api_get_recovery_reason(ctx), 4, "correct recovery reason");
 }
 
+static void dev_default_boot_tests(void)
+{
+	/* No default boot */
+	reset_common_data();
+	TEST_EQ(vb2_get_dev_boot_target(ctx), VB2_DEV_DEFAULT_BOOT_DISK,
+		"no default boot, boot disk");
+
+	/* Set boot legacy by GBB */
+	reset_common_data();
+	gbb.flags |= VB2_GBB_FLAG_DEFAULT_DEV_BOOT_LEGACY;
+	vb2_nv_set(ctx, VB2_NV_DEV_DEFAULT_BOOT, VB2_DEV_DEFAULT_BOOT_USB);
+	TEST_EQ(vb2_get_dev_boot_target(ctx), VB2_DEV_DEFAULT_BOOT_LEGACY,
+		"GBB set default boot legacy");
+
+	/* Boot from disk */
+	reset_common_data();
+	vb2_nv_set(ctx, VB2_NV_DEV_DEFAULT_BOOT, VB2_DEV_DEFAULT_BOOT_DISK);
+	TEST_EQ(vb2_get_dev_boot_target(ctx), VB2_DEV_DEFAULT_BOOT_DISK,
+		"set default boot disk");
+
+	/* Boot from usb */
+	reset_common_data();
+	vb2_nv_set(ctx, VB2_NV_DEV_DEFAULT_BOOT, VB2_DEV_DEFAULT_BOOT_USB);
+	TEST_EQ(vb2_get_dev_boot_target(ctx),
+		VB2_DEV_DEFAULT_BOOT_USB, "set default boot usb");
+
+	/* Boot legacy */
+	reset_common_data();
+	vb2_nv_set(ctx, VB2_NV_DEV_DEFAULT_BOOT, VB2_DEV_DEFAULT_BOOT_LEGACY);
+	TEST_EQ(vb2_get_dev_boot_target(ctx),
+		VB2_DEV_DEFAULT_BOOT_LEGACY, "set default boot legacy");
+}
+
+static void dev_boot_allowed_tests(void)
+{
+	/* Dev boot - allowed by default */
+	reset_common_data();
+	TEST_EQ(vb2_dev_boot_allowed(ctx), 1, "dev boot - allowed by default");
+
+	/* Dev boot - disabled by FWMP */
+	reset_common_data();
+	fwmp->flags |= VB2_SECDATA_FWMP_DEV_DISABLE_BOOT;
+	TEST_EQ(vb2_dev_boot_allowed(ctx), 0, "dev boot - FWMP disabled");
+
+	/* Dev boot - force enabled by GBB */
+	reset_common_data();
+	fwmp->flags |= VB2_SECDATA_FWMP_DEV_DISABLE_BOOT;
+	gbb.flags |= VB2_GBB_FLAG_FORCE_DEV_SWITCH_ON;
+	TEST_EQ(vb2_dev_boot_allowed(ctx), 1, "dev boot - GBB force dev on");
+
+	/* Legacy boot - not allowed by default */
+	reset_common_data();
+	TEST_EQ(vb2_dev_boot_legacy_allowed(ctx), 0,
+		"dev boot legacy - not allowed by default");
+
+	/* Legacy boot - enabled by nvdata */
+	reset_common_data();
+	vb2_nv_set(ctx, VB2_NV_DEV_BOOT_LEGACY, 1);
+	TEST_EQ(vb2_dev_boot_legacy_allowed(ctx), 1,
+		"dev boot legacy - nvdata enabled");
+
+	/* Legacy boot - enabled by FWMP */
+	reset_common_data();
+	fwmp->flags |= VB2_SECDATA_FWMP_DEV_ENABLE_LEGACY;
+	TEST_EQ(vb2_dev_boot_legacy_allowed(ctx), 1,
+		"dev boot legacy - secdata enabled");
+
+	/* Legacy boot - force enabled by GBB */
+	reset_common_data();
+	gbb.flags |= VB2_GBB_FLAG_FORCE_DEV_BOOT_LEGACY;
+	TEST_EQ(vb2_dev_boot_legacy_allowed(ctx), 1,
+		"dev boot legacy - GBB force enabled");
+
+	/* Legacy boot - set all flags */
+	reset_common_data();
+	vb2_nv_set(ctx, VB2_NV_DEV_BOOT_LEGACY, 1);
+	fwmp->flags |= VB2_SECDATA_FWMP_DEV_ENABLE_LEGACY;
+	gbb.flags |= VB2_GBB_FLAG_FORCE_DEV_BOOT_LEGACY;
+	TEST_EQ(vb2_dev_boot_legacy_allowed(ctx), 1,
+		"dev boot legacy - all flags set");
+
+	/* USB boot - not allowed by default */
+	reset_common_data();
+	TEST_EQ(vb2_dev_boot_usb_allowed(ctx), 0,
+		"dev boot usb - not allowed by default");
+
+	/* USB boot - enabled by nvdata */
+	reset_common_data();
+	vb2_nv_set(ctx, VB2_NV_DEV_BOOT_USB, 1);
+	TEST_EQ(vb2_dev_boot_usb_allowed(ctx), 1, "dev boot usb -"
+		" nvdata enabled");
+
+	/* USB boot - enabled by FWMP */
+	reset_common_data();
+	fwmp->flags |= VB2_SECDATA_FWMP_DEV_ENABLE_USB;
+	TEST_EQ(vb2_dev_boot_usb_allowed(ctx), 1,
+		"dev boot usb - secdata enabled");
+
+	/* USB boot - force enabled by GBB */
+	reset_common_data();
+	gbb.flags |= VB2_GBB_FLAG_FORCE_DEV_BOOT_USB;
+	TEST_EQ(vb2_dev_boot_usb_allowed(ctx), 1,
+		"dev boot usb - GBB force enabled");
+
+	/* USB boot - set all flags */
+	reset_common_data();
+	vb2_nv_set(ctx, VB2_NV_DEV_BOOT_USB, 1);
+	fwmp->flags |= VB2_SECDATA_FWMP_DEV_ENABLE_USB;
+	gbb.flags |= VB2_GBB_FLAG_FORCE_DEV_BOOT_USB;
+	TEST_EQ(vb2_dev_boot_usb_allowed(ctx), 1,
+		"dev boot usb - all flags set");
+}
+
 int main(int argc, char* argv[])
 {
 	init_workbuf_tests();
@@ -805,6 +922,8 @@
 	need_reboot_for_display_tests();
 	clear_recovery_tests();
 	get_recovery_reason_tests();
+	dev_default_boot_tests();
+	dev_boot_allowed_tests();
 
 	return gTestSuccess ? 0 : 255;
 }
diff --git a/tests/vb2_ui_tests.c b/tests/vb2_ui_tests.c
index ffcb7d6..eb7de29 100644
--- a/tests/vb2_ui_tests.c
+++ b/tests/vb2_ui_tests.c
@@ -5,13 +5,200 @@
  * Tests for developer and recovery mode UIs.
  */
 
+#include "2api.h"
+#include "2common.h"
+#include "2misc.h"
+#include "2nvstorage.h"
+#include "2ui.h"
 #include "test_common.h"
+#include "vboot_api.h"
+#include "vboot_kernel.h"
+
+/* Mock data */
+static uint8_t workbuf[VB2_KERNEL_WORKBUF_RECOMMENDED_SIZE]
+	__attribute__((aligned(VB2_WORKBUF_ALIGN)));
+static struct vb2_context *ctx;
+
+static enum vb2_screen mock_screens_displayed[64];
+static uint32_t mock_locales_displayed[64];
+static uint32_t mock_screens_count = 0;
+
+static enum vb2_dev_default_boot mock_default_boot;
+static int mock_dev_boot_allowed;
+static int mock_dev_boot_legacy_allowed;
+static int mock_dev_boot_usb_allowed;
+
+static int mock_vbexlegacy_called;
+static enum VbAltFwIndex_t mock_altfw_num;
+
+static vb2_error_t mock_vbtlk_retval[5];
+static uint32_t mock_vbtlk_expected_flag[5];
+static int mock_vbtlk_count;
+static int mock_vbtlk_total;
+
+static void add_mock_vbtlk(vb2_error_t retval, uint32_t get_info_flags)
+{
+	if (mock_vbtlk_total >= ARRAY_SIZE(mock_vbtlk_retval) ||
+	    mock_vbtlk_total >= ARRAY_SIZE(mock_vbtlk_expected_flag)) {
+		TEST_TRUE(0, "Test failed as mock_vbtlk ran out of entries!");
+		return;
+	}
+
+	mock_vbtlk_retval[mock_vbtlk_total] = retval;
+	mock_vbtlk_expected_flag[mock_vbtlk_total] = get_info_flags;
+	mock_vbtlk_total++;
+}
+
+/* Reset mock data (for use before each test) */
+static void reset_common_data()
+{
+	TEST_SUCC(vb2api_init(workbuf, sizeof(workbuf), &ctx),
+		  "vb2api_init failed");
+	vb2_nv_init(ctx);
+
+	memset(mock_screens_displayed, 0, sizeof(mock_screens_displayed));
+	mock_screens_count = 0;
+
+	mock_default_boot = VB2_DEV_DEFAULT_BOOT_DISK;
+	mock_dev_boot_allowed = 1;
+	mock_dev_boot_legacy_allowed = 0;
+	mock_dev_boot_usb_allowed = 0;
+
+	mock_vbexlegacy_called = 0;
+	mock_altfw_num = -100;
+
+	memset(mock_vbtlk_retval, 0, sizeof(mock_vbtlk_retval));
+	memset(mock_vbtlk_expected_flag, 0, sizeof(mock_vbtlk_expected_flag));
+	mock_vbtlk_count = 0;
+	mock_vbtlk_total = 0;
+}
+
+/* Mock functions */
+
+enum vb2_dev_default_boot vb2_get_dev_boot_target(struct vb2_context *c)
+{
+	return mock_default_boot;
+}
+
+int vb2_dev_boot_allowed(struct vb2_context *c)
+{
+	return mock_dev_boot_allowed;
+}
+
+int vb2_dev_boot_legacy_allowed(struct vb2_context *c)
+{
+	return mock_dev_boot_legacy_allowed;
+}
+
+int vb2_dev_boot_usb_allowed(struct vb2_context *c)
+{
+	return mock_dev_boot_usb_allowed;
+}
+
+vb2_error_t VbExLegacy(enum VbAltFwIndex_t altfw_num)
+{
+	mock_vbexlegacy_called++;
+	mock_altfw_num = altfw_num;
+
+	return VB2_SUCCESS;
+}
+
+vb2_error_t VbTryLoadKernel(struct vb2_context *c, uint32_t get_info_flags)
+{
+	if (mock_vbtlk_count >= mock_vbtlk_total) {
+		TEST_TRUE(0, "  VbTryLoadKernel called too many times.");
+		return VB2_ERROR_MOCK;
+	}
+
+	TEST_EQ(mock_vbtlk_expected_flag[mock_vbtlk_count], get_info_flags,
+		"  unexpected get_info_flags");
+
+	return mock_vbtlk_retval[mock_vbtlk_count++];
+}
+
+vb2_error_t vb2ex_display_ui(enum vb2_screen screen, uint32_t locale)
+{
+	VB2_DEBUG("screens %d: screen = %#x, locale = %u\n",
+		  mock_screens_count, screen, locale);
+
+	if (mock_screens_count >= ARRAY_SIZE(mock_screens_displayed) ||
+	    mock_screens_count >= ARRAY_SIZE(mock_locales_displayed)) {
+		TEST_TRUE(0, "Test failed as mock vb2ex_display_ui ran out of"
+			  " entries!");
+		return VB2_ERROR_MOCK;
+	}
+
+	mock_screens_displayed[mock_screens_count] = screen;
+	mock_locales_displayed[mock_screens_count] = locale;
+	mock_screens_count++;
+
+	return VB2_SUCCESS;
+}
 
 /* Tests */
 
 static void developer_tests(void)
 {
-	/* TODO(roccochen) */
+	/* Proceed */
+	reset_common_data();
+	add_mock_vbtlk(VB2_SUCCESS, VB_DISK_FLAG_FIXED);
+	TEST_EQ(vb2_developer_menu(ctx), VB2_SUCCESS, "proceed");
+	TEST_EQ(mock_screens_displayed[0], VB2_SCREEN_BLANK,
+		"  final blank screen");
+	TEST_EQ(mock_screens_count, 1, "  no extra screens");
+	TEST_EQ(vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST), 0,
+		"  recovery reason");
+	TEST_EQ(mock_vbtlk_count, mock_vbtlk_total, "  used up mock_vbtlk");
+
+	/* Proceed to legacy */
+	reset_common_data();
+	mock_default_boot = VB2_DEV_DEFAULT_BOOT_LEGACY;
+	mock_dev_boot_legacy_allowed = 1;
+	TEST_EQ(vb2_developer_menu(ctx), VB2_SUCCESS, "proceed to legacy");
+	TEST_EQ(mock_vbexlegacy_called, 1, "  try legacy");
+	TEST_EQ(mock_altfw_num, 0, "  check altfw_num");
+	TEST_EQ(mock_screens_displayed[0], VB2_SCREEN_BLANK,
+		"  final blank screen");
+	TEST_EQ(mock_screens_count, 1, "  no extra screens");
+	TEST_EQ(mock_vbtlk_count, mock_vbtlk_total, "  used up mock_vbtlk");
+
+	/* Proceed to legacy only if enabled */
+	reset_common_data();
+	add_mock_vbtlk(VB2_SUCCESS, VB_DISK_FLAG_FIXED);
+	mock_default_boot = VB2_DEV_DEFAULT_BOOT_LEGACY;
+	TEST_EQ(vb2_developer_menu(ctx), VB2_SUCCESS,
+		"default legacy not enabled");
+	TEST_EQ(mock_vbexlegacy_called, 0, "  not legacy");
+	TEST_EQ(mock_screens_displayed[0], VB2_SCREEN_BLANK,
+		"  final blank screen");
+	TEST_EQ(mock_screens_count, 1, "  no extra screens");
+	TEST_EQ(vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST), 0,
+		"  no recovery");
+	TEST_EQ(mock_vbtlk_count, mock_vbtlk_total, "  used up mock_vbtlk");
+
+	/* Proceed to usb */
+	reset_common_data();
+	add_mock_vbtlk(VB2_SUCCESS, VB_DISK_FLAG_REMOVABLE);
+	mock_default_boot = VB2_DEV_DEFAULT_BOOT_USB;
+	mock_dev_boot_usb_allowed = 1;
+	TEST_EQ(vb2_developer_menu(ctx), VB2_SUCCESS, "proceed to usb");
+	TEST_EQ(mock_screens_displayed[0], VB2_SCREEN_BLANK,
+		"  final blank screen");
+	TEST_EQ(mock_screens_count, 1, "  no extra screens");
+	TEST_EQ(mock_vbtlk_count, mock_vbtlk_total, "  used up mock_vbtlk");
+
+	/* Proceed to usb only if enabled */
+	reset_common_data();
+	add_mock_vbtlk(VB2_SUCCESS, VB_DISK_FLAG_FIXED);
+	mock_default_boot = VB2_DEV_DEFAULT_BOOT_USB;
+	TEST_EQ(vb2_developer_menu(ctx), VB2_SUCCESS,
+		"default usb not enabled");
+	TEST_EQ(mock_screens_displayed[0], VB2_SCREEN_BLANK,
+		"  final blank screen");
+	TEST_EQ(mock_screens_count, 1, "  no extra screens");
+	TEST_EQ(vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST), 0,
+		"  no recovery");
+	TEST_EQ(mock_vbtlk_count, mock_vbtlk_total, "  used up mock_vbtlk");
 }
 
 static void broken_recovery_tests(void)