minidiag: Add diagnostic menu screen

BRANCH=none
BUG=b:156692539, b:156693348
TEST=emerge-hatch vboot_reference
TEST=unittest passed:
( export CC=x86_64-pc-linux-gnu-clang DEBUG=1 MENU_UI=0 DIAGNOSTIC_UI=0
    MINIMAL=1 TPM2_MODE= MOCK_TPM=; make clean &&
        make -j32 test_setup && make runtests; echo $? )
( export CC=x86_64-pc-linux-gnu-clang DEBUG=1 MENU_UI=1 DIAGNOSTIC_UI=0
    MINIMAL=1 TPM2_MODE= MOCK_TPM=; make clean &&
        make -j32 test_setup && make runtests; echo $? )
( export CC=x86_64-pc-linux-gnu-clang DEBUG=1 MENU_UI=0 DIAGNOSTIC_UI=1
    MINIMAL=1 TPM2_MODE= MOCK_TPM=; make clean &&
        make -j32 test_setup && make runtests; echo $? )
( export CC=x86_64-pc-linux-gnu-clang DEBUG=1 MENU_UI=1 DIAGNOSTIC_UI=1
    MINIMAL=1 TPM2_MODE= MOCK_TPM=; make clean &&
        make -j32 test_setup && make runtests; echo $? )

Cq-Depend: chromium:2193314, chromium:2328704
Signed-off-by: Meng-Huan Yu <menghuan@chromium.org>
Change-Id: I4f3c64ce53b14437cb14d3c1109e14608d082141
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/2318590
Commit-Queue: Yu-Ping Wu <yupingso@chromium.org>
Reviewed-by: Yu-Ping Wu <yupingso@chromium.org>
diff --git a/firmware/2lib/2ui.c b/firmware/2lib/2ui.c
index 85c0067..d80002d 100644
--- a/firmware/2lib/2ui.c
+++ b/firmware/2lib/2ui.c
@@ -469,3 +469,11 @@
 
 	return VB2_REQUEST_UI_CONTINUE;
 }
+
+/*****************************************************************************/
+/* Diagnostics */
+
+vb2_error_t vb2_diagnostic_menu(struct vb2_context *ctx)
+{
+	return ui_loop(ctx, VB2_SCREEN_DIAGNOSTICS, NULL);
+}
diff --git a/firmware/2lib/2ui_screens.c b/firmware/2lib/2ui_screens.c
index 6728fe7..b8ff3a5 100644
--- a/firmware/2lib/2ui_screens.c
+++ b/firmware/2lib/2ui_screens.c
@@ -907,6 +907,29 @@
 };
 
 /******************************************************************************/
+/* VB2_SCREEN_DIAGNOSTICS */
+
+static const struct vb2_menu_item diagnostics_items[] = {
+	LANGUAGE_SELECT_ITEM,
+	{
+		.text = "Storage",
+	},
+	{
+		.text = "Quick memory check",
+	},
+	{
+		.text = "Full memory check",
+	},
+	POWER_OFF_ITEM,
+};
+
+static const struct vb2_screen_info diagnostics_screen = {
+	.id = VB2_SCREEN_DIAGNOSTICS,
+	.name = "Diagnostic tools",
+	.menu = MENU_ITEMS(diagnostics_items),
+};
+
+/******************************************************************************/
 /*
  * TODO(chromium:1035800): Refactor UI code across vboot and depthcharge.
  * Currently vboot and depthcharge maintain their own copies of menus/screens.
@@ -933,6 +956,7 @@
 	&developer_to_norm_screen,
 	&developer_boot_external_screen,
 	&developer_invalid_disk_screen,
+	&diagnostics_screen,
 };
 
 const struct vb2_screen_info *vb2_get_screen_info(enum vb2_screen id)
diff --git a/firmware/2lib/include/2api.h b/firmware/2lib/include/2api.h
index ad48482..a6c7676 100644
--- a/firmware/2lib/include/2api.h
+++ b/firmware/2lib/include/2api.h
@@ -1316,6 +1316,8 @@
 	VB2_SCREEN_DEVELOPER_BOOT_EXTERNAL	= 0x320,
 	/* Invalid external disk inserted */
 	VB2_SCREEN_DEVELOPER_INVALID_DISK	= 0x330,
+	/* Diagnostic tools */
+	VB2_SCREEN_DIAGNOSTICS			= 0x400,
 };
 
 enum vb2_ui_error {
diff --git a/firmware/2lib/include/2ui.h b/firmware/2lib/include/2ui.h
index 790301e..cc21f58 100644
--- a/firmware/2lib/include/2ui.h
+++ b/firmware/2lib/include/2ui.h
@@ -229,4 +229,15 @@
  */
 vb2_error_t vb2_manual_recovery_menu(struct vb2_context *ctx);
 
+/**
+ * UI for a diagnostic tools boot.
+ *
+ * Enter the diagnostic tools menu, which provides debug information and
+ * diagnostic tests of various hardware components.
+ *
+ * @param ctx		Vboot context
+ * @returns VB2_SUCCESS, or non-zero error code.
+ */
+vb2_error_t vb2_diagnostic_menu(struct vb2_context *ctx);
+
 #endif  /* VBOOT_REFERENCE_2UI_H_ */
diff --git a/firmware/lib/vboot_api_kernel.c b/firmware/lib/vboot_api_kernel.c
index eeabde8..a910b55 100644
--- a/firmware/lib/vboot_api_kernel.c
+++ b/firmware/lib/vboot_api_kernel.c
@@ -240,18 +240,20 @@
 		} else {
 			VB2_TRY(VbBootRecoveryLegacyClamshell(ctx));
 		}
-	} else if (DIAGNOSTIC_UI && !MENU_UI &&
-		   vb2api_diagnostic_ui_enabled(ctx) &&
+	} else if (DIAGNOSTIC_UI && vb2api_diagnostic_ui_enabled(ctx) &&
 		   vb2_nv_get(ctx, VB2_NV_DIAG_REQUEST)) {
 		vb2_nv_set(ctx, VB2_NV_DIAG_REQUEST, 0);
 
-		/*
-		 * Diagnostic boot. This has a UI but only power button
-		 * is used for input so no detachable-specific UI is
-		 * needed.  This mode is also 1-shot so it's placed
-		 * before developer mode.
-		 */
-		VB2_TRY(VbBootDiagnosticLegacyClamshell(ctx));
+		/* Diagnostic boot.  This has UI. */
+		if (MENU_UI)
+			VB2_TRY(vb2_diagnostic_menu(ctx));
+		else
+			/*
+			 * Only power button is used for input so no
+			 * detachable-specific UI is needed.  This mode is also
+			 * 1-shot so it's placed before developer mode.
+			 */
+			VB2_TRY(VbBootDiagnosticLegacyClamshell(ctx));
 		/*
 		 * The diagnostic menu should either boot a rom, or
 		 * return either of reboot or shutdown.
diff --git a/tests/vb2_ui_tests.c b/tests/vb2_ui_tests.c
index c29f7f7..00c6e93 100644
--- a/tests/vb2_ui_tests.c
+++ b/tests/vb2_ui_tests.c
@@ -236,6 +236,7 @@
 	FOR_DEVELOPER,
 	FOR_BROKEN_RECOVERY,
 	FOR_MANUAL_RECOVERY,
+	FOR_DIAGNOSTICS,
 };
 
 /* Reset mock data (for use before each test) */
@@ -1548,6 +1549,55 @@
 	VB2_DEBUG("...done.\n");
 }
 
+static void diagnostics_screen_tests(void)
+{
+	VB2_DEBUG("Testing diagnostic screens...\n");
+
+	/* Diagnostics screen */
+	reset_common_data(FOR_DIAGNOSTICS);
+
+	/* #0: Language menu */
+	add_mock_keypress(VB_KEY_UP);
+	add_mock_keypress(VB_KEY_ENTER);
+	/* #1: Storage (no-op) */
+	add_mock_keypress(VB_KEY_ESC);
+	add_mock_keypress(VB_KEY_DOWN);
+	/* #2: Quick memory test (no-op) */
+	add_mock_keypress(VB_KEY_DOWN);
+	/* #3: Full memory test (no-op) */
+	add_mock_keypress(VB_KEY_DOWN);
+	/* #4: Power off (End of menu) */
+	add_mock_keypress(VB_KEY_DOWN);
+	add_mock_keypress(VB_KEY_ENTER);
+	mock_calls_until_shutdown = -1;
+	TEST_EQ(vb2_diagnostic_menu(ctx), VB2_REQUEST_SHUTDOWN,
+		"diagnostic screen");
+
+	DISPLAYED_EQ("default on first button of menu",
+		     VB2_SCREEN_DIAGNOSTICS, MOCK_IGNORE, 1, 0x0, MOCK_IGNORE);
+	/* #0: Language menu */
+	DISPLAYED_EQ("language selection",
+		     VB2_SCREEN_DIAGNOSTICS, MOCK_IGNORE, 0, 0x0, MOCK_IGNORE);
+	DISPLAYED_EQ("#0: language menu", VB2_SCREEN_LANGUAGE_SELECT,
+		     MOCK_IGNORE, MOCK_IGNORE, MOCK_IGNORE, MOCK_IGNORE);
+	/* #1: Storage (no-op) */
+	DISPLAYED_PASS();
+	DISPLAYED_EQ("storage button",
+		     VB2_SCREEN_DIAGNOSTICS, MOCK_IGNORE, 1, 0x0, MOCK_IGNORE);
+	/* #2: Quick memory test (no-op) */
+	DISPLAYED_EQ("quick memory test button",
+		     VB2_SCREEN_DIAGNOSTICS, MOCK_IGNORE, 2, 0x0, MOCK_IGNORE);
+	/* #3: Full memory test (no-op) */
+	DISPLAYED_EQ("full memory test button",
+		     VB2_SCREEN_DIAGNOSTICS, MOCK_IGNORE, 3, 0x0, MOCK_IGNORE);
+	/* #4: Power of (End of menu) */
+	DISPLAYED_EQ("power off",
+		     VB2_SCREEN_DIAGNOSTICS, MOCK_IGNORE, 4, 0x0, MOCK_IGNORE);
+	DISPLAYED_NO_EXTRA();
+
+	VB2_DEBUG("...done.\n");
+}
+
 int main(void)
 {
 	developer_tests();
@@ -1560,6 +1610,7 @@
 	developer_screen_tests();
 	broken_recovery_screen_tests();
 	manual_recovery_screen_tests();
+	diagnostics_screen_tests();
 
 	return gTestSuccess ? 0 : 255;
 }
diff --git a/tests/vboot_api_kernel4_tests.c b/tests/vboot_api_kernel4_tests.c
index 8076d83..1e68748 100644
--- a/tests/vboot_api_kernel4_tests.c
+++ b/tests/vboot_api_kernel4_tests.c
@@ -177,6 +177,15 @@
 	return vbboot_retval;
 }
 
+vb2_error_t vb2_diagnostic_menu(struct vb2_context *c)
+{
+	TEST_TRUE(MENU_UI, "Using menu_ui");
+	if (vbboot_retval == -5)
+		return VB2_ERROR_MOCK;
+
+	return vbboot_retval;
+}
+
 vb2_error_t VbBootRecoveryLegacyClamshell(struct vb2_context *c)
 {
 	rec_check(c);
@@ -197,6 +206,7 @@
 
 vb2_error_t VbBootDiagnosticLegacyClamshell(struct vb2_context *c)
 {
+	TEST_TRUE(!MENU_UI, "Not using menu_ui");
 	if (vbboot_retval == -5)
 		return VB2_ERROR_MOCK;
 
@@ -268,20 +278,21 @@
 	test_slk(VB2_ERROR_MOCK, 0, "Normal boot bad");
 
 	/* Check that NV_DIAG_REQUEST triggers diagnostic UI */
-	if (DIAGNOSTIC_UI && !MENU_UI) {
+	if (DIAGNOSTIC_UI) {
 		reset_common_data();
 		mock_diagnostic_ui_enabled = 1;
 		vb2_nv_set(ctx, VB2_NV_DIAG_REQUEST, 1);
 		vbboot_retval = -5;
 		test_slk(VB2_ERROR_MOCK, 0,
 			 "Normal boot with diag enabled");
-		TEST_EQ(vb2_nv_get(ctx, VB2_NV_DIAG_REQUEST),
-			0, "  diag not requested");
+		TEST_EQ(vb2_nv_get(ctx, VB2_NV_DIAG_REQUEST), 0,
+			"  diag not requested");
 
 		reset_common_data();
 		vb2_nv_set(ctx, VB2_NV_DIAG_REQUEST, 1);
 		test_slk(VB2_REQUEST_REBOOT, 0,
-			 "Normal boot with diag disabled (reboot to unset)");
+			 "Normal boot with diag disabled (reboot to "
+			 "unset)");
 	}
 
 	/* Boot normal - phase1 failure */