diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1e7258754c257..9a92d9d9cac81 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -77,7 +77,7 @@ jobs: podman run --pull newer --init --rm -it -v $(pwd):$(pwd) -w $(pwd)/tests/docker/ docker.io/pboqemu/qemu-ci:${{matrix.container}} - env QEMU_SRC=$(pwd) QEMU_WERROR="${{ env.QEMU_WERROR }}" ENABLE_RUST=0 INSTALL_DIR=/ BUILD_DIR=/tmp MAKEFLAGS="-j $(nproc)" + env QEMU_SRC=$(pwd) QEMU_WERROR="${{ env.QEMU_WERROR }}" ENABLE_RUST=0 EXTRA_CONFIGURE_OPTS=--disable-docs INSTALL_DIR=/ BUILD_DIR=/tmp MAKEFLAGS="-j $(nproc)" ./test-build build: diff --git a/MAINTAINERS b/MAINTAINERS index a6e8d36468243..912900615ef39 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1814,6 +1814,20 @@ F: include/hw/watchdog/k230_wdt.h F: tests/qtest/k230-sysctl-test.c F: tests/qtest/k230-wdt-test.c +SpacemiT K3 Pico-ITX +M: Chao Liu +L: qemu-riscv@nongnu.org +S: Maintained +F: docs/system/riscv/spacemit-k3.rst +F: hw/misc/spacemit-k3.c +F: hw/riscv/spacemit-k3.c +F: hw/sd/spacemit-k3-sdhci.c +F: include/hw/misc/spacemit-k3.h +F: include/hw/riscv/spacemit-k3.h +F: include/hw/sd/spacemit-k3-sdhci.h +F: tests/functional/riscv64/test_spacemit_k3.py +F: tests/qtest/spacemit-k3-test.c + Milk-V Duo M: Kuan-Wei Chiu S: Maintained diff --git a/README.md b/README.md index a2ae8a7d2d2d2..20883ac8ea650 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ for new machine requests or bug reports. | arch | machine | direct | firmware | src | | --- | --- | --- | --- | --- | | RISC-V | [`k230-canmv`](docs/system/riscv/k230-canmv.rst) | ✅ | ✅ | PM | +| RISC-V | [`k3-pico-itx`](docs/system/riscv/spacemit-k3.rst) | ✅ | ✅ | PM | | RISC-V | [`milkv-duo`](docs/system/riscv/milkv-duo.rst) | ✅ | ✅ | UP | | RISC-V | [`riscv-server-ref`](docs/system/riscv/riscv-server-ref.rst) | ✅ | ✅ | UP | | ARM | [`phytium-pi`](docs/system/arm/phytium-pi.rst) | ✅ | ✅ | PM | diff --git a/accel/tcg/user-exec.c b/accel/tcg/user-exec.c index d283d3cc72497..15fed1c713245 100644 --- a/accel/tcg/user-exec.c +++ b/accel/tcg/user-exec.c @@ -238,13 +238,16 @@ void page_dump(FILE *f) int page_get_flags(vaddr address) { - PageFlagsNode *p = pageflags_find(address, address); + PageFlagsNode *p; + + RCU_READ_LOCK_GUARD(); /* * See util/interval-tree.c re lockless lookups: no false positives but * there are false negatives. If we find nothing, retry with the mmap * lock acquired. */ + p = pageflags_find(address, address); if (p) { return p->flags; } @@ -300,15 +303,15 @@ static void pageflags_create_merge(vaddr start, vaddr last, int flags) if (prev) { if (next) { - prev->itree.last = next->itree.last; + pageflags_create(prev->itree.start, next->itree.last, flags); g_free_rcu(next, rcu); } else { - prev->itree.last = last; + pageflags_create(prev->itree.start, last, flags); } - interval_tree_insert(&prev->itree, &pageflags_root); + g_free_rcu(prev, rcu); } else if (next) { - next->itree.start = start; - interval_tree_insert(&next->itree, &pageflags_root); + pageflags_create(start, next->itree.last, flags); + g_free_rcu(next, rcu); } else { pageflags_create(start, last, flags); } @@ -370,8 +373,8 @@ static bool pageflags_set_clear(vaddr start, vaddr last, if (set_flags != merge_flags) { if (p_start < start) { interval_tree_remove(&p->itree, &pageflags_root); - p->itree.last = start - 1; - interval_tree_insert(&p->itree, &pageflags_root); + pageflags_create(p_start, start - 1, p_flags); + g_free_rcu(p, rcu); if (last < p_last) { if (merge_flags & PAGE_VALID) { @@ -393,11 +396,11 @@ static bool pageflags_set_clear(vaddr start, vaddr last, } if (last < p_last) { interval_tree_remove(&p->itree, &pageflags_root); - p->itree.start = last + 1; - interval_tree_insert(&p->itree, &pageflags_root); + pageflags_create(last + 1, p_last, p_flags); if (merge_flags & PAGE_VALID) { pageflags_create(start, last, merge_flags); } + g_free_rcu(p, rcu); } else { if (merge_flags & PAGE_VALID) { p->flags = merge_flags; @@ -418,8 +421,8 @@ static bool pageflags_set_clear(vaddr start, vaddr last, if (set_flags == p_flags) { if (start < p_start) { interval_tree_remove(&p->itree, &pageflags_root); - p->itree.start = start; - interval_tree_insert(&p->itree, &pageflags_root); + pageflags_create(start, p_last, p_flags); + g_free_rcu(p, rcu); } if (p_last < last) { start = p_last + 1; @@ -431,8 +434,8 @@ static bool pageflags_set_clear(vaddr start, vaddr last, /* Maybe split out head and/or tail ranges with the original flags. */ interval_tree_remove(&p->itree, &pageflags_root); if (p_start < start) { - p->itree.last = start - 1; - interval_tree_insert(&p->itree, &pageflags_root); + pageflags_create(p_start, start - 1, p_flags); + g_free_rcu(p, rcu); if (p_last < last) { goto restart; @@ -441,8 +444,8 @@ static bool pageflags_set_clear(vaddr start, vaddr last, pageflags_create(last + 1, p_last, p_flags); } } else if (last < p_last) { - p->itree.start = last + 1; - interval_tree_insert(&p->itree, &pageflags_root); + pageflags_create(last + 1, p_last, p_flags); + g_free_rcu(p, rcu); } else { g_free_rcu(p, rcu); goto restart; @@ -504,6 +507,8 @@ bool page_check_range(vaddr start, vaddr len, int flags) return false; /* wrap around */ } + RCU_READ_LOCK_GUARD(); + locked = have_mmap_lock(); while (true) { PageFlagsNode *p = pageflags_find(start, last); diff --git a/configs/devices/riscv64-softmmu/default.mak b/configs/devices/riscv64-softmmu/default.mak index 5af71e5b56282..6d93cfeb0ee37 100644 --- a/configs/devices/riscv64-softmmu/default.mak +++ b/configs/devices/riscv64-softmmu/default.mak @@ -10,6 +10,7 @@ # CONFIG_SIFIVE_U=n # CONFIG_RISCV_VIRT=n # CONFIG_RISCV_SERVER_PLATFORM_REF=n +# CONFIG_SPACEMIT_K3=n # CONFIG_MICROCHIP_PFSOC=n # CONFIG_SHAKTI_C=n # CONFIG_K230=n diff --git a/docs/system/riscv/spacemit-k3.rst b/docs/system/riscv/spacemit-k3.rst new file mode 100644 index 0000000000000..af535bc3f966f --- /dev/null +++ b/docs/system/riscv/spacemit-k3.rst @@ -0,0 +1,95 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +SpacemiT K3 Pico-ITX board (``k3-pico-itx``) +================================================ + +The ``k3-pico-itx`` machine models the standard RISC-V platform subset +needed to boot a SpacemiT K3 SDK Linux kernel directly or through U-Boot on +the K3 Pico-ITX board. It focuses on the eight X100 application harts and +does not expose the K3 A100 or IME harts. + +The machine has a fixed CPU topology and memory layout matching the Linux +view of the board. The default configuration must be used without overriding +the CPU type, RAM size, or SMP topology. + +Supported devices +----------------- + +The ``k3-pico-itx`` machine supports: + +* eight SpacemiT X100 harts implementing RVA23S64 in two clusters of four; +* 256-bit RISC-V vector registers, Sstc, and the Smaia/Ssaia extensions; +* 2 GiB of RAM starting at ``0x102000000``; +* a 32 MiB firmware window starting at ``0x100000000``; +* an ACLINT software interrupt and machine timer block with a 24 MHz timebase; +* machine- and supervisor-level APLIC and IMSIC interrupt controllers; +* the 8250-compatible UART0 at ``0xd4017000``, using interrupt source 42; and +* the K3 SDHCI controller at ``0xd4280000``, using interrupt source 99. + +Boot options +------------ + +Both supported Linux boot paths start generic OpenSBI directly. QEMU can +either load Linux and its initramfs, or load U-Boot proper and let U-Boot load +Linux from an SD image. An external device tree is required because the +machine validates the exact K3 topology and address map before boot. + +The following examples use artifacts built from the SpacemiT K3 Buildroot SDK +v1.0.2 and published in the ``sdk-v1.0.2-qemu2`` release of +``spacemit-k3-qemu-images``. + +Direct Linux boot +~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + $ qemu-system-riscv64 \ + -machine k3-pico-itx \ + -bios fw_dynamic.bin \ + -kernel Image \ + -initrd k3-qemu-initramfs.cpio.gz \ + -dtb k3-pico-itx-qemu.dtb \ + -append "earlycon=uart8250,mmio32,0xd4017000,115200 \ + console=ttyS0,115200 rdinit=/init" \ + -nographic -no-reboot + +The machine supplies the fixed 2 GiB RAM size and 8-hart topology, so no +``-m`` or ``-smp`` options are needed. Direct kernel boot requires +OpenSBI; ``-bios none`` is rejected when ``-kernel`` is present. +The machine uses the fixed ``spacemit-x100`` CPU model. + +U-Boot and SD boot +~~~~~~~~~~~~~~~~~~ + +Decompress ``k3-qemu-sd.raw.xz``, then start U-Boot proper as OpenSBI's next +stage: + +.. code-block:: bash + + $ xz -dk k3-qemu-sd.raw.xz + $ qemu-system-riscv64 \ + -machine k3-pico-itx \ + -bios fw_dynamic.bin \ + -kernel u-boot.bin \ + -dtb k3-pico-itx-qemu-uboot.dtb \ + -drive file=k3-qemu-sd.raw,if=sd,format=raw,snapshot=on \ + -nographic -no-reboot + +U-Boot imports its deterministic environment from the SD boot partition and +loads the Linux kernel, Linux device tree, and initramfs from that partition. +The successful path prints ``K3-QEMU: Starting kernel from SD`` before Linux +prints ``K3_LINUX_MVP_PASS``. + +Limitations +----------- + +This machine is a Linux boot subset rather than a complete K3 hardware model. +It disables the X100 H extension because the VS interrupt files and +virtualization path are not modeled. The A100 and IME harts, PCIe, +networking, multimedia accelerators, power management, and most board +peripherals are not implemented. + +The K3 BootROM, bootinfo parser, U-Boot SPL, and LPDDR training are not +modeled. QEMU provides initialized RAM and starts generic OpenSBI directly; +the firmware boot path therefore uses U-Boot proper rather than the vendor +SPL chain. diff --git a/docs/system/target-riscv.rst b/docs/system/target-riscv.rst index 9ee2011625193..546ddba16eb92 100644 --- a/docs/system/target-riscv.rst +++ b/docs/system/target-riscv.rst @@ -73,6 +73,7 @@ undocumented; you can get a complete list by running riscv/mips riscv/shakti-c riscv/sifive_u + riscv/spacemit-k3 riscv/virt riscv/xiangshan-kunminghu riscv/riscv-server-ref diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index ee4d628dbd008..55db0c38ebfd8 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -208,6 +208,9 @@ config RK3588_CRU config RK3588_SCMI bool +config SPACEMIT_K3_CTRL + bool + config MAC_VIA bool select MOS6522 diff --git a/hw/misc/meson.build b/hw/misc/meson.build index 8bb2965b90d4b..e92e19236194c 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -183,6 +183,7 @@ system_ss.add(when: 'CONFIG_MAC_VIA', if_true: files('mac_via.c')) system_ss.add(when: 'CONFIG_RK3588_CRU', if_true: files('rk3588_cru.c')) system_ss.add(when: 'CONFIG_RK3588_SCMI', if_true: files('rk3588_scmi.c')) +system_ss.add(when: 'CONFIG_SPACEMIT_K3_CTRL', if_true: files('spacemit-k3.c')) system_ss.add(when: 'CONFIG_NXP_S32_MC_ME', if_true: files('nxp_s32_mc_me.c')) specific_ss.add(when: 'CONFIG_MIPS_CPS', if_true: files('mips_cmgcr.c', 'mips_cpc.c')) diff --git a/hw/misc/spacemit-k3.c b/hw/misc/spacemit-k3.c new file mode 100644 index 0000000000000..e160f14627bcd --- /dev/null +++ b/hw/misc/spacemit-k3.c @@ -0,0 +1,206 @@ +/* + * SpacemiT K3 clock and boot controls + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "hw/misc/spacemit-k3.h" +#include "migration/vmstate.h" + +#define K3_APMU_SDH0_CTRL 0x54 +#define K3_APMU_SDH0_AXI_RESET_N BIT(0) +#define K3_APMU_SDH0_RESET_N BIT(1) +#define K3_APMU_SDH0_AXI_CLK_EN BIT(3) +#define K3_APMU_SDH0_CLK_EN BIT(4) +#define K3_APMU_SDH0_CLK_MUX_MASK (0x7U << 5) +#define K3_APMU_SDH0_CLK_DIV_MASK (0x7U << 8) +#define K3_APMU_SDH0_CLK_DIV_DEFAULT (0x1U << 8) +#define K3_APMU_SDH0_CLK_FC BIT(11) +#define K3_APMU_SDH0_CTRL_MASK (K3_APMU_SDH0_AXI_RESET_N | \ + K3_APMU_SDH0_RESET_N | \ + K3_APMU_SDH0_AXI_CLK_EN | \ + K3_APMU_SDH0_CLK_EN | \ + K3_APMU_SDH0_CLK_MUX_MASK | \ + K3_APMU_SDH0_CLK_DIV_MASK | \ + K3_APMU_SDH0_CLK_FC) +#define K3_APMU_SDH0_CTRL_RESET (K3_APMU_SDH0_AXI_RESET_N | \ + K3_APMU_SDH0_AXI_CLK_EN | \ + K3_APMU_SDH0_CLK_EN | \ + K3_APMU_SDH0_CLK_DIV_DEFAULT) + +#define K3_CIU_BOOT_FLAG 0x110 +#define K3_CIU_BOOT_FROM_SD 0xb10 + +static uint64_t spacemit_k3_apmu_read(void *opaque, hwaddr addr, + unsigned int size) +{ + SpacemitK3APMUState *s = SPACEMIT_K3_APMU(opaque); + + switch (addr) { + case K3_APMU_SDH0_CTRL: + return s->sdh0_ctrl; + default: + qemu_log_mask(LOG_UNIMP, + "%s: unimplemented read at offset 0x%" HWADDR_PRIx + "\n", __func__, addr); + return 0; + } +} + +static void spacemit_k3_apmu_write(void *opaque, hwaddr addr, uint64_t value, + unsigned int size) +{ + SpacemitK3APMUState *s = SPACEMIT_K3_APMU(opaque); + + switch (addr) { + case K3_APMU_SDH0_CTRL: + s->sdh0_ctrl = value & K3_APMU_SDH0_CTRL_MASK & + ~K3_APMU_SDH0_CLK_FC; + break; + default: + qemu_log_mask(LOG_UNIMP, + "%s: unimplemented write at offset 0x%" HWADDR_PRIx + "\n", __func__, addr); + break; + } +} + +static const MemoryRegionOps spacemit_k3_apmu_ops = { + .read = spacemit_k3_apmu_read, + .write = spacemit_k3_apmu_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void spacemit_k3_apmu_reset(DeviceState *dev) +{ + SpacemitK3APMUState *s = SPACEMIT_K3_APMU(dev); + + s->sdh0_ctrl = K3_APMU_SDH0_CTRL_RESET; +} + +static const VMStateDescription vmstate_spacemit_k3_apmu = { + .name = TYPE_SPACEMIT_K3_APMU, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(sdh0_ctrl, SpacemitK3APMUState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void spacemit_k3_apmu_realize(DeviceState *dev, Error **errp) +{ + SpacemitK3APMUState *s = SPACEMIT_K3_APMU(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &spacemit_k3_apmu_ops, s, + TYPE_SPACEMIT_K3_APMU, + SPACEMIT_K3_APMU_MMIO_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); +} + +static void spacemit_k3_apmu_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = spacemit_k3_apmu_realize; + device_class_set_legacy_reset(dc, spacemit_k3_apmu_reset); + dc->vmsd = &vmstate_spacemit_k3_apmu; + dc->desc = "SpacemiT K3 application power management unit"; +} + +static const TypeInfo spacemit_k3_apmu_type_info = { + .name = TYPE_SPACEMIT_K3_APMU, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SpacemitK3APMUState), + .class_init = spacemit_k3_apmu_class_init, +}; + +static uint64_t spacemit_k3_ciu_read(void *opaque, hwaddr addr, + unsigned int size) +{ + switch (addr) { + case K3_CIU_BOOT_FLAG: + return K3_CIU_BOOT_FROM_SD; + default: + qemu_log_mask(LOG_UNIMP, + "%s: unimplemented read at offset 0x%" HWADDR_PRIx + "\n", __func__, addr); + return 0; + } +} + +static void spacemit_k3_ciu_write(void *opaque, hwaddr addr, uint64_t value, + unsigned int size) +{ + switch (addr) { + case K3_CIU_BOOT_FLAG: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to read-only boot flag\n", __func__); + break; + default: + qemu_log_mask(LOG_UNIMP, + "%s: unimplemented write at offset 0x%" HWADDR_PRIx + "\n", __func__, addr); + break; + } +} + +static const MemoryRegionOps spacemit_k3_ciu_ops = { + .read = spacemit_k3_ciu_read, + .write = spacemit_k3_ciu_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void spacemit_k3_ciu_realize(DeviceState *dev, Error **errp) +{ + SpacemitK3CIUState *s = SPACEMIT_K3_CIU(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &spacemit_k3_ciu_ops, s, + TYPE_SPACEMIT_K3_CIU, + SPACEMIT_K3_CIU_MMIO_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); +} + +static void spacemit_k3_ciu_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = spacemit_k3_ciu_realize; + dc->desc = "SpacemiT K3 chip interface unit"; +} + +static const TypeInfo spacemit_k3_ciu_type_info = { + .name = TYPE_SPACEMIT_K3_CIU, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SpacemitK3CIUState), + .class_init = spacemit_k3_ciu_class_init, +}; + +static void spacemit_k3_control_register_types(void) +{ + type_register_static(&spacemit_k3_apmu_type_info); + type_register_static(&spacemit_k3_ciu_type_info); +} + +type_init(spacemit_k3_control_register_types) diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index 070418d78919a..68dd5bbac97f0 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -85,6 +85,19 @@ config RISCV_SERVER_PLATFORM_REF select RISCV_IMSIC select RISCV_IOMMU +config SPACEMIT_K3 + bool + default y + depends on RISCV64 + select CPU_CLUSTER + select DEVICE_TREE + select RISCV_ACLINT + select RISCV_APLIC + select RISCV_IMSIC + select SERIAL_MM + select SPACEMIT_K3_CTRL + select SPACEMIT_K3_SDHCI + config SHAKTI_C bool default y diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build index 5319b0d0336a2..c10eda9c7bd95 100644 --- a/hw/riscv/meson.build +++ b/hw/riscv/meson.build @@ -5,6 +5,7 @@ riscv_ss.add(files('riscv_hart.c')) riscv_ss.add(when: 'CONFIG_OPENTITAN', if_true: files('opentitan.c')) riscv_ss.add(when: 'CONFIG_RISCV_VIRT', if_true: files('virt.c')) riscv_ss.add(when: 'CONFIG_RISCV_SERVER_PLATFORM_REF', if_true: files('server_platform_ref.c')) +riscv_ss.add(when: 'CONFIG_SPACEMIT_K3', if_true: files('spacemit-k3.c')) riscv_ss.add(when: 'CONFIG_SHAKTI_C', if_true: files('shakti_c.c')) riscv_ss.add(when: 'CONFIG_SIFIVE_E', if_true: files('sifive_e.c')) riscv_ss.add(when: 'CONFIG_SIFIVE_U', if_true: files('sifive_u.c')) diff --git a/hw/riscv/spacemit-k3.c b/hw/riscv/spacemit-k3.c new file mode 100644 index 0000000000000..aeaff041d0f33 --- /dev/null +++ b/hw/riscv/spacemit-k3.c @@ -0,0 +1,582 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * SpacemiT K3 Pico-ITX machine + * + * This model implements the RISC-V platform and K3 boot-path subset needed + * to boot the SDK Linux kernel directly or through U-Boot with generic + * OpenSBI. BootROM, SPL, DDR training, and the A100/IME harts are outside + * this machine's current contract. + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "hw/core/loader.h" +#include "hw/core/qdev-properties.h" +#include "hw/intc/riscv_aclint.h" +#include "hw/intc/riscv_aplic.h" +#include "hw/intc/riscv_imsic.h" +#include "hw/riscv/boot.h" +#include "hw/riscv/machines-qom.h" +#include "hw/riscv/spacemit-k3.h" +#include "hw/sd/sd.h" +#include "system/address-spaces.h" +#include "system/block-backend.h" +#include "system/blockdev.h" +#include "system/device_tree.h" +#include "system/memory.h" +#include "system/qtest.h" +#include "system/reset.h" +#include "system/system.h" +#include "target/riscv/cpu.h" +#include "target/riscv/cpu-qom.h" +#include "target/riscv/time_helper.h" + +#include + +const MemMapEntry spacemit_k3_memmap[] = { + [K3_DEV_SRAM] = { 0xc0800000, 0x80000 }, + [K3_DEV_DDR_TRAINING] = { 0xc08d0000, 0x100 }, + [K3_DEV_UART0] = { 0xd4017000, 0x100 }, + [K3_DEV_SDHCI0] = { 0xd4280000, 0x200 }, + [K3_DEV_APMU] = { 0xd4282800, 0x400 }, + [K3_DEV_CIU] = { 0xd4282c00, 0x400 }, + [K3_DEV_S_IMSIC] = { 0xe0400000, 0x400000 }, + [K3_DEV_S_APLIC] = { 0xe0804000, 0x4000 }, + [K3_DEV_M_IMSIC] = { 0xf1000000, 0x10000 }, + [K3_DEV_M_APLIC] = { 0xf1800000, 0x4000 }, + [K3_DEV_M_CLINT] = { 0xf1810000, 0x10000 }, + [K3_DEV_FIRMWARE] = { 0x100000000, 32 * MiB }, + [K3_DEV_DRAM] = { 0x102000000, 0 }, +}; + +static RISCVCPU *k3_pico_itx_hart(SpacemitK3SoCState *s, unsigned int hartid) +{ + unsigned int cluster = hartid / K3_PICO_ITX_HARTS_PER_CLUSTER; + unsigned int index = hartid % K3_PICO_ITX_HARTS_PER_CLUSTER; + + return &s->cpus[cluster].harts[index]; +} + +static bool k3_pico_itx_validate_cpu(SpacemitK3SoCState *s, Error **errp) +{ + unsigned int hartid; + + for (hartid = 0; hartid < K3_PICO_ITX_NUM_HARTS; hartid++) { + RISCVCPU *cpu = k3_pico_itx_hart(s, hartid); + CPUState *cs = CPU(cpu); + unsigned int expected_cluster = + hartid / K3_PICO_ITX_HARTS_PER_CLUSTER; + + if (!riscv_has_ext(&cpu->env, RVV)) { + error_setg(errp, + "K3 X100 hart %u requires the V extension", + hartid); + return false; + } + if (cpu->cfg.vlenb != 256 / 8) { + error_setg(errp, "K3 X100 hart %u requires VLEN=256", hartid); + return false; + } + if (!cpu->cfg.ext_smaia || !cpu->cfg.ext_ssaia) { + error_setg(errp, + "K3 X100 hart %u requires Smaia and Ssaia", hartid); + return false; + } + if (!cpu->cfg.ext_sstc) { + error_setg(errp, "K3 X100 hart %u requires Sstc", hartid); + return false; + } + if (riscv_has_ext(&cpu->env, RVH)) { + error_setg(errp, + "K3 X100 hart %u does not support H", + hartid); + return false; + } + if (cs->cluster_index != expected_cluster) { + error_setg(errp, + "K3 hart %u has cluster index %d, expected %u", + hartid, cs->cluster_index, expected_cluster); + return false; + } + } + + return true; +} + +/* + * The RISC-V qtest accelerator does not install TCG's profile and feature + * properties. Supply the subset exercised by this non-executing accelerator + * directly; TCG still gets the same values through machine compatibility + * properties and is checked below. + */ +static void k3_pico_itx_apply_qtest_cpu_defaults(SpacemitK3SoCState *s) +{ + unsigned int hartid; + + if (!qtest_enabled()) { + return; + } + + for (hartid = 0; hartid < K3_PICO_ITX_NUM_HARTS; hartid++) { + RISCVCPU *cpu = k3_pico_itx_hart(s, hartid); + + cpu->env.misa_ext |= RVS | RVU | RVV; + cpu->env.misa_ext &= ~RVH; + cpu->env.priv_ver = PRIV_VERSION_1_13_0; + cpu->cfg.vlenb = 256 / 8; + cpu->cfg.ext_zicsr = true; + cpu->cfg.ext_smaia = true; + cpu->cfg.ext_ssaia = true; + cpu->cfg.ext_sstc = true; + riscv_timer_init(cpu); + } +} + +static void k3_pico_itx_create_aia(SpacemitK3SoCState *s) +{ + unsigned int hartid; + + for (hartid = 0; hartid < K3_PICO_ITX_NUM_HARTS; hartid++) { + s->m_imsic[hartid] = riscv_imsic_create( + spacemit_k3_memmap[K3_DEV_M_IMSIC].base + + hartid * IMSIC_HART_SIZE(0), + hartid, true, 1, K3_PICO_ITX_IMSIC_NUM_IDS); + } + + /* + * K3 assigns a 0x40000-byte interrupt-file stride to each X100 hart. + * Only the 511-ID S-mode page is modeled. The VS pages in each stride + * and the aperture for A100 harts remain unmapped. + */ + for (hartid = 0; hartid < K3_PICO_ITX_NUM_HARTS; hartid++) { + s->s_imsic[hartid] = riscv_imsic_create( + spacemit_k3_memmap[K3_DEV_S_IMSIC].base + hartid * 0x40000, + hartid, false, 1, K3_PICO_ITX_IMSIC_NUM_IDS); + } + + /* MSI-mode APLICs have no direct per-hart interrupt contexts. */ + s->m_aplic = riscv_aplic_create( + spacemit_k3_memmap[K3_DEV_M_APLIC].base, + spacemit_k3_memmap[K3_DEV_M_APLIC].size, + 0, 0, K3_PICO_ITX_APLIC_NUM_SOURCES, + K3_PICO_ITX_APLIC_IPRIO_BITS, true, true, NULL); + s->s_aplic = riscv_aplic_create( + spacemit_k3_memmap[K3_DEV_S_APLIC].base, + spacemit_k3_memmap[K3_DEV_S_APLIC].size, + 0, 0, K3_PICO_ITX_APLIC_NUM_SOURCES, + K3_PICO_ITX_APLIC_IPRIO_BITS, true, false, s->m_aplic); +} + +static void spacemit_k3_soc_reset(void *opaque) +{ + SpacemitK3SoCState *s = opaque; + + memset(memory_region_get_ram_ptr(&s->ddr_training), 0, + spacemit_k3_memmap[K3_DEV_DDR_TRAINING].size); +} + +static void spacemit_k3_soc_realize(DeviceState *dev, Error **errp) +{ + SpacemitK3SoCState *s = SPACEMIT_K3_SOC(dev); + MachineState *ms = MACHINE(qdev_get_machine()); + MemoryRegion *system_memory = get_system_memory(); + unsigned int cluster; + + memory_region_init_ram(&s->sram, OBJECT(dev), "spacemit.k3.sram", + spacemit_k3_memmap[K3_DEV_SRAM].size, errp); + if (*errp) { + return; + } + memory_region_add_subregion(system_memory, + spacemit_k3_memmap[K3_DEV_SRAM].base, + &s->sram); + + memory_region_init_ram(&s->ddr_training, OBJECT(dev), + "spacemit.k3.ddr-training", + spacemit_k3_memmap[K3_DEV_DDR_TRAINING].size, + errp); + if (*errp) { + return; + } + memory_region_add_subregion( + system_memory, spacemit_k3_memmap[K3_DEV_DDR_TRAINING].base, + &s->ddr_training); + + memory_region_init_ram(&s->firmware, OBJECT(dev), "spacemit.k3.firmware", + spacemit_k3_memmap[K3_DEV_FIRMWARE].size, errp); + if (*errp) { + return; + } + memory_region_add_subregion(system_memory, + spacemit_k3_memmap[K3_DEV_FIRMWARE].base, + &s->firmware); + + for (cluster = 0; cluster < K3_PICO_ITX_NUM_CLUSTERS; cluster++) { + qdev_prop_set_string(DEVICE(&s->cpus[cluster]), "cpu-type", + ms->cpu_type); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->cpus[cluster]), errp)) { + return; + } + } + + /* The CPUs must exist below each cluster before the cluster is realized. */ + for (cluster = 0; cluster < K3_PICO_ITX_NUM_CLUSTERS; cluster++) { + if (!qdev_realize(DEVICE(&s->clusters[cluster]), NULL, errp)) { + return; + } + } + + k3_pico_itx_apply_qtest_cpu_defaults(s); + if (!k3_pico_itx_validate_cpu(s, errp)) { + return; + } + + s->swi = riscv_aclint_swi_create( + spacemit_k3_memmap[K3_DEV_M_CLINT].base, + 0, K3_PICO_ITX_NUM_HARTS, false); + s->mtimer = riscv_aclint_mtimer_create( + spacemit_k3_memmap[K3_DEV_M_CLINT].base + RISCV_ACLINT_SWI_SIZE, + RISCV_ACLINT_DEFAULT_MTIMER_SIZE, + 0, K3_PICO_ITX_NUM_HARTS, + RISCV_ACLINT_DEFAULT_MTIMECMP, RISCV_ACLINT_DEFAULT_MTIME, + K3_PICO_ITX_TIMEBASE_FREQ, true); + + k3_pico_itx_create_aia(s); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->apmu), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->apmu), 0, + spacemit_k3_memmap[K3_DEV_APMU].base); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->ciu), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->ciu), 0, + spacemit_k3_memmap[K3_DEV_CIU].base); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->sdhci0), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->sdhci0), 0, + spacemit_k3_memmap[K3_DEV_SDHCI0].base); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->sdhci0), 0, + qdev_get_gpio_in(s->m_aplic, + K3_PICO_ITX_SDHCI0_IRQ)); + + memory_region_init(&s->uart0_mem, OBJECT(dev), "spacemit.k3.uart0", + spacemit_k3_memmap[K3_DEV_UART0].size); + memory_region_add_subregion(system_memory, + spacemit_k3_memmap[K3_DEV_UART0].base, + &s->uart0_mem); + s->uart0 = serial_mm_init( + &s->uart0_mem, 0, 2, + qdev_get_gpio_in(s->m_aplic, K3_PICO_ITX_UART0_IRQ), + 115200, serial_hd(0), DEVICE_LITTLE_ENDIAN); + + qemu_register_reset(spacemit_k3_soc_reset, s); +} + +static void spacemit_k3_soc_instance_init(Object *obj) +{ + SpacemitK3SoCState *s = SPACEMIT_K3_SOC(obj); + unsigned int cluster; + + object_initialize_child(obj, "cluster0", &s->clusters[0], + TYPE_CPU_CLUSTER); + object_initialize_child(OBJECT(&s->clusters[0]), "cpus", &s->cpus[0], + TYPE_RISCV_HART_ARRAY); + object_initialize_child(obj, "cluster1", &s->clusters[1], + TYPE_CPU_CLUSTER); + object_initialize_child(OBJECT(&s->clusters[1]), "cpus", &s->cpus[1], + TYPE_RISCV_HART_ARRAY); + object_initialize_child(obj, "apmu", &s->apmu, TYPE_SPACEMIT_K3_APMU); + object_initialize_child(obj, "ciu", &s->ciu, TYPE_SPACEMIT_K3_CIU); + object_initialize_child(obj, "sdhci0", &s->sdhci0, + TYPE_SPACEMIT_K3_SDHCI); + + for (cluster = 0; cluster < K3_PICO_ITX_NUM_CLUSTERS; cluster++) { + qdev_prop_set_uint32(DEVICE(&s->clusters[cluster]), "cluster-id", + cluster); + qdev_prop_set_uint32(DEVICE(&s->cpus[cluster]), "num-harts", + K3_PICO_ITX_HARTS_PER_CLUSTER); + qdev_prop_set_uint32(DEVICE(&s->cpus[cluster]), "hartid-base", + cluster * K3_PICO_ITX_HARTS_PER_CLUSTER); + qdev_prop_set_uint64(DEVICE(&s->cpus[cluster]), "resetvec", + spacemit_k3_memmap[K3_DEV_SRAM].base); + } +} + +static void spacemit_k3_soc_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = spacemit_k3_soc_realize; + dc->user_creatable = false; +} + +static const TypeInfo spacemit_k3_soc_type_info = { + .name = TYPE_SPACEMIT_K3_SOC, + .parent = TYPE_DEVICE, + .instance_size = sizeof(SpacemitK3SoCState), + .instance_init = spacemit_k3_soc_instance_init, + .class_init = spacemit_k3_soc_class_init, +}; + +static void k3_pico_itx_validate_machine(MachineState *machine) +{ + const CpuTopology *smp = &machine->smp; + + if (machine->ram_size != 2 * GiB) { + error_report("k3-pico-itx requires exactly 2 GiB of RAM"); + exit(EXIT_FAILURE); + } + if (strcmp(machine->cpu_type, TYPE_RISCV_CPU_SPACEMIT_X100)) { + error_report("k3-pico-itx requires the spacemit-x100 CPU type"); + exit(EXIT_FAILURE); + } + if (smp->cpus != K3_PICO_ITX_NUM_HARTS || + smp->max_cpus != K3_PICO_ITX_NUM_HARTS || + smp->sockets != 1 || smp->clusters != K3_PICO_ITX_NUM_CLUSTERS || + smp->cores != K3_PICO_ITX_HARTS_PER_CLUSTER || smp->threads != 1) { + error_report("k3-pico-itx requires " + "cpus=8,sockets=1,clusters=2,cores=4,threads=1,maxcpus=8"); + exit(EXIT_FAILURE); + } +} + +static void k3_pico_itx_validate_fdt(K3PicoITXState *s) +{ + MachineState *machine = MACHINE(s); + const char expected_compat[] = "spacemit,k3-pico-itx"; + const fdt32_t *prop; + int len; + int offset; + unsigned int hartid; + uint64_t base; + uint64_t size; + + if (fdt_node_check_compatible(machine->fdt, 0, expected_compat)) { + error_report("k3-pico-itx DTB requires root compatible '%s'", + expected_compat); + exit(EXIT_FAILURE); + } + + offset = fdt_path_offset(machine->fdt, "/cpus"); + prop = offset >= 0 ? + fdt_getprop(machine->fdt, offset, "timebase-frequency", &len) : NULL; + if (!prop || len != sizeof(*prop) || + fdt32_to_cpu(*prop) != K3_PICO_ITX_TIMEBASE_FREQ) { + error_report("k3-pico-itx DTB requires a 24 MHz timebase"); + exit(EXIT_FAILURE); + } + + for (hartid = 0; hartid < K3_PICO_ITX_NUM_HARTS; hartid++) { + g_autofree char *node = g_strdup_printf("/cpus/cpu@%x", hartid); + + if (fdt_path_offset(machine->fdt, node) < 0) { + error_report("k3-pico-itx DTB is missing X100 hart %u", hartid); + exit(EXIT_FAILURE); + } + riscv_isa_write_fdt(k3_pico_itx_hart(&s->soc, hartid), + machine->fdt, node); + } + if (fdt_path_offset(machine->fdt, "/cpus/cpu@8") >= 0) { + error_report("k3-pico-itx DTB must not expose A100 harts"); + exit(EXIT_FAILURE); + } + + offset = fdt_path_offset(machine->fdt, "/memory@102000000"); + prop = offset >= 0 ? fdt_getprop(machine->fdt, offset, "reg", &len) : NULL; + if (!prop || len != 4 * sizeof(*prop)) { + error_report("k3-pico-itx DTB requires the 64-bit Linux memory node"); + exit(EXIT_FAILURE); + } + base = ((uint64_t)fdt32_to_cpu(prop[0]) << 32) | fdt32_to_cpu(prop[1]); + size = ((uint64_t)fdt32_to_cpu(prop[2]) << 32) | fdt32_to_cpu(prop[3]); + if (base != spacemit_k3_memmap[K3_DEV_DRAM].base || + size != machine->ram_size) { + error_report("k3-pico-itx DTB memory must be 2 GiB at 0x102000000"); + exit(EXIT_FAILURE); + } + + if (fdt_path_offset(machine->fdt, "/chosen") < 0) { + qemu_fdt_add_subnode(machine->fdt, "/chosen"); + } +} + +static void k3_pico_itx_check_loaded_payloads(MachineState *machine, + RISCVBootInfo *info) +{ + hwaddr dram_base = spacemit_k3_memmap[K3_DEV_DRAM].base; + hwaddr dram_end = dram_base + machine->ram_size; + + if (info->kernel_size && + (info->image_low_addr < dram_base || + info->image_high_addr > dram_end || + info->image_high_addr < info->image_low_addr)) { + error_report("K3 -kernel payload does not fit in the DRAM window"); + exit(EXIT_FAILURE); + } + if (info->initrd_size && + (info->initrd_start < dram_base || + info->initrd_start + info->initrd_size > dram_end || + info->initrd_start + info->initrd_size < info->initrd_start)) { + error_report("K3 -initrd payload does not fit in the DRAM window"); + exit(EXIT_FAILURE); + } +} + +static void k3_pico_itx_attach_sd_card(K3PicoITXState *s) +{ + DriveInfo *dinfo = drive_get(IF_SD, 0, 0); + DeviceState *card; + + if (!dinfo) { + return; + } + + card = qdev_new(TYPE_SD_CARD); + qdev_prop_set_drive_err(card, "drive", blk_by_legacy_dinfo(dinfo), + &error_fatal); + qdev_realize_and_unref(card, s->soc.sdhci0.sd_bus, &error_fatal); +} + +static void k3_pico_itx_machine_init(MachineState *machine) +{ + K3PicoITXState *s = K3_PICO_ITX_MACHINE(machine); + MemoryRegion *system_memory = get_system_memory(); + RISCVBootInfo boot_info; + hwaddr firmware_load_addr = spacemit_k3_memmap[K3_DEV_FIRMWARE].base; + hwaddr firmware_end_addr = firmware_load_addr; + hwaddr fdt_load_addr = 0; + uint64_t kernel_entry = 0; + + k3_pico_itx_validate_machine(machine); + + memory_region_add_subregion(system_memory, + spacemit_k3_memmap[K3_DEV_DRAM].base, + machine->ram); + qdev_realize(DEVICE(&s->soc), NULL, &error_fatal); + k3_pico_itx_attach_sd_card(s); + + riscv_boot_info_init(&boot_info, &s->soc.cpus[0]); + + if (machine->kernel_filename && + machine->firmware && !strcmp(machine->firmware, "none")) { + error_report("k3-pico-itx direct Linux boot requires OpenSBI firmware"); + exit(EXIT_FAILURE); + } + + firmware_end_addr = riscv_find_and_load_firmware( + machine, riscv_default_firmware_name(&s->soc.cpus[0]), + &firmware_load_addr, NULL); + if (firmware_load_addr < spacemit_k3_memmap[K3_DEV_FIRMWARE].base || + firmware_end_addr > spacemit_k3_memmap[K3_DEV_DRAM].base || + firmware_end_addr < firmware_load_addr) { + error_report("K3 firmware must fit in 0x100000000..0x102000000"); + exit(EXIT_FAILURE); + } + + if (machine->kernel_filename && !machine->dtb) { + error_report("k3-pico-itx direct Linux boot requires -dtb"); + exit(EXIT_FAILURE); + } + if (machine->dtb) { + machine->fdt = load_device_tree(machine->dtb, &s->fdt_size); + if (!machine->fdt) { + error_report("failed to load k3-pico-itx device tree"); + exit(EXIT_FAILURE); + } + k3_pico_itx_validate_fdt(s); + } + + if (machine->kernel_filename) { + hwaddr kernel_start_addr = MAX( + riscv_calc_kernel_start_addr(&boot_info, firmware_end_addr), + spacemit_k3_memmap[K3_DEV_DRAM].base); + + riscv_load_kernel(machine, &boot_info, kernel_start_addr, true, NULL); + k3_pico_itx_check_loaded_payloads(machine, &boot_info); + kernel_entry = boot_info.image_low_addr; + } + + if (machine->fdt) { + fdt_load_addr = riscv_compute_fdt_addr( + spacemit_k3_memmap[K3_DEV_DRAM].base, machine->ram_size, + machine, &boot_info); + if (fdt_load_addr < spacemit_k3_memmap[K3_DEV_DRAM].base || + fdt_load_addr + fdt_totalsize(machine->fdt) > + spacemit_k3_memmap[K3_DEV_DRAM].base + machine->ram_size) { + error_report("K3 DTB does not fit in the DRAM window"); + exit(EXIT_FAILURE); + } + riscv_load_fdt(fdt_load_addr, machine->fdt); + } + + riscv_setup_rom_reset_vec(machine, &s->soc.cpus[0], firmware_load_addr, + spacemit_k3_memmap[K3_DEV_SRAM].base, + spacemit_k3_memmap[K3_DEV_SRAM].size, + kernel_entry, fdt_load_addr); +} + +static void k3_pico_itx_machine_instance_init(Object *obj) +{ + K3PicoITXState *s = K3_PICO_ITX_MACHINE(obj); + MachineState *machine = MACHINE(obj); + + machine->smp.cpus = K3_PICO_ITX_NUM_HARTS; + machine->smp.max_cpus = K3_PICO_ITX_NUM_HARTS; + machine->smp.sockets = 1; + machine->smp.clusters = K3_PICO_ITX_NUM_CLUSTERS; + machine->smp.cores = K3_PICO_ITX_HARTS_PER_CLUSTER; + machine->smp.threads = 1; + + object_initialize_child(obj, "soc", &s->soc, TYPE_SPACEMIT_K3_SOC); +} + +static GlobalProperty k3_pico_itx_cpu_defaults[] = { + { TYPE_RISCV_CPU_SPACEMIT_X100, "h", "false", .optional = true }, +}; + +static void k3_pico_itx_machine_class_init(ObjectClass *oc, const void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + static const char *const valid_cpu_types[] = { + TYPE_RISCV_CPU_SPACEMIT_X100, + NULL, + }; + + mc->desc = "SpacemiT K3 Pico-ITX (X100 subset)"; + mc->init = k3_pico_itx_machine_init; + mc->min_cpus = K3_PICO_ITX_NUM_HARTS; + mc->max_cpus = K3_PICO_ITX_NUM_HARTS; + mc->default_cpus = K3_PICO_ITX_NUM_HARTS; + mc->default_cpu_type = TYPE_RISCV_CPU_SPACEMIT_X100; + mc->valid_cpu_types = valid_cpu_types; + mc->default_ram_size = 2 * GiB; + mc->default_ram_id = "spacemit.k3.ram"; + mc->auto_create_sdcard = true; + mc->smp_props.clusters_supported = true; + compat_props_add(mc->compat_props, k3_pico_itx_cpu_defaults, + G_N_ELEMENTS(k3_pico_itx_cpu_defaults)); +} + +static const TypeInfo k3_pico_itx_machine_type_info = { + .name = TYPE_K3_PICO_ITX_MACHINE, + .parent = TYPE_MACHINE, + .instance_size = sizeof(K3PicoITXState), + .instance_init = k3_pico_itx_machine_instance_init, + .class_init = k3_pico_itx_machine_class_init, + .interfaces = riscv64_machine_interfaces, +}; + +static void spacemit_k3_register_types(void) +{ + type_register_static(&spacemit_k3_soc_type_info); + type_register_static(&k3_pico_itx_machine_type_info); +} + +type_init(spacemit_k3_register_types) diff --git a/hw/sd/Kconfig b/hw/sd/Kconfig index ad081f7226d7c..4d98d664895fa 100644 --- a/hw/sd/Kconfig +++ b/hw/sd/Kconfig @@ -39,3 +39,7 @@ config CADENCE_SDHCI config K230_SDHCI bool select SDHCI + +config SPACEMIT_K3_SDHCI + bool + select SDHCI diff --git a/hw/sd/meson.build b/hw/sd/meson.build index 37fb5a89a891e..b637c06c87eef 100644 --- a/hw/sd/meson.build +++ b/hw/sd/meson.build @@ -14,3 +14,4 @@ system_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-sdhost.c')) system_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_sdhci.c')) system_ss.add(when: 'CONFIG_CADENCE_SDHCI', if_true: files('cadence_sdhci.c')) system_ss.add(when: 'CONFIG_K230_SDHCI', if_true: files('k230_sdhci.c')) +system_ss.add(when: 'CONFIG_SPACEMIT_K3_SDHCI', if_true: files('spacemit-k3-sdhci.c')) diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c index 88a6d05ace4c7..76c94642227f8 100644 --- a/hw/sd/sdhci.c +++ b/hw/sd/sdhci.c @@ -950,6 +950,11 @@ static void sdhci_data_transfer(void *opaque) if (s->trnmod & SDHC_TRNS_DMA) { switch (SDHC_DMA_TYPE(s->hostctl1)) { case SDHC_CTRL_SDMA: + if (!(s->capareg & R_SDHC_CAPAB_SDMA_MASK)) { + trace_sdhci_error("SDMA not supported"); + break; + } + sdhci_sdma_transfer(s); break; case SDHC_CTRL_ADMA1_32: @@ -1248,7 +1253,9 @@ sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) * DMA can be enabled only if it is supported as indicated by * capabilities register */ - if (!(s->capareg & R_SDHC_CAPAB_SDMA_MASK)) { + if (!(s->capareg & (R_SDHC_CAPAB_SDMA_MASK | + R_SDHC_CAPAB_ADMA1_MASK | + R_SDHC_CAPAB_ADMA2_MASK))) { value &= ~SDHC_TRNS_DMA; } diff --git a/hw/sd/spacemit-k3-sdhci.c b/hw/sd/spacemit-k3-sdhci.c new file mode 100644 index 0000000000000..07df5743d699f --- /dev/null +++ b/hw/sd/spacemit-k3-sdhci.c @@ -0,0 +1,164 @@ +/* + * SpacemiT K3 SDHCI controller + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "hw/core/qdev-properties.h" +#include "hw/sd/spacemit-k3-sdhci.h" +#include "migration/vmstate.h" + +#define K3_SDHCI_STD_SIZE 0x100 +#define K3_SDHCI_VENDOR_SIZE 0x100 + +/* Offsets below are relative to the vendor bank at controller offset 0x100. */ +#define K3_SDHCI_MMC_CTRL 0x14 +#define K3_SDHCI_TX_CFG 0x1c +#define K3_SDHCI_MMC_CTRL_MASK (BIT(8) | BIT(9) | BIT(10) | BIT(12)) +#define K3_SDHCI_TX_CFG_MASK (BIT(30) | BIT(31)) + +static uint64_t spacemit_k3_sdhci_vendor_read(void *opaque, hwaddr addr, + unsigned int size) +{ + SpacemitK3SDHCIState *s = SPACEMIT_K3_SDHCI(opaque); + + switch (addr) { + case K3_SDHCI_MMC_CTRL: + return s->mmc_ctrl; + case K3_SDHCI_TX_CFG: + return s->tx_cfg; + default: + qemu_log_mask(LOG_UNIMP, + "%s: unimplemented read at offset 0x%" HWADDR_PRIx + "\n", __func__, K3_SDHCI_STD_SIZE + addr); + return 0; + } +} + +static void spacemit_k3_sdhci_vendor_write(void *opaque, hwaddr addr, + uint64_t value, + unsigned int size) +{ + SpacemitK3SDHCIState *s = SPACEMIT_K3_SDHCI(opaque); + + switch (addr) { + case K3_SDHCI_MMC_CTRL: + s->mmc_ctrl = value & K3_SDHCI_MMC_CTRL_MASK; + break; + case K3_SDHCI_TX_CFG: + s->tx_cfg = value & K3_SDHCI_TX_CFG_MASK; + break; + default: + qemu_log_mask(LOG_UNIMP, + "%s: unimplemented write at offset 0x%" HWADDR_PRIx + "\n", __func__, K3_SDHCI_STD_SIZE + addr); + break; + } +} + +static const MemoryRegionOps spacemit_k3_sdhci_vendor_ops = { + .read = spacemit_k3_sdhci_vendor_read, + .write = spacemit_k3_sdhci_vendor_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void spacemit_k3_sdhci_instance_init(Object *obj) +{ + SpacemitK3SDHCIState *s = SPACEMIT_K3_SDHCI(obj); + + object_initialize_child(obj, "generic-sdhci", &s->sdhci, + TYPE_SYSBUS_SDHCI); + qdev_prop_set_uint8(DEVICE(&s->sdhci), "sd-spec-version", 3); + /* + * The core uses this property to retain v3 Host Control 2 writes. The + * capability register still advertises neither 1.8 V nor UHS modes. + */ + qdev_prop_set_uint8(DEVICE(&s->sdhci), "uhs", UHS_I); + qdev_prop_set_uint64(DEVICE(&s->sdhci), "capareg", + SPACEMIT_K3_SDHCI_CAPAREG); +} + +static void spacemit_k3_sdhci_reset(DeviceState *dev) +{ + SpacemitK3SDHCIState *s = SPACEMIT_K3_SDHCI(dev); + + s->mmc_ctrl = 0; + s->tx_cfg = 0; + device_cold_reset(DEVICE(&s->sdhci)); +} + +static void spacemit_k3_sdhci_realize(DeviceState *dev, Error **errp) +{ + SpacemitK3SDHCIState *s = SPACEMIT_K3_SDHCI(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + SysBusDevice *sdhci_sbd = SYS_BUS_DEVICE(&s->sdhci); + + memory_region_init(&s->container, OBJECT(s), + "spacemit.k3.sdhci-container", + SPACEMIT_K3_SDHCI_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->container); + + memory_region_init_io(&s->vendor_iomem, OBJECT(s), + &spacemit_k3_sdhci_vendor_ops, s, + "spacemit.k3.sdhci-vendor", K3_SDHCI_VENDOR_SIZE); + memory_region_add_subregion(&s->container, K3_SDHCI_STD_SIZE, + &s->vendor_iomem); + + if (!sysbus_realize(sdhci_sbd, errp)) { + return; + } + memory_region_add_subregion(&s->container, 0, + sysbus_mmio_get_region(sdhci_sbd, 0)); + + sysbus_pass_irq(sbd, sdhci_sbd); + s->sd_bus = qdev_get_child_bus(DEVICE(sdhci_sbd), "sd-bus"); +} + +static const VMStateDescription vmstate_spacemit_k3_sdhci = { + .name = TYPE_SPACEMIT_K3_SDHCI, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(mmc_ctrl, SpacemitK3SDHCIState), + VMSTATE_UINT32(tx_cfg, SpacemitK3SDHCIState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void spacemit_k3_sdhci_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = spacemit_k3_sdhci_realize; + device_class_set_legacy_reset(dc, spacemit_k3_sdhci_reset); + dc->vmsd = &vmstate_spacemit_k3_sdhci; + dc->desc = "SpacemiT K3 SDHCI controller"; +} + +static const TypeInfo spacemit_k3_sdhci_type_info = { + .name = TYPE_SPACEMIT_K3_SDHCI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SpacemitK3SDHCIState), + .instance_init = spacemit_k3_sdhci_instance_init, + .class_init = spacemit_k3_sdhci_class_init, +}; + +static void spacemit_k3_sdhci_register_types(void) +{ + type_register_static(&spacemit_k3_sdhci_type_info); +} + +type_init(spacemit_k3_sdhci_register_types) diff --git a/include/hw/misc/spacemit-k3.h b/include/hw/misc/spacemit-k3.h new file mode 100644 index 0000000000000..5b7ff65d4a487 --- /dev/null +++ b/include/hw/misc/spacemit-k3.h @@ -0,0 +1,37 @@ +/* + * SpacemiT K3 clock and boot controls + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_SPACEMIT_K3_H +#define HW_MISC_SPACEMIT_K3_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_SPACEMIT_K3_APMU "spacemit.k3.apmu" +OBJECT_DECLARE_SIMPLE_TYPE(SpacemitK3APMUState, SPACEMIT_K3_APMU) + +#define TYPE_SPACEMIT_K3_CIU "spacemit.k3.ciu" +OBJECT_DECLARE_SIMPLE_TYPE(SpacemitK3CIUState, SPACEMIT_K3_CIU) + +#define SPACEMIT_K3_APMU_MMIO_SIZE 0x400 +#define SPACEMIT_K3_CIU_MMIO_SIZE 0x400 + +struct SpacemitK3APMUState { + SysBusDevice parent_obj; + + MemoryRegion mmio; + uint32_t sdh0_ctrl; +}; + +struct SpacemitK3CIUState { + SysBusDevice parent_obj; + + MemoryRegion mmio; +}; + +#endif /* HW_MISC_SPACEMIT_K3_H */ diff --git a/include/hw/riscv/spacemit-k3.h b/include/hw/riscv/spacemit-k3.h new file mode 100644 index 0000000000000..5bc4ec3742ed7 --- /dev/null +++ b/include/hw/riscv/spacemit-k3.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * SpacemiT K3 SoC and Pico-ITX machine + */ + +#ifndef HW_SPACEMIT_K3_H +#define HW_SPACEMIT_K3_H + +#include "exec/hwaddr.h" +#include "hw/char/serial-mm.h" +#include "hw/core/boards.h" +#include "hw/cpu/cluster.h" +#include "hw/misc/spacemit-k3.h" +#include "hw/riscv/riscv_hart.h" +#include "hw/sd/spacemit-k3-sdhci.h" + +#define K3_PICO_ITX_NUM_CLUSTERS 2 +#define K3_PICO_ITX_HARTS_PER_CLUSTER 4 +#define K3_PICO_ITX_NUM_HARTS 8 + +#define K3_PICO_ITX_TIMEBASE_FREQ 24000000 +#define K3_PICO_ITX_APLIC_NUM_SOURCES 512 +#define K3_PICO_ITX_APLIC_IPRIO_BITS 8 +#define K3_PICO_ITX_IMSIC_NUM_IDS 511 +#define K3_PICO_ITX_UART0_IRQ 42 +#define K3_PICO_ITX_SDHCI0_IRQ 99 + +enum { + K3_DEV_SRAM, + K3_DEV_DDR_TRAINING, + K3_DEV_UART0, + K3_DEV_SDHCI0, + K3_DEV_APMU, + K3_DEV_CIU, + K3_DEV_S_IMSIC, + K3_DEV_S_APLIC, + K3_DEV_M_IMSIC, + K3_DEV_M_APLIC, + K3_DEV_M_CLINT, + K3_DEV_FIRMWARE, + K3_DEV_DRAM, +}; + +extern const MemMapEntry spacemit_k3_memmap[]; + +#define TYPE_SPACEMIT_K3_SOC "spacemit.k3.soc" +OBJECT_DECLARE_SIMPLE_TYPE(SpacemitK3SoCState, SPACEMIT_K3_SOC) + +struct SpacemitK3SoCState { + DeviceState parent_obj; + + CPUClusterState clusters[K3_PICO_ITX_NUM_CLUSTERS]; + RISCVHartArrayState cpus[K3_PICO_ITX_NUM_CLUSTERS]; + MemoryRegion sram; + MemoryRegion ddr_training; + MemoryRegion firmware; + MemoryRegion uart0_mem; + SpacemitK3APMUState apmu; + SpacemitK3CIUState ciu; + SpacemitK3SDHCIState sdhci0; + DeviceState *m_imsic[K3_PICO_ITX_NUM_HARTS]; + DeviceState *s_imsic[K3_PICO_ITX_NUM_HARTS]; + DeviceState *m_aplic; + DeviceState *s_aplic; + DeviceState *swi; + DeviceState *mtimer; + SerialMM *uart0; +}; + +#define TYPE_K3_PICO_ITX_MACHINE MACHINE_TYPE_NAME("k3-pico-itx") +OBJECT_DECLARE_SIMPLE_TYPE(K3PicoITXState, K3_PICO_ITX_MACHINE) + +struct K3PicoITXState { + MachineState parent_obj; + + SpacemitK3SoCState soc; + int fdt_size; +}; + +#endif diff --git a/include/hw/sd/spacemit-k3-sdhci.h b/include/hw/sd/spacemit-k3-sdhci.h new file mode 100644 index 0000000000000..d54c8e6d84bbf --- /dev/null +++ b/include/hw/sd/spacemit-k3-sdhci.h @@ -0,0 +1,33 @@ +/* + * SpacemiT K3 SDHCI controller + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_SD_SPACEMIT_K3_SDHCI_H +#define HW_SD_SPACEMIT_K3_SDHCI_H + +#include "hw/core/sysbus.h" +#include "hw/sd/sdhci.h" +#include "qom/object.h" + +#define TYPE_SPACEMIT_K3_SDHCI "spacemit.k3.sdhci" +OBJECT_DECLARE_SIMPLE_TYPE(SpacemitK3SDHCIState, SPACEMIT_K3_SDHCI) + +#define SPACEMIT_K3_SDHCI_MMIO_SIZE 0x200 +#define SPACEMIT_K3_SDHCI_CAPAREG UINT64_C(0x112834b4) + +struct SpacemitK3SDHCIState { + SysBusDevice parent_obj; + + SDHCIState sdhci; + MemoryRegion container; + MemoryRegion vendor_iomem; + uint32_t mmc_ctrl; + uint32_t tx_cfg; + BusState *sd_bus; +}; + +#endif /* HW_SD_SPACEMIT_K3_SDHCI_H */ diff --git a/target/riscv/cpu-qom.h b/target/riscv/cpu-qom.h index 61234842e3cb3..7bdca8e35dabf 100644 --- a/target/riscv/cpu-qom.h +++ b/target/riscv/cpu-qom.h @@ -45,6 +45,7 @@ #define TYPE_RISCV_CPU_RVSERVER_REF RISCV_CPU_TYPE_NAME("riscv-server-ref") #define TYPE_RISCV_CPU_IBEX RISCV_CPU_TYPE_NAME("lowrisc-ibex") #define TYPE_RISCV_CPU_SHAKTI_C RISCV_CPU_TYPE_NAME("shakti-c") +#define TYPE_RISCV_CPU_SPACEMIT_X100 RISCV_CPU_TYPE_NAME("spacemit-x100") #define TYPE_RISCV_CPU_SIFIVE_E RISCV_CPU_TYPE_NAME("sifive-e") #define TYPE_RISCV_CPU_SIFIVE_E31 RISCV_CPU_TYPE_NAME("sifive-e31") #define TYPE_RISCV_CPU_SIFIVE_E34 RISCV_CPU_TYPE_NAME("sifive-e34") diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 1c871723f8540..68c9692314189 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -3483,6 +3483,18 @@ static const TypeInfo riscv_cpu_type_infos[] = { DEFINE_PROFILE_CPU(TYPE_RISCV_CPU_RVA22S64, TYPE_RISCV_CPU_RV64I, RVA22S64), DEFINE_PROFILE_CPU(TYPE_RISCV_CPU_RVA23U64, TYPE_RISCV_CPU_RV64I, RVA23U64), DEFINE_PROFILE_CPU(TYPE_RISCV_CPU_RVA23S64, TYPE_RISCV_CPU_RV64I, RVA23S64), + + DEFINE_RISCV_CPU(TYPE_RISCV_CPU_SPACEMIT_X100, + TYPE_RISCV_VENDOR_CPU, + .profile = &RVA23S64, + .misa_mxl_max = MXL_RV64, + .misa_ext = RVH, + .cfg.mmu = true, + .cfg.vlenb = 256 >> 3, + .cfg.ext_smaia = true, + .cfg.ext_ssaia = true, + .cfg.max_satp_mode = VM_1_10_SV39, + ), #endif /* TARGET_RISCV64 */ }; diff --git a/tests/functional/riscv64/meson.build b/tests/functional/riscv64/meson.build index 2eb12586bf0e8..9746f20b3bab4 100644 --- a/tests/functional/riscv64/meson.build +++ b/tests/functional/riscv64/meson.build @@ -3,6 +3,7 @@ test_riscv64_timeouts = { 'boston' : 120, 'server_ref' : 120, + 'spacemit_k3' : 180, 'tuxrun' : 120, } @@ -16,5 +17,6 @@ tests_riscv64_system_thorough = [ 'boston', 'server_ref', 'sifive_u', + 'spacemit_k3', 'tuxrun', ] diff --git a/tests/functional/riscv64/test_spacemit_k3.py b/tests/functional/riscv64/test_spacemit_k3.py new file mode 100755 index 0000000000000..a7a41302a438f --- /dev/null +++ b/tests/functional/riscv64/test_spacemit_k3.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +# +# Functional test for the SpacemiT K3 Pico-ITX machine +# +# SPDX-License-Identifier: GPL-2.0-or-later + +""" +Boot pinned SpacemiT K3 SDK Linux images on k3-pico-itx. +""" + +from qemu_test import Asset, QemuSystemTest +from qemu_test import wait_for_console_pattern + + +class SpacemitK3Test(QemuSystemTest): + """Test direct and U-Boot K3 Pico-ITX Linux boot paths.""" + + RELEASE_URL = ( + 'https://github.com/zevorn/spacemit-k3-qemu-images/releases/' + 'download/sdk-v1.0.2-qemu2/' + ) + + ASSET_KERNEL = Asset( + RELEASE_URL + 'Image', + 'cc7359e16ba6569fee49be475d561ab42f90e0aa0e9fb7fc1ead4ab1be066efd') + ASSET_FIRMWARE = Asset( + RELEASE_URL + 'fw_dynamic.bin', + 'e62c515fa5aa5fadee169a316c30f99f365b09ae2eff72c783d936b0b337e0a1') + ASSET_INITRAMFS = Asset( + RELEASE_URL + 'k3-qemu-initramfs.cpio.gz', + '42670a19d756fc239376774577cc267b26fdec872827eed3a02139e8e5ce11e3') + ASSET_DTB = Asset( + RELEASE_URL + 'k3-pico-itx-qemu.dtb', + '35e2fc80a64e41963d091cb23b142ed6dfc1e56f9e808061c6e241d7da4bf56e') + ASSET_UBOOT = Asset( + RELEASE_URL + 'u-boot.bin', + '02a86461f8ea30e9bae42b6c420cd9897cdc6b29b334a00b7919cb67995bd7b5') + ASSET_UBOOT_DTB = Asset( + RELEASE_URL + 'k3-pico-itx-qemu-uboot.dtb', + 'd695bac441b5a5a2814fc2ae0c5e735c11a37379e36ec59cedd16cb9f6bc3486') + ASSET_SD_IMAGE = Asset( + RELEASE_URL + 'k3-qemu-sd.raw.xz', + 'b00d9abd9c65e25346c2f76b304af0785755b2fcf49ea7faf6ec877228f32e65') + + def _wait_for_linux_boot(self): + panic = 'Kernel panic - not syncing' + expected = ( + 'Linux version 6.18.3-g0ffac20d9ef9', + ('Machine model: SpacemiT K3 Pico-ITX ' + '(QEMU Linux-first subset)'), + 'SBI specification v2.0 detected', + 'SBI implementation ID=0x1 Version=0x10006', + ('riscv-timer: Timer interrupt in S-mode is available ' + 'via sstc extension'), + 'smp: Brought up 1 node, 8 CPUs', + ('riscv-imsic: interrupt-controller@e0400000: ' + 'per-CPU IDs 511'), + ('riscv-aplic e0804000.interrupt-controller: ' + '512 interrupts forwarded'), + 'd4017000.serial: ttyS0 at MMIO 0xd4017000', + ('K3-QEMU: model=SpacemiT K3 Pico-ITX ' + '(QEMU Linux-first subset)'), + 'K3-QEMU: cpus=8', + 'K3-QEMU: Linux boot successful', + 'K3_LINUX_MVP_PASS', + ) + + for pattern in expected: + wait_for_console_pattern(self, pattern, panic) + + def test_linux_boot(self): + self.set_machine('k3-pico-itx') + + kernel_path = self.ASSET_KERNEL.fetch() + firmware_path = self.ASSET_FIRMWARE.fetch() + initramfs_path = self.ASSET_INITRAMFS.fetch() + dtb_path = self.ASSET_DTB.fetch() + + kernel_command_line = ( + 'earlycon=uart8250,mmio32,0xd4017000,115200 ' + 'console=ttyS0,115200 rdinit=/init' + ) + self.vm.add_args('-bios', firmware_path, + '-kernel', kernel_path, + '-initrd', initramfs_path, + '-dtb', dtb_path, + '-append', kernel_command_line, + '-no-reboot') + self.vm.set_console() + self.vm.launch() + self._wait_for_linux_boot() + + def test_uboot_sd_boot(self): + self.set_machine('k3-pico-itx') + + firmware_path = self.ASSET_FIRMWARE.fetch() + uboot_path = self.ASSET_UBOOT.fetch() + dtb_path = self.ASSET_UBOOT_DTB.fetch() + sd_path = self.uncompress(self.ASSET_SD_IMAGE) + + self.vm.add_args('-bios', firmware_path, + '-kernel', uboot_path, + '-dtb', dtb_path, + '-drive', (f'file={sd_path},if=sd,format=raw,' + 'snapshot=on'), + '-no-reboot') + self.vm.set_console() + self.vm.launch() + + panic = 'Kernel panic - not syncing' + wait_for_console_pattern(self, 'U-Boot 2022.10', panic) + wait_for_console_pattern(self, + 'K3-QEMU: Starting kernel from SD', panic) + self._wait_for_linux_boot() + + +if __name__ == '__main__': + QemuSystemTest.main() diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 750c8ea1d2267..48359142c3cff 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -292,6 +292,8 @@ qtests_riscv32 = \ qtests_riscv64 = ['riscv-csr-test'] + \ (unpack_edk2_blobs ? ['bios-tables-test'] : []) + \ + (config_all_devices.has_key('CONFIG_SPACEMIT_K3') ? + ['spacemit-k3-test'] : []) + \ (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and config_all_devices.has_key('CONFIG_RISCV_IOMMU') ? ['iommu-riscv-test'] : []) + \ @@ -407,6 +409,7 @@ qtests = { 'pnv-xive2-test': files('pnv-xive2-common.c', 'pnv-xive2-flush-sync.c', 'pnv-xive2-nvpg_bar.c'), 'qos-test': [chardev, io, qos_test_ss.apply({}).sources()], + 'spacemit-k3-test': [fdt], 'tpm-crb-swtpm-test': [io, tpmemu_files], 'tpm-crb-test': [io, tpmemu_files], 'tpm-tis-swtpm-test': [io, tpmemu_files, 'tpm-tis-util.c'], diff --git a/tests/qtest/spacemit-k3-test.c b/tests/qtest/spacemit-k3-test.c new file mode 100644 index 0000000000000..68bfa9900d625 --- /dev/null +++ b/tests/qtest/spacemit-k3-test.c @@ -0,0 +1,782 @@ +/* + * QTest testcase for the SpacemiT K3 Pico-ITX machine + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include +#include +#include "qemu/bswap.h" +#include "qemu/units.h" +#include "libqtest.h" +#include "qobject/qdict.h" +#include "qobject/qlist.h" + +#define K3_NUM_HARTS 8 +#define K3_HARTS_PER_CLUSTER 4 +#define K3_TIMEBASE_FREQ 24000000 + +#define K3_SRAM_BASE UINT64_C(0xc0800000) +#define K3_SRAM_SIZE UINT64_C(0x80000) +#define K3_DDR_TRAINING_BASE UINT64_C(0xc08d0000) +#define K3_DDR_TRAINING_SIZE UINT64_C(0x100) +#define K3_UART0_BASE UINT64_C(0xd4017000) +#define K3_UART0_SIZE UINT64_C(0x100) +#define K3_SDHCI0_BASE UINT64_C(0xd4280000) +#define K3_APMU_BASE UINT64_C(0xd4282800) +#define K3_CIU_BASE UINT64_C(0xd4282c00) +#define K3_S_IMSIC_BASE UINT64_C(0xe0400000) +#define K3_S_IMSIC_HART_STRIDE UINT64_C(0x40000) +#define K3_S_APLIC_BASE UINT64_C(0xe0804000) +#define K3_M_IMSIC_BASE UINT64_C(0xf1000000) +#define K3_M_IMSIC_HART_STRIDE UINT64_C(0x1000) +#define K3_M_APLIC_BASE UINT64_C(0xf1800000) +#define K3_M_CLINT_BASE UINT64_C(0xf1810000) +#define K3_FIRMWARE_BASE UINT64_C(0x100000000) +#define K3_FIRMWARE_SIZE UINT64_C(0x2000000) +#define K3_DRAM_BASE UINT64_C(0x102000000) +#define K3_DRAM_SIZE UINT64_C(0x80000000) + +#define ACLINT_MTIME UINT64_C(0xbff8) + +#define APLIC_DOMAINCFG 0x0000 +#define APLIC_DOMAINCFG_IE (1U << 8) +#define APLIC_DOMAINCFG_DM (1U << 2) +#define APLIC_DOMAINCFG_RDONLY (1U << 31) +#define APLIC_SOURCECFG_BASE 0x0004 +#define APLIC_SOURCECFG_D (1U << 10) +#define APLIC_SOURCECFG_LEVEL_HIGH 0x6 +#define APLIC_MMSICFGADDR 0x1bc0 +#define APLIC_MMSICFGADDRH 0x1bc4 +#define APLIC_SMSICFGADDR 0x1bc8 +#define APLIC_SMSICFGADDRH 0x1bcc +#define APLIC_CLRIP_BASE 0x1d00 +#define APLIC_SETIENUM 0x1edc +#define APLIC_TARGET_BASE 0x3004 +#define APLIC_LHXS_SHIFT 20 +#define APLIC_LHXW_SHIFT 12 + +#define K3_UART0_IRQ 42 +#define K3_SDHCI0_IRQ 99 +#define UART_RBR 0x00 +#define UART_IER 0x04 +#define UART_LSR 0x14 +#define UART_IER_RDI 0x01 +#define UART_LSR_DR 0x01 + +#define K3_FDT_MAX_SIZE (64 * KiB) +#define K3_APMU_SDH0_CTRL 0x54 +#define K3_APMU_SDH0_RESET 0x119 +#define K3_CIU_BOOT_FLAG 0x110 +#define K3_CIU_BOOT_FROM_SD 0xb10 + +#define SDHCI_BLKSIZE 0x04 +#define SDHCI_BLKCNT 0x06 +#define SDHCI_ARGUMENT 0x08 +#define SDHCI_TRNMOD 0x0c +#define SDHCI_CMDREG 0x0e +#define SDHCI_RSPREG0 0x10 +#define SDHCI_PRNSTS 0x24 +#define SDHCI_HOSTCTL 0x28 +#define SDHCI_PWRCON 0x29 +#define SDHCI_CLKCON 0x2c +#define SDHCI_SWRST 0x2f +#define SDHCI_NORINTSTS 0x30 +#define SDHCI_NORINTSTSEN 0x34 +#define SDHCI_NORINTSIGEN 0x38 +#define SDHCI_ERRINTSIGEN 0x3a +#define SDHCI_HOSTCTL2 0x3e +#define SDHCI_CAPAB 0x40 +#define SDHCI_ADMAERR 0x54 +#define SDHCI_ADMASYSADDR 0x58 +#define SDHCI_HCVER 0xfe +#define K3_SDHCI_MMC_CTRL 0x114 +#define K3_SDHCI_TX_CFG 0x11c + +#define K3_SDHCI_CAPAB UINT64_C(0x112834b4) +#define K3_SDHCI_MMC_CTRL_MASK 0x1700 +#define K3_SDHCI_TX_CFG_MASK 0xc0000000 + +#define SDHCI_CARD_PRESENT (1U << 16) +#define SDHCI_CTRL_ADMA2_64 0x18 +#define SDHCI_POWER_330 0x0f +#define SDHCI_CLOCK_INT_EN 0x01 +#define SDHCI_CLOCK_INT_STABLE 0x02 +#define SDHCI_CLOCK_SDCLK_EN 0x04 +#define SDHCI_RESET_ALL 0x01 + +#define SDHCI_TRNS_DMA 0x0001 +#define SDHCI_TRNS_BLK_CNT_EN 0x0002 +#define SDHCI_TRNS_READ 0x0010 + +#define SDHCI_CMD_RESP_NONE 0x00 +#define SDHCI_CMD_RESP_LONG 0x01 +#define SDHCI_CMD_RESP_SHORT 0x02 +#define SDHCI_CMD_RESP_SHORT_BUSY 0x03 +#define SDHCI_CMD_CRC 0x08 +#define SDHCI_CMD_INDEX 0x10 +#define SDHCI_CMD_DATA 0x20 + +#define SDHCI_NIS_CMDCMP 0x0001 +#define SDHCI_NIS_TRSCMP 0x0002 +#define SDHCI_NIS_ERR 0x8000 + +#define SDHCI_ADMA_VALID 0x01 +#define SDHCI_ADMA_END 0x02 +#define SDHCI_ADMA_TRAN 0x20 + +#define K3_SD_SECTOR_SIZE 512 +#define K3_SD_IMAGE_SIZE (1 * MiB) +#define K3_ADMA_DESC_ADDR UINT64_C(0x102010000) +#define K3_ADMA_BUFFER_ADDR UINT64_C(0x102020000) + +#define CSR_MIP 0x344 +#define CSR_MENVCFG 0x30a +#define CSR_MISELECT 0x350 +#define CSR_MIREG 0x351 +#define CSR_MTOPEI 0x35c +#define CSR_SISELECT 0x150 +#define CSR_SIREG 0x151 +#define CSR_STOPEI 0x15c +#define CSR_STIMECMP 0x14d +#define MIP_STIP (UINT64_C(1) << 5) +#define MIP_SEIP (UINT64_C(1) << 9) +#define MIP_MEIP (UINT64_C(1) << 11) +#define MENVCFG_STCE (UINT64_C(1) << 63) +#define ISELECT_IMSIC_EIDELIVERY 0x70 +#define ISELECT_IMSIC_EIP0 0x80 +#define ISELECT_IMSIC_EIE0 0xc0 + +static QTestState *k3_qtest_init(void) +{ + return qtest_init("-M k3-pico-itx -bios none -display none " + "-nodefaults"); +} + +static char *k3_create_fdt(const void *compatible, size_t compatible_len) +{ + g_autofree uint8_t *fdt = g_malloc0(K3_FDT_MAX_SIZE); + const fdt32_t memory_reg[] = { + cpu_to_fdt32((uint32_t)(K3_DRAM_BASE >> 32)), + cpu_to_fdt32((uint32_t)K3_DRAM_BASE), + cpu_to_fdt32((uint32_t)(K3_DRAM_SIZE >> 32)), + cpu_to_fdt32((uint32_t)K3_DRAM_SIZE), + }; + g_autoptr(GError) error = NULL; + char *path = g_strdup("spacemit-k3-fdt-XXXXXX"); + int cpus; + int memory; + int fd; + + g_assert_cmpint(fdt_create_empty_tree(fdt, K3_FDT_MAX_SIZE), ==, 0); + g_assert_cmpint(fdt_setprop(fdt, 0, "compatible", compatible, + compatible_len), ==, 0); + + cpus = fdt_add_subnode(fdt, 0, "cpus"); + g_assert_cmpint(cpus, >=, 0); + g_assert_cmpint(fdt_setprop_u32(fdt, cpus, "timebase-frequency", + K3_TIMEBASE_FREQ), ==, 0); + for (unsigned int i = 0; i < K3_NUM_HARTS; i++) { + g_autofree char *name = g_strdup_printf("cpu@%x", i); + + g_assert_cmpint(fdt_add_subnode(fdt, cpus, name), >=, 0); + } + + memory = fdt_add_subnode(fdt, 0, "memory@102000000"); + g_assert_cmpint(memory, >=, 0); + g_assert_cmpint(fdt_setprop(fdt, memory, "reg", memory_reg, + sizeof(memory_reg)), ==, 0); + g_assert_cmpint(fdt_pack(fdt), ==, 0); + + fd = g_mkstemp(path); + g_assert_cmpint(fd, >=, 0); + close(fd); + g_assert_true(g_file_set_contents(path, (const char *)fdt, + fdt_totalsize(fdt), &error)); + g_assert_no_error(error); + + return path; +} + +static void test_fdt_compatible_list(void) +{ + static const char compatible[] = + "spacemit,k3\0spacemit,k3-pico-itx"; + g_autofree char *dtb_path = k3_create_fdt(compatible, + sizeof(compatible)); + QTestState *qts = qtest_initf( + "-M k3-pico-itx -bios none -display none -nodefaults -dtb %s", + dtb_path); + + qtest_quit(qts); + g_assert_cmpint(g_unlink(dtb_path), ==, 0); +} + +static void test_fdt_missing_compatible(void) +{ + static const char compatible[] = "spacemit,k3"; + g_autofree char *dtb_path = k3_create_fdt(compatible, + sizeof(compatible)); + g_autofree char *stderr_buf = NULL; + g_autoptr(GError) error = NULL; + /* Make an unexpected successful DTB validation terminate promptly. */ + const char *argv[] = { + qtest_qemu_binary(NULL), + "-M", "k3-pico-itx", + "-bios", "none", + "-display", "none", + "-nodefaults", + "-dtb", dtb_path, + "-device", "spacemit-k3-test-invalid", + NULL, + }; + int exit_status; + bool spawned; + + spawned = g_spawn_sync(NULL, (char **)argv, NULL, + G_SPAWN_STDOUT_TO_DEV_NULL, NULL, NULL, NULL, + &stderr_buf, &exit_status, &error); + g_assert_true(spawned); + g_assert_no_error(error); + g_assert_false(g_spawn_check_exit_status(exit_status, NULL)); + g_assert_nonnull(strstr(stderr_buf, + "DTB requires root compatible " + "'spacemit,k3-pico-itx'")); + g_assert_cmpint(g_unlink(dtb_path), ==, 0); +} + +static void test_cpu_requires_vector(void) +{ + g_autofree char *stderr_buf = NULL; + g_autoptr(GError) error = NULL; + const char *argv[] = { + qtest_qemu_binary(NULL), + "-M", "k3-pico-itx", + "-cpu", "spacemit-x100,v=false", + "-bios", "none", + "-display", "none", + "-nodefaults", + "-device", "spacemit-k3-test-invalid", + NULL, + }; + int exit_status; + bool spawned; + + spawned = g_spawn_sync(NULL, (char **)argv, NULL, + G_SPAWN_STDOUT_TO_DEV_NULL, NULL, NULL, NULL, + &stderr_buf, &exit_status, &error); + g_assert_true(spawned); + g_assert_no_error(error); + g_assert_false(g_spawn_check_exit_status(exit_status, NULL)); + g_assert_nonnull(strstr(stderr_buf, + "K3 X100 hart 0 requires the V extension")); +} + +static uint64_t k3_qom_get_uint(QTestState *qts, const char *path, + const char *property) +{ + QDict *response = qtest_qmp(qts, + "{ 'execute': 'qom-get', 'arguments': " + "{ 'path': %s, 'property': %s } }", path, property); + uint64_t value; + + g_assert(qdict_haskey(response, "return")); + value = qdict_get_int(response, "return"); + qobject_unref(response); + + return value; +} + +static uint64_t k3_csr_get(QTestState *qts, unsigned int hartid, int csr) +{ + uint64_t value = 0; + + g_assert_cmpuint(qtest_csr_call(qts, "get_csr", hartid, csr, &value), + ==, 0); + return value; +} + +static void k3_csr_set(QTestState *qts, unsigned int hartid, int csr, + uint64_t value) +{ + g_assert_cmpuint(qtest_csr_call(qts, "set_csr", hartid, csr, &value), + ==, 0); +} + +static uint64_t k3_imsic_indirect_read(QTestState *qts, unsigned int hartid, + bool mmode, uint64_t selector) +{ + k3_csr_set(qts, hartid, mmode ? CSR_MISELECT : CSR_SISELECT, selector); + return k3_csr_get(qts, hartid, mmode ? CSR_MIREG : CSR_SIREG); +} + +static void k3_imsic_indirect_write(QTestState *qts, unsigned int hartid, + bool mmode, uint64_t selector, + uint64_t value) +{ + k3_csr_set(qts, hartid, mmode ? CSR_MISELECT : CSR_SISELECT, selector); + k3_csr_set(qts, hartid, mmode ? CSR_MIREG : CSR_SIREG, value); +} + +static void k3_imsic_enable(QTestState *qts, unsigned int hartid, bool mmode, + unsigned int eiid) +{ + g_assert_cmpuint(eiid, <, 64); + k3_imsic_indirect_write(qts, hartid, mmode, + ISELECT_IMSIC_EIDELIVERY, 1); + k3_imsic_indirect_write(qts, hartid, mmode, ISELECT_IMSIC_EIE0, + UINT64_C(1) << eiid); +} + +static void k3_imsic_claim(QTestState *qts, unsigned int hartid, bool mmode, + unsigned int eiid) +{ + int topei_csr = mmode ? CSR_MTOPEI : CSR_STOPEI; + uint64_t topei = k3_csr_get(qts, hartid, topei_csr); + + g_assert_cmpuint(topei >> 16, ==, eiid); + k3_csr_set(qts, hartid, topei_csr, topei); +} + +static void k3_route_s_aplic_irq(QTestState *qts, unsigned int irq, + unsigned int eiid) +{ + const uint64_t sourcecfg = APLIC_SOURCECFG_BASE + (irq - 1) * 4; + const uint64_t target = APLIC_TARGET_BASE + (irq - 1) * 4; + + k3_imsic_enable(qts, 0, false, eiid); + + qtest_writel(qts, K3_M_APLIC_BASE + APLIC_MMSICFGADDR, + K3_M_IMSIC_BASE >> 12); + qtest_writel(qts, K3_M_APLIC_BASE + APLIC_MMSICFGADDRH, + 3U << APLIC_LHXW_SHIFT); + qtest_writel(qts, K3_M_APLIC_BASE + APLIC_SMSICFGADDR, + K3_S_IMSIC_BASE >> 12); + qtest_writel(qts, K3_M_APLIC_BASE + APLIC_SMSICFGADDRH, + 6U << APLIC_LHXS_SHIFT); + + qtest_writel(qts, K3_M_APLIC_BASE + sourcecfg, APLIC_SOURCECFG_D); + qtest_writel(qts, K3_S_APLIC_BASE + sourcecfg, + APLIC_SOURCECFG_LEVEL_HIGH); + qtest_writel(qts, K3_S_APLIC_BASE + target, eiid); + qtest_writel(qts, K3_S_APLIC_BASE + APLIC_SETIENUM, irq); + qtest_writel(qts, K3_S_APLIC_BASE + APLIC_DOMAINCFG, + APLIC_DOMAINCFG_IE); +} + +static void test_topology(void) +{ + QTestState *qts = k3_qtest_init(); + QDict *response; + QList *cpus; + QListEntry *entry; + bool seen[K3_NUM_HARTS] = { false }; + + response = qtest_qmp(qts, "{ 'execute': 'query-cpus-fast' }"); + g_assert(qdict_haskey(response, "return")); + cpus = qdict_get_qlist(response, "return"); + g_assert_cmpuint(qlist_size(cpus), ==, K3_NUM_HARTS); + + QLIST_FOREACH_ENTRY(cpus, entry) { + QDict *cpu = qobject_to(QDict, entry->value); + unsigned int index = qdict_get_int(cpu, "cpu-index"); + unsigned int cluster = index / K3_HARTS_PER_CLUSTER; + unsigned int hart = index % K3_HARTS_PER_CLUSTER; + g_autofree char *expected_path = g_strdup_printf( + "/machine/soc/cluster%u/cpus/harts[%u]", cluster, hart); + + g_assert_cmpuint(index, <, K3_NUM_HARTS); + g_assert_false(seen[index]); + seen[index] = true; + g_assert_cmpstr(qdict_get_str(cpu, "qom-type"), ==, + "spacemit-x100-riscv-cpu"); + g_assert_cmpstr(qdict_get_str(cpu, "qom-path"), ==, expected_path); + } + for (unsigned int i = 0; i < K3_NUM_HARTS; i++) { + g_assert_true(seen[i]); + } + + g_assert_cmpuint(k3_qom_get_uint(qts, "/machine/soc/cluster0", + "cluster-id"), ==, 0); + g_assert_cmpuint(k3_qom_get_uint(qts, "/machine/soc/cluster1", + "cluster-id"), ==, 1); + g_assert_cmpuint(k3_qom_get_uint( + qts, "/machine/soc/cluster0/cpus/harts[0]", "vlen"), ==, 256); + + qobject_unref(response); + qtest_quit(qts); +} + +static void test_address_map(void) +{ + static const uint64_t first_pattern = UINT64_C(0x0123456789abcdef); + static const uint64_t last_pattern = UINT64_C(0xfedcba9876543210); + QTestState *qts = k3_qtest_init(); + + g_assert_cmphex(qtest_readl(qts, K3_SRAM_BASE), ==, 0x00000297); + qtest_writeq(qts, K3_SRAM_BASE, first_pattern); + qtest_writeq(qts, K3_SRAM_BASE + K3_SRAM_SIZE - 8, last_pattern); + g_assert_cmphex(qtest_readq(qts, K3_SRAM_BASE), ==, first_pattern); + g_assert_cmphex(qtest_readq(qts, K3_SRAM_BASE + K3_SRAM_SIZE - 8), ==, + last_pattern); + g_assert_cmphex(qtest_readl(qts, K3_SRAM_BASE + K3_SRAM_SIZE), ==, 0); + + qtest_writel(qts, K3_DDR_TRAINING_BASE, 0xa5a5a5a5); + qtest_writel(qts, K3_DDR_TRAINING_BASE + K3_DDR_TRAINING_SIZE - 4, + 0x5a5a5a5a); + g_assert_cmphex(qtest_readl(qts, K3_DDR_TRAINING_BASE), ==, 0xa5a5a5a5); + g_assert_cmphex(qtest_readl(qts, K3_DDR_TRAINING_BASE + + K3_DDR_TRAINING_SIZE - 4), ==, 0x5a5a5a5a); + + qtest_system_reset(qts); + g_assert_cmphex(qtest_readl(qts, K3_SRAM_BASE), ==, 0x00000297); + g_assert_cmphex(qtest_readl(qts, K3_DDR_TRAINING_BASE), ==, 0); + g_assert_cmphex(qtest_readl(qts, K3_DDR_TRAINING_BASE + + K3_DDR_TRAINING_SIZE - 4), ==, 0); + + qtest_writeq(qts, K3_FIRMWARE_BASE, first_pattern); + qtest_writeq(qts, K3_FIRMWARE_BASE + K3_FIRMWARE_SIZE - 8, + last_pattern); + g_assert_cmphex(qtest_readq(qts, K3_FIRMWARE_BASE), ==, first_pattern); + g_assert_cmphex(qtest_readq(qts, + K3_FIRMWARE_BASE + K3_FIRMWARE_SIZE - 8), ==, + last_pattern); + + qtest_writeq(qts, K3_DRAM_BASE, last_pattern); + qtest_writeq(qts, K3_DRAM_BASE + K3_DRAM_SIZE - 8, first_pattern); + g_assert_cmphex(qtest_readq(qts, K3_DRAM_BASE), ==, last_pattern); + g_assert_cmphex(qtest_readq(qts, K3_DRAM_BASE + K3_DRAM_SIZE - 8), ==, + first_pattern); + + g_assert_cmphex(qtest_readl(qts, K3_M_APLIC_BASE + APLIC_DOMAINCFG), ==, + APLIC_DOMAINCFG_RDONLY | APLIC_DOMAINCFG_DM); + g_assert_cmphex(qtest_readl(qts, K3_S_APLIC_BASE + APLIC_DOMAINCFG), ==, + APLIC_DOMAINCFG_RDONLY | APLIC_DOMAINCFG_DM); + g_assert_cmphex(qtest_readl(qts, K3_M_IMSIC_BASE), ==, 0); + g_assert_cmphex(qtest_readl(qts, K3_S_IMSIC_BASE), ==, 0); + g_assert_cmphex(qtest_readl(qts, K3_UART0_BASE + UART_LSR), ==, 0x60); + g_assert_cmphex(qtest_readl(qts, K3_UART0_BASE + K3_UART0_SIZE - 4), ==, + 0); + + qtest_quit(qts); +} + +static void test_timer_and_sstc(void) +{ + QTestState *qts = k3_qtest_init(); + uint64_t before = qtest_readq(qts, K3_M_CLINT_BASE + ACLINT_MTIME); + uint64_t after; + + qtest_clock_step(qts, 1000000); + after = qtest_readq(qts, K3_M_CLINT_BASE + ACLINT_MTIME); + g_assert_cmpuint(after - before, ==, K3_TIMEBASE_FREQ / 1000); + + k3_csr_set(qts, 0, CSR_STIMECMP, UINT64_MAX); + k3_csr_set(qts, 0, CSR_MENVCFG, MENVCFG_STCE); + g_assert_cmphex(k3_csr_get(qts, 0, CSR_MIP) & MIP_STIP, ==, 0); + k3_csr_set(qts, 0, CSR_STIMECMP, after + K3_TIMEBASE_FREQ / 1000); + qtest_clock_step(qts, 999999); + g_assert_cmphex(k3_csr_get(qts, 0, CSR_MIP) & MIP_STIP, ==, 0); + qtest_clock_step(qts, 1); + g_assert_cmphex(k3_csr_get(qts, 0, CSR_MIP) & MIP_STIP, ==, MIP_STIP); + k3_csr_set(qts, 0, CSR_STIMECMP, UINT64_MAX); + g_assert_cmphex(k3_csr_get(qts, 0, CSR_MIP) & MIP_STIP, ==, 0); + + qtest_quit(qts); +} + +static void test_imsic_hart_routing(void) +{ + const unsigned int eiid = 10; + QTestState *qts = k3_qtest_init(); + + k3_imsic_enable(qts, 0, false, eiid); + k3_imsic_enable(qts, 7, false, eiid); + + /* The unimplemented VS page in hart 0's stride must not signal S-mode. */ + qtest_writel(qts, K3_S_IMSIC_BASE + 0x1000, eiid); + g_assert_cmphex(k3_imsic_indirect_read(qts, 0, false, + ISELECT_IMSIC_EIP0), ==, 0); + + qtest_writel(qts, K3_S_IMSIC_BASE, eiid); + g_assert_cmphex(k3_imsic_indirect_read(qts, 0, false, + ISELECT_IMSIC_EIP0), ==, + UINT64_C(1) << eiid); + g_assert_cmphex(k3_csr_get(qts, 0, CSR_MIP) & MIP_SEIP, ==, MIP_SEIP); + g_assert_cmphex(k3_imsic_indirect_read(qts, 7, false, + ISELECT_IMSIC_EIP0), ==, 0); + k3_imsic_claim(qts, 0, false, eiid); + + qtest_writel(qts, K3_S_IMSIC_BASE + 7 * K3_S_IMSIC_HART_STRIDE, eiid); + g_assert_cmphex(k3_imsic_indirect_read(qts, 7, false, + ISELECT_IMSIC_EIP0), ==, + UINT64_C(1) << eiid); + g_assert_cmphex(k3_imsic_indirect_read(qts, 0, false, + ISELECT_IMSIC_EIP0), ==, 0); + k3_imsic_claim(qts, 7, false, eiid); + + k3_imsic_enable(qts, 7, true, eiid); + qtest_writel(qts, K3_M_IMSIC_BASE + 7 * K3_M_IMSIC_HART_STRIDE, eiid); + g_assert_cmphex(k3_imsic_indirect_read(qts, 7, true, + ISELECT_IMSIC_EIP0), ==, + UINT64_C(1) << eiid); + g_assert_cmphex(k3_csr_get(qts, 7, CSR_MIP) & MIP_MEIP, ==, MIP_MEIP); + k3_imsic_claim(qts, 7, true, eiid); + + qtest_quit(qts); +} + +static bool k3_wait_for_uart_rx(QTestState *qts) +{ + for (unsigned int i = 0; i < 10000; i++) { + if (qtest_readl(qts, K3_UART0_BASE + UART_LSR) & UART_LSR_DR) { + return true; + } + g_usleep(100); + } + + return false; +} + +static void test_uart_aplic_imsic(void) +{ + const unsigned int eiid = K3_UART0_IRQ; + const uint64_t input_word = APLIC_CLRIP_BASE + + (K3_UART0_IRQ / 32) * 4; + const uint32_t input_mask = 1U << (K3_UART0_IRQ % 32); + int sock_fd; + QTestState *qts = qtest_init_with_serial( + "-M k3-pico-itx -bios none -display none -nodefaults", &sock_fd); + + k3_route_s_aplic_irq(qts, K3_UART0_IRQ, eiid); + + qtest_writel(qts, K3_UART0_BASE + UART_IER, UART_IER_RDI); + g_assert_cmpint(send(sock_fd, "K", 1, 0), ==, 1); + g_assert_true(k3_wait_for_uart_rx(qts)); + + g_assert_cmphex(qtest_readl(qts, K3_S_APLIC_BASE + input_word) & + input_mask, ==, input_mask); + g_assert_cmphex(k3_imsic_indirect_read(qts, 0, false, + ISELECT_IMSIC_EIP0), ==, + UINT64_C(1) << eiid); + g_assert_cmphex(k3_csr_get(qts, 0, CSR_MIP) & MIP_SEIP, ==, MIP_SEIP); + + g_assert_cmphex(qtest_readl(qts, K3_UART0_BASE + UART_RBR), ==, 'K'); + g_assert_cmphex(qtest_readl(qts, K3_S_APLIC_BASE + input_word) & + input_mask, ==, 0); + k3_imsic_claim(qts, 0, false, eiid); + g_assert_cmphex(k3_csr_get(qts, 0, CSR_MIP) & MIP_SEIP, ==, 0); + + close(sock_fd); + qtest_quit(qts); +} + +static uint32_t k3_sdhci_cmd(QTestState *qts, unsigned int index, + uint32_t argument, uint16_t flags) +{ + uint32_t status; + + qtest_writel(qts, K3_SDHCI0_BASE + SDHCI_ARGUMENT, argument); + qtest_writew(qts, K3_SDHCI0_BASE + SDHCI_CMDREG, + index << 8 | flags); + + status = qtest_readl(qts, K3_SDHCI0_BASE + SDHCI_NORINTSTS); + g_assert_cmphex(status & (SDHCI_NIS_ERR | 0xffff0000U), ==, 0); + g_assert_cmphex(status & SDHCI_NIS_CMDCMP, ==, SDHCI_NIS_CMDCMP); + qtest_writel(qts, K3_SDHCI0_BASE + SDHCI_NORINTSTS, status); + + return qtest_readl(qts, K3_SDHCI0_BASE + SDHCI_RSPREG0); +} + +static void k3_sdhci_init_card(QTestState *qts) +{ + uint16_t rca; + + k3_sdhci_cmd(qts, 0, 0, SDHCI_CMD_RESP_NONE); + k3_sdhci_cmd(qts, 55, 0, + SDHCI_CMD_RESP_SHORT | SDHCI_CMD_CRC | SDHCI_CMD_INDEX); + k3_sdhci_cmd(qts, 41, 0x00ff8000, SDHCI_CMD_RESP_SHORT); + k3_sdhci_cmd(qts, 2, 0, SDHCI_CMD_RESP_LONG | SDHCI_CMD_CRC); + rca = k3_sdhci_cmd(qts, 3, 0, + SDHCI_CMD_RESP_SHORT | SDHCI_CMD_CRC | + SDHCI_CMD_INDEX) >> 16; + g_assert_cmpuint(rca, !=, 0); + k3_sdhci_cmd(qts, 7, (uint32_t)rca << 16, + SDHCI_CMD_RESP_SHORT_BUSY | SDHCI_CMD_CRC | + SDHCI_CMD_INDEX); +} + +static char *k3_create_sd_image(uint8_t *sector) +{ + g_autofree uint8_t *image = g_malloc0(K3_SD_IMAGE_SIZE); + g_autoptr(GError) error = NULL; + char *path = NULL; + int fd; + + for (unsigned int i = 0; i < K3_SD_SECTOR_SIZE; i++) { + sector[i] = (i * 37 + 11) & 0xff; + } + memcpy(image, sector, K3_SD_SECTOR_SIZE); + + fd = g_file_open_tmp("spacemit-k3-sd-XXXXXX.img", &path, &error); + g_assert_no_error(error); + g_assert_cmpint(fd, >=, 0); + close(fd); + g_assert_true(g_file_set_contents(path, (const char *)image, + K3_SD_IMAGE_SIZE, &error)); + g_assert_no_error(error); + + return path; +} + +static void test_sd_boot_registers(void) +{ + QTestState *qts = k3_qtest_init(); + + g_assert_cmphex(qtest_readl(qts, K3_APMU_BASE + K3_APMU_SDH0_CTRL), ==, + K3_APMU_SDH0_RESET); + qtest_writel(qts, K3_APMU_BASE + K3_APMU_SDH0_CTRL, UINT32_MAX); + g_assert_cmphex(qtest_readl(qts, K3_APMU_BASE + K3_APMU_SDH0_CTRL), ==, + 0x7fb); + + g_assert_cmphex(qtest_readl(qts, K3_CIU_BASE + K3_CIU_BOOT_FLAG), ==, + K3_CIU_BOOT_FROM_SD); + qtest_writel(qts, K3_CIU_BASE + K3_CIU_BOOT_FLAG, 0); + g_assert_cmphex(qtest_readl(qts, K3_CIU_BASE + K3_CIU_BOOT_FLAG), ==, + K3_CIU_BOOT_FROM_SD); + + g_assert_cmphex(qtest_readq(qts, K3_SDHCI0_BASE + SDHCI_CAPAB), ==, + K3_SDHCI_CAPAB); + g_assert_cmphex(qtest_readw(qts, K3_SDHCI0_BASE + SDHCI_HCVER) & 0xff, + ==, 2); + + qtest_writew(qts, K3_SDHCI0_BASE + SDHCI_HOSTCTL2, 1); + g_assert_cmphex(qtest_readw(qts, K3_SDHCI0_BASE + SDHCI_HOSTCTL2), ==, 1); + qtest_writel(qts, K3_SDHCI0_BASE + K3_SDHCI_MMC_CTRL, UINT32_MAX); + qtest_writel(qts, K3_SDHCI0_BASE + K3_SDHCI_TX_CFG, UINT32_MAX); + g_assert_cmphex(qtest_readl(qts, K3_SDHCI0_BASE + K3_SDHCI_MMC_CTRL), ==, + K3_SDHCI_MMC_CTRL_MASK); + g_assert_cmphex(qtest_readl(qts, K3_SDHCI0_BASE + K3_SDHCI_TX_CFG), ==, + K3_SDHCI_TX_CFG_MASK); + + qtest_system_reset(qts); + g_assert_cmphex(qtest_readl(qts, K3_APMU_BASE + K3_APMU_SDH0_CTRL), ==, + K3_APMU_SDH0_RESET); + g_assert_cmphex(qtest_readw(qts, K3_SDHCI0_BASE + SDHCI_HOSTCTL2), ==, 0); + g_assert_cmphex(qtest_readl(qts, K3_SDHCI0_BASE + K3_SDHCI_MMC_CTRL), ==, + 0); + g_assert_cmphex(qtest_readl(qts, K3_SDHCI0_BASE + K3_SDHCI_TX_CFG), ==, + 0); + + qtest_quit(qts); +} + +static void test_sd_adma_aplic_imsic(void) +{ + const unsigned int eiid = 11; + const uint64_t input_word = APLIC_CLRIP_BASE + + (K3_SDHCI0_IRQ / 32) * 4; + const uint32_t input_mask = 1U << (K3_SDHCI0_IRQ % 32); + const uint16_t trnmod = SDHCI_TRNS_DMA | SDHCI_TRNS_BLK_CNT_EN | + SDHCI_TRNS_READ; + uint8_t expected[K3_SD_SECTOR_SIZE]; + uint8_t actual[K3_SD_SECTOR_SIZE]; + uint8_t poison[K3_SD_SECTOR_SIZE]; + uint8_t descriptor[12] = {}; + g_autofree char *sd_path = k3_create_sd_image(expected); + QTestState *qts = qtest_initf( + "-M k3-pico-itx -bios none -display none -nodefaults " + "-drive file=%s,if=sd,format=raw,snapshot=on", sd_path); + uint32_t status; + + g_assert_cmphex(qtest_readl(qts, K3_SDHCI0_BASE + SDHCI_PRNSTS) & + SDHCI_CARD_PRESENT, ==, SDHCI_CARD_PRESENT); + qtest_writeb(qts, K3_SDHCI0_BASE + SDHCI_SWRST, SDHCI_RESET_ALL); + qtest_writeb(qts, K3_SDHCI0_BASE + SDHCI_PWRCON, SDHCI_POWER_330); + qtest_writew(qts, K3_SDHCI0_BASE + SDHCI_CLKCON, + SDHCI_CLOCK_INT_EN | SDHCI_CLOCK_SDCLK_EN); + g_assert_cmphex(qtest_readw(qts, K3_SDHCI0_BASE + SDHCI_CLKCON), ==, + SDHCI_CLOCK_INT_EN | SDHCI_CLOCK_INT_STABLE | + SDHCI_CLOCK_SDCLK_EN); + + qtest_writel(qts, K3_SDHCI0_BASE + SDHCI_NORINTSTSEN, 0xffff0003U); + k3_sdhci_init_card(qts); + k3_route_s_aplic_irq(qts, K3_SDHCI0_IRQ, eiid); + qtest_writew(qts, K3_SDHCI0_BASE + SDHCI_NORINTSIGEN, + SDHCI_NIS_TRSCMP | SDHCI_NIS_ERR); + qtest_writew(qts, K3_SDHCI0_BASE + SDHCI_ERRINTSIGEN, UINT16_MAX); + + descriptor[0] = SDHCI_ADMA_VALID | SDHCI_ADMA_END | SDHCI_ADMA_TRAN; + stw_le_p(descriptor + 2, K3_SD_SECTOR_SIZE); + stq_le_p(descriptor + 4, K3_ADMA_BUFFER_ADDR); + qtest_memwrite(qts, K3_ADMA_DESC_ADDR, descriptor, sizeof(descriptor)); + memset(poison, 0xa5, sizeof(poison)); + qtest_memwrite(qts, K3_ADMA_BUFFER_ADDR, poison, sizeof(poison)); + + qtest_writel(qts, K3_SDHCI0_BASE + SDHCI_ADMASYSADDR, + (uint32_t)K3_ADMA_DESC_ADDR); + qtest_writel(qts, K3_SDHCI0_BASE + SDHCI_ADMASYSADDR + 4, + (uint32_t)(K3_ADMA_DESC_ADDR >> 32)); + qtest_writeb(qts, K3_SDHCI0_BASE + SDHCI_HOSTCTL, + SDHCI_CTRL_ADMA2_64); + qtest_writew(qts, K3_SDHCI0_BASE + SDHCI_BLKSIZE, K3_SD_SECTOR_SIZE); + qtest_writew(qts, K3_SDHCI0_BASE + SDHCI_BLKCNT, 1); + qtest_writel(qts, K3_SDHCI0_BASE + SDHCI_ARGUMENT, 0); + qtest_writew(qts, K3_SDHCI0_BASE + SDHCI_TRNMOD, trnmod); + g_assert_cmphex(qtest_readw(qts, K3_SDHCI0_BASE + SDHCI_TRNMOD), ==, + trnmod); + qtest_writew(qts, K3_SDHCI0_BASE + SDHCI_CMDREG, + 17 << 8 | SDHCI_CMD_RESP_SHORT | SDHCI_CMD_CRC | + SDHCI_CMD_INDEX | SDHCI_CMD_DATA); + + status = qtest_readl(qts, K3_SDHCI0_BASE + SDHCI_NORINTSTS); + g_assert_cmphex(status & (SDHCI_NIS_CMDCMP | SDHCI_NIS_TRSCMP), ==, + SDHCI_NIS_CMDCMP | SDHCI_NIS_TRSCMP); + g_assert_cmphex(status & (SDHCI_NIS_ERR | 0xffff0000U), ==, 0); + g_assert_cmphex(qtest_readb(qts, K3_SDHCI0_BASE + SDHCI_ADMAERR), ==, 0); + qtest_memread(qts, K3_ADMA_BUFFER_ADDR, actual, sizeof(actual)); + g_assert_cmpmem(actual, sizeof(actual), expected, sizeof(expected)); + + g_assert_cmphex(qtest_readl(qts, K3_S_APLIC_BASE + input_word) & + input_mask, ==, input_mask); + g_assert_cmphex(k3_imsic_indirect_read(qts, 0, false, + ISELECT_IMSIC_EIP0), ==, + UINT64_C(1) << eiid); + g_assert_cmphex(k3_csr_get(qts, 0, CSR_MIP) & MIP_SEIP, ==, MIP_SEIP); + + qtest_writel(qts, K3_SDHCI0_BASE + SDHCI_NORINTSTS, status); + g_assert_cmphex(qtest_readl(qts, K3_S_APLIC_BASE + input_word) & + input_mask, ==, 0); + k3_imsic_claim(qts, 0, false, eiid); + g_assert_cmphex(k3_csr_get(qts, 0, CSR_MIP) & MIP_SEIP, ==, 0); + + qtest_quit(qts); + g_assert_cmpint(g_unlink(sd_path), ==, 0); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + if (qtest_has_machine("k3-pico-itx")) { + qtest_add_func("spacemit-k3/topology", test_topology); + qtest_add_func("spacemit-k3/address-map", test_address_map); + qtest_add_func("spacemit-k3/fdt-compatible-list", + test_fdt_compatible_list); + qtest_add_func("spacemit-k3/fdt-missing-compatible", + test_fdt_missing_compatible); + qtest_add_func("spacemit-k3/cpu-requires-vector", + test_cpu_requires_vector); + qtest_add_func("spacemit-k3/timer-sstc", test_timer_and_sstc); + qtest_add_func("spacemit-k3/imsic-routing", test_imsic_hart_routing); + qtest_add_func("spacemit-k3/uart-aplic-imsic", + test_uart_aplic_imsic); + qtest_add_func("spacemit-k3/sd-boot-registers", + test_sd_boot_registers); + qtest_add_func("spacemit-k3/sd-adma-aplic-imsic", + test_sd_adma_aplic_imsic); + } + + return g_test_run(); +}