diff --git a/.agents b/.agents new file mode 160000 index 0000000000000..85faee895124d --- /dev/null +++ b/.agents @@ -0,0 +1 @@ +Subproject commit 85faee895124dd4259128805af6ce51b3fa31f7e diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000000..9a92d9d9cac81 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,565 @@ +name: Upstream QEMU CI + +on: push + +env: + # p-b-o/qemu-ci is an upstream-series runner. This downstream tree carries + # local board imports with cross-toolchain warnings, so keep Werror strictness + # only for the source CI repository. + QEMU_WERROR: ${{ github.repository == 'p-b-o/qemu-ci' && '--enable-werror' || '--disable-werror' }} + # Full thorough functional coverage includes host-sensitive GPU/KVM cases. + # Keep it in p-b-o/qemu-ci; downstream runs use quick functional smoke. + QEMU_FUNC_SUITES: ${{ github.repository == 'p-b-o/qemu-ci' && '--suite func-quick --suite func-thorough' || '--suite func-quick' }} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + checkapply: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + # to debug container live from GitHub + # - uses: mxschmitt/action-tmate@v3 + - run: bash -c '[ ! -f shazam.log ] || { cat shazam.log; exit 1; }' + + checkpatch: + needs: checkapply + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - id: patch-range + env: + GH_TOKEN: ${{ github.token }} + REPOSITORY: ${{ github.repository }} + REF_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + + if [ "$REPOSITORY" = "p-b-o/qemu-ci" ]; then + git remote add upstream https://gitlab.com/qemu-project/qemu + git fetch --no-tags upstream master + base="$(git merge-base upstream/master HEAD)" + echo "range=$base..HEAD" >> "$GITHUB_OUTPUT" + exit 0 + fi + + base_ref="$(gh pr list --repo "$REPOSITORY" --head "$REF_NAME" \ + --state open --json baseRefName --jq '.[0].baseRefName // ""')" + if [ -z "$base_ref" ]; then + echo "No open PR found for $REF_NAME; skipping checkpatch" + echo "range=" >> "$GITHUB_OUTPUT" + exit 0 + fi + + git fetch --no-tags origin "$base_ref" + base="$(git merge-base "origin/$base_ref" HEAD)" + echo "range=$base..HEAD" >> "$GITHUB_OUTPUT" + - if: ${{ steps.patch-range.outputs.range != '' }} + run: ./scripts/checkpatch.pl "${{ steps.patch-range.outputs.range }}" + + # Use prebuilt images instead of rebuilding them in CI. + build-cross: + needs: checkapply + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + container: [alpine,centos9,debian-amd64-cross,debian-arm64-cross,debian-hexagon-cross,debian-loongarch-cross,debian-ppc64el-cross,debian-riscv64-cross,debian-tricore-cross,fedora,ubuntu2404] + steps: + - uses: actions/checkout@v6 + - run: pip install meson + # - run: make docker-run J=$(nproc) RUNC=podman TEST=test-build IMAGE=docker.io/pboqemu/qemu-ci:${{matrix.container}} + - run: > + podman run --pull newer --init --rm -it -v $(pwd):$(pwd) + -w $(pwd)/tests/docker/ + docker.io/pboqemu/qemu-ci:${{matrix.container}} + env QEMU_SRC=$(pwd) QEMU_WERROR="${{ env.QEMU_WERROR }}" ENABLE_RUST=0 EXTRA_CONFIGURE_OPTS=--disable-docs INSTALL_DIR=/ BUILD_DIR=/tmp MAKEFLAGS="-j $(nproc)" + ./test-build + + build: + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + image: ubuntu-24.04 + - arch: aarch64 + image: ubuntu-24.04-arm + needs: checkapply + runs-on: ${{matrix.image}} + 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 + bash -cx './configure ${{ env.QEMU_WERROR }} && ninja -C build install' + - run: > + podman run --pull newer --init --rm -it -v $(pwd):$(pwd) -w $(pwd) + docker.io/pboqemu/qemu-ci:debian + ./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 + - run: > + podman run --pull newer --init --rm -it -v $(pwd):$(pwd) -w $(pwd) + docker.io/pboqemu/qemu-ci:debian + ./build/qemu-${{matrix.arch}} -plugin ./build/contrib/plugins/libstoptrigger,icount=1000000 -plugin ./build/tests/tcg/plugins/libinsn -plugin ./build/contrib/plugins/libcpp -d plugin ./build/qemu-system-x86_64 --version + + build-cross-mingw64: + needs: checkapply + 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:fedora-win64-cross + bash -cx './configure ${{ env.QEMU_WERROR }} $QEMU_CONFIGURE_OPTS && ninja -C build install' + + build-windows: + needs: checkapply + strategy: + fail-fast: false + matrix: + include: + - sys: UCRT64 + image: windows-2025 + arch: x64 + - sys: CLANG64 + image: windows-2025 + arch: x64 + - sys: MINGW64 + image: windows-2025 + arch: x64 + - sys: CLANGARM64 + image: windows-11-arm + arch: arm64 + runs-on: ${{matrix.image}} + defaults: + run: + shell: msys2 {0} + steps: + - uses: msys2/setup-msys2@v2 + with: + update: true + msystem: ${{matrix.sys}} + - uses: actions/checkout@v6 + - run: > + pacman -S --noconfirm base-devel binutils bison curl diffutils flex git grep make sed + ${MINGW_PACKAGE_PREFIX}-toolchain + ${MINGW_PACKAGE_PREFIX}-glib2 + ${MINGW_PACKAGE_PREFIX}-gtk3 + ${MINGW_PACKAGE_PREFIX}-libnfs + ${MINGW_PACKAGE_PREFIX}-libssh + ${MINGW_PACKAGE_PREFIX}-ninja + ${MINGW_PACKAGE_PREFIX}-pixman + ${MINGW_PACKAGE_PREFIX}-pkgconf + ${MINGW_PACKAGE_PREFIX}-python + ${MINGW_PACKAGE_PREFIX}-SDL2 + ${MINGW_PACKAGE_PREFIX}-zstd + # Install all dependencies for windows build + - run: git clone https://github.com/msys2/MINGW-packages --depth=1 + - run: pushd ./MINGW-packages/mingw-w64-qemu && (makepkg --syncdeps --nobuild --noprepare --noconfirm --skippgpcheck PKGBUILD || true) && popd + # Install debug tools (gflags, cdb), included in wdk: + - run: /c/ProgramData/Chocolatey/bin/choco install -y windowsdriverkit11 + - run: ./configure ${{ env.QEMU_WERROR }} --disable-docs && ninja -C build + # detect dll issue by loading qemu binary with cdb (run + quit) + # enable verbose load debug with gflags + # https://ten0s.github.io/blog/2022/07/01/debugging-dll-loading-errors + - run: /c/Program\ Files\ \(x86\)/Windows\ Kits/10/Debuggers/${{matrix.arch}}/gflags.exe -i qemu-system-x86_64.exe +sls + - run: /c/Program\ Files\ \(x86\)/Windows\ Kits/10/Debuggers/${{matrix.arch}}/cdb.exe -c 'g;q' ./build/qemu-system-x86_64.exe --version || true + - run: ./build/qemu-system-x86_64.exe -nographic -plugin ./build/contrib/plugins/libstoptrigger,icount=1000000 -plugin ./build/tests/tcg/plugins/libinsn -plugin ./build/contrib/plugins/libcpp -d plugin + + + build-macos: + needs: checkapply + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + image: macos-15-intel + - arch: aarch64 + image: macos-26 + runs-on: ${{matrix.image}} + steps: + - uses: actions/checkout@v6 + - run: brew uninstall cmake # conflict with install command below + - run: brew install --quiet $(env HOMEBREW_NO_ENV_HINTS=1 brew deps --include-build qemu) bison flex + - run: ./configure ${{ env.QEMU_WERROR }} && ninja -C build + - run: ./build/qemu-system-x86_64-unsigned -nographic -plugin ./build/contrib/plugins/libstoptrigger,icount=1000000 -plugin ./build/tests/tcg/plugins/libinsn -plugin ./build/contrib/plugins/libcpp -d plugin + + build-freebsd: + needs: checkapply + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + - uses: vmactions/freebsd-vm@v1 + with: + copyback: false + release: "14.4" + - run: rsync -av ./ freebsd:/home/qemu/ + - shell: freebsd {0} + run: set -ex && pkg update && pkg install -y git gcc gmake python pkgconf pixman bison flex glib ninja + - shell: freebsd {0} + run: cd /home/qemu && ./configure ${{ env.QEMU_WERROR }} && ninja -C build + - shell: freebsd {0} + run: cd /home/qemu && ./build/qemu-x86_64 -plugin ./build/contrib/plugins/libstoptrigger,icount=1000000 -plugin ./build/tests/tcg/plugins/libinsn -plugin ./build/contrib/plugins/libcpp -d plugin ./build/qemu-x86_64 --version + - 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 + steps: + - uses: actions/checkout@v6 + - uses: vmactions/openbsd-vm@v1 + with: + copyback: false + release: "7.8" + # move root home to /home, which has all disk space + - shell: openbsd {0} + run: mv /root/ /home/ && ln -s /home/root/ /root + - run: rsync -av ./ openbsd:/home/qemu/ + - shell: openbsd {0} + run: set -ex && pkg_add dtc git pkgconf bzip2 xz ninja bash gmake gsed gettext-tools gnutls capstone sdl2 gtk+3 libxkbcommon zstd libslirp bison flex + - shell: openbsd {0} + run: cd /home/qemu && ./configure ${{ env.QEMU_WERROR }} && ninja -C build + # split-wx=on: https://gitlab.com/qemu-project/qemu/-/issues/2685 + - shell: openbsd {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 -plugin ./build/contrib/plugins/libcpp -d plugin + + build-misc: + needs: checkapply + 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 + bash -cx './configure ${{ env.QEMU_WERROR }} --disable-user --disable-system --enable-docs --enable-tools && ninja -C build install' + + build-big-endian: + needs: checkapply + 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-s390x-cross + bash -cx './configure ${{ env.QEMU_WERROR }} $QEMU_CONFIGURE_OPTS && ninja -C build install' + + build-debug: + needs: checkapply + 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 + bash -cx './configure ${{ env.QEMU_WERROR }} --enable-rust --enable-debug --enable-modules && ninja -C build install' + - run: > + podman run --pull newer --init --rm -it -v $(pwd):$(pwd) -w $(pwd) + docker.io/pboqemu/qemu-ci:debian + ./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-static: + needs: checkapply + 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 + bash -cx './configure ${{ env.QEMU_WERROR }} --disable-system --disable-tools --disable-guest-agent --disable-docs --static && ninja -C build install' + + build-tsan: + needs: checkapply + 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 + bash -cx './configure ${{ env.QEMU_WERROR }} --enable-tsan && ninja -C build install' + + build-clang: + needs: checkapply + 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 + bash -cx './configure ${{ env.QEMU_WERROR }} --cxx=clang++ --cc=clang --host-cc=clang --enable-debug && ninja -C build install' + + build-clang-22: + needs: checkapply + runs-on: ubuntu-24.04 + steps: + - run: sudo rm -rf /opt/ /usr/local/.ghcup /usr/local/lib/android + - uses: actions/checkout@v6 + # bypass package signature check by faking sqv binary + - run: > + podman run --pull newer --init --rm -it -v $(pwd):$(pwd) -w $(pwd) + docker.io/pboqemu/qemu-ci:debian + bash -cx 'cp /usr/bin/echo /usr/bin/sqv && LLVM_VERSION=22 && apt update && apt install -y lsb-release wget gnupg && wget https://apt.llvm.org/llvm.sh && bash llvm.sh ${LLVM_VERSION} && ./configure ${{ env.QEMU_WERROR }} --cxx=clang++-${LLVM_VERSION} --cc=clang-${LLVM_VERSION} --host-cc=clang-${LLVM_VERSION} --enable-debug && ninja -C build install' + + build-kvm-only: + needs: checkapply + strategy: + fail-fast: false + matrix: + arch: [arm64, amd64, ppc64el, s390x] + 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 $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 + 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 + bash -cx './configure ${{ env.QEMU_WERROR }} --without-default-features --without-default-devices --disable-kvm --disable-tcg && ninja -C build install' + + build-oss-fuzz: + needs: checkapply + 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 + bash -cx 'mkdir build-oss-fuzz && export LSAN_OPTIONS=suppressions=scripts/lsan_suppressions.txt && CC="clang" CXX="clang++" CFLAGS="-fsanitize=address" ./scripts/oss-fuzz/build.sh' + - run: > + podman run --pull newer --init --rm -it -v $(pwd):$(pwd) -w $(pwd) + docker.io/pboqemu/qemu-ci:debian + bash -cx 'for fuzzer in $(find ./build-oss-fuzz/DEST_DIR/ -executable -type f); do grep "LLVMFuzzerTestOneInput" ${fuzzer} >& /dev/null || continue; env ASAN_OPTIONS="fast_unwind_on_malloc=0" ${fuzzer} -runs=1 -seed=1 || exit 1; done' + + check-tcg: + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + image: ubuntu-24.04 + - arch: aarch64 + image: ubuntu-24.04-arm + needs: checkapply + runs-on: ${{matrix.image}} + 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 + bash -cx './configure ${{ env.QEMU_WERROR }} --disable-docs --enable-rust --enable-debug-tcg --enable-debug-graph-lock --enable-debug-mutex && ninja -C build' + - run: > + podman run --pull newer --init --privileged --rm -it -v $(pwd):$(pwd) -w $(pwd) + docker.io/pboqemu/qemu-ci:debian + bash -cx "make -j1 check-tcg V=1" + + # run all meson tests, except functional. + check: + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + image: ubuntu-24.04 + - arch: aarch64 + image: ubuntu-24.04-arm + needs: checkapply + runs-on: ${{matrix.image}} + steps: + - run: sudo rm -rf /opt/ /usr/local/.ghcup /usr/local/lib/android + - uses: actions/checkout@v6 + # we use image with download cache filled. Solves servers flakiness. + - run: > + podman run --pull newer --init --rm -it -v $(pwd):$(pwd) -w $(pwd) + docker.io/pboqemu/qemu-ci:debian + bash -cx './configure ${{ env.QEMU_WERROR }} --disable-docs --enable-rust --enable-debug-tcg --enable-debug-graph-lock --enable-debug-mutex && ninja -C build' + - run: bash -c "[ -e /dev/kvm ] && sudo chown $USER:$USER /dev/kvm || true" + # run podman without -t to avoid interactive output of meson test + - run: > + podman run --pull newer --init --privileged --rm -i $([ -e /dev/kvm ] && echo '-v /dev/kvm:/dev/kvm') -v $(pwd):$(pwd) -w $(pwd) + docker.io/pboqemu/qemu-ci:debian + bash -cx "./build/pyvenv/bin/meson test -C build --setup thorough --no-suite func-quick --no-suite func-thorough --no-suite block-slow --no-suite block-thorough -t 5 --print-errorlogs" + + check-functional: + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + image: ubuntu-24.04 + - arch: aarch64 + image: ubuntu-24.04-arm + needs: checkapply + runs-on: ${{matrix.image}} + steps: + - run: sudo rm -rf /opt/ /usr/local/.ghcup /usr/local/lib/android + - uses: actions/checkout@v6 + # we use image with download cache filled. Solves servers flakiness. + - run: > + podman run --pull newer --init --rm -it -v $(pwd):$(pwd) -w $(pwd) + docker.io/pboqemu/qemu-ci:debian + bash -cx './configure ${{ env.QEMU_WERROR }} --disable-docs --enable-rust --enable-debug-tcg --enable-debug-graph-lock --enable-debug-mutex && ninja -C build' + - run: bash -c "[ -e /dev/kvm ] && sudo chown $USER:$USER /dev/kvm || true" + # run podman without -t to avoid interactive output of meson test + - run: > + podman run --pull newer --init --privileged --rm -i $([ -e /dev/kvm ] && echo '-v /dev/kvm:/dev/kvm') -v $(pwd):$(pwd) -w $(pwd) + docker.io/pboqemu/qemu-ci:debian + bash -cx "./build/pyvenv/bin/meson test -C build --setup thorough ${{ env.QEMU_FUNC_SUITES }} -j1 --timeout-multiplier 0 --print-errorlogs --wrapper $(pwd)/scripts/run-functional-test.sh --max-lines=0" + # Run with -j1 to avoid issues with parallel runs + + check-functional-rk3588-zvm: + needs: checkapply + runs-on: ubuntu-24.04 + timeout-minutes: 150 + steps: + - run: sudo rm -rf /opt/ /usr/local/.ghcup /usr/local/lib/android + - uses: actions/checkout@v6 + - uses: actions/cache@v4 + with: + path: .cache/qemu-test/download + key: zvm-rk3588-assets-v1 + - run: > + podman run --pull newer --init --rm -it -v $(pwd):$(pwd) -w $(pwd) + docker.io/pboqemu/qemu-ci:debian + bash -cx './configure ${{ env.QEMU_WERROR }} --disable-docs --target-list=aarch64-softmmu && ninja -C build qemu-system-aarch64 qemu-img' + - run: | + podman run --pull newer --init --privileged --rm -i \ + -v $(pwd):$(pwd) -w $(pwd) \ + docker.io/pboqemu/qemu-ci:debian \ + bash -exc ' + missing=0 + for cmd in curl zstd mkfs.vfat mcopy; do + command -v "$cmd" >/dev/null || missing=1 + done + command -v 7z >/dev/null || command -v 7zz >/dev/null || missing=1 + if [ "$missing" -ne 0 ]; then + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + --no-install-recommends curl dosfstools mtools zstd + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + --no-install-recommends 7zip || \ + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + --no-install-recommends p7zip-full + fi + + cache_root=$(pwd)/.cache/qemu-test + cache_dir=$cache_root/download + mkdir -p "$cache_dir" + + run_test() + { + timeout=$1 + test_name=$2 + + QEMU_TEST_CACHE_DIR=$cache_root \ + QEMU_FUNCTIONAL_TEST_TIMEOUT=$timeout \ + ./build/pyvenv/bin/meson test -C build --setup thorough \ + -j1 --timeout-multiplier 0 --print-errorlogs \ + --wrapper $(pwd)/scripts/run-functional-test.sh \ + --max-lines=0 "$test_name" + } + + download_asset() + { + url=$1 + sha=$2 + dest=$cache_dir/$sha + tmp=$dest.tmp + + if [ -f "$dest" ] && + printf "%s %s\n" "$sha" "$dest" | sha256sum -c - + then + return 0 + fi + + rm -f "$dest" "$tmp" + curl -fL --retry 5 --retry-all-errors \ + --connect-timeout 30 --speed-time 120 --speed-limit 1024 \ + -o "$tmp" "$url" + printf "%s %s\n" "$sha" "$tmp" | sha256sum -c - + mv "$tmp" "$dest" + } + + run_test 15m qemu:func-aarch64-rk3588_zvm + + download_asset \ + https://esnl.hnu.edu.cn/zvm/resource/kernel_images.zip \ + 05aa22b772808abef7082d3dfc513ea75b0813f3e0d4f028ad595e2c86fe3ec7 & + pids=$! + download_asset \ + https://esnl.hnu.edu.cn/zvm/resource/file_system_images_part1.zip \ + 9a1840d8d486b2de8e8bedc1647cd3c5aa2f622111b45bb9f0d8e1d26b07c5f0 & + pids="$pids $!" + download_asset \ + https://esnl.hnu.edu.cn/zvm/resource/file_system_images_part2.zip \ + 17b048b0a473dcbd4e5b7f1c5d4f50547ffefe3e040c0b8835e55cba7f45b823 & + pids="$pids $!" + + status=0 + for pid in $pids; do + wait "$pid" || status=1 + done + if [ "$status" -ne 0 ]; then + exit "$status" + fi + + run_test 90m qemu:func-aarch64-rk3588_zvm_linux_guest + ' diff --git a/.github/workflows/lockdown.yml b/.github/workflows/lockdown.yml deleted file mode 100644 index d5e1265cffb39..0000000000000 --- a/.github/workflows/lockdown.yml +++ /dev/null @@ -1,30 +0,0 @@ -# Configuration for Repo Lockdown - https://github.com/dessant/repo-lockdown - -name: 'Repo Lockdown' - -on: - pull_request_target: - types: opened - -permissions: - pull-requests: write - -jobs: - action: - runs-on: ubuntu-latest - steps: - - uses: dessant/repo-lockdown@v2 - with: - pr-comment: | - Thank you for your interest in the QEMU project. - - This repository is a read-only mirror of the project's repostories hosted - on https://gitlab.com/qemu-project/qemu.git. - The project does not process merge requests filed on GitHub. - - QEMU welcomes contributions of code (either fixing bugs or adding new - functionality). However, we get a lot of patches, and so we have some - guidelines about contributing on the project website: - https://www.qemu.org/contribute/ - lock-pr: true - close-pr: true diff --git a/.gitignore b/.gitignore index 61fa39967b542..073f7f0995214 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /GNUmakefile /build/ /.cache/ +/.oh-my-qemu/ /.vscode/ *.pyc .sdk diff --git a/.gitmodules b/.gitmodules index c307216d173ca..dc4c7c0fc2c80 100644 --- a/.gitmodules +++ b/.gitmodules @@ -45,3 +45,6 @@ [submodule "tests/lcitool/libvirt-ci"] path = tests/lcitool/libvirt-ci url = https://gitlab.com/libvirt/libvirt-ci.git +[submodule ".agents"] + path = .agents + url = https://github.com/processmission/oh-my-qemu.git diff --git a/MAINTAINERS b/MAINTAINERS index ecb8cfdc41eda..8329b3da2e65b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -888,6 +888,22 @@ F: include/hw/misc/arm_integrator_debug.h F: tests/functional/arm/test_integratorcp.py F: docs/system/arm/integratorcp.rst +M5Stack AI Pyramid (AX650X) +M: Chao Liu +L: qemu-arm@nongnu.org +S: Maintained +F: docs/system/arm/ax650x-pyramid.rst +F: hw/arm/ax650x*.c +F: hw/gpio/dw-apb-gpio.c +F: hw/misc/ax650x-*.c +F: hw/sd/ax650x-sdhci.c +F: include/hw/arm/ax650x*.h +F: include/hw/gpio/dw-apb-gpio.h +F: include/hw/misc/ax650x-*.h +F: include/hw/sd/ax650x-sdhci.h +F: tests/functional/aarch64/test_ax650x_ubuntu.py +F: tests/qtest/ax650x-pyramid-test.c + MAX78000FTHR L: qemu-arm@nongnu.org S: Orphan @@ -1821,18 +1837,67 @@ F: docs/system/riscv/xiangshan-kunminghu.rst F: hw/riscv/xiangshan_kmh.c F: include/hw/riscv/xiangshan_kmh.h -K230 Machines +CanMV-K230 Machines M: Chao Liu L: qemu-riscv@nongnu.org S: Maintained -F: docs/system/riscv/k230.rst +F: docs/system/riscv/k230-canmv.rst +F: hw/display/k230_*.c +F: hw/dma/k230_*.c +F: hw/i2c/k230_*.c +F: hw/intc/k230_*.c +F: hw/misc/k230_*.c +F: hw/rtc/k230_*.c F: hw/riscv/k230.c +F: hw/sd/k230_sdhci.c +F: hw/ssi/k230_spi.c F: hw/watchdog/k230_wdt.c +F: include/hw/display/k230_*.h +F: include/hw/dma/k230_*.h +F: include/hw/i2c/k230_*.h +F: include/hw/intc/k230_*.h +F: include/hw/misc/k230_*.h +F: include/hw/rtc/k230_*.h F: include/hw/riscv/k230.h +F: include/hw/sd/k230_sdhci.h +F: include/hw/ssi/k230_spi.h F: include/hw/watchdog/k230_wdt.h F: tests/functional/riscv64/test_k230.py +F: tests/qtest/k230-sysctl-test.c F: tests/qtest/k230-wdt-test.c +SpacemiT K3 Pico-ITX +M: Chao Liu +L: qemu-riscv@nongnu.org +S: Maintained +F: docs/system/riscv/spacemit-k3.rst +F: hw/misc/spacemit-k3.c +F: hw/riscv/spacemit-k3.c +F: hw/sd/spacemit-k3-sdhci.c +F: include/hw/misc/spacemit-k3.h +F: include/hw/riscv/spacemit-k3.h +F: include/hw/sd/spacemit-k3-sdhci.h +F: tests/functional/riscv64/test_spacemit_k3.py +F: tests/qtest/spacemit-k3-test.c + +Milk-V Duo +M: Kuan-Wei Chiu +S: Maintained +F: docs/system/riscv/milkv-duo.rst +F: hw/char/dw8250.c +F: hw/misc/cv1800b_clk.c +F: hw/riscv/cv1800b.c +F: hw/riscv/milkv_duo.c +F: include/hw/char/dw8250.h +F: include/hw/misc/cv1800b_clk.h +F: include/hw/riscv/cv1800b.h +F: tests/qtest/milkv-duo-test.c + +RTL8152 USB Ethernet +M: Chao Liu +S: Maintained +F: hw/usb/dev-rtl8152.c + RX Machines ----------- rx-gdbsim diff --git a/README.md b/README.md new file mode 100644 index 0000000000000..75b384e57113d --- /dev/null +++ b/README.md @@ -0,0 +1,111 @@ +# Process Mission QEMU + +Process Mission maintains this downstream QEMU branch for board and SoC model +development that is not yet carried by upstream QEMU. It tracks new machine +models, boot flows, and test coverage as they are added. + +This branch is regularly rebased on top of upstream QEMU. Please file an issue +for new machine requests or bug reports. + +## Machine Models + +| arch | machine | direct | firmware | src | +| --- | --- | --- | --- | --- | +| RISC-V | [`k230-canmv`](docs/system/riscv/k230-canmv.rst) | ✅ | ✅ | PM | +| RISC-V | [`k3-pico-itx`](docs/system/riscv/spacemit-k3.rst) | ✅ | ✅ | PM | +| RISC-V | [`milkv-duo`](docs/system/riscv/milkv-duo.rst) | ✅ | ✅ | UP | +| RISC-V | [`riscv-server-ref`](docs/system/riscv/riscv-server-ref.rst) | ✅ | ✅ | UP | +| ARM | [`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 | + +Source legend: + +- `PM`: Process Mission downstream-maintained model. +- `UP`: imported from upstream QEMU/qemu-devel. +- `OSS`: imported from other open-source repositories. +- `VND`: imported from vendor sources. + +## Development Workflow + +The machine models in this branch are developed with the `oh-my-qemu` workflow. +It provides agent skills for planning, register extraction, peripheral modeling, +board modeling, qtest, build, debugging, and verification. + +- Skill repository: + +## AX650X Pyramid quick start + +The `ax650x-pyramid` machine directly boots Linux on the M5Stack AI Pyramid / +AXERA AX650X platform. Detailed machine documentation is available in +[docs/system/arm/ax650x-pyramid.rst](docs/system/arm/ax650x-pyramid.rst). + +### Boot Ubuntu 22.04 from eMMC + +The Ubuntu image has no MBR or GPT. Linux creates its twelve partitions from +the fixed `blkdevparts` command line. The command below uses `snapshot=on`, so +guest writes are discarded when QEMU exits. + +```sh +QEMU=${QEMU:-build/qemu-system-aarch64} +ASSET_DIR=${ASSET_DIR:-assets/ax650x} +KERNEL=${KERNEL:-$ASSET_DIR/Image-5.15.73-axera} +EMMC=${EMMC:-$ASSET_DIR/ax650x-ubuntu-22.04-emmc.raw} + +PARTS='mmcblk0:1536K(uboot),1536K(uboot_bk),1M(env),20M(param)' +PARTS="$PARTS,6M(logo),1M(dtb),64M(kernel),1M(atf),1M(optee)" +PARTS="$PARTS,1M(recovery_dtb),74M(recovery),30380032K(rootfs)" +CMDLINE='console=ttyS0,115200n8 earlycon=uart8250,mmio32,0x2016000' +CMDLINE="$CMDLINE root=/dev/mmcblk0p12 rootfstype=ext4 rw rootwait" +CMDLINE="$CMDLINE blkdevparts=$PARTS" +CMDLINE="$CMDLINE systemd.show_status=yes systemd.log_target=console" + +for input in "$QEMU" "$KERNEL" "$EMMC"; do + if [ ! -r "$input" ]; then + echo "missing input: $input" >&2 + exit 1 + fi +done + +exec "$QEMU" \ + -machine ax650x-pyramid \ + -accel tcg,thread=multi \ + -cpu cortex-a55 \ + -smp 8 \ + -m 2G \ + -kernel "$KERNEL" \ + -append "$CMDLINE" \ + -drive "file=$EMMC,if=sd,format=raw,snapshot=on" \ + -chardev stdio,id=serial0,signal=off \ + -serial chardev:serial0 \ + -display none \ + -monitor none \ + -no-reboot +``` + +Override `QEMU`, `KERNEL`, or `EMMC` in the environment when the artifacts +live elsewhere. Remove `snapshot=on` only when persistent image writes are +intentional. + +### Run the Ubuntu quick-boot functional test + +The functional test uses the same direct-boot contract but lets the test +harness own the serial chardev. It is in the `thorough` suite because the +kernel and compressed eMMC image are downloaded assets. + +```sh +meson test -C build \ + --suite thorough \ + func-aarch64-ax650x_ubuntu \ + --print-errorlogs +``` + +The pinned kernel and qcow2 image are published at: + + + +The harness verifies both SHA-256 digests. The test uses disposable eMMC +writes and waits for DWMAC probe, partition 12, the mounted ext4 root +filesystem, Ubuntu readiness markers, and the serial login prompt. diff --git a/configs/devices/riscv64-softmmu/default.mak b/configs/devices/riscv64-softmmu/default.mak index a8e4d0ab336ff..6d93cfeb0ee37 100644 --- a/configs/devices/riscv64-softmmu/default.mak +++ b/configs/devices/riscv64-softmmu/default.mak @@ -9,7 +9,11 @@ # CONFIG_SIFIVE_E=n # CONFIG_SIFIVE_U=n # CONFIG_RISCV_VIRT=n +# CONFIG_RISCV_SERVER_PLATFORM_REF=n +# CONFIG_SPACEMIT_K3=n # CONFIG_MICROCHIP_PFSOC=n # CONFIG_SHAKTI_C=n +# CONFIG_K230=n # CONFIG_XIANGSHAN_KUNMINGHU=n # CONFIG_MIPS_BOSTON_AIA=n +# CONFIG_MILKV_DUO=n diff --git a/docs/about/deprecated.rst b/docs/about/deprecated.rst index 0c656a968fc0b..b4fdd1a13e729 100644 --- a/docs/about/deprecated.rst +++ b/docs/about/deprecated.rst @@ -392,6 +392,13 @@ It was implemented as a no-op instruction in TCG up to QEMU 9.0, but only with ``-cpu max`` (which does not guarantee migration compatibility across versions). +``debug=true|false`` on RISC-V CPUs (since 11.0) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This option, since QEMU 10.1, has been a simple alias to the ``sdtrig`` +extension. Users are advised to enable/disable ``sdtrig`` directly instead +of using ``debug``. + linux-user mode CPUs -------------------- @@ -416,7 +423,6 @@ ABI is long-obsolete. We are therefore deprecating both OABI support and NWFPE emulation, and they will be removed in a future QEMU release. - Backwards compatibility ----------------------- diff --git a/docs/system/arm/ax650x-pyramid.rst b/docs/system/arm/ax650x-pyramid.rst new file mode 100644 index 0000000000000..a7cb9662bdf42 --- /dev/null +++ b/docs/system/arm/ax650x-pyramid.rst @@ -0,0 +1,142 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +M5Stack AI Pyramid (``ax650x-pyramid``) +======================================== + +Overview +-------- + +The ``ax650x-pyramid`` machine models the Linux boot subset of the M5Stack +AI Pyramid, whose runtime device tree identifies the SoC as +``axera,ax650x``. The model is intended for direct kernel boot with an +AX650X-enabled Arm64 Linux kernel. It does not model the product's AXERA +accelerators or firmware boot chain. + +The machine has a fixed topology matching the target system: eight Cortex-A55 +CPUs and 2 GiB of RAM starting at physical address ``0x100000000``. Other CPU +counts and memory sizes are rejected. + +Supported devices +----------------- + +The machine provides the following devices and architectural services: + +* eight Cortex-A55 CPUs, with MPIDRs ``0x000`` through ``0x700``; +* GIC-400 compatible GICv2 interrupt controller with virtualization + extensions; +* Arm generic timers running at 24 MHz; +* PSCI 1.0 using the SMC conduit; +* the AXERA UART0 console at ``0x02016000``, including the extension + registers needed by the AXERA 8250 driver; +* an AX650X SDHCI/eMMC controller at ``0x28000000``, including the vendor PHY + and eMMC registers used during Linux probe; +* two Synopsys DWMAC 4.10a compatible Ethernet controllers, including the + AX650X clock/reset glue, MDIO PHY identity, DMA and interrupt paths; +* the DesignWare APB GPIO blocks used for PHY reset; and +* the AX650X hardware spinlock block used by the GPIO driver. + +QEMU generates a minimal device tree containing only those implemented +devices. An eMMC backend supplied with ``if=sd`` is attached to the AX650X +controller. + +Direct Linux boot +----------------- + +The model requires an uncompressed Arm64 ``Image`` containing the AXERA UART +and SDHCI drivers. The following example boots an ext4 root filesystem from +partition 12 of an eMMC image that uses the target's fixed partition layout. +The image has no MBR or GPT, so the complete ``blkdevparts`` argument is +required: + +.. code-block:: shell + + QEMU=${QEMU:-build/qemu-system-aarch64} + ASSET_DIR=${ASSET_DIR:-assets/ax650x} + KERNEL=${KERNEL:-$ASSET_DIR/Image-5.15.73-axera} + EMMC=${EMMC:-$ASSET_DIR/ax650x-ubuntu-22.04-emmc.raw} + + PARTS='mmcblk0:1536K(uboot),1536K(uboot_bk),1M(env),20M(param)' + PARTS="$PARTS,6M(logo),1M(dtb),64M(kernel),1M(atf),1M(optee)" + PARTS="$PARTS,1M(recovery_dtb),74M(recovery),30380032K(rootfs)" + CMDLINE='console=ttyS0,115200n8 earlycon=uart8250,mmio32,0x2016000' + CMDLINE="$CMDLINE root=/dev/mmcblk0p12 rootfstype=ext4 rw rootwait" + CMDLINE="$CMDLINE blkdevparts=$PARTS" + CMDLINE="$CMDLINE systemd.show_status=yes systemd.log_target=console" + + for input in "$QEMU" "$KERNEL" "$EMMC"; do + if [ ! -r "$input" ]; then + echo "missing input: $input" >&2 + exit 1 + fi + done + + exec "$QEMU" \ + -machine ax650x-pyramid \ + -accel tcg,thread=multi \ + -cpu cortex-a55 \ + -smp 8 \ + -m 2G \ + -kernel "$KERNEL" \ + -append "$CMDLINE" \ + -drive "file=$EMMC,if=sd,format=raw,snapshot=on" \ + -chardev stdio,id=serial0,signal=off \ + -serial chardev:serial0 \ + -display none \ + -monitor none \ + -no-reboot + +``snapshot=on`` keeps the reusable eMMC image unchanged. Remove it only when +persistent guest writes are intentional. + +The downstream model has been exercised with an AXERA Linux 5.15.73 kernel +and Ubuntu 22.04.5 LTS. Linux enumerates all eight CPUs, probes the eMMC using +64-bit ADMA, creates the twelve fixed partitions, and mounts +``/dev/mmcblk0p12`` as the writable root filesystem. + +Known limitations +----------------- + +* Only QEMU direct kernel boot is implemented. BootROM, SPL, U-Boot, Arm + Trusted Firmware and OP-TEE images are not loaded or executed. +* NPU, VDSP, RISC-V auxiliary cores, video, ISP, display, audio, USB, PCIe, + SATA and the full clock, reset and power-management trees are not modeled. + GPIO behavior is limited to the DesignWare APB subset needed for PHY reset. +* DWMAC TSO, PTP/TSN, multi-queue performance fidelity and analog PHY timing + are not modeled. +* The UART extension window implements the probe-time subset. The AXERA + driver can report a harmless capability mismatch because the UART component + version register intentionally selects its compatible fallback path. +* QEMU direct boot starts CPUs at EL2. The target firmware starts Linux at + EL1, so the vendor kernel can print a non-fatal GICv2 CPU-interface range + warning under QEMU. +* The SDHCI vendor bank implements the registers required for probe, reset and + data transfer. HS200/HS400 timing accuracy is not claimed. + +Running tests +------------- + +The board qtest covers CPU topology, RAM, GICv2, UART MMIO and IRQ behavior, +SDHCI vendor registers, reset, block I/O, eMMC IRQ routing, 64-bit ADMA, +Auto CMD23, DWMAC synthesis registers, MDIO, GPIO and reset glue, hardware +spinlocks, 40-bit DMA and Ethernet IRQ routing:: + + $ meson test -C build qemu:qtest-aarch64/ax650x-pyramid-test \ + --print-errorlogs + +The Ubuntu quick-boot functional test downloads a pinned kernel and compressed +qcow2 image from the `AX650X Ubuntu 22.04 QEMU assets release +`__. +The functional asset layer verifies both SHA-256 digests. The test uses +``snapshot=on`` and waits for DWMAC probe, the eMMC partition map, the mounted +root filesystem, Ubuntu readiness markers and the serial login prompt: + +.. code-block:: shell + + $ meson test -C build \ + --suite thorough \ + func-aarch64-ax650x_ubuntu \ + --print-errorlogs + +The test is registered in the ``thorough`` functional suite because the +kernel and Ubuntu image are external assets. A cached copy is reused only +after its declared content hash has been checked. diff --git a/docs/system/arm/phytium-pi.rst b/docs/system/arm/phytium-pi.rst new file mode 100644 index 0000000000000..fca33888aebb3 --- /dev/null +++ b/docs/system/arm/phytium-pi.rst @@ -0,0 +1,198 @@ +.. Copyright (c) 2026 Chao Liu +.. SPDX-License-Identifier: GPL-2.0-or-later + +Phytium Pi Board (``phytium-pi``) +================================= + +The ``phytium-pi`` machine is a local downstream model for the Phytium Pi +board based on the Phytium E2000Q/PE2204 SoC. It is intended for direct Arm +Linux boot, vendor SD-card firmware/U-Boot boot, and ZVM E2000 smoke testing. + +This is not a complete model of the SoC. Several register ranges are present +only to let firmware progress to the next boot stage. + +Supported devices +----------------- + +The machine currently models or stubs the devices needed by the verified +Linux, U-Boot, and ZVM paths: + +* Up to 4 AArch64 CPU objects with the PE2204 MPIDR layout. The generated + device tree reports the Phytium FTC664/FTC310 compatible strings used by the + board firmware and Linux BSP. +* Up to 4 GiB RAM, split between a low 2 GiB window at ``0x80000000`` and a + high 2 GiB window above 32-bit physical address space. +* GICv3 distributor and redistributor regions. The ITS and legacy CPU, + hypervisor, and virtual CPU interface windows are exposed as unimplemented + regions. +* Arm generic timer with a 50 MHz counter frequency. +* PL011 UART windows. The generated direct Linux device tree exposes + ``serial@2800d000`` as ``serial1`` and the default console. +* Two Phytium MCI controllers. The first controller is wired to + ``-drive if=sd,index=0`` and is the boot card for firmware and Linux tests. +* A Phytium XMAC register/MDIO/PHY shim sufficient for link-status probes. +* Firmware RAM/control windows, an SCP mailbox shim, and a DDR controller shim + used by the vendor firmware path. +* Placeholder PCIe ECAM, RNG, USB2, PHY configuration, and miscellaneous + firmware windows used to avoid synchronous aborts during boot probing. + +Boot options +------------ + +Direct Linux boot +~~~~~~~~~~~~~~~~~ + +The direct Linux path uses QEMU's standard Arm ``-kernel`` loader and a +QEMU-generated device tree: + +.. code-block:: bash + + $ APPEND="console=ttyAMA1,115200n8 earlycon=pl011,0x2800d000 rdinit=/init" + $ SDIMG=/path/to/sd.img + $ qemu-system-aarch64 \ + -accel tcg \ + -machine phytium-pi \ + -smp 4 \ + -m 4G \ + -kernel /path/to/Image \ + -initrd /path/to/initramfs.cpio.gz \ + -append "$APPEND" \ + -drive if=sd,index=0,file="$SDIMG",format=raw,auto-read-only=off \ + -serial mon:stdio \ + -display none + +The generated device tree sets ``stdout-path`` to ``serial1:115200n8``. Keep +``console=ttyAMA1,115200n8`` when using kernels that honor the serial alias. + +Firmware SD boot +~~~~~~~~~~~~~~~~ + +When ``-kernel`` is not provided, the machine enters the firmware path. The +firmware path requires at least three CPUs because the board starts the EL3 +firmware entry on CPU index 2. Attach the Phytium Pi SD image as +``if=sd,index=0``: + +.. code-block:: bash + + $ SDIMG=/path/to/phytium-pi-sd.img + $ qemu-system-aarch64 \ + -accel tcg \ + -machine phytium-pi \ + -smp 4 \ + -m 4G \ + -drive if=sd,index=0,file="$SDIMG",format=raw,auto-read-only=off \ + -serial mon:stdio \ + -display none + +The boot ROM window is initialized from the first 4 MiB of the SD image. The +model then provides the firmware RAM, DDR-status, SCP mailbox, and firmware +MMIO windows needed by the verified U-Boot path. The local openEuler smoke +run used a complete SD genimage with a rebuilt FIT whose kernel has the +required MMC/rootfs support built in; the original package FIT can stop at +``Waiting for root device`` if its storage drivers are modules and no initrd is +present. + +ZVM E2000 path +~~~~~~~~~~~~~~ + +The ZVM E2000 release path runs through SD firmware and U-Boot before entering +the ZVM ELF payload. The official `ZVM-E2000 deployment guide +`_ describes the +release flow and payload layout. In both flows below, the SD image provides +the Phytium Pi firmware and U-Boot, and ZVM is entered with +``bootelf 0xa1000000``. + +QEMU preloaded payloads +^^^^^^^^^^^^^^^^^^^^^^^ + +For a deterministic QEMU smoke test that does not depend on U-Boot networking, +preload the ZVM and guest payloads at the same RAM addresses used by the +official flow: + +.. code-block:: bash + + $ SDIMG=/path/to/phytium-pi-sd.img + $ ZVM=/path/to/zvm_release/e2000 + $ LINUX="$ZVM/Image_v5.16.0" + $ LINUX_DTB="$ZVM/linux_e2000q.dtb" + $ INITRD="$ZVM/initramfs_e2000.cpio.gz" + $ qemu-system-aarch64 \ + -accel tcg \ + -machine phytium-pi \ + -smp 4 \ + -m 2G \ + -drive if=sd,index=0,file="$SDIMG",format=raw,auto-read-only=off \ + -device loader,file="$ZVM/zvm.elf",addr=0xa1000000,force-raw=on \ + -device loader,file="$LINUX",addr=0xa2000000,force-raw=on \ + -device loader,file="$LINUX_DTB",addr=0xf8800000,force-raw=on \ + -device loader,file="$INITRD",addr=0xf9000000,force-raw=on \ + -serial mon:stdio \ + -display none + +From the U-Boot prompt, enter ZVM: + +.. code-block:: text + + => dcache flush; icache flush; dcache off; icache off; bootelf 0xa1000000 + +U-Boot TFTP payloads +^^^^^^^^^^^^^^^^^^^^ + +The official E2000 flow can also load the same payloads from U-Boot over TFTP. +Serve a directory containing the ``e2000/`` release files, boot the firmware SD +image, stop at the U-Boot prompt, and run the commands below. This uses the +same RAM layout as the QEMU preloaded path; actual TFTP traffic requires a +working XMAC data path. + +.. code-block:: text + + => mw 0x80100000 0x0 0x10000000 + => mw 0x90100000 0x0 0x10000000 + => setenv ipaddr 192.168.1.128 + => setenv serverip 192.168.1.100 + => setenv bootargs "" + => tftp 0xa1000000 e2000/zvm.elf + => tftp 0xf8000000 e2000/zephyr_e2000_c1_m128.bin + => tftp 0xf8200000 e2000/zephyr_e2000_c2_m128.bin + => tftp 0xf8400000 e2000/freertos_e2000_c1_m128.bin + => tftp 0xa2000000 e2000/Image_v5.16.0 + => tftp 0xf8800000 e2000/linux_e2000q.dtb + => tftp 0xf9000000 e2000/initramfs_e2000.cpio.gz + => dcache flush; icache flush; dcache off; icache off; bootelf 0xa1000000 + +The verified Linux guest smoke path used these ZVM commands: + +.. code-block:: text + + zvm create -t linux -c 1 -m 512 + zvm run -n 0 + zvm info + zvm look 0 + +The recorded run reached the ZVM host shell, created ``linux_os-0``, reported +the VM as ``running``, connected to the guest console, booted Linux 5.16, ran +``/init``, and reached a guest shell prompt. + +Known limitations +----------------- + +The model is a boot and smoke-test target, not a full Phytium E2000Q model. +The XMAC model does not provide a complete Ethernet data path, PCIe is not +implemented, and several firmware-visible MMIO ranges are RAM-backed or +unimplemented placeholders. The DDR controller shim reports the status values +needed by known firmware polling loops; it does not perform DDR training or +memory diagnostics. + +Running tests +------------- + +Build and run the Phytium Pi qtest with: + +.. code-block:: bash + + $ ninja -C build qemu-system-aarch64 tests/qtest/phytiumpi-test + $ QTEST_QEMU_BINARY=$PWD/build/qemu-system-aarch64 \ + build/tests/qtest/phytiumpi-test --tap -k + +The qtest covers machine creation, high-memory mapping, MCI register and ADMA +flows, XMAC PHY/link status, and SMP creation. diff --git a/docs/system/arm/rk3588.rst b/docs/system/arm/rk3588.rst new file mode 100644 index 0000000000000..bf67e6171ed84 --- /dev/null +++ b/docs/system/arm/rk3588.rst @@ -0,0 +1,427 @@ +.. Copyright (c) 2026 Chao Liu +.. SPDX-License-Identifier: GPL-2.0-or-later + +Rockchip RK3588 Boards +====================== + +The RK3588 machines are local downstream models for booting Linux and firmware +images built for Rockchip RK3588 boards. They support QEMU's direct Arm Linux +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: + +``rk3588-evb`` + Rockchip RK3588 EVB-style board. Firmware boot reads the Rockchip RKNS + image from ``if=sd,index=0`` and exposes it as the non-removable eMMC + controller. + +``rk3588s-roc-pc`` + Firefly ROC-RK3588S-PC board, exposed by QEMU as ``rk3588s-roc-pc`` and + using the hardware compatible ``rockchip,rk3588s-firefly-roc-pc``. + Firmware boot reads the Rockchip RKNS image from ``if=sd,index=2`` and + exposes it through the SDMMC/TF-card controller used by the package's + ``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. + +Supported devices +----------------- + +The machines currently model or stub the devices needed by the downstream +RK3588 kernel and ZVM smoke tests: + +* 8 Arm cores matching the RK3588 big.LITTLE layout: Cortex-A55 CPUs 0-3 and + Cortex-A76 CPUs 4-7. Under TCG the CPU objects expose EL2 and EL3 for the + firmware and ZVM paths. The RK3588 machines do not install RK3588-only CPU + compatibility properties for EL2 FP/SIMD traps or EL2 PAN handling. +* GICv3 with two ITS blocks and LPI support. +* 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. +* Rockchip secure timer stub. +* RK3588 CRU and SCMI-over-SMC clock/reset support. +* SDHCI eMMC and Synopsys DesignWare MMC for the SD-card controller. +* Synopsys DWMAC4 GMAC0 and GMAC1. +* Rockchip DesignWare PCIe host wrapper, including MSI/MSI-X through GIC ITS. +* Firmware-only USB2 EHCI/OHCI register windows used by U-Boot bootflow + scanning. + +The machine does not instantiate a SPI controller yet. ``SPI`` in the RK3588 +machine source refers to GIC Shared Peripheral Interrupt numbers. + +Common IP boundaries +-------------------- + +Several blocks are modeled as Rockchip- or DesignWare-style reusable IP, rather +than as RK3588-only devices: + +* ``rockchip-syscon`` covers RAM-backed GRF/IOC/firewall register banks and + implements Rockchip HIWORD writes. +* ``rockchip-stimer`` covers the secure timer register layout used by Rockchip + firmware. +* ``dw-apb-uart-vendor`` covers the Synopsys DesignWare APB UART extension + window above the 16550-compatible core. The UART core itself is QEMU's + reusable ``serial_mm`` 16550-compatible model. +* ``rockchip-dwcmshc-vendor`` covers the Rockchip vendor window above the + SDHCI core for DWCMSHC eMMC controllers. The SDHCI SDMA buffer-boundary + behavior is generic SDHCI logic, not RK3588-specific logic. +* ``dw-mmc`` covers the Synopsys DesignWare MMC register bank, PIO FIFO data + ports, and IDMAC descriptor flow used by the RK3588 SD-card controller. +* ``rockchip-pcie-host`` wraps QEMU's DesignWare PCIe host and adds the + Rockchip APB vendor window expected by the ``dw-rockchip`` Linux driver. + 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. + +Direct Linux Boot +----------------- + +The following command boots the RK3588 Linux image and initramfs stored under +``build/agent`` on the EVB machine, and attaches the smoke-test eMMC image, the +board GMAC, and one PCIe virtio-net device: + +.. code-block:: bash + + $ build/qemu-system-aarch64 \ + -accel tcg \ + -machine rk3588-evb \ + -smp 2 \ + -m 1G \ + -kernel build/agent/linux-src/arch/arm64/boot/Image \ + -initrd build/agent/initramfs.cpio.gz \ + -append "console=ttyS2,1500000n8 rdinit=/init" \ + -drive if=sd,file=build/agent/rk3588-modeling/logs/emmc-smoke.img,format=raw \ + -nic user,model=gmac0 \ + -netdev user,id=vnet0 \ + -device virtio-net-pci,netdev=vnet0 \ + -serial mon:stdio \ + -display none + +The kernel console is UART2, so keep ``console=ttyS2,1500000n8`` in the kernel +command line. + +The ROC PC machine supports the same direct Linux boot path: + +.. code-block:: bash + + $ build/qemu-system-aarch64 \ + -accel tcg \ + -machine rk3588s-roc-pc \ + -smp 2 \ + -m 1G \ + -kernel build/agent/linux-src/arch/arm64/boot/Image \ + -initrd build/agent/initramfs.cpio.gz \ + -append "console=ttyS2,1500000n8 rdinit=/init" \ + -drive if=sd,file=build/agent/rk3588-modeling/logs/emmc-smoke.img,format=raw \ + -nic user,model=gmac0 \ + -netdev user,id=vnet0 \ + -device virtio-net-pci,netdev=vnet0 \ + -serial mon:stdio \ + -display none + +Firmware U-Boot Boot +-------------------- + +The firmware path expects a raw eMMC image containing Rockchip's combined +``u-boot-rockchip.bin`` at LBA 64: + +.. code-block:: bash + + $ truncate -s 64M build/agent/rk3588-firmware-boot/scratch/rk3588-firmware-emmc.img + $ dd if=build/agent/rk3588-firmware-boot/scratch/u-boot-build/u-boot-rockchip.bin \ + of=build/agent/rk3588-firmware-boot/scratch/rk3588-firmware-emmc.img \ + bs=512 seek=64 conv=notrunc + +Run the firmware smoke path with: + +.. code-block:: bash + + $ build/qemu-system-aarch64 \ + -accel tcg \ + -machine rk3588-evb \ + -smp 1 \ + -m 1G \ + -drive if=sd,index=0,file=build/agent/rk3588-firmware-boot/scratch/rk3588-firmware-emmc.img,format=raw \ + -serial mon:stdio \ + -display none + +This path executes the Rockchip DDR/TPL image, enters U-Boot SPL, validates the +FIT loadables, and replaces SPL's final ATF branch with a QEMU-private SMC that +enters BL31 with SPL's BL31 parameter block. BL31 runs through the modeled +initialization path; a narrow BL31 exit shim then hands off to U-Boot proper at +``0x800000`` with the EL2/GIC/PSCI state expected by the downstream smoke path. +After the handoff the model exposes the USB2 host register windows so U-Boot +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. + +U-Boot to Linux Boot +-------------------- + +The complete U-Boot path uses a raw eMMC image that contains the Rockchip +firmware at LBA 64 and a boot partition with ``extlinux.conf``, ``Image``, +``initramfs.cpio.gz``, and a QEMU-generated RK3588 DTB. The DTB embedded in +the image must be generated with the same ``-smp`` value used at runtime. + +The locally verified downstream image boots with: + +.. code-block:: bash + + $ build/qemu-system-aarch64 \ + -accel tcg \ + -machine rk3588-evb \ + -smp 2 \ + -m 1G \ + -drive if=sd,index=0,file=build/agent/rk3588-uboot-linux/scratch/rk3588-uboot-linux-emmc-earlycon.img,format=raw \ + -nic user,model=gmac0 \ + -netdev user,id=vnet0 \ + -device virtio-net-pci,netdev=vnet0 \ + -serial mon:stdio \ + -display none + +The verified ``extlinux.conf`` passes: + +.. code-block:: text + + console=ttyS2,1500000n8 earlycon=uart8250,mmio32,0xfeb50000,1500000n8 keep_bootcon loglevel=8 rdinit=/init + +U-Boot loads the kernel, initramfs, and DTB from eMMC, prints +``Starting kernel ...``, and the smoke initramfs reaches +``=== init done, exec sh ===``. + +ZVM on RK3588 EVB +----------------- + +The EVB ZVM smoke path uses an eMMC image and the upstream package's manual +copy sequence with ``mmc dev 0``. Enable the optional ZVM RAM windows for this +machine, because the EVB board keeps them disabled by default: + +.. code-block:: bash + + $ build/qemu-system-aarch64 \ + -accel tcg \ + -machine rk3588-evb,zvm-ram=on \ + -smp 8 \ + -m 1G \ + -drive if=sd,index=0,file=build/agent/zvm-rk3588/scratch/zvm-rk3588-qemu-emmc.img,format=raw \ + -serial mon:stdio \ + -display none + +At the U-Boot prompt, run: + +.. code-block:: text + + => echo loading ZVM img ... + => mmc dev 0 + => fatload mmc 0:1 0x10000000 zvm_release_rk3588v2.bin + => cp.b 0x103fd000 0x38800000 0x2f000 + => cp.b 0x1038b000 0x38600000 0x71958 + => cp.b 0x10319000 0x38400000 0x712e0 + => cp.b 0x102a7000 0x38200000 0x71178 + => cp.b 0x1023c000 0x38000000 0x6afc8 + => cp.b 0x1023b000 0x12afa000 0x4 + => cp.b 0x100f7000 0x129b6000 0x143570 + => go 0x10000000 + +The validated no-compatibility run reaches ``zvm_host:~#`` and starts the +bundled Zephyr guest. This EVB image is useful for the ZVM host and basic +guest smoke path; use the ROC image below for the full Linux VM release-image +layout. + +ZVM on ROC-RK3588S-PC +--------------------- + +The `RK3588 ZVM deployment guide +`_ targets the +Firefly ROC-RK3588S-PC board. It expects an 8 GiB first FAT partition, a +second partition larger than 21 GiB, the boot files on the first partition, +and ``nrtos_images.bin`` written to the second partition with +``bs=1M seek=1024``. The packaged file-system images are then written to the +second partition at 3 GiB-spaced offsets, leaving the first 3 GiB of that +partition for ZVM guest run records: + +* ``android_vda.img`` at ``seek=3072`` +* ``oh_vda.bin`` at ``seek=6144`` +* ``linux_diskimg.bin`` at ``seek=9216``, ``12288``, ``15360``, and ``19432`` + +Download the ZVM release binaries from the official `ZVM RK3588 download page +`_. The QEMU image +flow uses these release packages: + +* ``kernel_images.zip``: provides ``kernel_images/`` with + ``zvm_release_rk3588v2.bin``, ``boot.scr``, ``nrtos_images.bin``, and the + raw images used by the package helper. +* ``file_system_images_part1.zip`` and ``file_system_images_part2.zip``: + provide ``file_system_images/``. Run its ``unpack.sh`` to produce the + ``file_system_images/image/`` directory containing ``android_vda.img``, + ``oh_vda.bin``, and ``linux_diskimg.bin``. + +The Rockchip RKNS firmware binary, ``u-boot-rockchip.bin``, is separate from +the ZVM release package. Use the RK3588 U-Boot build produced by the firmware +flow above, or another ROC-RK3588S-PC U-Boot build that emits the same combined +Rockchip image. + +The release package also ships ``nrtos_imagespack.py`` with +``N = 0x1205000``. For a raw QEMU TF-card image this means the second +partition must start at sector ``0x1005000`` so that ``seek=1024`` lands +``nrtos_images.bin`` at the absolute sector ZVM reads. A more conventional +8 GiB-aligned second partition start of ``0x1008000`` puts the image 6 MiB too +late; ZVM then detects the slots but loads zero-filled Linux buffers. + +The Linux kernel slot inside ``nrtos_images.bin`` must match the modules in +``linux_diskimg.bin``. The release file system carries +``/lib/modules/5.10.110/kernel/zshm.ko`` with ``vermagic=5.10.110 SMP +mod_unload aarch64``. If the first 64 MiB Linux kernel slot is replaced by an +unmatched kernel, Linux can still reach the Buildroot login prompt but +``zshm.ko`` fails to load and ZVM's operation-test heartbeat waits until the +Linux VM start timeout. The local validated image uses the release +``Image.official`` 5.10.110 kernel in that slot. + +To rebuild the boot payloads from the official package, run the package helper +from the ``kernel_images`` directory and choose option ``3``: + +.. code-block:: bash + + $ cd build/agent/zvm-rk3588/work/kernel_images + $ printf '3\n' | ./auto_py.sh + +This regenerates ``nrtos_images.bin``, ``zvm_release_rk3588v2.bin``, and +``boot.scr``. The helper runs ``raw_kernel_images/nrtos_imagespack.py`` first, +then ``raw_kernel_images/pack_img_v2.py``. The first script packs the Linux, +Android, and OpenHarmony kernel/initramfs/DTB slots into fixed-size regions in +``nrtos_images.bin``. The second script packs the ZVM segments listed in +``raw_kernel_images/segments.map`` together with the Zephyr and FreeRTOS helper +images into ``zvm_release_rk3588v2.bin`` and refreshes the U-Boot script. + +The repository also provides a QEMU raw-image helper for the ROC board. It +supports Linux hosts with ``dosfstools`` and ``mtools`` installed, and macOS +hosts through ``hdiutil`` and ``newfs_msdos``: + +.. code-block:: bash + + $ ZVM=build/agent/zvm-rk3588 + $ KIMG=$ZVM/work/kernel_images + $ FSIMG=$ZVM/work/file_system_images_official/image + $ UBOOT=$ZVM/scratch/u-boot-roc-zvmcompat-build/u-boot-rockchip.bin + $ OUT=$ZVM/scratch/zvm-rk3588-qemu-roc-sd-official-fixed.img + $ scripts/zvm/rk3588s-roc-pc-make-zvm-image.sh \ + --kernel-dir "$KIMG" \ + --filesystem-dir "$FSIMG" \ + --uboot-rockchip "$UBOOT" \ + --output "$OUT" \ + --force + +The script creates the 32 GiB raw card image, writes the MBR partition table, +formats both FAT partitions, copies ``zvm_release_rk3588v2.bin`` and +``boot.scr``/``boot.scr.uimg`` to the first partition, writes +``nrtos_images.bin`` to the second partition at ``bs=1M seek=1024``, writes the +Android/OpenHarmony/Linux file-system images at the offsets listed above, and +installs ``u-boot-rockchip.bin`` at LBA 64. + +When diagnosing ``Timeout waiting for vm: linux_os-1 start`` or +``Failed to run linux VM 1, ret=-19``, check the image contents before changing +CPU or machine state: + +* The ``nrtos_images.bin`` slot must be readable at the absolute sector + expected by ZVM. With the package's ``N = 0x1205000`` this means the second + partition starts at sector ``0x1005000`` and the file is written to that + partition with ``seek=1024``. +* The Linux kernel in the first 64 MiB of ``nrtos_images.bin`` must match the + file-system modules. A local test with a 7.1.0-rc7 kernel and the release + 5.10.110 root file system reached the Linux login prompt through + ``zvm look 1``, but ``insmod /lib/modules/5.10.110/kernel/zshm.ko`` failed + with ``invalid module format`` and ZVM timed out waiting for the heartbeat. +* The current model does not carry RK3588-specific EL2 FP/SIMD or PAN + compatibility properties. A validated no-compatibility run still reached + ``Start VM Successful`` and ``[CREATE] linux VM 1 created and running`` for + ``linux_os-1``. If the release image times out, first check for a consistent + ZVM Linux kernel and root file system. + +Use the dedicated ROC machine and attach the prepared TF-card image as +``if=sd,index=2``. The Rockchip RKNS firmware image in this card is read by +the QEMU BootROM from LBA 64 and U-Boot then exposes the same card as +``mmc 1``: + +.. code-block:: bash + + $ build/qemu-system-aarch64 \ + -accel tcg \ + -machine rk3588s-roc-pc \ + -smp 8 \ + -m 1G \ + -drive if=sd,index=2,file=build/agent/zvm-rk3588/scratch/zvm-rk3588-qemu-roc-sd-official-fixed.img,format=raw \ + -nic user,model=gmac0 \ + -nic user,model=gmac1 \ + -serial mon:stdio \ + -display none + +For repeatable local smoke tests, start from a fresh prepared TF-card image or +refresh the Rockchip RKNS firmware area at LBA 64 before each run. The ZVM +package writes runtime state back to the card image; a reused scratch image can +otherwise fail in SPL with ``spl_load_simple_fit: can't load image loadables`` +or ``mmc block read error`` before ZVM itself starts. + +The validated firmware path reaches U-Boot proper after TPL/SPL checks the +ATF/U-Boot FIT loadables. U-Boot bootflow finds ``boot.scr.uimg`` on the first +FAT partition and executes the package's load/copy sequence automatically. If +autoboot is interrupted at the prompt, the same sequence can be run manually: + +.. code-block:: text + + => echo loading ZVM img ... + => mmc dev 1 + => fatload mmc 1:1 0x10000000 zvm_release_rk3588v2.bin + => cp.b 0x103fd000 0x38800000 0x2f000 + => cp.b 0x1038b000 0x38600000 0x71958 + => cp.b 0x10319000 0x38400000 0x712e0 + => cp.b 0x102a7000 0x38200000 0x71178 + => cp.b 0x1023c000 0x38000000 0x6afc8 + => cp.b 0x1023b000 0x12afa000 0x4 + => cp.b 0x100f7000 0x129b6000 0x143570 + => go 0x10000000 + +The local smoke run loads ``zvm_release_rk3588v2.bin``, copies the packaged +guest images to their fixed addresses, initializes GMAC1, starts the bundled +Zephyr guest, detects the Android/OpenHarmony/Linux file-system slots, and +loads the Linux Image, DTB, and initramfs buffers from the corrected second +partition offsets. The ROC board aliases GMAC1 as ``ethernet0``; GMAC0 is +also given a user netdev in the command above so the second modeled MAC is not +left without a peer. The Linux guest console is configured by the package for +the PL011 at ``0xfeb50000`` using ``console=ttyAMA2,1500000`` and +``earlycon=pl011,0xfeb50000``. ZVM keeps the host shell on the QEMU serial +terminal; after the Linux VM is running, connect the guest console from the ZVM +shell with: + +.. code-block:: text + + zvm_host:~# zvm look 1 + +The validated run then shows the Linux Buildroot console on the same QEMU +serial terminal. With a matching 5.10.110 kernel and ``zshm.ko``, ZVM reports +``Start VM Successful`` for ``linux_os-1``. + +Known limitations +----------------- + +The current model is intended for kernel and firmware bring-up smoke testing. +It does not model the closed PMU firmware or full EL3 MMU/xlat setup; the +RK3588 machine applies narrow runtime shims to enter BL31, keep BL31 +initialization moving, hand off to U-Boot proper, and prepare the NonSecure +PSCI/GIC state U-Boot's Linux entry path expects. U-Boot autoboot scans the +modeled eMMC/SDMMC, USB2, and GMAC boot devices and either boots the extlinux +test image or reaches the prompt when no boot image is present. USB3 DWC3, a +real TRNG, and attached USB storage are not modeled. + +Linux may print an ignored SCMI protocol-16 event registration warning, and the +PCIe legacy INTx irq-domain path is only stubbed; PCIe MSI/MSI-X is functional +through ITS. diff --git a/docs/system/arm/s32k5.rst b/docs/system/arm/s32k5.rst new file mode 100644 index 0000000000000..7503280e80b87 --- /dev/null +++ b/docs/system/arm/s32k5.rst @@ -0,0 +1,131 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +NXP S32K5 board (``s32k566-cvb-r52``) +===================================== + +Overview +-------- + +The ``s32k566-cvb-r52`` machine models the Cortex-R52 side of the NXP +S32K5XXCVB evaluation board with an S32K566 SoC. It is a minimal board model +intended for RTOS boot smoke tests, including Zephyr images built for the +``s32k5xxcvb/s32k566/r52`` board target. + +The model does not aim to provide complete S32K5 automotive SoC coverage. It +implements the CPU, memory, interrupt controller, LPUART console, and the small +Mode Entry (MC_ME) register subset needed by the verified Zephyr startup flow. +It should not be used as a compatibility promise for other S32K5 firmware. + +Supported devices +----------------- + +The ``s32k566-cvb-r52`` machine currently provides: + +* up to two Arm Cortex-R52 CPUs, with one CPU enabled by default; +* GICv3 interrupt controller with architectural timer interrupt wiring; +* Code MRAM mapped at ``0x08000000``; +* SRAM windows at ``0x21000000``, ``0x21080000``, ``0x21100000``, and + ``0x22000000``; +* NXP S32 MC_ME register subset at ``0x40498000``; +* NXP LPUART0 at ``0x400e0000``, connected to QEMU serial port 0. + +The MC_ME model covers the partition and mode update handshake used during +Zephyr SoC initialization. The LPUART model covers the polling console path +used by Zephyr's MCUX LPUART driver. + +Memory map +---------- + +The machine exposes the following implemented regions: + +.. list-table:: + :header-rows: 1 + + * - Address + - Size + - Description + * - ``0x08000000`` + - ``32 MiB + 0x100`` + - Code MRAM, used as the direct kernel loader address + * - ``0x21000000`` + - ``512 KiB`` + - Shared SRAM0 + * - ``0x21080000`` + - ``512 KiB`` + - Shared SRAM1 + * - ``0x21100000`` + - ``512 KiB`` + - Shared SRAM2 + * - ``0x22000000`` + - ``1 MiB`` by default + - CPE SRAM, backed by the machine RAM property + * - ``0x400e0000`` + - ``0x4000`` + - LPUART0 + * - ``0x40498000`` + - ``0x4000`` + - MC_ME + * - ``0x43000000`` + - device-defined + - GICv3 distributor + * - ``0x43100000`` + - device-defined + - GICv3 redistributor region + +Boot options +------------ + +Use ``-kernel`` to load an ELF image built for the S32K566 Cortex-R52 memory +map. The loader start address is ``0x08000000``. The default and only +supported CPU type is ``cortex-r52``, so the ``-cpu`` option is optional. + +For example, a Zephyr ``hello_world`` image with UART console enabled can be +started with: + +.. code-block:: bash + + $ qemu-system-arm \ + -machine s32k566-cvb-r52 \ + -cpu cortex-r52 \ + -display none \ + -monitor none \ + -serial stdio \ + -kernel /path/to/zephyr.elf + +The first QEMU serial port is connected to LPUART0. For Zephyr, visible +console output requires the image to enable serial console support and to use +LPUART0 as ``zephyr,console``. The upstream board defconfig may leave the +console disabled; in that case the image can still boot, but no UART output is +expected. + +The machine has no firmware loader, flash programmer, SD/eMMC controller, or +network device. Boot media such as U-Boot images, SD card images, and Linux +distribution images are therefore not supported by this machine. + +Known limitations +----------------- + +The machine intentionally stubs or omits many S32K5 peripherals. In +particular, the following areas are not complete hardware models: + +* clock controller register blocks; +* SIUL2 pinmux/GPIO blocks; +* SWT startup watchdog; +* xSPI controller; +* eDMA controller; +* flash programming, reset management, and low-power modes. + +The current model is suitable for direct RTOS image loading. It does not +implement a firmware boot flow or a general-purpose Linux platform. The +S32K566 side modeled here is an Arm Cortex-R52 real-time core, not an +A-profile Linux board model. + +Running tests +------------- + +The qtest coverage for this machine checks construction, MC_ME update behavior, +and LPUART reset/read/write behavior: + +.. code-block:: bash + + $ meson test -C build qtest-arm/s32k5-test diff --git a/docs/system/riscv/k230-canmv.rst b/docs/system/riscv/k230-canmv.rst new file mode 100644 index 0000000000000..640d2c18ab09f --- /dev/null +++ b/docs/system/riscv/k230-canmv.rst @@ -0,0 +1,74 @@ +Canaan CanMV-K230 board (``k230-canmv``) +======================================== +The ``k230-canmv`` machine models the Canaan CanMV-K230 board and is +compatible with the Kendryte K230 SDK ``k230_canmv_defconfig`` output. + +The K230 is a chip from the AIoT SoC series made by Kendryte ® — a part of +Canaan Inc. It uses a brand-new multi-heterogeneous unit accelerated computing +structure. + +This chip has 2 RISC-V computing cores and a new-generation KPU (Knowledge +Process Unit) smart computing unit. + +For more information, see + +Supported devices +----------------- +The ``k230-canmv`` machine supports the following devices: + +* 1 T-Head C908 core and optional C908V big core +* Core Local Interruptor (CLINT) +* Platform-Level Interrupt Controller (PLIC) +* 2 K230 Watchdog Timer +* 5 UART +* GSDMA and UGZIP blocks for SDK U-Boot gzip image decompression +* PDMA register block +* 2 SDHCI-compatible K230 DWC MSHC controllers + +Boot options +------------ +The ``k230-canmv`` machine boots the K230 SDK through M-mode U-Boot. U-Boot +reads the Linux payload from an attached SD card image and then starts +OpenSBI/Linux using the normal SDK boot flow. + +QEMU models the K230 SDHCI storage controller used by the CanMV removable card +slot, so no manual RAM loader setup is needed for the common Linux boot path. + +Running +------- + +U-Boot SD card boot +~~~~~~~~~~~~~~~~~~~ + +Use the SDK U-Boot binary as the machine firmware and attach a K230 SDK SD card +image with ``-drive if=sd``. The first SD drive is wired to the SDK CanMV +removable card slot, ``sdhci1@91581000``: + +.. code-block:: bash + + $ SDK=k230_sdk/output/k230_canmv_defconfig + $ SDIMG=/path/to/k230-sdk-sdcard.img + $ qemu-system-riscv64 -machine k230-canmv \ + -bios "$SDK/little/uboot/u-boot" \ + -drive if=sd,file="$SDIMG",format=raw \ + -nographic + +For an SDK image that also starts the C908V big core, enable both virtual CPUs: + +.. code-block:: bash + + $ qemu-system-riscv64 -machine k230-canmv,boot-both-cores=on -smp 2 \ + -bios "$SDK/little/uboot/u-boot" \ + -drive if=sd,file="$SDIMG",format=raw \ + -nographic + +Additional UARTs can be connected with extra ``-serial`` options. UART0 is used +by the U-Boot and Linux console. The C908V RT-Smart console uses UART3: + +.. code-block:: bash + + $ qemu-system-riscv64 -machine k230-canmv,boot-both-cores=on -smp 2 \ + -bios "$SDK/little/uboot/u-boot" \ + -drive if=sd,file="$SDIMG",format=raw \ + -serial mon:stdio -serial null -serial null -serial pty -serial null \ + -display none diff --git a/docs/system/riscv/k230.rst b/docs/system/riscv/k230.rst deleted file mode 100644 index cea8202e553d7..0000000000000 --- a/docs/system/riscv/k230.rst +++ /dev/null @@ -1,113 +0,0 @@ -Kendryte K230 virt reference platform (``k230``) -========================================================================== -The ``k230`` machine is compatible with the Kendryte K230 SDK. - -The K230 is a chip from the AIoT SoC series made by Kendryte ® — a part of -Canaan Inc. It uses a brand-new multi-heterogeneous unit accelerated computing -structure. - -This chip has 2 RISC-V computing cores and a new-generation KPU (Knowledge -Process Unit) smart computing unit. - -For more information, see - -Supported devices ------------------ -The ``k230`` machine supports the following devices: - -* 1 c908 cores (little core) -* Core Local Interruptor (CLINT) -* Platform-Level Interrupt Controller (PLIC) -* 2 K230 Watchdog Timer -* 5 UART - -Boot options ------------- -The ``k230`` machine supports K230 SDK boot through M-mode U-Boot, which then -starts OpenSBI/Linux with ``bootm``. It also supports direct Linux boot. - -K230 SDK Linux kernels use T-HEAD C9xx private MAEE page table attributes. QEMU -does not implement MAEE in the generic RISC-V MMU, so such kernels need to be -built with standard RISC-V PTE bits before they can boot under QEMU. - -Running -------- - -Direct Linux boot -~~~~~~~~~~~~~~~~~ - -This flow lets QEMU load OpenSBI, Linux, initrd, and DTB directly, without -running SDK U-Boot. The Linux Image must be rebuilt with standard RISC-V PTE -bits before running under QEMU. - -.. code-block:: bash - - $ SDK=k230_sdk/output/k230_canmv_defconfig - $ qemu-system-riscv64 -machine k230 \ - -kernel "$SDK/images/little-core/Image" \ - -dtb "/tmp/user-k230-qemu.dtb" \ - -initrd "$SDK/images/little-core/rootfs.cpio.gz" \ - -append "console=ttyS0,115200 earlycon=sbi cma=0" \ - -nographic - -Direct boot uses the SDK little-core RAM layout for OpenSBI at -``0x08000000``, Linux at ``0x08200000``, and the DTB at ``0x0a000000``. The -initrd is placed by QEMU's generic RISC-V boot helper, and QEMU writes the -initrd range and kernel command line into ``/chosen``. The DTB passed with -``-dtb`` should be derived from ``$SDK/images/little-core/k230.dtb`` and must -describe that initrd location as usable memory and disable any devices that are -not emulated by this machine. - -U-Boot boot -~~~~~~~~~~~ - -This flow starts SDK U-Boot in M-mode with ``-bios``. Until the SDK storage -path is modeled, place OpenSBI, Linux, initrd, and DTB in RAM with loader -devices and run ``bootm`` manually. The Linux Image must be rebuilt with -standard RISC-V PTE bits before running under QEMU. - -.. code-block:: bash - - $ SDK=k230_sdk/output/k230_canmv_defconfig - $ IMAGE=$SDK/images/little-core/Image - $ INITRD=$SDK/images/little-core/rootfs.cpio.gz - $ DTB=$SDK/images/little-core/k230.dtb - $ FWJUMP_UIMAGE=/tmp/k230-fw-jump.uImage - $ INITRD_END=$(printf "0x%x" $((0x0a100000 + $(stat -c %s "$INITRD")))) - $ "$SDK/little/buildroot-ext/host/bin/mkimage" \ - -A riscv -O linux -T kernel -C none \ - -a 0x8000000 -e 0x8000000 -n opensbi \ - -d "$SDK/images/little-core/fw_jump.bin" "$FWJUMP_UIMAGE" - $ qemu-system-riscv64 -machine k230 \ - -bios "$SDK/little/uboot/u-boot" \ - -device loader,file="$FWJUMP_UIMAGE",addr=0xc100000,force-raw=on \ - -device loader,file="$IMAGE",addr=0x8200000,force-raw=on \ - -device loader,file="$INITRD",addr=0xa100000,force-raw=on \ - -device loader,file="$DTB",addr=0xa000000,force-raw=on \ - -nographic - -The loader addresses mirror the SDK ``k230_canmv_defconfig`` output. Read the -U-Boot addresses from the generated environment, and read the Linux RAM base -from the generated ``hw/k230.dts.txt``. This replaces the SDK storage and -decompression steps. - -Press Enter to stop autoboot. At the U-Boot prompt, run these commands: - -.. code-block:: bash - - K230# setenv bootargs console=ttyS0,115200 earlycon=sbi cma=0 - K230# fdt addr 0xa000000 - K230# fdt resize 8192 - K230# fdt set /chosen linux,initrd-start <0x0 0xa100000> - K230# fdt set /chosen linux,initrd-end <0x0 ${INITRD_END}> - K230# fdt set /soc/sdhci0@91580000 status disabled - K230# fdt set /soc/sdhci1@91581000 status disabled - K230# bootm 0xc100000 - 0xa000000 - -Use ``setenv`` so ``bootm`` writes the kernel command line into -``/chosen/bootargs``. The ``fdt`` commands select the loaded DTB, add space for -edits, describe the initrd range in ``/chosen``, and disable SDHCI nodes because -this machine does not emulate those controllers yet. Replace ``${INITRD_END}`` -with the host-calculated value above when typing the command. ``cma=0`` avoids -the SDK kernel reserving too much of the little-core memory window for initramfs -boot. diff --git a/docs/system/riscv/milkv-duo.rst b/docs/system/riscv/milkv-duo.rst new file mode 100644 index 0000000000000..768df82badd12 --- /dev/null +++ b/docs/system/riscv/milkv-duo.rst @@ -0,0 +1,80 @@ +Milk-V Duo board (``milkv-duo``) +================================ + +The ``milkv-duo`` machine models the Milk-V Duo board with a Sophgo CV1800B +SoC. It is intended for early firmware, kernel, and device-model development +for the CV1800B platform. + +Supported devices +----------------- + +The ``milkv-duo`` machine supports the following devices: + +* Up to 2 T-Head C906 RISC-V harts +* Core Local Interruptor (ACLINT) +* Platform-Level Interrupt Controller (PLIC) +* DesignWare 8250-compatible UART0 +* SDHCI-compatible SD0 controller +* CV1800B clock controller + +Several CV1800B MMIO regions are present as unimplemented devices so firmware +or guest software can probe the expected address map while the corresponding +device models are still incomplete. + +Boot options +------------ + +The machine supports direct payload loading with ``-kernel`` and firmware +entry with ``-bios``. The model does not generate a device tree, so direct +Linux boot requires an external CV1800B or Milk-V Duo DTB passed with +``-dtb``. + +Direct Linux boot +~~~~~~~~~~~~~~~~~ + +Use ``-kernel`` to load a kernel image, ``-dtb`` to provide the board device +tree, and optionally ``-initrd`` and ``-append`` for the root filesystem and +kernel command line: + +.. code-block:: bash + + $ qemu-system-riscv64 -machine milkv-duo \ + -smp 1 -m 64M \ + -kernel /path/to/Image \ + -dtb /path/to/milkv-duo.dtb \ + -initrd /path/to/initramfs.cpio.gz \ + -append "console=ttyS0" \ + -serial mon:stdio \ + -display none + +Firmware boot +~~~~~~~~~~~~~ + +Use ``-bios`` to enter a CV1800B firmware image. The firmware image is loaded +at the start of DRAM and entered from the modelled boot ROM reset vector: + +.. code-block:: bash + + $ qemu-system-riscv64 -machine milkv-duo \ + -smp 1 -m 64M \ + -bios /path/to/firmware.elf \ + -serial mon:stdio \ + -display none + +Known limitations +----------------- + +The current model focuses on the CPU, interrupt controller, UART, SDHCI +controller, and clock-controller paths needed by early software bring-up. +Other CV1800B peripheral blocks are represented by unimplemented MMIO regions. + +Running tests +------------- + +The Milk-V Duo qtest covers the UART component ID registers and basic +read/write behavior of the CV1800B clock controller: + +.. code-block:: bash + + $ QTEST_QEMU_BINARY=./build/qemu-system-riscv64 \ + ./build/tests/qtest/milkv-duo-test --tap -k diff --git a/docs/system/riscv/riscv-server-ref.rst b/docs/system/riscv/riscv-server-ref.rst new file mode 100644 index 0000000000000..0573b4e07cd4b --- /dev/null +++ b/docs/system/riscv/riscv-server-ref.rst @@ -0,0 +1,62 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +RISC-V Server Platform Reference board (``riscv-server-ref``) +============================================================= + +The RISC-V Server Platform specification `spec`_ defines a standardized +set of hardware and software capabilities that portable system software, +such as OS and hypervisors, can rely on being present in a RISC-V server +platform. This machine aims to emulate this specification, providing +an environment for firmware/OS development and testing. + +`spec`_ is version 1.0 at the introduction of this board. New spec versions +might trigger a revision of the emulation itself, which will strive to always +match the latest version available. In case the emulation changes aren't +backwards compatible we'll introduce a versioning scheme, probably via +a machine property, to allow older SW to run with older spec versions. + +The main features included in the riscv-server-ref board are: + +* IOMMU platform device (riscv-iommu-sys) +* AIA +* PCIe AHCI +* PCIe NIC +* No virtio mmio bus +* No fw_cfg device +* No ACPI table +* Minimal device tree nodes + +There are multiple ways of using this reference board. The spec compliant way +is using an EDK2 image and a TPM device. The board was tested with the TPM +device ``tpm-tis`` that uses the external ``swtpm`` emulator. More info on how +to use this device can be found in `tpm`_. + +To use this board coupled with the tpm-tis device, first start the ``swtpm`` +process in a shell (the ``log`` parameter is optional): + +.. code-block:: bash + + $ mkdir /tmp/mytpm1 + $ swtpm socket --tpmstate dir=/tmp/mytpm1 \ + --ctrl type=unixio,path=/tmp/mytpm1/swtpm-sock \ + --tpm2 \ + --log level=20 + +And then start QEMU with: + +.. code-block:: bash + + qemu-system-riscv64 -M riscv-server-ref \ + -bios fw_dynamic.bin \ + -kernel EDK2.fd \ + -drive file=nvme_disk.ext2,format=raw,id=hd0,if=none \ + -device ahci,id=ahci \ + -device ide-hd,drive=hd0,bus=ahci.0 \ + -chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock \ + -tpmdev emulator,id=tpm0,chardev=chrtpm \ + -device tpm-tis-device,tpmdev=tpm0 \ + -nographic + + +.. _spec: https://github.com/riscv-non-isa/riscv-server-platform +.. _tpm: https://qemu-project.gitlab.io/qemu/specs/tpm.html diff --git a/docs/system/riscv/spacemit-k3.rst b/docs/system/riscv/spacemit-k3.rst new file mode 100644 index 0000000000000..f6b17d34558a0 --- /dev/null +++ b/docs/system/riscv/spacemit-k3.rst @@ -0,0 +1,101 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +SpacemiT K3 Pico-ITX board (``k3-pico-itx``) +================================================ + +The ``k3-pico-itx`` machine models the standard RISC-V platform subset +needed to boot a SpacemiT K3 SDK Linux kernel on the K3 Pico-ITX board. It +focuses on the eight X100 application harts and does not expose the K3 A100 or +IME harts. + +The machine has a fixed CPU topology and memory layout matching the Linux +view of the board. The default configuration must be used without overriding +the CPU type, RAM size, or SMP topology. + +Supported devices +----------------- + +The ``k3-pico-itx`` machine supports: + +* eight SpacemiT X100 harts implementing RVA23S64 in two clusters of four; +* 256-bit RISC-V vector registers, Sstc, and the Smaia/Ssaia extensions; +* 2 GiB of RAM starting at ``0x102000000``; +* a 32 MiB firmware window starting at ``0x100000000``; +* 512 KiB of on-chip SRAM starting at ``0xc0800000``; +* an ACLINT software interrupt and machine timer block with a 24 MHz timebase; +* machine- and supervisor-level APLIC and IMSIC interrupt controllers; and +* the 8250-compatible UART0 at ``0xd4017000``, using interrupt source 42; +* the SDHCI0 controller at ``0xd4280000``, using interrupt source 99; and +* the SD clock/reset and boot-mode registers used by U-Boot. + +Boot options +------------ + +The machine supports both direct Linux boot and a firmware path through +U-Boot proper and SD. Both paths use generic OpenSBI firmware and require an +external device tree because the machine validates the exact K3 Linux topology +and address map before boot. + +Direct Linux boot +~~~~~~~~~~~~~~~~~ + +The following example uses artifacts built from the SpacemiT K3 Buildroot SDK +v1.0.2: + +.. code-block:: bash + + $ qemu-system-riscv64 \ + -machine k3-pico-itx \ + -bios fw_dynamic.bin \ + -kernel Image \ + -initrd k3-qemu-initramfs.cpio.gz \ + -dtb k3-pico-itx-qemu.dtb \ + -append "earlycon=uart8250,mmio32,0xd4017000,115200 \ + console=ttyS0,115200 rdinit=/init" \ + -nographic -no-reboot + +The machine supplies the fixed 2 GiB RAM size and 8-hart topology, so no +``-m`` or ``-smp`` options are needed. Direct kernel boot requires +OpenSBI; ``-bios none`` is rejected when ``-kernel`` is present. +The machine uses the fixed ``spacemit-x100`` CPU model. + +U-Boot and SD boot +~~~~~~~~~~~~~~~~~~ + +The K3 SDK U-Boot proper can run as the supervisor-mode next stage of OpenSBI. +Attach a raw SD image as the first SD drive; it is wired to SDHCI0 and appears +as MMC device 0 in U-Boot: + +.. code-block:: bash + + $ qemu-system-riscv64 \ + -machine k3-pico-itx \ + -bios fw_dynamic.bin \ + -kernel u-boot.bin \ + -dtb k3-pico-itx-qemu-uboot.dtb \ + -drive file=k3-qemu-sd.raw,if=sd,format=raw,snapshot=on \ + -nographic -no-reboot + +QEMU reports SD boot through the K3 CIU boot flag. U-Boot then initializes +SDHCI0, finds the GPT partition named ``bootfs``, imports ``env_k3.txt``, and +loads the Linux ``Image``, device tree, and optional initramfs from that FAT +partition. Using ``snapshot=on`` prevents firmware or the guest from changing +the source image. + +The controller advertises a 52 MHz base clock, four-bit 3.3 V operation, +SD High Speed (up to 50 MHz), and ADMA2. It does not advertise 1.8 V +signaling or UHS modes. + +Limitations +----------- + +This machine is a boot-path subset rather than a complete K3 hardware model. +It disables the X100 H extension because the VS interrupt files and +virtualization path are not modeled. The K3 BootROM, bootinfo parsing, +U-Boot SPL, and LPDDR initialization and training are also not modeled; QEMU +provides initialized RAM and starts OpenSBI directly. + +The A100 and IME harts, eMMC, UFS, SPI flash, PCIe, networking, multimedia +accelerators, system power management, and most board peripherals are not +implemented. SDHCI0 implements the register and DMA behavior required by the +documented U-Boot-to-Linux path, not every vendor PHY tuning mode. diff --git a/docs/system/target-arm.rst b/docs/system/target-arm.rst index e34492402f0af..be665576ab862 100644 --- a/docs/system/target-arm.rst +++ b/docs/system/target-arm.rst @@ -72,6 +72,7 @@ Board-specific documentation :maxdepth: 1 arm/max78000 + arm/ax650x-pyramid arm/integratorcp arm/mps2 arm/musca @@ -91,12 +92,15 @@ Board-specific documentation arm/kzm arm/nrf arm/nuvoton + arm/s32k5 arm/imx25-pdk arm/mcimx6ul-evk arm/mcimx7d-sabre arm/imx8m arm/orangepi + arm/phytium-pi arm/raspi + arm/rk3588 arm/collie arm/sx1 arm/stellaris diff --git a/docs/system/target-riscv.rst b/docs/system/target-riscv.rst index 2639866a3e8a1..f208195148973 100644 --- a/docs/system/target-riscv.rst +++ b/docs/system/target-riscv.rst @@ -66,15 +66,18 @@ undocumented; you can get a complete list by running .. toctree:: :maxdepth: 1 - riscv/k230 + riscv/k230-canmv riscv/microblaze-v-generic riscv/microchip-icicle-kit + riscv/milkv-duo riscv/mips riscv/shakti-c riscv/sifive_u + riscv/spacemit-k3 riscv/tt_atlantis riscv/virt riscv/xiangshan-kunminghu + riscv/riscv-server-ref RISC-V CPU firmware ------------------- diff --git a/fpu/meson.build b/fpu/meson.build index 646c76f0c69e3..2201d1f69df5b 100644 --- a/fpu/meson.build +++ b/fpu/meson.build @@ -1 +1,4 @@ common_ss.add(when: 'CONFIG_TCG', if_true: files('softfloat.c')) +if not config_all_accel.has_key('CONFIG_TCG') + system_ss.add(when: 'CONFIG_K230_KPU', if_true: files('softfloat.c')) +endif diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 82e0bc2e70043..957c0298e0607 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -38,6 +38,20 @@ config ARM_VIRT select ACPI_HMAT select WDT_SBSA +config AX650X_PYRAMID + bool + default y + depends on TCG && AARCH64 + select ARM_GIC + select AX650X_DWMAC_GLUE + select AX650X_HWSPINLOCK + select AX650X_SDHCI + select DEVICE_TREE + select DW_APB_GPIO + select DWMAC4 + select SERIAL_MM + select UNIMP + config CUBIEBOARD bool default y @@ -192,6 +206,50 @@ config SBSA_REF select IDE_BUS select IDE_DEV +config RK3588_EVB + bool + default y + depends on TCG && AARCH64 + select ARM_GIC + select DEVICE_TREE + select DWMAC4 + select DW_MMC + select DW_APB_UART_VENDOR + select PCI_EXPRESS_DESIGNWARE + select RK3588_CRU + select ROCKCHIP_DWCMSHC + select ROCKCHIP_PCIE + select ROCKCHIP_STIMER + select ROCKCHIP_SYSCON + select RK3588_SCMI + select ROCKCHIP_GPIO + select SDHCI + select SERIAL_MM + select UNIMP + +config S32K5 + bool + default y + depends on TCG && ARM + select ARM_GIC + select ARM_GICV5 if TCG + select NXP_LPUART + select NXP_S32_MC_ME + select UNIMP + +config PHYTIUMPI + bool + default y + depends on TCG && AARCH64 + select ARM_GIC + select DEVICE_TREE + select PHYTIUM_DDR_CTRL + select PHYTIUM_MCI + select PHYTIUM_SCP_MAILBOX + select PHYTIUM_XMAC + select PL011 # UART + select UNIMP + config SABRELITE bool default y @@ -466,6 +524,7 @@ config NPCM7XX select ARM_GIC select SMBUS select AT24C # EEPROM + select NPCM_GMAC select MAX34451 select ISL_PMBUS_VR select PL310 # cache controller diff --git a/hw/arm/ax650x-dwmac.c b/hw/arm/ax650x-dwmac.c new file mode 100644 index 0000000000000..0fa672758fc0f --- /dev/null +++ b/hw/arm/ax650x-dwmac.c @@ -0,0 +1,317 @@ +/* + * AX650X DWMAC board integration + * + * This file owns the networking-related board topology and FDT nodes. The + * reusable devices themselves live in hw/net, hw/gpio, and hw/misc. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "hw/arm/ax650x-dwmac.h" +#include "hw/arm/fdt.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/sysbus.h" +#include "hw/gpio/dw-apb-gpio.h" +#include "hw/misc/ax650x-dwmac-glue.h" +#include "hw/misc/ax650x-hwspinlock.h" +#include "hw/misc/unimp.h" +#include "hw/net/dwmac4.h" +#include "net/net.h" +#include "system/device_tree.h" + +#define AX650X_DWMAC_NR 2 +#define AX650X_DWMAC0_BASE 0x10140000 +#define AX650X_DWMAC1_BASE 0x30800000 +#define AX650X_DWMAC0_IRQ 104 +#define AX650X_DWMAC1_IRQ 186 +#define AX650X_DWMAC0_GLB_BASE 0x10000000 +#define AX650X_DWMAC1_GLB_BASE 0x30000000 +#define AX650X_DWMAC0_CLK_BASE 0x10010000 +#define AX650X_DWMAC1_CLK_BASE 0x30010000 +#define AX650X_GPIO0_BASE 0x02003000 +#define AX650X_GPIO1_BASE 0x02004000 +#define AX650X_HWSPINLOCK_BASE 0x04510000 +#define AX650X_DPHY_BASE 0x13c00000 +#define AX650X_DPHY_SIZE 0x00080000 + +#define AX650X_DWMAC0_CLOCK_HZ 400000000 +#define AX650X_DWMAC1_CLOCK_HZ 500000000 +#define AX650X_EPHY_CLOCK_HZ 25000000 +#define AX650X_RGMII_CLOCK_HZ 125000000 +#define AX650X_RMII_CLOCK_HZ 50000000 +#define AX650X_PTP_CLOCK_HZ 50000000 +#define AX650X_HWSPINLOCK_CLOCK_HZ 200000000 + +static const hwaddr dwmac_base[AX650X_DWMAC_NR] = { + AX650X_DWMAC0_BASE, AX650X_DWMAC1_BASE, +}; + +static const unsigned int dwmac_irq[AX650X_DWMAC_NR] = { + AX650X_DWMAC0_IRQ, AX650X_DWMAC1_IRQ, +}; + +static const hwaddr dwmac_glb_base[AX650X_DWMAC_NR] = { + AX650X_DWMAC0_GLB_BASE, AX650X_DWMAC1_GLB_BASE, +}; + +static const hwaddr dwmac_clk_base[AX650X_DWMAC_NR] = { + AX650X_DWMAC0_CLK_BASE, AX650X_DWMAC1_CLK_BASE, +}; + +static const hwaddr gpio_base[AX650X_DWMAC_NR] = { + AX650X_GPIO0_BASE, AX650X_GPIO1_BASE, +}; + +static const char *const eth_path[AX650X_DWMAC_NR] = { + "/soc/ethernet@10140000", + "/soc/ethernet@30800000", +}; + +static const char *const gpio_path[AX650X_DWMAC_NR] = { + "/soc/gpio@2003000", + "/soc/gpio@2004000", +}; + +void ax650x_dwmac_create(DeviceState *gic) +{ + DeviceState *hwspinlock = qdev_new(TYPE_AX650X_HWSPINLOCK); + SysBusDevice *sbd = SYS_BUS_DEVICE(hwspinlock); + + sysbus_realize_and_unref(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, AX650X_HWSPINLOCK_BASE); + + /* + * The vendor AXERA GPIO driver reads all eight DPHY windows before it + * registers GPIO lines. DPHY behavior is not part of networking, but a + * named unimplemented window keeps those discovery reads non-fatal. + */ + create_unimplemented_device("ax650x.dphy", AX650X_DPHY_BASE, + AX650X_DPHY_SIZE); + + for (unsigned int i = 0; i < AX650X_DWMAC_NR; i++) { + DeviceState *gpio = qdev_new(TYPE_DW_APB_GPIO); + DeviceState *glue = qdev_new(TYPE_AX650X_DWMAC_GLUE); + DeviceState *mac = qdev_new(TYPE_DWMAC4); + + sbd = SYS_BUS_DEVICE(gpio); + sysbus_realize_and_unref(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, gpio_base[i]); + + qdev_prop_set_uint8(glue, "port", i); + sbd = SYS_BUS_DEVICE(glue); + sysbus_realize_and_unref(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, dwmac_glb_base[i]); + sysbus_mmio_map(sbd, 1, dwmac_clk_base[i]); + + qemu_configure_nic_device(mac, i == 0, i == 0 ? "gmac0" : "gmac1"); + qdev_prop_set_uint8(mac, "snps-version", 0x52); + qdev_prop_set_uint8(mac, "user-version", 0x10); + qdev_prop_set_uint8(mac, "dma-width", 40); + qdev_prop_set_uint8(mac, "phy-addr", 1); + qdev_prop_set_uint16(mac, "phy-id1", 0x937c); + qdev_prop_set_uint16(mac, "phy-id2", 0x4030); + qdev_prop_set_uint32(mac, "rx-fifo-size", 65536); + qdev_prop_set_uint32(mac, "tx-fifo-size", 32768); + sbd = SYS_BUS_DEVICE(mac); + sysbus_realize_and_unref(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, dwmac_base[i]); + sysbus_connect_irq(sbd, 0, qdev_get_gpio_in(gic, dwmac_irq[i])); + } +} + +static uint32_t ax650x_fdt_add_fixed_clock(void *fdt, const char *path, + uint32_t frequency) +{ + uint32_t phandle; + + qemu_fdt_add_subnode(fdt, path); + qemu_fdt_setprop_string(fdt, path, "compatible", "fixed-clock"); + qemu_fdt_setprop_cell(fdt, path, "#clock-cells", 0); + qemu_fdt_setprop_cell(fdt, path, "clock-frequency", frequency); + phandle = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_setprop_cell(fdt, path, "phandle", phandle); + + return phandle; +} + +static void ax650x_dwmac_add_phy_fdt(void *fdt, const char *path, + uint32_t *phandle) +{ + qemu_fdt_add_subnode(fdt, path); + qemu_fdt_setprop_string(fdt, path, "compatible", + "ethernet-phy-id937c.4030"); + qemu_fdt_setprop_cell(fdt, path, "reg", 1); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,led-enable", 0x5); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,led-mode", 0x6251); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,led-period", 0x3); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,led-on", 0x2); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,led-polarity", 0x1c00); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,patch-enable", 0x1); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,rgmii-enable", 0x5); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,clk-enable", 0x9); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,fld-enable", 0x3); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,fld-delay", 0); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,wol-enable", 0x3); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,interrupt-enable", 0x5); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,downshift-enable", 0x3); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,downshift-count", 0x3); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,work_mode-enable", 0x1); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,work_mode-mode", 0); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,lpbk-enable", 0x1); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,lpbk-mode", 0x2); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,slew_rate-enable", 0x1); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,rxc_out-enable", 0x1); + *phandle = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_setprop_cell(fdt, path, "phandle", *phandle); +} + +void ax650x_dwmac_create_fdt(void *fdt) +{ + static const unsigned int phy_reset_pin[AX650X_DWMAC_NR] = { 7, 11 }; + const char clock_names[] = + "emac_aclk\0ephy_clk\0rgmii_tx_clk\0rmii_phy_clk\0rmii_rx_clk\0" + "ptp_clk"; + uint32_t dwmac_clock_phandles[AX650X_DWMAC_NR]; + uint32_t gpio_phandles[AX650X_DWMAC_NR]; + uint32_t ephy_clock_phandle; + uint32_t rgmii_clock_phandle; + uint32_t rmii_clock_phandle; + uint32_t ptp_clock_phandle; + uint32_t hwspinlock_clock_phandle; + uint32_t stmmac_axi_phandle; + uint32_t mtl_rx_phandle; + uint32_t mtl_tx_phandle; + + qemu_fdt_setprop_string(fdt, "/aliases", "ethernet0", eth_path[0]); + qemu_fdt_setprop_string(fdt, "/aliases", "ethernet1", eth_path[1]); + + dwmac_clock_phandles[0] = ax650x_fdt_add_fixed_clock( + fdt, "/dwmac0-clock", AX650X_DWMAC0_CLOCK_HZ); + dwmac_clock_phandles[1] = ax650x_fdt_add_fixed_clock( + fdt, "/dwmac1-clock", AX650X_DWMAC1_CLOCK_HZ); + ephy_clock_phandle = ax650x_fdt_add_fixed_clock( + fdt, "/ephy-clock", AX650X_EPHY_CLOCK_HZ); + rgmii_clock_phandle = ax650x_fdt_add_fixed_clock( + fdt, "/rgmii-clock", AX650X_RGMII_CLOCK_HZ); + rmii_clock_phandle = ax650x_fdt_add_fixed_clock( + fdt, "/rmii-clock", AX650X_RMII_CLOCK_HZ); + ptp_clock_phandle = ax650x_fdt_add_fixed_clock( + fdt, "/ptp-clock", AX650X_PTP_CLOCK_HZ); + hwspinlock_clock_phandle = ax650x_fdt_add_fixed_clock( + fdt, "/hwspinlock-clock", AX650X_HWSPINLOCK_CLOCK_HZ); + + qemu_fdt_add_subnode(fdt, "/soc/ax_hwspinlock@4510000"); + qemu_fdt_setprop_string(fdt, "/soc/ax_hwspinlock@4510000", "compatible", + "axera,hwspinlock-r1p0"); + qemu_fdt_setprop_sized_cells(fdt, "/soc/ax_hwspinlock@4510000", "reg", + 2, AX650X_HWSPINLOCK_BASE, + 2, AX650X_HWSPINLOCK_MMIO_SIZE); + qemu_fdt_setprop_cell(fdt, "/soc/ax_hwspinlock@4510000", "clocks", + hwspinlock_clock_phandle); + qemu_fdt_setprop_string(fdt, "/soc/ax_hwspinlock@4510000", "clock-names", + "spinlock_pclk"); + qemu_fdt_setprop_string(fdt, "/soc/ax_hwspinlock@4510000", "status", + "okay"); + + qemu_fdt_add_subnode(fdt, "/soc/stmmac-axi-config"); + qemu_fdt_setprop_cells(fdt, "/soc/stmmac-axi-config", + "snps,wr_osr_lmt", 15); + qemu_fdt_setprop_cells(fdt, "/soc/stmmac-axi-config", + "snps,rd_osr_lmt", 15); + qemu_fdt_setprop_cells(fdt, "/soc/stmmac-axi-config", "snps,blen", + 0, 0, 0, 32, 16, 8, 4); + stmmac_axi_phandle = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_setprop_cell(fdt, "/soc/stmmac-axi-config", "phandle", + stmmac_axi_phandle); + + qemu_fdt_add_subnode(fdt, "/soc/rx-queues-config"); + qemu_fdt_setprop_cell(fdt, "/soc/rx-queues-config", + "snps,rx-queues-to-use", 1); + qemu_fdt_add_subnode(fdt, "/soc/rx-queues-config/queue0"); + mtl_rx_phandle = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_setprop_cell(fdt, "/soc/rx-queues-config", "phandle", + mtl_rx_phandle); + + qemu_fdt_add_subnode(fdt, "/soc/tx-queues-config"); + qemu_fdt_setprop_cell(fdt, "/soc/tx-queues-config", + "snps,tx-queues-to-use", 1); + qemu_fdt_add_subnode(fdt, "/soc/tx-queues-config/queue0"); + mtl_tx_phandle = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_setprop_cell(fdt, "/soc/tx-queues-config", "phandle", + mtl_tx_phandle); + + for (unsigned int i = 0; i < AX650X_DWMAC_NR; i++) { + g_autofree char *gpio_port_path = + g_strdup_printf("%s/gpio-controller@0", gpio_path[i]); + g_autofree char *mdio_path = g_strdup_printf("%s/mdio", eth_path[i]); + g_autofree char *phy_path = g_strdup_printf("%s/jl2xxx-phy@1", + mdio_path); + uint32_t phy_phandle; + + qemu_fdt_add_subnode(fdt, gpio_path[i]); + qemu_fdt_setprop_string(fdt, gpio_path[i], "compatible", + "snps,ax-apb-gpio"); + qemu_fdt_setprop_cell(fdt, gpio_path[i], "#address-cells", 1); + qemu_fdt_setprop_cell(fdt, gpio_path[i], "#size-cells", 0); + qemu_fdt_setprop_sized_cells(fdt, gpio_path[i], "reg", + 2, gpio_base[i], + 2, DW_APB_GPIO_MMIO_SIZE); + if (i == 0) { + qemu_fdt_setprop_cell(fdt, gpio_path[i], "hwlock_id", 12); + } + qemu_fdt_setprop_string(fdt, gpio_path[i], "status", "okay"); + + qemu_fdt_add_subnode(fdt, gpio_port_path); + qemu_fdt_setprop_string(fdt, gpio_port_path, "compatible", + "snps,dw-apb-gpio-port"); + qemu_fdt_setprop_cell(fdt, gpio_port_path, "reg", 0); + qemu_fdt_setprop(fdt, gpio_port_path, "gpio-controller", NULL, 0); + qemu_fdt_setprop_cell(fdt, gpio_port_path, "#gpio-cells", 2); + qemu_fdt_setprop_cell(fdt, gpio_port_path, "snps,nr-gpios", + DW_APB_GPIO_NR_PINS); + gpio_phandles[i] = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_setprop_cell(fdt, gpio_port_path, "phandle", + gpio_phandles[i]); + + qemu_fdt_add_subnode(fdt, eth_path[i]); + qemu_fdt_setprop_string(fdt, eth_path[i], "compatible", + "axera,dwmac-4.10a"); + qemu_fdt_setprop_sized_cells(fdt, eth_path[i], "reg", + 2, dwmac_base[i], + 2, DWMAC4_MMIO_SIZE); + qemu_fdt_setprop_cells(fdt, eth_path[i], "interrupts", + GIC_FDT_IRQ_TYPE_SPI, dwmac_irq[i], + GIC_FDT_IRQ_FLAGS_LEVEL_HI); + qemu_fdt_setprop_string(fdt, eth_path[i], "interrupt-names", + "macirq"); + qemu_fdt_setprop_cells(fdt, eth_path[i], "clocks", + dwmac_clock_phandles[i], ephy_clock_phandle, + rgmii_clock_phandle, rmii_clock_phandle, + rmii_clock_phandle, ptp_clock_phandle); + qemu_fdt_setprop(fdt, eth_path[i], "clock-names", clock_names, + sizeof(clock_names)); + qemu_fdt_setprop_cell(fdt, eth_path[i], "rx-fifo-depth", 65536); + qemu_fdt_setprop_cell(fdt, eth_path[i], "tx-fifo-depth", 32768); + qemu_fdt_setprop_cell(fdt, eth_path[i], "snps,axi-config", + stmmac_axi_phandle); + qemu_fdt_setprop_cell(fdt, eth_path[i], "snps,mtl-rx-config", + mtl_rx_phandle); + qemu_fdt_setprop_cell(fdt, eth_path[i], "snps,mtl-tx-config", + mtl_tx_phandle); + qemu_fdt_setprop_cells(fdt, eth_path[i], "phy-rst-gpio", + gpio_phandles[i], phy_reset_pin[i], 0); + qemu_fdt_setprop_string(fdt, eth_path[i], "phy-mode", "rgmii"); + qemu_fdt_setprop_string(fdt, eth_path[i], "status", "okay"); + + qemu_fdt_add_subnode(fdt, mdio_path); + qemu_fdt_setprop_string(fdt, mdio_path, "compatible", + "snps,dwmac-mdio"); + qemu_fdt_setprop_cell(fdt, mdio_path, "#address-cells", 1); + qemu_fdt_setprop_cell(fdt, mdio_path, "#size-cells", 0); + + ax650x_dwmac_add_phy_fdt(fdt, phy_path, &phy_phandle); + qemu_fdt_setprop_cell(fdt, eth_path[i], "phy-handle", phy_phandle); + } +} diff --git a/hw/arm/ax650x.c b/hw/arm/ax650x.c new file mode 100644 index 0000000000000..d68ccfd299ba2 --- /dev/null +++ b/hw/arm/ax650x.c @@ -0,0 +1,551 @@ +/* + * Axera AX650X AI Pyramid machine + * + * Copyright (c) 2026 Zevorn + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "qemu/units.h" +#include "qapi/error.h" +#include "hw/arm/ax650x-dwmac.h" +#include "hw/arm/boot.h" +#include "hw/arm/bsa.h" +#include "hw/arm/fdt.h" +#include "hw/arm/machines-qom.h" +#include "hw/char/serial-mm.h" +#include "hw/core/boards.h" +#include "hw/core/cpu.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/sysbus.h" +#include "hw/intc/arm_gic.h" +#include "hw/sd/ax650x-sdhci.h" +#include "hw/sd/sd.h" +#include "system/address-spaces.h" +#include "system/blockdev.h" +#include "system/device_tree.h" +#include "system/system.h" +#include "target/arm/cpu.h" +#include "target/arm/cpu-qom.h" +#include "target/arm/gtimer.h" + +#define TYPE_AX650X_PYRAMID_MACHINE MACHINE_TYPE_NAME("ax650x-pyramid") +OBJECT_DECLARE_SIMPLE_TYPE(AX650XPyramidState, AX650X_PYRAMID_MACHINE) + +#define AX650X_NUM_CPUS 8 +#define AX650X_NUM_IRQS 256 +#define AX650X_NUM_SPIS (AX650X_NUM_IRQS - GIC_INTERNAL) + +#define AX650X_RAM_BASE 0x100000000ULL +#define AX650X_RAM_SIZE (2 * GiB) + +#define AX650X_GIC_DIST_BASE 0x04901000 +#define AX650X_GIC_DIST_SIZE 0x1000 +#define AX650X_GIC_CPU_BASE 0x04902000 +#define AX650X_GIC_CPU_SIZE 0x1000 +#define AX650X_GIC_HYP_BASE 0x04904000 +#define AX650X_GIC_HYP_SIZE 0x2000 +#define AX650X_GIC_VCPU_BASE 0x04906000 +#define AX650X_GIC_VCPU_SIZE 0x2000 + +#define AX650X_UART0_BASE 0x02016000 +#define AX650X_UART0_SIZE 0x400 +#define AX650X_UART_STD_SIZE 0x20 +#define AX650X_UART_USR 0x7c +#define AX650X_UART_UCV 0xf8 +#define AX650X_UART_USR_TFNF BIT(1) +#define AX650X_UART_USR_TFE BIT(2) +#define AX650X_UART0_IRQ 135 +#define AX650X_UART_CLOCK_HZ 200000000 +#define AX650X_UART_BAUDBASE (AX650X_UART_CLOCK_HZ / 16) + +#define AX650X_EMMC_BASE 0x28000000 +#define AX650X_EMMC_SIZE 0x600 +#define AX650X_EMMC_IRQ 93 +#define AX650X_EMMC_CLOCK_HZ 200000000 + +#define AX650X_TIMER_CLOCK_HZ 24000000 +#define AX650X_PMU_IRQ_BASE 80 +#define AX650X_GIC_MAINT_PPI 9 +#define AX650X_GIC_PPI_FLAGS 0xff04 + +struct AX650XPyramidState { + MachineState parent_obj; + + ARMCPU *cpus[AX650X_NUM_CPUS]; + DeviceState *gic; + MemoryRegion gic_cpu_alias; + MemoryRegion uart0_ext; + struct arm_boot_info bootinfo; + void *fdt; + int fdt_size; +}; + +static uint64_t ax650x_uart_ext_read(void *opaque, hwaddr offset, + unsigned int size) +{ + hwaddr reg = AX650X_UART_STD_SIZE + offset; + + switch (reg) { + case AX650X_UART_USR: + return AX650X_UART_USR_TFNF | AX650X_UART_USR_TFE; + case AX650X_UART_UCV: + /* + * The hardware component revision is not documented. Zero selects + * the AXERA driver's defined fallback for a UART without DesignWare + * additional features. + */ + return 0; + default: + return 0; + } +} + +static void ax650x_uart_ext_write(void *opaque, hwaddr offset, + uint64_t value, unsigned int size) +{ +} + +static const MemoryRegionOps ax650x_uart_ext_ops = { + .read = ax650x_uart_ext_read, + .write = ax650x_uart_ext_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +static uint64_t ax650x_cpu_mpidr(unsigned int cpu) +{ + return (uint64_t)cpu << 8; +} + +static void ax650x_create_cpus(AX650XPyramidState *s) +{ + MachineState *machine = MACHINE(s); + MemoryRegion *sysmem = get_system_memory(); + + for (unsigned int i = 0; i < machine->smp.cpus; i++) { + Object *cpuobj = object_new(machine->cpu_type); + CPUState *cs = CPU(cpuobj); + + cs->cpu_index = i; + object_property_set_int(cpuobj, "mp-affinity", + ax650x_cpu_mpidr(i), &error_abort); + object_property_set_int(cpuobj, "cntfrq", AX650X_TIMER_CLOCK_HZ, + &error_abort); + object_property_set_bool(cpuobj, "has_el3", false, &error_abort); + object_property_set_link(cpuobj, "memory", OBJECT(sysmem), + &error_abort); + qdev_realize(DEVICE(cpuobj), NULL, &error_fatal); + s->cpus[i] = ARM_CPU(cpuobj); + object_unref(cpuobj); + } +} + +static void ax650x_create_gic(AX650XPyramidState *s) +{ + MachineState *machine = MACHINE(s); + MemoryRegion *sysmem = get_system_memory(); + SysBusDevice *gicbus; + + s->gic = qdev_new(gic_class_name()); + qdev_prop_set_uint32(s->gic, "revision", 2); + qdev_prop_set_uint32(s->gic, "num-cpu", machine->smp.cpus); + qdev_prop_set_uint32(s->gic, "num-irq", AX650X_NUM_IRQS); + qdev_prop_set_bit(s->gic, "has-security-extensions", false); + qdev_prop_set_bit(s->gic, "has-virtualization-extensions", true); + + gicbus = SYS_BUS_DEVICE(s->gic); + sysbus_realize_and_unref(gicbus, &error_fatal); + sysbus_mmio_map(gicbus, 0, AX650X_GIC_DIST_BASE); + + /* + * QEMU exposes a 0x2000-byte GICC region for GICv2, while AX650X + * decodes only the architected 0x1000-byte CPU interface window. + */ + memory_region_init_alias(&s->gic_cpu_alias, OBJECT(s), + "ax650x.gic-cpu", + sysbus_mmio_get_region(gicbus, 1), + 0, AX650X_GIC_CPU_SIZE); + memory_region_add_subregion(sysmem, AX650X_GIC_CPU_BASE, + &s->gic_cpu_alias); + sysbus_mmio_map(gicbus, 2, AX650X_GIC_HYP_BASE); + sysbus_mmio_map(gicbus, 3, AX650X_GIC_VCPU_BASE); + + for (unsigned int i = 0; i < machine->smp.cpus; i++) { + DeviceState *cpu = DEVICE(s->cpus[i]); + int ppi_base = AX650X_NUM_SPIS + i * GIC_INTERNAL; + + qdev_connect_gpio_out(cpu, GTIMER_PHYS, + qdev_get_gpio_in(s->gic, ppi_base + + ARCH_TIMER_NS_EL1_IRQ)); + qdev_connect_gpio_out(cpu, GTIMER_VIRT, + qdev_get_gpio_in(s->gic, ppi_base + + ARCH_TIMER_VIRT_IRQ)); + qdev_connect_gpio_out(cpu, GTIMER_HYP, + qdev_get_gpio_in(s->gic, ppi_base + + ARCH_TIMER_NS_EL2_IRQ)); + qdev_connect_gpio_out(cpu, GTIMER_SEC, + qdev_get_gpio_in(s->gic, ppi_base + + ARCH_TIMER_S_EL1_IRQ)); + + sysbus_connect_irq(gicbus, i, + qdev_get_gpio_in(cpu, ARM_CPU_IRQ)); + sysbus_connect_irq(gicbus, i + machine->smp.cpus, + qdev_get_gpio_in(cpu, ARM_CPU_FIQ)); + sysbus_connect_irq(gicbus, i + 2 * machine->smp.cpus, + qdev_get_gpio_in(cpu, ARM_CPU_VIRQ)); + sysbus_connect_irq(gicbus, i + 3 * machine->smp.cpus, + qdev_get_gpio_in(cpu, ARM_CPU_VFIQ)); + sysbus_connect_irq(gicbus, i + 4 * machine->smp.cpus, + qdev_get_gpio_in(s->gic, + ppi_base + AX650X_GIC_MAINT_PPI)); + + qdev_connect_gpio_out_named(cpu, "pmu-interrupt", 0, + qdev_get_gpio_in(s->gic, + AX650X_PMU_IRQ_BASE + i)); + } +} + +static void ax650x_create_emmc(AX650XPyramidState *s) +{ + DeviceState *host = qdev_new(TYPE_AX650X_SDHCI); + SysBusDevice *sbd = SYS_BUS_DEVICE(host); + DriveInfo *dinfo = drive_get(IF_SD, 0, 0); + + sysbus_realize_and_unref(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, AX650X_EMMC_BASE); + sysbus_connect_irq(sbd, 0, + qdev_get_gpio_in(s->gic, AX650X_EMMC_IRQ)); + + if (dinfo) { + DeviceState *card = qdev_new(TYPE_EMMC); + + qdev_prop_set_drive_err(card, "drive", blk_by_legacy_dinfo(dinfo), + &error_fatal); + qdev_realize_and_unref(card, + BUS(ax650x_sdhci_get_bus(AX650X_SDHCI(host))), + &error_fatal); + } +} + +static void ax650x_create_fdt(AX650XPyramidState *s) +{ + MachineState *machine = MACHINE(s); + uint32_t cpu_phandles[AX650X_NUM_CPUS]; + uint32_t gic_phandle; + uint32_t uart_baud_phandle; + uint32_t uart_apb_phandle; + uint32_t emmc_clock_phandle; + const char clock_names[] = "baudclk\0apb_pclk"; + const char emmc_clock_names[] = "aclk\0hclk\0cardclk"; + const char uart_path[] = "/soc/ax_uart@2016000"; + const char emmc_path[] = "/soc/sdhc@28000000"; + + s->fdt = create_device_tree(&s->fdt_size); + if (!s->fdt) { + error_report("ax650x-pyramid: create_device_tree() failed"); + exit(EXIT_FAILURE); + } + + qemu_fdt_setprop_string(s->fdt, "/", "compatible", "axera,ax650x"); + qemu_fdt_setprop_string(s->fdt, "/", "model", + "M5Stack AI Pyramid (AX650X)"); + qemu_fdt_setprop_cell(s->fdt, "/", "#address-cells", 2); + qemu_fdt_setprop_cell(s->fdt, "/", "#size-cells", 2); + + qemu_fdt_add_subnode(s->fdt, "/aliases"); + qemu_fdt_add_subnode(s->fdt, "/chosen"); + qemu_fdt_setprop_string(s->fdt, "/aliases", "serial0", uart_path); + qemu_fdt_setprop_string(s->fdt, "/chosen", "stdout-path", uart_path); + + qemu_fdt_add_subnode(s->fdt, "/cpus"); + qemu_fdt_setprop_cell(s->fdt, "/cpus", "#address-cells", 1); + qemu_fdt_setprop_cell(s->fdt, "/cpus", "#size-cells", 0); + for (int i = machine->smp.cpus - 1; i >= 0; i--) { + g_autofree char *node = g_strdup_printf("/cpus/cpu@%x", + i << 8); + + qemu_fdt_add_subnode(s->fdt, node); + qemu_fdt_setprop_string(s->fdt, node, "device_type", "cpu"); + qemu_fdt_setprop_string(s->fdt, node, "compatible", + "arm,cortex-a55"); + qemu_fdt_setprop_cell(s->fdt, node, "reg", ax650x_cpu_mpidr(i)); + qemu_fdt_setprop_string(s->fdt, node, "enable-method", "psci"); + cpu_phandles[i] = qemu_fdt_alloc_phandle(s->fdt); + qemu_fdt_setprop_cell(s->fdt, node, "phandle", cpu_phandles[i]); + } + + qemu_fdt_add_subnode(s->fdt, "/psci"); + qemu_fdt_setprop_string(s->fdt, "/psci", "compatible", "arm,psci-1.0"); + qemu_fdt_setprop_string(s->fdt, "/psci", "method", "smc"); + + qemu_fdt_add_subnode(s->fdt, "/timer"); + qemu_fdt_setprop_string(s->fdt, "/timer", "compatible", + "arm,armv8-timer"); + qemu_fdt_setprop_cells(s->fdt, "/timer", "interrupts", + GIC_FDT_IRQ_TYPE_PPI, + INTID_TO_PPI(ARCH_TIMER_S_EL1_IRQ), + AX650X_GIC_PPI_FLAGS, + GIC_FDT_IRQ_TYPE_PPI, + INTID_TO_PPI(ARCH_TIMER_NS_EL1_IRQ), + AX650X_GIC_PPI_FLAGS, + GIC_FDT_IRQ_TYPE_PPI, + INTID_TO_PPI(ARCH_TIMER_VIRT_IRQ), + AX650X_GIC_PPI_FLAGS, + GIC_FDT_IRQ_TYPE_PPI, + INTID_TO_PPI(ARCH_TIMER_NS_EL2_IRQ), + AX650X_GIC_PPI_FLAGS); + qemu_fdt_setprop_cell(s->fdt, "/timer", "clock-frequency", + AX650X_TIMER_CLOCK_HZ); + + qemu_fdt_add_subnode(s->fdt, "/interrupt-controller@4900000"); + qemu_fdt_setprop_string(s->fdt, "/interrupt-controller@4900000", + "compatible", "arm,gic-400"); + qemu_fdt_setprop_cell(s->fdt, "/interrupt-controller@4900000", + "#interrupt-cells", 3); + qemu_fdt_setprop_cell(s->fdt, "/interrupt-controller@4900000", + "#address-cells", 2); + qemu_fdt_setprop_cell(s->fdt, "/interrupt-controller@4900000", + "#size-cells", 2); + qemu_fdt_setprop(s->fdt, "/interrupt-controller@4900000", + "interrupt-controller", NULL, 0); + qemu_fdt_setprop(s->fdt, "/interrupt-controller@4900000", "ranges", + NULL, 0); + qemu_fdt_setprop_sized_cells(s->fdt, "/interrupt-controller@4900000", + "reg", + 2, AX650X_GIC_DIST_BASE, + 2, AX650X_GIC_DIST_SIZE, + 2, AX650X_GIC_CPU_BASE, + 2, AX650X_GIC_CPU_SIZE, + 2, AX650X_GIC_HYP_BASE, + 2, AX650X_GIC_HYP_SIZE, + 2, AX650X_GIC_VCPU_BASE, + 2, AX650X_GIC_VCPU_SIZE); + qemu_fdt_setprop_cells(s->fdt, "/interrupt-controller@4900000", + "interrupts", GIC_FDT_IRQ_TYPE_PPI, + AX650X_GIC_MAINT_PPI, AX650X_GIC_PPI_FLAGS); + gic_phandle = qemu_fdt_alloc_phandle(s->fdt); + qemu_fdt_setprop_cell(s->fdt, "/interrupt-controller@4900000", + "phandle", gic_phandle); + qemu_fdt_setprop_cell(s->fdt, "/", "interrupt-parent", gic_phandle); + + qemu_fdt_add_subnode(s->fdt, "/pmu"); + qemu_fdt_setprop_string(s->fdt, "/pmu", "compatible", + "arm,cortex-a55-pmu"); + qemu_fdt_setprop_cells(s->fdt, "/pmu", "interrupts", + GIC_FDT_IRQ_TYPE_SPI, 80, + GIC_FDT_IRQ_FLAGS_LEVEL_HI, + GIC_FDT_IRQ_TYPE_SPI, 81, + GIC_FDT_IRQ_FLAGS_LEVEL_HI, + GIC_FDT_IRQ_TYPE_SPI, 82, + GIC_FDT_IRQ_FLAGS_LEVEL_HI, + GIC_FDT_IRQ_TYPE_SPI, 83, + GIC_FDT_IRQ_FLAGS_LEVEL_HI, + GIC_FDT_IRQ_TYPE_SPI, 84, + GIC_FDT_IRQ_FLAGS_LEVEL_HI, + GIC_FDT_IRQ_TYPE_SPI, 85, + GIC_FDT_IRQ_FLAGS_LEVEL_HI, + GIC_FDT_IRQ_TYPE_SPI, 86, + GIC_FDT_IRQ_FLAGS_LEVEL_HI, + GIC_FDT_IRQ_TYPE_SPI, 87, + GIC_FDT_IRQ_FLAGS_LEVEL_HI); + qemu_fdt_setprop_cells(s->fdt, "/pmu", "interrupt-affinity", + cpu_phandles[0], cpu_phandles[1], + cpu_phandles[2], cpu_phandles[3], + cpu_phandles[4], cpu_phandles[5], + cpu_phandles[6], cpu_phandles[7]); + + qemu_fdt_add_subnode(s->fdt, "/uart-baud-clock"); + qemu_fdt_setprop_string(s->fdt, "/uart-baud-clock", "compatible", + "fixed-clock"); + qemu_fdt_setprop_cell(s->fdt, "/uart-baud-clock", "#clock-cells", 0); + qemu_fdt_setprop_cell(s->fdt, "/uart-baud-clock", "clock-frequency", + AX650X_UART_CLOCK_HZ); + uart_baud_phandle = qemu_fdt_alloc_phandle(s->fdt); + qemu_fdt_setprop_cell(s->fdt, "/uart-baud-clock", "phandle", + uart_baud_phandle); + + qemu_fdt_add_subnode(s->fdt, "/uart-apb-clock"); + qemu_fdt_setprop_string(s->fdt, "/uart-apb-clock", "compatible", + "fixed-clock"); + qemu_fdt_setprop_cell(s->fdt, "/uart-apb-clock", "#clock-cells", 0); + qemu_fdt_setprop_cell(s->fdt, "/uart-apb-clock", "clock-frequency", + AX650X_UART_CLOCK_HZ); + uart_apb_phandle = qemu_fdt_alloc_phandle(s->fdt); + qemu_fdt_setprop_cell(s->fdt, "/uart-apb-clock", "phandle", + uart_apb_phandle); + + qemu_fdt_add_subnode(s->fdt, "/emmc-clock"); + qemu_fdt_setprop_string(s->fdt, "/emmc-clock", "compatible", + "fixed-clock"); + qemu_fdt_setprop_cell(s->fdt, "/emmc-clock", "#clock-cells", 0); + qemu_fdt_setprop_cell(s->fdt, "/emmc-clock", "clock-frequency", + AX650X_EMMC_CLOCK_HZ); + emmc_clock_phandle = qemu_fdt_alloc_phandle(s->fdt); + qemu_fdt_setprop_cell(s->fdt, "/emmc-clock", "phandle", + emmc_clock_phandle); + + qemu_fdt_add_subnode(s->fdt, "/soc"); + qemu_fdt_setprop_string(s->fdt, "/soc", "compatible", "simple-bus"); + qemu_fdt_setprop_cell(s->fdt, "/soc", "#address-cells", 2); + qemu_fdt_setprop_cell(s->fdt, "/soc", "#size-cells", 2); + qemu_fdt_setprop(s->fdt, "/soc", "ranges", NULL, 0); + ax650x_dwmac_create_fdt(s->fdt); + + qemu_fdt_add_subnode(s->fdt, uart_path); + qemu_fdt_setprop_string(s->fdt, uart_path, "compatible", + "axera,ax-apb-uart"); + qemu_fdt_setprop_sized_cells(s->fdt, uart_path, "reg", + 2, AX650X_UART0_BASE, + 2, AX650X_UART0_SIZE); + qemu_fdt_setprop_cell(s->fdt, uart_path, "reg-shift", 2); + qemu_fdt_setprop_cell(s->fdt, uart_path, "reg-io-width", 4); + qemu_fdt_setprop_cells(s->fdt, uart_path, "clocks", + uart_baud_phandle, uart_apb_phandle); + qemu_fdt_setprop(s->fdt, uart_path, "clock-names", clock_names, + sizeof(clock_names)); + qemu_fdt_setprop_cell(s->fdt, uart_path, "default_cpr_reg", 0x425f2); + qemu_fdt_setprop_cells(s->fdt, uart_path, "interrupts", + GIC_FDT_IRQ_TYPE_SPI, AX650X_UART0_IRQ, + GIC_FDT_IRQ_FLAGS_LEVEL_HI); + qemu_fdt_setprop_string(s->fdt, uart_path, "status", "okay"); + + qemu_fdt_add_subnode(s->fdt, emmc_path); + qemu_fdt_setprop_string(s->fdt, emmc_path, "compatible", + "axera,sdhc-ax650"); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "#address-cells", 2); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "#size-cells", 2); + qemu_fdt_setprop_sized_cells(s->fdt, emmc_path, "reg", + 2, AX650X_EMMC_BASE, + 2, AX650X_EMMC_SIZE); + qemu_fdt_setprop_cells(s->fdt, emmc_path, "interrupts", + GIC_FDT_IRQ_TYPE_SPI, AX650X_EMMC_IRQ, + GIC_FDT_IRQ_FLAGS_LEVEL_HI); + qemu_fdt_setprop_cells(s->fdt, emmc_path, "clocks", + emmc_clock_phandle, emmc_clock_phandle, + emmc_clock_phandle); + qemu_fdt_setprop(s->fdt, emmc_path, "clock-names", + emmc_clock_names, sizeof(emmc_clock_names)); + qemu_fdt_setprop_cells(s->fdt, emmc_path, "sdhci-caps-mask", + 0x2, 0x03200000); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "bus-width", 8); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "max-frequency", + AX650X_EMMC_CLOCK_HZ); + qemu_fdt_setprop(s->fdt, emmc_path, "cap-mmc-hw-reset", NULL, 0); + qemu_fdt_setprop(s->fdt, emmc_path, "cap-mmc-highspeed", NULL, 0); + qemu_fdt_setprop(s->fdt, emmc_path, "mmc-hs200-1_8v", NULL, 0); + qemu_fdt_setprop(s->fdt, emmc_path, "mmc-hs400-1_8v", NULL, 0); + qemu_fdt_setprop(s->fdt, emmc_path, "mmc-hs400-enhanced-strobe", + NULL, 0); + qemu_fdt_setprop(s->fdt, emmc_path, "no-sdio", NULL, 0); + qemu_fdt_setprop(s->fdt, emmc_path, "no-sd", NULL, 0); + qemu_fdt_setprop(s->fdt, emmc_path, "non-removable", NULL, 0); + qemu_fdt_setprop(s->fdt, emmc_path, "disable-wp", NULL, 0); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-cnfg", 0x00cc0000); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-cmdpad-cnfg", + 0x0449); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-datapad-cnfg", + 0x0449); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-clkpad-cnfg", + 0x0440); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-stbpad-cnfg", + 0x0451); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-rstnpad-cnfg", + 0x0449); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-commdl-cnfg", 0); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-sdclkdl-cnfg", 1); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-sdclkdl-dc", 0x7f); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-smpldl-cnfg", 0xc); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-atdl-cnfg", 0xc); + qemu_fdt_setprop_string(s->fdt, emmc_path, "status", "okay"); +} + +static void *ax650x_get_dtb(const struct arm_boot_info *binfo, int *fdt_size) +{ + AX650XPyramidState *s = container_of(binfo, AX650XPyramidState, + bootinfo); + + *fdt_size = s->fdt_size; + return s->fdt; +} + +static void ax650x_pyramid_init(MachineState *machine) +{ + AX650XPyramidState *s = AX650X_PYRAMID_MACHINE(machine); + MemoryRegion *sysmem = get_system_memory(); + + if (machine->ram_size != AX650X_RAM_SIZE) { + error_report("ax650x-pyramid: RAM size must be exactly 2 GiB"); + exit(EXIT_FAILURE); + } + + memory_region_add_subregion(sysmem, AX650X_RAM_BASE, machine->ram); + ax650x_create_cpus(s); + ax650x_create_gic(s); + ax650x_create_emmc(s); + ax650x_dwmac_create(s->gic); + + serial_mm_init(sysmem, AX650X_UART0_BASE, 2, + qdev_get_gpio_in(s->gic, AX650X_UART0_IRQ), + AX650X_UART_BAUDBASE, serial_hd(0), DEVICE_LITTLE_ENDIAN); + memory_region_init_io(&s->uart0_ext, OBJECT(s), &ax650x_uart_ext_ops, s, + "ax650x.uart0-ext", + AX650X_UART0_SIZE - AX650X_UART_STD_SIZE); + memory_region_add_subregion(sysmem, + AX650X_UART0_BASE + AX650X_UART_STD_SIZE, + &s->uart0_ext); + + ax650x_create_fdt(s); + + s->bootinfo.ram_size = machine->ram_size; + s->bootinfo.board_id = -1; + s->bootinfo.loader_start = AX650X_RAM_BASE; + s->bootinfo.get_dtb = ax650x_get_dtb; + s->bootinfo.psci_conduit = QEMU_PSCI_CONDUIT_SMC; + arm_load_kernel(s->cpus[0], machine, &s->bootinfo); +} + +static void ax650x_pyramid_class_init(ObjectClass *oc, const void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + static const char * const valid_cpu_types[] = { + ARM_CPU_TYPE_NAME("cortex-a55"), + NULL, + }; + + mc->desc = "M5Stack AI Pyramid (Axera AX650X)"; + mc->init = ax650x_pyramid_init; + mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-a55"); + mc->valid_cpu_types = valid_cpu_types; + mc->default_cpus = AX650X_NUM_CPUS; + mc->min_cpus = AX650X_NUM_CPUS; + mc->max_cpus = AX650X_NUM_CPUS; + mc->default_ram_size = AX650X_RAM_SIZE; + mc->default_ram_id = "ax650x.ram"; + mc->no_cdrom = true; + mc->no_floppy = true; +} + +static const TypeInfo ax650x_pyramid_info = { + .name = TYPE_AX650X_PYRAMID_MACHINE, + .parent = TYPE_MACHINE, + .instance_size = sizeof(AX650XPyramidState), + .class_init = ax650x_pyramid_class_init, + .interfaces = aarch64_machine_interfaces, +}; + +static void ax650x_pyramid_machine_register_types(void) +{ + type_register_static(&ax650x_pyramid_info); +} + +type_init(ax650x_pyramid_machine_register_types) diff --git a/hw/arm/meson.build b/hw/arm/meson.build index 4233a800be2b0..416e664d01934 100644 --- a/hw/arm/meson.build +++ b/hw/arm/meson.build @@ -1,5 +1,7 @@ arm_common_ss = ss.source_set() arm_common_ss.add(when: 'CONFIG_ARM_VIRT', if_true: files('virt.c')) +arm_common_ss.add(when: 'CONFIG_AX650X_PYRAMID', + if_true: files('ax650x.c', 'ax650x-dwmac.c')) arm_common_ss.add(when: 'CONFIG_ACPI', if_true: files('virt-acpi-build.c')) arm_common_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic_boards.c')) arm_common_ss.add(when: 'CONFIG_EMCRAFT_SF2', if_true: files('msf2-som.c')) @@ -13,10 +15,13 @@ 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_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')) arm_common_ss.add(when: 'CONFIG_ZYNQ', if_true: files('xilinx_zynq.c')) arm_common_ss.add(when: 'CONFIG_SABRELITE', if_true: files('sabrelite.c')) +arm_common_ss.add(when: 'CONFIG_S32K5', if_true: files('s32k5.c')) arm_common_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m.c')) arm_common_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210.c')) diff --git a/hw/arm/phytium-pi.c b/hw/arm/phytium-pi.c new file mode 100644 index 0000000000000..3d4610dae1a68 --- /dev/null +++ b/hw/arm/phytium-pi.c @@ -0,0 +1,1169 @@ +/* + * Local-only Phytium Pi board model + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/bswap.h" +#include "qemu/cutils.h" +#include "qemu/error-report.h" +#include "qemu/units.h" +#include "system/address-spaces.h" +#include "system/block-backend.h" +#include "system/block-backend-io.h" +#include "system/blockdev.h" +#include "system/device_tree.h" +#include "system/kvm.h" +#include "system/memory.h" +#include "system/numa.h" +#include "system/qtest.h" +#include "system/reset.h" +#include "system/system.h" +#include "exec/hwaddr.h" +#include "hw/arm/phytium-pi.h" +#include "hw/arm/bsa.h" +#include "hw/arm/boot.h" +#include "hw/arm/fdt.h" +#include "hw/arm/linux-boot-if.h" +#include "hw/arm/machines-qom.h" +#include "hw/char/pl011.h" +#include "hw/core/boards.h" +#include "hw/core/cpu.h" +#include "hw/core/sysbus.h" +#include "hw/intc/arm_gicv3_common.h" +#include "hw/misc/phytium-ddr-ctrl.h" +#include "hw/misc/phytium-scp-mailbox.h" +#include "hw/misc/unimp.h" +#include "hw/net/phytium-xmac.h" +#include "hw/sd/phytium-mci.h" +#include "hw/sd/sd.h" +#include "qobject/qlist.h" +#include "target/arm/cpu.h" +#include "target/arm/cpu-qom.h" +#include "target/arm/cpregs.h" +#include "target/arm/gtimer.h" +#include "target/arm/internals.h" +#include "target/arm/kvm-consts.h" + +#define PHYTIUMPI_FIRMWARE_BOOT_CPU_INDEX 2 +#define PHYTIUMPI_NUM_SPI_IRQS 256 +#define PHYTIUMPI_GTIMER_HZ 50000000 +#define PHYTIUMPI_DEFAULT_RAM_SIZE (4 * GiB) +#define PHYTIUMPI_RAM_LOW_BASE 0x80000000ULL +#define PHYTIUMPI_RAM_LOW_SIZE (2 * GiB) +#define PHYTIUMPI_RAM_HIGH_BASE 0x2000000000ULL +#define PHYTIUMPI_RAM_HIGH_SIZE (2 * GiB) +#define PHYTIUMPI_RAM_MAX_SIZE \ + (PHYTIUMPI_RAM_LOW_SIZE + PHYTIUMPI_RAM_HIGH_SIZE) +#define PHYTIUMPI_BOOTROM_SIZE (4 * MiB) +#define PHYTIUMPI_BOOTROM_BASE 0 +#define PHYTIUMPI_FIRMWARE_ENTRY_OFFSET 0x40000 +#define PHYTIUMPI_FW_RAM_BASE 0x38000000 +#define PHYTIUMPI_FW_RAM_SIZE (16 * MiB) +#define PHYTIUMPI_FW_HIGH_RAM_BASE 0x39000000 +#define PHYTIUMPI_FW_HIGH_RAM_SIZE (16 * MiB) +#define PHYTIUMPI_FW_SYNC_OFFSET 0xf4000 +#define PHYTIUMPI_FW_SYNC_MAGIC 0x54460000 +#define PHYTIUMPI_FW_CTRL_BASE 0x30c00000 +#define PHYTIUMPI_FW_CTRL_SIZE (1 * MiB) +#define PHYTIUMPI_FW_CTRL_RESET_PTR_OFFSET 0x1000 +#define PHYTIUMPI_FW_CTRL_RESET_MAGIC_OFFSET 0x2000 +#define PHYTIUMPI_FW_CTRL_RESET_MAGIC 0xffaabbcc +#define PHYTIUMPI_FW_CTRL_UART_OFFSET 0x12e8 +#define PHYTIUMPI_FW_CTRL_PTR_OFFSET 0x13f80 +#define PHYTIUMPI_FW_CTRL_PTR_VALUE 0 +#define PHYTIUMPI_SYS_CTRL_BASE 0x30000000 +#define PHYTIUMPI_SYS_CTRL_SIZE 0x1000 +#define PHYTIUMPI_SCP_CTRL_BASE 0x32a00000 +#define PHYTIUMPI_SCP_CTRL_SIZE 0x1000 +#define PHYTIUMPI_SCP_SRAM_BASE 0x32a10000 +#define PHYTIUMPI_SCP_SRAM_SIZE 0x2000 +#define PHYTIUMPI_RNG_BASE 0x32a36000 +#define PHYTIUMPI_RNG_SIZE 0x1000 +#define PHYTIUMPI_SCP_MAILBOX_BASE (PHYTIUMPI_SCP_SRAM_BASE + 0x400) +#define PHYTIUMPI_SCP_READY_OFFSET 0x1808 +#define PHYTIUMPI_SCP_READY_MAGIC 0x00abcdef +#define PHYTIUMPI_FW_MISC_BASE 0x32b30000 +#define PHYTIUMPI_FW_MISC_SIZE 0x10000 +#define PHYTIUMPI_DDR_CTRL_BASE 0x32b33000 +#define PHYTIUMPI_UBOOT_DDR_RESULT_LOAD_OFFSET 0x183fe8 +#define PHYTIUMPI_UBOOT_DDR_RESULT_LOAD 0xf94013a1 +#define PHYTIUMPI_UBOOT_DDR_RESULT_CHECK_OFFSET 0x183fec +#define PHYTIUMPI_UBOOT_DDR_RESULT_CHECK 0xb4000401 +#define PHYTIUMPI_UBOOT_DDR_RESULT_SKIP 0x14000020 +#define PHYTIUMPI_FW_CFG_BASE 0x33000000 +#define PHYTIUMPI_FW_CFG_SIZE 0x10000 +#define PHYTIUMPI_XMAC_BASE 0x3200c000 +#define PHYTIUMPI_XMAC_SIZE 0x2000 +#define PHYTIUMPI_XMAC_IRQ 87 +#define PHYTIUMPI_PCIE_ECAM_BASE 0x40000000 +#define PHYTIUMPI_PCIE_ECAM_SIZE 0x10000000 +#define PHYTIUMPI_SMCCC_ARCH_FEATURES 0x80000001 +#define PHYTIUMPI_SMCCC_ARCH_SOC_ID 0x80000002 +#define PHYTIUMPI_SMCCC_ARCH_WORKAROUND_1 0x80008000 +#define PHYTIUMPI_SMCCC_ARCH_WORKAROUND_2 0x80007fff +#define PHYTIUMPI_SMCCC_ARCH_WORKAROUND_3 0x80003fff +#define PHYTIUMPI_SMCCC_RET_SUCCESS 0 +#define PHYTIUMPI_SMCCC_RET_NOT_SUPPORTED (-1) +#define FDT_GIC_SPI GIC_FDT_IRQ_TYPE_SPI +#define FDT_GIC_PPI GIC_FDT_IRQ_TYPE_PPI +#define FDT_IRQ_TYPE_LEVEL_HIGH 4 +#define FDT_IRQ_TYPE_LEVEL_LOW 8 + +enum { + PHYTIUMPI_MEM, + PHYTIUMPI_MMC0, + PHYTIUMPI_MMC1, + PHYTIUMPI_UART_FIRMWARE, + PHYTIUMPI_UART_CONSOLE, + PHYTIUMPI_GIC_DIST, + PHYTIUMPI_GIC_ITS, + PHYTIUMPI_GIC_CPU, + PHYTIUMPI_GIC_HYP, + PHYTIUMPI_GIC_VCPU, + PHYTIUMPI_GIC_REDIST, +}; + +typedef struct PhytiumPiFirmwareWindow { + const char *name; + hwaddr base; + uint64_t size; +} PhytiumPiFirmwareWindow; + +typedef struct PhytiumPiUart { + hwaddr base; + unsigned int irq; + bool console; +} PhytiumPiUart; + +enum { + PHYTIUMPI_FW_WIN_PAD, + PHYTIUMPI_FW_WIN_LOW0, + PHYTIUMPI_FW_WIN_LOW1, + PHYTIUMPI_FW_WIN_LOW2, + PHYTIUMPI_FW_WIN_LOW3, + PHYTIUMPI_FW_WIN_LOW4, + PHYTIUMPI_FW_WIN_USB2_CLUSTER, + PHYTIUMPI_FW_WIN_MISC0, + PHYTIUMPI_FW_WIN_PHY_CFG, + PHYTIUMPI_FW_WIN_MISC1, + PHYTIUMPI_FW_WIN_MISC2, + PHYTIUMPI_FW_WIN_PHY_CFG1, + PHYTIUMPI_FW_WIN_MISC3, + PHYTIUMPI_FW_WIN_MISC4, + PHYTIUMPI_FW_WIN_MISC5, + PHYTIUMPI_FW_WIN_USB2_LOW, + PHYTIUMPI_FW_WIN_USB2_HIGH, + PHYTIUMPI_FW_WIN_MISC8, + PHYTIUMPI_FW_WIN_MISC9, + PHYTIUMPI_FW_WIN_MISC10, + PHYTIUMPI_FW_WIN_MISC11, + PHYTIUMPI_FW_WIN_MISC12, + PHYTIUMPI_FW_WIN_MISC13, + PHYTIUMPI_FW_WIN_MISC14, + PHYTIUMPI_FW_WIN_MISC15, + PHYTIUMPI_FW_WIN_COUNT, +}; + +static const PhytiumPiFirmwareWindow phytiumpi_fw_windows[] = { + [PHYTIUMPI_FW_WIN_PAD] = { + "phytium-pi.fw-pad", 0x28100000, 0x1000, + }, + [PHYTIUMPI_FW_WIN_LOW0] = { + "phytium-pi.fw-low0", 0x28002000, 0xb000, + }, + [PHYTIUMPI_FW_WIN_LOW1] = { + "phytium-pi.fw-low1", 0x28050000, 0x40000, + }, + [PHYTIUMPI_FW_WIN_LOW2] = { + "phytium-pi.fw-low2", 0x28010000, 0x10000, + }, + [PHYTIUMPI_FW_WIN_LOW3] = { + "phytium-pi.fw-low3", 0x28020000, 0xa000, + }, + [PHYTIUMPI_FW_WIN_LOW4] = { + "phytium-pi.fw-low4", 0x2802b000, 0x25000, + }, + [PHYTIUMPI_FW_WIN_USB2_CLUSTER] = { + "phytium-pi.fw-usb2-cluster", 0x31800000, 0x200000, + }, + [PHYTIUMPI_FW_WIN_MISC0] = { + "phytium-pi.fw-misc0", 0x31a40000, 0x10000, + }, + [PHYTIUMPI_FW_WIN_PHY_CFG] = { + "phytium-pi.fw-phy-cfg", 0x31b00000, 0x100000, + }, + [PHYTIUMPI_FW_WIN_MISC1] = { + "phytium-pi.fw-misc1", 0x31d00000, 0x1000, + }, + [PHYTIUMPI_FW_WIN_MISC2] = { + "phytium-pi.fw-phy-cfg0", 0x32000000, 0x100000, + }, + [PHYTIUMPI_FW_WIN_PHY_CFG1] = { + "phytium-pi.fw-phy-cfg1", 0x32100000, 0x400000, + }, + [PHYTIUMPI_FW_WIN_MISC3] = { + "phytium-pi.fw-misc3", 0x32500000, 0x1000, + }, + [PHYTIUMPI_FW_WIN_MISC4] = { + "phytium-pi.fw-misc4", 0x32f00000, 0x1000, + }, + [PHYTIUMPI_FW_WIN_MISC5] = { + "phytium-pi.fw-misc5", 0x31500000, 0x1000, + }, + [PHYTIUMPI_FW_WIN_USB2_LOW] = { + "phytium-pi.fw-usb2-low", 0x32800000, 0x80000, + }, + [PHYTIUMPI_FW_WIN_USB2_HIGH] = { + "phytium-pi.fw-usb2-high", 0x32880000, 0x80000, + }, + [PHYTIUMPI_FW_WIN_MISC8] = { + "phytium-pi.fw-misc8", 0x32900000, 0x1000, + }, + [PHYTIUMPI_FW_WIN_MISC9] = { + "phytium-pi.fw-misc9", 0x32b00000, 0x1000, + }, + [PHYTIUMPI_FW_WIN_MISC10] = { + "phytium-pi.fw-misc10", 0x32540000, 0x10000, + }, + [PHYTIUMPI_FW_WIN_MISC11] = { + "phytium-pi.fw-misc11", 0x32e40000, 0x10000, + }, + [PHYTIUMPI_FW_WIN_MISC12] = { + "phytium-pi.fw-misc12", 0x31100000, 0x10000, + }, + [PHYTIUMPI_FW_WIN_MISC13] = { + "phytium-pi.fw-misc13", 0x31000000, 0x40000, + }, + [PHYTIUMPI_FW_WIN_MISC14] = { + "phytium-pi.fw-misc14", 0x31c40000, 0x10000, + }, + [PHYTIUMPI_FW_WIN_MISC15] = { + "phytium-pi.fw-misc15", 0x31a00000, 0x40000, + }, +}; + +struct PhytiumPiMachineState { + MachineState parent; + ARMCPU *cpu[PHYTIUMPI_MAX_CPUS]; + DeviceState *gic; + MemoryRegion ram_low; + MemoryRegion ram_high; + MemoryRegion bootrom; + MemoryRegion firmware_ram; + MemoryRegion firmware_high_ram; + MemoryRegion firmware_ctrl; + MemoryRegion sys_ctrl; + MemoryRegion scp_ctrl; + MemoryRegion scp_sram; + MemoryRegion firmware_misc; + MemoryRegion firmware_cfg; + MemoryRegion firmware_windows[PHYTIUMPI_FW_WIN_COUNT]; + struct arm_boot_info bootinfo; +}; + +static const MemMapEntry phytiumpi_memmap[] = { + [PHYTIUMPI_MEM] = { PHYTIUMPI_RAM_LOW_BASE, + PHYTIUMPI_RAM_LOW_SIZE }, + [PHYTIUMPI_MMC0] = { 0x28000000, 0x1000 }, + [PHYTIUMPI_MMC1] = { 0x28001000, 0x1000 }, + [PHYTIUMPI_UART_FIRMWARE] = { 0x2800d000, 0x1000 }, + [PHYTIUMPI_UART_CONSOLE] = { 0x2802a000, 0x1000 }, + [PHYTIUMPI_GIC_DIST] = { 0x30800000, 0x20000 }, + [PHYTIUMPI_GIC_ITS] = { 0x30820000, 0x20000 }, + [PHYTIUMPI_GIC_CPU] = { 0x30840000, 0x10000 }, + [PHYTIUMPI_GIC_HYP] = { 0x30850000, 0x10000 }, + [PHYTIUMPI_GIC_VCPU] = { 0x30860000, 0x10000 }, + [PHYTIUMPI_GIC_REDIST] = { 0x30880000, 0x80000 }, +}; + +static const uint64_t phytiumpi_mpidr[PHYTIUMPI_MAX_CPUS] = { + 0x000, 0x100, 0x200, 0x201, +}; + +static const char * const phytiumpi_cpu_compat[PHYTIUMPI_MAX_CPUS] = { + "phytium,ftc664", + "phytium,ftc664", + "phytium,ftc310", + "phytium,ftc310", +}; + +static const PhytiumPiUart phytiumpi_uarts[] = { + { 0x2800c000, 83, false }, + { 0x2800d000, 84, true }, + { 0x2800e000, 85, false }, + { 0x2800f000, 86, false }, + { 0x28014000, 92, false }, + { 0x2802a000, 103, false }, + { 0x28032000, 107, false }, +}; + +static const ARMCPRegInfo phytiumpi_impdef_cp_reginfo[] = { + { .name = "PHYTIUMPI_IMPDEF_C15_C1_0", + .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 1, .crn = 15, .crm = 1, .opc2 = 0, + .access = PL3_RW, + .type = ARM_CP_NOP | ARM_CP_NO_RAW }, + { .name = "PHYTIUMPI_IMPDEF_C11_C8_6", + .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 1, .crn = 11, .crm = 8, .opc2 = 6, + .access = PL3_RW, + .type = ARM_CP_NOP | ARM_CP_NO_RAW }, +}; + +static void phytiumpi_create_cpus(PhytiumPiMachineState *s) +{ + MachineState *ms = MACHINE(s); + MachineClass *mc = MACHINE_GET_CLASS(ms); + const CPUArchIdList *possible_cpus = mc->possible_cpu_arch_ids(ms); + MemoryRegion *sysmem = get_system_memory(); + + for (unsigned int n = 0; n < ms->smp.cpus; n++) { + g_autofree char *name = g_strdup_printf("cpu%u", n); + Object *cpuobj = object_new(possible_cpus->cpus[n].type); + CPUState *cs = CPU(cpuobj); + + object_property_add_child(OBJECT(ms), name, cpuobj); + cs->cpu_index = n; + numa_cpu_pre_plug(&possible_cpus->cpus[n], DEVICE(cpuobj), + &error_fatal); + object_property_set_int(cpuobj, "mp-affinity", + possible_cpus->cpus[n].arch_id, + &error_abort); + object_property_set_int(cpuobj, "cntfrq", PHYTIUMPI_GTIMER_HZ, + &error_abort); + object_property_set_link(cpuobj, "memory", OBJECT(sysmem), + &error_abort); + + if (object_property_find(cpuobj, "reset-cbar")) { + object_property_set_int(cpuobj, "reset-cbar", + phytiumpi_memmap[PHYTIUMPI_GIC_DIST].base, + &error_abort); + } + if (object_property_find(cpuobj, "has_el2")) { + object_property_set_bool(cpuobj, "has_el2", !kvm_enabled(), + &error_abort); + } + if (object_property_find(cpuobj, "has_el3")) { + object_property_set_bool(cpuobj, "has_el3", !kvm_enabled(), + &error_abort); + } + + qdev_realize(DEVICE(cpuobj), NULL, &error_fatal); + define_arm_cp_regs(ARM_CPU(cpuobj), phytiumpi_impdef_cp_reginfo); + s->cpu[n] = ARM_CPU(cpuobj); + } +} + +static void phytiumpi_enable_psci_conduit(PhytiumPiMachineState *s) +{ + MachineState *ms = MACHINE(s); + + for (unsigned int n = 0; n < ms->smp.cpus; n++) { + object_property_set_int(OBJECT(s->cpu[n]), "psci-conduit", + QEMU_PSCI_CONDUIT_SMC, &error_abort); + if (n > 0) { + object_property_set_bool(OBJECT(s->cpu[n]), "start-powered-off", + true, &error_abort); + } + } +} + +static void +phytiumpi_prepare_nonsecure_linux_interrupts(PhytiumPiMachineState *s) +{ + ARMLinuxBootIf *albif = ARM_LINUX_BOOT_IF(s->gic); + ARMLinuxBootIfClass *albifc = ARM_LINUX_BOOT_IF_GET_CLASS(albif); + + /* + * The firmware path bypasses arm_load_kernel(), so run the same GIC + * Linux-init hook used by direct kernel boot. This models secure + * firmware handing interrupt ownership to the NonSecure kernel before + * U-Boot eventually jumps to Linux. + */ + if (albifc->arm_linux_init) { + albifc->arm_linux_init(albif, false); + } + device_cold_reset(s->gic); +} + +static void phytiumpi_create_gic(PhytiumPiMachineState *s) +{ + MachineState *ms = MACHINE(s); + SysBusDevice *gicbusdev; + QList *redist_region_count; + uint32_t redist_capacity; + + s->gic = qdev_new(gicv3_class_name()); + qdev_prop_set_uint32(s->gic, "revision", 3); + qdev_prop_set_uint32(s->gic, "num-cpu", ms->smp.cpus); + qdev_prop_set_uint32(s->gic, "num-irq", + PHYTIUMPI_NUM_SPI_IRQS + GIC_INTERNAL); + qdev_prop_set_bit(s->gic, "has-security-extensions", true); + + redist_capacity = phytiumpi_memmap[PHYTIUMPI_GIC_REDIST].size / + GICV3_REDIST_SIZE; + redist_region_count = qlist_new(); + qlist_append_int(redist_region_count, MIN(ms->smp.cpus, redist_capacity)); + qdev_prop_set_array(s->gic, "redist-region-count", redist_region_count); + object_property_set_link(OBJECT(s->gic), "sysmem", + OBJECT(get_system_memory()), &error_fatal); + + gicbusdev = SYS_BUS_DEVICE(s->gic); + sysbus_realize_and_unref(gicbusdev, &error_fatal); + sysbus_mmio_map(gicbusdev, 0, phytiumpi_memmap[PHYTIUMPI_GIC_DIST].base); + sysbus_mmio_map(gicbusdev, 1, phytiumpi_memmap[PHYTIUMPI_GIC_REDIST].base); + + for (unsigned int n = 0; n < ms->smp.cpus; n++) { + DeviceState *cpudev = DEVICE(s->cpu[n]); + int intidbase = PHYTIUMPI_NUM_SPI_IRQS + n * GIC_INTERNAL; + static const int timer_irqs[] = { + [GTIMER_PHYS] = ARCH_TIMER_NS_EL1_IRQ, + [GTIMER_VIRT] = ARCH_TIMER_VIRT_IRQ, + [GTIMER_HYP] = ARCH_TIMER_NS_EL2_IRQ, + [GTIMER_SEC] = ARCH_TIMER_S_EL1_IRQ, + [GTIMER_HYPVIRT] = ARCH_TIMER_NS_EL2_VIRT_IRQ, + [GTIMER_S_EL2_PHYS] = ARCH_TIMER_S_EL2_IRQ, + [GTIMER_S_EL2_VIRT] = ARCH_TIMER_S_EL2_VIRT_IRQ, + }; + + for (int irq = 0; irq < ARRAY_SIZE(timer_irqs); irq++) { + qdev_connect_gpio_out(cpudev, irq, + qdev_get_gpio_in(s->gic, + intidbase + + timer_irqs[irq])); + } + + qdev_connect_gpio_out_named(cpudev, "gicv3-maintenance-interrupt", 0, + qdev_get_gpio_in(s->gic, + intidbase + + ARCH_GIC_MAINT_IRQ)); + qdev_connect_gpio_out_named(cpudev, "pmu-interrupt", 0, + qdev_get_gpio_in(s->gic, + intidbase + + VIRTUAL_PMU_IRQ)); + + sysbus_connect_irq(gicbusdev, n, qdev_get_gpio_in(cpudev, ARM_CPU_IRQ)); + sysbus_connect_irq(gicbusdev, n + ms->smp.cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_FIQ)); + sysbus_connect_irq(gicbusdev, n + 2 * ms->smp.cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_VIRQ)); + sysbus_connect_irq(gicbusdev, n + 3 * ms->smp.cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_VFIQ)); + } +} + +static void phytiumpi_create_uart(PhytiumPiMachineState *s) +{ + for (int i = 0; i < ARRAY_SIZE(phytiumpi_uarts); i++) { + const PhytiumPiUart *uart = &phytiumpi_uarts[i]; + DeviceState *dev = qdev_new(TYPE_PL011); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + MemoryRegion *mmio; + + if (uart->console) { + qdev_prop_set_chr(dev, "chardev", serial_hd(0)); + } + sysbus_realize_and_unref(sbd, &error_fatal); + mmio = sysbus_mmio_get_region(sbd, 0); + memory_region_add_subregion(get_system_memory(), uart->base, mmio); + sysbus_connect_irq(sbd, 0, qdev_get_gpio_in(s->gic, uart->irq)); + } +} + +static DeviceState *phytiumpi_create_mmc_controller(PhytiumPiMachineState *s, + const char *name, + unsigned int map, + unsigned int irq) +{ + DeviceState *dev = qdev_new(TYPE_PHYTIUM_MCI); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + object_property_add_child(OBJECT(s), name, OBJECT(dev)); + sysbus_realize_and_unref(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, phytiumpi_memmap[map].base); + sysbus_connect_irq(sbd, 0, qdev_get_gpio_in(s->gic, irq)); + + return dev; +} + +static void phytiumpi_create_mmc(PhytiumPiMachineState *s) +{ + DeviceState *mci0; + DriveInfo *di = drive_get(IF_SD, 0, 0); + BlockBackend *blk = di ? blk_by_legacy_dinfo(di) : NULL; + + mci0 = phytiumpi_create_mmc_controller(s, "mci0", PHYTIUMPI_MMC0, 72); + phytiumpi_create_mmc_controller(s, "mci1", PHYTIUMPI_MMC1, 73); + + if (di) { + BusState *bus = qdev_get_child_bus(mci0, "sd-bus"); + DeviceState *card; + + if (!bus) { + error_report("phytium-pi: MCI controller has no sd-bus"); + exit(EXIT_FAILURE); + } + + card = qdev_new(TYPE_SD_CARD); + qdev_prop_set_drive_err(card, "drive", blk, &error_fatal); + qdev_realize_and_unref(card, bus, &error_fatal); + } +} + +static void phytiumpi_create_xmac(PhytiumPiMachineState *s) +{ + DeviceState *dev = qdev_new(TYPE_PHYTIUM_XMAC); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + MemoryRegion *mmio; + + object_property_add_child(OBJECT(s), "xmac0", OBJECT(dev)); + sysbus_realize_and_unref(sbd, &error_fatal); + mmio = sysbus_mmio_get_region(sbd, 0); + memory_region_add_subregion_overlap(get_system_memory(), + PHYTIUMPI_XMAC_BASE, mmio, 2); + sysbus_connect_irq(sbd, 0, qdev_get_gpio_in(s->gic, + PHYTIUMPI_XMAC_IRQ)); +} + +static void phytiumpi_create_overlap_mmio(Object *owner, const char *name, + const char *type, hwaddr base, + unsigned int priority) +{ + DeviceState *dev = qdev_new(type); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + MemoryRegion *mmio; + + object_property_add_child(owner, name, OBJECT(dev)); + sysbus_realize_and_unref(sbd, &error_fatal); + mmio = sysbus_mmio_get_region(sbd, 0); + memory_region_add_subregion_overlap(get_system_memory(), base, mmio, + priority); +} + +static void phytiumpi_create_scp_mailbox(PhytiumPiMachineState *s) +{ + phytiumpi_create_overlap_mmio(OBJECT(s), "scp-mailbox", + TYPE_PHYTIUM_SCP_MAILBOX, + PHYTIUMPI_SCP_MAILBOX_BASE, 1); +} + +static void phytiumpi_create_ddr_ctrl(PhytiumPiMachineState *s) +{ + phytiumpi_create_overlap_mmio(OBJECT(s), "ddr-ctrl", + TYPE_PHYTIUM_DDR_CTRL, + PHYTIUMPI_DDR_CTRL_BASE, 1); +} + +static void phytiumpi_create_bootrom(PhytiumPiMachineState *s) +{ + memory_region_init_ram(&s->bootrom, NULL, "phytium-pi.bootrom", + PHYTIUMPI_BOOTROM_SIZE, &error_fatal); + memory_region_add_subregion(get_system_memory(), PHYTIUMPI_BOOTROM_BASE, + &s->bootrom); +} + +static void phytiumpi_map_ram(PhytiumPiMachineState *s, MachineState *machine) +{ + uint64_t low_size = MIN(machine->ram_size, + (uint64_t)PHYTIUMPI_RAM_LOW_SIZE); + uint64_t high_size = machine->ram_size - low_size; + + if (low_size) { + memory_region_init_alias(&s->ram_low, OBJECT(s), + "phytium-pi.ram-low", machine->ram, 0, + low_size); + memory_region_add_subregion(get_system_memory(), + PHYTIUMPI_RAM_LOW_BASE, &s->ram_low); + } + + if (high_size) { + memory_region_init_alias(&s->ram_high, OBJECT(s), + "phytium-pi.ram-high", machine->ram, + PHYTIUMPI_RAM_LOW_SIZE, high_size); + memory_region_add_subregion(get_system_memory(), + PHYTIUMPI_RAM_HIGH_BASE, &s->ram_high); + } +} + +static void phytiumpi_create_firmware_ram(PhytiumPiMachineState *s) +{ + uint8_t *fw_ram; + uint8_t *fw_ctrl; + uint8_t *scp_sram; + + memory_region_init_ram(&s->firmware_ram, NULL, "phytium-pi.firmware-ram", + PHYTIUMPI_FW_RAM_SIZE, &error_fatal); + memory_region_add_subregion(get_system_memory(), PHYTIUMPI_FW_RAM_BASE, + &s->firmware_ram); + + memory_region_init_ram(&s->firmware_high_ram, NULL, + "phytium-pi.firmware-high-ram", + PHYTIUMPI_FW_HIGH_RAM_SIZE, &error_fatal); + memory_region_add_subregion(get_system_memory(), PHYTIUMPI_FW_HIGH_RAM_BASE, + &s->firmware_high_ram); + + memory_region_init_ram(&s->firmware_ctrl, NULL, "phytium-pi.firmware-ctrl", + PHYTIUMPI_FW_CTRL_SIZE, &error_fatal); + memory_region_add_subregion(get_system_memory(), PHYTIUMPI_FW_CTRL_BASE, + &s->firmware_ctrl); + + memory_region_init_ram(&s->sys_ctrl, NULL, "phytium-pi.sys-ctrl", + PHYTIUMPI_SYS_CTRL_SIZE, &error_fatal); + memory_region_add_subregion(get_system_memory(), PHYTIUMPI_SYS_CTRL_BASE, + &s->sys_ctrl); + + memory_region_init_ram(&s->scp_ctrl, NULL, "phytium-pi.scp-ctrl", + PHYTIUMPI_SCP_CTRL_SIZE, &error_fatal); + memory_region_add_subregion(get_system_memory(), PHYTIUMPI_SCP_CTRL_BASE, + &s->scp_ctrl); + + memory_region_init_ram(&s->scp_sram, NULL, "phytium-pi.scp-sram", + PHYTIUMPI_SCP_SRAM_SIZE, &error_fatal); + memory_region_add_subregion(get_system_memory(), PHYTIUMPI_SCP_SRAM_BASE, + &s->scp_sram); + + phytiumpi_create_scp_mailbox(s); + + memory_region_init_ram(&s->firmware_misc, NULL, "phytium-pi.firmware-misc", + PHYTIUMPI_FW_MISC_SIZE, &error_fatal); + memory_region_add_subregion(get_system_memory(), PHYTIUMPI_FW_MISC_BASE, + &s->firmware_misc); + phytiumpi_create_ddr_ctrl(s); + + memory_region_init_ram(&s->firmware_cfg, NULL, "phytium-pi.firmware-cfg", + PHYTIUMPI_FW_CFG_SIZE, &error_fatal); + memory_region_add_subregion(get_system_memory(), PHYTIUMPI_FW_CFG_BASE, + &s->firmware_cfg); + + for (unsigned int n = 0; n < ARRAY_SIZE(phytiumpi_fw_windows); n++) { + memory_region_init_ram(&s->firmware_windows[n], NULL, + phytiumpi_fw_windows[n].name, + phytiumpi_fw_windows[n].size, &error_fatal); + memory_region_add_subregion(get_system_memory(), + phytiumpi_fw_windows[n].base, + &s->firmware_windows[n]); + } + + scp_sram = memory_region_get_ram_ptr(&s->scp_sram); + stl_le_p(scp_sram + PHYTIUMPI_SCP_READY_OFFSET, + PHYTIUMPI_SCP_READY_MAGIC); + + fw_ram = memory_region_get_ram_ptr(&s->firmware_ram); + stl_le_p(fw_ram + PHYTIUMPI_FW_SYNC_OFFSET, PHYTIUMPI_FW_SYNC_MAGIC); + + fw_ctrl = memory_region_get_ram_ptr(&s->firmware_ctrl); + stq_le_p(fw_ctrl + PHYTIUMPI_FW_CTRL_RESET_PTR_OFFSET, + PHYTIUMPI_FW_CTRL_BASE + PHYTIUMPI_FW_CTRL_RESET_MAGIC_OFFSET); + stq_le_p(fw_ctrl + PHYTIUMPI_FW_CTRL_RESET_MAGIC_OFFSET, + PHYTIUMPI_FW_CTRL_RESET_MAGIC); + stq_le_p(fw_ctrl + PHYTIUMPI_FW_CTRL_UART_OFFSET, + phytiumpi_memmap[PHYTIUMPI_UART_FIRMWARE].base); + stq_le_p(fw_ctrl + PHYTIUMPI_FW_CTRL_PTR_OFFSET, + PHYTIUMPI_FW_CTRL_PTR_VALUE); +} + +static void phytiumpi_patch_firmware_ddr_training(uint8_t *fw_ram) +{ + uint32_t load = ldl_le_p(fw_ram + PHYTIUMPI_UBOOT_DDR_RESULT_LOAD_OFFSET); + uint32_t check = ldl_le_p(fw_ram + + PHYTIUMPI_UBOOT_DDR_RESULT_CHECK_OFFSET); + + /* + * The local model only needs the vendor firmware to advance past DDR + * training. When this known U-Boot sequence is present, bypass the + * post-SMC training error path instead of modelling the full DDR PHY. + */ + if (load == PHYTIUMPI_UBOOT_DDR_RESULT_LOAD && + check == PHYTIUMPI_UBOOT_DDR_RESULT_CHECK) { + stl_le_p(fw_ram + PHYTIUMPI_UBOOT_DDR_RESULT_CHECK_OFFSET, + PHYTIUMPI_UBOOT_DDR_RESULT_SKIP); + } else if (check != PHYTIUMPI_UBOOT_DDR_RESULT_SKIP) { + warn_report("phytium-pi: DDR training firmware patch not applied"); + } +} + +static bool phytiumpi_is_psci_smc(uint64_t fn) +{ + uint32_t fid = fn; + + switch (fid) { + case QEMU_SMCCC_VERSION_FUNC_ID: + case QEMU_PSCI_0_2_FN_PSCI_VERSION: + case QEMU_PSCI_0_2_FN_CPU_SUSPEND: + case QEMU_PSCI_0_2_FN_CPU_OFF: + case QEMU_PSCI_0_2_FN_CPU_ON: + case QEMU_PSCI_0_2_FN_AFFINITY_INFO: + case QEMU_PSCI_0_2_FN_MIGRATE: + case QEMU_PSCI_0_2_FN_MIGRATE_INFO_TYPE: + case QEMU_PSCI_0_2_FN_SYSTEM_OFF: + case QEMU_PSCI_0_2_FN_SYSTEM_RESET: + case QEMU_PSCI_1_0_FN_PSCI_FEATURES: + case QEMU_PSCI_0_2_FN64_CPU_SUSPEND: + case QEMU_PSCI_0_2_FN64_CPU_OFF: + case QEMU_PSCI_0_2_FN64_CPU_ON: + case QEMU_PSCI_0_2_FN64_AFFINITY_INFO: + case QEMU_PSCI_0_2_FN64_MIGRATE: + case QEMU_PSCI_0_1_FN_CPU_SUSPEND: + case QEMU_PSCI_0_1_FN_CPU_OFF: + case QEMU_PSCI_0_1_FN_CPU_ON: + case QEMU_PSCI_0_1_FN_MIGRATE: + return true; + default: + return false; + } +} + +static void phytiumpi_smc_set_ret(ARMCPU *cpu, int32_t ret) +{ + CPUARMState *env = &cpu->env; + + if (is_a64(env)) { + env->xregs[0] = (uint64_t)(int64_t)ret; + } else { + env->regs[0] = ret; + } +} + +static bool phytiumpi_handle_smccc_arch_call(ARMCPU *cpu, uint32_t fid) +{ + CPUARMState *env = &cpu->env; + uint64_t arg1 = is_a64(env) ? env->xregs[1] : env->regs[1]; + + switch (fid) { + case PHYTIUMPI_SMCCC_ARCH_FEATURES: + switch ((uint32_t)arg1) { + case PHYTIUMPI_SMCCC_ARCH_WORKAROUND_1: + phytiumpi_smc_set_ret(cpu, PHYTIUMPI_SMCCC_RET_SUCCESS); + break; + case PHYTIUMPI_SMCCC_ARCH_WORKAROUND_2: + case PHYTIUMPI_SMCCC_ARCH_WORKAROUND_3: + default: + phytiumpi_smc_set_ret(cpu, PHYTIUMPI_SMCCC_RET_NOT_SUPPORTED); + break; + } + return true; + case PHYTIUMPI_SMCCC_ARCH_SOC_ID: + phytiumpi_smc_set_ret(cpu, PHYTIUMPI_SMCCC_RET_NOT_SUPPORTED); + return true; + case PHYTIUMPI_SMCCC_ARCH_WORKAROUND_1: + phytiumpi_smc_set_ret(cpu, PHYTIUMPI_SMCCC_RET_SUCCESS); + return true; + case PHYTIUMPI_SMCCC_ARCH_WORKAROUND_2: + case PHYTIUMPI_SMCCC_ARCH_WORKAROUND_3: + phytiumpi_smc_set_ret(cpu, PHYTIUMPI_SMCCC_RET_NOT_SUPPORTED); + return true; + default: + return false; + } +} + +static bool phytiumpi_smc_handler(ARMCPU *cpu) +{ + CPUARMState *env = &cpu->env; + uint64_t fn = is_a64(env) ? env->xregs[0] : env->regs[0]; + uint32_t fid = fn; + + if (phytiumpi_handle_smccc_arch_call(cpu, fid)) { + return true; + } + if (!phytiumpi_is_psci_smc(fid)) { + return false; + } + + arm_handle_psci_call(cpu); + return true; +} + +static bool phytiumpi_bootrom_prepare(PhytiumPiMachineState *s, Error **errp) +{ + DriveInfo *di = drive_get(IF_SD, 0, 0); + BlockBackend *blk = di ? blk_by_legacy_dinfo(di) : NULL; + uint8_t *rom = memory_region_get_ram_ptr(&s->bootrom); + uint8_t *fw_ram = memory_region_get_ram_ptr(&s->firmware_ram); + int ret; + + if (!blk) { + error_setg(errp, "phytium-pi firmware boot requires " + "-drive if=sd,index=0,file=,format=raw"); + return false; + } + + ret = blk_pread(blk, 0, PHYTIUMPI_BOOTROM_SIZE, rom, 0); + if (ret < 0) { + error_setg_errno(errp, -ret, "failed to read Phytium Pi SD boot area"); + return false; + } + + memcpy(fw_ram, rom, PHYTIUMPI_BOOTROM_SIZE); + stl_le_p(fw_ram + PHYTIUMPI_FW_SYNC_OFFSET, PHYTIUMPI_FW_SYNC_MAGIC); + phytiumpi_patch_firmware_ddr_training(fw_ram); + + return true; +} + +typedef struct PhytiumPiFirmwareReset { + PhytiumPiMachineState *machine; + ARMCPU *cpu; + unsigned int index; +} PhytiumPiFirmwareReset; + +static void phytiumpi_firmware_cpu_reset(void *opaque) +{ + PhytiumPiFirmwareReset *rst = opaque; + ARMCPU *cpu = rst->cpu; + CPUState *cs = CPU(cpu); + CPUARMState *env = &cpu->env; + + cpu_reset(cs); + + if (rst->index != PHYTIUMPI_FIRMWARE_BOOT_CPU_INDEX) { + arm_set_cpu_power_state(cpu, PSCI_OFF); + cs->halted = true; + cs->exception_index = EXCP_HLT; + return; + } + + arm_emulate_firmware_reset(cs, 3); + cpu_set_pc(cs, PHYTIUMPI_FW_RAM_BASE + PHYTIUMPI_FIRMWARE_ENTRY_OFFSET); + env->xregs[31] = PHYTIUMPI_FW_RAM_BASE + PHYTIUMPI_FW_RAM_SIZE - + 0x100 - rst->index * KiB; + env->sp_el[3] = env->xregs[31]; + arm_rebuild_hflags(env); +} + +static void phytiumpi_register_firmware_reset(PhytiumPiMachineState *s) +{ + MachineState *ms = MACHINE(s); + + for (unsigned int n = 0; n < ms->smp.cpus; n++) { + PhytiumPiFirmwareReset *rst = g_new0(PhytiumPiFirmwareReset, 1); + + rst->machine = s; + rst->cpu = s->cpu[n]; + rst->index = n; + qemu_register_reset(phytiumpi_firmware_cpu_reset, rst); + } +} + +static void phytiumpi_create_unimplemented_regions(void) +{ + create_unimplemented_device("phytium-pi.gic-its", + phytiumpi_memmap[PHYTIUMPI_GIC_ITS].base, + phytiumpi_memmap[PHYTIUMPI_GIC_ITS].size); + create_unimplemented_device("phytium-pi.gic-cpu", + phytiumpi_memmap[PHYTIUMPI_GIC_CPU].base, + phytiumpi_memmap[PHYTIUMPI_GIC_CPU].size); + create_unimplemented_device("phytium-pi.gic-hyp", + phytiumpi_memmap[PHYTIUMPI_GIC_HYP].base, + phytiumpi_memmap[PHYTIUMPI_GIC_HYP].size); + create_unimplemented_device("phytium-pi.gic-vcpu", + phytiumpi_memmap[PHYTIUMPI_GIC_VCPU].base, + phytiumpi_memmap[PHYTIUMPI_GIC_VCPU].size); + create_unimplemented_device("phytium-pi.pcie-ecam", + PHYTIUMPI_PCIE_ECAM_BASE, + PHYTIUMPI_PCIE_ECAM_SIZE); + create_unimplemented_device("phytium-pi.rng", + PHYTIUMPI_RNG_BASE, PHYTIUMPI_RNG_SIZE); +} + +static void phytiumpi_fdt_add_cpu_nodes(PhytiumPiMachineState *s, void *fdt) +{ + MachineState *ms = MACHINE(s); + + qemu_fdt_add_subnode(fdt, "/cpus"); + qemu_fdt_setprop_cell(fdt, "/cpus", "#address-cells", 2); + qemu_fdt_setprop_cell(fdt, "/cpus", "#size-cells", 0); + + for (int n = ms->smp.cpus - 1; n >= 0; n--) { + g_autofree char *nodename = g_strdup_printf("/cpus/cpu@%" PRIx64, + phytiumpi_mpidr[n]); + const char * const compat[] = { + phytiumpi_cpu_compat[n], + "arm,armv8", + }; + + qemu_fdt_add_subnode(fdt, nodename); + qemu_fdt_setprop_string(fdt, nodename, "device_type", "cpu"); + qemu_fdt_setprop_string_array(fdt, nodename, "compatible", + (char **)&compat, ARRAY_SIZE(compat)); + qemu_fdt_setprop_u64(fdt, nodename, "reg", phytiumpi_mpidr[n]); + qemu_fdt_setprop_string(fdt, nodename, "enable-method", "psci"); + } +} + +static uint32_t phytiumpi_fdt_add_fixed_clock(void *fdt, const char *name, + uint32_t hz) +{ + g_autofree char *node = g_strdup_printf("/%s", name); + uint32_t phandle = qemu_fdt_alloc_phandle(fdt); + + qemu_fdt_add_subnode(fdt, node); + qemu_fdt_setprop_string(fdt, node, "compatible", "fixed-clock"); + qemu_fdt_setprop_cell(fdt, node, "#clock-cells", 0); + qemu_fdt_setprop_cell(fdt, node, "clock-frequency", hz); + qemu_fdt_setprop_cell(fdt, node, "phandle", phandle); + + return phandle; +} + +static void phytiumpi_fdt_add_gic_node(void *fdt) +{ + const char *gic = "/interrupt-controller@30800000"; + uint32_t phandle = qemu_fdt_alloc_phandle(fdt); + + qemu_fdt_add_subnode(fdt, gic); + qemu_fdt_setprop_string(fdt, gic, "compatible", "arm,gic-v3"); + qemu_fdt_setprop_cell(fdt, gic, "#interrupt-cells", 3); + qemu_fdt_setprop_cell(fdt, gic, "#address-cells", 2); + qemu_fdt_setprop_cell(fdt, gic, "#size-cells", 2); + qemu_fdt_setprop(fdt, gic, "interrupt-controller", NULL, 0); + qemu_fdt_setprop(fdt, gic, "ranges", NULL, 0); + qemu_fdt_setprop_sized_cells(fdt, gic, "reg", + 2, phytiumpi_memmap[PHYTIUMPI_GIC_DIST].base, + 2, phytiumpi_memmap[PHYTIUMPI_GIC_DIST].size, + 2, + phytiumpi_memmap[PHYTIUMPI_GIC_REDIST].base, + 2, + phytiumpi_memmap[PHYTIUMPI_GIC_REDIST].size); + qemu_fdt_setprop_cells(fdt, gic, "interrupts", + FDT_GIC_PPI, 9, FDT_IRQ_TYPE_LEVEL_LOW); + qemu_fdt_setprop_cell(fdt, gic, "phandle", phandle); + qemu_fdt_setprop_cell(fdt, "/", "interrupt-parent", phandle); +} + +static void phytiumpi_fdt_add_timer_node(void *fdt) +{ + qemu_fdt_add_subnode(fdt, "/timer"); + qemu_fdt_setprop_string(fdt, "/timer", "compatible", "arm,armv8-timer"); + qemu_fdt_setprop_cells(fdt, "/timer", "interrupts", + FDT_GIC_PPI, 13, FDT_IRQ_TYPE_LEVEL_HIGH, + FDT_GIC_PPI, 14, FDT_IRQ_TYPE_LEVEL_HIGH, + FDT_GIC_PPI, 11, FDT_IRQ_TYPE_LEVEL_HIGH, + FDT_GIC_PPI, 10, FDT_IRQ_TYPE_LEVEL_HIGH); + qemu_fdt_setprop_cell(fdt, "/timer", "clock-frequency", + PHYTIUMPI_GTIMER_HZ); + qemu_fdt_setprop(fdt, "/timer", "always-on", NULL, 0); +} + +static void phytiumpi_fdt_add_uart_node(void *fdt, uint32_t clk_phandle) +{ + const char *uart = "/soc/serial@2800d000"; + static const char compat[] = "arm,pl011\0arm,primecell"; + static const char clock_names[] = "uartclk\0apb_pclk"; + + qemu_fdt_add_subnode(fdt, "/aliases"); + qemu_fdt_setprop_string(fdt, "/aliases", "serial1", uart); + + qemu_fdt_add_subnode(fdt, "/chosen"); + qemu_fdt_setprop_string(fdt, "/chosen", "stdout-path", "serial1:115200n8"); + + qemu_fdt_add_subnode(fdt, "/soc"); + qemu_fdt_setprop_string(fdt, "/soc", "compatible", "simple-bus"); + qemu_fdt_setprop_cell(fdt, "/soc", "#address-cells", 2); + qemu_fdt_setprop_cell(fdt, "/soc", "#size-cells", 2); + qemu_fdt_setprop(fdt, "/soc", "ranges", NULL, 0); + + qemu_fdt_add_subnode(fdt, uart); + qemu_fdt_setprop(fdt, uart, "compatible", compat, sizeof(compat)); + qemu_fdt_setprop_sized_cells(fdt, uart, "reg", + 2, + phytiumpi_memmap[PHYTIUMPI_UART_FIRMWARE].base, + 2, + phytiumpi_memmap[PHYTIUMPI_UART_FIRMWARE].size); + qemu_fdt_setprop_cells(fdt, uart, "interrupts", + FDT_GIC_SPI, 84, FDT_IRQ_TYPE_LEVEL_HIGH); + qemu_fdt_setprop_cells(fdt, uart, "clocks", clk_phandle, clk_phandle); + qemu_fdt_setprop(fdt, uart, "clock-names", clock_names, + sizeof(clock_names)); + qemu_fdt_setprop_string(fdt, uart, "status", "okay"); +} + +static void phytiumpi_fdt_add_mmc_node(void *fdt, uint32_t clk_phandle) +{ + const char *mmc = "/soc/mmc@28000000"; + static const char clock_names[] = "phytium_mci_clk"; + + qemu_fdt_setprop_string(fdt, "/aliases", "mmc0", mmc); + + qemu_fdt_add_subnode(fdt, mmc); + qemu_fdt_setprop_string(fdt, mmc, "compatible", "phytium,mci"); + qemu_fdt_setprop_sized_cells(fdt, mmc, "reg", + 2, phytiumpi_memmap[PHYTIUMPI_MMC0].base, + 2, phytiumpi_memmap[PHYTIUMPI_MMC0].size); + qemu_fdt_setprop_cells(fdt, mmc, "interrupts", + FDT_GIC_SPI, 72, FDT_IRQ_TYPE_LEVEL_HIGH); + qemu_fdt_setprop_cell(fdt, mmc, "clocks", clk_phandle); + qemu_fdt_setprop(fdt, mmc, "clock-names", clock_names, + sizeof(clock_names)); + qemu_fdt_setprop_cell(fdt, mmc, "bus-width", 4); + qemu_fdt_setprop_cell(fdt, mmc, "max-frequency", 50000000); + qemu_fdt_setprop(fdt, mmc, "cap-sdio-irq", NULL, 0); + qemu_fdt_setprop(fdt, mmc, "cap-sd-highspeed", NULL, 0); + qemu_fdt_setprop(fdt, mmc, "sd-uhs-sdr12", NULL, 0); + qemu_fdt_setprop(fdt, mmc, "sd-uhs-sdr25", NULL, 0); + qemu_fdt_setprop(fdt, mmc, "sd-uhs-sdr50", NULL, 0); + qemu_fdt_setprop(fdt, mmc, "no-mmc", NULL, 0); + qemu_fdt_setprop_string(fdt, mmc, "status", "okay"); +} + +static void *phytiumpi_get_dtb(const struct arm_boot_info *binfo, + int *fdt_size) +{ + PhytiumPiMachineState *s = container_of(binfo, PhytiumPiMachineState, + bootinfo); + void *fdt = create_device_tree(fdt_size); + uint32_t clk_50mhz; + static const char * const root_compat[] = { + "phytium,pe2204", + "phytium,pe220x", + }; + + if (!fdt) { + error_report("create_device_tree() failed"); + exit(EXIT_FAILURE); + } + + qemu_fdt_setprop_string(fdt, "/", "model", "Phytium Pi Board"); + qemu_fdt_setprop_string_array(fdt, "/", "compatible", + (char **)root_compat, + ARRAY_SIZE(root_compat)); + qemu_fdt_setprop_cell(fdt, "/", "#address-cells", 2); + qemu_fdt_setprop_cell(fdt, "/", "#size-cells", 2); + + clk_50mhz = phytiumpi_fdt_add_fixed_clock(fdt, "clk50mhz", + PHYTIUMPI_GTIMER_HZ); + + phytiumpi_fdt_add_cpu_nodes(s, fdt); + phytiumpi_fdt_add_gic_node(fdt); + phytiumpi_fdt_add_timer_node(fdt); + phytiumpi_fdt_add_uart_node(fdt, clk_50mhz); + phytiumpi_fdt_add_mmc_node(fdt, clk_50mhz); + + return fdt; +} + +static void phytiumpi_init(MachineState *machine) +{ + PhytiumPiMachineState *s = PHYTIUMPI_MACHINE(machine); + + if (machine->smp.cpus > PHYTIUMPI_MAX_CPUS || + machine->smp.max_cpus > PHYTIUMPI_MAX_CPUS) { + error_report("phytium-pi: at most %u CPUs are supported", + PHYTIUMPI_MAX_CPUS); + exit(EXIT_FAILURE); + } + + if (machine->ram_size > PHYTIUMPI_RAM_MAX_SIZE) { + g_autofree char *sz = size_to_str(PHYTIUMPI_RAM_MAX_SIZE); + error_report("phytium-pi: RAM size must not exceed %s", sz); + exit(EXIT_FAILURE); + } + + phytiumpi_create_cpus(s); + phytiumpi_create_bootrom(s); + phytiumpi_create_firmware_ram(s); + phytiumpi_map_ram(s, machine); + phytiumpi_create_gic(s); + phytiumpi_create_unimplemented_regions(); + phytiumpi_create_uart(s); + phytiumpi_create_mmc(s); + phytiumpi_create_xmac(s); + + s->bootinfo = (struct arm_boot_info) { + .loader_start = phytiumpi_memmap[PHYTIUMPI_MEM].base, + .board_id = -1, + .ram_size = machine->ram_size, + .psci_conduit = QEMU_PSCI_CONDUIT_SMC, + .get_dtb = phytiumpi_get_dtb, + }; + + if (qtest_enabled()) { + return; + } + + if (machine->kernel_filename) { + phytiumpi_enable_psci_conduit(s); + arm_load_kernel(s->cpu[0], machine, &s->bootinfo); + } else { + Error *local_err = NULL; + + if (machine->smp.cpus <= PHYTIUMPI_FIRMWARE_BOOT_CPU_INDEX) { + error_report("phytium-pi firmware boot requires at least %u CPUs", + PHYTIUMPI_FIRMWARE_BOOT_CPU_INDEX + 1); + exit(EXIT_FAILURE); + } + if (!phytiumpi_bootrom_prepare(s, &local_err)) { + error_report_err(local_err); + exit(EXIT_FAILURE); + } + phytiumpi_prepare_nonsecure_linux_interrupts(s); + arm_register_psci_smc_handler(phytiumpi_smc_handler); + phytiumpi_register_firmware_reset(s); + } +} + +static const CPUArchIdList *phytiumpi_possible_cpu_arch_ids(MachineState *ms) +{ + unsigned int max_cpus = ms->smp.max_cpus; + + if (ms->possible_cpus) { + assert(ms->possible_cpus->len == max_cpus); + return ms->possible_cpus; + } + + ms->possible_cpus = g_malloc0(sizeof(CPUArchIdList) + + sizeof(CPUArchId) * max_cpus); + ms->possible_cpus->len = max_cpus; + + for (unsigned int n = 0; n < max_cpus; n++) { + ms->possible_cpus->cpus[n].type = ms->cpu_type; + ms->possible_cpus->cpus[n].arch_id = phytiumpi_mpidr[n]; + ms->possible_cpus->cpus[n].props.has_core_id = true; + ms->possible_cpus->cpus[n].props.core_id = n; + } + + return ms->possible_cpus; +} + +static CpuInstanceProperties +phytiumpi_cpu_index_to_props(MachineState *ms, unsigned cpu_index) +{ + MachineClass *mc = MACHINE_GET_CLASS(ms); + const CPUArchIdList *possible_cpus = mc->possible_cpu_arch_ids(ms); + + assert(cpu_index < possible_cpus->len); + return possible_cpus->cpus[cpu_index].props; +} + +static void phytiumpi_machine_class_init(ObjectClass *oc, const void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + + mc->desc = "Phytium Pi board (Phytium E2000Q/PE2204)"; + mc->init = phytiumpi_init; + mc->max_cpus = PHYTIUMPI_MAX_CPUS; + mc->default_cpus = PHYTIUMPI_MAX_CPUS; + mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-a72"); + mc->default_ram_size = PHYTIUMPI_DEFAULT_RAM_SIZE; + mc->default_ram_id = "phytium-pi.ram"; + mc->possible_cpu_arch_ids = phytiumpi_possible_cpu_arch_ids; + mc->cpu_index_to_instance_props = phytiumpi_cpu_index_to_props; +} + +static const TypeInfo phytiumpi_machine_typeinfo = { + .name = TYPE_PHYTIUMPI_MACHINE, + .parent = TYPE_MACHINE, + .instance_size = sizeof(PhytiumPiMachineState), + .class_init = phytiumpi_machine_class_init, + .interfaces = aarch64_machine_interfaces, +}; + +static void phytiumpi_machine_init_register_types(void) +{ + type_register_static(&phytiumpi_machine_typeinfo); +} + +type_init(phytiumpi_machine_init_register_types) diff --git a/hw/arm/rk3588.c b/hw/arm/rk3588.c new file mode 100644 index 0000000000000..199738f74b5e2 --- /dev/null +++ b/hw/arm/rk3588.c @@ -0,0 +1,2820 @@ +/* + * Local-only Rockchip RK3588 board machine models + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/cutils.h" +#include "qapi/error.h" +#include "qemu/bswap.h" +#include "qemu/error-report.h" +#include "qemu/timer.h" +#include "qemu/units.h" +#include "system/address-spaces.h" +#include "system/block-backend-io.h" +#include "exec/hwaddr.h" +#include "system/device_tree.h" +#include "system/kvm.h" +#include "system/memory.h" +#include "system/numa.h" +#include "system/qtest.h" +#include "system/reset.h" +#include "system/system.h" +#include "hw/arm/boot.h" +#include "hw/arm/bsa.h" +#include "hw/arm/linux-boot-if.h" +#include "hw/gpio/rockchip_gpio.h" +#include "hw/misc/rockchip_syscon.h" +#include "hw/misc/rk3588_scmi.h" +#include "hw/net/dwmac4.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 "net/net.h" +#include "system/block-backend.h" +#include "hw/arm/machines-qom.h" +#include "hw/char/dw-apb-uart-vendor.h" +#include "hw/char/serial-mm.h" +#include "hw/core/boards.h" +#include "hw/core/cpu.h" +#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" +#include "target/arm/cpu.h" +#include "target/arm/cpu-qom.h" +#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") +OBJECT_DECLARE_SIMPLE_TYPE(RK3588MachineState, RK3588_MACHINE) + +#define RK3588_MAX_CPUS 8 +#define RK3588_NUM_SPI_IRQS 512 +#define RK3588_GTIMER_HZ 24000000 +#define RK3588_UART_BAUDBASE 1500000 +#define RK3588_MAX_RAM_SIZE (rk3588_memmap[RK3588_GIC_DIST].base - \ + rk3588_memmap[RK3588_RAM].base) +#define RK3588_SRAM_SIZE MiB +#define RK3588_IRAM_SIZE 0x00ff0000 +#define RK3588_ATAGS_SIZE (8 * KiB) +#define RK3588_ZVM_LOW_RAM_BASE 0x68000000ULL +#define RK3588_ZVM_LOW_RAM_LIMIT 0xf0000000ULL +#define RK3588_ZVM_HIGH_RAM_BASE 0x100000000ULL +#define RK3588_ZVM_HIGH_RAM_SIZE 0x100000000ULL +#define RK3588_BROM_TRAMPOLINE 0xffff0000ULL +#define RK3588_TPL_LOAD_ADDR 0xff001000ULL +#define RK3588_BROM_SMC_NEXT_STAGE 0xc2003588 +#define RK3588_QEMU_SMC_UBOOT_HANDOFF 0xc2003589 +#define RK3588_QEMU_SMC_ATF_ENTRY 0x358a +#define RK3588_QEMU_SMC_BL31_EXIT 0x358b +#define RK3588_RKNS_MAGIC 0x534e4b52 +#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_PMUSRAM_SKIP_ADDR 0xff101764ULL +#define RK3588_BL31_BASE 0x00060000ULL +#define RK3588_BL31_LIMIT 0x00090000ULL +#define RK3588_BL31_PMUSRAM_COPY_CALL_ADDR 0x0006f2f8ULL +#define RK3588_BL31_PMUSRAM_RUN_CALL_ADDR 0x0006f2fcULL +#define RK3588_BL31_RMR_CBZ_ADDR 0x0006f350ULL +#define RK3588_BL31_MMU_ENABLE_ADDR 0x000796d8ULL +#define RK3588_BL31_MMU_ENABLE_BODY_ADDR 0x000796e4ULL +#define RK3588_BL31_EXIT_LR_ADDR 0x00079210ULL +#define RK3588_BL31_EXIT_ERET_ADDR 0x00079214ULL +#define RK3588_BL31_PMUSRAM_COPY_CALL 0x97fffc4b +#define RK3588_BL31_PMUSRAM_RUN_CALL 0x97ffc391 +#define RK3588_BL31_RMR_CBZ 0x340002f3 +#define RK3588_BL31_RMR_SKIP 0x1400000f +#define RK3588_BL31_MMU_ENABLE_BRANCH 0x14000003 +#define RK3588_BL31_MMU_ENABLE_TLBI 0xd50e871f +#define RK3588_BL31_EXIT_LR 0xf9407bfe +#define RK3588_BL31_EXIT_ERET 0xd69f03e0 +#define RK3588_BL31_EXIT_MOVZ 0xd286b160 +#define RK3588_SPL_ATF_PARAMS_MOV 0xaa1303e2 +#define RK3588_SPL_ATF_ENTRY_MOVZ 0xd286b140 +#define RK3588_AARCH64_SMC 0xd4000003 +#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_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 +#define FDT_IRQ_TYPE_LEVEL_HIGH 4 + +typedef struct RK3588BootImage { + uint32_t size_and_off; + uint32_t address; + uint32_t flag; + uint32_t counter; + uint8_t reserved[8]; + uint8_t hash[64]; +} QEMU_PACKED RK3588BootImage; + +typedef struct RK3588HeaderV2 { + uint32_t magic; + uint8_t reserved[4]; + uint32_t size_and_nimage; + uint32_t boot_flag; + uint8_t reserved1[104]; + RK3588BootImage images[4]; + uint8_t reserved2[1064]; + uint8_t hash[512]; +} QEMU_PACKED RK3588HeaderV2; + +typedef struct RK3588BootROM { + uint8_t *spl; + size_t spl_size; + hwaddr tpl_entry; + bool spl_loaded; +} 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, +}; + +struct RK3588MachineState { + MachineState parent_obj; + + struct arm_boot_info bootinfo; + const RK3588BoardConfig *board; + ARMCPU *cpu[RK3588_MAX_CPUS]; + DeviceState *gic; + DeviceState *its[2]; + DeviceState *sdhci; + DeviceState *sdmmc; /* dw_mmc - SD card controller */ + DeviceState *scmi; /* SCMI clock agent (shmem + SMC responder) */ + DeviceState *pcie; + DeviceState *gmac0; + DeviceState *gmac1; + DeviceState *gpio[5]; + RockchipSysconState *pmu0grf; + RockchipSysconState *pmu1grf; + + MemoryRegion sram; + MemoryRegion iram; + MemoryRegion atags; + MemoryRegion zvm_low_ram; + 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; + bool firmware_atf_entered; + bool zvm_ram; + RK3588BootROM bootrom_state; +}; + +static const uint64_t rk3588_cpu_mpidr[] = { + 0x000, 0x100, 0x200, 0x300, + 0x400, 0x500, 0x600, 0x700, +}; + +static const char * const rk3588_cpu_types[] = { + ARM_CPU_TYPE_NAME("cortex-a55"), + ARM_CPU_TYPE_NAME("cortex-a55"), + ARM_CPU_TYPE_NAME("cortex-a55"), + ARM_CPU_TYPE_NAME("cortex-a55"), + ARM_CPU_TYPE_NAME("cortex-a76"), + ARM_CPU_TYPE_NAME("cortex-a76"), + ARM_CPU_TYPE_NAME("cortex-a76"), + ARM_CPU_TYPE_NAME("cortex-a76"), +}; + +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); + +enum { + RK3588_SRAM, + RK3588_ATAGS, + RK3588_RAM, + RK3588_FIRMWARE_SCRATCH, + RK3588_SCMI_SHMEM, + RK3588_PMU0_GRF, + RK3588_PMU1_GRF, + RK3588_USB_GRF, + RK3588_PMU1_IOC, + RK3588_PMU2_IOC, + RK3588_BUS_IOC, + RK3588_PCIE3X4_APB, + RK3588_PCIE3X4_CFG, + RK3588_PCIE3X4_DBI, + RK3588_GMAC0, + RK3588_GMAC1, + RK3588_SDMMC, + RK3588_SDHCI, + RK3588_GIC_DIST, + RK3588_GIC_REDIST, + RK3588_GIC_ITS0, + RK3588_GIC_ITS1, + RK3588_GPIO0, + RK3588_GPIO1, + RK3588_GPIO2, + RK3588_GPIO3, + RK3588_GPIO4, + RK3588_SYS_GRF, + RK3588_PHP_GRF, + RK3588_CRU_MEM, + RK3588_STIMER, + RK3588_FIREWALL_DDR, + RK3588_FIREWALL_SYSMEM, + RK3588_IRAM, + RK3588_BROM, + RK3588_FIRMWARE_MMIO, + RK3588_UART2, +}; + +static const MemMapEntry rk3588_memmap[] = { + [RK3588_SRAM] = { 0x00000000, RK3588_SRAM_SIZE }, + [RK3588_ATAGS] = { 0x001fe000, RK3588_ATAGS_SIZE }, + [RK3588_RAM] = { 0x00200000, 0 }, + /* + * BL31 probes optional low-address payload metadata at 0x100000 before + * normal DRAM starts. Keep this as zeroed scratch RAM so that probe can + * fail cleanly without colliding with the SCMI shmem slot below. + */ + [RK3588_FIRMWARE_SCRATCH] = { 0x00100000, 0x0000f000 }, + /* + * SCMI shared-memory slot (single Tx/Rx). Lives in low DRAM below + * the kernel image so the loader does not stomp it. Backed by the + * rk3588-scmi SysBusDevice (RAM-backed MMIO + the SCMI responder). + */ + [RK3588_SCMI_SHMEM] = { 0x0010f000, 0x00000100 }, + [RK3588_PMU0_GRF] = { 0xfd588000, 0x00001000 }, + [RK3588_PMU1_GRF] = { 0xfd58a000, 0x00001000 }, + [RK3588_USB_GRF] = { 0xfd5ac000, 0x00001000 }, + [RK3588_PMU1_IOC] = { 0xfd5f0000, 0x00001000 }, + [RK3588_PMU2_IOC] = { 0xfd5f4000, 0x00001000 }, + [RK3588_BUS_IOC] = { 0xfd5f8000, 0x00001000 }, + [RK3588_PCIE3X4_APB] = { 0xfe150000, 0x00010000 }, + [RK3588_PCIE3X4_CFG] = { 0xf0000000, 0x00100000 }, + [RK3588_PCIE3X4_DBI] = { 0xa40000000ULL, 0x00400000 }, + [RK3588_GMAC0] = { 0xfe1b0000, 0x00010000 }, + [RK3588_GMAC1] = { 0xfe1c0000, 0x00010000 }, + [RK3588_SDMMC] = { 0xfe2c0000, 0x00004000 }, + [RK3588_SDHCI] = { 0xfe2e0000, 0x00010000 }, + [RK3588_GIC_DIST] = { 0xfe600000, 0x00010000 }, + [RK3588_GIC_ITS0] = { 0xfe640000, 0x00020000 }, + [RK3588_GIC_ITS1] = { 0xfe660000, 0x00020000 }, + [RK3588_GIC_REDIST] = { 0xfe680000, 0x00100000 }, + [RK3588_GPIO0] = { 0xfd8a0000, 0x00000100 }, + [RK3588_GPIO1] = { 0xfec20000, 0x00000100 }, + [RK3588_GPIO2] = { 0xfec30000, 0x00000100 }, + [RK3588_GPIO3] = { 0xfec40000, 0x00000100 }, + [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. + */ + [RK3588_SYS_GRF] = { 0xfd58c000, 0x00001000 }, + [RK3588_PHP_GRF] = { 0xfd5b0000, 0x00001000 }, + /* + * CRU (clock-and-reset-unit) - the reset provider the dw-rockchip + * PCIe driver mandates. CLK_OF_DECLARE -> clk-rk3588 binds very + * early; the only load-bearing read is offset 0x600 (PLL lock + * status) which MUST return 0xffffffff or init hangs before the + * console. Everything else is RAZ/WI. + */ + [RK3588_CRU_MEM] = { 0xfd7c0000, 0x0005c000 }, + [RK3588_STIMER] = { 0xfd8c8000, ROCKCHIP_STIMER_SIZE }, + [RK3588_FIREWALL_DDR] = { 0xfe030000, 0x00001000 }, + [RK3588_FIREWALL_SYSMEM] = { 0xfe038000, 0x00001000 }, + [RK3588_IRAM] = { 0xff000000, RK3588_IRAM_SIZE }, + [RK3588_BROM] = { RK3588_BROM_TRAMPOLINE, 0x00001000 }, + [RK3588_FIRMWARE_MMIO] = { 0xf7000000, RK3588_FIRMWARE_MMIO_SIZE }, + [RK3588_UART2] = { 0xfeb50000, 0x00000100 }, +}; + +enum { + RK3588_GIC_MAINT_PPI = 9, + RK3588_SDMMC_SPI = 203, + RK3588_SDHCI_SPI = 205, + RK3588_GMAC0_SPI = 227, + RK3588_GMAC1_SPI = 234, + RK3588_PCIE3X4_ERR_SPI = 259, + RK3588_PCIE3X4_LEGACY_SPI = 260, + RK3588_PCIE3X4_MSG_SPI = 261, + RK3588_PCIE3X4_PMC_SPI = 262, + RK3588_PCIE3X4_SYS_SPI = 263, + RK3588_GPIO0_SPI = 277, + 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]; +} + +static void rk3588_fdt_add_cpu_nodes(RK3588MachineState *s, void *fdt) +{ + MachineState *ms = MACHINE(s); + + qemu_fdt_add_subnode(fdt, "/cpus"); + qemu_fdt_setprop_cell(fdt, "/cpus", "#address-cells", 1); + qemu_fdt_setprop_cell(fdt, "/cpus", "#size-cells", 0); + + for (int n = ms->smp.cpus - 1; n >= 0; n--) { + g_autofree char *nodename = g_strdup_printf("/cpus/cpu@%" PRIx64, + rk3588_cpu_mpidr[n]); + ARMCPU *cpu = s->cpu[n]; + + qemu_fdt_add_subnode(fdt, nodename); + qemu_fdt_setprop_string(fdt, nodename, "device_type", "cpu"); + qemu_fdt_setprop_string(fdt, nodename, "compatible", + cpu->dtb_compatible); + qemu_fdt_setprop_cell(fdt, nodename, "reg", rk3588_cpu_mpidr[n]); + qemu_fdt_setprop_string(fdt, nodename, "enable-method", "psci"); + } +} + +static void rk3588_fdt_add_its_node(void *fdt, const char *gic, + unsigned int idx, uint32_t *phandle) +{ + int mem_idx = idx == 0 ? RK3588_GIC_ITS0 : RK3588_GIC_ITS1; + g_autofree char *node = g_strdup_printf("%s/msi-controller@%" PRIx64, + gic, + rk3588_memmap[mem_idx].base); + + *phandle = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_add_subnode(fdt, node); + qemu_fdt_setprop_string(fdt, node, "compatible", "arm,gic-v3-its"); + qemu_fdt_setprop_sized_cells(fdt, node, "reg", + 2, rk3588_memmap[mem_idx].base, + 2, rk3588_memmap[mem_idx].size); + qemu_fdt_setprop(fdt, node, "msi-controller", NULL, 0); + qemu_fdt_setprop_cell(fdt, node, "#msi-cells", 1); + qemu_fdt_setprop(fdt, node, "dma-noncoherent", NULL, 0); + qemu_fdt_setprop_cell(fdt, node, "phandle", *phandle); +} + +static void rk3588_fdt_add_gic_node(void *fdt, uint32_t *its0_phandle, + uint32_t *its1_phandle) +{ + const char *gic = "/interrupt-controller@fe600000"; + uint32_t phandle; + + qemu_fdt_add_subnode(fdt, gic); + qemu_fdt_setprop_string(fdt, gic, "compatible", "arm,gic-v3"); + qemu_fdt_setprop_sized_cells(fdt, gic, "reg", + 2, rk3588_memmap[RK3588_GIC_DIST].base, + 2, rk3588_memmap[RK3588_GIC_DIST].size, + 2, rk3588_memmap[RK3588_GIC_REDIST].base, + 2, rk3588_memmap[RK3588_GIC_REDIST].size); + qemu_fdt_setprop_cells(fdt, gic, "interrupts", + FDT_GIC_PPI, RK3588_GIC_MAINT_PPI, + FDT_IRQ_TYPE_LEVEL_HIGH, 0); + qemu_fdt_setprop(fdt, gic, "interrupt-controller", NULL, 0); + qemu_fdt_setprop_cell(fdt, gic, "#interrupt-cells", 4); + qemu_fdt_setprop_cell(fdt, gic, "#address-cells", 2); + qemu_fdt_setprop_cell(fdt, gic, "#size-cells", 2); + qemu_fdt_setprop(fdt, gic, "ranges", NULL, 0); + qemu_fdt_setprop(fdt, gic, "dma-noncoherent", NULL, 0); + + phandle = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_setprop_cell(fdt, gic, "phandle", phandle); + qemu_fdt_setprop_cell(fdt, "/", "interrupt-parent", phandle); + + rk3588_fdt_add_its_node(fdt, gic, 0, its0_phandle); + rk3588_fdt_add_its_node(fdt, gic, 1, its1_phandle); +} + +static void rk3588_fdt_add_timer_node(void *fdt) +{ + const char *timer = "/timer"; + + qemu_fdt_add_subnode(fdt, timer); + qemu_fdt_setprop_string(fdt, timer, "compatible", "arm,armv8-timer"); + qemu_fdt_setprop_cells(fdt, timer, "interrupts", + FDT_GIC_PPI, INTID_TO_PPI(ARCH_TIMER_S_EL1_IRQ), + FDT_IRQ_TYPE_LEVEL_HIGH, 0, + FDT_GIC_PPI, INTID_TO_PPI(ARCH_TIMER_NS_EL1_IRQ), + FDT_IRQ_TYPE_LEVEL_HIGH, 0, + FDT_GIC_PPI, INTID_TO_PPI(ARCH_TIMER_VIRT_IRQ), + FDT_IRQ_TYPE_LEVEL_HIGH, 0, + FDT_GIC_PPI, INTID_TO_PPI(ARCH_TIMER_NS_EL2_IRQ), + FDT_IRQ_TYPE_LEVEL_HIGH, 0, + FDT_GIC_PPI, INTID_TO_PPI(ARCH_TIMER_NS_EL2_VIRT_IRQ), + FDT_IRQ_TYPE_LEVEL_HIGH, 0); + qemu_fdt_setprop_cell(fdt, timer, "clock-frequency", RK3588_GTIMER_HZ); + qemu_fdt_setprop(fdt, timer, "always-on", NULL, 0); +} + +static void rk3588_fdt_add_uart_node(void *fdt) +{ + const char *uart = "/serial@feb50000"; + static const char * const compat[] = { + "rockchip,rk3588-uart", + "snps,dw-apb-uart", + "ns16550a", + }; + + qemu_fdt_add_subnode(fdt, "/aliases"); + qemu_fdt_setprop_string(fdt, "/aliases", "serial2", uart); + + qemu_fdt_add_subnode(fdt, "/chosen"); + qemu_fdt_setprop_string(fdt, "/chosen", "stdout-path", "serial2:1500000n8"); + + qemu_fdt_add_subnode(fdt, uart); + qemu_fdt_setprop_string_array(fdt, uart, "compatible", + (char **)&compat, ARRAY_SIZE(compat)); + qemu_fdt_setprop_sized_cells(fdt, uart, "reg", + 2, rk3588_memmap[RK3588_UART2].base, + 2, rk3588_memmap[RK3588_UART2].size); + qemu_fdt_setprop_cells(fdt, uart, "interrupts", + FDT_GIC_SPI, RK3588_UART2_SPI, + FDT_IRQ_TYPE_LEVEL_HIGH, 0); + qemu_fdt_setprop_cell(fdt, uart, "clock-frequency", RK3588_GTIMER_HZ); + qemu_fdt_setprop_cell(fdt, uart, "current-speed", RK3588_UART_BAUDBASE); + qemu_fdt_setprop_cell(fdt, uart, "reg-shift", 2); + qemu_fdt_setprop_cell(fdt, uart, "reg-io-width", 4); + qemu_fdt_setprop_string(fdt, uart, "status", "okay"); +} +static uint32_t rk3588_fdt_add_fixed_clock_node(void *fdt) +{ + const char *clk = "/xin24m"; + uint32_t phandle = qemu_fdt_alloc_phandle(fdt); + + qemu_fdt_add_subnode(fdt, clk); + qemu_fdt_setprop_string(fdt, clk, "compatible", "fixed-clock"); + qemu_fdt_setprop_cell(fdt, clk, "#clock-cells", 0); + qemu_fdt_setprop_cell(fdt, clk, "clock-frequency", RK3588_GTIMER_HZ); + qemu_fdt_setprop_string(fdt, clk, "clock-output-names", "xin24m"); + qemu_fdt_setprop_cell(fdt, clk, "phandle", phandle); + + return phandle; +} + +/* + * SCMI firmware node + shmem reserved-memory node. Returns the + * phandle of the scmi_clk protocol sub-node (used by the sdmmc FDT + * node to reference its biu/ciu clocks). + */ +static uint32_t rk3588_fdt_add_scmi_nodes(void *fdt) +{ + const char *firmware = "/firmware"; + const char *scmi = "/firmware/scmi"; + const char *scmi_clk = "/firmware/scmi/protocol@14"; + const char *scmi_reset = "/firmware/scmi/protocol@16"; + const char *shmem = "/reserved-memory/scmi_shmem@10f000"; + uint32_t shmem_ph, scmi_ph, scmi_clk_ph; + + /* + * reserved-memory/scmi_shmem - the 256-byte shmem slot the SCMI + * SMC transport reads/writes during a synchronous command. The + * `ranges;` property is required by the binding so the kernel's + * reserved-memory framework can map the shmem phandle to a + * physical address. + */ + qemu_fdt_add_subnode(fdt, "/reserved-memory"); + qemu_fdt_setprop_cell(fdt, "/reserved-memory", "#address-cells", 2); + qemu_fdt_setprop_cell(fdt, "/reserved-memory", "#size-cells", 2); + qemu_fdt_setprop(fdt, "/reserved-memory", "ranges", NULL, 0); + + shmem_ph = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_add_subnode(fdt, shmem); + qemu_fdt_setprop_string(fdt, shmem, "compatible", "arm,scmi-shmem"); + qemu_fdt_setprop_sized_cells(fdt, shmem, "reg", + 2, rk3588_memmap[RK3588_SCMI_SHMEM].base, + 2, rk3588_memmap[RK3588_SCMI_SHMEM].size); + qemu_fdt_setprop(fdt, shmem, "no-map", NULL, 0); + qemu_fdt_setprop_cell(fdt, shmem, "phandle", shmem_ph); + + /* + * /firmware/scmi - SMC transport, single shmem, smc-id 0x82000010. + * QEMU's SMC hook (target/arm/tcg/psci.c) intercepts that function-id + * and serves the shmem via the rk3588-scmi device. + */ + qemu_fdt_add_subnode(fdt, firmware); + qemu_fdt_add_subnode(fdt, scmi); + qemu_fdt_setprop_string(fdt, scmi, "compatible", "arm,scmi-smc"); + qemu_fdt_setprop_cell(fdt, scmi, "arm,smc-id", RK3588_SCMI_SMC_ID); + qemu_fdt_setprop_cell(fdt, scmi, "shmem", shmem_ph); + qemu_fdt_setprop_cell(fdt, scmi, "#address-cells", 1); + qemu_fdt_setprop_cell(fdt, scmi, "#size-cells", 0); + + scmi_ph = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_setprop_cell(fdt, scmi, "phandle", scmi_ph); + + /* CLOCK protocol (0x14). */ + scmi_clk_ph = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_add_subnode(fdt, scmi_clk); + qemu_fdt_setprop_cell(fdt, scmi_clk, "reg", 0x14); + qemu_fdt_setprop_cell(fdt, scmi_clk, "#clock-cells", 1); + qemu_fdt_setprop_cell(fdt, scmi_clk, "phandle", scmi_clk_ph); + + /* RESET protocol (0x16) - referenced by trng only; advertise for FDT fidelity. */ + qemu_fdt_add_subnode(fdt, scmi_reset); + qemu_fdt_setprop_cell(fdt, scmi_reset, "reg", 0x16); + qemu_fdt_setprop_cell(fdt, scmi_reset, "#reset-cells", 1); + + return scmi_clk_ph; +} + +static void rk3588_fdt_add_storage_nodes(void *fdt, uint32_t clk_phandle, + uint32_t scmi_clk_phandle) +{ + const char *sdhci = "/mmc@fe2e0000"; + const char *sdmmc = "/mmc@fe2c0000"; + static const char * const sdhci_compat[] = { + "rockchip,rk3588-dwcmshc", + "snps,dwcmshc-sdhci", + }; + static const char * const sdmmc_compat[] = { + "rockchip,rk3588-dw-mshc", + "rockchip,rk3288-dw-mshc", + }; + static const char * const sdhci_clock_names[] = { + "core", "bus", "axi", "block", "timer", + }; + /* + * Per rk3588-base.dtsi:2200-2213: biu=SCMI_HCLK_SD(23), + * ciu=SCMI_CCLK_SD(9); the ciu-drive / ciu-sample phase clocks + * are CRU-side (SCLK_SDMMC_DRV / _SAMPLE) and the model serves + * them as fire-and-forget via the cru stub - keep them on xin24m + * so clk_get succeeds. + */ + static const char * const sdmmc_clock_names[] = { + "biu", "ciu", "ciu-drive", "ciu-sample", + }; + + qemu_fdt_add_subnode(fdt, sdhci); + qemu_fdt_setprop_string_array(fdt, sdhci, "compatible", + (char **)&sdhci_compat, + ARRAY_SIZE(sdhci_compat)); + qemu_fdt_setprop_sized_cells(fdt, sdhci, "reg", + 2, rk3588_memmap[RK3588_SDHCI].base, + 2, rk3588_memmap[RK3588_SDHCI].size); + qemu_fdt_setprop_cells(fdt, sdhci, "interrupts", + FDT_GIC_SPI, RK3588_SDHCI_SPI, + FDT_IRQ_TYPE_LEVEL_HIGH, 0); + qemu_fdt_setprop_cells(fdt, sdhci, "clocks", + clk_phandle, clk_phandle, clk_phandle, + clk_phandle, clk_phandle); + qemu_fdt_setprop_string_array(fdt, sdhci, "clock-names", + (char **)&sdhci_clock_names, + ARRAY_SIZE(sdhci_clock_names)); + qemu_fdt_setprop_cell(fdt, sdhci, "bus-width", 8); + qemu_fdt_setprop(fdt, sdhci, "non-removable", NULL, 0); + qemu_fdt_setprop(fdt, sdhci, "no-sdio", NULL, 0); + qemu_fdt_setprop(fdt, sdhci, "no-sd", NULL, 0); + qemu_fdt_setprop_cell(fdt, sdhci, "max-frequency", 200000000); + qemu_fdt_setprop_string(fdt, sdhci, "status", "okay"); + + qemu_fdt_setprop_string(fdt, "/aliases", "mmc0", sdhci); + + /* dw_mmc SD-card controller. */ + qemu_fdt_add_subnode(fdt, sdmmc); + qemu_fdt_setprop_string_array(fdt, sdmmc, "compatible", + (char **)&sdmmc_compat, + ARRAY_SIZE(sdmmc_compat)); + qemu_fdt_setprop_sized_cells(fdt, sdmmc, "reg", + 2, rk3588_memmap[RK3588_SDMMC].base, + 2, rk3588_memmap[RK3588_SDMMC].size); + qemu_fdt_setprop_cells(fdt, sdmmc, "interrupts", + FDT_GIC_SPI, RK3588_SDMMC_SPI, + FDT_IRQ_TYPE_LEVEL_HIGH, 0); + qemu_fdt_setprop_cells(fdt, sdmmc, "clocks", + scmi_clk_phandle, 23, /* SCMI_HCLK_SD */ + scmi_clk_phandle, 9, /* SCMI_CCLK_SD */ + clk_phandle, 0, /* SCLK_SDMMC_DRV (CRU stub) */ + clk_phandle, 0); /* SCLK_SDMMC_SAMPLE */ + qemu_fdt_setprop_string_array(fdt, sdmmc, "clock-names", + (char **)&sdmmc_clock_names, + ARRAY_SIZE(sdmmc_clock_names)); + qemu_fdt_setprop_cell(fdt, sdmmc, "bus-width", 4); + qemu_fdt_setprop_cell(fdt, sdmmc, "cap-sd-highspeed", 1); + qemu_fdt_setprop_cell(fdt, sdmmc, "cap-mmc-highspeed", 1); + qemu_fdt_setprop_cell(fdt, sdmmc, "max-frequency", 200000000); + /* + * fifo-depth = 0x100 per rk3588-base.dtsi:2206. The driver uses + * this to size its PIO loop and to derive FIFOTH when no DTS + * value is present. + */ + qemu_fdt_setprop_cell(fdt, sdmmc, "fifo-depth", 0x100); + qemu_fdt_setprop_string(fdt, sdmmc, "status", "okay"); + + qemu_fdt_setprop_string(fdt, "/aliases", "mmc1", sdmmc); +} + +static void rk3588_fdt_add_gpio_nodes(void *fdt, uint32_t clk_phandle) +{ + const char *pinctrl = "/pinctrl"; + uint32_t pinctrl_phandle = qemu_fdt_alloc_phandle(fdt); + + qemu_fdt_add_subnode(fdt, pinctrl); + qemu_fdt_setprop_string(fdt, pinctrl, "compatible", + "rockchip,rk3588-pinctrl"); + qemu_fdt_setprop(fdt, pinctrl, "ranges", NULL, 0); + qemu_fdt_setprop_cell(fdt, pinctrl, "#address-cells", 2); + qemu_fdt_setprop_cell(fdt, pinctrl, "#size-cells", 2); + qemu_fdt_setprop_cell(fdt, pinctrl, "phandle", pinctrl_phandle); + + for (unsigned int i = 0; i < ARRAY_SIZE(((RK3588MachineState *)0)->gpio); + i++) { + int idx = RK3588_GPIO0 + i; + g_autofree char *node = g_strdup_printf("%s/gpio%u@%" PRIx64, + pinctrl, i, + rk3588_memmap[idx].base); + g_autofree char *alias = g_strdup_printf("gpio%u", i); + + qemu_fdt_add_subnode(fdt, node); + qemu_fdt_setprop_string(fdt, node, "compatible", + "rockchip,gpio-bank"); + qemu_fdt_setprop_sized_cells(fdt, node, "reg", + 2, rk3588_memmap[idx].base, + 2, rk3588_memmap[idx].size); + qemu_fdt_setprop_cells(fdt, node, "interrupts", + FDT_GIC_SPI, RK3588_GPIO0_SPI + i, + FDT_IRQ_TYPE_LEVEL_HIGH, 0); + qemu_fdt_setprop_cells(fdt, node, "clocks", clk_phandle, + clk_phandle); + qemu_fdt_setprop(fdt, node, "gpio-controller", NULL, 0); + qemu_fdt_setprop_cells(fdt, node, "gpio-ranges", + pinctrl_phandle, 0, i * ROCKCHIP_GPIO_PINS, + ROCKCHIP_GPIO_PINS); + qemu_fdt_setprop(fdt, node, "interrupt-controller", NULL, 0); + qemu_fdt_setprop_cell(fdt, node, "#gpio-cells", 2); + qemu_fdt_setprop_cell(fdt, node, "#interrupt-cells", 2); + + qemu_fdt_setprop_string(fdt, "/aliases", alias, node); + } +} + +static void rk3588_fdt_add_grf_nodes(void *fdt, uint32_t *sys_grf_ph, + uint32_t *php_grf_ph) +{ + const char *sys_grf = "/syscon@fd58c000"; + const char *php_grf = "/syscon@fd5b0000"; + static const char * const sys_grf_compat[] = { + "rockchip,rk3588-sys-grf", "syscon", "simple-mfd", + }; + static const char * const php_grf_compat[] = { + "rockchip,rk3588-php-grf", "syscon", + }; + + *sys_grf_ph = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_add_subnode(fdt, sys_grf); + qemu_fdt_setprop_string_array(fdt, sys_grf, "compatible", + (char **)&sys_grf_compat, + ARRAY_SIZE(sys_grf_compat)); + qemu_fdt_setprop_sized_cells(fdt, sys_grf, "reg", + 2, rk3588_memmap[RK3588_SYS_GRF].base, + 2, rk3588_memmap[RK3588_SYS_GRF].size); + qemu_fdt_setprop_cell(fdt, sys_grf, "phandle", *sys_grf_ph); + + *php_grf_ph = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_add_subnode(fdt, php_grf); + qemu_fdt_setprop_string_array(fdt, php_grf, "compatible", + (char **)&php_grf_compat, + ARRAY_SIZE(php_grf_compat)); + qemu_fdt_setprop_sized_cells(fdt, php_grf, "reg", + 2, rk3588_memmap[RK3588_PHP_GRF].base, + 2, rk3588_memmap[RK3588_PHP_GRF].size); + qemu_fdt_setprop_cell(fdt, php_grf, "phandle", *php_grf_ph); +} + +/* + * CRU node - the clock-and-reset provider. CLK_OF_DECLARE makes + * clk-rk3588 bind very early; the model backs offset 0x600 with + * 0xffffffff (all PLLs locked) so the early PLL-lock-status poll + * terminates and the rest of init can proceed. + */ +static uint32_t rk3588_fdt_add_cru_node(void *fdt) +{ + const char *cru = "/clock-reset-controller@fd7c0000"; + uint32_t phandle = qemu_fdt_alloc_phandle(fdt); + + qemu_fdt_add_subnode(fdt, cru); + qemu_fdt_setprop_string(fdt, cru, "compatible", "rockchip,rk3588-cru"); + qemu_fdt_setprop_sized_cells(fdt, cru, "reg", + 2, rk3588_memmap[RK3588_CRU_MEM].base, + 2, rk3588_memmap[RK3588_CRU_MEM].size); + qemu_fdt_setprop_cell(fdt, cru, "#clock-cells", 1); + qemu_fdt_setprop_cell(fdt, cru, "#reset-cells", 1); + qemu_fdt_setprop_string(fdt, cru, "clock-output-names", "rk3588-cru"); + qemu_fdt_setprop_cell(fdt, cru, "phandle", phandle); + + return phandle; +} + +static void rk3588_fdt_add_gmac_node(void *fdt, unsigned int id, + uint32_t clk_phandle, + uint32_t sys_grf_ph, uint32_t php_grf_ph) +{ + const char *gmac = id ? "/ethernet@fe1c0000" : "/ethernet@fe1b0000"; + int map = id ? RK3588_GMAC1 : RK3588_GMAC0; + int spi = id ? RK3588_GMAC1_SPI : RK3588_GMAC0_SPI; + static const char * const compat[] = { + "rockchip,rk3588-gmac", + "snps,dwmac-4.20a", + }; + static const char * const clock_names[] = { + "stmmaceth", "clk_mac_ref", "pclk_mac", "aclk_mac", "ptp_ref", + }; + + qemu_fdt_add_subnode(fdt, gmac); + qemu_fdt_setprop_string_array(fdt, gmac, "compatible", + (char **)&compat, ARRAY_SIZE(compat)); + qemu_fdt_setprop_sized_cells(fdt, gmac, "reg", + 2, rk3588_memmap[map].base, + 2, rk3588_memmap[map].size); + qemu_fdt_setprop_cells(fdt, gmac, "interrupts", + FDT_GIC_SPI, spi, FDT_IRQ_TYPE_LEVEL_HIGH, 0); + qemu_fdt_setprop_string(fdt, gmac, "interrupt-names", "macirq"); + qemu_fdt_setprop_cells(fdt, gmac, "clocks", + clk_phandle, clk_phandle, clk_phandle, + clk_phandle, clk_phandle); + qemu_fdt_setprop_string_array(fdt, gmac, "clock-names", + (char **)&clock_names, + ARRAY_SIZE(clock_names)); + /* + * dwmac-rk.c REQUIRES rockchip,grf (probe returns -ENODEV without it) + * and rk3588_ops also wants rockchip,php-grf for CON0/CLK_CON1. Both + * are modelled as RAZ/WI syscons - see rk3588_create_grf_devices(). + */ + qemu_fdt_setprop_cell(fdt, gmac, "rockchip,grf", sys_grf_ph); + qemu_fdt_setprop_cell(fdt, gmac, "rockchip,php-grf", php_grf_ph); + qemu_fdt_setprop_string(fdt, gmac, "phy-mode", + id ? "rgmii-id" : "rgmii-rxid"); + qemu_fdt_setprop_string(fdt, gmac, "status", "okay"); +} + +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; + + 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); + + 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"); +} + +static void rk3588_fdt_add_pcie_node(void *fdt, uint32_t cru_phandle, + uint32_t clk_phandle, + uint32_t its1_phandle) +{ + const char *pcie = "/pcie@fe150000"; + static const char * const compat[] = { + "rockchip,rk3588-pcie", + "rockchip,rk3568-pcie", + }; + static const char * const reg_names[] = { + "dbi", "apb", "config", + }; + static const char * const clock_names[] = { + "aclk_mst", "aclk_slv", "aclk_dbi", "pclk", "aux", "pipe", + }; + 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); + 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_IRQ_TYPE_LEVEL_HIGH, 0, + FDT_GIC_SPI, RK3588_PCIE3X4_PMC_SPI, + FDT_IRQ_TYPE_LEVEL_HIGH, 0, + FDT_GIC_SPI, RK3588_PCIE3X4_MSG_SPI, + FDT_IRQ_TYPE_LEVEL_HIGH, 0, + FDT_GIC_SPI, RK3588_PCIE3X4_LEGACY_SPI, + FDT_IRQ_TYPE_LEVEL_HIGH, 0, + FDT_GIC_SPI, RK3588_PCIE3X4_ERR_SPI, + FDT_IRQ_TYPE_LEVEL_HIGH, 0); + static const char * const irq_names[] = { + "sys", "pmc", "msg", "legacy", "err", + }; + qemu_fdt_setprop_string_array(fdt, pcie, "interrupt-names", + (char **)&irq_names, + ARRAY_SIZE(irq_names)); + /* + * Clocks stay on xin24m (the always-on 24 MHz oscillator). The CRU + * stub accepts the clk_prepare_enable gate writes fire-and-forget + * but is NOT a real clock provider (no rate), so referencing cru + * here makes clk_bulk_get fail with -EINVAL. xin24m is a real + * fixed-clock and lets the driver's clk_prepare_enable calls land. + */ + qemu_fdt_setprop_cells(fdt, pcie, "clocks", + clk_phandle, clk_phandle, clk_phandle, + clk_phandle, clk_phandle, clk_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). + */ + qemu_fdt_setprop_cells(fdt, pcie, "resets", + cru_phandle, SRST_PCIE0_POWER_UP, + cru_phandle, SRST_P_PCIE0); + 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_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. + */ + qemu_fdt_setprop_cells(fdt, pcie, "msi-map", + 0x0000, its1_phandle, 0x0000, 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. + */ + 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); + /* 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_string(fdt, pcie, "status", "okay"); +} + + +static void *rk3588_get_dtb(const struct arm_boot_info *binfo, int *fdt_size) +{ + RK3588MachineState *s = container_of(binfo, RK3588MachineState, bootinfo); + const RK3588BoardConfig *board = s->board; + void *fdt = create_device_tree(fdt_size); + uint32_t clk_phandle; + uint32_t its0_phandle, its1_phandle; + + if (!fdt) { + error_report("create_device_tree() failed"); + exit(EXIT_FAILURE); + } + + qemu_fdt_setprop_string(fdt, "/", "model", board->fdt_model); + qemu_fdt_setprop_string_array(fdt, "/", "compatible", + (char **)board->fdt_compatible, + board->fdt_compatible_count); + qemu_fdt_setprop_cell(fdt, "/", "#address-cells", 2); + qemu_fdt_setprop_cell(fdt, "/", "#size-cells", 2); + + clk_phandle = rk3588_fdt_add_fixed_clock_node(fdt); + + uint32_t sys_grf_ph, php_grf_ph; + rk3588_fdt_add_grf_nodes(fdt, &sys_grf_ph, &php_grf_ph); + uint32_t cru_phandle = rk3588_fdt_add_cru_node(fdt); + uint32_t scmi_clk_phandle = rk3588_fdt_add_scmi_nodes(fdt); + + rk3588_fdt_add_cpu_nodes(s, fdt); + rk3588_fdt_add_gic_node(fdt, &its0_phandle, &its1_phandle); + rk3588_fdt_add_timer_node(fdt); + rk3588_fdt_add_uart_node(fdt); + 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); + + return fdt; +} + +static void rk3588_create_cpus(RK3588MachineState *s) +{ + MachineState *ms = MACHINE(s); + MachineClass *mc = MACHINE_GET_CLASS(ms); + MemoryRegion *sysmem = get_system_memory(); + const CPUArchIdList *possible_cpus = mc->possible_cpu_arch_ids(ms); + + for (unsigned int n = 0; n < ms->smp.cpus; n++) { + g_autofree char *name = g_strdup_printf("cpu%u", n); + Object *cpuobj = object_new(possible_cpus->cpus[n].type); + CPUState *cs = CPU(cpuobj); + + object_property_add_child(OBJECT(ms), name, cpuobj); + cs->cpu_index = n; + numa_cpu_pre_plug(&possible_cpus->cpus[n], DEVICE(cpuobj), + &error_fatal); + object_property_set_int(cpuobj, "mp-affinity", + possible_cpus->cpus[n].arch_id, + &error_abort); + object_property_set_int(cpuobj, "cntfrq", RK3588_GTIMER_HZ, + &error_abort); + object_property_set_link(cpuobj, "memory", OBJECT(sysmem), + &error_abort); + + if (object_property_find(cpuobj, "reset-cbar")) { + object_property_set_int(cpuobj, "reset-cbar", + rk3588_memmap[RK3588_GIC_DIST].base, + &error_abort); + } + if (object_property_find(cpuobj, "has_el2")) { + object_property_set_bool(cpuobj, "has_el2", !kvm_enabled(), + &error_abort); + } + if (object_property_find(cpuobj, "has_el3")) { + object_property_set_bool(cpuobj, "has_el3", !kvm_enabled(), + &error_abort); + } + qdev_realize(DEVICE(cpuobj), NULL, &error_fatal); + s->cpu[n] = ARM_CPU(cpuobj); + } +} + +static void rk3588_enable_psci_conduit(RK3588MachineState *s) +{ + MachineState *ms = MACHINE(s); + + for (unsigned int n = 0; n < ms->smp.cpus; n++) { + object_property_set_int(OBJECT(s->cpu[n]), "psci-conduit", + QEMU_PSCI_CONDUIT_SMC, &error_abort); + if (n > 0) { + object_property_set_bool(OBJECT(s->cpu[n]), "start-powered-off", + true, &error_abort); + } + } +} + +static RockchipSysconState *rk3588_create_syscon(RK3588MachineState *s, + const char *name, int memidx) +{ + DeviceState *dev = qdev_new(TYPE_ROCKCHIP_SYSCON); + SysBusDevice *sbd; + + qdev_prop_set_uint32(dev, "size", rk3588_memmap[memidx].size); + object_property_add_child(OBJECT(s), name, OBJECT(dev)); + 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); + } +} + +static void rk3588_write_atags(RK3588MachineState *s) +{ + MachineState *ms = MACHINE(s); + uint8_t *base = memory_region_get_ram_ptr(&s->atags); + uint32_t tag_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); +} + +static void rk3588_seed_iram_firmware_shims(RK3588MachineState *s) +{ + uint8_t *iram = memory_region_get_ram_ptr(&s->iram); + hwaddr offset = RK3588_PMUSRAM_SKIP_ADDR - + rk3588_memmap[RK3588_IRAM].base; + + /* + * Rockchip BL31 calls into PMUSRAM code that would normally be loaded by + * earlier firmware stages. The downstream model does not execute the PMU + * firmware, so return from this low-power helper and keep the EL3 bring-up + * path moving when the SRAM slot is otherwise empty. + */ + stl_le_p(iram + offset, RK3588_AARCH64_RET); +} + +static bool rk3588_phys_read32(hwaddr addr, uint32_t *value) +{ + uint32_t data; + + if (address_space_read(&address_space_memory, addr, MEMTXATTRS_UNSPECIFIED, + &data, sizeof(data)) != MEMTX_OK) { + return false; + } + + *value = le32_to_cpu(data); + return true; +} + +static bool rk3588_phys_write32(hwaddr addr, uint32_t value) +{ + uint32_t data = cpu_to_le32(value); + + return address_space_write(&address_space_memory, addr, + MEMTXATTRS_UNSPECIFIED, + &data, sizeof(data)) == MEMTX_OK; +} + +static bool rk3588_patch_bl31_runtime(RK3588MachineState *s) +{ + uint32_t copy_call; + uint32_t run_call; + uint32_t rmr_branch; + uint32_t mmu_enable; + uint32_t mmu_body; + uint32_t exit_lr; + uint32_t exit_eret; + + if (s->firmware_patch_done) { + return true; + } + + if (!rk3588_phys_read32(RK3588_BL31_PMUSRAM_COPY_CALL_ADDR, ©_call) || + !rk3588_phys_read32(RK3588_BL31_PMUSRAM_RUN_CALL_ADDR, &run_call) || + !rk3588_phys_read32(RK3588_BL31_RMR_CBZ_ADDR, &rmr_branch) || + !rk3588_phys_read32(RK3588_BL31_MMU_ENABLE_ADDR, &mmu_enable) || + !rk3588_phys_read32(RK3588_BL31_MMU_ENABLE_BODY_ADDR, &mmu_body) || + !rk3588_phys_read32(RK3588_BL31_EXIT_LR_ADDR, &exit_lr) || + !rk3588_phys_read32(RK3588_BL31_EXIT_ERET_ADDR, &exit_eret)) { + return false; + } + + if (copy_call == RK3588_BL31_PMUSRAM_COPY_CALL && + run_call == RK3588_BL31_PMUSRAM_RUN_CALL && + rmr_branch == RK3588_BL31_RMR_CBZ && + mmu_enable == RK3588_BL31_MMU_ENABLE_BRANCH && + mmu_body == RK3588_BL31_MMU_ENABLE_TLBI && + exit_lr == RK3588_BL31_EXIT_LR && + exit_eret == RK3588_BL31_EXIT_ERET) { + rk3588_phys_write32(RK3588_BL31_PMUSRAM_COPY_CALL_ADDR, + RK3588_AARCH64_NOP); + rk3588_phys_write32(RK3588_BL31_PMUSRAM_RUN_CALL_ADDR, + RK3588_AARCH64_NOP); + rk3588_phys_write32(RK3588_BL31_RMR_CBZ_ADDR, + RK3588_BL31_RMR_SKIP); + rk3588_phys_write32(RK3588_BL31_MMU_ENABLE_ADDR, + RK3588_AARCH64_RET); + rk3588_phys_write32(RK3588_BL31_MMU_ENABLE_BODY_ADDR, + RK3588_AARCH64_RET); + rk3588_phys_write32(RK3588_BL31_EXIT_LR_ADDR, + RK3588_BL31_EXIT_MOVZ); + rk3588_phys_write32(RK3588_BL31_EXIT_ERET_ADDR, + RK3588_AARCH64_SMC); + s->firmware_patch_done = true; + return true; + } + + if (copy_call == RK3588_AARCH64_NOP && + run_call == RK3588_AARCH64_NOP && + rmr_branch == RK3588_BL31_RMR_SKIP && + mmu_enable == RK3588_AARCH64_RET && + mmu_body == RK3588_AARCH64_RET && + exit_lr == RK3588_BL31_EXIT_MOVZ && + exit_eret == RK3588_AARCH64_SMC) { + s->firmware_patch_done = true; + return true; + } + + return false; +} + +static void rk3588_patch_spl_atf_handoff(void) +{ + /* + * SPL is not covered by the FIT hashes it verifies. Replace the final + * mov x0, x19; mov x1, #0; blr x20 in spl_invoke_atf() with a + * QEMU-private SMC after preserving the BL31 params pointer in x2. + * The handler patches BL31 after SPL has validated and loaded the FIT + * images, then resumes at the real BL31 entry point. + */ + rk3588_phys_write32(RK3588_SPL_ATF_CALL_ADDR, + RK3588_SPL_ATF_PARAMS_MOV); + rk3588_phys_write32(RK3588_SPL_ATF_CALL_ADDR + sizeof(uint32_t), + RK3588_SPL_ATF_ENTRY_MOVZ); + rk3588_phys_write32(RK3588_SPL_ATF_CALL_ADDR + 2 * sizeof(uint32_t), + RK3588_AARCH64_SMC); +} + +static void rk3588_prepare_nonsecure_linux_interrupts(RK3588MachineState *s) +{ + ARMLinuxBootIf *albif = ARM_LINUX_BOOT_IF(s->gic); + ARMLinuxBootIfClass *albifc = ARM_LINUX_BOOT_IF_GET_CLASS(albif); + + /* + * The firmware shim bypasses arm_load_kernel(), so run the same GIC + * Linux-init hook that direct kernel boot uses. This models secure + * firmware handing IRQ ownership to the NonSecure kernel before U-Boot + * eventually jumps to Linux. + */ + if (albifc->arm_linux_init) { + albifc->arm_linux_init(albif, false); + } + device_cold_reset(s->gic); + + for (unsigned int i = 0; i < ARRAY_SIZE(s->its); i++) { + if (s->its[i]) { + device_cold_reset(s->its[i]); + } + } +} + +static void rk3588_schedule_firmware_patch(RK3588MachineState *s) +{ + if (!s->firmware_boot || s->firmware_handoff_done || + !s->firmware_patch_timer) { + return; + } + + timer_mod(s->firmware_patch_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + RK3588_FIRMWARE_PATCH_INTERVAL_NS); +} + +static void rk3588_firmware_handoff_to_uboot(RK3588MachineState *s, + ARMCPU *cpu) +{ + CPUState *cs = CPU(cpu); + CPUARMState *env = &cpu->env; + + rk3588_prepare_nonsecure_linux_interrupts(s); + cpu_reset(cs); + arm_emulate_firmware_reset(cs, 2); + cpu_set_pc(cs, RK3588_UBOOT_LOAD_ADDR); + env->xregs[0] = 0; + env->xregs[1] = 0; + env->xregs[2] = 0; + env->xregs[3] = 0; + cs->halted = false; + arm_rebuild_hflags(env); + s->firmware_handoff_done = true; + rk3588_seed_firmware_usb2_hosts(s); +} + +static void rk3588_firmware_patch_tick(void *opaque) +{ + RK3588MachineState *s = opaque; + ARMCPU *cpu = s->cpu[0]; + CPUARMState *env = &cpu->env; + uint32_t uboot_entry; + uint64_t pc; + bool pc_in_bl31; + + if (!s->firmware_boot || s->firmware_handoff_done) { + 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); + } + + 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); + return; + } + + rk3588_schedule_firmware_patch(s); +} + +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_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; + s->bootrom_state.spl_loaded = false; + rk3588_schedule_firmware_patch(s); +} + +static void rk3588_create_low_memory(RK3588MachineState *s) +{ + MemoryRegion *sysmem = get_system_memory(); + static const uint32_t trampoline[] = { + 0xd286b100, /* movz x0, #0x3588 */ + 0xf2b84000, /* movk x0, #0xc200, lsl #16 */ + 0xd4000003, /* smc #0 */ + 0x14000000, /* b . */ + }; + + memory_region_init_ram(&s->sram, NULL, "rk3588.sram", + rk3588_memmap[RK3588_SRAM].size, &error_fatal); + memory_region_add_subregion(sysmem, rk3588_memmap[RK3588_SRAM].base, + &s->sram); + + memory_region_init_ram(&s->firmware_scratch, NULL, "rk3588.fw-scratch", + rk3588_memmap[RK3588_FIRMWARE_SCRATCH].size, + &error_fatal); + memory_region_add_subregion(sysmem, + 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, + &s->atags); + rk3588_write_atags(s); + + memory_region_init_ram(&s->iram, NULL, "rk3588.iram", + rk3588_memmap[RK3588_IRAM].size, &error_fatal); + memory_region_add_subregion(sysmem, rk3588_memmap[RK3588_IRAM].base, + &s->iram); + rk3588_seed_iram_firmware_shims(s); + + memory_region_init_ram(&s->bootrom, NULL, "rk3588.bootrom", + rk3588_memmap[RK3588_BROM].size, &error_fatal); + memcpy(memory_region_get_ram_ptr(&s->bootrom), trampoline, + sizeof(trampoline)); + memory_region_set_readonly(&s->bootrom, true); + memory_region_add_subregion(sysmem, rk3588_memmap[RK3588_BROM].base, + &s->bootrom); + + s->firmware_patch_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, + rk3588_firmware_patch_tick, s); +} + +static void rk3588_create_zvm_ram(RK3588MachineState *s) +{ + MachineState *ms = MACHINE(s); + MemoryRegion *sysmem = get_system_memory(); + hwaddr main_ram_limit = rk3588_memmap[RK3588_RAM].base + ms->ram_size; + hwaddr low_base; + + if (!s->zvm_ram) { + return; + } + + /* + * The downstream RK3588 ZVM binary has fixed guest/shared-memory pools: + * + * 0x68000000..0xe7efffff: guest RAM allocator + * 0xe7f00000..0xefffffff: zshm/notify slots + * 0x100000000..0x1ffffffff: high guest RAM allocator + * + * These are intentionally not described to a normal Linux FDT. Map only + * the low-window portion not already covered by -m RAM, keeping ordinary + * board RAM sizing intact while still giving ZVM's fixed pools real RAM. + */ + low_base = MAX(main_ram_limit, RK3588_ZVM_LOW_RAM_BASE); + if (low_base < RK3588_ZVM_LOW_RAM_LIMIT) { + memory_region_init_ram(&s->zvm_low_ram, NULL, + "rk3588.zvm-low-ram", + RK3588_ZVM_LOW_RAM_LIMIT - low_base, + &error_fatal); + memory_region_add_subregion(sysmem, low_base, &s->zvm_low_ram); + } + + memory_region_init_ram(&s->zvm_high_ram, NULL, "rk3588.zvm-high-ram", + RK3588_ZVM_HIGH_RAM_SIZE, &error_fatal); + memory_region_add_subregion(sysmem, RK3588_ZVM_HIGH_RAM_BASE, + &s->zvm_high_ram); +} + +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); + + 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); +} + +static bool rk3588_blk_read(BlockBackend *blk, int64_t offset, + void *buf, size_t size, Error **errp) +{ + int ret = blk_pread(blk, offset, size, buf, 0); + + if (ret < 0) { + error_setg_errno(errp, -ret, "failed to read RK3588 firmware image"); + return false; + } + + return true; +} + +static bool rk3588_load_rkns_image(BlockBackend *blk, + const RK3588HeaderV2 *hdr, + unsigned int index, uint8_t **data, + size_t *size, Error **errp) +{ + uint32_t size_and_off = le32_to_cpu(hdr->images[index].size_and_off); + uint32_t sectors = size_and_off >> 16; + uint32_t offset_sectors = size_and_off & 0xffff; + int64_t base = RK3588_RKNS_LBA * RK3588_RKNS_SECTOR_SIZE; + size_t bytes; + + if (!sectors || !offset_sectors) { + error_setg(errp, "invalid RK3588 RKNS image%u descriptor", index); + return false; + } + + bytes = sectors * RK3588_RKNS_SECTOR_SIZE; + if (bytes > RK3588_SRAM_SIZE) { + error_setg(errp, "RK3588 RKNS image%u is too large: %zu bytes", + index, bytes); + return false; + } + + *data = g_malloc0(bytes); + *size = bytes; + return rk3588_blk_read(blk, + base + offset_sectors * RK3588_RKNS_SECTOR_SIZE, + *data, bytes, errp); +} + +static bool rk3588_bootrom_prepare(RK3588MachineState *s, Error **errp) +{ + const RK3588BoardConfig *board = s->board; + DriveInfo *di = drive_get(IF_SD, 0, board->firmware_sd_unit); + BlockBackend *blk = di ? blk_by_legacy_dinfo(di) : NULL; + RK3588HeaderV2 hdr; + uint8_t *tpl = NULL; + size_t tpl_size = 0; + uint8_t *spl = NULL; + size_t spl_size = 0; + int64_t header_offset = RK3588_RKNS_LBA * RK3588_RKNS_SECTOR_SIZE; + uint32_t nimage; + + if (!blk) { + error_setg(errp, "%s firmware boot requires " + "-drive if=sd,index=%u,file=,format=raw", + board->machine_name, board->firmware_sd_unit); + return false; + } + + if (!rk3588_blk_read(blk, header_offset, &hdr, sizeof(hdr), errp)) { + return false; + } + + if (le32_to_cpu(hdr.magic) != RK3588_RKNS_MAGIC) { + error_setg(errp, "%s boot media LBA %u does not contain an RKNS v2 " + "header", board->machine_name, RK3588_RKNS_LBA); + return false; + } + + nimage = le32_to_cpu(hdr.size_and_nimage) >> 16; + if (nimage < 2) { + error_setg(errp, "RK3588 RKNS header contains %u image(s), need 2", + nimage); + return false; + } + + if (!rk3588_load_rkns_image(blk, &hdr, 0, &tpl, &tpl_size, errp)) { + return false; + } + if (!rk3588_load_rkns_image(blk, &hdr, 1, &spl, &spl_size, errp)) { + g_free(tpl); + return false; + } + + if (address_space_write(&address_space_memory, RK3588_TPL_LOAD_ADDR, + MEMTXATTRS_UNSPECIFIED, tpl, tpl_size) != + MEMTX_OK) { + error_setg(errp, "failed to load RK3588 TPL into IRAM"); + g_free(tpl); + g_free(spl); + return false; + } + s->bootrom_state.spl = spl; + s->bootrom_state.spl_size = spl_size; + s->bootrom_state.tpl_entry = RK3588_TPL_LOAD_ADDR; + s->bootrom_state.spl_loaded = false; + g_free(tpl); + + return true; +} + +static void rk3588_bootrom_load_spl(RK3588MachineState *s, ARMCPU *cpu) +{ + const RK3588BoardConfig *board = s->board; + CPUARMState *env = &cpu->env; + + if (!s->bootrom_state.spl || s->bootrom_state.spl_loaded) { + return; + } + + 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(); + stl_le_p(memory_region_get_ram_ptr(&s->iram) + 0x10, + board->brom_bootsource); + s->bootrom_state.spl_loaded = true; + + cpu_set_pc(CPU(cpu), rk3588_memmap[RK3588_SRAM].base); + env->xregs[30] = RK3588_BROM_TRAMPOLINE; + env->xregs[31] = rk3588_memmap[RK3588_SRAM].base + + rk3588_memmap[RK3588_SRAM].size - 0x100; + env->sp_el[3] = env->xregs[31]; + arm_rebuild_hflags(env); +} + +typedef struct RK3588FirmwareReset { + RK3588MachineState *machine; + ARMCPU *cpu; + bool primary; +} RK3588FirmwareReset; + +static void rk3588_firmware_cpu_reset(void *opaque) +{ + RK3588FirmwareReset *rst = opaque; + RK3588MachineState *s = rst->machine; + ARMCPU *cpu = rst->cpu; + CPUState *cs = CPU(cpu); + CPUARMState *env = &cpu->env; + + cpu_reset(cs); + + if (!rst->primary) { + cs->halted = true; + return; + } + + arm_emulate_firmware_reset(cs, 3); + cpu_set_pc(cs, s->bootrom_state.tpl_entry); + env->xregs[30] = RK3588_BROM_TRAMPOLINE; + env->xregs[31] = rk3588_memmap[RK3588_IRAM].base + + RK3588_SRAM_SIZE - 0x100; + env->sp_el[3] = env->xregs[31]; + arm_rebuild_hflags(env); +} + +static void rk3588_register_firmware_reset(RK3588MachineState *s) +{ + MachineState *ms = MACHINE(s); + + for (unsigned int n = 0; n < ms->smp.cpus; n++) { + RK3588FirmwareReset *rst = g_new0(RK3588FirmwareReset, 1); + + rst->machine = s; + rst->cpu = s->cpu[n]; + rst->primary = n == 0; + qemu_register_reset(rk3588_firmware_cpu_reset, rst); + } +} + +static void rk3588_create_gic(RK3588MachineState *s) +{ + MachineState *ms = MACHINE(s); + SysBusDevice *gicbusdev; + QList *redist_region_count; + uint32_t redist_capacity; + + s->gic = qdev_new(gicv3_class_name()); + qdev_prop_set_uint32(s->gic, "revision", 3); + qdev_prop_set_uint32(s->gic, "num-cpu", ms->smp.cpus); + qdev_prop_set_uint32(s->gic, "num-irq", + RK3588_NUM_SPI_IRQS + GIC_INTERNAL); + qdev_prop_set_bit(s->gic, "has-security-extensions", true); + qdev_prop_set_bit(s->gic, "has-lpi", true); + + redist_capacity = rk3588_memmap[RK3588_GIC_REDIST].size / GICV3_REDIST_SIZE; + redist_region_count = qlist_new(); + qlist_append_int(redist_region_count, MIN(ms->smp.cpus, redist_capacity)); + qdev_prop_set_array(s->gic, "redist-region-count", redist_region_count); + object_property_set_link(OBJECT(s->gic), "sysmem", OBJECT(get_system_memory()), + &error_fatal); + + gicbusdev = SYS_BUS_DEVICE(s->gic); + sysbus_realize_and_unref(gicbusdev, &error_fatal); + sysbus_mmio_map(gicbusdev, 0, rk3588_memmap[RK3588_GIC_DIST].base); + sysbus_mmio_map(gicbusdev, 1, rk3588_memmap[RK3588_GIC_REDIST].base); + + for (unsigned int n = 0; n < ms->smp.cpus; n++) { + DeviceState *cpudev = DEVICE(s->cpu[n]); + int intidbase = RK3588_NUM_SPI_IRQS + n * GIC_INTERNAL; + static const int timer_irqs[] = { + [GTIMER_PHYS] = ARCH_TIMER_NS_EL1_IRQ, + [GTIMER_VIRT] = ARCH_TIMER_VIRT_IRQ, + [GTIMER_HYP] = ARCH_TIMER_NS_EL2_IRQ, + [GTIMER_SEC] = ARCH_TIMER_S_EL1_IRQ, + [GTIMER_HYPVIRT] = ARCH_TIMER_NS_EL2_VIRT_IRQ, + [GTIMER_S_EL2_PHYS] = ARCH_TIMER_S_EL2_IRQ, + [GTIMER_S_EL2_VIRT] = ARCH_TIMER_S_EL2_VIRT_IRQ, + }; + + for (int irq = 0; irq < ARRAY_SIZE(timer_irqs); irq++) { + qdev_connect_gpio_out(cpudev, irq, + qdev_get_gpio_in(s->gic, + intidbase + timer_irqs[irq])); + } + + qdev_connect_gpio_out_named(cpudev, "gicv3-maintenance-interrupt", 0, + qdev_get_gpio_in(s->gic, + intidbase + + ARCH_GIC_MAINT_IRQ)); + qdev_connect_gpio_out_named(cpudev, "pmu-interrupt", 0, + qdev_get_gpio_in(s->gic, + intidbase + + VIRTUAL_PMU_IRQ)); + + sysbus_connect_irq(gicbusdev, n, qdev_get_gpio_in(cpudev, ARM_CPU_IRQ)); + sysbus_connect_irq(gicbusdev, n + ms->smp.cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_FIQ)); + sysbus_connect_irq(gicbusdev, n + 2 * ms->smp.cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_VIRQ)); + sysbus_connect_irq(gicbusdev, n + 3 * ms->smp.cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_VFIQ)); + } +} + +static void rk3588_create_its(RK3588MachineState *s) +{ + static const int its_memmap[] = { + RK3588_GIC_ITS0, + RK3588_GIC_ITS1, + }; + + for (unsigned int i = 0; i < ARRAY_SIZE(its_memmap); i++) { + g_autofree char *name = g_strdup_printf("its%u", i); + DeviceState *dev = qdev_new(its_class_name()); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + object_property_add_child(OBJECT(s), name, OBJECT(dev)); + object_property_set_link(OBJECT(dev), "parent-gicv3", + OBJECT(s->gic), &error_abort); + sysbus_realize(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, rk3588_memmap[its_memmap[i]].base); + s->its[i] = dev; + } +} + +static void rk3588_create_uart(RK3588MachineState *s) +{ + DeviceState *vendor; + SysBusDevice *vendor_sbd; + + /* + * UART2 is a Synopsys dw-apb-uart (16550-compatible). serial_mm models the + * standard 16550 range (8 registers, regshift 2 -> a 0x20-byte window). The + * DesignWare extension registers (USR @0x7c, DMASA, CPR/UCV/CTR) sit + * above that window. Cover only that range so it does not overlap serial_mm. + */ + serial_mm_init(get_system_memory(), rk3588_memmap[RK3588_UART2].base, 2, + qdev_get_gpio_in(s->gic, RK3588_UART2_SPI), + RK3588_UART_BAUDBASE, serial_hd(0), DEVICE_LITTLE_ENDIAN); + + vendor = qdev_new(TYPE_DW_APB_UART_VENDOR); + vendor_sbd = SYS_BUS_DEVICE(vendor); + object_property_add_child(OBJECT(s), "uart2-vendor", OBJECT(vendor)); + sysbus_realize(vendor_sbd, &error_fatal); + sysbus_mmio_map(vendor_sbd, 0, + rk3588_memmap[RK3588_UART2].base + + DW_APB_UART_VENDOR_BASE); +} + +static void rk3588_attach_emmc_card(RK3588MachineState *s) +{ + DriveInfo *di = drive_get(IF_SD, 0, 0); + BlockBackend *blk = di ? blk_by_legacy_dinfo(di) : NULL; + DeviceState *sdhci = s->sdhci; + BusState *bus = qdev_get_child_bus(sdhci, "sd-bus"); + DeviceState *card; + + if (!di) { + return; + } + if (!bus) { + error_report("%s: eMMC controller has no sd-bus", + s->board->machine_name); + exit(EXIT_FAILURE); + } + + card = qdev_new(TYPE_EMMC); + qdev_prop_set_drive_err(card, "drive", blk, &error_fatal); + qdev_realize_and_unref(card, bus, &error_fatal); +} + +/* + * Attach an SD card to the dw_mmc SD-card controller. The board maps + * `-drive if=sd,index=2` to this slot: QEMU's block layer maps + * `if=sd,index=2` to drive_get(IF_SD, bus=0, unit=2) (since + * if_max_devs[IF_SD] = 0, index N maps to bus=0, unit=N). The eMMC + * at the SDHCI takes unit=0; unit=1 is left unused to mirror the + * common sd-host numbering (the secondary slot on most boards), so + * the SD card is at unit=2. + */ +static void rk3588_attach_sd_card(RK3588MachineState *s, DeviceState *sdmmc) +{ + DriveInfo *di = drive_get(IF_SD, 0, 2); + BlockBackend *blk = di ? blk_by_legacy_dinfo(di) : NULL; + BusState *bus = qdev_get_child_bus(sdmmc, "sd-bus"); + DeviceState *card; + + if (!di) { + return; + } + if (!bus) { + error_report("%s: dw_mmc has no sd-bus", s->board->machine_name); + exit(EXIT_FAILURE); + } + + card = qdev_new(TYPE_SD_CARD); + qdev_prop_set_drive_err(card, "drive", blk, &error_fatal); + qdev_realize_and_unref(card, bus, &error_fatal); +} + +static void rk3588_create_sdhci(RK3588MachineState *s) +{ + SysBusDevice *sbd; + + s->sdhci = qdev_new(TYPE_SYSBUS_SDHCI); + object_property_add_child(OBJECT(s), "sdhci", OBJECT(s->sdhci)); + object_property_set_uint(OBJECT(s->sdhci), "sd-spec-version", 3, + &error_abort); + object_property_set_uint(OBJECT(s->sdhci), "capareg", 0x280737ec6481, + &error_abort); + object_property_set_uint(OBJECT(s->sdhci), "uhs", UHS_I, &error_abort); + object_property_set_uint(OBJECT(s->sdhci), "vendor-area1", 0x500, + &error_abort); + object_property_set_uint(OBJECT(s->sdhci), "vendor-area2", 0x800, + &error_abort); + sbd = SYS_BUS_DEVICE(s->sdhci); + sysbus_realize(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, rk3588_memmap[RK3588_SDHCI].base); + sysbus_connect_irq(sbd, 0, qdev_get_gpio_in(s->gic, RK3588_SDHCI_SPI)); + + DeviceState *vendor = qdev_new(TYPE_ROCKCHIP_DWCMSHC_VENDOR); + SysBusDevice *vendor_sbd = SYS_BUS_DEVICE(vendor); + + object_property_add_child(OBJECT(s), "sdhci-vendor", OBJECT(vendor)); + sysbus_realize(vendor_sbd, &error_fatal); + sysbus_mmio_map(vendor_sbd, 0, + rk3588_memmap[RK3588_SDHCI].base + + ROCKCHIP_DWCMSHC_VENDOR_BASE); + + rk3588_attach_emmc_card(s); +} + +static void rk3588_create_gpio(RK3588MachineState *s) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(s->gpio); i++) { + int idx = RK3588_GPIO0 + i; + g_autofree char *name = g_strdup_printf("gpio%u", i); + SysBusDevice *sbd; + + s->gpio[i] = qdev_new(TYPE_ROCKCHIP_GPIO); + object_property_add_child(OBJECT(s), name, OBJECT(s->gpio[i])); + sbd = SYS_BUS_DEVICE(s->gpio[i]); + sysbus_realize(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, rk3588_memmap[idx].base); + sysbus_connect_irq(sbd, 0, + qdev_get_gpio_in(s->gic, RK3588_GPIO0_SPI + i)); + } +} + +static void rk3588_create_gmac(RK3588MachineState *s) +{ + /* + * 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. + */ + 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)); + + 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; + + /* + * 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); + 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); + + /* 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)); + } + /* 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. */ + sysbus_connect_irq(sbd, ROCKCHIP_PCIE_ERR_IRQ, + qdev_get_gpio_in(s->gic, RK3588_PCIE3X4_ERR_SPI)); + sysbus_connect_irq(sbd, ROCKCHIP_PCIE_PMC_IRQ, + qdev_get_gpio_in(s->gic, RK3588_PCIE3X4_PMC_SPI)); + sysbus_connect_irq(sbd, ROCKCHIP_PCIE_SYS_IRQ, + qdev_get_gpio_in(s->gic, RK3588_PCIE3X4_SYS_SPI)); +} + +static void rk3588_create_cru(RK3588MachineState *s) +{ + DeviceState *dev = qdev_new(TYPE_RK3588_CRU); + SysBusDevice *sbd; + + object_property_add_child(OBJECT(s), "cru", OBJECT(dev)); + sbd = SYS_BUS_DEVICE(dev); + sysbus_realize(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, rk3588_memmap[RK3588_CRU_MEM].base); +} + +static void rk3588_create_stimer(RK3588MachineState *s) +{ + DeviceState *dev = qdev_new(TYPE_ROCKCHIP_STIMER); + SysBusDevice *sbd; + + object_property_add_child(OBJECT(s), "stimer", OBJECT(dev)); + sbd = SYS_BUS_DEVICE(dev); + sysbus_realize(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, rk3588_memmap[RK3588_STIMER].base); +} + +/* + * Per-machine SMC handler entry. Registered with + * arm_register_psci_smc_handler() so accelerator SMC exception paths can run it + * before the generic PSCI switch or architectural SMC exception entry. + * 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. + */ +static RK3588SCMIState *rk3588_scmi_dev; +static RK3588MachineState *rk3588_active_machine; + +static bool rk3588_smc_handler(ARMCPU *cpu) +{ + 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); + } + if (is_a64(env)) { + env->xregs[0] = 0; + } else { + env->regs[0] = 0; + } + return true; + } + + if ((uint32_t)fn == RK3588_QEMU_SMC_UBOOT_HANDOFF) { + if (rk3588_active_machine) { + rk3588_firmware_handoff_to_uboot(rk3588_active_machine, 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); + } + return true; + } + + if ((uint32_t)fn == RK3588_QEMU_SMC_BL31_EXIT) { + if (rk3588_active_machine) { + rk3588_firmware_handoff_to_uboot(rk3588_active_machine, cpu); + } + return true; + } + + if ((uint32_t)fn != RK3588_SCMI_SMC_ID) { + return false; + } + if (!rk3588_scmi_dev) { + /* Responder not yet realized - return NOT_SUPPORTED. */ + if (is_a64(env)) { + env->xregs[0] = (uint64_t)(int64_t)-1; + } else { + env->regs[0] = (uint32_t)(int64_t)-1; + } + return true; + } + + rk3588_scmi_handle_smc(rk3588_scmi_dev); + /* a0 = 0 means "response is in shmem, fetch it". */ + if (is_a64(env)) { + env->xregs[0] = 0; + } else { + env->regs[0] = 0; + } + return true; +} + +static void rk3588_create_scmi(RK3588MachineState *s) +{ + DeviceState *dev = qdev_new(TYPE_RK3588_SCMI); + SysBusDevice *sbd; + + object_property_add_child(OBJECT(s), "scmi", OBJECT(dev)); + sbd = SYS_BUS_DEVICE(dev); + sysbus_realize(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, rk3588_memmap[RK3588_SCMI_SHMEM].base); + + rk3588_scmi_dev = RK3588_SCMI(dev); + arm_register_psci_smc_handler(rk3588_smc_handler); +} + +static void rk3588_create_sdmmc(RK3588MachineState *s) +{ + DeviceState *dev = qdev_new(TYPE_DW_MMC); + SysBusDevice *sbd; + + object_property_add_child(OBJECT(s), "sdmmc", OBJECT(dev)); + sbd = SYS_BUS_DEVICE(dev); + sysbus_realize(sbd, &error_fatal); + /* + * sysbus mmio[0] is the core bank through the legacy FIFO data port at + * +0x200; mmio[1] is the remaining RAZ/WI tail (RK vendor + the rest of + * the window). Both are placed at the same base - they're contiguous + * sub-windows of the 0x4000 controller MMIO. + */ + sysbus_mmio_map(sbd, 0, rk3588_memmap[RK3588_SDMMC].base); + sysbus_mmio_map(sbd, 1, rk3588_memmap[RK3588_SDMMC].base + 0x204); + sysbus_connect_irq(sbd, 0, qdev_get_gpio_in(s->gic, RK3588_SDMMC_SPI)); + + rk3588_attach_sd_card(s, dev); +} + +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); + 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); + rk3588_create_syscon(s, "pmu2-ioc", RK3588_PMU2_IOC); + 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_init(MachineState *machine) +{ + RK3588MachineState *s = RK3588_MACHINE(machine); + const RK3588BoardConfig *board = s->board; + + if (machine->smp.cpus > RK3588_MAX_CPUS || + machine->smp.max_cpus > RK3588_MAX_CPUS) { + error_report("%s: at most %u CPUs are supported", + board->machine_name, RK3588_MAX_CPUS); + exit(EXIT_FAILURE); + } + + if (machine->ram_size > RK3588_MAX_RAM_SIZE) { + g_autofree char *sz = size_to_str(RK3588_MAX_RAM_SIZE); + error_report("%s: RAM size must not exceed %s", + board->machine_name, sz); + exit(EXIT_FAILURE); + } + + rk3588_create_cpus(s); + rk3588_create_low_memory(s); + rk3588_create_firmware_mmio(s); + memory_region_add_subregion(get_system_memory(), + rk3588_memmap[RK3588_RAM].base, + machine->ram); + rk3588_create_zvm_ram(s); + + rk3588_create_gic(s); + rk3588_create_its(s); + rk3588_create_syscon_devices(s); + rk3588_create_cru(s); + rk3588_create_stimer(s); + rk3588_create_scmi(s); + rk3588_active_machine = s; + rk3588_create_uart(s); + rk3588_create_sdhci(s); + rk3588_create_sdmmc(s); + rk3588_create_gpio(s); + rk3588_create_gmac(s); + rk3588_create_pcie(s); + + s->bootinfo = (struct arm_boot_info) { + .loader_start = rk3588_memmap[RK3588_RAM].base, + .board_id = -1, + .ram_size = machine->ram_size, + .psci_conduit = QEMU_PSCI_CONDUIT_SMC, + .get_dtb = rk3588_get_dtb, + }; + + rk3588_enable_psci_conduit(s); + + if (qtest_enabled()) { + return; + } + + if (machine->kernel_filename) { + arm_load_kernel(s->cpu[0], machine, &s->bootinfo); + } else { + Error *local_err = NULL; + + if (!rk3588_bootrom_prepare(s, &local_err)) { + error_report_err(local_err); + exit(EXIT_FAILURE); + } + s->firmware_boot = true; + rk3588_schedule_firmware_patch(s); + rk3588_register_firmware_reset(s); + } +} + +static void rk3588_machine_reset(MachineState *machine, ResetType type) +{ + qemu_devices_reset(type); + rk3588_boot_state_reset(RK3588_MACHINE(machine)); +} + +static const CPUArchIdList *rk3588_possible_cpu_arch_ids(MachineState *ms) +{ + unsigned int max_cpus = ms->smp.max_cpus; + + if (ms->possible_cpus) { + assert(ms->possible_cpus->len == max_cpus); + return ms->possible_cpus; + } + + ms->possible_cpus = g_malloc0(sizeof(CPUArchIdList) + + sizeof(CPUArchId) * max_cpus); + ms->possible_cpus->len = max_cpus; + + for (unsigned int n = 0; n < max_cpus; n++) { + ms->possible_cpus->cpus[n].type = rk3588_cpu_type(n); + ms->possible_cpus->cpus[n].arch_id = rk3588_cpu_mpidr[n]; + ms->possible_cpus->cpus[n].props.has_core_id = true; + ms->possible_cpus->cpus[n].props.core_id = n; + } + + return ms->possible_cpus; +} + +static CpuInstanceProperties +rk3588_cpu_index_to_props(MachineState *ms, unsigned cpu_index) +{ + MachineClass *mc = MACHINE_GET_CLASS(ms); + const CPUArchIdList *possible_cpus = mc->possible_cpu_arch_ids(ms); + + assert(cpu_index < possible_cpus->len); + return possible_cpus->cpus[cpu_index].props; +} + +static bool rk3588_get_zvm_ram(Object *obj, Error **errp) +{ + RK3588MachineState *s = RK3588_MACHINE(obj); + + return s->zvm_ram; +} + +static void rk3588_set_zvm_ram(Object *obj, bool value, Error **errp) +{ + RK3588MachineState *s = RK3588_MACHINE(obj); + + s->zvm_ram = value; +} + +static void rk3588_machine_instance_init(Object *obj, + const RK3588BoardConfig *board) +{ + RK3588MachineState *s = RK3588_MACHINE(obj); + + 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) +{ + MachineClass *mc = MACHINE_CLASS(oc); + + mc->desc = board->desc; + mc->init = rk3588_init; + mc->reset = rk3588_machine_reset; + mc->max_cpus = RK3588_MAX_CPUS; + mc->default_cpus = RK3588_MAX_CPUS; + mc->default_ram_size = 2 * GiB; + mc->default_ram_id = board->ram_id; + mc->possible_cpu_arch_ids = rk3588_possible_cpu_arch_ids; + mc->cpu_index_to_instance_props = rk3588_cpu_index_to_props; + + object_class_property_add_bool(oc, "zvm-ram", rk3588_get_zvm_ram, + rk3588_set_zvm_ram); + object_class_property_set_description(oc, "zvm-ram", + "Map RK3588 ZVM fixed guest and " + "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, + .instance_size = sizeof(RK3588MachineState), + .abstract = true, + .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/s32k5.c b/hw/arm/s32k5.c new file mode 100644 index 0000000000000..3d51a0f43d01c --- /dev/null +++ b/hw/arm/s32k5.c @@ -0,0 +1,259 @@ +/* + * NXP S32K5 board emulation + * + * This model starts with the S32K566 Cortex-R52 side of the S32K5XXCVB + * board. It is intentionally minimal and targets Zephyr boot smoke tests + * rather than full automotive SoC coverage. + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qapi/error.h" +#include "qobject/qlist.h" +#include "system/address-spaces.h" +#include "target/arm/cpu.h" +#include "system/system.h" +#include "hw/arm/boot.h" +#include "hw/arm/bsa.h" +#include "hw/arm/machines-qom.h" +#include "hw/char/nxp_lpuart.h" +#include "hw/core/boards.h" +#include "hw/core/qdev-properties.h" +#include "hw/intc/arm_gicv3.h" +#include "hw/misc/nxp_s32_mc_me.h" +#include "hw/misc/unimp.h" + +#define TYPE_S32K5_MACHINE MACHINE_TYPE_NAME("s32k5") +#define TYPE_S32K566_CVB_R52_MACHINE MACHINE_TYPE_NAME("s32k566-cvb-r52") + +#define S32K5_CPU_MAX 2 +#define S32K5_NUM_SPIS 320 + +#define S32K5_CODE_MRAM_BASE 0x08000000 +#define S32K5_CODE_MRAM_SIZE (32 * MiB + 0x100) +#define S32K5_SRAM0_BASE 0x21000000 +#define S32K5_SRAM1_BASE 0x21080000 +#define S32K5_SRAM2_BASE 0x21100000 +#define S32K5_SHARED_SRAM_SIZE (512 * KiB) +#define S32K5_CPE_SRAM_BASE 0x22000000 +#define S32K5_CPE_SRAM_SIZE (1 * MiB) +#define S32K5_MC_ME_BASE 0x40498000 +#define S32K5_LPUART0_BASE 0x400e0000 +#define S32K5_LPUART0_IRQ 144 +#define S32K5_GIC_DIST_BASE 0x43000000 +#define S32K5_GIC_REDIST_BASE 0x43100000 +#define S32K5_ARCH_TIMER_FREQ 4000000 + +struct S32K5MachineState { + MachineState parent; + + struct arm_boot_info bootinfo; + Object *cpu[S32K5_CPU_MAX]; + GICv3State gic; + NXPS32MCMEState mc_me; + NXPLPUARTState lpuart0; + MemoryRegion code_mram; + MemoryRegion sram0; + MemoryRegion sram1; + MemoryRegion sram2; +}; + +OBJECT_DECLARE_SIMPLE_TYPE(S32K5MachineState, S32K5_MACHINE) + +static void s32k5_create_unimplemented_devices(void) +{ + static const struct { + const char *name; + hwaddr base; + hwaddr size; + } regions[] = { + { "s32k5.clock-0", 0x42110000, 0x4000 }, + { "s32k5.clock-1", 0x42118000, 0x4000 }, + { "s32k5.clock-2", 0x4211c000, 0x4000 }, + { "s32k5.clock-3", 0x41074000, 0x4000 }, + { "s32k5.clock-4", 0x41078000, 0x4000 }, + { "s32k5.clock-5", 0x40094000, 0x4000 }, + { "s32k5.clock-6", 0x40098000, 0x4000 }, + { "s32k5.clock-7", 0x402fc000, 0x4000 }, + { "s32k5.clock-8", 0x404b8000, 0x4000 }, + { "s32k5.clock-9", 0x40b38000, 0x4000 }, + { "s32k5.clock-10", 0x40b3c000, 0x4000 }, + { "s32k5.clock-11", 0x40b40000, 0x4000 }, + { "s32k5.clock-12", 0x42120000, 0x4000 }, + { "s32k5.swt-startup", 0x404a8000, 0x4000 }, + { "s32k5.siul2-0", 0x40014000, 0x10000 }, + { "s32k5.siul2-1", 0x40204000, 0x10000 }, + { "s32k5.siul2-2", 0x4208c000, 0x10000 }, + { "s32k5.siul2-3", 0x40610000, 0x10000 }, + { "s32k5.siul2-4", 0x40804000, 0x10000 }, + { "s32k5.xspi0", 0x40ba4000, 0x8000 }, + { "s32k5.edma", 0x40410000, 0x4000 }, + }; + + for (int i = 0; i < ARRAY_SIZE(regions); i++) { + create_unimplemented_device(regions[i].name, regions[i].base, + regions[i].size); + } +} + +static void s32k5_create_gic(S32K5MachineState *sms, MemoryRegion *sysmem) +{ + MachineState *machine = MACHINE(sms); + DeviceState *gicdev; + QList *redist_region_count; + + object_initialize_child(OBJECT(sms), "gic", &sms->gic, TYPE_ARM_GICV3); + gicdev = DEVICE(&sms->gic); + qdev_prop_set_uint32(gicdev, "num-cpu", machine->smp.cpus); + qdev_prop_set_uint32(gicdev, "num-irq", S32K5_NUM_SPIS + GIC_INTERNAL); + redist_region_count = qlist_new(); + qlist_append_int(redist_region_count, machine->smp.cpus); + qdev_prop_set_array(gicdev, "redist-region-count", redist_region_count); + object_property_set_link(OBJECT(&sms->gic), "sysmem", + OBJECT(sysmem), &error_fatal); + sysbus_realize(SYS_BUS_DEVICE(&sms->gic), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(&sms->gic), 0, S32K5_GIC_DIST_BASE); + sysbus_mmio_map(SYS_BUS_DEVICE(&sms->gic), 1, S32K5_GIC_REDIST_BASE); + + for (int i = 0; i < machine->smp.cpus; i++) { + DeviceState *cpudev = DEVICE(sms->cpu[i]); + SysBusDevice *gicsbd = SYS_BUS_DEVICE(&sms->gic); + int intidbase = S32K5_NUM_SPIS + i * GIC_INTERNAL; + const int timer_irq[] = { + [GTIMER_PHYS] = ARCH_TIMER_NS_EL1_IRQ, + [GTIMER_VIRT] = ARCH_TIMER_VIRT_IRQ, + [GTIMER_HYP] = ARCH_TIMER_NS_EL2_IRQ, + [GTIMER_SEC] = ARCH_TIMER_S_EL1_IRQ, + }; + + for (int irq = 0; irq < ARRAY_SIZE(timer_irq); irq++) { + qdev_connect_gpio_out(cpudev, irq, + qdev_get_gpio_in(gicdev, + intidbase + timer_irq[irq])); + } + + qdev_connect_gpio_out_named(cpudev, "gicv3-maintenance-interrupt", 0, + qdev_get_gpio_in(gicdev, + intidbase + ARCH_GIC_MAINT_IRQ)); + qdev_connect_gpio_out_named(cpudev, "pmu-interrupt", 0, + qdev_get_gpio_in(gicdev, + intidbase + VIRTUAL_PMU_IRQ)); + + sysbus_connect_irq(gicsbd, i, + qdev_get_gpio_in(cpudev, ARM_CPU_IRQ)); + sysbus_connect_irq(gicsbd, i + machine->smp.cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_FIQ)); + sysbus_connect_irq(gicsbd, i + 2 * machine->smp.cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_VIRQ)); + sysbus_connect_irq(gicsbd, i + 3 * machine->smp.cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_VFIQ)); + } +} + +static void s32k5_common_init(MachineState *machine) +{ + S32K5MachineState *sms = S32K5_MACHINE(machine); + MemoryRegion *sysmem = get_system_memory(); + SysBusDevice *sbd; + + memory_region_init_ram(&sms->code_mram, NULL, "s32k5.code-mram", + S32K5_CODE_MRAM_SIZE, &error_fatal); + memory_region_set_readonly(&sms->code_mram, true); + memory_region_add_subregion(sysmem, S32K5_CODE_MRAM_BASE, + &sms->code_mram); + + memory_region_init_ram(&sms->sram0, NULL, "s32k5.sram0", + S32K5_SHARED_SRAM_SIZE, &error_fatal); + memory_region_add_subregion(sysmem, S32K5_SRAM0_BASE, &sms->sram0); + + memory_region_init_ram(&sms->sram1, NULL, "s32k5.sram1", + S32K5_SHARED_SRAM_SIZE, &error_fatal); + memory_region_add_subregion(sysmem, S32K5_SRAM1_BASE, &sms->sram1); + + memory_region_init_ram(&sms->sram2, NULL, "s32k5.sram2", + S32K5_SHARED_SRAM_SIZE, &error_fatal); + memory_region_add_subregion(sysmem, S32K5_SRAM2_BASE, &sms->sram2); + + memory_region_add_subregion(sysmem, S32K5_CPE_SRAM_BASE, machine->ram); + + assert(machine->smp.cpus <= S32K5_CPU_MAX); + for (int i = 0; i < machine->smp.cpus; i++) { + sms->cpu[i] = object_new(machine->cpu_type); + object_property_set_link(sms->cpu[i], "memory", OBJECT(sysmem), + &error_abort); + object_property_set_int(sms->cpu[i], "reset-cbar", + S32K5_GIC_DIST_BASE, &error_abort); + object_property_set_int(sms->cpu[i], "cntfrq", + S32K5_ARCH_TIMER_FREQ, &error_abort); + if (i > 0) { + object_property_set_bool(sms->cpu[i], "start-powered-off", true, + &error_abort); + } + qdev_realize(DEVICE(sms->cpu[i]), NULL, &error_fatal); + object_unref(sms->cpu[i]); + } + + s32k5_create_gic(sms, sysmem); + + object_initialize_child(OBJECT(sms), "mc-me", &sms->mc_me, + TYPE_NXP_S32_MC_ME); + sbd = SYS_BUS_DEVICE(&sms->mc_me); + sysbus_realize(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, S32K5_MC_ME_BASE); + + object_initialize_child(OBJECT(sms), "lpuart0", &sms->lpuart0, + TYPE_NXP_LPUART); + qdev_prop_set_chr(DEVICE(&sms->lpuart0), "chardev", serial_hd(0)); + sbd = SYS_BUS_DEVICE(&sms->lpuart0); + sysbus_realize(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, S32K5_LPUART0_BASE); + sysbus_connect_irq(sbd, 0, + qdev_get_gpio_in(DEVICE(&sms->gic), S32K5_LPUART0_IRQ)); + + s32k5_create_unimplemented_devices(); + + sms->bootinfo.ram_size = S32K5_CPE_SRAM_BASE + S32K5_CPE_SRAM_SIZE - + S32K5_CODE_MRAM_BASE; + sms->bootinfo.board_id = -1; + sms->bootinfo.loader_start = S32K5_CODE_MRAM_BASE; + arm_load_kernel(ARM_CPU(sms->cpu[0]), machine, &sms->bootinfo); +} + +static void s32k566_cvb_r52_class_init(ObjectClass *oc, const void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + static const char * const valid_cpu_types[] = { + ARM_CPU_TYPE_NAME("cortex-r52"), + NULL + }; + + mc->desc = "NXP S32K566 CVB Cortex-R52"; + mc->init = s32k5_common_init; + mc->default_cpus = 1; + mc->min_cpus = 1; + mc->max_cpus = S32K5_CPU_MAX; + mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-r52"); + mc->valid_cpu_types = valid_cpu_types; + mc->default_ram_size = S32K5_CPE_SRAM_SIZE; + mc->default_ram_id = "s32k5.cpe-sram"; +} + +static const TypeInfo s32k5_machine_types[] = { + { + .name = TYPE_S32K5_MACHINE, + .parent = TYPE_MACHINE, + .abstract = true, + .instance_size = sizeof(S32K5MachineState), + }, { + .name = TYPE_S32K566_CVB_R52_MACHINE, + .parent = TYPE_S32K5_MACHINE, + .class_init = s32k566_cvb_r52_class_init, + .interfaces = arm_machine_interfaces, + }, +}; + +DEFINE_TYPES(s32k5_machine_types); diff --git a/hw/char/Kconfig b/hw/char/Kconfig index 020c0a84bb6e5..e2aee672029da 100644 --- a/hw/char/Kconfig +++ b/hw/char/Kconfig @@ -51,6 +51,10 @@ config VIRTIO_SERIAL config MAX78000_UART bool +config NXP_LPUART + bool + select REGISTER + config STM32F2XX_USART bool @@ -78,6 +82,9 @@ config AVR_USART config DIVA_GSP bool +config DW_APB_UART_VENDOR + bool + config MCHP_PFSOC_MMUART bool select SERIAL @@ -95,3 +102,7 @@ config IP_OCTAL_232 bool default y depends on IPACK + +config DW8250 + bool + select SERIAL diff --git a/hw/char/dw-apb-uart-vendor.c b/hw/char/dw-apb-uart-vendor.c new file mode 100644 index 0000000000000..492ba91f63884 --- /dev/null +++ b/hw/char/dw-apb-uart-vendor.c @@ -0,0 +1,140 @@ +/* + * Synopsys DesignWare APB UART vendor register window + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * The 16550-compatible register block is modeled by serial-mm. This companion + * window covers the DesignWare APB UART extension registers that sit above the + * core 16550 area. Firmware commonly polls USR while transmitting early debug + * output, so report the transmit FIFO as available and not busy. + */ + +#include "qemu/osdep.h" +#include "hw/char/dw-apb-uart-vendor.h" +#include "migration/vmstate.h" +#include "qemu/bitops.h" +#include "qemu/bswap.h" +#include "qemu/module.h" + +#define DW_APB_UART_USR (0x7c - DW_APB_UART_VENDOR_BASE) +#define DW_APB_UART_USR_BUSY BIT(0) +#define DW_APB_UART_USR_TFNF BIT(1) +#define DW_APB_UART_USR_TFE BIT(2) + +static uint64_t dw_apb_uart_vendor_read(void *opaque, hwaddr offset, + unsigned size) +{ + DWAPBUARTVendorState *s = opaque; + + if (offset + size > DW_APB_UART_VENDOR_SIZE) { + return 0; + } + + if (offset == DW_APB_UART_USR && size == 4) { + uint32_t value = ldl_le_p(&s->regs[offset]); + + value &= ~DW_APB_UART_USR_BUSY; + value |= DW_APB_UART_USR_TFNF | DW_APB_UART_USR_TFE; + return value; + } + + switch (size) { + case 1: + return s->regs[offset]; + case 2: + return lduw_le_p(&s->regs[offset]); + case 4: + return ldl_le_p(&s->regs[offset]); + default: + return 0; + } +} + +static void dw_apb_uart_vendor_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + DWAPBUARTVendorState *s = opaque; + + if (offset + size > DW_APB_UART_VENDOR_SIZE) { + return; + } + + switch (size) { + case 1: + s->regs[offset] = value; + break; + case 2: + stw_le_p(&s->regs[offset], value); + break; + case 4: + stl_le_p(&s->regs[offset], value); + break; + } +} + +static const MemoryRegionOps dw_apb_uart_vendor_ops = { + .read = dw_apb_uart_vendor_read, + .write = dw_apb_uart_vendor_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +static void dw_apb_uart_vendor_reset(DeviceState *dev) +{ + DWAPBUARTVendorState *s = DW_APB_UART_VENDOR(dev); + + memset(s->regs, 0, sizeof(s->regs)); +} + +static void dw_apb_uart_vendor_realize(DeviceState *dev, Error **errp) +{ + DWAPBUARTVendorState *s = DW_APB_UART_VENDOR(dev); + + memory_region_init_io(&s->iomem, OBJECT(s), &dw_apb_uart_vendor_ops, s, + "dw-apb-uart-vendor", DW_APB_UART_VENDOR_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); +} + +static const VMStateDescription vmstate_dw_apb_uart_vendor = { + .name = TYPE_DW_APB_UART_VENDOR, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, DWAPBUARTVendorState, + DW_APB_UART_VENDOR_SIZE), + VMSTATE_END_OF_LIST() + }, +}; + +static void dw_apb_uart_vendor_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = dw_apb_uart_vendor_realize; + device_class_set_legacy_reset(dc, dw_apb_uart_vendor_reset); + dc->vmsd = &vmstate_dw_apb_uart_vendor; + dc->user_creatable = false; +} + +static const TypeInfo dw_apb_uart_vendor_info = { + .name = TYPE_DW_APB_UART_VENDOR, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(DWAPBUARTVendorState), + .class_init = dw_apb_uart_vendor_class_init, +}; + +static void dw_apb_uart_vendor_register_types(void) +{ + type_register_static(&dw_apb_uart_vendor_info); +} + +type_init(dw_apb_uart_vendor_register_types) diff --git a/hw/char/dw8250.c b/hw/char/dw8250.c new file mode 100644 index 0000000000000..4ec829ceafd39 --- /dev/null +++ b/hw/char/dw8250.c @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Synopsys DesignWare APB UART (DW 8250) + * + * Copyright (c) 2026 Kuan-Wei Chiu + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "hw/char/dw8250.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/qdev-properties-system.h" + +#define DW_UART_REGION_SIZE 0x100 + +#define DW_UART_RE_EN 0xB4 /* Receiver Output Enable Register */ +#define DW_UART_DLF 0xC0 /* Divisor Latch Fraction Register */ +#define DW_UART_CPR 0xF4 /* Component Parameter Register */ +#define DW_UART_UCV 0xF8 /* UART Component Version */ +#define DW_UART_CTR 0xFC /* Component Type Register */ + +#define DW_UART_UCV_VALUE 0x3332332A /* "323*" -> v3.23a */ +#define DW_UART_CTR_VALUE 0x44570110 /* "DW" */ + +static uint64_t dw8250_ext_read(void *opaque, hwaddr addr, unsigned int size) +{ + switch (addr) { + case DW_UART_UCV: + return DW_UART_UCV_VALUE; + case DW_UART_CPR: + return 0x00000000; /* No advanced features (DMA, extra FIFOs) */ + case DW_UART_CTR: + return DW_UART_CTR_VALUE; + + case DW_UART_RE_EN: + case DW_UART_DLF: + /* + * Return 0 to indicate these optional features + * (RS485 and Fractional Divisor) are not implemented. + */ + return 0x00000000; + + default: + return 0; + } +} + +static void dw8250_ext_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size) +{ +} + +static const MemoryRegionOps dw8250_ext_ops = { + .read = dw8250_ext_read, + .write = dw8250_ext_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, +}; + +static void dw8250_instance_init(Object *obj) +{ + DW8250State *s = DW8250(obj); + + s->serial_mm = qdev_new("serial-mm"); + object_property_add_child(obj, "serial-mm", OBJECT(s->serial_mm)); + object_property_add_alias(obj, "chardev", OBJECT(s->serial_mm), "chardev"); +} + +static void dw8250_realize(DeviceState *dev, Error **errp) +{ + DW8250State *s = DW8250(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + SysBusDevice *serial_sbd = SYS_BUS_DEVICE(s->serial_mm); + + memory_region_init(&s->container, OBJECT(dev), "dw8250-container", + DW_UART_REGION_SIZE); + sysbus_init_mmio(sbd, &s->container); + + qdev_prop_set_uint8(s->serial_mm, "regshift", s->regshift); + qdev_prop_set_uint8(s->serial_mm, "endianness", DEVICE_LITTLE_ENDIAN); + sysbus_realize(serial_sbd, errp); + + memory_region_init_io(&s->ext_iomem, OBJECT(dev), &dw8250_ext_ops, s, + "dw8250-ext", DW_UART_REGION_SIZE); + memory_region_add_subregion(&s->container, 0, &s->ext_iomem); + + memory_region_add_subregion_overlap(&s->container, 0, + sysbus_mmio_get_region(serial_sbd, 0), 1); + + sysbus_pass_irq(sbd, serial_sbd); +} + +static const Property dw8250_properties[] = { + DEFINE_PROP_UINT8("regshift", DW8250State, regshift, 2), +}; + +static void dw8250_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = dw8250_realize; + device_class_set_props(dc, dw8250_properties); +} + +static const TypeInfo dw8250_info = { + .name = TYPE_DW8250, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(DW8250State), + .instance_init = dw8250_instance_init, + .class_init = dw8250_class_init, +}; + +static void dw8250_register_types(void) +{ + type_register_static(&dw8250_info); +} + +type_init(dw8250_register_types) diff --git a/hw/char/meson.build b/hw/char/meson.build index fc3d7ee506fcf..8140fbc6d0ce3 100644 --- a/hw/char/meson.build +++ b/hw/char/meson.build @@ -26,8 +26,10 @@ system_ss.add(when: 'CONFIG_DIVA_GSP', if_true: files('diva-gsp.c')) system_ss.add(when: 'CONFIG_AVR_USART', if_true: files('avr_usart.c')) system_ss.add(when: 'CONFIG_COLDFIRE', if_true: files('mcf_uart.c')) system_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic-uart.c')) +system_ss.add(when: 'CONFIG_DW_APB_UART_VENDOR', if_true: files('dw-apb-uart-vendor.c')) system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_uart.c')) system_ss.add(when: 'CONFIG_MAX78000_UART', if_true: files('max78000_uart.c')) +system_ss.add(when: 'CONFIG_NXP_LPUART', if_true: files('nxp_lpuart.c')) system_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_uart.c')) system_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_aux.c')) system_ss.add(when: 'CONFIG_RENESAS_SCI', if_true: files('renesas_sci.c')) @@ -38,6 +40,7 @@ system_ss.add(when: 'CONFIG_STM32L4X5_USART', if_true: files('stm32l4x5_usart.c' system_ss.add(when: 'CONFIG_MCHP_PFSOC_MMUART', if_true: files('mchp_pfsoc_mmuart.c')) system_ss.add(when: 'CONFIG_HTIF', if_true: files('riscv_htif.c')) system_ss.add(when: 'CONFIG_GOLDFISH_TTY', if_true: files('goldfish_tty.c')) +system_ss.add(when: 'CONFIG_DW8250', if_true: files('dw8250.c')) specific_ss.add(when: 'CONFIG_TERMINAL3270', if_true: files('terminal3270.c')) specific_ss.add(when: 'CONFIG_PSERIES', if_true: files('spapr_vty.c')) diff --git a/hw/char/nxp_lpuart.c b/hw/char/nxp_lpuart.c new file mode 100644 index 0000000000000..b2c117a61fdac --- /dev/null +++ b/hw/char/nxp_lpuart.c @@ -0,0 +1,222 @@ +/* + * NXP Low Power UART + * + * This is a small model for firmware and RTOS console smoke tests. It + * implements the register subset used by Zephyr's MCUX LPUART driver when + * it initializes a UART and transmits through poll_out(). + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/char/nxp_lpuart.h" +#include "hw/core/irq.h" +#include "hw/core/registerfields.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/qdev-properties-system.h" +#include "migration/vmstate.h" +#include "qemu/module.h" + +REG32(VERID, 0x00) +REG32(PARAM, 0x04) +REG32(GLOBAL, 0x08) +REG32(PINCFG, 0x0c) +REG32(BAUD, 0x10) +REG32(STAT, 0x14) + FIELD(STAT, PF, 16, 1) + FIELD(STAT, FE, 17, 1) + FIELD(STAT, NF, 18, 1) + FIELD(STAT, OR, 19, 1) + FIELD(STAT, RDRF, 21, 1) + FIELD(STAT, TC, 22, 1) + FIELD(STAT, TDRE, 23, 1) +REG32(CTRL, 0x18) + FIELD(CTRL, RE, 18, 1) + FIELD(CTRL, TE, 19, 1) + FIELD(CTRL, RIE, 21, 1) + FIELD(CTRL, TCIE, 22, 1) + FIELD(CTRL, TIE, 23, 1) +REG32(DATA, 0x1c) + FIELD(DATA, DATA, 0, 8) +REG32(MATCH, 0x20) +REG32(MODIR, 0x24) +REG32(FIFO, 0x28) + FIELD(FIFO, RXEMPT, 22, 1) + FIELD(FIFO, TXEMPT, 23, 1) +REG32(WATER, 0x2c) + +#define LPUART_SIZE 0x4000 + +#define R_STAT_ERROR_MASK \ + (R_STAT_OR_MASK | R_STAT_NF_MASK | R_STAT_FE_MASK | R_STAT_PF_MASK) +#define R_STAT_TX_READY_MASK \ + (R_STAT_TDRE_MASK | R_STAT_TC_MASK) +#define R_FIFO_EMPTY_MASK \ + (R_FIFO_TXEMPT_MASK | R_FIFO_RXEMPT_MASK) + +static void nxp_lpuart_update_irq(NXPLPUARTState *s) +{ + uint32_t ctrl = s->regs[R_CTRL]; + uint32_t stat = s->regs[R_STAT]; + bool level = false; + + if ((ctrl & R_CTRL_TIE_MASK) && (stat & R_STAT_TDRE_MASK)) { + level = true; + } + + if ((ctrl & R_CTRL_TCIE_MASK) && (stat & R_STAT_TC_MASK)) { + level = true; + } + + if ((ctrl & R_CTRL_RIE_MASK) && (stat & R_STAT_RDRF_MASK)) { + level = true; + } + + qemu_set_irq(s->irq, level); +} + +static void nxp_lpuart_stat_post_write(RegisterInfo *reg, uint64_t val) +{ + NXPLPUARTState *s = NXP_LPUART(reg->opaque); + + nxp_lpuart_update_irq(s); +} + +static void nxp_lpuart_ctrl_post_write(RegisterInfo *reg, uint64_t val) +{ + NXPLPUARTState *s = NXP_LPUART(reg->opaque); + + nxp_lpuart_update_irq(s); +} + +static void nxp_lpuart_data_post_write(RegisterInfo *reg, uint64_t val) +{ + NXPLPUARTState *s = NXP_LPUART(reg->opaque); + uint8_t ch = val & R_DATA_DATA_MASK; + + if (!device_is_in_reset(DEVICE(s)) && + qemu_chr_fe_backend_connected(&s->chr)) { + qemu_chr_fe_write_all(&s->chr, &ch, 1); + } + + s->regs[R_STAT] |= R_STAT_TX_READY_MASK; + nxp_lpuart_update_irq(s); +} + +static uint64_t nxp_lpuart_data_post_read(RegisterInfo *reg, uint64_t val) +{ + NXPLPUARTState *s = NXP_LPUART(reg->opaque); + + s->regs[R_STAT] &= ~R_STAT_RDRF_MASK; + nxp_lpuart_update_irq(s); + + return val; +} + +static const RegisterAccessInfo nxp_lpuart_regs_info[] = { + { .name = "VERID", .addr = A_VERID, + .reset = 0x04010003, + .ro = UINT32_MAX, + },{ .name = "PARAM", .addr = A_PARAM, + .ro = UINT32_MAX, + },{ .name = "GLOBAL", .addr = A_GLOBAL, + },{ .name = "PINCFG", .addr = A_PINCFG, + },{ .name = "BAUD", .addr = A_BAUD, + .reset = 0x0f000004, + },{ .name = "STAT", .addr = A_STAT, + .reset = R_STAT_TX_READY_MASK, + .ro = R_STAT_TX_READY_MASK | R_STAT_RDRF_MASK, + .w1c = R_STAT_ERROR_MASK, + .post_write = nxp_lpuart_stat_post_write, + },{ .name = "CTRL", .addr = A_CTRL, + .post_write = nxp_lpuart_ctrl_post_write, + },{ .name = "DATA", .addr = A_DATA, + .post_write = nxp_lpuart_data_post_write, + .post_read = nxp_lpuart_data_post_read, + },{ .name = "MATCH", .addr = A_MATCH, + },{ .name = "MODIR", .addr = A_MODIR, + },{ .name = "FIFO", .addr = A_FIFO, + .reset = R_FIFO_EMPTY_MASK, + .ro = R_FIFO_EMPTY_MASK, + },{ .name = "WATER", .addr = A_WATER, + } +}; + +static const MemoryRegionOps nxp_lpuart_ops = { + .read = register_read_memory, + .write = register_write_memory, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +static void nxp_lpuart_reset(DeviceState *dev) +{ + NXPLPUARTState *s = NXP_LPUART(dev); + + for (int i = 0; i < ARRAY_SIZE(s->regs_info); i++) { + register_reset(&s->regs_info[i]); + } + + nxp_lpuart_update_irq(s); +} + +static void nxp_lpuart_init(Object *obj) +{ + NXPLPUARTState *s = NXP_LPUART(obj); + DeviceState *dev = DEVICE(obj); + + s->reg_array = register_init_block32(dev, nxp_lpuart_regs_info, + ARRAY_SIZE(nxp_lpuart_regs_info), + s->regs_info, s->regs, + &nxp_lpuart_ops, false, + LPUART_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->reg_array->mem); + sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq); +} + +static const VMStateDescription vmstate_nxp_lpuart = { + .name = TYPE_NXP_LPUART, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, NXPLPUARTState, NXP_LPUART_R_MAX), + VMSTATE_END_OF_LIST() + } +}; + +static const Property nxp_lpuart_properties[] = { + DEFINE_PROP_CHR("chardev", NXPLPUARTState, chr), +}; + +static void nxp_lpuart_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, nxp_lpuart_reset); + dc->vmsd = &vmstate_nxp_lpuart; + device_class_set_props(dc, nxp_lpuart_properties); +} + +static const TypeInfo nxp_lpuart_info = { + .name = TYPE_NXP_LPUART, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NXPLPUARTState), + .instance_init = nxp_lpuart_init, + .class_init = nxp_lpuart_class_init, +}; + +static void nxp_lpuart_register_types(void) +{ + type_register_static(&nxp_lpuart_info); +} + +type_init(nxp_lpuart_register_types) diff --git a/hw/display/Kconfig b/hw/display/Kconfig index b3593fe981a92..6d4f498397045 100644 --- a/hw/display/Kconfig +++ b/hw/display/Kconfig @@ -29,6 +29,10 @@ config IMX6UL_LCDIF bool select FRAMEBUFFER +config K230_DISPLAY + bool + select FRAMEBUFFER + config SII9022 bool depends on I2C diff --git a/hw/display/k230_display.c b/hw/display/k230_display.c new file mode 100644 index 0000000000000..1c201d42603d5 --- /dev/null +++ b/hw/display/k230_display.c @@ -0,0 +1,647 @@ +/* + * K230 display controller register blocks + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "qemu/bitops.h" +#include "hw/core/qdev-properties.h" +#include "hw/display/k230_display.h" +#include "hw/display/framebuffer.h" +#include "migration/vmstate.h" +#include "system/address-spaces.h" +#include "trace.h" +#include "ui/pixel_ops.h" + +#define K230_VBLANK_NS 16666667 + +#define K230_VO_DEFAULT_WIDTH 640 +#define K230_VO_DEFAULT_HEIGHT 480 + +#define K230_VO_FBDEV_COMPAT_BASE 0x13100000 +#define K230_VO_FBDEV_COMPAT_WIDTH 1920 +#define K230_VO_FBDEV_COMPAT_HEIGHT 1080 +#define K230_VO_FBDEV_COMPAT_STRIDE \ + (K230_VO_FBDEV_COMPAT_WIDTH * sizeof(uint32_t)) + +#define K230_VO_DISP_ENABLE 0x118 + +#define K230_VO_OSD0_BASE 0x280 +#define K230_VO_OSD1_BASE 0x2c0 +#define K230_VO_OSD2_BASE 0x300 +#define K230_VO_OSD3_BASE 0x850 +#define K230_VO_OSD4_BASE 0x880 +#define K230_VO_OSD5_BASE 0x8b0 +#define K230_VO_OSD6_BASE 0x8e0 +#define K230_VO_OSD7_BASE 0x910 + +#define K230_VO_OSD_INFO 0x00 +#define K230_VO_OSD_SIZE 0x04 +#define K230_VO_OSD_VLU_ADDR0 0x08 +#define K230_VO_OSD_STRIDE 0x1c + +#define K230_VO_OSD0_ENABLE BIT(4) +#define K230_VO_OSD1_ENABLE BIT(5) +#define K230_VO_OSD2_ENABLE BIT(6) +#define K230_VO_OSD3_ENABLE BIT(7) +#define K230_VO_OSD4_ENABLE BIT(8) +#define K230_VO_OSD5_ENABLE BIT(9) +#define K230_VO_OSD6_ENABLE BIT(10) +#define K230_VO_OSD7_ENABLE BIT(11) + +#define K230_VO_OSD_FMT_RGB888 0x00 +#define K230_VO_OSD_FMT_RGB565 0x02 +#define K230_VO_OSD_FMT_ARGB8888 0x53 +#define K230_VO_OSD_FMT_ARGB4444 0x54 +#define K230_VO_OSD_FMT_ARGB1555 0x55 + +#define K230_DSI_CMD_STATUS 0x0b0 +#define K230_DSI_PHY_STATUS 0x0b8 + +#define K230_DSI_CMD_STATUS_READY 0x1fbd +#define K230_DSI_PHY_STATUS_READY 0x0580 + +typedef struct K230VoPlaneConfig { + uint32_t base; + uint32_t enable_mask; +} K230VoPlaneConfig; + +typedef struct K230VoScanout { + hwaddr base; + uint32_t width; + uint32_t height; + uint32_t stride; + drawfn draw_line; +} K230VoScanout; + +static const K230VoPlaneConfig k230_vo_osd_planes[] = { + { K230_VO_OSD4_BASE, K230_VO_OSD4_ENABLE }, + { K230_VO_OSD5_BASE, K230_VO_OSD5_ENABLE }, + { K230_VO_OSD6_BASE, K230_VO_OSD6_ENABLE }, + { K230_VO_OSD7_BASE, K230_VO_OSD7_ENABLE }, + { K230_VO_OSD0_BASE, K230_VO_OSD0_ENABLE }, + { K230_VO_OSD1_BASE, K230_VO_OSD1_ENABLE }, + { K230_VO_OSD2_BASE, K230_VO_OSD2_ENABLE }, + { K230_VO_OSD3_BASE, K230_VO_OSD3_ENABLE }, +}; + +static uint64_t k230_display_read_bytes(uint8_t *regs, size_t limit, + hwaddr addr, unsigned int size) +{ + uint64_t val = 0; + + if (addr >= limit || size > limit - addr) { + return 0; + } + + for (int i = 0; i < size; i++) { + val |= (uint64_t)regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_display_write_bytes(uint8_t *regs, size_t limit, + hwaddr addr, uint64_t val, + unsigned int size) +{ + if (addr >= limit || size > limit - addr) { + return; + } + + for (int i = 0; i < size; i++) { + regs[addr + i] = val >> (i * 8); + } +} + +static uint32_t k230_display_readl(uint8_t *regs, size_t limit, hwaddr addr) +{ + if (addr >= limit || sizeof(uint32_t) > limit - addr) { + return 0; + } + + return ldl_le_p(regs + addr); +} + +static void k230_vo_draw_line_xrgb8888(void *opaque, uint8_t *dst, + const uint8_t *src, int width, + int dststep) +{ + uint32_t *dst32 = (uint32_t *)dst; + + for (int i = 0; i < width; i++) { + uint32_t pixel = ldl_le_p(src); + uint8_t r = (pixel >> 16) & 0xff; + uint8_t g = (pixel >> 8) & 0xff; + uint8_t b = pixel & 0xff; + + *dst32++ = rgb_to_pixel32(r, g, b); + src += 4; + } +} + +static void k230_vo_draw_line_argb8888(void *opaque, uint8_t *dst, + const uint8_t *src, int width, + int dststep) +{ + k230_vo_draw_line_xrgb8888(opaque, dst, src, width, dststep); +} + +static void k230_vo_draw_line_argb4444(void *opaque, uint8_t *dst, + const uint8_t *src, int width, + int dststep) +{ + uint32_t *dst32 = (uint32_t *)dst; + + for (int i = 0; i < width; i++) { + uint16_t pixel = lduw_le_p(src); + uint8_t r = ((pixel >> 8) & 0xf) * 0x11; + uint8_t g = ((pixel >> 4) & 0xf) * 0x11; + uint8_t b = (pixel & 0xf) * 0x11; + + *dst32++ = rgb_to_pixel32(r, g, b); + src += 2; + } +} + +static void k230_vo_draw_line_argb1555(void *opaque, uint8_t *dst, + const uint8_t *src, int width, + int dststep) +{ + uint32_t *dst32 = (uint32_t *)dst; + + for (int i = 0; i < width; i++) { + uint16_t pixel = lduw_le_p(src); + uint8_t r = ((pixel >> 10) & 0x1f) << 3; + uint8_t g = ((pixel >> 5) & 0x1f) << 3; + uint8_t b = (pixel & 0x1f) << 3; + + *dst32++ = rgb_to_pixel32(r, g, b); + src += 2; + } +} + +static void k230_vo_draw_line_rgb888(void *opaque, uint8_t *dst, + const uint8_t *src, int width, + int dststep) +{ + uint32_t *dst32 = (uint32_t *)dst; + + for (int i = 0; i < width; i++) { + *dst32++ = rgb_to_pixel32(src[0], src[1], src[2]); + src += 3; + } +} + +static void k230_vo_draw_line_rgb565(void *opaque, uint8_t *dst, + const uint8_t *src, int width, + int dststep) +{ + uint32_t *dst32 = (uint32_t *)dst; + + for (int i = 0; i < width; i++) { + uint16_t pixel = lduw_le_p(src); + uint8_t r = ((pixel >> 11) & 0x1f) << 3; + uint8_t g = ((pixel >> 5) & 0x3f) << 2; + uint8_t b = (pixel & 0x1f) << 3; + + *dst32++ = rgb_to_pixel32(r, g, b); + src += 2; + } +} + +static bool k230_vo_decode_osd_format(uint32_t info, drawfn *draw_line, + uint32_t *bytes_per_pixel) +{ + switch (info & 0xff) { + case K230_VO_OSD_FMT_ARGB8888: + *draw_line = k230_vo_draw_line_argb8888; + *bytes_per_pixel = 4; + return true; + case K230_VO_OSD_FMT_ARGB4444: + *draw_line = k230_vo_draw_line_argb4444; + *bytes_per_pixel = 2; + return true; + case K230_VO_OSD_FMT_ARGB1555: + *draw_line = k230_vo_draw_line_argb1555; + *bytes_per_pixel = 2; + return true; + case K230_VO_OSD_FMT_RGB888: + *draw_line = k230_vo_draw_line_rgb888; + *bytes_per_pixel = 3; + return true; + case K230_VO_OSD_FMT_RGB565: + *draw_line = k230_vo_draw_line_rgb565; + *bytes_per_pixel = 2; + return true; + default: + return false; + } +} + +static bool k230_vo_get_fbdev_compat_scanout(K230VoState *s, + K230VoScanout *scanout) +{ + if (!s->fbdev_compat || !s->fbdev_base || + !s->fbdev_width || !s->fbdev_height) { + return false; + } + + scanout->base = s->fbdev_base; + scanout->width = s->fbdev_width; + scanout->height = s->fbdev_height; + scanout->stride = s->fbdev_stride; + if (!scanout->stride) { + scanout->stride = scanout->width * sizeof(uint32_t); + } + if (scanout->stride < scanout->width * sizeof(uint32_t)) { + return false; + } + + scanout->draw_line = k230_vo_draw_line_xrgb8888; + return true; +} + +static bool k230_vo_get_osd_scanout(K230VoState *s, K230VoScanout *scanout) +{ + uint32_t disp_en = k230_display_readl(s->regs, sizeof(s->regs), + K230_VO_DISP_ENABLE); + + for (size_t i = 0; i < ARRAY_SIZE(k230_vo_osd_planes); i++) { + const K230VoPlaneConfig *plane = &k230_vo_osd_planes[i]; + uint32_t size; + uint32_t info; + uint32_t stride_reg; + uint32_t bytes_per_pixel; + + if (!(disp_en & plane->enable_mask)) { + continue; + } + + info = k230_display_readl(s->regs, sizeof(s->regs), + plane->base + K230_VO_OSD_INFO); + if (!k230_vo_decode_osd_format(info, &scanout->draw_line, + &bytes_per_pixel)) { + continue; + } + + size = k230_display_readl(s->regs, sizeof(s->regs), + plane->base + K230_VO_OSD_SIZE); + scanout->width = size & 0xffff; + scanout->height = size >> 16; + if (!scanout->width || !scanout->height || + scanout->width > 4096 || scanout->height > 4096) { + continue; + } + + stride_reg = k230_display_readl(s->regs, sizeof(s->regs), + plane->base + K230_VO_OSD_STRIDE); + scanout->stride = stride_reg * 8; + if (scanout->stride < scanout->width * bytes_per_pixel) { + scanout->stride = scanout->width * bytes_per_pixel; + } + + scanout->base = k230_display_readl(s->regs, sizeof(s->regs), + plane->base + + K230_VO_OSD_VLU_ADDR0); + if (!scanout->base) { + continue; + } + + return true; + } + + return false; +} + +static bool k230_vo_get_scanout(K230VoState *s, K230VoScanout *scanout) +{ + if (k230_vo_get_osd_scanout(s, scanout)) { + return true; + } + + return k230_vo_get_fbdev_compat_scanout(s, scanout); +} + +static bool k230_vo_update_display(void *opaque) +{ + K230VoState *s = K230_VO(opaque); + DisplaySurface *surface = qemu_console_surface(s->con); + K230VoScanout scanout = { 0 }; + int first = 0; + int last = 0; + + if (!k230_vo_get_scanout(s, &scanout)) { + return true; + } + + if (surface_width(surface) != scanout.width || + surface_height(surface) != scanout.height) { + qemu_console_resize(s->con, scanout.width, scanout.height); + surface = qemu_console_surface(s->con); + s->invalidate = true; + } + + if (s->invalidate || s->fb_base != scanout.base || + s->src_width != scanout.stride || s->rows != scanout.height) { + framebuffer_update_memory_section(&s->fbsection, get_system_memory(), + scanout.base, scanout.height, + scanout.stride); + s->fb_base = scanout.base; + s->src_width = scanout.stride; + s->cols = scanout.width; + s->rows = scanout.height; + } + + framebuffer_update_display(surface, &s->fbsection, scanout.width, + scanout.height, scanout.stride, + surface_stride(surface), 0, s->invalidate, + scanout.draw_line, s, &first, &last); + if (first >= 0) { + qemu_console_update(s->con, 0, first, scanout.width, + last - first + 1); + } + + s->invalidate = false; + return true; +} + +static void k230_vo_invalidate_display(void *opaque) +{ + K230VoState *s = K230_VO(opaque); + + s->invalidate = true; +} + +static const GraphicHwOps k230_vo_gfx_ops = { + .invalidate = k230_vo_invalidate_display, + .gfx_update = k230_vo_update_display, +}; + +static void k230_vo_vblank_tick(void *opaque) +{ + K230VoState *s = K230_VO(opaque); + + if (s->con) { + qemu_console_hw_update(s->con); + } + + if (s->irq) { + trace_k230_vo_irq(true); + qemu_irq_pulse(s->irq); + } + + timer_mod(s->vblank_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + K230_VBLANK_NS); +} + +static uint64_t k230_vo_read(void *opaque, hwaddr addr, unsigned int size) +{ + K230VoState *s = K230_VO(opaque); + uint64_t val; + + val = k230_display_read_bytes(s->regs, sizeof(s->regs), addr, size); + trace_k230_vo_read(addr, val, size); + + return val; +} + +static void k230_vo_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230VoState *s = K230_VO(opaque); + + k230_display_write_bytes(s->regs, sizeof(s->regs), addr, val, size); + trace_k230_vo_write(addr, val, size); + s->invalidate = true; +} + +static const MemoryRegionOps k230_vo_ops = { + .read = k230_vo_read, + .write = k230_vo_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_vo_reset(DeviceState *dev) +{ + K230VoState *s = K230_VO(dev); + + memset(s->regs, 0, sizeof(s->regs)); + s->fb_base = 0; + s->src_width = 0; + s->cols = K230_VO_DEFAULT_WIDTH; + s->rows = K230_VO_DEFAULT_HEIGHT; + s->invalidate = true; + if (s->con) { + qemu_console_resize(s->con, K230_VO_DEFAULT_WIDTH, + K230_VO_DEFAULT_HEIGHT); + } + timer_mod(s->vblank_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + K230_VBLANK_NS); +} + +static const VMStateDescription vmstate_k230_vo = { + .name = TYPE_K230_VO, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, K230VoState, K230_VO_SIZE), + VMSTATE_UINT64(fb_base, K230VoState), + VMSTATE_UINT64(fbdev_base, K230VoState), + VMSTATE_UINT32(fbdev_width, K230VoState), + VMSTATE_UINT32(fbdev_height, K230VoState), + VMSTATE_UINT32(fbdev_stride, K230VoState), + VMSTATE_UINT32(src_width, K230VoState), + VMSTATE_UINT32(cols, K230VoState), + VMSTATE_UINT32(rows, K230VoState), + VMSTATE_BOOL(fbdev_compat, K230VoState), + VMSTATE_BOOL(invalidate, K230VoState), + VMSTATE_TIMER_PTR(vblank_timer, K230VoState), + VMSTATE_END_OF_LIST(), + }, +}; + +static const Property k230_vo_properties[] = { + DEFINE_PROP_BOOL("fbdev-compat", K230VoState, fbdev_compat, true), + DEFINE_PROP_UINT64("fbdev-base", K230VoState, fbdev_base, + K230_VO_FBDEV_COMPAT_BASE), + DEFINE_PROP_UINT32("fbdev-width", K230VoState, fbdev_width, + K230_VO_FBDEV_COMPAT_WIDTH), + DEFINE_PROP_UINT32("fbdev-height", K230VoState, fbdev_height, + K230_VO_FBDEV_COMPAT_HEIGHT), + DEFINE_PROP_UINT32("fbdev-stride", K230VoState, fbdev_stride, + K230_VO_FBDEV_COMPAT_STRIDE), +}; + +static void k230_vo_init(Object *obj) +{ + K230VoState *s = K230_VO(obj); + + memory_region_init_io(&s->mmio, obj, &k230_vo_ops, s, + TYPE_K230_VO, K230_VO_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); + sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq); + s->vblank_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, + k230_vo_vblank_tick, s); +} + +static void k230_vo_realize(DeviceState *dev, Error **errp) +{ + K230VoState *s = K230_VO(dev); + + s->con = qemu_graphic_console_create(dev, 0, &k230_vo_gfx_ops, s); + qemu_console_resize(s->con, K230_VO_DEFAULT_WIDTH, + K230_VO_DEFAULT_HEIGHT); +} + +static void k230_vo_unrealize(DeviceState *dev) +{ + K230VoState *s = K230_VO(dev); + + if (s->con) { + qemu_graphic_console_close(s->con); + s->con = NULL; + } +} + +static void k230_vo_finalize(Object *obj) +{ + K230VoState *s = K230_VO(obj); + + if (s->fbsection.mr) { + memory_region_set_log(s->fbsection.mr, false, DIRTY_MEMORY_VGA); + memory_region_unref(s->fbsection.mr); + s->fbsection.mr = NULL; + } + timer_free(s->vblank_timer); +} + +static void k230_vo_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + device_class_set_legacy_reset(dc, k230_vo_reset); + dc->realize = k230_vo_realize; + dc->unrealize = k230_vo_unrealize; + dc->vmsd = &vmstate_k230_vo; + device_class_set_props(dc, k230_vo_properties); + dc->desc = "K230 video output registers"; +} + +static const TypeInfo k230_vo_type_info = { + .name = TYPE_K230_VO, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230VoState), + .instance_init = k230_vo_init, + .instance_finalize = k230_vo_finalize, + .class_init = k230_vo_class_init, +}; + +static uint64_t k230_dsi_read(void *opaque, hwaddr addr, unsigned int size) +{ + K230DsiState *s = K230_DSI(opaque); + uint64_t val; + + switch (addr) { + case K230_DSI_CMD_STATUS: + val = K230_DSI_CMD_STATUS_READY; + break; + case K230_DSI_PHY_STATUS: + val = K230_DSI_PHY_STATUS_READY; + break; + default: + val = k230_display_read_bytes(s->regs, sizeof(s->regs), addr, size); + break; + } + trace_k230_dsi_read(addr, val, size); + + return val; +} + +static void k230_dsi_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230DsiState *s = K230_DSI(opaque); + + k230_display_write_bytes(s->regs, sizeof(s->regs), addr, val, size); + trace_k230_dsi_write(addr, val, size); +} + +static const MemoryRegionOps k230_dsi_ops = { + .read = k230_dsi_read, + .write = k230_dsi_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_dsi_reset(DeviceState *dev) +{ + K230DsiState *s = K230_DSI(dev); + + memset(s->regs, 0, sizeof(s->regs)); +} + +static const VMStateDescription vmstate_k230_dsi = { + .name = TYPE_K230_DSI, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, K230DsiState, K230_DSI_SIZE), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_dsi_init(Object *obj) +{ + K230DsiState *s = K230_DSI(obj); + + memory_region_init_io(&s->mmio, obj, &k230_dsi_ops, s, + TYPE_K230_DSI, K230_DSI_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); +} + +static void k230_dsi_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + device_class_set_legacy_reset(dc, k230_dsi_reset); + dc->vmsd = &vmstate_k230_dsi; + dc->desc = "K230 MIPI DSI registers"; +} + +static const TypeInfo k230_dsi_type_info = { + .name = TYPE_K230_DSI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230DsiState), + .instance_init = k230_dsi_init, + .class_init = k230_dsi_class_init, +}; + +static void k230_display_register_types(void) +{ + type_register_static(&k230_vo_type_info); + type_register_static(&k230_dsi_type_info); +} + +type_init(k230_display_register_types) diff --git a/hw/display/meson.build b/hw/display/meson.build index ffecedbf709e0..89f573ca4fb03 100644 --- a/hw/display/meson.build +++ b/hw/display/meson.build @@ -13,6 +13,7 @@ system_ss.add(when: 'CONFIG_G364FB', if_true: files('g364fb.c')) system_ss.add(when: 'CONFIG_JAZZ_LED', if_true: files('jazz_led.c')) system_ss.add(when: 'CONFIG_PL110', if_true: files('pl110.c')) system_ss.add(when: 'CONFIG_IMX6UL_LCDIF', if_true: files('imx6ul_lcdif.c')) +system_ss.add(when: 'CONFIG_K230_DISPLAY', if_true: files('k230_display.c')) system_ss.add(when: 'CONFIG_SII9022', if_true: files('sii9022.c')) system_ss.add(when: 'CONFIG_SSD0303', if_true: files('ssd0303.c')) system_ss.add(when: 'CONFIG_SSD0323', if_true: files('ssd0323.c')) diff --git a/hw/display/trace-events b/hw/display/trace-events index 4bfc457fbac1b..9f7e52c2cdd27 100644 --- a/hw/display/trace-events +++ b/hw/display/trace-events @@ -13,6 +13,13 @@ xenfb_input_connected(void *xendev, int abs_pointer_wanted) "%p abs %d" g364fb_read(uint64_t addr, uint32_t val) "read addr=0x%"PRIx64": 0x%x" g364fb_write(uint64_t addr, uint32_t new) "write addr=0x%"PRIx64": 0x%x" +# k230_display.c +k230_vo_read(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +k230_vo_write(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +k230_vo_irq(bool level) "level %d" +k230_dsi_read(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +k230_dsi_write(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" + # vmware_vga.c vmware_value_read(uint32_t index, uint32_t value) "index %d, value 0x%x" vmware_value_write(uint32_t index, uint32_t value) "index %d, value 0x%x" @@ -225,4 +232,3 @@ apple_gfx_iosfc_map_memory_new_region(size_t i, void *region, uint64_t start, ui apple_gfx_iosfc_unmap_memory(void *a, void *b, void *c, void *d, void *e, void *f) "a=%p b=%p c=%p d=%p e=%p f=%p" apple_gfx_iosfc_unmap_memory_region(void* mem, void *region) "unmapping @ %p from memory region %p" apple_gfx_iosfc_raise_irq(uint32_t vector) "vector=0x%x" - diff --git a/hw/dma/Kconfig b/hw/dma/Kconfig index 98fbb1bb049ab..c03c730f65e03 100644 --- a/hw/dma/Kconfig +++ b/hw/dma/Kconfig @@ -30,3 +30,10 @@ config SIFIVE_PDMA config XLNX_CSU_DMA bool select REGISTER + +config K230_GSDMA + bool + +config K230_PDMA + bool + select REGISTER diff --git a/hw/dma/k230_gsdma.c b/hw/dma/k230_gsdma.c new file mode 100644 index 0000000000000..1b26f03142bad --- /dev/null +++ b/hw/dma/k230_gsdma.c @@ -0,0 +1,196 @@ +/* + * K230 GSDMA controller + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/module.h" +#include "migration/vmstate.h" +#include "hw/core/irq.h" +#include "hw/dma/k230_gsdma.h" + +#define K230_GSDMA_INT_STAT 0x08 +#define K230_GSDMA_CH_BASE 0x50 +#define K230_GSDMA_CH_STRIDE 0x30 +#define K230_GSDMA_CH_CTL 0x00 +#define K230_GSDMA_CH_STATUS 0x04 +#define K230_GSDMA_CH_LLT_SADDR 0x10 +#define K230_GSDMA_CH_COUNT 4 +#define K230_GSDMA_CH_BUSY BIT(0) +#define K230_GSDMA_UGZIP_WR_DONE 0x222 + +static uint64_t k230_gsdma_read_bytes(uint8_t *regs, hwaddr addr, + unsigned int size) +{ + uint64_t val = 0; + + for (int i = 0; i < size; i++) { + val |= (uint64_t)regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_gsdma_write_bytes(uint8_t *regs, hwaddr addr, uint64_t val, + unsigned int size) +{ + for (int i = 0; i < size; i++) { + regs[addr + i] = val >> (i * 8); + } +} + +static uint32_t k230_gsdma_reg_read32(K230GsdmaState *s, hwaddr addr) +{ + return ldl_le_p(s->regs + addr); +} + +static void k230_gsdma_reg_write32(K230GsdmaState *s, hwaddr addr, + uint32_t val) +{ + stl_le_p(s->regs + addr, val); +} + +static hwaddr k230_gsdma_ch_addr(unsigned int ch, hwaddr reg) +{ + return K230_GSDMA_CH_BASE + ch * K230_GSDMA_CH_STRIDE + reg; +} + +static void k230_gsdma_update_irq(K230GsdmaState *s) +{ + qemu_set_irq(s->irq, k230_gsdma_reg_read32(s, K230_GSDMA_INT_STAT) != 0); +} + +uint32_t k230_gsdma_get_llt_saddr(K230GsdmaState *s, unsigned int ch) +{ + assert(ch < K230_GSDMA_CH_COUNT); + + return k230_gsdma_reg_read32(s, + k230_gsdma_ch_addr(ch, K230_GSDMA_CH_LLT_SADDR)); +} + +void k230_gsdma_ugzip_complete(K230GsdmaState *s) +{ + k230_gsdma_reg_write32(s, + k230_gsdma_ch_addr(K230_GSDMA_UGZIP_RD_CH, K230_GSDMA_CH_STATUS), 0); + k230_gsdma_reg_write32(s, + k230_gsdma_ch_addr(K230_GSDMA_UGZIP_WR_CH, K230_GSDMA_CH_STATUS), 0); + k230_gsdma_reg_write32(s, K230_GSDMA_INT_STAT, + k230_gsdma_reg_read32(s, K230_GSDMA_INT_STAT) | + K230_GSDMA_UGZIP_WR_DONE); + k230_gsdma_update_irq(s); +} + +static uint64_t k230_gsdma_read(void *opaque, hwaddr addr, unsigned int size) +{ + return k230_gsdma_read_bytes(K230_GSDMA(opaque)->regs, addr, size); +} + +static void k230_gsdma_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230GsdmaState *s = K230_GSDMA(opaque); + + if (addr == K230_GSDMA_INT_STAT && size == 4) { + uint32_t int_stat = k230_gsdma_reg_read32(s, K230_GSDMA_INT_STAT); + + k230_gsdma_reg_write32(s, K230_GSDMA_INT_STAT, + int_stat & ~(uint32_t)val); + k230_gsdma_update_irq(s); + return; + } + + k230_gsdma_write_bytes(s->regs, addr, val, size); + + if (size == 4) { + for (int ch = 0; ch < K230_GSDMA_CH_COUNT; ch++) { + hwaddr ctl = k230_gsdma_ch_addr(ch, K230_GSDMA_CH_CTL); + + if (addr == ctl) { + hwaddr status = k230_gsdma_ch_addr(ch, K230_GSDMA_CH_STATUS); + + if (val & BIT(1)) { + k230_gsdma_reg_write32(s, status, 0); + } else if (val & BIT(0)) { + k230_gsdma_reg_write32(s, status, 0); + k230_gsdma_reg_write32(s, K230_GSDMA_INT_STAT, + k230_gsdma_reg_read32(s, + K230_GSDMA_INT_STAT) | + BIT(ch)); + k230_gsdma_update_irq(s); + } + break; + } + } + } +} + +static const MemoryRegionOps k230_gsdma_ops = { + .read = k230_gsdma_read, + .write = k230_gsdma_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_gsdma_reset(DeviceState *dev) +{ + K230GsdmaState *s = K230_GSDMA(dev); + + memset(s->regs, 0, sizeof(s->regs)); + qemu_set_irq(s->irq, 0); +} + +static const VMStateDescription vmstate_k230_gsdma = { + .name = TYPE_K230_GSDMA, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, K230GsdmaState, K230_GSDMA_SIZE), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_gsdma_realize(DeviceState *dev, Error **errp) +{ + K230GsdmaState *s = K230_GSDMA(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_gsdma_ops, s, + TYPE_K230_GSDMA, K230_GSDMA_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq); +} + +static void k230_gsdma_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_gsdma_realize; + device_class_set_legacy_reset(dc, k230_gsdma_reset); + dc->vmsd = &vmstate_k230_gsdma; + dc->desc = "K230 GSDMA controller"; +} + +static const TypeInfo k230_gsdma_type_info = { + .name = TYPE_K230_GSDMA, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230GsdmaState), + .class_init = k230_gsdma_class_init, +}; + +static void k230_register_gsdma_types(void) +{ + type_register_static(&k230_gsdma_type_info); +} + +type_init(k230_register_gsdma_types) diff --git a/hw/dma/k230_pdma.c b/hw/dma/k230_pdma.c new file mode 100644 index 0000000000000..40d6b9132609a --- /dev/null +++ b/hw/dma/k230_pdma.c @@ -0,0 +1,514 @@ +/* + * K230 PDMA controller + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/bswap.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "migration/vmstate.h" +#include "system/dma.h" +#include "hw/core/irq.h" +#include "hw/dma/k230_pdma.h" + +#define K230_PDMA_CHANNELS 8 +#define K230_PDMA_DONE_INT BIT(0) +#define K230_PDMA_ITEM_INT BIT(8) +#define K230_PDMA_PAUSE_INT BIT(16) +#define K230_PDMA_TIMEOUT_INT BIT(24) +#define K230_PDMA_IRQ_MASK_CHANNEL \ + (K230_PDMA_DONE_INT | K230_PDMA_ITEM_INT | K230_PDMA_PAUSE_INT | \ + K230_PDMA_TIMEOUT_INT) +#define K230_PDMA_LLI_LIMIT 1024 + +REG32(PDMA_CH_EN, 0x000) +REG32(PDMA_INT_MASK, 0x004) +REG32(PDMA_INT_STAT, 0x008) + +#define K230_PDMA_CH_BASE(n) (0x020 + (n) * 0x20) +#define K230_PDMA_CH_CTL(n) (K230_PDMA_CH_BASE(n) + 0x00) +#define K230_PDMA_CH_STAT(n) (K230_PDMA_CH_BASE(n) + 0x04) +#define K230_PDMA_CH_CFG(n) (K230_PDMA_CH_BASE(n) + 0x08) +#define K230_PDMA_CH_LLT_SADDR(n) (K230_PDMA_CH_BASE(n) + 0x0c) +#define K230_PDMA_DEV_SEL(n) (0x120 + (n) * 4) +#define K230_PDMA_R(offset) ((offset) / sizeof(uint32_t)) + +#define K230_PDMA_CH_CTL_START 1 +#define K230_PDMA_CH_CTL_STOP 2 +#define K230_PDMA_CH_CTL_RESUME 4 +#define K230_PDMA_CH_STAT_BUSY BIT(0) +#define K230_PDMA_CH_STAT_PAUSE BIT(1) +#define K230_PDMA_CH_CFG_SRC_TYPE BIT(0) +#define K230_PDMA_CH_CFG_DEV_HSIZE_SHIFT 1 +#define K230_PDMA_CH_CFG_DEV_HSIZE_LENGTH 2 +#define K230_PDMA_LLI_LINE_SIZE_MASK MAKE_64BIT_MASK(0, 30) + +static unsigned int k230_pdma_channel_from_addr(hwaddr addr) +{ + return (addr - K230_PDMA_CH_BASE(0)) / 0x20; +} + + +static uint32_t k230_pdma_channel_done(unsigned int channel) +{ + return K230_PDMA_DONE_INT << channel; +} + +static uint32_t k230_pdma_channel_timeout(unsigned int channel) +{ + return K230_PDMA_TIMEOUT_INT << channel; +} +static uint32_t k230_pdma_enabled_irq_mask(const K230PdmaState *s) +{ + uint32_t enabled = s->regs[R_PDMA_CH_EN]; + uint32_t mask = 0; + + for (unsigned int channel = 0; channel < K230_PDMA_CHANNELS; channel++) { + if (enabled & BIT(channel)) { + mask |= k230_pdma_channel_done(channel); + mask |= K230_PDMA_ITEM_INT << channel; + mask |= K230_PDMA_PAUSE_INT << channel; + mask |= k230_pdma_channel_timeout(channel); + } + } + + return mask; +} + +static bool k230_pdma_is_modeled_addr(hwaddr addr) +{ + if (addr == A_PDMA_CH_EN || addr == A_PDMA_INT_MASK || + addr == A_PDMA_INT_STAT) { + return true; + } + if (addr >= K230_PDMA_CH_BASE(0) && + addr < K230_PDMA_CH_BASE(K230_PDMA_CHANNELS) && + ((addr - K230_PDMA_CH_BASE(0)) % 0x20) <= 0x0c && + (((addr - K230_PDMA_CH_BASE(0)) % 4) == 0)) { + return true; + } + if (addr >= K230_PDMA_DEV_SEL(0) && + addr < K230_PDMA_DEV_SEL(K230_PDMA_CHANNELS) && + ((addr - K230_PDMA_DEV_SEL(0)) % 4) == 0) { + return true; + } + + return false; +} + +static uint64_t k230_pdma_read_bytes(const uint8_t *regs, hwaddr addr, + unsigned int size) +{ + uint64_t val = 0; + + for (int i = 0; i < size; i++) { + val |= (uint64_t)regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_pdma_write_bytes(uint8_t *regs, hwaddr addr, uint64_t val, + unsigned int size) +{ + for (int i = 0; i < size; i++) { + regs[addr + i] = val >> (i * 8); + } +} + +static void k230_pdma_sync_legacy_reg(K230PdmaState *s, hwaddr addr) +{ + stl_le_p(&s->legacy_regs[addr], s->regs[K230_PDMA_R(addr)]); +} + +static uint64_t k230_pdma_mmio_read(void *opaque, hwaddr addr, + unsigned int size) +{ + K230PdmaState *s = K230_PDMA(opaque); + + if (size == 4 && addr < K230_PDMA_SIZE && k230_pdma_is_modeled_addr(addr)) { + return register_read_memory(s->reg_array, addr, size); + } + + return k230_pdma_read_bytes(s->legacy_regs, addr, size); +} + +static void k230_pdma_mmio_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230PdmaState *s = K230_PDMA(opaque); + + if (size == 4 && addr < K230_PDMA_SIZE && k230_pdma_is_modeled_addr(addr)) { + register_write_memory(s->reg_array, addr, val, size); + k230_pdma_sync_legacy_reg(s, addr); + return; + } + + k230_pdma_write_bytes(s->legacy_regs, addr, val, size); +} + + + +static void k230_pdma_update_irq(K230PdmaState *s) +{ + uint32_t pending; + bool level; + + pending = s->regs[R_PDMA_INT_STAT] & k230_pdma_enabled_irq_mask(s) & + ~s->regs[R_PDMA_INT_MASK]; + level = pending != 0; + if (s->irq_level != level) { + s->irq_level = level; + qemu_set_irq(s->irq, level); + } +} + +static bool k230_pdma_read32(hwaddr addr, uint32_t *value) +{ + uint32_t raw; + + if (dma_memory_read(&address_space_memory, addr, &raw, sizeof(raw), + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + return false; + } + + *value = ldl_le_p(&raw); + return true; +} + +static bool k230_pdma_copy_tx(hwaddr src, hwaddr dst, uint32_t len, + unsigned int width) +{ + uint8_t buf[4]; + uint32_t offset = 0; + + while (offset < len) { + unsigned int size = MIN(width, len - offset); + + if (dma_memory_read(&address_space_memory, src + offset, buf, size, + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK || + dma_memory_write(&address_space_memory, dst, buf, size, + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + return false; + } + offset += size; + } + + return true; +} + +static bool k230_pdma_copy_rx(hwaddr src, hwaddr dst, uint32_t len, + unsigned int width) +{ + uint8_t buf[4]; + uint32_t offset = 0; + + while (offset < len) { + unsigned int size = MIN(width, len - offset); + + if (dma_memory_read(&address_space_memory, src, buf, size, + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK || + dma_memory_write(&address_space_memory, dst + offset, buf, size, + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + return false; + } + offset += size; + } + + return true; +} + +static unsigned int k230_pdma_channel_width(uint32_t cfg) +{ + unsigned int hsize = extract32(cfg, K230_PDMA_CH_CFG_DEV_HSIZE_SHIFT, + K230_PDMA_CH_CFG_DEV_HSIZE_LENGTH); + + if (hsize > 2) { + return sizeof(uint32_t); + } + + return 1u << hsize; +} + +static bool k230_pdma_run_lli(K230PdmaState *s, unsigned int channel, + hwaddr desc_addr) +{ + uint32_t cfg = s->regs[K230_PDMA_R(K230_PDMA_CH_CFG(channel))]; + unsigned int width = k230_pdma_channel_width(cfg); + bool rx = cfg & K230_PDMA_CH_CFG_SRC_TYPE; + + for (int i = 0; desc_addr && i < K230_PDMA_LLI_LIMIT; i++) { + uint32_t control; + uint32_t src; + uint32_t dst; + uint32_t next; + uint32_t len; + bool ok; + + if (!k230_pdma_read32(desc_addr, &control) || + !k230_pdma_read32(desc_addr + 4, &src) || + !k230_pdma_read32(desc_addr + 8, &dst) || + !k230_pdma_read32(desc_addr + 12, &next)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: failed to read channel %u descriptor @0x%" + HWADDR_PRIx "\n", + TYPE_K230_PDMA, channel, desc_addr); + return false; + } + + len = control & K230_PDMA_LLI_LINE_SIZE_MASK; + if (len) { + if (rx) { + ok = k230_pdma_copy_rx(src, dst, len, width); + } else { + ok = k230_pdma_copy_tx(src, dst, len, width); + } + if (!ok) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: failed channel %u transfer src=0x%08x dst=0x%08x len=0x%x\n", + TYPE_K230_PDMA, channel, src, dst, len); + return false; + } + } + + desc_addr = next; + } + + if (desc_addr) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: channel %u descriptor chain did not terminate\n", + TYPE_K230_PDMA, channel); + return false; + } + + return true; +} + +static void k230_pdma_start_channel(K230PdmaState *s, unsigned int channel) +{ + hwaddr desc_addr; + bool ok; + + if (channel >= K230_PDMA_CHANNELS) { + return; + } + + desc_addr = s->regs[K230_PDMA_R(K230_PDMA_CH_LLT_SADDR(channel))]; + s->regs[K230_PDMA_R(K230_PDMA_CH_STAT(channel))] = K230_PDMA_CH_STAT_BUSY; + ok = k230_pdma_run_lli(s, channel, desc_addr); + s->regs[K230_PDMA_R(K230_PDMA_CH_STAT(channel))] = 0; + + if (ok) { + s->regs[R_PDMA_INT_STAT] |= k230_pdma_channel_done(channel); + } else { + s->regs[R_PDMA_INT_STAT] |= k230_pdma_channel_timeout(channel); + } + k230_pdma_update_irq(s); +} + +static void k230_pdma_int_stat_post_write(RegisterInfo *reg, uint64_t val) +{ + k230_pdma_update_irq(K230_PDMA(reg->opaque)); +} + +static void k230_pdma_ch_en_post_write(RegisterInfo *reg, uint64_t val) +{ + k230_pdma_update_irq(K230_PDMA(reg->opaque)); +} + +static void k230_pdma_ch_ctl_post_write(RegisterInfo *reg, uint64_t val) +{ + K230PdmaState *s = K230_PDMA(reg->opaque); + unsigned int channel = k230_pdma_channel_from_addr(reg->access->addr); + + if (val & K230_PDMA_CH_CTL_STOP) { + s->regs[K230_PDMA_R(K230_PDMA_CH_STAT(channel))] = 0; + return; + } + if (val & K230_PDMA_CH_CTL_RESUME) { + s->regs[K230_PDMA_R(K230_PDMA_CH_STAT(channel))] &= + ~K230_PDMA_CH_STAT_PAUSE; + } + if (val & K230_PDMA_CH_CTL_START) { + k230_pdma_start_channel(s, channel); + } +} + +#define K230_PDMA_CH_CTL_REG(n) \ + { .name = "CH" #n "_CTL", .addr = K230_PDMA_CH_CTL(n), \ + .post_write = k230_pdma_ch_ctl_post_write } +#define K230_PDMA_CH_STAT_REG(n) \ + { .name = "CH" #n "_STAT", .addr = K230_PDMA_CH_STAT(n), .ro = ~0ull } +#define K230_PDMA_CH_REG(n, reg) \ + { .name = "CH" #n "_" #reg, .addr = K230_PDMA_CH_ ## reg(n) } + +#define K230_PDMA_DEV_SEL_REG(n) \ + { .name = "DEV_SEL" #n, .addr = K230_PDMA_DEV_SEL(n) } + +static const RegisterAccessInfo k230_pdma_regs_info[] = { + { .name = "PDMA_CH_EN", .addr = A_PDMA_CH_EN, + .rsvd = ~MAKE_64BIT_MASK(0, K230_PDMA_CHANNELS), + .post_write = k230_pdma_ch_en_post_write }, + { .name = "PDMA_INT_MASK", .addr = A_PDMA_INT_MASK }, + { .name = "PDMA_INT_STAT", .addr = A_PDMA_INT_STAT, + .w1c = UINT32_MAX, .post_write = k230_pdma_int_stat_post_write }, + K230_PDMA_CH_CTL_REG(0), + K230_PDMA_CH_STAT_REG(0), + K230_PDMA_CH_REG(0, CFG), + K230_PDMA_CH_REG(0, LLT_SADDR), + K230_PDMA_CH_CTL_REG(1), + K230_PDMA_CH_STAT_REG(1), + K230_PDMA_CH_REG(1, CFG), + K230_PDMA_CH_REG(1, LLT_SADDR), + K230_PDMA_CH_CTL_REG(2), + K230_PDMA_CH_STAT_REG(2), + K230_PDMA_CH_REG(2, CFG), + K230_PDMA_CH_REG(2, LLT_SADDR), + K230_PDMA_CH_CTL_REG(3), + K230_PDMA_CH_STAT_REG(3), + K230_PDMA_CH_REG(3, CFG), + K230_PDMA_CH_REG(3, LLT_SADDR), + K230_PDMA_CH_CTL_REG(4), + K230_PDMA_CH_STAT_REG(4), + K230_PDMA_CH_REG(4, CFG), + K230_PDMA_CH_REG(4, LLT_SADDR), + K230_PDMA_CH_CTL_REG(5), + K230_PDMA_CH_STAT_REG(5), + K230_PDMA_CH_REG(5, CFG), + K230_PDMA_CH_REG(5, LLT_SADDR), + K230_PDMA_CH_CTL_REG(6), + K230_PDMA_CH_STAT_REG(6), + K230_PDMA_CH_REG(6, CFG), + K230_PDMA_CH_REG(6, LLT_SADDR), + K230_PDMA_CH_CTL_REG(7), + K230_PDMA_CH_STAT_REG(7), + K230_PDMA_CH_REG(7, CFG), + K230_PDMA_CH_REG(7, LLT_SADDR), + K230_PDMA_DEV_SEL_REG(0), + K230_PDMA_DEV_SEL_REG(1), + K230_PDMA_DEV_SEL_REG(2), + K230_PDMA_DEV_SEL_REG(3), + K230_PDMA_DEV_SEL_REG(4), + K230_PDMA_DEV_SEL_REG(5), + K230_PDMA_DEV_SEL_REG(6), + K230_PDMA_DEV_SEL_REG(7), +}; + +static const MemoryRegionOps k230_pdma_reg_array_ops = { + .read = register_read_memory, + .write = register_write_memory, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const MemoryRegionOps k230_pdma_ops = { + .read = k230_pdma_mmio_read, + .write = k230_pdma_mmio_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_pdma_reset(DeviceState *dev) +{ + K230PdmaState *s = K230_PDMA(dev); + + memset(s->legacy_regs, 0, sizeof(s->legacy_regs)); + for (int i = 0; i < ARRAY_SIZE(k230_pdma_regs_info); i++) { + register_reset(&s->regs_info[k230_pdma_regs_info[i].addr / 4]); + k230_pdma_sync_legacy_reg(s, k230_pdma_regs_info[i].addr); + } + s->irq_level = false; + qemu_set_irq(s->irq, 0); +} + +static int k230_pdma_post_load(void *opaque, int version_id) +{ + K230PdmaState *s = K230_PDMA(opaque); + if (version_id == 1) { + memset(s->regs, 0, sizeof(s->regs)); + for (unsigned int i = 0; i < K230_PDMA_R_MAX; i++) { + s->regs[i] = ldl_le_p(&s->legacy_regs[i * sizeof(uint32_t)]); + } + } + for (unsigned int i = 0; i < K230_PDMA_R_MAX; i++) { + k230_pdma_sync_legacy_reg(s, i * sizeof(uint32_t)); + } + + uint32_t pending; + + pending = s->regs[R_PDMA_INT_STAT] & k230_pdma_enabled_irq_mask(s) & + ~s->regs[R_PDMA_INT_MASK]; + s->irq_level = pending != 0; + qemu_set_irq(s->irq, s->irq_level); + + return 0; +} + +static const VMStateDescription vmstate_k230_pdma = { + .name = TYPE_K230_PDMA, + .version_id = 2, + .minimum_version_id = 1, + .post_load = k230_pdma_post_load, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(legacy_regs, K230PdmaState, + K230_PDMA_LEGACY_SIZE), + VMSTATE_BOOL_V(irq_level, K230PdmaState, 2), + VMSTATE_UINT32_ARRAY_V(regs, K230PdmaState, K230_PDMA_R_MAX, 2), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_pdma_realize(DeviceState *dev, Error **errp) +{ + K230PdmaState *s = K230_PDMA(dev); + s->reg_array = register_init_block32(dev, k230_pdma_regs_info, + ARRAY_SIZE(k230_pdma_regs_info), + s->regs_info, s->regs, + &k230_pdma_reg_array_ops, + false, K230_PDMA_SIZE); + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_pdma_ops, s, + TYPE_K230_PDMA, K230_PDMA_MMIO_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq); +} + +static void k230_pdma_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_pdma_realize; + device_class_set_legacy_reset(dc, k230_pdma_reset); + dc->vmsd = &vmstate_k230_pdma; + dc->desc = "K230 PDMA controller"; +} + +static const TypeInfo k230_pdma_type_info = { + .name = TYPE_K230_PDMA, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230PdmaState), + .class_init = k230_pdma_class_init, +}; + +static void k230_register_pdma_types(void) +{ + type_register_static(&k230_pdma_type_info); +} + +type_init(k230_register_pdma_types) diff --git a/hw/dma/meson.build b/hw/dma/meson.build index cc7810beb847b..abff17b12773d 100644 --- a/hw/dma/meson.build +++ b/hw/dma/meson.build @@ -12,3 +12,5 @@ system_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_dma.c', 'soc_dma.c')) system_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_dma.c')) system_ss.add(when: 'CONFIG_SIFIVE_PDMA', if_true: files('sifive_pdma.c')) system_ss.add(when: 'CONFIG_XLNX_CSU_DMA', if_true: files('xlnx_csu_dma.c')) +system_ss.add(when: 'CONFIG_K230_GSDMA', if_true: files('k230_gsdma.c')) +system_ss.add(when: 'CONFIG_K230_PDMA', if_true: files('k230_pdma.c')) diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig index a209294c20c4a..d8f12794f7254 100644 --- a/hw/gpio/Kconfig +++ b/hw/gpio/Kconfig @@ -1,6 +1,9 @@ config PL061 bool +config DW_APB_GPIO + bool + config GPIO_KEY bool @@ -13,6 +16,10 @@ config GPIO_PWR config SIFIVE_GPIO bool + +config ROCKCHIP_GPIO + bool + config STM32L4X5_GPIO bool diff --git a/hw/gpio/dw-apb-gpio.c b/hw/gpio/dw-apb-gpio.c new file mode 100644 index 0000000000000..2113aa2224a16 --- /dev/null +++ b/hw/gpio/dw-apb-gpio.c @@ -0,0 +1,132 @@ +/* + * Synopsys DesignWare APB GPIO + * + * This local model implements the single-port data, direction, and external + * value registers used by Linux gpio-dwapb. Interrupts and additional ports + * are deliberately outside its current contract. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/core/registerfields.h" +#include "hw/gpio/dw-apb-gpio.h" +#include "migration/vmstate.h" +#include "qemu/module.h" + +REG32(SWPORTA_DR, 0x00) +REG32(SWPORTA_DDR, 0x04) +REG32(EXT_PORTA, 0x50) + +static void dw_apb_gpio_update(DWAPBGPIOState *s) +{ + uint32_t data = s->regs[R_SWPORTA_DR]; + uint32_t direction = s->regs[R_SWPORTA_DDR]; + + s->regs[R_EXT_PORTA] = (data & direction) | (s->input & ~direction); + for (unsigned int i = 0; i < DW_APB_GPIO_NR_PINS; i++) { + qemu_set_irq(s->output[i], + (direction & BIT(i)) && (data & BIT(i))); + } +} + +static void dw_apb_gpio_data_postw(RegisterInfo *reg, uint64_t value) +{ + dw_apb_gpio_update(DW_APB_GPIO(reg->opaque)); +} + +static const RegisterAccessInfo dw_apb_gpio_regs_info[] = { + { .name = "SWPORTA_DR", .addr = A_SWPORTA_DR, + .post_write = dw_apb_gpio_data_postw, + }, + { .name = "SWPORTA_DDR", .addr = A_SWPORTA_DDR, + .post_write = dw_apb_gpio_data_postw, + }, + { .name = "EXT_PORTA", .addr = A_EXT_PORTA, + .ro = MAKE_64BIT_MASK(0, 32), + }, +}; + +static const MemoryRegionOps dw_apb_gpio_ops = { + .read = register_read_memory, + .write = register_write_memory, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void dw_apb_gpio_set(void *opaque, int line, int level) +{ + DWAPBGPIOState *s = opaque; + + s->input = deposit32(s->input, line, 1, !!level); + dw_apb_gpio_update(s); +} + +static void dw_apb_gpio_reset(DeviceState *dev) +{ + DWAPBGPIOState *s = DW_APB_GPIO(dev); + + for (unsigned int i = 0; i < DW_APB_GPIO_NR_REGS; i++) { + register_reset(&s->regs_info[i]); + } + s->input = 0; + dw_apb_gpio_update(s); +} + +static int dw_apb_gpio_post_load(void *opaque, int version_id) +{ + dw_apb_gpio_update(DW_APB_GPIO(opaque)); + return 0; +} + +static const VMStateDescription vmstate_dw_apb_gpio = { + .name = TYPE_DW_APB_GPIO, + .version_id = 1, + .minimum_version_id = 1, + .post_load = dw_apb_gpio_post_load, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, DWAPBGPIOState, DW_APB_GPIO_NR_REGS), + VMSTATE_UINT32(input, DWAPBGPIOState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void dw_apb_gpio_init(Object *obj) +{ + DWAPBGPIOState *s = DW_APB_GPIO(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + s->reg_array = register_init_block32(DEVICE(obj), + dw_apb_gpio_regs_info, + ARRAY_SIZE(dw_apb_gpio_regs_info), + s->regs_info, s->regs, + &dw_apb_gpio_ops, false, + DW_APB_GPIO_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->reg_array->mem); + qdev_init_gpio_in(DEVICE(obj), dw_apb_gpio_set, DW_APB_GPIO_NR_PINS); + qdev_init_gpio_out(DEVICE(obj), s->output, DW_APB_GPIO_NR_PINS); +} + +static void dw_apb_gpio_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "Synopsys DesignWare APB GPIO"; + device_class_set_legacy_reset(dc, dw_apb_gpio_reset); + dc->vmsd = &vmstate_dw_apb_gpio; +} + +static const TypeInfo dw_apb_gpio_types[] = { + { + .name = TYPE_DW_APB_GPIO, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(DWAPBGPIOState), + .instance_init = dw_apb_gpio_init, + .class_init = dw_apb_gpio_class_init, + }, +}; +DEFINE_TYPES(dw_apb_gpio_types) diff --git a/hw/gpio/meson.build b/hw/gpio/meson.build index 6a67ee958faac..fe890ab89e3be 100644 --- a/hw/gpio/meson.build +++ b/hw/gpio/meson.build @@ -1,4 +1,5 @@ system_ss.add(when: 'CONFIG_GPIO_KEY', if_true: files('gpio_key.c')) +system_ss.add(when: 'CONFIG_DW_APB_GPIO', if_true: files('dw-apb-gpio.c')) system_ss.add(when: 'CONFIG_GPIO_MPC8XXX', if_true: files('mpc8xxx.c')) system_ss.add(when: 'CONFIG_GPIO_PWR', if_true: files('gpio_pwr.c')) system_ss.add(when: 'CONFIG_PCA9552', if_true: files('pca9552.c')) @@ -18,4 +19,5 @@ system_ss.add(when: 'CONFIG_STM32L4X5_SOC', if_true: files('stm32l4x5_gpio.c')) system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_gpio.c')) system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_sgpio.c')) system_ss.add(when: 'CONFIG_SIFIVE_GPIO', if_true: files('sifive_gpio.c')) +system_ss.add(when: 'CONFIG_ROCKCHIP_GPIO', if_true: files('rockchip_gpio.c')) system_ss.add(when: 'CONFIG_PCF8574', if_true: files('pcf8574.c')) diff --git a/hw/gpio/rockchip_gpio.c b/hw/gpio/rockchip_gpio.c new file mode 100644 index 0000000000000..72e8b2d1cd441 --- /dev/null +++ b/hw/gpio/rockchip_gpio.c @@ -0,0 +1,304 @@ +/* + * Rockchip GPIO bank emulation + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/core/registerfields.h" +#include "hw/gpio/rockchip_gpio.h" +#include "migration/vmstate.h" +#include "qemu/bitops.h" +#include "qemu/module.h" + +#define ROCKCHIP_GPIO_VERSION_V2 0x01000c2b + +REG32(SWPORT_DR_L, 0x00) +REG32(SWPORT_DR_H, 0x04) +REG32(SWPORT_DDR_L, 0x08) +REG32(SWPORT_DDR_H, 0x0c) +REG32(INT_EN_L, 0x10) +REG32(INT_EN_H, 0x14) +REG32(INT_MASK_L, 0x18) +REG32(INT_MASK_H, 0x1c) +REG32(INT_TYPE_L, 0x20) +REG32(INT_TYPE_H, 0x24) +REG32(INT_POLARITY_L, 0x28) +REG32(INT_POLARITY_H, 0x2c) +REG32(INT_BOTHEDGE_L, 0x30) +REG32(INT_BOTHEDGE_H, 0x34) +REG32(DEBOUNCE_L, 0x38) +REG32(DEBOUNCE_H, 0x3c) +REG32(DBCLK_DIV_EN_L, 0x40) +REG32(DBCLK_DIV_EN_H, 0x44) +REG32(DBCLK_DIV_CON, 0x48) +REG32(INT_STATUS_L, 0x50) +REG32(INT_STATUS_H, 0x54) +REG32(INT_RAWSTATUS_L, 0x58) +REG32(INT_RAWSTATUS_H, 0x5c) +REG32(PORT_EOI_L, 0x60) +REG32(PORT_EOI_H, 0x64) +REG32(EXT_PORT_L, 0x70) +REG32(EXT_PORT_H, 0x74) +REG32(VERSION_ID, 0x78) + +static uint32_t rockchip_gpio_get_pair(RockchipGPIOState *s, hwaddr low_addr) +{ + return (s->regs[(low_addr >> 2)] & 0xffff) | + ((s->regs[((low_addr + 4) >> 2)] & 0xffff) << 16); +} + +static void rockchip_gpio_set_pair(RockchipGPIOState *s, hwaddr low_addr, + uint32_t value) +{ + s->regs[(low_addr >> 2)] = value & 0xffff; + s->regs[((low_addr + 4) >> 2)] = (value >> 16) & 0xffff; +} + +static void rockchip_gpio_update_irq(RockchipGPIOState *s) +{ + uint32_t status = rockchip_gpio_get_pair(s, A_INT_STATUS_L); + + qemu_set_irq(s->irq, status != 0); +} + +static void rockchip_gpio_update_outputs(RockchipGPIOState *s) +{ + uint32_t dr = rockchip_gpio_get_pair(s, A_SWPORT_DR_L); + uint32_t ddr = rockchip_gpio_get_pair(s, A_SWPORT_DDR_L); + uint32_t ext = (s->input_level & ~ddr) | (dr & ddr); + + rockchip_gpio_set_pair(s, A_EXT_PORT_L, ext); + + for (unsigned int n = 0; n < ROCKCHIP_GPIO_PINS; n++) { + qemu_set_irq(s->output[n], !!(ext & BIT(n))); + } +} + +static void rockchip_gpio_raise_for_level(RockchipGPIOState *s, + uint32_t old_level, + uint32_t new_level) +{ + uint32_t enabled = rockchip_gpio_get_pair(s, A_INT_EN_L); + uint32_t ddr = rockchip_gpio_get_pair(s, A_SWPORT_DDR_L); + uint32_t type = rockchip_gpio_get_pair(s, A_INT_TYPE_L); + uint32_t polarity = rockchip_gpio_get_pair(s, A_INT_POLARITY_L); + uint32_t bothedge = rockchip_gpio_get_pair(s, A_INT_BOTHEDGE_L); + uint32_t changed = old_level ^ new_level; + uint32_t high = new_level; + uint32_t rising = changed & new_level; + uint32_t falling = changed & old_level; + uint32_t active = enabled & ~ddr; + uint32_t edge = type & active; + uint32_t level = ~type & active; + uint32_t edge_pending; + uint32_t level_pending; + uint32_t mask = rockchip_gpio_get_pair(s, A_INT_MASK_L); + uint32_t raw; + + edge_pending = (bothedge & changed & active) | + (edge & polarity & rising) | + (edge & ~polarity & falling); + level_pending = (level & polarity & high) | + (level & ~polarity & ~high); + + raw = rockchip_gpio_get_pair(s, A_INT_RAWSTATUS_L); + raw = (raw & active & ~level) | level_pending | edge_pending; + + rockchip_gpio_set_pair(s, A_INT_RAWSTATUS_L, raw); + rockchip_gpio_set_pair(s, A_INT_STATUS_L, raw & ~mask); + rockchip_gpio_update_irq(s); +} + +static uint64_t rockchip_gpio_we16_prew(RegisterInfo *reg, uint64_t val) +{ + uint32_t old = *(uint32_t *)reg->data & 0xffff; + uint32_t mask = extract32(val, 16, 16); + uint32_t data = val & 0xffff; + + return (old & ~mask) | (data & mask); +} + +static void rockchip_gpio_data_postw(RegisterInfo *reg, uint64_t val) +{ + RockchipGPIOState *s = ROCKCHIP_GPIO(reg->opaque); + uint32_t old_ext = rockchip_gpio_get_pair(s, A_EXT_PORT_L); + + rockchip_gpio_update_outputs(s); + rockchip_gpio_raise_for_level(s, old_ext, + rockchip_gpio_get_pair(s, A_EXT_PORT_L)); +} + +static void rockchip_gpio_irqcfg_postw(RegisterInfo *reg, uint64_t val) +{ + RockchipGPIOState *s = ROCKCHIP_GPIO(reg->opaque); + + rockchip_gpio_raise_for_level(s, rockchip_gpio_get_pair(s, A_EXT_PORT_L), + rockchip_gpio_get_pair(s, A_EXT_PORT_L)); + rockchip_gpio_update_irq(s); +} + +static void rockchip_gpio_eoi_postw(RegisterInfo *reg, uint64_t val) +{ + RockchipGPIOState *s = ROCKCHIP_GPIO(reg->opaque); + uint32_t ack = rockchip_gpio_get_pair(s, A_PORT_EOI_L); + uint32_t mask = rockchip_gpio_get_pair(s, A_INT_MASK_L); + uint32_t raw = rockchip_gpio_get_pair(s, A_INT_RAWSTATUS_L) & ~ack; + + rockchip_gpio_set_pair(s, A_INT_RAWSTATUS_L, raw); + rockchip_gpio_set_pair(s, A_INT_STATUS_L, raw & ~mask); + rockchip_gpio_set_pair(s, A_PORT_EOI_L, 0); + rockchip_gpio_raise_for_level(s, rockchip_gpio_get_pair(s, A_EXT_PORT_L), + rockchip_gpio_get_pair(s, A_EXT_PORT_L)); + rockchip_gpio_update_irq(s); +} + +static void rockchip_gpio_set_input(void *opaque, int n, int level) +{ + RockchipGPIOState *s = ROCKCHIP_GPIO(opaque); + uint32_t old_ext; + + if (n < 0 || n >= ROCKCHIP_GPIO_PINS || level < 0) { + return; + } + + old_ext = rockchip_gpio_get_pair(s, A_EXT_PORT_L); + if (level) { + s->input_level |= BIT(n); + } else { + s->input_level &= ~BIT(n); + } + + rockchip_gpio_update_outputs(s); + rockchip_gpio_raise_for_level(s, old_ext, + rockchip_gpio_get_pair(s, A_EXT_PORT_L)); +} + +static uint64_t rockchip_gpio_read(void *opaque, hwaddr addr, unsigned size) +{ + return register_read_memory(opaque, addr, size); +} + +static void rockchip_gpio_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + register_write_memory(opaque, addr, value, size); +} + +static const MemoryRegionOps rockchip_gpio_ops = { + .read = rockchip_gpio_read, + .write = rockchip_gpio_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +#define WE16(_name, _addr, _postw) \ + { .name = (_name), .addr = (_addr), .pre_write = rockchip_gpio_we16_prew, \ + .post_write = (_postw) } +#define RO_REG(_name, _addr, _reset) \ + { .name = (_name), .addr = (_addr), .reset = (_reset), .ro = 0xffffffff } + +static const RegisterAccessInfo rockchip_gpio_regs_info[] = { + WE16("SWPORT_DR_L", A_SWPORT_DR_L, rockchip_gpio_data_postw), + WE16("SWPORT_DR_H", A_SWPORT_DR_H, rockchip_gpio_data_postw), + WE16("SWPORT_DDR_L", A_SWPORT_DDR_L, rockchip_gpio_data_postw), + WE16("SWPORT_DDR_H", A_SWPORT_DDR_H, rockchip_gpio_data_postw), + WE16("INT_EN_L", A_INT_EN_L, rockchip_gpio_irqcfg_postw), + WE16("INT_EN_H", A_INT_EN_H, rockchip_gpio_irqcfg_postw), + WE16("INT_MASK_L", A_INT_MASK_L, rockchip_gpio_irqcfg_postw), + WE16("INT_MASK_H", A_INT_MASK_H, rockchip_gpio_irqcfg_postw), + WE16("INT_TYPE_L", A_INT_TYPE_L, rockchip_gpio_irqcfg_postw), + WE16("INT_TYPE_H", A_INT_TYPE_H, rockchip_gpio_irqcfg_postw), + WE16("INT_POLARITY_L", A_INT_POLARITY_L, rockchip_gpio_irqcfg_postw), + WE16("INT_POLARITY_H", A_INT_POLARITY_H, rockchip_gpio_irqcfg_postw), + WE16("INT_BOTHEDGE_L", A_INT_BOTHEDGE_L, rockchip_gpio_irqcfg_postw), + WE16("INT_BOTHEDGE_H", A_INT_BOTHEDGE_H, rockchip_gpio_irqcfg_postw), + WE16("DEBOUNCE_L", A_DEBOUNCE_L, NULL), + WE16("DEBOUNCE_H", A_DEBOUNCE_H, NULL), + WE16("DBCLK_DIV_EN_L", A_DBCLK_DIV_EN_L, NULL), + WE16("DBCLK_DIV_EN_H", A_DBCLK_DIV_EN_H, NULL), + { .name = "DBCLK_DIV_CON", .addr = A_DBCLK_DIV_CON }, + RO_REG("INT_STATUS_L", A_INT_STATUS_L, 0), + RO_REG("INT_STATUS_H", A_INT_STATUS_H, 0), + RO_REG("INT_RAWSTATUS_L", A_INT_RAWSTATUS_L, 0), + RO_REG("INT_RAWSTATUS_H", A_INT_RAWSTATUS_H, 0), + WE16("PORT_EOI_L", A_PORT_EOI_L, rockchip_gpio_eoi_postw), + WE16("PORT_EOI_H", A_PORT_EOI_H, rockchip_gpio_eoi_postw), + RO_REG("EXT_PORT_L", A_EXT_PORT_L, 0), + RO_REG("EXT_PORT_H", A_EXT_PORT_H, 0), + RO_REG("VERSION_ID", A_VERSION_ID, ROCKCHIP_GPIO_VERSION_V2), +}; + +#undef RO_REG +#undef WE16 + +static void rockchip_gpio_reset(DeviceState *dev) +{ + RockchipGPIOState *s = ROCKCHIP_GPIO(dev); + + for (unsigned int i = 0; i < ARRAY_SIZE(s->regs_info); i++) { + register_reset(&s->regs_info[i]); + } + + s->input_level = 0; + rockchip_gpio_update_outputs(s); + rockchip_gpio_update_irq(s); +} + +static void rockchip_gpio_init(Object *obj) +{ + RockchipGPIOState *s = ROCKCHIP_GPIO(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + DeviceState *dev = DEVICE(obj); + + s->reg_array = register_init_block32(dev, rockchip_gpio_regs_info, + ARRAY_SIZE(rockchip_gpio_regs_info), + s->regs_info, s->regs, + &rockchip_gpio_ops, false, + ROCKCHIP_GPIO_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->reg_array->mem); + sysbus_init_irq(sbd, &s->irq); + qdev_init_gpio_in(dev, rockchip_gpio_set_input, ROCKCHIP_GPIO_PINS); + qdev_init_gpio_out_named(dev, s->output, "gpio-out", ROCKCHIP_GPIO_PINS); +} + +static const VMStateDescription vmstate_rockchip_gpio = { + .name = TYPE_ROCKCHIP_GPIO, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, RockchipGPIOState, ROCKCHIP_GPIO_NR_REGS), + VMSTATE_UINT32(input_level, RockchipGPIOState), + VMSTATE_END_OF_LIST(), + } +}; + +static void rockchip_gpio_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "Rockchip GPIO bank"; + device_class_set_legacy_reset(dc, rockchip_gpio_reset); + dc->vmsd = &vmstate_rockchip_gpio; +} + +static const TypeInfo rockchip_gpio_info = { + .name = TYPE_ROCKCHIP_GPIO, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RockchipGPIOState), + .instance_init = rockchip_gpio_init, + .class_init = rockchip_gpio_class_init, +}; + +static void rockchip_gpio_register_types(void) +{ + type_register_static(&rockchip_gpio_info); +} + +type_init(rockchip_gpio_register_types) diff --git a/hw/i2c/Kconfig b/hw/i2c/Kconfig index 0766130b5963e..10fba7b2e6836 100644 --- a/hw/i2c/Kconfig +++ b/hw/i2c/Kconfig @@ -35,6 +35,14 @@ config IMX_I2C bool select I2C +config K230_I2C + bool + select I2C + +config K230_OV5647 + bool + select I2C + config MPC_I2C bool select I2C diff --git a/hw/i2c/k230_i2c.c b/hw/i2c/k230_i2c.c new file mode 100644 index 0000000000000..5b1e0caf69a44 --- /dev/null +++ b/hw/i2c/k230_i2c.c @@ -0,0 +1,445 @@ +/* + * K230 DesignWare I2C controller + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "hw/core/irq.h" +#include "hw/i2c/k230_i2c.h" +#include "migration/vmstate.h" + +#define K230_I2C_CON 0x00 +#define K230_I2C_TAR 0x04 +#define K230_I2C_SAR 0x08 +#define K230_I2C_DATA_CMD 0x10 +#define K230_I2C_SS_SCL_HCNT 0x14 +#define K230_I2C_SS_SCL_LCNT 0x18 +#define K230_I2C_FS_SCL_HCNT 0x1c +#define K230_I2C_FS_SCL_LCNT 0x20 +#define K230_I2C_HS_SCL_HCNT 0x24 +#define K230_I2C_HS_SCL_LCNT 0x28 +#define K230_I2C_INTR_STAT 0x2c +#define K230_I2C_INTR_MASK 0x30 +#define K230_I2C_RAW_INTR_STAT 0x34 +#define K230_I2C_RX_TL 0x38 +#define K230_I2C_TX_TL 0x3c +#define K230_I2C_CLR_INTR 0x40 +#define K230_I2C_CLR_RX_UNDER 0x44 +#define K230_I2C_CLR_RX_OVER 0x48 +#define K230_I2C_CLR_TX_OVER 0x4c +#define K230_I2C_CLR_RD_REQ 0x50 +#define K230_I2C_CLR_TX_ABRT 0x54 +#define K230_I2C_CLR_RX_DONE 0x58 +#define K230_I2C_CLR_ACTIVITY 0x5c +#define K230_I2C_CLR_STOP_DET 0x60 +#define K230_I2C_CLR_START_DET 0x64 +#define K230_I2C_CLR_GEN_CALL 0x68 +#define K230_I2C_ENABLE 0x6c +#define K230_I2C_STATUS 0x70 +#define K230_I2C_TXFLR 0x74 +#define K230_I2C_RXFLR 0x78 +#define K230_I2C_SDA_HOLD 0x7c +#define K230_I2C_TX_ABRT_SOURCE 0x80 +#define K230_I2C_DMA_CR 0x88 +#define K230_I2C_DMA_TDLR 0x8c +#define K230_I2C_DMA_RDLR 0x90 +#define K230_I2C_ENABLE_STATUS 0x9c +#define K230_I2C_START 0xa0 +#define K230_I2C_CLR_RESTART_DET 0xa8 +#define K230_I2C_COMP_PARAM_1 0xf4 +#define K230_I2C_COMP_VERSION 0xf8 +#define K230_I2C_COMP_TYPE 0xfc + +#define K230_I2C_INTR_RX_UNDER BIT(0) +#define K230_I2C_INTR_RX_OVER BIT(1) +#define K230_I2C_INTR_RX_FULL BIT(2) +#define K230_I2C_INTR_TX_OVER BIT(3) +#define K230_I2C_INTR_TX_EMPTY BIT(4) +#define K230_I2C_INTR_RD_REQ BIT(5) +#define K230_I2C_INTR_TX_ABRT BIT(6) +#define K230_I2C_INTR_RX_DONE BIT(7) +#define K230_I2C_INTR_ACTIVITY BIT(8) +#define K230_I2C_INTR_STOP_DET BIT(9) +#define K230_I2C_INTR_START_DET BIT(10) +#define K230_I2C_INTR_GEN_CALL BIT(11) +#define K230_I2C_INTR_RESTART_DET BIT(12) + +#define K230_I2C_STATUS_ACTIVITY BIT(0) +#define K230_I2C_STATUS_TFNF BIT(1) +#define K230_I2C_STATUS_TFE BIT(2) +#define K230_I2C_STATUS_RFNE BIT(3) +#define K230_I2C_STATUS_MST_ACTIVITY BIT(5) + +#define K230_I2C_CMD_READ BIT(8) +#define K230_I2C_CMD_STOP BIT(9) +#define K230_I2C_CMD_RESTART BIT(10) + +#define K230_I2C_ABRT_7B_ADDR_NOACK BIT(0) +#define K230_I2C_ABRT_TXDATA_NOACK BIT(3) +#define K230_I2C_COMP_TYPE_VALUE 0x44570140 +#define K230_I2C_COMP_VERSION_VALUE 0x3131312a + +static uint32_t k230_i2c_reg(K230I2CState *s, hwaddr addr) +{ + return s->regs[addr / 4]; +} + +static void k230_i2c_set_reg(K230I2CState *s, hwaddr addr, uint32_t value) +{ + s->regs[addr / 4] = value; +} + +static bool k230_i2c_enabled(K230I2CState *s) +{ + return k230_i2c_reg(s, K230_I2C_ENABLE) & 1; +} + +static uint32_t k230_i2c_raw_intr(K230I2CState *s) +{ + uint32_t raw = k230_i2c_reg(s, K230_I2C_RAW_INTR_STAT); + + if (k230_i2c_enabled(s)) { + raw |= K230_I2C_INTR_TX_EMPTY; + } + + if (s->rx_len) { + raw |= K230_I2C_INTR_RX_FULL; + } else { + raw &= ~K230_I2C_INTR_RX_FULL; + } + + if (s->started) { + raw |= K230_I2C_INTR_ACTIVITY; + } else { + raw &= ~K230_I2C_INTR_ACTIVITY; + } + + return raw; +} + +static void k230_i2c_update_irq(K230I2CState *s) +{ + uint32_t raw = k230_i2c_raw_intr(s); + uint32_t stat = raw & k230_i2c_reg(s, K230_I2C_INTR_MASK); + + qemu_set_irq(s->irq, stat ? 1 : 0); +} + +static void k230_i2c_raise(K230I2CState *s, uint32_t mask) +{ + k230_i2c_set_reg(s, K230_I2C_RAW_INTR_STAT, + k230_i2c_reg(s, K230_I2C_RAW_INTR_STAT) | mask); + k230_i2c_update_irq(s); +} + +static void k230_i2c_clear(K230I2CState *s, uint32_t mask) +{ + k230_i2c_set_reg(s, K230_I2C_RAW_INTR_STAT, + k230_i2c_reg(s, K230_I2C_RAW_INTR_STAT) & ~mask); + k230_i2c_update_irq(s); +} + +static void k230_i2c_finish(K230I2CState *s) +{ + if (s->started) { + i2c_end_transfer(s->bus); + s->started = false; + } +} + +static void k230_i2c_abort(K230I2CState *s, uint32_t source) +{ + k230_i2c_set_reg(s, K230_I2C_TX_ABRT_SOURCE, source); + k230_i2c_finish(s); + k230_i2c_raise(s, K230_I2C_INTR_TX_ABRT | K230_I2C_INTR_STOP_DET); +} + +static bool k230_i2c_start(K230I2CState *s, bool recv, bool restart) +{ + uint8_t address = k230_i2c_reg(s, K230_I2C_TAR) & 0x7f; + + if (s->started && (!restart && s->recv == recv && s->address == address)) { + return true; + } + + k230_i2c_finish(s); + if (i2c_start_transfer(s->bus, address, recv)) { + k230_i2c_abort(s, K230_I2C_ABRT_7B_ADDR_NOACK); + return false; + } + + s->started = true; + s->recv = recv; + s->address = address; + k230_i2c_raise(s, K230_I2C_INTR_START_DET); + return true; +} + +static void k230_i2c_rx_push(K230I2CState *s, uint8_t value) +{ + if (s->rx_len >= K230_I2C_FIFO_DEPTH) { + k230_i2c_raise(s, K230_I2C_INTR_RX_OVER); + return; + } + + s->rx_fifo[(s->rx_pos + s->rx_len) % K230_I2C_FIFO_DEPTH] = value; + s->rx_len++; + k230_i2c_update_irq(s); +} + +static uint8_t k230_i2c_rx_pop(K230I2CState *s) +{ + uint8_t value; + + if (!s->rx_len) { + k230_i2c_raise(s, K230_I2C_INTR_RX_UNDER); + return 0xff; + } + + value = s->rx_fifo[s->rx_pos]; + s->rx_pos = (s->rx_pos + 1) % K230_I2C_FIFO_DEPTH; + s->rx_len--; + k230_i2c_update_irq(s); + + return value; +} + +static void k230_i2c_data_write(K230I2CState *s, uint32_t value) +{ + bool recv = value & K230_I2C_CMD_READ; + bool restart = value & K230_I2C_CMD_RESTART; + bool stop = value & K230_I2C_CMD_STOP; + + if (!k230_i2c_enabled(s)) { + k230_i2c_abort(s, BIT(11)); + return; + } + + if (!k230_i2c_start(s, recv, restart)) { + return; + } + + if (recv) { + k230_i2c_rx_push(s, i2c_recv(s->bus)); + } else if (i2c_send(s->bus, value & 0xff)) { + k230_i2c_abort(s, K230_I2C_ABRT_TXDATA_NOACK); + return; + } + + if (stop) { + k230_i2c_finish(s); + k230_i2c_raise(s, K230_I2C_INTR_STOP_DET); + } +} + +static uint64_t k230_i2c_read(void *opaque, hwaddr addr, unsigned int size) +{ + K230I2CState *s = K230_I2C(opaque); + uint32_t raw; + + if (addr >= K230_I2C_SIZE || size != 4) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: bad read offset 0x%" HWADDR_PRIx "\n", + TYPE_K230_I2C, addr); + return 0; + } + + switch (addr) { + case K230_I2C_DATA_CMD: + return k230_i2c_rx_pop(s); + case K230_I2C_INTR_STAT: + raw = k230_i2c_raw_intr(s); + return raw & k230_i2c_reg(s, K230_I2C_INTR_MASK); + case K230_I2C_RAW_INTR_STAT: + return k230_i2c_raw_intr(s); + case K230_I2C_CLR_INTR: + k230_i2c_clear(s, (uint32_t)~K230_I2C_INTR_RX_FULL); + return 1; + case K230_I2C_CLR_RX_UNDER: + k230_i2c_clear(s, K230_I2C_INTR_RX_UNDER); + return 1; + case K230_I2C_CLR_RX_OVER: + k230_i2c_clear(s, K230_I2C_INTR_RX_OVER); + return 1; + case K230_I2C_CLR_TX_OVER: + k230_i2c_clear(s, K230_I2C_INTR_TX_OVER); + return 1; + case K230_I2C_CLR_RD_REQ: + k230_i2c_clear(s, K230_I2C_INTR_RD_REQ); + return 1; + case K230_I2C_CLR_TX_ABRT: + k230_i2c_clear(s, K230_I2C_INTR_TX_ABRT); + k230_i2c_set_reg(s, K230_I2C_TX_ABRT_SOURCE, 0); + return 1; + case K230_I2C_CLR_RX_DONE: + k230_i2c_clear(s, K230_I2C_INTR_RX_DONE); + return 1; + case K230_I2C_CLR_ACTIVITY: + k230_i2c_clear(s, K230_I2C_INTR_ACTIVITY); + return 1; + case K230_I2C_CLR_STOP_DET: + k230_i2c_clear(s, K230_I2C_INTR_STOP_DET); + return 1; + case K230_I2C_CLR_START_DET: + k230_i2c_clear(s, K230_I2C_INTR_START_DET); + return 1; + case K230_I2C_CLR_GEN_CALL: + k230_i2c_clear(s, K230_I2C_INTR_GEN_CALL); + return 1; + case K230_I2C_CLR_RESTART_DET: + k230_i2c_clear(s, K230_I2C_INTR_RESTART_DET); + return 1; + case K230_I2C_ENABLE_STATUS: + return k230_i2c_enabled(s) ? 1 : 0; + case K230_I2C_STATUS: + return K230_I2C_STATUS_TFNF | K230_I2C_STATUS_TFE | + (s->rx_len ? K230_I2C_STATUS_RFNE : 0) | + (s->started ? K230_I2C_STATUS_ACTIVITY | + K230_I2C_STATUS_MST_ACTIVITY : 0); + case K230_I2C_TXFLR: + return 0; + case K230_I2C_RXFLR: + return s->rx_len; + case K230_I2C_COMP_PARAM_1: + return (K230_I2C_FIFO_DEPTH - 1) << 16 | + (K230_I2C_FIFO_DEPTH - 1) << 8 | + 0xc; + case K230_I2C_COMP_VERSION: + return K230_I2C_COMP_VERSION_VALUE; + case K230_I2C_COMP_TYPE: + return K230_I2C_COMP_TYPE_VALUE; + default: + return k230_i2c_reg(s, addr); + } +} + +static void k230_i2c_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230I2CState *s = K230_I2C(opaque); + uint32_t value = val; + + if (addr >= K230_I2C_SIZE || size != 4) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: bad write offset 0x%" HWADDR_PRIx "\n", + TYPE_K230_I2C, addr); + return; + } + + switch (addr) { + case K230_I2C_DATA_CMD: + k230_i2c_data_write(s, value); + break; + case K230_I2C_INTR_MASK: + k230_i2c_set_reg(s, addr, value); + k230_i2c_update_irq(s); + break; + case K230_I2C_ENABLE: + k230_i2c_set_reg(s, addr, value & 1); + if (!(value & 1)) { + k230_i2c_finish(s); + s->rx_pos = 0; + s->rx_len = 0; + } + k230_i2c_update_irq(s); + break; + case K230_I2C_RX_TL: + case K230_I2C_TX_TL: + k230_i2c_set_reg(s, addr, MIN(value, K230_I2C_FIFO_DEPTH - 1)); + break; + case K230_I2C_START: + if (value & 1) { + k230_i2c_set_reg(s, K230_I2C_ENABLE, 1); + } + k230_i2c_update_irq(s); + break; + default: + k230_i2c_set_reg(s, addr, value); + break; + } +} + +static const MemoryRegionOps k230_i2c_ops = { + .read = k230_i2c_read, + .write = k230_i2c_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void k230_i2c_reset(DeviceState *dev) +{ + K230I2CState *s = K230_I2C(dev); + + k230_i2c_finish(s); + memset(s->regs, 0, sizeof(s->regs)); + memset(s->rx_fifo, 0, sizeof(s->rx_fifo)); + s->rx_pos = 0; + s->rx_len = 0; + s->address = 0; + s->started = false; + s->recv = false; + k230_i2c_set_reg(s, K230_I2C_CON, 0x65); + k230_i2c_set_reg(s, K230_I2C_SAR, 0x55); + k230_i2c_set_reg(s, K230_I2C_SDA_HOLD, 1 << 16); + k230_i2c_update_irq(s); +} + +static const VMStateDescription vmstate_k230_i2c = { + .name = TYPE_K230_I2C, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, K230I2CState, K230_I2C_REG_COUNT), + VMSTATE_UINT8_ARRAY(rx_fifo, K230I2CState, K230_I2C_FIFO_DEPTH), + VMSTATE_UINT8(rx_pos, K230I2CState), + VMSTATE_UINT8(rx_len, K230I2CState), + VMSTATE_UINT8(address, K230I2CState), + VMSTATE_BOOL(started, K230I2CState), + VMSTATE_BOOL(recv, K230I2CState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_i2c_realize(DeviceState *dev, Error **errp) +{ + K230I2CState *s = K230_I2C(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_i2c_ops, s, + TYPE_K230_I2C, K230_I2C_SIZE); + sysbus_init_mmio(sbd, &s->mmio); + sysbus_init_irq(sbd, &s->irq); + s->bus = i2c_init_bus(dev, "i2c"); +} + +static void k230_i2c_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = k230_i2c_realize; + device_class_set_legacy_reset(dc, k230_i2c_reset); + dc->vmsd = &vmstate_k230_i2c; + dc->desc = "K230 DesignWare I2C controller"; +} + +static const TypeInfo k230_i2c_info = { + .name = TYPE_K230_I2C, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230I2CState), + .class_init = k230_i2c_class_init, +}; + +static void k230_i2c_register_types(void) +{ + type_register_static(&k230_i2c_info); +} + +type_init(k230_i2c_register_types) diff --git a/hw/i2c/k230_ov5647.c b/hw/i2c/k230_ov5647.c new file mode 100644 index 0000000000000..f46a9253e5b85 --- /dev/null +++ b/hw/i2c/k230_ov5647.c @@ -0,0 +1,133 @@ +/* + * K230 OV5647 camera sensor stub + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/i2c/k230_ov5647.h" +#include "migration/vmstate.h" +#include "qemu/module.h" + +OBJECT_DECLARE_SIMPLE_TYPE(K230OV5647State, K230_OV5647) + +#define K230_OV5647_REGS_SIZE 0x10000 +#define K230_OV5647_CHIP_ID_H 0x300a +#define K230_OV5647_CHIP_ID_L 0x300b +#define K230_OV5647_LONG_EXP_H 0x3501 +#define K230_OV5647_LONG_EXP_L 0x3502 +#define K230_OV5647_LONG_AGAIN_H 0x350a +#define K230_OV5647_LONG_AGAIN_L 0x350b + +struct K230OV5647State { + I2CSlave parent_obj; + + uint8_t regs[K230_OV5647_REGS_SIZE]; + uint16_t reg; + uint8_t addr_len; +}; + +static void k230_ov5647_reset(DeviceState *dev) +{ + K230OV5647State *s = K230_OV5647(dev); + + memset(s->regs, 0, sizeof(s->regs)); + s->regs[K230_OV5647_CHIP_ID_H] = 0x56; + s->regs[K230_OV5647_CHIP_ID_L] = 0x47; + s->regs[K230_OV5647_LONG_AGAIN_H] = 0x00; + s->regs[K230_OV5647_LONG_AGAIN_L] = 0x10; + s->regs[K230_OV5647_LONG_EXP_H] = 0x01; + s->regs[K230_OV5647_LONG_EXP_L] = 0x00; + s->reg = 0; + s->addr_len = 0; +} + +static int k230_ov5647_event(I2CSlave *i2c, enum i2c_event event) +{ + K230OV5647State *s = K230_OV5647(i2c); + + switch (event) { + case I2C_START_SEND: + s->addr_len = 0; + break; + case I2C_START_RECV: + case I2C_FINISH: + case I2C_NACK: + break; + default: + return -1; + } + + return 0; +} + +static uint8_t k230_ov5647_recv(I2CSlave *i2c) +{ + K230OV5647State *s = K230_OV5647(i2c); + uint8_t value = s->regs[s->reg]; + + s->reg++; + return value; +} + +static int k230_ov5647_send(I2CSlave *i2c, uint8_t data) +{ + K230OV5647State *s = K230_OV5647(i2c); + + if (s->addr_len == 0) { + s->reg = data << 8; + s->addr_len++; + return 0; + } + + if (s->addr_len == 1) { + s->reg |= data; + s->addr_len++; + return 0; + } + + s->regs[s->reg] = data; + s->reg++; + return 0; +} + +static const VMStateDescription vmstate_k230_ov5647 = { + .name = TYPE_K230_OV5647, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_I2C_SLAVE(parent_obj, K230OV5647State), + VMSTATE_UINT8_ARRAY(regs, K230OV5647State, K230_OV5647_REGS_SIZE), + VMSTATE_UINT16(reg, K230OV5647State), + VMSTATE_UINT8(addr_len, K230OV5647State), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_ov5647_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + I2CSlaveClass *sc = I2C_SLAVE_CLASS(oc); + + sc->event = k230_ov5647_event; + sc->recv = k230_ov5647_recv; + sc->send = k230_ov5647_send; + device_class_set_legacy_reset(dc, k230_ov5647_reset); + dc->vmsd = &vmstate_k230_ov5647; + dc->desc = "K230 OV5647 camera sensor stub"; +} + +static const TypeInfo k230_ov5647_info = { + .name = TYPE_K230_OV5647, + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(K230OV5647State), + .class_init = k230_ov5647_class_init, +}; + +static void k230_ov5647_register_types(void) +{ + type_register_static(&k230_ov5647_info); +} + +type_init(k230_ov5647_register_types) diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build index 88aea35662dd5..9fcb9d350e7ab 100644 --- a/hw/i2c/meson.build +++ b/hw/i2c/meson.build @@ -7,6 +7,8 @@ i2c_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_i2c.c')) i2c_ss.add(when: 'CONFIG_BITBANG_I2C', if_true: files('bitbang_i2c.c')) i2c_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_i2c.c')) i2c_ss.add(when: 'CONFIG_IMX_I2C', if_true: files('imx_i2c.c')) +i2c_ss.add(when: 'CONFIG_K230_I2C', if_true: files('k230_i2c.c')) +i2c_ss.add(when: 'CONFIG_K230_OV5647', if_true: files('k230_ov5647.c')) i2c_ss.add(when: 'CONFIG_MPC_I2C', if_true: files('mpc_i2c.c')) i2c_ss.add(when: 'CONFIG_ALLWINNER_I2C', if_true: files('allwinner-i2c.c')) i2c_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('microbit_i2c.c')) diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig index 636d00b7e888c..250fd6e6d354e 100644 --- a/hw/intc/Kconfig +++ b/hw/intc/Kconfig @@ -74,6 +74,9 @@ config LOONGSON_LIOINTC config RISCV_ACLINT bool +config K230_CLINT + bool + config RISCV_APLIC bool select MSI_NONBROKEN diff --git a/hw/intc/k230_clint.c b/hw/intc/k230_clint.c new file mode 100644 index 0000000000000..3d2481c69d765 --- /dev/null +++ b/hw/intc/k230_clint.c @@ -0,0 +1,262 @@ +/* + * K230 T-Head C908 S-mode CLINT extension + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/sysbus.h" +#include "hw/intc/k230_clint.h" +#include "system/memory.h" +#include "system/address-spaces.h" +#include "target/riscv/cpu.h" +#include "target/riscv/time_helper.h" + +#define K230_CLINT_CPU_INDEX_AUTO UINT32_MAX +#define K230_CLINT_SMODE_SIZE 0x2000 +#define K230_CLINT_SSIP_BASE 0x0000 +#define K230_CLINT_STIMECMP_BASE 0x1000 + +static CPUState *k230_clint_cpu(K230ClintSModeState *s, uint32_t hart_offset) +{ + if (s->cpu_index_base == K230_CLINT_CPU_INDEX_AUTO) { + return cpu_by_arch_id(s->hartid_base + hart_offset); + } + + return qemu_get_cpu(s->cpu_index_base + hart_offset); +} + +static CPURISCVState *k230_clint_env(K230ClintSModeState *s, + hwaddr hart_offset) +{ + CPUState *cpu; + + if (hart_offset >= s->num_harts) { + return NULL; + } + + cpu = k230_clint_cpu(s, hart_offset); + return cpu ? cpu_env(cpu) : NULL; +} + +static uint64_t k230_clint_read_ssip(K230ClintSModeState *s, hwaddr addr) +{ + CPURISCVState *env = k230_clint_env(s, addr >> 2); + + if (!env || (addr & 0x3)) { + qemu_log_mask(LOG_UNIMP, + "k230-clint: invalid SSIP read: %08x", + (uint32_t)addr); + return 0; + } + + return (env->mip & MIP_SSIP) != 0; +} + +static void k230_clint_write_ssip(K230ClintSModeState *s, hwaddr addr, + uint64_t value) +{ + CPURISCVState *env = k230_clint_env(s, addr >> 2); + + if (!env || (addr & 0x3)) { + qemu_log_mask(LOG_UNIMP, + "k230-clint: invalid SSIP write: %08x", + (uint32_t)addr); + return; + } + + riscv_cpu_update_mip(env, MIP_SSIP, BOOL_TO_MASK(value & 0x1)); +} + +static uint64_t k230_clint_read_stimecmp(K230ClintSModeState *s, hwaddr addr, + unsigned size) +{ + hwaddr reg_addr = addr - K230_CLINT_STIMECMP_BASE; + CPURISCVState *env = k230_clint_env(s, reg_addr >> 3); + + if (!env) { + qemu_log_mask(LOG_UNIMP, + "k230-clint: invalid STIMECMP read: %08x", + (uint32_t)addr); + return 0; + } + + switch (reg_addr & 0x7) { + case 0: + return (size == 4) ? (env->stimecmp & 0xffffffff) : env->stimecmp; + case 4: + if (size == 4) { + return env->stimecmp >> 32; + } + break; + } + + qemu_log_mask(LOG_UNIMP, + "k230-clint: invalid STIMECMP read: %08x", + (uint32_t)addr); + return 0; +} + +static void k230_clint_write_stimecmp(K230ClintSModeState *s, hwaddr addr, + uint64_t value, unsigned size) +{ + hwaddr reg_addr = addr - K230_CLINT_STIMECMP_BASE; + CPURISCVState *env = k230_clint_env(s, reg_addr >> 3); + + if (!env) { + qemu_log_mask(LOG_UNIMP, + "k230-clint: invalid STIMECMP write: %08x", + (uint32_t)addr); + return; + } + + switch (reg_addr & 0x7) { + case 0: + if (size == 4) { + env->stimecmp = deposit64(env->stimecmp, 0, 32, value); + } else { + env->stimecmp = value; + } + break; + case 4: + if (size == 4) { + env->stimecmp = deposit64(env->stimecmp, 32, 32, value); + break; + } + qemu_log_mask(LOG_UNIMP, + "k230-clint: invalid STIMECMPH write: %08x", + (uint32_t)addr); + return; + default: + qemu_log_mask(LOG_UNIMP, + "k230-clint: invalid STIMECMP write: %08x", + (uint32_t)addr); + return; + } + + riscv_timer_write_timecmp_mmio(env, env->stimer, env->stimecmp, 0, + MIP_STIP); +} + +static uint64_t k230_clint_read(void *opaque, hwaddr addr, unsigned size) +{ + K230ClintSModeState *s = K230_CLINT_SMODE(opaque); + + if (addr >= K230_CLINT_SSIP_BASE && + addr < K230_CLINT_STIMECMP_BASE) { + return k230_clint_read_ssip(s, addr); + } + + if (addr >= K230_CLINT_STIMECMP_BASE && + addr < K230_CLINT_SMODE_SIZE) { + return k230_clint_read_stimecmp(s, addr, size); + } + + qemu_log_mask(LOG_UNIMP, "k230-clint: invalid read: %08x", + (uint32_t)addr); + return 0; +} + +static void k230_clint_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + K230ClintSModeState *s = K230_CLINT_SMODE(opaque); + + if (addr >= K230_CLINT_SSIP_BASE && + addr < K230_CLINT_STIMECMP_BASE) { + k230_clint_write_ssip(s, addr, value); + return; + } + + if (addr >= K230_CLINT_STIMECMP_BASE && + addr < K230_CLINT_SMODE_SIZE) { + k230_clint_write_stimecmp(s, addr, value, size); + return; + } + + qemu_log_mask(LOG_UNIMP, "k230-clint: invalid write: %08x", + (uint32_t)addr); +} + +static const MemoryRegionOps k230_clint_ops = { + .read = k230_clint_read, + .write = k230_clint_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 8, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 8, + }, +}; + +static const Property k230_clint_properties[] = { + DEFINE_PROP_UINT32("hartid-base", K230ClintSModeState, hartid_base, 0), + DEFINE_PROP_UINT32("cpu-index-base", K230ClintSModeState, cpu_index_base, + K230_CLINT_CPU_INDEX_AUTO), + DEFINE_PROP_UINT32("num-harts", K230ClintSModeState, num_harts, 1), +}; + +static void k230_clint_realize(DeviceState *dev, Error **errp) +{ + K230ClintSModeState *s = K230_CLINT_SMODE(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_clint_ops, s, + TYPE_K230_CLINT_SMODE, K230_CLINT_SMODE_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); +} + +static void k230_clint_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_clint_realize; + device_class_set_props(dc, k230_clint_properties); + dc->desc = "K230 T-Head C908 S-mode CLINT extension"; +} + +static const TypeInfo k230_clint_type_info = { + .name = TYPE_K230_CLINT_SMODE, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230ClintSModeState), + .class_init = k230_clint_class_init, +}; + +DeviceState *k230_clint_smode_create_in(MemoryRegion *mem, hwaddr addr, + uint32_t hartid_base, + uint32_t cpu_index_base, + uint32_t num_harts) +{ + DeviceState *dev = qdev_new(TYPE_K230_CLINT_SMODE); + + qdev_prop_set_uint32(dev, "hartid-base", hartid_base); + qdev_prop_set_uint32(dev, "cpu-index-base", cpu_index_base); + qdev_prop_set_uint32(dev, "num-harts", num_harts); + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + + if (mem == get_system_memory()) { + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); + } else { + memory_region_add_subregion_overlap(mem, addr, + sysbus_mmio_get_region( + SYS_BUS_DEVICE(dev), 0), + 1); + } + + return dev; +} + +static void k230_clint_register_types(void) +{ + type_register_static(&k230_clint_type_info); +} + +type_init(k230_clint_register_types) diff --git a/hw/intc/meson.build b/hw/intc/meson.build index fac2d228f9b88..c875bf0e9810b 100644 --- a/hw/intc/meson.build +++ b/hw/intc/meson.build @@ -64,6 +64,7 @@ specific_ss.add(when: 'CONFIG_S390_FLIC', if_true: files('s390_flic.c')) specific_ss.add(when: 'CONFIG_S390_FLIC_KVM', if_true: files('s390_flic_kvm.c')) specific_ss.add(when: 'CONFIG_SH_INTC', if_true: files('sh_intc.c')) specific_ss.add(when: 'CONFIG_RISCV_ACLINT', if_true: files('riscv_aclint.c')) +specific_ss.add(when: 'CONFIG_K230_CLINT', if_true: files('k230_clint.c')) specific_ss.add(when: 'CONFIG_RISCV_APLIC', if_true: files('riscv_aplic.c')) specific_ss.add(when: 'CONFIG_RISCV_IMSIC', if_true: files('riscv_imsic.c')) specific_ss.add(when: 'CONFIG_SIFIVE_PLIC', if_true: files('sifive_plic.c')) diff --git a/hw/intc/riscv_aclint.c b/hw/intc/riscv_aclint.c index 361a8d1bcb453..ed2677b2519f1 100644 --- a/hw/intc/riscv_aclint.c +++ b/hw/intc/riscv_aclint.c @@ -34,6 +34,10 @@ #include "qemu/timer.h" #include "hw/core/irq.h" #include "migration/vmstate.h" +#include "system/memory.h" +#include "system/address-spaces.h" + +#define RISCV_ACLINT_CPU_INDEX_AUTO UINT32_MAX typedef struct riscv_aclint_mtimer_callback { RISCVAclintMTimerState *s; @@ -48,6 +52,37 @@ static void riscv_cpu_set_rdtime_fn(CPURISCVState *env, env->rdtime_fn_arg = arg; } +static size_t riscv_aclint_hartid(uint32_t hartid_base, uint32_t hart_offset) +{ + return hartid_base + hart_offset; +} + +static CPUState *riscv_aclint_cpu_for_hart_offset(uint32_t hartid_base, + uint32_t cpu_index_base, + uint32_t hart_offset) +{ + if (cpu_index_base == RISCV_ACLINT_CPU_INDEX_AUTO) { + return cpu_by_arch_id(riscv_aclint_hartid(hartid_base, hart_offset)); + } + + return qemu_get_cpu(cpu_index_base + hart_offset); +} + +static CPUState *riscv_aclint_mtimer_cpu_for_hart_offset( + RISCVAclintMTimerState *mtimer, uint32_t hart_offset) +{ + return riscv_aclint_cpu_for_hart_offset(mtimer->hartid_base, + mtimer->cpu_index_base, + hart_offset); +} + +static CPUState *riscv_aclint_swi_cpu_for_hart_offset( + RISCVAclintSwiState *swi, uint32_t hart_offset) +{ + return riscv_aclint_cpu_for_hart_offset(swi->hartid_base, + swi->cpu_index_base, hart_offset); +} + static uint64_t cpu_riscv_read_rtc_raw(uint32_t timebase_freq) { return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), @@ -65,8 +100,7 @@ static uint64_t cpu_riscv_read_rtc(void *opaque) * trigger timer interrupt if mtimecmp <= current timer value. */ static void riscv_aclint_mtimer_write_timecmp(RISCVAclintMTimerState *mtimer, - RISCVCPU *cpu, - int hartid, + uint32_t hart_offset, uint64_t value) { uint32_t timebase_freq = mtimer->timebase_freq; @@ -75,22 +109,19 @@ static void riscv_aclint_mtimer_write_timecmp(RISCVAclintMTimerState *mtimer, uint64_t rtc = cpu_riscv_read_rtc(mtimer); - /* Compute the relative hartid w.r.t the socket */ - hartid = hartid - mtimer->hartid_base; - - mtimer->timecmp[hartid] = value; - if (mtimer->timecmp[hartid] <= rtc) { + mtimer->timecmp[hart_offset] = value; + if (mtimer->timecmp[hart_offset] <= rtc) { /* * If we're setting an MTIMECMP value in the "past", * immediately raise the timer interrupt */ - qemu_irq_raise(mtimer->timer_irqs[hartid]); + qemu_irq_raise(mtimer->timer_irqs[hart_offset]); return; } /* otherwise, set up the future timer interrupt */ - qemu_irq_lower(mtimer->timer_irqs[hartid]); - diff = mtimer->timecmp[hartid] - rtc; + qemu_irq_lower(mtimer->timer_irqs[hart_offset]); + diff = mtimer->timecmp[hart_offset] - rtc; /* back to ns (note args switched in muldiv64) */ uint64_t ns_diff = muldiv64(diff, NANOSECONDS_PER_SECOND, timebase_freq); @@ -115,7 +146,7 @@ static void riscv_aclint_mtimer_write_timecmp(RISCVAclintMTimerState *mtimer, next = MIN(next, INT64_MAX); } - timer_mod(mtimer->timers[hartid], next); + timer_mod(mtimer->timers[hart_offset], next); } /* @@ -137,21 +168,22 @@ static uint64_t riscv_aclint_mtimer_read(void *opaque, hwaddr addr, if (addr >= mtimer->timecmp_base && addr < (mtimer->timecmp_base + (mtimer->num_harts << 3))) { - size_t hartid = mtimer->hartid_base + - ((addr - mtimer->timecmp_base) >> 3); - size_t hartid_offset = hartid - mtimer->hartid_base; - CPUState *cpu = cpu_by_arch_id(hartid); + uint32_t hart_offset = (addr - mtimer->timecmp_base) >> 3; + size_t hartid = riscv_aclint_hartid(mtimer->hartid_base, + hart_offset); + CPUState *cpu = + riscv_aclint_mtimer_cpu_for_hart_offset(mtimer, hart_offset); CPURISCVState *env = cpu ? cpu_env(cpu) : NULL; if (!env) { qemu_log_mask(LOG_GUEST_ERROR, "aclint-mtimer: invalid hartid: %zu", hartid); } else if ((addr & 0x7) == 0) { /* timecmp_lo for RV32/RV64 or timecmp for RV64 */ - uint64_t timecmp = mtimer->timecmp[hartid_offset]; + uint64_t timecmp = mtimer->timecmp[hart_offset]; return (size == 4) ? (timecmp & 0xFFFFFFFF) : timecmp; } else if ((addr & 0x7) == 4) { /* timecmp_hi */ - uint64_t timecmp = mtimer->timecmp[hartid_offset]; + uint64_t timecmp = mtimer->timecmp[hart_offset]; return (timecmp >> 32) & 0xFFFFFFFF; } else { qemu_log_mask(LOG_UNIMP, @@ -181,10 +213,11 @@ static void riscv_aclint_mtimer_write(void *opaque, hwaddr addr, if (addr >= mtimer->timecmp_base && addr < (mtimer->timecmp_base + (mtimer->num_harts << 3))) { - size_t hartid = mtimer->hartid_base + - ((addr - mtimer->timecmp_base) >> 3); - size_t hartid_offset = hartid - mtimer->hartid_base; - CPUState *cpu = cpu_by_arch_id(hartid); + uint32_t hart_offset = (addr - mtimer->timecmp_base) >> 3; + size_t hartid = riscv_aclint_hartid(mtimer->hartid_base, + hart_offset); + CPUState *cpu = + riscv_aclint_mtimer_cpu_for_hart_offset(mtimer, hart_offset); CPURISCVState *env = cpu ? cpu_env(cpu) : NULL; if (!env) { qemu_log_mask(LOG_GUEST_ERROR, @@ -192,19 +225,18 @@ static void riscv_aclint_mtimer_write(void *opaque, hwaddr addr, } else if ((addr & 0x7) == 0) { if (size == 4) { /* timecmp_lo for RV32/RV64 */ - uint64_t timecmp_hi = mtimer->timecmp[hartid_offset] >> 32; - riscv_aclint_mtimer_write_timecmp(mtimer, RISCV_CPU(cpu), hartid, + uint64_t timecmp_hi = mtimer->timecmp[hart_offset] >> 32; + riscv_aclint_mtimer_write_timecmp(mtimer, hart_offset, timecmp_hi << 32 | (value & 0xFFFFFFFF)); } else { /* timecmp for RV64 */ - riscv_aclint_mtimer_write_timecmp(mtimer, RISCV_CPU(cpu), hartid, - value); + riscv_aclint_mtimer_write_timecmp(mtimer, hart_offset, value); } } else if ((addr & 0x7) == 4) { if (size == 4) { /* timecmp_hi for RV32/RV64 */ - uint64_t timecmp_lo = mtimer->timecmp[hartid_offset]; - riscv_aclint_mtimer_write_timecmp(mtimer, RISCV_CPU(cpu), hartid, + uint64_t timecmp_lo = mtimer->timecmp[hart_offset]; + riscv_aclint_mtimer_write_timecmp(mtimer, hart_offset, value << 32 | (timecmp_lo & 0xFFFFFFFF)); } else { qemu_log_mask(LOG_GUEST_ERROR, @@ -243,13 +275,13 @@ static void riscv_aclint_mtimer_write(void *opaque, hwaddr addr, /* Check if timer interrupt is triggered for each hart. */ for (i = 0; i < mtimer->num_harts; i++) { - CPUState *cpu = cpu_by_arch_id(mtimer->hartid_base + i); + CPUState *cpu = + riscv_aclint_mtimer_cpu_for_hart_offset(mtimer, i); CPURISCVState *env = cpu ? cpu_env(cpu) : NULL; if (!env) { continue; } - riscv_aclint_mtimer_write_timecmp(mtimer, RISCV_CPU(cpu), - mtimer->hartid_base + i, + riscv_aclint_mtimer_write_timecmp(mtimer, i, mtimer->timecmp[i]); riscv_timer_write_timecmp(env, env->stimer, env->stimecmp, 0, MIP_STIP); riscv_timer_write_timecmp(env, env->vstimer, env->vstimecmp, @@ -280,6 +312,8 @@ static const MemoryRegionOps riscv_aclint_mtimer_ops = { static const Property riscv_aclint_mtimer_properties[] = { DEFINE_PROP_UINT32("hartid-base", RISCVAclintMTimerState, hartid_base, 0), + DEFINE_PROP_UINT32("cpu-index-base", RISCVAclintMTimerState, + cpu_index_base, RISCV_ACLINT_CPU_INDEX_AUTO), DEFINE_PROP_UINT32("num-harts", RISCVAclintMTimerState, num_harts, 1), DEFINE_PROP_UINT32("timecmp-base", RISCVAclintMTimerState, timecmp_base, RISCV_ACLINT_DEFAULT_MTIMECMP), @@ -307,12 +341,13 @@ static void riscv_aclint_mtimer_realize(DeviceState *dev, Error **errp) s->timecmp = g_new0(uint64_t, s->num_harts); /* Claim timer interrupt bits */ for (i = 0; i < s->num_harts; i++) { - CPUState *cpu_by_hartid = cpu_by_arch_id(s->hartid_base + i); - if (cpu_by_hartid == NULL) { + CPUState *cpu_state = + riscv_aclint_mtimer_cpu_for_hart_offset(s, i); + if (cpu_state == NULL) { /* Valid for sparse hart layouts - skip this hart ID */ continue; } - RISCVCPU *cpu = RISCV_CPU(cpu_by_hartid); + RISCVCPU *cpu = RISCV_CPU(cpu_state); if (riscv_cpu_claim_interrupts(cpu, MIP_MTIP) < 0) { error_report("MTIP already claimed"); exit(1); @@ -375,6 +410,18 @@ DeviceState *riscv_aclint_mtimer_create(hwaddr addr, hwaddr size, uint32_t hartid_base, uint32_t num_harts, uint32_t timecmp_base, uint32_t time_base, uint32_t timebase_freq, bool provide_rdtime) +{ + return riscv_aclint_mtimer_create_in(get_system_memory(), addr, size, + hartid_base, + RISCV_ACLINT_CPU_INDEX_AUTO, + num_harts, timecmp_base, time_base, + timebase_freq, provide_rdtime); +} + +DeviceState *riscv_aclint_mtimer_create_in(MemoryRegion *mem, hwaddr addr, + hwaddr size, uint32_t hartid_base, uint32_t cpu_index_base, + uint32_t num_harts, uint32_t timecmp_base, uint32_t time_base, + uint32_t timebase_freq, bool provide_rdtime) { int i; DeviceState *dev = qdev_new(TYPE_RISCV_ACLINT_MTIMER); @@ -386,17 +433,24 @@ DeviceState *riscv_aclint_mtimer_create(hwaddr addr, hwaddr size, assert(!(time_base & 0x7)); qdev_prop_set_uint32(dev, "hartid-base", hartid_base); + qdev_prop_set_uint32(dev, "cpu-index-base", cpu_index_base); qdev_prop_set_uint32(dev, "num-harts", num_harts); qdev_prop_set_uint32(dev, "timecmp-base", timecmp_base); qdev_prop_set_uint32(dev, "time-base", time_base); qdev_prop_set_uint32(dev, "aperture-size", size); qdev_prop_set_uint32(dev, "timebase-freq", timebase_freq); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); - sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); + if (mem == get_system_memory()) { + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); + } else { + memory_region_add_subregion_overlap(mem, addr, + sysbus_mmio_get_region( + SYS_BUS_DEVICE(dev), 0), + 1); + } for (i = 0; i < num_harts; i++) { - CPUState *cpu = cpu_by_arch_id(hartid_base + i); - RISCVCPU *rvcpu = RISCV_CPU(cpu); + CPUState *cpu = riscv_aclint_mtimer_cpu_for_hart_offset(s, i); CPURISCVState *env = cpu ? cpu_env(cpu) : NULL; riscv_aclint_mtimer_callback *cb = g_new0(riscv_aclint_mtimer_callback, 1); @@ -416,7 +470,8 @@ DeviceState *riscv_aclint_mtimer_create(hwaddr addr, hwaddr size, s->timecmp[i] = 0; qdev_connect_gpio_out(dev, i, - qdev_get_gpio_in(DEVICE(rvcpu), IRQ_M_TIMER)); + qdev_get_gpio_in(DEVICE(RISCV_CPU(cpu)), + IRQ_M_TIMER)); } return dev; @@ -429,8 +484,10 @@ static uint64_t riscv_aclint_swi_read(void *opaque, hwaddr addr, RISCVAclintSwiState *swi = opaque; if (addr < (swi->num_harts << 2)) { - size_t hartid = swi->hartid_base + (addr >> 2); - CPUState *cpu = cpu_by_arch_id(hartid); + uint32_t hart_offset = addr >> 2; + size_t hartid = riscv_aclint_hartid(swi->hartid_base, hart_offset); + CPUState *cpu = + riscv_aclint_swi_cpu_for_hart_offset(swi, hart_offset); CPURISCVState *env = cpu ? cpu_env(cpu) : NULL; if (!env) { qemu_log_mask(LOG_GUEST_ERROR, @@ -452,18 +509,20 @@ static void riscv_aclint_swi_write(void *opaque, hwaddr addr, uint64_t value, RISCVAclintSwiState *swi = opaque; if (addr < (swi->num_harts << 2)) { - size_t hartid = swi->hartid_base + (addr >> 2); - CPUState *cpu = cpu_by_arch_id(hartid); + uint32_t hart_offset = addr >> 2; + size_t hartid = riscv_aclint_hartid(swi->hartid_base, hart_offset); + CPUState *cpu = + riscv_aclint_swi_cpu_for_hart_offset(swi, hart_offset); CPURISCVState *env = cpu ? cpu_env(cpu) : NULL; if (!env) { qemu_log_mask(LOG_GUEST_ERROR, "aclint-swi: invalid hartid: %zu", hartid); } else if ((addr & 0x3) == 0) { if (value & 0x1) { - qemu_irq_raise(swi->soft_irqs[hartid - swi->hartid_base]); + qemu_irq_raise(swi->soft_irqs[hart_offset]); } else { if (!swi->sswi) { - qemu_irq_lower(swi->soft_irqs[hartid - swi->hartid_base]); + qemu_irq_lower(swi->soft_irqs[hart_offset]); } } return; @@ -486,6 +545,8 @@ static const MemoryRegionOps riscv_aclint_swi_ops = { static const Property riscv_aclint_swi_properties[] = { DEFINE_PROP_UINT32("hartid-base", RISCVAclintSwiState, hartid_base, 0), + DEFINE_PROP_UINT32("cpu-index-base", RISCVAclintSwiState, cpu_index_base, + RISCV_ACLINT_CPU_INDEX_AUTO), DEFINE_PROP_UINT32("num-harts", RISCVAclintSwiState, num_harts, 1), DEFINE_PROP_UINT32("sswi", RISCVAclintSwiState, sswi, false), }; @@ -504,12 +565,12 @@ static void riscv_aclint_swi_realize(DeviceState *dev, Error **errp) /* Claim software interrupt bits */ for (i = 0; i < swi->num_harts; i++) { - CPUState *cpu_by_hartid = cpu_by_arch_id(swi->hartid_base + i); - if (cpu_by_hartid == NULL) { + CPUState *cpu_state = riscv_aclint_swi_cpu_for_hart_offset(swi, i); + if (cpu_state == NULL) { /* Valid for sparse hart layouts - skip this hart ID */ continue; } - RISCVCPU *cpu = RISCV_CPU(cpu_by_hartid); + RISCVCPU *cpu = RISCV_CPU(cpu_state); /* We don't claim mip.SSIP because it is writable by software */ if (riscv_cpu_claim_interrupts(cpu, swi->sswi ? 0 : MIP_MSIP) < 0) { error_report("MSIP already claimed"); @@ -558,6 +619,15 @@ static const TypeInfo riscv_aclint_swi_info = { */ DeviceState *riscv_aclint_swi_create(hwaddr addr, uint32_t hartid_base, uint32_t num_harts, bool sswi) +{ + return riscv_aclint_swi_create_in(get_system_memory(), addr, hartid_base, + RISCV_ACLINT_CPU_INDEX_AUTO, num_harts, + sswi); +} + +DeviceState *riscv_aclint_swi_create_in(MemoryRegion *mem, hwaddr addr, + uint32_t hartid_base, uint32_t cpu_index_base, uint32_t num_harts, + bool sswi) { int i; DeviceState *dev = qdev_new(TYPE_RISCV_ACLINT_SWI); @@ -566,13 +636,22 @@ DeviceState *riscv_aclint_swi_create(hwaddr addr, uint32_t hartid_base, assert(!(addr & 0x3)); qdev_prop_set_uint32(dev, "hartid-base", hartid_base); + qdev_prop_set_uint32(dev, "cpu-index-base", cpu_index_base); qdev_prop_set_uint32(dev, "num-harts", num_harts); qdev_prop_set_uint32(dev, "sswi", sswi ? true : false); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); - sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); + if (mem == get_system_memory()) { + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); + } else { + memory_region_add_subregion_overlap(mem, addr, + sysbus_mmio_get_region( + SYS_BUS_DEVICE(dev), 0), + 1); + } for (i = 0; i < num_harts; i++) { - CPUState *cpu = cpu_by_arch_id(hartid_base + i); + CPUState *cpu = + riscv_aclint_swi_cpu_for_hart_offset(RISCV_ACLINT_SWI(dev), i); if (cpu == NULL) { /* Valid for sparse hart layouts - skip this hart ID */ continue; diff --git a/hw/intc/sifive_plic.c b/hw/intc/sifive_plic.c index 9c84ff06a9fd1..a5ea244ce2289 100644 --- a/hw/intc/sifive_plic.c +++ b/hw/intc/sifive_plic.c @@ -30,8 +30,12 @@ #include "target/riscv/cpu.h" #include "migration/vmstate.h" #include "hw/core/irq.h" +#include "system/memory.h" +#include "system/address-spaces.h" #include "system/kvm.h" +#define SIFIVE_PLIC_CPU_INDEX_AUTO UINT32_MAX + static bool addr_between(uint32_t addr, uint32_t base, uint32_t num) { return addr >= base && addr - base < num; @@ -62,6 +66,30 @@ static uint32_t atomic_set_masked(uint32_t *a, uint32_t mask, uint32_t value) return old; } +static uint32_t sifive_plic_hart_offset(SiFivePLICState *plic, + uint32_t hartid) +{ + return hartid - plic->hartid_base; +} + +static uint32_t sifive_plic_cpu_index_for_hart(SiFivePLICState *plic, + uint32_t hartid) +{ + uint32_t cpu_index_base = plic->cpu_index_base; + + if (cpu_index_base == SIFIVE_PLIC_CPU_INDEX_AUTO) { + cpu_index_base = plic->hartid_base; + } + + return cpu_index_base + sifive_plic_hart_offset(plic, hartid); +} + +static CPUState *sifive_plic_cpu_for_hart(SiFivePLICState *plic, + uint32_t hartid) +{ + return qemu_get_cpu(sifive_plic_cpu_index_for_hart(plic, hartid)); +} + static void sifive_plic_set_pending(SiFivePLICState *plic, int irq, bool level) { atomic_set_masked(&plic->pending[irq >> 5], 1 << (irq & 31), -!!level); @@ -190,7 +218,10 @@ static void sifive_plic_write(void *opaque, hwaddr addr, uint64_t value, if (addr_between(addr, plic->priority_base, plic->num_sources << 2)) { uint32_t irq = (addr - plic->priority_base) >> 2; if (irq == 0) { - /* IRQ 0 source prioority is reserved */ + /* IRQ 0 source priority is reserved. */ + if (value == 0) { + return; + } qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid source priority write 0x%" HWADDR_PRIx "\n", __func__, addr); @@ -399,7 +430,8 @@ static void sifive_plic_realize(DeviceState *dev, Error **errp) * hardware controlled when a PLIC is attached. */ for (i = 0; i < s->num_harts; i++) { - RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(s->hartid_base + i)); + RISCVCPU *cpu = + RISCV_CPU(sifive_plic_cpu_for_hart(s, s->hartid_base + i)); if (riscv_cpu_claim_interrupts(cpu, MIP_SEIP) < 0) { error_setg(errp, "SEIP already claimed"); return; @@ -433,6 +465,8 @@ static const VMStateDescription vmstate_sifive_plic = { static const Property sifive_plic_properties[] = { DEFINE_PROP_STRING("hart-config", SiFivePLICState, hart_config), DEFINE_PROP_UINT32("hartid-base", SiFivePLICState, hartid_base, 0), + DEFINE_PROP_UINT32("cpu-index-base", SiFivePLICState, cpu_index_base, + SIFIVE_PLIC_CPU_INDEX_AUTO), /* number of interrupt sources including interrupt source 0 */ DEFINE_PROP_UINT32("num-sources", SiFivePLICState, num_sources, 1), DEFINE_PROP_UINT32("num-priorities", SiFivePLICState, num_priorities, 0), @@ -480,6 +514,22 @@ DeviceState *sifive_plic_create(hwaddr addr, char *hart_config, uint32_t pending_base, uint32_t enable_base, uint32_t enable_stride, uint32_t context_base, uint32_t context_stride, uint32_t aperture_size) +{ + return sifive_plic_create_in(get_system_memory(), addr, hart_config, + num_harts, hartid_base, + SIFIVE_PLIC_CPU_INDEX_AUTO, + num_sources, num_priorities, priority_base, + pending_base, enable_base, enable_stride, + context_base, context_stride, + aperture_size); +} + +DeviceState *sifive_plic_create_in(MemoryRegion *mem, hwaddr addr, + char *hart_config, uint32_t num_harts, uint32_t hartid_base, + uint32_t cpu_index_base, uint32_t num_sources, uint32_t num_priorities, + uint32_t priority_base, uint32_t pending_base, uint32_t enable_base, + uint32_t enable_stride, uint32_t context_base, uint32_t context_stride, + uint32_t aperture_size) { DeviceState *dev = qdev_new(TYPE_SIFIVE_PLIC); int i; @@ -489,6 +539,7 @@ DeviceState *sifive_plic_create(hwaddr addr, char *hart_config, assert(context_stride == (context_stride & -context_stride)); qdev_prop_set_string(dev, "hart-config", hart_config); qdev_prop_set_uint32(dev, "hartid-base", hartid_base); + qdev_prop_set_uint32(dev, "cpu-index-base", cpu_index_base); qdev_prop_set_uint32(dev, "num-sources", num_sources); qdev_prop_set_uint32(dev, "num-priorities", num_priorities); qdev_prop_set_uint32(dev, "priority-base", priority_base); @@ -499,20 +550,28 @@ DeviceState *sifive_plic_create(hwaddr addr, char *hart_config, qdev_prop_set_uint32(dev, "context-stride", context_stride); qdev_prop_set_uint32(dev, "aperture-size", aperture_size); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); - sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); + if (mem == get_system_memory()) { + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); + } else { + memory_region_add_subregion_overlap(mem, addr, + sysbus_mmio_get_region( + SYS_BUS_DEVICE(dev), 0), + 1); + } plic = SIFIVE_PLIC(dev); for (i = 0; i < plic->num_addrs; i++) { - int cpu_num = plic->addr_config[i].hartid; - CPUState *cpu = qemu_get_cpu(cpu_num); + int hartid = plic->addr_config[i].hartid; + int hart_offset = sifive_plic_hart_offset(plic, hartid); + CPUState *cpu = sifive_plic_cpu_for_hart(plic, hartid); if (plic->addr_config[i].mode == PLICMode_M) { - qdev_connect_gpio_out(dev, cpu_num - hartid_base + num_harts, + qdev_connect_gpio_out(dev, hart_offset + num_harts, qdev_get_gpio_in(DEVICE(cpu), IRQ_M_EXT)); } if (plic->addr_config[i].mode == PLICMode_S) { - qdev_connect_gpio_out(dev, cpu_num - hartid_base, + qdev_connect_gpio_out(dev, hart_offset, qdev_get_gpio_in(DEVICE(cpu), IRQ_S_EXT)); } } diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index 1543ee66531bf..34512f6f4996e 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -43,6 +43,15 @@ config I2C_ECHO config PL310 bool +config ROCKCHIP_SYSCON + bool + +config PHYTIUM_DDR_CTRL + bool + +config PHYTIUM_SCP_MAILBOX + bool + config INTEGRATOR_DEBUG bool @@ -186,9 +195,22 @@ config AUX config UNIMP bool +config NXP_S32_MC_ME + bool + select REGISTER + config LED bool +config RK3588_CRU + bool + +config RK3588_SCMI + bool + +config SPACEMIT_K3_CTRL + bool + config MAC_VIA bool select MOS6522 @@ -209,6 +231,9 @@ config MCHP_PFSOC_SYSREG config SIFIVE_TEST bool +config K230_PMU + bool + config SIFIVE_E_PRCI bool @@ -221,6 +246,62 @@ config SIFIVE_U_OTP config SIFIVE_U_PRCI bool +config K230_HI_SYS_CFG + bool + +config K230_HARDLOCK + bool + +config K230_TSENSOR + bool + +config K230_GPIO + bool + +config K230_IOMUX + bool + +config K230_REGS + bool + +config K230_I2S + bool + select REGISTER + +config K230_KPU + bool + +config K230_NONAI_2D + bool + +config K230_DEWARP + bool + +config K230_ISP + bool + +config K230_RX_CSI + bool + +config K230_ADC + bool + +config K230_PWM + bool + +config K230_TIMER + bool + +config K230_SYSCTL + bool + +config K230_SECURITY + bool + +config K230_UGZIP + bool + select K230_GSDMA + config VIRT_CTRL bool @@ -257,4 +338,13 @@ config XLNX_VERSAL_TRNG config XLNX_ZYNQ_DDRC bool +config SOPHGO_CV1800B_CLK + bool + source macio/Kconfig +# AX650X DWMAC clock/reset/interface glue +config AX650X_DWMAC_GLUE + bool + +config AX650X_HWSPINLOCK + bool diff --git a/hw/misc/ax650x-dwmac-glue.c b/hw/misc/ax650x-dwmac-glue.c new file mode 100644 index 0000000000000..8840f1eef005c --- /dev/null +++ b/hw/misc/ax650x-dwmac-glue.c @@ -0,0 +1,165 @@ +/* + * Axera AX650X DWMAC clock/reset/interface glue + * + * The register locations and bit assignments are extracted from the vendor + * Linux 5.15.73 dwmac-axera driver. This model stores the RGMII interface, + * speed mux, and clock enable controls and implements software-reset set/clear + * aliases. It does not model clock waveforms or RGMII timing. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/registerfields.h" +#include "hw/misc/ax650x-dwmac-glue.h" +#include "migration/vmstate.h" +#include "qapi/error.h" +#include "qemu/module.h" + +REG32(GLB_EMAC1_PHY_IF, 0x94) +REG32(GLB_EMAC0_PHY_IF, 0x9c) + +REG32(CLK_MUX, 0x00) +REG32(CLK_ENABLE0, 0x04) +REG32(CLK_ENABLE1, 0x08) +REG32(CLK_DIV, 0x0c) +REG32(SW_RESET0, 0x10) +REG32(SW_RESET0_SET, 0x38) +REG32(SW_RESET0_CLEAR, 0x3c) + +static void ax650x_dwmac_reset_set_postw(RegisterInfo *reg, uint64_t value) +{ + AX650XDWMACGlueState *s = AX650X_DWMAC_GLUE(reg->opaque); + uint32_t mask = BIT(s->port ? 2 : 7); + + s->clk_regs[R_SW_RESET0] |= value & mask; + s->clk_regs[R_SW_RESET0_SET] = 0; +} + +static void ax650x_dwmac_reset_clear_postw(RegisterInfo *reg, uint64_t value) +{ + AX650XDWMACGlueState *s = AX650X_DWMAC_GLUE(reg->opaque); + uint32_t mask = BIT(s->port ? 2 : 7); + + s->clk_regs[R_SW_RESET0] &= ~(value & mask); + s->clk_regs[R_SW_RESET0_CLEAR] = 0; +} + +static const RegisterAccessInfo ax650x_dwmac_glb0_regs_info[] = { + { .name = "EMAC0_PHY_IF", .addr = A_GLB_EMAC0_PHY_IF }, +}; + +static const RegisterAccessInfo ax650x_dwmac_glb1_regs_info[] = { + { .name = "EMAC1_PHY_IF", .addr = A_GLB_EMAC1_PHY_IF }, +}; + +static const RegisterAccessInfo ax650x_dwmac_clk_regs_info[] = { + { .name = "CLK_MUX", .addr = A_CLK_MUX }, + { .name = "CLK_ENABLE0", .addr = A_CLK_ENABLE0 }, + { .name = "CLK_ENABLE1", .addr = A_CLK_ENABLE1 }, + { .name = "CLK_DIV", .addr = A_CLK_DIV }, + { .name = "SW_RESET0", .addr = A_SW_RESET0, + .ro = MAKE_64BIT_MASK(0, 32), + }, + { .name = "SW_RESET0_SET", .addr = A_SW_RESET0_SET, + .post_write = ax650x_dwmac_reset_set_postw, + }, + { .name = "SW_RESET0_CLEAR", .addr = A_SW_RESET0_CLEAR, + .post_write = ax650x_dwmac_reset_clear_postw, + }, +}; + +static const MemoryRegionOps ax650x_dwmac_glue_ops = { + .read = register_read_memory, + .write = register_write_memory, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void ax650x_dwmac_glue_reset(DeviceState *dev) +{ + AX650XDWMACGlueState *s = AX650X_DWMAC_GLUE(dev); + + for (unsigned int i = 0; i < AX650X_DWMAC_GLB_NR_REGS; i++) { + register_reset(&s->glb_regs_info[i]); + } + for (unsigned int i = 0; i < AX650X_DWMAC_CLK_NR_REGS; i++) { + register_reset(&s->clk_regs_info[i]); + } +} + +static void ax650x_dwmac_glue_realize(DeviceState *dev, Error **errp) +{ + AX650XDWMACGlueState *s = AX650X_DWMAC_GLUE(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + const RegisterAccessInfo *glb_regs; + size_t glb_nr_regs; + + if (s->port > 1) { + error_setg(errp, "ax650x-dwmac-glue: port must be 0 or 1"); + return; + } + + if (s->port == 0) { + glb_regs = ax650x_dwmac_glb0_regs_info; + glb_nr_regs = ARRAY_SIZE(ax650x_dwmac_glb0_regs_info); + } else { + glb_regs = ax650x_dwmac_glb1_regs_info; + glb_nr_regs = ARRAY_SIZE(ax650x_dwmac_glb1_regs_info); + } + + s->glb_reg_array = register_init_block32( + dev, glb_regs, glb_nr_regs, s->glb_regs_info, s->glb_regs, + &ax650x_dwmac_glue_ops, false, AX650X_DWMAC_GLUE_MMIO_SIZE); + s->clk_reg_array = register_init_block32( + dev, ax650x_dwmac_clk_regs_info, + ARRAY_SIZE(ax650x_dwmac_clk_regs_info), s->clk_regs_info, + s->clk_regs, &ax650x_dwmac_glue_ops, false, + AX650X_DWMAC_GLUE_MMIO_SIZE); + + sysbus_init_mmio(sbd, &s->glb_reg_array->mem); + sysbus_init_mmio(sbd, &s->clk_reg_array->mem); +} + +static const VMStateDescription vmstate_ax650x_dwmac_glue = { + .name = TYPE_AX650X_DWMAC_GLUE, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(glb_regs, AX650XDWMACGlueState, + AX650X_DWMAC_GLB_NR_REGS), + VMSTATE_UINT32_ARRAY(clk_regs, AX650XDWMACGlueState, + AX650X_DWMAC_CLK_NR_REGS), + VMSTATE_END_OF_LIST(), + }, +}; + +static const Property ax650x_dwmac_glue_properties[] = { + DEFINE_PROP_UINT8("port", AX650XDWMACGlueState, port, 0), +}; + +static void ax650x_dwmac_glue_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "AX650X DWMAC clock/reset/interface glue"; + dc->realize = ax650x_dwmac_glue_realize; + device_class_set_legacy_reset(dc, ax650x_dwmac_glue_reset); + dc->vmsd = &vmstate_ax650x_dwmac_glue; + device_class_set_props(dc, ax650x_dwmac_glue_properties); +} + +static const TypeInfo ax650x_dwmac_glue_types[] = { + { + .name = TYPE_AX650X_DWMAC_GLUE, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AX650XDWMACGlueState), + .class_init = ax650x_dwmac_glue_class_init, + }, +}; +DEFINE_TYPES(ax650x_dwmac_glue_types) diff --git a/hw/misc/ax650x-hwspinlock.c b/hw/misc/ax650x-hwspinlock.c new file mode 100644 index 0000000000000..e6c92d928ddbc --- /dev/null +++ b/hw/misc/ax650x-hwspinlock.c @@ -0,0 +1,109 @@ +/* + * AXERA AX650X hardware spinlock + * + * The AXERA Linux driver exposes 32 locks. Each lock has an acquire register + * at lock_id * 8 and an unlock register at lock_id * 8 + 4. Reading an + * unlocked acquire register claims the lock for CPU master ID 0 and returns + * that ID (zero); a later read reports the lock busy. The vendor driver + * converts the zero owner ID into a successful hwspinlock trylock result. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/misc/ax650x-hwspinlock.h" +#include "migration/vmstate.h" +#include "qemu/bitops.h" +#include "qemu/module.h" + +#define AX650X_HWSPINLOCK_STRIDE 8 + +static uint64_t ax650x_hwspinlock_read(void *opaque, hwaddr offset, + unsigned int size) +{ + AX650XHWSpinlockState *s = AX650X_HWSPINLOCK(opaque); + unsigned int lock = offset / AX650X_HWSPINLOCK_STRIDE; + + if (lock >= AX650X_HWSPINLOCK_COUNT || + offset % AX650X_HWSPINLOCK_STRIDE != 0) { + return 0; + } + + if (s->locked & BIT(lock)) { + return 1; + } + + s->locked |= BIT(lock); + return 0; +} + +static void ax650x_hwspinlock_write(void *opaque, hwaddr offset, + uint64_t value, unsigned int size) +{ + AX650XHWSpinlockState *s = AX650X_HWSPINLOCK(opaque); + unsigned int lock = offset / AX650X_HWSPINLOCK_STRIDE; + + if (lock < AX650X_HWSPINLOCK_COUNT && + offset % AX650X_HWSPINLOCK_STRIDE == sizeof(uint32_t)) { + s->locked &= ~BIT(lock); + } +} + +static const MemoryRegionOps ax650x_hwspinlock_ops = { + .read = ax650x_hwspinlock_read, + .write = ax650x_hwspinlock_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void ax650x_hwspinlock_reset(DeviceState *dev) +{ + AX650XHWSpinlockState *s = AX650X_HWSPINLOCK(dev); + + s->locked = 0; +} + +static void ax650x_hwspinlock_init(Object *obj) +{ + AX650XHWSpinlockState *s = AX650X_HWSPINLOCK(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + memory_region_init_io(&s->iomem, obj, &ax650x_hwspinlock_ops, s, + TYPE_AX650X_HWSPINLOCK, + AX650X_HWSPINLOCK_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->iomem); +} + +static const VMStateDescription vmstate_ax650x_hwspinlock = { + .name = TYPE_AX650X_HWSPINLOCK, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(locked, AX650XHWSpinlockState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void ax650x_hwspinlock_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "AX650X hardware spinlock"; + device_class_set_legacy_reset(dc, ax650x_hwspinlock_reset); + dc->vmsd = &vmstate_ax650x_hwspinlock; +} + +static const TypeInfo ax650x_hwspinlock_types[] = { + { + .name = TYPE_AX650X_HWSPINLOCK, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AX650XHWSpinlockState), + .instance_init = ax650x_hwspinlock_init, + .class_init = ax650x_hwspinlock_class_init, + }, +}; +DEFINE_TYPES(ax650x_hwspinlock_types) diff --git a/hw/misc/cv1800b_clk.c b/hw/misc/cv1800b_clk.c new file mode 100644 index 0000000000000..db7e626158211 --- /dev/null +++ b/hw/misc/cv1800b_clk.c @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Sophgo CV1800B Clock Controller + * + * Copyright (c) 2026 Kuan-Wei Chiu + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/misc/cv1800b_clk.h" + +#define REG_BYTE_WIDTH (4) +#define REG_CLK_BYP_0 (0x030 / REG_BYTE_WIDTH) +#define REG_CLK_BYP_1 (0x034 / REG_BYTE_WIDTH) + +static uint64_t cv1800b_clk_read(void *opaque, hwaddr addr, unsigned int size) +{ + CV1800BClkState *s = opaque; + uint32_t val = 0; + + if ((addr / 4) < ARRAY_SIZE(s->regs)) { + val = s->regs[addr / 4]; + } + + return val; +} + +static void cv1800b_clk_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size) +{ + CV1800BClkState *s = opaque; + + if ((addr / 4) < ARRAY_SIZE(s->regs)) { + s->regs[addr / 4] = val; + } +} + +static const MemoryRegionOps cv1800b_clk_ops = { + .read = cv1800b_clk_read, + .write = cv1800b_clk_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void cv1800b_clk_reset_hold(Object *obj, ResetType type) +{ + CV1800BClkState *s = CV1800B_CLK(obj); + + memset(s->regs, 0, sizeof(s->regs)); + + /* + * TODO: Implement proper PLL state machines. + * For now, use POR default to bypass PLLs and boot via 25MHz XTAL. + */ + s->regs[REG_CLK_BYP_0] = 0xFFFFFFFF; + s->regs[REG_CLK_BYP_1] = 0xFFFFFFFF; +} + +static void cv1800b_clk_init(Object *obj) +{ + CV1800BClkState *s = CV1800B_CLK(obj); + + memory_region_init_io(&s->iomem, obj, &cv1800b_clk_ops, s, + TYPE_CV1800B_CLK, 0x1000); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem); +} + +static void cv1800b_clk_class_init(ObjectClass *klass, const void *data) +{ + ResettableClass *rc = RESETTABLE_CLASS(klass); + + rc->phases.hold = cv1800b_clk_reset_hold; +} + +static const TypeInfo cv1800b_clk_info = { + .name = TYPE_CV1800B_CLK, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(CV1800BClkState), + .instance_init = cv1800b_clk_init, + .class_init = cv1800b_clk_class_init, +}; + +static void cv1800b_clk_register_types(void) +{ + type_register_static(&cv1800b_clk_info); +} + +type_init(cv1800b_clk_register_types) diff --git a/hw/misc/k230_adc.c b/hw/misc/k230_adc.c new file mode 100644 index 0000000000000..6db289fc1e163 --- /dev/null +++ b/hw/misc/k230_adc.c @@ -0,0 +1,136 @@ +/* + * K230 ADC register block + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "migration/vmstate.h" +#include "hw/misc/k230_adc.h" + +#define K230_ADC_CFG 0x04 +#define K230_ADC_DATA_BASE 0x14 +#define K230_ADC_CHANNELS 6 + +static uint64_t k230_adc_read_bytes(uint8_t *regs, hwaddr addr, + unsigned int size) +{ + uint64_t val = 0; + + for (int i = 0; i < size; i++) { + val |= (uint64_t)regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_adc_write_bytes(uint8_t *regs, hwaddr addr, uint64_t val, + unsigned int size) +{ + for (int i = 0; i < size; i++) { + regs[addr + i] = val >> (i * 8); + } +} + +static uint32_t k230_adc_sample(unsigned int channel) +{ + return 0x800 + channel * 0x40; +} + +static void k230_adc_update_samples(K230AdcState *s) +{ + for (int i = 0; i < K230_ADC_CHANNELS; i++) { + stl_le_p(s->regs + K230_ADC_DATA_BASE + i * 4, k230_adc_sample(i)); + } +} + +static uint64_t k230_adc_read(void *opaque, hwaddr addr, unsigned int size) +{ + return k230_adc_read_bytes(K230_ADC(opaque)->regs, addr, size); +} + +static void k230_adc_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230AdcState *s = K230_ADC(opaque); + + k230_adc_write_bytes(s->regs, addr, val, size); + + if (addr == K230_ADC_CFG && size == 4 && (val & 0x10)) { + unsigned int channel = val & 0x7; + + if (channel < K230_ADC_CHANNELS) { + stl_le_p(s->regs + K230_ADC_DATA_BASE + channel * 4, + k230_adc_sample(channel)); + } + } +} + +static const MemoryRegionOps k230_adc_ops = { + .read = k230_adc_read, + .write = k230_adc_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_adc_reset(DeviceState *dev) +{ + K230AdcState *s = K230_ADC(dev); + + memset(s->regs, 0, sizeof(s->regs)); + k230_adc_update_samples(s); +} + +static const VMStateDescription vmstate_k230_adc = { + .name = TYPE_K230_ADC, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, K230AdcState, K230_ADC_SIZE), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_adc_realize(DeviceState *dev, Error **errp) +{ + K230AdcState *s = K230_ADC(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_adc_ops, s, + TYPE_K230_ADC, K230_ADC_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); +} + +static void k230_adc_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_adc_realize; + device_class_set_legacy_reset(dc, k230_adc_reset); + dc->vmsd = &vmstate_k230_adc; + dc->desc = "K230 ADC register block"; +} + +static const TypeInfo k230_adc_type_info = { + .name = TYPE_K230_ADC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230AdcState), + .class_init = k230_adc_class_init, +}; + +static void k230_register_adc_types(void) +{ + type_register_static(&k230_adc_type_info); +} + +type_init(k230_register_adc_types) diff --git a/hw/misc/k230_dewarp.c b/hw/misc/k230_dewarp.c new file mode 100644 index 0000000000000..52526defe9f8b --- /dev/null +++ b/hw/misc/k230_dewarp.c @@ -0,0 +1,267 @@ +/* + * K230 DW200 dewarp engine + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "hw/core/irq.h" +#include "hw/misc/k230_dewarp.h" +#include "migration/vmstate.h" +#include "trace.h" + +#define K230_DWE_CTRL 0xc04 +#define K230_DWE_CTRL_LOW 0x004 +#define K230_DWE_DMA_START0 0x010 +#define K230_DWE_DMA_START1 0x014 +#define K230_DWE_IRQ_STATUS 0xc70 +#define K230_DWE_IRQ_STATUS_LOW 0x070 +#define K230_DWE_BUS_CTRL_LOW 0x074 +#define K230_DWE_IRQ_CLEAR 0xd00 +#define K230_FE_START 0xd04 +#define K230_FE_IRQ_STATUS 0xd14 +#define K230_FE_IRQ_CLEAR 0xd18 +#define K230_VSE_CTRL 0x004 +#define K230_VSE_DMA_TRIGGER 0x9e8 +#define K230_VSE_IRQ_STATUS 0xa50 +#define K230_VSE_IRQ_CLEAR 0xa58 + +#define K230_DWE_START BIT(1) +#define K230_FE_START_CMD BIT(16) +#define K230_VSE_START_DMA BIT(14) + +static bool k230_dewarp_access_hits(hwaddr addr, unsigned int size, + hwaddr offset) +{ + return addr <= offset && offset < addr + size; +} + +static uint32_t k230_dewarp_read32(K230DewarpState *s, hwaddr addr) +{ + uint32_t val = 0; + + if (addr > K230_DEWARP_SIZE - sizeof(val)) { + return 0; + } + + for (int i = 0; i < 4; i++) { + val |= (uint32_t)s->regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_dewarp_write32(K230DewarpState *s, hwaddr addr, uint32_t val) +{ + if (addr > K230_DEWARP_SIZE - sizeof(val)) { + return; + } + + for (int i = 0; i < 4; i++) { + s->regs[addr + i] = val >> (i * 8); + } +} + +static void k230_dewarp_set_irq(qemu_irq irq, bool *level, bool value) +{ + *level = value; + qemu_set_irq(irq, value); +} + +static void k230_dewarp_raise_dwe(K230DewarpState *s) +{ + k230_dewarp_write32(s, K230_DWE_IRQ_STATUS, 1); + k230_dewarp_write32(s, K230_DWE_IRQ_STATUS_LOW, 1); + trace_k230_dewarp_irq("dwe", true); + k230_dewarp_set_irq(s->dwe_irq, &s->dwe_irq_level, true); +} + +static void k230_dewarp_raise_fe(K230DewarpState *s) +{ + k230_dewarp_write32(s, K230_FE_IRQ_STATUS, 1); + trace_k230_dewarp_irq("fe", true); + k230_dewarp_set_irq(s->fe_irq, &s->fe_irq_level, true); +} + +static void k230_dewarp_raise_vse(K230DewarpState *s) +{ + k230_dewarp_write32(s, K230_VSE_IRQ_STATUS, 0x7); + trace_k230_dewarp_irq("vse", true); + k230_dewarp_set_irq(s->vse_irq, &s->vse_irq_level, true); +} + +static void k230_dewarp_clear_dwe(K230DewarpState *s) +{ + k230_dewarp_write32(s, K230_DWE_IRQ_STATUS, 0); + k230_dewarp_write32(s, K230_DWE_IRQ_STATUS_LOW, 0); + trace_k230_dewarp_irq("dwe", false); + k230_dewarp_set_irq(s->dwe_irq, &s->dwe_irq_level, false); +} + +static void k230_dewarp_clear_fe(K230DewarpState *s) +{ + k230_dewarp_write32(s, K230_FE_IRQ_STATUS, 0); + trace_k230_dewarp_irq("fe", false); + k230_dewarp_set_irq(s->fe_irq, &s->fe_irq_level, false); +} + +static void k230_dewarp_clear_vse(K230DewarpState *s) +{ + k230_dewarp_write32(s, K230_VSE_IRQ_STATUS, 0); + trace_k230_dewarp_irq("vse", false); + k230_dewarp_set_irq(s->vse_irq, &s->vse_irq_level, false); +} + +static uint64_t k230_dewarp_read(void *opaque, hwaddr addr, unsigned int size) +{ + K230DewarpState *s = K230_DEWARP(opaque); + uint64_t val = 0; + + if (addr >= K230_DEWARP_SIZE || size > K230_DEWARP_SIZE - addr) { + return 0; + } + + for (int i = 0; i < size; i++) { + val |= (uint64_t)s->regs[addr + i] << (i * 8); + } + + trace_k230_dewarp_read(addr, val, size); + + return val; +} + +static void k230_dewarp_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230DewarpState *s = K230_DEWARP(opaque); + bool dwe_start; + + if (addr >= K230_DEWARP_SIZE || size > K230_DEWARP_SIZE - addr) { + return; + } + + for (int i = 0; i < size; i++) { + s->regs[addr + i] = val >> (i * 8); + } + + trace_k230_dewarp_write(addr, val, size); + + if (k230_dewarp_access_hits(addr, size, K230_DWE_IRQ_CLEAR) && val) { + k230_dewarp_clear_dwe(s); + } + if (k230_dewarp_access_hits(addr, size, K230_DWE_IRQ_STATUS_LOW) && val) { + k230_dewarp_clear_dwe(s); + } + if (k230_dewarp_access_hits(addr, size, K230_FE_IRQ_CLEAR) && val) { + k230_dewarp_clear_fe(s); + } + if (k230_dewarp_access_hits(addr, size, K230_VSE_IRQ_CLEAR) && val) { + k230_dewarp_clear_vse(s); + } + + if (k230_dewarp_access_hits(addr, size, K230_FE_START) && + (k230_dewarp_read32(s, K230_FE_START) & K230_FE_START_CMD)) { + k230_dewarp_raise_fe(s); + } + + dwe_start = k230_dewarp_access_hits(addr, size, K230_DWE_CTRL) && + (k230_dewarp_read32(s, K230_DWE_CTRL) & K230_DWE_START); + if (dwe_start || + (k230_dewarp_access_hits(addr, size, K230_DWE_CTRL_LOW) && + (k230_dewarp_read32(s, K230_DWE_CTRL_LOW) & K230_DWE_START))) { + k230_dewarp_raise_dwe(s); + if (dwe_start) { + k230_dewarp_raise_vse(s); + } + } + + if ((k230_dewarp_access_hits(addr, size, K230_VSE_CTRL) && + (k230_dewarp_read32(s, K230_VSE_CTRL) & K230_VSE_START_DMA)) || + (k230_dewarp_access_hits(addr, size, K230_VSE_DMA_TRIGGER) && val)) { + k230_dewarp_raise_vse(s); + } + + if (k230_dewarp_access_hits(addr, size, K230_DWE_BUS_CTRL_LOW) && + (val & BIT(31))) { + k230_dewarp_write32(s, K230_DWE_BUS_CTRL_LOW, + k230_dewarp_read32(s, K230_DWE_BUS_CTRL_LOW) | + BIT(31)); + } +} + +static const MemoryRegionOps k230_dewarp_ops = { + .read = k230_dewarp_read, + .write = k230_dewarp_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 8, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + .unaligned = true, + }, +}; + +static void k230_dewarp_reset(DeviceState *dev) +{ + K230DewarpState *s = K230_DEWARP(dev); + + memset(s->regs, 0, sizeof(s->regs)); + k230_dewarp_set_irq(s->dwe_irq, &s->dwe_irq_level, false); + k230_dewarp_set_irq(s->fe_irq, &s->fe_irq_level, false); + k230_dewarp_set_irq(s->vse_irq, &s->vse_irq_level, false); +} + +static const VMStateDescription vmstate_k230_dewarp = { + .name = TYPE_K230_DEWARP, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_BOOL(dwe_irq_level, K230DewarpState), + VMSTATE_BOOL(fe_irq_level, K230DewarpState), + VMSTATE_BOOL(vse_irq_level, K230DewarpState), + VMSTATE_UINT8_ARRAY(regs, K230DewarpState, K230_DEWARP_SIZE), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_dewarp_realize(DeviceState *dev, Error **errp) +{ + K230DewarpState *s = K230_DEWARP(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_dewarp_ops, s, + TYPE_K230_DEWARP, K230_DEWARP_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->dwe_irq); + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->fe_irq); + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->vse_irq); +} + +static void k230_dewarp_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_dewarp_realize; + device_class_set_legacy_reset(dc, k230_dewarp_reset); + dc->vmsd = &vmstate_k230_dewarp; + dc->desc = "K230 DW200 dewarp engine"; +} + +static const TypeInfo k230_dewarp_type_info = { + .name = TYPE_K230_DEWARP, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230DewarpState), + .class_init = k230_dewarp_class_init, +}; + +static void k230_dewarp_register_types(void) +{ + type_register_static(&k230_dewarp_type_info); +} + +type_init(k230_dewarp_register_types) diff --git a/hw/misc/k230_gpio.c b/hw/misc/k230_gpio.c new file mode 100644 index 0000000000000..273cfcc7486ab --- /dev/null +++ b/hw/misc/k230_gpio.c @@ -0,0 +1,286 @@ +/* + * K230 GPIO register block + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/module.h" +#include "migration/vmstate.h" +#include "hw/core/irq.h" +#include "hw/misc/k230_gpio.h" + +#define K230_GPIO_SWPORTA_DR 0x00 +#define K230_GPIO_SWPORTA_DDR 0x04 +#define K230_GPIO_SWPORTB_DR 0x0c +#define K230_GPIO_SWPORTB_DDR 0x10 +#define K230_GPIO_SWPORTC_DR 0x18 +#define K230_GPIO_SWPORTC_DDR 0x1c +#define K230_GPIO_SWPORTD_DR 0x24 +#define K230_GPIO_SWPORTD_DDR 0x28 +#define K230_GPIO_INTEN 0x30 +#define K230_GPIO_INTMASK 0x34 +#define K230_GPIO_INTTYPE_LEVEL 0x38 +#define K230_GPIO_INT_POLARITY 0x3c +#define K230_GPIO_INTSTATUS 0x40 +#define K230_GPIO_PORTA_DEBOUNCE 0x48 +#define K230_GPIO_PORTA_EOI 0x4c +#define K230_GPIO_EXT_PORTA 0x50 +#define K230_GPIO_EXT_PORTB 0x54 +#define K230_GPIO_EXT_PORTC 0x58 +#define K230_GPIO_EXT_PORTD 0x5c +#define K230_GPIO_INTTYPE_BOTHEDGE 0x68 +#define K230_GPIO_INTSTATUS_V2 0x3c +#define K230_GPIO_PORTA_EOI_V2 0x40 + +#define K230_GPIO_INTMASK_V2 0x44 +#define K230_GPIO_INTTYPE_LEVEL_V2 0x34 +#define K230_GPIO_INT_POLARITY_V2 0x38 + +static uint64_t k230_gpio_read_bytes(uint8_t *regs, hwaddr addr, + unsigned int size) +{ + uint64_t val = 0; + + for (int i = 0; i < size; i++) { + val |= (uint64_t)regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_gpio_write_bytes(uint8_t *regs, hwaddr addr, uint64_t val, + unsigned int size) +{ + for (int i = 0; i < size; i++) { + regs[addr + i] = val >> (i * 8); + } +} + +static uint32_t k230_gpio_reg_read32(K230GpioState *s, hwaddr addr) +{ + return ldl_le_p(s->regs + addr); +} + +static void k230_gpio_reg_write32(K230GpioState *s, hwaddr addr, uint32_t val) +{ + stl_le_p(s->regs + addr, val); +} + +static uint32_t k230_gpio_port_value(K230GpioState *s, hwaddr dr, + hwaddr ddr, uint32_t input) +{ + uint32_t data = k230_gpio_reg_read32(s, dr); + uint32_t dir = k230_gpio_reg_read32(s, ddr); + + return (data & dir) | (input & ~dir); +} + +static uint32_t k230_gpio_pending(K230GpioState *s) +{ + uint32_t status = k230_gpio_reg_read32(s, K230_GPIO_INTSTATUS); + uint32_t enable = k230_gpio_reg_read32(s, K230_GPIO_INTEN); + uint32_t mask = k230_gpio_reg_read32(s, K230_GPIO_INTMASK); + + return status & enable & ~mask; +} + +static void k230_gpio_update_irq(K230GpioState *s) +{ + uint32_t pending = k230_gpio_pending(s); + + for (int i = 0; i < K230_GPIO_IRQ_COUNT; i++) { + qemu_set_irq(s->irq[i], extract32(pending, i, 1)); + } +} + +static void k230_gpio_update_level_status(K230GpioState *s) +{ + uint32_t status = k230_gpio_reg_read32(s, K230_GPIO_INTSTATUS); + uint32_t type = k230_gpio_reg_read32(s, K230_GPIO_INTTYPE_LEVEL); + uint32_t polarity = k230_gpio_reg_read32(s, K230_GPIO_INT_POLARITY); + uint32_t bothedge = k230_gpio_reg_read32(s, K230_GPIO_INTTYPE_BOTHEDGE); + uint32_t level_mask = ~(type | bothedge); + uint32_t active = (s->input & polarity) | (~s->input & ~polarity); + + status &= ~level_mask; + status |= active & level_mask; + k230_gpio_reg_write32(s, K230_GPIO_INTSTATUS, status); +} + +static void k230_gpio_set_input(void *opaque, int line, int level) +{ + K230GpioState *s = K230_GPIO(opaque); + uint32_t old = s->input; + uint32_t mask = BIT(line); + uint32_t changed; + uint32_t rising; + uint32_t falling; + uint32_t type; + uint32_t polarity; + uint32_t bothedge; + uint32_t status; + + if (level) { + s->input |= mask; + } else { + s->input &= ~mask; + } + + changed = old ^ s->input; + if (!changed) { + return; + } + + type = k230_gpio_reg_read32(s, K230_GPIO_INTTYPE_LEVEL); + polarity = k230_gpio_reg_read32(s, K230_GPIO_INT_POLARITY); + bothedge = k230_gpio_reg_read32(s, K230_GPIO_INTTYPE_BOTHEDGE); + status = k230_gpio_reg_read32(s, K230_GPIO_INTSTATUS); + rising = changed & s->input; + falling = changed & ~s->input; + + status |= changed & bothedge; + status |= rising & type & polarity; + status |= falling & type & ~polarity; + k230_gpio_reg_write32(s, K230_GPIO_INTSTATUS, status); + k230_gpio_update_level_status(s); + s->last_input = s->input; + k230_gpio_update_irq(s); +} + +static uint64_t k230_gpio_read(void *opaque, hwaddr addr, unsigned int size) +{ + K230GpioState *s = K230_GPIO(opaque); + + if (size == 4) { + switch (addr) { + case K230_GPIO_EXT_PORTA: + return k230_gpio_port_value(s, K230_GPIO_SWPORTA_DR, + K230_GPIO_SWPORTA_DDR, s->input); + case K230_GPIO_EXT_PORTB: + return k230_gpio_port_value(s, K230_GPIO_SWPORTB_DR, + K230_GPIO_SWPORTB_DDR, 0); + case K230_GPIO_EXT_PORTC: + return k230_gpio_port_value(s, K230_GPIO_SWPORTC_DR, + K230_GPIO_SWPORTC_DDR, 0); + case K230_GPIO_EXT_PORTD: + return k230_gpio_port_value(s, K230_GPIO_SWPORTD_DR, + K230_GPIO_SWPORTD_DDR, 0); + default: + break; + } + } + + return k230_gpio_read_bytes(s->regs, addr, size); +} + +static void k230_gpio_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230GpioState *s = K230_GPIO(opaque); + + k230_gpio_write_bytes(s->regs, addr, val, size); + + if (size == 4 && addr == K230_GPIO_PORTA_EOI) { + k230_gpio_reg_write32(s, K230_GPIO_INTSTATUS, + k230_gpio_reg_read32(s, K230_GPIO_INTSTATUS) & + ~(uint32_t)val); + k230_gpio_update_level_status(s); + } else if (size == 4 && addr == K230_GPIO_PORTA_EOI_V2) { + k230_gpio_reg_write32(s, K230_GPIO_INTSTATUS_V2, + k230_gpio_reg_read32(s, K230_GPIO_INTSTATUS_V2) & + ~(uint32_t)val); + } else if (size == 4 && + (addr == K230_GPIO_INTEN || + addr == K230_GPIO_INTMASK || + addr == K230_GPIO_INTTYPE_LEVEL || + addr == K230_GPIO_INT_POLARITY || + addr == K230_GPIO_INTTYPE_BOTHEDGE || + addr == K230_GPIO_INTMASK_V2 || + addr == K230_GPIO_INTTYPE_LEVEL_V2 || + addr == K230_GPIO_INT_POLARITY_V2)) { + k230_gpio_update_level_status(s); + } + + k230_gpio_update_irq(s); +} + +static const MemoryRegionOps k230_gpio_ops = { + .read = k230_gpio_read, + .write = k230_gpio_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_gpio_reset(DeviceState *dev) +{ + K230GpioState *s = K230_GPIO(dev); + + memset(s->regs, 0, sizeof(s->regs)); + s->input = 0; + s->last_input = 0; + k230_gpio_update_irq(s); +} + +static const VMStateDescription vmstate_k230_gpio = { + .name = TYPE_K230_GPIO, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, K230GpioState, K230_GPIO_SIZE), + VMSTATE_UINT32(input, K230GpioState), + VMSTATE_UINT32(last_input, K230GpioState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_gpio_realize(DeviceState *dev, Error **errp) +{ + K230GpioState *s = K230_GPIO(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + for (int i = 0; i < K230_GPIO_IRQ_COUNT; i++) { + sysbus_init_irq(sbd, &s->irq[i]); + } + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_gpio_ops, s, + TYPE_K230_GPIO, K230_GPIO_SIZE); + sysbus_init_mmio(sbd, &s->mmio); + qdev_init_gpio_in_named(dev, k230_gpio_set_input, "input", + K230_GPIO_IRQ_COUNT); +} + +static void k230_gpio_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_gpio_realize; + device_class_set_legacy_reset(dc, k230_gpio_reset); + dc->vmsd = &vmstate_k230_gpio; + dc->desc = "K230 GPIO register block"; +} + +static const TypeInfo k230_gpio_type_info = { + .name = TYPE_K230_GPIO, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230GpioState), + .class_init = k230_gpio_class_init, +}; + +static void k230_register_gpio_types(void) +{ + type_register_static(&k230_gpio_type_info); +} + +type_init(k230_register_gpio_types) diff --git a/hw/misc/k230_hardlock.c b/hw/misc/k230_hardlock.c new file mode 100644 index 0000000000000..625a4a272d984 --- /dev/null +++ b/hw/misc/k230_hardlock.c @@ -0,0 +1,260 @@ +/* + * K230 hardlock registers + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/module.h" +#include "migration/vmstate.h" +#include "hw/misc/k230_hardlock.h" + +#define K230_IPCM_CPU2DSP_INT_EN0 0x0000 +#define K230_IPCM_CPU2DSP_INT_SET0 0x0004 +#define K230_IPCM_CPU2DSP_INT_CLEAR0 0x0008 +#define K230_IPCM_CPU2DSP_INT_STATUS0 0x000c +#define K230_IPCM_CPU2DSP_INT_ERR0 0x0010 +#define K230_IPCM_DSP2CPU_INT_EN0 0x0014 +#define K230_IPCM_DSP2CPU_INT_SET0 0x0018 +#define K230_IPCM_DSP2CPU_INT_CLEAR0 0x001c +#define K230_IPCM_DSP2CPU_INT_STATUS0 0x0020 +#define K230_IPCM_DSP2CPU_INT_ERR0 0x0024 + +#define K230_IPCM_INT_ENABLE BIT(0) +#define K230_HARDLOCK_BASE 0xa0 + +static uint32_t k230_hardlock_readl(K230HardlockState *s, hwaddr addr) +{ + return ldl_le_p(s->regs + addr); +} + +static void k230_hardlock_writel(K230HardlockState *s, hwaddr addr, + uint32_t val) +{ + stl_le_p(s->regs + addr, val); +} + +static uint64_t k230_hardlock_read_bytes(uint8_t *regs, hwaddr addr, + unsigned int size) +{ + uint64_t val = 0; + + for (int i = 0; i < size; i++) { + val |= (uint64_t)regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_hardlock_write_bytes(uint8_t *regs, hwaddr addr, + uint64_t val, unsigned int size) +{ + for (int i = 0; i < size; i++) { + regs[addr + i] = val >> (i * 8); + } +} + +static bool k230_hardlock_index(hwaddr addr, unsigned int size, + unsigned int *index) +{ + hwaddr offset; + + if (size != 4 || addr < K230_HARDLOCK_BASE) { + return false; + } + + offset = addr - K230_HARDLOCK_BASE; + if (offset >= K230_HARDLOCK_COUNT * 4 || (offset & 3)) { + return false; + } + + *index = offset / 4; + return true; +} + +static uint32_t k230_ipcm_channel_mask(uint64_t val) +{ + if (val < 32) { + return BIT(val); + } + + return val; +} + +static void k230_ipcm_update_dsp2cpu_irq(K230HardlockState *s) +{ + uint32_t enable = k230_hardlock_readl(s, K230_IPCM_DSP2CPU_INT_EN0); + uint32_t status = k230_hardlock_readl(s, K230_IPCM_DSP2CPU_INT_STATUS0); + bool irq_enable = enable & K230_IPCM_INT_ENABLE; + + for (int i = 0; i < K230_HARDLOCK_IPCM_IRQ_COUNT; i++) { + qemu_set_irq(s->irqs[i], irq_enable && (status & BIT(i))); + } +} + +static bool k230_ipcm_write(K230HardlockState *s, hwaddr addr, uint64_t val, + unsigned int size) +{ + uint32_t mask; + uint32_t status; + + if (size != 4) { + return false; + } + + switch (addr) { + case K230_IPCM_CPU2DSP_INT_EN0: + case K230_IPCM_CPU2DSP_INT_ERR0: + case K230_IPCM_DSP2CPU_INT_ERR0: + k230_hardlock_writel(s, addr, val); + return true; + + case K230_IPCM_DSP2CPU_INT_EN0: + k230_hardlock_writel(s, addr, val); + k230_ipcm_update_dsp2cpu_irq(s); + return true; + + case K230_IPCM_CPU2DSP_INT_SET0: + mask = k230_ipcm_channel_mask(val); + status = k230_hardlock_readl(s, K230_IPCM_CPU2DSP_INT_STATUS0); + k230_hardlock_writel(s, K230_IPCM_CPU2DSP_INT_SET0, val); + k230_hardlock_writel(s, K230_IPCM_CPU2DSP_INT_STATUS0, status | mask); + return true; + + case K230_IPCM_CPU2DSP_INT_CLEAR0: + mask = k230_ipcm_channel_mask(val); + status = k230_hardlock_readl(s, K230_IPCM_CPU2DSP_INT_STATUS0); + k230_hardlock_writel(s, K230_IPCM_CPU2DSP_INT_CLEAR0, val); + k230_hardlock_writel(s, K230_IPCM_CPU2DSP_INT_STATUS0, + status & ~mask); + return true; + + case K230_IPCM_DSP2CPU_INT_SET0: + mask = k230_ipcm_channel_mask(val); + status = k230_hardlock_readl(s, K230_IPCM_DSP2CPU_INT_STATUS0); + k230_hardlock_writel(s, K230_IPCM_DSP2CPU_INT_SET0, val); + k230_hardlock_writel(s, K230_IPCM_DSP2CPU_INT_STATUS0, status | mask); + k230_ipcm_update_dsp2cpu_irq(s); + return true; + + case K230_IPCM_DSP2CPU_INT_CLEAR0: + mask = k230_ipcm_channel_mask(val); + status = k230_hardlock_readl(s, K230_IPCM_DSP2CPU_INT_STATUS0); + k230_hardlock_writel(s, K230_IPCM_DSP2CPU_INT_CLEAR0, val); + k230_hardlock_writel(s, K230_IPCM_DSP2CPU_INT_STATUS0, + status & ~mask); + k230_ipcm_update_dsp2cpu_irq(s); + return true; + + default: + return false; + } +} + +static uint64_t k230_hardlock_read(void *opaque, hwaddr addr, + unsigned int size) +{ + K230HardlockState *s = K230_HARDLOCK(opaque); + unsigned int index; + + if (k230_hardlock_index(addr, size, &index)) { + if (!s->locks[index]) { + s->locks[index] = true; + stl_le_p(s->regs + addr, 1); + return 0; + } + return 1; + } + + return k230_hardlock_read_bytes(s->regs, addr, size); +} + +static void k230_hardlock_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230HardlockState *s = K230_HARDLOCK(opaque); + unsigned int index; + + if (k230_ipcm_write(s, addr, val, size)) { + return; + } + + k230_hardlock_write_bytes(s->regs, addr, val, size); + + if (k230_hardlock_index(addr, size, &index)) { + s->locks[index] = val != 0; + } +} + +static const MemoryRegionOps k230_hardlock_ops = { + .read = k230_hardlock_read, + .write = k230_hardlock_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_hardlock_reset(DeviceState *dev) +{ + K230HardlockState *s = K230_HARDLOCK(dev); + + memset(s->regs, 0, sizeof(s->regs)); + memset(s->locks, 0, sizeof(s->locks)); +} + +static const VMStateDescription vmstate_k230_hardlock = { + .name = TYPE_K230_HARDLOCK, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, K230HardlockState, K230_HARDLOCK_SIZE), + VMSTATE_BOOL_ARRAY(locks, K230HardlockState, K230_HARDLOCK_COUNT), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_hardlock_realize(DeviceState *dev, Error **errp) +{ + K230HardlockState *s = K230_HARDLOCK(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_hardlock_ops, s, + TYPE_K230_HARDLOCK, K230_HARDLOCK_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); + for (int i = 0; i < K230_HARDLOCK_IPCM_IRQ_COUNT; i++) { + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irqs[i]); + } +} + +static void k230_hardlock_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_hardlock_realize; + device_class_set_legacy_reset(dc, k230_hardlock_reset); + dc->vmsd = &vmstate_k230_hardlock; + dc->desc = "K230 hardlock registers"; +} + +static const TypeInfo k230_hardlock_type_info = { + .name = TYPE_K230_HARDLOCK, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230HardlockState), + .class_init = k230_hardlock_class_init, +}; + +static void k230_register_hardlock_types(void) +{ + type_register_static(&k230_hardlock_type_info); +} + +type_init(k230_register_hardlock_types) diff --git a/hw/misc/k230_hi_sys_cfg.c b/hw/misc/k230_hi_sys_cfg.c new file mode 100644 index 0000000000000..d8f476003040f --- /dev/null +++ b/hw/misc/k230_hi_sys_cfg.c @@ -0,0 +1,111 @@ +/* + * K230 high-speed system config registers + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "migration/vmstate.h" +#include "hw/misc/k230_hi_sys_cfg.h" + +static uint64_t k230_hi_sys_cfg_read_bytes(uint8_t *regs, hwaddr addr, + unsigned int size) +{ + uint64_t val = 0; + + for (int i = 0; i < size; i++) { + val |= (uint64_t)regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_hi_sys_cfg_write_bytes(uint8_t *regs, hwaddr addr, + uint64_t val, unsigned int size) +{ + for (int i = 0; i < size; i++) { + regs[addr + i] = val >> (i * 8); + } +} + +static uint64_t k230_hi_sys_cfg_read(void *opaque, hwaddr addr, + unsigned int size) +{ + return k230_hi_sys_cfg_read_bytes(K230_HI_SYS_CFG(opaque)->regs, addr, + size); +} + +static void k230_hi_sys_cfg_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + k230_hi_sys_cfg_write_bytes(K230_HI_SYS_CFG(opaque)->regs, addr, val, + size); +} + +static const MemoryRegionOps k230_hi_sys_cfg_ops = { + .read = k230_hi_sys_cfg_read, + .write = k230_hi_sys_cfg_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_hi_sys_cfg_reset(DeviceState *dev) +{ + K230HiSysCfgState *s = K230_HI_SYS_CFG(dev); + + memset(s->regs, 0, sizeof(s->regs)); +} + +static const VMStateDescription vmstate_k230_hi_sys_cfg = { + .name = TYPE_K230_HI_SYS_CFG, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, K230HiSysCfgState, K230_HI_SYS_CFG_SIZE), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_hi_sys_cfg_realize(DeviceState *dev, Error **errp) +{ + K230HiSysCfgState *s = K230_HI_SYS_CFG(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_hi_sys_cfg_ops, s, + TYPE_K230_HI_SYS_CFG, K230_HI_SYS_CFG_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); +} + +static void k230_hi_sys_cfg_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_hi_sys_cfg_realize; + device_class_set_legacy_reset(dc, k230_hi_sys_cfg_reset); + dc->vmsd = &vmstate_k230_hi_sys_cfg; + dc->desc = "K230 high-speed system config registers"; +} + +static const TypeInfo k230_hi_sys_cfg_type_info = { + .name = TYPE_K230_HI_SYS_CFG, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230HiSysCfgState), + .class_init = k230_hi_sys_cfg_class_init, +}; + +static void k230_register_hi_sys_cfg_types(void) +{ + type_register_static(&k230_hi_sys_cfg_type_info); +} + +type_init(k230_register_hi_sys_cfg_types) diff --git a/hw/misc/k230_i2s.c b/hw/misc/k230_i2s.c new file mode 100644 index 0000000000000..2c4012acbeab1 --- /dev/null +++ b/hw/misc/k230_i2s.c @@ -0,0 +1,447 @@ +/* + * K230 I2S / WS2812 registers + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/bswap.h" +#include "qemu/module.h" +#include "migration/vmstate.h" +#include "hw/misc/k230_i2s.h" + +REG32(IER, 0x000) + FIELD(IER, ENABLE, 0, 1) +REG32(IRER, 0x004) + FIELD(IRER, ENABLE, 0, 1) +REG32(ITER, 0x008) + FIELD(ITER, ENABLE, 0, 1) +REG32(CER, 0x00c) + FIELD(CER, ENABLE, 0, 1) +REG32(CCR, 0x010) + FIELD(CCR, SCLK_GATING, 0, 3) + FIELD(CCR, WORD_SELECT, 3, 2) + FIELD(CCR, MODE, 5, 3) + FIELD(CCR, TX_DMA_ENABLE, 8, 1) +REG32(RXFFR, 0x014) +REG32(TXFFR, 0x018) + +#define K230_I2S_CHANNEL_COUNT 4 +#define K230_I2S_CHANNEL_STRIDE 0x40 +#define K230_I2S_CH_BASE(n) (0x020 + (n) * K230_I2S_CHANNEL_STRIDE) +#define K230_I2S_CH_LRXT(n) (K230_I2S_CH_BASE(n) + 0x00) +#define K230_I2S_CH_RRXT(n) (K230_I2S_CH_BASE(n) + 0x04) +#define K230_I2S_CH_RER(n) (K230_I2S_CH_BASE(n) + 0x08) +#define K230_I2S_CH_TER(n) (K230_I2S_CH_BASE(n) + 0x0c) +#define K230_I2S_CH_RCR(n) (K230_I2S_CH_BASE(n) + 0x10) +#define K230_I2S_CH_TCR(n) (K230_I2S_CH_BASE(n) + 0x14) +#define K230_I2S_CH_ISR(n) (K230_I2S_CH_BASE(n) + 0x18) +#define K230_I2S_CH_IMR(n) (K230_I2S_CH_BASE(n) + 0x1c) +#define K230_I2S_CH_ROR(n) (K230_I2S_CH_BASE(n) + 0x20) +#define K230_I2S_CH_TOR(n) (K230_I2S_CH_BASE(n) + 0x24) +#define K230_I2S_CH_RFCR(n) (K230_I2S_CH_BASE(n) + 0x28) +#define K230_I2S_CH_TFCR(n) (K230_I2S_CH_BASE(n) + 0x2c) +#define K230_I2S_CH_RFF(n) (K230_I2S_CH_BASE(n) + 0x30) +#define K230_I2S_CH_TFF(n) (K230_I2S_CH_BASE(n) + 0x34) + +REG32(RXDMA, 0x1c0) +REG32(RRXDMA, 0x1c4) +REG32(TXDMA, 0x1c8) +REG32(RTXDMA, 0x1cc) +REG32(COMP_PARAM_2, 0x1f0) +REG32(COMP_PARAM_1, 0x1f4) +REG32(COMP_VERSION, 0x1f8) +REG32(COMP_TYPE, 0x1fc) +REG32(AUDIO_IN_CTRL, 0x400) + FIELD(AUDIO_IN_CTRL, ENABLE, 5, 1) +REG32(AUDIO_OUT_CTRL, 0xc00) + FIELD(AUDIO_OUT_CTRL, ENABLE, 0, 1) + FIELD(AUDIO_OUT_CTRL, WIDTH, 1, 2) + FIELD(AUDIO_OUT_CTRL, MODE, 5, 2) + +static void k230_i2s_ws2812_clear(K230I2SState *s) +{ + s->ws2812_byte_count = 0; + memset(s->ws2812_byte, 0, sizeof(s->ws2812_byte)); + s->ws2812_padding_count = 0; + s->ws2812_invalid_count = 0; +} + +static bool k230_i2s_ws2812_symbol_decode(uint8_t symbol, uint8_t *bits) +{ + switch (symbol) { + case 0x88: + *bits = 0; + return true; + case 0x8e: + *bits = 1; + return true; + case 0xe8: + *bits = 2; + return true; + case 0xee: + *bits = 3; + return true; + default: + return false; + } +} + +static bool k230_i2s_ws2812_word_decode(uint32_t word, uint8_t *byte) +{ + uint8_t value = 0; + + for (int i = 0; i < 4; i++) { + uint8_t bits; + uint8_t symbol = extract32(word, i * 8, 8); + + if (!k230_i2s_ws2812_symbol_decode(symbol, &bits)) { + return false; + } + value |= bits << (i * 2); + } + + *byte = value; + return true; +} + +static void k230_i2s_ws2812_append(K230I2SState *s, uint8_t byte) +{ + if (s->ws2812_byte_count < K230_I2S_WS2812_SAMPLES) { + s->ws2812_byte[s->ws2812_byte_count] = byte; + } + s->ws2812_byte_count++; +} + +static bool k230_i2s_tx_dma_enabled(const K230I2SState *s) +{ + return FIELD_EX32(s->regs[R_CCR], CCR, TX_DMA_ENABLE) != 0; +} + +static uint64_t k230_i2s_txdma_pre_write(RegisterInfo *reg, uint64_t val) +{ + K230I2SState *s = K230_I2S(reg->opaque); + uint8_t byte; + + if (!k230_i2s_tx_dma_enabled(s)) { + return s->regs[R_TXDMA]; + } + + if (val == 0) { + s->ws2812_padding_count++; + return s->regs[R_TXDMA]; + } + if (k230_i2s_ws2812_word_decode(val, &byte)) { + k230_i2s_ws2812_append(s, byte); + return s->regs[R_TXDMA]; + } + + s->ws2812_invalid_count++; + return s->regs[R_TXDMA]; +} + +static void k230_i2s_flush_post_write(RegisterInfo *reg, uint64_t val) +{ + K230I2SState *s = K230_I2S(reg->opaque); + + if (val) { + k230_i2s_ws2812_clear(s); + } +} + +#define K230_I2S_CH_REG(n, reg) \ + { .name = "CH" #n "_" #reg, .addr = K230_I2S_CH_ ## reg(n) } +#define K230_I2S_CH_TFF_REG(n) \ + { .name = "CH" #n "_TFF", .addr = K230_I2S_CH_TFF(n), \ + .post_write = k230_i2s_flush_post_write } + +static const RegisterAccessInfo k230_i2s_regs_info[] = { + { .name = "IER", .addr = A_IER, .rsvd = ~R_IER_ENABLE_MASK }, + { .name = "IRER", .addr = A_IRER, .rsvd = ~R_IRER_ENABLE_MASK }, + { .name = "ITER", .addr = A_ITER, .rsvd = ~R_ITER_ENABLE_MASK }, + { .name = "CER", .addr = A_CER, .rsvd = ~R_CER_ENABLE_MASK }, + { .name = "CCR", .addr = A_CCR }, + { .name = "RXFFR", .addr = A_RXFFR, + .post_write = k230_i2s_flush_post_write }, + { .name = "TXFFR", .addr = A_TXFFR, + .post_write = k230_i2s_flush_post_write }, + K230_I2S_CH_REG(0, LRXT), + K230_I2S_CH_REG(0, RRXT), + K230_I2S_CH_REG(0, RER), + K230_I2S_CH_REG(0, TER), + K230_I2S_CH_REG(0, RCR), + K230_I2S_CH_REG(0, TCR), + K230_I2S_CH_REG(0, ISR), + K230_I2S_CH_REG(0, IMR), + K230_I2S_CH_REG(0, ROR), + K230_I2S_CH_REG(0, TOR), + K230_I2S_CH_REG(0, RFCR), + K230_I2S_CH_REG(0, TFCR), + K230_I2S_CH_REG(0, RFF), + K230_I2S_CH_TFF_REG(0), + K230_I2S_CH_REG(1, LRXT), + K230_I2S_CH_REG(1, RRXT), + K230_I2S_CH_REG(1, RER), + K230_I2S_CH_REG(1, TER), + K230_I2S_CH_REG(1, RCR), + K230_I2S_CH_REG(1, TCR), + K230_I2S_CH_REG(1, ISR), + K230_I2S_CH_REG(1, IMR), + K230_I2S_CH_REG(1, ROR), + K230_I2S_CH_REG(1, TOR), + K230_I2S_CH_REG(1, RFCR), + K230_I2S_CH_REG(1, TFCR), + K230_I2S_CH_REG(1, RFF), + K230_I2S_CH_TFF_REG(1), + K230_I2S_CH_REG(2, LRXT), + K230_I2S_CH_REG(2, RRXT), + K230_I2S_CH_REG(2, RER), + K230_I2S_CH_REG(2, TER), + K230_I2S_CH_REG(2, RCR), + K230_I2S_CH_REG(2, TCR), + K230_I2S_CH_REG(2, ISR), + K230_I2S_CH_REG(2, IMR), + K230_I2S_CH_REG(2, ROR), + K230_I2S_CH_REG(2, TOR), + K230_I2S_CH_REG(2, RFCR), + K230_I2S_CH_REG(2, TFCR), + K230_I2S_CH_REG(2, RFF), + K230_I2S_CH_TFF_REG(2), + K230_I2S_CH_REG(3, LRXT), + K230_I2S_CH_REG(3, RRXT), + K230_I2S_CH_REG(3, RER), + K230_I2S_CH_REG(3, TER), + K230_I2S_CH_REG(3, RCR), + K230_I2S_CH_REG(3, TCR), + K230_I2S_CH_REG(3, ISR), + K230_I2S_CH_REG(3, IMR), + K230_I2S_CH_REG(3, ROR), + K230_I2S_CH_REG(3, TOR), + K230_I2S_CH_REG(3, RFCR), + K230_I2S_CH_REG(3, TFCR), + K230_I2S_CH_REG(3, RFF), + K230_I2S_CH_TFF_REG(3), + { .name = "RXDMA", .addr = A_RXDMA }, + { .name = "RRXDMA", .addr = A_RRXDMA }, + { .name = "TXDMA", .addr = A_TXDMA, + .pre_write = k230_i2s_txdma_pre_write }, + { .name = "RTXDMA", .addr = A_RTXDMA, + .post_write = k230_i2s_flush_post_write }, + { .name = "COMP_PARAM_2", .addr = A_COMP_PARAM_2, .ro = ~0ull }, + { .name = "COMP_PARAM_1", .addr = A_COMP_PARAM_1, .ro = ~0ull }, + { .name = "COMP_VERSION", .addr = A_COMP_VERSION, .ro = ~0ull }, + { .name = "COMP_TYPE", .addr = A_COMP_TYPE, .ro = ~0ull }, + { .name = "AUDIO_IN_CTRL", .addr = A_AUDIO_IN_CTRL }, + { .name = "AUDIO_OUT_CTRL", .addr = A_AUDIO_OUT_CTRL }, +}; + +static void k230_i2s_sync_compat_reg(K230I2SState *s, hwaddr addr) +{ + if (!s->compat_regs) { + return; + } + + stl_le_p(&s->compat_regs->regs[addr], s->regs[addr / sizeof(uint32_t)]); +} + +static bool k230_i2s_is_modeled_addr(hwaddr addr) +{ + for (int i = 0; i < ARRAY_SIZE(k230_i2s_regs_info); i++) { + if (k230_i2s_regs_info[i].addr == addr) { + return true; + } + } + + return false; +} + +static uint64_t k230_i2s_read_bytes(const uint8_t *regs, hwaddr addr, + unsigned int size) +{ + uint64_t val = 0; + + for (int i = 0; i < size; i++) { + val |= (uint64_t)regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_i2s_write_bytes(uint8_t *regs, hwaddr addr, uint64_t val, + unsigned int size) +{ + for (int i = 0; i < size; i++) { + regs[addr + i] = val >> (i * 8); + } +} + +static void k230_i2s_import_compat(K230I2SState *s) +{ + if (s->state_loaded || s->compat_imported || !s->compat_regs) { + return; + } + + for (unsigned int i = 0; i < K230_I2S_R_MAX; i++) { + s->regs[i] = ldl_le_p(&s->compat_regs->regs[i * sizeof(uint32_t)]); + } + s->compat_imported = true; +} + +static uint64_t k230_i2s_mmio_read(void *opaque, hwaddr addr, + unsigned int size) +{ + K230I2SState *s = K230_I2S(opaque); + + k230_i2s_import_compat(s); + if (size == 4 && k230_i2s_is_modeled_addr(addr)) { + return register_read_memory(s->reg_array, addr, size); + } + + return s->compat_regs ? k230_i2s_read_bytes(s->compat_regs->regs, addr, + size) : 0; +} + +static void k230_i2s_mmio_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230I2SState *s = K230_I2S(opaque); + + k230_i2s_import_compat(s); + if (size == 4 && k230_i2s_is_modeled_addr(addr)) { + register_write_memory(s->reg_array, addr, val, size); + k230_i2s_sync_compat_reg(s, addr); + return; + } + if (s->compat_regs) { + k230_i2s_write_bytes(s->compat_regs->regs, addr, val, size); + } +} + +static const MemoryRegionOps k230_i2s_reg_array_ops = { + .read = register_read_memory, + .write = register_write_memory, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const MemoryRegionOps k230_i2s_ops = { + .read = k230_i2s_mmio_read, + .write = k230_i2s_mmio_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void k230_i2s_reset(DeviceState *dev) +{ + K230I2SState *s = K230_I2S(dev); + + k230_i2s_ws2812_clear(s); + s->state_loaded = false; + s->compat_imported = false; + for (int i = 0; i < ARRAY_SIZE(k230_i2s_regs_info); i++) { + register_reset(&s->regs_info[k230_i2s_regs_info[i].addr / 4]); + k230_i2s_sync_compat_reg(s, k230_i2s_regs_info[i].addr); + } +} + +static int k230_i2s_post_load(void *opaque, int version_id); + + +static const VMStateDescription vmstate_k230_i2s = { + .name = TYPE_K230_I2S, + .version_id = 1, + .post_load = k230_i2s_post_load, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, K230I2SState, K230_I2S_R_MAX), + VMSTATE_UINT32(ws2812_byte_count, K230I2SState), + VMSTATE_UINT32_ARRAY(ws2812_byte, K230I2SState, + K230_I2S_WS2812_SAMPLES), + VMSTATE_UINT32(ws2812_padding_count, K230I2SState), + VMSTATE_UINT32(ws2812_invalid_count, K230I2SState), + VMSTATE_END_OF_LIST(), + }, +}; + +static int k230_i2s_post_load(void *opaque, int version_id) +{ + K230I2SState *s = K230_I2S(opaque); + + s->state_loaded = true; + s->compat_imported = false; + for (int i = 0; i < ARRAY_SIZE(k230_i2s_regs_info); i++) { + k230_i2s_sync_compat_reg(s, k230_i2s_regs_info[i].addr); + } + + return 0; +} + + +static void k230_i2s_realize(DeviceState *dev, Error **errp) +{ + K230I2SState *s = K230_I2S(dev); + s->reg_array = register_init_block32(dev, k230_i2s_regs_info, + ARRAY_SIZE(k230_i2s_regs_info), + s->regs_info, s->regs, + &k230_i2s_reg_array_ops, + false, K230_I2S_SIZE); + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_i2s_ops, s, + TYPE_K230_I2S, K230_I2S_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); +} + +static void k230_i2s_instance_init(Object *obj) +{ + K230I2SState *s = K230_I2S(obj); + + object_property_add_uint32_ptr(obj, "ws2812-byte-count", + &s->ws2812_byte_count, + OBJ_PROP_FLAG_READ); + object_property_add_uint32_ptr(obj, "ws2812-byte0", + &s->ws2812_byte[0], OBJ_PROP_FLAG_READ); + object_property_add_uint32_ptr(obj, "ws2812-byte1", + &s->ws2812_byte[1], OBJ_PROP_FLAG_READ); + object_property_add_uint32_ptr(obj, "ws2812-byte2", + &s->ws2812_byte[2], OBJ_PROP_FLAG_READ); + object_property_add_uint32_ptr(obj, "ws2812-byte3", + &s->ws2812_byte[3], OBJ_PROP_FLAG_READ); + object_property_add_uint32_ptr(obj, "ws2812-padding-count", + &s->ws2812_padding_count, + OBJ_PROP_FLAG_READ); + object_property_add_uint32_ptr(obj, "ws2812-invalid-count", + &s->ws2812_invalid_count, + OBJ_PROP_FLAG_READ); +} + +static void k230_i2s_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_i2s_realize; + device_class_set_legacy_reset(dc, k230_i2s_reset); + dc->vmsd = &vmstate_k230_i2s; + dc->desc = "K230 I2S / WS2812 registers"; +} + +static const TypeInfo k230_i2s_type_info = { + .name = TYPE_K230_I2S, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230I2SState), + .instance_init = k230_i2s_instance_init, + .class_init = k230_i2s_class_init, +}; + +static void k230_i2s_register_types(void) +{ + type_register_static(&k230_i2s_type_info); +} + +type_init(k230_i2s_register_types) diff --git a/hw/misc/k230_iomux.c b/hw/misc/k230_iomux.c new file mode 100644 index 0000000000000..ad180be72f1fa --- /dev/null +++ b/hw/misc/k230_iomux.c @@ -0,0 +1,114 @@ +/* + * K230 IOMUX register block + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "migration/vmstate.h" +#include "hw/misc/k230_iomux.h" + +static uint64_t k230_iomux_read(void *opaque, hwaddr addr, unsigned int size) +{ + K230IomuxState *s = K230_IOMUX(opaque); + uint64_t val = 0; + + if (addr > K230_IOMUX_SIZE || size > K230_IOMUX_SIZE - addr) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: bad read offset 0x%" HWADDR_PRIx "\n", + TYPE_K230_IOMUX, addr); + return 0; + } + + for (int i = 0; i < size; i++) { + val |= (uint64_t)s->regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_iomux_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230IomuxState *s = K230_IOMUX(opaque); + + if (addr > K230_IOMUX_SIZE || size > K230_IOMUX_SIZE - addr) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: bad write offset 0x%" HWADDR_PRIx "\n", + TYPE_K230_IOMUX, addr); + return; + } + + for (int i = 0; i < size; i++) { + s->regs[addr + i] = val >> (i * 8); + } +} + +static const MemoryRegionOps k230_iomux_ops = { + .read = k230_iomux_read, + .write = k230_iomux_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_iomux_reset(DeviceState *dev) +{ + K230IomuxState *s = K230_IOMUX(dev); + + memset(s->regs, 0, sizeof(s->regs)); +} + +static const VMStateDescription vmstate_k230_iomux = { + .name = TYPE_K230_IOMUX, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, K230IomuxState, K230_IOMUX_SIZE), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_iomux_realize(DeviceState *dev, Error **errp) +{ + K230IomuxState *s = K230_IOMUX(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_iomux_ops, s, + TYPE_K230_IOMUX, K230_IOMUX_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); +} + +static void k230_iomux_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_iomux_realize; + device_class_set_legacy_reset(dc, k230_iomux_reset); + dc->vmsd = &vmstate_k230_iomux; + dc->desc = "K230 IOMUX register block"; +} + +static const TypeInfo k230_iomux_type_info = { + .name = TYPE_K230_IOMUX, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230IomuxState), + .class_init = k230_iomux_class_init, +}; + +static void k230_iomux_register_types(void) +{ + type_register_static(&k230_iomux_type_info); +} + +type_init(k230_iomux_register_types) diff --git a/hw/misc/k230_isp.c b/hw/misc/k230_isp.c new file mode 100644 index 0000000000000..3ac45168423f4 --- /dev/null +++ b/hw/misc/k230_isp.c @@ -0,0 +1,392 @@ +/* + * K230 ISP media pipeline registers + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "qemu/timer.h" +#include "hw/core/irq.h" +#include "hw/misc/k230_isp.h" +#include "migration/vmstate.h" +#include "trace.h" + +#define K230_ISP_MAIN_STATUS 0x05c4 +#define K230_ISP_MAIN_CLEAR 0x05c8 +#define K230_ISP_MCM_CTRL 0x1300 +#define K230_ISP_MCM0_STATUS 0x16d0 +#define K230_ISP_MCM0_RAW_STATUS 0x16d4 +#define K230_ISP_MCM0_CLEAR 0x16d8 +#define K230_ISP_MCM0_RAW_CLEAR 0x16dc +#define K230_ISP_MCM1_STATUS 0x16f0 +#define K230_ISP_MCM1_CLEAR 0x16f4 +#define K230_ISP_MCM2_STATUS 0x56d8 +#define K230_ISP_MCM2_CLEAR 0x56dc +#define K230_ISP_MCM3_STATUS 0x72c8 +#define K230_ISP_MCM3_CLEAR 0x72cc +#define K230_ISP_TOP_STATUS 0x3d60 +#define K230_ISP_FE_START 0x3d64 +#define K230_ISP_FE_MI_STATUS 0x3d74 +#define K230_ISP_FE_MI_CLEAR 0x3d78 + +#define K230_ISP_TOP_PENDING BIT(0) +#define K230_ISP_TOP_ACK BIT(1) +#define K230_ISP_FE_DONE_STATUS BIT(0) +#define K230_ISP_MI_FRAME_STATUS BIT(8) +#define K230_ISP_FE_START_CMD BIT(16) +#define K230_ISP_MCM_CH0_ENABLE BIT(6) +#define K230_ISP_MCM_CH1_ENABLE BIT(7) +#define K230_ISP_MCM_CH2_ENABLE BIT(17) +#define K230_ISP_MCM_CH2_STATUS BIT(14) +#define K230_ISP_MCM_CH0_BUFFER BIT(0) +#define K230_ISP_MCM_CH1_BUFFER BIT(3) +#define K230_ISP_MCM_CH0_DONE BIT(6) +#define K230_ISP_MCM_CH1_DONE BIT(7) +#define K230_ISP_MCM_STREAM_MASK (K230_ISP_MCM_CH0_ENABLE | \ + K230_ISP_MCM_CH1_ENABLE | \ + K230_ISP_MCM_CH2_ENABLE) +#define K230_ISP_MCM_FRAME_NS (NANOSECONDS_PER_SECOND / 30) + +static bool k230_isp_access_hits(hwaddr addr, unsigned int size, + hwaddr offset) +{ + return addr <= offset && offset < addr + size; +} + +static uint32_t k230_isp_read32(K230IspState *s, hwaddr addr) +{ + uint32_t val = 0; + + if (addr > K230_ISP_SIZE - sizeof(val)) { + return 0; + } + + for (int i = 0; i < 4; i++) { + val |= (uint32_t)s->regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_isp_write32(K230IspState *s, hwaddr addr, uint32_t val) +{ + if (addr > K230_ISP_SIZE - sizeof(val)) { + return; + } + + for (int i = 0; i < 4; i++) { + s->regs[addr + i] = val >> (i * 8); + } +} + +static void k230_isp_update_irq(qemu_irq irq, bool *level, bool new_level, + const char *name) +{ + if (*level == new_level) { + return; + } + + *level = new_level; + trace_k230_isp_irq(name, new_level); + qemu_set_irq(irq, new_level); +} + +static bool k230_isp_has_mi_status(K230IspState *s) +{ + return k230_isp_read32(s, K230_ISP_MCM0_STATUS) || + k230_isp_read32(s, K230_ISP_MCM0_RAW_STATUS) || + k230_isp_read32(s, K230_ISP_MCM1_STATUS) || + k230_isp_read32(s, K230_ISP_MCM2_STATUS) || + k230_isp_read32(s, K230_ISP_MCM3_STATUS); +} + +static void k230_isp_refresh_irqs(K230IspState *s) +{ + k230_isp_update_irq(s->isp_irq, &s->isp_irq_level, + k230_isp_read32(s, K230_ISP_MAIN_STATUS) != 0, + "isp"); + k230_isp_update_irq(s->mi_irq, &s->mi_irq_level, + k230_isp_has_mi_status(s), "mi"); + k230_isp_update_irq(s->fe_irq, &s->fe_irq_level, + k230_isp_read32(s, K230_ISP_FE_MI_STATUS) & + K230_ISP_FE_DONE_STATUS, + "fe"); +} + +static void k230_isp_set_top_pending(K230IspState *s) +{ + uint32_t status = k230_isp_read32(s, K230_ISP_TOP_STATUS); + + k230_isp_write32(s, K230_ISP_TOP_STATUS, status | K230_ISP_TOP_PENDING); +} + +static void k230_isp_clear_status(K230IspState *s, hwaddr status_offset, + uint64_t val) +{ + uint32_t status = k230_isp_read32(s, status_offset); + + k230_isp_write32(s, status_offset, status & ~(uint32_t)val); +} + +static void k230_isp_clear_mcm0_status(K230IspState *s, uint64_t val) +{ + uint32_t clear = val; + + if (clear & K230_ISP_MCM_CH0_DONE) { + clear |= K230_ISP_MCM_CH0_BUFFER; + } + if (clear & K230_ISP_MCM_CH0_BUFFER) { + clear |= K230_ISP_MCM_CH0_DONE; + } + if (clear & K230_ISP_MCM_CH1_DONE) { + clear |= K230_ISP_MCM_CH1_BUFFER; + } + if (clear & K230_ISP_MCM_CH1_BUFFER) { + clear |= K230_ISP_MCM_CH1_DONE; + } + + k230_isp_clear_status(s, K230_ISP_MCM0_STATUS, clear); +} + +static void k230_isp_raise_fe(K230IspState *s) +{ + uint32_t status = k230_isp_read32(s, K230_ISP_FE_MI_STATUS); + + k230_isp_write32(s, K230_ISP_FE_MI_STATUS, + status | K230_ISP_FE_DONE_STATUS); + k230_isp_set_top_pending(s); +} + +static void k230_isp_raise_mcm(K230IspState *s, uint32_t ctrl) +{ + uint32_t mcm0_status = k230_isp_read32(s, K230_ISP_MCM0_STATUS); + uint32_t mcm2_status = k230_isp_read32(s, K230_ISP_MCM2_STATUS); + + if (ctrl & K230_ISP_MCM_CH0_ENABLE) { + mcm0_status |= K230_ISP_MCM_CH0_BUFFER | K230_ISP_MCM_CH0_DONE; + } + if (ctrl & K230_ISP_MCM_CH1_ENABLE) { + mcm0_status |= K230_ISP_MCM_CH1_BUFFER | K230_ISP_MCM_CH1_DONE; + } + if (ctrl & K230_ISP_MCM_CH2_ENABLE) { + mcm2_status |= K230_ISP_MCM_CH2_STATUS; + } + + k230_isp_write32(s, K230_ISP_MCM0_STATUS, mcm0_status); + k230_isp_write32(s, K230_ISP_MCM2_STATUS, mcm2_status); + k230_isp_set_top_pending(s); +} + +static bool k230_isp_mcm_stream_active(K230IspState *s) +{ + uint32_t ctrl = k230_isp_read32(s, K230_ISP_MCM_CTRL); + + return ctrl & K230_ISP_MCM_STREAM_MASK; +} + +static void k230_isp_schedule_mcm_frame(K230IspState *s) +{ + if (!k230_isp_mcm_stream_active(s)) { + timer_del(&s->mcm_frame_timer); + return; + } + + timer_mod(&s->mcm_frame_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + K230_ISP_MCM_FRAME_NS); +} + +static void k230_isp_raise_mcm_frame(K230IspState *s) +{ + uint32_t ctrl = k230_isp_read32(s, K230_ISP_MCM_CTRL); + uint32_t status = k230_isp_read32(s, K230_ISP_MCM0_STATUS); + uint32_t top_status = k230_isp_read32(s, K230_ISP_FE_MI_STATUS); + + if (ctrl & K230_ISP_MCM_CH0_ENABLE) { + status |= K230_ISP_MCM_CH0_BUFFER | K230_ISP_MCM_CH0_DONE; + } + if (ctrl & K230_ISP_MCM_CH1_ENABLE) { + status |= K230_ISP_MCM_CH1_BUFFER | K230_ISP_MCM_CH1_DONE; + } + if (ctrl & K230_ISP_MCM_CH2_ENABLE) { + uint32_t mcm2_status = k230_isp_read32(s, K230_ISP_MCM2_STATUS); + + k230_isp_write32(s, K230_ISP_MCM2_STATUS, + mcm2_status | K230_ISP_MCM_CH2_STATUS); + } + + k230_isp_write32(s, K230_ISP_MCM0_STATUS, status); + k230_isp_write32(s, K230_ISP_FE_MI_STATUS, + top_status | K230_ISP_MI_FRAME_STATUS); + k230_isp_set_top_pending(s); +} + +static void k230_isp_mcm_frame_timer(void *opaque) +{ + K230IspState *s = K230_ISP(opaque); + + if (k230_isp_mcm_stream_active(s)) { + k230_isp_raise_mcm_frame(s); + k230_isp_refresh_irqs(s); + k230_isp_schedule_mcm_frame(s); + } +} + +static uint64_t k230_isp_read(void *opaque, hwaddr addr, unsigned int size) +{ + K230IspState *s = K230_ISP(opaque); + uint64_t val = 0; + + if (addr >= K230_ISP_SIZE || size > K230_ISP_SIZE - addr) { + return 0; + } + + for (int i = 0; i < size; i++) { + val |= (uint64_t)s->regs[addr + i] << (i * 8); + } + + trace_k230_isp_read(addr, val, size); + + return val; +} + +static void k230_isp_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230IspState *s = K230_ISP(opaque); + + if (addr >= K230_ISP_SIZE || size > K230_ISP_SIZE - addr) { + return; + } + + for (int i = 0; i < size; i++) { + s->regs[addr + i] = val >> (i * 8); + } + + trace_k230_isp_write(addr, val, size); + + if (k230_isp_access_hits(addr, size, K230_ISP_MAIN_CLEAR) && val) { + k230_isp_clear_status(s, K230_ISP_MAIN_STATUS, val); + } + if (k230_isp_access_hits(addr, size, K230_ISP_FE_MI_CLEAR) && val) { + k230_isp_clear_status(s, K230_ISP_FE_MI_STATUS, val); + } + if (k230_isp_access_hits(addr, size, K230_ISP_MCM0_CLEAR) && val) { + k230_isp_clear_mcm0_status(s, val); + } + if (k230_isp_access_hits(addr, size, K230_ISP_MCM0_RAW_CLEAR) && val) { + k230_isp_clear_status(s, K230_ISP_MCM0_RAW_STATUS, val); + } + if (k230_isp_access_hits(addr, size, K230_ISP_MCM1_CLEAR) && val) { + k230_isp_clear_status(s, K230_ISP_MCM1_STATUS, val); + } + if (k230_isp_access_hits(addr, size, K230_ISP_MCM2_CLEAR) && val) { + k230_isp_clear_status(s, K230_ISP_MCM2_STATUS, val); + } + if (k230_isp_access_hits(addr, size, K230_ISP_MCM3_CLEAR) && val) { + k230_isp_clear_status(s, K230_ISP_MCM3_STATUS, val); + } + if (k230_isp_access_hits(addr, size, K230_ISP_TOP_STATUS) && + (val & K230_ISP_TOP_ACK)) { + uint32_t status = k230_isp_read32(s, K230_ISP_TOP_STATUS); + + k230_isp_write32(s, K230_ISP_TOP_STATUS, + status & ~K230_ISP_TOP_PENDING); + } + + if (k230_isp_access_hits(addr, size, K230_ISP_FE_START) && + (k230_isp_read32(s, K230_ISP_FE_START) & K230_ISP_FE_START_CMD)) { + k230_isp_raise_fe(s); + } + + if (k230_isp_access_hits(addr, size, K230_ISP_MCM_CTRL)) { + uint32_t ctrl = k230_isp_read32(s, K230_ISP_MCM_CTRL); + + if (ctrl & K230_ISP_MCM_STREAM_MASK) { + k230_isp_raise_mcm(s, ctrl); + } + k230_isp_schedule_mcm_frame(s); + } + + k230_isp_refresh_irqs(s); +} + +static const MemoryRegionOps k230_isp_ops = { + .read = k230_isp_read, + .write = k230_isp_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 8, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + .unaligned = true, + }, +}; + +static void k230_isp_reset(DeviceState *dev) +{ + K230IspState *s = K230_ISP(dev); + + memset(s->regs, 0, sizeof(s->regs)); + timer_del(&s->mcm_frame_timer); + k230_isp_update_irq(s->isp_irq, &s->isp_irq_level, false, "isp"); + k230_isp_update_irq(s->mi_irq, &s->mi_irq_level, false, "mi"); + k230_isp_update_irq(s->fe_irq, &s->fe_irq_level, false, "fe"); +} + +static const VMStateDescription vmstate_k230_isp = { + .name = TYPE_K230_ISP, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_BOOL(isp_irq_level, K230IspState), + VMSTATE_BOOL(mi_irq_level, K230IspState), + VMSTATE_BOOL(fe_irq_level, K230IspState), + VMSTATE_TIMER(mcm_frame_timer, K230IspState), + VMSTATE_UINT8_ARRAY(regs, K230IspState, K230_ISP_SIZE), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_isp_realize(DeviceState *dev, Error **errp) +{ + K230IspState *s = K230_ISP(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_isp_ops, s, + TYPE_K230_ISP, K230_ISP_SIZE); + timer_init_ns(&s->mcm_frame_timer, QEMU_CLOCK_VIRTUAL, + k230_isp_mcm_frame_timer, s); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->isp_irq); + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->mi_irq); + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->fe_irq); +} + +static void k230_isp_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_isp_realize; + device_class_set_legacy_reset(dc, k230_isp_reset); + dc->vmsd = &vmstate_k230_isp; + dc->desc = "K230 ISP media pipeline registers"; +} + +static const TypeInfo k230_isp_type_info = { + .name = TYPE_K230_ISP, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230IspState), + .class_init = k230_isp_class_init, +}; + +static void k230_isp_register_types(void) +{ + type_register_static(&k230_isp_type_info); +} + +type_init(k230_isp_register_types) diff --git a/hw/misc/k230_kpu.c b/hw/misc/k230_kpu.c new file mode 100644 index 0000000000000..b1387e4f64478 --- /dev/null +++ b/hw/misc/k230_kpu.c @@ -0,0 +1,6366 @@ +/* + * K230 KPU/GNNE engine + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/bitmap.h" +#include "qemu/host-utils.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/units.h" +#include "fpu/softfloat.h" +#include "hw/core/irq.h" +#include "hw/misc/k230_kpu.h" +#include "migration/vmstate.h" +#include "system/dma.h" +#include "trace.h" + +#define K230_KPU_COMMAND_START 0x100 +#define K230_KPU_COMMAND_END 0x104 +#define K230_KPU_COMMAND_HI 0x108 +#define K230_KPU_CONTROL 0x128 +#define K230_KPU_STATUS 0x130 +#define K230_KPU_CONTROL_CLEAR 0x4 +#define K230_KPU_CONTROL_START 0x9 + +#define K230_KPU_DONE 0x0000000400000004ULL +#define K230_KPU_COMPLETE_DELAY_NS (100 * 1000) +#define K230_KPU_FAKE_OUTPUT_BASE 0x10090000 +#define K230_KPU_FAKE_OUTPUT_SIZE 0x00100000 +#define K230_KPU_PAGE_SIZE 4096 + +#define K230_GNNE_COMMAND_BASE_OFFSET 0x003a6000 +#define K230_GNNE_RUNTIME_RDATA_BASE 0x10000020 +#define K230_GNNE_RUNTIME_DDR_BASE 0x3c000000 +#define K230_GNNE_RUNTIME_WINDOW_SIZE (64 * MiB) +#define K230_GNNE_RDATA_ALIAS_BASE 0xfc000000 +#define K230_GNNE_RDATA_FALLBACK_BASE 0x10000000 +#define K230_GNNE_RUNTIME_ARG_TABLE_BASE 0x80000000 +#define K230_GNNE_RUNTIME_ARG_PREFIX 64 +#define K230_GNNE_RUNTIME_ARG_WORDS (K230_GNNE_RUNTIME_ARG_PREFIX / 4) +#define K230_GNNE_RUNTIME_ARG_MIN_ADDRS 2 +#define K230_GNNE_RUNTIME_ARG_MAX_ADDRS (K230_GNNE_RUNTIME_ARG_WORDS - 2) +#define K230_GNNE_MAX_COMMAND_SIZE (16 * MiB) +#define K230_GNNE_MAX_OUTPUT_SIZE (64 * MiB) +#define K230_GNNE_GLB_CACHE_SIZE (4 * MiB) +#define K230_GNNE_GLB_BANK_COUNT K230_GNNE_MMU_COUNT + +#define K230_GNNE_GLB_BANK_VBASE ((uint64_t)0x2000000000ULL) +#define K230_GNNE_RDATA_SHADOW_SIZE (4 * MiB) +#define K230_GNNE_GP_COUNT 32 +#define K230_GNNE_SHAPE_COUNT 8 +#define K230_GNNE_MMU_COUNT 16 +#define K230_GNNE_L2_LANE_WIDTH 24 +#define K230_GNNE_FUNCTION_DDR_BASE 0x7c00 +#define K230_GNNE_FUNCTION_GLB_ARG_BASE (K230_GNNE_FUNCTION_DDR_BASE + 0x200) + +#define K230_GNNE_SKIP_CONF 1 +#define K230_GNNE_SKIP_SRC_GP 2 +#define K230_GNNE_SKIP_DST_GP 3 +#define K230_GNNE_SKIP_SHAPE 4 +#define K230_GNNE_SKIP_SRC_TRANSLATE 5 +#define K230_GNNE_SKIP_DST_TRANSLATE 6 +#define K230_GNNE_SKIP_STRIDE 7 +#define K230_GNNE_SKIP_COUNT 8 +#define K230_GNNE_SKIP_OVERFLOW 9 +#define K230_GNNE_SKIP_SOURCE_READ 10 +#define K230_GNNE_SKIP_DEST_WRITE 11 +#define K230_GNNE_SKIP_PSUM 12 +#define K230_GNNE_SKIP_ACT0 13 + +#define K230_GNNE_PU_INPUT_GLOBAL 0 +#define K230_GNNE_PU_INPUT_L1_OFFSET 1 + +#define K230_GNNE_PU_IF_SAMPLE_PACKED 0 +#define K230_GNNE_PU_IF_SAMPLE_LINEAR 1 +#define K230_GNNE_PU_IF_SAMPLE_LANE 2 +#define K230_GNNE_PU_MAC_SAMPLE_HEAD 8 +#define K230_GNNE_PU_IF_SAMPLE_HEAD K230_GNNE_L2_LANE_WIDTH +#define K230_GNNE_PU_L1_STAGE_SAMPLES 32 +#define K230_GNNE_PU_ACT0_SAMPLE_HEAD 8 +#define K230_GNNE_PU_ACT0_SAMPLE_BASE 0x14700 +#define K230_GNNE_PU_ACT0_SAMPLE_SIZE 0x120 +#define K230_GNNE_SUB32_SAMPLE_BASE 0x32200 +#define K230_GNNE_SUB32_SAMPLE_SIZE 0x400 +#define K230_GNNE_L2_LOAD_SAMPLE_HEAD 8 +#define K230_GNNE_L2_LOAD_SAMPLE_SUB25_BASE 0x46500 +#define K230_GNNE_L2_LOAD_SAMPLE_SUB25_SIZE 0x40 +#define K230_GNNE_L2_LOAD_SAMPLE_MFU_SRC_BASE 0x14500 +#define K230_GNNE_L2_LOAD_SAMPLE_MFU_SRC_SIZE 0x120 +#define K230_GNNE_L2_LOAD_SAMPLE_PU_SRC_BASE 0x2ec00 +#define K230_GNNE_L2_LOAD_SAMPLE_PU_SRC_SIZE 0x4100 +#define K230_GNNE_L2_LOAD_SAMPLE_SUB9_BASE 0x5dc00 +#define K230_GNNE_L2_LOAD_SAMPLE_SUB9_SIZE 0x80 +#define K230_GNNE_L2_LOAD_W_SAMPLE_HEAD 8 +#define K230_GNNE_L2_LOAD_W_SAMPLE_IF_BASE 0x83600 +#define K230_GNNE_L2_LOAD_W_SAMPLE_IF_SIZE 0x240 +#define K230_GNNE_L2_LOAD_SOURCE_PROBE_SIZE 0x100 +#define K230_GNNE_RUNTIME_ARG_PROBE_SIZE 0x100 +#define K230_GNNE_RAW_WRITE_SAMPLE_SUB31_BASE 0x2000483400ULL +#define K230_GNNE_RAW_WRITE_SAMPLE_SUB31_SIZE 0x240 +#define K230_GNNE_RAW_WRITE_SAMPLE_SUB9_BASE 0x20004c1c00ULL +#define K230_GNNE_RAW_WRITE_SAMPLE_SUB9_SIZE 0x800 +#define K230_GNNE_L2_LOAD_W_SOURCE_PROBE_SIZE 64 +#define K230_GNNE_L2_STORE_SAMPLE_HEAD 8 +#define K230_GNNE_L2_STORE_SAMPLE_DEST_BASE 0x1076fca0 +#define K230_GNNE_L2_STORE_SAMPLE_DEST_SIZE 0x80 +#define K230_GNNE_L2_STORE_SAMPLE_SRC_BASE 0x105ba470 +#define K230_GNNE_L2_STORE_SAMPLE_SRC_SIZE 0x120 +#define K230_GNNE_L2_STORE_SAMPLE_SUB31_FEED_BASE 0x106de7a0 +#define K230_GNNE_L2_STORE_SAMPLE_SUB31_FEED_SIZE 0x240 +#define K230_GNNE_L2_STORE_SAMPLE_CPU_COPY_SRC_BASE 0x105b2730 +#define K230_GNNE_L2_STORE_SAMPLE_CPU_COPY_SRC_SIZE 0x300 +#define K230_GNNE_MFU_ACT1_SAMPLE_HEAD 8 +#define K230_GNNE_MFU_ACT1_SAMPLE_BASE 0x46700 +#define K230_GNNE_MFU_ACT1_SAMPLE_SIZE 0x120 +#define K230_GNNE_MFU_ACT1_SAMPLE_PU_SRC_BASE 0x60e00 +#define K230_GNNE_MFU_ACT1_SAMPLE_PU_SRC_SIZE 0x4100 +#define K230_GNNE_MFU_ACT1_SAMPLE_SUB9_DST_BASE 0xc1c00 +#define K230_GNNE_MFU_ACT1_SAMPLE_SUB9_DST_SIZE 0x800 +#define K230_GNNE_L2_SOURCE_F_RUNTIME_WINDOW BIT(0) +#define K230_GNNE_L2_SOURCE_F_ARG_TABLE BIT(1) +#define K230_GNNE_L2_SOURCE_F_GP_DIRECT BIT(2) +#define K230_GNNE_L2_SOURCE_F_ARG_PROVENANCE BIT(3) +#define K230_GNNE_L2_SOURCE_F_RUNTIME_DDR BIT(4) +#define K230_GNNE_L2_SOURCE_F_RUNTIME_DDR_DIRECT BIT(5) +#define K230_GNNE_L2_SOURCE_F_RDATA_PREFIX BIT(6) +#define K230_GNNE_L2_SOURCE_F_RDATA_ALIAS BIT(7) + +typedef struct K230GnneScalar { + uint32_t value; + bool valid; + bool direct_physical; + bool runtime_arg_valid; + uint32_t runtime_arg_slot; + uint32_t runtime_arg_base; + uint64_t runtime_arg_offset; +} K230GnneScalar; + +typedef struct K230GnneShape { + uint32_t n; + uint32_t c; + uint32_t h; + uint32_t w; + bool valid; +} K230GnneShape; + +typedef struct K230GnneStride { + uint32_t n; + uint32_t c; + uint32_t h; + bool valid; +} K230GnneStride; + +typedef struct K230GnneTraceWindow { + uint64_t base; + uint64_t size; +} K230GnneTraceWindow; + +static const K230GnneTraceWindow k230_gnne_l2_store_sample_windows[] = { + { + .base = K230_GNNE_L2_STORE_SAMPLE_DEST_BASE, + .size = K230_GNNE_L2_STORE_SAMPLE_DEST_SIZE, + }, { + .base = K230_GNNE_L2_STORE_SAMPLE_SRC_BASE, + .size = K230_GNNE_L2_STORE_SAMPLE_SRC_SIZE, + }, { + .base = K230_GNNE_SUB32_SAMPLE_BASE, + .size = K230_GNNE_SUB32_SAMPLE_SIZE, + }, { + .base = K230_GNNE_L2_STORE_SAMPLE_SUB31_FEED_BASE, + .size = K230_GNNE_L2_STORE_SAMPLE_SUB31_FEED_SIZE, + }, { + .base = K230_GNNE_L2_STORE_SAMPLE_CPU_COPY_SRC_BASE, + .size = K230_GNNE_L2_STORE_SAMPLE_CPU_COPY_SRC_SIZE, + }, +}; + +static const K230GnneTraceWindow k230_gnne_l2_load_sample_windows[] = { + { + .base = K230_GNNE_L2_LOAD_SAMPLE_SUB25_BASE, + .size = K230_GNNE_L2_LOAD_SAMPLE_SUB25_SIZE, + }, { + .base = K230_GNNE_L2_LOAD_SAMPLE_MFU_SRC_BASE, + .size = K230_GNNE_L2_LOAD_SAMPLE_MFU_SRC_SIZE, + }, { + .base = K230_GNNE_L2_LOAD_SAMPLE_PU_SRC_BASE, + .size = K230_GNNE_L2_LOAD_SAMPLE_PU_SRC_SIZE, + }, { + .base = K230_GNNE_L2_LOAD_SAMPLE_SUB9_BASE, + .size = K230_GNNE_L2_LOAD_SAMPLE_SUB9_SIZE, + }, { + .base = K230_GNNE_SUB32_SAMPLE_BASE, + .size = K230_GNNE_SUB32_SAMPLE_SIZE, + }, +}; + +static const K230GnneTraceWindow k230_gnne_l2_load_w_sample_windows[] = { + { + .base = K230_GNNE_L2_LOAD_W_SAMPLE_IF_BASE, + .size = K230_GNNE_L2_LOAD_W_SAMPLE_IF_SIZE, + }, +}; + +static const uint64_t k230_gnne_l2_load_source_probe_offsets[] = { + 0x0, + 0x200, + 0x6400, + 0x6434, + 0xc800, + 0xca00, + 0x25a00, + 0x3ea00, +}; + +static const uint64_t k230_gnne_runtime_arg_probe_offsets[] = { + 0x0, + 0x19000, + 0x19010, + 0x32000, + 0x32010, + 0x3e800, + 0x3e810, + 0x4b000, + 0x4b020, +}; + +static const K230GnneTraceWindow k230_gnne_raw_write_sample_windows[] = { + { + .base = K230_GNNE_RAW_WRITE_SAMPLE_SUB31_BASE, + .size = K230_GNNE_RAW_WRITE_SAMPLE_SUB31_SIZE, + }, { + .base = K230_GNNE_RAW_WRITE_SAMPLE_SUB9_BASE, + .size = K230_GNNE_RAW_WRITE_SAMPLE_SUB9_SIZE, + }, +}; + +static const K230GnneTraceWindow k230_gnne_mfu_act1_sample_windows[] = { + { + .base = K230_GNNE_MFU_ACT1_SAMPLE_BASE, + .size = K230_GNNE_MFU_ACT1_SAMPLE_SIZE, + }, { + .base = K230_GNNE_MFU_ACT1_SAMPLE_PU_SRC_BASE, + .size = K230_GNNE_MFU_ACT1_SAMPLE_PU_SRC_SIZE, + }, { + .base = K230_GNNE_MFU_ACT1_SAMPLE_SUB9_DST_BASE, + .size = K230_GNNE_MFU_ACT1_SAMPLE_SUB9_DST_SIZE, + }, { + .base = K230_GNNE_SUB32_SAMPLE_BASE, + .size = K230_GNNE_SUB32_SAMPLE_SIZE, + }, +}; + +static const K230GnneTraceWindow k230_gnne_pu_act0_sample_windows[] = { + { + .base = K230_GNNE_PU_ACT0_SAMPLE_BASE, + .size = K230_GNNE_PU_ACT0_SAMPLE_SIZE, + }, { + .base = K230_GNNE_SUB32_SAMPLE_BASE, + .size = K230_GNNE_SUB32_SAMPLE_SIZE, + }, +}; + +static bool k230_gnne_trace_window_contains( + const K230GnneTraceWindow *windows, size_t count, uint64_t addr) +{ + for (size_t i = 0; i < count; i++) { + if (windows[i].size && addr >= windows[i].base && + addr - windows[i].base < windows[i].size) { + return true; + } + } + + return false; +} + +static bool k230_gnne_range_overlaps(uint64_t base, uint64_t size, + uint64_t addr, uint64_t access_size) +{ + if (!size || !access_size || + addr > UINT64_MAX - access_size || + base > UINT64_MAX - size) { + return false; + } + + return addr < base + size && base < addr + access_size; +} + +static bool k230_gnne_trace_window_overlaps( + const K230GnneTraceWindow *windows, size_t count, uint64_t addr, + uint64_t access_size) +{ + for (size_t i = 0; i < count; i++) { + if (k230_gnne_range_overlaps(windows[i].base, windows[i].size, + addr, access_size)) { + return true; + } + } + + return false; +} + +typedef struct K230GnneMmu { + uint32_t start; + uint32_t depth; + bool valid; +} K230GnneMmu; + +typedef struct K230GnneL2Conf { + uint32_t rstride_d; + uint32_t rstride_s; + K230GnneStride stride_d; + K230GnneStride stride_s; + uint32_t l2_datatype; + uint32_t ddr_datatype; + uint32_t rlen_compressed; + uint32_t rlen_decompressed; + bool enable_decompress; + bool stride_d_valid; + bool stride_s_valid; + bool valid; +} K230GnneL2Conf; + +typedef struct K230GnneMfuAct1Src1 { + uint32_t rslice; + uint32_t rright_repeats; + uint32_t rslice_repeats; + bool slice_loc; + bool valid; +} K230GnneMfuAct1Src1; + +typedef struct K230GnneMfuAct1Src2 { + uint32_t rleft_repeats; + uint32_t rshape; + bool source_type; + bool valid; +} K230GnneMfuAct1Src2; + +typedef struct K230GnneMfuAct1Deq { + uint32_t rscale; + uint32_t rbias; + uint32_t quant_type; + uint32_t rshift_bits; + bool valid; +} K230GnneMfuAct1Deq; + +typedef struct K230GnneMfuAct1Conf { + uint32_t rstride_s1; + uint32_t rstride_s2; + uint32_t rstride_d1; + bool stride_valid; + K230GnneMfuAct1Src1 src1[2]; + K230GnneMfuAct1Src2 src2[2]; + uint32_t dest_rlen; + uint64_t dest_len; + uint32_t dest_rshape; + bool dest_valid; + bool dest_len_valid; + K230GnneMfuAct1Deq deq[2]; + uint32_t quant_type; + uint32_t quant_rshift_bits; + bool quant_valid; + uint32_t funct4; + bool is_by_channel; + bool is_16_segments; + bool act_valid; +} K230GnneMfuAct1Conf; + +typedef struct K230GnneMfuPdp1Conf { + uint32_t stride_w; + uint32_t stride_h; + uint32_t rstride_s; + uint32_t funct2; + uint32_t rstride_d; + bool conf1_valid; + uint32_t rcount_w; + uint32_t rcount_h; + uint32_t rpe_h; + uint32_t rpe_last_h; + bool conf2_valid; + uint32_t rpe_channels; + uint32_t rpe_last_channels; + uint32_t rpad_value; + uint32_t sspad; + bool conf3_valid; + uint32_t rwindow_w; + uint32_t rwindow_h; + uint32_t rscale; + bool enable_h2c; + bool enable_bw; + bool conf4_valid; + K230GnneMfuAct1Deq deq; + uint32_t quant_rscale; + uint32_t quant_rbias; + uint32_t quant_type; + uint32_t quant_rshift_bits; + bool quant_valid; +} K230GnneMfuPdp1Conf; + +typedef struct K230GnneMfuTransposeConf { + uint32_t rstride_d; + uint32_t rstride_s; + uint32_t l2_datatype; + uint32_t permute; + bool valid; +} K230GnneMfuTransposeConf; + +typedef struct K230GnneDmLoadL1Conf { + uint32_t rstride_s; + uint32_t datatype; + uint32_t l1_type; + bool valid; +} K230GnneDmLoadL1Conf; + +typedef struct K230GnneDmLoadL1 { + uint32_t raddr_s; + uint32_t rshape; + bool valid; +} K230GnneDmLoadL1; + +typedef struct K230GnneDmLoadWConf { + uint32_t kernel_h; + uint32_t kernel_w; + uint32_t rstride_oc; + uint32_t rgroups; + uint32_t rgoc; + uint32_t quant_type; + bool valid; +} K230GnneDmLoadWConf; + +typedef struct K230GnneDmLoadW { + uint32_t raddr_s; + uint32_t raddr_bw; + uint32_t r_iochannels; + bool valid; +} K230GnneDmLoadW; + +typedef struct K230GnneDmLoadAct0 { + uint32_t raddr_s; + uint32_t rlen; + bool is_by_channel; + bool valid; +} K230GnneDmLoadAct0; + +typedef struct K230GnneDmStoreOf { + uint32_t raddr_d; + uint32_t rshape; + bool valid; +} K230GnneDmStoreOf; + +typedef struct K230GnnePuConf { + uint32_t stride_w; + uint32_t stride_h; + uint32_t rstride_s; + uint32_t rgic; + uint32_t rgic_last; + uint32_t raddr_s; + uint32_t rgroups; + uint32_t rshape; + uint32_t rpad_value; + uint32_t sspad; + uint32_t ric; + uint32_t rbx; + uint32_t quant_type; + uint32_t kernel_h; + uint32_t kernel_w; + uint32_t rgoc; + uint32_t rgoc_last; + uint32_t rstride_d; + uint32_t raddr_d; + uint32_t rshape_d; + K230GnneShape output_shape; + K230GnneStride output_stride; + bool load_psum; + bool clr_psum; + bool release_if; + uint32_t dest_target; + uint32_t mode; + bool fetch1_valid; + bool fetch2_valid; + bool fetch3_valid; + bool fetch4_valid; + bool fetch_deq_valid; + bool w_valid; + bool of1_valid; + bool of2_valid; + bool output_shape_valid; + bool output_stride_valid; + bool compute_valid; +} K230GnnePuConf; + +typedef struct K230GnnePdp0Conf { + uint32_t mode; + uint32_t stride_w; + uint32_t stride_h; + uint32_t rgic; + uint32_t rgic_last; + uint32_t rshape; + K230GnneShape input_shape; + uint32_t rpad_value; + uint32_t sspad; + uint32_t rbx; + uint32_t quant_type; + uint32_t kernel_h; + uint32_t kernel_w; + uint32_t rstride_d; + uint32_t rshape_d; + K230GnneShape output_shape; + K230GnneStride output_stride; + bool mode_valid; + bool fetch1_valid; + bool fetch2_valid; + bool fetch3_valid; + bool input_shape_valid; + bool fetch4_valid; + bool fetch_deq_valid; + bool w_valid; + bool of_valid; + bool output_shape_valid; + bool output_stride_valid; +} K230GnnePdp0Conf; + +typedef struct K230GnneAct0Conf { + uint32_t rshape; + uint32_t rshift_bits; + bool valid; +} K230GnneAct0Conf; + +typedef struct K230GnneAct0Compute { + uint32_t raddr_d; + uint32_t dest_datatype; + bool is_by_channel; + bool valid; +} K230GnneAct0Compute; + +typedef struct K230GnneFrontend { + K230GnneScalar gp[K230_GNNE_GP_COUNT]; + K230GnneShape shape[K230_GNNE_SHAPE_COUNT]; + K230GnneStride stride[K230_GNNE_SHAPE_COUNT]; + K230GnneMmu mmu[K230_GNNE_MMU_COUNT]; + uint64_t glb_base; + bool glb_base_valid; + uint64_t rdata_base; + bool rdata_base_valid; + const uint8_t *rdata_shadow; + uint64_t rdata_shadow_base; + uint64_t rdata_shadow_size; + bool rdata_shadow_valid; + uint64_t runtime_arg_base; + bool runtime_arg_base_valid; + uint32_t runtime_arg_addr_words; + uint32_t runtime_arg_words[K230_GNNE_RUNTIME_ARG_WORDS]; + bool runtime_window; + K230GnneL2Conf l2_load_conf; + K230GnneL2Conf l2_load_w_conf; + K230GnneL2Conf l2_store_conf; + K230GnneMfuAct1Conf mfu_act1; + K230GnneMfuPdp1Conf mfu_pdp1; + K230GnneMfuTransposeConf mfu_transpose; + K230GnneDmLoadL1Conf dm_load_l1_conf; + K230GnneDmLoadL1 dm_load_l1; + K230GnneDmLoadWConf dm_load_w_conf; + K230GnneDmLoadW dm_load_w; + K230GnneDmLoadAct0 dm_load_act0; + K230GnneDmStoreOf dm_store_of; + K230GnnePuConf pu_conf; + K230GnnePdp0Conf pdp0_conf; + K230GnneAct0Conf act0_conf; + K230GnneAct0Compute act0_compute; + int32_t *pu_psum; + uint64_t pu_psum_count; + uint64_t pu_psum_base; + bool pu_psum_valid; + bool pu_psum_base_valid; + uint64_t instructions; + uint64_t l2_loads; + uint64_t l2_load_ws; + uint64_t l2_stores; + uint64_t mfu_act1s; + uint64_t mfu_pdp1s; + uint64_t mfu_transposes; + uint64_t ai2d_computes; + uint64_t pu_computes; + uint64_t pdp0_computes; + uint64_t input_bytes; + uint64_t output_bytes; + uint64_t unknown; + uint64_t current_pc; + uint8_t *glb_cache; + uint64_t glb_cache_base; + uint64_t glb_cache_size; + bool glb_cache_dirty; + uint8_t *glb_alias; + unsigned long *glb_alias_valid; + uint8_t *if_data; + uint64_t if_data_size; + uint8_t *glb_banks[K230_GNNE_GLB_BANK_COUNT]; +} K230GnneFrontend; + +static K230GnneFrontend *k230_gnne_active_fe; + +static const char *k230_kpu_name(K230KpuState *s) +{ + return object_get_canonical_path_component(OBJECT(s)); +} + +static bool k230_kpu_range_ok(hwaddr addr, unsigned int size) +{ + return addr <= K230_KPU_SIZE && size <= K230_KPU_SIZE - addr; +} + +static bool k230_kpu_access_hits(hwaddr addr, unsigned int size, + hwaddr offset) +{ + return addr <= offset && offset < addr + size; +} + +static bool k230_kpu_control_has(uint64_t val, uint32_t bits) +{ + return (val & bits) == bits || (((val >> 32) & bits) == bits); +} + +static bool k230_gnne_cache_access(K230GnneFrontend *fe, uint64_t addr, + uint64_t size, uint8_t **ptr) +{ + uint64_t offset; + + if (!fe || !fe->glb_cache || addr < fe->glb_cache_base || + size > fe->glb_cache_size) { + return false; + } + + offset = addr - fe->glb_cache_base; + if (offset > fe->glb_cache_size - size) { + return false; + } + + *ptr = fe->glb_cache + offset; + return true; +} + +static bool k230_gnne_bank_decode_addr(uint64_t addr, unsigned int *bank, + uint64_t *bank_addr) +{ + uint64_t bank_offset; + uint64_t bank_index; + + if (addr < K230_GNNE_GLB_BANK_VBASE) { + return false; + } + + bank_offset = addr - K230_GNNE_GLB_BANK_VBASE; + bank_index = bank_offset / K230_GNNE_GLB_CACHE_SIZE; + if (bank_index >= K230_GNNE_GLB_BANK_COUNT) { + return false; + } + + *bank = (unsigned int)bank_index; + *bank_addr = bank_offset % K230_GNNE_GLB_CACHE_SIZE; + return true; +} + +static bool k230_gnne_bank_access(K230GnneFrontend *fe, uint64_t addr, + uint64_t size, uint8_t **ptr, + unsigned int *bank_out, + uint64_t *bank_addr_out) +{ + unsigned int bank; + uint64_t bank_addr; + + if (!fe || !k230_gnne_bank_decode_addr(addr, &bank, &bank_addr)) { + return false; + } + + if (size > K230_GNNE_GLB_CACHE_SIZE || + bank_addr > K230_GNNE_GLB_CACHE_SIZE - size) { + return false; + } + + if (!fe->glb_banks[bank]) { + fe->glb_banks[bank] = g_malloc0(K230_GNNE_GLB_CACHE_SIZE); + } + + *ptr = fe->glb_banks[bank] + bank_addr; + if (bank_out) { + *bank_out = bank; + } + if (bank_addr_out) { + *bank_addr_out = bank_addr; + } + return true; +} + +static bool k230_gnne_alias_offset(K230GnneFrontend *fe, uint64_t addr, + uint64_t size, uint64_t *offset) +{ + if (!fe || !fe->glb_cache || !fe->glb_cache_size || + addr < fe->glb_cache_base || size > fe->glb_cache_size) { + return false; + } + + *offset = addr - fe->glb_cache_base; + return *offset <= fe->glb_cache_size - size; +} + +static void k230_gnne_alias_read(K230GnneFrontend *fe, uint64_t offset, + void *buf, uint64_t size) +{ + uint8_t *bytes = buf; + + if (!fe->glb_alias || !fe->glb_alias_valid) { + return; + } + + for (uint64_t i = 0; i < size; i++) { + if (test_bit((long)(offset + i), fe->glb_alias_valid)) { + bytes[i] = fe->glb_alias[offset + i]; + } + } +} + +static void k230_gnne_alias_write(K230GnneFrontend *fe, uint64_t logical, + const void *buf, uint64_t size) +{ + if (!fe || !fe->glb_cache || size > K230_GNNE_GLB_CACHE_SIZE || + logical > K230_GNNE_GLB_CACHE_SIZE - size) { + return; + } + + if (!fe->glb_alias) { + fe->glb_alias = g_malloc0(K230_GNNE_GLB_CACHE_SIZE); + } + if (!fe->glb_alias_valid) { + fe->glb_alias_valid = bitmap_new(K230_GNNE_GLB_CACHE_SIZE); + } + + memcpy(fe->glb_alias + logical, buf, size); + bitmap_set(fe->glb_alias_valid, (long)logical, (long)size); +} + +static void k230_gnne_alias_clear(K230GnneFrontend *fe, uint64_t addr, + uint64_t size) +{ + uint64_t offset; + + if (!fe || !fe->glb_alias_valid || + !k230_gnne_alias_offset(fe, addr, size, &offset)) { + return; + } + + bitmap_clear(fe->glb_alias_valid, (long)offset, (long)size); +} + +static void k230_gnne_alias_bank_write(K230GnneFrontend *fe, + unsigned int bank, + uint64_t bank_addr, const void *buf, + uint64_t size) +{ + uint64_t logical; + + if (!fe || !fe->glb_cache || bank >= K230_GNNE_MMU_COUNT || + !fe->mmu[bank].valid || + umul64_overflow((uint64_t)fe->mmu[bank].start, 32, &logical) || + UINT64_MAX - logical < bank_addr) { + return; + } + + logical += bank_addr; + if (size > K230_GNNE_GLB_CACHE_SIZE || + logical > K230_GNNE_GLB_CACHE_SIZE - size) { + return; + } + + k230_gnne_alias_write(fe, logical, buf, size); +} + +static bool k230_gnne_bank_read(K230GnneFrontend *fe, uint64_t addr, + void *buf, uint64_t size) +{ + uint8_t *ptr; + + if (!k230_gnne_bank_access(fe, addr, size, &ptr, NULL, NULL)) { + return false; + } + + memcpy(buf, ptr, size); + return true; +} + +static bool k230_gnne_bank_write(K230GnneFrontend *fe, uint64_t addr, + const void *buf, uint64_t size) +{ + uint8_t *ptr; + unsigned int bank; + uint64_t bank_addr; + + if (!k230_gnne_bank_access(fe, addr, size, &ptr, &bank, &bank_addr)) { + return false; + } + + memcpy(ptr, buf, size); + k230_gnne_alias_bank_write(fe, bank, bank_addr, buf, size); + fe->glb_cache_dirty = true; + return true; +} + +static bool k230_gnne_cache_read(K230GnneFrontend *fe, uint64_t addr, + void *buf, uint64_t size) +{ + uint8_t *ptr; + + if (!k230_gnne_cache_access(fe, addr, size, &ptr)) { + return false; + } + + memcpy(buf, ptr, size); + k230_gnne_alias_read(fe, addr - fe->glb_cache_base, buf, size); + return true; +} + +static bool k230_gnne_cache_write(K230GnneFrontend *fe, uint64_t addr, + const void *buf, uint64_t size) +{ + uint8_t *ptr; + + if (!k230_gnne_cache_access(fe, addr, size, &ptr)) { + return false; + } + + memcpy(ptr, buf, size); + k230_gnne_alias_clear(fe, addr, size); + fe->glb_cache_dirty = true; + return true; +} + +static bool k230_gnne_rdata_shadow_read(K230GnneFrontend *fe, uint64_t addr, + void *buf, uint64_t size) +{ + uint64_t offset; + + if (!fe || !fe->rdata_shadow_valid || + addr < fe->rdata_shadow_base || + size > fe->rdata_shadow_size) { + return false; + } + + offset = addr - fe->rdata_shadow_base; + if (offset > fe->rdata_shadow_size - size) { + return false; + } + + memcpy(buf, fe->rdata_shadow + offset, size); + return true; +} + +static uint64_t k230_kpu_read_bytes(uint8_t *regs, hwaddr addr, + unsigned int size) +{ + uint64_t val = 0; + + for (int i = 0; i < size; i++) { + val |= (uint64_t)regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_kpu_write_bytes(uint8_t *regs, hwaddr addr, uint64_t val, + unsigned int size) +{ + for (int i = 0; i < size; i++) { + regs[addr + i] = val >> (i * 8); + } +} + +static uint32_t k230_kpu_readl_regs(K230KpuState *s, hwaddr addr) +{ + return ldl_le_p(s->regs + addr); +} + +static void k230_kpu_writeq_regs(K230KpuState *s, hwaddr addr, uint64_t val) +{ + stq_le_p(s->regs + addr, val); +} + +static bool k230_gnne_read_phys_u32(uint64_t addr, uint32_t *value) +{ + uint8_t buf[4]; + + if (dma_memory_read(&address_space_memory, addr, buf, sizeof(buf), + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + return false; + } + + *value = ldl_le_p(buf); + return true; +} + +static bool k230_gnne_base_has_alias_anchor(uint64_t base) +{ + uint32_t value; + + if (UINT64_MAX - base < sizeof(value) * 3) { + return false; + } + + return k230_gnne_read_phys_u32(base + sizeof(value) * 2, &value) && + value == K230_GNNE_RDATA_ALIAS_BASE; +} + +static bool k230_gnne_command_in_runtime_window(uint64_t command_start) +{ + return command_start >= K230_GNNE_RUNTIME_RDATA_BASE && + command_start - K230_GNNE_RUNTIME_RDATA_BASE < + K230_GNNE_RUNTIME_WINDOW_SIZE; +} + +static bool k230_gnne_runtime_phys_candidate(K230GnneFrontend *fe, + uint32_t value) +{ + if (!fe->runtime_window || !value) { + return false; + } + + if (fe->glb_base_valid && value >= fe->glb_base && + value - fe->glb_base < K230_GNNE_RUNTIME_WINDOW_SIZE) { + return true; + } + + return value >= K230_GNNE_RUNTIME_DDR_BASE && + value - K230_GNNE_RUNTIME_DDR_BASE < + K230_GNNE_RUNTIME_WINDOW_SIZE; +} + +static bool k230_gnne_find_runtime_arg_table(K230KpuState *s, + K230GnneFrontend *fe) +{ + uint8_t table[K230_GNNE_RUNTIME_ARG_PREFIX]; + uint32_t words[K230_GNNE_RUNTIME_ARG_WORDS]; + uint32_t addr_words = 0; + uint32_t rdata_base; + + if (!fe->runtime_window || !fe->rdata_base_valid || + fe->rdata_base > UINT32_MAX) { + return false; + } + + if (dma_memory_read(&address_space_memory, + K230_GNNE_RUNTIME_ARG_TABLE_BASE, + table, sizeof(table), + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + return false; + } + + for (unsigned int i = 0; i < G_N_ELEMENTS(words); i++) { + words[i] = ldl_le_p(table + i * sizeof(words[0])); + } + + rdata_base = fe->rdata_base; + while (addr_words < K230_GNNE_RUNTIME_ARG_MAX_ADDRS && + words[addr_words] != rdata_base) { + if (!k230_gnne_runtime_phys_candidate(fe, words[addr_words])) { + return false; + } + addr_words++; + } + + if (addr_words < K230_GNNE_RUNTIME_ARG_MIN_ADDRS || + words[addr_words] != rdata_base || + words[addr_words + 1] != 0) { + return false; + } + + fe->runtime_arg_base = K230_GNNE_RUNTIME_ARG_TABLE_BASE; + fe->runtime_arg_base_valid = true; + fe->runtime_arg_addr_words = addr_words; + memcpy(fe->runtime_arg_words, words, sizeof(words)); + trace_k230_kpu_runtime_arg_table( + k230_kpu_name(s), fe->runtime_arg_base, addr_words, + words[0], words[1], words[2], words[3]); + return true; +} + +static void k230_gnne_select_rdata_base(K230GnneFrontend *fe) +{ + fe->rdata_base = fe->glb_base; + fe->rdata_base_valid = fe->glb_base_valid; + + if (!fe->glb_base_valid || + k230_gnne_base_has_alias_anchor(fe->glb_base)) { + return; + } + + if (k230_gnne_base_has_alias_anchor(K230_GNNE_RDATA_FALLBACK_BASE)) { + fe->rdata_base = K230_GNNE_RDATA_FALLBACK_BASE; + fe->rdata_base_valid = true; + } +} + +static void k230_kpu_clear_rdata_shadow(K230KpuState *s) +{ + g_clear_pointer(&s->gnne_rdata_shadow, g_free); + s->gnne_rdata_shadow_base = 0; + s->gnne_rdata_shadow_size = 0; + s->gnne_rdata_shadow_valid = false; +} + +static void k230_kpu_ensure_rdata_shadow(K230KpuState *s, uint64_t base) +{ + if (s->gnne_rdata_shadow_valid && + s->gnne_rdata_shadow_base == base && + s->gnne_rdata_shadow_size == K230_GNNE_RDATA_SHADOW_SIZE) { + return; + } + + k230_kpu_clear_rdata_shadow(s); + s->gnne_rdata_shadow = g_malloc(K230_GNNE_RDATA_SHADOW_SIZE); + if (dma_memory_read(&address_space_memory, base, s->gnne_rdata_shadow, + K230_GNNE_RDATA_SHADOW_SIZE, + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + k230_kpu_clear_rdata_shadow(s); + return; + } + + s->gnne_rdata_shadow_base = base; + s->gnne_rdata_shadow_size = K230_GNNE_RDATA_SHADOW_SIZE; + s->gnne_rdata_shadow_valid = true; +} + +static uint64_t k230_kpu_command_addr(K230KpuState *s, hwaddr offset) +{ + uint64_t lo = k230_kpu_readl_regs(s, offset); + uint64_t hi = k230_kpu_readl_regs(s, K230_KPU_COMMAND_HI); + + return (hi << 32) | lo; +} + +static void k230_gnne_frontend_init(K230KpuState *s, K230GnneFrontend *fe, + uint64_t command_start) +{ + memset(fe, 0, sizeof(*fe)); + fe->gp[0].valid = true; + fe->gp[0].value = 0; + fe->runtime_window = k230_gnne_command_in_runtime_window(command_start); + if (fe->runtime_window) { + fe->glb_base = K230_GNNE_RUNTIME_RDATA_BASE; + fe->glb_base_valid = true; + } else if (command_start >= K230_GNNE_COMMAND_BASE_OFFSET) { + fe->glb_base = command_start - K230_GNNE_COMMAND_BASE_OFFSET; + fe->glb_base_valid = true; + } + + if (fe->glb_base_valid) { + fe->glb_cache_base = fe->glb_base; + fe->glb_cache_size = K230_GNNE_GLB_CACHE_SIZE; + k230_gnne_select_rdata_base(fe); + if (fe->rdata_base_valid && + k230_gnne_command_in_runtime_window(command_start)) { + k230_kpu_ensure_rdata_shadow(s, fe->rdata_base); + if (s->gnne_rdata_shadow_valid && + s->gnne_rdata_shadow_base == fe->rdata_base) { + fe->rdata_shadow = s->gnne_rdata_shadow; + fe->rdata_shadow_base = s->gnne_rdata_shadow_base; + fe->rdata_shadow_size = s->gnne_rdata_shadow_size; + fe->rdata_shadow_valid = true; + } + } + k230_gnne_find_runtime_arg_table(s, fe); + fe->glb_cache = g_malloc(fe->glb_cache_size); + if (dma_memory_read(&address_space_memory, fe->glb_cache_base, + fe->glb_cache, fe->glb_cache_size, + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + g_clear_pointer(&fe->glb_cache, g_free); + fe->glb_cache_size = 0; + } + } +} + +static void k230_gnne_frontend_destroy(K230GnneFrontend *fe) +{ + if (fe->glb_cache && fe->glb_cache_dirty) { + dma_memory_write(&address_space_memory, fe->glb_cache_base, + fe->glb_cache, fe->glb_cache_size, + MEMTXATTRS_UNSPECIFIED); + } + + g_free(fe->glb_cache); + g_free(fe->glb_alias); + g_free(fe->glb_alias_valid); + for (unsigned int b = 1; b < K230_GNNE_GLB_BANK_COUNT; b++) { + g_free(fe->glb_banks[b]); + fe->glb_banks[b] = NULL; + } + g_free(fe->if_data); + g_free(fe->pu_psum); +} + +static uint32_t k230_gnne_gp(K230GnneFrontend *fe, unsigned int reg, + bool *valid) +{ + if (reg >= K230_GNNE_GP_COUNT || !fe->gp[reg].valid) { + *valid = false; + return 0; + } + + *valid = true; + return fe->gp[reg].value; +} + +static bool k230_gnne_gp_direct_physical(K230GnneFrontend *fe, + unsigned int reg) +{ + return reg < K230_GNNE_GP_COUNT && fe->gp[reg].valid && + fe->gp[reg].direct_physical; +} + +static bool k230_gnne_translate(K230GnneFrontend *fe, uint32_t encoded, + uint64_t *physical, uint64_t *logical); + +static bool k230_gnne_runtime_arg_offset(uint32_t base, uint32_t value, + uint64_t *offset) +{ + if (value < base) { + return false; + } + + *offset = (uint64_t)value - base; + return *offset < K230_GNNE_RUNTIME_WINDOW_SIZE; +} + +static bool k230_gnne_gp_runtime_arg(K230GnneFrontend *fe, unsigned int reg, + uint32_t *slot, uint32_t *base, + uint64_t *offset) +{ + K230GnneScalar *gp; + + if (reg >= K230_GNNE_GP_COUNT) { + return false; + } + + gp = &fe->gp[reg]; + if (!gp->valid || !gp->runtime_arg_valid) { + return false; + } + + if (slot) { + *slot = gp->runtime_arg_slot; + } + if (base) { + *base = gp->runtime_arg_base; + } + if (offset) { + *offset = gp->runtime_arg_offset; + } + return true; +} + +static bool k230_gnne_set_gp_runtime_arg(K230GnneFrontend *fe, + unsigned int reg, uint32_t slot, + uint32_t base, uint32_t value) +{ + uint64_t offset; + + if (reg >= K230_GNNE_GP_COUNT || !fe->gp[reg].valid || + !fe->gp[reg].direct_physical || + !k230_gnne_runtime_arg_offset(base, value, &offset)) { + return false; + } + + fe->gp[reg].runtime_arg_valid = true; + fe->gp[reg].runtime_arg_slot = slot; + fe->gp[reg].runtime_arg_base = base; + fe->gp[reg].runtime_arg_offset = offset; + return true; +} + +static bool k230_gnne_runtime_arg_slot_for_encoded(K230GnneFrontend *fe, + uint32_t encoded, + uint32_t *slot) +{ + uint64_t logical; + + if (!fe->runtime_arg_base_valid || + !k230_gnne_translate(fe, encoded, NULL, &logical) || + logical >= (uint64_t)fe->runtime_arg_addr_words * sizeof(uint32_t) || + (logical & (sizeof(uint32_t) - 1))) { + return false; + } + + *slot = logical / sizeof(uint32_t); + return true; +} + +static void k230_gnne_set_gp_ex(K230GnneFrontend *fe, unsigned int reg, + uint32_t value, bool valid, + bool direct_physical) +{ + if (reg >= K230_GNNE_GP_COUNT) { + return; + } + + if (reg == 0) { + fe->gp[0].value = 0; + fe->gp[0].valid = true; + fe->gp[0].direct_physical = false; + fe->gp[0].runtime_arg_valid = false; + return; + } + + fe->gp[reg].value = value; + fe->gp[reg].valid = valid; + fe->gp[reg].direct_physical = valid && direct_physical; + fe->gp[reg].runtime_arg_valid = false; +} + +static void k230_gnne_set_gp(K230GnneFrontend *fe, unsigned int reg, + uint32_t value, bool valid) +{ + k230_gnne_set_gp_ex(fe, reg, value, valid, false); +} + +static bool k230_gnne_translate(K230GnneFrontend *fe, uint32_t encoded, + uint64_t *physical, uint64_t *logical) +{ + unsigned int mmu_id = extract32(encoded, 28, 4); + uint64_t offset = encoded & 0x0fffffff; + uint64_t addr; + + if (!fe->glb_base_valid || mmu_id >= K230_GNNE_MMU_COUNT || + !fe->mmu[mmu_id].valid) { + return false; + } + + addr = (uint64_t)fe->mmu[mmu_id].start * 32 + offset; + if (addr >= K230_GNNE_GLB_CACHE_SIZE) { + return false; + } + + if (logical) { + *logical = addr; + } + if (physical) { + if (mmu_id == 0) { + *physical = fe->glb_base + addr; + } else { + *physical = K230_GNNE_GLB_BANK_VBASE + + (uint64_t)mmu_id * K230_GNNE_GLB_CACHE_SIZE + + offset; + } + } + return true; +} + +static bool k230_gnne_translate_rdata_alias(K230GnneFrontend *fe, + uint32_t encoded, + uint64_t *physical, + uint64_t *logical) +{ + uint64_t offset; + + if (!fe->rdata_base_valid || encoded < K230_GNNE_RDATA_ALIAS_BASE) { + return false; + } + + offset = (uint64_t)encoded - K230_GNNE_RDATA_ALIAS_BASE; + if (offset >= K230_GNNE_RUNTIME_WINDOW_SIZE || + UINT64_MAX - fe->rdata_base < offset) { + return false; + } + + if (logical) { + *logical = encoded; + } + if (physical) { + *physical = fe->rdata_base + offset; + } + return true; +} + +static bool k230_gnne_translate_store_dest(K230GnneFrontend *fe, + uint32_t encoded, + uint64_t *physical, + uint64_t *logical) +{ + return k230_gnne_translate(fe, encoded, physical, logical) || + k230_gnne_translate_rdata_alias(fe, encoded, physical, logical); +} + +static bool k230_gnne_translate_direct_physical(K230GnneFrontend *fe, + uint32_t encoded, + uint64_t *physical, + uint64_t *logical) +{ + if (!k230_gnne_runtime_phys_candidate(fe, encoded)) { + return false; + } + + if (physical) { + *physical = encoded; + } + if (logical) { + *logical = encoded; + } + return true; +} + +static bool k230_gnne_runtime_ddr_offset(K230GnneFrontend *fe, + uint64_t logical, + uint64_t *offset) +{ + if (!fe->runtime_window) { + return false; + } + + if (logical >= K230_GNNE_RDATA_ALIAS_BASE) { + logical -= K230_GNNE_RDATA_ALIAS_BASE; + } + + if (logical >= K230_GNNE_RUNTIME_WINDOW_SIZE) { + return false; + } + + *offset = logical; + return true; +} + +static bool k230_gnne_runtime_ddr_addr(K230GnneFrontend *fe, uint64_t logical, + uint64_t *physical) +{ + uint64_t offset; + + if (!k230_gnne_runtime_ddr_offset(fe, logical, &offset) || + UINT64_MAX - K230_GNNE_RUNTIME_DDR_BASE < offset) { + return false; + } + + *physical = K230_GNNE_RUNTIME_DDR_BASE + offset; + return true; +} + +static bool k230_gnne_runtime_ddr_source_addr(K230GnneFrontend *fe, + uint64_t source, + uint64_t *physical) +{ + if (!fe->runtime_window || source >= K230_GNNE_RDATA_ALIAS_BASE) { + return false; + } + + if (source >= K230_GNNE_RUNTIME_DDR_BASE && + source - K230_GNNE_RUNTIME_DDR_BASE < + K230_GNNE_RUNTIME_WINDOW_SIZE) { + *physical = source; + return true; + } + + return k230_gnne_runtime_ddr_addr(fe, source, physical); +} + +static bool k230_gnne_l2_load_w_source_nonzero(uint64_t source, + uint64_t bytes) +{ + uint8_t buf[K230_GNNE_L2_LOAD_W_SOURCE_PROBE_SIZE]; + + bytes = MIN(bytes, (uint64_t)sizeof(buf)); + if (!bytes || + dma_memory_read(&address_space_memory, source, buf, bytes, + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + return false; + } + + for (uint64_t i = 0; i < bytes; i++) { + if (buf[i]) { + return true; + } + } + + return false; +} + +static uint64_t k230_gnne_l2_load_w_source(K230GnneFrontend *fe, + uint32_t source, + uint64_t dst_logical, + uint32_t rlen, + unsigned int src_size) +{ + uint64_t logical; + uint64_t rebased; + uint64_t source_bytes; + uint64_t bias; + + if (source < K230_GNNE_RUNTIME_WINDOW_SIZE && + k230_gnne_translate(fe, source, NULL, &logical) && + logical != source) { + return logical; + } + + if (fe->runtime_window && + source >= MiB && source < K230_GNNE_RUNTIME_WINDOW_SIZE && + dst_logical >= K230_GNNE_FUNCTION_GLB_ARG_BASE) { + bias = dst_logical - K230_GNNE_FUNCTION_GLB_ARG_BASE; + if (UINT64_MAX - source >= bias && + source + bias < K230_GNNE_RUNTIME_WINDOW_SIZE) { + return source + bias; + } + } + + if (fe->runtime_window && fe->rdata_base_valid && + source >= fe->rdata_base && + source - fe->rdata_base < K230_GNNE_RUNTIME_WINDOW_SIZE && + dst_logical >= K230_GNNE_FUNCTION_GLB_ARG_BASE) { + bias = dst_logical - K230_GNNE_FUNCTION_GLB_ARG_BASE; + if (UINT64_MAX - source >= bias) { + rebased = source + bias; + if (rebased - fe->rdata_base < K230_GNNE_RUNTIME_WINDOW_SIZE && + !umul64_overflow(rlen, src_size, &source_bytes) && + !k230_gnne_l2_load_w_source_nonzero(source, source_bytes) && + k230_gnne_l2_load_w_source_nonzero(rebased, source_bytes)) { + return rebased; + } + } + } + + return source; +} + +static bool k230_gnne_l2_load_w_synth_arg(K230GnneFrontend *fe, + K230GnneL2Conf *conf, + uint32_t source, uint32_t rlen, + uint32_t valid_c, + uint32_t index, void *buf, + unsigned int size) +{ + static const uint16_t identity_pattern[] = { + 0x0000, 0x3c00, 0x3c00, 0x0000, 0x0000, 0xfc00, 0x7c00, + }; + static const uint16_t half_pattern[] = { + 0x0000, 0x3800, 0x3800, 0x0000, 0x0000, 0xfc00, 0x7c00, + }; + static const uint16_t bbox_scale_pattern[] = { + 0x0000, 0x681c, 0x681c, 0x0000, 0x0000, 0xe7ff, 0x67ff, + }; + const uint16_t *pattern = NULL; + uint64_t rdata_offset; + uint16_t value; + + if (!fe->runtime_window || + valid_c != K230_GNNE_L2_LANE_WIDTH || + conf->l2_datatype != 1 || conf->ddr_datatype != 1 || + size != sizeof(uint16_t)) { + return false; + } + + if (source < K230_GNNE_RUNTIME_WINDOW_SIZE && + rlen % G_N_ELEMENTS(identity_pattern) == 0) { + pattern = rlen == G_N_ELEMENTS(identity_pattern) ? + half_pattern : identity_pattern; + } else if (rlen == G_N_ELEMENTS(identity_pattern) && + fe->rdata_base_valid && + source >= fe->rdata_base) { + rdata_offset = source - fe->rdata_base; + switch (rdata_offset) { + case 0: + pattern = identity_pattern; + break; + case 0x20de: + pattern = half_pattern; + break; + case 0x210b: + pattern = bbox_scale_pattern; + break; + default: + break; + } + } + if (!pattern) { + return false; + } + + value = pattern[index % G_N_ELEMENTS(identity_pattern)]; + stw_le_p(buf, value); + return true; +} + +static uint32_t k230_gnne_scalar_raw(const uint8_t *buf, unsigned int size) +{ + switch (size) { + case 4: + return ldl_le_p(buf); + case 2: + return lduw_le_p(buf); + case 1: + return buf[0]; + default: + return 0; + } +} + +static bool k230_gnne_read_scalar(K230GnneFrontend *fe, uint32_t encoded, + unsigned int size, bool sign, + uint64_t pc, uint32_t *value, + bool *direct_physical) +{ + uint64_t logical; + uint64_t physical; + uint8_t buf[4] = {}; + uint8_t shadow_buf[4] = {}; + uint8_t live_buf[4] = {}; + + if (direct_physical) { + *direct_physical = false; + } + + if (size > sizeof(buf) || + !k230_gnne_translate(fe, encoded, NULL, &logical) || + !fe->rdata_base_valid || + UINT64_MAX - fe->rdata_base < logical) { + return false; + } + + if (fe->runtime_arg_base_valid && + logical < K230_GNNE_RUNTIME_ARG_PREFIX && + UINT64_MAX - fe->runtime_arg_base >= logical) { + physical = fe->runtime_arg_base + logical; + if (dma_memory_read(&address_space_memory, physical, buf, size, + MEMTXATTRS_UNSPECIFIED) == MEMTX_OK) { + if (k230_gnne_rdata_shadow_read(fe, fe->rdata_base + logical, + shadow_buf, size) && + memcmp(shadow_buf, buf, size)) { + trace_k230_kpu_rdata_shadow_diff( + pc, encoded, logical, + k230_gnne_scalar_raw(shadow_buf, size), + k230_gnne_scalar_raw(buf, size), size); + } + if (direct_physical && size == sizeof(uint32_t) && + k230_gnne_runtime_phys_candidate(fe, ldl_le_p(buf))) { + *direct_physical = true; + } + goto decode; + } + } + + physical = fe->rdata_base + logical; + if (k230_gnne_rdata_shadow_read(fe, physical, shadow_buf, size)) { + if (fe->runtime_window && logical < K230_GNNE_RUNTIME_ARG_PREFIX && + dma_memory_read(&address_space_memory, physical, live_buf, size, + MEMTXATTRS_UNSPECIFIED) == MEMTX_OK && + memcmp(shadow_buf, live_buf, size)) { + trace_k230_kpu_rdata_shadow_diff( + pc, encoded, logical, k230_gnne_scalar_raw(shadow_buf, size), + k230_gnne_scalar_raw(live_buf, size), size); + } + memcpy(buf, shadow_buf, size); + goto decode; + } + + if (dma_memory_read(&address_space_memory, physical, buf, size, + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + return false; + } + +decode: + switch (size) { + case 4: + *value = ldl_le_p(buf); + return true; + case 2: + if (sign) { + *value = (uint32_t)(int32_t)(int16_t)lduw_le_p(buf); + } else { + *value = lduw_le_p(buf); + } + return true; + case 1: + if (sign) { + *value = (uint32_t)(int32_t)(int8_t)buf[0]; + } else { + *value = buf[0]; + } + return true; + default: + return false; + } +} + +static bool k230_gnne_source_read(K230GnneFrontend *fe, uint64_t source, + void *buf, uint64_t size) +{ + uint64_t rdata_offset; + uint64_t physical; + + if (fe->runtime_window && fe->rdata_base_valid && + source >= fe->rdata_base) { + rdata_offset = source - fe->rdata_base; + if (rdata_offset < K230_GNNE_RUNTIME_ARG_PREFIX && + size <= K230_GNNE_RUNTIME_ARG_PREFIX - rdata_offset && + k230_gnne_rdata_shadow_read(fe, source, buf, size)) { + return true; + } + } + + if (k230_gnne_runtime_ddr_source_addr(fe, source, &physical) && + dma_memory_read(&address_space_memory, physical, buf, size, + MEMTXATTRS_UNSPECIFIED) == MEMTX_OK) { + return true; + } + + if (dma_memory_read(&address_space_memory, source, buf, size, + MEMTXATTRS_UNSPECIFIED) == MEMTX_OK) { + return true; + } + + if (!fe->rdata_base_valid || source < K230_GNNE_RDATA_ALIAS_BASE) { + return false; + } + + rdata_offset = source - K230_GNNE_RDATA_ALIAS_BASE; + if (UINT64_MAX - fe->rdata_base < rdata_offset) { + return false; + } + physical = fe->rdata_base + rdata_offset; + + if (k230_gnne_rdata_shadow_read(fe, physical, buf, size)) { + return true; + } + + return dma_memory_read(&address_space_memory, physical, buf, size, + MEMTXATTRS_UNSPECIFIED) == MEMTX_OK; +} + +static uint64_t k230_gnne_head_le_p(const void *buf, uint64_t size); +static void k230_gnne_hash_update(uint64_t *hash, const void *buf, + uint64_t size); + +static uint64_t k230_gnne_l2_source_read_physical(K230GnneFrontend *fe, + uint64_t source, + uint32_t *flags) +{ + uint64_t physical = source; + uint64_t rdata_offset; + + if (fe->runtime_window && fe->rdata_base_valid && + source >= fe->rdata_base) { + rdata_offset = source - fe->rdata_base; + if (rdata_offset < K230_GNNE_RUNTIME_ARG_PREFIX) { + *flags |= K230_GNNE_L2_SOURCE_F_RDATA_PREFIX; + } + } + + if (k230_gnne_runtime_ddr_source_addr(fe, source, &physical)) { + *flags |= K230_GNNE_L2_SOURCE_F_RUNTIME_DDR; + if (physical == source) { + *flags |= K230_GNNE_L2_SOURCE_F_RUNTIME_DDR_DIRECT; + } + } else if (fe->rdata_base_valid && source >= K230_GNNE_RDATA_ALIAS_BASE) { + rdata_offset = source - K230_GNNE_RDATA_ALIAS_BASE; + if (rdata_offset < K230_GNNE_RUNTIME_WINDOW_SIZE && + UINT64_MAX - fe->rdata_base >= rdata_offset) { + physical = fe->rdata_base + rdata_offset; + *flags |= K230_GNNE_L2_SOURCE_F_RDATA_ALIAS; + } + } + + return physical; +} + +static void k230_gnne_trace_l2_load_source_windows(K230KpuState *s, + K230GnneFrontend *fe, + uint64_t pc, + uint64_t source, + uint32_t base_flags) +{ + uint8_t buf[K230_GNNE_L2_LOAD_SOURCE_PROBE_SIZE]; + + for (unsigned int i = 0; + i < ARRAY_SIZE(k230_gnne_l2_load_source_probe_offsets); i++) { + uint64_t offset = k230_gnne_l2_load_source_probe_offsets[i]; + uint64_t probe_source; + uint64_t read_physical; + uint64_t hash = UINT64_C(0xcbf29ce484222325); + uint64_t head = 0; + uint32_t flags = base_flags; + uint32_t ok = 0; + + if (UINT64_MAX - source < offset) { + trace_k230_kpu_l2_load_source_window( + k230_kpu_name(s), pc, source, offset, UINT64_MAX, 0, 0, hash, + ok, flags); + continue; + } + + probe_source = source + offset; + read_physical = k230_gnne_l2_source_read_physical(fe, probe_source, + &flags); + if (k230_gnne_source_read(fe, probe_source, buf, sizeof(buf))) { + ok = 1; + head = k230_gnne_head_le_p(buf, sizeof(buf)); + k230_gnne_hash_update(&hash, buf, sizeof(buf)); + } + + trace_k230_kpu_l2_load_source_window( + k230_kpu_name(s), pc, source, offset, read_physical, sizeof(buf), + head, hash, ok, flags); + } +} + +static uint64_t k230_gnne_delta_or_invalid(uint64_t value, uint64_t base) +{ + if (value < base) { + return UINT64_MAX; + } + + return value - base; +} + +static void k230_gnne_trace_runtime_arg_windows(K230KpuState *s, + K230GnneFrontend *fe, + uint64_t pc, + uint64_t source, + uint32_t flags) +{ + uint8_t buf[K230_GNNE_RUNTIME_ARG_PROBE_SIZE]; + uint32_t addr_words; + + if (!fe->runtime_arg_base_valid) { + return; + } + + addr_words = MIN(fe->runtime_arg_addr_words, + (uint32_t)K230_GNNE_RUNTIME_ARG_WORDS); + for (uint32_t slot = 0; slot < addr_words; slot++) { + uint64_t arg_base = fe->runtime_arg_words[slot]; + uint64_t delta = k230_gnne_delta_or_invalid(source, arg_base); + + trace_k230_kpu_l2_load_source_arg_delta( + k230_kpu_name(s), pc, source, slot, arg_base, delta, flags); + + if (!trace_event_get_state(TRACE_K230_KPU_RUNTIME_ARG_WINDOW)) { + continue; + } + + for (unsigned int i = 0; + i < ARRAY_SIZE(k230_gnne_runtime_arg_probe_offsets); i++) { + uint64_t offset = k230_gnne_runtime_arg_probe_offsets[i]; + uint64_t read_physical; + uint64_t hash = UINT64_C(0xcbf29ce484222325); + uint64_t head = 0; + uint32_t ok = 0; + + if (UINT64_MAX - arg_base < offset) { + trace_k230_kpu_runtime_arg_window( + k230_kpu_name(s), pc, slot, arg_base, offset, UINT64_MAX, + head, hash, ok); + continue; + } + + read_physical = arg_base + offset; + if (dma_memory_read(&address_space_memory, read_physical, buf, + sizeof(buf), + MEMTXATTRS_UNSPECIFIED) == MEMTX_OK) { + ok = 1; + head = k230_gnne_head_le_p(buf, sizeof(buf)); + k230_gnne_hash_update(&hash, buf, sizeof(buf)); + } + + trace_k230_kpu_runtime_arg_window( + k230_kpu_name(s), pc, slot, arg_base, offset, read_physical, + head, hash, ok); + } + } +} + +static void k230_gnne_trace_l2_load_source(K230KpuState *s, + K230GnneFrontend *fe, + uint64_t pc, uint32_t word, + uint32_t raddr_s, + uint32_t raddr_d, + uint32_t src_addr, + uint32_t dst_encoded) +{ + uint64_t source = src_addr; + uint64_t read_physical = source; + uint64_t arg_offset = UINT64_MAX; + uint32_t arg_slot = UINT32_MAX; + uint32_t arg_base = 0; + uint32_t flags = 0; + + if (fe->runtime_window) { + flags |= K230_GNNE_L2_SOURCE_F_RUNTIME_WINDOW; + } + if (fe->runtime_arg_base_valid) { + flags |= K230_GNNE_L2_SOURCE_F_ARG_TABLE; + } + if (k230_gnne_gp_direct_physical(fe, raddr_s)) { + flags |= K230_GNNE_L2_SOURCE_F_GP_DIRECT; + } + if (k230_gnne_gp_runtime_arg(fe, raddr_s, &arg_slot, &arg_base, + &arg_offset)) { + flags |= K230_GNNE_L2_SOURCE_F_ARG_PROVENANCE; + } + read_physical = k230_gnne_l2_source_read_physical(fe, source, &flags); + + trace_k230_kpu_l2_load_source( + k230_kpu_name(s), pc, word, raddr_s | (raddr_d << 8), src_addr, + dst_encoded, flags); + trace_k230_kpu_l2_load_source_arg( + k230_kpu_name(s), pc, arg_slot, arg_base, arg_offset, read_physical, + flags); + + if (trace_event_get_state(TRACE_K230_KPU_L2_LOAD_SOURCE_ARG_DELTA) || + trace_event_get_state(TRACE_K230_KPU_RUNTIME_ARG_WINDOW)) { + k230_gnne_trace_runtime_arg_windows(s, fe, pc, source, flags); + } + + if (trace_event_get_state(TRACE_K230_KPU_L2_LOAD_SOURCE_WINDOW)) { + k230_gnne_trace_l2_load_source_windows(s, fe, pc, source, flags); + } +} + +static bool k230_gnne_is_short(uint32_t opcode) +{ + switch (opcode) { + case 0x01: + case 0x03: + case 0x05: + case 0x07: + case 0x41: + case 0x43: + case 0x45: + case 0x49: + case 0x4b: + case 0x4d: + case 0x4f: + case 0x51: + return true; + default: + return false; + } +} + +static bool k230_gnne_opcode_known(uint32_t word) +{ + uint32_t opcode = word & 0x7f; + uint32_t funct3_17 = extract32(word, 17, 3); + uint32_t funct4_13 = extract32(word, 13, 4); + uint32_t funct5_7 = extract32(word, 7, 5); + uint32_t funct5_17 = extract32(word, 17, 5); + + switch (opcode) { + case 0x06: + return funct3_17 <= 4; + case 0x08: + return funct3_17 <= 2; + case 0x0c: + return funct5_17 <= 6; + case 0x0e: + return funct3_17 == 0; + case 0x10: + return funct3_17 <= 5; + case 0x50: + return funct4_13 <= 5; + case 0x5a: + return funct4_13 <= 8; + case 0x5e: + return funct4_13 <= 7; + case 0x62: + return funct5_7 <= 0x0e; + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x07: + case 0x12: + case 0x14: + case 0x16: + case 0x18: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x48: + case 0x49: + case 0x4a: + case 0x4b: + case 0x4c: + case 0x4d: + case 0x4e: + case 0x4f: + case 0x51: + case 0x52: + case 0x54: + case 0x56: + case 0x57: + case 0x58: + case 0x5c: + case 0x60: + case 0x64: + case 0x66: + case 0x68: + case 0x6a: + case 0x72: + case 0x74: + return true; + default: + return false; + } +} + +static bool k230_gnne_direct_type_size(uint32_t datatype, unsigned int *size) +{ + switch (datatype) { + case 0: + *size = 1; + return true; + case 1: + case 5: + *size = 2; + return true; + case 2: + *size = 4; + return true; + default: + return false; + } +} + +static unsigned int k230_gnne_l2_type_size(uint32_t datatype) +{ + return datatype == 0 ? 1 : 2; +} + +static bool k230_gnne_l2_datatype_size(uint32_t datatype, unsigned int *size) +{ + switch (datatype) { + case 0: + *size = 1; + return true; + case 1: + case 2: + *size = 2; + return true; + default: + return false; + } +} + +typedef float32 K230GnneFloat; + +static float_status k230_gnne_float_status(FloatRoundMode rounding) +{ + float_status status = {}; + + set_float_rounding_mode(rounding, &status); + set_float_default_nan_pattern(0b01000000, &status); + set_default_nan_mode(true, &status); + set_float_detect_tininess(float_tininess_after_rounding, &status); + return status; +} + +static int k230_gnne_float_scalbn_shift(int64_t shift) +{ + if (shift > 63) { + return 63; + } + if (shift < -63) { + return -63; + } + return (int)shift; +} + +static K230GnneFloat k230_gnne_float_from_i64(int64_t value) +{ + float_status status = + k230_gnne_float_status(float_round_nearest_even); + + return int64_to_float32(value, &status); +} + +static K230GnneFloat k230_gnne_float_from_u64(uint64_t value) +{ + float_status status = + k230_gnne_float_status(float_round_nearest_even); + + return uint64_to_float32(value, &status); +} + +static K230GnneFloat k230_gnne_float_from_u32(uint32_t value) +{ + float_status status = + k230_gnne_float_status(float_round_nearest_even); + + return uint32_to_float32(value, &status); +} + +static K230GnneFloat k230_gnne_float_add(K230GnneFloat a, + K230GnneFloat b) +{ + float_status status = + k230_gnne_float_status(float_round_nearest_even); + + return float32_add(a, b, &status); +} + +static K230GnneFloat k230_gnne_float_mul(K230GnneFloat a, + K230GnneFloat b) +{ + float_status status = + k230_gnne_float_status(float_round_nearest_even); + + return float32_mul(a, b, &status); +} + +static K230GnneFloat k230_gnne_float_div(K230GnneFloat a, + K230GnneFloat b) +{ + float_status status = + k230_gnne_float_status(float_round_nearest_even); + + return float32_div(a, b, &status); +} + +static K230GnneFloat k230_gnne_float_scalbn(K230GnneFloat value, + int64_t shift) +{ + float_status status = + k230_gnne_float_status(float_round_nearest_even); + + return float32_scalbn(value, k230_gnne_float_scalbn_shift(shift), + &status); +} + +static bool k230_gnne_float_lt(K230GnneFloat a, K230GnneFloat b) +{ + float_status status = + k230_gnne_float_status(float_round_nearest_even); + + return float32_compare_quiet(a, b, &status) == float_relation_less; +} + +static bool k230_gnne_float_gt(K230GnneFloat a, K230GnneFloat b) +{ + float_status status = + k230_gnne_float_status(float_round_nearest_even); + + return float32_compare_quiet(a, b, &status) == float_relation_greater; +} + +static int64_t k230_gnne_float_round_ties_away(K230GnneFloat value) +{ + float_status status = k230_gnne_float_status(float_round_ties_away); + + return float32_to_int64_scalbn(value, float_round_ties_away, 0, + &status); +} + +static int64_t k230_gnne_clamp_i64(int64_t value, int64_t low, int64_t high) +{ + if (value < low) { + return low; + } + if (value > high) { + return high; + } + + return value; +} + +static uint16_t k230_gnne_fp16_finite_raw(uint16_t raw) +{ + if ((raw & 0x7c00) == 0x7c00) { + return (raw & 0x8000) | 0x7bff; + } + + return raw; +} + +static K230GnneFloat k230_gnne_fp16_to_float(uint16_t raw) +{ + float_status status = + k230_gnne_float_status(float_round_nearest_even); + + return float16_to_float32( + make_float16(k230_gnne_fp16_finite_raw(raw)), true, &status); +} + +static uint16_t k230_gnne_float_to_fp16(K230GnneFloat value) +{ + float_status status = k230_gnne_float_status(float_round_ties_away); + uint16_t raw = float16_val(float32_to_float16(value, true, &status)); + + if ((raw & 0x7c00) == 0x7c00) { + return (raw & 0x8000) | 0x7bff; + } + + return raw; +} + +static K230GnneFloat k230_gnne_float_round_fp16(K230GnneFloat value) +{ + return k230_gnne_fp16_to_float(k230_gnne_float_to_fp16(value)); +} + +static K230GnneFloat k230_gnne_float_dequant_i64(int64_t value, + K230GnneFloat scale, + uint32_t shift) +{ + K230GnneFloat result = k230_gnne_float_from_i64(value); + + result = k230_gnne_float_mul(result, scale); + return k230_gnne_float_scalbn(result, -(int64_t)shift); +} + +static K230GnneFloat k230_gnne_float_clamp(K230GnneFloat value, + K230GnneFloat low, + K230GnneFloat high) +{ + if (k230_gnne_float_lt(value, low)) { + return low; + } + if (k230_gnne_float_gt(value, high)) { + return high; + } + + return value; +} + +static K230GnneFloat k230_gnne_float_line(K230GnneFloat value, + K230GnneFloat slope, + int64_t shift, + K230GnneFloat bias) +{ + value = k230_gnne_float_mul(value, slope); + value = k230_gnne_float_scalbn(value, shift); + return k230_gnne_float_add(value, bias); +} + +static int32_t k230_gnne_round_average_i32(int32_t value, uint64_t count) +{ + K230GnneFloat numerator = k230_gnne_float_from_i64(value); + K230GnneFloat denominator = k230_gnne_float_from_u64(count); + K230GnneFloat quotient = k230_gnne_float_div(numerator, denominator); + int64_t rounded = k230_gnne_float_round_ties_away(quotient); + + return (int32_t)k230_gnne_clamp_i64(rounded, INT32_MIN, INT32_MAX); +} + +static bool k230_gnne_dma_read_bytes(uint64_t addr, void *buf, + unsigned int size) +{ + if (k230_gnne_bank_read(k230_gnne_active_fe, addr, buf, size)) { + return true; + } + if (k230_gnne_cache_read(k230_gnne_active_fe, addr, buf, size)) { + return true; + } + + return dma_memory_read(&address_space_memory, addr, buf, size, + MEMTXATTRS_UNSPECIFIED) == MEMTX_OK; +} + +static uint64_t k230_gnne_head_le_p(const void *buf, uint64_t size); + +static void k230_gnne_trace_dma_write(uint64_t addr, const void *buf, + unsigned int size, uint32_t flags) +{ + K230GnneFrontend *fe = k230_gnne_active_fe; + + if (!trace_event_get_state(TRACE_K230_KPU_RAW_WRITE_SAMPLE) || + !k230_gnne_trace_window_overlaps( + k230_gnne_raw_write_sample_windows, + ARRAY_SIZE(k230_gnne_raw_write_sample_windows), addr, size)) { + return; + } + + trace_k230_kpu_raw_write_sample("k230-kpu", + fe ? fe->current_pc : UINT64_MAX, + addr, size, + k230_gnne_head_le_p(buf, size), flags); +} + +static bool k230_gnne_dma_write_bytes(uint64_t addr, const void *buf, + unsigned int size) +{ + if (k230_gnne_bank_write(k230_gnne_active_fe, addr, buf, size)) { + k230_gnne_trace_dma_write(addr, buf, size, 0); + return true; + } + if (k230_gnne_cache_write(k230_gnne_active_fe, addr, buf, size)) { + k230_gnne_trace_dma_write(addr, buf, size, 1); + return true; + } + + if (dma_memory_write(&address_space_memory, addr, buf, size, + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + return false; + } + + k230_gnne_trace_dma_write(addr, buf, size, 2); + return true; +} + +static uint64_t k230_gnne_head_le_p(const void *buf, uint64_t size) +{ + uint8_t head[8] = {}; + + memcpy(head, buf, MIN(size, (uint64_t)sizeof(head))); + return ldq_le_p(head); +} + +static void k230_gnne_hash_update(uint64_t *hash, const void *buf, + uint64_t size) +{ + const uint8_t *bytes = buf; + + for (uint64_t i = 0; i < size; i++) { + *hash ^= bytes[i]; + *hash *= UINT64_C(0x100000001b3); + } +} + +static bool k230_gnne_read_fp16(uint64_t addr, K230GnneFloat *value) +{ + uint8_t buf[2]; + + if (!k230_gnne_dma_read_bytes(addr, buf, sizeof(buf))) { + return false; + } + + *value = k230_gnne_fp16_to_float(lduw_le_p(buf)); + return true; +} + +static bool k230_gnne_write_fp16(uint64_t addr, K230GnneFloat value) +{ + uint8_t buf[2]; + + stw_le_p(buf, k230_gnne_float_to_fp16(value)); + return k230_gnne_dma_write_bytes(addr, buf, sizeof(buf)); +} + +static K230GnneFloat k230_gnne_float32_from_raw(uint32_t raw) +{ + return make_float32(raw); +} + +static uint32_t k230_gnne_float32_to_raw(K230GnneFloat value) +{ + return float32_val(value); +} + +static void k230_gnne_l2_load_item(uint8_t *dst, const uint8_t *src, + K230GnneL2Conf *conf, + unsigned int src_size, + unsigned int dst_size) +{ + if (conf->ddr_datatype == 2 && conf->l2_datatype == 1) { + stw_le_p(dst, k230_gnne_float_to_fp16( + k230_gnne_float32_from_raw(ldl_le_p(src)))); + return; + } + + memcpy(dst, src, MIN(src_size, dst_size)); +} + +static void k230_gnne_l2_store_item(uint8_t *dst, const uint8_t *src, + K230GnneL2Conf *conf, + unsigned int src_size, + unsigned int dst_size) +{ + if (conf->l2_datatype == 1 && conf->ddr_datatype == 2) { + stl_le_p(dst, k230_gnne_float32_to_raw( + k230_gnne_fp16_to_float(lduw_le_p(src)))); + return; + } + + memcpy(dst, src, MIN(src_size, dst_size)); +} + +static bool k230_gnne_quant_type_size(uint32_t quant_type, unsigned int *size) +{ + switch (quant_type) { + case 0: + case 3: + *size = 2; + return true; + case 1: + case 2: + *size = 1; + return true; + default: + return false; + } +} + +static bool k230_gnne_act0_type_size(uint32_t datatype, unsigned int *size) +{ + switch (datatype) { + case 0: + case 1: + *size = 1; + return true; + case 2: + case 3: + *size = 2; + return true; + default: + return false; + } +} + +static bool k230_gnne_shape_count(const K230GnneShape *shape, uint64_t *count) +{ + if (!shape->valid) { + return false; + } + + if (!shape->n || !shape->c || !shape->h || !shape->w) { + *count = 0; + return true; + } + + if (umul64_overflow((uint64_t)shape->n, shape->c, count) || + umul64_overflow(*count, shape->h, count) || + umul64_overflow(*count, shape->w, count)) { + return false; + } + + return true; +} + +static bool k230_gnne_shape_product(K230GnneFrontend *fe, unsigned int index, + uint64_t *count) +{ + if (index >= K230_GNNE_SHAPE_COUNT) { + return false; + } + + return k230_gnne_shape_count(&fe->shape[index], count); +} + +static bool k230_gnne_packed_offset4(K230GnneStride *stride, uint32_t n, + uint32_t c, uint32_t h, uint32_t w, + uint64_t *offset); + +static bool k230_gnne_stride_value(K230GnneFrontend *fe, unsigned int index, + K230GnneStride *stride) +{ + if (index >= K230_GNNE_SHAPE_COUNT || !fe->stride[index].valid) { + return false; + } + + *stride = fe->stride[index]; + return true; +} + +static bool k230_gnne_l2_conf_sizes(K230GnneL2Conf *conf, + unsigned int *src_size, + unsigned int *dst_size) +{ + if (!conf->valid || + !k230_gnne_direct_type_size(conf->ddr_datatype, src_size)) { + return false; + } + + *dst_size = k230_gnne_l2_type_size(conf->l2_datatype); + return true; +} + +static void k230_gnne_l2_load(K230KpuState *s, K230GnneFrontend *fe, + uint32_t word, uint64_t pc) +{ + K230GnneL2Conf *conf = &fe->l2_load_conf; + K230GnneShape *shape; + K230GnneStride src_stride; + K230GnneStride dst_stride; + unsigned int raddr_d = extract32(word, 7, 5); + unsigned int raddr_s = extract32(word, 12, 5); + unsigned int rshape = extract32(word, 17, 3); + unsigned int src_size; + unsigned int dst_size; + uint32_t src_addr; + uint32_t dst_encoded; + uint64_t dst_base; + uint64_t dst_logical; + uint64_t total_count; + uint64_t copied = 0; + uint8_t source_head_buf[8] = {}; + uint8_t dest_head_buf[8] = {}; + uint64_t source_head_size = 0; + uint64_t dest_head_size = 0; + uint64_t source_hash = UINT64_C(0xcbf29ce484222325); + uint64_t dest_hash = UINT64_C(0xcbf29ce484222325); + bool trace_hash = trace_event_get_state(TRACE_K230_KPU_L2_LOAD_HASH); + bool trace_sample = trace_event_get_state(TRACE_K230_KPU_L2_LOAD_SAMPLE); + uint32_t sample_count = 0; + bool valid; + + src_addr = k230_gnne_gp(fe, raddr_s, &valid); + if (!valid) { + return; + } + dst_encoded = k230_gnne_gp(fe, raddr_d, &valid); + if (!valid || rshape >= K230_GNNE_SHAPE_COUNT || + !fe->shape[rshape].valid || + !k230_gnne_translate(fe, dst_encoded, &dst_base, &dst_logical) || + !k230_gnne_l2_conf_sizes(conf, &src_size, &dst_size) || + !conf->stride_s_valid || + !conf->stride_d_valid || + !k230_gnne_shape_product(fe, rshape, &total_count)) { + return; + } + + src_stride = conf->stride_s; + dst_stride = conf->stride_d; + + if (total_count > K230_GNNE_MAX_OUTPUT_SIZE / dst_size) { + return; + } + + if (trace_event_get_state(TRACE_K230_KPU_L2_LOAD_SOURCE) || + trace_event_get_state(TRACE_K230_KPU_L2_LOAD_SOURCE_WINDOW)) { + k230_gnne_trace_l2_load_source(s, fe, pc, word, raddr_s, raddr_d, + src_addr, dst_encoded); + } + + shape = &fe->shape[rshape]; + for (uint32_t n = 0; n < shape->n; n++) { + for (uint32_t c = 0; c < shape->c; c++) { + for (uint32_t h = 0; h < shape->h; h++) { + for (uint32_t w = 0; w < shape->w; w++) { + uint64_t src_index; + uint64_t dst_index; + uint64_t src_off; + uint64_t dst_off; + uint8_t raw[4] = {}; + uint8_t item[4] = {}; + + if (!k230_gnne_packed_offset4(&src_stride, n, c, h, w, + &src_index) || + !k230_gnne_packed_offset4(&dst_stride, n, c, h, w, + &dst_index) || + umul64_overflow(src_index, src_size, &src_off) || + umul64_overflow(dst_index, dst_size, &dst_off) || + UINT64_MAX - src_addr < src_off || + UINT64_MAX - dst_logical < dst_off || + UINT64_MAX - dst_base < dst_off) { + return; + } + if (!k230_gnne_source_read(fe, src_addr + src_off, raw, + src_size)) { + return; + } + if (source_head_size < sizeof(source_head_buf)) { + unsigned int chunk = + MIN(src_size, + (unsigned int)(sizeof(source_head_buf) - + source_head_size)); + + memcpy(source_head_buf + source_head_size, raw, chunk); + source_head_size += chunk; + } + if (trace_hash) { + k230_gnne_hash_update(&source_hash, raw, src_size); + } + k230_gnne_l2_load_item(item, raw, conf, src_size, dst_size); + if (trace_hash && + dest_head_size < sizeof(dest_head_buf)) { + unsigned int chunk = + MIN(dst_size, + (unsigned int)(sizeof(dest_head_buf) - + dest_head_size)); + + memcpy(dest_head_buf + dest_head_size, item, chunk); + dest_head_size += chunk; + } + if (trace_hash) { + k230_gnne_hash_update(&dest_hash, item, dst_size); + } + if (trace_sample && + (sample_count < K230_GNNE_L2_LOAD_SAMPLE_HEAD || + k230_gnne_trace_window_contains( + k230_gnne_l2_load_sample_windows, + ARRAY_SIZE(k230_gnne_l2_load_sample_windows), + dst_off))) { + trace_k230_kpu_l2_load_sample( + k230_kpu_name(s), pc, sample_count, + src_addr + src_off, dst_logical + dst_off, + dst_base + dst_off, src_index, dst_index, + k230_gnne_head_le_p(raw, src_size), + k230_gnne_head_le_p(item, dst_size)); + sample_count++; + } + if (!k230_gnne_dma_write_bytes(dst_base + dst_off, item, + dst_size)) { + return; + } + copied += dst_size; + } + } + } + } + + trace_k230_kpu_l2_load_detail(k230_kpu_name(s), pc, src_addr, + dst_logical, rshape, src_size, dst_size); + if (trace_hash) { + trace_k230_kpu_l2_load_hash( + k230_kpu_name(s), pc, + k230_gnne_head_le_p(source_head_buf, source_head_size), + k230_gnne_head_le_p(dest_head_buf, dest_head_size), source_hash, + dest_hash); + } + trace_k230_kpu_l2_load(k230_kpu_name(s), src_addr, dst_logical, copied, + k230_gnne_head_le_p(source_head_buf, + source_head_size)); + fe->l2_loads++; + fe->input_bytes += copied; +} + +static void k230_gnne_l2_load_w(K230KpuState *s, K230GnneFrontend *fe, + uint32_t word, uint64_t pc) +{ + K230GnneL2Conf *conf = &fe->l2_load_w_conf; + unsigned int raddr_d = extract32(word, 7, 5); + unsigned int raddr_s = extract32(word, 12, 5); + unsigned int rvalid_c_num = extract32(word, 17, 5); + unsigned int src_size; + unsigned int dst_size; + uint32_t src_addr; + uint32_t dst_encoded; + uint32_t rlen; + uint32_t valid_c; + uint64_t src_base_addr = 0; + uint64_t dst_base; + uint64_t dst_logical = 0; + uint64_t copied = 0; + uint8_t head_buf[8] = {}; + uint64_t head_size = 0; + uint32_t sample_count = 0; + bool trace_sample = + trace_event_get_state(TRACE_K230_KPU_L2_LOAD_W_SAMPLE); + bool valid; + + if (conf->enable_decompress || + !k230_gnne_l2_conf_sizes(conf, &src_size, &dst_size)) { + trace_k230_kpu_l2_load_w_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_CONF, 0, 0, 0, 0); + return; + } + + src_addr = k230_gnne_gp(fe, raddr_s, &valid); + if (!valid) { + trace_k230_kpu_l2_load_w_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_SRC_GP, 0, 0, 0, 0); + return; + } + src_base_addr = src_addr; + dst_encoded = k230_gnne_gp(fe, raddr_d, &valid); + if (!valid || + !k230_gnne_translate(fe, dst_encoded, &dst_base, &dst_logical)) { + trace_k230_kpu_l2_load_w_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_DST_TRANSLATE, + src_base_addr, 0, 0, 0); + return; + } + rlen = conf->rlen_decompressed; + if (rlen > K230_GNNE_MAX_OUTPUT_SIZE / dst_size) { + trace_k230_kpu_l2_load_w_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_COUNT, src_base_addr, + dst_logical, rlen, 0); + return; + } + src_base_addr = k230_gnne_l2_load_w_source(fe, src_addr, dst_logical, + rlen, src_size); + valid_c = k230_gnne_gp(fe, rvalid_c_num, &valid); + if (!valid || valid_c == UINT32_MAX) { + trace_k230_kpu_l2_load_w_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_COUNT, src_base_addr, + dst_logical, rlen, 0); + return; + } + valid_c++; + + for (uint32_t index = 0; index < rlen; index++) { + uint64_t src_off = (uint64_t)index * src_size; + uint64_t row = index / valid_c; + uint64_t col = index % valid_c; + uint64_t dst_index = row * K230_GNNE_L2_LANE_WIDTH + col; + uint64_t dst_off; + uint8_t item[4] = {}; + uint8_t raw_item[4] = {}; + bool trace_this = sample_count < K230_GNNE_L2_LOAD_W_SAMPLE_HEAD; + + if (UINT64_MAX - src_base_addr < src_off || + umul64_overflow(dst_index, dst_size, &dst_off) || + UINT64_MAX - dst_base < dst_off) { + trace_k230_kpu_l2_load_w_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_OVERFLOW, + src_base_addr, dst_logical, rlen, + valid_c); + return; + } + + if (!k230_gnne_l2_load_w_synth_arg(fe, conf, src_addr, rlen, + valid_c, index, item, + src_size) && + !k230_gnne_source_read(fe, src_base_addr + src_off, item, + src_size)) { + trace_k230_kpu_l2_load_w_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_SOURCE_READ, + src_base_addr + src_off, + dst_logical, rlen, valid_c); + return; + } + memcpy(raw_item, item, sizeof(raw_item)); + if (head_size < sizeof(head_buf)) { + unsigned int chunk = MIN(src_size, + (unsigned int)(sizeof(head_buf) - + head_size)); + + memcpy(head_buf + head_size, item, chunk); + head_size += chunk; + } + k230_gnne_l2_load_item(item, item, conf, src_size, dst_size); + if (!trace_this && UINT64_MAX - dst_logical >= dst_off) { + trace_this = k230_gnne_trace_window_contains( + k230_gnne_l2_load_w_sample_windows, + ARRAY_SIZE(k230_gnne_l2_load_w_sample_windows), + dst_logical + dst_off); + } + if (trace_sample && trace_this) { + trace_k230_kpu_l2_load_w_sample( + k230_kpu_name(s), pc, sample_count, + src_base_addr + src_off, dst_logical, dst_base + dst_off, + index, dst_index, ldl_le_p(raw_item), ldl_le_p(item)); + sample_count++; + } + if (!k230_gnne_dma_write_bytes(dst_base + dst_off, item, + dst_size)) { + trace_k230_kpu_l2_load_w_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_DEST_WRITE, + src_base_addr, dst_logical, rlen, + valid_c); + return; + } + copied += dst_size; + } + + trace_k230_kpu_l2_load_w(k230_kpu_name(s), src_base_addr, dst_logical, + rlen, valid_c, + k230_gnne_head_le_p(head_buf, head_size)); + fe->l2_load_ws++; + fe->input_bytes += copied; +} + +static bool k230_gnne_store_conf_sizes(K230GnneL2Conf *conf, + unsigned int *src_size, + unsigned int *dst_size) +{ + if (!conf->valid || + !k230_gnne_direct_type_size(conf->ddr_datatype, dst_size)) { + return false; + } + + *src_size = k230_gnne_l2_type_size(conf->l2_datatype); + return true; +} + +static void k230_gnne_l2_store(K230KpuState *s, K230GnneFrontend *fe, + uint32_t word, uint64_t pc) +{ + K230GnneL2Conf *conf = &fe->l2_store_conf; + K230GnneShape *shape; + K230GnneStride src_stride; + K230GnneStride dst_stride; + unsigned int raddr_d = extract32(word, 7, 5); + unsigned int raddr_s = extract32(word, 12, 5); + unsigned int rshape = extract32(word, 17, 3); + unsigned int src_size; + unsigned int dst_size; + uint32_t src_encoded; + uint32_t dst_encoded; + uint64_t src_base; + uint64_t dst_base; + uint64_t src_logical = 0; + uint64_t dst_logical = 0; + uint64_t total_count; + uint64_t copied = 0; + uint8_t source_head_buf[8] = {}; + uint8_t dest_head_buf[8] = {}; + uint64_t source_head_size = 0; + uint64_t dest_head_size = 0; + uint64_t source_hash = UINT64_C(0xcbf29ce484222325); + uint64_t dest_hash = UINT64_C(0xcbf29ce484222325); + bool trace_hash = trace_event_get_state(TRACE_K230_KPU_L2_STORE_HASH); + bool trace_sample = trace_event_get_state(TRACE_K230_KPU_L2_STORE_SAMPLE); + uint32_t sample_count = 0; + bool valid; + bool dst_direct_physical; + + src_encoded = k230_gnne_gp(fe, raddr_s, &valid); + if (!valid) { + trace_k230_kpu_l2_store_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_SRC_GP, 0, 0, rshape, + copied); + return; + } + dst_encoded = k230_gnne_gp(fe, raddr_d, &valid); + dst_direct_physical = k230_gnne_gp_direct_physical(fe, raddr_d); + if (!valid) { + trace_k230_kpu_l2_store_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_DST_GP, src_encoded, 0, + rshape, copied); + return; + } + if (rshape >= K230_GNNE_SHAPE_COUNT || !fe->shape[rshape].valid) { + trace_k230_kpu_l2_store_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_SHAPE, src_encoded, 0, + rshape, copied); + return; + } + if (!k230_gnne_translate(fe, src_encoded, &src_base, &src_logical)) { + trace_k230_kpu_l2_store_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_SRC_TRANSLATE, + src_encoded, 0, rshape, copied); + return; + } + if (dst_direct_physical) { + valid = k230_gnne_translate_direct_physical(fe, dst_encoded, + &dst_base, &dst_logical); + } else { + valid = k230_gnne_translate_store_dest(fe, dst_encoded, &dst_base, + &dst_logical); + } + if (!valid) { + trace_k230_kpu_l2_store_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_DST_TRANSLATE, + src_logical, dst_encoded, rshape, + copied); + return; + } + if (!k230_gnne_store_conf_sizes(conf, &src_size, &dst_size)) { + trace_k230_kpu_l2_store_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_CONF, src_logical, + dst_logical, rshape, copied); + return; + } + if (!conf->stride_s_valid || !conf->stride_d_valid) { + trace_k230_kpu_l2_store_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_STRIDE, src_logical, + dst_logical, rshape, copied); + return; + } + if (!k230_gnne_shape_product(fe, rshape, &total_count)) { + trace_k230_kpu_l2_store_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_COUNT, src_logical, + dst_logical, rshape, copied); + return; + } + + src_stride = conf->stride_s; + dst_stride = conf->stride_d; + + if (total_count > K230_GNNE_MAX_OUTPUT_SIZE / dst_size) { + trace_k230_kpu_l2_store_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_COUNT, src_logical, + dst_logical, rshape, copied); + return; + } + + shape = &fe->shape[rshape]; + for (uint32_t n = 0; n < shape->n; n++) { + for (uint32_t c = 0; c < shape->c; c++) { + for (uint32_t h = 0; h < shape->h; h++) { + for (uint32_t w = 0; w < shape->w; w++) { + uint64_t src_index; + uint64_t dst_index; + uint64_t src_byte; + uint64_t dst_byte; + uint64_t dst_addr; + uint64_t src_off; + uint64_t dst_off; + uint64_t mirror_base; + uint64_t mirror_off; + uint8_t item[4] = {}; + uint8_t out[4] = {}; + + if (!k230_gnne_packed_offset4(&src_stride, n, c, h, w, + &src_index) || + !k230_gnne_packed_offset4(&dst_stride, n, c, h, w, + &dst_index) || + umul64_overflow(src_index, src_size, &src_byte) || + umul64_overflow(dst_index, dst_size, &dst_byte) || + UINT64_MAX - src_base < src_byte || + UINT64_MAX - dst_logical < dst_byte || + UINT64_MAX - dst_base < dst_byte) { + trace_k230_kpu_l2_store_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_OVERFLOW, + src_logical, dst_logical, + rshape, copied); + return; + } + src_off = src_base + src_byte; + dst_off = dst_base + dst_byte; + dst_addr = dst_logical + dst_byte; + if (!k230_gnne_dma_read_bytes(src_off, item, src_size)) { + trace_k230_kpu_l2_store_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_DEST_WRITE, + src_off, dst_off, + rshape, copied); + return; + } + if (trace_hash && + source_head_size < sizeof(source_head_buf)) { + unsigned int chunk = + MIN(src_size, + (unsigned int)(sizeof(source_head_buf) - + source_head_size)); + + memcpy(source_head_buf + source_head_size, item, + chunk); + source_head_size += chunk; + } + if (trace_hash) { + k230_gnne_hash_update(&source_hash, item, src_size); + } + k230_gnne_l2_store_item(out, item, conf, src_size, + dst_size); + if (trace_hash && + dest_head_size < sizeof(dest_head_buf)) { + unsigned int chunk = + MIN(dst_size, + (unsigned int)(sizeof(dest_head_buf) - + dest_head_size)); + + memcpy(dest_head_buf + dest_head_size, out, chunk); + dest_head_size += chunk; + } + if (trace_hash) { + k230_gnne_hash_update(&dest_hash, out, dst_size); + } + if (trace_sample && + (sample_count < K230_GNNE_L2_STORE_SAMPLE_HEAD || + k230_gnne_trace_window_contains( + k230_gnne_l2_store_sample_windows, + ARRAY_SIZE(k230_gnne_l2_store_sample_windows), + dst_addr) || + (UINT64_MAX - src_logical >= src_byte && + k230_gnne_trace_window_contains( + k230_gnne_l2_store_sample_windows, + ARRAY_SIZE(k230_gnne_l2_store_sample_windows), + src_logical + src_byte)))) { + trace_k230_kpu_l2_store_sample( + k230_kpu_name(s), pc, sample_count, src_off, + dst_addr, src_index, dst_index, + k230_gnne_head_le_p(item, src_size), + k230_gnne_head_le_p(out, dst_size), + src_size | (dst_size << 8)); + sample_count++; + } + if (!k230_gnne_dma_write_bytes(dst_off, out, dst_size)) { + trace_k230_kpu_l2_store_skip(k230_kpu_name(s), pc, + K230_GNNE_SKIP_DEST_WRITE, + src_off, dst_off, + rshape, copied); + return; + } + if (k230_gnne_runtime_ddr_addr(fe, dst_logical, + &mirror_base)) { + if (UINT64_MAX - mirror_base < dst_byte) { + trace_k230_kpu_l2_store_skip( + k230_kpu_name(s), pc, + K230_GNNE_SKIP_OVERFLOW, src_off, + mirror_base, rshape, copied); + return; + } + mirror_off = mirror_base + dst_byte; + if (!k230_gnne_dma_write_bytes(mirror_off, out, + dst_size)) { + trace_k230_kpu_l2_store_skip( + k230_kpu_name(s), pc, + K230_GNNE_SKIP_DEST_WRITE, src_off, + mirror_off, rshape, copied); + return; + } + } + copied += dst_size; + } + } + } + } + + trace_k230_kpu_l2_store_detail(k230_kpu_name(s), pc, src_logical, + dst_logical, rshape, src_size, dst_size); + if (trace_hash) { + trace_k230_kpu_l2_store_hash( + k230_kpu_name(s), pc, + k230_gnne_head_le_p(source_head_buf, source_head_size), + k230_gnne_head_le_p(dest_head_buf, dest_head_size), source_hash, + dest_hash); + } + trace_k230_kpu_l2_store(k230_kpu_name(s), dst_logical, dst_base, copied); + fe->l2_stores++; + fe->output_bytes += copied; +} + +static bool k230_gnne_mfu_dequant(uint64_t src, uint64_t index, + uint32_t quant_type, K230GnneFloat scale, + uint32_t bias, uint32_t shift, + K230GnneFloat *value) +{ + uint64_t offset; + uint8_t byte; + uint8_t word[2]; + + switch (quant_type) { + case 0: + if (umul64_overflow(index, 2, &offset) || + UINT64_MAX - src < offset) { + return false; + } + return k230_gnne_read_fp16(src + offset, value); + case 1: + if (UINT64_MAX - src < index || + !k230_gnne_dma_read_bytes(src + index, &byte, sizeof(byte))) { + return false; + } + *value = k230_gnne_float_dequant_i64((int64_t)byte - bias, scale, + shift); + return true; + case 2: + if (UINT64_MAX - src < index || + !k230_gnne_dma_read_bytes(src + index, &byte, sizeof(byte))) { + return false; + } + *value = k230_gnne_float_dequant_i64((int8_t)byte, scale, shift); + return true; + case 3: + if (umul64_overflow(index, 2, &offset) || + UINT64_MAX - src < offset || + !k230_gnne_dma_read_bytes(src + offset, word, sizeof(word))) { + return false; + } + *value = k230_gnne_float_dequant_i64((int16_t)lduw_le_p(word), + scale, shift); + return true; + default: + return false; + } +} + +static bool k230_gnne_mfu_dequant_literal(uint32_t raw, uint32_t quant_type, + K230GnneFloat scale, uint32_t bias, + uint32_t shift, + K230GnneFloat *value) +{ + switch (quant_type) { + case 0: + *value = k230_gnne_fp16_to_float(raw); + return true; + case 1: + *value = k230_gnne_float_dequant_i64((int64_t)(uint8_t)raw - bias, + scale, shift); + return true; + case 2: + *value = k230_gnne_float_dequant_i64((int8_t)raw, scale, shift); + return true; + case 3: + *value = k230_gnne_float_dequant_i64((int16_t)(raw & 0xffff), + scale, shift); + return true; + default: + return false; + } +} + +static bool k230_gnne_mfu_act1_value(uint64_t arg, uint32_t channel, + K230GnneFloat value, uint32_t shift, + K230GnneFloat *result) +{ + uint64_t base; + K230GnneFloat threshold; + K230GnneFloat slope; + K230GnneFloat bias; + K230GnneFloat lower; + K230GnneFloat upper; + + if (umul64_overflow((uint64_t)channel, 14, &base) || + UINT64_MAX - arg < base) { + return false; + } + base += arg; + + if (!k230_gnne_read_fp16(base, &threshold)) { + return false; + } + + if (k230_gnne_float_lt(value, threshold)) { + if (!k230_gnne_read_fp16(base + 2, &slope) || + !k230_gnne_read_fp16(base + 6, &bias)) { + return false; + } + } else { + if (!k230_gnne_read_fp16(base + 4, &slope) || + !k230_gnne_read_fp16(base + 8, &bias)) { + return false; + } + } + + if (!k230_gnne_read_fp16(base + 10, &lower) || + !k230_gnne_read_fp16(base + 12, &upper)) { + return false; + } + + *result = k230_gnne_float_clamp( + k230_gnne_float_line(value, slope, shift, bias), lower, upper); + return true; +} + +static bool k230_gnne_mfu_act1_segment_value(uint64_t arg, uint32_t channel, + K230GnneFloat value, + uint32_t shift, + K230GnneFloat *result) +{ + const unsigned int segments = 16; + uint64_t base; + K230GnneFloat threshold; + K230GnneFloat slope; + K230GnneFloat bias; + K230GnneFloat lower; + K230GnneFloat upper; + unsigned int segment = segments - 1; + + if (umul64_overflow((uint64_t)channel, (3 * segments + 1) * 2, + &base) || + UINT64_MAX - arg < base) { + return false; + } + base += arg; + + for (unsigned int i = 0; i < segments - 1; i++) { + if (!k230_gnne_read_fp16(base + i * 2, &threshold)) { + return false; + } + if (k230_gnne_float_gt(threshold, value)) { + segment = i; + break; + } + } + + if (!k230_gnne_read_fp16(base + 2 * (segments - 1) + segment * 2, + &slope) || + !k230_gnne_read_fp16(base + 2 * (2 * (segments - 1) + 1) + + segment * 2, &bias) || + !k230_gnne_read_fp16(base + 2 * (3 * segments - 1), &lower) || + !k230_gnne_read_fp16(base + 2 * 3 * segments, &upper)) { + return false; + } + + *result = k230_gnne_float_clamp( + k230_gnne_float_line(value, slope, shift, bias), lower, upper); + return true; +} + +static bool k230_gnne_mfu_write_quant(uint64_t dst, uint64_t index, + uint32_t quant_type, + K230GnneFloat value, + unsigned int *written) +{ + uint64_t offset; + int64_t rounded; + uint8_t byte; + uint8_t word[2]; + + switch (quant_type) { + case 0: + if (umul64_overflow(index, 2, &offset) || + UINT64_MAX - dst < offset || + !k230_gnne_write_fp16(dst + offset, value)) { + return false; + } + *written = 2; + return true; + case 1: + rounded = k230_gnne_float_round_ties_away(value); + rounded = k230_gnne_clamp_i64(rounded, 0, 255); + byte = rounded; + if (UINT64_MAX - dst < index || + !k230_gnne_dma_write_bytes(dst + index, &byte, sizeof(byte))) { + return false; + } + *written = 1; + return true; + case 2: + rounded = k230_gnne_float_round_ties_away(value); + rounded = k230_gnne_clamp_i64(rounded, -127, 127); + byte = rounded; + if (UINT64_MAX - dst < index || + !k230_gnne_dma_write_bytes(dst + index, &byte, sizeof(byte))) { + return false; + } + *written = 1; + return true; + case 3: + rounded = k230_gnne_float_round_ties_away(value); + rounded = k230_gnne_clamp_i64(rounded, -32767, 32767); + if (umul64_overflow(index, 2, &offset) || + UINT64_MAX - dst < offset) { + return false; + } + stw_le_p(word, rounded); + if (!k230_gnne_dma_write_bytes(dst + offset, word, sizeof(word))) { + return false; + } + *written = 2; + return true; + default: + return false; + } +} + +static uint32_t k230_gnne_mfu_channel(K230GnneFrontend *fe, uint64_t index) +{ + K230GnneMfuAct1Conf *conf = &fe->mfu_act1; + K230GnneShape *shape; + uint64_t plane; + + if (!conf->is_by_channel || + conf->dest_rshape >= K230_GNNE_SHAPE_COUNT || + !fe->shape[conf->dest_rshape].valid) { + return 0; + } + + shape = &fe->shape[conf->dest_rshape]; + if (!shape->c || !shape->h || !shape->w || + umul64_overflow(shape->h, shape->w, &plane) || !plane) { + return 0; + } + + return (index / plane) % shape->c; +} + +static bool k230_gnne_linear_coords4(const K230GnneShape *shape, + uint64_t index, uint32_t *n, + uint32_t *c, uint32_t *h, uint32_t *w) +{ + uint64_t count; + uint64_t hw; + uint64_t chw; + + if (!k230_gnne_shape_count(shape, &count) || !count || index >= count || + umul64_overflow(shape->h, shape->w, &hw) || !hw || + umul64_overflow(shape->c, hw, &chw) || !chw) { + return false; + } + + *n = index / chw; + index %= chw; + *c = index / hw; + index %= hw; + *h = index / shape->w; + *w = index % shape->w; + return true; +} + +static bool k230_gnne_mfu_act1_index(K230GnneFrontend *fe, + bool stride_valid, uint32_t rshape, + uint32_t rstride, uint64_t index, + uint64_t *mapped) +{ + K230GnneShape *shape; + K230GnneStride stride; + uint32_t n; + uint32_t c; + uint32_t h; + uint32_t w; + + *mapped = index; + if (!stride_valid || rshape >= K230_GNNE_SHAPE_COUNT || + !fe->shape[rshape].valid || + !k230_gnne_stride_value(fe, rstride, &stride)) { + return true; + } + + shape = &fe->shape[rshape]; + if (!k230_gnne_linear_coords4(shape, index, &n, &c, &h, &w)) { + return true; + } + + return k230_gnne_packed_offset4(&stride, n, c, h, w, mapped); +} + +static bool k230_gnne_mfu_act1_count(K230GnneFrontend *fe, uint64_t *count) +{ + K230GnneMfuAct1Conf *conf = &fe->mfu_act1; + bool valid; + + if (conf->dest_len_valid && conf->dest_len) { + *count = conf->dest_len; + return true; + } + + *count = k230_gnne_gp(fe, conf->dest_rlen, &valid); + if (valid && *count) { + return true; + } + return k230_gnne_shape_product(fe, conf->dest_rshape, count); +} + +static bool k230_gnne_mfu_binary_source(K230GnneMfuAct1Conf *conf) +{ + return conf->deq[1].valid && conf->src2[0].valid && + conf->src2[1].valid; +} + +static bool k230_gnne_mfu_dequant_psum(K230GnneFrontend *fe, + uint64_t logical, uint64_t index, + uint32_t quant_type, + K230GnneFloat scale, uint32_t shift, + K230GnneFloat *value) +{ + uint64_t psum_index; + + if (quant_type != 0 || + UINT64_MAX - logical < index) { + return false; + } + + psum_index = logical + index; + if (psum_index >= fe->pu_psum_count) { + return false; + } + + *value = k230_gnne_float_dequant_i64(fe->pu_psum[psum_index], scale, + shift); + return true; +} + +static bool k230_gnne_mfu_dequant_source(K230GnneFrontend *fe, + uint64_t base, uint64_t logical, + bool source_type, uint64_t index, + uint32_t quant_type, + K230GnneFloat scale, + uint32_t bias, uint32_t shift, + K230GnneFloat *value) +{ + if (source_type) { + return k230_gnne_mfu_dequant_psum(fe, logical, index, quant_type, + scale, shift, value); + } + + return k230_gnne_mfu_dequant(base, index, quant_type, scale, bias, shift, + value); +} + +static bool k230_gnne_mfu_read_raw(uint64_t base, uint64_t index, + uint32_t quant_type, uint32_t *raw) +{ + uint64_t offset; + uint8_t byte; + uint8_t word[2]; + + switch (quant_type) { + case 0: + case 3: + if (umul64_overflow(index, 2, &offset) || + UINT64_MAX - base < offset || + !k230_gnne_dma_read_bytes(base + offset, word, sizeof(word))) { + return false; + } + *raw = lduw_le_p(word); + return true; + case 1: + case 2: + if (UINT64_MAX - base < index || + !k230_gnne_dma_read_bytes(base + index, &byte, sizeof(byte))) { + return false; + } + *raw = byte; + return true; + default: + return false; + } +} + +static bool k230_gnne_mfu_read_source_raw(K230GnneFrontend *fe, uint64_t base, + uint64_t logical, bool source_type, + uint64_t index, uint32_t quant_type, + uint32_t *raw) +{ + uint64_t psum_index; + + if (!source_type) { + return k230_gnne_mfu_read_raw(base, index, quant_type, raw); + } + + if (quant_type != 0 || UINT64_MAX - logical < index) { + return false; + } + psum_index = logical + index; + if (psum_index >= fe->pu_psum_count) { + return false; + } + + *raw = fe->pu_psum[psum_index]; + return true; +} + +static void k230_gnne_mfu_act1(K230KpuState *s, K230GnneFrontend *fe, + uint32_t word, uint64_t pc) +{ + K230GnneMfuAct1Conf *conf = &fe->mfu_act1; + K230GnneMfuAct1Deq *deq = &conf->deq[0]; + K230GnneMfuAct1Deq *deq2 = &conf->deq[1]; + unsigned int raddr_d1 = extract32(word, 7, 5); + unsigned int raddr_s1 = extract32(word, 12, 5); + unsigned int raddr_s2 = extract32(word, 17, 5); + unsigned int raddr_arg = extract32(word, 22, 5); + unsigned int write_size; + uint32_t dst_encoded; + uint32_t src_encoded; + uint32_t src2_encoded = 0; + uint32_t arg_encoded; + uint32_t scale_raw; + uint32_t scale2_raw = 0; + uint32_t bias; + uint32_t bias2 = 0; + uint32_t deq_shift; + uint32_t deq2_shift = 0; + uint32_t quant_shift; + uint64_t dst_base; + uint64_t src_base; + uint64_t src2_base = 0; + uint64_t arg_base; + uint64_t dst_logical; + uint64_t src_logical; + uint64_t src2_logical = 0; + uint64_t arg_logical; + uint64_t count; + uint64_t written = 0; + uint64_t head = 0; + uint64_t source_head = 0; + uint64_t arg_head = 0; + uint32_t samples = 0; + K230GnneFloat scale; + K230GnneFloat scale2 = float32_zero; + bool binary_source; + bool valid; + bool trace_sample = + trace_event_get_state(TRACE_K230_KPU_MFU_ACT1_SAMPLE); + + if (!conf->dest_valid || !deq->valid || !conf->quant_valid || + !conf->act_valid || + !k230_gnne_quant_type_size(conf->quant_type, &write_size) || + !k230_gnne_mfu_act1_count(fe, &count) || + count > K230_GNNE_MAX_OUTPUT_SIZE / write_size || + conf->funct4 > 1) { + return; + } + binary_source = raddr_s2 && k230_gnne_mfu_binary_source(conf); + + dst_encoded = k230_gnne_gp(fe, raddr_d1, &valid); + if (!valid) { + return; + } + src_encoded = k230_gnne_gp(fe, raddr_s1, &valid); + if (!valid) { + return; + } + arg_encoded = k230_gnne_gp(fe, raddr_arg, &valid); + if (!valid || + !k230_gnne_translate(fe, dst_encoded, &dst_base, &dst_logical) || + !k230_gnne_translate(fe, src_encoded, conf->src2[0].source_type ? + NULL : &src_base, &src_logical) || + !k230_gnne_translate(fe, arg_encoded, &arg_base, &arg_logical)) { + return; + } + if (binary_source) { + src2_encoded = k230_gnne_gp(fe, raddr_s2, &valid); + if (!valid || + !k230_gnne_translate(fe, src2_encoded, + conf->src2[1].source_type ? + NULL : &src2_base, &src2_logical)) { + return; + } + } + + scale_raw = k230_gnne_gp(fe, deq->rscale, &valid); + if (!valid) { + return; + } + bias = k230_gnne_gp(fe, deq->rbias, &valid); + if (!valid) { + return; + } + deq_shift = k230_gnne_gp(fe, deq->rshift_bits, &valid); + if (!valid) { + return; + } + quant_shift = k230_gnne_gp(fe, conf->quant_rshift_bits, &valid); + if (!valid) { + return; + } + if (binary_source) { + scale2_raw = k230_gnne_gp(fe, deq2->rscale, &valid); + if (!valid) { + return; + } + bias2 = k230_gnne_gp(fe, deq2->rbias, &valid); + if (!valid) { + return; + } + deq2_shift = k230_gnne_gp(fe, deq2->rshift_bits, &valid); + if (!valid) { + return; + } + } + + scale = k230_gnne_fp16_to_float(scale_raw); + scale2 = k230_gnne_fp16_to_float(scale2_raw); + + if (!conf->src2[0].source_type) { + unsigned int source_size; + + if (k230_gnne_quant_type_size(deq->quant_type, &source_size)) { + uint8_t source_head_buf[sizeof(source_head)] = {}; + uint64_t source_bytes; + + if (!umul64_overflow(count, source_size, &source_bytes)) { + source_bytes = MIN(source_bytes, + (uint64_t)sizeof(source_head_buf)); + if (source_bytes && + k230_gnne_dma_read_bytes(src_base, source_head_buf, + source_bytes)) { + source_head = k230_gnne_head_le_p(source_head_buf, + source_bytes); + } + } + } + } + { + uint8_t arg_head_buf[sizeof(arg_head)] = {}; + + if (k230_gnne_dma_read_bytes(arg_base, arg_head_buf, + sizeof(arg_head_buf))) { + arg_head = k230_gnne_head_le_p(arg_head_buf, + sizeof(arg_head_buf)); + } + } + + for (uint64_t index = 0; index < count; index++) { + K230GnneFloat value; + K230GnneFloat value2; + uint64_t src_index = index; + uint64_t src2_index = index; + uint64_t dst_index = index; + uint32_t channel = k230_gnne_mfu_channel(fe, index); + unsigned int element_size; + + if (conf->src2[0].valid && + !k230_gnne_mfu_act1_index(fe, conf->stride_valid, + conf->src2[0].rshape, + conf->rstride_s1, index, + &src_index)) { + return; + } + if (binary_source && conf->src2[1].valid && + !k230_gnne_mfu_act1_index(fe, conf->stride_valid, + conf->src2[1].rshape, + conf->rstride_s2, index, + &src2_index)) { + return; + } + if (!k230_gnne_mfu_act1_index(fe, conf->stride_valid, + conf->dest_rshape, conf->rstride_d1, + index, &dst_index)) { + return; + } + + if (!k230_gnne_mfu_dequant_source(fe, src_base, src_logical, + conf->src2[0].source_type, + src_index, + deq->quant_type, scale, bias, + deq_shift, &value)) { + return; + } + if (binary_source) { + if (!k230_gnne_mfu_dequant_source(fe, src2_base, src2_logical, + conf->src2[1].source_type, + src2_index, deq2->quant_type, + scale2, bias2, deq2_shift, + &value2)) { + return; + } + if (conf->funct4 == 1) { + value = k230_gnne_float_mul(value, value2); + } else { + value = k230_gnne_float_add(value, value2); + } + } + if ((conf->is_16_segments ? + !k230_gnne_mfu_act1_segment_value(arg_base, channel, value, + quant_shift, &value) : + !k230_gnne_mfu_act1_value(arg_base, channel, value, quant_shift, + &value)) || + !k230_gnne_mfu_write_quant(dst_base, dst_index, conf->quant_type, + value, &element_size)) { + return; + } + if (trace_sample) { + uint32_t src_raw = UINT32_MAX; + uint32_t out_raw = UINT32_MAX; + uint32_t raws; + uint64_t dst_byte; + uint32_t flags = (deq->quant_type & 0xf) | + ((conf->quant_type & 0xf) << 4) | + (conf->src2[0].source_type ? BIT(8) : 0) | + (binary_source ? BIT(9) : 0) | + (conf->is_16_segments ? BIT(10) : 0) | + (conf->is_by_channel ? BIT(11) : 0) | + ((conf->funct4 & 0xf) << 12) | + ((deq_shift & 0xff) << 16) | + ((quant_shift & 0xff) << 24); + bool trace_this = samples < K230_GNNE_MFU_ACT1_SAMPLE_HEAD; + + if (!trace_this && + !umul64_overflow(dst_index, write_size, &dst_byte) && + UINT64_MAX - dst_logical >= dst_byte) { + trace_this = k230_gnne_trace_window_contains( + k230_gnne_mfu_act1_sample_windows, + ARRAY_SIZE(k230_gnne_mfu_act1_sample_windows), + dst_logical + dst_byte); + } + if (!trace_this && !conf->src2[0].source_type) { + unsigned int source_size; + uint64_t src_byte; + + if (k230_gnne_quant_type_size(deq->quant_type, &source_size) && + !umul64_overflow(src_index, source_size, &src_byte) && + UINT64_MAX - src_logical >= src_byte) { + trace_this = k230_gnne_trace_window_contains( + k230_gnne_mfu_act1_sample_windows, + ARRAY_SIZE(k230_gnne_mfu_act1_sample_windows), + src_logical + src_byte); + } + } + if (trace_this) { + k230_gnne_mfu_read_source_raw(fe, src_base, src_logical, + conf->src2[0].source_type, + src_index, deq->quant_type, + &src_raw); + k230_gnne_mfu_read_raw(dst_base, dst_index, conf->quant_type, + &out_raw); + raws = (src_raw & 0xffff) | ((out_raw & 0xffff) << 16); + trace_k230_kpu_mfu_act1_sample( + k230_kpu_name(s), pc, samples, index, src_index, + dst_index, channel, raws, k230_gnne_float_to_fp16(value), + flags); + samples++; + } + } + written += element_size; + } + + if (written) { + unsigned int head_size = MIN(written, sizeof(head)); + uint8_t head_buf[sizeof(head)] = {}; + + if (k230_gnne_dma_read_bytes(dst_base, head_buf, head_size)) { + head = ldq_le_p(head_buf); + } + } + + trace_k230_kpu_mfu_act1(k230_kpu_name(s), pc, src_logical, dst_logical, + arg_logical, count, deq->quant_type, + conf->quant_type, written, head); + trace_k230_kpu_mfu_act1_enc(k230_kpu_name(s), pc, + src_encoded, dst_encoded, arg_encoded); + trace_k230_kpu_mfu_act1_inputs(k230_kpu_name(s), pc, source_head, + arg_head); + fe->mfu_act1s++; + fe->output_bytes += written; +} + +static bool k230_gnne_offset4(K230GnneStride *stride, uint32_t n, + uint32_t c, uint32_t h, uint32_t w, + uint64_t *offset) +{ + uint64_t result = 0; + uint64_t term; + + if (umul64_overflow((uint64_t)n, stride->n, &term)) { + return false; + } + result += term; + if (umul64_overflow((uint64_t)c, stride->c, &term) || + UINT64_MAX - result < term) { + return false; + } + result += term; + if (umul64_overflow((uint64_t)h, stride->h, &term) || + UINT64_MAX - result < term || + UINT64_MAX - result < w) { + return false; + } + + *offset = result + term + w; + return true; +} + +static bool k230_gnne_packed_offset4(K230GnneStride *stride, uint32_t n, + uint32_t c, uint32_t h, uint32_t w, + uint64_t *offset) +{ + uint64_t result; + uint64_t term; + + if (umul64_overflow((uint64_t)n, stride->n, &result) || + UINT64_MAX - result < c) { + return false; + } + result += c; + if (umul64_overflow(result, stride->c, &term) || + UINT64_MAX - term < h) { + return false; + } + result = term + h; + if (umul64_overflow(result, stride->h, &term) || + UINT64_MAX - term < w) { + return false; + } + + *offset = term + w; + return true; +} + +static bool k230_gnne_packed_stride_footprint(K230GnneShape *shape, + K230GnneStride *stride, + uint64_t *span) +{ + uint64_t last; + + if (!shape->n || !shape->c || !shape->h || !shape->w) { + *span = 0; + return true; + } + + if (!k230_gnne_packed_offset4(stride, shape->n - 1, shape->c - 1, + shape->h - 1, shape->w - 1, &last) || + last == UINT64_MAX) { + return false; + } + + *span = last + 1; + return true; +} + +static bool k230_gnne_pu_if_lane_offset(K230GnneShape *shape, uint32_t n, + uint32_t c, uint32_t h, uint32_t w, + uint64_t *offset) +{ + uint32_t groups; + uint32_t group; + uint32_t lane; + uint64_t result; + uint64_t term; + + if (!shape->c || !shape->h || !shape->w) { + return false; + } + + groups = (shape->c + K230_GNNE_L2_LANE_WIDTH - 1) / + K230_GNNE_L2_LANE_WIDTH; + group = c / K230_GNNE_L2_LANE_WIDTH; + lane = c % K230_GNNE_L2_LANE_WIDTH; + + if (umul64_overflow((uint64_t)n, groups, &result) || + UINT64_MAX - result < group) { + return false; + } + result += group; + if (umul64_overflow(result, shape->h, &term) || + UINT64_MAX - term < h) { + return false; + } + result = term + h; + if (umul64_overflow(result, shape->w, &term) || + UINT64_MAX - term < w) { + return false; + } + result = term + w; + if (umul64_overflow(result, K230_GNNE_L2_LANE_WIDTH, &term) || + UINT64_MAX - term < lane) { + return false; + } + + *offset = term + lane; + return true; +} + +static bool k230_gnne_mfu_pdp1_source_offset(K230GnneStride *stride, + uint32_t n, uint32_t c, + uint32_t h, uint32_t w, + uint64_t *offset) +{ + return k230_gnne_packed_offset4(stride, n, c, h, w, offset); +} + +static bool k230_gnne_mfu_pdp1_quant(K230GnneFrontend *fe, + K230GnneMfuPdp1Conf *conf, + K230GnneFloat *value) +{ + uint32_t scale_raw; + uint32_t bias; + uint32_t shift; + K230GnneFloat scale; + bool valid; + + scale_raw = k230_gnne_gp(fe, conf->quant_rscale, &valid); + if (!valid) { + return false; + } + bias = k230_gnne_gp(fe, conf->quant_rbias, &valid); + if (!valid) { + return false; + } + shift = k230_gnne_gp(fe, conf->quant_rshift_bits, &valid); + if (!valid) { + return false; + } + + scale = k230_gnne_fp16_to_float(scale_raw); + if (!float32_is_zero(scale)) { + *value = k230_gnne_float_div(*value, scale); + } + *value = k230_gnne_float_add(*value, k230_gnne_float_from_u32(bias)); + *value = k230_gnne_float_scalbn(*value, shift); + return true; +} + +static void k230_gnne_mfu_pdp1(K230KpuState *s, K230GnneFrontend *fe, + uint32_t word, uint64_t pc) +{ + K230GnneMfuPdp1Conf *conf = &fe->mfu_pdp1; + K230GnneMfuAct1Deq *deq = &conf->deq; + K230GnneShape *input_shape; + K230GnneStride input_stride; + K230GnneStride output_stride; + unsigned int raddr_d = extract32(word, 7, 5); + unsigned int raddr_s = extract32(word, 12, 5); + unsigned int rshape = extract32(word, 17, 3); + unsigned int write_size; + uint32_t dst_encoded; + uint32_t src_encoded; + uint32_t scale_raw; + uint32_t bias; + uint32_t deq_shift; + uint32_t window_h; + uint32_t window_w; + uint64_t dst_base; + uint64_t src_base; + uint64_t dst_logical; + uint64_t src_logical; + uint64_t output_count; + uint64_t samples; + uint64_t written = 0; + uint64_t head = 0; + uint32_t output_h; + uint32_t output_w; + uint32_t pad_h; + uint32_t pad_w; + K230GnneFloat scale; + bool valid; + + if (rshape >= K230_GNNE_SHAPE_COUNT || + !fe->shape[rshape].valid || + !conf->conf1_valid || !deq->valid || !conf->quant_valid || + conf->funct2 > 3 || + !k230_gnne_stride_value(fe, conf->rstride_s, &input_stride) || + !k230_gnne_stride_value(fe, conf->rstride_d, &output_stride) || + !k230_gnne_quant_type_size(conf->quant_type, &write_size)) { + return; + } + + input_shape = &fe->shape[rshape]; + if (!input_shape->n || !input_shape->c || + !input_shape->h || !input_shape->w || + umul64_overflow((uint64_t)input_shape->n, input_shape->c, + &output_count) || + output_count > K230_GNNE_MAX_OUTPUT_SIZE / write_size) { + return; + } + + window_h = conf->conf4_valid && conf->rwindow_h ? + MIN(conf->rwindow_h, input_shape->h) : input_shape->h; + window_w = conf->conf4_valid && conf->rwindow_w ? + MIN(conf->rwindow_w, input_shape->w) : input_shape->w; + output_h = conf->conf2_valid && conf->rcount_h ? conf->rcount_h : 1; + output_w = conf->conf2_valid && conf->rcount_w ? conf->rcount_w : 1; + output_h = MIN(output_h, input_shape->h); + output_w = MIN(output_w, input_shape->w); + pad_h = output_h == input_shape->h ? window_h / 2 : 0; + pad_w = output_w == input_shape->w ? window_w / 2 : 0; + if (!window_h || !window_w || + umul64_overflow((uint64_t)window_h, window_w, &samples) || + !samples || + umul64_overflow(output_count, output_h, &output_count) || + umul64_overflow(output_count, output_w, &output_count) || + output_count > K230_GNNE_MAX_OUTPUT_SIZE / write_size) { + return; + } + + dst_encoded = k230_gnne_gp(fe, raddr_d, &valid); + if (!valid) { + return; + } + src_encoded = k230_gnne_gp(fe, raddr_s, &valid); + if (!valid || + !k230_gnne_translate(fe, dst_encoded, &dst_base, &dst_logical) || + !k230_gnne_translate(fe, src_encoded, &src_base, &src_logical)) { + return; + } + + scale_raw = k230_gnne_gp(fe, deq->rscale, &valid); + if (!valid) { + return; + } + bias = k230_gnne_gp(fe, deq->rbias, &valid); + if (!valid) { + return; + } + deq_shift = k230_gnne_gp(fe, deq->rshift_bits, &valid); + if (!valid) { + return; + } + scale = k230_gnne_fp16_to_float(scale_raw); + + for (uint32_t n = 0; n < input_shape->n; n++) { + for (uint32_t c = 0; c < input_shape->c; c++) { + for (uint32_t oh = 0; oh < output_h; oh++) { + for (uint32_t ow = 0; ow < output_w; ow++) { + unsigned int element_size; + uint64_t output_index; + K230GnneFloat result = float32_zero; + bool have_sample = false; + + for (uint32_t kh = 0; kh < window_h; kh++) { + for (uint32_t kw = 0; kw < window_w; kw++) { + int64_t ih = (int64_t)oh * conf->stride_h + kh - + pad_h; + int64_t iw = (int64_t)ow * conf->stride_w + kw - + pad_w; + K230GnneFloat value; + + if (ih < 0 || iw < 0 || + ih >= input_shape->h || iw >= input_shape->w) { + if (!k230_gnne_mfu_dequant_literal( + conf->rpad_value, deq->quant_type, + scale, bias, deq_shift, &value)) { + return; + } + } else { + uint64_t input_index; + + if (!k230_gnne_mfu_pdp1_source_offset( + &input_stride, n, c, ih, iw, + &input_index) || + !k230_gnne_mfu_dequant( + src_base, input_index, deq->quant_type, + scale, bias, deq_shift, &value)) { + return; + } + } + if (!have_sample) { + result = value; + have_sample = true; + } else if (conf->funct2 == 0 && + k230_gnne_float_gt(value, result)) { + result = value; + } else if (conf->funct2 == 1 && + k230_gnne_float_lt(value, result)) { + result = value; + } else if (conf->funct2 >= 2) { + result = k230_gnne_float_add(result, value); + } + } + } + + if (!have_sample) { + return; + } + if (output_h == 1 && output_w == 1) { + if (!k230_gnne_offset4(&output_stride, n, c, 0, 0, + &output_index)) { + return; + } + } else if (!k230_gnne_packed_offset4(&output_stride, n, c, + oh, ow, + &output_index)) { + return; + } + if (conf->funct2 == 2) { + result = k230_gnne_float_div( + result, k230_gnne_float_from_u64(samples)); + } + if (!k230_gnne_mfu_pdp1_quant(fe, conf, &result) || + !k230_gnne_mfu_write_quant(dst_base, output_index, + conf->quant_type, result, + &element_size)) { + return; + } + written += element_size; + } + } + } + } + + if (written) { + unsigned int head_size = MIN(written, sizeof(head)); + uint8_t head_buf[sizeof(head)] = {}; + + if (k230_gnne_dma_read_bytes(dst_base, head_buf, head_size)) { + head = ldq_le_p(head_buf); + } + } + + trace_k230_kpu_mfu_pdp1(k230_kpu_name(s), pc, src_logical, dst_logical, + output_count, conf->funct2, deq->quant_type, + conf->quant_type, written, head); + fe->mfu_pdp1s++; + fe->output_bytes += written; +} + +static const uint8_t *k230_gnne_permute_axes(uint32_t permute) +{ + static const uint8_t axes[24][4] = { + { 0, 1, 2, 3 }, /* NCHW */ + { 0, 1, 3, 2 }, /* NCWH */ + { 0, 2, 1, 3 }, /* NHCW */ + { 0, 2, 3, 1 }, /* NHWC */ + { 0, 3, 1, 2 }, /* NWCH */ + { 0, 3, 2, 1 }, /* NWHC */ + { 1, 0, 2, 3 }, /* CNHW */ + { 1, 0, 3, 2 }, /* CNWH */ + { 1, 2, 0, 3 }, /* CHNW */ + { 1, 2, 3, 0 }, /* CHWN */ + { 1, 3, 0, 2 }, /* CWNH */ + { 1, 3, 2, 0 }, /* CWHN */ + { 2, 0, 1, 3 }, /* HNCW */ + { 2, 0, 3, 1 }, /* HNWC */ + { 2, 1, 0, 3 }, /* HCNW */ + { 2, 1, 3, 0 }, /* HCWN */ + { 2, 3, 0, 1 }, /* HWNC */ + { 2, 3, 1, 0 }, /* HWCN */ + { 3, 0, 1, 2 }, /* WNCH */ + { 3, 0, 2, 1 }, /* WNHC */ + { 3, 1, 0, 2 }, /* WCNH */ + { 3, 1, 2, 0 }, /* WCHN */ + { 3, 2, 0, 1 }, /* WHNC */ + { 3, 2, 1, 0 }, /* WHCN */ + }; + + return permute < ARRAY_SIZE(axes) ? axes[permute] : NULL; +} + +static bool k230_gnne_linear_offset4(K230GnneShape *shape, uint32_t n, + uint32_t c, uint32_t h, uint32_t w, + uint64_t *offset) +{ + uint64_t result; + + if (umul64_overflow((uint64_t)n, shape->c, &result) || + UINT64_MAX - result < c) { + return false; + } + result += c; + if (umul64_overflow(result, shape->h, &result) || + UINT64_MAX - result < h) { + return false; + } + result += h; + if (umul64_overflow(result, shape->w, &result) || + UINT64_MAX - result < w) { + return false; + } + + *offset = result + w; + return true; +} + +static bool k230_gnne_permute_shape(K230GnneShape *input_shape, + uint32_t permute, + K230GnneShape *output_shape) +{ + const uint8_t *axes = k230_gnne_permute_axes(permute); + uint32_t input_dims[4] = { + input_shape->n, input_shape->c, input_shape->h, input_shape->w, + }; + + if (!axes) { + return false; + } + + output_shape->n = input_dims[axes[0]]; + output_shape->c = input_dims[axes[1]]; + output_shape->h = input_dims[axes[2]]; + output_shape->w = input_dims[axes[3]]; + output_shape->valid = true; + return true; +} + +static bool k230_gnne_permute_coords(uint32_t n, uint32_t c, uint32_t h, + uint32_t w, uint32_t permute, + uint32_t output_coords[4]) +{ + const uint8_t *axes = k230_gnne_permute_axes(permute); + uint32_t input_coords[4] = { n, c, h, w }; + + if (!axes) { + return false; + } + + for (unsigned int i = 0; i < 4; i++) { + output_coords[i] = input_coords[axes[i]]; + } + return true; +} + +static void k230_gnne_mfu_transpose(K230KpuState *s, K230GnneFrontend *fe, + uint32_t word, uint64_t pc) +{ + K230GnneMfuTransposeConf *conf = &fe->mfu_transpose; + K230GnneShape *input_shape; + K230GnneShape output_shape; + K230GnneStride input_stride; + K230GnneStride output_stride; + unsigned int raddr_d = extract32(word, 7, 5); + unsigned int raddr_s = extract32(word, 12, 5); + unsigned int rshape = extract32(word, 17, 3); + unsigned int element_size; + uint32_t dst_encoded; + uint32_t src_encoded; + uint64_t dst_base; + uint64_t src_base; + uint64_t dst_logical; + uint64_t src_logical; + uint64_t input_count; + uint64_t input_span; + uint64_t output_span; + uint64_t written = 0; + uint64_t head = 0; + g_autofree uint8_t *tmp = NULL; + bool valid; + + if (!conf->valid || + rshape >= K230_GNNE_SHAPE_COUNT || + !fe->shape[rshape].valid || + !k230_gnne_l2_datatype_size(conf->l2_datatype, &element_size) || + !k230_gnne_stride_value(fe, conf->rstride_s, &input_stride) || + !k230_gnne_stride_value(fe, conf->rstride_d, &output_stride) || + !k230_gnne_shape_product(fe, rshape, &input_count)) { + return; + } + + input_shape = &fe->shape[rshape]; + if (!k230_gnne_permute_shape(input_shape, conf->permute, &output_shape) || + !k230_gnne_packed_stride_footprint(input_shape, &input_stride, + &input_span) || + !k230_gnne_packed_stride_footprint(&output_shape, &output_stride, + &output_span) || + input_count > K230_GNNE_MAX_OUTPUT_SIZE / element_size || + input_span > K230_GNNE_MAX_OUTPUT_SIZE / element_size || + output_span > K230_GNNE_MAX_OUTPUT_SIZE / element_size) { + return; + } + + dst_encoded = k230_gnne_gp(fe, raddr_d, &valid); + if (!valid) { + return; + } + src_encoded = k230_gnne_gp(fe, raddr_s, &valid); + if (!valid || + !k230_gnne_translate(fe, dst_encoded, &dst_base, &dst_logical) || + !k230_gnne_translate(fe, src_encoded, &src_base, &src_logical)) { + return; + } + + tmp = g_malloc(input_count * element_size); + for (uint32_t n = 0; n < input_shape->n; n++) { + for (uint32_t c = 0; c < input_shape->c; c++) { + for (uint32_t h = 0; h < input_shape->h; h++) { + for (uint32_t w = 0; w < input_shape->w; w++) { + uint64_t input_index; + uint64_t linear_index; + uint64_t input_offset; + uint64_t linear_offset; + + if (!k230_gnne_packed_offset4(&input_stride, n, c, h, w, + &input_index) || + !k230_gnne_linear_offset4(input_shape, n, c, h, w, + &linear_index) || + umul64_overflow(input_index, element_size, + &input_offset) || + umul64_overflow(linear_index, element_size, + &linear_offset) || + UINT64_MAX - src_base < input_offset || + !k230_gnne_dma_read_bytes(src_base + input_offset, + tmp + linear_offset, + element_size)) { + return; + } + } + } + } + } + + for (uint32_t n = 0; n < input_shape->n; n++) { + for (uint32_t c = 0; c < input_shape->c; c++) { + for (uint32_t h = 0; h < input_shape->h; h++) { + for (uint32_t w = 0; w < input_shape->w; w++) { + uint32_t output_coords[4]; + uint64_t linear_index; + uint64_t output_index; + uint64_t linear_offset; + uint64_t output_offset; + + if (!k230_gnne_permute_coords(n, c, h, w, conf->permute, + output_coords) || + !k230_gnne_linear_offset4(input_shape, n, c, h, w, + &linear_index) || + !k230_gnne_packed_offset4(&output_stride, + output_coords[0], + output_coords[1], + output_coords[2], + output_coords[3], + &output_index) || + umul64_overflow(linear_index, element_size, + &linear_offset) || + umul64_overflow(output_index, element_size, + &output_offset) || + UINT64_MAX - dst_base < output_offset || + !k230_gnne_dma_write_bytes(dst_base + output_offset, + tmp + linear_offset, + element_size)) { + return; + } + written += element_size; + } + } + } + } + + if (written) { + unsigned int head_size = MIN(written, sizeof(head)); + uint8_t head_buf[sizeof(head)] = {}; + + if (k230_gnne_dma_read_bytes(dst_base, head_buf, head_size)) { + head = ldq_le_p(head_buf); + } + } + + trace_k230_kpu_mfu_transpose(k230_kpu_name(s), pc, src_logical, + dst_logical, input_count, conf->permute, + conf->l2_datatype, written, head); + fe->mfu_transposes++; + fe->output_bytes += written; +} + +static bool k230_gnne_act0_write(uint64_t dst, uint64_t index, + uint32_t datatype, K230GnneFloat value, + unsigned int *written) +{ + uint64_t offset; + int64_t rounded; + uint8_t byte; + uint8_t word[2]; + + switch (datatype) { + case 0: + rounded = k230_gnne_clamp_i64(k230_gnne_float_round_ties_away(value), 0, + 255); + byte = rounded; + if (UINT64_MAX - dst < index || + !k230_gnne_dma_write_bytes(dst + index, &byte, sizeof(byte))) { + return false; + } + *written = 1; + return true; + case 1: + rounded = k230_gnne_clamp_i64(k230_gnne_float_round_ties_away(value), + -127, + 127); + byte = rounded; + if (UINT64_MAX - dst < index || + !k230_gnne_dma_write_bytes(dst + index, &byte, sizeof(byte))) { + return false; + } + *written = 1; + return true; + case 2: + if (umul64_overflow(index, 2, &offset) || + UINT64_MAX - dst < offset || + !k230_gnne_write_fp16(dst + offset, value)) { + return false; + } + *written = 2; + return true; + case 3: + rounded = k230_gnne_clamp_i64(k230_gnne_float_round_ties_away(value), + -32767, + 32767); + if (umul64_overflow(index, 2, &offset) || + UINT64_MAX - dst < offset) { + return false; + } + stw_le_p(word, rounded); + if (!k230_gnne_dma_write_bytes(dst + offset, word, sizeof(word))) { + return false; + } + *written = 2; + return true; + default: + return false; + } +} + +static bool k230_gnne_act0_value(uint64_t params, uint32_t channel, + int32_t psum, uint32_t shift, + K230GnneFloat *result) +{ + uint64_t base; + K230GnneFloat input; + K230GnneFloat threshold; + K230GnneFloat slope; + K230GnneFloat bias; + K230GnneFloat lower; + K230GnneFloat upper; + + if (umul64_overflow((uint64_t)channel, 14, &base) || + UINT64_MAX - params < base) { + return false; + } + base += params; + + input = k230_gnne_float_round_fp16( + k230_gnne_float_scalbn(k230_gnne_float_from_i64(psum), + -(int64_t)shift)); + + if (!k230_gnne_read_fp16(base + 12, &threshold)) { + return false; + } + if (k230_gnne_float_lt(input, threshold)) { + if (!k230_gnne_read_fp16(base, &slope) || + !k230_gnne_read_fp16(base + 4, &bias)) { + return false; + } + } else { + if (!k230_gnne_read_fp16(base + 2, &slope) || + !k230_gnne_read_fp16(base + 6, &bias)) { + return false; + } + } + if (!k230_gnne_read_fp16(base + 8, &lower) || + !k230_gnne_read_fp16(base + 10, &upper)) { + return false; + } + + input = k230_gnne_float_round_fp16(k230_gnne_float_mul(input, slope)); + input = k230_gnne_float_round_fp16(k230_gnne_float_add(input, bias)); + input = k230_gnne_float_clamp(input, lower, upper); + + *result = input; + return true; +} + +static int32_t k230_gnne_pu_shift_psum(int32_t value, uint32_t mode) +{ + int64_t shifted; + + switch (mode) { + case 0: + return value; + case 1: + shifted = (int64_t)value << 4; + return k230_gnne_clamp_i64(shifted, INT32_MIN, INT32_MAX); + default: + return value >> 4; + } +} + +static int32_t k230_gnne_add_i32_sat(int32_t a, int32_t b) +{ + int64_t sum = (int64_t)a + b; + + return k230_gnne_clamp_i64(sum, INT32_MIN, INT32_MAX); +} + +static bool k230_gnne_prepare_psum(K230GnneFrontend *fe, uint64_t count) +{ + if (count > K230_GNNE_MAX_OUTPUT_SIZE / sizeof(int32_t)) { + return false; + } + if (fe->pu_psum_valid && fe->pu_psum_count >= count) { + return true; + } + + g_free(fe->pu_psum); + fe->pu_psum = g_new0(int32_t, count); + fe->pu_psum_count = count; + fe->pu_psum_valid = true; + return true; +} + +static bool k230_gnne_read_u8(uint64_t addr, uint8_t *value) +{ + return k230_gnne_dma_read_bytes(addr, value, sizeof(*value)); +} + +static bool k230_gnne_pu_read_input(uint64_t addr, uint32_t quant_type, + int32_t zero_point, int32_t *value) +{ + uint8_t raw; + + if (!k230_gnne_read_u8(addr, &raw)) { + return false; + } + + switch (quant_type) { + case 1: + *value = (int32_t)raw - zero_point; + return true; + case 2: + *value = (int8_t)raw; + return true; + default: + return false; + } +} + +static bool k230_gnne_pu_read_if_raw_input(K230GnneFrontend *fe, + uint64_t offset, + uint32_t quant_type, + int32_t zero_point, + uint8_t *raw, + int32_t *value) +{ + if (offset >= fe->if_data_size) { + return false; + } + + *raw = fe->if_data[offset]; + switch (quant_type) { + case 1: + *value = (int32_t)*raw - zero_point; + return true; + case 2: + *value = (int8_t)*raw; + return true; + default: + return false; + } +} + +static bool k230_gnne_pu_read_if_input(K230GnneFrontend *fe, uint64_t offset, + uint32_t quant_type, + int32_t zero_point, int32_t *value) +{ + uint8_t raw; + + return k230_gnne_pu_read_if_raw_input(fe, offset, quant_type, zero_point, + &raw, value); +} + +typedef struct K230GnnePuInputSource { + uint32_t source_kind; + uint32_t dm_encoded; + uint64_t l1_base; + uint64_t l1_span; + uint64_t if_plane_span; + uint64_t if_channel_span; + uint64_t if_batch_span; + bool use_if; + bool split_i16; +} K230GnnePuInputSource; + +static bool k230_gnne_pu_read_source_input(K230GnneFrontend *fe, + K230GnnePuInputSource *source, + uint64_t offset, + uint32_t quant_type, + int32_t zero_point, + int32_t *value) +{ + if (source->use_if) { + return k230_gnne_pu_read_if_input(fe, offset, quant_type, + zero_point, value); + } + + return k230_gnne_pu_read_input(offset, quant_type, zero_point, value); +} + +static void k230_gnne_trace_pu_if_sample(K230KpuState *s, K230GnneFrontend *fe, + uint64_t pc, uint32_t sample, + uint32_t coords, uint32_t ic, + uint32_t kind, uint64_t input_base, + uint64_t input_index, + uint32_t quant_type, + int32_t zero_point, uint32_t flags) +{ + uint8_t raw; + int32_t input; + + if (UINT64_MAX - input_base < input_index || + !k230_gnne_pu_read_if_raw_input(fe, input_base + input_index, + quant_type, zero_point, &raw, &input)) { + return; + } + + trace_k230_kpu_pu_if_sample(k230_kpu_name(s), pc, sample, coords, ic, + kind, input_index, input, raw, flags); +} + +static bool k230_gnne_pu_if_split_i16_layout(K230GnneShape *shape, + uint64_t *plane_span, + uint64_t *channel_span, + uint64_t *batch_span, + uint64_t *total_span) +{ + uint64_t plane; + + if (!shape->n || !shape->c || !shape->h || !shape->w || + umul64_overflow((uint64_t)shape->h, shape->w, &plane) || + plane > UINT64_MAX - 0x1ff) { + return false; + } + + *plane_span = QEMU_ALIGN_UP(plane, 0x200); + if (umul64_overflow(*plane_span, 2, channel_span) || + umul64_overflow((uint64_t)shape->c, *channel_span, batch_span) || + umul64_overflow((uint64_t)shape->n, *batch_span, total_span) || + *total_span > K230_GNNE_MAX_OUTPUT_SIZE) { + return false; + } + return true; +} + +static bool k230_gnne_pu_if_split_i16_offset(K230GnnePuInputSource *source, + K230GnneShape *shape, + uint32_t n, uint32_t c, + uint32_t h, uint32_t w, + uint64_t fetch, + uint64_t *offset) +{ + uint64_t pixel; + uint64_t result; + uint64_t term; + + if (source->if_channel_span < source->if_plane_span || + fetch > source->if_channel_span - source->if_plane_span || + umul64_overflow((uint64_t)h, shape->w, &term) || + UINT64_MAX - term < w) { + return false; + } + term += w; + pixel = term; + if (pixel >= source->if_plane_span || + umul64_overflow((uint64_t)n, source->if_batch_span, &result) || + umul64_overflow((uint64_t)c, source->if_channel_span, &term) || + UINT64_MAX - result < term) { + return false; + } + result += term; + if (UINT64_MAX - result < fetch) { + return false; + } + result += fetch; + if (UINT64_MAX - result < pixel) { + return false; + } + + *offset = result + pixel; + return true; +} + +static bool k230_gnne_pu_source_input_offset(K230GnnePuInputSource *source, + K230GnneShape *shape, + uint32_t n, uint32_t c, + uint32_t h, uint32_t w, + uint64_t input_base, + uint64_t input_index, + uint64_t *source_offset) +{ + if (source->use_if && source->split_i16) { + return k230_gnne_pu_if_split_i16_offset(source, shape, n, c, h, w, + input_base, source_offset); + } + + if (UINT64_MAX - input_base < input_index) { + return false; + } + *source_offset = input_base + input_index; + return true; +} + +static bool k230_gnne_pu_l1_input_base(K230KpuState *s, K230GnneFrontend *fe, + uint64_t pc, + uint32_t fetch_encoded, + K230GnneShape *input_shape, + K230GnneStride *input_stride, + uint64_t *input_base, + K230GnnePuInputSource *source) +{ + K230GnneShape *shape; + K230GnneStride dm_stride; + uint32_t dm_encoded; + uint64_t dm_base; + uint64_t dm_span; + uint64_t if_span; + uint64_t if_size; + bool split_i16 = fe->dm_load_l1_conf.datatype == 2; + bool trace_stage = + trace_event_get_state(TRACE_K230_KPU_PU_L1_STAGE); + bool trace_stage_sample = + trace_event_get_state(TRACE_K230_KPU_PU_L1_STAGE_SAMPLE); + uint32_t stage_samples = 0; + bool valid; + + if (!fe->dm_load_l1.valid || + fe->dm_load_l1.rshape >= K230_GNNE_SHAPE_COUNT || + !fe->shape[fe->dm_load_l1.rshape].valid || + !k230_gnne_stride_value(fe, fe->dm_load_l1_conf.rstride_s, + &dm_stride)) { + return false; + } + + shape = &fe->shape[fe->dm_load_l1.rshape]; + if (input_shape->n > shape->n || input_shape->c > shape->c || + input_shape->h > shape->h || input_shape->w > shape->w) { + return false; + } + if (!k230_gnne_packed_stride_footprint(shape, &dm_stride, &dm_span)) { + return false; + } + if (split_i16) { + if (!k230_gnne_pu_if_split_i16_layout( + input_shape, &source->if_plane_span, + &source->if_channel_span, &source->if_batch_span, + &if_size) || + fetch_encoded > source->if_channel_span - + source->if_plane_span) { + return false; + } + } else { + if (!k230_gnne_packed_stride_footprint(input_shape, input_stride, + &if_span) || + !if_span || + fetch_encoded >= if_span || + UINT64_MAX - fetch_encoded < if_span) { + return false; + } + if_size = fetch_encoded + if_span; + } + + dm_encoded = k230_gnne_gp(fe, fe->dm_load_l1.raddr_s, &valid); + if (!valid || !k230_gnne_translate(fe, dm_encoded, &dm_base, NULL)) { + return false; + } + if (fetch_encoded == dm_encoded) { + return false; + } + if (trace_stage) { + uint32_t flags = fe->dm_load_l1_conf.datatype | + (split_i16 ? BIT(16) : 0); + + trace_k230_kpu_pu_l1_stage( + k230_kpu_name(s), pc, fetch_encoded, dm_encoded, dm_base, + dm_span, if_size, source->if_plane_span, + source->if_channel_span, flags); + } + + g_free(fe->if_data); + fe->if_data = g_malloc0(if_size); + fe->if_data_size = if_size; + + for (uint32_t n = 0; n < input_shape->n; n++) { + for (uint32_t c = 0; c < input_shape->c; c++) { + for (uint32_t h = 0; h < input_shape->h; h++) { + for (uint32_t w = 0; w < input_shape->w; w++) { + uint64_t src_offset; + uint64_t dst_offset; + + if (!k230_gnne_packed_offset4(&dm_stride, n, c, h, w, + &src_offset) || + src_offset >= dm_span) { + goto fail; + } + if (split_i16) { + uint8_t raw[2]; + uint64_t src_byte_offset; + uint64_t dst_high; + + if (umul64_overflow(src_offset, 2, + &src_byte_offset) || + UINT64_MAX - dm_base < src_byte_offset || + !k230_gnne_pu_if_split_i16_offset( + source, input_shape, n, c, h, w, 0, + &dst_offset) || + dst_offset >= if_size || + dst_offset > UINT64_MAX - source->if_plane_span || + dst_offset + source->if_plane_span >= if_size || + !k230_gnne_dma_read_bytes( + dm_base + src_byte_offset, raw, sizeof(raw))) { + goto fail; + } + dst_high = dst_offset + source->if_plane_span; + fe->if_data[dst_offset] = raw[0]; + fe->if_data[dst_high] = raw[1]; + if (trace_stage_sample && + stage_samples < K230_GNNE_PU_L1_STAGE_SAMPLES) { + uint32_t coords = (c & 0x3ff) | + ((h & 0x7ff) << 10) | + ((w & 0x7ff) << 21); + uint32_t packed_raw = raw[0] | (raw[1] << 8); + uint32_t flags = (fetch_encoded & 0xffff) | + ((n & 0xff) << 16) | + BIT(24); + + trace_k230_kpu_pu_l1_stage_sample( + k230_kpu_name(s), pc, stage_samples, coords, + src_offset, src_byte_offset, + dst_offset, dst_high, packed_raw, flags); + stage_samples++; + } + } else { + if (!k230_gnne_packed_offset4(input_stride, n, c, h, w, + &dst_offset) || + dst_offset >= if_size || + UINT64_MAX - dm_base < src_offset || + !k230_gnne_dma_read_bytes(dm_base + src_offset, + fe->if_data + dst_offset, + 1)) { + goto fail; + } + } + } + } + } + } + + if (fetch_encoded >= if_size) { + return false; + } + + *input_base = fetch_encoded; + source->source_kind = K230_GNNE_PU_INPUT_L1_OFFSET; + source->dm_encoded = dm_encoded; + source->l1_base = dm_base; + source->l1_span = dm_span; + source->use_if = true; + source->split_i16 = split_i16; + return true; + +fail: + g_clear_pointer(&fe->if_data, g_free); + fe->if_data_size = 0; + return false; +} + +static void k230_gnne_pu_compute(K230KpuState *s, K230GnneFrontend *fe, + uint32_t word, uint64_t pc) +{ + K230GnnePuConf *pu = &fe->pu_conf; + K230GnneShape input_shape; + K230GnneShape output_shape; + K230GnneStride input_stride; + K230GnneStride output_stride; + unsigned int tcu_id = extract32(word, 7, 3); + unsigned int of_shift_mode = extract32(word, 10, 2); + unsigned int dest_size; + uint32_t input_encoded; + uint32_t weight_encoded = 0; + uint32_t weight_zp_encoded = 0; + uint32_t act0_encoded; + uint32_t dest_encoded; + uint32_t input_source_flags; + int32_t input_zp; + K230GnnePuInputSource input_source = { + .source_kind = K230_GNNE_PU_INPUT_GLOBAL, + }; + uint64_t input_base; + uint64_t weight_base = 0; + uint64_t weight_zp_base = 0; + uint64_t act0_base = 0; + uint64_t dest_base = 0; + uint64_t dest_logical = 0; + uint64_t psum_base_index = 0; + uint64_t output_count = 0; + uint64_t output_span = 0; + uint64_t psum_span; + uint64_t written = 0; + uint64_t head = 0; + uint32_t acc_samples = 0; + uint32_t mac_samples = 0; + uint32_t if_samples = 0; + uint32_t act0_samples = 0; + bool to_act0; + bool deconv; + bool valid; + bool trace_acc_sample = + trace_event_get_state(TRACE_K230_KPU_PU_ACC_SAMPLE); + bool trace_mac_sample = + trace_event_get_state(TRACE_K230_KPU_PU_MAC_SAMPLE); + bool trace_if_sample = + trace_event_get_state(TRACE_K230_KPU_PU_IF_SAMPLE); + bool trace_act0_sample = + trace_event_get_state(TRACE_K230_KPU_PU_ACT0_SAMPLE); + +#define PU_COMPUTE_SKIP(reason, source, dest, detail) \ + do { \ + trace_k230_kpu_pu_compute_skip(k230_kpu_name(s), pc, reason, \ + tcu_id, source, dest, detail); \ + return; \ + } while (0) + + if (!fe->dm_load_w_conf.valid || !fe->dm_load_w.valid || + !pu->fetch1_valid || !pu->fetch3_valid || + !pu->fetch_deq_valid || !pu->w_valid || !pu->of1_valid || + !pu->of2_valid || !pu->compute_valid || pu->mode > 1 || + pu->dest_target > 1 || + (pu->mode == 0 && (pu->quant_type < 1 || pu->quant_type > 2)) || + (pu->mode == 1 && (pu->quant_type < 1 || pu->quant_type > 2 || + fe->dm_load_w_conf.kernel_h != 1 || + fe->dm_load_w_conf.kernel_w != 1))) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_CONF, 0, 0, pu->mode); + } + deconv = pu->mode == 1; + to_act0 = pu->dest_target == 1; + if (to_act0) { + if (!fe->dm_load_act0.valid || !fe->act0_conf.valid || + !fe->act0_compute.valid || + !k230_gnne_act0_type_size(fe->act0_compute.dest_datatype, + &dest_size)) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_ACT0, 0, 0, + fe->act0_compute.dest_datatype); + } + } else { + dest_size = sizeof(int32_t); + } + + if (pu->rshape >= K230_GNNE_SHAPE_COUNT || + !fe->shape[pu->rshape].valid || + !k230_gnne_stride_value(fe, pu->rstride_s, &input_stride)) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_SHAPE, pu->rshape, pu->rshape_d, + pu->rstride_s); + } + + input_shape = fe->shape[pu->rshape]; + if (pu->output_shape_valid) { + output_shape = pu->output_shape; + } else if (pu->rshape_d >= K230_GNNE_SHAPE_COUNT || + !fe->shape[pu->rshape_d].valid) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_SHAPE, pu->rshape, pu->rshape_d, + pu->rstride_s); + } else { + output_shape = fe->shape[pu->rshape_d]; + } + if (pu->output_stride_valid) { + output_stride = pu->output_stride; + } else if (!k230_gnne_stride_value(fe, pu->rstride_d, &output_stride)) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_SHAPE, pu->rshape, pu->rshape_d, + pu->rstride_d); + } + + if (!input_shape.c || !output_shape.c || + !fe->dm_load_w_conf.kernel_h || !fe->dm_load_w_conf.kernel_w || + !k230_gnne_shape_count(&output_shape, &output_count) || + !k230_gnne_packed_stride_footprint(&output_shape, &output_stride, + &output_span) || + output_count > K230_GNNE_MAX_OUTPUT_SIZE / dest_size || + output_span > K230_GNNE_MAX_OUTPUT_SIZE / dest_size) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_COUNT, output_count, output_span, + dest_size); + } + + if (!to_act0) { + dest_encoded = k230_gnne_gp(fe, pu->raddr_d, &valid); + if (!valid || + !k230_gnne_translate(fe, dest_encoded, NULL, &dest_logical)) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_DST_TRANSLATE, 0, + dest_encoded, pu->raddr_d); + } + psum_base_index = dest_logical; + fe->pu_psum_base = psum_base_index; + fe->pu_psum_base_valid = true; + } else if (pu->load_psum) { + if (fe->pu_psum_base_valid) { + psum_base_index = fe->pu_psum_base; + } else { + dest_encoded = k230_gnne_gp(fe, pu->raddr_d, &valid); + if (!valid || + !k230_gnne_translate(fe, dest_encoded, NULL, + &dest_logical)) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_DST_TRANSLATE, 0, + dest_encoded, pu->raddr_d); + } + psum_base_index = dest_logical; + } + } + if (UINT64_MAX - psum_base_index < output_span) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_OVERFLOW, psum_base_index, + output_span, 0); + } + psum_span = psum_base_index + output_span; + if (!k230_gnne_prepare_psum(fe, psum_span)) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_PSUM, psum_base_index, + output_span, psum_span); + } + if (pu->clr_psum && output_span) { + memset(fe->pu_psum, 0, psum_span * sizeof(*fe->pu_psum)); + } + + if (deconv) { + input_encoded = k230_gnne_gp(fe, pu->raddr_s, &valid); + } else { + input_encoded = k230_gnne_gp(fe, pu->raddr_s, &valid); + if (!valid && fe->dm_load_l1.valid) { + input_encoded = k230_gnne_gp(fe, fe->dm_load_l1.raddr_s, &valid); + } + } + if (!valid) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_SRC_GP, pu->raddr_s, 0, 0); + } + weight_encoded = k230_gnne_gp(fe, fe->dm_load_w.raddr_s, &valid); + if (!valid) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_SRC_GP, fe->dm_load_w.raddr_s, 0, 1); + } + weight_zp_encoded = k230_gnne_gp(fe, fe->dm_load_w.raddr_bw, &valid); + if (!valid) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_SRC_GP, fe->dm_load_w.raddr_bw, 0, 2); + } + input_zp = k230_gnne_gp(fe, pu->rbx, &valid); + if (!valid) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_SRC_TRANSLATE, input_encoded, + weight_encoded, weight_zp_encoded); + } + if (!deconv && + k230_gnne_pu_l1_input_base(s, fe, pc, input_encoded, &input_shape, + &input_stride, &input_base, + &input_source)) { + /* IF/L1 sources are staged through the current DM_LOAD_L1 view. */ + } else if (!k230_gnne_translate(fe, input_encoded, &input_base, NULL)) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_SRC_TRANSLATE, input_encoded, + weight_encoded, weight_zp_encoded); + } + input_source_flags = pu->quant_type | + (pu->load_psum ? BIT(8) : 0) | + (pu->dest_target ? BIT(9) : 0); + trace_k230_kpu_pu_input_source(k230_kpu_name(s), pc, tcu_id, + input_source.source_kind, input_encoded, + input_source.dm_encoded, input_base, + input_source.l1_base, + input_source.l1_span, + input_source_flags); + if (!k230_gnne_translate(fe, weight_encoded, &weight_base, NULL) || + !k230_gnne_translate(fe, weight_zp_encoded, &weight_zp_base, NULL)) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_SRC_TRANSLATE, input_encoded, + weight_encoded, weight_zp_encoded); + } + if (to_act0) { + act0_encoded = k230_gnne_gp(fe, fe->dm_load_act0.raddr_s, &valid); + if (!valid) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_ACT0, + fe->dm_load_act0.raddr_s, 0, 0); + } + if (fe->dm_store_of.valid) { + dest_encoded = k230_gnne_gp(fe, fe->dm_store_of.raddr_d, &valid); + } else { + valid = false; + } + if (!valid) { + dest_encoded = k230_gnne_gp(fe, fe->act0_compute.raddr_d, &valid); + } + if (!valid) { + dest_encoded = k230_gnne_gp(fe, pu->raddr_d, &valid); + } + if (!k230_gnne_translate(fe, act0_encoded, &act0_base, NULL) || + !k230_gnne_translate(fe, dest_encoded, &dest_base, + &dest_logical)) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_ACT0, act0_encoded, + dest_encoded, 1); + } + } + + for (uint32_t n = 0; n < output_shape.n; n++) { + for (uint32_t oc = 0; oc < output_shape.c; oc++) { + uint8_t weight_zp; + int32_t signed_weight_zp; + + if (!k230_gnne_read_u8(weight_zp_base + oc, &weight_zp)) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_SOURCE_READ, + weight_zp_base + oc, 0, oc); + } + signed_weight_zp = weight_zp; + for (uint32_t oh = 0; oh < output_shape.h; oh++) { + for (uint32_t ow = 0; ow < output_shape.w; ow++) { + int32_t acc = 0; + K230GnneFloat activated; + uint32_t act_channel; + unsigned int written_size; + uint64_t output_index; + uint64_t psum_index; + uint64_t output_offset = 0; + uint64_t trace_output_offset; + bool trace_output_window = false; + + if (to_act0 && + k230_gnne_packed_offset4(&output_stride, n, oc, oh, + ow, &output_index) && + !umul64_overflow(output_index, dest_size, + &trace_output_offset) && + UINT64_MAX - dest_logical >= trace_output_offset) { + trace_output_window = k230_gnne_trace_window_contains( + k230_gnne_pu_act0_sample_windows, + ARRAY_SIZE(k230_gnne_pu_act0_sample_windows), + dest_logical + trace_output_offset); + } + + for (uint32_t ic = 0; ic < input_shape.c; ic++) { + for (uint32_t ky = 0; + ky < fe->dm_load_w_conf.kernel_h; ky++) { + for (uint32_t kx = 0; + kx < fe->dm_load_w_conf.kernel_w; kx++) { + uint32_t ih = oh * pu->stride_h + ky; + uint32_t iw = ow * pu->stride_w + kx; + uint64_t input_index; + uint64_t input_read_offset; + uint64_t weight_index; + int32_t input; + int32_t product; + uint8_t weight; + + if (deconv) { + ih = oh; + iw = ow; + } + if (ih >= input_shape.h || + iw >= input_shape.w) { + continue; + } + if (!k230_gnne_packed_offset4(&input_stride, + n, ic, ih, iw, + &input_index)) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_OVERFLOW, + input_base, ic, ih); + } + if (!k230_gnne_pu_source_input_offset( + &input_source, &input_shape, + n, ic, ih, iw, input_base, + input_index, &input_read_offset)) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_OVERFLOW, + input_base, input_index, + ic); + } + weight_index = ((uint64_t)oc * + fe->dm_load_w_conf.kernel_h * + fe->dm_load_w_conf.kernel_w + + (uint64_t)ky * + fe->dm_load_w_conf.kernel_w + + kx) * + K230_GNNE_L2_LANE_WIDTH + ic; + if (!k230_gnne_pu_read_source_input( + fe, &input_source, + input_read_offset, + pu->quant_type, input_zp, &input) || + !k230_gnne_read_u8(weight_base + + weight_index, + &weight)) { + PU_COMPUTE_SKIP( + K230_GNNE_SKIP_SOURCE_READ, + input_read_offset, + weight_base + weight_index, + ic); + } + product = input * + ((int32_t)weight - + signed_weight_zp); + if (trace_mac_sample && + (mac_samples < + K230_GNNE_PU_MAC_SAMPLE_HEAD || + trace_output_window)) { + uint32_t coords = (oc & 0x3ff) | + ((oh & 0x7ff) << 10) | + ((ow & 0x7ff) << 21); + + trace_k230_kpu_pu_mac_sample( + k230_kpu_name(s), pc, mac_samples, + coords, ic, input_read_offset, + weight_index, input, weight, product); + mac_samples++; + } + if (trace_if_sample && input_source.use_if && + (if_samples < + K230_GNNE_PU_IF_SAMPLE_HEAD || + trace_output_window)) { + uint64_t linear_index; + uint64_t lane_index; + uint32_t coords = (oc & 0x3ff) | + ((oh & 0x7ff) << 10) | + ((ow & 0x7ff) << 21); + uint32_t flags = pu->quant_type | + (pu->load_psum ? + BIT(8) : 0) | + (pu->dest_target ? + BIT(9) : 0) | + (of_shift_mode << 16); + + if (input_source.split_i16) { + k230_gnne_trace_pu_if_sample( + s, fe, pc, if_samples, coords, ic, + K230_GNNE_PU_IF_SAMPLE_PACKED, 0, + input_read_offset, + pu->quant_type, input_zp, flags); + } else { + k230_gnne_trace_pu_if_sample( + s, fe, pc, if_samples, coords, ic, + K230_GNNE_PU_IF_SAMPLE_PACKED, + input_base, input_index, + pu->quant_type, input_zp, flags); + if (k230_gnne_offset4(&input_stride, n, + ic, ih, iw, + &linear_index)) { + k230_gnne_trace_pu_if_sample( + s, fe, pc, if_samples, coords, + ic, + K230_GNNE_PU_IF_SAMPLE_LINEAR, + input_base, linear_index, + pu->quant_type, input_zp, + flags); + } + if (k230_gnne_pu_if_lane_offset( + &input_shape, n, ic, ih, iw, + &lane_index)) { + k230_gnne_trace_pu_if_sample( + s, fe, pc, if_samples, coords, + ic, + K230_GNNE_PU_IF_SAMPLE_LANE, + input_base, lane_index, + pu->quant_type, input_zp, + flags); + } + } + if_samples++; + } + acc += product; + } + } + } + + if (!k230_gnne_packed_offset4(&output_stride, n, oc, oh, + ow, &output_index)) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_OVERFLOW, + dest_base, oc, oh); + } + if (to_act0 && + !umul64_overflow(output_index, dest_size, + &trace_output_offset) && + UINT64_MAX - dest_logical >= trace_output_offset) { + trace_output_window = k230_gnne_trace_window_contains( + k230_gnne_pu_act0_sample_windows, + ARRAY_SIZE(k230_gnne_pu_act0_sample_windows), + dest_logical + trace_output_offset); + } + psum_index = output_index; + if (!to_act0 || pu->load_psum) { + if (UINT64_MAX - psum_base_index < output_index) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_OVERFLOW, + psum_base_index, output_index, 1); + } + psum_index = psum_base_index + output_index; + } + acc = k230_gnne_pu_shift_psum(acc, of_shift_mode); + if (pu->load_psum) { + if (psum_index >= fe->pu_psum_count) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_PSUM, + psum_index, fe->pu_psum_count, 0); + } + acc = k230_gnne_add_i32_sat(acc, + fe->pu_psum[psum_index]); + } + if (trace_acc_sample && + (acc_samples < K230_GNNE_PU_ACT0_SAMPLE_HEAD || + trace_output_window)) { + uint32_t coords = (oc & 0x3ff) | + ((oh & 0x7ff) << 10) | + ((ow & 0x7ff) << 21); + uint32_t flags = pu->quant_type | + (pu->load_psum ? BIT(8) : 0) | + (pu->dest_target ? BIT(9) : 0) | + (of_shift_mode << 16); + + trace_k230_kpu_pu_acc_sample( + k230_kpu_name(s), pc, tcu_id, acc_samples, + coords, output_index, psum_index, acc, flags); + acc_samples++; + } + if (!to_act0) { + if (psum_index >= fe->pu_psum_count) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_PSUM, + psum_index, fe->pu_psum_count, 1); + } + fe->pu_psum[psum_index] = acc; + written += sizeof(int32_t); + continue; + } + act_channel = fe->act0_compute.is_by_channel ? oc : 0; + if (!k230_gnne_act0_value(act0_base, act_channel, acc, + fe->act0_conf.rshift_bits, + &activated)) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_ACT0, act0_base, + act_channel, acc); + } + if (umul64_overflow(output_index, dest_size, + &output_offset) || + UINT64_MAX - dest_base < output_offset) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_DEST_WRITE, + dest_base, output_offset, + output_index); + } + if (trace_act0_sample && + (act0_samples < K230_GNNE_PU_ACT0_SAMPLE_HEAD || + trace_output_window)) { + uint64_t act0_param_offset; + uint64_t act0_param_base = act0_base; + uint64_t act0_head = 0; + uint8_t act0_head_buf[sizeof(act0_head)] = {}; + uint32_t coords = (oc & 0x3ff) | + ((oh & 0x7ff) << 10) | + ((ow & 0x7ff) << 21); + + if (!umul64_overflow((uint64_t)act_channel, 14, + &act0_param_offset) && + UINT64_MAX - act0_base >= act0_param_offset) { + act0_param_base += act0_param_offset; + if (k230_gnne_dma_read_bytes( + act0_param_base, act0_head_buf, + sizeof(act0_head_buf))) { + act0_head = ldq_le_p(act0_head_buf); + } + } + trace_k230_kpu_pu_act0_sample( + k230_kpu_name(s), pc, tcu_id, act0_samples, + coords, output_index, psum_index, acc, + fe->act0_conf.rshift_bits, + k230_gnne_float_to_fp16(activated)); + trace_k230_kpu_pu_act0_sample_addr( + k230_kpu_name(s), pc, tcu_id, act0_samples, + act0_param_base, dest_base + output_offset, + act0_head); + act0_samples++; + } + + if (!k230_gnne_act0_write(dest_base + output_offset, 0, + fe->act0_compute.dest_datatype, + activated, &written_size)) { + PU_COMPUTE_SKIP(K230_GNNE_SKIP_DEST_WRITE, + dest_base, output_offset, + output_index); + } + written += written_size; + } + } + } + } + + if (to_act0 && written) { + unsigned int head_size = MIN(written, sizeof(head)); + uint8_t head_buf[sizeof(head)] = {}; + + if (k230_gnne_dma_read_bytes(dest_base, head_buf, head_size)) { + head = ldq_le_p(head_buf); + } + } + + trace_k230_kpu_pu_compute(k230_kpu_name(s), pc, tcu_id, dest_logical, + written, head); + fe->pu_computes++; + fe->output_bytes += written; +#undef PU_COMPUTE_SKIP +} + +static void k230_gnne_pdp0_compute(K230KpuState *s, K230GnneFrontend *fe, + uint32_t word, uint64_t pc) +{ + K230GnnePdp0Conf *pdp0 = &fe->pdp0_conf; + K230GnneShape input_shape; + K230GnneShape output_shape; + K230GnneStride output_stride; + unsigned int tcu_id = extract32(word, 7, 3); + unsigned int raddr_s = extract32(word, 10, 5); + unsigned int dest_size; + uint32_t input_encoded; + uint32_t weight_encoded; + uint32_t weight_zp_encoded; + uint32_t act0_encoded; + uint32_t dest_encoded; + int32_t input_zp; + uint64_t input_base; + uint64_t weight_base; + uint64_t weight_zp_base; + uint64_t act0_base; + uint64_t dest_base; + uint64_t dest_logical; + uint64_t input_count; + uint64_t output_count; + uint64_t written = 0; + uint64_t head = 0; + bool valid; + + if (!fe->dm_load_act0.valid || !fe->dm_store_of.valid || + !fe->act0_conf.valid || + !fe->act0_compute.valid || !pdp0->mode_valid || + !pdp0->fetch1_valid || !pdp0->fetch3_valid || + !pdp0->fetch_deq_valid || !pdp0->w_valid || !pdp0->of_valid || + pdp0->mode > 4 || pdp0->quant_type != 1 || + (pdp0->mode == 0 && !fe->dm_load_w.valid) || + !k230_gnne_act0_type_size(fe->act0_compute.dest_datatype, + &dest_size)) { + return; + } + + if (pdp0->input_shape_valid) { + input_shape = pdp0->input_shape; + } else if (pdp0->rshape >= K230_GNNE_SHAPE_COUNT || + !fe->shape[pdp0->rshape].valid) { + return; + } else { + input_shape = fe->shape[pdp0->rshape]; + } + if (pdp0->output_shape_valid) { + output_shape = pdp0->output_shape; + } else if (pdp0->rshape_d >= K230_GNNE_SHAPE_COUNT || + !fe->shape[pdp0->rshape_d].valid) { + return; + } else { + output_shape = fe->shape[pdp0->rshape_d]; + } + if (pdp0->output_stride_valid) { + output_stride = pdp0->output_stride; + } else if (!k230_gnne_stride_value(fe, pdp0->rstride_d, + &output_stride)) { + return; + } + + if (!input_shape.c || !input_shape.h || !input_shape.w || + !output_shape.c || output_shape.c > input_shape.c || + !pdp0->kernel_h || !pdp0->kernel_w || + !k230_gnne_shape_count(&input_shape, &input_count) || + !k230_gnne_shape_count(&output_shape, &output_count) || + input_count > K230_GNNE_MAX_OUTPUT_SIZE || + output_count > K230_GNNE_MAX_OUTPUT_SIZE / dest_size) { + return; + } + + input_encoded = k230_gnne_gp(fe, raddr_s, &valid); + if (!valid) { + return; + } + if (pdp0->mode == 0) { + weight_encoded = k230_gnne_gp(fe, fe->dm_load_w.raddr_s, &valid); + if (!valid) { + return; + } + weight_zp_encoded = k230_gnne_gp(fe, fe->dm_load_w.raddr_bw, &valid); + if (!valid) { + return; + } + } + act0_encoded = k230_gnne_gp(fe, fe->dm_load_act0.raddr_s, &valid); + if (!valid) { + return; + } + dest_encoded = k230_gnne_gp(fe, fe->dm_store_of.raddr_d, &valid); + input_zp = k230_gnne_gp(fe, pdp0->rbx, &valid); + if (!valid || + !k230_gnne_translate(fe, input_encoded, &input_base, NULL) || + !k230_gnne_translate(fe, act0_encoded, &act0_base, NULL) || + !k230_gnne_translate(fe, dest_encoded, &dest_base, &dest_logical)) { + return; + } + if (pdp0->mode == 0 && + (!k230_gnne_translate(fe, weight_encoded, &weight_base, NULL) || + !k230_gnne_translate(fe, weight_zp_encoded, &weight_zp_base, + NULL))) { + return; + } + + for (uint32_t n = 0; n < output_shape.n; n++) { + for (uint32_t c = 0; c < output_shape.c; c++) { + int32_t signed_weight_zp = 0; + + if (pdp0->mode == 0) { + uint8_t weight_zp; + + if (!k230_gnne_read_u8(weight_zp_base + c, &weight_zp)) { + return; + } + signed_weight_zp = weight_zp; + } + for (uint32_t oh = 0; oh < output_shape.h; oh++) { + for (uint32_t ow = 0; ow < output_shape.w; ow++) { + int32_t acc = 0; + K230GnneFloat activated; + uint32_t act_channel; + unsigned int written_size; + uint64_t output_index; + uint64_t output_offset; + + bool have_sample = false; + uint64_t samples = 0; + + for (uint32_t ky = 0; ky < pdp0->kernel_h; ky++) { + for (uint32_t kx = 0; kx < pdp0->kernel_w; kx++) { + uint32_t ih = oh * pdp0->stride_h + ky; + uint32_t iw = ow * pdp0->stride_w + kx; + uint64_t input_index; + uint64_t weight_index; + uint8_t input; + int32_t value; + + if (ih >= input_shape.h || iw >= input_shape.w) { + input = pdp0->rpad_value; + } else { + input_index = (uint64_t)n * input_shape.c * + input_shape.h * + input_shape.w + + (uint64_t)c * input_shape.h * + input_shape.w + + (uint64_t)ih * input_shape.w + + iw; + if (!k230_gnne_read_u8(input_base + + input_index, &input)) { + return; + } + } + value = (int32_t)input - input_zp; + samples++; + + switch (pdp0->mode) { + case 0: + { + uint8_t weight; + + weight_index = ((uint64_t)c * + pdp0->kernel_h * + pdp0->kernel_w + + (uint64_t)ky * + pdp0->kernel_w + kx); + if (!k230_gnne_read_u8(weight_base + + weight_index, + &weight)) { + return; + } + acc += value * + ((int32_t)weight - signed_weight_zp); + break; + } + case 1: + if (!have_sample || value < acc) { + acc = value; + } + break; + case 2: + if (!have_sample || value > acc) { + acc = value; + } + break; + case 3: + case 4: + acc = k230_gnne_add_i32_sat(acc, value); + break; + default: + return; + } + have_sample = true; + } + } + if (!have_sample) { + return; + } + if (pdp0->mode == 4 && samples) { + acc = k230_gnne_round_average_i32(acc, samples); + } + + act_channel = fe->act0_compute.is_by_channel ? c : 0; + if (!k230_gnne_act0_value(act0_base, act_channel, acc, + fe->act0_conf.rshift_bits, + &activated)) { + return; + } + + output_index = (uint64_t)n * output_stride.n + + (uint64_t)c * output_stride.c + + (uint64_t)oh * output_stride.h + ow; + if (umul64_overflow(output_index, dest_size, + &output_offset) || + UINT64_MAX - dest_base < output_offset || + !k230_gnne_act0_write(dest_base + output_offset, 0, + fe->act0_compute.dest_datatype, + activated, &written_size)) { + return; + } + written += written_size; + } + } + } + } + + if (written) { + unsigned int head_size = MIN(written, sizeof(head)); + uint8_t head_buf[sizeof(head)] = {}; + + if (k230_gnne_dma_read_bytes(dest_base, head_buf, head_size)) { + head = ldq_le_p(head_buf); + } + } + + trace_k230_kpu_pdp0_compute(k230_kpu_name(s), pc, tcu_id, dest_logical, + written, head); + fe->pdp0_computes++; + fe->output_bytes += written; +} + +static void k230_gnne_l2_load_conf(K230GnneFrontend *fe, uint32_t word) +{ + fe->l2_load_conf.rstride_d = extract32(word, 7, 3); + fe->l2_load_conf.rstride_s = extract32(word, 10, 3); + fe->l2_load_conf.stride_d_valid = + k230_gnne_stride_value(fe, fe->l2_load_conf.rstride_d, + &fe->l2_load_conf.stride_d); + fe->l2_load_conf.stride_s_valid = + k230_gnne_stride_value(fe, fe->l2_load_conf.rstride_s, + &fe->l2_load_conf.stride_s); + fe->l2_load_conf.l2_datatype = extract32(word, 13, 2); + fe->l2_load_conf.ddr_datatype = extract32(word, 15, 3); + fe->l2_load_conf.valid = true; +} + +static void k230_gnne_l2_load_w_conf(K230GnneFrontend *fe, uint32_t word) +{ + bool valid; + + fe->l2_load_w_conf.rlen_compressed = + k230_gnne_gp(fe, extract32(word, 7, 5), &valid); + fe->l2_load_w_conf.valid = valid; + fe->l2_load_w_conf.rlen_decompressed = + k230_gnne_gp(fe, extract32(word, 12, 5), &valid); + fe->l2_load_w_conf.valid &= valid; + fe->l2_load_w_conf.l2_datatype = extract32(word, 17, 2); + fe->l2_load_w_conf.ddr_datatype = extract32(word, 19, 3); + fe->l2_load_w_conf.enable_decompress = extract32(word, 22, 1); +} + +static void k230_gnne_l2_store_conf(K230GnneFrontend *fe, uint32_t word) +{ + fe->l2_store_conf.rstride_d = extract32(word, 7, 3); + fe->l2_store_conf.rstride_s = extract32(word, 10, 3); + fe->l2_store_conf.stride_d_valid = + k230_gnne_stride_value(fe, fe->l2_store_conf.rstride_d, + &fe->l2_store_conf.stride_d); + fe->l2_store_conf.stride_s_valid = + k230_gnne_stride_value(fe, fe->l2_store_conf.rstride_s, + &fe->l2_store_conf.stride_s); + fe->l2_store_conf.l2_datatype = extract32(word, 13, 2); + fe->l2_store_conf.ddr_datatype = extract32(word, 15, 3); + fe->l2_store_conf.valid = true; +} + +static void k230_gnne_mfu_conf(K230GnneFrontend *fe, uint32_t word) +{ + K230GnneMfuAct1Conf *conf = &fe->mfu_act1; + K230GnneMfuPdp1Conf *pdp1 = &fe->mfu_pdp1; + K230GnneMfuTransposeConf *transpose = &fe->mfu_transpose; + K230GnneMfuAct1Deq *deq; + unsigned int sid; + bool valid; + + switch (extract32(word, 7, 5)) { + case 0x00: + transpose->rstride_d = extract32(word, 12, 3); + transpose->rstride_s = extract32(word, 15, 3); + transpose->l2_datatype = extract32(word, 18, 2); + transpose->permute = extract32(word, 20, 5); + transpose->valid = true; + break; + case 0x01: + pdp1->stride_w = extract32(word, 12, 5); + pdp1->stride_h = extract32(word, 17, 5); + pdp1->rstride_s = extract32(word, 22, 3); + pdp1->funct2 = extract32(word, 25, 2); + pdp1->rstride_d = extract32(word, 27, 3); + pdp1->conf1_valid = true; + break; + case 0x02: + pdp1->rcount_w = k230_gnne_gp(fe, extract32(word, 12, 5), + &valid); + pdp1->conf2_valid = valid; + pdp1->rcount_h = k230_gnne_gp(fe, extract32(word, 17, 5), + &valid); + pdp1->conf2_valid &= valid; + pdp1->rpe_h = k230_gnne_gp(fe, extract32(word, 22, 5), + &valid); + pdp1->conf2_valid &= valid; + pdp1->rpe_last_h = k230_gnne_gp(fe, extract32(word, 27, 5), + &valid); + pdp1->conf2_valid &= valid; + break; + case 0x03: + pdp1->rpe_channels = k230_gnne_gp(fe, extract32(word, 12, 5), + &valid); + pdp1->conf3_valid = valid; + pdp1->rpe_last_channels = k230_gnne_gp(fe, extract32(word, 17, 5), + &valid); + pdp1->conf3_valid &= valid; + pdp1->rpad_value = k230_gnne_gp(fe, extract32(word, 22, 5), + &valid); + pdp1->conf3_valid &= valid; + pdp1->sspad = extract32(word, 27, 3); + break; + case 0x04: + pdp1->rwindow_w = k230_gnne_gp(fe, extract32(word, 12, 5), + &valid); + pdp1->conf4_valid = valid; + pdp1->rwindow_h = k230_gnne_gp(fe, extract32(word, 17, 5), + &valid); + pdp1->conf4_valid &= valid; + pdp1->rscale = k230_gnne_gp(fe, extract32(word, 22, 5), + &valid); + pdp1->conf4_valid &= valid; + pdp1->enable_h2c = extract32(word, 27, 1); + pdp1->enable_bw = extract32(word, 28, 1); + break; + case 0x06: + pdp1->deq.rscale = extract32(word, 12, 5); + pdp1->deq.rbias = extract32(word, 17, 5); + pdp1->deq.quant_type = extract32(word, 22, 2); + pdp1->deq.rshift_bits = extract32(word, 24, 5); + pdp1->deq.valid = true; + break; + case 0x07: + pdp1->quant_rscale = extract32(word, 12, 5); + pdp1->quant_rbias = extract32(word, 17, 5); + pdp1->quant_type = extract32(word, 22, 2); + pdp1->quant_rshift_bits = extract32(word, 24, 5); + pdp1->quant_valid = true; + break; + case 0x08: + conf->rstride_s1 = extract32(word, 12, 3); + conf->rstride_s2 = extract32(word, 15, 3); + conf->rstride_d1 = extract32(word, 18, 3); + conf->stride_valid = true; + break; + case 0x09: + sid = extract32(word, 27, 1); + conf->src1[sid].rslice = extract32(word, 12, 5); + conf->src1[sid].rright_repeats = extract32(word, 17, 5); + conf->src1[sid].rslice_repeats = extract32(word, 22, 5); + conf->src1[sid].slice_loc = extract32(word, 28, 1); + conf->src1[sid].valid = true; + break; + case 0x0a: + sid = extract32(word, 20, 1); + conf->src2[sid].rleft_repeats = extract32(word, 12, 5); + conf->src2[sid].rshape = extract32(word, 17, 3); + conf->src2[sid].source_type = extract32(word, 21, 1); + conf->src2[sid].valid = true; + break; + case 0x0b: + conf->dest_rlen = extract32(word, 12, 5); + conf->dest_len = k230_gnne_gp(fe, conf->dest_rlen, &valid); + conf->dest_len_valid = valid && conf->dest_len; + conf->dest_rshape = extract32(word, 17, 3); + conf->dest_valid = true; + break; + case 0x0c: + sid = extract32(word, 24, 1); + deq = &conf->deq[sid]; + deq->rscale = extract32(word, 12, 5); + deq->rbias = extract32(word, 17, 5); + deq->quant_type = extract32(word, 22, 2); + deq->rshift_bits = extract32(word, 25, 5); + deq->valid = true; + break; + case 0x0d: + conf->quant_type = extract32(word, 12, 2); + conf->quant_rshift_bits = extract32(word, 14, 5); + conf->quant_valid = true; + break; + case 0x0e: + conf->funct4 = extract32(word, 12, 4); + conf->is_by_channel = extract32(word, 16, 1); + conf->is_16_segments = extract32(word, 17, 1); + conf->act_valid = true; + break; + default: + break; + } +} + +static void k230_gnne_dm_conf(K230GnneFrontend *fe, uint32_t word) +{ + switch (extract32(word, 13, 4)) { + case 0: + fe->dm_load_l1_conf.rstride_s = extract32(word, 17, 3); + fe->dm_load_l1_conf.datatype = extract32(word, 20, 2); + fe->dm_load_l1_conf.l1_type = extract32(word, 22, 2); + fe->dm_load_l1_conf.valid = true; + break; + case 1: + fe->dm_load_w_conf.kernel_h = extract32(word, 17, 5); + fe->dm_load_w_conf.kernel_w = extract32(word, 22, 5); + fe->dm_load_w_conf.rstride_oc = extract32(word, 27, 5); + fe->dm_load_w_conf.valid = true; + break; + case 2: + fe->dm_load_w_conf.quant_type = extract32(word, 17, 2); + fe->dm_load_w_conf.valid = true; + break; + case 5: + fe->dm_load_w_conf.rgroups = extract32(word, 17, 5); + fe->dm_load_w_conf.rgoc = extract32(word, 22, 5); + fe->dm_load_w_conf.valid = true; + break; + default: + break; + } +} + +static void k230_gnne_pu_conf(K230GnneFrontend *fe, uint32_t word) +{ + K230GnnePuConf *conf = &fe->pu_conf; + + switch (extract32(word, 13, 4)) { + case 0: + conf->stride_w = extract32(word, 17, 5); + conf->stride_h = extract32(word, 22, 5); + conf->rstride_s = extract32(word, 27, 3); + conf->fetch1_valid = true; + break; + case 1: + conf->rgic = extract32(word, 17, 5); + conf->rgic_last = extract32(word, 22, 5); + conf->fetch2_valid = true; + break; + case 2: + conf->raddr_s = extract32(word, 17, 5); + conf->rgroups = extract32(word, 22, 5); + conf->rshape = extract32(word, 27, 3); + conf->fetch3_valid = true; + break; + case 3: + conf->rpad_value = extract32(word, 17, 5); + conf->sspad = extract32(word, 27, 3); + conf->fetch4_valid = true; + break; + case 4: + conf->ric = extract32(word, 17, 5); + conf->rbx = extract32(word, 22, 5); + conf->quant_type = extract32(word, 27, 2); + conf->fetch_deq_valid = true; + break; + case 5: + conf->kernel_h = extract32(word, 17, 5); + conf->kernel_w = extract32(word, 22, 5); + conf->w_valid = true; + break; + case 6: + conf->rgoc = extract32(word, 17, 5); + conf->rgoc_last = extract32(word, 22, 5); + conf->rstride_d = extract32(word, 27, 3); + conf->output_stride_valid = + k230_gnne_stride_value(fe, conf->rstride_d, + &conf->output_stride); + conf->of1_valid = true; + break; + case 7: + conf->raddr_d = extract32(word, 17, 5); + conf->rshape_d = extract32(word, 27, 3); + conf->output_shape_valid = false; + if (conf->rshape_d < K230_GNNE_SHAPE_COUNT && + fe->shape[conf->rshape_d].valid) { + conf->output_shape = fe->shape[conf->rshape_d]; + conf->output_shape_valid = true; + } + conf->of2_valid = true; + break; + case 8: + conf->load_psum = extract32(word, 17, 1); + conf->clr_psum = extract32(word, 18, 1); + conf->dest_target = extract32(word, 19, 1); + conf->release_if = extract32(word, 20, 1); + conf->mode = extract32(word, 21, 1); + conf->compute_valid = true; + break; + default: + break; + } +} + +static void k230_gnne_pdp0_conf(K230GnneFrontend *fe, uint32_t word) +{ + K230GnnePdp0Conf *conf = &fe->pdp0_conf; + + switch (extract32(word, 13, 4)) { + case 0: + conf->mode = extract32(word, 17, 3); + conf->mode_valid = true; + break; + case 1: + conf->stride_w = extract32(word, 17, 5); + conf->stride_h = extract32(word, 22, 5); + conf->fetch1_valid = true; + break; + case 2: + conf->rgic = extract32(word, 17, 5); + conf->rgic_last = extract32(word, 22, 5); + conf->fetch2_valid = true; + break; + case 3: + conf->rshape = extract32(word, 27, 3); + conf->input_shape_valid = false; + if (conf->rshape < K230_GNNE_SHAPE_COUNT && + fe->shape[conf->rshape].valid) { + conf->input_shape = fe->shape[conf->rshape]; + conf->input_shape_valid = true; + } + conf->fetch3_valid = true; + break; + case 4: + conf->rpad_value = extract32(word, 17, 5); + conf->sspad = extract32(word, 22, 3); + conf->fetch4_valid = true; + break; + case 5: + conf->rbx = extract32(word, 17, 5); + conf->quant_type = extract32(word, 22, 2); + conf->fetch_deq_valid = true; + break; + case 6: + conf->kernel_h = extract32(word, 17, 5); + conf->kernel_w = extract32(word, 22, 5); + conf->w_valid = true; + break; + case 7: + conf->rstride_d = extract32(word, 17, 3); + conf->rshape_d = extract32(word, 20, 3); + conf->output_shape_valid = false; + if (conf->rshape_d < K230_GNNE_SHAPE_COUNT && + fe->shape[conf->rshape_d].valid) { + conf->output_shape = fe->shape[conf->rshape_d]; + conf->output_shape_valid = true; + } + conf->output_stride_valid = + k230_gnne_stride_value(fe, conf->rstride_d, &conf->output_stride); + conf->of_valid = true; + break; + default: + break; + } +} + +static void k230_gnne_dm_op(K230GnneFrontend *fe, uint32_t word) +{ + switch (word & 0x7f) { + case 0x52: + fe->dm_load_l1.raddr_s = extract32(word, 13, 5); + fe->dm_load_l1.rshape = extract32(word, 23, 3); + fe->dm_load_l1.valid = true; + break; + case 0x54: + fe->dm_load_w.raddr_s = extract32(word, 13, 5); + fe->dm_load_w.raddr_bw = extract32(word, 18, 5); + fe->dm_load_w.r_iochannels = extract32(word, 23, 3); + fe->dm_load_w.valid = true; + break; + case 0x56: + fe->dm_load_act0.raddr_s = extract32(word, 13, 5); + fe->dm_load_act0.rlen = extract32(word, 18, 5); + fe->dm_load_act0.is_by_channel = extract32(word, 24, 1); + fe->dm_load_act0.valid = true; + break; + case 0x58: + fe->dm_store_of.raddr_d = extract32(word, 13, 5); + fe->dm_store_of.rshape = extract32(word, 18, 3); + fe->dm_store_of.valid = true; + break; + default: + break; + } +} + +static void k230_gnne_act0_conf(K230GnneFrontend *fe, uint32_t word) +{ + fe->act0_conf.rshape = extract32(word, 17, 3); + fe->act0_conf.rshift_bits = extract32(word, 20, 5); + fe->act0_conf.valid = true; +} + +static void k230_gnne_act0_compute(K230GnneFrontend *fe, uint32_t word) +{ + fe->act0_compute.raddr_d = extract32(word, 12, 5); + fe->act0_compute.dest_datatype = extract32(word, 23, 2); + fe->act0_compute.is_by_channel = extract32(word, 25, 1); + fe->act0_compute.valid = true; +} + +static void k230_gnne_step(K230KpuState *s, K230GnneFrontend *fe, + uint32_t word, uint64_t pc) +{ + uint32_t opcode = word & 0x7f; + uint32_t rd; + uint32_t rs; + uint32_t rs1; + uint32_t rs2; + uint32_t value; + uint32_t left; + uint32_t right; + bool left_valid; + bool right_valid; + bool valid; + bool direct_physical; + bool left_direct; + bool right_direct; + uint32_t arg_slot; + uint32_t arg_base; + uint32_t load_addr; + uint32_t op; + + fe->current_pc = pc; + fe->instructions++; + + switch (opcode) { + case 0x02: + rd = extract32(word, 7, 5); + k230_gnne_set_gp(fe, rd, extract32(word, 12, 20) << 12, true); + break; + case 0x04: + rd = extract32(word, 7, 5); + value = pc + (extract32(word, 12, 20) << 12); + k230_gnne_set_gp(fe, rd, value, true); + break; + case 0x06: + rd = extract32(word, 7, 5); + rs = extract32(word, 12, 5); + left = k230_gnne_gp(fe, rs, &left_valid); + value = left + sextract32(word, 20, 12); + load_addr = value; + op = extract32(word, 17, 3); + direct_physical = false; + switch (op) { + case 0: + valid = left_valid && + k230_gnne_read_scalar(fe, value, 4, false, pc, &value, + &direct_physical); + break; + case 1: + valid = left_valid && + k230_gnne_read_scalar(fe, value, 2, true, pc, &value, + &direct_physical); + break; + case 2: + valid = left_valid && + k230_gnne_read_scalar(fe, value, 2, false, pc, &value, + &direct_physical); + break; + case 3: + valid = left_valid && + k230_gnne_read_scalar(fe, value, 1, true, pc, &value, + &direct_physical); + break; + case 4: + valid = left_valid && + k230_gnne_read_scalar(fe, value, 1, false, pc, &value, + &direct_physical); + break; + default: + valid = false; + break; + } + k230_gnne_set_gp_ex(fe, rd, value, valid, direct_physical); + if (valid && direct_physical && op == 0 && + k230_gnne_runtime_arg_slot_for_encoded(fe, load_addr, &arg_slot)) { + k230_gnne_set_gp_runtime_arg(fe, rd, arg_slot, value, value); + } + break; + case 0x0c: + rd = extract32(word, 7, 5); + rs1 = extract32(word, 12, 5); + rs2 = extract32(word, 22, 5); + left = k230_gnne_gp(fe, rs1, &left_valid); + right = k230_gnne_gp(fe, rs2, &right_valid); + left_direct = k230_gnne_gp_direct_physical(fe, rs1); + right_direct = k230_gnne_gp_direct_physical(fe, rs2); + direct_physical = false; + valid = left_valid && right_valid; + op = extract32(word, 17, 5); + arg_slot = UINT32_MAX; + arg_base = 0; + switch (op) { + case 0: + value = left + right; + direct_physical = left_direct ^ right_direct; + if (direct_physical) { + if (left_direct && + k230_gnne_gp_runtime_arg(fe, rs1, &arg_slot, + &arg_base, NULL)) { + break; + } + if (right_direct) { + k230_gnne_gp_runtime_arg(fe, rs2, &arg_slot, + &arg_base, NULL); + } + } + break; + case 1: + value = left - right; + direct_physical = left_direct && !right_direct; + if (direct_physical) { + k230_gnne_gp_runtime_arg(fe, rs1, &arg_slot, + &arg_base, NULL); + } + break; + case 2: + value = left * right; + break; + default: + valid = false; + value = 0; + break; + } + k230_gnne_set_gp_ex(fe, rd, value, valid, direct_physical); + if (arg_slot != UINT32_MAX) { + k230_gnne_set_gp_runtime_arg(fe, rd, arg_slot, arg_base, value); + } + break; + case 0x0e: + if (extract32(word, 17, 3) != 0) { + break; + } + rd = extract32(word, 7, 5); + rs = extract32(word, 12, 5); + left = k230_gnne_gp(fe, rs, &left_valid); + value = left + sextract32(word, 20, 12); + left_direct = k230_gnne_gp_direct_physical(fe, rs); + k230_gnne_set_gp_ex(fe, rd, value, left_valid, + left_direct); + if (left_valid && left_direct && + k230_gnne_gp_runtime_arg(fe, rs, &arg_slot, &arg_base, NULL)) { + k230_gnne_set_gp_runtime_arg(fe, rd, arg_slot, arg_base, value); + } + break; + case 0x40: + if (extract32(word, 27, 3) >= K230_GNNE_SHAPE_COUNT) { + break; + } + rs = extract32(word, 27, 3); + fe->shape[rs].n = k230_gnne_gp(fe, extract32(word, 7, 5), + &valid); + fe->shape[rs].valid = valid; + fe->shape[rs].c = k230_gnne_gp(fe, extract32(word, 12, 5), + &valid); + fe->shape[rs].valid &= valid; + fe->shape[rs].h = k230_gnne_gp(fe, extract32(word, 17, 5), + &valid); + fe->shape[rs].valid &= valid; + fe->shape[rs].w = k230_gnne_gp(fe, extract32(word, 22, 5), + &valid); + fe->shape[rs].valid &= valid; + break; + case 0x42: + if (extract32(word, 27, 3) >= K230_GNNE_SHAPE_COUNT) { + break; + } + rs = extract32(word, 27, 3); + fe->stride[rs].n = k230_gnne_gp(fe, extract32(word, 7, 5), + &valid); + fe->stride[rs].valid = valid; + fe->stride[rs].c = k230_gnne_gp(fe, extract32(word, 12, 5), + &valid); + fe->stride[rs].valid &= valid; + fe->stride[rs].h = k230_gnne_gp(fe, extract32(word, 17, 5), + &valid); + fe->stride[rs].valid &= valid; + break; + case 0x44: + rs = extract32(word, 17, 4); + if (rs >= K230_GNNE_MMU_COUNT) { + break; + } + fe->mmu[rs].start = k230_gnne_gp(fe, extract32(word, 7, 5), + &valid); + fe->mmu[rs].valid = valid; + fe->mmu[rs].depth = k230_gnne_gp(fe, extract32(word, 12, 5), + &valid); + fe->mmu[rs].valid &= valid; + break; + case 0x46: + k230_gnne_l2_load_conf(fe, word); + break; + case 0x48: + k230_gnne_l2_load_w_conf(fe, word); + break; + case 0x4a: + k230_gnne_l2_store_conf(fe, word); + break; + case 0x4c: + k230_gnne_l2_load(s, fe, word, pc); + break; + case 0x4d: + k230_gnne_pu_compute(s, fe, word, pc); + break; + case 0x4e: + k230_gnne_l2_store(s, fe, word, pc); + break; + case 0x4f: + k230_gnne_pdp0_compute(s, fe, word, pc); + break; + case 0x50: + k230_gnne_dm_conf(fe, word); + break; + case 0x51: + fe->ai2d_computes++; + trace_k230_kpu_ai2d_compute(k230_kpu_name(s), pc, word & 0xffff); + break; + case 0x52: + case 0x54: + case 0x56: + case 0x58: + k230_gnne_dm_op(fe, word); + break; + case 0x57: + k230_gnne_l2_load_w(s, fe, word, pc); + break; + case 0x5a: + k230_gnne_pu_conf(fe, word); + break; + case 0x5e: + k230_gnne_pdp0_conf(fe, word); + break; + case 0x60: + k230_gnne_act0_conf(fe, word); + break; + case 0x62: + k230_gnne_mfu_conf(fe, word); + break; + case 0x68: + k230_gnne_mfu_transpose(s, fe, word, pc); + break; + case 0x6a: + k230_gnne_mfu_pdp1(s, fe, word, pc); + break; + case 0x72: + k230_gnne_mfu_act1(s, fe, word, pc); + break; + case 0x74: + k230_gnne_act0_compute(fe, word); + break; + default: + if (!k230_gnne_opcode_known(word)) { + fe->unknown++; + } + break; + } +} + +static void k230_kpu_execute_gnne(K230KpuState *s) +{ + uint64_t command_start = k230_kpu_command_addr(s, + K230_KPU_COMMAND_START); + uint64_t command_end = k230_kpu_command_addr(s, K230_KPU_COMMAND_END); + uint64_t size; + K230GnneFrontend fe; + g_autofree uint8_t *buf = NULL; + + if (!command_start || !command_end || command_end <= command_start) { + return; + } + + size = command_end - command_start; + if (size > K230_GNNE_MAX_COMMAND_SIZE) { + qemu_log_mask(LOG_GUEST_ERROR, + "k230 KPU command stream too large: 0x%" PRIx64 + " bytes\n", size); + return; + } + + buf = g_malloc(size); + if (dma_memory_read(&address_space_memory, command_start, buf, size, + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, + "k230 KPU failed to read command stream 0x%" PRIx64 + "..0x%" PRIx64 "\n", command_start, command_end); + return; + } + + k230_gnne_frontend_init(s, &fe, command_start); + k230_gnne_active_fe = &fe; + for (uint64_t pc = 0; pc < size; ) { + uint32_t opcode; + uint32_t word = 0; + unsigned int length; + + if (pc + sizeof(uint16_t) > size) { + break; + } + opcode = buf[pc] & 0x7f; + length = k230_gnne_is_short(opcode) ? sizeof(uint16_t) : + sizeof(uint32_t); + if (pc + length > size) { + break; + } + if (length == sizeof(uint16_t)) { + word = lduw_le_p(buf + pc); + } else { + word = ldl_le_p(buf + pc); + } + k230_gnne_step(s, &fe, word, pc); + pc += length; + } + + trace_k230_kpu_gnne_summary(k230_kpu_name(s), fe.instructions, + fe.l2_loads, fe.l2_load_ws, fe.l2_stores, + fe.input_bytes + fe.output_bytes, + fe.unknown); + trace_k230_kpu_gnne_compute_summary(k230_kpu_name(s), fe.mfu_act1s, + fe.mfu_pdp1s, fe.mfu_transposes, + fe.ai2d_computes, fe.pu_computes, + fe.pdp0_computes); + k230_gnne_active_fe = NULL; + k230_gnne_frontend_destroy(&fe); +} + +static void k230_kpu_completion_zero(K230KpuState *s) +{ + uint64_t command_start = k230_kpu_command_addr(s, + K230_KPU_COMMAND_START); + uint64_t command_end = k230_kpu_command_addr(s, K230_KPU_COMMAND_END); + uint64_t zero_end = K230_KPU_FAKE_OUTPUT_BASE + + K230_KPU_FAKE_OUTPUT_SIZE; + uint64_t pages = K230_KPU_FAKE_OUTPUT_SIZE / K230_KPU_PAGE_SIZE; + long page_count = pages; + g_autofree unsigned long *seen_pages = NULL; + + if (!command_start || !command_end || command_end <= command_start || + zero_end < K230_KPU_FAKE_OUTPUT_BASE || !pages) { + return; + } + + seen_pages = bitmap_new(page_count); + for (uint64_t addr = command_start; addr + sizeof(uint32_t) <= command_end; + addr += sizeof(uint32_t)) { + uint32_t raw; + uint32_t value; + uint64_t page; + uint64_t page_index; + + if (dma_memory_read(&address_space_memory, addr, &raw, sizeof(raw), + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + break; + } + + value = ldl_le_p(&raw); + page = value & ~(K230_KPU_PAGE_SIZE - 1); + if (page < K230_KPU_FAKE_OUTPUT_BASE || + page + K230_KPU_PAGE_SIZE > zero_end) { + continue; + } + + page_index = (page - K230_KPU_FAKE_OUTPUT_BASE) / K230_KPU_PAGE_SIZE; + if (page_index >= pages || + test_and_set_bit((long)page_index, seen_pages)) { + continue; + } + + trace_k230_kpu_completion_zero_page(k230_kpu_name(s), page, value); + dma_memory_set(&address_space_memory, page, 0, K230_KPU_PAGE_SIZE, + MEMTXATTRS_UNSPECIFIED); + } +} + +static void k230_kpu_set_irq(K230KpuState *s, bool level) +{ + s->irq_level = level; + trace_k230_kpu_irq(k230_kpu_name(s), level); + qemu_set_irq(s->irq, level); +} + +static void k230_kpu_complete(K230KpuState *s) +{ + s->busy = false; + k230_kpu_execute_gnne(s); + k230_kpu_completion_zero(s); + k230_kpu_writeq_regs(s, K230_KPU_STATUS, K230_KPU_DONE); + k230_kpu_set_irq(s, true); +} + +static void k230_kpu_complete_timer(void *opaque) +{ + K230KpuState *s = K230_KPU(opaque); + + if (s->busy) { + k230_kpu_complete(s); + } +} + +static void k230_kpu_start(K230KpuState *s) +{ + uint64_t command_start = k230_kpu_command_addr(s, + K230_KPU_COMMAND_START); + uint64_t command_end = k230_kpu_command_addr(s, K230_KPU_COMMAND_END); + + if (s->busy) { + return; + } + + k230_kpu_writeq_regs(s, K230_KPU_STATUS, 0); + s->busy = true; + trace_k230_kpu_start(k230_kpu_name(s), command_start, command_end, + k230_kpu_readl_regs(s, K230_KPU_COMMAND_HI)); + timer_mod(&s->complete_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + K230_KPU_COMPLETE_DELAY_NS); +} + +static uint64_t k230_kpu_read(void *opaque, hwaddr addr, unsigned int size) +{ + K230KpuState *s = K230_KPU(opaque); + uint64_t val; + + if (!k230_kpu_range_ok(addr, size)) { + qemu_log_mask(LOG_GUEST_ERROR, + "k230 KPU bad read offset 0x%" HWADDR_PRIx + " size %u\n", addr, size); + return 0; + } + + val = k230_kpu_read_bytes(s->regs, addr, size); + trace_k230_kpu_read(k230_kpu_name(s), addr, val, size); + return val; +} + +static void k230_kpu_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230KpuState *s = K230_KPU(opaque); + + if (!k230_kpu_range_ok(addr, size)) { + qemu_log_mask(LOG_GUEST_ERROR, + "k230 KPU bad write offset 0x%" HWADDR_PRIx + " value 0x%" PRIx64 " size %u\n", addr, val, size); + return; + } + + k230_kpu_write_bytes(s->regs, addr, val, size); + trace_k230_kpu_write(k230_kpu_name(s), addr, val, size); + + if (k230_kpu_access_hits(addr, size, K230_KPU_CONTROL)) { + if (k230_kpu_control_has(val, K230_KPU_CONTROL_CLEAR)) { + timer_del(&s->complete_timer); + s->busy = false; + k230_kpu_writeq_regs(s, K230_KPU_STATUS, 0); + k230_kpu_set_irq(s, false); + } + + if (k230_kpu_control_has(val, K230_KPU_CONTROL_START)) { + k230_kpu_start(s); + } + } +} + +static const MemoryRegionOps k230_kpu_ops = { + .read = k230_kpu_read, + .write = k230_kpu_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 8, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + .unaligned = true, + }, +}; + +static void k230_kpu_reset(DeviceState *dev) +{ + K230KpuState *s = K230_KPU(dev); + + memset(s->regs, 0, sizeof(s->regs)); + timer_del(&s->complete_timer); + s->busy = false; + k230_kpu_clear_rdata_shadow(s); + k230_kpu_set_irq(s, false); +} + +static const VMStateDescription vmstate_k230_kpu = { + .name = TYPE_K230_KPU, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_BOOL(busy, K230KpuState), + VMSTATE_BOOL(irq_level, K230KpuState), + VMSTATE_TIMER(complete_timer, K230KpuState), + VMSTATE_UINT8_ARRAY(regs, K230KpuState, K230_KPU_SIZE), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_kpu_realize(DeviceState *dev, Error **errp) +{ + K230KpuState *s = K230_KPU(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_kpu_ops, s, + TYPE_K230_KPU, K230_KPU_SIZE); + timer_init_ns(&s->complete_timer, QEMU_CLOCK_VIRTUAL, + k230_kpu_complete_timer, s); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq); +} + +static void k230_kpu_unrealize(DeviceState *dev) +{ + K230KpuState *s = K230_KPU(dev); + + k230_kpu_clear_rdata_shadow(s); +} + +static void k230_kpu_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_kpu_realize; + dc->unrealize = k230_kpu_unrealize; + device_class_set_legacy_reset(dc, k230_kpu_reset); + dc->vmsd = &vmstate_k230_kpu; + dc->desc = "K230 KPU/GNNE engine"; +} + +static const TypeInfo k230_kpu_type_info = { + .name = TYPE_K230_KPU, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230KpuState), + .class_init = k230_kpu_class_init, +}; + +static void k230_kpu_register_types(void) +{ + type_register_static(&k230_kpu_type_info); +} + +type_init(k230_kpu_register_types) diff --git a/hw/misc/k230_nonai_2d.c b/hw/misc/k230_nonai_2d.c new file mode 100644 index 0000000000000..2171b9434ff87 --- /dev/null +++ b/hw/misc/k230_nonai_2d.c @@ -0,0 +1,437 @@ +/* + * K230 non-AI 2D engine + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/units.h" +#include "hw/core/irq.h" +#include "hw/misc/k230_nonai_2d.h" +#include "migration/vmstate.h" +#include "system/dma.h" + +#define K230_NONAI_2D_STREAM_COUNT 3 +#define K230_NONAI_2D_STREAM_STRIDE 0x400 + +#define K230_NONAI_2D_SRC_SIZE 0x000 +#define K230_NONAI_2D_SRC_CH0_ADDR 0x004 +#define K230_NONAI_2D_SRC_CH1_ADDR 0x008 +#define K230_NONAI_2D_SRC_CH2_ADDR 0x00c +#define K230_NONAI_2D_SRC_STRIDE01 0x010 +#define K230_NONAI_2D_SRC_STRIDE2 0x014 +#define K230_NONAI_2D_FMT 0x018 +#define K230_NONAI_2D_DST_CH0_ADDR 0x13c +#define K230_NONAI_2D_DST_CH1_ADDR 0x140 +#define K230_NONAI_2D_DST_CH2_ADDR 0x144 +#define K230_NONAI_2D_DST_STRIDE01 0x148 +#define K230_NONAI_2D_DST_STRIDE2 0x14c + +#define K230_NONAI_2D_MAIN_CFG 0x3a0 +#define K230_NONAI_2D_INTR_STATUS 0x3a8 +#define K230_NONAI_2D_INTR_CLEAR 0x3ac +#define K230_NONAI_2D_CALC_EN BIT(0) +#define K230_NONAI_2D_STREAM_ID_SHIFT 2 +#define K230_NONAI_2D_STREAM_ID_LEN 2 + +#define K230_NONAI_2D_FMT_NV12 0 +#define K230_NONAI_2D_FMT_NV21 1 +#define K230_NONAI_2D_FMT_I420 2 +#define K230_NONAI_2D_FMT_ARGB8888 4 +#define K230_NONAI_2D_FMT_ARGB4444 5 +#define K230_NONAI_2D_FMT_ARGB1555 6 +#define K230_NONAI_2D_FMT_XRGB8888 7 +#define K230_NONAI_2D_FMT_XRGB4444 8 +#define K230_NONAI_2D_FMT_XRGB1555 9 +#define K230_NONAI_2D_FMT_BGRA8888 10 +#define K230_NONAI_2D_FMT_BGRA4444 11 +#define K230_NONAI_2D_FMT_BGRA5551 12 +#define K230_NONAI_2D_FMT_BGRX8888 13 +#define K230_NONAI_2D_FMT_BGRX4444 14 +#define K230_NONAI_2D_FMT_BGRX5551 15 +#define K230_NONAI_2D_FMT_RGB888 16 +#define K230_NONAI_2D_FMT_BGR888 17 +#define K230_NONAI_2D_FMT_RGB565 18 +#define K230_NONAI_2D_FMT_BGR565 19 +#define K230_NONAI_2D_FMT_SEPARATE_RGB 20 + +#define K230_NONAI_2D_MAX_COPY (64 * MiB) + +static uint64_t k230_nonai_2d_read_bytes(uint8_t *regs, hwaddr addr, + unsigned int size) +{ + uint64_t val = 0; + + for (int i = 0; i < size; i++) { + val |= (uint64_t)regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_nonai_2d_write_bytes(uint8_t *regs, hwaddr addr, + uint64_t val, unsigned int size) +{ + for (int i = 0; i < size; i++) { + regs[addr + i] = val >> (i * 8); + } +} + +static bool k230_nonai_2d_range_ok(hwaddr addr, unsigned int size) +{ + return addr <= K230_NONAI_2D_SIZE && size <= K230_NONAI_2D_SIZE - addr; +} + +static bool k230_nonai_2d_access_hits(hwaddr addr, unsigned int size, + hwaddr offset) +{ + return addr <= offset && offset < addr + size; +} + +static uint32_t k230_nonai_2d_readl_regs(K230NonAI2DState *s, hwaddr addr) +{ + return ldl_le_p(s->regs + addr); +} + +static void k230_nonai_2d_writel_regs(K230NonAI2DState *s, hwaddr addr, + uint32_t val) +{ + stl_le_p(s->regs + addr, val); +} + +static bool k230_nonai_2d_dma_copy(hwaddr src, hwaddr dst, size_t len) +{ + g_autofree uint8_t *buf = NULL; + + if (!src || !dst || !len || len > K230_NONAI_2D_MAX_COPY) { + return false; + } + + buf = g_malloc(len); + if (dma_memory_read(&address_space_memory, src, buf, len, + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + return false; + } + + return dma_memory_write(&address_space_memory, dst, buf, len, + MEMTXATTRS_UNSPECIFIED) == MEMTX_OK; +} + +static bool k230_nonai_2d_copy_rows(hwaddr src, hwaddr dst, size_t row_bytes, + size_t rows, size_t src_stride, + size_t dst_stride) +{ + g_autofree uint8_t *row = NULL; + + if (!row_bytes || !rows) { + return true; + } + + src_stride = src_stride ? src_stride : row_bytes; + dst_stride = dst_stride ? dst_stride : row_bytes; + if (row_bytes > src_stride || row_bytes > dst_stride || + rows > K230_NONAI_2D_MAX_COPY / row_bytes) { + return false; + } + + if (src_stride == row_bytes && dst_stride == row_bytes) { + return k230_nonai_2d_dma_copy(src, dst, row_bytes * rows); + } + + row = g_malloc(row_bytes); + for (size_t y = 0; y < rows; y++) { + if (dma_memory_read(&address_space_memory, src + y * src_stride, + row, row_bytes, MEMTXATTRS_UNSPECIFIED) != + MEMTX_OK || + dma_memory_write(&address_space_memory, dst + y * dst_stride, + row, row_bytes, MEMTXATTRS_UNSPECIFIED) != + MEMTX_OK) { + return false; + } + } + + return true; +} + +static uint16_t k230_nonai_2d_stride0(uint32_t val) +{ + return extract32(val, 0, 16); +} + +static uint16_t k230_nonai_2d_stride1(uint32_t val) +{ + return extract32(val, 16, 16); +} + +static bool k230_nonai_2d_copy_plane(K230NonAI2DState *s, hwaddr base, + hwaddr src_offset, hwaddr dst_offset, + size_t row_bytes, size_t rows, + size_t src_stride, size_t dst_stride) +{ + uint32_t src = k230_nonai_2d_readl_regs(s, base + src_offset); + uint32_t dst = k230_nonai_2d_readl_regs(s, base + dst_offset); + + return k230_nonai_2d_copy_rows(src, dst, row_bytes, rows, + src_stride, dst_stride); +} + +static bool k230_nonai_2d_copy_packed(K230NonAI2DState *s, hwaddr base, + uint32_t width, uint32_t height, + unsigned int bytes_per_pixel) +{ + uint32_t src_stride01 = k230_nonai_2d_readl_regs(s, base + + K230_NONAI_2D_SRC_STRIDE01); + uint32_t dst_stride01 = k230_nonai_2d_readl_regs(s, base + + K230_NONAI_2D_DST_STRIDE01); + size_t row_bytes = width * bytes_per_pixel; + + return k230_nonai_2d_copy_plane(s, base, K230_NONAI_2D_SRC_CH0_ADDR, + K230_NONAI_2D_DST_CH0_ADDR, row_bytes, + height, k230_nonai_2d_stride0(src_stride01), + k230_nonai_2d_stride0(dst_stride01)); +} + +static bool k230_nonai_2d_copy_yuv420(K230NonAI2DState *s, hwaddr base, + uint32_t width, uint32_t height, + uint32_t fmt) +{ + uint32_t src_stride01 = k230_nonai_2d_readl_regs(s, base + + K230_NONAI_2D_SRC_STRIDE01); + uint32_t dst_stride01 = k230_nonai_2d_readl_regs(s, base + + K230_NONAI_2D_DST_STRIDE01); + uint32_t src_stride2 = k230_nonai_2d_readl_regs(s, base + + K230_NONAI_2D_SRC_STRIDE2); + uint32_t dst_stride2 = k230_nonai_2d_readl_regs(s, base + + K230_NONAI_2D_DST_STRIDE2); + + if (!k230_nonai_2d_copy_plane(s, base, K230_NONAI_2D_SRC_CH0_ADDR, + K230_NONAI_2D_DST_CH0_ADDR, width, height, + k230_nonai_2d_stride0(src_stride01), + k230_nonai_2d_stride0(dst_stride01))) { + return false; + } + + if (fmt == K230_NONAI_2D_FMT_NV12 || fmt == K230_NONAI_2D_FMT_NV21) { + return k230_nonai_2d_copy_plane(s, base, K230_NONAI_2D_SRC_CH1_ADDR, + K230_NONAI_2D_DST_CH1_ADDR, width, + height / 2, + k230_nonai_2d_stride1(src_stride01), + k230_nonai_2d_stride1(dst_stride01)); + } + + return k230_nonai_2d_copy_plane(s, base, K230_NONAI_2D_SRC_CH1_ADDR, + K230_NONAI_2D_DST_CH1_ADDR, width / 2, + height / 2, + k230_nonai_2d_stride1(src_stride01), + k230_nonai_2d_stride1(dst_stride01)) && + k230_nonai_2d_copy_plane(s, base, K230_NONAI_2D_SRC_CH2_ADDR, + K230_NONAI_2D_DST_CH2_ADDR, width / 2, + height / 2, + k230_nonai_2d_stride0(src_stride2), + k230_nonai_2d_stride0(dst_stride2)); +} + +static bool k230_nonai_2d_copy_separate_rgb(K230NonAI2DState *s, hwaddr base, + uint32_t width, uint32_t height) +{ + uint32_t src0 = k230_nonai_2d_readl_regs(s, base + + K230_NONAI_2D_SRC_CH0_ADDR); + uint32_t src1 = k230_nonai_2d_readl_regs(s, base + + K230_NONAI_2D_SRC_CH1_ADDR); + uint32_t src2 = k230_nonai_2d_readl_regs(s, base + + K230_NONAI_2D_SRC_CH2_ADDR); + uint32_t dst0 = k230_nonai_2d_readl_regs(s, base + + K230_NONAI_2D_DST_CH0_ADDR); + uint32_t dst1 = k230_nonai_2d_readl_regs(s, base + + K230_NONAI_2D_DST_CH1_ADDR); + uint32_t dst2 = k230_nonai_2d_readl_regs(s, base + + K230_NONAI_2D_DST_CH2_ADDR); + size_t len = (size_t)width * height; + + return k230_nonai_2d_dma_copy(src0, dst0, len) && + k230_nonai_2d_dma_copy(src1, dst1, len) && + k230_nonai_2d_dma_copy(src2, dst2, len); +} + +static bool k230_nonai_2d_copy_stream(K230NonAI2DState *s, unsigned int stream) +{ + hwaddr base = stream * K230_NONAI_2D_STREAM_STRIDE; + uint32_t size = k230_nonai_2d_readl_regs(s, base + + K230_NONAI_2D_SRC_SIZE); + uint32_t width = extract32(size, 0, 16); + uint32_t height = extract32(size, 16, 16); + uint32_t fmt = k230_nonai_2d_readl_regs(s, base + K230_NONAI_2D_FMT) & + 0xff; + + if (!width || !height) { + return false; + } + + switch (fmt) { + case K230_NONAI_2D_FMT_NV12: + case K230_NONAI_2D_FMT_NV21: + case K230_NONAI_2D_FMT_I420: + return k230_nonai_2d_copy_yuv420(s, base, width, height, fmt); + case K230_NONAI_2D_FMT_ARGB8888: + case K230_NONAI_2D_FMT_XRGB8888: + case K230_NONAI_2D_FMT_BGRA8888: + case K230_NONAI_2D_FMT_BGRX8888: + return k230_nonai_2d_copy_packed(s, base, width, height, 4); + case K230_NONAI_2D_FMT_RGB888: + case K230_NONAI_2D_FMT_BGR888: + return k230_nonai_2d_copy_packed(s, base, width, height, 3); + case K230_NONAI_2D_FMT_ARGB4444: + case K230_NONAI_2D_FMT_ARGB1555: + case K230_NONAI_2D_FMT_XRGB4444: + case K230_NONAI_2D_FMT_XRGB1555: + case K230_NONAI_2D_FMT_BGRA4444: + case K230_NONAI_2D_FMT_BGRA5551: + case K230_NONAI_2D_FMT_BGRX4444: + case K230_NONAI_2D_FMT_BGRX5551: + case K230_NONAI_2D_FMT_RGB565: + case K230_NONAI_2D_FMT_BGR565: + return k230_nonai_2d_copy_packed(s, base, width, height, 2); + case K230_NONAI_2D_FMT_SEPARATE_RGB: + return k230_nonai_2d_copy_separate_rgb(s, base, width, height); + default: + return false; + } +} + +static void k230_nonai_2d_raise_irq(K230NonAI2DState *s) +{ + k230_nonai_2d_writel_regs(s, K230_NONAI_2D_INTR_STATUS, 1); + s->irq_level = true; + qemu_set_irq(s->irq, 1); +} + +static void k230_nonai_2d_complete(K230NonAI2DState *s, uint32_t cfg) +{ + unsigned int stream = extract32(cfg, K230_NONAI_2D_STREAM_ID_SHIFT, + K230_NONAI_2D_STREAM_ID_LEN); + + if (stream < K230_NONAI_2D_STREAM_COUNT && + !k230_nonai_2d_copy_stream(s, stream)) { + qemu_log_mask(LOG_GUEST_ERROR, + "k230 non-AI 2D stream %u passthrough copy skipped\n", + stream); + } + + k230_nonai_2d_raise_irq(s); +} + +static uint64_t k230_nonai_2d_read(void *opaque, hwaddr addr, + unsigned int size) +{ + K230NonAI2DState *s = K230_NONAI_2D(opaque); + + if (!k230_nonai_2d_range_ok(addr, size)) { + qemu_log_mask(LOG_GUEST_ERROR, + "k230 non-AI 2D bad read offset 0x%" HWADDR_PRIx + " size %u\n", addr, size); + return 0; + } + + return k230_nonai_2d_read_bytes(s->regs, addr, size); +} + +static void k230_nonai_2d_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230NonAI2DState *s = K230_NONAI_2D(opaque); + + if (!k230_nonai_2d_range_ok(addr, size)) { + qemu_log_mask(LOG_GUEST_ERROR, + "k230 non-AI 2D bad write offset 0x%" HWADDR_PRIx + " value 0x%" PRIx64 " size %u\n", addr, val, size); + return; + } + + k230_nonai_2d_write_bytes(s->regs, addr, val, size); + + if (k230_nonai_2d_access_hits(addr, size, K230_NONAI_2D_INTR_CLEAR) && + val) { + k230_nonai_2d_writel_regs(s, K230_NONAI_2D_INTR_STATUS, 0); + s->irq_level = false; + qemu_set_irq(s->irq, 0); + return; + } + + if (k230_nonai_2d_access_hits(addr, size, K230_NONAI_2D_MAIN_CFG) && + (val & K230_NONAI_2D_CALC_EN)) { + k230_nonai_2d_complete(s, val); + } +} + +static const MemoryRegionOps k230_nonai_2d_ops = { + .read = k230_nonai_2d_read, + .write = k230_nonai_2d_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 8, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + .unaligned = true, + }, +}; + +static void k230_nonai_2d_reset(DeviceState *dev) +{ + K230NonAI2DState *s = K230_NONAI_2D(dev); + + memset(s->regs, 0, sizeof(s->regs)); + s->irq_level = false; + qemu_set_irq(s->irq, 0); +} + +static const VMStateDescription vmstate_k230_nonai_2d = { + .name = TYPE_K230_NONAI_2D, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_BOOL(irq_level, K230NonAI2DState), + VMSTATE_UINT8_ARRAY(regs, K230NonAI2DState, K230_NONAI_2D_SIZE), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_nonai_2d_init(Object *obj) +{ + K230NonAI2DState *s = K230_NONAI_2D(obj); + + memory_region_init_io(&s->mmio, obj, &k230_nonai_2d_ops, s, + TYPE_K230_NONAI_2D, K230_NONAI_2D_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); + sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq); +} + +static void k230_nonai_2d_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, k230_nonai_2d_reset); + dc->vmsd = &vmstate_k230_nonai_2d; +} + +static const TypeInfo k230_nonai_2d_info = { + .name = TYPE_K230_NONAI_2D, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230NonAI2DState), + .instance_init = k230_nonai_2d_init, + .class_init = k230_nonai_2d_class_init, +}; + +static void k230_nonai_2d_register_types(void) +{ + type_register_static(&k230_nonai_2d_info); +} + +type_init(k230_nonai_2d_register_types) diff --git a/hw/misc/k230_pmu.c b/hw/misc/k230_pmu.c new file mode 100644 index 0000000000000..00a1a3c44f0d7 --- /dev/null +++ b/hw/misc/k230_pmu.c @@ -0,0 +1,177 @@ +/* + * K230 power management unit + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/misc/k230_pmu.h" +#include "migration/vmstate.h" +#include "qemu/module.h" +#include "system/runstate.h" + +#define K230_PMU_STATUS 0x3c +#define K230_PMU_INT_CLEAR 0x54 +#define K230_PMU_OUTPUT_REG_CTL 0x78 + +#define K230_PMU_SOC_NORMAL_PD 2 +#define K230_PMU_OUTPUT_ENABLE 1 +#define K230_PMU_INT_CLR_ALL 0x3ff + +enum { + K230_PMU_POWEROFF_IDLE, + K230_PMU_POWEROFF_STATUS, + K230_PMU_POWEROFF_OUTPUT_ENABLED, + K230_PMU_POWEROFF_INT_CLEARED, +}; + +static uint64_t k230_pmu_read(void *opaque, hwaddr addr, unsigned int size) +{ + K230PmuState *s = K230_PMU(opaque); + uint64_t val = 0; + + if (addr >= K230_PMU_MMIO_SIZE || size > K230_PMU_MMIO_SIZE - addr) { + return 0; + } + + for (int i = 0; i < size; i++) { + val |= (uint64_t)s->regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_pmu_restart_poweroff(K230PmuState *s, hwaddr addr, + uint64_t val) +{ + if (addr == K230_PMU_STATUS && val == K230_PMU_SOC_NORMAL_PD) { + s->poweroff_step = K230_PMU_POWEROFF_STATUS; + } else { + s->poweroff_step = K230_PMU_POWEROFF_IDLE; + } +} + +static void k230_pmu_update_poweroff(K230PmuState *s, hwaddr addr, + uint64_t val, unsigned int size) +{ + if (size != 4) { + return; + } + + switch (s->poweroff_step) { + case K230_PMU_POWEROFF_IDLE: + k230_pmu_restart_poweroff(s, addr, val); + break; + case K230_PMU_POWEROFF_STATUS: + if (addr == K230_PMU_OUTPUT_REG_CTL && + val == K230_PMU_OUTPUT_ENABLE) { + s->poweroff_step = K230_PMU_POWEROFF_OUTPUT_ENABLED; + } else { + k230_pmu_restart_poweroff(s, addr, val); + } + break; + case K230_PMU_POWEROFF_OUTPUT_ENABLED: + if (addr == K230_PMU_INT_CLEAR && val == K230_PMU_INT_CLR_ALL) { + s->poweroff_step = K230_PMU_POWEROFF_INT_CLEARED; + } else { + k230_pmu_restart_poweroff(s, addr, val); + } + break; + case K230_PMU_POWEROFF_INT_CLEARED: + if (addr == K230_PMU_OUTPUT_REG_CTL && val == 0) { + s->poweroff_step = K230_PMU_POWEROFF_IDLE; + qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN); + } else { + k230_pmu_restart_poweroff(s, addr, val); + } + break; + default: + k230_pmu_restart_poweroff(s, addr, val); + break; + } +} + +static void k230_pmu_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230PmuState *s = K230_PMU(opaque); + + if (addr >= K230_PMU_MMIO_SIZE || size > K230_PMU_MMIO_SIZE - addr) { + return; + } + + for (int i = 0; i < size; i++) { + s->regs[addr + i] = val >> (i * 8); + } + + k230_pmu_update_poweroff(s, addr, val, size); +} + +static const MemoryRegionOps k230_pmu_ops = { + .read = k230_pmu_read, + .write = k230_pmu_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_pmu_reset(DeviceState *dev) +{ + K230PmuState *s = K230_PMU(dev); + + memset(s->regs, 0, sizeof(s->regs)); + s->poweroff_step = K230_PMU_POWEROFF_IDLE; +} + +static const VMStateDescription vmstate_k230_pmu = { + .name = TYPE_K230_PMU, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, K230PmuState, K230_PMU_MMIO_SIZE), + VMSTATE_UINT8(poweroff_step, K230PmuState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_pmu_realize(DeviceState *dev, Error **errp) +{ + K230PmuState *s = K230_PMU(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_pmu_ops, s, + TYPE_K230_PMU, K230_PMU_MMIO_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); +} + +static void k230_pmu_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_pmu_realize; + device_class_set_legacy_reset(dc, k230_pmu_reset); + dc->vmsd = &vmstate_k230_pmu; + dc->desc = "K230 power management unit"; +} + +static const TypeInfo k230_pmu_type_info = { + .name = TYPE_K230_PMU, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230PmuState), + .class_init = k230_pmu_class_init, +}; + +static void k230_pmu_register_types(void) +{ + type_register_static(&k230_pmu_type_info); +} + +type_init(k230_pmu_register_types) diff --git a/hw/misc/k230_pwm.c b/hw/misc/k230_pwm.c new file mode 100644 index 0000000000000..7bf0b3f640865 --- /dev/null +++ b/hw/misc/k230_pwm.c @@ -0,0 +1,108 @@ +/* + * K230 PWM register block + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "migration/vmstate.h" +#include "hw/misc/k230_pwm.h" + +static uint64_t k230_pwm_read_bytes(uint8_t *regs, hwaddr addr, + unsigned int size) +{ + uint64_t val = 0; + + for (int i = 0; i < size; i++) { + val |= (uint64_t)regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_pwm_write_bytes(uint8_t *regs, hwaddr addr, uint64_t val, + unsigned int size) +{ + for (int i = 0; i < size; i++) { + regs[addr + i] = val >> (i * 8); + } +} + +static uint64_t k230_pwm_read(void *opaque, hwaddr addr, unsigned int size) +{ + return k230_pwm_read_bytes(K230_PWM(opaque)->regs, addr, size); +} + +static void k230_pwm_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + k230_pwm_write_bytes(K230_PWM(opaque)->regs, addr, val, size); +} + +static const MemoryRegionOps k230_pwm_ops = { + .read = k230_pwm_read, + .write = k230_pwm_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_pwm_reset(DeviceState *dev) +{ + K230PwmState *s = K230_PWM(dev); + + memset(s->regs, 0, sizeof(s->regs)); +} + +static const VMStateDescription vmstate_k230_pwm = { + .name = TYPE_K230_PWM, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, K230PwmState, K230_PWM_SIZE), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_pwm_realize(DeviceState *dev, Error **errp) +{ + K230PwmState *s = K230_PWM(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_pwm_ops, s, + TYPE_K230_PWM, K230_PWM_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); +} + +static void k230_pwm_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_pwm_realize; + device_class_set_legacy_reset(dc, k230_pwm_reset); + dc->vmsd = &vmstate_k230_pwm; + dc->desc = "K230 PWM register block"; +} + +static const TypeInfo k230_pwm_type_info = { + .name = TYPE_K230_PWM, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230PwmState), + .class_init = k230_pwm_class_init, +}; + +static void k230_register_pwm_types(void) +{ + type_register_static(&k230_pwm_type_info); +} + +type_init(k230_register_pwm_types) diff --git a/hw/misc/k230_regs.c b/hw/misc/k230_regs.c new file mode 100644 index 0000000000000..d0a7846ff616c --- /dev/null +++ b/hw/misc/k230_regs.c @@ -0,0 +1,419 @@ +/* + * K230 scratch register block + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/irq.h" +#include "hw/misc/k230_regs.h" +#include "migration/vmstate.h" +#include "qemu/bitmap.h" +#include "qemu/timer.h" +#include "system/dma.h" +#include "trace.h" + +static bool k230_regs_access_hits(hwaddr addr, unsigned int size, + uint64_t offset) +{ + return offset != K230_REGS_NO_IRQ_OFFSET && + addr <= offset && offset < addr + size; +} + +static bool k230_regs_access_hits_start(K230RegsState *s, hwaddr addr, + unsigned int size) +{ + return k230_regs_access_hits(addr, size, s->irq_start_offset) || + k230_regs_access_hits(addr, size, s->irq_start2_offset) || + k230_regs_access_hits(addr, size, s->irq_start3_offset); +} + +static const char *k230_regs_name(K230RegsState *s) +{ + return object_get_canonical_path_component(OBJECT(s)); +} + +static bool k230_regs_has_counter(const K230RegsState *s) +{ + return s->counter_offset != K230_REGS_NO_IRQ_OFFSET && + s->counter_size != 0 && s->counter_frequency != 0; +} + +static bool k230_regs_counter_contains(const K230RegsState *s, hwaddr addr) +{ + if (!k230_regs_has_counter(s) || addr < s->counter_offset) { + return false; + } + + return addr - s->counter_offset < s->counter_size; +} + +static uint8_t k230_regs_counter_byte(const K230RegsState *s, hwaddr addr) +{ + uint64_t index = addr - s->counter_offset; + uint64_t counter; + + counter = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), + s->counter_frequency, NANOSECONDS_PER_SECOND); + + return counter >> ((index % sizeof(counter)) * 8); +} + +static void k230_regs_store(K230RegsState *s, hwaddr addr, uint64_t val, + unsigned int size) +{ + if (addr >= K230_REGS_STORAGE_SIZE || + size > K230_REGS_STORAGE_SIZE - addr) { + return; + } + + for (int i = 0; i < size; i++) { + s->regs[addr + i] = val >> (i * 8); + } +} + +static uint64_t k230_regs_load(const K230RegsState *s, hwaddr addr, + unsigned int size) +{ + uint64_t val = 0; + + if (addr >= K230_REGS_STORAGE_SIZE || + size > K230_REGS_STORAGE_SIZE - addr) { + return 0; + } + + for (int i = 0; i < size; i++) { + val |= (uint64_t)s->regs[addr + i] << (i * 8); + } + + return val; +} + +static bool k230_regs_has_irq_status(const K230RegsState *s) +{ + return s->irq_status_offset != K230_REGS_NO_IRQ_OFFSET && + s->irq_status_size != 0; +} + +static void k230_regs_set_irq_status(K230RegsState *s, bool pending) +{ + unsigned int size; + + if (!k230_regs_has_irq_status(s) || + s->irq_status_offset >= K230_REGS_STORAGE_SIZE) { + return; + } + + size = MIN(s->irq_status_size, (uint64_t)sizeof(s->irq_status_value)); + if (size > K230_REGS_STORAGE_SIZE - s->irq_status_offset) { + return; + } + + k230_regs_store(s, s->irq_status_offset, + pending ? s->irq_status_value : 0, size); +} + +static void k230_regs_log_irq_command(K230RegsState *s) +{ + uint64_t start; + uint64_t end; + uint64_t hi; + + if (s->irq_command_start_offset == K230_REGS_NO_IRQ_OFFSET) { + return; + } + + start = k230_regs_load(s, s->irq_command_start_offset, sizeof(uint32_t)); + end = k230_regs_load(s, s->irq_command_end_offset, sizeof(uint32_t)); + hi = k230_regs_load(s, s->irq_command_hi_offset, sizeof(uint32_t)); + trace_k230_regs_irq_command(k230_regs_name(s), start, end, hi); +} + +static void k230_regs_completion_zero(K230RegsState *s) +{ + uint64_t command_start; + uint64_t command_end; + uint64_t zero_end; + uint64_t pages; + long page_count; + g_autofree unsigned long *seen_pages = NULL; + + if (s->complete_zero_base == K230_REGS_NO_IRQ_OFFSET || + s->complete_zero_size == 0 || s->complete_zero_page_size == 0 || + (s->complete_zero_page_size & (s->complete_zero_page_size - 1))) { + return; + } + + if (s->irq_command_start_offset == K230_REGS_NO_IRQ_OFFSET) { + dma_memory_set(&address_space_memory, s->complete_zero_base, 0, + s->complete_zero_size, MEMTXATTRS_UNSPECIFIED); + return; + } + + command_start = k230_regs_load(s, s->irq_command_start_offset, + sizeof(uint32_t)); + command_end = k230_regs_load(s, s->irq_command_end_offset, + sizeof(uint32_t)); + if (!command_start || !command_end || command_end <= command_start) { + return; + } + + if (!s->complete_zero_command_pages) { + dma_memory_set(&address_space_memory, s->complete_zero_base, 0, + s->complete_zero_size, MEMTXATTRS_UNSPECIFIED); + return; + } + + zero_end = s->complete_zero_base + s->complete_zero_size; + if (zero_end < s->complete_zero_base) { + return; + } + pages = s->complete_zero_size / s->complete_zero_page_size; + if (!pages || pages > LONG_MAX) { + return; + } + page_count = pages; + seen_pages = bitmap_new(page_count); + for (uint64_t addr = command_start; addr + sizeof(uint32_t) <= command_end; + addr += sizeof(uint32_t)) { + uint32_t raw; + uint32_t value; + uint64_t page; + uint64_t page_index; + + if (dma_memory_read(&address_space_memory, addr, &raw, sizeof(raw), + MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { + break; + } + + value = ldl_le_p(&raw); + page = value & ~(s->complete_zero_page_size - 1); + if (page < s->complete_zero_base || + page + s->complete_zero_page_size > zero_end) { + continue; + } + + page_index = (page - s->complete_zero_base) / + s->complete_zero_page_size; + if (page_index >= pages || + test_and_set_bit((long)page_index, seen_pages)) { + continue; + } + + trace_k230_regs_completion_zero_page(k230_regs_name(s), page, value); + dma_memory_set(&address_space_memory, page, 0, + s->complete_zero_page_size, + MEMTXATTRS_UNSPECIFIED); + } +} + +static void k230_regs_raise_irq(K230RegsState *s) +{ + s->irq_pending = false; + k230_regs_completion_zero(s); + k230_regs_set_irq_status(s, true); + s->irq_level = true; + trace_k230_regs_irq(k230_regs_name(s), true); + qemu_set_irq(s->irq, 1); +} + +static void k230_regs_irq_timer(void *opaque) +{ + K230RegsState *s = K230_REGS(opaque); + + if (!s->irq_pending) { + return; + } + + k230_regs_raise_irq(s); +} + +static uint64_t k230_regs_read(void *opaque, hwaddr addr, unsigned int size) +{ + K230RegsState *s = K230_REGS(opaque); + uint64_t val = 0; + + if (addr >= K230_REGS_STORAGE_SIZE || + size > K230_REGS_STORAGE_SIZE - addr) { + return 0; + } + + for (int i = 0; i < size; i++) { + if (k230_regs_counter_contains(s, addr + i)) { + val |= (uint64_t)k230_regs_counter_byte(s, addr + i) << (i * 8); + } else { + val |= (uint64_t)s->regs[addr + i] << (i * 8); + } + } + + trace_k230_regs_read(k230_regs_name(s), addr, val, size); + + return val; +} + +static void k230_regs_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230RegsState *s = K230_REGS(opaque); + + if (addr >= K230_REGS_STORAGE_SIZE || + size > K230_REGS_STORAGE_SIZE - addr) { + return; + } + + k230_regs_store(s, addr, val, size); + + trace_k230_regs_write(k230_regs_name(s), addr, val, size); + + if (k230_regs_access_hits(addr, size, s->irq_clear_offset) && val) { + if (s->irq_clear_clears_status) { + k230_regs_set_irq_status(s, false); + } + s->irq_level = false; + trace_k230_regs_irq(k230_regs_name(s), false); + qemu_set_irq(s->irq, 0); + return; + } + + if (s->irq_on_any_write || + (k230_regs_access_hits_start(s, addr, size) && + (val & s->irq_start_mask))) { + k230_regs_set_irq_status(s, false); + k230_regs_log_irq_command(s); + if (s->irq_delay_ns) { + s->irq_pending = true; + timer_mod(&s->irq_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + s->irq_delay_ns); + } else { + k230_regs_raise_irq(s); + } + } +} + +static const MemoryRegionOps k230_regs_ops = { + .read = k230_regs_read, + .write = k230_regs_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 8, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + .unaligned = true, + }, +}; + +static void k230_regs_reset(DeviceState *dev) +{ + K230RegsState *s = K230_REGS(dev); + + memset(s->regs, 0, sizeof(s->regs)); + timer_del(&s->irq_timer); + s->irq_pending = false; + s->irq_level = false; + qemu_set_irq(s->irq, 0); +} + +static const VMStateDescription vmstate_k230_regs = { + .name = TYPE_K230_REGS, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_BOOL(irq_level, K230RegsState), + VMSTATE_BOOL(irq_pending, K230RegsState), + VMSTATE_TIMER(irq_timer, K230RegsState), + VMSTATE_UINT8_ARRAY(regs, K230RegsState, K230_REGS_STORAGE_SIZE), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_regs_realize(DeviceState *dev, Error **errp) +{ + K230RegsState *s = K230_REGS(dev); + + if (!s->size) { + s->size = K230_REGS_DEFAULT_SIZE; + } + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_regs_ops, s, + TYPE_K230_REGS, s->size); + timer_init_ns(&s->irq_timer, QEMU_CLOCK_VIRTUAL, + k230_regs_irq_timer, s); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq); +} + +static const Property k230_regs_properties[] = { + DEFINE_PROP_UINT64("size", K230RegsState, size, + K230_REGS_DEFAULT_SIZE), + DEFINE_PROP_UINT64("irq-start-offset", K230RegsState, irq_start_offset, + K230_REGS_NO_IRQ_OFFSET), + DEFINE_PROP_UINT64("irq-start2-offset", K230RegsState, irq_start2_offset, + K230_REGS_NO_IRQ_OFFSET), + DEFINE_PROP_UINT64("irq-start3-offset", K230RegsState, irq_start3_offset, + K230_REGS_NO_IRQ_OFFSET), + DEFINE_PROP_UINT64("irq-start-mask", K230RegsState, irq_start_mask, + UINT64_MAX), + DEFINE_PROP_UINT64("irq-clear-offset", K230RegsState, irq_clear_offset, + K230_REGS_NO_IRQ_OFFSET), + DEFINE_PROP_UINT64("irq-status-offset", K230RegsState, irq_status_offset, + K230_REGS_NO_IRQ_OFFSET), + DEFINE_PROP_UINT64("irq-status-size", K230RegsState, irq_status_size, 0), + DEFINE_PROP_UINT64("irq-status-value", K230RegsState, irq_status_value, 0), + DEFINE_PROP_UINT64("irq-delay-ns", K230RegsState, irq_delay_ns, 0), + DEFINE_PROP_UINT64("irq-command-start-offset", K230RegsState, + irq_command_start_offset, K230_REGS_NO_IRQ_OFFSET), + DEFINE_PROP_UINT64("irq-command-end-offset", K230RegsState, + irq_command_end_offset, K230_REGS_NO_IRQ_OFFSET), + DEFINE_PROP_UINT64("irq-command-hi-offset", K230RegsState, + irq_command_hi_offset, K230_REGS_NO_IRQ_OFFSET), + DEFINE_PROP_UINT64("complete-zero-base", K230RegsState, + complete_zero_base, K230_REGS_NO_IRQ_OFFSET), + DEFINE_PROP_UINT64("complete-zero-size", K230RegsState, + complete_zero_size, 0), + DEFINE_PROP_UINT64("complete-zero-page-size", K230RegsState, + complete_zero_page_size, 4096), + DEFINE_PROP_UINT64("counter-offset", K230RegsState, counter_offset, + K230_REGS_NO_IRQ_OFFSET), + DEFINE_PROP_UINT64("counter-size", K230RegsState, counter_size, 0), + DEFINE_PROP_UINT64("counter-frequency", K230RegsState, + counter_frequency, 0), + DEFINE_PROP_BOOL("irq-clear-clears-status", K230RegsState, + irq_clear_clears_status, true), + DEFINE_PROP_BOOL("irq-on-any-write", K230RegsState, irq_on_any_write, + false), + DEFINE_PROP_BOOL("complete-zero-command-pages", K230RegsState, + complete_zero_command_pages, false), +}; + +static void k230_regs_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_regs_realize; + device_class_set_legacy_reset(dc, k230_regs_reset); + device_class_set_props(dc, k230_regs_properties); + dc->vmsd = &vmstate_k230_regs; + dc->desc = "K230 scratch register block"; +} + +static const TypeInfo k230_regs_type_info = { + .name = TYPE_K230_REGS, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230RegsState), + .class_init = k230_regs_class_init, +}; + +static void k230_regs_register_types(void) +{ + type_register_static(&k230_regs_type_info); +} + +type_init(k230_regs_register_types) diff --git a/hw/misc/k230_rx_csi.c b/hw/misc/k230_rx_csi.c new file mode 100644 index 0000000000000..f3caf5690cf10 --- /dev/null +++ b/hw/misc/k230_rx_csi.c @@ -0,0 +1,200 @@ +/* + * K230 RX CSI and video input registers + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "hw/misc/k230_rx_csi.h" +#include "migration/vmstate.h" +#include "trace.h" + +#define K230_RX_CSI_HOST_STRIDE 0x800 +#define K230_RX_CSI_HOST_COUNT 3 +#define K230_RX_CSI_HOST_ENABLE 0x008 +#define K230_RX_CSI_HOST_PHY_STATE 0x014 +#define K230_RX_CSI_PHY_STOPSTATE BIT(16) +#define K230_RX_CSI_PHY_CTRL0 0x850 +#define K230_RX_CSI_PHY_DATA0 0x854 +#define K230_RX_CSI_PHY_CTRL1 0x858 +#define K230_RX_CSI_PHY_DATA1 0x85c + +static bool k230_rx_csi_access_hits(hwaddr addr, unsigned int size, + hwaddr offset) +{ + return addr <= offset && offset < addr + size; +} + +static uint32_t k230_rx_csi_read32(K230RxCsiState *s, hwaddr addr) +{ + uint32_t val = 0; + + if (addr > K230_RX_CSI_SIZE - sizeof(val)) { + return 0; + } + + for (int i = 0; i < 4; i++) { + val |= (uint32_t)s->regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_rx_csi_write32(K230RxCsiState *s, hwaddr addr, uint32_t val) +{ + if (addr > K230_RX_CSI_SIZE - sizeof(val)) { + return; + } + + for (int i = 0; i < 4; i++) { + s->regs[addr + i] = val >> (i * 8); + } +} + +static void k230_rx_csi_set_host_ready(K230RxCsiState *s, unsigned int host) +{ + hwaddr state = host * K230_RX_CSI_HOST_STRIDE + + K230_RX_CSI_HOST_PHY_STATE; + uint32_t val = k230_rx_csi_read32(s, state); + + k230_rx_csi_write32(s, state, val | K230_RX_CSI_PHY_STOPSTATE); + trace_k230_rx_csi_ready(host, val | K230_RX_CSI_PHY_STOPSTATE); +} + +static void k230_rx_csi_reset_ready(K230RxCsiState *s) +{ + for (unsigned int i = 0; i < K230_RX_CSI_HOST_COUNT; i++) { + k230_rx_csi_set_host_ready(s, i); + } +} + +static void k230_rx_csi_mirror_phy_port(K230RxCsiState *s, hwaddr ctrl, + hwaddr data) +{ + uint32_t val = k230_rx_csi_read32(s, ctrl); + + if (!k230_rx_csi_read32(s, data)) { + k230_rx_csi_write32(s, data, val); + } +} + +static uint64_t k230_rx_csi_read(void *opaque, hwaddr addr, unsigned int size) +{ + K230RxCsiState *s = K230_RX_CSI(opaque); + uint64_t val = 0; + + if (addr >= K230_RX_CSI_SIZE || size > K230_RX_CSI_SIZE - addr) { + return 0; + } + + for (int i = 0; i < size; i++) { + val |= (uint64_t)s->regs[addr + i] << (i * 8); + } + + trace_k230_rx_csi_read(addr, val, size); + + return val; +} + +static void k230_rx_csi_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230RxCsiState *s = K230_RX_CSI(opaque); + + if (addr >= K230_RX_CSI_SIZE || size > K230_RX_CSI_SIZE - addr) { + return; + } + + for (int i = 0; i < size; i++) { + s->regs[addr + i] = val >> (i * 8); + } + + trace_k230_rx_csi_write(addr, val, size); + + for (unsigned int i = 0; i < K230_RX_CSI_HOST_COUNT; i++) { + hwaddr enable = i * K230_RX_CSI_HOST_STRIDE + + K230_RX_CSI_HOST_ENABLE; + + if (k230_rx_csi_access_hits(addr, size, enable) && val) { + k230_rx_csi_set_host_ready(s, i); + } + } + + if (k230_rx_csi_access_hits(addr, size, K230_RX_CSI_PHY_CTRL0)) { + k230_rx_csi_mirror_phy_port(s, K230_RX_CSI_PHY_CTRL0, + K230_RX_CSI_PHY_DATA0); + } + if (k230_rx_csi_access_hits(addr, size, K230_RX_CSI_PHY_CTRL1)) { + k230_rx_csi_mirror_phy_port(s, K230_RX_CSI_PHY_CTRL1, + K230_RX_CSI_PHY_DATA1); + } +} + +static const MemoryRegionOps k230_rx_csi_ops = { + .read = k230_rx_csi_read, + .write = k230_rx_csi_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 8, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + .unaligned = true, + }, +}; + +static void k230_rx_csi_reset(DeviceState *dev) +{ + K230RxCsiState *s = K230_RX_CSI(dev); + + memset(s->regs, 0, sizeof(s->regs)); + k230_rx_csi_reset_ready(s); +} + +static const VMStateDescription vmstate_k230_rx_csi = { + .name = TYPE_K230_RX_CSI, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, K230RxCsiState, K230_RX_CSI_SIZE), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_rx_csi_realize(DeviceState *dev, Error **errp) +{ + K230RxCsiState *s = K230_RX_CSI(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_rx_csi_ops, s, + TYPE_K230_RX_CSI, K230_RX_CSI_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); +} + +static void k230_rx_csi_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_rx_csi_realize; + device_class_set_legacy_reset(dc, k230_rx_csi_reset); + dc->vmsd = &vmstate_k230_rx_csi; + dc->desc = "K230 RX CSI and video input registers"; +} + +static const TypeInfo k230_rx_csi_type_info = { + .name = TYPE_K230_RX_CSI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230RxCsiState), + .class_init = k230_rx_csi_class_init, +}; + +static void k230_rx_csi_register_types(void) +{ + type_register_static(&k230_rx_csi_type_info); +} + +type_init(k230_rx_csi_register_types) diff --git a/hw/misc/k230_security.c b/hw/misc/k230_security.c new file mode 100644 index 0000000000000..c305985e84616 --- /dev/null +++ b/hw/misc/k230_security.c @@ -0,0 +1,325 @@ +/* + * K230 security register block + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/guest-random.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/units.h" +#include "migration/vmstate.h" +#include "hw/misc/k230_security.h" +#include "system/dma.h" + +#define K230_DMA_STAT_0 0x0010 +#define K230_DMA_START 0x0020 +#define K230_DMA_DSC_CFG_0 0x0034 +#define K230_DMA_DSC_CFG_1 0x0038 +#define K230_DMA_DSC_CFG_2 0x003c + +#define K230_CRYPTO_DGST_OUT 0x01c0 + +#define K230_GCM_BASE 0x0200 +#define K230_GCM_STAT 0x0070 + +#define K230_KWP_BASE 0x0300 +#define K230_KWP_STATUS 0x0010 +#define K230_KWP_START 0x0014 +#define K230_KWP_CONFIG 0x0018 + +#define K230_HASH_BASE 0x0800 +#define K230_HASH_STATUS 0x0010 +#define K230_HASH_PLEN 0x0020 +#define K230_HASH_ALEN 0x0030 + +#define K230_KA_BASE 0x0c00 +#define K230_KA_SK_FREE 0x0010 +#define K230_KA_SK_0 0x0020 + +#define K230_RSA_BASE 0x1000 +#define K230_RSA_CTRL 0x0008 +#define K230_RSA_STATUS 0x000c + +#define K230_TRNG_BASE 0x3000 +#define K230_TRNG_DATA 0x02a0 +#define K230_OTP_BASE 0x3500 + +#define K230_DMA_MAX_COPY (4 * MiB) + +static uint64_t k230_security_read_bytes(uint8_t *regs, hwaddr addr, + unsigned int size) +{ + uint64_t val = 0; + + for (int i = 0; i < size; i++) { + val |= (uint64_t)regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_security_write_bytes(uint8_t *regs, hwaddr addr, + uint64_t val, unsigned int size) +{ + for (int i = 0; i < size; i++) { + regs[addr + i] = val >> (i * 8); + } +} + +static bool k230_security_range_ok(hwaddr addr, unsigned int size) +{ + return addr <= K230_SECURITY_SIZE && size <= K230_SECURITY_SIZE - addr; +} + +static bool k230_otp_range(hwaddr addr, unsigned int size) +{ + hwaddr end = addr + size; + + return addr >= K230_OTP_BASE && end <= K230_OTP_BASE + K230_OTP_SIZE; +} + +static uint32_t k230_security_readl_regs(K230SecurityState *s, hwaddr addr) +{ + return ldl_le_p(s->regs + addr); +} + +static void k230_security_writel_regs(K230SecurityState *s, hwaddr addr, + uint32_t val) +{ + stl_le_p(s->regs + addr, val); +} + +static void k230_security_zero_digest(K230SecurityState *s) +{ + memset(s->regs + K230_CRYPTO_DGST_OUT, 0, 64); +} + +static void k230_security_dma_complete(K230SecurityState *s) +{ + uint32_t src = k230_security_readl_regs(s, K230_DMA_DSC_CFG_0); + uint32_t dst = k230_security_readl_regs(s, K230_DMA_DSC_CFG_1); + uint32_t len = k230_security_readl_regs(s, K230_DMA_DSC_CFG_2); + uint32_t plen = k230_security_readl_regs(s, K230_HASH_BASE + + K230_HASH_PLEN); + + if (src && dst && len && len <= K230_DMA_MAX_COPY) { + g_autofree uint8_t *buf = g_malloc(len); + + if (dma_memory_read(&address_space_memory, src, buf, len, + MEMTXATTRS_UNSPECIFIED) == MEMTX_OK) { + dma_memory_write(&address_space_memory, dst, buf, len, + MEMTXATTRS_UNSPECIFIED); + } + } + + k230_security_writel_regs(s, K230_DMA_STAT_0, 0); + k230_security_writel_regs(s, K230_GCM_BASE + K230_GCM_STAT, 0); + k230_security_writel_regs(s, K230_HASH_BASE + K230_HASH_STATUS, 0); + k230_security_writel_regs(s, K230_HASH_BASE + K230_HASH_ALEN, plen + len); + k230_security_zero_digest(s); +} + +static uint32_t k230_security_ka_metadata(uint32_t keybits, uint32_t tag) +{ + return 1 | (keybits << 4) | (tag << 16); +} + +static void k230_security_ka_set_slot(K230SecurityState *s, uint32_t slot, + uint32_t keybits) +{ + uint32_t tag; + uint32_t addr; + + if (slot >= 16 || !keybits) { + return; + } + + addr = K230_KA_BASE + K230_KA_SK_0 + slot * 4; + if (keybits <= 128) { + tag = 0x30 + slot; + k230_security_writel_regs(s, addr, + k230_security_ka_metadata(keybits, tag)); + } else if (keybits <= 256) { + tag = 0x50 + slot / 2; + k230_security_writel_regs(s, addr, + k230_security_ka_metadata(keybits, tag)); + k230_security_writel_regs(s, addr + 4, tag << 16); + } else { + tag = 0x60 + slot / 4; + k230_security_writel_regs(s, addr, + k230_security_ka_metadata(keybits, tag)); + for (int i = 1; i < 4; i++) { + k230_security_writel_regs(s, addr + i * 4, tag << 16); + } + } +} + +static void k230_security_ka_free(K230SecurityState *s, uint32_t mask) +{ + for (int i = 0; i < 16; i++) { + if (mask & BIT(i)) { + k230_security_writel_regs(s, K230_KA_BASE + K230_KA_SK_0 + i * 4, + 0); + } + } +} + +static void k230_security_kwp_start(K230SecurityState *s) +{ + uint32_t config = k230_security_readl_regs(s, K230_KWP_BASE + + K230_KWP_CONFIG); + uint32_t keybits = (config >> 8) & 0x7ff; + uint32_t slot = (config >> 20) & 0xf; + + k230_security_ka_set_slot(s, slot, keybits); + k230_security_writel_regs(s, K230_KWP_BASE + K230_KWP_STATUS, 0); +} + +static void k230_security_write_side_effect(K230SecurityState *s, hwaddr addr, + uint64_t val, unsigned int size) +{ + if (size != 4) { + return; + } + + switch (addr) { + case K230_DMA_START: + if (val & 1) { + k230_security_dma_complete(s); + } + break; + case K230_KWP_BASE + K230_KWP_START: + if (val & 1) { + k230_security_kwp_start(s); + } + break; + case K230_KA_BASE + K230_KA_SK_FREE: + k230_security_ka_free(s, val); + break; + case K230_RSA_BASE + K230_RSA_CTRL: + if (val & 1) { + k230_security_writel_regs(s, K230_RSA_BASE + K230_RSA_STATUS, 0); + } + break; + default: + break; + } +} + +static uint64_t k230_security_read(void *opaque, hwaddr addr, + unsigned int size) +{ + K230SecurityState *s = K230_SECURITY(opaque); + + if (!k230_security_range_ok(addr, size)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: bad read offset 0x%" HWADDR_PRIx "\n", + TYPE_K230_SECURITY, addr); + return 0; + } + + if (addr == K230_TRNG_BASE + K230_TRNG_DATA && size == 4) { + uint32_t value; + + qemu_guest_getrandom_nofail(&value, sizeof(value)); + return value; + } + + if (k230_otp_range(addr, size)) { + return k230_security_read_bytes(s->otp, addr - K230_OTP_BASE, size); + } + + return k230_security_read_bytes(s->regs, addr, size); +} + +static void k230_security_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230SecurityState *s = K230_SECURITY(opaque); + + if (!k230_security_range_ok(addr, size)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: bad write offset 0x%" HWADDR_PRIx "\n", + TYPE_K230_SECURITY, addr); + return; + } + + if (k230_otp_range(addr, size)) { + return; + } + + k230_security_write_bytes(s->regs, addr, val, size); + k230_security_write_side_effect(s, addr, val, size); +} + +static const MemoryRegionOps k230_security_ops = { + .read = k230_security_read, + .write = k230_security_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_security_reset(DeviceState *dev) +{ + K230SecurityState *s = K230_SECURITY(dev); + + memset(s->regs, 0, sizeof(s->regs)); + memset(s->otp, 0, sizeof(s->otp)); +} + +static const VMStateDescription vmstate_k230_security = { + .name = TYPE_K230_SECURITY, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, K230SecurityState, K230_SECURITY_SIZE), + VMSTATE_UINT8_ARRAY(otp, K230SecurityState, K230_OTP_SIZE), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_security_realize(DeviceState *dev, Error **errp) +{ + K230SecurityState *s = K230_SECURITY(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_security_ops, s, + TYPE_K230_SECURITY, K230_SECURITY_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); +} + +static void k230_security_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_security_realize; + device_class_set_legacy_reset(dc, k230_security_reset); + dc->vmsd = &vmstate_k230_security; + dc->desc = "K230 security registers"; +} + +static const TypeInfo k230_security_type_info = { + .name = TYPE_K230_SECURITY, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230SecurityState), + .class_init = k230_security_class_init, +}; + +static void k230_security_register_types(void) +{ + type_register_static(&k230_security_type_info); +} + +type_init(k230_security_register_types) diff --git a/hw/misc/k230_sysctl.c b/hw/misc/k230_sysctl.c new file mode 100644 index 0000000000000..e8e1db2c6a7c1 --- /dev/null +++ b/hw/misc/k230_sysctl.c @@ -0,0 +1,630 @@ +/* + * K230 system controller blocks + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/module.h" +#include "qemu/timer.h" +#include "exec/cpu-common.h" +#include "system/physmem.h" +#include "migration/vmstate.h" +#include "hw/core/qdev-properties.h" +#include "hw/misc/k230_sysctl.h" + +#define K230_SYSCTL_RTT_SAVE_SIZE (32 * 1024 * 1024) + +#define K230_SYSCTL_PLL_COUNT 4 +#define K230_SYSCTL_PLL_STRIDE 0x10 +#define K230_SYSCTL_PLL_CFG0(n) ((n) * K230_SYSCTL_PLL_STRIDE) +#define K230_SYSCTL_PLL_CTL(n) (K230_SYSCTL_PLL_CFG0(n) + 0x08) +#define K230_SYSCTL_PLL_STAT(n) (K230_SYSCTL_PLL_CFG0(n) + 0x0c) +#define K230_SYSCTL_PLL_GATE_EN BIT(2) +#define K230_SYSCTL_PLL_LOCK BIT(0) + +#define K230_SYSCTL_PWR_ON BIT(1) +#define K230_SYSCTL_PWR_ON_WEN BIT(17) +#define K230_SYSCTL_PWR_OFF BIT(0) +#define K230_SYSCTL_PWR_OFF_WEN BIT(16) +#define K230_SYSCTL_REPAIR_DONE 0x7 +#define K230_SYSCTL_AI_REPAIR BIT(4) +#define K230_SYSCTL_REPAIR_WEN BIT(20) + +/* + * SDK U-Boot programs cpu1_hart_rstvec in the BOOT block, then releases + * CPU1 through CPU1_RST_CTL using per-bit write-enable bits. + */ +#define K230_SYSCTL_CPU1_RST_CTL 0x0c +#define K230_SYSCTL_CPU1_RSTVEC 0x104 +#define K230_SYSCTL_CPU1_RST_REQ BIT(0) +#define K230_SYSCTL_CPU1_RST_DONE BIT(12) +#define K230_SYSCTL_CPU1_PRST_DONE BIT(13) +#define K230_SYSCTL_CPU1_RST_REQ_WEN BIT(16) +#define K230_SYSCTL_CPU1_RST_DONE_WEN BIT(28) +#define K230_SYSCTL_CPU1_PRST_DONE_WEN BIT(29) +#define K230_SYSCTL_CPU1_RST_CTL_RESET 0x00002001 + +typedef struct K230SysctlPowerDomain { + hwaddr en; + hwaddr stat; +} K230SysctlPowerDomain; + +typedef struct K230SysctlCpu1Reset { + uint64_t rstvec; +} K230SysctlCpu1Reset; + +static const K230SysctlPowerDomain k230_power_domains[] = { + { 0x018, 0x01c }, /* CPU1 */ + { 0x028, 0x02c }, /* AI */ + { 0x03c, 0x040 }, /* DISP */ + { 0x07c, 0x080 }, /* VPU */ + { 0x108, 0x10c }, /* DPU */ +}; + +static uint64_t k230_sysctl_read_bytes(uint8_t *regs, hwaddr addr, + unsigned int size) +{ + uint64_t val = 0; + + if (addr > K230_SYSCTL_SIZE || size > K230_SYSCTL_SIZE - addr) { + return 0; + } + + for (int i = 0; i < size; i++) { + val |= (uint64_t)regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_sysctl_write_bytes(uint8_t *regs, hwaddr addr, uint64_t val, + unsigned int size) +{ + if (addr > K230_SYSCTL_SIZE || size > K230_SYSCTL_SIZE - addr) { + return; + } + + for (int i = 0; i < size; i++) { + regs[addr + i] = val >> (i * 8); + } +} + +static uint32_t k230_sysctl_reg_read32(uint8_t *regs, hwaddr addr) +{ + return ldl_le_p(regs + addr); +} + +static void k230_sysctl_reg_write32(uint8_t *regs, hwaddr addr, uint32_t val) +{ + stl_le_p(regs + addr, val); +} + +static uint64_t k230_sysctl_boot_read(void *opaque, hwaddr addr, + unsigned int size) +{ + return k230_sysctl_read_bytes(K230_SYSCTL_BOOT(opaque)->regs, addr, size); +} + +static void k230_sysctl_boot_refresh_locks(K230SysctlBootState *s) +{ + for (int i = 0; i < K230_SYSCTL_PLL_COUNT; i++) { + hwaddr stat = K230_SYSCTL_PLL_STAT(i); + + k230_sysctl_reg_write32(s->regs, stat, + k230_sysctl_reg_read32(s->regs, stat) | K230_SYSCTL_PLL_LOCK); + } +} + +static void k230_sysctl_boot_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230SysctlBootState *s = K230_SYSCTL_BOOT(opaque); + + k230_sysctl_write_bytes(s->regs, addr, val, size); + k230_sysctl_boot_refresh_locks(s); +} + +static const MemoryRegionOps k230_sysctl_boot_ops = { + .read = k230_sysctl_boot_read, + .write = k230_sysctl_boot_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_sysctl_boot_set_pll(K230SysctlBootState *s, int pll, + uint32_t fb_div, uint32_t ref_div, + uint32_t out_div) +{ + uint32_t cfg0 = ((out_div - 1) << 24) | + ((ref_div - 1) << 16) | + (fb_div - 1); + + k230_sysctl_reg_write32(s->regs, K230_SYSCTL_PLL_CFG0(pll), cfg0); + k230_sysctl_reg_write32(s->regs, K230_SYSCTL_PLL_CTL(pll), + K230_SYSCTL_PLL_GATE_EN); + k230_sysctl_reg_write32(s->regs, K230_SYSCTL_PLL_STAT(pll), + K230_SYSCTL_PLL_LOCK); +} + +static void k230_sysctl_boot_reset(DeviceState *dev) +{ + K230SysctlBootState *s = K230_SYSCTL_BOOT(dev); + + memset(s->regs, 0, sizeof(s->regs)); + + k230_sysctl_boot_set_pll(s, 0, 200, 3, 1); + k230_sysctl_boot_set_pll(s, 1, 99, 1, 1); + k230_sysctl_boot_set_pll(s, 2, 111, 1, 1); + k230_sysctl_boot_set_pll(s, 3, 200, 3, 1); +} + +static const VMStateDescription vmstate_k230_sysctl_boot = { + .name = TYPE_K230_SYSCTL_BOOT, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, K230SysctlBootState, K230_SYSCTL_SIZE), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_sysctl_boot_realize(DeviceState *dev, Error **errp) +{ + K230SysctlBootState *s = K230_SYSCTL_BOOT(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_sysctl_boot_ops, s, + TYPE_K230_SYSCTL_BOOT, K230_SYSCTL_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); +} + +static void k230_sysctl_boot_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_sysctl_boot_realize; + device_class_set_legacy_reset(dc, k230_sysctl_boot_reset); + dc->vmsd = &vmstate_k230_sysctl_boot; + dc->desc = "K230 sysctl boot registers"; +} + +static const TypeInfo k230_sysctl_boot_type_info = { + .name = TYPE_K230_SYSCTL_BOOT, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230SysctlBootState), + .class_init = k230_sysctl_boot_class_init, +}; + +static uint64_t k230_sysctl_power_read(void *opaque, hwaddr addr, + unsigned int size) +{ + return k230_sysctl_read_bytes(K230_SYSCTL_POWER(opaque)->regs, addr, size); +} + +static void k230_sysctl_power_update_domain(K230SysctlPowerState *s, + hwaddr addr, uint32_t val) +{ + for (int i = 0; i < ARRAY_SIZE(k230_power_domains); i++) { + const K230SysctlPowerDomain *domain = &k230_power_domains[i]; + + if (addr != domain->en) { + continue; + } + + if ((val & K230_SYSCTL_PWR_ON_WEN) && (val & K230_SYSCTL_PWR_ON)) { + k230_sysctl_reg_write32(s->regs, domain->stat, + K230_SYSCTL_PWR_ON); + } else if ((val & K230_SYSCTL_PWR_OFF_WEN) && + (val & K230_SYSCTL_PWR_OFF)) { + k230_sysctl_reg_write32(s->regs, domain->stat, + K230_SYSCTL_PWR_OFF); + } + + if (addr == k230_power_domains[1].en && + (val & K230_SYSCTL_REPAIR_WEN) && (val & K230_SYSCTL_AI_REPAIR)) { + k230_sysctl_reg_write32(s->regs, 0x160, K230_SYSCTL_REPAIR_DONE); + } + } +} + +static void k230_sysctl_power_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230SysctlPowerState *s = K230_SYSCTL_POWER(opaque); + + k230_sysctl_write_bytes(s->regs, addr, val, size); + + if (size == 4) { + k230_sysctl_power_update_domain(s, addr, val); + } +} + +static const MemoryRegionOps k230_sysctl_power_ops = { + .read = k230_sysctl_power_read, + .write = k230_sysctl_power_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_sysctl_power_reset(DeviceState *dev) +{ + K230SysctlPowerState *s = K230_SYSCTL_POWER(dev); + + memset(s->regs, 0, sizeof(s->regs)); + + for (int i = 0; i < ARRAY_SIZE(k230_power_domains); i++) { + k230_sysctl_reg_write32(s->regs, k230_power_domains[i].stat, + K230_SYSCTL_PWR_OFF); + } +} + +static const VMStateDescription vmstate_k230_sysctl_power = { + .name = TYPE_K230_SYSCTL_POWER, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, K230SysctlPowerState, K230_SYSCTL_SIZE), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_sysctl_power_realize(DeviceState *dev, Error **errp) +{ + K230SysctlPowerState *s = K230_SYSCTL_POWER(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_sysctl_power_ops, s, + TYPE_K230_SYSCTL_POWER, K230_SYSCTL_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); +} + +static void k230_sysctl_power_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_sysctl_power_realize; + device_class_set_legacy_reset(dc, k230_sysctl_power_reset); + dc->vmsd = &vmstate_k230_sysctl_power; + dc->desc = "K230 sysctl power registers"; +} + +static const TypeInfo k230_sysctl_power_type_info = { + .name = TYPE_K230_SYSCTL_POWER, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230SysctlPowerState), + .class_init = k230_sysctl_power_class_init, +}; + +static uint64_t k230_sysctl_reset_read(void *opaque, hwaddr addr, + unsigned int size) +{ + return k230_sysctl_read_bytes(K230_SYSCTL_RESET(opaque)->regs, addr, size); +} + +static void k230_sysctl_reset_restore_rtt(K230SysctlResetState *s) +{ + if (s->rtt_saved_valid) { + physical_memory_write(s->rtt_addr, s->rtt_saved, s->rtt_size); + g_free(s->rtt_saved); + s->rtt_saved = NULL; + s->rtt_saved_valid = false; + s->rtt_addr = 0; + s->rtt_size = 0; + s->deferred_rstvec = 0; + } +} + +static void k230_sysctl_reset_cancel_deferred_release(K230SysctlResetState *s) +{ + if (s->release_timer) { + timer_del(s->release_timer); + } + k230_sysctl_reset_restore_rtt(s); +} + +static bool k230_sysctl_reset_has_rtt_saved(void *opaque, int version_id) +{ + K230SysctlResetState *s = K230_SYSCTL_RESET(opaque); + + return s->rtt_saved_valid; +} + + + + +static void k230_sysctl_reset_cpu1_async_work(CPUState *cpu, + run_on_cpu_data data) +{ + K230SysctlCpu1Reset *reset = data.host_ptr; + + cpu_reset(cpu); + cpu_set_pc(cpu, reset->rstvec); + cpu->halted = 0; + qemu_cpu_kick(cpu); + g_free(reset); +} + +static void k230_sysctl_reset_cpu1_hold_work(CPUState *cpu, + run_on_cpu_data data) +{ + cpu_reset(cpu); + cpu->halted = 1; + qemu_cpu_kick(cpu); +} + +static void k230_sysctl_reset_assert_cpu1(K230SysctlResetState *s) +{ + if (!s->cpu1) { + return; + } + run_on_cpu(s->cpu1, k230_sysctl_reset_cpu1_hold_work, RUN_ON_CPU_NULL); +} + + +static void k230_sysctl_reset_release_cpu1_rstvec(K230SysctlResetState *s, + uint32_t rstvec) +{ + K230SysctlCpu1Reset *reset; + + if (!s->cpu1 || !s->boot) { + return; + } + + reset = g_new(K230SysctlCpu1Reset, 1); + reset->rstvec = rstvec; + s->last_cpu1_rstvec = reset->rstvec; + run_on_cpu(s->cpu1, k230_sysctl_reset_cpu1_async_work, + RUN_ON_CPU_HOST_PTR(reset)); +} + +static void k230_sysctl_reset_release_cpu1_now(K230SysctlResetState *s) +{ + uint32_t rstvec; + + if (!s->cpu1 || !s->boot) { + return; + } + + rstvec = k230_sysctl_reg_read32(s->boot->regs, K230_SYSCTL_CPU1_RSTVEC); + k230_sysctl_reset_release_cpu1_rstvec(s, rstvec); +} + +static void k230_sysctl_reset_release_cpu1_timer(void *opaque) +{ + K230SysctlResetState *s = opaque; + uint32_t rstvec = s->deferred_rstvec; + + k230_sysctl_reset_restore_rtt(s); + k230_sysctl_reset_release_cpu1_rstvec(s, rstvec); +} + +static void k230_sysctl_reset_defer_cpu1(K230SysctlResetState *s) +{ + void *zeros; + + k230_sysctl_reset_cancel_deferred_release(s); + + s->rtt_addr = k230_sysctl_reg_read32(s->boot->regs, + K230_SYSCTL_CPU1_RSTVEC); + s->deferred_rstvec = s->rtt_addr; + s->rtt_size = K230_SYSCTL_RTT_SAVE_SIZE; + s->rtt_saved = g_malloc(s->rtt_size); + physical_memory_read(s->rtt_addr, s->rtt_saved, s->rtt_size); + zeros = g_malloc0(s->rtt_size); + physical_memory_write(s->rtt_addr, zeros, s->rtt_size); + g_free(zeros); + s->rtt_saved_valid = true; + + /* + * Defer CPU1 release to give Linux time to boot before the big + * core starts executing. + */ + timer_mod(s->release_timer, + qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 15000); +} + +static void k230_sysctl_reset_release_cpu1(K230SysctlResetState *s) +{ + if (!s->cpu1 || !s->boot) { + return; + } + + if (s->defer_cpu1_release) { + k230_sysctl_reset_defer_cpu1(s); + return; + } + + k230_sysctl_reset_release_cpu1_now(s); +} + +static void k230_sysctl_reset_write_cpu1(K230SysctlResetState *s, + uint32_t val) +{ + uint32_t old = k230_sysctl_reg_read32(s->regs, + K230_SYSCTL_CPU1_RST_CTL); + uint32_t new = old; + + if ((val & K230_SYSCTL_CPU1_RST_DONE_WEN) && + (val & K230_SYSCTL_CPU1_RST_DONE)) { + new &= ~K230_SYSCTL_CPU1_RST_DONE; + } + if ((val & K230_SYSCTL_CPU1_PRST_DONE_WEN) && + (val & K230_SYSCTL_CPU1_PRST_DONE)) { + new &= ~K230_SYSCTL_CPU1_PRST_DONE; + } + + if (val & K230_SYSCTL_CPU1_RST_REQ_WEN) { + if (val & K230_SYSCTL_CPU1_RST_REQ) { + new |= K230_SYSCTL_CPU1_RST_REQ; + } else { + new &= ~K230_SYSCTL_CPU1_RST_REQ; + } + } + + if (!(old & K230_SYSCTL_CPU1_RST_REQ) && + (new & K230_SYSCTL_CPU1_RST_REQ)) { + k230_sysctl_reset_cancel_deferred_release(s); + k230_sysctl_reset_assert_cpu1(s); + } + + if ((old & K230_SYSCTL_CPU1_RST_REQ) && + !(new & K230_SYSCTL_CPU1_RST_REQ)) { + k230_sysctl_reset_release_cpu1(s); + new |= K230_SYSCTL_CPU1_RST_DONE; + } + + k230_sysctl_reg_write32(s->regs, K230_SYSCTL_CPU1_RST_CTL, new); +} + +static void k230_sysctl_reset_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230SysctlResetState *s = K230_SYSCTL_RESET(opaque); + + if (size == 4 && addr == K230_SYSCTL_CPU1_RST_CTL) { + k230_sysctl_reset_write_cpu1(s, val); + } else { + k230_sysctl_write_bytes(s->regs, addr, val, size); + } +} + +static const MemoryRegionOps k230_sysctl_reset_ops = { + .read = k230_sysctl_reset_read, + .write = k230_sysctl_reset_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_sysctl_reset_reset(DeviceState *dev) +{ + K230SysctlResetState *s = K230_SYSCTL_RESET(dev); + + k230_sysctl_reset_cancel_deferred_release(s); + memset(s->regs, 0, sizeof(s->regs)); + k230_sysctl_reg_write32(s->regs, K230_SYSCTL_CPU1_RST_CTL, + K230_SYSCTL_CPU1_RST_CTL_RESET); + s->last_cpu1_rstvec = 0; + s->deferred_rstvec = 0; +} + +static const VMStateDescription vmstate_k230_sysctl_reset = { + .name = TYPE_K230_SYSCTL_RESET, + .version_id = 4, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, K230SysctlResetState, K230_SYSCTL_SIZE), + VMSTATE_UINT32_V(last_cpu1_rstvec, K230SysctlResetState, 2), + VMSTATE_BOOL_V(rtt_saved_valid, K230SysctlResetState, 3), + VMSTATE_UINT64_TEST(rtt_addr, K230SysctlResetState, + k230_sysctl_reset_has_rtt_saved), + VMSTATE_UINT32_TEST(rtt_size, K230SysctlResetState, + k230_sysctl_reset_has_rtt_saved), + VMSTATE_VBUFFER_ALLOC_UINT32(rtt_saved, K230SysctlResetState, 3, + k230_sysctl_reset_has_rtt_saved, + rtt_size), + VMSTATE_TIMER_PTR_V(release_timer, K230SysctlResetState, 3), + VMSTATE_UINT32_TEST(deferred_rstvec, K230SysctlResetState, + k230_sysctl_reset_has_rtt_saved), + VMSTATE_END_OF_LIST(), + }, +}; + +static const Property k230_sysctl_reset_properties[] = { + DEFINE_PROP_LINK("boot", K230SysctlResetState, boot, + TYPE_K230_SYSCTL_BOOT, K230SysctlBootState *), + DEFINE_PROP_LINK("cpu1", K230SysctlResetState, cpu1, TYPE_CPU, + CPUState *), + DEFINE_PROP_BOOL("defer-cpu1-release", K230SysctlResetState, + defer_cpu1_release, false), +}; + +static void k230_sysctl_reset_realize(DeviceState *dev, Error **errp) +{ + K230SysctlResetState *s = K230_SYSCTL_RESET(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_sysctl_reset_ops, s, + TYPE_K230_SYSCTL_RESET, K230_SYSCTL_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); + + s->release_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, + k230_sysctl_reset_release_cpu1_timer, s); +} + +static void k230_sysctl_reset_init(Object *obj) +{ + K230SysctlResetState *s = K230_SYSCTL_RESET(obj); + + object_property_add_uint32_ptr(obj, "last-cpu1-rstvec", + &s->last_cpu1_rstvec, + OBJ_PROP_FLAG_READ); +} + + +static void k230_sysctl_reset_finalize(Object *obj) +{ + K230SysctlResetState *s = K230_SYSCTL_RESET(obj); + + if (s->release_timer) { + timer_free(s->release_timer); + s->release_timer = NULL; + } + g_free(s->rtt_saved); + s->rtt_saved = NULL; + s->rtt_saved_valid = false; +} + +static void k230_sysctl_reset_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_sysctl_reset_realize; + device_class_set_props(dc, k230_sysctl_reset_properties); + device_class_set_legacy_reset(dc, k230_sysctl_reset_reset); + dc->vmsd = &vmstate_k230_sysctl_reset; + dc->desc = "K230 sysctl reset registers"; +} + +static const TypeInfo k230_sysctl_reset_type_info = { + .name = TYPE_K230_SYSCTL_RESET, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230SysctlResetState), + .instance_init = k230_sysctl_reset_init, + .instance_finalize = k230_sysctl_reset_finalize, + .class_init = k230_sysctl_reset_class_init, +}; + +static void k230_sysctl_register_types(void) +{ + type_register_static(&k230_sysctl_boot_type_info); + type_register_static(&k230_sysctl_power_type_info); + type_register_static(&k230_sysctl_reset_type_info); +} + +type_init(k230_sysctl_register_types) diff --git a/hw/misc/k230_timer.c b/hw/misc/k230_timer.c new file mode 100644 index 0000000000000..56f873f2bd6f8 --- /dev/null +++ b/hw/misc/k230_timer.c @@ -0,0 +1,208 @@ +/* + * K230 low-speed hardware timers + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/module.h" +#include "qemu/timer.h" +#include "migration/vmstate.h" +#include "hw/misc/k230_timer.h" + +#define K230_TIMER_FREQ 27000000ULL +#define K230_TIMER_CH_STRIDE 0x14 +#define K230_TIMER_LOAD_COUNT 0x00 +#define K230_TIMER_CURRENT_VALUE 0x04 +#define K230_TIMER_CONTROL 0x08 +#define K230_TIMER_EOI 0x0c +#define K230_TIMER_INTR_STAT 0x10 + +static uint64_t k230_timer_read_bytes(uint8_t *regs, hwaddr addr, + unsigned int size) +{ + uint64_t val = 0; + + for (int i = 0; i < size; i++) { + val |= (uint64_t)regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_timer_write_bytes(uint8_t *regs, hwaddr addr, uint64_t val, + unsigned int size) +{ + for (int i = 0; i < size; i++) { + regs[addr + i] = val >> (i * 8); + } +} + +static uint32_t k230_timer_reg_read32(K230TimerState *s, hwaddr addr) +{ + return ldl_le_p(s->regs + addr); +} + +static void k230_timer_reg_write32(K230TimerState *s, hwaddr addr, + uint32_t val) +{ + stl_le_p(s->regs + addr, val); +} + +static bool k230_timer_decode(hwaddr addr, unsigned int size, + unsigned int *channel, hwaddr *reg) +{ + if (size != 4) { + return false; + } + + *channel = addr / K230_TIMER_CH_STRIDE; + *reg = addr % K230_TIMER_CH_STRIDE; + return *channel < K230_TIMER_COUNT; +} + +static uint32_t k230_timer_current(K230TimerState *s, unsigned int channel) +{ + hwaddr base = channel * K230_TIMER_CH_STRIDE; + uint32_t load = k230_timer_reg_read32(s, base + K230_TIMER_LOAD_COUNT); + uint32_t control = k230_timer_reg_read32(s, base + K230_TIMER_CONTROL); + int64_t now; + uint64_t ticks; + + if (!(control & BIT(0))) { + return k230_timer_reg_read32(s, base + K230_TIMER_CURRENT_VALUE); + } + + now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + ticks = muldiv64(now - s->start_ns[channel], K230_TIMER_FREQ, + NANOSECONDS_PER_SECOND); + + return ticks >= load ? 0 : load - ticks; +} + +static void k230_timer_restart(K230TimerState *s, unsigned int channel) +{ + hwaddr base = channel * K230_TIMER_CH_STRIDE; + + s->start_ns[channel] = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + k230_timer_reg_write32(s, base + K230_TIMER_CURRENT_VALUE, + k230_timer_reg_read32(s, + base + K230_TIMER_LOAD_COUNT)); +} + +static uint64_t k230_timer_read(void *opaque, hwaddr addr, unsigned int size) +{ + K230TimerState *s = K230_TIMER(opaque); + unsigned int channel; + hwaddr reg; + + if (k230_timer_decode(addr, size, &channel, ®)) { + hwaddr base = channel * K230_TIMER_CH_STRIDE; + + switch (reg) { + case K230_TIMER_CURRENT_VALUE: + return k230_timer_current(s, channel); + case K230_TIMER_INTR_STAT: + return k230_timer_current(s, channel) == 0 ? 1 : 0; + case K230_TIMER_EOI: + k230_timer_reg_write32(s, base + K230_TIMER_INTR_STAT, 0); + return 1; + default: + break; + } + } + + return k230_timer_read_bytes(s->regs, addr, size); +} + +static void k230_timer_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230TimerState *s = K230_TIMER(opaque); + unsigned int channel; + hwaddr reg; + + k230_timer_write_bytes(s->regs, addr, val, size); + + if (k230_timer_decode(addr, size, &channel, ®) && + (reg == K230_TIMER_LOAD_COUNT || reg == K230_TIMER_CONTROL)) { + k230_timer_restart(s, channel); + } +} + +static const MemoryRegionOps k230_timer_ops = { + .read = k230_timer_read, + .write = k230_timer_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_timer_reset(DeviceState *dev) +{ + K230TimerState *s = K230_TIMER(dev); + + memset(s->regs, 0, sizeof(s->regs)); + for (int i = 0; i < K230_TIMER_COUNT; i++) { + hwaddr base = i * K230_TIMER_CH_STRIDE; + + k230_timer_reg_write32(s, base + K230_TIMER_LOAD_COUNT, UINT32_MAX); + k230_timer_reg_write32(s, base + K230_TIMER_CURRENT_VALUE, + UINT32_MAX); + s->start_ns[i] = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + } +} + +static const VMStateDescription vmstate_k230_timer = { + .name = TYPE_K230_TIMER, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, K230TimerState, K230_TIMER_SIZE), + VMSTATE_INT64_ARRAY(start_ns, K230TimerState, K230_TIMER_COUNT), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_timer_realize(DeviceState *dev, Error **errp) +{ + K230TimerState *s = K230_TIMER(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_timer_ops, s, + TYPE_K230_TIMER, K230_TIMER_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); +} + +static void k230_timer_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_timer_realize; + device_class_set_legacy_reset(dc, k230_timer_reset); + dc->vmsd = &vmstate_k230_timer; + dc->desc = "K230 low-speed hardware timers"; +} + +static const TypeInfo k230_timer_type_info = { + .name = TYPE_K230_TIMER, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230TimerState), + .class_init = k230_timer_class_init, +}; + +static void k230_register_timer_types(void) +{ + type_register_static(&k230_timer_type_info); +} + +type_init(k230_register_timer_types) diff --git a/hw/misc/k230_tsensor.c b/hw/misc/k230_tsensor.c new file mode 100644 index 0000000000000..a08ee0d5e1508 --- /dev/null +++ b/hw/misc/k230_tsensor.c @@ -0,0 +1,125 @@ +/* + * K230 temperature sensor + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "migration/vmstate.h" +#include "hw/misc/k230_tsensor.h" + +#define K230_TSENSOR_CONFIG 0x00 +#define K230_TSENSOR_DATA 0x04 +#define K230_TSENSOR_TEMP 42000 + +static uint64_t k230_tsensor_read_bytes(uint8_t *regs, hwaddr addr, + unsigned int size) +{ + uint64_t val = 0; + + for (int i = 0; i < size; i++) { + val |= (uint64_t)regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_tsensor_write_bytes(uint8_t *regs, hwaddr addr, + uint64_t val, unsigned int size) +{ + for (int i = 0; i < size; i++) { + regs[addr + i] = val >> (i * 8); + } +} + +static uint64_t k230_tsensor_read(void *opaque, hwaddr addr, + unsigned int size) +{ + K230TSensorState *s = K230_TSENSOR(opaque); + + if (addr == K230_TSENSOR_DATA && size == 4) { + return K230_TSENSOR_TEMP; + } + + return k230_tsensor_read_bytes(s->regs, addr, size); +} + +static void k230_tsensor_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230TSensorState *s = K230_TSENSOR(opaque); + + k230_tsensor_write_bytes(s->regs, addr, val, size); + if (addr == K230_TSENSOR_CONFIG && size == 4) { + stl_le_p(s->regs + K230_TSENSOR_DATA, K230_TSENSOR_TEMP); + } +} + +static const MemoryRegionOps k230_tsensor_ops = { + .read = k230_tsensor_read, + .write = k230_tsensor_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_tsensor_reset(DeviceState *dev) +{ + K230TSensorState *s = K230_TSENSOR(dev); + + memset(s->regs, 0, sizeof(s->regs)); + stl_le_p(s->regs + K230_TSENSOR_DATA, K230_TSENSOR_TEMP); +} + +static const VMStateDescription vmstate_k230_tsensor = { + .name = TYPE_K230_TSENSOR, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, K230TSensorState, K230_TSENSOR_SIZE), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_tsensor_realize(DeviceState *dev, Error **errp) +{ + K230TSensorState *s = K230_TSENSOR(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_tsensor_ops, s, + TYPE_K230_TSENSOR, K230_TSENSOR_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); +} + +static void k230_tsensor_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_tsensor_realize; + device_class_set_legacy_reset(dc, k230_tsensor_reset); + dc->vmsd = &vmstate_k230_tsensor; + dc->desc = "K230 temperature sensor"; +} + +static const TypeInfo k230_tsensor_type_info = { + .name = TYPE_K230_TSENSOR, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230TSensorState), + .class_init = k230_tsensor_class_init, +}; + +static void k230_register_tsensor_types(void) +{ + type_register_static(&k230_tsensor_type_info); +} + +type_init(k230_register_tsensor_types) diff --git a/hw/misc/k230_ugzip.c b/hw/misc/k230_ugzip.c new file mode 100644 index 0000000000000..aba5022f5be3f --- /dev/null +++ b/hw/misc/k230_ugzip.c @@ -0,0 +1,276 @@ +/* + * K230 UGZIP decompressor + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/error-report.h" +#include "qemu/module.h" +#include "migration/vmstate.h" +#include "system/dma.h" +#include "hw/misc/k230_ugzip.h" +#include + +#define K230_UGZIP_START 0x00 +#define K230_UGZIP_SRC_SIZE 0x04 +#define K230_UGZIP_OUT_SIZE 0x08 +#define K230_UGZIP_INTSTAT 0x0c +#define K230_UGZIP_SRC_VALID BIT(31) +#define K230_UGZIP_DONE BIT(10) + +#define K230_SDMA_LLT_SIZE 24 +#define K230_SDMA_LLT_SRC_ADDR 4 +#define K230_SDMA_LLT_LINE_SIZE 8 +#define K230_SDMA_LLT_DST_ADDR 16 +#define K230_SDMA_LLT_NEXT_ADDR 20 + +static uint64_t k230_ugzip_read_bytes(uint8_t *regs, hwaddr addr, + unsigned int size) +{ + uint64_t val = 0; + + for (int i = 0; i < size; i++) { + val |= (uint64_t)regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_ugzip_write_bytes(uint8_t *regs, hwaddr addr, uint64_t val, + unsigned int size) +{ + for (int i = 0; i < size; i++) { + regs[addr + i] = val >> (i * 8); + } +} + +static uint32_t k230_ugzip_reg_read32(K230UgzipState *s, hwaddr addr) +{ + return ldl_le_p(s->regs + addr); +} + +static void k230_ugzip_reg_write32(K230UgzipState *s, hwaddr addr, + uint32_t val) +{ + stl_le_p(s->regs + addr, val); +} + +static bool k230_dma_read(hwaddr addr, void *buf, size_t len) +{ + return dma_memory_read(&address_space_memory, addr, buf, len, + MEMTXATTRS_UNSPECIFIED) == MEMTX_OK; +} + +static bool k230_dma_write(hwaddr addr, const void *buf, size_t len) +{ + return dma_memory_write(&address_space_memory, addr, buf, len, + MEMTXATTRS_UNSPECIFIED) == MEMTX_OK; +} + +static bool k230_ugzip_read_src_llt(uint32_t llt_addr, uint8_t *buf, + size_t len) +{ + uint8_t desc[K230_SDMA_LLT_SIZE]; + size_t done = 0; + + for (int i = 0; llt_addr && done < len && i < 1024; i++) { + uint32_t src_addr; + uint32_t line_size; + size_t copy_len; + + if (!k230_dma_read(llt_addr, desc, sizeof(desc))) { + return false; + } + + src_addr = ldl_le_p(desc + K230_SDMA_LLT_SRC_ADDR); + line_size = ldl_le_p(desc + K230_SDMA_LLT_LINE_SIZE); + copy_len = MIN((size_t)line_size, len - done); + if (!copy_len || !k230_dma_read(src_addr, buf + done, copy_len)) { + return false; + } + + done += copy_len; + llt_addr = ldl_le_p(desc + K230_SDMA_LLT_NEXT_ADDR); + } + + return done == len; +} + +static bool k230_ugzip_write_dst_llt(uint32_t llt_addr, const uint8_t *buf, + size_t len) +{ + uint8_t desc[K230_SDMA_LLT_SIZE]; + size_t done = 0; + + for (int i = 0; llt_addr && done < len && i < 1024; i++) { + uint32_t dst_addr; + uint32_t line_size; + size_t copy_len; + + if (!k230_dma_read(llt_addr, desc, sizeof(desc))) { + return false; + } + + dst_addr = ldl_le_p(desc + K230_SDMA_LLT_DST_ADDR); + line_size = ldl_le_p(desc + K230_SDMA_LLT_LINE_SIZE); + copy_len = MIN((size_t)line_size, len - done); + if (!copy_len || !k230_dma_write(dst_addr, buf + done, copy_len)) { + return false; + } + + done += copy_len; + llt_addr = ldl_le_p(desc + K230_SDMA_LLT_NEXT_ADDR); + } + + return done == len; +} + +static int k230_ugzip_inflate(uint8_t *dst, size_t *dst_len, + uint8_t *src, size_t src_len) +{ + z_stream strm = { 0 }; + int ret; + + if (src_len >= 3 && src[0] == 0x1f && src[1] == 0x8b && src[2] == 0x09) { + src[2] = 0x08; + } + + ret = inflateInit2(&strm, 16 + MAX_WBITS); + if (ret != Z_OK) { + return ret; + } + + strm.next_in = src; + strm.avail_in = src_len; + strm.next_out = dst; + strm.avail_out = *dst_len; + + ret = inflate(&strm, Z_FINISH); + *dst_len -= strm.avail_out; + inflateEnd(&strm); + + return ret == Z_STREAM_END ? Z_OK : ret; +} + +static void k230_ugzip_start(K230UgzipState *s) +{ + uint32_t src_len = k230_ugzip_reg_read32(s, K230_UGZIP_SRC_SIZE) & + ~K230_UGZIP_SRC_VALID; + uint32_t out_limit = k230_ugzip_reg_read32(s, K230_UGZIP_OUT_SIZE); + uint32_t src_llt; + uint32_t dst_llt; + g_autofree uint8_t *src = NULL; + g_autofree uint8_t *dst = NULL; + size_t out_len = out_limit; + bool ok = false; + + if (!s->gsdma) { + warn_report("k230 ugzip has no gsdma link"); + return; + } + + src_llt = k230_gsdma_get_llt_saddr(s->gsdma, K230_GSDMA_UGZIP_RD_CH); + dst_llt = k230_gsdma_get_llt_saddr(s->gsdma, K230_GSDMA_UGZIP_WR_CH); + + if (src_len && out_limit && src_llt && dst_llt) { + src = g_malloc(src_len); + dst = g_malloc0(out_limit); + ok = k230_ugzip_read_src_llt(src_llt, src, src_len) && + k230_ugzip_inflate(dst, &out_len, src, src_len) == Z_OK && + k230_ugzip_write_dst_llt(dst_llt, dst, out_len); + } + + k230_ugzip_reg_write32(s, K230_UGZIP_INTSTAT, + ok ? K230_UGZIP_DONE : 0); + k230_gsdma_ugzip_complete(s->gsdma); + + if (!ok) { + warn_report("k230 ugzip decompression failed"); + } +} + +static uint64_t k230_ugzip_read(void *opaque, hwaddr addr, unsigned int size) +{ + return k230_ugzip_read_bytes(K230_UGZIP(opaque)->regs, addr, size); +} + +static void k230_ugzip_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230UgzipState *s = K230_UGZIP(opaque); + + k230_ugzip_write_bytes(s->regs, addr, val, size); + + if (addr == K230_UGZIP_START && size == 4 && (val & BIT(0))) { + k230_ugzip_start(s); + } +} + +static const MemoryRegionOps k230_ugzip_ops = { + .read = k230_ugzip_read, + .write = k230_ugzip_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_ugzip_reset(DeviceState *dev) +{ + K230UgzipState *s = K230_UGZIP(dev); + + memset(s->regs, 0, sizeof(s->regs)); +} + +static const VMStateDescription vmstate_k230_ugzip = { + .name = TYPE_K230_UGZIP, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, K230UgzipState, K230_UGZIP_SIZE), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_ugzip_realize(DeviceState *dev, Error **errp) +{ + K230UgzipState *s = K230_UGZIP(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_ugzip_ops, s, + TYPE_K230_UGZIP, K230_UGZIP_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); +} + +static void k230_ugzip_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_ugzip_realize; + device_class_set_legacy_reset(dc, k230_ugzip_reset); + dc->vmsd = &vmstate_k230_ugzip; + dc->desc = "K230 UGZIP decompressor"; +} + +static const TypeInfo k230_ugzip_type_info = { + .name = TYPE_K230_UGZIP, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230UgzipState), + .class_init = k230_ugzip_class_init, +}; + +static void k230_register_ugzip_types(void) +{ + type_register_static(&k230_ugzip_type_info); +} + +type_init(k230_register_ugzip_types) diff --git a/hw/misc/meson.build b/hw/misc/meson.build index 23265f6035bd5..a1953b05acd1d 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -9,6 +9,9 @@ system_ss.add(when: 'CONFIG_UNIMP', if_true: files('unimp.c')) 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_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')) # ARM devices system_ss.add(when: 'CONFIG_PL310', if_true: files('arm_l2x0.c')) @@ -36,6 +39,26 @@ system_ss.add(when: 'CONFIG_SIFIVE_E_PRCI', if_true: files('sifive_e_prci.c')) system_ss.add(when: 'CONFIG_SIFIVE_E_AON', if_true: files('sifive_e_aon.c')) system_ss.add(when: 'CONFIG_SIFIVE_U_OTP', if_true: files('sifive_u_otp.c')) system_ss.add(when: 'CONFIG_SIFIVE_U_PRCI', if_true: files('sifive_u_prci.c')) +system_ss.add(when: 'CONFIG_SOPHGO_CV1800B_CLK', if_true: files('cv1800b_clk.c')) +system_ss.add(when: 'CONFIG_K230_HI_SYS_CFG', if_true: files('k230_hi_sys_cfg.c')) +system_ss.add(when: 'CONFIG_K230_HARDLOCK', if_true: files('k230_hardlock.c')) +system_ss.add(when: 'CONFIG_K230_TSENSOR', if_true: files('k230_tsensor.c')) +system_ss.add(when: 'CONFIG_K230_GPIO', if_true: files('k230_gpio.c')) +system_ss.add(when: 'CONFIG_K230_IOMUX', if_true: files('k230_iomux.c')) +system_ss.add(when: 'CONFIG_K230_PMU', if_true: files('k230_pmu.c')) +system_ss.add(when: 'CONFIG_K230_REGS', if_true: files('k230_regs.c')) +system_ss.add(when: 'CONFIG_K230_I2S', if_true: files('k230_i2s.c')) +system_ss.add(when: 'CONFIG_K230_KPU', if_true: files('k230_kpu.c')) +system_ss.add(when: 'CONFIG_K230_NONAI_2D', if_true: files('k230_nonai_2d.c')) +system_ss.add(when: 'CONFIG_K230_DEWARP', if_true: files('k230_dewarp.c')) +system_ss.add(when: 'CONFIG_K230_ISP', if_true: files('k230_isp.c')) +system_ss.add(when: 'CONFIG_K230_RX_CSI', if_true: files('k230_rx_csi.c')) +system_ss.add(when: 'CONFIG_K230_ADC', if_true: files('k230_adc.c')) +system_ss.add(when: 'CONFIG_K230_PWM', if_true: files('k230_pwm.c')) +system_ss.add(when: 'CONFIG_K230_TIMER', if_true: files('k230_timer.c')) +system_ss.add(when: 'CONFIG_K230_SYSCTL', if_true: files('k230_sysctl.c')) +system_ss.add(when: 'CONFIG_K230_SECURITY', if_true: files('k230_security.c')) +system_ss.add(when: 'CONFIG_K230_UGZIP', if_true: files('k230_ugzip.c')) subdir('macio') @@ -158,6 +181,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_CRU', if_true: files('rk3588_cru.c')) +system_ss.add(when: 'CONFIG_RK3588_SCMI', if_true: files('rk3588_scmi.c')) +system_ss.add(when: 'CONFIG_SPACEMIT_K3_CTRL', if_true: files('spacemit-k3.c')) +system_ss.add(when: 'CONFIG_NXP_S32_MC_ME', if_true: files('nxp_s32_mc_me.c')) + specific_ss.add(when: 'CONFIG_MIPS_CPS', if_true: files('mips_cmgcr.c', 'mips_cpc.c')) specific_ss.add(when: 'CONFIG_MIPS_ITU', if_true: files('mips_itu.c')) @@ -168,3 +196,7 @@ system_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c')) # HPPA devices system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c')) +system_ss.add(when: 'CONFIG_AX650X_DWMAC_GLUE', + if_true: files('ax650x-dwmac-glue.c')) +system_ss.add(when: 'CONFIG_AX650X_HWSPINLOCK', + if_true: files('ax650x-hwspinlock.c')) diff --git a/hw/misc/nxp_s32_mc_me.c b/hw/misc/nxp_s32_mc_me.c new file mode 100644 index 0000000000000..d626df4acd596 --- /dev/null +++ b/hw/misc/nxp_s32_mc_me.c @@ -0,0 +1,150 @@ +/* + * NXP S32 Mode Entry module + * + * This is a small model of the MC_ME register flow used by Zephyr's S32K5 + * early startup code to ungate the startup watchdog block. + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/misc/nxp_s32_mc_me.h" +#include "hw/core/registerfields.h" +#include "migration/vmstate.h" +#include "qemu/module.h" + +REG32(CTL_KEY, 0x000) + FIELD(CTL_KEY, KEY, 0, 16) +REG32(MODE_CONF, 0x004) +REG32(MODE_UPD, 0x008) + FIELD(MODE_UPD, MODE_UPD, 0, 1) +REG32(MODE_STAT, 0x00c) +REG32(MAIN_COREID, 0x010) +REG32(PRTN2_PUPD, 0x504) + FIELD(PRTN2_PUPD, PCUD, 0, 1) +REG32(PRTN2_COFB1_STAT, 0x514) +REG32(PRTN2_COFB1_CLKEN, 0x534) + +#define MC_ME_CTL_KEY_DIRECT_KEY 0x5af0 +#define MC_ME_CTL_KEY_INVERTED_KEY 0xa50f + +static void nxp_s32_mc_me_complete_update(NXPS32MCMEState *s) +{ + if (s->regs[R_PRTN2_PUPD] & R_PRTN2_PUPD_PCUD_MASK) { + s->regs[R_PRTN2_COFB1_STAT] |= s->regs[R_PRTN2_COFB1_CLKEN]; + s->regs[R_PRTN2_PUPD] &= ~R_PRTN2_PUPD_PCUD_MASK; + } + + if (s->regs[R_MODE_UPD] & R_MODE_UPD_MODE_UPD_MASK) { + s->regs[R_MODE_STAT] = s->regs[R_MODE_CONF]; + s->regs[R_MODE_UPD] &= ~R_MODE_UPD_MODE_UPD_MASK; + } +} + +static void nxp_s32_mc_me_ctl_key_post_write(RegisterInfo *reg, uint64_t val) +{ + NXPS32MCMEState *s = NXP_S32_MC_ME(reg->opaque); + uint32_t key = FIELD_EX32(val, CTL_KEY, KEY); + + if (key == MC_ME_CTL_KEY_DIRECT_KEY) { + s->ctl_key_state = 1; + return; + } + + if (key == MC_ME_CTL_KEY_INVERTED_KEY && s->ctl_key_state == 1) { + nxp_s32_mc_me_complete_update(s); + } + + s->ctl_key_state = 0; +} + +static const RegisterAccessInfo nxp_s32_mc_me_regs_info[] = { + { .name = "CTL_KEY", .addr = A_CTL_KEY, + .rsvd = UINT32_MAX & ~R_CTL_KEY_KEY_MASK, + .post_write = nxp_s32_mc_me_ctl_key_post_write, + },{ .name = "MODE_CONF", .addr = A_MODE_CONF, + },{ .name = "MODE_UPD", .addr = A_MODE_UPD, + },{ .name = "MODE_STAT", .addr = A_MODE_STAT, + .ro = UINT32_MAX, + },{ .name = "MAIN_COREID", .addr = A_MAIN_COREID, + .ro = UINT32_MAX, + },{ .name = "PRTN2_PUPD", .addr = A_PRTN2_PUPD, + },{ .name = "PRTN2_COFB1_STAT", .addr = A_PRTN2_COFB1_STAT, + .ro = UINT32_MAX, + },{ .name = "PRTN2_COFB1_CLKEN", .addr = A_PRTN2_COFB1_CLKEN, + } +}; + +static const MemoryRegionOps nxp_s32_mc_me_ops = { + .read = register_read_memory, + .write = register_write_memory, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void nxp_s32_mc_me_reset(DeviceState *dev) +{ + NXPS32MCMEState *s = NXP_S32_MC_ME(dev); + + s->ctl_key_state = 0; + + for (int i = 0; i < ARRAY_SIZE(s->regs_info); i++) { + register_reset(&s->regs_info[i]); + } +} + +static void nxp_s32_mc_me_init(Object *obj) +{ + NXPS32MCMEState *s = NXP_S32_MC_ME(obj); + DeviceState *dev = DEVICE(obj); + + s->reg_array = register_init_block32(dev, nxp_s32_mc_me_regs_info, + ARRAY_SIZE(nxp_s32_mc_me_regs_info), + s->regs_info, s->regs, + &nxp_s32_mc_me_ops, false, + NXP_S32_MC_ME_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->reg_array->mem); +} + +static const VMStateDescription vmstate_nxp_s32_mc_me = { + .name = TYPE_NXP_S32_MC_ME, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, NXPS32MCMEState, NXP_S32_MC_ME_R_MAX), + VMSTATE_UINT8(ctl_key_state, NXPS32MCMEState), + VMSTATE_END_OF_LIST() + } +}; + +static void nxp_s32_mc_me_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, nxp_s32_mc_me_reset); + dc->vmsd = &vmstate_nxp_s32_mc_me; +} + +static const TypeInfo nxp_s32_mc_me_info = { + .name = TYPE_NXP_S32_MC_ME, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NXPS32MCMEState), + .instance_init = nxp_s32_mc_me_init, + .class_init = nxp_s32_mc_me_class_init, +}; + +static void nxp_s32_mc_me_register_types(void) +{ + type_register_static(&nxp_s32_mc_me_info); +} + +type_init(nxp_s32_mc_me_register_types) diff --git a/hw/misc/phytium-ddr-ctrl.c b/hw/misc/phytium-ddr-ctrl.c new file mode 100644 index 0000000000000..06a5b2c2d03af --- /dev/null +++ b/hw/misc/phytium-ddr-ctrl.c @@ -0,0 +1,142 @@ +/* + * Phytium DDR controller shim + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/misc/phytium-ddr-ctrl.h" +#include "migration/vmstate.h" +#include "qemu/module.h" + +#define PHYTIUM_DDR_CTRL_INDEX_OFFSET 0x80 +#define PHYTIUM_DDR_CTRL_DATA_OFFSET 0x84 +#define PHYTIUM_DDR_CTRL_POLL_INDEX ((0x76 + 0x800) << 2) +#define PHYTIUM_DDR_CTRL_DONE (1u << 27) +#define PHYTIUM_DDR_CTRL_READY_BITS \ + (PHYTIUM_DDR_CTRL_DONE | (1u << 25) | 1u) + +static uint64_t phytium_ddr_ctrl_read(void *opaque, hwaddr offset, + unsigned size) +{ + PhytiumDdrCtrlState *s = opaque; + unsigned int index = offset / sizeof(uint32_t); + unsigned int shift = (offset & 3) * 8; + uint32_t mask = size == 4 ? UINT32_MAX : (1u << (size * 8)) - 1; + uint32_t ddr_index; + uint32_t value; + + if (index >= ARRAY_SIZE(s->regs)) { + return 0; + } + + value = s->regs[index]; + ddr_index = s->regs[PHYTIUM_DDR_CTRL_INDEX_OFFSET / sizeof(uint32_t)]; + + if ((offset & ~3) == PHYTIUM_DDR_CTRL_DATA_OFFSET) { + if (ddr_index == PHYTIUM_DDR_CTRL_POLL_INDEX) { + value = PHYTIUM_DDR_CTRL_DONE; + } else if (ddr_index < PHYTIUM_DDR_INDEX_SPACE_SIZE) { + unsigned int ddr_reg = ddr_index / sizeof(uint32_t); + + value = s->index_regs[ddr_reg] ?: PHYTIUM_DDR_CTRL_READY_BITS; + } + } + + return (value >> shift) & mask; +} + +static void phytium_ddr_ctrl_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + PhytiumDdrCtrlState *s = opaque; + unsigned int index = offset / sizeof(uint32_t); + unsigned int shift = (offset & 3) * 8; + uint32_t mask = size == 4 ? UINT32_MAX : ((1u << (size * 8)) - 1) << shift; + uint32_t ddr_index; + uint32_t *reg; + + if (index >= ARRAY_SIZE(s->regs)) { + return; + } + + reg = &s->regs[index]; + ddr_index = s->regs[PHYTIUM_DDR_CTRL_INDEX_OFFSET / sizeof(uint32_t)]; + *reg = (*reg & ~mask) | (((uint32_t)value << shift) & mask); + + if ((offset & ~3) == PHYTIUM_DDR_CTRL_DATA_OFFSET && + ddr_index < PHYTIUM_DDR_INDEX_SPACE_SIZE) { + unsigned int ddr_reg = ddr_index / sizeof(uint32_t); + + s->index_regs[ddr_reg] = *reg; + } +} + +static const MemoryRegionOps phytium_ddr_ctrl_ops = { + .read = phytium_ddr_ctrl_read, + .write = phytium_ddr_ctrl_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +static void phytium_ddr_ctrl_reset(DeviceState *dev) +{ + PhytiumDdrCtrlState *s = PHYTIUM_DDR_CTRL(dev); + + memset(s->regs, 0, sizeof(s->regs)); + memset(s->index_regs, 0, sizeof(s->index_regs)); +} + +static void phytium_ddr_ctrl_init(Object *obj) +{ + PhytiumDdrCtrlState *s = PHYTIUM_DDR_CTRL(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + memory_region_init_io(&s->iomem, obj, &phytium_ddr_ctrl_ops, s, + TYPE_PHYTIUM_DDR_CTRL, + PHYTIUM_DDR_CTRL_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->iomem); +} + +static const VMStateDescription vmstate_phytium_ddr_ctrl = { + .name = TYPE_PHYTIUM_DDR_CTRL, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, PhytiumDdrCtrlState, + PHYTIUM_DDR_CTRL_MMIO_SIZE / sizeof(uint32_t)), + VMSTATE_UINT32_ARRAY(index_regs, PhytiumDdrCtrlState, + PHYTIUM_DDR_INDEX_SPACE_SIZE / sizeof(uint32_t)), + VMSTATE_END_OF_LIST() + }, +}; + +static void phytium_ddr_ctrl_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, phytium_ddr_ctrl_reset); + dc->vmsd = &vmstate_phytium_ddr_ctrl; +} + +static const TypeInfo phytium_ddr_ctrl_info = { + .name = TYPE_PHYTIUM_DDR_CTRL, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PhytiumDdrCtrlState), + .instance_init = phytium_ddr_ctrl_init, + .class_init = phytium_ddr_ctrl_class_init, +}; + +static void phytium_ddr_ctrl_register_types(void) +{ + type_register_static(&phytium_ddr_ctrl_info); +} + +type_init(phytium_ddr_ctrl_register_types) diff --git a/hw/misc/phytium-scp-mailbox.c b/hw/misc/phytium-scp-mailbox.c new file mode 100644 index 0000000000000..63b22dcee6a09 --- /dev/null +++ b/hw/misc/phytium-scp-mailbox.c @@ -0,0 +1,130 @@ +/* + * Phytium SCP mailbox model + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/misc/phytium-scp-mailbox.h" +#include "migration/vmstate.h" +#include "qemu/module.h" + +#define PHYTIUM_SCP_MAILBOX_STATUS_OFFSET 0x04 +#define PHYTIUM_SCP_MAILBOX_CONTROL_OFFSET 0x10 +#define PHYTIUM_SCP_MAILBOX_RESPONSE_OFFSET 0x1c +#define PHYTIUM_SCP_MAILBOX_DONE 0x1 + +static void phytium_scp_mailbox_complete(PhytiumScpMailboxState *s) +{ + s->regs[PHYTIUM_SCP_MAILBOX_STATUS_OFFSET / sizeof(uint32_t)] |= + PHYTIUM_SCP_MAILBOX_DONE; + s->regs[PHYTIUM_SCP_MAILBOX_RESPONSE_OFFSET / sizeof(uint32_t)] = 0; +} + +static uint64_t phytium_scp_mailbox_read(void *opaque, hwaddr offset, + unsigned size) +{ + PhytiumScpMailboxState *s = opaque; + unsigned int index = offset / sizeof(uint32_t); + unsigned int shift = (offset & 3) * 8; + uint32_t mask = size == 4 ? UINT32_MAX : (1u << (size * 8)) - 1; + + if (index >= ARRAY_SIZE(s->regs)) { + return 0; + } + + if ((offset & ~3) == PHYTIUM_SCP_MAILBOX_STATUS_OFFSET) { + phytium_scp_mailbox_complete(s); + } + + return (s->regs[index] >> shift) & mask; +} + +static void phytium_scp_mailbox_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + PhytiumScpMailboxState *s = opaque; + unsigned int index = offset / sizeof(uint32_t); + unsigned int shift = (offset & 3) * 8; + uint32_t mask = size == 4 ? UINT32_MAX : ((1u << (size * 8)) - 1) << shift; + uint32_t *reg; + + if (index >= ARRAY_SIZE(s->regs)) { + return; + } + + reg = &s->regs[index]; + *reg = (*reg & ~mask) | (((uint32_t)value << shift) & mask); + if ((offset & ~3) == PHYTIUM_SCP_MAILBOX_STATUS_OFFSET || + (offset & ~3) == PHYTIUM_SCP_MAILBOX_CONTROL_OFFSET) { + phytium_scp_mailbox_complete(s); + } +} + +static const MemoryRegionOps phytium_scp_mailbox_ops = { + .read = phytium_scp_mailbox_read, + .write = phytium_scp_mailbox_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +static void phytium_scp_mailbox_reset(DeviceState *dev) +{ + PhytiumScpMailboxState *s = PHYTIUM_SCP_MAILBOX(dev); + + memset(s->regs, 0, sizeof(s->regs)); +} + +static void phytium_scp_mailbox_init(Object *obj) +{ + PhytiumScpMailboxState *s = PHYTIUM_SCP_MAILBOX(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + memory_region_init_io(&s->iomem, obj, &phytium_scp_mailbox_ops, s, + TYPE_PHYTIUM_SCP_MAILBOX, + PHYTIUM_SCP_MAILBOX_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->iomem); +} + +static const VMStateDescription vmstate_phytium_scp_mailbox = { + .name = TYPE_PHYTIUM_SCP_MAILBOX, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, PhytiumScpMailboxState, + PHYTIUM_SCP_MAILBOX_MMIO_SIZE / + sizeof(uint32_t)), + VMSTATE_END_OF_LIST() + }, +}; + +static void phytium_scp_mailbox_class_init(ObjectClass *klass, + const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, phytium_scp_mailbox_reset); + dc->vmsd = &vmstate_phytium_scp_mailbox; +} + +static const TypeInfo phytium_scp_mailbox_info = { + .name = TYPE_PHYTIUM_SCP_MAILBOX, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PhytiumScpMailboxState), + .instance_init = phytium_scp_mailbox_init, + .class_init = phytium_scp_mailbox_class_init, +}; + +static void phytium_scp_mailbox_register_types(void) +{ + type_register_static(&phytium_scp_mailbox_info); +} + +type_init(phytium_scp_mailbox_register_types) diff --git a/hw/misc/rk3588_cru.c b/hw/misc/rk3588_cru.c new file mode 100644 index 0000000000000..c02c98c111c7c --- /dev/null +++ b/hw/misc/rk3588_cru.c @@ -0,0 +1,122 @@ +/* + * Minimal RK3588 CRU (Clock-and-Reset-Unit) stub. + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Backs the 0xfd7c0000/0x5c000 CRU window so the Linux + * "rockchip,rk3588-cru" provider (CLK_OF_DECLARE -> clk-rk3588 + + * rst-rk3588) registers and resolves the in-scope drivers' + * reset_control_get / clk_prepare_enable calls without aborting. + * + * Offset 0x600 (RK3588_GRF_SOC_STATUS0, PLL lock status) MUST read + * 0xffffffff or the early PLL init in clk-rk3588 hangs before the + * console comes up. SPL also polls per-PLL status registers at +0x18 + * for bit 15. Other registers are RAM-backed and honor the Rockchip + * HIWORD-mask update convention. + */ + +#include "qemu/osdep.h" +#include "hw/misc/rk3588_cru.h" +#include "qemu/log.h" +#include "qemu/module.h" + +#define RK3588_CRU_PLL_STATUS_OFFSET 0x18 +#define RK3588_CRU_PLL_STATUS_STRIDE 0x20 +#define RK3588_CRU_PLL_LOCKED 0x8000 + +static uint64_t rk3588_cru_read(void *opaque, hwaddr offset, unsigned size) +{ + RK3588CRUState *s = opaque; + uint32_t value; + + if (offset + size > RK3588_CRU_SIZE || (offset & 3) || size != 4) { + return 0; + } + + if (offset == RK3588_CRU_PLL_STATUS) { + return 0xffffffff; + } + + value = s->regs[offset >> 2]; + if ((offset & (RK3588_CRU_PLL_STATUS_STRIDE - 1)) == + RK3588_CRU_PLL_STATUS_OFFSET) { + value |= RK3588_CRU_PLL_LOCKED; + } + + return value; +} + +static void rk3588_cru_write(void *opaque, hwaddr offset, uint64_t val, + unsigned size) +{ + RK3588CRUState *s = opaque; + uint32_t old, mask, value = val; + + if (offset + size > RK3588_CRU_SIZE || (offset & 3) || size != 4) { + return; + } + + old = s->regs[offset >> 2]; + mask = value >> 16; + if (mask) { + s->regs[offset >> 2] = (old & ~mask) | (value & mask); + } else { + s->regs[offset >> 2] = value; + } +} + +static const MemoryRegionOps rk3588_cru_ops = { + .read = rk3588_cru_read, + .write = rk3588_cru_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void rk3588_cru_reset(DeviceState *dev) +{ + RK3588CRUState *s = RK3588_CRU(dev); + + memset(s->regs, 0, sizeof(s->regs)); +} + +static void rk3588_cru_realize(DeviceState *dev, Error **errp) +{ + RK3588CRUState *s = RK3588_CRU(dev); + + memory_region_init_io(&s->iomem, OBJECT(s), &rk3588_cru_ops, s, + "rk3588-cru", RK3588_CRU_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); +} + +static void rk3588_cru_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = rk3588_cru_realize; + device_class_set_legacy_reset(dc, rk3588_cru_reset); + /* Not user-creatable. */ + dc->user_creatable = false; +} + +static const TypeInfo rk3588_cru_info = { + .name = TYPE_RK3588_CRU, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RK3588CRUState), + .class_init = rk3588_cru_class_init, +}; + +static void rk3588_cru_register_types(void) +{ + type_register_static(&rk3588_cru_info); +} + +type_init(rk3588_cru_register_types) diff --git a/hw/misc/rk3588_scmi.c b/hw/misc/rk3588_scmi.c new file mode 100644 index 0000000000000..8da6661a37577 --- /dev/null +++ b/hw/misc/rk3588_scmi.c @@ -0,0 +1,542 @@ +/* + * Minimal RK3588 SCMI clock responder. + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * See include/hw/misc/rk3588_scmi.h for the message table. + */ + +#include "qemu/osdep.h" +#include "hw/misc/rk3588_scmi.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/bswap.h" +#include "exec/hwaddr.h" + +/* shmem layout offsets (struct scmi_shared_mem, 24-byte fixed header). */ +#define SHMEM_RESERVED 0x00 +#define SHMEM_CHANNEL_STATUS 0x04 +#define SHMEM_CHANNEL_FREE BIT(0) +#define SHMEM_CHANNEL_ERROR BIT(1) +#define SHMEM_RESERVED1 0x08 +#define SHMEM_FLAGS 0x10 +#define SHMEM_LENGTH 0x14 +#define SHMEM_MSG_HEADER 0x18 +#define SHMEM_MSG_PAYLOAD 0x1c + +/* msg_header field masks (drivers/firmware/arm_scmi/common.h). */ +#define MSG_ID_MASK 0xff +#define MSG_TYPE_MASK 0x300 +#define MSG_XTYPE 8 +#define MSG_PROTOCOL_ID_MASK 0x0003fc00 +#define MSG_PROT_SHIFT 10 +#define MSG_TOKEN_ID_MASK 0x0ffc0000 +#define MSG_TOKEN_SHIFT 18 + +/* SCMI protocol ids. */ +#define SCMI_PROTOCOL_BASE 0x10 +#define SCMI_PROTOCOL_CLOCK 0x14 +#define SCMI_PROTOCOL_RESET 0x16 + +/* SCMI message ids (common + clock). */ +#define MSG_PROTOCOL_VERSION 0x0 +#define MSG_PROTOCOL_ATTRIBUTES 0x1 +#define MSG_PROTOCOL_MESSAGE_ATTRIBUTES 0x2 +#define MSG_NEGOTIATE_PROTOCOL_VERSION 0x10 + +#define BASE_DISCOVER_VENDOR 0x3 +#define BASE_DISCOVER_SUB_VENDOR 0x4 +#define BASE_DISCOVER_IMPLEMENT_VERSION 0x5 +#define BASE_DISCOVER_LIST_PROTOCOLS 0x6 +#define BASE_DISCOVER_AGENT 0x7 +#define BASE_NOTIFY_ERRORS 0x8 + +#define CLOCK_ATTRIBUTES 0x3 +#define CLOCK_DESCRIBE_RATES 0x4 +#define CLOCK_RATE_SET 0x5 +#define CLOCK_RATE_GET 0x6 +#define CLOCK_CONFIG_SET 0x7 +#define CLOCK_CONFIG_GET 0xb + +#define SCMI_BASE_VERSION 0x00020001 +#define SCMI_CLOCK_VERSION 0x00030000 +#define SCMI_RESET_VERSION 0x00030001 +#define SCMI_NUM_PROTOCOLS 2 + +/* SCMI generic status codes. */ +#define SCMI_SUCCESS 0 +#define SCMI_ERR_SUPPORT (-1) +#define SCMI_ERR_INVALID_PARAMS (-2) +#define SCMI_ERR_NOT_FOUND (-4) + +/* IDs in include/dt-bindings/clock/rockchip,rk3588-cru.h. */ +#define SCMI_CCLK_SD 9 +#define SCMI_HCLK_SD 23 + +/* Nominal rates (Hz) - picked to be plausible so CCF accepts them. */ +#define SCMI_CCLK_SD_RATE 50000000ULL +#define SCMI_HCLK_SD_RATE 100000000ULL + +static uint32_t shmem_get_status_off(void) +{ + /* First 4 bytes of the payload are the status. */ + return SHMEM_MSG_PAYLOAD; +} + +static void shmem_write_u32(RK3588SCMIState *s, unsigned off, uint32_t v) +{ + stl_le_p(s->shmem_buf + off, v); +} + +static uint32_t shmem_read_u32(RK3588SCMIState *s, unsigned off) +{ + return ldl_le_p(s->shmem_buf + off); +} + +static void shmem_write_u64(RK3588SCMIState *s, unsigned off, uint64_t v) +{ + stq_le_p(s->shmem_buf + off, v); +} + +/* + * Resolve the per-clock nominal rate. Only IDs 9 and 23 are + * meaningful for the SD-card driver; for everything else we return + * a benign 24 MHz so CCF's clk-scmi does not explode during the + * 24-clock enumeration loop in scmi_clock_protocol_init. + */ +static uint64_t rk3588_scmi_clock_rate(RK3588SCMIState *s, uint32_t clk_id) +{ + if (clk_id >= RK3588_SCMI_NUM_CLOCKS) { + return 0; + } + if (s->rate[clk_id]) { + return s->rate[clk_id]; + } + switch (clk_id) { + case SCMI_CCLK_SD: + return SCMI_CCLK_SD_RATE; + case SCMI_HCLK_SD: + return SCMI_HCLK_SD_RATE; + default: + return 24000000ULL; + } +} + +static bool rk3588_scmi_base_msg_supported(uint32_t msg_id) +{ + switch (msg_id) { + case MSG_PROTOCOL_VERSION: + case MSG_PROTOCOL_ATTRIBUTES: + case MSG_PROTOCOL_MESSAGE_ATTRIBUTES: + case BASE_DISCOVER_VENDOR: + case BASE_DISCOVER_SUB_VENDOR: + case BASE_DISCOVER_IMPLEMENT_VERSION: + case BASE_DISCOVER_LIST_PROTOCOLS: + case BASE_DISCOVER_AGENT: + case BASE_NOTIFY_ERRORS: + case MSG_NEGOTIATE_PROTOCOL_VERSION: + return true; + default: + return false; + } +} + +static bool rk3588_scmi_clock_msg_supported(uint32_t msg_id) +{ + switch (msg_id) { + case MSG_PROTOCOL_VERSION: + case MSG_PROTOCOL_ATTRIBUTES: + case MSG_PROTOCOL_MESSAGE_ATTRIBUTES: + case CLOCK_ATTRIBUTES: + case CLOCK_RATE_SET: + case CLOCK_RATE_GET: + case CLOCK_CONFIG_SET: + case CLOCK_CONFIG_GET: + case MSG_NEGOTIATE_PROTOCOL_VERSION: + return true; + default: + return false; + } +} + +static bool rk3588_scmi_reset_msg_supported(uint32_t msg_id) +{ + switch (msg_id) { + case MSG_PROTOCOL_VERSION: + case MSG_PROTOCOL_ATTRIBUTES: + case MSG_PROTOCOL_MESSAGE_ATTRIBUTES: + case MSG_NEGOTIATE_PROTOCOL_VERSION: + return true; + default: + return false; + } +} + +/* + * Write the response into the shmem buffer. Caller has already placed + * the status word at SHMEM_MSG_PAYLOAD. We just fix up the header + * echo, length, and CHANNEL_FREE. + */ +static void rk3588_scmi_finalize_response(RK3588SCMIState *s, + uint32_t payload_len) +{ + uint32_t length = 4 /* header */ + 4 /* status */ + payload_len; + + shmem_write_u32(s, SHMEM_LENGTH, length); + shmem_write_u32(s, SHMEM_CHANNEL_STATUS, SHMEM_CHANNEL_FREE); +} + +/* + * Read the request from shmem, write a canned response, set + * CHANNEL_FREE. Returns true on any well-formed message (even ones + * we don't support, which still get a NOT_SUPPORTED response); false + * if the shmem does not look like a valid request. + */ +bool rk3588_scmi_handle_smc(RK3588SCMIState *s) +{ + uint32_t hdr, length, msg_id, msg_type, prot_id; + uint32_t status_off; + uint32_t status = SCMI_SUCCESS; + uint32_t extra_payload = 0; + + length = shmem_read_u32(s, SHMEM_LENGTH); + hdr = shmem_read_u32(s, SHMEM_MSG_HEADER); + + if (length < 4) { + /* No header to parse - leave shmem alone; transport will see the + * channel still busy and time out. Be lenient: still set + * CHANNEL_FREE so the guest can recover. */ + shmem_write_u32(s, SHMEM_CHANNEL_STATUS, SHMEM_CHANNEL_FREE); + return false; + } + + msg_id = hdr & MSG_ID_MASK; + msg_type = (hdr & MSG_TYPE_MASK) >> MSG_XTYPE; + prot_id = (hdr & MSG_PROTOCOL_ID_MASK) >> MSG_PROT_SHIFT; + (void)msg_type; /* only COMMAND (0) appears in practice */ + + status_off = shmem_get_status_off(); + + switch (prot_id) { + case SCMI_PROTOCOL_BASE: + switch (msg_id) { + case MSG_PROTOCOL_VERSION: + shmem_write_u32(s, status_off + 4, SCMI_BASE_VERSION); + extra_payload = 4; + break; + case MSG_PROTOCOL_ATTRIBUTES: + /* struct scmi_msg_resp_base_attributes: {num_protocols, agents}. */ + shmem_write_u32(s, status_off + 4, + SCMI_NUM_PROTOCOLS | (1 << 8)); + extra_payload = 4; + break; + case MSG_PROTOCOL_MESSAGE_ATTRIBUTES: + if (rk3588_scmi_base_msg_supported( + shmem_read_u32(s, SHMEM_MSG_PAYLOAD))) { + shmem_write_u32(s, status_off + 4, 0); + extra_payload = 4; + } else { + status = SCMI_ERR_SUPPORT; + extra_payload = 0; + } + break; + case BASE_DISCOVER_VENDOR: + memset(s->shmem_buf + status_off + 4, 0, 16); + strcpy((char *)(s->shmem_buf + status_off + 4), "QEMU"); + extra_payload = 16; + break; + case BASE_DISCOVER_SUB_VENDOR: + memset(s->shmem_buf + status_off + 4, 0, 16); + strcpy((char *)(s->shmem_buf + status_off + 4), "RK3588"); + extra_payload = 16; + break; + case BASE_DISCOVER_IMPLEMENT_VERSION: + shmem_write_u32(s, status_off + 4, 0); + extra_payload = 4; + break; + case BASE_DISCOVER_LIST_PROTOCOLS: { + uint32_t skip = shmem_read_u32(s, SHMEM_MSG_PAYLOAD); + + if (skip >= SCMI_NUM_PROTOCOLS) { + shmem_write_u32(s, status_off + 4, 0); + extra_payload = 4; + } else { + uint32_t count = SCMI_NUM_PROTOCOLS - skip; + uint32_t protocols = 0; + const uint8_t protocol_list[SCMI_NUM_PROTOCOLS] = { + SCMI_PROTOCOL_CLOCK, + SCMI_PROTOCOL_RESET, + }; + + for (uint32_t i = 0; i < count; i++) { + protocols |= protocol_list[skip + i] << (i * 8); + } + shmem_write_u32(s, status_off + 4, count); + shmem_write_u32(s, status_off + 8, protocols); + extra_payload = 8; + } + break; + } + case BASE_DISCOVER_AGENT: + memset(s->shmem_buf + status_off + 4, 0, 16); + strcpy((char *)(s->shmem_buf + status_off + 4), "agent0"); + extra_payload = 16; + break; + case BASE_NOTIFY_ERRORS: + extra_payload = 0; + break; + case MSG_NEGOTIATE_PROTOCOL_VERSION: + /* No payload; only status. */ + extra_payload = 0; + break; + default: + status = SCMI_ERR_SUPPORT; + extra_payload = 0; + break; + } + break; + + case SCMI_PROTOCOL_CLOCK: + switch (msg_id) { + case MSG_PROTOCOL_VERSION: + shmem_write_u32(s, status_off + 4, SCMI_CLOCK_VERSION); + extra_payload = 4; + break; + case MSG_PROTOCOL_ATTRIBUTES: { + /* num_clocks[15:0]=24, max_async_req[23:16]=0 */ + uint32_t attrs = RK3588_SCMI_NUM_CLOCKS; + shmem_write_u32(s, status_off + 4, attrs); + extra_payload = 4; + break; + } + case MSG_PROTOCOL_MESSAGE_ATTRIBUTES: + if (rk3588_scmi_clock_msg_supported( + shmem_read_u32(s, SHMEM_MSG_PAYLOAD))) { + shmem_write_u32(s, status_off + 4, 0); + extra_payload = 4; + } else { + status = SCMI_ERR_SUPPORT; + extra_payload = 0; + } + break; + case CLOCK_ATTRIBUTES: { + uint32_t clk_id = shmem_read_u32(s, SHMEM_MSG_PAYLOAD); + if (clk_id >= RK3588_SCMI_NUM_CLOCKS) { + status = SCMI_ERR_NOT_FOUND; + extra_payload = 0; + break; + } + /* + * attributes=0: no extended-name, no notifications, no parents. + * This makes Linux skip CLOCK_NAME_GET / DESCRIBE_RATES / + * POSSIBLE_PARENTS for this clock (clock.c:382-402). + */ + shmem_write_u32(s, status_off + 4, 0); + /* 16-byte ASCII name (NUL-padded). */ + memset(s->shmem_buf + status_off + 8, 0, 16); + switch (clk_id) { + case SCMI_CCLK_SD: + strcpy((char *)(s->shmem_buf + status_off + 8), "cclk_sd"); + break; + case SCMI_HCLK_SD: + strcpy((char *)(s->shmem_buf + status_off + 8), "hclk_sd"); + break; + default: { + char nm[8]; + snprintf(nm, sizeof(nm), "clk%u", clk_id); + strcpy((char *)(s->shmem_buf + status_off + 8), nm); + break; + } + } + /* clock_enable_latency=0. */ + shmem_write_u32(s, status_off + 24, 0); + extra_payload = 4 + 16 + 4; + break; + } + case CLOCK_RATE_GET: { + uint32_t clk_id = shmem_read_u32(s, SHMEM_MSG_PAYLOAD); + if (clk_id >= RK3588_SCMI_NUM_CLOCKS) { + status = SCMI_ERR_NOT_FOUND; + extra_payload = 0; + break; + } + shmem_write_u64(s, status_off + 4, + rk3588_scmi_clock_rate(s, clk_id)); + extra_payload = 8; + break; + } + case CLOCK_RATE_SET: { + /* payload: {u32 flags; u32 clk_id; u32 rate_lo; u32 rate_hi} */ + uint32_t clk_id = shmem_read_u32(s, SHMEM_MSG_PAYLOAD + 4); + uint32_t rate_lo = shmem_read_u32(s, SHMEM_MSG_PAYLOAD + 8); + uint32_t rate_hi = shmem_read_u32(s, SHMEM_MSG_PAYLOAD + 12); + if (clk_id >= RK3588_SCMI_NUM_CLOCKS) { + status = SCMI_ERR_NOT_FOUND; + } else { + s->rate[clk_id] = ((uint64_t)rate_hi << 32) | rate_lo; + } + extra_payload = 0; + break; + } + case CLOCK_CONFIG_SET: { + /* payload: {u32 clk_id; u32 attributes} */ + uint32_t clk_id = shmem_read_u32(s, SHMEM_MSG_PAYLOAD); + uint32_t attrs = shmem_read_u32(s, SHMEM_MSG_PAYLOAD + 4); + if (clk_id >= RK3588_SCMI_NUM_CLOCKS) { + status = SCMI_ERR_NOT_FOUND; + } else { + s->enabled[clk_id] = (attrs & 0x1) != 0; + } + extra_payload = 0; + break; + } + case CLOCK_CONFIG_GET: { + /* payload: {u32 clk_id; u32 flags}; v3 response has 3 u32s. */ + uint32_t clk_id = shmem_read_u32(s, SHMEM_MSG_PAYLOAD); + if (clk_id >= RK3588_SCMI_NUM_CLOCKS) { + status = SCMI_ERR_NOT_FOUND; + extra_payload = 0; + break; + } + shmem_write_u32(s, status_off + 4, 0); + shmem_write_u32(s, status_off + 8, s->enabled[clk_id] ? 1 : 0); + shmem_write_u32(s, status_off + 12, 0); + extra_payload = 12; + break; + } + case MSG_NEGOTIATE_PROTOCOL_VERSION: + extra_payload = 0; + break; + default: + status = SCMI_ERR_SUPPORT; + extra_payload = 0; + break; + } + break; + + case SCMI_PROTOCOL_RESET: + switch (msg_id) { + case MSG_PROTOCOL_VERSION: + shmem_write_u32(s, status_off + 4, SCMI_RESET_VERSION); + extra_payload = 4; + break; + case MSG_PROTOCOL_ATTRIBUTES: + /* num_reset_domains[15:0]=0. */ + shmem_write_u32(s, status_off + 4, 0); + extra_payload = 4; + break; + case MSG_PROTOCOL_MESSAGE_ATTRIBUTES: + if (rk3588_scmi_reset_msg_supported( + shmem_read_u32(s, SHMEM_MSG_PAYLOAD))) { + shmem_write_u32(s, status_off + 4, 0); + extra_payload = 4; + } else { + status = SCMI_ERR_SUPPORT; + extra_payload = 0; + } + break; + case MSG_NEGOTIATE_PROTOCOL_VERSION: + extra_payload = 0; + break; + default: + status = SCMI_ERR_SUPPORT; + extra_payload = 0; + break; + } + break; + + default: + /* Unknown protocol - let the framework see NOT_SUPPORTED. */ + status = SCMI_ERR_SUPPORT; + extra_payload = 0; + break; + } + + shmem_write_u32(s, status_off, (uint32_t)status); + rk3588_scmi_finalize_response(s, extra_payload); + return true; +} + +/* ------------------------------------------------------------------------- */ +/* SysBusDevice plumbing: a tiny RAM-backed shmem MMIO region. */ +/* ------------------------------------------------------------------------- */ + +static uint64_t rk3588_scmi_shmem_read(void *opaque, hwaddr off, unsigned sz) +{ + RK3588SCMIState *s = RK3588_SCMI(opaque); + uint64_t v = 0; + + if (off + sz <= RK3588_SCMI_SHMEM_SIZE) { + memcpy(&v, s->shmem_buf + off, sz); + } + return v; +} + +static void rk3588_scmi_shmem_write(void *opaque, hwaddr off, uint64_t v, + unsigned sz) +{ + RK3588SCMIState *s = RK3588_SCMI(opaque); + + if (off + sz <= RK3588_SCMI_SHMEM_SIZE) { + memcpy(s->shmem_buf + off, &v, sz); + } +} + +static const MemoryRegionOps rk3588_scmi_shmem_ops = { + .read = rk3588_scmi_shmem_read, + .write = rk3588_scmi_shmem_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_scmi_reset(DeviceState *dev) +{ + RK3588SCMIState *s = RK3588_SCMI(dev); + + memset(s->shmem_buf, 0, sizeof(s->shmem_buf)); + /* CHANNEL_FREE=1 at reset so the first tx_prepare can proceed. */ + shmem_write_u32(s, SHMEM_CHANNEL_STATUS, SHMEM_CHANNEL_FREE); + memset(s->rate, 0, sizeof(s->rate)); + memset(s->enabled, 0, sizeof(s->enabled)); +} + +static void rk3588_scmi_realize(DeviceState *dev, Error **errp) +{ + RK3588SCMIState *s = RK3588_SCMI(dev); + + memory_region_init_io(&s->shmem, OBJECT(s), &rk3588_scmi_shmem_ops, s, + "rk3588-scmi-shmem", RK3588_SCMI_SHMEM_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->shmem); +} + +static void rk3588_scmi_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = rk3588_scmi_realize; + device_class_set_legacy_reset(dc, rk3588_scmi_reset); + dc->user_creatable = false; +} + +static const TypeInfo rk3588_scmi_info = { + .name = TYPE_RK3588_SCMI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RK3588SCMIState), + .class_init = rk3588_scmi_class_init, +}; + +static void rk3588_scmi_register_types(void) +{ + type_register_static(&rk3588_scmi_info); +} + +type_init(rk3588_scmi_register_types) diff --git a/hw/misc/rockchip_syscon.c b/hw/misc/rockchip_syscon.c new file mode 100644 index 0000000000000..9fdf51527e5e2 --- /dev/null +++ b/hw/misc/rockchip_syscon.c @@ -0,0 +1,143 @@ +/* + * Rockchip RAM-backed syscon register bank + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Rockchip GRF/IOC/firewall style syscons are mostly side-effect + * configuration registers. The common write convention is a 32-bit + * HIWORD update: bits 31:16 are the write mask, bits 15:0 carry the + * data. If no high-half mask is present, store the full value. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "hw/misc/rockchip_syscon.h" +#include "hw/core/qdev-properties.h" +#include "qemu/module.h" + +static uint64_t rockchip_syscon_read(void *opaque, hwaddr offset, + unsigned size) +{ + RockchipSysconState *s = opaque; + uint32_t val; + + if (offset + size > s->size || (offset & 3) || size != 4) { + return 0; + } + + val = s->regs[offset >> 2]; + return val; +} + +static void rockchip_syscon_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + RockchipSysconState *s = opaque; + uint32_t old, mask, val = value; + + if (offset + size > s->size || (offset & 3) || size != 4) { + return; + } + + old = s->regs[offset >> 2]; + mask = val >> 16; + if (mask) { + s->regs[offset >> 2] = (old & ~mask) | (val & mask); + } else { + s->regs[offset >> 2] = val; + } +} + +static const MemoryRegionOps rockchip_syscon_ops = { + .read = rockchip_syscon_read, + .write = rockchip_syscon_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +void rockchip_syscon_set_u32(RockchipSysconState *s, hwaddr offset, + uint32_t value) +{ + if (offset + sizeof(uint32_t) > s->size || (offset & 3)) { + return; + } + + s->regs[offset >> 2] = value; +} + +uint32_t rockchip_syscon_get_u32(RockchipSysconState *s, hwaddr offset) +{ + if (offset + sizeof(uint32_t) > s->size || (offset & 3)) { + return 0; + } + + return s->regs[offset >> 2]; +} + +static void rockchip_syscon_reset(DeviceState *dev) +{ + RockchipSysconState *s = ROCKCHIP_SYSCON(dev); + + memset(s->regs, 0, s->size); +} + +static void rockchip_syscon_realize(DeviceState *dev, Error **errp) +{ + RockchipSysconState *s = ROCKCHIP_SYSCON(dev); + + if (!s->size || (s->size & 3)) { + error_setg(errp, "rockchip-syscon size must be a non-zero " + "multiple of 4"); + return; + } + + s->regs = g_new0(uint32_t, s->size / sizeof(uint32_t)); + memory_region_init_io(&s->iomem, OBJECT(s), &rockchip_syscon_ops, s, + "rockchip-syscon", s->size); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); +} + +static void rockchip_syscon_finalize(Object *obj) +{ + RockchipSysconState *s = ROCKCHIP_SYSCON(obj); + + g_free(s->regs); +} + +static const Property rockchip_syscon_properties[] = { + DEFINE_PROP_UINT32("size", RockchipSysconState, size, 0x1000), +}; + +static void rockchip_syscon_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = rockchip_syscon_realize; + device_class_set_legacy_reset(dc, rockchip_syscon_reset); + dc->user_creatable = false; + device_class_set_props(dc, rockchip_syscon_properties); +} + +static const TypeInfo rockchip_syscon_info = { + .name = TYPE_ROCKCHIP_SYSCON, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RockchipSysconState), + .instance_finalize = rockchip_syscon_finalize, + .class_init = rockchip_syscon_class_init, +}; + +static void rockchip_syscon_register_types(void) +{ + type_register_static(&rockchip_syscon_info); +} + +type_init(rockchip_syscon_register_types) diff --git a/hw/misc/spacemit-k3.c b/hw/misc/spacemit-k3.c new file mode 100644 index 0000000000000..e160f14627bcd --- /dev/null +++ b/hw/misc/spacemit-k3.c @@ -0,0 +1,206 @@ +/* + * SpacemiT K3 clock and boot controls + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "hw/misc/spacemit-k3.h" +#include "migration/vmstate.h" + +#define K3_APMU_SDH0_CTRL 0x54 +#define K3_APMU_SDH0_AXI_RESET_N BIT(0) +#define K3_APMU_SDH0_RESET_N BIT(1) +#define K3_APMU_SDH0_AXI_CLK_EN BIT(3) +#define K3_APMU_SDH0_CLK_EN BIT(4) +#define K3_APMU_SDH0_CLK_MUX_MASK (0x7U << 5) +#define K3_APMU_SDH0_CLK_DIV_MASK (0x7U << 8) +#define K3_APMU_SDH0_CLK_DIV_DEFAULT (0x1U << 8) +#define K3_APMU_SDH0_CLK_FC BIT(11) +#define K3_APMU_SDH0_CTRL_MASK (K3_APMU_SDH0_AXI_RESET_N | \ + K3_APMU_SDH0_RESET_N | \ + K3_APMU_SDH0_AXI_CLK_EN | \ + K3_APMU_SDH0_CLK_EN | \ + K3_APMU_SDH0_CLK_MUX_MASK | \ + K3_APMU_SDH0_CLK_DIV_MASK | \ + K3_APMU_SDH0_CLK_FC) +#define K3_APMU_SDH0_CTRL_RESET (K3_APMU_SDH0_AXI_RESET_N | \ + K3_APMU_SDH0_AXI_CLK_EN | \ + K3_APMU_SDH0_CLK_EN | \ + K3_APMU_SDH0_CLK_DIV_DEFAULT) + +#define K3_CIU_BOOT_FLAG 0x110 +#define K3_CIU_BOOT_FROM_SD 0xb10 + +static uint64_t spacemit_k3_apmu_read(void *opaque, hwaddr addr, + unsigned int size) +{ + SpacemitK3APMUState *s = SPACEMIT_K3_APMU(opaque); + + switch (addr) { + case K3_APMU_SDH0_CTRL: + return s->sdh0_ctrl; + default: + qemu_log_mask(LOG_UNIMP, + "%s: unimplemented read at offset 0x%" HWADDR_PRIx + "\n", __func__, addr); + return 0; + } +} + +static void spacemit_k3_apmu_write(void *opaque, hwaddr addr, uint64_t value, + unsigned int size) +{ + SpacemitK3APMUState *s = SPACEMIT_K3_APMU(opaque); + + switch (addr) { + case K3_APMU_SDH0_CTRL: + s->sdh0_ctrl = value & K3_APMU_SDH0_CTRL_MASK & + ~K3_APMU_SDH0_CLK_FC; + break; + default: + qemu_log_mask(LOG_UNIMP, + "%s: unimplemented write at offset 0x%" HWADDR_PRIx + "\n", __func__, addr); + break; + } +} + +static const MemoryRegionOps spacemit_k3_apmu_ops = { + .read = spacemit_k3_apmu_read, + .write = spacemit_k3_apmu_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void spacemit_k3_apmu_reset(DeviceState *dev) +{ + SpacemitK3APMUState *s = SPACEMIT_K3_APMU(dev); + + s->sdh0_ctrl = K3_APMU_SDH0_CTRL_RESET; +} + +static const VMStateDescription vmstate_spacemit_k3_apmu = { + .name = TYPE_SPACEMIT_K3_APMU, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(sdh0_ctrl, SpacemitK3APMUState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void spacemit_k3_apmu_realize(DeviceState *dev, Error **errp) +{ + SpacemitK3APMUState *s = SPACEMIT_K3_APMU(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &spacemit_k3_apmu_ops, s, + TYPE_SPACEMIT_K3_APMU, + SPACEMIT_K3_APMU_MMIO_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); +} + +static void spacemit_k3_apmu_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = spacemit_k3_apmu_realize; + device_class_set_legacy_reset(dc, spacemit_k3_apmu_reset); + dc->vmsd = &vmstate_spacemit_k3_apmu; + dc->desc = "SpacemiT K3 application power management unit"; +} + +static const TypeInfo spacemit_k3_apmu_type_info = { + .name = TYPE_SPACEMIT_K3_APMU, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SpacemitK3APMUState), + .class_init = spacemit_k3_apmu_class_init, +}; + +static uint64_t spacemit_k3_ciu_read(void *opaque, hwaddr addr, + unsigned int size) +{ + switch (addr) { + case K3_CIU_BOOT_FLAG: + return K3_CIU_BOOT_FROM_SD; + default: + qemu_log_mask(LOG_UNIMP, + "%s: unimplemented read at offset 0x%" HWADDR_PRIx + "\n", __func__, addr); + return 0; + } +} + +static void spacemit_k3_ciu_write(void *opaque, hwaddr addr, uint64_t value, + unsigned int size) +{ + switch (addr) { + case K3_CIU_BOOT_FLAG: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to read-only boot flag\n", __func__); + break; + default: + qemu_log_mask(LOG_UNIMP, + "%s: unimplemented write at offset 0x%" HWADDR_PRIx + "\n", __func__, addr); + break; + } +} + +static const MemoryRegionOps spacemit_k3_ciu_ops = { + .read = spacemit_k3_ciu_read, + .write = spacemit_k3_ciu_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void spacemit_k3_ciu_realize(DeviceState *dev, Error **errp) +{ + SpacemitK3CIUState *s = SPACEMIT_K3_CIU(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &spacemit_k3_ciu_ops, s, + TYPE_SPACEMIT_K3_CIU, + SPACEMIT_K3_CIU_MMIO_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); +} + +static void spacemit_k3_ciu_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = spacemit_k3_ciu_realize; + dc->desc = "SpacemiT K3 chip interface unit"; +} + +static const TypeInfo spacemit_k3_ciu_type_info = { + .name = TYPE_SPACEMIT_K3_CIU, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SpacemitK3CIUState), + .class_init = spacemit_k3_ciu_class_init, +}; + +static void spacemit_k3_control_register_types(void) +{ + type_register_static(&spacemit_k3_apmu_type_info); + type_register_static(&spacemit_k3_ciu_type_info); +} + +type_init(spacemit_k3_control_register_types) diff --git a/hw/misc/trace-events b/hw/misc/trace-events index c9a868b3efb9d..4b5b3f55465f8 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -1,5 +1,74 @@ # See docs/devel/tracing.rst for syntax documentation. +# k230_dewarp.c +k230_dewarp_read(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +k230_dewarp_write(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +k230_dewarp_irq(const char *name, bool level) "%s level %d" + +# k230_isp.c +k230_isp_read(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +k230_isp_write(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +k230_isp_irq(const char *name, bool level) "%s level %d" + +# k230_rx_csi.c +k230_rx_csi_read(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +k230_rx_csi_write(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +k230_rx_csi_ready(unsigned host, uint32_t status) "host %u status 0x%" PRIx32 + +# k230_regs.c +k230_regs_read(const char *name, uint64_t offset, uint64_t data, unsigned size) "%s offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +k230_regs_write(const char *name, uint64_t offset, uint64_t data, unsigned size) "%s offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +k230_regs_irq(const char *name, bool level) "%s level %d" +k230_regs_irq_command(const char *name, uint64_t start, uint64_t end, uint64_t hi) "%s start 0x%" PRIx64 " end 0x%" PRIx64 " hi 0x%" PRIx64 +k230_regs_completion_zero_page(const char *name, uint64_t page, uint32_t raw) "%s page 0x%" PRIx64 " raw 0x%" PRIx32 + +# k230_kpu.c +k230_kpu_read(const char *name, uint64_t offset, uint64_t data, unsigned size) "%s offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +k230_kpu_write(const char *name, uint64_t offset, uint64_t data, unsigned size) "%s offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +k230_kpu_irq(const char *name, bool level) "%s level %d" +k230_kpu_start(const char *name, uint64_t start, uint64_t end, uint64_t hi) "%s start 0x%" PRIx64 " end 0x%" PRIx64 " hi 0x%" PRIx64 +k230_kpu_completion_zero_page(const char *name, uint64_t page, uint32_t raw) "%s page 0x%" PRIx64 " raw 0x%" PRIx32 +k230_kpu_gnne_summary(const char *name, uint64_t instructions, uint64_t l2_load, uint64_t l2_load_w, uint64_t l2_store, uint64_t bytes, uint64_t unknown) "%s instructions %" PRIu64 " l2_load %" PRIu64 " l2_load_w %" PRIu64 " l2_store %" PRIu64 " bytes %" PRIu64 " unknown %" PRIu64 +k230_kpu_gnne_compute_summary(const char *name, uint64_t mfu_act1, uint64_t mfu_pdp1, uint64_t mfu_transpose, uint64_t ai2d_compute, uint64_t pu_compute, uint64_t pdp0_compute) "%s mfu_act1 %" PRIu64 " mfu_pdp1 %" PRIu64 " mfu_transpose %" PRIu64 " ai2d_compute %" PRIu64 " pu_compute %" PRIu64 " pdp0_compute %" PRIu64 +k230_kpu_runtime_arg_table(const char *name, uint64_t base, uint32_t addr_words, uint32_t word0, uint32_t word1, uint32_t word2, uint32_t word3) "%s base 0x%" PRIx64 " addr_words %" PRIu32 " words 0x%" PRIx32 " 0x%" PRIx32 " 0x%" PRIx32 " 0x%" PRIx32 +k230_kpu_rdata_shadow_diff(uint64_t pc, uint32_t encoded, uint64_t logical, uint32_t shadow, uint32_t live, unsigned size) "pc 0x%" PRIx64 " encoded 0x%" PRIx32 " logical 0x%" PRIx64 " shadow 0x%" PRIx32 " live 0x%" PRIx32 " size %u" +k230_kpu_l2_load(const char *name, uint64_t source, uint64_t logical, uint64_t bytes, uint64_t head) "%s source 0x%" PRIx64 " logical 0x%" PRIx64 " bytes %" PRIu64 " head 0x%" PRIx64 +k230_kpu_l2_load_source(const char *name, uint64_t pc, uint32_t word, uint32_t regs, uint32_t src_reg, uint32_t dst_reg, uint32_t flags) "%s pc 0x%" PRIx64 " word 0x%" PRIx32 " regs 0x%" PRIx32 " src_reg 0x%" PRIx32 " dst_reg 0x%" PRIx32 " flags 0x%" PRIx32 +k230_kpu_l2_load_source_arg(const char *name, uint64_t pc, uint32_t arg_slot, uint32_t arg_base, uint64_t arg_offset, uint64_t read_phys, uint32_t flags) "%s pc 0x%" PRIx64 " arg_slot %" PRIu32 " arg_base 0x%" PRIx32 " arg_offset 0x%" PRIx64 " read_phys 0x%" PRIx64 " flags 0x%" PRIx32 +k230_kpu_l2_load_source_arg_delta(const char *name, uint64_t pc, uint64_t source, uint32_t slot, uint64_t arg_base, uint64_t delta, uint32_t flags) "%s pc 0x%" PRIx64 " source 0x%" PRIx64 " slot %" PRIu32 " arg_base 0x%" PRIx64 " delta 0x%" PRIx64 " flags 0x%" PRIx32 +k230_kpu_l2_load_source_window(const char *name, uint64_t pc, uint64_t source, uint64_t offset, uint64_t read_phys, uint32_t size, uint64_t head, uint64_t hash, uint32_t ok, uint32_t flags) "%s pc 0x%" PRIx64 " source 0x%" PRIx64 " offset 0x%" PRIx64 " read_phys 0x%" PRIx64 " size %" PRIu32 " head 0x%" PRIx64 " hash 0x%" PRIx64 " ok %" PRIu32 " flags 0x%" PRIx32 +k230_kpu_runtime_arg_window(const char *name, uint64_t pc, uint32_t slot, uint64_t arg_base, uint64_t offset, uint64_t read_phys, uint64_t head, uint64_t hash, uint32_t ok) "%s pc 0x%" PRIx64 " slot %" PRIu32 " arg_base 0x%" PRIx64 " offset 0x%" PRIx64 " read_phys 0x%" PRIx64 " head 0x%" PRIx64 " hash 0x%" PRIx64 " ok %" PRIu32 +k230_kpu_l2_load_detail(const char *name, uint64_t pc, uint64_t source, uint64_t logical, uint64_t rshape, uint32_t src_size, uint32_t dst_size) "%s pc 0x%" PRIx64 " source 0x%" PRIx64 " logical 0x%" PRIx64 " rshape %" PRIu64 " src_size %" PRIu32 " dst_size %" PRIu32 +k230_kpu_l2_load_hash(const char *name, uint64_t pc, uint64_t source_head, uint64_t dest_head, uint64_t source_hash, uint64_t dest_hash) "%s pc 0x%" PRIx64 " source_head 0x%" PRIx64 " dest_head 0x%" PRIx64 " source_hash 0x%" PRIx64 " dest_hash 0x%" PRIx64 +k230_kpu_l2_load_sample(const char *name, uint64_t pc, uint32_t sample, uint64_t src, uint64_t dst_logical, uint64_t dst_physical, uint64_t src_index, uint64_t dst_index, uint32_t raw, uint32_t item) "%s pc 0x%" PRIx64 " sample %" PRIu32 " src 0x%" PRIx64 " dst_logical 0x%" PRIx64 " dst_physical 0x%" PRIx64 " src_index 0x%" PRIx64 " dst_index 0x%" PRIx64 " raw 0x%" PRIx32 " item 0x%" PRIx32 +k230_kpu_l2_load_w(const char *name, uint64_t source, uint64_t logical, uint64_t rlen, uint64_t valid_c, uint64_t head) "%s source 0x%" PRIx64 " logical 0x%" PRIx64 " rlen %" PRIu64 " valid_c %" PRIu64 " head 0x%" PRIx64 +k230_kpu_l2_load_w_sample(const char *name, uint64_t pc, uint32_t sample, uint64_t src, uint64_t dst_logical, uint64_t dst_physical, uint64_t src_index, uint64_t dst_index, uint32_t raw, uint32_t item) "%s pc 0x%" PRIx64 " sample %" PRIu32 " src 0x%" PRIx64 " dst_logical 0x%" PRIx64 " dst_physical 0x%" PRIx64 " src_index 0x%" PRIx64 " dst_index 0x%" PRIx64 " raw 0x%" PRIx32 " item 0x%" PRIx32 +k230_kpu_l2_load_w_skip(const char *name, uint64_t pc, uint32_t reason, uint64_t source, uint64_t logical, uint64_t rlen, uint64_t valid_c) "%s pc 0x%" PRIx64 " reason %" PRIu32 " source 0x%" PRIx64 " logical 0x%" PRIx64 " rlen %" PRIu64 " valid_c %" PRIu64 +k230_kpu_raw_write_sample(const char *name, uint64_t pc, uint64_t addr, uint32_t size, uint64_t head, uint32_t flags) "%s pc 0x%" PRIx64 " addr 0x%" PRIx64 " size %" PRIu32 " head 0x%" PRIx64 " flags 0x%" PRIx32 +k230_kpu_l2_store(const char *name, uint64_t logical, uint64_t physical, uint64_t bytes) "%s logical 0x%" PRIx64 " physical 0x%" PRIx64 " bytes %" PRIu64 +k230_kpu_l2_store_detail(const char *name, uint64_t pc, uint64_t source, uint64_t logical, uint64_t rshape, uint32_t src_size, uint32_t dst_size) "%s pc 0x%" PRIx64 " source 0x%" PRIx64 " logical 0x%" PRIx64 " rshape %" PRIu64 " src_size %" PRIu32 " dst_size %" PRIu32 +k230_kpu_l2_store_hash(const char *name, uint64_t pc, uint64_t source_head, uint64_t dest_head, uint64_t source_hash, uint64_t dest_hash) "%s pc 0x%" PRIx64 " source_head 0x%" PRIx64 " dest_head 0x%" PRIx64 " source_hash 0x%" PRIx64 " dest_hash 0x%" PRIx64 +k230_kpu_l2_store_sample(const char *name, uint64_t pc, uint32_t sample, uint64_t src, uint64_t dst, uint64_t src_index, uint64_t dst_index, uint32_t raw, uint32_t item, uint32_t flags) "%s pc 0x%" PRIx64 " sample %" PRIu32 " src 0x%" PRIx64 " dst 0x%" PRIx64 " src_index 0x%" PRIx64 " dst_index 0x%" PRIx64 " raw 0x%" PRIx32 " item 0x%" PRIx32 " flags 0x%" PRIx32 +k230_kpu_l2_store_skip(const char *name, uint64_t pc, uint32_t reason, uint64_t source, uint64_t logical, uint64_t rshape, uint64_t copied) "%s pc 0x%" PRIx64 " reason %" PRIu32 " source 0x%" PRIx64 " logical 0x%" PRIx64 " rshape %" PRIu64 " copied %" PRIu64 +k230_kpu_ai2d_compute(const char *name, uint64_t pc, uint32_t raw) "%s pc 0x%" PRIx64 " raw 0x%" PRIx32 +k230_kpu_mfu_act1(const char *name, uint64_t pc, uint64_t source, uint64_t dest, uint64_t arg, uint64_t count, uint32_t deq_type, uint32_t quant_type, uint64_t bytes, uint64_t head) "%s pc 0x%" PRIx64 " source 0x%" PRIx64 " dest 0x%" PRIx64 " arg 0x%" PRIx64 " count %" PRIu64 " deq_type %" PRIu32 " quant_type %" PRIu32 " bytes %" PRIu64 " head 0x%" PRIx64 +k230_kpu_mfu_act1_enc(const char *name, uint64_t pc, uint32_t src_enc, uint32_t dst_enc, uint32_t arg_enc) "%s pc 0x%" PRIx64 " src_enc 0x%" PRIx32 " dst_enc 0x%" PRIx32 " arg_enc 0x%" PRIx32 +k230_kpu_mfu_act1_inputs(const char *name, uint64_t pc, uint64_t source_head, uint64_t arg_head) "%s pc 0x%" PRIx64 " source_head 0x%" PRIx64 " arg_head 0x%" PRIx64 +k230_kpu_mfu_act1_sample(const char *name, uint64_t pc, uint32_t sample, uint64_t index, uint64_t src_index, uint64_t dst_index, uint32_t channel, uint32_t raws, uint32_t value_raw, uint32_t flags) "%s pc 0x%" PRIx64 " sample %" PRIu32 " index 0x%" PRIx64 " src_index 0x%" PRIx64 " dst_index 0x%" PRIx64 " channel %" PRIu32 " raws 0x%" PRIx32 " value_raw 0x%" PRIx32 " flags 0x%" PRIx32 +k230_kpu_mfu_pdp1(const char *name, uint64_t pc, uint64_t source, uint64_t dest, uint64_t count, uint32_t funct, uint32_t deq_type, uint32_t quant_type, uint64_t bytes, uint64_t head) "%s pc 0x%" PRIx64 " source 0x%" PRIx64 " dest 0x%" PRIx64 " count %" PRIu64 " funct %" PRIu32 " deq_type %" PRIu32 " quant_type %" PRIu32 " bytes %" PRIu64 " head 0x%" PRIx64 +k230_kpu_mfu_transpose(const char *name, uint64_t pc, uint64_t source, uint64_t dest, uint64_t count, uint32_t permute, uint32_t datatype, uint64_t bytes, uint64_t head) "%s pc 0x%" PRIx64 " source 0x%" PRIx64 " dest 0x%" PRIx64 " count %" PRIu64 " permute %" PRIu32 " datatype %" PRIu32 " bytes %" PRIu64 " head 0x%" PRIx64 +k230_kpu_pu_input_source(const char *name, uint64_t pc, uint32_t tcu_id, uint32_t source_kind, uint32_t fetch, uint32_t dm_source, uint64_t input, uint64_t l1_base, uint64_t l1_span, uint32_t flags) "%s pc 0x%" PRIx64 " tcu_id %" PRIu32 " source_kind %" PRIu32 " fetch 0x%" PRIx32 " dm_source 0x%" PRIx32 " input 0x%" PRIx64 " l1_base 0x%" PRIx64 " l1_span 0x%" PRIx64 " flags 0x%" PRIx32 +k230_kpu_pu_l1_stage(const char *name, uint64_t pc, uint32_t fetch, uint32_t dm_source, uint64_t dm_base, uint64_t dm_span, uint64_t if_size, uint64_t plane_span, uint64_t channel_span, uint32_t flags) "%s pc 0x%" PRIx64 " fetch 0x%" PRIx32 " dm_source 0x%" PRIx32 " dm_base 0x%" PRIx64 " dm_span 0x%" PRIx64 " if_size 0x%" PRIx64 " plane_span 0x%" PRIx64 " channel_span 0x%" PRIx64 " flags 0x%" PRIx32 +k230_kpu_pu_l1_stage_sample(const char *name, uint64_t pc, uint32_t sample, uint32_t coords, uint64_t src_offset, uint64_t src_byte_offset, uint64_t dst_low, uint64_t dst_high, uint32_t raw, uint32_t flags) "%s pc 0x%" PRIx64 " sample %" PRIu32 " coords 0x%" PRIx32 " src_offset 0x%" PRIx64 " src_byte_offset 0x%" PRIx64 " dst_low 0x%" PRIx64 " dst_high 0x%" PRIx64 " raw 0x%" PRIx32 " flags 0x%" PRIx32 +k230_kpu_pu_acc_sample(const char *name, uint64_t pc, uint32_t tcu_id, uint32_t sample, uint32_t coords, uint64_t output_index, uint64_t psum_index, int32_t acc, uint32_t flags) "%s pc 0x%" PRIx64 " tcu_id %" PRIu32 " sample %" PRIu32 " coords 0x%" PRIx32 " output_index 0x%" PRIx64 " psum_index 0x%" PRIx64 " acc %" PRId32 " flags 0x%" PRIx32 +k230_kpu_pu_mac_sample(const char *name, uint64_t pc, uint32_t sample, uint32_t coords, uint32_t ic, uint64_t input_index, uint64_t weight_index, int32_t input, uint32_t weight, int32_t product) "%s pc 0x%" PRIx64 " sample %" PRIu32 " coords 0x%" PRIx32 " ic %" PRIu32 " input_index 0x%" PRIx64 " weight_index 0x%" PRIx64 " input %" PRId32 " weight 0x%" PRIx32 " product %" PRId32 +k230_kpu_pu_if_sample(const char *name, uint64_t pc, uint32_t sample, uint32_t coords, uint32_t ic, uint32_t kind, uint64_t input_index, int32_t input, uint32_t raw, uint32_t flags) "%s pc 0x%" PRIx64 " sample %" PRIu32 " coords 0x%" PRIx32 " ic %" PRIu32 " kind %" PRIu32 " input_index 0x%" PRIx64 " input %" PRId32 " raw 0x%" PRIx32 " flags 0x%" PRIx32 +k230_kpu_pu_act0_sample(const char *name, uint64_t pc, uint32_t tcu_id, uint32_t sample, uint32_t coords, uint64_t output_index, uint64_t psum_index, int32_t acc, uint32_t shift, uint32_t out_raw) "%s pc 0x%" PRIx64 " tcu_id %" PRIu32 " sample %" PRIu32 " coords 0x%" PRIx32 " output_index 0x%" PRIx64 " psum_index 0x%" PRIx64 " acc %" PRId32 " shift %" PRIu32 " out_raw 0x%" PRIx32 +k230_kpu_pu_act0_sample_addr(const char *name, uint64_t pc, uint32_t tcu_id, uint32_t sample, uint64_t act0, uint64_t dest, uint64_t act0_head) "%s pc 0x%" PRIx64 " tcu_id %" PRIu32 " sample %" PRIu32 " act0 0x%" PRIx64 " dest 0x%" PRIx64 " act0_head 0x%" PRIx64 +k230_kpu_pu_compute(const char *name, uint64_t pc, uint32_t tcu_id, uint64_t dest, uint64_t bytes, uint64_t head) "%s pc 0x%" PRIx64 " tcu_id %" PRIu32 " dest 0x%" PRIx64 " bytes %" PRIu64 " head 0x%" PRIx64 +k230_kpu_pu_compute_skip(const char *name, uint64_t pc, uint32_t reason, uint32_t tcu_id, uint64_t source, uint64_t dest, uint64_t detail) "%s pc 0x%" PRIx64 " reason %" PRIu32 " tcu_id %" PRIu32 " source 0x%" PRIx64 " dest 0x%" PRIx64 " detail 0x%" PRIx64 +k230_kpu_pdp0_compute(const char *name, uint64_t pc, uint32_t tcu_id, uint64_t dest, uint64_t bytes, uint64_t head) "%s pc 0x%" PRIx64 " tcu_id %" PRIu32 " dest 0x%" PRIx64 " bytes %" PRIu64 " head 0x%" PRIx64 + # allwinner-cpucfg.c allwinner_cpucfg_cpu_reset(uint8_t cpu_id, uint32_t reset_addr) "id %u, reset_addr 0x%" PRIx32 allwinner_cpucfg_read(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32 diff --git a/hw/net/Kconfig b/hw/net/Kconfig index b56a173eed5ef..7ac0c04b3e172 100644 --- a/hw/net/Kconfig +++ b/hw/net/Kconfig @@ -109,6 +109,15 @@ config SUNHME config FTGMAC100 bool +config PHYTIUM_XMAC + bool + +config NPCM_GMAC + bool + +config DWMAC4 + bool + config SUNGEM bool depends on PCI diff --git a/hw/net/dwmac4.c b/hw/net/dwmac4.c new file mode 100644 index 0000000000000..0f1be0214d0a0 --- /dev/null +++ b/hw/net/dwmac4.c @@ -0,0 +1,1127 @@ +/* + * Synopsys DesignWare Ethernet MAC dwmac-4.20a (GMAC4). + * + * Copyright (c) 2026 Chao Liu + * + * RK3588 wires this for its GMAC node (snps,dwmac-4.20a / + * rockchip,rk3588-gmac). The model is driven by the GMAC4 register map + * (dwmac4.h / dwmac4_dma.h / dwmac4_descs.h in the Linux stmmac driver): + * + * - MAC register bank 0x000..0x3ff (MAC_CONFIG, PACKET_FILTER, ADDR slots, + * MDIO, HW_FEATURE0..3, and board-selectable MAC_VERSION @ 0x110. + * - DMA register bank 0x1000..0x11ff (DMA_BUS_MODE, per-channel block at + * 0x1100 + chan*0x80: TX/RX_CONTROL, TX/RX_BASE_ADDR, RING_LEN, + * INTR_ENA, CHAN_STATUS as W1C with 4.10-layout NIS/AIS). + * - dwmac4 4-word descriptor format: OWN/FIRST/LAST in TDES3/RDES3, buffer + * addr in DES0, sizes+IOC in TDES2, RX write-back length in RDES3. + * + * The whole 64 KiB MMIO window is wrapped by a custom dispatcher that RAZ/WIs + * any offset the banks do not model - guest writes to "unassigned" offsets + * (MTL, PTP, GRF mirror, etc.) must NOT abort the AArch64 CPU (lesson D-15). + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/core/register.h" +#include "hw/core/registerfields.h" +#include "hw/net/dwmac4.h" +#include "migration/vmstate.h" +#include "net/checksum.h" +#include "net/eth.h" +#include "net/net.h" +#include "qapi/error.h" +#include "qemu/bitops.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/units.h" +#include "system/dma.h" + +/* ---- MAC register offsets (GMAC4 family) ----------------------------- */ + +REG32(GMAC_CONFIG, 0x000) + FIELD(GMAC_CONFIG, RE, 0, 1) + FIELD(GMAC_CONFIG, TE, 1, 1) +REG32(GMAC_EXT_CONFIG, 0x004) +REG32(GMAC_PACKET_FILTER, 0x008) +REG32(GMAC_HASH_TAB0, 0x010) +REG32(GMAC_HASH_TAB1, 0x014) +REG32(GMAC_Q0_TX_FLOW_CTL, 0x070) +REG32(GMAC_RX_FLOW_CTRL, 0x090) +REG32(GMAC_TXQ_PRTY_MAP0, 0x098) +REG32(GMAC_TXQ_PRTY_MAP1, 0x09c) +REG32(GMAC_RXQ_CTRL0, 0x0a0) +REG32(GMAC_RXQ_CTRL1, 0x0a4) +REG32(GMAC_RXQ_CTRL2, 0x0a8) +REG32(GMAC_RXQ_CTRL3, 0x0ac) +REG32(GMAC_INT_STATUS, 0x0b0) +REG32(GMAC_INT_EN, 0x0b4) +REG32(GMAC_PMT, 0x0c0) +REG32(GMAC4_LPI_CTRL_STATUS, 0x0d0) +REG32(GMAC4_LPI_TIMER_CTRL, 0x0d4) +REG32(GMAC_PHYIF_CONTROL_STATUS, 0x0f8) + FIELD(GMAC_PHYIF_CONTROL_STATUS, TC, 0, 1) + FIELD(GMAC_PHYIF_CONTROL_STATUS, LUD, 1, 1) + FIELD(GMAC_PHYIF_CONTROL_STATUS, RGS, 2, 1) +REG32(GMAC4_VERSION, 0x110) +REG32(GMAC_DEBUG, 0x114) +REG32(GMAC_HW_FEATURE0, 0x11c) + FIELD(GMAC_HW_FEATURE0, RXCOESEL, 0, 1) /* dev->hwts_rx_en */ +REG32(GMAC_HW_FEATURE1, 0x120) +REG32(GMAC_HW_FEATURE2, 0x124) +REG32(GMAC_HW_FEATURE3, 0x128) +REG32(GMAC_MDIO_ADDR, 0x200) +REG32(GMAC_MDIO_DATA, 0x204) +REG32(GMAC_GPIO_STATUS, 0x20c) +REG32(GMAC_ARP_ADDR, 0x210) +REG32(GMAC_EXT_CFG1, 0x238) +REG32(GMAC_ADDR_HIGH0, 0x300) +REG32(GMAC_ADDR_LOW0, 0x304) + +/* ---- DMA register offsets ------------------------------------------- */ + +REG32(DMA_BUS_MODE, 0x000) /* +0x1000 - soft reset bit0 */ + FIELD(DMA_BUS_MODE, SFT_RESET, 0, 1) +REG32(DMA_SYS_BUS_MODE, 0x004) +REG32(DMA_STATUS, 0x008) /* legacy global status */ +REG32(DMA_AXI_BUS_MODE, 0x028) +REG32(DMA_TBS_CTRL, 0x050) + +/* + * Per-channel DMA block: base 0x1100, stride 0x80. The contract calls out + * chan 0 as primary. We expose chan 0 (and chan 1) at the natural offsets. + * Below REG32 names are chan-relative (offset within the per-channel block), + * the absolute address is DWMAC4_DMA_REG_BASE + 0x100 + chan*0x80 + off. + */ +#define DMA_CHAN_BLOCK_BASE 0x100 /* within DMA bank: 0x1000+0x100 */ +REG32(DMA_CHAN_CONTROL, 0x00) +REG32(DMA_CHAN_TX_CONTROL, 0x04) + FIELD(DMA_CHAN_TX_CONTROL, ST, 0, 1) +REG32(DMA_CHAN_RX_CONTROL, 0x08) + FIELD(DMA_CHAN_RX_CONTROL, SR, 0, 1) + FIELD(DMA_CHAN_RX_CONTROL, RBSZ, 1, 14) +REG32(DMA_CHAN_TX_BASE_ADDR_HI, 0x10) +REG32(DMA_CHAN_TX_BASE_ADDR, 0x14) +REG32(DMA_CHAN_RX_BASE_ADDR_HI, 0x18) +REG32(DMA_CHAN_RX_BASE_ADDR, 0x1c) +REG32(DMA_CHAN_TX_END_ADDR, 0x20) /* TX tail pointer */ +REG32(DMA_CHAN_RX_END_ADDR, 0x28) /* RX tail pointer */ +REG32(DMA_CHAN_TX_RING_LEN, 0x2c) +REG32(DMA_CHAN_RX_RING_LEN, 0x30) +REG32(DMA_CHAN_INTR_ENA, 0x34) +REG32(DMA_CHAN_RX_WATCHDOG, 0x38) +REG32(DMA_CHAN_SLOT_CTRL_STATUS, 0x3c) +REG32(DMA_CHAN_CUR_TX_DESC, 0x44) +REG32(DMA_CHAN_CUR_RX_DESC, 0x4c) +REG32(DMA_CHAN_CUR_TX_BUF_ADDR_HI, 0x50) +REG32(DMA_CHAN_CUR_TX_BUF_ADDR, 0x54) +REG32(DMA_CHAN_CUR_RX_BUF_ADDR_HI, 0x58) +REG32(DMA_CHAN_CUR_RX_BUF_ADDR, 0x5c) +REG32(DMA_CHAN_STATUS, 0x60) + FIELD(DMA_CHAN_STATUS, TI, 0, 1) + FIELD(DMA_CHAN_STATUS, TPS, 1, 1) + FIELD(DMA_CHAN_STATUS, TBU, 2, 1) + FIELD(DMA_CHAN_STATUS, RI, 6, 1) + FIELD(DMA_CHAN_STATUS, RBU, 7, 1) + FIELD(DMA_CHAN_STATUS, RPS, 8, 1) + FIELD(DMA_CHAN_STATUS, RWT, 9, 1) + FIELD(DMA_CHAN_STATUS, ETI, 10, 1) + FIELD(DMA_CHAN_STATUS, ERI, 11, 1) + FIELD(DMA_CHAN_STATUS, FBE, 12, 1) + FIELD(DMA_CHAN_STATUS, CDE, 13, 1) + /* 4.10 layout: AIS=14, NIS=15 (vs 4.00 which used 15/16). */ + FIELD(DMA_CHAN_STATUS, AIS, 14, 1) + FIELD(DMA_CHAN_STATUS, NIS, 15, 1) + +#define DMA_CHAN_STATUS_W1C_BITS (R_DMA_CHAN_STATUS_TI_MASK | \ + R_DMA_CHAN_STATUS_TPS_MASK | \ + R_DMA_CHAN_STATUS_TBU_MASK | \ + R_DMA_CHAN_STATUS_RI_MASK | \ + R_DMA_CHAN_STATUS_RBU_MASK | \ + R_DMA_CHAN_STATUS_RPS_MASK | \ + R_DMA_CHAN_STATUS_RWT_MASK | \ + R_DMA_CHAN_STATUS_ETI_MASK | \ + R_DMA_CHAN_STATUS_ERI_MASK | \ + R_DMA_CHAN_STATUS_FBE_MASK | \ + R_DMA_CHAN_STATUS_CDE_MASK | \ + R_DMA_CHAN_STATUS_AIS_MASK | \ + R_DMA_CHAN_STATUS_NIS_MASK) + +/* INTR_ENA bits - 4.10 layout (NIE=15, AIE=14). */ +#define DMA_CHAN_INTR_TIE BIT(0) +#define DMA_CHAN_INTR_RIE BIT(6) +#define DMA_CHAN_INTR_RBUE BIT(7) +#define DMA_CHAN_INTR_RSE BIT(8) +#define DMA_CHAN_INTR_FBE BIT(12) +#define DMA_CHAN_INTR_AIE BIT(14) +#define DMA_CHAN_INTR_NIE BIT(15) + +/* + * Default interrupt mask the stmmac driver writes for dwmac-4.10: + * NIE|RIE|TIE|AIE|RSE|RBUE|FBE. We don't enforce it; we just AND STATUS + * with INTR_ENA when deciding whether to assert IRQ. + */ + +/* ---- dwmac4 descriptor layout (4-word / 16-byte) -------------------- */ +/* + * TDES3 / RDES3 share the OWN bit (31). FIRST=29, LAST=28. For RX + * write-back, PACKET_SIZE lives in RDES3[14:0]. IOC for TX is TDES2[31]. + */ +#define DESC_OWN BIT(31) +#define TX_DESC3_FIRST BIT(29) +#define TX_DESC3_LAST BIT(28) +#define TX_DESC3_CIC_MASK 0x00030000u +#define TX_DESC2_IOC BIT(31) +#define TX_DESC2_B1SZ_MASK 0x00003fffu /* BUFFER1_SIZE[13:0] */ +#define RX_DESC3_FIRST BIT(29) +#define RX_DESC3_LAST BIT(28) +#define RX_DESC3_PKT_SIZE_MASK 0x00007fffu /* PACKET_SIZE[14:0] */ + +/* ---- Forward decls -------------------------------------------------- */ + +static void dwmac4_update_irq(DWMAC4State *s); + +/* ===================================================================== */ +/* Helpers */ +/* ===================================================================== */ + +static inline uint32_t dwmac4_chan_status_addr(int chan) +{ + return DMA_CHAN_BLOCK_BASE + chan * DWMAC4_DMA_CHAN_STRIDE + + A_DMA_CHAN_STATUS; +} + +static inline uint32_t dwmac4_chan_intr_ena_addr(int chan) +{ + return DMA_CHAN_BLOCK_BASE + chan * DWMAC4_DMA_CHAN_STRIDE + + A_DMA_CHAN_INTR_ENA; +} + +static inline uint32_t dwmac4_chan_reg(int chan, hwaddr intra) +{ + return DMA_CHAN_BLOCK_BASE + chan * DWMAC4_DMA_CHAN_STRIDE + intra; +} + +static int dwmac4_read_desc(hwaddr addr, uint32_t w[4]) +{ + if (dma_memory_read(&address_space_memory, addr, w, 16, + MEMTXATTRS_UNSPECIFIED)) { + qemu_log_mask(LOG_GUEST_ERROR, + "dwmac4: failed DMA read of descriptor @ 0x%" + HWADDR_PRIx "\n", addr); + return -1; + } + for (int i = 0; i < 4; i++) { + w[i] = le32_to_cpu(w[i]); + } + return 0; +} + +static int dwmac4_write_desc(hwaddr addr, const uint32_t w[4]) +{ + uint32_t le[4]; + for (int i = 0; i < 4; i++) { + le[i] = cpu_to_le32(w[i]); + } + if (dma_memory_write(&address_space_memory, addr, le, 16, + MEMTXATTRS_UNSPECIFIED)) { + qemu_log_mask(LOG_GUEST_ERROR, + "dwmac4: failed DMA write of descriptor @ 0x%" + HWADDR_PRIx "\n", addr); + return -1; + } + return 0; +} + +/* ===================================================================== */ +/* IRQ */ +/* ===================================================================== */ + +static void dwmac4_update_irq(DWMAC4State *s) +{ + /* + * macirq is asserted when any enabled status bit is set on any channel. + * We model chan 0 (primary); also fold in chan 1 for completeness. + */ + bool level = false; + for (int chan = 0; chan < DWMAC4_NR_CHANNELS; chan++) { + uint32_t status = s->dma_regs[dwmac4_chan_status_addr(chan) / 4]; + uint32_t ena = s->dma_regs[dwmac4_chan_intr_ena_addr(chan) / 4]; + if (status & ena) { + level = true; + break; + } + } + qemu_set_irq(s->sb_irq, level); +} + +/* ===================================================================== */ +/* TX descriptor-ring walk */ +/* ===================================================================== */ + +static void dwmac4_try_send(DWMAC4State *s, int chan) +{ + g_autoptr(GByteArray) frame = NULL; + bool frame_checksum = false; + hwaddr base_addr = ((uint64_t)s->dma_regs[dwmac4_chan_reg(chan, + A_DMA_CHAN_TX_BASE_ADDR_HI) / 4] << 32) | + s->dma_regs[dwmac4_chan_reg(chan, + A_DMA_CHAN_TX_BASE_ADDR) / 4]; + uint32_t ring_len = s->dma_regs[dwmac4_chan_reg(chan, + A_DMA_CHAN_TX_RING_LEN) / 4]; + uint64_t ring_entries = (uint64_t)ring_len + 1; + if (!base_addr) { + return; + } + + NetClientState *nc = qemu_get_queue(s->nic); + hwaddr cur = s->tx_desc_cur[chan]; + if (!cur) { + cur = base_addr; + } + + for (uint64_t i = 0; i < ring_entries; i++) { + uint32_t d[4]; + if (dwmac4_read_desc(cur, d)) { + return; + } + + /* OWN=1 -> MAC owns, can TX. Otherwise ring drained for now. */ + if (!(d[3] & DESC_OWN)) { + break; + } + + bool first = d[3] & TX_DESC3_FIRST; + bool last = d[3] & TX_DESC3_LAST; + bool ioc = d[2] & TX_DESC2_IOC; + bool checksum_insertion = d[3] & TX_DESC3_CIC_MASK; + uint32_t b1sz = d[2] & TX_DESC2_B1SZ_MASK; + /* + * dwmac4 TDES0 holds buffer-1 address (low 32). In extended 64-bit + * descriptor mode TDES1 holds the high 32 bits; in legacy 32-bit + * mode TDES1 holds a separate buffer-2 address. stmmac programs + * 64-bit descriptors only when ADDR64 (HW_FEATURE1) is set; we + * keep things simple and treat TDES0|TDES1<<32 as buf1 to cover + * both layouts for the single-buffer frames we send. + */ + hwaddr b1addr = ((uint64_t)d[1] << 32) | d[0]; + + if (first) { + g_clear_pointer(&frame, g_byte_array_unref); + frame = g_byte_array_new(); + frame_checksum = checksum_insertion; + } + + if (frame && b1sz) { + size_t offset = frame->len; + + if (offset + b1sz > 65536) { + qemu_log_mask(LOG_GUEST_ERROR, + "dwmac4: TX frame exceeds 64 KiB\n"); + return; + } + g_byte_array_set_size(frame, offset + b1sz); + if (dma_memory_read(&address_space_memory, b1addr, + frame->data + offset, b1sz, + MEMTXATTRS_UNSPECIFIED)) { + qemu_log_mask(LOG_GUEST_ERROR, + "dwmac4: TX buffer read failed @ 0x%" + HWADDR_PRIx "\n", b1addr); + return; + } + } + + if (last && frame) { + if (frame_checksum) { + net_checksum_calculate(frame->data, frame->len, CSUM_ALL); + } + qemu_send_packet(nc, frame->data, frame->len); + g_clear_pointer(&frame, g_byte_array_unref); + } + + /* Write-back: clear OWN. Error summary (bit15) left clear. */ + d[3] &= ~DESC_OWN; + if (dwmac4_write_desc(cur, d)) { + return; + } + + cur += 16; + if (cur >= base_addr + ring_entries * 16) { + cur = base_addr; + } + + if (ioc) { + uint32_t off = dwmac4_chan_status_addr(chan) / 4; + s->dma_regs[off] |= R_DMA_CHAN_STATUS_TI_MASK | + R_DMA_CHAN_STATUS_NIS_MASK; + dwmac4_update_irq(s); + } + + } + + s->tx_desc_cur[chan] = cur; +} + +/* ===================================================================== */ +/* RX */ +/* ===================================================================== */ + +static bool dwmac4_can_receive(NetClientState *nc) +{ + DWMAC4State *s = DWMAC4(qemu_get_nic_opaque(nc)); + + /* Need MAC RX enable and DMA RX start on chan 0. */ + if (!ARRAY_FIELD_EX32(s->mac_regs, GMAC_CONFIG, RE)) { + return false; + } + int chan = 0; + uint32_t rxctl = s->dma_regs[dwmac4_chan_reg(chan, A_DMA_CHAN_RX_CONTROL) / 4]; + if (!(rxctl & R_DMA_CHAN_RX_CONTROL_SR_MASK)) { + return false; + } + return true; +} + +static ssize_t dwmac4_receive(NetClientState *nc, const uint8_t *buf, size_t len) +{ + DWMAC4State *s = DWMAC4(qemu_get_nic_opaque(nc)); + int chan = 0; + + if (!dwmac4_can_receive(nc)) { + return 0; + } + + hwaddr base_addr = ((uint64_t)s->dma_regs[dwmac4_chan_reg(chan, + A_DMA_CHAN_RX_BASE_ADDR_HI) / 4] << 32) | + s->dma_regs[dwmac4_chan_reg(chan, + A_DMA_CHAN_RX_BASE_ADDR) / 4]; + uint32_t ring_len = s->dma_regs[dwmac4_chan_reg(chan, + A_DMA_CHAN_RX_RING_LEN) / 4]; + uint64_t ring_entries = (uint64_t)ring_len + 1; + if (!base_addr) { + return len; /* no ring posted yet - silently drop */ + } + + hwaddr cur = s->rx_desc_cur[chan]; + if (!cur) { + cur = base_addr; + } + + /* Find the next descriptor the MAC owns. */ + for (uint64_t i = 0; i < ring_entries; i++) { + uint32_t d[4]; + if (dwmac4_read_desc(cur, d)) { + return len; + } + if (!(d[3] & DESC_OWN)) { + /* Driver still owns this one - ring full, drop the frame. */ + return len; + } + + hwaddr b1addr = ((uint64_t)d[1] << 32) | d[0]; + /* DWMAC4 keeps the receive buffer size in DMA_CHAN_RX_CONTROL. */ + uint32_t rxctl = s->dma_regs[dwmac4_chan_reg( + chan, A_DMA_CHAN_RX_CONTROL) / 4]; + uint32_t b1sz = FIELD_EX32(rxctl, DMA_CHAN_RX_CONTROL, RBSZ); + uint32_t to_write = MIN((uint32_t)len, b1sz); + + if (to_write && dma_memory_write(&address_space_memory, b1addr, buf, + to_write, MEMTXATTRS_UNSPECIFIED)) { + qemu_log_mask(LOG_GUEST_ERROR, + "dwmac4: RX buffer write failed @ 0x%" + HWADDR_PRIx "\n", b1addr); + return len; + } + + /* + * Write-back: clear OWN, set FIRST+LAST, set packet length in + * RDES3[14:0]. We treat each frame as a single descriptor. + */ + d[3] &= ~DESC_OWN; + d[3] &= ~RX_DESC3_PKT_SIZE_MASK; + /* + * GMAC4 reports a packet size that includes the four-byte Ethernet + * FCS, although the DMA buffer does not contain the FCS. stmmac + * accounts for that contract by subtracting ETH_FCS_LEN before it + * hands the skb to the network stack. + */ + d[3] |= RX_DESC3_FIRST | RX_DESC3_LAST | + (((uint32_t)len + ETH_FCS_LEN) & RX_DESC3_PKT_SIZE_MASK); + if (dwmac4_write_desc(cur, d)) { + return len; + } + + cur += 16; + if (cur >= base_addr + ring_entries * 16) { + cur = base_addr; + } + s->rx_desc_cur[chan] = cur; + + /* Raise RI + NIS. */ + uint32_t off = dwmac4_chan_status_addr(chan) / 4; + s->dma_regs[off] |= R_DMA_CHAN_STATUS_RI_MASK | + R_DMA_CHAN_STATUS_NIS_MASK; + dwmac4_update_irq(s); + return len; + } + + return len; +} + +static void dwmac4_cleanup(NetClientState *nc) +{ +} + +static void dwmac4_link_status_changed(NetClientState *nc) +{ + /* Nothing to model - link state is reflected only via PHY MDIO reads. */ +} + +static NetClientInfo net_dwmac4_info = { + .type = NET_CLIENT_DRIVER_NIC, + .size = sizeof(NICState), + .can_receive = dwmac4_can_receive, + .receive = dwmac4_receive, + .cleanup = dwmac4_cleanup, + .link_status_changed = dwmac4_link_status_changed, +}; + +/* ===================================================================== */ +/* MDIO (clause-22, minimal PHY) */ +/* ===================================================================== */ + +#define GMAC4_MDIO_BUSY BIT(0) +#define GMAC4_MDIO_GOC(v) extract32((v), 2, 2) +#define GMAC4_MDIO_GOC_WRITE 1 +#define GMAC4_MDIO_GOC_READ 3 +#define GMAC4_MDIO_RDA(v) extract32((v), 16, 5) +#define GMAC4_MDIO_PA(v) extract32((v), 21, 5) + +static void dwmac4_mdio(DWMAC4State *s, uint32_t v) +{ + bool busy = v & GMAC4_MDIO_BUSY; + uint8_t goc = GMAC4_MDIO_GOC(v); + bool is_gmac4 = goc == GMAC4_MDIO_GOC_READ || + goc == GMAC4_MDIO_GOC_WRITE; + bool write = is_gmac4 ? goc == GMAC4_MDIO_GOC_WRITE : v & BIT(1); + uint8_t pa = is_gmac4 ? GMAC4_MDIO_PA(v) : (v >> 11) & 0x1f; + uint8_t gr = is_gmac4 ? GMAC4_MDIO_RDA(v) : (v >> 6) & 0x1f; + uint16_t data; + + if (!busy) { + s->mac_regs[R_GMAC_MDIO_ADDR] = v; + return; + } + if (pa != s->phy_addr) { + if (!write) { + s->mac_regs[R_GMAC_MDIO_DATA] = 0xffff; + } + s->mac_regs[R_GMAC_MDIO_ADDR] = v & ~GMAC4_MDIO_BUSY; + return; + } + + if (write) { + data = s->mac_regs[R_GMAC_MDIO_DATA] & 0xffff; + if (gr == 31) { + s->phy_page = data; + } else if (s->phy_page == 0) { + /* BMCR reset and autoneg restart are self-clearing commands. */ + s->phy_regs[gr] = gr == 0 ? data & ~(BIT(15) | BIT(9)) : data; + } + } else { + if (gr == 31) { + s->mac_regs[R_GMAC_MDIO_DATA] = s->phy_page; + } else { + /* Vendor pages are accepted as RAZ/WI; page 0 is clause-22. */ + s->mac_regs[R_GMAC_MDIO_DATA] = + s->phy_page == 0 ? s->phy_regs[gr] : 0; + } + } + + /* BUSY is self-clearing in hardware; model that. */ + s->mac_regs[R_GMAC_MDIO_ADDR] = v & ~GMAC4_MDIO_BUSY; +} + +/* ===================================================================== */ +/* Register side-effects (registerinfo callbacks) */ +/* ===================================================================== */ + +static void gmac_config_postw(RegisterInfo *reg, uint64_t val) +{ + DWMAC4State *s = DWMAC4(reg->opaque); + if (val & R_GMAC_CONFIG_RE_MASK) { + /* RX was just enabled - flush any queued packets from the netdev. */ + qemu_flush_queued_packets(qemu_get_queue(s->nic)); + } +} + +static void gmac_addr_high0_postw(RegisterInfo *reg, uint64_t val) +{ + DWMAC4State *s = DWMAC4(reg->opaque); + s->conf.macaddr.a[0] = (val >> 8) & 0xff; + s->conf.macaddr.a[1] = val & 0xff; +} + +static void gmac_addr_low0_postw(RegisterInfo *reg, uint64_t val) +{ + DWMAC4State *s = DWMAC4(reg->opaque); + s->conf.macaddr.a[2] = (val >> 24) & 0xff; + s->conf.macaddr.a[3] = (val >> 16) & 0xff; + s->conf.macaddr.a[4] = (val >> 8) & 0xff; + s->conf.macaddr.a[5] = val & 0xff; +} + +static void gmac_mdio_addr_postw(RegisterInfo *reg, uint64_t val) +{ + DWMAC4State *s = DWMAC4(reg->opaque); + dwmac4_mdio(s, val); +} + +static void dma_bus_mode_postw(RegisterInfo *reg, uint64_t val) +{ + DWMAC4State *s = DWMAC4(reg->opaque); + if (val & R_DMA_BUS_MODE_SFT_RESET_MASK) { + /* + * Soft reset: clear the per-channel state and the SFT_RESET bit, + * matching dwmac4_dma_reset() in the driver. We do NOT touch the + * MAC bank (driver separates MAC reset from DMA reset). + */ + for (int chan = 0; chan < DWMAC4_NR_CHANNELS; chan++) { + s->tx_desc_cur[chan] = 0; + s->rx_desc_cur[chan] = 0; + } + s->dma_regs[R_DMA_BUS_MODE] &= ~R_DMA_BUS_MODE_SFT_RESET_MASK; + } +} + +/* + * Map a DMA-bank-relative offset to a channel index, or -1 if it's not a + * per-channel register. The DMA bank starts at 0x1000; per-channel block + * starts at +0x100 within the bank. + */ +static int dwmac4_chan_of_dma(hwaddr off_in_bank) +{ + if (off_in_bank < DMA_CHAN_BLOCK_BASE) { + return -1; + } + hwaddr rel = off_in_bank - DMA_CHAN_BLOCK_BASE; + if (rel >= DWMAC4_NR_CHANNELS * DWMAC4_DMA_CHAN_STRIDE) { + return -1; + } + if ((rel % DWMAC4_DMA_CHAN_STRIDE) >= 0x80) { + return -1; + } + return rel / DWMAC4_DMA_CHAN_STRIDE; +} + +static void dma_chan_tx_tail_postw(RegisterInfo *reg, uint64_t val) +{ + /* The TX_END_ADDR register is the tail pointer; writing it kicks TX. */ + DWMAC4State *s = DWMAC4(reg->opaque); + int chan = dwmac4_chan_of_dma(reg->access->addr); + if (chan < 0) { + return; + } + uint32_t txctl = s->dma_regs[dwmac4_chan_reg(chan, + A_DMA_CHAN_TX_CONTROL) / 4]; + if (txctl & R_DMA_CHAN_TX_CONTROL_ST_MASK) { + dwmac4_try_send(s, chan); + } +} + +static void dma_chan_tx_base_postw(RegisterInfo *reg, uint64_t val) +{ + /* Ring base reprogrammed - reset the walk cursor so we start fresh. */ + DWMAC4State *s = DWMAC4(reg->opaque); + int chan = dwmac4_chan_of_dma(reg->access->addr); + if (chan >= 0) { + s->tx_desc_cur[chan] = 0; + } +} + +static void dma_chan_rx_base_postw(RegisterInfo *reg, uint64_t val) +{ + DWMAC4State *s = DWMAC4(reg->opaque); + int chan = dwmac4_chan_of_dma(reg->access->addr); + if (chan >= 0) { + s->rx_desc_cur[chan] = 0; + } +} + +static void dma_chan_tx_control_postw(RegisterInfo *reg, uint64_t val) +{ + DWMAC4State *s = DWMAC4(reg->opaque); + int chan = dwmac4_chan_of_dma(reg->access->addr); + if (chan < 0) { + return; + } + if (val & R_DMA_CHAN_TX_CONTROL_ST_MASK) { + dwmac4_try_send(s, chan); + } +} + +static void dma_chan_rx_control_postw(RegisterInfo *reg, uint64_t val) +{ + DWMAC4State *s = DWMAC4(reg->opaque); + if (val & R_DMA_CHAN_RX_CONTROL_SR_MASK) { + /* SR=1 - RX DMA started; tell the netdev we may now receive. */ + qemu_flush_queued_packets(qemu_get_queue(s->nic)); + } +} + +static void dma_chan_rx_tail_postw(RegisterInfo *reg, uint64_t val) +{ + /* RX_END_ADDR/tail pointer write - driver kicks RX after refilling. */ + DWMAC4State *s = DWMAC4(reg->opaque); + qemu_flush_queued_packets(qemu_get_queue(s->nic)); +} + +static void dma_chan_status_postw(RegisterInfo *reg, uint64_t val) +{ + /* W1C handled by registerinfo (.w1c=...); recompute the IRQ line. */ + DWMAC4State *s = DWMAC4(reg->opaque); + dwmac4_update_irq(s); +} + +/* ===================================================================== */ +/* Register definitions */ +/* ===================================================================== */ + +static const RegisterAccessInfo mac_regs_info[] = { + { .name = "GMAC_CONFIG", .addr = A_GMAC_CONFIG, + .post_write = gmac_config_postw, + }, + { .name = "GMAC_EXT_CONFIG", .addr = A_GMAC_EXT_CONFIG, + }, + { .name = "GMAC_PACKET_FILTER", .addr = A_GMAC_PACKET_FILTER, + }, + { .name = "GMAC_HASH_TAB0", .addr = A_GMAC_HASH_TAB0, + }, + { .name = "GMAC_HASH_TAB1", .addr = A_GMAC_HASH_TAB1, + }, + { .name = "GMAC_Q0_TX_FLOW_CTL", .addr = A_GMAC_Q0_TX_FLOW_CTL, + }, + { .name = "GMAC_RX_FLOW_CTRL", .addr = A_GMAC_RX_FLOW_CTRL, + }, + { .name = "GMAC_TXQ_PRTY_MAP0", .addr = A_GMAC_TXQ_PRTY_MAP0, + }, + { .name = "GMAC_TXQ_PRTY_MAP1", .addr = A_GMAC_TXQ_PRTY_MAP1, + }, + { .name = "GMAC_RXQ_CTRL0", .addr = A_GMAC_RXQ_CTRL0, + }, + { .name = "GMAC_RXQ_CTRL1", .addr = A_GMAC_RXQ_CTRL1, + }, + { .name = "GMAC_RXQ_CTRL2", .addr = A_GMAC_RXQ_CTRL2, + }, + { .name = "GMAC_RXQ_CTRL3", .addr = A_GMAC_RXQ_CTRL3, + }, + { .name = "GMAC_INT_STATUS", .addr = A_GMAC_INT_STATUS, + .ro = MAKE_64BIT_MASK(0, 32), + }, + { .name = "GMAC_INT_EN", .addr = A_GMAC_INT_EN, + }, + { .name = "GMAC_PMT", .addr = A_GMAC_PMT, + }, + { .name = "GMAC4_LPI_CTRL_STATUS", .addr = A_GMAC4_LPI_CTRL_STATUS, + }, + { .name = "GMAC4_LPI_TIMER_CTRL", .addr = A_GMAC4_LPI_TIMER_CTRL, + }, + { .name = "GMAC_PHYIF_CONTROL_STATUS", .addr = A_GMAC_PHYIF_CONTROL_STATUS, + /* Link is always up; return LNKSTS-style bit so probe sees carrier. */ + .reset = BIT(19), + .ro = MAKE_64BIT_MASK(0, 32), + }, + { .name = "GMAC4_VERSION", .addr = A_GMAC4_VERSION, + .reset = DWMAC4_DEFAULT_SNPS_VERSION, + .ro = MAKE_64BIT_MASK(0, 32), + }, + { .name = "GMAC_DEBUG", .addr = A_GMAC_DEBUG, + .ro = MAKE_64BIT_MASK(0, 32), + }, + /* + * HW_FEATURE0..3: report the minimum feature set needed for Linux + * stmmac probe: RXCOESEL/TXCOSEL/MMCSEL/MGKSEL/TSSEL. Bit layout from + * dwmac4.h: RXCOESEL=16, TXCOSEL=14, TSSEL=12, MMCSEL=8, MGKSEL=7. + */ + { .name = "GMAC_HW_FEATURE0", .addr = A_GMAC_HW_FEATURE0, + .reset = BIT(16) | BIT(14) | BIT(12) | BIT(8) | BIT(7) | BIT(6) | + BIT(5) | BIT(1) | BIT(0), + .ro = MAKE_64BIT_MASK(0, 32), + }, + { .name = "GMAC_HW_FEATURE1", .addr = A_GMAC_HW_FEATURE1, + /* + * ADDR64(bits[15:14])=0 -> 32-bit descriptors. + * TXFIFOSIZE(bits[10:6])=5 -> 4KB. RXFIFOSIZE(bits[4:0])=5 -> 4KB. + */ + .reset = (0x5 << 6) | 0x5, + .ro = MAKE_64BIT_MASK(0, 32), + }, + { .name = "GMAC_HW_FEATURE2", .addr = A_GMAC_HW_FEATURE2, + /* + * All queue/channel count fields zero => 1 TX chan / 1 RX chan / + * 1 TX queue / 1 RX queue (driver adds 1). + */ + .reset = 0, + .ro = MAKE_64BIT_MASK(0, 32), + }, + { .name = "GMAC_HW_FEATURE3", .addr = A_GMAC_HW_FEATURE3, + .ro = MAKE_64BIT_MASK(0, 32), + }, + { .name = "GMAC_MDIO_ADDR", .addr = A_GMAC_MDIO_ADDR, + .post_write = gmac_mdio_addr_postw, + }, + { .name = "GMAC_MDIO_DATA", .addr = A_GMAC_MDIO_DATA, + }, + { .name = "GMAC_GPIO_STATUS", .addr = A_GMAC_GPIO_STATUS, + }, + { .name = "GMAC_ARP_ADDR", .addr = A_GMAC_ARP_ADDR, + }, + { .name = "GMAC_EXT_CFG1", .addr = A_GMAC_EXT_CFG1, + }, + { .name = "GMAC_ADDR_HIGH0", .addr = A_GMAC_ADDR_HIGH0, + .reset = 0x80000000, + .post_write = gmac_addr_high0_postw, + }, + { .name = "GMAC_ADDR_LOW0", .addr = A_GMAC_ADDR_LOW0, + .reset = 0xffffffff, + .post_write = gmac_addr_low0_postw, + }, +}; + +static const RegisterAccessInfo dma_regs_info[] = { + { .name = "DMA_BUS_MODE", .addr = A_DMA_BUS_MODE, + .post_write = dma_bus_mode_postw, + }, + { .name = "DMA_SYS_BUS_MODE", .addr = A_DMA_SYS_BUS_MODE, + }, + { .name = "DMA_STATUS", .addr = A_DMA_STATUS, + .ro = MAKE_64BIT_MASK(0, 32), + }, + { .name = "DMA_AXI_BUS_MODE", .addr = A_DMA_AXI_BUS_MODE, + }, + { .name = "DMA_TBS_CTRL", .addr = A_DMA_TBS_CTRL, + }, + +/* + * Per-channel DMA block: base 0x1100, stride 0x80. We model channels 0 + * and 1 explicitly. Each entry's .addr is the absolute offset within the + * DMA bank (DMA_CHAN_BLOCK_BASE + chan*stride + intra). + */ +#define CHAN_ADDR(chan, name) \ + (DMA_CHAN_BLOCK_BASE + (chan) * DWMAC4_DMA_CHAN_STRIDE + A_DMA_CHAN_##name) + + /* Channel 0 (primary) */ + { .name = "DMA_CHAN_CONTROL0", .addr = CHAN_ADDR(0, CONTROL), }, + { .name = "DMA_CHAN_TX_CONTROL0", .addr = CHAN_ADDR(0, TX_CONTROL), + .post_write = dma_chan_tx_control_postw, + }, + { .name = "DMA_CHAN_RX_CONTROL0", .addr = CHAN_ADDR(0, RX_CONTROL), + .post_write = dma_chan_rx_control_postw, + }, + { .name = "DMA_CHAN_TX_BASE_ADDR_HI0", .addr = CHAN_ADDR(0, TX_BASE_ADDR_HI), }, + { .name = "DMA_CHAN_TX_BASE_ADDR0", .addr = CHAN_ADDR(0, TX_BASE_ADDR), + .post_write = dma_chan_tx_base_postw, + }, + { .name = "DMA_CHAN_RX_BASE_ADDR_HI0", .addr = CHAN_ADDR(0, RX_BASE_ADDR_HI), }, + { .name = "DMA_CHAN_RX_BASE_ADDR0", .addr = CHAN_ADDR(0, RX_BASE_ADDR), + .post_write = dma_chan_rx_base_postw, + }, + { .name = "DMA_CHAN_TX_END_ADDR0", .addr = CHAN_ADDR(0, TX_END_ADDR), + .post_write = dma_chan_tx_tail_postw, + }, + { .name = "DMA_CHAN_RX_END_ADDR0", .addr = CHAN_ADDR(0, RX_END_ADDR), + .post_write = dma_chan_rx_tail_postw, + }, + { .name = "DMA_CHAN_TX_RING_LEN0", .addr = CHAN_ADDR(0, TX_RING_LEN), }, + { .name = "DMA_CHAN_RX_RING_LEN0", .addr = CHAN_ADDR(0, RX_RING_LEN), }, + { .name = "DMA_CHAN_INTR_ENA0", .addr = CHAN_ADDR(0, INTR_ENA), + .post_write = dma_chan_status_postw, + }, + { .name = "DMA_CHAN_RX_WATCHDOG0", .addr = CHAN_ADDR(0, RX_WATCHDOG), }, + { .name = "DMA_CHAN_SLOT_CTRL_STATUS0", .addr = CHAN_ADDR(0, SLOT_CTRL_STATUS), }, + { .name = "DMA_CHAN_CUR_TX_DESC0", .addr = CHAN_ADDR(0, CUR_TX_DESC), }, + { .name = "DMA_CHAN_CUR_RX_DESC0", .addr = CHAN_ADDR(0, CUR_RX_DESC), }, + { .name = "DMA_CHAN_CUR_TX_BUF_ADDR_HI0", .addr = CHAN_ADDR(0, CUR_TX_BUF_ADDR_HI), }, + { .name = "DMA_CHAN_CUR_TX_BUF_ADDR0", .addr = CHAN_ADDR(0, CUR_TX_BUF_ADDR), }, + { .name = "DMA_CHAN_CUR_RX_BUF_ADDR_HI0", .addr = CHAN_ADDR(0, CUR_RX_BUF_ADDR_HI), }, + { .name = "DMA_CHAN_CUR_RX_BUF_ADDR0", .addr = CHAN_ADDR(0, CUR_RX_BUF_ADDR), }, + { .name = "DMA_CHAN_STATUS0", .addr = CHAN_ADDR(0, STATUS), + .w1c = DMA_CHAN_STATUS_W1C_BITS, + .post_write = dma_chan_status_postw, + }, + + /* Channel 1 (mirror; same callbacks). */ + { .name = "DMA_CHAN_CONTROL1", .addr = CHAN_ADDR(1, CONTROL), }, + { .name = "DMA_CHAN_TX_CONTROL1", .addr = CHAN_ADDR(1, TX_CONTROL), + .post_write = dma_chan_tx_control_postw, + }, + { .name = "DMA_CHAN_RX_CONTROL1", .addr = CHAN_ADDR(1, RX_CONTROL), + .post_write = dma_chan_rx_control_postw, + }, + { .name = "DMA_CHAN_TX_BASE_ADDR_HI1", .addr = CHAN_ADDR(1, TX_BASE_ADDR_HI), }, + { .name = "DMA_CHAN_TX_BASE_ADDR1", .addr = CHAN_ADDR(1, TX_BASE_ADDR), + .post_write = dma_chan_tx_base_postw, + }, + { .name = "DMA_CHAN_RX_BASE_ADDR_HI1", .addr = CHAN_ADDR(1, RX_BASE_ADDR_HI), }, + { .name = "DMA_CHAN_RX_BASE_ADDR1", .addr = CHAN_ADDR(1, RX_BASE_ADDR), + .post_write = dma_chan_rx_base_postw, + }, + { .name = "DMA_CHAN_TX_END_ADDR1", .addr = CHAN_ADDR(1, TX_END_ADDR), + .post_write = dma_chan_tx_tail_postw, + }, + { .name = "DMA_CHAN_RX_END_ADDR1", .addr = CHAN_ADDR(1, RX_END_ADDR), + .post_write = dma_chan_rx_tail_postw, + }, + { .name = "DMA_CHAN_TX_RING_LEN1", .addr = CHAN_ADDR(1, TX_RING_LEN), }, + { .name = "DMA_CHAN_RX_RING_LEN1", .addr = CHAN_ADDR(1, RX_RING_LEN), }, + { .name = "DMA_CHAN_INTR_ENA1", .addr = CHAN_ADDR(1, INTR_ENA), + .post_write = dma_chan_status_postw, + }, + { .name = "DMA_CHAN_RX_WATCHDOG1", .addr = CHAN_ADDR(1, RX_WATCHDOG), }, + { .name = "DMA_CHAN_SLOT_CTRL_STATUS1", .addr = CHAN_ADDR(1, SLOT_CTRL_STATUS), }, + { .name = "DMA_CHAN_CUR_TX_DESC1", .addr = CHAN_ADDR(1, CUR_TX_DESC), }, + { .name = "DMA_CHAN_CUR_RX_DESC1", .addr = CHAN_ADDR(1, CUR_RX_DESC), }, + { .name = "DMA_CHAN_CUR_TX_BUF_ADDR_HI1", .addr = CHAN_ADDR(1, CUR_TX_BUF_ADDR_HI), }, + { .name = "DMA_CHAN_CUR_TX_BUF_ADDR1", .addr = CHAN_ADDR(1, CUR_TX_BUF_ADDR), }, + { .name = "DMA_CHAN_CUR_RX_BUF_ADDR_HI1", .addr = CHAN_ADDR(1, CUR_RX_BUF_ADDR_HI), }, + { .name = "DMA_CHAN_CUR_RX_BUF_ADDR1", .addr = CHAN_ADDR(1, CUR_RX_BUF_ADDR), }, + { .name = "DMA_CHAN_STATUS1", .addr = CHAN_ADDR(1, STATUS), + .w1c = DMA_CHAN_STATUS_W1C_BITS, + .post_write = dma_chan_status_postw, + }, +#undef CHAN_ADDR +}; + +/* ===================================================================== */ +/* Top-level MMIO dispatcher */ +/* ===================================================================== */ +/* + * Single 64 KiB I/O region. Reads/writes inside the MAC bank (0x0..0x3ff) + * and the DMA bank (0x1000..0x11ff) are dispatched to the registerinfo + * memory ops; everything else is RAZ/WI so that guest accesses to the + * many unmodeled windows (MTL @ 0xc00, PTP, MAC addr slots 1..127, + * L3/L4 filters @ 0x900+, vendor ranges) do NOT raise an AArch64 + * synchronous external abort (lesson D-15: qtest returns 0 silently but + * a real guest boot would otherwise panic). + */ + +static uint64_t dwmac4_read(void *opaque, hwaddr offset, unsigned size) +{ + DWMAC4State *s = DWMAC4(opaque); + + if (offset < DWMAC4_MAC_REG_SIZE) { + return register_read_memory(s->mac_reg_array, offset, size); + } + if (offset >= DWMAC4_DMA_REG_BASE && + offset < DWMAC4_DMA_REG_BASE + DWMAC4_DMA_REG_SIZE) { + return register_read_memory(s->dma_reg_array, + offset - DWMAC4_DMA_REG_BASE, size); + } + /* RAZ everywhere else - no abort. */ + return 0; +} + +static void dwmac4_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + DWMAC4State *s = DWMAC4(opaque); + + if (offset < DWMAC4_MAC_REG_SIZE) { + register_write_memory(s->mac_reg_array, offset, value, size); + return; + } + if (offset >= DWMAC4_DMA_REG_BASE && + offset < DWMAC4_DMA_REG_BASE + DWMAC4_DMA_REG_SIZE) { + register_write_memory(s->dma_reg_array, offset - DWMAC4_DMA_REG_BASE, + value, size); + return; + } + /* WI everywhere else - no abort. */ +} + +static const MemoryRegionOps dwmac4_ops = { + .read = dwmac4_read, + .write = dwmac4_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +/* ===================================================================== */ +/* PHY reset */ +/* ===================================================================== */ + +static const uint16_t phy_reg_init[32] = { + /* + * Minimal clause-22 PHY advertising 1000BASE-T full-duplex with link + * up. Driver reads BMSR for link/AN-complete and CTRL1000/STAT1000 for + * gigabit capability. + */ + [1] = 0x796d, /* MII_BMSR: LINK|ANEG|EXTCAP|... */ + [2] = 0x001c, /* PHYIDR1: Realtek-style C22 PHY */ + [3] = 0xc916, /* PHYIDR2 */ + [4] = 0x01e1, /* MII_ADVERTISE: 100FX+100TX+10TX FD/HD */ + [5] = 0x45e1, /* MII_LPA: partner capability */ + [9] = 0x0300, /* MII_CTRL1000: 1000FD adv */ + [10] = 0x0800, /* MII_STAT1000: 1000FD link */ + [15] = 0x0004, /* idle */ +}; + +/* ===================================================================== */ +/* Device lifecycle */ +/* ===================================================================== */ + +static void dwmac4_reset(DeviceState *dev) +{ + DWMAC4State *s = DWMAC4(dev); + + for (int i = 0; i < DWMAC4_MAC_NR_REGS; i++) { + register_reset(&s->mac_regs_info[i]); + } + for (int i = 0; i < DWMAC4_DMA_NR_REGS; i++) { + register_reset(&s->dma_regs_info[i]); + } + for (int chan = 0; chan < DWMAC4_NR_CHANNELS; chan++) { + s->tx_desc_cur[chan] = 0; + s->rx_desc_cur[chan] = 0; + } + memcpy(s->phy_regs, phy_reg_init, sizeof(s->phy_regs)); + s->phy_page = 0; + s->phy_regs[2] = s->phy_id1; + s->phy_regs[3] = s->phy_id2; + + s->mac_regs[R_GMAC4_VERSION] = + ((uint32_t)s->user_version << 8) | s->snps_version; + s->mac_regs[R_GMAC_HW_FEATURE1] = + ((s->dma_width == 40 ? 1 : s->dma_width == 48 ? 2 : 0) << 14) | + ((ctz32(s->tx_fifo_size) - 7) << 6) | + (ctz32(s->rx_fifo_size) - 7) | + (s->tso ? BIT(18) : 0); + + /* Reflect the configured MAC address into MAC_ADDR_HIGH0/LOW0. */ + s->mac_regs[R_GMAC_ADDR_HIGH0] = 0x80000000 | + ((s->conf.macaddr.a[0] << 8) | s->conf.macaddr.a[1]); + s->mac_regs[R_GMAC_ADDR_LOW0] = + (s->conf.macaddr.a[2] << 24) | + (s->conf.macaddr.a[3] << 16) | + (s->conf.macaddr.a[4] << 8) | + s->conf.macaddr.a[5]; +} + +static void dwmac4_realize(DeviceState *dev, Error **errp) +{ + DWMAC4State *s = DWMAC4(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + if (s->dma_width != 32 && s->dma_width != 40 && s->dma_width != 48) { + error_setg(errp, "dwmac4: dma-width must be 32, 40, or 48"); + return; + } + if (!is_power_of_2(s->tx_fifo_size) || s->tx_fifo_size < 128 || + s->tx_fifo_size > 4 * MiB) { + error_setg(errp, "dwmac4: tx-fifo-size must be a power of two " + "between 128 bytes and 4 MiB"); + return; + } + if (!is_power_of_2(s->rx_fifo_size) || s->rx_fifo_size < 128 || + s->rx_fifo_size > 4 * MiB) { + error_setg(errp, "dwmac4: rx-fifo-size must be a power of two " + "between 128 bytes and 4 MiB"); + return; + } + if (s->phy_addr > 31) { + error_setg(errp, "dwmac4: phy-addr must be in the range 0..31"); + return; + } + + /* + * Two register blocks, each giving us a MemoryRegion + RegisterInfo + * array. We keep references to the arrays so our top-level dispatcher + * can call register_{read,write}_memory on them. + */ + s->mac_reg_array = register_init_block32(dev, mac_regs_info, + ARRAY_SIZE(mac_regs_info), + s->mac_regs_info, s->mac_regs, + &dwmac4_ops, false, + DWMAC4_MAC_REG_SIZE); + s->dma_reg_array = register_init_block32(dev, dma_regs_info, + ARRAY_SIZE(dma_regs_info), + s->dma_regs_info, s->dma_regs, + &dwmac4_ops, false, + DWMAC4_DMA_REG_SIZE); + + memory_region_init_io(&s->iomem, OBJECT(s), &dwmac4_ops, s, TYPE_DWMAC4, + DWMAC4_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->sb_irq); + + qemu_macaddr_default_if_unset(&s->conf.macaddr); + + s->nic = qemu_new_nic(&net_dwmac4_info, &s->conf, TYPE_DWMAC4, dev->id, + &dev->mem_reentrancy_guard, s); + qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a); +} + +static void dwmac4_unrealize(DeviceState *dev) +{ + DWMAC4State *s = DWMAC4(dev); + qemu_del_nic(s->nic); +} + +static const VMStateDescription vmstate_dwmac4 = { + .name = TYPE_DWMAC4, + .version_id = 2, + .minimum_version_id = 2, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(mac_regs, DWMAC4State, DWMAC4_MAC_NR_REGS), + VMSTATE_UINT32_ARRAY(dma_regs, DWMAC4State, DWMAC4_DMA_NR_REGS), + VMSTATE_UINT64_ARRAY(tx_desc_cur, DWMAC4State, DWMAC4_NR_CHANNELS), + VMSTATE_UINT64_ARRAY(rx_desc_cur, DWMAC4State, DWMAC4_NR_CHANNELS), + VMSTATE_UINT16_ARRAY(phy_regs, DWMAC4State, 32), + VMSTATE_UINT16(phy_page, DWMAC4State), + VMSTATE_END_OF_LIST(), + }, +}; + +static const Property dwmac4_properties[] = { + DEFINE_NIC_PROPERTIES(DWMAC4State, conf), + DEFINE_PROP_UINT8("snps-version", DWMAC4State, snps_version, + DWMAC4_DEFAULT_SNPS_VERSION), + DEFINE_PROP_UINT8("user-version", DWMAC4State, user_version, 0), + DEFINE_PROP_UINT8("dma-width", DWMAC4State, dma_width, + DWMAC4_DEFAULT_DMA_WIDTH), + DEFINE_PROP_UINT8("phy-addr", DWMAC4State, phy_addr, 1), + DEFINE_PROP_UINT16("phy-id1", DWMAC4State, phy_id1, 0x001c), + DEFINE_PROP_UINT16("phy-id2", DWMAC4State, phy_id2, 0xc916), + DEFINE_PROP_UINT32("tx-fifo-size", DWMAC4State, tx_fifo_size, + DWMAC4_DEFAULT_FIFO_SIZE), + DEFINE_PROP_UINT32("rx-fifo-size", DWMAC4State, rx_fifo_size, + DWMAC4_DEFAULT_FIFO_SIZE), + DEFINE_PROP_BOOL("tso", DWMAC4State, tso, false), +}; + +static void dwmac4_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + set_bit(DEVICE_CATEGORY_NETWORK, dc->categories); + dc->desc = "Synopsys DesignWare MAC (GMAC4 / dwmac-4.20a)"; + dc->realize = dwmac4_realize; + dc->unrealize = dwmac4_unrealize; + device_class_set_legacy_reset(dc, dwmac4_reset); + dc->vmsd = &vmstate_dwmac4; + device_class_set_props(dc, dwmac4_properties); +} + +static const TypeInfo dwmac4_types[] = { + { + .name = TYPE_DWMAC4, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(DWMAC4State), + .class_init = dwmac4_class_init, + }, +}; +DEFINE_TYPES(dwmac4_types) diff --git a/hw/net/meson.build b/hw/net/meson.build index 84f142df222af..6e6732c33187f 100644 --- a/hw/net/meson.build +++ b/hw/net/meson.build @@ -36,8 +36,11 @@ system_ss.add(when: 'CONFIG_LASI_82596', if_true: files('lasi_i82596.c')) system_ss.add(when: 'CONFIG_I82596_COMMON', if_true: files('i82596.c')) system_ss.add(when: 'CONFIG_SUNHME', if_true: files('sunhme.c')) system_ss.add(when: 'CONFIG_FTGMAC100', if_true: files('ftgmac100.c')) +system_ss.add(when: 'CONFIG_PHYTIUM_XMAC', if_true: files('phytium-xmac.c')) system_ss.add(when: 'CONFIG_SUNGEM', if_true: files('sungem.c')) -system_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_emc.c', 'npcm_gmac.c')) +system_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_emc.c')) +system_ss.add(when: 'CONFIG_NPCM_GMAC', if_true: files('npcm_gmac.c')) +system_ss.add(when: 'CONFIG_DWMAC4', if_true: files('dwmac4.c')) system_ss.add(when: 'CONFIG_NPCM8XX', if_true: files('npcm_pcs.c')) system_ss.add(when: 'CONFIG_COLDFIRE', if_true: files('mcf_fec.c')) diff --git a/hw/net/phytium-xmac.c b/hw/net/phytium-xmac.c new file mode 100644 index 0000000000000..b74ac2fa54375 --- /dev/null +++ b/hw/net/phytium-xmac.c @@ -0,0 +1,267 @@ +/* + * Phytium XMAC Ethernet controller model + * + * This is a minimal model for firmware and Zephyr/ZVM link bring-up. It + * implements the FXMAC MDIO command register and enough MAC registers for the + * driver to configure the controller and observe a stable PHY link. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/net/phytium-xmac.h" +#include "migration/vmstate.h" +#include "qemu/bitops.h" + +#define PHYTIUM_XMAC_MMIO_SIZE 0x2000 +#define PHYTIUM_XMAC_NUM_REGS (PHYTIUM_XMAC_MMIO_SIZE / sizeof(uint32_t)) + +#define XMAC_NETWORK_STATUS 0x008 +#define XMAC_INTERRUPT_STATUS 0x024 +#define XMAC_PHY_MAINTENANCE 0x034 +#define XMAC_PCS_STATUS 0x214 +#define XMAC_DESIGN_CFG_DEBUG2 0x280 + +#define XMAC_MDIO_IDLE BIT(2) +#define XMAC_PCS_LINK_UP BIT(15) +#define XMAC_MDIO_OP_MASK 0xf0000000u +#define XMAC_MDIO_OP_WRITE 0x50000000u +#define XMAC_MDIO_OP_READ 0x60000000u +#define XMAC_MDIO_PHY(value) (((value) >> 23) & 0x1f) +#define XMAC_MDIO_REG(value) (((value) >> 18) & 0x1f) +#define XMAC_MDIO_DATA(value) ((value) & 0xffff) + +#define PHY_ADDR 0 +#define PHY_REG_BMCR 0 +#define PHY_REG_BMSR 1 +#define PHY_REG_PHYID1 2 +#define PHY_REG_PHYID2 3 +#define PHY_REG_ANAR 4 +#define PHY_REG_ANLPAR 5 +#define PHY_REG_1000_CTRL 9 +#define PHY_REG_1000_STATUS 10 +#define PHY_REG_SPEC_STATUS 17 +#define PHY_NUM_REGS 32 + +#define PHY_BMCR_AUTONEG_ENABLE BIT(12) +#define PHY_BMCR_FULL_DUPLEX BIT(8) +#define PHY_BMCR_SPEED1000 BIT(6) +#define PHY_BMCR_RESET BIT(15) + +#define PHY_BMSR_EXTENDED_CAP BIT(0) +#define PHY_BMSR_LINK_STATUS BIT(2) +#define PHY_BMSR_AUTONEG_ABILITY BIT(3) +#define PHY_BMSR_AUTONEG_COMPLETE BIT(5) +#define PHY_BMSR_PREAMBLE_SUPPRESS BIT(6) +#define PHY_BMSR_EXTENDED_STATUS BIT(8) +#define PHY_BMSR_100BASE_T2_HALF BIT(9) +#define PHY_BMSR_100BASE_T2_FULL BIT(10) +#define PHY_BMSR_10_HALF BIT(11) +#define PHY_BMSR_10_FULL BIT(12) +#define PHY_BMSR_100_HALF BIT(13) +#define PHY_BMSR_100_FULL BIT(14) + +struct PhytiumXmacState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + qemu_irq irq; + uint32_t regs[PHYTIUM_XMAC_NUM_REGS]; + uint16_t phy_regs[PHY_NUM_REGS]; + uint8_t phy_addr; +}; + +static uint16_t phytium_xmac_phy_read(PhytiumXmacState *s, uint8_t phy, + uint8_t reg) +{ + if (phy != s->phy_addr) { + return 0xffff; + } + + switch (reg) { + case PHY_REG_BMSR: + return s->phy_regs[reg] | PHY_BMSR_LINK_STATUS | + PHY_BMSR_AUTONEG_COMPLETE; + case PHY_REG_BMCR: + return s->phy_regs[reg] & ~PHY_BMCR_RESET; + default: + return s->phy_regs[reg]; + } +} + +static void phytium_xmac_phy_write(PhytiumXmacState *s, uint8_t phy, + uint8_t reg, uint16_t value) +{ + if (phy != s->phy_addr) { + return; + } + + if (reg == PHY_REG_BMCR) { + value &= ~PHY_BMCR_RESET; + } + s->phy_regs[reg] = value; +} + +static void phytium_xmac_mdio_write(PhytiumXmacState *s, uint32_t value) +{ + uint8_t phy = XMAC_MDIO_PHY(value); + uint8_t reg = XMAC_MDIO_REG(value); + uint16_t data = XMAC_MDIO_DATA(value); + + switch (value & XMAC_MDIO_OP_MASK) { + case XMAC_MDIO_OP_READ: + data = phytium_xmac_phy_read(s, phy, reg); + s->regs[XMAC_PHY_MAINTENANCE / sizeof(uint32_t)] = + (value & 0xffff0000u) | data; + break; + case XMAC_MDIO_OP_WRITE: + phytium_xmac_phy_write(s, phy, reg, data); + s->regs[XMAC_PHY_MAINTENANCE / sizeof(uint32_t)] = value; + break; + default: + s->regs[XMAC_PHY_MAINTENANCE / sizeof(uint32_t)] = value; + break; + } + + s->regs[XMAC_NETWORK_STATUS / sizeof(uint32_t)] |= XMAC_MDIO_IDLE; +} + +static uint64_t phytium_xmac_read(void *opaque, hwaddr offset, unsigned size) +{ + PhytiumXmacState *s = opaque; + uint32_t index = offset / sizeof(uint32_t); + + if (index >= ARRAY_SIZE(s->regs)) { + return 0; + } + + switch (offset) { + case XMAC_NETWORK_STATUS: + return s->regs[index] | XMAC_MDIO_IDLE; + case XMAC_PCS_STATUS: + return s->regs[index] | XMAC_PCS_LINK_UP; + case XMAC_PHY_MAINTENANCE: + return s->regs[index]; + default: + return s->regs[index]; + } +} + +static void phytium_xmac_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + PhytiumXmacState *s = opaque; + uint32_t index = offset / sizeof(uint32_t); + + if (index >= ARRAY_SIZE(s->regs)) { + return; + } + + switch (offset) { + case XMAC_INTERRUPT_STATUS: + s->regs[index] &= ~(uint32_t)value; + break; + case XMAC_PHY_MAINTENANCE: + phytium_xmac_mdio_write(s, value); + break; + default: + s->regs[index] = value; + break; + } +} + +static const MemoryRegionOps phytium_xmac_ops = { + .read = phytium_xmac_read, + .write = phytium_xmac_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void phytium_xmac_reset(DeviceState *dev) +{ + PhytiumXmacState *s = PHYTIUM_XMAC(dev); + + memset(s->regs, 0, sizeof(s->regs)); + memset(s->phy_regs, 0, sizeof(s->phy_regs)); + + s->phy_addr = PHY_ADDR; + s->regs[XMAC_NETWORK_STATUS / sizeof(uint32_t)] = XMAC_MDIO_IDLE; + s->regs[XMAC_PCS_STATUS / sizeof(uint32_t)] = XMAC_PCS_LINK_UP; + s->regs[XMAC_DESIGN_CFG_DEBUG2 / sizeof(uint32_t)] = 0; + + s->phy_regs[PHY_REG_BMCR] = PHY_BMCR_AUTONEG_ENABLE | + PHY_BMCR_SPEED1000 | + PHY_BMCR_FULL_DUPLEX; + s->phy_regs[PHY_REG_BMSR] = PHY_BMSR_EXTENDED_CAP | + PHY_BMSR_LINK_STATUS | + PHY_BMSR_AUTONEG_ABILITY | + PHY_BMSR_AUTONEG_COMPLETE | + PHY_BMSR_PREAMBLE_SUPPRESS | + PHY_BMSR_EXTENDED_STATUS | + PHY_BMSR_100BASE_T2_HALF | + PHY_BMSR_100BASE_T2_FULL | + PHY_BMSR_10_HALF | + PHY_BMSR_10_FULL | + PHY_BMSR_100_HALF | + PHY_BMSR_100_FULL; + s->phy_regs[PHY_REG_PHYID1] = 0x001c; + s->phy_regs[PHY_REG_PHYID2] = 0xc915; + s->phy_regs[PHY_REG_ANAR] = 0x01e1; + s->phy_regs[PHY_REG_ANLPAR] = 0x0de1; + s->phy_regs[PHY_REG_1000_CTRL] = 0x0300; + s->phy_regs[PHY_REG_1000_STATUS] = 0x7800; + s->phy_regs[PHY_REG_SPEC_STATUS] = 0xac00; +} + +static void phytium_xmac_init(Object *obj) +{ + PhytiumXmacState *s = PHYTIUM_XMAC(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + memory_region_init_io(&s->iomem, obj, &phytium_xmac_ops, s, + TYPE_PHYTIUM_XMAC, PHYTIUM_XMAC_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); +} + +static const VMStateDescription vmstate_phytium_xmac = { + .name = TYPE_PHYTIUM_XMAC, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, PhytiumXmacState, PHYTIUM_XMAC_NUM_REGS), + VMSTATE_UINT16_ARRAY(phy_regs, PhytiumXmacState, PHY_NUM_REGS), + VMSTATE_UINT8(phy_addr, PhytiumXmacState), + VMSTATE_END_OF_LIST() + }, +}; + +static void phytium_xmac_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, phytium_xmac_reset); + dc->vmsd = &vmstate_phytium_xmac; +} + +static const TypeInfo phytium_xmac_info = { + .name = TYPE_PHYTIUM_XMAC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PhytiumXmacState), + .instance_init = phytium_xmac_init, + .class_init = phytium_xmac_class_init, +}; + +static void phytium_xmac_register_types(void) +{ + type_register_static(&phytium_xmac_info); +} + +type_init(phytium_xmac_register_types) diff --git a/hw/pci-host/Kconfig b/hw/pci-host/Kconfig index 8cbb8304a3ff6..4bb66dd357f42 100644 --- a/hw/pci-host/Kconfig +++ b/hw/pci-host/Kconfig @@ -69,6 +69,10 @@ config PCI_EXPRESS_DESIGNWARE select PCI_EXPRESS select MSI_NONBROKEN +config ROCKCHIP_PCIE + bool + select PCI_EXPRESS_DESIGNWARE + config PCI_BONITO select PCI select UNIMP diff --git a/hw/pci-host/meson.build b/hw/pci-host/meson.build index 3217e7e9123a3..385fa9fe0e19e 100644 --- a/hw/pci-host/meson.build +++ b/hw/pci-host/meson.build @@ -4,6 +4,7 @@ pci_ss.add(when: 'CONFIG_PCI_BONITO', if_true: files('bonito.c')) pci_ss.add(when: 'CONFIG_GT64120', if_true: files('gt64120.c')) pci_ss.add(when: 'CONFIG_PCI_EXPRESS_ASPEED', if_true: files('aspeed_pcie.c')) pci_ss.add(when: 'CONFIG_PCI_EXPRESS_DESIGNWARE', if_true: files('designware.c')) +pci_ss.add(when: 'CONFIG_ROCKCHIP_PCIE', if_true: files('rockchip_pcie.c')) pci_ss.add(when: 'CONFIG_PCI_EXPRESS_GENERIC_BRIDGE', if_true: files('gpex.c')) pci_ss.add(when: ['CONFIG_PCI_EXPRESS_GENERIC_BRIDGE', 'CONFIG_ACPI'], if_true: files('gpex-acpi.c')) pci_ss.add(when: 'CONFIG_PCI_EXPRESS_Q35', if_true: files('q35.c')) diff --git a/hw/pci-host/rockchip_pcie.c b/hw/pci-host/rockchip_pcie.c new file mode 100644 index 0000000000000..9c07d23aa6e72 --- /dev/null +++ b/hw/pci-host/rockchip_pcie.c @@ -0,0 +1,151 @@ +/* + * Rockchip DesignWare PCIe host wrapper. + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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 + * 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. + * + * - 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 + * and designware.c maps viewport->cfg at the programmed base - no + * static alias is needed here. + * + * IRQ wiring: the parent designware host already exports 5 sysbus IRQs + * (0..3 = INTA..INTD, 4 = MSI). This subclass adds three more sysbus + * IRQs (5 = err, 6 = pmc, 7 = sys) for Rockchip-specific inert lines. The + * board maps 0..3 -> GIC SPI 260 (legacy), 4 -> 261 (msg/MSI), + * 5 -> 259 (err), 6 -> 262 (pmc), 7 -> 263 (sys). + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/bitops.h" +#include "qemu/log.h" +#include "qemu/module.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. + */ +#define ROCKCHIP_PCIE_LTSSM_LINK_UP 0x00030011u + +static uint64_t rockchip_pcie_apb_read(void *opaque, hwaddr offset, + unsigned size) +{ + switch (offset) { + case ROCKCHIP_PCIE_APB_LTSSM_STATUS: + return ROCKCHIP_PCIE_LTSSM_LINK_UP; + default: + /* + * All other PCIE_CLIENT_* regs are write-only from the driver's + * perspective; reads return 0 (reset value). The driver does + * not read them at probe, so any value is fine - 0 is the + * documented reset value for the load-bearing intr/mask regs. + */ + return 0; + } +} + +static void rockchip_pcie_apb_write(void *opaque, hwaddr offset, uint64_t val, + unsigned size) +{ + /* + * Discard silently. The RK driver uses HIWORD-MASK writes to set + * RC mode, LTSSM enable, LTSSM enhance, clkreq, and INTx masks; + * none of these have behavioral effect in the substitution model + * but the writes MUST land (D-15: AArch64 aborts unassigned + * writes). + */ +} + +static const MemoryRegionOps rockchip_pcie_apb_ops = { + .read = rockchip_pcie_apb_read, + .write = rockchip_pcie_apb_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .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); + + /* + * Realize the parent designware host first. This registers its + * 4 KiB DBI mmio at sysbus mmio[0] and its 5 sysbus IRQs + * (0..3 = INTA..INTD, 4 = MSI). + */ + rkpc->parent_realize(dev, errp); + if (*errp) { + return; + } + + /* + * Three Rockchip-specific inert IRQs (err/pmc/sys). legacy (INTA..INTD) and + * msg (MSI) come from the parent. The board wires all five logical + * IRQs to the GIC. + */ + sysbus_init_irq(sbd, &s->err_irq); /* index 5 */ + sysbus_init_irq(sbd, &s->pmc_irq); /* index 6 */ + sysbus_init_irq(sbd, &s->sys_irq); /* index 7 */ + + /* + * RK APB vendor register window (sysbus mmio[1]). LTSSM pinned, + * everything else RAZ/WI. + */ + memory_region_init_io(&s->apb, OBJECT(s), &rockchip_pcie_apb_ops, s, + "rockchip-pcie-apb", 0x10000); + sysbus_init_mmio(sbd, &s->apb); +} + +static void rockchip_pcie_host_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + RockchipPCIEHostClass *rkpc = ROCKCHIP_PCIE_HOST_CLASS(klass); + + device_class_set_parent_realize(dc, rockchip_pcie_host_realize, + &rkpc->parent_realize); + /* Not user-creatable; instantiated by the board. */ + dc->user_creatable = false; +} + +static const TypeInfo rockchip_pcie_host_info = { + .name = TYPE_ROCKCHIP_PCIE_HOST, + .parent = TYPE_DESIGNWARE_PCIE_HOST, + .instance_size = sizeof(RockchipPCIEHost), + .class_init = rockchip_pcie_host_class_init, +}; + +static void rockchip_pcie_register_types(void) +{ + type_register_static(&rockchip_pcie_host_info); +} + +type_init(rockchip_pcie_register_types) diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index de37c08caecee..10b981f11fa40 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -69,6 +69,35 @@ config RISCV_VIRT select ACPI select ACPI_PCI +config RISCV_SERVER_PLATFORM_REF + bool + default y + depends on RISCV64 + imply TPM_TIS_SYSBUS + select RISCV_NUMA + select GOLDFISH_RTC + select PCI + select PCI_EXPRESS_GENERIC_BRIDGE + select PFLASH_CFI01 + select SERIAL + select RISCV_ACLINT + select RISCV_APLIC + select RISCV_IMSIC + select RISCV_IOMMU + +config SPACEMIT_K3 + bool + default y + depends on RISCV64 + select CPU_CLUSTER + select DEVICE_TREE + select RISCV_ACLINT + select RISCV_APLIC + select RISCV_IMSIC + select SERIAL_MM + select SPACEMIT_K3_CTRL + select SPACEMIT_K3_SDHCI + config SHAKTI_C bool default y @@ -140,6 +169,7 @@ config XIANGSHAN_KUNMINGHU select RISCV_ACLINT select RISCV_APLIC select RISCV_IMSIC + select K230_CLINT select SERIAL_MM config MIPS_BOSTON_AIA @@ -160,5 +190,49 @@ config K230 select RISCV_APLIC select RISCV_IMSIC select SERIAL_MM + select SPLIT_IRQ select UNIMP + select K230_DISPLAY select K230_WDT + select K230_SDHCI + select K230_GSDMA + select K230_PDMA + select K230_UGZIP + select K230_HI_SYS_CFG + select K230_HARDLOCK + select K230_TSENSOR + select K230_GPIO + select K230_IOMUX + select K230_PMU + select K230_ISP + select K230_RX_CSI + select K230_REGS + select K230_I2S + select K230_KPU + select K230_NONAI_2D + select K230_DEWARP + select K230_I2C + select K230_OV5647 + select K230_ADC + select K230_PWM + select K230_TIMER + select K230_SYSCTL + select K230_RTC + select K230_SECURITY + select K230_SPI + select USB_DWC2 + select USB_RTL8152 + +config SOPHGO_CV1800B + bool + depends on RISCV64 + select RISCV_ACLINT + select SIFIVE_PLIC + select SOPHGO_CV1800B_CLK + select DW8250 + +config MILKV_DUO + bool + depends on RISCV64 + default y + select SOPHGO_CV1800B diff --git a/hw/riscv/cv1800b.c b/hw/riscv/cv1800b.c new file mode 100644 index 0000000000000..c6749e1202cde --- /dev/null +++ b/hw/riscv/cv1800b.c @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Sophgo CV1800B SoC + * + * Copyright (c) 2026 Kuan-Wei Chiu + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "hw/riscv/cv1800b.h" +#include "hw/core/qdev-properties.h" +#include "target/riscv/cpu-qom.h" +#include "system/system.h" +#include "hw/char/serial.h" +#include "hw/intc/riscv_aclint.h" +#include "system/address-spaces.h" +#include "hw/intc/sifive_plic.h" +#include "target/riscv/cpu.h" +#include "hw/riscv/boot.h" +#include "hw/sd/sdhci.h" +#include "hw/misc/unimp.h" + +const MemMapEntry cv1800b_memmap[] = { + [CV1800B_DEV_TOP_MISC] = { 0x03000000, 0x1000 }, + [CV1800B_DEV_PINMUX] = { 0x03001000, 0x1000 }, + [CV1800B_DEV_CLK] = { 0x03002000, 0x1000 }, + [CV1800B_DEV_RST] = { 0x03003000, 0x1000 }, + [CV1800B_DEV_WDT] = { 0x03010000, 0x1000 }, + [CV1800B_DEV_GPIO] = { 0x03020000, 0x4000 }, + [CV1800B_DEV_UART0] = { 0x04140000, 0x10000 }, + [CV1800B_DEV_SD0] = { 0x04310000, 0x10000 }, + [CV1800B_DEV_ROM] = { 0x04400000, 0x10000 }, + [CV1800B_DEV_RTC_GPIO] = { 0x05021000, 0x1000 }, + [CV1800B_DEV_RTC_IO] = { 0x05027000, 0x1000 }, + [CV1800B_DEV_PLIC] = { 0x70000000, 0x4000000 }, + [CV1800B_DEV_CLINT] = { 0x74000000, 0x10000 }, + [CV1800B_DEV_DRAM] = { 0x80000000, 0x0 }, +}; + +static void cv1800b_soc_instance_init(Object *obj) +{ + CV1800BSoCState *s = CV1800B_SOC(obj); + + object_initialize_child(obj, "cpus", &s->cpus, TYPE_RISCV_HART_ARRAY); + object_initialize_child(obj, "clk", &s->clk, TYPE_CV1800B_CLK); +} + +static void cv1800b_soc_realize(DeviceState *dev, Error **errp) +{ + CV1800BSoCState *s = CV1800B_SOC(dev); + MachineState *ms = MACHINE(qdev_get_machine()); + uint32_t num_harts = ms->smp.cpus; + MemoryRegion *system_memory = get_system_memory(); + char *plic_hart_config; + DeviceState *uart, *sdhci; + + qdev_prop_set_uint32(DEVICE(&s->cpus), "num-harts", num_harts); + qdev_prop_set_uint32(DEVICE(&s->cpus), "hartid-base", 0); + qdev_prop_set_string(DEVICE(&s->cpus), "cpu-type", TYPE_RISCV_CPU_THEAD_C906); + + qdev_prop_set_uint64(DEVICE(&s->cpus), "resetvec", + cv1800b_memmap[CV1800B_DEV_ROM].base); + + sysbus_realize(SYS_BUS_DEVICE(&s->cpus), &error_fatal); + + memory_region_init_rom(&s->rom, OBJECT(dev), "cv1800b.rom", + cv1800b_memmap[CV1800B_DEV_ROM].size, &error_fatal); + memory_region_add_subregion(system_memory, + cv1800b_memmap[CV1800B_DEV_ROM].base, &s->rom); + + riscv_aclint_swi_create(cv1800b_memmap[CV1800B_DEV_CLINT].base, + 0, num_harts, false); + riscv_aclint_mtimer_create(cv1800b_memmap[CV1800B_DEV_CLINT].base + + RISCV_ACLINT_SWI_SIZE, + RISCV_ACLINT_DEFAULT_MTIMER_SIZE, + 0, num_harts, RISCV_ACLINT_DEFAULT_MTIMECMP, + RISCV_ACLINT_DEFAULT_MTIME, + RISCV_ACLINT_DEFAULT_TIMEBASE_FREQ, true); + + plic_hart_config = riscv_plic_hart_config_string(num_harts); + s->plic = sifive_plic_create( + cv1800b_memmap[CV1800B_DEV_PLIC].base, + plic_hart_config, + num_harts, + 0, + CV1800B_PLIC_NUM_SOURCES, + CV1800B_PLIC_NUM_PRIORITIES, + 0x0, + 0x1000, + 0x2000, + 0x80, + 0x200000, + 0x1000, + cv1800b_memmap[CV1800B_DEV_PLIC].size); + + g_free(plic_hart_config); + + uart = qdev_new("dw8250"); + qdev_prop_set_uint8(uart, "regshift", 2); + qdev_prop_set_chr(uart, "chardev", serial_hd(0)); + sysbus_realize(SYS_BUS_DEVICE(uart), errp); + sysbus_mmio_map(SYS_BUS_DEVICE(uart), 0, cv1800b_memmap[CV1800B_DEV_UART0].base); + sysbus_connect_irq(SYS_BUS_DEVICE(uart), 0, + qdev_get_gpio_in(DEVICE(s->plic), CV1800B_UART0_IRQ)); + + sdhci = qdev_new(TYPE_SYSBUS_SDHCI); + qdev_prop_set_uint8(sdhci, "sd-spec-version", 3); + qdev_prop_set_uint64(sdhci, "capareg", 0x056900b9); + sysbus_realize(SYS_BUS_DEVICE(sdhci), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(sdhci), 0, cv1800b_memmap[CV1800B_DEV_SD0].base); + sysbus_connect_irq(SYS_BUS_DEVICE(sdhci), 0, + qdev_get_gpio_in(DEVICE(s->plic), CV1800B_SD0_IRQ)); + + sysbus_realize(SYS_BUS_DEVICE(&s->clk), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->clk), 0, + cv1800b_memmap[CV1800B_DEV_CLK].base); + + create_unimplemented_device("cv1800b.top_misc", + cv1800b_memmap[CV1800B_DEV_TOP_MISC].base, + cv1800b_memmap[CV1800B_DEV_TOP_MISC].size); + + create_unimplemented_device("cv1800b.pinmux", + cv1800b_memmap[CV1800B_DEV_PINMUX].base, + cv1800b_memmap[CV1800B_DEV_PINMUX].size); + + create_unimplemented_device("cv1800b.rst", + cv1800b_memmap[CV1800B_DEV_RST].base, + cv1800b_memmap[CV1800B_DEV_RST].size); + + create_unimplemented_device("cv1800b.wdt", + cv1800b_memmap[CV1800B_DEV_WDT].base, + cv1800b_memmap[CV1800B_DEV_WDT].size); + + create_unimplemented_device("cv1800b.gpio0_3", + cv1800b_memmap[CV1800B_DEV_GPIO].base, + cv1800b_memmap[CV1800B_DEV_GPIO].size); + + create_unimplemented_device("cv1800b.rtc_gpio", + cv1800b_memmap[CV1800B_DEV_RTC_GPIO].base, + cv1800b_memmap[CV1800B_DEV_RTC_GPIO].size); + + create_unimplemented_device("cv1800b.rtc_io", + cv1800b_memmap[CV1800B_DEV_RTC_IO].base, + cv1800b_memmap[CV1800B_DEV_RTC_IO].size); +} + +static void cv1800b_soc_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = cv1800b_soc_realize; + dc->user_creatable = false; +} + +static const TypeInfo cv1800b_soc_type_info = { + .name = TYPE_CV1800B_SOC, + .parent = TYPE_DEVICE, + .instance_size = sizeof(CV1800BSoCState), + .instance_init = cv1800b_soc_instance_init, + .class_init = cv1800b_soc_class_init, +}; + +static void cv1800b_soc_register_types(void) +{ + type_register_static(&cv1800b_soc_type_info); +} + +type_init(cv1800b_soc_register_types) diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c index 656f28190cd02..059b6e00bc2e6 100644 --- a/hw/riscv/k230.c +++ b/hw/riscv/k230.c @@ -1,7 +1,7 @@ /* * QEMU RISC-V Virt Board Compatible with Kendryte K230 SDK * - * Copyright (c) 2025 Chao Liu + * Copyright (c) 2026 Process Mission * * SPDX-License-Identifier: GPL-2.0-or-later * @@ -15,28 +15,51 @@ #include "qemu/osdep.h" #include "cpu-qom.h" +#include "qemu/bitops.h" #include "qemu/cutils.h" #include "qemu/error-report.h" #include "qapi/error.h" +#include "hw/core/qdev-properties.h" #include "system/device_tree.h" #include "system/system.h" #include "system/memory.h" +#include "system/block-backend.h" +#include "system/blockdev.h" #include "target/riscv/cpu.h" +#include "hw/core/irq.h" #include "hw/core/loader.h" #include "hw/core/sysbus.h" +#include "hw/i2c/k230_ov5647.h" #include "hw/riscv/k230.h" #include "hw/riscv/boot.h" #include "hw/riscv/machines-qom.h" +#include "hw/intc/k230_clint.h" #include "hw/intc/riscv_aclint.h" #include "hw/intc/sifive_plic.h" #include "hw/char/serial-mm.h" #include "hw/misc/unimp.h" +#include "hw/sd/sd.h" +#include "hw/usb/usb.h" +#include "net/net.h" /* Align K230_SDK k230_canmv_defconfig */ #define K230_DIRECT_OPENSBI_ADDR 0x8000000 #define K230_DIRECT_KERNEL_ADDR 0x8200000 #define K230_DIRECT_DTB_ADDR 0xa000000 +#define K230_NOC_QOS_BASE 0x91302000 +#define K230_NOC_QOS_SIZE 0x1000 + +#define K230_FIRMWARE_OPENSBI_ADDR 0x00200000 +#define K230_CLINT_SMODE_OFFSET 0x0000c000 +#define K230_TIMEBASE_FREQ 27000000 +#define K230_STC_COUNTER_OFFSET 0x40 +#define K230_STC_COUNTER_SIZE 0x30 +#define K230_AI2D_CALC_ENABLE 0x80 +#define K230_AI2D_JOB_OFFSET 0x8c +#define K230_AI2D_CLEAR_OFFSET 0xa0 +#define K230_AI2D_LEGACY_START 0xc0 + static const MemMapEntry memmap[] = { [K230_DEV_DDRC] = { 0x00000000, 0x80000000 }, [K230_DEV_KPU_L2_CACHE] = { 0x80000000, 0x00200000 }, @@ -50,7 +73,7 @@ static const MemMapEntry memmap[] = { [K230_DEV_NON_AI_2D] = { 0x8080C000, 0x00004000 }, [K230_DEV_ISP] = { 0x90000000, 0x00008000 }, [K230_DEV_DEWARP] = { 0x90008000, 0x00001000 }, - [K230_DEV_RX_CSI] = { 0x90009000, 0x00002000 }, + [K230_DEV_RX_CSI] = { 0x90009000, 0x00010000 }, [K230_DEV_H264] = { 0x90400000, 0x00010000 }, [K230_DEV_2P5D] = { 0x90800000, 0x00040000 }, [K230_DEV_VO] = { 0x90840000, 0x00010000 }, @@ -108,8 +131,86 @@ static void k230_soc_init(Object *obj) RISCVHartArrayState *cpu0 = &s->c908_cpu; object_initialize_child(obj, "c908-cpu", cpu0, TYPE_RISCV_HART_ARRAY); + memory_region_init(&s->c908v_mem, obj, "k230.c908v-memory", UINT64_MAX); + memory_region_init_alias(&s->c908v_sysmem, obj, "k230.c908v-system", + get_system_memory(), 0, UINT64_MAX); + memory_region_add_subregion(&s->c908v_mem, 0, &s->c908v_sysmem); object_initialize_child(obj, "k230-wdt0", &s->wdt[0], TYPE_K230_WDT); object_initialize_child(obj, "k230-wdt1", &s->wdt[1], TYPE_K230_WDT); + object_initialize_child(obj, "k230-sdhci0", &s->sdhci[0], + TYPE_K230_SDHCI); + object_initialize_child(obj, "k230-sdhci1", &s->sdhci[1], + TYPE_K230_SDHCI); + object_initialize_child(obj, "k230-gsdma", &s->gsdma, TYPE_K230_GSDMA); + object_initialize_child(obj, "k230-pdma", &s->pdma, TYPE_K230_PDMA); + object_initialize_child(obj, "k230-ugzip", &s->ugzip, TYPE_K230_UGZIP); + object_initialize_child(obj, "k230-hi-sys-cfg", &s->hi_sys_cfg, + TYPE_K230_HI_SYS_CFG); + object_initialize_child(obj, "k230-hardlock", &s->hardlock, + TYPE_K230_HARDLOCK); + object_initialize_child(obj, "k230-tsensor", &s->tsensor, + TYPE_K230_TSENSOR); + object_initialize_child(obj, "k230-gpio0", &s->gpio[0], TYPE_K230_GPIO); + object_initialize_child(obj, "k230-gpio1", &s->gpio[1], TYPE_K230_GPIO); + object_initialize_child(obj, "k230-iomux", &s->iomux, TYPE_K230_IOMUX); + for (int i = 0; i < K230_UART_COUNT; i++) { + g_autofree char *name = g_strdup_printf("k230-uart-ext%d", i); + + object_initialize_child(obj, name, &s->uart_ext[i], TYPE_K230_REGS); + } + for (int i = 0; i < K230_I2C_COUNT; i++) { + g_autofree char *name = g_strdup_printf("k230-i2c%d", i); + + object_initialize_child(obj, name, &s->i2c[i], TYPE_K230_I2C); + } + object_initialize_child(obj, "k230-adc", &s->adc, TYPE_K230_ADC); + object_initialize_child(obj, "k230-pwm", &s->pwm, TYPE_K230_PWM); + object_initialize_child(obj, "k230-timer", &s->timer, TYPE_K230_TIMER); + object_initialize_child(obj, "k230-sysctl-boot", &s->sysctl_boot, + TYPE_K230_SYSCTL_BOOT); + object_initialize_child(obj, "k230-sysctl-power", &s->sysctl_power, + TYPE_K230_SYSCTL_POWER); + object_initialize_child(obj, "k230-sysctl-reset", &s->sysctl_reset, + TYPE_K230_SYSCTL_RESET); + object_initialize_child(obj, "k230-pmu", &s->pmu, TYPE_K230_PMU); + object_initialize_child(obj, "k230-rtc", &s->rtc, TYPE_K230_RTC); + object_initialize_child(obj, "k230-security", &s->security, + TYPE_K230_SECURITY); + object_initialize_child(obj, "k230-vo", &s->vo, TYPE_K230_VO); + object_initialize_child(obj, "k230-dsi", &s->dsi, TYPE_K230_DSI); + for (int i = 0; i < K230_SPI_COUNT; i++) { + g_autofree char *name = g_strdup_printf("k230-spi%d", i); + + object_initialize_child(obj, name, &s->spi[i], TYPE_K230_SPI); + } + object_initialize_child(obj, "k230-i2s", &s->i2s, TYPE_K230_I2S); + for (int i = 0; i < K230_REGS_COUNT; i++) { + g_autofree char *name = g_strdup_printf("k230-regs%d", i); + + object_initialize_child(obj, name, &s->regs[i], TYPE_K230_REGS); + } + object_initialize_child(obj, "k230-kpu", &s->kpu, TYPE_K230_KPU); + object_initialize_child(obj, "k230-nonai-2d", &s->nonai_2d, + TYPE_K230_NONAI_2D); + object_initialize_child(obj, "k230-isp", &s->isp, TYPE_K230_ISP); + object_initialize_child(obj, "k230-dewarp", &s->dewarp, + TYPE_K230_DEWARP); + object_initialize_child(obj, "k230-rx-csi", &s->rx_csi, + TYPE_K230_RX_CSI); + for (int i = 0; i < K230_PLIC_NUM_SOURCES; i++) { + g_autofree char *name = g_strdup_printf("k230-plic-irq-splitter%d", + i); + + object_initialize_child(obj, name, &s->plic_irq_splitter[i], + TYPE_SPLIT_IRQ); + } + object_initialize_child(obj, "k230-usb0", &s->usb[0], TYPE_DWC2_USB); + object_initialize_child(obj, "k230-usb1", &s->usb[1], TYPE_DWC2_USB); + object_property_add_const_link(OBJECT(&s->usb[0]), "dma-mr", + OBJECT(get_system_memory())); + object_property_add_const_link(OBJECT(&s->usb[1]), "dma-mr", + OBJECT(get_system_memory())); + s->ugzip.gsdma = &s->gsdma; qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0); qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C908); @@ -117,7 +218,32 @@ static void k230_soc_init(Object *obj) memmap[K230_DEV_BOOTROM].base); } -static DeviceState *k230_create_plic(int base_hartid, int hartid_count) +static void k230_soc_init_c908v_cpu(K230SoCState *s, Object *obj) +{ + RISCVHartArrayState *cpu1 = &s->c908v_cpu; + + object_initialize_child(obj, "c908v-cpu", cpu1, TYPE_RISCV_HART_ARRAY); + qdev_prop_set_uint32(DEVICE(cpu1), "hartid-base", C908V_CPU_HARTID); + qdev_prop_set_string(DEVICE(cpu1), "cpu-type", + TYPE_RISCV_CPU_THEAD_C908V); + qdev_prop_set_uint64(DEVICE(cpu1), "resetvec", + memmap[K230_DEV_BOOTROM].base); + object_property_set_link(OBJECT(cpu1), "memory", OBJECT(&s->c908v_mem), + &error_abort); + qdev_prop_set_bit(DEVICE(cpu1), "start-powered-off", true); +} + +static RISCVHartArrayState *k230_boot_harts(K230MachineState *s) +{ + if (s->boot_both_cores) { + return &s->soc.c908_cpu; + } + + return s->soc.c908v_enabled ? &s->soc.c908v_cpu : &s->soc.c908_cpu; +} + +static DeviceState *k230_create_plic_in(MemoryRegion *mem, int base_hartid, + int cpu_index_base, int hartid_count) { g_autofree char *plic_hart_config = NULL; @@ -125,41 +251,279 @@ static DeviceState *k230_create_plic(int base_hartid, int hartid_count) plic_hart_config = riscv_plic_hart_config_string(hartid_count); /* Per-socket PLIC */ - return sifive_plic_create(memmap[K230_DEV_PLIC].base, - plic_hart_config, hartid_count, base_hartid, - K230_PLIC_NUM_SOURCES, - K230_PLIC_NUM_PRIORITIES, - K230_PLIC_PRIORITY_BASE, K230_PLIC_PENDING_BASE, - K230_PLIC_ENABLE_BASE, K230_PLIC_ENABLE_STRIDE, - K230_PLIC_CONTEXT_BASE, - K230_PLIC_CONTEXT_STRIDE, - memmap[K230_DEV_PLIC].size); + return sifive_plic_create_in(mem, memmap[K230_DEV_PLIC].base, + plic_hart_config, hartid_count, base_hartid, + cpu_index_base, K230_PLIC_NUM_SOURCES, + K230_PLIC_NUM_PRIORITIES, + K230_PLIC_PRIORITY_BASE, + K230_PLIC_PENDING_BASE, + K230_PLIC_ENABLE_BASE, + K230_PLIC_ENABLE_STRIDE, + K230_PLIC_CONTEXT_BASE, + K230_PLIC_CONTEXT_STRIDE, + memmap[K230_DEV_PLIC].size); +} + +static DeviceState *k230_create_plic(int base_hartid, int hartid_count) +{ + return k230_create_plic_in(get_system_memory(), base_hartid, base_hartid, + hartid_count); } -static void k230_create_uart(MemoryRegion *sys_mem, DeviceState *plic, - int index) +static qemu_irq k230_plic_irq(K230SoCState *s, int irq) +{ + return qdev_get_gpio_in(DEVICE(&s->plic_irq_splitter[irq]), 0); +} + +static void k230_create_plic_irq_splitters(K230SoCState *s) +{ + uint16_t num_lines = s->c908v_enabled ? 2 : 1; + + for (int i = 0; i < K230_PLIC_NUM_SOURCES; i++) { + DeviceState *splitter = DEVICE(&s->plic_irq_splitter[i]); + + qdev_prop_set_uint16(splitter, "num-lines", num_lines); + qdev_realize(splitter, NULL, &error_fatal); + qdev_connect_gpio_out(splitter, 0, + qdev_get_gpio_in(DEVICE(s->c908_plic), i)); + if (s->c908v_enabled) { + qdev_connect_gpio_out(splitter, 1, + qdev_get_gpio_in(DEVICE(s->c908v_plic), i)); + } + } +} + +static void k230_create_uart(MemoryRegion *sys_mem, K230SoCState *s, int index) { int uart_dev = K230_DEV_UART0 + index; - g_autofree char *name = g_strdup_printf("uart%d", index); + SysBusDevice *uart_ext = SYS_BUS_DEVICE(&s->uart_ext[index]); /* Cover the non-16550 part of the SDK's 0x1000 UART window. */ - create_unimplemented_device(name, memmap[uart_dev].base, - memmap[uart_dev].size); + qdev_prop_set_uint64(DEVICE(&s->uart_ext[index]), "size", + memmap[uart_dev].size - 0x20); + sysbus_realize(uart_ext, &error_fatal); + sysbus_mmio_map(uart_ext, 0, memmap[uart_dev].base + 0x20); serial_mm_init(sys_mem, memmap[uart_dev].base, 2, - qdev_get_gpio_in(plic, K230_UART0_IRQ + index), - 399193, serial_hd(index), DEVICE_LITTLE_ENDIAN); + k230_plic_irq(s, K230_UART0_IRQ + index), 399193, + serial_hd(index), DEVICE_LITTLE_ENDIAN); +} + +static void k230_create_sdhci(K230SoCState *s, int index, int irq) +{ + int sd_dev = K230_DEV_SD0 + index; + SysBusDevice *sbd = SYS_BUS_DEVICE(&s->sdhci[index]); + + sysbus_realize(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, memmap[sd_dev].base); + sysbus_connect_irq(sbd, 0, k230_plic_irq(s, irq)); +} + +static void k230_create_usb(K230SoCState *s, int index, int irq) +{ + int usb_dev = K230_DEV_USB0 + index; + SysBusDevice *sbd = SYS_BUS_DEVICE(&s->usb[index]); + + sysbus_realize(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, memmap[usb_dev].base); + sysbus_connect_irq(sbd, 0, k230_plic_irq(s, irq)); +} + +static void k230_create_usb_nic(K230SoCState *s) +{ + DeviceState *dev = qemu_create_nic_device("usb-rtl8152", true, + "r8152_eth"); + + if (!dev) { + return; + } + + qdev_prop_set_string(dev, "port", "1"); + usb_realize_and_unref(USB_DEVICE(dev), &s->usb[1].bus, &error_fatal); +} + +static void k230_create_i2c(K230SoCState *s, int index) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(&s->i2c[index]); + int i2c_dev = K230_DEV_I2C0 + index; + + sysbus_realize(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, memmap[i2c_dev].base); + sysbus_connect_irq(sbd, 0, k230_plic_irq(s, K230_I2C0_IRQ + index)); +} + +static void k230_create_camera_i2c_devices(K230SoCState *s) +{ + static const int ov5647_bus_ids[] = { 0, 1, 3, 4 }; + + for (int i = 0; i < ARRAY_SIZE(ov5647_bus_ids); i++) { + int index = ov5647_bus_ids[i]; + + if (index < K230_I2C_COUNT) { + i2c_slave_create_simple(s->i2c[index].bus, TYPE_K230_OV5647, 0x36); + } + } +} + +static bool k230_create_spi(K230SoCState *s, int index, + int spi_dev, int irq_base, Error **errp) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(&s->spi[index]); + + if (!sysbus_realize(sbd, errp)) { + return false; + } + + sysbus_mmio_map(sbd, 0, memmap[spi_dev].base); + for (int i = 0; i < K230_SPI_IRQ_COUNT; i++) { + sysbus_connect_irq(sbd, i, k230_plic_irq(s, irq_base + i)); + } + + return true; +} + +static bool k230_create_regs(K230SoCState *s, int index, + hwaddr base, hwaddr size, Error **errp) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(&s->regs[index]); + + qdev_prop_set_uint64(DEVICE(&s->regs[index]), "size", size); + if (!sysbus_realize(sbd, errp)) { + return false; + } + + sysbus_mmio_map(sbd, 0, base); + return true; +} + +static bool k230_create_irq_regs(K230SoCState *s, int index, + hwaddr base, hwaddr size, int irq, + hwaddr start_offset, hwaddr start2_offset, + hwaddr start3_offset, hwaddr clear_offset, + hwaddr status_offset, + uint64_t status_value, + uint64_t delay_ns, + hwaddr command_start_offset, + hwaddr command_end_offset, + hwaddr command_hi_offset, + bool irq_clear_clears_status, + bool irq_on_any_write, Error **errp) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(&s->regs[index]); + + qdev_prop_set_uint64(DEVICE(&s->regs[index]), "size", size); + qdev_prop_set_uint64(DEVICE(&s->regs[index]), "irq-start-offset", + start_offset); + qdev_prop_set_uint64(DEVICE(&s->regs[index]), "irq-start2-offset", + start2_offset); + qdev_prop_set_uint64(DEVICE(&s->regs[index]), "irq-start3-offset", + start3_offset); + qdev_prop_set_uint64(DEVICE(&s->regs[index]), "irq-clear-offset", + clear_offset); + qdev_prop_set_uint64(DEVICE(&s->regs[index]), "irq-status-offset", + status_offset); + qdev_prop_set_uint64(DEVICE(&s->regs[index]), "irq-status-size", + sizeof(status_value)); + qdev_prop_set_uint64(DEVICE(&s->regs[index]), "irq-status-value", + status_value); + qdev_prop_set_uint64(DEVICE(&s->regs[index]), "irq-delay-ns", delay_ns); + qdev_prop_set_uint64(DEVICE(&s->regs[index]), + "irq-command-start-offset", command_start_offset); + qdev_prop_set_uint64(DEVICE(&s->regs[index]), "irq-command-end-offset", + command_end_offset); + qdev_prop_set_uint64(DEVICE(&s->regs[index]), "irq-command-hi-offset", + command_hi_offset); + qdev_prop_set_bit(DEVICE(&s->regs[index]), "irq-clear-clears-status", + irq_clear_clears_status); + qdev_prop_set_bit(DEVICE(&s->regs[index]), "irq-on-any-write", + irq_on_any_write); + if (!sysbus_realize(sbd, errp)) { + return false; + } + + sysbus_mmio_map(sbd, 0, base); + sysbus_connect_irq(sbd, 0, k230_plic_irq(s, irq)); + return true; +} + +static bool k230_create_stc(K230SoCState *s, hwaddr base, hwaddr size, + Error **errp) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(&s->regs[K230_REGS_STC]); + + qdev_prop_set_uint64(DEVICE(&s->regs[K230_REGS_STC]), "size", size); + qdev_prop_set_uint64(DEVICE(&s->regs[K230_REGS_STC]), "counter-offset", + K230_STC_COUNTER_OFFSET); + qdev_prop_set_uint64(DEVICE(&s->regs[K230_REGS_STC]), "counter-size", + K230_STC_COUNTER_SIZE); + qdev_prop_set_uint64(DEVICE(&s->regs[K230_REGS_STC]), + "counter-frequency", K230_TIMEBASE_FREQ); + if (!sysbus_realize(sbd, errp)) { + return false; + } + + sysbus_mmio_map(sbd, 0, base); + return true; +} + +static void k230_create_flash_xip(K230SoCState *s, DeviceState *dev, + MemoryRegion *sys_mem) +{ + hwaddr size = memmap[K230_DEV_FLASH].size; + K230SpiState *spi = &s->spi[K230_SPI_SPI0]; + uint8_t *storage; + + /* + * The RT-Smart fastboot image uses the XIP window as a mutable flash + * staging area during early startup. Back it with RAM so direct stores + * complete while keeping erased flash contents at 0xff by default. + */ + memory_region_init_ram(&s->flash_xip, OBJECT(dev), "k230.flash-xip", + size, &error_fatal); + storage = memory_region_get_ram_ptr(&s->flash_xip); + memset(storage, 0xff, size); + + if (spi->blk) { + int64_t length = blk_getlength(spi->blk); + + if (length > 0) { + int64_t read_len = MIN(length, (int64_t)size); + + if (blk_pread(spi->blk, 0, read_len, storage, 0) < 0) { + error_report("failed to read K230 SPI flash image"); + } + } + } + + memory_region_add_subregion(sys_mem, memmap[K230_DEV_FLASH].base, + &s->flash_xip); } static void k230_soc_realize(DeviceState *dev, Error **errp) { K230SoCState *s = RISCV_K230_SOC(dev); + MachineState *machine = MACHINE(qdev_get_machine()); + K230MachineState *k230_machine = RISCV_K230_MACHINE(machine); MemoryRegion *sys_mem = get_system_memory(); + static const int sd_irqs[] = { K230_SD0_IRQ, K230_SD1_IRQ }; + static const int usb_irqs[] = { K230_USB0_IRQ, K230_USB1_IRQ }; int c908_cpus; + int c908v_cpus = 0; + + s->c908v_enabled = machine->smp.cpus > 1; + qdev_prop_set_bit(DEVICE(&s->c908_cpu), "start-powered-off", + s->c908v_enabled && !k230_machine->boot_both_cores); sysbus_realize(SYS_BUS_DEVICE(&s->c908_cpu), &error_fatal); c908_cpus = s->c908_cpu.num_harts; + if (s->c908v_enabled) { + k230_soc_init_c908v_cpu(s, OBJECT(dev)); + qdev_prop_set_bit(DEVICE(&s->c908v_cpu), "start-powered-off", + false); + sysbus_realize(SYS_BUS_DEVICE(&s->c908v_cpu), &error_fatal); + c908v_cpus = s->c908v_cpu.num_harts; + } /* SRAM */ memory_region_init_ram(&s->sram, OBJECT(dev), "sram", @@ -167,6 +531,12 @@ static void k230_soc_realize(DeviceState *dev, Error **errp) memory_region_add_subregion(sys_mem, memmap[K230_DEV_SRAM].base, &s->sram); + /* KPU L2 cache is directly addressable by the SDK AI runtime. */ + memory_region_init_ram(&s->kpu_l2_cache, OBJECT(dev), "k230.kpu-l2-cache", + memmap[K230_DEV_KPU_L2_CACHE].size, &error_fatal); + memory_region_add_subregion(sys_mem, memmap[K230_DEV_KPU_L2_CACHE].base, + &s->kpu_l2_cache); + /* BootROM */ memory_region_init_rom(&s->bootrom, OBJECT(dev), "bootrom", memmap[K230_DEV_BOOTROM].size, &error_fatal); @@ -175,6 +545,12 @@ static void k230_soc_realize(DeviceState *dev, Error **errp) /* PLIC */ s->c908_plic = k230_create_plic(C908_CPU_HARTID, c908_cpus); + if (s->c908v_enabled) { + s->c908v_plic = k230_create_plic_in(&s->c908v_mem, + C908V_CPU_HARTID, + C908V_CPU_INDEX, c908v_cpus); + } + k230_create_plic_irq_splitters(s); /* CLINT */ riscv_aclint_swi_create(memmap[K230_DEV_CLINT].base, @@ -184,11 +560,34 @@ static void k230_soc_realize(DeviceState *dev, Error **errp) C908_CPU_HARTID, c908_cpus, RISCV_ACLINT_DEFAULT_MTIMECMP, RISCV_ACLINT_DEFAULT_MTIME, - RISCV_ACLINT_DEFAULT_TIMEBASE_FREQ, true); + K230_TIMEBASE_FREQ, true); + k230_clint_smode_create_in(sys_mem, + memmap[K230_DEV_CLINT].base + + K230_CLINT_SMODE_OFFSET, + C908_CPU_HARTID, C908_CPU_HARTID, c908_cpus); + if (s->c908v_enabled) { + riscv_aclint_swi_create_in(&s->c908v_mem, + memmap[K230_DEV_CLINT].base, + C908V_CPU_HARTID, C908V_CPU_INDEX, + c908v_cpus, false); + riscv_aclint_mtimer_create_in(&s->c908v_mem, + memmap[K230_DEV_CLINT].base + 0x4000, + RISCV_ACLINT_DEFAULT_MTIMER_SIZE, + C908V_CPU_HARTID, C908V_CPU_INDEX, + c908v_cpus, + RISCV_ACLINT_DEFAULT_MTIMECMP, + RISCV_ACLINT_DEFAULT_MTIME, + K230_TIMEBASE_FREQ, true); + k230_clint_smode_create_in(&s->c908v_mem, + memmap[K230_DEV_CLINT].base + + K230_CLINT_SMODE_OFFSET, + C908V_CPU_HARTID, C908V_CPU_INDEX, + c908v_cpus); + } /* UART */ for (int i = 0; i < K230_UART_COUNT; i++) { - k230_create_uart(sys_mem, DEVICE(s->c908_plic), i); + k230_create_uart(sys_mem, s, i); } /* Watchdog */ @@ -200,172 +599,321 @@ static void k230_soc_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt[0]), 0, memmap[K230_DEV_WDT0].base); sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[0]), 0, - qdev_get_gpio_in(DEVICE(s->c908_plic), K230_WDT0_IRQ)); + k230_plic_irq(s, K230_WDT0_IRQ)); sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt[1]), 0, memmap[K230_DEV_WDT1].base); sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[1]), 0, - qdev_get_gpio_in(DEVICE(s->c908_plic), K230_WDT1_IRQ)); - - /* unimplemented devices */ - create_unimplemented_device("kpu.l2-cache", - memmap[K230_DEV_KPU_L2_CACHE].base, - memmap[K230_DEV_KPU_L2_CACHE].size); - - create_unimplemented_device("kpu_cfg", memmap[K230_DEV_KPU_CFG].base, - memmap[K230_DEV_KPU_CFG].size); - - create_unimplemented_device("fft", memmap[K230_DEV_FFT].base, - memmap[K230_DEV_FFT].size); - - create_unimplemented_device("2d-engine.ai", - memmap[K230_DEV_AI_2D_ENGINE].base, - memmap[K230_DEV_AI_2D_ENGINE].size); - - create_unimplemented_device("gsdma", memmap[K230_DEV_GSDMA].base, - memmap[K230_DEV_GSDMA].size); - - create_unimplemented_device("dma", memmap[K230_DEV_DMA].base, - memmap[K230_DEV_DMA].size); - - create_unimplemented_device("decomp-gzip", - memmap[K230_DEV_DECOMP_GZIP].base, - memmap[K230_DEV_DECOMP_GZIP].size); + k230_plic_irq(s, K230_WDT1_IRQ)); - create_unimplemented_device("2d-engine.non-ai", - memmap[K230_DEV_NON_AI_2D].base, - memmap[K230_DEV_NON_AI_2D].size); - - create_unimplemented_device("isp", memmap[K230_DEV_ISP].base, - memmap[K230_DEV_ISP].size); - - create_unimplemented_device("dewarp", memmap[K230_DEV_DEWARP].base, - memmap[K230_DEV_DEWARP].size); - - create_unimplemented_device("rx-csi", memmap[K230_DEV_RX_CSI].base, - memmap[K230_DEV_RX_CSI].size); - - create_unimplemented_device("vpu", memmap[K230_DEV_H264].base, - memmap[K230_DEV_H264].size); - - create_unimplemented_device("gpu", memmap[K230_DEV_2P5D].base, - memmap[K230_DEV_2P5D].size); - - create_unimplemented_device("vo", memmap[K230_DEV_VO].base, - memmap[K230_DEV_VO].size); - - create_unimplemented_device("vo_cfg", memmap[K230_DEV_VO_CFG].base, - memmap[K230_DEV_VO_CFG].size); - - create_unimplemented_device("3d-engine", memmap[K230_DEV_3D_ENGINE].base, - memmap[K230_DEV_3D_ENGINE].size); - - create_unimplemented_device("pmu", memmap[K230_DEV_PMU].base, - memmap[K230_DEV_PMU].size); - - create_unimplemented_device("rtc", memmap[K230_DEV_RTC].base, - memmap[K230_DEV_RTC].size); - - create_unimplemented_device("cmu", memmap[K230_DEV_CMU].base, - memmap[K230_DEV_CMU].size); - - create_unimplemented_device("rmu", memmap[K230_DEV_RMU].base, - memmap[K230_DEV_RMU].size); - - create_unimplemented_device("boot", memmap[K230_DEV_BOOT].base, - memmap[K230_DEV_BOOT].size); - - create_unimplemented_device("pwr", memmap[K230_DEV_PWR].base, - memmap[K230_DEV_PWR].size); - - create_unimplemented_device("ipcm", memmap[K230_DEV_MAILBOX].base, - memmap[K230_DEV_MAILBOX].size); - - create_unimplemented_device("iomux", memmap[K230_DEV_IOMUX].base, - memmap[K230_DEV_IOMUX].size); - - create_unimplemented_device("timer", memmap[K230_DEV_TIMER].base, - memmap[K230_DEV_TIMER].size); + /* GSDMA/PDMA/UGZIP blocks used by SDK U-Boot and Linux DT. */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->gsdma), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gsdma), 0, + memmap[K230_DEV_GSDMA].base); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gsdma), 0, + k230_plic_irq(s, K230_DMA_IRQ)); - create_unimplemented_device("wdt0", memmap[K230_DEV_WDT0].base, - memmap[K230_DEV_WDT0].size); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->pdma), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->pdma), 0, memmap[K230_DEV_DMA].base); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->pdma), 0, + k230_plic_irq(s, K230_PDMA_IRQ)); - create_unimplemented_device("wdt1", memmap[K230_DEV_WDT1].base, - memmap[K230_DEV_WDT1].size); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->ugzip), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->ugzip), 0, + memmap[K230_DEV_DECOMP_GZIP].base); - create_unimplemented_device("ts", memmap[K230_DEV_TS].base, - memmap[K230_DEV_TS].size); + /* SDHCI */ + for (int i = 0; i < K230_SDHCI_COUNT; i++) { + k230_create_sdhci(s, i, sd_irqs[i]); + } - create_unimplemented_device("hdi", memmap[K230_DEV_HDI].base, - memmap[K230_DEV_HDI].size); + for (int i = 0; i < 2; i++) { + k230_create_usb(s, i, usb_irqs[i]); + } + k230_create_usb_nic(s); - create_unimplemented_device("stc", memmap[K230_DEV_STC].base, - memmap[K230_DEV_STC].size); + /* High-speed system config bits used by the SDK SDHCI driver. */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->hi_sys_cfg), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->hi_sys_cfg), 0, + memmap[K230_DEV_HI_SYS_CFG].base); - create_unimplemented_device("security", memmap[K230_DEV_SECURITY].base, - memmap[K230_DEV_SECURITY].size); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->hardlock), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->hardlock), 0, + memmap[K230_DEV_MAILBOX].base); + for (int i = 0; i < K230_IPCM_IRQ_COUNT; i++) { + sysbus_connect_irq(SYS_BUS_DEVICE(&s->hardlock), i, + k230_plic_irq(s, K230_IPCM_IRQ_BASE + i)); + } - create_unimplemented_device("i2c0", memmap[K230_DEV_I2C0].base, - memmap[K230_DEV_I2C0].size); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->tsensor), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->tsensor), 0, memmap[K230_DEV_TS].base); - create_unimplemented_device("i2c1", memmap[K230_DEV_I2C1].base, - memmap[K230_DEV_I2C1].size); + for (int i = 0; i < 2; i++) { + if (!sysbus_realize(SYS_BUS_DEVICE(&s->gpio[i]), errp)) { + return; + } + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpio[0]), 0, + memmap[K230_DEV_GPIO0].base); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpio[1]), 0, + memmap[K230_DEV_GPIO1].base); + for (int bank = 0; bank < 2; bank++) { + for (int line = 0; line < K230_GPIO_IRQ_COUNT; line++) { + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[bank]), line, + k230_plic_irq(s, K230_GPIO0_IRQ + + bank * K230_GPIO_IRQ_COUNT + line)); + } + } - create_unimplemented_device("i2c2", memmap[K230_DEV_I2C2].base, - memmap[K230_DEV_I2C2].size); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->iomux), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->iomux), 0, + memmap[K230_DEV_IOMUX].base); - create_unimplemented_device("i2c3", memmap[K230_DEV_I2C3].base, - memmap[K230_DEV_I2C3].size); + for (int i = 0; i < K230_I2C_COUNT; i++) { + k230_create_i2c(s, i); + } + k230_create_camera_i2c_devices(s); - create_unimplemented_device("i2c4", memmap[K230_DEV_I2C4].base, - memmap[K230_DEV_I2C4].size); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->adc), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->adc), 0, memmap[K230_DEV_ADC].base); - create_unimplemented_device("pwm", memmap[K230_DEV_PWM].base, - memmap[K230_DEV_PWM].size); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->pwm), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->pwm), 0, memmap[K230_DEV_PWM].base); - create_unimplemented_device("gpio0", memmap[K230_DEV_GPIO0].base, - memmap[K230_DEV_GPIO0].size); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->timer), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->timer), 0, + memmap[K230_DEV_TIMER].base); - create_unimplemented_device("gpio1", memmap[K230_DEV_GPIO1].base, - memmap[K230_DEV_GPIO1].size); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->sysctl_boot), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->sysctl_boot), 0, + memmap[K230_DEV_BOOT].base); - create_unimplemented_device("adc", memmap[K230_DEV_ADC].base, - memmap[K230_DEV_ADC].size); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->sysctl_power), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->sysctl_power), 0, + memmap[K230_DEV_PWR].base); + + object_property_set_link(OBJECT(&s->sysctl_reset), "boot", + OBJECT(&s->sysctl_boot), &error_abort); + qdev_prop_set_bit(DEVICE(&s->sysctl_reset), "defer-cpu1-release", + k230_machine->boot_both_cores); + if (s->c908v_enabled) { + object_property_set_link(OBJECT(&s->sysctl_reset), "cpu1", + OBJECT(&s->c908v_cpu.harts[0]), + &error_abort); + } + if (!sysbus_realize(SYS_BUS_DEVICE(&s->sysctl_reset), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->sysctl_reset), 0, + memmap[K230_DEV_RMU].base); - create_unimplemented_device("codec", memmap[K230_DEV_CODEC].base, - memmap[K230_DEV_CODEC].size); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->pmu), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->pmu), 0, memmap[K230_DEV_PMU].base); - create_unimplemented_device("i2s", memmap[K230_DEV_I2S].base, - memmap[K230_DEV_I2S].size); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->rtc), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->rtc), 0, memmap[K230_DEV_RTC].base); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->rtc), 0, + k230_plic_irq(s, K230_PMU_IRQ)); - create_unimplemented_device("usb0", memmap[K230_DEV_USB0].base, - memmap[K230_DEV_USB0].size); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->security), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->security), 0, + memmap[K230_DEV_SECURITY].base); - create_unimplemented_device("usb1", memmap[K230_DEV_USB1].base, - memmap[K230_DEV_USB1].size); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->vo), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->vo), 0, memmap[K230_DEV_VO].base); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->vo), 0, + k230_plic_irq(s, K230_VO_IRQ)); - create_unimplemented_device("sd0", memmap[K230_DEV_SD0].base, - memmap[K230_DEV_SD0].size); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->dsi), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->dsi), 0, memmap[K230_DEV_VO_CFG].base); - create_unimplemented_device("sd1", memmap[K230_DEV_SD1].base, - memmap[K230_DEV_SD1].size); + if (!k230_create_regs(s, K230_REGS_CMU, memmap[K230_DEV_CMU].base, + memmap[K230_DEV_CMU].size, errp)) { + return; + } + /* + * Keep the legacy regs[] slot realized so K230_REGS_KPU_CFG retains its + * index, while the dedicated KPU device owns the MMIO window. + */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->regs[K230_REGS_KPU_CFG]), errp)) { + return; + } + if (!sysbus_realize(SYS_BUS_DEVICE(&s->kpu), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->kpu), 0, + memmap[K230_DEV_KPU_CFG].base); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->kpu), 0, + k230_plic_irq(s, K230_GNNE_IRQ)); + if (!k230_create_regs(s, K230_REGS_HDI, memmap[K230_DEV_HDI].base, + memmap[K230_DEV_HDI].size, errp)) { + return; + } + if (!k230_create_stc(s, memmap[K230_DEV_STC].base, + memmap[K230_DEV_STC].size, errp)) { + return; + } + if (!k230_create_regs(s, K230_REGS_NOC_QOS, K230_NOC_QOS_BASE, + K230_NOC_QOS_SIZE, errp)) { + return; + } + if (!k230_create_regs(s, K230_REGS_CODEC, memmap[K230_DEV_CODEC].base, + memmap[K230_DEV_CODEC].size, errp)) { + return; + } + qdev_prop_set_uint64(DEVICE(&s->regs[K230_REGS_I2S]), "size", + memmap[K230_DEV_I2S].size); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->regs[K230_REGS_I2S]), errp)) { + return; + } + s->i2s.compat_regs = &s->regs[K230_REGS_I2S]; + if (!sysbus_realize(SYS_BUS_DEVICE(&s->i2s), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->i2s), 0, + memmap[K230_DEV_I2S].base); + if (!k230_create_regs(s, K230_REGS_DDRC_CFG, + memmap[K230_DEV_DDRC_CFG].base, + memmap[K230_DEV_DDRC_CFG].size, errp)) { + return; + } + if (!k230_create_irq_regs(s, K230_REGS_FFT, memmap[K230_DEV_FFT].base, + memmap[K230_DEV_FFT].size, K230_FFT_IRQ, + 0x10, K230_REGS_NO_IRQ_OFFSET, + K230_REGS_NO_IRQ_OFFSET, 0x20, + K230_REGS_NO_IRQ_OFFSET, 0, + 0, K230_REGS_NO_IRQ_OFFSET, + K230_REGS_NO_IRQ_OFFSET, + K230_REGS_NO_IRQ_OFFSET, + true, false, errp)) { + return; + } + if (!k230_create_irq_regs(s, K230_REGS_AI_2D_ENGINE, + memmap[K230_DEV_AI_2D_ENGINE].base, + memmap[K230_DEV_AI_2D_ENGINE].size, + K230_AI2D_IRQ, K230_AI2D_CALC_ENABLE, + K230_AI2D_JOB_OFFSET, K230_AI2D_LEGACY_START, + K230_AI2D_CLEAR_OFFSET, + K230_REGS_NO_IRQ_OFFSET, 0, + 0, K230_REGS_NO_IRQ_OFFSET, + K230_REGS_NO_IRQ_OFFSET, + K230_REGS_NO_IRQ_OFFSET, + true, false, errp)) { + return; + } + /* + * Keep the legacy regs[] slot realized so K230_REGS_DPU retains its old + * index, while the dedicated non-AI 2D device owns the MMIO window. + */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->regs[K230_REGS_NON_AI_2D]), + errp)) { + return; + } + if (!sysbus_realize(SYS_BUS_DEVICE(&s->nonai_2d), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->nonai_2d), 0, + memmap[K230_DEV_NON_AI_2D].base); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->nonai_2d), 0, + k230_plic_irq(s, K230_NON_AI_2D_IRQ)); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->isp), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->isp), 0, memmap[K230_DEV_ISP].base); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->isp), 0, + k230_plic_irq(s, K230_ISP_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->isp), 1, + k230_plic_irq(s, K230_ISP_MI_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->isp), 2, + k230_plic_irq(s, K230_ISP_FE_IRQ)); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->rx_csi), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->rx_csi), 0, + memmap[K230_DEV_RX_CSI].base); - create_unimplemented_device("qspi0", memmap[K230_DEV_QSPI0].base, - memmap[K230_DEV_QSPI0].size); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->dewarp), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->dewarp), 0, + memmap[K230_DEV_DEWARP].base); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->dewarp), 0, + k230_plic_irq(s, K230_DWE_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->dewarp), 1, + k230_plic_irq(s, K230_FE_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->dewarp), 2, + k230_plic_irq(s, K230_VSE_IRQ)); + + qdev_prop_set_uint64(DEVICE(&s->regs[K230_REGS_DPU]), + "irq-start-mask", BIT(0)); + if (!k230_create_irq_regs(s, K230_REGS_DPU, + memmap[K230_DEV_3D_ENGINE].base, + memmap[K230_DEV_3D_ENGINE].size, + K230_DPU_IRQ, 0x200, 0x380, + K230_REGS_NO_IRQ_OFFSET, 0x1fc, 0x1f4, 0x3, + 0, K230_REGS_NO_IRQ_OFFSET, + K230_REGS_NO_IRQ_OFFSET, + K230_REGS_NO_IRQ_OFFSET, + true, false, errp)) { + return; + } - create_unimplemented_device("qspi1", memmap[K230_DEV_QSPI1].base, - memmap[K230_DEV_QSPI1].size); + if (!k230_create_spi(s, K230_SPI_QSPI0, K230_DEV_QSPI0, + K230_QSPI0_IRQ, errp)) { + return; + } + if (!k230_create_spi(s, K230_SPI_QSPI1, K230_DEV_QSPI1, + K230_QSPI1_IRQ, errp)) { + return; + } + if (!k230_create_spi(s, K230_SPI_SPI0, K230_DEV_SPI, K230_SPI_IRQ, + errp)) { + return; + } - create_unimplemented_device("spi", memmap[K230_DEV_SPI].base, - memmap[K230_DEV_SPI].size); + k230_create_flash_xip(s, dev, sys_mem); - create_unimplemented_device("hi_sys_cfg", memmap[K230_DEV_HI_SYS_CFG].base, - memmap[K230_DEV_HI_SYS_CFG].size); + /* unimplemented devices */ + create_unimplemented_device("vpu", memmap[K230_DEV_H264].base, + memmap[K230_DEV_H264].size); - create_unimplemented_device("ddrc_cfg", memmap[K230_DEV_DDRC_CFG].base, - memmap[K230_DEV_DDRC_CFG].size); + create_unimplemented_device("gpu", memmap[K230_DEV_2P5D].base, + memmap[K230_DEV_2P5D].size); - create_unimplemented_device("flash", memmap[K230_DEV_FLASH].base, - memmap[K230_DEV_FLASH].size); } static void k230_soc_class_init(ObjectClass *oc, const void *data) @@ -392,7 +940,8 @@ type_init(k230_soc_register_types) static void k230_direct_boot(K230MachineState *s, MachineState *machine) { - const char *firmware_name = riscv_default_firmware_name(&s->soc.c908_cpu); + RISCVHartArrayState *boot_harts = k230_boot_harts(s); + const char *firmware_name = riscv_default_firmware_name(boot_harts); RISCVBootInfo boot_info = {0}; hwaddr start_addr = K230_DIRECT_OPENSBI_ADDR; hwaddr firmware_end_addr = 0; @@ -418,7 +967,7 @@ static void k230_direct_boot(K230MachineState *s, MachineState *machine) qemu_fdt_add_path(machine->fdt, "/chosen"); - riscv_boot_info_init(&boot_info, &s->soc.c908_cpu); + riscv_boot_info_init(&boot_info, boot_harts); riscv_load_kernel(machine, &boot_info, K230_DIRECT_KERNEL_ADDR, true, NULL); kernel_entry = boot_info.image_low_addr; @@ -433,7 +982,7 @@ static void k230_direct_boot(K230MachineState *s, MachineState *machine) exit(EXIT_FAILURE); } - riscv_setup_rom_reset_vec(machine, &s->soc.c908_cpu, start_addr, + riscv_setup_rom_reset_vec(machine, boot_harts, start_addr, memmap[K230_DEV_BOOTROM].base, memmap[K230_DEV_BOOTROM].size, kernel_entry, K230_DIRECT_DTB_ADDR); @@ -441,8 +990,9 @@ static void k230_direct_boot(K230MachineState *s, MachineState *machine) static void k230_firmware_boot(K230MachineState *s, MachineState *machine) { - const char *firmware_name = riscv_default_firmware_name(&s->soc.c908_cpu); - hwaddr start_addr = memmap[K230_DEV_DDRC].base; + RISCVHartArrayState *boot_harts = k230_boot_harts(s); + const char *firmware_name = riscv_default_firmware_name(boot_harts); + hwaddr start_addr = K230_FIRMWARE_OPENSBI_ADDR; RISCVBootInfo boot_info = {0}; if (machine->dtb || (machine->kernel_cmdline && *machine->kernel_cmdline)) { @@ -450,11 +1000,11 @@ static void k230_firmware_boot(K230MachineState *s, MachineState *machine) exit(EXIT_FAILURE); } - riscv_boot_info_init(&boot_info, &s->soc.c908_cpu); + riscv_boot_info_init(&boot_info, boot_harts); riscv_find_and_load_firmware(machine, &boot_info, firmware_name, &start_addr, NULL); - riscv_setup_rom_reset_vec(machine, &s->soc.c908_cpu, start_addr, + riscv_setup_rom_reset_vec(machine, boot_harts, start_addr, memmap[K230_DEV_BOOTROM].base, memmap[K230_DEV_BOOTROM].size, 0, 0); } @@ -472,6 +1022,44 @@ static void k230_machine_done(Notifier *notifier, void *data) } } +static void k230_attach_sd_drive(K230MachineState *s, int sd_index, + int drive_unit) +{ + DriveInfo *dinfo = drive_get(IF_SD, 0, drive_unit); + DeviceState *card; + + if (!dinfo) { + return; + } + + card = qdev_new(TYPE_SD_CARD); + qdev_prop_set_drive_err(card, "drive", blk_by_legacy_dinfo(dinfo), + &error_fatal); + qdev_realize_and_unref(card, s->soc.sdhci[sd_index].sd_bus, &error_fatal); +} + +static void k230_attach_sd_drives(K230MachineState *s) +{ + /* + * The SDK's CANMV DTB uses SD1 as the removable card slot; keep the first + * legacy SD drive there so "-drive if=sd" and "-sd" boot the SDK image. + */ + k230_attach_sd_drive(s, 1, 0); + k230_attach_sd_drive(s, 0, 1); +} + +static void k230_attach_spi_flash(K230MachineState *s) +{ + DriveInfo *dinfo = drive_get(IF_MTD, 0, 0); + + if (!dinfo) { + return; + } + + qdev_prop_set_drive_err(DEVICE(&s->soc.spi[K230_SPI_SPI0]), "drive", + blk_by_legacy_dinfo(dinfo), &error_fatal); +} + static void k230_machine_init(MachineState *machine) { MachineClass *mc = MACHINE_GET_CLASS(machine); @@ -488,12 +1076,15 @@ static void k230_machine_init(MachineState *machine) /* Initialize SoC */ object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_RISCV_K230_SOC); + k230_attach_spi_flash(s); qdev_realize(DEVICE(&s->soc), NULL, &error_fatal); /* Data Memory */ memory_region_add_subregion(sys_mem, memmap[K230_DEV_DDRC].base, machine->ram); + k230_attach_sd_drives(s); + s->machine_done.notify = k230_machine_done; qemu_add_machine_init_done_notifier(&s->machine_done); } @@ -502,15 +1093,40 @@ static void k230_machine_instance_init(Object *obj) { } +static bool k230_machine_get_boot_both_cores(Object *obj, Error **errp) +{ + K230MachineState *s = RISCV_K230_MACHINE(obj); + + return s->boot_both_cores; +} + +static void k230_machine_set_boot_both_cores(Object *obj, bool value, + Error **errp) +{ + K230MachineState *s = RISCV_K230_MACHINE(obj); + + s->boot_both_cores = value; +} + static void k230_machine_class_init(ObjectClass *oc, const void *data) { MachineClass *mc = MACHINE_CLASS(oc); - mc->desc = "RISC-V Board compatible with Kendryte K230 SDK"; + mc->desc = "Canaan CanMV-K230 board"; mc->init = k230_machine_init; + mc->max_cpus = 2; mc->default_cpus = 1; mc->default_ram_id = "riscv.K230.ram"; /* DDR */ mc->default_ram_size = memmap[K230_DEV_DDRC].size; + mc->default_nic = "usb-rtl8152"; + mc->auto_create_sdcard = true; + + object_class_property_add_bool(oc, "boot-both-cores", + k230_machine_get_boot_both_cores, + k230_machine_set_boot_both_cores); + object_class_property_set_description( + oc, "boot-both-cores", + "Start C908 and C908V at reset for U-Boot dual-core bring-up"); } static const TypeInfo k230_machine_typeinfo = { diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build index 0d82ceacc430f..17e6947b50530 100644 --- a/hw/riscv/meson.build +++ b/hw/riscv/meson.build @@ -6,6 +6,8 @@ riscv_ss.add(when: 'CONFIG_RISCV_NUMA', if_true: files('numa.c')) riscv_ss.add(files('riscv_hart.c')) riscv_ss.add(when: 'CONFIG_OPENTITAN', if_true: files('opentitan.c')) riscv_ss.add(when: 'CONFIG_RISCV_VIRT', if_true: files('virt.c')) +riscv_ss.add(when: 'CONFIG_RISCV_SERVER_PLATFORM_REF', if_true: files('server_platform_ref.c')) +riscv_ss.add(when: 'CONFIG_SPACEMIT_K3', if_true: files('spacemit-k3.c')) riscv_ss.add(when: 'CONFIG_SHAKTI_C', if_true: files('shakti_c.c')) riscv_ss.add(when: 'CONFIG_SIFIVE_E', if_true: files('sifive_e.c')) riscv_ss.add(when: 'CONFIG_SIFIVE_U', if_true: files('sifive_u.c')) @@ -21,4 +23,7 @@ riscv_ss.add(when: 'CONFIG_RISCV_MIPS_CPS', if_true: files('cps.c')) riscv_ss.add(when: 'CONFIG_MIPS_BOSTON_AIA', if_true: files('boston-aia.c')) riscv_ss.add(when: 'CONFIG_K230', if_true: files('k230.c')) +riscv_ss.add(when: 'CONFIG_SOPHGO_CV1800B', if_true: files('cv1800b.c')) +riscv_ss.add(when: 'CONFIG_MILKV_DUO', if_true: files('milkv_duo.c')) + hw_arch += {'riscv': riscv_ss} diff --git a/hw/riscv/milkv_duo.c b/hw/riscv/milkv_duo.c new file mode 100644 index 0000000000000..89bdd0e5b9951 --- /dev/null +++ b/hw/riscv/milkv_duo.c @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Milk-V Duo board + * + * Copyright (c) 2026 Kuan-Wei Chiu + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qapi/error.h" +#include "hw/core/boards.h" +#include "hw/riscv/cv1800b.h" +#include "hw/riscv/boot.h" +#include "target/riscv/cpu-qom.h" +#include "system/system.h" +#include "system/device_tree.h" +#include "qemu/error-report.h" +#include "hw/core/loader.h" +#include +#include "hw/riscv/machines-qom.h" + +struct MilkVDuoState { + MachineState parent_obj; + CV1800BSoCState soc; +}; + +#define TYPE_MILK_V_DUO MACHINE_TYPE_NAME("milkv-duo") +OBJECT_DECLARE_SIMPLE_TYPE(MilkVDuoState, MILK_V_DUO) + +static void milkv_duo_init(MachineState *machine) +{ + MilkVDuoState *s = MILK_V_DUO(machine); + MemoryRegion *system_memory = get_system_memory(); + RISCVBootInfo boot_info; + hwaddr firmware_load_addr, firmware_end_addr; + hwaddr fdt_load_addr = 0; + int fdt_size = 0; + uint64_t kernel_entry = 0; + + object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_CV1800B_SOC); + qdev_realize(DEVICE(&s->soc), NULL, &error_fatal); + + memory_region_add_subregion(system_memory, + cv1800b_memmap[CV1800B_DEV_DRAM].base, + machine->ram); + + riscv_boot_info_init(&boot_info, &s->soc.cpus); + + firmware_load_addr = cv1800b_memmap[CV1800B_DEV_DRAM].base; + firmware_end_addr = firmware_load_addr; + if (machine->firmware) { + firmware_end_addr = riscv_find_and_load_firmware( + machine, &boot_info, machine->firmware, + &firmware_load_addr, NULL); + } + + if (machine->dtb) { + machine->fdt = load_device_tree(machine->dtb, &fdt_size); + if (!machine->fdt) { + error_report("Failed to load device tree"); + exit(1); + } + + if (machine->kernel_cmdline && *machine->kernel_cmdline) { + if (fdt_path_offset(machine->fdt, "/chosen") < 0) { + qemu_fdt_add_subnode(machine->fdt, "/chosen"); + } + qemu_fdt_setprop_string(machine->fdt, "/chosen", "bootargs", + machine->kernel_cmdline); + } + } + + if (machine->kernel_filename) { + hwaddr kernel_start_addr = riscv_calc_kernel_start_addr(&boot_info, + firmware_end_addr); + riscv_load_kernel(machine, &boot_info, kernel_start_addr, true, NULL); + kernel_entry = boot_info.image_low_addr; + } + + if (machine->dtb) { + fdt_load_addr = riscv_compute_fdt_addr(cv1800b_memmap[CV1800B_DEV_DRAM].base, + machine->ram_size, machine, &boot_info); + rom_add_blob_fixed_as("fdt", machine->fdt, fdt_size, fdt_load_addr, + &address_space_memory); + } + + riscv_setup_rom_reset_vec(machine, &s->soc.cpus, + firmware_load_addr, + cv1800b_memmap[CV1800B_DEV_ROM].base, + cv1800b_memmap[CV1800B_DEV_ROM].size, + kernel_entry, + fdt_load_addr); +} + +static void milkv_duo_machine_class_init(ObjectClass *oc, const void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + static const char *const valid_cpu_types[] = { + TYPE_RISCV_CPU_THEAD_C906, + NULL + }; + + mc->desc = "Milk-V Duo Board (CV1800B)"; + mc->init = milkv_duo_init; + mc->max_cpus = 2; + mc->default_cpu_type = TYPE_RISCV_CPU_THEAD_C906; + mc->valid_cpu_types = valid_cpu_types; + mc->default_ram_size = 64 * MiB; + mc->default_ram_id = "riscv.milkv_duo.ram"; +} + +static const TypeInfo milkv_duo_machine_type_info = { + .name = TYPE_MILK_V_DUO, + .parent = TYPE_MACHINE, + .instance_size = sizeof(MilkVDuoState), + .class_init = milkv_duo_machine_class_init, + .interfaces = riscv64_machine_interfaces, +}; + +static void milkv_duo_machine_register_types(void) +{ + type_register_static(&milkv_duo_machine_type_info); +} + +type_init(milkv_duo_machine_register_types) diff --git a/hw/riscv/riscv_hart.c b/hw/riscv/riscv_hart.c index 747754be6158e..d327d7c26e65b 100644 --- a/hw/riscv/riscv_hart.c +++ b/hw/riscv/riscv_hart.c @@ -53,6 +53,10 @@ static const Property riscv_harts_props[] = { DEFINE_PROP_ARRAY("rnmi-exception-vector", RISCVHartArrayState, num_rnmi_excpvec, rnmi_excpvec, qdev_prop_uint64, uint64_t), + DEFINE_PROP_LINK("memory", RISCVHartArrayState, memory, + TYPE_MEMORY_REGION, MemoryRegion *), + DEFINE_PROP_BOOL("start-powered-off", RISCVHartArrayState, + start_powered_off, false), }; static void riscv_harts_cpu_reset(void *opaque) @@ -116,6 +120,12 @@ static bool riscv_hart_realize(RISCVHartArrayState *s, int idx, { object_initialize_child(OBJECT(s), "harts[*]", &s->harts[idx], cpu_type); qdev_prop_set_uint64(DEVICE(&s->harts[idx]), "resetvec", s->resetvec); + if (s->memory) { + object_property_set_link(OBJECT(&s->harts[idx]), "memory", + OBJECT(s->memory), &error_abort); + } + object_property_set_bool(OBJECT(&s->harts[idx]), "start-powered-off", + s->start_powered_off, &error_abort); if (s->harts[idx].cfg.ext_smrnmi) { if (idx < s->num_rnmi_irqvec) { diff --git a/hw/riscv/server_platform_ref.c b/hw/riscv/server_platform_ref.c new file mode 100644 index 0000000000000..cf7a20737f2f5 --- /dev/null +++ b/hw/riscv/server_platform_ref.c @@ -0,0 +1,1478 @@ +/* + * QEMU RISC-V Server Platform Reference Board (riscv-server-ref) + * + * Copyright (c) 2024 Intel, Inc. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qemu/error-report.h" +#include "qemu/guest-random.h" +#include "qapi/error.h" +#include "qapi/qapi-visit-common.h" +#include "hw/core/boards.h" +#include "hw/core/platform-bus.h" +#include "hw/core/loader.h" +#include "hw/core/sysbus.h" +#include "hw/core/qdev-properties.h" +#include "hw/char/serial.h" +#include "hw/block/flash.h" +#include "hw/ide/pci.h" +#include "hw/ide/ahci-pci.h" +#include "hw/pci/pci.h" +#include "hw/pci-host/gpex.h" +#include "hw/core/sysbus-fdt.h" +#include "hw/riscv/riscv_hart.h" +#include "hw/riscv/boot.h" +#include "hw/riscv/machines-qom.h" +#include "hw/riscv/numa.h" +#include "hw/riscv/iommu.h" +#include "hw/riscv/riscv-iommu-bits.h" +#include "hw/intc/riscv_aclint.h" +#include "hw/intc/riscv_aplic.h" +#include "hw/intc/riscv_imsic.h" +#include "chardev/char.h" +#include "hw/char/serial-mm.h" +#include "system/device_tree.h" +#include "system/runstate.h" +#include "system/system.h" +#include "system/tcg.h" +#include "system/tpm.h" +#include "system/qtest.h" +#include "target/riscv/cpu.h" +#include "target/riscv/tcg/pmu.h" +#include "net/net.h" + +#define RVSERVER_CPUS_MAX_BITS 9 +#define RVSERVER_CPUS_MAX (1 << RVSERVER_CPUS_MAX_BITS) +#define RVSERVER_SOCKETS_MAX_BITS 2 +#define RVSERVER_SOCKETS_MAX (1 << RVSERVER_SOCKETS_MAX_BITS) + +#define RVSERVER_IRQCHIP_NUM_MSIS 255 +#define RVSERVER_IRQCHIP_NUM_SOURCES 96 +#define RVSERVER_IRQCHIP_NUM_PRIO_BITS 3 +#define RVSERVER_IRQCHIP_MAX_GUESTS_BITS 3 +#define RVSERVER_IRQCHIP_MAX_GUESTS \ + ((1U << RVSERVER_IRQCHIP_MAX_GUESTS_BITS) - 1U) + +#define FDT_PCI_ADDR_CELLS 3 +#define FDT_PCI_INT_CELLS 1 +#define FDT_APLIC_INT_CELLS 2 +#define FDT_APLIC_ADDR_CELLS 0 +#define FDT_IMSIC_INT_CELLS 0 +#define FDT_MAX_INT_CELLS 2 +#define FDT_MAX_INT_MAP_WIDTH (FDT_PCI_ADDR_CELLS + FDT_PCI_INT_CELLS + \ + 1 + FDT_MAX_INT_CELLS) +#define FDT_APLIC_INT_MAP_WIDTH (FDT_PCI_ADDR_CELLS + FDT_PCI_INT_CELLS + \ + 1 + FDT_APLIC_INT_CELLS) + +#define NUM_SATA_PORTS 6 + +#define SYSCON_RESET 0x1 +#define SYSCON_POWEROFF 0x2 + +#define RVSERVER_PLATFORM_BUS_NUM_IRQS 8 + +#define TYPE_RISCV_SERVER_REF_MACHINE MACHINE_TYPE_NAME("riscv-server-ref") +OBJECT_DECLARE_SIMPLE_TYPE(RISCVServerRefMachineState, RISCV_SERVER_REF_MACHINE) + +struct RISCVServerRefMachineState { + /*< private >*/ + MachineState parent; + + /*< public >*/ + Notifier machine_done; + RISCVHartArrayState soc[RVSERVER_SOCKETS_MAX]; + DeviceState *irqchip[RVSERVER_SOCKETS_MAX]; + PFlashCFI01 *flash[2]; + + int fdt_size; + int aia_guests; + const MemMapEntry *memmap; + + DeviceState *platform_bus_dev; +}; + +enum { + RVSERVER_DEBUG, + RVSERVER_MROM, + RVSERVER_RESET_SYSCON, + RVSERVER_RTC, + RVSERVER_IOMMU_SYS, + RVSERVER_ACLINT, + RVSERVER_APLIC_M, + RVSERVER_APLIC_S, + RVSERVER_UART0, + RVSERVER_IMSIC_M, + RVSERVER_IMSIC_S, + RVSERVER_FLASH, + RVSERVER_DRAM, + RVSERVER_PCIE_MMIO, + RVSERVER_PCIE_PIO, + RVSERVER_PLATFORM_BUS, + RVSERVER_PCIE_ECAM, + RVSERVER_PCIE_MMIO_HIGH +}; + +enum { + RVSERVER_UART0_IRQ = 10, + RVSERVER_RTC_IRQ = 11, + RVSERVER_PCIE_IRQ = 0x20, /* 32 to 35 */ + IOMMU_SYS_IRQ = 0x24, /* 36 to 39 */ + RVSERVER_PLATFORM_BUS_IRQ = 40, /* 40 to 48 */ +}; + +/* + * The server soc reference machine physical address space used by some of the + * devices namely ACLINT, APLIC and IMSIC depend on number of Sockets, number + * of CPUs, and number of IMSIC guest files. + * + * Various limits defined by RVSERVER_SOCKETS_MAX_BITS, RVSERVER_CPUS_MAX_BITS, + * and RVSERVER_IRQCHIP_MAX_GUESTS_BITS are tuned for maximum utilization of + * server reference machine physical address space. + */ + +#define RVSERVER_IMSIC_GROUP_MAX_SIZE (1U << IMSIC_MMIO_GROUP_MIN_SHIFT) +#if RVSERVER_IMSIC_GROUP_MAX_SIZE < \ + IMSIC_GROUP_SIZE(RVSERVER_CPUS_MAX_BITS, RVSERVER_IRQCHIP_MAX_GUESTS_BITS) +#error "Can't accomodate single IMSIC group in address space" +#endif + +#define RVSERVER_IMSIC_MAX_SIZE (RVSERVER_SOCKETS_MAX * \ + RVSERVER_IMSIC_GROUP_MAX_SIZE) +#if 0x4000000 < RVSERVER_IMSIC_MAX_SIZE +#error "Can't accomodate all IMSIC groups in address space" +#endif + +static const MemMapEntry rvserver_ref_memmap[] = { + [RVSERVER_DEBUG] = { 0x0, 0x100 }, + [RVSERVER_MROM] = { 0x1000, 0xf000 }, + [RVSERVER_RESET_SYSCON] = { 0x100000, 0x1000 }, + [RVSERVER_RTC] = { 0x101000, 0x1000 }, + [RVSERVER_IOMMU_SYS] = { 0x102000, 0x1000 }, + [RVSERVER_ACLINT] = { 0x2000000, 0x10000 }, + [RVSERVER_PCIE_PIO] = { 0x3000000, 0x10000 }, + [RVSERVER_PLATFORM_BUS] = { 0x4000000, 0x2000000 }, + [RVSERVER_APLIC_M] = { 0xc000000, APLIC_SIZE(RVSERVER_CPUS_MAX) }, + [RVSERVER_APLIC_S] = { 0xd000000, APLIC_SIZE(RVSERVER_CPUS_MAX) }, + [RVSERVER_UART0] = { 0x10000000, 0x100 }, + [RVSERVER_FLASH] = { 0x20000000, 0x4000000 }, + [RVSERVER_IMSIC_M] = { 0x24000000, RVSERVER_IMSIC_MAX_SIZE }, + [RVSERVER_IMSIC_S] = { 0x28000000, RVSERVER_IMSIC_MAX_SIZE }, + [RVSERVER_PCIE_ECAM] = { 0x30000000, 0x10000000 }, + [RVSERVER_PCIE_MMIO] = { 0x40000000, 0x40000000 }, + [RVSERVER_DRAM] = { 0x80000000, 0xff80000000ull }, + [RVSERVER_PCIE_MMIO_HIGH] = { 0x10000000000ull, 0x10000000000ull }, +}; + +#define RVSERVER_FLASH_SECTOR_SIZE (256 * KiB) + +static PFlashCFI01 *rvserver_flash_create(RISCVServerRefMachineState *s, + const char *name, + const char *alias_prop_name) +{ + /* + * Create a single flash device. We use the same parameters as + * the flash devices on the ARM virt board. + */ + DeviceState *dev = qdev_new(TYPE_PFLASH_CFI01); + + qdev_prop_set_uint64(dev, "sector-length", RVSERVER_FLASH_SECTOR_SIZE); + qdev_prop_set_uint8(dev, "width", 4); + qdev_prop_set_uint8(dev, "device-width", 2); + qdev_prop_set_bit(dev, "big-endian", false); + qdev_prop_set_uint16(dev, "id0", 0x89); + qdev_prop_set_uint16(dev, "id1", 0x18); + qdev_prop_set_uint16(dev, "id2", 0x00); + qdev_prop_set_uint16(dev, "id3", 0x00); + qdev_prop_set_string(dev, "name", name); + + object_property_add_child(OBJECT(s), name, OBJECT(dev)); + object_property_add_alias(OBJECT(s), alias_prop_name, + OBJECT(dev), "drive"); + + return PFLASH_CFI01(dev); +} + +static void rvserver_flash_map(PFlashCFI01 *flash, + hwaddr base, hwaddr size, + MemoryRegion *sysmem) +{ + DeviceState *dev = DEVICE(flash); + + assert(QEMU_IS_ALIGNED(size, RVSERVER_FLASH_SECTOR_SIZE)); + assert(size / RVSERVER_FLASH_SECTOR_SIZE <= UINT32_MAX); + qdev_prop_set_uint32(dev, "num-blocks", size / RVSERVER_FLASH_SECTOR_SIZE); + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + + memory_region_add_subregion(sysmem, base, + sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), + 0)); +} + +static void rvserver_flash_maps(RISCVServerRefMachineState *s, + MemoryRegion *sysmem) +{ + hwaddr flashsize = rvserver_ref_memmap[RVSERVER_FLASH].size / 2; + hwaddr flashbase = rvserver_ref_memmap[RVSERVER_FLASH].base; + + rvserver_flash_map(s->flash[0], flashbase, flashsize, sysmem); + rvserver_flash_map(s->flash[1], flashbase + flashsize, flashsize, sysmem); +} + +static void create_platform_bus(RISCVServerRefMachineState *s, + DeviceState *irqchip) +{ + DeviceState *dev; + SysBusDevice *sysbus; + int i; + MemoryRegion *sysmem = get_system_memory(); + + dev = qdev_new(TYPE_PLATFORM_BUS_DEVICE); + dev->id = g_strdup(TYPE_PLATFORM_BUS_DEVICE); + qdev_prop_set_uint32(dev, "num_irqs", RVSERVER_PLATFORM_BUS_NUM_IRQS); + qdev_prop_set_uint32(dev, "mmio_size", + s->memmap[RVSERVER_PLATFORM_BUS].size); + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + + s->platform_bus_dev = dev; + + sysbus = SYS_BUS_DEVICE(dev); + for (i = 0; i < RVSERVER_PLATFORM_BUS_NUM_IRQS; i++) { + int irq = RVSERVER_PLATFORM_BUS_IRQ + i; + sysbus_connect_irq(sysbus, i, qdev_get_gpio_in(irqchip, irq)); + } + + memory_region_add_subregion(sysmem, + s->memmap[RVSERVER_PLATFORM_BUS].base, + sysbus_mmio_get_region(sysbus, 0)); +} + +static void create_pcie_irq_map(RISCVServerRefMachineState *s, + void *fdt, char *nodename, + uint32_t irqchip_phandle) +{ + int pin, dev; + uint32_t irq_map_stride = 0; + uint32_t full_irq_map[PCI_NUM_PINS * PCI_NUM_PINS * + FDT_MAX_INT_MAP_WIDTH] = {}; + uint32_t *irq_map = full_irq_map; + + /* + * This code creates a standard swizzle of interrupts such that + * each device's first interrupt is based on it's PCI_SLOT number. + * (See pci_swizzle_map_irq_fn()) + * + * We only need one entry per interrupt in the table (not one per + * possible slot) seeing the interrupt-map-mask will allow the table + * to wrap to any number of devices. + */ + for (dev = 0; dev < PCI_NUM_PINS; dev++) { + int devfn = dev * 0x8; + + for (pin = 0; pin < PCI_NUM_PINS; pin++) { + int irq_nr = RVSERVER_PCIE_IRQ + + ((pin + PCI_SLOT(devfn)) % PCI_NUM_PINS); + int i = 0; + + /* Fill PCI address cells */ + irq_map[i] = cpu_to_be32(devfn << 8); + i += FDT_PCI_ADDR_CELLS; + + /* Fill PCI Interrupt cells */ + irq_map[i] = cpu_to_be32(pin + 1); + i += FDT_PCI_INT_CELLS; + + /* Fill interrupt controller phandle and cells */ + irq_map[i++] = cpu_to_be32(irqchip_phandle); + irq_map[i++] = cpu_to_be32(irq_nr); + irq_map[i++] = cpu_to_be32(0x4); + + if (!irq_map_stride) { + irq_map_stride = i; + } + irq_map += irq_map_stride; + } + } + + qemu_fdt_setprop(fdt, nodename, "interrupt-map", full_irq_map, + PCI_NUM_PINS * PCI_NUM_PINS * + irq_map_stride * sizeof(uint32_t)); + + qemu_fdt_setprop_cells(fdt, nodename, "interrupt-map-mask", + 0x1800, 0, 0, 0x7); +} + +static void create_fdt_socket_cpus(RISCVServerRefMachineState *s, int socket, + char *clust_name, uint32_t *phandle, + uint32_t *intc_phandles) +{ + int cpu; + uint32_t cpu_phandle; + MachineState *ms = MACHINE(s); + bool is_32_bit = riscv_is_32bit(&s->soc[0]); + int8_t satp_mode_max; + + for (cpu = s->soc[socket].num_harts - 1; cpu >= 0; cpu--) { + RISCVCPU *cpu_ptr = &s->soc[socket].harts[cpu]; + satp_mode_max = cpu_ptr->cfg.max_satp_mode; + g_autofree char *cpu_name = NULL; + g_autofree char *core_name = NULL; + g_autofree char *intc_name = NULL; + g_autofree char *sv_name = NULL; + + cpu_phandle = (*phandle)++; + + cpu_name = g_strdup_printf("/cpus/cpu@%d", + s->soc[socket].hartid_base + cpu); + qemu_fdt_add_subnode(ms->fdt, cpu_name); + + if (satp_mode_max != -1) { + sv_name = g_strdup_printf("riscv,%s", + satp_mode_str(satp_mode_max, is_32_bit)); + qemu_fdt_setprop_string(ms->fdt, cpu_name, "mmu-type", sv_name); + } + + riscv_isa_write_fdt(cpu_ptr, ms->fdt, cpu_name); + + if (cpu_ptr->cfg.ext_zicbom) { + qemu_fdt_setprop_cell(ms->fdt, cpu_name, "riscv,cbom-block-size", + cpu_ptr->cfg.cbom_blocksize); + } + + if (cpu_ptr->cfg.ext_zicboz) { + qemu_fdt_setprop_cell(ms->fdt, cpu_name, "riscv,cboz-block-size", + cpu_ptr->cfg.cboz_blocksize); + } + + if (cpu_ptr->cfg.ext_zicbop) { + qemu_fdt_setprop_cell(ms->fdt, cpu_name, "riscv,cbop-block-size", + cpu_ptr->cfg.cbop_blocksize); + } + + qemu_fdt_setprop_string(ms->fdt, cpu_name, "compatible", "riscv"); + qemu_fdt_setprop_string(ms->fdt, cpu_name, "status", "okay"); + qemu_fdt_setprop_cell(ms->fdt, cpu_name, "reg", + s->soc[socket].hartid_base + cpu); + qemu_fdt_setprop_string(ms->fdt, cpu_name, "device_type", "cpu"); + riscv_socket_fdt_write_id(ms, cpu_name, socket); + qemu_fdt_setprop_cell(ms->fdt, cpu_name, "phandle", cpu_phandle); + + intc_phandles[cpu] = (*phandle)++; + + intc_name = g_strdup_printf("%s/interrupt-controller", cpu_name); + qemu_fdt_add_subnode(ms->fdt, intc_name); + qemu_fdt_setprop_cell(ms->fdt, intc_name, "phandle", + intc_phandles[cpu]); + qemu_fdt_setprop_string(ms->fdt, intc_name, "compatible", + "riscv,cpu-intc"); + qemu_fdt_setprop(ms->fdt, intc_name, "interrupt-controller", NULL, 0); + qemu_fdt_setprop_cell(ms->fdt, intc_name, "#interrupt-cells", 1); + + core_name = g_strdup_printf("%s/core%d", clust_name, cpu); + qemu_fdt_add_subnode(ms->fdt, core_name); + qemu_fdt_setprop_cell(ms->fdt, core_name, "cpu", cpu_phandle); + } +} + +static void create_fdt_socket_memory(RISCVServerRefMachineState *s, + const MemMapEntry *memmap, int socket) +{ + g_autofree char *mem_name = NULL; + hwaddr addr, size; + MachineState *ms = MACHINE(s); + + addr = memmap[RVSERVER_DRAM].base + riscv_socket_mem_offset(ms, socket); + size = riscv_socket_mem_size(ms, socket); + mem_name = g_strdup_printf("/memory@%"HWADDR_PRIx, addr); + qemu_fdt_add_subnode(ms->fdt, mem_name); + qemu_fdt_setprop_cells(ms->fdt, mem_name, "reg", + addr >> 32, addr, size >> 32, size); + qemu_fdt_setprop_string(ms->fdt, mem_name, "device_type", "memory"); + riscv_socket_fdt_write_id(ms, mem_name, socket); +} + +static void create_fdt_socket_aclint(RISCVServerRefMachineState *s, + const MemMapEntry *memmap, int socket, + uint32_t *intc_phandles) +{ + int cpu; + g_autofree char *name = NULL; + hwaddr addr, size; + uint32_t aclint_cells_size; + g_autofree uint32_t *aclint_mtimer_cells = NULL; + MachineState *ms = MACHINE(s); + + aclint_mtimer_cells = g_new0(uint32_t, s->soc[socket].num_harts * 2); + + for (cpu = 0; cpu < s->soc[socket].num_harts; cpu++) { + aclint_mtimer_cells[cpu * 2 + 0] = cpu_to_be32(intc_phandles[cpu]); + aclint_mtimer_cells[cpu * 2 + 1] = cpu_to_be32(IRQ_M_TIMER); + } + aclint_cells_size = s->soc[socket].num_harts * sizeof(uint32_t) * 2; + + addr = memmap[RVSERVER_ACLINT].base + + RISCV_ACLINT_DEFAULT_MTIMER_SIZE * socket; + size = RISCV_ACLINT_DEFAULT_MTIMER_SIZE; + + name = g_strdup_printf("/soc/mtimer@%"HWADDR_PRIx, addr); + qemu_fdt_add_subnode(ms->fdt, name); + qemu_fdt_setprop_string(ms->fdt, name, "compatible", + "riscv,aclint-mtimer"); + qemu_fdt_setprop_cells(ms->fdt, name, "reg", + 0x0, addr + RISCV_ACLINT_DEFAULT_MTIME, + 0x0, size - RISCV_ACLINT_DEFAULT_MTIME, + 0x0, addr + RISCV_ACLINT_DEFAULT_MTIMECMP, + 0x0, RISCV_ACLINT_DEFAULT_MTIME); + qemu_fdt_setprop(ms->fdt, name, "interrupts-extended", + aclint_mtimer_cells, aclint_cells_size); + riscv_socket_fdt_write_id(ms, name, socket); +} + +static uint32_t imsic_num_bits(uint32_t count) +{ + uint32_t ret = 0; + + while (BIT(ret) < count) { + ret++; + } + + return ret; +} + +static void create_fdt_one_imsic(RISCVServerRefMachineState *s, + hwaddr base_addr, + uint32_t *intc_phandles, + uint32_t msi_phandle, + bool m_mode, uint32_t imsic_guest_bits) +{ + int cpu, socket; + g_autofree char *imsic_name = NULL; + MachineState *ms = MACHINE(s); + int socket_count = riscv_socket_count(ms); + uint32_t imsic_max_hart_per_socket, imsic_addr, imsic_size; + g_autofree uint32_t *imsic_cells = NULL; + g_autofree uint32_t *imsic_regs = NULL; + static const char * const imsic_compat[2] = { + "qemu,imsics", "riscv,imsics" + }; + + imsic_cells = g_new0(uint32_t, ms->smp.cpus * 2); + imsic_regs = g_new0(uint32_t, socket_count * 4); + + for (cpu = 0; cpu < ms->smp.cpus; cpu++) { + imsic_cells[cpu * 2 + 0] = cpu_to_be32(intc_phandles[cpu]); + imsic_cells[cpu * 2 + 1] = cpu_to_be32(m_mode ? IRQ_M_EXT : IRQ_S_EXT); + } + + imsic_max_hart_per_socket = 0; + for (socket = 0; socket < socket_count; socket++) { + imsic_addr = base_addr + socket * RVSERVER_IMSIC_GROUP_MAX_SIZE; + imsic_size = IMSIC_HART_SIZE(imsic_guest_bits) * + s->soc[socket].num_harts; + imsic_regs[socket * 4 + 0] = 0; + imsic_regs[socket * 4 + 1] = cpu_to_be32(imsic_addr); + imsic_regs[socket * 4 + 2] = 0; + imsic_regs[socket * 4 + 3] = cpu_to_be32(imsic_size); + if (imsic_max_hart_per_socket < s->soc[socket].num_harts) { + imsic_max_hart_per_socket = s->soc[socket].num_harts; + } + } + + imsic_name = g_strdup_printf("/soc/interrupt-controller@%lx", + (unsigned long)base_addr); + qemu_fdt_add_subnode(ms->fdt, imsic_name); + qemu_fdt_setprop_string_array(ms->fdt, imsic_name, "compatible", + (char **)&imsic_compat, + ARRAY_SIZE(imsic_compat)); + + qemu_fdt_setprop_cell(ms->fdt, imsic_name, "#interrupt-cells", + FDT_IMSIC_INT_CELLS); + qemu_fdt_setprop(ms->fdt, imsic_name, "interrupt-controller", NULL, 0); + qemu_fdt_setprop(ms->fdt, imsic_name, "msi-controller", NULL, 0); + qemu_fdt_setprop(ms->fdt, imsic_name, "interrupts-extended", + imsic_cells, ms->smp.cpus * sizeof(uint32_t) * 2); + qemu_fdt_setprop(ms->fdt, imsic_name, "reg", imsic_regs, + socket_count * sizeof(uint32_t) * 4); + qemu_fdt_setprop_cell(ms->fdt, imsic_name, "riscv,num-ids", + RVSERVER_IRQCHIP_NUM_MSIS); + + if (imsic_guest_bits) { + qemu_fdt_setprop_cell(ms->fdt, imsic_name, "riscv,guest-index-bits", + imsic_guest_bits); + } + + if (socket_count > 1) { + qemu_fdt_setprop_cell(ms->fdt, imsic_name, "riscv,hart-index-bits", + imsic_num_bits(imsic_max_hart_per_socket)); + qemu_fdt_setprop_cell(ms->fdt, imsic_name, "riscv,group-index-bits", + imsic_num_bits(socket_count)); + qemu_fdt_setprop_cell(ms->fdt, imsic_name, "riscv,group-index-shift", + IMSIC_MMIO_GROUP_MIN_SHIFT); + } + qemu_fdt_setprop_cell(ms->fdt, imsic_name, "phandle", msi_phandle); +} + +static void create_fdt_imsic(RISCVServerRefMachineState *s, + const MemMapEntry *memmap, + uint32_t *phandle, uint32_t *intc_phandles, + uint32_t *msi_m_phandle, uint32_t *msi_s_phandle) +{ + *msi_m_phandle = (*phandle)++; + *msi_s_phandle = (*phandle)++; + + /* M-level IMSIC node */ + create_fdt_one_imsic(s, memmap[RVSERVER_IMSIC_M].base, intc_phandles, + *msi_m_phandle, true, 0); + + /* S-level IMSIC node */ + create_fdt_one_imsic(s, memmap[RVSERVER_IMSIC_S].base, intc_phandles, + *msi_s_phandle, false, + imsic_num_bits(s->aia_guests + 1)); + +} + +/* Caller must free string after use. Copied from hw/riscv/virt.c. */ +static char *fdt_get_aplic_nodename(unsigned long aplic_addr) +{ + return g_strdup_printf("/soc/interrupt-controller@%lx", aplic_addr); +} + +static void create_fdt_one_aplic(RISCVServerRefMachineState *s, int socket, + unsigned long aplic_addr, uint32_t aplic_size, + uint32_t msi_phandle, + uint32_t *intc_phandles, + uint32_t aplic_phandle, + uint32_t aplic_child_phandle, + bool m_mode, int num_harts) +{ + int cpu; + g_autofree char *aplic_name = fdt_get_aplic_nodename(aplic_addr); + g_autofree uint32_t *aplic_cells = g_new0(uint32_t, num_harts * 2); + MachineState *ms = MACHINE(s); + static const char * const aplic_compat[2] = { + "qemu,aplic", "riscv,aplic" + }; + + for (cpu = 0; cpu < num_harts; cpu++) { + aplic_cells[cpu * 2 + 0] = cpu_to_be32(intc_phandles[cpu]); + aplic_cells[cpu * 2 + 1] = cpu_to_be32(m_mode ? IRQ_M_EXT : IRQ_S_EXT); + } + + qemu_fdt_add_subnode(ms->fdt, aplic_name); + qemu_fdt_setprop_string_array(ms->fdt, aplic_name, "compatible", + (char **)&aplic_compat, + ARRAY_SIZE(aplic_compat)); + qemu_fdt_setprop_cell(ms->fdt, aplic_name, "#address-cells", + FDT_APLIC_ADDR_CELLS); + qemu_fdt_setprop_cell(ms->fdt, aplic_name, + "#interrupt-cells", FDT_APLIC_INT_CELLS); + qemu_fdt_setprop(ms->fdt, aplic_name, "interrupt-controller", NULL, 0); + + qemu_fdt_setprop_cell(ms->fdt, aplic_name, "msi-parent", msi_phandle); + + qemu_fdt_setprop_cells(ms->fdt, aplic_name, "reg", + 0x0, aplic_addr, 0x0, aplic_size); + qemu_fdt_setprop_cell(ms->fdt, aplic_name, "riscv,num-sources", + RVSERVER_IRQCHIP_NUM_SOURCES); + + if (aplic_child_phandle) { + qemu_fdt_setprop_cell(ms->fdt, aplic_name, "riscv,children", + aplic_child_phandle); + qemu_fdt_setprop_cells(ms->fdt, aplic_name, "riscv,delegate", + aplic_child_phandle, 0x1, + RVSERVER_IRQCHIP_NUM_SOURCES); + } + + riscv_socket_fdt_write_id(ms, aplic_name, socket); + qemu_fdt_setprop_cell(ms->fdt, aplic_name, "phandle", aplic_phandle); +} + +static void create_fdt_socket_aplic(RISCVServerRefMachineState *s, + const MemMapEntry *memmap, int socket, + uint32_t msi_m_phandle, + uint32_t msi_s_phandle, + uint32_t *phandle, + uint32_t *intc_phandles, + uint32_t *aplic_phandles, + int num_harts) +{ + unsigned long aplic_addr; + uint32_t aplic_m_phandle, aplic_s_phandle; + + aplic_m_phandle = (*phandle)++; + aplic_s_phandle = (*phandle)++; + + /* M-level APLIC node */ + aplic_addr = memmap[RVSERVER_APLIC_M].base + + memmap[RVSERVER_APLIC_M].size * socket; + create_fdt_one_aplic(s, socket, aplic_addr, memmap[RVSERVER_APLIC_M].size, + msi_m_phandle, intc_phandles, + aplic_m_phandle, aplic_s_phandle, + true, num_harts); + + /* S-level APLIC node */ + aplic_addr = memmap[RVSERVER_APLIC_S].base + + memmap[RVSERVER_APLIC_S].size * socket; + create_fdt_one_aplic(s, socket, aplic_addr, memmap[RVSERVER_APLIC_S].size, + msi_s_phandle, intc_phandles, + aplic_s_phandle, 0, + false, num_harts); + + if (socket == 0) { + g_autofree char *aplic_name = fdt_get_aplic_nodename(aplic_addr); + MachineState *ms = MACHINE(s); + + platform_bus_add_all_fdt_nodes(ms->fdt, aplic_name, + s->memmap[RVSERVER_PLATFORM_BUS].base, + s->memmap[RVSERVER_PLATFORM_BUS].size, + RVSERVER_PLATFORM_BUS_IRQ); + } + + aplic_phandles[socket] = aplic_s_phandle; +} + +static void create_fdt_pmu(RISCVServerRefMachineState *s) +{ + g_autofree char *pmu_name = g_strdup_printf("/pmu"); + MachineState *ms = MACHINE(s); + RISCVCPU *hart = &s->soc[0].harts[0]; + + qemu_fdt_add_subnode(ms->fdt, pmu_name); + qemu_fdt_setprop_string(ms->fdt, pmu_name, "compatible", "riscv,pmu"); + riscv_pmu_generate_fdt_node(ms->fdt, hart->pmu_avail_ctrs, pmu_name); +} + +static void create_fdt_sockets(RISCVServerRefMachineState *s, + const MemMapEntry *memmap, + uint32_t *phandle, + uint32_t *irq_mmio_phandle, + uint32_t *irq_pcie_phandle, + uint32_t *msi_pcie_phandle) +{ + int socket, phandle_pos; + MachineState *ms = MACHINE(s); + uint32_t msi_m_phandle = 0, msi_s_phandle = 0; + uint32_t xplic_phandles[MAX_NODES]; + g_autofree uint32_t *intc_phandles = NULL; + int socket_count = riscv_socket_count(ms); + + qemu_fdt_add_subnode(ms->fdt, "/cpus"); + qemu_fdt_setprop_cell(ms->fdt, "/cpus", "timebase-frequency", + RISCV_ACLINT_DEFAULT_TIMEBASE_FREQ); + qemu_fdt_setprop_cell(ms->fdt, "/cpus", "#size-cells", 0x0); + qemu_fdt_setprop_cell(ms->fdt, "/cpus", "#address-cells", 0x1); + qemu_fdt_add_subnode(ms->fdt, "/cpus/cpu-map"); + + intc_phandles = g_new0(uint32_t, ms->smp.cpus); + + phandle_pos = ms->smp.cpus; + for (socket = (socket_count - 1); socket >= 0; socket--) { + g_autofree char *clust_name = NULL; + phandle_pos -= s->soc[socket].num_harts; + + clust_name = g_strdup_printf("/cpus/cpu-map/cluster%d", socket); + qemu_fdt_add_subnode(ms->fdt, clust_name); + + create_fdt_socket_cpus(s, socket, clust_name, phandle, + &intc_phandles[phandle_pos]); + + create_fdt_socket_memory(s, memmap, socket); + + create_fdt_socket_aclint(s, memmap, socket, + &intc_phandles[phandle_pos]); + } + + create_fdt_imsic(s, memmap, phandle, intc_phandles, + &msi_m_phandle, &msi_s_phandle); + *msi_pcie_phandle = msi_s_phandle; + + phandle_pos = ms->smp.cpus; + for (socket = (socket_count - 1); socket >= 0; socket--) { + phandle_pos -= s->soc[socket].num_harts; + + create_fdt_socket_aplic(s, memmap, socket, + msi_m_phandle, msi_s_phandle, phandle, + &intc_phandles[phandle_pos], + xplic_phandles, + s->soc[socket].num_harts); + } + + for (socket = 0; socket < socket_count; socket++) { + if (socket == 0) { + *irq_mmio_phandle = xplic_phandles[socket]; + *irq_pcie_phandle = xplic_phandles[socket]; + } + if (socket == 1) { + *irq_pcie_phandle = xplic_phandles[socket]; + } + } + + riscv_socket_fdt_write_distance_matrix(ms); +} + +static void create_fdt_iommu_sys(RISCVServerRefMachineState *s, + uint32_t irq_chip, + uint32_t msi_phandle, + uint32_t *iommu_sys_phandle) +{ + const char comp[] = "riscv,iommu"; + void *fdt = MACHINE(s)->fdt; + uint32_t iommu_phandle; + g_autofree char *iommu_node = NULL; + hwaddr addr = s->memmap[RVSERVER_IOMMU_SYS].base; + hwaddr size = s->memmap[RVSERVER_IOMMU_SYS].size; + uint32_t iommu_irq_map[RISCV_IOMMU_INTR_COUNT] = { + IOMMU_SYS_IRQ + RISCV_IOMMU_INTR_CQ, + IOMMU_SYS_IRQ + RISCV_IOMMU_INTR_FQ, + IOMMU_SYS_IRQ + RISCV_IOMMU_INTR_PM, + IOMMU_SYS_IRQ + RISCV_IOMMU_INTR_PQ, + }; + + iommu_node = g_strdup_printf("/soc/iommu@%"HWADDR_PRIx, + s->memmap[RVSERVER_IOMMU_SYS].base); + iommu_phandle = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_add_subnode(fdt, iommu_node); + + qemu_fdt_setprop(fdt, iommu_node, "compatible", comp, sizeof(comp)); + qemu_fdt_setprop_cell(fdt, iommu_node, "#iommu-cells", 1); + qemu_fdt_setprop_cell(fdt, iommu_node, "phandle", iommu_phandle); + + qemu_fdt_setprop_cells(fdt, iommu_node, "reg", + addr >> 32, addr, size >> 32, size); + qemu_fdt_setprop_cell(fdt, iommu_node, "interrupt-parent", irq_chip); + + qemu_fdt_setprop_cells(fdt, iommu_node, "interrupts", + iommu_irq_map[0], FDT_IRQ_TYPE_EDGE_LOW, + iommu_irq_map[1], FDT_IRQ_TYPE_EDGE_LOW, + iommu_irq_map[2], FDT_IRQ_TYPE_EDGE_LOW, + iommu_irq_map[3], FDT_IRQ_TYPE_EDGE_LOW); + + qemu_fdt_setprop_cell(fdt, iommu_node, "msi-parent", msi_phandle); + + *iommu_sys_phandle = iommu_phandle; +} + +static void create_fdt_pcie(RISCVServerRefMachineState *s, + const MemMapEntry *memmap, + uint32_t irq_pcie_phandle, + uint32_t msi_pcie_phandle, + uint32_t iommu_sys_phandle) +{ + g_autofree char *name = NULL; + MachineState *ms = MACHINE(s); + + name = g_strdup_printf("/soc/pci@%"HWADDR_PRIx, + memmap[RVSERVER_PCIE_ECAM].base); + qemu_fdt_add_subnode(ms->fdt, name); + qemu_fdt_setprop_cell(ms->fdt, name, "#address-cells", + FDT_PCI_ADDR_CELLS); + qemu_fdt_setprop_cell(ms->fdt, name, "#interrupt-cells", + FDT_PCI_INT_CELLS); + qemu_fdt_setprop_cell(ms->fdt, name, "#size-cells", 0x2); + qemu_fdt_setprop_string(ms->fdt, name, "compatible", + "pci-host-ecam-generic"); + qemu_fdt_setprop_string(ms->fdt, name, "device_type", "pci"); + qemu_fdt_setprop_cell(ms->fdt, name, "linux,pci-domain", 0); + qemu_fdt_setprop_cells(ms->fdt, name, "bus-range", 0, + memmap[RVSERVER_PCIE_ECAM].size / PCIE_MMCFG_SIZE_MIN - 1); + qemu_fdt_setprop(ms->fdt, name, "dma-coherent", NULL, 0); + qemu_fdt_setprop_cell(ms->fdt, name, "msi-parent", msi_pcie_phandle); + qemu_fdt_setprop_cells(ms->fdt, name, "reg", 0, + memmap[RVSERVER_PCIE_ECAM].base, 0, memmap[RVSERVER_PCIE_ECAM].size); + qemu_fdt_setprop_sized_cells(ms->fdt, name, "ranges", + 1, FDT_PCI_RANGE_IOPORT, 2, 0, + 2, memmap[RVSERVER_PCIE_PIO].base, 2, memmap[RVSERVER_PCIE_PIO].size, + 1, FDT_PCI_RANGE_MMIO, + 2, memmap[RVSERVER_PCIE_MMIO].base, + 2, memmap[RVSERVER_PCIE_MMIO].base, 2, memmap[RVSERVER_PCIE_MMIO].size, + 1, FDT_PCI_RANGE_MMIO_64BIT, + 2, memmap[RVSERVER_PCIE_MMIO_HIGH].base, + 2, memmap[RVSERVER_PCIE_MMIO_HIGH].base, 2, + memmap[RVSERVER_PCIE_MMIO_HIGH].size); + + create_pcie_irq_map(s, ms->fdt, name, irq_pcie_phandle); + + qemu_fdt_setprop_cells(ms->fdt, name, "iommu-map", + 0, iommu_sys_phandle, 0, 0x10000); +} + +static void create_fdt_reset(RISCVServerRefMachineState *s, + const MemMapEntry *memmap, + uint32_t *phandle) +{ + char *name; + uint32_t test_phandle; + MachineState *ms = MACHINE(s); + + test_phandle = (*phandle)++; + name = g_strdup_printf("/soc/reset_syscon@%"HWADDR_PRIx, + memmap[RVSERVER_RESET_SYSCON].base); + qemu_fdt_add_subnode(ms->fdt, name); + qemu_fdt_setprop_string(ms->fdt, name, "compatible", "syscon"); + qemu_fdt_setprop_cells(ms->fdt, name, "reg", + 0x0, memmap[RVSERVER_RESET_SYSCON].base, + 0x0, memmap[RVSERVER_RESET_SYSCON].size); + qemu_fdt_setprop_cell(ms->fdt, name, "phandle", test_phandle); + test_phandle = qemu_fdt_get_phandle(ms->fdt, name); + g_free(name); + + name = g_strdup_printf("/soc/reboot"); + qemu_fdt_add_subnode(ms->fdt, name); + qemu_fdt_setprop_string(ms->fdt, name, "compatible", "syscon-reboot"); + qemu_fdt_setprop_cell(ms->fdt, name, "regmap", test_phandle); + qemu_fdt_setprop_cell(ms->fdt, name, "offset", 0x0); + qemu_fdt_setprop_cell(ms->fdt, name, "value", SYSCON_RESET); + g_free(name); + + name = g_strdup_printf("/soc/poweroff"); + qemu_fdt_add_subnode(ms->fdt, name); + qemu_fdt_setprop_string(ms->fdt, name, "compatible", "syscon-poweroff"); + qemu_fdt_setprop_cell(ms->fdt, name, "regmap", test_phandle); + qemu_fdt_setprop_cell(ms->fdt, name, "offset", 0x0); + qemu_fdt_setprop_cell(ms->fdt, name, "value", SYSCON_POWEROFF); + g_free(name); +} + +static void create_fdt_uart(RISCVServerRefMachineState *s, + const MemMapEntry *memmap, + uint32_t irq_mmio_phandle) +{ + g_autofree char *name = NULL; + MachineState *ms = MACHINE(s); + + name = g_strdup_printf("/soc/serial@%"HWADDR_PRIx, + memmap[RVSERVER_UART0].base); + qemu_fdt_add_subnode(ms->fdt, name); + qemu_fdt_setprop_string(ms->fdt, name, "compatible", "ns16550a"); + qemu_fdt_setprop_cells(ms->fdt, name, "reg", + 0x0, memmap[RVSERVER_UART0].base, + 0x0, memmap[RVSERVER_UART0].size); + qemu_fdt_setprop_cell(ms->fdt, name, "clock-frequency", 3686400); + qemu_fdt_setprop_cell(ms->fdt, name, "interrupt-parent", + irq_mmio_phandle); + qemu_fdt_setprop_cells(ms->fdt, name, "interrupts", + RVSERVER_UART0_IRQ, 0x4); + + qemu_fdt_setprop_string(ms->fdt, "/chosen", "stdout-path", name); +} + +static void create_fdt_rtc(RISCVServerRefMachineState *s, + const MemMapEntry *memmap, + uint32_t irq_mmio_phandle) +{ + g_autofree char *name = NULL; + MachineState *ms = MACHINE(s); + + name = g_strdup_printf("/soc/rtc@%"HWADDR_PRIx, + memmap[RVSERVER_RTC].base); + qemu_fdt_add_subnode(ms->fdt, name); + qemu_fdt_setprop_string(ms->fdt, name, "compatible", + "google,goldfish-rtc"); + qemu_fdt_setprop_cells(ms->fdt, name, "reg", + 0x0, memmap[RVSERVER_RTC].base, 0x0, memmap[RVSERVER_RTC].size); + qemu_fdt_setprop_cell(ms->fdt, name, "interrupt-parent", + irq_mmio_phandle); + qemu_fdt_setprop_cells(ms->fdt, name, "interrupts", + RVSERVER_RTC_IRQ, 0x4); +} + +static void create_fdt_flash(RISCVServerRefMachineState *s, + const MemMapEntry *memmap) +{ + MachineState *ms = MACHINE(s); + hwaddr flashsize = rvserver_ref_memmap[RVSERVER_FLASH].size / 2; + hwaddr flashbase = rvserver_ref_memmap[RVSERVER_FLASH].base; + g_autofree char *name = g_strdup_printf("/flash@%"HWADDR_PRIx, flashbase); + + qemu_fdt_add_subnode(ms->fdt, name); + qemu_fdt_setprop_string(ms->fdt, name, "compatible", "cfi-flash"); + qemu_fdt_setprop_sized_cells(ms->fdt, name, "reg", + 2, flashbase, 2, flashsize, + 2, flashbase + flashsize, 2, flashsize); + qemu_fdt_setprop_cell(ms->fdt, name, "bank-width", 4); +} + +static void finalize_fdt(RISCVServerRefMachineState *s) +{ + uint32_t phandle = 1, irq_mmio_phandle = 1, msi_pcie_phandle = 1; + uint32_t irq_pcie_phandle = 1, iommu_sys_phandle; + + create_fdt_sockets(s, rvserver_ref_memmap, &phandle, &irq_mmio_phandle, + &irq_pcie_phandle, &msi_pcie_phandle); + + create_fdt_iommu_sys(s, irq_mmio_phandle, msi_pcie_phandle, + &iommu_sys_phandle); + + create_fdt_pcie(s, rvserver_ref_memmap, irq_pcie_phandle, + msi_pcie_phandle, iommu_sys_phandle); + + create_fdt_reset(s, rvserver_ref_memmap, &phandle); + + create_fdt_uart(s, rvserver_ref_memmap, irq_mmio_phandle); + + create_fdt_rtc(s, rvserver_ref_memmap, irq_mmio_phandle); +} + +static void create_fdt(RISCVServerRefMachineState *s, + const MemMapEntry *memmap) +{ + MachineState *ms = MACHINE(s); + uint8_t rng_seed[32]; + + ms->fdt = create_device_tree(&s->fdt_size); + if (!ms->fdt) { + error_report("create_device_tree() failed"); + exit(1); + } + + qemu_fdt_setprop_string(ms->fdt, "/", "model", "qemu,riscv-server-ref"); + qemu_fdt_setprop_string(ms->fdt, "/", "compatible", "riscv-server-ref"); + qemu_fdt_setprop_cell(ms->fdt, "/", "#size-cells", 0x2); + qemu_fdt_setprop_cell(ms->fdt, "/", "#address-cells", 0x2); + + /* + * This versioning scheme is for informing platform fw only. It is neither: + * - A QEMU versioned machine type; a given version of QEMU will emulate + * a given version of the platform. + * - A reflection of level of server platform support provided. + * + * machine-version-major: updated when changes breaking fw compatibility + * are introduced. + * machine-version-minor: updated when features are added that don't break + * fw compatibility. + * + * It's the same as the scheme in arm sbsa-ref. + */ + qemu_fdt_setprop_cell(ms->fdt, "/", "machine-version-major", 0); + qemu_fdt_setprop_cell(ms->fdt, "/", "machine-version-minor", 0); + + qemu_fdt_add_subnode(ms->fdt, "/soc"); + qemu_fdt_setprop(ms->fdt, "/soc", "ranges", NULL, 0); + qemu_fdt_setprop_string(ms->fdt, "/soc", "compatible", "simple-bus"); + qemu_fdt_setprop_cell(ms->fdt, "/soc", "#size-cells", 0x2); + qemu_fdt_setprop_cell(ms->fdt, "/soc", "#address-cells", 0x2); + + qemu_fdt_add_subnode(ms->fdt, "/chosen"); + + /* Pass seed to RNG */ + qemu_guest_getrandom_nofail(rng_seed, sizeof(rng_seed)); + qemu_fdt_setprop(ms->fdt, "/chosen", "rng-seed", + rng_seed, sizeof(rng_seed)); + + create_fdt_flash(s, memmap); + create_fdt_pmu(s); + +} + +static inline DeviceState *gpex_pcie_init(MemoryRegion *sys_mem, + DeviceState *irqchip, + RISCVServerRefMachineState *s) +{ + DeviceState *dev; + PCIHostState *pci; + PCIDevice *pdev_ahci; + AHCIPCIState *ich9; + DriveInfo *hd[NUM_SATA_PORTS]; + MemoryRegion *ecam_alias, *ecam_reg; + MemoryRegion *mmio_alias, *high_mmio_alias, *mmio_reg; + hwaddr ecam_base = rvserver_ref_memmap[RVSERVER_PCIE_ECAM].base; + hwaddr ecam_size = rvserver_ref_memmap[RVSERVER_PCIE_ECAM].size; + hwaddr mmio_base = rvserver_ref_memmap[RVSERVER_PCIE_MMIO].base; + hwaddr mmio_size = rvserver_ref_memmap[RVSERVER_PCIE_MMIO].size; + hwaddr high_mmio_base = rvserver_ref_memmap[RVSERVER_PCIE_MMIO_HIGH].base; + hwaddr high_mmio_size = rvserver_ref_memmap[RVSERVER_PCIE_MMIO_HIGH].size; + hwaddr pio_base = rvserver_ref_memmap[RVSERVER_PCIE_PIO].base; + hwaddr pio_size = rvserver_ref_memmap[RVSERVER_PCIE_PIO].size; + MachineClass *mc = MACHINE_GET_CLASS(s); + qemu_irq irq; + int i; + + dev = qdev_new(TYPE_GPEX_HOST); + + object_property_set_uint(OBJECT(GPEX_HOST(dev)), PCI_HOST_ECAM_BASE, + ecam_base, NULL); + object_property_set_int(OBJECT(GPEX_HOST(dev)), PCI_HOST_ECAM_SIZE, + ecam_size, NULL); + object_property_set_uint(OBJECT(GPEX_HOST(dev)), + PCI_HOST_BELOW_4G_MMIO_BASE, + mmio_base, NULL); + object_property_set_int(OBJECT(GPEX_HOST(dev)), + PCI_HOST_BELOW_4G_MMIO_SIZE, + mmio_size, NULL); + object_property_set_uint(OBJECT(GPEX_HOST(dev)), + PCI_HOST_ABOVE_4G_MMIO_BASE, + high_mmio_base, NULL); + object_property_set_int(OBJECT(GPEX_HOST(dev)), + PCI_HOST_ABOVE_4G_MMIO_SIZE, + high_mmio_size, NULL); + object_property_set_uint(OBJECT(GPEX_HOST(dev)), PCI_HOST_PIO_BASE, + pio_base, NULL); + object_property_set_int(OBJECT(GPEX_HOST(dev)), PCI_HOST_PIO_SIZE, + pio_size, NULL); + + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + + ecam_alias = g_new0(MemoryRegion, 1); + ecam_reg = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0); + memory_region_init_alias(ecam_alias, OBJECT(dev), "pcie-ecam", + ecam_reg, 0, ecam_size); + memory_region_add_subregion(get_system_memory(), ecam_base, ecam_alias); + + mmio_alias = g_new0(MemoryRegion, 1); + mmio_reg = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 1); + memory_region_init_alias(mmio_alias, OBJECT(dev), "pcie-mmio", + mmio_reg, mmio_base, mmio_size); + memory_region_add_subregion(get_system_memory(), mmio_base, mmio_alias); + + /* Map high MMIO space */ + high_mmio_alias = g_new0(MemoryRegion, 1); + memory_region_init_alias(high_mmio_alias, OBJECT(dev), "pcie-mmio-high", + mmio_reg, high_mmio_base, high_mmio_size); + memory_region_add_subregion(get_system_memory(), high_mmio_base, + high_mmio_alias); + + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 2, pio_base); + + for (i = 0; i < PCI_NUM_PINS; i++) { + irq = qdev_get_gpio_in(irqchip, RVSERVER_PCIE_IRQ + i); + + sysbus_connect_irq(SYS_BUS_DEVICE(dev), i, irq); + gpex_set_irq_num(GPEX_HOST(dev), i, RVSERVER_PCIE_IRQ + i); + } + + pci = PCI_HOST_BRIDGE(dev); + pci_init_nic_devices(pci->bus, mc->default_nic); + /* IDE disk setup. */ + pdev_ahci = pci_create_simple(pci->bus, -1, TYPE_ICH9_AHCI); + ich9 = ICH9_AHCI(pdev_ahci); + g_assert(ARRAY_SIZE(hd) == ich9->ahci.ports); + ide_drive_get(hd, ich9->ahci.ports); + ahci_ide_create_devs(&ich9->ahci, hd); + + GPEX_HOST(dev)->gpex_cfg.bus = PCI_HOST_BRIDGE(GPEX_HOST(dev))->bus; + return dev; +} + +static DeviceState *rvserver_ref_create_aia(int aia_guests, + const MemMapEntry *memmap, + int socket, + int base_hartid, int hart_count) +{ + int i; + hwaddr addr; + uint32_t guest_bits; + DeviceState *aplic_s = NULL; + DeviceState *aplic_m = NULL; + bool msimode = true; + + /* Per-socket M-level IMSICs */ + addr = memmap[RVSERVER_IMSIC_M].base + + socket * RVSERVER_IMSIC_GROUP_MAX_SIZE; + for (i = 0; i < hart_count; i++) { + riscv_imsic_create(addr + i * IMSIC_HART_SIZE(0), + base_hartid + i, true, 1, + RVSERVER_IRQCHIP_NUM_MSIS); + } + + /* Per-socket S-level IMSICs */ + guest_bits = imsic_num_bits(aia_guests + 1); + addr = memmap[RVSERVER_IMSIC_S].base + + socket * RVSERVER_IMSIC_GROUP_MAX_SIZE; + for (i = 0; i < hart_count; i++) { + riscv_imsic_create(addr + i * IMSIC_HART_SIZE(guest_bits), + base_hartid + i, false, 1 + aia_guests, + RVSERVER_IRQCHIP_NUM_MSIS); + } + + /* Per-socket M-level APLIC */ + aplic_m = riscv_aplic_create(memmap[RVSERVER_APLIC_M].base + + socket * memmap[RVSERVER_APLIC_M].size, + memmap[RVSERVER_APLIC_M].size, + (msimode) ? 0 : base_hartid, + (msimode) ? 0 : hart_count, + RVSERVER_IRQCHIP_NUM_SOURCES, + RVSERVER_IRQCHIP_NUM_PRIO_BITS, + msimode, true, NULL); + + /* Per-socket S-level APLIC */ + aplic_s = riscv_aplic_create(memmap[RVSERVER_APLIC_S].base + + socket * memmap[RVSERVER_APLIC_S].size, + memmap[RVSERVER_APLIC_S].size, + (msimode) ? 0 : base_hartid, + (msimode) ? 0 : hart_count, + RVSERVER_IRQCHIP_NUM_SOURCES, + RVSERVER_IRQCHIP_NUM_PRIO_BITS, + msimode, false, aplic_m); + + (void)aplic_s; + return aplic_m; +} + +static uint64_t rvserver_reset_syscon_read(void *opaque, hwaddr addr, + unsigned size) +{ + return 0; +} + +static void rvserver_reset_syscon_write(void *opaque, hwaddr addr, + uint64_t val64, unsigned int size) +{ + switch (val64) { + case SYSCON_POWEROFF: + qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN); + return; + case SYSCON_RESET: + qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); + return; + default: + break; + } +} + +static const MemoryRegionOps rvserver_reset_syscon_ops = { + .read = rvserver_reset_syscon_read, + .write = rvserver_reset_syscon_write, + .endianness = DEVICE_LITTLE_ENDIAN +}; + +static void rvserver_ref_machine_done(Notifier *notifier, void *data) +{ + RISCVServerRefMachineState *s = container_of( + notifier, RISCVServerRefMachineState, machine_done); + const MemMapEntry *memmap = rvserver_ref_memmap; + MachineState *machine = MACHINE(s); + hwaddr start_addr = memmap[RVSERVER_DRAM].base; + target_ulong firmware_end_addr, kernel_start_addr; + const char *firmware_name = riscv_default_firmware_name(&s->soc[0]); + uint64_t fdt_load_addr; + uint64_t kernel_entry = 0; + BlockBackend *pflash_blk0; + RISCVBootInfo boot_info; + + /* + * An user provided dtb must include everything, including + * dynamic sysbus devices. Our FDT needs to be finalized. + */ + if (machine->dtb == NULL) { + finalize_fdt(s); + } + + riscv_boot_info_init(&boot_info, &s->soc[0]); + + firmware_end_addr = riscv_find_and_load_firmware(machine, &boot_info, + firmware_name, + &start_addr, NULL); + + pflash_blk0 = pflash_cfi01_get_blk(s->flash[0]); + if (pflash_blk0) { + if (machine->firmware && !strcmp(machine->firmware, "none")) { + /* + * Pflash was supplied but bios is none and not KVM guest, + * let's overwrite the address we jump to after reset to + * the base of the flash. + */ + start_addr = rvserver_ref_memmap[RVSERVER_FLASH].base; + } else { + /* + * Pflash was supplied but either KVM guest or bios is not none. + * In this case, base of the flash would contain S-mode payload. + */ + riscv_setup_firmware_boot(machine); + kernel_entry = rvserver_ref_memmap[RVSERVER_FLASH].base; + } + } + + if (machine->kernel_filename && !kernel_entry) { + kernel_start_addr = riscv_calc_kernel_start_addr(&boot_info, + firmware_end_addr); + riscv_load_kernel(machine, &boot_info, kernel_start_addr, true, NULL); + kernel_entry = boot_info.image_low_addr; + } + + fdt_load_addr = riscv_compute_fdt_addr(memmap[RVSERVER_DRAM].base, + memmap[RVSERVER_DRAM].size, + machine, &boot_info); + + riscv_load_fdt(fdt_load_addr, machine->fdt); + + /* load the reset vector */ + riscv_setup_rom_reset_vec(machine, &s->soc[0], start_addr, + rvserver_ref_memmap[RVSERVER_MROM].base, + rvserver_ref_memmap[RVSERVER_MROM].size, + kernel_entry, + fdt_load_addr); + +} + +static bool rvserver_aclint_allowed(void) +{ + return tcg_enabled() || qtest_enabled(); +} + +static void rvserver_ref_machine_init(MachineState *machine) +{ + const MemMapEntry *memmap = rvserver_ref_memmap; + RISCVServerRefMachineState *s = RISCV_SERVER_REF_MACHINE(machine); + MemoryRegion *system_memory = get_system_memory(); + MemoryRegion *mask_rom = g_new(MemoryRegion, 1); + MemoryRegion *reset_syscon_io = g_new(MemoryRegion, 1); + DeviceState *mmio_irqchip, *pcie_irqchip, *iommu_sys; + int i, base_hartid, hart_count; + int socket_count = riscv_socket_count(machine); + + /* Check socket count limit */ + if (RVSERVER_SOCKETS_MAX < socket_count) { + error_report("number of sockets/nodes should be less than %d", + RVSERVER_SOCKETS_MAX); + exit(1); + } + + if (!rvserver_aclint_allowed()) { + error_report("'aclint' is only available with TCG acceleration"); + exit(1); + } + + /* Initialize sockets */ + mmio_irqchip = pcie_irqchip = NULL; + for (i = 0; i < socket_count; i++) { + g_autofree char *soc_name = g_strdup_printf("soc%d", i); + + if (!riscv_socket_check_hartids(machine, i)) { + error_report("discontinuous hartids in socket%d", i); + exit(1); + } + + base_hartid = riscv_socket_first_hartid(machine, i); + if (base_hartid < 0) { + error_report("can't find hartid base for socket%d", i); + exit(1); + } + + hart_count = riscv_socket_hart_count(machine, i); + if (hart_count < 0) { + error_report("can't find hart count for socket%d", i); + exit(1); + } + + object_initialize_child(OBJECT(machine), soc_name, &s->soc[i], + TYPE_RISCV_HART_ARRAY); + object_property_set_str(OBJECT(&s->soc[i]), "cpu-type", + machine->cpu_type, &error_abort); + object_property_set_int(OBJECT(&s->soc[i]), "hartid-base", + base_hartid, &error_abort); + object_property_set_int(OBJECT(&s->soc[i]), "num-harts", + hart_count, &error_abort); + sysbus_realize(SYS_BUS_DEVICE(&s->soc[i]), &error_fatal); + + /* Per-socket ACLINT MTIMER */ + riscv_aclint_mtimer_create(memmap[RVSERVER_ACLINT].base + + i * RISCV_ACLINT_DEFAULT_MTIMER_SIZE, + RISCV_ACLINT_DEFAULT_MTIMER_SIZE, + base_hartid, hart_count, + RISCV_ACLINT_DEFAULT_MTIMECMP, + RISCV_ACLINT_DEFAULT_MTIME, + RISCV_ACLINT_DEFAULT_TIMEBASE_FREQ, true); + + /* Per-socket interrupt controller */ + s->irqchip[i] = rvserver_ref_create_aia(s->aia_guests, + memmap, i, base_hartid, + hart_count); + + /* Try to use different IRQCHIP instance based device type */ + if (i == 0) { + mmio_irqchip = s->irqchip[i]; + pcie_irqchip = s->irqchip[i]; + } + if (i == 1) { + pcie_irqchip = s->irqchip[i]; + } + } + + s->memmap = rvserver_ref_memmap; + + /* register system main memory (actual RAM) */ + memory_region_add_subregion(system_memory, memmap[RVSERVER_DRAM].base, + machine->ram); + + /* boot rom */ + memory_region_init_rom(mask_rom, NULL, "riscv_rvserver_ref_board.mrom", + memmap[RVSERVER_MROM].size, &error_fatal); + memory_region_add_subregion(system_memory, memmap[RVSERVER_MROM].base, + mask_rom); + + memory_region_init_io(reset_syscon_io, NULL, &rvserver_reset_syscon_ops, + NULL, "reset_syscon_io", + memmap[RVSERVER_RESET_SYSCON].size); + memory_region_add_subregion(system_memory, + memmap[RVSERVER_RESET_SYSCON].base, + reset_syscon_io); + + gpex_pcie_init(system_memory, pcie_irqchip, s); + + create_platform_bus(s, mmio_irqchip); + + serial_mm_init(system_memory, memmap[RVSERVER_UART0].base, + 0, qdev_get_gpio_in(mmio_irqchip, RVSERVER_UART0_IRQ), 399193, + serial_hd(0), DEVICE_LITTLE_ENDIAN); + + sysbus_create_simple("goldfish_rtc", memmap[RVSERVER_RTC].base, + qdev_get_gpio_in(mmio_irqchip, RVSERVER_RTC_IRQ)); + + for (i = 0; i < ARRAY_SIZE(s->flash); i++) { + /* Map legacy -drive if=pflash to machine properties */ + pflash_cfi01_legacy_drive(s->flash[i], + drive_get(IF_PFLASH, 0, i)); + } + rvserver_flash_maps(s, system_memory); + + /* load/create device tree */ + if (machine->dtb) { + machine->fdt = load_device_tree(machine->dtb, &s->fdt_size); + if (!machine->fdt) { + error_report("load_device_tree() failed"); + exit(1); + } + } else { + create_fdt(s, memmap); + } + + iommu_sys = qdev_new(TYPE_RISCV_IOMMU_SYS); + object_property_set_uint(OBJECT(iommu_sys), "addr", + s->memmap[RVSERVER_IOMMU_SYS].base, + &error_fatal); + + object_property_set_uint(OBJECT(iommu_sys), "base-irq", + IOMMU_SYS_IRQ, + &error_fatal); + + object_property_set_link(OBJECT(iommu_sys), "irqchip", + OBJECT(mmio_irqchip), + &error_fatal); + + sysbus_realize_and_unref(SYS_BUS_DEVICE(iommu_sys), &error_fatal); + + s->machine_done.notify = rvserver_ref_machine_done; + qemu_add_machine_init_done_notifier(&s->machine_done); +} + +static void rvserver_ref_machine_instance_init(Object *obj) +{ + RISCVServerRefMachineState *s = RISCV_SERVER_REF_MACHINE(obj); + + s->flash[0] = rvserver_flash_create(s, "riscv-server-ref.flash0", + "pflash0"); + s->flash[1] = rvserver_flash_create(s, "riscv-server-ref.flash1", + "pflash1"); +} + +static char *rvserver_ref_get_aia_guests(Object *obj, Error **errp) +{ + RISCVServerRefMachineState *s = RISCV_SERVER_REF_MACHINE(obj); + char val[32]; + + sprintf(val, "%d", s->aia_guests); + return g_strdup(val); +} + +static void rvserver_ref_set_aia_guests(Object *obj, const char *val, + Error **errp) +{ + RISCVServerRefMachineState *s = RISCV_SERVER_REF_MACHINE(obj); + + s->aia_guests = atoi(val); + if (s->aia_guests < 0 || s->aia_guests > RVSERVER_IRQCHIP_MAX_GUESTS) { + error_setg(errp, "Invalid number of AIA IMSIC guests"); + error_append_hint(errp, "Valid values be between 0 and %d.\n", + RVSERVER_IRQCHIP_MAX_GUESTS); + } +} + +static HotplugHandler *rvserver_machine_get_hotplug_handler(MachineState *ms, + DeviceState *dev) +{ + MachineClass *mc = MACHINE_GET_CLASS(ms); + + if (device_is_dynamic_sysbus(mc, dev)) { + return HOTPLUG_HANDLER(ms); + } + + return NULL; +} + +static void rvserver_machine_device_plug_cb(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + RISCVServerRefMachineState *s = RISCV_SERVER_REF_MACHINE(hotplug_dev); + + if (s->platform_bus_dev) { + MachineClass *mc = MACHINE_GET_CLASS(s); + + if (device_is_dynamic_sysbus(mc, dev)) { + platform_bus_link_device(PLATFORM_BUS_DEVICE(s->platform_bus_dev), + SYS_BUS_DEVICE(dev)); + } + } +} + +static void rvserver_ref_machine_class_init(ObjectClass *oc, const void *data) +{ + char str[128]; + MachineClass *mc = MACHINE_CLASS(oc); + HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(oc); + static const char * const valid_cpu_types[] = { + TYPE_RISCV_CPU_RVSERVER_REF, + }; + + mc->desc = "RISC-V Server SoC Reference board"; + mc->init = rvserver_ref_machine_init; + mc->max_cpus = RVSERVER_CPUS_MAX; + mc->default_cpu_type = TYPE_RISCV_CPU_RVSERVER_REF; + mc->valid_cpu_types = valid_cpu_types; + mc->pci_allow_0_address = true; + mc->default_nic = "e1000e"; + mc->possible_cpu_arch_ids = riscv_numa_possible_cpu_arch_ids; + mc->cpu_index_to_instance_props = riscv_numa_cpu_index_to_props; + mc->get_default_cpu_node_id = riscv_numa_get_default_cpu_node_id; + mc->numa_mem_supported = true; + /* platform instead of architectural choice */ + mc->cpu_cluster_has_numa_boundary = true; + mc->default_ram_id = "riscv_rvserver_ref_board.ram"; + + object_class_property_add_str(oc, "aia-guests", + rvserver_ref_get_aia_guests, + rvserver_ref_set_aia_guests); + sprintf(str, "Set number of guest MMIO pages for AIA IMSIC. Valid value " + "should be between 0 and %d.", RVSERVER_IRQCHIP_MAX_GUESTS); + object_class_property_set_description(oc, "aia-guests", str); + + assert(!mc->get_hotplug_handler); + mc->get_hotplug_handler = rvserver_machine_get_hotplug_handler; + hc->plug = rvserver_machine_device_plug_cb; +#ifdef CONFIG_TPM + machine_class_allow_dynamic_sysbus_dev(mc, TYPE_TPM_TIS_SYSBUS); +#endif +} + +static const TypeInfo rvserver_ref_typeinfo = { + .name = TYPE_RISCV_SERVER_REF_MACHINE, + .parent = TYPE_MACHINE, + .class_init = rvserver_ref_machine_class_init, + .instance_init = rvserver_ref_machine_instance_init, + .instance_size = sizeof(RISCVServerRefMachineState), + .interfaces = (const InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { TYPE_TARGET_RISCV64_MACHINE }, + { } + }, +}; + +static void rvserver_ref_init_register_types(void) +{ + type_register_static(&rvserver_ref_typeinfo); +} + +type_init(rvserver_ref_init_register_types) diff --git a/hw/riscv/spacemit-k3.c b/hw/riscv/spacemit-k3.c new file mode 100644 index 0000000000000..404081d89edc3 --- /dev/null +++ b/hw/riscv/spacemit-k3.c @@ -0,0 +1,583 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * SpacemiT K3 Pico-ITX machine + * + * This model implements the RISC-V platform and K3 boot-path subset needed + * to boot the SDK Linux kernel directly or through U-Boot with generic + * OpenSBI. BootROM, SPL, DDR training, and the A100/IME harts are outside + * this machine's current contract. + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "hw/core/loader.h" +#include "hw/core/qdev-properties.h" +#include "hw/intc/riscv_aclint.h" +#include "hw/intc/riscv_aplic.h" +#include "hw/intc/riscv_imsic.h" +#include "hw/riscv/boot.h" +#include "hw/riscv/machines-qom.h" +#include "hw/riscv/spacemit-k3.h" +#include "hw/sd/sd.h" +#include "system/address-spaces.h" +#include "system/block-backend.h" +#include "system/blockdev.h" +#include "system/device_tree.h" +#include "system/memory.h" +#include "system/qtest.h" +#include "system/reset.h" +#include "system/system.h" +#include "target/riscv/cpu.h" +#include "target/riscv/cpu-qom.h" +#include "target/riscv/time_helper.h" + +#include + +const MemMapEntry spacemit_k3_memmap[] = { + [K3_DEV_SRAM] = { 0xc0800000, 0x80000 }, + [K3_DEV_DDR_TRAINING] = { 0xc08d0000, 0x100 }, + [K3_DEV_UART0] = { 0xd4017000, 0x100 }, + [K3_DEV_SDHCI0] = { 0xd4280000, 0x200 }, + [K3_DEV_APMU] = { 0xd4282800, 0x400 }, + [K3_DEV_CIU] = { 0xd4282c00, 0x400 }, + [K3_DEV_S_IMSIC] = { 0xe0400000, 0x400000 }, + [K3_DEV_S_APLIC] = { 0xe0804000, 0x4000 }, + [K3_DEV_M_IMSIC] = { 0xf1000000, 0x10000 }, + [K3_DEV_M_APLIC] = { 0xf1800000, 0x4000 }, + [K3_DEV_M_CLINT] = { 0xf1810000, 0x10000 }, + [K3_DEV_FIRMWARE] = { 0x100000000, 32 * MiB }, + [K3_DEV_DRAM] = { 0x102000000, 0 }, +}; + +static RISCVCPU *k3_pico_itx_hart(SpacemitK3SoCState *s, unsigned int hartid) +{ + unsigned int cluster = hartid / K3_PICO_ITX_HARTS_PER_CLUSTER; + unsigned int index = hartid % K3_PICO_ITX_HARTS_PER_CLUSTER; + + return &s->cpus[cluster].harts[index]; +} + +static bool k3_pico_itx_validate_cpu(SpacemitK3SoCState *s, Error **errp) +{ + unsigned int hartid; + + for (hartid = 0; hartid < K3_PICO_ITX_NUM_HARTS; hartid++) { + RISCVCPU *cpu = k3_pico_itx_hart(s, hartid); + CPUState *cs = CPU(cpu); + unsigned int expected_cluster = + hartid / K3_PICO_ITX_HARTS_PER_CLUSTER; + + if (!riscv_has_ext(&cpu->env, RVV)) { + error_setg(errp, + "K3 X100 hart %u requires the V extension", + hartid); + return false; + } + if (cpu->cfg.vlenb != 256 / 8) { + error_setg(errp, "K3 X100 hart %u requires VLEN=256", hartid); + return false; + } + if (!cpu->cfg.ext_smaia || !cpu->cfg.ext_ssaia) { + error_setg(errp, + "K3 X100 hart %u requires Smaia and Ssaia", hartid); + return false; + } + if (!cpu->cfg.ext_sstc) { + error_setg(errp, "K3 X100 hart %u requires Sstc", hartid); + return false; + } + if (riscv_has_ext(&cpu->env, RVH)) { + error_setg(errp, + "K3 X100 hart %u does not support H", + hartid); + return false; + } + if (cs->cluster_index != expected_cluster) { + error_setg(errp, + "K3 hart %u has cluster index %d, expected %u", + hartid, cs->cluster_index, expected_cluster); + return false; + } + } + + return true; +} + +/* + * The RISC-V qtest accelerator does not install TCG's profile and feature + * properties. Supply the subset exercised by this non-executing accelerator + * directly; TCG still gets the same values through machine compatibility + * properties and is checked below. + */ +static void k3_pico_itx_apply_qtest_cpu_defaults(SpacemitK3SoCState *s) +{ + unsigned int hartid; + + if (!qtest_enabled()) { + return; + } + + for (hartid = 0; hartid < K3_PICO_ITX_NUM_HARTS; hartid++) { + RISCVCPU *cpu = k3_pico_itx_hart(s, hartid); + + cpu->env.misa_ext |= RVS | RVU | RVV; + cpu->env.misa_ext &= ~RVH; + cpu->env.priv_ver = PRIV_VERSION_1_13_0; + cpu->cfg.vlenb = 256 / 8; + cpu->cfg.ext_zicsr = true; + cpu->cfg.ext_smaia = true; + cpu->cfg.ext_ssaia = true; + cpu->cfg.ext_sstc = true; + riscv_timer_init(cpu); + } +} + +static void k3_pico_itx_create_aia(SpacemitK3SoCState *s) +{ + unsigned int hartid; + + for (hartid = 0; hartid < K3_PICO_ITX_NUM_HARTS; hartid++) { + s->m_imsic[hartid] = riscv_imsic_create( + spacemit_k3_memmap[K3_DEV_M_IMSIC].base + + hartid * IMSIC_HART_SIZE(0), + hartid, true, 1, K3_PICO_ITX_IMSIC_NUM_IDS); + } + + /* + * K3 assigns a 0x40000-byte interrupt-file stride to each X100 hart. + * Only the 511-ID S-mode page is modeled. The VS pages in each stride + * and the aperture for A100 harts remain unmapped. + */ + for (hartid = 0; hartid < K3_PICO_ITX_NUM_HARTS; hartid++) { + s->s_imsic[hartid] = riscv_imsic_create( + spacemit_k3_memmap[K3_DEV_S_IMSIC].base + hartid * 0x40000, + hartid, false, 1, K3_PICO_ITX_IMSIC_NUM_IDS); + } + + /* MSI-mode APLICs have no direct per-hart interrupt contexts. */ + s->m_aplic = riscv_aplic_create( + spacemit_k3_memmap[K3_DEV_M_APLIC].base, + spacemit_k3_memmap[K3_DEV_M_APLIC].size, + 0, 0, K3_PICO_ITX_APLIC_NUM_SOURCES, + K3_PICO_ITX_APLIC_IPRIO_BITS, true, true, NULL); + s->s_aplic = riscv_aplic_create( + spacemit_k3_memmap[K3_DEV_S_APLIC].base, + spacemit_k3_memmap[K3_DEV_S_APLIC].size, + 0, 0, K3_PICO_ITX_APLIC_NUM_SOURCES, + K3_PICO_ITX_APLIC_IPRIO_BITS, true, false, s->m_aplic); +} + +static void spacemit_k3_soc_reset(void *opaque) +{ + SpacemitK3SoCState *s = opaque; + + memset(memory_region_get_ram_ptr(&s->ddr_training), 0, + spacemit_k3_memmap[K3_DEV_DDR_TRAINING].size); +} + +static void spacemit_k3_soc_realize(DeviceState *dev, Error **errp) +{ + SpacemitK3SoCState *s = SPACEMIT_K3_SOC(dev); + MachineState *ms = MACHINE(qdev_get_machine()); + MemoryRegion *system_memory = get_system_memory(); + unsigned int cluster; + + memory_region_init_ram(&s->sram, OBJECT(dev), "spacemit.k3.sram", + spacemit_k3_memmap[K3_DEV_SRAM].size, errp); + if (*errp) { + return; + } + memory_region_add_subregion(system_memory, + spacemit_k3_memmap[K3_DEV_SRAM].base, + &s->sram); + + memory_region_init_ram(&s->ddr_training, OBJECT(dev), + "spacemit.k3.ddr-training", + spacemit_k3_memmap[K3_DEV_DDR_TRAINING].size, + errp); + if (*errp) { + return; + } + memory_region_add_subregion( + system_memory, spacemit_k3_memmap[K3_DEV_DDR_TRAINING].base, + &s->ddr_training); + + memory_region_init_ram(&s->firmware, OBJECT(dev), "spacemit.k3.firmware", + spacemit_k3_memmap[K3_DEV_FIRMWARE].size, errp); + if (*errp) { + return; + } + memory_region_add_subregion(system_memory, + spacemit_k3_memmap[K3_DEV_FIRMWARE].base, + &s->firmware); + + for (cluster = 0; cluster < K3_PICO_ITX_NUM_CLUSTERS; cluster++) { + qdev_prop_set_string(DEVICE(&s->cpus[cluster]), "cpu-type", + ms->cpu_type); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->cpus[cluster]), errp)) { + return; + } + } + + /* The CPUs must exist below each cluster before the cluster is realized. */ + for (cluster = 0; cluster < K3_PICO_ITX_NUM_CLUSTERS; cluster++) { + if (!qdev_realize(DEVICE(&s->clusters[cluster]), NULL, errp)) { + return; + } + } + + k3_pico_itx_apply_qtest_cpu_defaults(s); + if (!k3_pico_itx_validate_cpu(s, errp)) { + return; + } + + s->swi = riscv_aclint_swi_create( + spacemit_k3_memmap[K3_DEV_M_CLINT].base, + 0, K3_PICO_ITX_NUM_HARTS, false); + s->mtimer = riscv_aclint_mtimer_create( + spacemit_k3_memmap[K3_DEV_M_CLINT].base + RISCV_ACLINT_SWI_SIZE, + RISCV_ACLINT_DEFAULT_MTIMER_SIZE, + 0, K3_PICO_ITX_NUM_HARTS, + RISCV_ACLINT_DEFAULT_MTIMECMP, RISCV_ACLINT_DEFAULT_MTIME, + K3_PICO_ITX_TIMEBASE_FREQ, true); + + k3_pico_itx_create_aia(s); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->apmu), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->apmu), 0, + spacemit_k3_memmap[K3_DEV_APMU].base); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->ciu), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->ciu), 0, + spacemit_k3_memmap[K3_DEV_CIU].base); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->sdhci0), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->sdhci0), 0, + spacemit_k3_memmap[K3_DEV_SDHCI0].base); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->sdhci0), 0, + qdev_get_gpio_in(s->m_aplic, + K3_PICO_ITX_SDHCI0_IRQ)); + + memory_region_init(&s->uart0_mem, OBJECT(dev), "spacemit.k3.uart0", + spacemit_k3_memmap[K3_DEV_UART0].size); + memory_region_add_subregion(system_memory, + spacemit_k3_memmap[K3_DEV_UART0].base, + &s->uart0_mem); + s->uart0 = serial_mm_init( + &s->uart0_mem, 0, 2, + qdev_get_gpio_in(s->m_aplic, K3_PICO_ITX_UART0_IRQ), + 115200, serial_hd(0), DEVICE_LITTLE_ENDIAN); + + qemu_register_reset(spacemit_k3_soc_reset, s); +} + +static void spacemit_k3_soc_instance_init(Object *obj) +{ + SpacemitK3SoCState *s = SPACEMIT_K3_SOC(obj); + unsigned int cluster; + + object_initialize_child(obj, "cluster0", &s->clusters[0], + TYPE_CPU_CLUSTER); + object_initialize_child(OBJECT(&s->clusters[0]), "cpus", &s->cpus[0], + TYPE_RISCV_HART_ARRAY); + object_initialize_child(obj, "cluster1", &s->clusters[1], + TYPE_CPU_CLUSTER); + object_initialize_child(OBJECT(&s->clusters[1]), "cpus", &s->cpus[1], + TYPE_RISCV_HART_ARRAY); + object_initialize_child(obj, "apmu", &s->apmu, TYPE_SPACEMIT_K3_APMU); + object_initialize_child(obj, "ciu", &s->ciu, TYPE_SPACEMIT_K3_CIU); + object_initialize_child(obj, "sdhci0", &s->sdhci0, + TYPE_SPACEMIT_K3_SDHCI); + + for (cluster = 0; cluster < K3_PICO_ITX_NUM_CLUSTERS; cluster++) { + qdev_prop_set_uint32(DEVICE(&s->clusters[cluster]), "cluster-id", + cluster); + qdev_prop_set_uint32(DEVICE(&s->cpus[cluster]), "num-harts", + K3_PICO_ITX_HARTS_PER_CLUSTER); + qdev_prop_set_uint32(DEVICE(&s->cpus[cluster]), "hartid-base", + cluster * K3_PICO_ITX_HARTS_PER_CLUSTER); + qdev_prop_set_uint64(DEVICE(&s->cpus[cluster]), "resetvec", + spacemit_k3_memmap[K3_DEV_SRAM].base); + } +} + +static void spacemit_k3_soc_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = spacemit_k3_soc_realize; + dc->user_creatable = false; +} + +static const TypeInfo spacemit_k3_soc_type_info = { + .name = TYPE_SPACEMIT_K3_SOC, + .parent = TYPE_DEVICE, + .instance_size = sizeof(SpacemitK3SoCState), + .instance_init = spacemit_k3_soc_instance_init, + .class_init = spacemit_k3_soc_class_init, +}; + +static void k3_pico_itx_validate_machine(MachineState *machine) +{ + const CpuTopology *smp = &machine->smp; + + if (machine->ram_size != 2 * GiB) { + error_report("k3-pico-itx requires exactly 2 GiB of RAM"); + exit(EXIT_FAILURE); + } + if (strcmp(machine->cpu_type, TYPE_RISCV_CPU_SPACEMIT_X100)) { + error_report("k3-pico-itx requires the spacemit-x100 CPU type"); + exit(EXIT_FAILURE); + } + if (smp->cpus != K3_PICO_ITX_NUM_HARTS || + smp->max_cpus != K3_PICO_ITX_NUM_HARTS || + smp->sockets != 1 || smp->clusters != K3_PICO_ITX_NUM_CLUSTERS || + smp->cores != K3_PICO_ITX_HARTS_PER_CLUSTER || smp->threads != 1) { + error_report("k3-pico-itx requires " + "cpus=8,sockets=1,clusters=2,cores=4,threads=1,maxcpus=8"); + exit(EXIT_FAILURE); + } +} + +static void k3_pico_itx_validate_fdt(K3PicoITXState *s) +{ + MachineState *machine = MACHINE(s); + const char expected_compat[] = "spacemit,k3-pico-itx"; + const fdt32_t *prop; + int len; + int offset; + unsigned int hartid; + uint64_t base; + uint64_t size; + + if (fdt_node_check_compatible(machine->fdt, 0, expected_compat)) { + error_report("k3-pico-itx DTB requires root compatible '%s'", + expected_compat); + exit(EXIT_FAILURE); + } + + offset = fdt_path_offset(machine->fdt, "/cpus"); + prop = offset >= 0 ? + fdt_getprop(machine->fdt, offset, "timebase-frequency", &len) : NULL; + if (!prop || len != sizeof(*prop) || + fdt32_to_cpu(*prop) != K3_PICO_ITX_TIMEBASE_FREQ) { + error_report("k3-pico-itx DTB requires a 24 MHz timebase"); + exit(EXIT_FAILURE); + } + + for (hartid = 0; hartid < K3_PICO_ITX_NUM_HARTS; hartid++) { + g_autofree char *node = g_strdup_printf("/cpus/cpu@%x", hartid); + + if (fdt_path_offset(machine->fdt, node) < 0) { + error_report("k3-pico-itx DTB is missing X100 hart %u", hartid); + exit(EXIT_FAILURE); + } + riscv_isa_write_fdt(k3_pico_itx_hart(&s->soc, hartid), + machine->fdt, node); + } + if (fdt_path_offset(machine->fdt, "/cpus/cpu@8") >= 0) { + error_report("k3-pico-itx DTB must not expose A100 harts"); + exit(EXIT_FAILURE); + } + + offset = fdt_path_offset(machine->fdt, "/memory@102000000"); + prop = offset >= 0 ? fdt_getprop(machine->fdt, offset, "reg", &len) : NULL; + if (!prop || len != 4 * sizeof(*prop)) { + error_report("k3-pico-itx DTB requires the 64-bit Linux memory node"); + exit(EXIT_FAILURE); + } + base = ((uint64_t)fdt32_to_cpu(prop[0]) << 32) | fdt32_to_cpu(prop[1]); + size = ((uint64_t)fdt32_to_cpu(prop[2]) << 32) | fdt32_to_cpu(prop[3]); + if (base != spacemit_k3_memmap[K3_DEV_DRAM].base || + size != machine->ram_size) { + error_report("k3-pico-itx DTB memory must be 2 GiB at 0x102000000"); + exit(EXIT_FAILURE); + } + + if (fdt_path_offset(machine->fdt, "/chosen") < 0) { + qemu_fdt_add_subnode(machine->fdt, "/chosen"); + } +} + +static void k3_pico_itx_check_loaded_payloads(MachineState *machine, + RISCVBootInfo *info) +{ + hwaddr dram_base = spacemit_k3_memmap[K3_DEV_DRAM].base; + hwaddr dram_end = dram_base + machine->ram_size; + + if (info->kernel_size && + (info->image_low_addr < dram_base || + info->image_high_addr > dram_end || + info->image_high_addr < info->image_low_addr)) { + error_report("K3 -kernel payload does not fit in the DRAM window"); + exit(EXIT_FAILURE); + } + if (info->initrd_size && + (info->initrd_start < dram_base || + info->initrd_start + info->initrd_size > dram_end || + info->initrd_start + info->initrd_size < info->initrd_start)) { + error_report("K3 -initrd payload does not fit in the DRAM window"); + exit(EXIT_FAILURE); + } +} + +static void k3_pico_itx_attach_sd_card(K3PicoITXState *s) +{ + DriveInfo *dinfo = drive_get(IF_SD, 0, 0); + DeviceState *card; + + if (!dinfo) { + return; + } + + card = qdev_new(TYPE_SD_CARD); + qdev_prop_set_drive_err(card, "drive", blk_by_legacy_dinfo(dinfo), + &error_fatal); + qdev_realize_and_unref(card, s->soc.sdhci0.sd_bus, &error_fatal); +} + +static void k3_pico_itx_machine_init(MachineState *machine) +{ + K3PicoITXState *s = K3_PICO_ITX_MACHINE(machine); + MemoryRegion *system_memory = get_system_memory(); + RISCVBootInfo boot_info; + hwaddr firmware_load_addr = spacemit_k3_memmap[K3_DEV_FIRMWARE].base; + hwaddr firmware_end_addr = firmware_load_addr; + hwaddr fdt_load_addr = 0; + uint64_t kernel_entry = 0; + + k3_pico_itx_validate_machine(machine); + + memory_region_add_subregion(system_memory, + spacemit_k3_memmap[K3_DEV_DRAM].base, + machine->ram); + qdev_realize(DEVICE(&s->soc), NULL, &error_fatal); + k3_pico_itx_attach_sd_card(s); + + riscv_boot_info_init(&boot_info, &s->soc.cpus[0]); + + if (machine->kernel_filename && + machine->firmware && !strcmp(machine->firmware, "none")) { + error_report("k3-pico-itx direct Linux boot requires OpenSBI firmware"); + exit(EXIT_FAILURE); + } + + firmware_end_addr = riscv_find_and_load_firmware( + machine, &boot_info, + riscv_default_firmware_name(&s->soc.cpus[0]), + &firmware_load_addr, NULL); + if (firmware_load_addr < spacemit_k3_memmap[K3_DEV_FIRMWARE].base || + firmware_end_addr > spacemit_k3_memmap[K3_DEV_DRAM].base || + firmware_end_addr < firmware_load_addr) { + error_report("K3 firmware must fit in 0x100000000..0x102000000"); + exit(EXIT_FAILURE); + } + + if (machine->kernel_filename && !machine->dtb) { + error_report("k3-pico-itx direct Linux boot requires -dtb"); + exit(EXIT_FAILURE); + } + if (machine->dtb) { + machine->fdt = load_device_tree(machine->dtb, &s->fdt_size); + if (!machine->fdt) { + error_report("failed to load k3-pico-itx device tree"); + exit(EXIT_FAILURE); + } + k3_pico_itx_validate_fdt(s); + } + + if (machine->kernel_filename) { + hwaddr kernel_start_addr = MAX( + riscv_calc_kernel_start_addr(&boot_info, firmware_end_addr), + spacemit_k3_memmap[K3_DEV_DRAM].base); + + riscv_load_kernel(machine, &boot_info, kernel_start_addr, true, NULL); + k3_pico_itx_check_loaded_payloads(machine, &boot_info); + kernel_entry = boot_info.image_low_addr; + } + + if (machine->fdt) { + fdt_load_addr = riscv_compute_fdt_addr( + spacemit_k3_memmap[K3_DEV_DRAM].base, machine->ram_size, + machine, &boot_info); + if (fdt_load_addr < spacemit_k3_memmap[K3_DEV_DRAM].base || + fdt_load_addr + fdt_totalsize(machine->fdt) > + spacemit_k3_memmap[K3_DEV_DRAM].base + machine->ram_size) { + error_report("K3 DTB does not fit in the DRAM window"); + exit(EXIT_FAILURE); + } + riscv_load_fdt(fdt_load_addr, machine->fdt); + } + + riscv_setup_rom_reset_vec(machine, &s->soc.cpus[0], firmware_load_addr, + spacemit_k3_memmap[K3_DEV_SRAM].base, + spacemit_k3_memmap[K3_DEV_SRAM].size, + kernel_entry, fdt_load_addr); +} + +static void k3_pico_itx_machine_instance_init(Object *obj) +{ + K3PicoITXState *s = K3_PICO_ITX_MACHINE(obj); + MachineState *machine = MACHINE(obj); + + machine->smp.cpus = K3_PICO_ITX_NUM_HARTS; + machine->smp.max_cpus = K3_PICO_ITX_NUM_HARTS; + machine->smp.sockets = 1; + machine->smp.clusters = K3_PICO_ITX_NUM_CLUSTERS; + machine->smp.cores = K3_PICO_ITX_HARTS_PER_CLUSTER; + machine->smp.threads = 1; + + object_initialize_child(obj, "soc", &s->soc, TYPE_SPACEMIT_K3_SOC); +} + +static GlobalProperty k3_pico_itx_cpu_defaults[] = { + { TYPE_RISCV_CPU_SPACEMIT_X100, "h", "false", .optional = true }, +}; + +static void k3_pico_itx_machine_class_init(ObjectClass *oc, const void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + static const char *const valid_cpu_types[] = { + TYPE_RISCV_CPU_SPACEMIT_X100, + NULL, + }; + + mc->desc = "SpacemiT K3 Pico-ITX (X100 subset)"; + mc->init = k3_pico_itx_machine_init; + mc->min_cpus = K3_PICO_ITX_NUM_HARTS; + mc->max_cpus = K3_PICO_ITX_NUM_HARTS; + mc->default_cpus = K3_PICO_ITX_NUM_HARTS; + mc->default_cpu_type = TYPE_RISCV_CPU_SPACEMIT_X100; + mc->valid_cpu_types = valid_cpu_types; + mc->default_ram_size = 2 * GiB; + mc->default_ram_id = "spacemit.k3.ram"; + mc->auto_create_sdcard = true; + mc->smp_props.clusters_supported = true; + compat_props_add(mc->compat_props, k3_pico_itx_cpu_defaults, + G_N_ELEMENTS(k3_pico_itx_cpu_defaults)); +} + +static const TypeInfo k3_pico_itx_machine_type_info = { + .name = TYPE_K3_PICO_ITX_MACHINE, + .parent = TYPE_MACHINE, + .instance_size = sizeof(K3PicoITXState), + .instance_init = k3_pico_itx_machine_instance_init, + .class_init = k3_pico_itx_machine_class_init, + .interfaces = riscv64_machine_interfaces, +}; + +static void spacemit_k3_register_types(void) +{ + type_register_static(&spacemit_k3_soc_type_info); + type_register_static(&k3_pico_itx_machine_type_info); +} + +type_init(spacemit_k3_register_types) diff --git a/hw/rtc/Kconfig b/hw/rtc/Kconfig index 315b0e4eccdd8..dc0c8828e90e7 100644 --- a/hw/rtc/Kconfig +++ b/hw/rtc/Kconfig @@ -14,6 +14,9 @@ config M48T59 config PL031 bool +config K230_RTC + bool + config MC146818RTC depends on ISA_BUS bool diff --git a/hw/rtc/k230_rtc.c b/hw/rtc/k230_rtc.c new file mode 100644 index 0000000000000..0447194b26236 --- /dev/null +++ b/hw/rtc/k230_rtc.c @@ -0,0 +1,484 @@ +/* + * K230 Real-Time Clock + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/core/irq.h" +#include "hw/rtc/k230_rtc.h" +#include "migration/vmstate.h" +#include "qemu/bitops.h" +#include "qemu/cutils.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/timer.h" +#include "system/rtc.h" +#include "system/system.h" + +#define K230_RTC_MMIO_SIZE 0x400 + +#define K230_RTC_DATE 0x00 +#define K230_RTC_TIME 0x04 +#define K230_RTC_ALARM_DATE 0x08 +#define K230_RTC_ALARM_TIME 0x0c +#define K230_RTC_COUNT 0x10 +#define K230_RTC_INT_CTRL 0x14 + +#define K230_RTC_COUNT_CURR_MASK MAKE_64BIT_MASK(0, 15) +#define K230_RTC_COUNT_SUM_MASK MAKE_64BIT_MASK(16, 15) +#define K230_RTC_COUNT_DEFAULT (32767u << 16) + +#define K230_RTC_INT_TIMER_W_EN BIT(0) +#define K230_RTC_INT_TIMER_R_EN BIT(1) +#define K230_RTC_INT_TICK_EN BIT(8) +#define K230_RTC_INT_TICK_SEL MAKE_64BIT_MASK(9, 4) +#define K230_RTC_INT_ALARM_EN BIT(16) +#define K230_RTC_INT_ALARM_CLR BIT(17) +#define K230_RTC_INT_SECOND_CMP BIT(24) +#define K230_RTC_INT_MINUTE_CMP BIT(25) +#define K230_RTC_INT_HOUR_CMP BIT(26) +#define K230_RTC_INT_WEEK_CMP BIT(27) +#define K230_RTC_INT_DAY_CMP BIT(28) +#define K230_RTC_INT_MONTH_CMP BIT(29) +#define K230_RTC_INT_YEAR_CMP BIT(30) +#define K230_RTC_INT_CMP_MASK MAKE_64BIT_MASK(24, 7) + +static bool k230_rtc_year_is_leap(int year) +{ + return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; +} + +static int64_t k230_rtc_get_seconds(K230RtcState *s) +{ + return s->tick_offset + + qemu_clock_get_ns(rtc_clock) / NANOSECONDS_PER_SECOND; +} + +static void k230_rtc_set_seconds(K230RtcState *s, int64_t seconds) +{ + s->tick_offset = seconds - + qemu_clock_get_ns(rtc_clock) / NANOSECONDS_PER_SECOND; +} + +static void k230_rtc_get_time(K230RtcState *s, struct tm *tm) +{ + time_t seconds = k230_rtc_get_seconds(s); + + gmtime_r(&seconds, tm); +} + +static uint32_t k230_rtc_pack_date(const struct tm *tm) +{ + int year = tm->tm_year + 1900; + int year_l = year % 100; + int year_h = year / 100; + + if (year_l == 0) { + year_l = 100; + year_h--; + } + + return (tm->tm_mday & 0x1f) | + ((tm->tm_mon + 1) & 0xf) << 8 | + (year_l & 0x7f) << 16 | + (k230_rtc_year_is_leap(year) ? BIT(23) : 0) | + (year_h & 0x7f) << 24; +} + +static uint32_t k230_rtc_pack_time(const struct tm *tm) +{ + return (tm->tm_sec & 0x3f) | + (tm->tm_min & 0x3f) << 8 | + (tm->tm_hour & 0x1f) << 16 | + (tm->tm_wday & 0x7) << 24; +} + +static bool k230_rtc_decode_date(uint32_t value, struct tm *tm) +{ + int day = extract32(value, 0, 5); + int month = extract32(value, 8, 4); + int year_l = extract32(value, 16, 7); + int year_h = extract32(value, 24, 7); + + if (day < 1 || day > 31 || month < 1 || month > 12) { + return false; + } + + tm->tm_mday = day; + tm->tm_mon = month - 1; + tm->tm_year = year_h * 100 + year_l - 1900; + return true; +} + +static bool k230_rtc_decode_time(uint32_t value, struct tm *tm) +{ + int second = extract32(value, 0, 6); + int minute = extract32(value, 8, 6); + int hour = extract32(value, 16, 5); + int week = extract32(value, 24, 3); + + if (second > 59 || minute > 59 || hour > 23 || week > 6) { + return false; + } + + tm->tm_sec = second; + tm->tm_min = minute; + tm->tm_hour = hour; + tm->tm_wday = week; + return true; +} + +static uint32_t k230_rtc_get_count(K230RtcState *s) +{ + uint32_t sum = (s->count & K230_RTC_COUNT_SUM_MASK) >> 16; + int64_t elapsed_ns = qemu_clock_get_ns(rtc_clock) - s->count_base_ns; + uint64_t ticks; + + if (elapsed_ns < 0) { + elapsed_ns = 0; + } + + ticks = muldiv64(elapsed_ns, sum + 1, NANOSECONDS_PER_SECOND); + return (s->count & K230_RTC_COUNT_SUM_MASK) | (ticks % (sum + 1)); +} + +static void k230_rtc_set_count(K230RtcState *s, uint32_t value) +{ + uint32_t sum = (value & K230_RTC_COUNT_SUM_MASK) >> 16; + uint32_t curr = value & K230_RTC_COUNT_CURR_MASK; + int64_t elapsed_ns; + + curr %= sum + 1; + elapsed_ns = muldiv64(curr, NANOSECONDS_PER_SECOND, sum + 1); + + s->count = value & K230_RTC_COUNT_SUM_MASK; + s->count_base_ns = qemu_clock_get_ns(rtc_clock) - elapsed_ns; +} + +static void k230_rtc_update_irq(K230RtcState *s) +{ + bool enabled = s->int_ctrl & (K230_RTC_INT_ALARM_EN | + K230_RTC_INT_TICK_EN); + + qemu_set_irq(s->irq, s->irq_pending && enabled); +} + +static bool k230_rtc_alarm_matches(K230RtcState *s, const struct tm *tm) +{ + uint32_t cmp = s->int_ctrl & K230_RTC_INT_CMP_MASK; + uint32_t date = s->alarm_date; + uint32_t time = s->alarm_time; + int alarm_year = extract32(date, 24, 7) * 100 + + extract32(date, 16, 7); + + if ((cmp & K230_RTC_INT_YEAR_CMP) && + alarm_year != tm->tm_year + 1900) { + return false; + } + if ((cmp & K230_RTC_INT_MONTH_CMP) && + extract32(date, 8, 4) != tm->tm_mon + 1) { + return false; + } + if ((cmp & K230_RTC_INT_DAY_CMP) && + extract32(date, 0, 5) != tm->tm_mday) { + return false; + } + if ((cmp & K230_RTC_INT_WEEK_CMP) && + extract32(time, 24, 3) != tm->tm_wday) { + return false; + } + if ((cmp & K230_RTC_INT_HOUR_CMP) && + extract32(time, 16, 5) != tm->tm_hour) { + return false; + } + if ((cmp & K230_RTC_INT_MINUTE_CMP) && + extract32(time, 8, 6) != tm->tm_min) { + return false; + } + if ((cmp & K230_RTC_INT_SECOND_CMP) && + extract32(time, 0, 6) != tm->tm_sec) { + return false; + } + + return cmp != 0; +} + +static int64_t k230_rtc_next_alarm_ns(K230RtcState *s) +{ + int64_t now = k230_rtc_get_seconds(s); + uint32_t cmp = s->int_ctrl & K230_RTC_INT_CMP_MASK; + + if (!(s->int_ctrl & K230_RTC_INT_ALARM_EN) || !cmp) { + return -1; + } + + if ((cmp & (K230_RTC_INT_YEAR_CMP | K230_RTC_INT_MONTH_CMP | + K230_RTC_INT_DAY_CMP | K230_RTC_INT_HOUR_CMP | + K230_RTC_INT_MINUTE_CMP | K230_RTC_INT_SECOND_CMP)) == + (K230_RTC_INT_YEAR_CMP | K230_RTC_INT_MONTH_CMP | + K230_RTC_INT_DAY_CMP | K230_RTC_INT_HOUR_CMP | + K230_RTC_INT_MINUTE_CMP | K230_RTC_INT_SECOND_CMP)) { + struct tm tm = { 0 }; + int64_t alarm; + + if (k230_rtc_decode_date(s->alarm_date, &tm) && + k230_rtc_decode_time(s->alarm_time, &tm)) { + alarm = mktimegm(&tm); + if (alarm > now) { + return (alarm - s->tick_offset) * NANOSECONDS_PER_SECOND; + } + } + return -1; + } + + for (int i = 1; i <= 86400; i++) { + struct tm tm; + time_t seconds = now + i; + + gmtime_r(&seconds, &tm); + if (k230_rtc_alarm_matches(s, &tm)) { + return (seconds - s->tick_offset) * NANOSECONDS_PER_SECOND; + } + } + + return -1; +} + +static int64_t k230_rtc_tick_period_ns(K230RtcState *s) +{ + uint32_t tick_sel = (s->int_ctrl & K230_RTC_INT_TICK_SEL) >> 9; + + if (!(s->int_ctrl & K230_RTC_INT_TICK_EN)) { + return -1; + } + + switch (tick_sel) { + case 0: + return NANOSECONDS_PER_SECOND / 64; + case 1: + return NANOSECONDS_PER_SECOND / 8; + case 2: + return NANOSECONDS_PER_SECOND; + case 3: + return 60 * NANOSECONDS_PER_SECOND; + case 4: + return 60 * 60 * NANOSECONDS_PER_SECOND; + case 5: + return 7 * 24 * 60 * 60 * NANOSECONDS_PER_SECOND; + case 6: + return 24 * 60 * 60 * NANOSECONDS_PER_SECOND; + case 7: + return 30 * 24 * 60 * 60 * NANOSECONDS_PER_SECOND; + case 8: + return 365 * 24 * 60 * 60 * NANOSECONDS_PER_SECOND; + default: + return -1; + } +} + +static void k230_rtc_schedule(K230RtcState *s) +{ + int64_t now_ns = qemu_clock_get_ns(rtc_clock); + int64_t alarm_ns = k230_rtc_next_alarm_ns(s); + int64_t tick_period_ns = k230_rtc_tick_period_ns(s); + int64_t next_ns = -1; + + if (alarm_ns >= 0) { + next_ns = alarm_ns; + } + if (tick_period_ns > 0 && + (next_ns < 0 || now_ns + tick_period_ns < next_ns)) { + next_ns = now_ns + tick_period_ns; + } + + if (next_ns >= 0) { + timer_mod(s->timer, next_ns); + } else { + timer_del(s->timer); + } +} + +static void k230_rtc_timer_cb(void *opaque) +{ + K230RtcState *s = K230_RTC(opaque); + + s->irq_pending = true; + k230_rtc_update_irq(s); + k230_rtc_schedule(s); +} + +static uint64_t k230_rtc_read(void *opaque, hwaddr addr, unsigned int size) +{ + K230RtcState *s = K230_RTC(opaque); + struct tm tm; + + switch (addr) { + case K230_RTC_DATE: + k230_rtc_get_time(s, &tm); + return k230_rtc_pack_date(&tm); + case K230_RTC_TIME: + k230_rtc_get_time(s, &tm); + return k230_rtc_pack_time(&tm); + case K230_RTC_ALARM_DATE: + return s->alarm_date; + case K230_RTC_ALARM_TIME: + return s->alarm_time; + case K230_RTC_COUNT: + return k230_rtc_get_count(s); + case K230_RTC_INT_CTRL: + return s->int_ctrl; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: bad offset 0x%x\n", __func__, (uint32_t)addr); + return 0; + } +} + +static void k230_rtc_write(void *opaque, hwaddr addr, uint64_t value, + unsigned int size) +{ + K230RtcState *s = K230_RTC(opaque); + struct tm tm; + + switch (addr) { + case K230_RTC_DATE: + k230_rtc_get_time(s, &tm); + if (k230_rtc_decode_date(value, &tm)) { + k230_rtc_set_seconds(s, mktimegm(&tm)); + } + k230_rtc_schedule(s); + break; + case K230_RTC_TIME: + k230_rtc_get_time(s, &tm); + if (k230_rtc_decode_time(value, &tm)) { + k230_rtc_set_seconds(s, mktimegm(&tm)); + } + k230_rtc_schedule(s); + break; + case K230_RTC_ALARM_DATE: + s->alarm_date = value; + k230_rtc_schedule(s); + break; + case K230_RTC_ALARM_TIME: + s->alarm_time = value; + k230_rtc_schedule(s); + break; + case K230_RTC_COUNT: + k230_rtc_set_count(s, value); + break; + case K230_RTC_INT_CTRL: + if (value & K230_RTC_INT_ALARM_CLR) { + s->irq_pending = false; + } + s->int_ctrl = value & ~K230_RTC_INT_ALARM_CLR; + k230_rtc_update_irq(s); + k230_rtc_schedule(s); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: bad offset 0x%x\n", __func__, (uint32_t)addr); + break; + } +} + +static const MemoryRegionOps k230_rtc_ops = { + .read = k230_rtc_read, + .write = k230_rtc_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void k230_rtc_reset(DeviceState *dev) +{ + K230RtcState *s = K230_RTC(dev); + + s->alarm_date = 0; + s->alarm_time = 0; + k230_rtc_set_count(s, K230_RTC_COUNT_DEFAULT); + s->int_ctrl = 0; + s->irq_pending = false; + k230_rtc_update_irq(s); + k230_rtc_schedule(s); +} + +static int k230_rtc_post_load(void *opaque, int version_id) +{ + K230RtcState *s = K230_RTC(opaque); + + k230_rtc_update_irq(s); + k230_rtc_schedule(s); + return 0; +} + +static const VMStateDescription vmstate_k230_rtc = { + .name = TYPE_K230_RTC, + .version_id = 1, + .minimum_version_id = 1, + .post_load = k230_rtc_post_load, + .fields = (const VMStateField[]) { + VMSTATE_INT64(tick_offset, K230RtcState), + VMSTATE_INT64(count_base_ns, K230RtcState), + VMSTATE_UINT32(alarm_date, K230RtcState), + VMSTATE_UINT32(alarm_time, K230RtcState), + VMSTATE_UINT32(count, K230RtcState), + VMSTATE_UINT32(int_ctrl, K230RtcState), + VMSTATE_BOOL(irq_pending, K230RtcState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_rtc_init(Object *obj) +{ + K230RtcState *s = K230_RTC(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + struct tm tm; + + memory_region_init_io(&s->mmio, obj, &k230_rtc_ops, s, + TYPE_K230_RTC, K230_RTC_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->mmio); + sysbus_init_irq(sbd, &s->irq); + + qemu_get_timedate(&tm, 0); + s->tick_offset = mktimegm(&tm) - + qemu_clock_get_ns(rtc_clock) / NANOSECONDS_PER_SECOND; + s->timer = timer_new_ns(rtc_clock, k230_rtc_timer_cb, s); +} + +static void k230_rtc_finalize(Object *obj) +{ + K230RtcState *s = K230_RTC(obj); + + timer_free(s->timer); +} + +static void k230_rtc_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + device_class_set_legacy_reset(dc, k230_rtc_reset); + dc->vmsd = &vmstate_k230_rtc; + dc->desc = "K230 real-time clock"; +} + +static const TypeInfo k230_rtc_type_info = { + .name = TYPE_K230_RTC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230RtcState), + .instance_init = k230_rtc_init, + .instance_finalize = k230_rtc_finalize, + .class_init = k230_rtc_class_init, +}; + +static void k230_rtc_register_types(void) +{ + type_register_static(&k230_rtc_type_info); +} + +type_init(k230_rtc_register_types) diff --git a/hw/rtc/meson.build b/hw/rtc/meson.build index 6c87864dc07cf..4695ba0d35b05 100644 --- a/hw/rtc/meson.build +++ b/hw/rtc/meson.build @@ -10,6 +10,7 @@ system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_rtc.c')) system_ss.add(when: 'CONFIG_SUN4V_RTC', if_true: files('sun4v-rtc.c')) system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_rtc.c')) system_ss.add(when: 'CONFIG_GOLDFISH_RTC', if_true: files('goldfish_rtc.c')) +system_ss.add(when: 'CONFIG_K230_RTC', if_true: files('k230_rtc.c')) system_ss.add(when: 'CONFIG_LS7A_RTC', if_true: files('ls7a_rtc.c')) system_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-rtc.c')) system_ss.add(when: 'CONFIG_MC146818RTC', if_true: files('mc146818rtc.c')) diff --git a/hw/sd/Kconfig b/hw/sd/Kconfig index 633b9afec915b..7850afe7f9d1b 100644 --- a/hw/sd/Kconfig +++ b/hw/sd/Kconfig @@ -2,6 +2,14 @@ config PL181 bool select SD +config DW_MMC + bool + select SD + +config PHYTIUM_MCI + bool + select SD + config SSI_SD bool depends on SSI @@ -14,6 +22,10 @@ config SDHCI bool select SD +config ROCKCHIP_DWCMSHC + bool + select SDHCI + config SDHCI_PCI bool default y if PCI_DEVICES @@ -23,3 +35,15 @@ config SDHCI_PCI config CADENCE_SDHCI bool select SDHCI + +config K230_SDHCI + bool + select SDHCI + +config SPACEMIT_K3_SDHCI + bool + select SDHCI + +config AX650X_SDHCI + bool + select SDHCI diff --git a/hw/sd/ax650x-sdhci.c b/hw/sd/ax650x-sdhci.c new file mode 100644 index 0000000000000..ae6dcbd4515d9 --- /dev/null +++ b/hw/sd/ax650x-sdhci.c @@ -0,0 +1,246 @@ +/* + * Axera AX650X SDHCI controller + * + * Copyright (c) 2026 Zevorn + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/core/qdev-properties.h" +#include "hw/sd/ax650x-sdhci.h" +#include "migration/vmstate.h" +#include "sdhci-internal.h" + +#define AX650X_VENDOR_ADDR(_addr) ((_addr) - AX650X_SDHCI_VENDOR_BASE) + +#define VREG(_name, _addr) \ + { .name = (_name), .addr = AX650X_VENDOR_ADDR(_addr) } +#define VREG_RO(_name, _addr, _reset) \ + { .name = (_name), .addr = AX650X_VENDOR_ADDR(_addr), \ + .reset = (_reset), .ro = UINT8_MAX } + +static uint64_t ax650x_register_bank_read(void *opaque, hwaddr addr, + unsigned int size) +{ + uint64_t value = 0; + + for (unsigned int i = 0; i < size; i++) { + value |= register_read_memory(opaque, addr + i, 1) << (i * 8); + } + + return value; +} + +static void ax650x_register_bank_write(void *opaque, hwaddr addr, + uint64_t value, unsigned int size) +{ + for (unsigned int i = 0; i < size; i++) { + register_write_memory(opaque, addr + i, value >> (i * 8), 1); + } +} + +static const MemoryRegionOps ax650x_register_bank_ops = { + .read = ax650x_register_bank_read, + .write = ax650x_register_bank_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void ax650x_dll_ctrl_post_write(RegisterInfo *reg, uint64_t value) +{ + AX650XSDHCIState *s = AX650X_SDHCI(reg->opaque); + unsigned int status = AX650X_VENDOR_ADDR(AX650X_SDHCI_PHY_DLL_STATUS); + + if (value & AX650X_SDHCI_DLL_EN) { + s->vendor_regs[status] = AX650X_SDHCI_DLL_LOCKED; + } else { + s->vendor_regs[status] = 0; + } +} + +static void ax650x_emmc_ctrl_post_write(RegisterInfo *reg, uint64_t value) +{ + AX650XSDHCIState *s = AX650X_SDHCI(reg->opaque); + BusChild *child; + bool reset_asserted; + + reset_asserted = (value & AX650X_SDHCI_EMMC_RST_N_OE) && + !(value & AX650X_SDHCI_EMMC_RST_N); + if (s->emmc_reset_asserted && !reset_asserted) { + child = QTAILQ_FIRST(&s->sdhci.sdbus.qbus.children); + if (child) { + device_cold_reset(child->child); + } + } + s->emmc_reset_asserted = reset_asserted; +} + +static const RegisterAccessInfo ax650x_pointer_regs_info[] = { + { .name = "P_VENDOR_SPECIFIC_AREA[7:0]", .addr = 0, + .reset = AX650X_SDHCI_VENDOR_PTR_VALUE & 0xff, .ro = UINT8_MAX }, + { .name = "P_VENDOR_SPECIFIC_AREA[15:8]", .addr = 1, + .reset = AX650X_SDHCI_VENDOR_PTR_VALUE >> 8, .ro = UINT8_MAX }, +}; + +static const RegisterAccessInfo ax650x_vendor_regs_info[] = { + { .name = "PHY_CNFG[7:0]", + .addr = AX650X_VENDOR_ADDR(AX650X_SDHCI_PHY_CNFG), + .reset = AX650X_SDHCI_PHY_PWRGOOD, + .ro = AX650X_SDHCI_PHY_PWRGOOD }, + VREG("PHY_CNFG[15:8]", AX650X_SDHCI_PHY_CNFG + 1), + VREG("PHY_CNFG[23:16]", AX650X_SDHCI_PHY_CNFG + 2), + VREG("PHY_CNFG[31:24]", AX650X_SDHCI_PHY_CNFG + 3), + VREG("PHY_CMDPAD_CNFG[7:0]", AX650X_SDHCI_PHY_CMDPAD_CNFG), + VREG("PHY_CMDPAD_CNFG[15:8]", AX650X_SDHCI_PHY_CMDPAD_CNFG + 1), + VREG("PHY_DATAPAD_CNFG[7:0]", AX650X_SDHCI_PHY_DATAPAD_CNFG), + VREG("PHY_DATAPAD_CNFG[15:8]", AX650X_SDHCI_PHY_DATAPAD_CNFG + 1), + VREG("PHY_CLKPAD_CNFG[7:0]", AX650X_SDHCI_PHY_CLKPAD_CNFG), + VREG("PHY_CLKPAD_CNFG[15:8]", AX650X_SDHCI_PHY_CLKPAD_CNFG + 1), + VREG("PHY_STBPAD_CNFG[7:0]", AX650X_SDHCI_PHY_STBPAD_CNFG), + VREG("PHY_STBPAD_CNFG[15:8]", AX650X_SDHCI_PHY_STBPAD_CNFG + 1), + VREG("PHY_RSTNPAD_CNFG[7:0]", AX650X_SDHCI_PHY_RSTNPAD_CNFG), + VREG("PHY_RSTNPAD_CNFG[15:8]", AX650X_SDHCI_PHY_RSTNPAD_CNFG + 1), + VREG("PHY_COMMDL_CNFG", AX650X_SDHCI_PHY_COMMDL_CNFG), + VREG("PHY_SDCLKDL_CNFG", AX650X_SDHCI_PHY_SDCLKDL_CNFG), + VREG("PHY_SDCLKDL_DC", AX650X_SDHCI_PHY_SDCLKDL_DC), + VREG("PHY_SMPLDL_CNFG", AX650X_SDHCI_PHY_SMPLDL_CNFG), + VREG("PHY_ATDL_CNFG", AX650X_SDHCI_PHY_ATDL_CNFG), + { .name = "PHY_DLL_CTRL", + .addr = AX650X_VENDOR_ADDR(AX650X_SDHCI_PHY_DLL_CTRL), + .rsvd = 0xf8, + .post_write = ax650x_dll_ctrl_post_write }, + VREG("PHY_DLL_CNFG1", AX650X_SDHCI_PHY_DLL_CNFG1), + VREG("PHY_DLL_CNFG2", AX650X_SDHCI_PHY_DLL_CNFG2), + VREG("PHY_DLLDL_CNFG", AX650X_SDHCI_PHY_DLLDL_CNFG), + VREG("PHY_DLL_OFFSET", AX650X_SDHCI_PHY_DLL_OFFSET), + VREG("PHY_DLLLBT_CNFG[7:0]", AX650X_SDHCI_PHY_DLLLBT_CNFG), + VREG("PHY_DLLLBT_CNFG[15:8]", AX650X_SDHCI_PHY_DLLLBT_CNFG + 1), + VREG_RO("PHY_DLL_STATUS", AX650X_SDHCI_PHY_DLL_STATUS, 0), + VREG_RO("PHY_DLLDBG_MLKDC", AX650X_SDHCI_PHY_DLLDBG_MLKDC, 98), + VREG_RO("PHY_DLLDBG_SLKDC", AX650X_SDHCI_PHY_DLLDBG_SLKDC, 24), + { .name = "EMMC_CTRL[7:0]", + .addr = AX650X_VENDOR_ADDR(AX650X_SDHCI_EMMC_CTRL), + .rsvd = 0xf2, + .post_write = ax650x_emmc_ctrl_post_write }, + { .name = "EMMC_CTRL[15:8]", + .addr = AX650X_VENDOR_ADDR(AX650X_SDHCI_EMMC_CTRL + 1), + .rsvd = 0xfe }, +}; + +static void ax650x_sdhci_reset(DeviceState *dev) +{ + AX650XSDHCIState *s = AX650X_SDHCI(dev); + + device_cold_reset(DEVICE(&s->sdhci)); + for (unsigned int i = 0; + i < s->pointer_reg_array->num_elements; i++) { + register_reset(s->pointer_reg_array->r[i]); + } + for (unsigned int i = 0; + i < s->vendor_reg_array->num_elements; i++) { + register_reset(s->vendor_reg_array->r[i]); + } + s->emmc_reset_asserted = false; +} + +static void ax650x_sdhci_realize(DeviceState *dev, Error **errp) +{ + AX650XSDHCIState *s = AX650X_SDHCI(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + SysBusDevice *sdhci_sbd = SYS_BUS_DEVICE(&s->sdhci); + + qdev_prop_set_uint8(DEVICE(&s->sdhci), "sd-spec-version", 3); + qdev_prop_set_uint8(DEVICE(&s->sdhci), "uhs", UHS_I); + qdev_prop_set_uint64(DEVICE(&s->sdhci), "capareg", + SDHC_CAPAB_REG_DEFAULT | + R_SDHC_CAPAB_EMBEDDED_8BIT_MASK | + R_SDHC_CAPAB_BUS64BIT_MASK); + if (!sysbus_realize(sdhci_sbd, errp)) { + return; + } + + memory_region_init(&s->container, OBJECT(s), "ax650x.sdhci-container", + AX650X_SDHCI_REG_SIZE); + sysbus_init_mmio(sbd, &s->container); + memory_region_add_subregion(&s->container, 0, + sysbus_mmio_get_region(sdhci_sbd, 0)); + memory_region_add_subregion_overlap(&s->container, + AX650X_SDHCI_VENDOR_PTR, + &s->pointer_reg_array->mem, 1); + memory_region_add_subregion(&s->container, AX650X_SDHCI_VENDOR_BASE, + &s->vendor_reg_array->mem); + sysbus_pass_irq(sbd, sdhci_sbd); +} + +static const VMStateDescription vmstate_ax650x_sdhci = { + .name = TYPE_AX650X_SDHCI, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(pointer_regs, AX650XSDHCIState, 2), + VMSTATE_UINT8_ARRAY(vendor_regs, AX650XSDHCIState, + AX650X_SDHCI_VENDOR_SIZE), + VMSTATE_BOOL(emmc_reset_asserted, AX650XSDHCIState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void ax650x_sdhci_init(Object *obj) +{ + AX650XSDHCIState *s = AX650X_SDHCI(obj); + + object_initialize_child(obj, "generic-sdhci", &s->sdhci, + TYPE_SYSBUS_SDHCI); + s->pointer_reg_array = + register_init_block8(DEVICE(obj), ax650x_pointer_regs_info, + ARRAY_SIZE(ax650x_pointer_regs_info), + s->pointer_regs_info, s->pointer_regs, + &ax650x_register_bank_ops, false, + sizeof(s->pointer_regs)); + s->vendor_reg_array = + register_init_block8(DEVICE(obj), ax650x_vendor_regs_info, + ARRAY_SIZE(ax650x_vendor_regs_info), + s->vendor_regs_info, s->vendor_regs, + &ax650x_register_bank_ops, false, + sizeof(s->vendor_regs)); +} + +static void ax650x_sdhci_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->desc = "Axera AX650X SD/eMMC Host Controller"; + dc->realize = ax650x_sdhci_realize; + device_class_set_legacy_reset(dc, ax650x_sdhci_reset); + dc->vmsd = &vmstate_ax650x_sdhci; +} + +SDBus *ax650x_sdhci_get_bus(AX650XSDHCIState *s) +{ + return &s->sdhci.sdbus; +} + +static const TypeInfo ax650x_sdhci_info = { + .name = TYPE_AX650X_SDHCI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AX650XSDHCIState), + .instance_init = ax650x_sdhci_init, + .class_init = ax650x_sdhci_class_init, +}; + +static void ax650x_sdhci_register_types(void) +{ + type_register_static(&ax650x_sdhci_info); +} + +type_init(ax650x_sdhci_register_types) diff --git a/hw/sd/dw_mmc.c b/hw/sd/dw_mmc.c new file mode 100644 index 0000000000000..85286e8dd4209 --- /dev/null +++ b/hw/sd/dw_mmc.c @@ -0,0 +1,992 @@ +/* + * DesignWare Mobile Storage Host Controller (dw_mmc). + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * SysBusDevice model for Synopsys dw_mmc. RK3588 wires one instance as + * sdmmc@fe2c0000. See include/hw/sd/dw_mmc.h for the design contract. The + * 16 KiB register window is served with hand-written MMIO ops because nearly + * every register has side effects (CMD kicks the SD card, RINTSTS is W1C, + * FIFO data port pushes/pops, PLDMND kicks IDMAC, etc.); the registerinfo + * framework's strengths do not help here. + * + * Behavior summary: + * - CMD reg write with START set: assemble an SDRequest from + * CMDARG + cmd flags, run sdbus_do_command, copy the response + * into RESP0-3 (long resp maps R2 the way dw_mmc.c:1782-1787 + * expects), set CMD_DONE / error bits, raise IRQ. + * - CMD with DAT_EXP: set up a transfer; if CTRL[USE_IDMAC]+BMOD + * are armed the data phase is served by walking the IDMAC + * descriptor ring; otherwise the FIFO port is used (PIO) and + * RXDR/TXDR fire as the FIFO crosses a watermark. + * - RINTSTS / IDSTS: W1C. MINTSTS = RINTSTS & INTMASK. + * - STATUS: RO computed (BUSY=0, FIFO level/watermarks from + * the model FIFO). + * - CDETECT: RO tied to sdbus_get_inserted. + * - RK vendor regs (0x130-0x138): RAZ/WI via a separate MemoryRegion + * aliased over the top of the bank so writes there do not abort. + * + * The whole 0x4000 window is covered so no guest MMIO access aborts + * (D-15): the iomem region covers the core registers plus the 0x200 legacy + * FIFO data port, and the vendor region covers the rest of the 0x4000 window + * as RAZ/WI. + */ + +#include "qemu/osdep.h" +#include "hw/sd/dw_mmc.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include "hw/core/irq.h" +#include "system/blockdev.h" +#include "system/physmem.h" +#include "exec/cpu-common.h" +#include "qemu/bswap.h" + +/* CTRL (0x000) bits. */ +#define CTRL_CTRL_RESET BIT(0) +#define CTRL_FIFO_RESET BIT(1) +#define CTRL_DMA_RESET BIT(2) +#define CTRL_INT_ENABLE BIT(4) +#define CTRL_DMA_ENABLE BIT(5) +#define CTRL_USE_IDMAC BIT(25) + +/* CMD (0x02c) bits. */ +#define CMD_START BIT(31) +#define CMD_USE_HOLD_REG BIT(29) +#define CMD_VOLT_SWITCH BIT(28) +#define CMD_CCS_EXP BIT(23) +#define CMD_CEATA_RD BIT(22) +#define CMD_UPD_CLK BIT(21) +#define CMD_INIT BIT(15) +#define CMD_STOP BIT(14) +#define CMD_PRV_DAT_WAIT BIT(13) +#define CMD_SEND_STOP BIT(12) +#define CMD_STRM_MODE BIT(11) +#define CMD_DAT_WR BIT(10) +#define CMD_DAT_EXP BIT(9) +#define CMD_RESP_CRC BIT(8) +#define CMD_RESP_LONG BIT(7) +#define CMD_RESP_EXP BIT(6) +#define CMD_INDX 0x3f + +/* RINTSTS / INTMASK bit layout. */ +#define INT_CD BIT(0) +#define INT_RESP_ERR BIT(1) +#define INT_CMD_DONE BIT(2) +#define INT_DATA_OVER BIT(3) +#define INT_TXDR BIT(4) +#define INT_RXDR BIT(5) +#define INT_RCRC BIT(6) +#define INT_DCRC BIT(7) +#define INT_RTO BIT(8) +#define INT_DRTO BIT(9) +#define INT_HTO BIT(10) +#define INT_FRUN BIT(11) +#define INT_HLE BIT(12) +#define INT_SBE BIT(13) +#define INT_ACD BIT(14) +#define INT_EBE BIT(15) +#define INT_SDIO(n) BIT(16 + (n)) +#define INT_SDIO_DEFAULT INT_SDIO(8) /* RK overloads bit 24 */ + +/* STATUS (0x048) computed. */ +#define STATUS_FIFO_RX_WMARK BIT(0) +#define STATUS_FIFO_TX_WMARK BIT(1) +#define STATUS_FCNT_SHIFT 17 +#define STATUS_FCNT_MASK (0x1fff << STATUS_FCNT_SHIFT) +#define STATUS_BUSY BIT(9) +#define STATUS_FIFO_EMPTY BIT(2) +#define STATUS_FIFO_FULL BIT(3) + +/* FIFOTH (0x04c) fields. */ +#define FIFOTH_TX_WMARK_SHIFT 0 +#define FIFOTH_TX_WMARK_MASK 0xfff +#define FIFOTH_RX_WMARK_SHIFT 16 +#define FIFOTH_RX_WMARK_MASK (0xfff << FIFOTH_RX_WMARK_SHIFT) +#define FIFOTH_DMA_MSIZE_SHIFT 28 + +/* BMOD (0x080) bits. */ +#define BMOD_SWRESET BIT(0) +#define BMOD_FB BIT(1) +#define BMOD_ENABLE BIT(7) + +/* IDSTS / IDINTEN bits. */ +#define IDSTS_TI BIT(0) +#define IDSTS_RI BIT(1) +#define IDSTS_FBE BIT(2) +#define IDSTS_DU BIT(4) +#define IDSTS_CES BIT(5) +#define IDSTS_NI BIT(8) +#define IDSTS_AI BIT(9) +#define IDMAC_INT_CLR (IDSTS_TI | IDSTS_RI | IDSTS_FBE | IDSTS_DU | \ + IDSTS_CES | IDSTS_NI | IDSTS_AI) + +/* IDMAC descriptor des0 bits (dw_mmc.c:78-84). */ +#define IDMAC_DES0_DIC BIT(1) +#define IDMAC_DES0_LD BIT(2) +#define IDMAC_DES0_FD BIT(3) +#define IDMAC_DES0_CH BIT(4) +#define IDMAC_DES0_ER BIT(5) +#define IDMAC_DES0_CES BIT(30) +#define IDMAC_DES0_OWN BIT(31) + +/* IP defaults the driver probes against. */ +#define DW_MMC_CORE_MMIO_SIZE 0x204 +#define DW_MMC_VERID 0x270a /* >= 0x240a also exposes data @ 0x100 */ +#define DW_MMC_HCON 0x00000000 /* TRANS_MODE=IDMA, ADDR_CONFIG=32-bit, + NUM_SLOTS=0, HDATA_WIDTH=16-bit */ +/* RK3588 overloads SDIO slot 8 -> bit 24 for sdio_irq. */ +#define DW_MMC_SDIO_IRQ_BIT 24 + +static void dw_mmc_fifo_reset(DwMmcState *s); + +/* ------------------------------------------------------------------------- */ +/* IRQ evaluation */ +/* ------------------------------------------------------------------------- */ + +static void dw_mmc_update_irq(DwMmcState *s) +{ + uint32_t mintsts = s->rintsts & s->intmask; + bool raise = (s->ctrl & CTRL_INT_ENABLE) && mintsts != 0; + qemu_set_irq(s->irq, raise); +} + +static void dw_mmc_maybe_send_auto_stop(DwMmcState *s) +{ + SDRequest req = { + .cmd = 12, /* CMD12: STOP_TRANSMISSION */ + .arg = 0, + }; + uint8_t response[16]; + + if (!s->transfer_send_stop) { + return; + } + + s->transfer_send_stop = false; + sdbus_do_command(&s->sdbus, &req, response, sizeof(response)); + s->rintsts |= INT_ACD; +} + +static void dw_mmc_abort_pending_data(DwMmcState *s) +{ + SDRequest req = { + .cmd = 12, /* CMD12: STOP_TRANSMISSION */ + .arg = 0, + }; + uint8_t response[16]; + + if (!sdbus_data_ready(&s->sdbus) && !sdbus_receive_ready(&s->sdbus)) { + return; + } + + sdbus_do_command(&s->sdbus, &req, response, sizeof(response)); + s->rintsts |= INT_ACD; + s->transfer_active = false; + s->transfer_bytes_remaining = 0; + s->transfer_send_stop = false; + dw_mmc_fifo_reset(s); +} + +/* ------------------------------------------------------------------------- */ +/* FIFO */ +/* ------------------------------------------------------------------------- */ + +static uint32_t dw_mmc_fifo_used_words(DwMmcState *s) +{ + /* STATUS.FCNT reports the number of FIFO words the host can read. */ + return s->fifo_len / 4; +} + +static uint32_t dw_mmc_fifo_pop_le32(DwMmcState *s) +{ + uint32_t v; + if (s->fifo_len < 4) { + return 0; + } + v = ldl_le_p(s->fifo + s->fifo_pos); + s->fifo_pos += 4; + s->fifo_len -= 4; + if (s->fifo_pos >= sizeof(s->fifo)) { + s->fifo_pos = 0; + } + if (s->fifo_len == 0) { + s->fifo_pos = 0; + } + return v; +} + +static void dw_mmc_fifo_reset(DwMmcState *s) +{ + s->fifo_len = 0; + s->fifo_pos = 0; + memset(s->fifo, 0, sizeof(s->fifo)); +} + +static void dw_mmc_pio_refill_read_fifo(DwMmcState *s) +{ + uint32_t count; + + if (!s->transfer_active || s->transfer_is_write || + s->transfer_bytes_remaining == 0 || s->fifo_len != 0) { + return; + } + + count = MIN(s->transfer_bytes_remaining, + (uint32_t)(sizeof(s->fifo) - s->fifo_len)); + if (count == 0) { + return; + } + + sdbus_read_data(&s->sdbus, s->fifo + s->fifo_len, count); + s->fifo_len += count; + s->transfer_bytes_remaining -= count; + s->rintsts |= INT_RXDR; + + if (s->transfer_bytes_remaining == 0) { + dw_mmc_maybe_send_auto_stop(s); + s->rintsts |= INT_DATA_OVER; + } + dw_mmc_update_irq(s); +} + +static uint32_t dw_mmc_read_data_port(DwMmcState *s, uint32_t fallback) +{ + uint32_t r = fallback; + + if (s->transfer_active && !s->transfer_is_write && s->fifo_len >= 4) { + r = dw_mmc_fifo_pop_le32(s); + if (s->fifo_len == 0) { + if (s->transfer_bytes_remaining != 0) { + dw_mmc_pio_refill_read_fifo(s); + } else { + dw_mmc_maybe_send_auto_stop(s); + s->rintsts |= INT_DATA_OVER; + s->transfer_active = false; + dw_mmc_update_irq(s); + } + } + } + + return r; +} + +static void dw_mmc_write_data_port(DwMmcState *s, uint32_t value, + bool fallback_to_cdthrctl) +{ + if (s->transfer_active && s->transfer_is_write) { + uint8_t buf[4]; + + stl_le_p(buf, value); + sdbus_write_data(&s->sdbus, buf, sizeof(buf)); + + if (s->transfer_bytes_remaining > sizeof(buf)) { + s->transfer_bytes_remaining -= sizeof(buf); + s->rintsts |= INT_TXDR; + } else { + s->transfer_bytes_remaining = 0; + s->transfer_active = false; + dw_mmc_maybe_send_auto_stop(s); + s->rintsts |= INT_DATA_OVER; + } + dw_mmc_update_irq(s); + } else if (fallback_to_cdthrctl) { + s->cdthrctl = value; + } +} + +/* ------------------------------------------------------------------------- */ +/* IDMAC descriptor-DMA engine */ +/* ------------------------------------------------------------------------- */ + +/* + * Walk the IDMAC descriptor ring starting at s->dbaddr, transfer + * buffer1 of each descriptor between the SD card and guest RAM, clear + * OWN per descriptor, and raise IDSTS RI (read) or TI (write) + NI + * summary at the end. RXTX direction is determined by the last + * CMD's CMD_DAT_WR bit. + * + * 32-bit mode only (HCON[ADDR_CONFIG]=0 on RK3588). + */ +static void dw_mmc_idmac_kick(DwMmcState *s) +{ + uint32_t desc_addr = s->dbaddr & ~0x3u; + uint8_t desc[16]; + bool is_write = s->transfer_is_write; + uint32_t processed = 0; + + while (processed++ < DW_MMC_IDMAC_MAX_DESCS) { + physical_memory_read(desc_addr, desc, sizeof(desc)); + uint32_t des0 = ldl_le_p(desc + 0); + uint32_t des1 = ldl_le_p(desc + 4); + uint32_t des2 = ldl_le_p(desc + 8); + uint32_t des3 = ldl_le_p(desc + 12); + + if (!(des0 & IDMAC_DES0_OWN)) { + /* Not owned by DMA - engine stops here. */ + break; + } + if (s->transfer_bytes_remaining == 0) { + break; + } + + uint32_t buf_size = des1 & 0x1fff; + if (buf_size == 0) { + buf_size = 0x2000; + } + if (s->transfer_bytes_remaining != 0) { + buf_size = MIN(buf_size, s->transfer_bytes_remaining); + } + if (buf_size > 0 && des2 != 0) { + if (is_write) { + /* TX: host RAM -> FIFO -> card. */ + g_autofree uint8_t *buf = g_malloc(buf_size); + physical_memory_read(des2, buf, buf_size); + sdbus_write_data(&s->sdbus, buf, buf_size); + } else { + /* RX: card -> FIFO -> host RAM. */ + g_autofree uint8_t *buf = g_malloc(buf_size); + memset(buf, 0, buf_size); + sdbus_read_data(&s->sdbus, buf, buf_size); + physical_memory_write(des2, buf, buf_size); + } + if (s->transfer_bytes_remaining > buf_size) { + s->transfer_bytes_remaining -= buf_size; + } else { + s->transfer_bytes_remaining = 0; + } + } + + /* Hand the descriptor back to the host (clear OWN). */ + des0 &= ~IDMAC_DES0_OWN; + stl_le_p(desc + 0, des0); + physical_memory_write(desc_addr, desc, sizeof(desc)); + + if (des0 & IDMAC_DES0_LD) { + /* Last descriptor - raise completion. */ + break; + } + if (s->transfer_bytes_remaining == 0) { + break; + } + if (!(des0 & IDMAC_DES0_CH)) { + /* No chaining - stop. */ + break; + } + + /* Follow des3 -> next descriptor. */ + desc_addr = des3 & ~0x3u; + if (desc_addr == 0) { + break; + } + } + + s->idsts |= is_write ? IDSTS_TI : IDSTS_RI; + s->idsts |= IDSTS_NI; + dw_mmc_maybe_send_auto_stop(s); + s->rintsts |= INT_DATA_OVER; + s->transfer_active = false; + + if (s->idinten & (IDSTS_TI | IDSTS_RI | IDSTS_NI)) { + /* IDSTS interrupts feed the host IRQ via the IDINTEN mask; + * dw_mmc.c also gates these on INT_DATA_OVER in MINTSTS, but + * the controller routes IDMAC irqs via the SDMMC_INT_IDMAC + * path (bit not in INTMASK) - keep simple: just mirror the + * DATA_OVER into MINTSTS so the host IRQ fires. */ + } + dw_mmc_update_irq(s); +} + +/* ------------------------------------------------------------------------- */ +/* Command sequencing */ +/* ------------------------------------------------------------------------- */ + +/* + * Issue the command described by CMDARG + the CMD register's flag + * bits. Reads the response from the attached SD card and lays it out + * in RESP0-3. Sets CMD_DONE (or RTO) and either kicks the IDMAC + * engine (if DAT_EXP && CTRL[USE_IDMAC] && BMOD[ENABLE]) or sets up + * the FIFO transfer for PIO. The START bit self-clears on completion. + * + * R2 long-response mapping (dw_mmc.c:1782-1787): the controller + * presents the card's 16-byte big-endian R2 with RESP0<-resp[3], + * RESP1<-resp[2], RESP2<-resp[1], RESP3<-resp[0]&~1. The Linux + * driver reads RESP0 into cmd->resp[3], so this is the layout it + * expects. + */ +static void dw_mmc_issue_command(DwMmcState *s) +{ + SDRequest req; + uint8_t response[16]; + size_t rlen; + bool expect_resp = s->cmd & CMD_RESP_EXP; + bool long_resp = s->cmd & CMD_RESP_LONG; + bool data_expected = s->cmd & CMD_DAT_EXP; + bool data_write = s->cmd & CMD_DAT_WR; + + if (s->cmd & CMD_UPD_CLK) { + s->rintsts |= INT_CMD_DONE; + s->cmd &= ~CMD_START; + dw_mmc_update_irq(s); + return; + } + + req.cmd = s->cmd & CMD_INDX; + req.arg = s->cmdarg; + + if (req.cmd != 12 && req.cmd != 13) { + dw_mmc_abort_pending_data(s); + } + + rlen = sdbus_do_command(&s->sdbus, &req, response, sizeof(response)); + if (rlen == 0 && data_expected && expect_resp && + (req.cmd == 18 || req.cmd == 25)) { + /* + * Some firmware probes with CMD55 and then immediately issues a normal + * multi-block data command. The SD model treats CMD55+CMD18/CMD25 as + * unimplemented SD security commands; retry once after that prefix has + * been consumed so the ordinary data path can proceed. + */ + rlen = sdbus_do_command(&s->sdbus, &req, response, sizeof(response)); + } + + if (expect_resp) { + if (rlen == 0) { + s->rintsts |= INT_RTO; + } else if (long_resp && rlen == 16) { + /* R2-style 128-bit response; see dw_mmc.c:1782-1787. */ + s->resp[0] = ldl_be_p(response + 12) & ~1u; + s->resp[1] = ldl_be_p(response + 8); + s->resp[2] = ldl_be_p(response + 4); + s->resp[3] = ldl_be_p(response + 0); + s->rintsts |= INT_CMD_DONE; + } else if (rlen == 4) { + s->resp[0] = ldl_be_p(response + 0); + s->resp[1] = s->resp[2] = s->resp[3] = 0; + s->rintsts |= INT_CMD_DONE; + } else if (rlen == 16) { + /* Card gave a long response we didn't expect as long - + * treat as short: copy first 4 bytes. */ + s->resp[0] = ldl_be_p(response + 0); + s->resp[1] = s->resp[2] = s->resp[3] = 0; + s->rintsts |= INT_CMD_DONE; + } else { + s->rintsts |= INT_RESP_ERR; + } + } else { + /* No response expected - CMD_SENT. */ + s->rintsts |= INT_CMD_DONE; + } + (void)data_write; + + if (data_expected) { + /* Set up the data phase. */ + s->transfer_bytes_remaining = s->bytcnt; + s->transfer_is_write = data_write; + s->transfer_send_stop = s->cmd & CMD_SEND_STOP; + s->transfer_active = true; + if ((s->ctrl & (CTRL_DMA_ENABLE | CTRL_USE_IDMAC)) == + (CTRL_DMA_ENABLE | CTRL_USE_IDMAC) && + (s->bmod & BMOD_ENABLE)) { + dw_mmc_idmac_kick(s); + } else { + /* + * PIO: signal RXDR/TXDR immediately so the host can + * start filling/draining the FIFO via the data port. + */ + if (data_write) { + s->rintsts |= INT_TXDR; + } else { + dw_mmc_pio_refill_read_fifo(s); + } + } + } + + /* START self-clears on completion. */ + s->cmd &= ~CMD_START; + dw_mmc_update_irq(s); +} + +/* ------------------------------------------------------------------------- */ +/* MMIO ops */ +/* ------------------------------------------------------------------------- */ + +static uint64_t dw_mmc_read(void *opaque, hwaddr offset, unsigned size) +{ + DwMmcState *s = DW_MMC(opaque); + uint32_t r = 0; + + switch (offset) { + case 0x000: /* CTRL */ + r = s->ctrl; + break; + case 0x004: + r = s->pwren; + break; + case 0x008: + r = s->clkdiv; + break; + case 0x00c: + r = s->clksrc; + break; + case 0x010: + r = s->clkena; + break; + case 0x014: + r = s->tmout; + break; + case 0x018: + r = s->ctype; + break; + case 0x01c: + r = s->blksiz; + break; + case 0x020: + r = s->bytcnt; + break; + case 0x024: + r = s->intmask; + break; + case 0x028: + r = s->cmdarg; + break; + case 0x02c: + r = s->cmd; + break; + case 0x030: + r = s->resp[0]; + break; + case 0x034: + r = s->resp[1]; + break; + case 0x038: + r = s->resp[2]; + break; + case 0x03c: + r = s->resp[3]; + break; + case 0x040: /* MINTSTS = RINTSTS & INTMASK */ + r = s->rintsts & s->intmask; + break; + case 0x044: + r = s->rintsts; + break; + case 0x048: { /* STATUS - computed. */ + uint32_t used_words = dw_mmc_fifo_used_words(s); + uint32_t tx_wmark = (s->fifoth & FIFOTH_TX_WMARK_MASK) >> + FIFOTH_TX_WMARK_SHIFT; + uint32_t rx_wmark = (s->fifoth & FIFOTH_RX_WMARK_MASK) >> + FIFOTH_RX_WMARK_SHIFT; + + r = (used_words << STATUS_FCNT_SHIFT) & STATUS_FCNT_MASK; + if (s->fifo_len == 0) { + r |= STATUS_FIFO_EMPTY; + } + if (s->fifo_len == sizeof(s->fifo)) { + r |= STATUS_FIFO_FULL; + } + if (used_words <= tx_wmark) { + r |= STATUS_FIFO_TX_WMARK; + } + if (used_words > rx_wmark) { + r |= STATUS_FIFO_RX_WMARK; + } + break; + } + case 0x04c: + r = s->fifoth; + break; + case 0x050: /* CDETECT - bit0=0 means card present (active low). */ + r = sdbus_get_inserted(&s->sdbus) ? 0 : 1; + break; + case 0x054: /* WRTPRT */ + r = sdbus_get_readonly(&s->sdbus) ? 1 : 0; + break; + case 0x058: /* GPIO */ + r = 0; + break; + case 0x05c: /* TCBCNT */ + r = 0; + break; + case 0x060: /* TBBCNT */ + r = 0; + break; + case 0x064: + r = s->debnce; + break; + case 0x068: + r = s->usrid; + break; + case 0x06c: + r = s->verid; + break; + case 0x070: + r = s->hcon; + break; + case 0x074: + r = s->uhs_reg; + break; + case 0x078: + r = s->rst_n; + break; + case 0x080: + r = s->bmod; + break; + case 0x084: /* PLDMND - WO */ + r = 0; + break; + case 0x088: + r = s->dbaddr; + break; + case 0x08c: + r = s->idsts; + break; + case 0x090: + r = s->idinten; + break; + case 0x094: /* DSCADDR */ + r = 0; + break; + case 0x098: /* BUFADDR */ + r = 0; + break; + case 0x100: /* CDTHRCTL or FIFO data port for VERID >= 0x240a. */ + r = dw_mmc_read_data_port(s, s->cdthrctl); + break; + case 0x200: /* Legacy FIFO data port used by U-Boot dw_mmc. */ + r = dw_mmc_read_data_port(s, 0); + break; + case 0x108: /* UHS_REG_EXT */ + r = 0; + break; + case 0x10c: /* DDR_REG */ + r = 0; + break; + case 0x110: + r = s->enable_shift; + break; + default: + if (offset >= 0x114 && offset < DW_MMC_CORE_MMIO_SIZE) { + r = 0; + break; + } + qemu_log_mask(LOG_GUEST_ERROR, + "dw_mmc: unsupported read offset 0x%03x\n", + (unsigned)offset); + r = 0; + break; + } + + return r; +} + +static void dw_mmc_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + DwMmcState *s = DW_MMC(opaque); + uint32_t v = (uint32_t)value; + + switch (offset) { + case 0x000: /* CTRL */ + s->ctrl = v & ~(CTRL_CTRL_RESET | CTRL_FIFO_RESET | CTRL_DMA_RESET); + if (v & CTRL_FIFO_RESET) { + dw_mmc_fifo_reset(s); + } + if (v & CTRL_DMA_RESET) { + s->idsts = 0; + } + if (v & CTRL_CTRL_RESET) { + s->transfer_active = false; + s->transfer_bytes_remaining = 0; + s->transfer_send_stop = false; + } + /* Reset bits self-clear immediately in this model. */ + dw_mmc_update_irq(s); + break; + case 0x004: + s->pwren = v; + break; + case 0x008: + s->clkdiv = v; + break; + case 0x00c: + /* CLKSRC writes are ignored except 0 (single clock source). */ + s->clksrc = 0; + break; + case 0x010: + s->clkena = v; + break; + case 0x014: + s->tmout = v; + break; + case 0x018: + s->ctype = v; + break; + case 0x01c: + s->blksiz = v; + break; + case 0x020: + s->bytcnt = v; + break; + case 0x024: /* INTMASK */ + s->intmask = v; + dw_mmc_update_irq(s); + break; + case 0x028: + s->cmdarg = v; + break; + case 0x02c: /* CMD */ + s->cmd = v; + if (v & CMD_START) { + dw_mmc_issue_command(s); + } + break; + case 0x030: case 0x034: case 0x038: case 0x03c: + /* RESP* are RO. */ + break; + case 0x040: /* MINTSTS - RO. */ + break; + case 0x044: /* RINTSTS - W1C. */ + s->rintsts &= ~v; + dw_mmc_update_irq(s); + break; + case 0x048: /* STATUS - RO. */ + break; + case 0x04c: /* FIFOTH */ + s->fifoth = v; + break; + case 0x050: /* CDETECT - RO. */ + break; + case 0x054: + break; + case 0x058: /* GPIO - RAZ/WI */ + break; + case 0x05c: + case 0x060: /* TCBCNT/TBBCNT RO */ + break; + case 0x064: + s->debnce = v; + break; + case 0x068: + s->usrid = v; + break; + case 0x06c: /* VERID RO */ + break; + case 0x070: /* HCON RO */ + break; + case 0x074: + s->uhs_reg = v; + break; + case 0x078: + s->rst_n = v; + break; + case 0x080: /* BMOD - SWRESET self-clears. */ + s->bmod = v & ~BMOD_SWRESET; + break; + case 0x084: /* PLDMND - write 1 kicks IDMAC. */ + if (v && (s->ctrl & (CTRL_DMA_ENABLE | CTRL_USE_IDMAC)) && + (s->bmod & BMOD_ENABLE) && s->transfer_active) { + dw_mmc_idmac_kick(s); + } + break; + case 0x088: + s->dbaddr = v; + break; + case 0x08c: /* IDSTS - W1C. */ + s->idsts &= ~v; + dw_mmc_update_irq(s); + break; + case 0x090: + s->idinten = v; + break; + case 0x094: + case 0x098: /* DSCADDR/BUFADDR RO */ + break; + case 0x100: /* CDTHRCTL or FIFO data port for VERID >= 0x240a. */ + dw_mmc_write_data_port(s, v, true); + break; + case 0x200: /* Legacy FIFO data port used by U-Boot dw_mmc. */ + dw_mmc_write_data_port(s, v, false); + break; + case 0x108: + case 0x10c: /* UHS_REG_EXT / DDR_REG */ + break; + case 0x110: + s->enable_shift = v; + break; + default: + if (offset >= 0x114 && offset < DW_MMC_CORE_MMIO_SIZE) { + break; + } + qemu_log_mask(LOG_GUEST_ERROR, + "dw_mmc: unsupported write offset 0x%03x val 0x%x\n", + (unsigned)offset, v); + break; + } +} + +static const MemoryRegionOps dw_mmc_ops = { + .read = dw_mmc_read, + .write = dw_mmc_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +/* RAZ/WI ops for the RK vendor tail after the legacy FIFO data port. */ +static uint64_t dw_mmc_vendor_read(void *opaque, hwaddr off, unsigned sz) +{ + return 0; +} + +static void dw_mmc_vendor_write(void *opaque, hwaddr off, uint64_t v, + unsigned sz) +{ + /* drop */ +} + +static const MemoryRegionOps dw_mmc_vendor_ops = { + .read = dw_mmc_vendor_read, + .write = dw_mmc_vendor_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { .min_access_size = 1, .max_access_size = 8 }, + .impl = { .min_access_size = 1, .max_access_size = 8 }, +}; + +/* ------------------------------------------------------------------------- */ +/* Device lifecycle */ +/* ------------------------------------------------------------------------- */ + +static void dw_mmc_set_inserted(DeviceState *dev, bool inserted) +{ + DwMmcState *s = DW_MMC(dev); + if (inserted) { + s->rintsts &= ~INT_CD; + } else { + s->rintsts |= INT_CD; + } + dw_mmc_update_irq(s); +} + +static void dw_mmc_set_readonly(DeviceState *dev, bool readonly) +{ + /* nothing modelled */ +} + +static void dw_mmc_reset(DeviceState *dev) +{ + DwMmcState *s = DW_MMC(dev); + + s->ctrl = 0; + s->pwren = 0; + s->clkdiv = 0; + s->clksrc = 0; + s->clkena = 0; + s->tmout = 0xffffffff; + s->ctype = 0; + s->blksiz = 0; + s->bytcnt = 0; + s->intmask = 0; + s->cmdarg = 0; + s->cmd = 0; + memset(s->resp, 0, sizeof(s->resp)); + s->rintsts = 0; + s->fifoth = 0x00800000; /* POR: RX watermark = depth-1 */ + s->debnce = 0; + s->usrid = 0; + s->verid = DW_MMC_VERID; + s->hcon = DW_MMC_HCON; + s->uhs_reg = 0; + s->rst_n = 1; /* RST_HWACTIVE active high */ + s->bmod = 0; + s->dbaddr = 0; + s->idsts = 0; + s->idinten = 0; + s->cdthrctl = 0; + s->enable_shift = 0; + + s->transfer_bytes_remaining = 0; + s->transfer_is_write = false; + s->transfer_send_stop = false; + s->transfer_active = false; + dw_mmc_fifo_reset(s); + + dw_mmc_update_irq(s); +} + +static void dw_mmc_init(Object *obj) +{ + DwMmcState *s = DW_MMC(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + memory_region_init_io(&s->iomem, obj, &dw_mmc_ops, s, "dw-mmc", + DW_MMC_CORE_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + + /* RK vendor / RAZ tail after the legacy FIFO data port. */ + memory_region_init_io(&s->vendor, obj, &dw_mmc_vendor_ops, s, + "dw-mmc-vendor", + DW_MMC_MMIO_SIZE - DW_MMC_CORE_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->vendor); + + sysbus_init_irq(sbd, &s->irq); + qdev_init_gpio_out_named(DEVICE(s), &s->card_inserted, + "card-inserted", 1); + qdev_init_gpio_out_named(DEVICE(s), &s->card_readonly, + "card-read-only", 1); + + qbus_init(&s->sdbus, sizeof(s->sdbus), TYPE_DW_MMC_BUS, DEVICE(s), + "sd-bus"); +} + +static void dw_mmc_realize(DeviceState *dev, Error **errp) +{ + DwMmcState *s = DW_MMC(dev); + + /* Initialize the SD card (if any) attached via the bus. */ + /* Card creation is handled by the board (mirrors sdhci). */ + (void)s; +} + +static void dw_mmc_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + dc->realize = dw_mmc_realize; + device_class_set_legacy_reset(dc, dw_mmc_reset); + dc->user_creatable = false; +} + +static void dw_mmc_bus_class_init(ObjectClass *oc, const void *data) +{ + SDBusClass *sbc = SD_BUS_CLASS(oc); + sbc->set_inserted = dw_mmc_set_inserted; + sbc->set_readonly = dw_mmc_set_readonly; +} + +static const TypeInfo dw_mmc_types[] = { + { + .name = TYPE_DW_MMC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(DwMmcState), + .instance_init = dw_mmc_init, + .class_init = dw_mmc_class_init, + }, + { + .name = TYPE_DW_MMC_BUS, + .parent = TYPE_SD_BUS, + .instance_size = sizeof(SDBus), + .class_init = dw_mmc_bus_class_init, + }, +}; + +DEFINE_TYPES(dw_mmc_types) diff --git a/hw/sd/k230_sdhci.c b/hw/sd/k230_sdhci.c new file mode 100644 index 0000000000000..1e446f2415e26 --- /dev/null +++ b/hw/sd/k230_sdhci.c @@ -0,0 +1,163 @@ +/* + * K230 DWC MSHC SDHCI controller + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "qemu/bitops.h" +#include "hw/core/qdev-properties.h" +#include "migration/vmstate.h" +#include "hw/sd/k230_sdhci.h" + +#define K230_SDHCI_MMIO_SIZE 0x1000 +#define K230_SDHCI_STD_SIZE 0x100 +#define K230_SDHCI_CAPAREG 0x057c34b4 + +#define K230_DWC_MSHC_PHY_CNFG 0x300 +#define K230_DWC_MSHC_PHY_PWRGOOD BIT(1) + +static uint64_t k230_sdhci_read_bytes(uint8_t *regs, hwaddr addr, + unsigned int size) +{ + uint64_t val = 0; + + for (int i = 0; i < size; i++) { + val |= (uint64_t)regs[addr + i] << (i * 8); + } + + return val; +} + +static void k230_sdhci_write_bytes(uint8_t *regs, hwaddr addr, uint64_t val, + unsigned int size) +{ + for (int i = 0; i < size; i++) { + regs[addr + i] = val >> (i * 8); + } +} + +static void k230_sdhci_set_phy_power_good(K230SdhciState *s) +{ + hwaddr phy_cnfg = K230_DWC_MSHC_PHY_CNFG - K230_SDHCI_STD_SIZE; + + s->vendor_regs[phy_cnfg] |= K230_DWC_MSHC_PHY_PWRGOOD; +} + +static uint64_t k230_sdhci_vendor_read(void *opaque, hwaddr addr, + unsigned int size) +{ + return k230_sdhci_read_bytes(K230_SDHCI(opaque)->vendor_regs, addr, size); +} + +static void k230_sdhci_vendor_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230SdhciState *s = K230_SDHCI(opaque); + + k230_sdhci_write_bytes(s->vendor_regs, addr, val, size); + + if (addr <= K230_DWC_MSHC_PHY_CNFG - K230_SDHCI_STD_SIZE && + addr + size > K230_DWC_MSHC_PHY_CNFG - K230_SDHCI_STD_SIZE) { + k230_sdhci_set_phy_power_good(s); + } +} + +static const MemoryRegionOps k230_sdhci_vendor_ops = { + .read = k230_sdhci_vendor_read, + .write = k230_sdhci_vendor_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void k230_sdhci_instance_init(Object *obj) +{ + K230SdhciState *s = K230_SDHCI(obj); + + object_initialize_child(obj, "generic-sdhci", &s->sdhci, + TYPE_SYSBUS_SDHCI); + qdev_prop_set_uint8(DEVICE(&s->sdhci), "sd-spec-version", 3); + qdev_prop_set_uint64(DEVICE(&s->sdhci), "capareg", K230_SDHCI_CAPAREG); +} + +static void k230_sdhci_reset(DeviceState *dev) +{ + K230SdhciState *s = K230_SDHCI(dev); + + memset(s->vendor_regs, 0, sizeof(s->vendor_regs)); + k230_sdhci_set_phy_power_good(s); + device_cold_reset(DEVICE(&s->sdhci)); +} + +static void k230_sdhci_realize(DeviceState *dev, Error **errp) +{ + K230SdhciState *s = K230_SDHCI(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + SysBusDevice *sdhci_sbd = SYS_BUS_DEVICE(&s->sdhci); + + memory_region_init(&s->container, OBJECT(s), "k230.sdhci-container", + K230_SDHCI_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->container); + + memory_region_init_io(&s->vendor_iomem, OBJECT(s), + &k230_sdhci_vendor_ops, s, "k230.sdhci-vendor", + K230_SDHCI_VENDOR_SIZE); + memory_region_add_subregion(&s->container, K230_SDHCI_STD_SIZE, + &s->vendor_iomem); + + if (!sysbus_realize(sdhci_sbd, errp)) { + return; + } + memory_region_add_subregion(&s->container, 0, + sysbus_mmio_get_region(sdhci_sbd, 0)); + + sysbus_pass_irq(sbd, sdhci_sbd); + s->sd_bus = qdev_get_child_bus(DEVICE(sdhci_sbd), "sd-bus"); +} + +static const VMStateDescription vmstate_k230_sdhci = { + .name = TYPE_K230_SDHCI, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(vendor_regs, K230SdhciState, + K230_SDHCI_VENDOR_SIZE), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_sdhci_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = k230_sdhci_realize; + device_class_set_legacy_reset(dc, k230_sdhci_reset); + dc->vmsd = &vmstate_k230_sdhci; + dc->desc = "K230 DWC MSHC SDHCI controller"; +} + +static const TypeInfo k230_sdhci_type_info = { + .name = TYPE_K230_SDHCI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230SdhciState), + .instance_init = k230_sdhci_instance_init, + .class_init = k230_sdhci_class_init, +}; + +static void k230_register_sdhci_types(void) +{ + type_register_static(&k230_sdhci_type_info); +} + +type_init(k230_register_sdhci_types) diff --git a/hw/sd/meson.build b/hw/sd/meson.build index b43d45bc564ad..bf3ec8b98458d 100644 --- a/hw/sd/meson.build +++ b/hw/sd/meson.build @@ -1,5 +1,8 @@ system_ss.add(when: 'CONFIG_PL181', if_true: files('pl181.c')) system_ss.add(when: 'CONFIG_SD', if_true: files('sd.c', 'core.c')) +system_ss.add(when: 'CONFIG_DW_MMC', if_true: files('dw_mmc.c')) +system_ss.add(when: 'CONFIG_PHYTIUM_MCI', if_true: files('phytium-mci.c')) +system_ss.add(when: 'CONFIG_ROCKCHIP_DWCMSHC', if_true: files('rockchip_dwcmshc.c')) system_ss.add(when: 'CONFIG_SDHCI', if_true: files('sdhci.c')) system_ss.add(when: 'CONFIG_SDHCI_PCI', if_true: files('sdhci-pci.c')) system_ss.add(when: 'CONFIG_SSI_SD', if_true: files('ssi-sd.c')) @@ -10,3 +13,6 @@ system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_sdhci.c')) system_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-sdhost.c')) system_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_sdhci.c')) system_ss.add(when: 'CONFIG_CADENCE_SDHCI', if_true: files('cadence_sdhci.c')) +system_ss.add(when: 'CONFIG_K230_SDHCI', if_true: files('k230_sdhci.c')) +system_ss.add(when: 'CONFIG_SPACEMIT_K3_SDHCI', if_true: files('spacemit-k3-sdhci.c')) +system_ss.add(when: 'CONFIG_AX650X_SDHCI', if_true: files('ax650x-sdhci.c')) diff --git a/hw/sd/phytium-mci.c b/hw/sd/phytium-mci.c new file mode 100644 index 0000000000000..5c1b749cd11f2 --- /dev/null +++ b/hw/sd/phytium-mci.c @@ -0,0 +1,753 @@ +/* + * Local-only Phytium Multimedia Card Interface controller model. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/sd/phytium-mci.h" +#include "exec/cpu-common.h" +#include "system/physmem.h" +#include "hw/core/irq.h" +#include "migration/vmstate.h" +#include "qemu/bswap.h" +#include "qemu/log.h" +#include "qemu/module.h" + +REG32(CNTRL, 0x000) +REG32(PWREN, 0x004) +REG32(CLKDIV, 0x008) +REG32(CLKENA, 0x010) +REG32(TMOUT, 0x014) +REG32(CTYPE, 0x018) +REG32(BLKSIZ, 0x01c) +REG32(BYTCNT, 0x020) +REG32(INT_MASK, 0x024) +REG32(CMDARG, 0x028) +REG32(CMD, 0x02c) +REG32(RESP0, 0x030) +REG32(RESP1, 0x034) +REG32(RESP2, 0x038) +REG32(RESP3, 0x03c) +REG32(MASKED_INTS, 0x040) +REG32(RAW_INTS, 0x044) +REG32(STATUS, 0x048) +REG32(FIFOTH, 0x04c) +REG32(CARD_DETECT, 0x050) +REG32(CARD_WRTPRT, 0x054) +REG32(CCLK_RDY, 0x058) +REG32(TRAN_CARD_CNT, 0x05c) +REG32(TRAN_FIFO_CNT, 0x060) +REG32(DEBNCE, 0x064) +REG32(UID, 0x068) +REG32(VID, 0x06c) +REG32(HWCONF, 0x070) +REG32(UHS_REG, 0x074) +REG32(CARD_RESET, 0x078) +REG32(BUS_MODE, 0x080) +REG32(DESC_LIST_ADDRL, 0x088) +REG32(DESC_LIST_ADDRH, 0x08c) +REG32(DMAC_STATUS, 0x090) +REG32(DMAC_INT_ENA, 0x094) +REG32(CUR_DESC_ADDRL, 0x098) +REG32(CUR_DESC_ADDRH, 0x09c) +REG32(CUR_BUF_ADDRL, 0x0a0) +REG32(CUR_BUF_ADDRH, 0x0a4) +REG32(CARD_THRCTL, 0x100) +REG32(UHS_REG_EXT, 0x108) +REG32(EMMC_DDR_REG, 0x10c) +REG32(ENABLE_SHIFT, 0x110) +REG32(DATA, 0x200) +REG32(IRQ_ACK, 0xfd0) + +#define CNTRL_CONTROLLER_RESET BIT(0) +#define CNTRL_FIFO_RESET BIT(1) +#define CNTRL_DMA_RESET BIT(2) +#define CNTRL_INT_ENABLE BIT(4) +#define CNTRL_DMA_ENABLE BIT(5) +#define CNTRL_USE_INTERNAL_DMAC BIT(25) +#define CNTRL_RESET_MASK (CNTRL_CONTROLLER_RESET | \ + CNTRL_FIFO_RESET | \ + CNTRL_DMA_RESET) + +#define CMD_START BIT(31) +#define CMD_UPD_CLK BIT(21) +#define CMD_SEND_STOP BIT(12) +#define CMD_DAT_WR BIT(10) +#define CMD_DAT_EXP BIT(9) +#define CMD_RESP_LONG BIT(7) +#define CMD_RESP_EXP BIT(6) +#define CMD_INDEX_MASK 0x3f + +#define INT_CD BIT(0) +#define INT_RE BIT(1) +#define INT_CMD BIT(2) +#define INT_DTO BIT(3) +#define INT_TXDR BIT(4) +#define INT_RXDR BIT(5) +#define INT_RCRC BIT(6) +#define INT_DCRC BIT(7) +#define INT_RTO BIT(8) +#define INT_DRTO BIT(9) +#define INT_HTO BIT(10) +#define INT_FRUN BIT(11) +#define INT_HLE BIT(12) +#define INT_SBE_BCI BIT(13) +#define INT_ACD BIT(14) +#define INT_EBE BIT(15) +#define INT_SDIO BIT(16) +#define INT_ALL (INT_CD | INT_RE | INT_CMD | INT_DTO | \ + INT_TXDR | INT_RXDR | INT_RCRC | \ + INT_DCRC | INT_RTO | INT_DRTO | \ + INT_HTO | INT_FRUN | INT_HLE | \ + INT_SBE_BCI | INT_ACD | INT_EBE | \ + INT_SDIO) + +#define STATUS_FIFO_RX BIT(0) +#define STATUS_FIFO_TX BIT(1) +#define STATUS_FIFO_EMPTY BIT(2) +#define STATUS_FIFO_FULL BIT(3) +#define STATUS_DATA_BUSY BIT(10) +#define STATUS_RESPONSE_INDEX_SHIFT 11 + +#define BUS_MODE_SWR BIT(0) +#define BUS_MODE_DE BIT(7) + +#define DMAC_STATUS_TI BIT(0) +#define DMAC_STATUS_RI BIT(1) +#define DMAC_STATUS_FBE BIT(2) +#define DMAC_STATUS_DU BIT(4) +#define DMAC_STATUS_NIS BIT(8) +#define DMAC_STATUS_AIS BIT(9) +#define DMAC_STATUS_ALL (DMAC_STATUS_TI | DMAC_STATUS_RI | \ + DMAC_STATUS_FBE | DMAC_STATUS_DU | \ + DMAC_STATUS_NIS | DMAC_STATUS_AIS) + +#define SD_SCR_SIZE 8 + +static const uint8_t phytium_mci_scr[SD_SCR_SIZE] = { + 0x02, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +#define ADMA_ATTR_DIC BIT(1) +#define ADMA_ATTR_LD BIT(2) +#define ADMA_ATTR_FD BIT(3) +#define ADMA_ATTR_CH BIT(4) +#define ADMA_ATTR_ER BIT(5) +#define ADMA_ATTR_CES BIT(30) +#define ADMA_ATTR_OWN BIT(31) +#define ADMA_DESC_SIZE 32 + +static void phytium_mci_update_irq(PhytiumMciState *s) +{ + bool raw_irq = (s->regs[R_RAW_INTS] & s->regs[R_INT_MASK]) != 0; + bool dmac_irq = (s->regs[R_DMAC_STATUS] & s->regs[R_DMAC_INT_ENA]) != 0; + bool raise = (s->regs[R_CNTRL] & CNTRL_INT_ENABLE) && + (raw_irq || dmac_irq); + + qemu_set_irq(s->irq, raise); +} + +static void phytium_mci_fifo_reset(PhytiumMciState *s) +{ + s->fifo_len = 0; + s->fifo_pos = 0; + memset(s->fifo, 0, sizeof(s->fifo)); +} + +static uint32_t phytium_mci_fifo_pop_le32(PhytiumMciState *s) +{ + uint32_t value; + + if (s->fifo_len < 4) { + return 0; + } + + value = ldl_le_p(s->fifo + s->fifo_pos); + s->fifo_pos += 4; + s->fifo_len -= 4; + if (s->fifo_len == 0 || s->fifo_pos >= sizeof(s->fifo)) { + s->fifo_pos = 0; + } + + return value; +} + +static void phytium_mci_maybe_send_stop(PhytiumMciState *s) +{ + SDRequest req = { + .cmd = 12, + .arg = 0, + }; + uint8_t response[16]; + + if (!s->transfer_send_stop) { + return; + } + + s->transfer_send_stop = false; + sdbus_do_command(&s->sdbus, &req, response, sizeof(response)); + s->regs[R_RAW_INTS] |= INT_ACD; +} + +static void phytium_mci_pio_refill(PhytiumMciState *s) +{ + uint32_t count; + uint32_t pos; + + if (!s->transfer_active || s->transfer_is_write || + s->transfer_bytes_remaining == 0 || s->fifo_len != 0) { + return; + } + + count = MIN(s->transfer_bytes_remaining, + (uint32_t)(sizeof(s->fifo) - s->fifo_len)); + if (count == 0) { + return; + } + + memset(s->fifo + s->fifo_len, 0, count); + if (s->transfer_synthetic_scr) { + pos = SD_SCR_SIZE - s->transfer_bytes_remaining; + memcpy(s->fifo + s->fifo_len, phytium_mci_scr + pos, count); + } else { + sdbus_read_data(&s->sdbus, s->fifo + s->fifo_len, count); + } + s->fifo_len += count; + s->transfer_bytes_remaining -= count; + s->regs[R_RAW_INTS] |= INT_RXDR; + + if (s->transfer_bytes_remaining == 0) { + s->transfer_synthetic_scr = false; + phytium_mci_maybe_send_stop(s); + s->regs[R_RAW_INTS] |= INT_DTO; + } + + phytium_mci_update_irq(s); +} + +static uint32_t phytium_mci_read_data_port(PhytiumMciState *s) +{ + uint32_t value = 0; + + if (s->transfer_active && !s->transfer_is_write && s->fifo_len >= 4) { + value = phytium_mci_fifo_pop_le32(s); + if (s->fifo_len == 0) { + if (s->transfer_bytes_remaining != 0) { + phytium_mci_pio_refill(s); + } else { + phytium_mci_maybe_send_stop(s); + s->regs[R_RAW_INTS] |= INT_DTO; + s->transfer_active = false; + phytium_mci_update_irq(s); + } + } + } + + return value; +} + +static void phytium_mci_write_data_port(PhytiumMciState *s, uint32_t value) +{ + uint8_t buf[4]; + + if (!s->transfer_active || !s->transfer_is_write) { + return; + } + + stl_le_p(buf, value); + sdbus_write_data(&s->sdbus, buf, sizeof(buf)); + + if (s->transfer_bytes_remaining > sizeof(buf)) { + s->transfer_bytes_remaining -= sizeof(buf); + s->regs[R_RAW_INTS] |= INT_TXDR; + } else { + s->transfer_bytes_remaining = 0; + s->transfer_active = false; + phytium_mci_maybe_send_stop(s); + s->regs[R_RAW_INTS] |= INT_DTO; + } + + phytium_mci_update_irq(s); +} + +static bool phytium_mci_idmac_enabled(PhytiumMciState *s) +{ + return (s->regs[R_CNTRL] & CNTRL_USE_INTERNAL_DMAC) && + (s->regs[R_BUS_MODE] & BUS_MODE_DE); +} + +static void phytium_mci_adma_kick(PhytiumMciState *s) +{ + uint64_t desc_addr = deposit64(s->regs[R_DESC_LIST_ADDRL], 32, 32, + s->regs[R_DESC_LIST_ADDRH]) & ~3ULL; + bool is_write = s->transfer_is_write; + uint32_t processed = 0; + + while (desc_addr && processed++ < PHYTIUM_MCI_ADMA_MAX_DESCS) { + uint8_t desc[ADMA_DESC_SIZE]; + uint32_t attr; + uint32_t len; + uint64_t buf_addr; + uint64_t next_addr; + uint32_t count; + + physical_memory_read(desc_addr, desc, sizeof(desc)); + attr = ldl_le_p(desc + 0); + len = ldl_le_p(desc + 8); + buf_addr = deposit64(ldl_le_p(desc + 16), 32, 32, + ldl_le_p(desc + 20)); + next_addr = deposit64(ldl_le_p(desc + 24), 32, 32, + ldl_le_p(desc + 28)) & ~3ULL; + + if (!(attr & ADMA_ATTR_OWN)) { + break; + } + if (s->transfer_bytes_remaining == 0) { + break; + } + + count = len ? len : s->transfer_bytes_remaining; + count = MIN(count, s->transfer_bytes_remaining); + + s->regs[R_CUR_DESC_ADDRL] = (uint32_t)desc_addr; + s->regs[R_CUR_DESC_ADDRH] = (uint32_t)(desc_addr >> 32); + s->regs[R_CUR_BUF_ADDRL] = (uint32_t)buf_addr; + s->regs[R_CUR_BUF_ADDRH] = (uint32_t)(buf_addr >> 32); + + if (count && buf_addr) { + g_autofree uint8_t *buf = g_malloc0(count); + + if (is_write) { + physical_memory_read(buf_addr, buf, count); + sdbus_write_data(&s->sdbus, buf, count); + } else if (s->transfer_synthetic_scr) { + uint32_t pos = SD_SCR_SIZE - s->transfer_bytes_remaining; + + memcpy(buf, phytium_mci_scr + pos, count); + physical_memory_write(buf_addr, buf, count); + } else { + sdbus_read_data(&s->sdbus, buf, count); + physical_memory_write(buf_addr, buf, count); + } + s->transfer_bytes_remaining -= count; + } + + attr &= ~ADMA_ATTR_OWN; + stl_le_p(desc + 0, attr); + physical_memory_write(desc_addr, desc, sizeof(desc)); + + if ((attr & ADMA_ATTR_LD) || s->transfer_bytes_remaining == 0) { + break; + } + if ((attr & ADMA_ATTR_CH) && next_addr) { + desc_addr = next_addr; + } else { + desc_addr += ADMA_DESC_SIZE; + } + } + + s->regs[R_DMAC_STATUS] |= is_write ? DMAC_STATUS_TI : DMAC_STATUS_RI; + s->regs[R_DMAC_STATUS] |= DMAC_STATUS_NIS; + s->regs[R_RAW_INTS] |= INT_DTO; + s->transfer_active = false; + s->transfer_bytes_remaining = 0; + s->transfer_synthetic_scr = false; + phytium_mci_maybe_send_stop(s); + phytium_mci_update_irq(s); +} + +static uint32_t phytium_mci_data_length(PhytiumMciState *s, uint8_t cmd, + bool is_write) +{ + uint32_t length = s->regs[R_BYTCNT]; + + if (!is_write && cmd == 51) { + length = MIN(length, (uint32_t)SD_SCR_SIZE); + } + + return length; +} + +static void phytium_mci_issue_command(PhytiumMciState *s) +{ + SDRequest req; + uint8_t response[16] = {}; + uint32_t cmd = s->regs[R_CMD]; + bool expect_resp = cmd & CMD_RESP_EXP; + bool long_resp = cmd & CMD_RESP_LONG; + bool data_expected = cmd & CMD_DAT_EXP; + bool data_write = cmd & CMD_DAT_WR; + bool cmd_ok = true; + bool synthetic_scr = false; + uint8_t prev_cmd = s->last_cmd; + size_t rlen = 0; + + if (cmd & CMD_UPD_CLK) { + s->regs[R_RAW_INTS] |= INT_CMD; + s->regs[R_CMD] &= ~CMD_START; + phytium_mci_update_irq(s); + return; + } + + req.cmd = cmd & CMD_INDEX_MASK; + req.arg = s->regs[R_CMDARG]; + s->last_cmd = req.cmd; + + if (data_expected && expect_resp && req.cmd == 51 && prev_cmd != 55) { + stl_be_p(response, 0); + rlen = sizeof(uint32_t); + synthetic_scr = true; + } else { + rlen = sdbus_do_command(&s->sdbus, &req, response, sizeof(response)); + } + if (rlen == 0 && data_expected && expect_resp && + (req.cmd == 18 || req.cmd == 25)) { + rlen = sdbus_do_command(&s->sdbus, &req, response, sizeof(response)); + } + if (rlen == 0 && data_expected && expect_resp && req.cmd == 51) { + stl_be_p(response, 0); + rlen = sizeof(uint32_t); + synthetic_scr = true; + } + + if (expect_resp) { + if (rlen == 0) { + s->regs[R_RAW_INTS] |= INT_RTO; + cmd_ok = false; + } else if (long_resp && rlen == 16) { + s->regs[R_RESP0] = ldl_be_p(response + 12) & ~1u; + s->regs[R_RESP1] = ldl_be_p(response + 8); + s->regs[R_RESP2] = ldl_be_p(response + 4); + s->regs[R_RESP3] = ldl_be_p(response + 0); + s->regs[R_RAW_INTS] |= INT_CMD; + } else if (rlen >= 4) { + s->regs[R_RESP0] = ldl_be_p(response); + s->regs[R_RESP1] = 0; + s->regs[R_RESP2] = 0; + s->regs[R_RESP3] = 0; + s->regs[R_RAW_INTS] |= INT_CMD; + } else { + s->regs[R_RAW_INTS] |= INT_RE; + cmd_ok = false; + } + } else { + s->regs[R_RAW_INTS] |= INT_CMD; + } + + if (cmd_ok && data_expected) { + s->transfer_bytes_remaining = + phytium_mci_data_length(s, req.cmd, data_write); + s->transfer_is_write = data_write; + s->transfer_send_stop = cmd & CMD_SEND_STOP; + s->transfer_synthetic_scr = synthetic_scr; + s->transfer_active = s->transfer_bytes_remaining != 0; + + if (phytium_mci_idmac_enabled(s)) { + phytium_mci_adma_kick(s); + } else if (data_write) { + s->regs[R_RAW_INTS] |= INT_TXDR; + } else { + phytium_mci_pio_refill(s); + } + } + + s->regs[R_CMD] &= ~CMD_START; + phytium_mci_update_irq(s); +} + +static uint64_t phytium_mci_cntrl_pre_write(RegisterInfo *reg, uint64_t val) +{ + PhytiumMciState *s = PHYTIUM_MCI(reg->opaque); + + if (val & CNTRL_FIFO_RESET) { + phytium_mci_fifo_reset(s); + } + if (val & CNTRL_DMA_RESET) { + s->regs[R_DMAC_STATUS] = 0; + } + if (val & CNTRL_CONTROLLER_RESET) { + s->transfer_active = false; + s->transfer_bytes_remaining = 0; + s->transfer_send_stop = false; + s->transfer_synthetic_scr = false; + } + + return val & ~CNTRL_RESET_MASK; +} + +static void phytium_mci_irq_post_write(RegisterInfo *reg, uint64_t val) +{ + phytium_mci_update_irq(PHYTIUM_MCI(reg->opaque)); +} + +static void phytium_mci_cmd_post_write(RegisterInfo *reg, uint64_t val) +{ + PhytiumMciState *s = PHYTIUM_MCI(reg->opaque); + + if (val & CMD_START) { + phytium_mci_issue_command(s); + } else { + phytium_mci_update_irq(s); + } +} + +static uint64_t phytium_mci_bus_mode_pre_write(RegisterInfo *reg, uint64_t val) +{ + return val & ~BUS_MODE_SWR; +} + +static uint64_t phytium_mci_masked_post_read(RegisterInfo *reg, uint64_t val) +{ + PhytiumMciState *s = PHYTIUM_MCI(reg->opaque); + + return s->regs[R_RAW_INTS] & s->regs[R_INT_MASK]; +} + +static uint64_t phytium_mci_status_post_read(RegisterInfo *reg, uint64_t val) +{ + PhytiumMciState *s = PHYTIUM_MCI(reg->opaque); + uint32_t status = (s->last_cmd & CMD_INDEX_MASK) << + STATUS_RESPONSE_INDEX_SHIFT; + + if (s->fifo_len == 0) { + status |= STATUS_FIFO_EMPTY | STATUS_FIFO_TX; + } + if (s->fifo_len == sizeof(s->fifo)) { + status |= STATUS_FIFO_FULL; + } + if (s->fifo_len != 0) { + status |= STATUS_FIFO_RX; + } + if (s->transfer_active) { + status |= STATUS_DATA_BUSY; + } + + return status; +} + +static uint64_t phytium_mci_card_detect_post_read(RegisterInfo *reg, + uint64_t val) +{ + PhytiumMciState *s = PHYTIUM_MCI(reg->opaque); + + return sdbus_get_inserted(&s->sdbus) ? 0 : 1; +} + +static uint64_t phytium_mci_wrtprt_post_read(RegisterInfo *reg, uint64_t val) +{ + PhytiumMciState *s = PHYTIUM_MCI(reg->opaque); + + return sdbus_get_readonly(&s->sdbus) ? 1 : 0; +} + +static uint64_t phytium_mci_cclk_ready_post_read(RegisterInfo *reg, + uint64_t val) +{ + return 1; +} + +static uint64_t phytium_mci_read(void *opaque, hwaddr addr, unsigned size) +{ + RegisterInfoArray *reg_array = opaque; + PhytiumMciState *s = PHYTIUM_MCI(register_array_get_owner(reg_array)); + + if (addr == A_DATA) { + return phytium_mci_read_data_port(s); + } + + return register_read_memory(opaque, addr, size); +} + +static void phytium_mci_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + RegisterInfoArray *reg_array = opaque; + PhytiumMciState *s = PHYTIUM_MCI(register_array_get_owner(reg_array)); + + if (addr == A_DATA) { + phytium_mci_write_data_port(s, value); + return; + } + + register_write_memory(opaque, addr, value, size); +} + +static const MemoryRegionOps phytium_mci_ops = { + .read = phytium_mci_read, + .write = phytium_mci_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const RegisterAccessInfo phytium_mci_regs_info[] = { + { .name = "CNTRL", .addr = A_CNTRL, + .pre_write = phytium_mci_cntrl_pre_write, + .post_write = phytium_mci_irq_post_write }, + { .name = "PWREN", .addr = A_PWREN }, + { .name = "CLKDIV", .addr = A_CLKDIV }, + { .name = "CLKENA", .addr = A_CLKENA }, + { .name = "TMOUT", .addr = A_TMOUT, .reset = 0xffffffff }, + { .name = "CTYPE", .addr = A_CTYPE }, + { .name = "BLKSIZ", .addr = A_BLKSIZ }, + { .name = "BYTCNT", .addr = A_BYTCNT }, + { .name = "INT_MASK", .addr = A_INT_MASK, + .post_write = phytium_mci_irq_post_write }, + { .name = "CMDARG", .addr = A_CMDARG }, + { .name = "CMD", .addr = A_CMD, .post_write = phytium_mci_cmd_post_write }, + { .name = "RESP0", .addr = A_RESP0, .ro = UINT32_MAX }, + { .name = "RESP1", .addr = A_RESP1, .ro = UINT32_MAX }, + { .name = "RESP2", .addr = A_RESP2, .ro = UINT32_MAX }, + { .name = "RESP3", .addr = A_RESP3, .ro = UINT32_MAX }, + { .name = "MASKED_INTS", .addr = A_MASKED_INTS, .ro = UINT32_MAX, + .post_read = phytium_mci_masked_post_read }, + { .name = "RAW_INTS", .addr = A_RAW_INTS, .w1c = INT_ALL, + .post_write = phytium_mci_irq_post_write }, + { .name = "STATUS", .addr = A_STATUS, .ro = UINT32_MAX, + .post_read = phytium_mci_status_post_read }, + { .name = "FIFOTH", .addr = A_FIFOTH }, + { .name = "CARD_DETECT", .addr = A_CARD_DETECT, .ro = UINT32_MAX, + .post_read = phytium_mci_card_detect_post_read }, + { .name = "CARD_WRTPRT", .addr = A_CARD_WRTPRT, .ro = UINT32_MAX, + .post_read = phytium_mci_wrtprt_post_read }, + { .name = "CCLK_RDY", .addr = A_CCLK_RDY, .ro = UINT32_MAX, + .post_read = phytium_mci_cclk_ready_post_read }, + { .name = "TRAN_CARD_CNT", .addr = A_TRAN_CARD_CNT, .ro = UINT32_MAX }, + { .name = "TRAN_FIFO_CNT", .addr = A_TRAN_FIFO_CNT, .ro = UINT32_MAX }, + { .name = "DEBNCE", .addr = A_DEBNCE }, + { .name = "UID", .addr = A_UID }, + { .name = "VID", .addr = A_VID, .ro = UINT32_MAX }, + { .name = "HWCONF", .addr = A_HWCONF, .ro = UINT32_MAX }, + { .name = "UHS_REG", .addr = A_UHS_REG }, + { .name = "CARD_RESET", .addr = A_CARD_RESET, .reset = 1 }, + { .name = "BUS_MODE", .addr = A_BUS_MODE, + .pre_write = phytium_mci_bus_mode_pre_write }, + { .name = "DESC_LIST_ADDRL", .addr = A_DESC_LIST_ADDRL }, + { .name = "DESC_LIST_ADDRH", .addr = A_DESC_LIST_ADDRH }, + { .name = "DMAC_STATUS", .addr = A_DMAC_STATUS, + .w1c = DMAC_STATUS_ALL, .post_write = phytium_mci_irq_post_write }, + { .name = "DMAC_INT_ENA", .addr = A_DMAC_INT_ENA, + .post_write = phytium_mci_irq_post_write }, + { .name = "CUR_DESC_ADDRL", .addr = A_CUR_DESC_ADDRL, .ro = UINT32_MAX }, + { .name = "CUR_DESC_ADDRH", .addr = A_CUR_DESC_ADDRH, .ro = UINT32_MAX }, + { .name = "CUR_BUF_ADDRL", .addr = A_CUR_BUF_ADDRL, .ro = UINT32_MAX }, + { .name = "CUR_BUF_ADDRH", .addr = A_CUR_BUF_ADDRH, .ro = UINT32_MAX }, + { .name = "CARD_THRCTL", .addr = A_CARD_THRCTL }, + { .name = "UHS_REG_EXT", .addr = A_UHS_REG_EXT }, + { .name = "EMMC_DDR_REG", .addr = A_EMMC_DDR_REG }, + { .name = "ENABLE_SHIFT", .addr = A_ENABLE_SHIFT }, + { .name = "DATA", .addr = A_DATA }, + { .name = "IRQ_ACK", .addr = A_IRQ_ACK }, +}; + +static void phytium_mci_set_inserted(DeviceState *dev, bool inserted) +{ + PhytiumMciState *s = PHYTIUM_MCI(dev); + + if (inserted) { + s->regs[R_RAW_INTS] &= ~INT_CD; + } else { + s->regs[R_RAW_INTS] |= INT_CD; + } + phytium_mci_update_irq(s); +} + +static void phytium_mci_set_readonly(DeviceState *dev, bool readonly) +{ +} + +static void phytium_mci_reset(DeviceState *dev) +{ + PhytiumMciState *s = PHYTIUM_MCI(dev); + + for (unsigned int i = 0; i < PHYTIUM_MCI_REG_WORDS; i++) { + register_reset(&s->regs_info[i]); + } + + phytium_mci_fifo_reset(s); + s->transfer_bytes_remaining = 0; + s->transfer_is_write = false; + s->transfer_send_stop = false; + s->transfer_synthetic_scr = false; + s->transfer_active = false; + s->last_cmd = 0; + phytium_mci_update_irq(s); +} + +static const VMStateDescription vmstate_phytium_mci = { + .name = TYPE_PHYTIUM_MCI, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, PhytiumMciState, PHYTIUM_MCI_REG_WORDS), + VMSTATE_UINT8_ARRAY(fifo, PhytiumMciState, + PHYTIUM_MCI_FIFO_DEPTH * 4), + VMSTATE_UINT32(fifo_len, PhytiumMciState), + VMSTATE_UINT32(fifo_pos, PhytiumMciState), + VMSTATE_UINT32(transfer_bytes_remaining, PhytiumMciState), + VMSTATE_BOOL(transfer_is_write, PhytiumMciState), + VMSTATE_BOOL(transfer_send_stop, PhytiumMciState), + VMSTATE_BOOL(transfer_synthetic_scr, PhytiumMciState), + VMSTATE_BOOL(transfer_active, PhytiumMciState), + VMSTATE_UINT8(last_cmd, PhytiumMciState), + VMSTATE_END_OF_LIST() + }, +}; + +static void phytium_mci_init(Object *obj) +{ + PhytiumMciState *s = PHYTIUM_MCI(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + s->reg_array = register_init_block32(DEVICE(obj), phytium_mci_regs_info, + ARRAY_SIZE(phytium_mci_regs_info), + s->regs_info, s->regs, + &phytium_mci_ops, false, + PHYTIUM_MCI_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->reg_array->mem); + sysbus_init_irq(sbd, &s->irq); + qbus_init(&s->sdbus, sizeof(s->sdbus), TYPE_PHYTIUM_MCI_BUS, DEVICE(s), + "sd-bus"); +} + +static void phytium_mci_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + device_class_set_legacy_reset(dc, phytium_mci_reset); + dc->vmsd = &vmstate_phytium_mci; + dc->user_creatable = false; +} + +static void phytium_mci_bus_class_init(ObjectClass *oc, const void *data) +{ + SDBusClass *sbc = SD_BUS_CLASS(oc); + + sbc->set_inserted = phytium_mci_set_inserted; + sbc->set_readonly = phytium_mci_set_readonly; +} + +static const TypeInfo phytium_mci_types[] = { + { + .name = TYPE_PHYTIUM_MCI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PhytiumMciState), + .instance_init = phytium_mci_init, + .class_init = phytium_mci_class_init, + }, + { + .name = TYPE_PHYTIUM_MCI_BUS, + .parent = TYPE_SD_BUS, + .instance_size = sizeof(SDBus), + .class_init = phytium_mci_bus_class_init, + }, +}; + +DEFINE_TYPES(phytium_mci_types) diff --git a/hw/sd/rockchip_dwcmshc.c b/hw/sd/rockchip_dwcmshc.c new file mode 100644 index 0000000000000..4e811282f65ee --- /dev/null +++ b/hw/sd/rockchip_dwcmshc.c @@ -0,0 +1,122 @@ +/* + * Rockchip DWCMSHC vendor register window + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Synopsys DWCMSHC places Rockchip eMMC control, auto-tuning and DLL + * registers above the 0x100-byte SDHCI core register map. U-Boot and + * Linux poll DLL_STATUS0 for LOCKED and expect TIMEOUT clear. + */ + +#include "qemu/osdep.h" +#include "hw/sd/rockchip_dwcmshc.h" +#include "qemu/bswap.h" +#include "qemu/bitops.h" +#include "qemu/module.h" + +#define DWCMSHC_EMMC_DLL_STATUS0 0x840 +#define DWCMSHC_EMMC_DLL_LOCKED BIT(8) + +static uint64_t rockchip_dwcmshc_read(void *opaque, hwaddr offset, + unsigned size) +{ + RockchipDWCMSHCVendorState *s = opaque; + hwaddr reg = offset + ROCKCHIP_DWCMSHC_VENDOR_BASE; + + if (offset + size > ROCKCHIP_DWCMSHC_VENDOR_SIZE) { + return 0; + } + + if (reg == DWCMSHC_EMMC_DLL_STATUS0 && size == 4) { + return ldl_le_p(&s->regs[offset]) | DWCMSHC_EMMC_DLL_LOCKED; + } + + switch (size) { + case 1: + return s->regs[offset]; + case 2: + return lduw_le_p(&s->regs[offset]); + case 4: + return ldl_le_p(&s->regs[offset]); + default: + return 0; + } +} + +static void rockchip_dwcmshc_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + RockchipDWCMSHCVendorState *s = opaque; + + if (offset + size > ROCKCHIP_DWCMSHC_VENDOR_SIZE) { + return; + } + + switch (size) { + case 1: + s->regs[offset] = value; + break; + case 2: + stw_le_p(&s->regs[offset], value); + break; + case 4: + stl_le_p(&s->regs[offset], value); + break; + } +} + +static const MemoryRegionOps rockchip_dwcmshc_ops = { + .read = rockchip_dwcmshc_read, + .write = rockchip_dwcmshc_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +static void rockchip_dwcmshc_reset(DeviceState *dev) +{ + RockchipDWCMSHCVendorState *s = ROCKCHIP_DWCMSHC_VENDOR(dev); + + memset(s->regs, 0, sizeof(s->regs)); +} + +static void rockchip_dwcmshc_realize(DeviceState *dev, Error **errp) +{ + RockchipDWCMSHCVendorState *s = ROCKCHIP_DWCMSHC_VENDOR(dev); + + memory_region_init_io(&s->iomem, OBJECT(s), &rockchip_dwcmshc_ops, s, + "rockchip-dwcmshc-vendor", + ROCKCHIP_DWCMSHC_VENDOR_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); +} + +static void rockchip_dwcmshc_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = rockchip_dwcmshc_realize; + device_class_set_legacy_reset(dc, rockchip_dwcmshc_reset); + dc->user_creatable = false; +} + +static const TypeInfo rockchip_dwcmshc_info = { + .name = TYPE_ROCKCHIP_DWCMSHC_VENDOR, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RockchipDWCMSHCVendorState), + .class_init = rockchip_dwcmshc_class_init, +}; + +static void rockchip_dwcmshc_register_types(void) +{ + type_register_static(&rockchip_dwcmshc_info); +} + +type_init(rockchip_dwcmshc_register_types) diff --git a/hw/sd/sd.c b/hw/sd/sd.c index 336075700431f..0a4c99abccdac 100644 --- a/hw/sd/sd.c +++ b/hw/sd/sd.c @@ -2179,9 +2179,16 @@ static sd_rsp_type_t sd_cmd_APP_CMD(SDState *sd, SDRequest req) case sd_sleep_state: return sd_invalid_state_for_cmd(sd, req); case sd_idle_state: - if (!sd_is_spi(sd) && sd_req_get_rca(sd, req) != 0x0000) { - qemu_log_mask(LOG_GUEST_ERROR, - "SD: illegal RCA 0x%04x for APP_CMD\n", req.cmd); + if (!sd_is_spi(sd)) { + if (sd_req_get_rca(sd, req) != 0x0000) { + qemu_log_mask(LOG_GUEST_ERROR, + "SD: illegal RCA 0x%04x for APP_CMD\n", + sd_req_get_rca(sd, req)); + return sd_r0; + } + sd->expecting_acmd = true; + sd->card_status |= APP_CMD; + return sd_r1; } /* fall-through */ default: diff --git a/hw/sd/sdhci-internal.h b/hw/sd/sdhci-internal.h index 4aeed120bf143..6bc6b48c4e659 100644 --- a/hw/sd/sdhci-internal.h +++ b/hw/sd/sdhci-internal.h @@ -46,7 +46,7 @@ #define SDHC_TRNS_ACMD23 0x0008 /* since v3 */ #define SDHC_TRNS_READ 0x0010 #define SDHC_TRNS_MULTI 0x0020 -#define SDHC_TRNMOD_MASK 0x0037 +#define SDHC_TRNMOD_MASK 0x003f /* R/W Command Register 0x0 */ #define SDHC_CMDREG 0x0E @@ -309,6 +309,8 @@ extern const VMStateDescription sdhci_vmstate; #define DEFINE_SDHCI_COMMON_PROPERTIES(_state) \ DEFINE_PROP_UINT8("sd-spec-version", _state, sd_spec_version, 2), \ DEFINE_PROP_UINT8("uhs", _state, uhs_mode, UHS_NOT_SUPPORTED), \ + DEFINE_PROP_UINT16("vendor-area1", _state, vendor_area1, 0), \ + DEFINE_PROP_UINT16("vendor-area2", _state, vendor_area2, 0), \ \ /* Capabilities registers provide information on supported * features of this specific host controller implementation */ \ diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c index c86dfa281f4b0..67e7e600128d3 100644 --- a/hw/sd/sdhci.c +++ b/hw/sd/sdhci.c @@ -45,6 +45,8 @@ DECLARE_INSTANCE_CHECKER(SDBus, SDHCI_BUS, TYPE_SDHCI_BUS) +#define SDHC_VENDOR_AREA 0xe8 + #define MASKED_WRITE(reg, mask, val) (reg = (reg & (mask)) | (val)) static inline unsigned int sdhci_get_fifolen(SDHCIState *s) @@ -334,6 +336,30 @@ static void sdhci_data_transfer(void *opaque); #define BLOCK_SIZE_MASK (4 * KiB - 1) +static bool sdhci_send_auto_cmd23(SDHCIState *s) +{ + SDRequest request = { + .cmd = 23, + .arg = s->sdmasysad, + }; + uint8_t response[16]; + size_t rlen; + + trace_sdhci_send_command(request.cmd, request.arg); + rlen = sdbus_do_command(&s->sdbus, &request, response, sizeof(response)); + if (rlen == 4) { + return true; + } + + trace_sdhci_error("Auto CMD23 failed"); + s->acmd12errsts |= R_SDHC_ACMD12ERRSTS_TIMEOUT_ERR_MASK; + if (s->errintstsen & SDHC_EIS_CMD12ERR) { + s->errintsts |= SDHC_EIS_CMD12ERR; + s->norintsts |= SDHC_NIS_ERR; + } + return false; +} + static void sdhci_send_command(SDHCIState *s) { SDRequest request; @@ -346,6 +372,13 @@ static void sdhci_send_command(SDHCIState *s) request.cmd = s->cmdreg >> 8; request.arg = s->argument; + if ((s->trnmod & SDHC_TRNS_ACMD23) && + (s->cmdreg & SDHC_CMD_DATA_PRESENT) && + !sdhci_send_auto_cmd23(s)) { + sdhci_update_irq(s); + return; + } + trace_sdhci_send_command(request.cmd, request.arg); rlen = sdbus_do_command(&s->sdbus, &request, response, sizeof(response)); @@ -948,6 +981,11 @@ static void sdhci_data_transfer(void *opaque) if (s->trnmod & SDHC_TRNS_DMA) { switch (SDHC_DMA_TYPE(s->hostctl1)) { case SDHC_CTRL_SDMA: + if (!(s->capareg & R_SDHC_CAPAB_SDMA_MASK)) { + trace_sdhci_error("SDMA not supported"); + break; + } + sdhci_sdma_transfer(s); break; case SDHC_CTRL_ADMA1_32: @@ -1097,6 +1135,9 @@ static uint64_t sdhci_read(void *opaque, hwaddr offset, unsigned size) case SDHC_MAXCURR + 4: ret = (uint32_t)(s->maxcurr >> 32); break; + case SDHC_VENDOR_AREA: + ret = s->vendor_area1 | (s->vendor_area2 << 16); + break; case SDHC_ADMAERR: ret = s->admaerr; break; @@ -1185,7 +1226,14 @@ sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) switch (offset & ~0x3) { case SDHC_SYSAD: - if (!TRANSFERRING_DATA(s->prnsts)) { + /* + * SDMA pauses at the programmed buffer boundary and raises DMA_END. + * Software resumes the transfer by writing the next system address + * while the data line is still active. + */ + if (!TRANSFERRING_DATA(s->prnsts) || + ((s->trnmod & SDHC_TRNS_DMA) && + SDHC_DMA_TYPE(s->hostctl1) == SDHC_CTRL_SDMA)) { s->sdmasysad = (s->sdmasysad & mask) | value; MASKED_WRITE(s->sdmasysad, mask, value); /* Writing to last byte of sdmasysad might trigger transfer */ @@ -1236,7 +1284,9 @@ sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) * DMA can be enabled only if it is supported as indicated by * capabilities register */ - if (!(s->capareg & R_SDHC_CAPAB_SDMA_MASK)) { + if (!(s->capareg & (R_SDHC_CAPAB_SDMA_MASK | + R_SDHC_CAPAB_ADMA1_MASK | + R_SDHC_CAPAB_ADMA2_MASK))) { value &= ~SDHC_TRNS_DMA; } diff --git a/hw/sd/spacemit-k3-sdhci.c b/hw/sd/spacemit-k3-sdhci.c new file mode 100644 index 0000000000000..07df5743d699f --- /dev/null +++ b/hw/sd/spacemit-k3-sdhci.c @@ -0,0 +1,164 @@ +/* + * SpacemiT K3 SDHCI controller + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "hw/core/qdev-properties.h" +#include "hw/sd/spacemit-k3-sdhci.h" +#include "migration/vmstate.h" + +#define K3_SDHCI_STD_SIZE 0x100 +#define K3_SDHCI_VENDOR_SIZE 0x100 + +/* Offsets below are relative to the vendor bank at controller offset 0x100. */ +#define K3_SDHCI_MMC_CTRL 0x14 +#define K3_SDHCI_TX_CFG 0x1c +#define K3_SDHCI_MMC_CTRL_MASK (BIT(8) | BIT(9) | BIT(10) | BIT(12)) +#define K3_SDHCI_TX_CFG_MASK (BIT(30) | BIT(31)) + +static uint64_t spacemit_k3_sdhci_vendor_read(void *opaque, hwaddr addr, + unsigned int size) +{ + SpacemitK3SDHCIState *s = SPACEMIT_K3_SDHCI(opaque); + + switch (addr) { + case K3_SDHCI_MMC_CTRL: + return s->mmc_ctrl; + case K3_SDHCI_TX_CFG: + return s->tx_cfg; + default: + qemu_log_mask(LOG_UNIMP, + "%s: unimplemented read at offset 0x%" HWADDR_PRIx + "\n", __func__, K3_SDHCI_STD_SIZE + addr); + return 0; + } +} + +static void spacemit_k3_sdhci_vendor_write(void *opaque, hwaddr addr, + uint64_t value, + unsigned int size) +{ + SpacemitK3SDHCIState *s = SPACEMIT_K3_SDHCI(opaque); + + switch (addr) { + case K3_SDHCI_MMC_CTRL: + s->mmc_ctrl = value & K3_SDHCI_MMC_CTRL_MASK; + break; + case K3_SDHCI_TX_CFG: + s->tx_cfg = value & K3_SDHCI_TX_CFG_MASK; + break; + default: + qemu_log_mask(LOG_UNIMP, + "%s: unimplemented write at offset 0x%" HWADDR_PRIx + "\n", __func__, K3_SDHCI_STD_SIZE + addr); + break; + } +} + +static const MemoryRegionOps spacemit_k3_sdhci_vendor_ops = { + .read = spacemit_k3_sdhci_vendor_read, + .write = spacemit_k3_sdhci_vendor_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void spacemit_k3_sdhci_instance_init(Object *obj) +{ + SpacemitK3SDHCIState *s = SPACEMIT_K3_SDHCI(obj); + + object_initialize_child(obj, "generic-sdhci", &s->sdhci, + TYPE_SYSBUS_SDHCI); + qdev_prop_set_uint8(DEVICE(&s->sdhci), "sd-spec-version", 3); + /* + * The core uses this property to retain v3 Host Control 2 writes. The + * capability register still advertises neither 1.8 V nor UHS modes. + */ + qdev_prop_set_uint8(DEVICE(&s->sdhci), "uhs", UHS_I); + qdev_prop_set_uint64(DEVICE(&s->sdhci), "capareg", + SPACEMIT_K3_SDHCI_CAPAREG); +} + +static void spacemit_k3_sdhci_reset(DeviceState *dev) +{ + SpacemitK3SDHCIState *s = SPACEMIT_K3_SDHCI(dev); + + s->mmc_ctrl = 0; + s->tx_cfg = 0; + device_cold_reset(DEVICE(&s->sdhci)); +} + +static void spacemit_k3_sdhci_realize(DeviceState *dev, Error **errp) +{ + SpacemitK3SDHCIState *s = SPACEMIT_K3_SDHCI(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + SysBusDevice *sdhci_sbd = SYS_BUS_DEVICE(&s->sdhci); + + memory_region_init(&s->container, OBJECT(s), + "spacemit.k3.sdhci-container", + SPACEMIT_K3_SDHCI_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->container); + + memory_region_init_io(&s->vendor_iomem, OBJECT(s), + &spacemit_k3_sdhci_vendor_ops, s, + "spacemit.k3.sdhci-vendor", K3_SDHCI_VENDOR_SIZE); + memory_region_add_subregion(&s->container, K3_SDHCI_STD_SIZE, + &s->vendor_iomem); + + if (!sysbus_realize(sdhci_sbd, errp)) { + return; + } + memory_region_add_subregion(&s->container, 0, + sysbus_mmio_get_region(sdhci_sbd, 0)); + + sysbus_pass_irq(sbd, sdhci_sbd); + s->sd_bus = qdev_get_child_bus(DEVICE(sdhci_sbd), "sd-bus"); +} + +static const VMStateDescription vmstate_spacemit_k3_sdhci = { + .name = TYPE_SPACEMIT_K3_SDHCI, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(mmc_ctrl, SpacemitK3SDHCIState), + VMSTATE_UINT32(tx_cfg, SpacemitK3SDHCIState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void spacemit_k3_sdhci_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = spacemit_k3_sdhci_realize; + device_class_set_legacy_reset(dc, spacemit_k3_sdhci_reset); + dc->vmsd = &vmstate_spacemit_k3_sdhci; + dc->desc = "SpacemiT K3 SDHCI controller"; +} + +static const TypeInfo spacemit_k3_sdhci_type_info = { + .name = TYPE_SPACEMIT_K3_SDHCI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SpacemitK3SDHCIState), + .instance_init = spacemit_k3_sdhci_instance_init, + .class_init = spacemit_k3_sdhci_class_init, +}; + +static void spacemit_k3_sdhci_register_types(void) +{ + type_register_static(&spacemit_k3_sdhci_type_info); +} + +type_init(spacemit_k3_sdhci_register_types) diff --git a/hw/ssi/Kconfig b/hw/ssi/Kconfig index 1bd56463c1e0c..d0d833513e475 100644 --- a/hw/ssi/Kconfig +++ b/hw/ssi/Kconfig @@ -9,6 +9,11 @@ config SIFIVE_SPI config SSI bool +config K230_SPI + bool + select SSI + select SSI_M25P80 + config XILINX_SPI bool select SSI diff --git a/hw/ssi/k230_spi.c b/hw/ssi/k230_spi.c new file mode 100644 index 0000000000000..3d6f23dad7ac4 --- /dev/null +++ b/hw/ssi/k230_spi.c @@ -0,0 +1,465 @@ +/* + * K230 DesignWare SSI controller + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "migration/vmstate.h" +#include "system/dma.h" +#include "hw/core/irq.h" +#include "hw/core/qdev.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/qdev-properties-system.h" +#include "hw/ssi/k230_spi.h" + +#define K230_SPI_CTRLR0 0x000 +#define K230_SPI_CTRLR1 0x004 +#define K230_SPI_SSIENR 0x008 +#define K230_SPI_SER 0x010 +#define K230_SPI_TXFTLR 0x018 +#define K230_SPI_RXFTLR 0x01c +#define K230_SPI_TXFLR 0x020 +#define K230_SPI_RXFLR 0x024 +#define K230_SPI_SR 0x028 +#define K230_SPI_IMR 0x02c +#define K230_SPI_ISR 0x030 +#define K230_SPI_RISR 0x034 +#define K230_SPI_TXOICR 0x038 +#define K230_SPI_RXOICR 0x03c +#define K230_SPI_RXUICR 0x040 +#define K230_SPI_MSTICR 0x044 +#define K230_SPI_ICR 0x048 +#define K230_SPI_DMACR 0x04c +#define K230_SPI_DMATDLR 0x050 +#define K230_SPI_DMARDLR 0x054 +#define K230_SPI_IDR 0x058 +#define K230_SPI_VERSION 0x05c +#define K230_SPI_DR 0x060 +#define K230_SPI_RX_SAMPLE_DLY 0x0f0 +#define K230_SPI_SPI_CTRLR0 0x0f4 +#define K230_SPI_DDR_DRIVE_EDGE 0x0f8 +#define K230_SPI_SPIDR 0x120 +#define K230_SPI_SPIAR 0x124 +#define K230_SPI_AXIAR0 0x128 +#define K230_SPI_AXIAR1 0x12c +#define K230_SPI_DONECR 0x134 + +#define K230_SPI_SR_TF_NOT_FULL BIT(1) +#define K230_SPI_SR_TF_EMPTY BIT(2) +#define K230_SPI_SR_RF_NOT_EMPTY BIT(3) + +#define K230_SPI_INT_TXEI BIT(0) +#define K230_SPI_INT_TXOI BIT(1) +#define K230_SPI_INT_RXUI BIT(2) +#define K230_SPI_INT_RXOI BIT(3) +#define K230_SPI_INT_RXFI BIT(4) +#define K230_SPI_INT_MSTI BIT(5) +#define K230_SPI_INT_DONE BIT(11) + +#define K230_SPI_DMACR_IDMAE BIT(2) + +#define K230_SPI_TMOD_SHIFT 10 +#define K230_SPI_TMOD_MASK (0x3 << K230_SPI_TMOD_SHIFT) +#define K230_SPI_TMOD_TO 1 +#define K230_SPI_TMOD_RO 2 +#define K230_SPI_TMOD_EPROMREAD 3 + +#define K230_SPI_CTRL0_INST_L_SHIFT 8 +#define K230_SPI_CTRL0_INST_L_MASK (0x3 << K230_SPI_CTRL0_INST_L_SHIFT) +#define K230_SPI_CTRL0_ADDR_L_SHIFT 2 +#define K230_SPI_CTRL0_ADDR_L_MASK (0xf << K230_SPI_CTRL0_ADDR_L_SHIFT) +#define K230_SPI_CTRL0_WAIT_SHIFT 11 +#define K230_SPI_CTRL0_WAIT_MASK (0x1f << K230_SPI_CTRL0_WAIT_SHIFT) + +static uint32_t k230_spi_reg(K230SpiState *s, hwaddr addr) +{ + return s->regs[addr / 4]; +} + +static void k230_spi_set_reg(K230SpiState *s, hwaddr addr, uint32_t val) +{ + s->regs[addr / 4] = val; +} + +static uint32_t k230_spi_rx_avail(K230SpiState *s) +{ + return s->rx_len - s->rx_pos; +} + +static uint32_t k230_spi_rx_level(K230SpiState *s) +{ + return MIN(k230_spi_rx_avail(s), K230_SPI_FIFO_DEPTH); +} + +static void k230_spi_update_irq(K230SpiState *s) +{ + uint32_t isr = k230_spi_reg(s, K230_SPI_RISR) & + k230_spi_reg(s, K230_SPI_IMR); + + qemu_set_irq(s->irq[0], isr ? 1 : 0); +} + +static void k230_spi_clear_irqs(K230SpiState *s, uint32_t mask) +{ + k230_spi_set_reg(s, K230_SPI_RISR, + k230_spi_reg(s, K230_SPI_RISR) & ~mask); + k230_spi_update_irq(s); +} + +static uint8_t k230_spi_flash_transfer(K230SpiState *s, uint8_t val) +{ + return ssi_transfer(s->ssi, val) & 0xff; +} + +static void k230_spi_flash_select(K230SpiState *s, bool select) +{ + qemu_set_irq(s->flash_cs, select ? 0 : 1); +} + +static void k230_spi_fifo_reset(K230SpiState *s) +{ + s->tx_len = 0; + s->rx_len = 0; + s->rx_pos = 0; +} + +static unsigned int k230_spi_tmode(K230SpiState *s) +{ + return (k230_spi_reg(s, K230_SPI_CTRLR0) & K230_SPI_TMOD_MASK) >> + K230_SPI_TMOD_SHIFT; +} + +static void k230_spi_standard_flush(K230SpiState *s) +{ + unsigned int tmode; + unsigned int ndf; + + if (!(k230_spi_reg(s, K230_SPI_SSIENR) & 1) || + !k230_spi_reg(s, K230_SPI_SER) || !s->tx_len) { + return; + } + + tmode = k230_spi_tmode(s); + k230_spi_flash_select(s, true); + for (int i = 0; i < s->tx_len; i++) { + k230_spi_flash_transfer(s, s->tx_fifo[i]); + } + s->tx_len = 0; + + if (tmode == K230_SPI_TMOD_RO || + tmode == K230_SPI_TMOD_EPROMREAD) { + ndf = (k230_spi_reg(s, K230_SPI_CTRLR1) & 0xffff) + 1; + s->rx_len = MIN(ndf, K230_SPI_RX_BUFFER_SIZE); + s->rx_pos = 0; + + for (int i = 0; i < s->rx_len; i++) { + s->rx_buf[i] = k230_spi_flash_transfer(s, 0); + } + } +} + +static unsigned int k230_spi_inst_bytes(uint32_t spi_ctrlr0) +{ + switch ((spi_ctrlr0 & K230_SPI_CTRL0_INST_L_MASK) >> + K230_SPI_CTRL0_INST_L_SHIFT) { + case 3: + return 2; + case 2: + return 1; + default: + return 0; + } +} + +static unsigned int k230_spi_addr_bytes(uint32_t spi_ctrlr0) +{ + return ((spi_ctrlr0 & K230_SPI_CTRL0_ADDR_L_MASK) >> + K230_SPI_CTRL0_ADDR_L_SHIFT) / 2; +} + +static unsigned int k230_spi_dummy_bytes(uint32_t spi_ctrlr0) +{ + unsigned int wait_cycles = (spi_ctrlr0 & K230_SPI_CTRL0_WAIT_MASK) >> + K230_SPI_CTRL0_WAIT_SHIFT; + + return DIV_ROUND_UP(wait_cycles, 8); +} + +static void k230_spi_transfer_cmd_addr(K230SpiState *s) +{ + uint32_t spidr = k230_spi_reg(s, K230_SPI_SPIDR); + uint32_t spiar = k230_spi_reg(s, K230_SPI_SPIAR); + uint32_t spi_ctrlr0 = k230_spi_reg(s, K230_SPI_SPI_CTRLR0); + unsigned int inst_bytes = k230_spi_inst_bytes(spi_ctrlr0); + unsigned int addr_bytes = k230_spi_addr_bytes(spi_ctrlr0); + unsigned int dummy_bytes = k230_spi_dummy_bytes(spi_ctrlr0); + + for (int i = 0; i < inst_bytes; i++) { + k230_spi_flash_transfer(s, extract32(spidr, i * 8, 8)); + } + + for (int i = 0; i < addr_bytes; i++) { + k230_spi_flash_transfer(s, extract32(spiar, i * 8, 8)); + } + + for (int i = 0; i < dummy_bytes; i++) { + k230_spi_flash_transfer(s, 0); + } +} + +static void k230_spi_finish_idma(K230SpiState *s) +{ + k230_spi_set_reg(s, K230_SPI_RISR, + k230_spi_reg(s, K230_SPI_RISR) | K230_SPI_INT_DONE); + s->idma_active = false; + k230_spi_update_irq(s); +} + +static void k230_spi_try_idma(K230SpiState *s) +{ + uint32_t dmacr = k230_spi_reg(s, K230_SPI_DMACR); + uint32_t ndf = (k230_spi_reg(s, K230_SPI_CTRLR1) & 0xffff) + 1; + uint64_t dma_addr = k230_spi_reg(s, K230_SPI_AXIAR0) | + ((uint64_t)k230_spi_reg(s, K230_SPI_AXIAR1) << 32); + g_autofree uint8_t *buf = NULL; + bool is_read; + + if (s->idma_active || !(dmacr & K230_SPI_DMACR_IDMAE) || + !(k230_spi_reg(s, K230_SPI_SSIENR) & 1) || + !k230_spi_reg(s, K230_SPI_SER)) { + return; + } + + s->idma_active = true; + buf = g_malloc0(ndf); + is_read = k230_spi_tmode(s) == K230_SPI_TMOD_RO; + + k230_spi_flash_select(s, true); + k230_spi_transfer_cmd_addr(s); + + if (is_read) { + for (int i = 0; i < ndf; i++) { + buf[i] = k230_spi_flash_transfer(s, 0); + } + dma_memory_write(&address_space_memory, dma_addr, buf, ndf, + MEMTXATTRS_UNSPECIFIED); + } else { + if (dma_memory_read(&address_space_memory, dma_addr, buf, ndf, + MEMTXATTRS_UNSPECIFIED) == MEMTX_OK) { + for (int i = 0; i < ndf; i++) { + k230_spi_flash_transfer(s, buf[i]); + } + } + } + + k230_spi_flash_select(s, false); + k230_spi_finish_idma(s); +} + +static uint64_t k230_spi_read(void *opaque, hwaddr addr, unsigned int size) +{ + K230SpiState *s = K230_SPI(opaque); + + if (addr >= K230_SPI_SIZE || size != 4) { + return 0; + } + + switch (addr) { + case K230_SPI_TXFLR: + if ((k230_spi_reg(s, K230_SPI_SSIENR) & 1) && + k230_spi_reg(s, K230_SPI_SER) && !s->tx_len) { + return 1; + } + return s->tx_len; + case K230_SPI_RXFLR: + return k230_spi_rx_level(s); + case K230_SPI_SR: + return K230_SPI_SR_TF_NOT_FULL | K230_SPI_SR_TF_EMPTY | + (k230_spi_rx_avail(s) ? K230_SPI_SR_RF_NOT_EMPTY : 0); + case K230_SPI_ISR: + return k230_spi_reg(s, K230_SPI_RISR) & + k230_spi_reg(s, K230_SPI_IMR); + case K230_SPI_DR: + if (k230_spi_rx_avail(s)) { + return s->rx_buf[s->rx_pos++]; + } + return 0xff; + case K230_SPI_TXOICR: + k230_spi_clear_irqs(s, K230_SPI_INT_TXOI); + return 1; + case K230_SPI_RXOICR: + k230_spi_clear_irqs(s, K230_SPI_INT_RXOI); + return 1; + case K230_SPI_RXUICR: + k230_spi_clear_irqs(s, K230_SPI_INT_RXUI); + return 1; + case K230_SPI_MSTICR: + k230_spi_clear_irqs(s, K230_SPI_INT_MSTI); + return 1; + case K230_SPI_ICR: + k230_spi_clear_irqs(s, K230_SPI_INT_TXOI | K230_SPI_INT_RXUI | + K230_SPI_INT_RXOI | K230_SPI_INT_MSTI | + K230_SPI_INT_DONE); + return 1; + case K230_SPI_DONECR: + k230_spi_clear_irqs(s, K230_SPI_INT_DONE); + return 1; + default: + return k230_spi_reg(s, addr); + } +} + +static void k230_spi_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + K230SpiState *s = K230_SPI(opaque); + uint32_t value = val; + + if (addr >= K230_SPI_SIZE || size != 4) { + return; + } + + switch (addr) { + case K230_SPI_SSIENR: + k230_spi_set_reg(s, addr, value & 1); + if (!(value & 1)) { + k230_spi_fifo_reset(s); + k230_spi_flash_select(s, false); + } + break; + case K230_SPI_SER: + k230_spi_set_reg(s, addr, value); + if (!value) { + k230_spi_flash_select(s, false); + } else { + k230_spi_standard_flush(s); + } + break; + case K230_SPI_TXFTLR: + k230_spi_set_reg(s, addr, MIN(value, K230_SPI_FIFO_DEPTH - 1)); + break; + case K230_SPI_DR: + if (s->tx_len < K230_SPI_FIFO_DEPTH) { + s->tx_fifo[s->tx_len++] = value & 0xff; + } + if (k230_spi_reg(s, K230_SPI_SER)) { + k230_spi_standard_flush(s); + } + break; + case K230_SPI_DMACR: + k230_spi_set_reg(s, addr, value); + if (!(value & K230_SPI_DMACR_IDMAE)) { + s->idma_active = false; + } + break; + case K230_SPI_IMR: + k230_spi_set_reg(s, addr, value); + k230_spi_update_irq(s); + break; + default: + k230_spi_set_reg(s, addr, value); + break; + } + + k230_spi_try_idma(s); +} + +static const MemoryRegionOps k230_spi_ops = { + .read = k230_spi_read, + .write = k230_spi_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void k230_spi_reset(DeviceState *dev) +{ + K230SpiState *s = K230_SPI(dev); + + memset(s->regs, 0, sizeof(s->regs)); + k230_spi_fifo_reset(s); + s->idma_active = false; + + k230_spi_set_reg(s, K230_SPI_TXFTLR, 0); + k230_spi_set_reg(s, K230_SPI_RXFTLR, 0); + k230_spi_set_reg(s, K230_SPI_IDR, 0xffffffff); + k230_spi_set_reg(s, K230_SPI_VERSION, 0x3430332a); + k230_spi_flash_select(s, false); + k230_spi_update_irq(s); +} + +static const VMStateDescription vmstate_k230_spi = { + .name = TYPE_K230_SPI, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, K230SpiState, K230_SPI_REG_COUNT), + VMSTATE_UINT8_ARRAY(tx_fifo, K230SpiState, K230_SPI_FIFO_DEPTH), + VMSTATE_UINT8_ARRAY(rx_buf, K230SpiState, K230_SPI_RX_BUFFER_SIZE), + VMSTATE_UINT32(tx_len, K230SpiState), + VMSTATE_UINT32(rx_len, K230SpiState), + VMSTATE_UINT32(rx_pos, K230SpiState), + VMSTATE_BOOL(idma_active, K230SpiState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void k230_spi_realize(DeviceState *dev, Error **errp) +{ + K230SpiState *s = K230_SPI(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + for (int i = 0; i < K230_SPI_IRQ_COUNT; i++) { + sysbus_init_irq(sbd, &s->irq[i]); + } + + s->ssi = ssi_create_bus(dev, "spi"); + s->flash = qdev_new("w25q256"); + if (s->blk) { + if (!qdev_prop_set_drive_err(s->flash, "drive", s->blk, errp)) { + return; + } + } + ssi_realize_and_unref(s->flash, s->ssi, &error_fatal); + s->flash_cs = qdev_get_gpio_in_named(s->flash, SSI_GPIO_CS, 0); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_spi_ops, s, + TYPE_K230_SPI, K230_SPI_SIZE); + sysbus_init_mmio(sbd, &s->mmio); +} + +static const Property k230_spi_properties[] = { + DEFINE_PROP_DRIVE("drive", K230SpiState, blk), +}; + +static void k230_spi_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = k230_spi_realize; + device_class_set_legacy_reset(dc, k230_spi_reset); + device_class_set_props(dc, k230_spi_properties); + dc->vmsd = &vmstate_k230_spi; + dc->desc = "K230 DesignWare SSI controller"; +} + +static const TypeInfo k230_spi_type_info = { + .name = TYPE_K230_SPI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230SpiState), + .class_init = k230_spi_class_init, +}; + +static void k230_spi_register_types(void) +{ + type_register_static(&k230_spi_type_info); +} + +type_init(k230_spi_register_types) diff --git a/hw/ssi/meson.build b/hw/ssi/meson.build index 6afb1ea200121..cb118514074cb 100644 --- a/hw/ssi/meson.build +++ b/hw/ssi/meson.build @@ -5,6 +5,7 @@ system_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_fiu.c', 'npcm_pspi system_ss.add(when: 'CONFIG_PL022', if_true: files('pl022.c')) system_ss.add(when: 'CONFIG_SIFIVE_SPI', if_true: files('sifive_spi.c')) system_ss.add(when: 'CONFIG_SSI', if_true: files('ssi.c')) +system_ss.add(when: 'CONFIG_K230_SPI', if_true: files('k230_spi.c')) system_ss.add(when: 'CONFIG_STM32F2XX_SPI', if_true: files('stm32f2xx_spi.c')) system_ss.add(when: 'CONFIG_XILINX_SPI', if_true: files('xilinx_spi.c')) system_ss.add(when: 'CONFIG_XILINX_SPIPS', if_true: files('xilinx_spips.c')) diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig index b3d823ce2c394..2f2511b8169c8 100644 --- a/hw/timer/Kconfig +++ b/hw/timer/Kconfig @@ -54,6 +54,9 @@ config RENESAS_TMR config RENESAS_CMT bool +config ROCKCHIP_STIMER + bool + config SSE_COUNTER bool diff --git a/hw/timer/meson.build b/hw/timer/meson.build index 201b5d8316d12..1de90e965faf9 100644 --- a/hw/timer/meson.build +++ b/hw/timer/meson.build @@ -9,6 +9,7 @@ system_ss.add(when: 'CONFIG_CMSDK_APB_DUALTIMER', if_true: files('cmsdk-apb-dual system_ss.add(when: 'CONFIG_CMSDK_APB_TIMER', if_true: files('cmsdk-apb-timer.c')) system_ss.add(when: 'CONFIG_RENESAS_TMR', if_true: files('renesas_tmr.c')) system_ss.add(when: 'CONFIG_RENESAS_CMT', if_true: files('renesas_cmt.c')) +system_ss.add(when: 'CONFIG_ROCKCHIP_STIMER', if_true: files('rockchip_stimer.c')) system_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic-timer.c')) system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_mct.c')) system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_pwm.c')) diff --git a/hw/timer/rockchip_stimer.c b/hw/timer/rockchip_stimer.c new file mode 100644 index 0000000000000..ed34bc342203a --- /dev/null +++ b/hw/timer/rockchip_stimer.c @@ -0,0 +1,109 @@ +/* + * Rockchip secure timer stub + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * The RK firmware only needs a writable secure timer control block and + * a non-stuck counter while it initializes cntfrq_el0 and moves on. + */ + +#include "qemu/osdep.h" +#include "hw/timer/rockchip_stimer.h" +#include "qemu/module.h" +#include "qemu/timer.h" + +#define ROCKCHIP_STIMER_COUNTER_L 0x00 +#define ROCKCHIP_STIMER_COUNTER_H 0x08 +#define ROCKCHIP_STIMER_HZ 24000000ULL + +static uint64_t rockchip_stimer_counter(void) +{ + return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), + ROCKCHIP_STIMER_HZ, NANOSECONDS_PER_SECOND); +} + +static uint64_t rockchip_stimer_read(void *opaque, hwaddr offset, + unsigned size) +{ + RockchipSTimerState *s = opaque; + + if (offset + size > ROCKCHIP_STIMER_SIZE || (offset & 3) || size != 4) { + return 0; + } + + switch (offset) { + case ROCKCHIP_STIMER_COUNTER_L: + return (uint32_t)rockchip_stimer_counter(); + case ROCKCHIP_STIMER_COUNTER_H: + return (uint32_t)(rockchip_stimer_counter() >> 32); + default: + return s->regs[offset >> 2]; + } +} + +static void rockchip_stimer_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + RockchipSTimerState *s = opaque; + + if (offset + size > ROCKCHIP_STIMER_SIZE || (offset & 3) || size != 4) { + return; + } + + s->regs[offset >> 2] = value; +} + +static const MemoryRegionOps rockchip_stimer_ops = { + .read = rockchip_stimer_read, + .write = rockchip_stimer_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void rockchip_stimer_reset(DeviceState *dev) +{ + RockchipSTimerState *s = ROCKCHIP_STIMER(dev); + + memset(s->regs, 0, sizeof(s->regs)); +} + +static void rockchip_stimer_realize(DeviceState *dev, Error **errp) +{ + RockchipSTimerState *s = ROCKCHIP_STIMER(dev); + + memory_region_init_io(&s->iomem, OBJECT(s), &rockchip_stimer_ops, s, + "rockchip-stimer", ROCKCHIP_STIMER_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); +} + +static void rockchip_stimer_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = rockchip_stimer_realize; + device_class_set_legacy_reset(dc, rockchip_stimer_reset); + dc->user_creatable = false; +} + +static const TypeInfo rockchip_stimer_info = { + .name = TYPE_ROCKCHIP_STIMER, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RockchipSTimerState), + .class_init = rockchip_stimer_class_init, +}; + +static void rockchip_stimer_register_types(void) +{ + type_register_static(&rockchip_stimer_info); +} + +type_init(rockchip_stimer_register_types) diff --git a/hw/usb/Kconfig b/hw/usb/Kconfig index de95686720cb5..5c5060e601b7c 100644 --- a/hw/usb/Kconfig +++ b/hw/usb/Kconfig @@ -110,6 +110,11 @@ config USB_NETWORK default y depends on USB +config USB_RTL8152 + bool + default y + depends on USB + config USB_SMARTCARD bool default y diff --git a/hw/usb/dev-rtl8152.c b/hw/usb/dev-rtl8152.c new file mode 100644 index 0000000000000..2bb6aed936a5e --- /dev/null +++ b/hw/usb/dev-rtl8152.c @@ -0,0 +1,608 @@ +/* + * QEMU Realtek RTL8152 USB Ethernet device + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "hw/core/qdev-properties.h" +#include "hw/usb/usb.h" +#include "migration/vmstate.h" +#include "desc.h" +#include "net/checksum.h" +#include "net/eth.h" +#include "net/net.h" +#include "system/system.h" +#include "qemu/iov.h" +#include "qemu/module.h" +#include "qom/object.h" + +/*#define TRAFFIC_DEBUG*/ + +#define RTL8152_VENDOR_NUM 0x0bda +#define RTL8152_PRODUCT_NUM 0x8152 + +#define RTL8152_BUFSIZE 32768 + +#define RTL8152_REQ_GET_REGS 0x05 +#define RTL8152_REQ_SET_REGS 0x05 +#define RTL8152_MCU_TYPE_PLA 0x0100 +#define RTL8152_BYTE_EN_MASK 0x000f +#define RTL8152_BYTE_EN_ALL 0x000f +#define RTL8152_PLA_IDR 0xc000 +#define RTL8152_PLA_BOOT_CTRL 0xe004 +#define RTL8152_PLA_TCR0 0xe610 +#define RTL8152_PLA_TCR1 0xe612 +#define RTL8152_PLA_CR 0xe813 +#define RTL8152_PLA_OCP_BASE 0xe86c +#define RTL8152_PLA_PHYSTATUS 0xe908 +#define RTL8152_OCP_BASE_MII 0xa400 +#define RTL8152_OCP_PHY_STATUS 0xa420 +#define RTL8152_RX_DESC_SIZE 24 +#define RTL8152_TX_DESC_SIZE 8 +#define RTL8152_RX_FCS_SIZE 4 +#define RTL8152_RX_LEN_MASK 0x7fff +#define RTL8152_TX_LEN_MASK 0x3ffff +#define RTL8152_TX_IPV4_CS (1u << 29) +#define RTL8152_TX_TCP_CS (1u << 30) +#define RTL8152_TX_UDP_CS (1u << 31) +#define RTL8152_TX_ALIGN 4 +#define RTL8152_INTR_LINK 0x0004 +#define RTL8152_LINK_100_FULL 0x0b +#define RTL8152_AUTOLOAD_DONE (1u << 2) +#define RTL8152_CR_RST 0x10 +#define RTL8152_TCR0_TX_EMPTY 0x0800 + +#define TYPE_USB_RTL8152 "usb-rtl8152" +OBJECT_DECLARE_SIMPLE_TYPE(USBRtl8152State, USB_RTL8152) + +enum { + STRING_MANUFACTURER = 1, + STRING_PRODUCT, + STRING_ETHADDR, + STRING_SERIALNUMBER, + STRING_RTL8152, +}; + +struct USBRtl8152State { + USBDevice dev; + + unsigned int out_ptr; + uint8_t out_buf[RTL8152_BUFSIZE]; + + unsigned int in_ptr, in_len; + uint8_t in_buf[RTL8152_BUFSIZE]; + + USBEndpoint *bulk_in; + + char usbstring_mac[13]; + NICState *nic; + NICConf conf; + + uint16_t rtl_ocp_base; + uint8_t rtl_pla[UINT16_MAX + 1]; + uint8_t rtl_usb[UINT16_MAX + 1]; + uint8_t rtl_ocp[UINT16_MAX + 1]; +}; + +static const USBDescIface desc_iface_rtl8152[] = { + { + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .iInterface = STRING_RTL8152, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | 0x01, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 0x40, + },{ + .bEndpointAddress = USB_DIR_OUT | 0x02, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 0x40, + },{ + .bEndpointAddress = USB_DIR_IN | 0x03, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 0x02, + .bInterval = 8, + } + }, + } +}; + +static const USBDescIface desc_iface_rtl8152_high[] = { + { + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .iInterface = STRING_RTL8152, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | 0x01, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 0x200, + },{ + .bEndpointAddress = USB_DIR_OUT | 0x02, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 0x200, + },{ + .bEndpointAddress = USB_DIR_IN | 0x03, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 0x02, + .bInterval = 4, + } + }, + } +}; + +static const USBDescDevice desc_device_rtl8152 = { + .bcdUSB = 0x0200, + .bDeviceClass = USB_CLASS_VENDOR_SPEC, + .bMaxPacketSize0 = 0x40, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STRING_RTL8152, + .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER, + .bMaxPower = 0x32, + .nif = ARRAY_SIZE(desc_iface_rtl8152), + .ifs = desc_iface_rtl8152, + } + }, +}; + +static const USBDescDevice desc_device_rtl8152_high = { + .bcdUSB = 0x0200, + .bDeviceClass = USB_CLASS_VENDOR_SPEC, + .bMaxPacketSize0 = 0x40, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STRING_RTL8152, + .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER, + .bMaxPower = 0x32, + .nif = ARRAY_SIZE(desc_iface_rtl8152_high), + .ifs = desc_iface_rtl8152_high, + } + }, +}; + +static const USBDescStrings usb_rtl8152_stringtable = { + [STRING_MANUFACTURER] = "Realtek", + [STRING_PRODUCT] = "USB 10/100 LAN", + [STRING_ETHADDR] = "400102030405", + [STRING_SERIALNUMBER] = "1", + [STRING_RTL8152] = "Realtek RTL8152", +}; + +static const USBDesc desc_rtl8152 = { + .id = { + .idVendor = RTL8152_VENDOR_NUM, + .idProduct = RTL8152_PRODUCT_NUM, + .bcdDevice = 0x2000, + .iManufacturer = STRING_MANUFACTURER, + .iProduct = STRING_PRODUCT, + .iSerialNumber = STRING_SERIALNUMBER, + }, + .full = &desc_device_rtl8152, + .high = &desc_device_rtl8152_high, + .str = usb_rtl8152_stringtable, +}; + +static uint8_t *rtl8152_space(USBRtl8152State *s, int index, uint16_t addr) +{ + if (index & RTL8152_MCU_TYPE_PLA) { + if ((addr & 0xf000) == 0xb000) { + return &s->rtl_ocp[(s->rtl_ocp_base & 0xf000) | (addr & 0x0fff)]; + } + return &s->rtl_pla[addr]; + } + + return &s->rtl_usb[addr]; +} + +static uint16_t rtl8152_mii_addr(unsigned int reg) +{ + return RTL8152_OCP_BASE_MII + reg * 2; +} + +static void rtl8152_write_le16(uint8_t *space, uint16_t addr, uint16_t value) +{ + stw_le_p(&space[addr], value); +} + +static void rtl8152_reset_regs(USBRtl8152State *s) +{ + memset(s->rtl_pla, 0, sizeof(s->rtl_pla)); + memset(s->rtl_usb, 0, sizeof(s->rtl_usb)); + memset(s->rtl_ocp, 0, sizeof(s->rtl_ocp)); + + s->rtl_ocp_base = 0; + memcpy(&s->rtl_pla[RTL8152_PLA_IDR], s->conf.macaddr.a, + sizeof(s->conf.macaddr.a)); + rtl8152_write_le16(s->rtl_pla, RTL8152_PLA_TCR0, RTL8152_TCR0_TX_EMPTY); + rtl8152_write_le16(s->rtl_pla, RTL8152_PLA_TCR1, 0x4c00); + s->rtl_pla[RTL8152_PLA_BOOT_CTRL] = RTL8152_AUTOLOAD_DONE; + s->rtl_pla[RTL8152_PLA_CR] = 0; + s->rtl_pla[RTL8152_PLA_PHYSTATUS] = RTL8152_LINK_100_FULL; + rtl8152_write_le16(s->rtl_ocp, RTL8152_OCP_PHY_STATUS, 0x0003); + rtl8152_write_le16(s->rtl_ocp, rtl8152_mii_addr(0), 0x3100); + rtl8152_write_le16(s->rtl_ocp, rtl8152_mii_addr(1), 0x786d); + rtl8152_write_le16(s->rtl_ocp, rtl8152_mii_addr(2), 0x001c); + rtl8152_write_le16(s->rtl_ocp, rtl8152_mii_addr(3), 0xc912); + rtl8152_write_le16(s->rtl_ocp, rtl8152_mii_addr(4), 0x01e1); + rtl8152_write_le16(s->rtl_ocp, rtl8152_mii_addr(5), 0x45e1); + rtl8152_write_le16(s->rtl_ocp, rtl8152_mii_addr(9), 0x0200); +} + +static void rtl8152_update_ocp_base(USBRtl8152State *s, uint16_t addr, + const uint8_t *data, unsigned int size, + unsigned int byen) +{ + unsigned int shift = RTL8152_PLA_OCP_BASE & 3; + + if (addr != (RTL8152_PLA_OCP_BASE & ~3) || size < sizeof(uint32_t)) { + return; + } + + if (byen & (3u << shift)) { + uint32_t val = ldl_le_p(data); + + s->rtl_ocp_base = (val >> (shift * 8)) & 0xf000; + } +} + +static void rtl8152_reg_write(USBRtl8152State *s, int index, uint16_t addr, + const uint8_t *data, unsigned int size) +{ + uint8_t *space = rtl8152_space(s, index, addr); + unsigned int byen = index & RTL8152_BYTE_EN_MASK; + + if (!byen) { + byen = RTL8152_BYTE_EN_ALL; + } + + for (unsigned int i = 0; i < size; i++) { + if (byen & (1u << ((addr + i) & 3))) { + space[i] = data[i]; + } + } + + if (index & RTL8152_MCU_TYPE_PLA) { + rtl8152_update_ocp_base(s, addr, data, size, byen); + } + + if (addr <= RTL8152_PLA_CR && addr + size > RTL8152_PLA_CR) { + s->rtl_pla[RTL8152_PLA_CR] &= ~RTL8152_CR_RST; + } +} + +static void rtl8152_reg_read(USBRtl8152State *s, int index, uint16_t addr, + uint8_t *data, unsigned int size) +{ + uint8_t *space = rtl8152_space(s, index, addr); + + memcpy(data, space, size); +} + +static void rtl8152_reset_in_buf(USBRtl8152State *s) +{ + s->in_ptr = s->in_len = 0; + if (s->nic) { + qemu_flush_queued_packets(qemu_get_queue(s->nic)); + } +} + +static void rtl8152_handle_reset(USBDevice *dev) +{ + USBRtl8152State *s = USB_RTL8152(dev); + + s->out_ptr = 0; + rtl8152_reset_in_buf(s); + rtl8152_reset_regs(s); +} + +static void rtl8152_handle_control(USBDevice *dev, USBPacket *p, int request, + int value, int index, int length, + uint8_t *data) +{ + USBRtl8152State *s = USB_RTL8152(dev); + int ret; + + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return; + } + + switch (request) { + case VendorDeviceRequest | RTL8152_REQ_GET_REGS: + rtl8152_reg_read(s, index, value, data, length); + p->actual_length = length; + return; + + case VendorDeviceOutRequest | RTL8152_REQ_SET_REGS: + rtl8152_reg_write(s, index, value, data, length); + return; + + default: + fprintf(stderr, "rtl8152: failed control transaction: " + "request 0x%x value 0x%x index 0x%x length 0x%x\n", + request, value, index, length); + p->status = USB_RET_STALL; + return; + } +} + +static void rtl8152_handle_statusin(USBRtl8152State *s, USBPacket *p) +{ + uint16_t link = cpu_to_le16(RTL8152_INTR_LINK); + + if (p->iov.size < sizeof(link)) { + p->status = USB_RET_STALL; + return; + } + + usb_packet_copy(p, &link, sizeof(link)); +} + +static void rtl8152_handle_datain(USBRtl8152State *s, USBPacket *p) +{ + int len; + + if (s->in_ptr > s->in_len) { + rtl8152_reset_in_buf(s); + p->status = USB_RET_NAK; + return; + } + if (!s->in_len) { + p->status = USB_RET_NAK; + return; + } + + len = s->in_len - s->in_ptr; + if (len > p->iov.size) { + len = p->iov.size; + } + + usb_packet_copy(p, &s->in_buf[s->in_ptr], len); + s->in_ptr += len; + if (s->in_ptr >= s->in_len && ((s->in_len & (64 - 1)) || !len)) { + rtl8152_reset_in_buf(s); + } + +#ifdef TRAFFIC_DEBUG + fprintf(stderr, "rtl8152: data in len %zu return %d", p->iov.size, len); + iov_hexdump(p->iov.iov, p->iov.niov, stderr, "rtl8152", len); +#endif +} + +static void rtl8152_fix_tx_checksum(uint8_t *frame, size_t frame_len, + uint32_t opts2) +{ + int csum_flags = 0; + + if (opts2 & RTL8152_TX_IPV4_CS) { + csum_flags |= CSUM_IP; + } + if (opts2 & RTL8152_TX_TCP_CS) { + csum_flags |= CSUM_TCP; + } + if (opts2 & RTL8152_TX_UDP_CS) { + csum_flags |= CSUM_UDP; + } + + if (csum_flags) { + net_checksum_calculate(frame, frame_len, csum_flags); + } +} + +static void rtl8152_handle_dataout(USBRtl8152State *s, USBPacket *p) +{ + size_t pos = 0; + +#ifdef TRAFFIC_DEBUG + fprintf(stderr, "rtl8152: data out len %zu\n", p->iov.size); + iov_hexdump(p->iov.iov, p->iov.niov, stderr, "rtl8152", p->iov.size); +#endif + + if (p->iov.size > sizeof(s->out_buf) - s->out_ptr) { + s->out_ptr = 0; + p->status = USB_RET_STALL; + return; + } + + usb_packet_copy(p, &s->out_buf[s->out_ptr], p->iov.size); + s->out_ptr += p->iov.size; + + while (pos + RTL8152_TX_DESC_SIZE <= s->out_ptr) { + uint32_t opts1 = ldl_le_p(&s->out_buf[pos]); + uint32_t opts2 = ldl_le_p(&s->out_buf[pos + sizeof(opts1)]); + uint32_t frame_len = opts1 & RTL8152_TX_LEN_MASK; + size_t frame_off = pos + RTL8152_TX_DESC_SIZE; + size_t frame_end = frame_off + frame_len; + size_t next; + + if (!frame_len || + frame_len > sizeof(s->out_buf) - RTL8152_TX_DESC_SIZE || + frame_end > s->out_ptr) { + break; + } + + rtl8152_fix_tx_checksum(&s->out_buf[frame_off], frame_len, opts2); + qemu_send_packet(qemu_get_queue(s->nic), &s->out_buf[frame_off], + frame_len); + next = QEMU_ALIGN_UP(frame_end, RTL8152_TX_ALIGN); + pos = MIN(next, (size_t)s->out_ptr); + } + + if (pos) { + s->out_ptr -= pos; + memmove(s->out_buf, &s->out_buf[pos], s->out_ptr); + } +} + +static void rtl8152_handle_data(USBDevice *dev, USBPacket *p) +{ + USBRtl8152State *s = USB_RTL8152(dev); + + switch (p->pid) { + case USB_TOKEN_IN: + if (p->ep->nr == 1) { + rtl8152_handle_datain(s, p); + } else if (p->ep->nr == 3) { + rtl8152_handle_statusin(s, p); + } else { + p->status = USB_RET_STALL; + } + break; + + case USB_TOKEN_OUT: + if (p->ep->nr == 2) { + rtl8152_handle_dataout(s, p); + } else { + p->status = USB_RET_STALL; + } + break; + + default: + p->status = USB_RET_STALL; + break; + } + + if (p->status == USB_RET_STALL) { + fprintf(stderr, "rtl8152: failed data transaction: " + "pid 0x%x ep 0x%x len 0x%zx\n", + p->pid, p->ep->nr, p->iov.size); + } +} + +static ssize_t rtl8152_receive(NetClientState *nc, const uint8_t *buf, + size_t size) +{ + USBRtl8152State *s = qemu_get_nic_opaque(nc); + size_t payload_len = MAX(size, (size_t)ETH_ZLEN); + size_t packet_len = payload_len + RTL8152_RX_FCS_SIZE; + size_t total_size = RTL8152_RX_DESC_SIZE + packet_len; + + if (total_size > sizeof(s->in_buf)) { + return -1; + } + + if (s->in_len > 0) { + return 0; + } + + memset(s->in_buf, 0, total_size); + stl_le_p(s->in_buf, packet_len & RTL8152_RX_LEN_MASK); + memcpy(&s->in_buf[RTL8152_RX_DESC_SIZE], buf, size); + + s->in_len = total_size; + s->in_ptr = 0; + usb_wakeup(s->bulk_in, 0); + return size; +} + +static void rtl8152_cleanup(NetClientState *nc) +{ + USBRtl8152State *s = qemu_get_nic_opaque(nc); + + s->nic = NULL; +} + +static NetClientInfo net_rtl8152_info = { + .type = NET_CLIENT_DRIVER_NIC, + .size = sizeof(NICState), + .receive = rtl8152_receive, + .cleanup = rtl8152_cleanup, +}; + +static void rtl8152_realize(USBDevice *dev, Error **errp) +{ + USBRtl8152State *s = USB_RTL8152(dev); + + usb_desc_create_serial(dev); + usb_desc_init(dev); + + s->bulk_in = usb_ep_get(dev, USB_TOKEN_IN, 1); + + qemu_macaddr_default_if_unset(&s->conf.macaddr); + s->nic = qemu_new_nic(&net_rtl8152_info, &s->conf, + object_get_typename(OBJECT(s)), s->dev.qdev.id, + &s->dev.qdev.mem_reentrancy_guard, s); + qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a); + snprintf(s->usbstring_mac, sizeof(s->usbstring_mac), + "%02x%02x%02x%02x%02x%02x", + s->conf.macaddr.a[0], s->conf.macaddr.a[1], + s->conf.macaddr.a[2], s->conf.macaddr.a[3], + s->conf.macaddr.a[4], s->conf.macaddr.a[5]); + usb_desc_set_string(dev, STRING_ETHADDR, s->usbstring_mac); + + rtl8152_reset_regs(s); +} + +static void rtl8152_unrealize(USBDevice *dev) +{ + USBRtl8152State *s = USB_RTL8152(dev); + + qemu_del_nic(s->nic); +} + +static void rtl8152_instance_init(Object *obj) +{ + USBDevice *dev = USB_DEVICE(obj); + USBRtl8152State *s = USB_RTL8152(dev); + + device_add_bootindex_property(obj, &s->conf.bootindex, + "bootindex", "/ethernet-phy@0", + &dev->qdev); +} + +static const VMStateDescription vmstate_usb_rtl8152 = { + .name = "usb-rtl8152", + .unmigratable = 1, +}; + +static const Property rtl8152_properties[] = { + DEFINE_NIC_PROPERTIES(USBRtl8152State, conf), +}; + +static void rtl8152_class_initfn(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->realize = rtl8152_realize; + uc->product_desc = "Realtek RTL8152 USB Ethernet"; + uc->usb_desc = &desc_rtl8152; + uc->handle_attach = usb_desc_attach; + uc->handle_reset = rtl8152_handle_reset; + uc->handle_control = rtl8152_handle_control; + uc->handle_data = rtl8152_handle_data; + uc->unrealize = rtl8152_unrealize; + set_bit(DEVICE_CATEGORY_NETWORK, dc->categories); + dc->fw_name = "network"; + dc->vmsd = &vmstate_usb_rtl8152; + device_class_set_props(dc, rtl8152_properties); +} + +static const TypeInfo rtl8152_info = { + .name = TYPE_USB_RTL8152, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBRtl8152State), + .class_init = rtl8152_class_initfn, + .instance_init = rtl8152_instance_init, +}; + +static void rtl8152_register_types(void) +{ + type_register_static(&rtl8152_info); +} + +type_init(rtl8152_register_types) diff --git a/hw/usb/hcd-dwc2.c b/hw/usb/hcd-dwc2.c index cd3b61158d17f..29432b880d373 100644 --- a/hw/usb/hcd-dwc2.c +++ b/hw/usb/hcd-dwc2.c @@ -64,17 +64,16 @@ /* update irq line */ static inline void dwc2_update_irq(DWC2State *s) { - static int oldlevel; int level = 0; if ((s->gintsts & s->gintmsk) && (s->gahbcfg & GAHBCFG_GLBL_INTR_EN)) { level = 1; } - if (level != oldlevel) { - oldlevel = level; + if (level != s->irq_level) { + s->irq_level = level; trace_usb_dwc2_update_irq(level); - qemu_set_irq(s->irq, level); } + qemu_set_irq(s->irq, level); } /* flag interrupt condition */ @@ -96,15 +95,23 @@ static inline void dwc2_lower_global_irq(DWC2State *s, uint32_t intr) } } +static inline void dwc2_update_host_irq(DWC2State *s) +{ + if (s->haint & s->haintmsk) { + dwc2_raise_global_irq(s, GINTSTS_HCHINT); + } else { + dwc2_lower_global_irq(s, GINTSTS_HCHINT); + } + dwc2_update_irq(s); +} + static inline void dwc2_raise_host_irq(DWC2State *s, uint32_t host_intr) { if (!(s->haint & host_intr)) { s->haint |= host_intr; s->haint &= 0xffff; trace_usb_dwc2_raise_host_irq(host_intr); - if (s->haint & s->haintmsk) { - dwc2_raise_global_irq(s, GINTSTS_HCHINT); - } + dwc2_update_host_irq(s); } } @@ -113,9 +120,7 @@ static inline void dwc2_lower_host_irq(DWC2State *s, uint32_t host_intr) if (s->haint & host_intr) { s->haint &= ~host_intr; trace_usb_dwc2_lower_host_irq(host_intr); - if (!(s->haint & s->haintmsk)) { - dwc2_lower_global_irq(s, GINTSTS_HCHINT); - } + dwc2_update_host_irq(s); } } @@ -879,6 +884,7 @@ static void dwc2_hreg0_write(void *ptr, hwaddr addr, int index, uint64_t val, uint32_t tval, told, old; int prst = 0; int iflg = 0; + int hiflg = 0; if (addr < HCFG || addr > HPRT0) { qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%"HWADDR_PRIx"\n", @@ -900,6 +906,7 @@ static void dwc2_hreg0_write(void *ptr, hwaddr addr, int index, uint64_t val, return; case HAINTMSK: val &= 0xffff; + hiflg = 1; break; case HPRT0: /* don't allow clearing of read-only bits */ @@ -951,6 +958,10 @@ static void dwc2_hreg0_write(void *ptr, hwaddr addr, int index, uint64_t val, *mmio = val; + if (hiflg) { + dwc2_update_host_irq(s); + } + if (iflg > 0) { trace_usb_dwc2_hreg0_action("enable PRTINT"); dwc2_raise_global_irq(s, GINTSTS_PRTINT); @@ -1042,9 +1053,19 @@ static void dwc2_hreg1_write(void *ptr, hwaddr addr, int index, uint64_t val, *mmio = val; if (disflg) { - /* set ChHltd in HCINT */ - s->hreg1[(index & ~7) + 2] |= HCINTMSK_CHHLTD; - iflg = 1; + DWC2Packet *p = &s->packet[index >> 3]; + + p->needs_service = false; + if (p->async == DWC2_ASYNC_INFLIGHT) { + usb_cancel_packet(&p->packet); + usb_packet_cleanup(&p->packet); + p->async = DWC2_ASYNC_NONE; + } + + if (old & HCCHAR_CHENA) { + s->hreg1[(index & ~7) + 2] |= HCINTMSK_CHHLTD; + iflg = 1; + } } if (enflg) { @@ -1303,6 +1324,7 @@ static void dwc2_reset_enter(Object *obj, ResetType type) s->frame_number = 0; s->fi = USB_FRMINTVL - 1; s->next_chan = 0; + s->irq_level = 0; s->working = false; for (i = 0; i < DWC2_NB_CHAN; i++) { diff --git a/hw/usb/hcd-dwc2.h b/hw/usb/hcd-dwc2.h index 68877661ca0da..b68ba2692e1ea 100644 --- a/hw/usb/hcd-dwc2.h +++ b/hw/usb/hcd-dwc2.h @@ -28,7 +28,7 @@ #define DWC2_MMIO_SIZE 0x11000 -#define DWC2_NB_CHAN 8 /* Number of host channels */ +#define DWC2_NB_CHAN 16 /* Number of host channels */ #define DWC2_MAX_XFER_SIZE 65536 /* Max transfer size expected in HCTSIZ */ typedef struct DWC2Packet DWC2Packet; @@ -166,6 +166,7 @@ struct DWC2State { uint16_t frame_number; uint16_t fi; uint16_t next_chan; + int irq_level; bool working; USBPort uport; DWC2Packet packet[DWC2_NB_CHAN]; /* one packet per chan */ diff --git a/hw/usb/meson.build b/hw/usb/meson.build index ba55c28ef69e1..17a0c66b3b4a1 100644 --- a/hw/usb/meson.build +++ b/hw/usb/meson.build @@ -44,6 +44,7 @@ system_ss.add(when: 'CONFIG_USB_STORAGE_UAS', if_true: files('dev-uas.c')) system_ss.add(when: 'CONFIG_USB_AUDIO', if_true: files('dev-audio.c')) system_ss.add(when: 'CONFIG_USB_SERIAL', if_true: files('dev-serial.c')) system_ss.add(when: 'CONFIG_USB_NETWORK', if_true: files('dev-network.c')) +system_ss.add(when: 'CONFIG_USB_RTL8152', if_true: files('dev-rtl8152.c')) if host_os != 'windows' system_ss.add(when: 'CONFIG_USB_STORAGE_MTP', if_true: files('dev-mtp.c')) endif diff --git a/include/exec/translation-block.h b/include/exec/translation-block.h index 40cc6990318ca..ee15608c894eb 100644 --- a/include/exec/translation-block.h +++ b/include/exec/translation-block.h @@ -64,8 +64,8 @@ struct TranslationBlock { * x86: the original user, the Code Segment virtual base, * arm: an extension of tb->flags, * s390x: instruction data for EXECUTE, - * sparc: the next pc of the instruction queue (for delay slots). - * riscv: an extension of tb->flags, + * sparc: the next pc of the instruction queue (for delay slots), + * riscv: an extension of tb->flags. */ uint64_t cs_base; diff --git a/include/hw/arm/ax650x-dwmac.h b/include/hw/arm/ax650x-dwmac.h new file mode 100644 index 0000000000000..47fa262dc4057 --- /dev/null +++ b/include/hw/arm/ax650x-dwmac.h @@ -0,0 +1,15 @@ +/* + * AX650X DWMAC board integration + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_ARM_AX650X_DWMAC_H +#define HW_ARM_AX650X_DWMAC_H + +#include "hw/core/qdev.h" + +void ax650x_dwmac_create(DeviceState *gic); +void ax650x_dwmac_create_fdt(void *fdt); + +#endif /* HW_ARM_AX650X_DWMAC_H */ diff --git a/include/hw/arm/phytium-pi.h b/include/hw/arm/phytium-pi.h new file mode 100644 index 0000000000000..0bfa4d6bae9bb --- /dev/null +++ b/include/hw/arm/phytium-pi.h @@ -0,0 +1,17 @@ +/* + * Local-only Phytium Pi board model + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_ARM_PHYTIUM_PI_H +#define HW_ARM_PHYTIUM_PI_H + +#include "hw/core/boards.h" + +#define TYPE_PHYTIUMPI_MACHINE MACHINE_TYPE_NAME("phytium-pi") +OBJECT_DECLARE_SIMPLE_TYPE(PhytiumPiMachineState, PHYTIUMPI_MACHINE) + +#define PHYTIUMPI_MAX_CPUS 4 + +#endif /* HW_ARM_PHYTIUM_PI_H */ diff --git a/include/hw/char/dw-apb-uart-vendor.h b/include/hw/char/dw-apb-uart-vendor.h new file mode 100644 index 0000000000000..d628674656c72 --- /dev/null +++ b/include/hw/char/dw-apb-uart-vendor.h @@ -0,0 +1,28 @@ +/* + * Synopsys DesignWare APB UART vendor register window + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_CHAR_DW_APB_UART_VENDOR_H +#define HW_CHAR_DW_APB_UART_VENDOR_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_DW_APB_UART_VENDOR "dw-apb-uart-vendor" +OBJECT_DECLARE_SIMPLE_TYPE(DWAPBUARTVendorState, DW_APB_UART_VENDOR) + +#define DW_APB_UART_VENDOR_BASE 0x20 +#define DW_APB_UART_VENDOR_SIZE 0xe0 + +struct DWAPBUARTVendorState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + uint8_t regs[DW_APB_UART_VENDOR_SIZE]; +}; + +#endif /* HW_CHAR_DW_APB_UART_VENDOR_H */ diff --git a/include/hw/char/dw8250.h b/include/hw/char/dw8250.h new file mode 100644 index 0000000000000..59396ad20257e --- /dev/null +++ b/include/hw/char/dw8250.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Synopsys DesignWare APB UART (DW 8250) + * + * Copyright (c) 2026 Kuan-Wei Chiu + */ + +#ifndef HW_CHAR_DW8250_H +#define HW_CHAR_DW8250_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_DW8250 "dw8250" +OBJECT_DECLARE_SIMPLE_TYPE(DW8250State, DW8250) + +struct DW8250State { + SysBusDevice parent_obj; + + MemoryRegion container; + MemoryRegion ext_iomem; + DeviceState *serial_mm; + + uint8_t regshift; +}; + +#endif diff --git a/include/hw/char/nxp_lpuart.h b/include/hw/char/nxp_lpuart.h new file mode 100644 index 0000000000000..15de1f63ebe2b --- /dev/null +++ b/include/hw/char/nxp_lpuart.h @@ -0,0 +1,37 @@ +/* + * NXP Low Power UART + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_CHAR_NXP_LPUART_H +#define HW_CHAR_NXP_LPUART_H + +#include "chardev/char-fe.h" +#include "hw/core/register.h" +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_NXP_LPUART "nxp.lpuart" +OBJECT_DECLARE_SIMPLE_TYPE(NXPLPUARTState, NXP_LPUART) + +/* + * Register storage slots for the contiguous 32-bit LPUART register subset + * from offsets 0x00 through 0x2c. + */ +#define NXP_LPUART_R_MAX (0x30 / 4) + +struct NXPLPUARTState { + SysBusDevice parent_obj; + + RegisterInfoArray *reg_array; + RegisterInfo regs_info[NXP_LPUART_R_MAX]; + uint32_t regs[NXP_LPUART_R_MAX]; + + CharFrontend chr; + qemu_irq irq; +}; + +#endif diff --git a/include/hw/display/k230_display.h b/include/hw/display/k230_display.h new file mode 100644 index 0000000000000..76c5a136a9ec7 --- /dev/null +++ b/include/hw/display/k230_display.h @@ -0,0 +1,58 @@ +/* + * K230 display controller register blocks + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_DISPLAY_K230_DISPLAY_H +#define HW_DISPLAY_K230_DISPLAY_H + +#include "hw/core/sysbus.h" +#include "hw/core/irq.h" +#include "system/memory.h" +#include "qemu/timer.h" +#include "qom/object.h" +#include "ui/console.h" + +#define TYPE_K230_VO "riscv.k230.vo" +OBJECT_DECLARE_SIMPLE_TYPE(K230VoState, K230_VO) + +#define TYPE_K230_DSI "riscv.k230.dsi" +OBJECT_DECLARE_SIMPLE_TYPE(K230DsiState, K230_DSI) + +#define K230_VO_SIZE 0x10000 +#define K230_DSI_SIZE 0x1000 + +struct K230VoState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + MemoryRegionSection fbsection; + QemuConsole *con; + qemu_irq irq; + QEMUTimer *vblank_timer; + hwaddr fbdev_base; + hwaddr fb_base; + uint32_t fbdev_width; + uint32_t fbdev_height; + uint32_t fbdev_stride; + uint32_t src_width; + uint32_t cols; + uint32_t rows; + bool fbdev_compat; + bool invalidate; + uint8_t regs[K230_VO_SIZE]; +}; + +struct K230DsiState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + uint8_t regs[K230_DSI_SIZE]; +}; + +#endif /* HW_DISPLAY_K230_DISPLAY_H */ diff --git a/include/hw/dma/k230_gsdma.h b/include/hw/dma/k230_gsdma.h new file mode 100644 index 0000000000000..44c47a16756d9 --- /dev/null +++ b/include/hw/dma/k230_gsdma.h @@ -0,0 +1,36 @@ +/* + * K230 GSDMA controller + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_DMA_K230_GSDMA_H +#define HW_DMA_K230_GSDMA_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_K230_GSDMA "riscv.k230.gsdma" +OBJECT_DECLARE_SIMPLE_TYPE(K230GsdmaState, K230_GSDMA) + +#define K230_GSDMA_SIZE 0x4000 + +#define K230_GSDMA_UGZIP_RD_CH 0 +#define K230_GSDMA_UGZIP_WR_CH 1 + +struct K230GsdmaState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + qemu_irq irq; + uint8_t regs[K230_GSDMA_SIZE]; +}; + +uint32_t k230_gsdma_get_llt_saddr(K230GsdmaState *s, unsigned int ch); +void k230_gsdma_ugzip_complete(K230GsdmaState *s); + +#endif /* HW_DMA_K230_GSDMA_H */ diff --git a/include/hw/dma/k230_pdma.h b/include/hw/dma/k230_pdma.h new file mode 100644 index 0000000000000..d84984f074e83 --- /dev/null +++ b/include/hw/dma/k230_pdma.h @@ -0,0 +1,37 @@ +/* + * K230 PDMA controller + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef HW_DMA_K230_PDMA_H +#define HW_DMA_K230_PDMA_H + +#include "hw/core/register.h" +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_K230_PDMA "riscv.k230.pdma" +OBJECT_DECLARE_SIMPLE_TYPE(K230PdmaState, K230_PDMA) + +#define K230_PDMA_SIZE 0x400 +#define K230_PDMA_MMIO_SIZE 0x4000 +#define K230_PDMA_LEGACY_SIZE K230_PDMA_MMIO_SIZE +#define K230_PDMA_R_MAX (K230_PDMA_SIZE / sizeof(uint32_t)) + +struct K230PdmaState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + qemu_irq irq; + RegisterInfoArray *reg_array; + bool irq_level; + RegisterInfo regs_info[K230_PDMA_R_MAX]; + uint32_t regs[K230_PDMA_R_MAX]; + uint8_t legacy_regs[K230_PDMA_LEGACY_SIZE]; +}; + +#endif /* HW_DMA_K230_PDMA_H */ diff --git a/include/hw/gpio/dw-apb-gpio.h b/include/hw/gpio/dw-apb-gpio.h new file mode 100644 index 0000000000000..f3740f3b5c6c6 --- /dev/null +++ b/include/hw/gpio/dw-apb-gpio.h @@ -0,0 +1,32 @@ +/* + * Synopsys DesignWare APB GPIO + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_GPIO_DW_APB_GPIO_H +#define HW_GPIO_DW_APB_GPIO_H + +#include "hw/core/irq.h" +#include "hw/core/register.h" +#include "hw/core/sysbus.h" + +#define TYPE_DW_APB_GPIO "dw-apb-gpio" +OBJECT_DECLARE_SIMPLE_TYPE(DWAPBGPIOState, DW_APB_GPIO) + +#define DW_APB_GPIO_NR_PINS 32 +#define DW_APB_GPIO_REG_SIZE 0x54 +#define DW_APB_GPIO_NR_REGS (DW_APB_GPIO_REG_SIZE / sizeof(uint32_t)) +#define DW_APB_GPIO_MMIO_SIZE 0x400 + +typedef struct DWAPBGPIOState { + SysBusDevice parent; + + uint32_t regs[DW_APB_GPIO_NR_REGS]; + RegisterInfo regs_info[DW_APB_GPIO_NR_REGS]; + RegisterInfoArray *reg_array; + qemu_irq output[DW_APB_GPIO_NR_PINS]; + uint32_t input; +} DWAPBGPIOState; + +#endif /* HW_GPIO_DW_APB_GPIO_H */ diff --git a/include/hw/gpio/rockchip_gpio.h b/include/hw/gpio/rockchip_gpio.h new file mode 100644 index 0000000000000..80c40f60dff1a --- /dev/null +++ b/include/hw/gpio/rockchip_gpio.h @@ -0,0 +1,36 @@ +/* + * Rockchip GPIO bank emulation + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_GPIO_ROCKCHIP_GPIO_H +#define HW_GPIO_ROCKCHIP_GPIO_H + +#include "hw/core/irq.h" +#include "hw/core/register.h" +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_ROCKCHIP_GPIO "rockchip-gpio" +OBJECT_DECLARE_SIMPLE_TYPE(RockchipGPIOState, ROCKCHIP_GPIO) + +#define ROCKCHIP_GPIO_PINS 32 +#define ROCKCHIP_GPIO_MMIO_SIZE 0x100 +#define ROCKCHIP_GPIO_NR_REGS (ROCKCHIP_GPIO_MMIO_SIZE / sizeof(uint32_t)) + +struct RockchipGPIOState { + SysBusDevice parent_obj; + + RegisterInfoArray *reg_array; + RegisterInfo regs_info[ROCKCHIP_GPIO_NR_REGS]; + uint32_t regs[ROCKCHIP_GPIO_NR_REGS]; + + qemu_irq irq; + qemu_irq output[ROCKCHIP_GPIO_PINS]; + uint32_t input_level; +}; + +#endif /* HW_GPIO_ROCKCHIP_GPIO_H */ diff --git a/include/hw/i2c/k230_i2c.h b/include/hw/i2c/k230_i2c.h new file mode 100644 index 0000000000000..2f7a724a7bcbe --- /dev/null +++ b/include/hw/i2c/k230_i2c.h @@ -0,0 +1,39 @@ +/* + * K230 DesignWare I2C controller + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_I2C_K230_I2C_H +#define HW_I2C_K230_I2C_H + +#include "hw/core/sysbus.h" +#include "hw/i2c/i2c.h" +#include "qom/object.h" + +#define TYPE_K230_I2C "riscv.k230.i2c" +OBJECT_DECLARE_SIMPLE_TYPE(K230I2CState, K230_I2C) + +#define K230_I2C_SIZE 0x1000 +#define K230_I2C_REG_COUNT (K230_I2C_SIZE / 4) +#define K230_I2C_FIFO_DEPTH 16 + +struct K230I2CState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + qemu_irq irq; + I2CBus *bus; + + uint32_t regs[K230_I2C_REG_COUNT]; + uint8_t rx_fifo[K230_I2C_FIFO_DEPTH]; + uint8_t rx_pos; + uint8_t rx_len; + uint8_t address; + bool started; + bool recv; +}; + +#endif /* HW_I2C_K230_I2C_H */ diff --git a/include/hw/i2c/k230_ov5647.h b/include/hw/i2c/k230_ov5647.h new file mode 100644 index 0000000000000..14f1d52f0e68d --- /dev/null +++ b/include/hw/i2c/k230_ov5647.h @@ -0,0 +1,14 @@ +/* + * K230 OV5647 camera sensor stub + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_I2C_K230_OV5647_H +#define HW_I2C_K230_OV5647_H + +#include "hw/i2c/i2c.h" + +#define TYPE_K230_OV5647 "k230.ov5647" + +#endif /* HW_I2C_K230_OV5647_H */ diff --git a/include/hw/intc/k230_clint.h b/include/hw/intc/k230_clint.h new file mode 100644 index 0000000000000..2ab7972c6e6a0 --- /dev/null +++ b/include/hw/intc/k230_clint.h @@ -0,0 +1,33 @@ +/* + * K230 T-Head C908 S-mode CLINT extension + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_INTC_K230_CLINT_H +#define HW_INTC_K230_CLINT_H + +#include "hw/core/sysbus.h" + +#define TYPE_K230_CLINT_SMODE "riscv.k230.clint-smode" +OBJECT_DECLARE_SIMPLE_TYPE(K230ClintSModeState, K230_CLINT_SMODE) + +struct K230ClintSModeState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + uint32_t hartid_base; + uint32_t cpu_index_base; + uint32_t num_harts; +}; + +DeviceState *k230_clint_smode_create_in(MemoryRegion *mem, hwaddr addr, + uint32_t hartid_base, + uint32_t cpu_index_base, + uint32_t num_harts); + +#endif /* HW_INTC_K230_CLINT_H */ diff --git a/include/hw/intc/riscv_aclint.h b/include/hw/intc/riscv_aclint.h index 0e0b98acb08a9..73b2514b29a82 100644 --- a/include/hw/intc/riscv_aclint.h +++ b/include/hw/intc/riscv_aclint.h @@ -38,6 +38,7 @@ typedef struct RISCVAclintMTimerState { /*< public >*/ MemoryRegion mmio; uint32_t hartid_base; + uint32_t cpu_index_base; uint32_t num_harts; uint32_t timecmp_base; uint32_t time_base; @@ -51,6 +52,11 @@ DeviceState *riscv_aclint_mtimer_create(hwaddr addr, hwaddr size, uint32_t timecmp_base, uint32_t time_base, uint32_t timebase_freq, bool provide_rdtime); +DeviceState *riscv_aclint_mtimer_create_in(MemoryRegion *mem, hwaddr addr, + hwaddr size, uint32_t hartid_base, uint32_t cpu_index_base, + uint32_t num_harts, uint32_t timecmp_base, uint32_t time_base, + uint32_t timebase_freq, bool provide_rdtime); + #define TYPE_RISCV_ACLINT_SWI "riscv.aclint.swi" #define RISCV_ACLINT_SWI(obj) \ @@ -63,6 +69,7 @@ typedef struct RISCVAclintSwiState { /*< public >*/ MemoryRegion mmio; uint32_t hartid_base; + uint32_t cpu_index_base; uint32_t num_harts; uint32_t sswi; qemu_irq *soft_irqs; @@ -71,6 +78,10 @@ typedef struct RISCVAclintSwiState { DeviceState *riscv_aclint_swi_create(hwaddr addr, uint32_t hartid_base, uint32_t num_harts, bool sswi); +DeviceState *riscv_aclint_swi_create_in(MemoryRegion *mem, hwaddr addr, + uint32_t hartid_base, uint32_t cpu_index_base, uint32_t num_harts, + bool sswi); + enum { RISCV_ACLINT_DEFAULT_MTIMECMP = 0x0, RISCV_ACLINT_DEFAULT_MTIME = 0x7ff8, diff --git a/include/hw/intc/sifive_plic.h b/include/hw/intc/sifive_plic.h index 32973dbf281e1..f560b0e8fdf44 100644 --- a/include/hw/intc/sifive_plic.h +++ b/include/hw/intc/sifive_plic.h @@ -62,6 +62,7 @@ struct SiFivePLICState { /* config */ char *hart_config; uint32_t hartid_base; + uint32_t cpu_index_base; uint32_t num_sources; uint32_t num_priorities; uint32_t priority_base; @@ -84,4 +85,11 @@ DeviceState *sifive_plic_create(hwaddr addr, char *hart_config, uint32_t enable_stride, uint32_t context_base, uint32_t context_stride, uint32_t aperture_size); +DeviceState *sifive_plic_create_in(MemoryRegion *mem, hwaddr addr, + char *hart_config, uint32_t num_harts, uint32_t hartid_base, + uint32_t cpu_index_base, uint32_t num_sources, uint32_t num_priorities, + uint32_t priority_base, uint32_t pending_base, uint32_t enable_base, + uint32_t enable_stride, uint32_t context_base, uint32_t context_stride, + uint32_t aperture_size); + #endif diff --git a/include/hw/misc/ax650x-dwmac-glue.h b/include/hw/misc/ax650x-dwmac-glue.h new file mode 100644 index 0000000000000..d76705277dc68 --- /dev/null +++ b/include/hw/misc/ax650x-dwmac-glue.h @@ -0,0 +1,36 @@ +/* + * Axera AX650X DWMAC clock/reset/interface glue + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_AX650X_DWMAC_GLUE_H +#define HW_MISC_AX650X_DWMAC_GLUE_H + +#include "hw/core/register.h" +#include "hw/core/sysbus.h" + +#define TYPE_AX650X_DWMAC_GLUE "ax650x-dwmac-glue" +OBJECT_DECLARE_SIMPLE_TYPE(AX650XDWMACGlueState, AX650X_DWMAC_GLUE) + +#define AX650X_DWMAC_GLB_REG_SIZE 0xa0 +#define AX650X_DWMAC_GLB_NR_REGS \ + (AX650X_DWMAC_GLB_REG_SIZE / sizeof(uint32_t)) +#define AX650X_DWMAC_CLK_REG_SIZE 0x40 +#define AX650X_DWMAC_CLK_NR_REGS \ + (AX650X_DWMAC_CLK_REG_SIZE / sizeof(uint32_t)) +#define AX650X_DWMAC_GLUE_MMIO_SIZE 0x1000 + +typedef struct AX650XDWMACGlueState { + SysBusDevice parent; + + uint32_t glb_regs[AX650X_DWMAC_GLB_NR_REGS]; + RegisterInfo glb_regs_info[AX650X_DWMAC_GLB_NR_REGS]; + RegisterInfoArray *glb_reg_array; + uint32_t clk_regs[AX650X_DWMAC_CLK_NR_REGS]; + RegisterInfo clk_regs_info[AX650X_DWMAC_CLK_NR_REGS]; + RegisterInfoArray *clk_reg_array; + uint8_t port; +} AX650XDWMACGlueState; + +#endif /* HW_MISC_AX650X_DWMAC_GLUE_H */ diff --git a/include/hw/misc/ax650x-hwspinlock.h b/include/hw/misc/ax650x-hwspinlock.h new file mode 100644 index 0000000000000..1224091d06d35 --- /dev/null +++ b/include/hw/misc/ax650x-hwspinlock.h @@ -0,0 +1,25 @@ +/* + * AXERA AX650X hardware spinlock + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_AX650X_HWSPINLOCK_H +#define HW_MISC_AX650X_HWSPINLOCK_H + +#include "hw/core/sysbus.h" + +#define TYPE_AX650X_HWSPINLOCK "ax650x-hwspinlock" +OBJECT_DECLARE_SIMPLE_TYPE(AX650XHWSpinlockState, AX650X_HWSPINLOCK) + +#define AX650X_HWSPINLOCK_MMIO_SIZE 0x1000 +#define AX650X_HWSPINLOCK_COUNT 32 + +typedef struct AX650XHWSpinlockState { + SysBusDevice parent; + + MemoryRegion iomem; + uint32_t locked; +} AX650XHWSpinlockState; + +#endif /* HW_MISC_AX650X_HWSPINLOCK_H */ diff --git a/include/hw/misc/cv1800b_clk.h b/include/hw/misc/cv1800b_clk.h new file mode 100644 index 0000000000000..05c0d1ca1b5dc --- /dev/null +++ b/include/hw/misc/cv1800b_clk.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Sophgo CV1800B Clock Controller + * + * Copyright (c) 2026 Kuan-Wei Chiu + */ + +#ifndef HW_MISC_CV1800B_CLK_H +#define HW_MISC_CV1800B_CLK_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_CV1800B_CLK "cv1800b-clk" +OBJECT_DECLARE_SIMPLE_TYPE(CV1800BClkState, CV1800B_CLK) + +struct CV1800BClkState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + uint32_t regs[0x1000 / 4]; +}; + +#endif diff --git a/include/hw/misc/k230_adc.h b/include/hw/misc/k230_adc.h new file mode 100644 index 0000000000000..92a29a8f3005a --- /dev/null +++ b/include/hw/misc/k230_adc.h @@ -0,0 +1,27 @@ +/* + * K230 ADC register block + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_K230_ADC_H +#define HW_MISC_K230_ADC_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_K230_ADC "riscv.k230.adc" +OBJECT_DECLARE_SIMPLE_TYPE(K230AdcState, K230_ADC) + +#define K230_ADC_SIZE 0x1000 + +struct K230AdcState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + uint8_t regs[K230_ADC_SIZE]; +}; + +#endif /* HW_MISC_K230_ADC_H */ diff --git a/include/hw/misc/k230_dewarp.h b/include/hw/misc/k230_dewarp.h new file mode 100644 index 0000000000000..3df3abaaa1b50 --- /dev/null +++ b/include/hw/misc/k230_dewarp.h @@ -0,0 +1,33 @@ +/* + * K230 DW200 dewarp engine + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_K230_DEWARP_H +#define HW_MISC_K230_DEWARP_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_K230_DEWARP "riscv.k230.dewarp" +OBJECT_DECLARE_SIMPLE_TYPE(K230DewarpState, K230_DEWARP) + +#define K230_DEWARP_SIZE 0x1000 + +struct K230DewarpState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + qemu_irq dwe_irq; + qemu_irq fe_irq; + qemu_irq vse_irq; + bool dwe_irq_level; + bool fe_irq_level; + bool vse_irq_level; + uint8_t regs[K230_DEWARP_SIZE]; +}; + +#endif /* HW_MISC_K230_DEWARP_H */ diff --git a/include/hw/misc/k230_gpio.h b/include/hw/misc/k230_gpio.h new file mode 100644 index 0000000000000..2a1b9efc5ba0c --- /dev/null +++ b/include/hw/misc/k230_gpio.h @@ -0,0 +1,31 @@ +/* + * K230 GPIO register block + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_K230_GPIO_H +#define HW_MISC_K230_GPIO_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_K230_GPIO "riscv.k230.gpio" +OBJECT_DECLARE_SIMPLE_TYPE(K230GpioState, K230_GPIO) + +#define K230_GPIO_SIZE 0x1000 +#define K230_GPIO_IRQ_COUNT 32 + +struct K230GpioState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + qemu_irq irq[K230_GPIO_IRQ_COUNT]; + uint8_t regs[K230_GPIO_SIZE]; + uint32_t input; + uint32_t last_input; +}; + +#endif /* HW_MISC_K230_GPIO_H */ diff --git a/include/hw/misc/k230_hardlock.h b/include/hw/misc/k230_hardlock.h new file mode 100644 index 0000000000000..d4078ec571ff7 --- /dev/null +++ b/include/hw/misc/k230_hardlock.h @@ -0,0 +1,32 @@ +/* + * K230 hardlock registers + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_K230_HARDLOCK_H +#define HW_MISC_K230_HARDLOCK_H + +#include "hw/core/sysbus.h" +#include "hw/core/irq.h" +#include "qom/object.h" + +#define TYPE_K230_HARDLOCK "riscv.k230.hardlock" +OBJECT_DECLARE_SIMPLE_TYPE(K230HardlockState, K230_HARDLOCK) + +#define K230_HARDLOCK_SIZE 0x1000 +#define K230_HARDLOCK_COUNT 128 +#define K230_HARDLOCK_IPCM_IRQ_COUNT 4 + +struct K230HardlockState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + qemu_irq irqs[K230_HARDLOCK_IPCM_IRQ_COUNT]; + uint8_t regs[K230_HARDLOCK_SIZE]; + bool locks[K230_HARDLOCK_COUNT]; +}; + +#endif /* HW_MISC_K230_HARDLOCK_H */ diff --git a/include/hw/misc/k230_hi_sys_cfg.h b/include/hw/misc/k230_hi_sys_cfg.h new file mode 100644 index 0000000000000..fb88d11d353c7 --- /dev/null +++ b/include/hw/misc/k230_hi_sys_cfg.h @@ -0,0 +1,29 @@ +/* + * K230 high-speed system config registers + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_K230_HI_SYS_CFG_H +#define HW_MISC_K230_HI_SYS_CFG_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_K230_HI_SYS_CFG "riscv.k230.hi-sys-cfg" +OBJECT_DECLARE_SIMPLE_TYPE(K230HiSysCfgState, K230_HI_SYS_CFG) + +#define K230_HI_SYS_CFG_SIZE 0x400 + +struct K230HiSysCfgState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + uint8_t regs[K230_HI_SYS_CFG_SIZE]; +}; + +#endif /* HW_MISC_K230_HI_SYS_CFG_H */ diff --git a/include/hw/misc/k230_i2s.h b/include/hw/misc/k230_i2s.h new file mode 100644 index 0000000000000..b66da6f9b8a8e --- /dev/null +++ b/include/hw/misc/k230_i2s.h @@ -0,0 +1,41 @@ +/* + * K230 I2S / WS2812 registers + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef HW_MISC_K230_I2S_H +#define HW_MISC_K230_I2S_H + +#include "hw/core/register.h" +#include "hw/misc/k230_regs.h" +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_K230_I2S "riscv.k230.i2s" +OBJECT_DECLARE_SIMPLE_TYPE(K230I2SState, K230_I2S) + +#define K230_I2S_SIZE 0x1000 +#define K230_I2S_R_MAX (K230_I2S_SIZE / sizeof(uint32_t)) +#define K230_I2S_WS2812_SAMPLES 4 + +struct K230I2SState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + RegisterInfoArray *reg_array; + K230RegsState *compat_regs; + bool state_loaded; + bool compat_imported; + RegisterInfo regs_info[K230_I2S_R_MAX]; + uint32_t regs[K230_I2S_R_MAX]; + uint32_t ws2812_byte_count; + uint32_t ws2812_byte[K230_I2S_WS2812_SAMPLES]; + uint32_t ws2812_padding_count; + uint32_t ws2812_invalid_count; +}; + +#endif /* HW_MISC_K230_I2S_H */ diff --git a/include/hw/misc/k230_iomux.h b/include/hw/misc/k230_iomux.h new file mode 100644 index 0000000000000..04850e108d3c1 --- /dev/null +++ b/include/hw/misc/k230_iomux.h @@ -0,0 +1,27 @@ +/* + * K230 IOMUX register block + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_K230_IOMUX_H +#define HW_MISC_K230_IOMUX_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_K230_IOMUX "riscv.k230.iomux" +OBJECT_DECLARE_SIMPLE_TYPE(K230IomuxState, K230_IOMUX) + +#define K230_IOMUX_SIZE 0x800 + +struct K230IomuxState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + uint8_t regs[K230_IOMUX_SIZE]; +}; + +#endif /* HW_MISC_K230_IOMUX_H */ diff --git a/include/hw/misc/k230_isp.h b/include/hw/misc/k230_isp.h new file mode 100644 index 0000000000000..123e844945875 --- /dev/null +++ b/include/hw/misc/k230_isp.h @@ -0,0 +1,35 @@ +/* + * K230 ISP media pipeline registers + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_K230_ISP_H +#define HW_MISC_K230_ISP_H + +#include "hw/core/sysbus.h" +#include "qemu/timer.h" +#include "qom/object.h" + +#define TYPE_K230_ISP "riscv.k230.isp" +OBJECT_DECLARE_SIMPLE_TYPE(K230IspState, K230_ISP) + +#define K230_ISP_SIZE 0x8000 + +struct K230IspState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + qemu_irq isp_irq; + qemu_irq mi_irq; + qemu_irq fe_irq; + QEMUTimer mcm_frame_timer; + bool isp_irq_level; + bool mi_irq_level; + bool fe_irq_level; + uint8_t regs[K230_ISP_SIZE]; +}; + +#endif /* HW_MISC_K230_ISP_H */ diff --git a/include/hw/misc/k230_kpu.h b/include/hw/misc/k230_kpu.h new file mode 100644 index 0000000000000..1cdcface8cca2 --- /dev/null +++ b/include/hw/misc/k230_kpu.h @@ -0,0 +1,38 @@ +/* + * K230 KPU/GNNE engine + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_K230_KPU_H +#define HW_MISC_K230_KPU_H + +#include "hw/core/sysbus.h" +#include "qemu/timer.h" +#include "qom/object.h" + +#define TYPE_K230_KPU "riscv.k230.kpu" +OBJECT_DECLARE_SIMPLE_TYPE(K230KpuState, K230_KPU) + +#define K230_KPU_SIZE 0x800 + +struct K230KpuState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + qemu_irq irq; + QEMUTimer complete_timer; + bool busy; + bool irq_level; + uint8_t regs[K230_KPU_SIZE]; + uint8_t *gnne_rdata_shadow; + uint64_t gnne_rdata_shadow_base; + uint64_t gnne_rdata_shadow_size; + bool gnne_rdata_shadow_valid; +}; + +#endif /* HW_MISC_K230_KPU_H */ diff --git a/include/hw/misc/k230_nonai_2d.h b/include/hw/misc/k230_nonai_2d.h new file mode 100644 index 0000000000000..b863c5ebf4771 --- /dev/null +++ b/include/hw/misc/k230_nonai_2d.h @@ -0,0 +1,31 @@ +/* + * K230 non-AI 2D engine + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_K230_NONAI_2D_H +#define HW_MISC_K230_NONAI_2D_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_K230_NONAI_2D "riscv.k230.nonai-2d" +OBJECT_DECLARE_SIMPLE_TYPE(K230NonAI2DState, K230_NONAI_2D) + +#define K230_NONAI_2D_SIZE 0x4000 + +struct K230NonAI2DState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + qemu_irq irq; + bool irq_level; + uint8_t regs[K230_NONAI_2D_SIZE]; +}; + +#endif /* HW_MISC_K230_NONAI_2D_H */ diff --git a/include/hw/misc/k230_pmu.h b/include/hw/misc/k230_pmu.h new file mode 100644 index 0000000000000..a8b61b1661a6f --- /dev/null +++ b/include/hw/misc/k230_pmu.h @@ -0,0 +1,30 @@ +/* + * K230 power management unit + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_K230_PMU_H +#define HW_MISC_K230_PMU_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_K230_PMU "riscv.k230.pmu" +OBJECT_DECLARE_SIMPLE_TYPE(K230PmuState, K230_PMU) + +#define K230_PMU_MMIO_SIZE 0xc00 + +struct K230PmuState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + uint8_t regs[K230_PMU_MMIO_SIZE]; + uint8_t poweroff_step; +}; + +#endif /* HW_MISC_K230_PMU_H */ diff --git a/include/hw/misc/k230_pwm.h b/include/hw/misc/k230_pwm.h new file mode 100644 index 0000000000000..4bad46c87f362 --- /dev/null +++ b/include/hw/misc/k230_pwm.h @@ -0,0 +1,27 @@ +/* + * K230 PWM register block + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_K230_PWM_H +#define HW_MISC_K230_PWM_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_K230_PWM "riscv.k230.pwm" +OBJECT_DECLARE_SIMPLE_TYPE(K230PwmState, K230_PWM) + +#define K230_PWM_SIZE 0x1000 + +struct K230PwmState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + uint8_t regs[K230_PWM_SIZE]; +}; + +#endif /* HW_MISC_K230_PWM_H */ diff --git a/include/hw/misc/k230_regs.h b/include/hw/misc/k230_regs.h new file mode 100644 index 0000000000000..d319b1d7b16d5 --- /dev/null +++ b/include/hw/misc/k230_regs.h @@ -0,0 +1,56 @@ +/* + * K230 scratch register block + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_K230_REGS_H +#define HW_MISC_K230_REGS_H + +#include "hw/core/sysbus.h" +#include "qemu/timer.h" +#include "qom/object.h" + +#define TYPE_K230_REGS "riscv.k230.regs" +OBJECT_DECLARE_SIMPLE_TYPE(K230RegsState, K230_REGS) + +#define K230_REGS_DEFAULT_SIZE 0x1000 +#define K230_REGS_STORAGE_SIZE 0x10000 +#define K230_REGS_NO_IRQ_OFFSET UINT64_MAX + +struct K230RegsState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + qemu_irq irq; + uint64_t size; + uint64_t irq_start_offset; + uint64_t irq_start2_offset; + uint64_t irq_start3_offset; + uint64_t irq_start_mask; + uint64_t irq_clear_offset; + uint64_t irq_status_offset; + uint64_t irq_status_size; + uint64_t irq_status_value; + uint64_t irq_delay_ns; + uint64_t irq_command_start_offset; + uint64_t irq_command_end_offset; + uint64_t irq_command_hi_offset; + uint64_t complete_zero_base; + uint64_t complete_zero_size; + uint64_t complete_zero_page_size; + uint64_t counter_offset; + uint64_t counter_size; + uint64_t counter_frequency; + QEMUTimer irq_timer; + bool irq_pending; + bool irq_clear_clears_status; + bool irq_on_any_write; + bool complete_zero_command_pages; + bool irq_level; + uint8_t regs[K230_REGS_STORAGE_SIZE]; +}; + +#endif /* HW_MISC_K230_REGS_H */ diff --git a/include/hw/misc/k230_rx_csi.h b/include/hw/misc/k230_rx_csi.h new file mode 100644 index 0000000000000..5a0023f8648ed --- /dev/null +++ b/include/hw/misc/k230_rx_csi.h @@ -0,0 +1,27 @@ +/* + * K230 RX CSI and video input registers + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_K230_RX_CSI_H +#define HW_MISC_K230_RX_CSI_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_K230_RX_CSI "riscv.k230.rx-csi" +OBJECT_DECLARE_SIMPLE_TYPE(K230RxCsiState, K230_RX_CSI) + +#define K230_RX_CSI_SIZE 0x10000 + +struct K230RxCsiState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + uint8_t regs[K230_RX_CSI_SIZE]; +}; + +#endif /* HW_MISC_K230_RX_CSI_H */ diff --git a/include/hw/misc/k230_security.h b/include/hw/misc/k230_security.h new file mode 100644 index 0000000000000..e0a00634b2bca --- /dev/null +++ b/include/hw/misc/k230_security.h @@ -0,0 +1,29 @@ +/* + * K230 security register block + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_K230_SECURITY_H +#define HW_MISC_K230_SECURITY_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_K230_SECURITY "riscv.k230.security" +OBJECT_DECLARE_SIMPLE_TYPE(K230SecurityState, K230_SECURITY) + +#define K230_SECURITY_SIZE 0x8000 +#define K230_OTP_SIZE 0x300 + +struct K230SecurityState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + uint8_t regs[K230_SECURITY_SIZE]; + uint8_t otp[K230_OTP_SIZE]; +}; + +#endif /* HW_MISC_K230_SECURITY_H */ diff --git a/include/hw/misc/k230_sysctl.h b/include/hw/misc/k230_sysctl.h new file mode 100644 index 0000000000000..f288179d1348e --- /dev/null +++ b/include/hw/misc/k230_sysctl.h @@ -0,0 +1,62 @@ +/* + * K230 system controller blocks + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_K230_SYSCTL_H +#define HW_MISC_K230_SYSCTL_H + +#include "hw/core/sysbus.h" +#include "hw/core/cpu.h" +#include "qom/object.h" + +#define TYPE_K230_SYSCTL_BOOT "riscv.k230.sysctl-boot" +OBJECT_DECLARE_SIMPLE_TYPE(K230SysctlBootState, K230_SYSCTL_BOOT) + +#define TYPE_K230_SYSCTL_POWER "riscv.k230.sysctl-power" +OBJECT_DECLARE_SIMPLE_TYPE(K230SysctlPowerState, K230_SYSCTL_POWER) + +#define TYPE_K230_SYSCTL_RESET "riscv.k230.sysctl-reset" +OBJECT_DECLARE_SIMPLE_TYPE(K230SysctlResetState, K230_SYSCTL_RESET) + +#define K230_SYSCTL_SIZE 0x1000 + +struct K230SysctlBootState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + uint8_t regs[K230_SYSCTL_SIZE]; +}; + +struct K230SysctlPowerState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + uint8_t regs[K230_SYSCTL_SIZE]; +}; + +struct K230SysctlResetState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + uint8_t regs[K230_SYSCTL_SIZE]; + K230SysctlBootState *boot; + CPUState *cpu1; + QEMUTimer *release_timer; + uint8_t *rtt_saved; + hwaddr rtt_addr; + uint32_t rtt_size; + bool rtt_saved_valid; + bool defer_cpu1_release; + uint32_t last_cpu1_rstvec; + uint32_t deferred_rstvec; +}; + +#endif /* HW_MISC_K230_SYSCTL_H */ diff --git a/include/hw/misc/k230_timer.h b/include/hw/misc/k230_timer.h new file mode 100644 index 0000000000000..c5fcbe6bd9f35 --- /dev/null +++ b/include/hw/misc/k230_timer.h @@ -0,0 +1,29 @@ +/* + * K230 low-speed hardware timers + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_K230_TIMER_H +#define HW_MISC_K230_TIMER_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_K230_TIMER "riscv.k230.timer" +OBJECT_DECLARE_SIMPLE_TYPE(K230TimerState, K230_TIMER) + +#define K230_TIMER_SIZE 0x800 +#define K230_TIMER_COUNT 6 + +struct K230TimerState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + uint8_t regs[K230_TIMER_SIZE]; + int64_t start_ns[K230_TIMER_COUNT]; +}; + +#endif /* HW_MISC_K230_TIMER_H */ diff --git a/include/hw/misc/k230_tsensor.h b/include/hw/misc/k230_tsensor.h new file mode 100644 index 0000000000000..4152f79ec3f24 --- /dev/null +++ b/include/hw/misc/k230_tsensor.h @@ -0,0 +1,27 @@ +/* + * K230 temperature sensor + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_K230_TSENSOR_H +#define HW_MISC_K230_TSENSOR_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_K230_TSENSOR "riscv.k230.tsensor" +OBJECT_DECLARE_SIMPLE_TYPE(K230TSensorState, K230_TSENSOR) + +#define K230_TSENSOR_SIZE 0x800 + +struct K230TSensorState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + uint8_t regs[K230_TSENSOR_SIZE]; +}; + +#endif /* HW_MISC_K230_TSENSOR_H */ diff --git a/include/hw/misc/k230_ugzip.h b/include/hw/misc/k230_ugzip.h new file mode 100644 index 0000000000000..ca5dc617eb2f9 --- /dev/null +++ b/include/hw/misc/k230_ugzip.h @@ -0,0 +1,31 @@ +/* + * K230 UGZIP decompressor + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_K230_UGZIP_H +#define HW_MISC_K230_UGZIP_H + +#include "hw/core/sysbus.h" +#include "hw/dma/k230_gsdma.h" +#include "qom/object.h" + +#define TYPE_K230_UGZIP "riscv.k230.ugzip" +OBJECT_DECLARE_SIMPLE_TYPE(K230UgzipState, K230_UGZIP) + +#define K230_UGZIP_SIZE 0x4000 + +struct K230UgzipState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + uint8_t regs[K230_UGZIP_SIZE]; + K230GsdmaState *gsdma; +}; + +#endif /* HW_MISC_K230_UGZIP_H */ diff --git a/include/hw/misc/nxp_s32_mc_me.h b/include/hw/misc/nxp_s32_mc_me.h new file mode 100644 index 0000000000000..f2f93e4c9308f --- /dev/null +++ b/include/hw/misc/nxp_s32_mc_me.h @@ -0,0 +1,30 @@ +/* + * NXP S32 Mode Entry module + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_NXP_S32_MC_ME_H +#define HW_MISC_NXP_S32_MC_ME_H + +#include "hw/core/register.h" +#include "hw/core/sysbus.h" + +#define TYPE_NXP_S32_MC_ME "nxp-s32-mc-me" +OBJECT_DECLARE_SIMPLE_TYPE(NXPS32MCMEState, NXP_S32_MC_ME) + +#define NXP_S32_MC_ME_SIZE 0x4000 +#define NXP_S32_MC_ME_R_MAX (NXP_S32_MC_ME_SIZE / 4) + +struct NXPS32MCMEState { + SysBusDevice parent_obj; + + RegisterInfoArray *reg_array; + RegisterInfo regs_info[NXP_S32_MC_ME_R_MAX]; + uint32_t regs[NXP_S32_MC_ME_R_MAX]; + uint8_t ctl_key_state; +}; + +#endif diff --git a/include/hw/misc/phytium-ddr-ctrl.h b/include/hw/misc/phytium-ddr-ctrl.h new file mode 100644 index 0000000000000..4ff0db4a57fe4 --- /dev/null +++ b/include/hw/misc/phytium-ddr-ctrl.h @@ -0,0 +1,27 @@ +/* + * Phytium DDR controller shim + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_PHYTIUM_DDR_CTRL_H +#define HW_MISC_PHYTIUM_DDR_CTRL_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_PHYTIUM_DDR_CTRL "phytium-ddr-ctrl" +OBJECT_DECLARE_SIMPLE_TYPE(PhytiumDdrCtrlState, PHYTIUM_DDR_CTRL) + +#define PHYTIUM_DDR_CTRL_MMIO_SIZE 0x1000 +#define PHYTIUM_DDR_INDEX_SPACE_SIZE 0x10000 + +struct PhytiumDdrCtrlState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + uint32_t regs[PHYTIUM_DDR_CTRL_MMIO_SIZE / sizeof(uint32_t)]; + uint32_t index_regs[PHYTIUM_DDR_INDEX_SPACE_SIZE / sizeof(uint32_t)]; +}; + +#endif /* HW_MISC_PHYTIUM_DDR_CTRL_H */ diff --git a/include/hw/misc/phytium-scp-mailbox.h b/include/hw/misc/phytium-scp-mailbox.h new file mode 100644 index 0000000000000..52888844893c7 --- /dev/null +++ b/include/hw/misc/phytium-scp-mailbox.h @@ -0,0 +1,25 @@ +/* + * Phytium SCP mailbox model + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_PHYTIUM_SCP_MAILBOX_H +#define HW_MISC_PHYTIUM_SCP_MAILBOX_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_PHYTIUM_SCP_MAILBOX "phytium-scp-mailbox" +OBJECT_DECLARE_SIMPLE_TYPE(PhytiumScpMailboxState, PHYTIUM_SCP_MAILBOX) + +#define PHYTIUM_SCP_MAILBOX_MMIO_SIZE 0x100 + +struct PhytiumScpMailboxState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + uint32_t regs[PHYTIUM_SCP_MAILBOX_MMIO_SIZE / sizeof(uint32_t)]; +}; + +#endif /* HW_MISC_PHYTIUM_SCP_MAILBOX_H */ diff --git a/include/hw/misc/rk3588_cru.h b/include/hw/misc/rk3588_cru.h new file mode 100644 index 0000000000000..5a58cc4bf57b5 --- /dev/null +++ b/include/hw/misc/rk3588_cru.h @@ -0,0 +1,47 @@ +/* + * Minimal RK3588 CRU (Clock-and-Reset-Unit) stub. + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Local-only board model. The CRU is a CLK_OF_DECLARE provider that + * registers BOTH the rockchip clock framework (clk-rk3588) and the + * rockchip reset controller (rst-rk3588). The five in-scope drivers + * (UART, eMMC, GMAC, PCIe, GPIO) only need fire-and-forget + * clk_prepare_enable / reset_control_assert|deassert calls to land + * without aborting; no clock rates or PLL state are modelled. + * + * Special-case PLL lock status: + * + * - 0x600 (RK3588_GRF_SOC_STATUS0, in CRU space despite the "GRF" + * name) returns 0xffffffff so the early PLL-lock-status poll in + * rockchip_clk_register_plls sees every PLL locked and skips its + * wait. Returning 0 here hangs clk-rk3588 init very early (before + * the console is up), so this read is load-bearing. + * - per-PLL status offsets at +0x18 return bit 15 set so SPL PLL + * rate changes do not wait forever. + * + * Other offsets are RAM-backed and support Rockchip HIWORD-mask writes. + */ + +#ifndef HW_MISC_RK3588_CRU_H +#define HW_MISC_RK3588_CRU_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RK3588_CRU "rk3588-cru" +OBJECT_DECLARE_SIMPLE_TYPE(RK3588CRUState, RK3588_CRU) + +#define RK3588_CRU_SIZE 0x5c000 +#define RK3588_CRU_PLL_STATUS 0x600 /* RK3588_GRF_SOC_STATUS0; returns 0xffffffff */ + +struct RK3588CRUState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + uint32_t regs[RK3588_CRU_SIZE / 4]; +}; + +#endif /* HW_MISC_RK3588_CRU_H */ diff --git a/include/hw/misc/rk3588_scmi.h b/include/hw/misc/rk3588_scmi.h new file mode 100644 index 0000000000000..7e0642b586971 --- /dev/null +++ b/include/hw/misc/rk3588_scmi.h @@ -0,0 +1,85 @@ +/* + * Minimal RK3588 SCMI clock responder. + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Local-only board model. Implements the subset of the ARM SCMI + * Clock protocol (0x14) needed by the dw_mmc-rockchip SD-card + * driver's sdmmc@fe2c0000 clock requests, transported via SMC + * (arm,scmi-smc, smc-id 0x82000010) with a single shared-memory + * slot at 0x10f000 (size 0x100). + * + * The SMC trap path in target/arm/tcg/psci.c routes any SMC whose + * function-id is 0x82000010 (registered by the board via + * arm_register_psci_smc_handler) into rk3588_scmi_handle_smc(), + * which parses the shmem message header + payload, writes a canned + * response into the same shmem, sets CHANNEL_FREE, and returns + * x0=0 to the guest. + * + * Implemented messages (status=SUCCESS unless noted): + * BASE (0x10): + * PROTOCOL_VERSION (0x0) -> {minor=0, major=2} + * PROTOCOL_ATTRIBUTES (0x1) -> num_agents=1, num_protocols=2 + * PROTOCOL_MESSAGE_ATTRIBUTES -> attributes=0 + * CLOCK (0x14): + * PROTOCOL_VERSION (0x0) -> {minor=0, major=3} + * PROTOCOL_ATTRIBUTES (0x1) -> num_clocks=24 + * PROTOCOL_MESSAGE_ATTRIBUTES -> attributes=0 + * CLOCK_ATTRIBUTES (0x3) -> {attributes=0; name; latency=0} + * CLOCK_RATE_GET (0x6) -> nominal per-clock rate + * CLOCK_RATE_SET (0x5) -> SUCCESS (tracks last-set rate) + * CLOCK_CONFIG_SET (0x7) -> SUCCESS (tracks enable) + * NEGOTIATE_PROTOCOL_VERSION (0x10) -> SUCCESS + * + * Out-of-range clk_id returns SCMI_ERR_NOT_FOUND (-2); unsupported + * messages return SCMI_ERR_SUPPORT (-1). All values are + * little-endian. No IRQ, no notifications, no fastchannels. + */ + +#ifndef HW_MISC_RK3588_SCMI_H +#define HW_MISC_RK3588_SCMI_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RK3588_SCMI "rk3588-scmi" +OBJECT_DECLARE_SIMPLE_TYPE(RK3588SCMIState, RK3588_SCMI) + +#define RK3588_SCMI_SHMEM_SIZE 0x100 +#define RK3588_SCMI_NUM_CLOCKS 24 + +/* + * SMC function-id of the SCMI transport. Match the DTS `arm,smc-id`. + */ +#define RK3588_SCMI_SMC_ID 0x82000010ULL + +struct RK3588SCMIState { + SysBusDevice parent_obj; + + MemoryRegion shmem; + + /* Backing store for the 0x100 shmem MMIO region. */ + uint8_t shmem_buf[RK3588_SCMI_SHMEM_SIZE]; + + /* Per-clock rate (Hz) and enable state. */ + uint64_t rate[RK3588_SCMI_NUM_CLOCKS]; + bool enabled[RK3588_SCMI_NUM_CLOCKS]; +}; + +/* + * Run the SCMI responder against the shmem buffer. + * + * Reads the request header + payload from the shmem buffer, writes the + * canned response into the same buffer (header echoed, status=SUCCESS, + * CHANNEL_FREE set), and returns true. Returns false if the buffer + * does not contain a well-formed request. + * + * Exposed so the SMC hook (target/arm/tcg/psci.c) can call it via the + * board-registered callback without referencing the device instance + * directly; the board keeps a pointer to the realized device. + */ +bool rk3588_scmi_handle_smc(RK3588SCMIState *s); + +#endif /* HW_MISC_RK3588_SCMI_H */ diff --git a/include/hw/misc/rockchip_syscon.h b/include/hw/misc/rockchip_syscon.h new file mode 100644 index 0000000000000..ba8f666263a2c --- /dev/null +++ b/include/hw/misc/rockchip_syscon.h @@ -0,0 +1,30 @@ +/* + * Rockchip RAM-backed syscon register bank + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_ROCKCHIP_SYSCON_H +#define HW_MISC_ROCKCHIP_SYSCON_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_ROCKCHIP_SYSCON "rockchip-syscon" +OBJECT_DECLARE_SIMPLE_TYPE(RockchipSysconState, ROCKCHIP_SYSCON) + +struct RockchipSysconState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + uint32_t *regs; + uint32_t size; +}; + +void rockchip_syscon_set_u32(RockchipSysconState *s, hwaddr offset, + uint32_t value); +uint32_t rockchip_syscon_get_u32(RockchipSysconState *s, hwaddr offset); + +#endif /* HW_MISC_ROCKCHIP_SYSCON_H */ diff --git a/include/hw/misc/spacemit-k3.h b/include/hw/misc/spacemit-k3.h new file mode 100644 index 0000000000000..5b7ff65d4a487 --- /dev/null +++ b/include/hw/misc/spacemit-k3.h @@ -0,0 +1,37 @@ +/* + * SpacemiT K3 clock and boot controls + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_SPACEMIT_K3_H +#define HW_MISC_SPACEMIT_K3_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_SPACEMIT_K3_APMU "spacemit.k3.apmu" +OBJECT_DECLARE_SIMPLE_TYPE(SpacemitK3APMUState, SPACEMIT_K3_APMU) + +#define TYPE_SPACEMIT_K3_CIU "spacemit.k3.ciu" +OBJECT_DECLARE_SIMPLE_TYPE(SpacemitK3CIUState, SPACEMIT_K3_CIU) + +#define SPACEMIT_K3_APMU_MMIO_SIZE 0x400 +#define SPACEMIT_K3_CIU_MMIO_SIZE 0x400 + +struct SpacemitK3APMUState { + SysBusDevice parent_obj; + + MemoryRegion mmio; + uint32_t sdh0_ctrl; +}; + +struct SpacemitK3CIUState { + SysBusDevice parent_obj; + + MemoryRegion mmio; +}; + +#endif /* HW_MISC_SPACEMIT_K3_H */ diff --git a/include/hw/net/dwmac4.h b/include/hw/net/dwmac4.h new file mode 100644 index 0000000000000..817dbfe83b6bf --- /dev/null +++ b/include/hw/net/dwmac4.h @@ -0,0 +1,91 @@ +/* + * Synopsys DesignWare Ethernet MAC dwmac-4.20a (GMAC4). + * + * Copyright (c) 2026 Chao Liu + * + * RK3588 wires this for its GMAC node (snps,dwmac-4.20a / + * rockchip,rk3588-gmac). + * Register map, descriptor layout, and IRQ semantics come from the + * dwmac4 / dwmac4_dma / dwmac4_descs headers in the Linux stmmac driver. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Local-only model; not for upstream. + */ + +#ifndef HW_NET_DWMAC4_H +#define HW_NET_DWMAC4_H + +#include "hw/core/irq.h" +#include "hw/core/sysbus.h" +#include "net/net.h" + +/* + * MAC_VERSION (GMAC4_VERSION @ 0x110) contains the Synopsys version in + * bits[7:0] and a user version in bits[15:8]. Boards select their reported + * version through properties; the defaults retain the original 0x51 model. + */ +#define DWMAC4_DEFAULT_SNPS_VERSION 0x51u +#define DWMAC4_DEFAULT_DMA_WIDTH 32u +#define DWMAC4_DEFAULT_FIFO_SIZE 4096u + +/* MAC register bank window. Covers 0x000..0x3ff (some slots beyond 0x300). */ +#define DWMAC4_MAC_REG_SIZE 0x400 +#define DWMAC4_MAC_NR_REGS (DWMAC4_MAC_REG_SIZE / sizeof(uint32_t)) + +/* + * DMA + per-channel register window. We model 0x1000..0x11ff which covers + * the global DMA block plus channels 0 and 1 (stride 0x80). Chan 0 is what + * the stmmac driver uses for the primary RX/TX queue on RK3588. + */ +#define DWMAC4_DMA_REG_BASE 0x1000 +#define DWMAC4_DMA_REG_SIZE 0x200 +#define DWMAC4_DMA_NR_REGS (DWMAC4_DMA_REG_SIZE / sizeof(uint32_t)) + +#define DWMAC4_DMA_CHAN_STRIDE 0x80 +#define DWMAC4_NR_CHANNELS 2 + +/* Full MMIO window reserved for the device (matches the 64 KiB DT reg). */ +#define DWMAC4_MMIO_SIZE 0x10000 + +typedef struct DWMAC4State { + SysBusDevice parent; + + MemoryRegion iomem; + qemu_irq sb_irq; /* macirq line (SPI 227 on RK3588) */ + + NICState *nic; + NICConf conf; + + /* MAC + DMA register banks, accessed via registerinfo. */ + uint32_t mac_regs[DWMAC4_MAC_NR_REGS]; + RegisterInfo mac_regs_info[DWMAC4_MAC_NR_REGS]; + RegisterInfoArray *mac_reg_array; + uint32_t dma_regs[DWMAC4_DMA_NR_REGS]; + RegisterInfo dma_regs_info[DWMAC4_DMA_NR_REGS]; + RegisterInfoArray *dma_reg_array; + + /* Per-channel descriptor-ring cursor state (not part of the reg bank). */ + uint64_t tx_desc_cur[DWMAC4_NR_CHANNELS]; + uint64_t rx_desc_cur[DWMAC4_NR_CHANNELS]; + + /* MDIO clause-22 PHY scratch (minimal: link up, full-duplex 1G). */ + uint16_t phy_regs[32]; + uint16_t phy_page; + + /* Board-selectable synthesis and PHY identity. */ + uint8_t snps_version; + uint8_t user_version; + uint8_t dma_width; + uint8_t phy_addr; + uint16_t phy_id1; + uint16_t phy_id2; + uint32_t tx_fifo_size; + uint32_t rx_fifo_size; + bool tso; +} DWMAC4State; + +#define TYPE_DWMAC4 "dwmac4" +OBJECT_DECLARE_SIMPLE_TYPE(DWMAC4State, DWMAC4) + +#endif /* HW_NET_DWMAC4_H */ diff --git a/include/hw/net/phytium-xmac.h b/include/hw/net/phytium-xmac.h new file mode 100644 index 0000000000000..1b45a91651867 --- /dev/null +++ b/include/hw/net/phytium-xmac.h @@ -0,0 +1,15 @@ +/* + * Phytium XMAC Ethernet controller model + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_NET_PHYTIUM_XMAC_H +#define HW_NET_PHYTIUM_XMAC_H + +#include "hw/core/sysbus.h" + +#define TYPE_PHYTIUM_XMAC "phytium-xmac" +OBJECT_DECLARE_SIMPLE_TYPE(PhytiumXmacState, PHYTIUM_XMAC) + +#endif diff --git a/include/hw/pci-host/rockchip_pcie.h b/include/hw/pci-host/rockchip_pcie.h new file mode 100644 index 0000000000000..82fbdfee6f883 --- /dev/null +++ b/include/hw/pci-host/rockchip_pcie.h @@ -0,0 +1,81 @@ +/* + * Rockchip DesignWare PCIe host wrapper. + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Wraps TYPE_DESIGNWARE_PCIE_HOST and adds the Rockchip APB vendor window the + * Linux dw-rockchip driver expects. The board remains responsible for mapping + * the SoC-specific DBI/APB/CFG windows and wiring the IRQs: + * + * - 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 + * 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). + * All other APB offsets are RAZ/WI. + * - CFG @ SoC-specific base - ECAM window into the designware + * outbound CFG0 viewport alias. + * + * IRQs are exposed as five logical outputs: err / legacy / msg / pmc / sys. + * The board wires them to SoC-specific interrupt lines; only legacy (INTx) + * and msg (MSI) are functionally load-bearing for enumeration. + */ + +#ifndef HW_PCI_HOST_ROCKCHIP_PCIE_H +#define HW_PCI_HOST_ROCKCHIP_PCIE_H + +#include "hw/pci-host/designware.h" +#include "qom/object.h" + +#define TYPE_ROCKCHIP_PCIE_HOST "rockchip-pcie-host" +OBJECT_DECLARE_TYPE(RockchipPCIEHost, RockchipPCIEHostClass, + ROCKCHIP_PCIE_HOST) + +/* + * SysBus IRQ indices exported by this device (in declaration order). + * The legacy+msg IRQs come from the parent designware host + * (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_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 + +/* APB vendor register offsets (PCIE_CLIENT_*). */ +#define ROCKCHIP_PCIE_APB_LTSSM_STATUS 0x300 + +struct RockchipPCIEHost { + DesignwarePCIEHost parent_obj; + + /* RK APB vendor register window (overlaps PCIE_CLIENT_* regs). */ + MemoryRegion apb; + + /* + * Five RK-side IRQs wired to the GIC. legacy fans out into the + * four INTA..INTD lines inherited from designware; msg is the MSI + * parent. err/pmc/sys are inert in the model but kept for FDT + * fidelity. + */ + qemu_irq err_irq; + qemu_irq pmc_irq; + qemu_irq sys_irq; +}; + +struct RockchipPCIEHostClass { + PCIHostBridgeClass parent_class; + + DeviceRealize parent_realize; +}; + +#endif /* HW_PCI_HOST_ROCKCHIP_PCIE_H */ diff --git a/include/hw/riscv/cv1800b.h b/include/hw/riscv/cv1800b.h new file mode 100644 index 0000000000000..a214f7a9f65ec --- /dev/null +++ b/include/hw/riscv/cv1800b.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Sophgo CV1800B SoC + * + * Copyright (c) 2026 Kuan-Wei Chiu + */ + +#ifndef HW_RISCV_CV1800B_H +#define HW_RISCV_CV1800B_H + +#include "hw/core/boards.h" +#include "hw/riscv/riscv_hart.h" +#include "hw/misc/cv1800b_clk.h" + +#define TYPE_CV1800B_SOC "cv1800b-soc" +OBJECT_DECLARE_SIMPLE_TYPE(CV1800BSoCState, CV1800B_SOC) + +struct CV1800BSoCState { + DeviceState parent_obj; + + RISCVHartArrayState cpus; + MemoryRegion rom; + DeviceState *plic; + CV1800BClkState clk; +}; + +#define CV1800B_PLIC_NUM_SOURCES 136 +#define CV1800B_PLIC_NUM_PRIORITIES 31 + +#define CV1800B_UART0_IRQ 44 +#define CV1800B_SD0_IRQ 36 + +enum { + CV1800B_DEV_TOP_MISC, + CV1800B_DEV_PINMUX, + CV1800B_DEV_CLK, + CV1800B_DEV_RST, + CV1800B_DEV_WDT, + CV1800B_DEV_GPIO, + CV1800B_DEV_UART0, + CV1800B_DEV_SD0, + CV1800B_DEV_ROM, + CV1800B_DEV_RTC_GPIO, + CV1800B_DEV_RTC_IO, + CV1800B_DEV_PLIC, + CV1800B_DEV_CLINT, + CV1800B_DEV_DRAM, +}; + +extern const MemMapEntry cv1800b_memmap[]; + +#endif diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h index 592e1c26bf8a8..0332c0a49be6e 100644 --- a/include/hw/riscv/k230.h +++ b/include/hw/riscv/k230.h @@ -1,7 +1,7 @@ /* * QEMU RISC-V Virt Board Compatible with kendryte K230 SDK * - * Copyright (c) 2025 Chao Liu + * Copyright (c) 2026 Process Mission * * SPDX-License-Identifier: GPL-2.0-or-later * @@ -16,10 +16,43 @@ #define HW_K230_H #include "hw/core/boards.h" +#include "hw/core/split-irq.h" +#include "hw/display/k230_display.h" +#include "hw/dma/k230_gsdma.h" +#include "hw/dma/k230_pdma.h" +#include "hw/i2c/k230_i2c.h" +#include "hw/misc/k230_adc.h" +#include "hw/misc/k230_dewarp.h" +#include "hw/misc/k230_gpio.h" +#include "hw/misc/k230_hardlock.h" +#include "hw/misc/k230_hi_sys_cfg.h" +#include "hw/misc/k230_iomux.h" +#include "hw/misc/k230_i2s.h" +#include "hw/misc/k230_isp.h" +#include "hw/misc/k230_kpu.h" +#include "hw/misc/k230_nonai_2d.h" +#include "hw/misc/k230_pmu.h" +#include "hw/misc/k230_pwm.h" +#include "hw/misc/k230_regs.h" +#include "hw/misc/k230_rx_csi.h" +#include "hw/misc/k230_security.h" +#include "hw/misc/k230_sysctl.h" +#include "hw/misc/k230_timer.h" +#include "hw/misc/k230_tsensor.h" +#include "hw/misc/k230_ugzip.h" #include "hw/riscv/riscv_hart.h" +#include "hw/rtc/k230_rtc.h" +#include "hw/sd/k230_sdhci.h" +#include "hw/ssi/k230_spi.h" +#include "hw/usb/hcd-dwc2.h" #include "hw/watchdog/k230_wdt.h" #define C908_CPU_HARTID (0) +#define C908V_CPU_HARTID (0) +#define C908V_CPU_INDEX (1) +#define K230_UART_COUNT 5 +#define K230_REGS_COUNT 12 +#define K230_PLIC_NUM_SOURCES 257 #define TYPE_RISCV_K230_SOC "riscv.k230.soc" #define RISCV_K230_SOC(obj) \ @@ -31,15 +64,54 @@ typedef struct K230SoCState { /*< public >*/ RISCVHartArrayState c908_cpu; /* Small core */ + RISCVHartArrayState c908v_cpu; /* Big core */ K230WdtState wdt[2]; + K230SdhciState sdhci[K230_SDHCI_COUNT]; + K230GsdmaState gsdma; + K230PdmaState pdma; + K230UgzipState ugzip; + K230HiSysCfgState hi_sys_cfg; + K230HardlockState hardlock; + K230TSensorState tsensor; + K230GpioState gpio[2]; + K230IomuxState iomux; + K230RegsState uart_ext[K230_UART_COUNT]; + K230I2CState i2c[5]; + K230AdcState adc; + K230PwmState pwm; + K230TimerState timer; + K230SysctlBootState sysctl_boot; + K230SysctlPowerState sysctl_power; + K230SysctlResetState sysctl_reset; + K230PmuState pmu; + K230RtcState rtc; + K230SecurityState security; + K230VoState vo; + K230DsiState dsi; + K230SpiState spi[3]; + K230I2SState i2s; + K230RegsState regs[K230_REGS_COUNT]; + K230KpuState kpu; + K230NonAI2DState nonai_2d; + K230IspState isp; + K230DewarpState dewarp; + K230RxCsiState rx_csi; + DWC2State usb[2]; MemoryRegion sram; + MemoryRegion kpu_l2_cache; MemoryRegion bootrom; + MemoryRegion flash_xip; + MemoryRegion c908v_mem; + MemoryRegion c908v_sysmem; DeviceState *c908_plic; + DeviceState *c908v_plic; + SplitIRQ plic_irq_splitter[K230_PLIC_NUM_SOURCES]; + bool c908v_enabled; } K230SoCState; -#define TYPE_RISCV_K230_MACHINE MACHINE_TYPE_NAME("k230") +#define TYPE_RISCV_K230_MACHINE MACHINE_TYPE_NAME("k230-canmv") #define RISCV_K230_MACHINE(obj) \ OBJECT_CHECK(K230MachineState, (obj), TYPE_RISCV_K230_MACHINE) @@ -50,6 +122,7 @@ typedef struct K230MachineState { /*< public >*/ K230SoCState soc; Notifier machine_done; + bool boot_both_cores; } K230MachineState; enum { @@ -127,17 +200,67 @@ enum { K230_UART2_IRQ = 18, K230_UART3_IRQ = 19, K230_UART4_IRQ = 20, + K230_I2C0_IRQ = 21, + K230_GPIO0_IRQ = 32, K230_WDT0_IRQ = 107, K230_WDT1_IRQ = 108, + K230_IPCM_IRQ_BASE = 109, + K230_IPCM_IRQ_COUNT = 4, + K230_ISP_MI_IRQ = 127, + K230_ISP_FE_IRQ = 128, + K230_ISP_IRQ = 129, + K230_DWE_IRQ = 130, + K230_FE_IRQ = 131, + K230_VO_IRQ = 133, + K230_DMA_IRQ = 140, + K230_PDMA_IRQ = 203, + K230_NON_AI_2D_IRQ = 141, + K230_SD0_IRQ = 142, + K230_SD1_IRQ = 144, + K230_SPI_IRQ = 146, + K230_QSPI0_IRQ = 155, + K230_QSPI1_IRQ = 164, + K230_USB0_IRQ = 173, + K230_USB1_IRQ = 174, + K230_PMU_IRQ = 175, + K230_DPU_IRQ = 186, + K230_GNNE_IRQ = 189, + K230_FFT_IRQ = 190, + K230_AI2D_IRQ = 191, + K230_VSE_IRQ = 204, }; -#define K230_UART_COUNT 5 +#define K230_I2C_COUNT 5 +#define K230_SPI_COUNT 3 + +enum { + K230_SPI_QSPI0, + K230_SPI_QSPI1, + K230_SPI_SPI0, +}; + +enum { + K230_REGS_CMU, + K230_REGS_KPU_CFG, + K230_REGS_HDI, + K230_REGS_STC, + K230_REGS_NOC_QOS, + K230_REGS_CODEC, + K230_REGS_I2S, + K230_REGS_DDRC_CFG, + K230_REGS_FFT, + K230_REGS_AI_2D_ENGINE, + K230_REGS_NON_AI_2D, + K230_REGS_DPU, +}; /* - * Integrates with the interrupt controller (PLIC), - * which can process 208 interrupt external sources + * The TRM lists fewer implemented peripheral interrupt lines, but the + * RT-Smart maix3/c908 BSP defines IRQ_MAX_NR as 256 and initializes + * PLIC source IDs 1..256. Keep the PLIC source space wide enough for + * those SDK accesses while individual devices still only drive the lines + * they implement. */ -#define K230_PLIC_NUM_SOURCES 208 #define K230_PLIC_NUM_PRIORITIES 7 #define K230_PLIC_PRIORITY_BASE 0x00 #define K230_PLIC_PENDING_BASE 0x1000 diff --git a/include/hw/riscv/riscv_hart.h b/include/hw/riscv/riscv_hart.h index 197fa1623115a..a4abac7d23b51 100644 --- a/include/hw/riscv/riscv_hart.h +++ b/include/hw/riscv/riscv_hart.h @@ -42,6 +42,8 @@ struct RISCVHartArrayState { uint64_t *rnmi_irqvec; uint32_t num_rnmi_excpvec; uint64_t *rnmi_excpvec; + MemoryRegion *memory; + bool start_powered_off; RISCVCPU *harts; }; diff --git a/include/hw/riscv/spacemit-k3.h b/include/hw/riscv/spacemit-k3.h new file mode 100644 index 0000000000000..5bc4ec3742ed7 --- /dev/null +++ b/include/hw/riscv/spacemit-k3.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * SpacemiT K3 SoC and Pico-ITX machine + */ + +#ifndef HW_SPACEMIT_K3_H +#define HW_SPACEMIT_K3_H + +#include "exec/hwaddr.h" +#include "hw/char/serial-mm.h" +#include "hw/core/boards.h" +#include "hw/cpu/cluster.h" +#include "hw/misc/spacemit-k3.h" +#include "hw/riscv/riscv_hart.h" +#include "hw/sd/spacemit-k3-sdhci.h" + +#define K3_PICO_ITX_NUM_CLUSTERS 2 +#define K3_PICO_ITX_HARTS_PER_CLUSTER 4 +#define K3_PICO_ITX_NUM_HARTS 8 + +#define K3_PICO_ITX_TIMEBASE_FREQ 24000000 +#define K3_PICO_ITX_APLIC_NUM_SOURCES 512 +#define K3_PICO_ITX_APLIC_IPRIO_BITS 8 +#define K3_PICO_ITX_IMSIC_NUM_IDS 511 +#define K3_PICO_ITX_UART0_IRQ 42 +#define K3_PICO_ITX_SDHCI0_IRQ 99 + +enum { + K3_DEV_SRAM, + K3_DEV_DDR_TRAINING, + K3_DEV_UART0, + K3_DEV_SDHCI0, + K3_DEV_APMU, + K3_DEV_CIU, + K3_DEV_S_IMSIC, + K3_DEV_S_APLIC, + K3_DEV_M_IMSIC, + K3_DEV_M_APLIC, + K3_DEV_M_CLINT, + K3_DEV_FIRMWARE, + K3_DEV_DRAM, +}; + +extern const MemMapEntry spacemit_k3_memmap[]; + +#define TYPE_SPACEMIT_K3_SOC "spacemit.k3.soc" +OBJECT_DECLARE_SIMPLE_TYPE(SpacemitK3SoCState, SPACEMIT_K3_SOC) + +struct SpacemitK3SoCState { + DeviceState parent_obj; + + CPUClusterState clusters[K3_PICO_ITX_NUM_CLUSTERS]; + RISCVHartArrayState cpus[K3_PICO_ITX_NUM_CLUSTERS]; + MemoryRegion sram; + MemoryRegion ddr_training; + MemoryRegion firmware; + MemoryRegion uart0_mem; + SpacemitK3APMUState apmu; + SpacemitK3CIUState ciu; + SpacemitK3SDHCIState sdhci0; + DeviceState *m_imsic[K3_PICO_ITX_NUM_HARTS]; + DeviceState *s_imsic[K3_PICO_ITX_NUM_HARTS]; + DeviceState *m_aplic; + DeviceState *s_aplic; + DeviceState *swi; + DeviceState *mtimer; + SerialMM *uart0; +}; + +#define TYPE_K3_PICO_ITX_MACHINE MACHINE_TYPE_NAME("k3-pico-itx") +OBJECT_DECLARE_SIMPLE_TYPE(K3PicoITXState, K3_PICO_ITX_MACHINE) + +struct K3PicoITXState { + MachineState parent_obj; + + SpacemitK3SoCState soc; + int fdt_size; +}; + +#endif diff --git a/include/hw/rtc/k230_rtc.h b/include/hw/rtc/k230_rtc.h new file mode 100644 index 0000000000000..6c1ac8cd4db5d --- /dev/null +++ b/include/hw/rtc/k230_rtc.h @@ -0,0 +1,37 @@ +/* + * K230 Real-Time Clock + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_RTC_K230_RTC_H +#define HW_RTC_K230_RTC_H + +#include "hw/core/sysbus.h" +#include "qemu/timer.h" +#include "qom/object.h" + +#define TYPE_K230_RTC "riscv.k230.rtc" +OBJECT_DECLARE_SIMPLE_TYPE(K230RtcState, K230_RTC) + +struct K230RtcState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + QEMUTimer *timer; + qemu_irq irq; + + int64_t tick_offset; + int64_t count_base_ns; + uint32_t alarm_date; + uint32_t alarm_time; + uint32_t count; + uint32_t int_ctrl; + bool irq_pending; +}; + +#endif /* HW_RTC_K230_RTC_H */ diff --git a/include/hw/sd/ax650x-sdhci.h b/include/hw/sd/ax650x-sdhci.h new file mode 100644 index 0000000000000..be4b5083186b5 --- /dev/null +++ b/include/hw/sd/ax650x-sdhci.h @@ -0,0 +1,75 @@ +/* + * Axera AX650X SDHCI controller + * + * Copyright (c) 2026 Zevorn + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_SD_AX650X_SDHCI_H +#define HW_SD_AX650X_SDHCI_H + +#include "hw/core/register.h" +#include "hw/sd/sdhci.h" + +#define TYPE_AX650X_SDHCI "ax650x-sdhci" +OBJECT_DECLARE_SIMPLE_TYPE(AX650XSDHCIState, AX650X_SDHCI) + +#define AX650X_SDHCI_REG_SIZE 0x600 +#define AX650X_SDHCI_VENDOR_BASE 0x100 +#define AX650X_SDHCI_VENDOR_SIZE 0x500 +#define AX650X_SDHCI_VENDOR_PTR 0x0e8 +#define AX650X_SDHCI_VENDOR_PTR_VALUE 0x500 + +#define AX650X_SDHCI_PHY_CNFG 0x300 +#define AX650X_SDHCI_PHY_CMDPAD_CNFG 0x304 +#define AX650X_SDHCI_PHY_DATAPAD_CNFG 0x306 +#define AX650X_SDHCI_PHY_CLKPAD_CNFG 0x308 +#define AX650X_SDHCI_PHY_STBPAD_CNFG 0x30a +#define AX650X_SDHCI_PHY_RSTNPAD_CNFG 0x30c +#define AX650X_SDHCI_PHY_COMMDL_CNFG 0x31c +#define AX650X_SDHCI_PHY_SDCLKDL_CNFG 0x31d +#define AX650X_SDHCI_PHY_SDCLKDL_DC 0x31e +#define AX650X_SDHCI_PHY_SMPLDL_CNFG 0x320 +#define AX650X_SDHCI_PHY_ATDL_CNFG 0x321 +#define AX650X_SDHCI_PHY_DLL_CTRL 0x324 +#define AX650X_SDHCI_PHY_DLL_CNFG1 0x325 +#define AX650X_SDHCI_PHY_DLL_CNFG2 0x326 +#define AX650X_SDHCI_PHY_DLLDL_CNFG 0x328 +#define AX650X_SDHCI_PHY_DLL_OFFSET 0x329 +#define AX650X_SDHCI_PHY_DLLLBT_CNFG 0x32c +#define AX650X_SDHCI_PHY_DLL_STATUS 0x32e +#define AX650X_SDHCI_PHY_DLLDBG_MLKDC 0x330 +#define AX650X_SDHCI_PHY_DLLDBG_SLKDC 0x332 +#define AX650X_SDHCI_EMMC_CTRL 0x52c + +#define AX650X_SDHCI_PHY_PWRGOOD BIT(1) +#define AX650X_SDHCI_PHY_RSTN BIT(0) +#define AX650X_SDHCI_DLL_EN BIT(0) +#define AX650X_SDHCI_DLL_ERROR BIT(1) +#define AX650X_SDHCI_DLL_LOCKED BIT(0) +#define AX650X_SDHCI_CARD_IS_EMMC BIT(0) +#define AX650X_SDHCI_EMMC_RST_N BIT(2) +#define AX650X_SDHCI_EMMC_RST_N_OE BIT(3) +#define AX650X_SDHCI_ENH_STROBE_EN BIT(8) + +struct AX650XSDHCIState { + SysBusDevice parent_obj; + + MemoryRegion container; + SDHCIState sdhci; + + RegisterInfo pointer_regs_info[2]; + uint8_t pointer_regs[2]; + RegisterInfoArray *pointer_reg_array; + + RegisterInfo vendor_regs_info[AX650X_SDHCI_VENDOR_SIZE]; + uint8_t vendor_regs[AX650X_SDHCI_VENDOR_SIZE]; + RegisterInfoArray *vendor_reg_array; + + bool emmc_reset_asserted; +}; + +SDBus *ax650x_sdhci_get_bus(AX650XSDHCIState *s); + +#endif diff --git a/include/hw/sd/dw_mmc.h b/include/hw/sd/dw_mmc.h new file mode 100644 index 0000000000000..d26dfa4f28b4d --- /dev/null +++ b/include/hw/sd/dw_mmc.h @@ -0,0 +1,104 @@ +/* + * DesignWare Mobile Storage Host Controller (dw_mmc) - Synopsys IP. + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * SysBusDevice model for the standard dw_mmc register bank, an IDMAC + * descriptor-DMA engine, a PIO FIFO data port, a card-detect slot, and a + * single TYPE_SD_CARD attached via an SDBus. RK3588 currently wires this + * model as its SD-card controller at 0xfe2c0000. + * + * The model mirrors the register-extraction contract: + * - hand-written MMIO for the core regs, IDMAC block, and FIFO data ports + * because command, FIFO, W1C, and DMA side effects dominate this register + * bank. + * - FIFO data ports at +0x100 (>=2.40a) and +0x200 (legacy U-Boot + * register layout) drive an internal byte ring. + * - RK vendor regs (0x130/0x134/0x138 TIMING_CON/MISC_CON) RAZ/WI + * on RK3588 (internal_phase=false). + * - The CMD register START bit self-clears on command completion; + * the response is read out of the attached SD card via + * sdbus_do_command and copied into RESP0-3 (R2 long-resp maps + * RESP0<-resp[3], RESP1<-resp[2], etc., mirroring dw_mmc.c:1782-1787). + * - The whole 0x4000 window is covered so AArch64 guest writes to + * any address in [0xfe2c0000, 0xfe2c4000) do not abort (D-15). + */ + +#ifndef HW_SD_DW_MMC_H +#define HW_SD_DW_MMC_H + +#include "hw/core/sysbus.h" +#include "hw/sd/sd.h" +#include "qom/object.h" + +#define TYPE_DW_MMC "dw-mmc" +OBJECT_DECLARE_SIMPLE_TYPE(DwMmcState, DW_MMC) + +#define TYPE_DW_MMC_BUS "dw-mmc-bus" + +/* MMIO window size. */ +#define DW_MMC_MMIO_SIZE 0x4000 + +/* FIFO depth in 32-bit words (DTS fifo-depth = 0x100). */ +#define DW_MMC_FIFO_DEPTH 0x100 + +/* IDMAC descriptor walker ring bound (sanity limit). */ +#define DW_MMC_IDMAC_MAX_DESCS 4096 + +struct DwMmcState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + MemoryRegion vendor; /* RAZ/WI RK vendor/tail regs after 0x200 */ + + SDBus sdbus; + qemu_irq irq; + + /* Outbound card-present/readonly GPIOs (not wired by board). */ + qemu_irq card_inserted; + qemu_irq card_readonly; + + /* dw_mmc register file (only the fields the model uses). */ + uint32_t ctrl; /* 0x000 */ + uint32_t pwren; /* 0x004 */ + uint32_t clkdiv; /* 0x008 */ + uint32_t clksrc; /* 0x00c */ + uint32_t clkena; /* 0x010 */ + uint32_t tmout; /* 0x014 */ + uint32_t ctype; /* 0x018 */ + uint32_t blksiz; /* 0x01c */ + uint32_t bytcnt; /* 0x020 */ + uint32_t intmask; /* 0x024 */ + uint32_t cmdarg; /* 0x028 */ + uint32_t cmd; /* 0x02c */ + uint32_t resp[4]; /* 0x030-0x03c */ + uint32_t rintsts; /* 0x044 (W1C) */ + uint32_t fifoth; /* 0x04c */ + uint32_t debnce; /* 0x064 */ + uint32_t usrid; /* 0x068 */ + uint32_t verid; /* 0x06c (RO) */ + uint32_t hcon; /* 0x070 (RO) */ + uint32_t uhs_reg; /* 0x074 */ + uint32_t rst_n; /* 0x078 */ + uint32_t bmod; /* 0x080 (IDMAC bus mode) */ + uint32_t dbaddr; /* 0x088 (IDMAC desc base) */ + uint32_t idsts; /* 0x08c (W1C) */ + uint32_t idinten; /* 0x090 */ + uint32_t cdthrctl; /* 0x100 */ + uint32_t enable_shift; /* 0x110 */ + + /* FIFO byte ring (used for PIO and IDMAC staging). */ + uint8_t fifo[DW_MMC_FIFO_DEPTH * 4]; + uint32_t fifo_len; /* bytes currently in FIFO */ + uint32_t fifo_pos; /* read cursor */ + + /* Active transfer tracking. */ + uint32_t transfer_bytes_remaining; /* set on CMD with DAT_EXP */ + bool transfer_is_write; /* host->card */ + bool transfer_send_stop; + bool transfer_active; +}; + +#endif /* HW_SD_DW_MMC_H */ diff --git a/include/hw/sd/k230_sdhci.h b/include/hw/sd/k230_sdhci.h new file mode 100644 index 0000000000000..f010b39119991 --- /dev/null +++ b/include/hw/sd/k230_sdhci.h @@ -0,0 +1,34 @@ +/* + * K230 DWC MSHC SDHCI controller + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_SD_K230_SDHCI_H +#define HW_SD_K230_SDHCI_H + +#include "hw/core/sysbus.h" +#include "hw/sd/sdhci.h" +#include "qom/object.h" + +#define TYPE_K230_SDHCI "k230-sdhci" +OBJECT_DECLARE_SIMPLE_TYPE(K230SdhciState, K230_SDHCI) + +#define K230_SDHCI_COUNT 2 +#define K230_SDHCI_VENDOR_SIZE 0xf00 + +struct K230SdhciState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + SDHCIState sdhci; + MemoryRegion container; + MemoryRegion vendor_iomem; + uint8_t vendor_regs[K230_SDHCI_VENDOR_SIZE]; + BusState *sd_bus; +}; + +#endif /* HW_SD_K230_SDHCI_H */ diff --git a/include/hw/sd/phytium-mci.h b/include/hw/sd/phytium-mci.h new file mode 100644 index 0000000000000..80f49b260bb45 --- /dev/null +++ b/include/hw/sd/phytium-mci.h @@ -0,0 +1,47 @@ +/* + * Local-only Phytium MCI controller model + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_SD_PHYTIUM_MCI_H +#define HW_SD_PHYTIUM_MCI_H + +#include "hw/core/register.h" +#include "hw/core/sysbus.h" +#include "hw/sd/sd.h" +#include "qom/object.h" + +#define TYPE_PHYTIUM_MCI "phytium-mci" +OBJECT_DECLARE_SIMPLE_TYPE(PhytiumMciState, PHYTIUM_MCI) + +#define TYPE_PHYTIUM_MCI_BUS "phytium-mci-bus" + +#define PHYTIUM_MCI_MMIO_SIZE 0x1000 +#define PHYTIUM_MCI_REG_WORDS (0xfd4 / 4) +#define PHYTIUM_MCI_FIFO_DEPTH 0x100 +#define PHYTIUM_MCI_ADMA_MAX_DESCS 4096 + +struct PhytiumMciState { + SysBusDevice parent_obj; + + RegisterInfoArray *reg_array; + qemu_irq irq; + SDBus sdbus; + + uint32_t regs[PHYTIUM_MCI_REG_WORDS]; + RegisterInfo regs_info[PHYTIUM_MCI_REG_WORDS]; + + uint8_t fifo[PHYTIUM_MCI_FIFO_DEPTH * 4]; + uint32_t fifo_len; + uint32_t fifo_pos; + + uint32_t transfer_bytes_remaining; + bool transfer_is_write; + bool transfer_send_stop; + bool transfer_synthetic_scr; + bool transfer_active; + uint8_t last_cmd; +}; + +#endif /* HW_SD_PHYTIUM_MCI_H */ diff --git a/include/hw/sd/rockchip_dwcmshc.h b/include/hw/sd/rockchip_dwcmshc.h new file mode 100644 index 0000000000000..96ebcd861179e --- /dev/null +++ b/include/hw/sd/rockchip_dwcmshc.h @@ -0,0 +1,29 @@ +/* + * Rockchip DWCMSHC vendor register window + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_SD_ROCKCHIP_DWCMSHC_H +#define HW_SD_ROCKCHIP_DWCMSHC_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_ROCKCHIP_DWCMSHC_VENDOR "rockchip-dwcmshc-vendor" +OBJECT_DECLARE_SIMPLE_TYPE(RockchipDWCMSHCVendorState, + ROCKCHIP_DWCMSHC_VENDOR) + +#define ROCKCHIP_DWCMSHC_VENDOR_BASE 0x100 +#define ROCKCHIP_DWCMSHC_VENDOR_SIZE 0xff00 + +struct RockchipDWCMSHCVendorState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + uint8_t regs[ROCKCHIP_DWCMSHC_VENDOR_SIZE]; +}; + +#endif /* HW_SD_ROCKCHIP_DWCMSHC_H */ diff --git a/include/hw/sd/sdhci.h b/include/hw/sd/sdhci.h index a9da6203fcbef..c2afaf01cff3b 100644 --- a/include/hw/sd/sdhci.h +++ b/include/hw/sd/sdhci.h @@ -98,6 +98,8 @@ struct SDHCIState { uint32_t quirks; uint8_t sd_spec_version; uint8_t uhs_mode; + uint16_t vendor_area1; + uint16_t vendor_area2; /* * Write Protect pin default active low for detecting SD card * to be protected. Set wp_inverted to invert the signal. diff --git a/include/hw/sd/spacemit-k3-sdhci.h b/include/hw/sd/spacemit-k3-sdhci.h new file mode 100644 index 0000000000000..d54c8e6d84bbf --- /dev/null +++ b/include/hw/sd/spacemit-k3-sdhci.h @@ -0,0 +1,33 @@ +/* + * SpacemiT K3 SDHCI controller + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_SD_SPACEMIT_K3_SDHCI_H +#define HW_SD_SPACEMIT_K3_SDHCI_H + +#include "hw/core/sysbus.h" +#include "hw/sd/sdhci.h" +#include "qom/object.h" + +#define TYPE_SPACEMIT_K3_SDHCI "spacemit.k3.sdhci" +OBJECT_DECLARE_SIMPLE_TYPE(SpacemitK3SDHCIState, SPACEMIT_K3_SDHCI) + +#define SPACEMIT_K3_SDHCI_MMIO_SIZE 0x200 +#define SPACEMIT_K3_SDHCI_CAPAREG UINT64_C(0x112834b4) + +struct SpacemitK3SDHCIState { + SysBusDevice parent_obj; + + SDHCIState sdhci; + MemoryRegion container; + MemoryRegion vendor_iomem; + uint32_t mmc_ctrl; + uint32_t tx_cfg; + BusState *sd_bus; +}; + +#endif /* HW_SD_SPACEMIT_K3_SDHCI_H */ diff --git a/include/hw/ssi/k230_spi.h b/include/hw/ssi/k230_spi.h new file mode 100644 index 0000000000000..21f6149a7a58e --- /dev/null +++ b/include/hw/ssi/k230_spi.h @@ -0,0 +1,45 @@ +/* + * K230 DesignWare SSI controller + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_SSI_K230_SPI_H +#define HW_SSI_K230_SPI_H + +#include "hw/core/sysbus.h" +#include "hw/ssi/ssi.h" +#include "system/block-backend.h" +#include "qom/object.h" + +#define TYPE_K230_SPI "riscv.k230.spi" +OBJECT_DECLARE_SIMPLE_TYPE(K230SpiState, K230_SPI) + +#define K230_SPI_SIZE 0x1000 +#define K230_SPI_REG_COUNT (K230_SPI_SIZE / 4) +#define K230_SPI_FIFO_DEPTH 32 +#define K230_SPI_RX_BUFFER_SIZE 65536 +#define K230_SPI_IRQ_COUNT 9 + +struct K230SpiState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + qemu_irq irq[K230_SPI_IRQ_COUNT]; + qemu_irq flash_cs; + SSIBus *ssi; + DeviceState *flash; + BlockBackend *blk; + + uint32_t regs[K230_SPI_REG_COUNT]; + uint8_t tx_fifo[K230_SPI_FIFO_DEPTH]; + uint8_t rx_buf[K230_SPI_RX_BUFFER_SIZE]; + uint32_t tx_len; + uint32_t rx_len; + uint32_t rx_pos; + bool idma_active; +}; + +#endif /* HW_SSI_K230_SPI_H */ diff --git a/include/hw/timer/rockchip_stimer.h b/include/hw/timer/rockchip_stimer.h new file mode 100644 index 0000000000000..bf413c6f52dc2 --- /dev/null +++ b/include/hw/timer/rockchip_stimer.h @@ -0,0 +1,27 @@ +/* + * Rockchip secure timer stub + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_TIMER_ROCKCHIP_STIMER_H +#define HW_TIMER_ROCKCHIP_STIMER_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_ROCKCHIP_STIMER "rockchip-stimer" +OBJECT_DECLARE_SIMPLE_TYPE(RockchipSTimerState, ROCKCHIP_STIMER) + +#define ROCKCHIP_STIMER_SIZE 0x1000 + +struct RockchipSTimerState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + uint32_t regs[ROCKCHIP_STIMER_SIZE / 4]; +}; + +#endif /* HW_TIMER_ROCKCHIP_STIMER_H */ diff --git a/roms/zvm-rk3588s-roc-pc/Dockerfile b/roms/zvm-rk3588s-roc-pc/Dockerfile new file mode 100644 index 0000000000000..0965bac10c544 --- /dev/null +++ b/roms/zvm-rk3588s-roc-pc/Dockerfile @@ -0,0 +1,24 @@ +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + bc \ + bison \ + build-essential \ + ca-certificates \ + device-tree-compiler \ + flex \ + gcc-aarch64-linux-gnu \ + git \ + libgnutls28-dev \ + libssl-dev \ + make \ + patch \ + python3 \ + python3-dev \ + python3-pyelftools \ + python3-setuptools \ + swig && \ + rm -rf /var/lib/apt/lists/* diff --git a/roms/zvm-rk3588s-roc-pc/README.md b/roms/zvm-rk3588s-roc-pc/README.md new file mode 100644 index 0000000000000..cfeee0d1a98a1 --- /dev/null +++ b/roms/zvm-rk3588s-roc-pc/README.md @@ -0,0 +1,223 @@ +# ZVM RK3588S ROC PC Test Image + +This directory contains a minimal ZVM RK3588S ROC PC release test fixture for +the QEMU functional test suite. + +The checked-in image is: + +```text +rk3588s-zvm-no-fs.raw.zst +``` + +It is a zstd-compressed raw Rockchip TF-card image. The uncompressed image keeps +the release layout expected by the RK3588S ROC PC boot chain: + +- raw virtual size: 32 GiB +- previous full local ZVM image: 32 GiB raw virtual size, about 5.0 GiB + allocated on disk +- this no-filesystem fixture before compression: 32 GiB raw virtual size, + about 1.2 GiB allocated on disk +- checked-in compressed fixture: about 43 MiB +- sparse extracted fixture on APFS: about 199 MiB allocated on disk +- Rockchip RKNS/U-Boot firmware at LBA 64 +- partition 1 at sector `0x8000`, FAT32, containing: + - `zvm_release_rk3588v2.bin` + - `boot.scr` + - `boot.scr.uimg` +- partition 2 at sector `0x1005000`, FAT32 +- `nrtos_images.bin` at partition 2 offset 1024 MiB + +The fixture intentionally omits the Android/OpenHarmony/Linux filesystem slots. +It is enough to boot U-Boot, load ZVM, initialize the ZVM host, create Zephyr +VM0, and report `Start VM Successful!`. + +The longer `rk3588_zvm_linux_guest` functional test does not store the Linux +kernel or root filesystem images in this directory. It downloads the official +ZVM release archives through the QEMU functional-test `Asset` cache, unpacks +them in the test scratch directory, assembles the complete 32 GiB TF-card image +at runtime, and waits for `[CREATE] linux VM 1 created and running`. + +## Recreate The Fixture + +The fixture was produced from the ZVM RK3588v2.0 release package documented at: + +```text +https://esnl.hnu.edu.cn/zvm/document/deploy_rk3588.html +``` + +Input artifact hashes used for this copy: + +```text +05aa22b772808abef7082d3dfc513ea75b0813f3e0d4f028ad595e2c86fe3ec7 kernel_images.zip +9a1840d8d486b2de8e8bedc1647cd3c5aa2f622111b45bb9f0d8e1d26b07c5f0 file_system_images_part1.zip +17b048b0a473dcbd4e5b7f1c5d4f50547ffefe3e040c0b8835e55cba7f45b823 file_system_images_part2.zip +5402d2ea0e5a9a65e577799283ff46fd596914351769c3015d30d6285c787171 kernel_images/boot.scr +7f15679c69159927cb3f27a693e8f69972310a8a06fc6137b7402dde3a710408 kernel_images/nrtos_images.bin +e8851ef0a8f413d98554218248411162024d5e9ba47f90f4aa0908710d193380 kernel_images/zvm_release_rk3588v2.bin +8c1bc5c3568fcbc74c04f8862dea1687baf93e6dcec0c0b18aef494bf2d00c10 image/android_vda.img +7e50e7ac8cd5b90d087f09b0c4ebfacb13b9b4aeb7651e191d376b595bf6d9e2 image/oh_vda.bin +2a59a5503a3e43184adccdb6288a9c9b26599882eacbe387b5db3d408f85891f image/linux_diskimg.bin +6cd4b3d1931a252d564c88220e071eba57009fff49620b9520f33e8c58f80fb5 u-boot-rockchip.bin +``` + +## Recreate U-Boot + +The TF-card image uses `u-boot-rockchip.bin` at LBA 64. That file is the +Rockchip-packaged U-Boot image produced by binman. The plain `u-boot.bin` +artifact is U-Boot proper and is generated by the same build, but it is not the +file written directly to the TF-card image. + +This artifact was not built from a monolithic Rockchip Linux SDK checkout. The +firmware inputs are: + +- U-Boot source: `https://github.com/u-boot/u-boot.git` +- U-Boot commit: `5ca1a73c7d3064582498a8aa96c29e714402a6d3` +- Rockchip binary firmware repo: `https://github.com/rockchip-linux/rkbin` +- rkbin commit: `ecb4fcbe954edf38b3ae037d5de6d9f5bccf81f4` +- BL31: `bin/rk35/rk3588_bl31_v1.54.elf` +- TPL/DDR blob: `bin/rk35/rk3588_ddr_lp4_2112MHz_lp5_2400MHz_v1.21.bin` +- U-Boot base config: `generic-rk3588_defconfig` +- U-Boot config overrides: + - `CONFIG_OF_UPSTREAM=y` + - `CONFIG_DEFAULT_DEVICE_TREE="rockchip/rk3588s-roc-pc"` + - `CONFIG_DEFAULT_FDT_FILE="rockchip/rk3588-generic.dtb"` + - `CONFIG_OF_LIST="rockchip/rk3588s-roc-pc"` + - `CONFIG_SPL_OF_LIST="rockchip/rk3588s-roc-pc"` + - `CONFIG_CMD_CACHE=y` +- local DTS compatibility patch: + `u-boot-rk3588s-roc-pc-zvmcompat.diff` +- deterministic U-Boot compile epoch: `1781525104` + (`2026-06-15 12:05:04 +0000`) +- deterministic FIT/binman packaging epoch: `1781525116` + (`2026-06-15 12:05:16 +0000`) + +Known firmware input and output hashes: + +```text +4d111f0c793035dba84b65cfa741665814ae134af8243c3bb8a87d55a0937bc3 rk3588_bl31_v1.54.elf +ae96f7ddbc6ffa891ab24708e71930cb2968cfa7a2a0972fdc0cacd6891e4f95 rk3588_ddr_lp4_2112MHz_lp5_2400MHz_v1.21.bin +6cd4b3d1931a252d564c88220e071eba57009fff49620b9520f33e8c58f80fb5 u-boot-rockchip.bin +6f0a398e794782f9559f33561993550f262aa4deafb836b74f10a85faf0ea69b u-boot.bin +aaaa0ad289f3718fa6186c9d4f3851726c68df19eb67f3ea619082e5586097de idbloader.img +47cf6d65d29eb727822410faf24bd636f73bf647d9574b6e6071218c29659f2d u-boot.itb +``` + +Build the firmware in an Ubuntu 24.04 container: + +```sh +roms/zvm-rk3588s-roc-pc/build-u-boot-rockchip.sh \ + --output-dir /tmp/zvm-rk3588s-roc-pc-u-boot +``` + +The script first builds `roms/zvm-rk3588s-roc-pc/Dockerfile`, then runs the +firmware build inside that local image. It clones both repositories, checks out +the exact commits above, applies the local DTS patch, configures U-Boot, +temporarily hides `.git` during the build so `CONFIG_LOCALVERSION_AUTO` does not +append a dirty SCM suffix, builds with `aarch64-linux-gnu-gcc`, regenerates the +FIT/binman outputs with the packaging epoch, and verifies the +`u-boot-rockchip.bin` SHA256. + +Build the raw image and compressed fixture: + +```sh +roms/zvm-rk3588s-roc-pc/build-u-boot-rockchip.sh \ + --output-dir /tmp/zvm-rk3588s-roc-pc-u-boot + +roms/zvm-rk3588s-roc-pc/build-image.sh \ + --kernel-dir /path/to/kernel_images \ + --uboot-rockchip /tmp/zvm-rk3588s-roc-pc-u-boot/u-boot-rockchip.bin \ + --output /tmp/rk3588s-zvm-no-fs.raw.zst +``` + +The script uses `scripts/zvm/rk3588s-roc-pc-make-zvm-image.sh` with +`--no-filesystems`, then compresses the raw image with: + +```sh +zstd -T0 -15 -f -o .zst +``` + +Use `manifest.sha256` to verify this checked-in fixture and its decompressed raw +image. + +## Linux Guest Test Assets + +The full Linux guest test fetches these official release archives: + +```text +https://esnl.hnu.edu.cn/zvm/resource/kernel_images.zip +https://esnl.hnu.edu.cn/zvm/resource/file_system_images_part1.zip +https://esnl.hnu.edu.cn/zvm/resource/file_system_images_part2.zip +``` + +The test validates the official kernel package contents before assembling the +card image: + +```text +3f002128aef0fd5d9e5792e2643697b027dda1767efa43dd718439b13d4a2a63 kernel_images/boot.scr +c183dd5e0676a714c7a03781d0788961d797856a529e857e8eeed6ef18bda557 kernel_images/boot.txt +0f40c4e0d07aad5b5bef2b7528dbf3c395f95d0129a0f0a91d81ca9bbfb79a26 kernel_images/nrtos_images.bin +0eca25c10fb39e1200ccd097f6de2cfc767dcd0abd0360645fe816d17869f2e9 kernel_images/raw_kernel_images/Image +e8851ef0a8f413d98554218248411162024d5e9ba47f90f4aa0908710d193380 kernel_images/zvm_release_rk3588v2.bin +``` + +The Linux kernel in this package reports `Linux version 5.10.110`, kernel +commit `ea00faaf555f`, and SDK version +`rk3588_linux_release_20250114_v1.4.0g`. It matches the 5.10.110 `zshm.ko` +module in `linux_diskimg.bin`. + +Before assembling the card, the test regenerates the boot script from +`boot.txt` into the legacy U-Boot script format used by the checked-in fixture: + +```text +5402d2ea0e5a9a65e577799283ff46fd596914351769c3015d30d6285c787171 generated boot.scr +``` + +The two filesystem zip files contain the split `image.7z.*` archive. The test +requires `7z` or `7zz` to unpack it. Image assembly also needs the host tools +required by `scripts/zvm/rk3588s-roc-pc-make-zvm-image.sh`: `mkfs.vfat` and +`mcopy` on Linux, or `hdiutil` and `newfs_msdos` on macOS. + +The Linux guest path attaches both `gmac0` and `gmac1`. ZVM connects the Linux +guest virtio-net backend to the second modeled MAC, so leaving `gmac1` without +a peer can make the guest-start handshake wait indefinitely. + +For local debugging, the test can reuse pre-extracted release directories +instead of downloading and unpacking again. The kernel directory must be a clean +extraction of the official package; the test checks the hashes above. + +```sh +ZVM_RK3588_KERNEL_DIR=/path/to/kernel_images \ +ZVM_RK3588_FILESYSTEM_DIR=/path/to/file_system_images/image \ +build/pyvenv/bin/meson test -C build --setup thorough \ + qemu:func-aarch64-rk3588_zvm_linux_guest +``` + +Alternatively set `ZVM_RK3588_RELEASE_DIR` to a directory containing +`kernel_images/` and either `file_system_images_official/image/` or +`file_system_images/image/`. + +## Use In Tests + +Functional tests should not run QEMU directly on the compressed file. Extract it +with sparse output first: + +```sh +roms/zvm-rk3588s-roc-pc/extract-image.sh \ + roms/zvm-rk3588s-roc-pc/rk3588s-zvm-no-fs.raw.zst \ + /tmp/rk3588s-zvm-no-fs.raw +``` + +Then boot it with the RK3588S ROC PC SD slot: + +```sh +qemu-system-aarch64 \ + -accel tcg \ + -machine rk3588s-roc-pc \ + -smp 8 \ + -m 1G \ + -drive if=sd,index=2,file=/tmp/rk3588s-zvm-no-fs.raw,format=raw \ + -nic user,model=gmac0 \ + -serial stdio \ + -display none \ + -monitor none +``` diff --git a/roms/zvm-rk3588s-roc-pc/build-image.sh b/roms/zvm-rk3588s-roc-pc/build-image.sh new file mode 100755 index 0000000000000..56c90623d8835 --- /dev/null +++ b/roms/zvm-rk3588s-roc-pc/build-image.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +# +# Rebuild the minimal ZVM RK3588S ROC PC functional-test fixture. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +set -euo pipefail + +kernel_dir= +u_boot_rockchip= +output= +keep_raw=false + +usage() +{ + cat <<'EOF' +Usage: + build-image.sh \ + --kernel-dir DIR \ + --uboot-rockchip FILE \ + --output FILE.raw.zst [--keep-raw] + +Builds the no-filesystem RK3588S ROC PC ZVM raw image with the repository +packaging helper, then compresses it with zstd for functional tests. + +Required: + --kernel-dir DIR ZVM RK3588 kernel_images directory. + --uboot-rockchip FILE Rockchip u-boot-rockchip.bin. + --output FILE.raw.zst Compressed output fixture. + +Options: + --keep-raw Keep the intermediate raw image next to output. + -h, --help Show this help. +EOF +} + +die() +{ + echo "error: $*" >&2 + exit 1 +} + +need_value() +{ + [ "$#" -ge 2 ] || die "$1 requires an argument" +} + +while [ "$#" -gt 0 ]; do + case "$1" in + --kernel-dir) + need_value "$@" + kernel_dir=$2 + shift 2 + ;; + --uboot-rockchip) + need_value "$@" + u_boot_rockchip=$2 + shift 2 + ;; + --output) + need_value "$@" + output=$2 + shift 2 + ;; + --keep-raw) + keep_raw=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + die "unknown argument: $1" + ;; + esac +done + +[ -n "$kernel_dir" ] || die "--kernel-dir is required" +[ -n "$u_boot_rockchip" ] || die "--uboot-rockchip is required" +[ -n "$output" ] || die "--output is required" +command -v zstd >/dev/null 2>&1 || die "missing required command: zstd" + +case "$output" in +*.zst) + raw_output=${output%.zst} + ;; +*) + raw_output=$output.raw + ;; +esac + +script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +repo_root=$(CDPATH= cd -- "$script_dir/../.." && pwd) +helper=$repo_root/scripts/zvm/rk3588s-roc-pc-make-zvm-image.sh + +"$helper" \ + --kernel-dir "$kernel_dir" \ + --uboot-rockchip "$u_boot_rockchip" \ + --output "$raw_output" \ + --no-filesystems \ + --force + +zstd -T0 -15 -f "$raw_output" -o "$output" + +if ! $keep_raw; then + rm -f "$raw_output" +fi + +echo "Done: $output" diff --git a/roms/zvm-rk3588s-roc-pc/build-u-boot-rockchip.sh b/roms/zvm-rk3588s-roc-pc/build-u-boot-rockchip.sh new file mode 100755 index 0000000000000..aaa857dd6a838 --- /dev/null +++ b/roms/zvm-rk3588s-roc-pc/build-u-boot-rockchip.sh @@ -0,0 +1,265 @@ +#!/usr/bin/env bash +# +# Rebuild the RK3588S ROC PC Rockchip-packaged U-Boot fixture. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +set -euo pipefail + +UBOOT_URL=${UBOOT_URL:-https://github.com/u-boot/u-boot.git} +UBOOT_COMMIT=${UBOOT_COMMIT:-5ca1a73c7d3064582498a8aa96c29e714402a6d3} +RKBIN_URL=${RKBIN_URL:-https://github.com/rockchip-linux/rkbin} +RKBIN_COMMIT=${RKBIN_COMMIT:-ecb4fcbe954edf38b3ae037d5de6d9f5bccf81f4} +if [ -n "${SOURCE_DATE_EPOCH+x}" ]; then + UBOOT_SOURCE_DATE_EPOCH=${UBOOT_SOURCE_DATE_EPOCH:-$SOURCE_DATE_EPOCH} + FIT_SOURCE_DATE_EPOCH=${FIT_SOURCE_DATE_EPOCH:-$SOURCE_DATE_EPOCH} +else + UBOOT_SOURCE_DATE_EPOCH=${UBOOT_SOURCE_DATE_EPOCH:-1781525104} + FIT_SOURCE_DATE_EPOCH=${FIT_SOURCE_DATE_EPOCH:-1781525116} +fi +SOURCE_DATE_EPOCH=$UBOOT_SOURCE_DATE_EPOCH +: "${EXPECTED_UBOOT_ROCKCHIP_SHA256:=\ +6cd4b3d1931a252d564c88220e071eba57009fff49620b9520f33e8c58f80fb5}" + +script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +repo_root=$(CDPATH= cd -- "$script_dir/../.." && pwd) +output_dir=${OUTPUT_DIR:-$repo_root/build/zvm-rk3588s-roc-pc-u-boot} +dockerfile=${DOCKERFILE:-$script_dir/Dockerfile} +docker_image=${DOCKER_IMAGE:-qemu-zvm-rk3588s-roc-pc-u-boot:ubuntu-24.04} +inside_container=false +native=false + +usage() +{ + cat <<'EOF' +Usage: + build-u-boot-rockchip.sh [--output-dir DIR] [--native] + +Clone U-Boot and rkbin at the pinned revisions, apply the ROC-PC DTS +compatibility patch, build u-boot-rockchip.bin, and verify its SHA256. + +By default this script builds the local Dockerfile and runs inside that image. +Use --native to run directly on a Linux host with the required packages. + +Environment overrides: + UBOOT_URL, UBOOT_COMMIT, RKBIN_URL, RKBIN_COMMIT, + UBOOT_SOURCE_DATE_EPOCH, FIT_SOURCE_DATE_EPOCH, SOURCE_DATE_EPOCH, + DOCKERFILE, DOCKER_IMAGE, EXPECTED_UBOOT_ROCKCHIP_SHA256, + ALLOW_HASH_MISMATCH=1 +EOF +} + +die() +{ + echo "error: $*" >&2 + exit 1 +} + +need_value() +{ + [ "$#" -ge 2 ] || die "$1 requires an argument" +} + +while [ "$#" -gt 0 ]; do + case "$1" in + --output-dir) + need_value "$@" + output_dir=$2 + shift 2 + ;; + --native) + native=true + shift + ;; + --inside-container) + inside_container=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + die "unknown argument: $1" + ;; + esac +done + +mkdir -p "$output_dir" +output_dir=$(CDPATH= cd -- "$output_dir" && pwd) + +if ! $inside_container && ! $native; then + command -v docker >/dev/null 2>&1 || \ + die "missing docker; install Docker or rerun with --native on Linux" + docker build -f "$dockerfile" -t "$docker_image" "$script_dir" + docker run --rm \ + -e HOST_UID="$(id -u)" \ + -e HOST_GID="$(id -g)" \ + -e UBOOT_URL="$UBOOT_URL" \ + -e UBOOT_COMMIT="$UBOOT_COMMIT" \ + -e RKBIN_URL="$RKBIN_URL" \ + -e RKBIN_COMMIT="$RKBIN_COMMIT" \ + -e SOURCE_DATE_EPOCH="$SOURCE_DATE_EPOCH" \ + -e UBOOT_SOURCE_DATE_EPOCH="$UBOOT_SOURCE_DATE_EPOCH" \ + -e FIT_SOURCE_DATE_EPOCH="$FIT_SOURCE_DATE_EPOCH" \ + -e EXPECTED_UBOOT_ROCKCHIP_SHA256="$EXPECTED_UBOOT_ROCKCHIP_SHA256" \ + -e ALLOW_HASH_MISMATCH="${ALLOW_HASH_MISMATCH:-0}" \ + -v "$repo_root:$repo_root" \ + -v "$output_dir:$output_dir" \ + -w "$repo_root" \ + "$docker_image" \ + bash "$script_dir/build-u-boot-rockchip.sh" \ + --inside-container \ + --output-dir "$output_dir" + exit $? +fi + +if $inside_container && [ "${INSTALL_BUILD_DEPS:-0}" = 1 ]; then + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install -y \ + bc \ + bison \ + build-essential \ + device-tree-compiler \ + flex \ + gcc-aarch64-linux-gnu \ + git \ + libgnutls28-dev \ + libssl-dev \ + patch \ + python3 \ + python3-dev \ + python3-pyelftools \ + python3-setuptools \ + swig +fi + +command -v git >/dev/null 2>&1 || die "missing required command: git" +command -v make >/dev/null 2>&1 || die "missing required command: make" +command -v aarch64-linux-gnu-gcc >/dev/null 2>&1 || \ + die "missing required command: aarch64-linux-gnu-gcc" + +clone_at_commit() +{ + repo_url=$1 + commit=$2 + dest=$3 + + if [ ! -d "$dest/.git" ]; then + rm -rf "$dest" + mkdir -p "$dest" + git -C "$dest" init + git -C "$dest" remote add origin "$repo_url" + fi + + git config --global --add safe.directory "$dest" + git -C "$dest" remote set-url origin "$repo_url" + if ! git -C "$dest" fetch --depth=1 origin "$commit"; then + git -C "$dest" fetch origin + fi + git -C "$dest" checkout --detach "$commit" + git -C "$dest" reset --hard "$commit" + git -C "$dest" clean -ffdx +} + +sha256_file() +{ + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" | awk '{print $1}' + else + shasum -a 256 "$1" | awk '{print $1}' + fi +} + +rerun_binman_with_fit_epoch() +{ + cmd_file=$build_dir/..binman_stamp.cmd + [ -f "$cmd_file" ] || die "missing binman command file: $cmd_file" + + binman_cmd=$(sed -n 's/^cmd_\.binman_stamp := //p' "$cmd_file") + [ -n "$binman_cmd" ] || die "could not parse binman command from $cmd_file" + + rm -f \ + "$build_dir/.binman_stamp" \ + "$build_dir/idbloader.img" \ + "$build_dir/simple-bin.fit.fit" \ + "$build_dir/simple-bin.fit.itb" \ + "$build_dir/u-boot.itb" \ + "$build_dir/u-boot-rockchip.bin" + + ( + cd "$build_dir" + SOURCE_DATE_EPOCH=$FIT_SOURCE_DATE_EPOCH sh -c "$binman_cmd" + ) +} + +restore_uboot_git() +{ + if [ -d "$saved_uboot_git" ] && [ ! -e "$uboot_src/.git" ]; then + mv "$saved_uboot_git" "$uboot_src/.git" + fi +} + +mkdir -p "$output_dir" + +uboot_src=$output_dir/u-boot-src +rkbin_src=$output_dir/rkbin +build_dir=$output_dir/u-boot-build +patch_file=$script_dir/u-boot-rk3588s-roc-pc-zvmcompat.diff +saved_uboot_git=$output_dir/u-boot-src.git-for-build + +clone_at_commit "$UBOOT_URL" "$UBOOT_COMMIT" "$uboot_src" +clone_at_commit "$RKBIN_URL" "$RKBIN_COMMIT" "$rkbin_src" + +git -C "$uboot_src" apply "$patch_file" + +rm -rf "$build_dir" +mkdir -p "$build_dir" + +export CROSS_COMPILE=aarch64-linux-gnu- +export SOURCE_DATE_EPOCH + +make -C "$uboot_src" O="$build_dir" generic-rk3588_defconfig +"$uboot_src/scripts/config" --file "$build_dir/.config" \ + --enable OF_UPSTREAM \ + --set-str DEFAULT_DEVICE_TREE rockchip/rk3588s-roc-pc \ + --set-str DEFAULT_FDT_FILE rockchip/rk3588-generic.dtb \ + --set-str OF_LIST rockchip/rk3588s-roc-pc \ + --set-str SPL_OF_LIST rockchip/rk3588s-roc-pc \ + --enable CMD_CACHE +make -C "$uboot_src" O="$build_dir" olddefconfig + +rm -rf "$saved_uboot_git" +mv "$uboot_src/.git" "$saved_uboot_git" +trap restore_uboot_git EXIT + +BL31=$rkbin_src/bin/rk35/rk3588_bl31_v1.54.elf \ +ROCKCHIP_TPL=$rkbin_src/bin/rk35/rk3588_ddr_lp4_2112MHz_lp5_2400MHz_v1.21.bin \ + make -C "$uboot_src" O="$build_dir" -j"${JOBS:-$(nproc)}" + +if [ "$FIT_SOURCE_DATE_EPOCH" != "$UBOOT_SOURCE_DATE_EPOCH" ]; then + rerun_binman_with_fit_epoch +fi + +restore_uboot_git +trap - EXIT + +install -m 0644 "$build_dir/u-boot-rockchip.bin" "$output_dir/u-boot-rockchip.bin" +install -m 0644 "$build_dir/u-boot.bin" "$output_dir/u-boot.bin" +install -m 0644 "$build_dir/idbloader.img" "$output_dir/idbloader.img" +install -m 0644 "$build_dir/u-boot.itb" "$output_dir/u-boot.itb" + +if $inside_container && [ -n "${HOST_UID:-}" ]; then + chown -R "$HOST_UID:${HOST_GID:-$HOST_UID}" "$output_dir" +fi + +actual=$(sha256_file "$output_dir/u-boot-rockchip.bin") +if [ "$actual" != "$EXPECTED_UBOOT_ROCKCHIP_SHA256" ]; then + echo "error: u-boot-rockchip.bin hash mismatch" >&2 + echo "expected: $EXPECTED_UBOOT_ROCKCHIP_SHA256" >&2 + echo "actual: $actual" >&2 + [ "${ALLOW_HASH_MISMATCH:-0}" = 1 ] || exit 1 +fi + +echo "Done: $output_dir/u-boot-rockchip.bin" diff --git a/roms/zvm-rk3588s-roc-pc/extract-image.sh b/roms/zvm-rk3588s-roc-pc/extract-image.sh new file mode 100755 index 0000000000000..bebd4d8a2f7ba --- /dev/null +++ b/roms/zvm-rk3588s-roc-pc/extract-image.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# +# Extract a ZVM RK3588S ROC PC compressed raw image with sparse output. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +set -euo pipefail + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 INPUT.raw.zst OUTPUT.raw" >&2 + exit 1 +fi + +input=$1 +output=$2 + +command -v zstd >/dev/null 2>&1 || { + echo "error: missing required command: zstd" >&2 + exit 1 +} + +zstd --sparse -f -d "$input" -o "$output" diff --git a/roms/zvm-rk3588s-roc-pc/manifest.sha256 b/roms/zvm-rk3588s-roc-pc/manifest.sha256 new file mode 100644 index 0000000000000..bd857bfcd823c --- /dev/null +++ b/roms/zvm-rk3588s-roc-pc/manifest.sha256 @@ -0,0 +1,2 @@ +38443776ec81183788d203861bd89ac8ced55536175da422534129b36d3708d9 rk3588s-zvm-no-fs.raw.zst +2d68dcc324d0cc41eebee69c5d575e5b19e243383a5844f9f591499d1bfd78d7 rk3588s-zvm-no-fs.raw diff --git a/roms/zvm-rk3588s-roc-pc/rk3588s-zvm-no-fs.raw.zst b/roms/zvm-rk3588s-roc-pc/rk3588s-zvm-no-fs.raw.zst new file mode 100644 index 0000000000000..99155476dd7cc Binary files /dev/null and b/roms/zvm-rk3588s-roc-pc/rk3588s-zvm-no-fs.raw.zst differ diff --git a/roms/zvm-rk3588s-roc-pc/u-boot-rk3588s-roc-pc-zvmcompat.diff b/roms/zvm-rk3588s-roc-pc/u-boot-rk3588s-roc-pc-zvmcompat.diff new file mode 100644 index 0000000000000..7a76481904440 --- /dev/null +++ b/roms/zvm-rk3588s-roc-pc/u-boot-rk3588s-roc-pc-zvmcompat.diff @@ -0,0 +1,12 @@ +diff --git a/dts/upstream/src/arm64/rockchip/rk3588s-roc-pc.dts b/dts/upstream/src/arm64/rockchip/rk3588s-roc-pc.dts +index 7e179862..880ba418 100644 +--- a/dts/upstream/src/arm64/rockchip/rk3588s-roc-pc.dts ++++ b/dts/upstream/src/arm64/rockchip/rk3588s-roc-pc.dts +@@ -13,2 +13,5 @@ +- model = "Firefly Station M3"; +- compatible = "firefly,rk3588s-roc-pc", "rockchip,rk3588s"; ++ model = "Firefly ROC-RK3588S-PC"; ++ compatible = "rockchip,rk3588s-firefly-roc-pc", ++ "firefly,rk3588s-roc-pc", ++ "rockchip,rk3588s", ++ "rockchip,rk3588"; diff --git a/scripts/run-functional-test.sh b/scripts/run-functional-test.sh new file mode 100755 index 0000000000000..c66c433bf0693 --- /dev/null +++ b/scripts/run-functional-test.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-2.0-or-later + +set -euo pipefail + +test=$(basename "$*" .py) +test_log=$(mktemp -d) + +test_dir() +{ + find tests/functional -name "$test"'.*' || true +} + +show_logs() +{ + for log in $(test_dir)/*.log; do + cat << EOF +----------------------------------------------------- +$(echo $log) +$(tail -n 200 $log) +EOF + done + echo +} + +trap "rm -rf $test_log" EXIT + +err=0 +delay=${QEMU_FUNCTIONAL_TEST_TIMEOUT:-10m} +timeout $delay "$@" > $test_log/stdout 2> $test_log/stderr || err=$? + +if [ $err -eq 0 ]; then + # success, show results + cat $test_log/stdout + exit 0 +fi + +if [[ $err -eq 124 || $err -eq 137 ]]; then + echo "not ok TIMEOUT" >> $test_log/stdout +else + echo "FAIL: exit $err" >> $test_log/stderr +fi + +show_logs >&2 +cat $test_log/stderr >&2 +cat $test_log/stdout >&2 + +cat $test_log/stdout +exit $err diff --git a/scripts/zvm/rk3588s-roc-pc-make-zvm-image.sh b/scripts/zvm/rk3588s-roc-pc-make-zvm-image.sh new file mode 100755 index 0000000000000..e51d1e1ec09be --- /dev/null +++ b/scripts/zvm/rk3588s-roc-pc-make-zvm-image.sh @@ -0,0 +1,328 @@ +#!/usr/bin/env bash +# +# Build a QEMU raw TF-card image for the ZVM RK3588S ROC PC release package. +# +# Copyright (c) 2026 Process Mission +# +# SPDX-License-Identifier: GPL-2.0-or-later +# +# The generated image uses the layout expected by the upstream RK3588 ZVM +# deployment package: +# - Rockchip RKNS firmware at LBA 64 +# - partition 1: FAT32 boot partition with zvm_release_rk3588v2.bin +# and boot.scr/boot.scr.uimg +# - partition 2: FAT32 payload partition with nrtos_images.bin at +# partition offset 1024 MiB, and optional filesystem images +# at the release package offsets. + +set -euo pipefail + +total_sectors=$((32 * 1024 * 1024 * 1024 / 512)) +p1_start=$((0x8000)) +p2_start=$((0x1005000)) +p1_size=$((p2_start - p1_start)) +p2_size=$((total_sectors - p2_start)) +p1_start_mib=$((p1_start / 2048)) +p2_start_mib=$((p2_start / 2048)) + +kernel_dir= +filesystem_dir= +u_boot_rockchip= +output= +boot_script= +regen=false +write_filesystems=true +force=false + +usage() +{ + cat <<'EOF' +Usage: + rk3588s-roc-pc-make-zvm-image.sh \ + --kernel-dir DIR \ + --filesystem-dir DIR \ + --uboot-rockchip FILE \ + --output FILE [options] + +Required: + --kernel-dir DIR ZVM kernel_images directory. + --filesystem-dir DIR Directory containing android_vda.img, oh_vda.bin, + and linux_diskimg.bin. + --uboot-rockchip FILE Rockchip u-boot-rockchip.bin for the image header. + --output FILE Output raw TF-card image. + +Options: + --boot-script FILE U-Boot script image to copy as boot.scr and + boot.scr.uimg. Defaults to DIR/boot.scr. + --regen Run "printf '3\n' | ./auto_py.sh" in --kernel-dir + before packing the card. + --no-filesystems Skip Android/OpenHarmony/Linux filesystem slots. + --force Overwrite an existing output image. + -h, --help Show this help. +EOF +} + +die() +{ + echo "error: $*" >&2 + exit 1 +} + +need_cmd() +{ + command -v "$1" >/dev/null 2>&1 || die "missing required command: $1" +} + +need_value() +{ + [ "$#" -ge 2 ] || die "$1 requires an argument" +} + +while [ "$#" -gt 0 ]; do + case "$1" in + --kernel-dir) + need_value "$@" + kernel_dir=$2 + shift 2 + ;; + --filesystem-dir) + need_value "$@" + filesystem_dir=$2 + shift 2 + ;; + --uboot-rockchip) + need_value "$@" + u_boot_rockchip=$2 + shift 2 + ;; + --output) + need_value "$@" + output=$2 + shift 2 + ;; + --boot-script) + need_value "$@" + boot_script=$2 + shift 2 + ;; + --regen) + regen=true + shift + ;; + --no-filesystems) + write_filesystems=false + shift + ;; + --force) + force=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + die "unknown argument: $1" + ;; + esac +done + +[ -n "$kernel_dir" ] || die "--kernel-dir is required" +[ -n "$u_boot_rockchip" ] || die "--uboot-rockchip is required" +[ -n "$output" ] || die "--output is required" + +if $write_filesystems && [ -z "$filesystem_dir" ]; then + die "--filesystem-dir is required unless --no-filesystems is used" +fi + +need_cmd dd +need_cmd python3 +need_cmd truncate + +host_os=$(uname -s) +case "$host_os" in +Linux) + need_cmd mkfs.vfat + need_cmd mcopy + ;; +Darwin) + need_cmd hdiutil + need_cmd newfs_msdos + ;; +*) + die "unsupported host OS: $host_os" + ;; +esac + +[ -d "$kernel_dir" ] || die "kernel directory not found: $kernel_dir" +[ -f "$u_boot_rockchip" ] || die "u-boot-rockchip file not found: $u_boot_rockchip" + +if [ -z "$boot_script" ]; then + boot_script="$kernel_dir/boot.scr" +fi + +if $regen; then + [ -x "$kernel_dir/auto_py.sh" ] || die "cannot execute $kernel_dir/auto_py.sh" + (cd "$kernel_dir" && printf '3\n' | ./auto_py.sh) +fi + +zvm_release="$kernel_dir/zvm_release_rk3588v2.bin" +nrtos_images="$kernel_dir/nrtos_images.bin" + +[ -f "$zvm_release" ] || die "missing $zvm_release" +[ -f "$nrtos_images" ] || die "missing $nrtos_images" +[ -f "$boot_script" ] || die "missing boot script image: $boot_script" + +if $write_filesystems; then + [ -d "$filesystem_dir" ] || die "filesystem directory not found: $filesystem_dir" + [ -f "$filesystem_dir/android_vda.img" ] || die "missing android_vda.img" + [ -f "$filesystem_dir/oh_vda.bin" ] || die "missing oh_vda.bin" + [ -f "$filesystem_dir/linux_diskimg.bin" ] || die "missing linux_diskimg.bin" +fi + +if [ -e "$output" ] && ! $force; then + die "output exists, use --force to overwrite: $output" +fi + +mkdir -p "$(dirname "$output")" +workdir=$(mktemp -d "${TMPDIR:-/tmp}/rk3588s-zvm.XXXXXX") +attached_targets=() +darwin_target= + +cleanup() +{ + local target + + for target in ${attached_targets[@]+"${attached_targets[@]}"}; do + hdiutil detach -quiet "$target" >/dev/null 2>&1 || true + done + rm -rf "$workdir" +} + +darwin_attach() +{ + local image=$1 + local mode=$2 + local mountpoint=${3:-} + local target + + if [ "$mode" = "nomount" ]; then + target=$(hdiutil attach -imagekey diskimage-class=CRawDiskImage \ + -nomount "$image" | awk 'NR == 1 { print $1 }') + [ -n "$target" ] || die "failed to attach raw image: $image" + else + hdiutil attach -quiet -imagekey diskimage-class=CRawDiskImage \ + -nobrowse -mountpoint "$mountpoint" "$image" + target=$mountpoint + fi + + darwin_target=$target + attached_targets+=("$target") +} + +darwin_detach() +{ + local target=$1 + + hdiutil detach -quiet "$target" +} + +trap cleanup EXIT + +p1_img="$workdir/rk3588boot.fat" +p2_img="$workdir/zvmfs.fat" +p1_mount="$workdir/rk3588boot.mnt" + +echo "Creating sparse raw image: $output" +rm -f "$output" +truncate -s $((total_sectors * 512)) "$output" + +python3 - "$output" "$p1_start" "$p1_size" "$p2_start" "$p2_size" <<'PY' +import struct +import sys + +path, p1_start, p1_size, p2_start, p2_size = sys.argv[1:] + +def entry(active, ptype, start, size): + # CHS values are ignored by modern firmware; expose LBA fields only. + return struct.pack("/dev/null + mkfs.vfat -F 32 -n ZVMFS -C "$p2_img" $((p2_size / 2)) >/dev/null + ;; +Darwin) + truncate -s $((p1_size * 512)) "$p1_img" + darwin_attach "$p1_img" nomount + p1_dev=$darwin_target + if ! newfs_msdos -F 32 -v RK3588BOOT "$p1_dev" >/dev/null; then + darwin_detach "$p1_dev" || true + exit 1 + fi + darwin_detach "$p1_dev" + + truncate -s $((p2_size * 512)) "$p2_img" + darwin_attach "$p2_img" nomount + p2_dev=$darwin_target + if ! newfs_msdos -F 32 -v ZVMFS "$p2_dev" >/dev/null; then + darwin_detach "$p2_dev" || true + exit 1 + fi + darwin_detach "$p2_dev" + ;; +esac + +echo "Populating boot partition" +case "$host_os" in +Linux) + mcopy -o -i "$p1_img" "$zvm_release" ::/zvm_release_rk3588v2.bin + mcopy -o -i "$p1_img" "$boot_script" ::/boot.scr + mcopy -o -i "$p1_img" "$boot_script" ::/boot.scr.uimg + ;; +Darwin) + mkdir -p "$p1_mount" + darwin_attach "$p1_img" mount "$p1_mount" >/dev/null + cp -f "$zvm_release" "$p1_mount/zvm_release_rk3588v2.bin" + cp -f "$boot_script" "$p1_mount/boot.scr" + cp -f "$boot_script" "$p1_mount/boot.scr.uimg" + darwin_detach "$p1_mount" + ;; +esac + +echo "Writing nrtos_images.bin at partition 2 offset 1024 MiB" +dd if="$nrtos_images" of="$p2_img" bs=1M seek=1024 conv=notrunc status=progress + +if $write_filesystems; then + echo "Writing release filesystem images" + dd if="$filesystem_dir/android_vda.img" of="$p2_img" bs=1M seek=3072 \ + conv=notrunc,sparse status=progress + dd if="$filesystem_dir/oh_vda.bin" of="$p2_img" bs=1M seek=6144 \ + conv=notrunc,sparse status=progress + for seek in 9216 12288 15360 19432; do + dd if="$filesystem_dir/linux_diskimg.bin" of="$p2_img" bs=1M \ + seek="$seek" conv=notrunc,sparse status=progress + done +fi + +echo "Installing partitions into raw image" +dd if="$p1_img" of="$output" bs=1M seek="$p1_start_mib" \ + conv=notrunc,sparse status=progress +dd if="$p2_img" of="$output" bs=1M seek="$p2_start_mib" \ + conv=notrunc,sparse status=progress + +echo "Installing Rockchip RKNS firmware at LBA 64" +dd if="$u_boot_rockchip" of="$output" bs=512 seek=64 conv=notrunc status=none + +echo "Done: $output" diff --git a/target/arm/helper.c b/target/arm/helper.c index af45234ad2a7e..dd7a03b3ca03f 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -9637,6 +9637,13 @@ void arm_cpu_do_interrupt(CPUState *cs) env->exception.syndrome); } + if (tcg_enabled() && cs->exception_index == EXCP_SMC && + arm_handle_psci_smc_handler(cpu)) { + qemu_log_mask(CPU_LOG_INT, "...handled as board SMC call\n"); + qemu_plugin_vcpu_hostcall_cb(cs, last_pc); + return; + } + if (tcg_enabled() && arm_is_psci_call(cpu, cs->exception_index)) { arm_handle_psci_call(cpu); qemu_log_mask(CPU_LOG_INT, "...handled as PSCI call\n"); diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c index da3ec521fc7d1..290f11ee3fccd 100644 --- a/target/arm/hvf/hvf.c +++ b/target/arm/hvf/hvf.c @@ -1580,6 +1580,9 @@ static bool hvf_handle_psci_call(CPUState *cpu, int *excp_ret) case QEMU_PSCI_0_2_FN_PSCI_VERSION: ret = QEMU_PSCI_VERSION_1_1; break; + case QEMU_SMCCC_VERSION_FUNC_ID: + ret = QEMU_SMCCC_VERSION_1_1; + break; case QEMU_PSCI_0_2_FN_MIGRATE_INFO_TYPE: ret = QEMU_PSCI_0_2_RET_TOS_MIGRATION_NOT_REQUIRED; /* No trusted OS */ break; @@ -1662,6 +1665,7 @@ static bool hvf_handle_psci_call(CPUState *cpu, int *excp_ret) case QEMU_PSCI_0_2_FN_CPU_SUSPEND: case QEMU_PSCI_0_2_FN64_CPU_SUSPEND: case QEMU_PSCI_1_0_FN_PSCI_FEATURES: + case QEMU_SMCCC_VERSION_FUNC_ID: ret = 0; break; case QEMU_PSCI_0_1_FN_MIGRATE: @@ -2506,7 +2510,9 @@ static int hvf_handle_exception(CPUState *cpu, hv_vcpu_exit_exception_t *excp) /* Secure Monitor Call exception, we need to advance $pc */ advance_pc = true; - if (!hvf_handle_psci_call(cpu, &ret)) { + if (arm_handle_psci_smc_handler(arm_cpu)) { + /* Handled by board firmware; x0 already contains the result. */ + } else if (!hvf_handle_psci_call(cpu, &ret)) { trace_hvf_unknown_smc(env->xregs[0]); /* SMCCC 1.3 section 5.2 says every unknown SMCCC call returns -1 */ env->xregs[0] = -1; diff --git a/target/arm/internals.h b/target/arm/internals.h index 4026f67579e76..f30e73f4a04f4 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -714,6 +714,20 @@ bool arm_is_psci_call(ARMCPU *cpu, int excp_type); /* Actually handle a PSCI call */ void arm_handle_psci_call(ARMCPU *cpu); +/* + * Optional per-board SMC handler. A board may register a callback via + * arm_register_psci_smc_handler(); accelerator SMC exception paths call it + * before falling through to generic PSCI handling or architectural SMC + * behavior. The callback receives the calling ARMCPU and may inspect/modify + * its xregs. Returning true means "the SMC was handled (x0 is already set)". + * + * Default is NULL (no board hook) - non-registered boards see zero + * behavior change. + */ +typedef bool (*ArmPsciSmcHandler)(ARMCPU *cpu); +void arm_register_psci_smc_handler(ArmPsciSmcHandler handler); +bool arm_handle_psci_smc_handler(ARMCPU *cpu); + /** * arm_clear_exclusive: clear the exclusive monitor * @env: CPU env diff --git a/target/arm/kvm-consts.h b/target/arm/kvm-consts.h index 9fba3e886d34d..bb326ff190f74 100644 --- a/target/arm/kvm-consts.h +++ b/target/arm/kvm-consts.h @@ -79,6 +79,13 @@ MISMATCH_CHECK(QEMU_PSCI_0_1_FN_MIGRATE, KVM_PSCI_FN_MIGRATE); #define QEMU_PSCI_1_0_FN_PSCI_FEATURES QEMU_PSCI_0_2_FN(10) +/* + * SMCCC v1.1 discovery call. Linux asks for this via PSCI_FEATURES + * before enabling arm_smccc_1_1_invoke() users such as SCMI-over-SMC. + */ +#define QEMU_SMCCC_VERSION_FUNC_ID 0x80000000 +#define QEMU_SMCCC_VERSION_1_1 0x10001 + MISMATCH_CHECK(QEMU_PSCI_0_2_FN_CPU_SUSPEND, PSCI_0_2_FN_CPU_SUSPEND); MISMATCH_CHECK(QEMU_PSCI_0_2_FN_CPU_OFF, PSCI_0_2_FN_CPU_OFF); MISMATCH_CHECK(QEMU_PSCI_0_2_FN_CPU_ON, PSCI_0_2_FN_CPU_ON); diff --git a/target/arm/meson.build b/target/arm/meson.build index 4412fde065f20..4301d9a594a63 100644 --- a/target/arm/meson.build +++ b/target/arm/meson.build @@ -30,6 +30,7 @@ arm_common_user_system_ss.add(when: 'TARGET_AARCH64', if_true: files( arm_common_system_ss.add(files( 'arm-qmp-cmds.c', + 'psci-common.c', )) arm_system_ss.add(when: 'CONFIG_KVM', if_true: files('hyp_gdbstub.c', 'kvm.c')) arm_system_ss.add(when: 'CONFIG_HVF', if_true: files('hyp_gdbstub.c')) diff --git a/target/arm/psci-common.c b/target/arm/psci-common.c new file mode 100644 index 0000000000000..c493e6538620a --- /dev/null +++ b/target/arm/psci-common.c @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Common ARM PSCI helpers. + * + * Copyright (c) 2026 Chao Liu + */ + +#include "qemu/osdep.h" +#include "cpu.h" +#include "internals.h" + +/* + * Optional board-registered SMC handler. SMC exception paths call this before + * generic PSCI handling or architectural SMC behavior. Default NULL. + */ +static ArmPsciSmcHandler arm_psci_smc_handler; + +void arm_register_psci_smc_handler(ArmPsciSmcHandler handler) +{ + arm_psci_smc_handler = handler; +} + +bool arm_handle_psci_smc_handler(ARMCPU *cpu) +{ + return arm_psci_smc_handler && arm_psci_smc_handler(cpu); +} diff --git a/target/arm/tcg/psci.c b/target/arm/tcg/psci.c index 56754bde95161..3467d798ab6fc 100644 --- a/target/arm/tcg/psci.c +++ b/target/arm/tcg/psci.c @@ -95,6 +95,9 @@ void arm_handle_psci_call(ARMCPU *cpu) case QEMU_PSCI_0_2_FN_PSCI_VERSION: ret = QEMU_PSCI_VERSION_1_1; break; + case QEMU_SMCCC_VERSION_FUNC_ID: + ret = QEMU_SMCCC_VERSION_1_1; + break; case QEMU_PSCI_0_2_FN_MIGRATE_INFO_TYPE: ret = QEMU_PSCI_0_2_RET_TOS_MIGRATION_NOT_REQUIRED; /* No trusted OS */ break; @@ -191,6 +194,7 @@ void arm_handle_psci_call(ARMCPU *cpu) case QEMU_PSCI_0_2_FN_CPU_SUSPEND: case QEMU_PSCI_0_2_FN64_CPU_SUSPEND: case QEMU_PSCI_1_0_FN_PSCI_FEATURES: + case QEMU_SMCCC_VERSION_FUNC_ID: if (!(param[1] & QEMU_PSCI_0_2_64BIT) || is_a64(env)) { ret = 0; break; diff --git a/target/riscv/cpu-qom.h b/target/riscv/cpu-qom.h index 1a28f1369c907..7bdca8e35dabf 100644 --- a/target/riscv/cpu-qom.h +++ b/target/riscv/cpu-qom.h @@ -42,8 +42,10 @@ #define TYPE_RISCV_CPU_RVA22S64 RISCV_CPU_TYPE_NAME("rva22s64") #define TYPE_RISCV_CPU_RVA23U64 RISCV_CPU_TYPE_NAME("rva23u64") #define TYPE_RISCV_CPU_RVA23S64 RISCV_CPU_TYPE_NAME("rva23s64") +#define TYPE_RISCV_CPU_RVSERVER_REF RISCV_CPU_TYPE_NAME("riscv-server-ref") #define TYPE_RISCV_CPU_IBEX RISCV_CPU_TYPE_NAME("lowrisc-ibex") #define TYPE_RISCV_CPU_SHAKTI_C RISCV_CPU_TYPE_NAME("shakti-c") +#define TYPE_RISCV_CPU_SPACEMIT_X100 RISCV_CPU_TYPE_NAME("spacemit-x100") #define TYPE_RISCV_CPU_SIFIVE_E RISCV_CPU_TYPE_NAME("sifive-e") #define TYPE_RISCV_CPU_SIFIVE_E31 RISCV_CPU_TYPE_NAME("sifive-e31") #define TYPE_RISCV_CPU_SIFIVE_E34 RISCV_CPU_TYPE_NAME("sifive-e34") diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 5a82e6563bfde..921346bf4a9a3 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -36,6 +36,7 @@ #include "fpu/softfloat-helpers.h" #include "system/device_tree.h" #include "system/kvm.h" +#include "system/qtest.h" #include "system/tcg.h" #include "kvm/kvm_riscv.h" #include "tcg/tcg-cpu.h" @@ -247,7 +248,8 @@ const RISCVIsaExtData isa_edata_arr[] = { ISA_EXT_DATA_ENTRY(zvkt, PRIV_VERSION_1_12_0, ext_zvkt), ISA_EXT_DATA_ENTRY(zhinx, PRIV_VERSION_1_12_0, ext_zhinx), ISA_EXT_DATA_ENTRY(zhinxmin, PRIV_VERSION_1_12_0, ext_zhinxmin), - ISA_EXT_DATA_ENTRY(sdtrig, PRIV_VERSION_1_12_0, debug), + ISA_EXT_DATA_ENTRY(sdext, PRIV_VERSION_1_12_0, ext_sdext), + ISA_EXT_DATA_ENTRY(sdtrig, PRIV_VERSION_1_12_0, ext_sdtrig), ISA_INTERNAL_EXT_DATA_ENTRY(shcounterenw, PRIV_VERSION_1_12_0, has_priv_1_12), ISA_INTERNAL_EXT_DATA_ENTRY(sha, PRIV_VERSION_1_12_0, ext_sha), @@ -1078,8 +1080,14 @@ static void riscv_cpu_reset_hold(Object *obj, ResetType type) env->vill = true; #ifndef CONFIG_USER_ONLY + env->debug_mode = false; + env->dcsr = DCSR_DEBUGVER(4); + env->dpc = 0; + env->dscratch[0] = 0; + env->dscratch[1] = 0; + #ifdef CONFIG_TCG - if (cpu->cfg.debug) { + if (cpu->cfg.ext_sdtrig) { riscv_trigger_reset_hold(env); } #endif @@ -1241,7 +1249,7 @@ static void riscv_cpu_realize(DeviceState *dev, Error **errp) riscv_cpu_register_gdb_regs_for_features(cs); #if defined(CONFIG_TCG) && !defined(CONFIG_USER_ONLY) - if (cpu->cfg.debug) { + if (cpu->cfg.ext_sdtrig) { riscv_trigger_realize(&cpu->env); } #endif @@ -1258,7 +1266,7 @@ static void riscv_cpu_unrealize(DeviceState *dev) #if defined(CONFIG_TCG) && !defined(CONFIG_USER_ONLY) RISCVCPU *cpu = RISCV_CPU(dev); - if (cpu->cfg.debug) { + if (cpu->cfg.ext_sdtrig) { riscv_trigger_unrealize(&cpu->env); } #endif @@ -1346,7 +1354,7 @@ static void riscv_cpu_set_irq(void *opaque, int irq, int level) kvm_riscv_set_irq(cpu, irq, level); } - if (tcg_enabled()) { + if (tcg_enabled() || qtest_enabled()) { riscv_cpu_update_mip(env, 1 << irq, BOOL_TO_MASK(level)); } break; @@ -1355,7 +1363,7 @@ static void riscv_cpu_set_irq(void *opaque, int irq, int level) kvm_riscv_set_irq(cpu, irq, level); } - if (tcg_enabled()) { + if (tcg_enabled() || qtest_enabled()) { env->external_seip = level; riscv_cpu_update_mip(env, 1 << irq, BOOL_TO_MASK(level | env->software_seip)); @@ -1385,7 +1393,7 @@ static void riscv_cpu_set_irq(void *opaque, int irq, int level) if (kvm_enabled()) { kvm_riscv_set_irq(cpu, irq, level); } - if (tcg_enabled()) { + if (tcg_enabled() || qtest_enabled()) { /* Update mip.SGEIP bit */ riscv_cpu_update_mip(env, MIP_SGEIP, BOOL_TO_MASK(!!(env->hgeie & env->hgeip))); @@ -1444,6 +1452,14 @@ static void riscv_cpu_init(Object *obj) cpu->env.vext_ver = VEXT_VERSION_1_00_0; cpu->cfg.max_satp_mode = -1; + /* + * 'debug' started being deprecated in 11.0, been just a proxy + * to set ext_sdtrig ever since. It has been enabled by default + * for a long time though, so we're stuck with setting set 'strig' + * by default too. At least for now ... + */ + cpu->cfg.ext_sdtrig = true; + if (mcc->def->profile) { mcc->def->profile->enabled = true; } @@ -1545,6 +1561,8 @@ const char *riscv_get_misa_ext_description(uint32_t bit) return val; } + + static void cpu_set_prop_err(RISCVCPU *cpu, const char *propname, Error **errp) { @@ -2326,11 +2344,35 @@ static RISCVCPUProfile RVA23S64 = { } }; +/* + * The riscv-server-ref spec isn't a profile per se but its + * CPU definition can be modelled as a profile that extends + * RVA23, with additional things on top of it, and allowing + * future CPUs to derive from it via + * ".profile = &RVServerRef1_0;". + */ +static RISCVCPUProfile RVServerRef1_0 = { + .s_parent = &RVA23S64, + .name = "rvserver-ref-1.0", + .satp_mode = VM_1_10_SV48, + .ext_offsets = { + CPU_CFG_OFFSET(ext_zkr), + CPU_CFG_OFFSET(ext_sdtrig), + CPU_CFG_OFFSET(ext_ssaia), + CPU_CFG_OFFSET(ext_ssccfg), + /* ssstrict is always enabled for PRIV_VER_1_12 */ + + RISCV_PROFILE_EXT_LIST_END + } +}; + + RISCVCPUProfile *riscv_profiles[] = { &RVA22U64, &RVA22S64, &RVA23U64, &RVA23S64, + &RVServerRef1_0, NULL, }; @@ -2833,8 +2875,42 @@ RISCVCPUImpliedExtsRule *riscv_multi_ext_implied_rules[] = { NULL }; +/* + * DEPRECATED_11.0: just a proxy for ext_sdtrig. + */ +static void prop_debug_get(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + bool value = RISCV_CPU(obj)->cfg.ext_sdtrig; + + visit_type_bool(v, name, &value, errp); +} + +/* + * DEPRECATED_11.0: just a proxy for ext_sdtrig. + */ +static void prop_debug_set(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + RISCVCPU *cpu = RISCV_CPU(obj); + bool value; + + visit_type_bool(v, name, &value, errp); + cpu->cfg.ext_sdtrig = value; +} + +/* + * DEPRECATED_11.0: just a proxy for ext_sdtrig. + */ +static const PropertyInfo prop_debug = { + .type = "bool", + .description = "DEPRECATED: use 'sdtrig' instead.", + .get = prop_debug_get, + .set = prop_debug_set, +}; + static const Property riscv_cpu_properties[] = { - DEFINE_PROP_BOOL("debug", RISCVCPU, cfg.debug, true), + {.name = "debug", .info = &prop_debug}, DEFINE_PROP_BOOL("big-endian", RISCVCPU, cfg.big_endian, false), {.name = "pmu-mask", .info = &prop_pmu_mask}, @@ -3000,7 +3076,6 @@ static void riscv_cpu_class_base_init(ObjectClass *c, const void *data) mcc->def->bare |= def->bare; if (def->profile) { assert(profile_extends(def->profile, mcc->def->profile)); - assert(mcc->def->bare); mcc->def->profile = def->profile; } if (def->misa_mxl_max) { @@ -3433,6 +3508,7 @@ static const TypeInfo riscv_cpu_type_infos[] = { .cfg.ext_zicboz = true, .cfg.pmp = true, + .cfg.pmp_regions = 64, .cfg.mmu = true, .cfg.max_satp_mode = VM_1_10_SV48, @@ -3649,6 +3725,13 @@ static const TypeInfo riscv_cpu_type_infos[] = { #endif ), + DEFINE_RISCV_CPU(TYPE_RISCV_CPU_RVSERVER_REF, TYPE_RISCV_BARE_CPU, + .profile = &RVServerRef1_0, + .misa_mxl_max = MXL_RV64, + .cfg.max_satp_mode = VM_1_10_SV57, + .num_triggers = 11, + ), + #if defined(CONFIG_TCG) && !defined(CONFIG_USER_ONLY) DEFINE_RISCV_CPU(TYPE_RISCV_CPU_BASE128, TYPE_RISCV_DYNAMIC_CPU, .cfg.max_satp_mode = VM_1_10_SV57, @@ -3668,6 +3751,18 @@ static const TypeInfo riscv_cpu_type_infos[] = { DEFINE_PROFILE_CPU(TYPE_RISCV_CPU_RVA22S64, TYPE_RISCV_CPU_RV64I, RVA22S64), DEFINE_PROFILE_CPU(TYPE_RISCV_CPU_RVA23U64, TYPE_RISCV_CPU_RV64I, RVA23U64), DEFINE_PROFILE_CPU(TYPE_RISCV_CPU_RVA23S64, TYPE_RISCV_CPU_RV64I, RVA23S64), + + DEFINE_RISCV_CPU(TYPE_RISCV_CPU_SPACEMIT_X100, + TYPE_RISCV_VENDOR_CPU, + .profile = &RVA23S64, + .misa_mxl_max = MXL_RV64, + .misa_ext = RVH, + .cfg.mmu = true, + .cfg.vlenb = 256 >> 3, + .cfg.ext_smaia = true, + .cfg.ext_ssaia = true, + .cfg.max_satp_mode = VM_1_10_SV39, + ), #endif /* TARGET_RISCV64 */ }; diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h index c9dfa7daff2d9..074827b92d1dc 100644 --- a/target/riscv/cpu.h +++ b/target/riscv/cpu.h @@ -507,6 +507,10 @@ struct CPUArchState { /* True if in debugger mode. */ bool debugger; + bool debug_mode; + target_ulong dcsr; + target_ulong dpc; + target_ulong dscratch[2]; uint64_t mstateen[SMSTATEEN_MAX_COUNT]; uint64_t hstateen[SMSTATEEN_MAX_COUNT]; @@ -661,6 +665,9 @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, char *riscv_isa_string(RISCVCPU *cpu); int riscv_cpu_max_xlen(RISCVCPUClass *mcc); bool riscv_cpu_option_set(RISCVCPU *cpu, const char *optname); +void riscv_cpu_enter_debug_mode(CPURISCVState *env, target_ulong pc, + uint32_t cause); +void riscv_cpu_leave_debug_mode(CPURISCVState *env); #ifndef CONFIG_USER_ONLY void riscv_cpu_do_interrupt(CPUState *cpu); @@ -742,6 +749,8 @@ FIELD(TB_FLAGS, PM_SIGNEXTEND, 31, 1) FIELD(EXT_TB_FLAGS, MISA_EXT, 0, 32) FIELD(EXT_TB_FLAGS, ALTFMT, 32, 1) FIELD(EXT_TB_FLAGS, BIG_ENDIAN, 33, 1) +/* sdext single-step needs a TB flag to build 1-insn TBs */ +FIELD(EXT_TB_FLAGS, SDEXT_STEP, 34, 1) #ifdef TARGET_RISCV32 #define riscv_cpu_mxl(env) ((void)(env), MXL_RV32) diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h index 3f146a43fe41c..ab4fc0ca9322b 100644 --- a/target/riscv/cpu_bits.h +++ b/target/riscv/cpu_bits.h @@ -467,6 +467,39 @@ #define CSR_DCSR 0x7b0 #define CSR_DPC 0x7b1 #define CSR_DSCRATCH 0x7b2 +#define CSR_DSCRATCH1 0x7b3 + +/* DCSR fields */ +#define DCSR_XDEBUGVER_SHIFT 28 +#define DCSR_XDEBUGVER_MASK (0xfu << DCSR_XDEBUGVER_SHIFT) +#define DCSR_DEBUGVER(val) ((target_ulong)(val) << DCSR_XDEBUGVER_SHIFT) +#define DCSR_EXTCAUSE_SHIFT 24 +#define DCSR_EXTCAUSE_MASK (0x7u << DCSR_EXTCAUSE_SHIFT) +#define DCSR_CETRIG BIT(19) +#define DCSR_PELP BIT(18) +#define DCSR_EBREAKVS BIT(17) +#define DCSR_EBREAKVU BIT(16) +#define DCSR_EBREAKM BIT(15) +#define DCSR_EBREAKS BIT(13) +#define DCSR_EBREAKU BIT(12) +#define DCSR_STEPIE BIT(11) +#define DCSR_STOPCOUNT BIT(10) +#define DCSR_STOPTIME BIT(9) +#define DCSR_CAUSE_SHIFT 6 +#define DCSR_CAUSE_MASK (0x7u << DCSR_CAUSE_SHIFT) +#define DCSR_V BIT(5) +#define DCSR_MPRVEN BIT(4) +#define DCSR_NMIP BIT(3) +#define DCSR_STEP BIT(2) +#define DCSR_PRV_MASK 0x3u + +#define DCSR_CAUSE_EBREAK 1 +#define DCSR_CAUSE_TRIGGER 2 +#define DCSR_CAUSE_HALTREQ 3 +#define DCSR_CAUSE_STEP 4 +#define DCSR_CAUSE_RESET 5 +#define DCSR_CAUSE_GROUP 6 +#define DCSR_CAUSE_OTHER 7 /* Performance Counters */ #define CSR_MHPMCOUNTER3 0xb03 @@ -740,6 +773,7 @@ typedef enum { #define PTE_RESERVED(svrsw60t59b) \ (svrsw60t59b ? 0x07C0000000000000ULL : 0x1FC0000000000000ULL) /* Reserved bits */ #define PTE_ATTR (PTE_N | PTE_PBMT) /* All attributes bits */ +#define PTE_THEAD_ATTR 0xF800000000000000ULL /* T-Head MAEE attrs */ /* Page table PPN shift amount */ #define PTE_PPN_SHIFT 10 diff --git a/target/riscv/cpu_cfg_fields.h.inc b/target/riscv/cpu_cfg_fields.h.inc index 9eb47af0a7696..eb4adfb32b985 100644 --- a/target/riscv/cpu_cfg_fields.h.inc +++ b/target/riscv/cpu_cfg_fields.h.inc @@ -46,6 +46,8 @@ BOOL_FIELD(ext_zilsd) BOOL_FIELD(ext_zimop) BOOL_FIELD(ext_zcmop) BOOL_FIELD(ext_ztso) +BOOL_FIELD(ext_sdext) +BOOL_FIELD(ext_sdtrig) BOOL_FIELD(ext_smstateen) BOOL_FIELD(ext_sstc) BOOL_FIELD(ext_smcdeleg) @@ -160,7 +162,6 @@ BOOL_FIELD(ext_xlrbr) BOOL_FIELD(big_endian) BOOL_FIELD(mmu) BOOL_FIELD(pmp) -BOOL_FIELD(debug) BOOL_FIELD(misa_w) BOOL_FIELD(short_isa_string) diff --git a/target/riscv/helper.h b/target/riscv/helper.h index 542b7c264fc05..e8fb33f88e07e 100644 --- a/target/riscv/helper.h +++ b/target/riscv/helper.h @@ -133,6 +133,7 @@ DEF_HELPER_6(csrrw_i128, tl, env, int, tl, tl, tl, tl) #ifndef CONFIG_USER_ONLY DEF_HELPER_1(sret, tl, env) DEF_HELPER_1(mret, tl, env) +DEF_HELPER_1(dret, tl, env) DEF_HELPER_1(mnret, tl, env) DEF_HELPER_1(ctr_clear, void, env) DEF_HELPER_1(wfi, void, env) @@ -142,6 +143,8 @@ DEF_HELPER_1(tlb_flush_all, void, env) DEF_HELPER_4(ctr_add_entry, void, env, tl, tl, tl) /* Native Debug */ DEF_HELPER_1(itrigger_match, void, env) +DEF_HELPER_1(sdext_step, void, env) +DEF_HELPER_2(sdext_ebreak, void, env, tl) #endif /* Hypervisor functions */ diff --git a/target/riscv/insn32.decode b/target/riscv/insn32.decode index 21272fdb5042b..b06d6b8c20cd7 100644 --- a/target/riscv/insn32.decode +++ b/target/riscv/insn32.decode @@ -118,6 +118,7 @@ sctrclr 000100000100 00000 000 00000 1110011 uret 0000000 00010 00000 000 00000 1110011 sret 0001000 00010 00000 000 00000 1110011 mret 0011000 00010 00000 000 00000 1110011 +dret 0111101 10010 00000 000 00000 1110011 wfi 0001000 00101 00000 000 00000 1110011 sfence_vma 0001001 ..... ..... 000 00000 1110011 @sfence_vma diff --git a/target/riscv/machine.c b/target/riscv/machine.c index 0ab613a298005..cc307c251fcb7 100644 --- a/target/riscv/machine.c +++ b/target/riscv/machine.c @@ -224,14 +224,14 @@ static const VMStateDescription vmstate_kvmtimer = { }; #endif -static bool debug_needed(void *opaque) +static bool sdtrig_needed(void *opaque) { RISCVCPU *cpu = opaque; - return cpu->cfg.debug; + return cpu->cfg.ext_sdtrig; } -static int debug_post_load(void *opaque, int version_id) +static int sdtrig_post_load(void *opaque, int version_id) { RISCVCPU *cpu = opaque; CPURISCVState *env = &cpu->env; @@ -243,12 +243,12 @@ static int debug_post_load(void *opaque, int version_id) return 0; } -static const VMStateDescription vmstate_debug = { - .name = "cpu/debug", - .version_id = 4, - .minimum_version_id = 4, - .needed = debug_needed, - .post_load = debug_post_load, +static const VMStateDescription vmstate_sdtrig = { + .name = "cpu/sdtrig", + .version_id = 2, + .minimum_version_id = 2, + .needed = sdtrig_needed, + .post_load = sdtrig_post_load, .fields = (const VMStateField[]) { VMSTATE_UINT16(env.mcontext, RISCVCPU), VMSTATE_UINT8(env.trigger_cur, RISCVCPU), @@ -265,6 +265,27 @@ static const VMStateDescription vmstate_debug = { } }; +static bool sdext_needed(void *opaque) +{ + RISCVCPU *cpu = opaque; + + return cpu->cfg.ext_sdext; +} + +static const VMStateDescription vmstate_sdext = { + .name = "cpu/sdext", + .version_id = 1, + .minimum_version_id = 1, + .needed = sdext_needed, + .fields = (const VMStateField[]) { + VMSTATE_BOOL_V(env.debug_mode, RISCVCPU, 3), + VMSTATE_UINTTL_V(env.dcsr, RISCVCPU, 3), + VMSTATE_UINTTL_V(env.dpc, RISCVCPU, 3), + VMSTATE_UINTTL_ARRAY_V(env.dscratch, RISCVCPU, 2, 3), + VMSTATE_END_OF_LIST() + } +}; + static int riscv_cpu_post_load(void *opaque, int version_id) { RISCVCPU *cpu = opaque; @@ -457,8 +478,8 @@ static const VMStateDescription vmstate_mseccfg = { const VMStateDescription vmstate_riscv_cpu = { .name = "cpu", - .version_id = 11, - .minimum_version_id = 11, + .version_id = 12, + .minimum_version_id = 12, .post_load = riscv_cpu_post_load, .fields = (const VMStateField[]) { VMSTATE_UINT64_ARRAY(env.gpr, RISCVCPU, 32), @@ -524,7 +545,6 @@ const VMStateDescription vmstate_riscv_cpu = { &vmstate_kvmtimer, #endif &vmstate_envcfg, - &vmstate_debug, &vmstate_smstateen, &vmstate_jvt, &vmstate_elp, @@ -532,6 +552,8 @@ const VMStateDescription vmstate_riscv_cpu = { &vmstate_ctr, &vmstate_sstc, &vmstate_mseccfg, + &vmstate_sdtrig, + &vmstate_sdext, NULL } }; diff --git a/target/riscv/tcg/cpu_helper.c b/target/riscv/tcg/cpu_helper.c index 07d92226527d8..5d39188003bba 100644 --- a/target/riscv/tcg/cpu_helper.c +++ b/target/riscv/tcg/cpu_helper.c @@ -32,6 +32,7 @@ #include "accel/tcg/cpu-loop.h" #include "accel/tcg/cpu-ops.h" #include "trace.h" +#include "cpu_vendorid.h" #include "semihosting/common-semi.h" #include "exec/icount.h" #include "cpu_bits.h" @@ -63,6 +64,11 @@ int riscv_env_mmu_index(CPURISCVState *env, bool ifetch) #endif } +static bool riscv_cpu_has_thead_pte_attrs(CPURISCVState *env) +{ + return riscv_cpu_cfg(env)->mvendorid == THEAD_VENDOR_ID; +} + bool cpu_get_fcfien(CPURISCVState *env) { /* no cfi extension, return false */ @@ -132,6 +138,90 @@ bool riscv_env_smode_dbltrp_enabled(CPURISCVState *env, bool virt) #endif } +#ifndef CONFIG_USER_ONLY +static bool riscv_sdext_enabled(CPURISCVState *env) +{ + return riscv_cpu_cfg(env)->ext_sdext; +} +#endif + +void riscv_cpu_enter_debug_mode(CPURISCVState *env, target_ulong pc, + uint32_t cause) +{ +#ifndef CONFIG_USER_ONLY + if (!riscv_sdext_enabled(env)) { + return; + } + + env->debug_mode = true; + env->dpc = pc & get_xepc_mask(env); + env->dcsr &= ~(DCSR_CAUSE_MASK | DCSR_PRV_MASK | DCSR_V); + env->dcsr |= ((target_ulong)(cause & 0x7)) << DCSR_CAUSE_SHIFT; + env->dcsr |= env->priv & DCSR_PRV_MASK; + if (env->virt_enabled && riscv_has_ext(env, RVH)) { + env->dcsr |= DCSR_V; + } + + if (env_archcpu(env)->cfg.ext_zicfilp) { + if (env->elp) { + env->dcsr |= DCSR_PELP; + } else { + env->dcsr &= ~DCSR_PELP; + } + env->elp = false; + } +#endif +} + +void riscv_cpu_leave_debug_mode(CPURISCVState *env) +{ +#ifndef CONFIG_USER_ONLY + if (!riscv_sdext_enabled(env)) { + return; + } + + target_ulong new_priv = env->dcsr & DCSR_PRV_MASK; + bool new_virt = riscv_has_ext(env, RVH) && (env->dcsr & DCSR_V); + + if (new_priv > PRV_M) { + new_priv = PRV_M; + } + if (new_priv == PRV_M) { + new_virt = false; + } + + if (new_priv == PRV_S && !riscv_has_ext(env, RVS)) { + new_priv = PRV_M; + new_virt = false; + } else if (new_priv == PRV_U && !riscv_has_ext(env, RVU)) { + new_priv = riscv_has_ext(env, RVS) ? PRV_S : PRV_M; + new_virt = false; + } + + env->debug_mode = false; + riscv_cpu_set_mode(env, new_priv, new_virt); + + if (env_archcpu(env)->cfg.ext_zicfilp) { + env->elp = cpu_get_fcfien(env) && (env->dcsr & DCSR_PELP); + env->dcsr &= ~DCSR_PELP; + } + + if (new_priv != PRV_M) { + env->mstatus = set_field(env->mstatus, MSTATUS_MPRV, 0); + } + + if (env_archcpu(env)->cfg.ext_smdbltrp && new_priv != PRV_M) { + env->mstatus = set_field(env->mstatus, MSTATUS_MDT, 0); + } + if (env_archcpu(env)->cfg.ext_ssdbltrp && (new_priv == PRV_U || new_virt)) { + env->mstatus = set_field(env->mstatus, MSTATUS_SDT, 0); + if (new_virt && new_priv == PRV_U) { + env->vsstatus = set_field(env->vsstatus, MSTATUS_SDT, 0); + } + } +#endif +} + /* * Returns the effective PMM field. * @@ -415,6 +505,12 @@ bool riscv_cpu_exec_interrupt(CPUState *cs, int interrupt_request) if (interrupt_request & mask) { RISCVCPU *cpu = RISCV_CPU(cs); CPURISCVState *env = &cpu->env; + + if (cpu->cfg.ext_sdext && !env->debug_mode && + (env->dcsr & DCSR_STEP) && !(env->dcsr & DCSR_STEPIE)) { + return false; + } + int interruptno = riscv_cpu_local_irq_pending(env); if (interruptno >= 0) { cs->exception_index = RISCV_EXCP_INT_FLAG | interruptno; @@ -1124,6 +1220,7 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, bool svadu = riscv_cpu_cfg(env)->ext_svadu; bool adue = svadu ? env->menvcfg & MENVCFG_ADUE : !svade; bool svrsw60t59b = riscv_cpu_cfg(env)->ext_svrsw60t59b; + bool thead_pte_attrs = riscv_cpu_has_thead_pte_attrs(env); if (first_stage && two_stage && env->virt_enabled) { pbmte = pbmte && (env->henvcfg & HENVCFG_PBMTE); @@ -1131,7 +1228,7 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, } int ptshift; - target_ulong pte; + target_ulong pte, pte_flags; hwaddr pte_addr; const hwaddr base_root = base; const bool be = mo_endian_env(env) == MO_BE; @@ -1209,17 +1306,27 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, return TRANSLATE_PMA_FAIL; } + /* + * T-Head MAEE uses PTE[63:59] for memory attributes. QEMU does + * not model the cacheability/order side effects, but these bits + * must not be treated as standard reserved/PBMT/NAPOT bits. + */ + pte_flags = pte; + if (thead_pte_attrs) { + pte_flags &= ~PTE_THEAD_ATTR; + } + if (riscv_cpu_sxl(env) == MXL_RV32) { - ppn = pte >> PTE_PPN_SHIFT; + ppn = pte_flags >> PTE_PPN_SHIFT; } else { - if (pte & PTE_RESERVED(svrsw60t59b)) { + if (pte_flags & PTE_RESERVED(svrsw60t59b)) { qemu_log_mask(LOG_GUEST_ERROR, "%s: reserved bits set in PTE: " "addr: 0x%" HWADDR_PRIx " pte: 0x" TARGET_FMT_lx "\n", __func__, pte_addr, pte); return TRANSLATE_FAIL; } - if (!pbmte && (pte & PTE_PBMT)) { + if (!pbmte && (pte_flags & PTE_PBMT)) { /* Reserved without Svpbmt. */ qemu_log_mask(LOG_GUEST_ERROR, "%s: PBMT bits set in PTE, " "and Svpbmt extension is disabled: " @@ -1239,7 +1346,7 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, * case the page-fault is mandatory. Make both cases consistent * by also page faulting here. */ - if ((pte & PTE_PBMT) == PTE_PBMT) { + if ((pte_flags & PTE_PBMT) == PTE_PBMT) { qemu_log_mask(LOG_GUEST_ERROR, "%s: PBMT bits 62 and 61 are " "reserved but are set in PTE: " "addr: 0x%" HWADDR_PRIx " pte: 0x" TARGET_FMT_lx "\n", @@ -1247,7 +1354,7 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, return TRANSLATE_FAIL; } - if (!riscv_cpu_cfg(env)->ext_svnapot && (pte & PTE_N)) { + if (!riscv_cpu_cfg(env)->ext_svnapot && (pte_flags & PTE_N)) { /* Reserved without Svnapot extension */ qemu_log_mask(LOG_GUEST_ERROR, "%s: N bit set in PTE, " "and Svnapot extension is disabled: " @@ -1256,19 +1363,25 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, return TRANSLATE_FAIL; } - ppn = (pte & (target_ulong)PTE_PPN_MASK) >> PTE_PPN_SHIFT; + ppn = (pte_flags & (target_ulong)PTE_PPN_MASK) >> PTE_PPN_SHIFT; } - if (!(pte & PTE_V)) { + if (!(pte_flags & PTE_V)) { /* Invalid PTE */ return TRANSLATE_FAIL; } - if (pte & (PTE_R | PTE_W | PTE_X)) { + if (pte_flags & (PTE_R | PTE_W | PTE_X)) { goto leaf; } - if (pte & (PTE_D | PTE_A | PTE_U | PTE_ATTR)) { + target_ulong non_leaf_reserved = PTE_D | PTE_A | PTE_U | PTE_ATTR; + + if (thead_pte_attrs) { + non_leaf_reserved &= ~(PTE_D | PTE_A); + } + + if (pte_flags & non_leaf_reserved) { /* D, A, and U bits are reserved in non-leaf/inner PTEs */ qemu_log_mask(LOG_GUEST_ERROR, "%s: D, A, or U bits set in non-leaf PTE: " "addr: 0x%" HWADDR_PRIx " pte: 0x" TARGET_FMT_lx "\n", @@ -1290,7 +1403,7 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, __func__, pte_addr, pte); return TRANSLATE_FAIL; } - if (!pbmte && (pte & PTE_PBMT)) { + if (!pbmte && (pte_flags & PTE_PBMT)) { /* Reserved without Svpbmt. */ qemu_log_mask(LOG_GUEST_ERROR, "%s: PBMT bits set in PTE, " "and Svpbmt extension is disabled: " @@ -1308,7 +1421,7 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, * * Raise a fault if 62-61 (i.e. PTE_PBMT) are set. */ - if ((pte & PTE_PBMT) == PTE_PBMT) { + if ((pte_flags & PTE_PBMT) == PTE_PBMT) { qemu_log_mask(LOG_GUEST_ERROR, "%s: PBMT bits 62 and 61 are " "reserved but are set in leaf PTE: " "addr: 0x%" HWADDR_PRIx " pte: 0x" TARGET_FMT_lx "\n", @@ -1316,7 +1429,7 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, return TRANSLATE_FAIL; } - target_ulong rwx = pte & (PTE_R | PTE_W | PTE_X); + target_ulong rwx = pte_flags & (PTE_R | PTE_W | PTE_X); /* Check for reserved combinations of RWX flags. */ switch (rwx) { case PTE_W | PTE_X: @@ -1382,7 +1495,7 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, prot |= PAGE_EXEC; } - if (pte & PTE_U) { + if (pte_flags & PTE_U) { if (mode != PRV_U) { if (!mmuidx_sum(mmu_idx)) { return TRANSLATE_FAIL; @@ -1411,8 +1524,8 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, */ if (adue) { updated_pte |= PTE_A | (access_type == MMU_DATA_STORE ? PTE_D : 0); - } else if (!(pte & PTE_A) || - (access_type == MMU_DATA_STORE && !(pte & PTE_D))) { + } else if (!(pte_flags & PTE_A) || + (access_type == MMU_DATA_STORE && !(pte_flags & PTE_D))) { return TRANSLATE_FAIL; } @@ -1473,7 +1586,7 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, /* For superpage mappings, make a fake leaf PTE for the TLB's benefit. */ target_ulong vpn = addr >> PGSHIFT; - if (riscv_cpu_cfg(env)->ext_svnapot && (pte & PTE_N)) { + if (riscv_cpu_cfg(env)->ext_svnapot && (pte_flags & PTE_N)) { napot_bits = ctzl(ppn) + 1; if ((i != (levels - 1)) || (napot_bits != 4)) { return TRANSLATE_FAIL; @@ -1490,7 +1603,7 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, * already dirty, so that we TLB miss on later writes to update * the dirty bit. */ - if (access_type != MMU_DATA_STORE && !(pte & PTE_D)) { + if (access_type != MMU_DATA_STORE && !(pte_flags & PTE_D)) { prot &= ~PAGE_WRITE; } *ret_prot = prot; diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c index 36f2004bc5606..8491098891334 100644 --- a/target/riscv/tcg/csr.c +++ b/target/riscv/tcg/csr.c @@ -804,7 +804,7 @@ static RISCVException have_mseccfg(CPURISCVState *env, int csrno) static RISCVException debug(CPURISCVState *env, int csrno) { - if (riscv_cpu_cfg(env)->debug) { + if (riscv_cpu_cfg(env)->ext_sdtrig) { return RISCV_EXCP_NONE; } @@ -3198,6 +3198,126 @@ static RISCVException write_mtval(CPURISCVState *env, int csrno, return RISCV_EXCP_NONE; } +#if !defined(CONFIG_USER_ONLY) +static RISCVException sdext(CPURISCVState *env, int csrno) +{ + if (!riscv_cpu_cfg(env)->ext_sdext) { + return RISCV_EXCP_ILLEGAL_INST; + } + + if (!env->debug_mode && !env->debugger) { + return RISCV_EXCP_ILLEGAL_INST; + } + + return RISCV_EXCP_NONE; +} + +static target_ulong dcsr_visible_mask(CPURISCVState *env) +{ + target_ulong mask = (target_ulong)-1; + RISCVCPU *cpu = env_archcpu(env); + + if (!riscv_has_ext(env, RVH)) { + mask &= ~(DCSR_EBREAKVS | DCSR_EBREAKVU | DCSR_V); + } + if (!riscv_has_ext(env, RVS)) { + mask &= ~DCSR_EBREAKS; + } + if (!riscv_has_ext(env, RVU)) { + mask &= ~DCSR_EBREAKU; + } + if (!cpu->cfg.ext_zicfilp) { + mask &= ~DCSR_PELP; + } + if (!cpu->cfg.ext_smdbltrp) { + mask &= ~DCSR_CETRIG; + } + + return mask; +} + +static RISCVException read_dcsr(CPURISCVState *env, int csrno, + target_ulong *val) +{ + *val = env->dcsr & dcsr_visible_mask(env); + return RISCV_EXCP_NONE; +} + +static target_ulong dcsr_writable_mask(CPURISCVState *env) +{ + target_ulong mask = DCSR_EBREAKM | DCSR_EBREAKS | DCSR_EBREAKU | + DCSR_STEPIE | DCSR_STOPCOUNT | DCSR_STOPTIME | + DCSR_STEP | DCSR_PRV_MASK; + RISCVCPU *cpu = env_archcpu(env); + + mask |= DCSR_MPRVEN; + + if (riscv_has_ext(env, RVH)) { + mask |= DCSR_EBREAKVS | DCSR_EBREAKVU | DCSR_V; + } + if (riscv_has_ext(env, RVS)) { + mask |= DCSR_EBREAKS; + } + if (riscv_has_ext(env, RVU)) { + mask |= DCSR_EBREAKU; + } + if (cpu->cfg.ext_zicfilp) { + mask |= DCSR_PELP; + } + if (cpu->cfg.ext_smdbltrp) { + mask |= DCSR_CETRIG; + } + + return mask; +} + +static RISCVException write_dcsr(CPURISCVState *env, int csrno, + target_ulong val, uintptr_t ra) +{ + target_ulong mask = dcsr_writable_mask(env); + target_ulong new_val = env->dcsr; + + new_val &= ~mask; + new_val |= val & mask; + new_val &= ~DCSR_XDEBUGVER_MASK; + new_val |= DCSR_DEBUGVER(4); + env->dcsr = new_val; + return RISCV_EXCP_NONE; +} + +static RISCVException read_dpc(CPURISCVState *env, int csrno, + target_ulong *val) +{ + *val = env->dpc & get_xepc_mask(env); + return RISCV_EXCP_NONE; +} + +static RISCVException write_dpc(CPURISCVState *env, int csrno, + target_ulong val, uintptr_t ra) +{ + env->dpc = val & get_xepc_mask(env); + return RISCV_EXCP_NONE; +} + +static RISCVException read_dscratch(CPURISCVState *env, int csrno, + target_ulong *val) +{ + int index = (csrno == CSR_DSCRATCH1) ? 1 : 0; + + *val = env->dscratch[index]; + return RISCV_EXCP_NONE; +} + +static RISCVException write_dscratch(CPURISCVState *env, int csrno, + target_ulong val, uintptr_t ra) +{ + int index = (csrno == CSR_DSCRATCH1) ? 1 : 0; + + env->dscratch[index] = val; + return RISCV_EXCP_NONE; +} +#endif /* !CONFIG_USER_ONLY */ + /* Execution environment configuration setup */ static RISCVException read_menvcfg(CPURISCVState *env, int csrno, target_ulong *val) @@ -6417,6 +6537,12 @@ riscv_csr_operations csr_ops[CSR_TABLE_SIZE] = { [CSR_TDATA3] = { "tdata3", debug, read_tdata, write_tdata }, [CSR_TINFO] = { "tinfo", debug, read_tinfo, write_ignore }, [CSR_MCONTEXT] = { "mcontext", debug, read_mcontext, write_mcontext }, +#if !defined(CONFIG_USER_ONLY) + [CSR_DCSR] = { "dcsr", sdext, read_dcsr, write_dcsr }, + [CSR_DPC] = { "dpc", sdext, read_dpc, write_dpc }, + [CSR_DSCRATCH] = { "dscratch0", sdext, read_dscratch, write_dscratch }, + [CSR_DSCRATCH1] = { "dscratch1", sdext, read_dscratch, write_dscratch }, +#endif [CSR_MCTRCTL] = { "mctrctl", ctr_mmode, NULL, NULL, rmw_xctrctl }, [CSR_SCTRCTL] = { "sctrctl", ctr_smode, NULL, NULL, rmw_xctrctl }, diff --git a/target/riscv/tcg/debug.c b/target/riscv/tcg/debug.c index 3c0fe701010c0..8e174fb0ff001 100644 --- a/target/riscv/tcg/debug.c +++ b/target/riscv/tcg/debug.c @@ -27,6 +27,7 @@ #include "qemu/log.h" #include "qapi/error.h" #include "qemu/error-report.h" +#include "accel/tcg/cpu-loop.h" #include "cpu.h" #include "target/riscv/tcg/debug.h" #include "trace.h" @@ -112,6 +113,8 @@ static trigger_action_t get_trigger_action(CPURISCVState *env, action = (tdata1 & TYPE6_ACTION) >> 12; break; case TRIGGER_TYPE_INST_CNT: + action = tdata1 & ITRIGGER_ACTION; + break; case TRIGGER_TYPE_INT: case TRIGGER_TYPE_EXCP: case TRIGGER_TYPE_EXT_SRC: @@ -282,6 +285,7 @@ static target_ulong textra_validate(CPURISCVState *env, target_ulong tdata3) static void do_trigger_action(CPURISCVState *env, target_ulong trigger_index) { + CPUState *cs = env_cpu(env); trigger_action_t action = get_trigger_action(env, trigger_index); switch (action) { @@ -291,6 +295,21 @@ static void do_trigger_action(CPURISCVState *env, target_ulong trigger_index) riscv_raise_exception(env, RISCV_EXCP_BREAKPOINT, 0); break; case DBG_ACTION_DBG_MODE: + if (!env_archcpu(env)->cfg.ext_sdext) { + qemu_log_mask(LOG_GUEST_ERROR, + "trigger action=debug mode requires Sdext\n"); + riscv_raise_exception(env, RISCV_EXCP_BREAKPOINT, 0); + } + riscv_cpu_enter_debug_mode(env, env->pc, DCSR_CAUSE_TRIGGER); + /* + * If this came from the Trigger Module's CPU breakpoint/watchpoint, + * we're already returning via EXCP_DEBUG. Otherwise, stop now. + */ + if (cs->exception_index != EXCP_DEBUG) { + cs->exception_index = EXCP_DEBUG; + cpu_loop_exit_restore(cs, GETPC()); + } + break; case DBG_ACTION_TRACE0: case DBG_ACTION_TRACE1: case DBG_ACTION_TRACE2: @@ -443,6 +462,7 @@ static target_ulong type2_mcontrol_validate(CPURISCVState *env, { target_ulong val; uint32_t size; + uint32_t action; /* validate the generic part first */ val = tdata1_validate(env, ctrl, TRIGGER_TYPE_AD_MATCH); @@ -450,11 +470,25 @@ static target_ulong type2_mcontrol_validate(CPURISCVState *env, /* validate unimplemented (always zero) bits */ warn_always_zero_bit(ctrl, TYPE2_MATCH, "match"); warn_always_zero_bit(ctrl, TYPE2_CHAIN, "chain"); - warn_always_zero_bit(ctrl, TYPE2_ACTION, "action"); warn_always_zero_bit(ctrl, TYPE2_TIMING, "timing"); warn_always_zero_bit(ctrl, TYPE2_SELECT, "select"); warn_always_zero_bit(ctrl, TYPE2_HIT, "hit"); + action = (ctrl & TYPE2_ACTION) >> 12; + if (action == DBG_ACTION_BP) { + val |= ctrl & TYPE2_ACTION; + } else if (action == DBG_ACTION_DBG_MODE) { + if (env_archcpu(env)->cfg.ext_sdext) { + val |= ctrl & TYPE2_ACTION; + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "trigger action=debug mode requires Sdext\n"); + } + } else { + qemu_log_mask(LOG_UNIMP, "trigger action: %u is not supported\n", + action); + } + /* validate size encoding */ size = type2_breakpoint_size(env, ctrl); if (access_size[size] == -1) { @@ -571,6 +605,7 @@ static target_ulong type6_mcontrol6_validate(CPURISCVState *env, { target_ulong val; uint32_t size; + uint32_t action; /* validate the generic part first */ val = tdata1_validate(env, ctrl, TRIGGER_TYPE_AD_MATCH6); @@ -578,11 +613,25 @@ static target_ulong type6_mcontrol6_validate(CPURISCVState *env, /* validate unimplemented (always zero) bits */ warn_always_zero_bit(ctrl, TYPE6_MATCH, "match"); warn_always_zero_bit(ctrl, TYPE6_CHAIN, "chain"); - warn_always_zero_bit(ctrl, TYPE6_ACTION, "action"); warn_always_zero_bit(ctrl, TYPE6_TIMING, "timing"); warn_always_zero_bit(ctrl, TYPE6_SELECT, "select"); warn_always_zero_bit(ctrl, TYPE6_HIT, "hit"); + action = (ctrl & TYPE6_ACTION) >> 12; + if (action == DBG_ACTION_BP) { + val |= ctrl & TYPE6_ACTION; + } else if (action == DBG_ACTION_DBG_MODE) { + if (env_archcpu(env)->cfg.ext_sdext) { + val |= ctrl & TYPE6_ACTION; + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "trigger action=debug mode requires Sdext\n"); + } + } else { + qemu_log_mask(LOG_UNIMP, "trigger action: %u is not supported\n", + action); + } + /* validate size encoding */ size = extract32(ctrl, 16, 4); if (access_size[size] == -1) { @@ -921,6 +970,7 @@ target_ulong tinfo_csr_read(CPURISCVState *env) { /* assume all triggers support the same types of triggers */ return BIT(TRIGGER_TYPE_AD_MATCH) | + BIT(TRIGGER_TYPE_INST_CNT) | BIT(TRIGGER_TYPE_AD_MATCH6); } @@ -929,6 +979,11 @@ void riscv_cpu_debug_excp_handler(CPUState *cs) RISCVCPU *cpu = RISCV_CPU(cs); CPURISCVState *env = &cpu->env; + /* Triggers must not match or fire while in Debug Mode. */ + if (env->debug_mode) { + return; + } + if (cs->watchpoint_hit) { if (cs->watchpoint_hit->flags & BP_CPU) { do_trigger_action(env, DBG_ACTION_BP); diff --git a/target/riscv/tcg/insn_trans/trans_privileged.c.inc b/target/riscv/tcg/insn_trans/trans_privileged.c.inc index a8eaccef67e77..81ea98a55aa27 100644 --- a/target/riscv/tcg/insn_trans/trans_privileged.c.inc +++ b/target/riscv/tcg/insn_trans/trans_privileged.c.inc @@ -68,9 +68,15 @@ static bool trans_ebreak(DisasContext *ctx, arg_ebreak *a) if (pre == 0x01f01013 && ebreak == 0x00100073 && post == 0x40705013) { generate_exception(ctx, RISCV_EXCP_SEMIHOST); } else { +#ifndef CONFIG_USER_ONLY + gen_update_pc(ctx, 0); + gen_helper_sdext_ebreak(tcg_env, tcg_constant_tl(ebreak_addr)); + ctx->base.is_jmp = DISAS_NORETURN; +#else tcg_gen_st_i64(tcg_constant_i64(ebreak_addr), tcg_env, - offsetof(CPURISCVState, badaddr)); + offsetof(CPURISCVState, badaddr)); generate_exception(ctx, RISCV_EXCP_BREAKPOINT); +#endif } return true; } @@ -125,6 +131,24 @@ static bool trans_mret(DisasContext *ctx, arg_mret *a) #endif } +static bool trans_dret(DisasContext *ctx, arg_dret *a) +{ +#ifndef CONFIG_USER_ONLY + if (!ctx->cfg_ptr->ext_sdext) { + return false; + } + decode_save_opc(ctx, 0); + translator_io_start(&ctx->base); + gen_update_pc(ctx, 0); + gen_helper_dret(cpu_pc, tcg_env); + exit_tb(ctx); /* no chaining */ + ctx->base.is_jmp = DISAS_NORETURN; + return true; +#else + return false; +#endif +} + static bool trans_mnret(DisasContext *ctx, arg_mnret *a) { #ifndef CONFIG_USER_ONLY diff --git a/target/riscv/tcg/op_helper.c b/target/riscv/tcg/op_helper.c index ba3c7da375a2f..764e86ee22633 100644 --- a/target/riscv/tcg/op_helper.c +++ b/target/riscv/tcg/op_helper.c @@ -457,6 +457,72 @@ target_ulong helper_mret(CPURISCVState *env) return retpc; } +target_ulong helper_dret(CPURISCVState *env) +{ + uintptr_t ra = GETPC(); +#ifdef CONFIG_USER_ONLY + riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, ra); + return 0; +#else + if (!riscv_cpu_cfg(env)->ext_sdext || !env->debug_mode) { + riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, ra); + } + target_ulong retpc = env->dpc & get_xepc_mask(env); + riscv_cpu_leave_debug_mode(env); + return retpc; +#endif +} + +void helper_sdext_step(CPURISCVState *env) +{ +#ifndef CONFIG_USER_ONLY + CPUState *cs = env_cpu(env); + + if (!riscv_cpu_cfg(env)->ext_sdext || env->debug_mode || + !(env->dcsr & DCSR_STEP)) { + return; + } + + riscv_cpu_enter_debug_mode(env, env->pc, DCSR_CAUSE_STEP); + cs->exception_index = EXCP_DEBUG; + cpu_loop_exit_restore(cs, GETPC()); +#endif +} + +void helper_sdext_ebreak(CPURISCVState *env, target_ulong pc) +{ + CPUState *cs = env_cpu(env); + bool enter_debug = false; + + if (riscv_cpu_cfg(env)->ext_sdext && !env->debug_mode) { + if (env->virt_enabled) { + if (env->priv == PRV_S) { + enter_debug = env->dcsr & DCSR_EBREAKVS; + } else if (env->priv == PRV_U) { + enter_debug = env->dcsr & DCSR_EBREAKVU; + } + } else { + if (env->priv == PRV_M) { + enter_debug = env->dcsr & DCSR_EBREAKM; + } else if (env->priv == PRV_S) { + enter_debug = env->dcsr & DCSR_EBREAKS; + } else if (env->priv == PRV_U) { + enter_debug = env->dcsr & DCSR_EBREAKU; + } + } + } + + env->badaddr = pc; + + if (enter_debug) { + riscv_cpu_enter_debug_mode(env, pc, DCSR_CAUSE_EBREAK); + cs->exception_index = EXCP_DEBUG; + cpu_loop_exit_restore(cs, GETPC()); + } + + riscv_raise_exception(env, RISCV_EXCP_BREAKPOINT, GETPC()); +} + target_ulong helper_mnret(CPURISCVState *env) { target_ulong retpc = env->mnepc; @@ -555,6 +621,10 @@ void helper_wfi(CPURISCVState *env) (prv_u || (prv_s && get_field(env->hstatus, HSTATUS_VTW)))) { riscv_raise_exception(env, RISCV_EXCP_VIRT_INSTRUCTION_FAULT, GETPC()); } else { + if (riscv_cpu_cfg(env)->ext_sdext && !env->debug_mode && + (env->dcsr & DCSR_STEP)) { + return; + } cs->halted = 1; cs->exception_index = EXCP_HLT; cpu_loop_exit(cs); diff --git a/target/riscv/tcg/tcg-cpu.c b/target/riscv/tcg/tcg-cpu.c index 4af5cd9c7314a..0af3843e56af5 100644 --- a/target/riscv/tcg/tcg-cpu.c +++ b/target/riscv/tcg/tcg-cpu.c @@ -182,7 +182,7 @@ static TCGTBCPUState riscv_get_tb_cpu_state(CPUState *cs) ? EXT_STATUS_DIRTY : EXT_STATUS_DISABLED; } - if (cpu->cfg.debug && !icount_enabled()) { + if (cpu->cfg.ext_sdtrig && !icount_enabled()) { flags = FIELD_DP32(flags, TB_FLAGS, ITRIGGER, env->itrigger_enabled); } #endif @@ -197,6 +197,11 @@ static TCGTBCPUState riscv_get_tb_cpu_state(CPUState *cs) ext_flags = FIELD_DP64(ext_flags, EXT_TB_FLAGS, MISA_EXT, env->misa_ext); ext_flags = FIELD_DP64(ext_flags, EXT_TB_FLAGS, BIG_ENDIAN, mo_endian_env(env) == MO_BE); +#ifndef CONFIG_USER_ONLY + if (cpu->cfg.ext_sdext && !env->debug_mode && (env->dcsr & DCSR_STEP)) { + ext_flags = FIELD_DP64(ext_flags, EXT_TB_FLAGS, SDEXT_STEP, 1); + } +#endif return (TCGTBCPUState){ .pc = env->xl == MXL_RV32 ? env->pc & UINT32_MAX : env->pc, @@ -270,6 +275,19 @@ static vaddr riscv_pointer_wrap(CPUState *cs, int mmu_idx, } return extract64(result, 0, 64 - pm_len); } + +static void riscv_cpu_exec_enter(CPUState *cs) +{ + RISCVCPU *cpu = RISCV_CPU(cs); + CPURISCVState *env = &cpu->env; + + if (!cpu->cfg.ext_sdext || !env->debug_mode) { + return; + } + target_ulong pc = env->dpc; + riscv_cpu_leave_debug_mode(env); + env->pc = pc; +} #endif const TCGCPUOps riscv_tcg_ops = { @@ -286,6 +304,7 @@ const TCGCPUOps riscv_tcg_ops = { #ifndef CONFIG_USER_ONLY .tlb_fill = riscv_cpu_tlb_fill, .pointer_wrap = riscv_pointer_wrap, + .cpu_exec_enter = riscv_cpu_exec_enter, .cpu_exec_interrupt = riscv_cpu_exec_interrupt, .cpu_exec_halt = riscv_cpu_has_work, .cpu_exec_reset = cpu_reset, diff --git a/target/riscv/tcg/th_csr.c b/target/riscv/tcg/th_csr.c index e6f642991ced8..4c491214b4bee 100644 --- a/target/riscv/tcg/th_csr.c +++ b/target/riscv/tcg/th_csr.c @@ -38,6 +38,7 @@ #define CSR_TH_MCINDEX 0x7d3 #define CSR_TH_MCDATA0 0x7d4 #define CSR_TH_MCDATA1 0x7d5 +#define CSR_TH_MCPER 0x7d9 #define CSR_TH_MSMPR 0x7f3 #define CSR_TH_CPUID 0xfc0 #define CSR_TH_MAPBADDR 0xfc1 @@ -124,8 +125,11 @@ static bool test_thead_mvendorid(RISCVCPU *cpu) static RISCVException read_th_mxstatus(CPURISCVState *env, int csrno, target_ulong *val) { - /* We don't set MAEE here, because QEMU does not implement MAEE. */ - *val = TH_MXSTATUS_UCME | TH_MXSTATUS_THEADISAEE; + /* + * QEMU accepts T-Head MAEE PTE attribute bits for translation, but + * does not model their cacheability/order side effects. + */ + *val = TH_MXSTATUS_UCME | TH_MXSTATUS_MAEE | TH_MXSTATUS_THEADISAEE; return RISCV_EXCP_NONE; } @@ -139,8 +143,11 @@ static RISCVException read_unimp_th_csr(CPURISCVState *env, int csrno, static RISCVException read_th_sxstatus(CPURISCVState *env, int csrno, target_ulong *val) { - /* We don't set MAEE here, because QEMU does not implement MAEE. */ - *val = TH_SXSTATUS_UCME | TH_SXSTATUS_THEADISAEE; + /* + * QEMU accepts T-Head MAEE PTE attribute bits for translation, but + * does not model their cacheability/order side effects. + */ + *val = TH_SXSTATUS_UCME | TH_SXSTATUS_MAEE | TH_SXSTATUS_THEADISAEE; return RISCV_EXCP_NONE; } @@ -210,6 +217,11 @@ const RISCVCSR th_csr_list[] = { .insertion_test = test_thead_mvendorid, .csr_ops = { "th.mcdata1", mmode, read_unimp_th_csr } }, + { + .csrno = CSR_TH_MCPER, + .insertion_test = test_thead_mvendorid, + .csr_ops = { "th.mcper", mmode, read_unimp_th_csr } + }, { .csrno = CSR_TH_MSMPR, .insertion_test = test_thead_mvendorid, diff --git a/target/riscv/tcg/translate.c b/target/riscv/tcg/translate.c index 9684dbe7528e3..1a38d6aeb56ff 100644 --- a/target/riscv/tcg/translate.c +++ b/target/riscv/tcg/translate.c @@ -112,6 +112,8 @@ typedef struct DisasContext { bool ztso; /* Use icount trigger for native debug */ bool itrigger; + /* Enter Debug Mode after next instruction (sdext single-step). */ + bool sdext_step; /* FRM is known to contain a valid value. */ bool frm_valid; bool insn_start_updated; @@ -286,6 +288,9 @@ static void lookup_and_goto_ptr(DisasContext *ctx) if (ctx->itrigger) { gen_helper_itrigger_match(tcg_env); } + if (ctx->sdext_step) { + gen_helper_sdext_step(tcg_env); + } #endif tcg_gen_lookup_and_goto_ptr(); } @@ -296,6 +301,9 @@ static void exit_tb(DisasContext *ctx) if (ctx->itrigger) { gen_helper_itrigger_match(tcg_env); } + if (ctx->sdext_step) { + gen_helper_sdext_step(tcg_env); + } #endif tcg_gen_exit_tb(NULL, 0); } @@ -309,7 +317,8 @@ static void gen_goto_tb(DisasContext *ctx, unsigned tb_slot_idx, * Under itrigger, instruction executes one by one like singlestep, * direct block chain benefits will be small. */ - if (translator_use_goto_tb(&ctx->base, dest) && !ctx->itrigger) { + if (translator_use_goto_tb(&ctx->base, dest) && + !ctx->itrigger && !ctx->sdext_step) { /* * For pcrel, the pc must always be up-to-date on entry to * the linked TB, so that it can use simple additions for all @@ -1350,6 +1359,7 @@ static void riscv_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cs) ctx->bcfi_enabled = FIELD_EX32(tb_flags, TB_FLAGS, BCFI_ENABLED); ctx->fcfi_lp_expected = FIELD_EX32(tb_flags, TB_FLAGS, FCFI_LP_EXPECTED); ctx->fcfi_enabled = FIELD_EX32(tb_flags, TB_FLAGS, FCFI_ENABLED); + ctx->sdext_step = FIELD_EX64(ext_tb_flags, EXT_TB_FLAGS, SDEXT_STEP); ctx->zero = tcg_constant_tl(0); ctx->virt_inst_excp = false; ctx->decoders = cpu->decoders; @@ -1402,7 +1412,8 @@ static void riscv_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) /* Only the first insn within a TB is allowed to cross a page boundary. */ if (ctx->base.is_jmp == DISAS_NEXT) { - if (ctx->itrigger || !translator_is_same_page(&ctx->base, ctx->base.pc_next)) { + if (ctx->itrigger || ctx->sdext_step || + !translator_is_same_page(&ctx->base, ctx->base.pc_next)) { ctx->base.is_jmp = DISAS_TOO_MANY; } else { unsigned page_ofs = ctx->base.pc_next & ~TARGET_PAGE_MASK; diff --git a/target/riscv/time_helper.c b/target/riscv/time_helper.c index ede1a4c2a34b9..3275fc84d4945 100644 --- a/target/riscv/time_helper.c +++ b/target/riscv/time_helper.c @@ -23,6 +23,7 @@ #include "hw/intc/riscv_aclint.h" #include "kvm/kvm_riscv.h" #include "system/kvm.h" +#include "system/qtest.h" #include "system/tcg.h" static void riscv_accel_set_irq(RISCVCPU *cpu, int irq, int level) @@ -31,7 +32,7 @@ static void riscv_accel_set_irq(RISCVCPU *cpu, int irq, int level) kvm_riscv_set_irq(cpu, irq, level); } - if (tcg_enabled()) { + if (tcg_enabled() || qtest_enabled()) { riscv_cpu_update_mip(&cpu->env, irq, level); } } @@ -51,13 +52,12 @@ static void riscv_stimer_cb(void *opaque) riscv_accel_set_irq(cpu, MIP_STIP, BOOL_TO_MASK(1)); } -/* - * Called when timecmp is written to update the QEMU timer or immediately - * trigger timer interrupt if mtimecmp <= current timer value. - */ -void riscv_timer_write_timecmp(CPURISCVState *env, QEMUTimer *timer, - uint64_t timecmp, uint64_t delta, - uint32_t timer_irq) +static void riscv_timer_write_timecmp_common(CPURISCVState *env, + QEMUTimer *timer, + uint64_t timecmp, + uint64_t delta, + uint32_t timer_irq, + bool require_sstc) { uint64_t diff, ns_diff, next; RISCVAclintMTimerState *mtimer = env->rdtime_fn_arg; @@ -65,13 +65,18 @@ void riscv_timer_write_timecmp(CPURISCVState *env, QEMUTimer *timer, uint64_t rtc_r; RISCVCPU *cpu; - if (!riscv_cpu_cfg(env)->ext_sstc || !env->rdtime_fn || - !env->rdtime_fn_arg || !get_field(env->menvcfg, MENVCFG_STCE)) { + if (!timer || !env->rdtime_fn || !env->rdtime_fn_arg) { + return; + } + + if (require_sstc && + (!riscv_cpu_cfg(env)->ext_sstc || + !get_field(env->menvcfg, MENVCFG_STCE))) { /* S/VS Timer IRQ depends on sstc extension, rdtime_fn(), and STCE. */ return; } - if (timer_irq == MIP_VSTIP && + if (require_sstc && timer_irq == MIP_VSTIP && (!riscv_has_ext(env, RVH) || !get_field(env->henvcfg, HENVCFG_STCE))) { /* VS Timer IRQ also depends on RVH and henvcfg.STCE. */ return; @@ -157,6 +162,26 @@ void riscv_timer_write_timecmp(CPURISCVState *env, QEMUTimer *timer, timer_mod(timer, next); } +/* + * Called when timecmp is written to update the QEMU timer or immediately + * trigger timer interrupt if mtimecmp <= current timer value. + */ +void riscv_timer_write_timecmp(CPURISCVState *env, QEMUTimer *timer, + uint64_t timecmp, uint64_t delta, + uint32_t timer_irq) +{ + riscv_timer_write_timecmp_common(env, timer, timecmp, delta, timer_irq, + true); +} + +void riscv_timer_write_timecmp_mmio(CPURISCVState *env, QEMUTimer *timer, + uint64_t timecmp, uint64_t delta, + uint32_t timer_irq) +{ + riscv_timer_write_timecmp_common(env, timer, timecmp, delta, timer_irq, + false); +} + /* * When disabling xenvcfg.STCE, the S/VS Timer may be disabled at the same time. * It is safe to call this function regardless of whether the timer has been diff --git a/target/riscv/time_helper.h b/target/riscv/time_helper.h index af1f634f89076..169f706393025 100644 --- a/target/riscv/time_helper.h +++ b/target/riscv/time_helper.h @@ -25,6 +25,9 @@ void riscv_timer_write_timecmp(CPURISCVState *env, QEMUTimer *timer, uint64_t timecmp, uint64_t delta, uint32_t timer_irq); +void riscv_timer_write_timecmp_mmio(CPURISCVState *env, QEMUTimer *timer, + uint64_t timecmp, uint64_t delta, + uint32_t timer_irq); void riscv_timer_stce_changed(CPURISCVState *env, bool is_m_mode, bool enable); void riscv_timer_init(RISCVCPU *cpu); diff --git a/tests/docker/common.rc b/tests/docker/common.rc index 79d533ab2e500..318d124658881 100755 --- a/tests/docker/common.rc +++ b/tests/docker/common.rc @@ -50,7 +50,7 @@ requires_binary() configure_qemu() { - config_opts="--enable-werror \ + config_opts="${QEMU_WERROR---enable-werror} \ ${TARGET_LIST:+--target-list=${TARGET_LIST}} \ --prefix=$INSTALL_DIR \ $enable_rust \ diff --git a/tests/functional/aarch64/meson.build b/tests/functional/aarch64/meson.build index e81afd6c39fcf..23a4875dce255 100644 --- a/tests/functional/aarch64/meson.build +++ b/tests/functional/aarch64/meson.build @@ -4,11 +4,14 @@ test_aarch64_timeouts = { 'aspeed_ast2700a1' : 600, 'aspeed_ast2700a2' : 600, 'aspeed_ast2700fc' : 600, + 'ax650x_ubuntu' : 180, 'device_passthrough' : 720, 'imx8mm_evk' : 240, 'imx8mp_evk' : 240, 'raspi4' : 480, 'reverse_debug' : 180, + 'rk3588_zvm' : 240, + 'rk3588_zvm_linux_guest' : 1200, 'rme_virt' : 1200, 'rme_sbsaref' : 1200, 'sbsaref_alpine' : 1200, @@ -29,6 +32,7 @@ tests_aarch64_system_thorough = [ 'aspeed_ast2700a1', 'aspeed_ast2700a2', 'aspeed_ast2700fc', + 'ax650x_ubuntu', 'device_passthrough', 'hotplug_pci', 'imx8mm_evk', @@ -39,6 +43,8 @@ tests_aarch64_system_thorough = [ 'raspi4', 'replay', 'reverse_debug', + 'rk3588_zvm', + 'rk3588_zvm_linux_guest', 'rme_virt', 'rme_sbsaref', 'sbsaref', diff --git a/tests/functional/aarch64/test_ax650x_ubuntu.py b/tests/functional/aarch64/test_ax650x_ubuntu.py new file mode 100644 index 0000000000000..67c61856dbd76 --- /dev/null +++ b/tests/functional/aarch64/test_ax650x_ubuntu.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# +# Functional test that directly boots an AX650X Ubuntu eMMC image +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset, LinuxKernelTest + + +class AX650XUbuntuMachine(LinuxKernelTest): + + ASSET_URL = ( + 'https://github.com/processmission/qemu/releases/download/' + 'ax650x-ubuntu-22.04-qemu1/' + ) + ASSET_KERNEL = Asset( + ASSET_URL + 'Image-5.15.73-axera', + '9e3dc56e13cb5e786864d3456ceecfe7560d11fd25e9688531f86c0748be9bf8') + ASSET_EMMC = Asset( + ASSET_URL + 'ax650x-ubuntu-22.04-emmc.qcow2', + 'e362cff67d7358bc434394649f91d0b2424156eb9cd3b518438cf32fe3b9800c') + + PARTS = ( + 'mmcblk0:1536K(uboot),1536K(uboot_bk),1M(env),20M(param),' + '6M(logo),1M(dtb),64M(kernel),1M(atf),1M(optee),' + '1M(recovery_dtb),74M(recovery),30380032K(rootfs)' + ) + + def test_aarch64_ax650x_ubuntu_quick_boot(self): + kernel_path = self.ASSET_KERNEL.fetch() + image_path = self.ASSET_EMMC.fetch() + + self.set_machine('ax650x-pyramid') + self.require_accelerator('tcg') + self.vm.set_console() + + kernel_command_line = ( + 'console=ttyS0,115200n8 ' + 'earlycon=uart8250,mmio32,0x2016000 ' + 'root=/dev/mmcblk0p12 rootfstype=ext4 rw rootwait ' + f'blkdevparts={self.PARTS} ' + 'systemd.show_status=yes systemd.log_target=console' + ) + self.vm.add_args( + '-accel', 'tcg,thread=multi', + '-cpu', 'cortex-a55', + '-smp', '8', + '-m', '2G', + '-kernel', kernel_path, + '-append', kernel_command_line, + '-drive', f'file={image_path},if=sd,format=qcow2,snapshot=on', + '-display', 'none', + '-monitor', 'none', + '-no-reboot', + ) + + self.vm.launch() + self.wait_for_console_pattern('DWMAC4/5') + self.wait_for_console_pattern('p12(rootfs)') + self.wait_for_console_pattern( + 'Mounted root (ext4 filesystem) on device 179:12') + self.wait_for_console_pattern('AX650X_UBUNTU_READY') + self.wait_for_console_pattern('AX650X_OS=Ubuntu 22.04.5 LTS') + self.wait_for_console_pattern('AX650X_CPUS=8') + self.wait_for_console_pattern('AX650X_ROOT=/dev/mmcblk0p12 ext4') + self.wait_for_console_pattern('localhost login:') + + +if __name__ == '__main__': + LinuxKernelTest.main() diff --git a/tests/functional/aarch64/test_rk3588_zvm.py b/tests/functional/aarch64/test_rk3588_zvm.py new file mode 100644 index 0000000000000..a2d5f670d539e --- /dev/null +++ b/tests/functional/aarch64/test_rk3588_zvm.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# +# Functional test that boots the ZVM RK3588S ROC PC release fixture. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +from pathlib import Path +import stat +import subprocess + +from qemu_test import QemuSystemTest, skipIfMissingCommands +from qemu_test import wait_for_console_pattern + + +class RK3588ZVM(QemuSystemTest): + + ZVM_IMAGE = ('roms/zvm-rk3588s-roc-pc/' + 'rk3588s-zvm-no-fs.raw.zst') + BOOT_ATTEMPTS = 8 + + def extract_sparse_raw(self, attempt): + source_root = Path(__file__).resolve().parents[3] + compressed = source_root / self.ZVM_IMAGE + raw = self.scratch_file(f'rk3588s-zvm-no-fs-{attempt}.raw') + + subprocess.run(['zstd', '--sparse', '-f', '-d', str(compressed), + '-o', raw], + check=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True) + os.chmod(raw, stat.S_IRUSR | stat.S_IWUSR) + return raw + + def boot_zvm_release(self, attempt): + image_path = self.extract_sparse_raw(attempt) + vm = self.get_vm(name=f'zvm-boot-{attempt}') + + vm.set_console() + vm.add_args('-accel', 'tcg', + '-icount', 'shift=0,sleep=off', + '-smp', '8', + '-m', '1G', + '-drive', + f'if=sd,index=2,file={image_path},format=raw', + '-nic', 'user,model=gmac0', + '-display', 'none') + vm.launch() + + failure = 'SPL: failed to boot from all boot devices' + wait_for_console_pattern(self, 'U-Boot SPL 2026.07-rc4', failure, + vm=vm) + wait_for_console_pattern(self, 'loading ZVM img ...', failure, vm=vm) + wait_for_console_pattern(self, + 'Init virt syscon for ZVM successful.', + failure, vm=vm) + wait_for_console_pattern(self, 'Start VM Successful!', failure, vm=vm) + wait_for_console_pattern(self, + 'zephyr VM 0 created and running', + failure, vm=vm) + + @skipIfMissingCommands('zstd') + def test_aarch64_rk3588s_roc_pc_zvm_release(self): + self.require_accelerator('tcg') + self.set_machine('rk3588s-roc-pc') + + failure = 'SPL: failed to boot from all boot devices' + for attempt in range(1, self.BOOT_ATTEMPTS + 1): + try: + self.boot_zvm_release(attempt) + return + except AssertionError as exc: + if failure not in str(exc) or attempt == self.BOOT_ATTEMPTS: + raise + self.log.warning('ZVM boot attempt %d/%d hit SPL MMC read ' + 'failure; retrying with a fresh raw image', + attempt, self.BOOT_ATTEMPTS) + self.get_vm(name=f'zvm-boot-{attempt}').shutdown() + + +if __name__ == '__main__': + QemuSystemTest.main() diff --git a/tests/functional/aarch64/test_rk3588_zvm_linux_guest.py b/tests/functional/aarch64/test_rk3588_zvm_linux_guest.py new file mode 100644 index 0000000000000..958cc833c7e24 --- /dev/null +++ b/tests/functional/aarch64/test_rk3588_zvm_linux_guest.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python3 +# +# Functional test that assembles the full ZVM RK3588S ROC PC release image +# from remote assets and boots the bundled Linux guest. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import hashlib +import logging +import os +from pathlib import Path +import platform +import struct +import subprocess +import zlib + +from qemu_test import Asset, QemuSystemTest, get_qemu_img, which + + +class RK3588ZVMLinuxGuest(QemuSystemTest): + + ASSET_KERNEL_IMAGES = Asset( + 'https://esnl.hnu.edu.cn/zvm/resource/kernel_images.zip', + '05aa22b772808abef7082d3dfc513ea75b0813f3e0d4f028ad595e2c86fe3ec7') + ASSET_FILESYSTEM_PART1 = Asset( + 'https://esnl.hnu.edu.cn/zvm/resource/file_system_images_part1.zip', + '9a1840d8d486b2de8e8bedc1647cd3c5aa2f622111b45bb9f0d8e1d26b07c5f0') + ASSET_FILESYSTEM_PART2 = Asset( + 'https://esnl.hnu.edu.cn/zvm/resource/file_system_images_part2.zip', + '17b048b0a473dcbd4e5b7f1c5d4f50547ffefe3e040c0b8835e55cba7f45b823') + + SOURCE_ROOT = Path(__file__).resolve().parents[3] + ZVM_NO_FS_IMAGE = (SOURCE_ROOT / 'roms' / 'zvm-rk3588s-roc-pc' / + 'rk3588s-zvm-no-fs.raw.zst') + MAKE_IMAGE_SCRIPT = (SOURCE_ROOT / 'scripts' / 'zvm' / + 'rk3588s-roc-pc-make-zvm-image.sh') + + UBOOT_ROCKCHIP_OFFSET = 64 * 512 + UBOOT_ROCKCHIP_SIZE = 9456128 + UBOOT_ROCKCHIP_SHA256 = \ + '6cd4b3d1931a252d564c88220e071eba57009fff49620b9520f33e8c58f80fb5' + BOOT_SCRIPT_SHA256 = \ + '5402d2ea0e5a9a65e577799283ff46fd596914351769c3015d30d6285c787171' + BOOT_SCRIPT_TIMESTAMP = 0x6a2fe158 + + KERNEL_HASHES = { + 'boot.scr': + '3f002128aef0fd5d9e5792e2643697b027dda1767efa43dd718439b13d4a2a63', + 'boot.txt': + 'c183dd5e0676a714c7a03781d0788961d797856a529e857e8eeed6ef18bda557', + 'nrtos_images.bin': + '0f40c4e0d07aad5b5bef2b7528dbf3c395f95d0129a0f0a91d81ca9bbfb79a26', + 'raw_kernel_images/Image': + '0eca25c10fb39e1200ccd097f6de2cfc767dcd0abd0360645fe816d17869f2e9', + 'zvm_release_rk3588v2.bin': + 'e8851ef0a8f413d98554218248411162024d5e9ba47f90f4aa0908710d193380', + } + FILESYSTEM_HASHES = { + 'android_vda.img': + '8c1bc5c3568fcbc74c04f8862dea1687baf93e6dcec0c0b18aef494bf2d00c10', + 'oh_vda.bin': + '7e50e7ac8cd5b90d087f09b0c4ebfacb13b9b4aeb7651e191d376b595bf6d9e2', + 'linux_diskimg.bin': + '2a59a5503a3e43184adccdb6288a9c9b26599882eacbe387b5db3d408f85891f', + } + + BOOT_ATTEMPTS = 8 + SPL_FAILURE = 'SPL: failed to boot from all boot devices' + BOOT_FAILURES = ( + SPL_FAILURE, + 'load_linux_image_form_disk error', + 'load linux images error', + 'Failed to run linux VM 1', + 'Timeout waiting for vm: linux_os-1 start', + ) + + def local_release_dirs(self): + release_root = os.getenv('ZVM_RK3588_RELEASE_DIR') + kernel_dir = os.getenv('ZVM_RK3588_KERNEL_DIR') + filesystem_dir = os.getenv('ZVM_RK3588_FILESYSTEM_DIR') + + if release_root: + root = Path(release_root) + if not kernel_dir: + kernel_dir = str(root / 'kernel_images') + if not filesystem_dir: + for candidate in ( + root / 'file_system_images_official' / 'image', + root / 'file_system_images' / 'image', + root / 'image'): + if candidate.is_dir(): + filesystem_dir = str(candidate) + break + + if not kernel_dir or not filesystem_dir: + return None + + kernel_path = Path(kernel_dir) + filesystem_path = Path(filesystem_dir) + if not kernel_path.is_dir() or not filesystem_path.is_dir(): + return None + return kernel_path, filesystem_path + + def assets_available(self): + if self.local_release_dirs(): + return True + return super().assets_available() + + @staticmethod + def sha256_file(path): + digest = hashlib.sha256() + with open(path, 'rb') as stream: + for chunk in iter(lambda: stream.read(1 << 20), b''): + digest.update(chunk) + return digest.hexdigest() + + def require_commands(self, commands): + missing = [command for command in commands if not which(command)] + if missing: + self.skipTest('required command(s) not installed: ' + + ', '.join(missing)) + + def require_image_tools(self): + self.require_commands(('dd', 'python3', 'truncate', 'zstd')) + + host_os = platform.system() + if host_os == 'Linux': + self.require_commands(('mkfs.vfat', 'mcopy')) + elif host_os == 'Darwin': + self.require_commands(('hdiutil', 'newfs_msdos')) + else: + self.skipTest(f'unsupported host OS for image assembly: {host_os}') + + def sevenzip(self): + for command in ('7z', '7zz'): + path = which(command) + if path: + return path + self.skipTest('required command(s) not installed: 7z or 7zz') + + def validate_release_inputs(self, kernel_dir, filesystem_dir): + for name, expected in self.KERNEL_HASHES.items(): + path = kernel_dir / name + self.assertTrue(path.is_file(), f'missing {path}') + self.assertEqual(expected, self.sha256_file(path), + f'unexpected hash for {path}') + + for name, expected in self.FILESYSTEM_HASHES.items(): + path = filesystem_dir / name + self.assertTrue(path.is_file(), f'missing {path}') + self.assertEqual(expected, self.sha256_file(path), + f'unexpected hash for {path}') + + def prepare_release_dirs(self): + local_dirs = self.local_release_dirs() + if local_dirs: + kernel_dir, filesystem_dir = local_dirs + self.validate_release_inputs(kernel_dir, filesystem_dir) + return kernel_dir, filesystem_dir + + release_dir = Path(self.scratch_file('zvm-release')) + filesystem_split_dir = Path(self.scratch_file('zvm-filesystems')) + release_dir.mkdir(parents=True, exist_ok=True) + filesystem_split_dir.mkdir(parents=True, exist_ok=True) + + self.archive_extract(self.ASSET_KERNEL_IMAGES, sub_dir='zvm-release', + format='zip') + self.archive_extract(self.ASSET_FILESYSTEM_PART1, + sub_dir='zvm-filesystems', format='zip') + self.archive_extract(self.ASSET_FILESYSTEM_PART2, + sub_dir='zvm-filesystems', format='zip') + + subprocess.run([self.sevenzip(), 'x', '-y', 'image.7z.001'], + cwd=filesystem_split_dir, check=True, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + text=True) + + kernel_dir = release_dir / 'kernel_images' + filesystem_dir = filesystem_split_dir / 'image' + self.validate_release_inputs(kernel_dir, filesystem_dir) + return kernel_dir, filesystem_dir + + def extract_uboot_rockchip(self): + raw_path = self.scratch_file('rk3588s-zvm-no-fs.raw') + subprocess.run(['zstd', '--sparse', '-f', '-d', + str(self.ZVM_NO_FS_IMAGE), '-o', raw_path], + check=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True) + + uboot_path = Path(self.scratch_file('u-boot-rockchip.bin')) + with open(raw_path, 'rb') as raw, uboot_path.open('wb') as uboot: + raw.seek(self.UBOOT_ROCKCHIP_OFFSET) + data = raw.read(self.UBOOT_ROCKCHIP_SIZE) + self.assertEqual(self.UBOOT_ROCKCHIP_SIZE, len(data), + 'short u-boot-rockchip.bin extract') + uboot.write(data) + + self.assertEqual(self.UBOOT_ROCKCHIP_SHA256, + self.sha256_file(uboot_path), + f'unexpected hash for {uboot_path}') + return uboot_path + + def generate_boot_script(self, kernel_dir): + boot_txt = kernel_dir / 'boot.txt' + script = boot_txt.read_bytes() + data = struct.pack('>II', len(script), 0) + script + name = b'boot (Auto Generated)' + name = name + b'\0' * (32 - len(name)) + data_crc = zlib.crc32(data) & 0xffffffff + + header = struct.pack('>IIIIIIIBBBB32s', + 0x27051956, 0, self.BOOT_SCRIPT_TIMESTAMP, + len(data), 0, 0, data_crc, + 5, 2, 6, 0, name) + header_crc = zlib.crc32(header) & 0xffffffff + header = struct.pack('>IIIIIIIBBBB32s', + 0x27051956, header_crc, + self.BOOT_SCRIPT_TIMESTAMP, + len(data), 0, 0, data_crc, + 5, 2, 6, 0, name) + + boot_script = Path(self.scratch_file('boot.scr')) + boot_script.write_bytes(header + data) + self.assertEqual(self.BOOT_SCRIPT_SHA256, + self.sha256_file(boot_script), + f'unexpected hash for {boot_script}') + return boot_script + + def build_card_image(self): + self.require_image_tools() + kernel_dir, filesystem_dir = self.prepare_release_dirs() + uboot_path = self.extract_uboot_rockchip() + boot_script = self.generate_boot_script(kernel_dir) + image_path = self.scratch_file('rk3588s-zvm-linux-guest.raw') + + subprocess.run([str(self.MAKE_IMAGE_SCRIPT), + '--kernel-dir', str(kernel_dir), + '--filesystem-dir', str(filesystem_dir), + '--uboot-rockchip', str(uboot_path), + '--boot-script', str(boot_script), + '--output', image_path, + '--force'], + check=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True) + return image_path + + def image_for_attempt(self, base_image_path, attempt): + image_path = self.scratch_file(f'rk3588s-zvm-linux-guest-{attempt}.raw') + subprocess.run([get_qemu_img(self), 'convert', + '-f', 'raw', + '-O', 'raw', + '-S', '4k', + base_image_path, + image_path], + check=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True) + return image_path + + def wait_for_console_patterns(self, vm, success, failures): + success_bytes = success.encode() + failure_bytes = [(failure, failure.encode()) for failure in failures] + console_logger = logging.getLogger('console') + line = b'' + + while True: + char = vm.console_socket.recv(1) + if not char: + self.fail(f"EOF in console, expected '{success}'") + line += char + + if success_bytes in line: + console_logger.debug(line.decode(errors='replace').strip()) + return + + for failure, failure_b in failure_bytes: + if failure_b in line: + console_logger.debug(line.decode(errors='replace').strip()) + vm.console_socket.close() + self.fail(f"'{failure}' found in console, " + f"expected '{success}'") + + if char == b'\n': + console_logger.debug(line.decode(errors='replace').strip()) + line = b'' + + def boot_zvm_linux_guest(self, base_image_path, attempt): + image_path = self.image_for_attempt(base_image_path, attempt) + vm = self.get_vm(name=f'zvm-linux-guest-{attempt}') + + vm.set_console() + vm.add_args('-accel', 'tcg', + '-icount', 'shift=0,sleep=off', + '-smp', '8', + '-m', '1G', + '-drive', + f'if=sd,index=2,file={image_path},format=raw', + '-nic', 'user,model=gmac0', + '-nic', 'user,model=gmac1', + '-display', 'none') + vm.launch() + + for pattern in ( + 'U-Boot SPL 2026.07-rc4', + 'loading ZVM img ...', + 'Init virt syscon for ZVM successful.', + 'Start VM Successful!', + 'zephyr VM 0 created and running', + '[CREATE] linux VM 1 created and running'): + self.wait_for_console_patterns(vm, pattern, self.BOOT_FAILURES) + + def test_aarch64_rk3588s_roc_pc_zvm_linux_guest(self): + self.require_accelerator('tcg') + self.set_machine('rk3588s-roc-pc') + + base_image_path = self.build_card_image() + for attempt in range(1, self.BOOT_ATTEMPTS + 1): + try: + self.boot_zvm_linux_guest(base_image_path, attempt) + return + except AssertionError as exc: + if (self.SPL_FAILURE not in str(exc) or + attempt == self.BOOT_ATTEMPTS): + raise + self.log.warning('ZVM Linux guest boot attempt %d/%d hit ' + 'SPL MMC read failure; retrying', + attempt, self.BOOT_ATTEMPTS) + self.get_vm(name=f'zvm-linux-guest-{attempt}').shutdown() + + +if __name__ == '__main__': + QemuSystemTest.main() diff --git a/tests/functional/riscv64/meson.build b/tests/functional/riscv64/meson.build index d1a3e6c2bff88..2b101dc7bfeea 100644 --- a/tests/functional/riscv64/meson.build +++ b/tests/functional/riscv64/meson.build @@ -3,6 +3,8 @@ test_riscv64_timeouts = { 'boston' : 120, 'k230' : 120, + 'server_ref' : 120, + 'spacemit_k3' : 180, 'tuxrun' : 120, } @@ -15,7 +17,9 @@ tests_riscv64_system_thorough = [ 'endianness', 'boston', 'k230', + 'server_ref', 'sifive_u', + 'spacemit_k3', 'tt_atlantis', 'tuxrun', ] diff --git a/tests/functional/riscv64/test_k230.py b/tests/functional/riscv64/test_k230.py index 6ccbf2d581798..2980ad0fe1cd2 100755 --- a/tests/functional/riscv64/test_k230.py +++ b/tests/functional/riscv64/test_k230.py @@ -50,7 +50,7 @@ def wait_for_linux_shell(self): self.wait_for_console_pattern('~ #') def test_k230_direct_boot(self): - self.set_machine('k230') + self.set_machine('k230-canmv') kernel_path = self.ASSET_KERNEL.fetch() dtb_path = self.ASSET_DTB.fetch() initrd_path = self.ASSET_INITRD.fetch() @@ -65,7 +65,7 @@ def test_k230_direct_boot(self): self.wait_for_linux_shell() def test_k230_uboot_boot(self): - self.set_machine('k230') + self.set_machine('k230-canmv') kernel_path = self.ASSET_KERNEL.fetch() dtb_path = self.ASSET_DTB.fetch() initrd_path = self.ASSET_INITRD.fetch() diff --git a/tests/functional/riscv64/test_opensbi.py b/tests/functional/riscv64/test_opensbi.py index 0f8beb7e7a8c0..367e131164c1f 100755 --- a/tests/functional/riscv64/test_opensbi.py +++ b/tests/functional/riscv64/test_opensbi.py @@ -36,5 +36,9 @@ def test_riscv_virt(self): self.set_machine('virt') self.boot_opensbi() + def test_riscv_server_ref(self): + self.set_machine('riscv-server-ref') + self.boot_opensbi() + if __name__ == '__main__': QemuSystemTest.main() diff --git a/tests/functional/riscv64/test_server_ref.py b/tests/functional/riscv64/test_server_ref.py new file mode 100755 index 0000000000000..9b120628dcdb7 --- /dev/null +++ b/tests/functional/riscv64/test_server_ref.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# +# SPDX-License-Identifier: GPL-2.0-or-later +# +""" +riscv-server-ref board test +""" + +import os +import tempfile +import subprocess + +from qemu_test import QemuSystemTest, Asset +from qemu_test import wait_for_console_pattern +from qemu_test import skipIfMissingCommands + +class RiscvServerRefTest(QemuSystemTest): + """ + Test the riscv-server-ref board + """ + + ASSET_KERNEL = Asset( + ('https://github.com/danielhb/qemu-machine-boot/raw/refs/heads/' + 'master/riscv/images/virt64/buildroot/Image'), + '6bacc876c769c1bb6057d2bf549eba67fbe83916e8223f9fe21c8e8fff665a36') + + ASSET_ROOTFS = Asset( + ('https://github.com/danielhb/qemu-machine-boot/raw/refs/heads/' + 'master/riscv/images/virt64/buildroot/rootfs.ext2'), + 'f00bb88749f945d80675540a1338bd1ccb226574685a5b6c65ab44027d0411a8') + + def _test_boot_linux_test(self, tpmstate_dir=None): + self.set_machine('riscv-server-ref') + kernel_path = self.ASSET_KERNEL.fetch() + rootfs_path = self.ASSET_ROOTFS.fetch() + + self.vm.add_args('-kernel', kernel_path) + self.vm.add_args('-append', 'rw rootwait root=/dev/sda') + self.vm.add_args('-drive', + f'file={rootfs_path},format=raw,id=hd0,snapshot=on,if=none') + self.vm.add_args('-device', 'ahci,id=ahci') + self.vm.add_args('-device', 'ide-hd,drive=hd0,bus=ahci.0') + + if tpmstate_dir is not None: + # Note: code taken verbatim from + # tests/functional/arm/test_aspeed_ast2600_buildroot_tpm.py + + # We must put the TPM state dir in /tmp/, not the build dir, + # because some distros use AppArmor to lock down swtpm and + # restrict the set of locations it can access files in. + socket = os.path.join(tpmstate_dir, 'swtpm-socket') + subprocess.run(['swtpm', 'socket', '-d', '--tpm2', + '--tpmstate', f'dir={tpmstate_dir}', + '--ctrl', f'type=unixio,path={socket}'], + check=True) + self.vm.add_args('-chardev', f'socket,id=chrtpm,path={socket}') + self.vm.add_args('-tpmdev', 'emulator,id=tpm0,chardev=chrtpm') + self.vm.add_args('-device', 'tpm-tis-device,tpmdev=tpm0') + + self.vm.set_console() + self.vm.launch() + + # Wait for OpenSBI + wait_for_console_pattern(self, 'OpenSBI') + + # Wait for Linux kernel boot + wait_for_console_pattern(self, 'Linux version') + wait_for_console_pattern(self, 'Machine model: qemu,riscv-server-ref') + + # Test e1000e network card functionality + wait_for_console_pattern(self, 'e1000e') + wait_for_console_pattern(self, 'Network Connection') + + # Wait for boot to complete - system reaches login prompt + wait_for_console_pattern(self, 'Run /sbin/init as init process') + + def test_boot_linux_test(self): + self._test_boot_linux_test() + + @skipIfMissingCommands('swtpm') + def test_boot_linux_test_tpm(self): + with tempfile.TemporaryDirectory(prefix="qemu_") as tpmstate_dir: + self._test_boot_linux_test(tpmstate_dir) + +if __name__ == '__main__': + QemuSystemTest.main() diff --git a/tests/functional/riscv64/test_spacemit_k3.py b/tests/functional/riscv64/test_spacemit_k3.py new file mode 100755 index 0000000000000..a7a41302a438f --- /dev/null +++ b/tests/functional/riscv64/test_spacemit_k3.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +# +# Functional test for the SpacemiT K3 Pico-ITX machine +# +# SPDX-License-Identifier: GPL-2.0-or-later + +""" +Boot pinned SpacemiT K3 SDK Linux images on k3-pico-itx. +""" + +from qemu_test import Asset, QemuSystemTest +from qemu_test import wait_for_console_pattern + + +class SpacemitK3Test(QemuSystemTest): + """Test direct and U-Boot K3 Pico-ITX Linux boot paths.""" + + RELEASE_URL = ( + 'https://github.com/zevorn/spacemit-k3-qemu-images/releases/' + 'download/sdk-v1.0.2-qemu2/' + ) + + ASSET_KERNEL = Asset( + RELEASE_URL + 'Image', + 'cc7359e16ba6569fee49be475d561ab42f90e0aa0e9fb7fc1ead4ab1be066efd') + ASSET_FIRMWARE = Asset( + RELEASE_URL + 'fw_dynamic.bin', + 'e62c515fa5aa5fadee169a316c30f99f365b09ae2eff72c783d936b0b337e0a1') + ASSET_INITRAMFS = Asset( + RELEASE_URL + 'k3-qemu-initramfs.cpio.gz', + '42670a19d756fc239376774577cc267b26fdec872827eed3a02139e8e5ce11e3') + ASSET_DTB = Asset( + RELEASE_URL + 'k3-pico-itx-qemu.dtb', + '35e2fc80a64e41963d091cb23b142ed6dfc1e56f9e808061c6e241d7da4bf56e') + ASSET_UBOOT = Asset( + RELEASE_URL + 'u-boot.bin', + '02a86461f8ea30e9bae42b6c420cd9897cdc6b29b334a00b7919cb67995bd7b5') + ASSET_UBOOT_DTB = Asset( + RELEASE_URL + 'k3-pico-itx-qemu-uboot.dtb', + 'd695bac441b5a5a2814fc2ae0c5e735c11a37379e36ec59cedd16cb9f6bc3486') + ASSET_SD_IMAGE = Asset( + RELEASE_URL + 'k3-qemu-sd.raw.xz', + 'b00d9abd9c65e25346c2f76b304af0785755b2fcf49ea7faf6ec877228f32e65') + + def _wait_for_linux_boot(self): + panic = 'Kernel panic - not syncing' + expected = ( + 'Linux version 6.18.3-g0ffac20d9ef9', + ('Machine model: SpacemiT K3 Pico-ITX ' + '(QEMU Linux-first subset)'), + 'SBI specification v2.0 detected', + 'SBI implementation ID=0x1 Version=0x10006', + ('riscv-timer: Timer interrupt in S-mode is available ' + 'via sstc extension'), + 'smp: Brought up 1 node, 8 CPUs', + ('riscv-imsic: interrupt-controller@e0400000: ' + 'per-CPU IDs 511'), + ('riscv-aplic e0804000.interrupt-controller: ' + '512 interrupts forwarded'), + 'd4017000.serial: ttyS0 at MMIO 0xd4017000', + ('K3-QEMU: model=SpacemiT K3 Pico-ITX ' + '(QEMU Linux-first subset)'), + 'K3-QEMU: cpus=8', + 'K3-QEMU: Linux boot successful', + 'K3_LINUX_MVP_PASS', + ) + + for pattern in expected: + wait_for_console_pattern(self, pattern, panic) + + def test_linux_boot(self): + self.set_machine('k3-pico-itx') + + kernel_path = self.ASSET_KERNEL.fetch() + firmware_path = self.ASSET_FIRMWARE.fetch() + initramfs_path = self.ASSET_INITRAMFS.fetch() + dtb_path = self.ASSET_DTB.fetch() + + kernel_command_line = ( + 'earlycon=uart8250,mmio32,0xd4017000,115200 ' + 'console=ttyS0,115200 rdinit=/init' + ) + self.vm.add_args('-bios', firmware_path, + '-kernel', kernel_path, + '-initrd', initramfs_path, + '-dtb', dtb_path, + '-append', kernel_command_line, + '-no-reboot') + self.vm.set_console() + self.vm.launch() + self._wait_for_linux_boot() + + def test_uboot_sd_boot(self): + self.set_machine('k3-pico-itx') + + firmware_path = self.ASSET_FIRMWARE.fetch() + uboot_path = self.ASSET_UBOOT.fetch() + dtb_path = self.ASSET_UBOOT_DTB.fetch() + sd_path = self.uncompress(self.ASSET_SD_IMAGE) + + self.vm.add_args('-bios', firmware_path, + '-kernel', uboot_path, + '-dtb', dtb_path, + '-drive', (f'file={sd_path},if=sd,format=raw,' + 'snapshot=on'), + '-no-reboot') + self.vm.set_console() + self.vm.launch() + + panic = 'Kernel panic - not syncing' + wait_for_console_pattern(self, 'U-Boot 2022.10', panic) + wait_for_console_pattern(self, + 'K3-QEMU: Starting kernel from SD', panic) + self._wait_for_linux_boot() + + +if __name__ == '__main__': + QemuSystemTest.main() diff --git a/tests/qtest/ax650x-pyramid-test.c b/tests/qtest/ax650x-pyramid-test.c new file mode 100644 index 0000000000000..3043d03c8d28e --- /dev/null +++ b/tests/qtest/ax650x-pyramid-test.c @@ -0,0 +1,628 @@ +/* + * QTest for the M5Stack AI Pyramid (Axera AX650X) + * + * Copyright (c) 2026 Zevorn + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "hw/sd/ax650x-sdhci.h" +#include "qobject/qdict.h" +#include "qobject/qlist.h" +#include "qobject/qnum.h" +#include "libqtest.h" +#include "libqos/sdhci-cmd.h" + +#define AX650X_NUM_CPUS 8 + +#define AX650X_RAM_BASE 0x100000000ULL +#define AX650X_RESET_RAM_TEST_ADDR (AX650X_RAM_BASE + 256 * MiB) + +#define AX650X_GIC_DIST_BASE 0x04901000 +#define AX650X_GIC_HYP_BASE 0x04904000 +#define AX650X_GICD_CTLR 0x000 +#define AX650X_GICD_TYPER 0x004 +#define AX650X_GICD_ISPENDR 0x200 +#define AX650X_GICH_VTR 0x004 + +#define AX650X_UART0_BASE 0x02016000 +#define AX650X_UART_REGSHIFT 2 +#define AX650X_UART_IER (1 << AX650X_UART_REGSHIFT) +#define AX650X_UART_LSR (5 << AX650X_UART_REGSHIFT) +#define AX650X_UART_USR 0x7c +#define AX650X_UART_UCV 0xf8 +#define AX650X_UART_IER_THRI 0x02 +#define AX650X_UART_LSR_THRE 0x20 +#define AX650X_UART_LSR_TEMT 0x40 +#define AX650X_UART_USR_TFNF BIT(1) +#define AX650X_UART_USR_TFE BIT(2) +#define AX650X_UART0_INTID (32 + 135) + +#define AX650X_EMMC_BASE 0x28000000 +#define AX650X_EMMC_INTID (32 + 93) +#define AX650X_EMMC_TEST_IMAGE_SIZE (1 * MiB) +#define AX650X_EMMC_BLOCK_SIZE 512 + +#define AX650X_DWMAC0_BASE 0x10140000 +#define AX650X_DWMAC1_BASE 0x30800000 +#define AX650X_DWMAC0_INTID (32 + 104) +#define AX650X_DWMAC0_GLB_BASE 0x10000000 +#define AX650X_DWMAC0_CLK_BASE 0x10010000 +#define AX650X_GPIO0_BASE 0x02003000 +#define AX650X_GPIO1_BASE 0x02004000 +#define AX650X_HWSPINLOCK_BASE 0x04510000 +#define AX650X_HWSPINLOCK_STRIDE 8 + +#define DWMAC_GMAC_VERSION 0x110 +#define DWMAC_GMAC_HW_FEATURE1 0x120 +#define DWMAC_GMAC_MDIO_ADDR 0x200 +#define DWMAC_GMAC_MDIO_DATA 0x204 +#define DWMAC_GMAC_VERSION_AX650X 0x1052 +#define DWMAC_HW_FEATURE1_ADDR64 (0x3 << 14) +#define DWMAC_HW_FEATURE1_ADDR64_40 BIT(14) +#define DWMAC_HW_FEATURE1_TSO BIT(18) +#define DWMAC_HW_FEATURE1_TXFIFO (0x1f << 6) +#define DWMAC_HW_FEATURE1_RXFIFO 0x1f +#define DWMAC_HW_FEATURE1_TXFIFO_32K (8 << 6) +#define DWMAC_HW_FEATURE1_RXFIFO_64K 9 +#define DWMAC_MDIO_BUSY BIT(0) +#define DWMAC_MDIO_GOC_WRITE (1 << 2) +#define DWMAC_MDIO_GOC_READ (3 << 2) +#define DWMAC_MDIO_REG(reg) ((reg) << 16) +#define DWMAC_MDIO_PHY(addr) ((addr) << 21) +#define DWMAC_BMCR_ANRESTART BIT(9) +#define DWMAC_BMCR_ANENABLE BIT(12) +#define DWMAC_BMCR_RESET BIT(15) + +#define DWMAC_DMA_CHAN_TX_CONTROL 0x1104 +#define DWMAC_DMA_CHAN_TX_BASE_HI 0x1110 +#define DWMAC_DMA_CHAN_TX_BASE 0x1114 +#define DWMAC_DMA_CHAN_TX_END_ADDR 0x1120 +#define DWMAC_DMA_CHAN_TX_RING_LEN 0x112c +#define DWMAC_DMA_CHAN_INTR_ENA 0x1134 +#define DWMAC_DMA_CHAN_STATUS 0x1160 +#define DWMAC_DMA_TX_START BIT(0) +#define DWMAC_DMA_STATUS_TI BIT(0) +#define DWMAC_DMA_STATUS_NIS BIT(15) + +#define DWMAC_DESC_OWN BIT(31) +#define DWMAC_TX_DESC_FIRST BIT(29) +#define DWMAC_TX_DESC_LAST BIT(28) +#define DWMAC_TX_DESC_IOC BIT(31) + +#define DW_GPIO_SWPORTA_DR 0x00 +#define DW_GPIO_SWPORTA_DDR 0x04 +#define DW_GPIO_EXT_PORTA 0x50 + +#define AX650X_DWMAC_GLB_PHY_IF 0x9c +#define AX650X_DWMAC_CLK_SW_RESET 0x10 +#define AX650X_DWMAC_CLK_RESET_SET 0x38 +#define AX650X_DWMAC_CLK_RESET_CLEAR 0x3c + +#define SDHC_NORINTSTS 0x30 +#define SDHC_NORINTSTSEN 0x34 +#define SDHC_NORINTSIGEN 0x38 +#define SDHC_ARGUMENT2 0x00 +#define SDHC_TRNS_AUTO_CMD23 0x0008 +#define SDHC_NIS_CMDCMP BIT(0) +#define SDHC_AX650X_HCVER 0x2402 +#define SDHC_EMMC_SEND_OP_COND (1 << 8) +#define SDHC_EMMC_SET_RELATIVE_ADDR (3 << 8) +#define SDHC_EMMC_SEND_STATUS (13 << 8) +#define SDHC_R1_READY_FOR_DATA BIT(8) +#define SDHC_R1_CURRENT_STATE_MASK 0x1e00 +#define SDHC_R1_STATE_TRAN (4 << 9) + +static char *emmc_path; + +static QTestState *ax650x_pyramid_start(void) +{ + return qtest_init("-machine ax650x-pyramid -accel qtest -display none"); +} + +static uint64_t qom_get_uint(QTestState *qts, const char *path, + const char *property) +{ + QDict *response; + QNum *number; + uint64_t value; + + response = qtest_qmp(qts, + "{ 'execute': 'qom-get'," + " 'arguments': { 'path': %s, 'property': %s } }", + path, property); + g_assert(qdict_haskey(response, "return")); + number = qobject_to(QNum, qdict_get(response, "return")); + g_assert_nonnull(number); + g_assert_true(qnum_get_try_uint(number, &value)); + qobject_unref(response); + + return value; +} + +static void test_cpu_topology(void) +{ + QTestState *qts = ax650x_pyramid_start(); + QDict *response; + QList *cpus; + QListEntry *entry; + + response = qtest_qmp(qts, "{ 'execute': 'query-cpus-fast' }"); + g_assert(qdict_haskey(response, "return")); + cpus = qdict_get_qlist(response, "return"); + g_assert_cmpuint(qlist_size(cpus), ==, AX650X_NUM_CPUS); + + QLIST_FOREACH_ENTRY(cpus, entry) { + QDict *cpu = qobject_to(QDict, qlist_entry_obj(entry)); + unsigned int index = qdict_get_int(cpu, "cpu-index"); + const char *path = qdict_get_str(cpu, "qom-path"); + + g_assert_cmpuint(index, <, AX650X_NUM_CPUS); + g_assert_cmphex(qom_get_uint(qts, path, "mp-affinity"), ==, + (uint64_t)index << 8); + } + + qobject_unref(response); + qtest_quit(qts); +} + +static void test_memory_and_gic(void) +{ + QTestState *qts = ax650x_pyramid_start(); + uint64_t pattern = 0x0123456789abcdefULL; + uint32_t typer; + uint32_t vtr; + + qtest_writeq(qts, AX650X_RAM_BASE + 0x1000, pattern); + g_assert_cmphex(qtest_readq(qts, AX650X_RAM_BASE + 0x1000), ==, + pattern); + + g_assert_cmphex(qtest_readl(qts, AX650X_GIC_DIST_BASE + AX650X_GICD_CTLR), + ==, 0); + typer = qtest_readl(qts, AX650X_GIC_DIST_BASE + AX650X_GICD_TYPER); + g_assert_cmpuint(typer & 0x1f, ==, 7); + g_assert_cmpuint((typer >> 5) & 0x7, ==, AX650X_NUM_CPUS - 1); + + vtr = qtest_readl(qts, AX650X_GIC_HYP_BASE + AX650X_GICH_VTR); + g_assert_cmpuint(vtr & 0x3f, ==, 3); + + qtest_quit(qts); +} + +static void test_uart_irq_and_reset(void) +{ + QTestState *qts = ax650x_pyramid_start(); + uint64_t ram_pattern = 0xfedcba9876543210ULL; + uint64_t pending_addr; + uint32_t pending_mask; + uint32_t lsr; + + lsr = qtest_readl(qts, AX650X_UART0_BASE + AX650X_UART_LSR); + g_assert_cmphex(lsr & (AX650X_UART_LSR_THRE | AX650X_UART_LSR_TEMT), ==, + AX650X_UART_LSR_THRE | AX650X_UART_LSR_TEMT); + g_assert_cmphex(qtest_readl(qts, AX650X_UART0_BASE + AX650X_UART_IER), + ==, 0); + + qtest_writel(qts, AX650X_UART0_BASE + AX650X_UART_IER, + AX650X_UART_IER_THRI); + pending_addr = AX650X_GIC_DIST_BASE + AX650X_GICD_ISPENDR + + (AX650X_UART0_INTID / 32) * sizeof(uint32_t); + pending_mask = 1U << (AX650X_UART0_INTID % 32); + g_assert_cmphex(qtest_readl(qts, pending_addr) & pending_mask, ==, + pending_mask); + + qtest_writeq(qts, AX650X_RESET_RAM_TEST_ADDR, ram_pattern); + qtest_system_reset(qts); + g_assert_cmphex(qtest_readl(qts, AX650X_UART0_BASE + AX650X_UART_IER), + ==, 0); + g_assert_cmphex(qtest_readl(qts, AX650X_GIC_DIST_BASE + AX650X_GICD_CTLR), + ==, 0); + g_assert_cmphex(qtest_readq(qts, AX650X_RESET_RAM_TEST_ADDR), ==, + ram_pattern); + + qtest_quit(qts); +} + +static void test_uart_extension_registers(void) +{ + QTestState *qts = ax650x_pyramid_start(); + + g_assert_cmphex(qtest_readl(qts, AX650X_UART0_BASE + AX650X_UART_USR), + ==, AX650X_UART_USR_TFNF | AX650X_UART_USR_TFE); + g_assert_cmphex(qtest_readl(qts, AX650X_UART0_BASE + AX650X_UART_UCV), + ==, 0); + + qtest_quit(qts); +} + +static uint16_t dwmac_mdio_read(QTestState *qts, uint64_t base, + unsigned int phy, unsigned int reg) +{ + uint32_t command = DWMAC_MDIO_BUSY | DWMAC_MDIO_GOC_READ | + DWMAC_MDIO_PHY(phy) | DWMAC_MDIO_REG(reg); + + qtest_writel(qts, base + DWMAC_GMAC_MDIO_ADDR, command); + g_assert_cmphex(qtest_readl(qts, base + DWMAC_GMAC_MDIO_ADDR) & + DWMAC_MDIO_BUSY, ==, 0); + return qtest_readl(qts, base + DWMAC_GMAC_MDIO_DATA); +} + +static void dwmac_mdio_write(QTestState *qts, uint64_t base, + unsigned int phy, unsigned int reg, + uint16_t value) +{ + uint32_t command = DWMAC_MDIO_BUSY | DWMAC_MDIO_GOC_WRITE | + DWMAC_MDIO_PHY(phy) | DWMAC_MDIO_REG(reg); + + qtest_writel(qts, base + DWMAC_GMAC_MDIO_DATA, value); + qtest_writel(qts, base + DWMAC_GMAC_MDIO_ADDR, command); + g_assert_cmphex(qtest_readl(qts, base + DWMAC_GMAC_MDIO_ADDR) & + DWMAC_MDIO_BUSY, ==, 0); +} + +static void test_dwmac_registers_glue_and_reset(void) +{ + QTestState *qts = ax650x_pyramid_start(); + uint32_t feature; + + g_assert_cmphex(qtest_readl(qts, AX650X_DWMAC0_BASE + + DWMAC_GMAC_VERSION), ==, + DWMAC_GMAC_VERSION_AX650X); + g_assert_cmphex(qtest_readl(qts, AX650X_DWMAC1_BASE + + DWMAC_GMAC_VERSION), ==, + DWMAC_GMAC_VERSION_AX650X); + + feature = qtest_readl(qts, AX650X_DWMAC0_BASE + + DWMAC_GMAC_HW_FEATURE1); + g_assert_cmphex(feature & DWMAC_HW_FEATURE1_ADDR64, ==, + DWMAC_HW_FEATURE1_ADDR64_40); + g_assert_cmphex(feature & DWMAC_HW_FEATURE1_TSO, ==, + 0); + g_assert_cmphex(feature & DWMAC_HW_FEATURE1_TXFIFO, ==, + DWMAC_HW_FEATURE1_TXFIFO_32K); + g_assert_cmphex(feature & DWMAC_HW_FEATURE1_RXFIFO, ==, + DWMAC_HW_FEATURE1_RXFIFO_64K); + + g_assert_cmphex(dwmac_mdio_read(qts, AX650X_DWMAC0_BASE, 1, 2), ==, + 0x937c); + g_assert_cmphex(dwmac_mdio_read(qts, AX650X_DWMAC0_BASE, 1, 3), ==, + 0x4030); + g_assert_cmphex(dwmac_mdio_read(qts, AX650X_DWMAC0_BASE, 2, 2), ==, + 0xffff); + dwmac_mdio_write(qts, AX650X_DWMAC0_BASE, 1, 0, + DWMAC_BMCR_RESET | DWMAC_BMCR_ANENABLE | + DWMAC_BMCR_ANRESTART); + g_assert_cmphex(dwmac_mdio_read(qts, AX650X_DWMAC0_BASE, 1, 0), ==, + DWMAC_BMCR_ANENABLE); + + qtest_writel(qts, AX650X_GPIO0_BASE + DW_GPIO_SWPORTA_DDR, BIT(7)); + qtest_writel(qts, AX650X_GPIO0_BASE + DW_GPIO_SWPORTA_DR, BIT(7)); + g_assert_cmphex(qtest_readl(qts, AX650X_GPIO0_BASE + DW_GPIO_EXT_PORTA), + ==, BIT(7)); + qtest_writel(qts, AX650X_GPIO1_BASE + DW_GPIO_SWPORTA_DDR, BIT(11)); + qtest_writel(qts, AX650X_GPIO1_BASE + DW_GPIO_SWPORTA_DR, BIT(11)); + g_assert_cmphex(qtest_readl(qts, AX650X_GPIO1_BASE + DW_GPIO_EXT_PORTA), + ==, BIT(11)); + + qtest_writel(qts, AX650X_DWMAC0_GLB_BASE + AX650X_DWMAC_GLB_PHY_IF, + BIT(4)); + g_assert_cmphex(qtest_readl(qts, AX650X_DWMAC0_GLB_BASE + + AX650X_DWMAC_GLB_PHY_IF), ==, BIT(4)); + qtest_writel(qts, AX650X_DWMAC0_CLK_BASE + + AX650X_DWMAC_CLK_RESET_SET, BIT(7)); + g_assert_cmphex(qtest_readl(qts, AX650X_DWMAC0_CLK_BASE + + AX650X_DWMAC_CLK_SW_RESET), ==, BIT(7)); + qtest_writel(qts, AX650X_DWMAC0_CLK_BASE + + AX650X_DWMAC_CLK_RESET_CLEAR, BIT(7)); + g_assert_cmphex(qtest_readl(qts, AX650X_DWMAC0_CLK_BASE + + AX650X_DWMAC_CLK_SW_RESET), ==, 0); + + g_assert_cmphex(qtest_readl(qts, AX650X_HWSPINLOCK_BASE + + 12 * AX650X_HWSPINLOCK_STRIDE), ==, 0); + g_assert_cmphex(qtest_readl(qts, AX650X_HWSPINLOCK_BASE + + 12 * AX650X_HWSPINLOCK_STRIDE), ==, 1); + qtest_writel(qts, AX650X_HWSPINLOCK_BASE + + 12 * AX650X_HWSPINLOCK_STRIDE + sizeof(uint32_t), + 0xffffffff); + g_assert_cmphex(qtest_readl(qts, AX650X_HWSPINLOCK_BASE + + 12 * AX650X_HWSPINLOCK_STRIDE), ==, 0); + + qtest_system_reset(qts); + g_assert_cmphex(qtest_readl(qts, AX650X_DWMAC0_BASE + + DWMAC_GMAC_VERSION), ==, + DWMAC_GMAC_VERSION_AX650X); + g_assert_cmphex(qtest_readl(qts, AX650X_GPIO0_BASE + + DW_GPIO_EXT_PORTA), ==, 0); + g_assert_cmphex(qtest_readl(qts, AX650X_DWMAC0_GLB_BASE + + AX650X_DWMAC_GLB_PHY_IF), ==, 0); + + qtest_quit(qts); +} + +static void test_dwmac_40bit_dma_and_irq(void) +{ + const uint64_t desc_addr = AX650X_RAM_BASE + 2 * MiB; + const uint64_t frame_addr = desc_addr + 0x1000; + uint8_t frame[64] = { + 0x52, 0x54, 0x00, 0x12, 0x34, 0x56, + 0x52, 0x54, 0x00, 0x65, 0x43, 0x21, + 0x08, 0x00, + }; + uint32_t desc[4] = { + cpu_to_le32((uint32_t)frame_addr), + cpu_to_le32(frame_addr >> 32), + cpu_to_le32(DWMAC_TX_DESC_IOC | sizeof(frame)), + cpu_to_le32(DWMAC_DESC_OWN | DWMAC_TX_DESC_FIRST | + DWMAC_TX_DESC_LAST), + }; + QTestState *qts = ax650x_pyramid_start(); + uint64_t pending_addr; + uint32_t pending_mask; + uint32_t status; + + qtest_memwrite(qts, frame_addr, frame, sizeof(frame)); + qtest_memwrite(qts, desc_addr, desc, sizeof(desc)); + + qtest_writel(qts, AX650X_DWMAC0_BASE + DWMAC_DMA_CHAN_INTR_ENA, + DWMAC_DMA_STATUS_TI | DWMAC_DMA_STATUS_NIS); + qtest_writel(qts, AX650X_DWMAC0_BASE + DWMAC_DMA_CHAN_TX_BASE_HI, + desc_addr >> 32); + qtest_writel(qts, AX650X_DWMAC0_BASE + DWMAC_DMA_CHAN_TX_BASE, + (uint32_t)desc_addr); + qtest_writel(qts, AX650X_DWMAC0_BASE + DWMAC_DMA_CHAN_TX_RING_LEN, 0); + qtest_writel(qts, AX650X_DWMAC0_BASE + DWMAC_DMA_CHAN_TX_CONTROL, + DWMAC_DMA_TX_START); + qtest_writel(qts, AX650X_DWMAC0_BASE + DWMAC_DMA_CHAN_TX_END_ADDR, + (uint32_t)desc_addr); + + qtest_memread(qts, desc_addr, desc, sizeof(desc)); + g_assert_cmphex(le32_to_cpu(desc[3]) & DWMAC_DESC_OWN, ==, 0); + status = qtest_readl(qts, AX650X_DWMAC0_BASE + + DWMAC_DMA_CHAN_STATUS); + g_assert_cmphex(status & (DWMAC_DMA_STATUS_TI | DWMAC_DMA_STATUS_NIS), + ==, DWMAC_DMA_STATUS_TI | DWMAC_DMA_STATUS_NIS); + + pending_addr = AX650X_GIC_DIST_BASE + AX650X_GICD_ISPENDR + + (AX650X_DWMAC0_INTID / 32) * sizeof(uint32_t); + pending_mask = 1U << (AX650X_DWMAC0_INTID % 32); + g_assert_cmphex(qtest_readl(qts, pending_addr) & pending_mask, ==, + pending_mask); + + qtest_writel(qts, AX650X_DWMAC0_BASE + DWMAC_DMA_CHAN_STATUS, + DWMAC_DMA_STATUS_TI | DWMAC_DMA_STATUS_NIS); + g_assert_cmphex(qtest_readl(qts, AX650X_DWMAC0_BASE + + DWMAC_DMA_CHAN_STATUS) & + (DWMAC_DMA_STATUS_TI | DWMAC_DMA_STATUS_NIS), ==, 0); + + qtest_quit(qts); +} + +static void test_emmc_registers_and_reset(void) +{ + QTestState *qts = ax650x_pyramid_start(); + uint64_t base = AX650X_EMMC_BASE; + uint64_t capabilities; + + g_assert_cmphex(qtest_readw(qts, base + AX650X_SDHCI_VENDOR_PTR), ==, + AX650X_SDHCI_VENDOR_PTR_VALUE); + capabilities = qtest_readq(qts, base + SDHC_CAPAB); + g_assert_cmphex(capabilities & BIT_ULL(18), ==, BIT_ULL(18)); + g_assert_cmphex(capabilities & BIT_ULL(28), ==, BIT_ULL(28)); + g_assert_cmphex(qtest_readw(qts, base + SDHC_HCVER), ==, + SDHC_AX650X_HCVER); + + g_assert_cmphex(qtest_readl(qts, base + AX650X_SDHCI_PHY_CNFG), ==, + AX650X_SDHCI_PHY_PWRGOOD); + qtest_writel(qts, base + AX650X_SDHCI_PHY_CNFG, 0x00cc0001); + g_assert_cmphex(qtest_readl(qts, base + AX650X_SDHCI_PHY_CNFG), ==, + 0x00cc0003); + + qtest_writew(qts, base + AX650X_SDHCI_PHY_CMDPAD_CNFG, 0x0449); + g_assert_cmphex(qtest_readw(qts, + base + AX650X_SDHCI_PHY_CMDPAD_CNFG), ==, + 0x0449); + qtest_writeb(qts, base + AX650X_SDHCI_PHY_DLL_CTRL, + AX650X_SDHCI_DLL_EN); + g_assert_cmphex(qtest_readb(qts, + base + AX650X_SDHCI_PHY_DLL_STATUS), ==, + AX650X_SDHCI_DLL_LOCKED); + qtest_writeb(qts, base + AX650X_SDHCI_PHY_DLL_CTRL, 0); + g_assert_cmphex(qtest_readb(qts, + base + AX650X_SDHCI_PHY_DLL_STATUS), ==, 0); + + qtest_writeb(qts, base + AX650X_SDHCI_PHY_DLLDBG_MLKDC, 0xff); + qtest_writeb(qts, base + AX650X_SDHCI_PHY_DLLDBG_SLKDC, 0xff); + g_assert_cmphex(qtest_readb(qts, + base + AX650X_SDHCI_PHY_DLLDBG_MLKDC), ==, + 98); + g_assert_cmphex(qtest_readb(qts, + base + AX650X_SDHCI_PHY_DLLDBG_SLKDC), ==, + 24); + + qtest_writew(qts, base + AX650X_SDHCI_EMMC_CTRL, 0xffff); + g_assert_cmphex(qtest_readw(qts, base + AX650X_SDHCI_EMMC_CTRL), ==, + AX650X_SDHCI_CARD_IS_EMMC | + AX650X_SDHCI_EMMC_RST_N | + AX650X_SDHCI_EMMC_RST_N_OE | + AX650X_SDHCI_ENH_STROBE_EN); + + qtest_system_reset(qts); + g_assert_cmphex(qtest_readl(qts, base + AX650X_SDHCI_PHY_CNFG), ==, + AX650X_SDHCI_PHY_PWRGOOD); + g_assert_cmphex(qtest_readw(qts, + base + AX650X_SDHCI_PHY_CMDPAD_CNFG), ==, 0); + g_assert_cmphex(qtest_readb(qts, + base + AX650X_SDHCI_PHY_DLL_STATUS), ==, 0); + g_assert_cmphex(qtest_readw(qts, base + AX650X_SDHCI_EMMC_CTRL), ==, 0); + g_assert_cmphex(qtest_readw(qts, base + AX650X_SDHCI_VENDOR_PTR), ==, + AX650X_SDHCI_VENDOR_PTR_VALUE); + + qtest_quit(qts); +} + +static QTestState *ax650x_pyramid_start_with_emmc(void) +{ + QTestState *qts; + uint64_t base = AX650X_EMMC_BASE; + + qts = qtest_initf("-machine ax650x-pyramid -accel qtest -display none " + "-drive file=%s,if=sd,format=raw,auto-read-only=off", + emmc_path); + qtest_writeb(qts, base + SDHC_SWRST, SDHC_RESET_ALL); + qtest_writew(qts, base + SDHC_CLKCON, + SDHC_CLOCK_SDCLK_EN | SDHC_CLOCK_INT_EN); + + qtest_writel(qts, base + SDHC_NORINTSTSEN, SDHC_NIS_CMDCMP); + qtest_writel(qts, base + SDHC_NORINTSIGEN, SDHC_NIS_CMDCMP); + sdhci_cmd_regs(qts, base, 0, 0, 0, 0, 0); + + sdhci_cmd_regs(qts, base, 0, 0, 0x40ff8000, 0, + SDHC_EMMC_SEND_OP_COND); + sdhci_cmd_regs(qts, base, 0, 0, 0, 0, SDHC_ALL_SEND_CID); + sdhci_cmd_regs(qts, base, 0, 0, 1 << 16, 0, + SDHC_EMMC_SET_RELATIVE_ADDR | SDHC_CMD_RESPONSE); + sdhci_cmd_regs(qts, base, 0, 0, 1 << 16, 0, + SDHC_SELECT_DESELECT_CARD); + + return qts; +} + +static void test_emmc_block_io_and_irq(void) +{ + uint8_t source[AX650X_EMMC_BLOCK_SIZE]; + uint8_t written[AX650X_EMMC_BLOCK_SIZE]; + uint8_t readback[AX650X_EMMC_BLOCK_SIZE]; + QTestState *qts; + uint64_t pending_addr; + uint32_t pending_mask; + int fd; + ssize_t ret; + + for (unsigned int i = 0; i < sizeof(source); i++) { + source[i] = i % 127 + 1; + written[i] = (i * 3) % 127 + 1; + } + fd = open(emmc_path, O_WRONLY); + g_assert_cmpint(fd, >=, 0); + ret = pwrite(fd, source, sizeof(source), 0); + g_assert_cmpint(ret, ==, sizeof(source)); + close(fd); + + qts = ax650x_pyramid_start_with_emmc(); + g_assert_cmphex(qtest_readw(qts, + AX650X_EMMC_BASE + SDHC_NORINTSTS) & + SDHC_NIS_CMDCMP, ==, SDHC_NIS_CMDCMP); + pending_addr = AX650X_GIC_DIST_BASE + AX650X_GICD_ISPENDR + + (AX650X_EMMC_INTID / 32) * sizeof(uint32_t); + pending_mask = 1U << (AX650X_EMMC_INTID % 32); + g_assert_cmphex(qtest_readl(qts, pending_addr) & pending_mask, ==, + pending_mask); + + ret = sdhci_read_cmd(qts, AX650X_EMMC_BASE, (char *)readback, + sizeof(readback)); + g_assert_cmpint(ret, ==, sizeof(readback)); + g_assert_cmpmem(readback, sizeof(readback), source, sizeof(source)); + + sdhci_write_cmd(qts, AX650X_EMMC_BASE, (char *)written, + sizeof(written), AX650X_EMMC_BLOCK_SIZE); + qtest_quit(qts); + + fd = open(emmc_path, O_RDONLY); + g_assert_cmpint(fd, >=, 0); + ret = pread(fd, readback, sizeof(readback), 0); + g_assert_cmpint(ret, ==, sizeof(readback)); + close(fd); + g_assert_cmpmem(readback, sizeof(readback), written, sizeof(written)); +} + +static void test_emmc_auto_cmd23(void) +{ + uint8_t expected[AX650X_EMMC_BLOCK_SIZE]; + uint8_t readback[AX650X_EMMC_BLOCK_SIZE]; + QTestState *qts = ax650x_pyramid_start_with_emmc(); + uint32_t status; + int fd; + ssize_t ret; + + for (unsigned int i = 0; i < sizeof(expected); i++) { + expected[i] = (i * 5) % 251 + 1; + } + + qtest_writel(qts, AX650X_EMMC_BASE + SDHC_ARGUMENT2, 1); + sdhci_cmd_regs(qts, AX650X_EMMC_BASE, sizeof(expected), 1, + AX650X_EMMC_BLOCK_SIZE, + SDHC_TRNS_MULTI | SDHC_TRNS_AUTO_CMD23 | + SDHC_TRNS_BLK_CNT_EN, + SDHC_WRITE_MULTIPLE_BLOCK | SDHC_CMD_DATA_PRESENT); + for (unsigned int i = 0; i < sizeof(expected); i += sizeof(uint32_t)) { + qtest_writel(qts, AX650X_EMMC_BASE + SDHC_BDATA, + ldl_le_p(&expected[i])); + } + + sdhci_cmd_regs(qts, AX650X_EMMC_BASE, 0, 0, 1 << 16, 0, + SDHC_EMMC_SEND_STATUS | SDHC_CMD_RESPONSE); + status = qtest_readl(qts, AX650X_EMMC_BASE + SDHC_RSPREG0); + g_assert_cmphex(status & SDHC_R1_READY_FOR_DATA, ==, + SDHC_R1_READY_FOR_DATA); + g_assert_cmphex(status & SDHC_R1_CURRENT_STATE_MASK, ==, + SDHC_R1_STATE_TRAN); + qtest_quit(qts); + + fd = open(emmc_path, O_RDONLY); + g_assert_cmpint(fd, >=, 0); + ret = pread(fd, readback, sizeof(readback), AX650X_EMMC_BLOCK_SIZE); + g_assert_cmpint(ret, ==, sizeof(readback)); + close(fd); + g_assert_cmpmem(readback, sizeof(readback), expected, sizeof(expected)); +} + +static void emmc_drive_create(void) +{ + GError *error = NULL; + int fd; + + fd = g_file_open_tmp("ax650x-emmc-XXXXXX", &emmc_path, &error); + if (fd < 0) { + g_error("unable to create eMMC image: %s", error->message); + } + g_assert_nonnull(emmc_path); + g_assert_cmpint(ftruncate(fd, AX650X_EMMC_TEST_IMAGE_SIZE), ==, 0); + close(fd); +} + +static void emmc_drive_destroy(void) +{ + unlink(emmc_path); + g_free(emmc_path); +} + +int main(int argc, char **argv) +{ + int ret; + + emmc_drive_create(); + g_test_init(&argc, &argv, NULL); + + qtest_add_func("ax650x-pyramid/cpu-topology", test_cpu_topology); + qtest_add_func("ax650x-pyramid/memory-and-gic", test_memory_and_gic); + qtest_add_func("ax650x-pyramid/uart-irq-and-reset", + test_uart_irq_and_reset); + qtest_add_func("ax650x-pyramid/uart-extension-registers", + test_uart_extension_registers); + qtest_add_func("ax650x-pyramid/dwmac-registers-glue-and-reset", + test_dwmac_registers_glue_and_reset); + qtest_add_func("ax650x-pyramid/dwmac-40bit-dma-and-irq", + test_dwmac_40bit_dma_and_irq); + qtest_add_func("ax650x-pyramid/emmc-registers-and-reset", + test_emmc_registers_and_reset); + qtest_add_func("ax650x-pyramid/emmc-block-io-and-irq", + test_emmc_block_io_and_irq); + qtest_add_func("ax650x-pyramid/emmc-auto-cmd23", + test_emmc_auto_cmd23); + + ret = g_test_run(); + emmc_drive_destroy(); + return ret; +} diff --git a/tests/qtest/k230-kpu-test.c b/tests/qtest/k230-kpu-test.c new file mode 100644 index 0000000000000..9ce889dcf1fc6 --- /dev/null +++ b/tests/qtest/k230-kpu-test.c @@ -0,0 +1,4814 @@ +/* + * QTest testcase for K230 KPU/GNNE block + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bswap.h" +#include "libqtest.h" + +#define K230_FAKE_KPU_OUTPUT_BASE 0x10090000 +#define K230_FAKE_KPU_OUTPUT_SIZE 0x00100000 +#define K230_KPU_CFG_BASE 0x80400000 +#define K230_PLIC_BASE 0xf00000000ULL + +#define K230_PLIC_ENABLE_S 0x2080 +#define K230_PLIC_CONTEXT_S 0x201000 +#define K230_PLIC_THRESHOLD 0x00 +#define K230_PLIC_CLAIM 0x04 + +#define K230_GNNE_COMMAND_START 0x100 +#define K230_GNNE_COMMAND_END 0x104 +#define K230_GNNE_COMMAND_HI 0x108 +#define K230_GNNE_CONTROL 0x128 +#define K230_GNNE_CLEAR K230_GNNE_CONTROL +#define K230_GNNE_STATUS 0x130 +#define K230_GNNE_SETUP 0x190 + +#define K230_GNNE_COMMAND_TEST 0x01000000 +#define K230_GNNE_COMMAND_BASE_OFFSET 0x003a6000 +#define K230_GNNE_RUNTIME_RDATA_BASE 0x10000020 +#define K230_GNNE_RUNTIME_FUNCTION_COMMAND 0x1032b020 +#define K230_GNNE_RUNTIME_ARG_TABLE 0x80000000 +#define K230_GNNE_RUNTIME_DIRECT_SOURCE \ + (K230_GNNE_RUNTIME_RDATA_BASE + 0x500000) +#define K230_GNNE_RUNTIME_DIRECT_OUTPUT \ + (K230_GNNE_RUNTIME_RDATA_BASE + 0x501000) +#define K230_GNNE_RUNTIME_DDR_BASE 0x3c000000 +#define K230_GNNE_RDATA_ALIAS_BASE 0xfc000000 +#define K230_GNNE_RDATA_FALLBACK_BASE 0x10000000 +#define K230_GNNE_DONE 0x0000000400000004ULL +#define K230_GNNE_START 0x0000000900000009ULL +#define K230_GNNE_DELAY_NS (100 * 1000) +#define K230_GNNE_IRQ 189 +#define K230_KPU_PAGE_SIZE 4096 + +#define K230_KPU_OUTPUT_END \ + (K230_FAKE_KPU_OUTPUT_BASE + K230_FAKE_KPU_OUTPUT_SIZE) +#define K230_KPU_OUTPUT_TEST0 K230_FAKE_KPU_OUTPUT_BASE +#define K230_KPU_OUTPUT_TEST1 (K230_FAKE_KPU_OUTPUT_BASE + 0x3000) +#define K230_KPU_OUTPUT_LAST \ + (K230_KPU_OUTPUT_END - K230_KPU_PAGE_SIZE) +#define K230_KPU_OUTPUT_UNREFERENCED \ + (K230_FAKE_KPU_OUTPUT_BASE + 0x7000) +#define K230_KPU_OUTSIDE_LOW \ + (K230_FAKE_KPU_OUTPUT_BASE - K230_KPU_PAGE_SIZE) +#define K230_KPU_OUTSIDE_HIGH K230_KPU_OUTPUT_END +#define K230_GNNE_SYNTH_LANE_WIDTH 24 +#define K230_GNNE_SYNTH_GLB_BASE \ + (K230_GNNE_COMMAND_TEST - K230_GNNE_COMMAND_BASE_OFFSET) +#define K230_GNNE_SYNTH_OUTPUT (K230_GNNE_SYNTH_GLB_BASE + 0x180) +#define K230_GNNE_SYNTH_LOAD_OUTPUT (K230_GNNE_SYNTH_GLB_BASE + 0x240) +#define K230_GNNE_SYNTH_WEIGHT_OUTPUT (K230_GNNE_SYNTH_GLB_BASE + 0x300) +#define K230_GNNE_SYNTH_STORE_SOURCE (K230_GNNE_SYNTH_GLB_BASE + 0x340) +#define K230_GNNE_SYNTH_ROUNDTRIP_OUTPUT (K230_GNNE_SYNTH_GLB_BASE + 0x380) +#define K230_GNNE_SYNTH_MFU_SOURCE (K230_GNNE_SYNTH_GLB_BASE + 0x400) +#define K230_GNNE_SYNTH_MFU_ARG (K230_GNNE_SYNTH_GLB_BASE + 0x420) +#define K230_GNNE_SYNTH_MFU_OUTPUT (K230_GNNE_SYNTH_GLB_BASE + 0x440) +#define K230_GNNE_SYNTH_MFU_ADD_OUTPUT (K230_GNNE_SYNTH_GLB_BASE + 0x460) +#define K230_GNNE_SYNTH_MFU_FP16_OUTPUT (K230_GNNE_SYNTH_GLB_BASE + 0x480) +#define K230_GNNE_SYNTH_MFU_ROUNDTRIP_OUTPUT \ + (K230_GNNE_SYNTH_GLB_BASE + 0x4a0) +#define K230_GNNE_SYNTH_CONV_INPUT (K230_GNNE_SYNTH_GLB_BASE + 0x500) +#define K230_GNNE_SYNTH_CONV_WEIGHT (K230_GNNE_SYNTH_GLB_BASE + 0x520) +#define K230_GNNE_SYNTH_CONV_WEIGHT_ZP (K230_GNNE_SYNTH_GLB_BASE + 0x590) +#define K230_GNNE_SYNTH_CONV_ACT0 (K230_GNNE_SYNTH_GLB_BASE + 0x5a0) +#define K230_GNNE_SYNTH_CONV_OUTPUT (K230_GNNE_SYNTH_GLB_BASE + 0x5c0) +#define K230_GNNE_SYNTH_PDP0_INPUT (K230_GNNE_SYNTH_GLB_BASE + 0x600) +#define K230_GNNE_SYNTH_PDP0_WEIGHT (K230_GNNE_SYNTH_GLB_BASE + 0x620) +#define K230_GNNE_SYNTH_PDP0_WEIGHT_ZP (K230_GNNE_SYNTH_GLB_BASE + 0x680) +#define K230_GNNE_SYNTH_PDP0_ACT0 (K230_GNNE_SYNTH_GLB_BASE + 0x690) +#define K230_GNNE_SYNTH_PDP0_OUTPUT (K230_GNNE_SYNTH_GLB_BASE + 0x6c0) +#define K230_GNNE_SYNTH_PSUM_INPUT (K230_GNNE_SYNTH_GLB_BASE + 0x700) +#define K230_GNNE_SYNTH_PSUM_WEIGHT0 (K230_GNNE_SYNTH_GLB_BASE + 0x720) +#define K230_GNNE_SYNTH_PSUM_WEIGHT1 (K230_GNNE_SYNTH_GLB_BASE + 0x740) +#define K230_GNNE_SYNTH_PSUM_WEIGHT_ZP (K230_GNNE_SYNTH_GLB_BASE + 0x760) +#define K230_GNNE_SYNTH_PSUM_ACT0 (K230_GNNE_SYNTH_GLB_BASE + 0x770) +#define K230_GNNE_SYNTH_PSUM_OUTPUT (K230_GNNE_SYNTH_GLB_BASE + 0x790) +#define K230_GNNE_SYNTH_PDP1_INPUT (K230_GNNE_SYNTH_GLB_BASE + 0x800) +#define K230_GNNE_SYNTH_PDP1_OUTPUT (K230_GNNE_SYNTH_GLB_BASE + 0x840) +#define K230_GNNE_SYNTH_CLASS_INPUT (K230_GNNE_SYNTH_GLB_BASE + 0x900) +#define K230_GNNE_SYNTH_CLASS_WEIGHT0 (K230_GNNE_SYNTH_GLB_BASE + 0x940) +#define K230_GNNE_SYNTH_CLASS_WEIGHT1 (K230_GNNE_SYNTH_GLB_BASE + 0xc40) +#define K230_GNNE_SYNTH_CLASS_WEIGHT_ZP (K230_GNNE_SYNTH_GLB_BASE + 0xf40) +#define K230_GNNE_SYNTH_CLASS_ACT0 (K230_GNNE_SYNTH_GLB_BASE + 0xf80) +#define K230_GNNE_SYNTH_CLASS_OUTPUT (K230_GNNE_SYNTH_GLB_BASE + 0x1180) +#define K230_GNNE_SYNTH_DECONV_INPUT_U8 (K230_GNNE_SYNTH_GLB_BASE + 0x1200) +#define K230_GNNE_SYNTH_DECONV_INPUT_I8 (K230_GNNE_SYNTH_GLB_BASE + 0x1220) +#define K230_GNNE_SYNTH_DECONV_ZERO_INPUT (K230_GNNE_SYNTH_GLB_BASE + 0x1240) +#define K230_GNNE_SYNTH_DECONV_WEIGHT (K230_GNNE_SYNTH_GLB_BASE + 0x1260) +#define K230_GNNE_SYNTH_DECONV_ZERO_WEIGHT (K230_GNNE_SYNTH_GLB_BASE + 0x12a0) +#define K230_GNNE_SYNTH_DECONV_WEIGHT_ZP (K230_GNNE_SYNTH_GLB_BASE + 0x12e0) +#define K230_GNNE_SYNTH_DECONV_ACT0 (K230_GNNE_SYNTH_GLB_BASE + 0x1300) +#define K230_GNNE_SYNTH_DECONV_OUTPUT (K230_GNNE_SYNTH_GLB_BASE + 0x1320) +#define K230_GNNE_SYNTH_DECONV_MULTI_INPUT (K230_GNNE_SYNTH_GLB_BASE + 0x1340) +#define K230_GNNE_SYNTH_DECONV_MULTI_WEIGHT (K230_GNNE_SYNTH_GLB_BASE + 0x1360) +#define K230_GNNE_SYNTH_DECONV_MULTI_WEIGHT_ZP \ + (K230_GNNE_SYNTH_GLB_BASE + 0x13c0) +#define K230_GNNE_SYNTH_DECONV_MULTI_ACT0 (K230_GNNE_SYNTH_GLB_BASE + 0x13e0) +#define K230_GNNE_SYNTH_DECONV_MULTI_OUTPUT \ + (K230_GNNE_SYNTH_GLB_BASE + 0x1400) +#define K230_GNNE_SYNTH_TRANSPOSE_INPUT \ + (K230_GNNE_SYNTH_GLB_BASE + 0x1500) +#define K230_GNNE_SYNTH_TRANSPOSE_OUTPUT \ + (K230_GNNE_SYNTH_GLB_BASE + 0x1600) +#define K230_GNNE_SYNTH_MFU_SEG_ARG \ + (K230_GNNE_SYNTH_GLB_BASE + 0x1700) +#define K230_GNNE_SYNTH_MFU_SEG_OUTPUT \ + (K230_GNNE_SYNTH_GLB_BASE + 0x1780) +#define K230_GNNE_SYNTH_SOURCE 0x02000000 + +#define GNNE_FIELD(value, shift) ((uint32_t)(value) << (shift)) +#define GNNE_LUI(rd, imm) \ + (0x02 | GNNE_FIELD(rd, 7) | GNNE_FIELD(imm, 12)) +#define GNNE_LW(rd, rs, offset) \ + (0x06 | GNNE_FIELD(rd, 7) | GNNE_FIELD(rs, 12) | \ + GNNE_FIELD((offset) & 0xfff, 20)) +#define GNNE_ADDI(rd, rs, imm) \ + (0x0e | GNNE_FIELD(rd, 7) | GNNE_FIELD(rs, 12) | \ + GNNE_FIELD((imm) & 0xfff, 20)) +#define GNNE_MMU_CONF(rstart, rdepth, id) \ + (0x44 | GNNE_FIELD(rstart, 7) | GNNE_FIELD(rdepth, 12) | \ + GNNE_FIELD(id, 17)) +#define GNNE_SS_PACK_SHAPE(rn, rc, rh, rw, rss) \ + (0x40 | GNNE_FIELD(rn, 7) | GNNE_FIELD(rc, 12) | \ + GNNE_FIELD(rh, 17) | GNNE_FIELD(rw, 22) | GNNE_FIELD(rss, 27)) +#define GNNE_SS_PACK_STRIDE(rn, rc, rh, rss) \ + (0x42 | GNNE_FIELD(rn, 7) | GNNE_FIELD(rc, 12) | \ + GNNE_FIELD(rh, 17) | GNNE_FIELD(rss, 27)) +#define GNNE_L2_LOAD_CONF(rstride_d, rstride_s, l2_dt, ddr_dt) \ + (0x46 | GNNE_FIELD(rstride_d, 7) | GNNE_FIELD(rstride_s, 10) | \ + GNNE_FIELD(l2_dt, 13) | GNNE_FIELD(ddr_dt, 15)) +#define GNNE_L2_LOAD_W_CONF(rlen_c, rlen_d, l2_dt, ddr_dt, decomp) \ + (0x48 | GNNE_FIELD(rlen_c, 7) | GNNE_FIELD(rlen_d, 12) | \ + GNNE_FIELD(l2_dt, 17) | GNNE_FIELD(ddr_dt, 19) | \ + GNNE_FIELD(decomp, 22)) +#define GNNE_L2_STORE_CONF(rstride_d, rstride_s, l2_dt, ddr_dt) \ + (0x4a | GNNE_FIELD(rstride_d, 7) | GNNE_FIELD(rstride_s, 10) | \ + GNNE_FIELD(l2_dt, 13) | GNNE_FIELD(ddr_dt, 15)) +#define GNNE_L2_LOAD(raddr_d, raddr_s, rshape) \ + (0x4c | GNNE_FIELD(raddr_d, 7) | GNNE_FIELD(raddr_s, 12) | \ + GNNE_FIELD(rshape, 17)) +#define GNNE_L2_STORE(raddr_d, raddr_s, rshape) \ + (0x4e | GNNE_FIELD(raddr_d, 7) | GNNE_FIELD(raddr_s, 12) | \ + GNNE_FIELD(rshape, 17)) +#define GNNE_L2_LOAD_W(raddr_d, raddr_s, rvalid_c_num) \ + (0x57 | GNNE_FIELD(raddr_d, 7) | GNNE_FIELD(raddr_s, 12) | \ + GNNE_FIELD(rvalid_c_num, 17)) +#define GNNE_AI2D_COMPUTE() 0x51 +#define GNNE_MFU_PDP1_CONF1(stride_w, stride_h, rstride_s, funct2, rstride_d) \ + (0x62 | GNNE_FIELD(0x01, 7) | GNNE_FIELD(stride_w, 12) | \ + GNNE_FIELD(stride_h, 17) | GNNE_FIELD(rstride_s, 22) | \ + GNNE_FIELD(funct2, 25) | GNNE_FIELD(rstride_d, 27)) +#define GNNE_MFU_PDP1_CONF2(rcount_w, rcount_h, rpe_h, rpe_last_h) \ + (0x62 | GNNE_FIELD(0x02, 7) | GNNE_FIELD(rcount_w, 12) | \ + GNNE_FIELD(rcount_h, 17) | GNNE_FIELD(rpe_h, 22) | \ + GNNE_FIELD(rpe_last_h, 27)) +#define GNNE_MFU_PDP1_CONF3(rpe_channels, rpe_last_channels, rpad_value, \ + sspad) \ + (0x62 | GNNE_FIELD(0x03, 7) | GNNE_FIELD(rpe_channels, 12) | \ + GNNE_FIELD(rpe_last_channels, 17) | GNNE_FIELD(rpad_value, 22) | \ + GNNE_FIELD(sspad, 27)) +#define GNNE_MFU_PDP1_CONF4(rwindow_w, rwindow_h, rscale, enable_h2c, \ + enable_bw) \ + (0x62 | GNNE_FIELD(0x04, 7) | GNNE_FIELD(rwindow_w, 12) | \ + GNNE_FIELD(rwindow_h, 17) | GNNE_FIELD(rscale, 22) | \ + GNNE_FIELD(enable_h2c, 27) | GNNE_FIELD(enable_bw, 28)) +#define GNNE_MFU_PDP1_CONF_DEQ(rscale, rbias, quant_type, rshift) \ + (0x62 | GNNE_FIELD(0x06, 7) | GNNE_FIELD(rscale, 12) | \ + GNNE_FIELD(rbias, 17) | GNNE_FIELD(quant_type, 22) | \ + GNNE_FIELD(rshift, 24)) +#define GNNE_MFU_PDP1_CONF_QUANT(rscale, rbias, quant_type, rshift) \ + (0x62 | GNNE_FIELD(0x07, 7) | GNNE_FIELD(rscale, 12) | \ + GNNE_FIELD(rbias, 17) | GNNE_FIELD(quant_type, 22) | \ + GNNE_FIELD(rshift, 24)) +#define GNNE_MFU_PDP1_COMPUTE(raddr_d, raddr_s, rshape) \ + (0x6a | GNNE_FIELD(raddr_d, 7) | GNNE_FIELD(raddr_s, 12) | \ + GNNE_FIELD(rshape, 17)) +#define GNNE_MFU_ACT1_CONF_STRIDE(rstride_s1, rstride_s2, rstride_d1) \ + (0x62 | GNNE_FIELD(0x08, 7) | GNNE_FIELD(rstride_s1, 12) | \ + GNNE_FIELD(rstride_s2, 15) | GNNE_FIELD(rstride_d1, 18)) +#define GNNE_MFU_ACT1_CONF_SRC1(rslice, rright_repeats, rslice_repeats, sid, \ + slice_loc) \ + (0x62 | GNNE_FIELD(0x09, 7) | GNNE_FIELD(rslice, 12) | \ + GNNE_FIELD(rright_repeats, 17) | GNNE_FIELD(rslice_repeats, 22) | \ + GNNE_FIELD(sid, 27) | GNNE_FIELD(slice_loc, 28)) +#define GNNE_MFU_ACT1_CONF_SRC2(rleft_repeats, rshape, sid, source_type) \ + (0x62 | GNNE_FIELD(0x0a, 7) | GNNE_FIELD(rleft_repeats, 12) | \ + GNNE_FIELD(rshape, 17) | GNNE_FIELD(sid, 20) | \ + GNNE_FIELD(source_type, 21)) +#define GNNE_MFU_ACT1_CONF_DEST(rlen, rshape) \ + (0x62 | GNNE_FIELD(0x0b, 7) | GNNE_FIELD(rlen, 12) | \ + GNNE_FIELD(rshape, 17)) +#define GNNE_MFU_ACT1_CONF_DEQ(rscale, rbias, quant_type, sid, rshift) \ + (0x62 | GNNE_FIELD(0x0c, 7) | GNNE_FIELD(rscale, 12) | \ + GNNE_FIELD(rbias, 17) | GNNE_FIELD(quant_type, 22) | \ + GNNE_FIELD(sid, 24) | GNNE_FIELD(rshift, 25)) +#define GNNE_MFU_ACT1_CONF_QUANT(quant_type, rshift) \ + (0x62 | GNNE_FIELD(0x0d, 7) | GNNE_FIELD(quant_type, 12) | \ + GNNE_FIELD(rshift, 14)) +#define GNNE_MFU_ACT1_CONF(funct4, is_by_channel, is_16_segments) \ + (0x62 | GNNE_FIELD(0x0e, 7) | GNNE_FIELD(funct4, 12) | \ + GNNE_FIELD(is_by_channel, 16) | GNNE_FIELD(is_16_segments, 17)) +#define GNNE_MFU_ACT1_COMPUTE(raddr_d1, raddr_s1, raddr_s2, raddr_arg) \ + (0x72 | GNNE_FIELD(raddr_d1, 7) | GNNE_FIELD(raddr_s1, 12) | \ + GNNE_FIELD(raddr_s2, 17) | GNNE_FIELD(raddr_arg, 22)) +#define GNNE_MFU_TRANSPOSE_CONF(rstride_d, rstride_s, l2_dt, permute) \ + (0x62 | GNNE_FIELD(rstride_d, 12) | GNNE_FIELD(rstride_s, 15) | \ + GNNE_FIELD(l2_dt, 18) | GNNE_FIELD(permute, 20)) +#define GNNE_MFU_TRANSPOSE(raddr_d, raddr_s, rshape) \ + (0x68 | GNNE_FIELD(raddr_d, 7) | GNNE_FIELD(raddr_s, 12) | \ + GNNE_FIELD(rshape, 17)) +#define GNNE_DM_LOAD_L1_CONF(tcu, pu, rstride_s, datatype, l1_type) \ + (0x50 | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(0, 13) | GNNE_FIELD(rstride_s, 17) | \ + GNNE_FIELD(datatype, 20) | GNNE_FIELD(l1_type, 22)) +#define GNNE_DM_LOAD_W_CONF(tcu, pu, kernel_h, kernel_w, rstride_oc) \ + (0x50 | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(1, 13) | GNNE_FIELD(kernel_h, 17) | \ + GNNE_FIELD(kernel_w, 22) | GNNE_FIELD(rstride_oc, 27)) +#define GNNE_DM_LOAD_W_CONF_DEQ(tcu, pu, quant_type) \ + (0x50 | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(2, 13) | GNNE_FIELD(quant_type, 17)) +#define GNNE_DM_LOAD_W_CONF2(tcu, pu, rgroups, rgoc) \ + (0x50 | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(5, 13) | GNNE_FIELD(rgroups, 17) | \ + GNNE_FIELD(rgoc, 22)) +#define GNNE_DM_LOAD_L1(tcu, pu, raddr_s, rhtoc_window, rshape, l1_type) \ + (0x52 | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(raddr_s, 13) | GNNE_FIELD(rhtoc_window, 18) | \ + GNNE_FIELD(rshape, 23) | GNNE_FIELD(l1_type, 26)) +#define GNNE_DM_LOAD_W(tcu, pu, raddr_s, raddr_bw, r_iochannels, dest_type) \ + (0x54 | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(raddr_s, 13) | GNNE_FIELD(raddr_bw, 18) | \ + GNNE_FIELD(r_iochannels, 23) | GNNE_FIELD(dest_type, 26)) +#define GNNE_DM_LOAD_ACT0(tcu, pu, raddr_s, rlen, dest_channel, by_channel) \ + (0x56 | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(raddr_s, 13) | GNNE_FIELD(rlen, 18) | \ + GNNE_FIELD(dest_channel, 23) | GNNE_FIELD(by_channel, 24)) +#define GNNE_DM_STORE_OF(tcu, pu, raddr_d, rshape, src_channel) \ + (0x58 | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(raddr_d, 13) | GNNE_FIELD(rshape, 18) | \ + GNNE_FIELD(src_channel, 21)) +#define GNNE_PU_FETCHIF_CONF1(tcu, pu, stride_w, stride_h, rstride_s) \ + (0x5a | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(0, 13) | GNNE_FIELD(stride_w, 17) | \ + GNNE_FIELD(stride_h, 22) | GNNE_FIELD(rstride_s, 27)) +#define GNNE_PU_FETCHIF_CONF2(tcu, pu, rgic, rgic_last) \ + (0x5a | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(1, 13) | GNNE_FIELD(rgic, 17) | \ + GNNE_FIELD(rgic_last, 22)) +#define GNNE_PU_FETCHIF_CONF3(tcu, pu, raddr_s, rgroups, rshape) \ + (0x5a | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(2, 13) | GNNE_FIELD(raddr_s, 17) | \ + GNNE_FIELD(rgroups, 22) | GNNE_FIELD(rshape, 27)) +#define GNNE_PU_FETCHIF_CONF4(tcu, pu, rpad_value, sspad) \ + (0x5a | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(3, 13) | GNNE_FIELD(rpad_value, 17) | \ + GNNE_FIELD(sspad, 27)) +#define GNNE_PU_FETCHIF_CONF_DEQ(tcu, pu, ric, rbx, quant_type) \ + (0x5a | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(4, 13) | GNNE_FIELD(ric, 17) | GNNE_FIELD(rbx, 22) | \ + GNNE_FIELD(quant_type, 27)) +#define GNNE_PU_W_CONF(tcu, pu, kernel_h, kernel_w) \ + (0x5a | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(5, 13) | GNNE_FIELD(kernel_h, 17) | \ + GNNE_FIELD(kernel_w, 22)) +#define GNNE_PU_OF_CONF1(tcu, pu, rgoc, rgoc_last, rstride_d) \ + (0x5a | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(6, 13) | GNNE_FIELD(rgoc, 17) | \ + GNNE_FIELD(rgoc_last, 22) | GNNE_FIELD(rstride_d, 27)) +#define GNNE_PU_OF_CONF2(tcu, pu, raddr_d, rshape_d) \ + (0x5a | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(7, 13) | GNNE_FIELD(raddr_d, 17) | \ + GNNE_FIELD(rshape_d, 27)) +#define GNNE_PU_COMPUTE_CONF(tcu, pu, load_psum, clr_psum, dest_target, \ + release_if, mode) \ + (0x5a | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(8, 13) | GNNE_FIELD(load_psum, 17) | \ + GNNE_FIELD(clr_psum, 18) | GNNE_FIELD(dest_target, 19) | \ + GNNE_FIELD(release_if, 20) | GNNE_FIELD(mode, 21)) +#define GNNE_ACT0_SRC1_CONF(tcu, pu, channel, rshape, rshift_bits) \ + (0x60 | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(channel, 16) | GNNE_FIELD(rshape, 17) | \ + GNNE_FIELD(rshift_bits, 20)) +#define GNNE_ACT0_COMPUTE(raddr_d, tcu, channel, target, datatype, by_channel) \ + (0x74 | GNNE_FIELD(raddr_d, 12) | GNNE_FIELD(tcu, 17) | \ + GNNE_FIELD(channel, 20) | GNNE_FIELD(target, 21) | \ + GNNE_FIELD(datatype, 23) | GNNE_FIELD(by_channel, 25)) +#define GNNE_PU_COMPUTE(tcu, of_shift_mode) \ + (0x4d | GNNE_FIELD(tcu, 7) | GNNE_FIELD(of_shift_mode, 10)) +#define GNNE_PDP0_MODE_CONF(tcu, pu, mode) \ + (0x5e | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(0, 13) | GNNE_FIELD(mode, 17)) +#define GNNE_PDP0_FETCHIF_CONF1(tcu, pu, stride_w, stride_h) \ + (0x5e | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(1, 13) | GNNE_FIELD(stride_w, 17) | \ + GNNE_FIELD(stride_h, 22)) +#define GNNE_PDP0_FETCHIF_CONF2(tcu, pu, rgic, rgic_last) \ + (0x5e | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(2, 13) | GNNE_FIELD(rgic, 17) | \ + GNNE_FIELD(rgic_last, 22)) +#define GNNE_PDP0_FETCHIF_CONF3(tcu, pu, rshape) \ + (0x5e | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(3, 13) | GNNE_FIELD(rshape, 27)) +#define GNNE_PDP0_FETCHIF_CONF4(tcu, pu, rpad_value, sspad) \ + (0x5e | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(4, 13) | GNNE_FIELD(rpad_value, 17) | \ + GNNE_FIELD(sspad, 22)) +#define GNNE_PDP0_FETCHIF_CONF_DEQ(tcu, pu, rbx, quant_type) \ + (0x5e | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(5, 13) | GNNE_FIELD(rbx, 17) | \ + GNNE_FIELD(quant_type, 22)) +#define GNNE_PDP0_W_CONF(tcu, pu, kernel_h, kernel_w) \ + (0x5e | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(6, 13) | GNNE_FIELD(kernel_h, 17) | \ + GNNE_FIELD(kernel_w, 22)) +#define GNNE_PDP0_OF_CONF(tcu, pu, rstride_d, rshape_d) \ + (0x5e | GNNE_FIELD(tcu, 7) | GNNE_FIELD(pu, 10) | \ + GNNE_FIELD(7, 13) | GNNE_FIELD(rstride_d, 17) | \ + GNNE_FIELD(rshape_d, 20)) +#define GNNE_PDP0_COMPUTE(tcu, raddr_s) \ + (0x4f | GNNE_FIELD(tcu, 7) | GNNE_FIELD(raddr_s, 10)) + +static void k230_plic_enable_irq(QTestState *qts, unsigned int irq) +{ + uint32_t enable; + uint64_t enable_addr; + + qtest_writel(qts, K230_PLIC_BASE + irq * 4, 1); + enable_addr = K230_PLIC_BASE + K230_PLIC_ENABLE_S + (irq / 32) * 4; + enable = qtest_readl(qts, enable_addr); + qtest_writel(qts, enable_addr, enable | (1u << (irq % 32))); + qtest_writel(qts, K230_PLIC_BASE + K230_PLIC_CONTEXT_S + + K230_PLIC_THRESHOLD, 0); +} + +static uint32_t k230_plic_claim(QTestState *qts) +{ + return qtest_readl(qts, K230_PLIC_BASE + K230_PLIC_CONTEXT_S + + K230_PLIC_CLAIM); +} + +static void k230_plic_complete(QTestState *qts, unsigned int irq) +{ + qtest_writel(qts, K230_PLIC_BASE + K230_PLIC_CONTEXT_S + + K230_PLIC_CLAIM, irq); +} + +static void k230_assert_page_byte(QTestState *qts, uint64_t addr, + uint8_t expected) +{ + static const uint64_t offsets[] = { + 0, + K230_KPU_PAGE_SIZE / 2, + K230_KPU_PAGE_SIZE - 32, + }; + uint8_t data[32]; + + for (size_t i = 0; i < G_N_ELEMENTS(offsets); i++) { + qtest_memread(qts, addr + offsets[i], data, sizeof(data)); + for (size_t j = 0; j < sizeof(data); j++) { + g_assert_cmphex(data[j], ==, expected); + } + } +} + +static QTestState *k230_kpu_init(void) +{ + QTestState *qts = qtest_init("-machine k230-canmv"); + + k230_plic_enable_irq(qts, K230_GNNE_IRQ); + g_assert_cmphex(qtest_readq(qts, K230_KPU_CFG_BASE + K230_GNNE_STATUS), + ==, 0); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + + return qts; +} + +static void k230_kpu_set_command_range(QTestState *qts, uint32_t start, + uint32_t end) +{ + qtest_writel(qts, K230_KPU_CFG_BASE + K230_GNNE_COMMAND_START, start); + qtest_writel(qts, K230_KPU_CFG_BASE + K230_GNNE_COMMAND_END, end); +} + +static void k230_kpu_write_commands(QTestState *qts, const uint32_t *commands, + size_t command_count) +{ + for (size_t i = 0; i < command_count; i++) { + qtest_writel(qts, K230_GNNE_COMMAND_TEST + i * sizeof(commands[0]), + commands[i]); + } +} + +static void k230_kpu_start(QTestState *qts) +{ + qtest_writeq(qts, K230_KPU_CFG_BASE + K230_GNNE_CONTROL, + K230_GNNE_START); +} + +static void k230_kpu_run_commands(QTestState *qts, const uint32_t *commands, + size_t command_count) +{ + k230_kpu_write_commands(qts, commands, command_count); + k230_kpu_set_command_range(qts, K230_GNNE_COMMAND_TEST, + K230_GNNE_COMMAND_TEST + + command_count * sizeof(commands[0])); + k230_kpu_start(qts); + qtest_clock_step(qts, K230_GNNE_DELAY_NS); +} + +static void k230_kpu_command_u32(uint8_t *commands, size_t *offset, + uint32_t word) +{ + stl_le_p(commands + *offset, word); + *offset += sizeof(word); +} + +static void k230_kpu_command_u16(uint8_t *commands, size_t *offset, + uint16_t word) +{ + stw_le_p(commands + *offset, word); + *offset += sizeof(word); +} + +static uint16_t k230_test_fp16_from_uint(unsigned int value) +{ + unsigned int exp = 0; + unsigned int norm = value; + unsigned int frac; + + g_assert_cmpuint(value, >, 0); + g_assert_cmpuint(value, <, 1024); + + while (norm > 1) { + norm >>= 1; + exp++; + } + frac = (value << (10 - exp)) & 0x3ff; + return ((exp + 15) << 10) | frac; +} + +static void k230_kpu_run_command_bytes(QTestState *qts, + const uint8_t *commands, size_t size) +{ + qtest_memwrite(qts, K230_GNNE_COMMAND_TEST, commands, size); + k230_kpu_set_command_range(qts, K230_GNNE_COMMAND_TEST, + K230_GNNE_COMMAND_TEST + size); + k230_kpu_start(qts); + qtest_clock_step(qts, K230_GNNE_DELAY_NS); +} + +static void k230_kpu_run_command_bytes_at(QTestState *qts, uint32_t start, + const uint8_t *commands, + size_t size) +{ + qtest_memwrite(qts, start, commands, size); + k230_kpu_set_command_range(qts, start, start + size); + k230_kpu_start(qts); + qtest_clock_step(qts, K230_GNNE_DELAY_NS); +} + +static void k230_kpu_assert_done_irq(QTestState *qts) +{ + g_assert_cmphex(qtest_readq(qts, K230_KPU_CFG_BASE + K230_GNNE_STATUS), + ==, K230_GNNE_DONE); + g_assert_cmphex(k230_plic_claim(qts), ==, K230_GNNE_IRQ); +} + +static void k230_kpu_clear_done_irq(QTestState *qts) +{ + qtest_writeq(qts, K230_KPU_CFG_BASE + K230_GNNE_CLEAR, K230_GNNE_DONE); + g_assert_cmphex(qtest_readq(qts, K230_KPU_CFG_BASE + K230_GNNE_STATUS), + ==, 0); + k230_plic_complete(qts, K230_GNNE_IRQ); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); +} + +static void test_zero_start_does_not_complete(void) +{ + QTestState *qts = k230_kpu_init(); + const uint32_t commands[] = { + K230_KPU_OUTPUT_TEST0 | 2, + }; + + qtest_memset(qts, K230_KPU_OUTPUT_TEST0, 0xa5, K230_KPU_PAGE_SIZE); + k230_kpu_write_commands(qts, commands, G_N_ELEMENTS(commands)); + k230_kpu_set_command_range(qts, K230_GNNE_COMMAND_TEST, + K230_GNNE_COMMAND_TEST + sizeof(commands)); + qtest_writel(qts, K230_KPU_CFG_BASE + K230_GNNE_COMMAND_HI, 0x12345678); + + qtest_writeq(qts, K230_KPU_CFG_BASE + K230_GNNE_CONTROL, 0); + qtest_writel(qts, K230_KPU_CFG_BASE + K230_GNNE_SETUP, 0x30d40); + qtest_clock_step(qts, K230_GNNE_DELAY_NS); + + g_assert_cmphex(qtest_readl(qts, K230_KPU_CFG_BASE + + K230_GNNE_COMMAND_HI), ==, 0x12345678); + g_assert_cmphex(qtest_readq(qts, K230_KPU_CFG_BASE + K230_GNNE_STATUS), + ==, 0); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + k230_assert_page_byte(qts, K230_KPU_OUTPUT_TEST0, 0xa5); + + qtest_quit(qts); +} + +static void test_delayed_completion(void) +{ + QTestState *qts = k230_kpu_init(); + const uint32_t commands[] = { + K230_KPU_OUTPUT_TEST0 | 2, + }; + + qtest_memset(qts, K230_KPU_OUTPUT_TEST0, 0xa5, K230_KPU_PAGE_SIZE); + k230_kpu_write_commands(qts, commands, G_N_ELEMENTS(commands)); + k230_kpu_set_command_range(qts, K230_GNNE_COMMAND_TEST, + K230_GNNE_COMMAND_TEST + sizeof(commands)); + k230_kpu_start(qts); + + g_assert_cmphex(qtest_readq(qts, K230_KPU_CFG_BASE + K230_GNNE_STATUS), + ==, 0); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + k230_assert_page_byte(qts, K230_KPU_OUTPUT_TEST0, 0xa5); + + qtest_clock_step(qts, K230_GNNE_DELAY_NS / 2); + g_assert_cmphex(qtest_readq(qts, K230_KPU_CFG_BASE + K230_GNNE_STATUS), + ==, 0); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + k230_assert_page_byte(qts, K230_KPU_OUTPUT_TEST0, 0xa5); + + qtest_clock_step(qts, K230_GNNE_DELAY_NS - K230_GNNE_DELAY_NS / 2); + k230_assert_page_byte(qts, K230_KPU_OUTPUT_TEST0, 0); + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_command_completion_pages(void) +{ + QTestState *qts = k230_kpu_init(); + const uint32_t commands[] = { + K230_KPU_OUTPUT_TEST0 | 2, + K230_KPU_OUTSIDE_LOW | 2, + K230_KPU_OUTPUT_TEST1 + 0x40, + (K230_KPU_OUTPUT_TEST0 + 0x80) | 2, + }; + + qtest_memset(qts, K230_KPU_OUTPUT_TEST0, 0xa5, K230_KPU_PAGE_SIZE); + qtest_memset(qts, K230_KPU_OUTPUT_TEST1, 0x5a, K230_KPU_PAGE_SIZE); + qtest_memset(qts, K230_KPU_OUTPUT_UNREFERENCED, 0xc3, + K230_KPU_PAGE_SIZE); + qtest_memset(qts, K230_KPU_OUTSIDE_LOW, 0x3c, K230_KPU_PAGE_SIZE); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + + k230_assert_page_byte(qts, K230_KPU_OUTPUT_TEST0, 0); + k230_assert_page_byte(qts, K230_KPU_OUTPUT_TEST1, 0); + k230_assert_page_byte(qts, K230_KPU_OUTPUT_UNREFERENCED, 0xc3); + k230_assert_page_byte(qts, K230_KPU_OUTSIDE_LOW, 0x3c); + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_same_page_addresses_zero_one_page(void) +{ + QTestState *qts = k230_kpu_init(); + const uint32_t commands[] = { + K230_KPU_OUTPUT_TEST0 + 0x20, + K230_KPU_OUTPUT_TEST0 + 0x8f0, + K230_KPU_OUTPUT_TEST0 + 0xffc, + }; + + qtest_memset(qts, K230_KPU_OUTPUT_TEST0, 0xa5, K230_KPU_PAGE_SIZE); + qtest_memset(qts, K230_KPU_OUTPUT_TEST0 + K230_KPU_PAGE_SIZE, 0x5a, + K230_KPU_PAGE_SIZE); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + + k230_assert_page_byte(qts, K230_KPU_OUTPUT_TEST0, 0); + k230_assert_page_byte(qts, K230_KPU_OUTPUT_TEST0 + K230_KPU_PAGE_SIZE, + 0x5a); + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_output_window_boundaries(void) +{ + QTestState *qts = k230_kpu_init(); + const uint32_t commands[] = { + K230_KPU_OUTPUT_TEST0 + 1, + K230_KPU_OUTPUT_LAST + 0x80, + K230_KPU_OUTSIDE_LOW + 2, + K230_KPU_OUTSIDE_HIGH, + }; + + qtest_memset(qts, K230_KPU_OUTPUT_TEST0, 0xa5, K230_KPU_PAGE_SIZE); + qtest_memset(qts, K230_KPU_OUTPUT_LAST, 0x5a, K230_KPU_PAGE_SIZE); + qtest_memset(qts, K230_KPU_OUTSIDE_LOW, 0x3c, K230_KPU_PAGE_SIZE); + qtest_memset(qts, K230_KPU_OUTSIDE_HIGH, 0xc3, K230_KPU_PAGE_SIZE); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + + k230_assert_page_byte(qts, K230_KPU_OUTPUT_TEST0, 0); + k230_assert_page_byte(qts, K230_KPU_OUTPUT_LAST, 0); + k230_assert_page_byte(qts, K230_KPU_OUTSIDE_LOW, 0x3c); + k230_assert_page_byte(qts, K230_KPU_OUTSIDE_HIGH, 0xc3); + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_empty_command_range(void) +{ + QTestState *qts = k230_kpu_init(); + + qtest_memset(qts, K230_KPU_OUTPUT_TEST0, 0xa5, K230_KPU_PAGE_SIZE); + + k230_kpu_set_command_range(qts, 0, 0); + k230_kpu_start(qts); + qtest_clock_step(qts, K230_GNNE_DELAY_NS); + + k230_assert_page_byte(qts, K230_KPU_OUTPUT_TEST0, 0xa5); + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_reversed_command_range(void) +{ + QTestState *qts = k230_kpu_init(); + const uint32_t commands[] = { + K230_KPU_OUTPUT_TEST0 | 2, + }; + + qtest_memset(qts, K230_KPU_OUTPUT_TEST0, 0xa5, K230_KPU_PAGE_SIZE); + k230_kpu_write_commands(qts, commands, G_N_ELEMENTS(commands)); + k230_kpu_set_command_range(qts, K230_GNNE_COMMAND_TEST + sizeof(commands), + K230_GNNE_COMMAND_TEST); + k230_kpu_start(qts); + qtest_clock_step(qts, K230_GNNE_DELAY_NS); + + k230_assert_page_byte(qts, K230_KPU_OUTPUT_TEST0, 0xa5); + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_clear_status_allows_second_run(void) +{ + QTestState *qts = k230_kpu_init(); + const uint32_t first_commands[] = { + K230_KPU_OUTPUT_TEST0 | 2, + }; + const uint32_t second_commands[] = { + K230_KPU_OUTPUT_TEST1 | 2, + }; + + qtest_memset(qts, K230_KPU_OUTPUT_TEST0, 0xa5, K230_KPU_PAGE_SIZE); + qtest_memset(qts, K230_KPU_OUTPUT_TEST1, 0x5a, K230_KPU_PAGE_SIZE); + + k230_kpu_run_commands(qts, first_commands, G_N_ELEMENTS(first_commands)); + k230_assert_page_byte(qts, K230_KPU_OUTPUT_TEST0, 0); + k230_assert_page_byte(qts, K230_KPU_OUTPUT_TEST1, 0x5a); + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + g_assert_cmphex(qtest_readq(qts, K230_KPU_CFG_BASE + K230_GNNE_STATUS), + ==, 0); + k230_kpu_run_commands(qts, second_commands, G_N_ELEMENTS(second_commands)); + k230_assert_page_byte(qts, K230_KPU_OUTPUT_TEST1, 0); + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_store_copies_parsed_output(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x180), + GNNE_ADDI(7, 0, 0x340), + GNNE_ADDI(4, 0, 1), + GNNE_ADDI(5, 0, sizeof(source)), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(4, 4, 4, 5, 0), + GNNE_SS_PACK_STRIDE(5, 5, 5, 0), + GNNE_SS_PACK_STRIDE(5, 5, 5, 1), + GNNE_L2_STORE_CONF(1, 0, 0, 0), + GNNE_L2_STORE(3, 7, 0), + }; + uint8_t data[32]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_STORE_SOURCE, + source, sizeof(source)); + qtest_memset(qts, K230_GNNE_SYNTH_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_OUTPUT, data, sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(source) ? source[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_store_converts_fp16_to_fp32(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0x00, 0x3c, /* fp16 1.0 */ + 0x00, 0x40, /* fp16 2.0 */ + }; + const uint8_t expected[] = { + 0x00, 0x00, 0x80, 0x3f, /* fp32 1.0 */ + 0x00, 0x00, 0x00, 0x40, /* fp32 2.0 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x340), + GNNE_ADDI(3, 0, 0x180), + GNNE_ADDI(4, 0, 1), + GNNE_ADDI(5, 0, 2), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(4, 4, 4, 5, 0), + GNNE_SS_PACK_STRIDE(5, 5, 5, 0), + GNNE_SS_PACK_STRIDE(5, 5, 5, 1), + GNNE_L2_STORE_CONF(1, 0, 1, 2), + GNNE_L2_STORE(3, 2, 0), + }; + uint8_t data[12]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_STORE_SOURCE, + source, sizeof(source)); + qtest_memset(qts, K230_GNNE_SYNTH_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_OUTPUT, data, sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(expected) ? expected[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_load_copies_to_glb(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + }; + const uint32_t commands[] = { + GNNE_LUI(6, K230_GNNE_SYNTH_SOURCE >> 12), + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x240), + GNNE_ADDI(4, 0, 1), + GNNE_ADDI(5, 0, sizeof(source)), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(4, 4, 4, 5, 0), + GNNE_SS_PACK_STRIDE(5, 5, 5, 0), + GNNE_SS_PACK_STRIDE(5, 5, 5, 1), + GNNE_L2_LOAD_CONF(1, 0, 0, 0), + GNNE_L2_LOAD(3, 6, 0), + }; + uint8_t data[16]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_SOURCE, source, sizeof(source)); + qtest_memset(qts, K230_GNNE_SYNTH_LOAD_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_LOAD_OUTPUT, data, sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(source) ? source[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_load_uses_packed_strides(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0x11, 0x12, 0xa5, 0xa5, 0xa5, 0x21, 0x22, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0x31, + 0x32, 0xa5, 0xa5, 0xa5, 0x41, 0x42, 0xa5, 0xa5, + }; + const uint8_t expected[] = { + 0x11, 0x12, 0x21, 0x22, 0x31, 0x32, 0x41, 0x42, + }; + const uint32_t commands[] = { + GNNE_LUI(6, K230_GNNE_SYNTH_SOURCE >> 12), + GNNE_ADDI(3, 0, 0x240), + GNNE_ADDI(4, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 3), + GNNE_ADDI(11, 0, 5), + GNNE_MMU_CONF(0, 9, 0), + GNNE_SS_PACK_SHAPE(4, 9, 9, 9, 0), + GNNE_SS_PACK_STRIDE(9, 10, 11, 0), + GNNE_SS_PACK_STRIDE(9, 9, 9, 1), + GNNE_L2_LOAD_CONF(1, 0, 0, 0), + GNNE_L2_LOAD(3, 6, 0), + }; + uint8_t data[12]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_SOURCE, source, sizeof(source)); + qtest_memset(qts, K230_GNNE_SYNTH_LOAD_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_LOAD_OUTPUT, data, sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(expected) ? expected[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_store_uses_packed_strides(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0x11, 0x12, 0xa5, 0xa5, 0xa5, 0x21, 0x22, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0x31, + 0x32, 0xa5, 0xa5, 0xa5, 0x41, 0x42, 0xa5, 0xa5, + }; + const uint8_t expected[] = { + 0x11, 0x12, 0x21, 0x22, 0x31, 0x32, 0x41, 0x42, + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x340), + GNNE_ADDI(3, 0, 0x180), + GNNE_ADDI(4, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 3), + GNNE_ADDI(11, 0, 5), + GNNE_MMU_CONF(0, 9, 0), + GNNE_SS_PACK_SHAPE(4, 9, 9, 9, 0), + GNNE_SS_PACK_STRIDE(9, 10, 11, 0), + GNNE_SS_PACK_STRIDE(9, 9, 9, 1), + GNNE_L2_STORE_CONF(1, 0, 0, 0), + GNNE_L2_STORE(3, 2, 0), + }; + uint8_t data[12]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_STORE_SOURCE, + source, sizeof(source)); + qtest_memset(qts, K230_GNNE_SYNTH_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_OUTPUT, data, sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(expected) ? expected[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_store_reads_high_mmu0_glb(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0x6a, 0x6b, 0x6c, 0x6d, + }; + const uint32_t commands[] = { + GNNE_LUI(2, 0x12), + GNNE_ADDI(3, 0, 0x180), + GNNE_ADDI(4, 0, 1), + GNNE_ADDI(5, 0, sizeof(source)), + GNNE_MMU_CONF(0, 4, 0), + GNNE_SS_PACK_SHAPE(4, 4, 4, 5, 0), + GNNE_SS_PACK_STRIDE(5, 5, 5, 0), + GNNE_SS_PACK_STRIDE(5, 5, 5, 1), + GNNE_L2_STORE_CONF(1, 0, 0, 0), + GNNE_L2_STORE(3, 2, 0), + }; + uint8_t data[8]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_GLB_BASE + 0x12000, + source, sizeof(source)); + qtest_memset(qts, K230_GNNE_SYNTH_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_OUTPUT, data, sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(source) ? source[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_nonzero_bank_uses_raw_offset(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t expected[] = { + 0x31, 0x32, 0x33, 0x34, + }; + const uint8_t poison[] = { + 0xa1, 0xa2, 0xa3, 0xa4, + }; + const uint32_t commands[] = { + GNNE_LUI(2, 0x10000), + GNNE_ADDI(2, 2, 0x500), + GNNE_LUI(3, 0x10000), + GNNE_ADDI(3, 3, 0x700), + GNNE_ADDI(4, 0, 0x180), + GNNE_ADDI(5, 0, 0x184), + GNNE_ADDI(6, 0, 1), + GNNE_ADDI(7, 0, sizeof(expected)), + GNNE_LUI(8, K230_GNNE_SYNTH_SOURCE >> 12), + GNNE_LUI(9, K230_GNNE_SYNTH_SOURCE >> 12), + GNNE_ADDI(9, 9, 0x100), + GNNE_ADDI(10, 0, 0), + GNNE_ADDI(11, 0, 0x20), + GNNE_ADDI(12, 0, 0x10), + GNNE_MMU_CONF(10, 11, 0), + GNNE_MMU_CONF(10, 11, 1), + GNNE_SS_PACK_SHAPE(6, 6, 6, 7, 0), + GNNE_SS_PACK_STRIDE(7, 7, 7, 0), + GNNE_SS_PACK_STRIDE(7, 7, 7, 1), + GNNE_L2_LOAD_CONF(1, 0, 0, 0), + GNNE_L2_LOAD(3, 9, 0), + GNNE_MMU_CONF(12, 11, 1), + GNNE_L2_LOAD(2, 8, 0), + GNNE_MMU_CONF(10, 11, 1), + GNNE_L2_LOAD(3, 9, 0), + GNNE_L2_STORE_CONF(1, 0, 0, 0), + GNNE_L2_STORE(4, 2, 0), + GNNE_MMU_CONF(12, 11, 1), + GNNE_L2_STORE(5, 2, 0), + }; + uint8_t data[8]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_SOURCE, expected, + sizeof(expected)); + qtest_memwrite(qts, K230_GNNE_SYNTH_SOURCE + 0x100, poison, + sizeof(poison)); + qtest_memset(qts, K230_GNNE_SYNTH_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_OUTPUT, data, sizeof(data)); + + for (size_t i = 0; i < sizeof(expected); i++) { + g_assert_cmphex(data[i], ==, expected[i]); + g_assert_cmphex(data[i + sizeof(expected)], ==, expected[i]); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_bank_write_updates_logical_glb(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t expected[] = { + 0x41, 0x42, 0x43, 0x44, + }; + const uint8_t poison[] = { + 0xa1, 0xa2, 0xa3, 0xa4, + }; + const uint32_t commands[] = { + GNNE_LUI(2, 0x10000), + GNNE_ADDI(3, 0, 0x200), + GNNE_ADDI(4, 0, 0x180), + GNNE_ADDI(6, 0, 1), + GNNE_ADDI(7, 0, sizeof(expected)), + GNNE_LUI(8, K230_GNNE_SYNTH_SOURCE >> 12), + GNNE_ADDI(10, 0, 0x10), + GNNE_ADDI(11, 0, 0x20), + GNNE_MMU_CONF(0, 11, 0), + GNNE_MMU_CONF(10, 11, 1), + GNNE_SS_PACK_SHAPE(6, 6, 6, 7, 0), + GNNE_SS_PACK_STRIDE(7, 7, 7, 0), + GNNE_SS_PACK_STRIDE(7, 7, 7, 1), + GNNE_L2_LOAD_CONF(1, 0, 0, 0), + GNNE_L2_LOAD(2, 8, 0), + GNNE_L2_STORE_CONF(1, 0, 0, 0), + GNNE_L2_STORE(4, 3, 0), + }; + uint8_t data[8]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_SOURCE, expected, + sizeof(expected)); + qtest_memwrite(qts, K230_GNNE_SYNTH_GLB_BASE + 0x200, poison, + sizeof(poison)); + qtest_memset(qts, K230_GNNE_SYNTH_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_OUTPUT, data, sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(expected) ? expected[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_load_converts_fp32_to_fp16(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0x00, 0x00, 0x80, 0x3f, /* fp32 1.0 */ + 0x00, 0x00, 0x00, 0x40, /* fp32 2.0 */ + 0x00, 0x00, 0xc0, 0x7f, /* fp32 qNaN */ + }; + const uint8_t expected[] = { + 0x00, 0x3c, /* fp16 1.0 */ + 0x00, 0x40, /* fp16 2.0 */ + 0xff, 0x7b, /* fp16 max finite */ + }; + const uint32_t commands[] = { + GNNE_LUI(6, K230_GNNE_SYNTH_SOURCE >> 12), + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x240), + GNNE_ADDI(4, 0, 1), + GNNE_ADDI(5, 0, 3), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(4, 4, 4, 5, 0), + GNNE_SS_PACK_STRIDE(5, 5, 5, 0), + GNNE_SS_PACK_STRIDE(5, 5, 5, 1), + GNNE_L2_LOAD_CONF(1, 0, 1, 2), + GNNE_L2_LOAD(3, 6, 0), + }; + uint8_t data[8]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_SOURCE, source, sizeof(source)); + qtest_memset(qts, K230_GNNE_SYNTH_LOAD_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_LOAD_OUTPUT, data, sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(expected) ? expected[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_load_then_store_roundtrip(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + }; + const uint32_t commands[] = { + GNNE_LUI(6, K230_GNNE_SYNTH_SOURCE >> 12), + GNNE_ADDI(6, 6, 0x200), + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x240), + GNNE_ADDI(7, 0, 0x380), + GNNE_ADDI(4, 0, 1), + GNNE_ADDI(5, 0, sizeof(source)), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(4, 4, 4, 5, 0), + GNNE_SS_PACK_STRIDE(5, 5, 5, 0), + GNNE_SS_PACK_STRIDE(5, 5, 5, 1), + GNNE_L2_LOAD_CONF(1, 0, 0, 0), + GNNE_L2_LOAD(3, 6, 0), + GNNE_L2_STORE_CONF(1, 0, 0, 0), + GNNE_L2_STORE(7, 3, 0), + }; + uint8_t data[16]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_SOURCE + 0x200, + source, sizeof(source)); + qtest_memset(qts, K230_GNNE_SYNTH_ROUNDTRIP_OUTPUT, 0xa5, + sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_ROUNDTRIP_OUTPUT, data, + sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(source) ? source[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_ai2d_compute_preserves_command_stream(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, + }; + uint8_t commands[128]; + uint8_t data[16]; + size_t command_size = 0; + + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(2, 0, 0x100)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(3, 0, 0x380)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(7, 0, 0x340)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(4, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ADDI(5, 0, sizeof(source))); + k230_kpu_command_u32(commands, &command_size, GNNE_MMU_CONF(0, 2, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_SHAPE(4, 4, 4, 5, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(5, 5, 5, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(5, 5, 5, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_L2_STORE_CONF(1, 0, 0, 0)); + k230_kpu_command_u16(commands, &command_size, GNNE_AI2D_COMPUTE()); + k230_kpu_command_u32(commands, &command_size, GNNE_L2_STORE(3, 7, 0)); + + qtest_memwrite(qts, K230_GNNE_SYNTH_STORE_SOURCE, + source, sizeof(source)); + qtest_memset(qts, K230_GNNE_SYNTH_ROUNDTRIP_OUTPUT, 0xa5, + sizeof(data)); + + k230_kpu_run_command_bytes(qts, commands, command_size); + qtest_memread(qts, K230_GNNE_SYNTH_ROUNDTRIP_OUTPUT, data, + sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(source) ? source[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_load_w_uses_lane_layout(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, + }; + const uint32_t commands[] = { + GNNE_LUI(6, K230_GNNE_SYNTH_SOURCE >> 12), + GNNE_ADDI(6, 6, 0x100), + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x300), + GNNE_ADDI(5, 0, sizeof(source)), + GNNE_ADDI(8, 0, 3), + GNNE_MMU_CONF(0, 2, 0), + GNNE_L2_LOAD_W_CONF(5, 5, 0, 0, 0), + GNNE_L2_LOAD_W(3, 6, 8), + }; + uint8_t data[32]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_SOURCE + 0x100, + source, sizeof(source)); + qtest_memset(qts, K230_GNNE_SYNTH_WEIGHT_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_WEIGHT_OUTPUT, data, sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + uint8_t expected = 0xa5; + + if (i < 4) { + expected = source[i]; + } else if (i >= 24 && i < 28) { + expected = source[i - 20]; + } + g_assert_cmphex(data[i], ==, expected); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_load_w_conf_latches_rlen(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0x30, 0x31, 0x32, 0x33, 0x40, 0x41, 0x42, 0x43, + }; + const uint32_t commands[] = { + GNNE_LUI(6, K230_GNNE_SYNTH_SOURCE >> 12), + GNNE_ADDI(6, 6, 0x120), + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x300), + GNNE_ADDI(5, 0, sizeof(source)), + GNNE_ADDI(8, 0, 3), + GNNE_MMU_CONF(0, 2, 0), + GNNE_L2_LOAD_W_CONF(5, 5, 0, 0, 0), + GNNE_ADDI(5, 6, 0), + GNNE_L2_LOAD_W(3, 5, 8), + }; + uint8_t data[32]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_SOURCE + 0x120, + source, sizeof(source)); + qtest_memset(qts, K230_GNNE_SYNTH_WEIGHT_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_WEIGHT_OUTPUT, data, sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + uint8_t expected = 0xa5; + + if (i < 4) { + expected = source[i]; + } else if (i >= 24 && i < 28) { + expected = source[i - 20]; + } + g_assert_cmphex(data[i], ==, expected); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_load_w_translates_low_source(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t raw_source[] = { + 0x11, 0x12, 0x13, 0x14, + }; + const uint8_t translated_source[] = { + 0x91, 0x92, 0x93, 0x94, + }; + uint8_t commands[128]; + uint8_t data[16]; + size_t command_size = 0; + + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(2, 0, 0x20)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(3, 0, 0x180)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(5, 0, 4)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(6, 0, 0x100)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(7, 0, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(8, 0, 3)); + k230_kpu_command_u32(commands, &command_size, GNNE_MMU_CONF(2, 7, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_L2_LOAD_W_CONF(5, 5, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_L2_LOAD_W(3, 6, 8)); + + qtest_memwrite(qts, K230_GNNE_RUNTIME_DDR_BASE + 0x100, + raw_source, sizeof(raw_source)); + qtest_memwrite(qts, K230_GNNE_RUNTIME_DDR_BASE + 0x500, + translated_source, sizeof(translated_source)); + qtest_memset(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x580, 0xa5, + sizeof(data)); + + k230_kpu_run_command_bytes_at(qts, K230_GNNE_RUNTIME_FUNCTION_COMMAND, + commands, command_size); + qtest_memread(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x580, data, + sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(translated_source) ? + translated_source[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_load_w_rebases_function_source(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t raw_source[] = { + 0x21, 0x22, 0x23, 0x24, + }; + const uint8_t rebased_source[] = { + 0xa1, 0xa2, 0xa3, 0xa4, + }; + uint8_t commands[128]; + uint8_t data[16]; + size_t command_size = 0; + + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(2, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(3, 0x8)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(5, 0, 4)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(6, 0x103)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(6, 6, 0xb07)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(7, 0, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(8, 0, 3)); + k230_kpu_command_u32(commands, &command_size, GNNE_MMU_CONF(2, 7, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_L2_LOAD_W_CONF(5, 5, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_L2_LOAD_W(3, 6, 8)); + + qtest_memwrite(qts, K230_GNNE_RUNTIME_DDR_BASE + 0x102b07, + raw_source, sizeof(raw_source)); + qtest_memwrite(qts, K230_GNNE_RUNTIME_DDR_BASE + 0x102d07, + rebased_source, sizeof(rebased_source)); + qtest_memset(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x8000, 0xa5, + sizeof(data)); + + k230_kpu_run_command_bytes_at(qts, K230_GNNE_RUNTIME_FUNCTION_COMMAND, + commands, command_size); + qtest_memread(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x8000, data, + sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(rebased_source) ? + rebased_source[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_load_w_rebases_absolute_rdata_source(void) +{ + QTestState *qts = k230_kpu_init(); + const uint32_t source = K230_GNNE_RUNTIME_RDATA_BASE + 0x1000; + const uint8_t raw_source[] = { + 0x00, 0x00, 0x00, 0x00, + }; + const uint8_t rebased_source[] = { + 0xb1, 0xb2, 0xb3, 0xb4, + }; + uint8_t commands[128]; + uint8_t data[16]; + size_t command_size = 0; + + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(2, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(3, 0x8)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(5, 0, 4)); + k230_kpu_command_u32(commands, &command_size, + GNNE_LUI(6, source >> 12)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ADDI(6, 6, source & 0xfff)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(7, 0, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(8, 0, 3)); + k230_kpu_command_u32(commands, &command_size, GNNE_MMU_CONF(2, 7, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_L2_LOAD_W_CONF(5, 5, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_L2_LOAD_W(3, 6, 8)); + + qtest_memwrite(qts, source, raw_source, sizeof(raw_source)); + qtest_memwrite(qts, source + 0x200, + rebased_source, sizeof(rebased_source)); + qtest_memset(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x8000, 0xa5, + sizeof(data)); + + k230_kpu_run_command_bytes_at(qts, K230_GNNE_RUNTIME_FUNCTION_COMMAND, + commands, command_size); + qtest_memread(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x8000, data, + sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(rebased_source) ? + rebased_source[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_load_w_keeps_absolute_rdata_source(void) +{ + QTestState *qts = k230_kpu_init(); + const uint32_t source = K230_GNNE_RUNTIME_RDATA_BASE + 0x3000; + const uint8_t raw_source[] = { + 0x41, 0x42, 0x43, 0x44, + }; + const uint8_t rebased_source[] = { + 0xc1, 0xc2, 0xc3, 0xc4, + }; + uint8_t commands[128]; + uint8_t data[16]; + size_t command_size = 0; + + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(2, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(3, 0x8)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(5, 0, 4)); + k230_kpu_command_u32(commands, &command_size, + GNNE_LUI(6, source >> 12)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ADDI(6, 6, source & 0xfff)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(7, 0, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(8, 0, 3)); + k230_kpu_command_u32(commands, &command_size, GNNE_MMU_CONF(2, 7, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_L2_LOAD_W_CONF(5, 5, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_L2_LOAD_W(3, 6, 8)); + + qtest_memwrite(qts, source, raw_source, sizeof(raw_source)); + qtest_memwrite(qts, source + 0x200, + rebased_source, sizeof(rebased_source)); + qtest_memset(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x8000, 0xa5, + sizeof(data)); + + k230_kpu_run_command_bytes_at(qts, K230_GNNE_RUNTIME_FUNCTION_COMMAND, + commands, command_size); + qtest_memread(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x8000, data, + sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(raw_source) ? raw_source[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_load_w_synthesizes_function_arg(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t poison[] = { + 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, + 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, + }; + const uint8_t expected[] = { + 0x00, 0x00, 0x00, 0x38, 0x00, 0x38, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xfc, 0x00, 0x7c, + }; + uint8_t commands[128]; + uint8_t data[32]; + size_t command_size = 0; + + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(2, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(3, 0x8)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(5, 0, 7)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(6, 0x0a)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(6, 6, 0xcde)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(7, 0, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(8, 0, 23)); + k230_kpu_command_u32(commands, &command_size, GNNE_MMU_CONF(2, 7, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_L2_LOAD_W_CONF(5, 5, 1, 1, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_L2_LOAD_W(3, 6, 8)); + + qtest_memwrite(qts, K230_GNNE_RUNTIME_DDR_BASE + 0x9cde, + poison, sizeof(poison)); + qtest_memset(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x8000, 0xa5, + sizeof(data)); + + k230_kpu_run_command_bytes_at(qts, K230_GNNE_RUNTIME_FUNCTION_COMMAND, + commands, command_size); + qtest_memread(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x8000, data, + sizeof(data)); + + g_assert_cmpmem(data, sizeof(expected), expected, sizeof(expected)); + for (size_t i = sizeof(expected); i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void k230_l2_load_w_rdata_arg_case(uint32_t source, + const uint8_t *expected, + size_t expected_size) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t poison[] = { + 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, + 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, + }; + uint8_t commands[128]; + uint8_t data[32]; + size_t command_size = 0; + + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(2, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(3, 0x8)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(5, 0, 7)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(6, source >> 12)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ADDI(6, 6, source & 0xfff)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(7, 0, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(8, 0, 23)); + k230_kpu_command_u32(commands, &command_size, GNNE_MMU_CONF(2, 7, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_L2_LOAD_W_CONF(5, 5, 1, 1, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_L2_LOAD_W(3, 6, 8)); + + qtest_memwrite(qts, source, poison, sizeof(poison)); + qtest_memset(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x8000, 0xa5, + sizeof(data)); + + k230_kpu_run_command_bytes_at(qts, K230_GNNE_RUNTIME_FUNCTION_COMMAND, + commands, command_size); + qtest_memread(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x8000, data, + sizeof(data)); + + g_assert_cmpmem(data, expected_size, expected, expected_size); + for (size_t i = expected_size; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_load_w_synthesizes_rdata_function_args(void) +{ + const uint8_t identity[] = { + 0x00, 0x00, 0x00, 0x3c, 0x00, 0x3c, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xfc, 0x00, 0x7c, + }; + const uint8_t half[] = { + 0x00, 0x00, 0x00, 0x38, 0x00, 0x38, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xfc, 0x00, 0x7c, + }; + const uint8_t bbox_scale[] = { + 0x00, 0x00, 0x1c, 0x68, 0x1c, 0x68, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xe7, 0xff, 0x67, + }; + + k230_l2_load_w_rdata_arg_case(K230_GNNE_RUNTIME_RDATA_BASE, + identity, sizeof(identity)); + k230_l2_load_w_rdata_arg_case(K230_GNNE_RUNTIME_RDATA_BASE + 0x20de, + half, sizeof(half)); + k230_l2_load_w_rdata_arg_case(K230_GNNE_RUNTIME_RDATA_BASE + 0x210b, + bbox_scale, sizeof(bbox_scale)); +} + +static void test_l2_load_w_uses_rdata_fallback_base(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0x6a, + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 1), + GNNE_ADDI(4, 0, 0x180), + GNNE_ADDI(5, 0, 0), + GNNE_ADDI(7, 0, 0), + GNNE_MMU_CONF(0, 2, 0), + GNNE_L2_LOAD_W_CONF(3, 3, 0, 0, 0), + GNNE_LW(5, 0, 8), + GNNE_ADDI(5, 5, 0x120), + GNNE_L2_LOAD_W(4, 5, 7), + }; + uint8_t alias[4]; + uint8_t data[8]; + + stl_le_p(alias, K230_GNNE_RDATA_ALIAS_BASE); + qtest_memwrite(qts, K230_GNNE_RDATA_FALLBACK_BASE + 8, + alias, sizeof(alias)); + qtest_memwrite(qts, K230_GNNE_RDATA_FALLBACK_BASE + 0x120, + source, sizeof(source)); + qtest_memset(qts, K230_GNNE_SYNTH_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_OUTPUT, data, sizeof(data)); + + g_assert_cmphex(data[0], ==, source[0]); + for (size_t i = 1; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_runtime_function_command_uses_runtime_base(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0x71, 0x72, 0x73, 0x74, + }; + uint8_t commands[128]; + uint8_t data[8]; + size_t command_size = 0; + + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(2, 0, 0x100)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(3, 0, 0x180)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(4, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ADDI(5, 0, sizeof(source))); + k230_kpu_command_u32(commands, &command_size, GNNE_MMU_CONF(0, 2, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_SHAPE(4, 4, 4, 5, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(5, 5, 5, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_L2_STORE_CONF(0, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_L2_STORE(3, 2, 0)); + + qtest_memwrite(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x100, + source, sizeof(source)); + qtest_memset(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x180, 0xa5, + sizeof(data)); + + k230_kpu_run_command_bytes_at(qts, K230_GNNE_RUNTIME_FUNCTION_COMMAND, + commands, command_size); + qtest_memread(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x180, data, + sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(source) ? source[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_runtime_rdata_shadow_survives_glb_mutation(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0x6a, + }; + const uint8_t poisoned_source[] = { + 0x99, + }; + const uint32_t warm_commands[] = { + GNNE_ADDI(1, 0, 0), + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 1), + GNNE_ADDI(4, 0, 0x180), + GNNE_ADDI(5, 0, 0), + GNNE_ADDI(7, 0, 0), + GNNE_MMU_CONF(0, 2, 0), + GNNE_L2_LOAD_W_CONF(3, 3, 0, 0, 0), + GNNE_LW(5, 0, 8), + GNNE_ADDI(5, 5, 0x120), + GNNE_L2_LOAD_W(4, 5, 7), + }; + uint8_t alias[4]; + uint8_t data[8]; + + stl_le_p(alias, K230_GNNE_RDATA_ALIAS_BASE); + qtest_memwrite(qts, K230_GNNE_RUNTIME_RDATA_BASE + 8, + alias, sizeof(alias)); + qtest_memwrite(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x120, + source, sizeof(source)); + + k230_kpu_run_command_bytes_at(qts, K230_GNNE_RUNTIME_FUNCTION_COMMAND, + (const uint8_t *)warm_commands, + sizeof(warm_commands)); + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + stl_le_p(alias, 0); + qtest_memwrite(qts, K230_GNNE_RUNTIME_RDATA_BASE + 8, + alias, sizeof(alias)); + qtest_memwrite(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x120, + poisoned_source, sizeof(poisoned_source)); + qtest_memset(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x180, 0xa5, + sizeof(data)); + + k230_kpu_run_command_bytes_at(qts, K230_GNNE_RUNTIME_FUNCTION_COMMAND, + (const uint8_t *)commands, + sizeof(commands)); + qtest_memread(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x180, data, + sizeof(data)); + + g_assert_cmphex(data[0], ==, source[0]); + for (size_t i = 1; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_load_uses_rdata_prefix_shadow(void) +{ + QTestState *qts = k230_kpu_init(); + const uint32_t source_addr = K230_GNNE_RUNTIME_RDATA_BASE + 0x20; + const uint8_t source[] = { + 0x11, 0x12, 0x13, 0x14, + }; + const uint8_t poisoned_source[] = { + 0x91, 0x92, 0x93, 0x94, + }; + const uint32_t warm_commands[] = { + GNNE_ADDI(1, 0, 0), + }; + uint8_t commands[160]; + uint8_t data[8]; + size_t command_size = 0; + + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(2, 0, 0x180)); + k230_kpu_command_u32(commands, &command_size, + GNNE_LUI(3, source_addr >> 12)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ADDI(3, 3, source_addr & 0xfff)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(4, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ADDI(5, 0, sizeof(source))); + k230_kpu_command_u32(commands, &command_size, GNNE_MMU_CONF(0, 2, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_SHAPE(4, 4, 4, 5, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(5, 5, 5, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(5, 5, 5, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_L2_LOAD_CONF(1, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_L2_LOAD(2, 3, 0)); + + qtest_memwrite(qts, source_addr, source, sizeof(source)); + k230_kpu_run_command_bytes_at(qts, K230_GNNE_RUNTIME_FUNCTION_COMMAND, + (const uint8_t *)warm_commands, + sizeof(warm_commands)); + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_memwrite(qts, source_addr, poisoned_source, + sizeof(poisoned_source)); + qtest_memset(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x180, 0xa5, + sizeof(data)); + + k230_kpu_run_command_bytes_at(qts, K230_GNNE_RUNTIME_FUNCTION_COMMAND, + commands, command_size); + qtest_memread(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x180, data, + sizeof(data)); + + g_assert_cmpmem(data, sizeof(source), source, sizeof(source)); + for (size_t i = sizeof(source); i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_runtime_arg_table_drives_direct_io(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0x41, 0x42, 0x43, 0x44, + }; + uint8_t commands[160]; + uint8_t table[16] = {}; + uint8_t data[8]; + size_t command_size = 0; + + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(2, 0, 0x200)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(4, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ADDI(5, 0, sizeof(source))); + k230_kpu_command_u32(commands, &command_size, GNNE_MMU_CONF(0, 4, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_LW(6, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_SHAPE(4, 4, 4, 5, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(5, 5, 5, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(5, 5, 5, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_L2_LOAD_CONF(1, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_L2_LOAD(2, 6, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_LW(7, 0, 4)); + k230_kpu_command_u32(commands, &command_size, + GNNE_L2_STORE_CONF(1, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_L2_STORE(7, 2, 0)); + + stl_le_p(table, K230_GNNE_RUNTIME_DIRECT_SOURCE); + stl_le_p(table + 4, K230_GNNE_RUNTIME_DIRECT_OUTPUT); + stl_le_p(table + 8, K230_GNNE_RUNTIME_RDATA_BASE); + qtest_memwrite(qts, K230_GNNE_RUNTIME_ARG_TABLE, table, sizeof(table)); + qtest_memwrite(qts, K230_GNNE_RUNTIME_DIRECT_SOURCE, + source, sizeof(source)); + qtest_memset(qts, K230_GNNE_RUNTIME_DIRECT_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_command_bytes_at(qts, K230_GNNE_RUNTIME_FUNCTION_COMMAND, + commands, command_size); + qtest_memread(qts, K230_GNNE_RUNTIME_DIRECT_OUTPUT, data, sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(source) ? source[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_store_accepts_rdata_alias_destination(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0x51, 0x52, 0x53, 0x54, + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(4, 0, 1), + GNNE_ADDI(5, 0, sizeof(source)), + GNNE_MMU_CONF(0, 2, 0), + GNNE_LW(3, 0, 8), + GNNE_ADDI(3, 3, 0x180), + GNNE_SS_PACK_SHAPE(4, 4, 4, 5, 0), + GNNE_SS_PACK_STRIDE(5, 5, 5, 0), + GNNE_SS_PACK_STRIDE(5, 5, 5, 1), + GNNE_L2_STORE_CONF(1, 0, 0, 0), + GNNE_L2_STORE(3, 2, 0), + }; + uint8_t alias[4]; + uint8_t data[8]; + + stl_le_p(alias, K230_GNNE_RDATA_ALIAS_BASE); + qtest_memwrite(qts, K230_GNNE_RUNTIME_RDATA_BASE + 8, + alias, sizeof(alias)); + qtest_memwrite(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x100, + source, sizeof(source)); + qtest_memset(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x180, 0xa5, + sizeof(data)); + + k230_kpu_run_command_bytes_at(qts, K230_GNNE_RUNTIME_FUNCTION_COMMAND, + (const uint8_t *)commands, + sizeof(commands)); + qtest_memread(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x180, data, + sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(source) ? source[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_store_runtime_mirrors_to_ddr_source(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0x61, 0x62, 0x63, 0x64, + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(4, 0, 1), + GNNE_ADDI(5, 0, sizeof(source)), + GNNE_MMU_CONF(0, 2, 0), + GNNE_LW(3, 0, 8), + GNNE_SS_PACK_SHAPE(4, 4, 4, 5, 0), + GNNE_SS_PACK_STRIDE(5, 5, 5, 0), + GNNE_SS_PACK_STRIDE(5, 5, 5, 1), + GNNE_L2_STORE_CONF(1, 0, 0, 0), + GNNE_L2_STORE(3, 2, 0), + GNNE_LUI(6, 0x3c000), + GNNE_ADDI(7, 0, 0x180), + GNNE_L2_LOAD_CONF(1, 0, 0, 0), + GNNE_L2_LOAD(7, 6, 0), + }; + uint8_t alias[4]; + uint8_t ddr[sizeof(source)]; + uint8_t data[8]; + + stl_le_p(alias, K230_GNNE_RDATA_ALIAS_BASE); + qtest_memwrite(qts, K230_GNNE_RUNTIME_RDATA_BASE + 8, + alias, sizeof(alias)); + qtest_memwrite(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x100, + source, sizeof(source)); + qtest_memset(qts, K230_GNNE_RUNTIME_RDATA_BASE, 0xa5, + sizeof(data)); + qtest_memset(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x180, 0xa5, + sizeof(data)); + qtest_memset(qts, K230_GNNE_RUNTIME_DDR_BASE, 0, + sizeof(source)); + + k230_kpu_run_command_bytes_at(qts, K230_GNNE_RUNTIME_FUNCTION_COMMAND, + (const uint8_t *)commands, + sizeof(commands)); + qtest_memread(qts, K230_GNNE_RUNTIME_DDR_BASE, ddr, sizeof(ddr)); + qtest_memread(qts, K230_GNNE_RUNTIME_RDATA_BASE + 0x180, data, + sizeof(data)); + + g_assert_cmpmem(ddr, sizeof(ddr), source, sizeof(source)); + g_assert_cmpmem(data, sizeof(source), source, sizeof(source)); + for (size_t i = sizeof(source); i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_l2_store_conf_latches_stride(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0x11, 0x12, 0x13, 0x14, + }; + uint8_t commands[160]; + uint8_t data[8]; + size_t command_size = 0; + + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(2, 0, 0x100)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(3, 0, 0x180)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(4, 0, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(5, 0, 2)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(6, 0, 4)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(7, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_MMU_CONF(0, 2, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_SHAPE(4, 5, 4, 5, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(5, 4, 5, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_L2_STORE_CONF(1, 1, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(7, 7, 7, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_L2_STORE(3, 2, 0)); + + qtest_memwrite(qts, K230_GNNE_SYNTH_GLB_BASE + 0x100, + source, sizeof(source)); + qtest_memset(qts, K230_GNNE_SYNTH_GLB_BASE + 0x180, 0xa5, + sizeof(data)); + + k230_kpu_run_command_bytes(qts, commands, command_size); + qtest_memread(qts, K230_GNNE_SYNTH_GLB_BASE + 0x180, data, + sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(source) ? source[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_mfu_act1_identity_u8(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 1, 2, 200, 255, + }; + const uint8_t act1_table[] = { + 0x00, 0x00, /* threshold 0 */ + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0x00, 0x5c, /* upper 256 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x400), + GNNE_ADDI(6, 0, 0x440), + GNNE_ADDI(7, 0, 0x420), + GNNE_ADDI(4, 0, 1), + GNNE_ADDI(5, 0, sizeof(source)), + GNNE_LUI(10, 0x4), + GNNE_ADDI(10, 10, -0x400), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(4, 4, 4, 5, 0), + GNNE_MFU_ACT1_CONF_STRIDE(0, 0, 0), + GNNE_MFU_ACT1_CONF_DEST(5, 0), + GNNE_MFU_ACT1_CONF_DEQ(10, 0, 1, 0, 0), + GNNE_MFU_ACT1_CONF_QUANT(1, 0), + GNNE_MFU_ACT1_CONF(0, 1, 0), + GNNE_MFU_ACT1_COMPUTE(6, 3, 0, 7), + }; + uint8_t data[8]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_SOURCE, source, + sizeof(source)); + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_ARG, act1_table, + sizeof(act1_table)); + qtest_memset(qts, K230_GNNE_SYNTH_MFU_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_MFU_OUTPUT, data, sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(source) ? source[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_mfu_act1_conf_dest_latches_rlen(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 4, 5, 6, 7, + }; + const uint8_t act1_table[] = { + 0x00, 0x00, /* threshold 0 */ + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0x00, 0x5c, /* upper 256 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x400), + GNNE_ADDI(6, 0, 0x440), + GNNE_ADDI(7, 0, 0x420), + GNNE_ADDI(4, 0, 1), + GNNE_ADDI(5, 0, sizeof(source)), + GNNE_LUI(10, 0x4), + GNNE_ADDI(10, 10, -0x400), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(4, 4, 4, 5, 0), + GNNE_MFU_ACT1_CONF_STRIDE(0, 0, 0), + GNNE_MFU_ACT1_CONF_DEST(5, 0), + GNNE_LUI(5, 0x5000), + GNNE_MFU_ACT1_CONF_DEQ(10, 0, 1, 0, 0), + GNNE_MFU_ACT1_CONF_QUANT(1, 0), + GNNE_MFU_ACT1_CONF(0, 1, 0), + GNNE_MFU_ACT1_COMPUTE(6, 3, 0, 7), + }; + uint8_t data[8]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_SOURCE, source, + sizeof(source)); + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_ARG, act1_table, + sizeof(act1_table)); + qtest_memset(qts, K230_GNNE_SYNTH_MFU_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_MFU_OUTPUT, data, sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(source) ? source[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_mfu_act1_add_clip_u8(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0, 1, 254, 255, + }; + const uint8_t expected[] = { + 1, 2, 255, 255, + }; + const uint8_t act1_table[] = { + 0x00, 0x00, /* threshold 0 */ + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x20, 0x38, /* negative bias 0.515625 */ + 0x20, 0x38, /* positive bias 0.515625 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x400), + GNNE_ADDI(6, 0, 0x460), + GNNE_ADDI(7, 0, 0x420), + GNNE_ADDI(4, 0, 1), + GNNE_ADDI(5, 0, sizeof(source)), + GNNE_LUI(10, 0x4), + GNNE_ADDI(10, 10, -0x400), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(4, 4, 4, 5, 0), + GNNE_MFU_ACT1_CONF_STRIDE(0, 0, 0), + GNNE_MFU_ACT1_CONF_DEST(5, 0), + GNNE_MFU_ACT1_CONF_DEQ(10, 0, 1, 0, 0), + GNNE_MFU_ACT1_CONF_QUANT(1, 0), + GNNE_MFU_ACT1_CONF(0, 1, 0), + GNNE_MFU_ACT1_COMPUTE(6, 3, 0, 7), + }; + uint8_t data[8]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_SOURCE, source, + sizeof(source)); + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_ARG, act1_table, + sizeof(act1_table)); + qtest_memset(qts, K230_GNNE_SYNTH_MFU_ADD_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_MFU_ADD_OUTPUT, data, sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(expected) ? expected[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_mfu_act1_fp16_roundtrip(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0, 1, 254, 255, + }; + const uint8_t expected_fp16[] = { + 0x00, 0x00, /* 0 */ + 0x00, 0x3c, /* 1 */ + 0xf0, 0x5b, /* 254 */ + 0xf8, 0x5b, /* 255 */ + }; + const uint8_t act1_table[] = { + 0x00, 0x00, /* threshold 0 */ + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x400), + GNNE_ADDI(6, 0, 0x480), + GNNE_ADDI(7, 0, 0x420), + GNNE_ADDI(8, 0, 0x4a0), + GNNE_ADDI(4, 0, 1), + GNNE_ADDI(5, 0, sizeof(source)), + GNNE_LUI(10, 0x4), + GNNE_ADDI(10, 10, -0x400), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(4, 4, 4, 5, 0), + GNNE_MFU_ACT1_CONF_STRIDE(0, 0, 0), + GNNE_MFU_ACT1_CONF_DEST(5, 0), + GNNE_MFU_ACT1_CONF_DEQ(10, 0, 1, 0, 0), + GNNE_MFU_ACT1_CONF_QUANT(0, 0), + GNNE_MFU_ACT1_CONF(0, 1, 0), + GNNE_MFU_ACT1_COMPUTE(6, 3, 0, 7), + GNNE_MFU_ACT1_CONF_DEQ(10, 0, 0, 0, 0), + GNNE_MFU_ACT1_CONF_QUANT(1, 0), + GNNE_MFU_ACT1_COMPUTE(8, 6, 0, 7), + }; + uint8_t fp16_data[sizeof(expected_fp16)]; + uint8_t data[8]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_SOURCE, source, + sizeof(source)); + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_ARG, act1_table, + sizeof(act1_table)); + qtest_memset(qts, K230_GNNE_SYNTH_MFU_FP16_OUTPUT, 0xa5, + sizeof(fp16_data)); + qtest_memset(qts, K230_GNNE_SYNTH_MFU_ROUNDTRIP_OUTPUT, 0xa5, + sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_MFU_FP16_OUTPUT, fp16_data, + sizeof(fp16_data)); + qtest_memread(qts, K230_GNNE_SYNTH_MFU_ROUNDTRIP_OUTPUT, data, + sizeof(data)); + + for (size_t i = 0; i < sizeof(expected_fp16); i++) { + g_assert_cmphex(fp16_data[i], ==, expected_fp16[i]); + } + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(source) ? source[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_mfu_act1_mul_fp16_two_l2_sources(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source1[] = { + 0x00, 0x40, /* 2 */ + 0x00, 0x42, /* 3 */ + }; + const uint8_t source2[] = { + 0x00, 0x44, /* 4 */ + 0x00, 0x45, /* 5 */ + }; + const uint8_t expected[] = { + 0x00, 0x48, /* 8 */ + 0x80, 0x4b, /* 15 */ + }; + const uint8_t act1_table[] = { + 0x00, 0x00, /* threshold 0 */ + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0x00, 0x5c, /* upper 256 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x400), + GNNE_ADDI(4, 0, 0x480), + GNNE_ADDI(6, 0, 0x4a0), + GNNE_ADDI(7, 0, 0x420), + GNNE_ADDI(5, 0, 2), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 0), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 8, 8, 9, 0), + GNNE_MFU_ACT1_CONF_STRIDE(0, 0, 0), + GNNE_MFU_ACT1_CONF_SRC1(5, 10, 10, 0, 1), + GNNE_MFU_ACT1_CONF_SRC1(5, 10, 10, 1, 1), + GNNE_MFU_ACT1_CONF_SRC2(10, 0, 0, 0), + GNNE_MFU_ACT1_CONF_SRC2(10, 0, 1, 0), + GNNE_MFU_ACT1_CONF_DEST(5, 0), + GNNE_MFU_ACT1_CONF_DEQ(10, 0, 0, 0, 0), + GNNE_MFU_ACT1_CONF_DEQ(10, 0, 0, 1, 0), + GNNE_MFU_ACT1_CONF_QUANT(0, 0), + GNNE_MFU_ACT1_CONF(1, 0, 0), + GNNE_MFU_ACT1_COMPUTE(6, 3, 4, 7), + }; + uint8_t data[sizeof(expected)]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_SOURCE, source1, + sizeof(source1)); + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_FP16_OUTPUT, source2, + sizeof(source2)); + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_ARG, act1_table, + sizeof(act1_table)); + qtest_memset(qts, K230_GNNE_SYNTH_MFU_ROUNDTRIP_OUTPUT, 0xa5, + sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_MFU_ROUNDTRIP_OUTPUT, data, + sizeof(data)); + + g_assert_cmpmem(data, sizeof(data), expected, sizeof(expected)); + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_mfu_act1_raddr_s2_zero_is_unary(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source1[] = { + 0x00, 0x40, /* 2 */ + 0x00, 0x42, /* 3 */ + }; + const uint8_t zero_source[] = { + 0x00, 0x44, /* 4 */ + 0x00, 0x45, /* 5 */ + }; + const uint8_t expected[] = { + 0x00, 0x40, /* 2 */ + 0x00, 0x42, /* 3 */ + }; + const uint8_t act1_table[] = { + 0x00, 0x00, /* threshold 0 */ + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0x00, 0x5c, /* upper 256 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x400), + GNNE_ADDI(6, 0, 0x4a0), + GNNE_ADDI(7, 0, 0x420), + GNNE_ADDI(5, 0, 2), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 0), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 8, 8, 9, 0), + GNNE_MFU_ACT1_CONF_STRIDE(0, 0, 0), + GNNE_MFU_ACT1_CONF_SRC1(5, 10, 10, 0, 1), + GNNE_MFU_ACT1_CONF_SRC1(5, 10, 10, 1, 1), + GNNE_MFU_ACT1_CONF_SRC2(10, 0, 0, 0), + GNNE_MFU_ACT1_CONF_SRC2(10, 0, 1, 0), + GNNE_MFU_ACT1_CONF_DEST(5, 0), + GNNE_MFU_ACT1_CONF_DEQ(10, 0, 0, 0, 0), + GNNE_MFU_ACT1_CONF_DEQ(10, 0, 0, 1, 0), + GNNE_MFU_ACT1_CONF_QUANT(0, 0), + GNNE_MFU_ACT1_CONF(1, 0, 0), + GNNE_MFU_ACT1_COMPUTE(6, 3, 0, 7), + }; + uint8_t data[sizeof(expected)]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_GLB_BASE, zero_source, + sizeof(zero_source)); + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_SOURCE, source1, + sizeof(source1)); + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_ARG, act1_table, + sizeof(act1_table)); + qtest_memset(qts, K230_GNNE_SYNTH_MFU_ROUNDTRIP_OUTPUT, 0xa5, + sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_MFU_ROUNDTRIP_OUTPUT, data, + sizeof(data)); + + g_assert_cmpmem(data, sizeof(data), expected, sizeof(expected)); + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_mfu_act1_segment_linefit_fp16(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 0x00, 0x00, /* fp16 0 */ + 0x00, 0x3e, /* fp16 1.5 */ + }; + const uint8_t expected[] = { + 0x00, 0x3c, /* fp16 1 */ + 0x00, 0x45, /* fp16 5 */ + }; + uint8_t table[98] = {}; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x400), + GNNE_LUI(6, 1), + GNNE_ADDI(6, 6, 0x780), + GNNE_LUI(7, 1), + GNNE_ADDI(7, 7, 0x700), + GNNE_ADDI(4, 0, 1), + GNNE_ADDI(5, 0, 2), + GNNE_ADDI(10, 0, 0), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(4, 4, 4, 5, 0), + GNNE_MFU_ACT1_CONF_STRIDE(0, 0, 0), + GNNE_MFU_ACT1_CONF_DEST(5, 0), + GNNE_MFU_ACT1_CONF_DEQ(10, 0, 0, 0, 0), + GNNE_MFU_ACT1_CONF_QUANT(0, 0), + GNNE_MFU_ACT1_CONF(0, 0, 1), + GNNE_MFU_ACT1_COMPUTE(6, 3, 0, 7), + }; + uint8_t data[8]; + + stw_le_p(table + 0, 0x3c00); /* threshold 1 */ + stw_le_p(table + 2, 0x4000); /* threshold 2 */ + stw_le_p(table + 30, 0x3c00); /* segment 0 slope 1 */ + stw_le_p(table + 32, 0x4000); /* segment 1 slope 2 */ + stw_le_p(table + 62, 0x3c00); /* segment 0 bias 1 */ + stw_le_p(table + 64, 0x4000); /* segment 1 bias 2 */ + stw_le_p(table + 94, 0x0000); /* lower 0 */ + stw_le_p(table + 96, 0x5c00); /* upper 256 */ + + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_SOURCE, source, + sizeof(source)); + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_SEG_ARG, table, + sizeof(table)); + qtest_memset(qts, K230_GNNE_SYNTH_MFU_SEG_OUTPUT, 0xa5, + sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_MFU_SEG_OUTPUT, data, sizeof(data)); + + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, + i < sizeof(expected) ? expected[i] : 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_mfu_act1_packed_strides_u8(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 11, 12, 0xee, + 13, 14, 0xee, + 15, 16, 0xee, + 17, 18, 0xee, + }; + const uint8_t expected[] = { + 11, 12, 0xa5, + 13, 14, 0xa5, + 15, 16, 0xa5, + 17, 18, 0xa5, + }; + const uint8_t act1_table[] = { + 0x00, 0x00, /* threshold 0 */ + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0x00, 0x5c, /* upper 256 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x400), + GNNE_ADDI(6, 0, 0x440), + GNNE_ADDI(7, 0, 0x420), + GNNE_ADDI(4, 0, 1), + GNNE_ADDI(5, 0, 2), + GNNE_ADDI(8, 0, 4), + GNNE_ADDI(9, 0, 3), + GNNE_LUI(10, 0x4), + GNNE_ADDI(10, 10, -0x400), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(4, 5, 5, 5, 0), + GNNE_SS_PACK_STRIDE(8, 5, 9, 1), + GNNE_MFU_ACT1_CONF_STRIDE(1, 0, 1), + GNNE_MFU_ACT1_CONF_SRC2(0, 0, 0, 0), + GNNE_MFU_ACT1_CONF_DEST(0, 0), + GNNE_MFU_ACT1_CONF_DEQ(10, 0, 1, 0, 0), + GNNE_MFU_ACT1_CONF_QUANT(1, 0), + GNNE_MFU_ACT1_CONF(0, 0, 0), + GNNE_MFU_ACT1_COMPUTE(6, 3, 0, 7), + }; + uint8_t data[sizeof(expected)]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_SOURCE, source, + sizeof(source)); + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_ARG, act1_table, + sizeof(act1_table)); + qtest_memset(qts, K230_GNNE_SYNTH_MFU_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_MFU_OUTPUT, data, sizeof(data)); + + g_assert_cmpmem(data, sizeof(data), expected, sizeof(expected)); + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_mfu_pdp1_average_u8(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 2, 4, 0xee, 0xee, + 6, 8, 0xee, 0xee, + 10, 20, 0xdd, 0xdd, + 30, 40, 0xdd, 0xdd, + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_LUI(3, 1), + GNNE_ADDI(3, 3, 0x800), + GNNE_LUI(4, 1), + GNNE_ADDI(4, 4, 0x840), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 16), + GNNE_ADDI(11, 0, 4), + GNNE_LUI(12, 0x4), + GNNE_ADDI(12, 12, -0x400), + GNNE_ADDI(13, 0, 0), + GNNE_ADDI(14, 0, 2), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 9, 9, 9, 0), + GNNE_SS_PACK_STRIDE(10, 9, 11, 1), + GNNE_SS_PACK_STRIDE(9, 8, 8, 2), + GNNE_MFU_PDP1_CONF1(1, 1, 1, 2, 2), + GNNE_MFU_PDP1_CONF2(0, 0, 0, 0), + GNNE_MFU_PDP1_CONF3(0, 0, 0, 0), + GNNE_MFU_PDP1_CONF4(14, 14, 12, 0, 0), + GNNE_MFU_PDP1_CONF_DEQ(12, 13, 1, 13), + GNNE_MFU_PDP1_CONF_QUANT(12, 13, 1, 13), + GNNE_MFU_PDP1_COMPUTE(4, 3, 0), + }; + uint8_t data[4]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_PDP1_INPUT, source, + sizeof(source)); + qtest_memset(qts, K230_GNNE_SYNTH_PDP1_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_PDP1_OUTPUT, data, sizeof(data)); + + g_assert_cmphex(data[0], ==, 5); + g_assert_cmphex(data[1], ==, 25); + for (size_t i = 2; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_mfu_pdp1_min_fp16_sum_i16(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 2, 4, 0xee, 0xee, + 6, 8, 0xee, 0xee, + 10, 20, 0xdd, 0xdd, + 30, 40, 0xdd, 0xdd, + }; + const uint32_t min_commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_LUI(3, 1), + GNNE_ADDI(3, 3, 0x800), + GNNE_LUI(4, 1), + GNNE_ADDI(4, 4, 0x840), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 16), + GNNE_ADDI(11, 0, 4), + GNNE_LUI(12, 0x4), + GNNE_ADDI(12, 12, -0x400), + GNNE_ADDI(13, 0, 0), + GNNE_ADDI(14, 0, 2), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 9, 9, 9, 0), + GNNE_SS_PACK_STRIDE(10, 9, 11, 1), + GNNE_SS_PACK_STRIDE(9, 8, 8, 2), + GNNE_MFU_PDP1_CONF1(1, 1, 1, 1, 2), + GNNE_MFU_PDP1_CONF2(0, 0, 0, 0), + GNNE_MFU_PDP1_CONF3(0, 0, 0, 0), + GNNE_MFU_PDP1_CONF4(14, 14, 12, 0, 0), + GNNE_MFU_PDP1_CONF_DEQ(12, 13, 1, 13), + GNNE_MFU_PDP1_CONF_QUANT(12, 13, 0, 13), + GNNE_MFU_PDP1_COMPUTE(4, 3, 0), + }; + const uint32_t sum_commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_LUI(3, 1), + GNNE_ADDI(3, 3, 0x800), + GNNE_LUI(4, 1), + GNNE_ADDI(4, 4, 0x840), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 16), + GNNE_ADDI(11, 0, 4), + GNNE_LUI(12, 0x4), + GNNE_ADDI(12, 12, -0x400), + GNNE_ADDI(13, 0, 0), + GNNE_ADDI(14, 0, 2), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 9, 9, 9, 0), + GNNE_SS_PACK_STRIDE(10, 9, 11, 1), + GNNE_SS_PACK_STRIDE(9, 8, 8, 2), + GNNE_MFU_PDP1_CONF1(1, 1, 1, 3, 2), + GNNE_MFU_PDP1_CONF2(0, 0, 0, 0), + GNNE_MFU_PDP1_CONF3(0, 0, 0, 0), + GNNE_MFU_PDP1_CONF4(14, 14, 12, 0, 0), + GNNE_MFU_PDP1_CONF_DEQ(12, 13, 1, 13), + GNNE_MFU_PDP1_CONF_QUANT(12, 13, 3, 13), + GNNE_MFU_PDP1_COMPUTE(4, 3, 0), + }; + const uint8_t expected_min[] = { + 0x00, 0x40, + 0x00, 0x49, + }; + const uint8_t expected_sum[] = { + 20, 0, + 100, 0, + }; + uint8_t data[4]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_PDP1_INPUT, source, + sizeof(source)); + + qtest_memset(qts, K230_GNNE_SYNTH_PDP1_OUTPUT, 0xa5, sizeof(data)); + k230_kpu_run_commands(qts, min_commands, G_N_ELEMENTS(min_commands)); + qtest_memread(qts, K230_GNNE_SYNTH_PDP1_OUTPUT, data, sizeof(data)); + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, expected_min[i]); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_memset(qts, K230_GNNE_SYNTH_PDP1_OUTPUT, 0xa5, sizeof(data)); + k230_kpu_run_commands(qts, sum_commands, G_N_ELEMENTS(sum_commands)); + qtest_memread(qts, K230_GNNE_SYNTH_PDP1_OUTPUT, data, sizeof(data)); + for (size_t i = 0; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, expected_sum[i]); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_mfu_pdp1_sliding_min_u8(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t source[] = { + 1, 2, 3, + 4, 5, 6, + 7, 8, 9, + }; + const uint8_t expected[] = { + 1, 2, + 4, 5, + 0xa5, 0xa5, 0xa5, 0xa5, + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_LUI(3, 1), + GNNE_ADDI(3, 3, 0x800), + GNNE_LUI(4, 1), + GNNE_ADDI(4, 4, 0x840), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 3), + GNNE_ADDI(10, 0, 3), + GNNE_ADDI(11, 0, 2), + GNNE_LUI(12, 0x4), + GNNE_ADDI(12, 12, -0x400), + GNNE_ADDI(13, 0, 0), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 8, 9, 9, 0), + GNNE_SS_PACK_STRIDE(8, 8, 10, 1), + GNNE_SS_PACK_STRIDE(8, 8, 11, 2), + GNNE_MFU_PDP1_CONF1(1, 1, 1, 1, 2), + GNNE_MFU_PDP1_CONF2(11, 11, 0, 0), + GNNE_MFU_PDP1_CONF3(0, 0, 0, 0), + GNNE_MFU_PDP1_CONF4(11, 11, 12, 0, 0), + GNNE_MFU_PDP1_CONF_DEQ(12, 13, 1, 13), + GNNE_MFU_PDP1_CONF_QUANT(12, 13, 1, 13), + GNNE_MFU_PDP1_COMPUTE(4, 3, 0), + }; + uint8_t data[sizeof(expected)]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_PDP1_INPUT, source, + sizeof(source)); + qtest_memset(qts, K230_GNNE_SYNTH_PDP1_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_PDP1_OUTPUT, data, sizeof(data)); + + g_assert_cmpmem(data, sizeof(data), expected, sizeof(expected)); + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_pu_compute_conv2d_act0_u8(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input[] = { + 1, 2, + 3, 4, + }; + const uint8_t weight_zp[] = { + 3, + }; + const uint8_t act0_table[] = { + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x500), + GNNE_ADDI(4, 0, 0x520), + GNNE_ADDI(5, 0, 0x590), + GNNE_ADDI(6, 0, 0x5a0), + GNNE_ADDI(7, 0, 0x5c0), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 4), + GNNE_ADDI(11, 0, 2), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 8, 9, 9, 0), + GNNE_SS_PACK_SHAPE(8, 8, 8, 8, 1), + GNNE_SS_PACK_STRIDE(10, 10, 9, 0), + GNNE_SS_PACK_STRIDE(8, 8, 8, 1), + GNNE_DM_LOAD_L1_CONF(0, 0, 0, 0, 0), + GNNE_DM_LOAD_L1(0, 0, 3, 0, 0, 0), + GNNE_DM_LOAD_W_CONF(0, 0, 2, 2, 0), + GNNE_DM_LOAD_W_CONF_DEQ(0, 0, 1), + GNNE_DM_LOAD_W_CONF2(0, 0, 8, 8), + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 0), + GNNE_DM_LOAD_ACT0(0, 0, 6, 0, 0, 1), + GNNE_PU_FETCHIF_CONF1(0, 0, 1, 1, 0), + GNNE_PU_FETCHIF_CONF2(0, 0, 8, 0), + GNNE_PU_FETCHIF_CONF3(0, 0, 3, 8, 0), + GNNE_PU_FETCHIF_CONF4(0, 0, 0, 0), + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 8, 11, 1), + GNNE_PU_W_CONF(0, 0, 2, 2), + GNNE_PU_OF_CONF1(0, 0, 8, 0, 1), + GNNE_PU_OF_CONF2(0, 0, 7, 1), + GNNE_PU_COMPUTE_CONF(0, 0, 0, 0, 1, 1, 0), + GNNE_ACT0_SRC1_CONF(0, 0, 0, 1, 0), + GNNE_ACT0_COMPUTE(7, 0, 0, 0, 0, 1), + GNNE_PU_COMPUTE(0, 0), + }; + uint8_t weights[80] = {}; + uint8_t data[4]; + + weights[0] = 4; + weights[24] = 5; + weights[48] = 6; + weights[72] = 7; + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_INPUT, input, sizeof(input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT, weights, + sizeof(weights)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT_ZP, weight_zp, + sizeof(weight_zp)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_ACT0, act0_table, + sizeof(act0_table)); + qtest_memset(qts, K230_GNNE_SYNTH_CONV_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_CONV_OUTPUT, data, sizeof(data)); + + g_assert_cmphex(data[0], ==, 10); + for (size_t i = 1; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_pu_compute_dm_store_of_act0_dest(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input[] = { + 1, 2, + 3, 4, + }; + const uint8_t weight_zp[] = { + 3, + }; + const uint8_t act0_table[] = { + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x500), + GNNE_ADDI(4, 0, 0x520), + GNNE_ADDI(5, 0, 0x590), + GNNE_ADDI(6, 0, 0x5a0), + GNNE_ADDI(7, 0, 0x5c0), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 4), + GNNE_ADDI(11, 0, 2), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 8, 9, 9, 0), + GNNE_SS_PACK_SHAPE(8, 8, 8, 8, 1), + GNNE_SS_PACK_STRIDE(10, 10, 9, 0), + GNNE_SS_PACK_STRIDE(8, 8, 8, 1), + GNNE_DM_LOAD_L1_CONF(0, 0, 0, 0, 0), + GNNE_DM_LOAD_L1(0, 0, 3, 0, 0, 0), + GNNE_DM_LOAD_W_CONF(0, 0, 2, 2, 0), + GNNE_DM_LOAD_W_CONF_DEQ(0, 0, 1), + GNNE_DM_LOAD_W_CONF2(0, 0, 8, 8), + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 0), + GNNE_DM_LOAD_ACT0(0, 0, 6, 0, 0, 1), + GNNE_PU_FETCHIF_CONF1(0, 0, 1, 1, 0), + GNNE_PU_FETCHIF_CONF2(0, 0, 8, 0), + GNNE_PU_FETCHIF_CONF3(0, 0, 3, 8, 0), + GNNE_PU_FETCHIF_CONF4(0, 0, 0, 0), + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 8, 11, 1), + GNNE_PU_W_CONF(0, 0, 2, 2), + GNNE_PU_OF_CONF1(0, 0, 8, 0, 1), + GNNE_PU_OF_CONF2(0, 0, 7, 1), + GNNE_DM_STORE_OF(0, 0, 7, 1, 0), + GNNE_PU_COMPUTE_CONF(0, 0, 0, 0, 1, 1, 0), + GNNE_ACT0_SRC1_CONF(0, 0, 0, 1, 0), + GNNE_ACT0_COMPUTE(0, 0, 0, 0, 0, 1), + GNNE_PU_COMPUTE(0, 0), + }; + uint8_t weights[80] = {}; + uint8_t data[4]; + + weights[0] = 4; + weights[24] = 5; + weights[48] = 6; + weights[72] = 7; + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_INPUT, input, sizeof(input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT, weights, + sizeof(weights)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT_ZP, weight_zp, + sizeof(weight_zp)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_ACT0, act0_table, + sizeof(act0_table)); + qtest_memset(qts, K230_GNNE_SYNTH_CONV_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_CONV_OUTPUT, data, sizeof(data)); + + g_assert_cmphex(data[0], ==, 10); + for (size_t i = 1; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_pu_compute_conv2d_i8_input_act0_u8(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input[] = { + 0xfd, 4, + }; + const uint8_t weight_zp[] = { + 0, + }; + const uint8_t act0_table[] = { + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x500), + GNNE_ADDI(4, 0, 0x520), + GNNE_ADDI(5, 0, 0x590), + GNNE_ADDI(6, 0, 0x5a0), + GNNE_ADDI(7, 0, 0x5c0), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 0), + GNNE_ADDI(11, 0, 0), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 9, 8, 8, 0), + GNNE_SS_PACK_SHAPE(8, 8, 8, 8, 1), + GNNE_SS_PACK_STRIDE(9, 8, 8, 0), + GNNE_SS_PACK_STRIDE(8, 8, 8, 1), + GNNE_DM_LOAD_L1_CONF(0, 0, 0, 0, 0), + GNNE_DM_LOAD_L1(0, 0, 3, 0, 0, 0), + GNNE_DM_LOAD_W_CONF(0, 0, 1, 1, 0), + GNNE_DM_LOAD_W_CONF_DEQ(0, 0, 1), + GNNE_DM_LOAD_W_CONF2(0, 0, 8, 8), + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 0), + GNNE_DM_LOAD_ACT0(0, 0, 6, 0, 0, 1), + GNNE_PU_FETCHIF_CONF1(0, 0, 1, 1, 0), + GNNE_PU_FETCHIF_CONF2(0, 0, 9, 0), + GNNE_PU_FETCHIF_CONF3(0, 0, 3, 8, 0), + GNNE_PU_FETCHIF_CONF4(0, 0, 0, 0), + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 9, 11, 2), + GNNE_PU_W_CONF(0, 0, 1, 1), + GNNE_PU_OF_CONF1(0, 0, 8, 0, 1), + GNNE_PU_OF_CONF2(0, 0, 7, 1), + GNNE_PU_COMPUTE_CONF(0, 0, 0, 0, 1, 1, 0), + GNNE_ACT0_SRC1_CONF(0, 0, 0, 1, 0), + GNNE_ACT0_COMPUTE(7, 0, 0, 0, 0, 1), + GNNE_PU_COMPUTE(0, 0), + }; + uint8_t weights[24] = {}; + uint8_t data[4]; + + weights[0] = 2; + weights[1] = 3; + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_INPUT, input, sizeof(input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT, weights, + sizeof(weights)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT_ZP, weight_zp, + sizeof(weight_zp)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_ACT0, act0_table, + sizeof(act0_table)); + qtest_memset(qts, K230_GNNE_SYNTH_CONV_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_CONV_OUTPUT, data, sizeof(data)); + + g_assert_cmphex(data[0], ==, 6); + for (size_t i = 1; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_pu_compute_conv2d_packed_if_stride(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input[] = { + 1, 2, + 99, 4, + 5, 6, + 7, 8, + }; + const uint8_t weight_zp[] = { + 0, + }; + const uint8_t act0_table[] = { + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x500), + GNNE_ADDI(4, 0, 0x520), + GNNE_ADDI(5, 0, 0x590), + GNNE_ADDI(6, 0, 0x5a0), + GNNE_ADDI(7, 0, 0x5c0), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 0), + GNNE_ADDI(11, 0, 0), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 9, 9, 9, 0), + GNNE_SS_PACK_SHAPE(8, 8, 8, 8, 1), + GNNE_SS_PACK_STRIDE(8, 9, 9, 0), + GNNE_SS_PACK_STRIDE(8, 8, 8, 1), + GNNE_DM_LOAD_L1_CONF(0, 0, 0, 0, 0), + GNNE_DM_LOAD_L1(0, 0, 3, 0, 0, 0), + GNNE_DM_LOAD_W_CONF(0, 0, 1, 1, 0), + GNNE_DM_LOAD_W_CONF_DEQ(0, 0, 1), + GNNE_DM_LOAD_W_CONF2(0, 0, 8, 8), + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 0), + GNNE_DM_LOAD_ACT0(0, 0, 6, 0, 0, 1), + GNNE_PU_FETCHIF_CONF1(0, 0, 1, 1, 0), + GNNE_PU_FETCHIF_CONF2(0, 0, 9, 0), + GNNE_PU_FETCHIF_CONF3(0, 0, 3, 8, 0), + GNNE_PU_FETCHIF_CONF4(0, 0, 0, 0), + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 9, 11, 1), + GNNE_PU_W_CONF(0, 0, 1, 1), + GNNE_PU_OF_CONF1(0, 0, 8, 0, 1), + GNNE_PU_OF_CONF2(0, 0, 7, 1), + GNNE_PU_COMPUTE_CONF(0, 0, 0, 0, 1, 1, 0), + GNNE_ACT0_SRC1_CONF(0, 0, 0, 1, 0), + GNNE_ACT0_COMPUTE(7, 0, 0, 0, 0, 1), + GNNE_PU_COMPUTE(0, 0), + }; + uint8_t weights[24] = {}; + uint8_t data[4]; + + weights[0] = 1; + weights[1] = 2; + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_INPUT, input, sizeof(input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT, weights, + sizeof(weights)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT_ZP, weight_zp, + sizeof(weight_zp)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_ACT0, act0_table, + sizeof(act0_table)); + qtest_memset(qts, K230_GNNE_SYNTH_CONV_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_CONV_OUTPUT, data, sizeof(data)); + + g_assert_cmphex(data[0], ==, 11); + for (size_t i = 1; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_pu_compute_conv2d_packed_of_stride(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input[] = { + 1, 2, + 3, 4, + }; + const uint8_t weight_zp[] = { + 0, 0, + }; + const uint8_t act0_table[] = { + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x500), + GNNE_ADDI(4, 0, 0x520), + GNNE_ADDI(5, 0, 0x590), + GNNE_ADDI(6, 0, 0x5a0), + GNNE_ADDI(7, 0, 0x5c0), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 0), + GNNE_ADDI(11, 0, 0), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 8, 9, 9, 0), + GNNE_SS_PACK_SHAPE(8, 9, 9, 9, 1), + GNNE_SS_PACK_STRIDE(8, 9, 9, 0), + GNNE_SS_PACK_STRIDE(9, 9, 9, 1), + GNNE_DM_LOAD_L1_CONF(0, 0, 0, 0, 0), + GNNE_DM_LOAD_L1(0, 0, 3, 0, 0, 0), + GNNE_DM_LOAD_W_CONF(0, 0, 1, 1, 0), + GNNE_DM_LOAD_W_CONF_DEQ(0, 0, 1), + GNNE_DM_LOAD_W_CONF2(0, 0, 8, 9), + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 0), + GNNE_DM_LOAD_ACT0(0, 0, 6, 0, 0, 0), + GNNE_PU_FETCHIF_CONF1(0, 0, 1, 1, 0), + GNNE_PU_FETCHIF_CONF2(0, 0, 8, 0), + GNNE_PU_FETCHIF_CONF3(0, 0, 3, 8, 0), + GNNE_PU_FETCHIF_CONF4(0, 0, 0, 0), + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 8, 11, 1), + GNNE_PU_W_CONF(0, 0, 1, 1), + GNNE_PU_OF_CONF1(0, 0, 9, 0, 1), + GNNE_PU_OF_CONF2(0, 0, 7, 1), + GNNE_PU_COMPUTE_CONF(0, 0, 0, 0, 1, 1, 0), + GNNE_ACT0_SRC1_CONF(0, 0, 0, 1, 0), + GNNE_ACT0_COMPUTE(7, 0, 0, 0, 0, 0), + GNNE_PU_COMPUTE(0, 0), + }; + uint8_t weights[48] = {}; + uint8_t data[10]; + + weights[0] = 1; + weights[24] = 10; + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_INPUT, input, sizeof(input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT, weights, + sizeof(weights)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT_ZP, weight_zp, + sizeof(weight_zp)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_ACT0, act0_table, + sizeof(act0_table)); + qtest_memset(qts, K230_GNNE_SYNTH_CONV_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_CONV_OUTPUT, data, sizeof(data)); + + g_assert_cmphex(data[0], ==, 1); + g_assert_cmphex(data[1], ==, 2); + g_assert_cmphex(data[2], ==, 3); + g_assert_cmphex(data[3], ==, 4); + g_assert_cmphex(data[4], ==, 10); + g_assert_cmphex(data[5], ==, 20); + g_assert_cmphex(data[6], ==, 30); + g_assert_cmphex(data[7], ==, 40); + for (size_t i = 8; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_pu_compute_of_conf_latches_shape_stride(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input[] = { + 1, 2, + 3, 4, + }; + const uint8_t weight_zp[] = { + 0, 0, + }; + const uint8_t act0_table[] = { + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x500), + GNNE_ADDI(4, 0, 0x520), + GNNE_ADDI(5, 0, 0x590), + GNNE_ADDI(6, 0, 0x5a0), + GNNE_ADDI(7, 0, 0x5c0), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 0), + GNNE_ADDI(11, 0, 0), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 8, 9, 9, 0), + GNNE_SS_PACK_SHAPE(8, 9, 9, 9, 1), + GNNE_SS_PACK_STRIDE(8, 9, 9, 0), + GNNE_SS_PACK_STRIDE(9, 9, 9, 1), + GNNE_DM_LOAD_L1_CONF(0, 0, 0, 0, 0), + GNNE_DM_LOAD_L1(0, 0, 3, 0, 0, 0), + GNNE_DM_LOAD_W_CONF(0, 0, 1, 1, 0), + GNNE_DM_LOAD_W_CONF_DEQ(0, 0, 1), + GNNE_DM_LOAD_W_CONF2(0, 0, 8, 9), + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 0), + GNNE_DM_LOAD_ACT0(0, 0, 6, 0, 0, 0), + GNNE_PU_FETCHIF_CONF1(0, 0, 1, 1, 0), + GNNE_PU_FETCHIF_CONF2(0, 0, 8, 0), + GNNE_PU_FETCHIF_CONF3(0, 0, 3, 8, 0), + GNNE_PU_FETCHIF_CONF4(0, 0, 0, 0), + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 8, 11, 1), + GNNE_PU_W_CONF(0, 0, 1, 1), + GNNE_PU_OF_CONF1(0, 0, 9, 0, 1), + GNNE_PU_OF_CONF2(0, 0, 7, 1), + GNNE_SS_PACK_SHAPE(10, 10, 10, 10, 1), + GNNE_SS_PACK_STRIDE(10, 10, 10, 1), + GNNE_PU_COMPUTE_CONF(0, 0, 0, 0, 1, 1, 0), + GNNE_ACT0_SRC1_CONF(0, 0, 0, 1, 0), + GNNE_ACT0_COMPUTE(7, 0, 0, 0, 0, 0), + GNNE_PU_COMPUTE(0, 0), + }; + uint8_t weights[48] = {}; + uint8_t data[10]; + + weights[0] = 1; + weights[24] = 10; + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_INPUT, input, sizeof(input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT, weights, + sizeof(weights)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT_ZP, weight_zp, + sizeof(weight_zp)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_ACT0, act0_table, + sizeof(act0_table)); + qtest_memset(qts, K230_GNNE_SYNTH_CONV_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_CONV_OUTPUT, data, sizeof(data)); + + g_assert_cmphex(data[0], ==, 1); + g_assert_cmphex(data[1], ==, 2); + g_assert_cmphex(data[2], ==, 3); + g_assert_cmphex(data[3], ==, 4); + g_assert_cmphex(data[4], ==, 10); + g_assert_cmphex(data[5], ==, 20); + g_assert_cmphex(data[6], ==, 30); + g_assert_cmphex(data[7], ==, 40); + for (size_t i = 8; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_pu_compute_psum_accumulates_to_act0(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input[] = { + 7, + }; + const uint8_t weight0[24] = { + 9, + }; + const uint8_t weight1[24] = { + 3, + }; + const uint8_t weight_zp[] = { + 3, + }; + const uint8_t act0_table[] = { + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + }; + uint8_t commands[256]; + uint8_t data[4]; + size_t command_size = 0; + + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(2, 0, 0x100)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(3, 0, 0x700)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(4, 0, 0x720)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(5, 0, 0x760)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(6, 0, 0x770)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(7, 0, 0x790)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(8, 0, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(11, 0, 2)); + k230_kpu_command_u32(commands, &command_size, GNNE_MMU_CONF(0, 2, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_SHAPE(8, 8, 8, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_SHAPE(8, 8, 8, 8, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(8, 8, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(8, 8, 8, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_L1_CONF(0, 0, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_L1(0, 0, 3, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W_CONF(0, 0, 1, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W_CONF_DEQ(0, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W_CONF2(0, 0, 8, 8)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_ACT0(0, 0, 6, 0, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF1(0, 0, 1, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF2(0, 0, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF3(0, 0, 3, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF4(0, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 8, 11, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_W_CONF(0, 0, 1, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_OF_CONF1(0, 0, 8, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_OF_CONF2(0, 0, 7, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ACT0_SRC1_CONF(0, 0, 0, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ACT0_COMPUTE(7, 0, 0, 0, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_COMPUTE_CONF(0, 0, 0, 0, 0, 1, 0)); + k230_kpu_command_u16(commands, &command_size, GNNE_PU_COMPUTE(0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(4, 0, 0x740)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_COMPUTE_CONF(0, 0, 1, 0, 1, 1, 0)); + k230_kpu_command_u16(commands, &command_size, GNNE_PU_COMPUTE(0, 0)); + + qtest_memwrite(qts, K230_GNNE_SYNTH_PSUM_INPUT, input, sizeof(input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_PSUM_WEIGHT0, weight0, + sizeof(weight0)); + qtest_memwrite(qts, K230_GNNE_SYNTH_PSUM_WEIGHT1, weight1, + sizeof(weight1)); + qtest_memwrite(qts, K230_GNNE_SYNTH_PSUM_WEIGHT_ZP, weight_zp, + sizeof(weight_zp)); + qtest_memwrite(qts, K230_GNNE_SYNTH_PSUM_ACT0, act0_table, + sizeof(act0_table)); + qtest_memset(qts, K230_GNNE_SYNTH_PSUM_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_command_bytes(qts, commands, command_size); + qtest_memread(qts, K230_GNNE_SYNTH_PSUM_OUTPUT, data, sizeof(data)); + + g_assert_cmphex(data[0], ==, 30); + for (size_t i = 1; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_pu_compute_uses_fetchif_stride_without_l1(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input[] = { + 5, 7, + }; + const uint8_t stale_input[] = { + 1, 1, + }; + const uint8_t weight_zp[] = { + 1, + }; + const uint8_t act0_table[] = { + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x500), + GNNE_ADDI(4, 0, 0x520), + GNNE_ADDI(5, 0, 0x590), + GNNE_ADDI(6, 0, 0x5a0), + GNNE_ADDI(7, 0, 0x5c0), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 0), + GNNE_ADDI(11, 0, 0), + GNNE_ADDI(12, 0, 0x124), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 8, 8, 9, 1), + GNNE_SS_PACK_SHAPE(8, 8, 8, 8, 2), + GNNE_SS_PACK_STRIDE(8, 8, 8, 3), + GNNE_SS_PACK_STRIDE(8, 8, 8, 4), + GNNE_DM_LOAD_L1_CONF(0, 0, 0, 2, 0), + GNNE_DM_LOAD_L1(0, 0, 12, 0, 1, 0), + GNNE_DM_LOAD_W_CONF(0, 0, 1, 2, 0), + GNNE_DM_LOAD_W_CONF_DEQ(0, 0, 1), + GNNE_DM_LOAD_W_CONF2(0, 0, 8, 8), + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 0), + GNNE_DM_LOAD_ACT0(0, 0, 6, 0, 0, 1), + GNNE_PU_FETCHIF_CONF1(0, 0, 1, 1, 3), + GNNE_PU_FETCHIF_CONF2(0, 0, 8, 0), + GNNE_PU_FETCHIF_CONF3(0, 0, 3, 8, 1), + GNNE_PU_FETCHIF_CONF4(0, 0, 0, 0), + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 8, 11, 1), + GNNE_PU_W_CONF(0, 0, 1, 2), + GNNE_PU_OF_CONF1(0, 0, 8, 0, 4), + GNNE_PU_OF_CONF2(0, 0, 7, 2), + GNNE_PU_COMPUTE_CONF(0, 0, 0, 0, 1, 1, 0), + GNNE_ACT0_SRC1_CONF(0, 0, 0, 2, 0), + GNNE_ACT0_COMPUTE(7, 0, 0, 0, 0, 1), + GNNE_PU_COMPUTE(0, 0), + }; + uint8_t weights[48] = {}; + uint8_t data[4]; + + weights[0] = 3; + weights[24] = 4; + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_INPUT, input, sizeof(input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_INPUT + 0x24, + stale_input, sizeof(stale_input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT, weights, + sizeof(weights)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT_ZP, weight_zp, + sizeof(weight_zp)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_ACT0, act0_table, + sizeof(act0_table)); + qtest_memset(qts, K230_GNNE_SYNTH_CONV_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_CONV_OUTPUT, data, sizeof(data)); + + g_assert_cmphex(data[0], ==, 31); + for (size_t i = 1; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_pu_compute_zero_fetchif_uses_l1_source(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input[] = { + 5, 7, + }; + const uint8_t stale_input[] = { + 1, 1, + }; + const uint8_t weight_zp[] = { + 1, + }; + const uint8_t act0_table[] = { + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x500), + GNNE_ADDI(4, 0, 0x520), + GNNE_ADDI(5, 0, 0x590), + GNNE_ADDI(6, 0, 0x5a0), + GNNE_ADDI(7, 0, 0x5c0), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 0), + GNNE_ADDI(11, 0, 0), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 8, 8, 9, 2), + GNNE_SS_PACK_SHAPE(8, 8, 8, 8, 1), + GNNE_SS_PACK_STRIDE(8, 8, 8, 0), + GNNE_SS_PACK_STRIDE(8, 8, 8, 1), + GNNE_DM_LOAD_L1_CONF(0, 0, 0, 0, 0), + GNNE_DM_LOAD_L1(0, 0, 3, 0, 2, 0), + GNNE_DM_LOAD_W_CONF(0, 0, 1, 2, 0), + GNNE_DM_LOAD_W_CONF_DEQ(0, 0, 1), + GNNE_DM_LOAD_W_CONF2(0, 0, 8, 8), + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 0), + GNNE_DM_LOAD_ACT0(0, 0, 6, 0, 0, 1), + GNNE_PU_FETCHIF_CONF1(0, 0, 1, 1, 0), + GNNE_PU_FETCHIF_CONF2(0, 0, 8, 0), + GNNE_PU_FETCHIF_CONF3(0, 0, 0, 8, 2), + GNNE_PU_FETCHIF_CONF4(0, 0, 0, 0), + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 8, 11, 1), + GNNE_PU_W_CONF(0, 0, 1, 2), + GNNE_PU_OF_CONF1(0, 0, 8, 0, 1), + GNNE_PU_OF_CONF2(0, 0, 7, 1), + GNNE_PU_COMPUTE_CONF(0, 0, 0, 0, 1, 1, 0), + GNNE_ACT0_SRC1_CONF(0, 0, 0, 1, 0), + GNNE_ACT0_COMPUTE(7, 0, 0, 0, 0, 1), + GNNE_PU_COMPUTE(0, 0), + }; + uint8_t weights[48] = {}; + uint8_t data[4]; + + weights[0] = 3; + weights[24] = 4; + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_INPUT, input, sizeof(input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_GLB_BASE, + stale_input, sizeof(stale_input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT, weights, + sizeof(weights)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT_ZP, weight_zp, + sizeof(weight_zp)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_ACT0, act0_table, + sizeof(act0_table)); + qtest_memset(qts, K230_GNNE_SYNTH_CONV_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_CONV_OUTPUT, data, sizeof(data)); + + g_assert_cmphex(data[0], ==, 31); + for (size_t i = 1; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_pu_compute_fetchif_offset_uses_if_staging(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input[5] = { + 5, 99, 88, 77, 7, + }; + const uint8_t global_input[] = { + 1, 1, + }; + const uint8_t weight_zp[] = { + 0, + }; + const uint8_t act0_table[] = { + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x500), + GNNE_ADDI(4, 0, 0x520), + GNNE_ADDI(5, 0, 0x590), + GNNE_ADDI(6, 0, 0x5a0), + GNNE_ADDI(7, 0, 0x5c0), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 4), + GNNE_ADDI(11, 0, 0), + GNNE_ADDI(12, 0, 1), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 9, 8, 8, 2), + GNNE_SS_PACK_SHAPE(8, 8, 8, 8, 1), + GNNE_SS_PACK_STRIDE(8, 8, 8, 0), + GNNE_SS_PACK_STRIDE(8, 8, 8, 1), + GNNE_SS_PACK_STRIDE(8, 10, 8, 3), + GNNE_DM_LOAD_L1_CONF(0, 0, 3, 0, 0), + GNNE_DM_LOAD_L1(0, 0, 3, 0, 2, 0), + GNNE_DM_LOAD_W_CONF(0, 0, 1, 1, 0), + GNNE_DM_LOAD_W_CONF_DEQ(0, 0, 1), + GNNE_DM_LOAD_W_CONF2(0, 0, 8, 8), + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 0), + GNNE_DM_LOAD_ACT0(0, 0, 6, 0, 0, 1), + GNNE_PU_FETCHIF_CONF1(0, 0, 1, 1, 0), + GNNE_PU_FETCHIF_CONF2(0, 0, 8, 0), + GNNE_PU_FETCHIF_CONF3(0, 0, 12, 8, 2), + GNNE_PU_FETCHIF_CONF4(0, 0, 0, 0), + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 8, 11, 1), + GNNE_PU_W_CONF(0, 0, 1, 1), + GNNE_PU_OF_CONF1(0, 0, 8, 0, 1), + GNNE_PU_OF_CONF2(0, 0, 7, 1), + GNNE_PU_COMPUTE_CONF(0, 0, 0, 0, 1, 1, 0), + GNNE_ACT0_SRC1_CONF(0, 0, 0, 1, 0), + GNNE_ACT0_COMPUTE(7, 0, 0, 0, 0, 1), + GNNE_PU_COMPUTE(0, 0), + }; + uint8_t weights[24] = {}; + uint8_t data[4]; + + weights[0] = 3; + weights[1] = 4; + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_INPUT, input, sizeof(input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_GLB_BASE + 1, + global_input, sizeof(global_input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT, weights, + sizeof(weights)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT_ZP, weight_zp, + sizeof(weight_zp)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_ACT0, act0_table, + sizeof(act0_table)); + qtest_memset(qts, K230_GNNE_SYNTH_CONV_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_CONV_OUTPUT, data, sizeof(data)); + + g_assert_cmphex(data[0], ==, 21); + for (size_t i = 1; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_pu_compute_l1_bank_source_uses_raw_offset(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t raw_input[] = { + 5, 7, + }; + const uint8_t translated_input[] = { + 1, 1, + }; + const uint8_t weight_zp[] = { + 1, + }; + const uint8_t act0_table[] = { + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + }; + const uint32_t commands[] = { + GNNE_LUI(3, 0x10000), + GNNE_ADDI(3, 3, 0x500), + GNNE_ADDI(4, 0, 0x520), + GNNE_ADDI(5, 0, 0x590), + GNNE_ADDI(6, 0, 0x5a0), + GNNE_ADDI(7, 0, 0x5c0), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 0x10), + GNNE_ADDI(11, 0, 2), + GNNE_ADDI(15, 0, 0), + GNNE_LUI(12, K230_GNNE_SYNTH_SOURCE >> 12), + GNNE_LUI(13, 0x10000), + GNNE_ADDI(13, 13, 0x700), + GNNE_LUI(14, K230_GNNE_SYNTH_SOURCE >> 12), + GNNE_ADDI(14, 14, 0x100), + GNNE_MMU_CONF(0, 11, 0), + GNNE_MMU_CONF(0, 11, 1), + GNNE_SS_PACK_SHAPE(8, 8, 8, 9, 2), + GNNE_SS_PACK_SHAPE(8, 8, 8, 8, 1), + GNNE_SS_PACK_STRIDE(8, 8, 8, 0), + GNNE_SS_PACK_STRIDE(8, 8, 8, 1), + GNNE_L2_LOAD_CONF(1, 0, 0, 0), + GNNE_L2_LOAD(3, 12, 2), + GNNE_L2_LOAD(13, 14, 2), + GNNE_MMU_CONF(10, 11, 1), + GNNE_DM_LOAD_L1_CONF(0, 0, 0, 0, 0), + GNNE_DM_LOAD_L1(0, 0, 3, 0, 2, 0), + GNNE_DM_LOAD_W_CONF(0, 0, 1, 2, 0), + GNNE_DM_LOAD_W_CONF_DEQ(0, 0, 1), + GNNE_DM_LOAD_W_CONF2(0, 0, 8, 8), + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 0), + GNNE_DM_LOAD_ACT0(0, 0, 6, 0, 0, 1), + GNNE_PU_FETCHIF_CONF1(0, 0, 1, 1, 0), + GNNE_PU_FETCHIF_CONF2(0, 0, 8, 0), + GNNE_PU_FETCHIF_CONF3(0, 0, 0, 8, 2), + GNNE_PU_FETCHIF_CONF4(0, 0, 0, 0), + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 8, 15, 1), + GNNE_PU_W_CONF(0, 0, 1, 2), + GNNE_PU_OF_CONF1(0, 0, 8, 0, 1), + GNNE_PU_OF_CONF2(0, 0, 7, 1), + GNNE_PU_COMPUTE_CONF(0, 0, 0, 0, 1, 1, 0), + GNNE_ACT0_SRC1_CONF(0, 0, 0, 1, 0), + GNNE_ACT0_COMPUTE(7, 0, 0, 0, 0, 1), + GNNE_PU_COMPUTE(0, 0), + }; + uint8_t weights[48] = {}; + uint8_t data[4]; + + weights[0] = 3; + weights[24] = 4; + qtest_memwrite(qts, K230_GNNE_SYNTH_SOURCE, raw_input, + sizeof(raw_input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_SOURCE + 0x100, + translated_input, sizeof(translated_input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT, weights, + sizeof(weights)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT_ZP, weight_zp, + sizeof(weight_zp)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_ACT0, act0_table, + sizeof(act0_table)); + qtest_memset(qts, K230_GNNE_SYNTH_CONV_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_CONV_OUTPUT, data, sizeof(data)); + + g_assert_cmphex(data[0], ==, 31); + for (size_t i = 1; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_pu_compute_l1_i16_repacks_byte_planes(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input[] = { + 5, 9, /* channel 0: low byte, high byte */ + 7, 11, /* channel 1: low byte, high byte */ + }; + const uint8_t weight_zp[] = { + 0, + }; + const uint8_t act0_table[] = { + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + }; + uint8_t commands[512]; + uint8_t weights[24] = {}; + uint8_t data[4]; + size_t command_size = 0; + + weights[0] = 2; + weights[1] = 3; + + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(3, 0, 0x500)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(4, 0, 0x520)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(5, 0, 0x590)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(6, 0, 0x5a0)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(7, 0, 0x5c0)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(8, 0, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(9, 0, 2)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(12, 0, 0x200)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(13, 7, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(15, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_MMU_CONF(0, 9, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_SHAPE(8, 9, 8, 8, 2)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_SHAPE(8, 8, 8, 8, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(9, 8, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(8, 8, 8, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_L1_CONF(0, 0, 0, 2, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_L1(0, 0, 3, 0, 2, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W_CONF(0, 0, 1, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W_CONF_DEQ(0, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W_CONF2(0, 0, 8, 8)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_ACT0(0, 0, 6, 0, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF1(0, 0, 1, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF2(0, 0, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF3(0, 0, 0, 8, 2)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF4(0, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 8, 15, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_W_CONF(0, 0, 1, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_OF_CONF1(0, 0, 8, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_OF_CONF2(0, 0, 7, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_COMPUTE_CONF(0, 0, 0, 0, 1, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ACT0_SRC1_CONF(0, 0, 0, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ACT0_COMPUTE(7, 0, 0, 0, 0, 1)); + k230_kpu_command_u16(commands, &command_size, GNNE_PU_COMPUTE(0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF3(0, 0, 12, 8, 2)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 8, 15, 2)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ACT0_COMPUTE(13, 0, 0, 0, 0, 1)); + k230_kpu_command_u16(commands, &command_size, GNNE_PU_COMPUTE(0, 0)); + + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_INPUT, input, sizeof(input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT, weights, + sizeof(weights)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_WEIGHT_ZP, weight_zp, + sizeof(weight_zp)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CONV_ACT0, act0_table, + sizeof(act0_table)); + qtest_memset(qts, K230_GNNE_SYNTH_CONV_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_command_bytes(qts, commands, command_size); + qtest_memread(qts, K230_GNNE_SYNTH_CONV_OUTPUT, data, sizeof(data)); + + g_assert_cmphex(data[0], ==, 31); + g_assert_cmphex(data[1], ==, 51); + for (size_t i = 2; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_mfu_act1_adds_psum_and_l2_u8(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input[] = { + 5, + }; + const uint8_t weight[24] = { + 4, + }; + const uint8_t weight_zp[] = { + 1, + }; + const uint8_t l2_source[] = { + 2, + }; + const uint8_t act1_table[] = { + 0x00, 0x00, /* threshold 0 */ + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0x00, 0x5c, /* upper 256 */ + }; + uint8_t commands[512]; + uint8_t data[4]; + size_t command_size = 0; + + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(2, 0, 0x100)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(3, 0, 0x700)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(4, 0, 0x720)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(5, 0, 0x760)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(6, 0, 0x420)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(7, 0, 0x440)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(8, 0, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(9, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(10, 0, 0x790)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(11, 0, 0x400)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(12, 4)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(12, 12, -0x400)); + k230_kpu_command_u32(commands, &command_size, GNNE_MMU_CONF(0, 2, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_SHAPE(8, 8, 8, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(8, 8, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_L1_CONF(0, 0, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_L1(0, 0, 3, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W_CONF(0, 0, 1, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W_CONF_DEQ(0, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W_CONF2(0, 0, 8, 8)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF1(0, 0, 1, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF2(0, 0, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF3(0, 0, 3, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF4(0, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 8, 9, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_W_CONF(0, 0, 1, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_OF_CONF1(0, 0, 8, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_OF_CONF2(0, 0, 10, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_COMPUTE_CONF(0, 0, 0, 1, 0, 1, 0)); + k230_kpu_command_u16(commands, &command_size, GNNE_PU_COMPUTE(0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_MFU_ACT1_CONF_STRIDE(0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_MFU_ACT1_CONF_SRC1(0, 0, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_MFU_ACT1_CONF_SRC1(0, 0, 0, 1, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_MFU_ACT1_CONF_SRC2(0, 0, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_MFU_ACT1_CONF_SRC2(0, 0, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_MFU_ACT1_CONF_DEST(8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_MFU_ACT1_CONF_DEQ(12, 9, 0, 0, 9)); + k230_kpu_command_u32(commands, &command_size, + GNNE_MFU_ACT1_CONF_DEQ(12, 9, 1, 1, 9)); + k230_kpu_command_u32(commands, &command_size, + GNNE_MFU_ACT1_CONF_QUANT(1, 9)); + k230_kpu_command_u32(commands, &command_size, + GNNE_MFU_ACT1_CONF(0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_MFU_ACT1_COMPUTE(7, 10, 11, 6)); + + qtest_memwrite(qts, K230_GNNE_SYNTH_PSUM_INPUT, input, sizeof(input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_PSUM_WEIGHT0, weight, + sizeof(weight)); + qtest_memwrite(qts, K230_GNNE_SYNTH_PSUM_WEIGHT_ZP, weight_zp, + sizeof(weight_zp)); + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_SOURCE, l2_source, + sizeof(l2_source)); + qtest_memwrite(qts, K230_GNNE_SYNTH_MFU_ARG, act1_table, + sizeof(act1_table)); + qtest_memset(qts, K230_GNNE_SYNTH_MFU_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_command_bytes(qts, commands, command_size); + qtest_memread(qts, K230_GNNE_SYNTH_MFU_OUTPUT, data, sizeof(data)); + + g_assert_cmphex(data[0], ==, 17); + for (size_t i = 1; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void k230_run_pu_compute_psum_classifier_stride(unsigned int channels) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input[] = { + 1, + }; + const uint8_t act0_entry[] = { + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + }; + uint8_t weight0[32 * K230_GNNE_SYNTH_LANE_WIDTH] = {}; + uint8_t weight1[32 * K230_GNNE_SYNTH_LANE_WIDTH] = {}; + uint8_t weight_zp[32] = {}; + uint8_t act0_table[32 * sizeof(act0_entry)]; + uint8_t commands[256]; + uint8_t data[72]; + size_t command_size = 0; + + g_assert_cmpuint(channels, >, 0); + g_assert_cmpuint(channels, <=, 32); + + for (size_t i = 0; i < 32; i++) { + weight0[i * K230_GNNE_SYNTH_LANE_WIDTH] = i + 1; + weight1[i * K230_GNNE_SYNTH_LANE_WIDTH] = 2 * (i + 1); + memcpy(act0_table + i * sizeof(act0_entry), act0_entry, + sizeof(act0_entry)); + } + + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(2, 0, 0x100)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(3, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(3, 3, 0x900)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(4, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(4, 4, 0x940)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(5, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(5, 5, 0xf40)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(6, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(6, 6, 0xf80)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(7, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(7, 7, 0x180)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(8, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ADDI(9, 0, channels)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(11, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_MMU_CONF(0, 2, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_SHAPE(8, 8, 8, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_SHAPE(8, 9, 8, 8, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(8, 8, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(9, 8, 8, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_L1_CONF(0, 0, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_L1(0, 0, 3, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W_CONF(0, 0, 1, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W_CONF_DEQ(0, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W_CONF2(0, 0, 8, 9)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_ACT0(0, 0, 6, 0, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF1(0, 0, 1, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF2(0, 0, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF3(0, 0, 3, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF4(0, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 8, 11, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_W_CONF(0, 0, 1, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_OF_CONF1(0, 0, 9, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_OF_CONF2(0, 0, 7, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ACT0_SRC1_CONF(0, 0, 0, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ACT0_COMPUTE(7, 0, 0, 0, 2, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_COMPUTE_CONF(0, 0, 0, 1, 0, 1, 0)); + k230_kpu_command_u16(commands, &command_size, GNNE_PU_COMPUTE(0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(4, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(4, 4, 0xc40)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_COMPUTE_CONF(0, 0, 1, 0, 1, 1, 0)); + k230_kpu_command_u16(commands, &command_size, GNNE_PU_COMPUTE(0, 0)); + + qtest_memwrite(qts, K230_GNNE_SYNTH_CLASS_INPUT, input, sizeof(input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CLASS_WEIGHT0, weight0, + sizeof(weight0)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CLASS_WEIGHT1, weight1, + sizeof(weight1)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CLASS_WEIGHT_ZP, weight_zp, + sizeof(weight_zp)); + qtest_memwrite(qts, K230_GNNE_SYNTH_CLASS_ACT0, act0_table, + sizeof(act0_table)); + qtest_memset(qts, K230_GNNE_SYNTH_CLASS_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_command_bytes(qts, commands, command_size); + qtest_memread(qts, K230_GNNE_SYNTH_CLASS_OUTPUT, data, sizeof(data)); + + for (size_t i = 0; i < channels; i++) { + uint8_t expected[2]; + + stw_le_p(expected, k230_test_fp16_from_uint(3 * (i + 1))); + g_assert_cmphex(data[i * 2], ==, expected[0]); + g_assert_cmphex(data[i * 2 + 1], ==, expected[1]); + } + for (size_t i = channels * 2; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_pu_compute_psum_classifier_stride(void) +{ + k230_run_pu_compute_psum_classifier_stride(32); + k230_run_pu_compute_psum_classifier_stride(9); +} + +static void test_pu_compute_deconv_1x1_psum_to_act0(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input_u8[] = { + 5, 6, + }; + const uint8_t input_i8[] = { + 0xff, 2, + }; + const uint8_t zero_input[] = { + 0, + }; + uint8_t weight[K230_GNNE_SYNTH_LANE_WIDTH] = {}; + uint8_t zero_weight[K230_GNNE_SYNTH_LANE_WIDTH] = {}; + const uint8_t weight_zp[] = { + 0, + }; + const uint8_t act0_table[] = { + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + }; + uint8_t commands[512]; + size_t command_size = 0; + uint8_t data[2]; + + weight[0] = 2; + weight[1] = 3; + + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(2, 0, 0x100)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(3, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(3, 3, 0x200)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(4, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(4, 4, 0x220)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(5, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(5, 5, 0x260)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(6, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(6, 6, 0x2e0)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(7, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(7, 7, 0x300)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(8, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(8, 8, 0x320)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(9, 0, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(10, 0, 2)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(13, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_MMU_CONF(0, 2, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_SHAPE(9, 10, 9, 9, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_SHAPE(9, 9, 9, 9, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(10, 9, 9, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(9, 9, 9, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W_CONF(0, 0, 1, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W_CONF_DEQ(0, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W_CONF2(0, 0, 8, 8)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W(0, 0, 5, 6, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF1(0, 0, 2, 2, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF2(0, 0, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF3(0, 0, 3, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF4(0, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 10, 13, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_W_CONF(0, 0, 1, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_OF_CONF1(0, 0, 8, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_OF_CONF2(0, 0, 13, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_COMPUTE_CONF(0, 0, 0, 1, 0, 0, 1)); + k230_kpu_command_u16(commands, &command_size, GNNE_PU_COMPUTE(0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF3(0, 0, 4, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 10, 13, 2)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_COMPUTE_CONF(0, 0, 1, 0, 0, 0, 1)); + k230_kpu_command_u16(commands, &command_size, GNNE_PU_COMPUTE(0, 0)); + + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(11, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(11, 11, 0x240)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(12, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(12, 12, 0x2a0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_L1_CONF(0, 0, 1, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_L1(0, 0, 11, 0, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W(0, 0, 12, 6, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_ACT0(0, 0, 7, 0, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF1(0, 0, 1, 1, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF3(0, 0, 11, 8, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 9, 13, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_OF_CONF1(0, 0, 8, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_OF_CONF2(0, 0, 8, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ACT0_SRC1_CONF(0, 0, 0, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ACT0_COMPUTE(8, 0, 0, 0, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_COMPUTE_CONF(0, 0, 1, 0, 1, 1, 0)); + k230_kpu_command_u16(commands, &command_size, GNNE_PU_COMPUTE(0, 0)); + + qtest_memwrite(qts, K230_GNNE_SYNTH_DECONV_INPUT_U8, input_u8, + sizeof(input_u8)); + qtest_memwrite(qts, K230_GNNE_SYNTH_DECONV_INPUT_I8, input_i8, + sizeof(input_i8)); + qtest_memwrite(qts, K230_GNNE_SYNTH_DECONV_ZERO_INPUT, zero_input, + sizeof(zero_input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_DECONV_WEIGHT, weight, + sizeof(weight)); + qtest_memwrite(qts, K230_GNNE_SYNTH_DECONV_ZERO_WEIGHT, zero_weight, + sizeof(zero_weight)); + qtest_memwrite(qts, K230_GNNE_SYNTH_DECONV_WEIGHT_ZP, weight_zp, + sizeof(weight_zp)); + qtest_memwrite(qts, K230_GNNE_SYNTH_DECONV_ACT0, act0_table, + sizeof(act0_table)); + qtest_memset(qts, K230_GNNE_SYNTH_DECONV_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_command_bytes(qts, commands, command_size); + qtest_memread(qts, K230_GNNE_SYNTH_DECONV_OUTPUT, data, sizeof(data)); + + g_assert_cmphex(data[0], ==, 32); + g_assert_cmphex(data[1], ==, 0xa5); + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_pu_compute_deconv_1x1_multi_channel(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input[] = { + 4, 5, + }; + const uint8_t zero_input[] = { + 0, 0, + }; + uint8_t weights[2 * K230_GNNE_SYNTH_LANE_WIDTH] = {}; + uint8_t zero_weight[2 * K230_GNNE_SYNTH_LANE_WIDTH] = {}; + const uint8_t weight_zp[] = { + 0, 0, + }; + const uint8_t act0_table[] = { + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + }; + uint8_t commands[512]; + size_t command_size = 0; + uint8_t data[2]; + + weights[0] = 2; + weights[1] = 3; + weights[K230_GNNE_SYNTH_LANE_WIDTH] = 5; + weights[K230_GNNE_SYNTH_LANE_WIDTH + 1] = 7; + + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(2, 0, 0x100)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(3, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(3, 3, 0x340)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(4, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(4, 4, 0x360)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(5, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(5, 5, 0x3c0)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(6, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(6, 6, 0x3e0)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(7, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(7, 7, 0x400)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(8, 0, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(9, 0, 2)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(10, 0, 0)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(11, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(11, 11, 0x240)); + k230_kpu_command_u32(commands, &command_size, GNNE_LUI(12, 1)); + k230_kpu_command_u32(commands, &command_size, GNNE_ADDI(12, 12, 0x2a0)); + k230_kpu_command_u32(commands, &command_size, GNNE_MMU_CONF(0, 2, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_SHAPE(8, 9, 8, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_SHAPE(8, 9, 8, 8, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(9, 8, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_SS_PACK_STRIDE(9, 8, 8, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W_CONF(0, 0, 1, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W_CONF_DEQ(0, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W_CONF2(0, 0, 8, 8)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_ACT0(0, 0, 6, 0, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF1(0, 0, 2, 2, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF2(0, 0, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF3(0, 0, 3, 8, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF4(0, 0, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF_DEQ(0, 0, 9, 10, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_W_CONF(0, 0, 1, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_OF_CONF1(0, 0, 8, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_OF_CONF2(0, 0, 10, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_COMPUTE_CONF(0, 0, 0, 1, 0, 0, 1)); + k230_kpu_command_u16(commands, &command_size, GNNE_PU_COMPUTE(0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ACT0_SRC1_CONF(0, 0, 0, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_ACT0_COMPUTE(7, 0, 0, 0, 0, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_L1_CONF(0, 0, 1, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_L1(0, 0, 11, 0, 1, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_DM_LOAD_W(0, 0, 12, 5, 0, 0)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF1(0, 0, 1, 1, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_FETCHIF_CONF3(0, 0, 11, 8, 1)); + k230_kpu_command_u32(commands, &command_size, + GNNE_PU_COMPUTE_CONF(0, 0, 1, 0, 1, 1, 0)); + k230_kpu_command_u16(commands, &command_size, GNNE_PU_COMPUTE(0, 0)); + + qtest_memwrite(qts, K230_GNNE_SYNTH_DECONV_MULTI_INPUT, input, + sizeof(input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_DECONV_ZERO_INPUT, zero_input, + sizeof(zero_input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_DECONV_MULTI_WEIGHT, weights, + sizeof(weights)); + qtest_memwrite(qts, K230_GNNE_SYNTH_DECONV_ZERO_WEIGHT, zero_weight, + sizeof(zero_weight)); + qtest_memwrite(qts, K230_GNNE_SYNTH_DECONV_MULTI_WEIGHT_ZP, weight_zp, + sizeof(weight_zp)); + qtest_memwrite(qts, K230_GNNE_SYNTH_DECONV_MULTI_ACT0, act0_table, + sizeof(act0_table)); + qtest_memset(qts, K230_GNNE_SYNTH_DECONV_MULTI_OUTPUT, 0xa5, + sizeof(data)); + + k230_kpu_run_command_bytes(qts, commands, command_size); + qtest_memread(qts, K230_GNNE_SYNTH_DECONV_MULTI_OUTPUT, data, + sizeof(data)); + + g_assert_cmphex(data[0], ==, 23); + g_assert_cmphex(data[1], ==, 55); + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_mfu_transpose_nwch_i16(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input[] = { + 0x01, 0x10, 0x02, 0x10, 0x03, 0x10, + 0x04, 0x10, 0x05, 0x10, 0x06, 0x10, + }; + const uint8_t expected[] = { + 0x01, 0x10, 0x04, 0x10, + 0x02, 0x10, 0x05, 0x10, + 0x03, 0x10, 0x06, 0x10, + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_LUI(3, 1), + GNNE_ADDI(3, 3, 0x500), + GNNE_LUI(4, 1), + GNNE_ADDI(4, 4, 0x600), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 3), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 8, 9, 10, 0), + GNNE_SS_PACK_STRIDE(8, 9, 10, 0), + GNNE_SS_PACK_STRIDE(10, 8, 9, 1), + GNNE_MFU_TRANSPOSE_CONF(1, 0, 2, 4), + GNNE_MFU_TRANSPOSE(4, 3, 0), + }; + uint8_t data[sizeof(expected)]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_TRANSPOSE_INPUT, input, + sizeof(input)); + qtest_memset(qts, K230_GNNE_SYNTH_TRANSPOSE_OUTPUT, 0xa5, + sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_TRANSPOSE_OUTPUT, data, + sizeof(data)); + + g_assert_cmpmem(data, sizeof(data), expected, sizeof(expected)); + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_pdp0_compute_depthwise_act0_u8(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input[] = { + 1, 2, + 3, 4, + }; + const uint8_t weights[] = { + 4, 5, + 6, 7, + }; + const uint8_t weight_zp[] = { + 3, + }; + const uint8_t act0_table[] = { + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x600), + GNNE_ADDI(4, 0, 0x620), + GNNE_ADDI(5, 0, 0x680), + GNNE_ADDI(6, 0, 0x690), + GNNE_ADDI(7, 0, 0x6c0), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 2), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 8, 9, 9, 0), + GNNE_SS_PACK_SHAPE(8, 8, 8, 8, 1), + GNNE_SS_PACK_STRIDE(8, 8, 8, 2), + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 1), + GNNE_DM_LOAD_ACT0(0, 0, 6, 0, 1, 1), + GNNE_DM_STORE_OF(0, 0, 7, 1, 1), + GNNE_PDP0_MODE_CONF(0, 0, 0), + GNNE_PDP0_FETCHIF_CONF1(0, 0, 1, 1), + GNNE_PDP0_FETCHIF_CONF2(0, 0, 8, 0), + GNNE_PDP0_FETCHIF_CONF3(0, 0, 0), + GNNE_PDP0_FETCHIF_CONF4(0, 0, 0, 0), + GNNE_PDP0_FETCHIF_CONF_DEQ(0, 0, 10, 1), + GNNE_PDP0_W_CONF(0, 0, 2, 2), + GNNE_PDP0_OF_CONF(0, 0, 2, 1), + GNNE_ACT0_SRC1_CONF(0, 0, 0, 1, 0), + GNNE_ACT0_COMPUTE(0, 0, 0, 1, 0, 1), + GNNE_PDP0_COMPUTE(0, 3), + }; + uint8_t data[4]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_PDP0_INPUT, input, sizeof(input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_PDP0_WEIGHT, weights, + sizeof(weights)); + qtest_memwrite(qts, K230_GNNE_SYNTH_PDP0_WEIGHT_ZP, weight_zp, + sizeof(weight_zp)); + qtest_memwrite(qts, K230_GNNE_SYNTH_PDP0_ACT0, act0_table, + sizeof(act0_table)); + qtest_memset(qts, K230_GNNE_SYNTH_PDP0_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_PDP0_OUTPUT, data, sizeof(data)); + + g_assert_cmphex(data[0], ==, 10); + for (size_t i = 1; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_pdp0_conf_latches_shape_stride(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input[] = { + 1, 2, + 3, 4, + }; + const uint8_t weights[] = { + 4, 5, + 6, 7, + }; + const uint8_t weight_zp[] = { + 3, + }; + const uint8_t act0_table[] = { + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x600), + GNNE_ADDI(4, 0, 0x620), + GNNE_ADDI(5, 0, 0x680), + GNNE_ADDI(6, 0, 0x690), + GNNE_ADDI(7, 0, 0x6c0), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 2), + GNNE_ADDI(11, 0, 0), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 8, 9, 9, 0), + GNNE_SS_PACK_SHAPE(8, 8, 8, 8, 1), + GNNE_SS_PACK_STRIDE(8, 8, 8, 2), + GNNE_DM_LOAD_W(0, 0, 4, 5, 0, 1), + GNNE_DM_LOAD_ACT0(0, 0, 6, 0, 1, 1), + GNNE_DM_STORE_OF(0, 0, 7, 1, 1), + GNNE_PDP0_MODE_CONF(0, 0, 0), + GNNE_PDP0_FETCHIF_CONF1(0, 0, 1, 1), + GNNE_PDP0_FETCHIF_CONF2(0, 0, 8, 0), + GNNE_PDP0_FETCHIF_CONF3(0, 0, 0), + GNNE_PDP0_FETCHIF_CONF4(0, 0, 0, 0), + GNNE_PDP0_FETCHIF_CONF_DEQ(0, 0, 10, 1), + GNNE_PDP0_W_CONF(0, 0, 2, 2), + GNNE_PDP0_OF_CONF(0, 0, 2, 1), + GNNE_SS_PACK_SHAPE(11, 11, 11, 11, 0), + GNNE_SS_PACK_SHAPE(11, 11, 11, 11, 1), + GNNE_SS_PACK_STRIDE(11, 11, 11, 2), + GNNE_ACT0_SRC1_CONF(0, 0, 0, 1, 0), + GNNE_ACT0_COMPUTE(0, 0, 0, 1, 0, 1), + GNNE_PDP0_COMPUTE(0, 3), + }; + uint8_t data[4]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_PDP0_INPUT, input, sizeof(input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_PDP0_WEIGHT, weights, + sizeof(weights)); + qtest_memwrite(qts, K230_GNNE_SYNTH_PDP0_WEIGHT_ZP, weight_zp, + sizeof(weight_zp)); + qtest_memwrite(qts, K230_GNNE_SYNTH_PDP0_ACT0, act0_table, + sizeof(act0_table)); + qtest_memset(qts, K230_GNNE_SYNTH_PDP0_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_PDP0_OUTPUT, data, sizeof(data)); + + g_assert_cmphex(data[0], ==, 10); + for (size_t i = 1; i < sizeof(data); i++) { + g_assert_cmphex(data[i], ==, 0xa5); + } + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +static void test_pdp0_compute_max_act0_u8(void) +{ + QTestState *qts = k230_kpu_init(); + const uint8_t input[] = { + 1, 9, 2, + 4, 3, 8, + 7, 6, 5, + }; + const uint8_t expected[] = { + 9, 9, + 7, 8, + }; + const uint8_t act0_table[] = { + 0x00, 0x3c, /* negative slope 1 */ + 0x00, 0x3c, /* positive slope 1 */ + 0x00, 0x00, /* negative bias 0 */ + 0x00, 0x00, /* positive bias 0 */ + 0x00, 0x00, /* lower 0 */ + 0xf8, 0x5b, /* upper 255 */ + 0x00, 0x00, /* threshold 0 */ + }; + const uint32_t commands[] = { + GNNE_ADDI(2, 0, 0x100), + GNNE_ADDI(3, 0, 0x600), + GNNE_ADDI(6, 0, 0x690), + GNNE_ADDI(7, 0, 0x6c0), + GNNE_ADDI(8, 0, 1), + GNNE_ADDI(9, 0, 2), + GNNE_ADDI(10, 0, 3), + GNNE_ADDI(11, 0, 0), + GNNE_MMU_CONF(0, 2, 0), + GNNE_SS_PACK_SHAPE(8, 8, 10, 10, 0), + GNNE_SS_PACK_SHAPE(8, 8, 9, 9, 1), + GNNE_SS_PACK_STRIDE(8, 8, 9, 2), + GNNE_DM_LOAD_ACT0(0, 0, 6, 0, 1, 1), + GNNE_DM_STORE_OF(0, 0, 7, 1, 1), + GNNE_PDP0_MODE_CONF(0, 0, 2), + GNNE_PDP0_FETCHIF_CONF1(0, 0, 1, 1), + GNNE_PDP0_FETCHIF_CONF2(0, 0, 8, 0), + GNNE_PDP0_FETCHIF_CONF3(0, 0, 0), + GNNE_PDP0_FETCHIF_CONF4(0, 0, 11, 0), + GNNE_PDP0_FETCHIF_CONF_DEQ(0, 0, 11, 1), + GNNE_PDP0_W_CONF(0, 0, 2, 2), + GNNE_PDP0_OF_CONF(0, 0, 2, 1), + GNNE_ACT0_SRC1_CONF(0, 0, 0, 1, 0), + GNNE_ACT0_COMPUTE(0, 0, 0, 1, 0, 1), + GNNE_PDP0_COMPUTE(0, 3), + }; + uint8_t data[sizeof(expected)]; + + qtest_memwrite(qts, K230_GNNE_SYNTH_PDP0_INPUT, input, sizeof(input)); + qtest_memwrite(qts, K230_GNNE_SYNTH_PDP0_ACT0, act0_table, + sizeof(act0_table)); + qtest_memset(qts, K230_GNNE_SYNTH_PDP0_OUTPUT, 0xa5, sizeof(data)); + + k230_kpu_run_commands(qts, commands, G_N_ELEMENTS(commands)); + qtest_memread(qts, K230_GNNE_SYNTH_PDP0_OUTPUT, data, sizeof(data)); + + g_assert_cmpmem(data, sizeof(data), expected, sizeof(expected)); + + k230_kpu_assert_done_irq(qts); + k230_kpu_clear_done_irq(qts); + + qtest_quit(qts); +} + +int main(int argc, char *argv[]) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/k230-kpu/zero-start-does-not-complete", + test_zero_start_does_not_complete); + qtest_add_func("/k230-kpu/delayed-completion", + test_delayed_completion); + qtest_add_func("/k230-kpu/command-completion-pages", + test_command_completion_pages); + qtest_add_func("/k230-kpu/same-page-addresses-zero-one-page", + test_same_page_addresses_zero_one_page); + qtest_add_func("/k230-kpu/output-window-boundaries", + test_output_window_boundaries); + qtest_add_func("/k230-kpu/empty-command-range", + test_empty_command_range); + qtest_add_func("/k230-kpu/reversed-command-range", + test_reversed_command_range); + qtest_add_func("/k230-kpu/clear-status-allows-second-run", + test_clear_status_allows_second_run); + qtest_add_func("/k230-kpu/l2-store-copies-parsed-output", + test_l2_store_copies_parsed_output); + qtest_add_func("/k230-kpu/l2-store-converts-fp16-to-fp32", + test_l2_store_converts_fp16_to_fp32); + qtest_add_func("/k230-kpu/l2-load-copies-to-glb", + test_l2_load_copies_to_glb); + qtest_add_func("/k230-kpu/l2-load-uses-packed-strides", + test_l2_load_uses_packed_strides); + qtest_add_func("/k230-kpu/l2-store-uses-packed-strides", + test_l2_store_uses_packed_strides); + qtest_add_func("/k230-kpu/l2-store-reads-high-mmu0-glb", + test_l2_store_reads_high_mmu0_glb); + qtest_add_func("/k230-kpu/l2-nonzero-bank-raw-offset", + test_l2_nonzero_bank_uses_raw_offset); + qtest_add_func("/k230-kpu/l2-bank-write-updates-logical-glb", + test_l2_bank_write_updates_logical_glb); + qtest_add_func("/k230-kpu/l2-load-converts-fp32-to-fp16", + test_l2_load_converts_fp32_to_fp16); + qtest_add_func("/k230-kpu/l2-load-then-store-roundtrip", + test_l2_load_then_store_roundtrip); + qtest_add_func("/k230-kpu/ai2d-compute-preserves-command-stream", + test_ai2d_compute_preserves_command_stream); + qtest_add_func("/k230-kpu/l2-load-w-uses-lane-layout", + test_l2_load_w_uses_lane_layout); + qtest_add_func("/k230-kpu/l2-load-w-conf-latches-rlen", + test_l2_load_w_conf_latches_rlen); + qtest_add_func("/k230-kpu/l2-load-w-translates-low-source", + test_l2_load_w_translates_low_source); + qtest_add_func("/k230-kpu/l2-load-w-rebases-function-source", + test_l2_load_w_rebases_function_source); + qtest_add_func("/k230-kpu/l2-load-w-rebases-absolute-rdata-source", + test_l2_load_w_rebases_absolute_rdata_source); + qtest_add_func("/k230-kpu/l2-load-w-keeps-absolute-rdata-source", + test_l2_load_w_keeps_absolute_rdata_source); + qtest_add_func("/k230-kpu/l2-load-w-synthesizes-function-arg", + test_l2_load_w_synthesizes_function_arg); + qtest_add_func("/k230-kpu/l2-load-w-synthesizes-rdata-function-args", + test_l2_load_w_synthesizes_rdata_function_args); + qtest_add_func("/k230-kpu/l2-load-w-rdata-fallback-base", + test_l2_load_w_uses_rdata_fallback_base); + qtest_add_func("/k230-kpu/runtime-function-command-base", + test_runtime_function_command_uses_runtime_base); + qtest_add_func("/k230-kpu/runtime-rdata-shadow-survives-glb-mutation", + test_runtime_rdata_shadow_survives_glb_mutation); + qtest_add_func("/k230-kpu/l2-load-rdata-prefix-shadow", + test_l2_load_uses_rdata_prefix_shadow); + qtest_add_func("/k230-kpu/runtime-arg-table-direct-io", + test_runtime_arg_table_drives_direct_io); + qtest_add_func("/k230-kpu/l2-store-rdata-alias-destination", + test_l2_store_accepts_rdata_alias_destination); + qtest_add_func("/k230-kpu/l2-store-runtime-ddr-mirror", + test_l2_store_runtime_mirrors_to_ddr_source); + qtest_add_func("/k230-kpu/l2-store-conf-latches-stride", + test_l2_store_conf_latches_stride); + qtest_add_func("/k230-kpu/mfu-act1-identity-u8", + test_mfu_act1_identity_u8); + qtest_add_func("/k230-kpu/mfu-act1-conf-dest-latches-rlen", + test_mfu_act1_conf_dest_latches_rlen); + qtest_add_func("/k230-kpu/mfu-act1-add-clip-u8", + test_mfu_act1_add_clip_u8); + qtest_add_func("/k230-kpu/mfu-act1-fp16-roundtrip", + test_mfu_act1_fp16_roundtrip); + qtest_add_func("/k230-kpu/mfu-act1-mul-fp16-two-l2-sources", + test_mfu_act1_mul_fp16_two_l2_sources); + qtest_add_func("/k230-kpu/mfu-act1-raddr-s2-zero-is-unary", + test_mfu_act1_raddr_s2_zero_is_unary); + qtest_add_func("/k230-kpu/mfu-act1-segment-linefit-fp16", + test_mfu_act1_segment_linefit_fp16); + qtest_add_func("/k230-kpu/mfu-act1-packed-strides-u8", + test_mfu_act1_packed_strides_u8); + qtest_add_func("/k230-kpu/mfu-pdp1-average-u8", + test_mfu_pdp1_average_u8); + qtest_add_func("/k230-kpu/mfu-pdp1-min-fp16-sum-i16", + test_mfu_pdp1_min_fp16_sum_i16); + qtest_add_func("/k230-kpu/mfu-pdp1-sliding-min-u8", + test_mfu_pdp1_sliding_min_u8); + qtest_add_func("/k230-kpu/pu-compute-conv2d-act0-u8", + test_pu_compute_conv2d_act0_u8); + qtest_add_func("/k230-kpu/pu-compute-dm-store-of-act0-dest", + test_pu_compute_dm_store_of_act0_dest); + qtest_add_func("/k230-kpu/pu-compute-conv2d-i8-input-act0-u8", + test_pu_compute_conv2d_i8_input_act0_u8); + qtest_add_func("/k230-kpu/pu-compute-conv2d-packed-if-stride", + test_pu_compute_conv2d_packed_if_stride); + qtest_add_func("/k230-kpu/pu-compute-conv2d-packed-of-stride", + test_pu_compute_conv2d_packed_of_stride); + qtest_add_func("/k230-kpu/pu-compute-of-conf-latches-shape-stride", + test_pu_compute_of_conf_latches_shape_stride); + qtest_add_func("/k230-kpu/pu-compute-psum-accumulates-to-act0", + test_pu_compute_psum_accumulates_to_act0); + qtest_add_func("/k230-kpu/pu-compute-fetchif-stride-without-l1", + test_pu_compute_uses_fetchif_stride_without_l1); + qtest_add_func("/k230-kpu/pu-compute-zero-fetchif-uses-l1-source", + test_pu_compute_zero_fetchif_uses_l1_source); + qtest_add_func("/k230-kpu/pu-compute-fetchif-offset-uses-if-staging", + test_pu_compute_fetchif_offset_uses_if_staging); + qtest_add_func("/k230-kpu/pu-compute-l1-bank-source-raw-offset", + test_pu_compute_l1_bank_source_uses_raw_offset); + qtest_add_func("/k230-kpu/pu-compute-l1-i16-repacks-byte-planes", + test_pu_compute_l1_i16_repacks_byte_planes); + qtest_add_func("/k230-kpu/mfu-act1-adds-psum-and-l2-u8", + test_mfu_act1_adds_psum_and_l2_u8); + qtest_add_func("/k230-kpu/pu-compute-psum-classifier-stride", + test_pu_compute_psum_classifier_stride); + qtest_add_func("/k230-kpu/pu-compute-deconv-1x1-psum-to-act0", + test_pu_compute_deconv_1x1_psum_to_act0); + qtest_add_func("/k230-kpu/pu-compute-deconv-1x1-multi-channel", + test_pu_compute_deconv_1x1_multi_channel); + qtest_add_func("/k230-kpu/mfu-transpose-nwch-i16", + test_mfu_transpose_nwch_i16); + qtest_add_func("/k230-kpu/pdp0-compute-depthwise-act0-u8", + test_pdp0_compute_depthwise_act0_u8); + qtest_add_func("/k230-kpu/pdp0-conf-latches-shape-stride", + test_pdp0_conf_latches_shape_stride); + qtest_add_func("/k230-kpu/pdp0-compute-max-act0-u8", + test_pdp0_compute_max_act0_u8); + + return g_test_run(); +} diff --git a/tests/qtest/k230-sysctl-test.c b/tests/qtest/k230-sysctl-test.c new file mode 100644 index 0000000000000..0233d3b92589a --- /dev/null +++ b/tests/qtest/k230-sysctl-test.c @@ -0,0 +1,727 @@ +/* + * QTest testcase for K230 sysctl blocks + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qobject/qdict.h" +#include "qobject/qlist.h" + +#define K230_RMU_BASE 0x91101000 +#define K230_BOOT_BASE 0x91102000 +#define K230_FLASH_BASE 0xc0000000 +#define K230_KPU_CFG_BASE 0x80400000 +#define K230_CLINT_BASE 0xf04000000ULL +#define K230_PLIC_BASE 0xf00000000ULL +#define K230_GSDMA_BASE 0x80800000 +#define K230_FFT_BASE 0x80400800 +#define K230_AI_2D_BASE 0x80400c00 +#define K230_NON_AI_2D_BASE 0x8080c000 +#define K230_ISP_BASE 0x90000000 +#define K230_DEWARP_BASE 0x90008000 +#define K230_RX_CSI_BASE 0x90009000 +#define K230_DPU_BASE 0x90a00000 +#define K230_UART3_BASE 0x91403000 +#define K230_I2C0_BASE 0x91405000 + +#define K230_CPU1_RST_CTL 0x0c +#define K230_CPU1_HART_RSTVEC 0x104 +#define K230_CLINT_SSIP0 0xc000 +#define K230_CLINT_STIMECMPL0 0xd000 +#define K230_CLINT_STIMECMPH0 0xd004 +#define K230_I2C_TAR 0x04 +#define K230_I2C_DATA_CMD 0x10 +#define K230_I2C_ENABLE 0x6c +#define K230_I2C_CMD_READ (1u << 8) +#define K230_I2C_CMD_STOP (1u << 9) +#define K230_I2C_CMD_RESTART (1u << 10) +#define K230_PLIC_ENABLE_S 0x2080 +#define K230_PLIC_CONTEXT_S 0x201000 +#define K230_PLIC_THRESHOLD 0x00 +#define K230_PLIC_CLAIM 0x04 +#define K230_ISP_MAIN_STATUS 0x05c4 +#define K230_ISP_MAIN_CLEAR 0x05c8 +#define K230_ISP_MCM_CTRL 0x1300 +#define K230_ISP_MCM0_STATUS 0x16d0 +#define K230_ISP_MCM0_CLEAR 0x16d8 +#define K230_ISP_MCM2_STATUS 0x56d8 +#define K230_ISP_MCM2_CLEAR 0x56dc +#define K230_ISP_TOP_STATUS 0x3d60 +#define K230_ISP_FE_START 0x3d64 +#define K230_ISP_FE_MI_STATUS 0x3d74 +#define K230_ISP_FE_MI_CLEAR 0x3d78 +#define K230_ISP_TOP_PENDING (1u << 0) +#define K230_ISP_TOP_ACK (1u << 1) +#define K230_ISP_FE_START_CMD (1u << 16) +#define K230_ISP_MCM_CH0_ENABLE (1u << 6) +#define K230_ISP_MCM_CH1_ENABLE (1u << 7) +#define K230_ISP_MCM_CH2_ENABLE (1u << 17) +#define K230_ISP_MCM_CH2_STATUS (1u << 14) +#define K230_ISP_MCM_CH0_BUFFER (1u << 0) +#define K230_ISP_MCM_CH1_BUFFER (1u << 3) +#define K230_ISP_MCM_CH0_DONE (1u << 6) +#define K230_ISP_MCM_CH1_DONE (1u << 7) +#define K230_ISP_MI_FRAME_STATUS (1u << 8) +#define K230_ISP_MCM_FRAME_NS (1000000000 / 30) +#define K230_DWE_CTRL_LOW 0x004 +#define K230_DWE_DMA_START0 0x010 +#define K230_DWE_DMA_START1 0x014 +#define K230_DWE_IRQ_STATUS_LOW 0x070 +#define K230_DWE_BUS_CTRL_LOW 0x074 +#define K230_DWE_CTRL 0xc04 +#define K230_DWE_IRQ_STATUS 0xc70 +#define K230_DWE_IRQ_CLEAR 0xd00 +#define K230_DWE_START (1u << 1) +#define K230_DWE_BUS_ENABLE (1u << 31) +#define K230_FE_START 0xd04 +#define K230_FE_IRQ_STATUS 0xd14 +#define K230_FE_IRQ_CLEAR 0xd18 +#define K230_FE_START_CMD (1u << 16) +#define K230_VSE_IRQ_STATUS 0xa50 +#define K230_VSE_IRQ_CLEAR 0xa58 +#define K230_RX_CSI_HOST2_ENABLE 0x1008 +#define K230_RX_CSI_HOST2_PHY_STATE 0x1014 +#define K230_RX_CSI_PHY_STOPSTATE (1u << 16) +#define K230_RX_CSI_DIS_FRAME_M 0x008c +#define K230_RX_CSI_DIS_FRAME_N 0x0090 +#define K230_RX_CSI_DIS_FRAME_EN 0x0094 +#define K230_RX_CSI_PHY_CTRL0 0x0850 +#define K230_RX_CSI_PHY_DATA0 0x0854 +#define K230_RX_CSI_PHY_CTRL1 0x0858 +#define K230_RX_CSI_PHY_DATA1 0x085c +#define K230_GNNE_STATUS 0x130 +#define K230_GSDMA_INT_STAT 0x08 +#define K230_GSDMA_CH0_CTL 0x50 +#define K230_GSDMA_CH0_STATUS 0x54 +#define K230_GSDMA_DONE_CH0 (1u << 0) +#define K230_NON_AI_2D_SRC_SIZE 0x000 +#define K230_NON_AI_2D_SRC_CH0_ADDR 0x004 +#define K230_NON_AI_2D_SRC_CH1_ADDR 0x008 +#define K230_NON_AI_2D_SRC_CH2_ADDR 0x00c +#define K230_NON_AI_2D_SRC_STRIDE01 0x010 +#define K230_NON_AI_2D_SRC_STRIDE2 0x014 +#define K230_NON_AI_2D_FMT 0x018 +#define K230_NON_AI_2D_DST_CH0_ADDR 0x13c +#define K230_NON_AI_2D_DST_CH1_ADDR 0x140 +#define K230_NON_AI_2D_DST_CH2_ADDR 0x144 +#define K230_NON_AI_2D_DST_STRIDE01 0x148 +#define K230_NON_AI_2D_DST_STRIDE2 0x14c +#define K230_NON_AI_2D_MAIN_CFG 0x3a0 +#define K230_NON_AI_2D_INTR_STATUS 0x3a8 +#define K230_NON_AI_2D_INTR_CLEAR 0x3ac +#define K230_NON_AI_2D_STOP_BUSY 0x3b0 +#define K230_NON_AI_2D_CALC_EN (1u << 0) +#define K230_NON_AI_2D_FMT_I420 2 +#define K230_NON_AI_2D_MODE_OSD (1u << 4) +#define K230_NON_AI_2D_INTR_MASK (1u << 16) +#define K230_DPU_STATUS 0x1f4 +#define K230_DPU_IRQ_CLEAR 0x1fc +#define K230_DPU_START 0x200 +#define K230_DPU_START_CH1 0x380 +#define K230_DPU_DONE 0x3 +#define K230_DPU_CLEAR_ACK 0x20002 +#define K230_AI2D_CALC_ENABLE 0x80 +#define K230_AI2D_JOB 0x8c +#define K230_AI2D_CLEAR 0xa0 +#define K230_AI2D_LEGACY_START 0xc0 +#define K230_FFT_IRQ 190 +#define K230_AI2D_IRQ 191 +#define K230_ISP_MI_IRQ 127 +#define K230_ISP_FE_IRQ 128 +#define K230_ISP_IRQ 129 +#define K230_DWE_IRQ 130 +#define K230_FE_IRQ 131 +#define K230_DMA_IRQ 140 +#define K230_NON_AI_2D_IRQ 141 +#define K230_DPU_IRQ 186 +#define K230_VSE_IRQ 204 +#define K230_CPU1_RST_REQ (1u << 0) +#define K230_CPU1_RST_DONE (1u << 12) +#define K230_CPU1_PRST_DONE (1u << 13) +#define K230_CPU1_RST_REQ_WEN (1u << 16) +#define K230_CPU1_RST_DONE_WEN (1u << 28) +#define K230_CPU1_RST_CTL_RESET \ + (K230_CPU1_PRST_DONE | K230_CPU1_RST_REQ) +#define K230_NON_AI_2D_TEST_SRC_Y 0x01000000 +#define K230_NON_AI_2D_TEST_SRC_U 0x01000100 +#define K230_NON_AI_2D_TEST_SRC_V 0x01000200 +#define K230_NON_AI_2D_TEST_DST_Y 0x01000300 +#define K230_NON_AI_2D_TEST_DST_U 0x01000400 +#define K230_NON_AI_2D_TEST_DST_V 0x01000500 +#define K230_FLASH_TEST_OFFSET 0x6000 + +static void k230_test_cpu1_reset_sequence(const char *machine_args) +{ + QTestState *qts = qtest_init(machine_args); + + g_assert_cmphex(qtest_readl(qts, K230_RMU_BASE + K230_CPU1_RST_CTL), ==, + K230_CPU1_RST_CTL_RESET); + + qtest_writel(qts, K230_BOOT_BASE + K230_CPU1_HART_RSTVEC, 0x80200000); + + qtest_writel(qts, K230_RMU_BASE + K230_CPU1_RST_CTL, + K230_CPU1_RST_DONE_WEN | K230_CPU1_RST_DONE); + g_assert_cmphex(qtest_readl(qts, K230_RMU_BASE + K230_CPU1_RST_CTL), ==, + K230_CPU1_RST_CTL_RESET); + + qtest_writel(qts, K230_RMU_BASE + K230_CPU1_RST_CTL, + K230_CPU1_RST_REQ_WEN | K230_CPU1_RST_REQ); + g_assert_cmphex(qtest_readl(qts, K230_RMU_BASE + K230_CPU1_RST_CTL), ==, + K230_CPU1_RST_CTL_RESET); + + qtest_writel(qts, K230_RMU_BASE + K230_CPU1_RST_CTL, + K230_CPU1_RST_REQ_WEN); + g_assert_cmphex(qtest_readl(qts, K230_RMU_BASE + K230_CPU1_RST_CTL), ==, + K230_CPU1_PRST_DONE | K230_CPU1_RST_DONE); + + qtest_quit(qts); +} + +static void test_cpu1_reset_sequence_smp1(void) +{ + k230_test_cpu1_reset_sequence("-machine k230-canmv -smp 1"); +} + +static void test_cpu1_reset_sequence_smp2(void) +{ + k230_test_cpu1_reset_sequence("-machine k230-canmv -smp 2"); +} + +static QDict *k230_query_cpu(QTestState *qts, int cpu_index) +{ + QDict *resp; + QList *cpus; + QListEntry *entry; + + resp = qtest_qmp(qts, "{ 'execute': 'query-cpus-fast' }"); + g_assert(qdict_haskey(resp, "return")); + cpus = qdict_get_qlist(resp, "return"); + + QLIST_FOREACH_ENTRY(cpus, entry) { + QDict *cpu = qobject_to(QDict, entry->value); + + if (qdict_get_int(cpu, "cpu-index") == cpu_index) { + return resp; + } + } + + qobject_unref(resp); + return NULL; +} + +static bool k230_qom_get_bool(QTestState *qts, const char *path, + const char *property) +{ + QDict *resp; + bool value; + + resp = qtest_qmp(qts, "{ 'execute': 'qom-get', 'arguments': " + "{ 'path': %s, 'property': %s } }", + path, property); + g_assert(qdict_haskey(resp, "return")); + value = qdict_get_bool(resp, "return"); + qobject_unref(resp); + + return value; +} + +static void test_smp1_topology(void) +{ + QTestState *qts = qtest_init("-machine k230-canmv -smp 1"); + QDict *resp = k230_query_cpu(qts, 0); + QList *cpus; + const QListEntry *entry; + QDict *cpu; + + g_assert_nonnull(resp); + cpus = qdict_get_qlist(resp, "return"); + entry = qlist_first(cpus); + cpu = qobject_to(QDict, entry->value); + g_assert_cmpint(qlist_size(cpus), ==, 1); + g_assert_cmpstr(qdict_get_str(cpu, "qom-type"), ==, + "thead-c908-riscv-cpu"); + + qobject_unref(resp); + qtest_quit(qts); +} + +static void test_smp2_topology(void) +{ + QTestState *qts = qtest_init("-machine k230-canmv -smp 2"); + QDict *resp = k230_query_cpu(qts, 1); + QList *cpus; + QDict *cpu1 = NULL; + QListEntry *entry; + + g_assert_nonnull(resp); + cpus = qdict_get_qlist(resp, "return"); + g_assert_cmpint(qlist_size(cpus), ==, 2); + QLIST_FOREACH_ENTRY(cpus, entry) { + QDict *cpu = qobject_to(QDict, entry->value); + + if (qdict_get_int(cpu, "cpu-index") == 1) { + cpu1 = cpu; + } + } + + g_assert_nonnull(cpu1); + g_assert_cmpstr(qdict_get_str(cpu1, "qom-type"), ==, + "thead-c908v-riscv-cpu"); + g_assert_cmpstr(qdict_get_str(cpu1, "qom-path"), ==, + "/machine/soc/c908v-cpu/harts[0]"); + g_assert_true(k230_qom_get_bool(qts, "/machine/soc/c908-cpu/harts[0]", + "start-powered-off")); + g_assert_false(k230_qom_get_bool(qts, "/machine/soc/c908v-cpu/harts[0]", + "start-powered-off")); + + qobject_unref(resp); + qtest_quit(qts); +} + +static void test_smp2_boot_both_cores_topology(void) +{ + QTestState *qts = qtest_init("-machine k230-canmv,boot-both-cores=on -smp 2"); + QDict *resp = k230_query_cpu(qts, 1); + QList *cpus; + QDict *cpu1 = NULL; + QListEntry *entry; + + g_assert_nonnull(resp); + cpus = qdict_get_qlist(resp, "return"); + g_assert_cmpint(qlist_size(cpus), ==, 2); + QLIST_FOREACH_ENTRY(cpus, entry) { + QDict *cpu = qobject_to(QDict, entry->value); + + if (qdict_get_int(cpu, "cpu-index") == 1) { + cpu1 = cpu; + } + } + + g_assert_nonnull(cpu1); + g_assert_cmpstr(qdict_get_str(cpu1, "qom-type"), ==, + "thead-c908v-riscv-cpu"); + g_assert_false(k230_qom_get_bool(qts, "/machine/soc/c908-cpu/harts[0]", + "start-powered-off")); + g_assert_false(k230_qom_get_bool(qts, "/machine/soc/c908v-cpu/harts[0]", + "start-powered-off")); + + qobject_unref(resp); + qtest_quit(qts); +} + +static void test_clint_smode_regs(void) +{ + QTestState *qts = qtest_init("-machine k230-canmv -smp 1"); + + qtest_writel(qts, K230_CLINT_BASE + K230_CLINT_SSIP0, 1); + g_assert_cmphex(qtest_readl(qts, K230_CLINT_BASE + K230_CLINT_SSIP0), ==, + 1); + + qtest_writel(qts, K230_CLINT_BASE + K230_CLINT_SSIP0, 0); + g_assert_cmphex(qtest_readl(qts, K230_CLINT_BASE + K230_CLINT_SSIP0), ==, + 0); + + qtest_writel(qts, K230_CLINT_BASE + K230_CLINT_STIMECMPL0, 0x11223344); + qtest_writel(qts, K230_CLINT_BASE + K230_CLINT_STIMECMPH0, 0x55667788); + g_assert_cmphex(qtest_readl(qts, K230_CLINT_BASE + K230_CLINT_STIMECMPL0), + ==, 0x11223344); + g_assert_cmphex(qtest_readl(qts, K230_CLINT_BASE + K230_CLINT_STIMECMPH0), + ==, 0x55667788); + + qtest_quit(qts); +} + +static uint32_t k230_i2c_read_reg8(QTestState *qts, uint64_t base, + uint16_t reg) +{ + qtest_writel(qts, base + K230_I2C_ENABLE, 1); + qtest_writel(qts, base + K230_I2C_TAR, 0x36); + qtest_writel(qts, base + K230_I2C_DATA_CMD, reg >> 8); + qtest_writel(qts, base + K230_I2C_DATA_CMD, reg & 0xff); + qtest_writel(qts, base + K230_I2C_DATA_CMD, + K230_I2C_CMD_READ | K230_I2C_CMD_RESTART | + K230_I2C_CMD_STOP); + + return qtest_readl(qts, base + K230_I2C_DATA_CMD); +} + +static void k230_plic_enable_irq(QTestState *qts, unsigned int irq) +{ + uint32_t enable; + uint64_t enable_addr; + + qtest_writel(qts, K230_PLIC_BASE + irq * 4, 1); + enable_addr = K230_PLIC_BASE + K230_PLIC_ENABLE_S + (irq / 32) * 4; + enable = qtest_readl(qts, enable_addr); + qtest_writel(qts, enable_addr, enable | (1u << (irq % 32))); + qtest_writel(qts, K230_PLIC_BASE + K230_PLIC_CONTEXT_S + + K230_PLIC_THRESHOLD, 0); +} + +static uint32_t k230_plic_claim(QTestState *qts) +{ + return qtest_readl(qts, K230_PLIC_BASE + K230_PLIC_CONTEXT_S + + K230_PLIC_CLAIM); +} + +static void k230_plic_complete(QTestState *qts, unsigned int irq) +{ + qtest_writel(qts, K230_PLIC_BASE + K230_PLIC_CONTEXT_S + + K230_PLIC_CLAIM, irq); +} + +static void test_ov5647_chip_id(void) +{ + QTestState *qts = qtest_init("-machine k230-canmv"); + + g_assert_cmphex(k230_i2c_read_reg8(qts, K230_I2C0_BASE, 0x300a), ==, 0x56); + g_assert_cmphex(k230_i2c_read_reg8(qts, K230_I2C0_BASE, 0x300b), ==, 0x47); + + qtest_quit(qts); +} + +static void test_flash_xip_writable(void) +{ + QTestState *qts = qtest_init("-machine k230-canmv"); + uint64_t addr = K230_FLASH_BASE + K230_FLASH_TEST_OFFSET; + + g_assert_cmphex(qtest_readl(qts, addr), ==, 0xffffffff); + + qtest_writel(qts, addr, 0x5a17c0de); + g_assert_cmphex(qtest_readl(qts, addr), ==, 0x5a17c0de); + + qtest_quit(qts); +} + +static void test_media_regs_readback(void) +{ + QTestState *qts = qtest_init("-machine k230-canmv"); + static const uint8_t nonai_y_src[16] = { + 0x10, 0x11, 0x12, 0x13, 0x20, 0x21, 0x22, 0x23, + 0x30, 0x31, 0x32, 0x33, 0x40, 0x41, 0x42, 0x43, + }; + static const uint8_t nonai_u_src[4] = { 0x80, 0x81, 0x90, 0x91 }; + static const uint8_t nonai_v_src[4] = { 0xa0, 0xa1, 0xb0, 0xb1 }; + uint8_t nonai_y_dst[sizeof(nonai_y_src)]; + uint8_t nonai_u_dst[sizeof(nonai_u_src)]; + uint8_t nonai_v_dst[sizeof(nonai_v_src)]; + + k230_plic_enable_irq(qts, K230_DMA_IRQ); + k230_plic_enable_irq(qts, K230_NON_AI_2D_IRQ); + k230_plic_enable_irq(qts, K230_FFT_IRQ); + k230_plic_enable_irq(qts, K230_AI2D_IRQ); + k230_plic_enable_irq(qts, K230_ISP_MI_IRQ); + k230_plic_enable_irq(qts, K230_ISP_FE_IRQ); + k230_plic_enable_irq(qts, K230_ISP_IRQ); + k230_plic_enable_irq(qts, K230_DWE_IRQ); + k230_plic_enable_irq(qts, K230_FE_IRQ); + k230_plic_enable_irq(qts, K230_DPU_IRQ); + k230_plic_enable_irq(qts, K230_VSE_IRQ); + + qtest_writel(qts, K230_KPU_CFG_BASE + 0x10, 0x00000001); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + g_assert_cmphex(qtest_readq(qts, K230_KPU_CFG_BASE + K230_GNNE_STATUS), + ==, 0); + + qtest_writel(qts, K230_GSDMA_BASE + K230_GSDMA_CH0_CTL, 1); + g_assert_cmphex(qtest_readl(qts, K230_GSDMA_BASE + + K230_GSDMA_INT_STAT), ==, + K230_GSDMA_DONE_CH0); + g_assert_cmphex(qtest_readl(qts, K230_GSDMA_BASE + + K230_GSDMA_CH0_STATUS), ==, 0); + g_assert_cmphex(k230_plic_claim(qts), ==, K230_DMA_IRQ); + qtest_writel(qts, K230_GSDMA_BASE + K230_GSDMA_INT_STAT, + K230_GSDMA_DONE_CH0); + k230_plic_complete(qts, K230_DMA_IRQ); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + + qtest_memwrite(qts, K230_NON_AI_2D_TEST_SRC_Y, nonai_y_src, + sizeof(nonai_y_src)); + qtest_memwrite(qts, K230_NON_AI_2D_TEST_SRC_U, nonai_u_src, + sizeof(nonai_u_src)); + qtest_memwrite(qts, K230_NON_AI_2D_TEST_SRC_V, nonai_v_src, + sizeof(nonai_v_src)); + qtest_memset(qts, K230_NON_AI_2D_TEST_DST_Y, 0, sizeof(nonai_y_dst)); + qtest_memset(qts, K230_NON_AI_2D_TEST_DST_U, 0, sizeof(nonai_u_dst)); + qtest_memset(qts, K230_NON_AI_2D_TEST_DST_V, 0, sizeof(nonai_v_dst)); + qtest_writel(qts, K230_NON_AI_2D_BASE + K230_NON_AI_2D_SRC_SIZE, + (4 << 16) | 4); + qtest_writel(qts, K230_NON_AI_2D_BASE + K230_NON_AI_2D_SRC_CH0_ADDR, + K230_NON_AI_2D_TEST_SRC_Y); + qtest_writel(qts, K230_NON_AI_2D_BASE + K230_NON_AI_2D_SRC_CH1_ADDR, + K230_NON_AI_2D_TEST_SRC_U); + qtest_writel(qts, K230_NON_AI_2D_BASE + K230_NON_AI_2D_SRC_CH2_ADDR, + K230_NON_AI_2D_TEST_SRC_V); + qtest_writel(qts, K230_NON_AI_2D_BASE + K230_NON_AI_2D_DST_CH0_ADDR, + K230_NON_AI_2D_TEST_DST_Y); + qtest_writel(qts, K230_NON_AI_2D_BASE + K230_NON_AI_2D_DST_CH1_ADDR, + K230_NON_AI_2D_TEST_DST_U); + qtest_writel(qts, K230_NON_AI_2D_BASE + K230_NON_AI_2D_DST_CH2_ADDR, + K230_NON_AI_2D_TEST_DST_V); + qtest_writel(qts, K230_NON_AI_2D_BASE + K230_NON_AI_2D_SRC_STRIDE01, + 4 | (2 << 16)); + qtest_writel(qts, K230_NON_AI_2D_BASE + K230_NON_AI_2D_DST_STRIDE01, + 4 | (2 << 16)); + qtest_writel(qts, K230_NON_AI_2D_BASE + K230_NON_AI_2D_SRC_STRIDE2, 2); + qtest_writel(qts, K230_NON_AI_2D_BASE + K230_NON_AI_2D_DST_STRIDE2, 2); + qtest_writel(qts, K230_NON_AI_2D_BASE + K230_NON_AI_2D_FMT, + K230_NON_AI_2D_FMT_I420 | + (K230_NON_AI_2D_FMT_I420 << 16)); + qtest_writel(qts, K230_NON_AI_2D_BASE + K230_NON_AI_2D_MAIN_CFG, + K230_NON_AI_2D_INTR_MASK | K230_NON_AI_2D_MODE_OSD); + g_assert_cmphex(qtest_readl(qts, K230_NON_AI_2D_BASE + + K230_NON_AI_2D_INTR_STATUS), ==, 0); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + qtest_writel(qts, K230_NON_AI_2D_BASE + K230_NON_AI_2D_MAIN_CFG, + K230_NON_AI_2D_INTR_MASK | K230_NON_AI_2D_MODE_OSD | + K230_NON_AI_2D_CALC_EN); + g_assert_cmphex(qtest_readl(qts, K230_NON_AI_2D_BASE + + K230_NON_AI_2D_INTR_STATUS), ==, 1); + g_assert_cmphex(qtest_readl(qts, K230_NON_AI_2D_BASE + + K230_NON_AI_2D_STOP_BUSY), ==, 0); + g_assert_cmphex(k230_plic_claim(qts), ==, K230_NON_AI_2D_IRQ); + qtest_memread(qts, K230_NON_AI_2D_TEST_DST_Y, nonai_y_dst, + sizeof(nonai_y_dst)); + qtest_memread(qts, K230_NON_AI_2D_TEST_DST_U, nonai_u_dst, + sizeof(nonai_u_dst)); + qtest_memread(qts, K230_NON_AI_2D_TEST_DST_V, nonai_v_dst, + sizeof(nonai_v_dst)); + g_assert_cmpmem(nonai_y_dst, sizeof(nonai_y_dst), + nonai_y_src, sizeof(nonai_y_src)); + g_assert_cmpmem(nonai_u_dst, sizeof(nonai_u_dst), + nonai_u_src, sizeof(nonai_u_src)); + g_assert_cmpmem(nonai_v_dst, sizeof(nonai_v_dst), + nonai_v_src, sizeof(nonai_v_src)); + qtest_writel(qts, K230_NON_AI_2D_BASE + K230_NON_AI_2D_INTR_CLEAR, 1); + k230_plic_complete(qts, K230_NON_AI_2D_IRQ); + g_assert_cmphex(qtest_readl(qts, K230_NON_AI_2D_BASE + + K230_NON_AI_2D_INTR_STATUS), ==, 0); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + + qtest_writeb(qts, K230_FFT_BASE + 0x10, 0xa5); + g_assert_cmphex(k230_plic_claim(qts), ==, K230_FFT_IRQ); + qtest_writeq(qts, K230_FFT_BASE + 0x20, 1); + k230_plic_complete(qts, K230_FFT_IRQ); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + + qtest_writel(qts, K230_AI_2D_BASE + K230_AI2D_CALC_ENABLE, 1); + g_assert_cmphex(k230_plic_claim(qts), ==, K230_AI2D_IRQ); + qtest_writel(qts, K230_AI_2D_BASE + K230_AI2D_CLEAR, 1); + k230_plic_complete(qts, K230_AI2D_IRQ); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + + qtest_writel(qts, K230_AI_2D_BASE + K230_AI2D_JOB, 1); + g_assert_cmphex(k230_plic_claim(qts), ==, K230_AI2D_IRQ); + qtest_writel(qts, K230_AI_2D_BASE + K230_AI2D_CLEAR, 1); + k230_plic_complete(qts, K230_AI2D_IRQ); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + + qtest_writel(qts, K230_AI_2D_BASE + K230_AI2D_LEGACY_START, 1); + g_assert_cmphex(k230_plic_claim(qts), ==, K230_AI2D_IRQ); + qtest_writel(qts, K230_AI_2D_BASE + K230_AI2D_CLEAR, 1); + k230_plic_complete(qts, K230_AI2D_IRQ); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + + qtest_writel(qts, K230_ISP_BASE + K230_ISP_FE_START, + K230_ISP_FE_START_CMD); + g_assert_cmphex(k230_plic_claim(qts), ==, K230_ISP_FE_IRQ); + g_assert_cmphex(qtest_readl(qts, K230_ISP_BASE + K230_ISP_FE_MI_STATUS), + ==, 1); + g_assert_cmphex(qtest_readl(qts, K230_ISP_BASE + K230_ISP_TOP_STATUS) & + K230_ISP_TOP_PENDING, ==, K230_ISP_TOP_PENDING); + qtest_writel(qts, K230_ISP_BASE + K230_ISP_FE_MI_CLEAR, 1); + qtest_writel(qts, K230_ISP_BASE + K230_ISP_TOP_STATUS, + K230_ISP_TOP_ACK); + k230_plic_complete(qts, K230_ISP_FE_IRQ); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + + qtest_writel(qts, K230_ISP_BASE + K230_ISP_MCM_CTRL, + K230_ISP_MCM_CH0_ENABLE | K230_ISP_MCM_CH1_ENABLE | + K230_ISP_MCM_CH2_ENABLE); + g_assert_cmphex(k230_plic_claim(qts), ==, K230_ISP_MI_IRQ); + g_assert_cmphex(qtest_readl(qts, K230_ISP_BASE + K230_ISP_MCM0_STATUS), + ==, K230_ISP_MCM_CH0_BUFFER | + K230_ISP_MCM_CH1_BUFFER | + K230_ISP_MCM_CH0_DONE | + K230_ISP_MCM_CH1_DONE); + g_assert_cmphex(qtest_readl(qts, K230_ISP_BASE + K230_ISP_MCM2_STATUS), + ==, K230_ISP_MCM_CH2_STATUS); + qtest_writel(qts, K230_ISP_BASE + K230_ISP_MCM0_CLEAR, + K230_ISP_MCM_CH0_DONE | K230_ISP_MCM_CH1_DONE); + g_assert_cmphex(qtest_readl(qts, K230_ISP_BASE + K230_ISP_MCM0_STATUS), + ==, 0); + g_assert_cmphex(qtest_readl(qts, K230_ISP_BASE + K230_ISP_MCM2_STATUS), + ==, K230_ISP_MCM_CH2_STATUS); + qtest_writel(qts, K230_ISP_BASE + K230_ISP_MCM2_CLEAR, + K230_ISP_MCM_CH2_STATUS); + g_assert_cmphex(qtest_readl(qts, K230_ISP_BASE + K230_ISP_MCM2_STATUS), + ==, 0); + k230_plic_complete(qts, K230_ISP_MI_IRQ); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + g_assert_cmphex(qtest_readl(qts, K230_ISP_BASE + K230_ISP_FE_MI_STATUS), + ==, 0); + qtest_clock_step(qts, K230_ISP_MCM_FRAME_NS + 1); + g_assert_cmphex(k230_plic_claim(qts), ==, K230_ISP_MI_IRQ); + g_assert_cmphex(qtest_readl(qts, K230_ISP_BASE + K230_ISP_MCM0_STATUS), + ==, K230_ISP_MCM_CH0_BUFFER | + K230_ISP_MCM_CH1_BUFFER | + K230_ISP_MCM_CH0_DONE | + K230_ISP_MCM_CH1_DONE); + g_assert_cmphex(qtest_readl(qts, K230_ISP_BASE + K230_ISP_MCM2_STATUS), + ==, K230_ISP_MCM_CH2_STATUS); + g_assert_cmphex(qtest_readl(qts, K230_ISP_BASE + K230_ISP_FE_MI_STATUS), + ==, K230_ISP_MI_FRAME_STATUS); + qtest_writel(qts, K230_ISP_BASE + K230_ISP_MCM0_CLEAR, + K230_ISP_MCM_CH0_DONE | K230_ISP_MCM_CH1_DONE); + qtest_writel(qts, K230_ISP_BASE + K230_ISP_MCM2_CLEAR, + K230_ISP_MCM_CH2_STATUS); + qtest_writel(qts, K230_ISP_BASE + K230_ISP_FE_MI_CLEAR, + K230_ISP_MI_FRAME_STATUS); + k230_plic_complete(qts, K230_ISP_MI_IRQ); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + qtest_writel(qts, K230_ISP_BASE + K230_ISP_MCM_CTRL, 0); + + qtest_writel(qts, K230_ISP_BASE + K230_ISP_MAIN_STATUS, 0x20); + g_assert_cmphex(k230_plic_claim(qts), ==, K230_ISP_IRQ); + qtest_writel(qts, K230_ISP_BASE + K230_ISP_MAIN_CLEAR, 0x20); + k230_plic_complete(qts, K230_ISP_IRQ); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + + qtest_writel(qts, K230_DEWARP_BASE + K230_FE_START, + K230_FE_START_CMD); + g_assert_cmphex(k230_plic_claim(qts), ==, K230_FE_IRQ); + g_assert_cmphex(qtest_readl(qts, K230_DEWARP_BASE + + K230_FE_IRQ_STATUS), ==, 1); + qtest_writel(qts, K230_DEWARP_BASE + K230_FE_IRQ_CLEAR, 1); + k230_plic_complete(qts, K230_FE_IRQ); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + + qtest_writel(qts, K230_DEWARP_BASE + K230_DWE_DMA_START0, 1); + qtest_writel(qts, K230_DEWARP_BASE + K230_DWE_DMA_START1, 1); + qtest_writel(qts, K230_DEWARP_BASE + K230_DWE_BUS_CTRL_LOW, + K230_DWE_BUS_ENABLE); + g_assert_cmphex(qtest_readl(qts, K230_DEWARP_BASE + + K230_DWE_BUS_CTRL_LOW) & + K230_DWE_BUS_ENABLE, ==, K230_DWE_BUS_ENABLE); + qtest_writel(qts, K230_DEWARP_BASE + K230_DWE_CTRL_LOW, + K230_DWE_START); + g_assert_cmphex(k230_plic_claim(qts), ==, K230_DWE_IRQ); + g_assert_cmphex(qtest_readl(qts, K230_DEWARP_BASE + + K230_DWE_IRQ_STATUS_LOW), ==, 1); + qtest_writel(qts, K230_DEWARP_BASE + K230_DWE_IRQ_STATUS_LOW, 1); + k230_plic_complete(qts, K230_DWE_IRQ); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + + qtest_writel(qts, K230_DEWARP_BASE + K230_DWE_CTRL, + K230_DWE_START); + g_assert_cmphex(k230_plic_claim(qts), ==, K230_DWE_IRQ); + g_assert_cmphex(qtest_readl(qts, K230_DEWARP_BASE + + K230_DWE_IRQ_STATUS), ==, 1); + qtest_writel(qts, K230_DEWARP_BASE + K230_DWE_IRQ_CLEAR, 0x70); + k230_plic_complete(qts, K230_DWE_IRQ); + g_assert_cmphex(k230_plic_claim(qts), ==, K230_VSE_IRQ); + g_assert_cmphex(qtest_readl(qts, K230_DEWARP_BASE + + K230_VSE_IRQ_STATUS), ==, 0x7); + qtest_writel(qts, K230_DEWARP_BASE + K230_VSE_IRQ_CLEAR, 0x7); + k230_plic_complete(qts, K230_VSE_IRQ); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + + qtest_writel(qts, K230_DPU_BASE + K230_DPU_IRQ_CLEAR, + K230_DPU_CLEAR_ACK); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + qtest_writel(qts, K230_DPU_BASE + K230_DPU_START, 1); + g_assert_cmphex(qtest_readl(qts, K230_DPU_BASE + K230_DPU_STATUS), ==, + K230_DPU_DONE); + g_assert_cmphex(k230_plic_claim(qts), ==, K230_DPU_IRQ); + qtest_writel(qts, K230_DPU_BASE + K230_DPU_IRQ_CLEAR, + K230_DPU_CLEAR_ACK); + k230_plic_complete(qts, K230_DPU_IRQ); + g_assert_cmphex(qtest_readl(qts, K230_DPU_BASE + K230_DPU_STATUS), ==, 0); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + qtest_writel(qts, K230_DPU_BASE + K230_DPU_START_CH1, 1); + g_assert_cmphex(qtest_readl(qts, K230_DPU_BASE + K230_DPU_STATUS), ==, + K230_DPU_DONE); + g_assert_cmphex(k230_plic_claim(qts), ==, K230_DPU_IRQ); + qtest_writel(qts, K230_DPU_BASE + K230_DPU_IRQ_CLEAR, + K230_DPU_CLEAR_ACK); + k230_plic_complete(qts, K230_DPU_IRQ); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + + g_assert_cmphex(qtest_readl(qts, K230_RX_CSI_BASE + + K230_RX_CSI_HOST2_PHY_STATE) & + K230_RX_CSI_PHY_STOPSTATE, ==, + K230_RX_CSI_PHY_STOPSTATE); + qtest_writel(qts, K230_RX_CSI_BASE + K230_RX_CSI_HOST2_ENABLE, 1); + g_assert_cmphex(qtest_readl(qts, K230_RX_CSI_BASE + + K230_RX_CSI_HOST2_PHY_STATE) & + K230_RX_CSI_PHY_STOPSTATE, ==, + K230_RX_CSI_PHY_STOPSTATE); + qtest_writel(qts, K230_RX_CSI_BASE + K230_RX_CSI_DIS_FRAME_M, 1); + qtest_writel(qts, K230_RX_CSI_BASE + K230_RX_CSI_DIS_FRAME_N, 3); + qtest_writel(qts, K230_RX_CSI_BASE + K230_RX_CSI_DIS_FRAME_EN, 4); + qtest_writel(qts, K230_RX_CSI_BASE + K230_RX_CSI_PHY_CTRL0, + 0x00001234); + qtest_writel(qts, K230_RX_CSI_BASE + K230_RX_CSI_PHY_DATA1, + 0x00005678); + qtest_writel(qts, K230_RX_CSI_BASE + K230_RX_CSI_PHY_CTRL1, + 0x00009abc); + g_assert_cmphex(qtest_readl(qts, K230_RX_CSI_BASE + + K230_RX_CSI_DIS_FRAME_M), ==, 1); + g_assert_cmphex(qtest_readl(qts, K230_RX_CSI_BASE + + K230_RX_CSI_DIS_FRAME_N), ==, 3); + g_assert_cmphex(qtest_readl(qts, K230_RX_CSI_BASE + + K230_RX_CSI_DIS_FRAME_EN), ==, 4); + g_assert_cmphex(qtest_readl(qts, K230_RX_CSI_BASE + + K230_RX_CSI_PHY_DATA0), ==, 0x00001234); + g_assert_cmphex(qtest_readl(qts, K230_RX_CSI_BASE + + K230_RX_CSI_PHY_DATA1), ==, 0x00005678); + + qtest_writel(qts, K230_NON_AI_2D_BASE + 0x08, 0x5a5a5a5a); + qtest_writel(qts, K230_ISP_BASE + 0x05bc, 0x0780002f); + qtest_writel(qts, K230_ISP_BASE + 0x5714, 0x00000001); + qtest_writel(qts, K230_RX_CSI_BASE + 0x0880, 0x01000100); + qtest_writel(qts, K230_UART3_BASE + 0xc0, 0x00000002); + + g_assert_cmphex(qtest_readb(qts, K230_FFT_BASE + 0x10), ==, 0xa5); + g_assert_cmphex(qtest_readl(qts, K230_AI_2D_BASE + + K230_AI2D_LEGACY_START), ==, + 0x00000001); + g_assert_cmphex(qtest_readl(qts, K230_NON_AI_2D_BASE + 0x08), ==, + 0x5a5a5a5a); + g_assert_cmphex(qtest_readl(qts, K230_ISP_BASE + 0x05bc), ==, + 0x0780002f); + g_assert_cmphex(qtest_readl(qts, K230_ISP_BASE + 0x5714), ==, + 0x00000001); + g_assert_cmphex(qtest_readl(qts, K230_RX_CSI_BASE + 0x0880), ==, + 0x01000100); + g_assert_cmphex(qtest_readl(qts, K230_UART3_BASE + 0xc0), ==, + 0x00000002); + + qtest_quit(qts); +} + +int main(int argc, char *argv[]) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/k230-sysctl/cpu1-reset-sequence-smp1", + test_cpu1_reset_sequence_smp1); + qtest_add_func("/k230-sysctl/cpu1-reset-sequence-smp2", + test_cpu1_reset_sequence_smp2); + qtest_add_func("/k230-sysctl/smp1-topology", test_smp1_topology); + qtest_add_func("/k230-sysctl/smp2-topology", test_smp2_topology); + qtest_add_func("/k230-sysctl/smp2-boot-both-cores-topology", + test_smp2_boot_both_cores_topology); + qtest_add_func("/k230-sysctl/clint-smode-regs", test_clint_smode_regs); + qtest_add_func("/k230-sysctl/ov5647-chip-id", test_ov5647_chip_id); + qtest_add_func("/k230-sysctl/flash-xip-writable", + test_flash_xip_writable); + qtest_add_func("/k230-sysctl/media-regs-readback", + test_media_regs_readback); + + return g_test_run(); +} diff --git a/tests/qtest/k230-wdt-test.c b/tests/qtest/k230-wdt-test.c index c8eaeaf1ae327..acb4ac594e86d 100644 --- a/tests/qtest/k230-wdt-test.c +++ b/tests/qtest/k230-wdt-test.c @@ -28,7 +28,7 @@ static void test_register_read_write(void) { - QTestState *qts = qtest_init("-machine k230"); + QTestState *qts = qtest_init("-machine k230-canmv"); /* Test Control Register (CR) read/write */ qtest_writel(qts, WDT_BASE + K230_WDT_CR, 0xFFFFFFFF); @@ -50,7 +50,7 @@ static void test_register_read_write(void) static void test_counter_restart(void) { - QTestState *qts = qtest_init("-machine k230"); + QTestState *qts = qtest_init("-machine k230-canmv"); /* Enable watchdog and set timeout */ qtest_writel(qts, WDT_BASE + K230_WDT_CR, K230_WDT_CR_WDT_EN); @@ -76,7 +76,7 @@ static void test_counter_restart(void) static void test_interrupt_mode(void) { - QTestState *qts = qtest_init("-machine k230 --trace k230_*,file=k230.log"); + QTestState *qts = qtest_init("-machine k230-canmv --trace k230_*,file=k230.log"); /* Set interrupt mode and enable watchdog */ qtest_writel(qts, WDT_BASE + K230_WDT_CR, @@ -100,7 +100,7 @@ static void test_interrupt_mode(void) static void test_reset_mode(void) { - QTestState *qts = qtest_init("-machine k230 -no-reboot"); + QTestState *qts = qtest_init("-machine k230-canmv -no-reboot"); /* Set reset mode and enable watchdog */ qtest_writel(qts, WDT_BASE + K230_WDT_CR, K230_WDT_CR_WDT_EN); @@ -117,7 +117,7 @@ static void test_reset_mode(void) static void test_timeout_calculation(void) { - QTestState *qts = qtest_init("-machine k230"); + QTestState *qts = qtest_init("-machine k230-canmv"); /* Test different timeout values */ for (uint32_t top = 0; top <= 15; top++) { @@ -137,7 +137,7 @@ static void test_timeout_calculation(void) static void test_wdt1_registers(void) { - QTestState *qts = qtest_init("-machine k230"); + QTestState *qts = qtest_init("-machine k230-canmv"); /* Test WDT1 registers (second watchdog) */ qtest_writel(qts, K230_WDT1_BASE + K230_WDT_CR, 0xFFFFFFFF); @@ -154,7 +154,7 @@ static void test_wdt1_registers(void) static void test_enable_disable(void) { - QTestState *qts = qtest_init("-machine k230"); + QTestState *qts = qtest_init("-machine k230-canmv"); /* Initially disabled */ uint32_t cr = qtest_readl(qts, WDT_BASE + K230_WDT_CR); diff --git a/tests/qtest/k230-ws2812-amp-test.c b/tests/qtest/k230-ws2812-amp-test.c new file mode 100644 index 0000000000000..3d227d6b362f5 --- /dev/null +++ b/tests/qtest/k230-ws2812-amp-test.c @@ -0,0 +1,522 @@ +/* + * QTest testcase for K230 I2S/WS2812 and AMP flows + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include +#include "qemu/bswap.h" +#include "libqtest.h" +#include "migration/migration-qmp.h" +#include "qobject/qdict.h" +#include "qobject/qlist.h" + +#define K230_I2S_BASE 0x9140f000 +#define K230_PDMA_BASE 0x80804000 +#define K230_BOOT_BASE 0x91102000 +#define K230_RMU_BASE 0x91101000 +#define K230_PLIC_BASE 0xf00000000ULL + +#define K230_I2S_IER 0x000 +#define K230_I2S_ITER 0x008 +#define K230_I2S_CER 0x00c +#define K230_I2S_CCR 0x010 +#define K230_I2S_TXFFR 0x018 +#define K230_I2S_CH0_TER 0x02c +#define K230_I2S_CH0_TCR 0x034 +#define K230_I2S_CH0_IMR 0x03c +#define K230_I2S_CH0_TFCR 0x04c +#define K230_I2S_CH0_TFF 0x054 +#define K230_I2S_TXDMA 0x1c8 +#define K230_I2S_AUDIO_IN_CTRL 0x400 +#define K230_I2S_AUDIO_OUT_CTRL 0xc00 +#define K230_I2S_COMP_PARAM_2 0x1f0 +#define K230_I2S_COMP_PARAM_1 0x1f4 +#define K230_I2S_COMP_VERSION 0x1f8 +#define K230_I2S_COMP_TYPE 0x1fc +#define K230_I2S_CCR_DMA_ENABLE (1u << 8) + +#define K230_PDMA_CH_EN 0x000 +#define K230_PDMA_INT_STAT 0x008 +#define K230_PDMA_CH0_CTL 0x020 +#define K230_PDMA_CH0_CFG 0x028 +#define K230_PDMA_CH0_LLT_SADDR 0x02c +#define K230_PDMA_DEV_SEL0 0x120 +#define K230_PDMA_CH0_START 1 +#define K230_PDMA_CH0_DONE 1 +#define K230_PDMA_HSIZE_4 (2u << 1) +#define K230_PDMA_AUDIO_TX 20 +#define K230_PDMA_IRQ 203 + +#define K230_CPU1_RST_CTL 0x00c +#define K230_CPU1_HART_RSTVEC 0x104 +#define K230_CPU1_RST_REQ (1u << 0) +#define K230_CPU1_RST_DONE (1u << 12) +#define K230_CPU1_PRST_DONE (1u << 13) +#define K230_CPU1_RST_REQ_WEN (1u << 16) +#define K230_CPU1_RST_DONE_WEN (1u << 28) +#define K230_CPU1_RST_CTL_RESET \ + (K230_CPU1_PRST_DONE | K230_CPU1_RST_REQ) + +#define K230_PLIC_ENABLE_S 0x2080 +#define K230_PLIC_CONTEXT_S 0x201000 +#define K230_PLIC_THRESHOLD 0x00 +#define K230_PLIC_CLAIM 0x04 + +#define K230_TEST_DATA_ADDR 0x01000000 +#define K230_TEST_DESC_ADDR 0x01000100 +#define K230_CPU1_TEST_RESETVEC 0x00200000 +#define K230_CPU1_ALT_RESETVEC 0x00400000 +#define K230_CPU1_TEST_PATTERN_SIZE 16 +#define K230_CPU1_DEFER_NS 16000000000LL +#define K230_CPU1_TEST_MARKER 0x00201000 + +static void write_le32(uint8_t *buf, size_t offset, uint32_t value); + + +static uint32_t k230_qom_get_u32(QTestState *qts, const char *path, + const char *property) +{ + QDict *resp; + uint32_t value; + + resp = qtest_qmp(qts, "{ 'execute': 'qom-get', 'arguments': " + "{ 'path': %s, 'property': %s } }", + path, property); + g_assert(qdict_haskey(resp, "return")); + value = qdict_get_int(resp, "return"); + qobject_unref(resp); + + return value; +} + +static QDict *k230_query_cpu(QTestState *qts, int cpu_index) +{ + QDict *resp; + QList *cpus; + QListEntry *entry; + + resp = qtest_qmp(qts, "{ 'execute': 'query-cpus-fast' }"); + g_assert(qdict_haskey(resp, "return")); + cpus = qdict_get_qlist(resp, "return"); + + QLIST_FOREACH_ENTRY(cpus, entry) { + QDict *cpu = qobject_to(QDict, entry->value); + + if (qdict_get_int(cpu, "cpu-index") == cpu_index) { + return resp; + } + } + + qobject_unref(resp); + return NULL; +} + +static void k230_plic_enable_irq(QTestState *qts, unsigned int irq) +{ + uint32_t enable; + uint64_t enable_addr; + + qtest_writel(qts, K230_PLIC_BASE + irq * 4, 1); + enable_addr = K230_PLIC_BASE + K230_PLIC_ENABLE_S + (irq / 32) * 4; + enable = qtest_readl(qts, enable_addr); + qtest_writel(qts, enable_addr, enable | (1u << (irq % 32))); + qtest_writel(qts, K230_PLIC_BASE + K230_PLIC_CONTEXT_S + + K230_PLIC_THRESHOLD, 0); +} + +static uint32_t k230_plic_claim(QTestState *qts) +{ + return qtest_readl(qts, K230_PLIC_BASE + K230_PLIC_CONTEXT_S + + K230_PLIC_CLAIM); +} + +static void k230_plic_complete(QTestState *qts, unsigned int irq) +{ + qtest_writel(qts, K230_PLIC_BASE + K230_PLIC_CONTEXT_S + + K230_PLIC_CLAIM, irq); +} + +static uint32_t ws2812_encode_byte(uint8_t color) +{ + uint32_t ret = 0; + + for (int i = 3; i >= 0; i--) { + uint8_t symbol; + + switch ((color >> (i * 2)) & 3) { + case 0: + symbol = 0x88; + break; + case 1: + symbol = 0x8e; + break; + case 2: + symbol = 0xe8; + break; + case 3: + symbol = 0xee; + break; + default: + g_assert_not_reached(); + } + ret |= (uint32_t)symbol << (i * 8); + } + + return ret; +} + +static uint32_t k230_ws2812_prop(QTestState *qts, const char *property) +{ + return k230_qom_get_u32(qts, "/machine/soc/k230-i2s", property); +} + +static void k230_assert_cpu1_reset(QTestState *qts) +{ + qtest_writel(qts, K230_RMU_BASE + K230_CPU1_RST_CTL, + K230_CPU1_RST_DONE_WEN | K230_CPU1_RST_DONE); + qtest_writel(qts, K230_RMU_BASE + K230_CPU1_RST_CTL, + K230_CPU1_RST_REQ_WEN | K230_CPU1_RST_REQ); + g_assert_cmphex(qtest_readl(qts, K230_RMU_BASE + K230_CPU1_RST_CTL), ==, + K230_CPU1_RST_CTL_RESET); +} + +static void k230_release_cpu1_reset(QTestState *qts) +{ + qtest_writel(qts, K230_RMU_BASE + K230_CPU1_RST_CTL, + K230_CPU1_RST_REQ_WEN); +} + +static void k230_write_pattern(QTestState *qts, uint64_t addr, + const uint8_t *pattern, size_t size) +{ + qtest_memwrite(qts, addr, pattern, size); +} + +static void k230_read_pattern(QTestState *qts, uint64_t addr, + uint8_t *buf, size_t size) +{ + qtest_memread(qts, addr, buf, size); +} + +static void k230_expect_pattern(QTestState *qts, uint64_t addr, + const uint8_t *pattern, size_t size) +{ + uint8_t buf[K230_CPU1_TEST_PATTERN_SIZE]; + + g_assert_cmpuint(size, <=, sizeof(buf)); + k230_read_pattern(qts, addr, buf, size); + g_assert_cmpmem(buf, size, pattern, size); +} + +static void k230_expect_zeroed(QTestState *qts, uint64_t addr, size_t size) +{ + uint8_t buf[K230_CPU1_TEST_PATTERN_SIZE]; + uint8_t zeros[K230_CPU1_TEST_PATTERN_SIZE] = { 0 }; + + g_assert_cmpuint(size, <=, sizeof(buf)); + k230_read_pattern(qts, addr, buf, size); + g_assert_cmpmem(buf, size, zeros, size); +} + + + +static void k230_enable_ws2812_tx(QTestState *qts) +{ + qtest_writel(qts, K230_I2S_BASE + K230_I2S_IER, 1); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_ITER, 1); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_CER, 1); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_CH0_IMR, 0x33); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_CH0_TER, 1); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_CH0_TCR, 5); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_CH0_TFCR, 3); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_CCR, + K230_I2S_CCR_DMA_ENABLE | (2 << 3) | (1 << 5)); +} + +static void test_i2s_init_registers(void) +{ + QTestState *qts = qtest_init("-machine k230-canmv"); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_IER), ==, 0); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_ITER), ==, 0); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_CER), ==, 0); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_CCR), ==, 0); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_CH0_TER), ==, + 0); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_COMP_PARAM_2), + ==, 0); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_COMP_PARAM_1), + ==, 0); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_COMP_VERSION), + ==, 0); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_COMP_TYPE), + ==, 0); + + qtest_writeb(qts, K230_I2S_BASE + K230_I2S_IER, 1); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_IER), ==, 0); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_IER, 0xffffffff); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_IER), ==, 1); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_ITER, 0xffffffff); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_ITER), ==, 1); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_CER, 0xffffffff); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_CER), ==, 1); + + k230_enable_ws2812_tx(qts); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_AUDIO_IN_CTRL, 1 << 5); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_AUDIO_OUT_CTRL, + (2 << 5) | 1); + + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_IER), ==, 1); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_ITER), ==, 1); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_CH0_TER), ==, + 1); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_CH0_TCR), ==, + 5); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_CH0_TFCR), ==, + 3); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_AUDIO_IN_CTRL), + ==, 1 << 5); + g_assert_cmphex(qtest_readl(qts, K230_I2S_BASE + K230_I2S_AUDIO_OUT_CTRL), + ==, (2 << 5) | 1); + + qtest_writel(qts, K230_I2S_BASE + K230_I2S_TXFFR, 1); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_CH0_TFF, 1); + g_assert_cmpuint(k230_ws2812_prop(qts, "ws2812-byte-count"), ==, 0); + + qtest_quit(qts); +} + +static void test_direct_tx_encoded_words(void) +{ + QTestState *qts = qtest_init("-machine k230-canmv"); + + k230_enable_ws2812_tx(qts); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_TXFFR, 1); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_CCR, 0); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_TXDMA, + ws2812_encode_byte(0x00)); + g_assert_cmpuint(k230_ws2812_prop(qts, "ws2812-byte-count"), ==, 0); + g_assert_cmpuint(k230_ws2812_prop(qts, "ws2812-invalid-count"), ==, 0); + + qtest_writel(qts, K230_I2S_BASE + K230_I2S_CCR, + K230_I2S_CCR_DMA_ENABLE | (2 << 3) | (1 << 5)); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_TXDMA, + ws2812_encode_byte(0x00)); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_TXDMA, + ws2812_encode_byte(0xff)); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_TXDMA, 0); + + g_assert_cmpuint(k230_ws2812_prop(qts, "ws2812-byte-count"), ==, 2); + g_assert_cmpuint(k230_ws2812_prop(qts, "ws2812-byte0"), ==, 0x00); + g_assert_cmpuint(k230_ws2812_prop(qts, "ws2812-byte1"), ==, 0xff); + g_assert_cmpuint(k230_ws2812_prop(qts, "ws2812-padding-count"), ==, 1); + g_assert_cmpuint(k230_ws2812_prop(qts, "ws2812-invalid-count"), ==, 0); + + qtest_quit(qts); +} + +static void write_le32(uint8_t *buf, size_t offset, uint32_t value) +{ + stl_le_p(buf + offset, value); +} + +static void test_pdma_memory_to_i2s(void) +{ + QTestState *qts = qtest_init("-machine k230-canmv"); + uint8_t data[16]; + uint8_t desc[16]; + + k230_plic_enable_irq(qts, K230_PDMA_IRQ); + k230_enable_ws2812_tx(qts); + qtest_writel(qts, K230_I2S_BASE + K230_I2S_TXFFR, 1); + + write_le32(data, 0, ws2812_encode_byte(0x00)); + write_le32(data, 4, ws2812_encode_byte(0xff)); + write_le32(data, 8, ws2812_encode_byte(0x5a)); + write_le32(data, 12, 0); + qtest_memwrite(qts, K230_TEST_DATA_ADDR, data, sizeof(data)); + + write_le32(desc, 0, sizeof(data)); + write_le32(desc, 4, K230_TEST_DATA_ADDR); + write_le32(desc, 8, K230_I2S_BASE + K230_I2S_TXDMA); + write_le32(desc, 12, 0); + qtest_memwrite(qts, K230_TEST_DESC_ADDR, desc, sizeof(desc)); + + qtest_writel(qts, K230_PDMA_BASE + K230_PDMA_CH0_CFG, + K230_PDMA_HSIZE_4); + qtest_writel(qts, K230_PDMA_BASE + K230_PDMA_DEV_SEL0, + K230_PDMA_AUDIO_TX); + qtest_writel(qts, K230_PDMA_BASE + K230_PDMA_CH0_LLT_SADDR, + K230_TEST_DESC_ADDR); + qtest_writel(qts, K230_PDMA_BASE + K230_PDMA_CH_EN, 1); + qtest_writel(qts, K230_PDMA_BASE + K230_PDMA_CH0_CTL, + K230_PDMA_CH0_START); + + g_assert_cmphex(qtest_readl(qts, K230_PDMA_BASE + K230_PDMA_INT_STAT), + ==, K230_PDMA_CH0_DONE); + g_assert_cmphex(k230_plic_claim(qts), ==, K230_PDMA_IRQ); + g_assert_cmpuint(k230_ws2812_prop(qts, "ws2812-byte-count"), ==, 3); + g_assert_cmpuint(k230_ws2812_prop(qts, "ws2812-byte0"), ==, 0x00); + g_assert_cmpuint(k230_ws2812_prop(qts, "ws2812-byte1"), ==, 0xff); + g_assert_cmpuint(k230_ws2812_prop(qts, "ws2812-byte2"), ==, 0x5a); + g_assert_cmpuint(k230_ws2812_prop(qts, "ws2812-padding-count"), ==, 1); + + qtest_writel(qts, K230_PDMA_BASE + K230_PDMA_INT_STAT, + K230_PDMA_CH0_DONE); + k230_plic_complete(qts, K230_PDMA_IRQ); + g_assert_cmphex(qtest_readl(qts, K230_PDMA_BASE + K230_PDMA_INT_STAT), + ==, 0); + g_assert_cmphex(k230_plic_claim(qts), ==, 0); + + qtest_quit(qts); +} + +static void test_amp_exact_reset_sequence(void) +{ + QTestState *qts = qtest_init("-machine k230-canmv -smp 2"); + QDict *cpu1; + + cpu1 = k230_query_cpu(qts, 1); + g_assert_nonnull(cpu1); + qobject_unref(cpu1); + + g_assert_cmphex(qtest_readl(qts, K230_RMU_BASE + K230_CPU1_RST_CTL), ==, + K230_CPU1_RST_CTL_RESET); + qtest_writel(qts, K230_RMU_BASE + K230_CPU1_RST_CTL, + K230_CPU1_RST_DONE_WEN | K230_CPU1_RST_DONE); + qtest_writel(qts, K230_RMU_BASE + K230_CPU1_RST_CTL, + K230_CPU1_RST_REQ_WEN | K230_CPU1_RST_REQ); + g_assert_cmphex(qtest_readl(qts, K230_RMU_BASE + K230_CPU1_RST_CTL), ==, + K230_CPU1_RST_CTL_RESET); + + qtest_writel(qts, K230_CPU1_TEST_MARKER, 0xdeadbeef); + g_assert_cmphex(qtest_readl(qts, K230_CPU1_TEST_MARKER), ==, + 0xdeadbeef); + qtest_writel(qts, K230_BOOT_BASE + K230_CPU1_HART_RSTVEC, + K230_CPU1_TEST_RESETVEC); + qtest_writel(qts, K230_RMU_BASE + K230_CPU1_RST_CTL, + K230_CPU1_RST_REQ_WEN); + g_assert_cmphex(qtest_readl(qts, K230_RMU_BASE + K230_CPU1_RST_CTL), ==, + K230_CPU1_PRST_DONE | K230_CPU1_RST_DONE); + g_assert_cmphex(k230_qom_get_u32(qts, "/machine/soc/k230-sysctl-reset", + "last-cpu1-rstvec"), ==, + K230_CPU1_TEST_RESETVEC); + qtest_quit(qts); +} + +static void test_amp_deferred_reset_reassert_restores_rtt(void) +{ + QTestState *qts = qtest_init("-machine k230-canmv,boot-both-cores=on -smp 2"); + static const uint8_t pattern[K230_CPU1_TEST_PATTERN_SIZE] = { + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xf0, 0x0f, + }; + + k230_assert_cpu1_reset(qts); + k230_write_pattern(qts, K230_CPU1_TEST_RESETVEC, pattern, sizeof(pattern)); + qtest_writel(qts, K230_BOOT_BASE + K230_CPU1_HART_RSTVEC, + K230_CPU1_TEST_RESETVEC); + k230_release_cpu1_reset(qts); + k230_expect_zeroed(qts, K230_CPU1_TEST_RESETVEC, sizeof(pattern)); + + k230_assert_cpu1_reset(qts); + k230_expect_pattern(qts, K230_CPU1_TEST_RESETVEC, pattern, + sizeof(pattern)); + qtest_clock_step(qts, K230_CPU1_DEFER_NS); + k230_expect_pattern(qts, K230_CPU1_TEST_RESETVEC, pattern, + sizeof(pattern)); + + qtest_quit(qts); +} + +static void test_amp_deferred_release_snapshots_rstvec(void) +{ + QTestState *qts = qtest_init("-machine k230-canmv,boot-both-cores=on -smp 2"); + static const uint8_t pattern[K230_CPU1_TEST_PATTERN_SIZE] = { + 0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf6, 0x07, 0x18, + 0x29, 0x3a, 0x4b, 0x5c, 0x6d, 0x7e, 0x8f, 0x90, + }; + + k230_assert_cpu1_reset(qts); + k230_write_pattern(qts, K230_CPU1_TEST_RESETVEC, pattern, sizeof(pattern)); + qtest_writel(qts, K230_BOOT_BASE + K230_CPU1_HART_RSTVEC, + K230_CPU1_TEST_RESETVEC); + k230_release_cpu1_reset(qts); + k230_expect_zeroed(qts, K230_CPU1_TEST_RESETVEC, sizeof(pattern)); + + qtest_writel(qts, K230_BOOT_BASE + K230_CPU1_HART_RSTVEC, + K230_CPU1_ALT_RESETVEC); + qtest_clock_step(qts, K230_CPU1_DEFER_NS); + k230_expect_pattern(qts, K230_CPU1_TEST_RESETVEC, pattern, + sizeof(pattern)); + g_assert_cmphex(k230_qom_get_u32(qts, "/machine/soc/k230-sysctl-reset", + "last-cpu1-rstvec"), ==, + K230_CPU1_TEST_RESETVEC); + + qtest_quit(qts); +} + +static void test_amp_deferred_release_migration(void) +{ + QTestState *src; + QTestState *dst; + g_autofree char *tmpdir = g_dir_make_tmp("k230-mig-XXXXXX", NULL); + g_autofree char *sock = g_strdup_printf("%s/migsock", tmpdir); + g_autofree char *uri = g_strdup_printf("unix:%s", sock); + static const uint8_t pattern[K230_CPU1_TEST_PATTERN_SIZE] = { + 0xde, 0xad, 0xbe, 0xef, 0x12, 0x34, 0x56, 0x78, + 0x87, 0x65, 0x43, 0x21, 0xca, 0xfe, 0xba, 0xbe, + }; + + g_assert_nonnull(tmpdir); + src = qtest_init("-machine k230-canmv,boot-both-cores=on -smp 2 -nic none"); + dst = qtest_init("-machine k230-canmv,boot-both-cores=on -smp 2 " + "-incoming defer -nic none"); + + k230_assert_cpu1_reset(src); + k230_write_pattern(src, K230_CPU1_TEST_RESETVEC, pattern, sizeof(pattern)); + qtest_writel(src, K230_BOOT_BASE + K230_CPU1_HART_RSTVEC, + K230_CPU1_TEST_RESETVEC); + k230_release_cpu1_reset(src); + k230_expect_zeroed(src, K230_CPU1_TEST_RESETVEC, sizeof(pattern)); + + migrate_incoming_qmp(dst, uri, NULL, "{}"); + migrate_qmp(src, dst, uri, NULL, "{}"); + wait_for_migration_complete(src); + qtest_qmp_eventwait(dst, "RESUME"); + + k230_expect_zeroed(dst, K230_CPU1_TEST_RESETVEC, sizeof(pattern)); + qtest_clock_step(dst, K230_CPU1_DEFER_NS); + k230_expect_pattern(dst, K230_CPU1_TEST_RESETVEC, pattern, + sizeof(pattern)); + + qtest_quit(src); + qtest_quit(dst); + g_unlink(sock); + g_rmdir(tmpdir); +} + +int main(int argc, char *argv[]) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/k230-ws2812/i2s-init-registers", + test_i2s_init_registers); + qtest_add_func("/k230-ws2812/direct-tx-encoded-words", + test_direct_tx_encoded_words); + qtest_add_func("/k230-ws2812/pdma-memory-to-i2s", + test_pdma_memory_to_i2s); + qtest_add_func("/k230-amp/deferred-reset-reassert-restores-rtt", + test_amp_deferred_reset_reassert_restores_rtt); + qtest_add_func("/k230-amp/deferred-release-snapshots-rstvec", + test_amp_deferred_release_snapshots_rstvec); + qtest_add_func("/k230-amp/deferred-release-migration", + test_amp_deferred_release_migration); + qtest_add_func("/k230-amp/exact-reset-sequence", + test_amp_exact_reset_sequence); + + return g_test_run(); +} diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 56ff860e216c8..0cb890bab848e 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -252,6 +252,7 @@ qtests_arm = \ (config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \ (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \ (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') ? qtests_stm32l4x5 : []) + \ + (config_all_devices.has_key('CONFIG_S32K5') ? ['s32k5-test'] : []) + \ (config_all_devices.has_key('CONFIG_FSI_APB2OPB_ASPEED') ? ['aspeed_fsi-test'] : []) + \ (config_all_devices.has_key('CONFIG_CAN_FLEXCAN') ? ['flexcan-test'] : []) + \ (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') and @@ -262,6 +263,7 @@ qtests_arm = \ # TODO: once aarch64 TCG is fixed on ARM 32 bit host, make bios-tables-test unconditional qtests_aarch64 = \ (cpu != 'arm' and unpack_edk2_blobs ? ['bios-tables-test'] : []) + \ + (config_all_devices.has_key('CONFIG_AX650X_PYRAMID') ? ['ax650x-pyramid-test'] : []) + \ (config_all_accel.has_key('CONFIG_TCG') and config_all_devices.has_key('CONFIG_TPM_TIS_SYSBUS') ? \ ['tpm-tis-device-test', 'tpm-tis-device-swtpm-test'] : []) + \ (config_all_devices.has_key('CONFIG_XLNX_ZYNQMP_ARM') ? ['xlnx-can-test', 'fuzz-xlnx-dp-test'] : []) + \ @@ -274,6 +276,8 @@ 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_PHYTIUMPI') ? ['phytiumpi-test'] : []) + \ qtests_cxl + \ ['arm-cpu-features', 'numa-test', @@ -294,10 +298,16 @@ qtests_riscv32 = \ qtests_riscv64 = ['riscv-csr-test'] + \ (unpack_edk2_blobs ? ['bios-tables-test'] : []) + \ + (config_all_devices.has_key('CONFIG_SPACEMIT_K3') ? + ['spacemit-k3-test'] : []) + \ (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and config_all_devices.has_key('CONFIG_RISCV_IOMMU') ? ['iommu-riscv-test'] : []) + \ - (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : []) + (config_all_devices.has_key('CONFIG_K230') ? + ['k230-kpu-test', 'k230-sysctl-test', 'k230-wdt-test', + 'k230-ws2812-amp-test'] : []) + \ + (config_all_devices.has_key('CONFIG_MILKV_DUO') ? + ['milkv-duo-test'] : []) qtests_hexagon = ['boot-serial-test'] @@ -398,6 +408,8 @@ qtests = { 'dbus-vmstate-test': files('migration/migration-qmp.c', 'migration/migration-util.c') + dbus_vmstate1 + [gio], + 'k230-ws2812-amp-test': files('migration/migration-qmp.c', + 'migration/migration-util.c'), 'erst-test': files('erst-test.c'), 'ivshmem-test': [rt, '../../contrib/ivshmem-server/ivshmem-server.c'], 'migration-test': test_migration_files + migration_tls_files + migration_colo_files, @@ -405,6 +417,7 @@ qtests = { 'pnv-xive2-test': files('pnv-xive2-common.c', 'pnv-xive2-flush-sync.c', 'pnv-xive2-nvpg_bar.c'), 'qos-test': [chardev, io, qos_test_ss.apply({}).sources()], + 'spacemit-k3-test': [fdt], 'tpm-crb-swtpm-test': [io, tpmemu_files], 'tpm-crb-test': [io, tpmemu_files], 'tpm-tis-swtpm-test': [io, tpmemu_files, 'tpm-tis-util.c'], diff --git a/tests/qtest/milkv-duo-test.c b/tests/qtest/milkv-duo-test.c new file mode 100644 index 0000000000000..06064b8733eb7 --- /dev/null +++ b/tests/qtest/milkv-duo-test.c @@ -0,0 +1,70 @@ +/* + * QTest for Milk-V Duo Board + * + * Copyright (c) 2026 Kuan-Wei Chiu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest.h" + +#define CV1800B_CLK_BASE 0x03002000 +#define CV1800B_CLK_BYPASS 0x030 +#define CV1800B_CLK_BYPASS_RESET 0xFFFFFFFF +#define TEST_PATTERN_5A 0x5A5A5A5A +#define TEST_PATTERN_A5 0xA5A5A5A5 + +#define CV1800B_UART0_BASE 0x04140000 +#define DW_UART_UCV 0xF8 +#define DW_UART_CTR 0xFC +#define DW_UART_VERSION_3_23A 0x3332332A +#define DW_UART_TYPE_SIGNATURE 0x44570110 + +static void test_milkv_duo_uart(void) +{ + QTestState *qts; + uint32_t component_version; + uint32_t component_type; + + qts = qtest_init("-M milkv-duo"); + + component_version = qtest_readl(qts, CV1800B_UART0_BASE + DW_UART_UCV); + g_assert_cmphex(component_version, ==, DW_UART_VERSION_3_23A); + + component_type = qtest_readl(qts, CV1800B_UART0_BASE + DW_UART_CTR); + g_assert_cmphex(component_type, ==, DW_UART_TYPE_SIGNATURE); + + qtest_quit(qts); +} + +static void test_milkv_duo_clk(void) +{ + QTestState *qts; + uint32_t clk_bypass_val; + + qts = qtest_init("-M milkv-duo"); + + clk_bypass_val = qtest_readl(qts, CV1800B_CLK_BASE + CV1800B_CLK_BYPASS); + g_assert_cmphex(clk_bypass_val, ==, CV1800B_CLK_BYPASS_RESET); + + qtest_writel(qts, CV1800B_CLK_BASE + CV1800B_CLK_BYPASS, TEST_PATTERN_5A); + clk_bypass_val = qtest_readl(qts, CV1800B_CLK_BASE + CV1800B_CLK_BYPASS); + g_assert_cmphex(clk_bypass_val, ==, TEST_PATTERN_5A); + + qtest_writel(qts, CV1800B_CLK_BASE + CV1800B_CLK_BYPASS, TEST_PATTERN_A5); + clk_bypass_val = qtest_readl(qts, CV1800B_CLK_BASE + CV1800B_CLK_BYPASS); + g_assert_cmphex(clk_bypass_val, ==, TEST_PATTERN_A5); + + qtest_quit(qts); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/riscv/milkv-duo/uart", test_milkv_duo_uart); + qtest_add_func("/riscv/milkv-duo/clk", test_milkv_duo_clk); + + return g_test_run(); +} diff --git a/tests/qtest/phytiumpi-test.c b/tests/qtest/phytiumpi-test.c new file mode 100644 index 0000000000000..3abba729395d5 --- /dev/null +++ b/tests/qtest/phytiumpi-test.c @@ -0,0 +1,616 @@ +/* + * QTest for the local-only Phytium Pi board model + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include +#include "qemu/bswap.h" +#include "libqtest.h" + +#define PHYTIUMPI_MACHINE "phytium-pi" +#define PHYTIUMPI_RAM_BASE 0x80000000ULL +#define PHYTIUMPI_HIGH_RAM_BASE 0x2000000000ULL +#define PHYTIUMPI_MCI_BASE 0x28000000ULL +#define PHYTIUMPI_MCI1_BASE 0x28001000ULL +#define PHYTIUMPI_UART_BASE 0x2802a000ULL +#define PHYTIUMPI_GICD_BASE 0x30800000ULL +#define PHYTIUMPI_GICR_BASE 0x30880000ULL +#define PHYTIUMPI_GIC_ITS_BASE 0x30820000ULL +#define PHYTIUMPI_GIC_CPU_BASE 0x30840000ULL +#define PHYTIUMPI_SCP_MAILBOX_BASE 0x32a10400ULL +#define PHYTIUMPI_FW_USB2_CLUSTER_BASE 0x31800000ULL +#define PHYTIUMPI_FW_PHY_CFG_BASE 0x31b00000ULL +#define PHYTIUMPI_FW_PHY_CFG0_BASE 0x32000000ULL +#define PHYTIUMPI_FW_PHY_CFG1_BASE 0x32100000ULL +#define PHYTIUMPI_FW_USB2_LOW_BASE 0x32800000ULL +#define PHYTIUMPI_FW_USB2_HIGH_BASE 0x32880000ULL +#define PHYTIUMPI_DDR_CTRL_BASE 0x32b33000ULL +#define PHYTIUMPI_XMAC_BASE 0x3200c000ULL +#define PHYTIUMPI_RNG_BASE 0x32a36000ULL +#define PHYTIUMPI_PCIE_ECAM_BASE 0x40000000ULL + +#define GICD_TYPER 0x0004 +#define GICR_TYPER 0x0008 +#define PL011_FR 0x0018 +#define PL011_FR_TXFE 0x80 +#define TEST_SD_SIZE (1 << 20) + +#define MCI_CNTRL 0x000 +#define MCI_CNTRL_CONTROLLER_RESET 0x00000001 +#define MCI_CNTRL_FIFO_RESET 0x00000002 +#define MCI_CNTRL_DMA_RESET 0x00000004 +#define MCI_CNTRL_INT_ENABLE 0x00000010 +#define MCI_CNTRL_DMA_ENABLE 0x00000020 +#define MCI_CNTRL_USE_INTERNAL_DMAC 0x02000000 +#define MCI_BLKSIZ 0x01c +#define MCI_BYTCNT 0x020 +#define MCI_INT_MASK 0x024 +#define MCI_CMDARG 0x028 +#define MCI_CMD 0x02c +#define MCI_CMD_START 0x80000000 +#define MCI_CMD_DAT_EXP 0x00000200 +#define MCI_CMD_RESP_LONG 0x00000080 +#define MCI_CMD_RESP_EXP 0x00000040 +#define MCI_RESP0 0x030 +#define MCI_MASKED_INTS 0x040 +#define MCI_RAW_INTS 0x044 +#define MCI_STATUS 0x048 +#define MCI_STATUS_FIFO_EMPTY 0x00000004 +#define MCI_CARD_DETECT 0x050 +#define MCI_CCLK_RDY 0x058 +#define MCI_CARD_RESET 0x078 +#define MCI_BUS_MODE 0x080 +#define MCI_BUS_MODE_DE 0x00000080 +#define MCI_DESC_LIST_ADDRL 0x088 +#define MCI_DESC_LIST_ADDRH 0x08c +#define MCI_DMAC_STATUS 0x090 +#define MCI_DMAC_INT_ENA 0x094 +#define MCI_DATA 0x200 + +#define MCI_INT_CMD 0x00000004 +#define MCI_INT_DTO 0x00000008 +#define MCI_INT_RTO 0x00000100 +#define MCI_INT_DRTO 0x00000200 +#define MCI_DMAC_STATUS_RI 0x00000002 +#define MCI_DMAC_STATUS_NIS 0x00000100 + +#define ADMA_ATTR_LD 0x00000004 +#define ADMA_ATTR_FD 0x00000008 +#define ADMA_ATTR_OWN 0x80000000 + +#define SCP_MAILBOX_STATUS 0x004 +#define SCP_MAILBOX_CONTROL 0x010 +#define SCP_MAILBOX_LENGTH 0x014 +#define SCP_MAILBOX_COMMAND 0x018 +#define SCP_MAILBOX_RESPONSE 0x01c +#define SCP_MAILBOX_DONE 0x1 + +#define DDR_CTRL_INDEX 0x080 +#define DDR_CTRL_DATA 0x084 +#define DDR_CTRL_POLL_INDEX ((0x76 + 0x800) << 2) +#define DDR_CTRL_SAMPLE_INDEX 0x5de0 +#define DDR_CTRL_OTHER_INDEX 0x5dd0 +#define DDR_CTRL_READY_INDEX ((0xdc + 0x800) << 2) +#define DDR_CTRL_DONE (1u << 27) +#define DDR_CTRL_READY_BITS (DDR_CTRL_DONE | (1u << 25) | 1u) + +#define XMAC_NETWORK_STATUS 0x008 +#define XMAC_PHY_MAINTENANCE 0x034 +#define XMAC_PCS_STATUS 0x214 +#define XMAC_MDIO_IDLE 0x00000004 +#define XMAC_PCS_LINK_UP 0x00008000 +#define XMAC_MDIO_READ(phy, reg) \ + (0x60020000 | ((phy) << 23) | ((reg) << 18)) +#define XMAC_MDIO_WRITE(phy, reg, data) \ + (0x50020000 | ((phy) << 23) | ((reg) << 18) | ((data) & 0xffff)) + +#define PHY_BMCR 0 +#define PHY_BMSR 1 +#define PHY_PHYID1 2 +#define PHY_PHYID2 3 +#define PHY_BMCR_RESET 0x8000 +#define PHY_BMSR_LINK_STATUS 0x0004 +#define PHY_BMSR_AUTONEG_COMPLETE 0x0020 + +static QTestState *phytiumpi_qtest_start(unsigned int cpus) +{ + return qtest_initf("-machine " PHYTIUMPI_MACHINE " -smp %u -m 512M", + cpus); +} + +static QTestState *phytiumpi_qtest_start_extra(unsigned int cpus, + const char *extra) +{ + return qtest_initf("-machine " PHYTIUMPI_MACHINE " -smp %u -m 512M %s", + cpus, extra ? extra : ""); +} + +static QTestState *phytiumpi_qtest_start_ram(unsigned int cpus, + const char *ram_size) +{ + return qtest_initf("-machine " PHYTIUMPI_MACHINE " -smp %u -m %s", + cpus, ram_size); +} + +static char *phytiumpi_create_sd_image(void) +{ + g_autofree uint8_t *image = g_malloc0(TEST_SD_SIZE); + g_autoptr(GError) error = NULL; + char *path = NULL; + int fd; + + fd = g_file_open_tmp("phytium-pi-sd-XXXXXX.img", &path, &error); + g_assert_no_error(error); + g_assert_cmpint(fd, >=, 0); + close(fd); + + for (unsigned int i = 0; i < 512; i++) { + image[i] = i & 0xff; + } + g_assert_true(g_file_set_contents(path, (const char *)image, + TEST_SD_SIZE, &error)); + g_assert_no_error(error); + + return path; +} + +static void phytiumpi_remove_sd_image(char *path) +{ + if (path) { + g_unlink(path); + g_free(path); + } +} + +static uint32_t phytiumpi_mci_cmd(QTestState *qts, uint32_t arg, + uint32_t cmd) +{ + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_CMDARG, arg); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_CMD, MCI_CMD_START | cmd); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_CMD) & + MCI_CMD_START, ==, 0); + return qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_RESP0); +} + +static uint16_t phytiumpi_xmac_phy_read(QTestState *qts, uint8_t phy, + uint8_t reg) +{ + qtest_writel(qts, PHYTIUMPI_XMAC_BASE + XMAC_PHY_MAINTENANCE, + XMAC_MDIO_READ(phy, reg)); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_XMAC_BASE + + XMAC_NETWORK_STATUS) & XMAC_MDIO_IDLE, + ==, XMAC_MDIO_IDLE); + return qtest_readl(qts, PHYTIUMPI_XMAC_BASE + XMAC_PHY_MAINTENANCE); +} + +static void phytiumpi_xmac_phy_write(QTestState *qts, uint8_t phy, + uint8_t reg, uint16_t value) +{ + qtest_writel(qts, PHYTIUMPI_XMAC_BASE + XMAC_PHY_MAINTENANCE, + XMAC_MDIO_WRITE(phy, reg, value)); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_XMAC_BASE + + XMAC_NETWORK_STATUS) & XMAC_MDIO_IDLE, + ==, XMAC_MDIO_IDLE); +} + +static void phytiumpi_mci_clear_irqs(QTestState *qts) +{ + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_RAW_INTS, 0x0001ffff); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_DMAC_STATUS, 0x00000317); +} + +static void phytiumpi_mci_write_adma_desc(QTestState *qts, uint64_t desc_addr, + uint64_t buf_addr, size_t len) +{ + uint8_t desc[32] = {}; + + stl_le_p(desc + 0, ADMA_ATTR_OWN | ADMA_ATTR_FD | ADMA_ATTR_LD); + stl_le_p(desc + 8, len); + stl_le_p(desc + 16, (uint32_t)buf_addr); + stl_le_p(desc + 20, (uint32_t)(buf_addr >> 32)); + qtest_memwrite(qts, desc_addr, desc, sizeof(desc)); +} + +static uint32_t phytiumpi_mci_init_card(QTestState *qts) +{ + uint32_t resp; + uint32_t rca; + + phytiumpi_mci_cmd(qts, 0, 0); + phytiumpi_mci_cmd(qts, 0x1aa, MCI_CMD_RESP_EXP | 8); + + for (unsigned int i = 0; i < 100; i++) { + phytiumpi_mci_cmd(qts, 0, MCI_CMD_RESP_EXP | 55); + resp = phytiumpi_mci_cmd(qts, 0x41200000, MCI_CMD_RESP_EXP | 41); + if (resp & 0x80000000) { + break; + } + } + + phytiumpi_mci_cmd(qts, 0, MCI_CMD_RESP_EXP | MCI_CMD_RESP_LONG | 2); + resp = phytiumpi_mci_cmd(qts, 0, MCI_CMD_RESP_EXP | 3); + rca = resp & 0xffff0000; + g_assert_cmphex(rca, !=, 0); + + phytiumpi_mci_cmd(qts, rca, MCI_CMD_RESP_EXP | 7); + phytiumpi_mci_cmd(qts, 512, MCI_CMD_RESP_EXP | 16); + phytiumpi_mci_clear_irqs(qts); + + return rca; +} + +static void test_phytiumpi_machine_creation(void) +{ + QTestState *qts = phytiumpi_qtest_start(1); + uint64_t pattern = 0x1122334455667788ULL; + uint32_t gicd_typer; + uint32_t uart_fr; + + qtest_writeq(qts, PHYTIUMPI_RAM_BASE, pattern); + g_assert_cmphex(qtest_readq(qts, PHYTIUMPI_RAM_BASE), ==, pattern); + + gicd_typer = qtest_readl(qts, PHYTIUMPI_GICD_BASE + GICD_TYPER); + g_assert_cmpuint(gicd_typer & 0x1f, >=, 0x7); + + qtest_readq(qts, PHYTIUMPI_GICR_BASE + GICR_TYPER); + + uart_fr = qtest_readl(qts, PHYTIUMPI_UART_BASE + PL011_FR); + g_assert_cmphex(uart_fr & PL011_FR_TXFE, ==, PL011_FR_TXFE); + + qtest_readl(qts, PHYTIUMPI_GIC_ITS_BASE); + qtest_readl(qts, PHYTIUMPI_GIC_CPU_BASE); + qtest_readl(qts, PHYTIUMPI_PCIE_ECAM_BASE); + qtest_writel(qts, PHYTIUMPI_RNG_BASE + 4, 0xfeedface); + qtest_readl(qts, PHYTIUMPI_RNG_BASE + 4); + + qtest_writel(qts, PHYTIUMPI_FW_USB2_CLUSTER_BASE + 0x1c0000, + 0x11223344); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_FW_USB2_CLUSTER_BASE + 0x1c0000), + ==, 0x11223344); + qtest_writel(qts, PHYTIUMPI_FW_PHY_CFG_BASE + 0x40254, 0xaabbccdd); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_FW_PHY_CFG_BASE + 0x40254), + ==, 0xaabbccdd); + qtest_writel(qts, PHYTIUMPI_FW_PHY_CFG0_BASE + 0xe5a0, 0x05060708); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_FW_PHY_CFG0_BASE + 0xe5a0), + ==, 0x05060708); + qtest_writel(qts, PHYTIUMPI_FW_PHY_CFG1_BASE + 0x40254, 0xddccbbaa); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_FW_PHY_CFG1_BASE + 0x40254), + ==, 0xddccbbaa); + qtest_writel(qts, PHYTIUMPI_FW_PHY_CFG1_BASE + 0x14008c, 0x01020304); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_FW_PHY_CFG1_BASE + 0x14008c), + ==, 0x01020304); + qtest_writeb(qts, PHYTIUMPI_FW_USB2_LOW_BASE + 0x18d, 0xa5); + g_assert_cmphex(qtest_readb(qts, PHYTIUMPI_FW_USB2_LOW_BASE + 0x18d), + ==, 0xa5); + qtest_writel(qts, PHYTIUMPI_FW_USB2_HIGH_BASE + 0x7fffc, 0x55667788); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_FW_USB2_HIGH_BASE + 0x7fffc), + ==, 0x55667788); + + qtest_writel(qts, PHYTIUMPI_SCP_MAILBOX_BASE + SCP_MAILBOX_RESPONSE, 1); + qtest_writel(qts, PHYTIUMPI_SCP_MAILBOX_BASE + SCP_MAILBOX_STATUS, 0); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_SCP_MAILBOX_BASE + + SCP_MAILBOX_STATUS) & SCP_MAILBOX_DONE, + ==, SCP_MAILBOX_DONE); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_SCP_MAILBOX_BASE + + SCP_MAILBOX_RESPONSE), ==, 0); + + qtest_writel(qts, PHYTIUMPI_SCP_MAILBOX_BASE + SCP_MAILBOX_COMMAND, + 0x5006); + qtest_writel(qts, PHYTIUMPI_SCP_MAILBOX_BASE + SCP_MAILBOX_LENGTH, 8); + qtest_writel(qts, PHYTIUMPI_SCP_MAILBOX_BASE + SCP_MAILBOX_RESPONSE, 1); + qtest_writel(qts, PHYTIUMPI_SCP_MAILBOX_BASE + SCP_MAILBOX_CONTROL, 0); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_SCP_MAILBOX_BASE + + SCP_MAILBOX_STATUS) & SCP_MAILBOX_DONE, + ==, SCP_MAILBOX_DONE); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_SCP_MAILBOX_BASE + + SCP_MAILBOX_RESPONSE), ==, 0); + + qtest_writel(qts, PHYTIUMPI_DDR_CTRL_BASE + DDR_CTRL_DATA, 0x03000110); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_DDR_CTRL_BASE + DDR_CTRL_DATA), + ==, 0x03000110); + qtest_writel(qts, PHYTIUMPI_DDR_CTRL_BASE + DDR_CTRL_INDEX, + DDR_CTRL_POLL_INDEX); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_DDR_CTRL_BASE + DDR_CTRL_DATA), + ==, DDR_CTRL_DONE); + qtest_writel(qts, PHYTIUMPI_DDR_CTRL_BASE + DDR_CTRL_INDEX, + DDR_CTRL_SAMPLE_INDEX); + qtest_writel(qts, PHYTIUMPI_DDR_CTRL_BASE + DDR_CTRL_DATA, 0x18f00108); + qtest_writel(qts, PHYTIUMPI_DDR_CTRL_BASE + DDR_CTRL_INDEX, + DDR_CTRL_OTHER_INDEX); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_DDR_CTRL_BASE + DDR_CTRL_DATA), + ==, DDR_CTRL_READY_BITS); + qtest_writel(qts, PHYTIUMPI_DDR_CTRL_BASE + DDR_CTRL_DATA, 0); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_DDR_CTRL_BASE + DDR_CTRL_DATA), + ==, DDR_CTRL_READY_BITS); + qtest_writel(qts, PHYTIUMPI_DDR_CTRL_BASE + DDR_CTRL_INDEX, + DDR_CTRL_READY_INDEX); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_DDR_CTRL_BASE + DDR_CTRL_DATA), + ==, DDR_CTRL_READY_BITS); + + qtest_quit(qts); +} + +static void test_phytiumpi_highmem_creation(void) +{ + QTestState *qts = phytiumpi_qtest_start_ram(1, "2304M"); + uint64_t low_pattern = 0x1122334455667788ULL; + uint64_t high_pattern = 0x8877665544332211ULL; + + qtest_writeq(qts, PHYTIUMPI_RAM_BASE, low_pattern); + qtest_writeq(qts, PHYTIUMPI_HIGH_RAM_BASE, high_pattern); + g_assert_cmphex(qtest_readq(qts, PHYTIUMPI_RAM_BASE), ==, low_pattern); + g_assert_cmphex(qtest_readq(qts, PHYTIUMPI_HIGH_RAM_BASE), ==, + high_pattern); + + qtest_quit(qts); +} + +static void test_phytiumpi_mci_registers(void) +{ + char *sd_path = phytiumpi_create_sd_image(); + g_autofree char *args = g_strdup_printf( + "-drive if=sd,file=%s,format=raw,auto-read-only=off", sd_path); + QTestState *qts = phytiumpi_qtest_start_extra(1, args); + uint32_t ctrl; + + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_CCLK_RDY) & 1, + ==, 1); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_CARD_RESET), ==, + 1); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_CARD_DETECT) & 1, + ==, 0); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_STATUS) & + MCI_STATUS_FIFO_EMPTY, ==, MCI_STATUS_FIFO_EMPTY); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_MCI1_BASE + MCI_CCLK_RDY) & 1, + ==, 1); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_MCI1_BASE + MCI_CARD_DETECT) & + 1, ==, 1); + + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_CNTRL, + MCI_CNTRL_CONTROLLER_RESET | MCI_CNTRL_FIFO_RESET | + MCI_CNTRL_DMA_RESET); + ctrl = qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_CNTRL); + g_assert_cmphex(ctrl & (MCI_CNTRL_CONTROLLER_RESET | + MCI_CNTRL_FIFO_RESET | + MCI_CNTRL_DMA_RESET), ==, 0); + qtest_writel(qts, PHYTIUMPI_MCI1_BASE + MCI_CNTRL, + MCI_CNTRL_CONTROLLER_RESET | MCI_CNTRL_FIFO_RESET | + MCI_CNTRL_DMA_RESET); + ctrl = qtest_readl(qts, PHYTIUMPI_MCI1_BASE + MCI_CNTRL); + g_assert_cmphex(ctrl & (MCI_CNTRL_CONTROLLER_RESET | + MCI_CNTRL_FIFO_RESET | + MCI_CNTRL_DMA_RESET), ==, 0); + + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_CNTRL, + MCI_CNTRL_INT_ENABLE); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_INT_MASK, MCI_INT_CMD); + phytiumpi_mci_cmd(qts, 0, 0); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_RAW_INTS) & + MCI_INT_CMD, ==, MCI_INT_CMD); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_MASKED_INTS) & + MCI_INT_CMD, ==, MCI_INT_CMD); + + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_RAW_INTS, MCI_INT_CMD); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_RAW_INTS) & + MCI_INT_CMD, ==, 0); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_MASKED_INTS) & + MCI_INT_CMD, ==, 0); + + qtest_quit(qts); + phytiumpi_remove_sd_image(sd_path); +} + +static void test_phytiumpi_mci_send_scr(void) +{ + char *sd_path = phytiumpi_create_sd_image(); + g_autofree char *args = g_strdup_printf( + "-drive if=sd,file=%s,format=raw,auto-read-only=off", sd_path); + QTestState *qts = phytiumpi_qtest_start_extra(1, args); + uint32_t rca; + uint32_t raw; + + rca = phytiumpi_mci_init_card(qts); + + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_BLKSIZ, 512); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_BYTCNT, 512); + phytiumpi_mci_cmd(qts, rca, MCI_CMD_RESP_EXP | 55); + phytiumpi_mci_cmd(qts, 0, MCI_CMD_RESP_EXP | MCI_CMD_DAT_EXP | 51); + qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_DATA); + qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_DATA); + + raw = qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_RAW_INTS); + g_assert_cmphex(raw & MCI_INT_DTO, ==, MCI_INT_DTO); + g_assert_cmphex(raw & (MCI_INT_RTO | MCI_INT_DRTO), ==, 0); + + phytiumpi_mci_clear_irqs(qts); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_BLKSIZ, 512); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_BYTCNT, 512); + phytiumpi_mci_cmd(qts, 0, MCI_CMD_RESP_EXP | MCI_CMD_DAT_EXP | 51); + qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_DATA); + qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_DATA); + + raw = qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_RAW_INTS); + g_assert_cmphex(raw & MCI_INT_DTO, ==, MCI_INT_DTO); + g_assert_cmphex(raw & (MCI_INT_RTO | MCI_INT_DRTO), ==, 0); + + qtest_quit(qts); + phytiumpi_remove_sd_image(sd_path); +} + +static void test_phytiumpi_mci_adma_read(void) +{ + char *sd_path = phytiumpi_create_sd_image(); + g_autofree char *args = g_strdup_printf( + "-drive if=sd,file=%s,format=raw,auto-read-only=off", sd_path); + QTestState *qts = phytiumpi_qtest_start_extra(1, args); + uint64_t desc_addr = PHYTIUMPI_RAM_BASE + 0x10000; + uint64_t buf_addr = PHYTIUMPI_RAM_BASE + 0x11000; + uint8_t desc[32] = {}; + uint8_t data[512] = {}; + + phytiumpi_mci_init_card(qts); + + phytiumpi_mci_write_adma_desc(qts, desc_addr, buf_addr, sizeof(data)); + qtest_memset(qts, buf_addr, 0, sizeof(data)); + + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_CNTRL, + MCI_CNTRL_INT_ENABLE | MCI_CNTRL_USE_INTERNAL_DMAC); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_INT_MASK, + MCI_INT_CMD | MCI_INT_DTO); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_DMAC_INT_ENA, + MCI_DMAC_STATUS_RI | MCI_DMAC_STATUS_NIS); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_BUS_MODE, MCI_BUS_MODE_DE); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_BLKSIZ, sizeof(data)); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_BYTCNT, sizeof(data)); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_DESC_LIST_ADDRL, + (uint32_t)desc_addr); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_DESC_LIST_ADDRH, + (uint32_t)(desc_addr >> 32)); + + phytiumpi_mci_cmd(qts, 0, MCI_CMD_RESP_EXP | MCI_CMD_DAT_EXP | 17); + + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_RAW_INTS) & + MCI_INT_DTO, ==, MCI_INT_DTO); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_DMAC_STATUS) & + (MCI_DMAC_STATUS_RI | MCI_DMAC_STATUS_NIS), ==, + MCI_DMAC_STATUS_RI | MCI_DMAC_STATUS_NIS); + + qtest_memread(qts, buf_addr, data, sizeof(data)); + for (unsigned int i = 0; i < 512; i++) { + g_assert_cmphex(data[i], ==, (i & 0xff)); + } + + qtest_memread(qts, desc_addr, desc, sizeof(desc)); + g_assert_cmphex(ldl_le_p(desc) & ADMA_ATTR_OWN, ==, 0); + + qtest_quit(qts); + phytiumpi_remove_sd_image(sd_path); +} + +static void test_phytiumpi_mci_adma_scr(void) +{ + char *sd_path = phytiumpi_create_sd_image(); + g_autofree char *args = g_strdup_printf( + "-drive if=sd,file=%s,format=raw,auto-read-only=off", sd_path); + QTestState *qts = phytiumpi_qtest_start_extra(1, args); + uint64_t desc_addr = PHYTIUMPI_RAM_BASE + 0x12000; + uint64_t buf_addr = PHYTIUMPI_RAM_BASE + 0x13000; + uint8_t data[8] = {}; + uint8_t desc[32] = {}; + uint32_t rca; + uint32_t raw; + + rca = phytiumpi_mci_init_card(qts); + + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_CNTRL, + MCI_CNTRL_INT_ENABLE | MCI_CNTRL_USE_INTERNAL_DMAC); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_INT_MASK, + MCI_INT_CMD | MCI_INT_DTO); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_DMAC_INT_ENA, + MCI_DMAC_STATUS_RI | MCI_DMAC_STATUS_NIS); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_BUS_MODE, MCI_BUS_MODE_DE); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_BLKSIZ, 512); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_BYTCNT, 512); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_DESC_LIST_ADDRL, + (uint32_t)desc_addr); + qtest_writel(qts, PHYTIUMPI_MCI_BASE + MCI_DESC_LIST_ADDRH, + (uint32_t)(desc_addr >> 32)); + + phytiumpi_mci_write_adma_desc(qts, desc_addr, buf_addr, 512); + qtest_memset(qts, buf_addr, 0, 512); + phytiumpi_mci_cmd(qts, rca, MCI_CMD_RESP_EXP | 55); + phytiumpi_mci_cmd(qts, 0, MCI_CMD_RESP_EXP | MCI_CMD_DAT_EXP | 51); + + raw = qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_RAW_INTS); + g_assert_cmphex(raw & MCI_INT_DTO, ==, MCI_INT_DTO); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_DMAC_STATUS) & + (MCI_DMAC_STATUS_RI | MCI_DMAC_STATUS_NIS), ==, + MCI_DMAC_STATUS_RI | MCI_DMAC_STATUS_NIS); + + qtest_memread(qts, buf_addr, data, sizeof(data)); + g_assert_cmphex(data[0], ==, 0x02); + g_assert_cmphex(data[1], ==, 0x25); + qtest_memread(qts, desc_addr, desc, sizeof(desc)); + g_assert_cmphex(ldl_le_p(desc) & ADMA_ATTR_OWN, ==, 0); + + phytiumpi_mci_clear_irqs(qts); + phytiumpi_mci_write_adma_desc(qts, desc_addr, buf_addr, 512); + qtest_memset(qts, buf_addr, 0, 512); + phytiumpi_mci_cmd(qts, 0, MCI_CMD_RESP_EXP | MCI_CMD_DAT_EXP | 51); + + raw = qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_RAW_INTS); + g_assert_cmphex(raw & MCI_INT_DTO, ==, MCI_INT_DTO); + g_assert_cmphex(raw & (MCI_INT_RTO | MCI_INT_DRTO), ==, 0); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_MCI_BASE + MCI_DMAC_STATUS) & + (MCI_DMAC_STATUS_RI | MCI_DMAC_STATUS_NIS), ==, + MCI_DMAC_STATUS_RI | MCI_DMAC_STATUS_NIS); + + qtest_memread(qts, buf_addr, data, sizeof(data)); + g_assert_cmphex(data[0], ==, 0x02); + g_assert_cmphex(data[1], ==, 0x25); + + qtest_quit(qts); + phytiumpi_remove_sd_image(sd_path); +} + +static void test_phytiumpi_xmac_phy_link(void) +{ + QTestState *qts = phytiumpi_qtest_start(1); + uint16_t bmsr; + uint16_t phyid1; + uint16_t phyid2; + + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_XMAC_BASE + + XMAC_NETWORK_STATUS) & XMAC_MDIO_IDLE, + ==, XMAC_MDIO_IDLE); + g_assert_cmphex(qtest_readl(qts, PHYTIUMPI_XMAC_BASE + XMAC_PCS_STATUS) & + XMAC_PCS_LINK_UP, ==, XMAC_PCS_LINK_UP); + + bmsr = phytiumpi_xmac_phy_read(qts, 0, PHY_BMSR); + g_assert_cmphex(bmsr & PHY_BMSR_LINK_STATUS, ==, + PHY_BMSR_LINK_STATUS); + g_assert_cmphex(bmsr & PHY_BMSR_AUTONEG_COMPLETE, ==, + PHY_BMSR_AUTONEG_COMPLETE); + + phyid1 = phytiumpi_xmac_phy_read(qts, 0, PHY_PHYID1); + phyid2 = phytiumpi_xmac_phy_read(qts, 0, PHY_PHYID2); + g_assert_cmphex(phyid1, !=, 0); + g_assert_cmphex(phyid1, !=, 0xffff); + g_assert_cmphex(phyid2, !=, 0); + g_assert_cmphex(phyid2, !=, 0xffff); + + phytiumpi_xmac_phy_write(qts, 0, PHY_BMCR, PHY_BMCR_RESET); + g_assert_cmphex(phytiumpi_xmac_phy_read(qts, 0, PHY_BMCR) & + PHY_BMCR_RESET, ==, 0); + g_assert_cmphex(phytiumpi_xmac_phy_read(qts, 1, PHY_BMSR), ==, 0xffff); + + qtest_quit(qts); +} + +static void test_phytiumpi_smp_creation(void) +{ + QTestState *qts = phytiumpi_qtest_start(4); + + qtest_readq(qts, PHYTIUMPI_GICR_BASE + GICR_TYPER); + qtest_quit(qts); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/phytium-pi/machine_creation", + test_phytiumpi_machine_creation); + qtest_add_func("/phytium-pi/highmem_creation", + test_phytiumpi_highmem_creation); + qtest_add_func("/phytium-pi/mci_registers", test_phytiumpi_mci_registers); + qtest_add_func("/phytium-pi/mci_send_scr", test_phytiumpi_mci_send_scr); + qtest_add_func("/phytium-pi/mci_adma_read", test_phytiumpi_mci_adma_read); + qtest_add_func("/phytium-pi/mci_adma_scr", test_phytiumpi_mci_adma_scr); + qtest_add_func("/phytium-pi/xmac_phy_link", test_phytiumpi_xmac_phy_link); + qtest_add_func("/phytium-pi/smp_creation", test_phytiumpi_smp_creation); + + return g_test_run(); +} diff --git a/tests/qtest/rk3588-test.c b/tests/qtest/rk3588-test.c new file mode 100644 index 0000000000000..e5f43e27daf7b --- /dev/null +++ b/tests/qtest/rk3588-test.c @@ -0,0 +1,862 @@ +/* + * QTest for the local-only Rockchip RK3588 board machine models + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest.h" + +#define RK3588_QTEST_RAM_SIZE 0x20000000ULL +#define RK3588_RAM_BASE 0x00200000ULL +#define RK3588_ATAGS_BASE 0x001fe000ULL +#define RK3588_ATAG_DDR_MEM 0x54410052 +#define RK3588_ATAG_DDR_MEM_WORDS 48 +#define RK3588_IRAM_LOG_BASE 0xff100000ULL +#define RK3588_PMUSRAM_SKIP_ADDR 0xff101764ULL +#define RK3588_AARCH64_RET 0xd65f03c0 +#define RK3588_FIRMWARE_DEFAULT_REG 0xf7010000ULL +#define RK3588_DDR_CHANNEL_STATUS 0xf7010014ULL +#define RK3588_DDR_CHANNEL_STATUS_READY 0x1 +#define RK3588_DDR_CHANNEL_CMD 0xf7010080ULL +#define RK3588_DDR_CHANNEL_CMD_START 0x80000000U +#define RK3588_DDR_CHANNEL_BUSY 0xf7010090ULL +#define RK3588_DDR_CHANNEL_BUSY_FLAG 0x1 +#define RK3588_DDR_CHANNEL_GATE_STATUS 0xf7010514ULL +#define RK3588_DDR_CHANNEL_GATE_BUSY 0x1 +#define RK3588_DDR_CHANNEL_GATE_CMD 0xf7010510ULL +#define RK3588_DDR_CHANNEL_GATE_ENABLE 0x20 +#define RK3588_DDR_CHANNEL_GATE_CTRL 0xf7010c80ULL +#define RK3588_DDR_CHANNEL_PHY_STATUS 0xf7010b90ULL +#define RK3588_DDR_CHANNEL_PHY_BUSY 0x10000 +#define RK3588_DDR_PHY_GATE_CTRL 0xfe0c00b0ULL +#define RK3588_DDR_PHY_GATE_ENABLE 0x20 +#define RK3588_DDRPHY_CTRL 0xfd8d8154ULL +#define RK3588_DDRPHY_STATUS 0xfd8d8184ULL +#define RK3588_DDRPHY_STATUS_ACTIVE 0x3 +#define RK3588_PCIE3X4_APB_BASE 0xfe150000ULL +#define RK3588_PCIE3X4_DBI_BASE 0xa40000000ULL +#define RK3588_PCIE3X4_CFG_BASE 0xf0000000ULL +#define RK3588_CRU_BASE 0xfd7c0000ULL +#define RK3588_CRU_SPL_PLL_STATUS 0xfd810018ULL +#define RK3588_CRU_SPL_PLL_LOCKED 0x8000 +#define RK3588_PMU0_GRF_BASE 0xfd588000ULL +#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_ATF_DDR_RUNTIME_ADDR 0x0008d000ULL +#define RK3588_ATF_DDR_GLOBAL_PTR 0x0008d0a8ULL +#define RK3588_ATF_TIMER_PTR 0x0008d0b0ULL +#define RK3588_ATF_TIMER_TABLE 0x0008d0b8ULL +#define RK3588_ATF_TIMER_COUNTER 0x00062054ULL +#define RK3588_ATF_DDR_DESCRIPTOR 0x0008fd20ULL +#define RK3588_ATF_DDR_CHANNEL_TABLE 0x0008fe00ULL +#define RK3588_ATF_DDR_CHANNEL_TABLE_OFFSET \ + (RK3588_ATF_DDR_CHANNEL_TABLE - RK3588_ATF_DDR_DESCRIPTOR) +#define RK3588_ATF_DDR_GLOBAL_BASE 0xfd000000ULL +#define RK3588_ATF_DDR_CHANNEL0_BASE 0xfd100000ULL +#define RK3588_ATF_DDR_CTRL_BUSY_MASK ((1U << 31) | (1U << 3)) +#define RK3588_ATF_DDR_STATUS_HANDSHAKE 0x14 +#define RK3588_ATF_DDR_STATUS_REQUEST 0x1 +#define RK3588_ATF_DDR_STATUS_ACK (1U << 31) +#define RK3588_FIRMWARE_SCRATCH 0x00100000ULL +#define RK3588_GTIMER_HZ 24000000 +#define RK3588_STIMER_BASE 0xfd8c8000ULL +#define RK3588_GMAC0_BASE 0xfe1b0000ULL +#define RK3588_GMAC1_BASE 0xfe1c0000ULL +#define RK3588_SCMI_SHMEM_BASE 0x0010f000ULL +#define RK3588_SDMMC_BASE 0xfe2c0000ULL +#define RK3588_SDHCI_BASE 0xfe2e0000ULL +#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_GICD_BASE 0xfe600000ULL +#define RK3588_GIC_ITS0_BASE 0xfe640000ULL +#define RK3588_GIC_ITS1_BASE 0xfe660000ULL +#define RK3588_GICR_BASE 0xfe680000ULL +#define RK3588_GPIO0_BASE 0xfd8a0000ULL +#define RK3588_UART2_BASE 0xfeb50000ULL +#define RK3588_ZVM_LOW_RAM_BASE 0x68000000ULL +#define RK3588_ZVM_SHARED_RAM_BASE 0xe7f00000ULL +#define RK3588_ZVM_NOTIFY_INFO_BASE 0xefd00000ULL +#define RK3588_ZVM_NOTIFY_BASE 0xefe00000ULL +#define RK3588_ZVM_HIGH_RAM_BASE 0x100000000ULL +#define RK3588_ZVM_HIGH_RAM_LAST 0x1ffffffffULL + +#define GICD_TYPER 0x0004 +#define GICR_TYPER 0x0008 +#define GICR_TYPER_PLPIS 0x00000001 +#define GITS_CTLR 0x0000 +#define GITS_CTLR_QUIESCENT 0x80000000 +#define GITS_TYPER 0x0008 +#define GITS_TYPER_PHYSICAL 0x0000000000000001ULL +#define GITS_TYPER_IDBITS_MASK 0x0000000000001f00ULL +#define GITS_TYPER_IDBITS_15 0x0000000000000f00ULL +#define GITS_TYPER_DEVBITS_MASK 0x000000000003e000ULL +#define GITS_TYPER_DEVBITS_15 0x000000000001e000ULL +#define GITS_TYPER_CIDBITS_MASK 0x0000000f00000000ULL +#define GITS_TYPER_CIDBITS_15 0x0000000f00000000ULL +#define GITS_TYPER_CIL 0x0000001000000000ULL +#define GITS_PIDR0 0xffe0 +#define GITS_PIDR2 0xffe8 +#define GITS_PIDR0_ITS 0x94 +#define GITS_PIDR2_GICV3 0x3b +#define SCMI_CHANNEL_STATUS 0x0004 +#define SCMI_CHANNEL_FREE 0x00000001 +#define SCMI_MSG_PAYLOAD 0x001c +#define DWC_PCIE_VENDOR_DEVICE 0x0000 +#define DWC_PCIE_LTSSM_STATUS 0x300 +#define DWC_PCIE_LTSSM_LINK_UP 0x00030011 +#define DWMAC4_MAC_VERSION 0x0110 +#define DWMAC4_SNPSVER_0x51 0x00000051 +#define USB2_EHCI_CAPBASE 0x0000 +#define USB2_EHCI_CAPLENGTH 0x20 +#define USB2_EHCI_VERSION_1_0 0x0100 +#define USB2_EHCI_HCSPARAMS 0x0004 +#define USB2_EHCI_NPORTS 0x1 +#define USB2_EHCI_USBCMD 0x0020 +#define USB2_EHCI_USBSTS 0x0024 +#define USB2_EHCI_PORTSC0 0x0064 +#define USB2_EHCI_CMD_RESET 0x00000002 +#define USB2_EHCI_CMD_RUN 0x00000001 +#define USB2_EHCI_STS_HALT 0x00001000 +#define USB2_EHCI_PORT_POWER 0x00001000 +#define USB2_OHCI_REVISION 0x0000 +#define USB2_OHCI_REVISION_1_0 0x10 +#define USB2_OHCI_CMDSTATUS 0x0008 +#define USB2_OHCI_ROOTHUB_A 0x0048 +#define USB2_OHCI_PORTSTATUS0 0x0054 +#define USB2_OHCI_HCR 0x00000001 +#define USB2_OHCI_RH_A_NDP_MASK 0x000000ff +#define USB2_OHCI_RH_A_NDP1 0x00000001 +#define USB2_OHCI_RH_A_NPS 0x00000200 +#define USB2_OHCI_RH_A_NOCP 0x00001000 +#define USB2_OHCI_RH_PS_PPS 0x00000100 +#define DW_MMC_CTRL 0x0000 +#define DW_MMC_CTRL_RESET 0x00000001 +#define DW_MMC_FIFO_RESET 0x00000002 +#define DW_MMC_DMA_RESET 0x00000004 +#define DW_MMC_INT_ENABLE 0x00000010 +#define DW_MMC_INTMASK 0x0024 +#define DW_MMC_CMD 0x002c +#define DW_MMC_CMD_START 0x80000000 +#define DW_MMC_MINTSTS 0x0040 +#define DW_MMC_RINTSTS 0x0044 +#define DW_MMC_INT_CMD_DONE 0x00000004 +#define DW_MMC_STATUS 0x0048 +#define DW_MMC_STATUS_FIFO_EMPTY 0x00000004 +#define DW_MMC_FIFOTH 0x004c +#define DW_MMC_CDETECT 0x0050 +#define DW_MMC_VERID 0x006c +#define DW_MMC_VERID_270A 0x0000270a +#define DW_MMC_HCON 0x0070 +#define DW_MMC_RST_N 0x0078 +#define DW_MMC_BMOD 0x0080 +#define DW_MMC_BMOD_SWRESET 0x00000001 +#define DW_MMC_BMOD_ENABLE 0x00000080 +#define DW_MMC_DBADDR 0x0088 +#define DW_MMC_IDSTS 0x008c +#define DW_MMC_IDINTEN 0x0090 +#define DW_MMC_CDTHRCTL 0x0100 +#define DW_MMC_ENABLE_SHIFT 0x0110 +#define DW_MMC_RK_TIMING_CON0 0x0130 +#define DW_MMC_LEGACY_DATA 0x0200 +#define SDHCI_CAPABILITIES 0x0040 +#define SDHCI_VENDOR_AREA1 0x00e8 +#define DWCMSHC_VENDOR_AREA1 0x0500 +#define DWCMSHC_VENDOR_AREA2 0x0800 +#define DWCMSHC_EMMC_DLL_STATUS0 0x0840 +#define DWCMSHC_EMMC_DLL_LOCKED 0x00000100 +#define PMU1_GRF_OS_REG2 0x0208 +#define ROCKCHIP_HIWORD_PIN0_SET 0x00010001 +#define STIMER_CTRL 0x0004 +#define STIMER_LOAD0 0x0014 +#define STIMER_ENABLE 0x00000001 +#define GPIO_SWPORT_DR_L 0x0000 +#define GPIO_SWPORT_DDR_L 0x0008 +#define GPIO_INT_EN_L 0x0010 +#define GPIO_INT_MASK_L 0x0018 +#define GPIO_INT_TYPE_L 0x0020 +#define GPIO_INT_POLARITY_L 0x0028 +#define GPIO_INT_STATUS_L 0x0050 +#define GPIO_INT_RAWSTATUS_L 0x0058 +#define GPIO_PORT_EOI_L 0x0060 +#define GPIO_EXT_PORT_L 0x0070 +#define GPIO_VERSION_ID 0x0078 +#define GPIO_V2_ID 0x01000c2b +#define UART_LSR (5 << 2) +#define UART_LSR_THRE 0x20 +#define UART_LSR_TEMT 0x40 +/* Synopsys dw-apb-uart vendor register (sits above the 16550 core window). */ +#define DW_UART_USR 0x7c +#define DW_UART_USR_BUSY 0x1 +#define DW_UART_USR_TFNF 0x2 +#define DW_UART_USR_TFE 0x4 +#define RK3588_GPIO0_QOM "/machine/gpio0" +#define GPIO_PIN0 0x00000001U +#define GPIO_PIN0_WE 0x00010000U +#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) +{ + return qtest_initf("-machine " RK3588_EVB_MACHINE " -smp %u -m 512M", + cpus); +} + +static QTestState *rk3588_qtest_start_zvm_ram(void) +{ + return qtest_init("-machine " RK3588_EVB_MACHINE + ",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); + uint64_t pattern = 0x1122334455667788ULL; + uint32_t gicd_typer; + uint8_t uart_lsr; + + qtest_writeq(qts, RK3588_RAM_BASE, pattern); + g_assert_cmphex(qtest_readq(qts, RK3588_RAM_BASE), ==, pattern); + qtest_writeb(qts, RK3588_IRAM_LOG_BASE, 0x5a); + g_assert_cmphex(qtest_readb(qts, RK3588_IRAM_LOG_BASE), ==, 0x5a); + g_assert_cmphex(qtest_readl(qts, RK3588_PMUSRAM_SKIP_ADDR), ==, + RK3588_AARCH64_RET); + g_assert_cmphex(qtest_readl(qts, RK3588_ATAGS_BASE), ==, + RK3588_ATAG_DDR_MEM_WORDS); + g_assert_cmphex(qtest_readl(qts, RK3588_ATAGS_BASE + 4), ==, + RK3588_ATAG_DDR_MEM); + g_assert_cmphex(qtest_readl(qts, RK3588_ATAGS_BASE + 8), ==, 1); + g_assert_cmphex(qtest_readq(qts, RK3588_ATAGS_BASE + 16), ==, 0); + g_assert_cmphex(qtest_readq(qts, RK3588_ATAGS_BASE + 24), ==, + RK3588_RAM_BASE + RK3588_QTEST_RAM_SIZE); + g_assert_cmphex(qtest_readl(qts, RK3588_ATAGS_BASE + + RK3588_ATAG_DDR_MEM_WORDS * + sizeof(uint32_t)), ==, 0); + + gicd_typer = qtest_readl(qts, RK3588_GICD_BASE + GICD_TYPER); + g_assert_cmpuint(gicd_typer & 0x1f, >=, 0x10); + + uart_lsr = qtest_readb(qts, RK3588_UART2_BASE + UART_LSR); + g_assert_cmphex(uart_lsr & (UART_LSR_THRE | UART_LSR_TEMT), ==, + UART_LSR_THRE | UART_LSR_TEMT); + + /* + * dw-apb-uart USR (vendor range above the 0x20-byte 16550 core window) + * must read BUSY clear and TX FIFO ready so Linux and firmware transmit + * wait loops both terminate. + */ + g_assert_cmphex(qtest_readl(qts, RK3588_UART2_BASE + DW_UART_USR) & + (DW_UART_USR_BUSY | DW_UART_USR_TFNF | DW_UART_USR_TFE), + ==, DW_UART_USR_TFNF | DW_UART_USR_TFE); + + qtest_system_reset(qts); + uart_lsr = qtest_readb(qts, RK3588_UART2_BASE + UART_LSR); + g_assert_cmphex(uart_lsr & (UART_LSR_THRE | UART_LSR_TEMT), ==, + UART_LSR_THRE | UART_LSR_TEMT); + + qtest_quit(qts); +} + +static void test_rk3588_peripheral_mmio(void) +{ + QTestState *qts = rk3588_qtest_start(1); + uint32_t pcie_id; + uint32_t gmac_version; + uint64_t sdhci_caps; + + /* + * Root-port PCI vendor/device id lives in the DBI window (the DWC + * core Type-0 cfg header), not the RK APB vendor window. designware.c + * sets vendor=Synopsys(0x104C-ish) device=0xABCD for the root. The + * 32-bit read at DBI+0 returns DEVICEID(15:0) | VENDORID(31:16) on + * little-endian, so the assembled u32 is non-zero and non-0xffffffff. + */ + 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); + + gmac_version = qtest_readl(qts, RK3588_GMAC0_BASE + DWMAC4_MAC_VERSION); + g_assert_cmphex(gmac_version, ==, DWMAC4_SNPSVER_0x51); + gmac_version = qtest_readl(qts, RK3588_GMAC1_BASE + DWMAC4_MAC_VERSION); + g_assert_cmphex(gmac_version, ==, DWMAC4_SNPSVER_0x51); + + 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); + + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_VERSION_ID), ==, + GPIO_V2_ID); + + qtest_quit(qts); +} + +static void test_rk3588_sdmmc_scmi(void) +{ + QTestState *qts = rk3588_qtest_start(1); + uint32_t ctrl; + + /* + * The SCMI shmem window is a tiny RAM-backed MMIO region. The SMC + * transport itself is covered by guest-boot evidence, but qtest can still + * pin the reset-visible channel state and the shmem read/write contract. + */ + g_assert_cmphex(qtest_readl(qts, RK3588_SCMI_SHMEM_BASE + + SCMI_CHANNEL_STATUS), ==, SCMI_CHANNEL_FREE); + qtest_writel(qts, RK3588_SCMI_SHMEM_BASE + SCMI_MSG_PAYLOAD, + 0x5ca1c10c); + g_assert_cmphex(qtest_readl(qts, RK3588_SCMI_SHMEM_BASE + + SCMI_MSG_PAYLOAD), ==, 0x5ca1c10c); + + /* + * dw_mmc reset-visible contract: recent IP version exposes the 0x100 FIFO + * data port, U-Boot can still use the legacy 0x200 data port, HCON selects + * 32-bit IDMAC, RST_N is deasserted, and no qtest SD card is attached so + * CDETECT bit0 reports absent. + */ + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_VERID), ==, + DW_MMC_VERID_270A); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_HCON), ==, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_RST_N), ==, 1); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_CDETECT) & 1, + ==, 1); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_STATUS) & + DW_MMC_STATUS_FIFO_EMPTY, ==, DW_MMC_STATUS_FIFO_EMPTY); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_FIFOTH), ==, + 0x00800000); + + qtest_writel(qts, RK3588_SDMMC_BASE + DW_MMC_CTRL, + DW_MMC_CTRL_RESET | DW_MMC_FIFO_RESET | DW_MMC_DMA_RESET); + ctrl = qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_CTRL); + g_assert_cmphex(ctrl & (DW_MMC_CTRL_RESET | DW_MMC_FIFO_RESET | + DW_MMC_DMA_RESET), ==, 0); + + qtest_writel(qts, RK3588_SDMMC_BASE + DW_MMC_BMOD, + DW_MMC_BMOD_ENABLE | DW_MMC_BMOD_SWRESET); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_BMOD), ==, + DW_MMC_BMOD_ENABLE); + qtest_writel(qts, RK3588_SDMMC_BASE + DW_MMC_DBADDR, 0x00300000); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_DBADDR), ==, + 0x00300000); + qtest_writel(qts, RK3588_SDMMC_BASE + DW_MMC_IDINTEN, 0x00000303); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_IDINTEN), ==, + 0x00000303); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_IDSTS), ==, 0); + + /* + * A no-response CMD0 sets raw and masked CMD_DONE, then RINTSTS W1C clears + * both raw and masked views. This pins the command side effect and W1C + * status behavior without needing an attached card. + */ + qtest_writel(qts, RK3588_SDMMC_BASE + DW_MMC_CTRL, DW_MMC_INT_ENABLE); + qtest_writel(qts, RK3588_SDMMC_BASE + DW_MMC_INTMASK, + DW_MMC_INT_CMD_DONE); + qtest_writel(qts, RK3588_SDMMC_BASE + DW_MMC_CMD, DW_MMC_CMD_START); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_CMD) & + DW_MMC_CMD_START, ==, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_RINTSTS) & + DW_MMC_INT_CMD_DONE, ==, DW_MMC_INT_CMD_DONE); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_MINTSTS) & + DW_MMC_INT_CMD_DONE, ==, DW_MMC_INT_CMD_DONE); + qtest_writel(qts, RK3588_SDMMC_BASE + DW_MMC_RINTSTS, + DW_MMC_INT_CMD_DONE); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_RINTSTS) & + DW_MMC_INT_CMD_DONE, ==, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_MINTSTS) & + DW_MMC_INT_CMD_DONE, ==, 0); + + qtest_writel(qts, RK3588_SDMMC_BASE + DW_MMC_CDTHRCTL, 0x00100001); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_CDTHRCTL), ==, + 0x00100001); + qtest_writel(qts, RK3588_SDMMC_BASE + DW_MMC_LEGACY_DATA, 0xa5a5a5a5); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + + DW_MMC_LEGACY_DATA), ==, 0); + qtest_writel(qts, RK3588_SDMMC_BASE + DW_MMC_ENABLE_SHIFT, 1); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_ENABLE_SHIFT), + ==, 1); + + /* RK3588 does not use the internal phase registers; they are RAZ/WI. */ + qtest_writel(qts, RK3588_SDMMC_BASE + DW_MMC_RK_TIMING_CON0, 0xffffffff); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + + DW_MMC_RK_TIMING_CON0), ==, 0); + + qtest_system_reset(qts); + g_assert_cmphex(qtest_readl(qts, RK3588_SCMI_SHMEM_BASE + + SCMI_CHANNEL_STATUS), ==, SCMI_CHANNEL_FREE); + g_assert_cmphex(qtest_readl(qts, RK3588_SCMI_SHMEM_BASE + + SCMI_MSG_PAYLOAD), ==, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_VERID), ==, + DW_MMC_VERID_270A); + g_assert_cmphex(qtest_readl(qts, RK3588_SDMMC_BASE + DW_MMC_RINTSTS), ==, + 0); + + qtest_quit(qts); +} + +static void test_rk3588_firmware_registers(void) +{ + QTestState *qts = rk3588_qtest_start(1); + uint16_t vendor_area1; + uint16_t vendor_area2; + uint32_t sys_reg2; + uint32_t dll_status; + + sys_reg2 = qtest_readl(qts, RK3588_PMU1_GRF_BASE + PMU1_GRF_OS_REG2); + g_assert_cmphex(sys_reg2, !=, 0); + 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_readq(qts, RK3588_ATF_DDR_GLOBAL_PTR), ==, + RK3588_ATF_DDR_DESCRIPTOR); + g_assert_cmphex(qtest_readq(qts, RK3588_ATF_TIMER_PTR), ==, + RK3588_ATF_TIMER_TABLE); + g_assert_cmphex(qtest_readq(qts, RK3588_ATF_TIMER_TABLE), ==, + RK3588_ATF_TIMER_COUNTER); + g_assert_cmphex(qtest_readl(qts, RK3588_ATF_TIMER_TABLE + 0x8), ==, + 1000000); + g_assert_cmphex(qtest_readl(qts, RK3588_ATF_TIMER_TABLE + 0xc), ==, + RK3588_GTIMER_HZ); + g_assert_cmphex(qtest_readq(qts, RK3588_ATF_DDR_DESCRIPTOR), ==, + RK3588_ATF_DDR_GLOBAL_BASE); + g_assert_cmphex(qtest_readq(qts, RK3588_ATF_DDR_DESCRIPTOR + 0x20), ==, + RK3588_ATF_DDR_CHANNEL_TABLE); + g_assert_cmphex(qtest_readq(qts, RK3588_ATF_DDR_DESCRIPTOR + + RK3588_ATF_DDR_CHANNEL_TABLE_OFFSET), ==, + RK3588_ATF_DDR_CHANNEL0_BASE); + qtest_writeq(qts, RK3588_ATF_DDR_DESCRIPTOR, UINT64_MAX); + g_assert_cmphex(qtest_readq(qts, RK3588_ATF_DDR_DESCRIPTOR), ==, + RK3588_ATF_DDR_GLOBAL_BASE); + qtest_writel(qts, RK3588_FIRMWARE_SCRATCH, 0xa5a5f00d); + g_assert_cmphex(qtest_readl(qts, RK3588_FIRMWARE_SCRATCH), ==, + 0xa5a5f00d); + qtest_writel(qts, RK3588_ATF_DDR_GLOBAL_BASE, UINT32_MAX); + g_assert_cmphex(qtest_readl(qts, RK3588_ATF_DDR_GLOBAL_BASE) & + RK3588_ATF_DDR_CTRL_BUSY_MASK, ==, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_ATF_DDR_GLOBAL_BASE + 4), ==, 0); + qtest_writel(qts, RK3588_ATF_DDR_CHANNEL0_BASE + + RK3588_ATF_DDR_STATUS_HANDSHAKE, + RK3588_ATF_DDR_STATUS_REQUEST); + g_assert_cmphex(qtest_readl(qts, RK3588_ATF_DDR_CHANNEL0_BASE + + RK3588_ATF_DDR_STATUS_HANDSHAKE) & + RK3588_ATF_DDR_STATUS_ACK, ==, + RK3588_ATF_DDR_STATUS_ACK); + qtest_writel(qts, RK3588_ATF_DDR_CHANNEL0_BASE + + RK3588_ATF_DDR_STATUS_HANDSHAKE, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_ATF_DDR_CHANNEL0_BASE + + RK3588_ATF_DDR_STATUS_HANDSHAKE) & + RK3588_ATF_DDR_STATUS_ACK, ==, 0); + + g_assert_cmphex(qtest_readl(qts, RK3588_FIRMWARE_DEFAULT_REG), ==, + UINT32_MAX); + qtest_writeb(qts, RK3588_FIRMWARE_DEFAULT_REG, 0xa5); + g_assert_cmphex(qtest_readb(qts, RK3588_FIRMWARE_DEFAULT_REG), ==, 0xa5); + g_assert_cmphex(qtest_readl(qts, RK3588_DDR_CHANNEL_STATUS) & 0x7, ==, + RK3588_DDR_CHANNEL_STATUS_READY); + qtest_writel(qts, RK3588_DDR_CHANNEL_CMD, RK3588_DDR_CHANNEL_CMD_START); + g_assert_cmphex(qtest_readl(qts, RK3588_DDR_CHANNEL_CMD) & + RK3588_DDR_CHANNEL_CMD_START, ==, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_DDR_CHANNEL_BUSY) & + RK3588_DDR_CHANNEL_BUSY_FLAG, ==, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_DDR_CHANNEL_GATE_STATUS) & + RK3588_DDR_CHANNEL_GATE_BUSY, ==, 0); + qtest_writel(qts, RK3588_DDR_CHANNEL_GATE_CTRL, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_DDR_CHANNEL_GATE_STATUS) & + RK3588_DDR_CHANNEL_GATE_BUSY, ==, 0); + qtest_writel(qts, RK3588_DDR_CHANNEL_GATE_CMD, + ~RK3588_DDR_CHANNEL_GATE_ENABLE); + g_assert_cmphex(qtest_readl(qts, RK3588_DDR_CHANNEL_GATE_STATUS) & + RK3588_DDR_CHANNEL_GATE_BUSY, ==, + RK3588_DDR_CHANNEL_GATE_BUSY); + qtest_writel(qts, RK3588_DDR_CHANNEL_GATE_CMD, UINT32_MAX); + g_assert_cmphex(qtest_readl(qts, RK3588_DDR_CHANNEL_GATE_STATUS) & + RK3588_DDR_CHANNEL_GATE_BUSY, ==, 0); + qtest_writel(qts, RK3588_DDR_PHY_GATE_CTRL, ~RK3588_DDR_PHY_GATE_ENABLE); + g_assert_cmphex(qtest_readl(qts, RK3588_DDR_CHANNEL_GATE_STATUS) & + RK3588_DDR_CHANNEL_GATE_BUSY, ==, + RK3588_DDR_CHANNEL_GATE_BUSY); + g_assert_cmphex(qtest_readl(qts, RK3588_DDR_CHANNEL_PHY_STATUS) & + RK3588_DDR_CHANNEL_PHY_BUSY, ==, 0); + qtest_writel(qts, RK3588_DDRPHY_CTRL, 0x00030003); + g_assert_cmphex(qtest_readl(qts, RK3588_DDRPHY_STATUS) & + RK3588_DDRPHY_STATUS_ACTIVE, ==, + RK3588_DDRPHY_STATUS_ACTIVE); + qtest_writel(qts, RK3588_DDRPHY_CTRL, 0x00030000); + g_assert_cmphex(qtest_readl(qts, RK3588_DDRPHY_STATUS) & + RK3588_DDRPHY_STATUS_ACTIVE, ==, 0); + + qtest_writel(qts, RK3588_PMU1_GRF_BASE, ROCKCHIP_HIWORD_PIN0_SET); + g_assert_cmphex(qtest_readl(qts, RK3588_PMU1_GRF_BASE), ==, 1); + + qtest_writel(qts, RK3588_STIMER_BASE + STIMER_LOAD0, UINT32_MAX); + qtest_writel(qts, RK3588_STIMER_BASE + STIMER_CTRL, STIMER_ENABLE); + g_assert_cmphex(qtest_readl(qts, RK3588_STIMER_BASE + STIMER_CTRL), ==, + STIMER_ENABLE); + + vendor_area1 = qtest_readw(qts, RK3588_SDHCI_BASE + SDHCI_VENDOR_AREA1); + vendor_area2 = qtest_readw(qts, RK3588_SDHCI_BASE + + SDHCI_VENDOR_AREA1 + 2); + g_assert_cmphex(vendor_area1, ==, DWCMSHC_VENDOR_AREA1); + g_assert_cmphex(vendor_area2, ==, DWCMSHC_VENDOR_AREA2); + + dll_status = qtest_readl(qts, RK3588_SDHCI_BASE + + DWCMSHC_EMMC_DLL_STATUS0); + g_assert_cmphex(dll_status & DWCMSHC_EMMC_DLL_LOCKED, ==, + DWCMSHC_EMMC_DLL_LOCKED); + + qtest_quit(qts); +} + +static void rk3588_assert_usb2_ehci(QTestState *qts, uint64_t base) +{ + uint32_t capbase = qtest_readl(qts, base + USB2_EHCI_CAPBASE); + uint32_t hcsparams = qtest_readl(qts, base + USB2_EHCI_HCSPARAMS); + + g_assert_cmphex(capbase & 0xff, ==, USB2_EHCI_CAPLENGTH); + g_assert_cmphex(capbase >> 16, ==, USB2_EHCI_VERSION_1_0); + g_assert_cmphex(hcsparams & 0xf, ==, USB2_EHCI_NPORTS); + g_assert_cmphex(qtest_readl(qts, base + USB2_EHCI_USBSTS) & + USB2_EHCI_STS_HALT, ==, USB2_EHCI_STS_HALT); + g_assert_cmphex(qtest_readl(qts, base + USB2_EHCI_PORTSC0) & + USB2_EHCI_PORT_POWER, ==, USB2_EHCI_PORT_POWER); + + qtest_writel(qts, base + USB2_EHCI_USBCMD, USB2_EHCI_CMD_RESET); + g_assert_cmphex(qtest_readl(qts, base + USB2_EHCI_USBCMD) & + USB2_EHCI_CMD_RESET, ==, 0); + g_assert_cmphex(qtest_readl(qts, base + USB2_EHCI_USBSTS) & + USB2_EHCI_STS_HALT, ==, USB2_EHCI_STS_HALT); + + qtest_writel(qts, base + USB2_EHCI_USBCMD, USB2_EHCI_CMD_RUN); + g_assert_cmphex(qtest_readl(qts, base + USB2_EHCI_USBCMD) & + USB2_EHCI_CMD_RUN, ==, USB2_EHCI_CMD_RUN); + g_assert_cmphex(qtest_readl(qts, base + USB2_EHCI_USBSTS) & + USB2_EHCI_STS_HALT, ==, 0); +} + +static void rk3588_assert_usb2_ohci(QTestState *qts, uint64_t base) +{ + uint32_t roothub_a; + + g_assert_cmphex(qtest_readl(qts, base + USB2_OHCI_REVISION) & 0xff, ==, + USB2_OHCI_REVISION_1_0); + roothub_a = qtest_readl(qts, base + USB2_OHCI_ROOTHUB_A); + g_assert_cmphex(roothub_a & USB2_OHCI_RH_A_NDP_MASK, ==, + USB2_OHCI_RH_A_NDP1); + g_assert_cmphex(roothub_a & (USB2_OHCI_RH_A_NPS | USB2_OHCI_RH_A_NOCP), + ==, USB2_OHCI_RH_A_NPS | USB2_OHCI_RH_A_NOCP); + g_assert_cmphex(qtest_readl(qts, base + USB2_OHCI_PORTSTATUS0) & + USB2_OHCI_RH_PS_PPS, ==, USB2_OHCI_RH_PS_PPS); + + qtest_writel(qts, base + USB2_OHCI_CMDSTATUS, USB2_OHCI_HCR); + g_assert_cmphex(qtest_readl(qts, base + USB2_OHCI_CMDSTATUS) & + USB2_OHCI_HCR, ==, 0); +} + +static void test_rk3588_usb2_host_firmware_windows(void) +{ + QTestState *qts = rk3588_qtest_start(1); + + rk3588_assert_usb2_ehci(qts, RK3588_USB2_HOST0_EHCI_BASE); + rk3588_assert_usb2_ohci(qts, RK3588_USB2_HOST0_OHCI_BASE); + rk3588_assert_usb2_ehci(qts, RK3588_USB2_HOST1_EHCI_BASE); + rk3588_assert_usb2_ohci(qts, RK3588_USB2_HOST1_OHCI_BASE); + + qtest_system_reset(qts); + rk3588_assert_usb2_ehci(qts, RK3588_USB2_HOST0_EHCI_BASE); + rk3588_assert_usb2_ohci(qts, RK3588_USB2_HOST0_OHCI_BASE); + + qtest_quit(qts); +} + +static void test_rk3588_its_lpi(void) +{ + QTestState *qts = rk3588_qtest_start(1); + uint64_t typer; + + g_assert_cmphex(qtest_readq(qts, RK3588_GICR_BASE + GICR_TYPER) & + GICR_TYPER_PLPIS, ==, GICR_TYPER_PLPIS); + + g_assert_cmphex(qtest_readl(qts, RK3588_GIC_ITS0_BASE + GITS_CTLR), ==, + GITS_CTLR_QUIESCENT); + g_assert_cmphex(qtest_readl(qts, RK3588_GIC_ITS1_BASE + GITS_CTLR), ==, + GITS_CTLR_QUIESCENT); + + g_assert_cmphex(qtest_readl(qts, RK3588_GIC_ITS0_BASE + GITS_PIDR0), ==, + GITS_PIDR0_ITS); + g_assert_cmphex(qtest_readl(qts, RK3588_GIC_ITS1_BASE + GITS_PIDR0), ==, + GITS_PIDR0_ITS); + g_assert_cmphex(qtest_readl(qts, RK3588_GIC_ITS0_BASE + GITS_PIDR2), ==, + GITS_PIDR2_GICV3); + g_assert_cmphex(qtest_readl(qts, RK3588_GIC_ITS1_BASE + GITS_PIDR2), ==, + GITS_PIDR2_GICV3); + + typer = qtest_readq(qts, RK3588_GIC_ITS1_BASE + GITS_TYPER); + g_assert_cmphex(typer & GITS_TYPER_PHYSICAL, ==, GITS_TYPER_PHYSICAL); + g_assert_cmphex(typer & GITS_TYPER_IDBITS_MASK, ==, + GITS_TYPER_IDBITS_15); + g_assert_cmphex(typer & GITS_TYPER_DEVBITS_MASK, ==, + GITS_TYPER_DEVBITS_15); + g_assert_cmphex(typer & GITS_TYPER_CIDBITS_MASK, ==, + GITS_TYPER_CIDBITS_15); + g_assert_cmphex(typer & GITS_TYPER_CIL, ==, GITS_TYPER_CIL); + + qtest_quit(qts); +} + +/* + * RK3588 PCIe-specific qtest coverage. + * + * - APB LTSSM_STATUS pinned to 0x00030011 (substitution policy: + * bits 17:16 = 0b11 -> link up; bits 5:0 = 0x11 -> LTSSM L0). + * rockchip_pcie_link_up returns true on the first poll so + * dw_pcie_wait_for_link terminates immediately. + * + * - DBI root-port vendor/device id is readable through the inherited + * designware 4 KiB sysbus mmio (DWC core Type-0 cfg header). + * + * - APB vendor writes (PCIE_CLIENT_*) are dropped without aborting + * (D-15: AArch64 guest writes to unassigned MMIO abort). The driver + * writes GENERAL_CON, HOT_RESET_CTRL, INTR_MASK_LEGACY, POWER_CON. + * + * - CRU PLL lock status at 0x600 reads 0xffffffff so clk-rk3588 + * early init sees all PLLs locked. + */ +static void test_rk3588_pcie(void) +{ + QTestState *qts = rk3588_qtest_start(1); + uint32_t ltssm; + uint32_t pcie_id; + + /* APB LTSSM pinned link-up. */ + ltssm = qtest_readl(qts, RK3588_PCIE3X4_APB_BASE + DWC_PCIE_LTSSM_STATUS); + g_assert_cmphex(ltssm, ==, DWC_PCIE_LTSSM_LINK_UP); + + /* APB PCIE_CLIENT_* writes are accepted without aborting. */ + qtest_writel(qts, RK3588_PCIE3X4_APB_BASE + 0x000, 0xffff0004); + qtest_writel(qts, RK3588_PCIE3X4_APB_BASE + 0x180, 0xffff0010); + qtest_writel(qts, RK3588_PCIE3X4_APB_BASE + 0x01c, 0x000f000f); + g_assert_cmphex(qtest_readl(qts, RK3588_PCIE3X4_APB_BASE + 0x000), ==, 0); + + /* DBI root-port vendor/device id is readable. */ + 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); + + /* DBI writes (iATU viewport select) land without aborting. */ + qtest_writel(qts, RK3588_PCIE3X4_DBI_BASE + 0x900, 0x00000000); + qtest_writel(qts, RK3588_PCIE3X4_DBI_BASE + 0x904, 0x00000004); + + /* + * CRU PLL lock status MUST read 0xffffffff - clk-rk3588 PLL init + * polls this bit very early; 0 here hangs boot before the console. + */ + g_assert_cmphex(qtest_readl(qts, RK3588_CRU_BASE + 0x600), ==, + 0xffffffff); + g_assert_cmphex(qtest_readl(qts, RK3588_CRU_SPL_PLL_STATUS) & + RK3588_CRU_SPL_PLL_LOCKED, ==, + RK3588_CRU_SPL_PLL_LOCKED); + /* Other CRU offsets are RAZ. */ + g_assert_cmphex(qtest_readl(qts, RK3588_CRU_BASE + 0xa80), ==, 0); + + qtest_quit(qts); +} + +static void test_rk3588_gpio_bank(void) +{ + QTestState *qts = rk3588_qtest_start(1); + + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_VERSION_ID), ==, + GPIO_V2_ID); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_EXT_PORT_L) & + GPIO_PIN0, ==, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_INT_STATUS_L) & + GPIO_PIN0, ==, 0); + + qtest_writel(qts, RK3588_GPIO0_BASE + GPIO_SWPORT_DDR_L, GPIO_PIN0_SET); + qtest_writel(qts, RK3588_GPIO0_BASE + GPIO_SWPORT_DR_L, GPIO_PIN0_SET); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_EXT_PORT_L) & + GPIO_PIN0, ==, GPIO_PIN0); + + qtest_writel(qts, RK3588_GPIO0_BASE + GPIO_SWPORT_DR_L, GPIO_PIN0_CLEAR); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_EXT_PORT_L) & + GPIO_PIN0, ==, 0); + + qtest_writel(qts, RK3588_GPIO0_BASE + GPIO_SWPORT_DDR_L, GPIO_PIN0_CLEAR); + qtest_set_irq_in(qts, RK3588_GPIO0_QOM, "unnamed-gpio-in", 0, 1); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_EXT_PORT_L) & + GPIO_PIN0, ==, GPIO_PIN0); + + qtest_writel(qts, RK3588_GPIO0_BASE + GPIO_INT_POLARITY_L, + GPIO_PIN0_SET); + qtest_writel(qts, RK3588_GPIO0_BASE + GPIO_INT_MASK_L, GPIO_PIN0_SET); + qtest_writel(qts, RK3588_GPIO0_BASE + GPIO_INT_EN_L, GPIO_PIN0_SET); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_INT_RAWSTATUS_L) & + GPIO_PIN0, ==, GPIO_PIN0); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_INT_STATUS_L) & + GPIO_PIN0, ==, 0); + + qtest_writel(qts, RK3588_GPIO0_BASE + GPIO_INT_MASK_L, GPIO_PIN0_CLEAR); + qtest_writel(qts, RK3588_GPIO0_BASE + GPIO_SWPORT_DDR_L, GPIO_PIN0_SET); + qtest_writel(qts, RK3588_GPIO0_BASE + GPIO_SWPORT_DR_L, GPIO_PIN0_SET); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_EXT_PORT_L) & + GPIO_PIN0, ==, GPIO_PIN0); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_INT_STATUS_L) & + GPIO_PIN0, ==, 0); + + qtest_writel(qts, RK3588_GPIO0_BASE + GPIO_SWPORT_DR_L, GPIO_PIN0_CLEAR); + qtest_writel(qts, RK3588_GPIO0_BASE + GPIO_SWPORT_DDR_L, GPIO_PIN0_CLEAR); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_INT_STATUS_L) & + GPIO_PIN0, ==, GPIO_PIN0); + + qtest_writel(qts, RK3588_GPIO0_BASE + GPIO_INT_EN_L, GPIO_PIN0_CLEAR); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_INT_STATUS_L) & + GPIO_PIN0, ==, 0); + + qtest_writel(qts, RK3588_GPIO0_BASE + GPIO_INT_EN_L, GPIO_PIN0_SET); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_INT_STATUS_L) & + GPIO_PIN0, ==, GPIO_PIN0); + + qtest_writel(qts, RK3588_GPIO0_BASE + GPIO_PORT_EOI_L, GPIO_PIN0_SET); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_INT_STATUS_L) & + GPIO_PIN0, ==, GPIO_PIN0); + + qtest_set_irq_in(qts, RK3588_GPIO0_QOM, "unnamed-gpio-in", 0, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_INT_STATUS_L) & + GPIO_PIN0, ==, 0); + + qtest_writel(qts, RK3588_GPIO0_BASE + GPIO_PORT_EOI_L, GPIO_PIN0_SET); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_INT_STATUS_L) & + GPIO_PIN0, ==, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_EXT_PORT_L) & + GPIO_PIN0, ==, 0); + + qtest_system_reset(qts); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_VERSION_ID), ==, + GPIO_V2_ID); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_EXT_PORT_L) & + GPIO_PIN0, ==, 0); + g_assert_cmphex(qtest_readl(qts, RK3588_GPIO0_BASE + GPIO_INT_STATUS_L) & + GPIO_PIN0, ==, 0); + + qtest_quit(qts); +} + +static void test_rk3588_smp_creation(void) +{ + QTestState *qts = rk3588_qtest_start(8); + + qtest_quit(qts); +} + +static void test_rk3588_zvm_ram(void) +{ + QTestState *qts = rk3588_qtest_start_zvm_ram(); + + qtest_writel(qts, RK3588_ZVM_LOW_RAM_BASE, 0x68000000); + g_assert_cmphex(qtest_readl(qts, RK3588_ZVM_LOW_RAM_BASE), ==, + 0x68000000); + + qtest_writel(qts, RK3588_ZVM_SHARED_RAM_BASE + 0x408, 0xe7f00408); + g_assert_cmphex(qtest_readl(qts, RK3588_ZVM_SHARED_RAM_BASE + 0x408), ==, + 0xe7f00408); + + qtest_writel(qts, RK3588_ZVM_NOTIFY_INFO_BASE, 0xefd00000); + g_assert_cmphex(qtest_readl(qts, RK3588_ZVM_NOTIFY_INFO_BASE), ==, + 0xefd00000); + + qtest_writel(qts, RK3588_ZVM_NOTIFY_BASE, 0xefe00000); + g_assert_cmphex(qtest_readl(qts, RK3588_ZVM_NOTIFY_BASE), ==, + 0xefe00000); + + qtest_writeq(qts, RK3588_ZVM_HIGH_RAM_BASE, 0x100000000ULL); + g_assert_cmphex(qtest_readq(qts, RK3588_ZVM_HIGH_RAM_BASE), ==, + 0x100000000ULL); + + qtest_writeb(qts, RK3588_ZVM_HIGH_RAM_LAST, 0x5a); + g_assert_cmphex(qtest_readb(qts, RK3588_ZVM_HIGH_RAM_LAST), ==, 0x5a); + + 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); + + if (!qtest_has_machine(RK3588_EVB_MACHINE)) { + 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; + } + if (!qtest_has_device("rockchip-gpio")) { + g_test_skip("rockchip-gpio device not available"); + return 0; + } + if (!qtest_has_device("arm-gicv3-its")) { + g_test_skip("arm-gicv3-its device not available"); + 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", + test_rk3588_firmware_registers); + qtest_add_func("/rk3588/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); + + return g_test_run(); +} diff --git a/tests/qtest/s32k5-test.c b/tests/qtest/s32k5-test.c new file mode 100644 index 0000000000000..c0b5649e7230b --- /dev/null +++ b/tests/qtest/s32k5-test.c @@ -0,0 +1,231 @@ +/* + * QTest for the NXP S32K5 board machine model + * + * Copyright (c) 2026 Process Mission + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qobject/qdict.h" +#include "qobject/qlist.h" + +#define S32K5_MACHINE "s32k566-cvb-r52" + +#define S32K5_MC_ME_BASE 0x40498000ULL +#define S32K5_LPUART0_BASE 0x400e0000ULL +#define S32K5_ARCH_TIMER_FREQ 4000000ULL + +#define MC_ME_CTL_KEY 0x000 +#define MC_ME_MODE_CONF 0x004 +#define MC_ME_MODE_UPD 0x008 +#define MC_ME_MODE_STAT 0x00c +#define MC_ME_PRTN2_PUPD 0x504 +#define MC_ME_PRTN2_COFB1_STAT 0x514 +#define MC_ME_PRTN2_COFB1_CLKEN 0x534 + +#define MC_ME_CTL_KEY_DIRECT 0x5af0 +#define MC_ME_CTL_KEY_INVERTED 0xa50f +#define MC_ME_MODE_UPD_MODE_UPD (1U << 0) +#define MC_ME_PRTN2_PUPD_PCUD (1U << 0) +#define MC_ME_SWT_STARTUP_REQ (1U << 10) + +#define LPUART_VERID 0x00 +#define LPUART_BAUD 0x10 +#define LPUART_STAT 0x14 +#define LPUART_CTRL 0x18 +#define LPUART_DATA 0x1c +#define LPUART_FIFO 0x28 + +#define LPUART_STAT_TC (1U << 22) +#define LPUART_STAT_TDRE (1U << 23) +#define LPUART_CTRL_TIE (1U << 23) +#define LPUART_FIFO_RXEMPT (1U << 22) +#define LPUART_FIFO_TXEMPT (1U << 23) + +#define LPUART_STAT_TX_READY (LPUART_STAT_TDRE | LPUART_STAT_TC) +#define LPUART_FIFO_EMPTY (LPUART_FIFO_TXEMPT | LPUART_FIFO_RXEMPT) + +static QTestState *s32k5_qtest_start(void) +{ + return qtest_init("-machine " S32K5_MACHINE " -serial null"); +} + +static char *s32k5_get_first_cpu_path(QTestState *qts) +{ + QDict *resp; + QList *cpus; + QObject *cpu_obj; + const QDict *cpu; + char *path; + + resp = qtest_qmp(qts, "{ 'execute': 'query-cpus-fast' }"); + g_assert(qdict_haskey(resp, "return")); + cpus = qdict_get_qlist(resp, "return"); + g_assert(cpus); + + cpu_obj = qlist_pop(cpus); + g_assert(cpu_obj); + cpu = qobject_to(QDict, cpu_obj); + g_assert(qdict_haskey(cpu, "qom-path")); + path = g_strdup(qdict_get_str(cpu, "qom-path")); + + qobject_unref(cpu_obj); + qobject_unref(resp); + + return path; +} + +static uint64_t s32k5_qom_get_uint(QTestState *qts, const char *path, + const char *property) +{ + QDict *resp; + uint64_t value; + + resp = qtest_qmp(qts, "{ 'execute': 'qom-get', 'arguments': " + "{ 'path': %s, 'property': %s } }", + path, property); + g_assert(qdict_haskey(resp, "return")); + value = qdict_get_uint(resp, "return"); + qobject_unref(resp); + + return value; +} + +static uint32_t mc_me_readl(QTestState *qts, uint64_t offset) +{ + return qtest_readl(qts, S32K5_MC_ME_BASE + offset); +} + +static void mc_me_writel(QTestState *qts, uint64_t offset, uint32_t value) +{ + qtest_writel(qts, S32K5_MC_ME_BASE + offset, value); +} + +static uint32_t lpuart_readl(QTestState *qts, uint64_t offset) +{ + return qtest_readl(qts, S32K5_LPUART0_BASE + offset); +} + +static void lpuart_writel(QTestState *qts, uint64_t offset, uint32_t value) +{ + qtest_writel(qts, S32K5_LPUART0_BASE + offset, value); +} + +static void test_machine_constructs(void) +{ + QTestState *qts = s32k5_qtest_start(); + + qtest_quit(qts); +} + +static void test_cpu_timer_frequency(void) +{ + QTestState *qts = s32k5_qtest_start(); + char *cpu_path = s32k5_get_first_cpu_path(qts); + + g_assert_cmpuint(s32k5_qom_get_uint(qts, cpu_path, "cntfrq"), ==, + S32K5_ARCH_TIMER_FREQ); + + g_free(cpu_path); + qtest_quit(qts); +} + +static void test_mc_me_partition_update(void) +{ + QTestState *qts = s32k5_qtest_start(); + + mc_me_writel(qts, MC_ME_PRTN2_COFB1_CLKEN, MC_ME_SWT_STARTUP_REQ); + mc_me_writel(qts, MC_ME_PRTN2_PUPD, MC_ME_PRTN2_PUPD_PCUD); + g_assert_cmphex(mc_me_readl(qts, MC_ME_PRTN2_PUPD), ==, + MC_ME_PRTN2_PUPD_PCUD); + g_assert_cmphex(mc_me_readl(qts, MC_ME_PRTN2_COFB1_STAT), ==, 0); + + mc_me_writel(qts, MC_ME_CTL_KEY, MC_ME_CTL_KEY_DIRECT); + g_assert_cmphex(mc_me_readl(qts, MC_ME_PRTN2_PUPD), ==, + MC_ME_PRTN2_PUPD_PCUD); + + mc_me_writel(qts, MC_ME_CTL_KEY, MC_ME_CTL_KEY_INVERTED); + g_assert_cmphex(mc_me_readl(qts, MC_ME_PRTN2_PUPD), ==, 0); + g_assert_cmphex(mc_me_readl(qts, MC_ME_PRTN2_COFB1_STAT), ==, + MC_ME_SWT_STARTUP_REQ); + + qtest_quit(qts); +} + +static void test_mc_me_mode_update(void) +{ + QTestState *qts = s32k5_qtest_start(); + + mc_me_writel(qts, MC_ME_MODE_CONF, 1); + mc_me_writel(qts, MC_ME_MODE_UPD, MC_ME_MODE_UPD_MODE_UPD); + g_assert_cmphex(mc_me_readl(qts, MC_ME_MODE_UPD), ==, + MC_ME_MODE_UPD_MODE_UPD); + + mc_me_writel(qts, MC_ME_CTL_KEY, MC_ME_CTL_KEY_DIRECT); + mc_me_writel(qts, MC_ME_CTL_KEY, MC_ME_CTL_KEY_INVERTED); + g_assert_cmphex(mc_me_readl(qts, MC_ME_MODE_UPD), ==, 0); + g_assert_cmphex(mc_me_readl(qts, MC_ME_MODE_STAT), ==, 1); + + qtest_quit(qts); +} + +static void test_lpuart_reset_values(void) +{ + QTestState *qts = s32k5_qtest_start(); + + g_assert_cmphex(lpuart_readl(qts, LPUART_VERID), ==, 0x04010003); + g_assert_cmphex(lpuart_readl(qts, LPUART_BAUD), ==, 0x0f000004); + g_assert_cmphex(lpuart_readl(qts, LPUART_STAT) & LPUART_STAT_TX_READY, + ==, LPUART_STAT_TX_READY); + g_assert_cmphex(lpuart_readl(qts, LPUART_FIFO) & LPUART_FIFO_EMPTY, + ==, LPUART_FIFO_EMPTY); + + qtest_quit(qts); +} + +static void test_lpuart_read_only_bits(void) +{ + QTestState *qts = s32k5_qtest_start(); + + lpuart_writel(qts, LPUART_VERID, 0); + g_assert_cmphex(lpuart_readl(qts, LPUART_VERID), ==, 0x04010003); + + lpuart_writel(qts, LPUART_STAT, 0); + g_assert_cmphex(lpuart_readl(qts, LPUART_STAT) & LPUART_STAT_TX_READY, + ==, LPUART_STAT_TX_READY); + + qtest_quit(qts); +} + +static void test_lpuart_data_write_sets_tx_ready(void) +{ + QTestState *qts = s32k5_qtest_start(); + + lpuart_writel(qts, LPUART_CTRL, LPUART_CTRL_TIE); + g_assert_cmphex(lpuart_readl(qts, LPUART_CTRL), ==, LPUART_CTRL_TIE); + + lpuart_writel(qts, LPUART_DATA, 'A'); + g_assert_cmphex(lpuart_readl(qts, LPUART_STAT) & LPUART_STAT_TX_READY, + ==, LPUART_STAT_TX_READY); + + qtest_quit(qts); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/s32k5/machine/constructs", test_machine_constructs); + qtest_add_func("/s32k5/cpu/timer-frequency", test_cpu_timer_frequency); + qtest_add_func("/s32k5/mc-me/partition-update", + test_mc_me_partition_update); + qtest_add_func("/s32k5/mc-me/mode-update", test_mc_me_mode_update); + qtest_add_func("/s32k5/lpuart/reset", test_lpuart_reset_values); + qtest_add_func("/s32k5/lpuart/read-only", test_lpuart_read_only_bits); + qtest_add_func("/s32k5/lpuart/data-write", + test_lpuart_data_write_sets_tx_ready); + + return g_test_run(); +} diff --git a/tests/qtest/spacemit-k3-test.c b/tests/qtest/spacemit-k3-test.c new file mode 100644 index 0000000000000..68bfa9900d625 --- /dev/null +++ b/tests/qtest/spacemit-k3-test.c @@ -0,0 +1,782 @@ +/* + * QTest testcase for the SpacemiT K3 Pico-ITX machine + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include +#include +#include "qemu/bswap.h" +#include "qemu/units.h" +#include "libqtest.h" +#include "qobject/qdict.h" +#include "qobject/qlist.h" + +#define K3_NUM_HARTS 8 +#define K3_HARTS_PER_CLUSTER 4 +#define K3_TIMEBASE_FREQ 24000000 + +#define K3_SRAM_BASE UINT64_C(0xc0800000) +#define K3_SRAM_SIZE UINT64_C(0x80000) +#define K3_DDR_TRAINING_BASE UINT64_C(0xc08d0000) +#define K3_DDR_TRAINING_SIZE UINT64_C(0x100) +#define K3_UART0_BASE UINT64_C(0xd4017000) +#define K3_UART0_SIZE UINT64_C(0x100) +#define K3_SDHCI0_BASE UINT64_C(0xd4280000) +#define K3_APMU_BASE UINT64_C(0xd4282800) +#define K3_CIU_BASE UINT64_C(0xd4282c00) +#define K3_S_IMSIC_BASE UINT64_C(0xe0400000) +#define K3_S_IMSIC_HART_STRIDE UINT64_C(0x40000) +#define K3_S_APLIC_BASE UINT64_C(0xe0804000) +#define K3_M_IMSIC_BASE UINT64_C(0xf1000000) +#define K3_M_IMSIC_HART_STRIDE UINT64_C(0x1000) +#define K3_M_APLIC_BASE UINT64_C(0xf1800000) +#define K3_M_CLINT_BASE UINT64_C(0xf1810000) +#define K3_FIRMWARE_BASE UINT64_C(0x100000000) +#define K3_FIRMWARE_SIZE UINT64_C(0x2000000) +#define K3_DRAM_BASE UINT64_C(0x102000000) +#define K3_DRAM_SIZE UINT64_C(0x80000000) + +#define ACLINT_MTIME UINT64_C(0xbff8) + +#define APLIC_DOMAINCFG 0x0000 +#define APLIC_DOMAINCFG_IE (1U << 8) +#define APLIC_DOMAINCFG_DM (1U << 2) +#define APLIC_DOMAINCFG_RDONLY (1U << 31) +#define APLIC_SOURCECFG_BASE 0x0004 +#define APLIC_SOURCECFG_D (1U << 10) +#define APLIC_SOURCECFG_LEVEL_HIGH 0x6 +#define APLIC_MMSICFGADDR 0x1bc0 +#define APLIC_MMSICFGADDRH 0x1bc4 +#define APLIC_SMSICFGADDR 0x1bc8 +#define APLIC_SMSICFGADDRH 0x1bcc +#define APLIC_CLRIP_BASE 0x1d00 +#define APLIC_SETIENUM 0x1edc +#define APLIC_TARGET_BASE 0x3004 +#define APLIC_LHXS_SHIFT 20 +#define APLIC_LHXW_SHIFT 12 + +#define K3_UART0_IRQ 42 +#define K3_SDHCI0_IRQ 99 +#define UART_RBR 0x00 +#define UART_IER 0x04 +#define UART_LSR 0x14 +#define UART_IER_RDI 0x01 +#define UART_LSR_DR 0x01 + +#define K3_FDT_MAX_SIZE (64 * KiB) +#define K3_APMU_SDH0_CTRL 0x54 +#define K3_APMU_SDH0_RESET 0x119 +#define K3_CIU_BOOT_FLAG 0x110 +#define K3_CIU_BOOT_FROM_SD 0xb10 + +#define SDHCI_BLKSIZE 0x04 +#define SDHCI_BLKCNT 0x06 +#define SDHCI_ARGUMENT 0x08 +#define SDHCI_TRNMOD 0x0c +#define SDHCI_CMDREG 0x0e +#define SDHCI_RSPREG0 0x10 +#define SDHCI_PRNSTS 0x24 +#define SDHCI_HOSTCTL 0x28 +#define SDHCI_PWRCON 0x29 +#define SDHCI_CLKCON 0x2c +#define SDHCI_SWRST 0x2f +#define SDHCI_NORINTSTS 0x30 +#define SDHCI_NORINTSTSEN 0x34 +#define SDHCI_NORINTSIGEN 0x38 +#define SDHCI_ERRINTSIGEN 0x3a +#define SDHCI_HOSTCTL2 0x3e +#define SDHCI_CAPAB 0x40 +#define SDHCI_ADMAERR 0x54 +#define SDHCI_ADMASYSADDR 0x58 +#define SDHCI_HCVER 0xfe +#define K3_SDHCI_MMC_CTRL 0x114 +#define K3_SDHCI_TX_CFG 0x11c + +#define K3_SDHCI_CAPAB UINT64_C(0x112834b4) +#define K3_SDHCI_MMC_CTRL_MASK 0x1700 +#define K3_SDHCI_TX_CFG_MASK 0xc0000000 + +#define SDHCI_CARD_PRESENT (1U << 16) +#define SDHCI_CTRL_ADMA2_64 0x18 +#define SDHCI_POWER_330 0x0f +#define SDHCI_CLOCK_INT_EN 0x01 +#define SDHCI_CLOCK_INT_STABLE 0x02 +#define SDHCI_CLOCK_SDCLK_EN 0x04 +#define SDHCI_RESET_ALL 0x01 + +#define SDHCI_TRNS_DMA 0x0001 +#define SDHCI_TRNS_BLK_CNT_EN 0x0002 +#define SDHCI_TRNS_READ 0x0010 + +#define SDHCI_CMD_RESP_NONE 0x00 +#define SDHCI_CMD_RESP_LONG 0x01 +#define SDHCI_CMD_RESP_SHORT 0x02 +#define SDHCI_CMD_RESP_SHORT_BUSY 0x03 +#define SDHCI_CMD_CRC 0x08 +#define SDHCI_CMD_INDEX 0x10 +#define SDHCI_CMD_DATA 0x20 + +#define SDHCI_NIS_CMDCMP 0x0001 +#define SDHCI_NIS_TRSCMP 0x0002 +#define SDHCI_NIS_ERR 0x8000 + +#define SDHCI_ADMA_VALID 0x01 +#define SDHCI_ADMA_END 0x02 +#define SDHCI_ADMA_TRAN 0x20 + +#define K3_SD_SECTOR_SIZE 512 +#define K3_SD_IMAGE_SIZE (1 * MiB) +#define K3_ADMA_DESC_ADDR UINT64_C(0x102010000) +#define K3_ADMA_BUFFER_ADDR UINT64_C(0x102020000) + +#define CSR_MIP 0x344 +#define CSR_MENVCFG 0x30a +#define CSR_MISELECT 0x350 +#define CSR_MIREG 0x351 +#define CSR_MTOPEI 0x35c +#define CSR_SISELECT 0x150 +#define CSR_SIREG 0x151 +#define CSR_STOPEI 0x15c +#define CSR_STIMECMP 0x14d +#define MIP_STIP (UINT64_C(1) << 5) +#define MIP_SEIP (UINT64_C(1) << 9) +#define MIP_MEIP (UINT64_C(1) << 11) +#define MENVCFG_STCE (UINT64_C(1) << 63) +#define ISELECT_IMSIC_EIDELIVERY 0x70 +#define ISELECT_IMSIC_EIP0 0x80 +#define ISELECT_IMSIC_EIE0 0xc0 + +static QTestState *k3_qtest_init(void) +{ + return qtest_init("-M k3-pico-itx -bios none -display none " + "-nodefaults"); +} + +static char *k3_create_fdt(const void *compatible, size_t compatible_len) +{ + g_autofree uint8_t *fdt = g_malloc0(K3_FDT_MAX_SIZE); + const fdt32_t memory_reg[] = { + cpu_to_fdt32((uint32_t)(K3_DRAM_BASE >> 32)), + cpu_to_fdt32((uint32_t)K3_DRAM_BASE), + cpu_to_fdt32((uint32_t)(K3_DRAM_SIZE >> 32)), + cpu_to_fdt32((uint32_t)K3_DRAM_SIZE), + }; + g_autoptr(GError) error = NULL; + char *path = g_strdup("spacemit-k3-fdt-XXXXXX"); + int cpus; + int memory; + int fd; + + g_assert_cmpint(fdt_create_empty_tree(fdt, K3_FDT_MAX_SIZE), ==, 0); + g_assert_cmpint(fdt_setprop(fdt, 0, "compatible", compatible, + compatible_len), ==, 0); + + cpus = fdt_add_subnode(fdt, 0, "cpus"); + g_assert_cmpint(cpus, >=, 0); + g_assert_cmpint(fdt_setprop_u32(fdt, cpus, "timebase-frequency", + K3_TIMEBASE_FREQ), ==, 0); + for (unsigned int i = 0; i < K3_NUM_HARTS; i++) { + g_autofree char *name = g_strdup_printf("cpu@%x", i); + + g_assert_cmpint(fdt_add_subnode(fdt, cpus, name), >=, 0); + } + + memory = fdt_add_subnode(fdt, 0, "memory@102000000"); + g_assert_cmpint(memory, >=, 0); + g_assert_cmpint(fdt_setprop(fdt, memory, "reg", memory_reg, + sizeof(memory_reg)), ==, 0); + g_assert_cmpint(fdt_pack(fdt), ==, 0); + + fd = g_mkstemp(path); + g_assert_cmpint(fd, >=, 0); + close(fd); + g_assert_true(g_file_set_contents(path, (const char *)fdt, + fdt_totalsize(fdt), &error)); + g_assert_no_error(error); + + return path; +} + +static void test_fdt_compatible_list(void) +{ + static const char compatible[] = + "spacemit,k3\0spacemit,k3-pico-itx"; + g_autofree char *dtb_path = k3_create_fdt(compatible, + sizeof(compatible)); + QTestState *qts = qtest_initf( + "-M k3-pico-itx -bios none -display none -nodefaults -dtb %s", + dtb_path); + + qtest_quit(qts); + g_assert_cmpint(g_unlink(dtb_path), ==, 0); +} + +static void test_fdt_missing_compatible(void) +{ + static const char compatible[] = "spacemit,k3"; + g_autofree char *dtb_path = k3_create_fdt(compatible, + sizeof(compatible)); + g_autofree char *stderr_buf = NULL; + g_autoptr(GError) error = NULL; + /* Make an unexpected successful DTB validation terminate promptly. */ + const char *argv[] = { + qtest_qemu_binary(NULL), + "-M", "k3-pico-itx", + "-bios", "none", + "-display", "none", + "-nodefaults", + "-dtb", dtb_path, + "-device", "spacemit-k3-test-invalid", + NULL, + }; + int exit_status; + bool spawned; + + spawned = g_spawn_sync(NULL, (char **)argv, NULL, + G_SPAWN_STDOUT_TO_DEV_NULL, NULL, NULL, NULL, + &stderr_buf, &exit_status, &error); + g_assert_true(spawned); + g_assert_no_error(error); + g_assert_false(g_spawn_check_exit_status(exit_status, NULL)); + g_assert_nonnull(strstr(stderr_buf, + "DTB requires root compatible " + "'spacemit,k3-pico-itx'")); + g_assert_cmpint(g_unlink(dtb_path), ==, 0); +} + +static void test_cpu_requires_vector(void) +{ + g_autofree char *stderr_buf = NULL; + g_autoptr(GError) error = NULL; + const char *argv[] = { + qtest_qemu_binary(NULL), + "-M", "k3-pico-itx", + "-cpu", "spacemit-x100,v=false", + "-bios", "none", + "-display", "none", + "-nodefaults", + "-device", "spacemit-k3-test-invalid", + NULL, + }; + int exit_status; + bool spawned; + + spawned = g_spawn_sync(NULL, (char **)argv, NULL, + G_SPAWN_STDOUT_TO_DEV_NULL, NULL, NULL, NULL, + &stderr_buf, &exit_status, &error); + g_assert_true(spawned); + g_assert_no_error(error); + g_assert_false(g_spawn_check_exit_status(exit_status, NULL)); + g_assert_nonnull(strstr(stderr_buf, + "K3 X100 hart 0 requires the V extension")); +} + +static uint64_t k3_qom_get_uint(QTestState *qts, const char *path, + const char *property) +{ + QDict *response = qtest_qmp(qts, + "{ 'execute': 'qom-get', 'arguments': " + "{ 'path': %s, 'property': %s } }", path, property); + uint64_t value; + + g_assert(qdict_haskey(response, "return")); + value = qdict_get_int(response, "return"); + qobject_unref(response); + + return value; +} + +static uint64_t k3_csr_get(QTestState *qts, unsigned int hartid, int csr) +{ + uint64_t value = 0; + + g_assert_cmpuint(qtest_csr_call(qts, "get_csr", hartid, csr, &value), + ==, 0); + return value; +} + +static void k3_csr_set(QTestState *qts, unsigned int hartid, int csr, + uint64_t value) +{ + g_assert_cmpuint(qtest_csr_call(qts, "set_csr", hartid, csr, &value), + ==, 0); +} + +static uint64_t k3_imsic_indirect_read(QTestState *qts, unsigned int hartid, + bool mmode, uint64_t selector) +{ + k3_csr_set(qts, hartid, mmode ? CSR_MISELECT : CSR_SISELECT, selector); + return k3_csr_get(qts, hartid, mmode ? CSR_MIREG : CSR_SIREG); +} + +static void k3_imsic_indirect_write(QTestState *qts, unsigned int hartid, + bool mmode, uint64_t selector, + uint64_t value) +{ + k3_csr_set(qts, hartid, mmode ? CSR_MISELECT : CSR_SISELECT, selector); + k3_csr_set(qts, hartid, mmode ? CSR_MIREG : CSR_SIREG, value); +} + +static void k3_imsic_enable(QTestState *qts, unsigned int hartid, bool mmode, + unsigned int eiid) +{ + g_assert_cmpuint(eiid, <, 64); + k3_imsic_indirect_write(qts, hartid, mmode, + ISELECT_IMSIC_EIDELIVERY, 1); + k3_imsic_indirect_write(qts, hartid, mmode, ISELECT_IMSIC_EIE0, + UINT64_C(1) << eiid); +} + +static void k3_imsic_claim(QTestState *qts, unsigned int hartid, bool mmode, + unsigned int eiid) +{ + int topei_csr = mmode ? CSR_MTOPEI : CSR_STOPEI; + uint64_t topei = k3_csr_get(qts, hartid, topei_csr); + + g_assert_cmpuint(topei >> 16, ==, eiid); + k3_csr_set(qts, hartid, topei_csr, topei); +} + +static void k3_route_s_aplic_irq(QTestState *qts, unsigned int irq, + unsigned int eiid) +{ + const uint64_t sourcecfg = APLIC_SOURCECFG_BASE + (irq - 1) * 4; + const uint64_t target = APLIC_TARGET_BASE + (irq - 1) * 4; + + k3_imsic_enable(qts, 0, false, eiid); + + qtest_writel(qts, K3_M_APLIC_BASE + APLIC_MMSICFGADDR, + K3_M_IMSIC_BASE >> 12); + qtest_writel(qts, K3_M_APLIC_BASE + APLIC_MMSICFGADDRH, + 3U << APLIC_LHXW_SHIFT); + qtest_writel(qts, K3_M_APLIC_BASE + APLIC_SMSICFGADDR, + K3_S_IMSIC_BASE >> 12); + qtest_writel(qts, K3_M_APLIC_BASE + APLIC_SMSICFGADDRH, + 6U << APLIC_LHXS_SHIFT); + + qtest_writel(qts, K3_M_APLIC_BASE + sourcecfg, APLIC_SOURCECFG_D); + qtest_writel(qts, K3_S_APLIC_BASE + sourcecfg, + APLIC_SOURCECFG_LEVEL_HIGH); + qtest_writel(qts, K3_S_APLIC_BASE + target, eiid); + qtest_writel(qts, K3_S_APLIC_BASE + APLIC_SETIENUM, irq); + qtest_writel(qts, K3_S_APLIC_BASE + APLIC_DOMAINCFG, + APLIC_DOMAINCFG_IE); +} + +static void test_topology(void) +{ + QTestState *qts = k3_qtest_init(); + QDict *response; + QList *cpus; + QListEntry *entry; + bool seen[K3_NUM_HARTS] = { false }; + + response = qtest_qmp(qts, "{ 'execute': 'query-cpus-fast' }"); + g_assert(qdict_haskey(response, "return")); + cpus = qdict_get_qlist(response, "return"); + g_assert_cmpuint(qlist_size(cpus), ==, K3_NUM_HARTS); + + QLIST_FOREACH_ENTRY(cpus, entry) { + QDict *cpu = qobject_to(QDict, entry->value); + unsigned int index = qdict_get_int(cpu, "cpu-index"); + unsigned int cluster = index / K3_HARTS_PER_CLUSTER; + unsigned int hart = index % K3_HARTS_PER_CLUSTER; + g_autofree char *expected_path = g_strdup_printf( + "/machine/soc/cluster%u/cpus/harts[%u]", cluster, hart); + + g_assert_cmpuint(index, <, K3_NUM_HARTS); + g_assert_false(seen[index]); + seen[index] = true; + g_assert_cmpstr(qdict_get_str(cpu, "qom-type"), ==, + "spacemit-x100-riscv-cpu"); + g_assert_cmpstr(qdict_get_str(cpu, "qom-path"), ==, expected_path); + } + for (unsigned int i = 0; i < K3_NUM_HARTS; i++) { + g_assert_true(seen[i]); + } + + g_assert_cmpuint(k3_qom_get_uint(qts, "/machine/soc/cluster0", + "cluster-id"), ==, 0); + g_assert_cmpuint(k3_qom_get_uint(qts, "/machine/soc/cluster1", + "cluster-id"), ==, 1); + g_assert_cmpuint(k3_qom_get_uint( + qts, "/machine/soc/cluster0/cpus/harts[0]", "vlen"), ==, 256); + + qobject_unref(response); + qtest_quit(qts); +} + +static void test_address_map(void) +{ + static const uint64_t first_pattern = UINT64_C(0x0123456789abcdef); + static const uint64_t last_pattern = UINT64_C(0xfedcba9876543210); + QTestState *qts = k3_qtest_init(); + + g_assert_cmphex(qtest_readl(qts, K3_SRAM_BASE), ==, 0x00000297); + qtest_writeq(qts, K3_SRAM_BASE, first_pattern); + qtest_writeq(qts, K3_SRAM_BASE + K3_SRAM_SIZE - 8, last_pattern); + g_assert_cmphex(qtest_readq(qts, K3_SRAM_BASE), ==, first_pattern); + g_assert_cmphex(qtest_readq(qts, K3_SRAM_BASE + K3_SRAM_SIZE - 8), ==, + last_pattern); + g_assert_cmphex(qtest_readl(qts, K3_SRAM_BASE + K3_SRAM_SIZE), ==, 0); + + qtest_writel(qts, K3_DDR_TRAINING_BASE, 0xa5a5a5a5); + qtest_writel(qts, K3_DDR_TRAINING_BASE + K3_DDR_TRAINING_SIZE - 4, + 0x5a5a5a5a); + g_assert_cmphex(qtest_readl(qts, K3_DDR_TRAINING_BASE), ==, 0xa5a5a5a5); + g_assert_cmphex(qtest_readl(qts, K3_DDR_TRAINING_BASE + + K3_DDR_TRAINING_SIZE - 4), ==, 0x5a5a5a5a); + + qtest_system_reset(qts); + g_assert_cmphex(qtest_readl(qts, K3_SRAM_BASE), ==, 0x00000297); + g_assert_cmphex(qtest_readl(qts, K3_DDR_TRAINING_BASE), ==, 0); + g_assert_cmphex(qtest_readl(qts, K3_DDR_TRAINING_BASE + + K3_DDR_TRAINING_SIZE - 4), ==, 0); + + qtest_writeq(qts, K3_FIRMWARE_BASE, first_pattern); + qtest_writeq(qts, K3_FIRMWARE_BASE + K3_FIRMWARE_SIZE - 8, + last_pattern); + g_assert_cmphex(qtest_readq(qts, K3_FIRMWARE_BASE), ==, first_pattern); + g_assert_cmphex(qtest_readq(qts, + K3_FIRMWARE_BASE + K3_FIRMWARE_SIZE - 8), ==, + last_pattern); + + qtest_writeq(qts, K3_DRAM_BASE, last_pattern); + qtest_writeq(qts, K3_DRAM_BASE + K3_DRAM_SIZE - 8, first_pattern); + g_assert_cmphex(qtest_readq(qts, K3_DRAM_BASE), ==, last_pattern); + g_assert_cmphex(qtest_readq(qts, K3_DRAM_BASE + K3_DRAM_SIZE - 8), ==, + first_pattern); + + g_assert_cmphex(qtest_readl(qts, K3_M_APLIC_BASE + APLIC_DOMAINCFG), ==, + APLIC_DOMAINCFG_RDONLY | APLIC_DOMAINCFG_DM); + g_assert_cmphex(qtest_readl(qts, K3_S_APLIC_BASE + APLIC_DOMAINCFG), ==, + APLIC_DOMAINCFG_RDONLY | APLIC_DOMAINCFG_DM); + g_assert_cmphex(qtest_readl(qts, K3_M_IMSIC_BASE), ==, 0); + g_assert_cmphex(qtest_readl(qts, K3_S_IMSIC_BASE), ==, 0); + g_assert_cmphex(qtest_readl(qts, K3_UART0_BASE + UART_LSR), ==, 0x60); + g_assert_cmphex(qtest_readl(qts, K3_UART0_BASE + K3_UART0_SIZE - 4), ==, + 0); + + qtest_quit(qts); +} + +static void test_timer_and_sstc(void) +{ + QTestState *qts = k3_qtest_init(); + uint64_t before = qtest_readq(qts, K3_M_CLINT_BASE + ACLINT_MTIME); + uint64_t after; + + qtest_clock_step(qts, 1000000); + after = qtest_readq(qts, K3_M_CLINT_BASE + ACLINT_MTIME); + g_assert_cmpuint(after - before, ==, K3_TIMEBASE_FREQ / 1000); + + k3_csr_set(qts, 0, CSR_STIMECMP, UINT64_MAX); + k3_csr_set(qts, 0, CSR_MENVCFG, MENVCFG_STCE); + g_assert_cmphex(k3_csr_get(qts, 0, CSR_MIP) & MIP_STIP, ==, 0); + k3_csr_set(qts, 0, CSR_STIMECMP, after + K3_TIMEBASE_FREQ / 1000); + qtest_clock_step(qts, 999999); + g_assert_cmphex(k3_csr_get(qts, 0, CSR_MIP) & MIP_STIP, ==, 0); + qtest_clock_step(qts, 1); + g_assert_cmphex(k3_csr_get(qts, 0, CSR_MIP) & MIP_STIP, ==, MIP_STIP); + k3_csr_set(qts, 0, CSR_STIMECMP, UINT64_MAX); + g_assert_cmphex(k3_csr_get(qts, 0, CSR_MIP) & MIP_STIP, ==, 0); + + qtest_quit(qts); +} + +static void test_imsic_hart_routing(void) +{ + const unsigned int eiid = 10; + QTestState *qts = k3_qtest_init(); + + k3_imsic_enable(qts, 0, false, eiid); + k3_imsic_enable(qts, 7, false, eiid); + + /* The unimplemented VS page in hart 0's stride must not signal S-mode. */ + qtest_writel(qts, K3_S_IMSIC_BASE + 0x1000, eiid); + g_assert_cmphex(k3_imsic_indirect_read(qts, 0, false, + ISELECT_IMSIC_EIP0), ==, 0); + + qtest_writel(qts, K3_S_IMSIC_BASE, eiid); + g_assert_cmphex(k3_imsic_indirect_read(qts, 0, false, + ISELECT_IMSIC_EIP0), ==, + UINT64_C(1) << eiid); + g_assert_cmphex(k3_csr_get(qts, 0, CSR_MIP) & MIP_SEIP, ==, MIP_SEIP); + g_assert_cmphex(k3_imsic_indirect_read(qts, 7, false, + ISELECT_IMSIC_EIP0), ==, 0); + k3_imsic_claim(qts, 0, false, eiid); + + qtest_writel(qts, K3_S_IMSIC_BASE + 7 * K3_S_IMSIC_HART_STRIDE, eiid); + g_assert_cmphex(k3_imsic_indirect_read(qts, 7, false, + ISELECT_IMSIC_EIP0), ==, + UINT64_C(1) << eiid); + g_assert_cmphex(k3_imsic_indirect_read(qts, 0, false, + ISELECT_IMSIC_EIP0), ==, 0); + k3_imsic_claim(qts, 7, false, eiid); + + k3_imsic_enable(qts, 7, true, eiid); + qtest_writel(qts, K3_M_IMSIC_BASE + 7 * K3_M_IMSIC_HART_STRIDE, eiid); + g_assert_cmphex(k3_imsic_indirect_read(qts, 7, true, + ISELECT_IMSIC_EIP0), ==, + UINT64_C(1) << eiid); + g_assert_cmphex(k3_csr_get(qts, 7, CSR_MIP) & MIP_MEIP, ==, MIP_MEIP); + k3_imsic_claim(qts, 7, true, eiid); + + qtest_quit(qts); +} + +static bool k3_wait_for_uart_rx(QTestState *qts) +{ + for (unsigned int i = 0; i < 10000; i++) { + if (qtest_readl(qts, K3_UART0_BASE + UART_LSR) & UART_LSR_DR) { + return true; + } + g_usleep(100); + } + + return false; +} + +static void test_uart_aplic_imsic(void) +{ + const unsigned int eiid = K3_UART0_IRQ; + const uint64_t input_word = APLIC_CLRIP_BASE + + (K3_UART0_IRQ / 32) * 4; + const uint32_t input_mask = 1U << (K3_UART0_IRQ % 32); + int sock_fd; + QTestState *qts = qtest_init_with_serial( + "-M k3-pico-itx -bios none -display none -nodefaults", &sock_fd); + + k3_route_s_aplic_irq(qts, K3_UART0_IRQ, eiid); + + qtest_writel(qts, K3_UART0_BASE + UART_IER, UART_IER_RDI); + g_assert_cmpint(send(sock_fd, "K", 1, 0), ==, 1); + g_assert_true(k3_wait_for_uart_rx(qts)); + + g_assert_cmphex(qtest_readl(qts, K3_S_APLIC_BASE + input_word) & + input_mask, ==, input_mask); + g_assert_cmphex(k3_imsic_indirect_read(qts, 0, false, + ISELECT_IMSIC_EIP0), ==, + UINT64_C(1) << eiid); + g_assert_cmphex(k3_csr_get(qts, 0, CSR_MIP) & MIP_SEIP, ==, MIP_SEIP); + + g_assert_cmphex(qtest_readl(qts, K3_UART0_BASE + UART_RBR), ==, 'K'); + g_assert_cmphex(qtest_readl(qts, K3_S_APLIC_BASE + input_word) & + input_mask, ==, 0); + k3_imsic_claim(qts, 0, false, eiid); + g_assert_cmphex(k3_csr_get(qts, 0, CSR_MIP) & MIP_SEIP, ==, 0); + + close(sock_fd); + qtest_quit(qts); +} + +static uint32_t k3_sdhci_cmd(QTestState *qts, unsigned int index, + uint32_t argument, uint16_t flags) +{ + uint32_t status; + + qtest_writel(qts, K3_SDHCI0_BASE + SDHCI_ARGUMENT, argument); + qtest_writew(qts, K3_SDHCI0_BASE + SDHCI_CMDREG, + index << 8 | flags); + + status = qtest_readl(qts, K3_SDHCI0_BASE + SDHCI_NORINTSTS); + g_assert_cmphex(status & (SDHCI_NIS_ERR | 0xffff0000U), ==, 0); + g_assert_cmphex(status & SDHCI_NIS_CMDCMP, ==, SDHCI_NIS_CMDCMP); + qtest_writel(qts, K3_SDHCI0_BASE + SDHCI_NORINTSTS, status); + + return qtest_readl(qts, K3_SDHCI0_BASE + SDHCI_RSPREG0); +} + +static void k3_sdhci_init_card(QTestState *qts) +{ + uint16_t rca; + + k3_sdhci_cmd(qts, 0, 0, SDHCI_CMD_RESP_NONE); + k3_sdhci_cmd(qts, 55, 0, + SDHCI_CMD_RESP_SHORT | SDHCI_CMD_CRC | SDHCI_CMD_INDEX); + k3_sdhci_cmd(qts, 41, 0x00ff8000, SDHCI_CMD_RESP_SHORT); + k3_sdhci_cmd(qts, 2, 0, SDHCI_CMD_RESP_LONG | SDHCI_CMD_CRC); + rca = k3_sdhci_cmd(qts, 3, 0, + SDHCI_CMD_RESP_SHORT | SDHCI_CMD_CRC | + SDHCI_CMD_INDEX) >> 16; + g_assert_cmpuint(rca, !=, 0); + k3_sdhci_cmd(qts, 7, (uint32_t)rca << 16, + SDHCI_CMD_RESP_SHORT_BUSY | SDHCI_CMD_CRC | + SDHCI_CMD_INDEX); +} + +static char *k3_create_sd_image(uint8_t *sector) +{ + g_autofree uint8_t *image = g_malloc0(K3_SD_IMAGE_SIZE); + g_autoptr(GError) error = NULL; + char *path = NULL; + int fd; + + for (unsigned int i = 0; i < K3_SD_SECTOR_SIZE; i++) { + sector[i] = (i * 37 + 11) & 0xff; + } + memcpy(image, sector, K3_SD_SECTOR_SIZE); + + fd = g_file_open_tmp("spacemit-k3-sd-XXXXXX.img", &path, &error); + g_assert_no_error(error); + g_assert_cmpint(fd, >=, 0); + close(fd); + g_assert_true(g_file_set_contents(path, (const char *)image, + K3_SD_IMAGE_SIZE, &error)); + g_assert_no_error(error); + + return path; +} + +static void test_sd_boot_registers(void) +{ + QTestState *qts = k3_qtest_init(); + + g_assert_cmphex(qtest_readl(qts, K3_APMU_BASE + K3_APMU_SDH0_CTRL), ==, + K3_APMU_SDH0_RESET); + qtest_writel(qts, K3_APMU_BASE + K3_APMU_SDH0_CTRL, UINT32_MAX); + g_assert_cmphex(qtest_readl(qts, K3_APMU_BASE + K3_APMU_SDH0_CTRL), ==, + 0x7fb); + + g_assert_cmphex(qtest_readl(qts, K3_CIU_BASE + K3_CIU_BOOT_FLAG), ==, + K3_CIU_BOOT_FROM_SD); + qtest_writel(qts, K3_CIU_BASE + K3_CIU_BOOT_FLAG, 0); + g_assert_cmphex(qtest_readl(qts, K3_CIU_BASE + K3_CIU_BOOT_FLAG), ==, + K3_CIU_BOOT_FROM_SD); + + g_assert_cmphex(qtest_readq(qts, K3_SDHCI0_BASE + SDHCI_CAPAB), ==, + K3_SDHCI_CAPAB); + g_assert_cmphex(qtest_readw(qts, K3_SDHCI0_BASE + SDHCI_HCVER) & 0xff, + ==, 2); + + qtest_writew(qts, K3_SDHCI0_BASE + SDHCI_HOSTCTL2, 1); + g_assert_cmphex(qtest_readw(qts, K3_SDHCI0_BASE + SDHCI_HOSTCTL2), ==, 1); + qtest_writel(qts, K3_SDHCI0_BASE + K3_SDHCI_MMC_CTRL, UINT32_MAX); + qtest_writel(qts, K3_SDHCI0_BASE + K3_SDHCI_TX_CFG, UINT32_MAX); + g_assert_cmphex(qtest_readl(qts, K3_SDHCI0_BASE + K3_SDHCI_MMC_CTRL), ==, + K3_SDHCI_MMC_CTRL_MASK); + g_assert_cmphex(qtest_readl(qts, K3_SDHCI0_BASE + K3_SDHCI_TX_CFG), ==, + K3_SDHCI_TX_CFG_MASK); + + qtest_system_reset(qts); + g_assert_cmphex(qtest_readl(qts, K3_APMU_BASE + K3_APMU_SDH0_CTRL), ==, + K3_APMU_SDH0_RESET); + g_assert_cmphex(qtest_readw(qts, K3_SDHCI0_BASE + SDHCI_HOSTCTL2), ==, 0); + g_assert_cmphex(qtest_readl(qts, K3_SDHCI0_BASE + K3_SDHCI_MMC_CTRL), ==, + 0); + g_assert_cmphex(qtest_readl(qts, K3_SDHCI0_BASE + K3_SDHCI_TX_CFG), ==, + 0); + + qtest_quit(qts); +} + +static void test_sd_adma_aplic_imsic(void) +{ + const unsigned int eiid = 11; + const uint64_t input_word = APLIC_CLRIP_BASE + + (K3_SDHCI0_IRQ / 32) * 4; + const uint32_t input_mask = 1U << (K3_SDHCI0_IRQ % 32); + const uint16_t trnmod = SDHCI_TRNS_DMA | SDHCI_TRNS_BLK_CNT_EN | + SDHCI_TRNS_READ; + uint8_t expected[K3_SD_SECTOR_SIZE]; + uint8_t actual[K3_SD_SECTOR_SIZE]; + uint8_t poison[K3_SD_SECTOR_SIZE]; + uint8_t descriptor[12] = {}; + g_autofree char *sd_path = k3_create_sd_image(expected); + QTestState *qts = qtest_initf( + "-M k3-pico-itx -bios none -display none -nodefaults " + "-drive file=%s,if=sd,format=raw,snapshot=on", sd_path); + uint32_t status; + + g_assert_cmphex(qtest_readl(qts, K3_SDHCI0_BASE + SDHCI_PRNSTS) & + SDHCI_CARD_PRESENT, ==, SDHCI_CARD_PRESENT); + qtest_writeb(qts, K3_SDHCI0_BASE + SDHCI_SWRST, SDHCI_RESET_ALL); + qtest_writeb(qts, K3_SDHCI0_BASE + SDHCI_PWRCON, SDHCI_POWER_330); + qtest_writew(qts, K3_SDHCI0_BASE + SDHCI_CLKCON, + SDHCI_CLOCK_INT_EN | SDHCI_CLOCK_SDCLK_EN); + g_assert_cmphex(qtest_readw(qts, K3_SDHCI0_BASE + SDHCI_CLKCON), ==, + SDHCI_CLOCK_INT_EN | SDHCI_CLOCK_INT_STABLE | + SDHCI_CLOCK_SDCLK_EN); + + qtest_writel(qts, K3_SDHCI0_BASE + SDHCI_NORINTSTSEN, 0xffff0003U); + k3_sdhci_init_card(qts); + k3_route_s_aplic_irq(qts, K3_SDHCI0_IRQ, eiid); + qtest_writew(qts, K3_SDHCI0_BASE + SDHCI_NORINTSIGEN, + SDHCI_NIS_TRSCMP | SDHCI_NIS_ERR); + qtest_writew(qts, K3_SDHCI0_BASE + SDHCI_ERRINTSIGEN, UINT16_MAX); + + descriptor[0] = SDHCI_ADMA_VALID | SDHCI_ADMA_END | SDHCI_ADMA_TRAN; + stw_le_p(descriptor + 2, K3_SD_SECTOR_SIZE); + stq_le_p(descriptor + 4, K3_ADMA_BUFFER_ADDR); + qtest_memwrite(qts, K3_ADMA_DESC_ADDR, descriptor, sizeof(descriptor)); + memset(poison, 0xa5, sizeof(poison)); + qtest_memwrite(qts, K3_ADMA_BUFFER_ADDR, poison, sizeof(poison)); + + qtest_writel(qts, K3_SDHCI0_BASE + SDHCI_ADMASYSADDR, + (uint32_t)K3_ADMA_DESC_ADDR); + qtest_writel(qts, K3_SDHCI0_BASE + SDHCI_ADMASYSADDR + 4, + (uint32_t)(K3_ADMA_DESC_ADDR >> 32)); + qtest_writeb(qts, K3_SDHCI0_BASE + SDHCI_HOSTCTL, + SDHCI_CTRL_ADMA2_64); + qtest_writew(qts, K3_SDHCI0_BASE + SDHCI_BLKSIZE, K3_SD_SECTOR_SIZE); + qtest_writew(qts, K3_SDHCI0_BASE + SDHCI_BLKCNT, 1); + qtest_writel(qts, K3_SDHCI0_BASE + SDHCI_ARGUMENT, 0); + qtest_writew(qts, K3_SDHCI0_BASE + SDHCI_TRNMOD, trnmod); + g_assert_cmphex(qtest_readw(qts, K3_SDHCI0_BASE + SDHCI_TRNMOD), ==, + trnmod); + qtest_writew(qts, K3_SDHCI0_BASE + SDHCI_CMDREG, + 17 << 8 | SDHCI_CMD_RESP_SHORT | SDHCI_CMD_CRC | + SDHCI_CMD_INDEX | SDHCI_CMD_DATA); + + status = qtest_readl(qts, K3_SDHCI0_BASE + SDHCI_NORINTSTS); + g_assert_cmphex(status & (SDHCI_NIS_CMDCMP | SDHCI_NIS_TRSCMP), ==, + SDHCI_NIS_CMDCMP | SDHCI_NIS_TRSCMP); + g_assert_cmphex(status & (SDHCI_NIS_ERR | 0xffff0000U), ==, 0); + g_assert_cmphex(qtest_readb(qts, K3_SDHCI0_BASE + SDHCI_ADMAERR), ==, 0); + qtest_memread(qts, K3_ADMA_BUFFER_ADDR, actual, sizeof(actual)); + g_assert_cmpmem(actual, sizeof(actual), expected, sizeof(expected)); + + g_assert_cmphex(qtest_readl(qts, K3_S_APLIC_BASE + input_word) & + input_mask, ==, input_mask); + g_assert_cmphex(k3_imsic_indirect_read(qts, 0, false, + ISELECT_IMSIC_EIP0), ==, + UINT64_C(1) << eiid); + g_assert_cmphex(k3_csr_get(qts, 0, CSR_MIP) & MIP_SEIP, ==, MIP_SEIP); + + qtest_writel(qts, K3_SDHCI0_BASE + SDHCI_NORINTSTS, status); + g_assert_cmphex(qtest_readl(qts, K3_S_APLIC_BASE + input_word) & + input_mask, ==, 0); + k3_imsic_claim(qts, 0, false, eiid); + g_assert_cmphex(k3_csr_get(qts, 0, CSR_MIP) & MIP_SEIP, ==, 0); + + qtest_quit(qts); + g_assert_cmpint(g_unlink(sd_path), ==, 0); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + if (qtest_has_machine("k3-pico-itx")) { + qtest_add_func("spacemit-k3/topology", test_topology); + qtest_add_func("spacemit-k3/address-map", test_address_map); + qtest_add_func("spacemit-k3/fdt-compatible-list", + test_fdt_compatible_list); + qtest_add_func("spacemit-k3/fdt-missing-compatible", + test_fdt_missing_compatible); + qtest_add_func("spacemit-k3/cpu-requires-vector", + test_cpu_requires_vector); + qtest_add_func("spacemit-k3/timer-sstc", test_timer_and_sstc); + qtest_add_func("spacemit-k3/imsic-routing", test_imsic_hart_routing); + qtest_add_func("spacemit-k3/uart-aplic-imsic", + test_uart_aplic_imsic); + qtest_add_func("spacemit-k3/sd-boot-registers", + test_sd_boot_registers); + qtest_add_func("spacemit-k3/sd-adma-aplic-imsic", + test_sd_adma_aplic_imsic); + } + + return g_test_run(); +}