Reviewed-by: Anton Johansson <[email protected]>
Signed-off-by: Brian Cain <[email protected]>
---
 MAINTAINERS                               |   1 +
 include/hw/hexagon/hexagon.h              |   1 +
 include/hw/hexagon/qcom_dsp.h             |  81 +++++
 hw/hexagon/machine_cfg_qcs6490_cdsp.h.inc |  65 ++++
 hw/hexagon/qcom_dsp.c                     | 410 ++++++++++++++++++++++
 hw/hexagon/qcs6490_cdsp.c                 |  64 ++++
 tests/qtest/l2vic-test.c                  |   2 +
 tests/qtest/qcom-dsp-test.c               |  93 +++++
 tests/qtest/qct-qtimer-test.c             |   2 +
 hw/hexagon/Kconfig                        |  15 +
 hw/hexagon/meson.build                    |   2 +
 tests/qtest/meson.build                   |   3 +-
 12 files changed, 738 insertions(+), 1 deletion(-)
 create mode 100644 include/hw/hexagon/qcom_dsp.h
 create mode 100644 hw/hexagon/machine_cfg_qcs6490_cdsp.h.inc
 create mode 100644 hw/hexagon/qcom_dsp.c
 create mode 100644 hw/hexagon/qcs6490_cdsp.c
 create mode 100644 tests/qtest/qcom-dsp-test.c

diff --git a/MAINTAINERS b/MAINTAINERS
index de01869f906..0bbd7986658 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1391,6 +1391,7 @@ F: include/hw/misc/qcom-qdsp6ss-clk.h
 F: configs/devices/hexagon-softmmu/default.mak
 F: docs/system/hexagon/
 F: tests/functional/hexagon/
+F: tests/qtest/qcom-dsp-test.c
 F: docs/devel/hexagon-sys.rst
 
 LoongArch Machines
diff --git a/include/hw/hexagon/hexagon.h b/include/hw/hexagon/hexagon.h
index 01ed03cc833..baa33c37201 100644
--- a/include/hw/hexagon/hexagon.h
+++ b/include/hw/hexagon/hexagon.h
@@ -23,6 +23,7 @@ typedef enum {
     v66_rev = 0xa666,
     v67_rev = 0x2667,
     v68_rev = 0x8d68,
+    v68_qcs6490_rev = 0x8a68,
     v69_rev = 0x8c69,
     v71_rev = 0x8c71,
     v73_rev = 0x8c73,
diff --git a/include/hw/hexagon/qcom_dsp.h b/include/hw/hexagon/qcom_dsp.h
new file mode 100644
index 00000000000..d127b0b0b10
--- /dev/null
+++ b/include/hw/hexagon/qcom_dsp.h
@@ -0,0 +1,81 @@
+/*
+ * Qualcomm DSP subsystem machines: the board layer shared by the SoC models.
+ *
+ * Each SoC provides a QcomDspMachineInfo describing its subsystem, and the
+ * layer supplies what every one of them has in common: the CPUs, a QEMU-only
+ * PL011 console, virtio-mmio slots, a generated device tree, and the
+ * -bios/-kernel/-initrd boot flow.
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_HEXAGON_QCOM_DSP_H
+#define HW_HEXAGON_QCOM_DSP_H
+
+#include "hw/hexagon/hexagon.h"
+#include "qom/object.h"
+
+#define TYPE_QCOM_DSP_MACHINE MACHINE_TYPE_NAME("qcom-dsp")
+OBJECT_DECLARE_TYPE(QcomDspMachineState, QcomDspMachineClass, QCOM_DSP_MACHINE)
+
+/* Number of virtio-mmio transports every machine provides. */
+#define QCOM_DSP_VIRTIO_COUNT 8
+
+typedef struct QcomDspMachineInfo {
+    const char *desc;
+    const char *cpu_type;
+    unsigned int cpus;
+    Rev_t rev;
+    const struct hexagon_machine_config *cfg;
+    /* Where the H2 loadlinux of this SoC expects to find the Linux kernel. */
+    hwaddr kernel_addr;
+    /* Add the devices that are specific to this SoC; may be NULL. */
+    void (*init_devices)(MachineState *ms);
+} QcomDspMachineInfo;
+
+struct QcomDspMachineState {
+    HexagonCommonMachineState parent_obj;
+
+    MemoryRegion tcm;
+    MemoryRegion l2_ecc;
+    int fdt_size;
+    hwaddr fdt_addr;
+    char *firmware_path;
+};
+
+struct QcomDspMachineClass {
+    MachineClass parent_class;
+
+    const QcomDspMachineInfo *info;
+};
+
+/* Describes a register window the machine stubs out as unimplemented. */
+typedef struct QcomDspRegion {
+    const char *name;
+    hwaddr base;
+    /* Zero means one 4 KiB page. */
+    hwaddr size;
+} QcomDspRegion;
+
+/*
+ * Stub out register windows the guest may probe, named
+ * "<machine>.<name>@<base>".
+ */
+void qcom_dsp_add_regions(MachineState *ms, const QcomDspRegion *regions,
+                          unsigned int count);
+
+/* Create the sysbus device @type and map it at @base. */
+void qcom_dsp_add_device(const char *type, hwaddr base);
+
+/*
+ * Stub out @ports consecutive QUP GENI serial engines of @size bytes.  They
+ * belong to the SoC @soc rather than to the DSP.
+ */
+void qcom_soc_add_qup_geni_ports(const char *soc, hwaddr base,
+                                 unsigned int ports, hwaddr size);
+
+/* Class init for a machine type whose class_data is a QcomDspMachineInfo. */
+void qcom_dsp_class_init(ObjectClass *oc, const void *data);
+
+#endif /* HW_HEXAGON_QCOM_DSP_H */
diff --git a/hw/hexagon/machine_cfg_qcs6490_cdsp.h.inc 
b/hw/hexagon/machine_cfg_qcs6490_cdsp.h.inc
new file mode 100644
index 00000000000..11302da2238
--- /dev/null
+++ b/hw/hexagon/machine_cfg_qcs6490_cdsp.h.inc
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+static const struct hexagon_machine_config qcs6490_cdsp = {
+    .ddr_base =          0x80000000,
+    .cfgbase =           0x09980000,
+    .l2tcm_size =        0x00000000,
+    .l2vic_base =        0x0a390000,
+    .l2vic_size =        0x00001000,
+    .csr_base =          0x0a3a0000,
+    .qtmr_region =       0x0a3a1000,
+    .cfgtable = {
+        .l2tcm_base = 0x00000980,
+        .reserved0 = 0x00000000,
+        .subsystem_base = 0x00000a38,
+        .etm_base = 0x00000999,
+        .l2cfg_base = 0x0000099a,
+        .reserved1 = 0x0000099b,
+        .l1s0_base = 0x000009c0,
+        .axi2_lowaddr = 0x00000000,
+        .streamer_base = 0x0000099c,
+        .reserved2 = 0x0000099d,
+        .fastl2vic_base = 0x0000099e,
+        .jtlb_size_entries = 0x00000080,
+        .coproc_present = 0x00000001,
+        .ext_contexts = 0x00000002,
+        .vtcm_base = 0x000009c0,
+        .vtcm_size_kb = 0x00000800,
+        .l2tag_size = 0x00000400,
+        .l2ecomem_size = 0x00000400,
+        .thread_enable_mask = 0x0000003f,
+        .eccreg_base = 0x0000099f,
+        .l2line_size = 0x00000080,
+        .tiny_core = 0x00000000,
+        .l2itcm_size = 0x00000000,
+        .l2itcm_base = 0x00000980,
+        .reserved3 = 0x00000000,
+        .dtm_present = 0x00000000,
+        .dma_version = 0x00000001,
+        .hvx_vec_log_length = 0x00000007,
+        .core_id = 0x00000000,
+        .core_count = 0x00000000,
+        .coproc2_reg0 = 0x00000040,
+        .coproc2_reg1 = 0x00000020,
+        .v2x_mode = 0x00000001,
+        .coproc2_reg2 = 0x00000002,
+        .coproc2_reg3 = 0x00000000,
+        .coproc2_reg4 = 0x00000000,
+        .coproc2_reg5 = 0x00000000,
+        .coproc2_reg6 = 0x00000000,
+        .coproc2_reg7 = 0x00000000,
+        .acd_preset = 0x00000001,
+        .mnd_preset = 0x00000001,
+        .l1d_size_kb = 0x00000010,
+        .l1i_size_kb = 0x00000020,
+        .l1d_write_policy = 0x00000002,
+        .vtcm_bank_width = 0x00000040,
+        .reserved4 = 0x00000001,
+        .reserved5 = 0x00000000,
+        .reserved6 = 0x00000000,
+        .coproc2_cvt_mpy_size = 0x0000000a,
+        .consistency_domain = 0x00000000,
+        .capacity_domain = 0x00000000,
+        .axi3_lowaddr = 0x00000000,
+    },
+};
diff --git a/hw/hexagon/qcom_dsp.c b/hw/hexagon/qcom_dsp.c
new file mode 100644
index 00000000000..a78445132a6
--- /dev/null
+++ b/hw/hexagon/qcom_dsp.c
@@ -0,0 +1,410 @@
+/*
+ * DSP subsystem machines: the board layer shared by the SoC models.
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/core/boards.h"
+#include "hw/core/loader.h"
+#include "hw/core/sysbus.h"
+#include "hw/hexagon/hexagon.h"
+#include "hw/hexagon/hex-board.h"
+#include "hw/hexagon/hex-subsys.h"
+#include "hw/hexagon/qcom_dsp.h"
+#include "hw/misc/unimp.h"
+#include "qemu/datadir.h"
+#include "qemu/error-report.h"
+#include "qemu/units.h"
+#include "system/address-spaces.h"
+#include "system/device_tree.h"
+
+enum {
+    QCOM_DSP_UART0,
+    QCOM_DSP_VIRTIO,
+    QCOM_DSP_GPT,
+    QCOM_DSP_BOOT,
+};
+
+static const MemMapEntry qcom_dsp_memmap[] = {
+    [QCOM_DSP_UART0] = { 0x37000000, 0x00001000 },
+    [QCOM_DSP_VIRTIO] = { 0x37010000, 0x00001000 },
+    /* Not a device: the window the kernel's H2 timer driver binds to. */
+    [QCOM_DSP_GPT] = { 0xab000000, 0x00001000 },
+    [QCOM_DSP_BOOT] = { 0x99c00000, 0x00000200 },
+};
+
+static const int QCOM_DSP_UART0_IRQ = 15;
+/* Virtio IRQs run from QCOM_DSP_VIRTIO_IRQ to QCOM_DSP_VIRTIO_IRQ + 7 */
+static const int QCOM_DSP_VIRTIO_IRQ = 96;
+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;
+}
+
+void qcom_dsp_add_regions(MachineState *ms, const QcomDspRegion *regions,
+                          unsigned int count)
+{
+    const char *machine = MACHINE_GET_CLASS(ms)->name;
+
+    for (unsigned int i = 0; i < count; i++) {
+        g_autofree char *name = g_strdup_printf("%s.%s@%" HWADDR_PRIx, machine,
+                                                regions[i].name,
+                                                regions[i].base);
+
+        create_unimplemented_device(name, regions[i].base,
+                                    regions[i].size ? regions[i].size : 
0x1000);
+    }
+}
+
+void qcom_dsp_add_device(const char *type, hwaddr base)
+{
+    DeviceState *dev = qdev_new(type);
+
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
+}
+
+void qcom_soc_add_qup_geni_ports(const char *soc, hwaddr base,
+                                 unsigned int ports, hwaddr size)
+{
+    for (unsigned int i = 0; i < ports; i++) {
+        g_autofree char *name = g_strdup_printf("%s.qup-geni@%" HWADDR_PRIx,
+                                                soc, base + i * size);
+
+        create_unimplemented_device(name, base + i * size, size);
+    }
+}
+
+/*
+ * The firmware programs the L2 ECC registers and polls them until what it
+ * wrote reads back, so they need to hold state.
+ */
+static void qcom_dsp_add_l2_ecc_regs(QcomDspMachineState *qms,
+                                     const QcomDspMachineInfo *info)
+{
+    hwaddr base = (hwaddr)info->cfg->cfgtable.eccreg_base << 16;
+
+    if (!base) {
+        return;
+    }
+    memory_region_init_ram(&qms->l2_ecc, NULL, "l2-ecc-regs",
+                           QCOM_DSP_L2_ECC_SIZE, &error_fatal);
+    memory_region_add_subregion(get_system_memory(), base, &qms->l2_ecc);
+}
+
+static void qcom_dsp_create_fdt(QcomDspMachineState *qms)
+{
+    MachineState *ms = MACHINE(qms);
+    g_autofree char *compatible = g_strdup_printf("qcom,%s",
+                                             MACHINE_GET_CLASS(ms)->name);
+
+    hex_board_create_fdt(ms, MACHINE_GET_CLASS(ms)->desc, compatible,
+                         &qms->fdt_size);
+    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;
+}
+
+/* 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);
+        if (!qms->firmware_path) {
+            error_report("Could not find firmware '%s'", ms->firmware);
+            exit(1);
+        }
+    }
+
+    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);
+    }
+}
+
+/* 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;
+
+    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);
+        return true;
+    }
+    if (ms->kernel_filename) {
+        *start = qcom_dsp_load_kernel(ms, false, &image_high);
+        return true;
+    }
+    if (qms->firmware_path) {
+        *start = qcom_dsp_load_firmware(qms);
+        return true;
+    }
+    return false;
+}
+
+static void qcom_dsp_init(MachineState *ms)
+{
+    QcomDspMachineState *qms = QCOM_DSP_MACHINE(ms);
+    HexagonCommonMachineState *hms = HEXAGON_COMMON_MACHINE(ms);
+    const QcomDspMachineInfo *info = qcom_dsp_info(ms);
+    int32_t l2vic_phandle;
+    uint64_t start = 0;
+    bool have_start;
+
+    qcom_dsp_check_boot_options(qms);
+    qcom_dsp_create_fdt(qms);
+    qemu_fdt_setprop_string(ms->fdt, "/chosen", "bootargs", 
ms->kernel_cmdline);
+
+    hex_subsys_create(hms, info->cfg, info->rev);
+    hex_board_create_tcm(info->cfg, &qms->tcm);
+
+    hex_board_fdt_add_hvx(ms, info->cfg);
+    l2vic_phandle = hex_board_fdt_add_l2vic(ms, info->cfg);
+    qcom_dsp_fdt_add_gpt(ms);
+    /* 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) {
+        qcom_dsp_fdt_add_memory(ms);
+    }
+    hex_board_create_uart(ms, qcom_dsp_memmap[QCOM_DSP_UART0].base,
+                          qcom_dsp_memmap[QCOM_DSP_UART0].size,
+                          QCOM_DSP_UART0_IRQ, l2vic_phandle);
+    hex_board_create_virtio(ms, qcom_dsp_memmap[QCOM_DSP_VIRTIO].base,
+                            qcom_dsp_memmap[QCOM_DSP_VIRTIO].size,
+                            QCOM_DSP_VIRTIO_IRQ, QCOM_DSP_VIRTIO_COUNT,
+                            l2vic_phandle);
+    qcom_dsp_add_l2_ecc_regs(qms, info);
+    if (info->init_devices) {
+        info->init_devices(ms);
+    }
+
+    if (ms->dtb) {
+        g_free(ms->fdt);
+        ms->fdt = load_device_tree(ms->dtb, &qms->fdt_size);
+        if (!ms->fdt) {
+            error_report("could not load device tree '%s'", ms->dtb);
+            exit(1);
+        }
+        if (ms->kernel_cmdline && *ms->kernel_cmdline) {
+            qemu_fdt_setprop_string(ms->fdt, "/chosen", "bootargs",
+                                    ms->kernel_cmdline);
+        }
+    }
+
+    have_start = qcom_dsp_load_images(qms, &start);
+    hex_board_create_cpus(ms, have_start, start);
+
+    if (ms->kernel_filename && qms->firmware_path) {
+        hex_board_load_fdt(ms, qms->fdt_size, qms->fdt_addr);
+    }
+}
+
+static void qcom_dsp_base_class_init(ObjectClass *oc, const void *data)
+{
+    MachineClass *mc = MACHINE_CLASS(oc);
+
+    mc->init = qcom_dsp_init;
+    mc->default_ram_size = 1 * GiB;
+    mc->is_default = false;
+    mc->block_default_type = IF_VIRTIO;
+    mc->no_cdrom = true;
+    mc->no_floppy = true;
+    mc->no_parallel = true;
+    mc->numa_mem_supported = false;
+}
+
+void qcom_dsp_class_init(ObjectClass *oc, const void *data)
+{
+    MachineClass *mc = MACHINE_CLASS(oc);
+    QcomDspMachineClass *qc = QCOM_DSP_MACHINE_CLASS(oc);
+    const QcomDspMachineInfo *info = data;
+
+    qc->info = info;
+    mc->desc = info->desc;
+    mc->default_cpu_type = info->cpu_type;
+    mc->default_cpus = info->cpus;
+    mc->max_cpus = info->cpus;
+}
+
+static const TypeInfo qcom_dsp_types[] = {
+    {
+        .name = TYPE_QCOM_DSP_MACHINE,
+        .parent = TYPE_HEXAGON_COMMON_MACHINE,
+        .instance_size = sizeof(QcomDspMachineState),
+        .class_size = sizeof(QcomDspMachineClass),
+        .class_init = qcom_dsp_base_class_init,
+        .abstract = true,
+    },
+};
+
+DEFINE_TYPES(qcom_dsp_types)
diff --git a/hw/hexagon/qcs6490_cdsp.c b/hw/hexagon/qcs6490_cdsp.c
new file mode 100644
index 00000000000..fe255640c49
--- /dev/null
+++ b/hw/hexagon/qcs6490_cdsp.c
@@ -0,0 +1,64 @@
+/*
+ * QCS6490 CDSP machine.
+ *
+ * Models the Hexagon v68 CDSP subsystem of the QCS6490/SM7325.
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/boards.h"
+#include "hw/hexagon/hexagon.h"
+#include "hw/hexagon/qcom_dsp.h"
+#include "hw/misc/qcom-qdsp6ss-clk.h"
+#include "hw/misc/unimp.h"
+#include "qapi/error.h"
+#include "system/address-spaces.h"
+#include "target/hexagon/cpu.h"
+
+#include "machine_cfg_qcs6490_cdsp.h.inc"
+
+#define TYPE_QCS6490_CDSP_MACHINE MACHINE_TYPE_NAME("qcs6490-cdsp")
+
+#define QCS6490_CFGSPACE_BASE 0x09990000
+#define QCS6490_CFGSPACE_SIZE 0x00070000
+
+static const QcomDspRegion qcs6490_regions[] = {
+    { "ss-csr", 0x0a380000, 0x10000 },
+};
+
+static void qcs6490_cdsp_init_devices(MachineState *ms)
+{
+    MemoryRegion *cfgspace = g_new(MemoryRegion, 1);
+
+    memory_region_init_ram(cfgspace, NULL, "cfgspace.ram",
+                           QCS6490_CFGSPACE_SIZE, &error_fatal);
+    memory_region_add_subregion_overlap(get_system_memory(),
+                                        QCS6490_CFGSPACE_BASE, cfgspace, -1);
+    qcom_dsp_add_regions(ms, qcs6490_regions, ARRAY_SIZE(qcs6490_regions));
+    qcom_dsp_add_device(TYPE_QCOM_LUCID_PLL, 0x0a001000);
+    qcom_dsp_add_device(TYPE_QCOM_LUCID_PLL, 0x0a340000);
+    qcom_dsp_add_device(TYPE_QCOM_RCG, 0x0a341000);
+}
+
+static const QcomDspMachineInfo qcs6490_cdsp_info = {
+    .desc = "QCS6490 CDSP",
+    .cpu_type = HEXAGON_CPU_TYPE_NAME("v68"),
+    .cpus = 6,
+    .rev = v68_qcs6490_rev,
+    .cfg = &qcs6490_cdsp,
+    .kernel_addr = 0xa1000000,
+    .init_devices = qcs6490_cdsp_init_devices,
+};
+
+static const TypeInfo qcs6490_cdsp_types[] = {
+    {
+        .name = TYPE_QCS6490_CDSP_MACHINE,
+        .parent = TYPE_QCOM_DSP_MACHINE,
+        .class_init = qcom_dsp_class_init,
+        .class_data = &qcs6490_cdsp_info,
+    },
+};
+
+DEFINE_TYPES(qcs6490_cdsp_types)
diff --git a/tests/qtest/l2vic-test.c b/tests/qtest/l2vic-test.c
index adb4dc7281e..2e0d7dcb0f1 100644
--- a/tests/qtest/l2vic-test.c
+++ b/tests/qtest/l2vic-test.c
@@ -11,6 +11,7 @@
 
 #include "hw/hexagon/machine_cfg_v66g_1024.h.inc"
 #include "hw/hexagon/machine_cfg_v68n_1024.h.inc"
+#include "hw/hexagon/machine_cfg_qcs6490_cdsp.h.inc"
 
 /* L2VIC register offsets exercised by this test */
 #define L2VIC_INT_ENABLEn 0x100 /* Read/Write */
@@ -45,6 +46,7 @@ typedef struct {
 static const L2VICMachineCfg l2vic_machines[] = {
     { "virt",      &v68n_1024 },
     { "V66G_1024", &v66g_1024 },
+    { "qcs6490-cdsp", &qcs6490_cdsp },
 };
 
 static uint32_t l2vic_read32(uint64_t base, uint32_t offset)
diff --git a/tests/qtest/qcom-dsp-test.c b/tests/qtest/qcom-dsp-test.c
new file mode 100644
index 00000000000..39d6e53bc4e
--- /dev/null
+++ b/tests/qtest/qcom-dsp-test.c
@@ -0,0 +1,93 @@
+/*
+ * QTest for what the Qualcomm DSP machines have in common: the console and
+ * the virtio-mmio transports.
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "libqtest-single.h"
+
+#define QCOM_DSP_UART_BASE 0x37000000
+#define QCOM_DSP_VIRTIO_BASE 0x37010000
+#define QCOM_DSP_VIRTIO_STRIDE 0x1000
+#define QCOM_DSP_VIRTIO_COUNT 8
+
+#define PL011_UARTPERIPHID0 0xfe0
+#define PL011_PERIPHID0 0x11
+
+#define VIRTIO_MMIO_MAGIC_VALUE 0x000
+#define VIRTIO_MMIO_DEVICE_ID 0x008
+#define VIRTIO_MAGIC 0x74726976 /* "virt" */
+
+static const char *const qcom_dsp_machines[] = {
+    "qcs6490-cdsp",
+};
+
+static void test_console(const void *machine)
+{
+    g_autofree char *args = g_strdup_printf("-machine %s",
+                                            (const char *)machine);
+
+    qtest_start(args);
+    g_assert_cmphex(readl(QCOM_DSP_UART_BASE + PL011_UARTPERIPHID0), ==,
+                    PL011_PERIPHID0);
+    qtest_end();
+}
+
+static void test_virtio_slots(const void *machine)
+{
+    g_autofree char *args = g_strdup_printf("-machine %s",
+                                            (const char *)machine);
+
+    qtest_start(args);
+    for (int i = 0; i < QCOM_DSP_VIRTIO_COUNT; i++) {
+        uint64_t base = QCOM_DSP_VIRTIO_BASE + i * QCOM_DSP_VIRTIO_STRIDE;
+
+        g_assert_cmphex(readl(base + VIRTIO_MMIO_MAGIC_VALUE), ==,
+                        VIRTIO_MAGIC);
+        /* No backend is attached, so the transport is empty. */
+        g_assert_cmpuint(readl(base + VIRTIO_MMIO_DEVICE_ID), ==, 0);
+    }
+    qtest_end();
+}
+
+/* A drive is attached to a virtio transport without any -device. */
+static void test_default_drive(const void *machine)
+{
+    g_autofree char *args = g_strdup_printf(
+        "-machine %s -drive if=virtio,file=null-co://,format=raw",
+        (const char *)machine);
+    int found = 0;
+
+    qtest_start(args);
+    for (int i = 0; i < QCOM_DSP_VIRTIO_COUNT; i++) {
+        uint64_t base = QCOM_DSP_VIRTIO_BASE + i * QCOM_DSP_VIRTIO_STRIDE;
+
+        /* virtio-blk is device ID 2 */
+        found += readl(base + VIRTIO_MMIO_DEVICE_ID) == 2;
+    }
+    g_assert_cmpint(found, ==, 1);
+    qtest_end();
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    for (size_t i = 0; i < ARRAY_SIZE(qcom_dsp_machines); i++) {
+        const char *name = qcom_dsp_machines[i];
+        g_autofree char *path = NULL;
+
+        path = g_strdup_printf("/qcom-dsp/%s/console", name);
+        qtest_add_data_func(path, name, test_console);
+        path = g_strdup_printf("/qcom-dsp/%s/virtio-slots", name);
+        qtest_add_data_func(path, name, test_virtio_slots);
+        path = g_strdup_printf("/qcom-dsp/%s/default-drive", name);
+        qtest_add_data_func(path, name, test_default_drive);
+    }
+
+    return g_test_run();
+}
diff --git a/tests/qtest/qct-qtimer-test.c b/tests/qtest/qct-qtimer-test.c
index fd8bb0af776..263751b55bf 100644
--- a/tests/qtest/qct-qtimer-test.c
+++ b/tests/qtest/qct-qtimer-test.c
@@ -12,6 +12,7 @@
 
 #include "hw/hexagon/machine_cfg_v68n_1024.h.inc"
 #include "hw/hexagon/machine_cfg_v66g_1024.h.inc"
+#include "hw/hexagon/machine_cfg_qcs6490_cdsp.h.inc"
 
 #define QTIMER_DEFAULT_FREQ_HZ 19200000ULL
 
@@ -342,6 +343,7 @@ typedef struct {
 static const QtimerMachineCfg qtimer_machines[] = {
     { "virt",      &v68n_1024 },
     { "V66G_1024", &v66g_1024 },
+    { "qcs6490-cdsp", &qcs6490_cdsp },
 };
 
 static void test_qtimer_on_machine(gconstpointer data)
diff --git a/hw/hexagon/Kconfig b/hw/hexagon/Kconfig
index 63ee8836aca..ffc8623691c 100644
--- a/hw/hexagon/Kconfig
+++ b/hw/hexagon/Kconfig
@@ -21,3 +21,18 @@ config HEX_VIRT
     select HEX_BOARD
     select VIRTIO_BLK
     select VIRTIO_SCSI
+
+config HEX_QCOM_DSP
+    bool
+    depends on HEX_DSP && FDT
+    select HEX_BOARD
+    select UNIMP
+    select QCOM_QDSP6SS_CLK
+    select VIRTIO_BLK
+    select VIRTIO_SCSI
+
+config HEX_QCS6490_CDSP
+    bool
+    default y
+    depends on HEX_DSP && FDT
+    select HEX_QCOM_DSP
diff --git a/hw/hexagon/meson.build b/hw/hexagon/meson.build
index 57bff31ad00..7145e8a65df 100644
--- a/hw/hexagon/meson.build
+++ b/hw/hexagon/meson.build
@@ -6,5 +6,7 @@ hexagon_ss.add(when: 'CONFIG_HEX_DSP', if_true: 
files('hex-subsys.c'))
 hexagon_ss.add(when: 'CONFIG_HEX_DSP', if_true: files('hexagon_dsp.c'))
 hexagon_ss.add(when: 'CONFIG_HEX_BOARD', if_true: files('hex-board.c'))
 hexagon_ss.add(when: 'CONFIG_HEX_VIRT', if_true: files('virt.c'))
+hexagon_ss.add(when: 'CONFIG_HEX_QCOM_DSP', if_true: files('qcom_dsp.c'))
+hexagon_ss.add(when: 'CONFIG_HEX_QCS6490_CDSP', if_true: 
files('qcs6490_cdsp.c'))
 
 hw_arch += {'hexagon': hexagon_ss}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 46685d6d46f..7ab472a8100 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -321,7 +321,8 @@ qtests_riscv64 = ['riscv-csr-test'] + \
    ['k230-ddr-test', 'k230-wdt-test', 'k230-gsdma-test', 
'k230-decomp-gzip-test'] : []) + \
    ['milkv-duo-test']
 
-qtests_hexagon = ['boot-serial-test', 'l2vic-test', 'qct-qtimer-test']
+qtests_hexagon = ['boot-serial-test', 'l2vic-test', 'qcom-dsp-test',
+                  'qct-qtimer-test']
 
 qos_test_ss = ss.source_set()
 qos_test_ss.add(
-- 
2.34.1

Reply via email to