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(+) 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); -- 2.43.0