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


Reply via email to