diff options
328 files changed, 20667 insertions, 3304 deletions
diff --git a/.gitlab-ci.d/base.yml b/.gitlab-ci.d/base.yml index 72eadc8073..8fe0080935 100644 --- a/.gitlab-ci.d/base.yml +++ b/.gitlab-ci.d/base.yml @@ -52,6 +52,11 @@ variables: - if: '$CI_PIPELINE_SOURCE == "schedule"' when: never + # macOS jobs can't run in forks until gitlab opens up runner availability + # to all tiers https://gitlab.com/groups/gitlab-org/-/work_items/8267 + - if: '$QEMU_JOB_MACOS == "1" && $CI_PROJECT_NAMESPACE != $QEMU_CI_UPSTREAM' + when: never + # Publishing jobs should only run on the default branch in upstream - if: '$QEMU_JOB_PUBLISH == "1" && $CI_PROJECT_NAMESPACE == $QEMU_CI_UPSTREAM && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH' when: never @@ -129,3 +134,51 @@ variables: QEMU_CI_CONTAINER_TAG: $CI_COMMIT_REF_SLUG - when: on_success + +# Any job running meson should capture meson logs +# by default. Some jobs might override the artifacts +# to capture further files +.base_meson_job_template: + extends: .base_job_template + artifacts: + name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG" + when: always + expire_in: 7 days + paths: + - build/meson-logs + - ci-runner-env + reports: + junit: build/meson-logs/*.junit.xml + before_script: + - source scripts/ci/gitlab-ci-section + - section_start setup "Pre-script setup" + - JOBS=$(expr $(nproc) + 1) + # Prevent logs (if any) from prior build job artifacts + # from being duplicated in the new job artifacts + - rm -f build/meson-logs/* + - mkdir -p ci-runner-env + - test -f /packages.txt && cp /packages.txt ci-runner-env/packages.txt + - cp /proc/mounts ci-runner-env/mounts.txt + - cp /proc/cpuinfo ci-runner-env/cpuinfo.txt + - cp /proc/meminfo ci-runner-env/meminfo.txt + - ls -l /dev > ci-runner-env/devices.txt + - uname -a > ci-runner-env/kernel.txt + - id -a > ci-runner-env/user.txt + - printenv | sort > ci-runner-env/environ.txt + - section_end setup + +.base_meson_ccache_job_template: + extends: .base_meson_job_template + cache: + paths: + - ccache + key: "$CI_JOB_NAME" + when: always + script: + - export CCACHE_BASEDIR="$(pwd)" + - export CCACHE_DIR="$CCACHE_BASEDIR/ccache" + - export CCACHE_MAXSIZE="500M" + - export PATH="$CCACHE_WRAPPERSDIR:$PATH" + - ccache --zero-stats + after_script: + - ccache --show-stats diff --git a/.gitlab-ci.d/buildtest-template.yml b/.gitlab-ci.d/buildtest-template.yml index 005058625e..b5f3760618 100644 --- a/.gitlab-ci.d/buildtest-template.yml +++ b/.gitlab-ci.d/buildtest-template.yml @@ -1,42 +1,13 @@ -# Any job running meson should capture meson logs -# by default. Some jobs might override the artifacts -# to capture further files -.meson_job_template: - extends: .base_job_template - artifacts: - name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG" - when: always - expire_in: 7 days - paths: - - build/meson-logs - reports: - junit: build/meson-logs/*.junit.xml - .native_build_job_template: - extends: .meson_job_template + extends: .base_meson_ccache_job_template stage: build image: $CI_REGISTRY_IMAGE/qemu/$IMAGE:$QEMU_CI_CONTAINER_TAG - cache: - paths: - - ccache - key: "$CI_JOB_NAME" - when: always - before_script: - - source scripts/ci/gitlab-ci-section - - section_start setup "Pre-script setup" - - JOBS=$(expr $(nproc) + 1) - - cat /packages.txt - - section_end setup script: - - export CCACHE_BASEDIR="$(pwd)" - - export CCACHE_DIR="$CCACHE_BASEDIR/ccache" - - export CCACHE_MAXSIZE="500M" - - export PATH="$CCACHE_WRAPPERSDIR:$PATH" + - !reference [.base_meson_ccache_job_template, script] - du -sh .git - mkdir build - cd build - - ccache --zero-stats - section_start configure "Running configure" - ../configure --enable-werror --disable-docs --enable-fdt=system --disable-debug-info @@ -57,7 +28,6 @@ $MAKE -j"$JOBS" $MAKE_CHECK_ARGS ; fi - section_end test - - ccache --show-stats # We jump some hoops in common_test_job_template to avoid # rebuilding all the object files we skip in the artifacts @@ -68,6 +38,7 @@ paths: - build - .git-submodule-status + - ci-runner-env exclude: - build/**/*.p - build/**/*.a.p @@ -75,9 +46,18 @@ - build/**/*.c.o.d .common_test_job_template: - extends: .meson_job_template + extends: .base_meson_job_template stage: test image: $CI_REGISTRY_IMAGE/qemu/$IMAGE:$QEMU_CI_CONTAINER_TAG + artifacts: + name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG" + when: always + expire_in: 7 days + paths: + - build/meson-logs + - build/tests/functional/*/*/*.log + reports: + junit: build/meson-logs/*.junit.xml script: - source scripts/ci/gitlab-ci-section - section_start buildenv "Setting up to run tests" @@ -98,11 +78,6 @@ .native_test_job_template: extends: .common_test_job_template - before_script: - # Prevent logs from the build job that run earlier - # from being duplicated in the test job artifacts - - rm -f build/meson-logs/* - .functional_test_job_template: extends: .common_test_job_template @@ -111,6 +86,12 @@ paths: - ${CI_PROJECT_DIR}/functional-cache policy: pull-push + before_script: + - export QEMU_TEST_ALLOW_UNTRUSTED_CODE=1 + - export QEMU_TEST_CACHE_DIR=${CI_PROJECT_DIR}/functional-cache + # Prevent logs from the build job that run earlier + # from being duplicated in the test job artifacts + - rm -f build/meson-logs/* artifacts: name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG" when: always @@ -118,29 +99,21 @@ paths: - build/meson-logs - build/tests/functional/*/*/*.log + - ci-runner-env reports: junit: build/meson-logs/*.junit.xml - before_script: - - export QEMU_TEST_ALLOW_UNTRUSTED_CODE=1 - - export QEMU_TEST_CACHE_DIR=${CI_PROJECT_DIR}/functional-cache - # Prevent logs from the build job that run earlier - # from being duplicated in the test job artifacts - - rm -f build/meson-logs/* after_script: - cd build - du -chs ${CI_PROJECT_DIR}/*-cache variables: QEMU_JOB_FUNCTIONAL: 1 + QEMU_TEST_ALLOW_UNTRUSTED_CODE: 1 + QEMU_TEST_CACHE_DIR: ${CI_PROJECT_DIR}/functional-cache .wasm_build_job_template: - extends: .meson_job_template + extends: .base_meson_job_template stage: build image: $CI_REGISTRY_IMAGE/qemu/$IMAGE:$QEMU_CI_CONTAINER_TAG - before_script: - - source scripts/ci/gitlab-ci-section - - section_start setup "Pre-script setup" - - JOBS=$(expr $(nproc) + 1) - - section_end setup script: - du -sh .git - mkdir build diff --git a/.gitlab-ci.d/crossbuild-template.yml b/.gitlab-ci.d/crossbuild-template.yml index 58136d06e4..94423814a7 100644 --- a/.gitlab-ci.d/crossbuild-template.yml +++ b/.gitlab-ci.d/crossbuild-template.yml @@ -1,27 +1,12 @@ .cross_system_build_job: - extends: .base_job_template + extends: .base_meson_ccache_job_template stage: build image: $CI_REGISTRY_IMAGE/qemu/$IMAGE:$QEMU_CI_CONTAINER_TAG - cache: - paths: - - ccache - key: "$CI_JOB_NAME" - when: always timeout: 80m - before_script: - - source scripts/ci/gitlab-ci-section - - section_start setup "Pre-script setup" - - JOBS=$(expr $(nproc) + 1) - - cat /packages.txt - - section_end setup script: - - export CCACHE_BASEDIR="$(pwd)" - - export CCACHE_DIR="$CCACHE_BASEDIR/ccache" - - export CCACHE_MAXSIZE="500M" - - export PATH="$CCACHE_WRAPPERSDIR:$PATH" + - !reference [.base_meson_ccache_job_template, script] - mkdir build - cd build - - ccache --zero-stats - section_start configure "Running configure" - ../configure --enable-werror --disable-docs --enable-fdt=system --disable-user $QEMU_CONFIGURE_OPTS $EXTRA_CONFIGURE_OPTS @@ -46,7 +31,6 @@ mv -v qemu-setup*.exe qemu-setup-${version}.exe; fi - section_end installer - - ccache --show-stats # Job to cross-build specific accelerators. # @@ -54,22 +38,12 @@ # KVM), and set extra options (such disabling other accelerators) via the # $EXTRA_CONFIGURE_OPTS variable. .cross_accel_build_job: - extends: .base_job_template + extends: .base_meson_ccache_job_template stage: build image: $CI_REGISTRY_IMAGE/qemu/$IMAGE:$QEMU_CI_CONTAINER_TAG timeout: 60m - cache: - paths: - - ccache/ - key: "$CI_JOB_NAME" - before_script: - - source scripts/ci/gitlab-ci-section - - JOBS=$(expr $(nproc) + 1) script: - - export CCACHE_BASEDIR="$(pwd)" - - export CCACHE_DIR="$CCACHE_BASEDIR/ccache" - - export CCACHE_MAXSIZE="500M" - - export PATH="$CCACHE_WRAPPERSDIR:$PATH" + - !reference [.base_meson_ccache_job_template, script] - mkdir build - cd build - section_start configure "Running configure" @@ -87,20 +61,11 @@ - section_end test .cross_user_build_job: - extends: .base_job_template + extends: .base_meson_ccache_job_template stage: build image: $CI_REGISTRY_IMAGE/qemu/$IMAGE:$QEMU_CI_CONTAINER_TAG - cache: - paths: - - ccache/ - key: "$CI_JOB_NAME" - before_script: - - source scripts/ci/gitlab-ci-section - - JOBS=$(expr $(nproc) + 1) script: - - export CCACHE_BASEDIR="$(pwd)" - - export CCACHE_DIR="$CCACHE_BASEDIR/ccache" - - export CCACHE_MAXSIZE="500M" + - !reference [.base_meson_ccache_job_template, script] - mkdir build - cd build - section_start configure "Running configure" @@ -119,15 +84,3 @@ $MAKE -j"$JOBS" $MAKE_CHECK_ARGS ; fi - section_end test - -# We can still run some tests on some of our cross build jobs. They can add this -# template to their extends to save the build logs and test results -.cross_test_artifacts: - artifacts: - name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG" - when: always - expire_in: 7 days - paths: - - build/meson-logs - reports: - junit: build/meson-logs/*.junit.xml diff --git a/.gitlab-ci.d/macos.yml b/.gitlab-ci.d/macos.yml index 8366b4ad56..53b6e6c4d8 100644 --- a/.gitlab-ci.d/macos.yml +++ b/.gitlab-ci.d/macos.yml @@ -29,13 +29,13 @@ - cd build - ../configure --enable-werror $CONFIGURE_ARGS || { cat config.log meson-logs/meson-log.txt; exit 1; } - $MAKE -j$(sysctl -n hw.ncpu) - - for TARGET in $TEST_BINARIES ; do $MAKE $TARGET ; done - for TARGET in $TEST_TARGETS ; do $MAKE $TARGET ; done aarch64-macos-15-build: extends: .macos_job_template image: macos-15-xcode-16 variables: + QEMU_JOB_MACOS: 1 NAME: macos-15 PATH_EXTRA: /opt/homebrew/gettext/bin PKG_CONFIG_PATH: /opt/homebrew/curl/lib/pkgconfig:/opt/homebrew/ncurses/lib/pkgconfig:/opt/homebrew/readline/lib/pkgconfig @@ -45,13 +45,13 @@ aarch64-macos-15-build: --cross-prefix-i386=i686-elf- --cross-prefix-x86_64=x86_64-elf- --disable-plugins - TEST_BINARIES: qemu-system-aarch64 qemu-system-i386 qemu-system-x86_64 - TEST_TARGETS: check-unit run-tcg-tests-aarch64-softmmu run-tcg-tests-i386-softmmu run-tcg-tests-x86_64-softmmu + TEST_TARGETS: check-unit check-tcg aarch64-macos-26-build: extends: .macos_job_template image: macos-26-xcode-26 variables: + QEMU_JOB_MACOS: 1 NAME: macos-26 DYLD_LIBRARY_PATH: /opt/homebrew/opt/expat/lib/ PATH_EXTRA: /opt/homebrew/gettext/bin @@ -62,5 +62,4 @@ aarch64-macos-26-build: --cross-prefix-i386=i686-elf- --cross-prefix-x86_64=x86_64-elf- --disable-plugins - TEST_BINARIES: qemu-system-aarch64 qemu-system-i386 qemu-system-x86_64 - TEST_TARGETS: check-unit run-tcg-tests-aarch64-softmmu run-tcg-tests-i386-softmmu run-tcg-tests-x86_64-softmmu + TEST_TARGETS: check-unit check-tcg diff --git a/.gitlab/issue_templates/bug.md b/.gitlab/issue_templates/bug.md index e20f586008..faeeb00225 100644 --- a/.gitlab/issue_templates/bug.md +++ b/.gitlab/issue_templates/bug.md @@ -13,8 +13,9 @@ older than this should be reported to the distribution instead. See https://www.qemu.org/contribute/report-a-bug/ for additional guidance. -If this is a security issue, please consult -https://www.qemu.org/contribute/security-process/ +If this is a security issue, ensure this ticket is marked 'confidential' +before submission. See https://www.qemu.org/contribute/security-process/ +for additional guidance --> ## Host environment @@ -49,6 +50,12 @@ https://www.qemu.org/contribute/security-process/ 2. 3. +<!-- +Note: if this issue was discovered with the assistance of automated +tooling LLM, static analysis, fuzzers), the reporter must disclose +that in the description. The steps to reproduce, and any other findings, +must be fully validated by the user of the tool prior to submission. +--> ## Additional information @@ -31,7 +31,8 @@ malc <av1474@comtv.ru> malc <malc@c046a42c-6fe2-441c-8c8c-71466251a162> Aaron Larson <alarson@ddci.com> alarson@ddci.com Andreas Färber <andreas.faerber@web.de> Andreas Färber <andreas.faerber> fanwenjie <fanwj@mail.ustc.edu.cn> fanwj@mail.ustc.edu.cn <fanwj@mail.ustc.edu.cn> -Jason Wang <jasowang@redhat.com> Jason Wang <jasowang> +Jason Wang <jasowangio@gmail.com> Jason Wang <jasowang> +Jason Wang <jasowangio@gmail.com> Jason Wang <jasowang@redhat.com> Marek Dolata <mkdolata@us.ibm.com> mkdolata@us.ibm.com <mkdolata@us.ibm.com> Michael Ellerman <mpe@ellerman.id.au> michael@ozlabs.org <michael@ozlabs.org> Nick Hudson <hnick@vmware.com> hnick@vmware.com <hnick@vmware.com> diff --git a/MAINTAINERS b/MAINTAINERS index 93df53d87f..97dcc78ded 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -256,8 +256,12 @@ F: linux-user/hexagon/ F: tests/tcg/hexagon/ F: disas/hexagon.c F: configs/targets/hexagon-linux-user.mak +F: configs/targets/hexagon-softmmu.mak F: tests/docker/dockerfiles/debian-hexagon-cross.docker F: gdbstub/gdb-xml/hexagon*.xml +F: docs/system/target-hexagon.rst +F: docs/system/hexagon/ +F: docs/devel/hexagon-sys.rst T: git https://github.com/qualcomm/qemu.git hex-next Hexagon idef-parser @@ -1341,6 +1345,18 @@ F: pc-bios/hppa-firmware.img F: roms/seabios-hppa/ F: tests/functional/hppa/ +Hexagon Machines +---------------- +V66G_1024, V68N_1024, sa8775-cdsp0 +M: Brian Cain <brian.cain@oss.qualcomm.com> +R: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com> +S: Supported +F: hw/hexagon/ +F: include/hw/hexagon/ +F: configs/devices/hexagon-softmmu/default.mak +F: docs/system/hexagon/ +F: docs/devel/hexagon-sys.rst + LoongArch Machines ------------------ Virt @@ -1359,6 +1375,7 @@ F: hw/intc/loongarch_*.c F: hw/intc/loongson_ipi_common.c F: hw/rtc/ls7a_rtc.c F: gdbstub/gdb-xml/loongarch*.xml +F: tests/qtest/migration/loongarch64/ M68K Machines ------------- @@ -1773,6 +1790,19 @@ F: hw/*/*sifive*.c F: include/hw/*/*sifive*.h F: tests/functional/riscv64/test_sifive_u.py +Tenstorrent Machines +M: Joel Stanley <joel@jms.id.au> +M: Nicholas Piggin <npiggin@gmail.com> +R: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com> +R: Michael Ellerman <mpe@kernel.org> +R: Portia Stephens <portias@oss.tenstorrent.com> +L: qemu-riscv@nongnu.org +S: Supported +F: docs/system/riscv/tt_*.rst +F: hw/riscv/tt_*.c +F: include/hw/riscv/tt_*.h +F: tests/functional/riscv64/test_tt_*.py + AMD Microblaze-V Generic Board M: Sai Pavan Boddu <sai.pavan.boddu@amd.com> S: Maintained @@ -2106,6 +2136,15 @@ F: hw/net/can/xlnx-* F: include/hw/net/xlnx-* F: tests/qtest/xlnx-can*-test* +FlexCAN +M: Matyas Bobek <matyas.bobek@gmail.com> +M: Pavel Pisa <pisa@cmp.felk.cvut.cz> +S: Maintained +F: hw/net/can/flexcan.c +F: hw/net/can/flexcan_regs.h +F: include/hw/net/flexcan.h +F: tests/qtest/flexcan-test.c + EDU M: Jiri Slaby <jslaby@suse.cz> S: Maintained @@ -2274,7 +2313,7 @@ F: hw/char/ F: include/hw/char/ Network devices -M: Jason Wang <jasowang@redhat.com> +M: Jason Wang <jasowangio@gmail.com> S: Odd Fixes F: hw/net/ F: include/hw/net/ @@ -2347,6 +2386,7 @@ S: Supported F: hw/vfio/* F: util/vfio-helpers.c F: include/hw/vfio/ +F: docs/devel/vfio-mdpy.rst F: docs/devel/migration/vfio.rst F: qapi/vfio.json F: migration/vfio-stub.c @@ -2735,6 +2775,14 @@ S: Orphan F: hw/gpio/pcf8574.c F: include/hw/gpio/pcf8574.h +DesignWare I2C +M: Chris Rauer <crauer@google.com> +R: Alano Song <alanosong@163.com> +R: Joel Stanley <joel@jms.id.au> +S: Maintained +F: hw/i2c/designware_i2c.c +F: include/hw/i2c/designware_i2c.h + Generic Loader M: Alistair Francis <alistair@alistair23.me> S: Maintained @@ -3291,6 +3339,7 @@ F: scripts/dump-guest-memory.py F: stubs/dump.c F: docs/specs/vmcoreinfo.rst F: tests/qtest/vmcoreinfo-test.c +F: tests/qtest/dump-test.c Error reporting M: Markus Armbruster <armbru@redhat.com> @@ -3349,6 +3398,7 @@ F: include/system/memory.h F: include/system/memory_cached.h F: include/system/memory_ldst* F: include/system/physmem.h +F: include/system/ram-discard-manager.h F: include/system/ramblock.h F: include/system/memory_mapping.h F: system/dma-helpers.c @@ -3359,7 +3409,10 @@ F: system/physmem.c F: system/memory_ldst* F: system/memory-internal.h F: system/ram-block-attributes.c +F: system/ram-discard-manager.c F: scripts/coccinelle/memory-region-housekeeping.cocci +F: tests/unit/test-ram-discard-manager.c +F: tests/unit/test-ram-discard-manager-stubs.c Memory devices M: David Hildenbrand <david@kernel.org> @@ -3442,7 +3495,7 @@ F: include/qemu/qemu-print.h F: util/qemu-print.c Network device backends -M: Jason Wang <jasowang@redhat.com> +M: Jason Wang <jasowangio@gmail.com> S: Maintained F: net/ F: include/net/ @@ -4039,7 +4092,7 @@ F: target/i386/igvm.c VT-d Emulation M: Michael S. Tsirkin <mst@redhat.com> -R: Jason Wang <jasowang@redhat.com> +R: Jason Wang <jasowangio@gmail.com> R: Yi Liu <yi.l.liu@intel.com> R: Clément Mathieu--Drif <clement.mathieu--drif@bull.com> S: Supported @@ -4474,7 +4527,7 @@ F: subprojects/libvfio-user.wrap F: tests/functional/x86_64/test_vfio_user_client.py EBPF -M: Jason Wang <jasowang@redhat.com> +M: Jason Wang <jasowangio@gmail.com> R: Yuri Benditovich <ybendito@redhat.com> S: Maintained F: docs/devel/ebpf_rss.rst diff --git a/accel/accel-irq.c b/accel/accel-irq.c index 7f864e35c4..830be27c7f 100644 --- a/accel/accel-irq.c +++ b/accel/accel-irq.c @@ -10,19 +10,18 @@ */ #include "qemu/osdep.h" +#include "qemu/error-report.h" #include "hw/pci/msi.h" #include "system/kvm.h" #include "system/mshv.h" #include "system/accel-irq.h" -int accel_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev) +int accel_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev) { -#ifdef CONFIG_MSHV_IS_POSSIBLE if (mshv_msi_via_irqfd_enabled()) { - return mshv_irqchip_add_msi_route(vector, dev); + return mshv_irqchip_add_msi_route(c, vector, dev); } -#endif if (kvm_enabled()) { return kvm_irqchip_add_msi_route(c, vector, dev); } @@ -31,36 +30,28 @@ int accel_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev) int accel_irqchip_update_msi_route(int vector, MSIMessage msg, PCIDevice *dev) { -#ifdef CONFIG_MSHV_IS_POSSIBLE if (mshv_msi_via_irqfd_enabled()) { return mshv_irqchip_update_msi_route(vector, msg, dev); } -#endif if (kvm_enabled()) { return kvm_irqchip_update_msi_route(kvm_state, vector, msg, dev); } return -ENOSYS; } -void accel_irqchip_commit_route_changes(KVMRouteChange *c) +void accel_irqchip_commit_route_changes(AccelRouteChange *c) { -#ifdef CONFIG_MSHV_IS_POSSIBLE - if (mshv_msi_via_irqfd_enabled()) { - mshv_irqchip_commit_routes(); - } -#endif - if (kvm_enabled()) { - kvm_irqchip_commit_route_changes(c); + if (c->changes) { + accel_irqchip_commit_routes(); + c->changes = 0; } } void accel_irqchip_commit_routes(void) { -#ifdef CONFIG_MSHV_IS_POSSIBLE if (mshv_msi_via_irqfd_enabled()) { - mshv_irqchip_commit_routes(); + mshv_irqchip_commit_routes(mshv_state); } -#endif if (kvm_enabled()) { kvm_irqchip_commit_routes(kvm_state); } @@ -68,11 +59,9 @@ void accel_irqchip_commit_routes(void) void accel_irqchip_release_virq(int virq) { -#ifdef CONFIG_MSHV_IS_POSSIBLE if (mshv_msi_via_irqfd_enabled()) { - mshv_irqchip_release_virq(virq); + mshv_irqchip_release_virq(mshv_state, virq); } -#endif if (kvm_enabled()) { kvm_irqchip_release_virq(kvm_state, virq); } @@ -81,11 +70,9 @@ void accel_irqchip_release_virq(int virq) int accel_irqchip_add_irqfd_notifier_gsi(EventNotifier *n, EventNotifier *rn, int virq) { -#ifdef CONFIG_MSHV_IS_POSSIBLE if (mshv_msi_via_irqfd_enabled()) { return mshv_irqchip_add_irqfd_notifier_gsi(n, rn, virq); } -#endif if (kvm_enabled()) { return kvm_irqchip_add_irqfd_notifier_gsi(kvm_state, n, rn, virq); } @@ -94,13 +81,29 @@ int accel_irqchip_add_irqfd_notifier_gsi(EventNotifier *n, EventNotifier *rn, int accel_irqchip_remove_irqfd_notifier_gsi(EventNotifier *n, int virq) { -#ifdef CONFIG_MSHV_IS_POSSIBLE if (mshv_msi_via_irqfd_enabled()) { return mshv_irqchip_remove_irqfd_notifier_gsi(n, virq); } -#endif if (kvm_enabled()) { return kvm_irqchip_remove_irqfd_notifier_gsi(kvm_state, n, virq); } return -ENOSYS; } + +inline AccelRouteChange accel_irqchip_begin_route_changes(void) +{ + if (mshv_msi_via_irqfd_enabled()) { + return (AccelRouteChange) { + .accel = ACCEL(mshv_state), + .changes = 0, + }; + } + if (kvm_enabled()) { + return (AccelRouteChange) { + .accel = ACCEL(kvm_state), + .changes = 0, + }; + } + error_report("can't initiate route change, no accel irqchip available"); + abort(); +} diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c index f4f0e64fbd..5d55cb45cf 100644 --- a/accel/kvm/kvm-all.c +++ b/accel/kvm/kvm-all.c @@ -2359,11 +2359,11 @@ int kvm_irqchip_send_msi(KVMState *s, MSIMessage msg) return kvm_vm_ioctl(s, KVM_SIGNAL_MSI, &msi); } -int kvm_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev) +int kvm_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev) { struct kvm_irq_routing_entry kroute = {}; int virq; - KVMState *s = c->s; + KVMState *s = KVM_STATE(c->accel); MSIMessage msg = {0, 0}; if (pci_available && dev) { @@ -2506,7 +2506,7 @@ int kvm_irqchip_send_msi(KVMState *s, MSIMessage msg) abort(); } -int kvm_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev) +int kvm_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev) { return -ENOSYS; } @@ -3422,7 +3422,7 @@ int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private) */ goto out_unref; } - ret = ram_block_discard_range(rb, offset, size); + ret = ram_block_discard_shared_range(rb, offset, size); } else { ret = ram_block_discard_guest_memfd_range(rb, offset, size); } diff --git a/accel/mshv/irq.c b/accel/mshv/irq.c index 3c238c33c3..4828ac51ac 100644 --- a/accel/mshv/irq.c +++ b/accel/mshv/irq.c @@ -25,177 +25,6 @@ #define MSHV_IRQFD_RESAMPLE_FLAG (1 << MSHV_IRQFD_BIT_RESAMPLE) #define MSHV_IRQFD_BIT_DEASSIGN_FLAG (1 << MSHV_IRQFD_BIT_DEASSIGN) -static MshvMsiControl *msi_control; -static QemuMutex msi_control_mutex; - -void mshv_init_msicontrol(void) -{ - qemu_mutex_init(&msi_control_mutex); - msi_control = g_new0(MshvMsiControl, 1); - msi_control->gsi_routes = g_hash_table_new(g_direct_hash, g_direct_equal); - msi_control->updated = false; -} - -static int set_msi_routing(uint32_t gsi, uint64_t addr, uint32_t data) -{ - struct mshv_user_irq_entry *entry; - uint32_t high_addr = addr >> 32; - uint32_t low_addr = addr & 0xFFFFFFFF; - GHashTable *gsi_routes; - - trace_mshv_set_msi_routing(gsi, addr, data); - - if (gsi >= MSHV_MAX_MSI_ROUTES) { - error_report("gsi >= MSHV_MAX_MSI_ROUTES"); - return -1; - } - - assert(msi_control); - - WITH_QEMU_LOCK_GUARD(&msi_control_mutex) { - gsi_routes = msi_control->gsi_routes; - entry = g_hash_table_lookup(gsi_routes, GINT_TO_POINTER(gsi)); - - if (entry - && entry->address_hi == high_addr - && entry->address_lo == low_addr - && entry->data == data) - { - /* nothing to update */ - return 0; - } - - /* free old entry */ - g_free(entry); - - /* create new entry */ - entry = g_new0(struct mshv_user_irq_entry, 1); - entry->gsi = gsi; - entry->address_hi = high_addr; - entry->address_lo = low_addr; - entry->data = data; - - g_hash_table_insert(gsi_routes, GINT_TO_POINTER(gsi), entry); - msi_control->updated = true; - } - - return 0; -} - -static int add_msi_routing(uint64_t addr, uint32_t data) -{ - struct mshv_user_irq_entry *route_entry; - uint32_t high_addr = addr >> 32; - uint32_t low_addr = addr & 0xFFFFFFFF; - int gsi; - GHashTable *gsi_routes; - - trace_mshv_add_msi_routing(addr, data); - - assert(msi_control); - - WITH_QEMU_LOCK_GUARD(&msi_control_mutex) { - /* find an empty slot */ - gsi = 0; - gsi_routes = msi_control->gsi_routes; - while (gsi < MSHV_MAX_MSI_ROUTES) { - route_entry = g_hash_table_lookup(gsi_routes, GINT_TO_POINTER(gsi)); - if (!route_entry) { - break; - } - gsi++; - } - if (gsi >= MSHV_MAX_MSI_ROUTES) { - error_report("No empty gsi slot available"); - return -1; - } - - /* create new entry */ - route_entry = g_new0(struct mshv_user_irq_entry, 1); - route_entry->gsi = gsi; - route_entry->address_hi = high_addr; - route_entry->address_lo = low_addr; - route_entry->data = data; - - g_hash_table_insert(gsi_routes, GINT_TO_POINTER(gsi), route_entry); - msi_control->updated = true; - } - - return gsi; -} - -static int commit_msi_routing_table(int vm_fd) -{ - guint len; - int i, ret; - size_t table_size; - struct mshv_user_irq_table *table; - GHashTableIter iter; - gpointer key, value; - - assert(msi_control); - - WITH_QEMU_LOCK_GUARD(&msi_control_mutex) { - if (!msi_control->updated) { - /* nothing to update */ - return 0; - } - - /* Calculate the size of the table */ - len = g_hash_table_size(msi_control->gsi_routes); - table_size = sizeof(struct mshv_user_irq_table) - + len * sizeof(struct mshv_user_irq_entry); - table = g_malloc0(table_size); - - g_hash_table_iter_init(&iter, msi_control->gsi_routes); - i = 0; - while (g_hash_table_iter_next(&iter, &key, &value)) { - struct mshv_user_irq_entry *entry = value; - table->entries[i] = *entry; - i++; - } - table->nr = i; - - trace_mshv_commit_msi_routing_table(vm_fd, len); - - ret = ioctl(vm_fd, MSHV_SET_MSI_ROUTING, table); - g_free(table); - if (ret < 0) { - error_report("Failed to commit msi routing table"); - return -1; - } - msi_control->updated = false; - } - return 0; -} - -static int remove_msi_routing(uint32_t gsi) -{ - struct mshv_user_irq_entry *route_entry; - GHashTable *gsi_routes; - - trace_mshv_remove_msi_routing(gsi); - - if (gsi >= MSHV_MAX_MSI_ROUTES) { - error_report("Invalid GSI: %u", gsi); - return -1; - } - - assert(msi_control); - - WITH_QEMU_LOCK_GUARD(&msi_control_mutex) { - gsi_routes = msi_control->gsi_routes; - route_entry = g_hash_table_lookup(gsi_routes, GINT_TO_POINTER(gsi)); - if (route_entry) { - g_hash_table_remove(gsi_routes, GINT_TO_POINTER(gsi)); - g_free(route_entry); - msi_control->updated = true; - } - } - - return 0; -} - /* Pass an eventfd which is to be used for injecting interrupts from userland */ static int irqfd(int vm_fd, int fd, int resample_fd, uint32_t gsi, uint32_t flags) @@ -278,35 +107,155 @@ static int irqchip_update_irqfd_notifier_gsi(const EventNotifier *event, return register_irqfd(vm_fd, fd, virq); } +static int irqchip_allocate_gsi(MshvState *s, int *gsi) +{ + int next_gsi; -int mshv_irqchip_add_msi_route(int vector, PCIDevice *dev) + /* Return the lowest unused GSI in the bitmap */ + next_gsi = find_first_zero_bit(s->used_gsi_bitmap, s->gsi_count); + if (next_gsi >= s->gsi_count) { + return -ENOSPC; + } + + *gsi = next_gsi; + + return 0; +} + +static void irqchip_release_gsi(MshvState *s, int gsi) +{ + clear_bit(gsi, s->used_gsi_bitmap); +} + +static void add_routing_entry(MshvState *s, struct mshv_user_irq_entry *entry) { - MSIMessage msg = { 0, 0 }; - int virq = 0; + struct mshv_user_irq_entry *new; + int n, size; - if (pci_available && dev) { - msg = pci_get_msi_message(dev, vector); - virq = add_msi_routing(msg.address, le32_to_cpu(msg.data)); + if (s->irq_routes->nr == s->nr_allocated_irq_routes) { + n = s->nr_allocated_irq_routes * 2; + if (n < MSHV_MIN_ALLOCATED_MSI_ROUTES) { + n = MSHV_MIN_ALLOCATED_MSI_ROUTES; + } + size = sizeof(struct mshv_user_irq_table); + size += n * sizeof(*new); + s->irq_routes = g_realloc(s->irq_routes, size); + s->nr_allocated_irq_routes = n; } - return virq; + n = s->irq_routes->nr; + s->irq_routes->nr++; + new = &s->irq_routes->entries[n]; + + *new = *entry; + + set_bit(entry->gsi, s->used_gsi_bitmap); + + trace_mshv_add_msi_routing(entry->address_lo | entry->address_hi, + entry->data); } -void mshv_irqchip_release_virq(int virq) +int mshv_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev) { - remove_msi_routing(virq); + struct mshv_user_irq_entry entry = { 0 }; + MSIMessage msg = { 0 }; + uint32_t data, high_addr, low_addr; + int gsi, ret; + MshvState *s = MSHV_STATE(c->accel); + + if (!pci_available || !dev) { + return 0; + } + + msg = pci_get_msi_message(dev, vector); + + ret = irqchip_allocate_gsi(mshv_state, &gsi); + if (ret < 0) { + error_report("Could not allocate GSI for MSI route"); + return -1; + } + high_addr = msg.address >> 32; + low_addr = msg.address & 0xFFFFFFFF; + data = le32_to_cpu(msg.data); + + entry.gsi = gsi; + entry.address_hi = high_addr; + entry.address_lo = low_addr; + entry.data = data; + + if (s->irq_routes->nr < s->gsi_count) { + add_routing_entry(s, &entry); + c->changes++; + } else { + irqchip_release_gsi(s, gsi); + return -ENOSPC; + } + + return gsi; +} + +void mshv_irqchip_release_virq(MshvState *s, int virq) +{ + struct mshv_user_irq_entry *e; + int i; + + for (i = 0; i < s->irq_routes->nr; i++) { + e = &s->irq_routes->entries[i]; + if (e->gsi == virq) { + s->irq_routes->nr--; + *e = s->irq_routes->entries[s->irq_routes->nr]; + } + } + irqchip_release_gsi(s, virq); + + trace_mshv_remove_msi_routing(virq); +} + +static int update_routing_entry(MshvState *s, + struct mshv_user_irq_entry *new_entry) +{ + struct mshv_user_irq_entry *entry; + int n; + + for (n = 0; n < s->irq_routes->nr; n++) { + entry = &s->irq_routes->entries[n]; + if (entry->gsi != new_entry->gsi) { + continue; + } + + if (!memcmp(entry, new_entry, sizeof *entry)) { + return 0; + } + + *entry = *new_entry; + + return 0; + } + + return -ESRCH; } int mshv_irqchip_update_msi_route(int virq, MSIMessage msg, PCIDevice *dev) { + uint32_t addr_hi = msg.address >> 32; + uint32_t addr_lo = msg.address & 0xFFFFFFFF; + uint32_t data = le32_to_cpu(msg.data); + struct mshv_user_irq_entry entry = { + .gsi = virq, + .address_hi = addr_hi, + .address_lo = addr_lo, + .data = data, + }; int ret; - ret = set_msi_routing(virq, msg.address, le32_to_cpu(msg.data)); + ret = update_routing_entry(mshv_state, &entry); if (ret < 0) { - error_report("Failed to set msi routing"); - return -1; + error_report("Failed to set msi routing for gsi %d", virq); + abort(); } + trace_mshv_set_msi_routing(virq, msg.address, data); + return 0; } @@ -347,16 +296,17 @@ int mshv_request_interrupt(MshvState *mshv_state, uint32_t interrupt_type, uint3 return 0; } -void mshv_irqchip_commit_routes(void) +void mshv_irqchip_commit_routes(MshvState *s) { int ret; - int vm_fd = mshv_state->vm; + int vm_fd = s->vm; - ret = commit_msi_routing_table(vm_fd); + ret = ioctl(vm_fd, MSHV_SET_MSI_ROUTING, s->irq_routes); if (ret < 0) { error_report("Failed to commit msi routing table"); abort(); } + trace_mshv_commit_msi_routing_table(vm_fd, s->irq_routes->nr); } int mshv_irqchip_add_irqfd_notifier_gsi(const EventNotifier *event, @@ -372,27 +322,45 @@ int mshv_irqchip_remove_irqfd_notifier_gsi(const EventNotifier *event, return irqchip_update_irqfd_notifier_gsi(event, NULL, virq, false); } -int mshv_reserve_ioapic_msi_routes(int vm_fd) +static int mshv_reserve_ioapic_msi_routes(MshvState *s) { - int ret, gsi; + int ret, i; + int gsi = 0; + struct mshv_user_irq_entry blank_entry = { 0 }; /* * Reserve GSI 0-23 for IOAPIC pins, to avoid conflicts of legacy * peripherals with MSI-X devices */ - for (gsi = 0; gsi < IOAPIC_NUM_PINS; gsi++) { - ret = add_msi_routing(0, 0); + for (i = 0; i < IOAPIC_NUM_PINS; i++) { + /* ret = add_msi_routing(0, 0); */ + ret = irqchip_allocate_gsi(s, &gsi); if (ret < 0) { - error_report("Failed to reserve GSI %d", gsi); + error_report("Failed to reserve GSI %d: %s", gsi, strerror(-ret)); return -1; } + blank_entry.gsi = gsi; + add_routing_entry(s, &blank_entry); } - ret = commit_msi_routing_table(vm_fd); - if (ret < 0) { - error_report("Failed to commit reserved IOAPIC MSI routes"); - return -1; - } + mshv_irqchip_commit_routes(s); return 0; } + +void mshv_init_irq_routing(MshvState *s) +{ + int ret; + int gsi_count = MSHV_MAX_MSI_ROUTES; + + s->irq_routes = g_malloc0(sizeof(*s->irq_routes)); + s->nr_allocated_irq_routes = 0; + s->gsi_count = gsi_count; + s->used_gsi_bitmap = bitmap_new(gsi_count); + + ret = mshv_reserve_ioapic_msi_routes(s); + if (ret < 0) { + error_report("Failed to reserve IOAPIC MSI routes"); + abort(); + } +} diff --git a/accel/mshv/mem.c b/accel/mshv/mem.c index e55c38d4db..820f87ef0c 100644 --- a/accel/mshv/mem.c +++ b/accel/mshv/mem.c @@ -12,10 +12,13 @@ #include "qemu/osdep.h" #include "qemu/error-report.h" +#include "qapi/error.h" #include "linux/mshv.h" #include "system/address-spaces.h" #include "system/mshv.h" #include "system/mshv_int.h" +#include "hw/hyperv/hvhdk_mini.h" +#include "system/physmem.h" #include "exec/memattrs.h" #include <sys/ioctl.h> #include "trace.h" @@ -211,3 +214,211 @@ void mshv_set_phys_mem(MshvMemoryListener *mml, MemoryRegionSection *section, abort(); } } + +static int enable_dirty_page_tracking(int vm_fd) +{ + int ret; + struct hv_input_set_partition_property in = {0}; + struct mshv_root_hvcall args = {0}; + + in.property_code = HV_PARTITION_PROPERTY_GPA_PAGE_ACCESS_TRACKING; + in.property_value = 1; + + args.code = HVCALL_SET_PARTITION_PROPERTY; + args.in_sz = sizeof(in); + args.in_ptr = (uint64_t)∈ + + ret = mshv_hvcall(vm_fd, &args); + if (ret < 0) { + error_report("Failed to enable dirty page tracking: %s", + strerror(errno)); + return -1; + } + + return 0; +} + +/* + * Retrieve dirty page bitmap for a GPA range, clearing the dirty bits + * atomically. Large ranges are handled in batches. + */ +static int get_dirty_log(int vm_fd, uint64_t base_pfn, uint64_t page_count, + unsigned long *bitmap, size_t bitmap_size) +{ + uint64_t batch, bitmap_offset, completed = 0; + struct mshv_gpap_access_bitmap args = {0}; + int ret; + + QEMU_BUILD_BUG_ON(MSHV_DIRTY_PAGES_BATCH_SIZE % BITS_PER_LONG != 0); + assert(bitmap_size >= ROUND_UP(page_count, BITS_PER_LONG) / 8); + + while (completed < page_count) { + batch = MIN(MSHV_DIRTY_PAGES_BATCH_SIZE, page_count - completed); + bitmap_offset = completed / BITS_PER_LONG; + + args.access_type = MSHV_GPAP_ACCESS_TYPE_DIRTY; + args.access_op = MSHV_GPAP_ACCESS_OP_CLEAR; + args.page_count = batch; + args.gpap_base = base_pfn + completed; + args.bitmap_ptr = (uint64_t)(bitmap + bitmap_offset); + + ret = ioctl(vm_fd, MSHV_GET_GPAP_ACCESS_BITMAP, &args); + if (ret < 0) { + error_report("Failed to get dirty log (base_pfn=0x%" PRIx64 + " batch=%" PRIu64 "): %s", + base_pfn + completed, batch, strerror(errno)); + return -1; + } + completed += batch; + } + + return 0; +} + +bool mshv_log_global_start(MemoryListener *listener, Error **errp) +{ + int ret; + + ret = enable_dirty_page_tracking(mshv_state->vm); + if (ret < 0) { + error_setg_errno(errp, -ret, "Failed to enable dirty page tracking"); + return false; + } + return true; +} + +static int disable_dirty_page_tracking(int vm_fd) +{ + int ret; + struct hv_input_set_partition_property in = {0}; + struct mshv_root_hvcall args = {0}; + + in.property_code = HV_PARTITION_PROPERTY_GPA_PAGE_ACCESS_TRACKING; + in.property_value = 0; + + args.code = HVCALL_SET_PARTITION_PROPERTY; + args.in_sz = sizeof(in); + args.in_ptr = (uint64_t)∈ + + ret = mshv_hvcall(vm_fd, &args); + if (ret < 0) { + error_report("Failed to disable dirty page tracking: %s", + strerror(errno)); + return -1; + } + + return 0; +} + +static int set_dirty_pages(int vm_fd, uint64_t base_pfn, uint64_t page_count) +{ + uint64_t batch, completed = 0; + unsigned long bitmap[MSHV_DIRTY_PAGES_BATCH_SIZE / BITS_PER_LONG]; + struct mshv_gpap_access_bitmap args = {0}; + int ret; + + while (completed < page_count) { + batch = MIN(MSHV_DIRTY_PAGES_BATCH_SIZE, page_count - completed); + + args.access_type = MSHV_GPAP_ACCESS_TYPE_DIRTY; + args.access_op = MSHV_GPAP_ACCESS_OP_SET; + args.page_count = batch; + args.gpap_base = base_pfn + completed; + args.bitmap_ptr = (uint64_t)bitmap; + + ret = ioctl(vm_fd, MSHV_GET_GPAP_ACCESS_BITMAP, &args); + if (ret < 0) { + error_report("Failed to set dirty pages (base_pfn=0x%" PRIx64 + " batch=%" PRIu64 "): %s", + base_pfn + completed, batch, strerror(errno)); + return -1; + } + completed += batch; + } + + return 0; +} + +static bool set_dirty_bits_cb(Int128 start, Int128 len, const MemoryRegion *mr, + hwaddr offset_in_region, void *opaque) +{ + int ret, *errp = opaque; + hwaddr gpa, size; + uint64_t page_count, base_pfn; + + gpa = int128_get64(start); + size = int128_get64(len); + page_count = size >> MSHV_PAGE_SHIFT; + base_pfn = gpa >> MSHV_PAGE_SHIFT; + + if (!mr->ram || mr->readonly) { + return false; + } + + if (page_count == 0) { + return false; + } + + ret = set_dirty_pages(mshv_state->vm, base_pfn, page_count); + + /* true aborts the iteration, which is what we want if there's an error */ + if (ret < 0) { + *errp = ret; + return true; + } + + return false; +} + +void mshv_log_global_stop(MemoryListener *listener) +{ + int err = 0; + /* MSHV requires all dirty bits to be set before disabling tracking. */ + FlatView *fv = address_space_to_flatview(&address_space_memory); + flatview_for_each_range(fv, set_dirty_bits_cb, &err); + + if (err < 0) { + error_report("Failed to set dirty bits before disabling tracking"); + } + + disable_dirty_page_tracking(mshv_state->vm); +} + +void mshv_log_sync(MemoryListener *listener, MemoryRegionSection *section) +{ + hwaddr size, start_addr, mr_offset; + uint64_t page_count, base_pfn; + size_t bitmap_size; + unsigned long *bitmap; + ram_addr_t ram_addr; + int ret; + MemoryRegion *mr = section->mr; + + if (!memory_region_is_ram(mr) || memory_region_is_rom(mr)) { + return; + } + + size = align_section(section, &start_addr); + if (!size) { + return; + } + + page_count = size >> MSHV_PAGE_SHIFT; + base_pfn = start_addr >> MSHV_PAGE_SHIFT; + bitmap_size = ROUND_UP(page_count, BITS_PER_LONG) / 8; + bitmap = g_malloc0(bitmap_size); + + ret = get_dirty_log(mshv_state->vm, base_pfn, page_count, bitmap, + bitmap_size); + if (ret < 0) { + g_free(bitmap); + return; + } + + mr_offset = section->offset_within_region + start_addr - + section->offset_within_address_space; + ram_addr = memory_region_get_ram_addr(mr) + mr_offset; + + physical_memory_set_dirty_lebitmap(bitmap, ram_addr, page_count); + g_free(bitmap); +} diff --git a/accel/mshv/meson.build b/accel/mshv/meson.build index c1b1787c5e..e433187cde 100644 --- a/accel/mshv/meson.build +++ b/accel/mshv/meson.build @@ -1,6 +1,5 @@ system_ss.add(when: 'CONFIG_MSHV', if_true: files( 'irq.c', 'mem.c', - 'msr.c', 'mshv-all.c' )) diff --git a/accel/mshv/mshv-all.c b/accel/mshv/mshv-all.c index 58d8638c0c..9452504ac2 100644 --- a/accel/mshv/mshv-all.c +++ b/accel/mshv/mshv-all.c @@ -43,10 +43,6 @@ #include <err.h> #include <sys/ioctl.h> -#define TYPE_MSHV_ACCEL ACCEL_CLASS_NAME("mshv") - -DECLARE_INSTANCE_CHECKER(MshvState, MSHV_STATE, TYPE_MSHV_ACCEL) - bool mshv_allowed; MshvState *mshv_state; @@ -110,21 +106,136 @@ static int resume_vm(int vm_fd) return 0; } +static int get_host_partition_property(int mshv_fd, uint32_t property_code, + uint64_t *value) +{ + int ret; + struct hv_input_get_partition_property in = {0}; + struct hv_output_get_partition_property out = {0}; + struct mshv_root_hvcall args = {0}; + + in.property_code = property_code; + + args.code = HVCALL_GET_PARTITION_PROPERTY; + args.in_sz = sizeof(in); + args.in_ptr = (uint64_t)∈ + args.out_sz = sizeof(out); + args.out_ptr = (uint64_t)&out; + + ret = ioctl(mshv_fd, MSHV_ROOT_HVCALL, &args); + if (ret < 0) { + error_report("Failed to get host partition property bank: %s", + strerror(errno)); + return -1; + } + + *value = out.property_value; + return 0; +} + +static int get_partition_property(int vm_fd, uint32_t feature_bank, + uint64_t *value) +{ + struct hv_input_get_partition_property in = {0}; + struct hv_output_get_partition_property out = {0}; + struct mshv_root_hvcall args = {0}; + int ret; + + in.property_code = feature_bank; + + args.code = HVCALL_GET_PARTITION_PROPERTY; + args.in_sz = sizeof(in); + args.in_ptr = (uint64_t)∈ + args.out_sz = sizeof(out); + args.out_ptr = (uint64_t)&out; + + ret = ioctl(vm_fd, MSHV_ROOT_HVCALL, &args); + if (ret < 0) { + error_report("Failed to get guest partition property bank: %s", + strerror(errno)); + return -1; + } + + *value = out.property_value; + return 0; +} + +static int get_proc_features(int vm_fd, + union hv_partition_processor_features *features) +{ + int ret; + + ret = get_partition_property(vm_fd, + HV_PARTITION_PROPERTY_PROCESSOR_FEATURES0, + features[0].as_uint64); + if (ret < 0) { + error_report("Failed to get processor features bank 0"); + return -1; + } + + ret = get_partition_property(vm_fd, + HV_PARTITION_PROPERTY_PROCESSOR_FEATURES1, + features[1].as_uint64); + if (ret < 0) { + error_report("Failed to get processor features bank 1"); + return -1; + } + + return 0; +} + static int create_partition(int mshv_fd, int *vm_fd) { int ret; - struct mshv_create_partition args = {0}; + uint64_t pt_flags, host_proc_features; + union hv_partition_processor_xsave_features disabled_xsave_features; + union hv_partition_processor_features disabled_partition_features = {0}; + + struct mshv_create_partition_v2 args = {0}; + + QEMU_BUILD_BUG_ON(MSHV_NUM_CPU_FEATURES_BANKS != 2); /* Initialize pt_flags with the desired features */ - uint64_t pt_flags = (1ULL << MSHV_PT_BIT_LAPIC) | - (1ULL << MSHV_PT_BIT_X2APIC) | - (1ULL << MSHV_PT_BIT_GPA_SUPER_PAGES); + pt_flags = (1ULL << MSHV_PT_BIT_LAPIC) | + (1ULL << MSHV_PT_BIT_X2APIC) | + (1ULL << MSHV_PT_BIT_GPA_SUPER_PAGES) | + (1ULL << MSHV_PT_BIT_CPU_AND_XSAVE_FEATURES); + + /* enable all */ + disabled_xsave_features.as_uint64 = 0; + + /* + * query host for supported processor features and disable unsupported + * features: (0 means supported, 1 means disabled, hence the negation) + */ + ret = get_host_partition_property(mshv_fd, + HV_PARTITION_PROPERTY_PROCESSOR_FEATURES0, + &host_proc_features); + if (ret < 0) { + error_report("Failed to get host processor feature bank 0"); + return -1; + } + args.pt_cpu_fbanks[0] = ~host_proc_features; + + ret = get_host_partition_property(mshv_fd, + HV_PARTITION_PROPERTY_PROCESSOR_FEATURES1, + &host_proc_features); + if (ret < 0) { + error_report("Failed to get host processor feature bank 1"); + return -1; + } + args.pt_cpu_fbanks[1] = ~host_proc_features; - /* Set default isolation type */ - uint64_t pt_isolation = MSHV_PT_ISOLATION_NONE; + /* arch-specific features we disable regardless of host support */ + mshv_arch_disable_partition_proc_features(&disabled_partition_features); + args.pt_cpu_fbanks[0] |= disabled_partition_features.as_uint64[0]; + args.pt_cpu_fbanks[1] |= disabled_partition_features.as_uint64[1]; + /* populate args structure */ args.pt_flags = pt_flags; - args.pt_isolation = pt_isolation; + args.pt_isolation = MSHV_PT_ISOLATION_NONE; + args.pt_disabled_xsave = disabled_xsave_features.as_uint64; + args.pt_num_cpu_fbanks = MSHV_NUM_CPU_FEATURES_BANKS; ret = ioctl(mshv_fd, MSHV_CREATE_PARTITION, &args); if (ret < 0) { @@ -204,11 +315,6 @@ static int create_vm(int mshv_fd, int *vm_fd) return -1; } - ret = mshv_reserve_ioapic_msi_routes(*vm_fd); - if (ret < 0) { - return -1; - } - ret = mshv_arch_post_init_vm(*vm_fd); if (ret < 0) { return -1; @@ -371,6 +477,9 @@ static MemoryListener mshv_memory_listener = { .region_del = mem_region_del, .eventfd_add = mem_ioeventfd_add, .eventfd_del = mem_ioeventfd_del, + .log_sync = mshv_log_sync, + .log_global_start = mshv_log_global_start, + .log_global_stop = mshv_log_global_stop, }; static MemoryListener mshv_io_listener = { @@ -415,13 +524,13 @@ static int mshv_init_vcpu(CPUState *cpu) int ret; cpu->accel = g_new0(AccelCPUState, 1); - mshv_arch_init_vcpu(cpu); ret = mshv_create_vcpu(vm_fd, vp_index, &cpu->accel->cpufd); if (ret < 0) { return -1; } + mshv_arch_init_vcpu(cpu); cpu->accel->dirty = true; return 0; @@ -450,8 +559,6 @@ static int mshv_init(AccelState *as, MachineState *ms) mshv_init_mmio_emu(); - mshv_init_msicontrol(); - ret = create_vm(mshv_fd, &vm_fd); if (ret < 0) { close(mshv_fd); @@ -467,11 +574,19 @@ static int mshv_init(AccelState *as, MachineState *ms) s->vm = vm_fd; s->fd = mshv_fd; + + ret = get_proc_features(vm_fd, &s->processor_features); + if (ret < 0) { + return -1; + } + s->nr_as = 1; s->as = g_new0(MshvAddressSpace, s->nr_as); mshv_state = s; + mshv_init_irq_routing(s); + register_mshv_memory_listener(s, &s->memory_listener, &address_space_memory, 0, "mshv-memory"); memory_listener_register(&mshv_io_listener, &address_space_io); @@ -503,7 +618,7 @@ static int mshv_cpu_exec(CPUState *cpu) do { if (cpu->accel->dirty) { - ret = mshv_arch_put_registers(cpu); + ret = mshv_arch_store_vcpu_state(cpu); if (ret) { error_report("Failed to put registers after init: %s", strerror(-ret)); @@ -625,7 +740,7 @@ static void mshv_start_vcpu_thread(CPUState *cpu) static void do_mshv_cpu_synchronize_post_init(CPUState *cpu, run_on_cpu_data arg) { - int ret = mshv_arch_put_registers(cpu); + int ret = mshv_arch_store_vcpu_state(cpu); if (ret < 0) { error_report("Failed to put registers after init: %s", strerror(-ret)); abort(); @@ -641,7 +756,7 @@ static void mshv_cpu_synchronize_post_init(CPUState *cpu) static void mshv_cpu_synchronize_post_reset(CPUState *cpu) { - int ret = mshv_arch_put_registers(cpu); + int ret = mshv_arch_store_vcpu_state(cpu); if (ret) { error_report("Failed to put registers after reset: %s", strerror(-ret)); @@ -665,7 +780,7 @@ static void mshv_cpu_synchronize_pre_loadvm(CPUState *cpu) static void do_mshv_cpu_synchronize(CPUState *cpu, run_on_cpu_data arg) { if (!cpu->accel->dirty) { - int ret = mshv_load_regs(cpu); + int ret = mshv_arch_load_vcpu_state(cpu); if (ret < 0) { error_report("Failed to load registers for vcpu %d", cpu->cpu_index); diff --git a/accel/mshv/msr.c b/accel/mshv/msr.c deleted file mode 100644 index e6e5baef50..0000000000 --- a/accel/mshv/msr.c +++ /dev/null @@ -1,375 +0,0 @@ -/* - * QEMU MSHV support - * - * Copyright Microsoft, Corp. 2025 - * - * Authors: Magnus Kulke <magnuskulke@microsoft.com> - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#include "qemu/osdep.h" -#include "system/mshv.h" -#include "system/mshv_int.h" -#include "hw/hyperv/hvgdk_mini.h" -#include "linux/mshv.h" -#include "qemu/error-report.h" - -static uint32_t supported_msrs[64] = { - IA32_MSR_TSC, - IA32_MSR_EFER, - IA32_MSR_KERNEL_GS_BASE, - IA32_MSR_APIC_BASE, - IA32_MSR_PAT, - IA32_MSR_SYSENTER_CS, - IA32_MSR_SYSENTER_ESP, - IA32_MSR_SYSENTER_EIP, - IA32_MSR_STAR, - IA32_MSR_LSTAR, - IA32_MSR_CSTAR, - IA32_MSR_SFMASK, - IA32_MSR_MTRR_DEF_TYPE, - IA32_MSR_MTRR_PHYSBASE0, - IA32_MSR_MTRR_PHYSMASK0, - IA32_MSR_MTRR_PHYSBASE1, - IA32_MSR_MTRR_PHYSMASK1, - IA32_MSR_MTRR_PHYSBASE2, - IA32_MSR_MTRR_PHYSMASK2, - IA32_MSR_MTRR_PHYSBASE3, - IA32_MSR_MTRR_PHYSMASK3, - IA32_MSR_MTRR_PHYSBASE4, - IA32_MSR_MTRR_PHYSMASK4, - IA32_MSR_MTRR_PHYSBASE5, - IA32_MSR_MTRR_PHYSMASK5, - IA32_MSR_MTRR_PHYSBASE6, - IA32_MSR_MTRR_PHYSMASK6, - IA32_MSR_MTRR_PHYSBASE7, - IA32_MSR_MTRR_PHYSMASK7, - IA32_MSR_MTRR_FIX64K_00000, - IA32_MSR_MTRR_FIX16K_80000, - IA32_MSR_MTRR_FIX16K_A0000, - IA32_MSR_MTRR_FIX4K_C0000, - IA32_MSR_MTRR_FIX4K_C8000, - IA32_MSR_MTRR_FIX4K_D0000, - IA32_MSR_MTRR_FIX4K_D8000, - IA32_MSR_MTRR_FIX4K_E0000, - IA32_MSR_MTRR_FIX4K_E8000, - IA32_MSR_MTRR_FIX4K_F0000, - IA32_MSR_MTRR_FIX4K_F8000, - IA32_MSR_TSC_AUX, - IA32_MSR_DEBUG_CTL, - HV_X64_MSR_GUEST_OS_ID, - HV_X64_MSR_SINT0, - HV_X64_MSR_SINT1, - HV_X64_MSR_SINT2, - HV_X64_MSR_SINT3, - HV_X64_MSR_SINT4, - HV_X64_MSR_SINT5, - HV_X64_MSR_SINT6, - HV_X64_MSR_SINT7, - HV_X64_MSR_SINT8, - HV_X64_MSR_SINT9, - HV_X64_MSR_SINT10, - HV_X64_MSR_SINT11, - HV_X64_MSR_SINT12, - HV_X64_MSR_SINT13, - HV_X64_MSR_SINT14, - HV_X64_MSR_SINT15, - HV_X64_MSR_SCONTROL, - HV_X64_MSR_SIEFP, - HV_X64_MSR_SIMP, - HV_X64_MSR_REFERENCE_TSC, - HV_X64_MSR_EOM, -}; -static const size_t msr_count = ARRAY_SIZE(supported_msrs); - -static int compare_msr_index(const void *a, const void *b) -{ - return *(uint32_t *)a - *(uint32_t *)b; -} - -__attribute__((constructor)) -static void init_sorted_msr_map(void) -{ - qsort(supported_msrs, msr_count, sizeof(uint32_t), compare_msr_index); -} - -static int mshv_is_supported_msr(uint32_t msr) -{ - return bsearch(&msr, supported_msrs, msr_count, sizeof(uint32_t), - compare_msr_index) != NULL; -} - -static int mshv_msr_to_hv_reg_name(uint32_t msr, uint32_t *hv_reg) -{ - switch (msr) { - case IA32_MSR_TSC: - *hv_reg = HV_X64_REGISTER_TSC; - return 0; - case IA32_MSR_EFER: - *hv_reg = HV_X64_REGISTER_EFER; - return 0; - case IA32_MSR_KERNEL_GS_BASE: - *hv_reg = HV_X64_REGISTER_KERNEL_GS_BASE; - return 0; - case IA32_MSR_APIC_BASE: - *hv_reg = HV_X64_REGISTER_APIC_BASE; - return 0; - case IA32_MSR_PAT: - *hv_reg = HV_X64_REGISTER_PAT; - return 0; - case IA32_MSR_SYSENTER_CS: - *hv_reg = HV_X64_REGISTER_SYSENTER_CS; - return 0; - case IA32_MSR_SYSENTER_ESP: - *hv_reg = HV_X64_REGISTER_SYSENTER_ESP; - return 0; - case IA32_MSR_SYSENTER_EIP: - *hv_reg = HV_X64_REGISTER_SYSENTER_EIP; - return 0; - case IA32_MSR_STAR: - *hv_reg = HV_X64_REGISTER_STAR; - return 0; - case IA32_MSR_LSTAR: - *hv_reg = HV_X64_REGISTER_LSTAR; - return 0; - case IA32_MSR_CSTAR: - *hv_reg = HV_X64_REGISTER_CSTAR; - return 0; - case IA32_MSR_SFMASK: - *hv_reg = HV_X64_REGISTER_SFMASK; - return 0; - case IA32_MSR_MTRR_CAP: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_CAP; - return 0; - case IA32_MSR_MTRR_DEF_TYPE: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_DEF_TYPE; - return 0; - case IA32_MSR_MTRR_PHYSBASE0: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE0; - return 0; - case IA32_MSR_MTRR_PHYSMASK0: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK0; - return 0; - case IA32_MSR_MTRR_PHYSBASE1: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE1; - return 0; - case IA32_MSR_MTRR_PHYSMASK1: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK1; - return 0; - case IA32_MSR_MTRR_PHYSBASE2: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE2; - return 0; - case IA32_MSR_MTRR_PHYSMASK2: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK2; - return 0; - case IA32_MSR_MTRR_PHYSBASE3: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE3; - return 0; - case IA32_MSR_MTRR_PHYSMASK3: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK3; - return 0; - case IA32_MSR_MTRR_PHYSBASE4: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE4; - return 0; - case IA32_MSR_MTRR_PHYSMASK4: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK4; - return 0; - case IA32_MSR_MTRR_PHYSBASE5: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE5; - return 0; - case IA32_MSR_MTRR_PHYSMASK5: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK5; - return 0; - case IA32_MSR_MTRR_PHYSBASE6: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE6; - return 0; - case IA32_MSR_MTRR_PHYSMASK6: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK6; - return 0; - case IA32_MSR_MTRR_PHYSBASE7: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE7; - return 0; - case IA32_MSR_MTRR_PHYSMASK7: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK7; - return 0; - case IA32_MSR_MTRR_FIX64K_00000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX64K00000; - return 0; - case IA32_MSR_MTRR_FIX16K_80000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX16K80000; - return 0; - case IA32_MSR_MTRR_FIX16K_A0000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX16KA0000; - return 0; - case IA32_MSR_MTRR_FIX4K_C0000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KC0000; - return 0; - case IA32_MSR_MTRR_FIX4K_C8000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KC8000; - return 0; - case IA32_MSR_MTRR_FIX4K_D0000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KD0000; - return 0; - case IA32_MSR_MTRR_FIX4K_D8000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KD8000; - return 0; - case IA32_MSR_MTRR_FIX4K_E0000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KE0000; - return 0; - case IA32_MSR_MTRR_FIX4K_E8000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KE8000; - return 0; - case IA32_MSR_MTRR_FIX4K_F0000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KF0000; - return 0; - case IA32_MSR_MTRR_FIX4K_F8000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KF8000; - return 0; - case IA32_MSR_TSC_AUX: - *hv_reg = HV_X64_REGISTER_TSC_AUX; - return 0; - case IA32_MSR_BNDCFGS: - *hv_reg = HV_X64_REGISTER_BNDCFGS; - return 0; - case IA32_MSR_DEBUG_CTL: - *hv_reg = HV_X64_REGISTER_DEBUG_CTL; - return 0; - case IA32_MSR_TSC_ADJUST: - *hv_reg = HV_X64_REGISTER_TSC_ADJUST; - return 0; - case IA32_MSR_SPEC_CTRL: - *hv_reg = HV_X64_REGISTER_SPEC_CTRL; - return 0; - case HV_X64_MSR_GUEST_OS_ID: - *hv_reg = HV_REGISTER_GUEST_OS_ID; - return 0; - case HV_X64_MSR_SINT0: - *hv_reg = HV_REGISTER_SINT0; - return 0; - case HV_X64_MSR_SINT1: - *hv_reg = HV_REGISTER_SINT1; - return 0; - case HV_X64_MSR_SINT2: - *hv_reg = HV_REGISTER_SINT2; - return 0; - case HV_X64_MSR_SINT3: - *hv_reg = HV_REGISTER_SINT3; - return 0; - case HV_X64_MSR_SINT4: - *hv_reg = HV_REGISTER_SINT4; - return 0; - case HV_X64_MSR_SINT5: - *hv_reg = HV_REGISTER_SINT5; - return 0; - case HV_X64_MSR_SINT6: - *hv_reg = HV_REGISTER_SINT6; - return 0; - case HV_X64_MSR_SINT7: - *hv_reg = HV_REGISTER_SINT7; - return 0; - case HV_X64_MSR_SINT8: - *hv_reg = HV_REGISTER_SINT8; - return 0; - case HV_X64_MSR_SINT9: - *hv_reg = HV_REGISTER_SINT9; - return 0; - case HV_X64_MSR_SINT10: - *hv_reg = HV_REGISTER_SINT10; - return 0; - case HV_X64_MSR_SINT11: - *hv_reg = HV_REGISTER_SINT11; - return 0; - case HV_X64_MSR_SINT12: - *hv_reg = HV_REGISTER_SINT12; - return 0; - case HV_X64_MSR_SINT13: - *hv_reg = HV_REGISTER_SINT13; - return 0; - case HV_X64_MSR_SINT14: - *hv_reg = HV_REGISTER_SINT14; - return 0; - case HV_X64_MSR_SINT15: - *hv_reg = HV_REGISTER_SINT15; - return 0; - case IA32_MSR_MISC_ENABLE: - *hv_reg = HV_X64_REGISTER_MSR_IA32_MISC_ENABLE; - return 0; - case HV_X64_MSR_SCONTROL: - *hv_reg = HV_REGISTER_SCONTROL; - return 0; - case HV_X64_MSR_SIEFP: - *hv_reg = HV_REGISTER_SIEFP; - return 0; - case HV_X64_MSR_SIMP: - *hv_reg = HV_REGISTER_SIMP; - return 0; - case HV_X64_MSR_REFERENCE_TSC: - *hv_reg = HV_REGISTER_REFERENCE_TSC; - return 0; - case HV_X64_MSR_EOM: - *hv_reg = HV_REGISTER_EOM; - return 0; - default: - error_report("failed to map MSR %u to HV register name", msr); - return -1; - } -} - -static int set_msrs(const CPUState *cpu, GList *msrs) -{ - size_t n_msrs; - GList *entries; - MshvMsrEntry *entry; - enum hv_register_name name; - struct hv_register_assoc *assoc; - int ret; - size_t i = 0; - - n_msrs = g_list_length(msrs); - hv_register_assoc *assocs = g_new0(hv_register_assoc, n_msrs); - - entries = msrs; - for (const GList *elem = entries; elem != NULL; elem = elem->next) { - entry = elem->data; - ret = mshv_msr_to_hv_reg_name(entry->index, &name); - if (ret < 0) { - g_free(assocs); - return ret; - } - assoc = &assocs[i]; - assoc->name = name; - /* the union has been initialized to 0 */ - assoc->value.reg64 = entry->data; - i++; - } - ret = mshv_set_generic_regs(cpu, assocs, n_msrs); - g_free(assocs); - if (ret < 0) { - error_report("failed to set msrs"); - return -1; - } - return 0; -} - - -int mshv_configure_msr(const CPUState *cpu, const MshvMsrEntry *msrs, - size_t n_msrs) -{ - GList *valid_msrs = NULL; - uint32_t msr_index; - int ret; - - for (size_t i = 0; i < n_msrs; i++) { - msr_index = msrs[i].index; - /* check whether index of msrs is in SUPPORTED_MSRS */ - if (mshv_is_supported_msr(msr_index)) { - valid_msrs = g_list_append(valid_msrs, (void *) &msrs[i]); - } - } - - ret = set_msrs(cpu, valid_msrs); - g_list_free(valid_msrs); - - return ret; -} diff --git a/accel/stubs/kvm-stub.c b/accel/stubs/kvm-stub.c index c4617caac6..32b4b07403 100644 --- a/accel/stubs/kvm-stub.c +++ b/accel/stubs/kvm-stub.c @@ -44,7 +44,7 @@ int kvm_on_sigbus(int code, void *addr) return 1; } -int kvm_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev) +int kvm_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev) { return -ENOSYS; } diff --git a/accel/stubs/mshv-stub.c b/accel/stubs/mshv-stub.c index e499b199d9..566a17ff9e 100644 --- a/accel/stubs/mshv-stub.c +++ b/accel/stubs/mshv-stub.c @@ -13,13 +13,14 @@ #include "system/mshv.h" bool mshv_allowed; +MshvState *mshv_state; -int mshv_irqchip_add_msi_route(int vector, PCIDevice *dev) +int mshv_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev) { return -ENOSYS; } -void mshv_irqchip_release_virq(int virq) +void mshv_irqchip_release_virq(MshvState *s, int virq) { } @@ -28,7 +29,7 @@ int mshv_irqchip_update_msi_route(int virq, MSIMessage msg, PCIDevice *dev) return -ENOSYS; } -void mshv_irqchip_commit_routes(void) +void mshv_irqchip_commit_routes(MshvState *s) { } diff --git a/bsd-user/aarch64/target_arch_elf.h b/bsd-user/aarch64/target_arch_elf.h index 5ea5232a57..2fb7dcd778 100644 --- a/bsd-user/aarch64/target_arch_elf.h +++ b/bsd-user/aarch64/target_arch_elf.h @@ -126,7 +126,7 @@ static uint32_t get_elf_hwcap2(void) GET_FEATURE_ID(aa64_dcpodp, ARM_HWCAP2_A64_DCPODP); GET_FEATURE_ID(aa64_sve2, ARM_HWCAP2_A64_SVE2); - GET_FEATURE_ID(aa64_sve2_aes, ARM_HWCAP2_A64_SVEAES); + GET_FEATURE_ID(aa64_sve_aes, ARM_HWCAP2_A64_SVEAES); GET_FEATURE_ID(aa64_sve2_pmull128, ARM_HWCAP2_A64_SVEPMULL); GET_FEATURE_ID(aa64_sve2_bitperm, ARM_HWCAP2_A64_SVEBITPERM); GET_FEATURE_ID(aa64_sve2_sha3, ARM_HWCAP2_A64_SVESHA3); diff --git a/configs/devices/hexagon-softmmu/default.mak b/configs/devices/hexagon-softmmu/default.mak new file mode 100644 index 0000000000..6f2007771b --- /dev/null +++ b/configs/devices/hexagon-softmmu/default.mak @@ -0,0 +1,7 @@ +# Default configuration for hexagon-softmmu + +# Uncomment the following lines to disable these optional devices: + +# Boards are selected by default, uncomment to keep out of the build. +# CONFIG_HEX_VIRT=y +# CONFIG_HEX_DSP=y diff --git a/configs/meson/emscripten.txt b/configs/meson/emscripten.txt index 4230e88005..56c39763bb 100644 --- a/configs/meson/emscripten.txt +++ b/configs/meson/emscripten.txt @@ -6,3 +6,8 @@ objc_args = ['-pthread'] # configuration so explicitly add the flag here. c_link_args = ['-pthread','-sASYNCIFY=1','-sPROXY_TO_PTHREAD=1','-sFORCE_FILESYSTEM','-sALLOW_TABLE_GROWTH','-sTOTAL_MEMORY=2GB','-sWASM_BIGINT','-sEXPORT_ES6=1','-sASYNCIFY_IMPORTS=ffi_call_js','-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction,TTY,FS'] cpp_link_args = ['-pthread','-sASYNCIFY=1','-sPROXY_TO_PTHREAD=1','-sFORCE_FILESYSTEM','-sALLOW_TABLE_GROWTH','-sTOTAL_MEMORY=2GB','-sWASM_BIGINT','-sEXPORT_ES6=1','-sASYNCIFY_IMPORTS=ffi_call_js','-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction,TTY,FS'] + +[project options] +# Workaround for failure to find glib-2.0 when building idef parser, +# this may be a bug in the cross-build setup used by emscripten: +hexagon_idef_parser = false diff --git a/configs/targets/hexagon-softmmu.mak b/configs/targets/hexagon-softmmu.mak new file mode 100644 index 0000000000..a77c100f0c --- /dev/null +++ b/configs/targets/hexagon-softmmu.mak @@ -0,0 +1,8 @@ +# Default configuration for hexagon-softmmu + +TARGET_ARCH=hexagon +TARGET_XML_FILES=hexagon-core.xml hexagon-hvx.xml +TARGET_LONG_BITS=32 +TARGET_NOT_USING_LEGACY_LDST_PHYS_API=y +TARGET_NOT_USING_LEGACY_NATIVE_ENDIAN_API=y +TARGET_NEED_FDT=y diff --git a/docs/about/deprecated.rst b/docs/about/deprecated.rst index 97750f5edc..169c5dfe4f 100644 --- a/docs/about/deprecated.rst +++ b/docs/about/deprecated.rst @@ -206,15 +206,6 @@ in the QEMU object model anymore. ``Sun-UltraSparc-IIIi+`` and but for consistency these will get removed in a future release, too. Use ``Sun-UltraSparc-IIIi-plus`` and ``Sun-UltraSparc-IV-plus`` instead. -PPC 405 CPUs (since 10.0) -''''''''''''''''''''''''' - -The PPC 405 CPU has no known users and the ``ref405ep`` machine was -removed in QEMU 10.0. Since the IBM POWER [8-11] processors uses an -embedded 405 for power management (OCC) and other internal tasks, it -is theoretically possible to use QEMU to model them. Let's keep the -CPU implementation for a while before removing all support. - Power8E and Power8NVL CPUs and corresponding Pnv chips (since 10.1) ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' @@ -249,6 +240,13 @@ The RISC-V ``shakti_c`` machine hasn't had meaningful contributions since 2021 and is currently unmaintained. The machine is scheduled to be removed as it appears to have no users. +``memory-encryption`` machine property (since 11.1) +''''''''''''''''''''''''''''''''''''''''''''''''''' + +Use ``confidential-guest-support`` instead. The ``memory-encryption`` object +was an early implementation of memory encryption support in QEMU, but it has +been superseded by the more comprehensive ``confidential-guest-support`` +object. Backend options --------------- diff --git a/docs/devel/hexagon-sys.rst b/docs/devel/hexagon-sys.rst new file mode 100644 index 0000000000..92ebc32dce --- /dev/null +++ b/docs/devel/hexagon-sys.rst @@ -0,0 +1,112 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +.. _Hexagon-System-arch: + +Hexagon System Architecture +=========================== + +The hexagon architecture has some unique elements which are described here. + +Interrupts +---------- +When interrupts arrive at a Hexagon DSP core, they are priority-steered to +be handled by an eligible hardware thread with the lowest priority. + +Memory +------ +Each hardware thread has an ``SSR.ASID`` field that contains its Address +Space Identifier. This value is catenated with a 32-bit virtual address - +the MMU can then resolve this extended virtual address to a physical address. + +TLBs +---- +The format of a TLB entry is shown below. + +.. note:: + The Small Core DSPs have a different TLB format which is not yet + supported. + +.. admonition:: Diagram + + .. code:: text + + 6 5 4 3 + 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |v|g|x|A|A| | | + |a|l|P|1|0| ASID | Virtual Page | + |l|b| | | | | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + 3 2 1 0 + 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | | | | | | | + |x|w|r|u|Cacheab| Physical Page |S| + | | | | | | | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +* ASID: the address-space identifier +* A1, A0: the behavior of these cache line attributes are not modeled by QEMU. +* xP: the extra-physical bit is the most significant physical address bit. +* S: the S bit and the LSBs of the physical page indicate the page size +* val: this is the 'valid' bit, when set it indicates that page matching + should consider this entry. + +.. list-table:: Page sizes + :widths: 25 25 50 + :header-rows: 1 + + * - S-bit + - Phys page LSBs + - Page size + * - 1 + - N/A + - 4kb + * - 0 + - 0b1 + - 16kb + * - 0 + - 0b10 + - 64kb + * - 0 + - 0b100 + - 256kb + * - 0 + - 0b1000 + - 1MB + * - 0 + - 0b10000 + - 4MB + * - 0 + - 0b100000 + - 16MB + +* glb: if the global bit is set, the ASID is not considered when matching + TLBs. +* Cacheab: the cacheability attributes of TLBs are not modeled, these bits + are ignored. +* RWX: read-, write-, execute-, enable bits. Indicates if user programs + are permitted to read/write/execute the given page. +* U: indicates if user programs can access this page. + +Scheduler +--------- +The Hexagon system architecture has a feature to assist the guest OS +task scheduler. The guest OS can enable this feature by setting +``SCHEDCFG.EN``. The ``BESTWAIT`` register is programmed by the guest OS +to indicate the priority of the highest priority task waiting to run on a +hardware thread. The reschedule interrupt is triggered when any hardware +thread's priority in ``STID.PRIO`` is worse than the ``BESTWAIT``. When +it is triggered, the ``BESTWAIT.PRIO`` value is reset to 0x1ff. + +HVX Coprocessor +--------------- +The Supervisor Status Register field ``SSR.XA`` binds a DSP hardware thread +to one of the eight possible HVX contexts. The guest OS is responsible for +managing this resource. + +.. seealso:: + + ``target/hexagon/README`` in the QEMU source tree for more info about Hexagon. diff --git a/docs/devel/index-internals.rst b/docs/devel/index-internals.rst index 7a0678cbdd..b89bab9b30 100644 --- a/docs/devel/index-internals.rst +++ b/docs/devel/index-internals.rst @@ -14,6 +14,7 @@ Details about QEMU's various subsystems including how to add features to them. block-coroutine-wrapper clocks ebpf_rss + hexagon-sys migration/index multi-process reset @@ -22,6 +23,7 @@ Details about QEMU's various subsystems including how to add features to them. tracing uefi-vars vfio-iommufd + vfio-mdpy writing-monitor-commands virtio-backends crypto diff --git a/docs/devel/testing/ci-jobs.rst.inc b/docs/devel/testing/ci-jobs.rst.inc index d5b081978a..d747a7ad13 100644 --- a/docs/devel/testing/ci-jobs.rst.inc +++ b/docs/devel/testing/ci-jobs.rst.inc @@ -125,6 +125,13 @@ QEMU_JOB_FUNCTIONAL The job runs the functional test suite +QEMU_JOB_MACOS +~~~~~~~~~~~~~~ + +The job requires access to the macOS shared runners, which are +only available to Premium/Ultimate tiers. This includes OSS project +members, but not forks. + Contributor controlled runtime variables ---------------------------------------- diff --git a/docs/devel/vfio-mdpy.rst b/docs/devel/vfio-mdpy.rst new file mode 100644 index 0000000000..7875d00d1e --- /dev/null +++ b/docs/devel/vfio-mdpy.rst @@ -0,0 +1,90 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +=================================== +Testing VFIO display with mdev mdpy +=================================== + +.. contents:: Table of Contents + +The kernel provides a sample mediated device driver, ``mdpy`` +(``samples/vfio-mdev/mdpy.c``), that exposes a fake framebuffer through the VFIO +display region interface. It can be used to test VFIO display support, including +hotplug, without any real GPU hardware. + +The kernel modules +================== + +The ``mdpy`` driver depends on the ``mdev`` subsystem. Enable, build and load +the modules. + +The minimal set is:: + + CONFIG_SAMPLE_VFIO_MDEV_MDPY=m + CONFIG_SAMPLE_VFIO_MDEV_MDPY_FB=m # guest framebuffer driver + +CONFIG_VFIO_MDEV is selected automatically. + +Verify that the driver registered successfully: + +.. code-block:: bash + + ls /sys/devices/virtual/mdpy/mdpy/mdev_supported_types/ + +Creating an mdev instance +========================= + +Available types correspond to different resolutions (e.g. ``mdpy-vga`` +for 640x480, ``mdpy-xga`` for 1024x768, ``mdpy-hd`` for 1920x1080). + +Each mdev instance is identified by a UUID: + +.. code-block:: bash + + uuid=$(uuidgen) + echo "$uuid" > /sys/devices/virtual/mdpy/mdpy/mdev_supported_types/mdpy-xga/create + +To remove the instance later: + +.. code-block:: bash + + echo 1 > /sys/bus/mdev/devices/$uuid/remove + +Make sure your user has the necessary permissions to access the vfio group. +(ex: chmod 666 /dev/vfio/16) + +Starting QEMU +============= + +Boot-time attachment +-------------------- + +.. code-block:: bash + + qemu-system-x86_64 -machine q35 -m 1G \ + -device vfio-pci,sysfsdev=/sys/bus/mdev/devices/$uuid,display=on \ + -display gtk,gl=on + +Hotplug via HMP +--------------- + +Start QEMU with a PCIe root port (required for PCIe hotplug) and a +monitor: + +.. code-block:: bash + + qemu-system-x86_64 -machine q35 -m 1G \ + -device pcie-root-port,id=rp0,slot=1 \ + -display gtk,gl=on \ + -monitor stdio + +Then at the ``(qemu)`` prompt: + +.. code-block:: none + + device_add vfio-pci,sysfsdev=/sys/bus/mdev/devices/<uuid>,display=on,bus=rp0,id=mdpy0 + +To hot-unplug: + +.. code-block:: none + + device_del mdpy0 diff --git a/docs/system/arm/emulation.rst b/docs/system/arm/emulation.rst index a8072ddb67..191d1a8c93 100644 --- a/docs/system/arm/emulation.rst +++ b/docs/system/arm/emulation.rst @@ -60,6 +60,7 @@ the following architecture extensions: - FEAT_E2H0 (Programming of HCR_EL2.E2H) - FEAT_EBF16 (AArch64 Extended BFloat16 instructions) - FEAT_ECV (Enhanced Counter Virtualization) +- FEAT_ECV_POFF (Enhanced Counter Virtualization Physical Offset) - FEAT_EL0 (Support for execution at EL0) - FEAT_EL1 (Support for execution at EL1) - FEAT_EL2 (Support for execution at EL2) @@ -169,8 +170,11 @@ the following architecture extensions: - FEAT_SME_FA64 (Full A64 instruction set in Streaming SVE mode) - FEAT_SME_F16F16 (Non-widening half-precision FP16 arithmetic for SME2) - FEAT_SME_F64F64 (Double-precision floating-point outer product instructions) +- FEAT_SME_F8F16 (SME2 ZA-targeting FP8 multiply-accumulate, dot product, and outer product to half-precision instructions) +- FEAT_SME_F8F32 (SME2 ZA-targeting FP8 multiply-accumulate, dot product, and outer product to single-precision instructions) - FEAT_SME_I16I64 (16-bit to 64-bit integer widening outer product instructions) - FEAT_SME_LUTv2 (Lookup table instructions with 4-bit indices and 8-bit elements) +- FEAT_SSVE_AES (Streaming SVE Mode Advanced Encryption Standard and 128-bit polynomial multiply long instructions) - FEAT_SSVE_FP8DOT2 (SVE2 FP8 2-way dot product to half-precision instructions in Streaming SVE mode) - FEAT_SSVE_FP8DOT4 (SVE2 FP8 4-way dot product to single-precision instructions in Streaming SVE mode) - FEAT_SSVE_FP8FMA (SVE2 FP8 multiply-accumulate to half-precision and single-precision instructions in Streaming SVE mode) diff --git a/docs/system/arm/sabrelite.rst b/docs/system/arm/sabrelite.rst index 4ccb0560af..d3a3c01dd6 100644 --- a/docs/system/arm/sabrelite.rst +++ b/docs/system/arm/sabrelite.rst @@ -24,6 +24,7 @@ The SABRE Lite machine supports the following devices: * 4 SDHC storage controllers * 4 USB 2.0 host controllers * 5 ECSPI controllers + * 2 FlexCAN CAN controllers * 1 SST 25VF016B flash Please note above list is a complete superset the QEMU SABRE Lite machine can diff --git a/docs/system/arm/virt.rst b/docs/system/arm/virt.rst index f811e662d6..ae0be35d57 100644 --- a/docs/system/arm/virt.rst +++ b/docs/system/arm/virt.rst @@ -288,6 +288,32 @@ User-creatable SMMUv3 devices -device pxb-pcie,id=pcie.1,numa_node=1 -device arm-smmuv3,primary-bus=pcie.1,id=smmuv3.1 + *Accelerated SMMUv3 (nested translation)* + + The ``accel=on`` option enables hardware-accelerated nested translation + for vfio-pci passthrough devices. In this mode the guest SMMU driver + programs its own Stage-1 page tables, with the host SMMUv3 handling both + Stage-1 (guest) and Stage-2 (host) translations in hardware. The host + SMMUv3 must support nested translation. This mode requires the iommufd + backend and is only supported when booting with ACPI (not device tree). + + When ``accel=on``, QEMU automatically derives the values for the + ``ril``, ``ats``, ``oas``, ``ssidsize`` and ``cmdqv`` sub-options + from the host SMMUv3 capabilities unless they are set explicitly. + + Example:: + + -device arm-smmuv3,primary-bus=pcie.0,id=smmuv3.0,accel=on + + *Accelerated SMMUv3 command queues (Tegra241 CMDQV)* + + The ``cmdqv`` sub-option enables NVIDIA Tegra241 Command Queue + Virtualization (CMDQV) on supported hosts. With CMDQV, each accelerated + ``arm-smmuv3`` device gets dedicated hardware command queues and the + guest issues SMMU invalidation commands directly to real hardware, + bypassing QEMU and improving throughput for workloads that issue many + invalidations. Without it, every invalidation command traps into QEMU. + Linux guest kernel configuration """""""""""""""""""""""""""""""" diff --git a/docs/system/devices/can.rst b/docs/system/devices/can.rst index 09121836fd..622f898952 100644 --- a/docs/system/devices/can.rst +++ b/docs/system/devices/can.rst @@ -173,6 +173,30 @@ The test can also be run the other way around, generating messages in the guest system and capturing them in the host system. Other combinations are also possible. +Examples on how to use CAN emulation for FlexCAN on SabreLite board +------------------------------------------------------------------- +FlexCANs are connected to QEMU CAN buses by passing the bus IDs as machine +properties: + +* property ``canbus0`` for connecting ``flexcan1`` +* property ``canbus1`` for connecting ``flexcan2`` + +Note that upstream Linux SabreLite DTs have only a single FlexCAN (``flexcan1``) +enabled. + +An example command to run QEMU emulating a Sabrelite development board +with both FlexCANs connected to a single QEMU CAN bus (called ``qcan0``), +bridged to host system ``can0`` interface:: + + qemu-system-arm -M sabrelite -smp 4 -m 1G \ + -object can-bus,id=qcan0 \ + -machine canbus0=qcan0 -machine canbus1=qcan0 \ + -object can-host-socketcan,if=can0,canbus=qcan0,id=qcan0-socketcan \ + -kernel ... -dtb ... -initrd ... + +Note that in the Linux guest, bitrate for the FlexCAN device is ignored, +but needs to be set via the ``ip`` command. + Links to other resources ------------------------ diff --git a/docs/system/hexagon/cdsp.rst b/docs/system/hexagon/cdsp.rst new file mode 100644 index 0000000000..237529273c --- /dev/null +++ b/docs/system/hexagon/cdsp.rst @@ -0,0 +1,12 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Compute DSP +=========== + +A Hexagon CDSP is designed as a computation offload device for an SoC. The +``V66G_1024`` machine contains: + +* L2VIC interrupt controller +* QTimer timer device + +This machine will support any Hexagon CPU, but will default to ``v66``. diff --git a/docs/system/hexagon/emulation.rst b/docs/system/hexagon/emulation.rst new file mode 100644 index 0000000000..36f54f058a --- /dev/null +++ b/docs/system/hexagon/emulation.rst @@ -0,0 +1,15 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +.. _Hexagon Emulation: + +Hexagon CPU architecture support +================================ + +QEMU's TCG emulation includes support for v65, v66, v67, v68, v69, v71, v73. +It also has support for the following architecture extensions: + +- HVX (Hexagon Vector eXtensions) + +For information on the specifics of the HVX extension, please refer +to the `Qualcomm Hexagon V73 HVX Programmer's Reference Manual +<https://docs.qualcomm.com/bundle/publicresource/80-N2040-53.pdf>`_. diff --git a/docs/system/introduction.rst b/docs/system/introduction.rst index bec7291e47..8d9ef61d26 100644 --- a/docs/system/introduction.rst +++ b/docs/system/introduction.rst @@ -1,6 +1,15 @@ Introduction ============ +.. note:: + QEMU supports many different target CPU architectures and many + different machine types for each architecture. Options, properties, + and command lines that work for one architecture or machine type + will not necessarily work on another. The examples shown in this + manual are specific to the architecture and machine type being + demonstrated. When in doubt, consult the documentation for your + specific target architecture and machine type. + .. _Accelerators: Virtualisation Accelerators diff --git a/docs/system/ppc/pseries.rst b/docs/system/ppc/pseries.rst index bbc51aa7fc..107091a37f 100644 --- a/docs/system/ppc/pseries.rst +++ b/docs/system/ppc/pseries.rst @@ -43,7 +43,7 @@ Firmware The pSeries platform in QEMU comes with 2 firmwares: -`SLOF <https://github.com/aik/SLOF>`_ (Slimline Open Firmware) is an +`SLOF <https://gitlab.com/slof/slof>`_ (Slimline Open Firmware) is an implementation of the `IEEE 1275-1994, Standard for Boot (Initialization Configuration) Firmware: Core Requirements and Practices <https://standards.ieee.org/standard/1275-1994.html>`_. diff --git a/docs/system/riscv/tt_atlantis.rst b/docs/system/riscv/tt_atlantis.rst new file mode 100644 index 0000000000..1f2880d617 --- /dev/null +++ b/docs/system/riscv/tt_atlantis.rst @@ -0,0 +1,41 @@ +Tenstorrent Atlantis (``tt-atlantis``) +====================================== + +The Tenstorrent Atlantis platform is a collaboration between Tenstorrent +and CoreLab Technology. It is based on the Atlantis SoC, which includes +the Ascalon-X CPU and other IP from Tenstorrent and CoreLab Technology. + +The Tenstorrent Ascalon-X is a high performance 64-bit RVA23 compliant +RISC-V CPU. + +tt-atlantis QEMU model features +------------------------------- + +* 8-core Ascalon-X CPU Cluster +* RISC-V compliant Advanced Interrupt Architecture +* 16550A compatible UART + +Known limitations +----------------- + +The QEMU tt-atlantis machine does not yet model every device on the +real platform. Notably: + +* There is no PCI host bridge, so virtio-pci devices cannot be + attached. Boots that need block storage must use ``-initrd`` with + an initramfs. +* The DesignWare UART is modelled with QEMU's ns16550-compatible + ``serial_mm`` device; DesignWare-specific registers beyond that + set return 0. + +Supported software +------------------ + +The Tenstorrent Ascalon CPUs avoid proprietary or non-standard +extensions, so compatibility with existing software is generally +good. The QEMU tt-atlantis machine works with upstream OpenSBI +and Linux with default configurations. + +The development board hardware will require some implementation +specific setup in firmware which is being developed and may +become a requirement or option for the tt-atlantis machine. diff --git a/docs/system/target-hexagon.rst b/docs/system/target-hexagon.rst new file mode 100644 index 0000000000..416b8f7be7 --- /dev/null +++ b/docs/system/target-hexagon.rst @@ -0,0 +1,103 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +.. _Hexagon-System-emulator: + +Hexagon System emulator +----------------------- + +Use the ``qemu-system-hexagon`` executable to simulate a 32-bit Hexagon +machine. + +Hexagon Machines +================ + +Hexagon DSPs are suited to various functions and generally appear in a +"DSP subsystem" of a larger system-on-chip (SoC). + +Hexagon DSPs are often included in a subsystem that looks like the diagram +below. Instructions are loaded into DDR before the DSP is brought out of +reset and the first instructions are fetched from DDR via the EVB/reset vector. + +In a real system, a TBU/SMMU would normally arbitrate AXI accesses but +we don't have a need to model that for QEMU. + +Hexagon DSP cores use simultaneous multithreading (SMT) with as many as 8 +hardware threads. + +.. admonition:: Diagram + + .. code:: text + + AHB (local) bus AXI (global) bus + │ │ + │ │ + ┌─────────┐ │ ┌─────────────────┐ │ + │ L2VIC ├──┤ │ │ │ + │ ├──┼───────► ├───────┤ + └─────▲───┘ │ │ Hexagon DSP │ │ + │ │ │ │ │ ┌─────┐ + │ │ │ N threads │ │ │ DDR │ + │ ├───────┤ │ │ │ │ + ┌────┴──┐ │ │ │ ├────────┤ │ + │QTimer ├───┤ │ │ │ │ │ + │ │ │ │ │ │ │ │ + └───────┘ │ │ ┌─────────┐ │ │ │ │ + │ │ ┌─────────┐│ │ │ │ │ + ┌───────┐ │ │ │ HVX xM ││ │ │ │ │ + │QDSP6SS├───┤ │ │ │┘ │ │ │ │ + └───────┘ │ │ └─────────┘ │ │ └─────┘ + │ │ │ │ + ┌───────┐ │ └─────────────────┘ │ + │ CSR ├───┤ + └───────┘ │ ┌──────┐ ┌───────────┐ + │ │ TCM │ │ VTCM │ + │ │ │ │ + └──────┘ │ │ + │ │ + │ │ + │ │ + └───────────┘ + +Components +---------- +Other than l2vic and HVX, the components below are not implemented in QEMU. + +* L2VIC: the L2 vectored interrupt controller. Supports 1024 input + interrupts, edge- or level-triggered. The core ISA has system registers + ``VID``, ``VID1`` which read through to the L2VIC device. +* QTimer: ARMSSE-based programmable timer device. Its interrupts are + wired to the L2VIC. System registers ``TIMER``, ``UTIMER`` read + through to the QTimer device. +* QDSP6SS: DSP subsystem features, accessible to the entire SoC, including + DSP NMI, watchdog, reset, etc. +* CSR: Configuration/Status Registers. +* TCM: DSP-exclusive tightly-coupled memory. This memory can be used for + DSPs when isolated from DDR and in some bootstrapping modes. +* VTCM: DSP-exclusive vector tightly-coupled memory. This memory is accessed + by some HVX instructions. +* HVX: the vector coprocessor supports 64 and 128-byte vector registers. + 64-byte mode is not implemented in QEMU. + + +Bootstrapping +------------- +Hexagon systems do not generally have access to a block device. So, for +QEMU the typical use case involves loading a binary or ELF file into memory +and executing from the indicated start address:: + + $ qemu-system-hexagon -kernel ./prog -append 'arg1 arg2' + +Semihosting +----------- +Hexagon supports a semihosting interface similar to other architectures'. +The ``trap0`` instruction can activate these semihosting calls so that the +guest software can access the host console and filesystem. Semihosting +is not yet implemented in QEMU hexagon. + + +Hexagon Features +================ +.. toctree:: + hexagon/emulation + hexagon/cdsp + diff --git a/docs/system/target-riscv.rst b/docs/system/target-riscv.rst index 896f14e78b..2639866a3e 100644 --- a/docs/system/target-riscv.rst +++ b/docs/system/target-riscv.rst @@ -72,6 +72,7 @@ undocumented; you can get a complete list by running riscv/mips riscv/shakti-c riscv/sifive_u + riscv/tt_atlantis riscv/virt riscv/xiangshan-kunminghu diff --git a/docs/system/targets.rst b/docs/system/targets.rst index 5b12858b21..5ebdd0f7fe 100644 --- a/docs/system/targets.rst +++ b/docs/system/targets.rst @@ -30,3 +30,4 @@ Contents: target-sparc64 target-i386 target-xtensa + target-hexagon diff --git a/dump/dump.c b/dump/dump.c index eb2ee1c10d..52be7258a5 100644 --- a/dump/dump.c +++ b/dump/dump.c @@ -30,6 +30,7 @@ #include "qemu/main-loop.h" #include "hw/misc/vmcoreinfo.h" #include "migration/blocker.h" +#include "migration/misc.h" #include "hw/core/cpu.h" #include "win_dump.h" #include "qemu/range.h" @@ -1771,7 +1772,8 @@ static void vmcoreinfo_update_phys_base(DumpState *s) if (prefix && g_str_has_prefix(lines[i], prefix)) { if (qemu_strtou64(lines[i] + strlen(prefix), NULL, 16, &phys_base) < 0) { - warn_report("Failed to read %s", prefix); + warn_report("failed to parse %s in VMCOREINFO: '%s'", + prefix, lines[i] + strlen(prefix)); } else { s->dump_info.phys_base = phys_base; } @@ -2080,8 +2082,8 @@ void qmp_dump_guest_memory(bool paging, const char *protocol, bool detach_p = false; bool kdump_raw = false; - if (runstate_check(RUN_STATE_INMIGRATE)) { - error_setg(errp, "Dump not allowed during incoming migration."); + if (migration_guest_ram_loading()) { + error_setg(errp, "Dump not allowed during migration."); return; } diff --git a/dump/win_dump-x86.c b/dump/win_dump-x86.c index 8848c8bfca..0dd0c503c5 100644 --- a/dump/win_dump-x86.c +++ b/dump/win_dump-x86.c @@ -18,11 +18,10 @@ #include "qemu/win_dump_defs.h" #include "win_dump.h" #include "cpu.h" - -bool win_dump_available(Error **errp) -{ - return true; -} +#include "qemu/bswap.h" +#include "hw/misc/vmcoreinfo.h" +#include "migration/misc.h" +#include "standard-headers/linux/qemu_fw_cfg.h" static size_t win_dump_ptr_size(bool x64) { @@ -404,6 +403,46 @@ static void restore_context(WinDumpHeader *h, bool x64, } } +bool win_dump_available(Error **errp) +{ + VMCoreInfoState *vmci = vmcoreinfo_find(); + g_autofree uint8_t *note = NULL; + Error *local_err = NULL; + WinDumpHeader *h; + uint32_t size; + bool x64 = true; + + if (migration_guest_ram_loading()) { + error_setg(errp, "win-dump: not available during migration"); + return false; + } + + if (!vmci || !vmci->has_vmcoreinfo || + le16_to_cpu(vmci->vmcoreinfo.guest_format) != + FW_CFG_VMCOREINFO_FORMAT_ELF) { + error_setg(errp, "win-dump: no vmcoreinfo note from the guest"); + return false; + } + + size = le32_to_cpu(vmci->vmcoreinfo.size); + if (size != VMCOREINFO_WIN_DUMP_NOTE_SIZE32 && + size != VMCOREINFO_WIN_DUMP_NOTE_SIZE64) { + error_setg(errp, "win-dump: invalid vmcoreinfo note size"); + return false; + } + + note = g_malloc(size); + physical_memory_read(le64_to_cpu(vmci->vmcoreinfo.paddr), note, size); + + h = (void *)(note + VMCOREINFO_ELF_NOTE_HDR_SIZE); + if (!check_header(h, &x64, &local_err)) { + error_propagate(errp, local_err); + return false; + } + + return true; +} + void create_win_dump(DumpState *s, Error **errp) { WinDumpHeader *h = (void *)(s->guest_note + VMCOREINFO_ELF_NOTE_HDR_SIZE); diff --git a/fsdev/file-op-9p.h b/fsdev/file-op-9p.h index e8d0661c4b..1d931144f4 100644 --- a/fsdev/file-op-9p.h +++ b/fsdev/file-op-9p.h @@ -81,6 +81,11 @@ typedef struct ExtendedOps { #define V9FS_SEC_MASK 0x0000003C +/* + * Limits the maximum amount of simultaneously open xattr FIDs to prevent + * host memory exhaustion (as each xattr FID contains a xattr value buffer). + */ +#define V9FS_MAX_XATTR_DEFAULT 1024 typedef struct FileOperations FileOperations; typedef struct XattrOperations XattrOperations; @@ -96,6 +101,8 @@ typedef struct FsDriverEntry { FsThrottle fst; mode_t fmode; mode_t dmode; + /* temporary storage for parse_opts only */ + uint32_t max_xattr; } FsDriverEntry; struct FsContext { @@ -109,6 +116,10 @@ struct FsContext { void *private; mode_t fmode; mode_t dmode; + /* max. amount of simultaneously open xattr FIDs */ + uint32_t xattr_fid_limit; + /* current amount of open xattr FIDs */ + uint32_t xattr_fid_count; }; struct V9fsPath { diff --git a/fsdev/qemu-fsdev-opts.c b/fsdev/qemu-fsdev-opts.c index 07a18c6e48..c2c1e83611 100644 --- a/fsdev/qemu-fsdev-opts.c +++ b/fsdev/qemu-fsdev-opts.c @@ -46,6 +46,9 @@ static QemuOptsList qemu_fsdev_opts = { }, { .name = "dmode", .type = QEMU_OPT_NUMBER, + }, { + .name = "max_xattr", + .type = QEMU_OPT_NUMBER, }, THROTTLE_OPTS, @@ -92,6 +95,9 @@ static QemuOptsList qemu_virtfs_opts = { }, { .name = "dmode", .type = QEMU_OPT_NUMBER, + }, { + .name = "max_xattr", + .type = QEMU_OPT_NUMBER, }, { /*End of list */ } diff --git a/fsdev/qemu-fsdev.c b/fsdev/qemu-fsdev.c index 57877dad0a..f97103cf44 100644 --- a/fsdev/qemu-fsdev.c +++ b/fsdev/qemu-fsdev.c @@ -45,7 +45,7 @@ typedef struct FsDriverListEntry { static QTAILQ_HEAD(, FsDriverListEntry) fsdriver_entries = QTAILQ_HEAD_INITIALIZER(fsdriver_entries); -#define COMMON_FS_DRIVER_OPTIONS "id", "fsdriver", "readonly" +#define COMMON_FS_DRIVER_OPTIONS "id", "fsdriver", "readonly", "max_xattr" static FsDriverTable FsDrivers[] = { { diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c index aa48306b0e..ee592b62f8 100644 --- a/hw/9pfs/9p-local.c +++ b/hw/9pfs/9p-local.c @@ -775,8 +775,11 @@ static int local_fid_fd(int fid_type, V9fsFidOpenState *fs) { if (fid_type == P9_FID_DIR) { return dirfd(fs->dir.stream); - } else { + } else if (fid_type == P9_FID_FILE) { return fs->fd; + } else { + errno = EBADF; + return -1; } } @@ -1527,6 +1530,15 @@ static int local_parse_opts(QemuOpts *opts, FsDriverEntry *fse, Error **errp) const char *path = qemu_opt_get(opts, "path"); const char *multidevs = qemu_opt_get(opts, "multidevs"); + uint64_t val = qemu_opt_get_number(opts, "max_xattr", + V9FS_MAX_XATTR_DEFAULT); + if (val > UINT32_MAX) { + error_setg(errp, "max_xattr value '%s' too large", + qemu_opt_get(opts, "max_xattr")); + return -1; + } + fse->max_xattr = val; + if (!sec_model) { error_setg(errp, "security_model property not set"); error_append_security_model_hint(errp); diff --git a/hw/9pfs/9p-synth.c b/hw/9pfs/9p-synth.c index b3743f6169..3b3654b282 100644 --- a/hw/9pfs/9p-synth.c +++ b/hw/9pfs/9p-synth.c @@ -25,6 +25,8 @@ #include "qemu/rcu_queue.h" #include "qemu/cutils.h" #include "system/qtest.h" +#include "qapi/error.h" +#include "qemu/option.h" /* Root node for synth file system */ static V9fsSynthNode synth_root = { @@ -475,15 +477,15 @@ static int synth_lsetxattr(FsContext *ctx, V9fsPath *path, const char *name, void *value, size_t size, int flags) { - errno = ENOTSUP; - return -1; + /* pretend it worked */ + return 0; } static int synth_lremovexattr(FsContext *ctx, V9fsPath *path, const char *name) { - errno = ENOTSUP; - return -1; + /* pretend it worked */ + return 0; } static int synth_name_to_path(FsContext *ctx, V9fsPath *dir_path, @@ -563,6 +565,19 @@ static ssize_t v9fs_synth_qtest_flush_write(void *buf, int len, off_t offset, return 1; } +/* transmits internal xattr counter to client */ +static ssize_t v9fs_synth_read_xattr_count(void *buf, int len, off_t offset, + void *arg) +{ + FsContext *ctx = arg; + size_t local_count = ctx->xattr_fid_count; + if (len < (int)sizeof(size_t)) { + return -ENOSPC; + } + memcpy(buf, &local_count, sizeof(size_t)); + return sizeof(size_t); +} + static int synth_init(FsContext *ctx, Error **errp) { QLIST_INIT(&synth_root.child); @@ -624,7 +639,34 @@ static int synth_init(FsContext *ctx, Error **errp) g_free(name); } } + + /* Directory for internal statistic queries */ + { + V9fsSynthNode *stat_dir = NULL; + ret = qemu_v9fs_synth_mkdir(NULL, 0755, "stat", &stat_dir); + assert(!ret); + + /* File for internal xattr count query */ + ret = qemu_v9fs_synth_add_file(stat_dir, 0444, "xattr_count", + v9fs_synth_read_xattr_count, + NULL, ctx); + assert(!ret); + } + } + + return 0; +} + +static int synth_parse_opts(QemuOpts *opts, FsDriverEntry *fse, Error **errp) +{ + uint64_t val = qemu_opt_get_number(opts, "max_xattr", + V9FS_MAX_XATTR_DEFAULT); + if (val > UINT32_MAX) { + error_setg(errp, "max_xattr value '%s' too large", + qemu_opt_get(opts, "max_xattr")); + return -1; } + fse->max_xattr = val; return 0; } @@ -635,6 +677,7 @@ static bool synth_has_valid_file_handle(int fid_type, V9fsFidOpenState *fs) } FileOperations synth_ops = { + .parse_opts = synth_parse_opts, .init = synth_init, .lstat = synth_lstat, .readlink = synth_readlink, diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c index b486cce48a..3119f01117 100644 --- a/hw/9pfs/9p.c +++ b/hw/9pfs/9p.c @@ -265,6 +265,31 @@ static size_t v9fs_string_size(V9fsString *str) return str->size; } +static int xattr_fid_count_inc(V9fsPDU *pdu) +{ + V9fsState *s = pdu->s; + + if (s->ctx.xattr_fid_limit > 0 && + s->ctx.xattr_fid_count >= s->ctx.xattr_fid_limit) { + error_report_once("9pfs: xattr_fid_count limit exceeded " + "(configurable by option 'max_xattr')."); + return -ENOSPC; + } + s->ctx.xattr_fid_count++; + return 0; +} + +static void xattr_fid_count_decr(V9fsPDU *pdu) +{ + V9fsState *s = pdu->s; + + if (s->ctx.xattr_fid_count > 0) { + s->ctx.xattr_fid_count--; + } else { + error_report_once("9pfs: xattr_fid_count underflow detected"); + } +} + /* * returns 0 if fid got re-opened, 1 if not, < 0 on error */ @@ -397,6 +422,7 @@ static int coroutine_fn free_fid(V9fsPDU *pdu, V9fsFidState *fidp) } } else if (fidp->fid_type == P9_FID_XATTR) { retval = v9fs_xattr_fid_clunk(pdu, fidp); + xattr_fid_count_decr(pdu); } v9fs_path_free(&fidp->path); g_free(fidp); @@ -634,6 +660,14 @@ static void coroutine_fn virtfs_reset(V9fsPDU *pdu) fidp->clunked = true; put_fid(pdu, fidp); } + + /* + * Explicitly reset the xattr FID counter. + * + * free_fid() already decrements the counter for each P9_FID_XATTR, so the + * counter should already be zero, hence this is just a defensive measure. + */ + s->ctx.xattr_fid_count = 0; } #define P9_QID_TYPE_DIR 0x80 @@ -1469,6 +1503,16 @@ static void coroutine_fn v9fs_version(void *opaque) goto out; } + /* cap msize to transport's theoretical limit */ + if (s->transport->msize_limit) { + size_t limit = s->transport->msize_limit(s); + if (s->msize > limit) { + s->msize = limit; + warn_report_once("9p: client msize capped to %zu (transport limit)", + limit); + } + } + /* 8192 is the default msize of Linux clients */ if (s->msize <= 8192 && !(s->ctx.export_flags & V9FS_NO_PERF_WARN)) { warn_report_once( @@ -2261,10 +2305,15 @@ static void coroutine_fn v9fs_fsync(void *opaque) err = -ENOENT; goto out_nofid; } + if (!fid_has_valid_file_handle(pdu->s, fidp)) { + err = -EBADF; + goto out; + } err = v9fs_co_fsync(pdu, fidp, datasync); if (!err) { err = offset; } +out: put_fid(pdu, fidp); out_nofid: pdu_complete(pdu, err); @@ -2669,6 +2718,7 @@ static void coroutine_fn v9fs_readdir(void *opaque) uint32_t max_count; V9fsPDU *pdu = opaque; V9fsState *s = pdu->s; + size_t max_resp_sz; retval = pdu_unmarshal(pdu, offset, "dqd", &fid, &initial_offset, &max_count); @@ -2677,9 +2727,28 @@ static void coroutine_fn v9fs_readdir(void *opaque) } trace_v9fs_readdir(pdu->tag, pdu->id, fid, initial_offset, max_count); + max_resp_sz = s->msize; + + /* + * Constrain max_count to transport's current, actual response buffer size. + * A bad client might provide a response buffer < msize. + */ + if (s->transport->response_buffer_size) { + size_t buf_size = s->transport->response_buffer_size(pdu); + if (max_resp_sz > buf_size) { + max_resp_sz = buf_size; + } + } + /* Enough space for a R_readdir header: size[4] Rreaddir tag[2] count[4] */ - if (max_count > s->msize - 11) { - max_count = s->msize - 11; + if (max_resp_sz > 11) { + max_resp_sz -= 11; + } else { + max_resp_sz = 0; + } + + if (max_count > max_resp_sz) { + max_count = max_resp_sz; warn_report_once( "9p: bad client: T_readdir with count > msize - 11" ); @@ -3576,6 +3645,10 @@ static void coroutine_fn v9fs_wstat(void *opaque) } /* do we need to sync the file? */ if (donttouch_stat(&v9stat)) { + if (!fid_has_valid_file_handle(s, fidp)) { + err = -EBADF; + goto out; + } err = v9fs_co_fsync(pdu, fidp, 0); goto out; } @@ -3835,6 +3908,10 @@ static void coroutine_fn v9fs_lock(void *opaque) err = -ENOENT; goto out_nofid; } + if (!fid_has_valid_file_handle(pdu->s, fidp)) { + err = -EBADF; + goto out; + } err = v9fs_co_fstat(pdu, fidp, &stbuf); if (err < 0) { goto out; @@ -3880,6 +3957,10 @@ static void coroutine_fn v9fs_getlock(void *opaque) err = -ENOENT; goto out_nofid; } + if (!fid_has_valid_file_handle(pdu->s, fidp)) { + err = -EBADF; + goto out; + } err = v9fs_co_fstat(pdu, fidp, &stbuf); if (err < 0) { goto out; @@ -3993,6 +4074,14 @@ static void coroutine_fn v9fs_xattrwalk(void *opaque) clunk_fid(s, xattr_fidp->fid); goto out; } + + /* Check xattr FID limit */ + err = xattr_fid_count_inc(pdu); + if (err < 0) { + clunk_fid(s, xattr_fidp->fid); + goto out; + } + /* * Read the xattr value */ @@ -4000,6 +4089,7 @@ static void coroutine_fn v9fs_xattrwalk(void *opaque) xattr_fidp->fid_type = P9_FID_XATTR; xattr_fidp->fs.xattr.xattrwalk_fid = true; xattr_fidp->fs.xattr.value = g_malloc0(size); + if (size) { err = v9fs_co_llistxattr(pdu, &xattr_fidp->path, xattr_fidp->fs.xattr.value, @@ -4026,6 +4116,14 @@ static void coroutine_fn v9fs_xattrwalk(void *opaque) clunk_fid(s, xattr_fidp->fid); goto out; } + + /* Check xattr FID limit */ + err = xattr_fid_count_inc(pdu); + if (err < 0) { + clunk_fid(s, xattr_fidp->fid); + goto out; + } + /* * Read the xattr value */ @@ -4033,6 +4131,7 @@ static void coroutine_fn v9fs_xattrwalk(void *opaque) xattr_fidp->fid_type = P9_FID_XATTR; xattr_fidp->fs.xattr.xattrwalk_fid = true; xattr_fidp->fs.xattr.value = g_malloc0(size); + if (size) { err = v9fs_co_lgetxattr(pdu, &xattr_fidp->path, &name, xattr_fidp->fs.xattr.value, @@ -4134,6 +4233,12 @@ static void coroutine_fn v9fs_xattrcreate(void *opaque) goto out_put_fid; } + /* Check xattr FID limit */ + err = xattr_fid_count_inc(pdu); + if (err < 0) { + goto out_put_fid; + } + /* Make the file fid point to xattr */ xattr_fidp = file_fidp; xattr_fidp->fid_type = P9_FID_XATTR; @@ -4395,6 +4500,10 @@ int v9fs_device_realize_common(V9fsState *s, const V9fsTransport *t, s->reclaiming = false; + /* init xattr FID limit from fsdev config */ + s->ctx.xattr_fid_limit = fse->max_xattr; + s->ctx.xattr_fid_count = 0; + rc = 0; out: if (rc) { diff --git a/hw/9pfs/9p.h b/hw/9pfs/9p.h index b2df659b0e..1a309664f6 100644 --- a/hw/9pfs/9p.h +++ b/hw/9pfs/9p.h @@ -481,6 +481,8 @@ struct V9fsTransport { void (*init_out_iov_from_pdu)(V9fsPDU *pdu, struct iovec **piov, unsigned int *pniov, size_t size); void (*push_and_notify)(V9fsPDU *pdu); + size_t (*msize_limit)(V9fsState *s); + size_t (*response_buffer_size)(V9fsPDU *pdu); }; #endif diff --git a/hw/9pfs/virtio-9p-device.c b/hw/9pfs/virtio-9p-device.c index 9f70e2338c..50dc93091d 100644 --- a/hw/9pfs/virtio-9p-device.c +++ b/hw/9pfs/virtio-9p-device.c @@ -192,12 +192,29 @@ static void virtio_init_out_iov_from_pdu(V9fsPDU *pdu, struct iovec **piov, *pniov = elem->out_num; } +static size_t virtio_9p_msize_limit(V9fsState *s) +{ + const size_t guestPageSize = 4096; + return (VIRTQUEUE_MAX_SIZE - 2) * guestPageSize; +} + +static size_t virtio_9p_response_buffer_size(V9fsPDU *pdu) +{ + V9fsState *s = pdu->s; + V9fsVirtioState *v = container_of(s, V9fsVirtioState, state); + VirtQueueElement *elem = v->elems[pdu->idx]; + + return iov_size(elem->in_sg, elem->in_num); +} + static const V9fsTransport virtio_9p_transport = { .pdu_vmarshal = virtio_pdu_vmarshal, .pdu_vunmarshal = virtio_pdu_vunmarshal, .init_in_iov_from_pdu = virtio_init_in_iov_from_pdu, .init_out_iov_from_pdu = virtio_init_out_iov_from_pdu, .push_and_notify = virtio_9p_push_and_notify, + .msize_limit = virtio_9p_msize_limit, + .response_buffer_size = virtio_9p_response_buffer_size, }; static void virtio_9p_device_realize(DeviceState *dev, Error **errp) diff --git a/hw/9pfs/xen-9p-backend.c b/hw/9pfs/xen-9p-backend.c index ca0fff5fa9..24c90d97ec 100644 --- a/hw/9pfs/xen-9p-backend.c +++ b/hw/9pfs/xen-9p-backend.c @@ -250,12 +250,43 @@ static void xen_9pfs_push_and_notify(V9fsPDU *pdu) qemu_bh_schedule(ring->bh); } +static size_t xen_9p_msize_limit(V9fsState *s) +{ + Xen9pfsDev *xen_9pfs = container_of(s, Xen9pfsDev, state); + size_t limit; + int i; + + if (!xen_9pfs->num_rings) { + return 0; + } + + limit = XEN_FLEX_RING_SIZE(xen_9pfs->rings[0].ring_order); + for (i = 1; i < xen_9pfs->num_rings; i++) { + limit = MIN(limit, XEN_FLEX_RING_SIZE(xen_9pfs->rings[i].ring_order)); + } + + return limit; +} + +static size_t xen_9pfs_response_buffer_size(V9fsPDU *pdu) +{ + Xen9pfsDev *priv = container_of(pdu->s, Xen9pfsDev, state); + Xen9pfsRing *ring = &priv->rings[pdu->tag % priv->num_rings]; + struct iovec in_sg[2]; + int num; + + xen_9pfs_in_sg(ring, in_sg, &num, pdu->idx, 0); + return iov_size(in_sg, num); +} + static const V9fsTransport xen_9p_transport = { .pdu_vmarshal = xen_9pfs_pdu_vmarshal, .pdu_vunmarshal = xen_9pfs_pdu_vunmarshal, .init_in_iov_from_pdu = xen_9pfs_init_in_iov_from_pdu, .init_out_iov_from_pdu = xen_9pfs_init_out_iov_from_pdu, .push_and_notify = xen_9pfs_push_and_notify, + .msize_limit = xen_9p_msize_limit, + .response_buffer_size = xen_9pfs_response_buffer_size, }; static int xen_9pfs_init(struct XenLegacyDevice *xendev) diff --git a/hw/Kconfig b/hw/Kconfig index c109f5537b..c92ca2b13a 100644 --- a/hw/Kconfig +++ b/hw/Kconfig @@ -53,6 +53,7 @@ source arm/Kconfig source cpu/Kconfig source alpha/Kconfig source avr/Kconfig +source hexagon/Kconfig source hppa/Kconfig source i386/Kconfig source loongarch/Kconfig diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 500bfdfe2a..5f5c4899ad 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -520,6 +520,7 @@ config FSL_IMX6 select IMX_FEC select IMX_I2C select IMX_USBPHY + select CAN_FLEXCAN select WDT_IMX2 select PL310 # cache controller select PCI_EXPRESS_DESIGNWARE diff --git a/hw/arm/fsl-imx6.c b/hw/arm/fsl-imx6.c index f663ddbf0a..bf106a9063 100644 --- a/hw/arm/fsl-imx6.c +++ b/hw/arm/fsl-imx6.c @@ -90,6 +90,10 @@ static void fsl_imx6_init(Object *obj) snprintf(name, NAME_SIZE, "spi%d", i + 1); object_initialize_child(obj, name, &s->spi[i], TYPE_IMX_SPI); } + for (i = 0; i < FSL_IMX6_NUM_CANS; i++) { + snprintf(name, NAME_SIZE, "flexcan%d", i + 1); + object_initialize_child(obj, name, &s->flexcan[i], TYPE_CAN_FLEXCAN); + } for (i = 0; i < FSL_IMX6_NUM_WDTS; i++) { snprintf(name, NAME_SIZE, "wdt%d", i); object_initialize_child(obj, name, &s->wdt[i], TYPE_IMX2_WDT); @@ -377,6 +381,27 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in(gic, spi_table[i].irq)); } + /* Initialize all FLEXCANs */ + for (i = 0; i < FSL_IMX6_NUM_CANS; i++) { + static const struct { + hwaddr addr; + unsigned int irq; + } flexcan_table[FSL_IMX6_NUM_CANS] = { + { FSL_IMX6_CAN1_ADDR, FSL_IMX6_FLEXCAN1_IRQ }, + { FSL_IMX6_CAN2_ADDR, FSL_IMX6_FLEXCAN2_IRQ }, + }; + + s->flexcan[i].ccm = IMX_CCM(&s->ccm); + object_property_set_link(OBJECT(&s->flexcan[i]), "canbus", + OBJECT(s->canbus[i]), &error_abort); + + sysbus_realize(SYS_BUS_DEVICE(&s->flexcan[i]), &error_abort); + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->flexcan[i]), 0, flexcan_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->flexcan[i]), 0, + qdev_get_gpio_in(gic, flexcan_table[i].irq)); + } + object_property_set_uint(OBJECT(&s->eth), "phy-num", s->phy_num, &error_abort); qemu_configure_nic_device(DEVICE(&s->eth), true, NULL); @@ -480,6 +505,10 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) static const Property fsl_imx6_properties[] = { DEFINE_PROP_UINT32("fec-phy-num", FslIMX6State, phy_num, 0), + DEFINE_PROP_LINK("canbus0", FslIMX6State, canbus[0], TYPE_CAN_BUS, + CanBusState *), + DEFINE_PROP_LINK("canbus1", FslIMX6State, canbus[1], TYPE_CAN_BUS, + CanBusState *), }; static void fsl_imx6_class_init(ObjectClass *oc, const void *data) diff --git a/hw/arm/sabrelite.c b/hw/arm/sabrelite.c index db5669c5c2..84bbc2f979 100644 --- a/hw/arm/sabrelite.c +++ b/hw/arm/sabrelite.c @@ -20,6 +20,16 @@ #include "qemu/error-report.h" #include "system/qtest.h" +struct SabreliteMachineState { + MachineState parent_obj; + + FslIMX6State soc; + CanBusState *canbus[FSL_IMX6_NUM_CANS]; +}; + +#define TYPE_SABRELITE_MACHINE MACHINE_TYPE_NAME("sabrelite") +OBJECT_DECLARE_SIMPLE_TYPE(SabreliteMachineState, SABRELITE_MACHINE) + static struct arm_boot_info sabrelite_binfo = { /* DDR memory start */ .loader_start = FSL_IMX6_MMDC_ADDR, @@ -41,7 +51,7 @@ static void sabrelite_reset_secondary(ARMCPU *cpu, static void sabrelite_init(MachineState *machine) { - FslIMX6State *s; + SabreliteMachineState *s = SABRELITE_MACHINE(machine); /* Check the amount of memory is compatible with the SOC */ if (machine->ram_size > FSL_IMX6_MMDC_SIZE) { @@ -50,13 +60,19 @@ static void sabrelite_init(MachineState *machine) exit(1); } - s = FSL_IMX6(object_new(TYPE_FSL_IMX6)); - object_property_add_child(OBJECT(machine), "soc", OBJECT(s)); + object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_FSL_IMX6); /* Ethernet PHY address is 6 */ - object_property_set_int(OBJECT(s), "fec-phy-num", 6, &error_fatal); + object_property_set_int(OBJECT(&s->soc), "fec-phy-num", 6, &error_fatal); - qdev_realize(DEVICE(s), NULL, &error_fatal); + for (int i = 0; i < FSL_IMX6_NUM_CANS; i++) { + g_autofree char *bus_name = g_strdup_printf("canbus%d", i); + + object_property_set_link(OBJECT(&s->soc), bus_name, + OBJECT(s->canbus[i]), &error_fatal); + } + + qdev_realize(DEVICE(&s->soc), NULL, &error_fatal); memory_region_add_subregion(get_system_memory(), FSL_IMX6_MMDC_ADDR, machine->ram); @@ -70,7 +86,7 @@ static void sabrelite_init(MachineState *machine) /* Add the sst25vf016b NOR FLASH memory to first SPI */ Object *spi_dev; - spi_dev = object_resolve_path_component(OBJECT(s), "spi1"); + spi_dev = object_resolve_path_component(OBJECT(&s->soc), "spi1"); if (spi_dev) { SSIBus *spi_bus; @@ -89,7 +105,7 @@ static void sabrelite_init(MachineState *machine) qdev_realize_and_unref(flash_dev, BUS(spi_bus), &error_fatal); cs_line = qdev_get_gpio_in_named(flash_dev, SSI_GPIO_CS, 0); - qdev_connect_gpio_out(DEVICE(&s->gpio[2]), 19, cs_line); + qdev_connect_gpio_out(DEVICE(&s->soc.gpio[2]), 19, cs_line); } } } @@ -100,12 +116,29 @@ static void sabrelite_init(MachineState *machine) sabrelite_binfo.secondary_cpu_reset_hook = sabrelite_reset_secondary; if (!qtest_enabled()) { - arm_load_kernel(&s->cpu[0], machine, &sabrelite_binfo); + arm_load_kernel(&s->soc.cpu[0], machine, &sabrelite_binfo); } } -static void sabrelite_machine_init(MachineClass *mc) +static void sabrelite_machine_instance_init(Object *obj) +{ + SabreliteMachineState *s = SABRELITE_MACHINE(obj); + + object_property_add_link(obj, "canbus0", TYPE_CAN_BUS, + (Object **)&s->canbus[0], + object_property_allow_set_link, + 0); + + object_property_add_link(obj, "canbus1", TYPE_CAN_BUS, + (Object **)&s->canbus[1], + object_property_allow_set_link, + 0); +} + +static void sabrelite_machine_class_init(ObjectClass *oc, const void *data) { + MachineClass *mc = MACHINE_CLASS(oc); + mc->desc = "Freescale i.MX6 Quad SABRE Lite Board (Cortex-A9)"; mc->init = sabrelite_init; mc->max_cpus = FSL_IMX6_NUM_CPUS; @@ -114,4 +147,19 @@ static void sabrelite_machine_init(MachineClass *mc) mc->auto_create_sdcard = true; } -DEFINE_MACHINE_ARM("sabrelite", sabrelite_machine_init) +static const TypeInfo sabrelite_machine_init_typeinfo = { + .name = TYPE_SABRELITE_MACHINE, + .parent = TYPE_MACHINE, + .class_init = sabrelite_machine_class_init, + .instance_init = sabrelite_machine_instance_init, + .instance_size = sizeof(SabreliteMachineState), + .abstract = false, + .interfaces = arm_machine_interfaces, +}; + +static void sabrelite_machine_init_register_types(void) +{ + type_register_static(&sabrelite_machine_init_typeinfo); +} + +type_init(sabrelite_machine_init_register_types) diff --git a/hw/core/irq.c b/hw/core/irq.c index 106805e241..1b610e75e1 100644 --- a/hw/core/irq.c +++ b/hw/core/irq.c @@ -32,6 +32,9 @@ void qemu_set_irq(qemu_irq irq, int level) return; irq->handler(irq->opaque, irq->n, level); + if (unlikely(irq->observer)) { + irq->observer(irq->opaque, irq->n, level); + } } static void init_irq_fields(IRQState *irq, qemu_irq_handler handler, @@ -111,7 +114,7 @@ static void qemu_notirq(void *opaque, int line, int level) { IRQState *irq = opaque; - irq->handler(irq->opaque, irq->n, !level); + qemu_set_irq(irq, !level); } qemu_irq qemu_irq_invert(qemu_irq irq) @@ -121,14 +124,11 @@ qemu_irq qemu_irq_invert(qemu_irq irq) return qemu_allocate_irq(qemu_notirq, irq, 0); } -void qemu_irq_intercept_in(qemu_irq *gpio_in, qemu_irq_handler handler, int n) +void qemu_irq_set_observer(qemu_irq *gpio_in, qemu_irq_handler handler, int n) { int i; - qemu_irq *old_irqs = qemu_allocate_irqs(NULL, NULL, n); for (i = 0; i < n; i++) { - *old_irqs[i] = *gpio_in[i]; - gpio_in[i]->handler = handler; - gpio_in[i]->opaque = &old_irqs[i]; + gpio_in[i]->observer = handler; } } diff --git a/hw/core/machine.c b/hw/core/machine.c index 9a10e45aab..5b97df6db9 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c @@ -497,6 +497,8 @@ static void machine_set_memory_encryption(Object *obj, const char *value, Object *cgs = object_resolve_path_component(object_get_objects_root(), value); + warn_report("memory-encryption is deprecated, use confidential-guest-support instead"); + if (!cgs) { error_setg(errp, "No such memory encryption object '%s'", value); return; diff --git a/hw/display/vga.c b/hw/display/vga.c index 3ea20d7b0f..abe3f8e077 100644 --- a/hw/display/vga.c +++ b/hw/display/vga.c @@ -45,8 +45,10 @@ bool have_vga = true; -/* 16 state changes per vertical frame @60 Hz */ +/* frame counter bit 4: cursor blink toggles every 16 frames @60 Hz */ #define VGA_TEXT_CURSOR_PERIOD_MS (1000 * 2 * 16 / 60) +/* frame counter bit 5: character blink toggles every 32 frames @60 Hz */ +#define VGA_TEXT_BLINK_PERIOD_MS (1000 * 2 * 32 / 60) /* Address mask for non-VESA modes. */ #define VGA_VRAM_SIZE (256 * KiB) @@ -1190,7 +1192,6 @@ static void vga_get_text_resolution(VGACommonState *s, int *pwidth, int *pheight * - double scan * - double width * - underline - * - flashing */ static void vga_draw_text(VGACommonState *s, int full_update) { @@ -1286,6 +1287,13 @@ static void vga_draw_text(VGACommonState *s, int full_update) s->cursor_blink_time = now + VGA_TEXT_CURSOR_PERIOD_MS / 2; s->cursor_visible_phase = !s->cursor_visible_phase; } + if (now >= s->blink_time) { + s->blink_time = now + VGA_TEXT_BLINK_PERIOD_MS / 2; + s->blink_visible_phase = !s->blink_visible_phase; + if (s->ar[VGA_ATC_MODE] & 0x08) { + full_update = 1; + } + } dest = surface_data(surface); linesize = surface_stride(surface); @@ -1317,8 +1325,17 @@ static void vga_draw_text(VGACommonState *s, int full_update) #endif font_ptr = font_base[(cattr >> 3) & 1]; font_ptr += 32 * 4 * ch; - bgcol = palette[cattr >> 4]; - fgcol = palette[cattr & 0x0f]; + if (s->ar[VGA_ATC_MODE] & 0x08) { + bgcol = palette[(cattr >> 4) & 0x07]; + if ((cattr & 0x80) && !s->blink_visible_phase) { + fgcol = bgcol; + } else { + fgcol = palette[cattr & 0x0f]; + } + } else { + bgcol = palette[cattr >> 4]; + fgcol = palette[cattr & 0x0f]; + } if (cw == 16) { vga_draw_glyph16(d1, linesize, font_ptr, cheight, fgcol, bgcol); diff --git a/hw/display/vga_int.h b/hw/display/vga_int.h index 747b5cc6cf..5664317ecd 100644 --- a/hw/display/vga_int.h +++ b/hw/display/vga_int.h @@ -130,6 +130,8 @@ typedef struct VGACommonState { uint8_t cursor_start, cursor_end; bool cursor_visible_phase; int64_t cursor_blink_time; + bool blink_visible_phase; + int64_t blink_time; uint32_t cursor_offset; const GraphicHwOps *hw_ops; bool full_update_text; diff --git a/hw/hexagon/Kconfig b/hw/hexagon/Kconfig new file mode 100644 index 0000000000..52065ab3b2 --- /dev/null +++ b/hw/hexagon/Kconfig @@ -0,0 +1,14 @@ +config HEX_DSP + bool + default y + depends on HEXAGON + +config HEX_VIRT + bool + default y + depends on HEX_DSP && FDT + select DEVICE_TREE + select VIRTIO_MMIO + select PL011 + select VIRTIO_BLK + select VIRTIO_SCSI diff --git a/hw/hexagon/hexagon_dsp.c b/hw/hexagon/hexagon_dsp.c new file mode 100644 index 0000000000..aa49399322 --- /dev/null +++ b/hw/hexagon/hexagon_dsp.c @@ -0,0 +1,225 @@ +/* + * Hexagon DSP Subsystem emulation. This represents a generic DSP + * subsystem with few peripherals, like the Compute DSP. + * + * 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 "system/address-spaces.h" +#include "hw/core/boards.h" +#include "hw/core/qdev-properties.h" +#include "hw/hexagon/hexagon.h" +#include "hw/hexagon/hexagon_globalreg.h" +#include "hw/hexagon/hexagon_tlb.h" +#include "hw/core/loader.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "elf.h" +#include "cpu.h" +#include "migration/cpu.h" +#include "system/system.h" +#include "target/hexagon/internal.h" +#include "system/physmem.h" +#include "system/reset.h" + +#include "machine_cfg_v66g_1024.h.inc" + +#define TYPE_HEXAGON_DSP_MACHINE "hexagon-dsp-machine" +OBJECT_DECLARE_SIMPLE_TYPE(HexagonDspMachineState, HEXAGON_DSP_MACHINE) + +struct HexagonDspMachineState { + HexagonCommonMachineState parent_obj; + + hwaddr isdb_secure_flag; + hwaddr isdb_trusted_flag; +}; + +static HexagonDspMachineState *current_dms; + +static void hex_symbol_callback(const char *st_name, int st_info, + uint64_t st_value, uint64_t st_size) +{ + if (!g_strcmp0("isdb_secure_flag", st_name)) { + current_dms->isdb_secure_flag = st_value; + } + if (!g_strcmp0("isdb_trusted_flag", st_name)) { + current_dms->isdb_trusted_flag = st_value; + } +} + +/* Board init. */ +static struct hexagon_board_boot_info hexagon_binfo; + +static void hexagon_load_kernel(HexagonDspMachineState *dms, HexagonCPU *cpu) +{ + uint64_t pentry; + long kernel_size; + + current_dms = dms; + kernel_size = load_elf_ram_sym(hexagon_binfo.kernel_filename, NULL, NULL, + NULL, &pentry, NULL, NULL, + &hexagon_binfo.kernel_elf_flags, 0, EM_HEXAGON, 0, 0, + &address_space_memory, false, hex_symbol_callback); + current_dms = NULL; + + if (kernel_size <= 0) { + error_report("no kernel file '%s'", + hexagon_binfo.kernel_filename); + exit(1); + } + + qdev_prop_set_uint32(DEVICE(cpu), "exec-start-addr", pentry); +} + +static void hexagon_init_bootstrap(HexagonDspMachineState *dms, HexagonCPU *cpu) +{ + MachineState *machine = MACHINE(dms); + + if (machine->kernel_filename) { + uint32_t mem = 1; + + hexagon_load_kernel(dms, cpu); + if (dms->isdb_secure_flag) { + physical_memory_write(dms->isdb_secure_flag, + &mem, sizeof(mem)); + } + if (dms->isdb_trusted_flag) { + physical_memory_write(dms->isdb_trusted_flag, + &mem, sizeof(mem)); + } + } +} + +static void do_cpu_reset(void *opaque) +{ + HexagonCPU *cpu = opaque; + CPUState *cs = CPU(cpu); + cpu_reset(cs); +} + +static void hexagon_common_init(MachineState *machine, Rev_t rev, + const struct hexagon_machine_config *m_cfg) +{ + HexagonCommonMachineState *hms = HEXAGON_COMMON_MACHINE(machine); + HexagonDspMachineState *dms = HEXAGON_DSP_MACHINE(machine); + MemoryRegion *address_space; + DeviceState *glob_regs_dev; + DeviceState *tlb_dev; + + memset(&hexagon_binfo, 0, sizeof(hexagon_binfo)); + if (machine->kernel_filename) { + hexagon_binfo.ram_size = machine->ram_size; + hexagon_binfo.kernel_filename = machine->kernel_filename; + } + + machine->enable_graphics = 0; + + address_space = get_system_memory(); + + memory_region_init_rom(&hms->cfgtable_rom, NULL, "config_table.rom", + sizeof(m_cfg->cfgtable), &error_fatal); + memory_region_add_subregion(address_space, m_cfg->cfgbase, + &hms->cfgtable_rom); + + memory_region_init_ram(&hms->ram, NULL, "ddr.ram", + machine->ram_size, &error_fatal); + memory_region_add_subregion(address_space, 0x0, &hms->ram); + + glob_regs_dev = qdev_new(TYPE_HEXAGON_GLOBALREG); + object_property_add_child(OBJECT(machine), "global-regs", + OBJECT(glob_regs_dev)); + qdev_prop_set_uint64(glob_regs_dev, "config-table-addr", m_cfg->cfgbase); + qdev_prop_set_uint32(glob_regs_dev, "dsp-rev", rev); + sysbus_realize_and_unref(SYS_BUS_DEVICE(glob_regs_dev), &error_fatal); + + tlb_dev = qdev_new(TYPE_HEXAGON_TLB); + object_property_add_child(OBJECT(machine), "tlb", OBJECT(tlb_dev)); + qdev_prop_set_uint32(tlb_dev, "num-entries", + m_cfg->cfgtable.jtlb_size_entries); + sysbus_realize_and_unref(SYS_BUS_DEVICE(tlb_dev), &error_fatal); + + for (int i = 0; i < machine->smp.cpus; i++) { + HexagonCPU *cpu = HEXAGON_CPU(object_new(machine->cpu_type)); + qemu_register_reset(do_cpu_reset, cpu); + + /* + * CPU #0 is the only CPU running at boot, others must be + * explicitly enabled via start instruction. + */ + qdev_prop_set_bit(DEVICE(cpu), "start-powered-off", (i != 0)); + if (i == 0) { + hexagon_init_bootstrap(dms, cpu); + } + object_property_set_link(OBJECT(cpu), "global-regs", + OBJECT(glob_regs_dev), &error_fatal); + object_property_set_link(OBJECT(cpu), "tlb", + OBJECT(tlb_dev), &error_fatal); + qdev_realize_and_unref(DEVICE(cpu), NULL, &error_fatal); + } + + rom_add_blob_fixed_as("config_table.rom", &m_cfg->cfgtable, + sizeof(m_cfg->cfgtable), m_cfg->cfgbase, + &address_space_memory); +} + +static void init_mc(MachineClass *mc) +{ + mc->block_default_type = IF_SD; + mc->default_ram_size = 4 * GiB; + mc->no_parallel = 1; + mc->no_floppy = 1; + mc->no_cdrom = 1; + mc->no_serial = 1; + mc->is_default = false; + mc->max_cpus = 8; +} + +/* ----------------------------------------------------------------- */ +/* Core-specific configuration settings are defined below this line. */ +/* Config table values defined in machine_configs.h.inc */ +/* ----------------------------------------------------------------- */ + +static void v66g_1024_config_init(MachineState *machine) +{ + hexagon_common_init(machine, v66_rev, &v66g_1024); +} + +static void v66g_1024_init(ObjectClass *oc, const void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + + mc->desc = "Hexagon V66G_1024"; + mc->init = v66g_1024_config_init; + init_mc(mc); + mc->is_default = true; + mc->default_cpu_type = TYPE_HEXAGON_CPU_V66; + mc->default_cpus = 4; +} + +static const TypeInfo hexagon_machine_types[] = { + { + .name = TYPE_HEXAGON_COMMON_MACHINE, + .parent = TYPE_MACHINE, + .instance_size = sizeof(HexagonCommonMachineState), + .abstract = true, + }, + { + .name = TYPE_HEXAGON_DSP_MACHINE, + .parent = TYPE_HEXAGON_COMMON_MACHINE, + .instance_size = sizeof(HexagonDspMachineState), + .abstract = true, + }, + { + .name = MACHINE_TYPE_NAME("V66G_1024"), + .parent = TYPE_HEXAGON_DSP_MACHINE, + .class_init = v66g_1024_init, + }, +}; + +DEFINE_TYPES(hexagon_machine_types) diff --git a/hw/hexagon/hexagon_globalreg.c b/hw/hexagon/hexagon_globalreg.c new file mode 100644 index 0000000000..b5e5913507 --- /dev/null +++ b/hw/hexagon/hexagon_globalreg.c @@ -0,0 +1,316 @@ +/* + * Hexagon Global Registers + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/hexagon/hexagon.h" +#include "hw/hexagon/hexagon_globalreg.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/sysbus.h" +#include "hw/core/resettable.h" +#include "migration/vmstate.h" +#include "qom/object.h" +#include "target/hexagon/cpu.h" +#include "target/hexagon/hex_regs.h" +#include "qemu/log.h" +#include "trace.h" +#include "qapi/error.h" + +#define IMMUTABLE (~0) +#define INVALID_REG_VAL 0xdeadbeef + +static const char *hex_sreg_names[] = { + [HEX_SREG_SGP0] = "sgp0", + [HEX_SREG_SGP1] = "sgp1", + [HEX_SREG_STID] = "stid", + [HEX_SREG_ELR] = "elr", + [HEX_SREG_BADVA0] = "badva0", + [HEX_SREG_BADVA1] = "badva1", + [HEX_SREG_SSR] = "ssr", + [HEX_SREG_CCR] = "ccr", + [HEX_SREG_HTID] = "htid", + [HEX_SREG_BADVA] = "badva", + [HEX_SREG_IMASK] = "imask", + [HEX_SREG_GEVB] = "gevb", + [HEX_SREG_EVB] = "evb", + [HEX_SREG_MODECTL] = "modectl", + [HEX_SREG_SYSCFG] = "syscfg", + [HEX_SREG_IPENDAD] = "ipendad", + [HEX_SREG_VID] = "vid", + [HEX_SREG_VID1] = "vid1", + [HEX_SREG_BESTWAIT] = "bestwait", + [HEX_SREG_IEL] = "iel", + [HEX_SREG_SCHEDCFG] = "schedcfg", + [HEX_SREG_IAHL] = "iahl", + [HEX_SREG_CFGBASE] = "cfgbase", + [HEX_SREG_DIAG] = "diag", + [HEX_SREG_REV] = "rev", + [HEX_SREG_PCYCLELO] = "pcyclelo", + [HEX_SREG_PCYCLEHI] = "pcyclehi", + [HEX_SREG_ISDBST] = "isdbst", + [HEX_SREG_ISDBCFG0] = "isdbcfg0", + [HEX_SREG_ISDBCFG1] = "isdbcfg1", + [HEX_SREG_LIVELOCK] = "livelock", + [HEX_SREG_BRKPTPC0] = "brkptpc0", + [HEX_SREG_BRKPTCFG0] = "brkptcfg0", + [HEX_SREG_BRKPTPC1] = "brkptpc1", + [HEX_SREG_BRKPTCFG1] = "brkptcfg1", + [HEX_SREG_ISDBMBXIN] = "isdbmbxin", + [HEX_SREG_ISDBMBXOUT] = "isdbmbxout", + [HEX_SREG_ISDBEN] = "isdben", + [HEX_SREG_ISDBGPR] = "isdbgpr", + [HEX_SREG_PMUCNT4] = "pmucnt4", + [HEX_SREG_PMUCNT5] = "pmucnt5", + [HEX_SREG_PMUCNT6] = "pmucnt6", + [HEX_SREG_PMUCNT7] = "pmucnt7", + [HEX_SREG_PMUCNT0] = "pmucnt0", + [HEX_SREG_PMUCNT1] = "pmucnt1", + [HEX_SREG_PMUCNT2] = "pmucnt2", + [HEX_SREG_PMUCNT3] = "pmucnt3", + [HEX_SREG_PMUEVTCFG] = "pmuevtcfg", + [HEX_SREG_PMUSTID0] = "pmustid0", + [HEX_SREG_PMUEVTCFG1] = "pmuevtcfg1", + [HEX_SREG_PMUSTID1] = "pmustid1", + [HEX_SREG_TIMERLO] = "timerlo", + [HEX_SREG_TIMERHI] = "timerhi", + [HEX_SREG_PMUCFG] = "pmucfg", + [HEX_SREG_S59] = "s59", + [HEX_SREG_S60] = "s60", + [HEX_SREG_S61] = "s61", + [HEX_SREG_S62] = "s62", + [HEX_SREG_S63] = "s63", +}; + +static const char *get_sreg_name(uint32_t reg) +{ + if (reg < ARRAY_SIZE(hex_sreg_names) && hex_sreg_names[reg]) { + return hex_sreg_names[reg]; + } + return "UNKNOWN"; +} + +/* Global system register mutability masks */ +static const uint32_t global_sreg_immut_masks[NUM_SREGS] = { + [HEX_SREG_EVB] = 0x000000ff, + [HEX_SREG_MODECTL] = IMMUTABLE, + [HEX_SREG_SYSCFG] = 0x80001c00, + [HEX_SREG_IPENDAD] = IMMUTABLE, + [HEX_SREG_VID] = 0xfc00fc00, + [HEX_SREG_VID1] = 0xfc00fc00, + [HEX_SREG_BESTWAIT] = 0xfffffe00, + [HEX_SREG_IAHL] = 0x00000000, + [HEX_SREG_SCHEDCFG] = 0xfffffee0, + [HEX_SREG_CFGBASE] = IMMUTABLE, + [HEX_SREG_DIAG] = 0x00000000, + [HEX_SREG_REV] = IMMUTABLE, + [HEX_SREG_ISDBST] = IMMUTABLE, + [HEX_SREG_ISDBCFG0] = 0xe0000000, + [HEX_SREG_BRKPTPC0] = 0x00000003, + [HEX_SREG_BRKPTCFG0] = 0xfc007000, + [HEX_SREG_BRKPTPC1] = 0x00000003, + [HEX_SREG_BRKPTCFG1] = 0xfc007000, + [HEX_SREG_ISDBMBXIN] = IMMUTABLE, + [HEX_SREG_ISDBMBXOUT] = 0x00000000, + [HEX_SREG_ISDBEN] = 0xfffffffe, + [HEX_SREG_TIMERLO] = IMMUTABLE, + [HEX_SREG_TIMERHI] = IMMUTABLE, +}; + +static void hexagon_globalreg_init(Object *obj) +{ + HexagonGlobalRegState *s = HEXAGON_GLOBALREG(obj); + + memset(s->regs, 0, sizeof(s->regs)); +} + +static inline uint32_t apply_write_mask(uint32_t new_val, uint32_t cur_val, + uint32_t reg_mask) +{ + if (reg_mask) { + return (new_val & ~reg_mask) | (cur_val & reg_mask); + } + return new_val; +} + +uint32_t hexagon_globalreg_read(HexagonGlobalRegState *s, uint32_t reg, + uint32_t htid) +{ + uint32_t value; + + if (!s) { + return 0; + } + g_assert(reg < NUM_SREGS); + g_assert(reg >= HEX_SREG_GLB_START); + + value = s->regs[reg]; + + trace_hexagon_globalreg_read(htid, get_sreg_name(reg), value); + return value; +} + +void hexagon_globalreg_write(HexagonGlobalRegState *s, uint32_t reg, + uint32_t value, uint32_t htid) +{ + if (!s) { + return; + } + g_assert(reg < NUM_SREGS); + g_assert(reg >= HEX_SREG_GLB_START); + s->regs[reg] = value; + trace_hexagon_globalreg_write(htid, get_sreg_name(reg), value); +} + +uint32_t hexagon_globalreg_masked_value(HexagonGlobalRegState *s, uint32_t reg, + uint32_t value) +{ + uint32_t reg_mask; + + if (!s) { + return value; + } + g_assert(reg < NUM_SREGS); + g_assert(reg >= HEX_SREG_GLB_START); + reg_mask = global_sreg_immut_masks[reg]; + return reg_mask == IMMUTABLE ? + s->regs[reg] : + apply_write_mask(value, s->regs[reg], reg_mask); +} + +void hexagon_globalreg_write_masked(HexagonGlobalRegState *s, uint32_t reg, + uint32_t value) +{ + if (!s) { + return; + } + s->regs[reg] = hexagon_globalreg_masked_value(s, reg, value); +} + +uint64_t hexagon_globalreg_get_pcycle_base(HexagonGlobalRegState *s) +{ + g_assert(s); + return s->g_pcycle_base; +} + +void hexagon_globalreg_set_pcycle_base(HexagonGlobalRegState *s, + uint64_t value) +{ + g_assert(s); + s->g_pcycle_base = value; +} + +static void do_hexagon_globalreg_reset(HexagonGlobalRegState *s) +{ + uint32_t isdben_val = 0; + + g_assert(s); + memset(s->regs, 0, sizeof(s->regs)); + + s->g_pcycle_base = 0; + + s->regs[HEX_SREG_EVB] = s->boot_evb; + s->regs[HEX_SREG_CFGBASE] = HEXAGON_CFG_ADDR_BASE(s->config_table_addr); + s->regs[HEX_SREG_REV] = s->dsp_rev; + + if (s->isdben_etm_enable) { + isdben_val |= (1 << 0); /* ETM enable bit */ + } + if (s->isdben_dfd_enable) { + isdben_val |= (1 << 1); /* DFD enable bit */ + } + if (s->isdben_trusted) { + isdben_val |= (1 << 2); /* Trusted bit */ + } + if (s->isdben_secure) { + isdben_val |= (1 << 3); /* Secure bit */ + } + s->regs[HEX_SREG_ISDBEN] = isdben_val; + s->regs[HEX_SREG_MODECTL] = 0x1; + + /* + * These register indices are placeholders in these arrays + * and their actual values are synthesized from state elsewhere. + * We can initialize these with invalid values so that if we + * mistakenly generate reads, they will look obviously wrong. + */ + s->regs[HEX_SREG_PCYCLELO] = INVALID_REG_VAL; + s->regs[HEX_SREG_PCYCLEHI] = INVALID_REG_VAL; + s->regs[HEX_SREG_TIMERLO] = INVALID_REG_VAL; + s->regs[HEX_SREG_TIMERHI] = INVALID_REG_VAL; + s->regs[HEX_SREG_PMUCNT0] = INVALID_REG_VAL; + s->regs[HEX_SREG_PMUCNT1] = INVALID_REG_VAL; + s->regs[HEX_SREG_PMUCNT2] = INVALID_REG_VAL; + s->regs[HEX_SREG_PMUCNT3] = INVALID_REG_VAL; + s->regs[HEX_SREG_PMUCNT4] = INVALID_REG_VAL; + s->regs[HEX_SREG_PMUCNT5] = INVALID_REG_VAL; + s->regs[HEX_SREG_PMUCNT6] = INVALID_REG_VAL; + s->regs[HEX_SREG_PMUCNT7] = INVALID_REG_VAL; +} + +static void hexagon_globalreg_reset_hold(Object *obj, ResetType type) +{ + HexagonGlobalRegState *s = HEXAGON_GLOBALREG(obj); + do_hexagon_globalreg_reset(s); +} + +static const VMStateDescription vmstate_hexagon_globalreg = { + .name = "hexagon_globalreg", + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]){ + VMSTATE_UINT32_ARRAY(regs, HexagonGlobalRegState, NUM_SREGS), + VMSTATE_UINT64(g_pcycle_base, HexagonGlobalRegState), + VMSTATE_UINT32(boot_evb, HexagonGlobalRegState), + VMSTATE_UINT64(config_table_addr, HexagonGlobalRegState), + VMSTATE_UINT32(dsp_rev, HexagonGlobalRegState), + VMSTATE_BOOL(isdben_etm_enable, HexagonGlobalRegState), + VMSTATE_BOOL(isdben_dfd_enable, HexagonGlobalRegState), + VMSTATE_BOOL(isdben_trusted, HexagonGlobalRegState), + VMSTATE_BOOL(isdben_secure, HexagonGlobalRegState), + VMSTATE_END_OF_LIST() + } +}; + +static const Property hexagon_globalreg_properties[] = { + DEFINE_PROP_UINT32("boot-evb", HexagonGlobalRegState, boot_evb, 0x0), + DEFINE_PROP_UINT64("config-table-addr", HexagonGlobalRegState, + config_table_addr, 0xffffffffULL), + DEFINE_PROP_UINT32("dsp-rev", HexagonGlobalRegState, dsp_rev, 0), + DEFINE_PROP_BOOL("isdben-etm-enable", HexagonGlobalRegState, + isdben_etm_enable, false), + DEFINE_PROP_BOOL("isdben-dfd-enable", HexagonGlobalRegState, + isdben_dfd_enable, false), + DEFINE_PROP_BOOL("isdben-trusted", HexagonGlobalRegState, + isdben_trusted, false), + DEFINE_PROP_BOOL("isdben-secure", HexagonGlobalRegState, + isdben_secure, false), +}; + +static void hexagon_globalreg_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + rc->phases.hold = hexagon_globalreg_reset_hold; + dc->vmsd = &vmstate_hexagon_globalreg; + dc->user_creatable = false; + device_class_set_props(dc, hexagon_globalreg_properties); +} + +static const TypeInfo hexagon_globalreg_info = { + .name = TYPE_HEXAGON_GLOBALREG, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HexagonGlobalRegState), + .instance_init = hexagon_globalreg_init, + .class_init = hexagon_globalreg_class_init, +}; + +static void hexagon_globalreg_register_types(void) +{ + type_register_static(&hexagon_globalreg_info); +} + +type_init(hexagon_globalreg_register_types) diff --git a/hw/hexagon/hexagon_tlb.c b/hw/hexagon/hexagon_tlb.c new file mode 100644 index 0000000000..b6d4aff389 --- /dev/null +++ b/hw/hexagon/hexagon_tlb.c @@ -0,0 +1,467 @@ +/* + * Hexagon TLB QOM Device + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/hexagon/hexagon_tlb.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/resettable.h" +#include "migration/vmstate.h" +#include "monitor/monitor.h" +#include "qapi/error.h" +#include "exec/page-protection.h" +#include "exec/target_page.h" +#include "target/hexagon/cpu.h" +#include "target/hexagon/cpu_bits.h" + +/* PTE (TLB entry) field extraction */ +#define GET_PTE_PPD(entry) extract64((entry), 0, 24) +#define GET_PTE_C(entry) extract64((entry), 24, 4) +#define GET_PTE_U(entry) extract64((entry), 28, 1) +#define GET_PTE_R(entry) extract64((entry), 29, 1) +#define GET_PTE_W(entry) extract64((entry), 30, 1) +#define GET_PTE_X(entry) extract64((entry), 31, 1) +#define GET_PTE_VPN(entry) extract64((entry), 32, 20) +#define GET_PTE_ASID(entry) extract64((entry), 52, 7) +#define GET_PTE_ATR0(entry) extract64((entry), 59, 1) +#define GET_PTE_ATR1(entry) extract64((entry), 60, 1) +#define GET_PTE_PA35(entry) extract64((entry), 61, 1) +#define GET_PTE_G(entry) extract64((entry), 62, 1) +#define GET_PTE_V(entry) extract64((entry), 63, 1) + +/* PPD (physical page descriptor) */ +static inline uint64_t GET_PPD(uint64_t entry) +{ + return GET_PTE_PPD(entry) | (GET_PTE_PA35(entry) << 24); +} + +#define NO_ASID (1 << 8) + +typedef enum { + PGSIZE_4K, + PGSIZE_16K, + PGSIZE_64K, + PGSIZE_256K, + PGSIZE_1M, + PGSIZE_4M, + PGSIZE_16M, + PGSIZE_64M, + PGSIZE_256M, + PGSIZE_1G, +} tlb_pgsize_t; + +#define NUM_PGSIZE_TYPES (PGSIZE_1G + 1) + +static const char *pgsize_str[NUM_PGSIZE_TYPES] = { + "4K", + "16K", + "64K", + "256K", + "1M", + "4M", + "16M", + "64M", + "256M", + "1G", +}; + +#define INVALID_MASK 0xffffffffLL + +static const uint64_t encmask_2_mask[] = { + 0x0fffLL, /* 4k, 0000 */ + 0x3fffLL, /* 16k, 0001 */ + 0xffffLL, /* 64k, 0010 */ + 0x3ffffLL, /* 256k, 0011 */ + 0xfffffLL, /* 1m, 0100 */ + 0x3fffffLL, /* 4m, 0101 */ + 0xffffffLL, /* 16m, 0110 */ + 0x3ffffffLL, /* 64m, 0111 */ + 0xfffffffLL, /* 256m, 1000 */ + 0x3fffffffLL, /* 1g, 1001 */ + INVALID_MASK, /* RSVD, 1010 */ +}; + +static inline tlb_pgsize_t hex_tlb_pgsize_type(uint64_t entry) +{ + if (entry == 0) { + qemu_log_mask(CPU_LOG_MMU, "%s: Supplied TLB entry was 0!\n", + __func__); + return 0; + } + tlb_pgsize_t size = ctz64(entry); + g_assert(size < NUM_PGSIZE_TYPES); + return size; +} + +static inline uint64_t hex_tlb_page_size_bytes(uint64_t entry) +{ + return 1ull << (qemu_target_page_bits() + 2 * hex_tlb_pgsize_type(entry)); +} + +static inline uint64_t hex_tlb_phys_page_num(uint64_t entry) +{ + uint32_t ppd = GET_PPD(entry); + return ppd >> 1; +} + +static inline uint64_t hex_tlb_phys_addr(uint64_t entry) +{ + uint64_t pagemask = encmask_2_mask[hex_tlb_pgsize_type(entry)]; + uint64_t pagenum = hex_tlb_phys_page_num(entry); + uint64_t PA = (pagenum << qemu_target_page_bits()) & (~pagemask); + return PA; +} + +static inline uint64_t hex_tlb_virt_addr(uint64_t entry) +{ + return (uint64_t)GET_PTE_VPN(entry) << qemu_target_page_bits(); +} + +bool hexagon_tlb_dump_entry(Monitor *mon, uint64_t entry) +{ + if (GET_PTE_V(entry)) { + uint64_t PA = hex_tlb_phys_addr(entry); + uint64_t VA = hex_tlb_virt_addr(entry); + monitor_printf(mon, "0x%016" PRIx64 ": ", entry); + monitor_printf(mon, "V:%" PRId64 " G:%" PRId64 + " A1:%" PRId64 " A0:%" PRId64, + GET_PTE_V(entry), + GET_PTE_G(entry), + GET_PTE_ATR1(entry), + GET_PTE_ATR0(entry)); + monitor_printf(mon, " ASID:0x%02" PRIx64 " VA:0x%08" PRIx64, + GET_PTE_ASID(entry), VA); + monitor_printf(mon, + " X:%" PRId64 " W:%" PRId64 " R:%" PRId64 + " U:%" PRId64 " C:%" PRId64, + GET_PTE_X(entry), + GET_PTE_W(entry), + GET_PTE_R(entry), + GET_PTE_U(entry), + GET_PTE_C(entry)); + monitor_printf(mon, " PA:0x%09" PRIx64 " SZ:%s (0x%" PRIx64 ")", + PA, pgsize_str[hex_tlb_pgsize_type(entry)], + hex_tlb_page_size_bytes(entry)); + monitor_printf(mon, "\n"); + return true; + } + + /* Not valid */ + return false; +} + +static inline bool hex_tlb_entry_match_noperm(uint64_t entry, uint32_t asid, + uint64_t VA) +{ + if (GET_PTE_V(entry)) { + if (GET_PTE_G(entry)) { + /* Global entry - ignore ASID */ + } else if (asid != NO_ASID) { + uint32_t tlb_asid = GET_PTE_ASID(entry); + if (tlb_asid != asid) { + return false; + } + } + + uint64_t page_size = hex_tlb_page_size_bytes(entry); + uint64_t page_start = + ROUND_DOWN(hex_tlb_virt_addr(entry), page_size); + if (page_start <= VA && VA < page_start + page_size) { + return true; + } + } + return false; +} + +static inline void hex_tlb_entry_get_perm(uint64_t entry, + MMUAccessType access_type, + int mmu_idx, int *prot, + int32_t *excp, int *cause_code) +{ + bool perm_x = GET_PTE_X(entry); + bool perm_w = GET_PTE_W(entry); + bool perm_r = GET_PTE_R(entry); + bool perm_u = GET_PTE_U(entry); + bool user_idx = mmu_idx == MMU_USER_IDX; + + if (mmu_idx == MMU_KERNEL_IDX) { + *prot = PAGE_VALID | PAGE_READ | PAGE_WRITE | PAGE_EXEC; + return; + } + + *prot = PAGE_VALID; + switch (access_type) { + case MMU_INST_FETCH: + if (user_idx && !perm_u) { + *excp = HEX_EVENT_PRECISE; + *cause_code = HEX_CAUSE_FETCH_NO_UPAGE; + } else if (!perm_x) { + *excp = HEX_EVENT_PRECISE; + *cause_code = HEX_CAUSE_FETCH_NO_XPAGE; + } + break; + case MMU_DATA_LOAD: + if (user_idx && !perm_u) { + *excp = HEX_EVENT_PRECISE; + *cause_code = HEX_CAUSE_PRIV_NO_UREAD; + } else if (!perm_r) { + *excp = HEX_EVENT_PRECISE; + *cause_code = HEX_CAUSE_PRIV_NO_READ; + } + break; + case MMU_DATA_STORE: + if (user_idx && !perm_u) { + *excp = HEX_EVENT_PRECISE; + *cause_code = HEX_CAUSE_PRIV_NO_UWRITE; + } else if (!perm_w) { + *excp = HEX_EVENT_PRECISE; + *cause_code = HEX_CAUSE_PRIV_NO_WRITE; + } + break; + } + + if (!user_idx || perm_u) { + if (perm_x) { + *prot |= PAGE_EXEC; + } + if (perm_r) { + *prot |= PAGE_READ; + } + if (perm_w) { + *prot |= PAGE_WRITE; + } + } +} + +static inline bool hex_tlb_entry_match(uint64_t entry, uint8_t asid, + uint32_t VA, + MMUAccessType access_type, hwaddr *PA, + int *prot, uint64_t *size, + int32_t *excp, int *cause_code, + int mmu_idx) +{ + if (hex_tlb_entry_match_noperm(entry, asid, VA)) { + hex_tlb_entry_get_perm(entry, access_type, mmu_idx, prot, excp, + cause_code); + *PA = hex_tlb_phys_addr(entry); + *size = hex_tlb_page_size_bytes(entry); + return true; + } + return false; +} + +static bool hex_tlb_is_match(uint64_t entry1, uint64_t entry2, + bool consider_gbit) +{ + bool valid1 = GET_PTE_V(entry1); + bool valid2 = GET_PTE_V(entry2); + uint64_t size1 = hex_tlb_page_size_bytes(entry1); + uint64_t vaddr1 = ROUND_DOWN(hex_tlb_virt_addr(entry1), size1); + uint64_t size2 = hex_tlb_page_size_bytes(entry2); + uint64_t vaddr2 = ROUND_DOWN(hex_tlb_virt_addr(entry2), size2); + int asid1 = GET_PTE_ASID(entry1); + int asid2 = GET_PTE_ASID(entry2); + bool gbit1 = GET_PTE_G(entry1); + bool gbit2 = GET_PTE_G(entry2); + + if (!valid1 || !valid2) { + return false; + } + + if (((vaddr1 <= vaddr2) && (vaddr2 < (vaddr1 + size1))) || + ((vaddr2 <= vaddr1) && (vaddr1 < (vaddr2 + size2)))) { + if (asid1 == asid2) { + return true; + } + if ((consider_gbit && gbit1) || gbit2) { + return true; + } + } + return false; +} + +/* Public API */ + +uint64_t hexagon_tlb_read(HexagonTLBState *tlb, uint32_t index) +{ + g_assert(index < tlb->num_entries); + return tlb->entries[index]; +} + +void hexagon_tlb_write(HexagonTLBState *tlb, uint32_t index, uint64_t value) +{ + g_assert(index < tlb->num_entries); + tlb->entries[index] = value; +} + +bool hexagon_tlb_find_match(HexagonTLBState *tlb, uint32_t asid, + uint32_t VA, MMUAccessType access_type, + hwaddr *PA, int *prot, uint64_t *size, + int32_t *excp, int *cause_code, int mmu_idx) +{ + *PA = 0; + *prot = 0; + *size = 0; + *excp = 0; + *cause_code = 0; + + for (uint32_t i = 0; i < tlb->num_entries; i++) { + if (hex_tlb_entry_match(tlb->entries[i], asid, VA, access_type, + PA, prot, size, excp, cause_code, mmu_idx)) { + return true; + } + } + return false; +} + +uint32_t hexagon_tlb_lookup(HexagonTLBState *tlb, uint32_t asid, + uint32_t VA, int *cause_code) +{ + uint32_t not_found = 0x80000000; + uint32_t idx = not_found; + + for (uint32_t i = 0; i < tlb->num_entries; i++) { + uint64_t entry = tlb->entries[i]; + if (hex_tlb_entry_match_noperm(entry, asid, VA)) { + if (idx != not_found) { + *cause_code = HEX_CAUSE_IMPRECISE_MULTI_TLB_MATCH; + break; + } + idx = i; + } + } + + if (idx == not_found) { + qemu_log_mask(CPU_LOG_MMU, + "%s: 0x%" PRIx32 ", 0x%08" PRIx32 " => NOT FOUND\n", + __func__, asid, VA); + } else { + qemu_log_mask(CPU_LOG_MMU, + "%s: 0x%" PRIx32 ", 0x%08" PRIx32 " => %d\n", + __func__, asid, VA, idx); + } + + return idx; +} + +/* + * Return codes: + * 0 or positive index of match + * -1 multiple matches + * -2 no match + */ +int hexagon_tlb_check_overlap(HexagonTLBState *tlb, uint64_t entry, + uint64_t index) +{ + int matches = 0; + int last_match = 0; + + for (uint32_t i = 0; i < tlb->num_entries; i++) { + if (hex_tlb_is_match(entry, tlb->entries[i], false)) { + matches++; + last_match = i; + } + } + + if (matches == 1) { + return last_match; + } + if (matches == 0) { + return -2; + } + return -1; +} + +void hexagon_tlb_dump(Monitor *mon, HexagonTLBState *tlb) +{ + for (uint32_t i = 0; i < tlb->num_entries; i++) { + hexagon_tlb_dump_entry(mon, tlb->entries[i]); + } +} + +uint32_t hexagon_tlb_get_num_entries(HexagonTLBState *tlb) +{ + return tlb->num_entries; +} + +/* QOM lifecycle */ + +static void hexagon_tlb_init(Object *obj) +{ +} + +static void hexagon_tlb_realize(DeviceState *dev, Error **errp) +{ + HexagonTLBState *s = HEXAGON_TLB(dev); + + if (s->num_entries == 0 || s->num_entries > MAX_TLB_ENTRIES) { + error_setg(errp, "Invalid TLB num-entries: %" PRIu32, + s->num_entries); + return; + } + s->entries = g_new0(uint64_t, s->num_entries); +} + +static void hexagon_tlb_unrealize(DeviceState *dev) +{ + HexagonTLBState *s = HEXAGON_TLB(dev); + g_free(s->entries); + s->entries = NULL; +} + +static void hexagon_tlb_reset_hold(Object *obj, ResetType type) +{ + HexagonTLBState *s = HEXAGON_TLB(obj); + if (s->entries) { + memset(s->entries, 0, sizeof(uint64_t) * s->num_entries); + } +} + +static const VMStateDescription vmstate_hexagon_tlb = { + .name = "hexagon-tlb", + .version_id = 0, + .minimum_version_id = 0, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(num_entries, HexagonTLBState), + VMSTATE_VARRAY_UINT32_ALLOC(entries, HexagonTLBState, num_entries, + 0, vmstate_info_uint64, uint64_t), + VMSTATE_END_OF_LIST() + }, +}; + +static const Property hexagon_tlb_properties[] = { + DEFINE_PROP_UINT32("num-entries", HexagonTLBState, num_entries, + MAX_TLB_ENTRIES), +}; + +static void hexagon_tlb_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + dc->realize = hexagon_tlb_realize; + dc->unrealize = hexagon_tlb_unrealize; + rc->phases.hold = hexagon_tlb_reset_hold; + dc->vmsd = &vmstate_hexagon_tlb; + dc->user_creatable = false; + device_class_set_props(dc, hexagon_tlb_properties); +} + +static const TypeInfo hexagon_tlb_info = { + .name = TYPE_HEXAGON_TLB, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HexagonTLBState), + .instance_init = hexagon_tlb_init, + .class_init = hexagon_tlb_class_init, +}; + +static void hexagon_tlb_register_types(void) +{ + type_register_static(&hexagon_tlb_info); +} + +type_init(hexagon_tlb_register_types) diff --git a/hw/hexagon/machine_cfg_sa8775_cdsp0.h.inc b/hw/hexagon/machine_cfg_sa8775_cdsp0.h.inc new file mode 100644 index 0000000000..442cbe3be3 --- /dev/null +++ b/hw/hexagon/machine_cfg_sa8775_cdsp0.h.inc @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +static const struct hexagon_machine_config SA8775P_cdsp0 = { + .cfgbase = 0x24000000 + 0x180000, + .l2tcm_size = 0x00000000, + .l2vic_base = 0x26300000 + 0x90000, + .l2vic_size = 0x00001000, + .csr_base = 0x26300000, + .qtmr_region = 0x26300000 + 0xA1000, + .cfgtable = { + .l2tcm_base = 0x00002400, + .reserved0 = 0x00000000, + .subsystem_base = 0x00002638, + .etm_base = 0x00002419, + .l2cfg_base = 0x0000241a, + .reserved1 = 0x0000241b, + .l1s0_base = 0x00002500, + .axi2_lowaddr = 0x00000000, + .streamer_base = 0x00000000, + .reserved2 = 0x00000000, + .fastl2vic_base = 0x0000241e, + .jtlb_size_entries = 0x00000080, + .coproc_present = 0x00000001, + .ext_contexts = 0x00000004, + .vtcm_base = 0x00002500, + .vtcm_size_kb = 0x00002000, + .l2tag_size = 0x00000400, + .l2ecomem_size = 0x00000000, + .thread_enable_mask = 0x0000003f, + .eccreg_base = 0x0000241f, + .l2line_size = 0x00000080, + .tiny_core = 0x00000000, + .l2itcm_size = 0x00000000, + .l2itcm_base = 0x00002400, + .reserved3 = 0x00000000, + .dtm_present = 0x00000000, + .dma_version = 0x00000003, + .hvx_vec_log_length = 0x00000007, + .core_id = 0x00000000, + .core_count = 0x00000000, + .coproc2_reg0 = 0x00000040, + .coproc2_reg1 = 0x00000020, + .v2x_mode = 0x00000001, + .coproc2_reg2 = 0x00000008, + .coproc2_reg3 = 0x00000020, + .coproc2_reg4 = 0x00000000, + .coproc2_reg5 = 0x00000002, + .coproc2_reg6 = 0x00000016, + .coproc2_reg7 = 0x00000006, + .acd_preset = 0x00000001, + .mnd_preset = 0x00000000, + .l1d_size_kb = 0x00000010, + .l1i_size_kb = 0x00000020, + .l1d_write_policy = 0x00000002, + .vtcm_bank_width = 0x00000080, + .reserved4 = 0x00000001, + .reserved5 = 0x00000000, + .reserved6 = 0x00000003, + .coproc2_cvt_mpy_size = 0x0000000a, + .consistency_domain = 0x000000e0, + .capacity_domain = 0x00000080, + .axi3_lowaddr = 0x00000000, + }, +}; diff --git a/hw/hexagon/machine_cfg_v66g_1024.h.inc b/hw/hexagon/machine_cfg_v66g_1024.h.inc new file mode 100644 index 0000000000..cc4d89b89c --- /dev/null +++ b/hw/hexagon/machine_cfg_v66g_1024.h.inc @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +static const struct hexagon_machine_config v66g_1024 = { + .cfgbase = 0xd8180000, + .l2tcm_size = 0x00000000, + .l2vic_base = 0xfc910000, + .l2vic_size = 0x00001000, + .csr_base = 0xfc900000, + .qtmr_region = 0xfc921000, + .cfgtable = { + .l2tcm_base = 0x0000d800, + .reserved0 = 0x0000d400, + .subsystem_base = 0x0000fc90, + .etm_base = 0x0000d805, + .l2cfg_base = 0x0000d81a, + .reserved1 = 0x00000000, + .l1s0_base = 0x0000d820, + .axi2_lowaddr = 0x00003000, + .streamer_base = 0x00000000, + .reserved2 = 0x0000d819, + .fastl2vic_base = 0x0000d81e, + .jtlb_size_entries = 0x00000080, + .coproc_present = 0x00000001, + .ext_contexts = 0x00000004, + .vtcm_base = 0x0000d820, + .vtcm_size_kb = 0x00000100, + .l2tag_size = 0x00000400, + .l2ecomem_size = 0x00000400, + .thread_enable_mask = 0x0000000f, + .eccreg_base = 0x0000d81f, + .l2line_size = 0x00000080, + .tiny_core = 0x00000000, + .l2itcm_size = 0x00000000, + .l2itcm_base = 0x0000d820, + .reserved3 = 0x00000000, + .dtm_present = 0x00000000, + .dma_version = 0x00000000, + .hvx_vec_log_length = 0x00000080, + .core_id = 0x00000000, + .core_count = 0x00000000, + .coproc2_reg0 = 0x00000000, + .coproc2_reg1 = 0x00000000, + .v2x_mode = 0x00000000, + .coproc2_reg2 = 0x00000000, + .coproc2_reg3 = 0x00000000, + .coproc2_reg4 = 0x00000000, + .coproc2_reg5 = 0x00000000, + .coproc2_reg6 = 0x00000000, + .coproc2_reg7 = 0x00000000, + .acd_preset = 0x00000000, + .mnd_preset = 0x00000000, + .l1d_size_kb = 0x00000000, + .l1i_size_kb = 0x00000000, + .l1d_write_policy = 0x00000000, + .vtcm_bank_width = 0x00000000, + .reserved4 = 0x00000000, + .reserved5 = 0x00000000, + .reserved6 = 0x00000000, + .coproc2_cvt_mpy_size = 0x00000000, + .consistency_domain = 0x00000000, + .capacity_domain = 0x00000000, + .axi3_lowaddr = 0x00000000, + }, +}; diff --git a/hw/hexagon/machine_cfg_v68n_1024.h.inc b/hw/hexagon/machine_cfg_v68n_1024.h.inc new file mode 100644 index 0000000000..0c79689bc2 --- /dev/null +++ b/hw/hexagon/machine_cfg_v68n_1024.h.inc @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +static const struct hexagon_machine_config v68n_1024 = { + .cfgbase = 0xde000000, + .l2tcm_size = 0x00000000, + .l2vic_base = 0xfc910000, + .l2vic_size = 0x00001000, + .csr_base = 0xfc900000, + .qtmr_region = 0xfc921000, + .cfgtable = { + .l2tcm_base = 0x0000d800, + .reserved0 = 0x00000000, + .subsystem_base = 0x0000fc90, + .etm_base = 0x0000d819, + .l2cfg_base = 0x0000d81a, + .reserved1 = 0x00000000, + .l1s0_base = 0x0000d840, + .axi2_lowaddr = 0x00003000, + .streamer_base = 0x0000d81c, + .reserved2 = 0x0000d81d, + .fastl2vic_base = 0x0000d81e, + .jtlb_size_entries = 0x00000080, + .coproc_present = 0x00000001, + .ext_contexts = 0x00000004, + .vtcm_base = 0x0000d840, + .vtcm_size_kb = 0x00001000, + .l2tag_size = 0x00000400, + .l2ecomem_size = 0x00000400, + .thread_enable_mask = 0x0000003f, + .eccreg_base = 0x0000d81f, + .l2line_size = 0x00000080, + .tiny_core = 0x00000000, + .l2itcm_size = 0x00000000, + .l2itcm_base = 0x0000d820, + .reserved3 = 0x00000000, + .dtm_present = 0x00000000, + .dma_version = 0x00000001, + .hvx_vec_log_length = 0x00000007, + .core_id = 0x00000000, + .core_count = 0x00000000, + .coproc2_reg0 = 0x00000040, + .coproc2_reg1 = 0x00000020, + .v2x_mode = 0x1f1f1f1f, + .coproc2_reg2 = 0x1f1f1f1f, + .coproc2_reg3 = 0x1f1f1f1f, + .coproc2_reg4 = 0x1f1f1f1f, + .coproc2_reg5 = 0x1f1f1f1f, + .coproc2_reg6 = 0x1f1f1f1f, + .coproc2_reg7 = 0x1f1f1f1f, + .acd_preset = 0x1f1f1f1f, + .mnd_preset = 0x1f1f1f1f, + .l1d_size_kb = 0x1f1f1f1f, + .l1i_size_kb = 0x1f1f1f1f, + .l1d_write_policy = 0x1f1f1f1f, + .vtcm_bank_width = 0x1f1f1f1f, + .reserved4 = 0x1f1f1f1f, + .reserved5 = 0x1f1f1f1f, + .reserved6 = 0x1f1f1f1f, + .coproc2_cvt_mpy_size = 0x1f1f1f1f, + .consistency_domain = 0x1f1f1f1f, + .capacity_domain = 0x1f1f1f1f, + .axi3_lowaddr = 0x1f1f1f1f, + }, +}; diff --git a/hw/hexagon/meson.build b/hw/hexagon/meson.build new file mode 100644 index 0000000000..bade3a3292 --- /dev/null +++ b/hw/hexagon/meson.build @@ -0,0 +1,7 @@ +hexagon_ss = ss.source_set() +hexagon_ss.add(files('hexagon_tlb.c')) +hexagon_ss.add(files('hexagon_globalreg.c')) +hexagon_ss.add(when: 'CONFIG_HEX_DSP', if_true: files('hexagon_dsp.c')) +hexagon_ss.add(when: 'CONFIG_HEX_VIRT', if_true: files('virt.c')) + +hw_arch += {'hexagon': hexagon_ss} diff --git a/hw/hexagon/trace-events b/hw/hexagon/trace-events new file mode 100644 index 0000000000..5d623ed251 --- /dev/null +++ b/hw/hexagon/trace-events @@ -0,0 +1,3 @@ +# Hexagon global register access +hexagon_globalreg_read(uint32_t htid, const char *reg_name, uint32_t value) "htid=%u reg=%s value=0x%x" +hexagon_globalreg_write(uint32_t htid, const char *reg_name, uint32_t value) "htid=%u reg=%s value=0x%x" diff --git a/hw/hexagon/trace.h b/hw/hexagon/trace.h new file mode 100644 index 0000000000..9e0b39f3c6 --- /dev/null +++ b/hw/hexagon/trace.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "trace/trace-hw_hexagon.h" diff --git a/hw/hexagon/virt.c b/hw/hexagon/virt.c new file mode 100644 index 0000000000..a3638998b8 --- /dev/null +++ b/hw/hexagon/virt.c @@ -0,0 +1,347 @@ +/* + * Hexagon virt emulation + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "hw/hexagon/virt.h" +#include "elf.h" +#include "hw/char/pl011.h" +#include "hw/core/clock.h" +#include "hw/core/sysbus-fdt.h" +#include "hw/hexagon/hexagon.h" +#include "hw/hexagon/hexagon_globalreg.h" +#include "hw/hexagon/hexagon_tlb.h" +#include "hw/core/loader.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/qdev-clock.h" +#include "hw/core/register.h" +#include "qemu/error-report.h" +#include "qemu/guest-random.h" +#include "qemu/units.h" +#include "machine_cfg_v68n_1024.h.inc" +#include "system/address-spaces.h" +#include "system/device_tree.h" +#include "system/reset.h" +#include "system/system.h" +#include <libfdt.h> + +enum { + VIRT_UART0, + VIRT_FDT, +}; + +static const MemMapEntry base_memmap[] = { + [VIRT_UART0] = { 0x10000000, 0x00000200 }, + [VIRT_FDT] = { 0x99800000, 0x00400000 }, +}; + + +static void create_fdt(HexagonVirtMachineState *vms) +{ + MachineState *ms = MACHINE(vms); + void *fdt = create_device_tree(&vms->fdt_size); + uint8_t rng_seed[32]; + + if (!fdt) { + error_report("create_device_tree() failed"); + exit(1); + } + + ms->fdt = fdt; + + qemu_fdt_setprop_cell(fdt, "/", "#address-cells", 0x2); + qemu_fdt_setprop_cell(fdt, "/", "#size-cells", 0x1); + qemu_fdt_setprop_string(fdt, "/", "model", "hexagon-virt,qemu"); + qemu_fdt_setprop_string(fdt, "/", "compatible", "qcom,sm8150"); + + qemu_fdt_add_subnode(fdt, "/soc"); + qemu_fdt_setprop_cell(fdt, "/soc", "#address-cells", 0x2); + qemu_fdt_setprop_cell(fdt, "/soc", "#size-cells", 0x1); + qemu_fdt_setprop(fdt, "/soc", "ranges", NULL, 0); + + qemu_fdt_add_subnode(fdt, "/chosen"); + qemu_guest_getrandom_nofail(rng_seed, sizeof(rng_seed)); + qemu_fdt_setprop(fdt, "/chosen", "rng-seed", rng_seed, sizeof(rng_seed)); +} + +static void fdt_add_hvx(HexagonVirtMachineState *vms, + const struct hexagon_machine_config *m_cfg) +{ + const MachineState *ms = MACHINE(vms); + uint32_t vtcm_size_bytes = m_cfg->cfgtable.vtcm_size_kb * 1024; + if (vtcm_size_bytes > 0) { + memory_region_init_ram(&vms->vtcm, NULL, "vtcm.ram", vtcm_size_bytes, + &error_fatal); + memory_region_add_subregion(vms->sys, m_cfg->cfgtable.vtcm_base << 16, + &vms->vtcm); + + qemu_fdt_add_subnode(ms->fdt, "/soc/vtcm"); + qemu_fdt_setprop_string(ms->fdt, "/soc/vtcm", "compatible", + "qcom,hexagon_vtcm"); + + assert(sizeof(m_cfg->cfgtable.vtcm_base) == sizeof(uint32_t)); + qemu_fdt_setprop_cells(ms->fdt, "/soc/vtcm", "reg", 0, + m_cfg->cfgtable.vtcm_base << 16, + vtcm_size_bytes); + } + + if (m_cfg->cfgtable.ext_contexts > 0) { + qemu_fdt_add_subnode(ms->fdt, "/soc/hvx"); + qemu_fdt_setprop_string(ms->fdt, "/soc/hvx", "compatible", + "qcom,hexagon-hvx"); + qemu_fdt_setprop_cells(ms->fdt, "/soc/hvx", "qcom,hvx-max-ctxts", + m_cfg->cfgtable.ext_contexts); + qemu_fdt_setprop_cells(ms->fdt, "/soc/hvx", "qcom,hvx-vlength", + m_cfg->cfgtable.hvx_vec_log_length); + } +} + +static int32_t fdt_add_clocks(const HexagonVirtMachineState *vms) +{ + MachineState *ms = MACHINE(vms); + int32_t clk_phandle = qemu_fdt_alloc_phandle(ms->fdt); + + qemu_fdt_add_subnode(ms->fdt, "/apb-pclk"); + qemu_fdt_setprop_string(ms->fdt, "/apb-pclk", "compatible", "fixed-clock"); + qemu_fdt_setprop_cell(ms->fdt, "/apb-pclk", "#clock-cells", 0x0); + qemu_fdt_setprop_cell(ms->fdt, "/apb-pclk", "clock-frequency", 24000000); + qemu_fdt_setprop_string(ms->fdt, "/apb-pclk", "clock-output-names", + "clk24mhz"); + qemu_fdt_setprop_cell(ms->fdt, "/apb-pclk", "phandle", clk_phandle); + + return clk_phandle; +} + +static void fdt_add_uart(const HexagonVirtMachineState *vms, int uart, + int32_t clk_phandle) +{ + char *nodename; + hwaddr base = base_memmap[uart].base; + hwaddr size = base_memmap[uart].size; + assert(uart == 0); + const char compat[] = "arm,pl011\0arm,primecell"; + const char clocknames[] = "uartclk\0apb_pclk"; + MachineState *ms = MACHINE(vms); + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_new(TYPE_PL011); + s = SYS_BUS_DEVICE(dev); + qdev_prop_set_chr(dev, "chardev", serial_hd(0)); + qdev_connect_clock_in(dev, "clk", vms->apb_clk); + sysbus_realize_and_unref(s, &error_fatal); + sysbus_mmio_map(s, 0, base); + + nodename = g_strdup_printf("/pl011@%" PRIx64, base); + qemu_fdt_add_subnode(ms->fdt, nodename); + + /* Note that we can't use setprop_string because of the embedded NUL */ + qemu_fdt_setprop(ms->fdt, nodename, "compatible", compat, sizeof(compat)); + qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", 0, base, size); + qemu_fdt_setprop_cells(ms->fdt, nodename, "clocks", clk_phandle, + clk_phandle); + qemu_fdt_setprop(ms->fdt, nodename, "clock-names", clocknames, + sizeof(clocknames)); + + qemu_fdt_setprop_string(ms->fdt, "/chosen", "stdout-path", nodename); + qemu_fdt_add_subnode(ms->fdt, "/aliases"); + qemu_fdt_setprop_string(ms->fdt, "/aliases", "serial0", nodename); + + g_free(nodename); +} + +static void fdt_add_cpu_nodes(const HexagonVirtMachineState *vms) +{ + MachineState *ms = MACHINE(vms); + qemu_fdt_add_subnode(ms->fdt, "/cpus"); + qemu_fdt_setprop_cell(ms->fdt, "/cpus", "#address-cells", 0x1); + qemu_fdt_setprop_cell(ms->fdt, "/cpus", "#size-cells", 0x0); + + /* cpu nodes */ + for (int num = ms->smp.cpus - 1; num >= 0; num--) { + char *nodename = g_strdup_printf("/cpus/cpu@%d", num); + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_string(ms->fdt, nodename, "device_type", "cpu"); + qemu_fdt_setprop_cell(ms->fdt, nodename, "reg", num); + qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", + qemu_fdt_alloc_phandle(ms->fdt)); + g_free(nodename); + } +} + + + +static void virt_instance_init(Object *obj) +{ + HexagonVirtMachineState *vms = HEXAGON_VIRT_MACHINE(obj); + + create_fdt(vms); +} + +void hexagon_load_fdt(const HexagonVirtMachineState *vms) +{ + MachineState *ms = MACHINE(vms); + hwaddr fdt_addr = base_memmap[VIRT_FDT].base; + uint32_t fdtsize = vms->fdt_size; + + g_assert(fdtsize <= base_memmap[VIRT_FDT].size); + /* copy in the device tree */ + rom_add_blob_fixed_as("fdt", ms->fdt, fdtsize, fdt_addr, + &address_space_memory); + qemu_register_reset_nosnapshotload( + qemu_fdt_randomize_seeds, + rom_ptr_for_as(&address_space_memory, fdt_addr, fdtsize)); +} + +static uint64_t load_kernel(const HexagonVirtMachineState *vms) +{ + MachineState *ms = MACHINE(vms); + uint64_t entry = 0; + if (load_elf_ram_sym(ms->kernel_filename, NULL, NULL, NULL, &entry, NULL, + NULL, NULL, 0, EM_HEXAGON, 0, 0, &address_space_memory, + false, NULL) > 0) { + return entry; + } + error_report("error loading '%s'", ms->kernel_filename); + exit(1); +} + +static uint64_t load_bios(HexagonVirtMachineState *vms) +{ + MachineState *ms = MACHINE(vms); + uint64_t bios_addr = 0x0; /* Load BIOS at reset vector address 0x0 */ + int bios_size; + + bios_size = load_image_targphys(ms->firmware ?: "", + bios_addr, 64 * 1024, NULL); + if (bios_size < 0) { + error_report("Could not load BIOS '%s'", ms->firmware ?: ""); + exit(1); + } + + return bios_addr; /* Return entry point at address 0x0 */ +} + +static void do_cpu_reset(void *opaque) +{ + HexagonCPU *cpu = opaque; + CPUState *cs = CPU(cpu); + cpu_reset(cs); +} + +static void virt_init(MachineState *ms) +{ + HexagonVirtMachineState *vms = HEXAGON_VIRT_MACHINE(ms); + const struct hexagon_machine_config *m_cfg = &v68n_1024; + DeviceState *gsregs_dev; + DeviceState *tlb_dev; + DeviceState *cpu0; + int32_t clk_phandle; + + qemu_fdt_setprop_string(ms->fdt, "/chosen", "bootargs", ms->kernel_cmdline); + + vms->sys = get_system_memory(); + + /* Create APB clock for peripherals */ + vms->apb_clk = clock_new(OBJECT(ms), "apb-pclk"); + clock_set_hz(vms->apb_clk, 24000000); + + memory_region_init_ram(&vms->parent_obj.ram, NULL, "ddr.ram", + ms->ram_size, &error_fatal); + memory_region_add_subregion(vms->sys, 0x0, &vms->parent_obj.ram); + + if (m_cfg->l2tcm_size) { + memory_region_init_ram(&vms->tcm, NULL, "tcm.ram", m_cfg->l2tcm_size, + &error_fatal); + memory_region_add_subregion(vms->sys, m_cfg->cfgtable.l2tcm_base << 16, + &vms->tcm); + } + + memory_region_init_rom(&vms->parent_obj.cfgtable_rom, NULL, + "config_table.rom", sizeof(m_cfg->cfgtable), + &error_fatal); + memory_region_add_subregion(vms->sys, m_cfg->cfgbase, + &vms->parent_obj.cfgtable_rom); + fdt_add_hvx(vms, m_cfg); + + gsregs_dev = qdev_new(TYPE_HEXAGON_GLOBALREG); + object_property_add_child(OBJECT(ms), "global-regs", OBJECT(gsregs_dev)); + qdev_prop_set_uint64(gsregs_dev, "config-table-addr", m_cfg->cfgbase); + qdev_prop_set_uint32(gsregs_dev, "dsp-rev", v68_rev); + sysbus_realize_and_unref(SYS_BUS_DEVICE(gsregs_dev), &error_fatal); + + tlb_dev = qdev_new(TYPE_HEXAGON_TLB); + object_property_add_child(OBJECT(ms), "tlb", OBJECT(tlb_dev)); + qdev_prop_set_uint32(tlb_dev, "num-entries", + m_cfg->cfgtable.jtlb_size_entries); + sysbus_realize_and_unref(SYS_BUS_DEVICE(tlb_dev), &error_fatal); + + cpu0 = NULL; + for (int i = 0; i < ms->smp.cpus; i++) { + HexagonCPU *cpu = HEXAGON_CPU(object_new(ms->cpu_type)); + qemu_register_reset(do_cpu_reset, cpu); + + if (i == 0) { + cpu0 = DEVICE(cpu); + if (ms->kernel_filename) { + uint64_t entry = load_kernel(vms); + qdev_prop_set_uint32(cpu0, "exec-start-addr", entry); + } else if (ms->firmware) { + uint64_t entry = load_bios(vms); + qdev_prop_set_uint32(cpu0, "exec-start-addr", entry); + } + } + qdev_prop_set_uint32(DEVICE(cpu), "htid", i); + qdev_prop_set_bit(DEVICE(cpu), "start-powered-off", (i != 0)); + object_property_set_link(OBJECT(cpu), "global-regs", + OBJECT(gsregs_dev), &error_fatal); + object_property_set_link(OBJECT(cpu), "tlb", + OBJECT(tlb_dev), &error_fatal); + + qdev_realize_and_unref(DEVICE(cpu), NULL, &error_fatal); + } + fdt_add_cpu_nodes(vms); + clk_phandle = fdt_add_clocks(vms); + fdt_add_uart(vms, VIRT_UART0, clk_phandle); + + rom_add_blob_fixed_as("config_table.rom", &m_cfg->cfgtable, + sizeof(m_cfg->cfgtable), m_cfg->cfgbase, + &address_space_memory); + + hexagon_load_fdt(vms); +} + + +static void virt_class_init(ObjectClass *oc, const void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + + mc->desc = "Hexagon Virtual Machine"; + mc->init = virt_init; + mc->default_cpu_type = HEXAGON_CPU_TYPE_NAME("v68"); + mc->default_ram_size = 4 * GiB; + mc->max_cpus = 8; + mc->default_cpus = 8; + mc->is_default = false; + mc->default_kernel_irqchip_split = false; + mc->block_default_type = IF_VIRTIO; + mc->default_boot_order = NULL; + mc->no_cdrom = 1; + mc->numa_mem_supported = false; + mc->default_nic = "virtio-mmio-bus"; +} + + +static const TypeInfo virt_machine_types[] = { { + .name = TYPE_HEXAGON_VIRT_MACHINE, + .parent = TYPE_HEXAGON_COMMON_MACHINE, + .instance_size = sizeof(HexagonVirtMachineState), + .class_init = virt_class_init, + .instance_init = virt_instance_init, +} }; + +DEFINE_TYPES(virt_machine_types) diff --git a/hw/i2c/Kconfig b/hw/i2c/Kconfig index 596a7a3165..0766130b59 100644 --- a/hw/i2c/Kconfig +++ b/hw/i2c/Kconfig @@ -18,6 +18,11 @@ config ARM_SBCON_I2C bool select BITBANG_I2C +config DESIGNWARE_I2C + bool + select REGISTER + select I2C + config ACPI_SMBUS bool select SMBUS diff --git a/hw/i2c/designware_i2c.c b/hw/i2c/designware_i2c.c new file mode 100644 index 0000000000..daa9714cb7 --- /dev/null +++ b/hw/i2c/designware_i2c.c @@ -0,0 +1,745 @@ +/* + * DesignWare I2C Module. + * + * Copyright 2021 Google LLC + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" + +#include "hw/i2c/designware_i2c.h" +#include "migration/vmstate.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/units.h" + +#ifndef DESIGNWARE_I2C_ERR_DEBUG +#define DESIGNWARE_I2C_ERR_DEBUG 0 +#endif + +REG32(DW_IC_CON, 0x00) /* I2C control */ + FIELD(DW_IC_CON, STOP_DET_IF_MASTER_ACTIV, 10, 1) + FIELD(DW_IC_CON, RX_FIFO_FULL_HLD_CTRL, 9, 1) + FIELD(DW_IC_CON, TX_EMPTY_CTRL, 8, 1) + FIELD(DW_IC_CON, STOP_IF_ADDRESSED, 7, 1) + FIELD(DW_IC_CON, SLAVE_DISABLE, 6, 1) + FIELD(DW_IC_CON, IC_RESTART_EN, 5, 1) + FIELD(DW_IC_CON, 10BITADDR_MASTER, 4, 1) + FIELD(DW_IC_CON, 10BITADDR_SLAVE, 3, 1) + FIELD(DW_IC_CON, SPEED, 1, 2) + FIELD(DW_IC_CON, MASTER_MODE, 0, 1) +REG32(DW_IC_TAR, 0x04) /* I2C target address */ + FIELD(DW_IC_TAR, IC_10BITADDR_MASTER, 12, 1) + FIELD(DW_IC_TAR, SPECIAL, 11, 1) + FIELD(DW_IC_TAR, GC_OR_START, 10, 1) + FIELD(DW_IC_TAR, ADDRESS, 0, 10) +REG32(DW_IC_SAR, 0x08) /* I2C slave address */ +REG32(DW_IC_DATA_CMD, 0x10) + FIELD(DW_IC_DATA_CMD, RESTART, 10, 1) + FIELD(DW_IC_DATA_CMD, STOP, 9, 1) + FIELD(DW_IC_DATA_CMD, CMD, 8, 1) + FIELD(DW_IC_DATA_CMD, DAT, 0, 8) +REG32(DW_IC_SS_SCL_HCNT, 0x14) /* Standard speed i2c clock scl high count */ +REG32(DW_IC_SS_SCL_LCNT, 0x18) /* Standard speed i2c clock scl low count */ +REG32(DW_IC_FS_SCL_HCNT, 0x1c) /* Fast or fast plus i2c clock scl high count */ +REG32(DW_IC_FS_SCL_LCNT, 0x20) /* Fast or fast plus i2c clock scl low count */ +REG32(DW_IC_INTR_STAT, 0x2c) +REG32(DW_IC_INTR_MASK, 0x30) /* I2C Interrupt Mask */ +REG32(DW_IC_RAW_INTR_STAT, 0x34) /* I2C raw interrupt status */ + /* DW_IC_INTR_STAT/INTR_MASK/RAW_INTR_STAT fields */ + SHARED_FIELD(DW_IC_INTR_RESTART_DET, 12, 1) + SHARED_FIELD(DW_IC_INTR_GEN_CALL, 11, 1) + SHARED_FIELD(DW_IC_INTR_START_DET, 10, 1) + SHARED_FIELD(DW_IC_INTR_STOP_DET, 9, 1) + SHARED_FIELD(DW_IC_INTR_ACTIVITY, 8, 1) + SHARED_FIELD(DW_IC_INTR_RX_DONE, 7, 1) + SHARED_FIELD(DW_IC_INTR_TX_ABRT, 6, 1) + SHARED_FIELD(DW_IC_INTR_RD_REQ, 5, 1) + SHARED_FIELD(DW_IC_INTR_TX_EMPTY, 4, 1) /* Hardware clear only. */ + SHARED_FIELD(DW_IC_INTR_TX_OVER, 3, 1) + SHARED_FIELD(DW_IC_INTR_RX_FULL, 2, 1) /* Hardware clear only. */ + SHARED_FIELD(DW_IC_INTR_RX_OVER, 1, 1) + SHARED_FIELD(DW_IC_INTR_RX_UNDER, 0, 1) + +#define DW_IC_INTR_ANY_MASK \ + (DW_IC_INTR_RESTART_DET_MASK | \ + DW_IC_INTR_GEN_CALL_MASK | \ + DW_IC_INTR_START_DET_MASK | \ + DW_IC_INTR_STOP_DET_MASK | \ + DW_IC_INTR_ACTIVITY_MASK | \ + DW_IC_INTR_RX_DONE_MASK | \ + DW_IC_INTR_TX_ABRT_MASK | \ + DW_IC_INTR_RD_REQ_MASK | \ + DW_IC_INTR_TX_EMPTY_MASK | \ + DW_IC_INTR_TX_OVER_MASK | \ + DW_IC_INTR_RX_FULL_MASK | \ + DW_IC_INTR_RX_OVER_MASK | \ + DW_IC_INTR_RX_UNDER_MASK) + +#define DW_IC_INTR_ANY_SW_CLEAR_MASK \ + (DW_IC_INTR_ANY_MASK & \ + ~(DW_IC_INTR_TX_EMPTY_MASK | \ + DW_IC_INTR_RX_FULL_MASK)) + +REG32(DW_IC_RX_TL, 0x38) /* I2C receive FIFO threshold */ +REG32(DW_IC_TX_TL, 0x3c) /* I2C transmit FIFO threshold */ +REG32(DW_IC_CLR_INTR, 0x40) +REG32(DW_IC_CLR_RX_UNDER, 0x44) +REG32(DW_IC_CLR_RX_OVER, 0x48) +REG32(DW_IC_CLR_TX_OVER, 0x4c) +REG32(DW_IC_CLR_RD_REQ, 0x50) +REG32(DW_IC_CLR_TX_ABRT, 0x54) +REG32(DW_IC_CLR_RX_DONE, 0x58) +REG32(DW_IC_CLR_ACTIVITY, 0x5c) +REG32(DW_IC_CLR_STOP_DET, 0x60) +REG32(DW_IC_CLR_START_DET, 0x64) +REG32(DW_IC_CLR_GEN_CALL, 0x68) +REG32(DW_IC_ENABLE, 0x6c) /* I2C enable */ + FIELD(DW_IC_ENABLE, TX_CMD_BLOCK, 2, 1) + FIELD(DW_IC_ENABLE, ABORT, 1, 1) + FIELD(DW_IC_ENABLE, ENABLE, 0, 1) +REG32(DW_IC_STATUS, 0x70) /* I2C status */ + FIELD(DW_IC_STATUS, SLV_ACTIVITY, 6, 1) + FIELD(DW_IC_STATUS, MST_ACTIVITY, 5, 1) + FIELD(DW_IC_STATUS, RFF, 4, 1) + FIELD(DW_IC_STATUS, RFNE, 3, 1) + FIELD(DW_IC_STATUS, TFE, 2, 1) + FIELD(DW_IC_STATUS, TFNF, 1, 1) + FIELD(DW_IC_STATUS, ACTIVITY, 0, 1) +REG32(DW_IC_TXFLR, 0x74) /* I2C transmit fifo level */ +REG32(DW_IC_RXFLR, 0x78) /* I2C receive fifo level */ +REG32(DW_IC_SDA_HOLD, 0x7c) /* I2C SDA hold time length */ +REG32(DW_IC_TX_ABRT_SOURCE, 0x80) /* The I2C transmit abort source */ + FIELD(DW_IC_TX_ABRT_SOURCE, USER_ABRT, 16, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, SLVRD_INTX, 15, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, SLV_ARBLOST, 14, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, SLVFLUSH_TXFIFO, 13, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, ARB_LOST, 12, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, MASTER_DIS, 11, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, 10B_RD_NORSTRT, 10, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, SBYTE_NORSTRT, 9, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, HS_NORSTRT, 8, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, SBYTE_ACKDET, 7, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, HS_ACKDET, 6, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, GCALL_READ, 5, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, GCALL_NOACK, 4, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, TXDATA_NOACK, 3, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, 10ADDR2_NOACK, 2, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, 10ADDR1_NOACK, 1, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, 7B_ADDR_NOACK, 0, 1) +REG32(DW_IC_SLV_DATA_NACK_ONLY, 0x84) +REG32(DW_IC_DMA_CR, 0x88) +REG32(DW_IC_DMA_TDLR, 0x8c) +REG32(DW_IC_DMA_RDLR, 0x90) +REG32(DW_IC_SDA_SETUP, 0x94) /* I2C SDA setup */ +REG32(DW_IC_ACK_GENERAL_CALL, 0x98) +REG32(DW_IC_ENABLE_STATUS, 0x9c) /* I2C enable status */ + FIELD(DW_IC_ENABLE_STATUS, SLV_RX_DATA_LOST, 2, 1) + FIELD(DW_IC_ENABLE_STATUS, SLV_DISABLED_WHILE_BUSY, 1, 1) + FIELD(DW_IC_ENABLE_STATUS, IC_EN, 0, 1) +REG32(DW_IC_FS_SPKLEN, 0xa0) /* I2C SS, FS or FM+ spike suppression limit */ +REG32(DW_IC_CLR_RESTART_DET, 0xa8) +REG32(DW_IC_SMBUS_INTR_MASK, 0xcc) /* SMBus Interrupt Mask */ +REG32(DW_IC_COMP_PARAM_1, 0xf4) /* Component parameter */ + FIELD(DW_IC_COMP_PARAM_1, TX_FIFO_SIZE, 16, 8) + FIELD(DW_IC_COMP_PARAM_1, RX_FIFO_SIZE, 8, 8) + FIELD(DW_IC_COMP_PARAM_1, HAS_ENCODED_PARAMS, 7, 1) + FIELD(DW_IC_COMP_PARAM_1, HAS_DMA, 6, 1) + FIELD(DW_IC_COMP_PARAM_1, INTR_IO, 5, 1) + FIELD(DW_IC_COMP_PARAM_1, HC_COUNT_VAL, 4, 1) + FIELD(DW_IC_COMP_PARAM_1, HIGH_SPEED_MODE, 2, 2) + FIELD(DW_IC_COMP_PARAM_1, APB_DATA_WIDTH_32, 0, 2) +REG32(DW_IC_COMP_VERSION, 0xf8) /* I2C component version */ +REG32(DW_IC_COMP_TYPE, 0xfc) /* I2C component type */ + +static void dw_i2c_update_irq(DesignWareI2CState *s) +{ + uint32_t intr = s->regs[R_DW_IC_RAW_INTR_STAT] & s->regs[R_DW_IC_INTR_MASK]; + + qemu_set_irq(s->irq, !!(intr & DW_IC_INTR_ANY_MASK)); +} + +static uint64_t dw_ic_data_cmd_reg_post_read(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + g_assert(value == 0); + + if (s->status != DW_I2C_STATUS_RECEIVING) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Attempted to read from RX fifo when not in receive " + "state.\n", DEVICE(s)->canonical_path); + if (s->status != DW_I2C_STATUS_IDLE) { + SHARED_ARRAY_FIELD_DP32(s->regs, R_DW_IC_RAW_INTR_STAT, + DW_IC_INTR_RX_UNDER, 1); + dw_i2c_update_irq(s); + } + return 0; + } + + g_assert(s->regs[R_DW_IC_RXFLR] == fifo8_num_used(&s->rx_fifo)); + + if (fifo8_is_empty(&s->rx_fifo)) { + SHARED_ARRAY_FIELD_DP32(s->regs, R_DW_IC_RAW_INTR_STAT, DW_IC_INTR_RX_UNDER, 1); + dw_i2c_update_irq(s); + return 0; + } + + s->regs[R_DW_IC_RXFLR]--; + if (s->regs[R_DW_IC_RXFLR] <= s->regs[R_DW_IC_RX_TL]) { + SHARED_ARRAY_FIELD_DP32(s->regs, R_DW_IC_RAW_INTR_STAT, DW_IC_INTR_RX_FULL, 0); + dw_i2c_update_irq(s); + } + + return fifo8_pop(&s->rx_fifo); +} + +static uint64_t dw_ic_clr_intr_reg_post_read(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + g_assert(value == 0); + + switch (reg->access->addr) { + case A_DW_IC_CLR_INTR: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_ANY_SW_CLEAR_MASK; + break; + case A_DW_IC_CLR_RX_UNDER: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_RX_UNDER_MASK; + break; + case A_DW_IC_CLR_RX_OVER: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_RX_OVER_MASK; + break; + case A_DW_IC_CLR_TX_OVER: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_TX_OVER_MASK; + break; + case A_DW_IC_CLR_RD_REQ: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_RD_REQ_MASK; + break; + case A_DW_IC_CLR_TX_ABRT: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_TX_ABRT_MASK; + break; + case A_DW_IC_CLR_RX_DONE: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_RX_DONE_MASK; + break; + case A_DW_IC_CLR_ACTIVITY: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_ACTIVITY_MASK; + break; + case A_DW_IC_CLR_STOP_DET: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_STOP_DET_MASK; + break; + case A_DW_IC_CLR_START_DET: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_START_DET_MASK; + break; + case A_DW_IC_CLR_GEN_CALL: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_GEN_CALL_MASK; + break; + case A_DW_IC_CLR_RESTART_DET: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_RESTART_DET_MASK; + break; + default: + g_assert_not_reached(); + } + + dw_i2c_update_irq(s); + + return 0; +} + +static uint64_t dw_ic_intr_stat_reg_post_read(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + g_assert(value == 0); + + return s->regs[R_DW_IC_RAW_INTR_STAT] & s->regs[R_DW_IC_INTR_MASK]; +} + +static uint64_t dw_ic_unsupported_reg_post_read(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + qemu_log_mask(LOG_UNIMP, "%s: unsupported read - %s\n", + DEVICE(s)->canonical_path, reg->access->name); + + return 0; +} + +static uint64_t dw_ic_unsupported_reg_pre_write(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + qemu_log_mask(LOG_UNIMP, "%s: unsupported write - %s\n", + DEVICE(s)->canonical_path, reg->access->name); + + return 0; +} + +static uint64_t dw_ic_con_reg_pre_write(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + if (s->regs[R_DW_IC_ENABLE] & R_DW_IC_ENABLE_ENABLE_MASK) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid setting to ic_con %d when ic_enable[0]==1\n", + DEVICE(s)->canonical_path, (int)value); + return s->regs[R_DW_IC_CON]; /* keep old value */ + } + + return value; +} + +static void dw_i2c_reset_to_idle(DesignWareI2CState *s) +{ + s->regs[R_DW_IC_ENABLE_STATUS] &= ~R_DW_IC_ENABLE_STATUS_IC_EN_MASK; + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_TX_EMPTY_MASK; + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_RX_FULL_MASK; + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_RX_UNDER_MASK; + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_RX_OVER_MASK; + s->regs[R_DW_IC_RXFLR] = 0; + fifo8_reset(&s->rx_fifo); + s->regs[R_DW_IC_STATUS] &= ~R_DW_IC_STATUS_ACTIVITY_MASK; + s->status = DW_I2C_STATUS_IDLE; + dw_i2c_update_irq(s); +} + +static void dw_ic_tx_abort(DesignWareI2CState *s, uint32_t src) +{ + s->regs[R_DW_IC_TX_ABRT_SOURCE] |= src; + s->regs[R_DW_IC_RAW_INTR_STAT] |= DW_IC_INTR_TX_ABRT_MASK; + dw_i2c_reset_to_idle(s); + dw_i2c_update_irq(s); +} + +static void dw_ic_data_cmd_reg_post_write(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + int recv = !!(value & R_DW_IC_DATA_CMD_CMD_MASK); + + s->regs[R_DW_IC_DATA_CMD] = 0; /* Register has no storage */ + + if (!(s->regs[R_DW_IC_ENABLE] & R_DW_IC_ENABLE_ENABLE_MASK)) { + /* + * Controller is not enabled. The register_reset() path also lands + * here with value == 0, so silently ignore rather than reporting + * a spurious guest error. + */ + return; + } + + if (s->status == DW_I2C_STATUS_IDLE || + s->regs[R_DW_IC_RAW_INTR_STAT] & DW_IC_INTR_TX_ABRT_MASK) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Attempted to write to TX fifo when it is held in " + "reset\n", DEVICE(s)->canonical_path); + return; + } + + /* Send the address if it hasn't been sent yet. */ + if (s->status == DW_I2C_STATUS_SENDING_ADDRESS) { + int rv = i2c_start_transfer(s->bus, + ARRAY_FIELD_EX32(s->regs, DW_IC_TAR, ADDRESS), recv); + if (rv) { + dw_ic_tx_abort(s, R_DW_IC_TX_ABRT_SOURCE_7B_ADDR_NOACK_MASK); + return; + } + s->status = recv ? DW_I2C_STATUS_RECEIVING : DW_I2C_STATUS_SENDING; + } + + /* Send data */ + if (!recv) { + int rv = i2c_send(s->bus, FIELD_EX32(value, DW_IC_DATA_CMD, DAT)); + if (rv) { + i2c_end_transfer(s->bus); + dw_ic_tx_abort(s, R_DW_IC_TX_ABRT_SOURCE_TXDATA_NOACK_MASK); + return; + } + dw_i2c_update_irq(s); + } + + /* Restart command */ + if (value & R_DW_IC_DATA_CMD_RESTART_MASK && + s->regs[R_DW_IC_CON] & R_DW_IC_CON_IC_RESTART_EN_MASK) { + s->regs[R_DW_IC_RAW_INTR_STAT] |= DW_IC_INTR_RESTART_DET_MASK | + DW_IC_INTR_START_DET_MASK | + DW_IC_INTR_ACTIVITY_MASK; + s->regs[R_DW_IC_STATUS] |= R_DW_IC_STATUS_ACTIVITY_MASK; + dw_i2c_update_irq(s); + + if (i2c_start_transfer(s->bus, + ARRAY_FIELD_EX32(s->regs, DW_IC_TAR, ADDRESS), recv)) { + dw_ic_tx_abort(s, R_DW_IC_TX_ABRT_SOURCE_7B_ADDR_NOACK_MASK); + return; + } + + s->status = recv ? DW_I2C_STATUS_RECEIVING : DW_I2C_STATUS_SENDING; + } + + /* Receive data */ + if (recv) { + g_assert(s->regs[R_DW_IC_RXFLR] == fifo8_num_used(&s->rx_fifo)); + + if (!fifo8_is_full(&s->rx_fifo)) { + fifo8_push(&s->rx_fifo, i2c_recv(s->bus)); + s->regs[R_DW_IC_RXFLR]++; + } else { + s->regs[R_DW_IC_RAW_INTR_STAT] |= DW_IC_INTR_RX_OVER_MASK; + dw_i2c_update_irq(s); + } + + if (s->regs[R_DW_IC_RXFLR] > s->regs[R_DW_IC_RX_TL]) { + s->regs[R_DW_IC_RAW_INTR_STAT] |= DW_IC_INTR_RX_FULL_MASK; + dw_i2c_update_irq(s); + } + if (value & R_DW_IC_DATA_CMD_STOP_MASK) { + i2c_nack(s->bus); + } + } + + /* Stop command */ + if (value & R_DW_IC_DATA_CMD_STOP_MASK) { + s->regs[R_DW_IC_RAW_INTR_STAT] |= DW_IC_INTR_STOP_DET_MASK; + s->regs[R_DW_IC_STATUS] &= ~R_DW_IC_STATUS_ACTIVITY_MASK; + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_TX_EMPTY_MASK; + i2c_end_transfer(s->bus); + dw_i2c_update_irq(s); + } +} + +static void dw_ic_intr_mask_reg_post_write(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + dw_i2c_update_irq(s); +} + +static uint64_t dw_ic_enable_reg_pre_write(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + if (value & R_DW_IC_ENABLE_ENABLE_MASK && + !(s->regs[R_DW_IC_CON] & R_DW_IC_CON_SLAVE_DISABLE_MASK)) { + qemu_log_mask(LOG_UNIMP, + "%s: Designware I2C slave mode is not supported.\n", + DEVICE(s)->canonical_path); + return s->regs[R_DW_IC_ENABLE]; /* keep old value */ + } + + return value; +} + +static void dw_ic_enable_reg_post_write(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + s->regs[R_DW_IC_ENABLE] = value & R_DW_IC_ENABLE_ENABLE_MASK; + + if (value & R_DW_IC_ENABLE_ABORT_MASK || value & R_DW_IC_ENABLE_TX_CMD_BLOCK_MASK) { + dw_ic_tx_abort(s, R_DW_IC_TX_ABRT_SOURCE_USER_ABRT_MASK); + return; + } + + if (value & R_DW_IC_ENABLE_ENABLE_MASK) { + s->regs[R_DW_IC_ENABLE_STATUS] |= R_DW_IC_ENABLE_STATUS_IC_EN_MASK; + s->regs[R_DW_IC_STATUS] |= R_DW_IC_STATUS_ACTIVITY_MASK; + s->regs[R_DW_IC_RAW_INTR_STAT] |= DW_IC_INTR_ACTIVITY_MASK | + DW_IC_INTR_START_DET_MASK | + DW_IC_INTR_TX_EMPTY_MASK; + s->status = DW_I2C_STATUS_SENDING_ADDRESS; + dw_i2c_update_irq(s); + } else if ((value & R_DW_IC_ENABLE_ENABLE_MASK) == 0) { + dw_i2c_reset_to_idle(s); + } +} + +static uint64_t dw_ic_rx_tl_reg_pre_write(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + /* Note that a value of 0 for ic_rx_tl indicates a threshold of 1. */ + if (value > DESIGNWARE_I2C_RX_FIFO_SIZE - 1) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid setting to ic_rx_tl %d\n", + DEVICE(s)->canonical_path, (int)value); + return DESIGNWARE_I2C_RX_FIFO_SIZE - 1; + } + + return value; +} + +static void dw_ic_rx_tl_reg_post_write(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + if (s->regs[R_DW_IC_RXFLR] > s->regs[R_DW_IC_RX_TL] && + s->regs[R_DW_IC_ENABLE] & R_DW_IC_ENABLE_ENABLE_MASK) { + s->regs[R_DW_IC_RAW_INTR_STAT] |= DW_IC_INTR_RX_FULL_MASK; + } else { + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_RX_FULL_MASK; + } + dw_i2c_update_irq(s); +} + +static uint64_t dw_ic_tx_tl_reg_pre_write(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + /* + * Note that a value of 0 for ic_tx_tl indicates a threshold of 1. + * However, the tx threshold is not used in the model because commands are + * always sent out as soon as they are written. + */ + if (value > DESIGNWARE_I2C_TX_FIFO_SIZE - 1) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid setting to ic_tx_tl %d\n", + DEVICE(s)->canonical_path, (int)value); + return DESIGNWARE_I2C_TX_FIFO_SIZE - 1; + } + + return value; +} + +static const RegisterAccessInfo designware_i2c_regs_info[] = { + { .name = "DW_IC_CON", .addr = A_DW_IC_CON, + .reset = 0x7d, + .unimp = 0xfffffc00, + .pre_write = dw_ic_con_reg_pre_write, + },{ .name = "DW_IC_TAR", .addr = A_DW_IC_TAR, + .reset = 0x1055, + .unimp = 0xfffff000, + },{ .name = "DW_IC_SAR", .addr = A_DW_IC_SAR, + .reset = 0x55, + .unimp = 0xfffffc00, + .post_read = dw_ic_unsupported_reg_post_read, + .pre_write = dw_ic_unsupported_reg_pre_write, + },{ .name = "DW_IC_DATA_CMD", .addr = A_DW_IC_DATA_CMD, + .post_read = dw_ic_data_cmd_reg_post_read, + .post_write = dw_ic_data_cmd_reg_post_write, + },{ .name = "DW_IC_SS_SCL_HCNT", .addr = A_DW_IC_SS_SCL_HCNT, + .reset = 0x190, + .unimp = 0xffff0000, + },{ .name = "DW_IC_SS_SCL_LCNT", .addr = A_DW_IC_SS_SCL_LCNT, + .reset = 0x1d6, + .unimp = 0xffff0000, + },{ .name = "DW_IC_FS_SCL_HCNT", .addr = A_DW_IC_FS_SCL_HCNT, + .reset = 0x3c, + .unimp = 0xffff0000, + },{ .name = "DW_IC_FS_SCL_LCNT", .addr = A_DW_IC_FS_SCL_LCNT, + .reset = 0x82, + .unimp = 0xffff0000, + },{ .name = "DW_IC_INTR_STAT", .addr = A_DW_IC_INTR_STAT, + .ro = 0xffffffff, + .post_read = dw_ic_intr_stat_reg_post_read, + },{ .name = "DW_IC_INTR_MASK", .addr = A_DW_IC_INTR_MASK, + .reset = 0x8ff, + .unimp = 0xffff8000, + .post_write = dw_ic_intr_mask_reg_post_write, + },{ .name = "DW_IC_RAW_INTR_STAT", .addr = A_DW_IC_RAW_INTR_STAT, + .ro = 0xffffffff, + },{ .name = "DW_IC_RX_TL", .addr = A_DW_IC_RX_TL, + .pre_write = dw_ic_rx_tl_reg_pre_write, + .post_write = dw_ic_rx_tl_reg_post_write, + },{ .name = "DW_IC_TX_TL", .addr = A_DW_IC_TX_TL, + .pre_write = dw_ic_tx_tl_reg_pre_write, + },{ .name = "DW_IC_CLR_INTR", .addr = A_DW_IC_CLR_INTR, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_RX_UNDER", .addr = A_DW_IC_CLR_RX_UNDER, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_RX_OVER", .addr = A_DW_IC_CLR_RX_OVER, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_TX_OVER", .addr = A_DW_IC_CLR_TX_OVER, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_RD_REQ", .addr = A_DW_IC_CLR_RD_REQ, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_TX_ABRT", .addr = A_DW_IC_CLR_TX_ABRT, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_RX_DONE", .addr = A_DW_IC_CLR_RX_DONE, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_ACTIVITY", .addr = A_DW_IC_CLR_ACTIVITY, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_STOP_DET", .addr = A_DW_IC_CLR_STOP_DET, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_START_DET", .addr = A_DW_IC_CLR_START_DET, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_GEN_CALL", .addr = A_DW_IC_CLR_GEN_CALL, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_ENABLE", .addr = A_DW_IC_ENABLE, + .unimp = 0xfffffff8, + .pre_write = dw_ic_enable_reg_pre_write, + .post_write = dw_ic_enable_reg_post_write, + },{ .name = "DW_IC_STATUS", .addr = A_DW_IC_STATUS, + .reset = 0x6, + .ro = 0xffffffff, + },{ .name = "DW_IC_TXFLR", .addr = A_DW_IC_TXFLR, + .ro = 0xffffffff, + },{ .name = "DW_IC_RXFLR", .addr = A_DW_IC_RXFLR, + .ro = 0xffffffff, + },{ .name = "DW_IC_SDA_HOLD", .addr = A_DW_IC_SDA_HOLD, + .reset = 0x1, + .unimp = 0xff000000, + },{ .name = "DW_IC_TX_ABRT_SOURCE", .addr = A_DW_IC_TX_ABRT_SOURCE, + .ro = 0xffffffff, + },{ .name = "DW_IC_SLV_DATA_NACK_ONLY", .addr = A_DW_IC_SLV_DATA_NACK_ONLY, + .post_read = dw_ic_unsupported_reg_post_read, + .pre_write = dw_ic_unsupported_reg_pre_write, + },{ .name = "DW_IC_DMA_CR", .addr = A_DW_IC_DMA_CR, + .post_read = dw_ic_unsupported_reg_post_read, + .pre_write = dw_ic_unsupported_reg_pre_write, + },{ .name = "DW_IC_DMA_TDLR", .addr = A_DW_IC_DMA_TDLR, + .post_read = dw_ic_unsupported_reg_post_read, + .pre_write = dw_ic_unsupported_reg_pre_write, + },{ .name = "DW_IC_DMA_RDLR", .addr = A_DW_IC_DMA_RDLR, + .post_read = dw_ic_unsupported_reg_post_read, + .pre_write = dw_ic_unsupported_reg_pre_write, + },{ .name = "DW_IC_SDA_SETUP", .addr = A_DW_IC_SDA_SETUP, + .reset = 0x64, + .unimp = 0xffffff00, + },{ .name = "DW_IC_ACK_GENERAL_CALL", .addr = A_DW_IC_ACK_GENERAL_CALL, + .post_read = dw_ic_unsupported_reg_post_read, + .pre_write = dw_ic_unsupported_reg_pre_write, + },{ .name = "DW_IC_ENABLE_STATUS", .addr = A_DW_IC_ENABLE_STATUS, + .ro = 0xffffffff, + },{ .name = "DW_IC_FS_SPKLEN", .addr = A_DW_IC_FS_SPKLEN, + .reset = 0x2, + .ro = 0xffffff00, + },{ .name = "DW_IC_CLR_RESTART_DET", .addr = A_DW_IC_CLR_RESTART_DET, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_SMBUS_INTR_MASK", .addr = A_DW_IC_SMBUS_INTR_MASK, + /* No SMBus interrupts are implemented, Linux updates the mask */ + .reset = 0x7ff, + .unimp = 0xfffff800, + },{ .name = "DW_IC_COMP_PARAM_1", .addr = A_DW_IC_COMP_PARAM_1, + .reset = /* HAS_DMA and HC_COUNT_VAL are disabled */ + ((2 << R_DW_IC_COMP_PARAM_1_APB_DATA_WIDTH_32_SHIFT) | + R_DW_IC_COMP_PARAM_1_HIGH_SPEED_MODE_MASK | + R_DW_IC_COMP_PARAM_1_INTR_IO_MASK | + R_DW_IC_COMP_PARAM_1_HAS_ENCODED_PARAMS_MASK | + ((DESIGNWARE_I2C_RX_FIFO_SIZE - 1) + << R_DW_IC_COMP_PARAM_1_RX_FIFO_SIZE_SHIFT) | + ((DESIGNWARE_I2C_TX_FIFO_SIZE - 1) + << R_DW_IC_COMP_PARAM_1_TX_FIFO_SIZE_SHIFT)), + .ro = 0xffffffff, + },{ .name = "DW_IC_COMP_VERSION", .addr = A_DW_IC_COMP_VERSION, + .reset = 0x3132302a, + .ro = 0xffffffff, + },{ .name = "DW_IC_COMP_TYPE", .addr = A_DW_IC_COMP_TYPE, + .reset = 0x44570140, + .ro = 0xffffffff, + } +}; + +static const MemoryRegionOps designware_i2c_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, + .unaligned = false, + }, +}; + +static void designware_i2c_enter_reset(Object *obj, ResetType type) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(obj); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(s->regs); ++i) { + register_reset(&s->regs_info[i]); + } + + fifo8_reset(&s->rx_fifo); + + s->status = DW_I2C_STATUS_IDLE; +} + +static void designware_i2c_hold_reset(Object *obj, ResetType type) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(obj); + + qemu_irq_lower(s->irq); +} + +static const VMStateDescription vmstate_designware_i2c = { + .name = TYPE_DESIGNWARE_I2C, + .version_id = 0, + .minimum_version_id = 0, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, DesignWareI2CState, DESIGNWARE_I2C_R_MAX), + VMSTATE_FIFO8(rx_fifo, DesignWareI2CState), + VMSTATE_UINT32(status, DesignWareI2CState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void designware_i2c_instance_init(Object *obj) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + RegisterInfoArray *reg_array; + + fifo8_create(&s->rx_fifo, DESIGNWARE_I2C_RX_FIFO_SIZE); + + s->bus = i2c_init_bus(DEVICE(s), "i2c-bus"); + + memory_region_init(&s->iomem, obj, TYPE_DESIGNWARE_I2C, 4 * KiB); + reg_array = register_init_block32(DEVICE(obj), designware_i2c_regs_info, + ARRAY_SIZE(designware_i2c_regs_info), + s->regs_info, s->regs, + &designware_i2c_ops, + DESIGNWARE_I2C_ERR_DEBUG, + DESIGNWARE_I2C_R_MAX * 4); + memory_region_add_subregion(&s->iomem, 0, ®_array->mem); + + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); +} + +static void designware_i2c_finalize(Object *obj) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(obj); + + fifo8_destroy(&s->rx_fifo); +} + +static void designware_i2c_class_init(ObjectClass *klass, const void *data) +{ + ResettableClass *rc = RESETTABLE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "Designware I2C"; + dc->vmsd = &vmstate_designware_i2c; + rc->phases.enter = designware_i2c_enter_reset; + rc->phases.hold = designware_i2c_hold_reset; + + set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); +} + +static const TypeInfo designware_i2c_types[] = { + { + .name = TYPE_DESIGNWARE_I2C, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(DesignWareI2CState), + .class_init = designware_i2c_class_init, + .instance_init = designware_i2c_instance_init, + .instance_finalize = designware_i2c_finalize, + }, +}; +DEFINE_TYPES(designware_i2c_types); diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build index c459adcb59..88aea35662 100644 --- a/hw/i2c/meson.build +++ b/hw/i2c/meson.build @@ -11,6 +11,7 @@ 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')) i2c_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_smbus.c')) +i2c_ss.add(when: 'CONFIG_DESIGNWARE_I2C', if_true: files('designware_i2c.c')) i2c_ss.add(when: 'CONFIG_SMBUS_EEPROM', if_true: files('smbus_eeprom.c')) i2c_ss.add(when: 'CONFIG_ARM_SBCON_I2C', if_true: files('arm_sbcon_i2c.c')) i2c_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_i2c.c')) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index 6b1dd04991..08878193df 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -4,6 +4,11 @@ * Copyright (c) 2025 Linaro Limited * * SPDX-License-Identifier: GPL-2.0-or-later + * + * The IRS is defined in IHI 111701 + * (ARM Generic Interrupt Controller Architecture Specification, + * GIC architecture version 5): + * https://developer.arm.com/documentation/111701/latest */ #include "qemu/osdep.h" @@ -424,8 +429,7 @@ static void irs_recalc_hppi(GICv5 *s, GICv5Domain domain, uint32_t iaffid) ARMCPU *cpu = cpuidx >= 0 ? cs->cpus[cpuidx] : NULL; GICv5PendingIrq best; - best.intid = 0; - best.prio = PRIO_IDLE; + best = GICV5_PENDING_IRQ_NONE; if (!cpu) { /* Nothing happens for iaffids targeting nonexistent CPUs */ @@ -521,8 +525,7 @@ static void irs_recall_hppis(GICv5 *s, GICv5Domain domain) GICv5Common *cs = ARM_GICV5_COMMON(s); for (int i = 0; i < cs->num_cpus; i++) { - s->hppi[domain][i].intid = 0; - s->hppi[domain][i].prio = PRIO_IDLE; + s->hppi[domain][i] = GICV5_PENDING_IRQ_NONE; gicv5_forward_interrupt(cs->cpus[i], domain); } } diff --git a/hw/intc/riscv_aplic.c b/hw/intc/riscv_aplic.c index c2c67c29e6..84606e9f3d 100644 --- a/hw/intc/riscv_aplic.c +++ b/hw/intc/riscv_aplic.c @@ -910,6 +910,10 @@ static void riscv_aplic_reset_enter(Object *obj, ResetType type) RISCVAPLICState *aplic = RISCV_APLIC(obj); int i; + if (!riscv_use_emulated_aplic(aplic->msimode)) { + return; + } + aplic->domaincfg = 0; memset(aplic->sourcecfg, 0, sizeof(uint32_t) * aplic->num_irqs); memset(aplic->target, 0, sizeof(uint32_t) * aplic->num_irqs); diff --git a/hw/intc/riscv_imsic.c b/hw/intc/riscv_imsic.c index 1c86ecdb2c..7e5b5349ba 100644 --- a/hw/intc/riscv_imsic.c +++ b/hw/intc/riscv_imsic.c @@ -347,6 +347,10 @@ static void riscv_imsic_reset_enter(Object *obj, ResetType type) RISCVIMSICState *imsic = RISCV_IMSIC(obj); int i; + if (kvm_irqchip_in_kernel()) { + return; + } + memset(imsic->eidelivery, 0, sizeof(uint32_t) * imsic->num_pages); memset(imsic->eithreshold, 0, sizeof(uint32_t) * imsic->num_pages); diff --git a/hw/meson.build b/hw/meson.build index ef65ba5195..7fa81db453 100644 --- a/hw/meson.build +++ b/hw/meson.build @@ -3,6 +3,7 @@ subdir('alpha') subdir('arm') subdir('avr') subdir('hppa') +subdir('hexagon') subdir('xenpv') # i386 uses it subdir('i386') subdir('loongarch') diff --git a/hw/misc/imx6_ccm.c b/hw/misc/imx6_ccm.c index a10b67d396..45fdd0d5a8 100644 --- a/hw/misc/imx6_ccm.c +++ b/hw/misc/imx6_ccm.c @@ -257,6 +257,15 @@ static uint64_t imx6_analog_get_pll2_clk(IMX6CCMState *dev) return freq; } +static uint64_t imx6_analog_get_pll3_clk(IMX6CCMState *dev) +{ + uint64_t freq = 480000000; + + trace_imx6_analog_get_pll3_clk(freq); + + return freq; +} + static uint64_t imx6_analog_get_pll2_pfd0_clk(IMX6CCMState *dev) { uint64_t freq = 0; @@ -344,6 +353,18 @@ static uint64_t imx6_ccm_get_per_clk(IMX6CCMState *dev) return freq; } +static uint64_t imx6_ccm_get_can_clk(IMX6CCMState *dev) +{ + uint64_t freq = 0; + + freq = imx6_analog_get_pll3_clk(dev) / 8; + freq /= (1 + EXTRACT(dev->ccm[CCM_CSCMR2], CAN_CLK_PODF)); + + trace_imx6_ccm_get_can_clk(freq); + + return freq; +} + static uint32_t imx6_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock) { uint32_t freq = 0; @@ -358,6 +379,9 @@ static uint32_t imx6_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock) case CLK_IPG_HIGH: freq = imx6_ccm_get_per_clk(s); break; + case CLK_CAN: + freq = imx6_ccm_get_can_clk(s); + break; case CLK_32k: freq = CKIL_FREQ; break; diff --git a/hw/misc/ivshmem-pci.c b/hw/misc/ivshmem-pci.c index c987eebb98..536475e9de 100644 --- a/hw/misc/ivshmem-pci.c +++ b/hw/misc/ivshmem-pci.c @@ -26,7 +26,7 @@ #include "hw/core/qdev-properties-system.h" #include "hw/pci/msi.h" #include "hw/pci/msix.h" -#include "system/kvm.h" +#include "system/accel-irq.h" #include "migration/blocker.h" #include "migration/vmstate.h" #include "qemu/error-report.h" @@ -424,19 +424,19 @@ static void ivshmem_add_kvm_msi_virq(IVShmemState *s, int vector, Error **errp) { PCIDevice *pdev = PCI_DEVICE(s); - KVMRouteChange c; + AccelRouteChange c; int ret; IVSHMEM_DPRINTF("ivshmem_add_kvm_msi_virq vector:%d\n", vector); assert(!s->msi_vectors[vector].pdev); - c = kvm_irqchip_begin_route_changes(kvm_state); + c = accel_irqchip_begin_route_changes(); ret = kvm_irqchip_add_msi_route(&c, vector, pdev); if (ret < 0) { error_setg(errp, "kvm_irqchip_add_msi_route failed"); return; } - kvm_irqchip_commit_route_changes(&c); + accel_irqchip_commit_route_changes(&c); s->msi_vectors[vector].virq = ret; s->msi_vectors[vector].pdev = pdev; diff --git a/hw/misc/trace-events b/hw/misc/trace-events index b88accc437..99910fc068 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -242,11 +242,13 @@ imx6_analog_get_periph_clk(uint32_t freq) "freq = %u Hz" imx6_analog_get_pll2_clk(uint32_t freq) "freq = %u Hz" imx6_analog_get_pll2_pfd0_clk(uint32_t freq) "freq = %u Hz" imx6_analog_get_pll2_pfd2_clk(uint32_t freq) "freq = %u Hz" +imx6_analog_get_pll3_clk(uint32_t freq) "freq = %u Hz" imx6_analog_read(const char *reg, uint32_t value) "reg[%s] => 0x%" PRIx32 imx6_analog_write(const char *reg, uint32_t value) "reg[%s] <= 0x%" PRIx32 imx6_ccm_get_ahb_clk(uint32_t freq) "freq = %u Hz" imx6_ccm_get_ipg_clk(uint32_t freq) "freq = %u Hz" imx6_ccm_get_per_clk(uint32_t freq) "freq = %u Hz" +imx6_ccm_get_can_clk(uint32_t freq) "freq = %u Hz" imx6_ccm_get_clock_frequency(unsigned clock, uint32_t freq) "(Clock = %d) = %u" imx6_ccm_read(const char *reg, uint32_t value) "reg[%s] => 0x%" PRIx32 imx6_ccm_reset(void) "" diff --git a/hw/net/Kconfig b/hw/net/Kconfig index f9a1dfb80d..b56a173eed 100644 --- a/hw/net/Kconfig +++ b/hw/net/Kconfig @@ -157,3 +157,8 @@ config CAN_CTUCANFD_PCI default y if PCI_DEVICES depends on PCI && CAN_CTUCANFD select CAN_BUS + +config CAN_FLEXCAN + bool + depends on IMX + select CAN_BUS diff --git a/hw/net/can/flexcan.c b/hw/net/can/flexcan.c new file mode 100644 index 0000000000..1ea459d9f6 --- /dev/null +++ b/hw/net/can/flexcan.c @@ -0,0 +1,1396 @@ +/* + * QEMU model of the NXP FLEXCAN device. + * + * This implementation is based on the following reference manual: + * i.MX 6Dual/6Quad Applications Processor Reference Manual + * Document Number: IMX6DQRM, Rev. 6, 05/2020 + * + * Copyright (c) 2025 Matyas Bobek <matyas.bobek@gmail.com> + * + * Based on CTU CAN FD emulation implemented by Jan Charvat. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/core/sysbus.h" +#include "qapi/error.h" +#include "hw/core/irq.h" +#include "migration/vmstate.h" +#include "net/can_emu.h" +#include "hw/core/qdev-properties.h" +#include "trace.h" + +#include "hw/net/flexcan.h" +#include "flexcan_regs.h" +#include "qemu/timer.h" + +/* + * Indicates MB w/ received frame has not been serviced yet + * This is an emulator-only flag in position of unused (reserved) bit + * of message buffer control register + */ +#define FLEXCAN_MB_CNT_NOT_SRV BIT(23) +/** + * if no MB is locked, FlexcanState.locked_mb + * is set to FLEXCAN_NO_MB_LOCKED + */ +#define FLEXCAN_NO_MB_LOCKED -1 +/** + * if no frame is waiting in the SMB, FlexcanState.smb_target_mbid + * is set to FLEXCAN_SMB_EMPTY + */ +#define FLEXCAN_SMB_EMPTY -1 +/** + * When the module is disabled or in freeze mode, + * the timer is not running. That is indicated by setting + * FlexcanState.timer_start to FLEXCAN_TIMER_STOPPED. + */ +#define FLEXCAN_TIMER_STOPPED -1 + +/* These constants are returned by flexcan_fifo_rx() and flexcan_mb_rx(), */ +enum FlexcanRx { +/* Retry the other receiving mechanism (ie. message bufer or mailbox). */ + FLEXCAN_RX_SEARCH_RETRY, +/* The frame was received and stored. */ + FLEXCAN_RX_SEARCH_ACCEPT, +/* The frame was filtered out and dropped. */ + FLEXCAN_RX_SEARCH_DROPPED, +}; + +/* + * These constants are returned by flexcan_mb_rx_check_mb(). + * See flexcan_mb_rx_check_mb() kerneldoc for details. + */ +enum FlexcanCheck { + FLEXCAN_CHECK_MB_NIL = 0, + FLEXCAN_CHECK_MB_MATCH = 3, + FLEXCAN_CHECK_MB_MATCH_NON_FREE = 1, + FLEXCAN_CHECK_MB_MATCH_LOCKED = 5, +}; + +static const FlexcanRegs flexcan_regs_write_mask = { + .mcr = 0xF6EB337F, + .ctrl = 0xFFFFFFFF, + .timer = 0xFFFFFFFF, + .tcr = 0xFFFFFFFF, + .rxmgmask = 0xFFFFFFFF, + .rx14mask = 0xFFFFFFFF, + .rx15mask = 0xFFFFFFFF, + .ecr = 0xFFFFFFFF, + .esr = 0xFFFFFFFF, + .imask2 = 0xFFFFFFFF, + .imask1 = 0xFFFFFFFF, + .iflag2 = 0, + .iflag1 = 0, + .ctrl2 = 0xFFFFFFFF, + .esr2 = 0, + .imeur = 0, + .lrfr = 0, + .crcr = 0, + .rxfgmask = 0xFFFFFFFF, + .rxfir = 0, + .cbt = 0, + ._reserved2 = 0, + .dbg1 = 0, + .dbg2 = 0, + .mbs = { [0 ... 63] = { + .can_ctrl = 0xFFFFFFFF & ~FLEXCAN_MB_CNT_NOT_SRV, + .can_id = 0xFFFFFFFF, + .data = { 0xFFFFFFFF, 0xFFFFFFFF }, + } }, + ._reserved4 = {0}, + .rximr = { [0 ... 63] = 0xFFFFFFFF }, + ._reserved5 = {0}, + .gfwr_mx6 = 0xFFFFFFFF, + ._reserved6 = {0}, + ._reserved8 = {0}, + .rx_smb0_raw = {0, 0, 0, 0}, + .rx_smb1 = {0, 0, 0, 0}, +}; +static const FlexcanRegs flexcan_regs_reset_mask = { + .mcr = 0x80000000, + .ctrl = 0xFFFFFFFF, + .timer = 0, + .tcr = 0, + .rxmgmask = 0xFFFFFFFF, + .rx14mask = 0xFFFFFFFF, + .rx15mask = 0xFFFFFFFF, + .ecr = 0, + .esr = 0, + .imask2 = 0, + .imask1 = 0, + .iflag2 = 0, + .iflag1 = 0, + .ctrl2 = 0xFFFFFFFF, + .esr2 = 0, + .imeur = 0, + .lrfr = 0, + .crcr = 0, + .rxfgmask = 0xFFFFFFFF, + .rxfir = 0xFFFFFFFF, + .cbt = 0, + ._reserved2 = 0, + .dbg1 = 0, + .dbg2 = 0, + .mb = {0xFFFFFFFF}, + ._reserved4 = {0}, + .rximr = {0xFFFFFFFF}, + ._reserved5 = {0}, + .gfwr_mx6 = 0, + ._reserved6 = {0}, + ._reserved8 = {0}, + .rx_smb0_raw = {0, 0, 0, 0}, + .rx_smb1 = {0, 0, 0, 0}, +}; + +/* length of buffer used to format register names in trace output */ +#define FLEXCAN_DBG_BUF_LEN 16 + +/** + * flexcan_dbg_mb_code_strs - Readable names for CODE field codes + * + * Readable names for possible values of CODE field in message buffer + * control word. + */ +static const char *flexcan_dbg_mb_code_strs[16] = { + "INACTIVE_RX", + "FULL", + "EMPTY", + "OVERRUN", + "INACTIVE_TX", + "RANSWER", + "DATA", + "TANSWER" +}; + +/** + * flexcan_dbg_mb_code() - Get the string representation of a mailbox code + * @mb_ctrl: The mailbox control register value + * @buf: The buffer to store the string representation + * + * Return: Either constant string or string formatted into @buf + */ +static const char *flexcan_dbg_mb_code(uint32_t mb_ctrl, char *buf) +{ + uint32_t code = mb_ctrl & FLEXCAN_MB_CODE_MASK; + uint32_t code_idx = code >> 24; + if (code == FLEXCAN_MB_CODE_TX_ABORT) { + return "ABORT"; + } else { + const char *code_str = flexcan_dbg_mb_code_strs[code_idx >> 1]; + if (code_idx & 1) { + g_snprintf(buf, FLEXCAN_DBG_BUF_LEN, "%s+BUSY", code_str); + return buf; + } + + return code_str; + } +} + +static const char *flexcan_dbg_reg_name_fixed(hwaddr addr) +{ + switch (addr) { + case offsetof(FlexcanRegs, mcr): + return "MCR"; + case offsetof(FlexcanRegs, ctrl): + return "CTRL"; + case offsetof(FlexcanRegs, timer): + return "TIMER"; + case offsetof(FlexcanRegs, esr): + return "ESR"; + case offsetof(FlexcanRegs, rxmgmask): + return "RXMGMASK"; + case offsetof(FlexcanRegs, rx14mask): + return "RX14MASK"; + case offsetof(FlexcanRegs, rx15mask): + return "RX15MASK"; + case offsetof(FlexcanRegs, rxfgmask): + return "RXFGMASK"; + case offsetof(FlexcanRegs, ecr): + return "ECR"; + case offsetof(FlexcanRegs, ctrl2): + return "CTRL2"; + case offsetof(FlexcanRegs, imask2): + return "IMASK2"; + case offsetof(FlexcanRegs, imask1): + return "IMASK1"; + case offsetof(FlexcanRegs, iflag2): + return "IFLAG2"; + case offsetof(FlexcanRegs, iflag1): + return "IFLAG1"; + } + return NULL; +} + +static inline void flexcan_trace_mem_op(FlexcanState *s, hwaddr addr, + uint32_t value, int size, bool is_wr) +{ + if (trace_event_get_state_backends(TRACE_FLEXCAN_MEM_OP)) { + const char *reg_name = "unknown"; + char reg_name_buf[FLEXCAN_DBG_BUF_LEN] = { 0 }; + const char *reg_name_fixed = flexcan_dbg_reg_name_fixed(addr); + const char *op_string = is_wr ? "write" : "read"; + + if (reg_name_fixed) { + reg_name = reg_name_fixed; + } else if (addr >= 0x80 && addr < 0x480) { + int mbidx = (addr - 0x80) / 16; + g_snprintf(reg_name_buf, sizeof(reg_name_buf), "MB%i", mbidx); + reg_name = reg_name_buf; + } else if (addr >= 0x880 && addr < 0x9e0) { + int id = (addr - 0x880) / 4; + g_snprintf(reg_name_buf, sizeof(reg_name_buf), "RXIMR%i", id); + reg_name = reg_name_buf; + } + + trace_flexcan_mem_op(DEVICE(s)->canonical_path, op_string, value, addr, + reg_name, size); + } +} + +static enum FlexcanRx flexcan_mb_rx(FlexcanState *s, + const qemu_can_frame *frame); +static void flexcan_mb_unlock(FlexcanState *s); + +/* ========== Mailbox Utils ========== */ + +/** + * flexcan_mailbox_count() - Get number of enabled mailboxes + * @s: FlexCAN device pointer + * + * Count is based on MCR[MAXMB] field. Note that some of those mailboxes + * might be part of queue or queue ID filters or ordinary message buffers. + */ +static inline int flexcan_enabled_mailbox_count(const FlexcanState *s) +{ + return MIN((s->regs.mcr & FLEXCAN_MCR_MAXMB(UINT32_MAX)) + 1, + FLEXCAN_MAILBOX_COUNT); +} + +/** + * flexcan_get_first_message_buffer() - Get pointer to first message buffer + * @s: FlexCAN device pointer + * + * In context of this function, message buffer means a mailbox which is not + * a queue element nor a queue filter. Note this function does not take + * MCR[MAXMB] into account, meaning that the returned mailbox + * might be disabled. + */ +static FlexcanRegsMessageBuffer *flexcan_get_first_message_buffer( + FlexcanState *s) +{ + if (s->regs.mcr & FLEXCAN_MCR_FEN) { + int rffn = (s->regs.ctrl2 & FLEXCAN_CTRL2_RFFN(UINT32_MAX)) >> 24; + return s->regs.mbs + 8 + 2 * rffn; + } + + return s->regs.mbs; +} + +/** + * flexcan_get_last_enabled_mailbox() - Get pointer to last enabled mailbox. + * @s: FlexCAN device pointer + * + * When used with flexcan_get_first_message_buffer(), all mailboxes *ptr in + * range `first_message_buffer() <= ptr <= last_enabled_mailbox` are valid + * message buffer mailboxes. + * + * Return: Last enabled mailbox in MCR[MAXMB] sense. The mailbox might be + * of any type. + */ +static inline FlexcanRegsMessageBuffer *flexcan_get_last_enabled_mailbox( + FlexcanState *s) +{ + return s->regs.mbs + flexcan_enabled_mailbox_count(s); +} + +/* ========== Free-running Timer ========== */ +static inline int64_t flexcan_get_time(void) +{ + return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +} + +/** + * flexcan_get_bitrate() - Calculate CAN bitrate (in Hz) + * @s: FlexCAN device pointer + * + * The bitrate is determined by FlexCAN configuration in CTRL1 register, + * and CCM co + */ +static uint32_t flexcan_get_bitrate(FlexcanState *s) +{ + uint32_t conf_presdiv = (s->regs.ctrl & FLEXCAN_CTRL_PRESDIV_MASK) >> 24; + uint32_t conf_pseg1 = (s->regs.ctrl & FLEXCAN_CTRL_PSEG1_MASK) >> 19; + uint32_t conf_pseg2 = (s->regs.ctrl & FLEXCAN_CTRL_PSEG2_MASK) >> 16; + uint32_t conf_propseg = s->regs.ctrl & FLEXCAN_CTRL_PROPSEG_MASK; + + /* N of time quanta for segments */ + uint32_t tseg1 = 2 + conf_pseg1 + conf_propseg; + uint32_t tseg2 = 1 + conf_pseg2; + uint32_t total_qpb = 1 + tseg1 + tseg2; + + uint32_t pe_freq, s_freq, bitrate; + + assert(s->ccm); + + /* s_freq: CAN clock from CCM divided by the prescaler */ + pe_freq = imx_ccm_get_clock_frequency(s->ccm, CLK_CAN); + s_freq = pe_freq / (1 + conf_presdiv); + bitrate = s_freq / total_qpb; + + trace_flexcan_get_bitrate(DEVICE(s)->canonical_path, pe_freq, + 1 + conf_presdiv, s_freq, tseg1, tseg2, total_qpb, + bitrate); + return bitrate; +} + +/** + * int128_mul_6464() - Multiply two 64-bit integers into a 128-bit one + */ +static Int128 int128_muls_6464(int64_t ai, int64_t bi) +{ + uint64_t l, h; + + muls64(&l, &h, ai, bi); + return int128_make128(l, h); +} + +/** + * flexcan_get_timestamp() - Get current value of the 16-bit free-running timer + * @s: FlexCAN device pointer + * @mk_unique: if true, make the timestamp unique by incrementing it if needed + */ +static uint32_t flexcan_get_timestamp(FlexcanState *s, bool mk_unique) +{ + const Int128 nanoseconds_in_second = int128_makes64((int64_t)1e9); + Int128 ncycles, cycles128; + int64_t current_time, elapsed_time_ns; + uint64_t cycles; + uint32_t rv, shift = 0; + + if (s->timer_start == FLEXCAN_TIMER_STOPPED) { + /* timer is not running, return last value */ + trace_flexcan_get_timestamp(DEVICE(s)->canonical_path, -1, 0, 0, 0, + s->regs.timer); + return s->regs.timer; + } + + current_time = flexcan_get_time(); + elapsed_time_ns = current_time - s->timer_start; + if (elapsed_time_ns < 0) { + trace_flexcan_timer_overflow(DEVICE(s)->canonical_path, current_time, + s->timer_start, elapsed_time_ns); + return 0xFFFF; + } + + ncycles = int128_muls_6464(s->timer_freq, elapsed_time_ns); + cycles128 = int128_divs(ncycles, nanoseconds_in_second); + /* 64 bits hold for over 50k years at 10MHz */ + cycles = int128_getlo(cycles128); + + if (mk_unique && cycles <= s->last_rx_timer_cycles) { + shift = 1; + cycles = s->last_rx_timer_cycles + shift; + } + + s->last_rx_timer_cycles = cycles; + rv = (uint32_t)cycles & 0xFFFF; + + trace_flexcan_get_timestamp(DEVICE(s)->canonical_path, + elapsed_time_ns / (uint32_t)1e6, + s->timer_freq, cycles, shift, rv); + return rv; +} + +/** + * flexcan_timer_start() - Start the free-running timer + * @s: FlexCAN device pointer + * + * This should be called when the module leaves freeze mode. + */ +static void flexcan_timer_start(FlexcanState *s) +{ + s->timer_freq = flexcan_get_bitrate(s); + s->timer_start = flexcan_get_time(); + s->last_rx_timer_cycles = 0; + + trace_flexcan_timer_start(DEVICE(s)->canonical_path, s->timer_freq, + s->regs.timer); +} + +/** + * flexcan_timer_stop() - Stop the free-running timer + * @s: FlexCAN device pointer + * + * This should be called when the module enters freeze mode. + * Stores the current timestamp in the TIMER register. + */ +static void flexcan_timer_stop(FlexcanState *s) +{ + s->regs.timer = flexcan_get_timestamp(s, false); + s->timer_start = FLEXCAN_TIMER_STOPPED; + + trace_flexcan_timer_stop(DEVICE(s)->canonical_path, s->timer_freq, + s->regs.timer); +} + +/* ========== IRQ handling ========== */ +/** + * flexcan_irq_update() - Update qemu_irq line based on interrupt registers + * @s: FlexCAN device pointer + */ +static void flexcan_irq_update(FlexcanState *s) +{ + uint32_t mb_irqs[2]; + int irq_pending; + /* these are all interrupt sources from FlexCAN */ + /* mailbox interrupt sources */ + mb_irqs[0] = s->regs.iflag1 & s->regs.imask1; + mb_irqs[1] = s->regs.iflag2 & s->regs.imask2; + + /** + * these interrupts aren't currently used and they can never be raised + * + * bool irq_wake_up = (s->regs.mcr & FLEXCAN_MCR_WAK_MSK) && + * (s->regs.ecr & FLEXCAN_ESR_WAK_INT); + * bool irq_bus_off = (s->regs.ctrl & FLEXCAN_CTRL_BOFF_MSK) && + * (s->regs.ecr & FLEXCAN_ESR_BOFF_INT); + * bool irq_error = (s->regs.ctrl & FLEXCAN_CTRL_ERR_MSK) && + * (s->regs.ecr & FLEXCAN_ESR_ERR_INT); + * bool irq_tx_warn = (s->regs.ctrl & FLEXCAN_CTRL_TWRN_MSK) && + * (s->regs.ecr & FLEXCAN_ESR_TWRN_INT); + * bool irq_rx_warn = (s->regs.ctrl & FLEXCAN_CTRL_RWRN_MSK) && + * (s->regs.ecr & FLEXCAN_ESR_RWRN_INT); + */ + + irq_pending = (mb_irqs[0] || mb_irqs[1]) ? 1 : 0; + trace_flexcan_irq_update(DEVICE(s)->canonical_path, mb_irqs[0], mb_irqs[1], + irq_pending); + + qemu_set_irq(s->irq, irq_pending); +} + +/** + * flexcan_irq_iflag_set() - Set IFLAG bit corresponding to MB mbidx + * @s: FlexCAN device pointer + * @mbidx: mailbox index + */ +static void flexcan_irq_iflag_set(FlexcanState *s, int mbidx) +{ + if (mbidx < 32) { + s->regs.iflag1 |= BIT(mbidx); + } else { + s->regs.iflag2 |= BIT(mbidx - 32); + } +} + +/** + * flexcan_irq_iflag_clear() - Clear IFLAG bit corresponding to MB mbidx + * @s: FlexCAN device pointer + * @mbidx: mailbox index + */ +static void flexcan_irq_iflag_clear(FlexcanState *s, int mbidx) +{ + if (mbidx < 32) { + s->regs.iflag1 &= ~BIT(mbidx); + } else { + s->regs.iflag2 &= ~BIT(mbidx - 32); + } +} + +/* ========== RESET ========== */ +static void flexcan_reset_local_state(FlexcanState *s) +{ + uint32_t *reset_mask = (uint32_t *)&flexcan_regs_reset_mask; + for (int i = 0; i < (sizeof(FlexcanRegs) / 4); i++) { + s->regs_raw[i] &= reset_mask[i]; + } + + s->regs.mcr |= 0x5980000F; + s->locked_mbidx = FLEXCAN_NO_MB_LOCKED; + s->smb_target_mbidx = FLEXCAN_SMB_EMPTY; + s->timer_start = FLEXCAN_TIMER_STOPPED; + + trace_flexcan_reset(DEVICE(s)->canonical_path); +} + +static void flexcan_reset_enter(Object *obj, ResetType type) +{ + FlexcanState *s = CAN_FLEXCAN(obj); + + memset(&s->regs, 0, sizeof(s->regs)); + flexcan_reset_local_state(s); +} + +static void flexcan_reset_hold(Object *obj, ResetType type) +{ + FlexcanState *s = CAN_FLEXCAN(obj); + + flexcan_irq_update(s); +} + + +/* ========== Operation mode control ========== */ +/** + * flexcan_update_esr() - Update ESR based on mode and CAN bus connection state + * @s: FlexCAN device pointer + */ +static void flexcan_update_esr(FlexcanState *s) +{ + bool is_running = (s->regs.mcr & FLEXCAN_MCR_NOT_RDY) == 0; + /* potentially, there could be other influences on ESR[SYNCH] */ + + if (is_running && s->canbus) { + s->regs.esr |= FLEXCAN_ESR_SYNCH | FLEXCAN_ESR_IDLE; + } else { + s->regs.esr &= ~(FLEXCAN_ESR_SYNCH | FLEXCAN_ESR_IDLE); + } +} + +/** + * flexcan_update_esr() - Process MCR write + * @s: FlexCAN device pointer + * @pv: previously set MCR value + * + * This function expects the new MCR value to be already written in s->regs.mcr. + */ +static void flexcan_set_mcr(FlexcanState *s, const uint32_t pv) +{ + uint32_t cv = s->regs.mcr; + + /* -- module disable mode -- */ + if (!(pv & FLEXCAN_MCR_MDIS) && (cv & FLEXCAN_MCR_MDIS)) { + /* transition to Module Disable mode */ + cv |= FLEXCAN_MCR_LPM_ACK; + } else if ((pv & FLEXCAN_MCR_MDIS) && !(cv & FLEXCAN_MCR_MDIS)) { + /* transition from Module Disable mode */ + cv &= ~FLEXCAN_MCR_LPM_ACK; + } + + /* -- soft reset -- */ + if (!(cv & FLEXCAN_MCR_LPM_ACK) && (cv & FLEXCAN_MCR_SOFTRST)) { + if (s->regs.mcr & FLEXCAN_MCR_LPM_ACK) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid soft reset request in low-power mode", + DEVICE(s)->canonical_path); + } + + flexcan_reset_local_state(s); + cv = s->regs.mcr; + } + + /* -- freeze mode -- */ + if (!(cv & FLEXCAN_MCR_LPM_ACK) && + (cv & FLEXCAN_MCR_FRZ) && + (cv & FLEXCAN_MCR_HALT)) { + cv |= FLEXCAN_MCR_FRZ_ACK; + } else { + cv &= ~FLEXCAN_MCR_FRZ_ACK; + } + + /* -- fifo mode -- */ + if ( + ((pv & FLEXCAN_MCR_FEN) && !(cv & FLEXCAN_MCR_FEN)) || + (!(pv & FLEXCAN_MCR_FEN) && (cv & FLEXCAN_MCR_FEN)) + ) { + /* clear iflags used by fifo */ + s->regs.iflag1 &= ~( + FLEXCAN_IFLAG_RX_FIFO_AVAILABLE | + FLEXCAN_IFLAG_RX_FIFO_OVERFLOW | + FLEXCAN_IFLAG_RX_FIFO_WARN + ); + } + if (!(pv & FLEXCAN_MCR_FEN) && (cv & FLEXCAN_MCR_FEN)) { + /* zero out fifo region, we rely on zeroed can_ctrl for empty slots */ + memset(s->regs.mbs, 0, + FLEXCAN_FIFO_DEPTH * sizeof(FlexcanRegsMessageBuffer)); + } + + /* + * assert NOT_RDY bit if in disable, + * stop (not implemented) or freeze mode + */ + if ((cv & FLEXCAN_MCR_LPM_ACK) || (cv & FLEXCAN_MCR_FRZ_ACK)) { + cv |= FLEXCAN_MCR_NOT_RDY; + } else { + cv &= ~FLEXCAN_MCR_NOT_RDY; + } + + if ((pv & FLEXCAN_MCR_NOT_RDY) && !(cv & FLEXCAN_MCR_NOT_RDY)) { + /* module went up, start the timer */ + flexcan_timer_start(s); + } else if (!(pv & FLEXCAN_MCR_NOT_RDY) && (cv & FLEXCAN_MCR_NOT_RDY)) { + /* module went down, store the current timer value */ + flexcan_timer_stop(s); + } + + s->regs.mcr = cv; + flexcan_update_esr(s); + trace_flexcan_set_mcr( + DEVICE(s)->canonical_path, + cv & FLEXCAN_MCR_LPM_ACK ? "DISABLED" : "ENABLED", + (cv & FLEXCAN_MCR_FRZ_ACK || cv & FLEXCAN_MCR_LPM_ACK) ? + "FROZEN" : "RUNNING", + cv & FLEXCAN_MCR_FEN ? "FIFO" : "MAILBOX", + cv & FLEXCAN_MCR_NOT_RDY ? "NOT_RDY" : "RDY", + s->regs.esr & FLEXCAN_ESR_SYNCH ? "SYNC" : "NOSYNC" + ); +} + +/* ========== TX ========== */ +static void flexcan_transmit(FlexcanState *s, int mbidx) +{ + FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbidx]; + qemu_can_frame frame = { + .flags = 0, + }; + uint32_t *frame_data = (uint32_t *)&frame.data; + uint32_t timestamp = flexcan_get_timestamp(s, true); + + if ((s->regs.ctrl & FLEXCAN_CTRL_LOM) || + (s->regs.mcr & FLEXCAN_MCR_NOT_RDY)) { + /* no transmiting in listen-only, freeze or low-power mode */ + return; + } + + if (mb->can_ctrl & FLEXCAN_MB_CNT_IDE) { + /* 29b ID stored in bits [0, 29) */ + uint32_t id = mb->can_id & 0x1FFFFFFF; + frame.can_id = id | QEMU_CAN_EFF_FLAG; + } else { + /* 11b ID stored in bits [18, 29) */ + uint32_t id = (mb->can_id & (0x7FF << 18)) >> 18; + frame.can_id = id; + } + + frame.can_dlc = (mb->can_ctrl & (0xF << 16)) >> 16; + + for (int i = 0; i < 2; i++) { + stl_be_p(&frame_data[i], mb->data[i]); + } + + if (!(s->regs.mcr & FLEXCAN_MCR_SRX_DIS)) { + /* self-reception */ + flexcan_mb_rx(s, &frame); + } + if (!(s->regs.ctrl & FLEXCAN_CTRL_LPB)) { + /* send to bus if not in loopback mode */ + if (s->canbus) { + can_bus_client_send(&s->bus_client, &frame, 1); + } else { + /* todo: raise error (no ack) */ + } + } + + mb->can_ctrl &= ~(FLEXCAN_MB_CODE_MASK | FLEXCAN_MB_CNT_TIMESTAMP_MASK); + mb->can_ctrl |= FLEXCAN_MB_CODE_TX_INACTIVE | + FLEXCAN_MB_CNT_TIMESTAMP(timestamp); + + /* todo: compute the CRC */ + s->regs.crcr = FLEXCAN_CRCR_TXCRC(0) | FLEXCAN_CRCR_MBCRC(mbidx); + + flexcan_irq_iflag_set(s, mbidx); +} + +static void flexcan_mb_write(FlexcanState *s, int mbid) +{ + FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbid]; + + bool is_mailbox = (mb <= flexcan_get_last_enabled_mailbox(s)) && + (mb >= flexcan_get_first_message_buffer(s)); + + if (trace_event_get_state_backends(TRACE_FLEXCAN_MB_WRITE)) { + char code_str_buf[FLEXCAN_DBG_BUF_LEN] = { 0 }; + const char *code_str = flexcan_dbg_mb_code(mb->can_ctrl, code_str_buf); + trace_flexcan_mb_write(DEVICE(s)->canonical_path, mbid, code_str, + is_mailbox, mb->can_ctrl, mb->can_id); + } + + if (!is_mailbox) { + /** + * Disabled mailbox or mailbox in region of queue filters + * was updated. Either way there is nothing to do. + */ + return; + } + + /* any write to message buffer clears the not_serviced flag */ + mb->can_ctrl &= ~FLEXCAN_MB_CNT_NOT_SRV; + + /** + * todo: search for active tx mbs on transition from freeze/disable mode + */ + switch (mb->can_ctrl & FLEXCAN_MB_CODE_MASK) { + case FLEXCAN_MB_CODE_TX_INACTIVE: + QEMU_FALLTHROUGH; + case FLEXCAN_MB_CODE_RX_INACTIVE: + QEMU_FALLTHROUGH; + case FLEXCAN_MB_CODE_RX_EMPTY: + QEMU_FALLTHROUGH; + case FLEXCAN_MB_CODE_RX_FULL: + QEMU_FALLTHROUGH; + case FLEXCAN_MB_CODE_RX_RANSWER: + break; + + case FLEXCAN_MB_CODE_TX_DATA: + flexcan_transmit(s, mbid); + break; + case FLEXCAN_MB_CODE_TX_ABORT: + /* + * as transmission is instant, it can never be aborted + * we need to set CODE in C/S back to the previous code + */ + mb->can_ctrl &= ~FLEXCAN_MB_CODE(1); + break; + case FLEXCAN_MB_CODE_TX_TANSWER: + break; + default: + /* prevent setting the busy bit */ + mb->can_ctrl &= ~FLEXCAN_MB_CODE_RX_BUSY_BIT; + break; + } + +} + +/* ========== RX ========== */ +static void flexcan_mb_move_in(FlexcanState *s, const qemu_can_frame *frame, + FlexcanRegsMessageBuffer *target_mb) +{ + uint32_t frame_len = frame->can_dlc; + uint32_t *frame_data = (uint32_t *)&frame->data; + int timestamp = flexcan_get_timestamp(s, true); + uint32_t new_code = 0; + + memset(target_mb, 0, sizeof(FlexcanRegsMessageBuffer)); + + if (frame_len > 8) { + frame_len = 8; + } + for (int i = 0; i < 2; i++) { + target_mb->data[i] = ldl_be_p(&frame_data[i]); + } + + switch (target_mb->can_ctrl & FLEXCAN_MB_CODE_MASK) { + case FLEXCAN_MB_CODE_RX_FULL: + case FLEXCAN_MB_CODE_RX_OVERRUN: + if (target_mb->can_ctrl & FLEXCAN_MB_CNT_NOT_SRV) { + new_code = FLEXCAN_MB_CODE_RX_OVERRUN; + } else { + new_code = FLEXCAN_MB_CODE_RX_FULL; + } + break; + case FLEXCAN_MB_CODE_RX_RANSWER: + assert(s->regs.ctrl2 & FLEXCAN_CTRL2_RRS); + new_code = FLEXCAN_MB_CODE_TX_TANSWER; + break; + default: + new_code = FLEXCAN_MB_CODE_RX_FULL; + } + + target_mb->can_ctrl = new_code + | FLEXCAN_MB_CNT_TIMESTAMP(timestamp) + | FLEXCAN_MB_CNT_LENGTH(frame_len) + | FLEXCAN_MB_CNT_NOT_SRV + | FLEXCAN_MB_CNT_SRR; /* always set for received frames */ + if (frame->can_id & QEMU_CAN_RTR_FLAG) { + target_mb->can_ctrl |= FLEXCAN_MB_CNT_RTR; + } + + if (frame->can_id & QEMU_CAN_EFF_FLAG) { + target_mb->can_ctrl |= FLEXCAN_MB_CNT_IDE; + target_mb->can_id |= frame->can_id & QEMU_CAN_EFF_MASK; + } else { + target_mb->can_id |= (frame->can_id & QEMU_CAN_SFF_MASK) << 18; + } +} +static void flexcan_mb_lock(FlexcanState *s, int mbidx) +{ + FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbidx]; + if ((mb > flexcan_get_last_enabled_mailbox(s)) || + (mb < flexcan_get_first_message_buffer(s))) { + return; + } + switch (mb->can_ctrl & FLEXCAN_MB_CODE_MASK) { + case FLEXCAN_MB_CODE_RX_FULL: + QEMU_FALLTHROUGH; + case FLEXCAN_MB_CODE_RX_OVERRUN: + QEMU_FALLTHROUGH; + case FLEXCAN_MB_CODE_RX_RANSWER: + /* continue */ + trace_flexcan_mb_lock(DEVICE(s)->canonical_path, mbidx, 1); + break; + default: + trace_flexcan_mb_lock(DEVICE(s)->canonical_path, mbidx, 0); + return; + } + + s->locked_mbidx = mbidx; +} + +static void flexcan_mb_unlock(FlexcanState *s) +{ + int locked_mbidx = s->locked_mbidx; + bool has_pending_frame = locked_mbidx == s->smb_target_mbidx; + + if (s->locked_mbidx == FLEXCAN_NO_MB_LOCKED) { + return; + } + + assert(locked_mbidx >= 0 && locked_mbidx < FLEXCAN_MAILBOX_COUNT); + FlexcanRegsMessageBuffer *locked_mb = &s->regs.mbs[locked_mbidx]; + s->locked_mbidx = FLEXCAN_NO_MB_LOCKED; + + if (locked_mb >= flexcan_get_first_message_buffer(s) && + locked_mb <= flexcan_get_last_enabled_mailbox(s) + ) { + /* mark the message buffer as serviced */ + locked_mb->can_ctrl &= ~FLEXCAN_MB_CNT_NOT_SRV; + } + + /* try move in from SMB */ + trace_flexcan_mb_unlock(DEVICE(s)->canonical_path, locked_mbidx, + has_pending_frame ? " PENDING FRAME IN SMB" : ""); + + /* todo: in low-power modes, this should be postponed until exit */ + if (has_pending_frame) { + FlexcanRegsMessageBuffer *target_mb = &s->regs.mbs[locked_mbidx]; + memcpy(target_mb, &s->regs.rx_smb0, sizeof(FlexcanRegsMessageBuffer)); + + memset(&s->regs.rx_smb0, 0, sizeof(FlexcanRegsMessageBuffer)); + s->locked_mbidx = FLEXCAN_SMB_EMPTY; + + flexcan_irq_iflag_set(s, locked_mbidx); + } +} + +static bool flexcan_can_receive(CanBusClientState *client) +{ + FlexcanState *s = container_of(client, FlexcanState, bus_client); + return !(s->regs.mcr & FLEXCAN_MCR_NOT_RDY); +} + +/* --------- RX FIFO ---------- */ + +/** + * flexcan_fifo_pop() - Pop message from FIFO and update IRQs + * @s: FlexCAN device pointer + * + * Does not require the queue to be non-empty. + */ +static void flexcan_fifo_pop(FlexcanState *s) +{ + if (s->regs.fifo.mb_back.can_ctrl != 0) { + /* move queue elements forward */ + memmove(&s->regs.fifo.mb_back, &s->regs.fifo.mbs_queue[0], + sizeof(s->regs.fifo.mbs_queue)); + + /* clear the first-in slot */ + memset(&s->regs.mbs[FLEXCAN_FIFO_DEPTH - 1], 0, + sizeof(FlexcanRegsMessageBuffer)); + + trace_flexcan_fifo_pop(DEVICE(s)->canonical_path, 1, + s->regs.fifo.mb_back.can_ctrl != 0); + } else { + trace_flexcan_fifo_pop(DEVICE(s)->canonical_path, 0, 0); + } + + if (s->regs.fifo.mb_back.can_ctrl != 0) { + flexcan_irq_iflag_set(s, I_FIFO_AVAILABLE); + } else { + flexcan_irq_iflag_clear(s, I_FIFO_AVAILABLE); + } +} + +/** + * flexcan_fifo_find_free_slot() - Find the first free slot in the FIFO + * @s: FlexCAN device pointer + * + * Return: Pointer to the first free slot in the FIFO, + * or NULL if the queue is full. + */ +static FlexcanRegsMessageBuffer *flexcan_fifo_find_free_slot(FlexcanState *s) +{ + for (int i = 0; i < FLEXCAN_FIFO_DEPTH; i++) { + FlexcanRegsMessageBuffer *mb = &s->regs.mbs[i]; + if (mb->can_ctrl == 0) { + return mb; + } + } + return NULL; +} + +/** + * flexcan_fifo_push() - Update FIFO IRQs after frame move-in + * @s: FlexCAN device pointer + * @slot: Target FIFO slot + * + * The usage is as follows: + * 1. Get free slot pointer using flexcan_fifo_find_free_slot() + * 2. Move the frame in if not NULL + * 3. Call flexcan_fifo_push() regardless of the NULL pointer + */ +static void flexcan_fifo_push(FlexcanState *s, FlexcanRegsMessageBuffer *slot) +{ + if (slot) { + int n_occupied = slot - s->regs.mbs; + if (n_occupied == 4) { /* 4 means the 5th slot was filled in */ + /* + * fifo occupancy increased from 4 to 5, + * raising FIFO_WARN interrupt + */ + flexcan_irq_iflag_set(s, I_FIFO_WARN); + } + flexcan_irq_iflag_set(s, I_FIFO_AVAILABLE); + + trace_flexcan_fifo_push(DEVICE(s)->canonical_path, n_occupied); + } else { + flexcan_irq_iflag_set(s, I_FIFO_OVERFLOW); + + trace_flexcan_fifo_push(DEVICE(s)->canonical_path, -1); + } +} + +static enum FlexcanRx flexcan_fifo_rx(FlexcanState *s, + const qemu_can_frame *buf) +{ + /* todo: filtering. return FLEXCAN_FIFO_RX_RETRY if filtered out */ + if ((s->regs.mcr & FLEXCAN_MCR_IDAM_MASK) == FLEXCAN_MCR_IDAM_D) { + /* all frames rejected */ + return FLEXCAN_RX_SEARCH_RETRY; + } else { + /* push message to queue if not full */ + FlexcanRegsMessageBuffer *slot = flexcan_fifo_find_free_slot(s); + if (slot) { + flexcan_mb_move_in(s, buf, slot); + } + flexcan_fifo_push(s, slot); + + return slot ? FLEXCAN_RX_SEARCH_ACCEPT : FLEXCAN_RX_SEARCH_DROPPED; + } +} + +/* --------- RX message buffer ---------- */ + +/** + * flexcan_mb_rx_check_mb() - Check if a mb matches a received frame + * @s: FlexCAN device pointer + * @buf: Frame to be received from CAN subsystem + * @mbid: Target mailbox index. The mailbox must be a valid message buffer. + * + * Return: FLEXCAN_CHECK_MB_NIL if the message buffer does not match. + * FLEXCAN_CHECK_MB_MATCH if the message buffer matches the received + * frame and is free-to-receive, + * FLEXCAN_CHECK_MB_MATCH_LOCKED if the message buffer matches, + * but is locked, + * FLEXCAN_CHECK_MB_MATCH_NON_FREE if the message buffer matches, + * but is not free-to-receive + * for some other reason. + */ +static enum FlexcanCheck flexcan_mb_rx_check_mb(FlexcanState *s, + const qemu_can_frame *buf, + int mbid) +{ + FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbid]; + const bool is_rtr = !!(buf->can_id & QEMU_CAN_RTR_FLAG); + const bool is_serviced = !(mb->can_ctrl & FLEXCAN_MB_CNT_NOT_SRV); + const bool is_locked = s->locked_mbidx == mbid; + + bool is_free_to_receive = false; + bool is_matched = false; + + switch (mb->can_ctrl & FLEXCAN_MB_CODE_MASK) { + case FLEXCAN_MB_CODE_RX_RANSWER: + if (is_rtr && !(s->regs.ctrl2 & FLEXCAN_CTRL2_RRS)) { + /* todo: do the actual matching/filtering and RTR answer */ + is_matched = true; + } + break; + case FLEXCAN_MB_CODE_RX_FULL: + QEMU_FALLTHROUGH; + case FLEXCAN_MB_CODE_RX_OVERRUN: + is_free_to_receive = is_serviced; + /* todo: do the actual matching/filtering */ + is_matched = true; + break; + case FLEXCAN_MB_CODE_RX_EMPTY: + is_free_to_receive = true; + /* todo: do the actual matching/filtering */ + is_matched = true; + break; + default: + break; + } + + if (trace_event_get_state_backends(TRACE_FLEXCAN_MB_RX_CHECK_MB)) { + char code_str_buf[FLEXCAN_DBG_BUF_LEN] = { 0 }; + const char *code_str = flexcan_dbg_mb_code(mb->can_ctrl, code_str_buf); + trace_flexcan_mb_rx_check_mb(DEVICE(s)->canonical_path, mbid, code_str, + is_matched, is_free_to_receive, + is_serviced, is_locked); + } + + if (!is_matched) { + return FLEXCAN_CHECK_MB_NIL; + } + + if (is_locked) { + return FLEXCAN_CHECK_MB_MATCH_LOCKED; + } + + if (is_free_to_receive) { + return FLEXCAN_CHECK_MB_MATCH; + } + + return FLEXCAN_CHECK_MB_MATCH_NON_FREE; +} + +static enum FlexcanRx flexcan_mb_rx(FlexcanState *s, const qemu_can_frame *buf) +{ + int last_not_free_to_receive_mbid = -1; + bool last_not_free_to_receive_locked = false; + + FlexcanRegsMessageBuffer *first_mb = flexcan_get_first_message_buffer(s); + FlexcanRegsMessageBuffer *last_mb = flexcan_get_last_enabled_mailbox(s); + + for (FlexcanRegsMessageBuffer *mb = first_mb; + mb <= last_mb; mb++) { + int mbid = mb - s->regs.mbs; + enum FlexcanCheck r = flexcan_mb_rx_check_mb(s, buf, mbid); + if (r == FLEXCAN_CHECK_MB_MATCH) { + flexcan_mb_move_in(s, buf, mb); + flexcan_irq_iflag_set(s, mbid); + return FLEXCAN_RX_SEARCH_ACCEPT; + } + + if (r == FLEXCAN_CHECK_MB_MATCH_NON_FREE) { + last_not_free_to_receive_mbid = mbid; + last_not_free_to_receive_locked = false; + } else if (r == FLEXCAN_CHECK_MB_MATCH_LOCKED) { + /* + * message buffer is locked, + * we can move in the message after it's unlocked + */ + last_not_free_to_receive_mbid = mbid; + last_not_free_to_receive_locked = true; + } + } + + if (last_not_free_to_receive_mbid >= -1) { + if (last_not_free_to_receive_locked) { + /* + * copy to temporary mailbox (SMB) + * it will be moved in when the mailbox is unlocked + */ + s->regs.rx_smb0.can_ctrl = + s->regs.mbs[last_not_free_to_receive_mbid].can_id; + flexcan_mb_move_in(s, buf, &s->regs.rx_smb0); + s->smb_target_mbidx = last_not_free_to_receive_mbid; + return FLEXCAN_RX_SEARCH_ACCEPT; + } + + if (s->regs.mcr & FLEXCAN_MCR_IRMQ) { + flexcan_mb_move_in(s, buf, + &s->regs.mbs[last_not_free_to_receive_mbid]); + flexcan_irq_iflag_set(s, last_not_free_to_receive_mbid); + return FLEXCAN_RX_SEARCH_ACCEPT; + } + } + + return FLEXCAN_RX_SEARCH_RETRY; +} + +static ssize_t flexcan_receive(CanBusClientState *client, + const qemu_can_frame *frames, size_t frames_cnt) +{ + FlexcanState *s = container_of(client, FlexcanState, bus_client); + trace_flexcan_receive(DEVICE(s)->canonical_path, frames_cnt); + + if (frames_cnt == 0) { + return 0; + } + + /* clear the SMB, as it would be overriden in hardware */ + memset(&s->regs.rx_smb0, 0, sizeof(FlexcanRegsMessageBuffer)); + s->smb_target_mbidx = FLEXCAN_SMB_EMPTY; + + for (size_t i = 0; i < frames_cnt; i++) { + int r; + const qemu_can_frame *frame = &frames[i]; + if (frame->can_id & QEMU_CAN_ERR_FLAG) { + /* todo: error frame handling */ + continue; + } + if (frame->flags & QEMU_CAN_FRMF_TYPE_FD) { + /* CAN FD supported only in later FlexCAN version */ + continue; + } + + /* todo: this order logic is not complete and needs further work */ + if (s->regs.mcr & FLEXCAN_MCR_FEN && + s->regs.ctrl2 & FLEXCAN_CTRL2_MRP) { + r = flexcan_mb_rx(s, frame); + if (r == FLEXCAN_RX_SEARCH_RETRY) { + flexcan_fifo_rx(s, frame); + } + } else if (s->regs.mcr & FLEXCAN_MCR_FEN) { + r = flexcan_fifo_rx(s, frame); + if (r == FLEXCAN_RX_SEARCH_RETRY) { + flexcan_mb_rx(s, frame); + } + } else { + flexcan_mb_rx(s, frame); + } + } + + flexcan_irq_update(s); + return 1; +} + +/* ========== I/O handling ========== */ +static void flexcan_mem_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + FlexcanState *s = opaque; + uint32_t write_mask = ((const uint32_t *) + &flexcan_regs_write_mask)[addr / 4]; + uint32_t old_value = s->regs_raw[addr / 4]; + + /* + * 0 for bits that can "only be written in Freeze mode as it is blocked + * by hardware in other modes" + */ + const uint32_t freeze_mask_mcr = 0xDF54CC80; + const uint32_t freeze_mask_ctrl1 = 0x0000E740; + + flexcan_trace_mem_op(s, addr, val, size, true); + switch (addr) { + case offsetof(FlexcanRegs, mcr): + if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) { + write_mask &= freeze_mask_mcr; + } + s->regs.mcr = (val & write_mask) | (old_value & ~write_mask); + flexcan_set_mcr(s, old_value); + break; + case offsetof(FlexcanRegs, ctrl): + if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) { + write_mask &= freeze_mask_ctrl1; + } + s->regs.ctrl = (val & write_mask) | (old_value & ~write_mask); + break; + case offsetof(FlexcanRegs, iflag1): + s->regs.iflag1 &= ~val; + if ((s->regs.mcr & FLEXCAN_MCR_FEN) && + (val & FLEXCAN_IFLAG_RX_FIFO_AVAILABLE)) { + flexcan_fifo_pop(s); + } + break; + case offsetof(FlexcanRegs, iflag2): + s->regs.iflag2 &= ~val; + break; + case offsetof(FlexcanRegs, ctrl2): + QEMU_FALLTHROUGH; + case offsetof(FlexcanRegs, ecr): + QEMU_FALLTHROUGH; + case offsetof(FlexcanRegs, rxmgmask): + QEMU_FALLTHROUGH; + case offsetof(FlexcanRegs, rx14mask): + QEMU_FALLTHROUGH; + case offsetof(FlexcanRegs, rx15mask): + QEMU_FALLTHROUGH; + case offsetof(FlexcanRegs, rxfgmask): + QEMU_FALLTHROUGH; + case offsetof(FlexcanRegs, rximr[0]) ... offsetof(FlexcanRegs, rximr[63]): + /* these registers can only be written in freeze mode */ + if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) { + break; + } + QEMU_FALLTHROUGH; + default: + s->regs_raw[addr / 4] = (val & write_mask) | (old_value & ~write_mask); + + if (addr >= offsetof(FlexcanRegs, mb) && + addr < offsetof(FlexcanRegs, _reserved4)) { + /* access to mailbox */ + int mbid = (addr - offsetof(FlexcanRegs, mb)) / + sizeof(FlexcanRegsMessageBuffer); + + if (s->locked_mbidx == mbid) { + flexcan_mb_unlock(s); + } + + /* check for invalid writes into FIFO region */ + if (s->regs.mcr & FLEXCAN_MCR_FEN && mbid < FLEXCAN_FIFO_DEPTH) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Invalid write to Rx-FIFO structure", + DEVICE(s)->canonical_path); + return; + } + + /* run mailbox processing function on write to control word */ + if ((addr & 0xF) == 0) { + flexcan_mb_write(s, mbid); + } + } + break; + } + + flexcan_irq_update(s); +} + +static uint64_t flexcan_mem_read(void *opqaue, hwaddr addr, unsigned size) +{ + FlexcanState *s = opqaue; + uint32_t rv = s->regs_raw[addr >> 2]; + + if (addr >= offsetof(FlexcanRegs, mb) && + addr < offsetof(FlexcanRegs, _reserved4)) { + /* reading from mailbox */ + hwaddr offset = addr - offsetof(FlexcanRegs, mb); + int mbid = offset / sizeof(FlexcanRegsMessageBuffer); + + if (addr % 16 == 0 && s->locked_mbidx != mbid) { + /* reading control word locks the mailbox */ + flexcan_mb_unlock(s); + flexcan_mb_lock(s, mbid); + flexcan_irq_update(s); + rv = s->regs.mbs[mbid].can_ctrl & ~FLEXCAN_MB_CNT_NOT_SRV; + } + } else if (addr == offsetof(FlexcanRegs, timer)) { + flexcan_mb_unlock(s); + flexcan_irq_update(s); + rv = flexcan_get_timestamp(s, false); + } + + flexcan_trace_mem_op(s, addr, rv, size, false); + return rv; +} + +static bool flexcan_mem_accepts(void *opaque, hwaddr addr, + unsigned size, bool is_write, + MemTxAttrs attrs) +{ + FlexcanState *s = opaque; + + if ((s->regs.ctrl2 & FLEXCAN_CTRL2_WRMFRZ) && + (s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) { + /* unrestricted access to FlexCAN memory in freeze mode */ + return true; + } else if (attrs.user && (s->regs.mcr & FLEXCAN_MCR_SUPV)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Invalid user-mode access to restricted register", + DEVICE(s)->canonical_path); + return false; + } else if (attrs.user && is_write && addr < 4) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Invalid user-mode access to MCR", + DEVICE(s)->canonical_path); + return false; + } + + return true; +} + +static const struct MemoryRegionOps flexcan_ops = { + .read = flexcan_mem_read, + .write = flexcan_mem_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + .accepts = flexcan_mem_accepts + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false + }, +}; + +static CanBusClientInfo flexcan_bus_client_info = { + .can_receive = flexcan_can_receive, + .receive = flexcan_receive, +}; + +static int flexcan_connect_to_bus(FlexcanState *s, CanBusState *bus) +{ + s->bus_client.info = &flexcan_bus_client_info; + + if (can_bus_insert_client(bus, &s->bus_client) < 0) { + return -1; + } + return 0; +} + +static void flexcan_init(Object *obj) +{ + FlexcanState *s = CAN_FLEXCAN(obj); + + memory_region_init_io( + &s->iomem, obj, &flexcan_ops, s, TYPE_CAN_FLEXCAN, + offsetof(FlexcanRegs, _reserved6) + ); +} + +static void flexcan_realize(DeviceState *dev, Error **errp) +{ + FlexcanState *s = CAN_FLEXCAN(dev); + + if (s->canbus) { + if (flexcan_connect_to_bus(s, s->canbus) < 0) { + error_setg(errp, "%s: flexcan_connect_to_bus failed", + dev->canonical_path); + return; + } + } + + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); + sysbus_init_irq(SYS_BUS_DEVICE(SYS_BUS_DEVICE(dev)), &s->irq); +} + +static const VMStateDescription vmstate_can = { + .name = TYPE_CAN_FLEXCAN, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_INT64(timer_start, FlexcanState), + VMSTATE_UINT32_ARRAY(regs_raw, FlexcanState, sizeof(FlexcanRegs) / 4), + VMSTATE_INT32(locked_mbidx, FlexcanState), + VMSTATE_INT32(smb_target_mbidx, FlexcanState), + VMSTATE_END_OF_LIST(), + }, +}; + +static const Property flexcan_properties[] = { + DEFINE_PROP_LINK("canbus", FlexcanState, canbus, TYPE_CAN_BUS, + CanBusState *), +}; + +static void flexcan_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + rc->phases.enter = flexcan_reset_enter; + rc->phases.hold = flexcan_reset_hold; + dc->realize = flexcan_realize; + device_class_set_props(dc, flexcan_properties); + dc->vmsd = &vmstate_can; + dc->desc = "i.MX FLEXCAN Controller"; +} + +static const TypeInfo flexcan_info = { + .name = TYPE_CAN_FLEXCAN, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(FlexcanState), + .class_init = flexcan_class_init, + .instance_init = flexcan_init, +}; + +static void can_register_types(void) +{ + type_register_static(&flexcan_info); +} +type_init(can_register_types) diff --git a/hw/net/can/flexcan_regs.h b/hw/net/can/flexcan_regs.h new file mode 100644 index 0000000000..c18bc4736e --- /dev/null +++ b/hw/net/can/flexcan_regs.h @@ -0,0 +1,197 @@ +/* + * Field bitmasks and register structs definitions for FlexCAN + * + * This implementation is based on the following datasheet: + * i.MX 6Dual/6Quad Applications Processor Reference Manual + * Document Number: IMX6DQRM, Rev. 6, 05/2020 + * + * Copyright (c) 2025 Matyas Bobek <matyas.bobek@gmail.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "qemu/bitops.h" + +#ifndef HW_CAN_FLEXCAN_REGS_H +#define HW_CAN_FLEXCAN_REGS_H + +#define FLEXCAN_GENMASK(h, l) (((~(uint32_t)0) >> (31 - (h) + (l))) << (l)) + +/** + * The following macros were originally written for the Linux kernel by: + * Andrey Volkov <andrey@volkov.fr> + * Sascha Hauer <s.hauer@pengutronix.de> + * Marc Kleine-Budde <mkl@pengutronix.de> + * David Jander <david@protonic.nl> + * and they have agreed to license them under GPL-2.0-or-later. + */ + +/* FLEXCAN module configuration register (CANMCR) bits */ +#define FLEXCAN_MCR_MDIS BIT(31) +#define FLEXCAN_MCR_FRZ BIT(30) +#define FLEXCAN_MCR_FEN BIT(29) +#define FLEXCAN_MCR_HALT BIT(28) +#define FLEXCAN_MCR_NOT_RDY BIT(27) +#define FLEXCAN_MCR_WAK_MSK BIT(26) +#define FLEXCAN_MCR_SOFTRST BIT(25) +#define FLEXCAN_MCR_FRZ_ACK BIT(24) +#define FLEXCAN_MCR_SUPV BIT(23) +#define FLEXCAN_MCR_SLF_WAK BIT(22) +#define FLEXCAN_MCR_WRN_EN BIT(21) +#define FLEXCAN_MCR_LPM_ACK BIT(20) +#define FLEXCAN_MCR_WAK_SRC BIT(19) +#define FLEXCAN_MCR_DOZE BIT(18) +#define FLEXCAN_MCR_SRX_DIS BIT(17) +#define FLEXCAN_MCR_IRMQ BIT(16) +#define FLEXCAN_MCR_LPRIO_EN BIT(13) +#define FLEXCAN_MCR_AEN BIT(12) +#define FLEXCAN_MCR_FDEN BIT(11) +#define FLEXCAN_MCR_MAXMB(x) ((x) & 0x7f) +#define FLEXCAN_MCR_IDAM_A (0x0 << 8) +#define FLEXCAN_MCR_IDAM_B (0x1 << 8) +#define FLEXCAN_MCR_IDAM_C (0x2 << 8) +#define FLEXCAN_MCR_IDAM_D (0x3 << 8) +#define FLEXCAN_MCR_IDAM_MASK (0x3 << 8) + +/* FLEXCAN control register (CANCTRL) bits */ +#define FLEXCAN_CTRL_PRESDIV(x) (((x) & 0xFF) << 24) +#define FLEXCAN_CTRL_PRESDIV_MASK FLEXCAN_CTRL_PRESDIV(UINT32_MAX) +#define FLEXCAN_CTRL_RJW(x) (((x) & 0x03) << 22) +#define FLEXCAN_CTRL_RJW_MASK FLEXCAN_CTRL_RJW(UINT32_MAX) +#define FLEXCAN_CTRL_PSEG1(x) (((x) & 0x07) << 19) +#define FLEXCAN_CTRL_PSEG1_MASK FLEXCAN_CTRL_PSEG1(UINT32_MAX) +#define FLEXCAN_CTRL_PSEG2(x) (((x) & 0x07) << 16) +#define FLEXCAN_CTRL_PSEG2_MASK FLEXCAN_CTRL_PSEG2(UINT32_MAX) +#define FLEXCAN_CTRL_BOFF_MSK BIT(15) +#define FLEXCAN_CTRL_ERR_MSK BIT(14) +#define FLEXCAN_CTRL_CLK_SRC BIT(13) +#define FLEXCAN_CTRL_LPB BIT(12) +#define FLEXCAN_CTRL_TWRN_MSK BIT(11) +#define FLEXCAN_CTRL_RWRN_MSK BIT(10) +#define FLEXCAN_CTRL_SMP BIT(7) +#define FLEXCAN_CTRL_BOFF_REC BIT(6) +#define FLEXCAN_CTRL_TSYN BIT(5) +#define FLEXCAN_CTRL_LBUF BIT(4) +#define FLEXCAN_CTRL_LOM BIT(3) +#define FLEXCAN_CTRL_PROPSEG(x) ((x) & 0x07) +#define FLEXCAN_CTRL_PROPSEG_MASK FLEXCAN_CTRL_PROPSEG(UINT32_MAX) +#define FLEXCAN_CTRL_ERR_BUS (FLEXCAN_CTRL_ERR_MSK) +#define FLEXCAN_CTRL_ERR_STATE \ + (FLEXCAN_CTRL_TWRN_MSK | FLEXCAN_CTRL_RWRN_MSK | \ + FLEXCAN_CTRL_BOFF_MSK) +#define FLEXCAN_CTRL_ERR_ALL \ + (FLEXCAN_CTRL_ERR_BUS | FLEXCAN_CTRL_ERR_STATE) + +/* FLEXCAN control register 2 (CTRL2) bits */ +#define FLEXCAN_CTRL2_ECRWRE BIT(29) +#define FLEXCAN_CTRL2_WRMFRZ BIT(28) +#define FLEXCAN_CTRL2_RFFN(x) (((x) & 0x0f) << 24) +#define FLEXCAN_CTRL2_TASD(x) (((x) & 0x1f) << 19) +#define FLEXCAN_CTRL2_MRP BIT(18) +#define FLEXCAN_CTRL2_RRS BIT(17) +#define FLEXCAN_CTRL2_EACEN BIT(16) +#define FLEXCAN_CTRL2_ISOCANFDEN BIT(12) + +/* FLEXCAN memory error control register (MECR) bits */ +#define FLEXCAN_MECR_ECRWRDIS BIT(31) +#define FLEXCAN_MECR_HANCEI_MSK BIT(19) +#define FLEXCAN_MECR_FANCEI_MSK BIT(18) +#define FLEXCAN_MECR_CEI_MSK BIT(16) +#define FLEXCAN_MECR_HAERRIE BIT(15) +#define FLEXCAN_MECR_FAERRIE BIT(14) +#define FLEXCAN_MECR_EXTERRIE BIT(13) +#define FLEXCAN_MECR_RERRDIS BIT(9) +#define FLEXCAN_MECR_ECCDIS BIT(8) +#define FLEXCAN_MECR_NCEFAFRZ BIT(7) + +/* FLEXCAN error and status register (ESR) bits */ +#define FLEXCAN_ESR_SYNCH BIT(18) +#define FLEXCAN_ESR_TWRN_INT BIT(17) +#define FLEXCAN_ESR_RWRN_INT BIT(16) +#define FLEXCAN_ESR_BIT1_ERR BIT(15) +#define FLEXCAN_ESR_BIT0_ERR BIT(14) +#define FLEXCAN_ESR_ACK_ERR BIT(13) +#define FLEXCAN_ESR_CRC_ERR BIT(12) +#define FLEXCAN_ESR_FRM_ERR BIT(11) +#define FLEXCAN_ESR_STF_ERR BIT(10) +#define FLEXCAN_ESR_TX_WRN BIT(9) +#define FLEXCAN_ESR_RX_WRN BIT(8) +#define FLEXCAN_ESR_IDLE BIT(7) +#define FLEXCAN_ESR_BOFF_INT BIT(2) +#define FLEXCAN_ESR_ERR_INT BIT(1) +#define FLEXCAN_ESR_WAK_INT BIT(0) + +/* FLEXCAN Bit Timing register (CBT) bits */ +#define FLEXCAN_CBT_BTF BIT(31) +#define FLEXCAN_CBT_EPRESDIV_MASK FLEXCAN_GENMASK(30, 21) +#define FLEXCAN_CBT_ERJW_MASK FLEXCAN_GENMASK(20, 16) +#define FLEXCAN_CBT_EPROPSEG_MASK FLEXCAN_GENMASK(15, 10) +#define FLEXCAN_CBT_EPSEG1_MASK FLEXCAN_GENMASK(9, 5) +#define FLEXCAN_CBT_EPSEG2_MASK FLEXCAN_GENMASK(4, 0) + +/* FLEXCAN FD control register (FDCTRL) bits */ +#define FLEXCAN_FDCTRL_FDRATE BIT(31) +#define FLEXCAN_FDCTRL_MBDSR1 FLEXCAN_GENMASK(20, 19) +#define FLEXCAN_FDCTRL_MBDSR0 FLEXCAN_GENMASK(17, 16) +#define FLEXCAN_FDCTRL_MBDSR_8 0x0 +#define FLEXCAN_FDCTRL_MBDSR_12 0x1 +#define FLEXCAN_FDCTRL_MBDSR_32 0x2 +#define FLEXCAN_FDCTRL_MBDSR_64 0x3 +#define FLEXCAN_FDCTRL_TDCEN BIT(15) +#define FLEXCAN_FDCTRL_TDCFAIL BIT(14) +#define FLEXCAN_FDCTRL_TDCOFF FLEXCAN_GENMASK(12, 8) +#define FLEXCAN_FDCTRL_TDCVAL FLEXCAN_GENMASK(5, 0) + +/* FLEXCAN FD Bit Timing register (FDCBT) bits */ +#define FLEXCAN_FDCBT_FPRESDIV_MASK FLEXCAN_GENMASK(29, 20) +#define FLEXCAN_FDCBT_FRJW_MASK FLEXCAN_GENMASK(18, 16) +#define FLEXCAN_FDCBT_FPROPSEG_MASK FLEXCAN_GENMASK(14, 10) +#define FLEXCAN_FDCBT_FPSEG1_MASK FLEXCAN_GENMASK(7, 5) +#define FLEXCAN_FDCBT_FPSEG2_MASK FLEXCAN_GENMASK(2, 0) + +/* FLEXCAN CRC Register (CRCR) bits */ +#define FLEXCAN_CRCR_MBCRC_MASK FLEXCAN_GENMASK(22, 16) +#define FLEXCAN_CRCR_MBCRC(x) (((x) & FLEXCAN_CRCR_MBCRC_MASK) << 16) +#define FLEXCAN_CRCR_TXCRC_MASK FLEXCAN_GENMASK(14, 0) +#define FLEXCAN_CRCR_TXCRC(x) ((x) & FLEXCAN_CRCR_TXCRC_MASK) + +/* FLEXCAN interrupt flag register (IFLAG) bits */ +/* Errata ERR005829 step7: Reserve first valid MB */ +#define I_FIFO_OVERFLOW 7 +#define I_FIFO_WARN 6 +#define I_FIFO_AVAILABLE 5 + +#define FLEXCAN_TX_MB_RESERVED_RX_FIFO 8 +#define FLEXCAN_TX_MB_RESERVED_RX_MAILBOX 0 +#define FLEXCAN_RX_MB_RX_MAILBOX_FIRST (FLEXCAN_TX_MB_RESERVED_RX_MAILBOX + 1) +#define FLEXCAN_IFLAG_MB(x) BIT_ULL(x) +#define FLEXCAN_IFLAG_RX_FIFO_OVERFLOW BIT(I_FIFO_OVERFLOW) +#define FLEXCAN_IFLAG_RX_FIFO_WARN BIT(I_FIFO_WARN) +#define FLEXCAN_IFLAG_RX_FIFO_AVAILABLE BIT(I_FIFO_AVAILABLE) + +/* FLEXCAN message buffers */ +#define FLEXCAN_MB_CODE_RX_BUSY_BIT (0x1 << 24) +#define FLEXCAN_MB_CODE_RX_INACTIVE (0x0 << 24) +#define FLEXCAN_MB_CODE_RX_EMPTY (0x4 << 24) +#define FLEXCAN_MB_CODE_RX_FULL (0x2 << 24) +#define FLEXCAN_MB_CODE_RX_OVERRUN (0x6 << 24) +#define FLEXCAN_MB_CODE_RX_RANSWER (0xa << 24) + +#define FLEXCAN_MB_CODE_TX_INACTIVE (0x8 << 24) +#define FLEXCAN_MB_CODE_TX_ABORT (0x9 << 24) +#define FLEXCAN_MB_CODE_TX_DATA (0xc << 24) +#define FLEXCAN_MB_CODE_TX_TANSWER (0xe << 24) + +#define FLEXCAN_MB_CODE(x) (((x) & 0xF) << 24) +#define FLEXCAN_MB_CODE_MASK FLEXCAN_MB_CODE(UINT32_MAX) + +#define FLEXCAN_MB_CNT_EDL BIT(31) +#define FLEXCAN_MB_CNT_BRS BIT(30) +#define FLEXCAN_MB_CNT_ESI BIT(29) +#define FLEXCAN_MB_CNT_SRR BIT(22) +#define FLEXCAN_MB_CNT_IDE BIT(21) +#define FLEXCAN_MB_CNT_RTR BIT(20) +#define FLEXCAN_MB_CNT_LENGTH(x) (((x) & 0xF) << 16) +#define FLEXCAN_MB_CNT_TIMESTAMP(x) ((x) & 0xFFFF) +#define FLEXCAN_MB_CNT_TIMESTAMP_MASK FLEXCAN_MB_CNT_TIMESTAMP(UINT32_MAX) + +#endif diff --git a/hw/net/can/meson.build b/hw/net/can/meson.build index 7382344628..401afde2e4 100644 --- a/hw/net/can/meson.build +++ b/hw/net/can/meson.build @@ -6,3 +6,4 @@ system_ss.add(when: 'CONFIG_CAN_CTUCANFD', if_true: files('ctucan_core.c')) system_ss.add(when: 'CONFIG_CAN_CTUCANFD_PCI', if_true: files('ctucan_pci.c')) system_ss.add(when: 'CONFIG_XLNX_ZYNQMP', if_true: files('xlnx-zynqmp-can.c')) system_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal-canfd.c')) +system_ss.add(when: 'CONFIG_CAN_FLEXCAN', if_true: files('flexcan.c')) diff --git a/hw/net/can/trace-events b/hw/net/can/trace-events index de64ac1b31..7500f10d7a 100644 --- a/hw/net/can/trace-events +++ b/hw/net/can/trace-events @@ -1,3 +1,21 @@ +# flexcan.c +flexcan_irq_update(const char *inst, uint32_t mb_irqs1, uint32_t mb_irqs2, int setting) "%s: irqs1 0x%08x irqs2 0x%08x request %i" +flexcan_set_mcr(const char *inst, const char *enabled, const char *freeze, const char *fifo, const char *rdy, const char *sync) "%s: %s %s %s %s %s" +flexcan_mb_write(const char *inst, int mbidx, const char *code, int is_mailbox, uint32_t ctrl, uint32_t id) "%s: mbidx %i code %s is_mailbox %i ctrl 0x%08x id 0x%08x" +flexcan_mb_lock(const char *inst, int mbidx, int had_rx_code) "%s: mbidx %i had_rx_code %i" +flexcan_mb_unlock(const char *inst, int mbidx, const char *pending_frame) "%s: mbidx %i%s" +flexcan_fifo_pop(const char *inst, int non_empty_before, int non_empty_after) "%s: non_empty before %i non_empty_after %i" +flexcan_fifo_push(const char *inst, int n_occupied) "%s: n_slots_occupied %i" +flexcan_reset(const char *inst) "%s: resetting" +flexcan_mem_op(const char *inst, const char *op, uint32_t v, int offset, const char *reg_name, int size) "%s: %s 0x%08x at offset %i register %s size %i" +flexcan_get_timestamp(const char *inst, int64_t time_elapsed_ms, uint32_t bitrate, uint64_t cycles, uint32_t shift, uint32_t timestamp) "%s: time_elapsed %" PRIi64 "ms bitrate %ub/s cycles %" PRIu64 " shift %u timestamp 0x%04x" +flexcan_get_bitrate(const char *inst, uint32_t pe_freq, uint32_t prediv, uint32_t s_freq, uint32_t tseg1, uint32_t tseg2, uint32_t quata_per_bit, uint32_t bitrate) "%s: pe_freq %uHz prescaler %u s_freq %uHz tseg1 %uq tseg2 %uq total %uq/b bitrate %ub/s" +flexcan_timer_start(const char *inst, uint32_t bitrate, uint32_t value) "%s: bitrate %ub/s value 0x%04x" +flexcan_timer_stop(const char *inst, uint32_t bitrate, uint32_t value) "%s: bitrate %ub/s value 0x%04x" +flexcan_timer_overflow(const char *inst, int64_t current_time, int64_t timer_start, int64_t elapsed_ns) "%s: current_time %" PRIi64 "timer_start %" PRIi64 "elapsed_ns %" PRIi64 +flexcan_mb_rx_check_mb(const char *inst, int mbidx, const char *code, int is_matched, int is_ftr, int is_serviced, int is_locked) "%s: checking mb %i code %s is_matched %i is_free_to_receive %i is_serviced %i is_locked %i" +flexcan_receive(const char *inst, size_t n_frames) "%s: received %zu frames" + # xlnx-zynqmp-can.c xlnx_can_update_irq(uint32_t isr, uint32_t ier, uint32_t irq) "ISR: 0x%08x IER: 0x%08x IRQ: 0x%08x" xlnx_can_reset(uint32_t val) "Resetting controller with value = 0x%08x" diff --git a/hw/pci-host/meson.build b/hw/pci-host/meson.build index 86b754d0b0..3217e7e912 100644 --- a/hw/pci-host/meson.build +++ b/hw/pci-host/meson.build @@ -44,5 +44,7 @@ specific_ss.add(when: 'CONFIG_PCI_POWERNV', if_true: files( 'pnv_phb3_pbcq.c', 'pnv_phb4.c', 'pnv_phb4_pec.c', + 'pnv_phb5.c', + 'pnv_phb5_pec.c', 'pnv_phb.c', )) diff --git a/hw/pci-host/pnv_phb4.c b/hw/pci-host/pnv_phb4.c index 396bc47817..705a5bcf07 100644 --- a/hw/pci-host/pnv_phb4.c +++ b/hw/pci-host/pnv_phb4.c @@ -1721,11 +1721,6 @@ static const TypeInfo pnv_phb4_type_info = { } }; -static const TypeInfo pnv_phb5_type_info = { - .name = TYPE_PNV_PHB5, - .parent = TYPE_PNV_PHB4, - .instance_size = sizeof(PnvPHB4), -}; static void pnv_phb4_root_bus_get_prop(Object *obj, Visitor *v, const char *name, @@ -1794,7 +1789,6 @@ static void pnv_phb4_register_types(void) { type_register_static(&pnv_phb4_root_bus_info); type_register_static(&pnv_phb4_type_info); - type_register_static(&pnv_phb5_type_info); type_register_static(&pnv_phb4_iommu_memory_region_info); } diff --git a/hw/pci-host/pnv_phb4_pec.c b/hw/pci-host/pnv_phb4_pec.c index 58ec14ec2f..ee5cdc3e45 100644 --- a/hw/pci-host/pnv_phb4_pec.c +++ b/hw/pci-host/pnv_phb4_pec.c @@ -394,67 +394,9 @@ static const TypeInfo pnv_pec_type_info = { } }; -/* - * POWER10 definitions - */ -static uint32_t pnv_phb5_pec_xscom_cplt_base(PnvPhb4PecState *pec) -{ - return PNV10_XSCOM_PEC_NEST_CPLT_BASE + XPEC_PCI_CPLT_OFFSET * pec->index; -} - -static uint32_t pnv_phb5_pec_xscom_pci_base(PnvPhb4PecState *pec) -{ - return PNV10_XSCOM_PEC_PCI_BASE + 0x1000000 * pec->index; -} - -static uint32_t pnv_phb5_pec_xscom_nest_base(PnvPhb4PecState *pec) -{ - /* index goes down ... */ - return PNV10_XSCOM_PEC_NEST_BASE - 0x1000000 * pec->index; -} - -/* - * PEC0 -> 3 stacks - * PEC1 -> 3 stacks - */ -static const uint32_t pnv_phb5_pec_num_stacks[] = { 3, 3 }; - -static void pnv_phb5_pec_class_init(ObjectClass *klass, const void *data) -{ - PnvPhb4PecClass *pecc = PNV_PHB4_PEC_CLASS(klass); - static const char compat[] = "ibm,power10-pbcq"; - static const char stk_compat[] = "ibm,power10-phb-stack"; - - pecc->xscom_cplt_base = pnv_phb5_pec_xscom_cplt_base; - pecc->xscom_nest_base = pnv_phb5_pec_xscom_nest_base; - pecc->xscom_pci_base = pnv_phb5_pec_xscom_pci_base; - pecc->xscom_nest_size = PNV10_XSCOM_PEC_NEST_SIZE; - pecc->xscom_pci_size = PNV10_XSCOM_PEC_PCI_SIZE; - pecc->compat = compat; - pecc->compat_size = sizeof(compat); - pecc->stk_compat = stk_compat; - pecc->stk_compat_size = sizeof(stk_compat); - pecc->version = PNV_PHB5_VERSION; - pecc->phb_type = TYPE_PNV_PHB5; - pecc->num_phbs = pnv_phb5_pec_num_stacks; -} - -static const TypeInfo pnv_phb5_pec_type_info = { - .name = TYPE_PNV_PHB5_PEC, - .parent = TYPE_PNV_PHB4_PEC, - .instance_size = sizeof(PnvPhb4PecState), - .class_init = pnv_phb5_pec_class_init, - .class_size = sizeof(PnvPhb4PecClass), - .interfaces = (const InterfaceInfo[]) { - { TYPE_PNV_XSCOM_INTERFACE }, - { } - } -}; - static void pnv_pec_register_types(void) { type_register_static(&pnv_pec_type_info); - type_register_static(&pnv_phb5_pec_type_info); } type_init(pnv_pec_register_types); diff --git a/hw/pci-host/pnv_phb5.c b/hw/pci-host/pnv_phb5.c new file mode 100644 index 0000000000..c3f2e16ce6 --- /dev/null +++ b/hw/pci-host/pnv_phb5.c @@ -0,0 +1,23 @@ +/* + * QEMU PowerPC PowerNV (POWER10) PHB5 model + * + * Copyright (c) 2018-2026, IBM Corporation. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/pci-host/pnv_phb4.h" + +static const TypeInfo pnv_phb5_type_info = { + .name = TYPE_PNV_PHB5, + .parent = TYPE_PNV_PHB4, + .instance_size = sizeof(PnvPHB4), +}; + +static void pnv_phb5_register_types(void) +{ + type_register_static(&pnv_phb5_type_info); +} + +type_init(pnv_phb5_register_types); diff --git a/hw/pci-host/pnv_phb5_pec.c b/hw/pci-host/pnv_phb5_pec.c new file mode 100644 index 0000000000..488ccbfb12 --- /dev/null +++ b/hw/pci-host/pnv_phb5_pec.c @@ -0,0 +1,77 @@ +/* + * QEMU PowerPC PowerNV (POWER10) PHB5 PEC model + * + * Copyright (c) 2018-2026, IBM Corporation. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/pci-host/pnv_phb4.h" +#include "hw/ppc/pnv_xscom.h" + +#define XPEC_PCI_CPLT_OFFSET 0x1000000ULL + +/* + * POWER10 definitions + */ +static uint32_t pnv_phb5_pec_xscom_cplt_base(PnvPhb4PecState *pec) +{ + return PNV10_XSCOM_PEC_NEST_CPLT_BASE + XPEC_PCI_CPLT_OFFSET * pec->index; +} + +static uint32_t pnv_phb5_pec_xscom_pci_base(PnvPhb4PecState *pec) +{ + return PNV10_XSCOM_PEC_PCI_BASE + 0x1000000 * pec->index; +} + +static uint32_t pnv_phb5_pec_xscom_nest_base(PnvPhb4PecState *pec) +{ + /* index goes down ... */ + return PNV10_XSCOM_PEC_NEST_BASE - 0x1000000 * pec->index; +} + +/* + * PEC0 -> 3 stacks + * PEC1 -> 3 stacks + */ +static const uint32_t pnv_phb5_pec_num_stacks[] = { 3, 3 }; + +static void pnv_phb5_pec_class_init(ObjectClass *klass, const void *data) +{ + PnvPhb4PecClass *pecc = PNV_PHB4_PEC_CLASS(klass); + static const char compat[] = "ibm,power10-pbcq"; + static const char stk_compat[] = "ibm,power10-phb-stack"; + + pecc->xscom_cplt_base = pnv_phb5_pec_xscom_cplt_base; + pecc->xscom_nest_base = pnv_phb5_pec_xscom_nest_base; + pecc->xscom_pci_base = pnv_phb5_pec_xscom_pci_base; + pecc->xscom_nest_size = PNV10_XSCOM_PEC_NEST_SIZE; + pecc->xscom_pci_size = PNV10_XSCOM_PEC_PCI_SIZE; + pecc->compat = compat; + pecc->compat_size = sizeof(compat); + pecc->stk_compat = stk_compat; + pecc->stk_compat_size = sizeof(stk_compat); + pecc->version = PNV_PHB5_VERSION; + pecc->phb_type = TYPE_PNV_PHB5; + pecc->num_phbs = pnv_phb5_pec_num_stacks; +} + +static const TypeInfo pnv_phb5_pec_type_info = { + .name = TYPE_PNV_PHB5_PEC, + .parent = TYPE_PNV_PHB4_PEC, + .instance_size = sizeof(PnvPhb4PecState), + .class_init = pnv_phb5_pec_class_init, + .class_size = sizeof(PnvPhb4PecClass), + .interfaces = (const InterfaceInfo[]) { + { TYPE_PNV_XSCOM_INTERFACE }, + { } + } +}; + +static void pnv_phb5_pec_register_types(void) +{ + type_register_static(&pnv_phb5_pec_type_info); +} + +type_init(pnv_phb5_pec_register_types); diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c index 62812f22f8..afb6019b10 100644 --- a/hw/ppc/pnv.c +++ b/hw/ppc/pnv.c @@ -803,9 +803,13 @@ static void pnv_reset(MachineState *machine, ResetType type) mpipl_write_succeeded = do_mpipl_write(pnv); } - /* Regenerate device tree */ - fdt = pnv_dt_create(machine); - _FDT((fdt_pack(fdt))); + /* Only create new dt if not provided in -dtb */ + if (!machine->dtb) { + fdt = pnv_dt_create(machine); + _FDT((fdt_pack(fdt))); + } else { + fdt = machine->fdt; + } /* * If it's a MPIPL boot, add the "mpipl-boot" property, and reset the diff --git a/hw/ppc/ppc.c b/hw/ppc/ppc.c index a512d4fa64..b123b4cc1c 100644 --- a/hw/ppc/ppc.c +++ b/hw/ppc/ppc.c @@ -515,6 +515,11 @@ uint64_t cpu_ppc_get_tb(ppc_tb_t *tb_env, uint64_t vmclk, int64_t tb_offset) return ns_to_tb(tb_env->tb_freq, vmclk) + tb_offset; } +int64_t cpu_ppc_load_tb_offset(CPUPPCState *env) +{ + return env->tb_env->tb_offset; +} + uint64_t cpu_ppc_load_tbl (CPUPPCState *env) { ppc_tb_t *tb_env = env->tb_env; diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index 54e41a6afc..de37c08cae 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -120,6 +120,19 @@ config SPIKE select RISCV_ACLINT select SIFIVE_PLIC +config TENSTORRENT + bool + default y + depends on RISCV64 + select RISCV_ACLINT + select RISCV_APLIC + select RISCV_IMSIC + select SERIAL_MM + select DEVICE_TREE + select DESIGNWARE_I2C + select DS1338 + select TMP105 + config XIANGSHAN_KUNMINGHU bool default y diff --git a/hw/riscv/aia.c b/hw/riscv/aia.c new file mode 100644 index 0000000000..ed89160029 --- /dev/null +++ b/hw/riscv/aia.c @@ -0,0 +1,94 @@ +/* + * QEMU RISC-V Advanced Interrupt Architecture (AIA) + * + * Copyright (C) 2019 Western Digital Corporation or its affiliates. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "system/kvm.h" +#include "hw/intc/riscv_aplic.h" +#include "hw/intc/riscv_imsic.h" + +#include "aia.h" + +uint32_t imsic_num_bits(uint32_t count) +{ + uint32_t ret = 0; + + while (BIT(ret) < count) { + ret++; + } + + return ret; +} + +DeviceState *riscv_create_aia(bool msimode, int aia_guests, + uint32_t m_imsic_stride, + uint16_t num_sources, + const MemMapEntry *aplic_m, + const MemMapEntry *aplic_s, + const MemMapEntry *imsic_m, + const MemMapEntry *imsic_s, + int socket, int base_hartid, int hart_count, + uint32_t num_msis, uint32_t num_prio_bits) +{ + int i; + hwaddr addr = 0; + uint32_t guest_bits; + DeviceState *aplic_s_dev = NULL; + DeviceState *aplic_m_dev = NULL; + + /* The RISC-V Advanced Interrupt Architecture, Chapter 1.2. Limits */ + g_assert(num_sources <= 1023); + + if (msimode) { + if (!kvm_enabled()) { + /* Per-socket M-level IMSICs */ + addr = imsic_m->base + socket * (1U << IMSIC_MMIO_GROUP_MIN_SHIFT); + for (i = 0; i < hart_count; i++) { + riscv_imsic_create(addr + i * m_imsic_stride, + base_hartid + i, true, 1, + num_msis); + } + } + + /* Per-socket S-level IMSICs */ + guest_bits = imsic_num_bits(aia_guests + 1); + addr = imsic_s->base + socket * (1U << IMSIC_MMIO_GROUP_MIN_SHIFT); + for (i = 0; i < hart_count; i++) { + riscv_imsic_create(addr + i * IMSIC_HART_SIZE(guest_bits), + base_hartid + i, false, 1 + aia_guests, + num_msis); + } + } + + if (!kvm_enabled()) { + /* Per-socket M-level APLIC */ + aplic_m_dev = riscv_aplic_create(aplic_m->base + + socket * aplic_m->size, + aplic_m->size, + (msimode) ? 0 : base_hartid, + (msimode) ? 0 : hart_count, + num_sources, + num_prio_bits, + msimode, true, NULL); + } + + /* Per-socket S-level APLIC */ + aplic_s_dev = riscv_aplic_create(aplic_s->base + + socket * aplic_s->size, + aplic_s->size, + (msimode) ? 0 : base_hartid, + (msimode) ? 0 : hart_count, + num_sources, + num_prio_bits, + msimode, false, aplic_m_dev); + + if (kvm_enabled() && msimode) { + riscv_aplic_set_kvm_msicfgaddr(RISCV_APLIC(aplic_s_dev), addr); + } + + return kvm_enabled() ? aplic_s_dev : aplic_m_dev; +} diff --git a/hw/riscv/aia.h b/hw/riscv/aia.h new file mode 100644 index 0000000000..565f91accc --- /dev/null +++ b/hw/riscv/aia.h @@ -0,0 +1,26 @@ +/* + * QEMU RISC-V Advanced Interrupt Architecture (AIA) + * + * Copyright (C) 2019 Western Digital Corporation or its affiliates. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_RISCV_AIA_H +#define HW_RISCV_AIA_H + +#include "exec/hwaddr.h" + +uint32_t imsic_num_bits(uint32_t count); + +DeviceState *riscv_create_aia(bool msimode, int aia_guests, + uint32_t m_imsic_stride, + uint16_t num_sources, + const MemMapEntry *aplic_m, + const MemMapEntry *aplic_s, + const MemMapEntry *imsic_m, + const MemMapEntry *imsic_s, + int socket, int base_hartid, int hart_count, + uint32_t num_msis, uint32_t num_prio_bits); + +#endif diff --git a/hw/riscv/boot.c b/hw/riscv/boot.c index 7c9cd61468..5e2dfa091a 100644 --- a/hw/riscv/boot.c +++ b/hw/riscv/boot.c @@ -70,11 +70,27 @@ char *riscv_plic_hart_config_string(int hart_count) void riscv_boot_info_init(RISCVBootInfo *info, RISCVHartArrayState *harts) { + info->ram_low_start = 0; + info->ram_low_size = 0; info->kernel_size = 0; info->initrd_size = 0; info->is_32bit = riscv_is_32bit(harts); } +/* + * This can be used instead of riscv_boot_info_init() if the machine has + * discontiguous physical memory. The low memory range specified will be + * used to place firmware images. + */ +void riscv_boot_info_init_discontig_mem(RISCVBootInfo *info, + RISCVHartArrayState *harts, + hwaddr low_start, hwaddr low_size) +{ + riscv_boot_info_init(info, harts); + info->ram_low_start = low_start; + info->ram_low_size = low_size; +} + vaddr riscv_calc_kernel_start_addr(RISCVBootInfo *info, hwaddr firmware_end_addr) { if (info->is_32bit) { @@ -135,6 +151,7 @@ char *riscv_find_firmware(const char *firmware_filename, } hwaddr riscv_find_and_load_firmware(MachineState *machine, + RISCVBootInfo *info, const char *default_machine_firmware, hwaddr *firmware_load_addr, symbol_fn_t sym_cb) @@ -147,7 +164,8 @@ hwaddr riscv_find_and_load_firmware(MachineState *machine, if (firmware_filename) { /* If not "none" load the firmware */ - firmware_end_addr = riscv_load_firmware(firmware_filename, + firmware_end_addr = riscv_load_firmware(machine, info, + firmware_filename, firmware_load_addr, sym_cb); g_free(firmware_filename); } @@ -155,10 +173,13 @@ hwaddr riscv_find_and_load_firmware(MachineState *machine, return firmware_end_addr; } -hwaddr riscv_load_firmware(const char *firmware_filename, +hwaddr riscv_load_firmware(MachineState *machine, + const RISCVBootInfo *info, + const char *firmware_filename, hwaddr *firmware_load_addr, symbol_fn_t sym_cb) { + uint64_t mem_size = info->ram_low_size ?: machine->ram_size; uint64_t firmware_entry, firmware_end; ssize_t firmware_size; @@ -187,7 +208,7 @@ hwaddr riscv_load_firmware(const char *firmware_filename, firmware_size = load_image_targphys_as(firmware_filename, *firmware_load_addr, - current_machine->ram_size, NULL, + mem_size, NULL, NULL); if (firmware_size > 0) { @@ -202,7 +223,7 @@ hwaddr riscv_load_firmware(const char *firmware_filename, static void riscv_load_initrd(MachineState *machine, RISCVBootInfo *info) { const char *filename = machine->initrd_filename; - uint64_t mem_size = machine->ram_size; + uint64_t mem_size = info->ram_low_size ?: machine->ram_size; void *fdt = machine->fdt; hwaddr start, end; ssize_t size; @@ -248,6 +269,7 @@ void riscv_load_kernel(MachineState *machine, bool load_initrd, symbol_fn_t sym_cb) { + uint64_t mem_size = info->ram_low_size ?: machine->ram_size; const char *kernel_filename = machine->kernel_filename; ssize_t kernel_size; void *fdt = machine->fdt; @@ -279,7 +301,7 @@ void riscv_load_kernel(MachineState *machine, } kernel_size = load_image_targphys_as(kernel_filename, kernel_start_addr, - current_machine->ram_size, NULL, NULL); + mem_size, NULL, NULL); if (kernel_size > 0) { info->kernel_size = kernel_size; info->image_low_addr = kernel_start_addr; @@ -375,7 +397,7 @@ uint64_t riscv_compute_fdt_addr(hwaddr dram_base, hwaddr dram_size, dtb_start = QEMU_ALIGN_DOWN(temp - fdtsize, 2 * MiB); if (dtb_start_limit && (dtb_start < dtb_start_limit)) { - error_report("No enough memory to place DTB after kernel/initrd"); + error_report("Not enough memory to place DTB after kernel/initrd"); exit(1); } diff --git a/hw/riscv/fdt-common.c b/hw/riscv/fdt-common.c index b27ff13bca..e0e31af09b 100644 --- a/hw/riscv/fdt-common.c +++ b/hw/riscv/fdt-common.c @@ -200,3 +200,33 @@ create_fdt_socket_cpu_sifive(void *fdt, char *clust_name, socket_id, socket_hartid_base, phandle, intc_phandles, false, false); } + +void create_fdt_plic(void *fdt, hwaddr addr, uint64_t size, + uint32_t plic_phandle, uint32_t int_cells, + uint32_t addr_cells, uint32_t *plic_cells, + uint32_t cells_size, uint32_t ndev_sources, + bool numa_enabled, int socket_id) +{ + g_autofree char *nodename = NULL; + static const char * const plic_compat[2] = { + "sifive,plic-1.0.0", "riscv,plic0" + }; + + nodename = g_strdup_printf("/soc/interrupt-controller@%"HWADDR_PRIx, addr); + + qemu_fdt_add_subnode(fdt, nodename); + qemu_fdt_setprop_cell(fdt, nodename, "#interrupt-cells", int_cells); + qemu_fdt_setprop_cell(fdt, nodename, "#address-cells", addr_cells); + qemu_fdt_setprop_string_array(fdt, nodename, "compatible", + (char **)&plic_compat, ARRAY_SIZE(plic_compat)); + qemu_fdt_setprop(fdt, nodename, "interrupt-controller", NULL, 0); + qemu_fdt_setprop(fdt, nodename, "interrupts-extended", + plic_cells, cells_size); + qemu_fdt_setprop_sized_cells(fdt, nodename, "reg", + 2, addr, 2, size); + qemu_fdt_setprop_cell(fdt, nodename, "riscv,ndev", ndev_sources); + if (numa_enabled) { + qemu_fdt_setprop_cell(fdt, nodename, "numa-node-id", socket_id); + } + qemu_fdt_setprop_cell(fdt, nodename, "phandle", plic_phandle); +} diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c index 502281c52c..656f28190c 100644 --- a/hw/riscv/k230.c +++ b/hw/riscv/k230.c @@ -424,7 +424,8 @@ static void k230_direct_boot(K230MachineState *s, MachineState *machine) riscv_load_fdt(K230_DIRECT_DTB_ADDR, machine->fdt); - firmware_end_addr = riscv_find_and_load_firmware(machine, firmware_name, + firmware_end_addr = riscv_find_and_load_firmware(machine, &boot_info, + firmware_name, &start_addr, NULL); if (firmware_end_addr > K230_DIRECT_KERNEL_ADDR) { error_report("K230 firmware overlaps kernel address 0x%x", @@ -442,13 +443,16 @@ 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; + RISCVBootInfo boot_info = {0}; if (machine->dtb || (machine->kernel_cmdline && *machine->kernel_cmdline)) { error_report("K230 firmware boot does not support -dtb or -append"); exit(EXIT_FAILURE); } - riscv_find_and_load_firmware(machine, firmware_name, &start_addr, NULL); + riscv_boot_info_init(&boot_info, &s->soc.c908_cpu); + 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, memmap[K230_DEV_BOOTROM].base, diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build index b70a054579..0d82ceacc4 100644 --- a/hw/riscv/meson.build +++ b/hw/riscv/meson.build @@ -1,4 +1,5 @@ riscv_ss = ss.source_set() +riscv_ss.add(files('aia.c')) riscv_ss.add(files('boot.c')) riscv_ss.add(files('fdt-common.c')) riscv_ss.add(when: 'CONFIG_RISCV_NUMA', if_true: files('numa.c')) @@ -10,6 +11,7 @@ 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')) riscv_ss.add(when: 'CONFIG_SPIKE', if_true: files('spike.c')) riscv_ss.add(when: 'CONFIG_MICROCHIP_PFSOC', if_true: files('microchip_pfsoc.c')) +riscv_ss.add(when: 'CONFIG_TENSTORRENT', if_true: files('tt_atlantis.c')) riscv_ss.add(when: 'CONFIG_ACPI', if_true: files('virt-acpi-build.c')) riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files( 'riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c', 'riscv-iommu-hpm.c')) diff --git a/hw/riscv/microchip_pfsoc.c b/hw/riscv/microchip_pfsoc.c index 5e48a29708..4017129c83 100644 --- a/hw/riscv/microchip_pfsoc.c +++ b/hw/riscv/microchip_pfsoc.c @@ -619,18 +619,22 @@ static void microchip_icicle_kit_machine_init(MachineState *machine) firmware_load_addr = RESET_VECTOR; } + riscv_boot_info_init_discontig_mem(&boot_info, &s->soc.u_cpus, + memmap[MICROCHIP_PFSOC_DRAM_LO].base, + mem_low_size); + /* Load the firmware if necessary */ firmware_end_addr = firmware_load_addr; if (firmware_name) { char *filename = riscv_find_firmware(firmware_name, NULL); if (filename) { - firmware_end_addr = riscv_load_firmware(filename, + firmware_end_addr = riscv_load_firmware(machine, &boot_info, + filename, &firmware_load_addr, NULL); g_free(filename); } } - riscv_boot_info_init(&boot_info, &s->soc.u_cpus); if (machine->kernel_filename) { kernel_start_addr = riscv_calc_kernel_start_addr(&boot_info, firmware_end_addr); diff --git a/hw/riscv/opentitan.c b/hw/riscv/opentitan.c index c8b2f028f2..5b2f33d5ac 100644 --- a/hw/riscv/opentitan.c +++ b/hw/riscv/opentitan.c @@ -100,12 +100,14 @@ static void opentitan_machine_init(MachineState *machine) memory_region_add_subregion(sys_mem, memmap[IBEX_DEV_RAM].base, machine->ram); + riscv_boot_info_init(&boot_info, &s->soc.cpus); + if (machine->firmware) { hwaddr firmware_load_addr = memmap[IBEX_DEV_RAM].base; - riscv_load_firmware(machine->firmware, &firmware_load_addr, NULL); + riscv_load_firmware(machine, &boot_info, machine->firmware, + &firmware_load_addr, NULL); } - riscv_boot_info_init(&boot_info, &s->soc.cpus); if (machine->kernel_filename) { riscv_load_kernel(machine, &boot_info, memmap[IBEX_DEV_RAM].base, diff --git a/hw/riscv/riscv-iommu-bits.h b/hw/riscv/riscv-iommu-bits.h index a938fd3eb4..cc0b1c3ccd 100644 --- a/hw/riscv/riscv-iommu-bits.h +++ b/hw/riscv/riscv-iommu-bits.h @@ -308,6 +308,9 @@ struct riscv_iommu_dc { #define RISCV_IOMMU_DC_IOHGATP_GSCID GENMASK_ULL(59, 44) #define RISCV_IOMMU_DC_IOHGATP_MODE RISCV_IOMMU_ATP_MODE_FIELD +#define RISCV_IOMMU_DC_TC_RESERVED (GENMASK_ULL(23, 12) \ + | GENMASK_ULL(63, 32)) + enum riscv_iommu_dc_iohgatp_modes { RISCV_IOMMU_DC_IOHGATP_MODE_BARE = 0, RISCV_IOMMU_DC_IOHGATP_MODE_SV32X4 = 8, @@ -344,6 +347,7 @@ struct riscv_iommu_command { #define RISCV_IOMMU_CMD_IOFENCE_OPCODE 2 #define RISCV_IOMMU_CMD_IOFENCE_FUNC_C 0 #define RISCV_IOMMU_CMD_IOFENCE_AV BIT_ULL(10) +#define RISCV_IOMMU_CMD_IOFENCE_RESERVED GENMASK_ULL(31, 14) #define RISCV_IOMMU_CMD_IOFENCE_DATA GENMASK_ULL(63, 32) #define RISCV_IOMMU_CMD_IODIR_OPCODE 3 diff --git a/hw/riscv/riscv-iommu-hpm.c b/hw/riscv/riscv-iommu-hpm.c index 5bf80a8db9..9bfef6db45 100644 --- a/hw/riscv/riscv-iommu-hpm.c +++ b/hw/riscv/riscv-iommu-hpm.c @@ -60,8 +60,8 @@ static void hpm_incr_ctr(RISCVIOMMUState *s, uint32_t ctr_idx) const uint32_t off = ctr_idx << 3; uint64_t cntr_val; - cntr_val = ldq_le_p(&s->regs_rw[RISCV_IOMMU_REG_IOHPMCTR_BASE + off]); - stq_le_p(&s->regs_rw[RISCV_IOMMU_REG_IOHPMCTR_BASE + off], cntr_val + 1); + cntr_val = ldq_le_p(&s->regs[RISCV_IOMMU_REG_IOHPMCTR_BASE + off]); + stq_le_p(&s->regs[RISCV_IOMMU_REG_IOHPMCTR_BASE + off], cntr_val + 1); trace_riscv_iommu_hpm_incr_ctr(cntr_val); diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c index a500cb8440..974042d017 100644 --- a/hw/riscv/riscv-iommu.c +++ b/hw/riscv/riscv-iommu.c @@ -480,7 +480,7 @@ static int riscv_iommu_spa_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, break; /* Read access check failed */ } else if ((iotlb->perm & IOMMU_WO) && !(pte & PTE_W)) { break; /* Write access check failed */ - } else if ((iotlb->perm & IOMMU_RO) && !ade && !(pte & PTE_A)) { + } else if (!ade && !(pte & PTE_A)) { break; /* Access bit not set */ } else if ((iotlb->perm & IOMMU_WO) && !ade && !(pte & PTE_D)) { break; /* Dirty bit not set */ @@ -580,7 +580,7 @@ static void riscv_iommu_report_fault(RISCVIOMMUState *s, ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_CAUSE, cause); ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_TTYPE, fault_type); ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_DID, ctx->devid); - ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_PV, true); + ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_PV, pv); if (pv) { ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_PID, ctx->process_id); @@ -686,7 +686,26 @@ static MemTxResult riscv_iommu_msi_write(RISCVIOMMUState *s, /* MRIF pending bit address */ addr = get_field(pte[0], RISCV_IOMMU_MSI_PTE_MRIF_ADDR) << 9; - addr = addr | ((data & 0x7c0) >> 3); + /* + * AIA spec section "Format of a memory-resident interrupt file": + * address offset 0x000 contains interrupt-pending bits for + * identities 1-63, offfset 0x010 for identities 64-127, and + * so it goes up to 0x1F0 for identities 1984-2047. + * + * Hence each batch of identities advances offset by 16 (0x010) + * for every interrupt-pending bits. This means that doing + * (data & 0x7c0) will filter out the first 6 bits, then + * a >> 2 will turn the result in the 0x10 steps we need. + * + * E.g: + * + * - (1-63 & 0x7c0) = 0, 0 >> 2 = 0, offset 0x000 + * - (64-127 & 0x7c0) = 64, 64 >> 2 = 16, offset 0x010 + * - (128-191 & 0x7c0) = 128, 128 >> 2 = 32, offset 0x020 + * + * and so on. + */ + addr = addr | ((data & 0x7c0) >> 2); trace_riscv_iommu_msi(s->parent_obj.id, PCI_BUS_NUM(ctx->devid), PCI_SLOT(ctx->devid), PCI_FUNC(ctx->devid), @@ -752,6 +771,10 @@ static bool riscv_iommu_validate_device_ctx(RISCVIOMMUState *s, uint32_t fsc_mode, msi_mode; uint64_t gatp; + if (ctx->tc & RISCV_IOMMU_DC_TC_RESERVED) { + return false; + } + if (!(s->cap & RISCV_IOMMU_CAP_ATS) && (ctx->tc & RISCV_IOMMU_DC_TC_EN_ATS || ctx->tc & RISCV_IOMMU_DC_TC_EN_PRI || @@ -1876,6 +1899,10 @@ static void riscv_iommu_process_cq_tail(RISCVIOMMUState *s) switch (cmd_opcode) { case RISCV_IOMMU_CMD(RISCV_IOMMU_CMD_IOFENCE_FUNC_C, RISCV_IOMMU_CMD_IOFENCE_OPCODE): + if (cmd.dword0 & RISCV_IOMMU_CMD_IOFENCE_RESERVED) { + goto cmd_ill; + } + res = riscv_iommu_iofence(s, cmd.dword0 & RISCV_IOMMU_CMD_IOFENCE_AV, cmd.dword1 << 2, get_field(cmd.dword0, RISCV_IOMMU_CMD_IOFENCE_DATA)); @@ -2034,8 +2061,8 @@ static void riscv_iommu_process_cq_control(RISCVIOMMUState *s) s->cq_mask = (2ULL << get_field(base, RISCV_IOMMU_CQB_LOG2SZ)) - 1; s->cq_addr = PPN_PHYS(get_field(base, RISCV_IOMMU_CQB_PPN)); stl_le_p(&s->regs_ro[RISCV_IOMMU_REG_CQT], ~s->cq_mask); - stl_le_p(&s->regs_rw[RISCV_IOMMU_REG_CQH], 0); - stl_le_p(&s->regs_rw[RISCV_IOMMU_REG_CQT], 0); + stl_le_p(&s->regs[RISCV_IOMMU_REG_CQH], 0); + stl_le_p(&s->regs[RISCV_IOMMU_REG_CQT], 0); ctrl_set = RISCV_IOMMU_CQCSR_CQON; ctrl_clr = RISCV_IOMMU_CQCSR_BUSY | RISCV_IOMMU_CQCSR_CQMF | RISCV_IOMMU_CQCSR_CMD_ILL | RISCV_IOMMU_CQCSR_CMD_TO | @@ -2075,8 +2102,8 @@ static void riscv_iommu_process_fq_control(RISCVIOMMUState *s) s->fq_mask = (2ULL << get_field(base, RISCV_IOMMU_FQB_LOG2SZ)) - 1; s->fq_addr = PPN_PHYS(get_field(base, RISCV_IOMMU_FQB_PPN)); stl_le_p(&s->regs_ro[RISCV_IOMMU_REG_FQH], ~s->fq_mask); - stl_le_p(&s->regs_rw[RISCV_IOMMU_REG_FQH], 0); - stl_le_p(&s->regs_rw[RISCV_IOMMU_REG_FQT], 0); + stl_le_p(&s->regs[RISCV_IOMMU_REG_FQH], 0); + stl_le_p(&s->regs[RISCV_IOMMU_REG_FQT], 0); ctrl_set = RISCV_IOMMU_FQCSR_FQON; ctrl_clr = RISCV_IOMMU_FQCSR_BUSY | RISCV_IOMMU_FQCSR_FQMF | RISCV_IOMMU_FQCSR_FQOF; @@ -2105,8 +2132,8 @@ static void riscv_iommu_process_pq_control(RISCVIOMMUState *s) s->pq_mask = (2ULL << get_field(base, RISCV_IOMMU_PQB_LOG2SZ)) - 1; s->pq_addr = PPN_PHYS(get_field(base, RISCV_IOMMU_PQB_PPN)); stl_le_p(&s->regs_ro[RISCV_IOMMU_REG_PQH], ~s->pq_mask); - stl_le_p(&s->regs_rw[RISCV_IOMMU_REG_PQH], 0); - stl_le_p(&s->regs_rw[RISCV_IOMMU_REG_PQT], 0); + stl_le_p(&s->regs[RISCV_IOMMU_REG_PQH], 0); + stl_le_p(&s->regs[RISCV_IOMMU_REG_PQT], 0); ctrl_set = RISCV_IOMMU_PQCSR_PQON; ctrl_clr = RISCV_IOMMU_PQCSR_BUSY | RISCV_IOMMU_PQCSR_PQMF | RISCV_IOMMU_PQCSR_PQOF; @@ -2276,9 +2303,9 @@ static void riscv_iommu_write_reg_val(RISCVIOMMUState *s, { uint64_t ro = ldn_le_p(&s->regs_ro[reg_addr], size); uint64_t wc = ldn_le_p(&s->regs_wc[reg_addr], size); - uint64_t rw = ldn_le_p(&s->regs_rw[reg_addr], size); + uint64_t curr_val = ldn_le_p(&s->regs[reg_addr], size); - stn_le_p(dest, size, ((rw & ro) | (data & ~ro)) & ~(data & wc)); + stn_le_p(dest, size, ((curr_val & ro) | (data & ~ro)) & ~(data & wc)); } static MemTxResult riscv_iommu_mmio_write(void *opaque, hwaddr addr, @@ -2378,12 +2405,12 @@ static MemTxResult riscv_iommu_mmio_write(void *opaque, hwaddr addr, * is set IOMMU behavior of additional writes to the register * is UNSPECIFIED. */ - riscv_iommu_write_reg_val(s, &s->regs_rw[addr], addr, size, data); + riscv_iommu_write_reg_val(s, &s->regs[addr], addr, size, data); /* Busy flag update, MSB 4-byte register. */ if (busy) { - uint32_t rw = ldl_le_p(&s->regs_rw[regb]); - stl_le_p(&s->regs_rw[regb], rw | busy); + uint32_t rw = ldl_le_p(&s->regs[regb]); + stl_le_p(&s->regs[regb], rw | busy); } /* Process HPM writes and update any internal state if needed. */ @@ -2428,13 +2455,13 @@ static MemTxResult riscv_iommu_mmio_read(void *opaque, hwaddr addr, * it's not dependent over the timer callback and is computed * from cycle overflow. */ - val = ldq_le_p(&s->regs_rw[addr]); + val = ldq_le_p(&s->regs[addr]); val |= (riscv_iommu_hpmcycle_read(s) & RISCV_IOMMU_IOHPMCYCLES_OVF) ? RISCV_IOMMU_IOCOUNTOVF_CY : 0; ptr = (uint8_t *)&val + (addr & 3); } else { - ptr = &s->regs_rw[addr]; + ptr = &s->regs[addr]; } val = ldn_le_p(ptr, size); @@ -2529,7 +2556,7 @@ static void riscv_iommu_instance_init(Object *obj) s->cap |= RISCV_IOMMU_CAP_PD8; /* register storage */ - s->regs_rw = g_new0(uint8_t, RISCV_IOMMU_REG_SIZE); + s->regs = g_new0(uint8_t, RISCV_IOMMU_REG_SIZE); s->regs_ro = g_new0(uint8_t, RISCV_IOMMU_REG_SIZE); s->regs_wc = g_new0(uint8_t, RISCV_IOMMU_REG_SIZE); @@ -2554,7 +2581,7 @@ static void riscv_iommu_instance_finalize(Object *obj) { RISCVIOMMUState *s = RISCV_IOMMU(obj); - g_free(s->regs_rw); + g_free(s->regs); g_free(s->regs_ro); g_free(s->regs_wc); @@ -2608,10 +2635,12 @@ static void riscv_iommu_realize(DeviceState *dev, Error **errp) "riscv-iommu-regs", RISCV_IOMMU_REG_SIZE); /* Set power-on register state */ - stq_le_p(&s->regs_rw[RISCV_IOMMU_REG_CAP], s->cap); - stq_le_p(&s->regs_rw[RISCV_IOMMU_REG_FCTL], 0); + stq_le_p(&s->regs[RISCV_IOMMU_REG_CAP], s->cap); + + stq_le_p(&s->regs[RISCV_IOMMU_REG_FCTL], 0); stq_le_p(&s->regs_ro[RISCV_IOMMU_REG_FCTL], - ~(RISCV_IOMMU_FCTL_BE | RISCV_IOMMU_FCTL_WSI)); + ~(RISCV_IOMMU_FCTL_GXL | RISCV_IOMMU_FCTL_WSI)); + stq_le_p(&s->regs_ro[RISCV_IOMMU_REG_DDTP], ~(RISCV_IOMMU_DDTP_PPN | RISCV_IOMMU_DDTP_MODE)); stq_le_p(&s->regs_ro[RISCV_IOMMU_REG_CQB], @@ -2634,7 +2663,7 @@ static void riscv_iommu_realize(DeviceState *dev, Error **errp) RISCV_IOMMU_PQCSR_BUSY); stl_le_p(&s->regs_wc[RISCV_IOMMU_REG_IPSR], ~0); stl_le_p(&s->regs_ro[RISCV_IOMMU_REG_ICVEC], 0); - stq_le_p(&s->regs_rw[RISCV_IOMMU_REG_DDTP], s->ddtp); + stq_le_p(&s->regs[RISCV_IOMMU_REG_DDTP], s->ddtp); /* If debug registers enabled. */ if (s->cap & RISCV_IOMMU_CAP_DBG) { stq_le_p(&s->regs_ro[RISCV_IOMMU_REG_TR_REQ_IOVA], 0); diff --git a/hw/riscv/riscv-iommu.h b/hw/riscv/riscv-iommu.h index 2a9f6fccd5..a778e86fb7 100644 --- a/hw/riscv/riscv-iommu.h +++ b/hw/riscv/riscv-iommu.h @@ -76,9 +76,13 @@ struct RISCVIOMMUState { /* MMIO Hardware Interface */ MemoryRegion regs_mr; - uint8_t *regs_rw; /* register state (user write) */ + uint8_t *regs; /* current register state */ uint8_t *regs_wc; /* write-1-to-clear mask */ - uint8_t *regs_ro; /* read-only mask */ + /* + * read-only mask. NOTE: bits not present in this RO + * mask are assumed to be read and write. + */ + uint8_t *regs_ro; QLIST_ENTRY(RISCVIOMMUState) iommus; QLIST_HEAD(, RISCVIOMMUSpace) spaces; @@ -120,39 +124,39 @@ struct RISCVIOMMUContext { static inline uint32_t riscv_iommu_reg_mod32(RISCVIOMMUState *s, unsigned idx, uint32_t set, uint32_t clr) { - uint32_t val = ldl_le_p(s->regs_rw + idx); - stl_le_p(s->regs_rw + idx, (val & ~clr) | set); + uint32_t val = ldl_le_p(s->regs + idx); + stl_le_p(s->regs + idx, (val & ~clr) | set); return val; } static inline void riscv_iommu_reg_set32(RISCVIOMMUState *s, unsigned idx, uint32_t set) { - stl_le_p(s->regs_rw + idx, set); + stl_le_p(s->regs + idx, set); } static inline uint32_t riscv_iommu_reg_get32(RISCVIOMMUState *s, unsigned idx) { - return ldl_le_p(s->regs_rw + idx); + return ldl_le_p(s->regs + idx); } static inline uint64_t riscv_iommu_reg_mod64(RISCVIOMMUState *s, unsigned idx, uint64_t set, uint64_t clr) { - uint64_t val = ldq_le_p(s->regs_rw + idx); - stq_le_p(s->regs_rw + idx, (val & ~clr) | set); + uint64_t val = ldq_le_p(s->regs + idx); + stq_le_p(s->regs + idx, (val & ~clr) | set); return val; } static inline void riscv_iommu_reg_set64(RISCVIOMMUState *s, unsigned idx, uint64_t set) { - stq_le_p(s->regs_rw + idx, set); + stq_le_p(s->regs + idx, set); } static inline uint64_t riscv_iommu_reg_get64(RISCVIOMMUState *s, - unsigned idx) + unsigned idx) { - return ldq_le_p(s->regs_rw + idx); + return ldq_le_p(s->regs + idx); } #endif diff --git a/hw/riscv/shakti_c.c b/hw/riscv/shakti_c.c index b1823a3125..835b1f879b 100644 --- a/hw/riscv/shakti_c.c +++ b/hw/riscv/shakti_c.c @@ -46,6 +46,7 @@ static void shakti_c_machine_state_init(MachineState *mstate) { ShaktiCMachineState *sms = RISCV_SHAKTI_MACHINE(mstate); MemoryRegion *system_memory = get_system_memory(); + RISCVBootInfo boot_info; hwaddr firmware_load_addr = shakti_c_memmap[SHAKTI_C_RAM].base; /* Initialize SoC */ @@ -58,8 +59,11 @@ static void shakti_c_machine_state_init(MachineState *mstate) shakti_c_memmap[SHAKTI_C_RAM].base, mstate->ram); + riscv_boot_info_init(&boot_info, &sms->soc.cpus); + if (mstate->firmware) { - riscv_load_firmware(mstate->firmware, &firmware_load_addr, NULL); + riscv_load_firmware(mstate, &boot_info, mstate->firmware, + &firmware_load_addr, NULL); } /* ROM reset vector */ diff --git a/hw/riscv/sifive_u.c b/hw/riscv/sifive_u.c index 0c6e4204cb..99423ef472 100644 --- a/hw/riscv/sifive_u.c +++ b/hw/riscv/sifive_u.c @@ -100,14 +100,11 @@ static void create_fdt(SiFiveUState *s, const MemMapEntry *memmap, MachineState *ms = MACHINE(s); void *fdt; int cpu; - uint32_t *cells; + uint32_t *cells, cells_length; char *nodename; uint32_t plic_phandle, prci_phandle, gpio_phandle, phandle = 1; uint32_t hfclk_phandle, rtcclk_phandle, phy_phandle; static const char * const ethclk_names[2] = { "pclk", "hclk" }; - static const char * const plic_compat[2] = { - "sifive,plic-1.0.0", "riscv,plic0" - }; g_autofree uint32_t *intc_phandles = g_new0(uint32_t, ms->smp.cpus); g_autofree char *clust_name = NULL; @@ -197,41 +194,28 @@ static void create_fdt(SiFiveUState *s, const MemMapEntry *memmap, g_free(nodename); plic_phandle = phandle++; - cells = g_new0(uint32_t, ms->smp.cpus * 4 - 2); + cells_length = ms->smp.cpus * 4 - 2; + cells = g_new0(uint32_t, cells_length); for (cpu = 0; cpu < ms->smp.cpus; cpu++) { - nodename = - g_strdup_printf("/cpus/cpu@%d/interrupt-controller", cpu); - uint32_t intc_phandle = qemu_fdt_get_phandle(fdt, nodename); /* cpu 0 is the management hart that does not have S-mode */ if (cpu == 0) { - cells[0] = cpu_to_be32(intc_phandle); + cells[0] = cpu_to_be32(intc_phandles[cpu]); cells[1] = cpu_to_be32(IRQ_M_EXT); } else { - cells[cpu * 4 - 2] = cpu_to_be32(intc_phandle); + cells[cpu * 4 - 2] = cpu_to_be32(intc_phandles[cpu]); cells[cpu * 4 - 1] = cpu_to_be32(IRQ_M_EXT); - cells[cpu * 4 + 0] = cpu_to_be32(intc_phandle); + cells[cpu * 4 + 0] = cpu_to_be32(intc_phandles[cpu]); cells[cpu * 4 + 1] = cpu_to_be32(IRQ_S_EXT); } - g_free(nodename); } - nodename = g_strdup_printf("/soc/interrupt-controller@%lx", - (long)memmap[SIFIVE_U_DEV_PLIC].base); - qemu_fdt_add_subnode(fdt, nodename); - qemu_fdt_setprop_cell(fdt, nodename, "#interrupt-cells", 1); - qemu_fdt_setprop_string_array(fdt, nodename, "compatible", - (char **)&plic_compat, ARRAY_SIZE(plic_compat)); - qemu_fdt_setprop(fdt, nodename, "interrupt-controller", NULL, 0); - qemu_fdt_setprop(fdt, nodename, "interrupts-extended", - cells, (ms->smp.cpus * 4 - 2) * sizeof(uint32_t)); - qemu_fdt_setprop_cells(fdt, nodename, "reg", - 0x0, memmap[SIFIVE_U_DEV_PLIC].base, - 0x0, memmap[SIFIVE_U_DEV_PLIC].size); - qemu_fdt_setprop_cell(fdt, nodename, "riscv,ndev", - SIFIVE_U_PLIC_NUM_SOURCES - 1); - qemu_fdt_setprop_cell(fdt, nodename, "phandle", plic_phandle); - plic_phandle = qemu_fdt_get_phandle(fdt, nodename); + + create_fdt_plic(fdt, memmap[SIFIVE_U_DEV_PLIC].base, + memmap[SIFIVE_U_DEV_PLIC].size, + plic_phandle, SIFIVE_U_PLIC_INT_CELLS, + SIFIVE_U_PLIC_ADDR_CELLS, cells, + cells_length * sizeof(uint32_t), + SIFIVE_U_PLIC_NUM_SOURCES - 1, false, 0); g_free(cells); - g_free(nodename); gpio_phandle = phandle++; nodename = g_strdup_printf("/soc/gpio@%lx", @@ -540,11 +524,13 @@ static void sifive_u_machine_init(MachineState *machine) break; } + riscv_boot_info_init(&boot_info, &s->soc.u_cpus); + firmware_name = riscv_default_firmware_name(&s->soc.u_cpus); - firmware_end_addr = riscv_find_and_load_firmware(machine, firmware_name, + firmware_end_addr = riscv_find_and_load_firmware(machine, &boot_info, + firmware_name, &start_addr, NULL); - riscv_boot_info_init(&boot_info, &s->soc.u_cpus); if (machine->kernel_filename) { kernel_start_addr = riscv_calc_kernel_start_addr(&boot_info, firmware_end_addr); diff --git a/hw/riscv/spike.c b/hw/riscv/spike.c index 4e60724c14..9fde0faf39 100644 --- a/hw/riscv/spike.c +++ b/hw/riscv/spike.c @@ -205,9 +205,12 @@ static void spike_board_init(MachineState *machine) } } + riscv_boot_info_init(&boot_info, &s->soc[0]); + /* Load firmware */ if (firmware_name) { - firmware_end_addr = riscv_load_firmware(firmware_name, + firmware_end_addr = riscv_load_firmware(machine, &boot_info, + firmware_name, &firmware_load_addr, htif_symbol_callback); g_free(firmware_name); @@ -217,7 +220,6 @@ static void spike_board_init(MachineState *machine) create_fdt(s, memmap, riscv_is_32bit(&s->soc[0]), htif_custom_base); /* Load kernel */ - riscv_boot_info_init(&boot_info, &s->soc[0]); if (machine->kernel_filename) { kernel_start_addr = riscv_calc_kernel_start_addr(&boot_info, firmware_end_addr); diff --git a/hw/riscv/tt_atlantis.c b/hw/riscv/tt_atlantis.c new file mode 100644 index 0000000000..beaf64e0c1 --- /dev/null +++ b/hw/riscv/tt_atlantis.c @@ -0,0 +1,617 @@ +/* + * Tenstorrent Atlantis RISC-V System on Chip + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright 2025 Tenstorrent, Joel Stanley <joel@jms.id.au> + */ + +#include "qemu/osdep.h" +#include "qemu/cutils.h" +#include "qemu/error-report.h" +#include "qemu/guest-random.h" +#include "qemu/units.h" + +#include "hw/core/boards.h" +#include "hw/core/loader.h" +#include "hw/core/sysbus.h" + +#include "target/riscv/cpu.h" +#include "target/riscv/pmu.h" + +#include "hw/riscv/boot.h" +#include "hw/riscv/fdt-common.h" +#include "hw/riscv/machines-qom.h" +#include "hw/riscv/riscv_hart.h" + +#include "hw/char/serial-mm.h" +#include "hw/intc/riscv_aclint.h" +#include "hw/misc/unimp.h" + +#include "system/system.h" +#include "system/device_tree.h" + +#include "hw/riscv/tt_atlantis.h" + +#include "aia.h" + +#define TT_IRQCHIP_NUM_MSIS 255 +#define TT_IRQCHIP_NUM_SOURCES 128 +#define TT_IRQCHIP_NUM_PRIO_BITS 3 +#define TT_IRQCHIP_GUESTS 63 /* aia_guests, gives guest_index_bits=6 */ +#define TT_IRQCHIP_MIMSIC_STRIDE 0x40000 + +#define TT_ACLINT_MTIME_SIZE 0x8050 +#define TT_ACLINT_MTIME 0x0 +#define TT_ACLINT_MTIMECMP 0x8000 +#define TT_ACLINT_TIMEBASE_FREQ 1000000000 + +static const MemMapEntry tt_atlantis_memmap[] = { + /* Keep sorted with :'<,'>!sort -g -k 4 */ + [TT_ATL_DDR_LO] = { 0x00000000, 0x80000000 }, + [TT_ATL_BOOTROM] = { 0x80000000, 0x2000 }, + [TT_ATL_MIMSIC] = { 0xa0000000, 0x200000 }, + [TT_ATL_ACLINT] = { 0xa2180000, 0x10000 }, + [TT_ATL_SIMSIC] = { 0xa4000000, 0x200000 }, + [TT_ATL_MAPLIC] = { 0xcc000000, 0x4000000 }, + [TT_ATL_I2C0] = { 0xd4040000, 0x10000 }, + [TT_ATL_I2C1] = { 0xd4050000, 0x10000 }, + [TT_ATL_I2C2] = { 0xd4060000, 0x10000 }, + [TT_ATL_I2C3] = { 0xd4070000, 0x10000 }, + [TT_ATL_I2C4] = { 0xd4080000, 0x10000 }, + [TT_ATL_UART1] = { 0xd4110000, 0x10000 }, + [TT_ATL_SAPLIC] = { 0xe8000000, 0x4000000 }, + [TT_ATL_DDR_HI] = { 0x100000000, 0x1000000000 }, +}; + +static I2CBus *i2c_get_bus(TTAtlantisState *s, unsigned busnr) +{ + assert(busnr < TT_ATL_NUM_I2C); + + return s->i2c[busnr].bus; +} + +static uint32_t fdt_phandle = 1; +static uint32_t next_phandle(void) +{ + return fdt_phandle++; +} + +static void create_fdt_memory(TTAtlantisState *s) +{ + void *fdt = MACHINE(s)->fdt; + hwaddr size_lo = MACHINE(s)->ram_size; + hwaddr size_hi = 0; + + if (size_lo > s->memmap[TT_ATL_DDR_LO].size) { + size_lo = s->memmap[TT_ATL_DDR_LO].size; + size_hi = MACHINE(s)->ram_size - size_lo; + } + + create_fdt_socket_memory(fdt, s->memmap[TT_ATL_DDR_LO].base, size_lo, + 0, false); + if (size_hi) { + /* + * The first part of the HI address is aliased at the LO address + * so do not include that as usable memory. Is there any way + * (or good reason) to describe that aliasing 2GB with DT? + */ + create_fdt_socket_memory(fdt, s->memmap[TT_ATL_DDR_HI].base + size_lo, + size_hi, 0, false); + } +} + +static void create_fdt_aclint(TTAtlantisState *s, uint32_t *intc_phandles) +{ + void *fdt = MACHINE(s)->fdt; + g_autofree char *name = NULL; + g_autofree uint32_t *aclint_mtimer_cells = NULL; + uint32_t aclint_cells_size; + hwaddr addr; + + aclint_mtimer_cells = g_new0(uint32_t, s->soc.num_harts * 2); + + for (int cpu = 0; cpu < s->soc.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.num_harts * sizeof(uint32_t) * 2; + + addr = s->memmap[TT_ATL_ACLINT].base; + + name = g_strdup_printf("/soc/mtimer@%"HWADDR_PRIX, addr); + qemu_fdt_add_subnode(fdt, name); + qemu_fdt_setprop_string(fdt, name, "compatible", "riscv,aclint-mtimer"); + qemu_fdt_setprop_sized_cells(fdt, name, "reg", + 2, addr + TT_ACLINT_MTIME, + 2, 0x1000, + 2, addr + TT_ACLINT_MTIMECMP, + 2, 0x1000); + qemu_fdt_setprop(fdt, name, "interrupts-extended", + aclint_mtimer_cells, aclint_cells_size); +} + +static void create_fdt_one_imsic(void *fdt, const MemMapEntry *mem, int cpus, + uint32_t *intc_phandles, uint32_t msi_phandle, + int irq_line, uint32_t imsic_guest_bits) +{ + g_autofree char *name = NULL; + g_autofree uint32_t *imsic_cells = g_new0(uint32_t, cpus * 2); + + for (int cpu = 0; cpu < cpus; cpu++) { + imsic_cells[cpu * 2 + 0] = cpu_to_be32(intc_phandles[cpu]); + imsic_cells[cpu * 2 + 1] = cpu_to_be32(irq_line); + } + + name = g_strdup_printf("/soc/interrupt-controller@%"HWADDR_PRIX, mem->base); + qemu_fdt_add_subnode(fdt, name); + qemu_fdt_setprop_string(fdt, name, "compatible", "riscv,imsics"); + + qemu_fdt_setprop_cell(fdt, name, "#interrupt-cells", 0); + qemu_fdt_setprop(fdt, name, "interrupt-controller", NULL, 0); + qemu_fdt_setprop(fdt, name, "msi-controller", NULL, 0); + qemu_fdt_setprop(fdt, name, "interrupts-extended", + imsic_cells, sizeof(uint32_t) * cpus * 2); + qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size); + qemu_fdt_setprop_cell(fdt, name, "riscv,num-ids", TT_IRQCHIP_NUM_MSIS); + + if (imsic_guest_bits) { + qemu_fdt_setprop_cell(fdt, name, "riscv,guest-index-bits", + imsic_guest_bits); + } + qemu_fdt_setprop_cell(fdt, name, "phandle", msi_phandle); +} + +static void create_fdt_one_aplic(void *fdt, + const MemMapEntry *mem, + uint32_t msi_phandle, + uint32_t *intc_phandles, + uint32_t aplic_phandle, + uint32_t aplic_child_phandle, + int irq_line, int num_harts) +{ + g_autofree char *name = + g_strdup_printf("/soc/interrupt-controller@%"HWADDR_PRIX, mem->base); + g_autofree uint32_t *aplic_cells = g_new0(uint32_t, num_harts * 2); + + for (int 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(irq_line); + } + + qemu_fdt_add_subnode(fdt, name); + qemu_fdt_setprop_string(fdt, name, "compatible", "riscv,aplic"); + qemu_fdt_setprop_cell(fdt, name, "#address-cells", 0); + qemu_fdt_setprop_cell(fdt, name, "#interrupt-cells", 2); + qemu_fdt_setprop(fdt, name, "interrupt-controller", NULL, 0); + + qemu_fdt_setprop(fdt, name, "interrupts-extended", + aplic_cells, num_harts * sizeof(uint32_t) * 2); + qemu_fdt_setprop_cell(fdt, name, "msi-parent", msi_phandle); + + qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size); + qemu_fdt_setprop_cell(fdt, name, "riscv,num-sources", + TT_IRQCHIP_NUM_SOURCES); + + if (aplic_child_phandle) { + qemu_fdt_setprop_cell(fdt, name, "riscv,children", + aplic_child_phandle); + qemu_fdt_setprop_cells(fdt, name, "riscv,delegation", + aplic_child_phandle, 1, TT_IRQCHIP_NUM_SOURCES); + } + + qemu_fdt_setprop_cell(fdt, name, "phandle", aplic_phandle); +} + +static void create_fdt_pmu(TTAtlantisState *s) +{ + char pmu_name[] = "/pmu"; + void *fdt = MACHINE(s)->fdt; + RISCVCPU *hart = &s->soc.harts[0]; + + qemu_fdt_add_subnode(fdt, pmu_name); + qemu_fdt_setprop_string(fdt, pmu_name, "compatible", "riscv,pmu"); + riscv_pmu_generate_fdt_node(fdt, hart->pmu_avail_ctrs, pmu_name); +} + +static void create_fdt_cpu(TTAtlantisState *s, const MemMapEntry *memmap, + uint32_t aplic_s_phandle, + uint32_t imsic_s_phandle) +{ + MachineState *ms = MACHINE(s); + void *fdt = MACHINE(s)->fdt; + g_autofree uint32_t *intc_phandles = g_new0(uint32_t, ms->smp.cpus); + + fdt_create_cpu_socket_subnode(fdt, TT_ACLINT_TIMEBASE_FREQ); + + create_fdt_socket_cpus(fdt, s->soc.harts, 0, s->soc.num_harts, + s->soc.hartid_base, &fdt_phandle, intc_phandles, + false, false); + + create_fdt_memory(s); + + create_fdt_aclint(s, intc_phandles); + + uint32_t imsic_guest_bits = imsic_num_bits(TT_IRQCHIP_GUESTS + 1); + + /* M-level IMSIC node */ + uint32_t msi_m_phandle = next_phandle(); + create_fdt_one_imsic(fdt, &s->memmap[TT_ATL_MIMSIC], ms->smp.cpus, + intc_phandles, msi_m_phandle, + IRQ_M_EXT, imsic_guest_bits); + + /* S-level IMSIC node */ + create_fdt_one_imsic(fdt, &s->memmap[TT_ATL_SIMSIC], ms->smp.cpus, + intc_phandles, imsic_s_phandle, + IRQ_S_EXT, imsic_guest_bits); + + uint32_t aplic_m_phandle = next_phandle(); + + /* M-level APLIC node */ + create_fdt_one_aplic(fdt, &s->memmap[TT_ATL_MAPLIC], + msi_m_phandle, intc_phandles, + aplic_m_phandle, aplic_s_phandle, + IRQ_M_EXT, s->soc.num_harts); + + /* S-level APLIC node */ + create_fdt_one_aplic(fdt, &s->memmap[TT_ATL_SAPLIC], + imsic_s_phandle, intc_phandles, + aplic_s_phandle, 0, + IRQ_S_EXT, s->soc.num_harts); +} + +static void create_fdt_uart(void *fdt, const MemMapEntry *mem, int irq, + int irqchip_phandle) +{ + g_autofree char *name = g_strdup_printf("/soc/serial@%"HWADDR_PRIX, + mem->base); + + qemu_fdt_add_subnode(fdt, name); + qemu_fdt_setprop_string(fdt, name, "compatible", "ns16550a"); + qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size); + qemu_fdt_setprop_cell(fdt, name, "reg-shift", 2); + qemu_fdt_setprop_cell(fdt, name, "reg-io-width", 4); + qemu_fdt_setprop_cell(fdt, name, "clock-frequency", 3686400); + qemu_fdt_setprop_cell(fdt, name, "interrupt-parent", irqchip_phandle); + qemu_fdt_setprop_cells(fdt, name, "interrupts", irq, 0x4); + + qemu_fdt_setprop_string(fdt, "/chosen", "stdout-path", name); + qemu_fdt_setprop_string(fdt, "/aliases", "serial0", name); +} + +static void create_fdt_rng(void *fdt) +{ + uint8_t rng_seed[32]; + + qemu_guest_getrandom_nofail(rng_seed, sizeof(rng_seed)); + qemu_fdt_setprop(fdt, "/chosen", "rng-seed", rng_seed, sizeof(rng_seed)); +} + +static void create_fdt_clk(void *fdt, const char *clock_name, + uint32_t freq, uint32_t phandle) +{ + g_autofree char *name = g_strdup_printf("/clocks/%s", clock_name); + + qemu_fdt_add_path(fdt, name); + qemu_fdt_setprop_string(fdt, name, "compatible", "fixed-clock"); + qemu_fdt_setprop_string(fdt, name, "clock-output-names", clock_name); + qemu_fdt_setprop_cell(fdt, name, "#clock-cells", 0); + qemu_fdt_setprop_cell(fdt, name, "clock-frequency", freq); + qemu_fdt_setprop_cell(fdt, name, "phandle", phandle); +} + +static void create_fdt_i2c(void *fdt, const MemMapEntry *mem, uint32_t irq, + uint32_t irqchip_phandle, uint32_t clk_phandle) +{ + g_autofree char *name = g_strdup_printf("/soc/i2c@%"HWADDR_PRIX, mem->base); + + qemu_fdt_add_subnode(fdt, name); + qemu_fdt_setprop_string(fdt, name, "compatible", "snps,designware-i2c"); + qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size); + qemu_fdt_setprop_cell(fdt, name, "interrupt-parent", irqchip_phandle); + qemu_fdt_setprop_cells(fdt, name, "interrupts", irq, 0x4); + qemu_fdt_setprop_cell(fdt, name, "clocks", clk_phandle); + qemu_fdt_setprop_cell(fdt, name, "clock-frequency", 100000); + qemu_fdt_setprop_cell(fdt, name, "#address-cells", 1); + qemu_fdt_setprop_cell(fdt, name, "#size-cells", 0); +} + +static void create_fdt_i2c_device(TTAtlantisState *s, int bus, + const char *compat, int addr) +{ + void *fdt = MACHINE(s)->fdt; + hwaddr base = s->memmap[TT_ATL_I2C0 + bus].base; + g_autofree char *name = g_strdup_printf("/soc/i2c@%"HWADDR_PRIX"/sensor@%x", + base, addr); + + qemu_fdt_add_subnode(fdt, name); + qemu_fdt_setprop_string(fdt, name, "compatible", compat); + qemu_fdt_setprop_cell(fdt, name, "reg", addr); +} + +static void finalize_fdt(TTAtlantisState *s) +{ + uint32_t aplic_s_phandle = next_phandle(); + uint32_t imsic_s_phandle = next_phandle(); + uint32_t periph_clk_phandle = next_phandle(); + void *fdt = MACHINE(s)->fdt; + + create_fdt_cpu(s, s->memmap, aplic_s_phandle, imsic_s_phandle); + + /* + * We want to do this, but the Linux aplic driver was broken before v6.16 + * + * qemu_fdt_setprop_cell(MACHINE(s)->fdt, "/soc", "interrupt-parent", + * aplic_s_phandle); + */ + + create_fdt_uart(fdt, &s->memmap[TT_ATL_UART1], TT_ATL_UART1_IRQ, + aplic_s_phandle); + + create_fdt_clk(fdt, "periph-clk", 100000000, periph_clk_phandle); + + for (int i = 0; i < TT_ATL_NUM_I2C; i++) { + create_fdt_i2c(fdt, + &s->memmap[TT_ATL_I2C0 + i], + TT_ATL_I2C0_IRQ + i, + aplic_s_phandle, periph_clk_phandle); + } + + create_fdt_i2c_device(s, 0, "dallas,ds1338", 0x6f); + create_fdt_i2c_device(s, 4, "ti,tmp105", 0x48); +} + +static void create_fdt(TTAtlantisState *s) +{ + MachineState *ms = MACHINE(s); + + ms->fdt = create_board_device_tree("Tenstorrent Atlantis RISC-V Machine", + "tenstorrent,atlantis", &s->fdt_size); + + qemu_fdt_add_subnode(ms->fdt, "/chosen"); + + create_fdt_rng(ms->fdt); + + qemu_fdt_add_subnode(ms->fdt, "/aliases"); + + create_fdt_pmu(s); +} + +static void load_fdt(TTAtlantisState *s) +{ + MachineState *ms = MACHINE(s); + char **node_path; + Error *err = NULL; + + ms->fdt = load_device_tree(ms->dtb, &s->fdt_size); + if (!ms->fdt) { + error_report("load_device_tree() failed"); + exit(1); + } + + qemu_fdt_add_path(ms->fdt, "/chosen"); + + /* Clear memory nodes and update with the specified RAM size */ + node_path = qemu_fdt_node_unit_path(ms->fdt, "memory", &err); + if (err) { + warn_report_err(err); + } else { + for (int i = 0; node_path[i]; i++) { + warn_report("Replacing device tree %s with the requested RAM size", + node_path[i]); + qemu_fdt_nop_node(ms->fdt, node_path[i]); + } + g_strfreev(node_path); + } + + create_fdt_memory(s); +} + +static void tt_atlantis_machine_done(Notifier *notifier, void *data) +{ + TTAtlantisState *s = container_of(notifier, TTAtlantisState, machine_done); + MachineState *machine = MACHINE(s); + hwaddr start_addr = s->memmap[TT_ATL_DDR_LO].base; + hwaddr mem_size; + target_ulong firmware_end_addr, kernel_start_addr; + const char *firmware_name = riscv_default_firmware_name(&s->soc); + uint64_t fdt_load_addr; + uint64_t kernel_entry; + RISCVBootInfo boot_info; + + /* + * A user provided dtb must include everything, including + * dynamic sysbus devices. Our FDT needs to be finalized. + */ + if (machine->dtb == NULL) { + finalize_fdt(s); + } + + mem_size = machine->ram_size; + if (mem_size > s->memmap[TT_ATL_DDR_LO].size) { + mem_size = s->memmap[TT_ATL_DDR_LO].size; + } + riscv_boot_info_init_discontig_mem(&boot_info, &s->soc, + s->memmap[TT_ATL_DDR_LO].base, + mem_size); + + firmware_end_addr = riscv_find_and_load_firmware(machine, &boot_info, + firmware_name, + &start_addr, NULL); + + kernel_start_addr = riscv_calc_kernel_start_addr(&boot_info, + firmware_end_addr); + if (machine->kernel_filename) { + riscv_load_kernel(machine, &boot_info, kernel_start_addr, + true, NULL); + kernel_entry = boot_info.image_low_addr; + } else { + /* If we aren't loading a payload, OpenSBI thinks we are trying to boot + * address 0, which fails `sbi_domain_check_addr()` as that is where + * OpenSBI is running. Instead point OpenSBI to the end of the region + * where it was loaded, which avoids the early hang, allowing the + * system to proceed with the OpenSBI boot output. + */ + kernel_entry = kernel_start_addr; + } + + fdt_load_addr = riscv_compute_fdt_addr(s->memmap[TT_ATL_DDR_LO].base, + s->memmap[TT_ATL_DDR_LO].size, + machine, &boot_info); + riscv_load_fdt(fdt_load_addr, machine->fdt); + + /* load the reset vector */ + riscv_setup_rom_reset_vec(machine, &s->soc, start_addr, + s->memmap[TT_ATL_BOOTROM].base, + s->memmap[TT_ATL_BOOTROM].size, + kernel_entry, + fdt_load_addr); +} + +static void tt_atlantis_machine_init(MachineState *machine) +{ + TTAtlantisState *s = TT_ATLANTIS_MACHINE(machine); + + MemoryRegion *system_memory = get_system_memory(); + MemoryRegion *ram_hi = g_new(MemoryRegion, 1); + MemoryRegion *ram_lo = g_new(MemoryRegion, 1); + MemoryRegion *bootrom = g_new(MemoryRegion, 1); + ram_addr_t lo_ram_size; + int hart_count = machine->smp.cpus; + + s->memmap = tt_atlantis_memmap; + + object_initialize_child(OBJECT(machine), "soc", &s->soc, + TYPE_RISCV_HART_ARRAY); + object_property_set_str(OBJECT(&s->soc), "cpu-type", machine->cpu_type, + &error_abort); + object_property_set_int(OBJECT(&s->soc), "hartid-base", 0, + &error_abort); + object_property_set_int(OBJECT(&s->soc), "num-harts", hart_count, + &error_abort); + object_property_set_int(OBJECT(&s->soc), "resetvec", + s->memmap[TT_ATL_BOOTROM].base, + &error_abort); + sysbus_realize(SYS_BUS_DEVICE(&s->soc), &error_fatal); + + s->irqchip = riscv_create_aia(true, TT_IRQCHIP_GUESTS, + TT_IRQCHIP_MIMSIC_STRIDE, + TT_IRQCHIP_NUM_SOURCES, + &s->memmap[TT_ATL_MAPLIC], + &s->memmap[TT_ATL_SAPLIC], + &s->memmap[TT_ATL_MIMSIC], + &s->memmap[TT_ATL_SIMSIC], + 0, 0, hart_count, + TT_IRQCHIP_NUM_MSIS, + TT_IRQCHIP_NUM_PRIO_BITS); + + riscv_aclint_mtimer_create(s->memmap[TT_ATL_ACLINT].base, + TT_ACLINT_MTIME_SIZE, + 0, hart_count, + TT_ACLINT_MTIMECMP, + TT_ACLINT_MTIME, + TT_ACLINT_TIMEBASE_FREQ, true); + + /* + * DDR + * + * The high address is where RAM lives. It is always present and may be + * up to 64GB. The low address is an alias of the first 2GB of that RAM. + */ + if (machine->ram_size > s->memmap[TT_ATL_DDR_HI].size) { + char *sz = size_to_str(s->memmap[TT_ATL_DDR_HI].size); + error_report("RAM size is too large, maximum is %s", sz); + g_free(sz); + exit(EXIT_FAILURE); + } + + memory_region_init_alias(ram_hi, OBJECT(machine), "ram.high", machine->ram, + 0, machine->ram_size); + memory_region_add_subregion(system_memory, + s->memmap[TT_ATL_DDR_HI].base, ram_hi); + + lo_ram_size = MIN(machine->ram_size, s->memmap[TT_ATL_DDR_LO].size); + memory_region_init_alias(ram_lo, OBJECT(machine), "ram.low", machine->ram, + 0, lo_ram_size); + memory_region_add_subregion(system_memory, + s->memmap[TT_ATL_DDR_LO].base, ram_lo); + + /* Boot ROM */ + memory_region_init_rom(bootrom, NULL, "tt-atlantis.bootrom", + s->memmap[TT_ATL_BOOTROM].size, &error_fatal); + memory_region_add_subregion(system_memory, s->memmap[TT_ATL_BOOTROM].base, + bootrom); + + /* UART1, the soc console (UART0 is for the boot microcontroller) */ + serial_mm_init(system_memory, s->memmap[TT_ATL_UART1].base, 2, + qdev_get_gpio_in(s->irqchip, TT_ATL_UART1_IRQ), + 115200, serial_hd(0), DEVICE_LITTLE_ENDIAN); + /* + * Atlantis contains a DesignWare uart while the QEMU machine + * uses the serial_mm model with the base ns16550 register set. + * Linux's dw driver writes outside of serial_mm's 0x20 sized + * mapping and faults. + * + * Create an unimplemented device region so writes don't fault + * and reads return zero, which keeps Linux happy. + */ + create_unimplemented_device("tt-atlantis.uart0", + s->memmap[TT_ATL_UART1].base, + s->memmap[TT_ATL_UART1].size); + + /* I2C */ + for (int i = 0; i < TT_ATL_NUM_I2C; i++) { + SysBusDevice *sbd; + + object_initialize_child(OBJECT(s), "i2c[*]", &s->i2c[i], + TYPE_DESIGNWARE_I2C); + sbd = SYS_BUS_DEVICE(&s->i2c[i]); + sysbus_realize(sbd, &error_fatal); + memory_region_add_subregion(system_memory, + s->memmap[TT_ATL_I2C0 + i].base, + sysbus_mmio_get_region(sbd, 0)); + sysbus_connect_irq(sbd, 0, + qdev_get_gpio_in(s->irqchip, TT_ATL_I2C0_IRQ + i)); + } + + /* I2C peripherals: qemu specific */ + i2c_slave_create_simple(i2c_get_bus(s, 0), "ds1338", 0x6f); + i2c_slave_create_simple(i2c_get_bus(s, 4), "tmp105", 0x48); + + /* Load or create device tree */ + if (machine->dtb) { + load_fdt(s); + } else { + create_fdt(s); + } + + s->machine_done.notify = tt_atlantis_machine_done; + qemu_add_machine_init_done_notifier(&s->machine_done); +} + +static void tt_atlantis_machine_class_init(ObjectClass *oc, const void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + + mc->desc = "Tenstorrent Atlantis RISC-V SoC (Experimental)"; + mc->init = tt_atlantis_machine_init; + mc->max_cpus = 8; + mc->default_cpus = 8; + mc->default_ram_size = 4 * GiB; + mc->default_cpu_type = TYPE_RISCV_CPU_TT_ASCALON; + mc->block_default_type = IF_VIRTIO; + mc->no_cdrom = 1; + mc->default_ram_id = "tt_atlantis.ram"; +} + +static const TypeInfo tt_atlantis_types[] = { + { + .name = MACHINE_TYPE_NAME("tt-atlantis"), + .parent = TYPE_MACHINE, + .class_init = tt_atlantis_machine_class_init, + .instance_size = sizeof(TTAtlantisState), + .interfaces = riscv64_machine_interfaces, + }, +}; + +DEFINE_TYPES(tt_atlantis_types) diff --git a/hw/riscv/virt-acpi-build.c b/hw/riscv/virt-acpi-build.c index 413d47d70e..59c454f4f9 100644 --- a/hw/riscv/virt-acpi-build.c +++ b/hw/riscv/virt-acpi-build.c @@ -42,6 +42,8 @@ #include "system/kvm.h" #include "system/reset.h" +#include "aia.h" + #define ACPI_BUILD_TABLE_SIZE 0x20000 #define ACPI_BUILD_INTC_ID(socket, index) ((socket << 24) | (index)) @@ -100,6 +102,8 @@ static void riscv_acpi_madt_add_rintc(uint32_t uid, build_append_int_noprefix(entry, ACPI_BUILD_INTC_ID( arch_ids->cpus[uid].props.node_id, + kvm_enabled() ? + local_cpu_id : 2 * local_cpu_id + 1), 4); } else { @@ -144,6 +148,7 @@ static void acpi_dsdt_add_cpus(Aml *scope, RISCVVirtState *s) } static void acpi_dsdt_add_plic_aplic(Aml *scope, uint8_t socket_count, + uint16_t num_sources, uint64_t mmio_base, uint64_t mmio_size, const char *hid) { @@ -151,9 +156,12 @@ static void acpi_dsdt_add_plic_aplic(Aml *scope, uint8_t socket_count, uint32_t gsi_base; uint8_t socket; + /* The RISC-V Advanced Interrupt Architecture, Chapter 1.2. Limits */ + g_assert(num_sources <= 1023); + for (socket = 0; socket < socket_count; socket++) { plic_aplic_addr = mmio_base + mmio_size * socket; - gsi_base = VIRT_IRQCHIP_NUM_SOURCES * socket; + gsi_base = num_sources * socket; Aml *dev = aml_device("IC%.02X", socket); aml_append(dev, aml_name_decl("_HID", aml_string("%s", hid))); aml_append(dev, aml_name_decl("_UID", aml_int(socket))); @@ -472,10 +480,13 @@ static void build_dsdt(GArray *table_data, socket_count = riscv_socket_count(ms); if (s->aia_type == VIRT_AIA_TYPE_NONE) { - acpi_dsdt_add_plic_aplic(scope, socket_count, memmap[VIRT_PLIC].base, - memmap[VIRT_PLIC].size, "RSCV0001"); + acpi_dsdt_add_plic_aplic(scope, socket_count, s->num_sources, + memmap[VIRT_PLIC].base, + memmap[VIRT_PLIC].size, + "RSCV0001"); } else { - acpi_dsdt_add_plic_aplic(scope, socket_count, memmap[VIRT_APLIC_S].base, + acpi_dsdt_add_plic_aplic(scope, socket_count, s->num_sources, + memmap[VIRT_APLIC_S].base, memmap[VIRT_APLIC_S].size, "RSCV0002"); } @@ -492,15 +503,15 @@ static void build_dsdt(GArray *table_data, } else if (socket_count == 2) { virtio_acpi_dsdt_add(scope, memmap[VIRT_VIRTIO].base, memmap[VIRT_VIRTIO].size, - VIRTIO_IRQ + VIRT_IRQCHIP_NUM_SOURCES, 0, + VIRTIO_IRQ + s->num_sources, 0, VIRTIO_COUNT); - acpi_dsdt_add_gpex_host(scope, PCIE_IRQ + VIRT_IRQCHIP_NUM_SOURCES); + acpi_dsdt_add_gpex_host(scope, PCIE_IRQ + s->num_sources); } else { virtio_acpi_dsdt_add(scope, memmap[VIRT_VIRTIO].base, memmap[VIRT_VIRTIO].size, - VIRTIO_IRQ + VIRT_IRQCHIP_NUM_SOURCES, 0, + VIRTIO_IRQ + s->num_sources, 0, VIRTIO_COUNT); - acpi_dsdt_add_gpex_host(scope, PCIE_IRQ + VIRT_IRQCHIP_NUM_SOURCES * 2); + acpi_dsdt_add_gpex_host(scope, PCIE_IRQ + s->num_sources * 2); } aml_append(dsdt, scope); @@ -579,7 +590,7 @@ static void build_madt(GArray *table_data, for (socket = 0; socket < riscv_socket_count(ms); socket++) { aplic_addr = s->memmap[VIRT_APLIC_S].base + s->memmap[VIRT_APLIC_S].size * socket; - gsi_base = VIRT_IRQCHIP_NUM_SOURCES * socket; + gsi_base = s->num_sources * socket; build_append_int_noprefix(table_data, 0x1A, 1); /* Type */ build_append_int_noprefix(table_data, 36, 1); /* Length */ build_append_int_noprefix(table_data, 1, 1); /* Version */ diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c index 33775a61fd..b68067cfdd 100644 --- a/hw/riscv/virt.c +++ b/hw/riscv/virt.c @@ -61,6 +61,8 @@ #include "hw/virtio/virtio-iommu.h" #include "hw/uefi/var-service-api.h" +#include "aia.h" + /* KVM AIA only supports APLIC MSI. APLIC Wired is always emulated by QEMU. */ static bool virt_use_kvm_aia_aplic_imsic(RISCVVirtAIAType aia_type) { @@ -326,39 +328,25 @@ static void create_fdt_socket_plic(RISCVVirtState *s, int cpu; g_autofree char *plic_name = NULL; g_autofree uint32_t *plic_cells; - unsigned long plic_addr; MachineState *ms = MACHINE(s); - static const char * const plic_compat[2] = { - "sifive,plic-1.0.0", "riscv,plic0" - }; + unsigned long plic_addr = s->memmap[VIRT_PLIC].base + + (s->memmap[VIRT_PLIC].size * socket); + bool numa_enabled = riscv_numa_enabled(MACHINE(s)); + uint32_t cells_length; - plic_phandles[socket] = (*phandle)++; - plic_addr = s->memmap[VIRT_PLIC].base + - (s->memmap[VIRT_PLIC].size * socket); - plic_name = g_strdup_printf("/soc/plic@%lx", plic_addr); - qemu_fdt_add_subnode(ms->fdt, plic_name); - qemu_fdt_setprop_cell(ms->fdt, plic_name, - "#interrupt-cells", FDT_PLIC_INT_CELLS); - qemu_fdt_setprop_cell(ms->fdt, plic_name, - "#address-cells", FDT_PLIC_ADDR_CELLS); - qemu_fdt_setprop_string_array(ms->fdt, plic_name, "compatible", - (char **)&plic_compat, - ARRAY_SIZE(plic_compat)); - qemu_fdt_setprop(ms->fdt, plic_name, "interrupt-controller", NULL, 0); + plic_name = g_strdup_printf("/soc/interrupt-controller@%lx", plic_addr); if (kvm_enabled()) { - plic_cells = g_new0(uint32_t, s->soc[socket].num_harts * 2); + cells_length = s->soc[socket].num_harts * 2; + plic_cells = g_new0(uint32_t, cells_length); for (cpu = 0; cpu < s->soc[socket].num_harts; cpu++) { plic_cells[cpu * 2 + 0] = cpu_to_be32(intc_phandles[cpu]); plic_cells[cpu * 2 + 1] = cpu_to_be32(IRQ_S_EXT); } - - qemu_fdt_setprop(ms->fdt, plic_name, "interrupts-extended", - plic_cells, - s->soc[socket].num_harts * sizeof(uint32_t) * 2); } else { - plic_cells = g_new0(uint32_t, s->soc[socket].num_harts * 4); + cells_length = s->soc[socket].num_harts * 4; + plic_cells = g_new0(uint32_t, cells_length); for (cpu = 0; cpu < s->soc[socket].num_harts; cpu++) { plic_cells[cpu * 4 + 0] = cpu_to_be32(intc_phandles[cpu]); @@ -366,19 +354,16 @@ static void create_fdt_socket_plic(RISCVVirtState *s, plic_cells[cpu * 4 + 2] = cpu_to_be32(intc_phandles[cpu]); plic_cells[cpu * 4 + 3] = cpu_to_be32(IRQ_S_EXT); } - - qemu_fdt_setprop(ms->fdt, plic_name, "interrupts-extended", - plic_cells, - s->soc[socket].num_harts * sizeof(uint32_t) * 4); } - qemu_fdt_setprop_sized_cells(ms->fdt, plic_name, "reg", - 2, plic_addr, 2, s->memmap[VIRT_PLIC].size); - qemu_fdt_setprop_cell(ms->fdt, plic_name, "riscv,ndev", - VIRT_IRQCHIP_NUM_SOURCES - 1); - riscv_socket_fdt_write_id(ms, plic_name, socket); - qemu_fdt_setprop_cell(ms->fdt, plic_name, "phandle", - plic_phandles[socket]); + plic_phandles[socket] = (*phandle)++; + + create_fdt_plic(ms->fdt, plic_addr, s->memmap[VIRT_PLIC].size, + plic_phandles[socket], FDT_PLIC_INT_CELLS, + FDT_PLIC_ADDR_CELLS, plic_cells, + cells_length * sizeof(uint32_t), + VIRT_IRQCHIP_NUM_SOURCES - 1, + numa_enabled, socket); if (!socket) { platform_bus_add_all_fdt_nodes(ms->fdt, plic_name, @@ -388,17 +373,6 @@ static void create_fdt_socket_plic(RISCVVirtState *s, } } -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(RISCVVirtState *s, hwaddr base_addr, uint32_t *intc_phandles, uint32_t msi_phandle, bool m_mode, uint32_t imsic_guest_bits) @@ -1163,68 +1137,6 @@ static DeviceState *virt_create_plic(const MemMapEntry *memmap, int socket, memmap[VIRT_PLIC].size); } -static DeviceState *virt_create_aia(RISCVVirtAIAType aia_type, int aia_guests, - const MemMapEntry *memmap, int socket, - int base_hartid, int hart_count) -{ - int i; - hwaddr addr = 0; - uint32_t guest_bits; - DeviceState *aplic_s = NULL; - DeviceState *aplic_m = NULL; - bool msimode = aia_type == VIRT_AIA_TYPE_APLIC_IMSIC; - - if (msimode) { - if (!kvm_enabled()) { - /* Per-socket M-level IMSICs */ - addr = memmap[VIRT_IMSIC_M].base + - socket * VIRT_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, - VIRT_IRQCHIP_NUM_MSIS); - } - } - - /* Per-socket S-level IMSICs */ - guest_bits = imsic_num_bits(aia_guests + 1); - addr = memmap[VIRT_IMSIC_S].base + socket * VIRT_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, - VIRT_IRQCHIP_NUM_MSIS); - } - } - - if (!kvm_enabled()) { - /* Per-socket M-level APLIC */ - aplic_m = riscv_aplic_create(memmap[VIRT_APLIC_M].base + - socket * memmap[VIRT_APLIC_M].size, - memmap[VIRT_APLIC_M].size, - (msimode) ? 0 : base_hartid, - (msimode) ? 0 : hart_count, - VIRT_IRQCHIP_NUM_SOURCES, - VIRT_IRQCHIP_NUM_PRIO_BITS, - msimode, true, NULL); - } - - /* Per-socket S-level APLIC */ - aplic_s = riscv_aplic_create(memmap[VIRT_APLIC_S].base + - socket * memmap[VIRT_APLIC_S].size, - memmap[VIRT_APLIC_S].size, - (msimode) ? 0 : base_hartid, - (msimode) ? 0 : hart_count, - VIRT_IRQCHIP_NUM_SOURCES, - VIRT_IRQCHIP_NUM_PRIO_BITS, - msimode, false, aplic_m); - - if (kvm_enabled() && msimode) { - riscv_aplic_set_kvm_msicfgaddr(RISCV_APLIC(aplic_s), addr); - } - - return kvm_enabled() ? aplic_s : aplic_m; -} - static void create_platform_bus(RISCVVirtState *s, DeviceState *irqchip) { DeviceState *dev; @@ -1327,7 +1239,10 @@ static void virt_machine_done(Notifier *notifier, void *data) } } - firmware_end_addr = riscv_find_and_load_firmware(machine, firmware_name, + 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]); @@ -1350,8 +1265,6 @@ static void virt_machine_done(Notifier *notifier, void *data) } } - riscv_boot_info_init(&boot_info, &s->soc[0]); - if (machine->kernel_filename && !kernel_entry) { kernel_start_addr = riscv_calc_kernel_start_addr(&boot_info, firmware_end_addr); @@ -1486,9 +1399,17 @@ static void virt_machine_init(MachineState *machine) s->irqchip[i] = virt_create_plic(s->memmap, i, base_hartid, hart_count); } else { - s->irqchip[i] = virt_create_aia(s->aia_type, s->aia_guests, - s->memmap, i, base_hartid, - hart_count); + s->irqchip[i] = riscv_create_aia(s->aia_type == VIRT_AIA_TYPE_APLIC_IMSIC, + s->aia_guests, + IMSIC_HART_SIZE(0), + s->num_sources, + &s->memmap[VIRT_APLIC_M], + &s->memmap[VIRT_APLIC_S], + &s->memmap[VIRT_IMSIC_M], + &s->memmap[VIRT_IMSIC_S], + i, base_hartid, hart_count, + VIRT_IRQCHIP_NUM_MSIS, + VIRT_IRQCHIP_NUM_PRIO_BITS); } /* Try to use different IRQCHIP instance based device type */ @@ -1638,6 +1559,7 @@ static void virt_machine_instance_init(Object *obj) s->oem_table_id = g_strndup(ACPI_BUILD_APPNAME8, 8); s->acpi = ON_OFF_AUTO_AUTO; s->iommu_sys = ON_OFF_AUTO_AUTO; + s->num_sources = VIRT_IRQCHIP_NUM_SOURCES; } static char *virt_get_aia_guests(Object *obj, Error **errp) diff --git a/hw/riscv/xiangshan_kmh.c b/hw/riscv/xiangshan_kmh.c index 76417ba7ab..384624d69a 100644 --- a/hw/riscv/xiangshan_kmh.c +++ b/hw/riscv/xiangshan_kmh.c @@ -167,6 +167,7 @@ static void xiangshan_kmh_machine_init(MachineState *machine) const MemMapEntry *memmap = xiangshan_kmh_memmap; MemoryRegion *system_memory = get_system_memory(); hwaddr start_addr = memmap[XIANGSHAN_KMH_DRAM].base; + RISCVBootInfo boot_info; /* Initialize SoC */ object_initialize_child(OBJECT(machine), "soc", &s->soc, @@ -178,13 +179,16 @@ static void xiangshan_kmh_machine_init(MachineState *machine) memmap[XIANGSHAN_KMH_DRAM].base, machine->ram); + riscv_boot_info_init(&boot_info, &s->soc.cpus); + /* ROM reset vector */ riscv_setup_rom_reset_vec(machine, &s->soc.cpus, start_addr, memmap[XIANGSHAN_KMH_ROM].base, memmap[XIANGSHAN_KMH_ROM].size, 0, 0); if (machine->firmware) { - riscv_load_firmware(machine->firmware, &start_addr, NULL); + riscv_load_firmware(machine, &boot_info, machine->firmware, + &start_addr, NULL); } /* Note: dtb has been integrated into firmware(OpenSBI) when compiling */ diff --git a/hw/timer/imx_epit.c b/hw/timer/imx_epit.c index c67a39f10c..374ec7d324 100644 --- a/hw/timer/imx_epit.c +++ b/hw/timer/imx_epit.c @@ -20,18 +20,7 @@ #include "hw/misc/imx_ccm.h" #include "qemu/module.h" #include "qemu/log.h" - -#ifndef DEBUG_IMX_EPIT -#define DEBUG_IMX_EPIT 0 -#endif - -#define DPRINTF(fmt, args...) \ - do { \ - if (DEBUG_IMX_EPIT) { \ - fprintf(stderr, "[%s]%s: " fmt , TYPE_IMX_EPIT, \ - __func__, ##args); \ - } \ - } while (0) +#include "trace.h" static const char *imx_epit_reg_name(uint32_t reg) { @@ -80,7 +69,7 @@ static uint32_t imx_epit_get_freq(IMXEPITState *s) uint32_t prescaler = 1 + extract32(s->cr, CR_PRESCALE_SHIFT, CR_PRESCALE_BITS); uint32_t f_in = imx_ccm_get_clock_frequency(s->ccm, imx_epit_clocks[clksrc]); uint32_t freq = f_in / prescaler; - DPRINTF("ptimer frequency is %u\n", freq); + trace_imx_epit_get_freq(freq); return freq; } @@ -146,8 +135,7 @@ static uint64_t imx_epit_read(void *opaque, hwaddr offset, unsigned size) HWADDR_PRIx "\n", TYPE_IMX_EPIT, __func__, offset); break; } - - DPRINTF("(%s) = 0x%08x\n", imx_epit_reg_name(offset >> 2), reg_value); + trace_imx_epit_read(imx_epit_reg_name(offset >> 2), reg_value); return reg_value; } @@ -328,8 +316,7 @@ static void imx_epit_write(void *opaque, hwaddr offset, uint64_t value, { IMXEPITState *s = IMX_EPIT(opaque); - DPRINTF("(%s, value = 0x%08x)\n", imx_epit_reg_name(offset >> 2), - (uint32_t)value); + trace_imx_epit_write(imx_epit_reg_name(offset >> 2), value); switch (offset >> 2) { case 0: /* CR */ @@ -362,7 +349,7 @@ static void imx_epit_cmp(void *opaque) /* The cmp ptimer can't be running when the peripheral is disabled */ assert(s->cr & CR_EN); - DPRINTF("sr was %d\n", s->sr); + trace_imx_epit_cmp(s->sr); /* Set interrupt status bit SR.OCIF and update the interrupt state */ s->sr |= SR_OCIF; imx_epit_update_int(s); @@ -399,8 +386,6 @@ static void imx_epit_realize(DeviceState *dev, Error **errp) IMXEPITState *s = IMX_EPIT(dev); SysBusDevice *sbd = SYS_BUS_DEVICE(dev); - DPRINTF("\n"); - sysbus_init_irq(sbd, &s->irq); memory_region_init_io(&s->iomem, OBJECT(s), &imx_epit_ops, s, TYPE_IMX_EPIT, 0x00001000); diff --git a/hw/timer/trace-events b/hw/timer/trace-events index ac5afe84e8..634ba1da27 100644 --- a/hw/timer/trace-events +++ b/hw/timer/trace-events @@ -122,3 +122,9 @@ hpet_ram_write_tn_cmp(uint8_t reg_off) "hpet_ram_writel HPET_TN_CMP + %" PRIu8 hpet_ram_write_invalid_tn_cmp(void) "invalid HPET_TN_CMP + 4 write" hpet_ram_write_invalid(void) "invalid hpet_ram_writel" hpet_ram_write_counter_write_while_enabled(void) "Writing counter while HPET enabled!" + +# imx_epit.c +imx_epit_get_freq(uint32_t freq) "ptimer frequency is %u" +imx_epit_read(const char *name, uint32_t value) "(%s) = 0x%08x" +imx_epit_write(const char *name, uint64_t value) "(%s, value = 0x%08" PRIx64 ")" +imx_epit_cmp(uint32_t sr) "sr was %d" diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c index 35c2444385..464fd465b3 100644 --- a/hw/ufs/ufs.c +++ b/hw/ufs/ufs.c @@ -363,8 +363,32 @@ static void ufs_process_db(UfsHc *u, uint32_t val) qemu_bh_schedule(u->doorbell_bh); } +/* + * Return canned PA layer attribute values. The emulated link has no PHY, + * so these are purely declarative: a single lane in HS-Gear 4, FAST_MODE. + */ +static uint32_t ufs_uic_dme_get_value(uint16_t attr_id) +{ + switch (attr_id) { + case UFS_ATTR_PA_AVAILTXDATALANES: + case UFS_ATTR_PA_AVAILRXDATALANES: + case UFS_ATTR_PA_CONNECTEDTXDATALANES: + case UFS_ATTR_PA_CONNECTEDRXDATALANES: + return 1; + case UFS_ATTR_PA_MAXRXHSGEAR: + case UFS_ATTR_PA_MAXRXPWMGEAR: + return 4; + case UFS_ATTR_PA_PWRMODE: + return (1 << 4) | 1; + default: + return 0; + } +} + static void ufs_process_uiccmd(UfsHc *u, uint32_t val) { + uint16_t attr_id; + trace_ufs_process_uiccmd(val, u->reg.ucmdarg1, u->reg.ucmdarg2, u->reg.ucmdarg3); /* @@ -378,6 +402,22 @@ static void ufs_process_uiccmd(UfsHc *u, uint32_t val) u->reg.hcs = FIELD_DP32(u->reg.hcs, HCS, UTMRLRDY, 1); u->reg.ucmdarg2 = UFS_UIC_CMD_RESULT_SUCCESS; break; + case UFS_UIC_CMD_DME_GET: + case UFS_UIC_CMD_DME_PEER_GET: + attr_id = (u->reg.ucmdarg1 >> 16) & 0xFFFF; + u->reg.ucmdarg3 = ufs_uic_dme_get_value(attr_id); + u->reg.ucmdarg2 = UFS_UIC_CMD_RESULT_SUCCESS; + break; + case UFS_UIC_CMD_DME_SET: + case UFS_UIC_CMD_DME_PEER_SET: + attr_id = (u->reg.ucmdarg1 >> 16) & 0xFFFF; + u->reg.ucmdarg2 = UFS_UIC_CMD_RESULT_SUCCESS; + /* DME_SET(PA_PWRMODE) is a power-mode-change trigger. */ + if (val == UFS_UIC_CMD_DME_SET && attr_id == UFS_ATTR_PA_PWRMODE) { + u->reg.is = FIELD_DP32(u->reg.is, IS, UPMS, 1); + u->reg.hcs = FIELD_DP32(u->reg.hcs, HCS, UPMCRS, UFS_PWR_LOCAL); + } + break; /* * TODO: Revisit after PM implementation * Power Management is not supported in current QEMU-UFS, @@ -480,6 +520,12 @@ static void ufs_mcq_process_cq(void *opaque) req->cqe.prdt_off = cpu_to_le16(prdt_off); req->cqe.status = status; req->cqe.error = 0; + /* + * From UFSHCI 4.1 the host derives the request tag from cqe.task_tag + * rather than decoding it from utp_addr. + */ + req->cqe.task_tag = req->req_upiu.header.task_tag; + req->cqe.lun = req->req_upiu.header.lun; ret = ufs_addr_write(u, cq->addr + tail, &req->cqe, sizeof(req->cqe)); if (ret) { diff --git a/hw/vfio/cpr-legacy.c b/hw/vfio/cpr-legacy.c index c431d89973..2d40d8baea 100644 --- a/hw/vfio/cpr-legacy.c +++ b/hw/vfio/cpr-legacy.c @@ -226,7 +226,7 @@ void vfio_cpr_giommu_remap(VFIOContainer *bcontainer, memory_region_iommu_replay(giommu->iommu_mr, &giommu->n); } -static int vfio_cpr_rdm_remap(MemoryRegionSection *section, void *opaque) +static int vfio_cpr_rdm_remap(const MemoryRegionSection *section, void *opaque) { RamDiscardListener *rdl = opaque; @@ -242,7 +242,7 @@ static int vfio_cpr_rdm_remap(MemoryRegionSection *section, void *opaque) * directly, which calls vfio_legacy_cpr_dma_map. */ bool vfio_cpr_ram_discard_replay_populated(VFIOContainer *bcontainer, - MemoryRegionSection *section) + const MemoryRegionSection *section) { RamDiscardManager *rdm = memory_region_get_ram_discard_manager(section->mr); VFIORamDiscardListener *vrdl = diff --git a/hw/vfio/display.c b/hw/vfio/display.c index 8f91e83da8..cb83d98e9a 100644 --- a/hw/vfio/display.c +++ b/hw/vfio/display.c @@ -505,15 +505,6 @@ static bool vfio_display_region_init(VFIOPCIDevice *vdev, Error **errp) return true; } -static void vfio_display_region_exit(VFIODisplay *dpy) -{ - if (!dpy->region.buffer.size) { - return; - } - - vfio_region_exit(&dpy->region.buffer); - vfio_region_finalize(&dpy->region.buffer); -} /* ---------------------------------------------------------------------- */ @@ -547,17 +538,31 @@ bool vfio_display_probe(VFIOPCIDevice *vdev, Error **errp) return false; } -void vfio_display_finalize(VFIOPCIDevice *vdev) +void vfio_display_exit(VFIOPCIDevice *vdev) { if (!vdev->dpy) { return; } - qemu_graphic_console_close(vdev->dpy->con); vfio_display_dmabuf_exit(vdev->dpy); - vfio_display_region_exit(vdev->dpy); + qemu_graphic_console_close(vdev->dpy->con); + if (vdev->dpy->region.buffer.size) { + vfio_region_exit(&vdev->dpy->region.buffer); + } +} + +void vfio_display_finalize(VFIOPCIDevice *vdev) +{ + if (!vdev->dpy) { + return; + } + + if (vdev->dpy->region.buffer.size) { + vfio_region_finalize(&vdev->dpy->region.buffer); + } vfio_display_edid_exit(vdev->dpy); g_free(vdev->dpy); + vdev->dpy = NULL; } static bool migrate_needed(void *opaque) diff --git a/hw/vfio/listener.c b/hw/vfio/listener.c index 14cca678ae..109b5d61af 100644 --- a/hw/vfio/listener.c +++ b/hw/vfio/listener.c @@ -201,7 +201,7 @@ out: } static void vfio_ram_discard_notify_discard(RamDiscardListener *rdl, - MemoryRegionSection *section) + const MemoryRegionSection *section) { VFIORamDiscardListener *vrdl = container_of(rdl, VFIORamDiscardListener, listener); @@ -219,7 +219,7 @@ static void vfio_ram_discard_notify_discard(RamDiscardListener *rdl, } static int vfio_ram_discard_notify_populate(RamDiscardListener *rdl, - MemoryRegionSection *section) + const MemoryRegionSection *section) { VFIORamDiscardListener *vrdl = container_of(rdl, VFIORamDiscardListener, listener); @@ -461,7 +461,7 @@ static void vfio_device_error_append(VFIODevice *vbasedev, Error **errp) } VFIORamDiscardListener *vfio_find_ram_discard_listener( - VFIOContainer *bcontainer, MemoryRegionSection *section) + VFIOContainer *bcontainer, const MemoryRegionSection *section) { VFIORamDiscardListener *vrdl; @@ -1149,8 +1149,8 @@ out: } } -static int vfio_ram_discard_query_dirty_bitmap(MemoryRegionSection *section, - void *opaque) +static int vfio_ram_discard_query_dirty_bitmap(const MemoryRegionSection *section, + void *opaque) { const hwaddr size = int128_get64(section->size); const hwaddr iova = section->offset_within_address_space; diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c index 9c06b25e63..4d822b96b5 100644 --- a/hw/vfio/pci.c +++ b/hw/vfio/pci.c @@ -38,6 +38,7 @@ #include "qemu/module.h" #include "qemu/range.h" #include "qemu/units.h" +#include "system/accel-irq.h" #include "system/kvm.h" #include "system/runstate.h" #include "pci.h" @@ -50,7 +51,7 @@ #include "vfio-helpers.h" /* Protected by BQL */ -static KVMRouteChange vfio_route_change; +static AccelRouteChange vfio_route_change; static void vfio_disable_interrupts(VFIOPCIDevice *vdev); static void vfio_mmap_set_enabled(VFIOPCIDevice *vdev, bool enabled); @@ -688,9 +689,9 @@ static int vfio_msix_vector_do_use(PCIDevice *pdev, unsigned int nr, if (vdev->defer_kvm_irq_routing) { vfio_pci_add_kvm_msi_virq(vdev, vector, nr, true); } else { - vfio_route_change = kvm_irqchip_begin_route_changes(kvm_state); + vfio_route_change = accel_irqchip_begin_route_changes(); vfio_pci_add_kvm_msi_virq(vdev, vector, nr, true); - kvm_irqchip_commit_route_changes(&vfio_route_change); + accel_irqchip_commit_route_changes(&vfio_route_change); vfio_connect_kvm_msi_virq(vector, nr); } } @@ -789,7 +790,7 @@ void vfio_pci_prepare_kvm_msi_virq_batch(VFIOPCIDevice *vdev) { assert(!vdev->defer_kvm_irq_routing); vdev->defer_kvm_irq_routing = true; - vfio_route_change = kvm_irqchip_begin_route_changes(kvm_state); + vfio_route_change = accel_irqchip_begin_route_changes(); } void vfio_pci_commit_kvm_msi_virq_batch(VFIOPCIDevice *vdev) @@ -799,7 +800,7 @@ void vfio_pci_commit_kvm_msi_virq_batch(VFIOPCIDevice *vdev) assert(vdev->defer_kvm_irq_routing); vdev->defer_kvm_irq_routing = false; - kvm_irqchip_commit_route_changes(&vfio_route_change); + accel_irqchip_commit_route_changes(&vfio_route_change); for (i = 0; i < vdev->nr_vectors; i++) { vfio_connect_kvm_msi_virq(&vdev->msi_vectors[i], i); @@ -3591,6 +3592,7 @@ static void vfio_pci_realize(PCIDevice *pdev, Error **errp) return; out_deregister: + vfio_display_exit(vdev); if (vdev->interrupt == VFIO_INT_INTx) { vfio_intx_disable(vdev); } @@ -3624,6 +3626,7 @@ static void vfio_exitfn(PCIDevice *pdev) VFIOPCIDevice *vdev = VFIO_PCI_DEVICE(pdev); VFIODevice *vbasedev = &vdev->vbasedev; + vfio_display_exit(vdev); vfio_unregister_req_notifier(vdev); vfio_unregister_err_notifier(vdev); pci_device_set_intx_routing_notifier(pdev, NULL); diff --git a/hw/vfio/pci.h b/hw/vfio/pci.h index c3a1f53d35..cf56711587 100644 --- a/hw/vfio/pci.h +++ b/hw/vfio/pci.h @@ -270,6 +270,7 @@ bool vfio_populate_vga(VFIOPCIDevice *vdev, Error **errp); void vfio_display_reset(VFIOPCIDevice *vdev); bool vfio_display_probe(VFIOPCIDevice *vdev, Error **errp); +void vfio_display_exit(VFIOPCIDevice *vdev); void vfio_display_finalize(VFIOPCIDevice *vdev); extern const VMStateDescription vfio_display_vmstate; diff --git a/hw/virtio/virtio-mem.c b/hw/virtio/virtio-mem.c index a4b71974a1..35e03ed759 100644 --- a/hw/virtio/virtio-mem.c +++ b/hw/virtio/virtio-mem.c @@ -16,6 +16,7 @@ #include "qemu/error-report.h" #include "qemu/units.h" #include "qemu/target-info-qapi.h" +#include "system/memory.h" #include "system/numa.h" #include "system/system.h" #include "system/ramblock.h" @@ -258,140 +259,33 @@ static int virtio_mem_for_each_plugged_range(VirtIOMEM *vmem, void *arg, return ret; } -typedef int (*virtio_mem_section_cb)(MemoryRegionSection *s, void *arg); - -static int virtio_mem_for_each_plugged_section(const VirtIOMEM *vmem, - MemoryRegionSection *s, - void *arg, - virtio_mem_section_cb cb) -{ - unsigned long first_bit, last_bit; - uint64_t offset, size; - int ret = 0; - - first_bit = s->offset_within_region / vmem->block_size; - first_bit = find_next_bit(vmem->bitmap, vmem->bitmap_size, first_bit); - while (first_bit < vmem->bitmap_size) { - MemoryRegionSection tmp = *s; - - offset = first_bit * vmem->block_size; - last_bit = find_next_zero_bit(vmem->bitmap, vmem->bitmap_size, - first_bit + 1) - 1; - size = (last_bit - first_bit + 1) * vmem->block_size; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - break; - } - ret = cb(&tmp, arg); - if (ret) { - break; - } - first_bit = find_next_bit(vmem->bitmap, vmem->bitmap_size, - last_bit + 2); - } - return ret; -} - -static int virtio_mem_for_each_unplugged_section(const VirtIOMEM *vmem, - MemoryRegionSection *s, - void *arg, - virtio_mem_section_cb cb) -{ - unsigned long first_bit, last_bit; - uint64_t offset, size; - int ret = 0; - - first_bit = s->offset_within_region / vmem->block_size; - first_bit = find_next_zero_bit(vmem->bitmap, vmem->bitmap_size, first_bit); - while (first_bit < vmem->bitmap_size) { - MemoryRegionSection tmp = *s; - - offset = first_bit * vmem->block_size; - last_bit = find_next_bit(vmem->bitmap, vmem->bitmap_size, - first_bit + 1) - 1; - size = (last_bit - first_bit + 1) * vmem->block_size; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - break; - } - ret = cb(&tmp, arg); - if (ret) { - break; - } - first_bit = find_next_zero_bit(vmem->bitmap, vmem->bitmap_size, - last_bit + 2); - } - return ret; -} - -static int virtio_mem_notify_populate_cb(MemoryRegionSection *s, void *arg) -{ - RamDiscardListener *rdl = arg; - - return rdl->notify_populate(rdl, s); -} - static void virtio_mem_notify_unplug(VirtIOMEM *vmem, uint64_t offset, uint64_t size) { - RamDiscardListener *rdl; + RamDiscardManager *rdm = memory_region_get_ram_discard_manager(&vmem->memdev->mr); - QLIST_FOREACH(rdl, &vmem->rdl_list, next) { - MemoryRegionSection tmp = *rdl->section; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - rdl->notify_discard(rdl, &tmp); - } + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(vmem), + offset, size); } static int virtio_mem_notify_plug(VirtIOMEM *vmem, uint64_t offset, uint64_t size) { - RamDiscardListener *rdl, *rdl2; - int ret = 0; - - QLIST_FOREACH(rdl, &vmem->rdl_list, next) { - MemoryRegionSection tmp = *rdl->section; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - ret = rdl->notify_populate(rdl, &tmp); - if (ret) { - break; - } - } - - if (ret) { - /* Notify all already-notified listeners. */ - QLIST_FOREACH(rdl2, &vmem->rdl_list, next) { - MemoryRegionSection tmp = *rdl2->section; + RamDiscardManager *rdm = memory_region_get_ram_discard_manager(&vmem->memdev->mr); - if (rdl2 == rdl) { - break; - } - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - rdl2->notify_discard(rdl2, &tmp); - } - } - return ret; + return ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(vmem), + offset, size); } static void virtio_mem_notify_unplug_all(VirtIOMEM *vmem) { - RamDiscardListener *rdl; + RamDiscardManager *rdm = memory_region_get_ram_discard_manager(&vmem->memdev->mr); if (!vmem->size) { return; } - QLIST_FOREACH(rdl, &vmem->rdl_list, next) { - rdl->notify_discard(rdl, rdl->section); - } + ram_discard_manager_notify_discard_all(rdm, RAM_DISCARD_SOURCE(vmem)); } static bool virtio_mem_is_range_plugged(const VirtIOMEM *vmem, @@ -1037,13 +931,9 @@ static void virtio_mem_device_realize(DeviceState *dev, Error **errp) return; } - /* - * Set ourselves as RamDiscardManager before the plug handler maps the - * memory region and exposes it via an address space. - */ - if (memory_region_set_ram_discard_manager(&vmem->memdev->mr, - RAM_DISCARD_MANAGER(vmem))) { - error_setg(errp, "Failed to set RamDiscardManager"); + if (memory_region_add_ram_discard_source(&vmem->memdev->mr, + RAM_DISCARD_SOURCE(vmem))) { + error_setg(errp, "Failed to add RAM discard source"); ram_block_coordinated_discard_require(false); return; } @@ -1062,7 +952,8 @@ static void virtio_mem_device_realize(DeviceState *dev, Error **errp) ret = ram_block_discard_range(rb, 0, qemu_ram_get_used_length(rb)); if (ret) { error_setg_errno(errp, -ret, "Unexpected error discarding RAM"); - memory_region_set_ram_discard_manager(&vmem->memdev->mr, NULL); + memory_region_del_ram_discard_source(&vmem->memdev->mr, + RAM_DISCARD_SOURCE(vmem)); ram_block_coordinated_discard_require(false); return; } @@ -1147,7 +1038,7 @@ static void virtio_mem_device_unrealize(DeviceState *dev) * The unplug handler unmapped the memory region, it cannot be * found via an address space anymore. Unset ourselves. */ - memory_region_set_ram_discard_manager(&vmem->memdev->mr, NULL); + memory_region_del_ram_discard_source(&vmem->memdev->mr, RAM_DISCARD_SOURCE(vmem)); ram_block_coordinated_discard_require(false); } @@ -1175,9 +1066,7 @@ static int virtio_mem_activate_memslot_range_cb(VirtIOMEM *vmem, void *arg, static int virtio_mem_post_load_bitmap(VirtIOMEM *vmem) { - RamDiscardListener *rdl; - int ret; - + RamDiscardManager *rdm = memory_region_get_ram_discard_manager(&vmem->memdev->mr); /* * We restored the bitmap and updated the requested size; activate all * memslots (so listeners register) before notifying about plugged blocks. @@ -1195,14 +1084,7 @@ static int virtio_mem_post_load_bitmap(VirtIOMEM *vmem) * We started out with all memory discarded and our memory region is mapped * into an address space. Replay, now that we updated the bitmap. */ - QLIST_FOREACH(rdl, &vmem->rdl_list, next) { - ret = virtio_mem_for_each_plugged_section(vmem, rdl->section, rdl, - virtio_mem_notify_populate_cb); - if (ret) { - return ret; - } - } - return 0; + return ram_discard_manager_replay_populated_to_listeners(rdm); } static int virtio_mem_post_load(void *opaque, int version_id) @@ -1650,7 +1532,6 @@ static void virtio_mem_instance_init(Object *obj) VirtIOMEM *vmem = VIRTIO_MEM(obj); notifier_list_init(&vmem->size_change_notifiers); - QLIST_INIT(&vmem->rdl_list); object_property_add(obj, VIRTIO_MEM_SIZE_PROP, "size", virtio_mem_get_size, NULL, NULL, NULL); @@ -1694,19 +1575,19 @@ static const Property virtio_mem_legacy_guests_properties[] = { unplugged_inaccessible, ON_OFF_AUTO_ON), }; -static uint64_t virtio_mem_rdm_get_min_granularity(const RamDiscardManager *rdm, +static uint64_t virtio_mem_rds_get_min_granularity(const RamDiscardSource *rds, const MemoryRegion *mr) { - const VirtIOMEM *vmem = VIRTIO_MEM(rdm); + const VirtIOMEM *vmem = VIRTIO_MEM(rds); g_assert(mr == &vmem->memdev->mr); return vmem->block_size; } -static bool virtio_mem_rdm_is_populated(const RamDiscardManager *rdm, +static bool virtio_mem_rds_is_populated(const RamDiscardSource *rds, const MemoryRegionSection *s) { - const VirtIOMEM *vmem = VIRTIO_MEM(rdm); + const VirtIOMEM *vmem = VIRTIO_MEM(rds); uint64_t start_gpa = vmem->addr + s->offset_within_region; uint64_t end_gpa = start_gpa + int128_get64(s->size); @@ -1722,92 +1603,6 @@ static bool virtio_mem_rdm_is_populated(const RamDiscardManager *rdm, return virtio_mem_is_range_plugged(vmem, start_gpa, end_gpa - start_gpa); } -struct VirtIOMEMReplayData { - ReplayRamDiscardState fn; - void *opaque; -}; - -static int virtio_mem_rdm_replay_populated_cb(MemoryRegionSection *s, void *arg) -{ - struct VirtIOMEMReplayData *data = arg; - - return data->fn(s, data->opaque); -} - -static int virtio_mem_rdm_replay_populated(const RamDiscardManager *rdm, - MemoryRegionSection *s, - ReplayRamDiscardState replay_fn, - void *opaque) -{ - const VirtIOMEM *vmem = VIRTIO_MEM(rdm); - struct VirtIOMEMReplayData data = { - .fn = replay_fn, - .opaque = opaque, - }; - - g_assert(s->mr == &vmem->memdev->mr); - return virtio_mem_for_each_plugged_section(vmem, s, &data, - virtio_mem_rdm_replay_populated_cb); -} - -static int virtio_mem_rdm_replay_discarded_cb(MemoryRegionSection *s, - void *arg) -{ - struct VirtIOMEMReplayData *data = arg; - - return data->fn(s, data->opaque); -} - -static int virtio_mem_rdm_replay_discarded(const RamDiscardManager *rdm, - MemoryRegionSection *s, - ReplayRamDiscardState replay_fn, - void *opaque) -{ - const VirtIOMEM *vmem = VIRTIO_MEM(rdm); - struct VirtIOMEMReplayData data = { - .fn = replay_fn, - .opaque = opaque, - }; - - g_assert(s->mr == &vmem->memdev->mr); - return virtio_mem_for_each_unplugged_section(vmem, s, &data, - virtio_mem_rdm_replay_discarded_cb); -} - -static void virtio_mem_rdm_register_listener(RamDiscardManager *rdm, - RamDiscardListener *rdl, - MemoryRegionSection *s) -{ - VirtIOMEM *vmem = VIRTIO_MEM(rdm); - int ret; - - g_assert(s->mr == &vmem->memdev->mr); - rdl->section = memory_region_section_new_copy(s); - - QLIST_INSERT_HEAD(&vmem->rdl_list, rdl, next); - ret = virtio_mem_for_each_plugged_section(vmem, rdl->section, rdl, - virtio_mem_notify_populate_cb); - if (ret) { - error_report("%s: Replaying plugged ranges failed: %s", __func__, - strerror(-ret)); - } -} - -static void virtio_mem_rdm_unregister_listener(RamDiscardManager *rdm, - RamDiscardListener *rdl) -{ - VirtIOMEM *vmem = VIRTIO_MEM(rdm); - - g_assert(rdl->section->mr == &vmem->memdev->mr); - if (vmem->size) { - rdl->notify_discard(rdl, rdl->section); - } - - memory_region_section_free_copy(rdl->section); - rdl->section = NULL; - QLIST_REMOVE(rdl, next); -} - static void virtio_mem_unplug_request_check(VirtIOMEM *vmem, Error **errp) { if (vmem->unplugged_inaccessible == ON_OFF_AUTO_OFF) { @@ -1837,7 +1632,7 @@ static void virtio_mem_class_init(ObjectClass *klass, const void *data) DeviceClass *dc = DEVICE_CLASS(klass); VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); VirtIOMEMClass *vmc = VIRTIO_MEM_CLASS(klass); - RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_CLASS(klass); + RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_CLASS(klass); device_class_set_props(dc, virtio_mem_properties); if (virtio_mem_has_legacy_guests()) { @@ -1861,12 +1656,8 @@ static void virtio_mem_class_init(ObjectClass *klass, const void *data) vmc->remove_size_change_notifier = virtio_mem_remove_size_change_notifier; vmc->unplug_request_check = virtio_mem_unplug_request_check; - rdmc->get_min_granularity = virtio_mem_rdm_get_min_granularity; - rdmc->is_populated = virtio_mem_rdm_is_populated; - rdmc->replay_populated = virtio_mem_rdm_replay_populated; - rdmc->replay_discarded = virtio_mem_rdm_replay_discarded; - rdmc->register_listener = virtio_mem_rdm_register_listener; - rdmc->unregister_listener = virtio_mem_rdm_unregister_listener; + rdsc->get_min_granularity = virtio_mem_rds_get_min_granularity; + rdsc->is_populated = virtio_mem_rds_is_populated; } static const TypeInfo virtio_mem_info = { @@ -1878,7 +1669,7 @@ static const TypeInfo virtio_mem_info = { .class_init = virtio_mem_class_init, .class_size = sizeof(VirtIOMEMClass), .interfaces = (const InterfaceInfo[]) { - { TYPE_RAM_DISCARD_MANAGER }, + { TYPE_RAM_DISCARD_SOURCE }, { } }, }; diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c index fd9719547f..6f5db5fc42 100644 --- a/hw/virtio/virtio-pci.c +++ b/hw/virtio/virtio-pci.c @@ -869,7 +869,7 @@ static int kvm_virtio_pci_vq_vector_use(VirtIOPCIProxy *proxy, int ret; if (irqfd->users == 0) { - KVMRouteChange c = kvm_irqchip_begin_route_changes(kvm_state); + AccelRouteChange c = accel_irqchip_begin_route_changes(); ret = accel_irqchip_add_msi_route(&c, vector, &proxy->pci_dev); if (ret < 0) { return ret; @@ -2669,4 +2669,3 @@ static void virtio_pci_register_types(void) } type_init(virtio_pci_register_types) - diff --git a/include/accel/accel-route.h b/include/accel/accel-route.h new file mode 100644 index 0000000000..07fac27e2a --- /dev/null +++ b/include/accel/accel-route.h @@ -0,0 +1,17 @@ +/* + * Accelerator MSI route change tracking + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef ACCEL_ROUTE_H +#define ACCEL_ROUTE_H + +#include "qemu/accel.h" + +typedef struct AccelRouteChange { + AccelState *accel; + int changes; +} AccelRouteChange; + +#endif /* ACCEL_ROUTE_H */ diff --git a/include/block/ufs.h b/include/block/ufs.h index 6dd91181e5..d19b3c65ef 100644 --- a/include/block/ufs.h +++ b/include/block/ufs.h @@ -629,6 +629,18 @@ enum { #define UFS_MASK_UIC_COMMAND_RESULT 0xFF /* + * MIPI UniPro PHY Adapter (PA) layer attribute IDs accessed via + * DME_GET / DME_SET / DME_PEER_{GET,SET} UIC commands. + */ +#define UFS_ATTR_PA_AVAILTXDATALANES 0x1520 +#define UFS_ATTR_PA_AVAILRXDATALANES 0x1540 +#define UFS_ATTR_PA_CONNECTEDTXDATALANES 0x1561 +#define UFS_ATTR_PA_PWRMODE 0x1571 +#define UFS_ATTR_PA_CONNECTEDRXDATALANES 0x1581 +#define UFS_ATTR_PA_MAXRXPWMGEAR 0x1586 +#define UFS_ATTR_PA_MAXRXHSGEAR 0x1587 + +/* * Request Descriptor Definitions */ @@ -1321,7 +1333,11 @@ typedef struct QEMU_PACKED UfsCqEntry { uint8_t status; uint8_t error; uint16_t rsvd1; - uint32_t rsvd2[3]; + uint8_t task_tag; + uint8_t lun; + uint8_t iid_ext_iid; + uint8_t rsvd2; + uint32_t rsvd3[2]; } UfsCqEntry; static inline void _ufs_check_size(void) diff --git a/include/glib-compat.h b/include/glib-compat.h index 2e32b90f05..32ee2afdcd 100644 --- a/include/glib-compat.h +++ b/include/glib-compat.h @@ -30,6 +30,7 @@ #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #include <glib.h> +#include <glib/gstdio.h> #if defined(G_OS_UNIX) #include <glib-unix.h> #include <sys/types.h> @@ -129,6 +130,34 @@ qemu_g_test_slow(void) #define g_test_thorough() qemu_g_test_slow() #define g_test_quick() (!qemu_g_test_slow()) +static inline gboolean g_clear_fd_qemu(int *fd_ptr, GError **error) +{ +#if GLIB_CHECK_VERSION(2, 76, 0) + return g_clear_fd(fd_ptr, error); +#else + int fd = *fd_ptr; + + *fd_ptr = -1; + + if (fd < 0) { + return TRUE; + } + + return g_close(fd, error); +#endif +} +#define g_clear_fd(fd, err) g_clear_fd_qemu(fd, err) + +#if !GLIB_CHECK_VERSION(2, 76, 0) +static inline void _g_clear_fd_ignore_error(int *fd_ptr) +{ + int errsv = errno; + g_clear_fd(fd_ptr, NULL); + errno = errsv; +} +#define g_autofd __attribute__((cleanup(_g_clear_fd_ignore_error))) +#endif + #pragma GCC diagnostic pop #ifndef G_NORETURN diff --git a/include/hw/arm/fsl-imx6.h b/include/hw/arm/fsl-imx6.h index cddd100dd6..376deea3a4 100644 --- a/include/hw/arm/fsl-imx6.h +++ b/include/hw/arm/fsl-imx6.h @@ -30,12 +30,14 @@ #include "hw/sd/sdhci.h" #include "hw/ssi/imx_spi.h" #include "hw/net/imx_fec.h" +#include "hw/net/flexcan.h" #include "hw/usb/chipidea.h" #include "hw/usb/imx-usb-phy.h" #include "hw/pci-host/designware.h" #include "hw/core/or-irq.h" #include "system/memory.h" #include "target/arm/cpu.h" +#include "net/can_emu.h" #include "qom/object.h" #define TYPE_FSL_IMX6 "fsl-imx6" @@ -51,6 +53,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(FslIMX6State, FSL_IMX6) #define FSL_IMX6_NUM_WDTS 2 #define FSL_IMX6_NUM_USB_PHYS 2 #define FSL_IMX6_NUM_USBS 4 +#define FSL_IMX6_NUM_CANS 2 struct FslIMX6State { /*< private >*/ @@ -73,6 +76,7 @@ struct FslIMX6State { IMXUSBPHYState usbphy[FSL_IMX6_NUM_USB_PHYS]; ChipideaState usb[FSL_IMX6_NUM_USBS]; IMXFECState eth; + FlexcanState flexcan[FSL_IMX6_NUM_CANS]; DesignwarePCIEHost pcie; OrIRQState pcie4_msi_irq; MemoryRegion rom; @@ -80,6 +84,8 @@ struct FslIMX6State { MemoryRegion ocram; MemoryRegion ocram_alias; uint32_t phy_num; + + CanBusState *canbus[FSL_IMX6_NUM_CANS]; }; diff --git a/include/hw/core/irq.h b/include/hw/core/irq.h index 291fdd67df..299a4a9a8c 100644 --- a/include/hw/core/irq.h +++ b/include/hw/core/irq.h @@ -14,6 +14,7 @@ struct IRQState { qemu_irq_handler handler; void *opaque; int n; + qemu_irq_handler observer; }; void qemu_set_irq(qemu_irq irq, int level); @@ -96,9 +97,8 @@ void qemu_free_irq(qemu_irq irq); /* Returns a new IRQ with opposite polarity. */ qemu_irq qemu_irq_invert(qemu_irq irq); -/* For internal use in qtest. Similar to qemu_irq_split, but operating - on an existing vector of qemu_irq. */ -void qemu_irq_intercept_in(qemu_irq *gpio_in, qemu_irq_handler handler, int n); +/* For internal use in qtest. */ +void qemu_irq_set_observer(qemu_irq *gpio_in, qemu_irq_handler handler, int n); /** * qemu_irq_is_connected: Return true if IRQ line is wired up diff --git a/include/hw/hexagon/hexagon.h b/include/hw/hexagon/hexagon.h new file mode 100644 index 0000000000..1034b09c2a --- /dev/null +++ b/include/hw/hexagon/hexagon.h @@ -0,0 +1,161 @@ +/* + * Hexagon Baseboard System emulation. + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + + +#ifndef HW_HEXAGON_H +#define HW_HEXAGON_H + +#include "system/memory.h" +#include "hw/core/boards.h" + +struct hexagon_board_boot_info { + uint64_t ram_size; + const char *kernel_filename; + uint32_t kernel_elf_flags; +}; + +typedef enum { + unknown_rev = 0, + v66_rev = 0xa666, + v67_rev = 0x2667, + v68_rev = 0x8d68, + v69_rev = 0x8c69, + v71_rev = 0x8c71, + v73_rev = 0x8c73, + v73m_rev = 0xcc73, +} Rev_t; +#define HEXAGON_LATEST_REV v73 +#define HEXAGON_LATEST_REV_UPPER V73 + +/* + * Config table address bases represent bits [35:16]. + */ +#define HEXAGON_CFG_ADDR_BASE(addr) (((addr) >> 16) & 0x0fffff) + +#define HEXAGON_CFGSPACE_ENTRIES (128) + +union hexagon_config_table { + struct { + /* Base address of L2TCM space */ + uint32_t l2tcm_base; + uint32_t reserved0; + /* Base address of subsystem space */ + uint32_t subsystem_base; + /* Base address of ETM space */ + uint32_t etm_base; + /* Base address of L2 configuration space */ + uint32_t l2cfg_base; + uint32_t reserved1; + /* Base address of L1S */ + uint32_t l1s0_base; + /* Base address of AXI2 */ + uint32_t axi2_lowaddr; + /* Base address of streamer base */ + uint32_t streamer_base; + uint32_t reserved2; + /* Base address of fast L2VIC */ + uint32_t fastl2vic_base; + /* Number of entries in JTLB */ + uint32_t jtlb_size_entries; + /* Coprocessor type */ + uint32_t coproc_present; + /* Number of extension execution contexts available */ + uint32_t ext_contexts; + /* Base address of Hexagon Vector Tightly Coupled Memory (VTCM) */ + uint32_t vtcm_base; + /* Size of VTCM (in KB) */ + uint32_t vtcm_size_kb; + /* L2 tag size */ + uint32_t l2tag_size; + /* Amount of physical L2 memory in released version */ + uint32_t l2ecomem_size; + /* Hardware threads available on the core */ + uint32_t thread_enable_mask; + /* Base address of the ECC registers */ + uint32_t eccreg_base; + /* L2 line size */ + uint32_t l2line_size; + /* Small Core processor (also implies audio extension) */ + uint32_t tiny_core; + /* Size of L2TCM */ + uint32_t l2itcm_size; + /* Base address of L2-ITCM */ + uint32_t l2itcm_base; + uint32_t reserved3; + /* DTM is present */ + uint32_t dtm_present; + /* Version of the DMA */ + uint32_t dma_version; + /* Native HVX vector length in log of bytes */ + uint32_t hvx_vec_log_length; + /* Core ID of the multi-core */ + uint32_t core_id; + /* Number of multi-core cores */ + uint32_t core_count; + uint32_t coproc2_reg0; + uint32_t coproc2_reg1; + /* Supported HVX vector length */ + uint32_t v2x_mode; + uint32_t coproc2_reg2; + uint32_t coproc2_reg3; + uint32_t coproc2_reg4; + uint32_t coproc2_reg5; + uint32_t coproc2_reg6; + uint32_t coproc2_reg7; + /* Voltage droop mitigation technique parameter */ + uint32_t acd_preset; + /* Voltage droop mitigation technique parameter */ + uint32_t mnd_preset; + /* L1 data cache size (in KB) */ + uint32_t l1d_size_kb; + /* L1 instruction cache size in (KB) */ + uint32_t l1i_size_kb; + /* L1 data cache write policy: see HexagonL1WritePolicy */ + uint32_t l1d_write_policy; + /* VTCM bank width */ + uint32_t vtcm_bank_width; + uint32_t reserved4; + uint32_t reserved5; + uint32_t reserved6; + uint32_t coproc2_cvt_mpy_size; + uint32_t consistency_domain; + uint32_t capacity_domain; + uint32_t axi3_lowaddr; + uint32_t coproc2_int8_subcolumns; + uint32_t corecfg_present; + uint32_t coproc2_fp16_acc_exp; + uint32_t AXIM2_secondary_base; + }; + uint32_t raw[HEXAGON_CFGSPACE_ENTRIES]; +}; + +struct hexagon_machine_config { + /* Base address of config table */ + uint32_t cfgbase; + /* Size of L2 TCM */ + uint32_t l2tcm_size; + /* Base address of L2VIC */ + uint32_t l2vic_base; + /* Size of L2VIC region */ + uint32_t l2vic_size; + /* QTimer csr base */ + uint32_t csr_base; + uint32_t qtmr_region; + union hexagon_config_table cfgtable; +}; + +#define TYPE_HEXAGON_COMMON_MACHINE "hexagon-common-machine" +OBJECT_DECLARE_SIMPLE_TYPE(HexagonCommonMachineState, HEXAGON_COMMON_MACHINE) + +struct HexagonCommonMachineState { + MachineState parent_obj; + + MemoryRegion ram; + MemoryRegion cfgtable_rom; +}; + +#endif diff --git a/include/hw/hexagon/hexagon_globalreg.h b/include/hw/hexagon/hexagon_globalreg.h new file mode 100644 index 0000000000..950099808f --- /dev/null +++ b/include/hw/hexagon/hexagon_globalreg.h @@ -0,0 +1,55 @@ +/* + * Hexagon Global Registers QOM Object + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HEXAGON_GLOBALREG_H +#define HEXAGON_GLOBALREG_H + +#include "hw/core/qdev.h" +#include "hw/core/sysbus.h" +#include "qom/object.h" +#include "target/hexagon/cpu.h" + +#define TYPE_HEXAGON_GLOBALREG "hexagon-globalreg" +OBJECT_DECLARE_SIMPLE_TYPE(HexagonGlobalRegState, HEXAGON_GLOBALREG) + +struct HexagonGlobalRegState { + SysBusDevice parent_obj; + + /* Array of system registers */ + uint32_t regs[NUM_SREGS]; + + /* Global performance cycle counter base */ + uint64_t g_pcycle_base; + + /* Properties for global register reset values */ + uint32_t boot_evb; /* Boot Exception Vector Base (HEX_SREG_EVB) */ + uint64_t config_table_addr; /* Configuration table base */ + uint32_t dsp_rev; /* DSP revision register (HEX_SREG_REV) */ + + /* ISDB properties */ + bool isdben_etm_enable; /* ISDB ETM enable bit */ + bool isdben_dfd_enable; /* ISDB DFD enable bit */ + bool isdben_trusted; /* ISDB trusted mode bit */ + bool isdben_secure; /* ISDB secure mode bit */ +}; + +/* Public interface functions */ +uint32_t hexagon_globalreg_read(HexagonGlobalRegState *s, uint32_t reg, + uint32_t htid); +void hexagon_globalreg_write(HexagonGlobalRegState *s, uint32_t reg, + uint32_t value, uint32_t htid); +uint32_t hexagon_globalreg_masked_value(HexagonGlobalRegState *s, uint32_t reg, + uint32_t value); +void hexagon_globalreg_write_masked(HexagonGlobalRegState *s, uint32_t reg, + uint32_t value); + +/* Global performance cycle counter access */ +uint64_t hexagon_globalreg_get_pcycle_base(HexagonGlobalRegState *s); +void hexagon_globalreg_set_pcycle_base(HexagonGlobalRegState *s, + uint64_t value); + +#endif /* HEXAGON_GLOBALREG_H */ diff --git a/include/hw/hexagon/hexagon_tlb.h b/include/hw/hexagon/hexagon_tlb.h new file mode 100644 index 0000000000..90d9ed8404 --- /dev/null +++ b/include/hw/hexagon/hexagon_tlb.h @@ -0,0 +1,46 @@ +/* + * Hexagon TLB QOM Device + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_HEXAGON_TLB_H +#define HW_HEXAGON_TLB_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" +#include "exec/hwaddr.h" +#include "exec/mmu-access-type.h" +#include "monitor/monitor.h" +#define TYPE_HEXAGON_TLB "hexagon-tlb" +OBJECT_DECLARE_SIMPLE_TYPE(HexagonTLBState, HEXAGON_TLB) + +struct HexagonTLBState { + SysBusDevice parent_obj; + + uint32_t num_entries; + uint64_t *entries; +}; + +uint64_t hexagon_tlb_read(HexagonTLBState *tlb, uint32_t index); +void hexagon_tlb_write(HexagonTLBState *tlb, uint32_t index, uint64_t value); + +bool hexagon_tlb_find_match(HexagonTLBState *tlb, uint32_t asid, + uint32_t VA, MMUAccessType access_type, + hwaddr *PA, int *prot, uint64_t *size, + int32_t *excp, int *cause_code, int mmu_idx); + +uint32_t hexagon_tlb_lookup(HexagonTLBState *tlb, uint32_t asid, + uint32_t VA, int *cause_code); + +int hexagon_tlb_check_overlap(HexagonTLBState *tlb, uint64_t entry, + uint64_t index); + +void hexagon_tlb_dump(Monitor *mon, HexagonTLBState *tlb); + +bool hexagon_tlb_dump_entry(Monitor *mon, uint64_t entry); + +uint32_t hexagon_tlb_get_num_entries(HexagonTLBState *tlb); + +#endif /* HW_HEXAGON_TLB_H */ diff --git a/include/hw/hexagon/virt.h b/include/hw/hexagon/virt.h new file mode 100644 index 0000000000..fcb4776219 --- /dev/null +++ b/include/hw/hexagon/virt.h @@ -0,0 +1,30 @@ +/* + * Definitions for hexagon virt board. + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_HEXAGONVIRT_H +#define HW_HEXAGONVIRT_H + +#include "hw/hexagon/hexagon.h" +#include "target/hexagon/cpu.h" + +struct HexagonVirtMachineState { + HexagonCommonMachineState parent_obj; + + int fdt_size; + MemoryRegion *sys; + MemoryRegion tcm; + MemoryRegion vtcm; + MemoryRegion bios; + Clock *apb_clk; +}; + +void hexagon_load_fdt(const struct HexagonVirtMachineState *vms); + +#define TYPE_HEXAGON_VIRT_MACHINE MACHINE_TYPE_NAME("virt") +OBJECT_DECLARE_SIMPLE_TYPE(HexagonVirtMachineState, HEXAGON_VIRT_MACHINE) + +#endif /* HW_HEXAGONVIRT_H */ diff --git a/include/hw/hyperv/hvgdk_mini.h b/include/hw/hyperv/hvgdk_mini.h index c3a8f33280..f8838a31bb 100644 --- a/include/hw/hyperv/hvgdk_mini.h +++ b/include/hw/hyperv/hvgdk_mini.h @@ -9,9 +9,25 @@ #define MSHV_IOCTL 0xB8 +/* Hyper-V specific model specific registers (MSRs) */ + +/* HV_X64_SYNTHETIC_MSR */ +#define HV_X64_MSR_GUEST_OS_ID 0x40000000 +#define HV_X64_MSR_HYPERCALL 0x40000001 +#define HV_X64_MSR_VP_INDEX 0x40000002 +#define HV_X64_MSR_RESET 0x40000003 +#define HV_X64_MSR_VP_RUNTIME 0x40000010 +#define HV_X64_MSR_TIME_REF_COUNT 0x40000020 +#define HV_X64_MSR_REFERENCE_TSC 0x40000021 +#define HV_X64_MSR_TSC_FREQUENCY 0x40000022 +#define HV_X64_MSR_APIC_FREQUENCY 0x40000023 + typedef enum hv_register_name { /* Pending Interruption Register */ HV_REGISTER_PENDING_INTERRUPTION = 0x00010002, + HV_REGISTER_INTERRUPT_STATE = 0x00010003, + HV_REGISTER_PENDING_EVENT0 = 0x00010004, + HV_REGISTER_PENDING_EVENT1 = 0x00010005, /* X64 User-Mode Registers */ HV_X64_REGISTER_RAX = 0x00020000, @@ -154,10 +170,22 @@ typedef enum hv_register_name { HV_X64_REGISTER_SPEC_CTRL = 0x00080084, HV_X64_REGISTER_TSC_ADJUST = 0x00080096, + /* CET / Shadow Stack */ + HV_X64_REGISTER_U_XSS = 0x0008008B, + HV_X64_REGISTER_U_CET = 0x0008008C, + HV_X64_REGISTER_S_CET = 0x0008008D, + HV_X64_REGISTER_SSP = 0x0008008E, + HV_X64_REGISTER_PL0_SSP = 0x0008008F, + HV_X64_REGISTER_PL1_SSP = 0x00080090, + HV_X64_REGISTER_PL2_SSP = 0x00080091, + HV_X64_REGISTER_PL3_SSP = 0x00080092, + HV_X64_REGISTER_INTERRUPT_SSP_TABLE_ADDR = 0x00080093, + /* Other MSRs */ HV_X64_REGISTER_MSR_IA32_MISC_ENABLE = 0x000800A0, /* Misc */ + HV_X64_REGISTER_HYPERCALL = 0x00090001, HV_REGISTER_GUEST_OS_ID = 0x00090002, HV_REGISTER_REFERENCE_TSC = 0x00090017, @@ -454,6 +482,8 @@ typedef struct hv_input_set_vp_registers { struct hv_register_assoc elements[]; } QEMU_PACKED hv_input_set_vp_registers; +#define MSHV_VP_MAX_REGISTERS 128 + union hv_interrupt_control { uint64_t as_uint64; struct { @@ -474,6 +504,109 @@ struct hv_input_assert_virtual_interrupt { uint16_t rsvd_z1; } QEMU_PACKED; +/* Flags for dirty mask of hv_vp_register_page */ +enum hv_x64_register_class_type { + HV_X64_REGISTER_CLASS_GENERAL = 0, + HV_X64_REGISTER_CLASS_IP = 1, + HV_X64_REGISTER_CLASS_XMM = 2, + HV_X64_REGISTER_CLASS_SEGMENT = 3, + HV_X64_REGISTER_CLASS_FLAGS = 4, +}; + +#define HV_VP_REGISTER_PAGE_MAX_VECTOR_COUNT 7 + +union hv_vp_register_page_interrupt_vectors { + uint64_t as_uint64; + struct { + uint8_t vector_count; + uint8_t vector[HV_VP_REGISTER_PAGE_MAX_VECTOR_COUNT]; + }; +}; + +struct hv_vp_register_page { + uint16_t version; + uint8_t isvalid; + uint8_t rsvdz; + uint32_t dirty; + + union { + struct { + /* General purpose registers (HV_X64_REGISTER_CLASS_GENERAL) */ + union { + struct { + uint64_t rax; + uint64_t rcx; + uint64_t rdx; + uint64_t rbx; + uint64_t rsp; + uint64_t rbp; + uint64_t rsi; + uint64_t rdi; + uint64_t r8; + uint64_t r9; + uint64_t r10; + uint64_t r11; + uint64_t r12; + uint64_t r13; + uint64_t r14; + uint64_t r15; + } QEMU_PACKED; + + uint64_t gp_registers[16]; + }; + /* Instruction pointer (HV_X64_REGISTER_CLASS_IP) */ + uint64_t rip; + /* Flags (HV_X64_REGISTER_CLASS_FLAGS) */ + uint64_t rflags; + } QEMU_PACKED; + + uint64_t registers[18]; + }; + uint8_t reserved[8]; + /* Volatile XMM registers (HV_X64_REGISTER_CLASS_XMM) */ + union { + struct { + struct hv_u128 xmm0; + struct hv_u128 xmm1; + struct hv_u128 xmm2; + struct hv_u128 xmm3; + struct hv_u128 xmm4; + struct hv_u128 xmm5; + } QEMU_PACKED; + + struct hv_u128 xmm_registers[6]; + }; + /* Segment registers (HV_X64_REGISTER_CLASS_SEGMENT) */ + union { + struct { + struct hv_x64_segment_register es; + struct hv_x64_segment_register cs; + struct hv_x64_segment_register ss; + struct hv_x64_segment_register ds; + struct hv_x64_segment_register fs; + struct hv_x64_segment_register gs; + } QEMU_PACKED; + + struct hv_x64_segment_register segment_registers[6]; + }; + /* Misc. control registers (cannot be set via this interface) */ + uint64_t cr0; + uint64_t cr3; + uint64_t cr4; + uint64_t cr8; + uint64_t efer; + uint64_t dr7; + union hv_x64_pending_interruption_register pending_interruption; + union hv_x64_interrupt_state_register interrupt_state; + uint64_t instruction_emulation_hints; + uint64_t xfem; + + uint8_t reserved1[0x100]; + + /* Interrupts injected as part of HvCallDispatchVp. */ + union hv_vp_register_page_interrupt_vectors interrupt_vectors; +} QEMU_PACKED; + /* /dev/mshv */ #define MSHV_CREATE_PARTITION _IOW(MSHV_IOCTL, 0x00, struct mshv_create_partition) #define MSHV_CREATE_VP _IOW(MSHV_IOCTL, 0x01, struct mshv_create_vp) diff --git a/include/hw/hyperv/hvhdk.h b/include/hw/hyperv/hvhdk.h index 41af743847..9e6dcb22f6 100644 --- a/include/hw/hyperv/hvhdk.h +++ b/include/hw/hyperv/hvhdk.h @@ -11,6 +11,16 @@ #define HV_PARTITION_SYNTHETIC_PROCESSOR_FEATURES_BANKS 1 +struct hv_input_get_partition_property { + uint64_t partition_id; + uint32_t property_code; /* enum hv_partition_property_code */ + uint32_t padding; +} QEMU_PACKED; + +struct hv_output_get_partition_property { + uint64_t property_value; +} QEMU_PACKED; + struct hv_input_set_partition_property { uint64_t partition_id; uint32_t property_code; /* enum hv_partition_property_code */ @@ -161,6 +171,191 @@ union hv_partition_synthetic_processor_features { }; }; +union hv_partition_processor_xsave_features { + struct { + uint64_t xsave_support:1; + uint64_t xsaveopt_support:1; + uint64_t avx_support:1; + uint64_t avx2_support:1; + uint64_t fma_support:1; + uint64_t mpx_support:1; + uint64_t avx512_support:1; + uint64_t avx512_dq_support:1; + uint64_t avx512_cd_support:1; + uint64_t avx512_bw_support:1; + uint64_t avx512_vl_support:1; + uint64_t xsave_comp_support:1; + uint64_t xsave_supervisor_support:1; + uint64_t xcr1_support:1; + uint64_t avx512_bitalg_support:1; + uint64_t avx512_i_fma_support:1; + uint64_t avx512_v_bmi_support:1; + uint64_t avx512_v_bmi2_support:1; + uint64_t avx512_vnni_support:1; + uint64_t gfni_support:1; + uint64_t vaes_support:1; + uint64_t avx512_v_popcntdq_support:1; + uint64_t vpclmulqdq_support:1; + uint64_t avx512_bf16_support:1; + uint64_t avx512_vp2_intersect_support:1; + uint64_t avx512_fp16_support:1; + uint64_t xfd_support:1; + uint64_t amx_tile_support:1; + uint64_t amx_bf16_support:1; + uint64_t amx_int8_support:1; + uint64_t avx_vnni_support:1; + uint64_t avx_ifma_support:1; + uint64_t avx_ne_convert_support:1; + uint64_t avx_vnni_int8_support:1; + uint64_t avx_vnni_int16_support:1; + uint64_t avx10_1_256_support:1; + uint64_t avx10_1_512_support:1; + uint64_t amx_fp16_support:1; + uint64_t reserved1:26; + }; + uint64_t as_uint64; +}; + +#define HV_PARTITION_PROCESSOR_FEATURES_BANKS 2 +#define HV_PARTITION_PROCESSOR_FEATURES_RESERVEDBANK1_BITFIELD_COUNT 4 + + +union hv_partition_processor_features { + uint64_t as_uint64[HV_PARTITION_PROCESSOR_FEATURES_BANKS]; + struct { + uint64_t sse3_support:1; + uint64_t lahf_sahf_support:1; + uint64_t ssse3_support:1; + uint64_t sse4_1_support:1; + uint64_t sse4_2_support:1; + uint64_t sse4a_support:1; + uint64_t xop_support:1; + uint64_t pop_cnt_support:1; + uint64_t cmpxchg16b_support:1; + uint64_t altmovcr8_support:1; + uint64_t lzcnt_support:1; + uint64_t mis_align_sse_support:1; + uint64_t mmx_ext_support:1; + uint64_t amd3dnow_support:1; + uint64_t extended_amd3dnow_support:1; + uint64_t page_1gb_support:1; + uint64_t aes_support:1; + uint64_t pclmulqdq_support:1; + uint64_t pcid_support:1; + uint64_t fma4_support:1; + uint64_t f16c_support:1; + uint64_t rd_rand_support:1; + uint64_t rd_wr_fs_gs_support:1; + uint64_t smep_support:1; + uint64_t enhanced_fast_string_support:1; + uint64_t bmi1_support:1; + uint64_t bmi2_support:1; + uint64_t hle_support_deprecated:1; + uint64_t rtm_support_deprecated:1; + uint64_t movbe_support:1; + uint64_t npiep1_support:1; + uint64_t dep_x87_fpu_save_support:1; + uint64_t rd_seed_support:1; + uint64_t adx_support:1; + uint64_t intel_prefetch_support:1; + uint64_t smap_support:1; + uint64_t hle_support:1; + uint64_t rtm_support:1; + uint64_t rdtscp_support:1; + uint64_t clflushopt_support:1; + uint64_t clwb_support:1; + uint64_t sha_support:1; + uint64_t x87_pointers_saved_support:1; + uint64_t invpcid_support:1; + uint64_t ibrs_support:1; + uint64_t stibp_support:1; + uint64_t ibpb_support:1; + uint64_t unrestricted_guest_support:1; + uint64_t mdd_support:1; + uint64_t fast_short_rep_mov_support:1; + uint64_t l1dcache_flush_support:1; + uint64_t rdcl_no_support:1; + uint64_t ibrs_all_support:1; + uint64_t skip_l1df_support:1; + uint64_t ssb_no_support:1; + uint64_t rsb_a_no_support:1; + uint64_t virt_spec_ctrl_support:1; + uint64_t rd_pid_support:1; + uint64_t umip_support:1; + uint64_t mbs_no_support:1; + uint64_t mb_clear_support:1; + uint64_t taa_no_support:1; + uint64_t tsx_ctrl_support:1; + uint64_t reserved_bank0:1; + + /* N.B. Begin bank 1 processor features. */ + uint64_t a_count_m_count_support:1; + uint64_t tsc_invariant_support:1; + uint64_t cl_zero_support:1; + uint64_t rdpru_support:1; + uint64_t la57_support:1; + uint64_t mbec_support:1; + uint64_t nested_virt_support:1; + uint64_t psfd_support:1; + uint64_t cet_ss_support:1; + uint64_t cet_ibt_support:1; + uint64_t vmx_exception_inject_support:1; + uint64_t enqcmd_support:1; + uint64_t umwait_tpause_support:1; + uint64_t movdiri_support:1; + uint64_t movdir64b_support:1; + uint64_t cldemote_support:1; + uint64_t serialize_support:1; + uint64_t tsc_deadline_tmr_support:1; + uint64_t tsc_adjust_support:1; + uint64_t fzl_rep_movsb:1; + uint64_t fs_rep_stosb:1; + uint64_t fs_rep_cmpsb:1; + uint64_t tsx_ld_trk_support:1; + uint64_t vmx_ins_outs_exit_info_support:1; + uint64_t hlat_support:1; + uint64_t sbdr_ssdp_no_support:1; + uint64_t fbsdp_no_support:1; + uint64_t psdp_no_support:1; + uint64_t fb_clear_support:1; + uint64_t btc_no_support:1; + uint64_t ibpb_rsb_flush_support:1; + uint64_t stibp_always_on_support:1; + uint64_t perf_global_ctrl_support:1; + uint64_t npt_execute_only_support:1; + uint64_t npt_ad_flags_support:1; + uint64_t npt1_gb_page_support:1; + uint64_t amd_processor_topology_node_id_support:1; + uint64_t local_machine_check_support:1; + uint64_t extended_topology_leaf_fp256_amd_support:1; + uint64_t gds_no_support:1; + uint64_t cmpccxadd_support:1; + uint64_t tsc_aux_virtualization_support:1; + uint64_t rmp_query_support:1; + uint64_t bhi_no_support:1; + uint64_t bhi_dis_support:1; + uint64_t prefetch_i_support:1; + uint64_t sha512_support:1; + uint64_t mitigation_ctrl_support:1; + uint64_t rfds_no_support:1; + uint64_t rfds_clear_support:1; + uint64_t sm3_support:1; + uint64_t sm4_support:1; + uint64_t secure_avic_support:1; + uint64_t guest_intercept_ctrl_support:1; + uint64_t sbpb_supported:1; + uint64_t ibpb_br_type_supported:1; + uint64_t srso_no_supported:1; + uint64_t srso_user_kernel_no_supported:1; + uint64_t vrew_clear_supported:1; + uint64_t tsa_l1_no_supported:1; + uint64_t tsa_sq_no_supported:1; + uint64_t lass_support:1; + uint64_t idle_hlt_intercept_support:1; + uint64_t msr_list_support:1; + } QEMU_PACKED; +}; + enum hv_translate_gva_result_code { HV_TRANSLATE_GVA_SUCCESS = 0, diff --git a/include/hw/i2c/designware_i2c.h b/include/hw/i2c/designware_i2c.h new file mode 100644 index 0000000000..4d5ff5d973 --- /dev/null +++ b/include/hw/i2c/designware_i2c.h @@ -0,0 +1,56 @@ +/* + * DesignWare I2C Module. + * + * Copyright 2021 Google LLC + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef DESIGNWARE_I2C_H +#define DESIGNWARE_I2C_H + +#include "qemu/fifo8.h" +#include "hw/i2c/i2c.h" +#include "hw/core/irq.h" +#include "hw/core/register.h" +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define DESIGNWARE_I2C_R_MAX (0x100 / 4) + +#define DESIGNWARE_I2C_RX_FIFO_SIZE 16 +#define DESIGNWARE_I2C_TX_FIFO_SIZE 16 + +typedef enum DesignWareI2CStatus { + DW_I2C_STATUS_IDLE, + DW_I2C_STATUS_SENDING_ADDRESS, + DW_I2C_STATUS_SENDING, + DW_I2C_STATUS_RECEIVING, +} DesignWareI2CStatus; + +#define TYPE_DESIGNWARE_I2C "designware-i2c" +OBJECT_DECLARE_SIMPLE_TYPE(DesignWareI2CState, DESIGNWARE_I2C) + +/* + * struct DesignWareI2CState - DesignWare I2C device state. + * @bus: The underlying I2C Bus + * @irq: Interrupt line fired on transaction events. + * @rx_fifo: The FIFO buffer for receiving in FIFO mode. + */ +struct DesignWareI2CState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + + I2CBus *bus; + qemu_irq irq; + + uint32_t regs[DESIGNWARE_I2C_R_MAX]; + RegisterInfo regs_info[DESIGNWARE_I2C_R_MAX]; + + /* fifo8_num_used(rx_fifo) should always equal DW_IC_RXFLR */ + Fifo8 rx_fifo; + + DesignWareI2CStatus status; +}; + +#endif /* DESIGNWARE_I2C_H */ diff --git a/include/hw/intc/arm_gicv5_types.h b/include/hw/intc/arm_gicv5_types.h index de4f78a149..851286b1b5 100644 --- a/include/hw/intc/arm_gicv5_types.h +++ b/include/hw/intc/arm_gicv5_types.h @@ -105,6 +105,10 @@ typedef struct GICv5PendingIrq { uint8_t prio; } GICv5PendingIrq; +/* A GICv5PendingIrq struct initializer for "no pending interrupt" */ +#define GICV5_PENDING_IRQ_NONE \ + ((GICv5PendingIrq) { .intid = 0, .prio = PRIO_IDLE }) + /* Fields in a generic 32-bit INTID, per R_TJPHS */ FIELD(INTID, ID, 0, 24) FIELD(INTID, TYPE, 29, 3) diff --git a/include/hw/misc/imx6_ccm.h b/include/hw/misc/imx6_ccm.h index ccf46d7353..a54b940686 100644 --- a/include/hw/misc/imx6_ccm.h +++ b/include/hw/misc/imx6_ccm.h @@ -164,6 +164,10 @@ #define PERCLK_PODF_SHIFT (0) #define PERCLK_PODF_LENGTH (6) +/* CCM_CSCMR2 */ +#define CAN_CLK_PODF_SHIFT (2) +#define CAN_CLK_PODF_LENGTH (6) + /* CCM_ANALOG_PFD_528 */ #define PFD0_FRAC_SHIFT (0) #define PFD0_FRAC_LENGTH (6) diff --git a/include/hw/misc/imx_ccm.h b/include/hw/misc/imx_ccm.h index c4212d04ea..6839716ea3 100644 --- a/include/hw/misc/imx_ccm.h +++ b/include/hw/misc/imx_ccm.h @@ -46,6 +46,7 @@ typedef enum { CLK_EXT, CLK_HIGH_DIV, CLK_HIGH, + CLK_CAN, } IMXClk; struct IMXCCMClass { diff --git a/include/hw/net/flexcan.h b/include/hw/net/flexcan.h new file mode 100644 index 0000000000..153afc1e03 --- /dev/null +++ b/include/hw/net/flexcan.h @@ -0,0 +1,145 @@ +/* + * QEMU model of the NXP FLEXCAN device. + * + * Copyright (c) 2025 Matyas Bobek <matyas.bobek@gmail.com> + * + * Based on CTU CAN FD emulation implemented by Jan Charvat. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_CAN_FLEXCAN_H +#define HW_CAN_FLEXCAN_H + +#include "net/can_emu.h" +#include "qom/object.h" +#include "hw/misc/imx_ccm.h" + +#define FLEXCAN_FIFO_DEPTH 6 +#define FLEXCAN_MAILBOX_COUNT 64 + +/** + * Definitions of structs FlexcanRegs and FlexcanRegsMessageBuffer were + * originally written for the Linux kernel by: + * Andrey Volkov <andrey@volkov.fr> + * Sascha Hauer <s.hauer@pengutronix.de> + * Marc Kleine-Budde <mkl@pengutronix.de> + * David Jander <david@protonic.nl> + * and they have agreed to license them under GPL-2.0-or-later. + */ + +/* view of single message buffer registers */ +typedef struct FlexcanRegsMessageBuffer { + uint32_t can_ctrl; + uint32_t can_id; + uint32_t data[2]; +} FlexcanRegsMessageBuffer; + +/* RX FIFO view of message buffer registers */ +typedef struct FlexcanRegsRXFifo { + /* 6 message buffer deep queue, queue back first */ + FlexcanRegsMessageBuffer mb_back; + FlexcanRegsMessageBuffer mbs_queue[FLEXCAN_FIFO_DEPTH - 1]; + + /* number of filter elements active depends on ctrl2 | FLEXCAN_CTRL2_RFFN */ + uint32_t filter_table_els[128]; +} FlexcanRegsRXFifo; + +/* FlexCAN register in hw layout */ +typedef struct FlexcanRegs { + uint32_t mcr; /* 0x00 */ + uint32_t ctrl; /* 0x04 - not affected by soft reset */ + uint32_t timer; /* 0x08 */ + uint32_t tcr; /* 0x0C */ + uint32_t rxmgmask; /* 0x10 - not affected by soft reset */ + uint32_t rx14mask; /* 0x14 - not affected by soft reset */ + uint32_t rx15mask; /* 0x18 - not affected by soft reset */ + uint32_t ecr; /* 0x1C */ + uint32_t esr; /* 0x20 */ + uint32_t imask2; /* 0x24 */ + uint32_t imask1; /* 0x28 */ + uint32_t iflag2; /* 0x2C */ + uint32_t iflag1; /* 0x30 */ + union { /* 0x34 */ + uint32_t gfwr_mx28; /* MX28, MX53 */ + uint32_t ctrl2; /* MX6, VF610 - not affected by soft reset */ + }; + uint32_t esr2; /* 0x38 */ + uint32_t imeur; /* 0x3C, unused */ + uint32_t lrfr; /* 0x40, unused */ + uint32_t crcr; /* 0x44 */ + uint32_t rxfgmask; /* 0x48 */ + uint32_t rxfir; /* 0x4C - not affected by soft reset */ + uint32_t cbt; /* 0x50, unused - not affected by soft reset */ + uint32_t _reserved2; /* 0x54 */ + uint32_t dbg1; /* 0x58, unused */ + uint32_t dbg2; /* 0x5C, unused */ + uint32_t _reserved3[8]; /* 0x60 */ + union { /* 0x80 - not affected by soft reset */ + uint32_t mb[sizeof(FlexcanRegsMessageBuffer) * FLEXCAN_MAILBOX_COUNT]; + FlexcanRegsMessageBuffer mbs[FLEXCAN_MAILBOX_COUNT]; + FlexcanRegsRXFifo fifo; + }; + uint32_t _reserved4[256]; /* 0x480 */ + uint32_t rximr[64]; /* 0x880 - not affected by soft reset */ + uint32_t _reserved5[24]; /* 0x980 */ + uint32_t gfwr_mx6; /* 0x9E0 - MX6 */ + + /* the rest is unused except for SMB */ + uint32_t _reserved6[39]; /* 0x9E4 */ + uint32_t _rxfir[6]; /* 0xA80 */ + uint32_t _reserved8[2]; /* 0xA98 */ + uint32_t _rxmgmask; /* 0xAA0 */ + uint32_t _rxfgmask; /* 0xAA4 */ + uint32_t _rx14mask; /* 0xAA8 */ + uint32_t _rx15mask; /* 0xAAC */ + uint32_t tx_smb[4]; /* 0xAB0 */ + union { /* 0xAC0, used for SMB emulation */ + uint32_t rx_smb0_raw[4]; + FlexcanRegsMessageBuffer rx_smb0; + }; + uint32_t rx_smb1[4]; /* 0xAD0 */ + uint32_t mecr; /* 0xAE0 */ + uint32_t erriar; /* 0xAE4 */ + uint32_t erridpr; /* 0xAE8 */ + uint32_t errippr; /* 0xAEC */ + uint32_t rerrar; /* 0xAF0 */ + uint32_t rerrdr; /* 0xAF4 */ + uint32_t rerrsynr; /* 0xAF8 */ + uint32_t errsr; /* 0xAFC */ + uint32_t _reserved7[64]; /* 0xB00 */ + uint32_t fdctrl; /* 0xC00 - not affected by soft reset */ + uint32_t fdcbt; /* 0xC04 - not affected by soft reset */ + uint32_t fdcrc; /* 0xC08 */ + uint32_t _reserved9[199]; /* 0xC0C */ + uint32_t tx_smb_fd[18]; /* 0xF28 */ + uint32_t rx_smb0_fd[18]; /* 0xF70 */ + uint32_t rx_smb1_fd[18]; /* 0xFB8 */ +} FlexcanRegs; + +typedef struct FlexcanState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + IMXCCMState *ccm; + qemu_irq irq; + + CanBusState *canbus; + CanBusClientState bus_client; + + union { + FlexcanRegs regs; + uint32_t regs_raw[sizeof(FlexcanRegs) / 4]; + }; + int64_t timer_start; + uint64_t last_rx_timer_cycles; + int32_t locked_mbidx; + int32_t smb_target_mbidx; + uint32_t timer_freq; +} FlexcanState; + +#define TYPE_CAN_FLEXCAN "flexcan" + +OBJECT_DECLARE_SIMPLE_TYPE(FlexcanState, CAN_FLEXCAN); + +#endif diff --git a/include/hw/riscv/boot.h b/include/hw/riscv/boot.h index f00b3ca122..4e7bd9a225 100644 --- a/include/hw/riscv/boot.h +++ b/include/hw/riscv/boot.h @@ -28,6 +28,10 @@ #define RISCV64_BIOS_BIN "opensbi-riscv64-generic-fw_dynamic.bin" typedef struct RISCVBootInfo { + /* First contiguous RAM region. If size is zero then assume entire RAM */ + hwaddr ram_low_start; + hwaddr ram_low_size; + ssize_t kernel_size; hwaddr image_low_addr; hwaddr image_high_addr; @@ -43,16 +47,22 @@ bool riscv_is_32bit(RISCVHartArrayState *harts); char *riscv_plic_hart_config_string(int hart_count); void riscv_boot_info_init(RISCVBootInfo *info, RISCVHartArrayState *harts); +void riscv_boot_info_init_discontig_mem(RISCVBootInfo *info, + RISCVHartArrayState *harts, + hwaddr low_start, hwaddr low_size); vaddr riscv_calc_kernel_start_addr(RISCVBootInfo *info, hwaddr firmware_end_addr); hwaddr riscv_find_and_load_firmware(MachineState *machine, + RISCVBootInfo *info, const char *default_machine_firmware, hwaddr *firmware_load_addr, symbol_fn_t sym_cb); const char *riscv_default_firmware_name(RISCVHartArrayState *harts); char *riscv_find_firmware(const char *firmware_filename, const char *default_machine_firmware); -hwaddr riscv_load_firmware(const char *firmware_filename, +hwaddr riscv_load_firmware(MachineState *machine, + const RISCVBootInfo *info, + const char *firmware_filename, hwaddr *firmware_load_addr, symbol_fn_t sym_cb); void riscv_load_kernel(MachineState *machine, diff --git a/include/hw/riscv/fdt-common.h b/include/hw/riscv/fdt-common.h index 2d6b9a5d03..017278b611 100644 --- a/include/hw/riscv/fdt-common.h +++ b/include/hw/riscv/fdt-common.h @@ -30,4 +30,9 @@ void create_fdt_socket_cpu_sifive(void *fdt, char *clust_name, int cpu_id, int socket_id, int socket_hartid_base, uint32_t *phandle, uint32_t *intc_phandles); +void create_fdt_plic(void *fdt, hwaddr addr, uint64_t size, + uint32_t plic_phandle, uint32_t int_cells, + uint32_t addr_cells, uint32_t *plic_cells, + uint32_t cells_size, uint32_t ndev_sources, + bool numa_enabled, int socket); #endif diff --git a/include/hw/riscv/sifive_u.h b/include/hw/riscv/sifive_u.h index e4c9860d50..aed966a62d 100644 --- a/include/hw/riscv/sifive_u.h +++ b/include/hw/riscv/sifive_u.h @@ -156,6 +156,8 @@ enum { #define SIFIVE_U_MANAGEMENT_CPU_COUNT 1 #define SIFIVE_U_COMPUTE_CPU_COUNT 4 +#define SIFIVE_U_PLIC_ADDR_CELLS 0 +#define SIFIVE_U_PLIC_INT_CELLS 1 #define SIFIVE_U_PLIC_NUM_SOURCES 54 #define SIFIVE_U_PLIC_NUM_PRIORITIES 7 #define SIFIVE_U_PLIC_PRIORITY_BASE 0x00 diff --git a/include/hw/riscv/tt_atlantis.h b/include/hw/riscv/tt_atlantis.h new file mode 100644 index 0000000000..7f7d4a5a59 --- /dev/null +++ b/include/hw/riscv/tt_atlantis.h @@ -0,0 +1,64 @@ +/* + * Tenstorrent Atlantis RISC-V System on Chip + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright 2025 Tenstorrent, Joel Stanley <joel@jms.id.au> + */ + +#ifndef HW_RISCV_TT_ATLANTIS_H +#define HW_RISCV_TT_ATLANTIS_H + +#include "hw/core/boards.h" +#include "hw/core/sysbus.h" +#include "hw/i2c/designware_i2c.h" +#include "hw/intc/riscv_imsic.h" +#include "hw/riscv/riscv_hart.h" + +#define TYPE_TT_ATLANTIS_MACHINE MACHINE_TYPE_NAME("tt-atlantis") +OBJECT_DECLARE_SIMPLE_TYPE(TTAtlantisState, TT_ATLANTIS_MACHINE) + +#define TT_ATL_NUM_I2C 5 + +struct TTAtlantisState { + /*< private >*/ + MachineState parent; + + /*< public >*/ + Notifier machine_done; + const MemMapEntry *memmap; + + RISCVHartArrayState soc; + DeviceState *irqchip; + DesignWareI2CState i2c[TT_ATL_NUM_I2C]; + + int fdt_size; +}; + +enum { + TT_ATL_I2C0_IRQ = 33, + TT_ATL_I2C1_IRQ = 34, + TT_ATL_I2C2_IRQ = 35, + TT_ATL_I2C3_IRQ = 36, + TT_ATL_I2C4_IRQ = 37, + TT_ATL_UART1_IRQ = 39, +}; + +enum { + TT_ATL_ACLINT, + TT_ATL_BOOTROM, + TT_ATL_DDR_LO, + TT_ATL_DDR_HI, + TT_ATL_I2C0, + TT_ATL_I2C1, + TT_ATL_I2C2, + TT_ATL_I2C3, + TT_ATL_I2C4, + TT_ATL_MAPLIC, + TT_ATL_MIMSIC, + TT_ATL_SAPLIC, + TT_ATL_SIMSIC, + TT_ATL_UART1, +}; + +#endif diff --git a/include/hw/riscv/virt.h b/include/hw/riscv/virt.h index 18a2a323a3..36a2def410 100644 --- a/include/hw/riscv/virt.h +++ b/include/hw/riscv/virt.h @@ -64,6 +64,7 @@ struct RISCVVirtState { struct GPEXHost *gpex_host; OnOffAuto iommu_sys; uint16_t pci_iommu_bdf; + uint16_t num_sources; }; enum { @@ -135,7 +136,6 @@ enum { bool virt_is_acpi_enabled(RISCVVirtState *s); bool virt_is_iommu_sys_enabled(RISCVVirtState *s); void virt_acpi_setup(RISCVVirtState *vms); -uint32_t imsic_num_bits(uint32_t count); /* * The virt machine physical address space used by some of the devices diff --git a/include/hw/vfio/vfio-container.h b/include/hw/vfio/vfio-container.h index a7d5c5ed67..b2e7f4312c 100644 --- a/include/hw/vfio/vfio-container.h +++ b/include/hw/vfio/vfio-container.h @@ -277,7 +277,7 @@ struct VFIOIOMMUClass { }; VFIORamDiscardListener *vfio_find_ram_discard_listener( - VFIOContainer *bcontainer, MemoryRegionSection *section); + VFIOContainer *bcontainer, const MemoryRegionSection *section); void vfio_container_region_add(VFIOContainer *bcontainer, MemoryRegionSection *section, bool cpr_remap); diff --git a/include/hw/vfio/vfio-cpr.h b/include/hw/vfio/vfio-cpr.h index 4606da500a..ecabe0c747 100644 --- a/include/hw/vfio/vfio-cpr.h +++ b/include/hw/vfio/vfio-cpr.h @@ -69,7 +69,7 @@ void vfio_cpr_giommu_remap(struct VFIOContainer *bcontainer, MemoryRegionSection *section); bool vfio_cpr_ram_discard_replay_populated( - struct VFIOContainer *bcontainer, MemoryRegionSection *section); + struct VFIOContainer *bcontainer, const MemoryRegionSection *section); void vfio_cpr_save_vector_fd(struct VFIOPCIDevice *vdev, const char *name, int nr, int fd); diff --git a/include/hw/virtio/virtio-mem.h b/include/hw/virtio/virtio-mem.h index 221cfd76bf..5d1d19c6be 100644 --- a/include/hw/virtio/virtio-mem.h +++ b/include/hw/virtio/virtio-mem.h @@ -118,9 +118,6 @@ struct VirtIOMEM { /* notifiers to notify when "size" changes */ NotifierList size_change_notifiers; - /* listeners to notify on plug/unplug activity. */ - QLIST_HEAD(, RamDiscardListener) rdl_list; - /* Catch system resets -> qemu_devices_reset() only. */ VirtioMemSystemReset *system_reset; }; diff --git a/include/migration/misc.h b/include/migration/misc.h index 3159a5e53c..d3d6e1f50d 100644 --- a/include/migration/misc.h +++ b/include/migration/misc.h @@ -117,6 +117,9 @@ void migration_file_set_error(int ret, Error *err); /* True if incoming migration entered POSTCOPY_INCOMING_DISCARD */ bool migration_in_incoming_postcopy(void); +/* True while the destination still receives guest RAM (precopy or postcopy) */ +bool migration_guest_ram_loading(void); + /* True if incoming migration entered POSTCOPY_INCOMING_ADVISE */ bool migration_incoming_postcopy_advised(void); diff --git a/include/qobject/json-parser.h b/include/qobject/json-parser.h index 7345a9bd5c..e078b36b2d 100644 --- a/include/qobject/json-parser.h +++ b/include/qobject/json-parser.h @@ -17,17 +17,25 @@ typedef struct JSONLexer { int start_state, state; GString *token; + int cur_x, cur_y; int x, y; } JSONLexer; +typedef struct JSONParserContext { + Error *err; + GQueue *stack; + va_list *ap; +} JSONParserContext; + typedef struct JSONMessageParser { void (*emit)(void *opaque, QObject *json, Error *err); void *opaque; - va_list *ap; JSONLexer lexer; - int brace_count; - int bracket_count; - GQueue tokens; + JSONParserContext parser; + unsigned int brace_count; + unsigned int bracket_count; + unsigned int token_count; + bool error; uint64_t token_size; } JSONMessageParser; diff --git a/include/system/accel-irq.h b/include/system/accel-irq.h index a2caa06f54..fc94c54264 100644 --- a/include/system/accel-irq.h +++ b/include/system/accel-irq.h @@ -25,9 +25,11 @@ static inline bool accel_irqchip_is_split(void) return mshv_msi_via_irqfd_enabled() || kvm_irqchip_is_split(); } -int accel_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev); +AccelRouteChange accel_irqchip_begin_route_changes(void); +int accel_irqchip_add_msi_route(AccelRouteChange *c, int vector, + PCIDevice *dev); int accel_irqchip_update_msi_route(int vector, MSIMessage msg, PCIDevice *dev); -void accel_irqchip_commit_route_changes(KVMRouteChange *c); +void accel_irqchip_commit_route_changes(AccelRouteChange *c); void accel_irqchip_commit_routes(void); void accel_irqchip_release_virq(int virq); int accel_irqchip_add_irqfd_notifier_gsi(EventNotifier *n, EventNotifier *rn, diff --git a/include/system/kvm.h b/include/system/kvm.h index 5fa33eddda..cdd1856ac5 100644 --- a/include/system/kvm.h +++ b/include/system/kvm.h @@ -18,6 +18,7 @@ #include "exec/memattrs.h" #include "qemu/accel.h" +#include "accel/accel-route.h" #include "qom/object.h" #ifdef COMPILING_PER_TARGET @@ -183,11 +184,6 @@ extern KVMState *kvm_state; typedef struct Notifier Notifier; typedef struct NotifierWithReturn NotifierWithReturn; -typedef struct KVMRouteChange { - KVMState *s; - int changes; -} KVMRouteChange; - /* external API */ unsigned int kvm_get_max_memslots(void); @@ -466,7 +462,7 @@ void kvm_init_cpu_signals(CPUState *cpu); /** * kvm_irqchip_add_msi_route - Add MSI route for specific vector - * @c: KVMRouteChange instance. + * @c: AccelRouteChange instance. * @vector: which vector to add. This can be either MSI/MSIX * vector. The function will automatically detect whether * MSI/MSIX is enabled, and fetch corresponding MSI @@ -475,24 +471,11 @@ void kvm_init_cpu_signals(CPUState *cpu); * as @NULL, an empty MSI message will be inited. * @return: virq (>=0) when success, errno (<0) when failed. */ -int kvm_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev); +int kvm_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev); int kvm_irqchip_update_msi_route(KVMState *s, int virq, MSIMessage msg, PCIDevice *dev); void kvm_irqchip_commit_routes(KVMState *s); -static inline KVMRouteChange kvm_irqchip_begin_route_changes(KVMState *s) -{ - return (KVMRouteChange) { .s = s, .changes = 0 }; -} - -static inline void kvm_irqchip_commit_route_changes(KVMRouteChange *c) -{ - if (c->changes) { - kvm_irqchip_commit_routes(c->s); - c->changes = 0; - } -} - int kvm_irqchip_get_virq(KVMState *s); void kvm_irqchip_release_virq(KVMState *s, int virq); diff --git a/include/system/memory.h b/include/system/memory.h index e143c9c3f3..47a0e06fbf 100644 --- a/include/system/memory.h +++ b/include/system/memory.h @@ -16,6 +16,7 @@ #include "exec/hwaddr.h" #include "system/ram_addr.h" +#include "system/ram-discard-manager.h" #include "exec/memattrs.h" #include "exec/memop.h" #include "qemu/bswap.h" @@ -36,9 +37,6 @@ enum device_endian { #define RAM_ADDR_INVALID (~(ram_addr_t)0) -#define MAX_PHYS_ADDR_SPACE_BITS 62 -#define MAX_PHYS_ADDR (((hwaddr)1 << MAX_PHYS_ADDR_SPACE_BITS) - 1) - #define TYPE_MEMORY_REGION "memory-region" DECLARE_INSTANCE_CHECKER(MemoryRegion, MEMORY_REGION, TYPE_MEMORY_REGION) @@ -48,12 +46,6 @@ typedef struct IOMMUMemoryRegionClass IOMMUMemoryRegionClass; DECLARE_OBJ_CHECKERS(IOMMUMemoryRegion, IOMMUMemoryRegionClass, IOMMU_MEMORY_REGION, TYPE_IOMMU_MEMORY_REGION) -#define TYPE_RAM_DISCARD_MANAGER "ram-discard-manager" -typedef struct RamDiscardManagerClass RamDiscardManagerClass; -typedef struct RamDiscardManager RamDiscardManager; -DECLARE_OBJ_CHECKERS(RamDiscardManager, RamDiscardManagerClass, - RAM_DISCARD_MANAGER, TYPE_RAM_DISCARD_MANAGER); - #ifdef CONFIG_FUZZ void fuzz_dma_read_cb(size_t addr, size_t len, @@ -542,258 +534,6 @@ struct IOMMUMemoryRegionClass { int (*num_indexes)(IOMMUMemoryRegion *iommu); }; -typedef struct RamDiscardListener RamDiscardListener; -typedef int (*NotifyRamPopulate)(RamDiscardListener *rdl, - MemoryRegionSection *section); -typedef void (*NotifyRamDiscard)(RamDiscardListener *rdl, - MemoryRegionSection *section); - -struct RamDiscardListener { - /* - * @notify_populate: - * - * Notification that previously discarded memory is about to get populated. - * Listeners are able to object. If any listener objects, already - * successfully notified listeners are notified about a discard again. - * - * @rdl: the #RamDiscardListener getting notified - * @section: the #MemoryRegionSection to get populated. The section - * is aligned within the memory region to the minimum granularity - * unless it would exceed the registered section. - * - * Returns 0 on success. If the notification is rejected by the listener, - * an error is returned. - */ - NotifyRamPopulate notify_populate; - - /* - * @notify_discard: - * - * Notification that previously populated memory was discarded successfully - * and listeners should drop all references to such memory and prevent - * new population (e.g., unmap). - * - * @rdl: the #RamDiscardListener getting notified - * @section: the #MemoryRegionSection to get discarded. The section - * is aligned within the memory region to the minimum granularity - * unless it would exceed the registered section. - */ - NotifyRamDiscard notify_discard; - - MemoryRegionSection *section; - QLIST_ENTRY(RamDiscardListener) next; -}; - -static inline void ram_discard_listener_init(RamDiscardListener *rdl, - NotifyRamPopulate populate_fn, - NotifyRamDiscard discard_fn) -{ - rdl->notify_populate = populate_fn; - rdl->notify_discard = discard_fn; -} - -/** - * typedef ReplayRamDiscardState: - * - * The callback handler for #RamDiscardManagerClass.replay_populated/ - * #RamDiscardManagerClass.replay_discarded to invoke on populated/discarded - * parts. - * - * @section: the #MemoryRegionSection of populated/discarded part - * @opaque: pointer to forward to the callback - * - * Returns 0 on success, or a negative error if failed. - */ -typedef int (*ReplayRamDiscardState)(MemoryRegionSection *section, - void *opaque); - -/* - * RamDiscardManagerClass: - * - * A #RamDiscardManager coordinates which parts of specific RAM #MemoryRegion - * regions are currently populated to be used/accessed by the VM, notifying - * after parts were discarded (freeing up memory) and before parts will be - * populated (consuming memory), to be used/accessed by the VM. - * - * A #RamDiscardManager can only be set for a RAM #MemoryRegion while the - * #MemoryRegion isn't mapped into an address space yet (either directly - * or via an alias); it cannot change while the #MemoryRegion is - * mapped into an address space. - * - * The #RamDiscardManager is intended to be used by technologies that are - * incompatible with discarding of RAM (e.g., VFIO, which may pin all - * memory inside a #MemoryRegion), and require proper coordination to only - * map the currently populated parts, to hinder parts that are expected to - * remain discarded from silently getting populated and consuming memory. - * Technologies that support discarding of RAM don't have to bother and can - * simply map the whole #MemoryRegion. - * - * An example #RamDiscardManager is virtio-mem, which logically (un)plugs - * memory within an assigned RAM #MemoryRegion, coordinated with the VM. - * Logically unplugging memory consists of discarding RAM. The VM agreed to not - * access unplugged (discarded) memory - especially via DMA. virtio-mem will - * properly coordinate with listeners before memory is plugged (populated), - * and after memory is unplugged (discarded). - * - * Listeners are called in multiples of the minimum granularity (unless it - * would exceed the registered range) and changes are aligned to the minimum - * granularity within the #MemoryRegion. Listeners have to prepare for memory - * becoming discarded in a different granularity than it was populated and the - * other way around. - */ -struct RamDiscardManagerClass { - /* private */ - InterfaceClass parent_class; - - /* public */ - - /** - * @get_min_granularity: - * - * Get the minimum granularity in which listeners will get notified - * about changes within the #MemoryRegion via the #RamDiscardManager. - * - * @rdm: the #RamDiscardManager - * @mr: the #MemoryRegion - * - * Returns the minimum granularity. - */ - uint64_t (*get_min_granularity)(const RamDiscardManager *rdm, - const MemoryRegion *mr); - - /** - * @is_populated: - * - * Check whether the given #MemoryRegionSection is completely populated - * (i.e., no parts are currently discarded) via the #RamDiscardManager. - * There are no alignment requirements. - * - * @rdm: the #RamDiscardManager - * @section: the #MemoryRegionSection - * - * Returns whether the given range is completely populated. - */ - bool (*is_populated)(const RamDiscardManager *rdm, - const MemoryRegionSection *section); - - /** - * @replay_populated: - * - * Call the #ReplayRamDiscardState callback for all populated parts within - * the #MemoryRegionSection via the #RamDiscardManager. - * - * In case any call fails, no further calls are made. - * - * @rdm: the #RamDiscardManager - * @section: the #MemoryRegionSection - * @replay_fn: the #ReplayRamDiscardState callback - * @opaque: pointer to forward to the callback - * - * Returns 0 on success, or a negative error if any notification failed. - */ - int (*replay_populated)(const RamDiscardManager *rdm, - MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, void *opaque); - - /** - * @replay_discarded: - * - * Call the #ReplayRamDiscardState callback for all discarded parts within - * the #MemoryRegionSection via the #RamDiscardManager. - * - * @rdm: the #RamDiscardManager - * @section: the #MemoryRegionSection - * @replay_fn: the #ReplayRamDiscardState callback - * @opaque: pointer to forward to the callback - * - * Returns 0 on success, or a negative error if any notification failed. - */ - int (*replay_discarded)(const RamDiscardManager *rdm, - MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, void *opaque); - - /** - * @register_listener: - * - * Register a #RamDiscardListener for the given #MemoryRegionSection and - * immediately notify the #RamDiscardListener about all populated parts - * within the #MemoryRegionSection via the #RamDiscardManager. - * - * In case any notification fails, no further notifications are triggered - * and an error is logged. - * - * @rdm: the #RamDiscardManager - * @rdl: the #RamDiscardListener - * @section: the #MemoryRegionSection - */ - void (*register_listener)(RamDiscardManager *rdm, - RamDiscardListener *rdl, - MemoryRegionSection *section); - - /** - * @unregister_listener: - * - * Unregister a previously registered #RamDiscardListener via the - * #RamDiscardManager after notifying the #RamDiscardListener about all - * populated parts becoming unpopulated within the registered - * #MemoryRegionSection. - * - * @rdm: the #RamDiscardManager - * @rdl: the #RamDiscardListener - */ - void (*unregister_listener)(RamDiscardManager *rdm, - RamDiscardListener *rdl); -}; - -uint64_t ram_discard_manager_get_min_granularity(const RamDiscardManager *rdm, - const MemoryRegion *mr); - -bool ram_discard_manager_is_populated(const RamDiscardManager *rdm, - const MemoryRegionSection *section); - -/** - * ram_discard_manager_replay_populated: - * - * A wrapper to call the #RamDiscardManagerClass.replay_populated callback - * of the #RamDiscardManager. - * - * @rdm: the #RamDiscardManager - * @section: the #MemoryRegionSection - * @replay_fn: the #ReplayRamDiscardState callback - * @opaque: pointer to forward to the callback - * - * Returns 0 on success, or a negative error if any notification failed. - */ -int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, - MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, - void *opaque); - -/** - * ram_discard_manager_replay_discarded: - * - * A wrapper to call the #RamDiscardManagerClass.replay_discarded callback - * of the #RamDiscardManager. - * - * @rdm: the #RamDiscardManager - * @section: the #MemoryRegionSection - * @replay_fn: the #ReplayRamDiscardState callback - * @opaque: pointer to forward to the callback - * - * Returns 0 on success, or a negative error if any notification failed. - */ -int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, - MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, - void *opaque); - -void ram_discard_manager_register_listener(RamDiscardManager *rdm, - RamDiscardListener *rdl, - MemoryRegionSection *section); - -void ram_discard_manager_unregister_listener(RamDiscardManager *rdm, - RamDiscardListener *rdl); - /** * memory_translate_iotlb: Extract addresses from a TLB entry. * Called with rcu_read_lock held. @@ -2507,18 +2247,24 @@ static inline bool memory_region_has_ram_discard_manager(MemoryRegion *mr) } /** - * memory_region_set_ram_discard_manager: set the #RamDiscardManager for a + * memory_region_add_ram_discard_source: add a #RamDiscardSource for a * #MemoryRegion * - * This function must not be called for a mapped #MemoryRegion, a #MemoryRegion - * that does not cover RAM, or a #MemoryRegion that already has a - * #RamDiscardManager assigned. Return 0 if the rdm is set successfully. + * @mr: the #MemoryRegion + * @source: #RamDiscardSource to add + */ +int memory_region_add_ram_discard_source(MemoryRegion *mr, RamDiscardSource *source); + +/** + * memory_region_del_ram_discard_source: remove a #RamDiscardSource for a + * #MemoryRegion * * @mr: the #MemoryRegion - * @rdm: #RamDiscardManager to set + * @source: #RamDiscardSource to remove + * + * Returns: 0 on success, or a negative error code on failure. */ -int memory_region_set_ram_discard_manager(MemoryRegion *mr, - RamDiscardManager *rdm); +int memory_region_del_ram_discard_source(MemoryRegion *mr, RamDiscardSource *source); /** * memory_region_find: translate an address/size relative to a diff --git a/include/system/mshv.h b/include/system/mshv.h index 75286baf16..46fe3fcebc 100644 --- a/include/system/mshv.h +++ b/include/system/mshv.h @@ -21,6 +21,7 @@ #include "qapi/qapi-types-common.h" #include "system/memory.h" #include "accel/accel-ops.h" +#include "accel/accel-route.h" #ifdef COMPILING_PER_TARGET #ifdef CONFIG_MSHV @@ -32,6 +33,7 @@ #endif #define MSHV_MAX_MSI_ROUTES 4096 +#define MSHV_MIN_ALLOCATED_MSI_ROUTES 64 #define MSHV_PAGE_SHIFT 12 @@ -44,7 +46,13 @@ extern bool mshv_allowed; #define mshv_msi_via_irqfd_enabled() mshv_enabled() #endif +#define TYPE_MSHV_ACCEL ACCEL_CLASS_NAME("mshv") + typedef struct MshvState MshvState; + +DECLARE_INSTANCE_CHECKER(MshvState, MSHV_STATE, + TYPE_MSHV_ACCEL) + extern MshvState *mshv_state; /* interrupt */ @@ -52,12 +60,16 @@ int mshv_request_interrupt(MshvState *mshv_state, uint32_t interrupt_type, uint3 uint32_t vp_index, bool logical_destination_mode, bool level_triggered); -int mshv_irqchip_add_msi_route(int vector, PCIDevice *dev); +int mshv_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev); int mshv_irqchip_update_msi_route(int virq, MSIMessage msg, PCIDevice *dev); -void mshv_irqchip_commit_routes(void); -void mshv_irqchip_release_virq(int virq); +void mshv_irqchip_release_virq(MshvState *s, int virq); +void mshv_irqchip_commit_routes(MshvState *s); int mshv_irqchip_add_irqfd_notifier_gsi(const EventNotifier *n, const EventNotifier *rn, int virq); int mshv_irqchip_remove_irqfd_notifier_gsi(const EventNotifier *n, int virq); +void mshv_init_irq_routing(MshvState *s); + +/* cpuid */ +uint32_t mshv_get_supported_cpuid(uint32_t func, uint32_t idx, int reg); #endif diff --git a/include/system/mshv_int.h b/include/system/mshv_int.h index 35386c422f..ba3073888a 100644 --- a/include/system/mshv_int.h +++ b/include/system/mshv_int.h @@ -14,8 +14,25 @@ #ifndef QEMU_MSHV_INT_H #define QEMU_MSHV_INT_H +#include "hw/hyperv/hvhdk.h" + #define MSHV_MSR_ENTRIES_COUNT 64 +/* + * Interruption-type encoding, used by the hypervisor in + * hv_x64_pending_interruption_register.interruption_type + * See TLFS 6.0 section 7.9.2, p55 + * https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/tlfs + */ +#define MSHV_HV_INTERRUPTION_TYPE_EXT_INT 0 +#define MSHV_HV_INTERRUPTION_TYPE_NMI 2 +#define MSHV_HV_INTERRUPTION_TYPE_HW_EXC 3 +#define MSHV_HV_INTERRUPTION_TYPE_SW_INT 4 +#define MSHV_HV_INTERRUPTION_TYPE_PRIV_SW_EXC 5 +#define MSHV_HV_INTERRUPTION_TYPE_SW_EXC 6 + +#define MSHV_DIRTY_PAGES_BATCH_SIZE 0x10000 + typedef struct hyperv_message hv_message; typedef struct MshvHvCallArgs { @@ -48,6 +65,12 @@ struct MshvState { int nr_as; MshvAddressSpace *as; int fd; + /* irqchip routing */ + struct mshv_user_irq_table *irq_routes; + int nr_allocated_irq_routes; + unsigned long *used_gsi_bitmap; + unsigned int gsi_count; + union hv_partition_processor_features processor_features; }; typedef struct MshvMsiControl { @@ -58,19 +81,6 @@ typedef struct MshvMsiControl { #define mshv_vcpufd(cpu) (cpu->accel->cpufd) /* cpu */ -typedef struct MshvFPU { - uint8_t fpr[8][16]; - uint16_t fcw; - uint16_t fsw; - uint8_t ftwx; - uint8_t pad1; - uint16_t last_opcode; - uint64_t last_ip; - uint64_t last_dp; - uint8_t xmm[16][16]; - uint32_t mxcsr; - uint32_t pad2; -} MshvFPU; typedef enum MshvVmExit { MshvVmExitIgnore = 0, @@ -81,19 +91,20 @@ typedef enum MshvVmExit { void mshv_init_mmio_emu(void); int mshv_create_vcpu(int vm_fd, uint8_t vp_index, int *cpu_fd); void mshv_remove_vcpu(int vm_fd, int cpu_fd); -int mshv_configure_vcpu(const CPUState *cpu, const MshvFPU *fpu, uint64_t xcr0); -int mshv_get_standard_regs(CPUState *cpu); -int mshv_get_special_regs(CPUState *cpu); +int mshv_configure_vcpu(const CPUState *cpu); int mshv_run_vcpu(int vm_fd, CPUState *cpu, hv_message *msg, MshvVmExit *exit); -int mshv_load_regs(CPUState *cpu); -int mshv_store_regs(CPUState *cpu); int mshv_set_generic_regs(const CPUState *cpu, const hv_register_assoc *assocs, size_t n_regs); -int mshv_arch_put_registers(const CPUState *cpu); +int mshv_get_generic_regs(CPUState *cpu, hv_register_assoc *assocs, + size_t n_regs); +int mshv_arch_store_vcpu_state(const CPUState *cpu); +int mshv_arch_load_vcpu_state(CPUState *cpu); void mshv_arch_init_vcpu(CPUState *cpu); void mshv_arch_destroy_vcpu(CPUState *cpu); void mshv_arch_amend_proc_features( union hv_partition_synthetic_processor_features *features); +void mshv_arch_disable_partition_proc_features( + union hv_partition_processor_features *disabled_features); int mshv_arch_post_init_vm(int vm_fd); typedef struct mshv_root_hvcall mshv_root_hvcall; @@ -113,24 +124,13 @@ int mshv_guest_mem_write(uint64_t gpa, const uint8_t *data, uintptr_t size, bool is_secure_mode); void mshv_set_phys_mem(MshvMemoryListener *mml, MemoryRegionSection *section, bool add); +void mshv_log_sync(MemoryListener *listener, MemoryRegionSection *section); +bool mshv_log_global_start(MemoryListener *listener, Error **errp); +void mshv_log_global_stop(MemoryListener *listener); /* msr */ -typedef struct MshvMsrEntry { - uint32_t index; - uint32_t reserved; - uint64_t data; -} MshvMsrEntry; - -typedef struct MshvMsrEntries { - MshvMsrEntry entries[MSHV_MSR_ENTRIES_COUNT]; - uint32_t nmsrs; -} MshvMsrEntries; - -int mshv_configure_msr(const CPUState *cpu, const MshvMsrEntry *msrs, - size_t n_msrs); - -/* interrupt */ -void mshv_init_msicontrol(void); -int mshv_reserve_ioapic_msi_routes(int vm_fd); +int mshv_init_msrs(const CPUState *cpu); +int mshv_get_msrs(CPUState *cpu); +int mshv_set_msrs(const CPUState *cpu); #endif diff --git a/include/system/ram-discard-manager.h b/include/system/ram-discard-manager.h new file mode 100644 index 0000000000..05d3d31b55 --- /dev/null +++ b/include/system/ram-discard-manager.h @@ -0,0 +1,358 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * RAM Discard Manager + * + * Copyright Red Hat, Inc. 2026 + */ + +#ifndef RAM_DISCARD_MANAGER_H +#define RAM_DISCARD_MANAGER_H + +#include "qemu/typedefs.h" +#include "qom/object.h" +#include "qemu/queue.h" + +#define TYPE_RAM_DISCARD_MANAGER "ram-discard-manager" +typedef struct RamDiscardManagerClass RamDiscardManagerClass; +typedef struct RamDiscardManager RamDiscardManager; +DECLARE_OBJ_CHECKERS(RamDiscardManager, RamDiscardManagerClass, + RAM_DISCARD_MANAGER, TYPE_RAM_DISCARD_MANAGER); + +#define TYPE_RAM_DISCARD_SOURCE "ram-discard-source" +typedef struct RamDiscardSourceClass RamDiscardSourceClass; +typedef struct RamDiscardSource RamDiscardSource; +DECLARE_OBJ_CHECKERS(RamDiscardSource, RamDiscardSourceClass, + RAM_DISCARD_SOURCE, TYPE_RAM_DISCARD_SOURCE); + +typedef struct RamDiscardListener RamDiscardListener; +typedef int (*NotifyRamPopulate)(RamDiscardListener *rdl, + const MemoryRegionSection *section); +typedef void (*NotifyRamDiscard)(RamDiscardListener *rdl, + const MemoryRegionSection *section); + +struct RamDiscardListener { + /* + * @notify_populate: + * + * Notification that previously discarded memory is about to get populated. + * Listeners are able to object. If any listener objects, already + * successfully notified listeners are notified about a discard again. + * + * @rdl: the #RamDiscardListener getting notified + * @section: the #MemoryRegionSection to get populated. The section + * is aligned within the memory region to the minimum granularity + * unless it would exceed the registered section. + * + * Returns 0 on success. If the notification is rejected by the listener, + * an error is returned. + */ + NotifyRamPopulate notify_populate; + + /* + * @notify_discard: + * + * Notification that previously populated memory was discarded successfully + * and listeners should drop all references to such memory and prevent + * new population (e.g., unmap). + * + * @rdl: the #RamDiscardListener getting notified + * @section: the #MemoryRegionSection to get discarded. The section + * is aligned within the memory region to the minimum granularity + * unless it would exceed the registered section. + */ + NotifyRamDiscard notify_discard; + + MemoryRegionSection *section; + QLIST_ENTRY(RamDiscardListener) next; +}; + +static inline void ram_discard_listener_init(RamDiscardListener *rdl, + NotifyRamPopulate populate_fn, + NotifyRamDiscard discard_fn) +{ + rdl->notify_populate = populate_fn; + rdl->notify_discard = discard_fn; +} + +/** + * typedef ReplayRamDiscardState: + * + * The callback handler used by ram_discard_manager_replay_populated() and + * ram_discard_manager_replay_discarded() to invoke on populated/discarded + * parts. + * + * @section: the #MemoryRegionSection of populated/discarded part + * @opaque: pointer to forward to the callback + * + * Returns 0 on success, or a negative error if failed. + */ +typedef int (*ReplayRamDiscardState)(const MemoryRegionSection *section, + void *opaque); + +/* + * RamDiscardSourceClass: + * + * A #RamDiscardSource provides information about which parts of a specific + * RAM #MemoryRegion are currently populated (accessible) vs discarded. + * + * This is an interface that state providers (like virtio-mem or + * RamBlockAttributes) implement to provide discard state information. A + * #RamDiscardManager wraps sources and manages listener registrations and + * notifications. + */ +struct RamDiscardSourceClass { + /* private */ + InterfaceClass parent_class; + + /* public */ + + /** + * @get_min_granularity: + * + * Get the minimum granularity in which listeners will get notified + * about changes within the #MemoryRegion via the #RamDiscardSource. + * + * @rds: the #RamDiscardSource + * @mr: the #MemoryRegion + * + * Returns the minimum granularity. + */ + uint64_t (*get_min_granularity)(const RamDiscardSource *rds, + const MemoryRegion *mr); + + /** + * @is_populated: + * + * Check whether the given #MemoryRegionSection is completely populated + * (i.e., no parts are currently discarded) via the #RamDiscardSource. + * There are no alignment requirements. + * + * @rds: the #RamDiscardSource + * @section: the #MemoryRegionSection + * + * Returns whether the given range is completely populated. + */ + bool (*is_populated)(const RamDiscardSource *rds, + const MemoryRegionSection *section); +}; + +/** + * RamDiscardManager: + * + * A #RamDiscardManager coordinates which parts of specific RAM #MemoryRegion + * regions are currently populated to be used/accessed by the VM, notifying + * after parts were discarded (freeing up memory) and before parts will be + * populated (consuming memory), to be used/accessed by the VM. + * + * A #RamDiscardManager can only be set for a RAM #MemoryRegion while the + * #MemoryRegion isn't mapped into an address space yet (either directly + * or via an alias); it cannot change while the #MemoryRegion is + * mapped into an address space. + * + * The #RamDiscardManager is intended to be used by technologies that are + * incompatible with discarding of RAM (e.g., VFIO, which may pin all + * memory inside a #MemoryRegion), and require proper coordination to only + * map the currently populated parts, to hinder parts that are expected to + * remain discarded from silently getting populated and consuming memory. + * Technologies that support discarding of RAM don't have to bother and can + * simply map the whole #MemoryRegion. + * + * An example #RamDiscardSource is virtio-mem, which logically (un)plugs + * memory within an assigned RAM #MemoryRegion, coordinated with the VM. + * Logically unplugging memory consists of discarding RAM. The VM agreed to not + * access unplugged (discarded) memory - especially via DMA. virtio-mem will + * properly coordinate with listeners before memory is plugged (populated), + * and after memory is unplugged (discarded). + * + * Listeners are called in multiples of the minimum granularity (unless it + * would exceed the registered range) and changes are aligned to the minimum + * granularity within the #MemoryRegion. Listeners have to prepare for memory + * becoming discarded in a different granularity than it was populated and the + * other way around. + */ + +typedef struct RamDiscardSourceEntry RamDiscardSourceEntry; + +struct RamDiscardSourceEntry { + RamDiscardSource *rds; + QLIST_ENTRY(RamDiscardSourceEntry) next; +}; + +struct RamDiscardManager { + Object parent; + + MemoryRegion *mr; + QLIST_HEAD(, RamDiscardSourceEntry) source_list; + uint64_t min_granularity; + QLIST_HEAD(, RamDiscardListener) rdl_list; +}; + +RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr); + +/** + * ram_discard_manager_add_source: + * + * Register a #RamDiscardSource with the #RamDiscardManager. The manager + * aggregates state from all registered sources using AND semantics: a region + * is considered populated only if ALL sources report it as populated. + * + * If listeners are already registered, they will be notified about any + * regions that become discarded due to adding this source. Specifically, + * for each region that the new source reports as discarded, if all other + * sources reported it as populated, listeners receive a discard notification. + * + * If any listener rejects the notification (returns an error), previously + * notified listeners are rolled back with populate notifications and the + * source is not added. + * + * @rdm: the #RamDiscardManager + * @source: the #RamDiscardSource to add + * + * Returns: 0 on success, -EBUSY if @source is already registered, or a + * negative error code if a listener rejected the state change. + */ +int ram_discard_manager_add_source(RamDiscardManager *rdm, + RamDiscardSource *source); + +/** + * ram_discard_manager_del_source: + * + * Unregister a #RamDiscardSource from the #RamDiscardManager. + * + * If listeners are already registered, they will be notified about any + * regions that become populated due to removing this source. Specifically, + * for each region that the removed source reported as discarded, if all + * remaining sources report it as populated, listeners receive a populate + * notification. + * + * If any listener rejects the notification (returns an error), previously + * notified listeners are rolled back with discard notifications and the + * source is not removed. + * + * @rdm: the #RamDiscardManager + * @source: the #RamDiscardSource to remove + * + * Returns: 0 on success, -ENOENT if @source is not registered, or a + * negative error code if a listener rejected the state change. + */ +int ram_discard_manager_del_source(RamDiscardManager *rdm, + RamDiscardSource *source); + + +uint64_t ram_discard_manager_get_min_granularity(const RamDiscardManager *rdm, + const MemoryRegion *mr); + +/** + * ram_discard_manager_is_populated: + * + * Check if the given memory region section is populated. + * If the manager has no sources, it is considered populated. + * + * @rdm: the #RamDiscardManager + * @section: the #MemoryRegionSection to check + * + * Returns: true if the section is populated, false otherwise. + */ +bool ram_discard_manager_is_populated(const RamDiscardManager *rdm, + const MemoryRegionSection *section); + +/** + * ram_discard_manager_replay_populated: + * + * Call @replay_fn on regions that are populated in all sources. + * + * @rdm: the #RamDiscardManager + * @section: the #MemoryRegionSection + * @replay_fn: the #ReplayRamDiscardState callback + * @opaque: pointer to forward to the callback + * + * Returns 0 on success, or a negative error if any notification failed. + */ +int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, + const MemoryRegionSection *section, + ReplayRamDiscardState replay_fn, + void *opaque); + +/** + * ram_discard_manager_replay_discarded: + * + * Call @replay_fn on regions that are discarded in any sources. + * + * @rdm: the #RamDiscardManager + * @section: the #MemoryRegionSection + * @replay_fn: the #ReplayRamDiscardState callback + * @opaque: pointer to forward to the callback + * + * Returns 0 on success, or a negative error if any notification failed. + */ +int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, + const MemoryRegionSection *section, + ReplayRamDiscardState replay_fn, + void *opaque); + +void ram_discard_manager_register_listener(RamDiscardManager *rdm, + RamDiscardListener *rdl, + MemoryRegionSection *section); + +void ram_discard_manager_unregister_listener(RamDiscardManager *rdm, + RamDiscardListener *rdl); + +/** + * ram_discard_manager_notify_populate: + * + * Notify listeners that a region is about to be populated by a source. + * For multi-source aggregation, only notifies when all sources agree + * the region is populated (intersection). + * + * @rdm: the #RamDiscardManager + * @source: the #RamDiscardSource that is populating + * @offset: offset within the memory region + * @size: size of the region being populated + * + * Returns 0 on success, or a negative error if any listener rejects. + */ +int ram_discard_manager_notify_populate(RamDiscardManager *rdm, + RamDiscardSource *source, + uint64_t offset, uint64_t size); + +/** + * ram_discard_manager_notify_discard: + * + * Notify listeners that a region has been discarded by a source. + * For multi-source aggregation, always notifies immediately + * (union semantics - any source discarding makes region discarded). + * + * @rdm: the #RamDiscardManager + * @source: the #RamDiscardSource that is discarding + * @offset: offset within the memory region + * @size: size of the region being discarded + */ +void ram_discard_manager_notify_discard(RamDiscardManager *rdm, + RamDiscardSource *source, + uint64_t offset, uint64_t size); + +/** + * ram_discard_manager_notify_discard_all: + * + * Notify listeners that all regions have been discarded by a source. + * + * @rdm: the #RamDiscardManager + * @source: the #RamDiscardSource that is discarding + */ +void ram_discard_manager_notify_discard_all(RamDiscardManager *rdm, + RamDiscardSource *source); + +/** + * ram_discard_manager_replay_populated_to_listeners: + * + * Replay populated sections to all registered listeners. + * For multi-source aggregation, only replays regions where all sources + * are populated (intersection). + * + * @rdm: the #RamDiscardManager + * + * Returns 0 on success, or a negative error if any notification failed. + */ +int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *rdm); + +#endif /* RAM_DISCARD_MANAGER_H */ diff --git a/include/system/ramblock.h b/include/system/ramblock.h index 4435f8d55f..f0639287bf 100644 --- a/include/system/ramblock.h +++ b/include/system/ramblock.h @@ -99,13 +99,12 @@ struct RamBlockAttributes { /* 1-setting of the bitmap represents ram is populated (shared) */ unsigned bitmap_size; unsigned long *bitmap; - - QLIST_HEAD(, RamDiscardListener) rdl_list; }; /* @offset: the offset within the RAMBlock */ int ram_block_discard_range(RAMBlock *rb, uint64_t offset, size_t length); -/* @offset: the offset within the RAMBlock */ +int ram_block_discard_shared_range(RAMBlock *rb, uint64_t offset, + size_t length); int ram_block_discard_guest_memfd_range(RAMBlock *rb, uint64_t offset, size_t length); diff --git a/include/ui/console.h b/include/ui/console.h index b7bfecb6ee..29bf722888 100644 --- a/include/ui/console.h +++ b/include/ui/console.h @@ -403,6 +403,7 @@ struct QemuDisplay { DisplayType type; void (*early_init)(DisplayOptions *opts); void (*init)(DisplayState *ds, DisplayOptions *opts); + void (*cleanup)(void); const char *vc; }; @@ -411,8 +412,27 @@ bool qemu_display_find_default(DisplayOptions *opts); void qemu_display_early_init(DisplayOptions *opts); void qemu_display_init(DisplayState *ds, DisplayOptions *opts); const char *qemu_display_get_vc(DisplayOptions *opts); +void qemu_display_cleanup(void); void qemu_display_help(void); +/* + * Console notifications: + * Handlers must be idempotent, it may notify multiple times. + */ +typedef enum { + QEMU_CONSOLE_ADDED, + QEMU_CONSOLE_REMOVED, +} QemuConsoleEventType; + +typedef struct QemuConsoleEvent { + QemuConsoleEventType type; + QemuConsole *con; +} QemuConsoleEvent; + +void qemu_console_add_notifier(Notifier *notifier); +void qemu_console_remove_notifier(Notifier *notifier); +void qemu_console_notify(QemuConsoleEventType type, QemuConsole *con); + /* vnc.c */ void vnc_display_add_client(const char *id, int csock, bool skipauth); int vnc_display_password(const char *id, const char *password, Error **errp); diff --git a/include/ui/egl-helpers.h b/include/ui/egl-helpers.h index c97a0d5c24..405ddd9125 100644 --- a/include/ui/egl-helpers.h +++ b/include/ui/egl-helpers.h @@ -81,6 +81,7 @@ EGLContext qemu_egl_init_ctx(void); bool qemu_egl_has_dmabuf(void); bool egl_init(const char *rendernode, DisplayGLMode mode, Error **errp); +void egl_cleanup(void); const char *qemu_egl_get_error_string(void); diff --git a/include/ui/gtk.h b/include/ui/gtk.h index 3e6ce3cb48..5c9c34a069 100644 --- a/include/ui/gtk.h +++ b/include/ui/gtk.h @@ -118,9 +118,9 @@ struct GtkDisplayState { GtkWidget *grab_item; GtkWidget *grab_on_hover_item; - int nb_vcs; - VirtualConsole vc[MAX_VCS]; + GPtrArray *vcs; + GtkWidget *vc_menu_separator; GtkWidget *show_tabs_item; GtkWidget *untabify_item; GtkWidget *show_menubar_item; @@ -140,6 +140,8 @@ struct GtkDisplayState { GdkCursor *null_cursor; Notifier mouse_mode_notifier; + VMChangeStateEntry *vmse; + Notifier console_notifier; gboolean free_scale; gboolean keep_aspect_ratio; @@ -225,6 +227,7 @@ int gd_gl_area_make_current(DisplayGLCtx *dgc, /* gtk-clipboard.c */ void gd_clipboard_init(GtkDisplayState *gd); +void gd_clipboard_cleanup(GtkDisplayState *gd); void gd_update_scale(VirtualConsole *vc, int ww, int wh, int fbw, int fbh); diff --git a/include/ui/qemu-spice-module.h b/include/ui/qemu-spice-module.h index 072efa0c83..bb0f8437c2 100644 --- a/include/ui/qemu-spice-module.h +++ b/include/ui/qemu-spice-module.h @@ -26,6 +26,7 @@ typedef struct SpiceInfo SpiceInfo; struct QemuSpiceOps { void (*init)(void); + void (*cleanup)(void); void (*display_init)(void); int (*migrate_info)(const char *h, int p, int t, const char *s); int (*set_passwd)(const char *passwd, diff --git a/include/ui/qemu-spice.h b/include/ui/qemu-spice.h index 111a09ceca..2cdf10f031 100644 --- a/include/ui/qemu-spice.h +++ b/include/ui/qemu-spice.h @@ -27,19 +27,14 @@ #include "qemu/config-file.h" void qemu_spice_input_init(void); +void qemu_spice_input_cleanup(void); void qemu_spice_display_init(void); +void qemu_spice_display_cleanup(void); void qemu_spice_display_init_done(void); bool qemu_spice_have_display_interface(QemuConsole *con); int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con); int qemu_spice_migrate_info(const char *hostname, int port, int tls_port, const char *subject); - -#else /* CONFIG_SPICE */ - -#include "qemu/error-report.h" - -#define spice_displays 0 - #endif /* CONFIG_SPICE */ static inline bool qemu_using_spice(Error **errp) diff --git a/linux-user/aarch64/elfload.c b/linux-user/aarch64/elfload.c index 4887a3a7b7..42aeb29306 100644 --- a/linux-user/aarch64/elfload.c +++ b/linux-user/aarch64/elfload.c @@ -173,6 +173,7 @@ abi_ulong get_elf_hwcap(CPUState *cs) GET_FEATURE_ID(aa64_cmpbr, ARM_HWCAP_A64_CMPBR); GET_FEATURE_ID(aa64_f8mm8, ARM_HWCAP_A64_F8MM8); GET_FEATURE_ID(aa64_f8mm4, ARM_HWCAP_A64_F8MM4); + GET_FEATURE_ID(aa64_ssve_aes, ARM_HWCAP_A64_SME_AES); return hwcaps; } @@ -184,7 +185,7 @@ abi_ulong get_elf_hwcap2(CPUState *cs) GET_FEATURE_ID(aa64_dcpodp, ARM_HWCAP2_A64_DCPODP); GET_FEATURE_ID(aa64_sve2, ARM_HWCAP2_A64_SVE2); - GET_FEATURE_ID(aa64_sve2_aes, ARM_HWCAP2_A64_SVEAES); + GET_FEATURE_ID(aa64_sve_aes, ARM_HWCAP2_A64_SVEAES); GET_FEATURE_ID(aa64_sve2_pmull128, ARM_HWCAP2_A64_SVEPMULL); GET_FEATURE_ID(aa64_sve2_bitperm, ARM_HWCAP2_A64_SVEBITPERM); GET_FEATURE_ID(aa64_sve2_sha3, ARM_HWCAP2_A64_SVESHA3); @@ -234,6 +235,8 @@ abi_ulong get_elf_hwcap2(CPUState *cs) GET_FEATURE_ID(aa64_ssve_f8fma, ARM_HWCAP2_A64_SME_SF8FMA); GET_FEATURE_ID(aa64_ssve_f8dp4, ARM_HWCAP2_A64_SME_SF8DP4); GET_FEATURE_ID(aa64_ssve_f8dp2, ARM_HWCAP2_A64_SME_SF8DP2); + GET_FEATURE_ID(aa64_sme_f8f32, ARM_HWCAP2_A64_SME_F8F32); + GET_FEATURE_ID(aa64_sme_f8f16, ARM_HWCAP2_A64_SME_F8F16); return hwcaps; } diff --git a/linux-user/hexagon/cpu_loop.c b/linux-user/hexagon/cpu_loop.c index 9464246e9e..0958c51fbb 100644 --- a/linux-user/hexagon/cpu_loop.c +++ b/linux-user/hexagon/cpu_loop.c @@ -22,6 +22,7 @@ #include "qemu.h" #include "user-internals.h" #include "user/cpu_loop.h" +#include "target/hexagon/internal.h" #include "signal-common.h" #include "internal.h" @@ -60,6 +61,28 @@ void cpu_loop(CPUHexagonState *env) env->gpr[0] = ret; } break; + case HEX_EVENT_PRECISE: + switch (env->cause_code) { + case HEX_CAUSE_FETCH_NO_UPAGE: + case HEX_CAUSE_PRIV_NO_UREAD: + case HEX_CAUSE_PRIV_NO_UWRITE: + force_sig_fault(TARGET_SIGSEGV, TARGET_SEGV_MAPERR, + env->gpr[HEX_REG_PC]); + + break; + case HEX_CAUSE_PRIV_USER_NO_GINSN: + case HEX_CAUSE_PRIV_USER_NO_SINSN: + case HEX_CAUSE_INVALID_PACKET: + force_sig_fault(TARGET_SIGILL, TARGET_ILL_ILLOPC, + env->gpr[HEX_REG_PC]); + break; + default: + EXCP_DUMP(env, "\nqemu: unhandled CPU precise exception " + "cause code 0x%x - aborting\n", + env->cause_code); + exit(EXIT_FAILURE); + } + break; case HEX_CAUSE_PC_NOT_ALIGNED: force_sig_fault(TARGET_SIGBUS, TARGET_BUS_ADRALN, env->gpr[HEX_REG_R31]); diff --git a/meson.build b/meson.build index e026851309..164328ded8 100644 --- a/meson.build +++ b/meson.build @@ -545,6 +545,8 @@ if get_option('asan') if cc.has_argument('-fsanitize=address') qemu_cflags = ['-fsanitize=address'] + qemu_cflags qemu_ldflags = ['-fsanitize=address'] + qemu_ldflags + # Ensure complete stack traces for LSan suppressions to match correctly. + qemu_cflags += ['-fno-omit-frame-pointer'] else error('Your compiler does not support -fsanitize=address') endif @@ -3599,6 +3601,7 @@ if have_system 'hw/display', 'hw/dma', 'hw/fsi', + 'hw/hexagon', 'hw/hyperv', 'hw/i2c', 'hw/i3c', diff --git a/migration/migration.c b/migration/migration.c index 074d3f2c69..0f56432d90 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -297,7 +297,7 @@ void migration_object_init(void) { /* This can only be called once. */ assert(!current_migration); - current_migration = MIGRATION_OBJ(object_new(TYPE_MIGRATION)); + current_migration = MIGRATION(object_new(TYPE_MIGRATION)); /* * Init the migrate incoming object as well no matter whether @@ -1643,6 +1643,12 @@ bool migration_in_incoming_postcopy(void) return ps >= POSTCOPY_INCOMING_DISCARD && ps < POSTCOPY_INCOMING_END; } +bool migration_guest_ram_loading(void) +{ + return runstate_check(RUN_STATE_INMIGRATE) || + migration_in_incoming_postcopy(); +} + bool migration_incoming_postcopy_advised(void) { PostcopyState ps = postcopy_state_get(); @@ -3975,7 +3981,7 @@ static void migration_class_init(ObjectClass *klass, const void *data) static void migration_instance_finalize(Object *obj) { - MigrationState *ms = MIGRATION_OBJ(obj); + MigrationState *ms = MIGRATION(obj); qapi_free_BitmapMigrationNodeAliasList(ms->parameters.block_bitmap_mapping); qapi_free_strList(ms->parameters.cpr_exec_command); @@ -3993,7 +3999,7 @@ static void migration_instance_finalize(Object *obj) static void migration_instance_init(Object *obj) { - MigrationState *ms = MIGRATION_OBJ(obj); + MigrationState *ms = MIGRATION(obj); ms->state = MIGRATION_STATUS_NONE; ms->mbps = -1; @@ -4040,7 +4046,6 @@ static const TypeInfo migration_type = { */ .parent = TYPE_DEVICE, .class_init = migration_class_init, - .class_size = sizeof(MigrationClass), .instance_size = sizeof(MigrationState), .instance_init = migration_instance_init, .instance_finalize = migration_instance_finalize, diff --git a/migration/migration.h b/migration/migration.h index 841f49b215..293ad60e07 100644 --- a/migration/migration.h +++ b/migration/migration.h @@ -263,14 +263,7 @@ void fill_destination_postcopy_migration_info(MigrationInfo *info); #define TYPE_MIGRATION "migration" -typedef struct MigrationClass MigrationClass; -DECLARE_OBJ_CHECKERS(MigrationState, MigrationClass, - MIGRATION_OBJ, TYPE_MIGRATION) - -struct MigrationClass { - /*< private >*/ - DeviceClass parent_class; -}; +OBJECT_DECLARE_SIMPLE_TYPE(MigrationState, MIGRATION); struct MigrationState { /*< private >*/ diff --git a/migration/ram.c b/migration/ram.c index fc38ffbf8a..bd519f1931 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -860,7 +860,7 @@ static inline bool migration_bitmap_clear_dirty(RAMState *rs, return ret; } -static int dirty_bitmap_clear_section(MemoryRegionSection *section, +static int dirty_bitmap_clear_section(const MemoryRegionSection *section, void *opaque) { const hwaddr offset = section->offset_within_region; @@ -1588,7 +1588,7 @@ static inline void populate_read_range(RAMBlock *block, ram_addr_t offset, } } -static inline int populate_read_section(MemoryRegionSection *section, +static inline int populate_read_section(const MemoryRegionSection *section, void *opaque) { const hwaddr size = int128_get64(section->size); @@ -1663,7 +1663,7 @@ void ram_write_tracking_prepare(void) } } -static inline int uffd_protect_section(MemoryRegionSection *section, +static inline int uffd_protect_section(const MemoryRegionSection *section, void *opaque) { const hwaddr size = int128_get64(section->size); @@ -2495,7 +2495,10 @@ static void ram_state_reset(RAMState *rs) rs->last_seen_block = NULL; rs->last_page = 0; - rs->last_version = ram_list.version; + + /* Read version before ram_list.blocks */ + rs->last_version = qatomic_load_acquire(&ram_list.version); + rs->xbzrle_started = false; ram_page_hint_reset(&rs->page_hint); @@ -3270,13 +3273,10 @@ static int ram_save_iterate(QEMUFile *f, void *opaque) */ WITH_QEMU_LOCK_GUARD(&rs->bitmap_mutex) { WITH_RCU_READ_LOCK_GUARD() { - if (ram_list.version != rs->last_version) { + if (qatomic_read(&ram_list.version) != rs->last_version) { ram_state_reset(rs); } - /* Read version before ram_list.blocks */ - smp_rmb(); - ret = rdma_registration_start(f, RAM_CONTROL_ROUND); if (ret < 0) { qemu_file_set_error(f, ret); diff --git a/pc-bios/README b/pc-bios/README index 4818272968..2f8a565245 100644 --- a/pc-bios/README +++ b/pc-bios/README @@ -14,7 +14,7 @@ - SLOF (Slimline Open Firmware) is a free IEEE 1275 Open Firmware implementation for certain IBM POWER hardware. The sources are at https://gitlab.com/slof/slof, and the image currently in qemu is - built from git tag qemu-slof-20251026. + built from git tag qemu-slof-20260627. - VOF (Virtual Open Firmware) is a minimalistic firmware to work with -machine pseries,x-vof=on. When enabled, the firmware acts as a slim shim and diff --git a/pc-bios/slof.bin b/pc-bios/slof.bin Binary files differindex 39b9248967..6a6d944b07 100644 --- a/pc-bios/slof.bin +++ b/pc-bios/slof.bin diff --git a/python/qemu/machine/machine.py b/python/qemu/machine/machine.py index ebb58d5b68..3670c8cd39 100644 --- a/python/qemu/machine/machine.py +++ b/python/qemu/machine/machine.py @@ -292,8 +292,8 @@ class QEMUMachine: self._iolog = iolog.read() @property - def _base_args(self) -> List[str]: - args = ['-display', 'none', '-vga', 'none'] + def _harness_args(self) -> List[str]: + args: List[str] = [] if self._qmp_set: if self._sock_pair: @@ -306,12 +306,17 @@ class QEMUMachine: moncdev = f"socket,id=mon,path={self._monitor_address}" args.extend(['-chardev', moncdev, '-mon', 'chardev=mon,mode=control']) + return args - if self._machine is not None: - args.extend(['-machine', self._machine]) + def _console_args(self, interactive: bool = False) -> List[str]: + args: List[str] = [] + # redirect pre_console_index serials to null for _ in range(self._console_index): args.extend(['-serial', 'null']) - if self._console_set: + + if interactive: + args.extend(['-serial', 'mon:stdio']) + elif self._console_set: assert self._cons_sock_pair is not None fd = self._cons_sock_pair[0].fileno() chardev = f"socket,id=console,fd={fd}" @@ -324,6 +329,13 @@ class QEMUMachine: return args @property + def _base_args(self) -> List[str]: + args: List[str] = ['-display', 'none', '-vga', 'none'] + if self._machine is not None: + args.extend(['-machine', self._machine]) + return args + + @property def args(self) -> List[str]: """Returns the list of arguments given to the QEMU binary.""" return self._args @@ -366,6 +378,8 @@ class QEMUMachine: self._qemu_full_args = tuple(chain( self._wrapper, [self._binary], + self._harness_args, + self._console_args(), self._base_args, self._args )) @@ -474,6 +488,14 @@ class QEMUMachine: """ self._pre_launch() LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args)) + # Log a simplified, developer-runnable command: + # Exclude harness-managed infrastructure args (harness_args) + # and wrapper. + debug_cmd = [self._binary] + debug_cmd.extend(self._console_args(interactive=True)) + debug_cmd.extend(self._base_args) + debug_cmd.extend(self._args) + LOG.debug('Developer-runnable command: %r', ' '.join(debug_cmd)) # Cleaning up of this subprocess is guaranteed by _do_shutdown. # pylint: disable=consider-using-with diff --git a/qapi/migration.json b/qapi/migration.json index 27a7970556..e8756308cf 100644 --- a/qapi/migration.json +++ b/qapi/migration.json @@ -804,10 +804,6 @@ # Migration parameters enumeration. The enumeration values mirror the # members of @MigrationParameters. # -# @cpr-exec-command: Command to start the new QEMU process when @mode -# is @cpr-exec. The first list element is the program's filename, -# the remainder its arguments. (Since 10.2) -# # Features: # # @unstable: Members @x-checkpoint-delay, @x-rdma-chunk-size, and diff --git a/qemu-options.hx b/qemu-options.hx index c799286153..e44b47de68 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -36,7 +36,8 @@ DEF("machine", HAS_ARG, QEMU_OPTION_machine, \ " dea-key-wrap=on|off controls support for DEA key wrapping (default=on)\n" " suppress-vmdesc=on|off disables self-describing migration (default=off)\n" " nvdimm=on|off controls NVDIMM support (default=off)\n" - " memory-encryption=<id> memory encryption object to use (default=none)\n" + " confidential-guest-support=<id> specifies confidential guest support object (default=none)\n" + " memory-encryption=<id> (memory-encryption is the alias of confidential-guest-support, recommend to use confidential-guest-support)\n" " hmat=on|off controls ACPI HMAT support (default=off)\n" " spcr=on|off controls ACPI SPCR support (default=on)\n" #ifdef CONFIG_POSIX @@ -100,8 +101,12 @@ SRST ``nvdimm=on|off`` Enables or disables NVDIMM support. The default is off. + ``confidential-guest-support=<id>`` + confidential guest support object to use. The default is none. + ``memory-encryption=<id>`` - Memory encryption object to use. The default is none. + The alias of ``confidential-guest-support``. Recommend to use + confidential-guest-support. ``hmat=on|off`` Enables or disables ACPI Heterogeneous Memory Attribute Table @@ -1935,19 +1940,19 @@ ERST DEF("fsdev", HAS_ARG, QEMU_OPTION_fsdev, "-fsdev local,id=id,path=path,security_model=mapped-xattr|mapped-file|passthrough|none\n" - " [,writeout=immediate][,readonly=on][,fmode=fmode][,dmode=dmode]\n" + " [,writeout=immediate][,readonly=on][,fmode=fmode][,dmode=dmode][,max_xattr=max]\n" " [[,throttling.bps-total=b]|[[,throttling.bps-read=r][,throttling.bps-write=w]]]\n" " [[,throttling.iops-total=i]|[[,throttling.iops-read=r][,throttling.iops-write=w]]]\n" " [[,throttling.bps-total-max=bm]|[[,throttling.bps-read-max=rm][,throttling.bps-write-max=wm]]]\n" " [[,throttling.iops-total-max=im]|[[,throttling.iops-read-max=irm][,throttling.iops-write-max=iwm]]]\n" " [[,throttling.iops-size=is]]\n" - "-fsdev synth,id=id\n", + "-fsdev synth,id=id[,max_xattr=max]\n", QEMU_ARCH_ALL) SRST -``-fsdev local,id=id,path=path,security_model=security_model [,writeout=writeout][,readonly=on][,fmode=fmode][,dmode=dmode] [,throttling.option=value[,throttling.option=value[,...]]]`` +``-fsdev local,id=id,path=path,security_model=security_model [,writeout=writeout][,readonly=on][,fmode=fmode][,dmode=dmode][,max_xattr=max] [,throttling.option=value[,throttling.option=value[,...]]]`` \ -``-fsdev synth,id=id[,readonly=on]`` +``-fsdev synth,id=id[,readonly=on][,max_xattr=max]`` Define a new file system device. Valid options are: ``local`` @@ -2021,6 +2026,12 @@ SRST Let every is bytes of a request count as a new request for iops throttling purposes. + ``max_xattr=max`` + Specifies the maximum number of concurrent xattr FIDs allowed for + this export. The default is 1024. Set to 0 for allowing an infinite + number of xattr FIDs. This limit prevents host memory exhaustion + attacks by capping the number of simultaneous xattr FIDs. + -fsdev option is used along with -device driver "virtio-9p-...". ``-device virtio-9p-type,fsdev=id,mount_tag=mount_tag`` @@ -2040,14 +2051,14 @@ ERST DEF("virtfs", HAS_ARG, QEMU_OPTION_virtfs, "-virtfs local,path=path,mount_tag=tag,security_model=mapped-xattr|mapped-file|passthrough|none\n" - " [,id=id][,writeout=immediate][,readonly=on][,fmode=fmode][,dmode=dmode][,multidevs=remap|forbid|warn]\n" - "-virtfs synth,mount_tag=tag[,id=id][,readonly=on]\n", + " [,id=id][,writeout=immediate][,readonly=on][,fmode=fmode][,dmode=dmode][,multidevs=remap|forbid|warn][,max_xattr=max]\n" + "-virtfs synth,mount_tag=tag[,id=id][,readonly=on][,max_xattr=max]\n", QEMU_ARCH_ALL) SRST -``-virtfs local,path=path,mount_tag=mount_tag ,security_model=security_model[,writeout=writeout][,readonly=on] [,fmode=fmode][,dmode=dmode][,multidevs=multidevs]`` +``-virtfs local,path=path,mount_tag=mount_tag ,security_model=security_model[,writeout=writeout][,readonly=on] [,fmode=fmode][,dmode=dmode][,multidevs=multidevs][,max_xattr=max]`` \ -``-virtfs synth,mount_tag=mount_tag`` +``-virtfs synth,mount_tag=mount_tag[,max_xattr=max]`` Define a new virtual filesystem device and expose it to the guest using a virtio-9p-device (a.k.a. 9pfs), which essentially means that a certain directory on host is made directly accessible by guest as a pass-through @@ -2113,6 +2124,12 @@ SRST Specifies the tag name to be used by the guest to mount this export point. + ``max_xattr=max`` + Specifies the maximum number of concurrent xattr FIDs allowed for + this export. The default is 1024. Set to 0 for allowing an infinite + number of xattr FIDs. This limit prevents host memory exhaustion + attacks by capping the number of simultaneous xattr FIDs. + ``multidevs=remap|forbid|warn`` Specifies how to deal with multiple devices being shared with the same 9p export in order to avoid file ID collisions on guest. @@ -6364,9 +6381,50 @@ SRST # |qemu_system_x86| \\ ...... \\ -object sev-guest,id=sev0,cbitpos=47,reduced-phys-bits=1 \\ - -machine ...,memory-encryption=sev0 \\ + -machine ...,confidential-guest-support=sev0 \\ ..... + ``-object tdx-guest,id=id,[attributes=attrs,sept-ve-disable=on|off,mrconfigid=sha384_digest,mrowner=sha384_digest,mrownerconfig=sha384_digest,quote-generation-socket=socketaddr]`` + Create an Intel Trusted Domain eXtensions (TDX) guest object, which is + the type of ``confidential-guest-support`` object. When pass the object + ID to machine's ``confidential-guest-support`` property, it can create + a TDX guest. + + The ``attributes`` property is a 64-bit integer, which specifies the + TD attributes of the TD. + + The ``sept-ve-disable`` property controls the bit 28 of TD attributes + specifically. When it's on, the EPT violation conversion to #VE on + guest access of PENDING pages is disabled. Some guest OS (e.g., Linux + TD guest) may require this to be set, otherwise they refuse to boot. + The default value is on. + + The ``mrconfigid`` property is base64 encoded SHA384 digest, which + provides the ID for non-owner-defined configuration of the guest TD, + e.g., run-time or OS configuration. The default value is all zeros. + + The ``mrowner`` property is base64 encoded SHA384 digest, which + provides the ID for guest TD's owner. The default value is all zeros. + + The ``mrownerconfig`` property is base64 encoded SHA384 digest, which + provides the ID for owner-defined configuration of the guest TD, e.g., + the configuration specific to the workload rather than the run-time of + OS. The default value is all zeros. + + The ``quote-generation-socket`` property specifies the socket address + of the Quote Generation Service (QGS). QGS is a daemon running on the + host. QEMU forwards the <GetQuote> request from TD guest to QGS and + sents the reply (which contains generated QUOTE on success) from QGS + to guest TD. + + .. parsed-literal:: + + # |qemu_system_x86| \\ + ...... \\ + -object '{"qom-type":"tdx-guest","id":"tdx","quote-generation-socket":{"type":"unix","path":"/var/run/qgs.socket"}}' \\ + -machine ...,confidential-guest-support=tdx \\ + ...... + ``-object igvm-cfg,file=file`` Create an IGVM configuration object that defines the initial state of the guest using a file in that conforms to the Independent Guest diff --git a/qobject/json-lexer.c b/qobject/json-lexer.c index 51341d96e4..7753ba6c09 100644 --- a/qobject/json-lexer.c +++ b/qobject/json-lexer.c @@ -277,7 +277,8 @@ void json_lexer_init(JSONLexer *lexer, bool enable_interpolation) lexer->start_state = lexer->state = enable_interpolation ? IN_START_INTERP : IN_START; lexer->token = g_string_sized_new(3); - lexer->x = lexer->y = 0; + lexer->cur_x = lexer->cur_y = 1; + lexer->x = lexer->y = 1; } static void json_lexer_feed_char(JSONLexer *lexer, char ch, bool flush) @@ -285,10 +286,10 @@ static void json_lexer_feed_char(JSONLexer *lexer, char ch, bool flush) int new_state; bool char_consumed = false; - lexer->x++; + lexer->cur_x++; if (ch == '\n') { - lexer->x = 0; - lexer->y++; + lexer->cur_x = 1; + lexer->cur_y++; } while (flush ? lexer->state != lexer->start_state : !char_consumed) { @@ -316,6 +317,8 @@ static void json_lexer_feed_char(JSONLexer *lexer, char ch, bool flush) case IN_START: g_string_truncate(lexer->token, 0); new_state = lexer->start_state; + lexer->x = lexer->cur_x; + lexer->y = lexer->cur_y; break; case JSON_ERROR: json_message_process_token(lexer, lexer->token, JSON_ERROR, diff --git a/qobject/json-parser-int.h b/qobject/json-parser-int.h index 8c01f23627..5a6b5c9af9 100644 --- a/qobject/json-parser-int.h +++ b/qobject/json-parser-int.h @@ -35,7 +35,12 @@ typedef enum json_token_type { JSON_MAX = JSON_END_OF_INPUT } JSONTokenType; -typedef struct JSONToken JSONToken; +typedef struct JSONToken { + JSONTokenType type; + int x; + int y; + char *str; +} JSONToken; /* json-lexer.c */ void json_lexer_init(JSONLexer *lexer, bool enable_interpolation); @@ -48,7 +53,9 @@ void json_message_process_token(JSONLexer *lexer, GString *input, JSONTokenType type, int x, int y); /* json-parser.c */ -JSONToken *json_token(JSONTokenType type, int x, int y, GString *tokstr); -QObject *json_parser_parse(GQueue *tokens, va_list *ap, Error **errp); +void json_parser_init(JSONParserContext *ctxt, va_list *ap); +void json_parser_reset(JSONParserContext *ctxt); +QObject *json_parser_feed(JSONParserContext *ctxt, const JSONToken *token, Error **errp); +void json_parser_destroy(JSONParserContext *ctxt); #endif diff --git a/qobject/json-parser.c b/qobject/json-parser.c index f6622b82b0..5935730c0c 100644 --- a/qobject/json-parser.c +++ b/qobject/json-parser.c @@ -24,32 +24,136 @@ #include "qobject/qstring.h" #include "json-parser-int.h" -struct JSONToken { - JSONTokenType type; - int x; - int y; - char str[]; -}; - -typedef struct JSONParserContext { - Error *err; - JSONToken *current; - GQueue *buf; - va_list *ap; -} JSONParserContext; +/* + * The JSON parser is a push parser, returning a completed top-level + * object, an error, or NULL (if the object is incomplete and no error + * happened) after every token. Therefore it has an explicit + * representation of its parser stack; each stack entry consists of a + * parser state and a QObject: - a QList, for an array that is being + * added to - a QDict, for a dictionary that is being added to - a + * QString, for the key of the next pair that will be added to a QDict + * + * The stack represents an arbitrary nesting of arrays and dictionaries + * (whose next key has been parsed); it can also have a dictionary whose + * next key has not been parsed, but that can only happen at the top level. + * Because of this, the stack contents are always of the form + * "(QList | QDict QString)* QDict?". + * + * An empty stack represents the beginning of the parsing process, with + * start state BEFORE_VALUE. + */ -#define BUG_ON(cond) assert(!(cond)) +typedef enum JSONParserState { + AFTER_LCURLY, + AFTER_LSQUARE, + BEFORE_KEY, + BEFORE_VALUE, + END_OF_KEY, + END_OF_VALUE, +} JSONParserState; + +typedef struct JSONParserStackEntry { + /* + * State when the container is completed or, for the top of the stack, + * entry state for the next token. + */ + JSONParserState state; + + /* + * A QString with the last parsed key, or a QList/QDict for the current + * container. + */ + QObject *partial; +} JSONParserStackEntry; -/** - * TODO +/* + * This is the JSON grammar that's parsed, with the state transition and + * action at each point of the grammar. While this is not a formal + * description, "-> action" represents the pseudocode of the action + * and "-> STATE" sets the top stack entry's state to STATE. + * + * The state alone is enough to tell you what to parse; the state plus + * the type of the top of stack tells you which action to take. + * + * // The initial state is BEFORE_VALUE. + * input := value -> END_OF_VALUE -> return parsed value + * (input | END_OF_INPUT) + * + * // entered on BEFORE_VALUE; after any of these rules are processed, the + * // parser has completed a QObject and is in the END_OF_VALUE state. + * // + * // When the parser reaches the END_OF_VALUE state, it examines the + * // top of the stack to see if it's coming from "input" (stack empty), + * // "array_items" (TOS is a QList) or "dict_pairs" (TOS is a QString; the + * // item below will be a QDict). It then proceeds with the corresponding + * // actions, which will be one of: + * // - return parsed value + * // - add value to QList + * // - pop QString with the key, add key/value to the QDict + * value := literal -> END_OF_VALUE + * | '[' -> push empty QList -> AFTER_LSQUARE + * after_lsquare -> END_OF_VALUE + * | '{' -> push empty QDict -> AFTER_LCURLY + * after_lcurly -> END_OF_VALUE + * + * // non-recursive values, entered on BEFORE_VALUE + * literal := INTEGER -> END_OF_VALUE + * | FLOAT -> END_OF_VALUE + * | KEYWORD -> END_OF_VALUE + * | STRING -> END_OF_VALUE + * | INTERP -> END_OF_VALUE * - * 0) make errors meaningful again - * 1) add geometry information to tokens - * 3) should we return a parsed size? - * 4) deal with premature EOI + * // entered on AFTER_LSQUARE + * after_lsquare := ']' -> pop completed QList -> END_OF_VALUE + * | ϵ -> BEFORE_VALUE + * array_items -> END_OF_VALUE + * + * // entered on BEFORE_VALUE, with TOS being a QList + * array_items := value -> add value to QList -> END_OF_VALUE + * (']' -> pop completed QList -> END_OF_VALUE + * | ',' -> BEFORE_VALUE + * array_items) -> END_OF_VALUE + * + * // entered on AFTER_LCURLY + * after_lcurly := '}' -> pop completed QDict -> END_OF_VALUE + * | ϵ -> BEFORE_KEY + * dict_pairs -> END_OF_VALUE + * + * // entered on BEFORE_KEY, with TOS being a QDict + * dict_pairs := (STRING | INTERP) -> push QString -> END_OF_KEY + * ':' -> BEFORE_VALUE + * value -> pop QString + add pair to QDict -> END_OF_VALUE + * ('}' -> pop completed QDict -> END_OF_VALUE + * | ',' -> BEFORE_KEY + * dict_pairs) -> END_OF_VALUE + * + * Parse errors ignore the token. json_parser_reset() can be + * called to restart parsing from scratch, with an empty stack. */ -static QObject *parse_value(JSONParserContext *ctxt); +#define BUG_ON(cond) assert(!(cond)) + +static inline JSONParserStackEntry *current_entry(JSONParserContext *ctxt) +{ + return g_queue_peek_tail(ctxt->stack); +} + +static void push_entry(JSONParserContext *ctxt, QObject *partial, + JSONParserState state) +{ + JSONParserStackEntry *entry = g_new(JSONParserStackEntry, 1); + entry->partial = partial; + entry->state = state; + g_queue_push_tail(ctxt->stack, entry); +} + +/* Drop the top entry and return the new top entry. */ +static JSONParserStackEntry *pop_entry(JSONParserContext *ctxt) +{ + JSONParserStackEntry *entry = g_queue_pop_tail(ctxt->stack); + g_free(entry); + return current_entry(ctxt); +} /** * Error handler @@ -67,7 +171,8 @@ static void G_GNUC_PRINTF(3, 4) parse_error(JSONParserContext *ctxt, va_start(ap, msg); vsnprintf(message, sizeof(message), msg, ap); va_end(ap); - error_setg(&ctxt->err, "JSON parse error, %s", message); + error_setg(&ctxt->err, "%d:%d: JSON parse error, %s", + token->y, token->x, message); } static int cvt4hex(const char *s) @@ -236,200 +341,10 @@ out: return NULL; } -/* Note: the token object returned by parser_context_peek_token or - * parser_context_pop_token is deleted as soon as parser_context_pop_token - * is called again. - */ -static const JSONToken *parser_context_pop_token(JSONParserContext *ctxt) -{ - g_free(ctxt->current); - ctxt->current = g_queue_pop_head(ctxt->buf); - return ctxt->current; -} - -static const JSONToken *parser_context_peek_token(JSONParserContext *ctxt) -{ - return g_queue_peek_head(ctxt->buf); -} - -/** - * Parsing rules - */ -static int parse_pair(JSONParserContext *ctxt, QDict *dict) -{ - QObject *key_obj = NULL; - QString *key; - QObject *value; - const JSONToken *peek, *token; - - peek = parser_context_peek_token(ctxt); - if (peek == NULL) { - parse_error(ctxt, NULL, "premature EOI"); - goto out; - } - - key_obj = parse_value(ctxt); - key = qobject_to(QString, key_obj); - if (!key) { - parse_error(ctxt, peek, "key is not a string in object"); - goto out; - } - - token = parser_context_pop_token(ctxt); - if (token == NULL) { - parse_error(ctxt, NULL, "premature EOI"); - goto out; - } - - if (token->type != JSON_COLON) { - parse_error(ctxt, token, "missing : in object pair"); - goto out; - } - - value = parse_value(ctxt); - if (value == NULL) { - parse_error(ctxt, token, "Missing value in dict"); - goto out; - } - - if (qdict_haskey(dict, qstring_get_str(key))) { - parse_error(ctxt, token, "duplicate key"); - goto out; - } - - qdict_put_obj(dict, qstring_get_str(key), value); - - qobject_unref(key_obj); - return 0; - -out: - qobject_unref(key_obj); - return -1; -} - -static QObject *parse_object(JSONParserContext *ctxt) -{ - QDict *dict = NULL; - const JSONToken *token, *peek; - - token = parser_context_pop_token(ctxt); - assert(token && token->type == JSON_LCURLY); - - dict = qdict_new(); - - peek = parser_context_peek_token(ctxt); - if (peek == NULL) { - parse_error(ctxt, NULL, "premature EOI"); - goto out; - } - - if (peek->type != JSON_RCURLY) { - if (parse_pair(ctxt, dict) == -1) { - goto out; - } - - token = parser_context_pop_token(ctxt); - if (token == NULL) { - parse_error(ctxt, NULL, "premature EOI"); - goto out; - } - - while (token->type != JSON_RCURLY) { - if (token->type != JSON_COMMA) { - parse_error(ctxt, token, "expected separator in dict"); - goto out; - } - - if (parse_pair(ctxt, dict) == -1) { - goto out; - } - - token = parser_context_pop_token(ctxt); - if (token == NULL) { - parse_error(ctxt, NULL, "premature EOI"); - goto out; - } - } - } else { - (void)parser_context_pop_token(ctxt); - } - - return QOBJECT(dict); - -out: - qobject_unref(dict); - return NULL; -} - -static QObject *parse_array(JSONParserContext *ctxt) -{ - QList *list = NULL; - const JSONToken *token, *peek; - - token = parser_context_pop_token(ctxt); - assert(token && token->type == JSON_LSQUARE); - - list = qlist_new(); +/* Terminals */ - peek = parser_context_peek_token(ctxt); - if (peek == NULL) { - parse_error(ctxt, NULL, "premature EOI"); - goto out; - } - - if (peek->type != JSON_RSQUARE) { - QObject *obj; - - obj = parse_value(ctxt); - if (obj == NULL) { - parse_error(ctxt, token, "expecting value"); - goto out; - } - - qlist_append_obj(list, obj); - - token = parser_context_pop_token(ctxt); - if (token == NULL) { - parse_error(ctxt, NULL, "premature EOI"); - goto out; - } - - while (token->type != JSON_RSQUARE) { - if (token->type != JSON_COMMA) { - parse_error(ctxt, token, "expected separator in list"); - goto out; - } - - obj = parse_value(ctxt); - if (obj == NULL) { - parse_error(ctxt, token, "expecting value"); - goto out; - } - - qlist_append_obj(list, obj); - - token = parser_context_pop_token(ctxt); - if (token == NULL) { - parse_error(ctxt, NULL, "premature EOI"); - goto out; - } - } - } else { - (void)parser_context_pop_token(ctxt); - } - - return QOBJECT(list); - -out: - qobject_unref(list); - return NULL; -} - -static QObject *parse_keyword(JSONParserContext *ctxt) +static QObject *parse_keyword(JSONParserContext *ctxt, const JSONToken *token) { - const JSONToken *token; - - token = parser_context_pop_token(ctxt); assert(token && token->type == JSON_KEYWORD); if (!strcmp(token->str, "true")) { @@ -443,11 +358,9 @@ static QObject *parse_keyword(JSONParserContext *ctxt) return NULL; } -static QObject *parse_interpolation(JSONParserContext *ctxt) +static QObject *parse_interpolation(JSONParserContext *ctxt, + const JSONToken *token) { - const JSONToken *token; - - token = parser_context_pop_token(ctxt); assert(token && token->type == JSON_INTERP); if (!strcmp(token->str, "%p")) { @@ -479,11 +392,8 @@ static QObject *parse_interpolation(JSONParserContext *ctxt) return NULL; } -static QObject *parse_literal(JSONParserContext *ctxt) +static QObject *parse_literal(JSONParserContext *ctxt, const JSONToken *token) { - const JSONToken *token; - - token = parser_context_pop_token(ctxt); assert(token); switch (token->type) { @@ -531,61 +441,230 @@ static QObject *parse_literal(JSONParserContext *ctxt) } } -static QObject *parse_value(JSONParserContext *ctxt) -{ - const JSONToken *token; - - token = parser_context_peek_token(ctxt); - if (token == NULL) { - parse_error(ctxt, NULL, "premature EOI"); - return NULL; - } +/* Parsing state machine */ +static QObject *parse_begin_value(JSONParserContext *ctxt, + const JSONToken *token) +{ switch (token->type) { case JSON_LCURLY: - return parse_object(ctxt); + push_entry(ctxt, QOBJECT(qdict_new()), AFTER_LCURLY); + return NULL; case JSON_LSQUARE: - return parse_array(ctxt); + push_entry(ctxt, QOBJECT(qlist_new()), AFTER_LSQUARE); + return NULL; case JSON_INTERP: - return parse_interpolation(ctxt); + return parse_interpolation(ctxt, token); case JSON_INTEGER: case JSON_FLOAT: case JSON_STRING: - return parse_literal(ctxt); + return parse_literal(ctxt, token); case JSON_KEYWORD: - return parse_keyword(ctxt); + return parse_keyword(ctxt, token); default: parse_error(ctxt, token, "expecting value"); return NULL; } } -JSONToken *json_token(JSONTokenType type, int x, int y, GString *tokstr) +static QObject *parse_token(JSONParserContext *ctxt, const JSONToken *token) +{ + JSONParserStackEntry *entry; + JSONParserState state; + QString *key; + QObject *key_obj = NULL, *value = NULL; + + entry = current_entry(ctxt); + state = entry ? entry->state : BEFORE_VALUE; + switch (state) { + case AFTER_LCURLY: + /* Grab '}' for empty object or fall through to BEFORE_KEY */ + assert(qobject_type(entry->partial) == QTYPE_QDICT); + if (token->type == JSON_RCURLY) { + value = entry->partial; + entry = pop_entry(ctxt); + break; + } + entry->state = BEFORE_KEY; + /* fall through */ + + case BEFORE_KEY: + /* Expecting object key */ + assert(qobject_type(entry->partial) == QTYPE_QDICT); + if (token->type != JSON_STRING && token->type != JSON_INTERP) { + parse_error(ctxt, token, "expecting key"); + return NULL; + } + + key_obj = parse_begin_value(ctxt, token); + if (!key_obj) { + /* Parse error already reported */ + } else if (qobject_type(key_obj) != QTYPE_QSTRING) { + /* An interpolation was valid syntactically but not %s */ + parse_error(ctxt, token, "key is not a string in object"); + } else { + /* Store key in a special entry on the stack */ + push_entry(ctxt, key_obj, END_OF_KEY); + } + return NULL; + + case END_OF_KEY: + /* Expecting ':' after key */ + assert(qobject_type(entry->partial) == QTYPE_QSTRING); + if (token->type == JSON_COLON) { + entry->state = BEFORE_VALUE; + } else { + parse_error(ctxt, token, "expecting ':'"); + } + return NULL; + + case AFTER_LSQUARE: + /* Grab ']' for empty array or fall through to BEFORE_VALUE */ + assert(qobject_type(entry->partial) == QTYPE_QLIST); + if (token->type == JSON_RSQUARE) { + value = entry->partial; + entry = pop_entry(ctxt); + break; + } + entry->state = BEFORE_VALUE; + /* fall through */ + + case BEFORE_VALUE: + /* Expecting value */ + assert(!entry || qobject_type(entry->partial) != QTYPE_QDICT); + value = parse_begin_value(ctxt, token); + if (!value) { + /* Error or '['/'{' */ + return NULL; + } + /* Return value or insert it into a container */ + break; + + case END_OF_VALUE: + /* Grab ',' or ']' for array; ',' or '}' for object */ + if (qobject_to(QList, entry->partial)) { + /* Array */ + if (token->type != JSON_RSQUARE) { + if (token->type == JSON_COMMA) { + entry->state = BEFORE_VALUE; + } else { + parse_error(ctxt, token, "expected ',' or ']'"); + } + return NULL; + } + } else if (qobject_to(QDict, entry->partial)) { + /* Object */ + if (token->type != JSON_RCURLY) { + if (token->type == JSON_COMMA) { + entry->state = BEFORE_KEY; + } else { + parse_error(ctxt, token, "expected ',' or '}'"); + } + return NULL; + } + } else { + g_assert_not_reached(); + } + + /* Got ']' or '}'; return full value or insert into parent container */ + value = entry->partial; + entry = pop_entry(ctxt); + break; + } + + assert(value); + if (entry == NULL) { + /* Parse stack now empty, the top-level value is complete. */ + return value; + } + + /* + * Parse stack is not empty and entry->partial is the top of stack. + * It's a QString with the key (and a QDict is below it) if we're + * parsing an object, or a QList if we're parsing an array. + */ + key = qobject_to(QString, entry->partial); + if (key) { + const char *key_str; + QDict *dict; + + /* Pop off key, and store (key, value) in QDict. */ + entry = pop_entry(ctxt); + dict = qobject_to(QDict, entry->partial); + assert(dict); + key_str = qstring_get_str(key); + if (qdict_haskey(dict, key_str)) { + parse_error(ctxt, token, "duplicate key"); + qobject_unref(value); + return NULL; + } + qdict_put_obj(dict, key_str, value); + qobject_unref(key); + } else { + /* Array, just store value in the QList. */ + qlist_append_obj(qobject_to(QList, entry->partial), value); + } + + entry->state = END_OF_VALUE; + return NULL; +} + + +void json_parser_reset(JSONParserContext *ctxt) { - JSONToken *token = g_malloc(sizeof(JSONToken) + tokstr->len + 1); - - token->type = type; - memcpy(token->str, tokstr->str, tokstr->len); - token->str[tokstr->len] = 0; - token->x = x; - token->y = y; - return token; + JSONParserStackEntry *entry; + + ctxt->err = NULL; + while ((entry = g_queue_pop_tail(ctxt->stack)) != NULL) { + qobject_unref(entry->partial); + g_free(entry); + } } -QObject *json_parser_parse(GQueue *tokens, va_list *ap, Error **errp) +void json_parser_init(JSONParserContext *ctxt, va_list *ap) { - JSONParserContext ctxt = { .buf = tokens, .ap = ap }; - QObject *result; + ctxt->stack = g_queue_new(); + ctxt->ap = ap; + json_parser_reset(ctxt); +} - result = parse_value(&ctxt); - assert(ctxt.err || g_queue_is_empty(ctxt.buf)); +void json_parser_destroy(JSONParserContext *ctxt) +{ + json_parser_reset(ctxt); + g_queue_free(ctxt->stack); + ctxt->stack = NULL; +} + +/* + * Advance the parser based on the token that is passed. + * Return the finished top-level value if the token completes it, else + * NULL. + * Once an error is returned, the function must not be called again + * without first resetting the parser. + */ +QObject *json_parser_feed(JSONParserContext *ctxt, const JSONToken *token, + Error **errp) +{ + QObject *result = NULL; - error_propagate(errp, ctxt.err); + assert(!ctxt->err); + switch (token->type) { + case JSON_ERROR: + parse_error(ctxt, token, "stray '%s'", token->str); + break; + + case JSON_END_OF_INPUT: + /* Check for premature end of input */ + if (!g_queue_is_empty(ctxt->stack)) { + parse_error(ctxt, token, "premature end of input"); + } + break; - while (!g_queue_is_empty(ctxt.buf)) { - parser_context_pop_token(&ctxt); + default: + result = parse_token(ctxt, token); + break; } - g_free(ctxt.current); + error_propagate(errp, ctxt->err); return result; } diff --git a/qobject/json-streamer.c b/qobject/json-streamer.c index b93d97b995..b634bbe99c 100644 --- a/qobject/json-streamer.c +++ b/qobject/json-streamer.c @@ -1,5 +1,5 @@ /* - * JSON streaming support + * JSON parser - callback interface and error recovery * * Copyright IBM, Corp. 2009 * @@ -19,84 +19,93 @@ #define MAX_TOKEN_COUNT (2ULL << 20) #define MAX_NESTING (1 << 10) -static void json_message_free_tokens(JSONMessageParser *parser) -{ - JSONToken *token; - - while ((token = g_queue_pop_head(&parser->tokens))) { - g_free(token); - } -} - void json_message_process_token(JSONLexer *lexer, GString *input, JSONTokenType type, int x, int y) { JSONMessageParser *parser = container_of(lexer, JSONMessageParser, lexer); - QObject *json = NULL; Error *err = NULL; - JSONToken *token; + parser->token_size += input->len; + parser->token_count++; + + /* Detect message boundaries for error recovery purposes. */ switch (type) { case JSON_LCURLY: parser->brace_count++; break; case JSON_RCURLY: + if (!parser->brace_count) { + goto end_error_recovery; + } parser->brace_count--; break; case JSON_LSQUARE: parser->bracket_count++; break; case JSON_RSQUARE: + if (!parser->bracket_count) { + goto end_error_recovery; + } parser->bracket_count--; break; case JSON_ERROR: - error_setg(&err, "JSON parse error, stray '%s'", input->str); - goto out_emit; - case JSON_END_OF_INPUT: - if (g_queue_is_empty(&parser->tokens)) { - return; - } - json = json_parser_parse(&parser->tokens, parser->ap, &err); - goto out_emit; + end_error_recovery: + /* + * We come here due to receiving either JSON_ERROR or a + * JSON_R{CURLY,SQUARE}) that is known to be unbalanced. + * If in error recovery, end it immediately. If not in + * error recovery, json_parser_feed() will raise an error + * but error recovery won't be entered at all. + */ + parser->brace_count = 0; + parser->bracket_count = 0; + break; default: break; } - /* - * Security consideration, we limit total memory allocated per object - * and the maximum recursion depth that a message can force. - */ - if (parser->token_size + input->len + 1 > MAX_TOKEN_SIZE) { - error_setg(&err, "JSON token size limit exceeded"); - goto out_emit; - } - if (g_queue_get_length(&parser->tokens) + 1 > MAX_TOKEN_COUNT) { - error_setg(&err, "JSON token count limit exceeded"); - goto out_emit; - } - if (parser->bracket_count + parser->brace_count > MAX_NESTING) { - error_setg(&err, "JSON nesting depth limit exceeded"); - goto out_emit; - } - - token = json_token(type, x, y, input); - parser->token_size += input->len; - - g_queue_push_tail(&parser->tokens, token); + if (parser->error) { + /* error recovery, eat tokens until parentheses balance */ + } else { + /* + * Safety consideration, we limit total memory allocated per object + * and the maximum nesting depth that a message can force. + */ + if (parser->token_size >= MAX_TOKEN_SIZE) { + error_setg(&err, "JSON token size limit exceeded"); + } else if (parser->token_count > MAX_TOKEN_COUNT) { + error_setg(&err, "JSON token count limit exceeded"); + } else if (parser->bracket_count + parser->brace_count > MAX_NESTING) { + error_setg(&err, "JSON nesting depth limit exceeded"); + } else { + JSONToken token = (JSONToken) { + .type = type, + .x = x, + .y = y, + .str = input->str + }; + QObject *json = json_parser_feed(&parser->parser, &token, &err); + if (json) { + parser->emit(parser->opaque, json, NULL); + } + } - if ((parser->brace_count > 0 || parser->bracket_count > 0) - && parser->brace_count >= 0 && parser->bracket_count >= 0) { - return; + if (err) { + parser->emit(parser->opaque, NULL, err); + /* start recovery */ + parser->error = true; + } } - json = json_parser_parse(&parser->tokens, parser->ap, &err); - -out_emit: - parser->brace_count = 0; - parser->bracket_count = 0; - json_message_free_tokens(parser); - parser->token_size = 0; - parser->emit(parser->opaque, json, err); + if ((parser->brace_count == 0 && parser->bracket_count == 0) + || type == JSON_END_OF_INPUT) { + json_parser_reset(&parser->parser); + parser->error = false; + parser->brace_count = 0; + parser->bracket_count = 0; + parser->token_count = 0; + parser->token_size = 0; + } } void json_message_parser_init(JSONMessageParser *parser, @@ -106,12 +115,13 @@ void json_message_parser_init(JSONMessageParser *parser, { parser->emit = emit; parser->opaque = opaque; - parser->ap = ap; + parser->error = false; parser->brace_count = 0; parser->bracket_count = 0; - g_queue_init(&parser->tokens); + parser->token_count = 0; parser->token_size = 0; + json_parser_init(&parser->parser, ap); json_lexer_init(&parser->lexer, !!ap); } @@ -124,11 +134,10 @@ void json_message_parser_feed(JSONMessageParser *parser, void json_message_parser_flush(JSONMessageParser *parser) { json_lexer_flush(&parser->lexer); - assert(g_queue_is_empty(&parser->tokens)); } void json_message_parser_destroy(JSONMessageParser *parser) { json_lexer_destroy(&parser->lexer); - json_message_free_tokens(parser); + json_parser_destroy(&parser->parser); } diff --git a/qobject/json-writer.c b/qobject/json-writer.c index aac2c6ab71..c23c81709b 100644 --- a/qobject/json-writer.c +++ b/qobject/json-writer.c @@ -24,13 +24,16 @@ struct JSONWriter { GByteArray *container_is_array; }; +/* Should cover most QMP responses without reallocation */ +#define JSON_WRITER_INITIAL_SIZE 4096 + JSONWriter *json_writer_new(bool pretty) { JSONWriter *writer = g_new(JSONWriter, 1); writer->pretty = pretty; writer->need_comma = false; - writer->contents = g_string_new(NULL); + writer->contents = g_string_sized_new(JSON_WRITER_INITIAL_SIZE); writer->container_is_array = g_byte_array_new(); return writer; } diff --git a/roms/SLOF b/roms/SLOF -Subproject b7f755248e2dcc56c02634d288e1c0ff7e0ce1c +Subproject 47457c9078a217150aa1634f2657e6804fbde46 diff --git a/rust/bindings/system-sys/lib.rs b/rust/bindings/system-sys/lib.rs index 022fe65dd8..30adf683c3 100644 --- a/rust/bindings/system-sys/lib.rs +++ b/rust/bindings/system-sys/lib.rs @@ -20,7 +20,7 @@ use common::Zeroable; use hwcore_sys::{qemu_irq, DeviceClass, DeviceState}; -use qom_sys::{InterfaceClass, Object, ObjectClass}; +use qom_sys::{Object, ObjectClass}; use util_sys::{Error, EventNotifier, QEMUBH}; #[cfg(MESON)] diff --git a/scripts/lsan_suppressions.txt b/scripts/lsan_suppressions.txt index f88bbab18b..2dd6581a65 100644 --- a/scripts/lsan_suppressions.txt +++ b/scripts/lsan_suppressions.txt @@ -10,6 +10,10 @@ leak:/lib64/libtcmalloc_minimal.so.4 # libxkbcommon also leaks in qemu-keymap leak:/lib64/libxkbcommon.so.0 +# libfontconfig leaks are notorious, for ex +# https://gitlab.freedesktop.org/fontconfig/fontconfig/-/work_items/519 +leak:libfontconfig.so + # g_set_user_dirs() deliberately leaks the previous cached g_get_user_* # values. This is documented in upstream glib's valgrind-format # suppression file: @@ -17,10 +21,7 @@ leak:/lib64/libxkbcommon.so.0 # This avoids false positive leak reports for the qga-ssh-test. leak:g_set_user_dirs -# qemu_irq_intercept_in is only used by the qtest harness, and -# its API inherently involves a leak. -# While we could keep track of the old IRQ data structure -# in order to free it, it doesn't seem very important to fix -# since it is only used by the qtest test harness. -# Just ignore the leak, at least for the moment. -leak:qemu_irq_intercept_in +# spice_server_add_interface allocates internal channel data that +# spice_server_destroy does not free +# https://gitlab.freedesktop.org/spice/spice/-/merge_requests/246 +leak:spice_server_add_interface diff --git a/system/cpus.c b/system/cpus.c index 49f6daec3c..b31c825b46 100644 --- a/system/cpus.c +++ b/system/cpus.c @@ -43,6 +43,7 @@ #include "system/physmem.h" #include "system/replay.h" #include "system/runstate.h" +#include "migration/misc.h" #include "system/cpu-timers.h" #include "system/whpx.h" #include "hw/core/boards.h" @@ -843,6 +844,11 @@ void qmp_memsave(uint64_t addr, uint64_t size, const char *filename, uint8_t buf[1024]; uint64_t orig_addr = addr, orig_size = size; + if (migration_guest_ram_loading()) { + error_setg(errp, "Guest memory access not allowed during migration"); + return; + } + if (!has_cpu) { cpu_index = 0; } @@ -889,6 +895,11 @@ void qmp_pmemsave(uint64_t addr, uint64_t size, const char *filename, uint64_t l; uint8_t buf[1024]; + if (migration_guest_ram_loading()) { + error_setg(errp, "Guest memory access not allowed during migration"); + return; + } + f = fopen(filename, "wb"); if (!f) { error_setg_file_open(errp, errno, filename); diff --git a/system/memory.c b/system/memory.c index 8436668c18..5fc36708ec 100644 --- a/system/memory.c +++ b/system/memory.c @@ -1771,6 +1771,7 @@ static void memory_region_finalize(Object *obj) memory_region_clear_coalescing(mr); g_free((char *)mr->name); g_free(mr->ioeventfds); + object_unref(mr->rdm); } Object *memory_region_owner(const MemoryRegion *mr) @@ -2069,75 +2070,33 @@ RamDiscardManager *memory_region_get_ram_discard_manager(MemoryRegion *mr) return mr->rdm; } -int memory_region_set_ram_discard_manager(MemoryRegion *mr, - RamDiscardManager *rdm) +int memory_region_add_ram_discard_source(MemoryRegion *mr, + RamDiscardSource *source) { g_assert(memory_region_is_ram(mr)); - if (mr->rdm && rdm) { - return -EBUSY; - } - - mr->rdm = rdm; - return 0; -} - -uint64_t ram_discard_manager_get_min_granularity(const RamDiscardManager *rdm, - const MemoryRegion *mr) -{ - RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_GET_CLASS(rdm); - - g_assert(rdmc->get_min_granularity); - return rdmc->get_min_granularity(rdm, mr); -} -bool ram_discard_manager_is_populated(const RamDiscardManager *rdm, - const MemoryRegionSection *section) -{ - RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_GET_CLASS(rdm); - - g_assert(rdmc->is_populated); - return rdmc->is_populated(rdm, section); -} - -int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, - MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, - void *opaque) -{ - RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_GET_CLASS(rdm); - - g_assert(rdmc->replay_populated); - return rdmc->replay_populated(rdm, section, replay_fn, opaque); -} - -int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, - MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, - void *opaque) -{ - RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_GET_CLASS(rdm); - - g_assert(rdmc->replay_discarded); - return rdmc->replay_discarded(rdm, section, replay_fn, opaque); -} - -void ram_discard_manager_register_listener(RamDiscardManager *rdm, - RamDiscardListener *rdl, - MemoryRegionSection *section) -{ - RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_GET_CLASS(rdm); + if (!mr->rdm) { + mr->rdm = ram_discard_manager_new(mr); + } - g_assert(rdmc->register_listener); - rdmc->register_listener(rdm, rdl, section); + return ram_discard_manager_add_source(mr->rdm, source); } -void ram_discard_manager_unregister_listener(RamDiscardManager *rdm, - RamDiscardListener *rdl) +int memory_region_del_ram_discard_source(MemoryRegion *mr, + RamDiscardSource *source) { - RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_GET_CLASS(rdm); + int ret; + g_assert(mr->rdm); - g_assert(rdmc->unregister_listener); - rdmc->unregister_listener(rdm, rdl); + ret = ram_discard_manager_del_source(mr->rdm, source); + if (ret != 0) { + return ret; + } + if (QLIST_EMPTY(&mr->rdm->source_list) && QLIST_EMPTY(&mr->rdm->rdl_list)) { + object_unref(mr->rdm); + mr->rdm = NULL; + } + return 0; } /* Called with rcu_read_lock held. */ @@ -3769,17 +3728,10 @@ static const TypeInfo iommu_memory_region_info = { .abstract = true, }; -static const TypeInfo ram_discard_manager_info = { - .parent = TYPE_INTERFACE, - .name = TYPE_RAM_DISCARD_MANAGER, - .class_size = sizeof(RamDiscardManagerClass), -}; - static void memory_register_types(void) { type_register_static(&memory_region_info); type_register_static(&iommu_memory_region_info); - type_register_static(&ram_discard_manager_info); } type_init(memory_register_types) diff --git a/system/memory_mapping.c b/system/memory_mapping.c index da708a08ab..cacef504f6 100644 --- a/system/memory_mapping.c +++ b/system/memory_mapping.c @@ -196,7 +196,7 @@ typedef struct GuestPhysListener { } GuestPhysListener; static void guest_phys_block_add_section(GuestPhysListener *g, - MemoryRegionSection *section) + const MemoryRegionSection *section) { const hwaddr target_start = section->offset_within_address_space; const hwaddr target_end = target_start + int128_get64(section->size); @@ -248,7 +248,7 @@ static void guest_phys_block_add_section(GuestPhysListener *g, #endif } -static int guest_phys_ram_populate_cb(MemoryRegionSection *section, +static int guest_phys_ram_populate_cb(const MemoryRegionSection *section, void *opaque) { GuestPhysListener *g = opaque; diff --git a/system/meson.build b/system/meson.build index 9cdfe1b3e7..cd3193d170 100644 --- a/system/meson.build +++ b/system/meson.build @@ -14,6 +14,7 @@ system_ss.add(files( 'globals.c', 'ioport.c', 'ram-block-attributes.c', + 'ram-discard-manager.c', 'memory_mapping.c', 'memory.c', 'physmem.c', diff --git a/system/physmem.c b/system/physmem.c index 9e5b50c5b1..c21ea92915 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -839,12 +839,12 @@ found: /* It is safe to write mru_block outside the BQL. This * is what happens: * - * mru_block = xxx + * qatomic_set(&mru_block, xxx) * rcu_read_unlock() * xxx removed from list * rcu_read_lock() * read mru_block - * mru_block = NULL; + * qatomic_set(&mru_block, NULL); * call_rcu(reclaim_ramblock, xxx); * rcu_read_unlock() * @@ -852,7 +852,7 @@ found: * when it was placed into the list. Here we're just making an extra * copy of the pointer. */ - ram_list.mru_block = block; + qatomic_set(&ram_list.mru_block, block); return block; } @@ -2260,11 +2260,10 @@ static void ram_block_add(RAMBlock *new_block, Error **errp) } else { /* list is empty */ QLIST_INSERT_HEAD_RCU(&ram_list.blocks, new_block, next); } - ram_list.mru_block = NULL; + qatomic_set(&ram_list.mru_block, NULL); /* Write list before version */ - smp_wmb(); - ram_list.version++; + qatomic_store_release(&ram_list.version, ram_list.version + 1); qemu_mutex_unlock_ramlist(); physical_memory_set_dirty_range(new_block->offset, @@ -2583,7 +2582,6 @@ static void reclaim_ramblock(RAMBlock *block) } if (block->guest_memfd >= 0) { - ram_block_attributes_destroy(block->attributes); close(block->guest_memfd); ram_block_coordinated_discard_require(false); } @@ -2608,10 +2606,10 @@ void qemu_ram_free(RAMBlock *block) name = cpr_name(block->mr); cpr_delete_fd(name, 0); QLIST_REMOVE_RCU(block, next); - ram_list.mru_block = NULL; + qatomic_set(&ram_list.mru_block, NULL); /* Write list before version */ - smp_wmb(); - ram_list.version++; + qatomic_store_release(&ram_list.version, ram_list.version + 1); + g_clear_pointer(&block->attributes, ram_block_attributes_destroy); call_rcu(block, reclaim_ramblock, rcu); qemu_mutex_unlock_ramlist(); } @@ -4095,7 +4093,7 @@ int qemu_ram_foreach_block(RAMBlockIterFunc func, void *opaque) * Returns: 0 on success, none-0 on failure * */ -int ram_block_discard_range(RAMBlock *rb, uint64_t offset, size_t length) +int ram_block_discard_shared_range(RAMBlock *rb, uint64_t offset, size_t length) { int ret = -1; @@ -4144,7 +4142,7 @@ int ram_block_discard_range(RAMBlock *rb, uint64_t offset, size_t length) * have a MAP_PRIVATE mapping, possibly messing with other * MAP_PRIVATE/MAP_SHARED mappings. There is no easy way to * change that behavior whithout violating the promised - * semantics of ram_block_discard_range(). + * semantics of ram_block_discard_shared_range(). * * Only warn, because it works as long as nobody else uses that * file. @@ -4200,8 +4198,9 @@ int ram_block_discard_range(RAMBlock *rb, uint64_t offset, size_t length) goto err; #endif } - trace_ram_block_discard_range(rb->idstr, host_startaddr, length, - need_madvise, need_fallocate, ret); + trace_ram_block_discard_shared_range(rb->idstr, host_startaddr, length, + need_madvise, need_fallocate, + ret); } else { error_report("%s: Overrun block '%s' (%" PRIu64 "/%zx/" RAM_ADDR_FMT")", __func__, rb->idstr, offset, length, rb->max_length); @@ -4211,6 +4210,22 @@ err: return ret; } +int ram_block_discard_range(RAMBlock *rb, uint64_t offset, size_t length) +{ + int ret; + + ret = ram_block_discard_shared_range(rb, offset, length); + if (ret) { + return ret; + } + + if (rb->guest_memfd >= 0) { + ret = ram_block_discard_guest_memfd_range(rb, offset, length); + } + + return ret; +} + int ram_block_discard_guest_memfd_range(RAMBlock *rb, uint64_t offset, size_t length) { diff --git a/system/qdev-monitor.c b/system/qdev-monitor.c index dfc95a08c1..00fed791cc 100644 --- a/system/qdev-monitor.c +++ b/system/qdev-monitor.c @@ -71,7 +71,7 @@ typedef struct QDevAlias QEMU_ARCH_SPARC | \ QEMU_ARCH_XTENSA) #define QEMU_ARCH_VIRTIO_CCW (QEMU_ARCH_S390X) -#define QEMU_ARCH_VIRTIO_MMIO (QEMU_ARCH_M68K) +#define QEMU_ARCH_VIRTIO_MMIO (QEMU_ARCH_M68K | QEMU_ARCH_HEXAGON) /* Please keep this table sorted by typename. */ static const QDevAlias qdev_alias_table[] = { diff --git a/system/qtest.c b/system/qtest.c index fd37bcbfaa..cf30123917 100644 --- a/system/qtest.c +++ b/system/qtest.c @@ -326,9 +326,6 @@ void qtest_sendf(CharFrontend *chr, const char *fmt, ...) static void qtest_irq_handler(void *opaque, int n, int level) { - qemu_irq old_irq = *(qemu_irq *)opaque; - qemu_set_irq(old_irq, level); - if (irq_levels[n] != level) { CharFrontend *chr = &qtest->qtest_chr; irq_levels[n] = level; @@ -421,7 +418,7 @@ static void qtest_process_command(CharFrontend *chr, gchar **words) interception_succeeded = true; } } else { - qemu_irq_intercept_in(ngl->in, qtest_irq_handler, + qemu_irq_set_observer(ngl->in, qtest_irq_handler, ngl->num_in); interception_succeeded = true; } diff --git a/system/ram-block-attributes.c b/system/ram-block-attributes.c index 630b0fda12..59ec7a28eb 100644 --- a/system/ram-block-attributes.c +++ b/system/ram-block-attributes.c @@ -18,7 +18,7 @@ OBJECT_DEFINE_SIMPLE_TYPE_WITH_INTERFACES(RamBlockAttributes, ram_block_attributes, RAM_BLOCK_ATTRIBUTES, OBJECT, - { TYPE_RAM_DISCARD_MANAGER }, + { TYPE_RAM_DISCARD_SOURCE }, { }) static size_t @@ -32,12 +32,22 @@ ram_block_attributes_get_block_size(void) return qemu_real_host_page_size(); } +/* RamDiscardSource interface implementation */ +static uint64_t +ram_block_attributes_rds_get_min_granularity(const RamDiscardSource *rds, + const MemoryRegion *mr) +{ + const RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rds); + + g_assert(mr == attr->ram_block->mr); + return ram_block_attributes_get_block_size(); +} static bool -ram_block_attributes_rdm_is_populated(const RamDiscardManager *rdm, +ram_block_attributes_rds_is_populated(const RamDiscardSource *rds, const MemoryRegionSection *section) { - const RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); + const RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rds); const size_t block_size = ram_block_attributes_get_block_size(); const uint64_t first_bit = section->offset_within_region / block_size; const uint64_t last_bit = @@ -49,190 +59,6 @@ ram_block_attributes_rdm_is_populated(const RamDiscardManager *rdm, return first_discarded_bit > last_bit; } -typedef int (*ram_block_attributes_section_cb)(MemoryRegionSection *s, - void *arg); - -static int -ram_block_attributes_notify_populate_cb(MemoryRegionSection *section, - void *arg) -{ - RamDiscardListener *rdl = arg; - - return rdl->notify_populate(rdl, section); -} - -static int -ram_block_attributes_for_each_populated_section(const RamBlockAttributes *attr, - MemoryRegionSection *section, - void *arg, - ram_block_attributes_section_cb cb) -{ - unsigned long first_bit, last_bit; - uint64_t offset, size; - const size_t block_size = ram_block_attributes_get_block_size(); - int ret = 0; - - first_bit = section->offset_within_region / block_size; - first_bit = find_next_bit(attr->bitmap, attr->bitmap_size, - first_bit); - - while (first_bit < attr->bitmap_size) { - MemoryRegionSection tmp = *section; - - offset = first_bit * block_size; - last_bit = find_next_zero_bit(attr->bitmap, attr->bitmap_size, - first_bit + 1) - 1; - size = (last_bit - first_bit + 1) * block_size; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - break; - } - - ret = cb(&tmp, arg); - if (ret) { - error_report("%s: Failed to notify RAM discard listener: %s", - __func__, strerror(-ret)); - break; - } - - first_bit = find_next_bit(attr->bitmap, attr->bitmap_size, - last_bit + 2); - } - - return ret; -} - -static int -ram_block_attributes_for_each_discarded_section(const RamBlockAttributes *attr, - MemoryRegionSection *section, - void *arg, - ram_block_attributes_section_cb cb) -{ - unsigned long first_bit, last_bit; - uint64_t offset, size; - const size_t block_size = ram_block_attributes_get_block_size(); - int ret = 0; - - first_bit = section->offset_within_region / block_size; - first_bit = find_next_zero_bit(attr->bitmap, attr->bitmap_size, - first_bit); - - while (first_bit < attr->bitmap_size) { - MemoryRegionSection tmp = *section; - - offset = first_bit * block_size; - last_bit = find_next_bit(attr->bitmap, attr->bitmap_size, - first_bit + 1) - 1; - size = (last_bit - first_bit + 1) * block_size; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - break; - } - - ret = cb(&tmp, arg); - if (ret) { - error_report("%s: Failed to notify RAM discard listener: %s", - __func__, strerror(-ret)); - break; - } - - first_bit = find_next_zero_bit(attr->bitmap, - attr->bitmap_size, - last_bit + 2); - } - - return ret; -} - -static uint64_t -ram_block_attributes_rdm_get_min_granularity(const RamDiscardManager *rdm, - const MemoryRegion *mr) -{ - const RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); - - g_assert(mr == attr->ram_block->mr); - return ram_block_attributes_get_block_size(); -} - -static void -ram_block_attributes_rdm_register_listener(RamDiscardManager *rdm, - RamDiscardListener *rdl, - MemoryRegionSection *section) -{ - RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); - int ret; - - g_assert(section->mr == attr->ram_block->mr); - rdl->section = memory_region_section_new_copy(section); - - QLIST_INSERT_HEAD(&attr->rdl_list, rdl, next); - - ret = ram_block_attributes_for_each_populated_section(attr, section, rdl, - ram_block_attributes_notify_populate_cb); - if (ret) { - error_report("%s: Failed to register RAM discard listener: %s", - __func__, strerror(-ret)); - exit(1); - } -} - -static void -ram_block_attributes_rdm_unregister_listener(RamDiscardManager *rdm, - RamDiscardListener *rdl) -{ - RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); - - g_assert(rdl->section); - g_assert(rdl->section->mr == attr->ram_block->mr); - - rdl->notify_discard(rdl, rdl->section); - - memory_region_section_free_copy(rdl->section); - rdl->section = NULL; - QLIST_REMOVE(rdl, next); -} - -typedef struct RamBlockAttributesReplayData { - ReplayRamDiscardState fn; - void *opaque; -} RamBlockAttributesReplayData; - -static int ram_block_attributes_rdm_replay_cb(MemoryRegionSection *section, - void *arg) -{ - RamBlockAttributesReplayData *data = arg; - - return data->fn(section, data->opaque); -} - -static int -ram_block_attributes_rdm_replay_populated(const RamDiscardManager *rdm, - MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, - void *opaque) -{ - RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); - RamBlockAttributesReplayData data = { .fn = replay_fn, .opaque = opaque }; - - g_assert(section->mr == attr->ram_block->mr); - return ram_block_attributes_for_each_populated_section(attr, section, &data, - ram_block_attributes_rdm_replay_cb); -} - -static int -ram_block_attributes_rdm_replay_discarded(const RamDiscardManager *rdm, - MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, - void *opaque) -{ - RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); - RamBlockAttributesReplayData data = { .fn = replay_fn, .opaque = opaque }; - - g_assert(section->mr == attr->ram_block->mr); - return ram_block_attributes_for_each_discarded_section(attr, section, &data, - ram_block_attributes_rdm_replay_cb); -} - static bool ram_block_attributes_is_valid_range(RamBlockAttributes *attr, uint64_t offset, uint64_t size) @@ -257,42 +83,25 @@ ram_block_attributes_is_valid_range(RamBlockAttributes *attr, uint64_t offset, return true; } -static void ram_block_attributes_notify_discard(RamBlockAttributes *attr, - uint64_t offset, - uint64_t size) +static void +ram_block_attributes_notify_discard(RamBlockAttributes *attr, + uint64_t offset, + uint64_t size) { - RamDiscardListener *rdl; + RamDiscardManager *rdm = memory_region_get_ram_discard_manager(attr->ram_block->mr); - QLIST_FOREACH(rdl, &attr->rdl_list, next) { - MemoryRegionSection tmp = *rdl->section; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - rdl->notify_discard(rdl, &tmp); - } + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(attr), + offset, size); } static int ram_block_attributes_notify_populate(RamBlockAttributes *attr, uint64_t offset, uint64_t size) { - RamDiscardListener *rdl; - int ret = 0; - - QLIST_FOREACH(rdl, &attr->rdl_list, next) { - MemoryRegionSection tmp = *rdl->section; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - ret = rdl->notify_populate(rdl, &tmp); - if (ret) { - break; - } - } + RamDiscardManager *rdm = memory_region_get_ram_discard_manager(attr->ram_block->mr); - return ret; + return ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(attr), + offset, size); } int ram_block_attributes_state_change(RamBlockAttributes *attr, @@ -376,7 +185,8 @@ RamBlockAttributes *ram_block_attributes_create(RAMBlock *ram_block) attr = RAM_BLOCK_ATTRIBUTES(object_new(TYPE_RAM_BLOCK_ATTRIBUTES)); attr->ram_block = ram_block; - if (memory_region_set_ram_discard_manager(mr, RAM_DISCARD_MANAGER(attr))) { + + if (memory_region_add_ram_discard_source(mr, RAM_DISCARD_SOURCE(attr))) { object_unref(OBJECT(attr)); return NULL; } @@ -391,15 +201,12 @@ void ram_block_attributes_destroy(RamBlockAttributes *attr) g_assert(attr); g_free(attr->bitmap); - memory_region_set_ram_discard_manager(attr->ram_block->mr, NULL); + memory_region_del_ram_discard_source(attr->ram_block->mr, RAM_DISCARD_SOURCE(attr)); object_unref(OBJECT(attr)); } static void ram_block_attributes_init(Object *obj) { - RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(obj); - - QLIST_INIT(&attr->rdl_list); } static void ram_block_attributes_finalize(Object *obj) @@ -409,12 +216,8 @@ static void ram_block_attributes_finalize(Object *obj) static void ram_block_attributes_class_init(ObjectClass *klass, const void *data) { - RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_CLASS(klass); + RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_CLASS(klass); - rdmc->get_min_granularity = ram_block_attributes_rdm_get_min_granularity; - rdmc->register_listener = ram_block_attributes_rdm_register_listener; - rdmc->unregister_listener = ram_block_attributes_rdm_unregister_listener; - rdmc->is_populated = ram_block_attributes_rdm_is_populated; - rdmc->replay_populated = ram_block_attributes_rdm_replay_populated; - rdmc->replay_discarded = ram_block_attributes_rdm_replay_discarded; + rdsc->get_min_granularity = ram_block_attributes_rds_get_min_granularity; + rdsc->is_populated = ram_block_attributes_rds_is_populated; } diff --git a/system/ram-discard-manager.c b/system/ram-discard-manager.c new file mode 100644 index 0000000000..4e8816e5a2 --- /dev/null +++ b/system/ram-discard-manager.c @@ -0,0 +1,612 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * RAM Discard Manager + * + * Copyright Red Hat, Inc. 2026 + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "qemu/queue.h" +#include "system/memory.h" + +static uint64_t ram_discard_source_get_min_granularity(const RamDiscardSource *rds, + const MemoryRegion *mr) +{ + RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds); + + g_assert(rdsc->get_min_granularity); + return rdsc->get_min_granularity(rds, mr); +} + +static bool ram_discard_source_is_populated(const RamDiscardSource *rds, + const MemoryRegionSection *section) +{ + RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds); + + g_assert(rdsc->is_populated); + return rdsc->is_populated(rds, section); +} + +/* + * Iterate a single source's populated or discarded regions and call + * replay_fn for each contiguous run. + */ +static int replay_source_by_state(const RamDiscardSource *source, + const MemoryRegion *mr, + const MemoryRegionSection *section, + bool replay_populated, + ReplayRamDiscardState replay_fn, + void *opaque) +{ + uint64_t granularity, offset, size, end, pos, run_start = 0; + bool in_run = false; + int ret = 0; + + granularity = ram_discard_source_get_min_granularity(source, mr); + offset = section->offset_within_region; + size = int128_get64(section->size); + end = offset + size; + + /* Align iteration to granularity boundaries */ + pos = QEMU_ALIGN_DOWN(offset, granularity); + + for (; pos < end; pos += granularity) { + MemoryRegionSection chunk = { + .mr = section->mr, + .offset_within_region = pos, + .size = int128_make64(granularity), + }; + bool populated = ram_discard_source_is_populated(source, &chunk); + + if (populated == replay_populated) { + if (!in_run) { + run_start = pos; + in_run = true; + } + } else if (in_run) { + MemoryRegionSection tmp = *section; + + if (memory_region_section_intersect_range(&tmp, run_start, + pos - run_start)) { + ret = replay_fn(&tmp, opaque); + if (ret) { + return ret; + } + } + in_run = false; + } + } + + if (in_run) { + MemoryRegionSection tmp = *section; + + if (memory_region_section_intersect_range(&tmp, run_start, + pos - run_start)) { + ret = replay_fn(&tmp, opaque); + } + } + + return ret; +} + +RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr) +{ + RamDiscardManager *rdm; + + rdm = RAM_DISCARD_MANAGER(object_new(TYPE_RAM_DISCARD_MANAGER)); + rdm->mr = mr; + return rdm; +} + +static void ram_discard_manager_update_granularity(RamDiscardManager *rdm) +{ + RamDiscardSourceEntry *entry; + uint64_t granularity = 0; + + QLIST_FOREACH(entry, &rdm->source_list, next) { + uint64_t src_granularity; + + src_granularity = + ram_discard_source_get_min_granularity(entry->rds, rdm->mr); + g_assert(src_granularity != 0); + if (granularity == 0) { + granularity = src_granularity; + } else { + granularity = MIN(granularity, src_granularity); + } + } + rdm->min_granularity = granularity; +} + +static RamDiscardSourceEntry * +ram_discard_manager_find_source(RamDiscardManager *rdm, RamDiscardSource *rds) +{ + RamDiscardSourceEntry *entry; + + QLIST_FOREACH(entry, &rdm->source_list, next) { + if (entry->rds == rds) { + return entry; + } + } + return NULL; +} + +static int rdl_populate_cb(const MemoryRegionSection *section, void *opaque) +{ + RamDiscardListener *rdl = opaque; + MemoryRegionSection tmp = *rdl->section; + + g_assert(section->mr == rdl->section->mr); + + if (!memory_region_section_intersect_range(&tmp, + section->offset_within_region, + int128_get64(section->size))) { + return 0; + } + + return rdl->notify_populate(rdl, &tmp); +} + +static int rdl_discard_cb(const MemoryRegionSection *section, void *opaque) +{ + RamDiscardListener *rdl = opaque; + MemoryRegionSection tmp = *rdl->section; + + g_assert(section->mr == rdl->section->mr); + + if (!memory_region_section_intersect_range(&tmp, + section->offset_within_region, + int128_get64(section->size))) { + return 0; + } + + rdl->notify_discard(rdl, &tmp); + return 0; +} + +static bool rdm_is_all_populated_skip(const RamDiscardManager *rdm, + const MemoryRegionSection *section, + const RamDiscardSource *skip_source) +{ + RamDiscardSourceEntry *entry; + + QLIST_FOREACH(entry, &rdm->source_list, next) { + if (skip_source && entry->rds == skip_source) { + continue; + } + if (!ram_discard_source_is_populated(entry->rds, section)) { + return false; + } + } + return true; +} + +typedef struct SourceNotifyCtx { + RamDiscardManager *rdm; + RamDiscardListener *rdl; + RamDiscardSource *source; /* added or removed */ +} SourceNotifyCtx; + +/* + * Unified helper to replay regions based on populated state. + * If replay_populated is true: replay regions where ALL sources are populated. + * If replay_populated is false: replay regions where ANY source is discarded. + */ +static int replay_by_populated_state(const RamDiscardManager *rdm, + const MemoryRegionSection *section, + const RamDiscardSource *skip_source, + bool replay_populated, + ReplayRamDiscardState replay_fn, + void *user_opaque) +{ + uint64_t granularity = rdm->min_granularity; + uint64_t offset, end_offset; + uint64_t run_start = 0; + bool in_run = false; + int ret = 0; + + if (QLIST_EMPTY(&rdm->source_list)) { + if (replay_populated) { + return replay_fn(section, user_opaque); + } + return 0; + } + + g_assert(granularity != 0); + + offset = section->offset_within_region; + end_offset = offset + int128_get64(section->size); + + while (offset < end_offset) { + MemoryRegionSection subsection = { + .mr = section->mr, + .offset_within_region = offset, + .size = int128_make64(MIN(granularity, end_offset - offset)), + }; + bool all_populated; + bool included; + + all_populated = rdm_is_all_populated_skip(rdm, &subsection, + skip_source); + included = replay_populated ? all_populated : !all_populated; + + if (included) { + if (!in_run) { + run_start = offset; + in_run = true; + } + } else { + if (in_run) { + MemoryRegionSection run_section = { + .mr = section->mr, + .offset_within_region = run_start, + .size = int128_make64(offset - run_start), + }; + ret = replay_fn(&run_section, user_opaque); + if (ret) { + return ret; + } + in_run = false; + } + } + if (granularity > end_offset - offset) { + break; + } + offset += granularity; + } + + if (in_run) { + MemoryRegionSection run_section = { + .mr = section->mr, + .offset_within_region = run_start, + .size = int128_make64(end_offset - run_start), + }; + ret = replay_fn(&run_section, user_opaque); + } + + return ret; +} + +static int add_source_check_discard_cb(const MemoryRegionSection *section, + void *opaque) +{ + SourceNotifyCtx *ctx = opaque; + + return replay_by_populated_state(ctx->rdm, section, ctx->source, true, + rdl_discard_cb, ctx->rdl); +} + +static int del_source_check_populate_cb(const MemoryRegionSection *section, + void *opaque) +{ + SourceNotifyCtx *ctx = opaque; + + return replay_by_populated_state(ctx->rdm, section, ctx->source, true, + rdl_populate_cb, ctx->rdl); +} + +int ram_discard_manager_add_source(RamDiscardManager *rdm, + RamDiscardSource *source) +{ + RamDiscardSourceEntry *entry; + RamDiscardListener *rdl, *rdl2; + int ret = 0; + + if (ram_discard_manager_find_source(rdm, source)) { + return -EBUSY; + } + + /* + * If there are existing listeners, notify them about regions that + * become discarded due to adding this source. Only notify for regions + * that were previously populated (all other sources agreed). + */ + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + SourceNotifyCtx ctx = { + .rdm = rdm, + .rdl = rdl, + /* no need to set source */ + }; + ret = replay_source_by_state(source, rdm->mr, rdl->section, + false, + add_source_check_discard_cb, &ctx); + if (ret) { + break; + } + } + if (ret) { + QLIST_FOREACH(rdl2, &rdm->rdl_list, next) { + SourceNotifyCtx ctx = { + .rdm = rdm, + .rdl = rdl2, + }; + replay_source_by_state(source, rdm->mr, rdl2->section, + false, + del_source_check_populate_cb, + &ctx); + if (rdl == rdl2) { + break; + } + } + + return ret; + } + + entry = g_new0(RamDiscardSourceEntry, 1); + entry->rds = source; + QLIST_INSERT_HEAD(&rdm->source_list, entry, next); + + ram_discard_manager_update_granularity(rdm); + + return ret; +} + +int ram_discard_manager_del_source(RamDiscardManager *rdm, + RamDiscardSource *source) +{ + RamDiscardSourceEntry *entry; + RamDiscardListener *rdl, *rdl2; + int ret = 0; + + entry = ram_discard_manager_find_source(rdm, source); + if (!entry) { + return -ENOENT; + } + + /* + * If there are existing listeners, check if any regions become + * populated due to removing this source. + */ + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + SourceNotifyCtx ctx = { + .rdm = rdm, + .rdl = rdl, + .source = source, + }; + /* + * From the previously discarded regions, check if any + * regions become populated. + */ + ret = replay_source_by_state(source, rdm->mr, rdl->section, + false, + del_source_check_populate_cb, + &ctx); + if (ret) { + break; + } + } + if (ret) { + QLIST_FOREACH(rdl2, &rdm->rdl_list, next) { + SourceNotifyCtx ctx = { + .rdm = rdm, + .rdl = rdl2, + .source = source, + }; + replay_source_by_state(source, rdm->mr, rdl2->section, + false, + add_source_check_discard_cb, + &ctx); + if (rdl == rdl2) { + break; + } + } + + return ret; + } + + QLIST_REMOVE(entry, next); + g_free(entry); + ram_discard_manager_update_granularity(rdm); + return ret; +} + +uint64_t ram_discard_manager_get_min_granularity(const RamDiscardManager *rdm, + const MemoryRegion *mr) +{ + g_assert(mr == rdm->mr); + return rdm->min_granularity; +} + +/* + * Aggregated query: returns true only if ALL sources report populated (AND). + */ +bool ram_discard_manager_is_populated(const RamDiscardManager *rdm, + const MemoryRegionSection *section) +{ + RamDiscardSourceEntry *entry; + + QLIST_FOREACH(entry, &rdm->source_list, next) { + if (!ram_discard_source_is_populated(entry->rds, section)) { + return false; + } + } + return true; +} + +int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, + const MemoryRegionSection *section, + ReplayRamDiscardState replay_fn, + void *opaque) +{ + return replay_by_populated_state(rdm, section, NULL, true, + replay_fn, opaque); +} + +int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, + const MemoryRegionSection *section, + ReplayRamDiscardState replay_fn, + void *opaque) +{ + return replay_by_populated_state(rdm, section, NULL, false, + replay_fn, opaque); +} + +static void ram_discard_manager_initfn(Object *obj) +{ + RamDiscardManager *rdm = RAM_DISCARD_MANAGER(obj); + + QLIST_INIT(&rdm->source_list); + QLIST_INIT(&rdm->rdl_list); + rdm->min_granularity = 0; +} + +static void ram_discard_manager_finalize(Object *obj) +{ + RamDiscardManager *rdm = RAM_DISCARD_MANAGER(obj); + + g_assert(QLIST_EMPTY(&rdm->rdl_list)); + g_assert(QLIST_EMPTY(&rdm->source_list)); +} + +int ram_discard_manager_notify_populate(RamDiscardManager *rdm, + RamDiscardSource *source, + uint64_t offset, uint64_t size) +{ + RamDiscardListener *rdl, *rdl2; + MemoryRegionSection section = { + .mr = rdm->mr, + .offset_within_region = offset, + .size = int128_make64(size), + }; + int ret = 0; + + g_assert(ram_discard_manager_find_source(rdm, source)); + + /* + * Only notify about regions that are populated in ALL sources. + * Skip the calling source: it has implicitly declared itself populated + * for this range but may not have updated its bitmap yet. + */ + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + ret = replay_by_populated_state(rdm, §ion, source, true, + rdl_populate_cb, rdl); + if (ret) { + break; + } + } + + if (ret) { + /* + * Rollback: notify discard for listeners we already notified, + * including the failing listener which may have been partially + * notified. Listeners must handle discard notifications for + * regions they didn't receive populate notifications for. + */ + QLIST_FOREACH(rdl2, &rdm->rdl_list, next) { + replay_by_populated_state(rdm, §ion, source, true, + rdl_discard_cb, rdl2); + if (rdl2 == rdl) { + break; + } + } + } + return ret; +} + +void ram_discard_manager_notify_discard(RamDiscardManager *rdm, + RamDiscardSource *source, + uint64_t offset, uint64_t size) +{ + RamDiscardListener *rdl; + MemoryRegionSection section = { + .mr = rdm->mr, + .offset_within_region = offset, + .size = int128_make64(size), + }; + + g_assert(ram_discard_manager_find_source(rdm, source)); + + /* + * Only notify about ranges that were aggregately populated before this + * source's discard. Since the source has already updated its state, + * we use replay_by_populated_state with this source skipped - it will + * replay only the ranges where all OTHER sources are populated. + */ + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + replay_by_populated_state(rdm, §ion, source, true, + rdl_discard_cb, rdl); + } +} + +void ram_discard_manager_notify_discard_all(RamDiscardManager *rdm, + RamDiscardSource *source) +{ + RamDiscardListener *rdl; + + g_assert(ram_discard_manager_find_source(rdm, source)); + + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + rdl->notify_discard(rdl, rdl->section); + } +} + +void ram_discard_manager_register_listener(RamDiscardManager *rdm, + RamDiscardListener *rdl, + MemoryRegionSection *section) +{ + int ret; + + g_assert(section->mr == rdm->mr); + + object_ref(rdm); + rdl->section = memory_region_section_new_copy(section); + QLIST_INSERT_HEAD(&rdm->rdl_list, rdl, next); + + ret = ram_discard_manager_replay_populated(rdm, rdl->section, + rdl_populate_cb, rdl); + if (ret) { + error_report("%s: Replaying populated ranges failed: %s", __func__, + strerror(-ret)); + } +} + +void ram_discard_manager_unregister_listener(RamDiscardManager *rdm, + RamDiscardListener *rdl) +{ + g_assert(rdl->section); + g_assert(rdl->section->mr == rdm->mr); + + rdl->notify_discard(rdl, rdl->section); + memory_region_section_free_copy(rdl->section); + rdl->section = NULL; + QLIST_REMOVE(rdl, next); + object_unref(rdm); +} + +int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *rdm) +{ + RamDiscardListener *rdl; + int ret = 0; + + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + ret = ram_discard_manager_replay_populated(rdm, rdl->section, + rdl_populate_cb, rdl); + if (ret) { + break; + } + } + return ret; +} + +static const TypeInfo ram_discard_manager_info = { + .parent = TYPE_OBJECT, + .name = TYPE_RAM_DISCARD_MANAGER, + .instance_size = sizeof(RamDiscardManager), + .instance_init = ram_discard_manager_initfn, + .instance_finalize = ram_discard_manager_finalize, +}; + +static const TypeInfo ram_discard_source_info = { + .parent = TYPE_INTERFACE, + .name = TYPE_RAM_DISCARD_SOURCE, + .class_size = sizeof(RamDiscardSourceClass), +}; + +static void ram_discard_manager_register_types(void) +{ + type_register_static(&ram_discard_manager_info); + type_register_static(&ram_discard_source_info); +} + +type_init(ram_discard_manager_register_types) diff --git a/system/runstate.c b/system/runstate.c index 0e1cb3b4e6..08acf801b0 100644 --- a/system/runstate.c +++ b/system/runstate.c @@ -706,7 +706,7 @@ void qemu_system_guest_panicked(GuestPanicInformation *info) " error code: 0x%" PRIx32 " error message:\"%s\"\n", info->u.tdx.error_code, message); g_free(message); - if (info->u.tdx.gpa != -1ull) { + if (info->u.tdx.has_gpa) { qemu_log_mask(LOG_GUEST_ERROR, "Additional error information " "can be found at gpa page: 0x%" PRIx64 "\n", info->u.tdx.gpa); @@ -1045,9 +1045,7 @@ void qemu_cleanup(int status) audio_cleanup(); monitor_cleanup(); qemu_chr_cleanup(); + qemu_display_cleanup(); user_creatable_cleanup(); -#ifdef CONFIG_VNC - vnc_cleanup(); -#endif /* TODO: unref root container, check all devices are ok */ } diff --git a/system/trace-events b/system/trace-events index e6e1b61279..51b4a4679a 100644 --- a/system/trace-events +++ b/system/trace-events @@ -32,7 +32,7 @@ global_dirty_changed(unsigned int bitmask) "bitmask 0x%"PRIx32 address_space_map(void *as, uint64_t addr, uint64_t len, bool is_write, uint32_t attrs) "as:%p addr 0x%"PRIx64":%"PRIx64" write:%d attrs:0x%x" find_ram_offset(uint64_t size, uint64_t offset) "size: 0x%" PRIx64 " @ 0x%" PRIx64 find_ram_offset_loop(uint64_t size, uint64_t candidate, uint64_t offset, uint64_t next, uint64_t mingap) "trying size: 0x%" PRIx64 " @ 0x%" PRIx64 ", offset: 0x%" PRIx64" next: 0x%" PRIx64 " mingap: 0x%" PRIx64 -ram_block_discard_range(const char *rbname, void *hva, size_t length, bool need_madvise, bool need_fallocate, int ret) "%s@%p + 0x%zx: madvise: %d fallocate: %d ret: %d" +ram_block_discard_shared_range(const char *rbname, void *hva, size_t length, bool need_madvise, bool need_fallocate, int ret) "%s@%p + 0x%zx: madvise: %d fallocate: %d ret: %d" qemu_ram_alloc_shared(const char *name, size_t size, size_t max_size, int fd, void *host) "%s size %zu max_size %zu fd %d host %p" subpage_register(void *subpage, uint32_t start, uint32_t end, int idx, int eidx, uint16_t section) "subpage %p start 0x%08x end 0x%08x idx 0x%08x eidx 0x%08x section %u" diff --git a/system/vl.c b/system/vl.c index 1c0da7df29..1d14e2e207 100644 --- a/system/vl.c +++ b/system/vl.c @@ -3260,7 +3260,7 @@ void qemu_init(int argc, char **argv) QemuOpts *fsdev; QemuOpts *device; const char *writeout, *sock_fd, *socket, *path, *security_model, - *multidevs; + *multidevs, *max_xattr_str; olist = qemu_find_opts("virtfs"); if (!olist) { @@ -3324,6 +3324,11 @@ void qemu_init(int argc, char **argv) if (multidevs) { qemu_opt_set(fsdev, "multidevs", multidevs, &error_abort); } + max_xattr_str = qemu_opt_get(opts, "max_xattr"); + if (max_xattr_str) { + qemu_opt_set(fsdev, "max_xattr", max_xattr_str, + &error_abort); + } device = qemu_opts_create(qemu_find_opts("device"), NULL, 0, &error_abort); qemu_opt_set(device, "driver", "virtio-9p-pci", &error_abort); diff --git a/target/Kconfig b/target/Kconfig index 3c73e3bae0..0288a3f416 100644 --- a/target/Kconfig +++ b/target/Kconfig @@ -16,6 +16,7 @@ source sh4/Kconfig source sparc/Kconfig source tricore/Kconfig source xtensa/Kconfig +source hexagon/Kconfig config TARGET_BIG_ENDIAN bool diff --git a/target/arm/cpu-features.h b/target/arm/cpu-features.h index a80b251589..98102d75bf 100644 --- a/target/arm/cpu-features.h +++ b/target/arm/cpu-features.h @@ -1159,6 +1159,11 @@ static inline bool isar_feature_aa64_rme_gpc2(const ARMISARegisters *id) return FIELD_EX64_IDREG(id, ID_AA64PFR0, RME) >= 2; } +static inline bool isar_feature_aa64_rme_gpc3(const ARMISARegisters *id) +{ + return FIELD_EX64_IDREG(id, ID_AA64PFR0, RME) >= 3; +} + static inline bool isar_feature_aa64_dit(const ARMISARegisters *id) { return FIELD_EX64_IDREG(id, ID_AA64PFR0, DIT) != 0; @@ -1524,7 +1529,7 @@ static inline bool isar_feature_aa64_sve2p2(const ARMISARegisters *id) return FIELD_EX64_IDREG(id, ID_AA64ZFR0, SVEVER) >= 3; } -static inline bool isar_feature_aa64_sve2_aes(const ARMISARegisters *id) +static inline bool isar_feature_aa64_sve_aes(const ARMISARegisters *id) { return FIELD_EX64_IDREG(id, ID_AA64ZFR0, AES) != 0; } @@ -1575,6 +1580,11 @@ static inline bool isar_feature_aa64_sve_b16b16(const ARMISARegisters *id) return FIELD_EX64_IDREG(id, ID_AA64ZFR0, B16B16); } +static inline bool isar_feature_aa64_ssve_aes(const ARMISARegisters *id) +{ + return FIELD_EX64_IDREG(id, ID_AA64SMFR0, AES); +} + static inline bool isar_feature_aa64_ssve_f8fma(const ARMISARegisters *id) { return FIELD_EX64_IDREG(id, ID_AA64SMFR0, SF8FMA); @@ -1595,6 +1605,16 @@ static inline bool isar_feature_aa64_sme_b16b16(const ARMISARegisters *id) return FIELD_EX64_IDREG(id, ID_AA64SMFR0, B16B16); } +static inline bool isar_feature_aa64_sme_f8f32(const ARMISARegisters *id) +{ + return FIELD_EX64_IDREG(id, ID_AA64SMFR0, F8F32); +} + +static inline bool isar_feature_aa64_sme_f8f16(const ARMISARegisters *id) +{ + return FIELD_EX64_IDREG(id, ID_AA64SMFR0, F8F16); +} + static inline bool isar_feature_aa64_sme_f16f16(const ARMISARegisters *id) { return FIELD_EX64_IDREG(id, ID_AA64SMFR0, F16F16); @@ -1766,6 +1786,12 @@ isar_feature_aa64_sme2_or_sve2_lut(const ARMISARegisters *id) return isar_feature_aa64_sme2_or_sve2(id) && isar_feature_aa64_lut(id); } +static inline bool +isar_feature_aa64_sme_f16f16_or_f8f16(const ARMISARegisters *id) +{ + return isar_feature_aa64_sme_f16f16(id) || isar_feature_aa64_sme_f8f16(id); +} + /* * Feature tests for "does this exist in either 32-bit or 64-bit?" */ diff --git a/target/arm/cpu.c b/target/arm/cpu.c index 86aae36ae5..128c78f6cf 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -421,6 +421,10 @@ static void arm_cpu_reset_hold(Object *obj, ResetType type) env->cp15.mdscr_el1 |= 1 << 12; /* Enable FEAT_MOPS */ env->cp15.sctlr_el[1] |= SCTLR_MSCEN; + /* Enable FEAT_FPMR */ + if (cpu_isar_feature(aa64_fpmr, cpu)) { + env->cp15.sctlr_el[1] |= SCTLR_EnFPM; + } /* For Linux, GCSPR_EL0 is always readable. */ if (cpu_isar_feature(aa64_gcs, cpu)) { env->cp15.gcscr_el[0] = GCSCRE0_NTR; diff --git a/target/arm/cpu.h b/target/arm/cpu.h index 31a5567c95..84d33e87dc 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -559,6 +559,7 @@ typedef struct CPUArchState { /* RME registers */ uint64_t gpccr_el3; uint64_t gptbr_el3; + uint64_t gpcbw_el3; uint64_t mfar_el3; /* NV2 register */ @@ -1244,6 +1245,8 @@ void arm_v7m_cpu_do_interrupt(CPUState *cpu); typedef struct ARMGranuleProtectionConfig { /* GPCCR_EL3 */ uint64_t gpccr; + /* GPCBW_EL3 */ + uint64_t gpcbw; /* GPTBR_EL3 */ uint64_t gptbr; /* ID_AA64MMFR0_EL1.PARange */ @@ -2116,6 +2119,10 @@ FIELD(GPCCR, NA6, 27, 1) FIELD(GPCCR, NA7, 28, 1) FIELD(GPCCR, GPCBW, 29, 1) +FIELD(GPCBW, BWSIZE, 37, 2) +FIELD(GPCBW, BWSTRIDE, 32, 5) +FIELD(GPCBW, BWADDR, 0, 25) + FIELD(MFAR, FPA, 12, 40) FIELD(MFAR, NSE, 62, 1) FIELD(MFAR, NS, 63, 1) diff --git a/target/arm/helper.c b/target/arm/helper.c index a234aa031c..93e3d8b575 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -37,6 +37,9 @@ #include "qemu/plugin.h" static void switch_mode(CPUARMState *env, int mode); +#ifndef CONFIG_USER_ONLY +static void gt_recalc_timer(ARMCPU *cpu, int timeridx); +#endif int compare_u64(const void *a, const void *b) { @@ -821,6 +824,12 @@ static void scr_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) changed = env->cp15.scr_el3 ^ value; env->cp15.scr_el3 = value; +#ifndef CONFIG_USER_ONLY + if (changed & SCR_ECVEN) { + gt_recalc_timer(cpu, GTIMER_PHYS); + } +#endif + /* * If SCR_EL3.{NS,NSE} changes, i.e. change of security state, * we must invalidate all TLBs below EL3. @@ -1404,10 +1413,10 @@ void gt_rme_post_el_change(ARMCPU *cpu, void *ignored) static uint64_t gt_phys_raw_cnt_offset(CPUARMState *env) { - if ((env->cp15.scr_el3 & SCR_ECVEN) && - FIELD_EX64(env->cp15.cnthctl_el2, CNTHCTL, ECV) && - arm_is_el2_enabled(env) && - (arm_hcr_el2_eff(env) & (HCR_E2H | HCR_TGE)) != (HCR_E2H | HCR_TGE)) { + if ((!arm_feature(env, ARM_FEATURE_EL3) || (env->cp15.scr_el3 & SCR_ECVEN)) + && FIELD_EX64(env->cp15.cnthctl_el2, CNTHCTL, ECV) + && arm_is_el2_enabled(env) + && (arm_hcr_el2_eff(env) & (HCR_E2H | HCR_TGE)) != (HCR_E2H | HCR_TGE)) { return env->cp15.cntpoff_el2; } return 0; @@ -1802,9 +1811,13 @@ static void gt_cnthctl_write(CPUARMState *env, const ARMCPRegInfo *ri, if ((oldval ^ value) & R_CNTHCTL_CNTVMASK_MASK) { gt_update_irq(cpu, GTIMER_VIRT); - } else if ((oldval ^ value) & R_CNTHCTL_CNTPMASK_MASK) { + } + if ((oldval ^ value) & R_CNTHCTL_CNTPMASK_MASK) { gt_update_irq(cpu, GTIMER_PHYS); } + if ((oldval ^ value) & R_CNTHCTL_ECV_MASK) { + gt_recalc_timer(cpu, GTIMER_PHYS); + } } static void gt_cntvoff_write(CPUARMState *env, const ARMCPRegInfo *ri, @@ -3703,6 +3716,7 @@ static const ARMCPRegInfo v8_aa32_el1_reginfo[] = { static void do_hcr_write(CPUARMState *env, uint64_t value, uint64_t valid_mask) { ARMCPU *cpu = env_archcpu(env); + bool hcr_change_timer; if (arm_feature(env, ARM_FEATURE_V8)) { valid_mask |= MAKE_64BIT_MASK(0, 34); /* ARMv8.0 */ @@ -3794,6 +3808,10 @@ static void do_hcr_write(CPUARMState *env, uint64_t value, uint64_t valid_mask) (HCR_VM | HCR_PTW | HCR_DC | HCR_DCT | HCR_FWB | HCR_NV | HCR_NV1)) { tlb_flush(CPU(cpu)); } + hcr_change_timer = (env->cp15.hcr_el2 ^ value) & + (HCR_E2H | HCR_TGE); + + /* update */ env->cp15.hcr_el2 = value; /* @@ -3815,6 +3833,11 @@ static void do_hcr_write(CPUARMState *env, uint64_t value, uint64_t valid_mask) arm_cpu_update_vinmi(cpu); arm_cpu_update_vfnmi(cpu); } + if (hcr_change_timer) { +#ifndef CONFIG_USER_ONLY + gt_recalc_timer(cpu, GTIMER_PHYS); +#endif + } } static void hcr_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) @@ -5001,6 +5024,10 @@ static void gpccr_write(CPUARMState *env, const ARMCPRegInfo *ri, R_GPCCR_SPAD_MASK | R_GPCCR_NSPAD_MASK | R_GPCCR_RLPAD_MASK; } + if (cpu_isar_feature(aa64_rme_gpc3, env_archcpu(env))) { + rw_mask |= R_GPCCR_GPCBW_MASK; + } + env->cp15.gpccr_el3 = (value & rw_mask) | (env->cp15.gpccr_el3 & ~rw_mask); } @@ -5010,11 +5037,26 @@ static void gpccr_reset(CPUARMState *env, const ARMCPRegInfo *ri) env_archcpu(env)->reset_l0gptsz); } +static void gpcbw_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + uint64_t rw_mask = R_GPCBW_BWADDR_MASK | R_GPCBW_BWSTRIDE_MASK | + R_GPCBW_BWSIZE_MASK; + ARMCPU *cpu = env_archcpu(env); + + tlb_flush(CPU(cpu)); + env->cp15.gpcbw_el3 = (value & rw_mask); +} + static const ARMCPRegInfo rme_reginfo[] = { { .name = "GPCCR_EL3", .state = ARM_CP_STATE_AA64, .opc0 = 3, .opc1 = 6, .crn = 2, .crm = 1, .opc2 = 6, .access = PL3_RW, .writefn = gpccr_write, .resetfn = gpccr_reset, .fieldoffset = offsetof(CPUARMState, cp15.gpccr_el3) }, + { .name = "GPCBW_EL3", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 6, .crn = 2, .crm = 1, .opc2 = 5, + .access = PL3_RW, .writefn = gpcbw_write, + .fieldoffset = offsetof(CPUARMState, cp15.gpcbw_el3) }, { .name = "GPTBR_EL3", .state = ARM_CP_STATE_AA64, .opc0 = 3, .opc1 = 6, .crn = 2, .crm = 1, .opc2 = 4, .access = PL3_RW, .fieldoffset = offsetof(CPUARMState, cp15.gptbr_el3) }, diff --git a/target/arm/ptw.c b/target/arm/ptw.c index 1470de3010..99295954a1 100644 --- a/target/arm/ptw.c +++ b/target/arm/ptw.c @@ -343,11 +343,21 @@ bool arm_granule_protection_check(ARMGranuleProtectionConfig config, .space = ARMSS_Root, }; const uint64_t gpccr = config.gpccr; + const uint64_t gpcbw = config.gpcbw; unsigned pps, pgs, l0gptsz, level = 0; uint64_t tableaddr, pps_mask, align, entry, index; MemTxResult result; int gpi; + const uint64_t BW_ADDR_SHIFT = 30; + const uint64_t BW_SIZE_SHIFT = 30; + const uint64_t BW_STRIDE_SHIFT = 40; + + uint64_t bw_size_field = FIELD_EX64(gpcbw, GPCBW, BWSIZE); + uint64_t bw_stride_field = FIELD_EX64(gpcbw, GPCBW, BWSTRIDE); + uint64_t bw_addr = FIELD_EX64(gpcbw, GPCBW, BWADDR) << BW_ADDR_SHIFT; + uint64_t bw_mask = 0; + /* * We assume Granule Protection Check is enabled when * calling this function (GPCCR.GPC == 1). @@ -399,6 +409,58 @@ bool arm_granule_protection_check(ARMGranuleProtectionConfig config, goto fault_walk; } + /* At this point, GPCCR_EL3 is valid */ + + /* + * GPC Priority 1 (R_GMGRR): + * If GPCCR_EL3.GPCBW is 1 and the configuration GPCBW + * is invalid, the access fails as GPT walk fault at level 0. + */ + if (FIELD_EX64(gpccr, GPCCR, GPCBW)) { + uint64_t bw_size = 0; + uint64_t bw_stride = 0; + + /* BWSIZE, BWSTRIDE have a limited number of acceptable values. */ + switch (bw_size_field) { + case 0b000: + case 0b001: + case 0b010: + case 0b100: + case 0b110: + bw_size = 1ULL << (bw_size_field + BW_SIZE_SHIFT); + break; + default: /* Reserved value */ + goto fault_walk; + } + switch (bw_stride_field) { + case 0b00000: + case 0b00010: + case 0b00100: + case 0b00110: + case 0b00111: + case 0b01000: + case 0b01001: + case 0b01010: + case 0b10000: + bw_stride = 1ULL << (bw_stride_field + BW_STRIDE_SHIFT); + break; + default: /* Reserved value */ + goto fault_walk; + } + /* + * GPCBW is invalid if the base address is: + * not aligned to the size programmed in BWSIZE, or + * greater than or equal to the stride value configured by BWSTRIDE. + * We can make bw_mask which marks exactly which bits in bw_addr may + * be set (gpcbwu:gpcbwl). + */ + bw_mask = bw_stride - bw_size; + + if (bw_addr & ~bw_mask) { + goto fault_walk; + } + } + /* Note this field is read-only and fixed at reset. */ l0gptsz = 30 + FIELD_EX64(gpccr, GPCCR, L0GPTSZ); @@ -433,6 +495,20 @@ bool arm_granule_protection_check(ARMGranuleProtectionConfig config, goto fault_fail; } + /* + * Bypass window check. + * I_JJLRM: Granule Protection Table (GPT) lookups can be skipped + * in portions of the memory map by using GPC bypass windows. + * I_XNHTX: The GPC bypass window check (...) is performed + * immediately after priority 3. + * bw_mask from earlier makes this check for us. + */ + if (FIELD_EX64(gpccr, GPCCR, GPCBW)) { + if ((paddress & bw_mask) == bw_addr) { + return true; + } + } + /* GPC Priority 4: the base address of GPTBR_EL3 exceeds PPS. */ tableaddr = config.gptbr << 12; if (tableaddr & ~pps_mask) { @@ -3879,6 +3955,7 @@ static bool get_phys_addr_gpc(CPUARMState *env, S1Translate *ptw, }; struct ARMGranuleProtectionConfig config = { .gpccr = env->cp15.gpccr_el3, + .gpcbw = env->cp15.gpcbw_el3, .gptbr = env->cp15.gptbr_el3, .parange = FIELD_EX64_IDREG(&cpu->isar, ID_AA64MMFR0, PARANGE), .support_sel2 = cpu_isar_feature(aa64_sel2, cpu), diff --git a/target/arm/tcg/cpu64.c b/target/arm/tcg/cpu64.c index f7a920a202..ae3952a1ed 100644 --- a/target/arm/tcg/cpu64.c +++ b/target/arm/tcg/cpu64.c @@ -159,8 +159,8 @@ static void cpu_arm_set_rme(Object *obj, bool value, Error **errp) { ARMCPU *cpu = ARM_CPU(obj); - /* Enable FEAT_RME_GPC2 */ - FIELD_DP64_IDREG(&cpu->isar, ID_AA64PFR0, RME, value ? 2 : 0); + /* Enable FEAT_RME_GPC3 */ + FIELD_DP64_IDREG(&cpu->isar, ID_AA64PFR0, RME, value ? 3 : 0); } static void cpu_max_set_l0gptsz(Object *obj, Visitor *v, const char *name, @@ -1383,6 +1383,7 @@ void aarch64_max_tcg_initfn(Object *obj) SET_IDREG(isar, ID_AA64DFR0, t); t = GET_IDREG(isar, ID_AA64SMFR0); + t = FIELD_DP64(t, ID_AA64SMFR0, AES, 1); /* FEAT_SSVE_AES */ t = FIELD_DP64(t, ID_AA64SMFR0, SF8DP2, 1); /* FEAT_SSVE_FP8DOT2 */ t = FIELD_DP64(t, ID_AA64SMFR0, SF8DP4, 1); /* FEAT_SSVE_FP8DOT4 */ t = FIELD_DP64(t, ID_AA64SMFR0, SF8FMA, 1); /* FEAT_SSVE_FP8FMA */ @@ -1391,6 +1392,8 @@ void aarch64_max_tcg_initfn(Object *obj) t = FIELD_DP64(t, ID_AA64SMFR0, B16F32, 1); /* FEAT_SME */ t = FIELD_DP64(t, ID_AA64SMFR0, F16F32, 1); /* FEAT_SME */ t = FIELD_DP64(t, ID_AA64SMFR0, I8I32, 0xf); /* FEAT_SME */ + t = FIELD_DP64(t, ID_AA64SMFR0, F8F32, 1); /* FEAT_SME_F8F32 */ + t = FIELD_DP64(t, ID_AA64SMFR0, F8F16, 1); /* FEAT_SME_F8F16 */ t = FIELD_DP64(t, ID_AA64SMFR0, F16F16, 1); /* FEAT_SME_F16F16 */ t = FIELD_DP64(t, ID_AA64SMFR0, B16B16, 1); /* FEAT_SME_B16B16 */ t = FIELD_DP64(t, ID_AA64SMFR0, I16I32, 5); /* FEAT_SME2 */ diff --git a/target/arm/tcg/fp8_helper.c b/target/arm/tcg/fp8_helper.c index bd0877071b..902e4b0ae9 100644 --- a/target/arm/tcg/fp8_helper.c +++ b/target/arm/tcg/fp8_helper.c @@ -857,3 +857,119 @@ void HELPER(gvec_fmmla_hb)(void *vd, void *vn, void *vm, clear_tail(vd, oprsz, simd_maxsz(desc)); } + +void HELPER(sme_fmopa_sb)(void *vza, void *vzn, void *vzm, void *vpn, + void *vpm, CPUARMState *env, uint32_t desc) +{ + FP8MulContext ctx = fp8_mul_start(env, -1); + intptr_t oprsz = simd_maxsz(desc); + uint16_t *pn = vpn, *pm = vpm; + + for (intptr_t row = 0; row < oprsz; ) { + uint16_t prow = pn[H2(row >> 4)]; + do { + void *vza_row = vza + tile_vslice_offset(row); + uint32_t n = *(uint32_t *)(vzn + H1_4(row)); + + n &= expand_pred_b(prow & 0xf); + + for (intptr_t col = 0; col < oprsz; ) { + uint16_t pcol = pm[H2(col >> 4)]; + do { + if (prow & pcol & 0xf) { + uint32_t *a = vza_row + H1_4(col); + uint32_t m = *(uint32_t *)(vzm + H1_4(col)); + + m &= expand_pred_b(pcol & 0xf); + *a = f8dotadd_s(n, m, 4, *a, &ctx); + } + col += 4; + pcol >>= 4; + } while (col & 15); + } + row += 4; + prow >>= 4; + } while (row & 15); + } +} + +void HELPER(sme_fmopa_hb)(void *vza, void *vzn, void *vzm, void *vpn, + void *vpm, CPUARMState *env, uint32_t desc) +{ + FP8MulContext ctx = fp8_mul_start(env, 0xf); + intptr_t oprsz = simd_maxsz(desc); + uint16_t *pn = vpn, *pm = vpm; + + for (intptr_t row = 0; row < oprsz; ) { + uint16_t prow = pn[H2(row >> 4)]; + do { + void *vza_row = vza + tile_vslice_offset(row); + uint16_t n = *(uint16_t *)(vzn + H1_2(row)); + + n &= expand_pred_b(prow & 3); + + for (intptr_t col = 0; col < oprsz; ) { + uint16_t pcol = pm[H2(col >> 4)]; + do { + if (prow & pcol & 0x3) { + uint16_t *a = vza_row + H1_2(col); + uint16_t m = *(uint16_t *)(vzm + H1_2(col)); + + m &= expand_pred_b(pcol & 3); + *a = f8dotadd_h(n, m, 2, *a, &ctx); + } + col += 2; + pcol >>= 2; + } while (col & 15); + } + row += 2; + prow >>= 2; + } while (row & 15); + } +} + +void HELPER(sme_fvdot_idx_sb)(void *vd, void *vn, void *vm, + CPUARMState *env, uint32_t desc) +{ + FP8MulContext ctx = fp8_mul_start(env, -1); + intptr_t oprsz = simd_maxsz(desc); + intptr_t elements = oprsz / sizeof(float32); + int idx_n = extract32(desc, SIMD_DATA_SHIFT, 2); + int idx_m = extract32(desc, SIMD_DATA_SHIFT + 2, 3); + float32 *d = vd; + uint8_t *n0 = vn; + uint8_t *n1 = vn + sizeof(ARMVectorReg); + uint16_t *m = vm; + intptr_t i = 0; + + do { + uint16_t mm = m[H2(2 * i + idx_m)]; + do { + uint16_t nn = n0[H1(4 * i + idx_n)] | (n1[H1(4 * i + idx_n)] << 8); + d[H4(i)] = f8dotadd_s(nn, mm, 2, d[H4(i)], &ctx); + } while (++i & 3); + } while (i < elements); +} + +void HELPER(sme_fvdot_idx_hb)(void *vd, void *vn, void *vm, + CPUARMState *env, uint32_t desc) +{ + FP8MulContext ctx = fp8_mul_start(env, 0xf); + intptr_t oprsz = simd_maxsz(desc); + intptr_t elements = oprsz / sizeof(float16); + int idx_n = extract32(desc, SIMD_DATA_SHIFT, 1); + int idx_m = extract32(desc, SIMD_DATA_SHIFT + 1, 3); + float16 *d = vd; + uint8_t *n0 = vn; + uint8_t *n1 = vn + sizeof(ARMVectorReg); + uint16_t *m = vm; + intptr_t i = 0; + + do { + uint16_t mm = m[H2(2 * i + idx_m)]; + do { + uint16_t nn = n0[H1(4 * i + idx_n)] | (n1[H1(4 * i + idx_n)] << 8); + d[H2(i)] = f8dotadd_h(nn, mm, 2, d[H2(i)], &ctx); + } while (++i & 7); + } while (i < elements); +} diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index dd2f696511..1cdd4103d0 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -4,6 +4,11 @@ * Copyright (c) 2025 Linaro Limited * * SPDX-License-Identifier: GPL-2.0-or-later + * + * The cpu interface is defined in IHI 111701 + * (ARM Generic Interrupt Controller Architecture Specification, + * GIC architecture version 5): + * https://developer.arm.com/documentation/111701/latest */ #include "qemu/osdep.h" @@ -139,7 +144,7 @@ static GICv5PendingIrq gic_hppi(CPUARMState *env, GICv5Domain domain) if (!(env->gicv5_cpuif.icc_cr0[domain] & R_ICC_CR0_EN_MASK)) { /* If cpuif is disabled there is no HPPI */ - return (GICv5PendingIrq) { .intid = 0, .prio = PRIO_IDLE }; + return GICV5_PENDING_IRQ_NONE; } irs_hppi = gicv5_get_hppi(gic, domain, env->gicv5_iaffid); @@ -163,7 +168,7 @@ static GICv5PendingIrq gic_hppi(CPUARMState *env, GICv5Domain domain) if (best.prio == PRIO_IDLE || best.prio > env->gicv5_cpuif.icc_pcr[domain] || best.prio >= gic_running_prio(env, domain)) { - return (GICv5PendingIrq) { .intid = 0, .prio = PRIO_IDLE }; + return GICV5_PENDING_IRQ_NONE; } return best; } @@ -253,8 +258,7 @@ static void gic_recalc_ppi_hppi(CPUARMState *env) * enabled, pending and not active. */ for (int i = 0; i < ARRAY_SIZE(env->gicv5_cpuif.ppi_hppi); i++) { - env->gicv5_cpuif.ppi_hppi[i].intid = 0; - env->gicv5_cpuif.ppi_hppi[i].prio = PRIO_IDLE; + env->gicv5_cpuif.ppi_hppi[i] = GICV5_PENDING_IRQ_NONE; }; for (int i = 0; i < ARRAY_SIZE(env->gicv5_cpuif.ppi_active); i++) { @@ -506,15 +510,6 @@ static void gic_icc_apr_el1_reset(CPUARMState *env, const ARMCPRegInfo *ri) } } -static uint64_t gic_icc_hapr_el1_read(CPUARMState *env, const ARMCPRegInfo *ri) -{ - /* - * ICC_HAPR_EL1 reports the current running priority, which can be - * calculated from the APR register. - */ - return gic_running_prio(env, gicv5_current_phys_domain(env)); -} - /* ICC_CR0_EL1 is also banked */ static uint64_t gic_icc_cr0_el1_read(CPUARMState *env, const ARMCPRegInfo *ri) { @@ -560,6 +555,7 @@ static void gic_icc_pcr_el1_write(CPUARMState *env, const ARMCPRegInfo *ri, value &= R_ICC_PCR_PRIORITY_MASK; env->gicv5_cpuif.icc_pcr[domain] = value; + gicv5_update_irq_fiq(env); } static void gic_icc_pcr_el1_reset(CPUARMState *env, const ARMCPRegInfo *ri) @@ -922,11 +918,6 @@ static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { .writefn = gic_icc_pcr_el1_write, .resetfn = gic_icc_pcr_el1_reset, }, - { .name = "ICC_HAPR_EL1", .state = ARM_CP_STATE_AA64, - .opc0 = 3, .opc1 = 1, .crn = 12, .crm = 0, .opc2 = 3, - .access = PL1_R, .type = ARM_CP_IO | ARM_CP_NO_RAW, - .readfn = gic_icc_hapr_el1_read, .raw_writefn = arm_cp_write_ignore, - }, }; void define_gicv5_cpuif_regs(ARMCPU *cpu) @@ -944,7 +935,7 @@ void define_gicv5_cpuif_regs(ARMCPU *cpu) .name = name, .state = ARM_CP_STATE_AA64, .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 14 + (i >> 3), .opc2 = i & 7, - .access = PL1_RW, .type = ARM_CP_IO, + .access = PL1_RW, .type = ARM_CP_IO | ARM_CP_NO_RAW, .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_priority[i]), .writefn = gic_ppi_priority_write, .raw_writefn = raw_write, }; diff --git a/target/arm/tcg/helper-fp8-defs.h b/target/arm/tcg/helper-fp8-defs.h index e942308af4..126dcadf77 100644 --- a/target/arm/tcg/helper-fp8-defs.h +++ b/target/arm/tcg/helper-fp8-defs.h @@ -38,3 +38,9 @@ DEF_HELPER_FLAGS_5(gvec_fdot_idx_hb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, env, DEF_HELPER_FLAGS_5(gvec_fmmla_sb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, env, i32) DEF_HELPER_FLAGS_5(gvec_fmmla_hb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, env, i32) + +DEF_HELPER_FLAGS_7(sme_fmopa_sb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, ptr, env, i32) +DEF_HELPER_FLAGS_7(sme_fmopa_hb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, ptr, env, i32) + +DEF_HELPER_FLAGS_5(sme_fvdot_idx_sb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, env, i32) +DEF_HELPER_FLAGS_5(sme_fvdot_idx_hb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, env, i32) diff --git a/target/arm/tcg/helper-sve-defs.h b/target/arm/tcg/helper-sve-defs.h index de2254bb19..19bd936f6e 100644 --- a/target/arm/tcg/helper-sve-defs.h +++ b/target/arm/tcg/helper-sve-defs.h @@ -733,6 +733,8 @@ DEF_HELPER_FLAGS_4(sve_trn_s, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32) DEF_HELPER_FLAGS_4(sve_trn_d, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32) DEF_HELPER_FLAGS_4(sve2_trn_q, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32) +DEF_HELPER_FLAGS_4(sve_compact_b, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32) +DEF_HELPER_FLAGS_4(sve_compact_h, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32) DEF_HELPER_FLAGS_4(sve_compact_s, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32) DEF_HELPER_FLAGS_4(sve_compact_d, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32) @@ -959,6 +961,9 @@ DEF_HELPER_FLAGS_4(sve_brkns, TCG_CALL_NO_RWG, i32, ptr, ptr, ptr, i32) DEF_HELPER_FLAGS_3(sve_cntp, TCG_CALL_NO_RWG, i64, ptr, ptr, i32) DEF_HELPER_FLAGS_2(sve2p1_cntp_c, TCG_CALL_NO_RWG_SE, i64, i32, i32) +DEF_HELPER_FLAGS_3(sve_firstp, TCG_CALL_NO_RWG_SE, i64, ptr, ptr, i32) +DEF_HELPER_FLAGS_3(sve_lastp, TCG_CALL_NO_RWG_SE, i64, ptr, ptr, i32) + DEF_HELPER_FLAGS_3(sve_whilel, TCG_CALL_NO_RWG, i32, ptr, i32, i32) DEF_HELPER_FLAGS_3(sve_whileg, TCG_CALL_NO_RWG, i32, ptr, i32, i32) @@ -3203,3 +3208,8 @@ DEF_HELPER_FLAGS_6(sve2_famin_s, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, fpst, i32) DEF_HELPER_FLAGS_6(sve2_famin_d, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, fpst, i32) + +DEF_HELPER_FLAGS_4(sve_expand_b, TCG_CALL_NO_WG, void, ptr, ptr, ptr, i32) +DEF_HELPER_FLAGS_4(sve_expand_h, TCG_CALL_NO_WG, void, ptr, ptr, ptr, i32) +DEF_HELPER_FLAGS_4(sve_expand_s, TCG_CALL_NO_WG, void, ptr, ptr, ptr, i32) +DEF_HELPER_FLAGS_4(sve_expand_d, TCG_CALL_NO_WG, void, ptr, ptr, ptr, i32) diff --git a/target/arm/tcg/sme.decode b/target/arm/tcg/sme.decode index 495330aed7..3a65e1ad4b 100644 --- a/target/arm/tcg/sme.decode +++ b/target/arm/tcg/sme.decode @@ -198,6 +198,9 @@ BFMOPA 10000001 101 ..... ... ... ..... . 100 . @op_16 BFMOPA_w 10000001 100 ..... ... ... ..... . 00 .. @op_32 FMOPA_w_h 10000001 101 ..... ... ... ..... . 00 .. @op_32 +FMOPA_sb 10000000 101 zm:5 pm:3 pn:3 zn:5 0 00 zad:2 &op sub=0 +FMOPA_hb 10000000 101 zm:5 pm:3 pn:3 zn:5 0100 zad:1 &op sub=0 + SMOPA_s 1010000 0 10 0 ..... ... ... ..... . 00 .. @op_32 SUMOPA_s 1010000 0 10 1 ..... ... ... ..... . 00 .. @op_32 USMOPA_s 1010000 1 10 0 ..... ... ... ..... . 00 .. @op_32 @@ -322,13 +325,13 @@ SUB_azz_n1_d 11000001 0111 .... 0 .. 110 ..... 11 ... @azz_nx1_o3 n=4 @azz_nx1_o2x2 ........ ... . zm:4 . .. ... zn:5 ... .. \ &azz_n off=%off2_x2 rv=%mova_rv -FMLAL_n1 11000001 001 0 .... 0 .. 011 ..... 00 ... @azz_nx1_o3x2 n=1 -FMLAL_n1 11000001 001 0 .... 0 .. 010 ..... 000 .. @azz_nx1_o2x2 n=2 -FMLAL_n1 11000001 001 1 .... 0 .. 010 ..... 000 .. @azz_nx1_o2x2 n=4 +FMLAL_n1_sh 11000001 001 0 .... 0 .. 011 ..... 00 ... @azz_nx1_o3x2 n=1 +FMLAL_n1_sh 11000001 001 0 .... 0 .. 010 ..... 000 .. @azz_nx1_o2x2 n=2 +FMLAL_n1_sh 11000001 001 1 .... 0 .. 010 ..... 000 .. @azz_nx1_o2x2 n=4 -FMLSL_n1 11000001 001 0 .... 0 .. 011 ..... 01 ... @azz_nx1_o3x2 n=1 -FMLSL_n1 11000001 001 0 .... 0 .. 010 ..... 010 .. @azz_nx1_o2x2 n=2 -FMLSL_n1 11000001 001 1 .... 0 .. 010 ..... 010 .. @azz_nx1_o2x2 n=4 +FMLSL_n1_sh 11000001 001 0 .... 0 .. 011 ..... 01 ... @azz_nx1_o3x2 n=1 +FMLSL_n1_sh 11000001 001 0 .... 0 .. 010 ..... 010 .. @azz_nx1_o2x2 n=2 +FMLSL_n1_sh 11000001 001 1 .... 0 .. 010 ..... 010 .. @azz_nx1_o2x2 n=4 BFMLAL_n1 11000001 001 0 .... 0 .. 011 ..... 10 ... @azz_nx1_o3x2 n=1 BFMLAL_n1 11000001 001 0 .... 0 .. 010 ..... 100 .. @azz_nx1_o2x2 n=2 @@ -443,6 +446,20 @@ FMLS_n1_h 11000001 001 1 .... 0 .. 111 ..... 01 ... @azz_nx1_o3 n=4 FMLS_n1_s 11000001 001 1 .... 0 .. 110 ..... 01 ... @azz_nx1_o3 n=4 FMLS_n1_d 11000001 011 1 .... 0 .. 110 ..... 01 ... @azz_nx1_o3 n=4 +FMLALL_n1_b 11000001 001 1 .... 0 .. 001 ..... 000 .. @azz_nx1_o2x4 n=1 +FMLALL_n1_b 11000001 001 0 .... 0 .. 000 ..... 0001 . @azz_nx1_o1x4 n=2 +FMLALL_n1_b 11000001 001 1 .... 0 .. 000 ..... 0001 . @azz_nx1_o1x4 n=4 + +FDOT_n1_sb 11000001 001 0 .... 0 .. 100 ..... 11 ... @azz_nx1_o3 n=2 +FDOT_n1_sb 11000001 001 1 .... 0 .. 100 ..... 11 ... @azz_nx1_o3 n=4 + +FMLAL_n1_hb 11000001 001 1 .... 0 .. 011 ..... 00 ... @azz_nx1_o3x2 n=1 +FMLAL_n1_hb 11000001 001 0 .... 0 .. 010 ..... 001 .. @azz_nx1_o2x2 n=2 +FMLAL_n1_hb 11000001 001 1 .... 0 .. 010 ..... 001 .. @azz_nx1_o2x2 n=4 + +FDOT_n1_hb 11000001 001 0 .... 0 .. 100 ..... 01 ... @azz_nx1_o3 n=2 +FDOT_n1_hb 11000001 001 1 .... 0 .. 100 ..... 01 ... @azz_nx1_o3 n=4 + ### SME2 Multi-vector Multiple Array Vectors %zn_ax2 6:4 !function=times_2 @@ -468,11 +485,11 @@ SUB_azz_nn_d 11000001 111 ...01 0 .. 110 ...00 11 ... @azz_4x4_o3 @azz_4x4_o2x2 ........ ... ..... . .. ... ..... ... .. \ &azz_n n=4 rv=%mova_rv zn=%zn_ax4 zm=%zm_ax4 off=%off2_x2 -FMLAL_nn 11000001 101 ....0 0 .. 010 ....0 000 .. @azz_2x2_o2x2 -FMLAL_nn 11000001 101 ...01 0 .. 010 ...00 000 .. @azz_4x4_o2x2 +FMLAL_nn_sh 11000001 101 ....0 0 .. 010 ....0 000 .. @azz_2x2_o2x2 +FMLAL_nn_sh 11000001 101 ...01 0 .. 010 ...00 000 .. @azz_4x4_o2x2 -FMLSL_nn 11000001 101 ....0 0 .. 010 ....0 010 .. @azz_2x2_o2x2 -FMLSL_nn 11000001 101 ...01 0 .. 010 ...00 010 .. @azz_4x4_o2x2 +FMLSL_nn_sh 11000001 101 ....0 0 .. 010 ....0 010 .. @azz_2x2_o2x2 +FMLSL_nn_sh 11000001 101 ...01 0 .. 010 ...00 010 .. @azz_4x4_o2x2 BFMLAL_nn 11000001 101 ....0 0 .. 010 ....0 100 .. @azz_2x2_o2x2 BFMLAL_nn 11000001 101 ...01 0 .. 010 ...00 100 .. @azz_4x4_o2x2 @@ -563,6 +580,18 @@ FMLS_nn_h 11000001 101 ...01 0 .. 100 ...00 11 ... @azz_4x4_o3 FMLS_nn_s 11000001 101 ...01 0 .. 110 ...00 01 ... @azz_4x4_o3 FMLS_nn_d 11000001 111 ...01 0 .. 110 ...00 01 ... @azz_4x4_o3 +FMLALL_nn_b 11000001 101 ....0 0 .. 000 ....1 0000 . @azz_2x2_o1x4 +FMLALL_nn_b 11000001 101 ...01 0 .. 000 ...01 0000 . @azz_4x4_o1x4 + +FDOT_nn_sb 11000001 101 ....0 0 .. 100 ....1 10 ... @azz_2x2_o3 +FDOT_nn_sb 11000001 101 ...01 0 .. 100 ...01 10 ... @azz_4x4_o3 + +FMLAL_nn_hb 11000001 101 ....0 0 .. 010 ....1 000 .. @azz_2x2_o2x2 +FMLAL_nn_hb 11000001 101 ...01 0 .. 010 ...01 000 .. @azz_4x4_o2x2 + +FDOT_nn_hb 11000001 101 ....0 0 .. 100 ....1 00 ... @azz_2x2_o3 +FDOT_nn_hb 11000001 101 ...01 0 .. 100 ...01 00 ... @azz_4x4_o3 + &az_n n off rv zm @az_2x2_o3 ........ ... ..... . .. ... ..... .. off:3 \ &az_n n=2 rv=%mova_rv zm=%zn_ax2 @@ -602,13 +631,13 @@ BFSUB_nn 11000001 111 00101 0 .. 111 ...00 01 ... @az_4x4_o3 @azx_4x1_o2x2 ........ .... zm:4 . .. . .. ..... .. ... \ &azx_n n=4 rv=%mova_rv off=%off2_x2 zn=%zn_ax4 idx=%idx2_10_2 -FMLAL_nx 11000001 1000 .... . .. 1 .. ..... 00 ... @azx_1x1_o3x2 -FMLAL_nx 11000001 1001 .... 0 .. 1 .. ....0 00 ... @azx_2x1_o2x2 -FMLAL_nx 11000001 1001 .... 1 .. 1 .. ...00 00 ... @azx_4x1_o2x2 +FMLAL_nx_sh 11000001 1000 .... . .. 1 .. ..... 00 ... @azx_1x1_o3x2 +FMLAL_nx_sh 11000001 1001 .... 0 .. 1 .. ....0 00 ... @azx_2x1_o2x2 +FMLAL_nx_sh 11000001 1001 .... 1 .. 1 .. ...00 00 ... @azx_4x1_o2x2 -FMLSL_nx 11000001 1000 .... . .. 1 .. ..... 01 ... @azx_1x1_o3x2 -FMLSL_nx 11000001 1001 .... 0 .. 1 .. ....0 01 ... @azx_2x1_o2x2 -FMLSL_nx 11000001 1001 .... 1 .. 1 .. ...00 01 ... @azx_4x1_o2x2 +FMLSL_nx_sh 11000001 1000 .... . .. 1 .. ..... 01 ... @azx_1x1_o3x2 +FMLSL_nx_sh 11000001 1001 .... 0 .. 1 .. ....0 01 ... @azx_2x1_o2x2 +FMLSL_nx_sh 11000001 1001 .... 1 .. 1 .. ...00 01 ... @azx_4x1_o2x2 BFMLAL_nx 11000001 1000 .... . .. 1 .. ..... 10 ... @azx_1x1_o3x2 BFMLAL_nx 11000001 1001 .... 0 .. 1 .. ....0 10 ... @azx_2x1_o2x2 @@ -633,7 +662,7 @@ FDOT_nx 11000001 0101 .... 1 .. 1 .. ...00 01 ... @azx_4x1_i2_o3 BFDOT_nx 11000001 0101 .... 0 .. 1 .. ....0 11 ... @azx_2x1_i2_o3 BFDOT_nx 11000001 0101 .... 1 .. 1 .. ...00 11 ... @azx_4x1_i2_o3 -FVDOT 11000001 0101 .... 0 .. 0 .. ....0 01 ... @azx_2x1_i2_o3 +FVDOT_sh 11000001 0101 .... 0 .. 0 .. ....0 01 ... @azx_2x1_i2_o3 BFVDOT 11000001 0101 .... 0 .. 0 .. ....0 11 ... @azx_2x1_i2_o3 SDOT_nx_2h 11000001 0101 .... 0 .. 1 .. ....0 00 ... @azx_2x1_i2_o3 @@ -762,6 +791,38 @@ FMLS_nx_h 11000001 0001 .... 1 .. 1.. ...00 1 .... @azx_4x1_i3_o3 FMLS_nx_s 11000001 0101 .... 1 .. 0.. ...00 10 ... @azx_4x1_i2_o3 FMLS_nx_d 11000001 1101 .... 1 .. 00. ...00 10 ... @azx_4x1_i1_o3 +FMLALL_nx_b 11000001 0100 .... . .. ... ..... 000 .. @azx_1x1_i4_o2 +FMLALL_nx_b 11000001 1001 .... 0 .. 0.. ....1 00.. . @azx_2x1_i4_o1 +FMLALL_nx_b 11000001 0001 .... 1 .. 0.. ...10 00.. . @azx_4x1_i4_o1 + +FDOT_nx_b 11000001 0101 .... 0 .. 0.. ....1 11 ... @azx_2x1_i2_o3 +FDOT_nx_b 11000001 0101 .... 1 .. 0.. ...00 01 ... @azx_4x1_i2_o3 + +%idx4_15_10_3 15:1 10:2 3:1 +%idx4_10_2 10:2 2:2 +@azx_1x1_i4_o3x2 ........ .... zm:4 . .. . .. zn:5 .. ... \ + &azx_n n=1 rv=%mova_rv off=%off3_x2 idx=%idx4_15_10_3 +@azx_2x2_i4_o3x2 ........ .... zm:4 . .. . .. .... .. .. .. \ + &azx_n n=2 rv=%mova_rv zn=%zn_ax2 off=%off2_x2 idx=%idx4_10_2 +@azx_4x4_i4_o3x2 ........ .... zm:4 . .. . .. ... ... .. .. \ + &azx_n n=4 rv=%mova_rv zn=%zn_ax4 off=%off2_x2 idx=%idx4_10_2 + +FMLAL_nx_hb 11000001 1100 .... . .. 0.. ..... 0. ... @azx_1x1_i4_o3x2 +FMLAL_nx_hb 11000001 1001 .... 0 .. 1.. ....1 1.. .. @azx_2x2_i4_o3x2 +FMLAL_nx_hb 11000001 1001 .... 1 .. 1.. ...01 0.. .. @azx_4x4_i4_o3x2 + +FDOT_nx_hb 11000001 1101 .... 0 .. 0.. ....1 0. ... @azx_2x1_i3_o3 +FDOT_nx_hb 11000001 0001 .... 1 .. 1.. ...10 0. ... @azx_4x1_i3_o3 + +%idx2_10_3 10:1 3:1 +@azx_4x2_i2_o3 ........ .... zm:4 . .. ... .... ... off:3 \ + &azx_n n=4 rv=%mova_rv zn=%zn_ax2 idx=%idx2_10_3 + +FVDOTB_sb 11000001 1101 .... 0 .. 01. ....0 0. ... @azx_4x2_i2_o3 +FVDOTT_sb 11000001 1101 .... 0 .. 01. ....0 1. ... @azx_4x2_i2_o3 + +FVDOT_hb 11000001 1101 .... 0 .. 1.. ....1 0. ... @azx_2x1_i3_o3 + ### SME2 Add / Sub array accumulators ADD_aaz_s 11000001 101 000000 .. 111 ....0 10 ... @az_2x2_o3 diff --git a/target/arm/tcg/sme_helper.c b/target/arm/tcg/sme_helper.c index 8e8e6c00ca..685b6b46be 100644 --- a/target/arm/tcg/sme_helper.c +++ b/target/arm/tcg/sme_helper.c @@ -64,39 +64,6 @@ void helper_sme_zero(CPUARMState *env, uint32_t imm, uint32_t svl) } } - -/* - * When considering the ZA storage as an array of elements of - * type T, the index within that array of the Nth element of - * a vertical slice of a tile can be calculated like this, - * regardless of the size of type T. This is because the tiles - * are interleaved, so if type T is size N bytes then row 1 of - * the tile is N rows away from row 0. The division by N to - * convert a byte offset into an array index and the multiplication - * by N to convert from vslice-index-within-the-tile to - * the index within the ZA storage cancel out. - */ -#define tile_vslice_index(i) ((i) * sizeof(ARMVectorReg)) - -/* - * When doing byte arithmetic on the ZA storage, the element - * byteoff bytes away in a tile vertical slice is always this - * many bytes away in the ZA storage, regardless of the - * size of the tile element, assuming that byteoff is a multiple - * of the element size. Again this is because of the interleaving - * of the tiles. For instance if we have 1 byte per element then - * each row of the ZA storage has one byte of the vslice data, - * and (counting from 0) byte 8 goes in row 8 of the storage - * at offset (8 * row-size-in-bytes). - * If we have 8 bytes per element then each row of the ZA storage - * has 8 bytes of the data, but there are 8 interleaved tiles and - * so byte 8 of the data goes into row 1 of the tile, - * which is again row 8 of the storage, so the offset is still - * (8 * row-size-in-bytes). Similarly for other element sizes. - */ -#define tile_vslice_offset(byteoff) ((byteoff) * sizeof(ARMVectorReg)) - - /* * Move Zreg vector to ZArray column. */ diff --git a/target/arm/tcg/sve.decode b/target/arm/tcg/sve.decode index 2795c2ec7f..bdc2b7f0c0 100644 --- a/target/arm/tcg/sve.decode +++ b/target/arm/tcg/sve.decode @@ -693,7 +693,6 @@ TBXQ 00000101 .. 1 ..... 001 101 ..... ..... @rd_rn_rm ### SVE Permute - Predicated Group # SVE compress active elements -# Note esz >= 2 COMPACT 00000101 .. 100001 100 ... ..... ..... @rd_pg_rn # SVE conditionally broadcast element to vector @@ -742,6 +741,8 @@ SPLICE 00000101 .. 101 100 100 ... ..... ..... @rdn_pg_rm # SVE2 vector splice (predicated, constructive) SPLICE_sve2 00000101 .. 101 101 100 ... ..... ..... @rd_pg_rn +EXPAND 00000101 .. 110 001 100 ... ..... ..... @rd_pg_rn + ### SVE Select Vectors Group # SVE select vector elements (predicated) @@ -851,6 +852,9 @@ BRKN 00100101 0. 01100001 .... 0 .... 0 .... @pd_pg_pn_s CNTP 00100101 .. 100 000 10 .... 0 .... ..... @rd_pg4_pn CNTP_c 00100101 esz:2 100 000 10 000 vl:1 1 rn:4 rd:5 +FIRSTP 00100101 .. 100 001 10 .... 0 .... ..... @rd_pg4_pn +LASTP 00100101 .. 100 010 10 .... 0 .... ..... @rd_pg4_pn + # SVE inc/dec register by predicate count INCDECP_r 00100101 .. 10110 d:1 10001 00 .... ..... @incdec_pred u=1 diff --git a/target/arm/tcg/sve_helper.c b/target/arm/tcg/sve_helper.c index a0a189eb1e..ac96b18784 100644 --- a/target/arm/tcg/sve_helper.c +++ b/target/arm/tcg/sve_helper.c @@ -917,12 +917,45 @@ DO_ZPZ(sve_ah_fneg_h, uint16_t, H1_2, DO_AH_FNEG_H) DO_ZPZ(sve_ah_fneg_s, uint32_t, H1_4, DO_AH_FNEG_S) DO_ZPZ_D(sve_ah_fneg_d, uint64_t, DO_AH_FNEG_D) -#define DO_NOT(N) (~N) +static inline void +sve_not_zpz(uint64_t *d, uint64_t *n, uint8_t *pg, uint32_t desc, + uint64_t (*expand)(uint8_t)) +{ + intptr_t opr_sz = simd_oprsz(desc) / 8; + bool zeroing = simd_data(desc) & 1; + + if (zeroing) { + for (intptr_t i = 0; i < opr_sz; ++i) { + uint64_t p = expand(pg[H1(i)]); + d[i] = ~n[i] & p; + } + } else { + for (intptr_t i = 0; i < opr_sz; ++i) { + uint64_t p = expand(pg[H1(i)]); + d[i] = (~n[i] & p) | (d[i] & ~p); + } + } +} + +void HELPER(sve_not_zpz_b)(void *vd, void *vn, void *pg, uint32_t desc) +{ + sve_not_zpz(vd, vn, pg, desc, expand_pred_b); +} + +void HELPER(sve_not_zpz_h)(void *vd, void *vn, void *pg, uint32_t desc) +{ + sve_not_zpz(vd, vn, pg, desc, expand_pred_h); +} + +void HELPER(sve_not_zpz_s)(void *vd, void *vn, void *pg, uint32_t desc) +{ + sve_not_zpz(vd, vn, pg, desc, expand_pred_s); +} -DO_ZPZ(sve_not_zpz_b, uint8_t, H1, DO_NOT) -DO_ZPZ(sve_not_zpz_h, uint16_t, H1_2, DO_NOT) -DO_ZPZ(sve_not_zpz_s, uint32_t, H1_4, DO_NOT) -DO_ZPZ_D(sve_not_zpz_d, uint64_t, DO_NOT) +void HELPER(sve_not_zpz_d)(void *vd, void *vn, void *pg, uint32_t desc) +{ + sve_not_zpz(vd, vn, pg, desc, expand_pred_d); +} #define DO_SXTB(N) ((int8_t)N) #define DO_SXTH(N) ((int16_t)N) @@ -3637,40 +3670,59 @@ DO_TRN(sve2_trn_q, Int128, ) #undef DO_UZP #undef DO_TRN -void HELPER(sve_compact_s)(void *vd, void *vn, void *vg, uint32_t desc) -{ - intptr_t i, j, opr_sz = simd_oprsz(desc) / 4; - uint32_t *d = vd, *n = vn; - uint8_t *pg = vg; - - for (i = j = 0; i < opr_sz; i++) { - if (pg[H1(i / 2)] & (i & 1 ? 0x10 : 0x01)) { - d[H4(j)] = n[H4(i)]; - j++; - } - } - for (; j < opr_sz; j++) { - d[H4(j)] = 0; - } +#define DO_COMPACT(NAME, TYPE, H) \ +void HELPER(NAME)(void *vd, void *vn, void *vg, uint32_t desc) \ +{ \ + intptr_t j = 0, oprsz = simd_oprsz(desc); \ + for (intptr_t i = 0; i < oprsz; ) { \ + uint16_t pg = *(uint16_t *)(vg + H1_2(i >> 3)); \ + do { \ + if (pg & 1) { \ + *(TYPE *)(vd + H(j)) = *(TYPE *)(vn + H(i)); \ + j += sizeof(TYPE); \ + } \ + i += sizeof(TYPE); \ + pg >>= sizeof(TYPE); \ + } while (i & 15); \ + } \ + for (; j < oprsz; j += sizeof(TYPE)) { \ + *(TYPE *)(vd + H(j)) = 0; \ + } \ } -void HELPER(sve_compact_d)(void *vd, void *vn, void *vg, uint32_t desc) -{ - intptr_t i, j, opr_sz = simd_oprsz(desc) / 8; - uint64_t *d = vd, *n = vn; - uint8_t *pg = vg; +DO_COMPACT(sve_compact_b, uint8_t, H1) +DO_COMPACT(sve_compact_h, uint16_t, H1_2) +DO_COMPACT(sve_compact_s, uint32_t, H1_4) +DO_COMPACT(sve_compact_d, uint64_t, H1_8) - for (i = j = 0; i < opr_sz; i++) { - if (pg[H1(i)] & 1) { - d[j] = n[i]; - j++; - } - } - for (; j < opr_sz; j++) { - d[j] = 0; - } +#undef DO_COMPACT + +#define DO_EXPAND(NAME, TYPE, H) \ +void HELPER(NAME)(void *vd, void *vn, void *vg, uint32_t desc) \ +{ \ + intptr_t oprsz = simd_oprsz(desc); \ + ARMVectorReg tmp_n = *(ARMVectorReg *)vn; \ + for (intptr_t i = 0, j = 0; i < oprsz; ) { \ + uint16_t pg = *(uint16_t *)(vg + H1_2(i >> 3)); \ + do { \ + TYPE nn = 0; \ + if (pg & 1) { \ + nn = *(TYPE *)((void *)&tmp_n + H(j)); \ + j += sizeof(TYPE); \ + } \ + *(TYPE *)(vd + H(i)) = nn; \ + i += sizeof(TYPE); \ + pg >>= sizeof(TYPE); \ + } while (i & 15); \ + } \ } +DO_EXPAND(sve_expand_b, uint8_t, H1) +DO_EXPAND(sve_expand_h, uint16_t, H1_2) +DO_EXPAND(sve_expand_s, uint32_t, H1_4) +DO_EXPAND(sve_expand_d, uint64_t, H1_8) + +#undef DO_EXPAND /* Similar to the ARM LastActiveElement pseudocode function, except the * result is multiplied by the element size. This includes the not found * indication; e.g. not found for esz=3 is -8. @@ -4309,6 +4361,36 @@ uint64_t HELPER(sve2p1_cntp_c)(uint32_t png, uint32_t desc) return count >> p.lg2_stride; } +uint64_t HELPER(sve_firstp)(void *vn, void *vg, uint32_t pred_desc) +{ + intptr_t words = DIV_ROUND_UP(FIELD_EX32(pred_desc, PREDDESC, OPRSZ), 8); + intptr_t esz = FIELD_EX32(pred_desc, PREDDESC, ESZ); + uint64_t *n = vn, *g = vg, mask = pred_esz_masks[esz]; + + for (intptr_t i = 0; i < words; ++i) { + uint64_t t = n[i] & g[i] & mask; + if (t) { + return (i * 64 + ctz64(t)) >> esz; + } + } + return -1; +} + +uint64_t HELPER(sve_lastp)(void *vn, void *vg, uint32_t pred_desc) +{ + intptr_t words = DIV_ROUND_UP(FIELD_EX32(pred_desc, PREDDESC, OPRSZ), 8); + intptr_t esz = FIELD_EX32(pred_desc, PREDDESC, ESZ); + uint64_t *n = vn, *g = vg, mask = pred_esz_masks[esz]; + + for (intptr_t i = words - 1; i >= 0; --i) { + uint64_t t = n[i] & g[i] & mask; + if (t) { + return (i * 64 + (63 - clz64(t))) >> esz; + } + } + return -1; +} + /* C.f. Arm pseudocode EncodePredCount */ static uint64_t encode_pred_count(uint32_t elements, uint32_t count, uint32_t esz, bool invert) diff --git a/target/arm/tcg/translate-sme.c b/target/arm/tcg/translate-sme.c index 98d3d18791..ff5554eefb 100644 --- a/target/arm/tcg/translate-sme.c +++ b/target/arm/tcg/translate-sme.c @@ -616,6 +616,29 @@ TRANS_FEAT(BFMOPA_w, aa64_sme, do_outprod_env, a, MO_32, : !s->fpcr_ah ? gen_helper_sme_bfmops_w : gen_helper_sme_ah_bfmops_w) +static bool do_outprod_fp8(DisasContext *s, arg_op *a, MemOp esz, + gen_helper_gvec_5_ptr *fn) +{ + if (fpmr_access_check(s) && sme_smza_enabled_check(s)) { + int svl = streaming_vec_reg_size(s); + uint32_t desc = simd_desc(svl, svl, 0); + + TCGv_ptr za = get_tile(s, esz, a->zad); + TCGv_ptr zn = vec_full_reg_ptr(s, a->zn); + TCGv_ptr zm = vec_full_reg_ptr(s, a->zm); + TCGv_ptr pn = pred_full_reg_ptr(s, a->pn); + TCGv_ptr pm = pred_full_reg_ptr(s, a->pm); + + fn(za, zn, zm, pn, pm, tcg_env, tcg_constant_i32(desc)); + } + return true; +} + +TRANS_FEAT(FMOPA_sb, aa64_sme_f8f32, do_outprod_fp8, + a, MO_32, gen_helper_sme_fmopa_sb) +TRANS_FEAT(FMOPA_hb, aa64_sme_f8f16, do_outprod_fp8, + a, MO_16, gen_helper_sme_fmopa_hb) + TRANS_FEAT(SMOPA_s, aa64_sme, do_outprod, a, MO_32, gen_helper_sme_smopa_s) TRANS_FEAT(UMOPA_s, aa64_sme, do_outprod, a, MO_32, gen_helper_sme_umopa_s) TRANS_FEAT(SUMOPA_s, aa64_sme, do_outprod, a, MO_32, gen_helper_sme_sumopa_s) @@ -1040,6 +1063,47 @@ static bool do_azz_acc_fp(DisasContext *s, int nreg, int nsel, return true; } +static bool do_azz_acc_fp8(DisasContext *s, int nreg, int nsel, + int rv, int off, int zn, int zm, + int data, int shsel, bool multi, + gen_helper_gvec_3_ptr *fn) +{ + /* + * TODO: Could plausibly reuse do_azz_acc_fp, after the fpmr check, + * but the fp8 helpers were written without a separate addend operand. + */ + if (fpmr_access_check(s) && sme_smza_enabled_check(s)) { + int svl = streaming_vec_reg_size(s); + int vstride = svl / nreg; + TCGv_ptr t_za = get_zarray(s, rv, off, nreg, nsel); + TCGv_ptr t; + + t = tcg_temp_new_ptr(); + + for (int r = 0; r < nreg; ++r) { + TCGv_ptr t_zn = vec_full_reg_ptr(s, zn); + TCGv_ptr t_zm = vec_full_reg_ptr(s, zm); + + for (int i = 0; i < nsel; ++i) { + int o_za = (r * vstride + i) * sizeof(ARMVectorReg); + int desc = simd_desc(svl, svl, data | (i << shsel)); + + tcg_gen_addi_ptr(t, t_za, o_za); + fn(t, t_zn, t_zm, tcg_env, tcg_constant_i32(desc)); + } + + /* + * For multiple-and-single vectors, Zn may wrap. + * For multiple vectors, both Zn and Zm are aligned. + */ + zn = (zn + 1) % 32; + zm += multi; + } + } + return true; +} + + static bool do_fmlal(DisasContext *s, arg_azz_n *a, bool sub, bool multi) { return do_azz_acc_fp(s, a->n, 2, a->rv, a->off, a->zn, a->zm, @@ -1047,10 +1111,28 @@ static bool do_fmlal(DisasContext *s, arg_azz_n *a, bool sub, bool multi) multi, FPST_ENV, gen_helper_sve2_fmlal_zzzw_s); } -TRANS_FEAT(FMLAL_n1, aa64_sme2, do_fmlal, a, false, false) -TRANS_FEAT(FMLSL_n1, aa64_sme2, do_fmlal, a, true, false) -TRANS_FEAT(FMLAL_nn, aa64_sme2, do_fmlal, a, false, true) -TRANS_FEAT(FMLSL_nn, aa64_sme2, do_fmlal, a, true, true) +TRANS_FEAT(FMLAL_n1_sh, aa64_sme2, do_fmlal, a, false, false) +TRANS_FEAT(FMLSL_n1_sh, aa64_sme2, do_fmlal, a, true, false) +TRANS_FEAT(FMLAL_nn_sh, aa64_sme2, do_fmlal, a, false, true) +TRANS_FEAT(FMLSL_nn_sh, aa64_sme2, do_fmlal, a, true, true) + +static bool do_fmlall_fp8(DisasContext *s, arg_azz_n *a, bool multi) +{ + return do_azz_acc_fp8(s, a->n, 4, a->rv, a->off, a->zn, a->zm, + 0, 0, multi, gen_helper_gvec_fmla_sb); +} + +TRANS_FEAT(FMLALL_n1_b, aa64_sme_f8f32, do_fmlall_fp8, a, false) +TRANS_FEAT(FMLALL_nn_b, aa64_sme_f8f32, do_fmlall_fp8, a, true) + +static bool do_fmlal_fp8(DisasContext *s, arg_azz_n *a, bool multi) +{ + return do_azz_acc_fp8(s, a->n, 2, a->rv, a->off, a->zn, a->zm, + 0, 0, multi, gen_helper_gvec_fmla_hb); +} + +TRANS_FEAT(FMLAL_n1_hb, aa64_sme_f8f16, do_fmlal_fp8, a, false) +TRANS_FEAT(FMLAL_nn_hb, aa64_sme_f8f16, do_fmlal_fp8, a, true) static bool do_fmlal_nx(DisasContext *s, arg_azx_n *a, bool sub) { @@ -1059,8 +1141,12 @@ static bool do_fmlal_nx(DisasContext *s, arg_azx_n *a, bool sub) false, FPST_ENV, gen_helper_sve2_fmlal_zzxw_s); } -TRANS_FEAT(FMLAL_nx, aa64_sme2, do_fmlal_nx, a, false) -TRANS_FEAT(FMLSL_nx, aa64_sme2, do_fmlal_nx, a, true) +TRANS_FEAT(FMLAL_nx_sh, aa64_sme2, do_fmlal_nx, a, false) +TRANS_FEAT(FMLSL_nx_sh, aa64_sme2, do_fmlal_nx, a, true) + +TRANS_FEAT(FMLAL_nx_hb, aa64_sme_f8f16, do_azz_acc_fp8, + a->n, 2, a->rv, a->off, a->zn, a->zm, + a->idx << 2, 0, false, gen_helper_gvec_fmla_idx_hb) static bool do_bfmlal(DisasContext *s, arg_azz_n *a, bool sub, bool multi) { @@ -1088,6 +1174,18 @@ static bool do_bfmlal_nx(DisasContext *s, arg_azx_n *a, bool sub) TRANS_FEAT(BFMLAL_nx, aa64_sme2, do_bfmlal_nx, a, false) TRANS_FEAT(BFMLSL_nx, aa64_sme2, do_bfmlal_nx, a, true) +TRANS_FEAT(FMLALL_nx_b, aa64_sme_f8f32, do_azz_acc_fp8, + a->n, 4, a->rv, a->off, a->zn, a->zm, + a->idx << 2, 0, false, gen_helper_gvec_fmla_idx_sb) + +TRANS_FEAT(FDOT_nx_b, aa64_sme_f8f32, do_azz_acc_fp8, + a->n, 1, a->rv, a->off, a->zn, a->zm, + a->idx, 0, false, gen_helper_gvec_fdot_idx_sb) + +TRANS_FEAT(FDOT_nx_hb, aa64_sme_f8f16, do_azz_acc_fp8, + a->n, 1, a->rv, a->off, a->zn, a->zm, + a->idx, 0, false, gen_helper_gvec_fdot_idx_hb) + static bool do_fdot(DisasContext *s, arg_azz_n *a, bool multi) { return do_azz_acc_fp(s, a->n, 1, a->rv, a->off, a->zn, a->zm, 1, 0, @@ -1097,6 +1195,24 @@ static bool do_fdot(DisasContext *s, arg_azz_n *a, bool multi) TRANS_FEAT(FDOT_n1, aa64_sme2, do_fdot, a, false) TRANS_FEAT(FDOT_nn, aa64_sme2, do_fdot, a, true) +static bool do_fdot_fp8(DisasContext *s, arg_azz_n *a, bool multi) +{ + return do_azz_acc_fp8(s, a->n, 1, a->rv, a->off, a->zn, a->zm, + 0, 0, multi, gen_helper_gvec_fdot_sb); +} + +TRANS_FEAT(FDOT_n1_sb, aa64_sme_f8f32, do_fdot_fp8, a, false) +TRANS_FEAT(FDOT_nn_sb, aa64_sme_f8f32, do_fdot_fp8, a, true) + +static bool do_fdot_hb(DisasContext *s, arg_azz_n *a, bool multi) +{ + return do_azz_acc_fp8(s, a->n, 1, a->rv, a->off, a->zn, a->zm, + 0, 0, multi, gen_helper_gvec_fdot_hb); +} + +TRANS_FEAT(FDOT_n1_hb, aa64_sme_f8f16, do_fdot_hb, a, false) +TRANS_FEAT(FDOT_nn_hb, aa64_sme_f8f16, do_fdot_hb, a, true) + static bool do_fdot_nx(DisasContext *s, arg_azx_n *a) { return do_azz_acc_fp(s, a->n, 1, a->rv, a->off, a->zn, a->zm, @@ -1144,9 +1260,23 @@ static bool do_vdot(DisasContext *s, arg_azx_n *a, gen_helper_gvec_4_ptr *fn) return true; } -TRANS_FEAT(FVDOT, aa64_sme, do_vdot, a, gen_helper_sme2_fvdot_idx_h) +TRANS_FEAT(FVDOT_sh, aa64_sme, do_vdot, a, gen_helper_sme2_fvdot_idx_h) TRANS_FEAT(BFVDOT, aa64_sme, do_vdot, a, gen_helper_sme2_bfvdot_idx) +static bool do_fvdot_sb(DisasContext *s, arg_azx_n *a, bool top) +{ + return do_azz_acc_fp8(s, a->n, 1, a->rv, a->off, a->zn, a->zm, + (2 * a->idx + top) << 2, 0, false, + gen_helper_sme_fvdot_idx_sb); +} + +TRANS_FEAT(FVDOTB_sb, aa64_sme_f8f32, do_fvdot_sb, a, false) +TRANS_FEAT(FVDOTT_sb, aa64_sme_f8f32, do_fvdot_sb, a, true) + +TRANS_FEAT(FVDOT_hb, aa64_sme_f8f16, do_azz_acc_fp8, + a->n, 2, a->rv, a->off, a->zn, a->zm, + (a->idx << 1), 0, false, gen_helper_sme_fvdot_idx_hb) + static bool do_fmla(DisasContext *s, arg_azz_n *a, bool multi, ARMFPStatusFlavour fpst, gen_helper_gvec_3_ptr *fn) { @@ -1239,9 +1369,9 @@ static bool do_faddsub(DisasContext *s, arg_az_n *a, ARMFPStatusFlavour fpst, return true; } -TRANS_FEAT(FADD_nn_h, aa64_sme_f16f16, do_faddsub, a, +TRANS_FEAT(FADD_nn_h, aa64_sme_f16f16_or_f8f16, do_faddsub, a, FPST_ZA_F16, gen_helper_gvec_fadd_h) -TRANS_FEAT(FSUB_nn_h, aa64_sme_f16f16, do_faddsub, a, +TRANS_FEAT(FSUB_nn_h, aa64_sme_f16f16_or_f8f16, do_faddsub, a, FPST_ZA_F16, gen_helper_gvec_fsub_h) TRANS_FEAT(FADD_nn_s, aa64_sme2, do_faddsub, a, diff --git a/target/arm/tcg/translate-sve.c b/target/arm/tcg/translate-sve.c index 9a72e03513..79e8e16b24 100644 --- a/target/arm/tcg/translate-sve.c +++ b/target/arm/tcg/translate-sve.c @@ -2769,11 +2769,23 @@ TRANS_FEAT_NONSTREAMING(TRN2_q, aa64_sve_f64mm, do_interleave_q, *** SVE Permute Vector - Predicated Group */ -static gen_helper_gvec_3 * const compact_fns[4] = { - NULL, NULL, gen_helper_sve_compact_s, gen_helper_sve_compact_d -}; -TRANS_FEAT_NONSTREAMING(COMPACT, aa64_sve, gen_gvec_ool_arg_zpz, - compact_fns[a->esz], a, 0) +static bool trans_COMPACT(DisasContext *s, arg_COMPACT *a) +{ + static gen_helper_gvec_3 * const fns[4] = { + gen_helper_sve_compact_b, gen_helper_sve_compact_h, + gen_helper_sve_compact_s, gen_helper_sve_compact_d + }; + + if (!dc_isar_feature(aa64_sme2p2, s)) { + if (!(a->esz >= MO_32 + ? dc_isar_feature(aa64_sve, s) + : dc_isar_feature(aa64_sve2p2, s))) { + return false; + } + s->is_nonstreaming = true; + } + return gen_gvec_ool_arg_zpz(s, fns[a->esz], a, 0); +} /* Call the helper that computes the ARM LastActiveElement pseudocode * function, scaled by the element size. This includes the not found @@ -3469,6 +3481,62 @@ static bool trans_SINCDECP_z(DisasContext *s, arg_incdec2_pred *a) return true; } +static bool do_firstp_lastp(DisasContext *s, arg_rpr_esz *a, bool firstp) +{ + if (sve_access_check(s)) { + unsigned psz = pred_full_reg_size(s); + TCGv_i64 v = cpu_reg(s, a->rd); + + if (psz <= 8) { + uint64_t psz_mask; + + tcg_gen_ld_i64(v, tcg_env, pred_full_reg_offset(s, a->rn)); + if (a->rn != a->pg) { + TCGv_i64 g = tcg_temp_new_i64(); + tcg_gen_ld_i64(g, tcg_env, pred_full_reg_offset(s, a->pg)); + tcg_gen_and_i64(v, v, g); + } + + /* + * Reduce the pred_esz_masks value simply to reduce the + * size of the code generated here. + */ + psz_mask = MAKE_64BIT_MASK(0, psz * 8); + tcg_gen_andi_i64(v, v, pred_esz_masks[a->esz] & psz_mask); + + if (firstp) { + tcg_gen_ctzi_i64(v, v, -1); + } else { + tcg_gen_clzi_i64(v, v, 64); + tcg_gen_subfi_i64(v, 63, v); + } + tcg_gen_sari_i64(v, v, a->esz); + } else { + TCGv_ptr t_pn = tcg_temp_new_ptr(); + TCGv_ptr t_pg = tcg_temp_new_ptr(); + unsigned desc = 0; + TCGv_i32 t_desc; + + desc = FIELD_DP32(desc, PREDDESC, OPRSZ, psz); + desc = FIELD_DP32(desc, PREDDESC, ESZ, a->esz); + + tcg_gen_addi_ptr(t_pn, tcg_env, pred_full_reg_offset(s, a->rn)); + tcg_gen_addi_ptr(t_pg, tcg_env, pred_full_reg_offset(s, a->pg)); + t_desc = tcg_constant_i32(desc); + + if (firstp) { + gen_helper_sve_firstp(v, t_pn, t_pg, t_desc); + } else { + gen_helper_sve_lastp(v, t_pn, t_pg, t_desc); + } + } + } + return true; +} + +TRANS_FEAT(FIRSTP, aa64_sme2p2_or_sve2p2, do_firstp_lastp, a, true) +TRANS_FEAT(LASTP, aa64_sme2p2_or_sve2p2, do_firstp_lastp, a, false) + /* *** SVE Integer Compare Scalars Group */ @@ -6956,6 +7024,13 @@ static gen_helper_gvec_3 * const sqneg_fns[4] = { TRANS_FEAT(SQNEG_m, aa64_sme_or_sve2, gen_gvec_ool_arg_zpz, sqneg_fns[a->esz], a, 0) TRANS_FEAT(SQNEG_z, aa64_sme2p2_or_sve2p2, gen_gvec_ool_arg_zpz, sqneg_fns[a->esz], a, 1) +static gen_helper_gvec_3 * const expand_fns[4] = { + gen_helper_sve_expand_b, gen_helper_sve_expand_h, + gen_helper_sve_expand_s, gen_helper_sve_expand_d, +}; +TRANS_FEAT_STREAMING_IF(EXPAND, aa64_sme2p2_or_sve2p2, aa64_sme2p2, + gen_gvec_ool_arg_zpz, expand_fns[a->esz], a, 0) + DO_ZPZZ(SQSHL, aa64_sme_or_sve2, sve2_sqshl) DO_ZPZZ(SQRSHL, aa64_sme_or_sve2, sve2_sqrshl) DO_ZPZZ(SRSHL, aa64_sme_or_sve2, sve2_srshl) @@ -7094,7 +7169,7 @@ static bool do_trans_pmull(DisasContext *s, arg_rrr_esz *a, bool sel) if (!dc_isar_feature(aa64_sve2_pmull128, s)) { return false; } - s->is_nonstreaming = true; + s->is_nonstreaming = !dc_isar_feature(aa64_ssve_aes, s); } return gen_gvec_ool_arg_zzz(s, fns[a->esz], a, sel); } @@ -8099,15 +8174,17 @@ TRANS_FEAT(SDOT_zzzz_2s, aa64_sme2_or_sve2p1, gen_gvec_ool_arg_zzzz, TRANS_FEAT(UDOT_zzzz_2s, aa64_sme2_or_sve2p1, gen_gvec_ool_arg_zzzz, gen_helper_gvec_udot_2h, a, 0) -TRANS_FEAT_NONSTREAMING(AESMC, aa64_sve2_aes, gen_gvec_ool_zz, - gen_helper_crypto_aesmc, a->rd, a->rd, 0) -TRANS_FEAT_NONSTREAMING(AESIMC, aa64_sve2_aes, gen_gvec_ool_zz, - gen_helper_crypto_aesimc, a->rd, a->rd, 0) - -TRANS_FEAT_NONSTREAMING(AESE, aa64_sve2_aes, gen_gvec_ool_arg_zzz, - gen_helper_crypto_aese, a, 0) -TRANS_FEAT_NONSTREAMING(AESD, aa64_sve2_aes, gen_gvec_ool_arg_zzz, - gen_helper_crypto_aesd, a, 0) +TRANS_FEAT_STREAMING_IF(AESMC, aa64_sve_aes, aa64_ssve_aes, + gen_gvec_ool_zz, gen_helper_crypto_aesmc, + a->rd, a->rd, 0) +TRANS_FEAT_STREAMING_IF(AESIMC, aa64_sve_aes, aa64_ssve_aes, + gen_gvec_ool_zz, gen_helper_crypto_aesimc, + a->rd, a->rd, 0) + +TRANS_FEAT_STREAMING_IF(AESE, aa64_sve_aes, aa64_ssve_aes, + gen_gvec_ool_arg_zzz, gen_helper_crypto_aese, a, 0) +TRANS_FEAT_STREAMING_IF(AESD, aa64_sve_aes, aa64_ssve_aes, + gen_gvec_ool_arg_zzz, gen_helper_crypto_aesd, a, 0) TRANS_FEAT_NONSTREAMING(SM4E, aa64_sve2_sm4, gen_gvec_ool_arg_zzz, gen_helper_crypto_sm4e, a, 0) diff --git a/target/arm/tcg/vec_internal.h b/target/arm/tcg/vec_internal.h index 06fef2a209..77e5b01550 100644 --- a/target/arm/tcg/vec_internal.h +++ b/target/arm/tcg/vec_internal.h @@ -56,6 +56,37 @@ typedef struct CPUArchState CPUARMState; #define H1_8(x) (x) /* + * When considering the ZA storage as an array of elements of + * type T, the index within that array of the Nth element of + * a vertical slice of a tile can be calculated like this, + * regardless of the size of type T. This is because the tiles + * are interleaved, so if type T is size N bytes then row 1 of + * the tile is N rows away from row 0. The division by N to + * convert a byte offset into an array index and the multiplication + * by N to convert from vslice-index-within-the-tile to + * the index within the ZA storage cancel out. + */ +#define tile_vslice_index(i) ((i) * sizeof(ARMVectorReg)) + +/* + * When doing byte arithmetic on the ZA storage, the element + * byteoff bytes away in a tile vertical slice is always this + * many bytes away in the ZA storage, regardless of the + * size of the tile element, assuming that byteoff is a multiple + * of the element size. Again this is because of the interleaving + * of the tiles. For instance if we have 1 byte per element then + * each row of the ZA storage has one byte of the vslice data, + * and (counting from 0) byte 8 goes in row 8 of the storage + * at offset (8 * row-size-in-bytes). + * If we have 8 bytes per element then each row of the ZA storage + * has 8 bytes of the data, but there are 8 interleaved tiles and + * so byte 8 of the data goes into row 1 of the tile, + * which is again row 8 of the storage, so the offset is still + * (8 * row-size-in-bytes). Similarly for other element sizes. + */ +#define tile_vslice_offset(byteoff) ((byteoff) * sizeof(ARMVectorReg)) + +/* * Expand active predicate bits to bytes, for byte elements. */ extern const uint64_t expand_pred_b_data[256]; diff --git a/target/hexagon/Kconfig b/target/hexagon/Kconfig new file mode 100644 index 0000000000..7e556f3506 --- /dev/null +++ b/target/hexagon/Kconfig @@ -0,0 +1,2 @@ +config HEXAGON + bool diff --git a/target/hexagon/arch.c b/target/hexagon/arch.c index e17e714a6a..0a400bf7a7 100644 --- a/target/hexagon/arch.c +++ b/target/hexagon/arch.c @@ -208,6 +208,11 @@ void arch_fpop_start(CPUHexagonState *env) * model it in qemu user mode. */ #define RAISE_FP_EXCEPTION do {} while (0) +#else + /* + * To be implemented. + */ +#define RAISE_FP_EXCEPTION do { g_assert_not_reached(); } while (0) #endif #define SOFTFLOAT_TEST_FLAG(FLAG, MYF, MYE) \ diff --git a/target/hexagon/attribs_def.h.inc b/target/hexagon/attribs_def.h.inc index 9e3a05f882..6c55063a30 100644 --- a/target/hexagon/attribs_def.h.inc +++ b/target/hexagon/attribs_def.h.inc @@ -52,6 +52,9 @@ DEF_ATTRIB(REGWRSIZE_4B, "Memory width is 4 bytes", "", "") DEF_ATTRIB(REGWRSIZE_8B, "Memory width is 8 bytes", "", "") DEF_ATTRIB(MEMLIKE, "Memory-like instruction", "", "") DEF_ATTRIB(MEMLIKE_PACKET_RULES, "follows Memory-like packet rules", "", "") +DEF_ATTRIB(CACHEOP, "Cache operation", "", "") +DEF_ATTRIB(COPBYADDRESS, "Cache operation by address", "", "") +DEF_ATTRIB(COPBYIDX, "Cache operation by index", "", "") DEF_ATTRIB(RELEASE, "Releases a lock", "", "") DEF_ATTRIB(ACQUIRE, "Acquires a lock", "", "") @@ -101,7 +104,9 @@ DEF_ATTRIB(ROPS_3, "Compound instruction worth 3 RISC-ops", "", "") /* access to implicit registers */ DEF_ATTRIB(IMPLICIT_WRITES_LR, "Writes the link register", "", "UREG.LR") +DEF_ATTRIB(IMPLICIT_READS_PC, "Reads the program counter", "UREG.PC", "") DEF_ATTRIB(IMPLICIT_WRITES_SP, "Writes the stack pointer", "", "UREG.SP") +DEF_ATTRIB(IMPLICIT_READS_SP, "Reads the stack pointer", "UREG.SP", "") DEF_ATTRIB(IMPLICIT_WRITES_FP, "Writes the frame pointer", "", "UREG.FP") DEF_ATTRIB(IMPLICIT_WRITES_LC0, "Writes loop count for loop 0", "", "UREG.LC0") DEF_ATTRIB(IMPLICIT_WRITES_LC1, "Writes loop count for loop 1", "", "UREG.LC1") @@ -111,13 +116,19 @@ DEF_ATTRIB(IMPLICIT_WRITES_P0, "Writes Predicate 0", "", "UREG.P0") DEF_ATTRIB(IMPLICIT_WRITES_P1, "Writes Predicate 1", "", "UREG.P1") DEF_ATTRIB(IMPLICIT_WRITES_P2, "Writes Predicate 1", "", "UREG.P2") DEF_ATTRIB(IMPLICIT_WRITES_P3, "May write Predicate 3", "", "UREG.P3") -DEF_ATTRIB(IMPLICIT_READS_PC, "Reads the PC register", "", "") -DEF_ATTRIB(IMPLICIT_READS_P0, "Reads the P0 register", "", "") -DEF_ATTRIB(IMPLICIT_READS_P1, "Reads the P1 register", "", "") -DEF_ATTRIB(IMPLICIT_READS_P2, "Reads the P2 register", "", "") -DEF_ATTRIB(IMPLICIT_READS_P3, "Reads the P3 register", "", "") +DEF_ATTRIB(IMPLICIT_READS_P0, "Reads Predicate 0", "UREG.P0", "") +DEF_ATTRIB(IMPLICIT_READS_P1, "Reads Predicate 1", "UREG.P1", "") +DEF_ATTRIB(IMPLICIT_READS_P3, "Reads Predicate 3", "UREG.P3", "") DEF_ATTRIB(IMPLICIT_WRITES_USR, "May write USR", "", "") -DEF_ATTRIB(IMPLICIT_READS_SP, "Reads the SP register", "", "") +DEF_ATTRIB(IMPLICIT_WRITES_CCR, "Writes CCR register", "", "UREG.CCR") +DEF_ATTRIB(IMPLICIT_WRITES_GOSP, "Writes GOSP register", "", "UREG.GOSP") +DEF_ATTRIB(IMPLICIT_WRITES_SSR, "Writes SSR register", "", "UREG.SSR") +DEF_ATTRIB(IMPLICIT_WRITES_SGP0, "Writes SGP0 register", "", "UREG.SGP0") +DEF_ATTRIB(IMPLICIT_WRITES_SGP1, "Writes SGP1 register", "", "UREG.SGP1") +DEF_ATTRIB(IMPLICIT_WRITES_IMASK_ANYTHREAD, + "Writes IMASK for any thread", "", "") +DEF_ATTRIB(IMPLICIT_WRITES_STID_PRIO_ANYTHREAD, + "Writes STID priority for any thread", "", "") DEF_ATTRIB(COMMUTES, "The operation is communitive", "", "") DEF_ATTRIB(DEALLOCRET, "dealloc_return", "", "") DEF_ATTRIB(DEALLOCFRAME, "deallocframe", "", "") @@ -137,9 +148,14 @@ DEF_ATTRIB(RESTRICT_SLOT3ONLY, "Must execute on slot3", "", "") DEF_ATTRIB(RESTRICT_NOSLOT1, "No slot 1 instruction in parallel", "", "") DEF_ATTRIB(RESTRICT_PREFERSLOT0, "Try to encode into slot 0", "", "") DEF_ATTRIB(RESTRICT_PACKET_AXOK, "May exist with A-type or X-type", "", "") +DEF_ATTRIB(RESTRICT_SLOT1_AOK, "Slot 1 is allowed", "", "") DEF_ATTRIB(ICOP, "Instruction cache op", "", "") +DEF_ATTRIB(EXCEPTION_SWI, "Software interrupt exception", "", "") +DEF_ATTRIB(DMA, "DMA instruction", "", "") +DEF_ATTRIB(NO_TIMING_LOG, "Does not get logged to the timing model", "", "") + DEF_ATTRIB(HWLOOP0_END, "Ends HW loop0", "", "") DEF_ATTRIB(HWLOOP1_END, "Ends HW loop1", "", "") DEF_ATTRIB(RET_TYPE, "return type", "", "") @@ -151,6 +167,10 @@ DEF_ATTRIB(DCFETCH, "dcfetch type", "", "") DEF_ATTRIB(L2FETCH, "Instruction is l2fetch type", "", "") +DEF_ATTRIB(DCTAGOP, "Data cache tag operation", "", "") +DEF_ATTRIB(ICTAGOP, "Instruction cache tag operation", "", "") +DEF_ATTRIB(L2TAGOP, "L2 cache tag operation", "", "") + DEF_ATTRIB(ICINVA, "icinva", "", "") DEF_ATTRIB(DCCLEANINVA, "dccleaninva", "", "") @@ -166,6 +186,9 @@ DEF_ATTRIB(NOTE_LATEPRED, "The predicate can not be used as a .new", "", "") DEF_ATTRIB(NOTE_NVSLOT0, "Can execute only in slot 0 (ST)", "", "") DEF_ATTRIB(NOTE_NOVP, "Cannot be paired with a HVX permute instruction", "", "") DEF_ATTRIB(NOTE_VA_UNARY, "Combined with HVX ALU op (must be unary)", "", "") +DEF_ATTRIB(NOTE_SLOT1_AOK, "Slot 1 is allowed", "", "") +DEF_ATTRIB(NOTE_GUEST, "Guest mode instruction", "", "") +DEF_ATTRIB(NOTE_BADTAG_UNDEF, "Bad tag results in undefined behavior", "", "") /* V6 MMVector Notes for Documentation */ DEF_ATTRIB(NOTE_SHIFT_RESOURCE, "Uses the HVX shift resource.", "", "") diff --git a/target/hexagon/cpu-param.h b/target/hexagon/cpu-param.h index 1f0f22a796..bfe9a868d6 100644 --- a/target/hexagon/cpu-param.h +++ b/target/hexagon/cpu-param.h @@ -18,7 +18,7 @@ #ifndef HEXAGON_CPU_PARAM_H #define HEXAGON_CPU_PARAM_H -#define TARGET_PAGE_BITS 16 /* 64K pages */ +#define TARGET_PAGE_BITS 12 /* 4K pages */ #define TARGET_VIRT_ADDR_SPACE_BITS 32 diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c index 2c53f2c283..42d93e5da4 100644 --- a/target/hexagon/cpu.c +++ b/target/hexagon/cpu.c @@ -16,16 +16,34 @@ */ #include "qemu/osdep.h" +#include "qemu/log.h" #include "qemu/qemu-print.h" #include "cpu.h" #include "internal.h" +#include "exec/cputlb.h" #include "exec/translation-block.h" #include "qapi/error.h" #include "hw/core/qdev-properties.h" #include "fpu/softfloat-helpers.h" +#include "hw/hexagon/hexagon_tlb.h" #include "tcg/tcg.h" #include "exec/gdbstub.h" #include "accel/tcg/cpu-ops.h" +#include "cpu_helper.h" +#include "hex_mmu.h" + +#ifndef CONFIG_USER_ONLY +#include "macros.h" +#include "sys_macros.h" +#include "accel/tcg/cpu-ldst.h" +#include "qemu/main-loop.h" +#include "hex_interrupts.h" +#include "hexswi.h" +#include "exec/cpu-interrupt.h" +#include "exec/page-protection.h" +#include "exec/target_page.h" +#include "hw/hexagon/hexagon_globalreg.h" +#endif static ObjectClass *hexagon_cpu_class_by_name(const char *cpu_model) { @@ -43,6 +61,14 @@ static ObjectClass *hexagon_cpu_class_by_name(const char *cpu_model) } static const Property hexagon_cpu_properties[] = { +#ifndef CONFIG_USER_ONLY + DEFINE_PROP_LINK("tlb", HexagonCPU, tlb, TYPE_HEXAGON_TLB, + HexagonTLBState *), + DEFINE_PROP_UINT32("exec-start-addr", HexagonCPU, boot_addr, 0xffffffff), + DEFINE_PROP_LINK("global-regs", HexagonCPU, globalregs, + TYPE_HEXAGON_GLOBALREG, HexagonGlobalRegState *), + DEFINE_PROP_UINT32("htid", HexagonCPU, htid, 0), +#endif DEFINE_PROP_BOOL("lldb-compat", HexagonCPU, lldb_compat, false), DEFINE_PROP_UNSIGNED("lldb-stack-adjust", HexagonCPU, lldb_stack_adjust, 0, qdev_prop_uint32, target_ulong), @@ -60,6 +86,35 @@ const char * const hexagon_regnames[TOTAL_PER_THREAD_REGS] = { "c24", "c25", "c26", "c27", "c28", "c29", "c30", "c31", }; +#ifndef CONFIG_USER_ONLY +const char * const hexagon_sregnames[] = { + "sgp0", "sgp1", "stid", "elr", "badva0", + "badva1", "ssr", "ccr", "htid", "badva", + "imask", "gevb", "vwctrl", "s13", "s14", + "s15", "evb", "modectl", "syscfg", "segment", + "ipendad", "vid", "vid1", "bestwait", "s24", + "schedcfg", "s26", "cfgbase", "diag", "rev", + "pcyclelo", "pcyclehi", "isdbst", "isdbcfg0", "isdbcfg1", + "livelock", "brkptpc0", "brkptcfg0", "brkptpc1", "brkptcfg1", + "isdbmbxin", "isdbmbxout", "isdben", "isdbgpr", "pmucnt4", + "pmucnt5", "pmucnt6", "pmucnt7", "pmucnt0", "pmucnt1", + "pmucnt2", "pmucnt3", "pmuevtcfg", "pmustid0", "pmuevtcfg1", + "pmustid1", "timerlo", "timerhi", "pmucfg", "s59", + "s60", "s61", "s62", "s63", +}; + +G_STATIC_ASSERT(NUM_SREGS == ARRAY_SIZE(hexagon_sregnames)); + +const char * const hexagon_gregnames[] = { + "gelr", "gsr", "gosp", "gbadva", "gcommit1t", + "gcommit2t", "gcommit3t", "gcommit4t", "gcommit5t", "gcommit6t", + "gpcycle1t", "gpcycle2t", "gpcycle3t", "gpcycle4t", "gpcycle5t", + "gpcycle6t", "gpmucnt4", "gpmucnt5", "gpmucnt6", "gpmucnt7", + "gcommit7t", "gcommit8t", "gpcycle7t", "gpcycle8t", "gpcyclelo", + "gpcyclehi", "gpmucnt0", "gpmucnt1", "gpmucnt2", "gpmucnt3", + "g30", "g31", +}; +#endif /* * One of the main debugging techniques is to use "-d cpu" and compare against * LLDB output when single stepping. However, the target and qemu put the @@ -110,6 +165,14 @@ static void print_reg(FILE *f, CPUHexagonState *env, int regnum) hexagon_regnames[regnum], value); } +#ifndef CONFIG_USER_ONLY +static void print_t_sreg(FILE *f, const CPUHexagonState *env, int regnum) +{ + qemu_fprintf(f, " %s = 0x" TARGET_FMT_lx "\n", + hexagon_sregnames[regnum], env->t_sreg[regnum]); +} +#endif + static void print_vreg(FILE *f, CPUHexagonState *env, int regnum, bool skip_if_zero) { @@ -209,8 +272,7 @@ static void hexagon_dump(CPUHexagonState *env, FILE *f, int flags) qemu_fprintf(f, " cs0 = 0x00000000\n"); qemu_fprintf(f, " cs1 = 0x00000000\n"); #else - print_reg(f, env, HEX_REG_CAUSE); - print_reg(f, env, HEX_REG_BADVA); + print_t_sreg(f, env, HEX_SREG_BADVA); print_reg(f, env, HEX_REG_CS0); print_reg(f, env, HEX_REG_CS1); #endif @@ -261,6 +323,14 @@ static TCGTBCPUState hexagon_get_tb_cpu_state(CPUState *cs) hexagon_raise_exception_err(env, HEX_CAUSE_PC_NOT_ALIGNED, 0); } +#ifndef CONFIG_USER_ONLY + hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, MMU_INDEX, + cpu_mmu_index(env_cpu(env), false)); + hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, PCYCLE_ENABLED, 1); +#else + hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, MMU_INDEX, MMU_USER_IDX); +#endif + return (TCGTBCPUState){ .pc = pc, .flags = hex_flags }; } @@ -271,6 +341,36 @@ static void hexagon_cpu_synchronize_from_tb(CPUState *cs, cpu_env(cs)->gpr[HEX_REG_PC] = tb->pc; } +#ifndef CONFIG_USER_ONLY +bool hexagon_thread_is_enabled(CPUHexagonState *env) +{ + HexagonCPU *cpu = env_archcpu(env); + uint32_t modectl; + uint32_t thread_enabled_mask; + bool E_bit; + + if (!cpu->globalregs) { + return true; + } + modectl = + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_MODECTL, + env->threadId); + thread_enabled_mask = GET_FIELD(MODECTL_E, modectl); + E_bit = thread_enabled_mask & (0x1 << env->threadId); + + return E_bit; +} + +static bool hexagon_cpu_has_work(CPUState *cs) +{ + CPUHexagonState *env = cpu_env(cs); + + return hexagon_thread_is_enabled(env) && + (cs->interrupt_request & (CPU_INTERRUPT_HARD | CPU_INTERRUPT_SWI + | CPU_INTERRUPT_K0_UNLOCK | CPU_INTERRUPT_TLB_UNLOCK)); +} +#endif + static void hexagon_restore_state_to_opc(CPUState *cs, const TranslationBlock *tb, const uint64_t *data) @@ -278,11 +378,36 @@ static void hexagon_restore_state_to_opc(CPUState *cs, cpu_env(cs)->gpr[HEX_REG_PC] = data[0]; } + +#ifndef CONFIG_USER_ONLY +void hexagon_cpu_soft_reset(CPUHexagonState *env) +{ + HexagonCPU *cpu; + + BQL_LOCK_GUARD(); + env->t_sreg[HEX_SREG_SSR] = 0; + hexagon_ssr_set_cause(env, HEX_CAUSE_RESET); + + cpu = env_archcpu(env); + if (cpu->globalregs) { + uint32_t evb = + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_EVB, + env->threadId); + env->gpr[HEX_REG_PC] = evb; + } else { + env->gpr[HEX_REG_PC] = cpu->boot_addr; + } +} +#endif + static void hexagon_cpu_reset_hold(Object *obj, ResetType type) { CPUState *cs = CPU(obj); HexagonCPUClass *mcc = HEXAGON_CPU_GET_CLASS(obj); CPUHexagonState *env = cpu_env(cs); +#ifndef CONFIG_USER_ONLY + HexagonCPU *cpu = HEXAGON_CPU(cs); +#endif if (mcc->parent_phases.hold) { mcc->parent_phases.hold(obj, type); @@ -292,6 +417,22 @@ static void hexagon_cpu_reset_hold(Object *obj, ResetType type) set_float_detect_tininess(float_tininess_before_rounding, &env->fp_status); /* Default NaN value: sign bit set, all frac bits set */ set_float_default_nan_pattern(0b11111111, &env->fp_status); +#ifndef CONFIG_USER_ONLY + memset(env->t_sreg, 0, sizeof(uint32_t) * NUM_SREGS); + memset(env->greg, 0, sizeof(uint32_t) * NUM_GREGS); + env->wait_next_pc = 0; + env->tlb_lock_state = HEX_LOCK_UNLOCKED; + env->k0_lock_state = HEX_LOCK_UNLOCKED; + env->tlb_lock_count = 0; + env->k0_lock_count = 0; + env->next_PC = 0; + + env->t_sreg[HEX_SREG_HTID] = cpu->htid; + env->threadId = cpu->htid; + hexagon_cpu_soft_reset(env); + env->cause_code = HEX_EVENT_NONE; + env->gpr[HEX_REG_PC] = cpu->boot_addr; +#endif } static void hexagon_cpu_disas_set_info(const CPUState *cs, @@ -319,21 +460,263 @@ static void hexagon_cpu_realize(DeviceState *dev, Error **errp) hexagon_hvx_gdb_write_register, gdb_find_static_feature("hexagon-hvx.xml")); +#ifndef CONFIG_USER_ONLY + if (!HEXAGON_CPU(dev)->tlb) { + error_setg(errp, "hexagon cpu requires 'tlb' link property to be set"); + return; + } +#endif + qemu_init_vcpu(cs); - cpu_reset(cs); + cpu_reset(cs); mcc->parent_realize(dev, errp); } static int hexagon_cpu_mmu_index(CPUState *cs, bool ifetch) { +#ifndef CONFIG_USER_ONLY + CPUHexagonState *env = cpu_env(cs); + HexagonCPU *cpu = HEXAGON_CPU(cs); + int cpu_mode; + + BQL_LOCK_GUARD(); + if (cpu->globalregs) { + uint32_t syscfg = + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SYSCFG, + env->threadId); + uint8_t mmuen = GET_SYSCFG_FIELD(SYSCFG_MMUEN, syscfg); + if (!mmuen) { + return MMU_KERNEL_IDX; + } + } + + cpu_mode = get_cpu_mode(env); + if (cpu_mode == HEX_CPU_MODE_MONITOR) { + return MMU_KERNEL_IDX; + } else if (cpu_mode == HEX_CPU_MODE_GUEST) { + return MMU_GUEST_IDX; + } +#endif + return MMU_USER_IDX; } +#ifndef CONFIG_USER_ONLY +static void hexagon_cpu_set_irq(void *opaque, int irq, int level) +{ + HexagonCPU *cpu = HEXAGON_CPU(opaque); + CPUState *cs = CPU(cpu); + CPUHexagonState *env = cpu_env(cs); + + switch (irq) { + case HEXAGON_CPU_IRQ_0 ... HEXAGON_CPU_IRQ_7: + qemu_log_mask(CPU_LOG_INT, "%s: irq %d, level %d\n", + __func__, irq, level); + if (level) { + hex_raise_interrupts(env, 1 << irq, CPU_INTERRUPT_HARD); + } + break; + default: + g_assert_not_reached(); + } +} +#endif + static void hexagon_cpu_init(Object *obj) { +#ifndef CONFIG_USER_ONLY + HexagonCPU *cpu = HEXAGON_CPU(obj); + qdev_init_gpio_in(DEVICE(cpu), hexagon_cpu_set_irq, 8); +#endif } +#ifndef CONFIG_USER_ONLY +static bool get_physical_address(CPUHexagonState *env, hwaddr *phys, int *prot, + uint64_t *size, int32_t *excp, + uint32_t address, + MMUAccessType access_type, int mmu_idx) + +{ + if (hexagon_cpu_mmu_enabled(env)) { + return hex_tlb_find_match(env, address, access_type, phys, prot, size, + excp, mmu_idx); + } else { + *phys = address & 0xFFFFFFFF; + *prot = PAGE_VALID | PAGE_READ | PAGE_WRITE | PAGE_EXEC; + *size = TARGET_PAGE_SIZE; + return true; + } +} + +/* qemu seems to only want to know about TARGET_PAGE_SIZE pages */ +static void find_qemu_subpage(vaddr *addr, hwaddr *phys, uint64_t page_size) +{ + vaddr page_start = *addr & ~((vaddr)(page_size - 1)); + vaddr offset = ((*addr - page_start) / TARGET_PAGE_SIZE) * TARGET_PAGE_SIZE; + *addr = page_start + offset; + *phys += offset; +} + +static hwaddr hexagon_cpu_get_phys_addr_debug(CPUState *cs, vaddr addr) +{ + CPUHexagonState *env = cpu_env(cs); + hwaddr phys_addr; + int prot; + uint64_t page_size = 0; + int32_t excp = 0; + int mmu_idx = MMU_KERNEL_IDX; + + if (get_physical_address(env, &phys_addr, &prot, &page_size, &excp, + addr, 0, mmu_idx)) { + find_qemu_subpage(&addr, &phys_addr, page_size); + return phys_addr; + } + + return -1; +} + + +#define INVALID_BADVA 0xbadabada + +static void set_badva_regs(CPUHexagonState *env, uint32_t VA, int slot, + MMUAccessType access_type) +{ + env->t_sreg[HEX_SREG_BADVA] = VA; + + if (access_type == MMU_INST_FETCH || slot == 0) { + env->t_sreg[HEX_SREG_BADVA0] = VA; + env->t_sreg[HEX_SREG_BADVA1] = INVALID_BADVA; + SET_SSR_FIELD(env, SSR_V0, 1); + SET_SSR_FIELD(env, SSR_V1, 0); + SET_SSR_FIELD(env, SSR_BVS, 0); + } else if (slot == 1) { + env->t_sreg[HEX_SREG_BADVA0] = INVALID_BADVA; + env->t_sreg[HEX_SREG_BADVA1] = VA; + SET_SSR_FIELD(env, SSR_V0, 0); + SET_SSR_FIELD(env, SSR_V1, 1); + SET_SSR_FIELD(env, SSR_BVS, 1); + } else { + g_assert_not_reached(); + } +} + +static void raise_tlbmiss_exception(CPUState *cs, uint32_t VA, int slot, + MMUAccessType access_type) +{ + CPUHexagonState *env = cpu_env(cs); + + set_badva_regs(env, VA, slot, access_type); + + switch (access_type) { + case MMU_INST_FETCH: + cs->exception_index = HEX_EVENT_TLB_MISS_X; + if ((VA & ~TARGET_PAGE_MASK) == 0) { + env->cause_code = HEX_CAUSE_TLBMISSX_CAUSE_NEXTPAGE; + } else { + env->cause_code = HEX_CAUSE_TLBMISSX_CAUSE_NORMAL; + } + break; + case MMU_DATA_LOAD: + cs->exception_index = HEX_EVENT_TLB_MISS_RW; + env->cause_code = HEX_CAUSE_TLBMISSRW_CAUSE_READ; + break; + case MMU_DATA_STORE: + cs->exception_index = HEX_EVENT_TLB_MISS_RW; + env->cause_code = HEX_CAUSE_TLBMISSRW_CAUSE_WRITE; + break; + } +} + +static void raise_perm_exception(CPUState *cs, uint32_t VA, int slot, + MMUAccessType access_type, int32_t excp) +{ + CPUHexagonState *env = cpu_env(cs); + + set_badva_regs(env, VA, slot, access_type); + cs->exception_index = excp; +} + +static const char *access_type_names[] = { "MMU_DATA_LOAD ", "MMU_DATA_STORE", + "MMU_INST_FETCH" }; + +static const char *mmu_idx_names[] = { "MMU_USER_IDX", "MMU_GUEST_IDX", + "MMU_KERNEL_IDX" }; + +static bool hexagon_tlb_fill(CPUState *cs, vaddr address, int size, + MMUAccessType access_type, int mmu_idx, bool probe, + uintptr_t retaddr) +{ + CPUHexagonState *env = cpu_env(cs); + int slot = 0; + hwaddr phys; + int prot = 0; + uint64_t page_size = 0; + int32_t excp = 0; + bool ret = 0; + + qemu_log_mask( + CPU_LOG_MMU, + "%s: tid = 0x%" PRIx32 ", pc = 0x%08" PRIx32 + ", vaddr = 0x%08" VADDR_PRIx ", size = %d, %s,\tprobe = %d, %s\n", + __func__, env->threadId, env->gpr[HEX_REG_PC], address, size, + access_type_names[access_type], probe, mmu_idx_names[mmu_idx]); + ret = get_physical_address(env, &phys, &prot, &page_size, &excp, address, + access_type, mmu_idx); + if (ret) { + if (!excp) { + find_qemu_subpage(&address, &phys, page_size); + tlb_set_page(cs, address, phys, prot, mmu_idx, TARGET_PAGE_SIZE); + return ret; + } + if (probe) { + return false; + } + raise_perm_exception(cs, address, slot, access_type, excp); + do_raise_exception(env, cs->exception_index, env->gpr[HEX_REG_PC], + retaddr); + } + if (probe) { + return false; + } + raise_tlbmiss_exception(cs, address, slot, access_type); + do_raise_exception(env, cs->exception_index, env->gpr[HEX_REG_PC], retaddr); +} + +#include "hw/core/sysemu-cpu-ops.h" + +static const struct SysemuCPUOps hexagon_sysemu_ops = { + .has_work = hexagon_cpu_has_work, + .get_phys_addr_debug = hexagon_cpu_get_phys_addr_debug, +}; + +static bool hexagon_cpu_exec_interrupt(CPUState *cs, int interrupt_request) +{ + CPUHexagonState *env = cpu_env(cs); + if (interrupt_request & CPU_INTERRUPT_TLB_UNLOCK) { + cs->halted = false; + cpu_reset_interrupt(cs, CPU_INTERRUPT_TLB_UNLOCK); + return true; + } + if (interrupt_request & CPU_INTERRUPT_K0_UNLOCK) { + cs->halted = false; + cpu_reset_interrupt(cs, CPU_INTERRUPT_K0_UNLOCK); + return true; + } + if (interrupt_request & (CPU_INTERRUPT_HARD | CPU_INTERRUPT_SWI)) { + return hex_check_interrupts(env); + } + return false; +} + +static vaddr hexagon_pointer_wrap(CPUState *cs, int mmu_idx, + vaddr result, vaddr base) +{ + return result; +} + +#endif + static const TCGCPUOps hexagon_tcg_ops = { /* MTTCG not yet supported: require strict ordering */ .guest_default_memory_order = TCG_MO_ALL, @@ -344,6 +727,14 @@ static const TCGCPUOps hexagon_tcg_ops = { .synchronize_from_tb = hexagon_cpu_synchronize_from_tb, .restore_state_to_opc = hexagon_restore_state_to_opc, .mmu_index = hexagon_cpu_mmu_index, +#ifndef CONFIG_USER_ONLY + .cpu_exec_interrupt = hexagon_cpu_exec_interrupt, + .pointer_wrap = hexagon_pointer_wrap, + .cpu_exec_reset = cpu_reset, + .tlb_fill = hexagon_tlb_fill, + .cpu_exec_halt = hexagon_cpu_has_work, + .do_interrupt = hexagon_cpu_do_interrupt, +#endif /* !CONFIG_USER_ONLY */ }; static void hexagon_cpu_class_init(ObjectClass *c, const void *data) @@ -369,9 +760,34 @@ static void hexagon_cpu_class_init(ObjectClass *c, const void *data) cc->gdb_stop_before_watchpoint = true; cc->gdb_core_xml_file = "hexagon-core.xml"; cc->disas_set_info = hexagon_cpu_disas_set_info; +#ifndef CONFIG_USER_ONLY + cc->sysemu_ops = &hexagon_sysemu_ops; + dc->vmsd = &vmstate_hexagon_cpu; +#endif +#ifdef CONFIG_TCG cc->tcg_ops = &hexagon_tcg_ops; +#endif } +#ifndef CONFIG_USER_ONLY +uint32_t hexagon_greg_read(CPUHexagonState *env, uint32_t reg) +{ + if (reg <= HEX_GREG_G3) { + return env->greg[reg]; + } + switch (reg) { + case HEX_GREG_GPCYCLELO: + return hexagon_get_sys_pcycle_count_low(env); + case HEX_GREG_GPCYCLEHI: + return hexagon_get_sys_pcycle_count_high(env); + default: + qemu_log_mask(LOG_UNIMP, "reading greg %" PRId32 + " not yet supported.\n", reg); + return 0; + } +} +#endif + static void hexagon_cpu_class_base_init(ObjectClass *c, const void *data) { HexagonCPUClass *mcc = HEXAGON_CPU_CLASS(c); diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h index 7c09c97db8..7694fd91fa 100644 --- a/target/hexagon/cpu.h +++ b/target/hexagon/cpu.h @@ -20,6 +20,16 @@ #include "fpu/softfloat-types.h" +#ifndef CONFIG_USER_ONLY +#define NUM_GREGS 32 +#define GREG_WRITES_MAX 2 +#define NUM_SREGS 64 +#define SREG_WRITES_MAX 2 +#endif + +typedef struct HexagonTLBState HexagonTLBState; +typedef struct HexagonGlobalRegState HexagonGlobalRegState; + #include "cpu-qom.h" #include "exec/cpu-common.h" #include "exec/target_long.h" @@ -28,9 +38,7 @@ #include "hw/core/registerfields.h" #include "qemu/bitmap.h" -#ifndef CONFIG_USER_ONLY -#error "Hexagon does not support system emulation" -#endif +#include "target/hexagon/reg_fields.h" #define NUM_PREGS 4 #define TOTAL_PER_THREAD_REGS 64 @@ -40,10 +48,47 @@ #define REG_WRITES_MAX 32 #define PRED_WRITES_MAX 5 /* 4 insns + endloop */ #define VSTORES_MAX 2 +#define MAX_TLB_ENTRIES 1024 +#define THREADS_MAX 8 #define CPU_RESOLVING_TYPE TYPE_HEXAGON_CPU +#ifndef CONFIG_USER_ONLY +#define CPU_INTERRUPT_SWI CPU_INTERRUPT_TGT_INT_0 +#define CPU_INTERRUPT_K0_UNLOCK CPU_INTERRUPT_TGT_INT_1 +#define CPU_INTERRUPT_TLB_UNLOCK CPU_INTERRUPT_TGT_INT_2 + +#define HEX_CPU_MODE_USER 1 +#define HEX_CPU_MODE_GUEST 2 +#define HEX_CPU_MODE_MONITOR 3 + +#define HEX_EXE_MODE_OFF 1 +#define HEX_EXE_MODE_RUN 2 +#define HEX_EXE_MODE_WAIT 3 +#define HEX_EXE_MODE_DEBUG 4 +#endif + +#define MMU_USER_IDX 0 +#ifndef CONFIG_USER_ONLY +#define MMU_GUEST_IDX 1 +#define MMU_KERNEL_IDX 2 + +typedef enum { + HEX_LOCK_UNLOCKED = 0, + HEX_LOCK_WAITING = 1, + HEX_LOCK_OWNER = 2, + HEX_LOCK_QUEUED = 3 +} hex_lock_state_t; +#endif -#define MMU_USER_IDX 0 + +#define HEXAGON_CPU_IRQ_0 0 +#define HEXAGON_CPU_IRQ_1 1 +#define HEXAGON_CPU_IRQ_2 2 +#define HEXAGON_CPU_IRQ_3 3 +#define HEXAGON_CPU_IRQ_4 4 +#define HEXAGON_CPU_IRQ_5 5 +#define HEXAGON_CPU_IRQ_6 6 +#define HEXAGON_CPU_IRQ_7 7 typedef struct { target_ulong va; @@ -77,12 +122,30 @@ typedef struct { typedef struct CPUArchState { target_ulong gpr[TOTAL_PER_THREAD_REGS]; target_ulong pred[NUM_PREGS]; + uint32_t cause_code; /* For comparing with LLDB on target - see adjust_stack_ptrs function */ target_ulong last_pc_dumped; target_ulong stack_start; uint8_t slot_cancelled; + +#ifndef CONFIG_USER_ONLY + /* Some system registers are per thread and some are global. */ + uint32_t t_sreg[NUM_SREGS]; + + uint32_t greg[NUM_GREGS]; + uint32_t wait_next_pc; + + /* This alias of CPUState.cpu_index is used by imported sources: */ + uint32_t threadId; + hex_lock_state_t tlb_lock_state; + hex_lock_state_t k0_lock_state; + uint32_t tlb_lock_count; + uint32_t k0_lock_count; + uint64_t t_cycle_count; +#endif + uint32_t next_PC; target_ulong new_value_usr; MemLog mem_log_stores[STORES_MAX]; @@ -130,16 +193,34 @@ struct ArchCPU { bool lldb_compat; target_ulong lldb_stack_adjust; bool short_circuit; +#ifndef CONFIG_USER_ONLY + HexagonTLBState *tlb; + uint32_t boot_addr; + HexagonGlobalRegState *globalregs; + uint32_t htid; +#endif }; #include "cpu_bits.h" FIELD(TB_FLAGS, IS_TIGHT_LOOP, 0, 1) +FIELD(TB_FLAGS, MMU_INDEX, 1, 3) +FIELD(TB_FLAGS, PCYCLE_ENABLED, 4, 1) G_NORETURN void hexagon_raise_exception_err(CPUHexagonState *env, uint32_t exception, uintptr_t pc); +#ifndef CONFIG_USER_ONLY +/* + * @return true if the @a thread_env hardware thread is + * not stopped. + */ +bool hexagon_thread_is_enabled(CPUHexagonState *thread_env); +uint32_t hexagon_greg_read(CPUHexagonState *env, uint32_t reg); +void hexagon_cpu_soft_reset(CPUHexagonState *env); +#endif + typedef HexagonCPU ArchCPU; void hexagon_translate_init(void); diff --git a/target/hexagon/cpu_bits.h b/target/hexagon/cpu_bits.h index aaac6b9ea6..164e74c782 100644 --- a/target/hexagon/cpu_bits.h +++ b/target/hexagon/cpu_bits.h @@ -25,20 +25,77 @@ #define PCALIGN_MASK (PCALIGN - 1) enum hex_event { - HEX_EVENT_NONE = -1, - HEX_EVENT_TRAP0 = 0x008, + HEX_EVENT_NONE = -1, + HEX_EVENT_RESET = 0x0, + HEX_EVENT_IMPRECISE = 0x1, + HEX_EVENT_PRECISE = 0x2, + HEX_EVENT_TLB_MISS_X = 0x4, + HEX_EVENT_TLB_MISS_RW = 0x6, + HEX_EVENT_TRAP0 = 0x8, + HEX_EVENT_TRAP1 = 0x9, + HEX_EVENT_FPTRAP = 0xb, + HEX_EVENT_DEBUG = 0xc, + HEX_EVENT_INT0 = 0x10, + HEX_EVENT_INT1 = 0x11, + HEX_EVENT_INT2 = 0x12, + HEX_EVENT_INT3 = 0x13, + HEX_EVENT_INT4 = 0x14, + HEX_EVENT_INT5 = 0x15, + HEX_EVENT_INT6 = 0x16, + HEX_EVENT_INT7 = 0x17, + HEX_EVENT_INT8 = 0x18, + HEX_EVENT_INT9 = 0x19, + HEX_EVENT_INTA = 0x1a, + HEX_EVENT_INTB = 0x1b, + HEX_EVENT_INTC = 0x1c, + HEX_EVENT_INTD = 0x1d, + HEX_EVENT_INTE = 0x1e, + HEX_EVENT_INTF = 0x1f, }; enum hex_cause { HEX_CAUSE_NONE = -1, - HEX_CAUSE_TRAP0 = 0x172, - HEX_CAUSE_FETCH_NO_UPAGE = 0x012, - HEX_CAUSE_INVALID_PACKET = 0x015, - HEX_CAUSE_INVALID_OPCODE = 0x015, + HEX_CAUSE_RESET = 0x000, + HEX_CAUSE_BIU_PRECISE = 0x001, + HEX_CAUSE_UNSUPPORTED_HVX_64B = 0x002, /* QEMU-specific */ + HEX_CAUSE_DOUBLE_EXCEPT = 0x003, + HEX_CAUSE_TRAP0 = 0x008, + HEX_CAUSE_TRAP1 = 0x009, + HEX_CAUSE_FETCH_NO_XPAGE = 0x011, + HEX_CAUSE_FETCH_NO_UPAGE = 0x012, + HEX_CAUSE_INVALID_PACKET = 0x015, + HEX_CAUSE_INVALID_OPCODE = 0x015, /* alias: same cause as INVALID_PACKET */ + HEX_CAUSE_NO_COPROC_ENABLE = 0x016, + HEX_CAUSE_NO_COPROC2_ENABLE = 0x018, + HEX_CAUSE_PRIV_USER_NO_GINSN = 0x01a, + HEX_CAUSE_PRIV_USER_NO_SINSN = 0x01b, HEX_CAUSE_REG_WRITE_CONFLICT = 0x01d, - HEX_CAUSE_PC_NOT_ALIGNED = 0x01e, - HEX_CAUSE_PRIV_NO_UREAD = 0x024, - HEX_CAUSE_PRIV_NO_UWRITE = 0x025, + HEX_CAUSE_PC_NOT_ALIGNED = 0x01e, + HEX_CAUSE_MISALIGNED_LOAD = 0x020, + HEX_CAUSE_MISALIGNED_STORE = 0x021, + HEX_CAUSE_PRIV_NO_READ = 0x022, + HEX_CAUSE_PRIV_NO_WRITE = 0x023, + HEX_CAUSE_PRIV_NO_UREAD = 0x024, + HEX_CAUSE_PRIV_NO_UWRITE = 0x025, + HEX_CAUSE_COPROC_LDST = 0x026, + HEX_CAUSE_STACK_LIMIT = 0x027, + HEX_CAUSE_VWCTRL_WINDOW_MISS = 0x029, + HEX_CAUSE_IMPRECISE_NMI = 0x043, + HEX_CAUSE_IMPRECISE_MULTI_TLB_MATCH = 0x044, + HEX_CAUSE_TLBMISSX_CAUSE_NORMAL = 0x060, + HEX_CAUSE_TLBMISSX_CAUSE_NEXTPAGE = 0x061, + HEX_CAUSE_TLBMISSRW_CAUSE_READ = 0x070, + HEX_CAUSE_TLBMISSRW_CAUSE_WRITE = 0x071, + HEX_CAUSE_DEBUG_SINGLESTEP = 0x80, + HEX_CAUSE_FPTRAP_CAUSE_BADFLOAT = 0x0bf, + HEX_CAUSE_INT0 = 0x0c0, + HEX_CAUSE_INT1 = 0x0c1, + HEX_CAUSE_INT2 = 0x0c2, + HEX_CAUSE_INT3 = 0x0c3, + HEX_CAUSE_INT4 = 0x0c4, + HEX_CAUSE_INT5 = 0x0c5, + HEX_CAUSE_INT6 = 0x0c6, + HEX_CAUSE_INT7 = 0x0c7, }; #define PACKET_WORDS_MAX 4 diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c new file mode 100644 index 0000000000..64c5746c6d --- /dev/null +++ b/target/hexagon/cpu_helper.c @@ -0,0 +1,399 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "cpu.h" +#include "cpu_helper.h" +#include "system/cpus.h" +#include "hw/core/boards.h" +#include "hw/hexagon/hexagon.h" +#include "hw/hexagon/hexagon_globalreg.h" +#include "hex_interrupts.h" +#include "hex_mmu.h" +#include "system/runstate.h" +#include "exec/cpu-interrupt.h" +#include "exec/target_page.h" +#include "accel/tcg/cpu-ldst.h" +#include "exec/cputlb.h" +#include "qemu/log.h" +#include "tcg/tcg-op.h" +#include "internal.h" +#include "macros.h" +#include "sys_macros.h" +#include "arch.h" + + +uint32_t hexagon_get_pmu_counter(CPUHexagonState *cur_env, int index) +{ + g_assert_not_reached(); +} + +uint64_t hexagon_get_sys_pcycle_count(CPUHexagonState *env) +{ + uint64_t total = 0; + CPUState *cs; + + g_assert(bql_locked()); + CPU_FOREACH(cs) { + CPUHexagonState *thread_env = cpu_env(cs); + total += thread_env->t_cycle_count; + } + return total; +} + +uint32_t hexagon_get_sys_pcycle_count_high(CPUHexagonState *env) +{ + return (uint32_t)(hexagon_get_sys_pcycle_count(env) >> 32); +} + +uint32_t hexagon_get_sys_pcycle_count_low(CPUHexagonState *env) +{ + return (uint32_t)(hexagon_get_sys_pcycle_count(env)); +} + +void hexagon_set_sys_pcycle_count_high(CPUHexagonState *env, uint32_t val) +{ + uint64_t old; + + g_assert(bql_locked()); + old = hexagon_get_sys_pcycle_count(env); + old = deposit64(old, 32, 32, val); + hexagon_set_sys_pcycle_count(env, old); +} + +void hexagon_set_sys_pcycle_count_low(CPUHexagonState *env, uint32_t val) +{ + uint64_t old; + + g_assert(bql_locked()); + old = hexagon_get_sys_pcycle_count(env); + old = deposit64(old, 0, 32, val); + hexagon_set_sys_pcycle_count(env, old); +} + +void hexagon_set_sys_pcycle_count(CPUHexagonState *env, uint64_t val) +{ + CPUState *cs; + uint64_t total; + int num_threads; + int64_t delta, per_thread, remainder; + + g_assert(bql_locked()); + total = hexagon_get_sys_pcycle_count(env); + + /* Count active threads */ + num_threads = 0; + CPU_FOREACH(cs) { + num_threads++; + } + g_assert(num_threads > 0); + + /* + * Distribute the delta evenly across all threads. + * Any remainder goes to the calling thread. + */ + delta = (int64_t)(val - total); + per_thread = delta / num_threads; + remainder = delta - per_thread * num_threads; + + CPU_FOREACH(cs) { + CPUHexagonState *thread_env = cpu_env(cs); + thread_env->t_cycle_count += per_thread; + } + env->t_cycle_count += remainder; +} + +static void hexagon_resume_thread(CPUHexagonState *env) +{ + CPUState *cs = env_cpu(env); + clear_wait_mode(env); + /* + * The wait instruction keeps the PC pointing to itself + * so that it has an opportunity to check for interrupts. + * + * When we come out of wait mode, adjust the PC to the + * next executable instruction. + */ + env->gpr[HEX_REG_PC] = env->wait_next_pc; + cs = env_cpu(env); + ASSERT_DIRECT_TO_GUEST_UNSET(env, cs->exception_index); + cs->halted = false; + cs->exception_index = HEX_EVENT_NONE; + qemu_cpu_kick(cs); +} + +void hexagon_resume_threads(CPUHexagonState *current_env, uint32_t mask) +{ + CPUState *cs; + CPUHexagonState *env; + + g_assert(bql_locked()); + CPU_FOREACH(cs) { + env = cpu_env(cs); + g_assert(env->threadId < THREADS_MAX); + if ((mask & (0x1 << env->threadId))) { + if (get_exe_mode(env) == HEX_EXE_MODE_WAIT) { + hexagon_resume_thread(env); + } + } + } +} + +void hexagon_modify_ssr(CPUHexagonState *env, uint32_t new, uint32_t old) +{ + bool old_EX, old_UM, old_GM, old_IE; + bool new_EX, new_UM, new_GM, new_IE; + uint8_t old_asid, new_asid; + + g_assert(bql_locked()); + + old_EX = GET_SSR_FIELD(SSR_EX, old); + old_UM = GET_SSR_FIELD(SSR_UM, old); + old_GM = GET_SSR_FIELD(SSR_GM, old); + old_IE = GET_SSR_FIELD(SSR_IE, old); + new_EX = GET_SSR_FIELD(SSR_EX, new); + new_UM = GET_SSR_FIELD(SSR_UM, new); + new_GM = GET_SSR_FIELD(SSR_GM, new); + new_IE = GET_SSR_FIELD(SSR_IE, new); + + if ((old_EX != new_EX) || + (old_UM != new_UM) || + (old_GM != new_GM)) { + hex_mmu_mode_change(env); + } + + old_asid = GET_SSR_FIELD(SSR_ASID, old); + new_asid = GET_SSR_FIELD(SSR_ASID, new); + if (new_asid != old_asid) { + CPUState *cs = env_cpu(env); + tlb_flush(cs); + } + + /* See if the interrupts have been enabled or we have exited EX mode */ + if ((new_IE && !old_IE) || + (!new_EX && old_EX)) { + hex_interrupt_update(env); + } +} + +void clear_wait_mode(CPUHexagonState *env) +{ + HexagonCPU *cpu; + uint32_t modectl, thread_wait_mask; + + g_assert(bql_locked()); + + cpu = env_archcpu(env); + if (cpu->globalregs) { + modectl = + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_MODECTL, + env->threadId); + thread_wait_mask = GET_FIELD(MODECTL_W, modectl); + thread_wait_mask &= ~(0x1 << env->threadId); + SET_SYSTEM_FIELD(env, HEX_SREG_MODECTL, MODECTL_W, thread_wait_mask); + } +} + +void hexagon_ssr_set_cause(CPUHexagonState *env, uint32_t cause) +{ + uint32_t old, new; + + g_assert(bql_locked()); + + old = env->t_sreg[HEX_SREG_SSR]; + SET_SYSTEM_FIELD(env, HEX_SREG_SSR, SSR_EX, 1); + SET_SYSTEM_FIELD(env, HEX_SREG_SSR, SSR_CAUSE, cause); + new = env->t_sreg[HEX_SREG_SSR]; + + hexagon_modify_ssr(env, new, old); +} + + +int get_exe_mode(CPUHexagonState *env) +{ + HexagonCPU *cpu; + uint32_t modectl, thread_enabled_mask, thread_wait_mask; + uint32_t isdbst, debugmode; + bool E_bit, W_bit, D_bit; + + g_assert(bql_locked()); + + cpu = env_archcpu(env); + modectl = cpu->globalregs ? + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_MODECTL, + env->threadId) : 0; + thread_enabled_mask = GET_FIELD(MODECTL_E, modectl); + E_bit = thread_enabled_mask & (0x1 << env->threadId); + thread_wait_mask = GET_FIELD(MODECTL_W, modectl); + W_bit = thread_wait_mask & (0x1 << env->threadId); + isdbst = cpu->globalregs ? + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_ISDBST, + env->threadId) : 0; + debugmode = GET_FIELD(ISDBST_DEBUGMODE, isdbst); + D_bit = debugmode & (0x1 << env->threadId); + + if (!D_bit && !W_bit && !E_bit) { + return HEX_EXE_MODE_OFF; + } + if (!D_bit && !W_bit && E_bit) { + return HEX_EXE_MODE_RUN; + } + if (!D_bit && W_bit && E_bit) { + return HEX_EXE_MODE_WAIT; + } + if (D_bit && !W_bit && E_bit) { + return HEX_EXE_MODE_DEBUG; + } + g_assert_not_reached(); +} + +static uint32_t set_enable_mask(CPUHexagonState *env) +{ + HexagonCPU *cpu; + uint32_t modectl, thread_enabled_mask; + + g_assert(bql_locked()); + + cpu = env_archcpu(env); + if (!cpu->globalregs) { + return 0; + } + modectl = + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_MODECTL, + env->threadId); + thread_enabled_mask = GET_FIELD(MODECTL_E, modectl); + thread_enabled_mask |= 0x1 << env->threadId; + SET_SYSTEM_FIELD(env, HEX_SREG_MODECTL, MODECTL_E, thread_enabled_mask); + return thread_enabled_mask; +} + +static uint32_t clear_enable_mask(CPUHexagonState *env) +{ + HexagonCPU *cpu; + uint32_t modectl, thread_enabled_mask; + + g_assert(bql_locked()); + + cpu = env_archcpu(env); + if (!cpu->globalregs) { + return 0; + } + modectl = + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_MODECTL, + env->threadId); + thread_enabled_mask = GET_FIELD(MODECTL_E, modectl); + thread_enabled_mask &= ~(0x1 << env->threadId); + SET_SYSTEM_FIELD(env, HEX_SREG_MODECTL, MODECTL_E, thread_enabled_mask); + return thread_enabled_mask; +} +static void do_start_thread(CPUState *cs, run_on_cpu_data tbd) +{ + CPUHexagonState *env; + + BQL_LOCK_GUARD(); + + env = cpu_env(cs); + + hexagon_cpu_soft_reset(env); + + set_enable_mask(env); + + cs->halted = 0; + cs->exception_index = HEX_EVENT_NONE; + cpu_resume(cs); +} + +void hexagon_start_threads(CPUHexagonState *current_env, uint32_t mask) +{ + CPUState *cs; + CPU_FOREACH(cs) { + CPUHexagonState *env = cpu_env(cs); + if (!(mask & (0x1 << env->threadId))) { + continue; + } + + if (current_env->threadId != env->threadId) { + async_safe_run_on_cpu(cs, do_start_thread, RUN_ON_CPU_NULL); + } + } +} + +/* + * When we have all threads stopped, the return + * value to the shell is register 2 from thread 0. + */ +static uint32_t get_thread0_r2(void) +{ + CPUState *cs; + CPU_FOREACH(cs) { + CPUHexagonState *thread = cpu_env(cs); + if (thread->threadId == 0) { + return thread->gpr[2]; + } + } + g_assert_not_reached(); +} + +void hexagon_stop_thread(CPUHexagonState *env) +{ + uint32_t thread_enabled_mask; + CPUState *cs; + + BQL_LOCK_GUARD(); + + thread_enabled_mask = clear_enable_mask(env); + cs = env_cpu(env); + cpu_interrupt(cs, CPU_INTERRUPT_HALT); + if (!thread_enabled_mask) { + /* All threads are stopped, request shutdown */ + qemu_system_shutdown_request_with_code( + SHUTDOWN_CAUSE_GUEST_SHUTDOWN, get_thread0_r2()); + } +} + +static int sys_in_monitor_mode_ssr(uint32_t ssr) +{ + if ((GET_SSR_FIELD(SSR_EX, ssr) != 0) || + ((GET_SSR_FIELD(SSR_EX, ssr) == 0) && + (GET_SSR_FIELD(SSR_UM, ssr) == 0))) { + return 1; + } + return 0; +} + +static int sys_in_guest_mode_ssr(uint32_t ssr) +{ + if ((GET_SSR_FIELD(SSR_EX, ssr) == 0) && + (GET_SSR_FIELD(SSR_UM, ssr) != 0) && + (GET_SSR_FIELD(SSR_GM, ssr) != 0)) { + return 1; + } + return 0; +} + +static int sys_in_user_mode_ssr(uint32_t ssr) +{ + if ((GET_SSR_FIELD(SSR_EX, ssr) == 0) && + (GET_SSR_FIELD(SSR_UM, ssr) != 0) && + (GET_SSR_FIELD(SSR_GM, ssr) == 0)) { + return 1; + } + return 0; +} + +int get_cpu_mode(CPUHexagonState *env) +{ + uint32_t ssr = env->t_sreg[HEX_SREG_SSR]; + + if (sys_in_monitor_mode_ssr(ssr)) { + return HEX_CPU_MODE_MONITOR; + } else if (sys_in_guest_mode_ssr(ssr)) { + return HEX_CPU_MODE_GUEST; + } else if (sys_in_user_mode_ssr(ssr)) { + return HEX_CPU_MODE_USER; + } + return HEX_CPU_MODE_MONITOR; +} diff --git a/target/hexagon/cpu_helper.h b/target/hexagon/cpu_helper.h new file mode 100644 index 0000000000..d176750315 --- /dev/null +++ b/target/hexagon/cpu_helper.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HEXAGON_CPU_HELPER_H +#define HEXAGON_CPU_HELPER_H + +uint32_t hexagon_get_pmu_counter(CPUHexagonState *cur_env, int index); +void hexagon_modify_ssr(CPUHexagonState *env, uint32_t new, uint32_t old); +int get_cpu_mode(CPUHexagonState *env); +int get_exe_mode(CPUHexagonState *env); +void clear_wait_mode(CPUHexagonState *env); +void hexagon_ssr_set_cause(CPUHexagonState *env, uint32_t cause); +void hexagon_start_threads(CPUHexagonState *env, uint32_t mask); +void hexagon_stop_thread(CPUHexagonState *env); +void hexagon_resume_threads(CPUHexagonState *env, uint32_t mask); +uint64_t hexagon_get_sys_pcycle_count(CPUHexagonState *env); +uint32_t hexagon_get_sys_pcycle_count_high(CPUHexagonState *env); +uint32_t hexagon_get_sys_pcycle_count_low(CPUHexagonState *env); +void hexagon_set_sys_pcycle_count_high(CPUHexagonState *env, uint32_t val); +void hexagon_set_sys_pcycle_count_low(CPUHexagonState *env, uint32_t val); +void hexagon_set_sys_pcycle_count(CPUHexagonState *env, uint64_t val); + +#endif diff --git a/target/hexagon/decode.c b/target/hexagon/decode.c index 15954518b8..6eddcca26e 100644 --- a/target/hexagon/decode.c +++ b/target/hexagon/decode.c @@ -185,6 +185,8 @@ static bool decode_opcode_can_jump(int opcode) if ((GET_ATTRIB(opcode, A_JUMP)) || (GET_ATTRIB(opcode, A_CALL)) || (opcode == J2_trap0) || + (opcode == J2_trap1) || + (opcode == J2_rte) || (opcode == J2_pause)) { /* Exception to A_JUMP attribute */ if (opcode == J4_hintjumpr) { @@ -363,6 +365,18 @@ static void decode_shuffle_for_execution(Packet *packet) break; } } + /* + * And at the very very very end, move any RTE's, since they update + * user/supervisor mode. + */ +#if !defined(CONFIG_USER_ONLY) + for (i = 0; i < last_insn; i++) { + if (packet->insn[i].opcode == J2_rte) { + decode_send_insn_to(packet, i, last_insn); + break; + } + } +#endif } static void diff --git a/target/hexagon/gen_analyze_funcs.py b/target/hexagon/gen_analyze_funcs.py index fdefd5b4b3..44bb22ed92 100755 --- a/target/hexagon/gen_analyze_funcs.py +++ b/target/hexagon/gen_analyze_funcs.py @@ -22,7 +22,6 @@ import re import string import hex_common - ## ## Generate the code to analyze the instruction ## For A2_add: Rd32=add(Rs32,Rt32), { RdV=RsV+RtV;} @@ -42,6 +41,13 @@ def gen_analyze_func(f, tag, regs, imms): f.write(f"static void analyze_{tag}(DisasContext *ctx)\n") f.write("{\n") + if hex_common.tag_ignore(tag): + f.write("}\n\n") + return + + if hex_common.is_sysemu_tag(tag): + f.write("#ifndef CONFIG_USER_ONLY\n") + f.write(" Insn *insn G_GNUC_UNUSED = ctx->insn;\n") if (hex_common.is_hvx_insn(tag)): if hex_common.has_hvx_helper(tag): @@ -58,7 +64,8 @@ def gen_analyze_func(f, tag, regs, imms): for regno, register in enumerate(regs): reg_type, reg_id = register reg = hex_common.get_register(tag, reg_type, reg_id) - reg.decl_reg_num(f, regno) + if reg.is_read() or reg.is_written(): + reg.decl_reg_num(f, regno) ## Analyze the register reads for regno, register in enumerate(regs): @@ -78,6 +85,9 @@ def gen_analyze_func(f, tag, regs, imms): f.write(" mark_implicit_writes(ctx);\n") + if hex_common.is_sysemu_tag(tag): + f.write("#endif /* !CONFIG_USER_ONLY */\n") + f.write("}\n\n") diff --git a/target/hexagon/gen_helper_funcs.py b/target/hexagon/gen_helper_funcs.py index 6206104424..1629ebc0e1 100755 --- a/target/hexagon/gen_helper_funcs.py +++ b/target/hexagon/gen_helper_funcs.py @@ -104,29 +104,23 @@ def main(): tagimms = hex_common.get_tagimms() with open(args.out, "w") as f: - for tag in hex_common.tags: - ## Skip the priv instructions - if "A_PRIV" in hex_common.attribdict[tag]: + for tag in hex_common.get_user_tags(): + if hex_common.tag_ignore(tag): continue - ## Skip the guest instructions - if "A_GUEST" in hex_common.attribdict[tag]: - continue - ## Skip the floating point instructions - if "A_FPOP" in hex_common.attribdict[tag]: - continue - ## Skip the diag instructions - if tag == "Y6_diag": - continue - if tag == "Y6_diag0": + if hex_common.skip_qemu_helper(tag): continue - if tag == "Y6_diag1": + if hex_common.is_idef_parser_enabled(tag): continue + gen_helper_function(f, tag, tagregs, tagimms) + + f.write("#if !defined(CONFIG_USER_ONLY)\n") + for tag in hex_common.get_sys_tags(): if hex_common.skip_qemu_helper(tag): continue if hex_common.is_idef_parser_enabled(tag): continue - gen_helper_function(f, tag, tagregs, tagimms) + f.write("#endif\n") if __name__ == "__main__": diff --git a/target/hexagon/gen_helper_protos.py b/target/hexagon/gen_helper_protos.py index 77f8e0a6a3..59c8bdd05c 100755 --- a/target/hexagon/gen_helper_protos.py +++ b/target/hexagon/gen_helper_protos.py @@ -59,27 +59,28 @@ def main(): tagimms = hex_common.get_tagimms() with open(args.out, "w") as f: - for tag in hex_common.tags: - ## Skip the priv instructions - if "A_PRIV" in hex_common.attribdict[tag]: + for tag in hex_common.get_user_tags(): + if hex_common.tag_ignore(tag): continue - ## Skip the guest instructions - if "A_GUEST" in hex_common.attribdict[tag]: - continue - ## Skip the diag instructions - if tag == "Y6_diag": - continue - if tag == "Y6_diag0": + + if hex_common.skip_qemu_helper(tag): continue - if tag == "Y6_diag1": + if hex_common.is_idef_parser_enabled(tag): continue + gen_helper_prototype(f, tag, tagregs, tagimms) + + f.write("#if !defined(CONFIG_USER_ONLY)\n") + for tag in hex_common.get_sys_tags(): + if hex_common.tag_ignore(tag): + continue if hex_common.skip_qemu_helper(tag): continue if hex_common.is_idef_parser_enabled(tag): continue gen_helper_prototype(f, tag, tagregs, tagimms) + f.write("#endif\n") if __name__ == "__main__": diff --git a/target/hexagon/gen_idef_parser_funcs.py b/target/hexagon/gen_idef_parser_funcs.py index 2f6e826f76..32bce9b002 100644 --- a/target/hexagon/gen_idef_parser_funcs.py +++ b/target/hexagon/gen_idef_parser_funcs.py @@ -60,6 +60,8 @@ def main(): f.write('#include "macros.h.inc"\n\n') for tag in hex_common.tags: + if hex_common.tag_ignore(tag): + continue ## Skip the priv instructions if "A_PRIV" in hex_common.attribdict[tag]: continue diff --git a/target/hexagon/gen_op_attribs.py b/target/hexagon/gen_op_attribs.py index bbbb02df3a..94dd1f876b 100755 --- a/target/hexagon/gen_op_attribs.py +++ b/target/hexagon/gen_op_attribs.py @@ -38,7 +38,7 @@ def main(): ## Generate all the attributes associated with each instruction ## with open(args.out, "w") as f: - for tag in hex_common.tags: + for tag in hex_common.get_all_tags(): f.write( f"OP_ATTRIB({tag},ATTRIBS(" f'{",".join(sorted(hex_common.attribdict[tag]))}))\n' diff --git a/target/hexagon/gen_opcodes_def.py b/target/hexagon/gen_opcodes_def.py index 94a19ff412..17ba3f9db9 100755 --- a/target/hexagon/gen_opcodes_def.py +++ b/target/hexagon/gen_opcodes_def.py @@ -37,7 +37,10 @@ def main(): ## Generate a list of all the opcodes ## with open(args.out, "w") as f: - for tag in hex_common.tags: + for tag in hex_common.get_user_tags(): + f.write(f"OPCODE({tag}),\n") + + for tag in hex_common.get_sys_tags(): f.write(f"OPCODE({tag}),\n") diff --git a/target/hexagon/gen_tcg.h b/target/hexagon/gen_tcg.h index 0159e5c2d5..a28af0c245 100644 --- a/target/hexagon/gen_tcg.h +++ b/target/hexagon/gen_tcg.h @@ -488,6 +488,7 @@ /* dczeroa clears the 32 byte cache line at the address given */ #define fGEN_TCG_Y2_dczeroa(SHORTCODE) SHORTCODE +#define fGEN_TCG_Y2_dczeroa_nt(SHORTCODE) SHORTCODE /* In linux-user mode, these are not modelled, suppress compiler warning */ #define fGEN_TCG_Y2_dcinva(SHORTCODE) \ @@ -1112,6 +1113,9 @@ RdV, tcg_constant_tl(0)); \ } while (0) +#define fGEN_TCG_Y2_break(SHORTCODE) +#define fGEN_TCG_J2_unpause(SHORTCODE) + #define fGEN_TCG_J2_pause(SHORTCODE) \ do { \ uiV = uiV; \ @@ -1321,6 +1325,11 @@ RsV = RsV; \ uiV = uiV; \ } while (0) +#define fGEN_TCG_Y2_dcfetchbo_nt(SHORTCODE) \ + do { \ + RsV = RsV; \ + uiV = uiV; \ + } while (0) #define fGEN_TCG_L2_loadw_aq(SHORTCODE) SHORTCODE #define fGEN_TCG_L4_loadd_aq(SHORTCODE) SHORTCODE @@ -1340,13 +1349,6 @@ #define fGEN_TCG_S2_storew_rl_st_vi(SHORTCODE) SHORTCODE #define fGEN_TCG_S4_stored_rl_st_vi(SHORTCODE) SHORTCODE -#define fGEN_TCG_J2_trap0(SHORTCODE) \ - do { \ - uiV = uiV; \ - tcg_gen_movi_tl(hex_gpr[HEX_REG_PC], ctx->pkt.pc); \ - TCGv excp = tcg_constant_tl(HEX_EVENT_TRAP0); \ - gen_helper_raise_exception(tcg_env, excp); \ - } while (0) #endif #define fGEN_TCG_A2_nop(SHORTCODE) do { } while (0) diff --git a/target/hexagon/gen_tcg_funcs.py b/target/hexagon/gen_tcg_funcs.py index e7f90a0da1..6d5d99cee3 100755 --- a/target/hexagon/gen_tcg_funcs.py +++ b/target/hexagon/gen_tcg_funcs.py @@ -21,7 +21,7 @@ import sys import re import string import hex_common - +from textwrap import dedent ## ## Generate the TCG code to call the helper @@ -49,6 +49,18 @@ def gen_tcg_func(f, tag, regs, imms): f.write(" Insn *insn G_GNUC_UNUSED = ctx->insn;\n") + if "A_PRIV" in hex_common.attribdict[tag]: + f.write(dedent("""\ +#ifdef CONFIG_USER_ONLY + hex_gen_exception_end_tb(ctx, HEX_CAUSE_PRIV_USER_NO_SINSN); +#else +""")) + if "A_GUEST" in hex_common.attribdict[tag]: + f.write(dedent("""\ +#ifdef CONFIG_USER_ONLY + hex_gen_exception_end_tb(ctx, HEX_CAUSE_PRIV_USER_NO_GINSN); +#else +""")) if hex_common.need_ea(tag): f.write(" TCGv EA G_GNUC_UNUSED = tcg_temp_new();\n") @@ -100,6 +112,11 @@ def gen_tcg_func(f, tag, regs, imms): if reg.is_written(): reg.gen_write(f, tag) + if ( + "A_PRIV" in hex_common.attribdict[tag] + or "A_GUEST" in hex_common.attribdict[tag] + ): + f.write("#endif /* CONFIG_USER_ONLY */\n") f.write("}\n\n") @@ -124,18 +141,10 @@ def main(): f.write('#include "idef-generated-emitter.h.inc"\n\n') for tag in hex_common.tags: - ## Skip the priv instructions - if "A_PRIV" in hex_common.attribdict[tag]: - continue - ## Skip the guest instructions - if "A_GUEST" in hex_common.attribdict[tag]: - continue - ## Skip the diag instructions - if tag == "Y6_diag": - continue - if tag == "Y6_diag0": - continue - if tag == "Y6_diag1": + if hex_common.tag_ignore(tag): + f.write(f"static void generate_{tag}" + f"(DisasContext *ctx)\n") + f.write("{\n}\n\n") continue gen_def_tcg_func(f, tag, tagregs, tagimms) diff --git a/target/hexagon/gen_tcg_sys.h b/target/hexagon/gen_tcg_sys.h new file mode 100644 index 0000000000..af5032214c --- /dev/null +++ b/target/hexagon/gen_tcg_sys.h @@ -0,0 +1,139 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HEXAGON_GEN_TCG_SYS_H +#define HEXAGON_GEN_TCG_SYS_H + +/* System mode instructions */ +#define fGEN_TCG_Y2_swi(SHORTCODE) \ + gen_helper_swi(tcg_env, RsV) + +#define fGEN_TCG_Y2_cswi(SHORTCODE) \ + gen_helper_cswi(tcg_env, RsV) + +#define fGEN_TCG_Y2_ciad(SHORTCODE) \ + gen_helper_ciad(tcg_env, RsV) + +#define fGEN_TCG_Y4_siad(SHORTCODE) \ + gen_helper_siad(tcg_env, RsV) + +#define fGEN_TCG_Y2_iassignw(SHORTCODE) \ + gen_helper_iassignw(tcg_env, RsV) + +#define fGEN_TCG_Y2_iassignr(SHORTCODE) \ + gen_helper_iassignr(RdV, tcg_env, RsV) + +#define fGEN_TCG_Y2_getimask(SHORTCODE) \ + gen_helper_getimask(RdV, tcg_env, RsV) + +#define fGEN_TCG_Y2_setimask(SHORTCODE) \ + gen_helper_setimask(tcg_env, PtV, RsV) + +#define fGEN_TCG_Y2_setprio(SHORTCODE) \ + gen_helper_setprio(tcg_env, PtV, RsV) + +#define fGEN_TCG_Y2_crswap0(SHORTCODE) \ + do { \ + TCGv_i32 tmp = tcg_temp_new_i32(); \ + tcg_gen_mov_tl(tmp, RxV); \ + tcg_gen_mov_tl(RxV, hex_t_sreg[HEX_SREG_SGP0]); \ + if (ctx->need_commit) { \ + tcg_gen_mov_tl(ctx->t_sreg_new_value[HEX_SREG_SGP0], tmp); \ + } else { \ + tcg_gen_mov_tl(hex_t_sreg[HEX_SREG_SGP0], tmp); \ + } \ + } while (0) + +#define fGEN_TCG_Y4_crswap1(SHORTCODE) \ + do { \ + TCGv_i32 tmp = tcg_temp_new_i32(); \ + tcg_gen_mov_tl(tmp, RxV); \ + tcg_gen_mov_tl(RxV, hex_t_sreg[HEX_SREG_SGP1]); \ + if (ctx->need_commit) { \ + tcg_gen_mov_tl(ctx->t_sreg_new_value[HEX_SREG_SGP1], tmp); \ + } else { \ + tcg_gen_mov_tl(hex_t_sreg[HEX_SREG_SGP1], tmp); \ + } \ + } while (0) + +#define fGEN_TCG_Y4_crswap10(SHORTCODE) \ + do { \ + TCGv_i64 tmp = tcg_temp_new_i64(); \ + tcg_gen_mov_i64(tmp, RxxV); \ + tcg_gen_concat_i32_i64(RxxV, \ + hex_t_sreg[HEX_SREG_SGP0], \ + hex_t_sreg[HEX_SREG_SGP1]); \ + if (ctx->need_commit) { \ + tcg_gen_extrl_i64_i32( \ + ctx->t_sreg_new_value[HEX_SREG_SGP0], tmp); \ + tcg_gen_extrh_i64_i32( \ + ctx->t_sreg_new_value[HEX_SREG_SGP1], tmp); \ + } else { \ + tcg_gen_extrl_i64_i32(hex_t_sreg[HEX_SREG_SGP0], tmp); \ + tcg_gen_extrh_i64_i32(hex_t_sreg[HEX_SREG_SGP1], tmp); \ + } \ + } while (0) + +#define fGEN_TCG_Y2_wait(SHORTCODE) \ + do { \ + RsV = RsV; \ + gen_helper_wait(tcg_env, tcg_constant_tl(ctx->pkt.pc)); \ + } while (0) + +#define fGEN_TCG_Y2_resume(SHORTCODE) \ + gen_helper_resume(tcg_env, RsV) + +#define fGEN_TCG_Y2_start(SHORTCODE) \ + gen_helper_start(tcg_env, RsV) + +#define fGEN_TCG_Y2_stop(SHORTCODE) \ + do { \ + RsV = RsV; \ + gen_helper_stop(tcg_env); \ + } while (0) + +#define fGEN_TCG_Y2_tfrscrr(SHORTCODE) \ + tcg_gen_mov_tl(RdV, SsV) + +#define fGEN_TCG_Y2_tfrsrcr(SHORTCODE) \ + tcg_gen_mov_tl(SdV, RsV) + +#define fGEN_TCG_Y4_tfrscpp(SHORTCODE) \ + tcg_gen_mov_i64(RddV, SssV) + +#define fGEN_TCG_Y4_tfrspcp(SHORTCODE) \ + tcg_gen_mov_i64(SddV, RssV) + +#define fGEN_TCG_G4_tfrgcrr(SHORTCODE) \ + tcg_gen_mov_tl(RdV, GsV) + +#define fGEN_TCG_G4_tfrgrcr(SHORTCODE) \ + tcg_gen_mov_tl(GdV, RsV) + +#define fGEN_TCG_G4_tfrgcpp(SHORTCODE) \ + tcg_gen_mov_i64(RddV, GssV) + +#define fGEN_TCG_G4_tfrgpcp(SHORTCODE) \ + tcg_gen_mov_i64(GddV, RssV) + + +/* + * rte (return from exception) + * Clear the EX bit in SSR + * Jump to ELR + */ +#define fGEN_TCG_J2_rte(SHORTCODE) \ + do { \ + TCGv_i32 new_ssr = tcg_temp_new_i32(); \ + tcg_gen_deposit_tl(new_ssr, hex_t_sreg[HEX_SREG_SSR], \ + tcg_constant_tl(0), \ + reg_field_info[SSR_EX].offset, \ + reg_field_info[SSR_EX].width); \ + gen_log_sreg_write(ctx, HEX_SREG_SSR, new_ssr); \ + gen_jumpr(ctx, hex_t_sreg[HEX_SREG_ELR]); \ + } while (0) + +#endif diff --git a/target/hexagon/genptr.c b/target/hexagon/genptr.c index 5d5adace4b..1ebc747140 100644 --- a/target/hexagon/genptr.c +++ b/target/hexagon/genptr.c @@ -23,6 +23,7 @@ #include "exec/helper-gen.h" #include "insn.h" #include "opcodes.h" +#include "sys_macros.h" #include "translate.h" #define QEMU_GENERATE /* Used internally by macros.h */ #include "macros.h" @@ -30,6 +31,10 @@ #undef QEMU_GENERATE #include "gen_tcg.h" #include "gen_tcg_hvx.h" +#ifndef CONFIG_USER_ONLY +#include "gen_tcg_sys.h" +#endif + #include "genptr.h" TCGv gen_read_reg(TCGv result, int num) @@ -120,6 +125,156 @@ TCGv get_result_pred(DisasContext *ctx, int pnum) } } +#ifndef CONFIG_USER_ONLY +G_GNUC_UNUSED +static bool greg_writable(int rnum, bool pair) +{ + if (pair) { + if (rnum < HEX_GREG_G3) { + return true; + } + qemu_log_mask(LOG_UNIMP, + "Warning: ignoring write to guest register pair G%d:%d\n", + rnum + 1, rnum); + } else { + if (rnum <= HEX_GREG_G3) { + return true; + } + qemu_log_mask(LOG_UNIMP, + "Warning: ignoring write to guest register G%d\n", rnum); + } + return false; +} + +G_GNUC_UNUSED +static void check_greg_impl(int rnum, bool pair) +{ + if (pair && (!greg_implemented(rnum) || !greg_implemented(rnum + 1))) { + qemu_log_mask(LOG_UNIMP, + "Warning: guest register pair G%d:%d is unimplemented or " + "reserved. Read will yield 0.\n", + rnum + 1, rnum); + } else if (!pair && !greg_implemented(rnum)) { + qemu_log_mask(LOG_UNIMP, + "Warning: guest register G%d is unimplemented or reserved." + " Read will yield 0.\n", rnum); + } +} + +G_GNUC_UNUSED +static inline void gen_log_greg_write(DisasContext *ctx, int rnum, TCGv_i32 val) +{ + tcg_gen_mov_i32(ctx->greg_new_value[rnum], val); +} + +G_GNUC_UNUSED +static void gen_log_greg_write_pair(DisasContext *ctx, int rnum, TCGv_i64 val) +{ + TCGv_i32 val32 = tcg_temp_new_i32(); + + /* Low word */ + tcg_gen_extrl_i64_i32(val32, val); + gen_log_greg_write(ctx, rnum, val32); + + /* High word */ + tcg_gen_extrh_i64_i32(val32, val); + gen_log_greg_write(ctx, rnum + 1, val32); +} + +static const uint32_t sreg_immut_masks[NUM_SREGS] = { + [HEX_SREG_STID] = 0xff00ff00, + [HEX_SREG_ELR] = 0x00000003, + [HEX_SREG_SSR] = 0x00008000, + [HEX_SREG_CCR] = 0x10e0ff24, + [HEX_SREG_HTID] = IMMUTABLE, + [HEX_SREG_IMASK] = 0xffff0000, + [HEX_SREG_GEVB] = 0x000000ff, +}; + +G_GNUC_UNUSED +static void gen_log_sreg_write(DisasContext *ctx, int rnum, TCGv_i32 val) +{ + const uint32_t reg_mask = sreg_immut_masks[rnum]; + + if (reg_mask != IMMUTABLE) { + if (rnum < HEX_SREG_GLB_START) { + gen_masked_reg_write(val, hex_t_sreg[rnum], reg_mask); + if (ctx->need_commit || rnum == HEX_SREG_SSR) { + tcg_gen_mov_i32(ctx->t_sreg_new_value[rnum], val); + } else { + tcg_gen_mov_i32(hex_t_sreg[rnum], val); + } + } else { + gen_helper_sreg_write_masked(tcg_env, tcg_constant_i32(rnum), val); + } + } +} + +G_GNUC_UNUSED +static void gen_log_sreg_write_pair(DisasContext *ctx, int rnum, TCGv_i64 val) +{ + TCGv_i32 val32 = tcg_temp_new_i32(); + + /* Low word */ + tcg_gen_extrl_i64_i32(val32, val); + gen_log_sreg_write(ctx, rnum, val32); + + /* High word */ + tcg_gen_extrh_i64_i32(val32, val); + gen_log_sreg_write(ctx, rnum + 1, val32); +} + +G_GNUC_UNUSED +static void gen_read_sreg(TCGv_i32 dst, int reg_num) +{ + if (reg_num >= HEX_SREG_GLB_START || reg_num == HEX_SREG_BADVA) { + gen_helper_sreg_read(dst, tcg_env, tcg_constant_i32(reg_num)); + } else { + tcg_gen_mov_i32(dst, hex_t_sreg[reg_num]); + } +} + +G_GNUC_UNUSED +static void gen_read_sreg_pair(TCGv_i64 dst, int reg_num) +{ + if (reg_num < HEX_SREG_GLB_START) { + if (reg_num + 1 == HEX_SREG_BADVA) { + TCGv_i32 badva = tcg_temp_new_i32(); + gen_helper_sreg_read(badva, tcg_env, + tcg_constant_i32(HEX_SREG_BADVA)); + tcg_gen_concat_i32_i64(dst, hex_t_sreg[reg_num], badva); + } else { + tcg_gen_concat_i32_i64(dst, hex_t_sreg[reg_num], + hex_t_sreg[reg_num + 1]); + } + } else { + gen_helper_sreg_read_pair(dst, tcg_env, tcg_constant_i32(reg_num)); + } +} + +G_GNUC_UNUSED +static void gen_read_greg(TCGv_i32 dst, int reg_num) +{ + if (reg_num <= HEX_GREG_G3) { + tcg_gen_mov_i32(dst, hex_greg[reg_num]); + } else { + gen_helper_greg_read(dst, tcg_env, tcg_constant_i32(reg_num)); + } +} + +G_GNUC_UNUSED +static void gen_read_greg_pair(TCGv_i64 dst, int reg_num) +{ + if (reg_num == HEX_GREG_G0 || reg_num == HEX_GREG_G2) { + tcg_gen_concat_i32_i64(dst, hex_greg[reg_num], + hex_greg[reg_num + 1]); + } else { + gen_helper_greg_read_pair(dst, tcg_env, tcg_constant_i32(reg_num)); + } +} +#endif + + void gen_pred_write(DisasContext *ctx, int pnum, TCGv val) { TCGv pred = get_result_pred(ctx, pnum); @@ -738,26 +893,30 @@ static void gen_load_frame(DisasContext *ctx, TCGv_i64 frame, TCGv EA) tcg_gen_qemu_ld_i64(frame, EA, ctx->mem_idx, MO_LE | MO_UQ); } -#ifndef CONFIG_HEXAGON_IDEF_PARSER /* Stack overflow check */ -static void gen_framecheck(TCGv EA, int framesize) +void gen_framecheck(DisasContext *ctx, TCGv_i32 addr, TCGv_i32 ea) { - /* Not modelled in linux-user mode */ - /* Placeholder for system mode */ #ifndef CONFIG_USER_ONLY - g_assert_not_reached(); + TCGLabel *ok = gen_new_label(); + tcg_gen_brcond_i32(TCG_COND_GEU, addr, hex_gpr[HEX_REG_FRAMELIMIT], ok); + gen_helper_raise_stack_overflow(tcg_env, + tcg_constant_i32(ctx->insn->slot), ea); + gen_set_label(ok); #endif } +#ifndef CONFIG_HEXAGON_IDEF_PARSER static void gen_allocframe(DisasContext *ctx, TCGv r29, int framesize) { TCGv r30 = get_result_gpr(ctx, HEX_REG_FP); + TCGv_i32 new_r29 = tcg_temp_new_i32(); TCGv_i64 frame; tcg_gen_addi_tl(r30, r29, -8); frame = gen_frame_scramble(); gen_store8(tcg_env, r30, frame, ctx->insn->slot); - gen_framecheck(r30, framesize); - tcg_gen_subi_tl(r29, r30, framesize); + tcg_gen_subi_tl(new_r29, r30, framesize); + gen_framecheck(ctx, new_r29, hex_gpr[HEX_REG_PC]); + tcg_gen_mov_tl(r29, new_r29); } static void gen_deallocframe(DisasContext *ctx, TCGv_i64 r31_30, TCGv r30) diff --git a/target/hexagon/helper.h b/target/hexagon/helper.h index 64456822bc..033e5619d6 100644 --- a/target/hexagon/helper.h +++ b/target/hexagon/helper.h @@ -18,7 +18,7 @@ #include "internal.h" #include "helper_protos_generated.h.inc" -DEF_HELPER_FLAGS_2(raise_exception, TCG_CALL_NO_RETURN, noreturn, env, i32) +DEF_HELPER_FLAGS_3(raise_exception, TCG_CALL_NO_RETURN, noreturn, env, i32, i32) DEF_HELPER_2(commit_store, void, env, int) DEF_HELPER_3(gather_store, void, env, i32, int) DEF_HELPER_1(commit_hvx_stores, void, env) @@ -107,3 +107,28 @@ DEF_HELPER_4(probe_noshuf_load, void, env, i32, int, int) DEF_HELPER_2(probe_pkt_scalar_store_s0, void, env, int) DEF_HELPER_2(probe_hvx_stores, void, env, int) DEF_HELPER_2(probe_pkt_scalar_hvx_stores, void, env, int) + +#if !defined(CONFIG_USER_ONLY) +DEF_HELPER_3(raise_stack_overflow, void, env, i32, i32) +DEF_HELPER_2(swi, void, env, i32) +DEF_HELPER_2(cswi, void, env, i32) +DEF_HELPER_2(ciad, void, env, i32) +DEF_HELPER_2(siad, void, env, i32) +DEF_HELPER_2(iassignw, void, env, i32) +DEF_HELPER_2(iassignr, i32, env, i32) +DEF_HELPER_2(getimask, i32, env, i32) +DEF_HELPER_3(setimask, void, env, i32, i32) +DEF_HELPER_2(sreg_read, i32, env, i32) +DEF_HELPER_2(sreg_read_pair, i64, env, i32) +DEF_HELPER_2(greg_read, i32, env, i32) +DEF_HELPER_2(greg_read_pair, i64, env, i32) +DEF_HELPER_3(sreg_write_masked, void, env, i32, i32) +DEF_HELPER_3(setprio, void, env, i32, i32) +DEF_HELPER_2(start, void, env, i32) +DEF_HELPER_1(stop, void, env) +DEF_HELPER_2(wait, void, env, i32) +DEF_HELPER_2(resume, void, env, i32) +DEF_HELPER_1(resched, void, env) +DEF_HELPER_3(modify_ssr, void, env, i32, i32) +DEF_HELPER_1(pending_interrupt, void, env) +#endif diff --git a/target/hexagon/hex_common.py b/target/hexagon/hex_common.py index e37d5a514f..d91a653c3d 100755 --- a/target/hexagon/hex_common.py +++ b/target/hexagon/hex_common.py @@ -33,6 +33,41 @@ tags = [] # list of all tags overrides = {} # tags with helper overrides idef_parser_enabled = {} # tags enabled for idef-parser + +def is_sysemu_tag(tag): + return bool(attribdict[tag] & {"A_PRIV", "A_GUEST"}) + + +def tag_ignore(tag): + tag_skips = ( + "Y6_diag", + "Y6_diag0", + "Y6_diag1", + ) + attr_skips = { + "A_FAKEINSN", + "A_MAPPING", + "A_CONDMAPPING", + } + return tag in tag_skips or attribdict[tag] & attr_skips + + +def get_sys_tags(): + return sorted( + tag for tag in frozenset(tags) if is_sysemu_tag(tag) + ) + + +def get_user_tags(): + return sorted( + tag for tag in frozenset(tags) if not is_sysemu_tag(tag) + ) + + +def get_all_tags(): + return get_user_tags() + get_sys_tags() + + # We should do this as a hash for performance, # but to keep order let's keep it as a list. def uniquify(seq): @@ -93,8 +128,11 @@ def calculate_attribs(): add_qemu_macro_attrib("fTRAP", "A_IMPLICIT_READS_PC") add_qemu_macro_attrib("fSET_OVERFLOW", "A_IMPLICIT_WRITES_USR") add_qemu_macro_attrib("fSET_LPCFG", "A_IMPLICIT_WRITES_USR") + add_qemu_macro_attrib("fCLEAR_RTE_EX", "A_IMPLICIT_WRITES_SSR") add_qemu_macro_attrib("fLOAD", "A_SCALAR_LOAD") add_qemu_macro_attrib("fSTORE", "A_SCALAR_STORE") + add_qemu_macro_attrib("fSET_K0_LOCK", "A_IMPLICIT_READS_PC") + add_qemu_macro_attrib("fSET_TLB_LOCK", "A_IMPLICIT_READS_PC") add_qemu_macro_attrib('fLSBNEW0', 'A_IMPLICIT_READS_P0') add_qemu_macro_attrib('fLSBNEW0NOT', 'A_IMPLICIT_READS_P0') add_qemu_macro_attrib('fREAD_P0', 'A_IMPLICIT_READS_P0') @@ -215,7 +253,9 @@ def need_env(tag): "A_LOAD" in attribdict[tag] or "A_CVI_GATHER" in attribdict[tag] or "A_CVI_SCATTER" in attribdict[tag] or - "A_IMPLICIT_WRITES_USR" in attribdict[tag]) + "A_IMPLICIT_WRITES_USR" in attribdict[tag] or + "A_PRIV" in attribdict[tag] or + "J2_trap" in tag) def need_slot(tag): @@ -224,6 +264,9 @@ def need_slot(tag): and "A_CVI_GATHER" not in attribdict[tag] and ("A_STORE" in attribdict[tag] or "A_LOAD" in attribdict[tag]) + and tag != "L4_loadw_phys" + and tag != "L6_memcpy" + and tag != "Y6_dmlink" ): return 1 else: @@ -247,7 +290,9 @@ def need_next_PC(tag): def need_pkt_has_multi_cof(tag): - return "A_COF" in attribdict[tag] + if attribdict[tag] & {"A_JUMP", "A_CALL"}: + return tag != "J4_hintjumpr" + return False def need_pkt_need_commit(tag): @@ -367,12 +412,16 @@ class Single(Scalar): return "s32" def helper_arg_type(self): return "int32_t" + def is_pair(self): + return False class Pair(Scalar): def helper_proto_type(self): return "s64" def helper_arg_type(self): return "int64_t" + def is_pair(self): + return True class Hvx: def is_scalar_reg(self): @@ -1008,6 +1057,125 @@ class QRegReadWrite(Register, Hvx, ReadWrite): ctx_log_qreg_write(ctx, {self.reg_num}, insn_has_hvx_helper); """)) +class GuestRegister(Register): + pass + +class GuestDest(GuestRegister, Single, Dest): + def decl_tcg(self, f, tag, regno): + self.decl_reg_num(f, regno) + f.write(code_fmt(f"""\ + TCGv_i32 {self.reg_tcg()} = tcg_temp_new_i32(); + """)) + def gen_write(self, f, tag): + f.write(code_fmt(f"""\ + gen_log_greg_write(ctx, {self.reg_num}, {self.reg_tcg()}); + """)) + def analyze_write(self, f, tag, regno): + f.write(code_fmt(f"""\ + ctx_log_greg_write(ctx, {self.reg_num}); + """)) + +class GuestSource(GuestRegister, Single, OldSource): + def decl_reg_num(self, f, regno): + f.write(code_fmt(f"""\ + const int {self.reg_num} G_GNUC_UNUSED = insn->regno[{regno}]; + """)) + def decl_tcg(self, f, tag, regno): + self.decl_reg_num(f, regno) + f.write(code_fmt(f"""\ + TCGv_i32 {self.reg_tcg()} = tcg_temp_new_i32(); + gen_read_greg({self.reg_tcg()}, {self.reg_num}); + """)) + def analyze_read(self, f, regno): + pass + +class GuestPairDest(GuestRegister, Pair, Dest): + def decl_tcg(self, f, tag, regno): + self.decl_reg_num(f, regno) + f.write(code_fmt(f"""\ + TCGv_i64 {self.reg_tcg()} = tcg_temp_new_i64(); + """)) + def gen_write(self, f, tag): + f.write(code_fmt(f"""\ + gen_log_greg_write_pair(ctx, {self.reg_num}, {self.reg_tcg()}); + """)) + def analyze_write(self, f, tag, regno): + f.write(code_fmt(f"""\ + ctx_log_greg_write_pair(ctx, {self.reg_num}); + """)) + +class GuestPairSource(GuestRegister, Pair, OldSource): + def decl_reg_num(self, f, regno): + f.write(code_fmt(f"""\ + const int {self.reg_num} G_GNUC_UNUSED = insn->regno[{regno}]; + """)) + def decl_tcg(self, f, tag, regno): + self.decl_reg_num(f, regno) + f.write(code_fmt(f"""\ + TCGv_i64 {self.reg_tcg()} = tcg_temp_new_i64(); + gen_read_greg_pair({self.reg_tcg()}, {self.reg_num}); + """)) + def analyze_read(self, f, regno): + pass + +class SystemDest(Register, Single, Dest): + def decl_tcg(self, f, tag, regno): + self.decl_reg_num(f, regno) + f.write(code_fmt(f"""\ + TCGv_i32 {self.reg_tcg()} = tcg_temp_new_i32(); + """)) + def gen_write(self, f, tag): + f.write(code_fmt(f"""\ + gen_log_sreg_write(ctx, {self.reg_num}, {self.reg_tcg()}); + """)) + def analyze_write(self, f, tag, regno): + f.write(code_fmt(f"""\ + ctx_log_sreg_write(ctx, {self.reg_num}); + """)) + +class SystemSource(Register, Single, OldSource): + def decl_reg_num(self, f, regno): + f.write(code_fmt(f"""\ + const int {self.reg_num} G_GNUC_UNUSED = insn->regno[{regno}]; + """)) + def decl_tcg(self, f, tag, regno): + self.decl_reg_num(f, regno) + f.write(code_fmt(f"""\ + TCGv_i32 {self.reg_tcg()} = tcg_temp_new_i32(); + gen_read_sreg({self.reg_tcg()}, {self.reg_num}); + """)) + def analyze_read(self, f, regno): + pass + +class SystemPairDest(Register, Pair, Dest): + def decl_tcg(self, f, tag, regno): + self.decl_reg_num(f, regno) + f.write(code_fmt(f"""\ + TCGv_i64 {self.reg_tcg()} = tcg_temp_new_i64(); + """)) + def gen_write(self, f, tag): + f.write(code_fmt(f"""\ + gen_log_sreg_write_pair(ctx, {self.reg_num}, {self.reg_tcg()}); + """)) + def analyze_write(self, f, tag, regno): + f.write(code_fmt(f"""\ + ctx_log_sreg_write_pair(ctx, {self.reg_num}); + """)) + +class SystemPairSource(Register, Pair, OldSource): + def decl_reg_num(self, f, regno): + f.write(code_fmt(f"""\ + const int {self.reg_num} G_GNUC_UNUSED = insn->regno[{regno}]; + """)) + def decl_tcg(self, f, tag, regno): + self.decl_reg_num(f, regno) + f.write(code_fmt(f"""\ + TCGv_i64 {self.reg_tcg()} = tcg_temp_new_i64(); + gen_read_sreg_pair({self.reg_tcg()}, {self.reg_num}); + """)) + def analyze_read(self, f, regno): + pass + def init_registers(): regs = { GprDest("R", "d"), @@ -1054,6 +1222,16 @@ def init_registers(): QRegSource("Q", "u"), QRegSource("Q", "v"), QRegReadWrite("Q", "x"), + + # system regs + GuestDest("G", "d"), + GuestSource("G", "s"), + GuestPairDest("G", "dd"), + GuestPairSource("G", "ss"), + SystemDest("S", "d"), + SystemSource("S", "s"), + SystemPairDest("S", "dd"), + SystemPairSource("S", "ss"), } for reg in regs: registers[f"{reg.regtype}{reg.regid}"] = reg @@ -1197,6 +1375,7 @@ def parse_common_args(desc): parser.add_argument("semantics", help="semantics file") parser.add_argument("overrides", help="overrides file") parser.add_argument("overrides_vec", help="vector overrides file") + parser.add_argument("overrides_sys", help="system overrides file") parser.add_argument("out", help="output file") parser.add_argument("--idef-parser", help="file of instructions translated by idef-parser") @@ -1204,6 +1383,7 @@ def parse_common_args(desc): read_semantics_file(args.semantics) read_overrides_file(args.overrides) read_overrides_file(args.overrides_vec) + read_overrides_file(args.overrides_sys) if args.idef_parser: read_idef_parser_enabled_file(args.idef_parser) calculate_attribs() diff --git a/target/hexagon/hex_interrupts.c b/target/hexagon/hex_interrupts.c new file mode 100644 index 0000000000..3534481da2 --- /dev/null +++ b/target/hexagon/hex_interrupts.c @@ -0,0 +1,371 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/main-loop.h" +#include "cpu.h" +#include "cpu_helper.h" +#include "exec/cpu-interrupt.h" +#include "hex_interrupts.h" +#include "macros.h" +#include "sys_macros.h" +#include "system/cpus.h" +#include "hw/hexagon/hexagon_globalreg.h" + +static bool hex_is_qualified_for_int(CPUHexagonState *env, int int_num); + +static bool get_syscfg_gie(CPUHexagonState *env) +{ + HexagonCPU *cpu = env_archcpu(env); + uint32_t syscfg = + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SYSCFG, + env->threadId); + return GET_SYSCFG_FIELD(SYSCFG_GIE, syscfg); +} + +static bool get_ssr_ex(CPUHexagonState *env) +{ + uint32_t ssr = env->t_sreg[HEX_SREG_SSR]; + return GET_SSR_FIELD(SSR_EX, ssr); +} + +static bool get_ssr_ie(CPUHexagonState *env) +{ + uint32_t ssr = env->t_sreg[HEX_SREG_SSR]; + return GET_SSR_FIELD(SSR_IE, ssr); +} + +/* Do these together so we only have to call hexagon_modify_ssr once */ +static void set_ssr_ex_cause(CPUHexagonState *env, int ex, uint32_t cause) +{ + uint32_t old, new; + + old = env->t_sreg[HEX_SREG_SSR]; + SET_SYSTEM_FIELD(env, HEX_SREG_SSR, SSR_EX, ex); + SET_SYSTEM_FIELD(env, HEX_SREG_SSR, SSR_CAUSE, cause); + new = env->t_sreg[HEX_SREG_SSR]; + hexagon_modify_ssr(env, new, old); +} + +static bool get_iad_bit(CPUHexagonState *env, int int_num) +{ + HexagonCPU *cpu = env_archcpu(env); + uint32_t ipendad = + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD, + env->threadId); + uint32_t iad = GET_FIELD(IPENDAD_IAD, ipendad); + return extract32(iad, int_num, 1); +} + +static void set_iad_bit(CPUHexagonState *env, int int_num, int val) +{ + HexagonCPU *cpu = env_archcpu(env); + uint32_t ipendad = + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD, + env->threadId); + uint32_t iad = GET_FIELD(IPENDAD_IAD, ipendad); + iad = deposit32(iad, int_num, 1, val); + fSET_FIELD(ipendad, IPENDAD_IAD, iad); + hexagon_globalreg_write(cpu->globalregs, HEX_SREG_IPENDAD, + ipendad, env->threadId); +} + +static uint32_t get_ipend(CPUHexagonState *env) +{ + HexagonCPU *cpu = env_archcpu(env); + uint32_t ipendad = + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD, + env->threadId); + return GET_FIELD(IPENDAD_IPEND, ipendad); +} + +static inline bool get_ipend_bit(CPUHexagonState *env, int int_num) +{ + HexagonCPU *cpu = env_archcpu(env); + uint32_t ipendad = + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD, + env->threadId); + uint32_t ipend = GET_FIELD(IPENDAD_IPEND, ipendad); + return extract32(ipend, int_num, 1); +} + +static void clear_ipend(CPUHexagonState *env, uint32_t mask) +{ + HexagonCPU *cpu = env_archcpu(env); + uint32_t ipendad = + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD, + env->threadId); + uint32_t ipend = GET_FIELD(IPENDAD_IPEND, ipendad); + ipend &= ~mask; + fSET_FIELD(ipendad, IPENDAD_IPEND, ipend); + hexagon_globalreg_write(cpu->globalregs, HEX_SREG_IPENDAD, + ipendad, env->threadId); +} + +static void set_ipend(CPUHexagonState *env, uint32_t mask) +{ + HexagonCPU *cpu = env_archcpu(env); + uint32_t ipendad = + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD, + env->threadId); + uint32_t ipend = GET_FIELD(IPENDAD_IPEND, ipendad); + ipend |= mask; + fSET_FIELD(ipendad, IPENDAD_IPEND, ipend); + hexagon_globalreg_write(cpu->globalregs, HEX_SREG_IPENDAD, + ipendad, env->threadId); +} + +static void set_ipend_bit(CPUHexagonState *env, int int_num, int val) +{ + HexagonCPU *cpu = env_archcpu(env); + uint32_t ipendad = + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD, + env->threadId); + uint32_t ipend = GET_FIELD(IPENDAD_IPEND, ipendad); + ipend = deposit32(ipend, int_num, 1, val); + fSET_FIELD(ipendad, IPENDAD_IPEND, ipend); + hexagon_globalreg_write(cpu->globalregs, HEX_SREG_IPENDAD, + ipendad, env->threadId); +} + +static bool get_imask_bit(CPUHexagonState *env, int int_num) +{ + uint32_t imask = env->t_sreg[HEX_SREG_IMASK]; + return extract32(imask, int_num, 1); +} + +static uint32_t get_prio(CPUHexagonState *env) +{ + uint32_t stid = env->t_sreg[HEX_SREG_STID]; + return extract32(stid, reg_field_info[STID_PRIO].offset, + reg_field_info[STID_PRIO].width); +} + +static void set_elr(CPUHexagonState *env, uint32_t val) +{ + env->t_sreg[HEX_SREG_ELR] = val; +} + +static bool get_schedcfgen(CPUHexagonState *env) +{ + HexagonCPU *cpu = env_archcpu(env); + uint32_t schedcfg = + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SCHEDCFG, + env->threadId); + return extract32(schedcfg, reg_field_info[SCHEDCFG_EN].offset, + reg_field_info[SCHEDCFG_EN].width); +} + +static bool is_lowest_prio(CPUHexagonState *env, int int_num) +{ + uint32_t my_prio = get_prio(env); + CPUState *cs; + + CPU_FOREACH(cs) { + CPUHexagonState *hex_env = cpu_env(cs); + if (!hex_is_qualified_for_int(hex_env, int_num)) { + continue; + } + + /* Note that lower values indicate *higher* priority */ + if (my_prio < get_prio(hex_env)) { + return false; + } + } + return true; +} + +static bool hex_is_qualified_for_int(CPUHexagonState *env, int int_num) +{ + bool syscfg_gie = get_syscfg_gie(env); + bool iad = get_iad_bit(env, int_num); + bool ssr_ie = get_ssr_ie(env); + bool ssr_ex = get_ssr_ex(env); + bool imask = get_imask_bit(env, int_num); + + return syscfg_gie && !iad && ssr_ie && !ssr_ex && !imask; +} + +static void clear_pending_locks(CPUHexagonState *env) +{ + g_assert(bql_locked()); + if (env->k0_lock_state == HEX_LOCK_WAITING) { + env->k0_lock_state = HEX_LOCK_UNLOCKED; + } + if (env->tlb_lock_state == HEX_LOCK_WAITING) { + env->tlb_lock_state = HEX_LOCK_UNLOCKED; + } +} + +static bool should_not_exec(CPUHexagonState *env) +{ + return (get_exe_mode(env) == HEX_EXE_MODE_WAIT); +} + +static void restore_state(CPUHexagonState *env, bool int_accepted) +{ + CPUState *cs = env_cpu(env); + cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD | CPU_INTERRUPT_SWI); + if (!int_accepted && should_not_exec(env)) { + cpu_interrupt(cs, CPU_INTERRUPT_HALT); + } +} + +static void hex_accept_int(CPUHexagonState *env, int int_num) +{ + CPUState *cs = env_cpu(env); + HexagonCPU *cpu = env_archcpu(env); + uint32_t evb = + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_EVB, + env->threadId); + const int exe_mode = get_exe_mode(env); + const bool in_wait_mode = exe_mode == HEX_EXE_MODE_WAIT; + + set_ipend_bit(env, int_num, 0); + set_iad_bit(env, int_num, 1); + set_ssr_ex_cause(env, 1, HEX_CAUSE_INT0 | int_num); + cs->exception_index = HEX_EVENT_INT0 + int_num; + env->cause_code = HEX_EVENT_INT0 + int_num; + clear_pending_locks(env); + if (in_wait_mode) { + qemu_log_mask(CPU_LOG_INT, + "%s: thread " TARGET_FMT_ld " resuming, exiting WAIT mode\n", + __func__, env->threadId); + set_elr(env, env->wait_next_pc); + clear_wait_mode(env); + cs->halted = false; + } else if (env->k0_lock_state == HEX_LOCK_WAITING) { + g_assert_not_reached(); + } else { + set_elr(env, env->gpr[HEX_REG_PC]); + } + env->gpr[HEX_REG_PC] = evb | (cs->exception_index << 2); + if (get_ipend(env) == 0) { + restore_state(env, true); + } +} + + +bool hex_check_interrupts(CPUHexagonState *env) +{ + CPUState *cs = env_cpu(env); + bool int_handled = false; + bool ssr_ex = get_ssr_ex(env); + int max_ints = 32; + bool schedcfgen; + + /* Early exit if nothing pending */ + if (get_ipend(env) == 0) { + restore_state(env, false); + return false; + } + + BQL_LOCK_GUARD(); + /* Only check priorities when schedcfgen is set */ + schedcfgen = get_schedcfgen(env); + for (int i = 0; i < max_ints; i++) { + if (!get_iad_bit(env, i) && get_ipend_bit(env, i)) { + bool syscfg_gie, iad, ssr_ie, imask; + + qemu_log_mask(CPU_LOG_INT, + "%s: thread[" TARGET_FMT_ld "] " + "pc = 0x" TARGET_FMT_lx + " found int %d\n", + __func__, env->threadId, + env->gpr[HEX_REG_PC], i); + if (hex_is_qualified_for_int(env, i) && + (!schedcfgen || is_lowest_prio(env, i))) { + qemu_log_mask(CPU_LOG_INT, + "%s: thread[" TARGET_FMT_ld "] int %d handled_\n", + __func__, env->threadId, i); + hex_accept_int(env, i); + int_handled = true; + break; + } + syscfg_gie = get_syscfg_gie(env); + iad = get_iad_bit(env, i); + ssr_ie = get_ssr_ie(env); + imask = get_imask_bit(env, i); + + qemu_log_mask(CPU_LOG_INT, + "%s: thread[" TARGET_FMT_ld "] " + "int %d not handled, qualified: %d, " + "schedcfg_en: %d, low prio %d\n", + __func__, env->threadId, i, + hex_is_qualified_for_int(env, i), schedcfgen, + is_lowest_prio(env, i)); + + qemu_log_mask(CPU_LOG_INT, + "%s: thread[" TARGET_FMT_ld "] " + "int %d not handled, GIE %d, iad %d, " + "SSR:IE %d, SSR:EX: %d, imask bit %d\n", + __func__, env->threadId, i, syscfg_gie, iad, ssr_ie, + ssr_ex, imask); + } + } + + /* + * If we didn't handle the interrupt and it wasn't + * because we were in EX state, then we won't be able + * to execute the interrupt on this CPU unless something + * changes in the CPU state. Clear the interrupt_request bits + * while preserving the IPEND bits, and we can re-assert the + * interrupt_request bit(s) when we execute one of those instructions. + */ + if (!int_handled && !ssr_ex) { + restore_state(env, int_handled); + } else if (int_handled) { + assert(!cs->halted); + } + + return int_handled; +} + +void hex_clear_interrupts(CPUHexagonState *env, uint32_t mask, uint32_t type) +{ + if (mask == 0) { + return; + } + + /* + * Notify all CPUs that the interrupt has happened + */ + BQL_LOCK_GUARD(); + clear_ipend(env, mask); + hex_interrupt_update(env); +} + +void hex_raise_interrupts(CPUHexagonState *env, uint32_t mask, uint32_t type) +{ + g_assert(bql_locked()); + if (mask == 0) { + return; + } + + /* + * Notify all CPUs that the interrupt has happened + */ + set_ipend(env, mask); + hex_interrupt_update(env); +} + +void hex_interrupt_update(CPUHexagonState *env) +{ + CPUState *cs; + + g_assert(bql_locked()); + if (get_ipend(env) != 0) { + CPU_FOREACH(cs) { + CPUHexagonState *hex_env = cpu_env(cs); + const int exe_mode = get_exe_mode(hex_env); + if (exe_mode != HEX_EXE_MODE_OFF) { + cpu_interrupt(cs, CPU_INTERRUPT_SWI); + cpu_resume(cs); + } + } + } +} diff --git a/target/hexagon/hex_interrupts.h b/target/hexagon/hex_interrupts.h new file mode 100644 index 0000000000..6b6f540363 --- /dev/null +++ b/target/hexagon/hex_interrupts.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HEX_INTERRUPTS_H +#define HEX_INTERRUPTS_H + +bool hex_check_interrupts(CPUHexagonState *env); +void hex_clear_interrupts(CPUHexagonState *env, uint32_t mask, uint32_t type); +void hex_raise_interrupts(CPUHexagonState *env, uint32_t mask, uint32_t type); +void hex_interrupt_update(CPUHexagonState *env); + +#endif diff --git a/target/hexagon/hex_mmu.c b/target/hexagon/hex_mmu.c new file mode 100644 index 0000000000..81d64a6146 --- /dev/null +++ b/target/hexagon/hex_mmu.c @@ -0,0 +1,270 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/main-loop.h" +#include "qemu/qemu-print.h" +#include "cpu.h" +#include "system/cpus.h" +#include "internal.h" +#include "exec/cpu-interrupt.h" +#include "cpu_helper.h" +#include "exec/cputlb.h" +#include "hex_mmu.h" +#include "macros.h" +#include "sys_macros.h" +#include "hw/hexagon/hexagon_tlb.h" +#include "hw/hexagon/hexagon_globalreg.h" + +static inline void hex_log_tlbw(uint32_t index, uint64_t entry) +{ + qemu_log_mask(CPU_LOG_MMU, + "tlbw[%03" PRIu32 "]: 0x%016" PRIx64 "\n", + index, entry); +} + +void hex_tlbw(CPUHexagonState *env, uint32_t index, uint64_t value) +{ + uint32_t myidx = fTLB_NONPOW2WRAP(fTLB_IDXMASK(index)); + HexagonTLBState *tlb = env_archcpu(env)->tlb; + uint64_t old_entry = hexagon_tlb_read(tlb, myidx); + + bool old_entry_valid = extract64(old_entry, 63, 1); + if (old_entry_valid && hexagon_cpu_mmu_enabled(env)) { + CPUState *cs = env_cpu(env); + tlb_flush(cs); + } + hexagon_tlb_write(tlb, myidx, value); + hex_log_tlbw(myidx, value); +} + +void hex_mmu_on(CPUHexagonState *env) +{ + CPUState *cs = env_cpu(env); + qemu_log_mask(CPU_LOG_MMU, "Hexagon MMU turned on!\n"); + tlb_flush(cs); +} + +void hex_mmu_off(CPUHexagonState *env) +{ + CPUState *cs = env_cpu(env); + qemu_log_mask(CPU_LOG_MMU, "Hexagon MMU turned off!\n"); + tlb_flush(cs); +} + +void hex_mmu_mode_change(CPUHexagonState *env) +{ + qemu_log_mask(CPU_LOG_MMU, "Hexagon mode change!\n"); + CPUState *cs = env_cpu(env); + tlb_flush(cs); +} + +bool hex_tlb_find_match(CPUHexagonState *env, uint32_t VA, + MMUAccessType access_type, hwaddr *PA, int *prot, + uint64_t *size, int32_t *excp, int mmu_idx) +{ + HexagonCPU *cpu = env_archcpu(env); + uint32_t ssr = env->t_sreg[HEX_SREG_SSR]; + uint8_t asid = GET_SSR_FIELD(SSR_ASID, ssr); + int cause_code = 0; + + bool found = hexagon_tlb_find_match(cpu->tlb, asid, VA, access_type, + PA, prot, size, excp, &cause_code, + mmu_idx); + if (cause_code) { + env->cause_code = cause_code; + } + return found; +} + +/* Called from tlbp instruction */ +uint32_t hex_tlb_lookup(CPUHexagonState *env, uint32_t ssr, uint32_t VA) +{ + HexagonCPU *cpu = env_archcpu(env); + uint8_t asid = GET_SSR_FIELD(SSR_ASID, ssr); + int cause_code = 0; + + uint32_t result = hexagon_tlb_lookup(cpu->tlb, asid, VA, &cause_code); + if (cause_code) { + env->cause_code = cause_code; + } + return result; +} + +/* + * Return codes: + * 0 or positive index of match + * -1 multiple matches + * -2 no match + */ +int hex_tlb_check_overlap(CPUHexagonState *env, uint64_t entry, uint64_t index) +{ + HexagonCPU *cpu = env_archcpu(env); + return hexagon_tlb_check_overlap(cpu->tlb, entry, index); +} + +void dump_mmu(Monitor *mon, CPUHexagonState *env) +{ + HexagonCPU *cpu = env_archcpu(env); + hexagon_tlb_dump(mon, cpu->tlb); +} + +static inline void print_thread(const char *str, CPUState *cs) +{ + g_assert(bql_locked()); + CPUHexagonState *thread = cpu_env(cs); + bool is_stopped = cpu_is_stopped(cs); + int exe_mode = get_exe_mode(thread); + hex_lock_state_t lock_state = thread->tlb_lock_state; + qemu_log_mask(CPU_LOG_MMU, + "%s: threadId = %" PRIu32 ": %s," + " exe_mode = %s, tlb_lock_state = %s\n", + str, + thread->threadId, + is_stopped ? "stopped" : "running", + exe_mode == HEX_EXE_MODE_OFF ? "off" : + exe_mode == HEX_EXE_MODE_RUN ? "run" : + exe_mode == HEX_EXE_MODE_WAIT ? "wait" : + exe_mode == HEX_EXE_MODE_DEBUG ? "debug" : + "unknown", + lock_state == HEX_LOCK_UNLOCKED ? "unlocked" : + lock_state == HEX_LOCK_WAITING ? "waiting" : + lock_state == HEX_LOCK_OWNER ? "owner" : + "unknown"); +} + +static inline void print_thread_states(const char *str) +{ + CPUState *cs; + CPU_FOREACH(cs) { + print_thread(str, cs); + } +} + +void hex_tlb_lock(CPUHexagonState *env) +{ + qemu_log_mask(CPU_LOG_MMU, "hex_tlb_lock: " TARGET_FMT_ld "\n", + env->threadId); + BQL_LOCK_GUARD(); + g_assert((env->tlb_lock_count == 0) || (env->tlb_lock_count == 1)); + + HexagonCPU *cpu = env_archcpu(env); + uint32_t syscfg = cpu->globalregs ? + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SYSCFG, + env->threadId) : 0; + uint8_t tlb_lock = GET_SYSCFG_FIELD(SYSCFG_TLBLOCK, syscfg); + if (tlb_lock) { + if (env->tlb_lock_state == HEX_LOCK_QUEUED) { + env->next_PC += 4; + env->tlb_lock_count++; + env->tlb_lock_state = HEX_LOCK_OWNER; + SET_SYSCFG_FIELD(env, SYSCFG_TLBLOCK, 1); + return; + } + if (env->tlb_lock_state == HEX_LOCK_OWNER) { + qemu_log_mask(CPU_LOG_MMU | LOG_GUEST_ERROR, + "Double tlblock at PC: 0x%" PRIx32 + ", thread may hang\n", + env->next_PC); + env->next_PC += 4; + CPUState *cs = env_cpu(env); + cpu_interrupt(cs, CPU_INTERRUPT_HALT); + return; + } + env->tlb_lock_state = HEX_LOCK_WAITING; + CPUState *cs = env_cpu(env); + cpu_interrupt(cs, CPU_INTERRUPT_HALT); + } else { + env->next_PC += 4; + env->tlb_lock_count++; + env->tlb_lock_state = HEX_LOCK_OWNER; + SET_SYSCFG_FIELD(env, SYSCFG_TLBLOCK, 1); + } + + if (qemu_loglevel_mask(CPU_LOG_MMU)) { + qemu_log_mask(CPU_LOG_MMU, "Threads after hex_tlb_lock:\n"); + print_thread_states("\tThread"); + } +} + +void hex_tlb_unlock(CPUHexagonState *env) +{ + BQL_LOCK_GUARD(); + g_assert((env->tlb_lock_count == 0) || (env->tlb_lock_count == 1)); + + /* Nothing to do if the TLB isn't locked by this thread */ + HexagonCPU *cpu = env_archcpu(env); + uint32_t syscfg = cpu->globalregs ? + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SYSCFG, + env->threadId) : 0; + uint8_t tlb_lock = GET_SYSCFG_FIELD(SYSCFG_TLBLOCK, syscfg); + if ((tlb_lock == 0) || + (env->tlb_lock_state != HEX_LOCK_OWNER)) { + qemu_log_mask(LOG_GUEST_ERROR, + "thread %" PRIu32 " attempted to tlbunlock" + " without having the lock, tlb_lock state = %u\n", + env->threadId, (unsigned)env->tlb_lock_state); + g_assert(env->tlb_lock_state != HEX_LOCK_WAITING); + return; + } + + env->tlb_lock_count--; + env->tlb_lock_state = HEX_LOCK_UNLOCKED; + SET_SYSCFG_FIELD(env, SYSCFG_TLBLOCK, 0); + + /* Look for a thread to unlock */ + unsigned int this_threadId = env->threadId; + CPUHexagonState *unlock_thread = NULL; + CPUState *cs; + CPU_FOREACH(cs) { + CPUHexagonState *thread = cpu_env(cs); + + /* + * The hardware implements round-robin fairness, so we look for threads + * starting at env->threadId + 1 and incrementing modulo the number of + * threads. + * + * To implement this, we check if thread is a earlier in the modulo + * sequence than unlock_thread. + * if unlock thread is higher than this thread + * thread must be between this thread and unlock_thread + * else + * thread higher than this thread is ahead of unlock_thread + * thread must be lower then unlock thread + */ + if (thread->tlb_lock_state == HEX_LOCK_WAITING) { + if (!unlock_thread) { + unlock_thread = thread; + } else if (unlock_thread->threadId > this_threadId) { + if (this_threadId < thread->threadId && + thread->threadId < unlock_thread->threadId) { + unlock_thread = thread; + } + } else { + if (thread->threadId > this_threadId) { + unlock_thread = thread; + } + if (thread->threadId < unlock_thread->threadId) { + unlock_thread = thread; + } + } + } + } + if (unlock_thread) { + cs = env_cpu(unlock_thread); + print_thread("\tWaiting thread found", cs); + unlock_thread->tlb_lock_state = HEX_LOCK_QUEUED; + SET_SYSCFG_FIELD(unlock_thread, SYSCFG_TLBLOCK, 1); + cpu_interrupt(cs, CPU_INTERRUPT_TLB_UNLOCK); + } + + if (qemu_loglevel_mask(CPU_LOG_MMU)) { + qemu_log_mask(CPU_LOG_MMU, "Threads after hex_tlb_unlock:\n"); + print_thread_states("\tThread"); + } + +} diff --git a/target/hexagon/hex_mmu.h b/target/hexagon/hex_mmu.h new file mode 100644 index 0000000000..4f556c715a --- /dev/null +++ b/target/hexagon/hex_mmu.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HEXAGON_MMU_H +#define HEXAGON_MMU_H + +#include "cpu.h" +#include "monitor/monitor.h" + +extern void hex_tlbw(CPUHexagonState *env, uint32_t index, uint64_t value); +extern uint32_t hex_tlb_lookup(CPUHexagonState *env, uint32_t ssr, uint32_t VA); +extern void hex_mmu_on(CPUHexagonState *env); +extern void hex_mmu_off(CPUHexagonState *env); +extern void hex_mmu_mode_change(CPUHexagonState *env); +extern bool hex_tlb_find_match(CPUHexagonState *env, uint32_t VA, + MMUAccessType access_type, hwaddr *PA, int *prot, + uint64_t *size, int32_t *excp, int mmu_idx); +extern int hex_tlb_check_overlap(CPUHexagonState *env, uint64_t entry, + uint64_t index); +extern void hex_tlb_lock(CPUHexagonState *env); +extern void hex_tlb_unlock(CPUHexagonState *env); +void dump_mmu(Monitor *mon, CPUHexagonState *env); +#endif diff --git a/target/hexagon/hex_regs.h b/target/hexagon/hex_regs.h index bddfc28021..214b7b8fde 100644 --- a/target/hexagon/hex_regs.h +++ b/target/hexagon/hex_regs.h @@ -81,4 +81,121 @@ enum { HEX_REG_UTIMERHI = 63, }; +#ifndef CONFIG_USER_ONLY + +#define HEX_GREG_VALUES \ + DECL_HEX_GREG(G0, 0) \ + DECL_HEX_GREG(GELR, 0) \ + DECL_HEX_GREG(G1, 1) \ + DECL_HEX_GREG(GSR, 1) \ + DECL_HEX_GREG(G2, 2) \ + DECL_HEX_GREG(GOSP, 2) \ + DECL_HEX_GREG(G3, 3) \ + DECL_HEX_GREG(GBADVA, 3) \ + DECL_HEX_GREG(GCYCLE_1T, 10) \ + DECL_HEX_GREG(GCYCLE_2T, 11) \ + DECL_HEX_GREG(GCYCLE_3T, 12) \ + DECL_HEX_GREG(GCYCLE_4T, 13) \ + DECL_HEX_GREG(GCYCLE_5T, 14) \ + DECL_HEX_GREG(GCYCLE_6T, 15) \ + DECL_HEX_GREG(GPMUCNT4, 16) \ + DECL_HEX_GREG(GPMUCNT5, 17) \ + DECL_HEX_GREG(GPMUCNT6, 18) \ + DECL_HEX_GREG(GPMUCNT7, 19) \ + DECL_HEX_GREG(GPCYCLELO, 24) \ + DECL_HEX_GREG(GPCYCLEHI, 25) \ + DECL_HEX_GREG(GPMUCNT0, 26) \ + DECL_HEX_GREG(GPMUCNT1, 27) \ + DECL_HEX_GREG(GPMUCNT2, 28) \ + DECL_HEX_GREG(GPMUCNT3, 29) \ + DECL_HEX_GREG_DONE + +#define DECL_HEX_GREG_DONE +#define DECL_HEX_GREG(name, val) HEX_GREG_ ##name = val, +enum hex_greg { + HEX_GREG_VALUES +}; +#undef DECL_HEX_GREG +#undef DECL_HEX_GREG_DONE + +#define DECL_HEX_GREG_DONE 0 +#define DECL_HEX_GREG(_, val) (1 << val) | +static inline bool greg_implemented(enum hex_greg greg) +{ +#if NUM_GREGS > 32 +#error "NUM_GREGS too large for greg_implemented(): update `impl_bitmap`" +#endif + static int32_t impl_bitmap = HEX_GREG_VALUES; + return impl_bitmap & (1 << greg); +} +#undef DECL_HEX_GREG +#undef DECL_HEX_GREG_DONE + +#endif /* CONFIG_USER_ONLY */ + +#ifndef CONFIG_USER_ONLY +enum { + HEX_SREG_SGP0 = 0, + HEX_SREG_SGP1 = 1, + HEX_SREG_STID = 2, + HEX_SREG_ELR = 3, + HEX_SREG_BADVA0 = 4, + HEX_SREG_BADVA1 = 5, + HEX_SREG_SSR = 6, + HEX_SREG_CCR = 7, + HEX_SREG_HTID = 8, + HEX_SREG_BADVA = 9, + HEX_SREG_IMASK = 10, + HEX_SREG_GEVB = 11, + HEX_SREG_GLB_START = 16, + HEX_SREG_EVB = 16, + HEX_SREG_MODECTL = 17, + HEX_SREG_SYSCFG = 18, + HEX_SREG_IPENDAD = 20, + HEX_SREG_VID = 21, + HEX_SREG_VID1 = 22, + HEX_SREG_BESTWAIT = 23, + HEX_SREG_IEL = 24, + HEX_SREG_SCHEDCFG = 25, + HEX_SREG_IAHL = 26, + HEX_SREG_CFGBASE = 27, + HEX_SREG_DIAG = 28, + HEX_SREG_REV = 29, + HEX_SREG_PCYCLELO = 30, + HEX_SREG_PCYCLEHI = 31, + HEX_SREG_ISDBST = 32, + HEX_SREG_ISDBCFG0 = 33, + HEX_SREG_ISDBCFG1 = 34, + HEX_SREG_LIVELOCK = 35, + HEX_SREG_BRKPTPC0 = 36, + HEX_SREG_BRKPTCFG0 = 37, + HEX_SREG_BRKPTPC1 = 38, + HEX_SREG_BRKPTCFG1 = 39, + HEX_SREG_ISDBMBXIN = 40, + HEX_SREG_ISDBMBXOUT = 41, + HEX_SREG_ISDBEN = 42, + HEX_SREG_ISDBGPR = 43, + HEX_SREG_PMUCNT4 = 44, + HEX_SREG_PMUCNT5 = 45, + HEX_SREG_PMUCNT6 = 46, + HEX_SREG_PMUCNT7 = 47, + HEX_SREG_PMUCNT0 = 48, + HEX_SREG_PMUCNT1 = 49, + HEX_SREG_PMUCNT2 = 50, + HEX_SREG_PMUCNT3 = 51, + HEX_SREG_PMUEVTCFG = 52, + HEX_SREG_PMUSTID0 = 53, + HEX_SREG_PMUEVTCFG1 = 54, + HEX_SREG_PMUSTID1 = 55, + HEX_SREG_TIMERLO = 56, + HEX_SREG_TIMERHI = 57, + HEX_SREG_PMUCFG = 58, + HEX_SREG_S59 = 59, + HEX_SREG_S60 = 60, + HEX_SREG_S61 = 61, + HEX_SREG_S62 = 62, + HEX_SREG_S63 = 63, +}; +#endif /* !CONFIG_USER_ONLY */ + #endif diff --git a/target/hexagon/hexswi.c b/target/hexagon/hexswi.c new file mode 100644 index 0000000000..43c373ea2e --- /dev/null +++ b/target/hexagon/hexswi.c @@ -0,0 +1,271 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "cpu.h" +#include "cpu_helper.h" +#include "exec/helper-proto.h" +#include "qemu/log.h" +#include "qemu/main-loop.h" +#include "arch.h" +#include "internal.h" +#include "macros.h" +#include "sys_macros.h" +#include "accel/tcg/cpu-loop.h" +#include "tcg/tcg-op.h" +#include "hex_mmu.h" +#include "hexswi.h" +#include "hw/hexagon/hexagon_globalreg.h" + +#ifdef CONFIG_USER_ONLY +#error "This file is only used in system emulation" +#endif + +static void set_addresses(CPUHexagonState *env, uint32_t pc_offset, + uint32_t exception_index) + +{ + HexagonCPU *cpu = env_archcpu(env); + uint32_t evb = cpu->globalregs ? + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_EVB, + env->threadId) : + cpu->boot_addr; + env->t_sreg[HEX_SREG_ELR] = env->gpr[HEX_REG_PC] + pc_offset; + env->gpr[HEX_REG_PC] = evb | (exception_index << 2); +} + +static const char *event_name[] = { + [HEX_EVENT_RESET] = "HEX_EVENT_RESET", + [HEX_EVENT_IMPRECISE] = "HEX_EVENT_IMPRECISE", + [HEX_EVENT_PRECISE] = "HEX_EVENT_PRECISE", + [HEX_EVENT_TLB_MISS_X] = "HEX_EVENT_TLB_MISS_X", + [HEX_EVENT_TLB_MISS_RW] = "HEX_EVENT_TLB_MISS_RW", + [HEX_EVENT_TRAP0] = "HEX_EVENT_TRAP0", + [HEX_EVENT_TRAP1] = "HEX_EVENT_TRAP1", + [HEX_EVENT_FPTRAP] = "HEX_EVENT_FPTRAP", + [HEX_EVENT_DEBUG] = "HEX_EVENT_DEBUG", + [HEX_EVENT_INT0] = "HEX_EVENT_INT0", + [HEX_EVENT_INT1] = "HEX_EVENT_INT1", + [HEX_EVENT_INT2] = "HEX_EVENT_INT2", + [HEX_EVENT_INT3] = "HEX_EVENT_INT3", + [HEX_EVENT_INT4] = "HEX_EVENT_INT4", + [HEX_EVENT_INT5] = "HEX_EVENT_INT5", + [HEX_EVENT_INT6] = "HEX_EVENT_INT6", + [HEX_EVENT_INT7] = "HEX_EVENT_INT7", + [HEX_EVENT_INT8] = "HEX_EVENT_INT8", + [HEX_EVENT_INT9] = "HEX_EVENT_INT9", + [HEX_EVENT_INTA] = "HEX_EVENT_INTA", + [HEX_EVENT_INTB] = "HEX_EVENT_INTB", + [HEX_EVENT_INTC] = "HEX_EVENT_INTC", + [HEX_EVENT_INTD] = "HEX_EVENT_INTD", + [HEX_EVENT_INTE] = "HEX_EVENT_INTE", + [HEX_EVENT_INTF] = "HEX_EVENT_INTF" +}; + +void hexagon_cpu_do_interrupt(CPUState *cs) + +{ + CPUHexagonState *env = cpu_env(cs); + uint32_t ssr; + + BQL_LOCK_GUARD(); + + qemu_log_mask(CPU_LOG_INT, + "\t%s: event 0x%02x:%s, cause 0x%" PRIx32 "(%" PRIu32 ")\n", + __func__, (unsigned)cs->exception_index, + event_name[cs->exception_index], env->cause_code, + env->cause_code); + + env->llsc_addr = ~0; + + ssr = env->t_sreg[HEX_SREG_SSR]; + if (GET_SSR_FIELD(SSR_EX, ssr) == 1) { + HexagonCPU *cpu = env_archcpu(env); + if (cpu->globalregs) { + hexagon_globalreg_write(cpu->globalregs, HEX_SREG_DIAG, + env->cause_code, env->threadId); + } + env->cause_code = HEX_CAUSE_DOUBLE_EXCEPT; + cs->exception_index = HEX_EVENT_PRECISE; + } + + switch (cs->exception_index) { + case HEX_EVENT_TRAP0: + if (env->cause_code == 0) { + qemu_log_mask(LOG_UNIMP, + "trap0 is unhandled, no semihosting available\n"); + } + + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 4, cs->exception_index); + break; + + case HEX_EVENT_TRAP1: + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 4, cs->exception_index); + break; + + case HEX_EVENT_TLB_MISS_X: + switch (env->cause_code) { + case HEX_CAUSE_TLBMISSX_CAUSE_NORMAL: + case HEX_CAUSE_TLBMISSX_CAUSE_NEXTPAGE: + qemu_log_mask(CPU_LOG_MMU, + "TLB miss EX exception (0x%02" PRIx32 ") caught: " + "Cause code (0x%" PRIx32 ") " + "TID = 0x%" PRIx32 ", PC = 0x%" PRIx32 + ", BADVA = 0x%" PRIx32 "\n", + (uint32_t)cs->exception_index, + env->cause_code, env->threadId, + env->gpr[HEX_REG_PC], + env->t_sreg[HEX_SREG_BADVA]); + + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 0, cs->exception_index); + break; + + default: + cpu_abort(cs, + "1:Hexagon exception %" PRId32 "/0x%02" PRIx32 ": " + "Unknown cause code %" PRIu32 "/0x%" PRIx32 "\n", + (uint32_t)cs->exception_index, + (uint32_t)cs->exception_index, + env->cause_code, + env->cause_code); + break; + } + break; + + case HEX_EVENT_TLB_MISS_RW: + switch (env->cause_code) { + case HEX_CAUSE_TLBMISSRW_CAUSE_READ: + case HEX_CAUSE_TLBMISSRW_CAUSE_WRITE: + qemu_log_mask(CPU_LOG_MMU, + "TLB miss RW exception (0x%02" PRIx32 ") caught: " + "Cause code (0x%" PRIx32 ") " + "TID = 0x%" PRIx32 ", PC = 0x%" PRIx32 + ", BADVA = 0x%" PRIx32 "\n", + (uint32_t)cs->exception_index, + env->cause_code, env->threadId, + env->gpr[HEX_REG_PC], + env->t_sreg[HEX_SREG_BADVA]); + + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 0, cs->exception_index); + /* env->sreg[HEX_SREG_BADVA] is set when the exception is raised */ + break; + + default: + cpu_abort(cs, + "2:Hexagon exception %" PRId32 "/0x%02" PRIx32 ": " + "Unknown cause code %" PRIu32 "/0x%" PRIx32 "\n", + (uint32_t)cs->exception_index, + (uint32_t)cs->exception_index, + env->cause_code, + env->cause_code); + break; + } + break; + + case HEX_EVENT_FPTRAP: + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 0, cs->exception_index); + break; + + case HEX_EVENT_DEBUG: + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 0, cs->exception_index); + qemu_log_mask(LOG_UNIMP, "single-step exception is not handled\n"); + break; + + case HEX_EVENT_PRECISE: + switch (env->cause_code) { + case HEX_CAUSE_FETCH_NO_XPAGE: + case HEX_CAUSE_FETCH_NO_UPAGE: + case HEX_CAUSE_PRIV_NO_READ: + case HEX_CAUSE_PRIV_NO_UREAD: + case HEX_CAUSE_PRIV_NO_WRITE: + case HEX_CAUSE_PRIV_NO_UWRITE: + case HEX_CAUSE_MISALIGNED_LOAD: + case HEX_CAUSE_MISALIGNED_STORE: + case HEX_CAUSE_PC_NOT_ALIGNED: + qemu_log_mask(CPU_LOG_MMU, + "MMU permission exception (0x%02" PRIx32 ") caught: " + "Cause code (0x%" PRIx32 ") " + "TID = 0x%" PRIx32 ", PC = 0x%" PRIx32 + ", BADVA = 0x%" PRIx32 "\n", + (uint32_t)cs->exception_index, + env->cause_code, env->threadId, + env->gpr[HEX_REG_PC], + env->t_sreg[HEX_SREG_BADVA]); + + + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 0, cs->exception_index); + /* env->sreg[HEX_SREG_BADVA] is set when the exception is raised */ + break; + + case HEX_CAUSE_DOUBLE_EXCEPT: + case HEX_CAUSE_PRIV_USER_NO_SINSN: + case HEX_CAUSE_PRIV_USER_NO_GINSN: + case HEX_CAUSE_INVALID_OPCODE: + case HEX_CAUSE_NO_COPROC_ENABLE: + case HEX_CAUSE_NO_COPROC2_ENABLE: + case HEX_CAUSE_UNSUPPORTED_HVX_64B: + case HEX_CAUSE_REG_WRITE_CONFLICT: + case HEX_CAUSE_VWCTRL_WINDOW_MISS: + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 0, cs->exception_index); + break; + + case HEX_CAUSE_COPROC_LDST: + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 0, cs->exception_index); + break; + + case HEX_CAUSE_STACK_LIMIT: + hexagon_ssr_set_cause(env, env->cause_code); + set_addresses(env, 0, cs->exception_index); + break; + + default: + cpu_abort(cs, + "3:Hexagon exception %" PRId32 "/0x%02" PRIx32 ": " + "Unknown cause code %" PRIu32 "/0x%" PRIx32 "\n", + (uint32_t)cs->exception_index, + (uint32_t)cs->exception_index, + env->cause_code, + env->cause_code); + break; + } + break; + + case HEX_EVENT_IMPRECISE: + qemu_log_mask(LOG_UNIMP, + "Imprecise exception: this case is not yet handled"); + break; + + default: + qemu_log_mask(LOG_UNIMP, + "Hexagon Unsupported exception 0x%02x/0x%" PRIx32 "\n", + (unsigned)cs->exception_index, env->cause_code); + break; + } + + cs->exception_index = HEX_EVENT_NONE; +} + +void register_trap_exception(CPUHexagonState *env, int traptype, int imm, + uint32_t PC) +{ + CPUState *cs = env_cpu(env); + + cs->exception_index = (traptype == 0) ? HEX_EVENT_TRAP0 : HEX_EVENT_TRAP1; + ASSERT_DIRECT_TO_GUEST_UNSET(env, cs->exception_index); + + env->cause_code = imm; + env->gpr[HEX_REG_PC] = PC; + cpu_loop_exit(cs); +} diff --git a/target/hexagon/hexswi.h b/target/hexagon/hexswi.h new file mode 100644 index 0000000000..48c1ae6e4c --- /dev/null +++ b/target/hexagon/hexswi.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HEXSWI_H +#define HEXSWI_H + + +#include "cpu.h" + +void hexagon_cpu_do_interrupt(CPUState *cpu); +void register_trap_exception(CPUHexagonState *env, int type, int imm, + uint32_t PC); + +#endif /* HEXSWI_H */ diff --git a/target/hexagon/idef-parser/idef-parser.y b/target/hexagon/idef-parser/idef-parser.y index c6f17c6afa..3cffafa5b7 100644 --- a/target/hexagon/idef-parser/idef-parser.y +++ b/target/hexagon/idef-parser/idef-parser.y @@ -404,6 +404,9 @@ control_statement : frame_check ; frame_check : FCHK '(' rvalue ',' rvalue ')' ';' + { + gen_framecheck(c, &@1, &$3, &$5); + } ; cancel_statement : LOAD_CANCEL @@ -869,7 +872,7 @@ int main(int argc, char **argv) context.header_str = g_string_new(NULL); context.ternary = g_array_new(FALSE, TRUE, sizeof(Ternary)); /* Read input file */ - FILE *input_file = fopen(argv[ARG_INDEX_IDEFS], "r"); + FILE *input_file = fopen(argv[ARG_INDEX_IDEFS], "rb"); fseek(input_file, 0L, SEEK_END); long input_size = ftell(input_file); context.input_buffer = (char *) calloc(input_size + 1, sizeof(char)); diff --git a/target/hexagon/idef-parser/parser-helpers.c b/target/hexagon/idef-parser/parser-helpers.c index 70bfa64432..b942d9ea16 100644 --- a/target/hexagon/idef-parser/parser-helpers.c +++ b/target/hexagon/idef-parser/parser-helpers.c @@ -1731,6 +1731,15 @@ void gen_load_cancel(Context *c, YYLTYPE *locp) OUT(c, locp, "}\n"); } +void gen_framecheck(Context *c, YYLTYPE *locp, HexValue *addr, HexValue *ea) +{ + HexValue addr_m = rvalue_materialize(c, locp, addr); + HexValue ea_m = rvalue_materialize(c, locp, ea); + addr_m = gen_rvalue_truncate(c, locp, &addr_m); + ea_m = gen_rvalue_truncate(c, locp, &ea_m); + OUT(c, locp, "gen_framecheck(ctx, ", &addr_m, ", ", &ea_m, ");\n"); +} + void gen_load(Context *c, YYLTYPE *locp, HexValue *width, HexSignedness signedness, HexValue *ea, HexValue *dst) { diff --git a/target/hexagon/idef-parser/parser-helpers.h b/target/hexagon/idef-parser/parser-helpers.h index 2087d534a9..d3dfcec569 100644 --- a/target/hexagon/idef-parser/parser-helpers.h +++ b/target/hexagon/idef-parser/parser-helpers.h @@ -295,6 +295,8 @@ void gen_cancel(Context *c, YYLTYPE *locp); void gen_load_cancel(Context *c, YYLTYPE *locp); +void gen_framecheck(Context *c, YYLTYPE *locp, HexValue *addr, HexValue *ea); + void gen_load(Context *c, YYLTYPE *locp, HexValue *size, HexSignedness signedness, HexValue *ea, HexValue *dst); diff --git a/target/hexagon/imported/encode_pp.def b/target/hexagon/imported/encode_pp.def index 0cd30a5e85..1c64495d51 100644 --- a/target/hexagon/imported/encode_pp.def +++ b/target/hexagon/imported/encode_pp.def @@ -382,15 +382,19 @@ DEF_ENC32(L4_return_fnew_pt, ICLASS_LD" 011 0 000 sssss PP1110vv ---ddddd") DEF_ENC32(L4_return_tnew_pnt, ICLASS_LD" 011 0 000 sssss PP0010vv ---ddddd") DEF_ENC32(L4_return_fnew_pnt, ICLASS_LD" 011 0 000 sssss PP1010vv ---ddddd") -DEF_ENC32(L2_loadw_locked,ICLASS_LD" 001 0 000 sssss PP000--- 000ddddd") - +/** Load Acquire Store Release Encoding **/ +DEF_ENC32(L4_loadw_phys, ICLASS_LD" 001 0 000 sssss PP1ttttt -00ddddd") +DEF_ENC32(L2_loadw_locked, ICLASS_LD" 001 0 000 sssss PP000--- 000ddddd") +DEF_ENC32(L4_loadd_locked, ICLASS_LD" 001 0 000 sssss PP010--- 000ddddd") DEF_ENC32(L2_loadw_aq, ICLASS_LD" 001 0 000 sssss PP001--- 000ddddd") DEF_ENC32(L4_loadd_aq, ICLASS_LD" 001 0 000 sssss PP011--- 000ddddd") -DEF_ENC32(R6_release_at_vi, ICLASS_ST" 000 01 11sssss PP0ttttt --0011dd") -DEF_ENC32(R6_release_st_vi, ICLASS_ST" 000 01 11sssss PP0ttttt --1011dd") + +DEF_ENC32(S2_storew_locked, ICLASS_ST" 000 01 01sssss PP-ttttt ----00dd") +DEF_ENC32(S4_stored_locked, ICLASS_ST" 000 01 11sssss PP0ttttt ----00dd") + DEF_ENC32(S2_storew_rl_at_vi, ICLASS_ST" 000 01 01sssss PP-ttttt --0010dd") DEF_ENC32(S2_storew_rl_st_vi, ICLASS_ST" 000 01 01sssss PP-ttttt --1010dd") @@ -398,13 +402,11 @@ DEF_ENC32(S2_storew_rl_st_vi, ICLASS_ST" 000 01 01sssss PP-ttttt --1010dd") DEF_ENC32(S4_stored_rl_at_vi, ICLASS_ST" 000 01 11sssss PP0ttttt --0010dd") DEF_ENC32(S4_stored_rl_st_vi, ICLASS_ST" 000 01 11sssss PP0ttttt --1010dd") -DEF_ENC32(L4_loadd_locked,ICLASS_LD" 001 0 000 sssss PP010--- 000ddddd") -DEF_EXT_SPACE(EXTRACTW, ICLASS_LD" 001 0 000 iiiii PP0iiiii -01iiiii") -DEF_ENC32(Y2_dcfetchbo, ICLASS_LD" 010 0 000 sssss PP0--iii iiiiiiii") - - - +DEF_ENC32(R6_release_at_vi, ICLASS_ST" 000 01 11sssss PP0ttttt --0011dd") +DEF_ENC32(R6_release_st_vi, ICLASS_ST" 000 01 11sssss PP0ttttt --1011dd") +DEF_EXT_SPACE(EXTRACTW, ICLASS_LD" 001 0 000 iiiii PP0iiiii 001iiiii") +DEF_ENC32(Y2_dcfetchbo, ICLASS_LD" 010 0 000 sssss PP0--iii iiiiiiii") @@ -488,13 +490,17 @@ STD_PST_ENC(rinew, "1 101","10ttt") /* x bus/cache */ /* x store/cache */ DEF_ENC32(S2_allocframe, ICLASS_ST" 000 01 00xxxxx PP000iii iiiiiiii") -DEF_ENC32(S2_storew_locked,ICLASS_ST" 000 01 01sssss PP-ttttt ----00dd") -DEF_ENC32(S4_stored_locked,ICLASS_ST" 000 01 11sssss PP0ttttt ----00dd") +DEF_ENC32(Y5_l2locka, ICLASS_ST" 000 01 11sssss PP1----- ------dd") DEF_ENC32(Y2_dczeroa, ICLASS_ST" 000 01 10sssss PP0----- --------") DEF_ENC32(Y2_barrier, ICLASS_ST" 100 00 00----- PP------ 000-----") DEF_ENC32(Y2_syncht, ICLASS_ST" 100 00 10----- PP------ --------") +DEF_ENC32(Y2_l2kill, ICLASS_ST" 100 00 01----- PP-000-- --------") +DEF_ENC32(Y5_l2gunlock, ICLASS_ST" 100 00 01----- PP-010-- --------") +DEF_ENC32(Y5_l2gclean, ICLASS_ST" 100 00 01----- PP-100-- --------") +DEF_ENC32(Y5_l2gcleaninv, ICLASS_ST" 100 00 01----- PP-110-- --------") +DEF_ENC32(Y2_l2cleaninvidx,ICLASS_ST" 100 00 11sssss PP------ --------") @@ -502,9 +508,28 @@ DEF_ENC32(Y2_dccleana, ICLASS_ST" 000 00 00sssss PP------ --------") DEF_ENC32(Y2_dcinva, ICLASS_ST" 000 00 01sssss PP------ --------") DEF_ENC32(Y2_dccleaninva, ICLASS_ST" 000 00 10sssss PP------ --------") -DEF_ENC32(Y4_l2fetch, ICLASS_ST" 011 00 00sssss PP-ttttt 000-----") +/* Super */ +DEF_ENC32(Y2_dckill, ICLASS_ST" 001 00 00----- PP------ --------") +DEF_ENC32(Y2_dccleanidx, ICLASS_ST" 001 00 01sssss PP------ --------") +DEF_ENC32(Y2_dcinvidx, ICLASS_ST" 001 00 10sssss PP------ --------") +DEF_ENC32(Y2_dccleaninvidx,ICLASS_ST" 001 00 11sssss PP------ --------") + +DEF_ENC32(Y2_dctagw ,ICLASS_ST" 010 00 00sssss PP-ttttt --------") +DEF_ENC32(Y2_dctagr ,ICLASS_ST" 010 00 01sssss PP------ ---ddddd") + +DEF_ENC32(Y4_l2tagw ,ICLASS_ST" 010 00 10sssss PP0ttttt --------") +DEF_ENC32(Y4_l2tagr ,ICLASS_ST" 010 00 11sssss PP------ ---ddddd") + +DEF_ENC32(Y4_l2fetch, ICLASS_ST" 011 00 00sssss PP-ttttt 000-----") +DEF_ENC32(Y5_l2cleanidx, ICLASS_ST" 011 00 01sssss PP------ --------") +DEF_ENC32(Y5_l2invidx, ICLASS_ST" 011 00 10sssss PP------ --------") +DEF_ENC32(Y5_l2unlocka, ICLASS_ST" 011 00 11sssss PP------ --------") DEF_ENC32(Y5_l2fetch, ICLASS_ST" 011 01 00sssss PP-ttttt --------") +DEF_ENC32(Y6_l2gcleanpa, ICLASS_ST" 011 01 01----- PP-ttttt --------") +DEF_ENC32(Y6_l2gcleaninvpa,ICLASS_ST" 011 01 10----- PP-ttttt --------") + + /*******************************/ /* */ /* */ @@ -547,13 +572,23 @@ DEF_ENC32(J2_jumprfnewpt, ICLASS_J" 0011 011sssss PP-11-uu --------") DEF_FIELDROW_DESC32(ICLASS_J" 0100 -------- PP------ --------","[#4] (#u8) ") DEF_ENC32(J2_trap0, ICLASS_J" 0100 00------ PP-iiiii ---iii--") -DEF_ENC32(J2_pause, ICLASS_J" 0100 01------ PP-iiiii ---iii--") +DEF_ENC32(J2_trap1, ICLASS_J" 0100 10-xxxxx PP-iiiii ---iii--") +DEF_ENC32(J2_pause, ICLASS_J" 0100 01----ii PP-iiiii ---iii--") + +DEF_FIELDROW_DESC32(ICLASS_J" 0101 -------- PP------ --------","[#5] Rd=(Rs) ") +DEF_ENC32(Y2_icdatar, ICLASS_J" 0101 101sssss PP------ ---ddddd") +DEF_ENC32(Y2_ictagr, ICLASS_J" 0101 111sssss PP------ ---ddddd") +DEF_ENC32(Y2_ictagw, ICLASS_J" 0101 110sssss PP0ttttt --------") +DEF_ENC32(Y2_icdataw, ICLASS_J" 0101 110sssss PP1ttttt --------") DEF_FIELDROW_DESC32(ICLASS_J" 0110 -------- PP------ --------","[#6] icop(Rs) ") DEF_ENC32(Y2_icinva, ICLASS_J" 0110 110sssss PP000--- --------") +DEF_ENC32(Y2_icinvidx, ICLASS_J" 0110 110sssss PP001--- --------") +DEF_ENC32(Y2_ickill, ICLASS_J" 0110 110----- PP010--- --------") DEF_FIELDROW_DESC32(ICLASS_J" 0111 -------- PP------ --------","[#7] () ") DEF_ENC32(Y2_isync, ICLASS_J" 0111 11000000 PP0---00 00000010") +DEF_ENC32(J2_rte, ICLASS_J" 0111 111----- PP00---- 000-----") /* JUMP */ DEF_FIELDROW_DESC32(ICLASS_J" 100- -------- PP------ --------","[#8,9] PC=(#r22)") @@ -738,12 +773,19 @@ DEF_ENC32(J2_jumprltezpt,ICLASS_CR" 0001 11isssss PPi1iiii iiiiiii-") DEF_FIELDROW_DESC32( ICLASS_CR" 0010 -------- PP------ --------","[#2] Cd=Rs ") DEF_ENC32(A2_tfrrcr, ICLASS_CR" 0010 001sssss PP------ ---ddddd") +DEF_ENC32(G4_tfrgrcr, ICLASS_CR" 0010 000sssss PP------ ---ddddd") +DEF_ENC32(Y4_trace, ICLASS_CR" 0010 010sssss PP------ 000-----") +DEF_ENC32(Y6_diag, ICLASS_CR" 0010 010sssss PP------ 001-----") +DEF_ENC32(Y6_diag0, ICLASS_CR" 0010 010sssss PP-ttttt 010-----") +DEF_ENC32(Y6_diag1, ICLASS_CR" 0010 010sssss PP-ttttt 011-----") DEF_FIELDROW_DESC32( ICLASS_CR" 0011 -------- PP------ --------","[#3] Cdd=Rss ") DEF_ENC32(A4_tfrpcp, ICLASS_CR" 0011 001sssss PP------ ---ddddd") +DEF_ENC32(G4_tfrgpcp, ICLASS_CR" 0011 000sssss PP------ ---ddddd") DEF_FIELDROW_DESC32( ICLASS_CR" 1000 -------- PP------ --------","[#8] Rdd=Css ") DEF_ENC32(A4_tfrcpp, ICLASS_CR" 1000 000sssss PP------ ---ddddd") +DEF_ENC32(G4_tfrgcpp, ICLASS_CR" 1000 001sssss PP------ ---ddddd") DEF_FIELDROW_DESC32( ICLASS_CR" 1001 -------- PP------ --------","[#9] (#r8,#U10)") DEF_ENC32(J2_ploop1si, ICLASS_CR" 1001 101IIIII PP-iiiii IIIii-II") @@ -754,6 +796,7 @@ DEF_ENC32(J2_loop1i, ICLASS_CR" 1001 001IIIII PP-iiiii IIIii-II") DEF_FIELDROW_DESC32( ICLASS_CR" 1010 -------- PP------ --------","[#10] Rd=Cs ") DEF_ENC32(A2_tfrcrr, ICLASS_CR" 1010 000sssss PP------ ---ddddd") +DEF_ENC32(G4_tfrgcrr, ICLASS_CR" 1010 001sssss PP------ ---ddddd") DEF_ENC32(C4_addipc, ICLASS_CR" 1010 01001001 PP-iiiii i--ddddd") @@ -781,6 +824,64 @@ DEF_ENC32(C4_fastcorner9_not, ICLASS_CR"1011 0001--ss PP1---tt 1--1--dd") +/* Supervisor CR ops */ +/* Interrupts */ +DEF_FIELDROW_DESC32( ICLASS_CR" 0100 -------- PP------ --------","[#4] (Rs,Pt)") +DEF_ENC32(Y2_swi, ICLASS_CR" 0100 000sssss PP------ 000-----") +DEF_ENC32(Y2_cswi, ICLASS_CR" 0100 000sssss PP------ 001-----") +DEF_ENC32(Y2_iassignw, ICLASS_CR" 0100 000sssss PP------ 010-----") +DEF_ENC32(Y2_ciad, ICLASS_CR" 0100 000sssss PP------ 011-----") +DEF_ENC32(Y2_setimask, ICLASS_CR" 0100 100sssss PP----tt 000-----") +DEF_ENC32(Y2_setprio, ICLASS_CR" 0100 100sssss PP----tt 001-----") +DEF_ENC32(Y4_siad, ICLASS_CR" 0100 100sssss PP------ 011-----") + +DEF_ENC32(Y2_wait, ICLASS_CR" 0100 010sssss PP------ 000-----") +DEF_ENC32(Y2_resume, ICLASS_CR" 0100 010sssss PP------ 001-----") +DEF_ENC32(Y2_stop, ICLASS_CR" 0100 011sssss PP------ 000-----") +DEF_ENC32(Y2_start, ICLASS_CR" 0100 011sssss PP------ 001-----") +DEF_ENC32(Y4_nmi, ICLASS_CR" 0100 011sssss PP------ 010-----") + +DEF_FIELDROW_DESC32( ICLASS_CR" 0101 -------- PP------ --------","[#5] Rx ") +DEF_ENC32(Y2_crswap0, ICLASS_CR" 0101 000xxxxx PP------ --------") +DEF_ENC32(Y4_crswap1, ICLASS_CR" 0101 001xxxxx PP------ --------") + +DEF_FIELDROW_DESC32( ICLASS_CR" 0110 -------- PP------ --------","[#6] Rd=(Rs)") +DEF_ENC32(Y2_getimask, ICLASS_CR" 0110 000sssss PP------ ---ddddd") +DEF_ENC32(Y2_iassignr, ICLASS_CR" 0110 011sssss PP------ ---ddddd") + +DEF_FIELDROW_DESC32( ICLASS_CR" 0111 -------- PP------ --------","[#7] cr=Rs ") +DEF_ENC32(Y2_tfrsrcr, ICLASS_CR" 0111 00-sssss PP------ -ddddddd") + +DEF_FIELDROW_DESC32( ICLASS_CR" 1100 -------- PP------ --------","[#12] ") +DEF_ENC32(Y2_break, ICLASS_CR" 1100 001----- PP------ 000-----") +DEF_ENC32(Y2_tlblock, ICLASS_CR" 1100 001----- PP------ 001-----") +DEF_ENC32(Y2_tlbunlock,ICLASS_CR" 1100 001----- PP------ 010-----") +DEF_ENC32(Y2_k0lock, ICLASS_CR" 1100 001----- PP------ 011-----") +DEF_ENC32(Y2_k0unlock, ICLASS_CR" 1100 001----- PP------ 100-----") +DEF_ENC32(Y2_tlbp, ICLASS_CR" 1100 100sssss PP------ ---ddddd") +DEF_ENC32(Y5_tlboc, ICLASS_CR" 1100 111sssss PP------ ---ddddd") +DEF_ENC32(Y5_tlbasidi, ICLASS_CR" 1100 101sssss PP------ --------") +DEF_ENC32(Y2_tlbr, ICLASS_CR" 1100 010sssss PP------ ---ddddd") +DEF_ENC32(Y2_tlbw, ICLASS_CR" 1100 000sssss PP0ttttt --------") +DEF_ENC32(Y5_ctlbw, ICLASS_CR" 1100 110sssss PP0ttttt ---ddddd") + +DEF_FIELDROW_DESC32( ICLASS_CR" 1101 -------- PP------ --------","[#13] Rxx ") +DEF_ENC32(Y4_crswap10, ICLASS_CR" 1101 10-xxxxx PP------ ---00000") +DEF_ENC32(Y4_tfrspcp, ICLASS_CR" 1101 00-sssss PP------ -ddddddd") + +DEF_FIELDROW_DESC32( ICLASS_CR" 1110 -------- PP------ --------","[#14] Rd=cr ") +DEF_ENC32(Y2_tfrscrr, ICLASS_CR" 1110 1sssssss PP------ ---ddddd") + +DEF_FIELDROW_DESC32( ICLASS_CR" 1111 -------- PP------ --------","[#15] Rdd=Sss ") +DEF_ENC32(Y4_tfrscpp, ICLASS_CR" 1111 0sssssss PP------ ---ddddd") + + + + + + + + /*******************************/ /* */ /* */ diff --git a/target/hexagon/imported/ldst.idef b/target/hexagon/imported/ldst.idef index 53198176a9..4e1e5d5326 100644 --- a/target/hexagon/imported/ldst.idef +++ b/target/hexagon/imported/ldst.idef @@ -203,6 +203,9 @@ Q6INSN(S2_storew_locked,"memw_locked(Rs32,Pd4)=Rt32", ATTRIBS(A_REGWRSIZE_4B,A_M Q6INSN(L4_loadd_locked,"Rdd32=memd_locked(Rs32)", ATTRIBS(A_REGWRSIZE_8B,A_MEMSIZE_8B,A_LOAD,A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK), "Load double with lock", { fEA_REG(RsV); fLOAD_LOCKED(1,8,u,EA,RddV) }) +Q6INSN(L4_loadw_phys,"Rd32=memw_phys(Rs32,Rt32)", ATTRIBS(A_REGWRSIZE_4B,A_PRIV,A_RESTRICT_SLOT0ONLY,A_NOTE_PRIV,A_MEMSIZE_4B,A_LOAD,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Load word from physical address", +{ fLOAD_PHYS(1,4,u,RsV,RtV,RdV); }) + Q6INSN(S4_stored_locked,"memd_locked(Rs32,Pd4)=Rtt32", ATTRIBS(A_REGWRSIZE_8B,A_MEMSIZE_8B,A_STORE,A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK,A_RESTRICT_LATEPRED,A_NOTE_LATEPRED), "Store word with lock", { fEA_REG(RsV); fSTORE_LOCKED(1,8,EA,RttV,PdV) }) diff --git a/target/hexagon/imported/macros.def b/target/hexagon/imported/macros.def index 4bbcfdd5e1..13eb13c71f 100755..100644 --- a/target/hexagon/imported/macros.def +++ b/target/hexagon/imported/macros.def @@ -354,6 +354,12 @@ DEF_MACRO( ) DEF_MACRO( + fREAD_SSR, /* read SSR register */ + (READ_RREG(REG_SSR)), /* behavior */ + () +) + +DEF_MACRO( fWRITE_LR, /* write lr */ WRITE_RREG(REG_LR,A), /* behavior */ (A_IMPLICIT_WRITES_LR) @@ -372,12 +378,36 @@ DEF_MACRO( ) DEF_MACRO( + fWRITE_GOSP, /* write gosp */ + WRITE_RREG(REG_GOSP,A), /* behavior */ + (A_IMPLICIT_WRITES_GOSP) +) + +DEF_MACRO( fREAD_SP, /* read stack pointer */ (READ_RREG(REG_SP)), /* behavior */ () ) DEF_MACRO( + fREAD_GOSP, /* read guest other stack pointer */ + (READ_RREG(REG_GOSP)), /* behavior */ + () +) + +DEF_MACRO( + fREAD_GELR, /* read guest other stack pointer */ + (READ_RREG(REG_GELR)), /* behavior */ + () +) + +DEF_MACRO( + fREAD_GEVB, /* read guest other stack pointer */ + (READ_RREG(REG_GEVB)), /* behavior */ + () +) + +DEF_MACRO( fREAD_CSREG, /* read CS register */ (READ_RREG(REG_CSA+N)), /* behavior */ () @@ -570,6 +600,11 @@ DEF_MACRO( WRITE_PREG(3,VAL), /* behavior */ (A_IMPLICIT_WRITES_P3) ) +DEF_MACRO( + fWRITE_P3_LATE, /* write Predicate 0 */ + {WRITE_PREG(3,VAL); fHIDE(MARK_LATE_PRED_WRITE(3))} , /* behavior */ + (A_IMPLICIT_WRITES_P3,A_RESTRICT_LATEPRED) +) DEF_MACRO( fPART1, /* write Predicate 0 */ @@ -660,6 +695,7 @@ DEF_MACRO( ((size8s_t)((size2s_t)(A))), /* optional attributes */ ) + DEF_MACRO( fCAST2_8u, /* macro name */ ((size8u_t)((size2u_t)(A))), @@ -1532,18 +1568,137 @@ DEF_MACRO(fECHO, /* OS interface and stop/wait */ /********************************************/ +DEF_MACRO(RUNNABLE_THREADS_MAX, + /* */, + () +) + +DEF_MACRO(THREAD_IS_ON, + ((PROC->arch_proc_options->thread_enable_mask>>TNUM) & 0x1), + () +) + +DEF_MACRO(THREAD_EN_MASK, + ((PROC->arch_proc_options->thread_enable_mask)), + () +) + + + +DEF_MACRO(READ_IMASK, + /* */, + () +) +DEF_MACRO(WRITE_IMASK, + /* */, + (A_IMPLICIT_WRITES_IMASK_ANYTHREAD) +) + + +DEF_MACRO(WRITE_PRIO, + /* */, + (A_IMPLICIT_WRITES_STID_PRIO_ANYTHREAD) +) + + +DEF_MACRO(DO_IASSIGNW, + /* */, + (A_IMPLICIT_WRITES_IMASK_ANYTHREAD) +) + + + + +DEF_MACRO(fDO_NMI, + /* */, +) + +DEF_MACRO(fDO_TRACE, + /* */, +) + +DEF_MACRO(DO_IASSIGNR, + /* */, + () +) + +DEF_MACRO(DO_SWI, + /* */, + (A_EXCEPTION_SWI) +) + +DEF_MACRO(DO_CSWI, + LOG_GLOBAL_REG_WRITE(REG_IPEND,GLOBAL_REG_READ(REG_IPEND) & ~((REG) & GLOBAL_REG_READ(REG_IEL)));, + () +) + +DEF_MACRO(DO_CIAD, + sys_ciad(thread,VAL); LOG_GLOBAL_REG_WRITE(REG_IAD,GLOBAL_REG_READ(REG_IAD) & ~(VAL));, + (A_EXCEPTION_SWI) +) + +DEF_MACRO(DO_SIAD, + sys_siad(thread,VAL); LOG_GLOBAL_REG_WRITE(REG_IAD,GLOBAL_REG_READ(REG_IAD) | (VAL));, + (A_EXCEPTION_SWI) +) + +DEF_MACRO(fBREAK, + /* */, + () +) + DEF_MACRO(fPAUSE, {sys_pause(thread, insn->slot, IMM);}, () ) + DEF_MACRO(fTRAP, - warn("Trap NPC=%x ",fREAD_NPC()); - warn("Trap exception, PCYCLE=%lld TYPE=%d NPC=%x IMM=0x%x",thread->processor_ptr->pstats[pcycles],TRAPTYPE,fREAD_NPC(),IMM); - register_trap_exception(thread,fREAD_NPC(),TRAPTYPE,IMM);, + /* */, + (A_EXCEPTION_SWI) +) + +DEF_MACRO(fCLEAR_RTE_EX, + /* */, + () +) + +DEF_MACRO(fTLB_LOCK_AVAILABLE, + (fREAD_GLOBAL_REG_FIELD(SYSCONF,SYSCFG_TLBLOCK) == 0), + () +) + +DEF_MACRO(fK0_LOCK_AVAILABLE, + (fREAD_GLOBAL_REG_FIELD(SYSCONF,SYSCFG_K0LOCK) == 0), () ) +DEF_MACRO(fSET_TLB_LOCK, + { + if (fTLB_LOCK_AVAILABLE()) { + fLOG_GLOBAL_REG_FIELD(SYSCONF,SYSCFG_TLBLOCK,1); + } else { + sys_waiting_for_tlb_lock(thread); + } + }, + () +) + +DEF_MACRO(fSET_K0_LOCK, + /* */, + () +) + +DEF_MACRO(fCLEAR_TLB_LOCK, + /* */, + () +) + +DEF_MACRO(fCLEAR_K0_LOCK, + /* */, + () +) + DEF_MACRO(fALIGN_REG_FIELD_VALUE, ((VAL)<<reg_field_info[FIELD].offset), /* */ @@ -1554,6 +1709,24 @@ DEF_MACRO(fGET_REG_FIELD_MASK, /* */ ) +DEF_MACRO(fLOG_REG_FIELD, + LOG_MASKED_REG_WRITE(thread,REG_##REG, + fALIGN_REG_FIELD_VALUE(FIELD,VAL), + fGET_REG_FIELD_MASK(FIELD)), + () +) + +DEF_MACRO(fWRITE_GLOBAL_REG_FIELD, + /* */, +) + +DEF_MACRO(fLOG_GLOBAL_REG_FIELD, + LOG_MASKED_GLOBAL_REG_WRITE(REG_##REG, + fALIGN_REG_FIELD_VALUE(FIELD,VAL), + fGET_REG_FIELD_MASK(FIELD)), + () +) + DEF_MACRO(fREAD_REG_FIELD, fEXTRACTU_BITS(thread->Regs[REG_##REG], reg_field_info[FIELD].width, @@ -1561,6 +1734,11 @@ DEF_MACRO(fREAD_REG_FIELD, /* ATTRIBS */ ) +DEF_MACRO(fREAD_GLOBAL_REG_FIELD, + /* */, + /* ATTRIBS */ +) + DEF_MACRO(fGET_FIELD, fEXTRACTU_BITS(VAL, reg_field_info[FIELD].width, @@ -1576,6 +1754,174 @@ DEF_MACRO(fSET_FIELD, /* ATTRIBS */ ) +DEF_MACRO(fSET_RUN_MODE_NOW, + /* */, +) + +DEF_MACRO(fIN_DEBUG_MODE, + (thread->debug_mode || (fREAD_GLOBAL_REG_FIELD(ISDBST,ISDBST_DEBUGMODE) & 1<<TNUM)), + () +) +DEF_MACRO(fIN_DEBUG_MODE_NO_ISDB, + (thread->debug_mode), + () +) + + +DEF_MACRO(fIN_DEBUG_MODE_WARN, + { + if (fREAD_GLOBAL_REG_FIELD(ISDBST,ISDBST_DEBUGMODE) & 1<<TNUM) + warn("In ISDB debug mode, but TB told me to step normally"); + }, + () +) + +DEF_MACRO(fCLEAR_RUN_MODE, + {fLOG_GLOBAL_REG_FIELD(MODECTL,MODECTL_E, + fREAD_GLOBAL_REG_FIELD(MODECTL,MODECTL_E) & ~(1<<(TNUM)))}, + /* NOTHING */ +) + +DEF_MACRO(fCLEAR_RUN_MODE_NOW, + /* */, + /* NOTHING */ +) + +DEF_MACRO(fGET_RUN_MODE, + /* */, +) + +DEF_MACRO(fSET_WAIT_MODE, + {fLOG_GLOBAL_REG_FIELD(MODECTL,MODECTL_W, + fREAD_GLOBAL_REG_FIELD(MODECTL,MODECTL_W) | 1<<(TNUM))}, + /* NOTHING */ +) + +DEF_MACRO(fCLEAR_WAIT_MODE, + /* */, +) + +DEF_MACRO(fGET_WAIT_MODE, + /* */, +) + + +DEF_MACRO(fRESET_THREAD, + register_reset_interrupt(T,NUM), +) + +DEF_MACRO(fREAD_CURRENT_EVB, + (GLOBAL_REG_READ(REG_EVB)), + /* nothing */ +) + +DEF_MACRO(fREAD_ELR, + READ_RREG(REG_ELR), + () +) + +DEF_MACRO(fPOW2_HELP_ROUNDUP, + ((VAL) | ((VAL) >> 1) | ((VAL) >> 2) | ((VAL) >> 4) | ((VAL) >> 8) | ((VAL) >> 16)), + () +) + +DEF_MACRO(fPOW2_ROUNDUP, + fPOW2_HELP_ROUNDUP((VAL)-1)+1, + () +) + +DEF_MACRO(fTLB_IDXMASK, + /* */, + () +) + +DEF_MACRO(fTLB_NONPOW2WRAP, + /* */, + /* ATTRIBS */ +) + +DEF_MACRO(fTLBW, + /* */, + /* ATTRIBS */ +) + +DEF_MACRO(fTLB_ENTRY_OVERLAP, + fHIDE( (sys_check_overlap(thread,VALUE)!=-2) ), + /* ATTRIBS */ +) + +DEF_MACRO(fTLB_ENTRY_OVERLAP_IDX, + fHIDE(sys_check_overlap(thread,VALUE)), + /* ATTRIBS */ +) + + +DEF_MACRO(fTLBR, + TLB_REG_READ(fTLB_NONPOW2WRAP(fTLB_IDXMASK(INDEX))), + /* ATTRIBS */ +) + +DEF_MACRO(fTLBP, + tlb_lookup(thread,((TLBHI)>>12),((TLBHI)<<12),1), + /* attribs */ +) + + + +DEF_MACRO(READ_SGP0, + READ_RREG(REG_SGP), + () +) + +DEF_MACRO(READ_SGP1, + READ_RREG(REG_SGP+1), + () +) + +DEF_MACRO(READ_SGP10, + READ_RREG_PAIR(REG_SGP), + () +) + +DEF_MACRO(READ_UGP, + READ_RREG(REG_UGP), +) + +DEF_MACRO(WRITE_SGP0, + WRITE_RREG(REG_SGP,VAL), + (A_IMPLICIT_WRITES_SGP0) +) + +DEF_MACRO(WRITE_SGP1, + WRITE_RREG(REG_SGP+1,VAL), + (A_IMPLICIT_WRITES_SGP1) +) + +DEF_MACRO(WRITE_SGP10, + WRITE_RREG_PAIR(REG_SGP,VAL), + (A_IMPLICIT_WRITES_SGP0,A_IMPLICIT_WRITES_SGP1) +) + +DEF_MACRO(WRITE_UGP, + WRITE_RREG(REG_UGP,VAL), +) + +DEF_MACRO(fSTART, + /* */, + () +) + +DEF_MACRO(fRESUME, + fLOG_GLOBAL_REG_FIELD(MODECTL,MODECTL_W, + fREAD_GLOBAL_REG_FIELD(MODECTL,MODECTL_W) & (~(REG))), + () +) + +DEF_MACRO(fGET_TNUM, + thread->threadId, + () +) + /********************************************/ /* Cache Management */ /********************************************/ @@ -1602,19 +1948,49 @@ DEF_MACRO(fISYNC, ) +DEF_MACRO(fICFETCH, + , + () +) + DEF_MACRO(fDCFETCH, sys_dcfetch(thread, (REG), insn->slot), (A_MEMLIKE) ) DEF_MACRO(fICINVA, - { - arch_internal_flush(thread->processor_ptr, 0, 0xffffffff); - sys_icinva(thread, (REG),insn->slot); - }, + /* */, (A_ICINVA) ) +DEF_MACRO(fDCTAGR, + ({DST=sys_dctagr(thread, INDEX, insn->slot,DSTREGNO);})/* FIXME */, + () +) + +DEF_MACRO(fDCTAGW, + (sys_dctagw(thread, INDEX, PART2, insn->slot)), + () +) +DEF_MACRO(fICTAGR, + ({DST=sys_ictagr(thread, INDEX, insn->slot,REGNO);}), + () +) + +DEF_MACRO(fICDATAR, + ({DST=sys_icdatar(thread, INDEX, insn->slot);}), + () +) + +DEF_MACRO(fICTAGW, + (sys_ictagw(thread, INDEX, PART2, insn->slot)), + () +) +DEF_MACRO(fICDATAW, + ({ fHIDE(); }), + () +) + DEF_MACRO(fL2FETCH, sys_l2fetch(thread, ADDR,HEIGHT,WIDTH,STRIDE,FLAGS, insn->slot), (A_MEMLIKE,A_L2FETCH) @@ -1635,6 +2011,12 @@ DEF_MACRO(fDCZEROA, (A_MEMLIKE) ) +DEF_MACRO(fDCINVA, + sys_dcinva(thread, (REG)), + (A_MEMLIKE) +) + + DEF_MACRO(fCHECKFORPRIV, {sys_check_privs(thread); if (EXCEPTION_DETECTED) return; }, () @@ -1645,6 +2027,16 @@ DEF_MACRO(fCHECKFORGUEST, () ) +DEF_MACRO(fTAKEN_INTERRUPT_EDGECLEAR, + { proc->global_regs[REG_IPEND] &= ~(INT_NUMTOMASK(intnum) & proc->global_regs[REG_IEL]); }, + () +) + +DEF_MACRO(fSET_IAD, + /* */, + () +) + DEF_MACRO(fBRANCH_SPECULATE_STALL, { sys_speculate_branch_stall(thread, insn->slot, JUMP_COND(JUMP_PRED_SET), @@ -1664,3 +2056,79 @@ DEF_MACRO(IV1DEAD, , () ) + +DEF_MACRO(fIN_MONITOR_MODE, + sys_in_monitor_mode(thread), + () +) + +DEF_MACRO(fIN_USER_MODE, + sys_in_user_mode(thread), + () +) + +DEF_MACRO(fIN_GUEST_MODE, + sys_in_guest_mode(thread), + () +) + +DEF_MACRO(fGRE_ENABLED, + fREAD_REG_FIELD(CCR,CCR_GRE), + () +) + +DEF_MACRO(fGTE_ENABLED, + fREAD_REG_FIELD(CCR,CCR_GRE), + () +) + +DEF_MACRO(fTRAP1_VIRTINSN, + ((fIN_GUEST_MODE()) + && (fGRE_ENABLED()) + && ( ((IMM) == 1) + || ((IMM) == 3) + || ((IMM) == 4) + || ((IMM) == 6))), + () +) + +DEF_MACRO(fVIRTINSN_RTE, + do { + thread->trap1_info = TRAP1_VIRTINSN_RTE; + fLOG_REG_FIELD(SSR,SSR_SS,fREAD_REG_FIELD(GSR,GSR_SS)); + fLOG_REG_FIELD(CCR,CCR_GIE,fREAD_REG_FIELD(GSR,GSR_IE)); + fLOG_REG_FIELD(SSR,SSR_GM,!fREAD_REG_FIELD(GSR,GSR_UM)); + fBRANCH((fREAD_GELR() & -4),COF_TYPE_RTE); + fINTERNAL_CLEAR_SAMEPAGE(); + } while (0), + (A_IMPLICIT_WRITES_CCR,A_IMPLICIT_WRITES_SSR) +) + +DEF_MACRO(fVIRTINSN_SETIE, + do { + fLOG_REG_FIELD(CCR,CCR_GIE,(REG) & 1); + REG = fREAD_REG_FIELD(CCR,CCR_GIE); + thread->trap1_info = TRAP1_VIRTINSN_SETIE; + } while (0), + (A_IMPLICIT_WRITES_CCR) +) + +DEF_MACRO(fVIRTINSN_GETIE, + { + thread->trap1_info = TRAP1_VIRTINSN_GETIE; + REG = fREAD_REG_FIELD(CCR,CCR_GIE); + }, + () +) + +DEF_MACRO(fVIRTINSN_SPSWAP, + do { + if (fREAD_REG_FIELD(GSR,GSR_UM)) { + size4u_t TEMP = REG; + REG = fREAD_GOSP(); + fWRITE_GOSP(TEMP); + thread->trap1_info = TRAP1_VIRTINSN_SPSWAP; + } + } while (0), + (A_IMPLICIT_WRITES_GOSP) +) diff --git a/target/hexagon/imported/system.idef b/target/hexagon/imported/system.idef index 7c6568e75e..9e85bed0a6 100644 --- a/target/hexagon/imported/system.idef +++ b/target/hexagon/imported/system.idef @@ -25,31 +25,230 @@ /* User->OS interface */ /********************************************/ -Q6INSN(J2_trap0,"trap0(#u8)",ATTRIBS(A_COF), +Q6INSN(J2_trap0,"trap0(#u8)",ATTRIBS(A_COF,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Trap to Operating System", fTRAP(0,uiV); ) -Q6INSN(J2_pause,"pause(#u8)",ATTRIBS(A_COF), +Q6INSN(J2_trap1,"trap1(Rx32,#u8)",ATTRIBS(A_COF,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), +"Trap to Operating System", + /* + * Note: if RxV is not written, we get the same as the input. + * Since trap1 is SOLO, this means the register will effectively not be updated + */ + if (!fTRAP1_VIRTINSN(uiV)) { + fTRAP(1,uiV); + } else if (uiV == 1) { + fVIRTINSN_RTE(uiV,RxV); + } else if (uiV == 3) { + fVIRTINSN_SETIE(uiV,RxV); + } else if (uiV == 4) { + fVIRTINSN_GETIE(uiV,RxV); + } else if (uiV == 6) { + fVIRTINSN_SPSWAP(uiV,RxV); + }) + +Q6INSN(J2_pause,"pause(#u8)",ATTRIBS(A_COF,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Enter low-power state for #u8 cycles",{fPAUSE(uiV);}) -Q6INSN(Y2_icinva,"icinva(Rs32)",ATTRIBS(A_ICOP,A_ICFLUSHOP),"Instruction Cache Invalidate Address",{fEA_REG(RsV); fICINVA(EA);}) +Q6INSN(J2_rte, "rte", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NO_TIMING_LOG), +"Return from Exception", +{ +fHIDE(if((thread->timing_on) && (thread->status & EXEC_STATUS_REPLAY)) { return; }) +fHIDE(CALLBACK(thread->processor_ptr->options->rte_callback, + thread->system_ptr,thread->processor_ptr, + thread->threadId,0);) +fCLEAR_RTE_EX(); +fBRANCH(fREAD_ELR(),COF_TYPE_RTE);}) + + +/********************************************/ +/* Interrupt Management */ +/********************************************/ + +Q6INSN(Y2_swi,"swi(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Software Interrupt",{DO_SWI(RsV);}) +Q6INSN(Y2_cswi,"cswi(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Cancel Software Interrupt",{DO_CSWI(RsV);}) +Q6INSN(Y2_ciad,"ciad(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Re-enable interrupt in IAD",{DO_CIAD(RsV);}) +Q6INSN(Y4_siad,"siad(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Disable interrupt in IAD",{DO_SIAD(RsV);}) +Q6INSN(Y2_iassignr,"Rd32=iassignr(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Read interrupt to thread assignments",{DO_IASSIGNR(RsV,RdV);}) +Q6INSN(Y2_iassignw,"iassignw(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Write interrupt to thread assignments",{DO_IASSIGNW(RsV);}) + + +Q6INSN(Y2_getimask,"Rd32=getimask(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Read imask register of another thread", +{RdV = READ_IMASK(RsV & thread->processor_ptr->thread_system_mask); }) + +Q6INSN(Y2_setimask,"setimask(Pt4,Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Change imask register of another thread", +{fPREDUSE_TIMING();WRITE_IMASK(PtV & thread->processor_ptr->thread_system_mask,RsV); }) + + + +/********************************************/ +/* TLB management */ +/********************************************/ + +Q6INSN(Y2_tlbw,"tlbw(Rss32,Rt32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), +"Write TLB entry", {fTLBW(RtV,RssV);}) + +Q6INSN(Y5_ctlbw,"Rd32=ctlbw(Rss32,Rt32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), +"Conditional Write TLB entry", +{ + if (fTLB_ENTRY_OVERLAP( (1LL<<63) | RssV )) { + RdV=fTLB_ENTRY_OVERLAP_IDX( (1LL<<63) | RssV); + } else { + fTLBW(RtV,RssV); + RdV=0x80000000; + } +}) + +Q6INSN(Y5_tlboc,"Rd32=tlboc(Rss32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), +"TLB overlap check", +{ + if (fTLB_ENTRY_OVERLAP( (1LL<<63) | RssV )) { + RdV=fTLB_ENTRY_OVERLAP_IDX( (1LL<<63) | RssV); + } else { + RdV=0x80000000; + } +}) + +Q6INSN(Y2_tlbr,"Rdd32=tlbr(Rs32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Read TLB entry", +{RddV = fTLBR(RsV);}) + +Q6INSN(Y2_tlbp,"Rd32=tlbp(Rs32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Probe TLB", {RdV=fTLBP(RsV);}) + +Q6INSN(Y5_tlbasidi,"tlbinvasid(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Invalidate ASID", +{ + fHIDE(int i;) + fHIDE(unsigned int NUM_TLB_ENTRIES = NUM_TLB_REGS(thread->processor_ptr);) + for (i = 0; i < NUM_TLB_ENTRIES; i++) { + if ((fGET_FIELD(fTLBR(i),PTE_G) == 0) && + (fGET_FIELD(fTLBR(i),PTE_ASID) == fEXTRACTU_RANGE(RsV,26,20))) { + fTLBW(i,fTLBR(i) & ~(1ULL << 63)); + } + } +}) + +Q6INSN(Y2_tlblock,"tlblock", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_NO_TIMING_LOG), "Lock TLB", +{fSET_TLB_LOCK();}) + +Q6INSN(Y2_tlbunlock,"tlbunlock", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Unlock TLB", +{fCLEAR_TLB_LOCK();}) + +Q6INSN(Y2_k0lock,"k0lock", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_NO_TIMING_LOG), "Lock K0", +{fSET_K0_LOCK();}) + +Q6INSN(Y2_k0unlock,"k0unlock", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Unlock K0", +{fCLEAR_K0_LOCK();}) + +/********************************************/ +/* Supervisor Reg Management */ +/********************************************/ + +Q6INSN(Y2_crswap0,"crswap(Rx32,sgp0)",ATTRIBS(A_PRIV,A_NOTE_PRIV), "Swap system general pointer 0 with GPR", +{fHIDE(size4s_t tmp;) tmp = RxV; RxV = READ_SGP0(); WRITE_SGP0(tmp);}) +Q6INSN(Y4_crswap1,"crswap(Rx32,sgp1)",ATTRIBS(A_PRIV,A_NOTE_PRIV), "Swap system general pointer 1 with GPR", +{fHIDE(size4s_t tmp;) tmp = RxV; RxV = READ_SGP1(); WRITE_SGP1(tmp);}) + +Q6INSN(Y4_crswap10,"crswap(Rxx32,sgp1:0)",ATTRIBS(A_PRIV,A_NOTE_PRIV), "Swap system general purpose 0/1 with GPR Pair", +{fHIDE(size8s_t tmp;) tmp = RxxV; RxxV=READ_SGP10(); WRITE_SGP10(tmp);}) + +Q6INSN(Y2_tfrscrr,"Rd32=Ss128",ATTRIBS(A_PRIV,A_NOTE_PRIV),"Transfer Supervisor Reg to GPR", {RdV=SsV;}) +Q6INSN(Y2_tfrsrcr,"Sd128=Rs32",ATTRIBS(A_PRIV,A_NOTE_PRIV),"Transfer GPR to Supervisor Reg", {SdV=RsV;}) +Q6INSN(Y4_tfrscpp,"Rdd32=Sss128",ATTRIBS(A_PRIV,A_NOTE_PRIV),"Transfer Supervisor Reg to GPR", {RddV=SssV;}) +Q6INSN(Y4_tfrspcp,"Sdd128=Rss32",ATTRIBS(A_PRIV,A_NOTE_PRIV),"Transfer GPR to Supervisor Reg", {SddV=RssV;}) + +Q6INSN(G4_tfrgcrr,"Rd32=Gs32",ATTRIBS(A_GUEST,A_NOTE_GUEST),"Transfer Guest Reg to GPR", {RdV=GsV;}) +Q6INSN(G4_tfrgrcr,"Gd32=Rs32",ATTRIBS(A_GUEST,A_NOTE_GUEST),"Transfer GPR to Guest Reg", {GdV=RsV;}) +Q6INSN(G4_tfrgcpp,"Rdd32=Gss32",ATTRIBS(A_GUEST,A_NOTE_GUEST),"Transfer Guest Reg to GPR", {RddV=GssV;}) +Q6INSN(G4_tfrgpcp,"Gdd32=Rss32",ATTRIBS(A_GUEST,A_NOTE_GUEST),"Transfer GPR to Guest Reg", {GddV=RssV;}) + + + +Q6INSN(Y2_setprio,"setprio(Pt4,Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV),"Change TID Prio of another thread", +{fPREDUSE_TIMING();WRITE_PRIO(PtV & thread->processor_ptr->thread_system_mask,RsV); }) + -Q6INSN(Y2_isync,"isync",ATTRIBS(),"Memory Synchronization",{fISYNC();}) -Q6INSN(Y2_barrier,"barrier",ATTRIBS(A_RESTRICT_SLOT0ONLY),"Memory Barrier",{fBARRIER();}) -Q6INSN(Y2_syncht,"syncht",ATTRIBS(A_RESTRICT_SLOT0ONLY),"Memory Synchronization",{fSYNCH();}) -Q6INSN(Y2_dcfetchbo,"dcfetch(Rs32+#u11:3)",ATTRIBS(A_RESTRICT_PREFERSLOT0,A_DCFETCH),"Data Cache Prefetch",{fEA_RI(RsV,uiV); fDCFETCH(EA);}) +/********************************************/ +/* Power Management / Thread on/off */ +/********************************************/ +Q6INSN(Y6_diag,"diag(Rs32)",ATTRIBS(),"Send value to Diag trace module",{ +}) +Q6INSN(Y6_diag0,"diag0(Rss32,Rtt32)",ATTRIBS(),"Send values of two register to DIAG Trace. Set X=0",{ +}) +Q6INSN(Y6_diag1,"diag1(Rss32,Rtt32)",ATTRIBS(),"Send values of two register to DIAG Trace. Set X=1",{ +}) + + +Q6INSN(Y4_trace,"trace(Rs32)",ATTRIBS(A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Send value to ETM trace",{ + fDO_TRACE(RsV); +}) + +Q6INSN(Y2_stop,"stop(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),"Stop thread(s)",{ + fHIDE(RsV=RsV;) + if (!fIN_DEBUG_MODE_NO_ISDB(fGET_TNUM())) fCLEAR_RUN_MODE(fGET_TNUM()); +}) +Q6INSN(Y4_nmi,"nmi(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_NO_TIMING_LOG),"Raise NMI on thread(s)",{ + fDO_NMI(RsV); +}) + +Q6INSN(Y2_start,"start(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),"Start thread(s)",fSTART(RsV);) + +Q6INSN(Y2_wait,"wait(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_NO_TIMING_LOG),"Make thread(s) wait",{ + fHIDE(RsV=RsV;) + if (!fIN_DEBUG_MODE(fGET_TNUM())) fSET_WAIT_MODE(fGET_TNUM()); + fIN_DEBUG_MODE_WARN(fGET_TNUM()); +}) + +Q6INSN(Y2_resume,"resume(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),"Make thread(s) stop waiting",fRESUME(RsV);) + +Q6INSN(Y2_break,"brkpt",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),"Breakpoint",{fBREAK();}) + + +/********************************************/ +/* Cache Management */ +/********************************************/ -Q6INSN(Y2_dczeroa,"dczeroa(Rs32)",ATTRIBS(A_STORE,A_RESTRICT_SLOT0ONLY,A_DCZEROA),"Zero an aligned 32-byte cacheline",{fEA_REG(RsV); fDCZEROA(EA);}) -Q6INSN(Y2_dccleana,"dccleana(Rs32)",ATTRIBS(A_RESTRICT_SLOT0ONLY,A_DCFLUSHOP),"Data Cache Clean Address",{fEA_REG(RsV); fDCCLEANA(EA);}) -Q6INSN(Y2_dccleaninva,"dccleaninva(Rs32)",ATTRIBS(A_RESTRICT_SLOT0ONLY,A_DCFLUSHOP),"Data Cache Clean and Invalidate Address",{fEA_REG(RsV); fDCCLEANINVA(EA);}) -Q6INSN(Y2_dcinva,"dcinva(Rs32)",ATTRIBS(A_RESTRICT_SLOT0ONLY,A_DCFLUSHOP),"Data Cache Invalidate Address",{fEA_REG(RsV); fDCCLEANINVA(EA);}) +Q6INSN(Y2_ictagr,"Rd32=ictagr(Rs32)",ATTRIBS(A_ICOP,A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_ICTAGOP),"Instruction Cache Tag Read",{fICTAGR(RsV,RdV,RdN);}) +Q6INSN(Y2_ictagw,"ictagw(Rs32,Rt32)",ATTRIBS(A_ICOP,A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_ICTAGOP),"Instruction Cache Tag Write",{fICTAGW(RsV,RtV);}) +Q6INSN(Y2_icdataw,"icdataw(Rs32,Rt32)",ATTRIBS(A_ICOP,A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_ICTAGOP),"Instruction Cache Data Write",{fICDATAW(RsV,RtV);}) +Q6INSN(Y2_icdatar,"Rd32=icdatar(Rs32)",ATTRIBS(A_ICOP,A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_ICTAGOP),"Instruction Cache Data Read",{fICDATAR(RsV, RdV);}) +Q6INSN(Y2_icinva,"icinva(Rs32)",ATTRIBS(A_ICOP,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYADDRESS,A_ICFLUSHOP),"Instruction Cache Invalidate Address",{fEA_REG(RsV); fICINVA(EA);}) +Q6INSN(Y2_icinvidx,"icinvidx(Rs32)",ATTRIBS(A_ICOP,A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_ICFLUSHOP),"Instruction Cache Invalidate Index",{fICINVIDX(RsV);}) +Q6INSN(Y2_ickill,"ickill",ATTRIBS(A_ICOP,A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_ICFLUSHOP),"Instruction Cache Invalidate",{fICKILL();}) +Q6INSN(Y2_isync,"isync",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),"Memory Synchronization",{fISYNC();}) +Q6INSN(Y2_barrier,"barrier",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK),"Memory Barrier",{fBARRIER();}) +Q6INSN(Y2_syncht,"syncht",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET),"Memory Synchronization",{fSYNCH();}) -Q6INSN(Y4_l2fetch,"l2fetch(Rs32,Rt32)",ATTRIBS(A_RESTRICT_SLOT0ONLY),"L2 Cache Prefetch", + +Q6INSN(Y2_dcfetchbo,"dcfetch(Rs32+#u11:3)",ATTRIBS(A_RESTRICT_PREFERSLOT0,A_DCFETCH,A_RESTRICT_NOSLOT1_STORE),"Data Cache Prefetch",{fEA_RI(RsV,uiV); fDCFETCH(EA);}) +Q6INSN(Y2_dckill,"dckill",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_DCFLUSHOP),"Data Cache Invalidate",{fDCKILL();}) + + +Q6INSN(Y2_dczeroa,"dczeroa(Rs32)",ATTRIBS(A_STORE,A_RESTRICT_SLOT1_AOK,A_NOTE_SLOT1_AOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYADDRESS,A_DCZEROA),"Zero an aligned 32-byte cacheline",{fEA_REG(RsV); fDCZEROA(EA);}) +Q6INSN(Y2_dccleana,"dccleana(Rs32)",ATTRIBS(A_RESTRICT_SLOT1_AOK,A_NOTE_SLOT1_AOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYADDRESS,A_DCFLUSHOP),"Data Cache Clean Address",{fEA_REG(RsV); fDCCLEANA(EA);}) +Q6INSN(Y2_dccleanidx,"dccleanidx(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_DCFLUSHOP),"Data Cache Clean Index",{fDCCLEANIDX(RsV);}) +Q6INSN(Y2_dccleaninva,"dccleaninva(Rs32)",ATTRIBS(A_RESTRICT_SLOT1_AOK,A_NOTE_SLOT1_AOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYADDRESS,A_DCFLUSHOP),"Data Cache Clean and Invalidate Address",{fEA_REG(RsV); fDCCLEANINVA(EA);}) +Q6INSN(Y2_dccleaninvidx,"dccleaninvidx(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_DCFLUSHOP),"Data Cache Clean and Invalidate Index",{fDCCLEANINVIDX(RsV);}) +Q6INSN(Y2_dcinva,"dcinva(Rs32)",ATTRIBS(A_RESTRICT_SLOT1_AOK,A_NOTE_SLOT1_AOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYADDRESS,A_DCFLUSHOP),"Data Cache Invalidate Address",{fEA_REG(RsV); fDCCLEANINVA(EA);}) +Q6INSN(Y2_dcinvidx,"dcinvidx(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_DCFLUSHOP),"Data Cache Invalidate Index",{fDCINVIDX(RsV);}) +Q6INSN(Y2_dctagr,"Rd32=dctagr(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_DCTAGOP),"Data Cache Tag Read",{fDCTAGR(RsV,RdV,RdN);}) +Q6INSN(Y2_dctagw,"dctagw(Rs32,Rt32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_RESTRICT_SLOT0ONLY,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_DCTAGOP),"Data Cache Tag Write",{fDCTAGW(RsV,RtV);}) + + +Q6INSN(Y2_l2kill,"l2kill",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Cache Invalidate",{fL2KILL();}) +Q6INSN(Y4_l2tagw,"l2tagw(Rs32,Rt32)",ATTRIBS(A_PRIV,A_NOTE_BADTAG_UNDEF,A_NOTE_PRIV,A_RESTRICT_SLOT0ONLY,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_L2TAGOP),"L2 Cache Tag Write",{fL2TAGW(RsV,RtV);}) +Q6INSN(Y4_l2tagr,"Rd32=l2tagr(Rs32)",ATTRIBS(A_PRIV,A_NOTE_BADTAG_UNDEF,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_L2TAGOP),"L2 Cache Tag Read",{fL2TAGR(RsV,RdV,RdN);}) + +Q6INSN(Y2_l2cleaninvidx,"l2cleaninvidx(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_L2FLUSHOP),"L2 Cache Clean and Invalidate Index",{fL2CLEANINVIDX(RsV); }) +Q6INSN(Y5_l2cleanidx,"l2cleanidx(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_L2FLUSHOP),"L2 Cache Clean by Index",{fL2CLEANIDX(RsV); }) +Q6INSN(Y5_l2invidx,"l2invidx(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_L2FLUSHOP),"L2 Cache Invalidate by Index",{fL2INVIDX(RsV); }) + + + +Q6INSN(Y4_l2fetch,"l2fetch(Rs32,Rt32)",ATTRIBS(A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK),"L2 Cache Prefetch", { fL2FETCH(RsV, (RtV&0xff), /*height*/ ((RtV>>8)&0xff), /*width*/ @@ -59,10 +258,28 @@ Q6INSN(Y4_l2fetch,"l2fetch(Rs32,Rt32)",ATTRIBS(A_RESTRICT_SLOT0ONLY),"L2 Cache P -Q6INSN(Y5_l2fetch,"l2fetch(Rs32,Rtt32)",ATTRIBS(A_RESTRICT_SLOT0ONLY),"L2 Cache Prefetch", +Q6INSN(Y5_l2fetch,"l2fetch(Rs32,Rtt32)",ATTRIBS(A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK),"L2 Cache Prefetch", { fL2FETCH(RsV, fGETUHALF(0,RttV), /*height*/ fGETUHALF(1,RttV), /*width*/ fGETUHALF(2,RttV), /*stride*/ fGETUHALF(3,RttV)); /*flags*/ }) + +Q6INSN(Y5_l2locka,"Pd4=l2locka(Rs32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_CACHEOP,A_COPBYADDRESS,A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK,A_RESTRICT_LATEPRED,A_NOTE_LATEPRED), +"Lock L2 cache line by address", { fEA_REG(RsV); fL2LOCKA(EA,PdV,PdN); fHIDE(MARK_LATE_PRED_WRITE(PdN)) }) + + +Q6INSN(Y5_l2unlocka,"l2unlocka(Rs32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_CACHEOP,A_COPBYADDRESS,A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK), "UnLock L2 cache line by address", { fEA_REG(RsV); fL2UNLOCKA(EA); }) + + + +Q6INSN(Y5_l2gunlock,"l2gunlock",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Global Unlock",{fL2UNLOCK();}) + +Q6INSN(Y5_l2gclean,"l2gclean",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Global Clean",{fL2CLEAN();}) + +Q6INSN(Y5_l2gcleaninv,"l2gcleaninv",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Global Clean and Invalidate",{fL2CLEANINV();}) + +Q6INSN(Y6_l2gcleanpa,"l2gclean(Rtt32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Global Clean by PA Range",{fL2CLEANPA(RttV);}) + +Q6INSN(Y6_l2gcleaninvpa,"l2gcleaninv(Rtt32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Global Clean and Invalidate by PA Range",{fL2CLEANINVPA(RttV);}) diff --git a/target/hexagon/internal.h b/target/hexagon/internal.h index 5fc837ae22..00b37aea7a 100644 --- a/target/hexagon/internal.h +++ b/target/hexagon/internal.h @@ -30,5 +30,27 @@ void hexagon_debug_qreg(CPUHexagonState *env, int regnum); void hexagon_debug(CPUHexagonState *env); extern const char * const hexagon_regnames[TOTAL_PER_THREAD_REGS]; +#ifndef CONFIG_USER_ONLY +extern const char * const hexagon_sregnames[]; +extern const char * const hexagon_gregnames[]; +#endif + +void G_NORETURN do_raise_exception(CPUHexagonState *env, + uint32_t exception, + uint32_t PC, + uintptr_t retaddr); + +#define hexagon_cpu_mmu_enabled(env) ({ \ + HexagonCPU *cpu = env_archcpu(env); \ + cpu->globalregs ? \ + GET_SYSCFG_FIELD(SYSCFG_MMUEN, \ + hexagon_globalreg_read(cpu->globalregs, \ + HEX_SREG_SYSCFG, (env)->threadId)) : \ + 0; \ +}) + +#ifndef CONFIG_USER_ONLY +extern const VMStateDescription vmstate_hexagon_cpu; +#endif #endif diff --git a/target/hexagon/machine.c b/target/hexagon/machine.c new file mode 100644 index 0000000000..2dd95466e7 --- /dev/null +++ b/target/hexagon/machine.c @@ -0,0 +1,32 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "migration/vmstate.h" +#include "cpu.h" + +const VMStateDescription vmstate_hexagon_cpu = { + .name = "cpu", + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(env.gpr, HexagonCPU, TOTAL_PER_THREAD_REGS), + VMSTATE_UINT32_ARRAY(env.pred, HexagonCPU, NUM_PREGS), + VMSTATE_UINT32_ARRAY(env.t_sreg, HexagonCPU, NUM_SREGS), + VMSTATE_UINT32_ARRAY(env.greg, HexagonCPU, NUM_GREGS), + VMSTATE_UINT32(env.next_PC, HexagonCPU), + VMSTATE_UINT32(env.tlb_lock_state, HexagonCPU), + VMSTATE_UINT32(env.k0_lock_state, HexagonCPU), + VMSTATE_UINT32(env.tlb_lock_count, HexagonCPU), + VMSTATE_UINT32(env.k0_lock_count, HexagonCPU), + VMSTATE_UINT32(env.threadId, HexagonCPU), + VMSTATE_UINT32(env.cause_code, HexagonCPU), + VMSTATE_UINT32(env.wait_next_pc, HexagonCPU), + VMSTATE_UINT64(env.t_cycle_count, HexagonCPU), + + VMSTATE_END_OF_LIST() + }, +}; diff --git a/target/hexagon/macros.h b/target/hexagon/macros.h index eebfe1e5ed..21ab8ae5bb 100644 --- a/target/hexagon/macros.h +++ b/target/hexagon/macros.h @@ -538,9 +538,6 @@ static inline TCGv gen_read_ireg(TCGv result, TCGv val, int shift) #ifdef CONFIG_USER_ONLY #define fFRAMECHECK(ADDR, EA) do { } while (0) /* Not modelled in linux-user */ -#else -/* System mode not implemented yet */ -#define fFRAMECHECK(ADDR, EA) g_assert_not_reached(); #endif #ifdef QEMU_GENERATE @@ -631,8 +628,18 @@ static inline TCGv gen_read_ireg(TCGv result, TCGv val, int shift) #define fCONSTLL(A) A##LL #define fECHO(A) (A) -#define fTRAP(TRAPTYPE, IMM) helper_raise_exception(env, HEX_EXCP_TRAP0) +#ifdef CONFIG_USER_ONLY +#define fTRAP(TRAPTYPE, IMM) \ + do { \ + hexagon_raise_exception_err(env, HEX_EVENT_TRAP0, PC); \ + } while (0) +#endif + +#define fDO_TRACE(SREG) +#define fBREAK() +#define fUNPAUSE() #define fPAUSE(IMM) +#define fDCFETCH(REG) #define fALIGN_REG_FIELD_VALUE(FIELD, VAL) \ ((VAL) << reg_field_info[FIELD].offset) @@ -643,6 +650,16 @@ static inline TCGv gen_read_ireg(TCGv result, TCGv val, int shift) reg_field_info[FIELD].width, \ reg_field_info[FIELD].offset) +#define fGET_FIELD(VAL, FIELD) \ + fEXTRACTU_BITS(VAL, \ + reg_field_info[FIELD].width, \ + reg_field_info[FIELD].offset) +#define fSET_FIELD(VAL, FIELD, NEWVAL) \ + fINSERT_BITS(VAL, \ + reg_field_info[FIELD].width, \ + reg_field_info[FIELD].offset, \ + (NEWVAL)) + #ifdef QEMU_GENERATE #define fDCZEROA(REG) \ do { \ @@ -654,5 +671,18 @@ static inline TCGv gen_read_ireg(TCGv result, TCGv val, int shift) #define fBRANCH_SPECULATE_STALL(DOTNEWVAL, JUMP_COND, SPEC_DIR, HINTBITNUM, \ STRBITNUM) /* Nothing */ +#ifdef CONFIG_USER_ONLY +/* + * This macro can only be true in guest mode. + * In user mode, the 4 VIRTINSN's can't be reached + */ +#define fTRAP1_VIRTINSN(IMM) (false) +#define fVIRTINSN_SPSWAP(IMM, REG) g_assert_not_reached() +#define fVIRTINSN_GETIE(IMM, REG) g_assert_not_reached() +#define fVIRTINSN_SETIE(IMM, REG) g_assert_not_reached() +#define fVIRTINSN_RTE(IMM, REG) g_assert_not_reached() +#endif + +#define fPREDUSE_TIMING() #endif diff --git a/target/hexagon/meson.build b/target/hexagon/meson.build index d169cf71b2..59cb09c107 100644 --- a/target/hexagon/meson.build +++ b/target/hexagon/meson.build @@ -20,6 +20,7 @@ hexagon_ss = ss.source_set() hex_common_py = 'hex_common.py' gen_tcg_h = meson.current_source_dir() / 'gen_tcg.h' gen_tcg_hvx_h = meson.current_source_dir() / 'gen_tcg_hvx.h' +gen_tcg_sys_h = meson.current_source_dir() / 'gen_tcg_sys.h' idef_parser_dir = meson.current_source_dir() / 'idef-parser' # @@ -234,6 +235,7 @@ decodetree_trans_funcs_generated = custom_target( command: [python, files('gen_trans_funcs.py'), semantics_generated, '@OUTPUT@'], ) hexagon_ss.add(decodetree_trans_funcs_generated) +hexagon_softmmu_ss = ss.source_set() hexagon_ss.add(files( 'cpu.c', @@ -252,6 +254,14 @@ hexagon_ss.add(files( 'mmvec/system_ext_mmvec.c', )) +hexagon_softmmu_ss.add(files( + 'cpu_helper.c', + 'hex_mmu.c', + 'hex_interrupts.c', + 'hexswi.c', + 'machine.c', +)) + # # Step 4.5 # We use flex/bison based idef-parser to generate TCG code for a lot @@ -261,7 +271,8 @@ hexagon_ss.add(files( # idef-generated-enabled-instructions # idef_parser_enabled = get_option('hexagon_idef_parser') -if idef_parser_enabled and 'hexagon-linux-user' in target_dirs +if idef_parser_enabled and ('hexagon-linux-user' in target_dirs or + 'hexagon-softmmu' in target_dirs) idef_parser_input_generated = custom_target( 'idef_parser_input.h.inc', output: 'idef_parser_input.h.inc', @@ -270,13 +281,13 @@ if idef_parser_enabled and 'hexagon-linux-user' in target_dirs command: [python, files('gen_idef_parser_funcs.py'), semantics_generated, '@OUTPUT@'], ) - compiler = meson.get_compiler('c').get_id() + compiler = meson.get_compiler('c').cmd_array() preprocessed_idef_parser_input_generated = custom_target( 'idef_parser_input.preprocessed.h.inc', output: 'idef_parser_input.preprocessed.h.inc', input: idef_parser_input_generated, depend_files: [idef_parser_dir / 'macros.h.inc'], - command: [compiler, '-x', 'c', '-E', '-I', idef_parser_dir, '-o', '@OUTPUT@', '@INPUT@'], + command: compiler + ['-x', 'c', '-E', '-I', idef_parser_dir, '-o', '@OUTPUT@', '@INPUT@'], ) flex = generator( @@ -337,12 +348,12 @@ if idef_parser_enabled and 'hexagon-linux-user' in target_dirs # Setup input and dependencies for the next step, this depends on whether or # not idef-parser is enabled helper_dep = [semantics_generated, idef_generated_tcg_c, idef_generated_tcg] - helper_in = [semantics_generated, gen_tcg_h, gen_tcg_hvx_h, '--idef-parser', idef_generated_list] + helper_in = [semantics_generated, gen_tcg_h, gen_tcg_hvx_h, gen_tcg_sys_h, '--idef-parser', idef_generated_list] else # Setup input and dependencies for the next step, this depends on whether or # not idef-parser is enabled helper_dep = [semantics_generated] - helper_in = [semantics_generated, gen_tcg_h, gen_tcg_hvx_h] + helper_in = [semantics_generated, gen_tcg_h, gen_tcg_hvx_h, gen_tcg_sys_h] endif # @@ -356,7 +367,7 @@ helper_protos_generated = custom_target( 'helper_protos_generated.h.inc', output: 'helper_protos_generated.h.inc', depends: helper_dep, - depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h], + depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h, gen_tcg_sys_h], command: [python, files('gen_helper_protos.py'), helper_in, '@OUTPUT@'], ) hexagon_ss.add(helper_protos_generated) @@ -365,7 +376,7 @@ helper_funcs_generated = custom_target( 'helper_funcs_generated.c.inc', output: 'helper_funcs_generated.c.inc', depends: helper_dep, - depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h], + depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h, gen_tcg_sys_h], command: [python, files('gen_helper_funcs.py'), helper_in, '@OUTPUT@'], ) hexagon_ss.add(helper_funcs_generated) @@ -374,7 +385,7 @@ tcg_funcs_generated = custom_target( 'tcg_funcs_generated.c.inc', output: 'tcg_funcs_generated.c.inc', depends: helper_dep, - depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h], + depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h, gen_tcg_sys_h], command: [python, files('gen_tcg_funcs.py'), helper_in, '@OUTPUT@'], ) hexagon_ss.add(tcg_funcs_generated) @@ -383,9 +394,10 @@ analyze_funcs_generated = custom_target( 'analyze_funcs_generated.c.inc', output: 'analyze_funcs_generated.c.inc', depends: helper_dep, - depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h], + depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h, gen_tcg_sys_h], command: [python, files('gen_analyze_funcs.py'), helper_in, '@OUTPUT@'], ) hexagon_ss.add(analyze_funcs_generated) target_arch += {'hexagon': hexagon_ss} +target_system_arch += {'hexagon': hexagon_softmmu_ss} diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c index 0a94643364..125952aee5 100644 --- a/target/hexagon/op_helper.c +++ b/target/hexagon/op_helper.c @@ -20,36 +20,59 @@ #include "accel/tcg/cpu-ldst.h" #include "accel/tcg/cpu-loop.h" #include "accel/tcg/probe.h" +#include "qemu/main-loop.h" +#include "cpu.h" #include "exec/helper-proto.h" #include "fpu/softfloat.h" -#include "cpu.h" +#include "exec/cpu-interrupt.h" #include "internal.h" #include "macros.h" +#include "sys_macros.h" #include "arch.h" #include "hex_arch_types.h" #include "fma_emu.h" #include "mmvec/mmvec.h" #include "mmvec/macros.h" #include "op_helper.h" +#include "cpu_helper.h" #include "translate.h" +#ifndef CONFIG_USER_ONLY +#include "hw/hexagon/hexagon_globalreg.h" +#include "hex_mmu.h" +#include "hw/hexagon/hexagon_tlb.h" +#include "hex_interrupts.h" +#include "hexswi.h" +#endif #define SF_BIAS 127 #define SF_MANTBITS 23 /* Exceptions processing helpers */ +G_NORETURN +void do_raise_exception(CPUHexagonState *env, uint32_t exception, + uint32_t PC, uintptr_t retaddr) +{ + CPUState *cs = env_cpu(env); + qemu_log_mask(CPU_LOG_INT, "%s: 0x%08" PRIx32 ", @ %08" PRIx32 "\n", + __func__, exception, PC); + ASSERT_DIRECT_TO_GUEST_UNSET(env, exception); + + env->gpr[HEX_REG_PC] = PC; + cs->exception_index = exception; + cpu_loop_exit_restore(cs, retaddr); +} + G_NORETURN void hexagon_raise_exception_err(CPUHexagonState *env, uint32_t exception, uintptr_t pc) { - CPUState *cs = env_cpu(env); - qemu_log_mask(CPU_LOG_INT, "%s: %d\n", __func__, exception); - cs->exception_index = exception; - cpu_loop_exit_restore(cs, pc); + do_raise_exception(env, exception, pc, 0); } -G_NORETURN void HELPER(raise_exception)(CPUHexagonState *env, uint32_t excp) +G_NORETURN void HELPER(raise_exception)(CPUHexagonState *env, uint32_t excp, + uint32_t PC) { - hexagon_raise_exception_err(env, excp, 0); + hexagon_raise_exception_err(env, excp, PC); } void log_store32(CPUHexagonState *env, target_ulong addr, @@ -1205,6 +1228,131 @@ float64 HELPER(dfmpyhh)(CPUHexagonState *env, float64 RxxV, return RxxV; } +#ifndef CONFIG_USER_ONLY +void HELPER(modify_ssr)(CPUHexagonState *env, uint32_t new, uint32_t old) +{ + BQL_LOCK_GUARD(); + hexagon_modify_ssr(env, new, old); +} + +static void hex_k0_lock(CPUHexagonState *env) +{ + HexagonCPU *cpu = env_archcpu(env); + CPUState *cs = env_cpu(env); + target_ulong syscfg; + + BQL_LOCK_GUARD(); + g_assert((env->k0_lock_count == 0) || (env->k0_lock_count == 1)); + + syscfg = cpu->globalregs ? + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SYSCFG, + env->threadId) : 0; + if (GET_SYSCFG_FIELD(SYSCFG_K0LOCK, syscfg)) { + if (env->k0_lock_state == HEX_LOCK_QUEUED) { + env->next_PC += 4; + env->k0_lock_count++; + env->k0_lock_state = HEX_LOCK_OWNER; + SET_SYSCFG_FIELD(env, SYSCFG_K0LOCK, 1); + return; + } + if (env->k0_lock_state == HEX_LOCK_OWNER) { + qemu_log_mask(LOG_GUEST_ERROR, + "Double k0lock at PC: 0x%" PRIx32 + ", thread may hang\n", + env->next_PC); + env->next_PC += 4; + cpu_interrupt(cs, CPU_INTERRUPT_HALT); + cpu_loop_exit(cs); + return; + } + env->k0_lock_state = HEX_LOCK_WAITING; + cpu_interrupt(cs, CPU_INTERRUPT_HALT); + cpu_loop_exit(cs); + } else { + env->next_PC += 4; + env->k0_lock_count++; + env->k0_lock_state = HEX_LOCK_OWNER; + SET_SYSCFG_FIELD(env, SYSCFG_K0LOCK, 1); + } +} + +static void hex_k0_unlock(CPUHexagonState *env) +{ + HexagonCPU *cpu = env_archcpu(env); + unsigned int this_threadId = env->threadId; + CPUHexagonState *unlock_thread = NULL; + CPUState *cs; + target_ulong syscfg; + + BQL_LOCK_GUARD(); + g_assert((env->k0_lock_count == 0) || (env->k0_lock_count == 1)); + + /* Nothing to do if the k0 isn't locked by this thread */ + syscfg = cpu->globalregs ? + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SYSCFG, + env->threadId) : 0; + if ((GET_SYSCFG_FIELD(SYSCFG_K0LOCK, syscfg) == 0) || + (env->k0_lock_state != HEX_LOCK_OWNER)) { + qemu_log_mask(LOG_GUEST_ERROR, + "thread %" PRIu32 " attempted to unlock k0 without" + " having the lock, k0_lock state = %u," + " syscfg:k0 = %" PRIu32 "\n", + env->threadId, (unsigned)env->k0_lock_state, + (uint32_t)GET_SYSCFG_FIELD(SYSCFG_K0LOCK, syscfg)); + g_assert(env->k0_lock_state != HEX_LOCK_WAITING); + return; + } + + env->k0_lock_count--; + env->k0_lock_state = HEX_LOCK_UNLOCKED; + SET_SYSCFG_FIELD(env, SYSCFG_K0LOCK, 0); + + /* Look for a thread to unlock */ + CPU_FOREACH(cs) { + CPUHexagonState *thread = cpu_env(cs); + + /* + * The hardware implements round-robin fairness, so we look for threads + * starting at env->threadId + 1 and incrementing modulo the number of + * threads. + * + * To implement this, we check if thread is a earlier in the modulo + * sequence than unlock_thread. + * if unlock thread is higher than this thread + * thread must be between this thread and unlock_thread + * else + * thread higher than this thread is ahead of unlock_thread + * thread must be lower then unlock thread + */ + if (thread->k0_lock_state == HEX_LOCK_WAITING) { + if (!unlock_thread) { + unlock_thread = thread; + } else if (unlock_thread->threadId > this_threadId) { + if (this_threadId < thread->threadId && + thread->threadId < unlock_thread->threadId) { + unlock_thread = thread; + } + } else { + if (thread->threadId > this_threadId) { + unlock_thread = thread; + } + if (thread->threadId < unlock_thread->threadId) { + unlock_thread = thread; + } + } + } + } + if (unlock_thread) { + cs = env_cpu(unlock_thread); + unlock_thread->k0_lock_state = HEX_LOCK_QUEUED; + SET_SYSCFG_FIELD(unlock_thread, SYSCFG_K0LOCK, 1); + cpu_interrupt(cs, CPU_INTERRUPT_K0_UNLOCK); + } + +} +#endif + + /* Histogram instructions */ void HELPER(vhist)(CPUHexagonState *env) @@ -1370,6 +1518,420 @@ void HELPER(vwhist128qm)(CPUHexagonState *env, int32_t uiV) } } +#ifndef CONFIG_USER_ONLY +void HELPER(raise_stack_overflow)(CPUHexagonState *env, uint32_t slot, + uint32_t badva) +{ + /* + * Per section 7.3.1 of the V67 Programmer's Reference, + * stack limit exception isn't raised in monitor mode. + */ + uint32_t ssr = env->t_sreg[HEX_SREG_SSR]; + CPUState *cs; + + if (GET_SSR_FIELD(SSR_EX, ssr) || + !GET_SSR_FIELD(SSR_UM, ssr)) { + return; + } + + cs = env_cpu(env); + cs->exception_index = HEX_EVENT_PRECISE; + env->cause_code = HEX_CAUSE_STACK_LIMIT; + ASSERT_DIRECT_TO_GUEST_UNSET(env, cs->exception_index); + + if (slot == 0) { + env->t_sreg[HEX_SREG_BADVA0] = badva; + SET_SSR_FIELD(env, SSR_V0, 1); + SET_SSR_FIELD(env, SSR_V1, 0); + SET_SSR_FIELD(env, SSR_BVS, 0); + } else if (slot == 1) { + env->t_sreg[HEX_SREG_BADVA1] = badva; + SET_SSR_FIELD(env, SSR_V0, 0); + SET_SSR_FIELD(env, SSR_V1, 1); + SET_SSR_FIELD(env, SSR_BVS, 1); + } else { + g_assert_not_reached(); + } + cpu_loop_exit_restore(cs, 0); +} + +void HELPER(ciad)(CPUHexagonState *env, uint32_t mask) +{ + g_assert_not_reached(); +} + +void HELPER(siad)(CPUHexagonState *env, uint32_t mask) +{ + uint32_t ipendad; + uint32_t iad; + HexagonCPU *cpu; + + BQL_LOCK_GUARD(); + cpu = env_archcpu(env); + ipendad = cpu->globalregs ? + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD, + env->threadId) : 0; + iad = fGET_FIELD(ipendad, IPENDAD_IAD); + fSET_FIELD(ipendad, IPENDAD_IAD, iad | mask); + if (cpu->globalregs) { + hexagon_globalreg_write(cpu->globalregs, HEX_SREG_IPENDAD, + ipendad, env->threadId); + } + hex_interrupt_update(env); +} + +void HELPER(swi)(CPUHexagonState *env, uint32_t mask) +{ + BQL_LOCK_GUARD(); + hex_raise_interrupts(env, mask, CPU_INTERRUPT_SWI); +} + +void HELPER(cswi)(CPUHexagonState *env, uint32_t mask) +{ + BQL_LOCK_GUARD(); + hex_clear_interrupts(env, mask, CPU_INTERRUPT_SWI); +} + +void HELPER(iassignw)(CPUHexagonState *env, uint32_t src) +{ + uint32_t modectl; + uint32_t thread_enabled_mask; + CPUState *cpu; + HexagonCPU *hex_cpu; + + BQL_LOCK_GUARD(); + hex_cpu = env_archcpu(env); + modectl = hex_cpu->globalregs ? + hexagon_globalreg_read(hex_cpu->globalregs, HEX_SREG_MODECTL, + env->threadId) : 0; + thread_enabled_mask = GET_FIELD(MODECTL_E, modectl); + + CPU_FOREACH(cpu) { + CPUHexagonState *thread_env = &(HEXAGON_CPU(cpu)->env); + uint32_t thread_id_mask = 0x1 << thread_env->threadId; + if (thread_enabled_mask & thread_id_mask) { + uint32_t imask = thread_env->t_sreg[HEX_SREG_IMASK]; + uint32_t intbitpos = (src >> 16) & 0xF; + uint32_t val = (src >> thread_env->threadId) & 0x1; + imask = deposit32(imask, intbitpos, 1, val); + thread_env->t_sreg[HEX_SREG_IMASK] = imask; + + qemu_log_mask(CPU_LOG_INT, "%s: thread " TARGET_FMT_ld + ", new imask 0x%" PRIx32 "\n", __func__, + thread_env->threadId, imask); + } + } + hex_interrupt_update(env); +} + +uint32_t HELPER(iassignr)(CPUHexagonState *env, uint32_t src) +{ + uint32_t modectl; + uint32_t thread_enabled_mask; + uint32_t intbitpos; + uint32_t dest_reg; + CPUState *cpu; + HexagonCPU *hex_cpu; + + BQL_LOCK_GUARD(); + hex_cpu = env_archcpu(env); + modectl = hex_cpu->globalregs ? + hexagon_globalreg_read(hex_cpu->globalregs, HEX_SREG_MODECTL, + env->threadId) : 0; + thread_enabled_mask = GET_FIELD(MODECTL_E, modectl); + /* src fields are in same position as modectl, but mean different things */ + intbitpos = GET_FIELD(MODECTL_W, src); + dest_reg = 0; + CPU_FOREACH(cpu) { + CPUHexagonState *thread_env = &(HEXAGON_CPU(cpu)->env); + uint32_t thread_id_mask = 0x1 << thread_env->threadId; + if (thread_enabled_mask & thread_id_mask) { + uint32_t imask = thread_env->t_sreg[HEX_SREG_IMASK]; + dest_reg |= ((imask >> intbitpos) & 0x1) << thread_env->threadId; + } + } + + return dest_reg; +} + +void HELPER(start)(CPUHexagonState *env, uint32_t imask) +{ + hexagon_start_threads(env, imask); +} + +void HELPER(stop)(CPUHexagonState *env) +{ + hexagon_stop_thread(env); +} + +static void set_wait_mode(CPUHexagonState *env) +{ + HexagonCPU *cpu; + uint32_t modectl; + uint32_t thread_wait_mask; + + g_assert(bql_locked()); + + cpu = env_archcpu(env); + if (!cpu->globalregs) { + return; + } + modectl = + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_MODECTL, + env->threadId); + thread_wait_mask = GET_FIELD(MODECTL_W, modectl); + thread_wait_mask |= 0x1 << env->threadId; + SET_SYSTEM_FIELD(env, HEX_SREG_MODECTL, MODECTL_W, thread_wait_mask); +} + +static void hexagon_wait_thread(CPUHexagonState *env, uint32_t PC) +{ + CPUState *cs; + + g_assert(bql_locked()); + + if (qemu_loglevel_mask(LOG_GUEST_ERROR) && + (env->k0_lock_state != HEX_LOCK_UNLOCKED || + env->tlb_lock_state != HEX_LOCK_UNLOCKED)) { + qemu_log("WARNING: executing wait() with acquired lock" + "may lead to deadlock\n"); + } + g_assert(get_exe_mode(env) != HEX_EXE_MODE_WAIT); + + cs = env_cpu(env); + /* + * The addtion of cpu_has_work is borrowed from arm's wfi helper + * and is critical for our stability + */ + if ((cs->exception_index != HEX_EVENT_NONE) || + (cpu_has_work(cs))) { + qemu_log_mask(CPU_LOG_INT, + "%s: thread %" PRIu32 " skipping WAIT mode, have some work\n", + __func__, env->threadId); + return; + } + set_wait_mode(env); + env->wait_next_pc = PC + 4; + + cpu_interrupt(cs, CPU_INTERRUPT_HALT); +} + +static inline QEMU_ALWAYS_INLINE void resched(CPUHexagonState *env) +{ + uint32_t schedcfg; + uint32_t schedcfg_en; + int int_number; + CPUState *cs; + uint32_t lowest_th_prio = 0; /* 0 is highest prio */ + uint32_t bestwait_reg; + uint32_t best_prio; + HexagonCPU *cpu; + + BQL_LOCK_GUARD(); + qemu_log_mask(CPU_LOG_INT, "%s: check resched\n", __func__); + cpu = env_archcpu(env); + schedcfg = cpu->globalregs ? + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SCHEDCFG, + env->threadId) : 0; + schedcfg_en = GET_FIELD(SCHEDCFG_EN, schedcfg); + int_number = GET_FIELD(SCHEDCFG_INTNO, schedcfg); + + if (!schedcfg_en) { + return; + } + + CPU_FOREACH(cs) { + HexagonCPU *thread = HEXAGON_CPU(cs); + CPUHexagonState *thread_env = &(thread->env); + uint32_t th_prio = GET_FIELD( + STID_PRIO, thread_env->t_sreg[HEX_SREG_STID]); + if (!hexagon_thread_is_enabled(thread_env)) { + continue; + } + + lowest_th_prio = (lowest_th_prio > th_prio) + ? lowest_th_prio + : th_prio; + } + + bestwait_reg = cpu->globalregs ? + hexagon_globalreg_read(cpu->globalregs, HEX_SREG_BESTWAIT, + env->threadId) : 0; + best_prio = GET_FIELD(BESTWAIT_PRIO, bestwait_reg); + + /* + * If the lowest priority thread is lower priority than the + * value in the BESTWAIT register, we must raise the reschedule + * interrupt on the lowest priority thread. + */ + if (lowest_th_prio > best_prio) { + qemu_log_mask(CPU_LOG_INT, + "%s: raising resched int %u," + " cur PC 0x%" PRIx32 "\n", + __func__, (unsigned)int_number, env->gpr[HEX_REG_PC]); + SET_SYSTEM_FIELD(env, HEX_SREG_BESTWAIT, BESTWAIT_PRIO, ~0); + hex_raise_interrupts(env, 1 << int_number, CPU_INTERRUPT_SWI); + } +} + +void HELPER(resched)(CPUHexagonState *env) +{ + resched(env); +} + +void HELPER(wait)(CPUHexagonState *env, uint32_t PC) +{ + BQL_LOCK_GUARD(); + + if (!fIN_DEBUG_MODE(env->threadId)) { + hexagon_wait_thread(env, PC); + } +} + +void HELPER(resume)(CPUHexagonState *env, uint32_t mask) +{ + BQL_LOCK_GUARD(); + hexagon_resume_threads(env, mask); +} + +uint32_t HELPER(getimask)(CPUHexagonState *env, uint32_t tid) +{ + CPUState *cs; + BQL_LOCK_GUARD(); + CPU_FOREACH(cs) { + HexagonCPU *found_cpu = HEXAGON_CPU(cs); + CPUHexagonState *found_env = &found_cpu->env; + if (found_env->threadId == tid) { + uint32_t imask = found_env->t_sreg[HEX_SREG_IMASK]; + qemu_log_mask(CPU_LOG_INT, "%s: tid " TARGET_FMT_lx + " imask = 0x%" PRIx32 "\n", __func__, + env->threadId, + (uint32_t)GET_FIELD(IMASK_MASK, imask)); + return GET_FIELD(IMASK_MASK, imask); + } + } + return 0; +} + +void HELPER(setimask)(CPUHexagonState *env, uint32_t tid, uint32_t imask) +{ + CPUState *cs; + + BQL_LOCK_GUARD(); + CPU_FOREACH(cs) { + HexagonCPU *found_cpu = HEXAGON_CPU(cs); + CPUHexagonState *found_env = &found_cpu->env; + + if (tid == found_env->threadId) { + SET_SYSTEM_FIELD(found_env, HEX_SREG_IMASK, IMASK_MASK, imask); + qemu_log_mask(CPU_LOG_INT, "%s: tid " TARGET_FMT_lx + " imask 0x%" PRIx32 "\n", + __func__, found_env->threadId, imask); + hex_interrupt_update(found_env); + return; + } + } + qemu_log_mask(LOG_GUEST_ERROR, + "setimask used with an invalid tid near PC: 0x%" + PRIx32 "\n", env->next_PC); +} + +void HELPER(sreg_write_masked)(CPUHexagonState *env, uint32_t reg, uint32_t val) +{ + BQL_LOCK_GUARD(); + if (reg < HEX_SREG_GLB_START) { + env->t_sreg[reg] = val; + } else { + HexagonCPU *cpu = env_archcpu(env); + if (cpu->globalregs) { + hexagon_globalreg_write_masked(cpu->globalregs, reg, val); + } + } +} + +static inline QEMU_ALWAYS_INLINE uint32_t sreg_read(CPUHexagonState *env, + uint32_t reg) +{ + HexagonCPU *cpu; + + g_assert(bql_locked()); + if (reg < HEX_SREG_GLB_START) { + return env->t_sreg[reg]; + } + cpu = env_archcpu(env); + return cpu->globalregs ? + hexagon_globalreg_read(cpu->globalregs, reg, env->threadId) : 0; +} + +uint32_t HELPER(sreg_read)(CPUHexagonState *env, uint32_t reg) +{ + BQL_LOCK_GUARD(); + return sreg_read(env, reg); +} + +uint64_t HELPER(sreg_read_pair)(CPUHexagonState *env, uint32_t reg) +{ + BQL_LOCK_GUARD(); + + return deposit64((uint64_t) sreg_read(env, reg), 32, 32, + sreg_read(env, reg + 1)); +} + +uint32_t HELPER(greg_read)(CPUHexagonState *env, uint32_t reg) + +{ + return hexagon_greg_read(env, reg); +} + +uint64_t HELPER(greg_read_pair)(CPUHexagonState *env, uint32_t reg) + +{ + if (reg == HEX_GREG_G0 || reg == HEX_GREG_G2) { + return (uint64_t)(env->greg[reg]) | + (((uint64_t)(env->greg[reg + 1])) << 32); + } + switch (reg) { + case HEX_GREG_GPCYCLELO: + return hexagon_get_sys_pcycle_count(env); + default: + return (uint64_t)hexagon_greg_read(env, reg) | + ((uint64_t)(hexagon_greg_read(env, reg + 1)) << 32); + } +} + +/* + * setprio/resched - hardware-assisted scheduler helpers for managing + * the run queue and interrupt steering. + */ +void HELPER(setprio)(CPUHexagonState *env, uint32_t thread, uint32_t prio) +{ + CPUState *cs; + + BQL_LOCK_GUARD(); + CPU_FOREACH(cs) { + HexagonCPU *found_cpu = HEXAGON_CPU(cs); + CPUHexagonState *found_env = &found_cpu->env; + if (thread == found_env->threadId) { + SET_SYSTEM_FIELD(found_env, HEX_SREG_STID, STID_PRIO, prio); + qemu_log_mask(CPU_LOG_INT, + "%s: tid %" PRIu32 " prio = 0x%" PRIx32 "\n", + __func__, found_env->threadId, prio); + resched(env); + return; + } + } + g_assert_not_reached(); +} + + +void HELPER(pending_interrupt)(CPUHexagonState *env) +{ + BQL_LOCK_GUARD(); + hex_interrupt_update(env); +} +#endif + + /* These macros can be referenced in the generated helper functions */ #define warn(...) /* Nothing */ #define fatal(...) g_assert_not_reached(); diff --git a/target/hexagon/reg_fields_def.h.inc b/target/hexagon/reg_fields_def.h.inc index f2a58d486c..d2c706d56b 100644 --- a/target/hexagon/reg_fields_def.h.inc +++ b/target/hexagon/reg_fields_def.h.inc @@ -39,3 +39,110 @@ DEF_REG_FIELD(USR_FPDBZE, 26, 1) DEF_REG_FIELD(USR_FPOVFE, 27, 1) DEF_REG_FIELD(USR_FPUNFE, 28, 1) DEF_REG_FIELD(USR_FPINPE, 29, 1) + +DEF_REG_FIELD(IPENDAD_IAD, 16, 16) +DEF_REG_FIELD(IPENDAD_IPEND, 0, 16) + +DEF_REG_FIELD(SCHEDCFG_EN, 8, 1) +DEF_REG_FIELD(SCHEDCFG_INTNO, 0, 4) +DEF_REG_FIELD(BESTWAIT_PRIO, 0, 9) + + +/* PTE (aka TLB entry) fields */ +DEF_REG_FIELD(PTE_PPD, 0, 24) +DEF_REG_FIELD(PTE_C, 24, 4) +DEF_REG_FIELD(PTE_U, 28, 1) +DEF_REG_FIELD(PTE_R, 29, 1) +DEF_REG_FIELD(PTE_W, 30, 1) +DEF_REG_FIELD(PTE_X, 31, 1) +DEF_REG_FIELD(PTE_VPN, 32, 20) +DEF_REG_FIELD(PTE_ASID, 52, 7) +DEF_REG_FIELD(PTE_ATR0, 59, 1) +DEF_REG_FIELD(PTE_ATR1, 60, 1) +DEF_REG_FIELD(PTE_PA35, 61, 1) +DEF_REG_FIELD(PTE_G, 62, 1) +DEF_REG_FIELD(PTE_V, 63, 1) + +/* SYSCFG fields */ +DEF_REG_FIELD(SYSCFG_MMUEN, 0, 1) +DEF_REG_FIELD(SYSCFG_ICEN, 1, 1) +DEF_REG_FIELD(SYSCFG_DCEN, 2, 1) +DEF_REG_FIELD(SYSCFG_ISDBTRUSTED, 3, 1) +DEF_REG_FIELD(SYSCFG_GIE, 4, 1) +DEF_REG_FIELD(SYSCFG_ISDBREADY, 5, 1) +DEF_REG_FIELD(SYSCFG_PCYCLEEN, 6, 1) +DEF_REG_FIELD(SYSCFG_V2X, 7, 1) +DEF_REG_FIELD(SYSCFG_IGNOREDABORT, 8, 1) +DEF_REG_FIELD(SYSCFG_PM, 9, 1) +DEF_REG_FIELD(SYSCFG_TLBLOCK, 11, 1) +DEF_REG_FIELD(SYSCFG_K0LOCK, 12, 1) +DEF_REG_FIELD(SYSCFG_BQ, 13, 1) +DEF_REG_FIELD(SYSCFG_PRIO, 14, 1) +DEF_REG_FIELD(SYSCFG_DMT, 15, 1) +DEF_REG_FIELD(SYSCFG_L2CFG, 16, 3) +DEF_REG_FIELD(SYSCFG_ITCM, 19, 1) +DEF_REG_FIELD(SYSCFG_L2NWA, 21, 1) +DEF_REG_FIELD(SYSCFG_L2NRA, 22, 1) +DEF_REG_FIELD(SYSCFG_L2WB, 23, 1) +DEF_REG_FIELD(SYSCFG_L2P, 24, 1) +DEF_REG_FIELD(SYSCFG_SLVCTL0, 25, 2) +DEF_REG_FIELD(SYSCFG_SLVCTL1, 27, 2) +DEF_REG_FIELD(SYSCFG_L2PARTSIZE, 29, 2) +DEF_REG_FIELD(SYSCFG_L2GCA, 31, 1) + +/* SSR fields */ +DEF_REG_FIELD(SSR_CAUSE, 0, 8) +DEF_REG_FIELD(SSR_ASID, 8, 7) +DEF_REG_FIELD(SSR_UM, 16, 1) +DEF_REG_FIELD(SSR_EX, 17, 1) +DEF_REG_FIELD(SSR_IE, 18, 1) +DEF_REG_FIELD(SSR_GM, 19, 1) +DEF_REG_FIELD(SSR_V0, 20, 1) +DEF_REG_FIELD(SSR_V1, 21, 1) +DEF_REG_FIELD(SSR_BVS, 22, 1) +DEF_REG_FIELD(SSR_CE, 23, 1) +DEF_REG_FIELD(SSR_PE, 24, 1) +DEF_REG_FIELD(SSR_BP, 25, 1) +DEF_REG_FIELD(SSR_XE2, 26, 1) +DEF_REG_FIELD(SSR_XA, 27, 3) +DEF_REG_FIELD(SSR_SS, 30, 1) +DEF_REG_FIELD(SSR_XE, 31, 1) + +/* misc registers */ +DEF_REG_FIELD(IMASK_MASK, 0, 16) + +DEF_REG_FIELD(STID_PRIO, 16, 8) +DEF_REG_FIELD(STID_STID, 0, 8) + +/* MODECTL fields */ +DEF_REG_FIELD(MODECTL_E, 0, 8) +DEF_REG_FIELD(MODECTL_W, 16, 8) + +DEF_REG_FIELD(CCR_L1ICP, 0, 2) +DEF_REG_FIELD(CCR_L1DCP, 3, 2) +DEF_REG_FIELD(CCR_L2CP, 6, 2) + +DEF_REG_FIELD(CCR_HFI, 16, 1) +DEF_REG_FIELD(CCR_HFD, 17, 1) +DEF_REG_FIELD(CCR_HFIL2, 18, 1) +DEF_REG_FIELD(CCR_HFDL2, 19, 1) +DEF_REG_FIELD(CCR_SFD, 20, 1) + +DEF_REG_FIELD(CCR_GIE, 24, 1) +DEF_REG_FIELD(CCR_GTE, 25, 1) +DEF_REG_FIELD(CCR_GEE, 26, 1) +DEF_REG_FIELD(CCR_GRE, 27, 1) +DEF_REG_FIELD(CCR_VV1, 29, 1) +DEF_REG_FIELD(CCR_VV2, 30, 1) +DEF_REG_FIELD(CCR_VV3, 31, 1) + +/* ISDB ST fields */ +DEF_REG_FIELD(ISDBST_WAITRUN, 24, 8) +DEF_REG_FIELD(ISDBST_ONOFF, 16, 8) +DEF_REG_FIELD(ISDBST_DEBUGMODE, 8, 8) +DEF_REG_FIELD(ISDBST_STUFFSTATUS, 5, 1) +DEF_REG_FIELD(ISDBST_CMDSTATUS, 4, 1) +DEF_REG_FIELD(ISDBST_PROCMODE, 3, 1) +DEF_REG_FIELD(ISDBST_MBXINSTATUS, 2, 1) +DEF_REG_FIELD(ISDBST_MBXOUTSTATUS, 1, 1) +DEF_REG_FIELD(ISDBST_READY, 0, 1) diff --git a/target/hexagon/sys_macros.h b/target/hexagon/sys_macros.h new file mode 100644 index 0000000000..a49c72715d --- /dev/null +++ b/target/hexagon/sys_macros.h @@ -0,0 +1,252 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HEXAGON_SYS_MACROS_H +#define HEXAGON_SYS_MACROS_H + +/* + * Macro definitions for Hexagon system mode + */ + +#ifndef CONFIG_USER_ONLY + +#include "system/physmem.h" + +#ifdef QEMU_GENERATE +#define GET_SSR_FIELD(RES, FIELD) \ + GET_FIELD(RES, FIELD, hex_t_sreg[HEX_SREG_SSR]) +#else + +#define GET_SSR_FIELD(FIELD, REGIN) \ + (uint32_t)GET_FIELD(FIELD, REGIN) +#define GET_SYSCFG_FIELD(FIELD, REGIN) \ + (uint32_t)GET_FIELD(FIELD, REGIN) +#define SET_SYSTEM_FIELD(ENV, REG, FIELD, VAL) \ + do { \ + HexagonCPU *_sf_cpu = env_archcpu(ENV); \ + uint32_t regval; \ + if ((REG) < HEX_SREG_GLB_START) { \ + regval = (ENV)->t_sreg[(REG)]; \ + } else { \ + regval = _sf_cpu->globalregs ? \ + hexagon_globalreg_read(_sf_cpu->globalregs, (REG), \ + (ENV)->threadId) : 0; \ + } \ + fINSERT_BITS(regval, reg_field_info[FIELD].width, \ + reg_field_info[FIELD].offset, (VAL)); \ + if ((REG) < HEX_SREG_GLB_START) { \ + (ENV)->t_sreg[(REG)] = regval; \ + } else if (_sf_cpu->globalregs) { \ + hexagon_globalreg_write(_sf_cpu->globalregs, (REG), regval, \ + (ENV)->threadId); \ + } \ + } while (0) +#define SET_SSR_FIELD(ENV, FIELD, VAL) \ + SET_SYSTEM_FIELD(ENV, HEX_SREG_SSR, FIELD, VAL) +#define SET_SYSCFG_FIELD(ENV, FIELD, VAL) \ + SET_SYSTEM_FIELD(ENV, HEX_SREG_SYSCFG, FIELD, VAL) + +#define CCR_FIELD_SET(ENV, FIELD) \ + (!!GET_FIELD(FIELD, (ENV)->t_sreg[HEX_SREG_CCR])) + +/* + * Direct-to-guest is not implemented yet, continuing would cause unexpected + * behavior, so we abort. + */ +#define ASSERT_DIRECT_TO_GUEST_UNSET(ENV, EXCP) \ + do { \ + switch (EXCP) { \ + case HEX_EVENT_TRAP0: \ + g_assert(!CCR_FIELD_SET(ENV, CCR_GTE)); \ + break; \ + case HEX_EVENT_IMPRECISE: \ + case HEX_EVENT_PRECISE: \ + case HEX_EVENT_FPTRAP: \ + g_assert(!CCR_FIELD_SET(ENV, CCR_GEE)); \ + break; \ + default: \ + if ((EXCP) >= HEX_EVENT_INT0) { \ + g_assert(!CCR_FIELD_SET(ENV, CCR_GIE)); \ + } \ + break; \ + } \ + } while (0) +#endif + +#define fREAD_ELR() (env->t_sreg[HEX_SREG_ELR]) + +#define fLOAD_PHYS(NUM, SIZE, SIGN, SRC1, SRC2, DST) { \ + const uintptr_t rs = ((unsigned long)(unsigned)(SRC1)) & 0x7ff; \ + const uintptr_t rt = ((unsigned long)(unsigned)(SRC2)) << 11; \ + const uintptr_t addr = rs + rt; \ + physical_memory_read(addr, &DST, sizeof(uint32_t)); \ +} + +#define fPOW2_HELP_ROUNDUP(VAL) \ + ((VAL) | \ + ((VAL) >> 1) | \ + ((VAL) >> 2) | \ + ((VAL) >> 4) | \ + ((VAL) >> 8) | \ + ((VAL) >> 16)) +#define fPOW2_ROUNDUP(VAL) (fPOW2_HELP_ROUNDUP((VAL) - 1) + 1) + +#define fTRAP(TRAPTYPE, IMM) \ + register_trap_exception(env, TRAPTYPE, IMM, PC) + +#ifdef QEMU_GENERATE +#define fFRAMECHECK(ADDR, EA) gen_framecheck(ctx, ADDR, EA) +#endif + +#define fVIRTINSN_SPSWAP(IMM, REG) +#define fVIRTINSN_GETIE(IMM, REG) { REG = 0xdeafbeef; } +#define fVIRTINSN_SETIE(IMM, REG) +#define fVIRTINSN_RTE(IMM, REG) +#define fGRE_ENABLED() \ + GET_FIELD(CCR_GRE, env->t_sreg[HEX_SREG_CCR]) +#define fTRAP1_VIRTINSN(IMM) \ + (fGRE_ENABLED() && \ + (((IMM) == 1) || ((IMM) == 3) || ((IMM) == 4) || ((IMM) == 6))) + +/* Not modeled in qemu */ + +#define MARK_LATE_PRED_WRITE(RNUM) +#define fICINVIDX(REG) +#define fICKILL() +#define fDCKILL() +#define fL2KILL() +#define fL2UNLOCK() +#define fL2CLEAN() +#define fL2CLEANINV() +#define fL2CLEANPA(REG) +#define fL2CLEANINVPA(REG) +#define fL2CLEANINVIDX(REG) +#define fL2CLEANIDX(REG) +#define fL2INVIDX(REG) +#define fL2TAGR(INDEX, DST, DSTREG) +#define fL2UNLOCKA(VA) ((void) VA) +#define fL2TAGW(INDEX, PART2) +#define fDCCLEANIDX(REG) +#define fDCCLEANINVIDX(REG) + +/* Always succeed: */ +#define fL2LOCKA(EA, PDV, PDN) ((void) EA, PDV = 0xFF) +#define fCLEAR_RTE_EX() \ + do { \ + uint32_t tmp = env->t_sreg[HEX_SREG_SSR]; \ + fINSERT_BITS(tmp, reg_field_info[SSR_EX].width, \ + reg_field_info[SSR_EX].offset, 0); \ + log_sreg_write(env, HEX_SREG_SSR, tmp, slot); \ + } while (0) + +#define fDCINVIDX(REG) +#define fDCINVA(REG) do { REG = REG; } while (0) /* Nothing to do in qemu */ + +#define fSET_TLB_LOCK() hex_tlb_lock(env); +#define fCLEAR_TLB_LOCK() hex_tlb_unlock(env); + +#define fSET_K0_LOCK() hex_k0_lock(env); +#define fCLEAR_K0_LOCK() hex_k0_unlock(env); + +#define fTLB_IDXMASK(INDEX) \ + ((INDEX) & (fPOW2_ROUNDUP( \ + fCAST4u(hexagon_tlb_get_num_entries(env_archcpu(env)->tlb))) - 1)) + +#define fTLB_NONPOW2WRAP(INDEX) \ + (((INDEX) >= hexagon_tlb_get_num_entries(env_archcpu(env)->tlb)) ? \ + ((INDEX) - hexagon_tlb_get_num_entries(env_archcpu(env)->tlb)) : \ + (INDEX)) + + +#define fTLBW(INDEX, VALUE) \ + hex_tlbw(env, (INDEX), (VALUE)) +#define fTLBW_EXTENDED(INDEX, VALUE) \ + hex_tlbw(env, (INDEX), (VALUE)) +#define fTLB_ENTRY_OVERLAP(VALUE) \ + (hex_tlb_check_overlap(env, VALUE, -1) != -2) +#define fTLB_ENTRY_OVERLAP_IDX(VALUE) \ + hex_tlb_check_overlap(env, VALUE, -1) +#define fTLBR(INDEX) \ + hexagon_tlb_read(env_archcpu(env)->tlb, \ + fTLB_NONPOW2WRAP(fTLB_IDXMASK(INDEX))) +#define fTLBR_EXTENDED(INDEX) \ + hexagon_tlb_read(env_archcpu(env)->tlb, \ + fTLB_NONPOW2WRAP(fTLB_IDXMASK(INDEX))) +#define fTLBP(TLBHI) \ + hex_tlb_lookup(env, ((TLBHI) >> 12), ((TLBHI) << 12)) +#define iic_flush_cache(p) + +#define fIN_DEBUG_MODE(TNUM) ({ \ + HexagonCPU *_cpu = env_archcpu(env); \ + uint32_t _isdbst = _cpu->globalregs ? \ + hexagon_globalreg_read(_cpu->globalregs, \ + HEX_SREG_ISDBST, env->threadId) : 0; \ + (GET_FIELD(ISDBST_DEBUGMODE, _isdbst) \ + & (0x1 << (TNUM))) != 0; }) + +#define fIN_DEBUG_MODE_NO_ISDB(TNUM) false +#define fIN_DEBUG_MODE_WARN(TNUM) false + +#ifdef QEMU_GENERATE + +/* + * Read tags back as zero for now: + * + * tag value in RD[31:10] for 32k, RD[31:9] for 16k + */ +#define fICTAGR(RS, RD, RD2) \ + do { \ + RD = ctx->zero; \ + } while (0) +#define fICTAGW(RS, RD) +#define fICDATAR(RS, RD) \ + do { \ + RD = ctx->zero; \ + } while (0) +#define fICDATAW(RS, RD) + +#define fDCTAGW(RS, RT) +/* tag: RD[23:0], state: RD[30:29] */ +#define fDCTAGR(INDEX, DST, DST_REG_NUM) \ + do { \ + DST = ctx->zero; \ + } while (0) +#else + +/* + * Read tags back as zero for now: + * + * tag value in RD[31:10] for 32k, RD[31:9] for 16k + */ +#define fICTAGR(RS, RD, RD2) \ + do { \ + RD = 0x00; \ + } while (0) +#define fICTAGW(RS, RD) +#define fICDATAR(RS, RD) \ + do { \ + RD = 0x00; \ + } while (0) +#define fICDATAW(RS, RD) + +#define fDCTAGW(RS, RT) +/* tag: RD[23:0], state: RD[30:29] */ +#define fDCTAGR(INDEX, DST, DST_REG_NUM) \ + do { \ + DST = 0; \ + } while (0) +#endif + +#else +#define ASSERT_DIRECT_TO_GUEST_UNSET(ENV, EXCP) do { } while (0) +#endif + +#define NUM_TLB_REGS(x) (hexagon_tlb_get_num_entries(env_archcpu(env)->tlb)) + +/* NMI routing not yet implemented; Y4_nmi is a no-op for now */ +#define fDO_NMI(THREAD_MASK) do { } while (0) + +#endif diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c index 0b9337d912..77235916f4 100644 --- a/target/hexagon/translate.c +++ b/target/hexagon/translate.c @@ -32,6 +32,7 @@ #include "translate.h" #include "genptr.h" #include "printinsn.h" +#include "exec/target_page.h" #define HELPER_H "helper.h" #include "exec/helper-info.c.inc" @@ -61,10 +62,19 @@ TCGv_i64 hex_store_val64[STORES_MAX]; TCGv hex_llsc_addr; TCGv hex_llsc_val; TCGv_i64 hex_llsc_val_i64; +#ifndef CONFIG_USER_ONLY +TCGv_i64 hex_cycle_count; +#endif TCGv hex_vstore_addr[VSTORES_MAX]; TCGv hex_vstore_size[VSTORES_MAX]; TCGv hex_vstore_pending[VSTORES_MAX]; +#ifndef CONFIG_USER_ONLY +TCGv_i32 hex_greg[NUM_GREGS]; +TCGv_i32 hex_t_sreg[NUM_SREGS]; +TCGv_i32 hex_cause_code; +#endif + static const char * const hexagon_prednames[] = { "p0", "p1", "p2", "p3" }; @@ -117,10 +127,27 @@ intptr_t ctx_tmp_vreg_off(DisasContext *ctx, int regnum, return offset; } -static void gen_exception_raw(int excp) +static void gen_exception(int excp, uint32_t PC) +{ + gen_helper_raise_exception(tcg_env, tcg_constant_i32(excp), + tcg_constant_i32(PC)); +} + +#ifndef CONFIG_USER_ONLY +static inline void gen_precise_exception(int excp, uint32_t PC) +{ + tcg_gen_movi_i32(hex_cause_code, excp); + gen_exception(HEX_EVENT_PRECISE, PC); +} + +static void gen_pcycle_counters(DisasContext *ctx) { - gen_helper_raise_exception(tcg_env, tcg_constant_i32(excp)); + if (ctx->pcycle_enabled) { + tcg_gen_addi_i64(hex_cycle_count, hex_cycle_count, ctx->num_cycles); + } } +#endif + static void gen_exec_counters(DisasContext *ctx) { @@ -130,6 +157,9 @@ static void gen_exec_counters(DisasContext *ctx) hex_gpr[HEX_REG_QEMU_INSN_CNT], ctx->num_insns); tcg_gen_addi_tl(hex_gpr[HEX_REG_QEMU_HVX_CNT], hex_gpr[HEX_REG_QEMU_HVX_CNT], ctx->num_hvx_insns); +#ifndef CONFIG_USER_ONLY + gen_pcycle_counters(ctx); +#endif } static bool use_goto_tb(DisasContext *ctx, target_ulong dest) @@ -187,11 +217,14 @@ static void gen_end_tb(DisasContext *ctx) ctx->base.is_jmp = DISAS_NORETURN; } -static void gen_exception_end_tb(DisasContext *ctx, int excp) +void hex_gen_exception_end_tb(DisasContext *ctx, int excp) { gen_exec_counters(ctx); - tcg_gen_movi_tl(hex_gpr[HEX_REG_PC], ctx->next_PC); - gen_exception_raw(excp); +#ifdef CONFIG_USER_ONLY + gen_exception(excp, ctx->pkt.pc); +#else + gen_precise_exception(excp, ctx->pkt.pc); +#endif ctx->base.is_jmp = DISAS_NORETURN; } @@ -205,7 +238,7 @@ static void gen_exception_decode_fail(DisasContext *ctx, int nwords, int excp) gen_exec_counters(ctx); tcg_gen_movi_tl(hex_gpr[HEX_REG_PC], fail_pc); - gen_exception_raw(excp); + gen_exception(excp, fail_pc); ctx->base.is_jmp = DISAS_NORETURN; ctx->base.pc_next = fail_pc; } @@ -249,6 +282,16 @@ static bool check_for_attrib(Packet *pkt, int attrib) return false; } +static bool check_for_opcode(Packet *pkt, uint16_t opcode) +{ + for (int i = 0; i < pkt->num_insns; i++) { + if (pkt->insn[i].opcode == opcode) { + return true; + } + } + return false; +} + static bool need_slot_cancelled(Packet *pkt) { /* We only need slot_cancelled for conditional store instructions */ @@ -262,6 +305,90 @@ static bool need_slot_cancelled(Packet *pkt) return false; } +#ifndef CONFIG_USER_ONLY +static bool sreg_write_ends_tb(int reg_num) +{ + return reg_num == HEX_SREG_SSR || + reg_num == HEX_SREG_STID || + reg_num == HEX_SREG_IMASK || + reg_num == HEX_SREG_IPENDAD || + reg_num == HEX_SREG_BESTWAIT || + reg_num == HEX_SREG_SCHEDCFG; +} + +static bool has_sreg_write_ends_tb(Packet const *pkt) +{ + for (int i = 0; i < pkt->num_insns; i++) { + Insn const *insn = &pkt->insn[i]; + uint16_t opcode = insn->opcode; + if (opcode == Y2_tfrsrcr) { + /* Write to a single sreg */ + int reg_num = insn->regno[0]; + if (sreg_write_ends_tb(reg_num)) { + return true; + } + } else if (opcode == Y4_tfrspcp) { + /* Write to a sreg pair */ + int reg_num = insn->regno[0]; + if (sreg_write_ends_tb(reg_num)) { + return true; + } + if (sreg_write_ends_tb(reg_num + 1)) { + return true; + } + } + } + return false; +} +#endif + +static bool pkt_ends_tb(Packet *pkt) +{ + if (pkt->pkt_has_cof) { + return true; + } +#ifndef CONFIG_USER_ONLY + /* System mode instructions that end TLB */ + if (check_for_opcode(pkt, Y2_swi) || + check_for_opcode(pkt, Y2_cswi) || + check_for_opcode(pkt, Y2_ciad) || + check_for_opcode(pkt, Y4_siad) || + check_for_opcode(pkt, Y2_wait) || + check_for_opcode(pkt, Y2_resume) || + check_for_opcode(pkt, Y2_iassignw) || + check_for_opcode(pkt, Y2_setimask) || + check_for_opcode(pkt, Y4_nmi) || + check_for_opcode(pkt, Y2_setprio) || + check_for_opcode(pkt, Y2_start) || + check_for_opcode(pkt, Y2_stop) || + check_for_opcode(pkt, Y2_k0lock) || + check_for_opcode(pkt, Y2_k0unlock) || + check_for_opcode(pkt, Y2_tlblock) || + check_for_opcode(pkt, Y2_tlbunlock) || + check_for_opcode(pkt, Y2_break) || + check_for_opcode(pkt, Y2_isync) || + check_for_opcode(pkt, Y2_syncht) || + check_for_opcode(pkt, Y2_tlbp) || + check_for_opcode(pkt, Y2_tlbw) || + check_for_opcode(pkt, Y5_ctlbw) || + check_for_opcode(pkt, Y5_tlbasidi)) { + return true; + } + + /* + * Check for sreg writes that would end the TB + */ + if (check_for_attrib(pkt, A_IMPLICIT_WRITES_SSR)) { + return true; + } + if (has_sreg_write_ends_tb(pkt)) { + return true; + } +#endif + return false; +} + + static bool need_next_PC(DisasContext *ctx) { /* Check for conditional control flow or HW loop end */ @@ -309,6 +436,16 @@ static void mark_implicit_usr_write(DisasContext *ctx, int attrib) } } +#ifndef CONFIG_USER_ONLY +static void mark_implicit_sreg_write(DisasContext *ctx, int attrib, int snum) +{ + uint16_t opcode = ctx->insn->opcode; + if (GET_ATTRIB(opcode, attrib)) { + ctx_log_sreg_write(ctx, snum); + } +} +#endif + static void mark_implicit_reg_writes(DisasContext *ctx) { mark_implicit_reg_write(ctx, A_IMPLICIT_WRITES_FP, HEX_REG_FP); @@ -321,6 +458,12 @@ static void mark_implicit_reg_writes(DisasContext *ctx) mark_implicit_usr_write(ctx, A_IMPLICIT_WRITES_USR); mark_implicit_usr_write(ctx, A_FPOP); + +#ifndef CONFIG_USER_ONLY + mark_implicit_sreg_write(ctx, A_IMPLICIT_WRITES_SGP0, HEX_SREG_SGP0); + mark_implicit_sreg_write(ctx, A_IMPLICIT_WRITES_SGP1, HEX_SREG_SGP1); + mark_implicit_sreg_write(ctx, A_IMPLICIT_WRITES_SSR, HEX_SREG_SSR); +#endif } static void mark_implicit_pred_write(DisasContext *ctx, int attrib, int pnum) @@ -418,7 +561,11 @@ static void analyze_packet(DisasContext *ctx) static void gen_start_packet(DisasContext *ctx) { - target_ulong next_PC = ctx->base.pc_next + ctx->pkt.encod_pkt_size_in_bytes; + Packet *pkt = &ctx->pkt; + target_ulong next_PC = (check_for_opcode(pkt, Y2_k0lock) || + check_for_opcode(pkt, Y2_tlblock)) ? + ctx->base.pc_next : + ctx->base.pc_next + pkt->encod_pkt_size_in_bytes; int i; /* Clear out the disassembly context */ @@ -426,6 +573,10 @@ static void gen_start_packet(DisasContext *ctx) ctx->reg_log_idx = 0; bitmap_zero(ctx->regs_written, TOTAL_PER_THREAD_REGS); bitmap_zero(ctx->predicated_regs, TOTAL_PER_THREAD_REGS); +#ifndef CONFIG_USER_ONLY + ctx->greg_log_idx = 0; + ctx->sreg_log_idx = 0; +#endif ctx->preg_log_idx = 0; bitmap_zero(ctx->pregs_written, NUM_PREGS); ctx->future_vregs_idx = 0; @@ -458,6 +609,25 @@ static void gen_start_packet(DisasContext *ctx) * gen phase, so clear it again. */ bitmap_zero(ctx->pregs_written, NUM_PREGS); +#ifndef CONFIG_USER_ONLY + for (i = 0; i < HEX_SREG_GLB_START; i++) { + ctx->t_sreg_new_value[i] = NULL; + } + for (i = 0; i < ctx->sreg_log_idx; i++) { + int reg_num = ctx->sreg_log[i]; + if (reg_num < HEX_SREG_GLB_START && + (ctx->need_commit || reg_num == HEX_SREG_SSR)) { + ctx->t_sreg_new_value[reg_num] = tcg_temp_new(); + } + } + for (i = 0; i < NUM_GREGS; i++) { + ctx->greg_new_value[i] = NULL; + } + for (i = 0; i < ctx->greg_log_idx; i++) { + int reg_num = ctx->greg_log[i]; + ctx->greg_new_value[reg_num] = tcg_temp_new(); + } +#endif /* Initialize the runtime state for packet semantics */ if (need_slot_cancelled(&ctx->pkt)) { @@ -580,7 +750,7 @@ static void gen_insn(DisasContext *ctx) ctx->insn->generate(ctx); mark_store_width(ctx); } else { - gen_exception_end_tb(ctx, HEX_CAUSE_INVALID_OPCODE); + hex_gen_exception_end_tb(ctx, HEX_CAUSE_INVALID_OPCODE); } } @@ -615,6 +785,50 @@ static void gen_reg_writes(DisasContext *ctx) } } +#ifndef CONFIG_USER_ONLY +static void gen_greg_writes(DisasContext *ctx) +{ + int i; + + for (i = 0; i < ctx->greg_log_idx; i++) { + int reg_num = ctx->greg_log[i]; + + tcg_gen_mov_tl(hex_greg[reg_num], ctx->greg_new_value[reg_num]); + } +} + + +static void gen_sreg_writes(DisasContext *ctx) +{ + int i; + + TCGv_i32 old_reg = tcg_temp_new_i32(); + for (i = 0; i < ctx->sreg_log_idx; i++) { + int reg_num = ctx->sreg_log[i]; + + if (reg_num == HEX_SREG_SSR) { + tcg_gen_mov_tl(old_reg, hex_t_sreg[reg_num]); + tcg_gen_mov_tl(hex_t_sreg[reg_num], ctx->t_sreg_new_value[reg_num]); + gen_helper_modify_ssr(tcg_env, ctx->t_sreg_new_value[reg_num], + old_reg); + } else if ((reg_num == HEX_SREG_STID) || + (reg_num == HEX_SREG_IMASK) || + (reg_num == HEX_SREG_IPENDAD)) { + if (ctx->need_commit && reg_num < HEX_SREG_GLB_START) { + tcg_gen_mov_tl(hex_t_sreg[reg_num], + ctx->t_sreg_new_value[reg_num]); + } + gen_helper_pending_interrupt(tcg_env); + } else if ((reg_num == HEX_SREG_BESTWAIT) || + (reg_num == HEX_SREG_SCHEDCFG)) { + gen_helper_resched(tcg_env); + } else if (ctx->need_commit && reg_num < HEX_SREG_GLB_START) { + tcg_gen_mov_tl(hex_t_sreg[reg_num], ctx->t_sreg_new_value[reg_num]); + } + } +} +#endif + static void gen_pred_writes(DisasContext *ctx) { /* Early exit if not needed or the log is empty */ @@ -804,6 +1018,8 @@ static void gen_commit_hvx(DisasContext *ctx) } } +#define PCYCLES_PER_PACKET 1 + static void update_exec_counters(DisasContext *ctx) { int num_real_insns = 0; @@ -823,6 +1039,7 @@ static void update_exec_counters(DisasContext *ctx) ctx->num_packets++; ctx->num_insns += num_real_insns; ctx->num_hvx_insns += num_hvx_insns; + ctx->num_cycles += PCYCLES_PER_PACKET; } static void gen_commit_packet(DisasContext *ctx) @@ -908,6 +1125,10 @@ static void gen_commit_packet(DisasContext *ctx) process_store_log(ctx); gen_reg_writes(ctx); +#ifndef CONFIG_USER_ONLY + gen_greg_writes(ctx); + gen_sreg_writes(ctx); +#endif gen_pred_writes(ctx); if (ctx->pkt.pkt_has_hvx) { gen_commit_hvx(ctx); @@ -920,7 +1141,7 @@ static void gen_commit_packet(DisasContext *ctx) ctx->pkt.vhist_insn->generate(ctx); } - if (ctx->pkt.pkt_has_cof) { + if (pkt_ends_tb(&ctx->pkt) || ctx->base.is_jmp == DISAS_NORETURN) { gen_end_tb(ctx); } } @@ -964,7 +1185,7 @@ static void hexagon_tr_init_disas_context(DisasContextBase *dcbase, HexagonCPU *hex_cpu = env_archcpu(cpu_env(cs)); uint32_t hex_flags = dcbase->tb->flags; - ctx->mem_idx = MMU_USER_IDX; + ctx->mem_idx = FIELD_EX32(hex_flags, TB_FLAGS, MMU_INDEX); ctx->num_packets = 0; ctx->num_insns = 0; ctx->num_hvx_insns = 0; @@ -972,6 +1193,10 @@ static void hexagon_tr_init_disas_context(DisasContextBase *dcbase, ctx->is_tight_loop = FIELD_EX32(hex_flags, TB_FLAGS, IS_TIGHT_LOOP); ctx->short_circuit = hex_cpu->short_circuit; ctx->hex_def = HEXAGON_CPU_GET_CLASS(hex_cpu)->hex_def; +#ifndef CONFIG_USER_ONLY + ctx->num_cycles = 0; + ctx->pcycle_enabled = FIELD_EX32(hex_flags, TB_FLAGS, PCYCLE_ENABLED); +#endif } static void hexagon_tr_tb_start(DisasContextBase *db, CPUState *cpu) @@ -1080,6 +1305,20 @@ void hexagon_translate_init(void) opcode_init(); +#ifndef CONFIG_USER_ONLY + for (i = 0; i < NUM_GREGS; i++) { + hex_greg[i] = tcg_global_mem_new_i32(tcg_env, + offsetof(CPUHexagonState, greg[i]), + hexagon_gregnames[i]); + } + for (i = 0; i < NUM_SREGS; i++) { + if (i < HEX_SREG_GLB_START) { + hex_t_sreg[i] = tcg_global_mem_new_i32(tcg_env, + offsetof(CPUHexagonState, t_sreg[i]), + hexagon_sregnames[i]); + } + } +#endif for (i = 0; i < TOTAL_PER_THREAD_REGS; i++) { hex_gpr[i] = tcg_global_mem_new(tcg_env, offsetof(CPUHexagonState, gpr[i]), @@ -1101,6 +1340,12 @@ void hexagon_translate_init(void) offsetof(CPUHexagonState, llsc_val), "llsc_val"); hex_llsc_val_i64 = tcg_global_mem_new_i64(tcg_env, offsetof(CPUHexagonState, llsc_val_i64), "llsc_val_i64"); +#ifndef CONFIG_USER_ONLY + hex_cause_code = tcg_global_mem_new_i32(tcg_env, + offsetof(CPUHexagonState, cause_code), "cause_code"); + hex_cycle_count = tcg_global_mem_new_i64(tcg_env, + offsetof(CPUHexagonState, t_cycle_count), "t_cycle_count"); +#endif for (i = 0; i < STORES_MAX; i++) { snprintf(store_addr_names[i], NAME_LEN, "store_addr_%d", i); hex_store_addr[i] = tcg_global_mem_new(tcg_env, diff --git a/target/hexagon/translate.h b/target/hexagon/translate.h index 1fc185e3ed..0a7f37d584 100644 --- a/target/hexagon/translate.h +++ b/target/hexagon/translate.h @@ -41,6 +41,14 @@ typedef struct DisasContext { DECLARE_BITMAP(regs_written, TOTAL_PER_THREAD_REGS); DECLARE_BITMAP(predicated_regs, TOTAL_PER_THREAD_REGS); bool implicit_usr_write; +#ifndef CONFIG_USER_ONLY + int greg_log[GREG_WRITES_MAX]; + int greg_log_idx; + int sreg_log[SREG_WRITES_MAX]; + int sreg_log_idx; + TCGv_i32 t_sreg_new_value[HEX_SREG_GLB_START]; + TCGv_i32 greg_new_value[NUM_GREGS]; +#endif int preg_log[PRED_WRITES_MAX]; int preg_log_idx; DECLARE_BITMAP(pregs_written, NUM_PREGS); @@ -77,10 +85,40 @@ typedef struct DisasContext { TCGv new_pred_value[NUM_PREGS]; TCGv branch_taken; TCGv dczero_addr; + bool pcycle_enabled; + uint32_t num_cycles; } DisasContext; bool is_gather_store_insn(DisasContext *ctx); +#ifndef CONFIG_USER_ONLY +static inline void ctx_log_greg_write(DisasContext *ctx, int rnum) +{ + assert(rnum <= HEX_GREG_G3); + ctx->greg_log[ctx->greg_log_idx] = rnum; + ctx->greg_log_idx++; +} + +static inline void ctx_log_greg_write_pair(DisasContext *ctx, int rnum) +{ + assert(!(rnum % 2)); + ctx_log_greg_write(ctx, rnum); + ctx_log_greg_write(ctx, rnum + 1); +} + +static inline void ctx_log_sreg_write(DisasContext *ctx, int rnum) +{ + ctx->sreg_log[ctx->sreg_log_idx] = rnum; + ctx->sreg_log_idx++; +} + +static inline void ctx_log_sreg_write_pair(DisasContext *ctx, int rnum) +{ + ctx_log_sreg_write(ctx, rnum); + ctx_log_sreg_write(ctx, rnum + 1); +} +#endif + static inline void ctx_log_pred_write(DisasContext *ctx, int pnum) { if (!test_bit(pnum, ctx->pregs_written)) { @@ -282,6 +320,13 @@ extern TCGv_i64 hex_llsc_val_i64; extern TCGv hex_vstore_addr[VSTORES_MAX]; extern TCGv hex_vstore_size[VSTORES_MAX]; extern TCGv hex_vstore_pending[VSTORES_MAX]; +#ifndef CONFIG_USER_ONLY +extern TCGv_i32 hex_greg[NUM_GREGS]; +extern TCGv_i32 hex_t_sreg[NUM_SREGS]; +#endif + + +void hex_gen_exception_end_tb(DisasContext *ctx, int excp); void process_store(DisasContext *ctx, int slot_num); @@ -295,4 +340,6 @@ FIELD(PROBE_PKT_SCALAR_HVX_STORES, S0_IS_PRED, 3, 1) FIELD(PROBE_PKT_SCALAR_HVX_STORES, S1_IS_PRED, 4, 1) FIELD(PROBE_PKT_SCALAR_HVX_STORES, MMU_IDX, 5, 2) +void gen_framecheck(DisasContext *ctx, TCGv_i32 addr, TCGv_i32 ea); + #endif diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 8929a75c7c..5805d33ab9 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -26,6 +26,7 @@ #include "tcg/helper-tcg.h" #include "exec/translation-block.h" #include "system/hvf.h" +#include "system/mshv.h" #include "system/whpx.h" #include "whpx/whpx-i386.h" #include "hvf/hvf-i386.h" @@ -8233,6 +8234,13 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w) r = hvf_get_supported_cpuid(wi->cpuid.eax, wi->cpuid.ecx, wi->cpuid.reg); + } else if (mshv_enabled()) { + if (wi->type != CPUID_FEATURE_WORD) { + return 0; + } + r = mshv_get_supported_cpuid(wi->cpuid.eax, + wi->cpuid.ecx, + wi->cpuid.reg); } else if (whpx_enabled()) { switch (wi->type) { case CPUID_FEATURE_WORD: diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 67e2ecf325..e6a197602d 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -1983,6 +1983,9 @@ typedef struct CPUCaches { CPUCacheInfo *l3_cache; } CPUCaches; +struct kvm_msrs; +struct hv_vp_register_page; + typedef struct CPUArchState { /* standard registers */ target_ulong regs[CPU_NB_EREGS]; @@ -2289,6 +2292,10 @@ typedef struct CPUArchState { QEMUTimer *xen_periodic_timer; QemuMutex xen_timers_lock; #endif +#if defined(CONFIG_MSHV) + /* Shared register page */ + struct hv_vp_register_page *regs_page; +#endif #if defined(CONFIG_HVF) || defined(CONFIG_MSHV) || defined(CONFIG_WHPX) void *emu_mmio_buf; #endif @@ -2315,8 +2322,6 @@ typedef struct CPUArchState { DECLARE_BITMAP(avail_cpu_topo, CPU_TOPOLOGY_LEVEL__MAX); } CPUX86State; -struct kvm_msrs; - /** * X86CPU: * @env: #CPUX86State diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c index 9e352882c8..64cc421abe 100644 --- a/target/i386/kvm/kvm.c +++ b/target/i386/kvm/kvm.c @@ -32,6 +32,7 @@ #include "vmsr_energy.h" #include "system/system.h" #include "system/hw_accel.h" +#include "system/accel-irq.h" #include "system/kvm_int.h" #include "system/runstate.h" #include "system/ramblock.h" @@ -6678,7 +6679,7 @@ void kvm_arch_init_irq_routing(KVMState *s) kvm_gsi_routing_allowed = true; if (kvm_irqchip_is_split()) { - KVMRouteChange c = kvm_irqchip_begin_route_changes(s); + AccelRouteChange c = accel_irqchip_begin_route_changes(); int i; /* If the ioapic is in QEMU and the lapics are in KVM, reserve @@ -6689,7 +6690,7 @@ void kvm_arch_init_irq_routing(KVMState *s) exit(1); } } - kvm_irqchip_commit_route_changes(&c); + accel_irqchip_commit_route_changes(&c); } } diff --git a/target/i386/kvm/tdx.c b/target/i386/kvm/tdx.c index df46fce769..c9c3b05d5f 100644 --- a/target/i386/kvm/tdx.c +++ b/target/i386/kvm/tdx.c @@ -385,7 +385,7 @@ static void tdx_finalize_vm(Notifier *notifier, void *unused) * KVM_MEMORY_MAPPING. It becomes useless. */ ram_block = tdx_guest->tdvf_mr->ram_block; - ram_block_discard_range(ram_block, 0, ram_block->max_length); + ram_block_discard_shared_range(ram_block, 0, ram_block->max_length); tdx_vm_ioctl(KVM_TDX_FINALIZE_VM, 0, NULL, &error_fatal); CONFIDENTIAL_GUEST_SUPPORT(tdx_guest)->ready = true; @@ -560,19 +560,29 @@ typedef struct TdxXFAMDep { } TdxXFAMDep; /* - * Note, only the CPUID bits whose virtualization type are "XFAM & Native" are - * defiend here. + * Note, usually the CPUID bits whose virtualization type are "XFAM & Native" + * are defined here while "XFAM & Configured & Native" are not. Because the + * latter are reported as configurable bits by KVM when they are supported. + * And they are not supported when not in the configurable bits list from KVM + * even if the corresponding XFAM bit is supported. * - * For those whose virtualization type are "XFAM & Configured & Native", they - * are reported as configurable bits. And they are not supported if not in the - * configureable bits list from KVM even if the corresponding XFAM bit is - * supported. + * Special cases: + * + * - AMX alias bits, their type is "CPUID_Enabled & Native" which means their + * value is determined by the CPUID bit they are aliased to. + * + * - AVX10_VL_MASK, their type is "XFAM & CPUID_Enabled & Native" which means + * their value is determined by both the corresponding XFAM bit and CPUID bit. + * + * For simplicity, relax the dependency to related XFAM bit. + * tdx_check_features() will eventually catch the unsupported configurations. */ TdxXFAMDep tdx_xfam_deps[] = { { XSTATE_YMM_BIT, { FEAT_1_ECX, CPUID_EXT_FMA } }, { XSTATE_YMM_BIT, { FEAT_7_0_EBX, CPUID_7_0_EBX_AVX2 } }, { XSTATE_OPMASK_BIT, { FEAT_7_0_ECX, CPUID_7_0_ECX_AVX512_VBMI } }, { XSTATE_OPMASK_BIT, { FEAT_7_0_EDX, CPUID_7_0_EDX_AVX512_FP16 } }, + { XSTATE_OPMASK_BIT, { FEAT_24_0_EBX, CPUID_24_0_EBX_AVX10_VL_MASK } }, { XSTATE_PT_BIT, { FEAT_7_0_EBX, CPUID_7_0_EBX_INTEL_PT } }, { XSTATE_PKRU_BIT, { FEAT_7_0_ECX, CPUID_7_0_ECX_PKU } }, { XSTATE_CET_U_BIT, { FEAT_7_0_ECX, CPUID_7_0_ECX_CET_SHSTK } }, @@ -580,6 +590,10 @@ TdxXFAMDep tdx_xfam_deps[] = { { XSTATE_XTILE_CFG_BIT, { FEAT_7_0_EDX, CPUID_7_0_EDX_AMX_BF16 } }, { XSTATE_XTILE_CFG_BIT, { FEAT_7_0_EDX, CPUID_7_0_EDX_AMX_TILE } }, { XSTATE_XTILE_CFG_BIT, { FEAT_7_0_EDX, CPUID_7_0_EDX_AMX_INT8 } }, + { XSTATE_XTILE_CFG_BIT, { FEAT_1E_1_EAX, CPUID_1E_1_EAX_AMX_INT8_ALIAS } }, + { XSTATE_XTILE_CFG_BIT, { FEAT_1E_1_EAX, CPUID_1E_1_EAX_AMX_BF16_ALIAS } }, + { XSTATE_XTILE_CFG_BIT, { FEAT_1E_1_EAX, CPUID_1E_1_EAX_AMX_COMPLEX_ALIAS } }, + { XSTATE_XTILE_CFG_BIT, { FEAT_1E_1_EAX, CPUID_1E_1_EAX_AMX_FP16_ALIAS } }, }; static struct kvm_cpuid_entry2 *find_in_supported_entry(uint32_t function, @@ -1403,7 +1417,7 @@ int tdx_handle_report_fatal_error(X86CPU *cpu, struct kvm_run *run) uint64_t reg_mask = run->system_event.data[R_ECX]; char *message = NULL; uint64_t *tmp; - uint64_t gpa = -1ull; + uint64_t gpa = 0; bool has_gpa = false; if (error_code & 0xffff) { diff --git a/target/i386/meson.build b/target/i386/meson.build index 80062d1d0d..b4fbf8a537 100644 --- a/target/i386/meson.build +++ b/target/i386/meson.build @@ -12,7 +12,7 @@ i386_ss.add(when: 'CONFIG_KVM', if_true: files('xsave_helper.c', 'host-cpu.c')) i386_ss.add(when: 'CONFIG_HVF', if_true: files('xsave_helper.c', 'host-cpu.c')) i386_ss.add(when: 'CONFIG_WHPX', if_true: files('xsave_helper.c', 'host-cpu.c')) i386_ss.add(when: 'CONFIG_NVMM', if_true: files('host-cpu.c')) -i386_ss.add(when: 'CONFIG_MSHV', if_true: files('host-cpu.c')) +i386_ss.add(when: 'CONFIG_MSHV', if_true: files('xsave_helper.c', 'host-cpu.c')) i386_system_ss = ss.source_set() i386_system_ss.add(files( diff --git a/target/i386/mshv/meson.build b/target/i386/mshv/meson.build index 49f28d4a5b..6091c21887 100644 --- a/target/i386/mshv/meson.build +++ b/target/i386/mshv/meson.build @@ -2,6 +2,7 @@ i386_mshv_ss = ss.source_set() i386_mshv_ss.add(files( 'mshv-cpu.c', + 'msr.c', )) i386_system_ss.add_all(when: 'CONFIG_MSHV', if_true: i386_mshv_ss) diff --git a/target/i386/mshv/mshv-cpu.c b/target/i386/mshv/mshv-cpu.c index 4ed6e7548f..126ca40b48 100644 --- a/target/i386/mshv/mshv-cpu.c +++ b/target/i386/mshv/mshv-cpu.c @@ -24,10 +24,13 @@ #include "hw/i386/apic_internal.h" #include "cpu.h" +#include "host-cpu.h" #include "emulate/x86_decode.h" #include "emulate/x86_emu.h" #include "emulate/x86_flags.h" +#include "accel/accel-cpu-target.h" + #include "trace-accel_mshv.h" #include "trace.h" @@ -107,6 +110,175 @@ static enum hv_register_name FPU_REGISTER_NAMES[26] = { HV_X64_REGISTER_XMM_CONTROL_STATUS, }; +static int set_special_regs(const CPUState *cpu); + +static int get_xsave_state(CPUState *cpu) +{ + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + int cpu_fd = mshv_vcpufd(cpu); + int ret; + void *xsavec_buf; + const size_t page = HV_HYP_PAGE_SIZE; + size_t xsavec_buf_len = page; + + /* TODO: should properly determine xsavec size based on CPUID */ + xsavec_buf = qemu_memalign(page, xsavec_buf_len); + memset(xsavec_buf, 0, xsavec_buf_len); + + struct mshv_get_set_vp_state args = { + .type = MSHV_VP_STATE_XSAVE, + .buf_sz = xsavec_buf_len, + .buf_ptr = (uintptr_t)xsavec_buf, + }; + + ret = ioctl(cpu_fd, MSHV_GET_VP_STATE, &args); + if (ret < 0) { + error_report("failed to get xsave state: %s", strerror(errno)); + return -errno; + } + + ret = decompact_xsave_area(xsavec_buf, xsavec_buf_len, env); + g_free(xsavec_buf); + if (ret < 0) { + error_report("failed to decompact xsave area"); + return ret; + } + x86_cpu_xrstor_all_areas(x86cpu, env->xsave_buf, env->xsave_buf_len); + + return 0; +} + +static int set_xsave_state(const CPUState *cpu) +{ + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + int cpu_fd = mshv_vcpufd(cpu); + int ret; + void *xsavec_buf; + size_t page = HV_HYP_PAGE_SIZE, xsavec_buf_len; + + /* allocate and populate compacted buffer */ + xsavec_buf = qemu_memalign(page, page); + xsavec_buf_len = page; + + /* save registers to standard format buffer */ + x86_cpu_xsave_all_areas(x86cpu, env->xsave_buf, env->xsave_buf_len); + + /* store compacted version of xsave area in xsavec_buf */ + compact_xsave_area(env, xsavec_buf, xsavec_buf_len); + + struct mshv_get_set_vp_state args = { + .type = MSHV_VP_STATE_XSAVE, + .buf_sz = xsavec_buf_len, + .buf_ptr = (uintptr_t)xsavec_buf, + }; + + ret = ioctl(cpu_fd, MSHV_SET_VP_STATE, &args); + g_free(xsavec_buf); + if (ret < 0) { + error_report("failed to set xsave state: %s", strerror(errno)); + return -errno; + } + + return 0; +} + +static void populate_fpu(const hv_register_assoc *assocs, X86CPU *x86cpu) +{ + union hv_register_value value; + const union hv_x64_fp_control_status_register *ctrl_status; + const union hv_x64_xmm_control_status_register *xmm_ctrl; + CPUX86State *env = &x86cpu->env; + size_t i, fp_i; + bool valid; + + /* first 16 registers are xmm0-xmm15 */ + for (i = 0; i < 16; i++) { + value = assocs[i].value; + env->xmm_regs[i].ZMM_Q(0) = value.reg128.low_part; + env->xmm_regs[i].ZMM_Q(1) = value.reg128.high_part; + } + + /* next 8 registers are fp_mmx0-fp_mmx7 */ + for (i = 16; i < 24; i++) { + fp_i = i - 16; + value = assocs[i].value; + env->fpregs[fp_i].d.low = value.fp.mantissa; + env->fpregs[fp_i].d.high = (value.fp.sign << 15) + | (value.fp.biased_exponent & 0x7FFF); + } + + /* last two registers are fp_control_status and xmm_control_status */ + ctrl_status = &assocs[24].value.fp_control_status; + env->fpuc = ctrl_status->fp_control; + + env->fpus = ctrl_status->fp_status & ~0x3800; + /* bits 11,12,13 are the top of stack pointer */ + env->fpstt = (ctrl_status->fp_status >> 11) & 0x7; + + for (i = 0; i < 8; i++) { + valid = ctrl_status->fp_tag & (1 << i); + env->fptags[i] = valid ? 0 : 1; + } + + env->fpop = ctrl_status->last_fp_op; + env->fpip = ctrl_status->last_fp_rip; + + xmm_ctrl = &assocs[25].value.xmm_control_status; + env->mxcsr = xmm_ctrl->xmm_status_control; + env->fpdp = xmm_ctrl->last_fp_rdp; +} + +static int get_fpu(CPUState *cpu) +{ + struct hv_register_assoc assocs[ARRAY_SIZE(FPU_REGISTER_NAMES)]; + int ret; + X86CPU *x86cpu = X86_CPU(cpu); + size_t n_regs = ARRAY_SIZE(FPU_REGISTER_NAMES); + + for (size_t i = 0; i < n_regs; i++) { + assocs[i].name = FPU_REGISTER_NAMES[i]; + } + ret = mshv_get_generic_regs(cpu, assocs, n_regs); + if (ret < 0) { + error_report("failed to get special registers"); + return -errno; + } + + populate_fpu(assocs, x86cpu); + + return 0; +} + +static int get_xc_reg(CPUState *cpu) +{ + int ret; + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + struct hv_register_assoc assocs[1]; + + assocs[0].name = HV_X64_REGISTER_XFEM; + + ret = mshv_get_generic_regs(cpu, assocs, 1); + if (ret < 0) { + error_report("failed to get xcr0"); + return -1; + } + env->xcr0 = assocs[0].value.reg64; + + return 0; +} + +static enum hv_register_name NON_VP_PAGE_REGISTER_NAMES[6] = { + HV_X64_REGISTER_TR, + HV_X64_REGISTER_LDTR, + HV_X64_REGISTER_GDTR, + HV_X64_REGISTER_IDTR, + HV_X64_REGISTER_CR2, + HV_X64_REGISTER_APIC_BASE, +}; + static int translate_gva(const CPUState *cpu, uint64_t gva, uint64_t *gpa, uint64_t flags) { @@ -190,8 +362,8 @@ int mshv_set_generic_regs(const CPUState *cpu, const hv_register_assoc *assocs, return 0; } -static int get_generic_regs(CPUState *cpu, hv_register_assoc *assocs, - size_t n_regs) +int mshv_get_generic_regs(CPUState *cpu, hv_register_assoc *assocs, + size_t n_regs) { int cpu_fd = mshv_vcpufd(cpu); int vp_index = cpu->cpu_index; @@ -285,14 +457,56 @@ static int set_standard_regs(const CPUState *cpu) return 0; } -int mshv_store_regs(CPUState *cpu) +static void mshv_set_standard_regs_vp_page(CPUState *cpu) { + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + + env->regs_page->rax = env->regs[R_EAX]; + env->regs_page->rbx = env->regs[R_EBX]; + env->regs_page->rcx = env->regs[R_ECX]; + env->regs_page->rdx = env->regs[R_EDX]; + env->regs_page->rsi = env->regs[R_ESI]; + env->regs_page->rdi = env->regs[R_EDI]; + env->regs_page->rsp = env->regs[R_ESP]; + env->regs_page->rbp = env->regs[R_EBP]; + env->regs_page->r8 = env->regs[R_R8]; + env->regs_page->r9 = env->regs[R_R9]; + env->regs_page->r10 = env->regs[R_R10]; + env->regs_page->r11 = env->regs[R_R11]; + env->regs_page->r12 = env->regs[R_R12]; + env->regs_page->r13 = env->regs[R_R13]; + env->regs_page->r14 = env->regs[R_R14]; + env->regs_page->r15 = env->regs[R_R15]; + env->regs_page->rip = env->eip; + lflags_to_rflags(env); + env->regs_page->rflags = env->eflags; + + env->regs_page->dirty |= (1u << HV_X64_REGISTER_CLASS_GENERAL) + | (1u << HV_X64_REGISTER_CLASS_IP) + | (1u << HV_X64_REGISTER_CLASS_FLAGS); +} + +static int store_regs(CPUState *cpu) +{ + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; int ret; - ret = set_standard_regs(cpu); + /* Use register vp page to optimize registers access */ + if (env->regs_page && env->regs_page->isvalid != 0) { + mshv_set_standard_regs_vp_page(cpu); + } else { + ret = set_standard_regs(cpu); + if (ret < 0) { + return ret; + } + } + + ret = set_special_regs(cpu); if (ret < 0) { - error_report("Failed to store standard registers"); - return -1; + error_report("Failed to store speical registers"); + return ret; } return 0; @@ -323,7 +537,7 @@ static void populate_standard_regs(const hv_register_assoc *assocs, rflags_to_lflags(env); } -int mshv_get_standard_regs(CPUState *cpu) +static int get_standard_regs(CPUState *cpu) { struct hv_register_assoc assocs[ARRAY_SIZE(STANDARD_REGISTER_NAMES)]; int ret; @@ -334,7 +548,7 @@ int mshv_get_standard_regs(CPUState *cpu) for (size_t i = 0; i < n_regs; i++) { assocs[i].name = STANDARD_REGISTER_NAMES[i]; } - ret = get_generic_regs(cpu, assocs, n_regs); + ret = mshv_get_generic_regs(cpu, assocs, n_regs); if (ret < 0) { error_report("failed to get standard registers"); return -1; @@ -401,8 +615,107 @@ static void populate_special_regs(const hv_register_assoc *assocs, cpu_set_apic_base(x86cpu->apic_state, assocs[16].value.reg64); } +static void mshv_get_standard_regs_vp_page(CPUState *cpu) +{ + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + + /* General Purpose Registers */ + env->regs[R_EAX] = env->regs_page->rax; + env->regs[R_EBX] = env->regs_page->rbx; + env->regs[R_ECX] = env->regs_page->rcx; + env->regs[R_EDX] = env->regs_page->rdx; + env->regs[R_ESI] = env->regs_page->rsi; + env->regs[R_EDI] = env->regs_page->rdi; + env->regs[R_ESP] = env->regs_page->rsp; + env->regs[R_EBP] = env->regs_page->rbp; + env->regs[R_R8] = env->regs_page->r8; + env->regs[R_R9] = env->regs_page->r9; + env->regs[R_R10] = env->regs_page->r10; + env->regs[R_R11] = env->regs_page->r11; + env->regs[R_R12] = env->regs_page->r12; + env->regs[R_R13] = env->regs_page->r13; + env->regs[R_R14] = env->regs_page->r14; + env->regs[R_R15] = env->regs_page->r15; + + env->eip = env->regs_page->rip; + env->eflags = env->regs_page->rflags; + rflags_to_lflags(env); +} + +static int mshv_get_special_regs_vp_page(CPUState *cpu) +{ + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + struct hv_register_assoc assocs[ARRAY_SIZE(NON_VP_PAGE_REGISTER_NAMES)]; + int ret; + size_t n_regs = ARRAY_SIZE(NON_VP_PAGE_REGISTER_NAMES); + hv_x64_segment_register seg; + + /* Populate special registers that are in the VP register page */ + env->cr[0] = env->regs_page->cr0; + env->cr[3] = env->regs_page->cr3; + env->cr[4] = env->regs_page->cr4; + env->efer = env->regs_page->efer; + cpu_set_apic_tpr(x86cpu->apic_state, env->regs_page->cr8); + + /* Segment Registers - copy from packed struct to avoid unaligned access */ + memcpy(&seg, &env->regs_page->es, sizeof(hv_x64_segment_register)); + populate_segment_reg(&seg, &env->segs[R_ES]); + memcpy(&seg, &env->regs_page->cs, sizeof(hv_x64_segment_register)); + populate_segment_reg(&seg, &env->segs[R_CS]); + memcpy(&seg, &env->regs_page->ss, sizeof(hv_x64_segment_register)); + populate_segment_reg(&seg, &env->segs[R_SS]); + memcpy(&seg, &env->regs_page->ds, sizeof(hv_x64_segment_register)); + populate_segment_reg(&seg, &env->segs[R_DS]); + memcpy(&seg, &env->regs_page->fs, sizeof(hv_x64_segment_register)); + populate_segment_reg(&seg, &env->segs[R_FS]); + memcpy(&seg, &env->regs_page->gs, sizeof(hv_x64_segment_register)); + populate_segment_reg(&seg, &env->segs[R_GS]); + + /* The rest of the special registers that are not in the VP register page */ + for (size_t i = 0; i < n_regs; i++) { + assocs[i].name = NON_VP_PAGE_REGISTER_NAMES[i]; + } + + ret = mshv_get_generic_regs(cpu, assocs, n_regs); + if (ret < 0) { + error_report("failed to get non-vp-page special registers"); + return -1; + } + + /* Non-VP page registers - TR, LDTR, GDTR, IDTR, CR2, APIC_BASE */ + populate_segment_reg(&assocs[0].value.segment, &env->tr); + populate_segment_reg(&assocs[1].value.segment, &env->ldt); + + populate_table_reg(&assocs[2].value.table, &env->gdt); + populate_table_reg(&assocs[3].value.table, &env->idt); + env->cr[2] = assocs[4].value.reg64; + + cpu_set_apic_base(x86cpu->apic_state, assocs[5].value.reg64); + + return ret; +} + +static int mshv_get_registers_vp_page(CPUState *cpu) +{ + int ret; + + /* General Purpose Registers */ + mshv_get_standard_regs_vp_page(cpu); + + /* Special Registers - makes a hypercall */ + ret = mshv_get_special_regs_vp_page(cpu); + if (ret < 0) { + error_report("failed to get special registers for vp page"); + return -1; + } + + return 0; +} + -int mshv_get_special_regs(CPUState *cpu) +static int get_special_regs(CPUState *cpu) { struct hv_register_assoc assocs[ARRAY_SIZE(SPECIAL_REGISTER_NAMES)]; int ret; @@ -412,7 +725,7 @@ int mshv_get_special_regs(CPUState *cpu) for (size_t i = 0; i < n_regs; i++) { assocs[i].name = SPECIAL_REGISTER_NAMES[i]; } - ret = get_generic_regs(cpu, assocs, n_regs); + ret = mshv_get_generic_regs(cpu, assocs, n_regs); if (ret < 0) { error_report("failed to get special registers"); return -errno; @@ -422,26 +735,245 @@ int mshv_get_special_regs(CPUState *cpu) return 0; } -int mshv_load_regs(CPUState *cpu) +static int load_regs(CPUState *cpu) +{ + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + int ret; + + /* Use register vp page to optimize registers access */ + if (env->regs_page && env->regs_page->isvalid != 0) { + ret = mshv_get_registers_vp_page(cpu); + return ret; + } + + ret = get_standard_regs(cpu); + if (ret < 0) { + return ret; + } + + ret = get_special_regs(cpu); + if (ret < 0) { + return ret; + } + + return 0; +} + +static int get_vcpu_events(CPUState *cpu) { + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + struct hv_register_assoc assocs[] = { + { .name = HV_REGISTER_PENDING_INTERRUPTION }, + { .name = HV_REGISTER_INTERRUPT_STATE }, + { .name = HV_REGISTER_PENDING_EVENT0 }, + }; + union hv_x64_pending_interruption_register pending_int; + union hv_x64_interrupt_state_register int_state; + union hv_x64_pending_exception_event pending_exc; int ret; - ret = mshv_get_standard_regs(cpu); + ret = mshv_get_generic_regs(cpu, assocs, ARRAY_SIZE(assocs)); if (ret < 0) { - error_report("Failed to load standard registers"); + error_report("failed to get vcpu event registers"); return -1; } - ret = mshv_get_special_regs(cpu); + pending_int.as_uint64 = assocs[0].value.reg64; + int_state.as_uint64 = assocs[1].value.reg64; + pending_exc = assocs[2].value.pending_exception_event; + + /* Clear previous state. injected ints/excs are blanked w/ -1 */ + env->interrupt_injected = -1; + env->soft_interrupt = 0; + env->exception_injected = 0; + env->exception_pending = 0; + env->exception_nr = -1; + env->has_error_code = 0; + env->error_code = 0; + env->exception_has_payload = 0; + env->exception_payload = 0; + env->nmi_injected = 0; + + if (pending_int.interruption_pending) { + switch (pending_int.interruption_type) { + case MSHV_HV_INTERRUPTION_TYPE_EXT_INT: + env->interrupt_injected = pending_int.interruption_vector; + break; + case MSHV_HV_INTERRUPTION_TYPE_NMI: + env->nmi_injected = 1; + break; + case MSHV_HV_INTERRUPTION_TYPE_HW_EXC: + env->exception_injected = 1; + env->exception_nr = pending_int.interruption_vector; + env->has_error_code = pending_int.deliver_error_code; + env->error_code = pending_int.error_code; + break; + case MSHV_HV_INTERRUPTION_TYPE_SW_INT: + env->interrupt_injected = pending_int.interruption_vector; + env->soft_interrupt = 1; + break; + case MSHV_HV_INTERRUPTION_TYPE_SW_EXC: + case MSHV_HV_INTERRUPTION_TYPE_PRIV_SW_EXC: + env->exception_injected = 1; + env->exception_nr = pending_int.interruption_vector; + env->has_error_code = pending_int.deliver_error_code; + env->error_code = pending_int.error_code; + break; + default: + error_report("unknown interruption type %u", + pending_int.interruption_type); + return -EINVAL; + } + } + + /* disabled for one instr after STI, MOV/POP SS, see hvf_store_events() */ + if (int_state.interrupt_shadow) { + env->hflags |= HF_INHIBIT_IRQ_MASK; + } else { + env->hflags &= ~HF_INHIBIT_IRQ_MASK; + } + + /* see kvm_get_vcpu_events(), hvf_store_events() */ + if (int_state.nmi_masked) { + env->hflags2 |= HF2_NMI_MASK; + } else { + env->hflags2 &= ~HF2_NMI_MASK; + } + + /* HV_REGISTER_PENDING_EVENT0: pending exception not yet injected */ + if (pending_exc.event_pending) { + env->exception_pending = 1; + env->exception_nr = pending_exc.vector; + env->has_error_code = pending_exc.deliver_error_code; + env->error_code = pending_exc.error_code; + env->exception_has_payload = (pending_exc.exception_parameter != 0); + env->exception_payload = pending_exc.exception_parameter; + } + + /* + * Ignoring HV_REGISTER_PENDING_EVENT1, virtualization fault events, MSHV + * does not support nested virtualization. + */ + + return 0; +} + +static int set_vcpu_events(const CPUState *cpu) +{ + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + union hv_x64_pending_interruption_register pending_int = { 0 }; + union hv_x64_interrupt_state_register int_state = { 0 }; + union hv_x64_pending_exception_event pending_exc = { 0 }; + struct hv_register_assoc assocs[3]; + int ret; + + /* build pending_int from CPUX86State */ + if (env->exception_injected) { + pending_int.interruption_pending = 1; + pending_int.interruption_type = MSHV_HV_INTERRUPTION_TYPE_HW_EXC; + pending_int.interruption_vector = env->exception_nr; + pending_int.deliver_error_code = env->has_error_code; + pending_int.error_code = env->error_code; + } else if (env->nmi_injected) { + pending_int.interruption_pending = 1; + pending_int.interruption_type = MSHV_HV_INTERRUPTION_TYPE_NMI; + pending_int.interruption_vector = EXCP02_NMI; + } else if (env->interrupt_injected >= 0) { + pending_int.interruption_pending = 1; + pending_int.interruption_type = env->soft_interrupt + ? MSHV_HV_INTERRUPTION_TYPE_SW_INT + : MSHV_HV_INTERRUPTION_TYPE_EXT_INT; + pending_int.interruption_vector = env->interrupt_injected; + } + + /* build int_state, normalize to bool */ + int_state.interrupt_shadow = !!(env->hflags & HF_INHIBIT_IRQ_MASK); + int_state.nmi_masked = !!(env->hflags2 & HF2_NMI_MASK); + + /* build pending_exc */ + if (env->exception_pending) { + pending_exc.event_pending = 1; + pending_exc.vector = env->exception_nr; + pending_exc.deliver_error_code = env->has_error_code; + pending_exc.error_code = env->error_code; + pending_exc.exception_parameter = env->exception_payload; + } + + assocs[0].name = HV_REGISTER_PENDING_INTERRUPTION; + assocs[0].value.reg64 = pending_int.as_uint64; + assocs[1].name = HV_REGISTER_INTERRUPT_STATE; + assocs[1].value.reg64 = int_state.as_uint64; + assocs[2].name = HV_REGISTER_PENDING_EVENT0; + assocs[2].value.pending_exception_event = pending_exc; + + ret = mshv_set_generic_regs(cpu, assocs, ARRAY_SIZE(assocs)); if (ret < 0) { - error_report("Failed to load special registers"); + error_report("failed to set vcpu event registers"); return -1; } return 0; } -static void add_cpuid_entry(GList *cpuid_entries, +static int update_hflags(CPUState *cpu) +{ + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + + x86_update_hflags(env); + + return 0; +} + +int mshv_arch_load_vcpu_state(CPUState *cpu) +{ + int ret; + + ret = get_standard_regs(cpu); + if (ret < 0) { + return ret; + } + + ret = get_special_regs(cpu); + if (ret < 0) { + return ret; + } + + /* INVARIANT: hflags are derived from regs+sregs, need to get both first */ + update_hflags(cpu); + + ret = get_xc_reg(cpu); + if (ret < 0) { + return ret; + } + + ret = get_fpu(cpu); + if (ret < 0) { + return ret; + } + + ret = mshv_get_msrs(cpu); + if (ret < 0) { + return ret; + } + + ret = get_xsave_state(cpu); + if (ret < 0) { + return ret; + } + + ret = get_vcpu_events(cpu); + if (ret < 0) { + return ret; + } + + return 0; +} + +static void add_cpuid_entry(GList **cpuid_entries, uint32_t function, uint32_t index, uint32_t eax, uint32_t ebx, uint32_t ecx, uint32_t edx) @@ -456,23 +988,26 @@ static void add_cpuid_entry(GList *cpuid_entries, entry->ecx = ecx; entry->edx = edx; - cpuid_entries = g_list_append(cpuid_entries, entry); + *cpuid_entries = g_list_append(*cpuid_entries, entry); } -static void collect_cpuid_entries(const CPUState *cpu, GList *cpuid_entries) +static void collect_cpuid_entries(const CPUState *cpu, GList **cpuid_entries) { X86CPU *x86_cpu = X86_CPU(cpu); CPUX86State *env = &x86_cpu->env; uint32_t eax, ebx, ecx, edx; uint32_t leaf, subleaf; - size_t max_leaf = 0x1F; - size_t max_subleaf = 0x20; - - uint32_t leaves_with_subleaves[] = {0x4, 0x7, 0xD, 0xF, 0x10}; + uint32_t max_basic_leaf, max_extended_leaf; + uint32_t max_subleaf = 0x20; + uint32_t leaves_with_subleaves[] = {0x04, 0x07, 0x0d, 0x0f, 0x10}; int n_subleaf_leaves = ARRAY_SIZE(leaves_with_subleaves); - /* Regular leaves without subleaves */ - for (leaf = 0; leaf <= max_leaf; leaf++) { + /* Get maximum basic and and extended CPUID leaves */ + cpu_x86_cpuid(env, 0, 0, &max_basic_leaf, &ebx, &ecx, &edx); + cpu_x86_cpuid(env, 0x80000000, 0, &max_extended_leaf, &ebx, &ecx, &edx); + + /* Collect basic leaves (0x0 to max_basic_leaf) */ + for (leaf = 0; leaf <= max_basic_leaf; leaf++) { bool has_subleaves = false; for (int i = 0; i < n_subleaf_leaves; i++) { if (leaf == leaves_with_subleaves[i]) { @@ -483,32 +1018,46 @@ static void collect_cpuid_entries(const CPUState *cpu, GList *cpuid_entries) if (!has_subleaves) { cpu_x86_cpuid(env, leaf, 0, &eax, &ebx, &ecx, &edx); - if (eax == 0 && ebx == 0 && ecx == 0 && edx == 0) { - /* all zeroes indicates no more leaves */ - continue; - } - add_cpuid_entry(cpuid_entries, leaf, 0, eax, ebx, ecx, edx); continue; } + /* + * Valid XSAVE components can exist at a higher index se we need to set + * all subleaves for leaf 0x0d, even if we encounter an empty one. + */ + if (leaf == 0x0d) { + for (subleaf = 0; subleaf <= 63; subleaf++) { + cpu_x86_cpuid(env, leaf, subleaf, &eax, &ebx, &ecx, &edx); + add_cpuid_entry(cpuid_entries, leaf, subleaf, + eax, ebx, ecx, edx); + } + continue; + } + subleaf = 0; while (subleaf < max_subleaf) { cpu_x86_cpuid(env, leaf, subleaf, &eax, &ebx, &ecx, &edx); if (eax == 0 && ebx == 0 && ecx == 0 && edx == 0) { - /* all zeroes indicates no more leaves */ break; } - add_cpuid_entry(cpuid_entries, leaf, 0, eax, ebx, ecx, edx); + add_cpuid_entry(cpuid_entries, leaf, subleaf, eax, ebx, ecx, edx); subleaf++; } } + + /* Collect extended leaves (0x80000000 to max_extended_leaf) */ + for (leaf = 0x80000000; leaf <= max_extended_leaf; leaf++) { + cpu_x86_cpuid(env, leaf, 0, &eax, &ebx, &ecx, &edx); + add_cpuid_entry(cpuid_entries, leaf, 0, eax, ebx, ecx, edx); + } } static int register_intercept_result_cpuid_entry(const CPUState *cpu, uint8_t subleaf_specific, uint8_t always_override, + uint32_t ebx_mask, struct hv_cpuid_entry *entry) { int ret; @@ -522,22 +1071,18 @@ static int register_intercept_result_cpuid_entry(const CPUState *cpu, .input.always_override = always_override, .input.padding = 0, /* - * With regard to masks - these are to specify bits to be overwritten - * The current CpuidEntry structure wouldn't allow to carry the masks - * in addition to the actual register values. For this reason, the - * masks are set to the exact values of the corresponding register bits - * to be registered for an overwrite. To view resulting values the - * hypervisor would return, HvCallGetVpCpuidValues hypercall can be - * used. + * Masks specify which bits to override. Set to 0xFFFFFFFF to + * override all bits with the values from the QEMU CPU model. + * A mask of 0 lets the hypervisor supply its own value. */ .result.eax = entry->eax, - .result.eax_mask = entry->eax, + .result.eax_mask = 0xFFFFFFFF, .result.ebx = entry->ebx, - .result.ebx_mask = entry->ebx, + .result.ebx_mask = ebx_mask, .result.ecx = entry->ecx, - .result.ecx_mask = entry->ecx, + .result.ecx_mask = 0xFFFFFFFF, .result.edx = entry->edx, - .result.edx_mask = entry->edx, + .result.edx_mask = 0xFFFFFFFF, }; union hv_register_intercept_result_parameters parameters = { .cpuid = cpuid_params, @@ -568,6 +1113,7 @@ static int register_intercept_result_cpuid(const CPUState *cpu, int ret = 0, entry_ret; struct hv_cpuid_entry *entry; uint8_t subleaf_specific, always_override; + uint32_t ebx_mask; for (size_t i = 0; i < cpuid->nent; i++) { entry = &cpuid->entries[i]; @@ -575,29 +1121,62 @@ static int register_intercept_result_cpuid(const CPUState *cpu, /* set defaults */ subleaf_specific = 0; always_override = 1; + ebx_mask = 0xFFFFFFFF; - /* Intel */ - /* 0xb - Extended Topology Enumeration Leaf */ - /* 0x1f - V2 Extended Topology Enumeration Leaf */ - /* AMD */ - /* 0x8000_001e - Processor Topology Information */ - /* 0x8000_0026 - Extended CPU Topology */ - if (entry->function == 0xb - || entry->function == 0x1f - || entry->function == 0x8000001e - || entry->function == 0x80000026) { + /* + * Intel + * 0xb - Extended Topology Enumeration Leaf + * 0x1f - V2 Extended Topology Enumeration Leaf + * AMD + * 0x8000_001e - Processor Topology Information + * 0x8000_0026 - Extended CPU Topology + */ + if (entry->function == 0xb || + entry->function == 0x1f || + entry->function == 0x8000001e || + entry->function == 0x80000026) { subleaf_specific = 1; always_override = 1; - } else if (entry->function == 0x00000001 - || entry->function == 0x80000000 - || entry->function == 0x80000001 - || entry->function == 0x80000008) { + /* + * Feature enumeration leaves (subleaf-specific) + * 0x04: Deterministic Cache Parameters + * 0x07: Structured Extended Feature Flags + * 0x0D: Processor Extended State Enumeration + * 0x0F: Platform QoS Monitoring + * 0x10: Platform QoS Enforcement + */ + } else if (entry->function == 0x04 || + entry->function == 0x07 || + entry->function == 0x0d || + entry->function == 0x0f || + entry->function == 0x10) { + subleaf_specific = 1; + always_override = 1; + /* Basic feature leaves (no subleaves) */ + } else if (entry->function == 0x00000001 || + entry->function == 0x80000000 || + entry->function == 0x80000001 || + entry->function == 0x80000008) { subleaf_specific = 0; always_override = 1; } - entry_ret = register_intercept_result_cpuid_entry(cpu, subleaf_specific, + /* + * CPUID[0xD,0].EBX and CPUID[0xD,1].EBX report the XSAVE area + * size based on features currently enabled in XCR0/XSS. These + * values are dynamic and must not be overridden with static + * results from the QEMU CPU model. Setting ebx_mask to 0 lets + * the hypervisor supply EBX based on the guest's actual state. + */ + if (entry->function == 0x0d && + (entry->index == 0 || entry->index == 1)) { + ebx_mask = 0; + } + + entry_ret = register_intercept_result_cpuid_entry(cpu, + subleaf_specific, always_override, + ebx_mask, entry); if ((entry_ret < 0) && (ret == 0)) { ret = entry_ret; @@ -607,7 +1186,7 @@ static int register_intercept_result_cpuid(const CPUState *cpu, return ret; } -static int set_cpuid2(const CPUState *cpu) +static int init_cpuid2(const CPUState *cpu) { int ret; size_t n_entries, cpuid_size; @@ -615,7 +1194,7 @@ static int set_cpuid2(const CPUState *cpu) struct hv_cpuid_entry *entry; GList *entries = NULL; - collect_cpuid_entries(cpu, entries); + collect_cpuid_entries(cpu, &entries); n_entries = g_list_length(entries); cpuid_size = sizeof(struct hv_cpuid) @@ -710,48 +1289,65 @@ static int set_special_regs(const CPUState *cpu) return 0; } -static int set_fpu(const CPUState *cpu, const struct MshvFPU *regs) +static int set_fpu(const CPUState *cpu) { struct hv_register_assoc assocs[ARRAY_SIZE(FPU_REGISTER_NAMES)]; union hv_register_value *value; - size_t fp_i; union hv_x64_fp_control_status_register *ctrl_status; union hv_x64_xmm_control_status_register *xmm_ctrl_status; int ret; size_t n_regs = ARRAY_SIZE(FPU_REGISTER_NAMES); + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + size_t i, fp_i; + bool valid; /* first 16 registers are xmm0-xmm15 */ - for (size_t i = 0; i < 16; i++) { + for (i = 0; i < 16; i++) { assocs[i].name = FPU_REGISTER_NAMES[i]; value = &assocs[i].value; - memcpy(&value->reg128, ®s->xmm[i], 16); + value->reg128.low_part = env->xmm_regs[i].ZMM_Q(0); + value->reg128.high_part = env->xmm_regs[i].ZMM_Q(1); } /* next 8 registers are fp_mmx0-fp_mmx7 */ - for (size_t i = 16; i < 24; i++) { - assocs[i].name = FPU_REGISTER_NAMES[i]; + for (i = 16; i < 24; i++) { fp_i = (i - 16); + assocs[i].name = FPU_REGISTER_NAMES[i]; value = &assocs[i].value; - memcpy(&value->reg128, ®s->fpr[fp_i], 16); + value->fp.mantissa = env->fpregs[fp_i].d.low; + value->fp.biased_exponent = env->fpregs[fp_i].d.high & 0x7FFF; + value->fp.sign = (env->fpregs[fp_i].d.high >> 15) & 0x1; + value->fp.reserved = 0; } /* last two registers are fp_control_status and xmm_control_status */ assocs[24].name = FPU_REGISTER_NAMES[24]; value = &assocs[24].value; ctrl_status = &value->fp_control_status; - ctrl_status->fp_control = regs->fcw; - ctrl_status->fp_status = regs->fsw; - ctrl_status->fp_tag = regs->ftwx; + + ctrl_status->fp_control = env->fpuc; + /* bits 11,12,13 are the top of stack pointer */ + ctrl_status->fp_status = (env->fpus & ~0x3800) | ((env->fpstt & 0x7) << 11); + + ctrl_status->fp_tag = 0; + for (i = 0; i < 8; i++) { + valid = (env->fptags[i] == 0); + if (valid) { + ctrl_status->fp_tag |= (1u << i); + } + } + ctrl_status->reserved = 0; - ctrl_status->last_fp_op = regs->last_opcode; - ctrl_status->last_fp_rip = regs->last_ip; + ctrl_status->last_fp_op = env->fpop; + ctrl_status->last_fp_rip = env->fpip; assocs[25].name = FPU_REGISTER_NAMES[25]; value = &assocs[25].value; xmm_ctrl_status = &value->xmm_control_status; - xmm_ctrl_status->xmm_status_control = regs->mxcsr; - xmm_ctrl_status->xmm_status_control_mask = 0; - xmm_ctrl_status->last_fp_rdp = regs->last_dp; + xmm_ctrl_status->xmm_status_control = env->mxcsr; + xmm_ctrl_status->xmm_status_control_mask = 0x0000ffff; + xmm_ctrl_status->last_fp_rdp = env->fpdp; ret = mshv_set_generic_regs(cpu, assocs, n_regs); if (ret < 0) { @@ -762,12 +1358,15 @@ static int set_fpu(const CPUState *cpu, const struct MshvFPU *regs) return 0; } -static int set_xc_reg(const CPUState *cpu, uint64_t xcr0) +static int set_xc_reg(const CPUState *cpu) { int ret; + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + struct hv_register_assoc assoc = { .name = HV_X64_REGISTER_XFEM, - .value.reg64 = xcr0, + .value.reg64 = env->xcr0, }; ret = mshv_set_generic_regs(cpu, &assoc, 1); @@ -778,30 +1377,6 @@ static int set_xc_reg(const CPUState *cpu, uint64_t xcr0) return 0; } -static int set_cpu_state(const CPUState *cpu, const MshvFPU *fpu_regs, - uint64_t xcr0) -{ - int ret; - - ret = set_standard_regs(cpu); - if (ret < 0) { - return ret; - } - ret = set_special_regs(cpu); - if (ret < 0) { - return ret; - } - ret = set_fpu(cpu, fpu_regs); - if (ret < 0) { - return ret; - } - ret = set_xc_reg(cpu, xcr0); - if (ret < 0) { - return ret; - } - return 0; -} - static int get_vp_state(int cpu_fd, struct mshv_get_set_vp_state *state) { int ret; @@ -815,7 +1390,7 @@ static int get_vp_state(int cpu_fd, struct mshv_get_set_vp_state *state) return 0; } -static int get_lapic(int cpu_fd, +static int get_lapic(const CPUState *cpu, struct hv_local_interrupt_controller_state *state) { int ret; @@ -823,6 +1398,7 @@ static int get_lapic(int cpu_fd, /* buffer aligned to 4k, as *state requires that */ void *buffer = qemu_memalign(size, size); struct mshv_get_set_vp_state mshv_state = { 0 }; + int cpu_fd = mshv_vcpufd(cpu); mshv_state.buf_ptr = (uint64_t) buffer; mshv_state.buf_sz = size; @@ -859,7 +1435,7 @@ static int set_vp_state(int cpu_fd, const struct mshv_get_set_vp_state *state) return 0; } -static int set_lapic(int cpu_fd, +static int set_lapic(const CPUState *cpu, const struct hv_local_interrupt_controller_state *state) { int ret; @@ -867,6 +1443,7 @@ static int set_lapic(int cpu_fd, /* buffer aligned to 4k, as *state requires that */ void *buffer = qemu_memalign(size, size); struct mshv_get_set_vp_state mshv_state = { 0 }; + int cpu_fd = mshv_vcpufd(cpu); if (!state) { error_report("lapic state is NULL"); @@ -888,13 +1465,13 @@ static int set_lapic(int cpu_fd, return 0; } -static int set_lint(int cpu_fd) +static int init_lint(const CPUState *cpu) { int ret; uint32_t *lvt_lint0, *lvt_lint1; struct hv_local_interrupt_controller_state lapic_state = { 0 }; - ret = get_lapic(cpu_fd, &lapic_state); + ret = get_lapic(cpu, &lapic_state); if (ret < 0) { return ret; } @@ -907,167 +1484,46 @@ static int set_lint(int cpu_fd) /* TODO: should we skip setting lapic if the values are the same? */ - return set_lapic(cpu_fd, &lapic_state); + return set_lapic(cpu, &lapic_state); } -static int setup_msrs(const CPUState *cpu) +int mshv_arch_store_vcpu_state(const CPUState *cpu) { int ret; - uint64_t default_type = MSR_MTRR_ENABLE | MSR_MTRR_MEM_TYPE_WB; - - /* boot msr entries */ - MshvMsrEntry msrs[9] = { - { .index = IA32_MSR_SYSENTER_CS, .data = 0x0, }, - { .index = IA32_MSR_SYSENTER_ESP, .data = 0x0, }, - { .index = IA32_MSR_SYSENTER_EIP, .data = 0x0, }, - { .index = IA32_MSR_STAR, .data = 0x0, }, - { .index = IA32_MSR_CSTAR, .data = 0x0, }, - { .index = IA32_MSR_LSTAR, .data = 0x0, }, - { .index = IA32_MSR_KERNEL_GS_BASE, .data = 0x0, }, - { .index = IA32_MSR_SFMASK, .data = 0x0, }, - { .index = IA32_MSR_MTRR_DEF_TYPE, .data = default_type, }, - }; - ret = mshv_configure_msr(cpu, msrs, 9); + ret = set_standard_regs(cpu); if (ret < 0) { - error_report("failed to setup msrs"); - return -1; + return ret; } - return 0; -} - -/* - * TODO: populate topology info: - * - * X86CPU *x86cpu = X86_CPU(cpu); - * CPUX86State *env = &x86cpu->env; - * X86CPUTopoInfo *topo_info = &env->topo_info; - */ -int mshv_configure_vcpu(const CPUState *cpu, const struct MshvFPU *fpu, - uint64_t xcr0) -{ - int ret; - int cpu_fd = mshv_vcpufd(cpu); - - ret = set_cpuid2(cpu); + ret = set_special_regs(cpu); if (ret < 0) { - error_report("failed to set cpuid"); - return -1; + return ret; } - ret = setup_msrs(cpu); + ret = set_xc_reg(cpu); if (ret < 0) { - error_report("failed to setup msrs"); - return -1; + return ret; } - ret = set_cpu_state(cpu, fpu, xcr0); + ret = set_fpu(cpu); if (ret < 0) { - error_report("failed to set cpu state"); - return -1; + return ret; } - ret = set_lint(cpu_fd); + ret = mshv_set_msrs(cpu); if (ret < 0) { - error_report("failed to set lpic int"); - return -1; - } - - return 0; -} - -static int put_regs(const CPUState *cpu) -{ - X86CPU *x86cpu = X86_CPU(cpu); - CPUX86State *env = &x86cpu->env; - MshvFPU fpu = {0}; - int ret; - - memset(&fpu, 0, sizeof(fpu)); - - ret = mshv_configure_vcpu(cpu, &fpu, env->xcr0); - if (ret < 0) { - error_report("failed to configure vcpu"); return ret; } - return 0; -} - -struct MsrPair { - uint32_t index; - uint64_t value; -}; - -static int put_msrs(const CPUState *cpu) -{ - int ret = 0; - X86CPU *x86cpu = X86_CPU(cpu); - CPUX86State *env = &x86cpu->env; - MshvMsrEntries *msrs = g_malloc0(sizeof(MshvMsrEntries)); - - struct MsrPair pairs[] = { - { MSR_IA32_SYSENTER_CS, env->sysenter_cs }, - { MSR_IA32_SYSENTER_ESP, env->sysenter_esp }, - { MSR_IA32_SYSENTER_EIP, env->sysenter_eip }, - { MSR_EFER, env->efer }, - { MSR_PAT, env->pat }, - { MSR_STAR, env->star }, - { MSR_CSTAR, env->cstar }, - { MSR_LSTAR, env->lstar }, - { MSR_KERNELGSBASE, env->kernelgsbase }, - { MSR_FMASK, env->fmask }, - { MSR_MTRRdefType, env->mtrr_deftype }, - { MSR_VM_HSAVE_PA, env->vm_hsave }, - { MSR_SMI_COUNT, env->msr_smi_count }, - { MSR_IA32_PKRS, env->pkrs }, - { MSR_IA32_BNDCFGS, env->msr_bndcfgs }, - { MSR_IA32_XSS, env->xss }, - { MSR_IA32_UMWAIT_CONTROL, env->umwait }, - { MSR_IA32_TSX_CTRL, env->tsx_ctrl }, - { MSR_AMD64_TSC_RATIO, env->amd_tsc_scale_msr }, - { MSR_TSC_AUX, env->tsc_aux }, - { MSR_TSC_ADJUST, env->tsc_adjust }, - { MSR_IA32_SMBASE, env->smbase }, - { MSR_IA32_SPEC_CTRL, env->spec_ctrl }, - { MSR_VIRT_SSBD, env->virt_ssbd }, - }; - - if (ARRAY_SIZE(pairs) > MSHV_MSR_ENTRIES_COUNT) { - error_report("MSR entries exceed maximum size"); - g_free(msrs); - return -1; - } - - for (size_t i = 0; i < ARRAY_SIZE(pairs); i++) { - MshvMsrEntry *entry = &msrs->entries[i]; - entry->index = pairs[i].index; - entry->reserved = 0; - entry->data = pairs[i].value; - msrs->nmsrs++; - } - - ret = mshv_configure_msr(cpu, &msrs->entries[0], msrs->nmsrs); - g_free(msrs); - return ret; -} - - -int mshv_arch_put_registers(const CPUState *cpu) -{ - int ret; - - ret = put_regs(cpu); + ret = set_xsave_state(cpu); if (ret < 0) { - error_report("Failed to put registers"); - return -1; + return ret; } - ret = put_msrs(cpu); + ret = set_vcpu_events(cpu); if (ret < 0) { - error_report("Failed to put msrs"); - return -1; + return ret; } return 0; @@ -1079,6 +1535,12 @@ void mshv_arch_amend_proc_features( features->access_guest_idle_reg = 1; } +void mshv_arch_disable_partition_proc_features( + union hv_partition_processor_features *disabled_features) +{ + disabled_features->la57_support = 1; +} + static int set_memory_info(const struct hyperv_message *msg, struct hv_x64_memory_intercept_message *info) { @@ -1103,16 +1565,16 @@ static int emulate_instruction(CPUState *cpu, int ret; x86_insn_stream stream = { .bytes = insn_bytes, .len = insn_len }; - ret = mshv_load_regs(cpu); + ret = load_regs(cpu); if (ret < 0) { - error_report("failed to load registers"); + error_report("Failed to load registers"); return -1; } decode_instruction_stream(env, &decode, &stream); exec_instruction(env, &decode); - ret = mshv_store_regs(cpu); + ret = store_regs(cpu); if (ret < 0) { error_report("failed to store registers"); return -1; @@ -1291,25 +1753,6 @@ static int handle_pio_non_str(const CPUState *cpu, return 0; } -static int fetch_guest_state(CPUState *cpu) -{ - int ret; - - ret = mshv_get_standard_regs(cpu); - if (ret < 0) { - error_report("Failed to get standard registers"); - return -1; - } - - ret = mshv_get_special_regs(cpu); - if (ret < 0) { - error_report("Failed to get special registers"); - return -1; - } - - return 0; -} - static int read_memory(const CPUState *cpu, uint64_t initial_gva, uint64_t initial_gpa, uint64_t gva, uint8_t *data, size_t len) @@ -1429,9 +1872,9 @@ static int handle_pio_str(CPUState *cpu, hv_x64_io_port_intercept_message *info) X86CPU *x86_cpu = X86_CPU(cpu); CPUX86State *env = &x86_cpu->env; - ret = fetch_guest_state(cpu); + ret = load_regs(cpu); if (ret < 0) { - error_report("Failed to fetch guest state"); + error_report("Failed to load registers"); return -1; } @@ -1462,7 +1905,7 @@ static int handle_pio_str(CPUState *cpu, hv_x64_io_port_intercept_message *info) ret = set_x64_registers(cpu, reg_names, reg_values); if (ret < 0) { - error_report("Failed to set x64 registers"); + error_report("Failed to set RIP and RAX registers"); return -1; } @@ -1605,8 +2048,10 @@ void mshv_arch_init_vcpu(CPUState *cpu) X86CPU *x86_cpu = X86_CPU(cpu); CPUX86State *env = &x86_cpu->env; AccelCPUState *state = cpu->accel; - size_t page = HV_HYP_PAGE_SIZE; + size_t page = HV_HYP_PAGE_SIZE, xsave_len; void *mem = qemu_memalign(page, 2 * page); + int ret; + X86XSaveHeader *header; /* sanity check, to make sure we don't overflow the page */ QEMU_BUILD_BUG_ON((MAX_REGISTER_COUNT @@ -1614,11 +2059,48 @@ void mshv_arch_init_vcpu(CPUState *cpu) + sizeof(hv_input_get_vp_registers) > HV_HYP_PAGE_SIZE)); + /* mmap the registers page */ + void *rp = mmap(NULL, page, PROT_READ | PROT_WRITE, + MAP_SHARED, mshv_vcpufd(cpu), + MSHV_VP_MMAP_OFFSET_REGISTERS * page); + if (rp == MAP_FAILED) { + warn_report("register page mmap failed, falling back to hypercalls: %s", + strerror(errno)); + env->regs_page = NULL; + } else { + env->regs_page = (struct hv_vp_register_page *) rp; + } + state->hvcall_args.base = mem; state->hvcall_args.input_page = mem; state->hvcall_args.output_page = (uint8_t *)mem + page; env->emu_mmio_buf = g_new(char, 4096); + + /* Initialize XSAVE buffer page-aligned */ + /* TODO: pick proper size based on CPUID */ + xsave_len = page; + env->xsave_buf = qemu_memalign(page, xsave_len); + env->xsave_buf_len = xsave_len; + memset(env->xsave_buf, 0, env->xsave_buf_len); + + /* we need to set the compacted format bit in xsave header for mshv */ + header = (X86XSaveHeader *)(env->xsave_buf + sizeof(X86LegacyXSaveArea)); + header->xcomp_bv = header->xstate_bv | (1ULL << 63); + + /* + * TODO: populate topology info: + * X86CPUTopoInfo *topo_info = &env->topo_info; + */ + + ret = init_cpuid2(cpu); + assert(ret == 0); + + ret = mshv_init_msrs(cpu); + assert(ret == 0); + + ret = init_lint(cpu); + assert(ret == 0); } void mshv_arch_destroy_vcpu(CPUState *cpu) @@ -1627,9 +2109,55 @@ void mshv_arch_destroy_vcpu(CPUState *cpu) CPUX86State *env = &x86_cpu->env; AccelCPUState *state = cpu->accel; + /* Unmap the register page */ + if (env->regs_page) { + munmap(env->regs_page, HV_HYP_PAGE_SIZE); + env->regs_page = NULL; + } g_free(state->hvcall_args.base); state->hvcall_args = (MshvHvCallArgs){0}; g_clear_pointer(&env->emu_mmio_buf, g_free); + + qemu_vfree(env->xsave_buf); + env->xsave_buf = NULL; + env->xsave_buf_len = 0; +} + +uint32_t mshv_get_supported_cpuid(uint32_t func, uint32_t idx, int reg) +{ + uint32_t eax, ebx, ecx, edx; + uint32_t ret = 0; + + host_cpuid(func, idx, &eax, &ebx, &ecx, &edx); + switch (reg) { + case R_EAX: + ret = eax; break; + case R_EBX: + ret = ebx; break; + case R_ECX: + ret = ecx; break; + case R_EDX: + ret = edx; break; + } + + /* Disable nested virtualization features not yet supported by MSHV */ + if (func == 0x80000001 && reg == R_ECX) { + ret &= ~CPUID_EXT3_SVM; + } + if (func == 0x01 && reg == R_ECX) { + ret &= ~CPUID_EXT_VMX; + } + + if (func == 0x07 && idx == 0 && reg == R_ECX) { + /* + * LA57 (5-level paging) causes incorrect GVA=>GPA translations + * in the instruction decoder/emulator. Disable until page table + * walk in x86_mmu.c works w/ 5-level paging. + */ + ret &= ~CPUID_7_0_ECX_LA57; + } + + return ret; } /* @@ -1671,3 +2199,62 @@ int mshv_arch_post_init_vm(int vm_fd) return ret; } + +static void mshv_cpu_xsave_init(void) +{ + static bool first = true; + uint32_t eax, ebx, ecx, edx; + int i; + + if (!first) { + return; + } + first = false; + + /* x87 and SSE states are in the legacy region of the XSAVE area. */ + x86_ext_save_areas[XSTATE_FP_BIT].offset = 0; + x86_ext_save_areas[XSTATE_SSE_BIT].offset = 0; + + for (i = XSTATE_SSE_BIT + 1; i < XSAVE_STATE_AREA_COUNT; i++) { + ExtSaveArea *esa = &x86_ext_save_areas[i]; + + if (!esa->size) { + continue; + } + host_cpuid(0xd, i, &eax, &ebx, &ecx, &edx); + if (eax != 0) { + assert(esa->size == eax); + esa->offset = ebx; + esa->ecx = ecx; + } + } +} + +static void mshv_cpu_instance_init(CPUState *cs) +{ + X86CPU *cpu = X86_CPU(cs); + + host_cpu_instance_init(cpu); + mshv_cpu_xsave_init(); +} + +static void mshv_cpu_accel_class_init(ObjectClass *oc, const void *data) +{ + AccelCPUClass *acc = ACCEL_CPU_CLASS(oc); + + acc->cpu_instance_init = mshv_cpu_instance_init; +} + +static const TypeInfo mshv_cpu_accel_type_info = { + .name = ACCEL_CPU_NAME("mshv"), + .parent = TYPE_ACCEL_CPU, + .class_init = mshv_cpu_accel_class_init, + .abstract = true, +}; + +static void mshv_cpu_accel_register_types(void) +{ + type_register_static(&mshv_cpu_accel_type_info); +} + +type_init(mshv_cpu_accel_register_types); diff --git a/target/i386/mshv/msr.c b/target/i386/mshv/msr.c new file mode 100644 index 0000000000..8c220a9942 --- /dev/null +++ b/target/i386/mshv/msr.c @@ -0,0 +1,425 @@ +/* + * QEMU MSHV support + * + * Copyright Microsoft, Corp. 2025 + * + * Authors: Magnus Kulke <magnuskulke@microsoft.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "system/mshv.h" +#include "system/mshv_int.h" +#include "hw/hyperv/hvgdk_mini.h" +#include "linux/mshv.h" +#include "qemu/error-report.h" +#include "cpu.h" + +#define MSHV_ENV_FIELD(env, offset) (*(uint64_t *)((char *)(env) + (offset))) + +typedef struct MshvMsrEnvMap { + uint32_t msr_index; + uint32_t hv_name; + ptrdiff_t env_offset; +} MshvMsrEnvMap; + +/* We assert that 64bit access to sysenter_cs is safe because of padding */ +QEMU_BUILD_BUG_ON(offsetof(CPUX86State, sysenter_esp) - + offsetof(CPUX86State, sysenter_cs) + < sizeof(uint64_t)); + +/* Those MSRs have a direct mapping to fields in CPUX86State */ +static const MshvMsrEnvMap msr_env_map[] = { + /* Architectural */ + { IA32_MSR_EFER, HV_X64_REGISTER_EFER, offsetof(CPUX86State, efer) }, + { IA32_MSR_PAT, HV_X64_REGISTER_PAT, offsetof(CPUX86State, pat) }, + + /* Syscall */ + { IA32_MSR_SYSENTER_CS, HV_X64_REGISTER_SYSENTER_CS, + offsetof(CPUX86State, sysenter_cs) }, + { IA32_MSR_SYSENTER_ESP, HV_X64_REGISTER_SYSENTER_ESP, + offsetof(CPUX86State, sysenter_esp) }, + { IA32_MSR_SYSENTER_EIP, HV_X64_REGISTER_SYSENTER_EIP, + offsetof(CPUX86State, sysenter_eip) }, + { IA32_MSR_STAR, HV_X64_REGISTER_STAR, + offsetof(CPUX86State, star) }, + { IA32_MSR_LSTAR, HV_X64_REGISTER_LSTAR, + offsetof(CPUX86State, lstar) }, + { IA32_MSR_CSTAR, HV_X64_REGISTER_CSTAR, + offsetof(CPUX86State, cstar) }, + { IA32_MSR_SFMASK, HV_X64_REGISTER_SFMASK, + offsetof(CPUX86State, fmask) }, + { IA32_MSR_KERNEL_GS_BASE, HV_X64_REGISTER_KERNEL_GS_BASE, + offsetof(CPUX86State, kernelgsbase) }, + + /* TSC-related */ + { IA32_MSR_TSC, HV_X64_REGISTER_TSC, + offsetof(CPUX86State, tsc) }, + { IA32_MSR_TSC_AUX, HV_X64_REGISTER_TSC_AUX, + offsetof(CPUX86State, tsc_aux) }, + { IA32_MSR_TSC_ADJUST, HV_X64_REGISTER_TSC_ADJUST, + offsetof(CPUX86State, tsc_adjust) }, + + /* Hyper-V per-partition MSRs */ + { HV_X64_MSR_HYPERCALL, HV_X64_REGISTER_HYPERCALL, + offsetof(CPUX86State, msr_hv_hypercall) }, + { HV_X64_MSR_GUEST_OS_ID, HV_REGISTER_GUEST_OS_ID, + offsetof(CPUX86State, msr_hv_guest_os_id) }, + { HV_X64_MSR_REFERENCE_TSC, HV_REGISTER_REFERENCE_TSC, + offsetof(CPUX86State, msr_hv_tsc) }, + + /* Hyper-V MSRs (non-SINT) */ + { HV_X64_MSR_SCONTROL, HV_REGISTER_SCONTROL, + offsetof(CPUX86State, msr_hv_synic_control) }, + { HV_X64_MSR_SIEFP, HV_REGISTER_SIEFP, + offsetof(CPUX86State, msr_hv_synic_evt_page) }, + { HV_X64_MSR_SIMP, HV_REGISTER_SIMP, + offsetof(CPUX86State, msr_hv_synic_msg_page) }, + + /* MTRR default type */ + { IA32_MSR_MTRR_DEF_TYPE, HV_X64_REGISTER_MSR_MTRR_DEF_TYPE, + offsetof(CPUX86State, mtrr_deftype) }, + + /* CET / Shadow Stack */ + { MSR_IA32_U_CET, HV_X64_REGISTER_U_CET, + offsetof(CPUX86State, u_cet) }, + { MSR_IA32_S_CET, HV_X64_REGISTER_S_CET, + offsetof(CPUX86State, s_cet) }, + { MSR_IA32_PL0_SSP, HV_X64_REGISTER_PL0_SSP, + offsetof(CPUX86State, pl0_ssp) }, + { MSR_IA32_PL1_SSP, HV_X64_REGISTER_PL1_SSP, + offsetof(CPUX86State, pl1_ssp) }, + { MSR_IA32_PL2_SSP, HV_X64_REGISTER_PL2_SSP, + offsetof(CPUX86State, pl2_ssp) }, + { MSR_IA32_PL3_SSP, HV_X64_REGISTER_PL3_SSP, + offsetof(CPUX86State, pl3_ssp) }, + { MSR_IA32_INT_SSP_TAB, HV_X64_REGISTER_INTERRUPT_SSP_TABLE_ADDR, + offsetof(CPUX86State, int_ssp_table) }, + + /* XSAVE Supervisor State */ + { MSR_IA32_XSS, HV_X64_REGISTER_U_XSS, + offsetof(CPUX86State, xss) }, + + /* Other */ + + /* TODO: find out processor features that correlate to unsupported MSRs. */ + /* { IA32_MSR_MISC_ENABLE, HV_X64_REGISTER_MSR_IA32_MISC_ENABLE, */ + /* offsetof(CPUX86State, msr_ia32_misc_enable) }, */ + /* { IA32_MSR_BNDCFGS, HV_X64_REGISTER_BNDCFGS, */ + /* offsetof(CPUX86State, msr_bndcfgs) }, */ + { IA32_MSR_SPEC_CTRL, HV_X64_REGISTER_SPEC_CTRL, + offsetof(CPUX86State, spec_ctrl) }, +}; + +/* + * The assocs have to be set according to this schema: + * 8 entries for 0-7 mtrr_base + * 8 entries for mtrr_mask 0-7 + * 11 entries for 1 x 64k, 2 x 16k, 8 x 4k fixed MTRR + * 27 total entries + */ + +#define MSHV_MTRR_MSR_COUNT 27 +#define MSHV_MSR_TOTAL_COUNT (ARRAY_SIZE(msr_env_map) + MSHV_MTRR_MSR_COUNT) + +static void store_in_env_mtrr_phys(CPUState *cpu, + const struct hv_register_assoc *assocs, + size_t n_assocs) +{ + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + size_t i, fixed_offset; + hv_register_name hv_name; + uint64_t base, mask; + + assert(n_assocs == MSHV_MTRR_MSR_COUNT); + + for (i = 0; i < MSR_MTRRcap_VCNT; i++) { + hv_name = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE0 + i; + assert(assocs[i].name == hv_name); + hv_name = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK0 + i; + assert(assocs[i + MSR_MTRRcap_VCNT].name == hv_name); + + base = assocs[i].value.reg64; + mask = assocs[i + MSR_MTRRcap_VCNT].value.reg64; + env->mtrr_var[i].base = base; + env->mtrr_var[i].mask = mask; + } + + /* fixed 1x 64, 2x 16, 8x 4 kB */ + fixed_offset = MSR_MTRRcap_VCNT * 2; + for (i = 0; i < 11; i++) { + hv_name = HV_X64_REGISTER_MSR_MTRR_FIX64K00000 + i; + assert(assocs[fixed_offset + i].name == hv_name); + env->mtrr_fixed[i] = assocs[fixed_offset + i].value.reg64; + } +} + +/* + * The assocs have to be set according to this schema: + * 8 entries for 0-7 mtrr_base + * 8 entries for mtrr_mask 0-7 + * 11 entries for 1 x 64k, 2 x 16k, 8 x 4k fixed MTRR + * 27 total entries + */ +static void load_from_env_mtrr_phys(const CPUState *cpu, + struct hv_register_assoc *assocs, + size_t n_assocs) +{ + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + size_t i, fixed_offset; + uint64_t base, mask, fixed_value; + hv_register_name base_name, mask_name, fixed_name; + hv_register_assoc *assoc; + + assert(n_assocs == MSHV_MTRR_MSR_COUNT); + + for (i = 0; i < MSR_MTRRcap_VCNT; i++) { + base = env->mtrr_var[i].base; + mask = env->mtrr_var[i].mask; + + base_name = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE0 + i; + mask_name = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK0 + i; + + assoc = &assocs[i]; + assoc->name = base_name; + assoc->value.reg64 = base; + + assoc = &assocs[i + MSR_MTRRcap_VCNT]; + assoc->name = mask_name; + assoc->value.reg64 = mask; + } + + /* fixed 1x 64, 2x 16, 8x 4 kB */ + fixed_offset = MSR_MTRRcap_VCNT * 2; + for (i = 0; i < 11; i++) { + fixed_name = HV_X64_REGISTER_MSR_MTRR_FIX64K00000 + i; + fixed_value = env->mtrr_fixed[i]; + + assoc = &assocs[fixed_offset + i]; + assoc->name = fixed_name; + assoc->value.reg64 = fixed_value; + } +} + +int mshv_init_msrs(const CPUState *cpu) +{ + int ret; + uint64_t d_t = MSR_MTRR_ENABLE | MSR_MTRR_MEM_TYPE_WB; + + const struct hv_register_assoc assocs[] = { + { .name = HV_X64_REGISTER_SYSENTER_CS, .value.reg64 = 0x0 }, + { .name = HV_X64_REGISTER_SYSENTER_ESP, .value.reg64 = 0x0 }, + { .name = HV_X64_REGISTER_SYSENTER_EIP, .value.reg64 = 0x0 }, + { .name = HV_X64_REGISTER_STAR, .value.reg64 = 0x0 }, + { .name = HV_X64_REGISTER_CSTAR, .value.reg64 = 0x0 }, + { .name = HV_X64_REGISTER_LSTAR, .value.reg64 = 0x0 }, + { .name = HV_X64_REGISTER_KERNEL_GS_BASE, .value.reg64 = 0x0 }, + { .name = HV_X64_REGISTER_SFMASK, .value.reg64 = 0x0 }, + { .name = HV_X64_REGISTER_MSR_MTRR_DEF_TYPE, .value.reg64 = d_t }, + }; + + ret = mshv_set_generic_regs(cpu, assocs, ARRAY_SIZE(assocs)); + if (ret < 0) { + error_report("failed to put msrs"); + return -1; + } + + return 0; +} + + +/* + * INVARIANT: this fn expects assocs in the same order as they appear in + * msr_env_map. + */ +static void store_in_env(CPUState *cpu, const struct hv_register_assoc *assocs, + size_t n_assocs) +{ + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + size_t i, j; + const MshvMsrEnvMap *mapping; + union hv_register_value hv_value; + ptrdiff_t offset; + uint32_t hv_name; + size_t mtrr_index; + + assert(n_assocs <= MSHV_MSR_TOTAL_COUNT); + + for (i = 0, j = 0; i < ARRAY_SIZE(msr_env_map); i++) { + hv_name = assocs[j].name; + mapping = &msr_env_map[i]; + if (hv_name != mapping->hv_name) { + continue; + } + + hv_value = assocs[j].value; + offset = mapping->env_offset; + MSHV_ENV_FIELD(env, offset) = hv_value.reg64; + j++; + } + + mtrr_index = j; + store_in_env_mtrr_phys(cpu, &assocs[mtrr_index], MSHV_MTRR_MSR_COUNT); +} + +static void set_hv_name_in_assocs(struct hv_register_assoc *assocs, + size_t n_assocs) +{ + size_t i; + size_t mtrr_offset, mtrr_fixed_offset; + hv_register_name hv_name; + + assert(n_assocs == MSHV_MSR_TOTAL_COUNT); + + for (i = 0; i < ARRAY_SIZE(msr_env_map); i++) { + assocs[i].name = msr_env_map[i].hv_name; + } + + mtrr_offset = ARRAY_SIZE(msr_env_map); + for (i = 0; i < MSR_MTRRcap_VCNT; i++) { + hv_name = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE0 + i; + assocs[mtrr_offset + i].name = hv_name; + hv_name = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK0 + i; + assocs[mtrr_offset + MSR_MTRRcap_VCNT + i].name = hv_name; + } + + /* fixed 1x 64, 2x 16, 8x 4 kB */ + mtrr_fixed_offset = mtrr_offset + MSR_MTRRcap_VCNT * 2; + for (i = 0; i < 11; i++) { + hv_name = HV_X64_REGISTER_MSR_MTRR_FIX64K00000 + i; + assocs[mtrr_fixed_offset + i].name = hv_name; + } +} + +static bool msr_supported(uint32_t name) +{ + /* + * This check is not done comprehensively, it's meant to avoid hvcall + * failures for certain MSRs on architectures that don't support them. + */ + + switch (name) { + case HV_X64_REGISTER_SPEC_CTRL: + return mshv_state->processor_features.ibrs_support; + case HV_X64_REGISTER_TSC_ADJUST: + return mshv_state->processor_features.tsc_adjust_support; + case HV_X64_REGISTER_U_CET: + case HV_X64_REGISTER_S_CET: + case HV_X64_REGISTER_PL0_SSP: + case HV_X64_REGISTER_PL1_SSP: + case HV_X64_REGISTER_PL2_SSP: + case HV_X64_REGISTER_PL3_SSP: + case HV_X64_REGISTER_INTERRUPT_SSP_TABLE_ADDR: + case HV_X64_REGISTER_U_XSS: + return mshv_state->processor_features.cet_ss_support || + mshv_state->processor_features.cet_ibt_support; + } + + return true; +} + +int mshv_get_msrs(CPUState *cpu) +{ + int ret = 0; + size_t n_assocs = MSHV_MSR_TOTAL_COUNT; + struct hv_register_assoc assocs[MSHV_MSR_TOTAL_COUNT]; + size_t i, j; + uint32_t name; + + set_hv_name_in_assocs(assocs, n_assocs); + + /* Filter out MSRs that cannot be read */ + for (i = 0, j = 0; i < n_assocs; i++) { + name = assocs[i].name; + + if (!msr_supported(name)) { + continue; + } + + if (j != i) { + assocs[j] = assocs[i]; + } + j++; + } + n_assocs = j; + + ret = mshv_get_generic_regs(cpu, assocs, n_assocs); + if (ret < 0) { + error_report("Failed to get MSRs"); + return -errno; + } + + store_in_env(cpu, assocs, n_assocs); + + return 0; +} + +static void load_from_env(const CPUState *cpu, struct hv_register_assoc *assocs, + size_t n_assocs) +{ + size_t i; + const MshvMsrEnvMap *mapping; + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + ptrdiff_t offset; + union hv_register_value *hv_value; + size_t mtrr_offset; + + assert(n_assocs == MSHV_MSR_TOTAL_COUNT); + + for (i = 0; i < ARRAY_SIZE(msr_env_map); i++) { + mapping = &msr_env_map[i]; + offset = mapping->env_offset; + assocs[i].name = mapping->hv_name; + hv_value = &assocs[i].value; + hv_value->reg64 = MSHV_ENV_FIELD(env, offset); + } + + mtrr_offset = ARRAY_SIZE(msr_env_map); + load_from_env_mtrr_phys(cpu, &assocs[mtrr_offset], MSHV_MTRR_MSR_COUNT); +} + +int mshv_set_msrs(const CPUState *cpu) +{ + size_t n_assocs = MSHV_MSR_TOTAL_COUNT; + struct hv_register_assoc assocs[MSHV_MSR_TOTAL_COUNT]; + int ret; + size_t i, j; + + load_from_env(cpu, assocs, n_assocs); + + /* Filter out MSRs that cannot be written */ + for (i = 0, j = 0; i < n_assocs; i++) { + uint32_t name = assocs[i].name; + + /* Partition-wide MSRs: only write on vCPU 0 */ + if (cpu->cpu_index != 0 && + (name == HV_X64_REGISTER_HYPERCALL || + name == HV_REGISTER_GUEST_OS_ID || + name == HV_REGISTER_REFERENCE_TSC)) { + continue; + } + + if (!msr_supported(name)) { + continue; + } + + if (j != i) { + assocs[j] = assocs[i]; + } + j++; + } + n_assocs = j; + + ret = mshv_set_generic_regs(cpu, assocs, n_assocs); + if (ret < 0) { + error_report("Failed to set MSRs"); + return -errno; + } + + return 0; +} diff --git a/target/i386/sev.c b/target/i386/sev.c index 99cf30806b..debe6344ab 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -160,10 +160,6 @@ struct SevCommonStateClass { * * The SevGuestState object is used for creating and managing a SEV * guest. - * - * # $QEMU \ - * -object sev-guest,id=sev0 \ - * -machine ...,memory-encryption=sev0 */ struct SevGuestState { SevCommonState parent_obj; diff --git a/target/ppc/cpu.h b/target/ppc/cpu.h index 057c54bbb8..cbd5964b1a 100644 --- a/target/ppc/cpu.h +++ b/target/ppc/cpu.h @@ -1693,6 +1693,7 @@ void cpu_ppc_store_hdecr(CPUPPCState *env, target_ulong value); void cpu_ppc_store_tbu40(CPUPPCState *env, uint64_t value); uint64_t cpu_ppc_load_purr(CPUPPCState *env); void cpu_ppc_store_purr(CPUPPCState *env, uint64_t value); +int64_t cpu_ppc_load_tb_offset(CPUPPCState *env); #if !defined(CONFIG_USER_ONLY) target_ulong load_40x_pit(CPUPPCState *env); void store_40x_pit(CPUPPCState *env, target_ulong val); diff --git a/target/ppc/cpu_init.c b/target/ppc/cpu_init.c index a02187ce5a..8cab5e12b6 100644 --- a/target/ppc/cpu_init.c +++ b/target/ppc/cpu_init.c @@ -7620,8 +7620,9 @@ void ppc_cpu_dump_state(CPUState *cs, FILE *f, int flags) #if !defined(CONFIG_USER_ONLY) if (env->tb_env) { qemu_fprintf(f, "TB %08" PRIu32 " %08" PRIu64 - " DECR " TARGET_FMT_lu "\n", cpu_ppc_load_tbu(env), - cpu_ppc_load_tbl(env), cpu_ppc_load_decr(env)); + " DECR " TARGET_FMT_lu " TB_OFFSET %016" PRId64 "\n", + cpu_ppc_load_tbu(env), cpu_ppc_load_tbl(env), + cpu_ppc_load_decr(env), cpu_ppc_load_tb_offset(env)); } #else qemu_fprintf(f, "TB %08" PRIu32 " %08" PRIu64 "\n", cpu_ppc_load_tbu(env), diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index fa497e5e8a..4d2eb281bf 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -47,6 +47,13 @@ static const char riscv_single_letter_exts[] = "IEMAFDQCBPVH"; const uint32_t misa_bits[] = {RVI, RVE, RVM, RVA, RVF, RVD, RVV, RVC, RVS, RVU, RVH, RVG, RVB, 0}; +#define RISCV_CPU_MVENDORID 0 +#define RISCV_CPU_MIMPID 0 +/* + * marchid allocated for qemu: + * https://github.com/riscv/riscv-isa-manual/blob/main/marchid.md + */ +#define RISCV_CPU_MARCHID 42 /* * From vector_helper.c @@ -418,7 +425,7 @@ int riscv_cpu_max_xlen(RISCVCPUClass *mcc) #ifndef CONFIG_USER_ONLY static uint8_t satp_mode_from_str(const char *satp_mode_str) { - if (!strncmp(satp_mode_str, "mbare", 5)) { + if (!strncmp(satp_mode_str, "svbare", 6)) { return VM_1_10_MBARE; } @@ -1009,6 +1016,19 @@ static void riscv_cpu_realize(DeviceState *dev, Error **errp) mcc->parent_realize(dev, errp); } +static void riscv_cpu_unrealize(DeviceState *dev) +{ + RISCVCPUClass *mcc = RISCV_CPU_GET_CLASS(dev); +#ifndef CONFIG_USER_ONLY + RISCVCPU *cpu = RISCV_CPU(dev); + + if (cpu->cfg.debug) { + riscv_trigger_unrealize(&cpu->env); + } +#endif + mcc->parent_unrealize(dev); +} + bool riscv_cpu_accelerator_compatible(RISCVCPU *cpu) { if (tcg_enabled()) { @@ -1050,6 +1070,9 @@ void riscv_add_satp_mode_properties(Object *obj) { RISCVCPU *cpu = RISCV_CPU(obj); + object_property_add(obj, "svbare", "bool", cpu_riscv_get_satp, + cpu_riscv_set_satp, NULL, &cpu->satp_modes); + if (cpu->env.misa_mxl == MXL_RV32) { object_property_add(obj, "sv32", "bool", cpu_riscv_get_satp, cpu_riscv_set_satp, NULL, &cpu->satp_modes); @@ -1151,6 +1174,11 @@ static void riscv_cpu_init(Object *obj) IRQ_LOCAL_MAX + IRQ_LOCAL_GUEST_MAX); qdev_init_gpio_in_named(DEVICE(cpu), riscv_cpu_set_nmi, "riscv.cpu.rnmi", RNMI_MAX); + + if (mcc->def->num_triggers) { + env->num_triggers = mcc->def->num_triggers; + } + #endif /* CONFIG_USER_ONLY */ cpu->user_options = g_hash_table_new(g_str_hash, g_str_equal); @@ -1183,6 +1211,10 @@ static void riscv_cpu_init(Object *obj) mcc->def->profile->enabled = true; } + cpu->cfg.mvendorid = RISCV_CPU_MVENDORID; + cpu->cfg.marchid = RISCV_CPU_MARCHID; + cpu->cfg.mimpid = RISCV_CPU_MIMPID; + env->misa_ext_mask = env->misa_ext = mcc->def->misa_ext; riscv_cpu_cfg_merge(&cpu->cfg, &mcc->def->cfg); @@ -2603,6 +2635,8 @@ static const Property riscv_cpu_properties[] = { DEFAULT_RNMI_IRQVEC), DEFINE_PROP_UINT64("rnmi-exception-vector", RISCVCPU, env.rnmi_excpvec, DEFAULT_RNMI_EXCPVEC), + DEFINE_PROP_UINT32("num-triggers", RISCVCPU, env.num_triggers, + RV_DEFAULT_NUM_TRIGGERS), #endif DEFINE_PROP_BOOL("short-isa-string", RISCVCPU, cfg.short_isa_string, false), @@ -2664,6 +2698,8 @@ static void riscv_cpu_common_class_init(ObjectClass *c, const void *data) device_class_set_parent_realize(dc, riscv_cpu_realize, &mcc->parent_realize); + device_class_set_parent_unrealize(dc, riscv_cpu_unrealize, + &mcc->parent_unrealize); resettable_class_set_parent_phases(rc, NULL, riscv_cpu_reset_hold, NULL, &mcc->parent_phases); @@ -2746,6 +2782,10 @@ static void riscv_cpu_class_base_init(ObjectClass *c, const void *data) !valid_vm_1_10_32[mcc->def->cfg.max_satp_mode]) { mcc->def->cfg.max_satp_mode = VM_1_10_SV32; } + + if (def->num_triggers) { + mcc->def->num_triggers = def->num_triggers; + } #endif } if (def->priv_spec != RISCV_PROFILE_ATTR_UNUSED) { @@ -3211,6 +3251,7 @@ static const TypeInfo riscv_cpu_type_infos[] = { .cfg.ext_zba = true, .cfg.ext_zbb = true, .cfg.ext_zbs = true, + .cfg.ext_zkr = true, .cfg.ext_zkt = true, .cfg.ext_zvbb = true, .cfg.ext_zvbc = true, diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h index 7582874c35..bdd28d329b 100644 --- a/target/riscv/cpu.h +++ b/target/riscv/cpu.h @@ -189,7 +189,22 @@ extern RISCVCPUImpliedExtsRule *riscv_multi_ext_implied_rules[]; #define RV_VLEN_MAX 1024 #define RV_MAX_MHPMEVENTS 32 #define RV_MAX_MHPMCOUNTERS 32 -#define RV_MAX_TRIGGERS 2 + +/* + * The Debug 1.0 spec allows a humongous amount of triggers. Section + * "Enumeration" says: "The above algorithm reads back tselect so that + * implementations which have 2^n triggers only need to implement n + * bits of tselect.". tselect can have up to XLEN bits, so the max + * theoretical RV_MAX_TRIGGERS value is 2^XLEN. + * + * Allowing 2^XLEN triggers per hart is silly so we'll set a max to a + * modest 1024 triggers, which is way more than what we see current + * hardware use (most chips uses 2-4 triggers per hart, RISC-V Server + * Ref requires at least 11). With a 1024 max per hart we'll be set + * for a long time ... hopefully. + */ +#define RV_MAX_TRIGGERS 1024 +#define RV_DEFAULT_NUM_TRIGGERS 2 FIELD(VTYPE, VLMUL, 0, 3) FIELD(VTYPE, VSEW, 3, 3) @@ -467,12 +482,18 @@ struct CPUArchState { /* trigger module */ uint16_t mcontext; uint8_t trigger_cur; - uint64_t tdata1[RV_MAX_TRIGGERS]; - uint64_t tdata2[RV_MAX_TRIGGERS]; - uint64_t tdata3[RV_MAX_TRIGGERS]; - struct CPUBreakpoint *cpu_breakpoint[RV_MAX_TRIGGERS]; - struct CPUWatchpoint *cpu_watchpoint[RV_MAX_TRIGGERS]; - QEMUTimer *itrigger_timer[RV_MAX_TRIGGERS]; + /* + * num_triggers is the length of tdata1, tdata2, tdata3, + * cpu_breakpoint, cpu_watchpoint and itrigger_timer + * arrays. + */ + uint32_t num_triggers; + uint64_t *tdata1; + uint64_t *tdata2; + uint64_t *tdata3; + struct CPUBreakpoint **cpu_breakpoint; + struct CPUWatchpoint **cpu_watchpoint; + QEMUTimer **itrigger_timer; int64_t last_icount; bool itrigger_enabled; @@ -571,11 +592,14 @@ typedef struct RISCVCPUDef { RISCVCPUConfig cfg; bool bare; const RISCVCSR *custom_csrs; + /* This is just a setter for env->num_triggers. */ + uint32_t num_triggers; } RISCVCPUDef; /** * RISCVCPUClass: * @parent_realize: The parent class' realize handler. + * @parent_unrealize: The parent class' unrealize handler. * @parent_phases: The parent class' reset phase handlers. * * A RISCV CPU model. @@ -584,6 +608,7 @@ struct RISCVCPUClass { CPUClass parent_class; DeviceRealize parent_realize; + DeviceUnrealize parent_unrealize; ResettablePhases parent_phases; RISCVCPUDef *def; }; diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c index 59edcdd370..2db07f5dfb 100644 --- a/target/riscv/cpu_helper.c +++ b/target/riscv/cpu_helper.c @@ -1655,10 +1655,18 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, /* Page table updates need to be atomic with MTTCG enabled */ if (updated_pte != pte && !is_debug) { + int pmp_prot, pmp_ret; + if (!adue) { return TRANSLATE_FAIL; } + pmp_ret = get_physical_address_pmp(env, &pmp_prot, pte_addr, + sxlen_bytes, MMU_DATA_STORE, PRV_S); + if (pmp_ret != TRANSLATE_SUCCESS) { + return TRANSLATE_PMP_FAIL; + } + /* * - if accessed or dirty bits need updating, and the PTE is * in RAM, then we do so atomically with a compare and swap. diff --git a/target/riscv/csr.c b/target/riscv/csr.c index dd9726fcf4..7168a4dc12 100644 --- a/target/riscv/csr.c +++ b/target/riscv/csr.c @@ -949,7 +949,16 @@ static RISCVException read_vtype(CPURISCVState *env, int csrno, target_ulong *val) { uint64_t vill; - switch (env->xl) { + int xl = env->xl; + /* + * TCG plugins can read registers before env->xl is initialized. + * Fall back to the CPU's maximum XLEN in that early-init case. + */ + if (xl == 0) { + xl = riscv_cpu_mxl(env); + } + + switch (xl) { case MXL_RV32: vill = (uint32_t)env->vill << 31; break; @@ -2012,6 +2021,20 @@ static target_ulong legalize_mpp(CPURISCVState *env, target_ulong old_mpp, return val; } +static uint64_t riscv_write_uxl(CPURISCVState *env, uint64_t val, + uint64_t field) +{ + RISCVMXL xl = riscv_cpu_mxl(env); + uint64_t uxl = get_field(val, field); + + if (uxl == MXL_RV128) { + uxl = xl == MXL_RV128 ? MXL_RV64 : xl; + val = set_field(val, field, uxl); + } + + return val; +} + static RISCVException write_mstatus(CPURISCVState *env, int csrno, target_ulong val, uintptr_t ra) { @@ -2058,17 +2081,8 @@ static RISCVException write_mstatus(CPURISCVState *env, int csrno, if (xl != MXL_RV32 || env->debugger) { if ((val & MSTATUS64_UXL) != 0) { - uint64_t uxl = val & MSTATUS64_UXL >> 32; mask |= MSTATUS64_UXL; - - /* - * uxl = 3 is reserved so write the current xl instead. - * In case xl = MXL_RV128 (3) write MXL_RV64. - */ - if (uxl == 3) { - uxl = xl == MXL_RV128 ? MXL_RV64 : xl; - val = deposit64(val, 32, 2, uxl); - } + val = riscv_write_uxl(env, val, MSTATUS64_UXL); } } @@ -2733,7 +2747,9 @@ static RISCVException rmw_xireg_aia(CPURISCVState *env, int csrno, AIA_MAKE_IREG(isel, priv, virt, vgein, riscv_cpu_mxl_bits(env)), &wide_val, new_val, wr_mask); - *val = wide_val; + if (val) { + *val = wide_val; + } } } else { isel_reserved = true; @@ -3009,7 +3025,9 @@ static RISCVException rmw_xtopei(CPURISCVState *env, int csrno, AIA_MAKE_IREG(ISELECT_IMSIC_TOPEI, priv, virt, vgein, riscv_cpu_mxl_bits(env)), &wide_val, new_val, wr_mask); - *val = wide_val; + if (val) { + *val = wide_val; + } done: if (ret) { @@ -5226,6 +5244,8 @@ static RISCVException write_vsstatus(CPURISCVState *env, int csrno, uint64_t mask = (target_ulong)-1; if ((val & VSSTATUS64_UXL) == 0) { mask &= ~VSSTATUS64_UXL; + } else { + val = riscv_write_uxl(env, val, VSSTATUS64_UXL); } if ((env->henvcfg & HENVCFG_DTE)) { if ((val & SSTATUS_SDT) != 0) { @@ -5437,7 +5457,7 @@ static RISCVException read_tdata(CPURISCVState *env, int csrno, target_ulong *val) { /* return 0 in tdata1 to end the trigger enumeration */ - if (env->trigger_cur >= RV_MAX_TRIGGERS && csrno == CSR_TDATA1) { + if (env->trigger_cur >= env->num_triggers && csrno == CSR_TDATA1) { *val = 0; return RISCV_EXCP_NONE; } diff --git a/target/riscv/debug.c b/target/riscv/debug.c index 30d39ee5cd..ba5bc6ae13 100644 --- a/target/riscv/debug.c +++ b/target/riscv/debug.c @@ -26,6 +26,7 @@ #include "qemu/osdep.h" #include "qemu/log.h" #include "qapi/error.h" +#include "qemu/error-report.h" #include "cpu.h" #include "target/riscv/debug.h" #include "trace.h" @@ -172,7 +173,7 @@ target_ulong tselect_csr_read(CPURISCVState *env) void tselect_csr_write(CPURISCVState *env, target_ulong val) { - if (val < RV_MAX_TRIGGERS) { + if (val < env->num_triggers) { env->trigger_cur = val; } } @@ -701,7 +702,7 @@ static bool check_itrigger_priv(CPURISCVState *env, int index) bool riscv_itrigger_enabled(CPURISCVState *env) { int count; - for (int i = 0; i < RV_MAX_TRIGGERS; i++) { + for (int i = 0; i < env->num_triggers; i++) { if (get_trigger_type(env, i) != TRIGGER_TYPE_INST_CNT) { continue; } @@ -721,7 +722,7 @@ bool riscv_itrigger_enabled(CPURISCVState *env) void helper_itrigger_match(CPURISCVState *env) { int count; - for (int i = 0; i < RV_MAX_TRIGGERS; i++) { + for (int i = 0; i < env->num_triggers; i++) { if (get_trigger_type(env, i) != TRIGGER_TYPE_INST_CNT) { continue; } @@ -750,7 +751,7 @@ static void riscv_itrigger_update_count(CPURISCVState *env) int64_t last_icount = env->last_icount, current_icount; current_icount = env->last_icount = icount_get_raw(); - for (int i = 0; i < RV_MAX_TRIGGERS; i++) { + for (int i = 0; i < env->num_triggers; i++) { if (get_trigger_type(env, i) != TRIGGER_TYPE_INST_CNT) { continue; } @@ -950,7 +951,7 @@ bool riscv_cpu_debug_check_breakpoint(CPUState *cs) int i; QTAILQ_FOREACH(bp, &cs->breakpoints, entry) { - for (i = 0; i < RV_MAX_TRIGGERS; i++) { + for (i = 0; i < env->num_triggers; i++) { trigger_type = get_trigger_type(env, i); if (!trigger_common_match(env, trigger_type, i)) { @@ -996,7 +997,7 @@ bool riscv_cpu_debug_check_watchpoint(CPUState *cs, CPUWatchpoint *wp) int flags; int i; - for (i = 0; i < RV_MAX_TRIGGERS; i++) { + for (i = 0; i < env->num_triggers; i++) { trigger_type = get_trigger_type(env, i); if (!trigger_common_match(env, trigger_type, i)) { @@ -1049,19 +1050,48 @@ void riscv_trigger_realize(CPURISCVState *env) { int i; - for (i = 0; i < RV_MAX_TRIGGERS; i++) { + if (env->num_triggers > RV_MAX_TRIGGERS) { + error_report( + "Invalid configuration: 'num-triggers' must be less than %u", + RV_MAX_TRIGGERS); + exit(1); + } + + env->tdata1 = g_new0(uint64_t, env->num_triggers); + env->tdata2 = g_new0(uint64_t, env->num_triggers); + env->tdata3 = g_new0(uint64_t, env->num_triggers); + env->cpu_breakpoint = g_new0(struct CPUBreakpoint *, env->num_triggers); + env->cpu_watchpoint = g_new0(struct CPUWatchpoint *, env->num_triggers); + env->itrigger_timer = g_new0(QEMUTimer *, env->num_triggers); + + for (i = 0; i < env->num_triggers; i++) { env->itrigger_timer[i] = timer_new_ns(QEMU_CLOCK_VIRTUAL, riscv_itrigger_timer_cb, env); } } +void riscv_trigger_unrealize(CPURISCVState *env) +{ + g_free(env->tdata1); + g_free(env->tdata2); + g_free(env->tdata3); + + g_free(env->cpu_breakpoint); + g_free(env->cpu_watchpoint); + + for (int i = 0; i < env->num_triggers; i++) { + timer_del(env->itrigger_timer[i]); + } + g_free(env->itrigger_timer); +} + void riscv_trigger_reset_hold(CPURISCVState *env) { target_ulong tdata1 = build_tdata1(env, TRIGGER_TYPE_AD_MATCH, 0, 0); int i; /* init to type 2 triggers */ - for (i = 0; i < RV_MAX_TRIGGERS; i++) { + for (i = 0; i < env->num_triggers; i++) { /* * type = TRIGGER_TYPE_AD_MATCH * dmode = 0 (both debug and M-mode can write tdata) diff --git a/target/riscv/debug.h b/target/riscv/debug.h index 55a3ac72e6..a25d099b37 100644 --- a/target/riscv/debug.h +++ b/target/riscv/debug.h @@ -148,6 +148,7 @@ bool riscv_cpu_debug_check_breakpoint(CPUState *cs); bool riscv_cpu_debug_check_watchpoint(CPUState *cs, CPUWatchpoint *wp); void riscv_trigger_realize(CPURISCVState *env); +void riscv_trigger_unrealize(CPURISCVState *env); void riscv_trigger_reset_hold(CPURISCVState *env); bool riscv_itrigger_enabled(CPURISCVState *env); diff --git a/target/riscv/kvm/kvm-cpu.c b/target/riscv/kvm/kvm-cpu.c index f0de5c3071..39d48a9db4 100644 --- a/target/riscv/kvm/kvm-cpu.c +++ b/target/riscv/kvm/kvm-cpu.c @@ -1395,6 +1395,17 @@ int kvm_arch_put_registers(CPUState *cs, KvmPutState level, Error **errp) return ret; } + /* + * For RUNTIME_STATE, KVM already has the correct FP and Vector state + * from the preceding KVM_RUN exit. QEMU never modifies these registers + * during exit handling, so re-syncing is unnecessary. This saves ~68 + * KVM_SET_ONE_REG ioctls per vCPU exit. See also s390x which uses + * the same pattern. + */ + if (KVM_PUT_RUNTIME_STATE == level) { + return ret; + } + ret = kvm_riscv_put_regs_fp(cs); if (ret) { return ret; @@ -1407,11 +1418,9 @@ int kvm_arch_put_registers(CPUState *cs, KvmPutState level, Error **errp) if (KVM_PUT_RESET_STATE == level) { RISCVCPU *cpu = RISCV_CPU(cs); - if (cs->cpu_index == 0) { - ret = kvm_riscv_sync_mpstate_to_kvm(cpu, KVM_MP_STATE_RUNNABLE); - } else { - ret = kvm_riscv_sync_mpstate_to_kvm(cpu, KVM_MP_STATE_STOPPED); - } + int state = cs->cpu_index == 0 ? KVM_MP_STATE_RUNNABLE + : KVM_MP_STATE_STOPPED; + ret = kvm_riscv_sync_mpstate_to_kvm(cpu, state); if (ret) { return ret; } diff --git a/target/riscv/machine.c b/target/riscv/machine.c index 6e70b145a5..ba96ceceef 100644 --- a/target/riscv/machine.c +++ b/target/riscv/machine.c @@ -240,16 +240,22 @@ static int debug_post_load(void *opaque, int version_id) static const VMStateDescription vmstate_debug = { .name = "cpu/debug", - .version_id = 3, - .minimum_version_id = 3, + .version_id = 4, + .minimum_version_id = 4, .needed = debug_needed, .post_load = debug_post_load, .fields = (const VMStateField[]) { VMSTATE_UINT16(env.mcontext, RISCVCPU), VMSTATE_UINT8(env.trigger_cur, RISCVCPU), - VMSTATE_UINT64_ARRAY(env.tdata1, RISCVCPU, RV_MAX_TRIGGERS), - VMSTATE_UINT64_ARRAY(env.tdata2, RISCVCPU, RV_MAX_TRIGGERS), - VMSTATE_UINT64_ARRAY(env.tdata3, RISCVCPU, RV_MAX_TRIGGERS), + VMSTATE_VARRAY_UINT32(env.tdata1, RISCVCPU, + env.num_triggers, 0, + vmstate_info_uint64, uint64_t), + VMSTATE_VARRAY_UINT32(env.tdata2, RISCVCPU, + env.num_triggers, 0, + vmstate_info_uint64, uint64_t), + VMSTATE_VARRAY_UINT32(env.tdata3, RISCVCPU, + env.num_triggers, 0, + vmstate_info_uint64, uint64_t), VMSTATE_END_OF_LIST() } }; diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c index 2e4f435c53..fdef8f9e8a 100644 --- a/target/s390x/kvm/kvm.c +++ b/target/s390x/kvm/kvm.c @@ -1765,6 +1765,15 @@ static void insert_stsi_3_2_2(S390CPU *cpu, __u64 addr, uint8_t ar) } else if (s390_cpu_virt_mem_read(cpu, addr, ar, &sysib, sizeof(sysib))) { return; } + + /* + * The memory was filled by the kernel but mapped into the guest. + * If something is fishy, do not touch the buffer. + */ + if (sysib.count == 0 || sysib.count > ARRAY_SIZE(sysib.ext_names)) { + return; + } + /* Shift the stack of Extended Names to prepare for our own data */ memmove(&sysib.ext_names[1], &sysib.ext_names[0], sizeof(sysib.ext_names[0]) * (sysib.count - 1)); diff --git a/tests/docker/dockerfiles/emsdk-wasm64-cross.docker b/tests/docker/dockerfiles/emsdk-wasm64-cross.docker index 8a924816f9..c04a741b0e 100644 --- a/tests/docker/dockerfiles/emsdk-wasm64-cross.docker +++ b/tests/docker/dockerfiles/emsdk-wasm64-cross.docker @@ -1,7 +1,7 @@ # syntax = docker/dockerfile:1.5 ARG EMSDK_VERSION_QEMU=4.0.10 -ARG ZLIB_VERSION=1.3.1 +ARG ZLIB_VERSION=1.3.2 ARG GLIB_MINOR_VERSION=2.84 ARG GLIB_VERSION=${GLIB_MINOR_VERSION}.0 ARG PIXMAN_VERSION=0.44.2 @@ -19,7 +19,9 @@ ENV CXXFLAGS="$CFLAGS" ENV LDFLAGS="-sWASM_BIGINT -sASYNCIFY=1 -L$TARGET/lib" RUN apt-get update && apt-get install -y \ autoconf \ + bison \ build-essential \ + flex \ libglib2.0-dev \ libtool \ pkgconf \ diff --git a/tests/functional/ppc64/test_powernv.py b/tests/functional/ppc64/test_powernv.py index 0ea6c93e42..b5b2f0d158 100755 --- a/tests/functional/ppc64/test_powernv.py +++ b/tests/functional/ppc64/test_powernv.py @@ -27,6 +27,11 @@ class PowernvMachine(LinuxKernelTest): 'buildroot/qemu_ppc64le_powernv8-2025.02/rootfs.ext2'), 'aee2192b692077c4bde31cb56ce474424b358f17cec323d5c94af3970c9aada2') + # testdtb for power11, which contains string "hello world" in command line + ASSET_SAMPLE_DTB = Asset( + ('https://github.com/roz3x/qemu/raw/refs/heads/sample-dtb/qemu-powernv11.dtb'), + 'ea1271516264eea1eb58a067a99d0c2ca9528be8dc7d4e46bb2d5ae0d42fc568') + def do_test_linux_boot(self, command_line = KERNEL_COMMON_COMMAND_LINE): self.require_accelerator("tcg") kernel_path = self.ASSET_KERNEL.fetch() @@ -104,6 +109,20 @@ class PowernvMachine(LinuxKernelTest): # Device detection output driven by udev probing is sometimes cut off # from console output, suspect S14silence-console init script. + def test_ppc64_powernv_external_dtb(self): + self.set_machine('powernv11') + self.require_accelerator("tcg") + + kernel_path = self.ASSET_KERNEL.fetch() + sample_dtb_path = self.ASSET_SAMPLE_DTB.fetch() + self.vm.set_console() + self.vm.add_args('-kernel', kernel_path, + '-dtb', sample_dtb_path) + self.vm.launch() + + # check if custom dtb is reflected or not + wait_for_console_pattern(self, "Kernel command line: hello world", self.panic_message) + def test_powernv8(self): self.set_machine('powernv8') self.do_test_ppc64_powernv('P8') diff --git a/tests/functional/pylintrc b/tests/functional/pylintrc index 049c3e76f1..949bea611f 100644 --- a/tests/functional/pylintrc +++ b/tests/functional/pylintrc @@ -58,6 +58,7 @@ confidence=HIGH, # no Warning level messages displayed, use "--disable=all --enable=classes # --disable=W". disable=bad-inline-option, + c-extension-no-member, consider-using-f-string, file-ignored, fixme, diff --git a/tests/functional/riscv64/meson.build b/tests/functional/riscv64/meson.build index 5871211e89..c9d4c090fb 100644 --- a/tests/functional/riscv64/meson.build +++ b/tests/functional/riscv64/meson.build @@ -14,5 +14,6 @@ tests_riscv64_system_thorough = [ 'endianness', 'boston', 'sifive_u', + 'tt_atlantis', 'tuxrun', ] diff --git a/tests/functional/riscv64/test_opensbi.py b/tests/functional/riscv64/test_opensbi.py index d077e40f42..0f8beb7e7a 100755 --- a/tests/functional/riscv64/test_opensbi.py +++ b/tests/functional/riscv64/test_opensbi.py @@ -28,6 +28,10 @@ class RiscvOpenSBI(QemuSystemTest): self.set_machine('sifive_u') self.boot_opensbi() + def test_riscv_tt_atlantis(self): + self.set_machine('tt-atlantis') + self.boot_opensbi() + def test_riscv_virt(self): self.set_machine('virt') self.boot_opensbi() diff --git a/tests/functional/riscv64/test_tt_atlantis.py b/tests/functional/riscv64/test_tt_atlantis.py new file mode 100755 index 0000000000..48abd5cd27 --- /dev/null +++ b/tests/functional/riscv64/test_tt_atlantis.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# +# Functional test that boots a Linux kernel on a Tenstorrent Atlantis machine +# and checks the console +# +# Copyright (c) Linaro Ltd. +# Copyright 2026 Tenstorrent +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset, LinuxKernelTest + + +class TTAtlantis(LinuxKernelTest): + + ASSET_KERNEL = Asset( + 'https://storage.tuxboot.com/kernels/6.11.9/riscv64/Image', + '174f8bb87f08961e54fa3fcd954a8e31f4645f6d6af4dd43983d5e9841490fb0') + ASSET_ROOTFS = Asset( + ('https://github.com/groeck/linux-build-test/raw/' + '9819da19e6eef291686fdd7b029ea00e764dc62f/rootfs/riscv64/' + 'rootfs.ext2.gz'), + 'b6ed95610310b7956f9bf20c4c9c0c05fea647900df441da9dfe767d24e8b28b') + + def do_test_riscv64_tt_atlantis(self, connect_disk): + self.set_machine('tt-atlantis') + kernel_path = self.ASSET_KERNEL.fetch() + rootfs_path = self.uncompress(self.ASSET_ROOTFS) + + self.vm.set_console() + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'earlycon=sbi ' + + if connect_disk: + kernel_command_line += 'root=/dev/vda panic=-1 noreboot rootwait ' + self.vm.add_args('-device', + 'virtio-blk,drive=drive0,serial=0x1234,bus=pcie.0') + self.vm.add_args('-drive', + f'file={rootfs_path},if=none,id=drive0,format=raw') + pattern = 'Boot successful.' + else: + kernel_command_line += 'panic=0 noreboot ' + pattern = 'Cannot open root device' + + self.vm.add_args('-kernel', kernel_path, + '-append', kernel_command_line, + '-no-reboot') + + self.vm.launch() + self.wait_for_console_pattern(pattern) + + def test_riscv64_tt_atlantis(self): + # tt-atlantis machine has no PCI host yet, so no disk + self.do_test_riscv64_tt_atlantis(False) + + +if __name__ == '__main__': + LinuxKernelTest.main() diff --git a/tests/functional/riscv64/test_tuxrun.py b/tests/functional/riscv64/test_tuxrun.py index 0d8de36204..6203a45060 100755 --- a/tests/functional/riscv64/test_tuxrun.py +++ b/tests/functional/riscv64/test_tuxrun.py @@ -17,8 +17,8 @@ from qemu_test.tuxruntest import TuxRunBaselineTest class TuxRunRiscV64Test(TuxRunBaselineTest): ASSET_RISCV64_KERNEL = Asset( - 'https://storage.tuxboot.com/buildroot/20241119/riscv64/Image', - '2bd8132a3bf21570290042324fff48c987f42f2a00c08de979f43f0662ebadba') + 'https://storage.tuxboot.com/kernels/6.11.9/riscv64/Image', + '174f8bb87f08961e54fa3fcd954a8e31f4645f6d6af4dd43983d5e9841490fb0') ASSET_RISCV64_ROOTFS = Asset( 'https://storage.tuxboot.com/buildroot/20241119/riscv64/rootfs.ext4.zst', 'aa4736a9872651dfc0d95e709465eedf1134fd19d42b8cb305bfd776f9801004') @@ -41,6 +41,13 @@ class TuxRunRiscV64Test(TuxRunBaselineTest): self.common_tuxrun(kernel_asset=self.ASSET_RISCV64_KERNEL, rootfs_asset=self.ASSET_RISCV64_ROOTFS) + def test_riscv64_aia(self): + self.set_machine('virt') + self.vm.set_machine('virt,aia=aplic-imsic') + self.cpu='max' + self.common_tuxrun(kernel_asset=self.ASSET_RISCV64_KERNEL, + rootfs_asset=self.ASSET_RISCV64_ROOTFS) + def test_riscv64_rv32(self): self.set_machine('virt') self.cpu='rv32' diff --git a/tests/qemu-iotests/testenv.py b/tests/qemu-iotests/testenv.py index c357e6ebf5..86bcdf7cfa 100644 --- a/tests/qemu-iotests/testenv.py +++ b/tests/qemu-iotests/testenv.py @@ -259,6 +259,7 @@ class TestEnv(contextlib.AbstractContextManager['TestEnv']): ('arm', 'virt'), ('aarch64', 'virt'), ('avr', 'mega2560'), + ('hexagon', 'virt'), ('m68k', 'virt'), ('or1k', 'virt'), ('riscv32', 'virt'), diff --git a/tests/qtest/boot-serial-test.c b/tests/qtest/boot-serial-test.c index bcd0a9c50e..37fee7a91c 100644 --- a/tests/qtest/boot-serial-test.c +++ b/tests/qtest/boot-serial-test.c @@ -142,6 +142,13 @@ static const uint8_t kernel_stm32vldiscovery[] = { 0x04, 0x38, 0x01, 0x40 /* 0x40013804 = USART1 TXD */ }; +static const uint8_t bios_hexagon[] = { + 0x00, 0x40, 0x00, 0x01, /* immext(#0x10000000) */ + 0x00, 0xc0, 0x00, 0x78, /* r0 = ##0x10000000 */ + 0x54, 0xc0, 0x00, 0x3c, /* memb(r0+#0) = #0x54 Write 'T' */ + 0xf8, 0xff, 0xff, 0x59 /* jump 0x0 ; Loop back to start */ +}; + typedef struct testdef { const char *arch; /* Target architecture */ const char *machine; /* Name of the machine */ @@ -194,6 +201,7 @@ static const testdef_t tests[] = { { "arm", "microbit", "", "T", sizeof(kernel_nrf51), kernel_nrf51 }, { "arm", "stm32vldiscovery", "", "T", sizeof(kernel_stm32vldiscovery), kernel_stm32vldiscovery }, + { "hexagon", "virt", "", "TT", sizeof(bios_hexagon), NULL, bios_hexagon }, { NULL } }; diff --git a/tests/qtest/dbus-display-test.c b/tests/qtest/dbus-display-test.c index 5773776cad..7838ce7323 100644 --- a/tests/qtest/dbus-display-test.c +++ b/tests/qtest/dbus-display-test.c @@ -7,6 +7,8 @@ #include <gio/gio.h> #include <gio/gunixfdlist.h> #include "libqtest.h" +#include "qobject/qdict.h" +#include "qobject/qstring.h" #include "ui/dbus-display1.h" static GDBusConnection* @@ -38,11 +40,11 @@ test_dbus_p2p_from_fd(int fd) } static void -test_setup(QTestState **qts, GDBusConnection **conn) +test_setup_args(QTestState **qts, GDBusConnection **conn, const char *args) { int pair[2]; - *qts = qtest_init("-display dbus,p2p=yes -name dbus-test"); + *qts = qtest_init(args); g_assert_cmpint(qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0); @@ -53,6 +55,12 @@ test_setup(QTestState **qts, GDBusConnection **conn) } static void +test_setup(QTestState **qts, GDBusConnection **conn) +{ + test_setup_args(qts, conn, "-display dbus,p2p=yes -name dbus-test"); +} + +static void test_dbus_display_vm(void) { g_autoptr(GError) err = NULL; @@ -360,6 +368,92 @@ test_dbus_display_keyboard(void) qtest_quit(qts); } +static gsize +get_console_ids_count(GDBusConnection *conn) +{ + g_autoptr(GError) err = NULL; + g_autoptr(QemuDBusDisplay1VMProxy) vm = NULL; + GVariant *console_ids; + gsize n_ids = 0; + + vm = QEMU_DBUS_DISPLAY1_VM_PROXY( + qemu_dbus_display1_vm_proxy_new_sync( + conn, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + DBUS_DISPLAY1_ROOT "/VM", + NULL, + &err)); + g_assert_no_error(err); + + console_ids = qemu_dbus_display1_vm_get_console_ids( + QEMU_DBUS_DISPLAY1_VM(vm)); + if (console_ids) { + n_ids = g_variant_n_children(console_ids); + } + return n_ids; +} + +static void +wait_device_event(QTestState *qts, const char *event_name, const char *id) +{ + QDict *resp, *data; + QString *qstr; + + for (;;) { + resp = qtest_qmp_eventwait_ref(qts, event_name); + data = qdict_get_qdict(resp, "data"); + if (!data || !qdict_get(data, "device")) { + qobject_unref(resp); + continue; + } + qstr = qobject_to(QString, qdict_get(data, "device")); + if (!strcmp(qstring_get_str(qstr), id)) { + qobject_unref(resp); + break; + } + qobject_unref(resp); + } +} + +static void +test_dbus_display_hotplug(void) +{ + g_autoptr(GDBusConnection) conn = NULL; + QTestState *qts = NULL; + gsize n; + + test_setup_args(&qts, &conn, + "-machine q35" + " -device pcie-root-port,id=rp0" + " -display dbus,p2p=yes" + " -name dbus-test"); + + n = get_console_ids_count(conn); + g_assert_cmpuint(n, ==, 1); + + qtest_qmp_device_add(qts, "bochs-display", "bochs0", + "{'bus': 'rp0'}"); + + n = get_console_ids_count(conn); + g_assert_cmpuint(n, ==, 2); + + /* + * On q35, PCI device removal is ACPI-based and requires guest + * acknowledgement. Since qtest has no guest OS, issue the delete + * request and force removal via system reset. + */ + qtest_qmp_device_del_send(qts, "bochs0"); + qtest_system_reset_nowait(qts); + wait_device_event(qts, "DEVICE_DELETED", "bochs0"); + + n = get_console_ids_count(conn); + g_assert_cmpuint(n, ==, 1); + + g_clear_object(&conn); + qtest_quit(qts); +} + int main(int argc, char **argv) { @@ -369,6 +463,9 @@ main(int argc, char **argv) qtest_add_data_func("/dbus-display/console", GINT_TO_POINTER(false), test_dbus_display_console); qtest_add_data_func("/dbus-display/console/map", GINT_TO_POINTER(true), test_dbus_display_console); qtest_add_func("/dbus-display/keyboard", test_dbus_display_keyboard); + if (qtest_has_machine("q35") && qtest_has_device("bochs-display")) { + qtest_add_func("/dbus-display/hotplug", test_dbus_display_hotplug); + } return g_test_run(); } diff --git a/tests/qtest/dump-test.c b/tests/qtest/dump-test.c new file mode 100644 index 0000000000..30c95a3528 --- /dev/null +++ b/tests/qtest/dump-test.c @@ -0,0 +1,331 @@ +/* + * QTest testcase for dump-guest-memory + * + * Generic coverage for the dump-guest-memory QMP command and the + * query-dump-guest-memory-capability reporting, exercised on a bare + * machine (no guest OS required). + * + * Copyright (c) 2026 Virtuozzo International GmbH + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "libqos/libqos-pc.h" +#include "libqos/fw_cfg.h" +#include "qobject/qdict.h" +#include "qobject/qlist.h" +#include "qobject/qstring.h" +#include "qemu/bswap.h" +#include "qemu/win_dump_defs.h" +#include "standard-headers/linux/qemu_fw_cfg.h" +#include "hw/misc/vmcoreinfo.h" +#include "elf.h" + +#define KDUMP_RAW_MAGIC "KDUMP " +#define KDUMP_FLAT_MAGIC "makedumpfile" + +static QTestState *dump_test_start(void) +{ + return qtest_initf("-machine q35 -accel qtest -m 16"); +} + +static void assert_file_magic(const char *path, const char *magic, size_t len) +{ + g_autofree char *buf = g_malloc0(len); + FILE *f = fopen(path, "rb"); + + g_assert_nonnull(f); + g_assert_cmpint(fread(buf, 1, len, f), ==, len); + fclose(f); + g_assert_cmpint(memcmp(buf, magic, len), ==, 0); +} + +/* validate that the file is a sane x86 ELF core, not just the leading magic */ +static void assert_valid_elf_core(const char *path) +{ + unsigned char e[64]; + FILE *f = fopen(path, "rb"); + uint16_t e_type, e_machine, e_phnum; + + g_assert_nonnull(f); + g_assert_cmpint(fread(e, 1, sizeof(e), f), ==, sizeof(e)); + fclose(f); + + g_assert_cmpint(memcmp(e, ELFMAG, SELFMAG), ==, 0); + + /* e_type and e_machine sit at the same offset for ELF32 and ELF64 */ + e_type = lduw_le_p(e + 16); + e_machine = lduw_le_p(e + 18); + g_assert_cmpint(e_type, ==, ET_CORE); + g_assert(e_machine == EM_386 || e_machine == EM_X86_64); + + /* e_phnum lives at a class-dependent offset */ + if (e[EI_CLASS] == ELFCLASS64) { + e_phnum = lduw_le_p(e + 56); + } else { + e_phnum = lduw_le_p(e + 44); + } + g_assert_cmpint(e_phnum, >, 0); +} + +/* dump-guest-memory to a fresh temp file; returns the path (caller frees) */ +static char *do_dump(QTestState *qts, const char *format) +{ + g_autofree char *tmp = NULL; + g_autofree char *proto = NULL; + GError *err = NULL; + int fd; + + fd = g_file_open_tmp("dump-test-XXXXXX", &tmp, &err); + g_assert_no_error(err); + close(fd); + proto = g_strdup_printf("file:%s", tmp); + + if (format) { + qtest_qmp_assert_success(qts, + "{ 'execute': 'dump-guest-memory'," + " 'arguments': { 'paging': false, 'protocol': %s," + " 'format': %s } }", proto, format); + } else { + qtest_qmp_assert_success(qts, + "{ 'execute': 'dump-guest-memory'," + " 'arguments': { 'paging': false, 'protocol': %s } }", proto); + } + + return g_steal_pointer(&tmp); +} + +/* query-dump-guest-memory-capability must always advertise at least 'elf' */ +static void test_query_capability(void) +{ + QTestState *qts = dump_test_start(); + QDict *resp, *ret; + QList *formats; + QListEntry *e; + bool has_elf = false; + + resp = qtest_qmp(qts, + "{ 'execute': 'query-dump-guest-memory-capability' }"); + g_assert(qdict_haskey(resp, "return")); + ret = qdict_get_qdict(resp, "return"); + formats = qdict_get_qlist(ret, "formats"); + g_assert_nonnull(formats); + + QLIST_FOREACH_ENTRY(formats, e) { + QString *qs = qobject_to(QString, qlist_entry_obj(e)); + + if (g_str_equal(qstring_get_str(qs), "elf")) { + has_elf = true; + } + } + g_assert_true(has_elf); + + qobject_unref(resp); + qtest_quit(qts); +} + +static void test_dump_elf(void) +{ + QTestState *qts = dump_test_start(); + g_autofree char *path = do_dump(qts, NULL); + + assert_valid_elf_core(path); + unlink(path); + qtest_quit(qts); +} + +/* non-raw kdump is emitted in makedumpfile flattened format */ +static void test_dump_kdump_zlib(void) +{ + QTestState *qts = dump_test_start(); + g_autofree char *path = do_dump(qts, "kdump-zlib"); + + assert_file_magic(path, KDUMP_FLAT_MAGIC, strlen(KDUMP_FLAT_MAGIC)); + unlink(path); + qtest_quit(qts); +} + +/* raw kdump starts with the on-disk KDUMP header */ +static void test_dump_kdump_raw_zlib(void) +{ + QTestState *qts = dump_test_start(); + g_autofree char *path = do_dump(qts, "kdump-raw-zlib"); + + assert_file_magic(path, KDUMP_RAW_MAGIC, strlen(KDUMP_RAW_MAGIC)); + unlink(path); + qtest_quit(qts); +} + +/* an unknown protocol must be rejected, not crash the VM */ +static void test_dump_invalid_protocol(void) +{ + QTestState *qts = dump_test_start(); + g_autofree char *path = NULL; + QDict *resp; + + resp = qtest_qmp(qts, + "{ 'execute': 'dump-guest-memory'," + " 'arguments': { 'paging': false, 'protocol': 'bogus:/x' } }"); + g_assert(qdict_haskey(resp, "error")); + qobject_unref(resp); + + /* VM is still alive and dumping still works afterwards */ + path = do_dump(qts, NULL); + assert_valid_elf_core(path); + unlink(path); + + qtest_quit(qts); +} + +/* + * Requesting win-dmp without a Windows dump header in vmcoreinfo must be + * rejected with a clear error -- and must leave the VM usable, rather than + * produce a bogus dump. + */ +static void test_dump_win_dmp_unavailable(void) +{ + QTestState *qts = dump_test_start(); + g_autofree char *tmp = NULL; + g_autofree char *proto = NULL; + g_autofree char *path = NULL; + GError *err = NULL; + QDict *resp, *error; + const char *desc; + int fd; + + fd = g_file_open_tmp("dump-test-XXXXXX", &tmp, &err); + g_assert_no_error(err); + close(fd); + proto = g_strdup_printf("file:%s", tmp); + + resp = qtest_qmp(qts, + "{ 'execute': 'dump-guest-memory'," + " 'arguments': { 'paging': false, 'protocol': %s," + " 'format': 'win-dmp' } }", proto); + error = qdict_get_qdict(resp, "error"); + g_assert_nonnull(error); + desc = qdict_get_try_str(error, "desc"); + g_assert_nonnull(desc); + g_assert_nonnull(strstr(desc, "vmcoreinfo")); + qobject_unref(resp); + unlink(tmp); + + /* the failed request must not wedge the VM: a plain dump still works */ + path = do_dump(qts, NULL); + assert_valid_elf_core(path); + unlink(path); + + qtest_quit(qts); +} + +static bool capability_has_format(QTestState *qts, const char *want) +{ + QDict *resp, *ret; + QList *formats; + QListEntry *e; + bool found = false; + + resp = qtest_qmp(qts, + "{ 'execute': 'query-dump-guest-memory-capability' }"); + g_assert(qdict_haskey(resp, "return")); + ret = qdict_get_qdict(resp, "return"); + formats = qdict_get_qlist(ret, "formats"); + g_assert_nonnull(formats); + + QLIST_FOREACH_ENTRY(formats, e) { + QString *qs = qobject_to(QString, qlist_entry_obj(e)); + + if (g_str_equal(qstring_get_str(qs), want)) { + found = true; + } + } + qobject_unref(resp); + return found; +} + +/* + * win-dmp becomes available only once the guest exposes a Windows dump + * header through vmcoreinfo. Forge exactly such a note -- the layout a + * Windows guest with the QEMU vmcoreinfo writer produces: a fixed-size ELF + * note header followed by a WinDumpHeader64 carrying the PAGE/DU64 + * signatures -- place it in guest RAM, point the vmcoreinfo device at it + * via fw_cfg, and check that win-dmp flips from unavailable to available. + * + * This only covers availability *reporting*; the actual win-dmp generation + * (create_win_dump()) needs real Windows kernel structures and is not + * exercised here. + */ +static void test_dump_win_dmp_available(void) +{ + const uint64_t paddr = 0x800000; /* 8 MiB, inside guest RAM */ + size_t notesz = VMCOREINFO_WIN_DUMP_NOTE_SIZE64; + g_autofree uint8_t *note = g_malloc0(notesz); + Elf64_Nhdr *nhdr = (Elf64_Nhdr *)note; + WinDumpHeader64 *hdr; + FWCfgVMCoreInfo info; + QFWCFG *fw_cfg; + QOSState *qs; + + /* the WinDumpHeader64 must sit right after the fixed ELF note header */ + g_assert_cmpint(sizeof(WinDumpHeader64) % 4, ==, 0); + + nhdr->n_namesz = cpu_to_le32(sizeof("VMCOREINFO")); /* 11 -> padded 12 */ + nhdr->n_descsz = cpu_to_le32(sizeof(WinDumpHeader64)); + nhdr->n_type = 0; + memcpy(note + sizeof(Elf64_Nhdr), "VMCOREINFO", sizeof("VMCOREINFO") - 1); + + hdr = (WinDumpHeader64 *)(note + VMCOREINFO_ELF_NOTE_HDR_SIZE); + memcpy(hdr->Signature, "PAGE", sizeof(hdr->Signature)); + memcpy(hdr->ValidDump, "DU64", sizeof(hdr->ValidDump)); + + qs = qtest_pc_boot("-device vmcoreinfo -m 16"); + fw_cfg = pc_fw_cfg_init(qs->qts); + + /* with no guest note yet, win-dmp must not be advertised */ + g_assert_false(capability_has_format(qs->qts, "win-dmp")); + + /* place the forged note in guest RAM and point vmcoreinfo at it */ + qtest_memwrite(qs->qts, paddr, note, notesz); + + memset(&info, 0, sizeof(info)); + info.host_format = cpu_to_le16(FW_CFG_VMCOREINFO_FORMAT_ELF); + info.guest_format = cpu_to_le16(FW_CFG_VMCOREINFO_FORMAT_ELF); + info.size = cpu_to_le32(notesz); + info.paddr = cpu_to_le64(paddr); + qfw_cfg_write_file(fw_cfg, qs, FW_CFG_VMCOREINFO_FILENAME, + &info, sizeof(info)); + + /* now win-dmp must be reported as available */ + g_assert_true(capability_has_format(qs->qts, "win-dmp")); + + pc_fw_cfg_uninit(fw_cfg); + qtest_shutdown(qs); +} + +int main(int argc, char **argv) +{ + const char *arch = qtest_get_arch(); + + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/dump/query-capability", test_query_capability); + qtest_add_func("/dump/elf", test_dump_elf); + qtest_add_func("/dump/kdump-zlib", test_dump_kdump_zlib); + qtest_add_func("/dump/kdump-raw-zlib", test_dump_kdump_raw_zlib); + qtest_add_func("/dump/invalid-protocol", test_dump_invalid_protocol); + + /* win-dmp is an x86_64-only format */ + if (g_str_equal(arch, "x86_64")) { + qtest_add_func("/dump/win-dmp-unavailable", + test_dump_win_dmp_unavailable); + + if (qtest_has_device("vmcoreinfo")) { + qtest_add_func("/dump/win-dmp-available", + test_dump_win_dmp_available); + } + } + + return g_test_run(); +} diff --git a/tests/qtest/flexcan-test.c b/tests/qtest/flexcan-test.c new file mode 100644 index 0000000000..87d947a806 --- /dev/null +++ b/tests/qtest/flexcan-test.c @@ -0,0 +1,421 @@ +/* + * QTests for FlexCAN CAN controller device model + * + * Copyright (c) 2025 Matyas Bobek <matyas.bobek@gmail.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest-single.h" + +#include "hw/net/flexcan.h" +#include "hw/net/can/flexcan_regs.h" + +#define FSL_IMX6_CAN2_ADDR 0x02094000 +#define FSL_IMX6_CAN2_SIZE 0x4000 +#define FSL_IMX6_CAN1_ADDR 0x02090000 +#define FSL_IMX6_CAN1_SIZE 0x4000 + +#define FC_QEMU_ARGS "-nographic -M sabrelite " \ + "-object can-bus,id=qcan0 " \ + "-machine canbus0=qcan0 -machine canbus1=qcan0" + +/* used for masking out unused/reserved bits */ +#define FC_MB_CNT_USED_MASK (~0xF080FFFFu) + +#define FCREG(BASE_ADDR, REG) ((BASE_ADDR) + offsetof(FlexcanRegs, REG)) +#define FCMB(BASE_ADDR, MB_IDX, WORD_IDX) (FCREG(BASE_ADDR, mbs) + \ + 0x10 * (MB_IDX) + 4 * (WORD_IDX)) + +typedef struct FcTestFrame { + uint32_t id; + uint32_t data[2]; + uint8_t len; + bool ide; + bool rtr; + + /* rx only */ + bool expect_overrun; +} FcTestFrame; + +static const FcTestFrame fc_test_frame_1 = { + .id = 0x5AF, + .len = 8, + .data = { + 0x01020304, + 0x0A0B0C0D + }, + .ide = false +}; + +static const FcTestFrame fc_test_frame_1_ide = { + .id = 0x105AF5AF, + .len = 8, + .data = { + 0x01020304, + 0x0A0B0C0D + }, + .ide = true +}; + +static void fc_reset(hwaddr ba, uint32_t mcr_flags) +{ + /* disable */ + writel(FCREG(ba, mcr), 0xD890000F); + + /* enable in freeze mode */ + writel(FCREG(ba, mcr), 0x5980000F); + + /* soft reset */ + writel(FCREG(ba, mcr), 0x5980000F | FLEXCAN_MCR_SOFTRST); + + g_assert_cmpuint(readl(FCREG(ba, mcr)), ==, 0x5980000F); + g_assert_cmpuint(readl(FCREG(ba, ctrl)), ==, 0); + g_assert_cmpuint(readl(FCREG(ba, ctrl2)), ==, 0); + + writel(FCREG(ba, mcr), (0x5980000F & ~FLEXCAN_MCR_HALT) | mcr_flags); + writel(FCREG(ba, ctrl2), FLEXCAN_CTRL2_RRS); + + /* initialize all mailboxes as rx inactive */ + for (int i = 0; i < FLEXCAN_MAILBOX_COUNT; i++) { + writel(FCMB(ba, i, 0), FLEXCAN_MB_CODE_RX_INACTIVE); + writel(FCMB(ba, i, 1), 0); + writel(FCMB(ba, i, 2), 0); + writel(FCMB(ba, i, 3), 0); + } +} + +static uint64_t fc_get_irqs(hwaddr ba) +{ + return (uint64_t)readl(FCREG(ba, iflag1)) | + ((uint64_t)readl(FCREG(ba, iflag2)) << 32); +} + +static void fc_clear_irq(hwaddr ba, int idx) +{ + if (idx >= 32) { + writel(FCREG(ba, iflag2), (uint32_t)1 << (idx - 32)); + } else { + writel(FCREG(ba, iflag1), (uint32_t)1 << idx); + } + + g_assert_cmpuint(fc_get_irqs(ba) & ((uint64_t)1 << idx), ==, 0); +} + +static void fc_setup_rx_mb(hwaddr ba, int mbidx) +{ + writel(FCMB(ba, mbidx, 0), FLEXCAN_MB_CODE_RX_EMPTY); + writel(FCMB(ba, mbidx, 1), 0); + /* the data value should be ignored for RX mb */ + writel(FCMB(ba, mbidx, 2), 0); + writel(FCMB(ba, mbidx, 3), 0); + + g_assert_cmpuint(readl(FCMB(ba, mbidx, 0)), ==, FLEXCAN_MB_CODE_RX_EMPTY); +} + +static void fc_tx(hwaddr ba, int mbidx, const FcTestFrame *frame) +{ + g_assert_cmpuint(frame->len, <=, 8); + + writel(FCMB(ba, mbidx, 0), FLEXCAN_MB_CODE_TX_INACTIVE); + uint32_t id = frame->ide ? frame->id : frame->id << 18; + writel(FCMB(ba, mbidx, 1), id); + writel(FCMB(ba, mbidx, 2), frame->data[0]); + writel(FCMB(ba, mbidx, 3), frame->data[1]); + + uint32_t ctrl = FLEXCAN_MB_CODE_TX_DATA | FLEXCAN_MB_CNT_LENGTH(frame->len); + if (frame->ide) { + ctrl |= FLEXCAN_MB_CNT_IDE | FLEXCAN_MB_CNT_SRR; + } + if (frame->rtr) { + ctrl |= FLEXCAN_MB_CNT_RTR; + } + writel(FCMB(ba, mbidx, 0), ctrl); + + /* check frame was transmitted */ + g_assert_cmpuint(fc_get_irqs(ba) & ((uint64_t)1 << mbidx), + !=, 0); + + uint32_t xpectd_ctrl = (ctrl & ~FLEXCAN_MB_CODE_MASK) | + FLEXCAN_MB_CODE_TX_INACTIVE; + g_assert_cmpuint(readl(FCMB(ba, mbidx, 0)) & FC_MB_CNT_USED_MASK, ==, + xpectd_ctrl); + /* other fields should stay unchanged */ + g_assert_cmpuint(readl(FCMB(ba, mbidx, 1)), ==, id); + g_assert_cmpuint(readl(FCMB(ba, mbidx, 2)), ==, frame->data[0]); + g_assert_cmpuint(readl(FCMB(ba, mbidx, 3)), ==, frame->data[1]); +} + +static void fc_rx_check(hwaddr ba, int mbidx, const FcTestFrame *frame) +{ + uint32_t xpectd_ctrl = frame->expect_overrun ? FLEXCAN_MB_CODE_RX_OVERRUN + : FLEXCAN_MB_CODE_RX_FULL; + xpectd_ctrl |= FLEXCAN_MB_CNT_LENGTH(frame->len) | FLEXCAN_MB_CNT_SRR; + if (frame->ide) { + xpectd_ctrl |= FLEXCAN_MB_CNT_IDE; + } + if (frame->rtr) { + xpectd_ctrl |= FLEXCAN_MB_CNT_RTR; + } + + uint32_t xpectd_id = frame->ide ? frame->id : frame->id << 18; + + uint32_t ctrl = readl(FCMB(ba, mbidx, 0)) & FC_MB_CNT_USED_MASK; + if ((ctrl & FLEXCAN_MB_CODE_MASK) == FLEXCAN_MB_CODE_RX_EMPTY) { + fprintf(stderr, "expected frame (id=0x%X) not received\n", frame->id); + } + + g_assert_cmpuint(ctrl, ==, xpectd_ctrl); + g_assert_cmpuint(readl(FCMB(ba, mbidx, 1)), ==, xpectd_id); + g_assert_cmpuint(readl(FCMB(ba, mbidx, 2)), ==, frame->data[0]); + g_assert_cmpuint(readl(FCMB(ba, mbidx, 3)), ==, frame->data[1]); +} + +static void fc_check_empty_multi(hwaddr ba, int idx_count, int mbidxs[]) +{ + for (int i = 0; i < FLEXCAN_MAILBOX_COUNT; i++) { + bool check_empty = true; + int ctrl = readl(FCMB(ba, i, 0)); + + for (int j = 0; j < idx_count; j++) { + if (i == mbidxs[j]) { + check_empty = false; + } + } + + if (check_empty) { + if (!(ctrl == FLEXCAN_MB_CODE_RX_EMPTY || + ctrl == FLEXCAN_MB_CODE_RX_INACTIVE)) { + g_assert_cmpuint(ctrl, ==, FLEXCAN_MB_CODE_RX_INACTIVE); + } + g_assert_cmpuint(readl(FCMB(ba, i, 1)), ==, 0); + g_assert_cmpuint(readl(FCMB(ba, i, 2)), ==, 0); + g_assert_cmpuint(readl(FCMB(ba, i, 3)), ==, 0); + } else { + g_assert_cmpuint( + ctrl & FLEXCAN_MB_CODE_MASK, !=, + FLEXCAN_MB_CODE_RX_INACTIVE + ); + } + } +} + +static void fc_check_empty(hwaddr ba, int mbidx) +{ + fc_check_empty_multi(ba, 1, &mbidx); +} + +static void flexcan_test_linux_probe_impl(hwaddr fba) +{ + /* -- test a Linux driver-like probe sequence -- */ + /* disable */ + writel(FCREG(fba, mcr), 0xD890000F); + g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0xD890000F); + g_assert_cmpuint(readl(FCREG(fba, ctrl)), ==, 0); + + /* set bit in reserved field we do not implement (CTRL_CLK_SRC) */ + writel(FCREG(fba, ctrl), 0x00002000); + g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0xD890000F); + + /* enable in freeze mode */ + writel(FCREG(fba, mcr), 0x5980000F); + g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0x5980000F); + + /* enable Rx-FIFO */ + writel(FCREG(fba, mcr), 0x7980000F); + g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0x7980000F); + g_assert_cmpuint(readl(FCREG(fba, ecr)), ==, 0); + + /* disable */ + writel(FCREG(fba, mcr), 0xF890000F); + g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0xF890000F); +} + +static void flexcan_test_freeze_disable_interaction_impl(hwaddr fba) +{ + /* -- test normal <=> freeze <=> disable transitions -- */ + + /* leave freeze in disabled, FRZ_ACK should stay cleared */ + writel(FCREG(fba, mcr), 0xF890000F); /* disable */ + g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0xF890000F); + writel(FCREG(fba, mcr), 0xB890000F); /* by clearing FRZ */ + g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0xB890000F); + + writel(FCREG(fba, mcr), 0xF890000F); /* disable */ + g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0xF890000F); + writel(FCREG(fba, mcr), 0xE890000F); /* by clearing HALT */ + g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0xE890000F); + + writel(FCREG(fba, mcr), 0xF890000F); /* disable */ + g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0xF890000F); + writel(FCREG(fba, mcr), 0xA890000F); /* by clearing both */ + g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0xA890000F); + + /* enter and leave freeze */ + writel(FCREG(fba, mcr), 0x7980000F); /* enable in freeze mode */ + g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0x7980000F); + writel(FCREG(fba, mcr), 0x3980000F); /* leave by clearing FRZ */ + g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0x3080000F); + + writel(FCREG(fba, mcr), 0x7980000F); /* enable in freeze mode */ + g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0x7980000F); + writel(FCREG(fba, mcr), 0x6980000F); /* leave by clearing HALT */ + g_assert_cmpuint(readl(FCREG(fba, mcr)), ==, 0x6080000F); +} + +static void flexcan_test_mailbox_io_impl(hwaddr ba_tx, hwaddr ba_rx) +{ + /* -- test correct handling of mailbox IO -- */ + const int test_1_mbidx = 0; + fc_reset(ba_tx, + FLEXCAN_MCR_SRX_DIS | FLEXCAN_MCR_MAXMB(FLEXCAN_MAILBOX_COUNT)); + fc_reset(ba_rx, + FLEXCAN_MCR_SRX_DIS | FLEXCAN_MCR_MAXMB(FLEXCAN_MAILBOX_COUNT)); + + fc_setup_rx_mb(ba_rx, test_1_mbidx); + fc_tx(ba_tx, test_1_mbidx, &fc_test_frame_1_ide); + g_assert_cmpuint(fc_get_irqs(ba_rx), ==, 1 << test_1_mbidx); + fc_rx_check(ba_rx, test_1_mbidx, &fc_test_frame_1_ide); + readl(FCREG(ba_rx, timer)); /* reset lock */ + + writel(FCMB(ba_rx, test_1_mbidx, 0), 0); + g_assert_cmpuint(readl(FCMB(ba_rx, test_1_mbidx, 0)), ==, 0); + writel(FCMB(ba_rx, test_1_mbidx, 1), 0x99AABBCC); + g_assert_cmpuint(readl(FCMB(ba_rx, test_1_mbidx, 1)), ==, 0x99AABBCC); +} + +static void flexcan_test_dual_transmit_receive_impl(hwaddr ba_tx, hwaddr ba_rx) +{ + /* -- test TX and RX between the two FlexCAN instances -- */ + const int test_1_mbidx = 50; + const int test_rounds = 50; + + /* self-receive enabled on tx FC */ + fc_reset(ba_tx, + FLEXCAN_MCR_MAXMB(FLEXCAN_MAILBOX_COUNT)); + fc_reset(ba_rx, + FLEXCAN_MCR_SRX_DIS | FLEXCAN_MCR_MAXMB(FLEXCAN_MAILBOX_COUNT)); + + /* tests self-receive on tx and reception on rx */ + fc_setup_rx_mb(ba_rx, test_1_mbidx); + fc_check_empty(ba_rx, test_1_mbidx); + fc_setup_rx_mb(ba_tx, test_1_mbidx + 1); + fc_check_empty(ba_tx, test_1_mbidx + 1); + g_assert_cmpuint(fc_get_irqs(ba_rx), ==, 0); + g_assert_cmpuint(fc_get_irqs(ba_tx), ==, 0); + + fc_tx(ba_tx, test_1_mbidx, &fc_test_frame_1); + fc_clear_irq(ba_tx, test_1_mbidx); + + fc_rx_check(ba_rx, test_1_mbidx, &fc_test_frame_1); + fc_check_empty(ba_rx, test_1_mbidx); + fc_rx_check(ba_tx, test_1_mbidx + 1, &fc_test_frame_1); + int tx_non_empty_mbidxs[] = {test_1_mbidx, test_1_mbidx + 1}; + + fc_check_empty_multi(ba_tx, 2, tx_non_empty_mbidxs); + fc_clear_irq(ba_rx, test_1_mbidx); + fc_clear_irq(ba_tx, test_1_mbidx + 1); + readl(FCREG(ba_rx, timer)); /* reset lock */ + + for (int ridx = 0; ridx < test_rounds; ridx++) { + /* test extended IDs sent to all mailboxes */ + for (int i = 0; i < FLEXCAN_MAILBOX_COUNT; i++) { + fc_setup_rx_mb(ba_rx, i); + } + fc_check_empty_multi(ba_rx, 0, NULL); + g_assert_cmpuint(fc_get_irqs(ba_rx), ==, 0); + g_assert_cmpuint(fc_get_irqs(ba_tx), ==, 0); + + for (int i = 0; i < FLEXCAN_MAILBOX_COUNT; i++) { + fc_tx(ba_tx, i, &fc_test_frame_1_ide); + } + g_assert_cmpuint(fc_get_irqs(ba_rx), ==, UINT64_MAX); + g_assert_cmpuint(fc_get_irqs(ba_tx), ==, UINT64_MAX); + for (int i = 0; i < FLEXCAN_MAILBOX_COUNT; i++) { + fc_rx_check(ba_rx, i, &fc_test_frame_1_ide); + } + + /* reset interrupts */ + writel(FCREG(ba_rx, iflag1), UINT32_MAX); + writel(FCREG(ba_rx, iflag2), UINT32_MAX); + writel(FCREG(ba_tx, iflag1), UINT32_MAX); + writel(FCREG(ba_tx, iflag2), UINT32_MAX); + g_assert_cmpuint(fc_get_irqs(ba_rx), ==, 0); + g_assert_cmpuint(fc_get_irqs(ba_tx), ==, 0); + } +} + +static void flexcan_test_tx_abort_impl(hwaddr ba) +{ + /* -- test the TX abort feature -- */ + fc_reset(ba, + FLEXCAN_MCR_SRX_DIS | FLEXCAN_MCR_MAXMB(FLEXCAN_MAILBOX_COUNT)); + + + for (int mbidx = 0; mbidx < FLEXCAN_MAILBOX_COUNT; mbidx++) { + fc_tx(ba, mbidx, &fc_test_frame_1); + + writel(FCMB(ba, mbidx, 0), FLEXCAN_MB_CODE_TX_ABORT); + g_assert_cmpuint(readl(FCMB(ba, mbidx, 0)), ==, + FLEXCAN_MB_CODE_TX_INACTIVE); + } +} + +static void flexcan_test_freeze_disable_interaction(void) +{ + qtest_start(FC_QEMU_ARGS); + flexcan_test_freeze_disable_interaction_impl(FSL_IMX6_CAN1_ADDR); + flexcan_test_freeze_disable_interaction_impl(FSL_IMX6_CAN2_ADDR); + qtest_end(); +} + +static void flexcan_test_linux_probe(void) +{ + qtest_start(FC_QEMU_ARGS); + flexcan_test_linux_probe_impl(FSL_IMX6_CAN1_ADDR); + flexcan_test_linux_probe_impl(FSL_IMX6_CAN2_ADDR); + qtest_end(); +} + +static void flexcan_test_dual_transmit_receive(void) +{ + qtest_start(FC_QEMU_ARGS); + flexcan_test_dual_transmit_receive_impl(FSL_IMX6_CAN1_ADDR, + FSL_IMX6_CAN2_ADDR); + flexcan_test_dual_transmit_receive_impl(FSL_IMX6_CAN2_ADDR, + FSL_IMX6_CAN1_ADDR); + qtest_end(); +} + +static void flexcan_test_tx_abort(void) +{ + qtest_start(FC_QEMU_ARGS); + flexcan_test_tx_abort_impl(FSL_IMX6_CAN1_ADDR); + flexcan_test_tx_abort_impl(FSL_IMX6_CAN2_ADDR); + qtest_end(); +} + +static void flexcan_test_mailbox_io(void) +{ + qtest_start(FC_QEMU_ARGS); + flexcan_test_mailbox_io_impl(FSL_IMX6_CAN1_ADDR, FSL_IMX6_CAN2_ADDR); + flexcan_test_mailbox_io_impl(FSL_IMX6_CAN2_ADDR, FSL_IMX6_CAN1_ADDR); + qtest_end(); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/net/flexcan/linux_probe", flexcan_test_linux_probe); + qtest_add_func("/net/flexcan/freeze_disable_interaction", + flexcan_test_freeze_disable_interaction); + qtest_add_func("/net/flexcan/dual_transmit_receive", + flexcan_test_dual_transmit_receive); + qtest_add_func("/net/flexcan/tx_abort", + flexcan_test_tx_abort); + qtest_add_func("/net/flexcan/mailbox_io", flexcan_test_mailbox_io); + + return g_test_run(); +} diff --git a/tests/qtest/libqos/virtio-9p-client.c b/tests/qtest/libqos/virtio-9p-client.c index af01d4c345..305b0dac63 100644 --- a/tests/qtest/libqos/virtio-9p-client.c +++ b/tests/qtest/libqos/virtio-9p-client.c @@ -241,6 +241,9 @@ static const char *rmessage_name(uint8_t id) id == P9_RUNLINKAT ? "RUNLINKAT" : id == P9_RFLUSH ? "RFLUSH" : id == P9_RREADDIR ? "RREADDIR" : + id == P9_RREAD ? "RREAD" : + id == P9_RCLUNK ? "RCLUNK" : + id == P9_RXATTRCREATE ? "RXATTRCREATE" : "<unknown>"; } @@ -1103,3 +1106,124 @@ void v9fs_runlinkat(P9Req *req) v9fs_req_recv(req, P9_RUNLINKAT); v9fs_req_free(req); } + +/* size[4] Tread tag[2] fid[4] offset[8] count[4] */ +TReadRes v9fs_tread(TReadOpt opt) +{ + P9Req *req; + uint32_t err; + + g_assert(opt.client); + + uint32_t body_size = 4 + 8 + 4; + + req = v9fs_req_init(opt.client, body_size, P9_TREAD, opt.tag); + v9fs_uint32_write(req, opt.fid); + v9fs_uint64_write(req, opt.offset); + v9fs_uint32_write(req, opt.count); + v9fs_req_send(req); + + if (!opt.requestOnly) { + v9fs_req_wait_for_reply(req, NULL); + if (opt.expectErr) { + v9fs_rlerror(req, &err); + g_assert_cmpint(err, ==, opt.expectErr); + } else { + v9fs_rread(req, opt.rread.count, opt.rread.data); + } + req = NULL; /* request was freed */ + } + + return (TReadRes) { + .req = req, + .count = opt.rread.count ? *opt.rread.count : 0 + }; +} + +/* size[4] Rread tag[2] count[4] data[count] */ +void v9fs_rread(P9Req *req, uint32_t *count, void *data) +{ + v9fs_req_recv(req, P9_RREAD); + v9fs_uint32_read(req, count); + if (data && *count > 0) { + v9fs_memread(req, data, *count); + } + v9fs_req_free(req); +} + +/* size[4] Tclunk tag[2] fid[4] */ +TClunkRes v9fs_tclunk(TClunkOpt opt) +{ + P9Req *req; + uint32_t err; + + g_assert(opt.client); + + req = v9fs_req_init(opt.client, 4, P9_TCLUNK, opt.tag); + v9fs_uint32_write(req, opt.fid); + v9fs_req_send(req); + + if (!opt.requestOnly) { + v9fs_req_wait_for_reply(req, NULL); + if (opt.expectErr) { + v9fs_rlerror(req, &err); + g_assert_cmpint(err, ==, opt.expectErr); + } else { + v9fs_rclunk(req); + } + req = NULL; /* request was freed */ + } + + return (TClunkRes) { .req = req }; +} + +/* size[4] Rclunk tag[2] */ +void v9fs_rclunk(P9Req *req) +{ + v9fs_req_recv(req, P9_RCLUNK); + v9fs_req_free(req); +} + +/* size[4] Txattrcreate tag[2] fid[4] name[s] attr_size[8] flags[4] */ +TXattrCreateRes v9fs_txattrcreate(TXattrCreateOpt opt) +{ + P9Req *req; + uint32_t err; + + g_assert(opt.client); + g_assert(opt.name); + + uint32_t body_size = 4 + 8 + 4; + uint16_t string_size = v9fs_string_size(opt.name); + + g_assert_cmpint(body_size, <=, UINT32_MAX - string_size); + body_size += string_size; + + req = v9fs_req_init(opt.client, body_size, P9_TXATTRCREATE, opt.tag); + v9fs_uint32_write(req, opt.fid); + v9fs_string_write(req, opt.name); + v9fs_uint64_write(req, opt.size); + v9fs_uint32_write(req, opt.flags); + v9fs_req_send(req); + + err = 0; + if (!opt.requestOnly) { + v9fs_req_wait_for_reply(req, NULL); + if (opt.expectErr) { + v9fs_rlerror(req, &err); + g_assert_cmpint(err, ==, opt.expectErr); + } else { + v9fs_rxattrcreate(req); + } + req = NULL; /* request was freed */ + } + + return (TXattrCreateRes) { .req = req, .err = err }; +} + +/* size[4] Rxattrcreate tag[2] */ +void v9fs_rxattrcreate(P9Req *req) +{ + v9fs_req_recv(req, P9_RXATTRCREATE); + v9fs_req_free(req); +} diff --git a/tests/qtest/libqos/virtio-9p-client.h b/tests/qtest/libqos/virtio-9p-client.h index e3221a3104..4b04324503 100644 --- a/tests/qtest/libqos/virtio-9p-client.h +++ b/tests/qtest/libqos/virtio-9p-client.h @@ -21,7 +21,8 @@ #include "qgraph.h" #include "tests/qtest/libqtest-single.h" -#define P9_MAX_SIZE 4096 /* Max size of a T-message or R-message */ +/* Max size of a T-message or R-message */ +#define P9_MAX_SIZE (32 * 1024) typedef struct { QTestState *qts; @@ -473,6 +474,85 @@ typedef struct TunlinkatRes { P9Req *req; } TunlinkatRes; +/* options for 'Tread' 9p request */ +typedef struct TReadOpt { + /* 9P client being used (mandatory) */ + QVirtio9P *client; + /* user supplied tag number being returned with response (optional) */ + uint16_t tag; + /* file ID of file to read from (required) */ + uint32_t fid; + /* start position of read from beginning of file (optional) */ + uint64_t offset; + /* how many bytes to read (required) */ + uint32_t count; + /* data being received from 9p server as 'Rread' response (optional) */ + struct { + uint32_t *count; + void *data; + } rread; + /* only send Tread request but not wait for a reply? (optional) */ + bool requestOnly; + /* do we expect an Rlerror response, if yes which error code? (optional) */ + uint32_t expectErr; +} TReadOpt; + +/* result of 'Tread' 9p request */ +typedef struct TReadRes { + /* if requestOnly was set: request object for further processing */ + P9Req *req; + /* amount of bytes read */ + uint32_t count; +} TReadRes; + +/* options for 'Tclunk' 9p request */ +typedef struct TClunkOpt { + /* 9P client being used (mandatory) */ + QVirtio9P *client; + /* user supplied tag number being returned with response (optional) */ + uint16_t tag; + /* file ID to clunk (required) */ + uint32_t fid; + /* only send Tclunk request but not wait for a reply? (optional) */ + bool requestOnly; + /* do we expect an Rlerror response, if yes which error code? (optional) */ + uint32_t expectErr; +} TClunkOpt; + +/* result of 'Tclunk' 9p request */ +typedef struct TClunkRes { + /* if requestOnly was set: request object for further processing */ + P9Req *req; +} TClunkRes; + +/* options for 'Txattrcreate' 9p request */ +typedef struct TXattrCreateOpt { + /* 9P client being used (mandatory) */ + QVirtio9P *client; + /* user supplied tag number being returned with response (optional) */ + uint16_t tag; + /* file ID to convert to xattr fid (required) */ + uint32_t fid; + /* name of the xattr (required) */ + const char *name; + /* size of the xattr value (required) */ + uint64_t size; + /* flags: P9_XATTR_CREATE or P9_XATTR_REPLACE (optional) */ + uint32_t flags; + /* only send Txattrcreate request but not wait for a reply? (optional) */ + bool requestOnly; + /* do we expect an Rlerror response, if yes which error code? (optional) */ + uint32_t expectErr; +} TXattrCreateOpt; + +/* result of 'Txattrcreate' 9p request */ +typedef struct TXattrCreateRes { + /* if requestOnly was set: request object for further processing */ + P9Req *req; + /* error code if Rlerror received */ + uint32_t err; +} TXattrCreateRes; + void v9fs_set_allocator(QGuestAllocator *t_alloc); void v9fs_memwrite(P9Req *req, const void *addr, size_t len); void v9fs_memskip(P9Req *req, size_t len); @@ -524,5 +604,11 @@ TlinkRes v9fs_tlink(TlinkOpt); void v9fs_rlink(P9Req *req); TunlinkatRes v9fs_tunlinkat(TunlinkatOpt); void v9fs_runlinkat(P9Req *req); +TReadRes v9fs_tread(TReadOpt opt); +void v9fs_rread(P9Req *req, uint32_t *count, void *data); +TClunkRes v9fs_tclunk(TClunkOpt opt); +void v9fs_rclunk(P9Req *req); +TXattrCreateRes v9fs_txattrcreate(TXattrCreateOpt opt); +void v9fs_rxattrcreate(P9Req *req); #endif diff --git a/tests/qtest/libqos/virtio-9p.c b/tests/qtest/libqos/virtio-9p.c index 186fcc1141..823756de8c 100644 --- a/tests/qtest/libqos/virtio-9p.c +++ b/tests/qtest/libqos/virtio-9p.c @@ -228,6 +228,12 @@ static void regex_replace(GString *haystack, const char *pattern, g_string_assign(haystack, s); } +void virtio_9p_add_synth_driver_args(GString *cmd_line, const char *args) +{ + /* append passed args to '-fsdev ...' group */ + regex_replace(cmd_line, "(-fsdev \\w[^ ]*)", "\\1,%s", args); +} + void virtio_9p_assign_local_driver(GString *cmd_line, const char *args) { g_assert_nonnull(local_test_path); diff --git a/tests/qtest/libqos/virtio-9p.h b/tests/qtest/libqos/virtio-9p.h index 480727120e..e7efeef7a1 100644 --- a/tests/qtest/libqos/virtio-9p.h +++ b/tests/qtest/libqos/virtio-9p.h @@ -45,6 +45,12 @@ struct QVirtio9PDevice { }; /** + * Add required test specific args to the QEMU command line for the 9pfs + * 'synth' fs driver. + */ +void virtio_9p_add_synth_driver_args(GString *cmd_line, const char *args); + +/** * Creates the directory for the 9pfs 'local' filesystem driver to access. */ void virtio_9p_create_local_test_dir(void); diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 4897325d84..5e18b947c7 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -59,6 +59,7 @@ qtests_i386 = \ (config_all_devices.has_key('CONFIG_FDC_ISA') ? ['fdc-test'] : []) + \ (config_all_devices.has_key('CONFIG_I440FX') ? ['fw_cfg-test'] : []) + \ (config_all_devices.has_key('CONFIG_FW_CFG_DMA') ? ['vmcoreinfo-test'] : []) + \ + (config_all_devices.has_key('CONFIG_Q35') ? ['dump-test'] : []) + \ (config_all_devices.has_key('CONFIG_I440FX') ? ['i440fx-test'] : []) + \ (config_all_devices.has_key('CONFIG_I440FX') ? ['ide-test'] : []) + \ (config_all_devices.has_key('CONFIG_I440FX') ? ['numa-test'] : []) + \ @@ -155,7 +156,8 @@ qtests_loongarch64 = qtests_filter + \ (config_all_devices.has_key('CONFIG_LOONGARCH_VIRT') ? ['numa-test'] : []) + \ (unpack_edk2_blobs ? ['bios-tables-test'] : []) + \ ['boot-serial-test', - 'cpu-plug-test'] + 'cpu-plug-test', + 'migration-test'] qtests_m68k = ['boot-serial-test'] + \ qtests_filter @@ -250,6 +252,7 @@ qtests_arm = \ (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_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 config_all_devices.has_key('CONFIG_DM163')? ['dm163-test'] : []) + \ ['arm-cpu-features', @@ -295,6 +298,8 @@ qtests_riscv64 = ['riscv-csr-test'] + \ ['iommu-riscv-test'] : []) + \ (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : []) +qtests_hexagon = ['boot-serial-test'] + qos_test_ss = ss.source_set() qos_test_ss.add( 'ac97-test.c', diff --git a/tests/qtest/migration/Makefile b/tests/qtest/migration/Makefile index c183b69941..626e3974f7 100644 --- a/tests/qtest/migration/Makefile +++ b/tests/qtest/migration/Makefile @@ -5,7 +5,7 @@ # See the COPYING file in the top-level directory. # -TARGET_LIST = i386 aarch64 s390x ppc64 +TARGET_LIST = i386 aarch64 s390x ppc64 loongarch64 SRC_PATH = ../../../.. @@ -23,6 +23,8 @@ help: @echo " Possible targets are: $(TARGET_LIST)" override define __note +/* SPDX-License-Identifier: GPL-2.0-or-later */ + /* * This file is automatically generated from the assembly file in * tests/qtest/migration/$@. Edit that file and then run "make" diff --git a/tests/qtest/migration/aarch64/a-b-kernel.h b/tests/qtest/migration/aarch64/a-b-kernel.h index c444cdaf73..4d46997354 100644 --- a/tests/qtest/migration/aarch64/a-b-kernel.h +++ b/tests/qtest/migration/aarch64/a-b-kernel.h @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + /* * This file is automatically generated from the assembly file in * tests/qtest/migration/aarch64. Edit that file and then run "make" diff --git a/tests/qtest/migration/bootfile.c b/tests/qtest/migration/bootfile.c index 479c43231d..e46ad09de7 100644 --- a/tests/qtest/migration/bootfile.c +++ b/tests/qtest/migration/bootfile.c @@ -19,6 +19,7 @@ #include "bootfile.h" #include "i386/a-b-bootblock.h" #include "aarch64/a-b-kernel.h" +#include "loongarch64/a-b-kernel.h" #include "ppc64/a-b-kernel.h" #include "s390x/a-b-bios.h" @@ -57,6 +58,9 @@ char *bootfile_create(const char *arch, const char *dir, bool suspend_me) content = aarch64_kernel; len = sizeof(aarch64_kernel); g_assert(sizeof(aarch64_kernel) <= ARM_TEST_MAX_KERNEL_SIZE); + } else if (strcmp(arch, "loongarch64") == 0) { + content = loongarch64_kernel; + len = sizeof(loongarch64_kernel); } else { g_assert_not_reached(); } diff --git a/tests/qtest/migration/bootfile.h b/tests/qtest/migration/bootfile.h index 0ce5b34433..a4060ea4f7 100644 --- a/tests/qtest/migration/bootfile.h +++ b/tests/qtest/migration/bootfile.h @@ -42,6 +42,10 @@ */ #define ARM_TEST_MAX_KERNEL_SIZE (512 * 1024) +/* LoongArch64 */ +#define LOONGARCH_TEST_MEM_START (32 * 1024 * 1024) +#define LOONGARCH_TEST_MEM_END (100 * 1024 * 1024) + #ifndef MIGRATION_GUEST_CODE void bootfile_delete(void); char *bootfile_create(const char *arch, const char *dir, bool suspend_me); diff --git a/tests/qtest/migration/framework.c b/tests/qtest/migration/framework.c index a9b58d03aa..a830b96f41 100644 --- a/tests/qtest/migration/framework.c +++ b/tests/qtest/migration/framework.c @@ -360,6 +360,12 @@ int migrate_args(char **from, char **to, MigrateStart *args) arch_opts = g_strdup_printf("-cpu max -kernel %s", bootpath); start_address = ARM_TEST_MEM_START; end_address = ARM_TEST_MEM_END; + } else if (strcmp(arch, "loongarch64") == 0) { + memory_size = "256M"; + machine_alias = "virt"; + arch_opts = g_strdup_printf("-bios %s", bootpath); + start_address = LOONGARCH_TEST_MEM_START; + end_address = LOONGARCH_TEST_MEM_END; } else { g_assert_not_reached(); } diff --git a/tests/qtest/migration/i386/a-b-bootblock.h b/tests/qtest/migration/i386/a-b-bootblock.h index 3b5de60161..356392b83a 100644 --- a/tests/qtest/migration/i386/a-b-bootblock.h +++ b/tests/qtest/migration/i386/a-b-bootblock.h @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + /* * This file is automatically generated from the assembly file in * tests/qtest/migration/i386. Edit that file and then run "make" diff --git a/tests/qtest/migration/loongarch64/Makefile b/tests/qtest/migration/loongarch64/Makefile new file mode 100644 index 0000000000..3c8cfeee86 --- /dev/null +++ b/tests/qtest/migration/loongarch64/Makefile @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +# To specify cross compiler prefix, use CROSS_PREFIX= +# $ make CROSS_PREFIX=loongarch64-linux-gnu- + +.PHONY: all clean +all: a-b-kernel.h + +a-b-kernel.h: loongarch64.kernel + echo "$$__note" > $@ + xxd -i $< | sed -e 's/.*int.*//' >> $@ + +loongarch64.kernel: loongarch64.elf + $(CROSS_PREFIX)objcopy -j .text -O binary $< $@ + +loongarch64.elf: a-b-kernel.S + $(CROSS_PREFIX)gcc -o $@ -nostdlib -Wl,--build-id=none $< + +clean: + $(RM) *.kernel *.elf diff --git a/tests/qtest/migration/loongarch64/a-b-kernel.S b/tests/qtest/migration/loongarch64/a-b-kernel.S new file mode 100644 index 0000000000..577d4b238c --- /dev/null +++ b/tests/qtest/migration/loongarch64/a-b-kernel.S @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "../bootfile.h" + +#define LOONGARCH_CSR_CRMD 0 +#define LOONGARCH_VIRT_UART 0x1FE001E0 +.section .text + + .globl _start +_start: + /* output char 'A' to UART16550 */ + li.d $t0, LOONGARCH_VIRT_UART + li.w $t1, 'A' + st.b $t1, $t0, 0 + + /* traverse test memory region */ + li.d $t0, LOONGARCH_TEST_MEM_START + li.d $t1, LOONGARCH_TEST_MEM_END + li.d $t2, TEST_MEM_PAGE_SIZE + li.d $t4, LOONGARCH_VIRT_UART + li.w $t5, 'B' + +clean: + st.b $zero, $t0, 0 + add.d $t0, $t0, $t2 + bne $t0, $t1, clean + /* keeps a counter so we can limit the output speed */ + addi.d $t6, $zero, 0 + +mainloop: + li.d $t0, LOONGARCH_TEST_MEM_START + +innerloop: + ld.bu $t3, $t0, 0 + addi.w $t3, $t3, 1 + ext.w.b $t3, $t3 + st.b $t3, $t0, 0 + add.d $t0, $t0, $t2 + bne $t0, $t1, innerloop + + addi.d $t6, $t6, 1 + andi $t6, $t6, 31 + bnez $t6, mainloop + + st.b $t5, $t4, 0 + b mainloop diff --git a/tests/qtest/migration/loongarch64/a-b-kernel.h b/tests/qtest/migration/loongarch64/a-b-kernel.h new file mode 100644 index 0000000000..6d5d811f79 --- /dev/null +++ b/tests/qtest/migration/loongarch64/a-b-kernel.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* + * This file is automatically generated from the assembly file in + * tests/qtest/migration/loongarch64. Edit that file and then run "make" + * inside tests/qtest/migration to update, and then remember to send both + * the header and the assembler differences in your patch submission. + */ +unsigned char loongarch64_kernel[] = { + 0x0c, 0xc0, 0x3f, 0x14, 0x8c, 0x81, 0x87, 0x03, 0x0d, 0x04, 0x81, 0x03, + 0x8d, 0x01, 0x00, 0x29, 0x0c, 0x00, 0x04, 0x14, 0x0d, 0x80, 0x0c, 0x14, + 0x2e, 0x00, 0x00, 0x14, 0x10, 0xc0, 0x3f, 0x14, 0x10, 0x82, 0x87, 0x03, + 0x11, 0x08, 0x81, 0x03, 0x80, 0x01, 0x00, 0x29, 0x8c, 0xb9, 0x10, 0x00, + 0x8d, 0xf9, 0xff, 0x5f, 0x12, 0x00, 0xc0, 0x02, 0x0c, 0x00, 0x04, 0x14, + 0x8f, 0x01, 0x00, 0x2a, 0xef, 0x05, 0x80, 0x02, 0xef, 0x5d, 0x00, 0x00, + 0x8f, 0x01, 0x00, 0x29, 0x8c, 0xb9, 0x10, 0x00, 0x8d, 0xed, 0xff, 0x5f, + 0x52, 0x06, 0xc0, 0x02, 0x52, 0x7e, 0x40, 0x03, 0x5f, 0xde, 0xff, 0x47, + 0x11, 0x02, 0x00, 0x29, 0xff, 0xd7, 0xff, 0x53 +}; + diff --git a/tests/qtest/migration/ppc64/a-b-kernel.h b/tests/qtest/migration/ppc64/a-b-kernel.h index 8dc2bcd61a..11477c1554 100644 --- a/tests/qtest/migration/ppc64/a-b-kernel.h +++ b/tests/qtest/migration/ppc64/a-b-kernel.h @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + /* * This file is automatically generated from the assembly file in * tests/qtest/migration/ppc64. Edit that file and then run "make" diff --git a/tests/qtest/migration/s390x/a-b-bios.h b/tests/qtest/migration/s390x/a-b-bios.h index f8e385c75c..c1d6e0846c 100644 --- a/tests/qtest/migration/s390x/a-b-bios.h +++ b/tests/qtest/migration/s390x/a-b-bios.h @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + /* * This file is automatically generated from the assembly file in * tests/qtest/migration/s390x. Edit that file and then run "make" @@ -7,10 +9,10 @@ unsigned char s390x_elf[] = { 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x16, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x98, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x07, 0x00, 0x40, - 0x00, 0x10, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x0e, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x88, 0x00, 0x00, 0x00, 0x00, @@ -22,86 +24,87 @@ unsigned char s390x_elf[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x88, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x88, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x60, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xd8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1e, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xd8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x30, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x21, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xe8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1e, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xe8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x21, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0e, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xd8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xd8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, + 0x00, 0x00, 0x0e, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xe8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xe8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x64, 0x74, 0xe5, 0x51, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x64, 0x74, 0xe5, 0x52, 0x00, 0x00, 0x00, 0x04, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xd8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1e, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xd8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x28, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xe8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1e, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xe8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x6c, 0x69, 0x62, 0x2f, 0x6c, 0x64, 0x36, 0x34, 0x2e, 0x73, 0x6f, - 0x2e, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x2e, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0xeb, 0xdf, 0xf0, 0x68, + 0x00, 0x24, 0xc0, 0xd0, 0x00, 0x00, 0x01, 0x05, 0xa7, 0xfb, 0xff, 0x60, + 0xc0, 0x10, 0x00, 0x00, 0x96, 0xd4, 0xa7, 0x28, 0x00, 0x1c, 0x40, 0x20, + 0x10, 0x00, 0xa5, 0x2c, 0x00, 0x04, 0xe3, 0x20, 0x10, 0x0a, 0x00, 0x24, + 0xa7, 0x28, 0x00, 0x40, 0x40, 0x20, 0x10, 0x12, 0x58, 0x20, 0xd0, 0x00, + 0xb2, 0x20, 0x00, 0x21, 0xb2, 0x22, 0x00, 0x30, 0x88, 0x30, 0x00, 0x1c, + 0xc0, 0xe5, 0x00, 0x00, 0x00, 0x63, 0xa7, 0x29, 0x00, 0x41, 0xc0, 0xe5, + 0x00, 0x00, 0x00, 0xbf, 0xa5, 0x2e, 0x00, 0x10, 0xa7, 0x19, 0x63, 0x00, + 0x92, 0x00, 0x20, 0x00, 0xa7, 0x2b, 0x10, 0x00, 0xa7, 0x17, 0xff, 0xfc, + 0xa5, 0x1e, 0x00, 0x10, 0xa7, 0x29, 0x63, 0x00, 0xe3, 0x30, 0x10, 0x00, + 0x00, 0x90, 0xa7, 0x3a, 0x00, 0x01, 0x42, 0x30, 0x10, 0x00, 0xa7, 0x1b, + 0x10, 0x00, 0xa7, 0x27, 0xff, 0xf7, 0xa7, 0x29, 0x00, 0x42, 0xc0, 0xe5, + 0x00, 0x00, 0x00, 0xa1, 0xa7, 0xf4, 0xff, 0xec, 0xc0, 0xf0, 0x00, 0x00, + 0x56, 0x44, 0xc0, 0x20, 0x00, 0x00, 0x0e, 0x91, 0xe3, 0x20, 0x20, 0x00, + 0x00, 0x04, 0xc0, 0x30, 0x00, 0x00, 0x9e, 0x8b, 0xb9, 0x0b, 0x00, 0x32, + 0xb9, 0x02, 0x00, 0x33, 0xa7, 0x84, 0x00, 0x19, 0xa7, 0x3b, 0xff, 0xff, + 0xeb, 0x43, 0x00, 0x08, 0x00, 0x0c, 0xb9, 0x02, 0x00, 0x44, 0xb9, 0x04, + 0x00, 0x12, 0xa7, 0x84, 0x00, 0x09, 0xd7, 0xff, 0x10, 0x00, 0x10, 0x00, + 0x41, 0x10, 0x11, 0x00, 0xa7, 0x47, 0xff, 0xfb, 0xc0, 0x20, 0x00, 0x00, + 0x00, 0x0d, 0x44, 0x30, 0x20, 0x00, 0xc0, 0x20, 0x00, 0x00, 0x00, 0x57, + 0xd2, 0x0f, 0x01, 0xd0, 0x20, 0x00, 0xa7, 0xf4, 0xff, 0x89, 0xd7, 0x00, + 0x10, 0x00, 0x10, 0x00, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x4c, 0xb2, 0xb2, + 0x10, 0x00, 0xa7, 0xf4, 0x00, 0x00, 0xeb, 0x00, 0xf0, 0x00, 0x00, 0x25, + 0x96, 0x02, 0xf0, 0x06, 0xeb, 0x00, 0xf0, 0x00, 0x00, 0x2f, 0xc0, 0x10, + 0x00, 0x00, 0x00, 0x2a, 0xe3, 0x10, 0x01, 0xb8, 0x00, 0x24, 0xc0, 0x10, + 0x00, 0x00, 0x00, 0x47, 0xd2, 0x07, 0x01, 0xb0, 0x10, 0x00, 0xc0, 0x10, + 0x00, 0x00, 0x00, 0x39, 0xb2, 0xb2, 0x10, 0x00, 0xeb, 0x66, 0xf0, 0x00, + 0x00, 0x25, 0x96, 0xff, 0xf0, 0x04, 0xeb, 0x66, 0xf0, 0x00, 0x00, 0x2f, + 0xc0, 0x10, 0x00, 0x00, 0x00, 0x1a, 0xe3, 0x10, 0x01, 0xf8, 0x00, 0x24, + 0xc0, 0x10, 0x00, 0x00, 0x00, 0x32, 0xd2, 0x07, 0x01, 0xf0, 0x10, 0x00, + 0xc0, 0x10, 0x00, 0x00, 0x00, 0x20, 0xb2, 0xb2, 0x10, 0x00, 0xeb, 0x00, + 0xf0, 0x00, 0x00, 0x25, 0x94, 0xfd, 0xf0, 0x06, 0xeb, 0x00, 0xf0, 0x00, + 0x00, 0x2f, 0x07, 0xfe, 0xeb, 0x66, 0xf0, 0x00, 0x00, 0x25, 0x94, 0x00, + 0xf0, 0x04, 0xeb, 0x66, 0xf0, 0x00, 0x00, 0x2f, 0x07, 0xfe, 0x07, 0x07, + 0x00, 0x02, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, + 0x07, 0xfe, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xc0, 0x50, 0x00, 0x00, + 0x00, 0x20, 0xc0, 0x10, 0x00, 0x00, 0x95, 0xf5, 0x42, 0x20, 0x10, 0x0e, + 0xa7, 0x28, 0x00, 0x0f, 0x40, 0x20, 0x10, 0x00, 0x58, 0x20, 0x50, 0x04, + 0x50, 0x20, 0x10, 0x08, 0x92, 0x00, 0x10, 0x02, 0x58, 0x20, 0x50, 0x00, + 0xb2, 0x20, 0x00, 0x21, 0xb2, 0x22, 0x00, 0x30, 0x88, 0x30, 0x00, 0x1c, + 0xc0, 0xf4, 0xff, 0xff, 0xff, 0x85, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x00, 0x76, 0x00, 0x05, 0x00, 0x07, 0x1a, 0x00, 0x00, 0x78, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x07, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, - 0xeb, 0xdf, 0xf0, 0x68, 0x00, 0x24, 0xc0, 0xd0, 0x00, 0x00, 0x01, 0x05, - 0xa7, 0xfb, 0xff, 0x60, 0xc0, 0x10, 0x00, 0x00, 0x96, 0xc0, 0xa7, 0x28, - 0x00, 0x1c, 0x40, 0x20, 0x10, 0x00, 0xa5, 0x2c, 0x00, 0x04, 0xe3, 0x20, - 0x10, 0x0a, 0x00, 0x24, 0xa7, 0x28, 0x00, 0x40, 0x40, 0x20, 0x10, 0x12, - 0x58, 0x20, 0xd0, 0x00, 0xb2, 0x20, 0x00, 0x21, 0xb2, 0x22, 0x00, 0x30, - 0x88, 0x30, 0x00, 0x1c, 0xc0, 0xe5, 0x00, 0x00, 0x00, 0x63, 0xa7, 0x29, - 0x00, 0x41, 0xc0, 0xe5, 0x00, 0x00, 0x00, 0xbf, 0xa5, 0x2e, 0x00, 0x10, - 0xa7, 0x19, 0x63, 0x00, 0x92, 0x00, 0x20, 0x00, 0xa7, 0x2b, 0x10, 0x00, - 0xa7, 0x17, 0xff, 0xfc, 0xa5, 0x1e, 0x00, 0x10, 0xa7, 0x29, 0x63, 0x00, - 0xe3, 0x30, 0x10, 0x00, 0x00, 0x90, 0xa7, 0x3a, 0x00, 0x01, 0x42, 0x30, - 0x10, 0x00, 0xa7, 0x1b, 0x10, 0x00, 0xa7, 0x27, 0xff, 0xf7, 0xa7, 0x29, - 0x00, 0x42, 0xc0, 0xe5, 0x00, 0x00, 0x00, 0xa1, 0xa7, 0xf4, 0xff, 0xec, - 0xc0, 0xf0, 0x00, 0x00, 0x56, 0x30, 0xc0, 0x20, 0x00, 0x00, 0x0e, 0x7d, - 0xe3, 0x20, 0x20, 0x00, 0x00, 0x04, 0xc0, 0x30, 0x00, 0x00, 0x9e, 0x77, - 0xb9, 0x0b, 0x00, 0x32, 0xb9, 0x02, 0x00, 0x33, 0xa7, 0x84, 0x00, 0x19, - 0xa7, 0x3b, 0xff, 0xff, 0xeb, 0x43, 0x00, 0x08, 0x00, 0x0c, 0xb9, 0x02, - 0x00, 0x44, 0xb9, 0x04, 0x00, 0x12, 0xa7, 0x84, 0x00, 0x09, 0xd7, 0xff, - 0x10, 0x00, 0x10, 0x00, 0x41, 0x10, 0x11, 0x00, 0xa7, 0x47, 0xff, 0xfb, - 0xc0, 0x20, 0x00, 0x00, 0x00, 0x0d, 0x44, 0x30, 0x20, 0x00, 0xc0, 0x20, - 0x00, 0x00, 0x00, 0x57, 0xd2, 0x0f, 0x01, 0xd0, 0x20, 0x00, 0xa7, 0xf4, - 0xff, 0x89, 0xd7, 0x00, 0x10, 0x00, 0x10, 0x00, 0xc0, 0x10, 0x00, 0x00, - 0x00, 0x4c, 0xb2, 0xb2, 0x10, 0x00, 0xa7, 0xf4, 0x00, 0x00, 0xeb, 0x00, - 0xf0, 0x00, 0x00, 0x25, 0x96, 0x02, 0xf0, 0x06, 0xeb, 0x00, 0xf0, 0x00, - 0x00, 0x2f, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x2a, 0xe3, 0x10, 0x01, 0xb8, - 0x00, 0x24, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x47, 0xd2, 0x07, 0x01, 0xb0, - 0x10, 0x00, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x39, 0xb2, 0xb2, 0x10, 0x00, - 0xeb, 0x66, 0xf0, 0x00, 0x00, 0x25, 0x96, 0xff, 0xf0, 0x04, 0xeb, 0x66, - 0xf0, 0x00, 0x00, 0x2f, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x1a, 0xe3, 0x10, - 0x01, 0xf8, 0x00, 0x24, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x32, 0xd2, 0x07, - 0x01, 0xf0, 0x10, 0x00, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x20, 0xb2, 0xb2, - 0x10, 0x00, 0xeb, 0x00, 0xf0, 0x00, 0x00, 0x25, 0x94, 0xfd, 0xf0, 0x06, - 0xeb, 0x00, 0xf0, 0x00, 0x00, 0x2f, 0x07, 0xfe, 0xeb, 0x66, 0xf0, 0x00, - 0x00, 0x25, 0x94, 0x00, 0xf0, 0x04, 0xeb, 0x66, 0xf0, 0x00, 0x00, 0x2f, - 0x07, 0xfe, 0x07, 0x07, 0x00, 0x02, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00, 0x01, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x80, 0x00, 0x00, 0x00, 0x07, 0xfe, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, - 0xc0, 0x50, 0x00, 0x00, 0x00, 0x20, 0xc0, 0x10, 0x00, 0x00, 0x95, 0xe1, - 0x42, 0x20, 0x10, 0x0e, 0xa7, 0x28, 0x00, 0x0f, 0x40, 0x20, 0x10, 0x00, - 0x58, 0x20, 0x50, 0x04, 0x50, 0x20, 0x10, 0x08, 0x92, 0x00, 0x10, 0x02, - 0x58, 0x20, 0x50, 0x00, 0xb2, 0x20, 0x00, 0x21, 0xb2, 0x22, 0x00, 0x30, - 0x88, 0x30, 0x00, 0x1c, 0xc0, 0xf4, 0xff, 0xff, 0xff, 0x85, 0x07, 0x07, - 0x07, 0x07, 0x07, 0x07, 0x00, 0x76, 0x00, 0x05, 0x00, 0x07, 0x1a, 0x00, - 0x00, 0x78, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -322,17 +325,16 @@ unsigned char s390x_elf[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xff, 0xfe, 0xf5, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, + 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x28, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x02, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xff, 0xff, 0xfb, 0x00, 0x00, 0x00, 0x00, @@ -344,105 +346,95 @@ unsigned char s390x_elf[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xd8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, - 0x47, 0x43, 0x43, 0x3a, 0x20, 0x28, 0x53, 0x55, 0x53, 0x45, 0x20, 0x4c, - 0x69, 0x6e, 0x75, 0x78, 0x29, 0x20, 0x31, 0x32, 0x2e, 0x33, 0x2e, 0x30, - 0x00, 0x00, 0x2e, 0x73, 0x68, 0x73, 0x74, 0x72, 0x74, 0x61, 0x62, 0x00, - 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x00, 0x2e, 0x67, 0x6e, 0x75, - 0x2e, 0x68, 0x61, 0x73, 0x68, 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x73, 0x79, - 0x6d, 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x73, 0x74, 0x72, 0x00, 0x2e, 0x72, - 0x65, 0x6c, 0x61, 0x2e, 0x64, 0x79, 0x6e, 0x00, 0x2e, 0x74, 0x65, 0x78, - 0x74, 0x00, 0x2e, 0x72, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x00, 0x2e, 0x65, - 0x68, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x00, 0x2e, 0x64, 0x79, 0x6e, - 0x61, 0x6d, 0x69, 0x63, 0x00, 0x2e, 0x67, 0x6f, 0x74, 0x00, 0x2e, 0x64, - 0x61, 0x74, 0x61, 0x00, 0x2e, 0x62, 0x73, 0x73, 0x00, 0x2e, 0x63, 0x6f, - 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x47, 0x43, 0x43, 0x3a, 0x20, 0x28, 0x47, 0x4e, 0x55, 0x29, 0x20, 0x31, + 0x35, 0x2e, 0x32, 0x2e, 0x31, 0x20, 0x32, 0x30, 0x32, 0x35, 0x30, 0x38, + 0x30, 0x38, 0x20, 0x28, 0x52, 0x65, 0x64, 0x20, 0x48, 0x61, 0x74, 0x20, + 0x43, 0x72, 0x6f, 0x73, 0x73, 0x20, 0x31, 0x35, 0x2e, 0x32, 0x2e, 0x31, + 0x2d, 0x31, 0x29, 0x00, 0x00, 0x2e, 0x73, 0x68, 0x73, 0x74, 0x72, 0x74, + 0x61, 0x62, 0x00, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x00, 0x2e, + 0x67, 0x6e, 0x75, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x00, 0x2e, 0x64, 0x79, + 0x6e, 0x73, 0x79, 0x6d, 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x73, 0x74, 0x72, + 0x00, 0x2e, 0x72, 0x65, 0x6c, 0x61, 0x2e, 0x64, 0x79, 0x6e, 0x00, 0x2e, + 0x74, 0x65, 0x78, 0x74, 0x00, 0x2e, 0x72, 0x6f, 0x64, 0x61, 0x74, 0x61, + 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x00, 0x2e, 0x67, + 0x6f, 0x74, 0x00, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x00, 0x2e, 0x62, 0x73, + 0x73, 0x00, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, - 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, - 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x6f, 0xff, 0xff, 0xf6, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xd8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x28, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x58, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x58, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x70, + 0x00, 0x00, 0x02, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x78, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x04, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x50, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x88, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x88, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xe8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xe8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x06, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1e, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xd8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x1f, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xe8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x58, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xe8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0f, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, - 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x08, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, + 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x21, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c index 1c69d41e33..cfd3c02da4 100644 --- a/tests/qtest/virtio-9p-test.c +++ b/tests/qtest/virtio-9p-test.c @@ -31,6 +31,18 @@ #define tsymlink(...) v9fs_tsymlink((TsymlinkOpt) __VA_ARGS__) #define tlink(...) v9fs_tlink((TlinkOpt) __VA_ARGS__) #define tunlinkat(...) v9fs_tunlinkat((TunlinkatOpt) __VA_ARGS__) +#define tread(...) v9fs_tread((TReadOpt) __VA_ARGS__) +#define tclunk(...) v9fs_tclunk((TClunkOpt) __VA_ARGS__) +#define txattrcreate(...) v9fs_txattrcreate((TXattrCreateOpt) __VA_ARGS__) + +/* + * xattr size to be used for xattr tests + * + * 64k is the max. xattr size supported by the Linux kernel, However btrfs + * for instance supports only 16219 bytes. So let's be conservative and + * just use 8k for the xattr tests. + */ +#define TEST_XATTR_SIZE (8 * 1024) static void pci_config(void *obj, void *data, QGuestAllocator *t_alloc) { @@ -104,6 +116,42 @@ static bool fs_dirents_contain_name(struct V9fsDirent *e, const char* name) return false; } +/* + * Returns the current internal xattr FID count (works with synth driver only). + */ +static size_t get_xattr_count(QVirtio9P *v9p) +{ + uint16_t nwqid; + v9fs_qid *wqid; + const char *xattr_count_path[] = { "stat", "xattr_count" }; + size_t xattr_count; + uint32_t bytes_read; + + /* walk to /stat/xattr_count file */ + uint32_t fid = twalk({ + .client = v9p, .fid = 0, + .nwname = 2, .wnames = (char **)xattr_count_path, + .rwalk = { .nwqid = &nwqid, .wqid = &wqid } + }).newfid; + + /* open for read */ + tlopen({ + .client = v9p, .fid = fid, .flags = O_RDONLY, + .rlopen = { .qid = NULL, .iounit = NULL } + }); + + /* read the internal xattr FID count */ + tread({ + .client = v9p, .fid = fid, .offset = 0, .count = sizeof(xattr_count), + .rread = { .count = &bytes_read, .data = &xattr_count } + }); + + /* cleanup */ + tclunk({ .client = v9p, .fid = fid }); + + return xattr_count; +} + /* basic readdir test where reply fits into a single response message */ static void fs_readdir(void *obj, void *data, QGuestAllocator *t_alloc) { @@ -245,6 +293,121 @@ static void do_readdir_split(QVirtio9P *v9p, uint32_t count) g_free(wnames[0]); } +/* + * Test 9p server's xattr FID count limit enforcement. + * + * Shared test code for both 'synth' and 'local' driver to verify correct + * behaviour of 9p server enforcing preconfigured xattr FID count limit + * correctly. + * + * @v9p: 9pfs client + * + * @max_xattr: max. allowed xattr FIDs, or -1 for infinite + * + * @check_counter: whether to verify 9p server internal xattr FID counter + * (only works with 'synth' fs driver) + */ +static void do_xattr_limit(QVirtio9P *v9p, int max_xattr, bool check_counter) +{ + size_t count; + int i; + int limit = (max_xattr != -1) ? max_xattr : V9FS_MAX_XATTR_DEFAULT + 100; + g_autofree uint32_t *fids = g_new0(uint32_t, limit); + uint32_t err_fid = 0; + const char *file_path[] = { QTEST_V9FS_SYNTH_WRITE_FILE }; + g_autofree uint8_t *xattr_data = g_malloc(TEST_XATTR_SIZE); + + if (!g_test_slow()) { + g_test_skip("This is a slow test, run with -m slow"); + return; + } + + /* prepare xattr data with 'X' characters */ + memset(xattr_data, 'X', TEST_XATTR_SIZE); + + tattach({ .client = v9p }); + + /* create max. amount of permitted xattrs */ + for (i = 0; i < limit; i++) { + /* walk to create a new fid */ + fids[i] = twalk({ + .client = v9p, .fid = 0, + .nwname = 1, .wnames = (char **) file_path + }).newfid; + + /* create new xattr fid */ + txattrcreate({ + .client = v9p, .fid = fids[i], .name = "user.test", + .size = TEST_XATTR_SIZE, .flags = 0 + }); + + /* transfer the xattr data */ + twrite({ + .client = v9p, .fid = fids[i], .offset = 0, + .count = TEST_XATTR_SIZE, .data = xattr_data + }); + + /* verify server internal xattr counter */ + if (check_counter) { + count = get_xattr_count(v9p); + g_assert_cmpuint(count, ==, (i + 1)); + } + + /* avoid virtio descriptor exhaustion */ + qvirtqueue_reset_pool(v9p->vq); + } + + /* if xattrs are limited, the next xattr should fail */ + if (max_xattr != -1) { + /* walk to create another fid */ + err_fid = twalk({ + .client = v9p, .fid = 0, + .nwname = 1, .wnames = (char **) file_path + }).newfid; + + /* try to create one more xattr fid - should fail */ + txattrcreate({ + .client = v9p, .fid = err_fid, .name = "user.test_exceed", + .size = TEST_XATTR_SIZE, .flags = 0, + .expectErr = ENOSPC + }); + + /* verify internal xattr counter hasn't changed */ + if (check_counter) { + count = get_xattr_count(v9p); + g_assert_cmpuint(count, ==, limit); + } + } + + /* clunk all fids (should decrement xattr counter) */ + for (i = 0; i < limit; i++) { + tclunk({ .client = v9p, .fid = fids[i] }); + qvirtqueue_reset_pool(v9p->vq); + } + if (err_fid) { + tclunk({ .client = v9p, .fid = err_fid }); + } + + /* verify internal xattr counter is zero */ + if (check_counter) { + count = get_xattr_count(v9p); + g_assert_cmpuint(count, ==, 0); + } +} + +static void do_local_xattr_limit(QVirtio9P *v9p, int max_xattr) +{ + g_autofree char *test_file = virtio_9p_test_path("WRITE"); + + /* + * this file must be created for the test to work with the 'local' fs driver + */ + g_file_set_contents(test_file, "", 0, NULL); + + /* the actual test code shared with the 'synth' fs driver tests */ + do_xattr_limit(v9p, max_xattr, false); +} + static void fs_walk_no_slash(void *obj, void *data, QGuestAllocator *t_alloc) { QVirtio9P *v9p = obj; @@ -505,6 +668,27 @@ static void fs_readdir_split_512(void *obj, void *data, do_readdir_split(obj, 512); } +static void fs_synth_xattr_limit_default(void *obj, void *data, + QGuestAllocator *t_alloc) +{ + v9fs_set_allocator(t_alloc); + do_xattr_limit(obj, V9FS_MAX_XATTR_DEFAULT, true); +} + +static void fs_synth_xattr_limit_custom(void *obj, void *data, + QGuestAllocator *t_alloc) +{ + v9fs_set_allocator(t_alloc); + do_xattr_limit(obj, 100, true); +} + +static void fs_synth_xattr_limit_unlimited(void *obj, void *data, + QGuestAllocator *t_alloc) +{ + v9fs_set_allocator(t_alloc); + do_xattr_limit(obj, -1, true); +} + /* tests using the 9pfs 'local' fs driver */ @@ -819,26 +1003,81 @@ static void fs_deep_absolute_path(void *obj, void *data, g_string_free(path, TRUE); } +static void fs_local_xattr_limit_default(void *obj, void *data, + QGuestAllocator *t_alloc) +{ + v9fs_set_allocator(t_alloc); + do_local_xattr_limit(obj, V9FS_MAX_XATTR_DEFAULT); +} + +static void fs_local_xattr_limit_custom(void *obj, void *data, + QGuestAllocator *t_alloc) +{ + v9fs_set_allocator(t_alloc); + do_local_xattr_limit(obj, 100); +} + +static void fs_local_xattr_limit_unlimited(void *obj, void *data, + QGuestAllocator *t_alloc) +{ + v9fs_set_allocator(t_alloc); + do_local_xattr_limit(obj, -1); +} + +static void *synth_max_xattr_custom_opt(GString *cmd_line, void *arg) +{ + virtio_9p_add_synth_driver_args(cmd_line, "max_xattr=100"); + return arg; +} + +static void *synth_max_xattr_unlimited_opt(GString *cmd_line, void *arg) +{ + virtio_9p_add_synth_driver_args(cmd_line, "max_xattr=0"); + return arg; +} + static void cleanup_9p_local_driver(void *data) { /* remove previously created test dir when test is completed */ virtio_9p_remove_local_test_dir(); } -static void *assign_9p_local_driver(GString *cmd_line, void *arg) +static void assign_9p_local_driver_with_args(GString *cmd_line, + const char *extra_opts) { /* make sure test dir for the 'local' tests exists */ virtio_9p_create_local_test_dir(); - virtio_9p_assign_local_driver(cmd_line, "security_model=mapped-xattr"); + g_autofree char *opts = + (extra_opts) ? + g_strdup_printf("security_model=mapped-xattr,%s", extra_opts) : + g_strdup("security_model=mapped-xattr"); + + virtio_9p_assign_local_driver(cmd_line, opts); g_test_queue_destroy(cleanup_9p_local_driver, NULL); +} + +static void *assign_9p_local_driver(GString *cmd_line, void *arg) +{ + assign_9p_local_driver_with_args(cmd_line, NULL); return arg; } -static void register_virtio_9p_test(void) +static void *local_max_xattr_custom_opt(GString *cmd_line, void *arg) +{ + assign_9p_local_driver_with_args(cmd_line, "max_xattr=100"); + return arg; +} + +static void *local_max_xattr_unlimited_opt(GString *cmd_line, void *arg) { + assign_9p_local_driver_with_args(cmd_line, "max_xattr=0"); + return arg; +} +static void register_virtio_9p_test(void) +{ QOSGraphTestOptions opts = { }; @@ -869,7 +1108,14 @@ static void register_virtio_9p_test(void) fs_readdir_split_256, &opts); qos_add_test("synth/readdir/split_128", "virtio-9p", fs_readdir_split_128, &opts); - + qos_add_test("synth/xattr_limit/default", "virtio-9p", + fs_synth_xattr_limit_default, &opts); + opts.before = synth_max_xattr_custom_opt; + qos_add_test("synth/xattr_limit/custom", "virtio-9p", + fs_synth_xattr_limit_custom, &opts); + opts.before = synth_max_xattr_unlimited_opt; + qos_add_test("synth/xattr_limit/unlimited", "virtio-9p", + fs_synth_xattr_limit_unlimited, &opts); /* 9pfs test cases using the 'local' filesystem driver */ opts.before = assign_9p_local_driver; @@ -888,6 +1134,14 @@ static void register_virtio_9p_test(void) &opts); qos_add_test("local/deep_absolute_path", "virtio-9p", fs_deep_absolute_path, &opts); + qos_add_test("local/xattr_limit/default", "virtio-9p", + fs_local_xattr_limit_default, &opts); + opts.before = local_max_xattr_custom_opt; + qos_add_test("local/xattr_limit/custom", "virtio-9p", + fs_local_xattr_limit_custom, &opts); + opts.before = local_max_xattr_unlimited_opt; + qos_add_test("local/xattr_limit/unlimited", "virtio-9p", + fs_local_xattr_limit_unlimited, &opts); } libqos_init(register_virtio_9p_test); diff --git a/tests/tcg/aarch64/Makefile.softmmu-target b/tests/tcg/aarch64/Makefile.softmmu-target index f7a7d2b800..3f30be6c5d 100644 --- a/tests/tcg/aarch64/Makefile.softmmu-target +++ b/tests/tcg/aarch64/Makefile.softmmu-target @@ -25,7 +25,7 @@ LDFLAGS=-Wl,-T$(LINK_SCRIPT) TESTS+=$(AARCH64_TESTS) $(MULTIARCH_TESTS) EXTRA_RUNS+=$(MULTIARCH_RUNS) CFLAGS+=-nostdlib -ggdb -O0 $(MINILIB_INC) -LDFLAGS+=-static -nostdlib $(CRT_OBJS) $(MINILIB_OBJS) -lgcc +LDFLAGS+=-static -nostdlib $(MINILIB_OBJS) -lgcc config-cc.mak: Makefile $(quiet-@)( \ @@ -36,21 +36,27 @@ config-cc.mak: Makefile # building head blobs .PRECIOUS: $(CRT_OBJS) +vector_log_boot.o: $(CRT_PATH)/boot.S + $(CC) $(CFLAGS) $(EXTRA_CFLAGS) -DLOGGING_VECTOR_TABLE -x assembler-with-cpp -Wa,--noexecstack -c $< -o $@ + %.o: $(CRT_PATH)/%.S $(CC) $(CFLAGS) $(EXTRA_CFLAGS) -x assembler-with-cpp -Wa,--noexecstack -c $< -o $@ # Build and link the tests %: %.c $(LINK_SCRIPT) $(CRT_OBJS) $(MINILIB_OBJS) - $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS) + $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS) boot.o memory: CFLAGS+=-DCHECK_UNALIGNED=1 memory-sve: memory.c $(LINK_SCRIPT) $(CRT_OBJS) $(MINILIB_OBJS) - $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS) + $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS) boot.o memory-sve: CFLAGS+=-DCHECK_UNALIGNED=1 -march=armv8.1-a+sve -O3 -TESTS+=memory-sve +gpc-test: gpc-test.c $(LINK_SCRIPT) vector_log_boot.o $(MINILIB_OBJS) + $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS) vector_log_boot.o + +TESTS+=memory-sve gpc-test # Running QEMU_BASE_MACHINE=-M virt -cpu max -display none @@ -71,6 +77,12 @@ QEMU_EL2_MACHINE=-machine virt,virtualization=on,gic-version=2 -cpu cortex-a57 - QEMU_EL2_BASE_ARGS=-semihosting-config enable=on,target=native,chardev=output,arg="2" run-vtimer: QEMU_OPTS=$(QEMU_EL2_MACHINE) $(QEMU_EL2_BASE_ARGS) -kernel +# gpc tests need EL3 and RME +QEMU_EL3_MACHINE=-machine virt,virtualization=on,secure=on,gic-version=3 -cpu max,x-rme=on +QEMU_EL3_BASE_ARGS=-semihosting-config enable=on,target=native,chardev=output,arg="3" +run-gpc-test: QEMU_OPTS=$(QEMU_EL3_MACHINE) $(QEMU_EL3_BASE_ARGS) -kernel +run-gpc3-test: QEMU_OPTS=$(QEMU_EL3_MACHINE) $(QEMU_EL3_BASE_ARGS) -kernel + # Simple Record/Replay Test .PHONY: memory-record run-memory-record: memory-record memory @@ -107,7 +119,7 @@ QEMU_MTE_ENABLED_MACHINE=-M virt,mte=on -cpu max -display none QEMU_OPTS_WITH_MTE_ON = $(QEMU_MTE_ENABLED_MACHINE) $(QEMU_BASE_ARGS) -kernel mte: CFLAGS+=-march=armv8.5-a+memtag mte: mte.S $(LINK_SCRIPT) $(CRT_OBJS) $(MINILIB_OBJS) - $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS) + $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS) boot.o run-mte: QEMU_OPTS=$(QEMU_OPTS_WITH_MTE_ON) run-mte: mte diff --git a/tests/tcg/aarch64/system/boot.S b/tests/tcg/aarch64/system/boot.S index 8bfa4e4efc..c6e5ad889b 100644 --- a/tests/tcg/aarch64/system/boot.S +++ b/tests/tcg/aarch64/system/boot.S @@ -60,6 +60,40 @@ curr_sp0_irq: curr_sp0_fiq: curr_sp0_serror: curr_spx_sync: +#ifdef LOGGING_VECTOR_TABLE + sub sp, sp, #16 + stp x0, x1, [sp, #0] + mrs x0, ESR_EL3 + lsr x0, x0, #26 + and x0, x0, #0x3f + cmp x0, #37 + beq data_fault + cmp x0, #30 + beq gpc_fault + b generic_exception + +data_fault: + mrs x0, FAR_EL3 + adrp x1, exception_log + str x0, [x1] + ldr x0, =0x1001 + str x0, [x1, #8] + b skip_return +gpc_fault: + mrs x0, FAR_EL3 + adrp x1, exception_log + str x0, [x1] + ldr x0, =0x1002 + str x0, [x1, #8] + /* Fall through */ +skip_return: + mrs x0, ELR_EL3 + add x0, x0, #4 /* Skip faulting instruction */ + msr ELR_EL3, x0 + ldp x0, x1, [sp, #0] + add sp, sp, #16 + eret +#endif curr_spx_irq: curr_spx_fiq: curr_spx_serror: @@ -71,6 +105,7 @@ lower_a32_sync: lower_a32_irq: lower_a32_fiq: lower_a32_serror: +generic_exception: adr x1, .unexp_excp exit_msg: mov x0, SYS_WRITE0 @@ -404,6 +439,36 @@ ttb_stage2: .space 4096, 0 .align 12 + .global realms_gpt0 + /* GPT stage 0 table */ +realms_gpt0: + .space 4096, 0 + .align 17 + .global realms_gpt1 + /* GPT stage 1 table, initialised to all 0xFF (full access) */ +realms_gpt1: + .space 524288, 0xFF +#ifdef LOGGING_VECTOR_TABLE + .align 12 + .global exception_fault_address + .type exception_fault_address, @object + .size exception_fault_address, 8 + .global exception_type_code + .type exception_type_code, @object + .size exception_type_code, 8 + /* + * These fields record details of the last exception, if + * LOGGING_VECTOR_TABLE is defined. + */ +exception_log: +exception_fault_address: + /* The contents of FAR_EL3 when an exception is taken. */ + .space 8, 0 +exception_type_code: + /* A generic code indicating what type of exception occurred. */ + .space 8, 0 +#endif + .align 12 system_stack: .space 4096, 0 system_stack_end: diff --git a/tests/tcg/aarch64/system/boot.h b/tests/tcg/aarch64/system/boot.h new file mode 100644 index 0000000000..cb9ab6c1b9 --- /dev/null +++ b/tests/tcg/aarch64/system/boot.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * + * + * Copyright (c) 2026 Linaro Ltd + * + */ + + +/* Global variables exported in boot.S */ +extern volatile uint64_t exception_fault_address; /* Updated by ISR */ +extern volatile uint64_t exception_type_code; /* Updated by ISR */ +extern uint64_t realms_gpt0[]; +extern uint64_t realms_gpt1[]; diff --git a/tests/tcg/aarch64/system/gpc-test.c b/tests/tcg/aarch64/system/gpc-test.c new file mode 100644 index 0000000000..f26b23efaf --- /dev/null +++ b/tests/tcg/aarch64/system/gpc-test.c @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * + * + * Copyright (c) 2026 Linaro Ltd + * + */ + +#include <stdbool.h> +#include <stdint.h> +#include <minilib.h> +#include "boot.h" + +#define ID_AA64PFR0_EL1 "S3_0_C0_C4_0" + +#define GPTBR_EL3 "S3_6_C2_C1_4" +#define GPCBW_EL3 "S3_6_C2_C1_5" +#define GPCCR_EL3 "S3_6_C2_C1_6" +#define VBAR_EL3 "S3_6_C12_C0_0" + +#define get_sys_reg(register_name, dest) \ + asm("mrs %[reg], " register_name "\n\t" : [reg] "=r" (dest)) +#define set_sys_reg(register_name, value) \ + asm("msr " register_name ", %[reg]\n\r" : : [reg] "r" (value)) + +const uint32_t gpc_granule_size = 4096; +const uint32_t gpis_per_64_bits = 16; + +int main(uint64_t sp) +{ + uint64_t out; + uint64_t pfr0; + uint64_t gpt_base; + uint64_t rme_status; + uint64_t currentel_raw; + uint64_t currentel; + uint64_t gpcbw; + uint64_t gpt_table0_addr = (uint64_t) realms_gpt0; + uint64_t gpt_table1_addr = (uint64_t) realms_gpt1; + + /* Mask is FNG1, FNG0, and A2 */ + const uint64_t feature_mask = (1ULL << 18 | 1ULL << 17 | 1ULL << 16); + const uint64_t in = feature_mask; + + get_sys_reg("CurrentEL", currentel_raw); + currentel = (currentel_raw >> 2) & 0x3; + + if (currentel < 3) { + ml_printf("FAIL: Test must be run at EL3 (it is %d)\n", currentel); + return 1; + } + + get_sys_reg(ID_AA64PFR0_EL1, pfr0); + + /* rme_status is 1 for RME, 2 for RME + GPC2, 3 for RME+GPC3 */ + rme_status = (pfr0 >> 52) & 0xF; + if (rme_status < 2) { + ml_printf("SKIP: System does not support RME (RME=%ld)\n", rme_status); + return 0; + } + + /* Configure the level 0 table for the first 4GB of memory */ + realms_gpt0[0] = gpt_table1_addr | 0x3; /* Covers GB 0; table descriptor */ + realms_gpt0[1] = 0xf1; /* Covers GB 1; full access */ + realms_gpt0[2] = 0xf1; /* Covers GB 2; full access */ + realms_gpt0[3] = 0xf1; /* Covers GB 3; full access */ + + /* Pick an artibtrary location to read inside the first 1GB. */ + uint64_t fault_location = 0x10202008; + uint32_t gpi_index = fault_location / gpc_granule_size; + realms_gpt1[gpi_index / gpis_per_64_bits] = 0; + + gpt_base = gpt_table0_addr >> 12; + set_sys_reg(GPTBR_EL3, gpt_base); + + /* + * Default values: + * PPS=0: GPC table 0 protects 4GB. + * RLPAD=0: Realm physical address spaces are normal + * NSPAD=0: Non-secure physical address spaces are normal + * SPAD=0: Secure physical address spaces are normal + * IRGN=0: Inner non-cacheable + * ORGN=0: Outer non-cacheable + * PGS=0: Physical granule size is 4KB. + * GPCP=0: All GPC faults reported + * TBGPCP=0: Trace buffer rejects trace + * L0GPTSZ=0: Each entry in table 0 protects 1GB. + * APPSAA=0: Accesses above 4GB must be to Non-secure PAs + * GPCBW=0: Bypass windows disabled. + * NA6, NA7, NSP, SA, NSO are all reserved values for GPI. + */ + uint64_t gpccr = 0; + + /* Switch on granule protection check */ + gpccr |= 1 << 16; /* GPC enabled. */ + gpccr |= 0b10 << 12; /* SH = Outer shareable */ + set_sys_reg(GPCCR_EL3, gpccr); + + /* Access some memory outside the GPC forbidden region */ + uint64_t x = *(unsigned int *) (fault_location + 4096 * 16); + ml_printf("Fault address: %lx\n", exception_fault_address); + if (exception_fault_address != 0) { + ml_printf("FAIL: Memory access was blocked by GPC, " + "and should not have been\n"); + return 1; + } + + /* Access the GPC forbidden region */ + x = *(unsigned int *) fault_location; + + ml_printf("Fault address: %lx\n", exception_fault_address); + if (exception_fault_address != fault_location) { + ml_printf("FAIL: Memory access was not blocked by GPC, " + "and should have been\n"); + return 1; + } + + rme_status = (pfr0 >> 52) & 0xF; + if (rme_status < 3) { + ml_printf("SKIP: System does not support GPC3 (RME=%ld)\n", rme_status); + return 0; + } + + /* Clear the exception record */ + exception_fault_address = 0; + + /* Enable bypass windows */ + gpccr |= 1 << 29; /* GPC Bypass windows enabled */ + set_sys_reg(GPCCR_EL3, gpccr); + + gpcbw = 0; /* Base 0GB, Size 1GB, Stride 1TB */ + set_sys_reg(GPCBW_EL3, gpcbw); + ml_printf("GPCBW configured\n"); + + /* Access the GPC forbidden region again */ + x = *(unsigned int *) fault_location; + + ml_printf("Fault address: %lx\n", exception_fault_address); + if (exception_fault_address != 0) { + ml_printf("FAIL: Memory access was blocked by GPC, " + "and should have been allowed by bypass window. code=%lx\n", + exception_type_code); + return 1; + } + + /* Clear the exception record */ + exception_fault_address = 0; + /* Reconfigure GPCBW to 1GB start */ + gpcbw = 1; /* Base 1GB, Size 1GB, Stride 1TB */ + set_sys_reg(GPCBW_EL3, gpcbw); + ml_printf("GPCBW reconfigured for 1GB start\n"); + + /* Access the GPC forbidden region again */ + x = *(unsigned int *) fault_location; + + ml_printf("Fault address: %lx\n", exception_fault_address); + if (exception_fault_address != fault_location) { + ml_printf("FAIL: Memory access was allowed by GPC, " + "and should not have been allowed by bypass window. code=%lx\n", + exception_type_code); + return 1; + } + + return 0; +} diff --git a/tests/unit/meson.build b/tests/unit/meson.build index 01cc540a45..5ba6b1a230 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -138,7 +138,13 @@ if have_system 'test-bufferiszero': [], 'test-smp-parse': [qom, meson.project_source_root() / 'hw/core/machine-smp.c'], 'test-vmstate': [migration, io], - 'test-yank': ['socket-helpers.c', qom, io, chardev] + 'test-yank': ['socket-helpers.c', qom, io, chardev], + 'test-ram-discard-manager': [ + 'test-ram-discard-manager.c', + 'test-ram-discard-manager-stubs.c', + meson.project_source_root() / 'system/ram-discard-manager.c', + genh, qemuutil, qom + ], } if config_host_data.get('CONFIG_INOTIFY1') tests += {'test-util-filemonitor': []} diff --git a/tests/unit/test-ram-discard-manager-stubs.c b/tests/unit/test-ram-discard-manager-stubs.c new file mode 100644 index 0000000000..74b126fd3f --- /dev/null +++ b/tests/unit/test-ram-discard-manager-stubs.c @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "qemu/osdep.h" +#include "qom/object.h" +#include "glib.h" +#include "system/memory.h" + +RamDiscardManager *memory_region_get_ram_discard_manager(MemoryRegion *mr) +{ + return mr->rdm; +} + +int memory_region_add_ram_discard_source(MemoryRegion *mr, + RamDiscardSource *source) +{ + if (!mr->rdm) { + mr->rdm = ram_discard_manager_new(mr); + } + return ram_discard_manager_add_source(mr->rdm, source); +} + +int memory_region_del_ram_discard_source(MemoryRegion *mr, + RamDiscardSource *source) +{ + RamDiscardManager *rdm = mr->rdm; + + if (!rdm) { + return 0; + } + + return ram_discard_manager_del_source(rdm, source); +} + +uint64_t memory_region_size(const MemoryRegion *mr) +{ + return int128_get64(mr->size); +} + +MemoryRegionSection *memory_region_section_new_copy(MemoryRegionSection *s) +{ + MemoryRegionSection *copy = g_new(MemoryRegionSection, 1); + *copy = *s; + return copy; +} + +void memory_region_section_free_copy(MemoryRegionSection *s) +{ + g_free(s); +} diff --git a/tests/unit/test-ram-discard-manager.c b/tests/unit/test-ram-discard-manager.c new file mode 100644 index 0000000000..3d39a1e94b --- /dev/null +++ b/tests/unit/test-ram-discard-manager.c @@ -0,0 +1,1235 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "qemu/osdep.h" +#include "qemu/bitmap.h" +#include "qemu/module.h" +#include "qemu/main-loop.h" +#include "qapi/error.h" +#include "qom/object.h" +#include "qom/qom-qobject.h" +#include "glib.h" +#include "system/memory.h" + +#define TYPE_TEST_RAM_DISCARD_SOURCE "test-ram-discard-source" + +OBJECT_DECLARE_SIMPLE_TYPE(TestRamDiscardSource, TEST_RAM_DISCARD_SOURCE) + +struct TestRamDiscardSource { + Object parent; + + MemoryRegion *mr; + uint64_t granularity; + unsigned long *bitmap; + uint64_t bitmap_size; +}; + +static uint64_t test_rds_get_min_granularity(const RamDiscardSource *rds, + const MemoryRegion *mr) +{ + TestRamDiscardSource *src = TEST_RAM_DISCARD_SOURCE(rds); + + g_assert(mr == src->mr); + return src->granularity; +} + +static bool test_rds_is_populated(const RamDiscardSource *rds, + const MemoryRegionSection *section) +{ + TestRamDiscardSource *src = TEST_RAM_DISCARD_SOURCE(rds); + uint64_t offset = section->offset_within_region; + uint64_t size = int128_get64(section->size); + uint64_t first_bit = offset / src->granularity; + uint64_t last_bit = (offset + size - 1) / src->granularity; + unsigned long found; + + g_assert(section->mr == src->mr); + + /* Check if any bit in range is zero (discarded) */ + found = find_next_zero_bit(src->bitmap, last_bit + 1, first_bit); + return found > last_bit; +} + +static void test_rds_class_init(ObjectClass *klass, const void *data) +{ + RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_CLASS(klass); + + rdsc->get_min_granularity = test_rds_get_min_granularity; + rdsc->is_populated = test_rds_is_populated; +} + +static const TypeInfo test_rds_info = { + .name = TYPE_TEST_RAM_DISCARD_SOURCE, + .parent = TYPE_OBJECT, + .instance_size = sizeof(TestRamDiscardSource), + .class_init = test_rds_class_init, + .interfaces = (const InterfaceInfo[]) { + { TYPE_RAM_DISCARD_SOURCE }, + { } + }, +}; + +static TestRamDiscardSource *test_source_new(MemoryRegion *mr, + uint64_t granularity) +{ + TestRamDiscardSource *src; + uint64_t region_size = memory_region_size(mr); + + src = TEST_RAM_DISCARD_SOURCE(object_new(TYPE_TEST_RAM_DISCARD_SOURCE)); + src->mr = mr; + src->granularity = granularity; + src->bitmap_size = DIV_ROUND_UP(region_size, granularity); + src->bitmap = bitmap_new(src->bitmap_size); + + return src; +} + +static void test_source_free(TestRamDiscardSource *src) +{ + g_free(src->bitmap); + object_unref(OBJECT(src)); +} + +static void test_source_populate(TestRamDiscardSource *src, + uint64_t offset, uint64_t size) +{ + uint64_t first_bit = offset / src->granularity; + uint64_t nbits = size / src->granularity; + + bitmap_set(src->bitmap, first_bit, nbits); +} + +static void test_source_discard(TestRamDiscardSource *src, + uint64_t offset, uint64_t size) +{ + uint64_t first_bit = offset / src->granularity; + uint64_t nbits = size / src->granularity; + + bitmap_clear(src->bitmap, first_bit, nbits); +} + +typedef struct TestListener { + RamDiscardListener rdl; + int populate_count; + int discard_count; + uint64_t last_populate_offset; + uint64_t last_populate_size; + uint64_t last_discard_offset; + uint64_t last_discard_size; + int fail_on_populate; /* Return error on Nth populate */ + int populate_call_num; +} TestListener; + +static int test_listener_populate(RamDiscardListener *rdl, + const MemoryRegionSection *section) +{ + TestListener *tl = container_of(rdl, TestListener, rdl); + + tl->populate_call_num++; + if (tl->fail_on_populate > 0 && + tl->populate_call_num >= tl->fail_on_populate) { + return -ENOMEM; + } + + tl->populate_count++; + tl->last_populate_offset = section->offset_within_region; + tl->last_populate_size = int128_get64(section->size); + return 0; +} + +static void test_listener_discard(RamDiscardListener *rdl, + const MemoryRegionSection *section) +{ + TestListener *tl = container_of(rdl, TestListener, rdl); + + tl->discard_count++; + tl->last_discard_offset = section->offset_within_region; + tl->last_discard_size = int128_get64(section->size); +} + +static void test_listener_init(TestListener *tl) +{ + ram_discard_listener_init(&tl->rdl, + test_listener_populate, + test_listener_discard); +} + +#define TEST_REGION_SIZE (16 * 1024 * 1024) /* 16 MB */ +#define GRANULARITY_4K (4 * 1024) +#define GRANULARITY_2M (2 * 1024 * 1024) + +static MemoryRegion *test_mr; + +static void test_setup(void) +{ + test_mr = g_new0(MemoryRegion, 1); + test_mr->size = int128_make64(TEST_REGION_SIZE); + test_mr->ram = true; +} + +static void test_teardown(void) +{ + g_clear_pointer(&test_mr->rdm, object_unref); + object_unparent(OBJECT(test_mr)); + g_free(test_mr); + test_mr = NULL; +} + +static void test_single_source_basic(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + rdm = memory_region_get_ram_discard_manager(test_mr); + g_assert_null(rdm); + + /* Add source */ + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + g_assert_nonnull(rdm); + + g_assert_cmpuint(ram_discard_manager_get_min_granularity(rdm, test_mr), + ==, GRANULARITY_4K); + + /* Initially all discarded */ + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(GRANULARITY_4K); + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate a range in source */ + test_source_populate(src, 0, GRANULARITY_4K * 4); + + /* Now should be populated */ + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + /* Check larger section */ + section.size = int128_make64(GRANULARITY_4K * 4); + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + /* Check section that spans populated and discarded */ + section.size = int128_make64(GRANULARITY_4K * 8); + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + test_source_free(src); + test_teardown(); +} + +static void test_single_source_listener(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + /* Populate some ranges before adding listener */ + test_source_populate(src, 0, GRANULARITY_4K * 4); + test_source_populate(src, GRANULARITY_4K * 8, GRANULARITY_4K * 4); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + g_assert_nonnull(rdm); + + /* Register listener */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Should have been notified about populated regions */ + g_assert_cmpint(tl.populate_count, ==, 2); + + /* Notify populate for new range */ + tl.populate_count = 0; + test_source_populate(src, GRANULARITY_4K * 16, GRANULARITY_4K * 2); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 16, + GRANULARITY_4K * 2); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 16); + g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 2); + + /* Notify discard */ + tl.discard_count = 0; + test_source_discard(src, 0, GRANULARITY_4K * 4); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(tl.discard_count, ==, 1); + g_assert_cmpuint(tl.last_discard_offset, ==, 0); + g_assert_cmpuint(tl.last_discard_size, ==, GRANULARITY_4K * 4); + + /* Unregister listener */ + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +static void test_two_sources_same_granularity(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + /* Add first source */ + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + + /* Add second source */ + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + g_assert_nonnull(rdm); + + /* Check granularity */ + g_assert_cmpuint(ram_discard_manager_get_min_granularity(rdm, test_mr), + ==, GRANULARITY_4K); + + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(GRANULARITY_4K); + + /* Both discarded -> aggregated discarded */ + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate in src1 only */ + test_source_populate(src1, 0, GRANULARITY_4K); + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate in src2 only */ + test_source_discard(src1, 0, GRANULARITY_4K); + test_source_populate(src2, 0, GRANULARITY_4K); + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate in both -> aggregated populated */ + test_source_populate(src1, 0, GRANULARITY_4K); + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + /* Remove sources */ + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +/* + * Test: Two sources with different granularities (4K and 2M). + * The aggregated granularity should be GCD(4K, 2M) = 4K. + */ +static void test_two_sources_different_granularity(void) +{ + TestRamDiscardSource *src_4k, *src_2m; + RamDiscardManager *rdm; + MemoryRegionSection section; + int ret; + + test_setup(); + + src_4k = test_source_new(test_mr, GRANULARITY_4K); + src_2m = test_source_new(test_mr, GRANULARITY_2M); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src_4k)); + g_assert_cmpint(ret, ==, 0); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src_2m)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + + g_assert_cmpuint(ram_discard_manager_get_min_granularity(rdm, test_mr), + ==, GRANULARITY_4K); + + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(GRANULARITY_4K); + + /* Both discarded */ + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate 4K in src_4k, but src_2m still discarded the whole 2M block */ + test_source_populate(src_4k, 0, GRANULARITY_4K); + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate 2M in src_2m (which includes the 4K block) */ + test_source_populate(src_2m, 0, GRANULARITY_2M); + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + /* Check a 4K block at offset 4K (populated in src_2m but not in src_4k) */ + section.offset_within_region = GRANULARITY_4K; + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate it in src_4k */ + test_source_populate(src_4k, GRANULARITY_4K, GRANULARITY_4K); + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src_2m)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src_4k)); + + test_source_free(src_2m); + test_source_free(src_4k); + test_teardown(); +} + +/* + * Test: Notification with two sources. + * Populate notification should only fire when all sources are populated. + */ +static void test_two_sources_notification(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* No populate notifications yet (all discarded) */ + g_assert_cmpint(tl.populate_count, ==, 0); + + /* Populate in src1 only - no notification (src2 still discarded) */ + test_source_populate(src1, 0, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src1), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl.populate_count, ==, 0); + + /* Populate same range in src2 - now should notify */ + test_source_populate(src2, 0, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src2), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl.populate_count, ==, 1); + + /* Discard from src1 - should notify discard immediately */ + tl.discard_count = 0; + test_source_discard(src1, 0, GRANULARITY_4K * 2); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src1), + 0, GRANULARITY_4K * 2); + g_assert_cmpint(tl.discard_count, ==, 1); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +/* + * Test: Adding source with existing listener. + * When a new source is added, listeners should be notified about + * regions that become discarded. + */ +static void test_add_source_with_listener(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + /* Populate some range in src1 */ + test_source_populate(src1, 0, GRANULARITY_4K * 8); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Should have been notified about populated region */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpint(tl.last_populate_offset, ==, 0); + g_assert_cmpint(tl.last_populate_size, ==, GRANULARITY_4K * 8); + + /* src2 has part of the region populated, part discarded */ + /* src2 has 0-4 populated, 4-8 discarded */ + test_source_populate(src2, 0, GRANULARITY_4K * 4); + + /* Add src2 - listener should be notified about newly discarded regions */ + tl.discard_count = 0; + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + /* + * The range 4K*4 to 4K*8 was populated in src1 but discarded in src2, + * so it becomes aggregated-discarded. Listener should be notified. + * Only this range should trigger a discard notification - regions beyond + * 4K*8 were already discarded in src1, so adding src2 doesn't change them. + */ + g_assert_cmpint(tl.discard_count, ==, 1); + g_assert_cmpint(tl.last_discard_offset, ==, GRANULARITY_4K * 4); + g_assert_cmpint(tl.last_discard_size, ==, GRANULARITY_4K * 4); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +/* + * Test: Removing source with existing listener. + * When a source is removed, listeners should be notified about + * regions that become populated. + */ +static void test_remove_source_with_listener(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + /* src1: all of first 8 blocks populated */ + test_source_populate(src1, 0, GRANULARITY_4K * 8); + /* src2: only first 4 blocks populated */ + test_source_populate(src2, 0, GRANULARITY_4K * 4); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Only first 4 blocks are aggregated-populated */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4); + + /* Remove src2 - blocks 4-8 should become populated */ + tl.populate_count = 0; + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + + /* Listener should be notified about newly populated region (4K*4 to 4K*8) */ + g_assert_cmpint(tl.populate_count, >=, 1); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +/* + * Test: Add a source, register a listener, remove the source, then add it back. + * This checks the transition from 0 sources (all populated) to 1 source + * (partially discarded) with an active listener. + */ +static void test_readd_source_with_listener(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + /* Populate some range in src */ + test_source_populate(src, 0, GRANULARITY_4K * 8); + + /* 1. Add source */ + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* 2. Register listener */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Listener notified about populated region (0 - 32K) */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 8); + + /* 3. Remove source */ + tl.populate_count = 0; + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + + /* + * With 0 sources, everything is populated. + * The range that was discarded in src (from 32K to end) becomes populated. + */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 8); + g_assert_cmpuint(tl.last_populate_size, ==, TEST_REGION_SIZE - GRANULARITY_4K * 8); + + /* 4. Add source back */ + tl.discard_count = 0; + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + + /* + * Now we have 1 source again. The range (32K to end) is discarded again. + * Listener should be notified about this discard. + */ + g_assert_cmpint(tl.discard_count, ==, 1); + g_assert_cmpuint(tl.last_discard_offset, ==, GRANULARITY_4K * 8); + g_assert_cmpuint(tl.last_discard_size, ==, TEST_REGION_SIZE - GRANULARITY_4K * 8); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +/* + * Test: Duplicate source registration should fail. + */ +static void test_duplicate_source(void) +{ + TestRamDiscardSource *src; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + + /* Adding same source again should fail */ + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, -EBUSY); + + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +/* + * Test: Populate notification rollback on listener error. + */ +static void test_populate_rollback(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl1 = { 0, }, tl2 = { 0, }; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register two listeners */ + test_listener_init(&tl1); + test_listener_init(&tl2); + tl2.fail_on_populate = 1; /* Second listener fails on first populate */ + + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + + /* + * Register tl2 first so it's visited second (QLIST_INSERT_HEAD reverses + * registration order). This ensures tl1 receives populate before tl2 + * fails. + */ + ram_discard_manager_register_listener(rdm, &tl2.rdl, §ion); + ram_discard_manager_register_listener(rdm, &tl1.rdl, §ion); + + /* Try to populate - should fail and roll back */ + test_source_populate(src, 0, GRANULARITY_4K); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + 0, GRANULARITY_4K); + g_assert_cmpint(ret, ==, -ENOMEM); + + /* First listener should have received populate then discard (rollback) */ + g_assert_cmpint(tl1.populate_count, ==, 1); + g_assert_cmpint(tl1.discard_count, ==, 1); + + ram_discard_manager_unregister_listener(rdm, &tl1.rdl); + ram_discard_manager_unregister_listener(rdm, &tl2.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +/* + * Test: Replay populated with two sources (intersection). + */ +static void test_replay_populated_intersection(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + /* + * src1: blocks 0-7 populated + * src2: blocks 4-11 populated + * Intersection: blocks 4-7 + */ + test_source_populate(src1, 0, GRANULARITY_4K * 8); + test_source_populate(src2, GRANULARITY_4K * 4, GRANULARITY_4K * 8); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener - should only get notified about intersection */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Should have been notified about blocks 4-7 (intersection) */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 4); + g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +/* + * Test: Empty region (no sources). + */ +static void test_no_sources(void) +{ + test_setup(); + + /* No sources - should have no manager */ + g_assert_null(memory_region_get_ram_discard_manager(test_mr)); + g_assert_false(memory_region_has_ram_discard_manager(test_mr)); + + test_teardown(); +} + +static void test_redundant_discard(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + /* Add sources */ + ret = memory_region_add_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + ret = memory_region_add_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Populate intersection (0-4K) in both sources */ + test_source_populate(src1, 0, GRANULARITY_4K); + test_source_populate(src2, 0, GRANULARITY_4K); + + /* Notify populate src1 - should trigger listener populate */ + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src1), + 0, GRANULARITY_4K); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl.populate_count, ==, 1); + + /* Now Discard src1 -> Aggregate Discarded */ + tl.discard_count = 0; + test_source_discard(src1, 0, GRANULARITY_4K); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src1), 0, GRANULARITY_4K); + g_assert_cmpint(tl.discard_count, ==, 1); + + /* Now Discard src2 -> Aggregate Discarded (Already Discarded!) */ + /* Listener should NOT receive another discard notification for the same range. */ + test_source_discard(src2, 0, GRANULARITY_4K); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src2), 0, GRANULARITY_4K); + + g_assert_cmpint(tl.discard_count, ==, 1); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +/* + * Test: Listener with partial section coverage. + * Listener should only receive notifications for its registered range. + */ +static void test_partial_listener_section(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + /* Populate blocks 0-7 */ + test_source_populate(src, 0, GRANULARITY_4K * 8); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener for only blocks 2-5 (not the full region) */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = GRANULARITY_4K * 2; + section.size = int128_make64(GRANULARITY_4K * 4); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Should be notified only about blocks 2-5 (intersection) */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 2); + g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4); + + /* Discard block 0 - outside listener's section, no notification */ + tl.discard_count = 0; + test_source_discard(src, 0, GRANULARITY_4K); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src), + 0, GRANULARITY_4K); + g_assert_cmpint(tl.discard_count, ==, 0); + + /* Discard block 3 - inside listener's section */ + test_source_discard(src, GRANULARITY_4K * 3, GRANULARITY_4K); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 3, GRANULARITY_4K); + g_assert_cmpint(tl.discard_count, ==, 1); + g_assert_cmpuint(tl.last_discard_offset, ==, GRANULARITY_4K * 3); + + /* Discard spanning boundary (blocks 5-6) - only block 5 in section */ + tl.discard_count = 0; + test_source_discard(src, GRANULARITY_4K * 5, GRANULARITY_4K * 2); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 5, GRANULARITY_4K * 2); + g_assert_cmpint(tl.discard_count, ==, 1); + g_assert_cmpuint(tl.last_discard_offset, ==, GRANULARITY_4K * 5); + g_assert_cmpuint(tl.last_discard_size, ==, GRANULARITY_4K); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +/* + * Test: Multiple listeners with different (non-overlapping) sections. + */ +static void test_multiple_listeners_different_sections(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section1, section2; + TestListener tl1 = { 0, }, tl2 = { 0, }; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Listener 1: blocks 0-3 */ + test_listener_init(&tl1); + section1.mr = test_mr; + section1.offset_within_region = 0; + section1.size = int128_make64(GRANULARITY_4K * 4); + ram_discard_manager_register_listener(rdm, &tl1.rdl, §ion1); + + /* Listener 2: blocks 8-11 */ + test_listener_init(&tl2); + section2.mr = test_mr; + section2.offset_within_region = GRANULARITY_4K * 8; + section2.size = int128_make64(GRANULARITY_4K * 4); + ram_discard_manager_register_listener(rdm, &tl2.rdl, §ion2); + + /* Initially all discarded - no populate notifications */ + g_assert_cmpint(tl1.populate_count, ==, 0); + g_assert_cmpint(tl2.populate_count, ==, 0); + + /* Populate blocks 0-3 - only tl1 should be notified */ + test_source_populate(src, 0, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl1.populate_count, ==, 1); + g_assert_cmpint(tl2.populate_count, ==, 0); + + /* Populate blocks 8-11 - only tl2 should be notified */ + test_source_populate(src, GRANULARITY_4K * 8, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 8, + GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl1.populate_count, ==, 1); + g_assert_cmpint(tl2.populate_count, ==, 1); + + /* Populate blocks 4-7 (gap) - neither listener should be notified */ + test_source_populate(src, GRANULARITY_4K * 4, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 4, + GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl1.populate_count, ==, 1); + g_assert_cmpint(tl2.populate_count, ==, 1); + + ram_discard_manager_unregister_listener(rdm, &tl2.rdl); + ram_discard_manager_unregister_listener(rdm, &tl1.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +/* + * Test: Multiple listeners with overlapping sections. + */ +static void test_overlapping_listener_sections(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section1, section2; + TestListener tl1 = { 0, }, tl2 = { 0, }; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Listener 1: blocks 0-7 */ + test_listener_init(&tl1); + section1.mr = test_mr; + section1.offset_within_region = 0; + section1.size = int128_make64(GRANULARITY_4K * 8); + ram_discard_manager_register_listener(rdm, &tl1.rdl, §ion1); + + /* Listener 2: blocks 4-11 (overlaps with tl1 on blocks 4-7) */ + test_listener_init(&tl2); + section2.mr = test_mr; + section2.offset_within_region = GRANULARITY_4K * 4; + section2.size = int128_make64(GRANULARITY_4K * 8); + ram_discard_manager_register_listener(rdm, &tl2.rdl, §ion2); + + /* Populate blocks 4-7 (overlap region) - both should be notified */ + test_source_populate(src, GRANULARITY_4K * 4, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 4, + GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl1.populate_count, ==, 1); + g_assert_cmpint(tl2.populate_count, ==, 1); + + /* Populate blocks 0-3 - only tl1 */ + test_source_populate(src, 0, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl1.populate_count, ==, 2); + g_assert_cmpint(tl2.populate_count, ==, 1); + + /* Populate blocks 8-11 - only tl2 */ + test_source_populate(src, GRANULARITY_4K * 8, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 8, + GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl1.populate_count, ==, 2); + g_assert_cmpint(tl2.populate_count, ==, 2); + + ram_discard_manager_unregister_listener(rdm, &tl2.rdl); + ram_discard_manager_unregister_listener(rdm, &tl1.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +/* + * Test: Listener at exact memory region boundaries. + */ +static void test_boundary_section(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + uint64_t last_offset; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + /* Populate last 4 blocks of the region */ + last_offset = TEST_REGION_SIZE - GRANULARITY_4K * 4; + test_source_populate(src, last_offset, GRANULARITY_4K * 4); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener for exactly the last 4 blocks */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = last_offset; + section.size = int128_make64(GRANULARITY_4K * 4); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Should receive notification for the populated range */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_offset, ==, last_offset); + g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4); + + /* Discard exactly at boundary */ + tl.discard_count = 0; + test_source_discard(src, last_offset, GRANULARITY_4K * 4); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src), + last_offset, GRANULARITY_4K * 4); + g_assert_cmpint(tl.discard_count, ==, 1); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +static int count_discarded_blocks(const MemoryRegionSection *section, + void *opaque) +{ + int *count = opaque; + *count += int128_get64(section->size) / GRANULARITY_4K; + return 0; +} + +/* + * Test: replay_discarded with two sources (union semantics). + */ +static void test_replay_discarded(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + int count = 0; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + /* + * src1: blocks 0-3 populated, rest discarded + * src2: blocks 2-5 populated, rest discarded + * Aggregated populated: blocks 2-3 (intersection) + * Aggregated discarded: blocks 0-1, 4-5, 6+ (union of discarded) + */ + test_source_populate(src1, 0, GRANULARITY_4K * 4); + test_source_populate(src2, GRANULARITY_4K * 2, GRANULARITY_4K * 4); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(GRANULARITY_4K * 8); + + /* Count discarded blocks */ + ret = ram_discard_manager_replay_discarded(rdm, §ion, + count_discarded_blocks, &count); + + g_assert_cmpint(ret, ==, 0); + /* Discarded: blocks 0-1 (2), blocks 4-5 (2), blocks 6-7 (2) = 6 blocks */ + g_assert_cmpint(count, ==, 6); + + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + module_call_init(MODULE_INIT_QOM); + type_register_static(&test_rds_info); + + g_test_add_func("/ram-discard-manager/single-source/basic", + test_single_source_basic); + g_test_add_func("/ram-discard-manager/single-source/listener", + test_single_source_listener); + g_test_add_func("/ram-discard-manager/two-sources/same-granularity", + test_two_sources_same_granularity); + g_test_add_func("/ram-discard-manager/two-sources/different-granularity", + test_two_sources_different_granularity); + g_test_add_func("/ram-discard-manager/two-sources/notification", + test_two_sources_notification); + g_test_add_func("/ram-discard-manager/dynamic/add-source-with-listener", + test_add_source_with_listener); + g_test_add_func("/ram-discard-manager/dynamic/remove-source-with-listener", + test_remove_source_with_listener); + g_test_add_func("/ram-discard-manager/dynamic/readd-source-with-listener", + test_readd_source_with_listener); + g_test_add_func("/ram-discard-manager/edge/duplicate-source", + test_duplicate_source); + g_test_add_func("/ram-discard-manager/edge/populate-rollback", + test_populate_rollback); + g_test_add_func("/ram-discard-manager/edge/replay-intersection", + test_replay_populated_intersection); + g_test_add_func("/ram-discard-manager/edge/no-sources", + test_no_sources); + g_test_add_func("/ram-discard-manager/multi-source/redundant-discard", + test_redundant_discard); + g_test_add_func("/ram-discard-manager/listener/partial-section", + test_partial_listener_section); + g_test_add_func("/ram-discard-manager/listener/multiple-different", + test_multiple_listeners_different_sections); + g_test_add_func("/ram-discard-manager/listener/overlapping", + test_overlapping_listener_sections); + g_test_add_func("/ram-discard-manager/edge/boundary-section", + test_boundary_section); + g_test_add_func("/ram-discard-manager/multi-source/replay-discarded", + test_replay_discarded); + + return g_test_run(); +} diff --git a/ui/cocoa.m b/ui/cocoa.m index e157ad01d8..3751be3792 100644 --- a/ui/cocoa.m +++ b/ui/cocoa.m @@ -1206,8 +1206,6 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven COCOA_DEBUG("QemuCocoaAppController: dealloc\n"); [cocoaView release]; - [cbowner release]; - cbowner = nil; [super dealloc]; } @@ -2038,9 +2036,26 @@ static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts) qemu_main = cocoa_main; } +static void cocoa_display_cleanup(void) +{ + if (!kbd) { + return; + } + + qemu_console_unregister_listener(&dcl); + g_clear_pointer(&kbd, qkbd_state_free); + qemu_remove_mouse_mode_change_notifier(&mouse_mode_change_notifier); + qemu_clipboard_peer_unregister(&cbpeer); + g_clear_pointer(&cbinfo, qemu_clipboard_info_unref); + qemu_event_destroy(&cbevent); + [cbowner release]; + cbowner = nil; +} + static QemuDisplay qemu_display_cocoa = { .type = DISPLAY_TYPE_COCOA, .init = cocoa_display_init, + .cleanup = cocoa_display_cleanup, }; static void register_cocoa(void) diff --git a/ui/console-vc.c b/ui/console-vc.c index 828e78c41e..53d9e9d39b 100644 --- a/ui/console-vc.c +++ b/ui/console-vc.c @@ -255,6 +255,7 @@ static bool vc_chr_open(Chardev *chr, ChardevBackend *backend, Error **errp) } qemu_chr_be_event(chr, CHR_EVENT_OPENED); + qemu_console_notify(QEMU_CONSOLE_ADDED, QEMU_CONSOLE(s)); return true; } @@ -327,12 +328,24 @@ static void char_vc_init(Object *obj) vc->encoding = CHARDEV_VC_ENCODING_UTF8; } +static void char_vc_finalize(Object *obj) +{ + VCChardev *vc = VC_CHARDEV(obj); + QemuConsole *con = QEMU_CONSOLE(vc->console); + + if (con) { + qemu_console_notify(QEMU_CONSOLE_REMOVED, con); + object_unref(con); + } +} + static const TypeInfo char_vc_type_info = { .name = TYPE_CHARDEV_VC, .parent = TYPE_CHARDEV, .instance_size = sizeof(VCChardev), .instance_init = char_vc_init, .instance_post_init = object_apply_compat_props, + .instance_finalize = char_vc_finalize, .class_init = char_vc_class_init, }; diff --git a/ui/console.c b/ui/console.c index a7c977d0c4..a8a2a247d8 100644 --- a/ui/console.c +++ b/ui/console.c @@ -42,6 +42,7 @@ #include "qemu/memfd.h" #include "ui/vt100.h" #include "vgafont.h" +#include "ui/qemu-spice.h" #include "console-priv.h" @@ -67,8 +68,10 @@ struct DisplayState { uint64_t last_update; uint64_t update_interval; bool refreshing; + bool initialized; QLIST_HEAD(, DisplayChangeListener) listeners; + NotifierList console_notifiers; }; static DisplayState *display_state; @@ -83,6 +86,16 @@ static bool console_compatible_with(QemuConsole *con, static QemuConsole *qemu_graphic_console_lookup_unused(void); static void dpy_set_ui_info_timer(void *opaque); +void qemu_console_notify(QemuConsoleEventType type, QemuConsole *con) +{ + DisplayState *ds = get_alloc_displaystate(); + QemuConsoleEvent event = { + .type = type, + .con = con, + }; + notifier_list_notify(&ds->console_notifiers, &event); +} + static void gui_update(void *opaque) { uint64_t interval = GUI_REFRESH_INTERVAL_IDLE; @@ -364,6 +377,13 @@ void qemu_text_console_put_string(QemuTextConsole *s, const char *str, int len) } } +static void qemu_console_add_to_qom(QemuConsole *con) +{ + g_autofree gchar *name = g_strdup_printf("console[%d]", con->index); + object_property_add_child(object_get_container("backend"), + name, OBJECT(con)); +} + static void qemu_console_register(QemuConsole *c) { @@ -401,6 +421,10 @@ qemu_console_register(QemuConsole *c) } } } + + if (c->ds->initialized) { + qemu_console_add_to_qom(c); + } } static void @@ -433,6 +457,8 @@ qemu_console_init(Object *obj) c->window_id = -1; c->ui_timer = timer_new_ms(QEMU_CLOCK_REALTIME, dpy_set_ui_info_timer, c); + c->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME, + console_hw_gl_unblock_timer, c); qemu_console_register(c); } @@ -573,7 +599,7 @@ void qemu_console_set_display_gl_ctx(QemuConsole *con, DisplayGLCtx *gl) { /* display has opengl support */ assert(con); - if (con->gl) { + if (gl && con->gl) { error_report("The console already has an OpenGL context."); exit(1); } @@ -1053,30 +1079,41 @@ static DisplayState *get_alloc_displaystate(void) { if (!display_state) { display_state = g_new0(DisplayState, 1); + notifier_list_init(&display_state->console_notifiers); } return display_state; } +void qemu_console_add_notifier(Notifier *notifier) +{ + DisplayState *ds = get_alloc_displaystate(); + notifier_list_add(&ds->console_notifiers, notifier); +} + +void qemu_console_remove_notifier(Notifier *notifier) +{ + notifier_remove(notifier); +} + /* * Called by main(), after creating QemuConsoles * and before initializing ui (sdl/vnc/...). */ DisplayState *init_displaystate(void) { - gchar *name; + DisplayState *ds = get_alloc_displaystate(); QemuConsole *con; QTAILQ_FOREACH(con, &consoles, next) { /* Hook up into the qom tree here (not in object_new()), once * all QemuConsoles are created and the order / numbering * doesn't change any more */ - name = g_strdup_printf("console[%d]", con->index); - object_property_add_child(object_get_container("backend"), - name, OBJECT(con)); - g_free(name); + qemu_console_add_to_qom(con); } - return display_state; + ds->initialized = true; + + return ds; } void qemu_graphic_console_set_hwops(QemuConsole *con, @@ -1103,6 +1140,9 @@ QemuConsole *qemu_graphic_console_create(DeviceState *dev, uint32_t head, trace_console_gfx_reuse(s->index); width = qemu_console_get_width(s, 0); height = qemu_console_get_height(s, 0); + if (s->ds->initialized) { + qemu_console_add_to_qom(s); + } } else { trace_console_gfx_new(); s = (QemuConsole *)object_new(TYPE_QEMU_GRAPHIC_CONSOLE); @@ -1116,8 +1156,7 @@ QemuConsole *qemu_graphic_console_create(DeviceState *dev, uint32_t head, surface = qemu_create_placeholder_surface(width, height, noinit); qemu_console_set_surface(s, surface); - s->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME, - console_hw_gl_unblock_timer, s); + qemu_console_notify(QEMU_CONSOLE_ADDED, s); return s; } @@ -1134,14 +1173,17 @@ void qemu_graphic_console_close(QemuConsole *con) int height = qemu_console_get_height(con, 480); trace_console_gfx_close(con->index); + qemu_console_notify(QEMU_CONSOLE_REMOVED, con); object_property_set_link(OBJECT(con), "device", NULL, &error_abort); qemu_graphic_console_set_hwops(con, &unused_ops, NULL); + timer_del(con->ui_timer); if (con->gl) { qemu_console_gl_scanout_disable(con); } surface = qemu_create_placeholder_surface(width, height, unplugged); qemu_console_set_surface(con, surface); + object_unparent(OBJECT(con)); } QemuConsole *qemu_console_lookup_default(void) @@ -1413,6 +1455,23 @@ void qemu_display_init(DisplayState *ds, DisplayOptions *opts) dpys[opts->type]->init(ds, opts); } +void qemu_display_cleanup(void) +{ + int i; + + for (i = 0; i < DISPLAY_TYPE__MAX; i++) { + if (dpys[i] && dpys[i]->cleanup) { + dpys[i]->cleanup(); + } + } +#ifdef CONFIG_VNC + vnc_cleanup(); +#endif +#ifdef CONFIG_SPICE + qemu_spice.cleanup(); +#endif +} + const char *qemu_display_get_vc(DisplayOptions *opts) { #ifdef CONFIG_PIXMAN diff --git a/ui/curses.c b/ui/curses.c index 24d3713e57..4db32b6168 100644 --- a/ui/curses.c +++ b/ui/curses.c @@ -411,11 +411,19 @@ static void curses_refresh(DisplayChangeListener *dcl) } } -static void curses_atexit(void) +static void curses_cleanup(void) { + if (!dcl) { + return; + } + endwin(); - g_free(vga_to_curses); - g_free(screen); + qemu_console_unregister_listener(dcl); + g_clear_pointer(&dcl, g_free); + g_clear_pointer(&screenpad, delwin); + g_clear_pointer(&vga_to_curses, g_free); + g_clear_pointer(&screen, g_free); + g_clear_pointer(&kbd_layout, kbd_layout_free); } /* @@ -799,8 +807,6 @@ static void curses_display_init(DisplayState *ds, DisplayOptions *opts) vga_to_curses = g_new0(cchar_t, 256); curses_setup(); curses_keyboard_setup(); - atexit(curses_atexit); - curses_winch_init(); dcl = g_new0(DisplayChangeListener, 1); @@ -812,6 +818,7 @@ static void curses_display_init(DisplayState *ds, DisplayOptions *opts) static QemuDisplay qemu_display_curses = { .type = DISPLAY_TYPE_CURSES, .init = curses_display_init, + .cleanup = curses_cleanup, }; static void register_curses(void) diff --git a/ui/dbus-console.c b/ui/dbus-console.c index 9e154da1ec..e1ac06814b 100644 --- a/ui/dbus-console.c +++ b/ui/dbus-console.c @@ -154,6 +154,7 @@ dbus_display_console_dispose(GObject *object) qemu_input_led_notifier_remove(&ddc->led_notifier); qemu_console_unregister_listener(&ddc->dcl); + qemu_remove_mouse_mode_change_notifier(&ddc->mouse_mode_notifier); g_clear_object(&ddc->iface_touch); g_clear_object(&ddc->iface_mouse); g_clear_object(&ddc->iface_kbd); @@ -532,6 +533,11 @@ int dbus_display_console_get_index(DBusDisplayConsole *ddc) return qemu_console_get_index(ddc->dcl.con); } +QemuConsole *dbus_display_console_get_qemu_console(DBusDisplayConsole *ddc) +{ + return ddc->dcl.con; +} + DBusDisplayConsole * dbus_display_console_new(DBusDisplay *display, QemuConsole *con) { @@ -142,6 +142,9 @@ dbus_display_finalize(Object *o) { DBusDisplay *dd = DBUS_DISPLAY(o); + if (dd->console_notifier.notify) { + qemu_console_remove_notifier(&dd->console_notifier); + } if (dd->notifier.notify) { dbus_display_notifier_remove(&dd->notifier); } @@ -164,14 +167,35 @@ dbus_display_finalize(Object *o) dbus_display = NULL; } +static void +dbus_update_console_ids(DBusDisplay *dd) +{ + g_autoptr(GArray) arr = g_array_new(FALSE, FALSE, sizeof(guint32)); + + for (guint i = 0; i < dd->consoles->len; i++) { + DBusDisplayConsole *ddc = g_ptr_array_index(dd->consoles, i); + guint32 idx = dbus_display_console_get_index(ddc); + g_array_append_val(arr, idx); + } + + g_object_set(dd->iface, "console-ids", + g_variant_new_fixed_array(G_VARIANT_TYPE("u"), + arr->data, arr->len, + sizeof(guint32)), + NULL); +} + static bool -dbus_display_add_console(DBusDisplay *dd, int idx, Error **errp) +dbus_display_add_console(DBusDisplay *dd, QemuConsole *con, Error **errp) { - QemuConsole *con; DBusDisplayConsole *dbus_console; - con = qemu_console_lookup_by_index(idx); - assert(con); + for (guint i = 0; i < dd->consoles->len; i++) { + DBusDisplayConsole *ddc = g_ptr_array_index(dd->consoles, i); + if (dbus_display_console_get_qemu_console(ddc) == con) { + return true; + } + } if (qemu_console_is_graphic(con) && dd->gl_mode != DISPLAY_GL_MODE_OFF) { @@ -179,20 +203,58 @@ dbus_display_add_console(DBusDisplay *dd, int idx, Error **errp) } dbus_console = dbus_display_console_new(dd, con); - g_ptr_array_insert(dd->consoles, idx, dbus_console); + g_ptr_array_add(dd->consoles, dbus_console); g_dbus_object_manager_server_export(dd->server, G_DBUS_OBJECT_SKELETON(dbus_console)); + dbus_update_console_ids(dd); return true; } static void +dbus_display_remove_console(DBusDisplay *dd, QemuConsole *con) +{ + for (guint i = 0; i < dd->consoles->len; i++) { + DBusDisplayConsole *ddc = g_ptr_array_index(dd->consoles, i); + if (dbus_display_console_get_qemu_console(ddc) == con) { + if (display_opengl) { + qemu_console_set_display_gl_ctx(con, NULL); + } + g_dbus_object_manager_server_unexport( + dd->server, + g_dbus_object_get_object_path(G_DBUS_OBJECT(ddc))); + g_ptr_array_remove_index(dd->consoles, i); + dbus_update_console_ids(dd); + break; + } + } +} + +static void +dbus_console_notify(Notifier *n, void *data) +{ + DBusDisplay *dd = container_of(n, DBusDisplay, console_notifier); + QemuConsoleEvent *event = data; + + switch (event->type) { + case QEMU_CONSOLE_ADDED: { + Error *err = NULL; + if (!dbus_display_add_console(dd, event->con, &err)) { + error_report_err(err); + } + break; + } + case QEMU_CONSOLE_REMOVED: + dbus_display_remove_console(dd, event->con); + break; + } +} + +static void dbus_display_complete(UserCreatable *uc, Error **errp) { DBusDisplay *dd = DBUS_DISPLAY(uc); g_autoptr(GError) err = NULL; g_autofree char *uuid = qemu_uuid_unparse_strdup(&qemu_uuid); - g_autoptr(GArray) consoles = NULL; - GVariant *console_ids; int idx; if (!object_resolve_path_type("", TYPE_DBUS_DISPLAY, NULL)) { @@ -233,27 +295,24 @@ dbus_display_complete(UserCreatable *uc, Error **errp) } } - consoles = g_array_new(FALSE, FALSE, sizeof(guint32)); for (idx = 0;; idx++) { - if (!qemu_console_lookup_by_index(idx)) { + QemuConsole *con = qemu_console_lookup_by_index(idx); + if (!con) { break; } - if (!dbus_display_add_console(dd, idx, errp)) { + if (!dbus_display_add_console(dd, con, errp)) { return; } - g_array_append_val(consoles, idx); } - console_ids = g_variant_new_from_data( - G_VARIANT_TYPE("au"), - consoles->data, consoles->len * sizeof(guint32), TRUE, - (GDestroyNotify)g_array_unref, consoles); - g_steal_pointer(&consoles); g_object_set(dd->iface, "name", qemu_name ?: "QEMU " QEMU_VERSION, "uuid", uuid, - "console-ids", console_ids, NULL); + dbus_update_console_ids(dd); + + dd->console_notifier.notify = dbus_console_notify; + qemu_console_add_notifier(&dd->console_notifier); if (dd->bus) { g_dbus_object_manager_server_set_connection(dd->server, dd->bus); @@ -615,10 +674,23 @@ static const TypeInfo dbus_display_info = { } }; +static void +dbus_cleanup(void) +{ + Object *o; + + o = object_resolve_path_component(object_get_objects_root(), + "dbus-display"); + if (o) { + object_unparent(o); + } +} + static QemuDisplay qemu_display_dbus = { .type = DISPLAY_TYPE_DBUS, .early_init = early_dbus_init, .init = dbus_init, + .cleanup = dbus_cleanup, .vc = "vc", }; @@ -60,6 +60,7 @@ struct DBusDisplay { DBusClipboardRequest clipboard_request[QEMU_CLIPBOARD_SELECTION__COUNT]; Notifier notifier; + Notifier console_notifier; }; #ifdef WIN32 @@ -86,6 +87,8 @@ dbus_display_console_new(DBusDisplay *display, QemuConsole *con); int dbus_display_console_get_index(DBusDisplayConsole *ddc); +QemuConsole * +dbus_display_console_get_qemu_console(DBusDisplayConsole *ddc); extern const DisplayChangeListenerOps dbus_console_dcl_ops; diff --git a/ui/egl-headless.c b/ui/egl-headless.c index 878bfebb40..ba27efcf4c 100644 --- a/ui/egl-headless.c +++ b/ui/egl-headless.c @@ -9,6 +9,7 @@ typedef struct egl_dpy { DisplayChangeListener dcl; + DisplayGLCtx *ctx; DisplaySurface *ds; QemuGLShader *gls; egl_fb guest_fb; @@ -19,6 +20,8 @@ typedef struct egl_dpy { uint32_t pos_y; } egl_dpy; +static GPtrArray *egl_dpys; + /* ------------------------------------------------------------------ */ static void egl_refresh(DisplayChangeListener *dcl) @@ -220,6 +223,8 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts) egl_dpy *edpy; int idx; + egl_dpys = g_ptr_array_new(); + for (idx = 0;; idx++) { DisplayGLCtx *ctx; @@ -232,15 +237,41 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts) edpy->gls = qemu_gl_init_shader(); ctx = g_new0(DisplayGLCtx, 1); ctx->ops = &eglctx_ops; + edpy->ctx = ctx; qemu_console_set_display_gl_ctx(con, ctx); qemu_console_register_listener(con, &edpy->dcl, &egl_ops); + g_ptr_array_add(egl_dpys, edpy); + } +} + +static void egl_headless_cleanup(void) +{ + if (!egl_dpys) { + return; } + + for (guint i = 0; i < egl_dpys->len; i++) { + egl_dpy *edpy = g_ptr_array_index(egl_dpys, i); + + qemu_console_unregister_listener(&edpy->dcl); + qemu_console_set_display_gl_ctx(edpy->dcl.con, NULL); + egl_fb_destroy(&edpy->guest_fb); + egl_fb_destroy(&edpy->cursor_fb); + egl_fb_destroy(&edpy->blit_fb); + qemu_gl_fini_shader(edpy->gls); + g_free(edpy->ctx); + g_free(edpy); + } + g_clear_pointer(&egl_dpys, g_ptr_array_unref); + + egl_cleanup(); } static QemuDisplay qemu_display_egl = { .type = DISPLAY_TYPE_EGL_HEADLESS, .early_init = early_egl_headless_init, .init = egl_headless_init, + .cleanup = egl_headless_cleanup, }; static void register_egl(void) diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c index 069a524955..d689f187c4 100644 --- a/ui/egl-helpers.c +++ b/ui/egl-helpers.c @@ -733,3 +733,22 @@ bool egl_init(const char *rendernode, DisplayGLMode mode, Error **errp) display_opengl = 1; return true; } + +void egl_cleanup(void) +{ + if (qemu_egl_rn_ctx) { + eglDestroyContext(qemu_egl_display, qemu_egl_rn_ctx); + qemu_egl_rn_ctx = NULL; + } + +#ifdef CONFIG_GBM + g_clear_pointer(&qemu_egl_rn_gbm_dev, gbm_device_destroy); + g_clear_fd(&qemu_egl_rn_fd, NULL); +#endif + + if (qemu_egl_display) { + eglReleaseThread(); + eglTerminate(qemu_egl_display); + qemu_egl_display = NULL; + } +} diff --git a/ui/gtk-clipboard.c b/ui/gtk-clipboard.c index ea9444be70..476e6b8303 100644 --- a/ui/gtk-clipboard.c +++ b/ui/gtk-clipboard.c @@ -235,3 +235,18 @@ void gd_clipboard_init(GtkDisplayState *gd) g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECONDARY], "owner-change", G_CALLBACK(gd_owner_change), gd); } + +void gd_clipboard_cleanup(GtkDisplayState *gd) +{ + if (!gd->cbpeer.name) { + return; + } + qemu_clipboard_peer_unregister(&gd->cbpeer); + g_signal_handlers_disconnect_by_data( + gd->gtkcb[QEMU_CLIPBOARD_SELECTION_CLIPBOARD], gd); + g_signal_handlers_disconnect_by_data( + gd->gtkcb[QEMU_CLIPBOARD_SELECTION_PRIMARY], gd); + g_signal_handlers_disconnect_by_data( + gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECONDARY], gd); + gd->cbpeer.name = NULL; +} @@ -142,6 +142,7 @@ static void gd_grab_pointer(VirtualConsole *vc, const char *reason); static void gd_ungrab_pointer(GtkDisplayState *s); static void gd_grab_keyboard(VirtualConsole *vc, const char *reason); static void gd_ungrab_keyboard(GtkDisplayState *s); +static void gd_rebuild_vc_menu(GtkDisplayState *s); /** Utility Functions **/ @@ -150,8 +151,8 @@ static VirtualConsole *gd_vc_find_by_menu(GtkDisplayState *s) VirtualConsole *vc; gint i; - for (i = 0; i < s->nb_vcs; i++) { - vc = &s->vc[i]; + for (i = 0; i < s->vcs->len; i++) { + vc = g_ptr_array_index(s->vcs, i); if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM(vc->menu_item))) { return vc; @@ -165,8 +166,11 @@ static VirtualConsole *gd_vc_find_by_page(GtkDisplayState *s, gint page) VirtualConsole *vc; gint i, p; - for (i = 0; i < s->nb_vcs; i++) { - vc = &s->vc[i]; + if (!s->vcs) { + return NULL; + } + for (i = 0; i < s->vcs->len; i++) { + vc = g_ptr_array_index(s->vcs, i); p = gtk_notebook_page_num(GTK_NOTEBOOK(s->notebook), vc->tab_item); if (p == page) { return vc; @@ -247,8 +251,8 @@ static void gd_update_caption(GtkDisplayState *s) gtk_window_set_title(GTK_WINDOW(s->window), title); g_free(title); - for (i = 0; i < s->nb_vcs; i++) { - VirtualConsole *vc = &s->vc[i]; + for (i = 0; i < s->vcs->len; i++) { + VirtualConsole *vc = g_ptr_array_index(s->vcs, i); if (!vc->window) { continue; @@ -357,7 +361,7 @@ static void gtk_release_modifiers(GtkDisplayState *s) { VirtualConsole *vc = gd_vc_find_current(s); - if (vc->type != GD_VC_GFX || + if (!vc || vc->type != GD_VC_GFX || !qemu_console_is_graphic(vc->gfx.dcl.con)) { return; } @@ -702,8 +706,8 @@ static void gd_mouse_mode_change(Notifier *notify, void *data) gd_ungrab_pointer(s); } } - for (i = 0; i < s->nb_vcs; i++) { - VirtualConsole *vc = &s->vc[i]; + for (i = 0; i < s->vcs->len; i++) { + VirtualConsole *vc = g_ptr_array_index(s->vcs, i); gd_update_cursor(vc); } } @@ -1461,16 +1465,36 @@ static void gd_menu_show_tabs(GtkMenuItem *item, void *opaque) gd_update_windowsize(vc); } +static int gd_vc_notebook_pos(GtkDisplayState *s, VirtualConsole *target) +{ + int pos = 0; + guint i; + + for (i = 0; i < s->vcs->len; i++) { + VirtualConsole *vc = g_ptr_array_index(s->vcs, i); + if (vc == target) { + return pos; + } + if (!vc->window) { + pos++; + } + } + g_assert_not_reached(); +} + static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event, void *opaque) { VirtualConsole *vc = opaque; GtkDisplayState *s = vc->s; - - gtk_widget_set_sensitive(vc->menu_item, true); - gd_widget_reparent(vc->window, s->notebook, vc->tab_item); - gtk_notebook_set_tab_label_text(GTK_NOTEBOOK(s->notebook), - vc->tab_item, vc->label); + int page; + + g_object_ref(vc->tab_item); + gtk_container_remove(GTK_CONTAINER(vc->window), vc->tab_item); + page = gd_vc_notebook_pos(s, vc); + gtk_notebook_insert_page(GTK_NOTEBOOK(s->notebook), + vc->tab_item, gtk_label_new(vc->label), page); + g_object_unref(vc->tab_item); gtk_widget_destroy(vc->window); vc->window = NULL; #if defined(CONFIG_OPENGL) @@ -1484,6 +1508,8 @@ static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event, } #endif + gd_rebuild_vc_menu(s); + if (vc == gd_vc_find_by_menu(s)) { gtk_widget_grab_focus(vc->focus); } @@ -1515,7 +1541,6 @@ static void gd_menu_untabify(GtkMenuItem *item, void *opaque) FALSE); } if (!vc->window) { - gtk_widget_set_sensitive(vc->menu_item, false); vc->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); #if defined(CONFIG_OPENGL) if (vc->gfx.esurface) { @@ -1542,6 +1567,7 @@ static void gd_menu_untabify(GtkMenuItem *item, void *opaque) gtk_accel_group_connect(ag, GDK_KEY_g, HOTKEY_MODIFIERS, 0, cb); } + gd_rebuild_vc_menu(s); gd_update_geometry_hints(vc); gd_update_caption(s); } @@ -1882,22 +1908,73 @@ static gboolean gd_configure(GtkWidget *widget, /** Virtual Console Callbacks **/ -static GSList *gd_vc_menu_init(GtkDisplayState *s, VirtualConsole *vc, - int idx, GSList *group, GtkWidget *view_menu) +static void gd_rebuild_vc_menu(GtkDisplayState *s) { - vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group, vc->label); - gtk_accel_group_connect(s->accel_group, GDK_KEY_1 + idx, - HOTKEY_MODIFIERS, 0, - g_cclosure_new_swap(G_CALLBACK(gd_accel_switch_vc), vc, NULL)); - gtk_accel_label_set_accel( - GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(vc->menu_item))), - GDK_KEY_1 + idx, HOTKEY_MODIFIERS); + GSList *group = NULL; + VirtualConsole *vc; + GList *children; + gint insert_pos; + int shortcut_idx = 0; + guint i; + + for (i = 0; i < s->vcs->len; i++) { + vc = g_ptr_array_index(s->vcs, i); + if (vc->menu_item) { + gtk_widget_destroy(vc->menu_item); + vc->menu_item = NULL; + } + } + + for (i = 0; i < 9; i++) { + gtk_accel_group_disconnect_key(s->accel_group, + GDK_KEY_1 + i, HOTKEY_MODIFIERS); + } + + /* find insertion position (just before vc_menu_separator) */ + children = gtk_container_get_children(GTK_CONTAINER(s->view_menu)); + insert_pos = g_list_index(children, s->vc_menu_separator); + g_list_free(children); + + /* create new menu items for each console */ + for (i = 0; i < s->vcs->len; i++) { + vc = g_ptr_array_index(s->vcs, i); + + vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group, + vc->label); + group = gtk_radio_menu_item_get_group( + GTK_RADIO_MENU_ITEM(vc->menu_item)); + + if (vc->window) { + gtk_widget_set_sensitive(vc->menu_item, false); + } else if (shortcut_idx < 9) { + guint key = GDK_KEY_1 + shortcut_idx; + gtk_accel_group_connect(s->accel_group, key, + HOTKEY_MODIFIERS, 0, + g_cclosure_new_swap(G_CALLBACK(gd_accel_switch_vc), + vc, NULL)); + gtk_accel_label_set_accel( + GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(vc->menu_item))), + key, HOTKEY_MODIFIERS); + shortcut_idx++; + } - g_signal_connect(vc->menu_item, "activate", - G_CALLBACK(gd_menu_switch_vc), s); - gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), vc->menu_item); + g_signal_connect(vc->menu_item, "activate", + G_CALLBACK(gd_menu_switch_vc), s); + gtk_menu_shell_insert(GTK_MENU_SHELL(s->view_menu), + vc->menu_item, insert_pos + i); + gtk_widget_show(vc->menu_item); + } - return gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item)); + /* sync active menu item with current notebook page */ + vc = gd_vc_find_current(s); + if (vc && vc->menu_item) { + g_signal_handlers_block_by_func(vc->menu_item, + gd_menu_switch_vc, s); + gtk_check_menu_item_set_active( + GTK_CHECK_MENU_ITEM(vc->menu_item), TRUE); + g_signal_handlers_unblock_by_func(vc->menu_item, + gd_menu_switch_vc, s); + } } #if defined(CONFIG_VTE) @@ -2040,9 +2117,8 @@ static gboolean gd_vc_in(VteTerminal *terminal, gchar *text, guint size, return TRUE; } -static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc, - Chardev *chr, int idx, - GSList *group, GtkWidget *view_menu) +static void gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc, + Chardev *chr, int idx) { char buffer[32]; GtkWidget *box; @@ -2058,7 +2134,6 @@ static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc, snprintf(buffer, sizeof(buffer), "vc%d", idx); vc->label = g_strdup(vc->vte.chr->label ? : buffer); - group = gd_vc_menu_init(s, vc, idx, group, view_menu); vc->vte.terminal = vte_terminal_new(); g_signal_connect(vc->vte.terminal, "commit", G_CALLBACK(gd_vc_in), vc); @@ -2104,19 +2179,16 @@ static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc, gtk_label_new(vc->label)); qemu_chr_be_event(vc->vte.chr, CHR_EVENT_OPENED); - - return group; } -static void gd_vcs_init(GtkDisplayState *s, GSList *group, - GtkWidget *view_menu) +static void gd_vcs_init(GtkDisplayState *s) { int i; for (i = 0; i < nb_vcs; i++) { - VirtualConsole *vc = &s->vc[s->nb_vcs]; - group = gd_vc_vte_init(s, vc, vcs[i], s->nb_vcs, group, view_menu); - s->nb_vcs++; + VirtualConsole *vc = g_new0(VirtualConsole, 1); + g_ptr_array_add(s->vcs, vc); + gd_vc_vte_init(s, vc, vcs[i], s->vcs->len - 1); } } #endif /* CONFIG_VTE */ @@ -2260,14 +2332,13 @@ static bool gd_scale_valid(double scale) return scale >= VC_SCALE_MIN && scale <= VC_SCALE_MAX; } -static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc, - QemuConsole *con, int idx, - GSList *group, GtkWidget *view_menu) +static VirtualConsole * +add_gfx_console(GtkDisplayState *s, QemuConsole *con) { + VirtualConsole *vc = g_new0(VirtualConsole, 1); const DisplayChangeListenerOps *ops = &dcl_ops; - bool zoom_to_fit = false; - int i; + g_ptr_array_add(s->vcs, vc); vc->label = qemu_console_get_label(con); vc->s = s; vc->gfx.preferred_scale = 1.0; @@ -2344,40 +2415,86 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc, qemu_console_register_listener(con, &vc->gfx.dcl, ops); gd_connect_vc_gfx_signals(vc); - group = gd_vc_menu_init(s, vc, idx, group, view_menu); + return vc; +} - if (qemu_console_ui_info_supported(vc->gfx.dcl.con)) { - zoom_to_fit = true; - } - if (s->opts->u.gtk.has_zoom_to_fit) { - zoom_to_fit = s->opts->u.gtk.zoom_to_fit; +static void gd_vc_add_gfx(GtkDisplayState *s, QemuConsole *con) +{ + VirtualConsole *vc; + int i; + + for (i = 0; i < (int)s->vcs->len; i++) { + VirtualConsole *v = g_ptr_array_index(s->vcs, i); + if (v->type == GD_VC_GFX && v->gfx.dcl.con == con) { + return; + } } - if (zoom_to_fit) { - gtk_menu_item_activate(GTK_MENU_ITEM(s->zoom_fit_item)); - s->free_scale = true; + + vc = add_gfx_console(s, con); + gtk_widget_show_all(vc->tab_item); + gtk_widget_realize(vc->gfx.drawing_area); + + if (s->free_scale) { + gd_update_windowsize(vc); } - s->keep_aspect_ratio = true; - if (s->opts->u.gtk.has_keep_aspect_ratio) - s->keep_aspect_ratio = s->opts->u.gtk.keep_aspect_ratio; + gd_update_caption(s); + gd_rebuild_vc_menu(s); +} - for (i = 0; i < INPUT_EVENT_SLOTS_MAX; i++) { - struct touch_slot *slot = &touch_slots[i]; - slot->tracking_id = -1; +static void gd_vc_remove_gfx(GtkDisplayState *s, QemuConsole *con) +{ + VirtualConsole *vc = NULL; + guint idx; + + for (idx = 0; idx < s->vcs->len; idx++) { + VirtualConsole *v = g_ptr_array_index(s->vcs, idx); + if (v->type == GD_VC_GFX && v->gfx.dcl.con == con) { + vc = v; + break; + } + } + if (!vc) { + return; } - return group; + if (s->kbd_owner == vc) { + gd_ungrab_keyboard(s); + } + if (s->ptr_owner == vc) { + gd_ungrab_pointer(s); + } + + g_ptr_array_remove_index(s->vcs, idx); + gd_rebuild_vc_menu(s); + gd_update_caption(s); } -static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts) +static void gd_console_notify(Notifier *n, void *data) +{ + GtkDisplayState *s = container_of(n, GtkDisplayState, console_notifier); + QemuConsoleEvent *event = data; + + switch (event->type) { + case QEMU_CONSOLE_ADDED: + gd_vc_add_gfx(s, event->con); + break; + case QEMU_CONSOLE_REMOVED: + gd_vc_remove_gfx(s, event->con); + break; + } +} + +static void gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts) { - GSList *group = NULL; GtkWidget *view_menu; GtkWidget *separator; QemuConsole *con; - int vc; + bool zoom_to_fit = false; + int vc, i; view_menu = gtk_menu_new(); + s->view_menu = view_menu; gtk_menu_set_accel_group(GTK_MENU(view_menu), s->accel_group); s->full_screen_item = gtk_menu_item_new_with_mnemonic(_("_Fullscreen")); @@ -2445,18 +2562,39 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts) if (!con) { break; } - group = gd_vc_gfx_init(s, &s->vc[vc], con, - vc, group, view_menu); - s->nb_vcs++; + add_gfx_console(s, con); + if (qemu_console_ui_info_supported(con)) { + zoom_to_fit = true; + } + } + + if (s->opts->u.gtk.has_zoom_to_fit) { + zoom_to_fit = s->opts->u.gtk.zoom_to_fit; + } + if (zoom_to_fit) { + gtk_menu_item_activate(GTK_MENU_ITEM(s->zoom_fit_item)); + s->free_scale = true; + } + + s->keep_aspect_ratio = true; + if (s->opts->u.gtk.has_keep_aspect_ratio) { + s->keep_aspect_ratio = s->opts->u.gtk.keep_aspect_ratio; + } + + for (i = 0; i < INPUT_EVENT_SLOTS_MAX; i++) { + struct touch_slot *slot = &touch_slots[i]; + slot->tracking_id = -1; } #if defined(CONFIG_VTE) /* vte */ - gd_vcs_init(s, group, view_menu); + gd_vcs_init(s); #endif - separator = gtk_separator_menu_item_new(); - gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator); + s->vc_menu_separator = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->vc_menu_separator); + + gd_rebuild_vc_menu(s); s->show_tabs_item = gtk_check_menu_item_new_with_mnemonic(_("Show _Tabs")); gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->show_tabs_item); @@ -2475,8 +2613,6 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts) GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(s->show_menubar_item))), GDK_KEY_m, HOTKEY_MODIFIERS); gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->show_menubar_item); - - return view_menu; } static void gd_create_menus(GtkDisplayState *s, DisplayOptions *opts) @@ -2485,7 +2621,7 @@ static void gd_create_menus(GtkDisplayState *s, DisplayOptions *opts) s->accel_group = gtk_accel_group_new(); s->machine_menu = gd_create_menu_machine(s); - s->view_menu = gd_create_menu_view(s, opts); + gd_create_menu_view(s, opts); s->machine_menu_item = gtk_menu_item_new_with_mnemonic(_("_Machine")); gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->machine_menu_item), @@ -2505,6 +2641,65 @@ static void gd_create_menus(GtkDisplayState *s, DisplayOptions *opts) } +static void gd_vc_free(void *p) +{ + VirtualConsole *vc = p; + + switch (vc->type) { + case GD_VC_GFX: + qemu_console_unregister_listener(&vc->gfx.dcl); +#if defined(CONFIG_OPENGL) + if (display_opengl) { + qemu_console_set_display_gl_ctx(vc->gfx.dcl.con, NULL); + } + if (vc->gfx.ectx) { + eglMakeCurrent(qemu_egl_display, vc->gfx.esurface, + vc->gfx.esurface, vc->gfx.ectx); + } else if (gtk_use_gl_area) { + gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); + } + if (vc->gfx.gls) { + surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds); + qemu_gl_fini_shader(vc->gfx.gls); + } + egl_fb_destroy(&vc->gfx.guest_fb); + egl_fb_destroy(&vc->gfx.win_fb); + egl_fb_destroy(&vc->gfx.cursor_fb); + if (vc->gfx.esurface) { + eglDestroySurface(qemu_egl_display, vc->gfx.esurface); + } + if (vc->gfx.ectx) { + eglDestroyContext(qemu_egl_display, vc->gfx.ectx); + } +#endif + qkbd_state_free(vc->gfx.kbd); + if (vc->gfx.surface) { + cairo_surface_destroy(vc->gfx.surface); + } + if (vc->gfx.convert) { + pixman_image_unref(vc->gfx.convert); + } + break; + case GD_VC_VTE: +#ifdef CONFIG_VTE + fifo8_destroy(&vc->vte.out_fifo); +#endif + break; + } + + if (vc->window) { + gtk_widget_destroy(vc->window); + } else if (vc->tab_item) { + gtk_widget_destroy(vc->tab_item); + } + if (vc->menu_item) { + gtk_widget_destroy(vc->menu_item); + } + g_free(vc->label); + g_free(vc); +} + +static GtkDisplayState *gtk_display_state; static gboolean gtkinit; static void gtk_display_init(DisplayState *ds, DisplayOptions *opts) @@ -2523,6 +2718,8 @@ static void gtk_display_init(DisplayState *ds, DisplayOptions *opts) } assert(opts->type == DISPLAY_TYPE_GTK); s = g_malloc0(sizeof(*s)); + gtk_display_state = s; + s->vcs = g_ptr_array_new_with_free_func(gd_vc_free); s->opts = opts; theme = gtk_icon_theme_get_default(); @@ -2560,12 +2757,15 @@ static void gtk_display_init(DisplayState *ds, DisplayOptions *opts) s->mouse_mode_notifier.notify = gd_mouse_mode_change; qemu_add_mouse_mode_change_notifier(&s->mouse_mode_notifier); - qemu_add_vm_change_state_handler(gd_change_runstate, s); + s->vmse = qemu_add_vm_change_state_handler(gd_change_runstate, s); gtk_window_set_icon_name(GTK_WINDOW(s->window), "qemu"); gd_create_menus(s, opts); + s->console_notifier.notify = gd_console_notify; + qemu_console_add_notifier(&s->console_notifier); + gd_connect_signals(s); gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE); @@ -2580,12 +2780,11 @@ static void gtk_display_init(DisplayState *ds, DisplayOptions *opts) gtk_widget_show_all(s->window); - for (idx = 0;; idx++) { - QemuConsole *con = qemu_console_lookup_by_index(idx); - if (!con) { - break; + for (idx = 0; idx < s->vcs->len; idx++) { + VirtualConsole *v = g_ptr_array_index(s->vcs, idx); + if (v->type == GD_VC_GFX) { + gtk_widget_realize(v->gfx.drawing_area); } - gtk_widget_realize(s->vc[idx].gfx.drawing_area); } if (opts->u.gtk.has_show_menubar && @@ -2679,10 +2878,30 @@ static void early_gtk_display_init(DisplayOptions *opts) #endif } +static void gtk_display_cleanup(void) +{ + GtkDisplayState *s = gtk_display_state; + + if (!s) { + return; + } + qemu_del_vm_change_state_handler(s->vmse); + qemu_console_remove_notifier(&s->console_notifier); + qemu_remove_mouse_mode_change_notifier(&s->mouse_mode_notifier); + gd_clipboard_cleanup(s); + g_signal_handlers_disconnect_by_func(s->notebook, + G_CALLBACK(gd_change_page), s); + g_clear_pointer(&s->vcs, g_ptr_array_unref); + g_clear_pointer(&s->window, gtk_widget_destroy); + g_clear_object(&s->null_cursor); + g_clear_pointer(>k_display_state, g_free); +} + static QemuDisplay qemu_display_gtk = { .type = DISPLAY_TYPE_GTK, .early_init = early_gtk_display_init, .init = gtk_display_init, + .cleanup = gtk_display_cleanup, .vc = "vc", }; diff --git a/ui/meson.build b/ui/meson.build index 0c6a432948..d345f6c70d 100644 --- a/ui/meson.build +++ b/ui/meson.build @@ -42,13 +42,14 @@ libui_sources = files( 'kbd-state.c', 'keymaps.c', 'qemu-pixman.c', + 'spice-module.c', 'vgafont.c', ) if pixman.found() libui_sources += files('cp437.c', 'vt100.c') endif libui = static_library('qemuui', libui_sources + genh, - dependencies: [pixman], + dependencies: [pixman, spice_headers], build_by_default: false) ui = declare_dependency(objects: libui.extract_all_objects(recursive: false), dependencies: [pixman]) system_ss.add(png) @@ -64,7 +65,6 @@ system_ss.add(when: pixman, if_true: files('console-vc.c'), if_false: files('con if dbus_display system_ss.add(files('dbus-module.c')) endif -system_ss.add([spice_headers, files('spice-module.c')]) system_ss.add(when: spice_protocol, if_true: files('vdagent.c')) if host_os == 'linux' @@ -790,9 +790,25 @@ static void sdl_mouse_define(DisplayChangeListener *dcl, static void sdl_cleanup(void) { - if (guest_sprite) { - SDL_FreeCursor(guest_sprite); + int i; + + if (!sdl2_console) { + return; } + + qemu_remove_mouse_mode_change_notifier(&mouse_mode_notifier); + + for (i = 0; i < sdl2_num_outputs; i++) { + qemu_console_unregister_listener(&sdl2_console[i].dcl); + qkbd_state_free(sdl2_console[i].kbd); + sdl2_window_destroy(&sdl2_console[i]); + } + g_clear_pointer(&sdl2_console, g_free); + sdl2_num_outputs = 0; + + g_clear_pointer(&guest_sprite, SDL_FreeCursor); + g_clear_pointer(&guest_sprite_surface, SDL_FreeSurface); + g_clear_pointer(&sdl_cursor_hidden, SDL_FreeCursor); SDL_QuitSubSystem(SDL_INIT_VIDEO); } @@ -998,8 +1014,6 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o) sdl_grab_start(&sdl2_console[0]); } - atexit(sdl_cleanup); - /* SDL's event polling (in dpy_refresh) must happen on the main thread. */ qemu_main = NULL; } @@ -1008,6 +1022,7 @@ static QemuDisplay qemu_display_sdl2 = { .type = DISPLAY_TYPE_SDL, .early_init = sdl2_display_early_init, .init = sdl2_display_init, + .cleanup = sdl_cleanup, }; static void register_sdl1(void) diff --git a/ui/spice-app.c b/ui/spice-app.c index 0df7325e67..fe3df62bfa 100644 --- a/ui/spice-app.c +++ b/ui/spice-app.c @@ -119,16 +119,17 @@ static const TypeInfo char_vc_type_info = { .class_size = sizeof(VCChardevClass), }; -static void spice_app_atexit(void) +static void spice_app_cleanup(void) { if (sock_path) { unlink(sock_path); + g_clear_pointer(&sock_path, g_free); } if (tmp_dir) { rmdir(tmp_dir); + tmp_dir = NULL; } - g_free(sock_path); - g_free(app_dir); + g_clear_pointer(&app_dir, g_free); } static void spice_app_display_early_init(DisplayOptions *opts) @@ -146,8 +147,6 @@ static void spice_app_display_early_init(DisplayOptions *opts) exit(1); } - atexit(spice_app_atexit); - if (qemu_name) { app_dir = g_build_filename(g_get_user_runtime_dir(), "qemu", qemu_name, NULL); @@ -218,6 +217,7 @@ static QemuDisplay qemu_display_spice_app = { .type = DISPLAY_TYPE_SPICE_APP, .early_init = spice_app_display_early_init, .init = spice_app_display_init, + .cleanup = spice_app_cleanup, .vc = "vc", }; diff --git a/ui/spice-core.c b/ui/spice-core.c index ef1c00134f..1d2315f0b6 100644 --- a/ui/spice-core.c +++ b/ui/spice-core.c @@ -651,12 +651,15 @@ static void vm_change_state_handler(void *opaque, bool running, } } +static VMChangeStateEntry *vm_change_entry; + void qemu_spice_display_init_done(void) { if (runstate_is_running()) { qemu_spice_display_start(); } - qemu_add_vm_change_state_handler(vm_change_state_handler, NULL); + vm_change_entry = + qemu_add_vm_change_state_handler(vm_change_state_handler, NULL); } static void qemu_spice_init(void) @@ -894,7 +897,8 @@ static int qemu_spice_add_interface(SpiceBaseInstance *sin) spice_server = spice_server_new(); spice_server_set_sasl_appname(spice_server, "qemu"); spice_server_init(spice_server, &core_interface); - qemu_add_vm_change_state_handler(vm_change_state_handler, NULL); + vm_change_entry = + qemu_add_vm_change_state_handler(vm_change_state_handler, NULL); } return spice_server_add_interface(spice_server, sin); @@ -1005,8 +1009,25 @@ int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd) return spice_display_is_running; } +static void qemu_spice_cleanup(void) +{ + if (!spice_server) { + return; + } + + qemu_spice_display_cleanup(); + qemu_spice_input_cleanup(); + migration_remove_notifier(&migration_state); + g_clear_pointer(&spice_consoles, g_slist_free); + g_clear_pointer(&auth_passwd, g_free); + g_clear_pointer(&spice_server, spice_server_destroy); + g_clear_pointer(&vm_change_entry, qemu_del_vm_change_state_handler); + using_spice = 0; +} + static struct QemuSpiceOps real_spice_ops = { .init = qemu_spice_init, + .cleanup = qemu_spice_cleanup, .display_init = qemu_spice_display_init, .migrate_info = qemu_spice_migrate_info, .set_passwd = qemu_spice_set_passwd, diff --git a/ui/spice-display.c b/ui/spice-display.c index e371612720..75c7df7bb5 100644 --- a/ui/spice-display.c +++ b/ui/spice-display.c @@ -34,6 +34,8 @@ bool spice_opengl; bool spice_remote_client; int spice_max_refresh_rate; +static GPtrArray *spice_displays; + int qemu_spice_rect_is_empty(const QXLRect* r) { return r->top == r->bottom || r->left == r->right; @@ -1421,6 +1423,54 @@ static void qemu_spice_display_init_one(QemuConsole *con) qemu_console_set_display_gl_ctx(con, &ssd->dgc); } qemu_console_register_listener(con, &ssd->dcl, ops); + g_ptr_array_add(spice_displays, ssd); +} + +void qemu_spice_display_cleanup(void) +{ + if (!spice_displays) { + return; + } + + for (guint i = 0; i < spice_displays->len; i++) { + SimpleSpiceDisplay *ssd = g_ptr_array_index(spice_displays, i); + SimpleSpiceUpdate *update; + + qemu_console_unregister_listener(&ssd->dcl); +#ifdef HAVE_SPICE_GL + if (spice_opengl) { + qemu_console_set_display_gl_ctx(ssd->dcl.con, NULL); + } +#endif + + if (ssd->ds) { + qemu_spice_destroy_host_primary(ssd); + } + qemu_spice_del_memslot(ssd, MEMSLOT_GROUP_HOST, 0); + spice_server_remove_interface(&ssd->qxl.base); + + while ((update = QTAILQ_FIRST(&ssd->updates)) != NULL) { + QTAILQ_REMOVE(&ssd->updates, update, next); + qemu_spice_destroy_update(ssd, update); + } + g_clear_pointer(&ssd->ptr_define, g_free); + g_clear_pointer(&ssd->ptr_move, g_free); + g_clear_pointer(&ssd->cursor, cursor_unref); + g_clear_pointer(&ssd->surface, pixman_image_unref); + g_clear_pointer(&ssd->mirror, pixman_image_unref); + g_clear_pointer(&ssd->buf, g_free); +#ifdef HAVE_SPICE_GL + g_clear_pointer(&ssd->gl_unblock_bh, qemu_bh_delete); + g_clear_pointer(&ssd->gl_unblock_timer, timer_free); + g_clear_pointer(&ssd->gls, qemu_gl_fini_shader); + egl_fb_destroy(&ssd->guest_fb); + egl_fb_destroy(&ssd->blit_fb); + egl_fb_destroy(&ssd->cursor_fb); +#endif + qemu_mutex_destroy(&ssd->lock); + g_free(ssd); + } + g_clear_pointer(&spice_displays, g_ptr_array_unref); } void qemu_spice_display_init(void) @@ -1431,6 +1481,8 @@ void qemu_spice_display_init(void) const char *str; int i; + spice_displays = g_ptr_array_new(); + str = qemu_opt_get(opts, "display"); if (str) { int head = qemu_opt_get_number(opts, "head", 0); diff --git a/ui/spice-input.c b/ui/spice-input.c index 845abc7387..34f1b03867 100644 --- a/ui/spice-input.c +++ b/ui/spice-input.c @@ -242,24 +242,41 @@ static void mouse_mode_notifier(Notifier *notifier, void *data) pointer->absolute = is_absolute; } +static QemuSpiceKbd *spice_kbd; +static QemuSpicePointer *spice_pointer; + void qemu_spice_input_init(void) { - QemuSpiceKbd *kbd; - QemuSpicePointer *pointer; - - kbd = g_malloc0(sizeof(*kbd)); - kbd->sin.base.sif = &kbd_interface.base; - qemu_spice.add_interface(&kbd->sin.base); - kbd->led_notifier.notify = kbd_leds; - qemu_input_led_notifier_add(&kbd->led_notifier); - - pointer = g_malloc0(sizeof(*pointer)); - pointer->mouse.base.sif = &mouse_interface.base; - pointer->tablet.base.sif = &tablet_interface.base; - qemu_spice.add_interface(&pointer->mouse.base); - - pointer->absolute = false; - pointer->mouse_mode.notify = mouse_mode_notifier; - qemu_add_mouse_mode_change_notifier(&pointer->mouse_mode); - mouse_mode_notifier(&pointer->mouse_mode, NULL); + spice_kbd = g_new0(QemuSpiceKbd, 1); + spice_kbd->sin.base.sif = &kbd_interface.base; + qemu_spice.add_interface(&spice_kbd->sin.base); + spice_kbd->led_notifier.notify = kbd_leds; + qemu_input_led_notifier_add(&spice_kbd->led_notifier); + + spice_pointer = g_new0(QemuSpicePointer, 1); + spice_pointer->mouse.base.sif = &mouse_interface.base; + spice_pointer->tablet.base.sif = &tablet_interface.base; + qemu_spice.add_interface(&spice_pointer->mouse.base); + + spice_pointer->absolute = false; + spice_pointer->mouse_mode.notify = mouse_mode_notifier; + qemu_add_mouse_mode_change_notifier(&spice_pointer->mouse_mode); + mouse_mode_notifier(&spice_pointer->mouse_mode, NULL); +} + +void qemu_spice_input_cleanup(void) +{ + if (spice_pointer) { + qemu_remove_mouse_mode_change_notifier(&spice_pointer->mouse_mode); + if (spice_pointer->absolute) { + spice_server_remove_interface(&spice_pointer->tablet.base); + } + spice_server_remove_interface(&spice_pointer->mouse.base); + g_clear_pointer(&spice_pointer, g_free); + } + if (spice_kbd) { + qemu_input_led_notifier_remove(&spice_kbd->led_notifier); + spice_server_remove_interface(&spice_kbd->sin.base); + g_clear_pointer(&spice_kbd, g_free); + } } diff --git a/ui/spice-module.c b/ui/spice-module.c index 7651c85885..1961060d12 100644 --- a/ui/spice-module.c +++ b/ui/spice-module.c @@ -62,6 +62,10 @@ static int qemu_spice_display_add_client_stub(int csock, int skipauth, return -1; } +static void qemu_spice_cleanup_stub(void) +{ +} + struct QemuSpiceOps qemu_spice = { .init = qemu_spice_init_stub, .display_init = qemu_spice_display_init_stub, @@ -69,6 +73,7 @@ struct QemuSpiceOps qemu_spice = { .set_passwd = qemu_spice_set_passwd_stub, .set_pw_expire = qemu_spice_set_pw_expire_stub, .display_add_client = qemu_spice_display_add_client_stub, + .cleanup = qemu_spice_cleanup_stub, }; #ifdef CONFIG_SPICE diff --git a/ui/ui-qmp-cmds.c b/ui/ui-qmp-cmds.c index 1173c82cf7..753cc2bf52 100644 --- a/ui/ui-qmp-cmds.c +++ b/ui/ui-qmp-cmds.c @@ -348,6 +348,7 @@ qmp_screendump(const char *filename, const char *device, } } + object_ref(con); qemu_console_co_wait_update(con); /* @@ -358,9 +359,11 @@ qmp_screendump(const char *filename, const char *device, surface = qemu_console_surface(con); if (!surface) { error_setg(errp, "no surface"); + object_unref(con); return; } image = pixman_image_ref(surface->image); + object_unref(con); fd = qemu_create(filename, O_WRONLY | O_TRUNC | O_BINARY, 0666, errp); if (fd == -1) { diff --git a/util/notify.c b/util/notify.c index c6e158ffb3..24420a7288 100644 --- a/util/notify.c +++ b/util/notify.c @@ -28,7 +28,7 @@ void notifier_list_add(NotifierList *list, Notifier *notifier) void notifier_remove(Notifier *notifier) { - QLIST_REMOVE(notifier, node); + QLIST_SAFE_REMOVE(notifier, node); } void notifier_list_notify(NotifierList *list, void *data) diff --git a/util/thread-pool.c b/util/thread-pool.c index 8f8cb38d5c..4e75191c98 100644 --- a/util/thread-pool.c +++ b/util/thread-pool.c @@ -493,5 +493,5 @@ bool thread_pool_adjust_max_threads_to_work(ThreadPool *pool) { QEMU_LOCK_GUARD(&pool->cur_work_lock); - return thread_pool_set_max_threads(pool, pool->cur_work); + return thread_pool_set_max_threads(pool, MAX(pool->cur_work, 1)); } |
