Commit 3461642bdb for qemu.org

commit 3461642bdb135ba74c9f70f0e2e093a2e46045e8
Author: Brian Cain <brian.cain@oss.qualcomm.com>
Date:   Mon Nov 9 10:49:23 2026 -0800

    hw/hexagon/virt: fix firmware boot

    -bios on virt only loaded a raw image of at most 64 KiB at address
    zero.  This is insufficient for H2 firmware, which is usually hundreds of
    KiB.

    Boot a kernel given with -kernel through the firmware given with -bios.
    Load both ELFs at their declared physical addresses, place the device tree
    and initrd after the kernel, and enter the firmware through a boot stub
    that passes the device tree in r1:r0.

    Fixes: 88a8bc7f43ff ("hw/hexagon: Define hexagon "virt" machine")
    Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
    Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>

diff --git a/hw/hexagon/hex-board.c b/hw/hexagon/hex-board.c
index c3b8c647e4..d2e958f9b5 100644
--- a/hw/hexagon/hex-board.c
+++ b/hw/hexagon/hex-board.c
@@ -337,7 +337,7 @@ void hex_board_check_initrd(MachineState *ms, bool firmware)
 }

 uint64_t hex_board_load_vm_guest(MachineState *ms, const char *firmware,
-                                 hwaddr ddr_base, hwaddr kernel_addr,
+                                 hwaddr ddr_base, hwaddr kernel_load_offset,
                                  hwaddr stub_base, int fdt_size,
                                  hwaddr *fdt_addr)
 {
@@ -345,15 +345,17 @@ uint64_t hex_board_load_vm_guest(MachineState *ms, const char *firmware,
     hwaddr ram_end = ddr_base + ms->ram_size;
     uint32_t stub[ARRAY_SIZE(vm_boot_stub)];

-    if (ddr_base > stub_base || ram_end <= kernel_addr) {
+    if (ddr_base > stub_base || ram_end <= kernel_load_offset) {
         error_report("RAM (%" PRIu64 " MiB at 0x%" HWADDR_PRIx ") is too "
                      "small: the kernel is loaded at 0x%" HWADDR_PRIx,
-                     ms->ram_size / MiB, ddr_base, kernel_addr);
+                     ms->ram_size / MiB, ddr_base, kernel_load_offset);
         exit(1);
     }

     firmware_entry = hex_board_load_elf(firmware, NULL, NULL, NULL);
-    hex_board_load_elf(ms->kernel_filename, vm_guest_translate, &kernel_addr,
+    hex_board_load_elf(ms->kernel_filename,
+                       kernel_load_offset ? vm_guest_translate : NULL,
+                       kernel_load_offset ? &kernel_load_offset : NULL,
                        &image_high);
     *fdt_addr = QEMU_ALIGN_UP(image_high + 16 * MiB, 4 * MiB);
     hex_board_load_initrd(ms, ram_end, *fdt_addr, fdt_size);
diff --git a/hw/hexagon/virt.c b/hw/hexagon/virt.c
index d8e9177059..3327e28d12 100644
--- a/hw/hexagon/virt.c
+++ b/hw/hexagon/virt.c
@@ -7,11 +7,13 @@

 #include "qemu/osdep.h"
 #include "qapi/error.h"
+#include "elf.h"
 #include "hw/hexagon/virt.h"
 #include "hw/hexagon/hexagon.h"
 #include "hw/hexagon/hex-board.h"
 #include "hw/hexagon/hex-subsys.h"
 #include "hw/core/loader.h"
+#include "qemu/datadir.h"
 #include "qemu/error-report.h"
 #include "qemu/target-info.h"
 #include "qemu/units.h"
@@ -22,6 +24,7 @@ enum {
     VIRT_UART0,
     VIRT_MMIO,
     VIRT_FDT,
+    VIRT_BOOT,
 };

 /*
@@ -32,9 +35,10 @@ static const int VIRTIO_IRQ_BASE = 16;
 static const int VIRT_UART0_IRQ = 15;

 static const MemMapEntry base_memmap[] = {
-    [VIRT_UART0] = { 0x10000000, 0x00000200 },
+    [VIRT_UART0] = { 0x10000000, 0x00001000 },
     [VIRT_MMIO] = { 0x11000000, 0x00001000 },
     [VIRT_FDT] = { 0x99800000, 0x00400000 },
+    [VIRT_BOOT] = { 0x99c00000, 0x00000200 },
 };


@@ -43,20 +47,35 @@ static uint64_t load_kernel(const HexagonVirtMachineState *vms)
     return hex_board_load_elf(MACHINE(vms)->kernel_filename, NULL, NULL, NULL);
 }

-static uint64_t load_bios(HexagonVirtMachineState *vms)
+static bool is_elf(const char *filename)
 {
-    MachineState *ms = MACHINE(vms);
-    uint64_t bios_addr = 0x0;  /* Load BIOS at reset vector address 0x0 */
-    int bios_size;
-
-    bios_size = load_image_targphys(ms->firmware ?: "",
-                                    bios_addr, 64 * 1024, NULL);
-    if (bios_size < 0) {
-        error_report("Could not load BIOS '%s'", ms->firmware ?: "");
-        exit(1);
+    uint8_t magic[SELFMAG];
+    int fd = qemu_open_old(filename, O_RDONLY | O_BINARY);
+    bool elf;
+
+    if (fd < 0) {
+        return false;
     }
+    elf = read(fd, magic, sizeof(magic)) == sizeof(magic) &&
+          memcmp(magic, ELFMAG, SELFMAG) == 0;
+    qemu_close(fd);
+    return elf;
+}

-    return bios_addr;  /* Return entry point at address 0x0 */
+/*
+ * An ELF firmware starts at its entry point.  Any other firmware is a raw
+ * image that starts at the reset vector, address zero, where it is loaded.
+ */
+static uint64_t load_bios(MachineState *ms, const char *filename)
+{
+    if (is_elf(filename)) {
+        return hex_board_load_elf(filename, NULL, NULL, NULL);
+    }
+    if (load_image_targphys(filename, 0, ms->ram_size, NULL) < 0) {
+        error_report("Could not load BIOS '%s'", filename);
+        exit(1);
+    }
+    return 0;
 }

 static void virt_init(MachineState *ms)
@@ -66,9 +85,23 @@ static void virt_init(MachineState *ms)
     int32_t l2vic_phandle;
     uint64_t start = 0;
     bool have_start = false;
+    hwaddr fdt_addr = base_memmap[VIRT_FDT].base;
+    g_autofree char *firmware = NULL;
+    bool vm_guest;
+
+    if (ms->firmware) {
+        firmware = qemu_find_file(QEMU_FILE_TYPE_BIOS, ms->firmware);
+        if (!firmware) {
+            error_report("Could not find BIOS '%s'", ms->firmware);
+            exit(1);
+        }
+    }
+    hex_board_check_initrd(ms, firmware);
+    vm_guest = ms->kernel_filename && firmware;

     hex_board_create_fdt(ms, "hexagon-virt,qemu", "qcom,sm8150",
                          &vms->fdt_size);
+    qemu_fdt_setprop_string(ms->fdt, "/soc", "compatible", "simple-bus");
     qemu_fdt_setprop_string(ms->fdt, "/chosen", "bootargs", ms->kernel_cmdline);

     hex_subsys_create(&vms->parent_obj, m_cfg, v68_rev);
@@ -80,11 +113,16 @@ static void virt_init(MachineState *ms)
                             base_memmap[VIRT_MMIO].size, VIRTIO_IRQ_BASE,
                             VIRTIO_DEV_COUNT, l2vic_phandle);

-    if (ms->kernel_filename) {
+    if (vm_guest) {
+        start = hex_board_load_vm_guest(ms, firmware, 0, 0,
+                                        base_memmap[VIRT_BOOT].base,
+                                        vms->fdt_size, &fdt_addr);
+        have_start = true;
+    } else if (ms->kernel_filename) {
         start = load_kernel(vms);
         have_start = true;
-    } else if (ms->firmware) {
-        start = load_bios(vms);
+    } else if (firmware) {
+        start = load_bios(ms, firmware);
         have_start = true;
     }
     hex_board_create_cpus(ms, have_start, start);
@@ -94,7 +132,7 @@ static void virt_init(MachineState *ms)
                           base_memmap[VIRT_UART0].size, VIRT_UART0_IRQ,
                           l2vic_phandle);

-    hex_board_load_fdt(ms, vms->fdt_size, base_memmap[VIRT_FDT].base);
+    hex_board_load_fdt(ms, vms->fdt_size, fdt_addr);
 }


diff --git a/include/hw/hexagon/hex-board.h b/include/hw/hexagon/hex-board.h
index 5cdea79ea8..79548dfa62 100644
--- a/include/hw/hexagon/hex-board.h
+++ b/include/hw/hexagon/hex-board.h
@@ -69,16 +69,17 @@ void hex_board_fdt_add_vm_timer(MachineState *ms, hwaddr base, hwaddr size,

 /*
  * Boot a guest kernel through the Hexagon VM host firmware (H2).  Load the
- * firmware ELF @firmware as it is linked and the kernel ELF named by -kernel
- * relocated by @kernel_addr.  Place the device tree, whose size is
- * @fdt_size, after the kernel, and the -initrd after that, in the RAM
- * that spans from @ddr_base for the machine's RAM size.  Write a boot stub
- * at @stub_base that passes the device tree address in r1:r0 and enters the
- * firmware, and return @stub_base, where CPU 0 starts.  The device tree
- * address is returned in @fdt_addr.  Exits on failure.
+ * firmware ELF @firmware as it is linked and the kernel ELF named by -kernel.
+ * @kernel_load_offset is added to the kernel's physical addresses; zero loads
+ * the ELF as linked.  Place the device tree, whose size is @fdt_size, after
+ * the kernel, and the -initrd after that, in the RAM that spans from
+ * @ddr_base for the machine's RAM size.  Write a boot stub at @stub_base that
+ * passes the device tree address in r1:r0 and enters the firmware, and return
+ * @stub_base, where CPU 0 starts.  The device tree address is returned in
+ * @fdt_addr.  Exits on failure.
  */
 uint64_t hex_board_load_vm_guest(MachineState *ms, const char *firmware,
-                                 hwaddr ddr_base, hwaddr kernel_addr,
+                                 hwaddr ddr_base, hwaddr kernel_load_offset,
                                  hwaddr stub_base, int fdt_size,
                                  hwaddr *fdt_addr);

diff --git a/tests/qtest/boot-serial-test.c b/tests/qtest/boot-serial-test.c
index 37fee7a91c..e5d3e155d1 100644
--- a/tests/qtest/boot-serial-test.c
+++ b/tests/qtest/boot-serial-test.c
@@ -146,7 +146,7 @@ static const uint8_t bios_hexagon[] = {
     0x00, 0x40, 0x00, 0x01,                 /* immext(#0x10000000) */
     0x00, 0xc0, 0x00, 0x78,                 /* r0 = ##0x10000000 */
     0x54, 0xc0, 0x00, 0x3c,                 /* memb(r0+#0) = #0x54 Write 'T' */
-    0xf8, 0xff, 0xff, 0x59                  /* jump 0x0 ; Loop back to start */
+    0xfa, 0xff, 0xff, 0x59                  /* jump _start */
 };

 typedef struct testdef {
@@ -201,7 +201,8 @@ static const testdef_t tests[] = {
     { "arm", "microbit", "", "T", sizeof(kernel_nrf51), kernel_nrf51 },
     { "arm", "stm32vldiscovery", "", "T",
       sizeof(kernel_stm32vldiscovery), kernel_stm32vldiscovery },
-    { "hexagon", "virt", "", "TT", sizeof(bios_hexagon), NULL, bios_hexagon },
+    { "hexagon", "virt", "", "TT", sizeof(bios_hexagon), NULL,
+      bios_hexagon },

     { NULL }
 };