Commit d32e78c5c6 for qemu.org

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

    hw/hexagon: share the Hexagon VM guest boot code

    The DSP subsystem machines boot a guest kernel through the hypervisor
    firmware.  Move that code, the DT nodes, guest timer, into hex-board so that
    it can be used by virt.

    No functional change, this will be used by a subsequent commit.

    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 f38c244bb2..c3b8c647e4 100644
--- a/hw/hexagon/hex-board.c
+++ b/hw/hexagon/hex-board.c
@@ -20,6 +20,7 @@
 #include "hw/hexagon/hex-subsys.h"
 #include "qemu/error-report.h"
 #include "qemu/guest-random.h"
+#include "qemu/units.h"
 #include "system/address-spaces.h"
 #include "system/device_tree.h"
 #include "system/reset.h"
@@ -232,6 +233,143 @@ uint64_t hex_board_load_elf(const char *filename,
     return entry;
 }

+void hex_board_fdt_add_memory(MachineState *ms, hwaddr base, hwaddr size)
+{
+    g_autofree char *nodename = g_strdup_printf("/memory@%" HWADDR_PRIx, base);
+
+    qemu_fdt_add_subnode(ms->fdt, nodename);
+    qemu_fdt_setprop_string(ms->fdt, nodename, "device_type", "memory");
+    qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", extract64(base, 32, 32),
+                           extract64(base, 0, 32), extract64(size, 32, 32),
+                           extract64(size, 0, 32));
+}
+
+void hex_board_fdt_add_vm_timer(MachineState *ms, hwaddr base, hwaddr size,
+                                int irq)
+{
+    static const char compat[] = "qcom,h2-timer\0hvm-timer";
+    g_autofree char *name = g_strdup_printf("/soc/gpt@%" HWADDR_PRIx, base);
+
+    qemu_fdt_add_subnode(ms->fdt, name);
+    qemu_fdt_setprop(ms->fdt, name, "compatible", compat, sizeof(compat));
+    qemu_fdt_setprop_cells(ms->fdt, name, "interrupts", irq, 0);
+    qemu_fdt_setprop_cells(ms->fdt, name, "reg", extract64(base, 32, 32),
+                           extract64(base, 0, 32), extract64(size, 32, 32),
+                           extract64(size, 0, 32));
+}
+
+enum {
+    STUB_FDT_HI = 11,
+    STUB_FDT_LO,
+    STUB_ENTRY_ADDR,
+};
+
+static const uint32_t vm_boot_stub[] = {
+    /* Load fdt_base_low value into r0: */
+    0x099c4000, /* { immext(#0x99c00000) */
+    0x7800c606, /*   r6 = ##-0x662fffd0 } */
+    0x9186c000, /* { r0 = memw(r6+#0x0) } */
+
+    /* Load fdt_base_high value into r1: */
+    0x099c4000, /* { immext(#0x99c00000) */
+    0x7800c586, /*   r6 = ##-0x662fffd4 } */
+    0x9186c001, /* { r1 = memw(r6+#0x0) } */
+
+    /* Load next_stage_entry value into r7: */
+    0x099c4000, /* { immext(#0x99c00000) */
+    0x7800c687, /*   r7 = ##-0x662fffcc } */
+    0x9187c007, /* { r7 = memw(r7+#0x0) } */
+
+    /* Jump to next_stage_entry, r1:0 now contains fdt_base: */
+    0x5287c000, /* { jumpr r7 } */
+    0x0, /* Invalid packet */
+    0x0, /* Pad for fdt_base_high */
+    0x0, /* Pad for fdt_base_low */
+    0x0, /* Pad for next_stage_entry */
+};
+
+static uint64_t vm_guest_translate(void *opaque, uint64_t addr)
+{
+    return addr + *(hwaddr *)opaque;
+}
+
+/* Place the FDT's room after the kernel, and the initrd after that. */
+static void hex_board_load_initrd(MachineState *ms, hwaddr ram_end,
+                                  hwaddr fdt_addr, int fdt_size)
+{
+    hwaddr start = fdt_addr + 4 * MiB;
+    hwaddr available;
+    ssize_t size;
+
+    available = fdt_addr < ram_end ? ram_end - fdt_addr : 0;
+    if (fdt_size > available) {
+        error_report("no room for the FDT (size 0x%x): only 0x%" HWADDR_PRIx
+                     " bytes available at 0x%" HWADDR_PRIx,
+                     fdt_size, available, fdt_addr);
+        exit(1);
+    }
+
+    if (!ms->initrd_filename) {
+        return;
+    }
+
+    if (start >= ram_end) {
+        error_report("no room for the initrd: RAM ends at 0x%" HWADDR_PRIx
+                     " but the initrd would start at 0x%" HWADDR_PRIx,
+                     ram_end, start);
+        exit(1);
+    }
+
+    size = load_image_targphys_as(ms->initrd_filename, start, ram_end - start,
+                                  &address_space_memory, &error_fatal);
+
+    qemu_fdt_setprop_u64(ms->fdt, "/chosen", "linux,initrd-start", start);
+    qemu_fdt_setprop_u64(ms->fdt, "/chosen", "linux,initrd-end", start + size);
+}
+
+void hex_board_check_initrd(MachineState *ms, bool firmware)
+{
+    if (ms->initrd_filename && !(ms->kernel_filename && firmware)) {
+        error_report("-initrd needs both -kernel and the H2 loadlinux "
+                     "firmware given with -bios");
+        exit(1);
+    }
+}
+
+uint64_t hex_board_load_vm_guest(MachineState *ms, const char *firmware,
+                                 hwaddr ddr_base, hwaddr kernel_addr,
+                                 hwaddr stub_base, int fdt_size,
+                                 hwaddr *fdt_addr)
+{
+    uint64_t firmware_entry, image_high;
+    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) {
+        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);
+        exit(1);
+    }
+
+    firmware_entry = hex_board_load_elf(firmware, NULL, NULL, NULL);
+    hex_board_load_elf(ms->kernel_filename, vm_guest_translate, &kernel_addr,
+                       &image_high);
+    *fdt_addr = QEMU_ALIGN_UP(image_high + 16 * MiB, 4 * MiB);
+    hex_board_load_initrd(ms, ram_end, *fdt_addr, fdt_size);
+
+    for (size_t i = 0; i < ARRAY_SIZE(stub); i++) {
+        stub[i] = cpu_to_le32(vm_boot_stub[i]);
+    }
+    stub[STUB_FDT_LO] = cpu_to_le32(extract64(*fdt_addr, 0, 32));
+    stub[STUB_FDT_HI] = cpu_to_le32(extract64(*fdt_addr, 32, 32));
+    stub[STUB_ENTRY_ADDR] = cpu_to_le32(extract64(firmware_entry, 0, 32));
+    rom_add_blob_fixed_as("bootloader", stub, sizeof(stub), stub_base,
+                          &address_space_memory);
+
+    return stub_base;
+}
+
 void hex_board_load_fdt(MachineState *ms, int fdt_size, hwaddr addr)
 {
     rom_add_blob_fixed_as("fdt", ms->fdt, fdt_size, addr,
diff --git a/hw/hexagon/qcom_dsp.c b/hw/hexagon/qcom_dsp.c
index a78445132a..e822e6ce1d 100644
--- a/hw/hexagon/qcom_dsp.c
+++ b/hw/hexagon/qcom_dsp.c
@@ -43,38 +43,6 @@ static const int QCOM_DSP_GPT_IRQ = 12;

 #define QCOM_DSP_L2_ECC_SIZE 0x1000

-#define QCOM_DSP_MAX_KERNEL_RAM (896 * MiB)
-
-static const uint32_t bootloader[] = {
-    /* Load fdt_base_low value into r0: */
-    0x099c4000, /* { immext(#0x99c00000) */
-    0x7800c606, /*   r6 = ##-0x662fffd0 } */
-    0x9186c000, /* { r0 = memw(r6+#0x0) } */
-
-    /* Load fdt_base_high value into r1: */
-    0x099c4000, /* { immext(#0x99c00000) */
-    0x7800c586, /*   r6 = ##-0x662fffd4 } */
-    0x9186c001, /* { r1 = memw(r6+#0x0) } */
-
-    /* Load next_stage_entry value into r7: */
-    0x099c4000, /* { immext(#0x99c00000) */
-    0x7800c687, /*   r7 = ##-0x662fffcc } */
-    0x9187c007, /* { r7 = memw(r7+#0x0) } */
-
-    /* Jump to next_stage_entry, r1:0 now contains fdt_base: */
-    0x5287c000, /* { jumpr r7 } */
-    0x0, /* Invalid packet */
-    0x0, /* Pad for fdt_base_high */
-    0x0, /* Pad for fdt_base_low */
-    0x0, /* Pad for next_stage_entry */
-};
-
-enum {
-    FDT_HI = 11,
-    FDT_LO,
-    ENTRY_ADDR,
-};
-
 static const QcomDspMachineInfo *qcom_dsp_info(MachineState *ms)
 {
     return QCOM_DSP_MACHINE_GET_CLASS(ms)->info;
@@ -142,125 +110,20 @@ static void qcom_dsp_create_fdt(QcomDspMachineState *qms)
     qemu_fdt_setprop_string(ms->fdt, "/soc", "compatible", "simple-bus");
 }

-/*
- * The H2 hypervisor provides the guest timer through hypercalls, so this
- * node only tells the kernel's timer driver which interrupt to expect.
- */
-static void qcom_dsp_fdt_add_gpt(MachineState *ms)
-{
-    static const char compat[] = "qcom,h2-timer\0hvm-timer";
-    g_autofree char *name = g_strdup_printf("/soc/gpt@%" PRIx64,
-                                            qcom_dsp_memmap[QCOM_DSP_GPT].base);
-
-    qemu_fdt_add_subnode(ms->fdt, name);
-    qemu_fdt_setprop(ms->fdt, name, "compatible", compat, sizeof(compat));
-    qemu_fdt_setprop_cells(ms->fdt, name, "interrupts", QCOM_DSP_GPT_IRQ, 0);
-    qemu_fdt_setprop_cells(ms->fdt, name, "reg", 0x0,
-                           qcom_dsp_memmap[QCOM_DSP_GPT].base,
-                           qcom_dsp_memmap[QCOM_DSP_GPT].size);
-}
-
 static void qcom_dsp_fdt_add_memory(MachineState *ms)
 {
     hwaddr kernel_addr = qcom_dsp_info(ms)->kernel_addr;
     hwaddr ram_end = qcom_dsp_info(ms)->cfg->ddr_base + ms->ram_size;
-    hwaddr size = MIN(ram_end - kernel_addr, QCOM_DSP_MAX_KERNEL_RAM);
-    g_autofree char *nodename = g_strdup_printf("/memory@%" HWADDR_PRIx,
-                                                kernel_addr);
-
-    qemu_fdt_add_subnode(ms->fdt, nodename);
-    qemu_fdt_setprop_string(ms->fdt, nodename, "device_type", "memory");
-    qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", 0, kernel_addr,
-                           size);
-}
-
-static uint64_t qcom_dsp_kernel_translate(void *opaque, uint64_t addr)
-{
-    return addr + qcom_dsp_info(MACHINE(opaque))->kernel_addr;
-}
-
-static uint64_t qcom_dsp_load_kernel(MachineState *ms, bool through_firmware,
-                                     hwaddr *image_high)
-{
-    return hex_board_load_elf(ms->kernel_filename,
-                              through_firmware ? qcom_dsp_kernel_translate
-                                               : NULL,
-                              ms, image_high);
-}
-
-static uint64_t qcom_dsp_load_firmware(QcomDspMachineState *qms)
-{
-    return hex_board_load_elf(qms->firmware_path, NULL, NULL, NULL);
-}
-
-/*
- * Place the FDT after the kernel image, inside the RAM window the kernel
- * maps, and the initrd after that.
- */
-static void qcom_dsp_load_initrd(QcomDspMachineState *qms)
-{
-    MachineState *ms = MACHINE(qms);
-    hwaddr start = qms->fdt_addr + 4 * MiB;
-    hwaddr ram_end = qcom_dsp_info(ms)->cfg->ddr_base + ms->ram_size;
-    hwaddr available;
-    ssize_t size;
-
-    available = qms->fdt_addr < ram_end ? ram_end - qms->fdt_addr : 0;
-    if (qms->fdt_size > available) {
-        error_report("no room for the FDT (size 0x%x): only 0x%" HWADDR_PRIx
-                     " bytes available at 0x%" HWADDR_PRIx,
-                     qms->fdt_size, available, qms->fdt_addr);
-        exit(1);
-    }
-
-    if (!ms->initrd_filename) {
-        return;
-    }
-
-    if (start >= ram_end) {
-        error_report("no room for the initrd: RAM ends at 0x%" HWADDR_PRIx
-                     " but the initrd would start at 0x%" HWADDR_PRIx,
-                     ram_end, start);
-        exit(1);
-    }
-
-    size = load_image_targphys_as(ms->initrd_filename, start, ram_end - start,
-                                  &address_space_memory, &error_fatal);
-
-    qemu_fdt_setprop_u64(ms->fdt, "/chosen", "linux,initrd-start", start);
-    qemu_fdt_setprop_u64(ms->fdt, "/chosen", "linux,initrd-end", start + size);
-}
-
-/*
- * A boot stub that passes the FDT address to the firmware in r1:r0, then
- * jumps to the firmware's entry point.
- */
-static uint64_t qcom_dsp_setup_boot_stub(QcomDspMachineState *qms,
-                                         uint64_t firmware_entry)
-{
-    hwaddr bootl_base = qcom_dsp_memmap[QCOM_DSP_BOOT].base;
-    uint32_t stub[ARRAY_SIZE(bootloader)];
-
-    for (size_t i = 0; i < ARRAY_SIZE(stub); i++) {
-        stub[i] = cpu_to_le32(bootloader[i]);
-    }
-    stub[FDT_LO] = cpu_to_le32(extract64(qms->fdt_addr, 0, 32));
-    stub[FDT_HI] = cpu_to_le32(extract64(qms->fdt_addr, 32, 32));
-    stub[ENTRY_ADDR] = cpu_to_le32(extract64(firmware_entry, 0, 32));
-
-    g_assert(sizeof(stub) <= qcom_dsp_memmap[QCOM_DSP_BOOT].size);
-    rom_add_blob_fixed_as("bootloader", stub, sizeof(stub), bootl_base,
-                          &address_space_memory);

-    return bootl_base;
+    hex_board_fdt_add_memory(ms, kernel_addr,
+                             MIN(ram_end - kernel_addr,
+                                 HEX_BOARD_MAX_KERNEL_RAM));
 }

 /* Reject boot options that do not make sense together, and find -bios. */
 static void qcom_dsp_check_boot_options(QcomDspMachineState *qms)
 {
     MachineState *ms = MACHINE(qms);
-    hwaddr ddr_base = qcom_dsp_info(ms)->cfg->ddr_base;
-    hwaddr kernel_addr = qcom_dsp_info(ms)->kernel_addr;

     if (ms->firmware) {
         qms->firmware_path = qemu_find_file(QEMU_FILE_TYPE_BIOS, ms->firmware);
@@ -270,43 +133,28 @@ static void qcom_dsp_check_boot_options(QcomDspMachineState *qms)
         }
     }

-    if (ms->initrd_filename && !(ms->kernel_filename && qms->firmware_path)) {
-        error_report("-initrd needs both -kernel and the H2 loadlinux "
-                     "firmware given with -bios");
-        exit(1);
-    }
-
-    if (ms->kernel_filename && qms->firmware_path &&
-        (ddr_base > qcom_dsp_memmap[QCOM_DSP_BOOT].base ||
-         ddr_base + ms->ram_size <= kernel_addr)) {
-        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);
-        exit(1);
-    }
+    hex_board_check_initrd(ms, qms->firmware_path);
 }

 /* Load what -bios and -kernel name and return where CPU 0 starts, if known. */
 static bool qcom_dsp_load_images(QcomDspMachineState *qms, uint64_t *start)
 {
     MachineState *ms = MACHINE(qms);
-    hwaddr image_high = 0;
+    const QcomDspMachineInfo *info = qcom_dsp_info(ms);

     if (ms->kernel_filename && qms->firmware_path) {
-        uint64_t firmware_entry = qcom_dsp_load_firmware(qms);
-
-        qcom_dsp_load_kernel(ms, true, &image_high);
-        qms->fdt_addr = QEMU_ALIGN_UP(image_high + 16 * MiB, 4 * MiB);
-        qcom_dsp_load_initrd(qms);
-        *start = qcom_dsp_setup_boot_stub(qms, firmware_entry);
+        *start = hex_board_load_vm_guest(ms, qms->firmware_path,
+                                         info->cfg->ddr_base, info->kernel_addr,
+                                         qcom_dsp_memmap[QCOM_DSP_BOOT].base,
+                                         qms->fdt_size, &qms->fdt_addr);
         return true;
     }
     if (ms->kernel_filename) {
-        *start = qcom_dsp_load_kernel(ms, false, &image_high);
+        *start = hex_board_load_elf(ms->kernel_filename, NULL, NULL, NULL);
         return true;
     }
     if (qms->firmware_path) {
-        *start = qcom_dsp_load_firmware(qms);
+        *start = hex_board_load_elf(qms->firmware_path, NULL, NULL, NULL);
         return true;
     }
     return false;
@@ -330,7 +178,9 @@ static void qcom_dsp_init(MachineState *ms)

     hex_board_fdt_add_hvx(ms, info->cfg);
     l2vic_phandle = hex_board_fdt_add_l2vic(ms, info->cfg);
-    qcom_dsp_fdt_add_gpt(ms);
+    hex_board_fdt_add_vm_timer(ms, qcom_dsp_memmap[QCOM_DSP_GPT].base,
+                               qcom_dsp_memmap[QCOM_DSP_GPT].size,
+                               QCOM_DSP_GPT_IRQ);
     /* loadlinux gives the kernel a virtual CPU for every thread that runs. */
     hex_board_fdt_add_cpus(ms);
     if (ms->kernel_filename && qms->firmware_path) {
diff --git a/include/hw/hexagon/hex-board.h b/include/hw/hexagon/hex-board.h
index 93a31cb2ec..5cdea79ea8 100644
--- a/include/hw/hexagon/hex-board.h
+++ b/include/hw/hexagon/hex-board.h
@@ -53,6 +53,41 @@ uint64_t hex_board_load_elf(const char *filename,
                             uint64_t (*translate)(void *opaque, uint64_t addr),
                             void *opaque, uint64_t *high);

+/* The most RAM that a guest of the Hexagon VM host maps for its kernel. */
+#define HEX_BOARD_MAX_KERNEL_RAM (896 * MiB)
+
+/* Add a /memory node for @size bytes of RAM at @base. */
+void hex_board_fdt_add_memory(MachineState *ms, hwaddr base, hwaddr size);
+
+/*
+ * Add the node that tells a guest's timer driver which interrupt to expect.
+ * The Hexagon VM host provides the timer itself through hypercalls, so
+ * nothing backs the window at @base.
+ */
+void hex_board_fdt_add_vm_timer(MachineState *ms, hwaddr base, hwaddr size,
+                                int irq);
+
+/*
+ * 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.
+ */
+uint64_t hex_board_load_vm_guest(MachineState *ms, const char *firmware,
+                                 hwaddr ddr_base, hwaddr kernel_addr,
+                                 hwaddr stub_base, int fdt_size,
+                                 hwaddr *fdt_addr);
+
+/*
+ * Reject -initrd unless the guest boots through firmware, which is when
+ * @firmware is set and -kernel is given.
+ */
+void hex_board_check_initrd(MachineState *ms, bool firmware);
+
 /* Copy the device tree to @addr and refresh its rng-seed on reset. */
 void hex_board_load_fdt(MachineState *ms, int fdt_size, hwaddr addr);