summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.d/base.yml53
-rw-r--r--.gitlab-ci.d/buildtest-template.yml73
-rw-r--r--.gitlab-ci.d/crossbuild-template.yml59
-rw-r--r--.gitlab-ci.d/macos.yml9
-rw-r--r--.gitlab/issue_templates/bug.md11
-rw-r--r--.mailmap3
-rw-r--r--MAINTAINERS61
-rw-r--r--accel/accel-irq.c51
-rw-r--r--accel/kvm/kvm-all.c8
-rw-r--r--accel/mshv/irq.c360
-rw-r--r--accel/mshv/mem.c211
-rw-r--r--accel/mshv/meson.build1
-rw-r--r--accel/mshv/mshv-all.c161
-rw-r--r--accel/mshv/msr.c375
-rw-r--r--accel/stubs/kvm-stub.c2
-rw-r--r--accel/stubs/mshv-stub.c7
-rw-r--r--bsd-user/aarch64/target_arch_elf.h2
-rw-r--r--configs/devices/hexagon-softmmu/default.mak7
-rw-r--r--configs/meson/emscripten.txt5
-rw-r--r--configs/targets/hexagon-softmmu.mak8
-rw-r--r--docs/about/deprecated.rst16
-rw-r--r--docs/devel/hexagon-sys.rst112
-rw-r--r--docs/devel/index-internals.rst2
-rw-r--r--docs/devel/testing/ci-jobs.rst.inc7
-rw-r--r--docs/devel/vfio-mdpy.rst90
-rw-r--r--docs/system/arm/emulation.rst4
-rw-r--r--docs/system/arm/sabrelite.rst1
-rw-r--r--docs/system/arm/virt.rst26
-rw-r--r--docs/system/devices/can.rst24
-rw-r--r--docs/system/hexagon/cdsp.rst12
-rw-r--r--docs/system/hexagon/emulation.rst15
-rw-r--r--docs/system/introduction.rst9
-rw-r--r--docs/system/ppc/pseries.rst2
-rw-r--r--docs/system/riscv/tt_atlantis.rst41
-rw-r--r--docs/system/target-hexagon.rst103
-rw-r--r--docs/system/target-riscv.rst1
-rw-r--r--docs/system/targets.rst1
-rw-r--r--dump/dump.c8
-rw-r--r--dump/win_dump-x86.c49
-rw-r--r--fsdev/file-op-9p.h11
-rw-r--r--fsdev/qemu-fsdev-opts.c6
-rw-r--r--fsdev/qemu-fsdev.c2
-rw-r--r--hw/9pfs/9p-local.c14
-rw-r--r--hw/9pfs/9p-synth.c51
-rw-r--r--hw/9pfs/9p.c113
-rw-r--r--hw/9pfs/9p.h2
-rw-r--r--hw/9pfs/virtio-9p-device.c17
-rw-r--r--hw/9pfs/xen-9p-backend.c31
-rw-r--r--hw/Kconfig1
-rw-r--r--hw/arm/Kconfig1
-rw-r--r--hw/arm/fsl-imx6.c29
-rw-r--r--hw/arm/sabrelite.c68
-rw-r--r--hw/core/irq.c12
-rw-r--r--hw/core/machine.c2
-rw-r--r--hw/display/vga.c25
-rw-r--r--hw/display/vga_int.h2
-rw-r--r--hw/hexagon/Kconfig14
-rw-r--r--hw/hexagon/hexagon_dsp.c225
-rw-r--r--hw/hexagon/hexagon_globalreg.c316
-rw-r--r--hw/hexagon/hexagon_tlb.c467
-rw-r--r--hw/hexagon/machine_cfg_sa8775_cdsp0.h.inc64
-rw-r--r--hw/hexagon/machine_cfg_v66g_1024.h.inc64
-rw-r--r--hw/hexagon/machine_cfg_v68n_1024.h.inc64
-rw-r--r--hw/hexagon/meson.build7
-rw-r--r--hw/hexagon/trace-events3
-rw-r--r--hw/hexagon/trace.h2
-rw-r--r--hw/hexagon/virt.c347
-rw-r--r--hw/i2c/Kconfig5
-rw-r--r--hw/i2c/designware_i2c.c745
-rw-r--r--hw/i2c/meson.build1
-rw-r--r--hw/intc/arm_gicv5.c11
-rw-r--r--hw/intc/riscv_aplic.c4
-rw-r--r--hw/intc/riscv_imsic.c4
-rw-r--r--hw/meson.build1
-rw-r--r--hw/misc/imx6_ccm.c24
-rw-r--r--hw/misc/ivshmem-pci.c8
-rw-r--r--hw/misc/trace-events2
-rw-r--r--hw/net/Kconfig5
-rw-r--r--hw/net/can/flexcan.c1396
-rw-r--r--hw/net/can/flexcan_regs.h197
-rw-r--r--hw/net/can/meson.build1
-rw-r--r--hw/net/can/trace-events18
-rw-r--r--hw/pci-host/meson.build2
-rw-r--r--hw/pci-host/pnv_phb4.c6
-rw-r--r--hw/pci-host/pnv_phb4_pec.c58
-rw-r--r--hw/pci-host/pnv_phb5.c23
-rw-r--r--hw/pci-host/pnv_phb5_pec.c77
-rw-r--r--hw/ppc/pnv.c10
-rw-r--r--hw/ppc/ppc.c5
-rw-r--r--hw/riscv/Kconfig13
-rw-r--r--hw/riscv/aia.c94
-rw-r--r--hw/riscv/aia.h26
-rw-r--r--hw/riscv/boot.c34
-rw-r--r--hw/riscv/fdt-common.c30
-rw-r--r--hw/riscv/k230.c8
-rw-r--r--hw/riscv/meson.build2
-rw-r--r--hw/riscv/microchip_pfsoc.c8
-rw-r--r--hw/riscv/opentitan.c6
-rw-r--r--hw/riscv/riscv-iommu-bits.h4
-rw-r--r--hw/riscv/riscv-iommu-hpm.c4
-rw-r--r--hw/riscv/riscv-iommu.c73
-rw-r--r--hw/riscv/riscv-iommu.h26
-rw-r--r--hw/riscv/shakti_c.c6
-rw-r--r--hw/riscv/sifive_u.c48
-rw-r--r--hw/riscv/spike.c6
-rw-r--r--hw/riscv/tt_atlantis.c617
-rw-r--r--hw/riscv/virt-acpi-build.c29
-rw-r--r--hw/riscv/virt.c148
-rw-r--r--hw/riscv/xiangshan_kmh.c6
-rw-r--r--hw/timer/imx_epit.c25
-rw-r--r--hw/timer/trace-events6
-rw-r--r--hw/ufs/ufs.c46
-rw-r--r--hw/vfio/cpr-legacy.c4
-rw-r--r--hw/vfio/display.c29
-rw-r--r--hw/vfio/listener.c10
-rw-r--r--hw/vfio/pci.c13
-rw-r--r--hw/vfio/pci.h1
-rw-r--r--hw/virtio/virtio-mem.c259
-rw-r--r--hw/virtio/virtio-pci.c3
-rw-r--r--include/accel/accel-route.h17
-rw-r--r--include/block/ufs.h18
-rw-r--r--include/glib-compat.h29
-rw-r--r--include/hw/arm/fsl-imx6.h6
-rw-r--r--include/hw/core/irq.h6
-rw-r--r--include/hw/hexagon/hexagon.h161
-rw-r--r--include/hw/hexagon/hexagon_globalreg.h55
-rw-r--r--include/hw/hexagon/hexagon_tlb.h46
-rw-r--r--include/hw/hexagon/virt.h30
-rw-r--r--include/hw/hyperv/hvgdk_mini.h133
-rw-r--r--include/hw/hyperv/hvhdk.h195
-rw-r--r--include/hw/i2c/designware_i2c.h56
-rw-r--r--include/hw/intc/arm_gicv5_types.h4
-rw-r--r--include/hw/misc/imx6_ccm.h4
-rw-r--r--include/hw/misc/imx_ccm.h1
-rw-r--r--include/hw/net/flexcan.h145
-rw-r--r--include/hw/riscv/boot.h12
-rw-r--r--include/hw/riscv/fdt-common.h5
-rw-r--r--include/hw/riscv/sifive_u.h2
-rw-r--r--include/hw/riscv/tt_atlantis.h64
-rw-r--r--include/hw/riscv/virt.h2
-rw-r--r--include/hw/vfio/vfio-container.h2
-rw-r--r--include/hw/vfio/vfio-cpr.h2
-rw-r--r--include/hw/virtio/virtio-mem.h3
-rw-r--r--include/migration/misc.h3
-rw-r--r--include/qobject/json-parser.h16
-rw-r--r--include/system/accel-irq.h6
-rw-r--r--include/system/kvm.h23
-rw-r--r--include/system/memory.h282
-rw-r--r--include/system/mshv.h18
-rw-r--r--include/system/mshv_int.h72
-rw-r--r--include/system/ram-discard-manager.h358
-rw-r--r--include/system/ramblock.h5
-rw-r--r--include/ui/console.h20
-rw-r--r--include/ui/egl-helpers.h1
-rw-r--r--include/ui/gtk.h7
-rw-r--r--include/ui/qemu-spice-module.h1
-rw-r--r--include/ui/qemu-spice.h9
-rw-r--r--linux-user/aarch64/elfload.c5
-rw-r--r--linux-user/hexagon/cpu_loop.c23
-rw-r--r--meson.build3
-rw-r--r--migration/migration.c13
-rw-r--r--migration/migration.h9
-rw-r--r--migration/ram.c16
-rw-r--r--pc-bios/README2
-rw-r--r--pc-bios/slof.binbin994176 -> 997576 bytes
-rw-r--r--python/qemu/machine/machine.py32
-rw-r--r--qapi/migration.json4
-rw-r--r--qemu-options.hx80
-rw-r--r--qobject/json-lexer.c11
-rw-r--r--qobject/json-parser-int.h13
-rw-r--r--qobject/json-parser.c587
-rw-r--r--qobject/json-streamer.c121
-rw-r--r--qobject/json-writer.c5
m---------roms/SLOF0
-rw-r--r--rust/bindings/system-sys/lib.rs2
-rw-r--r--scripts/lsan_suppressions.txt15
-rw-r--r--system/cpus.c11
-rw-r--r--system/memory.c88
-rw-r--r--system/memory_mapping.c4
-rw-r--r--system/meson.build1
-rw-r--r--system/physmem.c43
-rw-r--r--system/qdev-monitor.c2
-rw-r--r--system/qtest.c5
-rw-r--r--system/ram-block-attributes.c255
-rw-r--r--system/ram-discard-manager.c612
-rw-r--r--system/runstate.c6
-rw-r--r--system/trace-events2
-rw-r--r--system/vl.c7
-rw-r--r--target/Kconfig1
-rw-r--r--target/arm/cpu-features.h28
-rw-r--r--target/arm/cpu.c4
-rw-r--r--target/arm/cpu.h7
-rw-r--r--target/arm/helper.c52
-rw-r--r--target/arm/ptw.c77
-rw-r--r--target/arm/tcg/cpu64.c7
-rw-r--r--target/arm/tcg/fp8_helper.c116
-rw-r--r--target/arm/tcg/gicv5-cpuif.c29
-rw-r--r--target/arm/tcg/helper-fp8-defs.h6
-rw-r--r--target/arm/tcg/helper-sve-defs.h10
-rw-r--r--target/arm/tcg/sme.decode95
-rw-r--r--target/arm/tcg/sme_helper.c33
-rw-r--r--target/arm/tcg/sve.decode6
-rw-r--r--target/arm/tcg/sve_helper.c150
-rw-r--r--target/arm/tcg/translate-sme.c148
-rw-r--r--target/arm/tcg/translate-sve.c107
-rw-r--r--target/arm/tcg/vec_internal.h31
-rw-r--r--target/hexagon/Kconfig2
-rw-r--r--target/hexagon/arch.c5
-rw-r--r--target/hexagon/attribs_def.h.inc35
-rw-r--r--target/hexagon/cpu-param.h2
-rw-r--r--target/hexagon/cpu.c422
-rw-r--r--target/hexagon/cpu.h89
-rw-r--r--target/hexagon/cpu_bits.h75
-rw-r--r--target/hexagon/cpu_helper.c399
-rw-r--r--target/hexagon/cpu_helper.h26
-rw-r--r--target/hexagon/decode.c14
-rwxr-xr-xtarget/hexagon/gen_analyze_funcs.py14
-rwxr-xr-xtarget/hexagon/gen_helper_funcs.py24
-rwxr-xr-xtarget/hexagon/gen_helper_protos.py23
-rw-r--r--target/hexagon/gen_idef_parser_funcs.py2
-rwxr-xr-xtarget/hexagon/gen_op_attribs.py2
-rwxr-xr-xtarget/hexagon/gen_opcodes_def.py5
-rw-r--r--target/hexagon/gen_tcg.h16
-rwxr-xr-xtarget/hexagon/gen_tcg_funcs.py35
-rw-r--r--target/hexagon/gen_tcg_sys.h139
-rw-r--r--target/hexagon/genptr.c173
-rw-r--r--target/hexagon/helper.h27
-rwxr-xr-xtarget/hexagon/hex_common.py184
-rw-r--r--target/hexagon/hex_interrupts.c371
-rw-r--r--target/hexagon/hex_interrupts.h15
-rw-r--r--target/hexagon/hex_mmu.c270
-rw-r--r--target/hexagon/hex_mmu.h26
-rw-r--r--target/hexagon/hex_regs.h117
-rw-r--r--target/hexagon/hexswi.c271
-rw-r--r--target/hexagon/hexswi.h17
-rw-r--r--target/hexagon/idef-parser/idef-parser.y5
-rw-r--r--target/hexagon/idef-parser/parser-helpers.c9
-rw-r--r--target/hexagon/idef-parser/parser-helpers.h2
-rw-r--r--target/hexagon/imported/encode_pp.def129
-rw-r--r--target/hexagon/imported/ldst.idef3
-rw-r--r--[-rwxr-xr-x]target/hexagon/imported/macros.def482
-rw-r--r--target/hexagon/imported/system.idef243
-rw-r--r--target/hexagon/internal.h22
-rw-r--r--target/hexagon/machine.c32
-rw-r--r--target/hexagon/macros.h38
-rw-r--r--target/hexagon/meson.build30
-rw-r--r--target/hexagon/op_helper.c576
-rw-r--r--target/hexagon/reg_fields_def.h.inc107
-rw-r--r--target/hexagon/sys_macros.h252
-rw-r--r--target/hexagon/translate.c265
-rw-r--r--target/hexagon/translate.h47
-rw-r--r--target/i386/cpu.c8
-rw-r--r--target/i386/cpu.h9
-rw-r--r--target/i386/kvm/kvm.c5
-rw-r--r--target/i386/kvm/tdx.c30
-rw-r--r--target/i386/meson.build2
-rw-r--r--target/i386/mshv/meson.build1
-rw-r--r--target/i386/mshv/mshv-cpu.c1119
-rw-r--r--target/i386/mshv/msr.c425
-rw-r--r--target/i386/sev.c4
-rw-r--r--target/ppc/cpu.h1
-rw-r--r--target/ppc/cpu_init.c5
-rw-r--r--target/riscv/cpu.c43
-rw-r--r--target/riscv/cpu.h39
-rw-r--r--target/riscv/cpu_helper.c8
-rw-r--r--target/riscv/csr.c48
-rw-r--r--target/riscv/debug.c46
-rw-r--r--target/riscv/debug.h1
-rw-r--r--target/riscv/kvm/kvm-cpu.c19
-rw-r--r--target/riscv/machine.c16
-rw-r--r--target/s390x/kvm/kvm.c9
-rw-r--r--tests/docker/dockerfiles/emsdk-wasm64-cross.docker4
-rwxr-xr-xtests/functional/ppc64/test_powernv.py19
-rw-r--r--tests/functional/pylintrc1
-rw-r--r--tests/functional/riscv64/meson.build1
-rwxr-xr-xtests/functional/riscv64/test_opensbi.py4
-rwxr-xr-xtests/functional/riscv64/test_tt_atlantis.py57
-rwxr-xr-xtests/functional/riscv64/test_tuxrun.py11
-rw-r--r--tests/qemu-iotests/testenv.py1
-rw-r--r--tests/qtest/boot-serial-test.c8
-rw-r--r--tests/qtest/dbus-display-test.c101
-rw-r--r--tests/qtest/dump-test.c331
-rw-r--r--tests/qtest/flexcan-test.c421
-rw-r--r--tests/qtest/libqos/virtio-9p-client.c124
-rw-r--r--tests/qtest/libqos/virtio-9p-client.h88
-rw-r--r--tests/qtest/libqos/virtio-9p.c6
-rw-r--r--tests/qtest/libqos/virtio-9p.h6
-rw-r--r--tests/qtest/meson.build7
-rw-r--r--tests/qtest/migration/Makefile4
-rw-r--r--tests/qtest/migration/aarch64/a-b-kernel.h2
-rw-r--r--tests/qtest/migration/bootfile.c4
-rw-r--r--tests/qtest/migration/bootfile.h4
-rw-r--r--tests/qtest/migration/framework.c6
-rw-r--r--tests/qtest/migration/i386/a-b-bootblock.h2
-rw-r--r--tests/qtest/migration/loongarch64/Makefile20
-rw-r--r--tests/qtest/migration/loongarch64/a-b-kernel.S46
-rw-r--r--tests/qtest/migration/loongarch64/a-b-kernel.h20
-rw-r--r--tests/qtest/migration/ppc64/a-b-kernel.h2
-rw-r--r--tests/qtest/migration/s390x/a-b-bios.h272
-rw-r--r--tests/qtest/virtio-9p-test.c262
-rw-r--r--tests/tcg/aarch64/Makefile.softmmu-target22
-rw-r--r--tests/tcg/aarch64/system/boot.S65
-rw-r--r--tests/tcg/aarch64/system/boot.h14
-rw-r--r--tests/tcg/aarch64/system/gpc-test.c165
-rw-r--r--tests/unit/meson.build8
-rw-r--r--tests/unit/test-ram-discard-manager-stubs.c48
-rw-r--r--tests/unit/test-ram-discard-manager.c1235
-rw-r--r--ui/cocoa.m19
-rw-r--r--ui/console-vc.c13
-rw-r--r--ui/console.c77
-rw-r--r--ui/curses.c17
-rw-r--r--ui/dbus-console.c6
-rw-r--r--ui/dbus.c106
-rw-r--r--ui/dbus.h3
-rw-r--r--ui/egl-headless.c31
-rw-r--r--ui/egl-helpers.c19
-rw-r--r--ui/gtk-clipboard.c15
-rw-r--r--ui/gtk.c375
-rw-r--r--ui/meson.build4
-rw-r--r--ui/sdl2.c23
-rw-r--r--ui/spice-app.c10
-rw-r--r--ui/spice-core.c25
-rw-r--r--ui/spice-display.c52
-rw-r--r--ui/spice-input.c53
-rw-r--r--ui/spice-module.c5
-rw-r--r--ui/ui-qmp-cmds.c3
-rw-r--r--util/notify.c2
-rw-r--r--util/thread-pool.c2
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
diff --git a/.mailmap b/.mailmap
index 34c092714a..8ade2eed66 100644
--- a/.mailmap
+++ b/.mailmap
@@ -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)&in;
+
+ 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)&in;
+
+ 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)&in;
+ 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)&in;
+ 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, &reg_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
index 39b9248967..6a6d944b07 100644
--- a/pc-bios/slof.bin
+++ b/pc-bios/slof.bin
Binary files differ
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, &section, 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, &section, 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, &section, 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, &regs->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, &regs->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, &section));
+
+ /* 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, &section));
+
+ /* Check larger section */
+ section.size = int128_make64(GRANULARITY_4K * 4);
+ g_assert_true(ram_discard_manager_is_populated(rdm, &section));
+
+ /* Check section that spans populated and discarded */
+ section.size = int128_make64(GRANULARITY_4K * 8);
+ g_assert_false(ram_discard_manager_is_populated(rdm, &section));
+
+ memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
+
+ g_assert_true(ram_discard_manager_is_populated(rdm, &section));
+
+ 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, &section);
+
+ /* 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, &section));
+
+ /* Populate in src1 only */
+ test_source_populate(src1, 0, GRANULARITY_4K);
+ g_assert_false(ram_discard_manager_is_populated(rdm, &section));
+
+ /* 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, &section));
+
+ /* Populate in both -> aggregated populated */
+ test_source_populate(src1, 0, GRANULARITY_4K);
+ g_assert_true(ram_discard_manager_is_populated(rdm, &section));
+
+ /* 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, &section));
+
+ /* 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, &section));
+
+ /* 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, &section));
+
+ /* 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, &section));
+
+ /* Populate it in src_4k */
+ test_source_populate(src_4k, GRANULARITY_4K, GRANULARITY_4K);
+ g_assert_true(ram_discard_manager_is_populated(rdm, &section));
+
+ 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, &section);
+
+ /* 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, &section);
+
+ /* 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, &section);
+
+ /* 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, &section);
+
+ /* 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, &section);
+ ram_discard_manager_register_listener(rdm, &tl1.rdl, &section);
+
+ /* 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, &section);
+
+ /* 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, &section);
+
+ /* 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, &section);
+
+ /* 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, &section1);
+
+ /* 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, &section2);
+
+ /* 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, &section1);
+
+ /* 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, &section2);
+
+ /* 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, &section);
+
+ /* 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, &section,
+ 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)
{
diff --git a/ui/dbus.c b/ui/dbus.c
index e02a94df2f..7be0f8e261 100644
--- a/ui/dbus.c
+++ b/ui/dbus.c
@@ -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",
};
diff --git a/ui/dbus.h b/ui/dbus.h
index e4e78590b4..d2cc176648 100644
--- a/ui/dbus.h
+++ b/ui/dbus.h
@@ -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;
+}
diff --git a/ui/gtk.c b/ui/gtk.c
index 4f706c6bbb..7078d89d67 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -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(&gtk_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'
diff --git a/ui/sdl2.c b/ui/sdl2.c
index c75f750e00..1c97d23a47 100644
--- a/ui/sdl2.c
+++ b/ui/sdl2.c
@@ -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));
}