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