On 19/10/26, Brian Cain wrote:
> 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                     | 399 ++++++++++++++++++++++
>  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, 727 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 ef619991b4b..04df9db77dc 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1386,6 +1386,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 6955e600aee..1d0d55ea938 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..5520366bb28
> --- /dev/null
> +++ b/hw/hexagon/qcom_dsp.c
> @@ -0,0 +1,399 @@
> +/*
> + * 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;
> +    ssize_t size;
> +
> +    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)];
> +
> +    memcpy(stub, bootloader, sizeof(stub));

Maybe I'm missing something, but wouldn't we need to endian swap the
instruction words in `bootloader` on big-endian hosts?

> +    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);

checks against ram_end only occur when -initrd is specified in
qcom_dsp_load_initrd(), so without -initrd you could get a fdt_addr
that's outside the range of ram.

> +        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;
> +}
> +

-- 
Anton Johansson
rev.ng Labs Srl.

Reply via email to