[Concept,03/22] sandbox: Add --video_frames option to capture test frames

Message ID 20251003165525.440173-4-sjg@u-boot.org
State New
Headers
Series video: Enhancements to support a pointer |

Commit Message

Simon Glass Oct. 3, 2025, 4:54 p.m. UTC
  From: Simon Glass <sjg@chromium.org>

Add a new --video_frames option to sandbox which accepts a directory path.
When set, every video test assertion writes a BMP file (frame0.bmp,
frame1.bmp, etc.) to the specified directory, allowing visual inspection
of the framebuffer at each test step.

A new video_write_bmp() function writes 16/32bpp framebuffer contents as
Windows BMP files. On startup, any existing frame*.bmp files in the
directory are removed to ensure a clean state.

Usage example:

  ./u-boot --video_frames /tmp/frames -c "ut dm video_text"

Co-developed-by: Claude <noreply@anthropic.com>
Signed-off-by: Simon Glass <sjg@chromium.org>
---

 arch/sandbox/cpu/start.c         | 22 ++++++++
 arch/sandbox/include/asm/state.h |  2 +
 doc/arch/sandbox/sandbox.rst     |  3 ++
 test/dm/video.c                  | 86 ++++++++++++++++++++++++++++++++
 4 files changed, 113 insertions(+)
  

Patch

diff --git a/arch/sandbox/cpu/start.c b/arch/sandbox/cpu/start.c
index 559c11a2dbb..30d4f83b6ee 100644
--- a/arch/sandbox/cpu/start.c
+++ b/arch/sandbox/cpu/start.c
@@ -375,6 +375,15 @@  static int sandbox_cmdline_cb_video_test(struct sandbox_state *state,
 SANDBOX_CMDLINE_OPT_SHORT(video_test, 'V', 1,
 			  "Enable video test mode (ms delay between asserts)");
 
+static int sandbox_cmdline_cb_video_frames(struct sandbox_state *state,
+					   const char *arg)
+{
+	state->video_frames_dir = arg;
+
+	return 0;
+}
+SANDBOX_CMDLINE_OPT(video_frames, 1, "Directory to write video frames");
+
 static const char *term_args[STATE_TERM_COUNT] = {
 	"raw-with-sigs",
 	"raw",
@@ -655,6 +664,19 @@  int sandbox_init(int argc, char *argv[], struct global_data *data)
 	if (os_parse_args(state, argc, argv))
 		return 1;
 
+	/* Remove old frame*.bmp files if video_frames_dir is set */
+	if (state->video_frames_dir) {
+		char pattern[256];
+		int i;
+
+		for (i = 0; i < 1000; i++) {
+			snprintf(pattern, sizeof(pattern), "%s/frame%d.bmp",
+				 state->video_frames_dir, i);
+			if (os_unlink(pattern))
+				break;
+		}
+	}
+
 	/* Detect if serial console is connected to a terminal */
 	state->serial_is_tty = os_isatty(1) &&
 		state->term_raw != STATE_TERM_COOKED;
diff --git a/arch/sandbox/include/asm/state.h b/arch/sandbox/include/asm/state.h
index fc3c39cde27..6a89f0ca5ef 100644
--- a/arch/sandbox/include/asm/state.h
+++ b/arch/sandbox/include/asm/state.h
@@ -178,6 +178,8 @@  struct sandbox_state {
 	bool pager_bypass;		/* Enable pager-bypass mode */
 	bool no_term_present;		/* Assume no terminal present */
 	int video_test;			/* ms to wait before next assert */
+	const char *video_frames_dir;	/* Directory to write video frames */
+	int video_frame_count;		/* Number of frames written */
 
 	/* Pointer to information for each SPI bus/cs */
 	struct sandbox_spi_info spi[CONFIG_SANDBOX_SPI_MAX_BUS]
diff --git a/doc/arch/sandbox/sandbox.rst b/doc/arch/sandbox/sandbox.rst
index bfc0ee70834..fc2b7c482f4 100644
--- a/doc/arch/sandbox/sandbox.rst
+++ b/doc/arch/sandbox/sandbox.rst
@@ -252,6 +252,9 @@  available options. Some of these are described below:
 -v, --verbose
   Show console output from tests. Normally this is suppressed.
 
+--video_frames <dir>
+  Write video frames to the specified directory for debugging purposes.
+
 -V, --video_test <ms>
   Enable video test mode with a delay (in milliseconds) between assertions. This
   allows time to examine the display during testing.
diff --git a/test/dm/video.c b/test/dm/video.c
index f8c126dba5c..56e63a6f5cf 100644
--- a/test/dm/video.c
+++ b/test/dm/video.c
@@ -4,6 +4,7 @@ 
  * Written by Simon Glass <sjg@chromium.org>
  */
 
+#include <bmp_layout.h>
 #include <bzlib.h>
 #include <dm.h>
 #include <gzip.h>
@@ -47,6 +48,79 @@  static int dm_test_video_base(struct unit_test_state *uts)
 }
 DM_TEST(dm_test_video_base, UTF_SCAN_PDATA | UTF_SCAN_FDT);
 
+/**
+ * video_write_bmp() - Write framebuffer to BMP file
+ *
+ * This writes the current framebuffer contents to a BMP file on the host
+ * filesystem. Useful for debugging video tests.
+ *
+ * @uts: Test state
+ * @dev: Video device
+ * @fname: Filename to write to
+ * Return: 0 if OK, -ve on error
+ */
+static int video_write_bmp(struct unit_test_state *uts, struct udevice *dev,
+			   const char *fname)
+{
+	struct video_priv *priv = dev_get_uclass_priv(dev);
+	struct bmp_image *bmp;
+	u32 width = priv->xsize;
+	u32 height = priv->ysize;
+	u32 row_bytes, bmp_size, bpp, bytes_per_pixel;
+	void *bmp_data;
+	int ret, y;
+
+	/* Support 16bpp and 32bpp */
+	switch (priv->bpix) {
+	case VIDEO_BPP16:
+		bpp = 16;
+		bytes_per_pixel = 2;
+		break;
+	case VIDEO_BPP32:
+		bpp = 32;
+		bytes_per_pixel = 4;
+		break;
+	default:
+		return -ENOSYS;
+	}
+
+	/* BMP rows are padded to 4-byte boundary */
+	row_bytes = ALIGN(width * bytes_per_pixel, BMP_DATA_ALIGN);
+	bmp_size = sizeof(struct bmp_header) + row_bytes * height;
+
+	bmp = malloc(bmp_size);
+	if (!bmp)
+		return -ENOMEM;
+
+	memset(bmp, 0, bmp_size);
+
+	/* Fill in BMP header */
+	bmp->header.signature[0] = 'B';
+	bmp->header.signature[1] = 'M';
+	bmp->header.file_size = cpu_to_le32(bmp_size);
+	bmp->header.data_offset = cpu_to_le32(sizeof(struct bmp_header));
+	bmp->header.size = cpu_to_le32(40);
+	bmp->header.width = cpu_to_le32(width);
+	bmp->header.height = cpu_to_le32(height);
+	bmp->header.planes = cpu_to_le16(1);
+	bmp->header.bit_count = cpu_to_le16(bpp);
+	bmp->header.compression = cpu_to_le32(BMP_BI_RGB);
+
+	/* Copy framebuffer data (BMP is bottom-up) */
+	bmp_data = (void *)bmp + sizeof(struct bmp_header);
+	for (y = 0; y < height; y++) {
+		void *src = priv->fb + (height - 1 - y) * priv->line_length;
+		void *dst = bmp_data + y * row_bytes;
+
+		memcpy(dst, src, width * bytes_per_pixel);
+	}
+
+	ret = os_write_file(fname, bmp, bmp_size);
+	free(bmp);
+
+	return ret;
+}
+
 int video_compress_fb(struct unit_test_state *uts, struct udevice *dev,
 		      bool use_copy)
 {
@@ -71,6 +145,18 @@  int video_compress_fb(struct unit_test_state *uts, struct udevice *dev,
 	if (ret)
 		return ret;
 
+	/* Write frame to file if --video-frames option is set */
+	if (state->video_frames_dir) {
+		char filename[256];
+
+		snprintf(filename, sizeof(filename), "%s/frame%d.bmp",
+			 state->video_frames_dir, state->video_frame_count++);
+		ret = video_write_bmp(uts, dev, filename);
+		if (ret)
+			printf("Failed to write frame to %s: %d\n", filename,
+			       ret);
+	}
+
 	/* provide a useful delay if -V flag is used or LOG_DEBUG is set */
 	if (state->video_test)
 		mdelay(state->video_test);