vboot/ui: Remove extra delay for long iteration time

If an iteration takes longer than KEY_DELAY_MS, no extra delay.
Otherwise, delay until the iteration time reaches KEY_DELAY_MS.

BUG=b:168776970
BRANCH=none
TEST=Build locally

Signed-off-by: Hsuan Ting Chen <roccochen@chromium.org>
Change-Id: Ia78dbe1cc87d08c02f99f4fc9269929c12c18b77
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/2424373
diff --git a/firmware/2lib/2ui.c b/firmware/2lib/2ui.c
index 24f7473..10de176 100644
--- a/firmware/2lib/2ui.c
+++ b/firmware/2lib/2ui.c
@@ -300,6 +300,7 @@
 	const struct vb2_menu *menu;
 	const struct vb2_screen_info *root_info;
 	uint32_t key_flags;
+	uint32_t start_time_ms, elapsed_ms;
 	vb2_error_t rv;
 
 	memset(&ui, 0, sizeof(ui));
@@ -316,6 +317,8 @@
 	prev_error_code = VB2_UI_ERROR_NONE;
 
 	while (1) {
+		start_time_ms = vb2ex_mtime();
+
 		/* Draw if there are state changes. */
 		if (memcmp(&prev_state, ui.state, sizeof(*ui.state)) ||
 		    /* Redraw when timer is disabled. */
@@ -392,7 +395,9 @@
 		}
 
 		/* Delay. */
-		vb2ex_msleep(KEY_DELAY_MS);
+		elapsed_ms = vb2ex_mtime() - start_time_ms;
+		if (elapsed_ms < KEY_DELAY_MS)
+			vb2ex_msleep(KEY_DELAY_MS - elapsed_ms);
 	}
 
 	return VB2_SUCCESS;
diff --git a/tests/vb2_ui_action_tests.c b/tests/vb2_ui_action_tests.c
index 84bb9e0..f6f18dc 100644
--- a/tests/vb2_ui_action_tests.c
+++ b/tests/vb2_ui_action_tests.c
@@ -71,6 +71,9 @@
 static enum VbAltFwIndex_t mock_altfw_num_last;
 static uint32_t mock_bootloader_count;
 
+static uint32_t mock_time_ms;
+static const uint32_t mock_time_start_ms = 31ULL * VB2_MSEC_PER_SEC;
+
 /* Mock actions */
 static uint32_t mock_action_called;
 static uint32_t mock_action_countdown_limit;
@@ -114,6 +117,13 @@
 	return VB2_REQUEST_UI_CONTINUE;
 }
 
+static uint32_t mock_action_delay_ms;
+static vb2_error_t mock_action_msleep(struct vb2_ui_context *ui)
+{
+	vb2ex_msleep(mock_action_delay_ms);
+	return VB2_REQUEST_UI_CONTINUE;
+}
+
 /* Mock screens */
 struct vb2_screen_info mock_screen_temp;
 const struct vb2_screen_info mock_screen_blank = {
@@ -339,6 +349,7 @@
 	mock_action_called = 0;
 	mock_action_countdown_limit = 1;
 	mock_action_flags = 0;
+	mock_action_delay_ms = 0;
 
 	/* For chagen_screen and vb2_get_screen_info */
 	mock_get_screen_info_called = 0;
@@ -355,6 +366,9 @@
 	mock_vbexlegacy_called = 0;
 	mock_altfw_num_last = -100;
 	mock_bootloader_count = 2;
+
+	/* For vb2ex_mtime and vb2ex_msleep  */
+	mock_time_ms = mock_time_start_ms;
 }
 
 /* Mock functions */
@@ -499,6 +513,16 @@
 	return mock_bootloader_count;
 }
 
+uint32_t vb2ex_mtime(void)
+{
+	return mock_time_ms;
+}
+
+void vb2ex_msleep(uint32_t msec)
+{
+	mock_time_ms += msec;
+}
+
 /* Tests */
 static void menu_prev_tests(void)
 {
@@ -962,6 +986,66 @@
 	VB2_DEBUG("...done.\n");
 }
 
+static void ui_loop_delay_tests(void)
+{
+	VB2_DEBUG("Testing ui_loop delay...\n");
+
+	/* Sleep for 20 ms each iteration */
+	reset_common_data();
+	mock_calls_until_shutdown = 1;
+	TEST_EQ(ui_loop(ctx, MOCK_SCREEN_BASE, mock_action_msleep),
+		VB2_REQUEST_SHUTDOWN, "  sleep for 20 ms in each iteration");
+	TEST_EQ(mock_time_ms - mock_time_start_ms, KEY_DELAY_MS,
+		"  delay 20 ms in total");
+
+	/* Complement to 20 ms */
+	reset_common_data();
+	mock_calls_until_shutdown = 1;
+	mock_action_delay_ms = KEY_DELAY_MS / 2;
+	TEST_EQ(ui_loop(ctx, MOCK_SCREEN_BASE, mock_action_msleep),
+		VB2_REQUEST_SHUTDOWN, "  complement to 20 ms");
+	TEST_EQ(mock_time_ms - mock_time_start_ms, KEY_DELAY_MS,
+		"  delay 10 ms in total");
+
+	/* No extra sleep if an iteration takes longer than KEY_DELAY_MS */
+	reset_common_data();
+	mock_calls_until_shutdown = 1;
+	mock_action_delay_ms = 1234;
+	TEST_EQ(ui_loop(ctx, MOCK_SCREEN_BASE, mock_action_msleep),
+		VB2_REQUEST_SHUTDOWN, "  no extra sleep time");
+	TEST_EQ(mock_time_ms - mock_time_start_ms, mock_action_delay_ms,
+		"  no extra delay");
+
+	/* Integer overflow */
+	reset_common_data();
+	mock_calls_until_shutdown = 1;
+	mock_time_ms = UINT32_MAX;
+	TEST_EQ(ui_loop(ctx, MOCK_SCREEN_BASE, mock_action_msleep),
+		VB2_REQUEST_SHUTDOWN, "  integer overflow #1");
+	TEST_EQ(mock_time_ms - UINT32_MAX, KEY_DELAY_MS,
+		"  delay 20 ms in total");
+
+	reset_common_data();
+	mock_calls_until_shutdown = 1;
+	mock_time_ms = UINT32_MAX;
+	mock_action_delay_ms = KEY_DELAY_MS / 2;
+	TEST_EQ(ui_loop(ctx, MOCK_SCREEN_BASE, mock_action_msleep),
+		VB2_REQUEST_SHUTDOWN, "  integer overflow #2");
+	TEST_EQ(mock_time_ms - UINT32_MAX, KEY_DELAY_MS,
+		"  delay 10 ms in total");
+
+	reset_common_data();
+	mock_calls_until_shutdown = 1;
+	mock_time_ms = UINT32_MAX;
+	mock_action_delay_ms = 1234;
+	TEST_EQ(ui_loop(ctx, MOCK_SCREEN_BASE, mock_action_msleep),
+		VB2_REQUEST_SHUTDOWN, "  integer overflow #3");
+	TEST_EQ(mock_time_ms - UINT32_MAX, mock_action_delay_ms,
+		"  no extra delay");
+
+	VB2_DEBUG("...done.\n");
+}
+
 int main(void)
 {
 	/* Input actions */
@@ -977,6 +1061,7 @@
 
 	/* Core UI loop */
 	ui_loop_tests();
+	ui_loop_delay_tests();
 
 	return gTestSuccess ? 0 : 255;
 }