diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3b37af376bcb..40fdec572028 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -217,25 +217,6 @@ jobs: - shell: freebsd {0} run: cd /home/qemu && ./build/qemu-system-x86_64 -nographic -plugin ./build/contrib/plugins/libstoptrigger,icount=1000000 -plugin ./build/tests/tcg/plugins/libinsn -plugin ./build/contrib/plugins/libcpp -d plugin - build-netbsd: - needs: checkapply - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - - uses: vmactions/netbsd-vm@v1 - with: - copyback: false - release: "10.1" - - run: rsync -av ./ netbsd:/home/qemu/ - - shell: netbsd {0} - run: set -ex && pkg_add pkgin && pkgin -y install git gmake python314 pkgconf pixman bison flex glib ninja-build && ln -s /usr/pkg/bin/python3.14 /usr/bin/python3 - - shell: netbsd {0} - run: cd /home/qemu && ./configure ${{ env.QEMU_WERROR }} && ninja -C build - # for some reason meson can't find c++ compiler, so no cpp plugin - # split-wx=on: https://gitlab.com/qemu-project/qemu/-/issues/2685 - - shell: netbsd {0} - run: cd /home/qemu && ./build/qemu-system-x86_64 -accel tcg,split-wx=on -nographic -plugin ./build/contrib/plugins/libstoptrigger,icount=1000000 -plugin ./build/tests/tcg/plugins/libinsn -d plugin - build-openbsd: needs: checkapply runs-on: ubuntu-24.04 @@ -355,21 +336,6 @@ jobs: docker.io/pboqemu/qemu-ci:debian-${{matrix.arch}}-cross bash -cx './configure ${{ env.QEMU_WERROR }} --disable-tcg $QEMU_CONFIGURE_OPTS && ninja -C build install' - build-xen-only: - needs: checkapply - strategy: - fail-fast: false - matrix: - arch: [arm64, amd64] - runs-on: ubuntu-24.04 - steps: - - run: sudo rm -rf /opt/ /usr/local/.ghcup /usr/local/lib/android - - uses: actions/checkout@v6 - - run: > - podman run --pull newer --init --rm -it -v $(pwd):$(pwd) -w $(pwd) - docker.io/pboqemu/qemu-ci:debian-${{matrix.arch}}-cross - bash -cx './configure ${{ env.QEMU_WERROR }} --disable-tcg --disable-kvm $QEMU_CONFIGURE_OPTS && ninja -C build install' - build-minimal: needs: checkapply runs-on: ubuntu-24.04 diff --git a/MAINTAINERS b/MAINTAINERS index 8329b3da2e65..24beeae6f35f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1028,6 +1028,21 @@ F: roms/vbootrom F: docs/system/arm/nuvoton.rst F: tests/functional/arm/test_quanta_gsj.py +Rockchip RK3588 boards +M: Chao Liu +L: qemu-arm@nongnu.org +S: Maintained +F: configs/devices/aarch64-softmmu/rk3588*.mak +F: configs/devices/aarch64-softmmu/rock-5b-plus.mak +F: docs/system/arm/rk3588.rst +F: hw/arm/rk3588* +F: hw/arm/rock5b_plus.c +F: hw/*/rk3588* +F: include/hw/*/rk3588* +F: tests/functional/aarch64/test_rock5b_plus.py +F: tests/qtest/rk3588* +F: tests/qtest/rock5b-plus-test.c + Raspberry Pi M: Peter Maydell R: Philippe Mathieu-Daudé diff --git a/README.md b/README.md index 46aaef35ff54..7a770265d6ad 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,12 @@ for new machine requests or bug reports. | 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 | [`ax650x-pyramid`](docs/system/arm/ax650x-pyramid.rst) | ✅ | ❌ | PM | +| ARM | [`ax650x-pyramid`](docs/system/arm/ax650x-pyramid.rst) | ✅ | - | PM | | ARM | [`phytium-pi`](docs/system/arm/phytium-pi.rst) | ✅ | ✅ | PM | | ARM | [`rk3588-evb`](docs/system/arm/rk3588.rst) | ✅ | ✅ | PM | | ARM | [`rk3588s-roc-pc`](docs/system/arm/rk3588.rst) | ✅ | ✅ | PM | -| ARM | [`s32k566-cvb-r52`](docs/system/arm/s32k5.rst) | ✅ | ❌ | PM | +| ARM | [`rock-5b-plus`](docs/system/arm/rk3588.rst) | ✅ | ✅ | PM | +| ARM | [`s32k566-cvb-r52`](docs/system/arm/s32k5.rst) | ✅ | - | PM | Source legend: diff --git a/configs/devices/aarch64-softmmu/rk3588-evb.mak b/configs/devices/aarch64-softmmu/rk3588-evb.mak new file mode 100644 index 000000000000..908a051a044a --- /dev/null +++ b/configs/devices/aarch64-softmmu/rk3588-evb.mak @@ -0,0 +1 @@ +CONFIG_RK3588_EVB=y diff --git a/configs/devices/aarch64-softmmu/rk3588s-roc-pc.mak b/configs/devices/aarch64-softmmu/rk3588s-roc-pc.mak new file mode 100644 index 000000000000..5429a1feb181 --- /dev/null +++ b/configs/devices/aarch64-softmmu/rk3588s-roc-pc.mak @@ -0,0 +1 @@ +CONFIG_RK3588S_ROC_PC=y diff --git a/configs/devices/aarch64-softmmu/rock-5b-plus.mak b/configs/devices/aarch64-softmmu/rock-5b-plus.mak new file mode 100644 index 000000000000..eb89219d443d --- /dev/null +++ b/configs/devices/aarch64-softmmu/rock-5b-plus.mak @@ -0,0 +1 @@ +CONFIG_ROCK_5B_PLUS=y diff --git a/docs/system/arm/rk3588.rst b/docs/system/arm/rk3588.rst index bf67e6171ed8..cdda1f390b49 100644 --- a/docs/system/arm/rk3588.rst +++ b/docs/system/arm/rk3588.rst @@ -10,7 +10,9 @@ boot path and a downstream firmware path that runs the Rockchip TPL/SPL image, validates the U-Boot FIT loadables, hands off to U-Boot proper, and lets U-Boot boot Linux or ZVM test images from modeled MMC media. -Two board machines are available: +Three board machines are available. Each board has its own machine source +file and build-time Kconfig entry; the common ``rk3588`` type is abstract and +contains only shared SoC construction and boot orchestration: ``rk3588-evb`` Rockchip RK3588 EVB-style board. Firmware boot reads the Rockchip RKNS @@ -25,6 +27,12 @@ Two board machines are available: ``mmc dev 1`` boot script. This machine maps the fixed ZVM guest/shared RAM windows by default, corresponding to the real board's larger DRAM layout. +``rock-5b-plus`` + Radxa ROCK 5B+ with LPDDR5, the two board PCIe hosts, and the firmware + compatibility profile needed by Radxa's RK3588 SDK image. Firmware boot + reads the raw Radxa image from ``if=sd,index=0`` and exposes it through the + non-removable eMMC controller. + Supported devices ----------------- @@ -39,8 +47,10 @@ RK3588 kernel and ZVM smoke tests: * UART2 through QEMU's ``serial_mm`` 16550-compatible model, plus a vendor register cover for the DesignWare APB UART window. * Rockchip GPIO banks. -* Rockchip RAM-backed syscon blocks for GRF, IOC and firewall-style register - banks. +* Dedicated RK3588 PMU0/PMU1/SYS GRF banks, plus reusable Rockchip RAM-backed + syscon blocks for IOC and firewall-style register banks. +* Dedicated RK3588 DDR controller/PHY windows and the BL31 DDR runtime + descriptor used by the vendor firmware. * Rockchip secure timer stub. * RK3588 CRU and SCMI-over-SMC clock/reset support. * SDHCI eMMC and Synopsys DesignWare MMC for the SD-card controller. @@ -75,13 +85,15 @@ than as RK3588-only devices: Address mapping, compatible strings, MSI routing, and interrupt numbers stay in the RK3588 board code. -The RK3588 CRU model, BootROM handoff, ATF DDR runtime descriptor, firmware -MMIO catch-all, firmware USB2 host windows, PSCI/GIC handoff state for the -firmware path, SPL-to-U-Boot handoff shim, and closed-BL31 runtime fallback -patch logic are RK3588 machine code because their addresses and firmware -assumptions are SoC- and image-specific. The USB2 windows are not a complete -reusable Rockchip USB host model; they only provide the reset-visible EHCI/OHCI -state U-Boot needs while scanning boot devices. +All MMIO register behavior is implemented outside the machine sources. The +RK3588 CRU, GRF, DDR, ATF DDR runtime descriptor, firmware compatibility MMIO, +SCMI, secure OTP, and firmware USB2 host windows live in independently built +devices under the corresponding ``hw/*`` subsystem directories. The common +machine source only maps and wires those devices and implements SoC boot-flow +orchestration such as BootROM loading, PSCI/GIC handoff, and the +SPL/BL31-to-U-Boot compatibility path. The USB2 device is intentionally a +firmware-oriented register model; it supplies the reset-visible EHCI/OHCI state +U-Boot needs while scanning boot devices. Direct Linux Boot ----------------- @@ -129,6 +141,21 @@ The ROC PC machine supports the same direct Linux boot path: -serial mon:stdio \ -display none +ROCK 5B+ supports the same path with a Radxa SDK kernel. For example: + +.. code-block:: bash + + $ build/qemu-system-aarch64 \ + -accel tcg \ + -machine rock-5b-plus \ + -smp 8 \ + -m 1G \ + -kernel Image \ + -initrd initramfs.cpio.gz \ + -append "console=ttyS2,1500000n8 earlycon=uart8250,mmio32,0xfeb50000,1500000n8 keep_bootcon rdinit=/init" \ + -serial mon:stdio \ + -display none + Firmware U-Boot Boot -------------------- @@ -165,6 +192,30 @@ bootflow probing can continue without taking a synchronous abort on unmapped firmware MMIO. It is intended for downstream firmware bring-up coverage, not as a complete replacement for a real RK3588 PMU/secure firmware environment. +Radxa ROCK 5B+ Firmware Image +----------------------------- + +The ``rock-5b-plus`` firmware profile also accepts a complete raw image made by +the Radxa RK3588 SDK. No extraction or repackaging is required: + +.. code-block:: bash + + $ build/qemu-system-aarch64 \ + -accel tcg \ + -machine rock-5b-plus \ + -smp 1 \ + -m 2G \ + -drive if=sd,index=0,file=rock-5b-plus_bookworm_kde_b2.output.img,format=raw \ + -snapshot \ + -serial mon:stdio \ + -display none + +This path runs the image's DDR/TPL and SPL code, verifies all FIT loadables, +uses the FIT metadata to select the U-Boot load address dynamically, enters +U-Boot proper, and continues through its ``extlinux`` boot flow. With the +Radxa Bookworm image, U-Boot loads the SDK kernel, initramfs, and +``rk3588-rock-5b-plus.dtb`` before printing ``Starting kernel ...``. + U-Boot to Linux Boot -------------------- diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 957c0298e060..301618c32531 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -206,17 +206,19 @@ config SBSA_REF select IDE_BUS select IDE_DEV -config RK3588_EVB +config RK3588 bool - default y - depends on TCG && AARCH64 select ARM_GIC + select ARM_GICV5 if TCG select DEVICE_TREE - select DWMAC4 select DW_MMC select DW_APB_UART_VENDOR select PCI_EXPRESS_DESIGNWARE + select RK3588_ATF_DDR select RK3588_CRU + select RK3588_DDR + select RK3588_FIRMWARE_MMIO + select RK3588_GRF select ROCKCHIP_DWCMSHC select ROCKCHIP_PCIE select ROCKCHIP_STIMER @@ -225,7 +227,29 @@ config RK3588_EVB select ROCKCHIP_GPIO select SDHCI select SERIAL_MM - select UNIMP + select RK3588_USB2_HOST + +config RK3588_EVB + bool + default y + depends on TCG && AARCH64 + select RK3588 + select DWMAC4 + +config RK3588S_ROC_PC + bool + default y + depends on TCG && AARCH64 + select RK3588 + select DWMAC4 + +config ROCK_5B_PLUS + bool + default y + depends on TCG && AARCH64 + select RK3588 + select RK3588_SECURE_OTP + select ROCKCHIP_CRYPTO_V2 config S32K5 bool diff --git a/hw/arm/meson.build b/hw/arm/meson.build index 416e664d0193..fa71a62804f7 100644 --- a/hw/arm/meson.build +++ b/hw/arm/meson.build @@ -15,7 +15,10 @@ arm_common_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx.c', 'npcm7xx_b arm_common_ss.add(when: 'CONFIG_NPCM8XX', if_true: files('npcm8xx.c', 'npcm8xx_boards.c')) arm_common_ss.add(when: 'CONFIG_REALVIEW', if_true: files('realview.c')) arm_common_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa-ref.c')) -arm_common_ss.add(when: 'CONFIG_RK3588_EVB', if_true: files('rk3588.c')) +arm_common_ss.add(when: 'CONFIG_RK3588', if_true: files('rk3588.c')) +arm_common_ss.add(when: 'CONFIG_RK3588_EVB', if_true: files('rk3588_evb.c')) +arm_common_ss.add(when: 'CONFIG_RK3588S_ROC_PC', if_true: files('rk3588s_roc_pc.c')) +arm_common_ss.add(when: 'CONFIG_ROCK_5B_PLUS', if_true: files('rock5b_plus.c')) arm_common_ss.add(when: 'CONFIG_PHYTIUMPI', if_true: files('phytium-pi.c')) arm_common_ss.add(when: 'CONFIG_STELLARIS', if_true: files('stellaris.c')) arm_common_ss.add(when: 'CONFIG_STM32VLDISCOVERY', if_true: files('stm32vldiscovery.c')) diff --git a/hw/arm/rk3588-internal.h b/hw/arm/rk3588-internal.h new file mode 100644 index 000000000000..ab0988c98aed --- /dev/null +++ b/hw/arm/rk3588-internal.h @@ -0,0 +1,57 @@ +/* + * Rockchip RK3588 machine extension interface + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_ARM_RK3588_INTERNAL_H +#define HW_ARM_RK3588_INTERNAL_H + +#include "hw/arm/machines-qom.h" +#include "qom/object.h" + +#define TYPE_RK3588_MACHINE MACHINE_TYPE_NAME("rk3588") + +#define RK3588_BROM_BOOTSOURCE_EMMC 2 +#define RK3588_BROM_BOOTSOURCE_SD 5 + +#define RK3588_DRAM_TYPE_LPDDR4X 8 +#define RK3588_DRAM_TYPE_LPDDR5 9 + +typedef struct RK3588FirmwareProfile { + bool unfused_secure_otp; + bool crypto_v2_sha256; + bool dynamic_fit_handoff; + bool atags_core; + uint64_t fit_offset; + uint32_t fit_alignment; +} RK3588FirmwareProfile; + +typedef struct RK3588BoardConfig { + const char *machine_name; + const char *desc; + const char *ram_id; + const char *fdt_model; + const char * const *fdt_compatible; + size_t fdt_compatible_count; + unsigned int firmware_sd_unit; + uint32_t brom_bootsource; + uint32_t dram_type; + uint32_t gmac_mask; + unsigned int pcie3x4_num_lanes; + unsigned int pcie3x2_num_lanes; + bool pcie3x4_link_down; + bool pcie3x2_link_down; + bool swap_gmac_aliases; + bool default_zvm_ram; + const RK3588FirmwareProfile *firmware_profile; +} RK3588BoardConfig; + +void rk3588_machine_instance_configure(Object *obj, + const RK3588BoardConfig *board); +void rk3588_machine_class_configure(ObjectClass *oc, + const RK3588BoardConfig *board); + +#endif diff --git a/hw/arm/rk3588.c b/hw/arm/rk3588.c index 199738f74b5e..215f8fbe0ae4 100644 --- a/hw/arm/rk3588.c +++ b/hw/arm/rk3588.c @@ -16,6 +16,7 @@ #include "system/address-spaces.h" #include "system/block-backend-io.h" #include "exec/hwaddr.h" +#include "exec/translation-block.h" #include "system/device_tree.h" #include "system/kvm.h" #include "system/memory.h" @@ -26,16 +27,24 @@ #include "hw/arm/boot.h" #include "hw/arm/bsa.h" #include "hw/arm/linux-boot-if.h" +#include "rk3588-internal.h" #include "hw/gpio/rockchip_gpio.h" +#include "hw/misc/rockchip_crypto_v2.h" #include "hw/misc/rockchip_syscon.h" +#include "hw/misc/rk3588_atf_ddr.h" +#include "hw/misc/rk3588_ddr.h" +#include "hw/misc/rk3588_firmware_mmio.h" +#include "hw/misc/rk3588_grf.h" #include "hw/misc/rk3588_scmi.h" #include "hw/net/dwmac4.h" +#include "hw/nvram/rk3588_secure_otp.h" #include "hw/pci-host/designware.h" #include "hw/sd/dw_mmc.h" #include "hw/sd/rockchip_dwcmshc.h" #include "hw/sd/sd.h" #include "hw/sd/sdhci.h" #include "hw/timer/rockchip_stimer.h" +#include "hw/usb/rk3588_usb2_host.h" #include "net/net.h" #include "system/block-backend.h" #include "hw/arm/machines-qom.h" @@ -46,7 +55,6 @@ #include "hw/core/sysbus.h" #include "hw/intc/arm_gicv3_common.h" #include "hw/intc/arm_gicv3_its_common.h" -#include "hw/misc/unimp.h" #include "hw/misc/rk3588_cru.h" #include "hw/pci-host/rockchip_pcie.h" #include "qobject/qlist.h" @@ -55,9 +63,8 @@ #include "target/arm/gtimer.h" #include "target/arm/internals.h" -#define TYPE_RK3588_MACHINE MACHINE_TYPE_NAME("rk3588") -#define TYPE_RK3588_EVB_MACHINE MACHINE_TYPE_NAME("rk3588-evb") -#define TYPE_RK3588S_ROC_PC_MACHINE MACHINE_TYPE_NAME("rk3588s-roc-pc") +#include + OBJECT_DECLARE_SIMPLE_TYPE(RK3588MachineState, RK3588_MACHINE) #define RK3588_MAX_CPUS 8 @@ -83,35 +90,17 @@ OBJECT_DECLARE_SIMPLE_TYPE(RK3588MachineState, RK3588_MACHINE) #define RK3588_RKNS_LBA 64 #define RK3588_RKNS_HEADER_SIZE 2048 #define RK3588_RKNS_SECTOR_SIZE 512 -#define RK3588_UBOOT_ITB_OFFSET 0x800000 #define RK3588_UBOOT_LOAD_ADDR 0x00800000ULL #define RK3588_UBOOT_ENTRY_BRANCH 0x1400000a #define RK3588_SPL_ATF_CALL_ADDR 0x00002a98ULL -#define RK3588_BROM_BOOTSOURCE_EMMC 2 -#define RK3588_BROM_BOOTSOURCE_SD 5 -#define RK3588_FIRMWARE_MMIO_SIZE 0x08000000 -#define RK3588_DDR_SYS_REG_VERSION 3 -#define RK3588_LPDDR4X 8 -#define RK3588_DDRPHY_CTRL_OFFSET 0x154 -#define RK3588_DDRPHY_STATUS_OFFSET 0x184 -#define RK3588_DDRPHY_STATUS_ACTIVE 0x3 -#define RK3588_DDR_CHANNEL_STATUS_OFFSET 0x14 -#define RK3588_DDR_CHANNEL_STATUS_READY 0x1 -#define RK3588_DDR_CHANNEL_CMD_OFFSET 0x80 -#define RK3588_DDR_CHANNEL_CMD_START 0x80000000U -#define RK3588_DDR_CHANNEL_BUSY_OFFSET 0x90 -#define RK3588_DDR_CHANNEL_BUSY 0x1 -#define RK3588_DDR_CHANNEL_GATE_STATUS_OFFSET 0x514 -#define RK3588_DDR_CHANNEL_GATE_BUSY 0x1 -#define RK3588_DDR_CHANNEL_GATE_CMD_OFFSET 0x510 -#define RK3588_DDR_CHANNEL_GATE_ENABLE 0x20 -#define RK3588_DDR_CHANNEL_GATE_CTRL_OFFSET 0xc80 -#define RK3588_DDR_CHANNEL_PHY_STATUS_OFFSET 0xb90 -#define RK3588_DDR_CHANNEL_PHY_BUSY 0x10000 -#define RK3588_DDR_PHY_GATE_CTRL_OFFSET 0xb0 -#define RK3588_DDR_PHY_GATE_ENABLE 0x20 -#define RK3588_PMU0_GRF_WARM_BOOT_MAGIC_OFFSET 0x84 -#define RK3588_PMU0_GRF_WARM_BOOT_MAGIC 0x13579bdf +#define RK3588_SECURE_OTP_BASE 0xfe3a0000ULL +#define RK3588_DDR_LEGACY_BASE 0xf7000000ULL +#define RK3588_DDR_LEGACY_PHY_BASE 0xfd800000ULL +#define RK3588_DDR_LEGACY_PHY_AUX_BASE 0xfe000000ULL +#define RK3588_DDR_GLOBAL_BASE 0xfd000000ULL +#define RK3588_DDR_CHANNEL_BASE 0xfd100000ULL +#define RK3588_DDRPHY_BASE 0xfd8d8000ULL +#define RK3588_DDR_PHY_GATE_BASE 0xfe0c0000ULL #define RK3588_PMUSRAM_SKIP_ADDR 0xff101764ULL #define RK3588_BL31_BASE 0x00060000ULL #define RK3588_BL31_LIMIT 0x00090000ULL @@ -137,54 +126,11 @@ OBJECT_DECLARE_SIMPLE_TYPE(RK3588MachineState, RK3588_MACHINE) #define RK3588_AARCH64_NOP 0xd503201f #define RK3588_AARCH64_RET 0xd65f03c0 #define RK3588_FIRMWARE_PATCH_INTERVAL_NS SCALE_US -#define RK3588_ATF_DDR_RUNTIME_ADDR 0x0008d000ULL -#define RK3588_ATF_DDR_RUNTIME_SIZE 0x8000 -#define RK3588_ATF_DDR_GLOBAL_PTR_ADDR 0x0008d0a8ULL -#define RK3588_ATF_TIMER_PTR_ADDR 0x0008d0b0ULL -#define RK3588_ATF_TIMER_TABLE_ADDR 0x0008d0b8ULL -#define RK3588_ATF_TIMER_COUNTER_ADDR 0x00062054ULL -#define RK3588_ATF_DDR_DESCRIPTOR_ADDR 0x0008fd20ULL -#define RK3588_ATF_DDR_CHANNEL_TABLE_ADDR 0x0008fe00ULL -#define RK3588_ATF_DDR_GLOBAL_PTR_OFFSET \ - (RK3588_ATF_DDR_GLOBAL_PTR_ADDR - RK3588_ATF_DDR_RUNTIME_ADDR) -#define RK3588_ATF_TIMER_PTR_OFFSET \ - (RK3588_ATF_TIMER_PTR_ADDR - RK3588_ATF_DDR_RUNTIME_ADDR) -#define RK3588_ATF_TIMER_TABLE_OFFSET \ - (RK3588_ATF_TIMER_TABLE_ADDR - RK3588_ATF_DDR_RUNTIME_ADDR) -#define RK3588_ATF_DDR_DESCRIPTOR_OFFSET \ - (RK3588_ATF_DDR_DESCRIPTOR_ADDR - RK3588_ATF_DDR_RUNTIME_ADDR) -#define RK3588_ATF_DDR_CHANNEL_TABLE_OFFSET \ - (RK3588_ATF_DDR_CHANNEL_TABLE_ADDR - RK3588_ATF_DDR_RUNTIME_ADDR) -#define RK3588_ATF_DDR_GLOBAL_BASE 0xfd000000ULL -#define RK3588_ATF_DDR_CTRL_WINDOW_SIZE 0x00020000ULL -#define RK3588_ATF_DDR_CHANNEL_BASE 0xfd100000ULL -#define RK3588_ATF_DDR_CHANNEL_STRIDE 0x00020000ULL -#define RK3588_ATF_DDR_CHANNELS 8 -#define RK3588_ATF_DDR_CTRL_BUSY_MASK ((1U << 31) | (1U << 3)) +#define RK3588_FIT_METADATA_MAX_SIZE MiB #define RK3588_USB2_HOST0_EHCI_BASE 0xfc800000ULL #define RK3588_USB2_HOST0_OHCI_BASE 0xfc840000ULL #define RK3588_USB2_HOST1_EHCI_BASE 0xfc880000ULL #define RK3588_USB2_HOST1_OHCI_BASE 0xfc8c0000ULL -#define RK3588_USB2_HOST_WINDOW_SIZE 0x00040000ULL -#define RK3588_USB2_EHCI_CAPBASE 0x01000020U -#define RK3588_USB2_EHCI_HCSPARAMS 0x00000011U -#define RK3588_USB2_EHCI_USBCMD 0x20 -#define RK3588_USB2_EHCI_USBSTS 0x24 -#define RK3588_USB2_EHCI_PORTSC0 0x64 -#define RK3588_USB2_EHCI_CMD_RESET (1U << 1) -#define RK3588_USB2_EHCI_CMD_RUN (1U << 0) -#define RK3588_USB2_EHCI_STS_HALT (1U << 12) -#define RK3588_USB2_EHCI_PORT_POWER (1U << 12) -#define RK3588_USB2_OHCI_REVISION 0x00000010U -#define RK3588_USB2_OHCI_CMDSTATUS 0x08 -#define RK3588_USB2_OHCI_INTRSTATUS 0x0c -#define RK3588_USB2_OHCI_ROOTHUB_A 0x48 -#define RK3588_USB2_OHCI_PORTSTATUS0 0x54 -#define RK3588_USB2_OHCI_HCR (1U << 0) -#define RK3588_USB2_OHCI_RH_A_NPS (1U << 9) -#define RK3588_USB2_OHCI_RH_A_NOCP (1U << 12) -#define RK3588_USB2_OHCI_RH_A_NDP1 1U -#define RK3588_USB2_OHCI_RH_PS_PPS (1U << 8) #define FDT_GIC_SPI 0 #define FDT_GIC_PPI 1 @@ -214,57 +160,22 @@ typedef struct RK3588BootROM { uint8_t *spl; size_t spl_size; hwaddr tpl_entry; + hwaddr atf_load; + hwaddr uboot_load; + hwaddr uboot_entry; + uint32_t atf_size; + uint32_t uboot_size; + uint32_t uboot_entry_word; bool spl_loaded; + bool fit_handoff_valid; } RK3588BootROM; -typedef struct RK3588BoardConfig { - const char *machine_name; - const char *desc; - const char *ram_id; - const char *fdt_model; - const char * const *fdt_compatible; - size_t fdt_compatible_count; - unsigned int firmware_sd_unit; - uint32_t brom_bootsource; - bool default_zvm_ram; -} RK3588BoardConfig; - -static const char * const rk3588_evb_compatible[] = { - "qemu,rk3588-evb", - "rockchip,rk3588-evb1-v10", - "rockchip,rk3588", -}; - -static const char * const rk3588s_roc_pc_compatible[] = { - "rockchip,rk3588s-firefly-roc-pc", - "firefly,rk3588s-roc-pc", - "rockchip,rk3588s", - "rockchip,rk3588", -}; - -static const RK3588BoardConfig rk3588_evb_board = { - .machine_name = "rk3588-evb", - .desc = "Rockchip RK3588 EVB (minimal)", - .ram_id = "rk3588-evb.ram", - .fdt_model = "QEMU Rockchip RK3588 EVB", - .fdt_compatible = rk3588_evb_compatible, - .fdt_compatible_count = ARRAY_SIZE(rk3588_evb_compatible), - .firmware_sd_unit = 0, - .brom_bootsource = RK3588_BROM_BOOTSOURCE_EMMC, - .default_zvm_ram = false, -}; - -static const RK3588BoardConfig rk3588s_roc_pc_board = { - .machine_name = "rk3588s-roc-pc", - .desc = "Firefly ROC-RK3588S-PC", - .ram_id = "rk3588s-roc-pc.ram", - .fdt_model = "Firefly ROC-RK3588S-PC", - .fdt_compatible = rk3588s_roc_pc_compatible, - .fdt_compatible_count = ARRAY_SIZE(rk3588s_roc_pc_compatible), - .firmware_sd_unit = 2, - .brom_bootsource = RK3588_BROM_BOOTSOURCE_SD, - .default_zvm_ram = true, -}; +typedef struct RK3588FITImage { + hwaddr load; + hwaddr entry; + uint64_t media_offset; + uint32_t size; +} RK3588FITImage; struct RK3588MachineState { MachineState parent_obj; @@ -277,12 +188,16 @@ struct RK3588MachineState { DeviceState *sdhci; DeviceState *sdmmc; /* dw_mmc - SD card controller */ DeviceState *scmi; /* SCMI clock agent (shmem + SMC responder) */ - DeviceState *pcie; + DeviceState *pcie3x4; + DeviceState *pcie3x2; DeviceState *gmac0; DeviceState *gmac1; DeviceState *gpio[5]; - RockchipSysconState *pmu0grf; - RockchipSysconState *pmu1grf; + DeviceState *crypto; + DeviceState *secure_otp; + DeviceState *atf_ddr; + RK3588DDRState *ddr; + RK3588USB2HostState *usb2_host; MemoryRegion sram; MemoryRegion iram; @@ -291,14 +206,7 @@ struct RK3588MachineState { MemoryRegion zvm_high_ram; MemoryRegion bootrom; MemoryRegion firmware_scratch; - MemoryRegion atf_ddr_runtime; - uint8_t atf_ddr_runtime_regs[RK3588_ATF_DDR_RUNTIME_SIZE]; - MemoryRegion firmware_mmio; - uint8_t *firmware_mmio_regs; QEMUTimer *firmware_patch_timer; - bool firmware_mmio_gate_done; - bool firmware_mmio_last_phy_gate; - bool firmware_mmio_gate_bit5_clear; bool firmware_boot; bool firmware_patch_done; bool firmware_handoff_done; @@ -327,7 +235,7 @@ G_STATIC_ASSERT(ARRAY_SIZE(rk3588_cpu_mpidr) == RK3588_MAX_CPUS); G_STATIC_ASSERT(ARRAY_SIZE(rk3588_cpu_types) == RK3588_MAX_CPUS); static void rk3588_firmware_patch_tick(void *opaque); -static bool rk3588_firmware_usb2_hosts_active(RK3588MachineState *s); +static bool rk3588_dynamic_fit_handoff(RK3588MachineState *s); enum { RK3588_SRAM, @@ -344,6 +252,9 @@ enum { RK3588_PCIE3X4_APB, RK3588_PCIE3X4_CFG, RK3588_PCIE3X4_DBI, + RK3588_PCIE3X2_APB, + RK3588_PCIE3X2_CFG, + RK3588_PCIE3X2_DBI, RK3588_GMAC0, RK3588_GMAC1, RK3588_SDMMC, @@ -363,9 +274,9 @@ enum { RK3588_STIMER, RK3588_FIREWALL_DDR, RK3588_FIREWALL_SYSMEM, + RK3588_CRYPTO, RK3588_IRAM, RK3588_BROM, - RK3588_FIRMWARE_MMIO, RK3588_UART2, }; @@ -394,6 +305,9 @@ static const MemMapEntry rk3588_memmap[] = { [RK3588_PCIE3X4_APB] = { 0xfe150000, 0x00010000 }, [RK3588_PCIE3X4_CFG] = { 0xf0000000, 0x00100000 }, [RK3588_PCIE3X4_DBI] = { 0xa40000000ULL, 0x00400000 }, + [RK3588_PCIE3X2_APB] = { 0xfe160000, 0x00010000 }, + [RK3588_PCIE3X2_CFG] = { 0xf1000000, 0x00100000 }, + [RK3588_PCIE3X2_DBI] = { 0xa40400000ULL, 0x00400000 }, [RK3588_GMAC0] = { 0xfe1b0000, 0x00010000 }, [RK3588_GMAC1] = { 0xfe1c0000, 0x00010000 }, [RK3588_SDMMC] = { 0xfe2c0000, 0x00004000 }, @@ -409,9 +323,9 @@ static const MemMapEntry rk3588_memmap[] = { [RK3588_GPIO4] = { 0xfec50000, 0x00000100 }, /* * General Register File (GRF) syscons - write-only RGMII-delay / PHY- - * interface-select registers consumed by dwmac-rk. Modeled as RAZ/WI - * (create_unimplemented_device); the gmac FDT node references these - * via rockchip,grf / rockchip,php-grf phandles. + * interface-select registers consumed by dwmac-rk. Dedicated RK3588 GRF + * and reusable Rockchip syscon devices own their register storage; the + * gmac FDT node references them through phandles. */ [RK3588_SYS_GRF] = { 0xfd58c000, 0x00001000 }, [RK3588_PHP_GRF] = { 0xfd5b0000, 0x00001000 }, @@ -426,9 +340,10 @@ static const MemMapEntry rk3588_memmap[] = { [RK3588_STIMER] = { 0xfd8c8000, ROCKCHIP_STIMER_SIZE }, [RK3588_FIREWALL_DDR] = { 0xfe030000, 0x00001000 }, [RK3588_FIREWALL_SYSMEM] = { 0xfe038000, 0x00001000 }, + [RK3588_CRYPTO] = { 0xfe370000, + ROCKCHIP_CRYPTO_V2_MMIO_SIZE }, [RK3588_IRAM] = { 0xff000000, RK3588_IRAM_SIZE }, [RK3588_BROM] = { RK3588_BROM_TRAMPOLINE, 0x00001000 }, - [RK3588_FIRMWARE_MMIO] = { 0xf7000000, RK3588_FIRMWARE_MMIO_SIZE }, [RK3588_UART2] = { 0xfeb50000, 0x00000100 }, }; @@ -438,6 +353,11 @@ enum { RK3588_SDHCI_SPI = 205, RK3588_GMAC0_SPI = 227, RK3588_GMAC1_SPI = 234, + RK3588_PCIE3X2_ERR_SPI = 254, + RK3588_PCIE3X2_LEGACY_SPI = 255, + RK3588_PCIE3X2_MSG_SPI = 256, + RK3588_PCIE3X2_PMC_SPI = 257, + RK3588_PCIE3X2_SYS_SPI = 258, RK3588_PCIE3X4_ERR_SPI = 259, RK3588_PCIE3X4_LEGACY_SPI = 260, RK3588_PCIE3X4_MSG_SPI = 261, @@ -447,326 +367,6 @@ enum { RK3588_UART2_SPI = 333, }; -static bool rk3588_firmware_ddr_ctrl_offset(hwaddr offset, - hwaddr *reg_offset) -{ - uint64_t phys = rk3588_memmap[RK3588_FIRMWARE_MMIO].base + offset; - - if (phys >= RK3588_ATF_DDR_GLOBAL_BASE && - phys < RK3588_ATF_DDR_GLOBAL_BASE + - RK3588_ATF_DDR_CTRL_WINDOW_SIZE) { - *reg_offset = phys - RK3588_ATF_DDR_GLOBAL_BASE; - return true; - } - - if (phys >= RK3588_ATF_DDR_CHANNEL_BASE && - phys < RK3588_ATF_DDR_CHANNEL_BASE + - RK3588_ATF_DDR_CHANNELS * RK3588_ATF_DDR_CHANNEL_STRIDE) { - *reg_offset = (phys - RK3588_ATF_DDR_CHANNEL_BASE) % - RK3588_ATF_DDR_CHANNEL_STRIDE; - return true; - } - - return false; -} - -static bool rk3588_firmware_usb2_host_offset(hwaddr offset, - hwaddr *reg_offset, - bool *is_ehci) -{ - uint64_t phys = rk3588_memmap[RK3588_FIRMWARE_MMIO].base + offset; - - if (phys >= RK3588_USB2_HOST0_EHCI_BASE && - phys < RK3588_USB2_HOST0_EHCI_BASE + RK3588_USB2_HOST_WINDOW_SIZE) { - *reg_offset = phys - RK3588_USB2_HOST0_EHCI_BASE; - *is_ehci = true; - return true; - } - - if (phys >= RK3588_USB2_HOST1_EHCI_BASE && - phys < RK3588_USB2_HOST1_EHCI_BASE + RK3588_USB2_HOST_WINDOW_SIZE) { - *reg_offset = phys - RK3588_USB2_HOST1_EHCI_BASE; - *is_ehci = true; - return true; - } - - if (phys >= RK3588_USB2_HOST0_OHCI_BASE && - phys < RK3588_USB2_HOST0_OHCI_BASE + RK3588_USB2_HOST_WINDOW_SIZE) { - *reg_offset = phys - RK3588_USB2_HOST0_OHCI_BASE; - *is_ehci = false; - return true; - } - - if (phys >= RK3588_USB2_HOST1_OHCI_BASE && - phys < RK3588_USB2_HOST1_OHCI_BASE + RK3588_USB2_HOST_WINDOW_SIZE) { - *reg_offset = phys - RK3588_USB2_HOST1_OHCI_BASE; - *is_ehci = false; - return true; - } - - return false; -} - -static uint64_t rk3588_firmware_mmio_read(void *opaque, hwaddr offset, - unsigned size) -{ - RK3588MachineState *s = opaque; - hwaddr reg_offset; - - if (offset + size > rk3588_memmap[RK3588_FIRMWARE_MMIO].size || - size > 8) { - return 0; - } - - if (size == 4 && - rk3588_firmware_ddr_ctrl_offset(offset, ®_offset)) { - uint32_t value = ldl_le_p(&s->firmware_mmio_regs[offset]); - - if (reg_offset == 0) { - return value & ~RK3588_ATF_DDR_CTRL_BUSY_MASK; - } else if (reg_offset == 4) { - return 0; - } else if (reg_offset == RK3588_DDR_CHANNEL_STATUS_OFFSET) { - bool request = value & RK3588_DDR_CHANNEL_STATUS_READY; - - value &= ~0x7; - value |= RK3588_DDR_CHANNEL_STATUS_READY; - if (request) { - value |= (1U << 31); - } - return value; - } - } - - if (size == 4 && - (offset & 0xfff) == RK3588_DDRPHY_STATUS_OFFSET) { - hwaddr ctrl = offset - RK3588_DDRPHY_STATUS_OFFSET + - RK3588_DDRPHY_CTRL_OFFSET; - uint32_t value = ldl_le_p(&s->firmware_mmio_regs[offset]); - - value &= ~RK3588_DDRPHY_STATUS_ACTIVE; - value |= ldl_le_p(&s->firmware_mmio_regs[ctrl]) & - RK3588_DDRPHY_STATUS_ACTIVE; - return value; - } - - if (size == 4 && - (offset & 0xffff) == RK3588_DDR_CHANNEL_STATUS_OFFSET) { - uint32_t value = ldl_le_p(&s->firmware_mmio_regs[offset]); - - value &= ~0x7; - value |= RK3588_DDR_CHANNEL_STATUS_READY; - return value; - } - - if (size == 4 && - (offset & 0xffff) == RK3588_DDR_CHANNEL_CMD_OFFSET) { - uint32_t value = ldl_le_p(&s->firmware_mmio_regs[offset]); - - return value & ~RK3588_DDR_CHANNEL_CMD_START; - } - - if (size == 4 && - (offset & 0xffff) == RK3588_DDR_CHANNEL_BUSY_OFFSET) { - uint32_t value = ldl_le_p(&s->firmware_mmio_regs[offset]); - - return value & ~RK3588_DDR_CHANNEL_BUSY; - } - - if (size == 4 && - (offset & 0xffff) == RK3588_DDR_CHANNEL_GATE_STATUS_OFFSET) { - uint32_t value = ldl_le_p(&s->firmware_mmio_regs[offset]); - - if (s->firmware_mmio_gate_done || s->firmware_mmio_gate_bit5_clear) { - return value | RK3588_DDR_CHANNEL_GATE_BUSY; - } - - return value & ~RK3588_DDR_CHANNEL_GATE_BUSY; - } - - if (size == 4 && - (offset & 0xffff) == RK3588_DDR_CHANNEL_PHY_STATUS_OFFSET) { - uint32_t value = ldl_le_p(&s->firmware_mmio_regs[offset]); - - return value & ~RK3588_DDR_CHANNEL_PHY_BUSY; - } - - switch (size) { - case 1: - return s->firmware_mmio_regs[offset]; - case 2: - return lduw_le_p(&s->firmware_mmio_regs[offset]); - case 4: - return ldl_le_p(&s->firmware_mmio_regs[offset]); - case 8: - return ldq_le_p(&s->firmware_mmio_regs[offset]); - default: - return 0; - } -} - -static void rk3588_firmware_mmio_write(void *opaque, hwaddr offset, - uint64_t value, unsigned size) -{ - RK3588MachineState *s = opaque; - hwaddr reg_offset; - bool is_ehci; - - if (offset + size > rk3588_memmap[RK3588_FIRMWARE_MMIO].size || - size > 8) { - return; - } - - if (size == 4) { - if (rk3588_firmware_usb2_hosts_active(s) && - rk3588_firmware_usb2_host_offset(offset, ®_offset, &is_ehci)) { - if (is_ehci && reg_offset == RK3588_USB2_EHCI_USBCMD) { - uint32_t cmd = value & ~RK3588_USB2_EHCI_CMD_RESET; - uint32_t status = ldl_le_p(&s->firmware_mmio_regs[ - offset - - RK3588_USB2_EHCI_USBCMD + - RK3588_USB2_EHCI_USBSTS]); - - if (cmd & RK3588_USB2_EHCI_CMD_RUN) { - status &= ~RK3588_USB2_EHCI_STS_HALT; - } else { - status |= RK3588_USB2_EHCI_STS_HALT; - } - - stl_le_p(&s->firmware_mmio_regs[offset], cmd); - stl_le_p(&s->firmware_mmio_regs[offset - - RK3588_USB2_EHCI_USBCMD + RK3588_USB2_EHCI_USBSTS], - status); - return; - } - - if (!is_ehci && reg_offset == RK3588_USB2_OHCI_CMDSTATUS) { - stl_le_p(&s->firmware_mmio_regs[offset], - value & ~RK3588_USB2_OHCI_HCR); - return; - } - - if (!is_ehci && reg_offset == RK3588_USB2_OHCI_INTRSTATUS) { - uint32_t status = ldl_le_p(&s->firmware_mmio_regs[offset]); - - stl_le_p(&s->firmware_mmio_regs[offset], status & ~value); - return; - } - } - - if ((offset & 0xffff) == RK3588_DDR_CHANNEL_GATE_CMD_OFFSET) { - s->firmware_mmio_gate_bit5_clear = - !(value & RK3588_DDR_CHANNEL_GATE_ENABLE); - s->firmware_mmio_last_phy_gate = false; - } else if ((offset & 0xffff) == RK3588_DDR_PHY_GATE_CTRL_OFFSET && - !(value & RK3588_DDR_PHY_GATE_ENABLE)) { - s->firmware_mmio_gate_done = true; - s->firmware_mmio_last_phy_gate = true; - } else { - if ((offset & 0xffff) == RK3588_DDR_CHANNEL_GATE_CTRL_OFFSET && - value == 0 && !s->firmware_mmio_last_phy_gate) { - s->firmware_mmio_gate_done = false; - } - s->firmware_mmio_last_phy_gate = false; - } - } - - switch (size) { - case 1: - s->firmware_mmio_regs[offset] = value; - break; - case 2: - stw_le_p(&s->firmware_mmio_regs[offset], value); - break; - case 4: - stl_le_p(&s->firmware_mmio_regs[offset], value); - break; - case 8: - stq_le_p(&s->firmware_mmio_regs[offset], value); - break; - } -} - -static const MemoryRegionOps rk3588_firmware_mmio_ops = { - .read = rk3588_firmware_mmio_read, - .write = rk3588_firmware_mmio_write, - .endianness = DEVICE_LITTLE_ENDIAN, - .impl = { - .min_access_size = 1, - .max_access_size = 8, - }, - .valid = { - .min_access_size = 1, - .max_access_size = 8, - }, -}; - -static void rk3588_seed_atf_ddr_runtime(RK3588MachineState *s); - -static uint64_t rk3588_atf_ddr_runtime_read(void *opaque, hwaddr offset, - unsigned size) -{ - RK3588MachineState *s = opaque; - - if (offset + size > sizeof(s->atf_ddr_runtime_regs) || size > 8) { - return 0; - } - - switch (size) { - case 1: - return s->atf_ddr_runtime_regs[offset]; - case 2: - return lduw_le_p(&s->atf_ddr_runtime_regs[offset]); - case 4: - return ldl_le_p(&s->atf_ddr_runtime_regs[offset]); - case 8: - return ldq_le_p(&s->atf_ddr_runtime_regs[offset]); - default: - return 0; - } -} - -static void rk3588_atf_ddr_runtime_write(void *opaque, hwaddr offset, - uint64_t value, unsigned size) -{ - RK3588MachineState *s = opaque; - - if (offset + size > sizeof(s->atf_ddr_runtime_regs) || size > 8) { - return; - } - - switch (size) { - case 1: - s->atf_ddr_runtime_regs[offset] = value; - break; - case 2: - stw_le_p(&s->atf_ddr_runtime_regs[offset], value); - break; - case 4: - stl_le_p(&s->atf_ddr_runtime_regs[offset], value); - break; - case 8: - stq_le_p(&s->atf_ddr_runtime_regs[offset], value); - break; - } - - rk3588_seed_atf_ddr_runtime(s); -} - -static const MemoryRegionOps rk3588_atf_ddr_runtime_ops = { - .read = rk3588_atf_ddr_runtime_read, - .write = rk3588_atf_ddr_runtime_write, - .endianness = DEVICE_LITTLE_ENDIAN, - .impl = { - .min_access_size = 1, - .max_access_size = 8, - }, - .valid = { - .min_access_size = 1, - .max_access_size = 8, - }, -}; - static const char *rk3588_cpu_type(unsigned int n) { return rk3588_cpu_types[n < RK3588_MAX_CPUS ? n : RK3588_MAX_CPUS - 1]; @@ -1210,24 +810,110 @@ static void rk3588_fdt_add_gmac_nodes(RK3588MachineState *s, void *fdt, uint32_t clk_phandle, uint32_t sys_grf_ph, uint32_t php_grf_ph) { - bool roc_pc = s->board == &rk3588s_roc_pc_board; + const RK3588BoardConfig *board = s->board; - rk3588_fdt_add_gmac_node(fdt, 0, clk_phandle, sys_grf_ph, php_grf_ph); - rk3588_fdt_add_gmac_node(fdt, 1, clk_phandle, sys_grf_ph, php_grf_ph); + if (board->gmac_mask & BIT(0)) { + rk3588_fdt_add_gmac_node(fdt, 0, clk_phandle, + sys_grf_ph, php_grf_ph); + } + if (board->gmac_mask & BIT(1)) { + rk3588_fdt_add_gmac_node(fdt, 1, clk_phandle, + sys_grf_ph, php_grf_ph); + } + + if (board->gmac_mask == (BIT(0) | BIT(1))) { + qemu_fdt_setprop_string(fdt, "/aliases", "ethernet0", + board->swap_gmac_aliases ? + "/ethernet@fe1c0000" : + "/ethernet@fe1b0000"); + qemu_fdt_setprop_string(fdt, "/aliases", "ethernet1", + board->swap_gmac_aliases ? + "/ethernet@fe1b0000" : + "/ethernet@fe1c0000"); + } else if (board->gmac_mask & BIT(0)) { + qemu_fdt_setprop_string(fdt, "/aliases", "ethernet0", + "/ethernet@fe1b0000"); + } else if (board->gmac_mask & BIT(1)) { + qemu_fdt_setprop_string(fdt, "/aliases", "ethernet0", + "/ethernet@fe1c0000"); + } +} + +typedef struct RK3588PCIEFDTConfig { + const char *node; + unsigned int dbi_map; + unsigned int apb_map; + unsigned int cfg_map; + uint32_t sys_spi; + uint32_t pmc_spi; + uint32_t msg_spi; + uint32_t legacy_spi; + uint32_t err_spi; + uint32_t power_up_reset; + uint32_t pipe_reset; + uint32_t domain; + uint32_t bus_start; + uint32_t requester_id; + uint32_t prefetch_hi; + uint32_t prefetch_lo; +} RK3588PCIEFDTConfig; - qemu_fdt_setprop_string(fdt, "/aliases", "ethernet0", - roc_pc ? "/ethernet@fe1c0000" : - "/ethernet@fe1b0000"); - qemu_fdt_setprop_string(fdt, "/aliases", "ethernet1", - roc_pc ? "/ethernet@fe1b0000" : - "/ethernet@fe1c0000"); -} +enum { + RK3588_SRST_PCIE0_POWER_UP = 294, + RK3588_SRST_P_PCIE0 = 299, + RK3588_SRST_PCIE1_POWER_UP = 526, + RK3588_SRST_P_PCIE1 = 541, +}; + +static const RK3588PCIEFDTConfig rk3588_pcie3x4_fdt = { + .node = "/pcie@fe150000", + .dbi_map = RK3588_PCIE3X4_DBI, + .apb_map = RK3588_PCIE3X4_APB, + .cfg_map = RK3588_PCIE3X4_CFG, + .sys_spi = RK3588_PCIE3X4_SYS_SPI, + .pmc_spi = RK3588_PCIE3X4_PMC_SPI, + .msg_spi = RK3588_PCIE3X4_MSG_SPI, + .legacy_spi = RK3588_PCIE3X4_LEGACY_SPI, + .err_spi = RK3588_PCIE3X4_ERR_SPI, + .power_up_reset = RK3588_SRST_PCIE0_POWER_UP, + .pipe_reset = RK3588_SRST_P_PCIE0, + .domain = 0, + .bus_start = 0, + .requester_id = 0, + .prefetch_hi = 0x9, + .prefetch_lo = 0, +}; -static void rk3588_fdt_add_pcie_node(void *fdt, uint32_t cru_phandle, +static const RK3588PCIEFDTConfig rk3588_pcie3x2_fdt = { + .node = "/pcie@fe160000", + .dbi_map = RK3588_PCIE3X2_DBI, + .apb_map = RK3588_PCIE3X2_APB, + .cfg_map = RK3588_PCIE3X2_CFG, + .sys_spi = RK3588_PCIE3X2_SYS_SPI, + .pmc_spi = RK3588_PCIE3X2_PMC_SPI, + .msg_spi = RK3588_PCIE3X2_MSG_SPI, + .legacy_spi = RK3588_PCIE3X2_LEGACY_SPI, + .err_spi = RK3588_PCIE3X2_ERR_SPI, + .power_up_reset = RK3588_SRST_PCIE1_POWER_UP, + .pipe_reset = RK3588_SRST_P_PCIE1, + .domain = 1, + .bus_start = 0x10, + .requester_id = 0x1000, + .prefetch_hi = 0x9, + .prefetch_lo = 0x40000000, +}; + +static void rk3588_fdt_add_pcie_node(void *fdt, + const RK3588PCIEFDTConfig *config, + unsigned int num_lanes, + uint32_t cru_phandle, uint32_t clk_phandle, uint32_t its1_phandle) { - const char *pcie = "/pcie@fe150000"; + const char *pcie = config->node; + uint32_t io_base = rk3588_memmap[config->cfg_map].base + + rk3588_memmap[config->cfg_map].size; + uint32_t mem_base = io_base + 0x00100000; static const char * const compat[] = { "rockchip,rk3588-pcie", "rockchip,rk3568-pcie", @@ -1241,38 +927,30 @@ static void rk3588_fdt_add_pcie_node(void *fdt, uint32_t cru_phandle, static const char * const reset_names[] = { "pwr", "pipe", }; - /* - * SRST_PCIE0_POWER_UP (294) and SRST_P_PCIE0 (299) - - * include/dt-bindings/reset/rockchip,rk3588-cru.h. The - * dw-rockchip driver does reset_control_get_exclusive on both; - * the CRU stub accepts the deassert writes (fire-and-forget). - */ - enum { SRST_PCIE0_POWER_UP = 294, SRST_P_PCIE0 = 299 }; - qemu_fdt_add_subnode(fdt, pcie); qemu_fdt_setprop_string_array(fdt, pcie, "compatible", (char **)&compat, ARRAY_SIZE(compat)); qemu_fdt_setprop_string(fdt, pcie, "device_type", "pci"); qemu_fdt_setprop_sized_cells(fdt, pcie, "reg", - 2, rk3588_memmap[RK3588_PCIE3X4_DBI].base, - 2, rk3588_memmap[RK3588_PCIE3X4_DBI].size, - 2, rk3588_memmap[RK3588_PCIE3X4_APB].base, - 2, rk3588_memmap[RK3588_PCIE3X4_APB].size, - 2, rk3588_memmap[RK3588_PCIE3X4_CFG].base, - 2, rk3588_memmap[RK3588_PCIE3X4_CFG].size); + 2, rk3588_memmap[config->dbi_map].base, + 2, rk3588_memmap[config->dbi_map].size, + 2, rk3588_memmap[config->apb_map].base, + 2, rk3588_memmap[config->apb_map].size, + 2, rk3588_memmap[config->cfg_map].base, + 2, rk3588_memmap[config->cfg_map].size); qemu_fdt_setprop_string_array(fdt, pcie, "reg-names", (char **)®_names, ARRAY_SIZE(reg_names)); qemu_fdt_setprop_cells(fdt, pcie, "interrupts", - FDT_GIC_SPI, RK3588_PCIE3X4_SYS_SPI, + FDT_GIC_SPI, config->sys_spi, FDT_IRQ_TYPE_LEVEL_HIGH, 0, - FDT_GIC_SPI, RK3588_PCIE3X4_PMC_SPI, + FDT_GIC_SPI, config->pmc_spi, FDT_IRQ_TYPE_LEVEL_HIGH, 0, - FDT_GIC_SPI, RK3588_PCIE3X4_MSG_SPI, + FDT_GIC_SPI, config->msg_spi, FDT_IRQ_TYPE_LEVEL_HIGH, 0, - FDT_GIC_SPI, RK3588_PCIE3X4_LEGACY_SPI, + FDT_GIC_SPI, config->legacy_spi, FDT_IRQ_TYPE_LEVEL_HIGH, 0, - FDT_GIC_SPI, RK3588_PCIE3X4_ERR_SPI, + FDT_GIC_SPI, config->err_spi, FDT_IRQ_TYPE_LEVEL_HIGH, 0); static const char * const irq_names[] = { "sys", "pmc", "msg", "legacy", "err", @@ -1293,49 +971,63 @@ static void rk3588_fdt_add_pcie_node(void *fdt, uint32_t cru_phandle, qemu_fdt_setprop_string_array(fdt, pcie, "clock-names", (char **)&clock_names, ARRAY_SIZE(clock_names)); - /* - * The load-bearing property: dw-rockchip does - * devm_reset_control_array_get_exclusive on these. Without a cru - * reset provider the probe fails with -ENOENT (which is the - * baseline symptom we are fixing). - */ + /* Both reset IDs are defined by rockchip,rk3588-cru.h. */ qemu_fdt_setprop_cells(fdt, pcie, "resets", - cru_phandle, SRST_PCIE0_POWER_UP, - cru_phandle, SRST_P_PCIE0); + cru_phandle, config->power_up_reset, + cru_phandle, config->pipe_reset); qemu_fdt_setprop_string_array(fdt, pcie, "reset-names", (char **)&reset_names, ARRAY_SIZE(reset_names)); qemu_fdt_setprop_cell(fdt, pcie, "#address-cells", 3); qemu_fdt_setprop_cell(fdt, pcie, "#size-cells", 2); qemu_fdt_setprop_cell(fdt, pcie, "#interrupt-cells", 1); - qemu_fdt_setprop_cells(fdt, pcie, "bus-range", 0, 0x0f); - qemu_fdt_setprop_cell(fdt, pcie, "num-lanes", 4); + qemu_fdt_setprop_cells(fdt, pcie, "bus-range", config->bus_start, + config->bus_start + 0x0f); + qemu_fdt_setprop_cell(fdt, pcie, "num-lanes", num_lanes); qemu_fdt_setprop_cell(fdt, pcie, "max-link-speed", 3); /* - * RK3588 routes pcie3x4 Requester IDs 0x0000..0x0fff to ITS1. - * PCIe device MSI writes then target the ITS1 GITS_TRANSLATER doorbell - * directly; the host bridge line IRQs above remain separate. + * Each host owns a disjoint 0x1000 Requester ID range routed to ITS1. + * PCIe MSI writes then target the ITS1 GITS_TRANSLATER doorbell directly; + * the host bridge line IRQs above remain separate. */ qemu_fdt_setprop_cells(fdt, pcie, "msi-map", - 0x0000, its1_phandle, 0x0000, 0x1000); + config->requester_id, its1_phandle, + config->requester_id, 0x1000); /* - * Bus ranges - IO/MEM/prefetch. The 1 MiB CFG window at - * 0xf0000000 is the reg "config" entry above; the designware - * model serves it via the outbound CFG viewport once the guest - * programs the iATU. + * Bus ranges - IO/MEM/prefetch. The 1 MiB CFG window is the reg + * "config" entry above; the designware model serves it via the outbound + * CFG viewport once the guest programs the iATU. */ qemu_fdt_setprop_cells(fdt, pcie, "ranges", - 0x01000000, 0x0, 0xf0100000, - 0x0, 0xf0100000, 0x0, 0x00100000, - 0x02000000, 0x0, 0xf0200000, - 0x0, 0xf0200000, 0x0, 0x00e00000, - 0x03000000, 0x9, 0x00000000, - 0x9, 0x00000000, 0x0, 0x40000000); + 0x01000000, 0x0, io_base, + 0x0, io_base, 0x0, 0x00100000, + 0x02000000, 0x0, mem_base, + 0x0, mem_base, 0x0, 0x00e00000, + 0x03000000, config->prefetch_hi, + config->prefetch_lo, + config->prefetch_hi, + config->prefetch_lo, + 0x0, 0x40000000); /* Refer to xin24m so the cru-of-declare path doesn't grab us. */ - qemu_fdt_setprop_cell(fdt, pcie, "linux,pci-domain", 0); + qemu_fdt_setprop_cell(fdt, pcie, "linux,pci-domain", config->domain); qemu_fdt_setprop_string(fdt, pcie, "status", "okay"); } +static void rk3588_fdt_add_pcie_nodes(RK3588MachineState *s, void *fdt, + uint32_t cru_phandle, + uint32_t clk_phandle, + uint32_t its1_phandle) +{ + rk3588_fdt_add_pcie_node(fdt, &rk3588_pcie3x4_fdt, + s->board->pcie3x4_num_lanes, + cru_phandle, clk_phandle, its1_phandle); + + if (s->board->pcie3x2_num_lanes) { + rk3588_fdt_add_pcie_node(fdt, &rk3588_pcie3x2_fdt, + s->board->pcie3x2_num_lanes, + cru_phandle, clk_phandle, its1_phandle); + } +} static void *rk3588_get_dtb(const struct arm_boot_info *binfo, int *fdt_size) { @@ -1371,7 +1063,8 @@ static void *rk3588_get_dtb(const struct arm_boot_info *binfo, int *fdt_size) rk3588_fdt_add_storage_nodes(fdt, clk_phandle, scmi_clk_phandle); rk3588_fdt_add_gpio_nodes(fdt, clk_phandle); rk3588_fdt_add_gmac_nodes(s, fdt, clk_phandle, sys_grf_ph, php_grf_ph); - rk3588_fdt_add_pcie_node(fdt, cru_phandle, clk_phandle, its1_phandle); + rk3588_fdt_add_pcie_nodes(s, fdt, cru_phandle, clk_phandle, + its1_phandle); return fdt; } @@ -1432,8 +1125,8 @@ static void rk3588_enable_psci_conduit(RK3588MachineState *s) } } -static RockchipSysconState *rk3588_create_syscon(RK3588MachineState *s, - const char *name, int memidx) +static void rk3588_create_syscon(RK3588MachineState *s, const char *name, + int memidx) { DeviceState *dev = qdev_new(TYPE_ROCKCHIP_SYSCON); SysBusDevice *sbd; @@ -1443,192 +1136,42 @@ static RockchipSysconState *rk3588_create_syscon(RK3588MachineState *s, sbd = SYS_BUS_DEVICE(dev); sysbus_realize(sbd, &error_fatal); sysbus_mmio_map(sbd, 0, rk3588_memmap[memidx].base); - - return ROCKCHIP_SYSCON(dev); -} - -static uint32_t rk3588_ddr_sys_reg2(uint64_t group_bytes) -{ - uint64_t group_mb = MAX(group_bytes / MiB, 256); - unsigned int row = 13; - uint32_t row_delta, reg; - - while ((256ULL << (row - 13)) < group_mb && row < 17) { - row++; - } - - row_delta = row - 13; - reg = (RK3588_LPDDR4X & 7) << 13; - reg |= 1 << 28; /* chinfo: channel 0 present */ - reg |= 1 << 9; /* col = 10 */ - reg |= (row_delta & 3) << 6; /* cs0_row low bits */ - - return reg; -} - -static uint32_t rk3588_ddr_sys_reg3(uint64_t group_bytes) -{ - uint64_t group_mb = MAX(group_bytes / MiB, 256); - unsigned int row = 13; - uint32_t row_delta, reg; - - while ((256ULL << (row - 13)) < group_mb && row < 17) { - row++; - } - - row_delta = row - 13; - reg = RK3588_DDR_SYS_REG_VERSION << 28; - reg |= (RK3588_LPDDR4X >> 3) << 12; - reg |= ((row_delta >> 2) & 1) << 5; - - return reg; -} - -static void rk3588_seed_dram_info(RK3588MachineState *s) -{ - MachineState *ms = MACHINE(s); - uint64_t group = MAX(ms->ram_size / 2, 256 * MiB); - uint32_t sys_reg2 = rk3588_ddr_sys_reg2(group); - uint32_t sys_reg3 = rk3588_ddr_sys_reg3(group); - - if (!s->pmu1grf) { - return; - } - - rockchip_syscon_set_u32(s->pmu1grf, 0x200 + 2 * sizeof(uint32_t), - sys_reg2); - rockchip_syscon_set_u32(s->pmu1grf, 0x200 + 3 * sizeof(uint32_t), - sys_reg3); - rockchip_syscon_set_u32(s->pmu1grf, 0x200 + 4 * sizeof(uint32_t), - sys_reg2); - rockchip_syscon_set_u32(s->pmu1grf, 0x200 + 5 * sizeof(uint32_t), - sys_reg3); -} - -static void rk3588_seed_firmware_sysregs(RK3588MachineState *s) -{ - if (s->pmu0grf) { - rockchip_syscon_set_u32(s->pmu0grf, - RK3588_PMU0_GRF_WARM_BOOT_MAGIC_OFFSET, - RK3588_PMU0_GRF_WARM_BOOT_MAGIC); - } -} - -static uint8_t *rk3588_firmware_mmio_ptr(RK3588MachineState *s, hwaddr phys) -{ - return &s->firmware_mmio_regs[ - phys - rk3588_memmap[RK3588_FIRMWARE_MMIO].base]; -} - -static bool rk3588_firmware_usb2_hosts_active(RK3588MachineState *s) -{ - return !s->firmware_boot || s->firmware_handoff_done; -} - -static void rk3588_reset_usb2_host_window(RK3588MachineState *s, hwaddr base) -{ - memset(rk3588_firmware_mmio_ptr(s, base), 0xff, - RK3588_USB2_HOST_WINDOW_SIZE); -} - -static void rk3588_seed_usb2_ehci(RK3588MachineState *s, hwaddr base) -{ - uint8_t *regs = rk3588_firmware_mmio_ptr(s, base); - - memset(regs, 0, RK3588_USB2_HOST_WINDOW_SIZE); - stl_le_p(regs, RK3588_USB2_EHCI_CAPBASE); - stl_le_p(regs + 0x04, RK3588_USB2_EHCI_HCSPARAMS); - stl_le_p(regs + RK3588_USB2_EHCI_USBSTS, RK3588_USB2_EHCI_STS_HALT); - stl_le_p(regs + RK3588_USB2_EHCI_PORTSC0, RK3588_USB2_EHCI_PORT_POWER); -} - -static void rk3588_seed_usb2_ohci(RK3588MachineState *s, hwaddr base) -{ - uint8_t *regs = rk3588_firmware_mmio_ptr(s, base); - - memset(regs, 0, RK3588_USB2_HOST_WINDOW_SIZE); - stl_le_p(regs, RK3588_USB2_OHCI_REVISION); - stl_le_p(regs + RK3588_USB2_OHCI_ROOTHUB_A, - RK3588_USB2_OHCI_RH_A_NDP1 | - RK3588_USB2_OHCI_RH_A_NPS | - RK3588_USB2_OHCI_RH_A_NOCP); - stl_le_p(regs + RK3588_USB2_OHCI_PORTSTATUS0, - RK3588_USB2_OHCI_RH_PS_PPS); -} - -static void rk3588_seed_firmware_usb2_hosts(RK3588MachineState *s) -{ - if (!s->firmware_mmio_regs) { - return; - } - - rk3588_seed_usb2_ehci(s, RK3588_USB2_HOST0_EHCI_BASE); - rk3588_seed_usb2_ohci(s, RK3588_USB2_HOST0_OHCI_BASE); - rk3588_seed_usb2_ehci(s, RK3588_USB2_HOST1_EHCI_BASE); - rk3588_seed_usb2_ohci(s, RK3588_USB2_HOST1_OHCI_BASE); -} - -static void rk3588_reset_firmware_usb2_hosts(RK3588MachineState *s) -{ - if (!s->firmware_mmio_regs) { - return; - } - - rk3588_reset_usb2_host_window(s, RK3588_USB2_HOST0_EHCI_BASE); - rk3588_reset_usb2_host_window(s, RK3588_USB2_HOST0_OHCI_BASE); - rk3588_reset_usb2_host_window(s, RK3588_USB2_HOST1_EHCI_BASE); - rk3588_reset_usb2_host_window(s, RK3588_USB2_HOST1_OHCI_BASE); -} - -static void rk3588_seed_atf_ddr_runtime(RK3588MachineState *s) -{ - memset(s->atf_ddr_runtime_regs, 0, sizeof(s->atf_ddr_runtime_regs)); - - /* - * Rockchip's closed BL31 keeps a DDR controller runtime descriptor in its - * SRAM BSS. SPL also uses this SRAM area, so provide the minimum stable - * descriptor the BL31 DDR save/restore code expects before it programs - * per-channel registers. - */ - stq_le_p(&s->atf_ddr_runtime_regs[RK3588_ATF_DDR_GLOBAL_PTR_OFFSET], - RK3588_ATF_DDR_DESCRIPTOR_ADDR); - stq_le_p(&s->atf_ddr_runtime_regs[RK3588_ATF_TIMER_PTR_OFFSET], - RK3588_ATF_TIMER_TABLE_ADDR); - stq_le_p(&s->atf_ddr_runtime_regs[RK3588_ATF_TIMER_TABLE_OFFSET], - RK3588_ATF_TIMER_COUNTER_ADDR); - stl_le_p(&s->atf_ddr_runtime_regs[RK3588_ATF_TIMER_TABLE_OFFSET + 0x8], - 1000000); - stl_le_p(&s->atf_ddr_runtime_regs[RK3588_ATF_TIMER_TABLE_OFFSET + 0xc], - RK3588_GTIMER_HZ); - stq_le_p(&s->atf_ddr_runtime_regs[RK3588_ATF_DDR_DESCRIPTOR_OFFSET], - RK3588_ATF_DDR_GLOBAL_BASE); - stq_le_p(&s->atf_ddr_runtime_regs[RK3588_ATF_DDR_DESCRIPTOR_OFFSET + 0x20], - RK3588_ATF_DDR_CHANNEL_TABLE_ADDR); - - for (unsigned int i = 0; i < RK3588_ATF_DDR_CHANNELS; i++) { - stq_le_p(&s->atf_ddr_runtime_regs[ - RK3588_ATF_DDR_CHANNEL_TABLE_OFFSET + i * sizeof(uint64_t)], - RK3588_ATF_DDR_CHANNEL_BASE + - i * RK3588_ATF_DDR_CHANNEL_STRIDE); - } + object_unref(OBJECT(dev)); } static void rk3588_write_atags(RK3588MachineState *s) { + const RK3588FirmwareProfile *profile = s->board->firmware_profile; MachineState *ms = MACHINE(s); uint8_t *base = memory_region_get_ram_ptr(&s->atags); - uint32_t tag_size_words = (8 + 184) / sizeof(uint32_t); + uint8_t *ddr_tag = base + 8 + 12; + uint32_t core_size_words = (8 + 12) / sizeof(uint32_t); + uint32_t ddr_size_words = (8 + 184) / sizeof(uint32_t); uint64_t ddr_size = rk3588_memmap[RK3588_RAM].base + ms->ram_size; memset(base, 0, RK3588_ATAGS_SIZE); - stl_le_p(base, tag_size_words); - stl_le_p(base + 4, 0x54410052); /* ATAG_DDR_MEM */ - stl_le_p(base + 8, 1); /* one DRAM bank */ - stl_le_p(base + 12, 0); /* tag version */ - stq_le_p(base + 16, 0); /* bank[0] start */ - stq_le_p(base + 24, ddr_size); /* bank[0] size */ - stl_le_p(base + tag_size_words * sizeof(uint32_t), 0); + if (!profile || !profile->atags_core) { + stl_le_p(base, ddr_size_words); + stl_le_p(base + 4, 0x54410052); /* ATAG_DDR_MEM */ + stl_le_p(base + 8, 1); /* one DRAM bank */ + stl_le_p(base + 12, 0); /* tag version */ + stq_le_p(base + 16, 0); /* bank[0] start */ + stq_le_p(base + 24, ddr_size); /* bank[0] size */ + stl_le_p(base + ddr_size_words * sizeof(uint32_t), 0); + return; + } + + stl_le_p(base, core_size_words); + stl_le_p(base + 4, 0x54410001); /* ATAG_CORE */ + + stl_le_p(ddr_tag, ddr_size_words); + stl_le_p(ddr_tag + 4, 0x54410052); /* ATAG_DDR_MEM */ + stl_le_p(ddr_tag + 8, 1); /* one DRAM bank */ + stl_le_p(ddr_tag + 12, 0); /* tag version */ + stq_le_p(ddr_tag + 16, 0); /* bank[0] start */ + stq_le_p(ddr_tag + 24, ddr_size); /* bank[0] size */ + stl_le_p(ddr_tag + ddr_size_words * sizeof(uint32_t), 0); } static void rk3588_seed_iram_firmware_shims(RK3588MachineState *s) @@ -1783,24 +1326,56 @@ static void rk3588_schedule_firmware_patch(RK3588MachineState *s) RK3588_FIRMWARE_PATCH_INTERVAL_NS); } -static void rk3588_firmware_handoff_to_uboot(RK3588MachineState *s, - ARMCPU *cpu) +static void rk3588_set_uboot_cpu_state(RK3588MachineState *s, ARMCPU *cpu) { CPUState *cs = CPU(cpu); CPUARMState *env = &cpu->env; + hwaddr entry = RK3588_UBOOT_LOAD_ADDR; + + if (rk3588_dynamic_fit_handoff(s) && + s->bootrom_state.fit_handoff_valid) { + entry = s->bootrom_state.uboot_entry; + tb_invalidate_phys_range(cs, s->bootrom_state.uboot_load, + s->bootrom_state.uboot_load + + s->bootrom_state.uboot_size - 1); + } - rk3588_prepare_nonsecure_linux_interrupts(s); cpu_reset(cs); arm_emulate_firmware_reset(cs, 2); - cpu_set_pc(cs, RK3588_UBOOT_LOAD_ADDR); + cpu_set_pc(cs, entry); env->xregs[0] = 0; env->xregs[1] = 0; env->xregs[2] = 0; env->xregs[3] = 0; cs->halted = false; arm_rebuild_hflags(env); +} + +static void rk3588_firmware_handoff_to_uboot(RK3588MachineState *s, + ARMCPU *cpu) +{ + rk3588_prepare_nonsecure_linux_interrupts(s); + rk3588_set_uboot_cpu_state(s, cpu); s->firmware_handoff_done = true; - rk3588_seed_firmware_usb2_hosts(s); + rk3588_usb2_host_set_active(s->usb2_host, true); +} + +static void rk3588_firmware_handoff_work(CPUState *cs, + run_on_cpu_data data) +{ + RK3588MachineState *s = data.host_ptr; + + rk3588_set_uboot_cpu_state(s, ARM_CPU(cs)); +} + +static void rk3588_schedule_firmware_handoff(RK3588MachineState *s, + ARMCPU *cpu) +{ + rk3588_prepare_nonsecure_linux_interrupts(s); + s->firmware_handoff_done = true; + rk3588_usb2_host_set_active(s->usb2_host, true); + async_run_on_cpu(CPU(cpu), rk3588_firmware_handoff_work, + RUN_ON_CPU_HOST_PTR(s)); } static void rk3588_firmware_patch_tick(void *opaque) @@ -1816,12 +1391,29 @@ static void rk3588_firmware_patch_tick(void *opaque) return; } + pc = env->pc; + if (rk3588_dynamic_fit_handoff(s)) { + RK3588BootROM *bootrom = &s->bootrom_state; + + if (bootrom->fit_handoff_valid && + arm_current_el(env) == 3 && + pc >= bootrom->atf_load && + pc < bootrom->atf_load + bootrom->atf_size && + rk3588_phys_read32(bootrom->uboot_entry, &uboot_entry) && + uboot_entry == bootrom->uboot_entry_word) { + rk3588_schedule_firmware_handoff(s, cpu); + return; + } + + rk3588_schedule_firmware_patch(s); + return; + } + /* * Keep BL31 writes after SPL hash verification by patching only once the * CPU has entered BL31. The U-Boot proper load may become visible on a * later tick, so handoff below is not gated by the current PC. */ - pc = env->pc; pc_in_bl31 = pc >= RK3588_BL31_BASE && pc < RK3588_BL31_LIMIT; if (pc_in_bl31) { rk3588_patch_bl31_runtime(s); @@ -1830,7 +1422,7 @@ static void rk3588_firmware_patch_tick(void *opaque) if (!s->firmware_atf_entered && s->firmware_patch_done && rk3588_phys_read32(RK3588_UBOOT_LOAD_ADDR, &uboot_entry) && uboot_entry == RK3588_UBOOT_ENTRY_BRANCH) { - rk3588_firmware_handoff_to_uboot(s, cpu); + rk3588_schedule_firmware_handoff(s, cpu); return; } @@ -1841,19 +1433,9 @@ static void rk3588_boot_state_reset(void *opaque) { RK3588MachineState *s = opaque; - rk3588_seed_dram_info(s); - rk3588_seed_firmware_sysregs(s); - rk3588_seed_atf_ddr_runtime(s); - if (!s->firmware_boot) { - rk3588_seed_firmware_usb2_hosts(s); - } else { - rk3588_reset_firmware_usb2_hosts(s); - } + rk3588_usb2_host_set_active(s->usb2_host, !s->firmware_boot); rk3588_write_atags(s); rk3588_seed_iram_firmware_shims(s); - s->firmware_mmio_gate_done = false; - s->firmware_mmio_last_phy_gate = false; - s->firmware_mmio_gate_bit5_clear = false; s->firmware_patch_done = false; s->firmware_handoff_done = false; s->firmware_atf_entered = false; @@ -1883,14 +1465,6 @@ static void rk3588_create_low_memory(RK3588MachineState *s) rk3588_memmap[RK3588_FIRMWARE_SCRATCH].base, &s->firmware_scratch); - memory_region_init_io(&s->atf_ddr_runtime, OBJECT(s), - &rk3588_atf_ddr_runtime_ops, s, - "rk3588.atf-ddr-runtime", - RK3588_ATF_DDR_RUNTIME_SIZE); - memory_region_add_subregion_overlap(sysmem, RK3588_ATF_DDR_RUNTIME_ADDR, - &s->atf_ddr_runtime, 10); - rk3588_seed_atf_ddr_runtime(s); - memory_region_init_ram(&s->atags, NULL, "rk3588.atags", rk3588_memmap[RK3588_ATAGS].size, &error_fatal); memory_region_add_subregion(sysmem, rk3588_memmap[RK3588_ATAGS].base, @@ -1915,6 +1489,18 @@ static void rk3588_create_low_memory(RK3588MachineState *s) rk3588_firmware_patch_tick, s); } +static void rk3588_create_atf_ddr(RK3588MachineState *s) +{ + DeviceState *dev = qdev_new(TYPE_RK3588_ATF_DDR); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + object_property_add_child(OBJECT(s), "atf-ddr", OBJECT(dev)); + sysbus_realize(sbd, &error_fatal); + sysbus_mmio_map_overlap(sbd, 0, RK3588_ATF_DDR_RUNTIME_BASE, 10); + s->atf_ddr = dev; + object_unref(OBJECT(dev)); +} + static void rk3588_create_zvm_ram(RK3588MachineState *s) { MachineState *ms = MACHINE(s); @@ -1954,17 +1540,13 @@ static void rk3588_create_zvm_ram(RK3588MachineState *s) static void rk3588_create_firmware_mmio(RK3588MachineState *s) { - s->firmware_mmio_regs = g_malloc(rk3588_memmap[RK3588_FIRMWARE_MMIO].size); - memset(s->firmware_mmio_regs, 0xff, - rk3588_memmap[RK3588_FIRMWARE_MMIO].size); + DeviceState *dev = qdev_new(TYPE_RK3588_FIRMWARE_MMIO); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); - memory_region_init_io(&s->firmware_mmio, OBJECT(s), - &rk3588_firmware_mmio_ops, s, - "rk3588.firmware-mmio", - rk3588_memmap[RK3588_FIRMWARE_MMIO].size); - memory_region_add_subregion_overlap(get_system_memory(), - rk3588_memmap[RK3588_FIRMWARE_MMIO].base, - &s->firmware_mmio, -1000); + object_property_add_child(OBJECT(s), "firmware-mmio", OBJECT(dev)); + sysbus_realize(sbd, &error_fatal); + sysbus_mmio_map_overlap(sbd, 0, RK3588_FIRMWARE_MMIO_BASE, -1000); + object_unref(OBJECT(dev)); } static bool rk3588_blk_read(BlockBackend *blk, int64_t offset, @@ -1980,6 +1562,377 @@ static bool rk3588_blk_read(BlockBackend *blk, int64_t offset, return true; } +static bool rk3588_dynamic_fit_handoff(RK3588MachineState *s) +{ + const RK3588FirmwareProfile *profile = s->board->firmware_profile; + + return profile && profile->dynamic_fit_handoff; +} + +static const char *rk3588_fit_single_string(const void *fit, int node, + const char *property, + Error **errp) +{ + const char *value; + const char *node_name = fdt_get_name(fit, node, NULL); + int count = fdt_stringlist_count(fit, node, property); + int len; + + if (count != 1) { + if (count < 0) { + error_setg(errp, "invalid FIT %s/%s property: %s", + node_name, property, fdt_strerror(count)); + } else { + error_setg(errp, "FIT %s/%s contains %d strings, expected 1", + node_name, property, count); + } + return NULL; + } + + value = fdt_stringlist_get(fit, node, property, 0, &len); + if (!value || !len) { + error_setg(errp, "FIT %s/%s is empty", node_name, property); + return NULL; + } + + return value; +} + +static bool rk3588_fit_check_string(const void *fit, int node, + const char *property, + const char *expected, Error **errp) +{ + const char *value = rk3588_fit_single_string(fit, node, property, errp); + + if (!value) { + return false; + } + if (strcmp(value, expected)) { + error_setg(errp, "FIT image %s has %s '%s', expected '%s'", + fdt_get_name(fit, node, NULL), property, value, expected); + return false; + } + + return true; +} + +static bool rk3588_fit_get_address(const void *fit, int node, + const char *property, bool optional, + hwaddr *value, Error **errp) +{ + const void *data; + int len; + + data = fdt_getprop(fit, node, property, &len); + if (!data) { + if (optional && len == -FDT_ERR_NOTFOUND) { + return true; + } + error_setg(errp, "cannot read FIT image %s/%s: %s", + fdt_get_name(fit, node, NULL), property, + fdt_strerror(len)); + return false; + } + + switch (len) { + case sizeof(fdt32_t): + *value = fdt32_ld(data); + return true; + case sizeof(fdt64_t): + *value = fdt64_ld(data); + return true; + default: + error_setg(errp, "FIT image %s/%s has invalid length %d", + fdt_get_name(fit, node, NULL), property, len); + return false; + } +} + +static bool rk3588_fit_get_u32(const void *fit, int node, + const char *property, uint32_t *value, + Error **errp) +{ + const fdt32_t *data; + int len; + + data = fdt_getprop(fit, node, property, &len); + if (!data) { + error_setg(errp, "cannot read FIT image %s/%s: %s", + fdt_get_name(fit, node, NULL), property, + fdt_strerror(len)); + return false; + } + if (len != sizeof(*data)) { + error_setg(errp, "FIT image %s/%s has invalid length %d", + fdt_get_name(fit, node, NULL), property, len); + return false; + } + + *value = fdt32_ld(data); + return true; +} + +static const char *rk3588_fit_find_uboot(const void *fit, int config, + int images, Error **errp) +{ + const char *candidate = NULL; + int count = fdt_stringlist_count(fit, config, "loadables"); + + if (count <= 0) { + error_setg(errp, "FIT configuration has no valid loadables list"); + return NULL; + } + + for (int i = 0; i < count; i++) { + const char *name = fdt_stringlist_get(fit, config, "loadables", i, + NULL); + + if (!name) { + error_setg(errp, "cannot read FIT loadables[%d]", i); + return NULL; + } + if (!strcmp(name, "uboot")) { + return name; + } + } + + for (int i = 0; i < count; i++) { + const char *name = fdt_stringlist_get(fit, config, "loadables", i, + NULL); + int image = fdt_subnode_offset(fit, images, name); + + if (image < 0) { + error_setg(errp, "FIT loadable '%s' has no image node", name); + return NULL; + } + if (fdt_stringlist_search(fit, image, "type", "standalone") >= 0 && + fdt_stringlist_search(fit, image, "os", "U-Boot") >= 0) { + if (candidate) { + error_setg(errp, "FIT configuration has multiple U-Boot " + "loadables"); + return NULL; + } + candidate = name; + } + } + + if (!candidate) { + error_setg(errp, "FIT configuration has no U-Boot loadable"); + } + return candidate; +} + +static bool rk3588_fit_read_image(const void *fit, int images, + const char *name, + const char *expected_type, + const char *expected_os, + uint64_t payload_base, + uint64_t media_size, + RK3588FITImage *image, Error **errp) +{ + uint32_t data_offset; + int node = fdt_subnode_offset(fit, images, name); + + if (node < 0) { + error_setg(errp, "FIT configuration references missing image '%s'", + name); + return false; + } + if (!rk3588_fit_check_string(fit, node, "type", expected_type, errp) || + !rk3588_fit_check_string(fit, node, "os", expected_os, errp) || + !rk3588_fit_check_string(fit, node, "compression", "none", errp) || + !rk3588_fit_get_address(fit, node, "load", false, + &image->load, errp) || + !rk3588_fit_get_u32(fit, node, "data-size", &image->size, errp) || + !rk3588_fit_get_u32(fit, node, "data-offset", &data_offset, errp)) { + return false; + } + + image->entry = 0; + if (!rk3588_fit_get_address(fit, node, "entry", true, + &image->entry, errp)) { + return false; + } + if (!image->entry) { + image->entry = image->load; + } + + if (!image->size || image->load > HWADDR_MAX - image->size || + image->entry < image->load || + image->entry >= image->load + image->size) { + error_setg(errp, "FIT image '%s' has an invalid load range", name); + return false; + } + if (payload_base > media_size || + data_offset > media_size - payload_base || + image->size > media_size - payload_base - data_offset) { + error_setg(errp, "FIT image '%s' external data exceeds boot media", + name); + return false; + } + + image->media_offset = payload_base + data_offset; + return true; +} + +static bool rk3588_bootrom_prepare_fit_handoff(RK3588MachineState *s, + BlockBackend *blk, + Error **errp) +{ + const RK3588FirmwareProfile *profile = s->board->firmware_profile; + MachineState *ms = MACHINE(s); + struct fdt_header header; + g_autofree uint8_t *fit = NULL; + RK3588FITImage atf = { 0 }; + RK3588FITImage uboot = { 0 }; + const char *default_name; + const char *atf_name; + const char *uboot_name; + int64_t media_len; + uint64_t media_size; + uint64_t payload_base; + uint64_t entry_delta; + uint32_t metadata_size; + uint32_t entry_word; + int configs; + int config; + int images; + int ret; + + if (!rk3588_dynamic_fit_handoff(s)) { + return true; + } + + s->bootrom_state.fit_handoff_valid = false; + if (!profile->fit_alignment || + (profile->fit_alignment & (profile->fit_alignment - 1)) || + profile->fit_alignment > RK3588_FIT_METADATA_MAX_SIZE) { + error_setg(errp, "%s has invalid FIT alignment %u", + s->board->machine_name, profile->fit_alignment); + return false; + } + + media_len = blk_getlength(blk); + if (media_len < 0) { + error_setg_errno(errp, -media_len, + "cannot determine RK3588 boot media size"); + return false; + } + media_size = media_len; + if (profile->fit_offset > media_size || + sizeof(header) > media_size - profile->fit_offset || + !rk3588_blk_read(blk, profile->fit_offset, &header, + sizeof(header), errp)) { + if (!*errp) { + error_setg(errp, "%s FIT header exceeds boot media", + s->board->machine_name); + } + return false; + } + + ret = fdt_check_header(&header); + if (ret < 0) { + error_setg(errp, "%s boot media has an invalid FIT header: %s", + s->board->machine_name, fdt_strerror(ret)); + return false; + } + metadata_size = fdt_totalsize(&header); + if (metadata_size < sizeof(header) || + metadata_size > RK3588_FIT_METADATA_MAX_SIZE || + metadata_size > media_size - profile->fit_offset) { + error_setg(errp, "%s FIT metadata size 0x%x is invalid", + s->board->machine_name, metadata_size); + return false; + } + + fit = g_malloc(metadata_size); + if (!rk3588_blk_read(blk, profile->fit_offset, fit, metadata_size, + errp)) { + return false; + } + ret = fdt_check_full(fit, metadata_size); + if (ret < 0) { + error_setg(errp, "%s FIT metadata is invalid: %s", + s->board->machine_name, fdt_strerror(ret)); + return false; + } + + payload_base = ROUND_UP((uint64_t)metadata_size, + profile->fit_alignment); + if (payload_base > media_size - profile->fit_offset) { + error_setg(errp, "%s FIT payload exceeds boot media", + s->board->machine_name); + return false; + } + payload_base += profile->fit_offset; + + configs = fdt_path_offset(fit, "/configurations"); + images = fdt_path_offset(fit, "/images"); + if (configs < 0 || images < 0) { + error_setg(errp, "%s FIT lacks configurations or images", + s->board->machine_name); + return false; + } + default_name = rk3588_fit_single_string(fit, configs, "default", errp); + if (!default_name) { + return false; + } + config = fdt_subnode_offset(fit, configs, default_name); + if (config < 0) { + error_setg(errp, "FIT default configuration '%s' is missing", + default_name); + return false; + } + atf_name = rk3588_fit_single_string(fit, config, "firmware", errp); + if (!atf_name) { + return false; + } + uboot_name = rk3588_fit_find_uboot(fit, config, images, errp); + if (!uboot_name || + !rk3588_fit_read_image(fit, images, atf_name, "firmware", + "arm-trusted-firmware", payload_base, + media_size, &atf, errp) || + !rk3588_fit_read_image(fit, images, uboot_name, "standalone", + "U-Boot", payload_base, media_size, + &uboot, errp)) { + return false; + } + + if (atf.load >= rk3588_memmap[RK3588_SRAM].size || + atf.size > rk3588_memmap[RK3588_SRAM].size - atf.load) { + error_setg(errp, "FIT ATF image lies outside RK3588 SRAM"); + return false; + } + if (uboot.load < rk3588_memmap[RK3588_RAM].base || + uboot.load - rk3588_memmap[RK3588_RAM].base >= ms->ram_size || + uboot.size > ms->ram_size - + (uboot.load - rk3588_memmap[RK3588_RAM].base)) { + error_setg(errp, "FIT U-Boot image lies outside guest RAM"); + return false; + } + + entry_delta = uboot.entry - uboot.load; + if (uboot.size < sizeof(entry_word) || + entry_delta > uboot.size - sizeof(entry_word) || + !rk3588_blk_read(blk, uboot.media_offset + entry_delta, + &entry_word, sizeof(entry_word), errp)) { + if (!*errp) { + error_setg(errp, "FIT U-Boot entry does not contain an " + "instruction"); + } + return false; + } + + s->bootrom_state.atf_load = atf.load; + s->bootrom_state.atf_size = atf.size; + s->bootrom_state.uboot_load = uboot.load; + s->bootrom_state.uboot_entry = uboot.entry; + s->bootrom_state.uboot_size = uboot.size; + s->bootrom_state.uboot_entry_word = le32_to_cpu(entry_word); + s->bootrom_state.fit_handoff_valid = true; + return true; +} + static bool rk3588_load_rkns_image(BlockBackend *blk, const RK3588HeaderV2 *hdr, unsigned int index, uint8_t **data, @@ -2054,6 +2007,11 @@ static bool rk3588_bootrom_prepare(RK3588MachineState *s, Error **errp) g_free(tpl); return false; } + if (!rk3588_bootrom_prepare_fit_handoff(s, blk, errp)) { + g_free(tpl); + g_free(spl); + return false; + } if (address_space_write(&address_space_memory, RK3588_TPL_LOAD_ADDR, MEMTXATTRS_UNSPECIFIED, tpl, tpl_size) != @@ -2084,7 +2042,9 @@ static void rk3588_bootrom_load_spl(RK3588MachineState *s, ARMCPU *cpu) address_space_write(&address_space_memory, rk3588_memmap[RK3588_SRAM].base, MEMTXATTRS_UNSPECIFIED, s->bootrom_state.spl, s->bootrom_state.spl_size); - rk3588_patch_spl_atf_handoff(); + if (!rk3588_dynamic_fit_handoff(s)) { + rk3588_patch_spl_atf_handoff(); + } stl_le_p(memory_region_get_ram_ptr(&s->iram) + 0x10, board->brom_bootsource); s->bootrom_state.spl_loaded = true; @@ -2353,99 +2313,125 @@ static void rk3588_create_gpio(RK3588MachineState *s) static void rk3588_create_gmac(RK3588MachineState *s) { + SysBusDevice *sbd; + /* - * Synopsys dwmac-4.20a (GMAC4). The FDT advertises both RK3588 GMAC - * instances as "rockchip,rk3588-gmac", "snps,dwmac-4.20a" so Linux - * stmmac reads - * MAC_VERSION @0x110 expecting SNPSVER 0x51 (GMAC4) - TYPE_DWMAC4 - * returns exactly that, letting stmmac_bind complete and eth0 - * enumerate. Replaces the older TYPE_NPCM_GMAC v3.50a model which - * returned the wrong synth-id and never bound. + * Synopsys dwmac-4.20a (GMAC4). Boards select the RK3588 GMAC instances + * that their FDT advertises as "rockchip,rk3588-gmac", + * "snps,dwmac-4.20a". Linux stmmac reads MAC_VERSION @0x110 expecting + * SNPSVER 0x51 (GMAC4); TYPE_DWMAC4 returns exactly that, letting + * stmmac_bind complete and the interface enumerate. Replaces the older + * TYPE_NPCM_GMAC v3.50a model which returned the wrong synth-id and never + * bound. */ - s->gmac0 = qdev_new(TYPE_DWMAC4); - object_property_add_child(OBJECT(s), "gmac0", OBJECT(s->gmac0)); - qemu_configure_nic_device(s->gmac0, false, "gmac0"); - SysBusDevice *sbd = SYS_BUS_DEVICE(s->gmac0); - sysbus_realize(sbd, &error_fatal); - sysbus_mmio_map(sbd, 0, rk3588_memmap[RK3588_GMAC0].base); - sysbus_connect_irq(sbd, 0, qdev_get_gpio_in(s->gic, RK3588_GMAC0_SPI)); + if (s->board->gmac_mask & BIT(0)) { + s->gmac0 = qdev_new(TYPE_DWMAC4); + object_property_add_child(OBJECT(s), "gmac0", OBJECT(s->gmac0)); + qemu_configure_nic_device(s->gmac0, false, "gmac0"); + sbd = SYS_BUS_DEVICE(s->gmac0); + sysbus_realize(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, rk3588_memmap[RK3588_GMAC0].base); + sysbus_connect_irq(sbd, 0, + qdev_get_gpio_in(s->gic, RK3588_GMAC0_SPI)); + } - s->gmac1 = qdev_new(TYPE_DWMAC4); - object_property_add_child(OBJECT(s), "gmac1", OBJECT(s->gmac1)); - qemu_configure_nic_device(s->gmac1, true, "gmac1"); - sbd = SYS_BUS_DEVICE(s->gmac1); - sysbus_realize(sbd, &error_fatal); - sysbus_mmio_map(sbd, 0, rk3588_memmap[RK3588_GMAC1].base); - sysbus_connect_irq(sbd, 0, qdev_get_gpio_in(s->gic, RK3588_GMAC1_SPI)); + if (s->board->gmac_mask & BIT(1)) { + s->gmac1 = qdev_new(TYPE_DWMAC4); + object_property_add_child(OBJECT(s), "gmac1", OBJECT(s->gmac1)); + qemu_configure_nic_device(s->gmac1, true, "gmac1"); + sbd = SYS_BUS_DEVICE(s->gmac1); + sysbus_realize(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, rk3588_memmap[RK3588_GMAC1].base); + sysbus_connect_irq(sbd, 0, + qdev_get_gpio_in(s->gic, RK3588_GMAC1_SPI)); + } } -static void rk3588_create_pcie(RK3588MachineState *s) -{ - SysBusDevice *sbd; +enum { + RK3588_PCIE_IRQ_ERR, + RK3588_PCIE_IRQ_LEGACY, + RK3588_PCIE_IRQ_MSG, + RK3588_PCIE_IRQ_PMC, + RK3588_PCIE_IRQ_SYS, +}; - /* - * RK3588 PCIe 3x4 host - wraps TYPE_DESIGNWARE_PCIE_HOST and adds - * the RK APB vendor register window (LTSSM pinned link-up at - * 0x300=0x00030011, rest RAZ/WI). sysbus mmio[0] is the inherited - * 4 KiB DBI (DWC core), sysbus mmio[1] is the 64 KiB RK APB. - * - * sysbus IRQs: 0..3 = INTA..INTD (legacy), 4 = MSI (msg), - * 5/6/7 = err/pmc/sys (inert). The board wires them to the five - * GIC SPIs the DT advertises. - */ - s->pcie = qdev_new(TYPE_ROCKCHIP_PCIE_HOST); - object_property_add_child(OBJECT(s), "pcie3x4", OBJECT(s->pcie)); - sbd = SYS_BUS_DEVICE(s->pcie); +static DeviceState *rk3588_create_pcie_host(RK3588MachineState *s, + const char *name, + const char *vmstate_id, + hwaddr dbi_base, + hwaddr apb_base, + uint32_t domain, + uint8_t bus_nr, + bool link_down, + const int *spis) +{ + DeviceState *dev = qdev_new(TYPE_ROCKCHIP_PCIE_HOST); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + if (vmstate_id) { + dev->id = g_strdup(vmstate_id); + } + qdev_prop_set_bit(dev, "link-up", !link_down); + qdev_prop_set_uint32(dev, "domain", domain); + qdev_prop_set_uint8(dev, "bus-nr", bus_nr); + object_property_add_child(OBJECT(s), name, OBJECT(dev)); sysbus_realize(sbd, &error_fatal); - /* DBI: inherited 4 KiB DWC core mmio at the very start of the - * 4 MiB DBI window. The remaining (4 MiB - 4 KiB) - including - * DBI2 at +0x10_0000 - is backed by an unimplemented device so - * guest DBI reads/writes above 0xfff don't abort (D-15). */ - sysbus_mmio_map(sbd, 0, rk3588_memmap[RK3588_PCIE3X4_DBI].base); - create_unimplemented_device("rk3588.pcie3x4-dbi-tail", - rk3588_memmap[RK3588_PCIE3X4_DBI].base + 0x1000, - rk3588_memmap[RK3588_PCIE3X4_DBI].size - 0x1000); + sysbus_mmio_map(sbd, 0, dbi_base); + sysbus_mmio_map(sbd, 1, apb_base); + sysbus_mmio_map(sbd, 2, dbi_base + ROCKCHIP_PCIE_DBI_CORE_SIZE); - /* APB: RK vendor PCIE_CLIENT_* window (LTSSM_STATUS pinned). */ - sysbus_mmio_map(sbd, 1, rk3588_memmap[RK3588_PCIE3X4_APB].base); - - /* - * CFG window at 0xf0000000: served by the designware root's - * outbound CFG viewport once the guest programs the iATU in - * dw_pcie_config_ecam_iatu. The designware model maps - * viewport->cfg at the programmed base in system memory, so no - * static alias is needed here. Cover the 1 MiB window with an - * unimplemented device up front so that any pre-iATU-program - * access does not abort; once the viewport is enabled it shadows - * this hole (the viewport region is added with subregion overlap - * priority 0, which beats the unimplemented device's default 0). - * To make that override robust, drop the cover before the guest - * reaches iATU setup is not possible; instead we leave the hole - * unbacked and rely on the designware model's - * memory_region_set_address to install the viewport region on - * top - which works because MemoryRegion overlap resolves the - * most-recently-added region first. So we do NOT pre-cover CFG. - */ - - /* IRQs: legacy INTA..INTD all map to SPI 260 (the dw-rockchip - * driver installs a single chained handler on the legacy line - * and demuxes INTA..D from PCIE_CLIENT_INTR_STATUS_LEGACY). */ for (unsigned int i = 0; i < 4; i++) { sysbus_connect_irq(sbd, i, qdev_get_gpio_in(s->gic, - RK3588_PCIE3X4_LEGACY_SPI)); + spis[RK3588_PCIE_IRQ_LEGACY])); } - /* msg -> MSI parent (SPI 261). */ sysbus_connect_irq(sbd, ROCKCHIP_PCIE_MSG_IRQ, - qdev_get_gpio_in(s->gic, RK3588_PCIE3X4_MSG_SPI)); - /* RK-only inert IRQs (err/pmc/sys). Wired for FDT fidelity. */ + qdev_get_gpio_in(s->gic, + spis[RK3588_PCIE_IRQ_MSG])); sysbus_connect_irq(sbd, ROCKCHIP_PCIE_ERR_IRQ, - qdev_get_gpio_in(s->gic, RK3588_PCIE3X4_ERR_SPI)); + qdev_get_gpio_in(s->gic, + spis[RK3588_PCIE_IRQ_ERR])); sysbus_connect_irq(sbd, ROCKCHIP_PCIE_PMC_IRQ, - qdev_get_gpio_in(s->gic, RK3588_PCIE3X4_PMC_SPI)); + qdev_get_gpio_in(s->gic, + spis[RK3588_PCIE_IRQ_PMC])); sysbus_connect_irq(sbd, ROCKCHIP_PCIE_SYS_IRQ, - qdev_get_gpio_in(s->gic, RK3588_PCIE3X4_SYS_SPI)); + qdev_get_gpio_in(s->gic, + spis[RK3588_PCIE_IRQ_SYS])); + + return dev; +} + +static void rk3588_create_pcie(RK3588MachineState *s) +{ + static const int pcie3x4_spis[] = { + [RK3588_PCIE_IRQ_ERR] = RK3588_PCIE3X4_ERR_SPI, + [RK3588_PCIE_IRQ_LEGACY] = RK3588_PCIE3X4_LEGACY_SPI, + [RK3588_PCIE_IRQ_MSG] = RK3588_PCIE3X4_MSG_SPI, + [RK3588_PCIE_IRQ_PMC] = RK3588_PCIE3X4_PMC_SPI, + [RK3588_PCIE_IRQ_SYS] = RK3588_PCIE3X4_SYS_SPI, + }; + static const int pcie3x2_spis[] = { + [RK3588_PCIE_IRQ_ERR] = RK3588_PCIE3X2_ERR_SPI, + [RK3588_PCIE_IRQ_LEGACY] = RK3588_PCIE3X2_LEGACY_SPI, + [RK3588_PCIE_IRQ_MSG] = RK3588_PCIE3X2_MSG_SPI, + [RK3588_PCIE_IRQ_PMC] = RK3588_PCIE3X2_PMC_SPI, + [RK3588_PCIE_IRQ_SYS] = RK3588_PCIE3X2_SYS_SPI, + }; + + s->pcie3x4 = rk3588_create_pcie_host( + s, "pcie3x4", "pcie3x4", + rk3588_memmap[RK3588_PCIE3X4_DBI].base, + rk3588_memmap[RK3588_PCIE3X4_APB].base, + 0, 0, s->board->pcie3x4_link_down, pcie3x4_spis); + + if (s->board->pcie3x2_num_lanes) { + s->pcie3x2 = rk3588_create_pcie_host( + s, "pcie3x2", "pcie3x2", + rk3588_memmap[RK3588_PCIE3X2_DBI].base, + rk3588_memmap[RK3588_PCIE3X2_APB].base, + 1, 0x10, s->board->pcie3x2_link_down, pcie3x2_spis); + } } static void rk3588_create_cru(RK3588MachineState *s) @@ -2470,6 +2456,66 @@ static void rk3588_create_stimer(RK3588MachineState *s) sysbus_mmio_map(sbd, 0, rk3588_memmap[RK3588_STIMER].base); } +static void rk3588_create_ddr(RK3588MachineState *s) +{ + DeviceState *dev = qdev_new(TYPE_RK3588_DDR); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + object_property_add_child(OBJECT(s), "ddr", OBJECT(dev)); + sysbus_realize(sbd, &error_fatal); + for (unsigned int i = 0; i < RK3588_DDR_LEGACY_CHANNEL_COUNT; i++) { + hwaddr base = RK3588_DDR_LEGACY_BASE + + i * RK3588_DDR_LEGACY_CHANNEL_STRIDE; + + for (unsigned int j = 0; + j < RK3588_DDR_LEGACY_CTRL_WINDOWS_PER_CHANNEL; j++) { + sysbus_mmio_map(sbd, RK3588_DDR_MMIO_LEGACY(i, j), + base + j * RK3588_DDR_LEGACY_WINDOW_STRIDE); + } + sysbus_mmio_map(sbd, RK3588_DDR_MMIO_LEGACY_GATE(i), + RK3588_DDR_PHY_GATE_BASE + + i * RK3588_DDR_LEGACY_WINDOW_STRIDE); + } + sysbus_mmio_map(sbd, RK3588_DDR_MMIO_LEGACY_PHY, + RK3588_DDR_LEGACY_PHY_BASE); + sysbus_mmio_map(sbd, RK3588_DDR_MMIO_LEGACY_PHY_AUX, + RK3588_DDR_LEGACY_PHY_AUX_BASE); + sysbus_mmio_map(sbd, RK3588_DDR_MMIO_GLOBAL, + RK3588_DDR_GLOBAL_BASE); + for (unsigned int i = 0; i < RK3588_DDR_CHANNEL_COUNT; i++) { + sysbus_mmio_map(sbd, RK3588_DDR_MMIO_CHANNEL(i), + RK3588_DDR_CHANNEL_BASE + + i * RK3588_DDR_CHANNEL_MMIO_STRIDE); + } + sysbus_mmio_map(sbd, RK3588_DDR_MMIO_DDRPHY, RK3588_DDRPHY_BASE); + s->ddr = RK3588_DDR(dev); + object_unref(OBJECT(dev)); +} + +static void rk3588_create_usb2_host(RK3588MachineState *s) +{ + static const hwaddr bases[RK3588_USB2_HOST_MMIO_COUNT] = { + [RK3588_USB2_HOST_EHCI0] = RK3588_USB2_HOST0_EHCI_BASE, + [RK3588_USB2_HOST_OHCI0] = RK3588_USB2_HOST0_OHCI_BASE, + [RK3588_USB2_HOST_EHCI1] = RK3588_USB2_HOST1_EHCI_BASE, + [RK3588_USB2_HOST_OHCI1] = RK3588_USB2_HOST1_OHCI_BASE, + }; + MachineState *machine = MACHINE(s); + DeviceState *dev = qdev_new(TYPE_RK3588_USB2_HOST); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + object_property_add_child(OBJECT(s), "usb2-host", OBJECT(dev)); + sysbus_realize(sbd, &error_fatal); + for (unsigned int i = 0; i < ARRAY_SIZE(bases); i++) { + sysbus_mmio_map(sbd, i, bases[i]); + } + + s->usb2_host = RK3588_USB2_HOST(dev); + rk3588_usb2_host_set_active(s->usb2_host, + qtest_enabled() || machine->kernel_filename); + object_unref(OBJECT(dev)); +} + /* * Per-machine SMC handler entry. Registered with * arm_register_psci_smc_handler() so accelerator SMC exception paths can run it @@ -2477,23 +2523,18 @@ static void rk3588_create_stimer(RK3588MachineState *s) * Consumes only the SCMI SMC (function-id 0x82000010); all other SMCs fall * through to standard PSCI handling (CPU_ON/OFF/SYSTEM_RESET/...). * - * The shmem-backed responder lives in the rk3588-scmi device - * (realized in rk3588_create_scmi). We stash the realized device - * pointer in a file-scope variable so the SMC hook can find it from - * the CPU run-loop without an explicit per-CPU linkage. + * The shmem-backed responder lives in the rk3588-scmi device realized by + * rk3588_create_scmi(). Resolve the active machine through QOM so multiple + * machine instances do not share file-scope device state. */ -static RK3588SCMIState *rk3588_scmi_dev; -static RK3588MachineState *rk3588_active_machine; - static bool rk3588_smc_handler(ARMCPU *cpu) { + RK3588MachineState *s = RK3588_MACHINE(qdev_get_machine()); CPUARMState *env = &cpu->env; uint64_t fn = is_a64(env) ? env->xregs[0] : env->regs[0]; if ((uint32_t)fn == RK3588_BROM_SMC_NEXT_STAGE) { - if (rk3588_active_machine) { - rk3588_bootrom_load_spl(rk3588_active_machine, cpu); - } + rk3588_bootrom_load_spl(s, cpu); if (is_a64(env)) { env->xregs[0] = 0; } else { @@ -2503,46 +2544,40 @@ static bool rk3588_smc_handler(ARMCPU *cpu) } if ((uint32_t)fn == RK3588_QEMU_SMC_UBOOT_HANDOFF) { - if (rk3588_active_machine) { - rk3588_firmware_handoff_to_uboot(rk3588_active_machine, cpu); - } + rk3588_firmware_handoff_to_uboot(s, cpu); return true; } if ((uint32_t)fn == RK3588_QEMU_SMC_ATF_ENTRY) { - if (rk3588_active_machine) { - uint64_t bl31_params = is_a64(env) ? env->xregs[2] : env->regs[2]; - - rk3588_patch_bl31_runtime(rk3588_active_machine); - rk3588_active_machine->firmware_atf_entered = true; - cpu_set_pc(CPU(cpu), RK3588_BL31_BASE); - if (is_a64(env)) { - env->xregs[0] = bl31_params; - env->xregs[1] = 0; - env->xregs[2] = 0; - env->xregs[3] = 0; - } else { - env->regs[0] = bl31_params; - env->regs[1] = 0; - env->regs[2] = 0; - env->regs[3] = 0; - } - arm_rebuild_hflags(env); + uint64_t bl31_params = is_a64(env) ? env->xregs[2] : env->regs[2]; + + rk3588_patch_bl31_runtime(s); + s->firmware_atf_entered = true; + cpu_set_pc(CPU(cpu), RK3588_BL31_BASE); + if (is_a64(env)) { + env->xregs[0] = bl31_params; + env->xregs[1] = 0; + env->xregs[2] = 0; + env->xregs[3] = 0; + } else { + env->regs[0] = bl31_params; + env->regs[1] = 0; + env->regs[2] = 0; + env->regs[3] = 0; } + arm_rebuild_hflags(env); return true; } if ((uint32_t)fn == RK3588_QEMU_SMC_BL31_EXIT) { - if (rk3588_active_machine) { - rk3588_firmware_handoff_to_uboot(rk3588_active_machine, cpu); - } + rk3588_firmware_handoff_to_uboot(s, cpu); return true; } if ((uint32_t)fn != RK3588_SCMI_SMC_ID) { return false; } - if (!rk3588_scmi_dev) { + if (!s->scmi) { /* Responder not yet realized - return NOT_SUPPORTED. */ if (is_a64(env)) { env->xregs[0] = (uint64_t)(int64_t)-1; @@ -2552,7 +2587,7 @@ static bool rk3588_smc_handler(ARMCPU *cpu) return true; } - rk3588_scmi_handle_smc(rk3588_scmi_dev); + rk3588_scmi_handle_smc(RK3588_SCMI(s->scmi)); /* a0 = 0 means "response is in shmem, fetch it". */ if (is_a64(env)) { env->xregs[0] = 0; @@ -2572,7 +2607,7 @@ static void rk3588_create_scmi(RK3588MachineState *s) sysbus_realize(sbd, &error_fatal); sysbus_mmio_map(sbd, 0, rk3588_memmap[RK3588_SCMI_SHMEM].base); - rk3588_scmi_dev = RK3588_SCMI(dev); + s->scmi = dev; arm_register_psci_smc_handler(rk3588_smc_handler); } @@ -2599,9 +2634,21 @@ static void rk3588_create_sdmmc(RK3588MachineState *s) static void rk3588_create_syscon_devices(RK3588MachineState *s) { - s->pmu0grf = rk3588_create_syscon(s, "pmu0-grf", RK3588_PMU0_GRF); - s->pmu1grf = rk3588_create_syscon(s, "pmu1-grf", RK3588_PMU1_GRF); - rk3588_create_syscon(s, "sys-grf", RK3588_SYS_GRF); + DeviceState *grf = qdev_new(TYPE_RK3588_GRF); + SysBusDevice *grf_sbd = SYS_BUS_DEVICE(grf); + + qdev_prop_set_uint64(grf, "ram-size", MACHINE(s)->ram_size); + qdev_prop_set_uint32(grf, "dram-type", s->board->dram_type); + object_property_add_child(OBJECT(s), "pmu-grf", OBJECT(grf)); + sysbus_realize(grf_sbd, &error_fatal); + sysbus_mmio_map(grf_sbd, RK3588_GRF_MMIO_PMU0, + rk3588_memmap[RK3588_PMU0_GRF].base); + sysbus_mmio_map(grf_sbd, RK3588_GRF_MMIO_PMU1, + rk3588_memmap[RK3588_PMU1_GRF].base); + sysbus_mmio_map(grf_sbd, RK3588_GRF_MMIO_SYS, + rk3588_memmap[RK3588_SYS_GRF].base); + object_unref(OBJECT(grf)); + rk3588_create_syscon(s, "php-grf", RK3588_PHP_GRF); rk3588_create_syscon(s, "usb-grf", RK3588_USB_GRF); rk3588_create_syscon(s, "pmu1-ioc", RK3588_PMU1_IOC); @@ -2609,8 +2656,40 @@ static void rk3588_create_syscon_devices(RK3588MachineState *s) rk3588_create_syscon(s, "bus-ioc", RK3588_BUS_IOC); rk3588_create_syscon(s, "firewall-ddr", RK3588_FIREWALL_DDR); rk3588_create_syscon(s, "firewall-sysmem", RK3588_FIREWALL_SYSMEM); - rk3588_seed_dram_info(s); - rk3588_seed_firmware_sysregs(s); +} + +static void rk3588_create_crypto(RK3588MachineState *s) +{ + const RK3588FirmwareProfile *profile = s->board->firmware_profile; + SysBusDevice *sbd; + + if (!profile || !profile->crypto_v2_sha256) { + return; + } + + s->crypto = qdev_new(TYPE_ROCKCHIP_CRYPTO_V2); + object_property_add_child(OBJECT(s), "crypto", OBJECT(s->crypto)); + sbd = SYS_BUS_DEVICE(s->crypto); + sysbus_realize(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, rk3588_memmap[RK3588_CRYPTO].base); +} + +static void rk3588_create_secure_otp(RK3588MachineState *s) +{ + const RK3588FirmwareProfile *profile = s->board->firmware_profile; + SysBusDevice *sbd; + + if (!profile || !profile->unfused_secure_otp) { + return; + } + + s->secure_otp = qdev_new(TYPE_RK3588_SECURE_OTP); + object_property_add_child(OBJECT(s), "secure-otp", + OBJECT(s->secure_otp)); + sbd = SYS_BUS_DEVICE(s->secure_otp); + sysbus_realize(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, RK3588_SECURE_OTP_BASE); + object_unref(OBJECT(s->secure_otp)); } static void rk3588_init(MachineState *machine) @@ -2634,6 +2713,7 @@ static void rk3588_init(MachineState *machine) rk3588_create_cpus(s); rk3588_create_low_memory(s); + rk3588_create_atf_ddr(s); rk3588_create_firmware_mmio(s); memory_region_add_subregion(get_system_memory(), rk3588_memmap[RK3588_RAM].base, @@ -2645,8 +2725,11 @@ static void rk3588_init(MachineState *machine) rk3588_create_syscon_devices(s); rk3588_create_cru(s); rk3588_create_stimer(s); + rk3588_create_ddr(s); + rk3588_create_usb2_host(s); rk3588_create_scmi(s); - rk3588_active_machine = s; + rk3588_create_secure_otp(s); + rk3588_create_crypto(s); rk3588_create_uart(s); rk3588_create_sdhci(s); rk3588_create_sdmmc(s); @@ -2736,27 +2819,26 @@ static void rk3588_set_zvm_ram(Object *obj, bool value, Error **errp) s->zvm_ram = value; } -static void rk3588_machine_instance_init(Object *obj, - const RK3588BoardConfig *board) +void rk3588_machine_instance_configure(Object *obj, + const RK3588BoardConfig *board) { RK3588MachineState *s = RK3588_MACHINE(obj); + assert(board); + assert(board->dram_type && board->dram_type <= 0xf); + assert(!(board->gmac_mask & ~(BIT(0) | BIT(1)))); + assert(board->pcie3x4_num_lanes == 1 || + board->pcie3x4_num_lanes == 2 || + board->pcie3x4_num_lanes == 4); + assert(board->pcie3x2_num_lanes == 0 || + board->pcie3x2_num_lanes == 1 || + board->pcie3x2_num_lanes == 2); s->board = board; s->zvm_ram = board->default_zvm_ram; } -static void rk3588_evb_machine_instance_init(Object *obj) -{ - rk3588_machine_instance_init(obj, &rk3588_evb_board); -} - -static void rk3588s_roc_pc_machine_instance_init(Object *obj) -{ - rk3588_machine_instance_init(obj, &rk3588s_roc_pc_board); -} - -static void rk3588_machine_class_init(ObjectClass *oc, - const RK3588BoardConfig *board) +void rk3588_machine_class_configure(ObjectClass *oc, + const RK3588BoardConfig *board) { MachineClass *mc = MACHINE_CLASS(oc); @@ -2777,17 +2859,6 @@ static void rk3588_machine_class_init(ObjectClass *oc, "shared RAM windows"); } -static void rk3588_evb_machine_class_init(ObjectClass *oc, const void *data) -{ - rk3588_machine_class_init(oc, &rk3588_evb_board); -} - -static void rk3588s_roc_pc_machine_class_init(ObjectClass *oc, - const void *data) -{ - rk3588_machine_class_init(oc, &rk3588s_roc_pc_board); -} - static const TypeInfo rk3588_machine_typeinfo = { .name = TYPE_RK3588_MACHINE, .parent = TYPE_MACHINE, @@ -2796,25 +2867,9 @@ static const TypeInfo rk3588_machine_typeinfo = { .interfaces = aarch64_machine_interfaces, }; -static const TypeInfo rk3588_evb_machine_typeinfo = { - .name = TYPE_RK3588_EVB_MACHINE, - .parent = TYPE_RK3588_MACHINE, - .class_init = rk3588_evb_machine_class_init, - .instance_init = rk3588_evb_machine_instance_init, -}; - -static const TypeInfo rk3588s_roc_pc_machine_typeinfo = { - .name = TYPE_RK3588S_ROC_PC_MACHINE, - .parent = TYPE_RK3588_MACHINE, - .class_init = rk3588s_roc_pc_machine_class_init, - .instance_init = rk3588s_roc_pc_machine_instance_init, -}; - static void rk3588_machine_init_register_types(void) { type_register_static(&rk3588_machine_typeinfo); - type_register_static(&rk3588_evb_machine_typeinfo); - type_register_static(&rk3588s_roc_pc_machine_typeinfo); } type_init(rk3588_machine_init_register_types) diff --git a/hw/arm/rk3588_evb.c b/hw/arm/rk3588_evb.c new file mode 100644 index 000000000000..732c62a56c32 --- /dev/null +++ b/hw/arm/rk3588_evb.c @@ -0,0 +1,59 @@ +/* + * Rockchip RK3588 EVB machine + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/module.h" +#include "rk3588-internal.h" + +#define TYPE_RK3588_EVB_MACHINE MACHINE_TYPE_NAME("rk3588-evb") + +static const char * const rk3588_evb_compatible[] = { + "qemu,rk3588-evb", + "rockchip,rk3588-evb1-v10", + "rockchip,rk3588", +}; + +static const RK3588BoardConfig rk3588_evb_board = { + .machine_name = "rk3588-evb", + .desc = "Rockchip RK3588 EVB (minimal)", + .ram_id = "rk3588-evb.ram", + .fdt_model = "QEMU Rockchip RK3588 EVB", + .fdt_compatible = rk3588_evb_compatible, + .fdt_compatible_count = ARRAY_SIZE(rk3588_evb_compatible), + .firmware_sd_unit = 0, + .brom_bootsource = RK3588_BROM_BOOTSOURCE_EMMC, + .dram_type = RK3588_DRAM_TYPE_LPDDR4X, + .gmac_mask = BIT(0) | BIT(1), + .pcie3x4_num_lanes = 4, + .default_zvm_ram = false, +}; + +static void rk3588_evb_machine_instance_init(Object *obj) +{ + rk3588_machine_instance_configure(obj, &rk3588_evb_board); +} + +static void rk3588_evb_machine_class_init(ObjectClass *oc, const void *data) +{ + rk3588_machine_class_configure(oc, &rk3588_evb_board); +} + +static const TypeInfo rk3588_evb_machine_typeinfo = { + .name = TYPE_RK3588_EVB_MACHINE, + .parent = TYPE_RK3588_MACHINE, + .class_init = rk3588_evb_machine_class_init, + .instance_init = rk3588_evb_machine_instance_init, +}; + +static void rk3588_evb_machine_register_types(void) +{ + type_register_static(&rk3588_evb_machine_typeinfo); +} + +type_init(rk3588_evb_machine_register_types) diff --git a/hw/arm/rk3588s_roc_pc.c b/hw/arm/rk3588s_roc_pc.c new file mode 100644 index 000000000000..0c2f0a18db22 --- /dev/null +++ b/hw/arm/rk3588s_roc_pc.c @@ -0,0 +1,62 @@ +/* + * Firefly ROC-RK3588S-PC machine + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/module.h" +#include "rk3588-internal.h" + +#define TYPE_RK3588S_ROC_PC_MACHINE MACHINE_TYPE_NAME("rk3588s-roc-pc") + +static const char * const rk3588s_roc_pc_compatible[] = { + "rockchip,rk3588s-firefly-roc-pc", + "firefly,rk3588s-roc-pc", + "rockchip,rk3588s", + "rockchip,rk3588", +}; + +static const RK3588BoardConfig rk3588s_roc_pc_board = { + .machine_name = "rk3588s-roc-pc", + .desc = "Firefly ROC-RK3588S-PC", + .ram_id = "rk3588s-roc-pc.ram", + .fdt_model = "Firefly ROC-RK3588S-PC", + .fdt_compatible = rk3588s_roc_pc_compatible, + .fdt_compatible_count = ARRAY_SIZE(rk3588s_roc_pc_compatible), + .firmware_sd_unit = 2, + .brom_bootsource = RK3588_BROM_BOOTSOURCE_SD, + .dram_type = RK3588_DRAM_TYPE_LPDDR4X, + .gmac_mask = BIT(0) | BIT(1), + .pcie3x4_num_lanes = 4, + .swap_gmac_aliases = true, + .default_zvm_ram = true, +}; + +static void rk3588s_roc_pc_machine_instance_init(Object *obj) +{ + rk3588_machine_instance_configure(obj, &rk3588s_roc_pc_board); +} + +static void rk3588s_roc_pc_machine_class_init(ObjectClass *oc, + const void *data) +{ + rk3588_machine_class_configure(oc, &rk3588s_roc_pc_board); +} + +static const TypeInfo rk3588s_roc_pc_machine_typeinfo = { + .name = TYPE_RK3588S_ROC_PC_MACHINE, + .parent = TYPE_RK3588_MACHINE, + .class_init = rk3588s_roc_pc_machine_class_init, + .instance_init = rk3588s_roc_pc_machine_instance_init, +}; + +static void rk3588s_roc_pc_machine_register_types(void) +{ + type_register_static(&rk3588s_roc_pc_machine_typeinfo); +} + +type_init(rk3588s_roc_pc_machine_register_types) diff --git a/hw/arm/rock5b_plus.c b/hw/arm/rock5b_plus.c new file mode 100644 index 000000000000..b117753e292d --- /dev/null +++ b/hw/arm/rock5b_plus.c @@ -0,0 +1,72 @@ +/* + * Radxa ROCK 5B+ machine + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "rk3588-internal.h" + +#define TYPE_ROCK_5B_PLUS_MACHINE MACHINE_TYPE_NAME("rock-5b-plus") + +static const char * const rock_5b_plus_compatible[] = { + "radxa,rock-5b-plus", + "rockchip,rk3588", +}; + +static const RK3588FirmwareProfile rock_5b_plus_firmware = { + .unfused_secure_otp = true, + .crypto_v2_sha256 = true, + .dynamic_fit_handoff = true, + .atags_core = true, + .fit_offset = 0x800000, + .fit_alignment = 512, +}; + +static const RK3588BoardConfig rock_5b_plus_board = { + .machine_name = "rock-5b-plus", + .desc = "Radxa ROCK 5B+", + .ram_id = "rock-5b-plus.ram", + .fdt_model = "Radxa ROCK 5B+", + .fdt_compatible = rock_5b_plus_compatible, + .fdt_compatible_count = ARRAY_SIZE(rock_5b_plus_compatible), + .firmware_sd_unit = 0, + .brom_bootsource = RK3588_BROM_BOOTSOURCE_EMMC, + .dram_type = RK3588_DRAM_TYPE_LPDDR5, + .gmac_mask = 0, + .pcie3x4_num_lanes = 2, + .pcie3x2_num_lanes = 2, + .pcie3x4_link_down = true, + .pcie3x2_link_down = true, + .swap_gmac_aliases = false, + .default_zvm_ram = false, + .firmware_profile = &rock_5b_plus_firmware, +}; + +static void rock_5b_plus_machine_instance_init(Object *obj) +{ + rk3588_machine_instance_configure(obj, &rock_5b_plus_board); +} + +static void rock_5b_plus_machine_class_init(ObjectClass *oc, + const void *data) +{ + rk3588_machine_class_configure(oc, &rock_5b_plus_board); +} + +static const TypeInfo rock_5b_plus_machine_typeinfo = { + .name = TYPE_ROCK_5B_PLUS_MACHINE, + .parent = TYPE_RK3588_MACHINE, + .class_init = rock_5b_plus_machine_class_init, + .instance_init = rock_5b_plus_machine_instance_init, +}; + +static void rock_5b_plus_machine_register_types(void) +{ + type_register_static(&rock_5b_plus_machine_typeinfo); +} + +type_init(rock_5b_plus_machine_register_types) diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index 34512f6f4996..cc28d713c3ee 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -46,6 +46,10 @@ config PL310 config ROCKCHIP_SYSCON bool +config ROCKCHIP_CRYPTO_V2 + bool + select REGISTER + config PHYTIUM_DDR_CTRL bool @@ -202,9 +206,23 @@ config NXP_S32_MC_ME config LED bool +config RK3588_ATF_DDR + bool + config RK3588_CRU bool +config RK3588_DDR + bool + select REGISTER + +config RK3588_FIRMWARE_MMIO + bool + +config RK3588_GRF + bool + select REGISTER + config RK3588_SCMI bool diff --git a/hw/misc/meson.build b/hw/misc/meson.build index a1953b05acd1..e65b567b63e9 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -10,6 +10,7 @@ system_ss.add(when: 'CONFIG_EMPTY_SLOT', if_true: files('empty_slot.c')) system_ss.add(when: 'CONFIG_LED', if_true: files('led.c')) system_ss.add(when: 'CONFIG_PVPANIC_COMMON', if_true: files('pvpanic.c')) system_ss.add(when: 'CONFIG_ROCKCHIP_SYSCON', if_true: files('rockchip_syscon.c')) +system_ss.add(when: 'CONFIG_ROCKCHIP_CRYPTO_V2', if_true: files('rockchip_crypto_v2.c')) system_ss.add(when: 'CONFIG_PHYTIUM_DDR_CTRL', if_true: files('phytium-ddr-ctrl.c')) system_ss.add(when: 'CONFIG_PHYTIUM_SCP_MAILBOX', if_true: files('phytium-scp-mailbox.c')) @@ -181,7 +182,11 @@ system_ss.add(when: 'CONFIG_AVR_POWER', if_true: files('avr_power.c')) system_ss.add(when: 'CONFIG_MAC_VIA', if_true: files('mac_via.c')) +system_ss.add(when: 'CONFIG_RK3588_ATF_DDR', if_true: files('rk3588_atf_ddr.c')) system_ss.add(when: 'CONFIG_RK3588_CRU', if_true: files('rk3588_cru.c')) +system_ss.add(when: 'CONFIG_RK3588_DDR', if_true: files('rk3588_ddr.c')) +system_ss.add(when: 'CONFIG_RK3588_FIRMWARE_MMIO', if_true: files('rk3588_firmware_mmio.c')) +system_ss.add(when: 'CONFIG_RK3588_GRF', if_true: files('rk3588_grf.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')) diff --git a/hw/misc/rk3588_atf_ddr.c b/hw/misc/rk3588_atf_ddr.c new file mode 100644 index 000000000000..0cecf7a5fb35 --- /dev/null +++ b/hw/misc/rk3588_atf_ddr.c @@ -0,0 +1,200 @@ +/* + * Rockchip RK3588 ATF DDR runtime descriptor + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/misc/rk3588_atf_ddr.h" + +#include "migration/vmstate.h" +#include "qemu/bswap.h" +#include "qemu/module.h" + +#define RK3588_ATF_DDR_GLOBAL_PTR_ADDR 0x0008d0a8ULL +#define RK3588_ATF_TIMER_PTR_ADDR 0x0008d0b0ULL +#define RK3588_ATF_TIMER_TABLE_ADDR 0x0008d0b8ULL +#define RK3588_ATF_TIMER_COUNTER_ADDR 0x00062054ULL +#define RK3588_ATF_DDR_DESCRIPTOR_ADDR 0x0008fd20ULL +#define RK3588_ATF_DDR_CHANNEL_TABLE_ADDR 0x0008fe00ULL + +#define RK3588_ATF_DDR_GLOBAL_PTR_OFFSET \ + (RK3588_ATF_DDR_GLOBAL_PTR_ADDR - RK3588_ATF_DDR_RUNTIME_BASE) +#define RK3588_ATF_TIMER_PTR_OFFSET \ + (RK3588_ATF_TIMER_PTR_ADDR - RK3588_ATF_DDR_RUNTIME_BASE) +#define RK3588_ATF_TIMER_TABLE_OFFSET \ + (RK3588_ATF_TIMER_TABLE_ADDR - RK3588_ATF_DDR_RUNTIME_BASE) +#define RK3588_ATF_DDR_DESCRIPTOR_OFFSET \ + (RK3588_ATF_DDR_DESCRIPTOR_ADDR - RK3588_ATF_DDR_RUNTIME_BASE) +#define RK3588_ATF_DDR_CHANNEL_TABLE_OFFSET \ + (RK3588_ATF_DDR_CHANNEL_TABLE_ADDR - RK3588_ATF_DDR_RUNTIME_BASE) + +#define RK3588_ATF_DDR_GLOBAL_BASE 0xfd000000ULL +#define RK3588_ATF_DDR_CHANNEL_BASE 0xfd100000ULL +#define RK3588_ATF_DDR_CHANNEL_STRIDE 0x00020000ULL +#define RK3588_ATF_DDR_CHANNELS 8 +#define RK3588_ATF_TIMER_US_HZ 1000000 +#define RK3588_ATF_TIMER_HZ 24000000 + +struct RK3588ATFDDRState { + SysBusDevice parent_obj; + + MemoryRegion mmio; + uint8_t runtime[RK3588_ATF_DDR_RUNTIME_SIZE]; +}; + +static void rk3588_atf_ddr_seed(RK3588ATFDDRState *s) +{ + memset(s->runtime, 0, sizeof(s->runtime)); + + /* + * Rockchip's closed BL31 keeps a DDR controller runtime descriptor in its + * SRAM BSS. SPL also uses this SRAM area, so provide the minimum stable + * descriptor the BL31 DDR save/restore code expects before it programs + * per-channel registers. + */ + stq_le_p(&s->runtime[RK3588_ATF_DDR_GLOBAL_PTR_OFFSET], + RK3588_ATF_DDR_DESCRIPTOR_ADDR); + stq_le_p(&s->runtime[RK3588_ATF_TIMER_PTR_OFFSET], + RK3588_ATF_TIMER_TABLE_ADDR); + stq_le_p(&s->runtime[RK3588_ATF_TIMER_TABLE_OFFSET], + RK3588_ATF_TIMER_COUNTER_ADDR); + stl_le_p(&s->runtime[RK3588_ATF_TIMER_TABLE_OFFSET + 0x8], + RK3588_ATF_TIMER_US_HZ); + stl_le_p(&s->runtime[RK3588_ATF_TIMER_TABLE_OFFSET + 0xc], + RK3588_ATF_TIMER_HZ); + stq_le_p(&s->runtime[RK3588_ATF_DDR_DESCRIPTOR_OFFSET], + RK3588_ATF_DDR_GLOBAL_BASE); + stq_le_p(&s->runtime[RK3588_ATF_DDR_DESCRIPTOR_OFFSET + 0x20], + RK3588_ATF_DDR_CHANNEL_TABLE_ADDR); + + for (unsigned int i = 0; i < RK3588_ATF_DDR_CHANNELS; i++) { + stq_le_p(&s->runtime[ + RK3588_ATF_DDR_CHANNEL_TABLE_OFFSET + i * sizeof(uint64_t)], + RK3588_ATF_DDR_CHANNEL_BASE + + i * RK3588_ATF_DDR_CHANNEL_STRIDE); + } +} + +static uint64_t rk3588_atf_ddr_read(void *opaque, hwaddr offset, + unsigned size) +{ + RK3588ATFDDRState *s = opaque; + + if (size > sizeof(uint64_t) || + offset > RK3588_ATF_DDR_RUNTIME_SIZE - size) { + return 0; + } + + switch (size) { + case 1: + return s->runtime[offset]; + case 2: + return lduw_le_p(&s->runtime[offset]); + case 4: + return ldl_le_p(&s->runtime[offset]); + case 8: + return ldq_le_p(&s->runtime[offset]); + default: + return 0; + } +} + +static void rk3588_atf_ddr_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + RK3588ATFDDRState *s = opaque; + + if (size > sizeof(uint64_t) || + offset > RK3588_ATF_DDR_RUNTIME_SIZE - size) { + return; + } + + switch (size) { + case 1: + s->runtime[offset] = value; + break; + case 2: + stw_le_p(&s->runtime[offset], value); + break; + case 4: + stl_le_p(&s->runtime[offset], value); + break; + case 8: + stq_le_p(&s->runtime[offset], value); + break; + default: + return; + } + + rk3588_atf_ddr_seed(s); +} + +static const MemoryRegionOps rk3588_atf_ddr_ops = { + .read = rk3588_atf_ddr_read, + .write = rk3588_atf_ddr_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 8, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + }, +}; + +static void rk3588_atf_ddr_reset_hold(Object *obj, ResetType type) +{ + RK3588ATFDDRState *s = RK3588_ATF_DDR(obj); + + rk3588_atf_ddr_seed(s); +} + +static const VMStateDescription vmstate_rk3588_atf_ddr = { + .name = TYPE_RK3588_ATF_DDR, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(runtime, RK3588ATFDDRState, + RK3588_ATF_DDR_RUNTIME_SIZE), + VMSTATE_END_OF_LIST() + }, +}; + +static void rk3588_atf_ddr_init(Object *obj) +{ + RK3588ATFDDRState *s = RK3588_ATF_DDR(obj); + + rk3588_atf_ddr_seed(s); + memory_region_init_io(&s->mmio, obj, &rk3588_atf_ddr_ops, s, + TYPE_RK3588_ATF_DDR, + RK3588_ATF_DDR_RUNTIME_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); +} + +static void rk3588_atf_ddr_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + ResettableClass *rc = RESETTABLE_CLASS(oc); + + dc->vmsd = &vmstate_rk3588_atf_ddr; + rc->phases.hold = rk3588_atf_ddr_reset_hold; +} + +static const TypeInfo rk3588_atf_ddr_info = { + .name = TYPE_RK3588_ATF_DDR, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RK3588ATFDDRState), + .instance_init = rk3588_atf_ddr_init, + .class_init = rk3588_atf_ddr_class_init, +}; + +static void rk3588_atf_ddr_register_types(void) +{ + type_register_static(&rk3588_atf_ddr_info); +} + +type_init(rk3588_atf_ddr_register_types) diff --git a/hw/misc/rk3588_ddr.c b/hw/misc/rk3588_ddr.c new file mode 100644 index 000000000000..aae304ccf4c1 --- /dev/null +++ b/hw/misc/rk3588_ddr.c @@ -0,0 +1,581 @@ +/* + * Rockchip RK3588 DDR controller compatibility model + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" + +#include "hw/misc/rk3588_ddr.h" +#include "hw/core/register.h" +#include "migration/vmstate.h" +#include "qemu/bitops.h" +#include "qemu/module.h" + +#define RK3588_DDR_LEGACY_WORDS \ + (RK3588_DDR_LEGACY_MMIO_SIZE / sizeof(uint32_t)) +#define RK3588_DDR_LEGACY_PHY_WORDS \ + (RK3588_DDR_LEGACY_PHY_MMIO_SIZE / sizeof(uint32_t)) +#define RK3588_DDR_LEGACY_PHY_AUX_WORDS \ + (RK3588_DDR_LEGACY_PHY_AUX_MMIO_SIZE / sizeof(uint32_t)) +#define RK3588_DDR_GLOBAL_WORDS \ + (RK3588_DDR_GLOBAL_MMIO_SIZE / sizeof(uint32_t)) +#define RK3588_DDR_CHANNEL_WORDS \ + (RK3588_DDR_CHANNEL_MMIO_SIZE / sizeof(uint32_t)) +#define RK3588_DDRPHY_WORDS \ + (RK3588_DDRPHY_MMIO_SIZE / sizeof(uint32_t)) + +#define RK3588_DDR_CTRL_BUSY_MASK (BIT(31) | BIT(3)) +#define RK3588_DDR_STATUS_LOW_MASK 0x7 +#define RK3588_DDR_STATUS_READY BIT(0) +#define RK3588_DDR_STATUS_ACK BIT(31) +#define RK3588_DDR_CMD_START BIT(31) +#define RK3588_DDR_BUSY BIT(0) +#define RK3588_DDR_GATE_BUSY BIT(0) +#define RK3588_DDR_GATE_ENABLE BIT(5) +#define RK3588_DDR_CHANNEL_PHY_BUSY BIT(16) +#define RK3588_DDRPHY_STATUS_ACTIVE 0x3 + +REG32(DDR_CTRL, 0x00000) +REG32(DDR_CTRL_STATUS, 0x00004) +REG32(CHANNEL_STATUS, 0x00014) +REG32(CHANNEL_CMD, 0x00080) +REG32(CHANNEL_BUSY, 0x00090) +REG32(CHANNEL_GATE_CMD, 0x00510) +REG32(CHANNEL_GATE_STATUS, 0x00514) +REG32(CHANNEL_PHY_STATUS, 0x00b90) +REG32(CHANNEL_GATE_CTRL, 0x00c80) + +REG32(CHANNEL_STATUS_ALIAS, 0x10014) +REG32(CHANNEL_CMD_ALIAS, 0x10080) +REG32(CHANNEL_BUSY_ALIAS, 0x10090) +REG32(CHANNEL_GATE_CMD_ALIAS, 0x10510) +REG32(CHANNEL_GATE_STATUS_ALIAS, 0x10514) +REG32(CHANNEL_PHY_STATUS_ALIAS, 0x10b90) +REG32(CHANNEL_GATE_CTRL_ALIAS, 0x10c80) + +REG32(DDRPHY_CTRL, 0x00154) +REG32(DDRPHY_STATUS, 0x00184) +REG32(DDR_PHY_GATE_CTRL, 0x000b0) + +struct RK3588DDRState { + SysBusDevice parent_obj; + + RegisterInfoArray *reg_arrays[RK3588_DDR_MMIO_COUNT]; + RegisterInfo *regs_info[RK3588_DDR_MMIO_COUNT]; + + uint32_t legacy_regs[RK3588_DDR_LEGACY_WINDOW_COUNT] + [RK3588_DDR_LEGACY_WORDS]; + uint32_t legacy_phy_regs[RK3588_DDR_LEGACY_PHY_WORDS]; + uint32_t legacy_phy_aux_regs[RK3588_DDR_LEGACY_PHY_AUX_WORDS]; + uint32_t global_regs[RK3588_DDR_GLOBAL_WORDS]; + uint32_t channel_regs[RK3588_DDR_CHANNEL_COUNT] + [RK3588_DDR_CHANNEL_WORDS]; + uint32_t ddrphy_regs[RK3588_DDRPHY_WORDS]; + + bool gate_done; + bool last_phy_gate; + bool gate_bit5_clear; +}; + +static uint64_t rk3588_ddr_ctrl_post_read(RegisterInfo *reg, uint64_t value) +{ + return value & ~RK3588_DDR_CTRL_BUSY_MASK; +} + +static uint64_t rk3588_ddr_ctrl_status_post_read(RegisterInfo *reg, + uint64_t value) +{ + return 0; +} + +static uint64_t rk3588_ddr_handshake_post_read(RegisterInfo *reg, + uint64_t value) +{ + bool request = value & RK3588_DDR_STATUS_READY; + + value &= ~RK3588_DDR_STATUS_LOW_MASK; + value |= RK3588_DDR_STATUS_READY; + if (request) { + value |= RK3588_DDR_STATUS_ACK; + } + + return value; +} + +static uint64_t rk3588_ddr_legacy_status_post_read(RegisterInfo *reg, + uint64_t value) +{ + value &= ~RK3588_DDR_STATUS_LOW_MASK; + return value | RK3588_DDR_STATUS_READY; +} + +static uint64_t rk3588_ddr_cmd_post_read(RegisterInfo *reg, uint64_t value) +{ + return value & ~RK3588_DDR_CMD_START; +} + +static uint64_t rk3588_ddr_busy_post_read(RegisterInfo *reg, uint64_t value) +{ + return value & ~RK3588_DDR_BUSY; +} + +static uint64_t rk3588_ddr_gate_status_post_read(RegisterInfo *reg, + uint64_t value) +{ + RK3588DDRState *s = RK3588_DDR(reg->opaque); + + if (s->gate_done || s->gate_bit5_clear) { + return value | RK3588_DDR_GATE_BUSY; + } + + return value & ~RK3588_DDR_GATE_BUSY; +} + +static uint64_t rk3588_ddr_channel_phy_post_read(RegisterInfo *reg, + uint64_t value) +{ + return value & ~RK3588_DDR_CHANNEL_PHY_BUSY; +} + +static uint64_t rk3588_ddrphy_status_post_read(RegisterInfo *reg, + uint64_t value) +{ + RK3588DDRState *s = RK3588_DDR(reg->opaque); + + value &= ~RK3588_DDRPHY_STATUS_ACTIVE; + value |= s->ddrphy_regs[R_DDRPHY_CTRL] & + RK3588_DDRPHY_STATUS_ACTIVE; + return value; +} + +static void rk3588_ddr_gate_cmd_post_write(RegisterInfo *reg, uint64_t value) +{ + RK3588DDRState *s = RK3588_DDR(reg->opaque); + + s->gate_bit5_clear = !(value & RK3588_DDR_GATE_ENABLE); + s->last_phy_gate = false; +} + +static void rk3588_ddr_gate_ctrl_post_write(RegisterInfo *reg, uint64_t value) +{ + RK3588DDRState *s = RK3588_DDR(reg->opaque); + + if (value == 0 && !s->last_phy_gate) { + s->gate_done = false; + } + s->last_phy_gate = false; +} + +static void rk3588_ddr_phy_gate_post_write(RegisterInfo *reg, uint64_t value) +{ + RK3588DDRState *s = RK3588_DDR(reg->opaque); + + if (!(value & RK3588_DDR_GATE_ENABLE)) { + s->gate_done = true; + s->last_phy_gate = true; + } else { + s->last_phy_gate = false; + } +} + +static const RegisterAccessInfo rk3588_ddr_legacy_regs_info[] = { + { .name = "DDR_PHY_GATE_CTRL", .addr = A_DDR_PHY_GATE_CTRL, + .reset = UINT32_MAX, .post_write = rk3588_ddr_phy_gate_post_write }, + { .name = "CHANNEL_STATUS", .addr = A_CHANNEL_STATUS, + .reset = UINT32_MAX, + .post_read = rk3588_ddr_legacy_status_post_read }, + { .name = "CHANNEL_CMD", .addr = A_CHANNEL_CMD, + .reset = UINT32_MAX, .post_read = rk3588_ddr_cmd_post_read }, + { .name = "CHANNEL_BUSY", .addr = A_CHANNEL_BUSY, + .reset = UINT32_MAX, .post_read = rk3588_ddr_busy_post_read }, + { .name = "CHANNEL_GATE_CMD", .addr = A_CHANNEL_GATE_CMD, + .reset = UINT32_MAX, .post_write = rk3588_ddr_gate_cmd_post_write }, + { .name = "CHANNEL_GATE_STATUS", .addr = A_CHANNEL_GATE_STATUS, + .reset = UINT32_MAX, + .post_read = rk3588_ddr_gate_status_post_read }, + { .name = "CHANNEL_PHY_STATUS", .addr = A_CHANNEL_PHY_STATUS, + .reset = UINT32_MAX, + .post_read = rk3588_ddr_channel_phy_post_read }, + { .name = "CHANNEL_GATE_CTRL", .addr = A_CHANNEL_GATE_CTRL, + .reset = UINT32_MAX, .post_write = rk3588_ddr_gate_ctrl_post_write }, +}; + +static const RegisterAccessInfo rk3588_ddr_ctrl_regs_info[] = { + { .name = "DDR_CTRL", .addr = A_DDR_CTRL, + .reset = UINT32_MAX, .post_read = rk3588_ddr_ctrl_post_read }, + { .name = "DDR_CTRL_STATUS", .addr = A_DDR_CTRL_STATUS, + .reset = UINT32_MAX, + .post_read = rk3588_ddr_ctrl_status_post_read }, + { .name = "CHANNEL_STATUS", .addr = A_CHANNEL_STATUS, + .reset = UINT32_MAX, .post_read = rk3588_ddr_handshake_post_read }, + { .name = "CHANNEL_CMD", .addr = A_CHANNEL_CMD, + .reset = UINT32_MAX, .post_read = rk3588_ddr_cmd_post_read }, + { .name = "CHANNEL_BUSY", .addr = A_CHANNEL_BUSY, + .reset = UINT32_MAX, .post_read = rk3588_ddr_busy_post_read }, + { .name = "CHANNEL_GATE_CMD", .addr = A_CHANNEL_GATE_CMD, + .reset = UINT32_MAX, .post_write = rk3588_ddr_gate_cmd_post_write }, + { .name = "CHANNEL_GATE_STATUS", .addr = A_CHANNEL_GATE_STATUS, + .reset = UINT32_MAX, + .post_read = rk3588_ddr_gate_status_post_read }, + { .name = "CHANNEL_PHY_STATUS", .addr = A_CHANNEL_PHY_STATUS, + .reset = UINT32_MAX, + .post_read = rk3588_ddr_channel_phy_post_read }, + { .name = "CHANNEL_GATE_CTRL", .addr = A_CHANNEL_GATE_CTRL, + .reset = UINT32_MAX, .post_write = rk3588_ddr_gate_ctrl_post_write }, + { .name = "CHANNEL_STATUS_ALIAS", .addr = A_CHANNEL_STATUS_ALIAS, + .reset = UINT32_MAX, + .post_read = rk3588_ddr_legacy_status_post_read }, + { .name = "CHANNEL_CMD_ALIAS", .addr = A_CHANNEL_CMD_ALIAS, + .reset = UINT32_MAX, .post_read = rk3588_ddr_cmd_post_read }, + { .name = "CHANNEL_BUSY_ALIAS", .addr = A_CHANNEL_BUSY_ALIAS, + .reset = UINT32_MAX, .post_read = rk3588_ddr_busy_post_read }, + { .name = "CHANNEL_GATE_CMD_ALIAS", .addr = A_CHANNEL_GATE_CMD_ALIAS, + .reset = UINT32_MAX, .post_write = rk3588_ddr_gate_cmd_post_write }, + { .name = "CHANNEL_GATE_STATUS_ALIAS", + .addr = A_CHANNEL_GATE_STATUS_ALIAS, .reset = UINT32_MAX, + .post_read = rk3588_ddr_gate_status_post_read }, + { .name = "CHANNEL_PHY_STATUS_ALIAS", + .addr = A_CHANNEL_PHY_STATUS_ALIAS, .reset = UINT32_MAX, + .post_read = rk3588_ddr_channel_phy_post_read }, + { .name = "CHANNEL_GATE_CTRL_ALIAS", .addr = A_CHANNEL_GATE_CTRL_ALIAS, + .reset = UINT32_MAX, .post_write = rk3588_ddr_gate_ctrl_post_write }, +}; + +static const RegisterAccessInfo rk3588_ddrphy_regs_info[] = { + { .name = "DDRPHY_CTRL", .addr = A_DDRPHY_CTRL, + .reset = UINT32_MAX }, + { .name = "DDRPHY_STATUS", .addr = A_DDRPHY_STATUS, + .reset = UINT32_MAX, .post_read = rk3588_ddrphy_status_post_read }, +}; + +static unsigned int rk3588_ddr_window_index(RK3588DDRState *s, + RegisterInfoArray *reg_array) +{ + for (unsigned int i = 0; i < RK3588_DDR_MMIO_COUNT; i++) { + if (s->reg_arrays[i] == reg_array) { + return i; + } + } + + g_assert_not_reached(); +} + +static uint32_t *rk3588_ddr_window_regs(RK3588DDRState *s, + unsigned int index) +{ + if (index < RK3588_DDR_LEGACY_WINDOW_COUNT) { + return s->legacy_regs[index]; + } + if (index == RK3588_DDR_MMIO_LEGACY_PHY) { + return s->legacy_phy_regs; + } + if (index == RK3588_DDR_MMIO_LEGACY_PHY_AUX) { + return s->legacy_phy_aux_regs; + } + if (index == RK3588_DDR_MMIO_GLOBAL) { + return s->global_regs; + } + if (index >= RK3588_DDR_MMIO_CHANNEL0 && + index < RK3588_DDR_MMIO_DDRPHY) { + return s->channel_regs[index - RK3588_DDR_MMIO_CHANNEL0]; + } + if (index == RK3588_DDR_MMIO_DDRPHY) { + return s->ddrphy_regs; + } + g_assert_not_reached(); +} + +static uint64_t rk3588_ddr_window_size(unsigned int index) +{ + if (index < RK3588_DDR_LEGACY_WINDOW_COUNT) { + return RK3588_DDR_LEGACY_MMIO_SIZE; + } + if (index == RK3588_DDR_MMIO_LEGACY_PHY) { + return RK3588_DDR_LEGACY_PHY_MMIO_SIZE; + } + if (index == RK3588_DDR_MMIO_LEGACY_PHY_AUX) { + return RK3588_DDR_LEGACY_PHY_AUX_MMIO_SIZE; + } + if (index == RK3588_DDR_MMIO_GLOBAL) { + return RK3588_DDR_GLOBAL_MMIO_SIZE; + } + if (index >= RK3588_DDR_MMIO_CHANNEL0 && + index < RK3588_DDR_MMIO_DDRPHY) { + return RK3588_DDR_CHANNEL_MMIO_SIZE; + } + if (index == RK3588_DDR_MMIO_DDRPHY) { + return RK3588_DDRPHY_MMIO_SIZE; + } + g_assert_not_reached(); +} + +static RegisterInfo *rk3588_ddr_find_register(RegisterInfoArray *reg_array, + hwaddr addr) +{ + for (unsigned int i = 0; i < reg_array->num_elements; i++) { + RegisterInfo *reg = reg_array->r[i]; + + if (reg->access->addr == addr) { + return reg; + } + } + + return NULL; +} + +static bool rk3588_ddr_access_valid(hwaddr addr, unsigned int size, + uint64_t window_size) +{ + if (size != 1 && size != 2 && size != 4 && size != 8) { + return false; + } + + return addr <= window_size && size <= window_size - addr; +} + +static uint64_t rk3588_ddr_raw_read(uint32_t *regs, hwaddr addr, + unsigned int size) +{ + uint64_t value = 0; + + for (unsigned int i = 0; i < size; i++) { + unsigned int shift = ((addr + i) & 3) * 8; + uint8_t byte = regs[(addr + i) / sizeof(uint32_t)] >> shift; + + value |= (uint64_t)byte << (i * 8); + } + + return value; +} + +static void rk3588_ddr_raw_write(uint32_t *regs, hwaddr addr, + uint64_t value, unsigned int size) +{ + for (unsigned int i = 0; i < size; i++) { + unsigned int index = (addr + i) / sizeof(uint32_t); + unsigned int shift = ((addr + i) & 3) * 8; + uint32_t mask = UINT32_C(0xff) << shift; + + regs[index] = (regs[index] & ~mask) | + (((value >> (i * 8)) & 0xff) << shift); + } +} + +static uint64_t rk3588_ddr_read(void *opaque, hwaddr addr, unsigned int size) +{ + RegisterInfoArray *reg_array = opaque; + RK3588DDRState *s = RK3588_DDR(register_array_get_owner(reg_array)); + unsigned int index = rk3588_ddr_window_index(s, reg_array); + uint64_t window_size = rk3588_ddr_window_size(index); + + if (!rk3588_ddr_access_valid(addr, size, window_size)) { + return 0; + } + + if (index <= RK3588_DDR_MMIO_LEGACY_PHY_AUX && + size == sizeof(uint32_t) && + (addr & 0xfff) == A_DDRPHY_STATUS) { + uint32_t *regs = rk3588_ddr_window_regs(s, index); + uint32_t value = rk3588_ddr_raw_read(regs, addr, size); + hwaddr ctrl = addr - A_DDRPHY_STATUS + A_DDRPHY_CTRL; + + value &= ~RK3588_DDRPHY_STATUS_ACTIVE; + value |= rk3588_ddr_raw_read(regs, ctrl, size) & + RK3588_DDRPHY_STATUS_ACTIVE; + return value; + } + + if (size == 4 && rk3588_ddr_find_register(reg_array, addr)) { + return register_read_memory(reg_array, addr, size); + } + + return rk3588_ddr_raw_read(rk3588_ddr_window_regs(s, index), addr, size); +} + +static void rk3588_ddr_write(void *opaque, hwaddr addr, uint64_t value, + unsigned int size) +{ + RegisterInfoArray *reg_array = opaque; + RK3588DDRState *s = RK3588_DDR(register_array_get_owner(reg_array)); + unsigned int index = rk3588_ddr_window_index(s, reg_array); + uint64_t window_size = rk3588_ddr_window_size(index); + RegisterInfo *reg; + + if (!rk3588_ddr_access_valid(addr, size, window_size)) { + return; + } + + reg = size == 4 ? rk3588_ddr_find_register(reg_array, addr) : NULL; + if (size == 4 && + (!reg || (reg->access->post_write != rk3588_ddr_gate_cmd_post_write && + reg->access->post_write != rk3588_ddr_gate_ctrl_post_write && + reg->access->post_write != rk3588_ddr_phy_gate_post_write))) { + s->last_phy_gate = false; + } + + if (reg) { + register_write_memory(reg_array, addr, value, size); + return; + } + + rk3588_ddr_raw_write(rk3588_ddr_window_regs(s, index), addr, value, + size); +} + +static const MemoryRegionOps rk3588_ddr_ops = { + .read = rk3588_ddr_read, + .write = rk3588_ddr_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 8, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + }, +}; + +static void rk3588_ddr_reset_hold(Object *obj, ResetType type) +{ + RK3588DDRState *s = RK3588_DDR(obj); + + memset(s->legacy_regs, 0xff, sizeof(s->legacy_regs)); + memset(s->legacy_phy_regs, 0xff, sizeof(s->legacy_phy_regs)); + memset(s->legacy_phy_aux_regs, 0xff, + sizeof(s->legacy_phy_aux_regs)); + memset(s->global_regs, 0xff, sizeof(s->global_regs)); + memset(s->channel_regs, 0xff, sizeof(s->channel_regs)); + memset(s->ddrphy_regs, 0xff, sizeof(s->ddrphy_regs)); + + for (unsigned int i = 0; i < RK3588_DDR_MMIO_COUNT; i++) { + for (unsigned int j = 0; j < s->reg_arrays[i]->num_elements; j++) { + register_reset(s->reg_arrays[i]->r[j]); + } + } + + s->gate_done = false; + s->last_phy_gate = false; + s->gate_bit5_clear = false; +} + +static const VMStateDescription vmstate_rk3588_ddr = { + .name = TYPE_RK3588_DDR, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_2DARRAY(legacy_regs, RK3588DDRState, + RK3588_DDR_LEGACY_WINDOW_COUNT, + RK3588_DDR_LEGACY_WORDS), + VMSTATE_UINT32_ARRAY(legacy_phy_regs, RK3588DDRState, + RK3588_DDR_LEGACY_PHY_WORDS), + VMSTATE_UINT32_ARRAY(legacy_phy_aux_regs, RK3588DDRState, + RK3588_DDR_LEGACY_PHY_AUX_WORDS), + VMSTATE_UINT32_ARRAY(global_regs, RK3588DDRState, + RK3588_DDR_GLOBAL_WORDS), + VMSTATE_UINT32_2DARRAY(channel_regs, RK3588DDRState, + RK3588_DDR_CHANNEL_COUNT, + RK3588_DDR_CHANNEL_WORDS), + VMSTATE_UINT32_ARRAY(ddrphy_regs, RK3588DDRState, + RK3588_DDRPHY_WORDS), + VMSTATE_BOOL(gate_done, RK3588DDRState), + VMSTATE_BOOL(last_phy_gate, RK3588DDRState), + VMSTATE_BOOL(gate_bit5_clear, RK3588DDRState), + VMSTATE_END_OF_LIST() + }, +}; + +static void rk3588_ddr_init_window(RK3588DDRState *s, unsigned int index, + uint32_t *regs, uint64_t size, + const RegisterAccessInfo *regs_info, + unsigned int regs_count) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(s); + unsigned int max_index = 0; + + for (unsigned int i = 0; i < regs_count; i++) { + max_index = MAX(max_index, (unsigned int)(regs_info[i].addr / 4)); + } + + s->regs_info[index] = g_new0(RegisterInfo, max_index + 1); + s->reg_arrays[index] = register_init_block32( + DEVICE(s), regs_info, regs_count, s->regs_info[index], regs, + &rk3588_ddr_ops, false, size); + sysbus_init_mmio(sbd, &s->reg_arrays[index]->mem); +} + +static void rk3588_ddr_init(Object *obj) +{ + RK3588DDRState *s = RK3588_DDR(obj); + + for (unsigned int i = 0; i < RK3588_DDR_LEGACY_WINDOW_COUNT; i++) { + rk3588_ddr_init_window(s, i, s->legacy_regs[i], + RK3588_DDR_LEGACY_MMIO_SIZE, + rk3588_ddr_legacy_regs_info, + ARRAY_SIZE(rk3588_ddr_legacy_regs_info)); + } + rk3588_ddr_init_window(s, RK3588_DDR_MMIO_LEGACY_PHY, + s->legacy_phy_regs, + RK3588_DDR_LEGACY_PHY_MMIO_SIZE, + rk3588_ddr_legacy_regs_info, + ARRAY_SIZE(rk3588_ddr_legacy_regs_info)); + rk3588_ddr_init_window(s, RK3588_DDR_MMIO_LEGACY_PHY_AUX, + s->legacy_phy_aux_regs, + RK3588_DDR_LEGACY_PHY_AUX_MMIO_SIZE, + rk3588_ddr_legacy_regs_info, + ARRAY_SIZE(rk3588_ddr_legacy_regs_info)); + rk3588_ddr_init_window(s, RK3588_DDR_MMIO_GLOBAL, s->global_regs, + RK3588_DDR_GLOBAL_MMIO_SIZE, + rk3588_ddr_ctrl_regs_info, + ARRAY_SIZE(rk3588_ddr_ctrl_regs_info)); + + for (unsigned int i = 0; i < RK3588_DDR_CHANNEL_COUNT; i++) { + rk3588_ddr_init_window(s, RK3588_DDR_MMIO_CHANNEL(i), + s->channel_regs[i], + RK3588_DDR_CHANNEL_MMIO_SIZE, + rk3588_ddr_ctrl_regs_info, + ARRAY_SIZE(rk3588_ddr_ctrl_regs_info)); + } + + rk3588_ddr_init_window(s, RK3588_DDR_MMIO_DDRPHY, s->ddrphy_regs, + RK3588_DDRPHY_MMIO_SIZE, + rk3588_ddrphy_regs_info, + ARRAY_SIZE(rk3588_ddrphy_regs_info)); +} + +static void rk3588_ddr_finalize(Object *obj) +{ + RK3588DDRState *s = RK3588_DDR(obj); + + for (unsigned int i = 0; i < RK3588_DDR_MMIO_COUNT; i++) { + g_free(s->regs_info[i]); + } +} + +static void rk3588_ddr_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + ResettableClass *rc = RESETTABLE_CLASS(oc); + + dc->vmsd = &vmstate_rk3588_ddr; + rc->phases.hold = rk3588_ddr_reset_hold; +} + +static const TypeInfo rk3588_ddr_info = { + .name = TYPE_RK3588_DDR, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RK3588DDRState), + .instance_init = rk3588_ddr_init, + .instance_finalize = rk3588_ddr_finalize, + .class_init = rk3588_ddr_class_init, +}; + +static void rk3588_ddr_register_types(void) +{ + type_register_static(&rk3588_ddr_info); +} + +type_init(rk3588_ddr_register_types) diff --git a/hw/misc/rk3588_firmware_mmio.c b/hw/misc/rk3588_firmware_mmio.c new file mode 100644 index 000000000000..33e46ee40230 --- /dev/null +++ b/hw/misc/rk3588_firmware_mmio.c @@ -0,0 +1,71 @@ +/* + * Rockchip RK3588 vendor firmware MMIO compatibility region + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Rockchip vendor TPL, SPL and BL31 binaries touch undocumented SoC + * registers before the individual peripherals are initialized. Keep a + * low-priority, RAM-backed compatibility region for those accesses. Board + * code maps concrete peripheral devices over this region at higher priority. + */ + +#include "qemu/osdep.h" +#include "hw/misc/rk3588_firmware_mmio.h" + +#include "qapi/error.h" +#include "qemu/module.h" + +struct RK3588FirmwareMMIOState { + SysBusDevice parent_obj; + + MemoryRegion iomem; +}; + +static void rk3588_firmware_mmio_reset_hold(Object *obj, ResetType type) +{ + RK3588FirmwareMMIOState *s = RK3588_FIRMWARE_MMIO(obj); + + memset(memory_region_get_ram_ptr(&s->iomem), 0xff, + RK3588_FIRMWARE_MMIO_SIZE); +} + +static void rk3588_firmware_mmio_realize(DeviceState *dev, Error **errp) +{ + RK3588FirmwareMMIOState *s = RK3588_FIRMWARE_MMIO(dev); + + memory_region_init_ram(&s->iomem, OBJECT(s), TYPE_RK3588_FIRMWARE_MMIO, + RK3588_FIRMWARE_MMIO_SIZE, errp); + if (*errp) { + return; + } + + memset(memory_region_get_ram_ptr(&s->iomem), 0xff, + RK3588_FIRMWARE_MMIO_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); +} + +static void rk3588_firmware_mmio_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + ResettableClass *rc = RESETTABLE_CLASS(oc); + + dc->realize = rk3588_firmware_mmio_realize; + dc->user_creatable = false; + rc->phases.hold = rk3588_firmware_mmio_reset_hold; +} + +static const TypeInfo rk3588_firmware_mmio_info = { + .name = TYPE_RK3588_FIRMWARE_MMIO, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RK3588FirmwareMMIOState), + .class_init = rk3588_firmware_mmio_class_init, +}; + +static void rk3588_firmware_mmio_register_types(void) +{ + type_register_static(&rk3588_firmware_mmio_info); +} + +type_init(rk3588_firmware_mmio_register_types) diff --git a/hw/misc/rk3588_grf.c b/hw/misc/rk3588_grf.c new file mode 100644 index 000000000000..553c3e50f36b --- /dev/null +++ b/hw/misc/rk3588_grf.c @@ -0,0 +1,203 @@ +/* + * Rockchip RK3588 PMU General Register Files + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/misc/rk3588_grf.h" + +#include "hw/core/qdev-properties.h" +#include "hw/core/register.h" +#include "migration/vmstate.h" +#include "qemu/module.h" +#include "qemu/units.h" + +#define RK3588_GRF_REG_WORDS \ + (RK3588_GRF_MMIO_SIZE / sizeof(uint32_t)) + +#define RK3588_DDR_SYS_REG_VERSION 3 +#define RK3588_PMU0_WARM_BOOT_MAGIC 0x13579bdf +#define RK3588_SYS_CORE_STATUS 0xf0 + +REG32(PMU0_WARM_BOOT_MAGIC, 0x84) +REG32(PMU1_OS_REG2, 0x208) +REG32(PMU1_OS_REG3, 0x20c) +REG32(PMU1_OS_REG4, 0x210) +REG32(PMU1_OS_REG5, 0x214) +REG32(SYS_CORE_STATUS, 0x38c) + +struct RK3588GRFState { + SysBusDevice parent_obj; + + RegisterInfoArray *reg_array[RK3588_GRF_MMIO_COUNT]; + RegisterInfo regs_info[RK3588_GRF_MMIO_COUNT][RK3588_GRF_REG_WORDS]; + RegisterAccessInfo access_info[RK3588_GRF_MMIO_COUNT] + [RK3588_GRF_REG_WORDS]; + uint32_t regs[RK3588_GRF_MMIO_COUNT][RK3588_GRF_REG_WORDS]; + uint64_t ram_size; + uint32_t dram_type; +}; + +static uint64_t rk3588_grf_pre_write(RegisterInfo *reg, uint64_t value) +{ + uint32_t old = *(uint32_t *)reg->data; + uint32_t mask = value >> 16; + + if (!mask) { + return value; + } + + return (old & ~mask) | (value & mask); +} + +static const MemoryRegionOps rk3588_grf_ops = { + .read = register_read_memory, + .write = register_write_memory, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static uint32_t rk3588_grf_ddr_sys_reg2(uint64_t group_bytes, + uint32_t dram_type) +{ + uint64_t group_mb = MAX(group_bytes / MiB, 256); + unsigned int row = 13; + uint32_t row_delta, reg; + + while ((256ULL << (row - 13)) < group_mb && row < 17) { + row++; + } + + row_delta = row - 13; + reg = (dram_type & 7) << 13; + reg |= 1 << 28; + reg |= 1 << 9; + reg |= (row_delta & 3) << 6; + + return reg; +} + +static uint32_t rk3588_grf_ddr_sys_reg3(uint64_t group_bytes, + uint32_t dram_type) +{ + uint64_t group_mb = MAX(group_bytes / MiB, 256); + unsigned int row = 13; + uint32_t row_delta, reg; + + while ((256ULL << (row - 13)) < group_mb && row < 17) { + row++; + } + + row_delta = row - 13; + reg = RK3588_DDR_SYS_REG_VERSION << 28; + reg |= (dram_type >> 3) << 12; + reg |= ((row_delta >> 2) & 1) << 5; + + return reg; +} + +static void rk3588_grf_reset_hold(Object *obj, ResetType type) +{ + RK3588GRFState *s = RK3588_GRF(obj); + uint64_t group = MAX(s->ram_size / 2, 256 * MiB); + uint32_t sys_reg2 = rk3588_grf_ddr_sys_reg2(group, s->dram_type); + uint32_t sys_reg3 = rk3588_grf_ddr_sys_reg3(group, s->dram_type); + + s->access_info[RK3588_GRF_MMIO_PMU0] + [R_PMU0_WARM_BOOT_MAGIC].reset = + RK3588_PMU0_WARM_BOOT_MAGIC; + s->access_info[RK3588_GRF_MMIO_PMU1][R_PMU1_OS_REG2].reset = sys_reg2; + s->access_info[RK3588_GRF_MMIO_PMU1][R_PMU1_OS_REG3].reset = sys_reg3; + s->access_info[RK3588_GRF_MMIO_PMU1][R_PMU1_OS_REG4].reset = sys_reg2; + s->access_info[RK3588_GRF_MMIO_PMU1][R_PMU1_OS_REG5].reset = sys_reg3; + s->access_info[RK3588_GRF_MMIO_SYS][R_SYS_CORE_STATUS].reset = + RK3588_SYS_CORE_STATUS; + s->access_info[RK3588_GRF_MMIO_SYS][R_SYS_CORE_STATUS].ro = UINT32_MAX; + + for (unsigned int bank = 0; bank < RK3588_GRF_MMIO_COUNT; bank++) { + for (unsigned int reg = 0; reg < RK3588_GRF_REG_WORDS; reg++) { + register_reset(&s->regs_info[bank][reg]); + } + } +} + +static const VMStateDescription vmstate_rk3588_grf = { + .name = TYPE_RK3588_GRF, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_2DARRAY(regs, RK3588GRFState, + RK3588_GRF_MMIO_COUNT, + RK3588_GRF_REG_WORDS), + VMSTATE_END_OF_LIST(), + }, +}; + +static void rk3588_grf_init(Object *obj) +{ + RK3588GRFState *s = RK3588_GRF(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + static const char * const bank_names[] = { + [RK3588_GRF_MMIO_PMU0] = "PMU0_GRF", + [RK3588_GRF_MMIO_PMU1] = "PMU1_GRF", + [RK3588_GRF_MMIO_SYS] = "SYS_GRF", + }; + + for (unsigned int bank = 0; bank < RK3588_GRF_MMIO_COUNT; bank++) { + for (unsigned int reg = 0; reg < RK3588_GRF_REG_WORDS; reg++) { + RegisterAccessInfo *access = &s->access_info[bank][reg]; + + access->name = bank_names[bank]; + access->addr = reg * sizeof(uint32_t); + access->pre_write = rk3588_grf_pre_write; + } + + s->reg_array[bank] = register_init_block32( + DEVICE(obj), s->access_info[bank], RK3588_GRF_REG_WORDS, + s->regs_info[bank], s->regs[bank], &rk3588_grf_ops, false, + RK3588_GRF_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->reg_array[bank]->mem); + } +} + +static const Property rk3588_grf_properties[] = { + DEFINE_PROP_UINT64("ram-size", RK3588GRFState, ram_size, 0), + DEFINE_PROP_UINT32("dram-type", RK3588GRFState, dram_type, 0), +}; + +static void rk3588_grf_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + ResettableClass *rc = RESETTABLE_CLASS(oc); + + dc->vmsd = &vmstate_rk3588_grf; + device_class_set_props(dc, rk3588_grf_properties); + rc->phases.hold = rk3588_grf_reset_hold; +} + +static const TypeInfo rk3588_grf_info = { + .name = TYPE_RK3588_GRF, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RK3588GRFState), + .instance_init = rk3588_grf_init, + .class_init = rk3588_grf_class_init, +}; + +static void rk3588_grf_register_types(void) +{ + type_register_static(&rk3588_grf_info); +} + +type_init(rk3588_grf_register_types) diff --git a/hw/misc/rockchip_crypto_v2.c b/hw/misc/rockchip_crypto_v2.c new file mode 100644 index 000000000000..1daeae6ff0a1 --- /dev/null +++ b/hw/misc/rockchip_crypto_v2.c @@ -0,0 +1,439 @@ +/* + * Rockchip Crypto V2 SHA-256 engine + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "crypto/hash.h" +#include "hw/core/register.h" +#include "hw/misc/rockchip_crypto_v2.h" +#include "migration/vmstate.h" +#include "qapi/error.h" +#include "qemu/bswap.h" +#include "qemu/log.h" +#include "qemu/main-loop.h" +#include "qemu/module.h" +#include "qemu/units.h" +#include "system/dma.h" + +#define ROCKCHIP_CRYPTO_V2_REG_WORDS \ + (ROCKCHIP_CRYPTO_V2_MMIO_SIZE / sizeof(uint32_t)) +#define ROCKCHIP_CRYPTO_V2_MAX_DMA (32 * MiB) + +REG32(RST_CTL, 0x004) + FIELD(RST_CTL, SW_CC_RESET, 0, 1) + FIELD(RST_CTL, SW_PKA_RESET, 2, 1) +REG32(DMA_INT_EN, 0x008) +REG32(DMA_INT_ST, 0x00c) + FIELD(DMA_INT_ST, SRC_ITEM_DONE, 2, 1) + FIELD(DMA_INT_ST, SRC_ERR, 4, 1) + FIELD(DMA_INT_ST, LIST_ERR, 5, 1) +REG32(DMA_CTL, 0x010) + FIELD(DMA_CTL, START, 0, 1) + FIELD(DMA_CTL, RESTART, 1, 1) +REG32(DMA_LLI_ADDR, 0x014) +REG32(FIFO_CTL, 0x040) + FIELD(FIFO_CTL, DOIN_BYTESWAP, 0, 1) + FIELD(FIFO_CTL, DOUT_BYTESWAP, 1, 1) +REG32(HASH_CTL, 0x048) + FIELD(HASH_CTL, ENABLE, 0, 1) + FIELD(HASH_CTL, HW_PAD_ENABLE, 2, 1) + FIELD(HASH_CTL, MODE, 4, 4) +REG32(HASH_DOUT_0, 0x3a0) +REG32(HASH_DOUT_1, 0x3a4) +REG32(HASH_DOUT_2, 0x3a8) +REG32(HASH_DOUT_3, 0x3ac) +REG32(HASH_DOUT_4, 0x3b0) +REG32(HASH_DOUT_5, 0x3b4) +REG32(HASH_DOUT_6, 0x3b8) +REG32(HASH_DOUT_7, 0x3bc) +REG32(HASH_DOUT_8, 0x3c0) +REG32(HASH_DOUT_9, 0x3c4) +REG32(HASH_DOUT_10, 0x3c8) +REG32(HASH_DOUT_11, 0x3cc) +REG32(HASH_DOUT_12, 0x3d0) +REG32(HASH_DOUT_13, 0x3d4) +REG32(HASH_DOUT_14, 0x3d8) +REG32(HASH_DOUT_15, 0x3dc) +REG32(HASH_VALID, 0x3e4) + FIELD(HASH_VALID, VALID, 0, 1) + +#define ROCKCHIP_CRYPTO_WRITE_MASK_SHIFT 16 +#define ROCKCHIP_CRYPTO_HASH_MODE_SHA256 2 +#define ROCKCHIP_CRYPTO_LLI_USER_CIPHER_START BIT(0) +#define ROCKCHIP_CRYPTO_LLI_USER_STRING_START BIT(1) +#define ROCKCHIP_CRYPTO_LLI_USER_STRING_LAST BIT(2) +#define ROCKCHIP_CRYPTO_LLI_DMA_LAST BIT(0) +#define ROCKCHIP_CRYPTO_LLI_DMA_SRC_DONE BIT(10) + +typedef struct QEMU_PACKED RockchipCryptoV2LLI { + uint32_t src_addr; + uint32_t src_len; + uint32_t dst_addr; + uint32_t dst_len; + uint32_t user_define; + uint32_t reserved; + uint32_t dma_ctrl; + uint32_t next_addr; +} RockchipCryptoV2LLI; + +struct RockchipCryptoV2State { + SysBusDevice parent_obj; + + RegisterInfoArray *reg_array; + uint32_t regs[ROCKCHIP_CRYPTO_V2_REG_WORDS]; + RegisterInfo regs_info[ROCKCHIP_CRYPTO_V2_REG_WORDS]; + QEMUBH *dma_bh; + bool dma_pending; + bool start_requested; + bool restart_requested; + bool reset_requested; +}; + +static void rockchip_crypto_v2_clear_hash(RockchipCryptoV2State *s) +{ + s->regs[R_DMA_INT_ST] = 0; + s->regs[R_DMA_CTL] = 0; + s->regs[R_FIFO_CTL] = 0; + s->regs[R_HASH_CTL] = 0; + memset(&s->regs[R_HASH_DOUT_0], 0, + (R_HASH_DOUT_15 - R_HASH_DOUT_0 + 1) * sizeof(uint32_t)); + s->regs[R_HASH_VALID] = 0; +} + +static uint64_t rockchip_crypto_v2_write_mask(RegisterInfo *reg, + uint64_t value) +{ + uint32_t old = *(uint32_t *)reg->data & UINT16_MAX; + uint32_t mask = extract32(value, ROCKCHIP_CRYPTO_WRITE_MASK_SHIFT, 16); + uint32_t data = value & UINT16_MAX; + + return (old & ~mask) | (data & mask); +} + +static uint64_t rockchip_crypto_v2_reset_pre_write(RegisterInfo *reg, + uint64_t value) +{ + RockchipCryptoV2State *s = ROCKCHIP_CRYPTO_V2(reg->opaque); + uint32_t reset = rockchip_crypto_v2_write_mask(reg, value); + + s->reset_requested = reset & (R_RST_CTL_SW_CC_RESET_MASK | + R_RST_CTL_SW_PKA_RESET_MASK); + return 0; +} + +static void rockchip_crypto_v2_reset_post_write(RegisterInfo *reg, + uint64_t value) +{ + RockchipCryptoV2State *s = ROCKCHIP_CRYPTO_V2(reg->opaque); + + if (!s->reset_requested) { + return; + } + + s->reset_requested = false; + s->dma_pending = false; + if (s->dma_bh) { + qemu_bh_cancel(s->dma_bh); + } + rockchip_crypto_v2_clear_hash(s); +} + +static uint64_t rockchip_crypto_v2_dma_pre_write(RegisterInfo *reg, + uint64_t value) +{ + RockchipCryptoV2State *s = ROCKCHIP_CRYPTO_V2(reg->opaque); + uint32_t control = rockchip_crypto_v2_write_mask(reg, value); + + s->start_requested = control & R_DMA_CTL_START_MASK; + s->restart_requested = control & R_DMA_CTL_RESTART_MASK; + return 0; +} + +static void rockchip_crypto_v2_dma_error(RockchipCryptoV2State *s, + uint32_t status); + +static void rockchip_crypto_v2_dma_post_write(RegisterInfo *reg, + uint64_t value) +{ + RockchipCryptoV2State *s = ROCKCHIP_CRYPTO_V2(reg->opaque); + + if (s->restart_requested) { + s->restart_requested = false; + s->start_requested = false; + rockchip_crypto_v2_dma_error(s, R_DMA_INT_ST_LIST_ERR_MASK); + return; + } + + if (!s->start_requested) { + return; + } + + s->start_requested = false; + s->dma_pending = true; + qemu_bh_schedule(s->dma_bh); +} + +static void rockchip_crypto_v2_dma_error(RockchipCryptoV2State *s, + uint32_t status) +{ + s->regs[R_HASH_VALID] &= ~R_HASH_VALID_VALID_MASK; + s->regs[R_DMA_INT_ST] |= status; + s->dma_pending = false; +} + +static bool rockchip_crypto_v2_hash_config_valid(RockchipCryptoV2State *s) +{ + uint32_t hash = s->regs[R_HASH_CTL]; + uint32_t fifo = s->regs[R_FIFO_CTL]; + + return FIELD_EX32(hash, HASH_CTL, MODE) == + ROCKCHIP_CRYPTO_HASH_MODE_SHA256 && + (hash & (R_HASH_CTL_ENABLE_MASK | + R_HASH_CTL_HW_PAD_ENABLE_MASK)) == + (R_HASH_CTL_ENABLE_MASK | R_HASH_CTL_HW_PAD_ENABLE_MASK) && + (fifo & (R_FIFO_CTL_DOIN_BYTESWAP_MASK | + R_FIFO_CTL_DOUT_BYTESWAP_MASK)) == + (R_FIFO_CTL_DOIN_BYTESWAP_MASK | + R_FIFO_CTL_DOUT_BYTESWAP_MASK); +} + +static void rockchip_crypto_v2_dma_bh(void *opaque) +{ + RockchipCryptoV2State *s = opaque; + RockchipCryptoV2LLI lli; + g_autofree uint8_t *input = NULL; + g_autofree uint8_t *digest = NULL; + Error *local_err = NULL; + size_t digest_len = 0; + uint32_t src_addr; + uint32_t src_len; + uint32_t user_define; + uint32_t dma_ctrl; + + if (!s->dma_pending) { + return; + } + + if (!rockchip_crypto_v2_hash_config_valid(s) || + dma_memory_read(&address_space_memory, s->regs[R_DMA_LLI_ADDR], + &lli, sizeof(lli), MEMTXATTRS_UNSPECIFIED) != + MEMTX_OK) { + rockchip_crypto_v2_dma_error(s, R_DMA_INT_ST_LIST_ERR_MASK); + return; + } + + src_addr = le32_to_cpu(lli.src_addr); + src_len = le32_to_cpu(lli.src_len); + user_define = le32_to_cpu(lli.user_define); + dma_ctrl = le32_to_cpu(lli.dma_ctrl); + + if (!src_len || src_len > ROCKCHIP_CRYPTO_V2_MAX_DMA || + src_addr > UINT32_MAX - (src_len - 1) || + user_define != + (ROCKCHIP_CRYPTO_LLI_USER_CIPHER_START | + ROCKCHIP_CRYPTO_LLI_USER_STRING_START | + ROCKCHIP_CRYPTO_LLI_USER_STRING_LAST) || + dma_ctrl != + (ROCKCHIP_CRYPTO_LLI_DMA_LAST | + ROCKCHIP_CRYPTO_LLI_DMA_SRC_DONE) || + le32_to_cpu(lli.dst_addr) || le32_to_cpu(lli.dst_len) || + le32_to_cpu(lli.reserved) || le32_to_cpu(lli.next_addr)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: unsupported LLI src=%08x/%u dst=%08x/%u " + "user=%08x reserved=%08x dma=%08x next=%08x\n", + TYPE_ROCKCHIP_CRYPTO_V2, src_addr, src_len, + le32_to_cpu(lli.dst_addr), le32_to_cpu(lli.dst_len), + user_define, le32_to_cpu(lli.reserved), dma_ctrl, + le32_to_cpu(lli.next_addr)); + rockchip_crypto_v2_dma_error(s, R_DMA_INT_ST_LIST_ERR_MASK); + return; + } + + input = g_malloc(src_len); + if (dma_memory_read(&address_space_memory, src_addr, input, src_len, + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + rockchip_crypto_v2_dma_error(s, R_DMA_INT_ST_SRC_ERR_MASK); + return; + } + + if (qcrypto_hash_bytes(QCRYPTO_HASH_ALGO_SHA256, input, src_len, + &digest, &digest_len, &local_err) < 0 || + digest_len != QCRYPTO_HASH_DIGEST_LEN_SHA256) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: SHA-256 calculation failed: %s\n", + TYPE_ROCKCHIP_CRYPTO_V2, + local_err ? error_get_pretty(local_err) : + "invalid digest length"); + error_free(local_err); + rockchip_crypto_v2_dma_error(s, R_DMA_INT_ST_SRC_ERR_MASK); + return; + } + + for (unsigned int i = 0; i < digest_len / sizeof(uint32_t); i++) { + s->regs[R_HASH_DOUT_0 + i] = ldl_be_p(digest + + i * sizeof(uint32_t)); + } + + s->regs[R_HASH_VALID] |= R_HASH_VALID_VALID_MASK; + s->regs[R_DMA_INT_ST] |= R_DMA_INT_ST_SRC_ITEM_DONE_MASK; + s->dma_pending = false; +} + +static const RegisterAccessInfo rockchip_crypto_v2_regs_info[] = { + { .name = "RST_CTL", .addr = A_RST_CTL, + .pre_write = rockchip_crypto_v2_reset_pre_write, + .post_write = rockchip_crypto_v2_reset_post_write }, + { .name = "DMA_INT_EN", .addr = A_DMA_INT_EN }, + { .name = "DMA_INT_ST", .addr = A_DMA_INT_ST, + .w1c = R_DMA_INT_ST_SRC_ITEM_DONE_MASK | + R_DMA_INT_ST_SRC_ERR_MASK | R_DMA_INT_ST_LIST_ERR_MASK, + .ro = ~(R_DMA_INT_ST_SRC_ITEM_DONE_MASK | + R_DMA_INT_ST_SRC_ERR_MASK | R_DMA_INT_ST_LIST_ERR_MASK) }, + { .name = "DMA_CTL", .addr = A_DMA_CTL, + .pre_write = rockchip_crypto_v2_dma_pre_write, + .post_write = rockchip_crypto_v2_dma_post_write }, + { .name = "DMA_LLI_ADDR", .addr = A_DMA_LLI_ADDR }, + { .name = "FIFO_CTL", .addr = A_FIFO_CTL, + .pre_write = rockchip_crypto_v2_write_mask }, + { .name = "HASH_CTL", .addr = A_HASH_CTL, + .pre_write = rockchip_crypto_v2_write_mask }, + { .name = "HASH_DOUT_0", .addr = A_HASH_DOUT_0, .ro = UINT32_MAX }, + { .name = "HASH_DOUT_1", .addr = A_HASH_DOUT_1, .ro = UINT32_MAX }, + { .name = "HASH_DOUT_2", .addr = A_HASH_DOUT_2, .ro = UINT32_MAX }, + { .name = "HASH_DOUT_3", .addr = A_HASH_DOUT_3, .ro = UINT32_MAX }, + { .name = "HASH_DOUT_4", .addr = A_HASH_DOUT_4, .ro = UINT32_MAX }, + { .name = "HASH_DOUT_5", .addr = A_HASH_DOUT_5, .ro = UINT32_MAX }, + { .name = "HASH_DOUT_6", .addr = A_HASH_DOUT_6, .ro = UINT32_MAX }, + { .name = "HASH_DOUT_7", .addr = A_HASH_DOUT_7, .ro = UINT32_MAX }, + { .name = "HASH_DOUT_8", .addr = A_HASH_DOUT_8, .ro = UINT32_MAX }, + { .name = "HASH_DOUT_9", .addr = A_HASH_DOUT_9, .ro = UINT32_MAX }, + { .name = "HASH_DOUT_10", .addr = A_HASH_DOUT_10, .ro = UINT32_MAX }, + { .name = "HASH_DOUT_11", .addr = A_HASH_DOUT_11, .ro = UINT32_MAX }, + { .name = "HASH_DOUT_12", .addr = A_HASH_DOUT_12, .ro = UINT32_MAX }, + { .name = "HASH_DOUT_13", .addr = A_HASH_DOUT_13, .ro = UINT32_MAX }, + { .name = "HASH_DOUT_14", .addr = A_HASH_DOUT_14, .ro = UINT32_MAX }, + { .name = "HASH_DOUT_15", .addr = A_HASH_DOUT_15, .ro = UINT32_MAX }, + { .name = "HASH_VALID", .addr = A_HASH_VALID, + .w1c = R_HASH_VALID_VALID_MASK, + .ro = ~R_HASH_VALID_VALID_MASK }, +}; + +static const MemoryRegionOps rockchip_crypto_v2_ops = { + .read = register_read_memory, + .write = register_write_memory, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void rockchip_crypto_v2_reset_hold(Object *obj, ResetType type) +{ + RockchipCryptoV2State *s = ROCKCHIP_CRYPTO_V2(obj); + + if (s->dma_bh) { + qemu_bh_cancel(s->dma_bh); + } + s->dma_pending = false; + s->start_requested = false; + s->restart_requested = false; + s->reset_requested = false; + + for (unsigned int i = 0; + i < ARRAY_SIZE(rockchip_crypto_v2_regs_info); i++) { + register_reset(&s->regs_info[ + rockchip_crypto_v2_regs_info[i].addr / 4]); + } + rockchip_crypto_v2_clear_hash(s); +} + +static int rockchip_crypto_v2_post_load(void *opaque, int version_id) +{ + RockchipCryptoV2State *s = opaque; + + s->start_requested = false; + s->restart_requested = false; + s->reset_requested = false; + if (s->dma_pending) { + qemu_bh_schedule(s->dma_bh); + } + + return 0; +} + +static const VMStateDescription vmstate_rockchip_crypto_v2 = { + .name = TYPE_ROCKCHIP_CRYPTO_V2, + .version_id = 1, + .minimum_version_id = 1, + .post_load = rockchip_crypto_v2_post_load, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, RockchipCryptoV2State, + ROCKCHIP_CRYPTO_V2_REG_WORDS), + VMSTATE_BOOL(dma_pending, RockchipCryptoV2State), + VMSTATE_END_OF_LIST() + }, +}; + +static void rockchip_crypto_v2_realize(DeviceState *dev, Error **errp) +{ + RockchipCryptoV2State *s = ROCKCHIP_CRYPTO_V2(dev); + + s->dma_bh = qemu_bh_new_guarded(rockchip_crypto_v2_dma_bh, s, + &dev->mem_reentrancy_guard); +} + +static void rockchip_crypto_v2_init(Object *obj) +{ + RockchipCryptoV2State *s = ROCKCHIP_CRYPTO_V2(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + s->reg_array = register_init_block32( + DEVICE(obj), rockchip_crypto_v2_regs_info, + ARRAY_SIZE(rockchip_crypto_v2_regs_info), s->regs_info, s->regs, + &rockchip_crypto_v2_ops, false, ROCKCHIP_CRYPTO_V2_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->reg_array->mem); +} + +static void rockchip_crypto_v2_finalize(Object *obj) +{ + RockchipCryptoV2State *s = ROCKCHIP_CRYPTO_V2(obj); + + if (s->dma_bh) { + qemu_bh_delete(s->dma_bh); + } +} + +static void rockchip_crypto_v2_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + ResettableClass *rc = RESETTABLE_CLASS(oc); + + dc->realize = rockchip_crypto_v2_realize; + dc->vmsd = &vmstate_rockchip_crypto_v2; + rc->phases.hold = rockchip_crypto_v2_reset_hold; +} + +static const TypeInfo rockchip_crypto_v2_info = { + .name = TYPE_ROCKCHIP_CRYPTO_V2, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RockchipCryptoV2State), + .instance_init = rockchip_crypto_v2_init, + .instance_finalize = rockchip_crypto_v2_finalize, + .class_init = rockchip_crypto_v2_class_init, +}; + +static void rockchip_crypto_v2_register_types(void) +{ + type_register_static(&rockchip_crypto_v2_info); +} + +type_init(rockchip_crypto_v2_register_types) diff --git a/hw/nvram/Kconfig b/hw/nvram/Kconfig index 24cfc18f8b15..4117e119a0a6 100644 --- a/hw/nvram/Kconfig +++ b/hw/nvram/Kconfig @@ -9,6 +9,10 @@ config MAC_NVRAM bool select CHRP_NVRAM +config RK3588_SECURE_OTP + bool + select REGISTER + # NMC93XX uses the NS uWire interface (similar to SPI but less configurable) config NMC93XX_EEPROM bool diff --git a/hw/nvram/meson.build b/hw/nvram/meson.build index 263433598758..113040a38462 100644 --- a/hw/nvram/meson.build +++ b/hw/nvram/meson.build @@ -8,6 +8,8 @@ system_ss.add(when: 'CONFIG_AT24C', if_true: files('eeprom_at24c.c')) system_ss.add(when: 'CONFIG_MAC_NVRAM', if_true: files('mac_nvram.c')) system_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_otp.c')) system_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('nrf51_nvm.c')) +system_ss.add(when: 'CONFIG_RK3588_SECURE_OTP', if_true: files( + 'rk3588_secure_otp.c')) system_ss.add(when: 'CONFIG_XLNX_EFUSE_CRC', if_true: files('xlnx-efuse-crc.c')) system_ss.add(when: 'CONFIG_XLNX_EFUSE', if_true: files('xlnx-efuse.c')) system_ss.add(when: 'CONFIG_XLNX_EFUSE_VERSAL', if_true: files( diff --git a/hw/nvram/rk3588_secure_otp.c b/hw/nvram/rk3588_secure_otp.c new file mode 100644 index 000000000000..a5edf616793e --- /dev/null +++ b/hw/nvram/rk3588_secure_otp.c @@ -0,0 +1,134 @@ +/* + * Rockchip RK3588 secure OTP + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/nvram/rk3588_secure_otp.h" + +#include "hw/core/register.h" +#include "migration/vmstate.h" +#include "qemu/module.h" + +REG32(DOUT, 0x20) +REG32(INT_STATUS, 0x84) + FIELD(INT_STATUS, READ_DONE, 1, 1) + +#define RK3588_SECURE_OTP_REG_WORDS (R_INT_STATUS + 1) + +struct RK3588SecureOTPState { + SysBusDevice parent_obj; + + uint32_t regs[RK3588_SECURE_OTP_REG_WORDS]; + RegisterInfo regs_info[RK3588_SECURE_OTP_REG_WORDS]; + RegisterInfoArray *reg_array; +}; + +static const RegisterAccessInfo rk3588_secure_otp_regs_info[] = { + { .name = "DOUT", .addr = A_DOUT, .ro = UINT32_MAX }, + { .name = "INT_STATUS", .addr = A_INT_STATUS, .ro = UINT32_MAX, + .reset = R_INT_STATUS_READ_DONE_MASK }, +}; + +static bool rk3588_secure_otp_is_register(hwaddr addr) +{ + return addr == A_DOUT || addr == A_INT_STATUS; +} + +static uint64_t rk3588_secure_otp_read(void *opaque, hwaddr addr, + unsigned size) +{ + if (!rk3588_secure_otp_is_register(addr)) { + return 0; + } + + return register_read_memory(opaque, addr, size); +} + +static void rk3588_secure_otp_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + if (!rk3588_secure_otp_is_register(addr)) { + return; + } + + register_write_memory(opaque, addr, value, size); +} + +static const MemoryRegionOps rk3588_secure_otp_ops = { + .read = rk3588_secure_otp_read, + .write = rk3588_secure_otp_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void rk3588_secure_otp_reset_hold(Object *obj, ResetType type) +{ + RK3588SecureOTPState *s = RK3588_SECURE_OTP(obj); + + memset(s->regs, 0, sizeof(s->regs)); + for (unsigned int i = 0; + i < ARRAY_SIZE(rk3588_secure_otp_regs_info); i++) { + register_reset(&s->regs_info[ + rk3588_secure_otp_regs_info[i].addr / 4]); + } +} + +static const VMStateDescription vmstate_rk3588_secure_otp = { + .name = TYPE_RK3588_SECURE_OTP, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, RK3588SecureOTPState, + RK3588_SECURE_OTP_REG_WORDS), + VMSTATE_END_OF_LIST() + }, +}; + +static void rk3588_secure_otp_init(Object *obj) +{ + RK3588SecureOTPState *s = RK3588_SECURE_OTP(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + s->reg_array = register_init_block32( + DEVICE(obj), rk3588_secure_otp_regs_info, + ARRAY_SIZE(rk3588_secure_otp_regs_info), s->regs_info, s->regs, + &rk3588_secure_otp_ops, false, RK3588_SECURE_OTP_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->reg_array->mem); +} + +static void rk3588_secure_otp_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + ResettableClass *rc = RESETTABLE_CLASS(oc); + + dc->vmsd = &vmstate_rk3588_secure_otp; + rc->phases.hold = rk3588_secure_otp_reset_hold; +} + +static const TypeInfo rk3588_secure_otp_info = { + .name = TYPE_RK3588_SECURE_OTP, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RK3588SecureOTPState), + .instance_init = rk3588_secure_otp_init, + .class_init = rk3588_secure_otp_class_init, +}; + +static void rk3588_secure_otp_register_types(void) +{ + type_register_static(&rk3588_secure_otp_info); +} + +type_init(rk3588_secure_otp_register_types) diff --git a/hw/pci-host/designware.c b/hw/pci-host/designware.c index 00c9449c4d2e..f8e64ae95d2d 100644 --- a/hw/pci-host/designware.c +++ b/hw/pci-host/designware.c @@ -55,16 +55,25 @@ #define DESIGNWARE_PCIE_ATU_DEVFN(x) (((x) >> 16) & 0xff) #define DESIGNWARE_PCIE_ATU_UPPER_TARGET 0x91C +static int designware_pcie_root_bus_num(PCIBus *bus) +{ + DesignwarePCIEHost *host = DESIGNWARE_PCIE_HOST(BUS(bus)->parent); + + return host->bus_nr; +} + static void designware_pcie_root_bus_class_init(ObjectClass *klass, const void *data) { BusClass *k = BUS_CLASS(klass); + PCIBusClass *pbc = PCI_BUS_CLASS(klass); /* * Designware has only a single root complex. Enforce the limit on the * parent bus */ k->max_dev = 1; + pbc->bus_num = designware_pcie_root_bus_num; } static DesignwarePCIEHost * @@ -615,7 +624,7 @@ static uint64_t designware_pcie_host_mmio_read(void *opaque, hwaddr addr, unsigned int size) { PCIHostState *pci = PCI_HOST_BRIDGE(opaque); - PCIDevice *device = pci_find_device(pci->bus, 0, 0); + PCIDevice *device = pci_find_device(pci->bus, pci_bus_num(pci->bus), 0); return pci_host_config_read_common(device, addr, @@ -627,7 +636,7 @@ static void designware_pcie_host_mmio_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size) { PCIHostState *pci = PCI_HOST_BRIDGE(opaque); - PCIDevice *device = pci_find_device(pci->bus, 0, 0); + PCIDevice *device = pci_find_device(pci->bus, pci_bus_num(pci->bus), 0); return pci_host_config_write_common(device, addr, @@ -726,6 +735,10 @@ static const VMStateDescription vmstate_designware_pcie_host = { } }; +static const Property designware_pcie_host_properties[] = { + DEFINE_PROP_UINT8("bus-nr", DesignwarePCIEHost, bus_nr, 0), +}; + static void designware_pcie_host_class_init(ObjectClass *klass, const void *data) { @@ -736,6 +749,7 @@ static void designware_pcie_host_class_init(ObjectClass *klass, dc->realize = designware_pcie_host_realize; dc->fw_name = "pci"; dc->vmsd = &vmstate_designware_pcie_host; + device_class_set_props(dc, designware_pcie_host_properties); } static void designware_pcie_host_init(Object *obj) diff --git a/hw/pci-host/rockchip_pcie.c b/hw/pci-host/rockchip_pcie.c index 9c07d23aa6e7..2e8157dd5f69 100644 --- a/hw/pci-host/rockchip_pcie.c +++ b/hw/pci-host/rockchip_pcie.c @@ -8,18 +8,17 @@ * Subclasses TYPE_DESIGNWARE_PCIE_HOST and adds: * * - Rockchip APB vendor register window (sysbus mmio[1]). - * Only PCIE_CLIENT_LTSSM_STATUS (0x300) is load-bearing: read-only - * 0x00030011 so rockchip_pcie_link_up() returns true on the first - * read (substitution policy - no analog link in QEMU). Every other + * Only PCIE_CLIENT_LTSSM_STATUS (0x300) is load-bearing: it reports + * either L0/link-up or detect/link-down according to the link-up + * property. Every other * APB offset is RAZ/WI so the driver's PCIE_CLIENT_* writes * (GENERAL_CON mode set, HOT_RESET_CTRL LTSSM enhance, POWER_CON * clkreq, INTR_MASK_LEGACY) land without aborting (D-15). * * - The inherited 4 KiB DBI mmio (sysbus mmio[0]) covers the DWC * core Type-0 header / Port-Logic / iATU viewport / MSI block that - * designware.c implements. The rest of the 4 MiB DBI window - * (including DBI2 at +0x100000) is backed by an unimplemented - * device so the guest's DBI writes/reads above 0xfff don't abort. + * designware.c implements. A device-owned RAZ/WI region covers the + * rest of the 4 MiB DBI window, including DBI2 at +0x100000. * * - The board's CFG window is served by the designware root's outbound * CFG viewports; the guest programs them in dw_pcie_config_ecam_iatu @@ -38,22 +37,25 @@ #include "qemu/bitops.h" #include "qemu/log.h" #include "qemu/module.h" +#include "hw/core/qdev-properties.h" #include "hw/core/sysbus.h" #include "hw/pci-host/rockchip_pcie.h" /* * PCIE_CLIENT_LTSSM_STATUS (APB 0x300). Driver reads bits 17:16 for * link-up (0b11 = up) and bits 5:0 for LTSSM state (L0 = 0x11). Pin - * both so dw_pcie_wait_for_link terminates on the first poll. + * both when the link-up property is enabled. */ #define ROCKCHIP_PCIE_LTSSM_LINK_UP 0x00030011u static uint64_t rockchip_pcie_apb_read(void *opaque, hwaddr offset, unsigned size) { + RockchipPCIEHost *s = opaque; + switch (offset) { case ROCKCHIP_PCIE_APB_LTSSM_STATUS: - return ROCKCHIP_PCIE_LTSSM_LINK_UP; + return s->link_up ? ROCKCHIP_PCIE_LTSSM_LINK_UP : 0; default: /* * All other PCIE_CLIENT_* regs are write-only from the driver's @@ -65,6 +67,11 @@ static uint64_t rockchip_pcie_apb_read(void *opaque, hwaddr offset, } } +static const Property rockchip_pcie_host_properties[] = { + DEFINE_PROP_BOOL("link-up", RockchipPCIEHost, link_up, true), + DEFINE_PROP_UINT32("domain", RockchipPCIEHost, domain, 0), +}; + static void rockchip_pcie_apb_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) { @@ -91,12 +98,36 @@ static const MemoryRegionOps rockchip_pcie_apb_ops = { }, }; +static uint64_t rockchip_pcie_dbi_tail_read(void *opaque, hwaddr offset, + unsigned size) +{ + return 0; +} + +static void rockchip_pcie_dbi_tail_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ +} + +static const MemoryRegionOps rockchip_pcie_dbi_tail_ops = { + .read = rockchip_pcie_dbi_tail_read, + .write = rockchip_pcie_dbi_tail_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + }, +}; + static void rockchip_pcie_host_realize(DeviceState *dev, Error **errp) { RockchipPCIEHost *s = ROCKCHIP_PCIE_HOST(dev); RockchipPCIEHostClass *rkpc = ROCKCHIP_PCIE_HOST_GET_CLASS(dev); SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + snprintf(s->root_bus_path, sizeof(s->root_bus_path), "%04x:%02x", + s->domain, s->parent_obj.bus_nr); + /* * Realize the parent designware host first. This registers its * 4 KiB DBI mmio at sysbus mmio[0] and its 5 sysbus IRQs @@ -123,15 +154,32 @@ static void rockchip_pcie_host_realize(DeviceState *dev, Error **errp) memory_region_init_io(&s->apb, OBJECT(s), &rockchip_pcie_apb_ops, s, "rockchip-pcie-apb", 0x10000); sysbus_init_mmio(sbd, &s->apb); + + memory_region_init_io(&s->dbi_tail, OBJECT(s), + &rockchip_pcie_dbi_tail_ops, s, + "rockchip-pcie-dbi-tail", + ROCKCHIP_PCIE_DBI_TAIL_SIZE); + sysbus_init_mmio(sbd, &s->dbi_tail); +} + +static const char *rockchip_pcie_host_root_bus_path( + PCIHostState *host_bridge, PCIBus *rootbus) +{ + RockchipPCIEHost *s = ROCKCHIP_PCIE_HOST(host_bridge); + + return s->root_bus_path; } static void rockchip_pcie_host_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); + PCIHostBridgeClass *hc = PCI_HOST_BRIDGE_CLASS(klass); RockchipPCIEHostClass *rkpc = ROCKCHIP_PCIE_HOST_CLASS(klass); device_class_set_parent_realize(dc, rockchip_pcie_host_realize, &rkpc->parent_realize); + device_class_set_props(dc, rockchip_pcie_host_properties); + hc->root_bus_path = rockchip_pcie_host_root_bus_path; /* Not user-creatable; instantiated by the board. */ dc->user_creatable = false; } diff --git a/hw/usb/Kconfig b/hw/usb/Kconfig index 5c5060e601b7..41a1ee7b7e86 100644 --- a/hw/usb/Kconfig +++ b/hw/usb/Kconfig @@ -34,6 +34,10 @@ config USB_EHCI_SYSBUS bool select USB_EHCI +config RK3588_USB2_HOST + bool + select REGISTER + config USB_XHCI bool select USB diff --git a/hw/usb/meson.build b/hw/usb/meson.build index 17a0c66b3b4a..aaf23eba5fd4 100644 --- a/hw/usb/meson.build +++ b/hw/usb/meson.build @@ -20,6 +20,7 @@ system_ss.add(when: 'CONFIG_USB_OHCI_SYSBUS', if_true: files('hcd-ohci-sysbus.c' system_ss.add(when: 'CONFIG_USB_EHCI', if_true: files('hcd-ehci.c')) system_ss.add(when: 'CONFIG_USB_EHCI_PCI', if_true: files('hcd-ehci-pci.c')) system_ss.add(when: 'CONFIG_USB_EHCI_SYSBUS', if_true: files('hcd-ehci-sysbus.c')) +system_ss.add(when: 'CONFIG_RK3588_USB2_HOST', if_true: files('rk3588_usb2_host.c')) system_ss.add(when: 'CONFIG_USB_XHCI', if_true: files('hcd-xhci.c')) system_ss.add(when: 'CONFIG_USB_XHCI_PCI', if_true: files('hcd-xhci-pci.c')) system_ss.add(when: 'CONFIG_USB_XHCI_SYSBUS', if_true: files('hcd-xhci-sysbus.c')) diff --git a/hw/usb/rk3588_usb2_host.c b/hw/usb/rk3588_usb2_host.c new file mode 100644 index 000000000000..a7378e56a400 --- /dev/null +++ b/hw/usb/rk3588_usb2_host.c @@ -0,0 +1,340 @@ +/* + * Rockchip RK3588 USB2 host register shim + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/core/register.h" +#include "hw/core/registerfields.h" +#include "hw/usb/rk3588_usb2_host.h" +#include "migration/vmstate.h" +#include "qemu/bitops.h" +#include "qemu/module.h" + +#define RK3588_USB2_HOST_WINDOW_WORDS \ + (RK3588_USB2_HOST_MMIO_SIZE / sizeof(uint32_t)) + +#define RK3588_USB2_EHCI_CAPBASE_RESET 0x01000020U +#define RK3588_USB2_EHCI_HCSPARAMS_RESET 0x00000011U +#define RK3588_USB2_EHCI_CMD_RESET BIT(1) +#define RK3588_USB2_EHCI_CMD_RUN BIT(0) +#define RK3588_USB2_EHCI_STS_HALT BIT(12) +#define RK3588_USB2_EHCI_PORT_POWER BIT(12) + +#define RK3588_USB2_OHCI_REVISION_RESET 0x00000010U +#define RK3588_USB2_OHCI_HCR BIT(0) +#define RK3588_USB2_OHCI_RH_A_NPS BIT(9) +#define RK3588_USB2_OHCI_RH_A_NOCP BIT(12) +#define RK3588_USB2_OHCI_RH_A_NDP1 1U +#define RK3588_USB2_OHCI_RH_PS_PPS BIT(8) + +REG32(EHCI_CAPBASE, 0x00) +REG32(EHCI_HCSPARAMS, 0x04) +REG32(EHCI_USBCMD, 0x20) +REG32(EHCI_USBSTS, 0x24) +REG32(EHCI_PORTSC0, 0x64) + +REG32(OHCI_REVISION, 0x00) +REG32(OHCI_CMDSTATUS, 0x08) +REG32(OHCI_INTRSTATUS, 0x0c) +REG32(OHCI_ROOTHUB_A, 0x48) +REG32(OHCI_PORTSTATUS0, 0x54) + +#define RK3588_USB2_HOST_REG_WORDS (R_EHCI_PORTSC0 + 1) + +struct RK3588USB2HostState { + SysBusDevice parent_obj; + + RegisterInfoArray *reg_array[RK3588_USB2_HOST_MMIO_COUNT]; + RegisterInfo regs_info[RK3588_USB2_HOST_MMIO_COUNT] + [RK3588_USB2_HOST_REG_WORDS]; + uint32_t regs[RK3588_USB2_HOST_MMIO_COUNT] + [RK3588_USB2_HOST_WINDOW_WORDS]; + bool active; +}; + +static unsigned int rk3588_usb2_host_window_index( + RK3588USB2HostState *s, RegisterInfoArray *reg_array) +{ + unsigned int i; + + for (i = 0; i < RK3588_USB2_HOST_MMIO_COUNT; i++) { + if (s->reg_array[i] == reg_array) { + return i; + } + } + + g_assert_not_reached(); +} + +static bool rk3588_usb2_host_is_register(RegisterInfoArray *reg_array, + hwaddr addr) +{ + unsigned int i; + + for (i = 0; i < reg_array->num_elements; i++) { + if (reg_array->r[i]->access->addr == addr) { + return true; + } + } + + return false; +} + +static uint64_t rk3588_usb2_host_raw_read(RK3588USB2HostState *s, + unsigned int window, + hwaddr addr, unsigned int size) +{ + uint64_t value = 0; + unsigned int i; + + for (i = 0; i < size; i++) { + hwaddr byte_addr = addr + i; + uint32_t word = s->regs[window][byte_addr / sizeof(uint32_t)]; + unsigned int shift = (byte_addr & 3) * 8; + + value |= (uint64_t)extract32(word, shift, 8) << (i * 8); + } + + return value; +} + +static void rk3588_usb2_host_raw_write(RK3588USB2HostState *s, + unsigned int window, hwaddr addr, + uint64_t value, unsigned int size) +{ + unsigned int i; + + for (i = 0; i < size; i++) { + hwaddr byte_addr = addr + i; + uint32_t *word = &s->regs[window][byte_addr / sizeof(uint32_t)]; + unsigned int shift = (byte_addr & 3) * 8; + + *word = deposit32(*word, shift, 8, value >> (i * 8)); + } +} + +static uint64_t rk3588_usb2_host_read(void *opaque, hwaddr addr, + unsigned int size) +{ + RegisterInfoArray *reg_array = opaque; + RK3588USB2HostState *s = RK3588_USB2_HOST( + register_array_get_owner(reg_array)); + unsigned int window = rk3588_usb2_host_window_index(s, reg_array); + + if (addr > RK3588_USB2_HOST_MMIO_SIZE - size) { + return 0; + } + + if (!s->active) { + return size == sizeof(uint64_t) ? UINT64_MAX : + MAKE_64BIT_MASK(0, size * 8); + } + + if (size == sizeof(uint32_t) && + rk3588_usb2_host_is_register(reg_array, addr)) { + return register_read_memory(reg_array, addr, size); + } + + return rk3588_usb2_host_raw_read(s, window, addr, size); +} + +static void rk3588_usb2_host_write(void *opaque, hwaddr addr, + uint64_t value, unsigned int size) +{ + RegisterInfoArray *reg_array = opaque; + RK3588USB2HostState *s = RK3588_USB2_HOST( + register_array_get_owner(reg_array)); + unsigned int window = rk3588_usb2_host_window_index(s, reg_array); + + if (!s->active || addr > RK3588_USB2_HOST_MMIO_SIZE - size) { + return; + } + + if (size == sizeof(uint32_t) && + rk3588_usb2_host_is_register(reg_array, addr)) { + register_write_memory(reg_array, addr, value, size); + return; + } + + rk3588_usb2_host_raw_write(s, window, addr, value, size); +} + +static uint64_t rk3588_usb2_ehci_cmd_pre_write(RegisterInfo *reg, + uint64_t value) +{ + return value & ~RK3588_USB2_EHCI_CMD_RESET; +} + +static void rk3588_usb2_ehci_cmd_post_write(RegisterInfo *reg, + uint64_t value) +{ + RK3588USB2HostState *s = RK3588_USB2_HOST(reg->opaque); + unsigned int window; + + for (window = RK3588_USB2_HOST_EHCI0; + window < RK3588_USB2_HOST_MMIO_COUNT; window += 2) { + if (reg->data == &s->regs[window][R_EHCI_USBCMD]) { + if (value & RK3588_USB2_EHCI_CMD_RUN) { + s->regs[window][R_EHCI_USBSTS] &= + ~RK3588_USB2_EHCI_STS_HALT; + } else { + s->regs[window][R_EHCI_USBSTS] |= + RK3588_USB2_EHCI_STS_HALT; + } + return; + } + } + + g_assert_not_reached(); +} + +static uint64_t rk3588_usb2_ohci_cmd_pre_write(RegisterInfo *reg, + uint64_t value) +{ + return value & ~RK3588_USB2_OHCI_HCR; +} + +static const RegisterAccessInfo rk3588_usb2_ehci_regs_info[] = { + { .name = "CAPBASE", .addr = A_EHCI_CAPBASE, + .reset = RK3588_USB2_EHCI_CAPBASE_RESET }, + { .name = "HCSPARAMS", .addr = A_EHCI_HCSPARAMS, + .reset = RK3588_USB2_EHCI_HCSPARAMS_RESET }, + { .name = "USBCMD", .addr = A_EHCI_USBCMD, + .pre_write = rk3588_usb2_ehci_cmd_pre_write, + .post_write = rk3588_usb2_ehci_cmd_post_write }, + { .name = "USBSTS", .addr = A_EHCI_USBSTS, + .reset = RK3588_USB2_EHCI_STS_HALT }, + { .name = "PORTSC0", .addr = A_EHCI_PORTSC0, + .reset = RK3588_USB2_EHCI_PORT_POWER }, +}; + +static const RegisterAccessInfo rk3588_usb2_ohci_regs_info[] = { + { .name = "REVISION", .addr = A_OHCI_REVISION, + .reset = RK3588_USB2_OHCI_REVISION_RESET }, + { .name = "CMDSTATUS", .addr = A_OHCI_CMDSTATUS, + .pre_write = rk3588_usb2_ohci_cmd_pre_write }, + { .name = "INTRSTATUS", .addr = A_OHCI_INTRSTATUS, + .w1c = UINT32_MAX }, + { .name = "ROOTHUB_A", .addr = A_OHCI_ROOTHUB_A, + .reset = RK3588_USB2_OHCI_RH_A_NDP1 | + RK3588_USB2_OHCI_RH_A_NPS | + RK3588_USB2_OHCI_RH_A_NOCP }, + { .name = "PORTSTATUS0", .addr = A_OHCI_PORTSTATUS0, + .reset = RK3588_USB2_OHCI_RH_PS_PPS }, +}; + +static const MemoryRegionOps rk3588_usb2_host_ops = { + .read = rk3588_usb2_host_read, + .write = rk3588_usb2_host_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + }, + .impl = { + .min_access_size = 1, + .max_access_size = 8, + }, +}; + +static void rk3588_usb2_host_reset_state(RK3588USB2HostState *s) +{ + unsigned int window; + + memset(s->regs, s->active ? 0 : 0xff, sizeof(s->regs)); + if (!s->active) { + return; + } + + for (window = 0; window < RK3588_USB2_HOST_MMIO_COUNT; window++) { + RegisterInfoArray *reg_array = s->reg_array[window]; + unsigned int i; + + for (i = 0; i < reg_array->num_elements; i++) { + register_reset(reg_array->r[i]); + } + } +} + +void rk3588_usb2_host_set_active(RK3588USB2HostState *s, bool active) +{ + s->active = active; + rk3588_usb2_host_reset_state(s); +} + +static void rk3588_usb2_host_reset_hold(Object *obj, ResetType type) +{ + RK3588USB2HostState *s = RK3588_USB2_HOST(obj); + + rk3588_usb2_host_reset_state(s); +} + +static const VMStateDescription vmstate_rk3588_usb2_host = { + .name = TYPE_RK3588_USB2_HOST, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_BOOL(active, RK3588USB2HostState), + VMSTATE_UINT32_2DARRAY(regs, RK3588USB2HostState, + RK3588_USB2_HOST_MMIO_COUNT, + RK3588_USB2_HOST_WINDOW_WORDS), + VMSTATE_END_OF_LIST(), + }, +}; + +static void rk3588_usb2_host_init(Object *obj) +{ + RK3588USB2HostState *s = RK3588_USB2_HOST(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + unsigned int window; + + for (window = 0; window < RK3588_USB2_HOST_MMIO_COUNT; window++) { + const RegisterAccessInfo *access_info; + size_t access_info_count; + + if (window == RK3588_USB2_HOST_EHCI0 || + window == RK3588_USB2_HOST_EHCI1) { + access_info = rk3588_usb2_ehci_regs_info; + access_info_count = ARRAY_SIZE(rk3588_usb2_ehci_regs_info); + } else { + access_info = rk3588_usb2_ohci_regs_info; + access_info_count = ARRAY_SIZE(rk3588_usb2_ohci_regs_info); + } + + s->reg_array[window] = register_init_block32( + DEVICE(obj), access_info, access_info_count, + s->regs_info[window], s->regs[window], &rk3588_usb2_host_ops, + false, RK3588_USB2_HOST_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->reg_array[window]->mem); + } + + rk3588_usb2_host_reset_state(s); +} + +static void rk3588_usb2_host_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + ResettableClass *rc = RESETTABLE_CLASS(oc); + + dc->vmsd = &vmstate_rk3588_usb2_host; + rc->phases.hold = rk3588_usb2_host_reset_hold; +} + +static const TypeInfo rk3588_usb2_host_info = { + .name = TYPE_RK3588_USB2_HOST, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RK3588USB2HostState), + .instance_init = rk3588_usb2_host_init, + .class_init = rk3588_usb2_host_class_init, +}; + +static void rk3588_usb2_host_register_types(void) +{ + type_register_static(&rk3588_usb2_host_info); +} + +type_init(rk3588_usb2_host_register_types) diff --git a/include/hw/misc/rk3588_atf_ddr.h b/include/hw/misc/rk3588_atf_ddr.h new file mode 100644 index 000000000000..a94946cc07ac --- /dev/null +++ b/include/hw/misc/rk3588_atf_ddr.h @@ -0,0 +1,21 @@ +/* + * Rockchip RK3588 ATF DDR runtime descriptor + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_RK3588_ATF_DDR_H +#define HW_MISC_RK3588_ATF_DDR_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RK3588_ATF_DDR "rk3588-atf-ddr" +OBJECT_DECLARE_SIMPLE_TYPE(RK3588ATFDDRState, RK3588_ATF_DDR) + +#define RK3588_ATF_DDR_RUNTIME_BASE 0x0008d000ULL +#define RK3588_ATF_DDR_RUNTIME_SIZE 0x8000 + +#endif /* HW_MISC_RK3588_ATF_DDR_H */ diff --git a/include/hw/misc/rk3588_ddr.h b/include/hw/misc/rk3588_ddr.h new file mode 100644 index 000000000000..79b37a987eca --- /dev/null +++ b/include/hw/misc/rk3588_ddr.h @@ -0,0 +1,57 @@ +/* + * Rockchip RK3588 DDR controller compatibility model + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_RK3588_DDR_H +#define HW_MISC_RK3588_DDR_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RK3588_DDR "rk3588-ddr" +OBJECT_DECLARE_SIMPLE_TYPE(RK3588DDRState, RK3588_DDR) + +#define RK3588_DDR_LEGACY_CHANNEL_COUNT 4 +#define RK3588_DDR_LEGACY_CTRL_WINDOWS_PER_CHANNEL 4 +#define RK3588_DDR_LEGACY_WINDOWS_PER_CHANNEL 5 +#define RK3588_DDR_LEGACY_WINDOW_COUNT \ + (RK3588_DDR_LEGACY_CHANNEL_COUNT * \ + RK3588_DDR_LEGACY_WINDOWS_PER_CHANNEL) +#define RK3588_DDR_CHANNEL_COUNT 8 + +#define RK3588_DDR_LEGACY_MMIO_SIZE 0x00010000 +#define RK3588_DDR_LEGACY_CHANNEL_STRIDE 0x01000000 +#define RK3588_DDR_LEGACY_WINDOW_STRIDE 0x00010000 +#define RK3588_DDR_LEGACY_PHY_MMIO_SIZE 0x00010000 +#define RK3588_DDR_LEGACY_PHY_AUX_MMIO_SIZE 0x00008000 +#define RK3588_DDR_GLOBAL_MMIO_SIZE 0x00020000 +#define RK3588_DDR_CHANNEL_MMIO_SIZE 0x00020000 +#define RK3588_DDR_CHANNEL_MMIO_STRIDE 0x00020000 +#define RK3588_DDRPHY_MMIO_SIZE 0x00001000 + +enum RK3588DDRMMIOIndex { + RK3588_DDR_MMIO_LEGACY0 = 0, + RK3588_DDR_MMIO_LEGACY_PHY = RK3588_DDR_LEGACY_WINDOW_COUNT, + RK3588_DDR_MMIO_LEGACY_PHY_AUX, + RK3588_DDR_MMIO_GLOBAL, + RK3588_DDR_MMIO_CHANNEL0, + RK3588_DDR_MMIO_DDRPHY = RK3588_DDR_MMIO_CHANNEL0 + + RK3588_DDR_CHANNEL_COUNT, + RK3588_DDR_MMIO_COUNT, +}; + +#define RK3588_DDR_MMIO_LEGACY(_channel, _window) \ + (RK3588_DDR_MMIO_LEGACY0 + \ + (_channel) * RK3588_DDR_LEGACY_WINDOWS_PER_CHANNEL + (_window)) +#define RK3588_DDR_MMIO_LEGACY_GATE(_channel) \ + RK3588_DDR_MMIO_LEGACY( \ + (_channel), RK3588_DDR_LEGACY_CTRL_WINDOWS_PER_CHANNEL) + +#define RK3588_DDR_MMIO_CHANNEL(_channel) \ + (RK3588_DDR_MMIO_CHANNEL0 + (_channel)) + +#endif /* HW_MISC_RK3588_DDR_H */ diff --git a/include/hw/misc/rk3588_firmware_mmio.h b/include/hw/misc/rk3588_firmware_mmio.h new file mode 100644 index 000000000000..06c762cf8968 --- /dev/null +++ b/include/hw/misc/rk3588_firmware_mmio.h @@ -0,0 +1,21 @@ +/* + * Rockchip RK3588 vendor firmware MMIO compatibility region + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_RK3588_FIRMWARE_MMIO_H +#define HW_MISC_RK3588_FIRMWARE_MMIO_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RK3588_FIRMWARE_MMIO "rk3588-firmware-mmio" +OBJECT_DECLARE_SIMPLE_TYPE(RK3588FirmwareMMIOState, RK3588_FIRMWARE_MMIO) + +#define RK3588_FIRMWARE_MMIO_BASE 0xf7000000ULL +#define RK3588_FIRMWARE_MMIO_SIZE 0x08000000 + +#endif /* HW_MISC_RK3588_FIRMWARE_MMIO_H */ diff --git a/include/hw/misc/rk3588_grf.h b/include/hw/misc/rk3588_grf.h new file mode 100644 index 000000000000..41ed9cdf9394 --- /dev/null +++ b/include/hw/misc/rk3588_grf.h @@ -0,0 +1,27 @@ +/* + * Rockchip RK3588 PMU General Register Files + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_RK3588_GRF_H +#define HW_MISC_RK3588_GRF_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RK3588_GRF "rk3588-grf" +OBJECT_DECLARE_SIMPLE_TYPE(RK3588GRFState, RK3588_GRF) + +#define RK3588_GRF_MMIO_SIZE 0x1000 + +enum { + RK3588_GRF_MMIO_PMU0, + RK3588_GRF_MMIO_PMU1, + RK3588_GRF_MMIO_SYS, + RK3588_GRF_MMIO_COUNT, +}; + +#endif /* HW_MISC_RK3588_GRF_H */ diff --git a/include/hw/misc/rockchip_crypto_v2.h b/include/hw/misc/rockchip_crypto_v2.h new file mode 100644 index 000000000000..f795c276dfcb --- /dev/null +++ b/include/hw/misc/rockchip_crypto_v2.h @@ -0,0 +1,20 @@ +/* + * Rockchip Crypto V2 + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_ROCKCHIP_CRYPTO_V2_H +#define HW_MISC_ROCKCHIP_CRYPTO_V2_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_ROCKCHIP_CRYPTO_V2 "rockchip-crypto-v2" +OBJECT_DECLARE_SIMPLE_TYPE(RockchipCryptoV2State, ROCKCHIP_CRYPTO_V2) + +#define ROCKCHIP_CRYPTO_V2_MMIO_SIZE 0x4000 + +#endif diff --git a/include/hw/nvram/rk3588_secure_otp.h b/include/hw/nvram/rk3588_secure_otp.h new file mode 100644 index 000000000000..e1e4468fd832 --- /dev/null +++ b/include/hw/nvram/rk3588_secure_otp.h @@ -0,0 +1,20 @@ +/* + * Rockchip RK3588 secure OTP + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_NVRAM_RK3588_SECURE_OTP_H +#define HW_NVRAM_RK3588_SECURE_OTP_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RK3588_SECURE_OTP "rk3588-secure-otp" +OBJECT_DECLARE_SIMPLE_TYPE(RK3588SecureOTPState, RK3588_SECURE_OTP) + +#define RK3588_SECURE_OTP_MMIO_SIZE 0x1000 + +#endif /* HW_NVRAM_RK3588_SECURE_OTP_H */ diff --git a/include/hw/pci-host/designware.h b/include/hw/pci-host/designware.h index e82fdc5fe744..7b6d5d27d3cf 100644 --- a/include/hw/pci-host/designware.h +++ b/include/hw/pci-host/designware.h @@ -84,6 +84,7 @@ struct DesignwarePCIEHost { PCIHostState parent_obj; DesignwarePCIERoot root; + uint8_t bus_nr; struct { AddressSpace address_space; diff --git a/include/hw/pci-host/rockchip_pcie.h b/include/hw/pci-host/rockchip_pcie.h index 82fbdfee6f88..ab0e257530f6 100644 --- a/include/hw/pci-host/rockchip_pcie.h +++ b/include/hw/pci-host/rockchip_pcie.h @@ -12,14 +12,13 @@ * - DBI @ SoC-specific base - DWC core cfg/Port-Logic/iATU/MSI, * already implemented by designware.c * (via the inherited 4 KiB sysbus mmio - * plus an optional board cover over the + * plus a device-owned RAZ/WI tail over the * rest of the larger DBI window). * - APB @ SoC-specific base - Rockchip PCIE_CLIENT_* regs. Only * PCIE_CLIENT_LTSSM_STATUS (0x300) is - * load-bearing: pinned to 0x00030011 - * so rockchip_pcie_link_up() returns - * true on the first read (substitution - * policy - no analog link in QEMU). + * load-bearing: it reflects the link-up + * property so boards without an endpoint + * can expose a down link. * All other APB offsets are RAZ/WI. * - CFG @ SoC-specific base - ECAM window into the designware * outbound CFG0 viewport alias. @@ -45,13 +44,18 @@ OBJECT_DECLARE_TYPE(RockchipPCIEHost, RockchipPCIEHostClass, * (s->pci.irqs[0..3] -> INTA..INTD, s->pci.msi -> MSI). The RK wrapper * re-exports them plus three inert RK-only IRQs (err/pmc/sys). */ -#define ROCKCHIP_PCIE_ERR_IRQ 0 +#define ROCKCHIP_PCIE_MSG_IRQ 4 /* MSI parent */ +#define ROCKCHIP_PCIE_ERR_IRQ 5 #define ROCKCHIP_PCIE_LEGACY_IRQ 1 /* fans out to INTA..INTD */ -#define ROCKCHIP_PCIE_MSG_IRQ 5 /* MSI parent */ #define ROCKCHIP_PCIE_PMC_IRQ 6 #define ROCKCHIP_PCIE_SYS_IRQ 7 #define ROCKCHIP_PCIE_NUM_IRQS 8 +#define ROCKCHIP_PCIE_DBI_CORE_SIZE 0x1000 +#define ROCKCHIP_PCIE_DBI_SIZE 0x400000 +#define ROCKCHIP_PCIE_DBI_TAIL_SIZE \ + (ROCKCHIP_PCIE_DBI_SIZE - ROCKCHIP_PCIE_DBI_CORE_SIZE) + /* APB vendor register offsets (PCIE_CLIENT_*). */ #define ROCKCHIP_PCIE_APB_LTSSM_STATUS 0x300 @@ -60,6 +64,10 @@ struct RockchipPCIEHost { /* RK APB vendor register window (overlaps PCIE_CLIENT_* regs). */ MemoryRegion apb; + MemoryRegion dbi_tail; + bool link_up; + uint32_t domain; + char root_bus_path[8]; /* * Five RK-side IRQs wired to the GIC. legacy fans out into the diff --git a/include/hw/usb/rk3588_usb2_host.h b/include/hw/usb/rk3588_usb2_host.h new file mode 100644 index 000000000000..55be6be20595 --- /dev/null +++ b/include/hw/usb/rk3588_usb2_host.h @@ -0,0 +1,30 @@ +/* + * Rockchip RK3588 USB2 host register shim + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_USB_RK3588_USB2_HOST_H +#define HW_USB_RK3588_USB2_HOST_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RK3588_USB2_HOST "rk3588-usb2-host" +OBJECT_DECLARE_SIMPLE_TYPE(RK3588USB2HostState, RK3588_USB2_HOST) + +#define RK3588_USB2_HOST_MMIO_SIZE 0x40000 + +enum { + RK3588_USB2_HOST_EHCI0, + RK3588_USB2_HOST_OHCI0, + RK3588_USB2_HOST_EHCI1, + RK3588_USB2_HOST_OHCI1, + RK3588_USB2_HOST_MMIO_COUNT, +}; + +void rk3588_usb2_host_set_active(RK3588USB2HostState *s, bool active); + +#endif diff --git a/tests/functional/aarch64/meson.build b/tests/functional/aarch64/meson.build index 23a4875dce25..44df820d9665 100644 --- a/tests/functional/aarch64/meson.build +++ b/tests/functional/aarch64/meson.build @@ -12,6 +12,7 @@ test_aarch64_timeouts = { 'reverse_debug' : 180, 'rk3588_zvm' : 240, 'rk3588_zvm_linux_guest' : 1200, + 'rock5b_plus' : 600, 'rme_virt' : 1200, 'rme_sbsaref' : 1200, 'sbsaref_alpine' : 1200, @@ -45,6 +46,7 @@ tests_aarch64_system_thorough = [ 'reverse_debug', 'rk3588_zvm', 'rk3588_zvm_linux_guest', + 'rock5b_plus', 'rme_virt', 'rme_sbsaref', 'sbsaref', diff --git a/tests/functional/aarch64/test_rock5b_plus.py b/tests/functional/aarch64/test_rock5b_plus.py new file mode 100644 index 000000000000..79840adf9403 --- /dev/null +++ b/tests/functional/aarch64/test_rock5b_plus.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +# +# Functional tests for the Radxa ROCK 5B+ +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset, LinuxKernelTest +from qemu_test import exec_command_and_wait_for_pattern + + +class Rock5BPlusMachine(LinuxKernelTest): + + ASSET_URL = ( + 'https://github.com/processmission/qemu/releases/download/' + 'rock-5b-plus-rsdk-b2-qemu1/' + ) + ASSET_KERNEL = Asset( + ASSET_URL + 'Image-6.1.43-15-rk2312', + '356a9085a1bc5144aeb19c7eb0bb21ab446ad26d24074e55be0b5c901b64510a') + ASSET_INITRAMFS = Asset( + ASSET_URL + 'rock-5b-plus-initramfs.cpio.gz', + '61167f83a4dad3de82a7f91a3cd00aaf5aa31210b8e00be8ebdd2cd77138b520') + ASSET_FIRMWARE_DISK = Asset( + ASSET_URL + 'rock-5b-plus-rsdk-b2-linux.raw.xz', + 'f63953e48e830354f5f31e90382f1e1171b919d20aaa737ac20a1d5070759342') + + KERNEL_COMMAND_LINE = ( + 'console=ttyS2,1500000n8 ' + 'earlycon=uart8250,mmio32,0xfeb50000,1500000n8 ' + 'keep_bootcon loglevel=8 rdinit=/init' + ) + + def setUp(self): + super().setUp() + self.set_machine('rock-5b-plus') + self.require_accelerator('tcg') + self.vm.set_console() + + def add_common_args(self): + self.vm.add_args( + '-accel', 'tcg', + '-smp', '1', + '-m', '1G', + '-display', 'none', + '-monitor', 'none', + '-no-reboot', + ) + + def wait_for_linux_shell(self): + self.wait_for_console_pattern('Linux version 6.1.43-15-rk2312') + self.wait_for_console_pattern('Machine model: Radxa ROCK 5B+') + self.wait_for_console_pattern('Run /init as init process') + self.wait_for_console_pattern('=== init done, exec sh ===') + self.wait_for_console_pattern('~ #') + exec_command_and_wait_for_pattern( + self, 'uname -r', '6.1.43-15-rk2312') + + def test_aarch64_rock5b_plus_direct_linux_boot(self): + kernel_path = self.ASSET_KERNEL.fetch() + initramfs_path = self.ASSET_INITRAMFS.fetch() + + self.add_common_args() + self.vm.add_args( + '-kernel', kernel_path, + '-initrd', initramfs_path, + '-append', self.KERNEL_COMMAND_LINE, + ) + + self.vm.launch() + self.wait_for_linux_shell() + + def test_aarch64_rock5b_plus_firmware_linux_boot(self): + disk_path = self.uncompress( + self.ASSET_FIRMWARE_DISK, + target='rock-5b-plus-rsdk-b2-linux.raw') + + self.add_common_args() + self.vm.add_args( + '-drive', + f'file={disk_path},if=sd,index=0,format=raw,snapshot=on', + ) + + self.vm.launch() + self.wait_for_console_pattern('DDR 9fffbe1e78') + self.wait_for_console_pattern('U-Boot SPL rknext-2017.09-33') + for component in ('atf-1', 'uboot', 'fdt', 'atf-2', 'atf-3'): + self.wait_for_console_pattern(f'## Checking {component}') + self.wait_for_console_pattern('+ OK') + self.wait_for_console_pattern( + 'Jumping to U-Boot(0x00200000) via ARM Trusted Firmware') + self.wait_for_console_pattern('U-Boot rknext-2017.09-33') + self.wait_for_console_pattern('Found /boot/extlinux/extlinux.conf') + self.wait_for_console_pattern('Starting kernel ...') + self.wait_for_linux_shell() + + +if __name__ == '__main__': + LinuxKernelTest.main() diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 0cb890bab848..fd23793266e7 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -276,7 +276,9 @@ qtests_aarch64 = \ (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and config_all_devices.has_key('CONFIG_ARM_VIRT') ? ['iommu-smmuv3-test'] : []) + \ - (config_all_devices.has_key('CONFIG_RK3588_EVB') ? ['rk3588-test'] : []) + \ + (config_all_devices.has_key('CONFIG_RK3588_EVB') ? ['rk3588-evb-test'] : []) + \ + (config_all_devices.has_key('CONFIG_RK3588S_ROC_PC') ? ['rk3588s-roc-pc-test'] : []) + \ + (config_all_devices.has_key('CONFIG_ROCK_5B_PLUS') ? ['rock5b-plus-test'] : []) + \ (config_all_devices.has_key('CONFIG_PHYTIUMPI') ? ['phytiumpi-test'] : []) + \ qtests_cxl + \ ['arm-cpu-features', @@ -417,6 +419,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()], + 'rock5b-plus-test': [fdt], 'spacemit-k3-test': [fdt], 'tpm-crb-swtpm-test': [io, tpmemu_files], 'tpm-crb-test': [io, tpmemu_files], diff --git a/tests/qtest/rk3588-test.c b/tests/qtest/rk3588-evb-test.c similarity index 94% rename from tests/qtest/rk3588-test.c rename to tests/qtest/rk3588-evb-test.c index e5f43e27daf7..d9e7d06d18d6 100644 --- a/tests/qtest/rk3588-test.c +++ b/tests/qtest/rk3588-evb-test.c @@ -1,5 +1,5 @@ /* - * QTest for the local-only Rockchip RK3588 board machine models + * QTest for the Rockchip RK3588 EVB machine model * * Copyright (c) 2026 Chao Liu * @@ -46,6 +46,8 @@ #define RK3588_PMU0_GRF_WARM_BOOT_MAGIC 0x0084 #define RK3588_PMU0_GRF_WARM_BOOT_MAGIC_VALUE 0x13579bdf #define RK3588_PMU1_GRF_BASE 0xfd58a000ULL +#define RK3588_SYS_GRF_CORE_STATUS 0xfd58c38cULL +#define RK3588_SYS_GRF_CORE_STATUS_ALL 0xf0 #define RK3588_ATF_DDR_RUNTIME_ADDR 0x0008d000ULL #define RK3588_ATF_DDR_GLOBAL_PTR 0x0008d0a8ULL #define RK3588_ATF_TIMER_PTR 0x0008d0b0ULL @@ -201,7 +203,6 @@ #define GPIO_PIN0_SET (GPIO_PIN0_WE | GPIO_PIN0) #define GPIO_PIN0_CLEAR GPIO_PIN0_WE #define RK3588_EVB_MACHINE "rk3588-evb" -#define RK3588S_ROC_PC_MACHINE "rk3588s-roc-pc" static QTestState *rk3588_qtest_start(unsigned int cpus) { @@ -215,12 +216,6 @@ static QTestState *rk3588_qtest_start_zvm_ram(void) ",zvm-ram=on -smp 1 -m 512M"); } -static QTestState *rk3588s_roc_pc_qtest_start(unsigned int cpus) -{ - return qtest_initf("-machine " RK3588S_ROC_PC_MACHINE - " -smp %u -m 512M", cpus); -} - static void test_rk3588_machine_creation(void) { QTestState *qts = rk3588_qtest_start(1); @@ -421,6 +416,8 @@ static void test_rk3588_firmware_registers(void) g_assert_cmphex(qtest_readl(qts, RK3588_PMU0_GRF_BASE + RK3588_PMU0_GRF_WARM_BOOT_MAGIC), ==, RK3588_PMU0_GRF_WARM_BOOT_MAGIC_VALUE); + g_assert_cmphex(qtest_readl(qts, RK3588_SYS_GRF_CORE_STATUS), ==, + RK3588_SYS_GRF_CORE_STATUS_ALL); g_assert_cmphex(qtest_readq(qts, RK3588_ATF_DDR_GLOBAL_PTR), ==, RK3588_ATF_DDR_DESCRIPTOR); g_assert_cmphex(qtest_readq(qts, RK3588_ATF_TIMER_PTR), ==, @@ -793,31 +790,6 @@ static void test_rk3588_zvm_ram(void) qtest_quit(qts); } -static void test_rk3588s_roc_pc_machine_creation(void) -{ - QTestState *qts = rk3588s_roc_pc_qtest_start(1); - - qtest_writel(qts, RK3588_RAM_BASE, 0x3588); - g_assert_cmphex(qtest_readl(qts, RK3588_RAM_BASE), ==, 0x3588); - g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_VERID), ==, - DW_MMC_VERID_270A); - g_assert_cmphex(qtest_readl(qts, RK3588_GMAC1_BASE + DWMAC4_MAC_VERSION), - ==, DWMAC4_SNPSVER_0x51); - - /* - * ROC-RK3588S-PC is the ZVM target board. Its machine default maps the - * fixed ZVM guest/shared RAM windows that are real RAM on the 8 GiB board. - */ - qtest_writel(qts, RK3588_ZVM_SHARED_RAM_BASE + 0x408, 0xe7f00408); - g_assert_cmphex(qtest_readl(qts, RK3588_ZVM_SHARED_RAM_BASE + 0x408), ==, - 0xe7f00408); - qtest_writeq(qts, RK3588_ZVM_HIGH_RAM_BASE, 0x100000000ULL); - g_assert_cmphex(qtest_readq(qts, RK3588_ZVM_HIGH_RAM_BASE), ==, - 0x100000000ULL); - - qtest_quit(qts); -} - int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); @@ -826,10 +798,6 @@ int main(int argc, char **argv) g_test_skip(RK3588_EVB_MACHINE " machine not available"); return 0; } - if (!qtest_has_machine(RK3588S_ROC_PC_MACHINE)) { - g_test_skip(RK3588S_ROC_PC_MACHINE " machine not available"); - return 0; - } if (!qtest_has_device("arm-gicv3")) { g_test_skip("arm-gicv3 device not available"); return 0; @@ -843,20 +811,20 @@ int main(int argc, char **argv) return 0; } - qtest_add_func("/rk3588/machine-creation", test_rk3588_machine_creation); - qtest_add_func("/rk3588/smp-creation", test_rk3588_smp_creation); - qtest_add_func("/rk3588/peripheral-mmio", test_rk3588_peripheral_mmio); - qtest_add_func("/rk3588/sdmmc-scmi", test_rk3588_sdmmc_scmi); - qtest_add_func("/rk3588/firmware-registers", + qtest_add_func("/rk3588-evb/machine-creation", + test_rk3588_machine_creation); + qtest_add_func("/rk3588-evb/smp-creation", test_rk3588_smp_creation); + qtest_add_func("/rk3588-evb/peripheral-mmio", + test_rk3588_peripheral_mmio); + qtest_add_func("/rk3588-evb/sdmmc-scmi", test_rk3588_sdmmc_scmi); + qtest_add_func("/rk3588-evb/firmware-registers", test_rk3588_firmware_registers); - qtest_add_func("/rk3588/usb2-host-firmware-windows", + qtest_add_func("/rk3588-evb/usb2-host-firmware-windows", test_rk3588_usb2_host_firmware_windows); - qtest_add_func("/rk3588/its-lpi", test_rk3588_its_lpi); - qtest_add_func("/rk3588/pcie", test_rk3588_pcie); - qtest_add_func("/rk3588/gpio-bank", test_rk3588_gpio_bank); - qtest_add_func("/rk3588/zvm-ram", test_rk3588_zvm_ram); - qtest_add_func("/rk3588s-roc-pc/machine-creation", - test_rk3588s_roc_pc_machine_creation); + qtest_add_func("/rk3588-evb/its-lpi", test_rk3588_its_lpi); + qtest_add_func("/rk3588-evb/pcie", test_rk3588_pcie); + qtest_add_func("/rk3588-evb/gpio-bank", test_rk3588_gpio_bank); + qtest_add_func("/rk3588-evb/zvm-ram", test_rk3588_zvm_ram); return g_test_run(); } diff --git a/tests/qtest/rk3588s-roc-pc-test.c b/tests/qtest/rk3588s-roc-pc-test.c new file mode 100644 index 000000000000..67f87f98e320 --- /dev/null +++ b/tests/qtest/rk3588s-roc-pc-test.c @@ -0,0 +1,69 @@ +/* + * QTest for the Firefly ROC-RK3588S-PC machine model + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest.h" + +#define RK3588_RAM_BASE 0x00200000ULL +#define RK3588_SDMMC_BASE 0xfe2c0000ULL +#define RK3588_GMAC1_BASE 0xfe1c0000ULL +#define RK3588_ZVM_SHARED_RAM_BASE 0xe7f00000ULL +#define RK3588_ZVM_HIGH_RAM_BASE 0x100000000ULL + +#define DW_MMC_VERID 0x006c +#define DW_MMC_VERID_270A 0x0000270a +#define DWMAC4_MAC_VERSION 0x0110 +#define DWMAC4_SNPSVER_0x51 0x00000051 + +#define RK3588S_ROC_PC_MACHINE "rk3588s-roc-pc" + +static QTestState *rk3588s_roc_pc_qtest_start(unsigned int cpus) +{ + return qtest_initf("-machine " RK3588S_ROC_PC_MACHINE + " -smp %u -m 512M", cpus); +} + +static void test_rk3588s_roc_pc_machine_creation(void) +{ + QTestState *qts = rk3588s_roc_pc_qtest_start(1); + + qtest_writel(qts, RK3588_RAM_BASE, 0x3588); + g_assert_cmphex(qtest_readl(qts, RK3588_RAM_BASE), ==, 0x3588); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_VERID), ==, + DW_MMC_VERID_270A); + g_assert_cmphex(qtest_readl(qts, RK3588_GMAC1_BASE + DWMAC4_MAC_VERSION), + ==, DWMAC4_SNPSVER_0x51); + + /* + * ROC-RK3588S-PC is the ZVM target board. Its machine default maps the + * fixed ZVM guest/shared RAM windows that are real RAM on the 8 GiB board. + */ + qtest_writel(qts, RK3588_ZVM_SHARED_RAM_BASE + 0x408, 0xe7f00408); + g_assert_cmphex(qtest_readl(qts, RK3588_ZVM_SHARED_RAM_BASE + 0x408), ==, + 0xe7f00408); + qtest_writeq(qts, RK3588_ZVM_HIGH_RAM_BASE, 0x100000000ULL); + g_assert_cmphex(qtest_readq(qts, RK3588_ZVM_HIGH_RAM_BASE), ==, + 0x100000000ULL); + + qtest_quit(qts); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + if (!qtest_has_machine(RK3588S_ROC_PC_MACHINE)) { + g_test_skip(RK3588S_ROC_PC_MACHINE " machine not available"); + return 0; + } + + qtest_add_func("/rk3588s-roc-pc/machine-creation", + test_rk3588s_roc_pc_machine_creation); + + return g_test_run(); +} diff --git a/tests/qtest/rock5b-plus-test.c b/tests/qtest/rock5b-plus-test.c new file mode 100644 index 000000000000..fccabe113d6d --- /dev/null +++ b/tests/qtest/rock5b-plus-test.c @@ -0,0 +1,550 @@ +/* + * QTest for the Radxa ROCK 5B+ machine + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include +#include "qemu/bitops.h" +#include "qobject/qdict.h" +#include "qobject/qlist.h" +#include "libqtest.h" + +#define ROCK_5B_PLUS_MACHINE "rock-5b-plus" + +#define RK3588_ATAGS_BASE 0x001fe000ULL +#define RK3588_RAM_BASE 0x00200000ULL +#define RK3588_PMU1_GRF_BASE 0xfd58a000ULL +#define RK3588_CRYPTO_BASE 0xfe370000ULL +#define RK3588_PCIE3X4_APB_BASE 0xfe150000ULL +#define RK3588_PCIE3X4_DBI_BASE 0xa40000000ULL +#define RK3588_PCIE3X2_APB_BASE 0xfe160000ULL +#define RK3588_PCIE3X2_DBI_BASE 0xa40400000ULL +#define RK3588_PCIE3X2_CFG_BASE 0xf1000000ULL +#define RK3588_GMAC0_BASE 0xfe1b0000ULL +#define RK3588_GMAC1_BASE 0xfe1c0000ULL +#define RK3588_SDMMC_BASE 0xfe2c0000ULL +#define RK3588_SDHCI_BASE 0xfe2e0000ULL +#define RK3588_GICD_BASE 0xfe600000ULL +#define RK3588_SECURE_OTP_BASE 0xfe3a0000ULL +#define RK3588_UART2_BASE 0xfeb50000ULL + +#define PMU1_GRF_OS_REG2 0x0208 +#define PMU1_GRF_OS_REG3 0x020c +#define RK3588_DDRTYPE_LOW_SHIFT 13 +#define RK3588_DDRTYPE_HIGH_SHIFT 12 +#define RK3588_DDRTYPE_LOW_MASK 0x7 +#define RK3588_DDRTYPE_HIGH_MASK 0x1 +#define RK3588_LPDDR5 9 + +#define RK3588_ATAG_CORE 0x54410001 +#define RK3588_ATAG_CORE_WORDS 5 +#define RK3588_ATAG_DDR_MEM 0x54410052 +#define RK3588_ATAG_DDR_MEM_WORDS 48 + +#define DWC_PCIE_VENDOR_DEVICE 0x0000 +#define DWC_PCIE_LTSSM_STATUS 0x0300 +#define DWC_PCIE_ATU_VIEWPORT 0x0900 +#define DWC_PCIE_ATU_CR1 0x0904 +#define DWC_PCIE_ATU_CR2 0x0908 +#define DWC_PCIE_ATU_LOWER_BASE 0x090c +#define DWC_PCIE_ATU_UPPER_BASE 0x0910 +#define DWC_PCIE_ATU_LIMIT 0x0914 +#define DWC_PCIE_ATU_LOWER_TARGET 0x0918 +#define DWC_PCIE_ATU_UPPER_TARGET 0x091c +#define DWC_PCIE_ATU_TYPE_CFG0 0x4 +#define DWC_PCIE_ATU_ENABLE BIT(31) +#define DWMAC4_MAC_VERSION 0x0110 +#define DWMAC4_SNPSVER_0x51 0x00000051 +#define DW_MMC_VERID 0x006c +#define DW_MMC_VERID_270A 0x0000270a +#define SDHCI_CAPABILITIES 0x0040 +#define GICD_TYPER 0x0004 +#define UART_LSR (5 << 2) +#define UART_LSR_THRE 0x20 +#define UART_LSR_TEMT 0x40 +#define SECURE_OTP_DOUT 0x20 +#define SECURE_OTP_INT_STATUS 0x84 +#define SECURE_OTP_UNIMPLEMENTED 0x100 +#define SECURE_OTP_READ_DONE 0x2 +#define CRYPTO_RST_CTL 0x004 +#define CRYPTO_DMA_INT_ST 0x00c +#define CRYPTO_DMA_CTL 0x010 +#define CRYPTO_DMA_LLI_ADDR 0x014 +#define CRYPTO_FIFO_CTL 0x040 +#define CRYPTO_HASH_CTL 0x048 +#define CRYPTO_HASH_DOUT_0 0x3a0 +#define CRYPTO_HASH_VALID 0x3e4 +#define CRYPTO_WRITE_MASK(value) ((uint32_t)(value) << 16) +#define CRYPTO_DMA_SRC_ITEM_DONE 0x4 +#define CRYPTO_DMA_LIST_ERR 0x20 +#define CRYPTO_HASH_VALID_BIT 0x1 +#define CRYPTO_FIFO_BYTESWAP 0x3 +#define CRYPTO_HASH_SHA256_PAD_ENABLE 0x25 +#define CRYPTO_LLI_USER_HASH_START_LAST 0x7 +#define CRYPTO_LLI_DMA_LAST_SRC_DONE 0x401 + +static QTestState *rock_5b_plus_qtest_start(unsigned int cpus) +{ + return qtest_initf("-machine " ROCK_5B_PLUS_MACHINE + " -smp %u -m 512M", cpus); +} + +static void assert_fdt_cells(const void *fdt, int node, const char *property, + const uint32_t *expected, size_t count) +{ + const fdt32_t *cells; + int length; + + cells = fdt_getprop(fdt, node, property, &length); + g_assert_nonnull(cells); + g_assert_cmpint(length, ==, (int)(count * sizeof(*cells))); + + for (size_t i = 0; i < count; i++) { + g_assert_cmphex(fdt32_to_cpu(cells[i]), ==, expected[i]); + } +} + +static void test_rock_5b_plus_pcie3x2_fdt(void) +{ + static const uint32_t kernel_insn = GUINT32_TO_LE(0x14000000); + static const uint32_t expected_reg[] = { + 0x0000000a, 0x40400000, 0x00000000, 0x00400000, + 0x00000000, 0xfe160000, 0x00000000, 0x00010000, + 0x00000000, 0xf1000000, 0x00000000, 0x00100000, + }; + static const uint32_t expected_interrupts[] = { + 0, 258, 4, 0, + 0, 257, 4, 0, + 0, 256, 4, 0, + 0, 255, 4, 0, + 0, 254, 4, 0, + }; + static const uint32_t expected_bus_range[] = { 0x10, 0x1f }; + static const uint32_t expected_ranges[] = { + 0x01000000, 0x00000000, 0xf1100000, + 0x00000000, 0xf1100000, 0x00000000, 0x00100000, + 0x02000000, 0x00000000, 0xf1200000, + 0x00000000, 0xf1200000, 0x00000000, 0x00e00000, + 0x03000000, 0x00000009, 0x40000000, + 0x00000009, 0x40000000, 0x00000000, 0x40000000, + }; + g_autofree char *kernel_path = NULL; + g_autofree char *dtb_path = NULL; + g_autofree char *machine_arg = NULL; + g_autofree char *dtb = NULL; + g_autofree char *stderr_buf = NULL; + g_autoptr(GError) error = NULL; + gsize dtb_size; + int kernel_fd, dtb_fd, exit_status; + int pcie, its; + const fdt32_t *cells; + int length; + bool spawned; + + kernel_fd = g_file_open_tmp("rock5b-plus-kernel-XXXXXX", &kernel_path, + &error); + g_assert_no_error(error); + g_assert_cmpint(kernel_fd, >=, 0); + g_assert_cmpint(close(kernel_fd), ==, 0); + g_assert_true(g_file_set_contents(kernel_path, + (const char *)&kernel_insn, + sizeof(kernel_insn), &error)); + g_assert_no_error(error); + + dtb_fd = g_file_open_tmp("rock5b-plus-dtb-XXXXXX", &dtb_path, &error); + g_assert_no_error(error); + g_assert_cmpint(dtb_fd, >=, 0); + g_assert_cmpint(close(dtb_fd), ==, 0); + + machine_arg = g_strdup_printf(ROCK_5B_PLUS_MACHINE ",dumpdtb=%s", + dtb_path); + const char *argv[] = { + qtest_qemu_binary(NULL), + "-machine", machine_arg, + "-cpu", "cortex-a76", + "-smp", "1", + "-m", "512M", + "-kernel", kernel_path, + "-display", "none", + "-serial", "none", + "-nodefaults", + NULL, + }; + + 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); + if (!g_spawn_check_exit_status(exit_status, &error)) { + g_error("QEMU failed to dump the ROCK 5B+ DTB: %s\n%s", + error->message, stderr_buf ? stderr_buf : ""); + } + + g_assert_true(g_file_get_contents(dtb_path, &dtb, &dtb_size, &error)); + g_assert_no_error(error); + g_assert_cmpint(fdt_check_header(dtb), ==, 0); + g_assert_cmpuint(fdt_totalsize(dtb), <=, dtb_size); + + pcie = fdt_path_offset(dtb, "/pcie@fe160000"); + g_assert_cmpint(pcie, >=, 0); + g_assert_cmpint(fdt_node_check_compatible(dtb, pcie, + "rockchip,rk3588-pcie"), ==, + 0); + assert_fdt_cells(dtb, pcie, "reg", expected_reg, + ARRAY_SIZE(expected_reg)); + assert_fdt_cells(dtb, pcie, "interrupts", expected_interrupts, + ARRAY_SIZE(expected_interrupts)); + assert_fdt_cells(dtb, pcie, "bus-range", expected_bus_range, + ARRAY_SIZE(expected_bus_range)); + assert_fdt_cells(dtb, pcie, "ranges", expected_ranges, + ARRAY_SIZE(expected_ranges)); + + cells = fdt_getprop(dtb, pcie, "num-lanes", &length); + g_assert_nonnull(cells); + g_assert_cmpint(length, ==, (int)sizeof(*cells)); + g_assert_cmphex(fdt32_to_cpu(cells[0]), ==, 2); + + cells = fdt_getprop(dtb, pcie, "linux,pci-domain", &length); + g_assert_nonnull(cells); + g_assert_cmpint(length, ==, (int)sizeof(*cells)); + g_assert_cmphex(fdt32_to_cpu(cells[0]), ==, 1); + + cells = fdt_getprop(dtb, pcie, "resets", &length); + g_assert_nonnull(cells); + g_assert_cmpint(length, ==, 4 * (int)sizeof(*cells)); + g_assert_cmphex(fdt32_to_cpu(cells[0]), !=, 0); + g_assert_cmphex(fdt32_to_cpu(cells[0]), ==, fdt32_to_cpu(cells[2])); + g_assert_cmphex(fdt32_to_cpu(cells[1]), ==, 526); + g_assert_cmphex(fdt32_to_cpu(cells[3]), ==, 541); + + its = fdt_path_offset(dtb, + "/interrupt-controller@fe600000/" + "msi-controller@fe660000"); + g_assert_cmpint(its, >=, 0); + cells = fdt_getprop(dtb, pcie, "msi-map", &length); + g_assert_nonnull(cells); + g_assert_cmpint(length, ==, 4 * (int)sizeof(*cells)); + g_assert_cmphex(fdt32_to_cpu(cells[0]), ==, 0x1000); + g_assert_cmphex(fdt32_to_cpu(cells[1]), ==, + fdt_get_phandle(dtb, its)); + g_assert_cmphex(fdt32_to_cpu(cells[2]), ==, 0x1000); + g_assert_cmphex(fdt32_to_cpu(cells[3]), ==, 0x1000); + + g_assert_cmpint(g_unlink(kernel_path), ==, 0); + g_assert_cmpint(g_unlink(dtb_path), ==, 0); +} + +static void test_rock_5b_plus_pcie3x2_bus_number(void) +{ + QTestState *qts = rock_5b_plus_qtest_start(1); + QDict *response; + QList *buses; + QListEntry *entry; + uint32_t dbi_id; + bool bus_10_found = false; + + response = qtest_qmp(qts, "{ 'execute': 'query-pci' }"); + g_assert(qdict_haskey(response, "return")); + buses = qdict_get_qlist(response, "return"); + + QLIST_FOREACH_ENTRY(buses, entry) { + QDict *bus = qobject_to(QDict, qlist_entry_obj(entry)); + + if (qdict_get_int(bus, "bus") == 0x10) { + bus_10_found = true; + break; + } + } + g_assert_true(bus_10_found); + qobject_unref(response); + + qtest_writel(qts, RK3588_PCIE3X2_DBI_BASE + DWC_PCIE_ATU_VIEWPORT, 0); + qtest_writel(qts, RK3588_PCIE3X2_DBI_BASE + DWC_PCIE_ATU_CR1, + DWC_PCIE_ATU_TYPE_CFG0); + qtest_writel(qts, RK3588_PCIE3X2_DBI_BASE + DWC_PCIE_ATU_LOWER_BASE, + RK3588_PCIE3X2_CFG_BASE); + qtest_writel(qts, RK3588_PCIE3X2_DBI_BASE + DWC_PCIE_ATU_UPPER_BASE, 0); + qtest_writel(qts, RK3588_PCIE3X2_DBI_BASE + DWC_PCIE_ATU_LIMIT, + RK3588_PCIE3X2_CFG_BASE + 0xfffff); + qtest_writel(qts, RK3588_PCIE3X2_DBI_BASE + DWC_PCIE_ATU_LOWER_TARGET, + 0x10 << 24); + qtest_writel(qts, RK3588_PCIE3X2_DBI_BASE + DWC_PCIE_ATU_UPPER_TARGET, 0); + qtest_writel(qts, RK3588_PCIE3X2_DBI_BASE + DWC_PCIE_ATU_CR2, + DWC_PCIE_ATU_ENABLE); + + dbi_id = qtest_readl(qts, RK3588_PCIE3X2_DBI_BASE + + DWC_PCIE_VENDOR_DEVICE); + g_assert_cmphex(dbi_id, !=, UINT32_MAX); + g_assert_cmphex(qtest_readl(qts, RK3588_PCIE3X2_CFG_BASE), ==, dbi_id); + + qtest_quit(qts); +} + +static void test_rock_5b_plus_machine_creation(void) +{ + QTestState *qts = rock_5b_plus_qtest_start(1); + uint64_t sdhci_caps; + uint32_t pcie_id; + uint32_t sys_reg2; + uint32_t sys_reg3; + + qtest_writel(qts, RK3588_RAM_BASE, 0x5b5b3588); + g_assert_cmphex(qtest_readl(qts, RK3588_RAM_BASE), ==, 0x5b5b3588); + + g_assert_cmphex(qtest_readb(qts, RK3588_UART2_BASE + UART_LSR) & + (UART_LSR_THRE | UART_LSR_TEMT), ==, + UART_LSR_THRE | UART_LSR_TEMT); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_VERID), ==, + DW_MMC_VERID_270A); + + sdhci_caps = qtest_readl(qts, RK3588_SDHCI_BASE + SDHCI_CAPABILITIES); + sdhci_caps |= (uint64_t)qtest_readl(qts, RK3588_SDHCI_BASE + + SDHCI_CAPABILITIES + 4) << 32; + g_assert_cmphex(sdhci_caps, !=, 0); + g_assert_cmphex(sdhci_caps, !=, UINT64_MAX); + + pcie_id = qtest_readl(qts, RK3588_PCIE3X4_DBI_BASE + + DWC_PCIE_VENDOR_DEVICE); + g_assert_cmphex(pcie_id, !=, 0); + g_assert_cmphex(pcie_id, !=, UINT32_MAX); + g_assert_cmphex(qtest_readl(qts, RK3588_PCIE3X4_APB_BASE + + DWC_PCIE_LTSSM_STATUS), ==, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_PCIE3X4_DBI_BASE + + 0x3ffffc), ==, 0); + + pcie_id = qtest_readl(qts, RK3588_PCIE3X2_DBI_BASE + + DWC_PCIE_VENDOR_DEVICE); + g_assert_cmphex(pcie_id, !=, 0); + g_assert_cmphex(pcie_id, !=, UINT32_MAX); + g_assert_cmphex(qtest_readl(qts, RK3588_PCIE3X2_APB_BASE + + DWC_PCIE_LTSSM_STATUS), ==, 0); + qtest_writel(qts, RK3588_PCIE3X2_DBI_BASE + 0x100010, + UINT32_MAX); + g_assert_cmphex(qtest_readl(qts, RK3588_PCIE3X2_DBI_BASE + + 0x100010), ==, 0); + + g_assert_cmphex(qtest_readl(qts, RK3588_GICD_BASE + GICD_TYPER), !=, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_GICD_BASE + GICD_TYPER), !=, + UINT32_MAX); + + sys_reg2 = qtest_readl(qts, RK3588_PMU1_GRF_BASE + PMU1_GRF_OS_REG2); + sys_reg3 = qtest_readl(qts, RK3588_PMU1_GRF_BASE + PMU1_GRF_OS_REG3); + g_assert_cmphex((sys_reg2 >> RK3588_DDRTYPE_LOW_SHIFT) & + RK3588_DDRTYPE_LOW_MASK, ==, + RK3588_LPDDR5 & RK3588_DDRTYPE_LOW_MASK); + g_assert_cmphex((sys_reg3 >> RK3588_DDRTYPE_HIGH_SHIFT) & + RK3588_DDRTYPE_HIGH_MASK, ==, + (RK3588_LPDDR5 >> 3) & RK3588_DDRTYPE_HIGH_MASK); + + g_assert_cmphex(qtest_readl(qts, RK3588_ATAGS_BASE), ==, + RK3588_ATAG_CORE_WORDS); + g_assert_cmphex(qtest_readl(qts, RK3588_ATAGS_BASE + 4), ==, + RK3588_ATAG_CORE); + g_assert_cmphex(qtest_readl(qts, RK3588_ATAGS_BASE + 20), ==, + RK3588_ATAG_DDR_MEM_WORDS); + g_assert_cmphex(qtest_readl(qts, RK3588_ATAGS_BASE + 24), ==, + RK3588_ATAG_DDR_MEM); + g_assert_cmphex(qtest_readl(qts, RK3588_ATAGS_BASE + 28), ==, 1); + g_assert_cmphex(qtest_readq(qts, RK3588_ATAGS_BASE + 36), ==, 0); + g_assert_cmphex(qtest_readq(qts, RK3588_ATAGS_BASE + 44), ==, + RK3588_RAM_BASE + 512ULL * 1024 * 1024); + g_assert_cmphex(qtest_readl(qts, RK3588_ATAGS_BASE + 20 + + RK3588_ATAG_DDR_MEM_WORDS * + sizeof(uint32_t)), ==, 0); + + /* ROCK 5B+ uses a PCIe RTL8125 NIC, not either RK3588 DWMAC. */ + g_assert_cmphex(qtest_readl(qts, RK3588_GMAC0_BASE + + DWMAC4_MAC_VERSION), !=, + DWMAC4_SNPSVER_0x51); + g_assert_cmphex(qtest_readl(qts, RK3588_GMAC1_BASE + + DWMAC4_MAC_VERSION), !=, + DWMAC4_SNPSVER_0x51); + + qtest_system_reset(qts); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_VERID), ==, + DW_MMC_VERID_270A); + + qtest_quit(qts); +} + +static void test_rock_5b_plus_smp_creation(void) +{ + QTestState *qts = rock_5b_plus_qtest_start(8); + + qtest_quit(qts); +} + +static void test_rock_5b_plus_unfused_secure_otp(void) +{ + QTestState *qts = rock_5b_plus_qtest_start(1); + + g_assert_cmphex(qtest_readl(qts, RK3588_SECURE_OTP_BASE + + SECURE_OTP_INT_STATUS), ==, + SECURE_OTP_READ_DONE); + g_assert_cmphex(qtest_readl(qts, RK3588_SECURE_OTP_BASE + + SECURE_OTP_DOUT), ==, 0); + + qtest_writel(qts, RK3588_SECURE_OTP_BASE + SECURE_OTP_DOUT, + UINT32_MAX); + qtest_writel(qts, RK3588_SECURE_OTP_BASE + SECURE_OTP_INT_STATUS, 0); + qtest_writel(qts, RK3588_SECURE_OTP_BASE + SECURE_OTP_UNIMPLEMENTED, + UINT32_MAX); + g_assert_cmphex(qtest_readl(qts, RK3588_SECURE_OTP_BASE + + SECURE_OTP_DOUT), ==, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_SECURE_OTP_BASE + + SECURE_OTP_INT_STATUS), ==, + SECURE_OTP_READ_DONE); + g_assert_cmphex(qtest_readl(qts, RK3588_SECURE_OTP_BASE + + SECURE_OTP_UNIMPLEMENTED), ==, 0); + + qtest_system_reset(qts); + g_assert_cmphex(qtest_readl(qts, RK3588_SECURE_OTP_BASE + + SECURE_OTP_DOUT), ==, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_SECURE_OTP_BASE + + SECURE_OTP_INT_STATUS), ==, + SECURE_OTP_READ_DONE); + g_assert_cmphex(qtest_readl(qts, RK3588_SECURE_OTP_BASE + + SECURE_OTP_UNIMPLEMENTED), ==, 0); + + qtest_quit(qts); +} + +static void test_rock_5b_plus_crypto_sha256(void) +{ + static const uint8_t input[] = { 'a', 'b', 'c' }; + static const uint32_t expected[] = { + 0xba7816bf, 0x8f01cfea, 0x414140de, 0x5dae2223, + 0xb00361a3, 0x96177a9c, 0xb410ff61, 0xf20015ad, + }; + const uint64_t input_addr = RK3588_RAM_BASE; + const uint64_t lli_addr = RK3588_RAM_BASE + 0x1000; + QTestState *qts = rock_5b_plus_qtest_start(1); + uint32_t status = 0; + + qtest_memwrite(qts, input_addr, input, sizeof(input)); + qtest_writel(qts, lli_addr + 0x00, input_addr); + qtest_writel(qts, lli_addr + 0x04, sizeof(input)); + qtest_writel(qts, lli_addr + 0x08, 0); + qtest_writel(qts, lli_addr + 0x0c, 0); + qtest_writel(qts, lli_addr + 0x10, CRYPTO_LLI_USER_HASH_START_LAST); + qtest_writel(qts, lli_addr + 0x14, 0); + qtest_writel(qts, lli_addr + 0x18, CRYPTO_LLI_DMA_LAST_SRC_DONE); + qtest_writel(qts, lli_addr + 0x1c, 0); + + qtest_writel(qts, RK3588_CRYPTO_BASE + CRYPTO_RST_CTL, + CRYPTO_WRITE_MASK(1) | 1); + g_assert_cmphex(qtest_readl(qts, RK3588_CRYPTO_BASE + + CRYPTO_RST_CTL), ==, 0); + qtest_writel(qts, RK3588_CRYPTO_BASE + CRYPTO_FIFO_CTL, + CRYPTO_WRITE_MASK(CRYPTO_FIFO_BYTESWAP) | + CRYPTO_FIFO_BYTESWAP); + qtest_writel(qts, RK3588_CRYPTO_BASE + CRYPTO_HASH_CTL, + CRYPTO_WRITE_MASK(0xffff) | + CRYPTO_HASH_SHA256_PAD_ENABLE); + qtest_writel(qts, RK3588_CRYPTO_BASE + CRYPTO_HASH_CTL, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_CRYPTO_BASE + + CRYPTO_HASH_CTL), ==, + CRYPTO_HASH_SHA256_PAD_ENABLE); + qtest_writel(qts, RK3588_CRYPTO_BASE + CRYPTO_DMA_LLI_ADDR, lli_addr); + qtest_writel(qts, RK3588_CRYPTO_BASE + CRYPTO_DMA_CTL, + CRYPTO_WRITE_MASK(1) | 1); + + for (unsigned int i = 0; i < 1000 && !status; i++) { + qtest_clock_step(qts, 1); + status = qtest_readl(qts, RK3588_CRYPTO_BASE + + CRYPTO_DMA_INT_ST); + } + + g_assert_cmphex(status, ==, CRYPTO_DMA_SRC_ITEM_DONE); + g_assert_cmphex(qtest_readl(qts, RK3588_CRYPTO_BASE + + CRYPTO_HASH_VALID), ==, + CRYPTO_HASH_VALID_BIT); + for (unsigned int i = 0; i < ARRAY_SIZE(expected); i++) { + g_assert_cmphex(qtest_readl(qts, RK3588_CRYPTO_BASE + + CRYPTO_HASH_DOUT_0 + i * 4), ==, + expected[i]); + } + + qtest_writel(qts, RK3588_CRYPTO_BASE + CRYPTO_DMA_INT_ST, + CRYPTO_DMA_SRC_ITEM_DONE); + qtest_writel(qts, RK3588_CRYPTO_BASE + CRYPTO_HASH_VALID, + CRYPTO_HASH_VALID_BIT); + g_assert_cmphex(qtest_readl(qts, RK3588_CRYPTO_BASE + + CRYPTO_DMA_INT_ST), ==, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_CRYPTO_BASE + + CRYPTO_HASH_VALID), ==, 0); + + qtest_writel(qts, RK3588_CRYPTO_BASE + CRYPTO_DMA_CTL, + CRYPTO_WRITE_MASK(2) | 2); + g_assert_cmphex(qtest_readl(qts, RK3588_CRYPTO_BASE + + CRYPTO_DMA_INT_ST), ==, + CRYPTO_DMA_LIST_ERR); + qtest_writel(qts, RK3588_CRYPTO_BASE + CRYPTO_DMA_INT_ST, + CRYPTO_DMA_LIST_ERR); + + qtest_writel(qts, lli_addr + 0x10, 0x6); + qtest_writel(qts, RK3588_CRYPTO_BASE + CRYPTO_DMA_CTL, + CRYPTO_WRITE_MASK(1) | 1); + status = 0; + for (unsigned int i = 0; i < 1000 && !status; i++) { + qtest_clock_step(qts, 1); + status = qtest_readl(qts, RK3588_CRYPTO_BASE + + CRYPTO_DMA_INT_ST); + } + g_assert_cmphex(status, ==, CRYPTO_DMA_LIST_ERR); + qtest_writel(qts, RK3588_CRYPTO_BASE + CRYPTO_DMA_INT_ST, + CRYPTO_DMA_LIST_ERR); + + qtest_writel(qts, lli_addr + 0x10, + CRYPTO_LLI_USER_HASH_START_LAST | 0x8); + qtest_writel(qts, RK3588_CRYPTO_BASE + CRYPTO_DMA_CTL, + CRYPTO_WRITE_MASK(1) | 1); + status = 0; + for (unsigned int i = 0; i < 1000 && !status; i++) { + qtest_clock_step(qts, 1); + status = qtest_readl(qts, RK3588_CRYPTO_BASE + + CRYPTO_DMA_INT_ST); + } + g_assert_cmphex(status, ==, CRYPTO_DMA_LIST_ERR); + g_assert_cmphex(qtest_readl(qts, RK3588_CRYPTO_BASE + + CRYPTO_HASH_VALID), ==, 0); + + qtest_writel(qts, RK3588_CRYPTO_BASE + CRYPTO_RST_CTL, + CRYPTO_WRITE_MASK(1) | 1); + g_assert_cmphex(qtest_readl(qts, RK3588_CRYPTO_BASE + + CRYPTO_DMA_INT_ST), ==, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_CRYPTO_BASE + + CRYPTO_HASH_DOUT_0), ==, 0); + + qtest_quit(qts); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + if (!qtest_has_machine(ROCK_5B_PLUS_MACHINE)) { + g_test_skip(ROCK_5B_PLUS_MACHINE " machine not available"); + return 0; + } + + qtest_add_func("/rock-5b-plus/machine-creation", + test_rock_5b_plus_machine_creation); + qtest_add_func("/rock-5b-plus/smp-creation", + test_rock_5b_plus_smp_creation); + qtest_add_func("/rock-5b-plus/pcie3x2-fdt", + test_rock_5b_plus_pcie3x2_fdt); + qtest_add_func("/rock-5b-plus/pcie3x2-bus-number", + test_rock_5b_plus_pcie3x2_bus_number); + qtest_add_func("/rock-5b-plus/unfused-secure-otp", + test_rock_5b_plus_unfused_secure_otp); + qtest_add_func("/rock-5b-plus/crypto-sha256", + test_rock_5b_plus_crypto_sha256); + + return g_test_run(); +}