We don't take care to force re-validation of the config on migration leading to a crash when the defaults don't match the migration stream. The other frame-buffers seem to do a similar thing in post-loads to address this.
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4668 Signed-off-by: Alex Bennée <[email protected]> --- Bellow is an AI imported qtest-ification of the test case with the bug report. I didn't think it was worth adding as a commit (and this will be dropped when merged) but in case anyone wants to see how it looks and think if it meets the bar for code legibility and understanding. --8<---------------cut here---------------start------------->8--- new file tests/qtest/bcm2835-fb-test.c @@ -0,0 +1,109 @@ +/* + * QTest testcase for BCM2835 Framebuffer + * + * Copyright (c) 2026 QEMU contributors + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "migration/migration-qmp.h" + +#define MBOX_ADDR 0x3f00b8a0 +#define PROP_BUFFER_ADDR 0x30000000 + +static void test_bcm2835_fb_resize_migrate(void) +{ + g_autofree char *tmpdir = g_dir_make_tmp("qtest-bcm2835-fb-XXXXXX", NULL); + g_autofree char *migfile = g_build_filename(tmpdir, "fb.state", NULL); + g_autofree char *mig_uri = g_strdup_printf("file:%s", migfile); + g_autofree char *ppm_path = g_build_filename(tmpdir, "out.ppm", NULL); + QTestState *src, *dst; + QDict *rsp; + FILE *f; + char header[64]; + + /* Start source machine */ + src = qtest_init("-accel tcg -M raspi3b -m 1G -display none -S"); + + /* + * Configure 1024x768 framebuffer via property mailbox. + * Buffer at 0x30000000: + * - size: 0x30 (48 bytes) + * - request code: 0 + * - tag: 0x00048003 (SET_PHYSICAL_WIDTH_HEIGHT) + * - val buf size: 8 + * - req/resp indicator: 8 + * - width: 1024 + * - height: 768 + * - end tag: 0 + */ + qtest_writel(src, PROP_BUFFER_ADDR + 0x00, 0x30); + qtest_writel(src, PROP_BUFFER_ADDR + 0x04, 0x00000000); + qtest_writel(src, PROP_BUFFER_ADDR + 0x08, 0x00048003); + qtest_writel(src, PROP_BUFFER_ADDR + 0x0c, 8); + qtest_writel(src, PROP_BUFFER_ADDR + 0x10, 8); + qtest_writel(src, PROP_BUFFER_ADDR + 0x14, 1024); + qtest_writel(src, PROP_BUFFER_ADDR + 0x18, 768); + qtest_writel(src, PROP_BUFFER_ADDR + 0x1c, 0x00000000); + + /* Post buffer address ORed with property channel (8) to Mailbox 1 */ + qtest_writel(src, MBOX_ADDR, PROP_BUFFER_ADDR | 8); + + /* Migrate source to file */ + qtest_qmp_assert_success(src, + "{ 'execute': 'migrate', 'arguments': { 'uri': %s } }", mig_uri); + wait_for_migration_complete(src); + qtest_quit(src); + + /* Start destination and load state */ + dst = qtest_initf("-accel tcg -M raspi3b -m 1G -display none -S " + "-incoming %s", mig_uri); + wait_for_migration_complete(dst); + + /* Request screendump from destination */ + rsp = qtest_qmp(dst, + "{ 'execute': 'screendump', 'arguments': { 'filename': %s } }", + ppm_path); + if (qdict_haskey(rsp, "error")) { + QDict *error = qdict_get_qdict(rsp, "error"); + const char *class = qdict_get_str(error, "class"); + if (g_str_equal(class, "CommandNotFound")) { + /* Built without pixman */ + qobject_unref(rsp); + qtest_quit(dst); + unlink(migfile); + rmdir(tmpdir); + g_test_skip("screendump command not available (without pixman)"); + return; + } + } + g_assert(!qdict_haskey(rsp, "error")); + qobject_unref(rsp); + + /* Check PPM header for 1024x768 resolution */ + f = fopen(ppm_path, "r"); + g_assert_nonnull(f); + g_assert_nonnull(fgets(header, sizeof(header), f)); + g_assert_cmpstr(header, ==, "P6\n"); + g_assert_nonnull(fgets(header, sizeof(header), f)); + g_assert_cmpstr(header, ==, "1024 768\n"); + fclose(f); + + qtest_quit(dst); + + unlink(ppm_path); + unlink(migfile); + rmdir(tmpdir); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/bcm2835/fb/resize_migrate", + test_bcm2835_fb_resize_migrate); + + return g_test_run(); +} modified tests/qtest/meson.build @@ -283,7 +283,7 @@ qtests_aarch64 = \ ['tpm-tis-device-test', 'tpm-tis-device-swtpm-test'] : []) + \ (config_all_devices.has_key('CONFIG_XLNX_ZYNQMP_ARM') ? ['xlnx-can-test', 'fuzz-xlnx-dp-test'] : []) + \ (config_all_devices.has_key('CONFIG_XLNX_VERSAL') ? ['xlnx-canfd-test', 'xlnx-versal-trng-test'] : []) + \ - (config_all_devices.has_key('CONFIG_RASPI') ? ['bcm2835-dma-test', 'bcm2835-i2c-test', 'bcm2835-ic-test'] : []) + \ + (config_all_devices.has_key('CONFIG_RASPI') ? ['bcm2835-dma-test', 'bcm2835-fb-test', 'bcm2835-i2c-test', 'bcm2835-ic-test'] : []) + \ (config_all_accel.has_key('CONFIG_TCG') and \ config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test', 'tpm-tis-i2c-swtpm-test'] : []) + \ (config_all_devices.has_key('CONFIG_ASPEED_SOC') ? qtests_aspeed64 : []) + \ @@ -424,6 +424,8 @@ qtests = { 'ast2700-hace-test': [files('aspeed-hace-utils.c', 'ast2700-hace-test.c'), crypto], 'ast2700-smc-test': files('aspeed-smc-utils.c', 'ast2700-smc-test.c'), + 'bcm2835-fb-test': files('migration/migration-qmp.c', + 'migration/migration-util.c'), 'bios-tables-test': [io, 'boot-sector.c', 'acpi-utils.c', 'tpm-emu.c'], 'cdrom-test': files('boot-sector.c'), 'dbus-vmstate-test': files('migration/migration-qmp.c', --8<---------------cut here---------------end--------------->8--- --- hw/display/bcm2835_fb.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/hw/display/bcm2835_fb.c b/hw/display/bcm2835_fb.c index bd58f625fcd..c5731521b57 100644 --- a/hw/display/bcm2835_fb.c +++ b/hw/display/bcm2835_fb.c @@ -352,10 +352,21 @@ static const MemoryRegionOps bcm2835_fb_ops = { .valid.max_access_size = 4, }; +static int bcm2835_fb_post_load(void *opaque, int version_id) +{ + BCM2835FBState *s = opaque; + + bcm2835_fb_validate_config(&s->config); + s->invalidate = true; + qemu_console_resize(s->con, s->config.xres, s->config.yres); + return 0; +} + static const VMStateDescription vmstate_bcm2835_fb = { .name = TYPE_BCM2835_FB, .version_id = 1, .minimum_version_id = 1, + .post_load = bcm2835_fb_post_load, .fields = (const VMStateField[]) { VMSTATE_BOOL(lock, BCM2835FBState), VMSTATE_BOOL(invalidate, BCM2835FBState), -- 2.47.3
