diff --git a/.gitlab-ci.d/base.yml b/.gitlab-ci.d/base.yml index 921c5620008c..7640a1d52ca4 100644 --- a/.gitlab-ci.d/base.yml +++ b/.gitlab-ci.d/base.yml @@ -28,6 +28,9 @@ variables: # we don't need. The --filter options avoid blobs and tree references we aren't going to use # and we also avoid fetching tags. GIT_FETCH_EXTRA_FLAGS: --filter=blob:none --filter=tree:0 --no-tags --prune --quiet + # Ensure docker.py / tests/docker/Makefile.include always displays stdout + # from any docker commands to aid debugging of failures + DOCKER_V: 1 interruptible: true diff --git a/.gitlab-ci.d/buildtest.yml b/.gitlab-ci.d/buildtest.yml index e9b5b05e6e8c..4b1949a3a525 100644 --- a/.gitlab-ci.d/buildtest.yml +++ b/.gitlab-ci.d/buildtest.yml @@ -40,7 +40,7 @@ build-system-ubuntu: variables: IMAGE: ubuntu2204 CONFIGURE_ARGS: --enable-docs --enable-rust - TARGETS: alpha-softmmu microblazeel-softmmu mips64el-softmmu + TARGETS: alpha-softmmu microblaze-softmmu mips64el-softmmu MAKE_CHECK_ARGS: check-build check-system-ubuntu: @@ -101,8 +101,7 @@ crash-test-debian: IMAGE: debian script: - cd build - - make NINJA=":" check-venv - - pyvenv/bin/python3 scripts/device-crash-test -q --tcg-only ./qemu-system-i386 + - ./run scripts/device-crash-test -q --tcg-only ./qemu-system-i386 build-system-fedora: extends: @@ -113,7 +112,7 @@ build-system-fedora: variables: IMAGE: fedora CONFIGURE_ARGS: --disable-gcrypt --enable-nettle --enable-docs --enable-crypto-afalg --enable-rust - TARGETS: microblaze-softmmu mips-softmmu + TARGETS: mips-softmmu xtensa-softmmu m68k-softmmu riscv32-softmmu ppc-softmmu sparc64-softmmu MAKE_CHECK_ARGS: check-build check-doc @@ -158,9 +157,8 @@ crash-test-fedora: IMAGE: fedora script: - cd build - - make NINJA=":" check-venv - - pyvenv/bin/python3 scripts/device-crash-test -q ./qemu-system-ppc - - pyvenv/bin/python3 scripts/device-crash-test -q ./qemu-system-riscv32 + - ./run scripts/device-crash-test -q ./qemu-system-ppc + - ./run scripts/device-crash-test -q ./qemu-system-riscv32 build-system-centos: extends: @@ -350,15 +348,15 @@ build-tcg-disabled: - make -j"$JOBS" - make check-unit - make check-qapi-schema - - cd tests/qemu-iotests/ - - ./check -raw 001 002 003 004 005 008 009 010 011 012 021 025 032 033 048 - 052 063 077 086 101 104 106 113 148 150 151 152 157 159 160 163 - 170 171 184 192 194 208 221 226 227 236 253 277 image-fleecing - - ./check -qcow2 028 051 056 057 058 065 068 082 085 091 095 096 102 122 - 124 132 139 142 144 145 151 152 155 157 165 194 196 200 202 - 208 209 216 218 227 234 246 247 248 250 254 255 257 258 - 260 261 262 263 264 270 272 273 277 279 image-fleecing - - cd ../.. + - ./run tests/qemu-iotests/check -raw 001 002 003 004 005 008 009 + 010 011 012 021 025 032 033 048 052 063 077 086 101 104 106 + 113 148 150 151 152 157 159 160 163 170 171 184 192 194 208 + 221 226 227 236 253 277 image-fleecing + - ./run tests/qemu-iotests/check -qcow2 028 051 056 057 058 065 068 + 082 085 091 095 096 102 122 124 132 139 142 144 145 151 152 + 155 157 165 194 196 200 202 208 209 216 218 227 234 246 247 + 248 250 254 255 257 258 260 261 262 263 264 270 272 273 277 + 279 image-fleecing - make distclean build-user: @@ -378,18 +376,6 @@ build-user-static: variables: IMAGE: debian-all-test-cross CONFIGURE_ARGS: --disable-tools --disable-system --static - --target-list-exclude=alpha-linux-user,sh4-linux-user - MAKE_CHECK_ARGS: check-tcg - -# targets stuck on older compilers -build-legacy: - extends: .native_build_job_template - needs: - - job: amd64-debian-legacy-cross-container - variables: - IMAGE: debian-legacy-test-cross - TARGETS: alpha-linux-user alpha-softmmu sh4-linux-user - CONFIGURE_ARGS: --disable-tools MAKE_CHECK_ARGS: check-tcg build-user-hexagon: @@ -628,7 +614,7 @@ build-oss-fuzz: IMAGE: fedora script: - mkdir build-oss-fuzz - - export LSAN_OPTIONS=suppressions=scripts/oss-fuzz/lsan_suppressions.txt + - export LSAN_OPTIONS=suppressions=scripts/lsan_suppressions.txt - CC="clang" CXX="clang++" CFLAGS="-fsanitize=address" ./scripts/oss-fuzz/build.sh - export ASAN_OPTIONS="fast_unwind_on_malloc=0" @@ -678,7 +664,7 @@ build-without-defaults: --disable-pie --disable-qom-cast-debug --disable-strip - --target-list-exclude=aarch64-softmmu,microblaze-softmmu,mips64-softmmu,mipsel-softmmu,ppc64-softmmu,sh4el-softmmu,xtensa-softmmu,x86_64-softmmu + --target-list-exclude=aarch64-softmmu,mips64-softmmu,mipsel-softmmu,ppc64-softmmu,sh4el-softmmu,xtensa-softmmu,x86_64-softmmu MAKE_CHECK_ARGS: check build-libvhost-user: diff --git a/.gitlab-ci.d/cirrus.yml b/.gitlab-ci.d/cirrus.yml index 177bd684ef54..f2a9a64b76a7 100644 --- a/.gitlab-ci.d/cirrus.yml +++ b/.gitlab-ci.d/cirrus.yml @@ -42,7 +42,7 @@ x64-freebsd-14-build: CIRRUS_VM_RAM: 8G UPDATE_COMMAND: pkg update; pkg upgrade -y INSTALL_COMMAND: pkg install -y - CONFIGURE_ARGS: --target-list-exclude=arm-softmmu,i386-softmmu,microblaze-softmmu,mips64el-softmmu,mipsel-softmmu,mips-softmmu,ppc-softmmu,sh4eb-softmmu,xtensa-softmmu --enable-rust + CONFIGURE_ARGS: --target-list-exclude=arm-softmmu,i386-softmmu,mips64el-softmmu,mipsel-softmmu,mips-softmmu,ppc-softmmu,sh4eb-softmmu,xtensa-softmmu --enable-rust TEST_TARGETS: check aarch64-macos-build: @@ -56,5 +56,5 @@ aarch64-macos-build: INSTALL_COMMAND: brew install PATH_EXTRA: /opt/homebrew/ccache/libexec:/opt/homebrew/gettext/bin PKG_CONFIG_PATH: /opt/homebrew/curl/lib/pkgconfig:/opt/homebrew/ncurses/lib/pkgconfig:/opt/homebrew/readline/lib/pkgconfig - CONFIGURE_ARGS: --target-list-exclude=arm-softmmu,i386-softmmu,microblazeel-softmmu,mips64-softmmu,mipsel-softmmu,mips-softmmu,ppc-softmmu,sh4-softmmu,xtensaeb-softmmu --enable-rust + CONFIGURE_ARGS: --target-list-exclude=arm-softmmu,i386-softmmu,mips64-softmmu,mipsel-softmmu,mips-softmmu,ppc-softmmu,sh4-softmmu,xtensaeb-softmmu --enable-rust TEST_TARGETS: check-unit check-block check-qapi-schema check-softfloat check-qtest-x86_64 diff --git a/.gitlab-ci.d/cirrus/freebsd-14.vars b/.gitlab-ci.d/cirrus/freebsd-14.vars index 6477440ef30f..98fbde6cc646 100644 --- a/.gitlab-ci.d/cirrus/freebsd-14.vars +++ b/.gitlab-ci.d/cirrus/freebsd-14.vars @@ -11,6 +11,6 @@ MAKE='/usr/local/bin/gmake' NINJA='/usr/local/bin/ninja' PACKAGING_COMMAND='pkg' PIP3='/usr/local/bin/pip' -PKGS='alsa-lib bash bison bzip2 ca_root_nss capstone4 ccache4 cmocka coreutils ctags curl cyrus-sasl dbus diffutils dtc flex fusefs-libs3 gettext git glib gmake gnutls gsed gtk-vnc gtk3 json-c libepoxy libffi libgcrypt libjpeg-turbo libnfs libslirp libspice-server libssh libtasn1 llvm lzo2 meson mtools ncurses nettle ninja opencv pixman pkgconf png py311-numpy py311-pillow py311-pip py311-pyyaml py311-sphinx py311-sphinx_rtd_theme py311-tomli python3 rpm2cpio rust rust-bindgen-cli sdl2 sdl2_image snappy sndio socat spice-protocol tesseract usbredir virglrenderer vte3 vulkan-tools xorriso zstd' +PKGS='alsa-lib bash bison bzip2 ca_root_nss capstone4 ccache4 cmocka coreutils ctags curl cyrus-sasl dbus diffutils dtc flex fusefs-libs3 gettext git glib gmake gnutls gsed gtk-vnc gtk3 json-c libepoxy libffi libgcrypt libjpeg-turbo libnfs libslirp libspice-server libssh libtasn1 llvm lzo2 meson mtools ncurses nettle ninja opencv pixman pkgconf png py311-numpy py311-pillow py311-pip py311-pyyaml py311-setuptools py311-sphinx py311-sphinx_rtd_theme py311-tomli py311-wheel python3 rpm2cpio rust rust-bindgen-cli sdl2 sdl2_image snappy sndio socat spice-protocol tesseract usbredir virglrenderer vte3 vulkan-tools xorriso zstd' PYPI_PKGS='' PYTHON='/usr/local/bin/python3' diff --git a/.gitlab-ci.d/cirrus/macos-14.vars b/.gitlab-ci.d/cirrus/macos-14.vars index 4701c388e14f..def77cfdea53 100644 --- a/.gitlab-ci.d/cirrus/macos-14.vars +++ b/.gitlab-ci.d/cirrus/macos-14.vars @@ -11,6 +11,6 @@ MAKE='/opt/homebrew/bin/gmake' NINJA='/opt/homebrew/bin/ninja' PACKAGING_COMMAND='brew' PIP3='/opt/homebrew/bin/pip3' -PKGS='bash bc bindgen bison bzip2 capstone ccache cmocka coreutils ctags curl dbus diffutils dtc flex gcovr gettext git glib gnu-sed gnutls gtk+3 gtk-vnc jemalloc jpeg-turbo json-c libcbor libepoxy libffi libgcrypt libiscsi libnfs libpng libslirp libssh libtasn1 libusb llvm lzo make meson mtools ncurses nettle ninja pixman pkg-config python3 rpm2cpio rust sdl2 sdl2_image snappy socat sparse spice-protocol swtpm tesseract usbredir vde vte3 vulkan-tools xorriso zlib zstd' +PKGS='bash bc bindgen bison bzip2 capstone ccache cmocka coreutils ctags curl dbus diffutils dtc flex gcovr gettext git glib gnu-sed gnutls gtk+3 gtk-vnc jemalloc jpeg-turbo json-c libcbor libepoxy libffi libgcrypt libiscsi libnfs libpng libslirp libssh libtasn1 libusb llvm lzo make meson mtools ncurses nettle ninja pixman pkg-config python-setuptools python3 rpm2cpio rust sdl2 sdl2_image snappy socat sparse spice-protocol swtpm tesseract usbredir vde vte3 vulkan-tools xorriso zlib zstd' PYPI_PKGS='PyYAML numpy pillow sphinx sphinx-rtd-theme tomli' PYTHON='/opt/homebrew/bin/python3' diff --git a/.gitlab-ci.d/container-cross.yml b/.gitlab-ci.d/container-cross.yml index 2a0cea6ce1a7..92a7f1c1b475 100644 --- a/.gitlab-ci.d/container-cross.yml +++ b/.gitlab-ci.d/container-cross.yml @@ -10,12 +10,6 @@ amd64-debian-user-cross-container: variables: NAME: debian-all-test-cross -amd64-debian-legacy-cross-container: - extends: .container_job_template - stage: containers - variables: - NAME: debian-legacy-test-cross - arm64-debian-cross-container: extends: .container_job_template stage: containers diff --git a/.gitlab-ci.d/containers.yml b/.gitlab-ci.d/containers.yml index 6aeccf8be0c1..c8b8e44ad86f 100644 --- a/.gitlab-ci.d/containers.yml +++ b/.gitlab-ci.d/containers.yml @@ -45,7 +45,6 @@ weekly-container-builds: # cross - amd64-debian-cross-container - amd64-debian-user-cross-container - - amd64-debian-legacy-cross-container - arm64-debian-cross-container - hexagon-cross-container - loongarch-debian-cross-container diff --git a/.gitlab-ci.d/crossbuilds.yml b/.gitlab-ci.d/crossbuilds.yml index 59ff8b1d8705..cf977dfefb18 100644 --- a/.gitlab-ci.d/crossbuilds.yml +++ b/.gitlab-ci.d/crossbuilds.yml @@ -111,7 +111,7 @@ cross-win64-system: IMAGE: fedora-win64-cross EXTRA_CONFIGURE_OPTS: --enable-fdt=internal --disable-plugins CROSS_SKIP_TARGETS: alpha-softmmu avr-softmmu hppa-softmmu - m68k-softmmu microblazeel-softmmu + m68k-softmmu microblaze-softmmu or1k-softmmu rx-softmmu sh4eb-softmmu sparc64-softmmu tricore-softmmu xtensaeb-softmmu artifacts: diff --git a/.gitlab-ci.d/windows.yml b/.gitlab-ci.d/windows.yml index 5ef4d34d1eab..145500ae464a 100644 --- a/.gitlab-ci.d/windows.yml +++ b/.gitlab-ci.d/windows.yml @@ -89,6 +89,8 @@ msys2-64bit: mingw-w64-x86_64-pkgconf mingw-w64-x86_64-python mingw-w64-x86_64-python-certifi + mingw-w64-x86_64-python-setuptools + mingw-w64-x86_64-python-wheel mingw-w64-x86_64-rust mingw-w64-x86_64-rust-bindgen mingw-w64-x86_64-zstd" diff --git a/MAINTAINERS b/MAINTAINERS index e0c481e2125d..9b99839221e0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -152,6 +152,13 @@ F: tools/i386/ F: tests/functional/i386/ F: tests/functional/x86_64/ +X86 VM file descriptor change on reset test +M: Ani Sinha +M: Paolo Bonzini +S: Maintained +F: stubs/kvm.c +F: tests/functional/x86_64/test_rebuild_vmfd.py + Guest CPU cores (TCG) --------------------- Overall TCG CPUs @@ -159,7 +166,7 @@ M: Richard Henderson R: Paolo Bonzini S: Maintained F: system/watchpoint.c -F: page-vary-target.c +F: page-vary-system.c F: page-vary-common.c F: accel/tcg/ F: accel/stubs/tcg-stub.c @@ -286,7 +293,7 @@ F: target/microblaze/ F: hw/microblaze/ F: disas/microblaze.c F: tests/docker/dockerfiles/debian-microblaze-cross.d/build-toolchain.sh -F: tests/functional/microblaze*/meson.build +F: tests/functional/microblaze/ MIPS TCG CPUs M: Philippe Mathieu-Daudé @@ -304,6 +311,7 @@ M: Stafford Horne S: Odd Fixes F: docs/system/or1k/cpu-features.rst F: target/or1k/ +F: gdb-xml/or1k-core.xml F: hw/or1k/ F: include/hw/or1k/ F: tests/functional/or1k/meson.build @@ -312,6 +320,7 @@ F: tests/tcg/or1k/ PowerPC TCG CPUs M: Nicholas Piggin R: Chinmay Rath +R: Glenn Miles L: qemu-ppc@nongnu.org S: Odd Fixes F: target/ppc/ @@ -402,6 +411,7 @@ M: Mark Cave-Ayland M: Artyom Tarasenko S: Maintained F: target/sparc/ +F: gdb-xml/sparc*.xml F: hw/sparc/ F: hw/sparc64/ F: include/hw/sparc/sparc64.h @@ -576,7 +586,7 @@ F: include/system/whpx-common.h F: include/system/whpx-internal.h MSHV -M: Magnus Kulke +M: Magnus Kulke R: Wei Liu S: Supported F: accel/mshv/ @@ -584,8 +594,14 @@ F: include/system/mshv.h F: include/hw/hyperv/hvgdk*.h F: include/hw/hyperv/hvhdk*.h +Nitro Enclaves (native) +M: Alexander Graf +S: Maintained +F: accel/nitro/ +F: include/system/nitro-accel.h + X86 MSHV CPUs -M: Magnus Kulke +M: Magnus Kulke R: Wei Liu S: Supported F: target/i386/mshv/ @@ -603,7 +619,6 @@ Guest CPU Cores (Xen) X86 Xen CPUs M: Stefano Stabellini M: Anthony PERARD -M: Paul Durrant M: Edgar E. Iglesias L: xen-devel@lists.xenproject.org S: Supported @@ -817,15 +832,6 @@ F: include/hw/*/exynos* F: docs/system/arm/exynos.rst F: tests/functional/arm/test_smdkc210.py -Calxeda Highbank -M: Rob Herring -M: Peter Maydell -L: qemu-arm@nongnu.org -S: Odd Fixes -F: hw/arm/highbank.c -F: hw/net/xgmac.c -F: docs/system/arm/highbank.rst - Canon DIGIC M: Antony Pavlov M: Peter Maydell @@ -1351,13 +1357,13 @@ F: gdb-xml/loongarch*.xml M68K Machines ------------- an5206 -M: Thomas Huth +M: Thomas Huth S: Odd Fixes F: hw/m68k/an5206.c F: hw/m68k/mcf5206.c mcf5208 -M: Thomas Huth +M: Thomas Huth S: Odd Fixes F: hw/m68k/mcf5208.c F: hw/m68k/mcf_intc.c @@ -1367,7 +1373,7 @@ F: include/hw/m68k/mcf*.h F: tests/functional/m68k/test_mcf5208evb.py NeXTcube -M: Thomas Huth +M: Thomas Huth S: Odd Fixes F: hw/m68k/next-*.c F: hw/display/next-fb.c @@ -1422,7 +1428,7 @@ M: Edgar E. Iglesias S: Maintained F: hw/microblaze/petalogix_s3adsp1800_mmu.c F: include/hw/char/xilinx_uartlite.h -F: tests/functional/microblaze*/test_s3adsp1800.py +F: tests/functional/microblaze/test_s3adsp1800.py petalogix_ml605 M: Edgar E. Iglesias @@ -1876,6 +1882,7 @@ R: Eric Farman S: Supported F: hw/s390x/s390-pci* F: include/hw/s390x/s390-pci* +F: include/hw/s390x/ipl/s390-pci* F: util/s390x_pci_mmio.c L: qemu-s390x@nongnu.org @@ -2406,6 +2413,7 @@ F: subprojects/libvhost-user/ F: block/export/vhost-user* F: util/vhost-user-server.c F: net/vhost* +F: tests/functional/x86_64/test_vhost_user_bridge.py vhost-shadow-virtqueue R: Eugenio Pérez @@ -2601,7 +2609,7 @@ F: include/hw/virtio/virtio-mem.h virtio-snd M: Gerd Hoffmann -R: Manos Pitsidianakis +M: Manos Pitsidianakis S: Supported F: hw/audio/virtio-snd.c F: hw/audio/virtio-snd-pci.c @@ -2735,7 +2743,7 @@ F: tests/qtest/hexloader-test.c F: tests/data/hex-loader/test.hex CHRP NVRAM -M: Thomas Huth +M: Mark Cave-Ayland S: Maintained F: hw/nvram/chrp_nvram.c F: include/hw/nvram/chrp_nvram.h @@ -3017,6 +3025,13 @@ F: hw/vmapple/* F: include/hw/vmapple/* F: docs/system/arm/vmapple.rst +Nitro Enclaves (native) +M: Alexander Graf +S: Maintained +F: hw/nitro/ +F: include/hw/nitro/ +F: docs/system/nitro.rst + Subsystems ---------- Overall Audio backends @@ -3035,6 +3050,7 @@ X: audio/sndioaudio.c X: audio/spiceaudio.c F: include/qemu/audio*.h F: qapi/audio.json +F: tests/audio/ ALSA Audio backend M: Gerd Hoffmann @@ -3073,7 +3089,7 @@ F: audio/paaudio.c SDL Audio backend M: Gerd Hoffmann -R: Thomas Huth +R: Thomas Huth S: Odd Fixes F: audio/sdlaudio.c @@ -3865,12 +3881,14 @@ F: include/qemu/yank.h F: qapi/yank.json COLO Framework -M: Hailiang Zhang +M: Lukas Straub S: Maintained F: migration/colo* +F: migration/multifd-colo.* F: include/migration/colo.h F: include/migration/failover.h -F: docs/COLO-FT.txt +F: tests/qtest/migration/colo-tests.c +F: docs/system/qemu-colo.rst COLO Proxy M: Zhang Chen @@ -3931,6 +3949,19 @@ F: include/hw/i2c/smbus_master.h F: include/hw/i2c/smbus_slave.h F: include/hw/i2c/smbus_eeprom.h +I3C +M: Joe Komlodi +M: Cédric Le Goater +M: Jamin Lin +R: Nabih Estefan +S: Maintained +F: hw/i3c/*.c +F: hw/i3c/.h +F: hw/i3c/Kconfig +F: hw/i3c/meson.build +F: hw/i3c/trace-events +F: include/hw/i3c/*.h + PMBus M: Titus Rwantare S: Maintained @@ -3988,7 +4019,7 @@ VT-d Emulation M: Michael S. Tsirkin R: Jason Wang R: Yi Liu -R: Clément Mathieu--Drif +R: Clément Mathieu--Drif S: Supported F: hw/i386/intel_iommu.c F: hw/i386/intel_iommu_internal.h @@ -4079,6 +4110,7 @@ S: Maintained F: docs/devel/tcg-plugins.rst F: plugins/ F: tests/tcg/plugins/ +F: tests/tcg/multiarch/plugin/ F: tests/functional/aarch64/test_tcg_plugins.py F: contrib/plugins/ F: scripts/qemu-plugin-symbols.py @@ -4119,7 +4151,7 @@ M: Alistair Francis L: qemu-riscv@nongnu.org S: Maintained F: tcg/riscv64/ -F: disas/riscv.[ch] +F: disas/riscv*.[ch] S390 TCG target M: Richard Henderson diff --git a/accel/Kconfig b/accel/Kconfig index a60f1149238b..6d052875ee2e 100644 --- a/accel/Kconfig +++ b/accel/Kconfig @@ -16,6 +16,9 @@ config KVM config MSHV bool +config NITRO + bool + config XEN bool select FSDEV_9P if VIRTFS diff --git a/accel/hvf/hvf-all.c b/accel/hvf/hvf-all.c index 033c677b6f63..5f357c6d1989 100644 --- a/accel/hvf/hvf-all.c +++ b/accel/hvf/hvf-all.c @@ -18,7 +18,6 @@ #include "system/hvf_int.h" #include "hw/core/cpu.h" #include "hw/core/boards.h" -#include "target/arm/hvf_arm.h" #include "trace.h" bool hvf_allowed; diff --git a/accel/hvf/meson.build b/accel/hvf/meson.build index fc52cb78433a..6e2dcc4a5f0c 100644 --- a/accel/hvf/meson.build +++ b/accel/hvf/meson.build @@ -1,7 +1,4 @@ -hvf_ss = ss.source_set() -hvf_ss.add(files( +system_ss.add(when: 'CONFIG_HVF', if_true: files( 'hvf-all.c', 'hvf-accel-ops.c', )) - -specific_ss.add_all(when: 'CONFIG_HVF', if_true: hvf_ss) diff --git a/accel/kvm/kvm-accel-ops.c b/accel/kvm/kvm-accel-ops.c index 8ed6945c2f78..6d9140e549f7 100644 --- a/accel/kvm/kvm-accel-ops.c +++ b/accel/kvm/kvm-accel-ops.c @@ -16,6 +16,7 @@ #include "qemu/osdep.h" #include "qemu/error-report.h" #include "qemu/main-loop.h" +#include "exec/cpu-common.h" #include "accel/accel-cpu-ops.h" #include "system/kvm.h" #include "system/kvm_int.h" diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c index 0d8b0c434709..984db9777959 100644 --- a/accel/kvm/kvm-all.c +++ b/accel/kvm/kvm-all.c @@ -90,6 +90,7 @@ struct KVMParkedVcpu { }; KVMState *kvm_state; +VmfdChangeNotifier vmfd_notifier; bool kvm_kernel_irqchip; bool kvm_split_irqchip; bool kvm_async_interrupts_allowed; @@ -123,6 +124,16 @@ static const KVMCapabilityInfo kvm_required_capabilites[] = { static NotifierList kvm_irqchip_change_notifiers = NOTIFIER_LIST_INITIALIZER(kvm_irqchip_change_notifiers); +static NotifierWithReturnList register_vmfd_changed_notifiers = + NOTIFIER_WITH_RETURN_LIST_INITIALIZER(register_vmfd_changed_notifiers); + +static NotifierWithReturnList register_vcpufd_changed_notifiers = + NOTIFIER_WITH_RETURN_LIST_INITIALIZER(register_vcpufd_changed_notifiers); + +static int map_kvm_run(KVMState *s, CPUState *cpu, Error **errp); +static int map_kvm_dirty_gfns(KVMState *s, CPUState *cpu, Error **errp); +static int vcpu_unmap_regions(KVMState *s, CPUState *cpu); + struct KVMResampleFd { int gsi; EventNotifier *resample_event; @@ -416,6 +427,90 @@ static int kvm_set_user_memory_region(KVMMemoryListener *kml, KVMSlot *slot, boo return ret; } +static void kvm_create_vcpu_internal(CPUState *cpu, KVMState *s, int kvm_fd) +{ + cpu->kvm_fd = kvm_fd; + cpu->kvm_state = s; + if (!s->guest_state_protected) { + cpu->vcpu_dirty = true; + } + cpu->dirty_pages = 0; + cpu->throttle_us_per_full = 0; + + return; +} + +static int kvm_rebind_vcpus(Error **errp) +{ + CPUState *cpu; + unsigned long vcpu_id; + KVMState *s = kvm_state; + int kvm_fd, ret = 0; + + CPU_FOREACH(cpu) { + vcpu_id = kvm_arch_vcpu_id(cpu); + + if (cpu->kvm_fd) { + close(cpu->kvm_fd); + } + + ret = kvm_arch_destroy_vcpu(cpu); + if (ret < 0) { + goto err; + } + + if (s->coalesced_mmio_ring == (void *)cpu->kvm_run + PAGE_SIZE) { + s->coalesced_mmio_ring = NULL; + } + + ret = vcpu_unmap_regions(s, cpu); + if (ret < 0) { + goto err; + } + + ret = kvm_arch_pre_create_vcpu(cpu, errp); + if (ret < 0) { + goto err; + } + + kvm_fd = kvm_vm_ioctl(s, KVM_CREATE_VCPU, vcpu_id); + if (kvm_fd < 0) { + error_report("KVM_CREATE_VCPU IOCTL failed for vCPU %lu (%s)", + vcpu_id, strerror(kvm_fd)); + return kvm_fd; + } + + kvm_create_vcpu_internal(cpu, s, kvm_fd); + + ret = map_kvm_run(s, cpu, errp); + if (ret < 0) { + goto err; + } + + if (s->kvm_dirty_ring_size) { + ret = map_kvm_dirty_gfns(s, cpu, errp); + if (ret < 0) { + goto err; + } + } + + ret = kvm_arch_init_vcpu(cpu); + if (ret < 0) { + error_setg_errno(errp, -ret, + "kvm_init_vcpu: kvm_arch_init_vcpu failed (%lu)", + vcpu_id); + } + + close(cpu->kvm_vcpu_stats_fd); + cpu->kvm_vcpu_stats_fd = kvm_vcpu_ioctl(cpu, KVM_GET_STATS_FD, NULL); + kvm_init_cpu_signals(cpu); + } + trace_kvm_rebind_vcpus(); + + err: + return ret; +} + static void kvm_park_vcpu(CPUState *cpu) { struct KVMParkedVcpu *vcpu; @@ -479,13 +574,7 @@ static int kvm_create_vcpu(CPUState *cpu) } } - cpu->kvm_fd = kvm_fd; - cpu->kvm_state = s; - if (!s->guest_state_protected) { - cpu->vcpu_dirty = true; - } - cpu->dirty_pages = 0; - cpu->throttle_us_per_full = 0; + kvm_create_vcpu_internal(cpu, s, kvm_fd); trace_kvm_create_vcpu(cpu->cpu_index, vcpu_id, kvm_fd); @@ -504,19 +593,11 @@ int kvm_create_and_park_vcpu(CPUState *cpu) return ret; } -static int do_kvm_destroy_vcpu(CPUState *cpu) +static int vcpu_unmap_regions(KVMState *s, CPUState *cpu) { - KVMState *s = kvm_state; int mmap_size; int ret = 0; - trace_kvm_destroy_vcpu(cpu->cpu_index, kvm_arch_vcpu_id(cpu)); - - ret = kvm_arch_destroy_vcpu(cpu); - if (ret < 0) { - goto err; - } - mmap_size = kvm_ioctl(s, KVM_GET_VCPU_MMAP_SIZE, 0); if (mmap_size < 0) { ret = mmap_size; @@ -544,39 +625,47 @@ static int do_kvm_destroy_vcpu(CPUState *cpu) cpu->kvm_dirty_gfns = NULL; } - kvm_park_vcpu(cpu); -err: + err: return ret; } -void kvm_destroy_vcpu(CPUState *cpu) -{ - if (do_kvm_destroy_vcpu(cpu) < 0) { - error_report("kvm_destroy_vcpu failed"); - exit(EXIT_FAILURE); - } -} - -int kvm_init_vcpu(CPUState *cpu, Error **errp) +static int do_kvm_destroy_vcpu(CPUState *cpu) { KVMState *s = kvm_state; - int mmap_size; - int ret; + int ret = 0; - trace_kvm_init_vcpu(cpu->cpu_index, kvm_arch_vcpu_id(cpu)); + trace_kvm_destroy_vcpu(cpu->cpu_index, kvm_arch_vcpu_id(cpu)); - ret = kvm_arch_pre_create_vcpu(cpu, errp); + ret = kvm_arch_destroy_vcpu(cpu); if (ret < 0) { goto err; } - ret = kvm_create_vcpu(cpu); + /* If I am the CPU that created coalesced_mmio_ring, then discard it */ + if (s->coalesced_mmio_ring == (void *)cpu->kvm_run + PAGE_SIZE) { + s->coalesced_mmio_ring = NULL; + } + + ret = vcpu_unmap_regions(s, cpu); if (ret < 0) { - error_setg_errno(errp, -ret, - "kvm_init_vcpu: kvm_create_vcpu failed (%lu)", - kvm_arch_vcpu_id(cpu)); goto err; } + kvm_park_vcpu(cpu); +err: + return ret; +} + +void kvm_destroy_vcpu(CPUState *cpu) +{ + if (do_kvm_destroy_vcpu(cpu) < 0) { + error_report("kvm_destroy_vcpu failed"); + exit(EXIT_FAILURE); + } +} + +static int map_kvm_run(KVMState *s, CPUState *cpu, Error **errp) +{ + int mmap_size, ret = 0; mmap_size = kvm_ioctl(s, KVM_GET_VCPU_MMAP_SIZE, 0); if (mmap_size < 0) { @@ -601,14 +690,53 @@ int kvm_init_vcpu(CPUState *cpu, Error **errp) (void *)cpu->kvm_run + s->coalesced_mmio * PAGE_SIZE; } + err: + return ret; +} + +static int map_kvm_dirty_gfns(KVMState *s, CPUState *cpu, Error **errp) +{ + int ret = 0; + /* Use MAP_SHARED to share pages with the kernel */ + cpu->kvm_dirty_gfns = mmap(NULL, s->kvm_dirty_ring_bytes, + PROT_READ | PROT_WRITE, MAP_SHARED, + cpu->kvm_fd, + PAGE_SIZE * KVM_DIRTY_LOG_PAGE_OFFSET); + if (cpu->kvm_dirty_gfns == MAP_FAILED) { + ret = -errno; + } + + return ret; +} + +int kvm_init_vcpu(CPUState *cpu, Error **errp) +{ + KVMState *s = kvm_state; + int ret; + + trace_kvm_init_vcpu(cpu->cpu_index, kvm_arch_vcpu_id(cpu)); + + ret = kvm_arch_pre_create_vcpu(cpu, errp); + if (ret < 0) { + goto err; + } + + ret = kvm_create_vcpu(cpu); + if (ret < 0) { + error_setg_errno(errp, -ret, + "kvm_init_vcpu: kvm_create_vcpu failed (%lu)", + kvm_arch_vcpu_id(cpu)); + goto err; + } + + ret = map_kvm_run(s, cpu, errp); + if (ret < 0) { + goto err; + } + if (s->kvm_dirty_ring_size) { - /* Use MAP_SHARED to share pages with the kernel */ - cpu->kvm_dirty_gfns = mmap(NULL, s->kvm_dirty_ring_bytes, - PROT_READ | PROT_WRITE, MAP_SHARED, - cpu->kvm_fd, - PAGE_SIZE * KVM_DIRTY_LOG_PAGE_OFFSET); - if (cpu->kvm_dirty_gfns == MAP_FAILED) { - ret = -errno; + ret = map_kvm_dirty_gfns(s, cpu, errp); + if (ret < 0) { goto err; } } @@ -2173,6 +2301,38 @@ void kvm_irqchip_change_notify(void) notifier_list_notify(&kvm_irqchip_change_notifiers, NULL); } +void kvm_vmfd_add_change_notifier(NotifierWithReturn *n) +{ + notifier_with_return_list_add(®ister_vmfd_changed_notifiers, n); +} + +void kvm_vmfd_remove_change_notifier(NotifierWithReturn *n) +{ + notifier_with_return_remove(n); +} + +static int kvm_vmfd_change_notify(Error **errp) +{ + return notifier_with_return_list_notify(®ister_vmfd_changed_notifiers, + &vmfd_notifier, errp); +} + +void kvm_vcpufd_add_change_notifier(NotifierWithReturn *n) +{ + notifier_with_return_list_add(®ister_vcpufd_changed_notifiers, n); +} + +void kvm_vcpufd_remove_change_notifier(NotifierWithReturn *n) +{ + notifier_with_return_remove(n); +} + +static int kvm_vcpufd_change_notify(Error **errp) +{ + return notifier_with_return_list_notify(®ister_vcpufd_changed_notifiers, + &vmfd_notifier, errp); +} + int kvm_irqchip_get_virq(KVMState *s) { int next_virq; @@ -2415,11 +2575,9 @@ void kvm_irqchip_set_qemuirq_gsi(KVMState *s, qemu_irq irq, int gsi) g_hash_table_insert(s->gsimap, irq, GINT_TO_POINTER(gsi)); } -static void kvm_irqchip_create(KVMState *s) +static void do_kvm_irqchip_create(KVMState *s) { int ret; - - assert(s->kernel_irqchip_split != ON_OFF_AUTO_AUTO); if (kvm_check_extension(s, KVM_CAP_IRQCHIP)) { ; } else if (kvm_check_extension(s, KVM_CAP_S390_IRQCHIP)) { @@ -2452,7 +2610,13 @@ static void kvm_irqchip_create(KVMState *s) fprintf(stderr, "Create kernel irqchip failed: %s\n", strerror(-ret)); exit(1); } +} + +static void kvm_irqchip_create(KVMState *s) +{ + assert(s->kernel_irqchip_split != ON_OFF_AUTO_AUTO); + do_kvm_irqchip_create(s); kvm_kernel_irqchip = true; /* If we have an in-kernel IRQ chip then we must have asynchronous * interrupt delivery (though the reverse is not necessarily true) @@ -2607,6 +2771,122 @@ static int kvm_setup_dirty_ring(KVMState *s) return 0; } +static int kvm_reset_vmfd(MachineState *ms) +{ + KVMState *s; + KVMMemoryListener *kml; + int ret = 0, type; + Error *err = NULL; + + /* + * bail if the current architecture does not support VM file + * descriptor change. + */ + if (!kvm_arch_supports_vmfd_change()) { + error_report("This target architecture does not support KVM VM " + "file descriptor change."); + return -EOPNOTSUPP; + } + + s = KVM_STATE(ms->accelerator); + kml = &s->memory_listener; + + memory_listener_unregister(&kml->listener); + memory_listener_unregister(&kvm_io_listener); + + vmfd_notifier.pre = true; + ret = kvm_vmfd_change_notify(&err); + if (ret < 0) { + return ret; + } + assert(!err); + + if (s->vmfd >= 0) { + close(s->vmfd); + } + + type = find_kvm_machine_type(ms); + if (type < 0) { + return -EINVAL; + } + + ret = do_kvm_create_vm(s, type); + if (ret < 0) { + return ret; + } + + s->vmfd = ret; + + /* guest state is now unprotected again */ + kvm_state->guest_state_protected = false; + + kvm_setup_dirty_ring(s); + + /* rebind memory to new vm fd */ + ret = ram_block_rebind(&err); + if (ret < 0) { + return ret; + } + assert(!err); + + ret = kvm_arch_on_vmfd_change(ms, s); + if (ret < 0) { + return ret; + } + + if (s->kernel_irqchip_allowed) { + do_kvm_irqchip_create(s); + } + + /* + * notify everyone that vmfd has changed. + */ + vmfd_notifier.vmfd = s->vmfd; + vmfd_notifier.pre = false; + + ret = kvm_vmfd_change_notify(&err); + if (ret < 0) { + return ret; + } + assert(!err); + + /* + * rebind new vcpu fds with the new kvm fds + * These can only be called after kvm_arch_on_vmfd_change() + */ + ret = kvm_rebind_vcpus(&err); + if (ret < 0) { + return ret; + } + assert(!err); + + /* notify everyone that vcpu fd has changed. */ + ret = kvm_vcpufd_change_notify(&err); + if (ret < 0) { + return ret; + } + assert(!err); + + /* these can be only called after ram_block_rebind() */ + memory_listener_register(&kml->listener, &address_space_memory); + memory_listener_register(&kvm_io_listener, &address_space_io); + + /* + * kvm fd has changed. Commit the irq routes to KVM once more. + */ + kvm_irqchip_commit_routes(s); + /* + * for confidential guest, this is the last possible place where we + * can call synchronize_all_post_init() to sync all vcpu states to + * kvm. + */ + if (ms->cgs) { + cpu_synchronize_all_post_init(); + } + trace_kvm_reset_vmfd(); + return ret; +} + static int kvm_init(AccelState *as, MachineState *ms) { MachineClass *mc = MACHINE_GET_CLASS(ms); @@ -3124,7 +3404,7 @@ int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private) addr = memory_region_get_ram_ptr(mr) + section.offset_within_region; rb = qemu_ram_block_from_host(addr, false, &offset); - ret = ram_block_attributes_state_change(RAM_BLOCK_ATTRIBUTES(mr->rdm), + ret = ram_block_attributes_state_change(rb->attributes, offset, size, to_private); if (ret) { error_report("Failed to notify the listener the state change of " @@ -3997,6 +4277,7 @@ static void kvm_accel_instance_init(Object *obj) s->xen_evtchn_max_pirq = 256; s->device = NULL; s->msr_energy.enable = false; + s->honor_guest_pat = ON_OFF_AUTO_OFF; } /** @@ -4015,6 +4296,7 @@ static void kvm_accel_class_init(ObjectClass *oc, const void *data) AccelClass *ac = ACCEL_CLASS(oc); ac->name = "KVM"; ac->init_machine = kvm_init; + ac->rebuild_guest = kvm_reset_vmfd; ac->has_memory = kvm_accel_has_memory; ac->allowed = &kvm_allowed; ac->gdbstub_supported_sstep_flags = kvm_gdbstub_sstep_flags; diff --git a/accel/kvm/trace-events b/accel/kvm/trace-events index e43d18a8692a..4a8921c632bf 100644 --- a/accel/kvm/trace-events +++ b/accel/kvm/trace-events @@ -14,6 +14,8 @@ kvm_destroy_vcpu(int cpu_index, unsigned long arch_cpu_id) "index: %d id: %lu" kvm_park_vcpu(int cpu_index, unsigned long arch_cpu_id) "index: %d id: %lu" kvm_unpark_vcpu(unsigned long arch_cpu_id, const char *msg) "id: %lu %s" kvm_irqchip_commit_routes(void) "" +kvm_reset_vmfd(void) "" +kvm_rebind_vcpus(void) "" kvm_irqchip_add_msi_route(char *name, int vector, int virq) "dev %s vector %d virq %d" kvm_irqchip_update_msi_route(int virq) "Updating MSI route virq=%d" kvm_irqchip_release_virq(int virq) "virq %d" diff --git a/accel/meson.build b/accel/meson.build index 289b7420ffaf..7da12b9741fd 100644 --- a/accel/meson.build +++ b/accel/meson.build @@ -12,6 +12,7 @@ if have_system subdir('xen') subdir('stubs') subdir('mshv') + subdir('nitro') endif # qtest diff --git a/accel/mshv/meson.build b/accel/mshv/meson.build index d3a2b3258112..c1b1787c5e62 100644 --- a/accel/mshv/meson.build +++ b/accel/mshv/meson.build @@ -1,9 +1,6 @@ -mshv_ss = ss.source_set() -mshv_ss.add(if_true: files( +system_ss.add(when: 'CONFIG_MSHV', if_true: files( 'irq.c', 'mem.c', 'msr.c', 'mshv-all.c' )) - -specific_ss.add_all(when: 'CONFIG_MSHV', if_true: mshv_ss) diff --git a/accel/mshv/mshv-all.c b/accel/mshv/mshv-all.c index ddc4c18cba47..d4cc7f537158 100644 --- a/accel/mshv/mshv-all.c +++ b/accel/mshv/mshv-all.c @@ -381,7 +381,7 @@ static void register_mshv_memory_listener(MshvState *s, MshvMemoryListener *mml, } } -int mshv_hvcall(int fd, const struct mshv_root_hvcall *args) +int mshv_hvcall(int fd, const mshv_root_hvcall *args) { int ret = 0; diff --git a/accel/nitro/meson.build b/accel/nitro/meson.build new file mode 100644 index 000000000000..e01c1bab96d4 --- /dev/null +++ b/accel/nitro/meson.build @@ -0,0 +1,3 @@ +nitro_ss = ss.source_set() +nitro_ss.add(files('nitro-accel.c')) +system_ss.add_all(when: 'CONFIG_NITRO', if_true: nitro_ss) diff --git a/accel/nitro/nitro-accel.c b/accel/nitro/nitro-accel.c new file mode 100644 index 000000000000..a1e97a9162e9 --- /dev/null +++ b/accel/nitro/nitro-accel.c @@ -0,0 +1,284 @@ +/* + * Nitro Enclaves accelerator + * + * Copyright © 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Authors: + * Alexander Graf + * + * Nitro Enclaves are a confidential compute technology which + * allows a parent instance to carve out resources from itself + * and spawn a confidential sibling VM next to itself. Similar + * to other confidential compute solutions, this sibling is + * controlled by an underlying vmm, but still has a higher level + * vmm (QEMU) to implement some of its I/O functionality and + * lifecycle. + * + * This accelerator drives /dev/nitro_enclaves to spawn a Nitro + * Enclave. It works in tandem with the nitro_enclaves machine + * which ensures the correct backend devices are available and + * that the initial seed (an EIF file) is loaded at the correct + * offset in memory. + * + * The accel starts the enclave when the machine starts, after + * all device setup is finished. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include "qapi/visitor.h" +#include "qemu/module.h" +#include "qemu/rcu.h" +#include "qemu/accel.h" +#include "qemu/guest-random.h" +#include "qemu/main-loop.h" +#include "accel/accel-ops.h" +#include "accel/accel-cpu-ops.h" +#include "accel/dummy-cpus.h" +#include "system/cpus.h" +#include "hw/core/cpu.h" +#include "hw/core/boards.h" +#include "hw/nitro/nitro-vsock-bus.h" +#include "system/ramblock.h" +#include "system/nitro-accel.h" +#include "trace.h" + +#include +#include "standard-headers/linux/nitro_enclaves.h" + +bool nitro_allowed; + +typedef struct NitroAccelState { + AccelState parent_obj; + + int ne_fd; + int enclave_fd; + uint64_t slot_uid; + uint64_t enclave_cid; + bool debug_mode; +} NitroAccelState; + +static int nitro_init_machine(AccelState *as, MachineState *ms) +{ + NitroAccelState *s = NITRO_ACCEL(as); + uint64_t slot_uid = 0; + int ret; + + s->ne_fd = open("/dev/nitro_enclaves", O_RDWR | O_CLOEXEC); + if (s->ne_fd < 0) { + error_report("nitro: failed to open /dev/nitro_enclaves: %s", + strerror(errno)); + return -errno; + } + + ret = ioctl(s->ne_fd, NE_CREATE_VM, &slot_uid); + if (ret < 0) { + error_report("nitro: NE_CREATE_VM failed: %s", strerror(errno)); + close(s->ne_fd); + return -errno; + } + s->enclave_fd = ret; + s->slot_uid = slot_uid; + + return 0; +} + +static int nitro_donate_ram_block(RAMBlock *rb, void *opaque) +{ + NitroAccelState *s = opaque; + struct ne_user_memory_region region = { + .flags = 0, + .memory_size = rb->used_length, + .userspace_addr = (uint64_t)(uintptr_t)rb->host, + }; + + if (!rb->used_length) { + return 0; + } + + if (ioctl(s->enclave_fd, NE_SET_USER_MEMORY_REGION, ®ion) < 0) { + error_report("nitro: NE_SET_USER_MEMORY_REGION failed for %s " + "(%" PRIu64 " bytes): %s", rb->idstr, rb->used_length, + strerror(errno)); + return -errno; + } + return 0; +} + +/* + * Start the Enclave. At this point memory is set up and the EIF is loaded. + * This function donates memory, adds vCPUs, and starts the enclave. + */ +static void nitro_setup_post(AccelState *as) +{ + MachineState *ms = MACHINE(qdev_get_machine()); + NitroAccelState *s = NITRO_ACCEL(as); + int nr_cpus = ms->smp.cpus; + int i, ret; + struct ne_enclave_start_info start_info = { + .flags = s->debug_mode ? NE_ENCLAVE_DEBUG_MODE : 0, + .enclave_cid = s->enclave_cid, + }; + + ret = qemu_ram_foreach_block(nitro_donate_ram_block, s); + if (ret < 0) { + error_report("nitro: failed to donate memory"); + exit(1); + } + + for (i = 0; i < nr_cpus; i++) { + uint32_t cpu_id = 0; + if (ioctl(s->enclave_fd, NE_ADD_VCPU, &cpu_id) < 0) { + error_report("nitro: NE_ADD_VCPU failed: %s", strerror(errno)); + exit(1); + } + } + + ret = ioctl(s->enclave_fd, NE_START_ENCLAVE, &start_info); + if (ret < 0) { + switch (errno) { + case NE_ERR_NO_MEM_REGIONS_ADDED: + error_report("nitro: no memory regions added"); + break; + case NE_ERR_NO_VCPUS_ADDED: + error_report("nitro: no vCPUs added"); + break; + case NE_ERR_ENCLAVE_MEM_MIN_SIZE: + error_report("nitro: memory is below the minimum " + "required size. Try increasing -m"); + break; + case NE_ERR_FULL_CORES_NOT_USED: + error_report("nitro: requires full CPU cores. " + "Try increasing -smp to a multiple of threads " + "per core on this host (e.g. -smp 2)"); + break; + case NE_ERR_NOT_IN_INIT_STATE: + error_report("nitro: not in init state"); + break; + case NE_ERR_INVALID_FLAG_VALUE: + error_report("nitro: invalid flag value for NE_START_ENCLAVE"); + break; + case NE_ERR_INVALID_ENCLAVE_CID: + error_report("nitro: invalid enclave CID"); + break; + default: + error_report("nitro: NE_START_ENCLAVE failed: %s (errno %d)", + strerror(errno), errno); + break; + } + exit(1); + } + + s->enclave_cid = start_info.enclave_cid; + trace_nitro_enclave_started(s->enclave_cid); + + /* + * Notify all Nitro vsock bus devices that the enclave has started + * and provide them with the CID for vsock connections. + */ + { + NitroVsockBridge *bridge = nitro_vsock_bridge_find(); + Error *err = NULL; + + if (bridge) { + nitro_vsock_bridge_start_enclave(bridge, + (uint32_t)s->enclave_cid, &err); + if (err) { + error_report_err(err); + exit(1); + } + } + } +} + +/* QOM properties */ + +static bool nitro_get_debug_mode(Object *obj, Error **errp) +{ + return NITRO_ACCEL(obj)->debug_mode; +} + +static void nitro_set_debug_mode(Object *obj, bool value, Error **errp) +{ + NITRO_ACCEL(obj)->debug_mode = value; +} + +static void nitro_get_enclave_cid(Object *obj, Visitor *v, + const char *name, void *opaque, + Error **errp) +{ + uint64_t val = NITRO_ACCEL(obj)->enclave_cid; + visit_type_uint64(v, name, &val, errp); +} + +static void nitro_set_enclave_cid(Object *obj, Visitor *v, + const char *name, void *opaque, + Error **errp) +{ + uint64_t val; + if (visit_type_uint64(v, name, &val, errp)) { + NITRO_ACCEL(obj)->enclave_cid = val; + } +} + +static void nitro_accel_class_init(ObjectClass *oc, const void *data) +{ + AccelClass *ac = ACCEL_CLASS(oc); + ac->name = "Nitro"; + ac->init_machine = nitro_init_machine; + ac->setup_post = nitro_setup_post; + ac->allowed = &nitro_allowed; + + object_class_property_add_bool(oc, "debug-mode", + nitro_get_debug_mode, + nitro_set_debug_mode); + object_class_property_set_description(oc, "debug-mode", + "Start enclave in debug mode (enables console output)"); + + object_class_property_add(oc, "enclave-cid", "uint64", + nitro_get_enclave_cid, + nitro_set_enclave_cid, + NULL, NULL); + object_class_property_set_description(oc, "enclave-cid", + "Enclave CID (0 = auto-assigned by Nitro)"); +} + +static const TypeInfo nitro_accel_type = { + .name = TYPE_NITRO_ACCEL, + .parent = TYPE_ACCEL, + .instance_size = sizeof(NitroAccelState), + .class_init = nitro_accel_class_init, +}; +module_obj(TYPE_NITRO_ACCEL); + +static bool nitro_cpus_are_resettable(void) +{ + return false; +} + +static void nitro_accel_ops_class_init(ObjectClass *oc, const void *data) +{ + AccelOpsClass *ops = ACCEL_OPS_CLASS(oc); + ops->create_vcpu_thread = dummy_start_vcpu_thread; + ops->handle_interrupt = generic_handle_interrupt; + ops->cpus_are_resettable = nitro_cpus_are_resettable; +} + +static const TypeInfo nitro_accel_ops_type = { + .name = ACCEL_OPS_NAME("nitro"), + .parent = TYPE_ACCEL_OPS, + .class_init = nitro_accel_ops_class_init, + .abstract = true, +}; +module_obj(ACCEL_OPS_NAME("nitro")); + +static void nitro_type_init(void) +{ + type_register_static(&nitro_accel_type); + type_register_static(&nitro_accel_ops_type); +} + +type_init(nitro_type_init); diff --git a/accel/nitro/trace-events b/accel/nitro/trace-events new file mode 100644 index 000000000000..9673eb5aa224 --- /dev/null +++ b/accel/nitro/trace-events @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# See docs/devel/tracing.rst for syntax documentation. + +# nitro-accel.c +nitro_enclave_started(uint64_t cid) "nitro: enclave started, CID=%"PRIu64 diff --git a/accel/nitro/trace.h b/accel/nitro/trace.h new file mode 100644 index 000000000000..8c5564725dce --- /dev/null +++ b/accel/nitro/trace.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "trace/trace-accel_nitro.h" diff --git a/accel/qtest/meson.build b/accel/qtest/meson.build index 2018de8a05dc..e1b089e02c70 100644 --- a/accel/qtest/meson.build +++ b/accel/qtest/meson.build @@ -1 +1,4 @@ -qtest_module_ss.add(when: ['CONFIG_SYSTEM_ONLY'], if_true: files('qtest.c')) +qtest_module_ss = ss.source_set() +qtest_module_ss.add(files('qtest.c')) + +modules += {'accel': {'qtest': qtest_module_ss}} diff --git a/accel/stubs/kvm-stub.c b/accel/stubs/kvm-stub.c index 68cd33ba9735..c4617caac6bf 100644 --- a/accel/stubs/kvm-stub.c +++ b/accel/stubs/kvm-stub.c @@ -79,6 +79,24 @@ void kvm_irqchip_change_notify(void) { } +void kvm_vmfd_add_change_notifier(NotifierWithReturn *n) +{ +} + +void kvm_vmfd_remove_change_notifier(NotifierWithReturn *n) +{ +} + +void kvm_vcpufd_add_change_notifier(NotifierWithReturn *n) +{ + return; +} + +void kvm_vcpufd_remove_change_notifier(NotifierWithReturn *n) +{ + return; +} + int kvm_irqchip_add_irqfd_notifier_gsi(KVMState *s, EventNotifier *n, EventNotifier *rn, int virq) { diff --git a/accel/stubs/meson.build b/accel/stubs/meson.build index 48eccd1b8617..ccad583e6477 100644 --- a/accel/stubs/meson.build +++ b/accel/stubs/meson.build @@ -1,10 +1,10 @@ -system_stubs_ss = ss.source_set() -system_stubs_ss.add(when: 'CONFIG_XEN', if_false: files('xen-stub.c')) -system_stubs_ss.add(when: 'CONFIG_KVM', if_false: files('kvm-stub.c')) -system_stubs_ss.add(when: 'CONFIG_TCG', if_false: files('tcg-stub.c')) -system_stubs_ss.add(when: 'CONFIG_HVF', if_false: files('hvf-stub.c')) -system_stubs_ss.add(when: 'CONFIG_NVMM', if_false: files('nvmm-stub.c')) -system_stubs_ss.add(when: 'CONFIG_WHPX', if_false: files('whpx-stub.c')) -system_stubs_ss.add(when: 'CONFIG_MSHV', if_false: files('mshv-stub.c')) - -specific_ss.add_all(when: ['CONFIG_SYSTEM_ONLY'], if_true: system_stubs_ss) +stub_ss.add(files( + 'hvf-stub.c', + 'kvm-stub.c', + 'nitro-stub.c', + 'mshv-stub.c', + 'nvmm-stub.c', + 'tcg-stub.c', + 'whpx-stub.c', + 'xen-stub.c', +)) diff --git a/accel/stubs/nitro-stub.c b/accel/stubs/nitro-stub.c new file mode 100644 index 000000000000..186c8444f865 --- /dev/null +++ b/accel/stubs/nitro-stub.c @@ -0,0 +1,11 @@ +/* + * Nitro accel stubs for QEMU + * + * Copyright © 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" + +bool nitro_allowed; diff --git a/accel/whpx/whpx-accel-ops.c b/accel/whpx/whpx-accel-ops.c index 50fadea0fd61..b8f41544cbef 100644 --- a/accel/whpx/whpx-accel-ops.c +++ b/accel/whpx/whpx-accel-ops.c @@ -17,6 +17,7 @@ #include "system/whpx.h" #include "system/whpx-internal.h" +#include "system/whpx-all.h" #include "system/whpx-accel-ops.h" static void *whpx_cpu_thread_fn(void *arg) @@ -81,6 +82,12 @@ static bool whpx_vcpu_thread_is_idle(CPUState *cpu) return !whpx_irqchip_in_kernel(); } +static bool whpx_supports_guest_debug(void) +{ + return whpx_arch_supports_guest_debug(); +} + + static void whpx_accel_ops_class_init(ObjectClass *oc, const void *data) { AccelOpsClass *ops = ACCEL_OPS_CLASS(oc); @@ -89,6 +96,7 @@ static void whpx_accel_ops_class_init(ObjectClass *oc, const void *data) ops->kick_vcpu_thread = whpx_kick_vcpu_thread; ops->cpu_thread_is_idle = whpx_vcpu_thread_is_idle; ops->handle_interrupt = generic_handle_interrupt; + ops->supports_guest_debug = whpx_supports_guest_debug; ops->synchronize_post_reset = whpx_cpu_synchronize_post_reset; ops->synchronize_post_init = whpx_cpu_synchronize_post_init; diff --git a/accel/whpx/whpx-common.c b/accel/whpx/whpx-common.c index f018a8f5c7db..b813a5d9d257 100644 --- a/accel/whpx/whpx-common.c +++ b/accel/whpx/whpx-common.c @@ -39,13 +39,45 @@ bool whpx_allowed; bool whpx_irqchip_in_kernel; static bool whp_dispatch_initialized; static HMODULE hWinHvPlatform; -#ifdef HOST_X86_64 -static HMODULE hWinHvEmulation; -#endif struct whpx_state whpx_global; struct WHPDispatch whp_dispatch; +void whpx_flush_cpu_state(CPUState *cpu) +{ + if (cpu->vcpu_dirty) { + whpx_set_registers(cpu, WHPX_LEVEL_RUNTIME_STATE); + cpu->vcpu_dirty = false; + } +} + +void whpx_get_reg(CPUState *cpu, WHV_REGISTER_NAME reg, WHV_REGISTER_VALUE* val) +{ + struct whpx_state *whpx = &whpx_global; + HRESULT hr; + + whpx_flush_cpu_state(cpu); + + hr = whp_dispatch.WHvGetVirtualProcessorRegisters(whpx->partition, cpu->cpu_index, + ®, 1, val); + + if (FAILED(hr)) { + error_report("WHPX: Failed to get register %08x, hr=%08lx", reg, hr); + } +} + +void whpx_set_reg(CPUState *cpu, WHV_REGISTER_NAME reg, WHV_REGISTER_VALUE val) +{ + struct whpx_state *whpx = &whpx_global; + HRESULT hr; + hr = whp_dispatch.WHvSetVirtualProcessorRegisters(whpx->partition, cpu->cpu_index, + ®, 1, &val); + + if (FAILED(hr)) { + error_report("WHPX: Failed to set register %08x, hr=%08lx", reg, hr); + } +} + /* Tries to find a breakpoint at the specified address. */ struct whpx_breakpoint *whpx_lookup_breakpoint_by_addr(uint64_t address) { @@ -148,7 +180,7 @@ int whpx_last_vcpu_stopping(CPUState *cpu) static void do_whpx_cpu_synchronize_state(CPUState *cpu, run_on_cpu_data arg) { if (!cpu->vcpu_dirty) { - whpx_get_registers(cpu); + whpx_get_registers(cpu, WHPX_LEVEL_FULL_STATE); cpu->vcpu_dirty = true; } } @@ -156,14 +188,14 @@ static void do_whpx_cpu_synchronize_state(CPUState *cpu, run_on_cpu_data arg) static void do_whpx_cpu_synchronize_post_reset(CPUState *cpu, run_on_cpu_data arg) { - whpx_set_registers(cpu, WHPX_SET_RESET_STATE); + whpx_set_registers(cpu, WHPX_LEVEL_RESET_STATE); cpu->vcpu_dirty = false; } static void do_whpx_cpu_synchronize_post_init(CPUState *cpu, run_on_cpu_data arg) { - whpx_set_registers(cpu, WHPX_SET_FULL_STATE); + whpx_set_registers(cpu, WHPX_LEVEL_FULL_STATE); cpu->vcpu_dirty = false; } @@ -236,10 +268,7 @@ void whpx_destroy_vcpu(CPUState *cpu) struct whpx_state *whpx = &whpx_global; whp_dispatch.WHvDeleteVirtualProcessor(whpx->partition, cpu->cpu_index); -#ifdef HOST_X86_64 - AccelCPUState *vcpu = cpu->accel; - whp_dispatch.WHvEmulatorDestroyEmulator(vcpu->emulator); -#endif + whpx_arch_destroy_vcpu(cpu); g_free(cpu->accel); } @@ -361,7 +390,6 @@ static bool load_whp_dispatch_fns(HMODULE *handle, HMODULE hLib = *handle; #define WINHV_PLATFORM_DLL "WinHvPlatform.dll" - #define WINHV_EMULATION_DLL "WinHvEmulation.dll" #define WHP_LOAD_FIELD_OPTIONAL(return_type, function_name, signature) \ whp_dispatch.function_name = \ (function_name ## _t)GetProcAddress(hLib, #function_name); \ @@ -388,14 +416,6 @@ static bool load_whp_dispatch_fns(HMODULE *handle, WHP_LOAD_LIB(WINHV_PLATFORM_DLL, hLib) LIST_WINHVPLATFORM_FUNCTIONS(WHP_LOAD_FIELD) break; - case WINHV_EMULATION_FNS_DEFAULT: -#ifdef HOST_X86_64 - WHP_LOAD_LIB(WINHV_EMULATION_DLL, hLib) - LIST_WINHVEMULATION_FUNCTIONS(WHP_LOAD_FIELD) -#else - g_assert_not_reached(); -#endif - break; case WINHV_PLATFORM_FNS_SUPPLEMENTAL: WHP_LOAD_LIB(WINHV_PLATFORM_DLL, hLib) LIST_WINHVPLATFORM_FUNCTIONS_SUPPLEMENTAL(WHP_LOAD_FIELD_OPTIONAL) @@ -450,6 +470,41 @@ static void whpx_set_kernel_irqchip(Object *obj, Visitor *v, } } +static void whpx_set_hyperv(Object *obj, Visitor *v, + const char *name, void *opaque, + Error **errp) +{ + struct whpx_state *whpx = &whpx_global; + OnOffAuto mode; + + if (!visit_type_OnOffAuto(v, name, &mode, errp)) { + return; + } + + switch (mode) { + case ON_OFF_AUTO_ON: + whpx->hyperv_enlightenments_allowed = true; + whpx->hyperv_enlightenments_required = true; + break; + + case ON_OFF_AUTO_OFF: + whpx->hyperv_enlightenments_allowed = false; + whpx->hyperv_enlightenments_required = false; + break; + + case ON_OFF_AUTO_AUTO: + whpx->hyperv_enlightenments_allowed = true; + whpx->hyperv_enlightenments_required = false; + break; + default: + /* + * The value was checked in visit_type_OnOffAuto() above. If + * we get here, then something is wrong in QEMU. + */ + abort(); + } +} + static void whpx_cpu_accel_class_init(ObjectClass *oc, const void *data) { AccelCPUClass *acc = ACCEL_CPU_CLASS(oc); @@ -478,6 +533,11 @@ static void whpx_accel_class_init(ObjectClass *oc, const void *data) NULL, NULL); object_class_property_set_description(oc, "kernel-irqchip", "Configure WHPX in-kernel irqchip"); + object_class_property_add(oc, "hyperv", "OnOffAuto", + NULL, whpx_set_hyperv, + NULL, NULL); + object_class_property_set_description(oc, "hyperv", + "Configure Hyper-V enlightenments"); } static void whpx_accel_instance_init(Object *obj) @@ -487,6 +547,9 @@ static void whpx_accel_instance_init(Object *obj) memset(whpx, 0, sizeof(struct whpx_state)); /* Turn on kernel-irqchip, by default */ whpx->kernel_irqchip_allowed = true; + + whpx->hyperv_enlightenments_allowed = true; + whpx->hyperv_enlightenments_required = false; } static const TypeInfo whpx_accel_type = { @@ -511,11 +574,6 @@ bool init_whp_dispatch(void) if (!load_whp_dispatch_fns(&hWinHvPlatform, WINHV_PLATFORM_FNS_DEFAULT)) { goto error; } -#ifdef HOST_X86_64 - if (!load_whp_dispatch_fns(&hWinHvEmulation, WINHV_EMULATION_FNS_DEFAULT)) { - goto error; - } -#endif assert(load_whp_dispatch_fns(&hWinHvPlatform, WINHV_PLATFORM_FNS_SUPPLEMENTAL)); whp_dispatch_initialized = true; @@ -525,11 +583,6 @@ bool init_whp_dispatch(void) if (hWinHvPlatform) { FreeLibrary(hWinHvPlatform); } -#ifdef HOST_X86_64 - if (hWinHvEmulation) { - FreeLibrary(hWinHvEmulation); - } -#endif return false; } diff --git a/accel/xen/meson.build b/accel/xen/meson.build index 002bdb03c629..455ad5d6be4a 100644 --- a/accel/xen/meson.build +++ b/accel/xen/meson.build @@ -1 +1 @@ -specific_ss.add(when: 'CONFIG_XEN', if_true: files('xen-all.c')) +system_ss.add(when: 'CONFIG_XEN', if_true: files('xen-all.c')) diff --git a/audio/alsaaudio.c b/audio/alsaaudio.c index 7d7da576dc95..dc99ccb44fa5 100644 --- a/audio/alsaaudio.c +++ b/audio/alsaaudio.c @@ -26,36 +26,43 @@ #include #include "qemu/main-loop.h" #include "qemu/module.h" +#include "qemu/error-report.h" #include "qemu/audio.h" +#include "qom/object.h" #include "trace.h" #pragma GCC diagnostic ignored "-Waddress" -#define AUDIO_CAP "alsa" #include "audio_int.h" -#define DEBUG_ALSA 0 +#define TYPE_AUDIO_ALSA "audio-alsa" +OBJECT_DECLARE_SIMPLE_TYPE(AudioALSA, AUDIO_ALSA) + +static AudioBackendClass *audio_alsa_parent_class; + +struct AudioALSA { + AudioMixengBackend parent_obj; +}; + struct pollhlp { snd_pcm_t *handle; struct pollfd *pfds; int count; int mask; - AudioBackend *s; + AudioMixengBackend *s; }; typedef struct ALSAVoiceOut { HWVoiceOut hw; snd_pcm_t *handle; struct pollhlp pollhlp; - Audiodev *dev; } ALSAVoiceOut; typedef struct ALSAVoiceIn { HWVoiceIn hw; snd_pcm_t *handle; struct pollhlp pollhlp; - Audiodev *dev; } ALSAVoiceIn; struct alsa_params_req { @@ -72,33 +79,29 @@ struct alsa_params_obt { snd_pcm_uframes_t samples; }; -static void G_GNUC_PRINTF (2, 3) alsa_logerr (int err, const char *fmt, ...) +static void G_GNUC_PRINTF(2, 3) alsa_logerr(int err, const char *fmt, ...) { va_list ap; - va_start (ap, fmt); - AUD_vlog (AUDIO_CAP, fmt, ap); - va_end (ap); - - AUD_log (AUDIO_CAP, "Reason: %s\n", snd_strerror (err)); + error_printf("alsa: "); + va_start(ap, fmt); + error_vprintf(fmt, ap); + va_end(ap); + error_printf(" Reason: %s", snd_strerror(err)); + error_printf("\n"); } -static void G_GNUC_PRINTF (3, 4) alsa_logerr2 ( - int err, - const char *typ, - const char *fmt, - ... - ) +static void G_GNUC_PRINTF(3, 4) alsa_logerr2(int err, const char *typ, + const char *fmt, ...) { va_list ap; - AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ); - - va_start (ap, fmt); - AUD_vlog (AUDIO_CAP, fmt, ap); - va_end (ap); - - AUD_log (AUDIO_CAP, "Reason: %s\n", snd_strerror (err)); + error_printf("alsa: Could not initialize %s:", typ); + va_start(ap, fmt); + error_vprintf(fmt, ap); + va_end(ap); + error_printf(" Reason: %s", snd_strerror(err)); + error_printf("\n"); } static void alsa_fini_poll (struct pollhlp *hlp) @@ -121,7 +124,7 @@ static void alsa_anal_close1 (snd_pcm_t **handlep) { int err = snd_pcm_close (*handlep); if (err) { - alsa_logerr (err, "Failed to close PCM handle %p\n", *handlep); + alsa_logerr(err, "Failed to close PCM handle %p", *handlep); } *handlep = NULL; } @@ -136,7 +139,7 @@ static int alsa_recover (snd_pcm_t *handle) { int err = snd_pcm_prepare (handle); if (err < 0) { - alsa_logerr (err, "Failed to prepare handle %p\n", handle); + alsa_logerr(err, "Failed to prepare handle %p", handle); return -1; } return 0; @@ -146,7 +149,7 @@ static int alsa_resume (snd_pcm_t *handle) { int err = snd_pcm_resume (handle); if (err < 0) { - alsa_logerr (err, "Failed to resume handle %p\n", handle); + alsa_logerr(err, "Failed to resume handle %p", handle); return -1; } return 0; @@ -161,7 +164,7 @@ static void alsa_poll_handler (void *opaque) count = poll (hlp->pfds, hlp->count, 0); if (count < 0) { - dolog ("alsa_poll_handler: poll %s\n", strerror (errno)); + warn_report("alsa_poll_handler: poll %s", strerror(errno)); return; } @@ -174,7 +177,7 @@ static void alsa_poll_handler (void *opaque) err = snd_pcm_poll_descriptors_revents (hlp->handle, hlp->pfds, hlp->count, &revents); if (err < 0) { - alsa_logerr (err, "snd_pcm_poll_descriptors_revents"); + alsa_logerr(err, "snd_pcm_poll_descriptors_revents"); return; } @@ -206,7 +209,7 @@ static void alsa_poll_handler (void *opaque) break; default: - dolog ("Unexpected state %d\n", state); + warn_report("alsa: Unexpected state %d", state); } } @@ -217,8 +220,8 @@ static int alsa_poll_helper (snd_pcm_t *handle, struct pollhlp *hlp, int mask) count = snd_pcm_poll_descriptors_count (handle); if (count <= 0) { - dolog ("Could not initialize poll mode\n" - "Invalid number of poll descriptors %d\n", count); + warn_report("alsa: Could not initialize poll mode: " + "Invalid number of poll descriptors %d", count); return -1; } @@ -226,8 +229,7 @@ static int alsa_poll_helper (snd_pcm_t *handle, struct pollhlp *hlp, int mask) err = snd_pcm_poll_descriptors (handle, pfds, count); if (err < 0) { - alsa_logerr (err, "Could not initialize poll mode\n" - "Could not obtain poll descriptors\n"); + alsa_logerr(err, "Could not initialize poll mode: Could not obtain poll descriptors"); g_free (pfds); return -1; } @@ -289,10 +291,7 @@ static snd_pcm_format_t aud_to_alsafmt(AudioFormat fmt, bool big_endian) return big_endian ? SND_PCM_FORMAT_FLOAT_BE : SND_PCM_FORMAT_FLOAT_LE; default: - dolog ("Internal logic error: Bad audio format %d\n", fmt); -#ifdef DEBUG_AUDIO - abort (); -#endif + warn_report("alsa: Internal logic error: Bad audio format %d", fmt); return SND_PCM_FORMAT_U8; } } @@ -362,29 +361,13 @@ static int alsa_to_audfmt (snd_pcm_format_t alsafmt, AudioFormat *fmt, break; default: - dolog ("Unrecognized audio format %d\n", alsafmt); + warn_report("alsa: Unrecognized audio format %d", alsafmt); return -1; } return 0; } -static void alsa_dump_info (struct alsa_params_req *req, - struct alsa_params_obt *obt, - snd_pcm_format_t obtfmt, - AudiodevAlsaPerDirectionOptions *apdo) -{ - dolog("parameter | requested value | obtained value\n"); - dolog("format | %10d | %10d\n", req->fmt, obtfmt); - dolog("channels | %10d | %10d\n", - req->nchannels, obt->nchannels); - dolog("frequency | %10d | %10d\n", req->freq, obt->freq); - dolog("============================================\n"); - dolog("requested: buffer len %" PRId32 " period len %" PRId32 "\n", - apdo->buffer_length, apdo->period_length); - dolog("obtained: samples %ld\n", obt->samples); -} - static void alsa_set_threshold (snd_pcm_t *handle, snd_pcm_uframes_t threshold) { int err; @@ -394,23 +377,22 @@ static void alsa_set_threshold (snd_pcm_t *handle, snd_pcm_uframes_t threshold) err = snd_pcm_sw_params_current (handle, sw_params); if (err < 0) { - dolog ("Could not fully initialize DAC\n"); - alsa_logerr (err, "Failed to get current software parameters\n"); + error_report("alsa: Could not fully initialize DAC"); + alsa_logerr(err, "Failed to get current software parameters"); return; } err = snd_pcm_sw_params_set_start_threshold (handle, sw_params, threshold); if (err < 0) { - dolog ("Could not fully initialize DAC\n"); - alsa_logerr (err, "Failed to set software threshold to %ld\n", - threshold); + error_report("alsa: Could not fully initialize DAC"); + alsa_logerr(err, "Failed to set software threshold to %ld", threshold); return; } err = snd_pcm_sw_params (handle, sw_params); if (err < 0) { - dolog ("Could not fully initialize DAC\n"); - alsa_logerr (err, "Failed to set software parameters\n"); + error_report("alsa: Could not fully initialize DAC"); + alsa_logerr(err, "Failed to set software parameters"); return; } } @@ -442,13 +424,13 @@ static int alsa_open(bool in, struct alsa_params_req *req, SND_PCM_NONBLOCK ); if (err < 0) { - alsa_logerr2 (err, typ, "Failed to open `%s':\n", pcm_name); + alsa_logerr2(err, typ, "Failed to open `%s'", pcm_name); return -1; } err = snd_pcm_hw_params_any (handle, hw_params); if (err < 0) { - alsa_logerr2 (err, typ, "Failed to initialize hardware parameters\n"); + alsa_logerr2(err, typ, "Failed to initialize hardware parameters"); goto err; } @@ -458,18 +440,18 @@ static int alsa_open(bool in, struct alsa_params_req *req, SND_PCM_ACCESS_RW_INTERLEAVED ); if (err < 0) { - alsa_logerr2 (err, typ, "Failed to set access type\n"); + alsa_logerr2(err, typ, "Failed to set access type"); goto err; } err = snd_pcm_hw_params_set_format (handle, hw_params, req->fmt); if (err < 0) { - alsa_logerr2 (err, typ, "Failed to set format %d\n", req->fmt); + alsa_logerr2(err, typ, "Failed to set format %d", req->fmt); } err = snd_pcm_hw_params_set_rate_near (handle, hw_params, &freq, 0); if (err < 0) { - alsa_logerr2 (err, typ, "Failed to set frequency %d\n", req->freq); + alsa_logerr2(err, typ, "Failed to set frequency %d", req->freq); goto err; } @@ -479,8 +461,7 @@ static int alsa_open(bool in, struct alsa_params_req *req, &nchannels ); if (err < 0) { - alsa_logerr2 (err, typ, "Failed to set number of channels %d\n", - req->nchannels); + alsa_logerr2(err, typ, "Failed to set number of channels %d", req->nchannels); goto err; } @@ -492,14 +473,14 @@ static int alsa_open(bool in, struct alsa_params_req *req, handle, hw_params, &btime, &dir); if (err < 0) { - alsa_logerr2(err, typ, "Failed to set buffer time to %" PRId32 "\n", + alsa_logerr2(err, typ, "Failed to set buffer time to %" PRId32, apdo->buffer_length); goto err; } if (apdo->has_buffer_length && btime != apdo->buffer_length) { - dolog("Requested buffer time %" PRId32 - " was rejected, using %u\n", apdo->buffer_length, btime); + warn_report("alsa: Requested buffer time %" PRId32 " was rejected, using %u", + apdo->buffer_length, btime); } } @@ -511,43 +492,43 @@ static int alsa_open(bool in, struct alsa_params_req *req, &dir); if (err < 0) { - alsa_logerr2(err, typ, "Failed to set period time to %" PRId32 "\n", + alsa_logerr2(err, typ, "Failed to set period time to %" PRId32, apdo->period_length); goto err; } if (apdo->has_period_length && ptime != apdo->period_length) { - dolog("Requested period time %" PRId32 " was rejected, using %d\n", - apdo->period_length, ptime); + warn_report("alsa: Requested period time %" PRId32 " was rejected, using %d", + apdo->period_length, ptime); } } err = snd_pcm_hw_params (handle, hw_params); if (err < 0) { - alsa_logerr2 (err, typ, "Failed to apply audio parameters\n"); + alsa_logerr2(err, typ, "Failed to apply audio parameters"); goto err; } err = snd_pcm_hw_params_get_buffer_size (hw_params, &obt_buffer_size); if (err < 0) { - alsa_logerr2 (err, typ, "Failed to get buffer size\n"); + alsa_logerr2(err, typ, "Failed to get buffer size"); goto err; } err = snd_pcm_hw_params_get_format (hw_params, &obtfmt); if (err < 0) { - alsa_logerr2 (err, typ, "Failed to get format\n"); + alsa_logerr2(err, typ, "Failed to get format"); goto err; } if (alsa_to_audfmt (obtfmt, &obt->fmt, &obt->endianness)) { - dolog ("Invalid format was returned %d\n", obtfmt); + error_report("alsa: Invalid format was returned %d", obtfmt); goto err; } err = snd_pcm_prepare (handle); if (err < 0) { - alsa_logerr2 (err, typ, "Could not prepare handle %p\n", handle); + alsa_logerr2(err, typ, "Could not prepare handle %p", handle); goto err; } @@ -565,11 +546,9 @@ static int alsa_open(bool in, struct alsa_params_req *req, *handlep = handle; - if (DEBUG_ALSA || obtfmt != req->fmt || - obt->nchannels != req->nchannels || obt->freq != req->freq) { - dolog ("Audio parameters for %s\n", typ); - alsa_dump_info(req, obt, obtfmt, apdo); - } + trace_alsa_info_params(req->fmt, obtfmt, req->nchannels, obt->nchannels, + req->freq, obt->freq); + trace_alsa_info_samples(apdo->buffer_length, apdo->period_length, obt->samples); return 0; @@ -592,8 +571,7 @@ static size_t alsa_buffer_get_free(HWVoiceOut *hw) } } if (avail < 0) { - alsa_logerr(avail, - "Could not obtain number of available frames\n"); + alsa_logerr(avail, "Could not obtain number of available frames"); avail = 0; } } @@ -634,8 +612,7 @@ static size_t alsa_write(HWVoiceOut *hw, void *buf, size_t len) case -EPIPE: if (alsa_recover(alsa->handle)) { - alsa_logerr(written, "Failed to write %zu frames\n", - len_frames); + alsa_logerr(written, "Failed to write %zu frames", len_frames); return pos; } trace_alsa_xrun_out(); @@ -647,8 +624,7 @@ static size_t alsa_write(HWVoiceOut *hw, void *buf, size_t len) * recovery */ if (alsa_resume(alsa->handle)) { - alsa_logerr(written, "Failed to write %zu frames\n", - len_frames); + alsa_logerr(written, "Failed to write %zu frames", len_frames); return pos; } trace_alsa_resume_out(); @@ -658,8 +634,7 @@ static size_t alsa_write(HWVoiceOut *hw, void *buf, size_t len) return pos; default: - alsa_logerr(written, "Failed to write %zu frames from %p\n", - len, src); + alsa_logerr(written, "Failed to write %zu frames from %p", len_frames, src); return pos; } } @@ -678,21 +653,20 @@ static void alsa_fini_out (HWVoiceOut *hw) { ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; - ldebug ("alsa_fini\n"); + trace_alsa_fini_out(); alsa_anal_close (&alsa->handle, &alsa->pollhlp); } -static int alsa_init_out(HWVoiceOut *hw, struct audsettings *as, - void *drv_opaque) +static int alsa_init_out(HWVoiceOut *hw, struct audsettings *as) { ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; struct alsa_params_req req; struct alsa_params_obt obt; snd_pcm_t *handle; struct audsettings obt_as; - Audiodev *dev = drv_opaque; + Audiodev *dev = hw->s->dev; - req.fmt = aud_to_alsafmt (as->fmt, as->endianness); + req.fmt = aud_to_alsafmt (as->fmt, as->big_endian); req.freq = as->freq; req.nchannels = as->nchannels; @@ -703,14 +677,13 @@ static int alsa_init_out(HWVoiceOut *hw, struct audsettings *as, obt_as.freq = obt.freq; obt_as.nchannels = obt.nchannels; obt_as.fmt = obt.fmt; - obt_as.endianness = obt.endianness; + obt_as.big_endian = obt.endianness; audio_pcm_init_info (&hw->info, &obt_as); hw->samples = obt.samples; alsa->pollhlp.s = hw->s; alsa->handle = handle; - alsa->dev = dev; return 0; } @@ -725,19 +698,19 @@ static int alsa_voice_ctl (snd_pcm_t *handle, const char *typ, int ctl) if (ctl == VOICE_CTL_PAUSE) { err = snd_pcm_drop (handle); if (err < 0) { - alsa_logerr (err, "Could not stop %s\n", typ); + alsa_logerr(err, "Could not stop %s", typ); return -1; } } else { err = snd_pcm_prepare (handle); if (err < 0) { - alsa_logerr (err, "Could not prepare handle for %s\n", typ); + alsa_logerr(err, "Could not prepare handle for %s", typ); return -1; } if (ctl == VOICE_CTL_START) { err = snd_pcm_start(handle); if (err < 0) { - alsa_logerr (err, "Could not start handle for %s\n", typ); + alsa_logerr(err, "Could not start handle for %s", typ); return -1; } } @@ -749,19 +722,19 @@ static int alsa_voice_ctl (snd_pcm_t *handle, const char *typ, int ctl) static void alsa_enable_out(HWVoiceOut *hw, bool enable) { ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; - AudiodevAlsaPerDirectionOptions *apdo = alsa->dev->u.alsa.out; + AudiodevAlsaPerDirectionOptions *apdo = hw->s->dev->u.alsa.out; + + trace_alsa_enable_out(enable); if (enable) { bool poll_mode = apdo->try_poll; - ldebug("enabling voice\n"); if (poll_mode && alsa_poll_out(hw)) { poll_mode = 0; } hw->poll_mode = poll_mode; alsa_voice_ctl(alsa->handle, "playback", VOICE_CTL_PREPARE); } else { - ldebug("disabling voice\n"); if (hw->poll_mode) { hw->poll_mode = 0; alsa_fini_poll(&alsa->pollhlp); @@ -770,16 +743,16 @@ static void alsa_enable_out(HWVoiceOut *hw, bool enable) } } -static int alsa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) +static int alsa_init_in(HWVoiceIn *hw, struct audsettings *as) { ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; struct alsa_params_req req; struct alsa_params_obt obt; snd_pcm_t *handle; struct audsettings obt_as; - Audiodev *dev = drv_opaque; + Audiodev *dev = hw->s->dev; - req.fmt = aud_to_alsafmt (as->fmt, as->endianness); + req.fmt = aud_to_alsafmt (as->fmt, as->big_endian); req.freq = as->freq; req.nchannels = as->nchannels; @@ -790,14 +763,13 @@ static int alsa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) obt_as.freq = obt.freq; obt_as.nchannels = obt.nchannels; obt_as.fmt = obt.fmt; - obt_as.endianness = obt.endianness; + obt_as.big_endian = obt.endianness; audio_pcm_init_info (&hw->info, &obt_as); hw->samples = obt.samples; alsa->pollhlp.s = hw->s; alsa->handle = handle; - alsa->dev = dev; return 0; } @@ -828,7 +800,7 @@ static size_t alsa_read(HWVoiceIn *hw, void *buf, size_t len) case -EPIPE: if (alsa_recover(alsa->handle)) { - alsa_logerr(nread, "Failed to read %zu frames\n", len); + alsa_logerr(nread, "Failed to read %zu frames", len); return pos; } trace_alsa_xrun_in(); @@ -838,8 +810,7 @@ static size_t alsa_read(HWVoiceIn *hw, void *buf, size_t len) return pos; default: - alsa_logerr(nread, "Failed to read %zu frames to %p\n", - len, dst); + alsa_logerr(nread, "Failed to read %zu frames to %p", len, dst); return pos; } } @@ -854,12 +825,13 @@ static size_t alsa_read(HWVoiceIn *hw, void *buf, size_t len) static void alsa_enable_in(HWVoiceIn *hw, bool enable) { ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; - AudiodevAlsaPerDirectionOptions *apdo = alsa->dev->u.alsa.in; + AudiodevAlsaPerDirectionOptions *apdo = hw->s->dev->u.alsa.in; + + trace_alsa_enable_in(enable); if (enable) { bool poll_mode = apdo->try_poll; - ldebug("enabling voice\n"); if (poll_mode && alsa_poll_in(hw)) { poll_mode = 0; } @@ -867,7 +839,6 @@ static void alsa_enable_in(HWVoiceIn *hw, bool enable) alsa_voice_ctl(alsa->handle, "capture", VOICE_CTL_START); } else { - ldebug ("disabling voice\n"); if (hw->poll_mode) { hw->poll_mode = 0; alsa_fini_poll(&alsa->pollhlp); @@ -884,7 +855,8 @@ static void alsa_init_per_direction(AudiodevAlsaPerDirectionOptions *apdo) } } -static void *alsa_audio_init(Audiodev *dev, Error **errp) +static bool +audio_alsa_realize(AudioBackend *abe, Audiodev *dev, Error **errp) { AudiodevAlsaOptions *aopts; assert(dev->driver == AUDIODEV_DRIVER_ALSA); @@ -912,41 +884,44 @@ static void *alsa_audio_init(Audiodev *dev, Error **errp) dev->u.alsa.in->buffer_length = 92880; } - return dev; + return audio_alsa_parent_class->realize(abe, dev, errp); } -static void alsa_audio_fini (void *opaque) +static void audio_alsa_class_init(ObjectClass *klass, const void *data) { + AudioBackendClass *b = AUDIO_BACKEND_CLASS(klass); + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_CLASS(klass); + + audio_alsa_parent_class = AUDIO_BACKEND_CLASS(object_class_get_parent(klass)); + + b->realize = audio_alsa_realize; + k->max_voices_out = INT_MAX; + k->max_voices_in = INT_MAX; + k->voice_size_out = sizeof(ALSAVoiceOut); + k->voice_size_in = sizeof(ALSAVoiceIn); + + k->init_out = alsa_init_out; + k->fini_out = alsa_fini_out; + k->write = alsa_write; + k->buffer_get_free = alsa_buffer_get_free; + k->run_buffer_out = audio_generic_run_buffer_out; + k->enable_out = alsa_enable_out; + + k->init_in = alsa_init_in; + k->fini_in = alsa_fini_in; + k->read = alsa_read; + k->run_buffer_in = audio_generic_run_buffer_in; + k->enable_in = alsa_enable_in; } -static struct audio_pcm_ops alsa_pcm_ops = { - .init_out = alsa_init_out, - .fini_out = alsa_fini_out, - .write = alsa_write, - .buffer_get_free = alsa_buffer_get_free, - .run_buffer_out = audio_generic_run_buffer_out, - .enable_out = alsa_enable_out, - - .init_in = alsa_init_in, - .fini_in = alsa_fini_in, - .read = alsa_read, - .run_buffer_in = audio_generic_run_buffer_in, - .enable_in = alsa_enable_in, +static const TypeInfo audio_types[] = { + { + .name = TYPE_AUDIO_ALSA, + .parent = TYPE_AUDIO_MIXENG_BACKEND, + .instance_size = sizeof(AudioALSA), + .class_init = audio_alsa_class_init, + }, }; -static struct audio_driver alsa_audio_driver = { - .name = "alsa", - .init = alsa_audio_init, - .fini = alsa_audio_fini, - .pcm_ops = &alsa_pcm_ops, - .max_voices_out = INT_MAX, - .max_voices_in = INT_MAX, - .voice_size_out = sizeof (ALSAVoiceOut), - .voice_size_in = sizeof (ALSAVoiceIn) -}; - -static void register_audio_alsa(void) -{ - audio_driver_register(&alsa_audio_driver); -} -type_init(register_audio_alsa); +DEFINE_TYPES(audio_types) +module_obj(TYPE_AUDIO_ALSA); diff --git a/audio/audio-be.c b/audio/audio-be.c new file mode 100644 index 000000000000..ee065fa49688 --- /dev/null +++ b/audio/audio-be.c @@ -0,0 +1,274 @@ +/* SPDX-License-Identifier: MIT */ + +#include "qemu/osdep.h" +#include "qemu/audio.h" +#include "qemu/audio-capture.h" +#include "qapi/error.h" +#include "trace.h" +#include "qapi-types-audio.h" + +bool audio_be_check(AudioBackend **be, Error **errp) +{ + assert(be != NULL); + + if (!*be) { + *be = audio_get_default_audio_be(errp); + if (!*be) { + return false; + } + } + + return true; +} + +SWVoiceIn *audio_be_open_in( + AudioBackend *be, + SWVoiceIn *sw, + const char *name, + void *callback_opaque, + audio_callback_fn callback_fn, + const struct audsettings *as) +{ + AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be); + + assert(name != NULL); + assert(callback_fn != NULL); + assert(as != NULL); + + return klass->open_in(be, sw, name, callback_opaque, callback_fn, as); +} + +SWVoiceOut *audio_be_open_out( + AudioBackend *be, + SWVoiceOut *sw, + const char *name, + void *callback_opaque, + audio_callback_fn callback_fn, + const struct audsettings *as) +{ + AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be); + + assert(name != NULL); + assert(callback_fn != NULL); + assert(as != NULL); + + return klass->open_out(be, sw, name, callback_opaque, callback_fn, as); +} + +void audio_be_close_out(AudioBackend *be, SWVoiceOut *sw) +{ + AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be); + + if (!sw) { + return; + } + + return klass->close_out(be, sw); +} + +void audio_be_close_in(AudioBackend *be, SWVoiceIn *sw) +{ + AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be); + + if (!sw) { + return; + } + + return klass->close_in(be, sw); +} + +bool audio_be_is_active_out(AudioBackend *be, SWVoiceOut *sw) +{ + AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be); + + if (!sw) { + return false; + } + + return klass->is_active_out(be, sw); +} + +bool audio_be_is_active_in(AudioBackend *be, SWVoiceIn *sw) +{ + AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be); + + if (!sw) { + return false; + } + + return klass->is_active_in(be, sw); +} + +size_t audio_be_write(AudioBackend *be, SWVoiceOut *sw, void *buf, size_t size) +{ + AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be); + + if (!sw) { + return 0; + } + + return klass->write(be, sw, buf, size); +} + +size_t audio_be_read(AudioBackend *be, SWVoiceIn *sw, void *buf, size_t size) +{ + AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be); + + if (!sw) { + return 0; + } + + return klass->read(be, sw, buf, size); +} + +int audio_be_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw) +{ + AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be); + + if (!sw) { + return 0; + } + + return klass->get_buffer_size_out(be, sw); +} + +void audio_be_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on) +{ + AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be); + + trace_audio_be_set_active_out(sw, on); + + if (!sw) { + return; + } + + return klass->set_active_out(be, sw, on); +} + +void audio_be_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on) +{ + AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be); + + trace_audio_be_set_active_in(sw, on); + + if (!sw) { + return; + } + + return klass->set_active_in(be, sw, on); +} + +void audio_be_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol) +{ + AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be); + + if (!sw) { + return; + } + + klass->set_volume_out(be, sw, vol); +} + +void audio_be_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol) +{ + AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be); + + if (!sw) { + return; + } + + klass->set_volume_in(be, sw, vol); +} + +CaptureVoiceOut *audio_be_add_capture( + AudioBackend *be, + const struct audsettings *as, + const struct audio_capture_ops *ops, + void *cb_opaque) +{ + AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be); + + assert(as != NULL); + assert(ops != NULL); + + return klass->add_capture(be, as, ops, cb_opaque); +} + +void audio_be_del_capture(AudioBackend *be, CaptureVoiceOut *cap, void *cb_opaque) +{ + AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be); + + if (!cap) { + return; + } + + klass->del_capture(be, cap, cb_opaque); +} + +#ifdef CONFIG_GIO +bool audio_be_can_set_dbus_server(AudioBackend *be) +{ + AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be); + + return klass->set_dbus_server != NULL; +} + +bool audio_be_set_dbus_server(AudioBackend *be, + GDBusObjectManagerServer *server, + bool p2p, + Error **errp) +{ + AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be); + + assert(server != NULL); + + if (!audio_be_can_set_dbus_server(be)) { + error_setg(errp, "Audiodev '%s' is not compatible with DBus", + audio_be_get_id(be)); + return false; + } + + return klass->set_dbus_server(be, server, p2p, errp); +} +#endif + +const char *audio_be_get_id(AudioBackend *be) +{ + if (be) { + return AUDIO_BACKEND_GET_CLASS(be)->get_id(be); + } else { + return ""; + } +} + +AudioBackend *audio_be_new(Audiodev *dev, Error **errp) +{ + const char *drvname = AudiodevDriver_str(dev->driver); + g_autofree char *type = g_strconcat("audio-", drvname, NULL); + AudioBackend *be = AUDIO_BACKEND(object_new(type)); + + if (!be) { + error_setg(errp, "Unknown audio driver `%s'", drvname); + qapi_free_Audiodev(dev); + return NULL; + } + + if (!AUDIO_BACKEND_GET_CLASS(be)->realize(be, dev, errp)) { + object_unref(OBJECT(be)); + return NULL; + } + + return be; +} + +static const TypeInfo audio_types[] = { + { + .name = TYPE_AUDIO_BACKEND, + .parent = TYPE_OBJECT, + .instance_size = sizeof(AudioBackend), + .abstract = true, + .class_size = sizeof(AudioBackendClass), + }, +}; + +DEFINE_TYPES(audio_types) diff --git a/audio/audio-mixeng-be.c b/audio/audio-mixeng-be.c new file mode 100644 index 000000000000..5878b23e04ec --- /dev/null +++ b/audio/audio-mixeng-be.c @@ -0,0 +1,1770 @@ +/* + * SPDX-License-Identifier: MIT + * + * Copyright (c) 2003-2005 Vassili Karpov (malc) + */ + +#include "qemu/osdep.h" +#include "qemu/audio.h" +#include "migration/vmstate.h" +#include "qemu/bswap.h" +#include "qemu/timer.h" +#include "qapi/error.h" +#include "qapi/clone-visitor.h" +#include "qapi/qobject-input-visitor.h" +#include "qapi/qapi-visit-audio.h" +#include "qapi/qapi-commands-audio.h" +#include "qobject/qdict.h" +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/help_option.h" +#include "qom/object.h" +#include "system/system.h" +#include "system/replay.h" +#include "system/runstate.h" +#include "trace.h" +#include "trace/control.h" + +#include "audio_int.h" + +#define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown" + +#define audio_bug(fmt, ...) error_report("%s: " fmt, __func__, ##__VA_ARGS__) + +const struct mixeng_volume nominal_volume = { + .mute = 0, +#ifdef FLOAT_MIXENG + .r = 1.0, + .l = 1.0, +#else + .r = 1ULL << 32, + .l = 1ULL << 32, +#endif +}; + +/* + * Convert audio format to mixeng_clip index. Used by audio_pcm_sw_init_ and + * audio_mixeng_backend_add_capture() + */ +static int audio_format_to_index(AudioFormat af) +{ + switch (af) { + case AUDIO_FORMAT_U8: + case AUDIO_FORMAT_S8: + return 0; + case AUDIO_FORMAT_U16: + case AUDIO_FORMAT_S16: + return 1; + case AUDIO_FORMAT_U32: + case AUDIO_FORMAT_S32: + return 2; + case AUDIO_FORMAT_F32: + case AUDIO_FORMAT__MAX: + break; + } + + g_assert_not_reached(); +} + +static char *audsettings_to_string(const struct audsettings *as) +{ + return g_strdup_printf("frequency=%d nchannels=%d fmt=%s endian=%s", + as->freq, as->nchannels, AudioFormat_str(as->fmt), + as->big_endian ? "big" : "little"); +} + +static int audio_validate_settings (const struct audsettings *as) +{ + int invalid; + + invalid = as->nchannels < 1; + + switch (as->fmt) { + case AUDIO_FORMAT_S8: + case AUDIO_FORMAT_U8: + case AUDIO_FORMAT_S16: + case AUDIO_FORMAT_U16: + case AUDIO_FORMAT_S32: + case AUDIO_FORMAT_U32: + case AUDIO_FORMAT_F32: + break; + default: + invalid = 1; + break; + } + + invalid |= as->freq <= 0; + return invalid ? -1 : 0; +} + +static int audio_pcm_info_eq (struct audio_pcm_info *info, const struct audsettings *as) +{ + return info->af == as->fmt + && info->freq == as->freq + && info->nchannels == as->nchannels + && info->swap_endianness == (as->big_endian != HOST_BIG_ENDIAN); +} + +void audio_pcm_init_info (struct audio_pcm_info *info, const struct audsettings *as) +{ + info->af = as->fmt; + info->freq = as->freq; + info->nchannels = as->nchannels; + info->bytes_per_frame = as->nchannels * audio_format_bits(as->fmt) / 8; + info->bytes_per_second = info->freq * info->bytes_per_frame; + info->swap_endianness = (as->big_endian != HOST_BIG_ENDIAN); +} + +void audio_pcm_info_clear_buf(struct audio_pcm_info *info, void *buf, int len) +{ + if (!len) { + return; + } + + switch (info->af) { + case AUDIO_FORMAT_U8: + memset(buf, 0x80, len * info->bytes_per_frame); + break; + case AUDIO_FORMAT_U16: { + int i; + uint16_t *p = buf; + short s = INT16_MAX; + + if (info->swap_endianness) { + s = bswap16(s); + } + + for (i = 0; i < len * info->nchannels; i++) { + p[i] = s; + } + break; + } + case AUDIO_FORMAT_U32: { + int i; + uint32_t *p = buf; + int32_t s = INT32_MAX; + + if (info->swap_endianness) { + s = bswap32(s); + } + + for (i = 0; i < len * info->nchannels; i++) { + p[i] = s; + } + break; + } + case AUDIO_FORMAT_S8: + case AUDIO_FORMAT_S16: + case AUDIO_FORMAT_S32: + case AUDIO_FORMAT_F32: + memset(buf, 0x00, len * info->bytes_per_frame); + break; + case AUDIO_FORMAT__MAX: + g_assert_not_reached(); + } +} + +/* + * Capture + */ +static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioMixengBackend *s, + const struct audsettings *as) +{ + CaptureVoiceOut *cap; + + for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) { + if (audio_pcm_info_eq (&cap->hw.info, as)) { + return cap; + } + } + return NULL; +} + +static void audio_notify_capture (CaptureVoiceOut *cap, audcnotification_e cmd) +{ + struct capture_callback *cb; + + trace_audio_notify_capture(cmd); + + for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { + cb->ops.notify (cb->opaque, cmd); + } +} + +static void audio_capture_maybe_changed(CaptureVoiceOut *cap, bool enabled) +{ + if (cap->hw.enabled != enabled) { + audcnotification_e cmd; + cap->hw.enabled = enabled; + cmd = enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE; + audio_notify_capture (cap, cmd); + } +} + +static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap) +{ + HWVoiceOut *hw = &cap->hw; + SWVoiceOut *sw; + bool enabled = false; + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (sw->active) { + enabled = true; + break; + } + } + audio_capture_maybe_changed (cap, enabled); +} + +static void audio_detach_capture (HWVoiceOut *hw) +{ + SWVoiceCap *sc = hw->cap_head.lh_first; + + while (sc) { + SWVoiceCap *sc1 = sc->entries.le_next; + SWVoiceOut *sw = &sc->sw; + CaptureVoiceOut *cap = sc->cap; + int was_active = sw->active; + + g_clear_pointer(&sw->name, g_free); + if (sw->rate) { + st_rate_stop (sw->rate); + sw->rate = NULL; + } + + QLIST_REMOVE (sw, entries); + QLIST_REMOVE (sc, entries); + g_free (sc); + if (was_active) { + /* We have removed soft voice from the capture: + this might have changed the overall status of the capture + since this might have been the only active voice */ + audio_recalc_and_notify_capture (cap); + } + sc = sc1; + } +} + +static int audio_attach_capture (HWVoiceOut *hw) +{ + AudioMixengBackend *s = hw->s; + CaptureVoiceOut *cap; + + audio_detach_capture (hw); + for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) { + SWVoiceCap *sc; + SWVoiceOut *sw; + HWVoiceOut *hw_cap = &cap->hw; + + sc = g_malloc0(sizeof(*sc)); + + sc->cap = cap; + sw = &sc->sw; + sw->hw = hw_cap; + sw->info = hw->info; + sw->empty = true; + sw->active = hw->enabled; + sw->vol = nominal_volume; + sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq); + QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries); + QLIST_INSERT_HEAD (&hw->cap_head, sc, entries); + sw->name = g_strdup_printf("for %p %d,%s,%d", + hw, sw->info.freq, AudioFormat_str(sw->info.af), + sw->info.nchannels); + trace_audio_capture_attach(sw->name, sw->active); + if (sw->active) { + audio_capture_maybe_changed (cap, 1); + } + } + return 0; +} + +/* + * Hard voice (capture) + */ +static size_t audio_pcm_hw_find_min_in (HWVoiceIn *hw) +{ + SWVoiceIn *sw; + size_t m = hw->total_samples_captured; + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (sw->active) { + m = MIN (m, sw->total_hw_samples_acquired); + } + } + return m; +} + +static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw) +{ + size_t live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw); + if (live > hw->conv_buf.size) { + audio_bug("live=%zu hw->conv_buf.size=%zu", live, hw->conv_buf.size); + return 0; + } + return live; +} + +static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t samples) +{ + size_t conv = 0; + STSampleBuffer *conv_buf = &hw->conv_buf; + + while (samples) { + uint8_t *src = advance(pcm_buf, conv * hw->info.bytes_per_frame); + size_t proc = MIN(samples, conv_buf->size - conv_buf->pos); + + hw->conv(conv_buf->buffer + conv_buf->pos, src, proc); + conv_buf->pos = (conv_buf->pos + proc) % conv_buf->size; + samples -= proc; + conv += proc; + } + + return conv; +} + +/* + * Soft voice (capture) + */ +static void audio_pcm_sw_resample_in(SWVoiceIn *sw, + size_t frames_in_max, size_t frames_out_max, + size_t *total_in, size_t *total_out) +{ + HWVoiceIn *hw = sw->hw; + struct st_sample *src, *dst; + size_t live, rpos, frames_in, frames_out; + + live = hw->total_samples_captured - sw->total_hw_samples_acquired; + rpos = audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size); + + /* resample conv_buf from rpos to end of buffer */ + src = hw->conv_buf.buffer + rpos; + frames_in = MIN(frames_in_max, hw->conv_buf.size - rpos); + dst = sw->resample_buf.buffer; + frames_out = frames_out_max; + st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out); + rpos += frames_in; + *total_in = frames_in; + *total_out = frames_out; + + /* resample conv_buf from start of buffer if there are input frames left */ + if (frames_in_max - frames_in && rpos == hw->conv_buf.size) { + src = hw->conv_buf.buffer; + frames_in = frames_in_max - frames_in; + dst += frames_out; + frames_out = frames_out_max - frames_out; + st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out); + *total_in += frames_in; + *total_out += frames_out; + } +} + +static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len) +{ + HWVoiceIn *hw = sw->hw; + size_t live, frames_out_max, total_in, total_out; + + live = hw->total_samples_captured - sw->total_hw_samples_acquired; + if (!live) { + return 0; + } + if (live > hw->conv_buf.size) { + audio_bug("live=%zu hw->conv_buf.size=%zu", live, hw->conv_buf.size); + return 0; + } + + frames_out_max = MIN(buf_len / sw->info.bytes_per_frame, + sw->resample_buf.size); + + audio_pcm_sw_resample_in(sw, live, frames_out_max, &total_in, &total_out); + + if (!AUDIO_MIXENG_BACKEND_GET_CLASS(hw->s)->volume_in) { + mixeng_volume(sw->resample_buf.buffer, total_out, &sw->vol); + } + sw->clip(buf, sw->resample_buf.buffer, total_out); + + sw->total_hw_samples_acquired += total_in; + return total_out * sw->info.bytes_per_frame; +} + +/* + * Hard voice (playback) + */ +static size_t audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep) +{ + SWVoiceOut *sw; + size_t m = SIZE_MAX; + int nb_live = 0; + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (sw->active || !sw->empty) { + m = MIN (m, sw->total_hw_samples_mixed); + nb_live += 1; + } + } + + *nb_livep = nb_live; + return m; +} + +static size_t audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live) +{ + size_t smin; + int nb_live1; + + smin = audio_pcm_hw_find_min_out (hw, &nb_live1); + if (nb_live) { + *nb_live = nb_live1; + } + + if (nb_live1) { + size_t live = smin; + + if (live > hw->mix_buf.size) { + audio_bug("live=%zu hw->mix_buf.size=%zu", live, hw->mix_buf.size); + return 0; + } + return live; + } + return 0; +} + +static size_t audio_pcm_hw_get_free(HWVoiceOut *hw) +{ + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_GET_CLASS(hw->s); + + return (k->buffer_get_free ? k->buffer_get_free(hw) : INT_MAX) / + hw->info.bytes_per_frame; +} + +static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len) +{ + size_t clipped = 0; + size_t pos = hw->mix_buf.pos; + + while (len) { + st_sample *src = hw->mix_buf.buffer + pos; + uint8_t *dst = advance(pcm_buf, clipped * hw->info.bytes_per_frame); + size_t samples_till_end_of_buf = hw->mix_buf.size - pos; + size_t samples_to_clip = MIN(len, samples_till_end_of_buf); + + hw->clip(dst, src, samples_to_clip); + + pos = (pos + samples_to_clip) % hw->mix_buf.size; + len -= samples_to_clip; + clipped += samples_to_clip; + } +} + +/* + * Soft voice (playback) + */ +static void audio_pcm_sw_resample_out(SWVoiceOut *sw, + size_t frames_in_max, size_t frames_out_max, + size_t *total_in, size_t *total_out) +{ + HWVoiceOut *hw = sw->hw; + struct st_sample *src, *dst; + size_t live, wpos, frames_in, frames_out; + + live = sw->total_hw_samples_mixed; + wpos = (hw->mix_buf.pos + live) % hw->mix_buf.size; + + /* write to mix_buf from wpos to end of buffer */ + src = sw->resample_buf.buffer; + frames_in = frames_in_max; + dst = hw->mix_buf.buffer + wpos; + frames_out = MIN(frames_out_max, hw->mix_buf.size - wpos); + st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out); + wpos += frames_out; + *total_in = frames_in; + *total_out = frames_out; + + /* write to mix_buf from start of buffer if there are input frames left */ + if (frames_in_max - frames_in > 0 && wpos == hw->mix_buf.size) { + src += frames_in; + frames_in = frames_in_max - frames_in; + dst = hw->mix_buf.buffer; + frames_out = frames_out_max - frames_out; + st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out); + *total_in += frames_in; + *total_out += frames_out; + } +} + +static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len) +{ + HWVoiceOut *hw = sw->hw; + size_t live, dead, hw_free, sw_max, fe_max; + size_t frames_in_max, frames_out_max, total_in, total_out; + + live = sw->total_hw_samples_mixed; + if (live > hw->mix_buf.size) { + audio_bug("live=%zu hw->mix_buf.size=%zu", live, hw->mix_buf.size); + return 0; + } + + if (live == hw->mix_buf.size) { + trace_audio_out_full(sw->name, live); + return 0; + } + + dead = hw->mix_buf.size - live; + hw_free = audio_pcm_hw_get_free(hw); + hw_free = hw_free > live ? hw_free - live : 0; + frames_out_max = MIN(dead, hw_free); + sw_max = st_rate_frames_in(sw->rate, frames_out_max); + fe_max = MIN(buf_len / sw->info.bytes_per_frame + sw->resample_buf.pos, + sw->resample_buf.size); + frames_in_max = MIN(sw_max, fe_max); + + if (!frames_in_max) { + return 0; + } + + if (frames_in_max > sw->resample_buf.pos) { + sw->conv(sw->resample_buf.buffer + sw->resample_buf.pos, + buf, frames_in_max - sw->resample_buf.pos); + if (!AUDIO_MIXENG_BACKEND_GET_CLASS(sw->hw->s)->volume_out) { + mixeng_volume(sw->resample_buf.buffer + sw->resample_buf.pos, + frames_in_max - sw->resample_buf.pos, &sw->vol); + } + } + + audio_pcm_sw_resample_out(sw, frames_in_max, frames_out_max, + &total_in, &total_out); + + sw->total_hw_samples_mixed += total_out; + sw->empty = sw->total_hw_samples_mixed == 0; + + /* + * Upsampling may leave one audio frame in the resample buffer. Decrement + * total_in by one if there was a leftover frame from the previous resample + * pass in the resample buffer. Increment total_in by one if the current + * resample pass left one frame in the resample buffer. + */ + if (frames_in_max - total_in == 1) { + /* copy one leftover audio frame to the beginning of the buffer */ + *sw->resample_buf.buffer = *(sw->resample_buf.buffer + total_in); + total_in += 1 - sw->resample_buf.pos; + sw->resample_buf.pos = 1; + } else if (total_in >= sw->resample_buf.pos) { + total_in -= sw->resample_buf.pos; + sw->resample_buf.pos = 0; + } + + trace_audio_sw_write(SW_NAME(sw), buf_len / sw->info.bytes_per_frame, + total_in, sw->total_hw_samples_mixed); + + return total_in * sw->info.bytes_per_frame; +} + +#define DAC +#include "audio_template.h" +#undef DAC +#include "audio_template.h" + +/* + * Timer + */ +static int audio_is_timer_needed(AudioMixengBackend *s) +{ + HWVoiceIn *hwi = NULL; + HWVoiceOut *hwo = NULL; + + while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) { + if (!hwo->poll_mode) { + return 1; + } + } + while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) { + if (!hwi->poll_mode) { + return 1; + } + } + return 0; +} + +static void audio_reset_timer(AudioMixengBackend *s) +{ + if (audio_is_timer_needed(s)) { + timer_mod_anticipate_ns(s->ts, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks); + if (!s->timer_running) { + s->timer_running = true; + s->timer_last = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + trace_audio_timer_start(s->period_ticks / SCALE_MS); + } + } else { + timer_del(s->ts); + if (s->timer_running) { + s->timer_running = false; + trace_audio_timer_stop(); + } + } +} + +static void audio_timer (void *opaque) +{ + int64_t now, diff; + AudioMixengBackend *s = opaque; + + now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + diff = now - s->timer_last; + if (diff > s->period_ticks * 3 / 2) { + trace_audio_timer_delayed(diff / SCALE_MS); + } + s->timer_last = now; + + audio_run(s, "timer"); + audio_reset_timer(s); +} + +/* + * Public API + */ +static size_t audio_mixeng_backend_write(AudioBackend *be, SWVoiceOut *sw, + void *buf, size_t size) +{ + HWVoiceOut *hw; + + if (!sw) { + /* XXX: Consider options */ + return size; + } + hw = sw->hw; + + if (!hw->enabled) { + warn_report("audio: Writing to disabled voice %s", SW_NAME(sw)); + return 0; + } + + if (audio_get_pdo_out(hw->s->dev)->mixing_engine) { + return audio_pcm_sw_write(sw, buf, size); + } else { + return AUDIO_MIXENG_BACKEND_GET_CLASS(hw->s)->write(hw, buf, size); + } +} + +static size_t audio_mixeng_backend_read(AudioBackend *be, + SWVoiceIn *sw, void *buf, size_t size) +{ + HWVoiceIn *hw; + + if (!sw) { + /* XXX: Consider options */ + return size; + } + hw = sw->hw; + + if (!hw->enabled) { + warn_report("audio: Reading from disabled voice %s", SW_NAME(sw)); + return 0; + } + + if (audio_get_pdo_in(hw->s->dev)->mixing_engine) { + return audio_pcm_sw_read(sw, buf, size); + } else { + return AUDIO_MIXENG_BACKEND_GET_CLASS(hw->s)->read(hw, buf, size); + } + +} + +static int audio_mixeng_backend_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw) +{ + if (!sw) { + return 0; + } + + if (audio_get_pdo_out(sw->s->dev)->mixing_engine) { + return sw->resample_buf.size * sw->info.bytes_per_frame; + } + + return sw->hw->samples * sw->hw->info.bytes_per_frame; +} + +static void audio_mixeng_backend_set_active_out(AudioBackend *be, SWVoiceOut *sw, + bool on) +{ + HWVoiceOut *hw; + + if (!sw) { + return; + } + + hw = sw->hw; + if (sw->active != on) { + AudioMixengBackend *s = sw->s; + SWVoiceOut *temp_sw; + SWVoiceCap *sc; + + if (on) { + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_GET_CLASS(s); + + hw->pending_disable = 0; + if (!hw->enabled) { + hw->enabled = true; + if (runstate_is_running()) { + if (k->enable_out) { + k->enable_out(hw, true); + } + audio_reset_timer (s); + } + } + } else { + if (hw->enabled) { + int nb_active = 0; + + for (temp_sw = hw->sw_head.lh_first; temp_sw; + temp_sw = temp_sw->entries.le_next) { + nb_active += temp_sw->active != 0; + } + + hw->pending_disable = nb_active == 1; + } + } + + for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) { + sc->sw.active = hw->enabled; + if (hw->enabled) { + audio_capture_maybe_changed (sc->cap, 1); + } + } + sw->active = on; + } + +} + +static void audio_mixeng_backend_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on) +{ + HWVoiceIn *hw; + + if (!sw) { + return; + } + + hw = sw->hw; + if (sw->active != on) { + AudioMixengBackend *s = sw->s; + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_GET_CLASS(s); + SWVoiceIn *temp_sw; + + if (on) { + if (!hw->enabled) { + hw->enabled = true; + if (runstate_is_running()) { + if (k->enable_in) { + k->enable_in(hw, true); + } + audio_reset_timer (s); + } + } + sw->total_hw_samples_acquired = hw->total_samples_captured; + } else { + if (hw->enabled) { + int nb_active = 0; + + for (temp_sw = hw->sw_head.lh_first; temp_sw; + temp_sw = temp_sw->entries.le_next) { + nb_active += temp_sw->active != 0; + } + + if (nb_active == 1) { + hw->enabled = false; + if (k->enable_in) { + k->enable_in(hw, false); + } + } + } + } + sw->active = on; + } +} + +static size_t audio_get_avail(SWVoiceIn *sw) +{ + size_t live; + + if (!sw) { + return 0; + } + + live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired; + if (live > sw->hw->conv_buf.size) { + audio_bug("live=%zu sw->hw->conv_buf.size=%zu", live, + sw->hw->conv_buf.size); + return 0; + } + + trace_audio_get_avail(SW_NAME(sw), live, st_rate_frames_out(sw->rate, live)); + + return live; +} + +static size_t audio_get_free(SWVoiceOut *sw) +{ + size_t live, dead; + + if (!sw) { + return 0; + } + + live = sw->total_hw_samples_mixed; + + if (live > sw->hw->mix_buf.size) { + audio_bug("live=%zu sw->hw->mix_buf.size=%zu", live, + sw->hw->mix_buf.size); + return 0; + } + + dead = sw->hw->mix_buf.size - live; + + trace_audio_get_free(SW_NAME(sw), live, dead, + st_rate_frames_in(sw->rate, dead)); + + return dead; +} + +static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos, + size_t samples) +{ + size_t n; + + if (hw->enabled) { + SWVoiceCap *sc; + + for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) { + SWVoiceOut *sw = &sc->sw; + size_t rpos2 = rpos; + + n = samples; + while (n) { + size_t till_end_of_hw = hw->mix_buf.size - rpos2; + size_t to_read = MIN(till_end_of_hw, n); + size_t live, frames_in, frames_out; + + sw->resample_buf.buffer = hw->mix_buf.buffer + rpos2; + sw->resample_buf.size = to_read; + live = sw->total_hw_samples_mixed; + + audio_pcm_sw_resample_out(sw, + to_read, sw->hw->mix_buf.size - live, + &frames_in, &frames_out); + + sw->total_hw_samples_mixed += frames_out; + sw->empty = sw->total_hw_samples_mixed == 0; + + if (to_read - frames_in) { + audio_bug("Could not mix %zu frames into a capture " + "buffer, mixed %zu", to_read, frames_in); + break; + } + n -= to_read; + rpos2 = (rpos2 + to_read) % hw->mix_buf.size; + } + } + } + + n = MIN(samples, hw->mix_buf.size - rpos); + mixeng_clear(hw->mix_buf.buffer + rpos, n); + mixeng_clear(hw->mix_buf.buffer, samples - n); +} + +static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live) +{ + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_GET_CLASS(hw->s); + size_t clipped = 0; + + while (live) { + size_t size = live * hw->info.bytes_per_frame; + size_t decr, proc; + void *buf = k->get_buffer_out(hw, &size); + + if (size == 0) { + break; + } + + decr = MIN(size / hw->info.bytes_per_frame, live); + if (buf) { + audio_pcm_hw_clip_out(hw, buf, decr); + } + proc = k->put_buffer_out(hw, buf, decr * hw->info.bytes_per_frame) / + hw->info.bytes_per_frame; + + live -= proc; + clipped += proc; + hw->mix_buf.pos = (hw->mix_buf.pos + proc) % hw->mix_buf.size; + + if (proc == 0 || proc < decr) { + break; + } + } + + if (k->run_buffer_out) { + k->run_buffer_out(hw); + } + + return clipped; +} + +static void audio_run_out(AudioMixengBackend *s) +{ + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_GET_CLASS(s); + HWVoiceOut *hw = NULL; + SWVoiceOut *sw; + + while ((hw = audio_pcm_hw_find_any_enabled_out(s, hw))) { + size_t played, live, prev_rpos; + size_t hw_free = audio_pcm_hw_get_free(hw); + int nb_live; + + if (!audio_get_pdo_out(s->dev)->mixing_engine) { + /* there is exactly 1 sw for each hw with no mixeng */ + sw = hw->sw_head.lh_first; + + if (hw->pending_disable) { + hw->enabled = false; + hw->pending_disable = false; + if (k->enable_out) { + k->enable_out(hw, false); + } + } + + if (sw->active) { + sw->callback.fn(sw->callback.opaque, + hw_free * sw->info.bytes_per_frame); + } + + if (k->run_buffer_out) { + k->run_buffer_out(hw); + } + + continue; + } + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (sw->active) { + size_t sw_free = audio_get_free(sw); + size_t free; + + if (hw_free > sw->total_hw_samples_mixed) { + free = st_rate_frames_in(sw->rate, + MIN(sw_free, hw_free - sw->total_hw_samples_mixed)); + } else { + free = 0; + } + if (free > sw->resample_buf.pos) { + free = MIN(free, sw->resample_buf.size) + - sw->resample_buf.pos; + sw->callback.fn(sw->callback.opaque, + free * sw->info.bytes_per_frame); + } + } + } + + live = audio_pcm_hw_get_live_out (hw, &nb_live); + if (!nb_live) { + live = 0; + } + + if (live > hw->mix_buf.size) { + audio_bug("live=%zu hw->mix_buf.size=%zu", live, hw->mix_buf.size); + continue; + } + + if (hw->pending_disable && !nb_live) { + SWVoiceCap *sc; + + trace_audio_out_disable(); + hw->enabled = false; + hw->pending_disable = false; + if (k->enable_out) { + k->enable_out(hw, false); + } + for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) { + sc->sw.active = false; + audio_recalc_and_notify_capture (sc->cap); + } + continue; + } + + if (!live) { + if (k->run_buffer_out) { + k->run_buffer_out(hw); + } + continue; + } + + prev_rpos = hw->mix_buf.pos; + played = audio_pcm_hw_run_out(hw, live); + replay_audio_out(&played); + if (hw->mix_buf.pos >= hw->mix_buf.size) { + audio_bug("hw->mix_buf.pos=%zu hw->mix_buf.size=%zu played=%zu", + hw->mix_buf.pos, hw->mix_buf.size, played); + hw->mix_buf.pos = 0; + } + + trace_audio_out_played(played); + + if (played) { + audio_capture_mix_and_clear (hw, prev_rpos, played); + } + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (!sw->active && sw->empty) { + continue; + } + + if (played > sw->total_hw_samples_mixed) { + audio_bug("played=%zu sw->total_hw_samples_mixed=%zu", + played, sw->total_hw_samples_mixed); + played = sw->total_hw_samples_mixed; + } + + sw->total_hw_samples_mixed -= played; + + if (!sw->total_hw_samples_mixed) { + sw->empty = true; + } + } + } +} + +static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples) +{ + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_GET_CLASS(hw->s); + size_t conv = 0; + + if (k->run_buffer_in) { + k->run_buffer_in(hw); + } + + while (samples) { + size_t proc; + size_t size = samples * hw->info.bytes_per_frame; + void *buf = k->get_buffer_in(hw, &size); + + assert(size % hw->info.bytes_per_frame == 0); + if (size == 0) { + break; + } + + proc = audio_pcm_hw_conv_in(hw, buf, size / hw->info.bytes_per_frame); + + samples -= proc; + conv += proc; + k->put_buffer_in(hw, buf, proc * hw->info.bytes_per_frame); + } + + return conv; +} + +static void audio_run_in(AudioMixengBackend *s) +{ + HWVoiceIn *hw = NULL; + + if (!audio_get_pdo_in(s->dev)->mixing_engine) { + while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) { + /* there is exactly 1 sw for each hw with no mixeng */ + SWVoiceIn *sw = hw->sw_head.lh_first; + if (sw->active) { + sw->callback.fn(sw->callback.opaque, INT_MAX); + } + } + return; + } + + while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) { + SWVoiceIn *sw; + size_t captured = 0, min; + int pos; + + if (replay_mode != REPLAY_MODE_PLAY) { + captured = audio_pcm_hw_run_in( + hw, hw->conv_buf.size - audio_pcm_hw_get_live_in(hw)); + } + + replay_audio_in_start(&captured); + assert(captured <= hw->conv_buf.size); + if (replay_mode == REPLAY_MODE_PLAY) { + hw->conv_buf.pos = (hw->conv_buf.pos + captured) % hw->conv_buf.size; + } + for (pos = (hw->conv_buf.pos - captured + hw->conv_buf.size) % hw->conv_buf.size; + pos != hw->conv_buf.pos; + pos = (pos + 1) % hw->conv_buf.size) { + uint64_t left, right; + + if (replay_mode == REPLAY_MODE_RECORD) { + audio_sample_to_uint64(hw->conv_buf.buffer, pos, &left, &right); + } + replay_audio_in_sample_lr(&left, &right); + if (replay_mode == REPLAY_MODE_PLAY) { + audio_sample_from_uint64(hw->conv_buf.buffer, pos, left, right); + } + } + replay_audio_in_finish(); + + min = audio_pcm_hw_find_min_in (hw); + hw->total_samples_captured += captured - min; + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + sw->total_hw_samples_acquired -= min; + + if (sw->active) { + size_t sw_avail = audio_get_avail(sw); + size_t avail; + + avail = st_rate_frames_out(sw->rate, sw_avail); + if (avail > 0) { + avail = MIN(avail, sw->resample_buf.size); + sw->callback.fn(sw->callback.opaque, + avail * sw->info.bytes_per_frame); + } + } + } + } +} + +static void audio_run_capture(AudioMixengBackend *s) +{ + CaptureVoiceOut *cap; + + for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) { + size_t live, rpos, captured; + HWVoiceOut *hw = &cap->hw; + SWVoiceOut *sw; + + captured = live = audio_pcm_hw_get_live_out (hw, NULL); + rpos = hw->mix_buf.pos; + while (live) { + size_t left = hw->mix_buf.size - rpos; + size_t to_capture = MIN(live, left); + struct st_sample *src; + struct capture_callback *cb; + + src = hw->mix_buf.buffer + rpos; + hw->clip (cap->buf, src, to_capture); + mixeng_clear (src, to_capture); + + for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { + cb->ops.capture (cb->opaque, cap->buf, + to_capture * hw->info.bytes_per_frame); + } + rpos = (rpos + to_capture) % hw->mix_buf.size; + live -= to_capture; + } + hw->mix_buf.pos = rpos; + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (!sw->active && sw->empty) { + continue; + } + + if (captured > sw->total_hw_samples_mixed) { + audio_bug("captured=%zu sw->total_hw_samples_mixed=%zu", + captured, sw->total_hw_samples_mixed); + captured = sw->total_hw_samples_mixed; + } + + sw->total_hw_samples_mixed -= captured; + sw->empty = sw->total_hw_samples_mixed == 0; + } + } +} + +void audio_run(AudioMixengBackend *s, const char *msg) +{ + audio_run_out(s); + audio_run_in(s); + audio_run_capture(s); + + if (trace_event_get_state(TRACE_AUDIO_RUN_POLL)) { + /* Convert seconds to microseconds for trace event */ + int64_t elapsed_us = g_timer_elapsed(s->run_timer, NULL) * MICROSECONDS_PER_SECOND; + trace_audio_run_poll(msg, elapsed_us); + g_timer_start(s->run_timer); + } +} + +void audio_generic_run_buffer_in(HWVoiceIn *hw) +{ + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_GET_CLASS(hw->s); + + if (unlikely(!hw->buf_emul)) { + hw->size_emul = hw->samples * hw->info.bytes_per_frame; + hw->buf_emul = g_malloc(hw->size_emul); + hw->pos_emul = hw->pending_emul = 0; + } + + while (hw->pending_emul < hw->size_emul) { + size_t read_len = MIN(hw->size_emul - hw->pos_emul, + hw->size_emul - hw->pending_emul); + size_t read = k->read(hw, hw->buf_emul + hw->pos_emul, read_len); + hw->pending_emul += read; + hw->pos_emul = (hw->pos_emul + read) % hw->size_emul; + if (read < read_len) { + break; + } + } +} + +void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size) +{ + size_t start; + + start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul); + assert(start < hw->size_emul); + + *size = MIN(*size, hw->pending_emul); + *size = MIN(*size, hw->size_emul - start); + return hw->buf_emul + start; +} + +void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size) +{ + assert(size <= hw->pending_emul); + hw->pending_emul -= size; +} + +size_t audio_generic_buffer_get_free(HWVoiceOut *hw) +{ + if (hw->buf_emul) { + return hw->size_emul - hw->pending_emul; + } else { + return hw->samples * hw->info.bytes_per_frame; + } +} + +void audio_generic_run_buffer_out(HWVoiceOut *hw) +{ + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_GET_CLASS(hw->s); + + while (hw->pending_emul) { + size_t write_len, written, start; + + start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul); + assert(start < hw->size_emul); + + write_len = MIN(hw->pending_emul, hw->size_emul - start); + + written = k->write(hw, hw->buf_emul + start, write_len); + hw->pending_emul -= written; + + if (written < write_len) { + break; + } + } +} + +void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size) +{ + if (unlikely(!hw->buf_emul)) { + hw->size_emul = hw->samples * hw->info.bytes_per_frame; + hw->buf_emul = g_malloc(hw->size_emul); + hw->pos_emul = hw->pending_emul = 0; + } + + *size = MIN(hw->size_emul - hw->pending_emul, + hw->size_emul - hw->pos_emul); + return hw->buf_emul + hw->pos_emul; +} + +size_t audio_generic_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size) +{ + assert(buf == hw->buf_emul + hw->pos_emul && + size + hw->pending_emul <= hw->size_emul); + + hw->pending_emul += size; + hw->pos_emul = (hw->pos_emul + size) % hw->size_emul; + + return size; +} + +size_t audio_generic_write(HWVoiceOut *hw, void *buf, size_t size) +{ + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_GET_CLASS(hw->s); + size_t total = 0; + + if (k->buffer_get_free) { + size_t free = k->buffer_get_free(hw); + + size = MIN(size, free); + } + + while (total < size) { + size_t dst_size = size - total; + size_t copy_size, proc; + void *dst = k->get_buffer_out(hw, &dst_size); + + if (dst_size == 0) { + break; + } + + copy_size = MIN(size - total, dst_size); + if (dst) { + memcpy(dst, (char *)buf + total, copy_size); + } + proc = k->put_buffer_out(hw, dst, copy_size); + total += proc; + + if (proc == 0 || proc < copy_size) { + break; + } + } + + return total; +} + +size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size) +{ + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_GET_CLASS(hw->s); + size_t total = 0; + + if (k->run_buffer_in) { + k->run_buffer_in(hw); + } + + while (total < size) { + size_t src_size = size - total; + void *src = k->get_buffer_in(hw, &src_size); + + if (src_size == 0) { + break; + } + + memcpy((char *)buf + total, src, src_size); + k->put_buffer_in(hw, src, src_size); + total += src_size; + } + + return total; +} + +static bool audio_mixeng_backend_realize(AudioBackend *abe, + Audiodev *dev, Error **errp) +{ + AudioMixengBackend *be = AUDIO_MIXENG_BACKEND(abe); + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_GET_CLASS(be); + + be->dev = dev; + if (!k->get_buffer_in) { + k->get_buffer_in = audio_generic_get_buffer_in; + k->put_buffer_in = audio_generic_put_buffer_in; + } + if (!k->get_buffer_out) { + k->get_buffer_out = audio_generic_get_buffer_out; + k->put_buffer_out = audio_generic_put_buffer_out; + } + + audio_init_nb_voices_out(be, k, 1); + audio_init_nb_voices_in(be, k, 0); + + if (be->dev->timer_period <= 0) { + be->period_ticks = 1; + } else { + be->period_ticks = be->dev->timer_period * (int64_t)SCALE_US; + } + + return true; +} + +static void audio_vm_change_state_handler (void *opaque, bool running, + RunState state) +{ + AudioMixengBackend *s = opaque; + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_GET_CLASS(s); + HWVoiceOut *hwo = NULL; + HWVoiceIn *hwi = NULL; + + while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) { + if (k->enable_out) { + k->enable_out(hwo, running); + } + } + + while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) { + if (k->enable_in) { + k->enable_in(hwi, running); + } + } + audio_reset_timer (s); +} + +static const VMStateDescription vmstate_audio; + +static const char *audio_mixeng_backend_get_id(AudioBackend *be) +{ + return AUDIO_MIXENG_BACKEND(be)->dev->id; +} + +static CaptureVoiceOut *audio_mixeng_backend_add_capture( + AudioBackend *be, + const struct audsettings *as, + const struct audio_capture_ops *ops, + void *cb_opaque); + +static void audio_mixeng_backend_del_capture( + AudioBackend *be, + CaptureVoiceOut *cap, + void *cb_opaque); + +static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw, + Volume *vol); +static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw, + Volume *vol); + +static void audio_mixeng_backend_class_init(ObjectClass *klass, const void *data) +{ + AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass); + + be->realize = audio_mixeng_backend_realize; + be->get_id = audio_mixeng_backend_get_id; + be->open_in = audio_mixeng_backend_open_in; + be->open_out = audio_mixeng_backend_open_out; + be->close_in = audio_mixeng_backend_close_in; + be->close_out = audio_mixeng_backend_close_out; + be->is_active_out = audio_mixeng_backend_is_active_out; + be->is_active_in = audio_mixeng_backend_is_active_in; + be->set_active_out = audio_mixeng_backend_set_active_out; + be->set_active_in = audio_mixeng_backend_set_active_in; + be->set_volume_out = audio_mixeng_backend_set_volume_out; + be->set_volume_in = audio_mixeng_backend_set_volume_in; + be->read = audio_mixeng_backend_read; + be->write = audio_mixeng_backend_write; + be->get_buffer_size_out = audio_mixeng_backend_get_buffer_size_out; + be->add_capture = audio_mixeng_backend_add_capture; + be->del_capture = audio_mixeng_backend_del_capture; +} + +static void audio_mixeng_backend_init(Object *obj) +{ + AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj); + + QLIST_INIT(&s->hw_head_out); + QLIST_INIT(&s->hw_head_in); + QLIST_INIT(&s->cap_head); + s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s); + s->run_timer = g_timer_new(); + + s->vmse = qemu_add_vm_change_state_handler(audio_vm_change_state_handler, s); + assert(s->vmse != NULL); + + vmstate_register_any(NULL, &vmstate_audio, s); +} + +static void audio_mixeng_backend_finalize(Object *obj) +{ + AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj); + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_GET_CLASS(s); + HWVoiceOut *hwo, *hwon; + HWVoiceIn *hwi, *hwin; + + QLIST_FOREACH_SAFE(hwo, &s->hw_head_out, entries, hwon) { + SWVoiceCap *sc; + + if (hwo->enabled && k->enable_out) { + k->enable_out(hwo, false); + } + k->fini_out(hwo); + + for (sc = hwo->cap_head.lh_first; sc; sc = sc->entries.le_next) { + CaptureVoiceOut *cap = sc->cap; + struct capture_callback *cb; + + for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { + cb->ops.destroy (cb->opaque); + } + } + QLIST_REMOVE(hwo, entries); + } + + QLIST_FOREACH_SAFE(hwi, &s->hw_head_in, entries, hwin) { + if (hwi->enabled && k->enable_in) { + k->enable_in(hwi, false); + } + k->fini_in(hwi); + QLIST_REMOVE(hwi, entries); + } + + if (s->dev) { + qapi_free_Audiodev(s->dev); + s->dev = NULL; + } + + if (s->ts) { + timer_free(s->ts); + s->ts = NULL; + } + + g_clear_pointer(&s->run_timer, g_timer_destroy); + + if (s->vmse) { + qemu_del_vm_change_state_handler(s->vmse); + s->vmse = NULL; + } + + vmstate_unregister(NULL, &vmstate_audio, s); +} + +static bool vmstate_audio_needed(void *opaque) +{ + /* + * Never needed, this vmstate only exists in case + * an old qemu sends it to us. + */ + return false; +} + +static const VMStateDescription vmstate_audio = { + .name = "audio", + .version_id = 1, + .minimum_version_id = 1, + .needed = vmstate_audio_needed, + .fields = (const VMStateField[]) { + VMSTATE_END_OF_LIST() + } +}; + +static CaptureVoiceOut *audio_mixeng_backend_add_capture( + AudioBackend *be, + const struct audsettings *as, + const struct audio_capture_ops *ops, + void *cb_opaque) +{ + AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(be); + CaptureVoiceOut *cap; + struct capture_callback *cb; + + if (!s) { + error_report("Capturing without setting an audiodev is not supported"); + abort(); + } + + if (!audio_get_pdo_out(s->dev)->mixing_engine) { + error_report("audio: Can't capture with mixeng disabled"); + return NULL; + } + + if (audio_validate_settings(as)) { + g_autofree char *str = audsettings_to_string(as); + error_report("audio: Invalid audio settings when trying to add capture: %s", str); + return NULL; + } + + cb = g_malloc0(sizeof(*cb)); + cb->ops = *ops; + cb->opaque = cb_opaque; + + cap = audio_pcm_capture_find_specific(s, as); + if (cap) { + QLIST_INSERT_HEAD (&cap->cb_head, cb, entries); + } else { + HWVoiceOut *hw; + + cap = g_malloc0(sizeof(*cap)); + + hw = &cap->hw; + hw->s = s; + QLIST_INIT (&hw->sw_head); + QLIST_INIT (&cap->cb_head); + + /* XXX find a more elegant way */ + hw->samples = 4096 * 4; + audio_pcm_hw_alloc_resources_out(hw); + + audio_pcm_init_info (&hw->info, as); + + cap->buf = g_malloc0_n(hw->mix_buf.size, hw->info.bytes_per_frame); + + if (audio_format_is_float(hw->info.af)) { + hw->clip = mixeng_clip_float[hw->info.nchannels == 2] + [hw->info.swap_endianness]; + } else { + hw->clip = mixeng_clip + [hw->info.nchannels == 2] + [audio_format_is_signed(hw->info.af)] + [hw->info.swap_endianness] + [audio_format_to_index(hw->info.af)]; + } + + QLIST_INSERT_HEAD (&s->cap_head, cap, entries); + QLIST_INSERT_HEAD (&cap->cb_head, cb, entries); + + QLIST_FOREACH(hw, &s->hw_head_out, entries) { + audio_attach_capture (hw); + } + } + + return cap; +} + +static void audio_mixeng_backend_del_capture( + AudioBackend *be, + CaptureVoiceOut *cap, + void *cb_opaque) +{ + struct capture_callback *cb; + + for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { + if (cb->opaque == cb_opaque) { + cb->ops.destroy (cb_opaque); + QLIST_REMOVE (cb, entries); + g_free (cb); + + if (!cap->cb_head.lh_first) { + SWVoiceOut *sw = cap->hw.sw_head.lh_first, *sw1; + + while (sw) { + SWVoiceCap *sc = (SWVoiceCap *) sw; + + trace_audio_capture_free_sw(sw->name); + g_clear_pointer(&sw->name, g_free); + sw1 = sw->entries.le_next; + if (sw->rate) { + st_rate_stop (sw->rate); + sw->rate = NULL; + } + QLIST_REMOVE (sw, entries); + QLIST_REMOVE (sc, entries); + g_free (sc); + sw = sw1; + } + QLIST_REMOVE (cap, entries); + g_free(cap->hw.mix_buf.buffer); + g_free (cap->buf); + g_free (cap); + } + return; + } + } +} + +static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw, + Volume *vol) +{ + if (sw) { + HWVoiceOut *hw = sw->hw; + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_GET_CLASS(hw->s); + + sw->vol.mute = vol->mute; + sw->vol.l = nominal_volume.l * vol->vol[0] / 255; + sw->vol.r = nominal_volume.r * vol->vol[vol->channels > 1 ? 1 : 0] / + 255; + + if (k->volume_out) { + k->volume_out(hw, vol); + } + } +} + +static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw, + Volume *vol) +{ + if (sw) { + HWVoiceIn *hw = sw->hw; + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_GET_CLASS(hw->s); + + sw->vol.mute = vol->mute; + sw->vol.l = nominal_volume.l * vol->vol[0] / 255; + sw->vol.r = nominal_volume.r * vol->vol[vol->channels > 1 ? 1 : 0] / + 255; + + if (k->volume_in) { + k->volume_in(hw, vol); + } + } +} + +audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo) +{ + return (audsettings) { + .freq = pdo->frequency, + .nchannels = pdo->channels, + .fmt = pdo->format, + .big_endian = HOST_BIG_ENDIAN, + }; +} + +/* frames = freq * usec / 1e6 */ +int audio_buffer_frames(AudiodevPerDirectionOptions *pdo, + audsettings *as, int def_usecs) +{ + uint64_t usecs = pdo->has_buffer_length ? pdo->buffer_length : def_usecs; + return (as->freq * usecs + 500000) / 1000000; +} + +/* samples = channels * frames = channels * freq * usec / 1e6 */ +int audio_buffer_samples(AudiodevPerDirectionOptions *pdo, + audsettings *as, int def_usecs) +{ + return as->nchannels * audio_buffer_frames(pdo, as, def_usecs); +} + +/* + * bytes = bytes_per_sample * samples = + * bytes_per_sample * channels * freq * usec / 1e6 + */ +int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo, + audsettings *as, int def_usecs) +{ + return audio_buffer_samples(pdo, as, def_usecs) * audio_format_bits(as->fmt) / 8; +} + +void audio_rate_start(RateCtl *rate) +{ + memset(rate, 0, sizeof(RateCtl)); + rate->start_ticks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +} + +size_t audio_rate_peek_bytes(RateCtl *rate, struct audio_pcm_info *info) +{ + int64_t now; + int64_t ticks; + int64_t bytes; + int64_t frames; + + now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + ticks = now - rate->start_ticks; + bytes = muldiv64(ticks, info->bytes_per_second, NANOSECONDS_PER_SECOND); + frames = (bytes - rate->bytes_sent) / info->bytes_per_frame; + rate->peeked_frames = frames; + + return frames < 0 ? 0 : frames * info->bytes_per_frame; +} + +void audio_rate_add_bytes(RateCtl *rate, size_t bytes_used) +{ + if (rate->peeked_frames < 0 || rate->peeked_frames > 65536) { + trace_audio_rate_reset(rate->peeked_frames); + audio_rate_start(rate); + } + + rate->bytes_sent += bytes_used; +} + +size_t audio_rate_get_bytes(RateCtl *rate, struct audio_pcm_info *info, + size_t bytes_avail) +{ + size_t bytes; + + bytes = audio_rate_peek_bytes(rate, info); + bytes = MIN(bytes, bytes_avail); + audio_rate_add_bytes(rate, bytes); + + return bytes; +} + +static const TypeInfo audio_types[] = { + { + .name = TYPE_AUDIO_MIXENG_BACKEND, + .parent = TYPE_AUDIO_BACKEND, + .instance_size = sizeof(AudioMixengBackend), + .instance_init = audio_mixeng_backend_init, + .instance_finalize = audio_mixeng_backend_finalize, + .class_size = sizeof(AudioMixengBackendClass), + .class_init = audio_mixeng_backend_class_init, + }, +}; + +DEFINE_TYPES(audio_types) diff --git a/audio/audio.c b/audio/audio.c index b7a28f79d4fa..6f10ee470ee4 100644 --- a/audio/audio.c +++ b/audio/audio.c @@ -1,1690 +1,55 @@ -/* - * QEMU Audio subsystem - * - * Copyright (c) 2003-2005 Vassili Karpov (malc) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "qemu/osdep.h" -#include "qemu/audio.h" -#include "migration/vmstate.h" -#include "qemu/timer.h" -#include "qapi/error.h" -#include "qapi/clone-visitor.h" -#include "qapi/qobject-input-visitor.h" -#include "qapi/qapi-visit-audio.h" -#include "qapi/qapi-commands-audio.h" -#include "qobject/qdict.h" -#include "qemu/bswap.h" -#include "qemu/error-report.h" -#include "qemu/log.h" -#include "qemu/module.h" -#include "qemu/help_option.h" -#include "system/system.h" -#include "system/replay.h" -#include "system/runstate.h" -#include "trace.h" - -#define AUDIO_CAP "audio" -#include "audio_int.h" - -/* #define DEBUG_LIVE */ -/* #define DEBUG_OUT */ -/* #define DEBUG_CAPTURE */ -/* #define DEBUG_POLL */ - -#define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown" - - -/* Order of CONFIG_AUDIO_DRIVERS is import. - The 1st one is the one used by default, that is the reason - that we generate the list. -*/ -const char *audio_prio_list[] = { - "spice", - CONFIG_AUDIO_DRIVERS - "none", - NULL -}; - -static QLIST_HEAD(, audio_driver) audio_drivers; -static AudiodevListHead audiodevs = - QSIMPLEQ_HEAD_INITIALIZER(audiodevs); -static AudiodevListHead default_audiodevs = - QSIMPLEQ_HEAD_INITIALIZER(default_audiodevs); - - -void audio_driver_register(audio_driver *drv) -{ - QLIST_INSERT_HEAD(&audio_drivers, drv, next); -} - -static audio_driver *audio_driver_lookup(const char *name) -{ - struct audio_driver *d; - Error *local_err = NULL; - int rv; - - QLIST_FOREACH(d, &audio_drivers, next) { - if (strcmp(name, d->name) == 0) { - return d; - } - } - rv = audio_module_load(name, &local_err); - if (rv > 0) { - QLIST_FOREACH(d, &audio_drivers, next) { - if (strcmp(name, d->name) == 0) { - return d; - } - } - } else if (rv < 0) { - error_report_err(local_err); - } - return NULL; -} - -static AudioBackend *default_audio_be; - -const struct mixeng_volume nominal_volume = { - .mute = 0, -#ifdef FLOAT_MIXENG - .r = 1.0, - .l = 1.0, -#else - .r = 1ULL << 32, - .l = 1ULL << 32, -#endif -}; - -int audio_bug (const char *funcname, int cond) -{ - if (cond) { - static int shown; - - AUD_log (NULL, "A bug was just triggered in %s\n", funcname); - if (!shown) { - shown = 1; - AUD_log (NULL, "Save all your work and restart without audio\n"); - AUD_log (NULL, "I am sorry\n"); - } - AUD_log (NULL, "Context:\n"); - } - - return cond; -} - -static inline int audio_bits_to_index (int bits) -{ - switch (bits) { - case 8: - return 0; - - case 16: - return 1; - - case 32: - return 2; - - default: - audio_bug ("bits_to_index", 1); - AUD_log (NULL, "invalid bits %d\n", bits); - return 0; - } -} - -void AUD_vlog (const char *cap, const char *fmt, va_list ap) -{ - if (cap) { - fprintf(stderr, "%s: ", cap); - } - - vfprintf(stderr, fmt, ap); -} - -void AUD_log (const char *cap, const char *fmt, ...) -{ - va_list ap; - - va_start (ap, fmt); - AUD_vlog (cap, fmt, ap); - va_end (ap); -} - -static void audio_print_settings (struct audsettings *as) -{ - dolog ("frequency=%d nchannels=%d fmt=", as->freq, as->nchannels); - - switch (as->fmt) { - case AUDIO_FORMAT_S8: - AUD_log (NULL, "S8"); - break; - case AUDIO_FORMAT_U8: - AUD_log (NULL, "U8"); - break; - case AUDIO_FORMAT_S16: - AUD_log (NULL, "S16"); - break; - case AUDIO_FORMAT_U16: - AUD_log (NULL, "U16"); - break; - case AUDIO_FORMAT_S32: - AUD_log (NULL, "S32"); - break; - case AUDIO_FORMAT_U32: - AUD_log (NULL, "U32"); - break; - case AUDIO_FORMAT_F32: - AUD_log (NULL, "F32"); - break; - default: - AUD_log (NULL, "invalid(%d)", as->fmt); - break; - } - - AUD_log (NULL, " endianness="); - switch (as->endianness) { - case 0: - AUD_log (NULL, "little"); - break; - case 1: - AUD_log (NULL, "big"); - break; - default: - AUD_log (NULL, "invalid"); - break; - } - AUD_log (NULL, "\n"); -} - -static int audio_validate_settings (struct audsettings *as) -{ - int invalid; - - invalid = as->nchannels < 1; - invalid |= as->endianness != 0 && as->endianness != 1; - - switch (as->fmt) { - case AUDIO_FORMAT_S8: - case AUDIO_FORMAT_U8: - case AUDIO_FORMAT_S16: - case AUDIO_FORMAT_U16: - case AUDIO_FORMAT_S32: - case AUDIO_FORMAT_U32: - case AUDIO_FORMAT_F32: - break; - default: - invalid = 1; - break; - } - - invalid |= as->freq <= 0; - return invalid ? -1 : 0; -} - -static int audio_pcm_info_eq (struct audio_pcm_info *info, struct audsettings *as) -{ - int bits = 8; - bool is_signed = false, is_float = false; - - switch (as->fmt) { - case AUDIO_FORMAT_S8: - is_signed = true; - /* fall through */ - case AUDIO_FORMAT_U8: - break; - - case AUDIO_FORMAT_S16: - is_signed = true; - /* fall through */ - case AUDIO_FORMAT_U16: - bits = 16; - break; - - case AUDIO_FORMAT_F32: - is_float = true; - /* fall through */ - case AUDIO_FORMAT_S32: - is_signed = true; - /* fall through */ - case AUDIO_FORMAT_U32: - bits = 32; - break; - - default: - abort(); - } - return info->freq == as->freq - && info->nchannels == as->nchannels - && info->is_signed == is_signed - && info->is_float == is_float - && info->bits == bits - && info->swap_endianness == (as->endianness != HOST_BIG_ENDIAN); -} - -void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as) -{ - int bits = 8, mul; - bool is_signed = false, is_float = false; - - switch (as->fmt) { - case AUDIO_FORMAT_S8: - is_signed = true; - /* fall through */ - case AUDIO_FORMAT_U8: - mul = 1; - break; - - case AUDIO_FORMAT_S16: - is_signed = true; - /* fall through */ - case AUDIO_FORMAT_U16: - bits = 16; - mul = 2; - break; - - case AUDIO_FORMAT_F32: - is_float = true; - /* fall through */ - case AUDIO_FORMAT_S32: - is_signed = true; - /* fall through */ - case AUDIO_FORMAT_U32: - bits = 32; - mul = 4; - break; - - default: - abort(); - } - - info->freq = as->freq; - info->bits = bits; - info->is_signed = is_signed; - info->is_float = is_float; - info->nchannels = as->nchannels; - info->bytes_per_frame = as->nchannels * mul; - info->bytes_per_second = info->freq * info->bytes_per_frame; - info->swap_endianness = (as->endianness != HOST_BIG_ENDIAN); -} - -void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len) -{ - if (!len) { - return; - } - - if (info->is_signed || info->is_float) { - memset(buf, 0x00, len * info->bytes_per_frame); - } else { - switch (info->bits) { - case 8: - memset(buf, 0x80, len * info->bytes_per_frame); - break; - - case 16: - { - int i; - uint16_t *p = buf; - short s = INT16_MAX; - - if (info->swap_endianness) { - s = bswap16 (s); - } - - for (i = 0; i < len * info->nchannels; i++) { - p[i] = s; - } - } - break; - - case 32: - { - int i; - uint32_t *p = buf; - int32_t s = INT32_MAX; - - if (info->swap_endianness) { - s = bswap32 (s); - } - - for (i = 0; i < len * info->nchannels; i++) { - p[i] = s; - } - } - break; - - default: - AUD_log (NULL, "audio_pcm_info_clear_buf: invalid bits %d\n", - info->bits); - break; - } - } -} - -/* - * Capture - */ -static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioBackend *s, - struct audsettings *as) -{ - CaptureVoiceOut *cap; - - for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) { - if (audio_pcm_info_eq (&cap->hw.info, as)) { - return cap; - } - } - return NULL; -} - -static void audio_notify_capture (CaptureVoiceOut *cap, audcnotification_e cmd) -{ - struct capture_callback *cb; - -#ifdef DEBUG_CAPTURE - dolog ("notification %d sent\n", cmd); -#endif - for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { - cb->ops.notify (cb->opaque, cmd); - } -} - -static void audio_capture_maybe_changed(CaptureVoiceOut *cap, bool enabled) -{ - if (cap->hw.enabled != enabled) { - audcnotification_e cmd; - cap->hw.enabled = enabled; - cmd = enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE; - audio_notify_capture (cap, cmd); - } -} - -static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap) -{ - HWVoiceOut *hw = &cap->hw; - SWVoiceOut *sw; - bool enabled = false; - - for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { - if (sw->active) { - enabled = true; - break; - } - } - audio_capture_maybe_changed (cap, enabled); -} - -static void audio_detach_capture (HWVoiceOut *hw) -{ - SWVoiceCap *sc = hw->cap_head.lh_first; - - while (sc) { - SWVoiceCap *sc1 = sc->entries.le_next; - SWVoiceOut *sw = &sc->sw; - CaptureVoiceOut *cap = sc->cap; - int was_active = sw->active; - - if (sw->rate) { - st_rate_stop (sw->rate); - sw->rate = NULL; - } - - QLIST_REMOVE (sw, entries); - QLIST_REMOVE (sc, entries); - g_free (sc); - if (was_active) { - /* We have removed soft voice from the capture: - this might have changed the overall status of the capture - since this might have been the only active voice */ - audio_recalc_and_notify_capture (cap); - } - sc = sc1; - } -} - -static int audio_attach_capture (HWVoiceOut *hw) -{ - AudioBackend *s = hw->s; - CaptureVoiceOut *cap; - - audio_detach_capture (hw); - for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) { - SWVoiceCap *sc; - SWVoiceOut *sw; - HWVoiceOut *hw_cap = &cap->hw; - - sc = g_malloc0(sizeof(*sc)); - - sc->cap = cap; - sw = &sc->sw; - sw->hw = hw_cap; - sw->info = hw->info; - sw->empty = true; - sw->active = hw->enabled; - sw->vol = nominal_volume; - sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq); - QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries); - QLIST_INSERT_HEAD (&hw->cap_head, sc, entries); -#ifdef DEBUG_CAPTURE - sw->name = g_strdup_printf ("for %p %d,%d,%d", - hw, sw->info.freq, sw->info.bits, - sw->info.nchannels); - dolog ("Added %s active = %d\n", sw->name, sw->active); -#endif - if (sw->active) { - audio_capture_maybe_changed (cap, 1); - } - } - return 0; -} - -/* - * Hard voice (capture) - */ -static size_t audio_pcm_hw_find_min_in (HWVoiceIn *hw) -{ - SWVoiceIn *sw; - size_t m = hw->total_samples_captured; - - for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { - if (sw->active) { - m = MIN (m, sw->total_hw_samples_acquired); - } - } - return m; -} - -static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw) -{ - size_t live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw); - if (audio_bug(__func__, live > hw->conv_buf.size)) { - dolog("live=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size); - return 0; - } - return live; -} - -static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t samples) -{ - size_t conv = 0; - STSampleBuffer *conv_buf = &hw->conv_buf; - - while (samples) { - uint8_t *src = advance(pcm_buf, conv * hw->info.bytes_per_frame); - size_t proc = MIN(samples, conv_buf->size - conv_buf->pos); - - hw->conv(conv_buf->buffer + conv_buf->pos, src, proc); - conv_buf->pos = (conv_buf->pos + proc) % conv_buf->size; - samples -= proc; - conv += proc; - } - - return conv; -} - -/* - * Soft voice (capture) - */ -static void audio_pcm_sw_resample_in(SWVoiceIn *sw, - size_t frames_in_max, size_t frames_out_max, - size_t *total_in, size_t *total_out) -{ - HWVoiceIn *hw = sw->hw; - struct st_sample *src, *dst; - size_t live, rpos, frames_in, frames_out; - - live = hw->total_samples_captured - sw->total_hw_samples_acquired; - rpos = audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size); - - /* resample conv_buf from rpos to end of buffer */ - src = hw->conv_buf.buffer + rpos; - frames_in = MIN(frames_in_max, hw->conv_buf.size - rpos); - dst = sw->resample_buf.buffer; - frames_out = frames_out_max; - st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out); - rpos += frames_in; - *total_in = frames_in; - *total_out = frames_out; - - /* resample conv_buf from start of buffer if there are input frames left */ - if (frames_in_max - frames_in && rpos == hw->conv_buf.size) { - src = hw->conv_buf.buffer; - frames_in = frames_in_max - frames_in; - dst += frames_out; - frames_out = frames_out_max - frames_out; - st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out); - *total_in += frames_in; - *total_out += frames_out; - } -} - -static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len) -{ - HWVoiceIn *hw = sw->hw; - size_t live, frames_out_max, total_in, total_out; - - live = hw->total_samples_captured - sw->total_hw_samples_acquired; - if (!live) { - return 0; - } - if (audio_bug(__func__, live > hw->conv_buf.size)) { - dolog("live_in=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size); - return 0; - } - - frames_out_max = MIN(buf_len / sw->info.bytes_per_frame, - sw->resample_buf.size); - - audio_pcm_sw_resample_in(sw, live, frames_out_max, &total_in, &total_out); - - if (!hw->pcm_ops->volume_in) { - mixeng_volume(sw->resample_buf.buffer, total_out, &sw->vol); - } - sw->clip(buf, sw->resample_buf.buffer, total_out); - - sw->total_hw_samples_acquired += total_in; - return total_out * sw->info.bytes_per_frame; -} - -/* - * Hard voice (playback) - */ -static size_t audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep) -{ - SWVoiceOut *sw; - size_t m = SIZE_MAX; - int nb_live = 0; - - for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { - if (sw->active || !sw->empty) { - m = MIN (m, sw->total_hw_samples_mixed); - nb_live += 1; - } - } - - *nb_livep = nb_live; - return m; -} - -static size_t audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live) -{ - size_t smin; - int nb_live1; - - smin = audio_pcm_hw_find_min_out (hw, &nb_live1); - if (nb_live) { - *nb_live = nb_live1; - } - - if (nb_live1) { - size_t live = smin; - - if (audio_bug(__func__, live > hw->mix_buf.size)) { - dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size); - return 0; - } - return live; - } - return 0; -} - -static size_t audio_pcm_hw_get_free(HWVoiceOut *hw) -{ - return (hw->pcm_ops->buffer_get_free ? hw->pcm_ops->buffer_get_free(hw) : - INT_MAX) / hw->info.bytes_per_frame; -} - -static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len) -{ - size_t clipped = 0; - size_t pos = hw->mix_buf.pos; - - while (len) { - st_sample *src = hw->mix_buf.buffer + pos; - uint8_t *dst = advance(pcm_buf, clipped * hw->info.bytes_per_frame); - size_t samples_till_end_of_buf = hw->mix_buf.size - pos; - size_t samples_to_clip = MIN(len, samples_till_end_of_buf); - - hw->clip(dst, src, samples_to_clip); - - pos = (pos + samples_to_clip) % hw->mix_buf.size; - len -= samples_to_clip; - clipped += samples_to_clip; - } -} - -/* - * Soft voice (playback) - */ -static void audio_pcm_sw_resample_out(SWVoiceOut *sw, - size_t frames_in_max, size_t frames_out_max, - size_t *total_in, size_t *total_out) -{ - HWVoiceOut *hw = sw->hw; - struct st_sample *src, *dst; - size_t live, wpos, frames_in, frames_out; - - live = sw->total_hw_samples_mixed; - wpos = (hw->mix_buf.pos + live) % hw->mix_buf.size; - - /* write to mix_buf from wpos to end of buffer */ - src = sw->resample_buf.buffer; - frames_in = frames_in_max; - dst = hw->mix_buf.buffer + wpos; - frames_out = MIN(frames_out_max, hw->mix_buf.size - wpos); - st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out); - wpos += frames_out; - *total_in = frames_in; - *total_out = frames_out; - - /* write to mix_buf from start of buffer if there are input frames left */ - if (frames_in_max - frames_in > 0 && wpos == hw->mix_buf.size) { - src += frames_in; - frames_in = frames_in_max - frames_in; - dst = hw->mix_buf.buffer; - frames_out = frames_out_max - frames_out; - st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out); - *total_in += frames_in; - *total_out += frames_out; - } -} - -static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len) -{ - HWVoiceOut *hw = sw->hw; - size_t live, dead, hw_free, sw_max, fe_max; - size_t frames_in_max, frames_out_max, total_in, total_out; - - live = sw->total_hw_samples_mixed; - if (audio_bug(__func__, live > hw->mix_buf.size)) { - dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size); - return 0; - } - - if (live == hw->mix_buf.size) { -#ifdef DEBUG_OUT - dolog ("%s is full %zu\n", sw->name, live); -#endif - return 0; - } - - dead = hw->mix_buf.size - live; - hw_free = audio_pcm_hw_get_free(hw); - hw_free = hw_free > live ? hw_free - live : 0; - frames_out_max = MIN(dead, hw_free); - sw_max = st_rate_frames_in(sw->rate, frames_out_max); - fe_max = MIN(buf_len / sw->info.bytes_per_frame + sw->resample_buf.pos, - sw->resample_buf.size); - frames_in_max = MIN(sw_max, fe_max); - - if (!frames_in_max) { - return 0; - } - - if (frames_in_max > sw->resample_buf.pos) { - sw->conv(sw->resample_buf.buffer + sw->resample_buf.pos, - buf, frames_in_max - sw->resample_buf.pos); - if (!sw->hw->pcm_ops->volume_out) { - mixeng_volume(sw->resample_buf.buffer + sw->resample_buf.pos, - frames_in_max - sw->resample_buf.pos, &sw->vol); - } - } - - audio_pcm_sw_resample_out(sw, frames_in_max, frames_out_max, - &total_in, &total_out); - - sw->total_hw_samples_mixed += total_out; - sw->empty = sw->total_hw_samples_mixed == 0; - - /* - * Upsampling may leave one audio frame in the resample buffer. Decrement - * total_in by one if there was a leftover frame from the previous resample - * pass in the resample buffer. Increment total_in by one if the current - * resample pass left one frame in the resample buffer. - */ - if (frames_in_max - total_in == 1) { - /* copy one leftover audio frame to the beginning of the buffer */ - *sw->resample_buf.buffer = *(sw->resample_buf.buffer + total_in); - total_in += 1 - sw->resample_buf.pos; - sw->resample_buf.pos = 1; - } else if (total_in >= sw->resample_buf.pos) { - total_in -= sw->resample_buf.pos; - sw->resample_buf.pos = 0; - } - -#ifdef DEBUG_OUT - dolog ( - "%s: write size %zu written %zu total mixed %zu\n", - SW_NAME(sw), - buf_len / sw->info.bytes_per_frame, - total_in, - sw->total_hw_samples_mixed - ); -#endif - - return total_in * sw->info.bytes_per_frame; -} - -#ifdef DEBUG_AUDIO -static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info) -{ - dolog("%s: bits %d, sign %d, float %d, freq %d, nchan %d\n", - cap, info->bits, info->is_signed, info->is_float, info->freq, - info->nchannels); -} -#endif - -#define DAC -#include "audio_template.h" -#undef DAC -#include "audio_template.h" - -/* - * Timer - */ -static int audio_is_timer_needed(AudioBackend *s) -{ - HWVoiceIn *hwi = NULL; - HWVoiceOut *hwo = NULL; - - while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) { - if (!hwo->poll_mode) { - return 1; - } - } - while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) { - if (!hwi->poll_mode) { - return 1; - } - } - return 0; -} - -static void audio_reset_timer(AudioBackend *s) -{ - if (audio_is_timer_needed(s)) { - timer_mod_anticipate_ns(s->ts, - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks); - if (!s->timer_running) { - s->timer_running = true; - s->timer_last = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - trace_audio_timer_start(s->period_ticks / SCALE_MS); - } - } else { - timer_del(s->ts); - if (s->timer_running) { - s->timer_running = false; - trace_audio_timer_stop(); - } - } -} - -static void audio_timer (void *opaque) -{ - int64_t now, diff; - AudioBackend *s = opaque; - - now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - diff = now - s->timer_last; - if (diff > s->period_ticks * 3 / 2) { - trace_audio_timer_delayed(diff / SCALE_MS); - } - s->timer_last = now; - - audio_run(s, "timer"); - audio_reset_timer(s); -} - -/* - * Public API - */ -size_t AUD_write(SWVoiceOut *sw, void *buf, size_t size) -{ - HWVoiceOut *hw; - - if (!sw) { - /* XXX: Consider options */ - return size; - } - hw = sw->hw; - - if (!hw->enabled) { - dolog ("Writing to disabled voice %s\n", SW_NAME (sw)); - return 0; - } - - if (audio_get_pdo_out(hw->s->dev)->mixing_engine) { - return audio_pcm_sw_write(sw, buf, size); - } else { - return hw->pcm_ops->write(hw, buf, size); - } -} - -size_t AUD_read(SWVoiceIn *sw, void *buf, size_t size) -{ - HWVoiceIn *hw; - - if (!sw) { - /* XXX: Consider options */ - return size; - } - hw = sw->hw; - - if (!hw->enabled) { - dolog ("Reading from disabled voice %s\n", SW_NAME (sw)); - return 0; - } - - if (audio_get_pdo_in(hw->s->dev)->mixing_engine) { - return audio_pcm_sw_read(sw, buf, size); - } else { - return hw->pcm_ops->read(hw, buf, size); - } -} - -int AUD_get_buffer_size_out(SWVoiceOut *sw) -{ - if (!sw) { - return 0; - } - - if (audio_get_pdo_out(sw->s->dev)->mixing_engine) { - return sw->resample_buf.size * sw->info.bytes_per_frame; - } - - return sw->hw->samples * sw->hw->info.bytes_per_frame; -} - -void AUD_set_active_out(SWVoiceOut *sw, bool on) -{ - HWVoiceOut *hw; - - if (!sw) { - return; - } - - hw = sw->hw; - if (sw->active != on) { - AudioBackend *s = sw->s; - SWVoiceOut *temp_sw; - SWVoiceCap *sc; - - if (on) { - hw->pending_disable = 0; - if (!hw->enabled) { - hw->enabled = true; - if (s->vm_running) { - if (hw->pcm_ops->enable_out) { - hw->pcm_ops->enable_out(hw, true); - } - audio_reset_timer (s); - } - } - } else { - if (hw->enabled) { - int nb_active = 0; - - for (temp_sw = hw->sw_head.lh_first; temp_sw; - temp_sw = temp_sw->entries.le_next) { - nb_active += temp_sw->active != 0; - } - - hw->pending_disable = nb_active == 1; - } - } - - for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) { - sc->sw.active = hw->enabled; - if (hw->enabled) { - audio_capture_maybe_changed (sc->cap, 1); - } - } - sw->active = on; - } -} - -void AUD_set_active_in(SWVoiceIn *sw, bool on) -{ - HWVoiceIn *hw; - - if (!sw) { - return; - } - - hw = sw->hw; - if (sw->active != on) { - AudioBackend *s = sw->s; - SWVoiceIn *temp_sw; - - if (on) { - if (!hw->enabled) { - hw->enabled = true; - if (s->vm_running) { - if (hw->pcm_ops->enable_in) { - hw->pcm_ops->enable_in(hw, true); - } - audio_reset_timer (s); - } - } - sw->total_hw_samples_acquired = hw->total_samples_captured; - } else { - if (hw->enabled) { - int nb_active = 0; - - for (temp_sw = hw->sw_head.lh_first; temp_sw; - temp_sw = temp_sw->entries.le_next) { - nb_active += temp_sw->active != 0; - } - - if (nb_active == 1) { - hw->enabled = false; - if (hw->pcm_ops->enable_in) { - hw->pcm_ops->enable_in(hw, false); - } - } - } - } - sw->active = on; - } -} - -static size_t audio_get_avail (SWVoiceIn *sw) -{ - size_t live; - - if (!sw) { - return 0; - } - - live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired; - if (audio_bug(__func__, live > sw->hw->conv_buf.size)) { - dolog("live=%zu sw->hw->conv_buf.size=%zu\n", live, - sw->hw->conv_buf.size); - return 0; - } - - ldebug ( - "%s: get_avail live %zu frontend frames %u\n", - SW_NAME (sw), - live, st_rate_frames_out(sw->rate, live) - ); - - return live; -} - -static size_t audio_get_free(SWVoiceOut *sw) -{ - size_t live, dead; - - if (!sw) { - return 0; - } - - live = sw->total_hw_samples_mixed; - - if (audio_bug(__func__, live > sw->hw->mix_buf.size)) { - dolog("live=%zu sw->hw->mix_buf.size=%zu\n", live, - sw->hw->mix_buf.size); - return 0; - } - - dead = sw->hw->mix_buf.size - live; - -#ifdef DEBUG_OUT - dolog("%s: get_free live %zu dead %zu frontend frames %u\n", - SW_NAME(sw), live, dead, st_rate_frames_in(sw->rate, dead)); -#endif - - return dead; -} - -static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos, - size_t samples) -{ - size_t n; - - if (hw->enabled) { - SWVoiceCap *sc; - - for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) { - SWVoiceOut *sw = &sc->sw; - size_t rpos2 = rpos; - - n = samples; - while (n) { - size_t till_end_of_hw = hw->mix_buf.size - rpos2; - size_t to_read = MIN(till_end_of_hw, n); - size_t live, frames_in, frames_out; - - sw->resample_buf.buffer = hw->mix_buf.buffer + rpos2; - sw->resample_buf.size = to_read; - live = sw->total_hw_samples_mixed; - - audio_pcm_sw_resample_out(sw, - to_read, sw->hw->mix_buf.size - live, - &frames_in, &frames_out); - - sw->total_hw_samples_mixed += frames_out; - sw->empty = sw->total_hw_samples_mixed == 0; - - if (to_read - frames_in) { - dolog("Could not mix %zu frames into a capture " - "buffer, mixed %zu\n", - to_read, frames_in); - break; - } - n -= to_read; - rpos2 = (rpos2 + to_read) % hw->mix_buf.size; - } - } - } - - n = MIN(samples, hw->mix_buf.size - rpos); - mixeng_clear(hw->mix_buf.buffer + rpos, n); - mixeng_clear(hw->mix_buf.buffer, samples - n); -} - -static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live) -{ - size_t clipped = 0; - - while (live) { - size_t size = live * hw->info.bytes_per_frame; - size_t decr, proc; - void *buf = hw->pcm_ops->get_buffer_out(hw, &size); - - if (size == 0) { - break; - } - - decr = MIN(size / hw->info.bytes_per_frame, live); - if (buf) { - audio_pcm_hw_clip_out(hw, buf, decr); - } - proc = hw->pcm_ops->put_buffer_out(hw, buf, - decr * hw->info.bytes_per_frame) / - hw->info.bytes_per_frame; - - live -= proc; - clipped += proc; - hw->mix_buf.pos = (hw->mix_buf.pos + proc) % hw->mix_buf.size; - - if (proc == 0 || proc < decr) { - break; - } - } - - if (hw->pcm_ops->run_buffer_out) { - hw->pcm_ops->run_buffer_out(hw); - } - - return clipped; -} - -static void audio_run_out(AudioBackend *s) -{ - HWVoiceOut *hw = NULL; - SWVoiceOut *sw; - - while ((hw = audio_pcm_hw_find_any_enabled_out(s, hw))) { - size_t played, live, prev_rpos; - size_t hw_free = audio_pcm_hw_get_free(hw); - int nb_live; - - if (!audio_get_pdo_out(s->dev)->mixing_engine) { - /* there is exactly 1 sw for each hw with no mixeng */ - sw = hw->sw_head.lh_first; - - if (hw->pending_disable) { - hw->enabled = false; - hw->pending_disable = false; - if (hw->pcm_ops->enable_out) { - hw->pcm_ops->enable_out(hw, false); - } - } - - if (sw->active) { - sw->callback.fn(sw->callback.opaque, - hw_free * sw->info.bytes_per_frame); - } - - if (hw->pcm_ops->run_buffer_out) { - hw->pcm_ops->run_buffer_out(hw); - } - - continue; - } - - for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { - if (sw->active) { - size_t sw_free = audio_get_free(sw); - size_t free; - - if (hw_free > sw->total_hw_samples_mixed) { - free = st_rate_frames_in(sw->rate, - MIN(sw_free, hw_free - sw->total_hw_samples_mixed)); - } else { - free = 0; - } - if (free > sw->resample_buf.pos) { - free = MIN(free, sw->resample_buf.size) - - sw->resample_buf.pos; - sw->callback.fn(sw->callback.opaque, - free * sw->info.bytes_per_frame); - } - } - } - - live = audio_pcm_hw_get_live_out (hw, &nb_live); - if (!nb_live) { - live = 0; - } - - if (audio_bug(__func__, live > hw->mix_buf.size)) { - dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size); - continue; - } - - if (hw->pending_disable && !nb_live) { - SWVoiceCap *sc; -#ifdef DEBUG_OUT - dolog ("Disabling voice\n"); -#endif - hw->enabled = false; - hw->pending_disable = false; - if (hw->pcm_ops->enable_out) { - hw->pcm_ops->enable_out(hw, false); - } - for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) { - sc->sw.active = false; - audio_recalc_and_notify_capture (sc->cap); - } - continue; - } - - if (!live) { - if (hw->pcm_ops->run_buffer_out) { - hw->pcm_ops->run_buffer_out(hw); - } - continue; - } - - prev_rpos = hw->mix_buf.pos; - played = audio_pcm_hw_run_out(hw, live); - replay_audio_out(&played); - if (audio_bug(__func__, hw->mix_buf.pos >= hw->mix_buf.size)) { - dolog("hw->mix_buf.pos=%zu hw->mix_buf.size=%zu played=%zu\n", - hw->mix_buf.pos, hw->mix_buf.size, played); - hw->mix_buf.pos = 0; - } - -#ifdef DEBUG_OUT - dolog("played=%zu\n", played); -#endif - - if (played) { - hw->ts_helper += played; - audio_capture_mix_and_clear (hw, prev_rpos, played); - } - - for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { - if (!sw->active && sw->empty) { - continue; - } - - if (audio_bug(__func__, played > sw->total_hw_samples_mixed)) { - dolog("played=%zu sw->total_hw_samples_mixed=%zu\n", - played, sw->total_hw_samples_mixed); - played = sw->total_hw_samples_mixed; - } - - sw->total_hw_samples_mixed -= played; - - if (!sw->total_hw_samples_mixed) { - sw->empty = true; - } - } - } -} - -static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples) -{ - size_t conv = 0; - - if (hw->pcm_ops->run_buffer_in) { - hw->pcm_ops->run_buffer_in(hw); - } - - while (samples) { - size_t proc; - size_t size = samples * hw->info.bytes_per_frame; - void *buf = hw->pcm_ops->get_buffer_in(hw, &size); - - assert(size % hw->info.bytes_per_frame == 0); - if (size == 0) { - break; - } - - proc = audio_pcm_hw_conv_in(hw, buf, size / hw->info.bytes_per_frame); - - samples -= proc; - conv += proc; - hw->pcm_ops->put_buffer_in(hw, buf, proc * hw->info.bytes_per_frame); - } - - return conv; -} - -static void audio_run_in(AudioBackend *s) -{ - HWVoiceIn *hw = NULL; - - if (!audio_get_pdo_in(s->dev)->mixing_engine) { - while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) { - /* there is exactly 1 sw for each hw with no mixeng */ - SWVoiceIn *sw = hw->sw_head.lh_first; - if (sw->active) { - sw->callback.fn(sw->callback.opaque, INT_MAX); - } - } - return; - } - - while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) { - SWVoiceIn *sw; - size_t captured = 0, min; - - if (replay_mode != REPLAY_MODE_PLAY) { - captured = audio_pcm_hw_run_in( - hw, hw->conv_buf.size - audio_pcm_hw_get_live_in(hw)); - } - replay_audio_in(&captured, hw->conv_buf.buffer, &hw->conv_buf.pos, - hw->conv_buf.size); - - min = audio_pcm_hw_find_min_in (hw); - hw->total_samples_captured += captured - min; - hw->ts_helper += captured; - - for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { - sw->total_hw_samples_acquired -= min; - - if (sw->active) { - size_t sw_avail = audio_get_avail(sw); - size_t avail; - - avail = st_rate_frames_out(sw->rate, sw_avail); - if (avail > 0) { - avail = MIN(avail, sw->resample_buf.size); - sw->callback.fn(sw->callback.opaque, - avail * sw->info.bytes_per_frame); - } - } - } - } -} - -static void audio_run_capture(AudioBackend *s) -{ - CaptureVoiceOut *cap; - - for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) { - size_t live, rpos, captured; - HWVoiceOut *hw = &cap->hw; - SWVoiceOut *sw; - - captured = live = audio_pcm_hw_get_live_out (hw, NULL); - rpos = hw->mix_buf.pos; - while (live) { - size_t left = hw->mix_buf.size - rpos; - size_t to_capture = MIN(live, left); - struct st_sample *src; - struct capture_callback *cb; - - src = hw->mix_buf.buffer + rpos; - hw->clip (cap->buf, src, to_capture); - mixeng_clear (src, to_capture); - - for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { - cb->ops.capture (cb->opaque, cap->buf, - to_capture * hw->info.bytes_per_frame); - } - rpos = (rpos + to_capture) % hw->mix_buf.size; - live -= to_capture; - } - hw->mix_buf.pos = rpos; - - for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { - if (!sw->active && sw->empty) { - continue; - } - - if (audio_bug(__func__, captured > sw->total_hw_samples_mixed)) { - dolog("captured=%zu sw->total_hw_samples_mixed=%zu\n", - captured, sw->total_hw_samples_mixed); - captured = sw->total_hw_samples_mixed; - } - - sw->total_hw_samples_mixed -= captured; - sw->empty = sw->total_hw_samples_mixed == 0; - } - } -} - -void audio_run(AudioBackend *s, const char *msg) -{ - audio_run_out(s); - audio_run_in(s); - audio_run_capture(s); - -#ifdef DEBUG_POLL - { - static double prevtime; - double currtime; - struct timeval tv; - - if (gettimeofday (&tv, NULL)) { - perror ("audio_run: gettimeofday"); - return; - } - - currtime = tv.tv_sec + tv.tv_usec * 1e-6; - dolog ("Elapsed since last %s: %f\n", msg, currtime - prevtime); - prevtime = currtime; - } -#endif -} - -void audio_generic_run_buffer_in(HWVoiceIn *hw) -{ - if (unlikely(!hw->buf_emul)) { - hw->size_emul = hw->samples * hw->info.bytes_per_frame; - hw->buf_emul = g_malloc(hw->size_emul); - hw->pos_emul = hw->pending_emul = 0; - } - - while (hw->pending_emul < hw->size_emul) { - size_t read_len = MIN(hw->size_emul - hw->pos_emul, - hw->size_emul - hw->pending_emul); - size_t read = hw->pcm_ops->read(hw, hw->buf_emul + hw->pos_emul, - read_len); - hw->pending_emul += read; - hw->pos_emul = (hw->pos_emul + read) % hw->size_emul; - if (read < read_len) { - break; - } - } -} - -void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size) -{ - size_t start; - - start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul); - assert(start < hw->size_emul); - - *size = MIN(*size, hw->pending_emul); - *size = MIN(*size, hw->size_emul - start); - return hw->buf_emul + start; -} - -void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size) -{ - assert(size <= hw->pending_emul); - hw->pending_emul -= size; -} - -size_t audio_generic_buffer_get_free(HWVoiceOut *hw) -{ - if (hw->buf_emul) { - return hw->size_emul - hw->pending_emul; - } else { - return hw->samples * hw->info.bytes_per_frame; - } -} - -void audio_generic_run_buffer_out(HWVoiceOut *hw) -{ - while (hw->pending_emul) { - size_t write_len, written, start; - - start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul); - assert(start < hw->size_emul); - - write_len = MIN(hw->pending_emul, hw->size_emul - start); - - written = hw->pcm_ops->write(hw, hw->buf_emul + start, write_len); - hw->pending_emul -= written; - - if (written < write_len) { - break; - } - } -} - -void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size) -{ - if (unlikely(!hw->buf_emul)) { - hw->size_emul = hw->samples * hw->info.bytes_per_frame; - hw->buf_emul = g_malloc(hw->size_emul); - hw->pos_emul = hw->pending_emul = 0; - } - - *size = MIN(hw->size_emul - hw->pending_emul, - hw->size_emul - hw->pos_emul); - return hw->buf_emul + hw->pos_emul; -} - -size_t audio_generic_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size) -{ - assert(buf == hw->buf_emul + hw->pos_emul && - size + hw->pending_emul <= hw->size_emul); - - hw->pending_emul += size; - hw->pos_emul = (hw->pos_emul + size) % hw->size_emul; - - return size; -} - -size_t audio_generic_write(HWVoiceOut *hw, void *buf, size_t size) -{ - size_t total = 0; - - if (hw->pcm_ops->buffer_get_free) { - size_t free = hw->pcm_ops->buffer_get_free(hw); - - size = MIN(size, free); - } - - while (total < size) { - size_t dst_size = size - total; - size_t copy_size, proc; - void *dst = hw->pcm_ops->get_buffer_out(hw, &dst_size); - - if (dst_size == 0) { - break; - } - - copy_size = MIN(size - total, dst_size); - if (dst) { - memcpy(dst, (char *)buf + total, copy_size); - } - proc = hw->pcm_ops->put_buffer_out(hw, dst, copy_size); - total += proc; - - if (proc == 0 || proc < copy_size) { - break; - } - } - - return total; -} - -size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size) -{ - size_t total = 0; - - if (hw->pcm_ops->run_buffer_in) { - hw->pcm_ops->run_buffer_in(hw); - } - - while (total < size) { - size_t src_size = size - total; - void *src = hw->pcm_ops->get_buffer_in(hw, &src_size); - - if (src_size == 0) { - break; - } - - memcpy((char *)buf + total, src, src_size); - hw->pcm_ops->put_buffer_in(hw, src, src_size); - total += src_size; - } - - return total; -} - -static bool audio_driver_init(AudioBackend *s, struct audio_driver *drv, - Audiodev *dev, Error **errp) -{ - s->drv_opaque = drv->init(dev, errp); - if (!s->drv_opaque) { - return false; - } - - if (!drv->pcm_ops->get_buffer_in) { - drv->pcm_ops->get_buffer_in = audio_generic_get_buffer_in; - drv->pcm_ops->put_buffer_in = audio_generic_put_buffer_in; - } - if (!drv->pcm_ops->get_buffer_out) { - drv->pcm_ops->get_buffer_out = audio_generic_get_buffer_out; - drv->pcm_ops->put_buffer_out = audio_generic_put_buffer_out; - } - - audio_init_nb_voices_out(s, drv, 1); - audio_init_nb_voices_in(s, drv, 0); - s->drv = drv; - - if (dev->timer_period <= 0) { - s->period_ticks = 1; - } else { - s->period_ticks = dev->timer_period * (int64_t)SCALE_US; - } - - return true; -} - -static void audio_vm_change_state_handler (void *opaque, bool running, - RunState state) -{ - AudioBackend *s = opaque; - HWVoiceOut *hwo = NULL; - HWVoiceIn *hwi = NULL; - - s->vm_running = running; - while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) { - if (hwo->pcm_ops->enable_out) { - hwo->pcm_ops->enable_out(hwo, running); - } - } - - while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) { - if (hwi->pcm_ops->enable_in) { - hwi->pcm_ops->enable_in(hwi, running); - } - } - audio_reset_timer (s); -} - -static const VMStateDescription vmstate_audio; - -static void audio_be_init(Object *obj) -{ - AudioBackend *s = AUDIO_BACKEND(obj); - - QLIST_INIT(&s->hw_head_out); - QLIST_INIT(&s->hw_head_in); - QLIST_INIT(&s->cap_head); - s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s); - - s->vmse = qemu_add_vm_change_state_handler(audio_vm_change_state_handler, s); - assert(s->vmse != NULL); - - vmstate_register_any(NULL, &vmstate_audio, s); -} - -static void audio_be_finalize(Object *obj) -{ - AudioBackend *s = AUDIO_BACKEND(obj); - HWVoiceOut *hwo, *hwon; - HWVoiceIn *hwi, *hwin; - - QLIST_FOREACH_SAFE(hwo, &s->hw_head_out, entries, hwon) { - SWVoiceCap *sc; - - if (hwo->enabled && hwo->pcm_ops->enable_out) { - hwo->pcm_ops->enable_out(hwo, false); - } - hwo->pcm_ops->fini_out (hwo); +/* SPDX-License-Identifier: MIT */ - for (sc = hwo->cap_head.lh_first; sc; sc = sc->entries.le_next) { - CaptureVoiceOut *cap = sc->cap; - struct capture_callback *cb; +#include "qemu/osdep.h" +#include "qemu/audio.h" +#include "qemu/help_option.h" +#include "qapi/clone-visitor.h" +#include "qapi/qobject-input-visitor.h" +#include "qapi/qapi-visit-audio.h" +#include "qapi/qapi-commands-audio.h" +#include "qobject/qdict.h" +#include "system/system.h" - for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { - cb->ops.destroy (cb->opaque); - } - } - QLIST_REMOVE(hwo, entries); - } +/* Order of CONFIG_AUDIO_DRIVERS is import. + The 1st one is the one used by default, that is the reason + that we generate the list. +*/ +const char *audio_prio_list[] = { +#ifdef CONFIG_GIO + "dbus", +#endif + "spice", + CONFIG_AUDIO_DRIVERS + "none", + NULL +}; - QLIST_FOREACH_SAFE(hwi, &s->hw_head_in, entries, hwin) { - if (hwi->enabled && hwi->pcm_ops->enable_in) { - hwi->pcm_ops->enable_in(hwi, false); - } - hwi->pcm_ops->fini_in (hwi); - QLIST_REMOVE(hwi, entries); - } +typedef struct AudiodevListEntry { + Audiodev *dev; + QSIMPLEQ_ENTRY(AudiodevListEntry) next; +} AudiodevListEntry; - if (s->drv) { - s->drv->fini (s->drv_opaque); - s->drv = NULL; - } +typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead; - if (s->dev) { - qapi_free_Audiodev(s->dev); - s->dev = NULL; - } +static AudiodevListHead audiodevs = + QSIMPLEQ_HEAD_INITIALIZER(audiodevs); +static AudiodevListHead default_audiodevs = + QSIMPLEQ_HEAD_INITIALIZER(default_audiodevs); - if (s->ts) { - timer_free(s->ts); - s->ts = NULL; - } +static AudioBackendClass *audio_be_class_by_name(const char *name) +{ + g_autofree char *tname = g_strconcat("audio-", name, NULL); + ObjectClass *oc = module_object_class_by_name(tname); - if (s->vmse) { - qemu_del_vm_change_state_handler(s->vmse); - s->vmse = NULL; + if (!oc || !object_class_dynamic_cast(oc, TYPE_AUDIO_BACKEND)) { + return NULL; } - vmstate_unregister(NULL, &vmstate_audio, s); + return AUDIO_BACKEND_CLASS(oc); } +static AudioBackend *default_audio_be; + static Object *get_audiodevs_root(void) { return object_get_container("audiodevs"); @@ -1697,29 +62,10 @@ void audio_cleanup(void) object_unparent(get_audiodevs_root()); } -static bool vmstate_audio_needed(void *opaque) -{ - /* - * Never needed, this vmstate only exists in case - * an old qemu sends it to us. - */ - return false; -} - -static const VMStateDescription vmstate_audio = { - .name = "audio", - .version_id = 1, - .minimum_version_id = 1, - .needed = vmstate_audio_needed, - .fields = (const VMStateField[]) { - VMSTATE_END_OF_LIST() - } -}; - void audio_create_default_audiodevs(void) { for (int i = 0; audio_prio_list[i]; i++) { - if (audio_driver_lookup(audio_prio_list[i])) { + if (audio_be_class_by_name(audio_prio_list[i]) != NULL) { QDict *dict = qdict_new(); Audiodev *dev = NULL; Visitor *v; @@ -1745,25 +91,12 @@ void audio_create_default_audiodevs(void) */ static AudioBackend *audio_init(Audiodev *dev, Error **errp) { - int done = 0; - const char *drvname; - AudioBackend *s; - struct audio_driver *driver; - - s = AUDIO_BACKEND(object_new(TYPE_AUDIO_BACKEND)); + AudioBackend *be; if (dev) { - /* -audiodev option */ - s->dev = dev; - drvname = AudiodevDriver_str(dev->driver); - driver = audio_driver_lookup(drvname); - if (driver) { - done = audio_driver_init(s, driver, dev, errp); - } else { - error_setg(errp, "Unknown audio driver `%s'", drvname); - } - if (!done) { - goto out; + be = audio_be_new(dev, errp); + if (!be) { + return NULL; } } else { assert(!default_audio_be); @@ -1771,30 +104,24 @@ static AudioBackend *audio_init(Audiodev *dev, Error **errp) AudiodevListEntry *e = QSIMPLEQ_FIRST(&default_audiodevs); if (!e) { error_setg(errp, "no default audio driver available"); - goto out; + return NULL; } - s->dev = dev = e->dev; QSIMPLEQ_REMOVE_HEAD(&default_audiodevs, next); + be = audio_be_new(e->dev, NULL); g_free(e); - drvname = AudiodevDriver_str(dev->driver); - driver = audio_driver_lookup(drvname); - if (audio_driver_init(s, driver, dev, NULL)) { + if (be) { break; } - qapi_free_Audiodev(dev); - s->dev = NULL; } } - if (!object_property_try_add_child(get_audiodevs_root(), dev->id, OBJECT(s), errp)) { - goto out; + if (!object_property_try_add_child(get_audiodevs_root(), + audio_be_get_id(be), OBJECT(be), errp)) { + object_unref(be); + return NULL; } - object_unref(s); - return s; - -out: - object_unref(s); - return NULL; + object_unref(be); + return be; } AudioBackend *audio_get_default_audio_be(Error **errp) @@ -1812,168 +139,38 @@ AudioBackend *audio_get_default_audio_be(Error **errp) return default_audio_be; } -bool AUD_backend_check(AudioBackend **be, Error **errp) -{ - assert(be != NULL); - - if (!*be) { - *be = audio_get_default_audio_be(errp); - if (!*be) { - return false; - } - } - - return true; -} - -static struct audio_pcm_ops capture_pcm_ops; - -CaptureVoiceOut *AUD_add_capture( - AudioBackend *s, - struct audsettings *as, - struct audio_capture_ops *ops, - void *cb_opaque - ) -{ - CaptureVoiceOut *cap; - struct capture_callback *cb; - - if (!s) { - error_report("Capturing without setting an audiodev is not supported"); - abort(); - } - - if (!audio_get_pdo_out(s->dev)->mixing_engine) { - dolog("Can't capture with mixeng disabled\n"); - return NULL; - } - - if (audio_validate_settings (as)) { - dolog ("Invalid settings were passed when trying to add capture\n"); - audio_print_settings (as); - return NULL; - } - - cb = g_malloc0(sizeof(*cb)); - cb->ops = *ops; - cb->opaque = cb_opaque; - - cap = audio_pcm_capture_find_specific(s, as); - if (cap) { - QLIST_INSERT_HEAD (&cap->cb_head, cb, entries); - } else { - HWVoiceOut *hw; - - cap = g_malloc0(sizeof(*cap)); - - hw = &cap->hw; - hw->s = s; - hw->pcm_ops = &capture_pcm_ops; - QLIST_INIT (&hw->sw_head); - QLIST_INIT (&cap->cb_head); - - /* XXX find a more elegant way */ - hw->samples = 4096 * 4; - audio_pcm_hw_alloc_resources_out(hw); - - audio_pcm_init_info (&hw->info, as); - - cap->buf = g_malloc0_n(hw->mix_buf.size, hw->info.bytes_per_frame); - - if (hw->info.is_float) { - hw->clip = mixeng_clip_float[hw->info.nchannels == 2] - [hw->info.swap_endianness]; - } else { - hw->clip = mixeng_clip - [hw->info.nchannels == 2] - [hw->info.is_signed] - [hw->info.swap_endianness] - [audio_bits_to_index(hw->info.bits)]; - } - - QLIST_INSERT_HEAD (&s->cap_head, cap, entries); - QLIST_INSERT_HEAD (&cap->cb_head, cb, entries); - - QLIST_FOREACH(hw, &s->hw_head_out, entries) { - audio_attach_capture (hw); - } - } - - return cap; -} - -void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque) +void audio_help(void) { - struct capture_callback *cb; - - for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { - if (cb->opaque == cb_opaque) { - cb->ops.destroy (cb_opaque); - QLIST_REMOVE (cb, entries); - g_free (cb); + int i; - if (!cap->cb_head.lh_first) { - SWVoiceOut *sw = cap->hw.sw_head.lh_first, *sw1; + printf("Available audio drivers:\n"); - while (sw) { - SWVoiceCap *sc = (SWVoiceCap *) sw; -#ifdef DEBUG_CAPTURE - dolog ("freeing %s\n", sw->name); -#endif + for (i = 0; i < AUDIODEV_DRIVER__MAX; i++) { + const char *name = AudiodevDriver_str(i); + AudioBackendClass *be = audio_be_class_by_name(name); - sw1 = sw->entries.le_next; - if (sw->rate) { - st_rate_stop (sw->rate); - sw->rate = NULL; - } - QLIST_REMOVE (sw, entries); - QLIST_REMOVE (sc, entries); - g_free (sc); - sw = sw1; - } - QLIST_REMOVE (cap, entries); - g_free(cap->hw.mix_buf.buffer); - g_free (cap->buf); - g_free (cap); - } - return; + if (be) { + printf("%s\n", name); } } } -void AUD_set_volume_out(SWVoiceOut *sw, Volume *vol) +void audio_parse_option(const char *opt) { - if (sw) { - HWVoiceOut *hw = sw->hw; - - sw->vol.mute = vol->mute; - sw->vol.l = nominal_volume.l * vol->vol[0] / 255; - sw->vol.r = nominal_volume.l * vol->vol[vol->channels > 1 ? 1 : 0] / - 255; + Audiodev *dev = NULL; - if (hw->pcm_ops->volume_out) { - hw->pcm_ops->volume_out(hw, vol); - } + if (is_help_option(opt)) { + audio_help(); + exit(EXIT_SUCCESS); } -} - -void AUD_set_volume_in(SWVoiceIn *sw, Volume *vol) -{ - if (sw) { - HWVoiceIn *hw = sw->hw; - - sw->vol.mute = vol->mute; - sw->vol.l = nominal_volume.l * vol->vol[0] / 255; - sw->vol.r = nominal_volume.r * vol->vol[vol->channels > 1 ? 1 : 0] / - 255; + Visitor *v = qobject_input_visitor_new_str(opt, "driver", &error_fatal); + visit_type_Audiodev(v, NULL, &dev, &error_fatal); + visit_free(v); - if (hw->pcm_ops->volume_in) { - hw->pcm_ops->volume_in(hw, vol); - } - } + audio_add_audiodev(dev); } -void audio_create_pdos(Audiodev *dev) +static void audio_create_pdos(Audiodev *dev) { switch (dev->driver) { #define CASE(DRIVER, driver, pdo_name) \ @@ -2069,6 +266,124 @@ static void audio_validate_per_direction_opts( } } +static AudiodevPerDirectionOptions *audio_get_pdo_out(Audiodev *dev) +{ + switch (dev->driver) { + case AUDIODEV_DRIVER_NONE: + return dev->u.none.out; +#ifdef CONFIG_AUDIO_ALSA + case AUDIODEV_DRIVER_ALSA: + return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.out); +#endif +#ifdef CONFIG_AUDIO_COREAUDIO + case AUDIODEV_DRIVER_COREAUDIO: + return qapi_AudiodevCoreaudioPerDirectionOptions_base( + dev->u.coreaudio.out); +#endif +#ifdef CONFIG_DBUS_DISPLAY + case AUDIODEV_DRIVER_DBUS: + return dev->u.dbus.out; +#endif +#ifdef CONFIG_AUDIO_DSOUND + case AUDIODEV_DRIVER_DSOUND: + return dev->u.dsound.out; +#endif +#ifdef CONFIG_AUDIO_JACK + case AUDIODEV_DRIVER_JACK: + return qapi_AudiodevJackPerDirectionOptions_base(dev->u.jack.out); +#endif +#ifdef CONFIG_AUDIO_OSS + case AUDIODEV_DRIVER_OSS: + return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.out); +#endif +#ifdef CONFIG_AUDIO_PA + case AUDIODEV_DRIVER_PA: + return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.out); +#endif +#ifdef CONFIG_AUDIO_PIPEWIRE + case AUDIODEV_DRIVER_PIPEWIRE: + return qapi_AudiodevPipewirePerDirectionOptions_base(dev->u.pipewire.out); +#endif +#ifdef CONFIG_AUDIO_SDL + case AUDIODEV_DRIVER_SDL: + return qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.out); +#endif +#ifdef CONFIG_AUDIO_SNDIO + case AUDIODEV_DRIVER_SNDIO: + return dev->u.sndio.out; +#endif +#ifdef CONFIG_SPICE + case AUDIODEV_DRIVER_SPICE: + return dev->u.spice.out; +#endif + case AUDIODEV_DRIVER_WAV: + return dev->u.wav.out; + + case AUDIODEV_DRIVER__MAX: + break; + } + abort(); +} + +static AudiodevPerDirectionOptions *audio_get_pdo_in(Audiodev *dev) +{ + switch (dev->driver) { + case AUDIODEV_DRIVER_NONE: + return dev->u.none.in; +#ifdef CONFIG_AUDIO_ALSA + case AUDIODEV_DRIVER_ALSA: + return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.in); +#endif +#ifdef CONFIG_AUDIO_COREAUDIO + case AUDIODEV_DRIVER_COREAUDIO: + return qapi_AudiodevCoreaudioPerDirectionOptions_base( + dev->u.coreaudio.in); +#endif +#ifdef CONFIG_DBUS_DISPLAY + case AUDIODEV_DRIVER_DBUS: + return dev->u.dbus.in; +#endif +#ifdef CONFIG_AUDIO_DSOUND + case AUDIODEV_DRIVER_DSOUND: + return dev->u.dsound.in; +#endif +#ifdef CONFIG_AUDIO_JACK + case AUDIODEV_DRIVER_JACK: + return qapi_AudiodevJackPerDirectionOptions_base(dev->u.jack.in); +#endif +#ifdef CONFIG_AUDIO_OSS + case AUDIODEV_DRIVER_OSS: + return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.in); +#endif +#ifdef CONFIG_AUDIO_PA + case AUDIODEV_DRIVER_PA: + return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.in); +#endif +#ifdef CONFIG_AUDIO_PIPEWIRE + case AUDIODEV_DRIVER_PIPEWIRE: + return qapi_AudiodevPipewirePerDirectionOptions_base(dev->u.pipewire.in); +#endif +#ifdef CONFIG_AUDIO_SDL + case AUDIODEV_DRIVER_SDL: + return qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.in); +#endif +#ifdef CONFIG_AUDIO_SNDIO + case AUDIODEV_DRIVER_SNDIO: + return dev->u.sndio.in; +#endif +#ifdef CONFIG_SPICE + case AUDIODEV_DRIVER_SPICE: + return dev->u.spice.in; +#endif + case AUDIODEV_DRIVER_WAV: + return dev->u.wav.in; + + case AUDIODEV_DRIVER__MAX: + break; + } + abort(); +} + static void audio_validate_opts(Audiodev *dev, Error **errp) { Error *err = NULL; @@ -2093,35 +408,6 @@ static void audio_validate_opts(Audiodev *dev, Error **errp) } } -void audio_help(void) -{ - int i; - - printf("Available audio drivers:\n"); - - for (i = 0; i < AUDIODEV_DRIVER__MAX; i++) { - audio_driver *driver = audio_driver_lookup(AudiodevDriver_str(i)); - if (driver) { - printf("%s\n", driver->name); - } - } -} - -void audio_parse_option(const char *opt) -{ - Audiodev *dev = NULL; - - if (is_help_option(opt)) { - audio_help(); - exit(EXIT_SUCCESS); - } - Visitor *v = qobject_input_visitor_new_str(opt, "driver", &error_fatal); - visit_type_Audiodev(v, NULL, &dev, &error_fatal); - visit_free(v); - - audio_add_audiodev(dev); -} - void audio_add_audiodev(Audiodev *dev) { AudiodevListEntry *e; @@ -2149,67 +435,8 @@ void audio_init_audiodevs(void) AudiodevListEntry *e; QSIMPLEQ_FOREACH(e, &audiodevs, next) { - audio_init(e->dev, &error_fatal); - } -} - -audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo) -{ - return (audsettings) { - .freq = pdo->frequency, - .nchannels = pdo->channels, - .fmt = pdo->format, - .endianness = HOST_BIG_ENDIAN, - }; -} - -int audioformat_bytes_per_sample(AudioFormat fmt) -{ - switch (fmt) { - case AUDIO_FORMAT_U8: - case AUDIO_FORMAT_S8: - return 1; - - case AUDIO_FORMAT_U16: - case AUDIO_FORMAT_S16: - return 2; - - case AUDIO_FORMAT_U32: - case AUDIO_FORMAT_S32: - case AUDIO_FORMAT_F32: - return 4; - - case AUDIO_FORMAT__MAX: - ; + audio_init(QAPI_CLONE(Audiodev, e->dev), &error_fatal); } - abort(); -} - - -/* frames = freq * usec / 1e6 */ -int audio_buffer_frames(AudiodevPerDirectionOptions *pdo, - audsettings *as, int def_usecs) -{ - uint64_t usecs = pdo->has_buffer_length ? pdo->buffer_length : def_usecs; - return (as->freq * usecs + 500000) / 1000000; -} - -/* samples = channels * frames = channels * freq * usec / 1e6 */ -int audio_buffer_samples(AudiodevPerDirectionOptions *pdo, - audsettings *as, int def_usecs) -{ - return as->nchannels * audio_buffer_frames(pdo, as, def_usecs); -} - -/* - * bytes = bytes_per_sample * samples = - * bytes_per_sample * channels * freq * usec / 1e6 - */ -int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo, - audsettings *as, int def_usecs) -{ - return audio_buffer_samples(pdo, as, def_usecs) * - audioformat_bytes_per_sample(as->fmt); } AudioBackend *audio_be_by_name(const char *name, Error **errp) @@ -2224,33 +451,6 @@ AudioBackend *audio_be_by_name(const char *name, Error **errp) } } -#ifdef CONFIG_GIO -bool audio_be_set_dbus_server(AudioBackend *be, - GDBusObjectManagerServer *server, - bool p2p, - Error **errp) -{ - assert(be != NULL); - - if (!be->drv->set_dbus_server) { - error_setg(errp, "Audiodev '%s' is not compatible with DBus", be->dev->id); - return false; - } - - return be->drv->set_dbus_server(be, server, p2p, errp); -} -#endif - -const char *audio_be_get_id(AudioBackend *be) -{ - if (be) { - assert(be->dev); - return be->dev->id; - } else { - return ""; - } -} - const char *audio_application_name(void) { const char *vm_name; @@ -2259,51 +459,6 @@ const char *audio_application_name(void) return vm_name ? vm_name : "qemu"; } -void audio_rate_start(RateCtl *rate) -{ - memset(rate, 0, sizeof(RateCtl)); - rate->start_ticks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); -} - -size_t audio_rate_peek_bytes(RateCtl *rate, struct audio_pcm_info *info) -{ - int64_t now; - int64_t ticks; - int64_t bytes; - int64_t frames; - - now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - ticks = now - rate->start_ticks; - bytes = muldiv64(ticks, info->bytes_per_second, NANOSECONDS_PER_SECOND); - frames = (bytes - rate->bytes_sent) / info->bytes_per_frame; - rate->peeked_frames = frames; - - return frames < 0 ? 0 : frames * info->bytes_per_frame; -} - -void audio_rate_add_bytes(RateCtl *rate, size_t bytes_used) -{ - if (rate->peeked_frames < 0 || rate->peeked_frames > 65536) { - AUD_log(NULL, "Resetting rate control (%" PRId64 " frames)\n", - rate->peeked_frames); - audio_rate_start(rate); - } - - rate->bytes_sent += bytes_used; -} - -size_t audio_rate_get_bytes(RateCtl *rate, struct audio_pcm_info *info, - size_t bytes_avail) -{ - size_t bytes; - - bytes = audio_rate_peek_bytes(rate, info); - bytes = MIN(bytes, bytes_avail); - audio_rate_add_bytes(rate, bytes); - - return bytes; -} - AudiodevList *qmp_query_audiodevs(Error **errp) { AudiodevList *ret = NULL; @@ -2313,20 +468,3 @@ AudiodevList *qmp_query_audiodevs(Error **errp) } return ret; } - -static const TypeInfo audio_be_info = { - .name = TYPE_AUDIO_BACKEND, - .parent = TYPE_OBJECT, - .instance_size = sizeof(AudioBackend), - .instance_init = audio_be_init, - .instance_finalize = audio_be_finalize, - .abstract = false, /* TODO: subclass drivers and make it abstract */ - .class_size = sizeof(AudioBackendClass), -}; - -static void register_types(void) -{ - type_register_static(&audio_be_info); -} - -type_init(register_types); diff --git a/audio/audio_int.h b/audio/audio_int.h index b2b8002477c2..06f79ade6b0a 100644 --- a/audio/audio_int.h +++ b/audio/audio_int.h @@ -33,27 +33,13 @@ #include "qemu/audio-capture.h" #include "mixeng.h" -#ifdef CONFIG_GIO -#include -#endif - -void G_GNUC_PRINTF(2, 0) -AUD_vlog(const char *cap, const char *fmt, va_list ap); - -void G_GNUC_PRINTF(2, 3) -AUD_log(const char *cap, const char *fmt, ...); - -struct audio_pcm_ops; - struct audio_callback { void *opaque; audio_callback_fn fn; }; struct audio_pcm_info { - int bits; - bool is_signed; - bool is_float; + AudioFormat af; int freq; int nchannels; int bytes_per_frame; @@ -61,7 +47,7 @@ struct audio_pcm_info { int swap_endianness; }; -typedef struct AudioBackend AudioBackend; +typedef struct AudioMixengBackend AudioMixengBackend; typedef struct SWVoiceCap SWVoiceCap; typedef struct STSampleBuffer { @@ -70,14 +56,13 @@ typedef struct STSampleBuffer { } STSampleBuffer; typedef struct HWVoiceOut { - AudioBackend *s; + AudioMixengBackend *s; bool enabled; int poll_mode; bool pending_disable; struct audio_pcm_info info; f_sample *clip; - uint64_t ts_helper; STSampleBuffer mix_buf; void *buf_emul; @@ -86,12 +71,11 @@ typedef struct HWVoiceOut { size_t samples; QLIST_HEAD (sw_out_listhead, SWVoiceOut) sw_head; QLIST_HEAD (sw_cap_listhead, SWVoiceCap) cap_head; - struct audio_pcm_ops *pcm_ops; QLIST_ENTRY (HWVoiceOut) entries; } HWVoiceOut; typedef struct HWVoiceIn { - AudioBackend *s; + AudioMixengBackend *s; bool enabled; int poll_mode; struct audio_pcm_info info; @@ -99,7 +83,6 @@ typedef struct HWVoiceIn { t_sample *conv; size_t total_samples_captured; - uint64_t ts_helper; STSampleBuffer conv_buf; void *buf_emul; @@ -107,12 +90,11 @@ typedef struct HWVoiceIn { size_t samples; QLIST_HEAD (sw_in_listhead, SWVoiceIn) sw_head; - struct audio_pcm_ops *pcm_ops; QLIST_ENTRY (HWVoiceIn) entries; } HWVoiceIn; struct SWVoiceOut { - AudioBackend *s; + AudioMixengBackend *s; struct audio_pcm_info info; t_sample *conv; STSampleBuffer resample_buf; @@ -128,7 +110,7 @@ struct SWVoiceOut { }; struct SWVoiceIn { - AudioBackend *s; + AudioMixengBackend *s; bool active; struct audio_pcm_info info; void *rate; @@ -142,59 +124,6 @@ struct SWVoiceIn { QLIST_ENTRY (SWVoiceIn) entries; }; -typedef struct audio_driver audio_driver; -struct audio_driver { - const char *name; - void *(*init) (Audiodev *, Error **); - void (*fini) (void *); -#ifdef CONFIG_GIO - bool (*set_dbus_server)(AudioBackend *be, - GDBusObjectManagerServer *manager, - bool p2p, - Error **errp); -#endif - struct audio_pcm_ops *pcm_ops; - int max_voices_out; - int max_voices_in; - size_t voice_size_out; - size_t voice_size_in; - QLIST_ENTRY(audio_driver) next; -}; - -struct audio_pcm_ops { - int (*init_out)(HWVoiceOut *hw, audsettings *as, void *drv_opaque); - void (*fini_out)(HWVoiceOut *hw); - size_t (*write) (HWVoiceOut *hw, void *buf, size_t size); - void (*run_buffer_out)(HWVoiceOut *hw); - /* - * Get the free output buffer size. This is an upper limit. The size - * returned by function get_buffer_out may be smaller. - */ - size_t (*buffer_get_free)(HWVoiceOut *hw); - /* - * get a buffer that after later can be passed to put_buffer_out; optional - * returns the buffer, and writes it's size to size (in bytes) - */ - void *(*get_buffer_out)(HWVoiceOut *hw, size_t *size); - /* - * put back the buffer returned by get_buffer_out; optional - * buf must be equal the pointer returned by get_buffer_out, - * size may be smaller - */ - size_t (*put_buffer_out)(HWVoiceOut *hw, void *buf, size_t size); - void (*enable_out)(HWVoiceOut *hw, bool enable); - void (*volume_out)(HWVoiceOut *hw, Volume *vol); - - int (*init_in) (HWVoiceIn *hw, audsettings *as, void *drv_opaque); - void (*fini_in) (HWVoiceIn *hw); - size_t (*read) (HWVoiceIn *hw, void *buf, size_t size); - void (*run_buffer_in)(HWVoiceIn *hw); - void *(*get_buffer_in)(HWVoiceIn *hw, size_t *size); - void (*put_buffer_in)(HWVoiceIn *hw, void *buf, size_t size); - void (*enable_in)(HWVoiceIn *hw, bool enable); - void (*volume_in)(HWVoiceIn *hw, Volume *vol); -}; - audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo); int audioformat_bytes_per_sample(AudioFormat fmt); int audio_buffer_frames(AudiodevPerDirectionOptions *pdo, @@ -241,41 +170,74 @@ struct SWVoiceCap { QLIST_ENTRY (SWVoiceCap) entries; }; -typedef struct AudioBackend { - Object parent; +struct AudioMixengBackendClass { + AudioBackendClass parent_class; + + int max_voices_out; + int max_voices_in; + size_t voice_size_out; + size_t voice_size_in; + + int (*init_out)(HWVoiceOut *hw, audsettings *as); + void (*fini_out)(HWVoiceOut *hw); + size_t (*write) (HWVoiceOut *hw, void *buf, size_t size); + void (*run_buffer_out)(HWVoiceOut *hw); + /* + * Get the free output buffer size. This is an upper limit. The size + * returned by function get_buffer_out may be smaller. + */ + size_t (*buffer_get_free)(HWVoiceOut *hw); + /* + * get a buffer that after later can be passed to put_buffer_out; optional + * returns the buffer, and writes it's size to size (in bytes) + */ + void *(*get_buffer_out)(HWVoiceOut *hw, size_t *size); + /* + * put back the buffer returned by get_buffer_out; optional + * buf must be equal the pointer returned by get_buffer_out, + * size may be smaller + */ + size_t (*put_buffer_out)(HWVoiceOut *hw, void *buf, size_t size); + void (*enable_out)(HWVoiceOut *hw, bool enable); + void (*volume_out)(HWVoiceOut *hw, Volume *vol); + + int (*init_in) (HWVoiceIn *hw, audsettings *as); + void (*fini_in) (HWVoiceIn *hw); + size_t (*read) (HWVoiceIn *hw, void *buf, size_t size); + void (*run_buffer_in)(HWVoiceIn *hw); + void *(*get_buffer_in)(HWVoiceIn *hw, size_t *size); + void (*put_buffer_in)(HWVoiceIn *hw, void *buf, size_t size); + void (*enable_in)(HWVoiceIn *hw, bool enable); + void (*volume_in)(HWVoiceIn *hw, Volume *vol); +}; + +struct AudioMixengBackend { + AudioBackend parent_obj; - struct audio_driver *drv; Audiodev *dev; - void *drv_opaque; QEMUTimer *ts; + GTimer *run_timer; QLIST_HEAD (hw_in_listhead, HWVoiceIn) hw_head_in; QLIST_HEAD (hw_out_listhead, HWVoiceOut) hw_head_out; QLIST_HEAD (cap_listhead, CaptureVoiceOut) cap_head; int nb_hw_voices_out; int nb_hw_voices_in; - int vm_running; int64_t period_ticks; bool timer_running; uint64_t timer_last; VMChangeStateEntry *vmse; -} AudioBackend; +}; extern const struct mixeng_volume nominal_volume; extern const char *audio_prio_list[]; -void audio_driver_register(audio_driver *drv); - -void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as); +void audio_pcm_init_info (struct audio_pcm_info *info, const struct audsettings *as); void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len); -int audio_bug (const char *funcname, int cond); - -void audio_run(AudioBackend *s, const char *msg); - -const char *audio_application_name(void); +void audio_run(AudioMixengBackend *s, const char *msg); typedef struct RateCtl { int64_t start_ticks; @@ -307,23 +269,15 @@ static inline size_t audio_ring_posb(size_t pos, size_t dist, size_t len) return pos >= dist ? pos - dist : len - dist + pos; } -#define dolog(fmt, ...) AUD_log(AUDIO_CAP, fmt, ## __VA_ARGS__) - -#ifdef DEBUG -#define ldebug(fmt, ...) AUD_log(AUDIO_CAP, fmt, ## __VA_ARGS__) -#else -#define ldebug(fmt, ...) (void)0 -#endif - -typedef struct AudiodevListEntry { - Audiodev *dev; - QSIMPLEQ_ENTRY(AudiodevListEntry) next; -} AudiodevListEntry; - -typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead; - -void audio_create_pdos(Audiodev *dev); AudiodevPerDirectionOptions *audio_get_pdo_in(Audiodev *dev); AudiodevPerDirectionOptions *audio_get_pdo_out(Audiodev *dev); +void audio_sample_to_uint64(const st_sample *sample, int pos, + uint64_t *left, uint64_t *right); +void audio_sample_from_uint64(st_sample *sample, int pos, + uint64_t left, uint64_t right); + +#define TYPE_AUDIO_MIXENG_BACKEND "audio-mixeng-backend" +OBJECT_DECLARE_TYPE(AudioMixengBackend, AudioMixengBackendClass, AUDIO_MIXENG_BACKEND) + #endif /* QEMU_AUDIO_INT_H */ diff --git a/audio/audio_template.h b/audio/audio_template.h index 7a8c431f2d72..228369cf9a13 100644 --- a/audio/audio_template.h +++ b/audio/audio_template.h @@ -36,42 +36,42 @@ #define HWBUF hw->conv_buf #endif -static void glue(audio_init_nb_voices_, TYPE)(AudioBackend *s, - struct audio_driver *drv, int min_voices) +static void glue(audio_init_nb_voices_, TYPE)(AudioMixengBackend *s, + AudioMixengBackendClass *k, + int min_voices) { - int max_voices = glue (drv->max_voices_, TYPE); - size_t voice_size = glue(drv->voice_size_, TYPE); + const char *be_name = object_class_get_name(OBJECT_CLASS(k)); + int max_voices = glue(k->max_voices_, TYPE); + size_t voice_size = glue(k->voice_size_, TYPE); - glue (s->nb_hw_voices_, TYPE) = glue(audio_get_pdo_, TYPE)(s->dev)->voices; - if (glue (s->nb_hw_voices_, TYPE) > max_voices) { + glue(s->nb_hw_voices_, TYPE) = glue(audio_get_pdo_, TYPE)(s->dev)->voices; + if (glue(s->nb_hw_voices_, TYPE) > max_voices) { if (!max_voices) { #ifdef DAC - dolog ("Driver `%s' does not support " NAME "\n", drv->name); + warn_report("audio: '%s' backend does not support " NAME, be_name); #endif } else { - dolog ("Driver `%s' does not support %d " NAME " voices, max %d\n", - drv->name, - glue (s->nb_hw_voices_, TYPE), - max_voices); + warn_report("audio: '%s' backend does not support %d " NAME " voices, max %d", + be_name, glue(s->nb_hw_voices_, TYPE), max_voices); } - glue (s->nb_hw_voices_, TYPE) = max_voices; + glue(s->nb_hw_voices_, TYPE) = max_voices; } - if (glue (s->nb_hw_voices_, TYPE) < min_voices) { - dolog ("Bogus number of " NAME " voices %d, setting to %d\n", - glue (s->nb_hw_voices_, TYPE), - min_voices); + if (glue(s->nb_hw_voices_, TYPE) < min_voices) { + warn_report("audio: Bogus number of " NAME " voices %d, setting to %d", + glue(s->nb_hw_voices_, TYPE), + min_voices); } - if (audio_bug(__func__, !voice_size && max_voices)) { - dolog ("drv=`%s' voice_size=0 max_voices=%d\n", - drv->name, max_voices); - glue (s->nb_hw_voices_, TYPE) = 0; + if (!voice_size && max_voices) { + audio_bug("backend=`%s' voice_size=0 max_voices=%d", + be_name, max_voices); + glue(s->nb_hw_voices_, TYPE) = 0; } - if (audio_bug(__func__, voice_size && !max_voices)) { - dolog("drv=`%s' voice_size=%zu max_voices=0\n", - drv->name, voice_size); + if (voice_size && !max_voices) { + audio_bug("backend=`%s' voice_size=%zu max_voices=0", + be_name, voice_size); } } @@ -87,8 +87,8 @@ static void glue(audio_pcm_hw_alloc_resources_, TYPE)(HW *hw) { if (glue(audio_get_pdo_, TYPE)(hw->s->dev)->mixing_engine) { size_t samples = hw->samples; - if (audio_bug(__func__, samples == 0)) { - dolog("Attempted to allocate empty buffer\n"); + if (samples == 0) { + audio_bug("Attempted to allocate empty buffer"); } HWBUF.buffer = g_new0(st_sample, samples); @@ -129,7 +129,7 @@ static int glue (audio_pcm_sw_alloc_resources_, TYPE) (SW *sw) /* f_fe_min = ceil(1 [frames] * f_be [Hz] / size_be [frames]) */ f_fe_min = (f_be + HWBUF.size - 1) / HWBUF.size; qemu_log_mask(LOG_UNIMP, - AUDIO_CAP ": The guest selected a " NAME " sample rate" + "audio: The guest selected a " NAME " sample rate" " of %d Hz for %s. Only sample rates >= %" PRIu64 " Hz" " are supported.\n", sw->info.freq, sw->name, f_fe_min); @@ -159,7 +159,7 @@ static int glue (audio_pcm_sw_init_, TYPE) ( SW *sw, HW *hw, const char *name, - struct audsettings *as + const struct audsettings *as ) { int err; @@ -172,7 +172,7 @@ static int glue (audio_pcm_sw_init_, TYPE) ( sw->empty = true; #endif - if (sw->info.is_float) { + if (audio_format_is_float(hw->info.af)) { #ifdef DAC sw->conv = mixeng_conv_float[sw->info.nchannels == 2] [sw->info.swap_endianness]; @@ -187,9 +187,9 @@ static int glue (audio_pcm_sw_init_, TYPE) ( sw->clip = mixeng_clip #endif [sw->info.nchannels == 2] - [sw->info.is_signed] + [audio_format_is_signed(hw->info.af)] [sw->info.swap_endianness] - [audio_bits_to_index(sw->info.bits)]; + [audio_format_to_index(hw->info.af)]; } sw->name = g_strdup (name); @@ -221,27 +221,29 @@ static void glue (audio_pcm_hw_del_sw_, TYPE) (SW *sw) static void glue (audio_pcm_hw_gc_, TYPE) (HW **hwp) { HW *hw = *hwp; - AudioBackend *s = hw->s; + AudioMixengBackend *s = hw->s; + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_GET_CLASS(s); if (!hw->sw_head.lh_first) { #ifdef DAC audio_detach_capture(hw); #endif QLIST_REMOVE(hw, entries); - glue(hw->pcm_ops->fini_, TYPE) (hw); + glue(k->fini_, TYPE)(hw); glue(s->nb_hw_voices_, TYPE) += 1; glue(audio_pcm_hw_free_resources_ , TYPE) (hw); + object_unref(hw->s); g_free(hw); *hwp = NULL; } } -static HW *glue(audio_pcm_hw_find_any_, TYPE)(AudioBackend *s, HW *hw) +static HW *glue(audio_pcm_hw_find_any_, TYPE)(AudioMixengBackend *s, HW *hw) { return hw ? hw->entries.le_next : glue (s->hw_head_, TYPE).lh_first; } -static HW *glue(audio_pcm_hw_find_any_enabled_, TYPE)(AudioBackend *s, HW *hw) +static HW *glue(audio_pcm_hw_find_any_enabled_, TYPE)(AudioMixengBackend *s, HW *hw) { while ((hw = glue(audio_pcm_hw_find_any_, TYPE)(s, hw))) { if (hw->enabled) { @@ -251,7 +253,7 @@ static HW *glue(audio_pcm_hw_find_any_enabled_, TYPE)(AudioBackend *s, HW *hw) return NULL; } -static HW *glue(audio_pcm_hw_find_specific_, TYPE)(AudioBackend *s, HW *hw, +static HW *glue(audio_pcm_hw_find_specific_, TYPE)(AudioMixengBackend *s, HW *hw, struct audsettings *as) { while ((hw = glue(audio_pcm_hw_find_any_, TYPE)(s, hw))) { @@ -262,48 +264,42 @@ static HW *glue(audio_pcm_hw_find_specific_, TYPE)(AudioBackend *s, HW *hw, return NULL; } -static HW *glue(audio_pcm_hw_add_new_, TYPE)(AudioBackend *s, +static HW *glue(audio_pcm_hw_add_new_, TYPE)(AudioMixengBackend *s, struct audsettings *as) { HW *hw; - struct audio_driver *drv = s->drv; + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_GET_CLASS(s); - if (!glue (s->nb_hw_voices_, TYPE)) { + if (!glue(s->nb_hw_voices_, TYPE)) { return NULL; } - if (audio_bug(__func__, !drv)) { - dolog ("No host audio driver\n"); - return NULL; - } - - if (audio_bug(__func__, !drv->pcm_ops)) { - dolog ("Host audio driver without pcm_ops\n"); + if (!glue(k->init_, TYPE)) { + audio_bug("No host audio driver or missing init_%s", NAME); return NULL; } /* - * Since glue(s->nb_hw_voices_, TYPE) is != 0, glue(drv->voice_size_, TYPE) + * Since glue(s->nb_hw_voices_, TYPE) is != 0, glue(k->voice_size_, TYPE) * is guaranteed to be != 0. See the audio_init_nb_voices_* functions. */ - hw = g_malloc0(glue(drv->voice_size_, TYPE)); - hw->s = s; - hw->pcm_ops = drv->pcm_ops; + hw = g_malloc0(glue(k->voice_size_, TYPE)); + hw->s = AUDIO_MIXENG_BACKEND(object_ref(s)); QLIST_INIT (&hw->sw_head); #ifdef DAC QLIST_INIT (&hw->cap_head); #endif - if (glue (hw->pcm_ops->init_, TYPE) (hw, as, s->drv_opaque)) { + if (glue(k->init_, TYPE)(hw, as)) { goto err0; } - if (audio_bug(__func__, hw->samples <= 0)) { - dolog("hw->samples=%zd\n", hw->samples); + if (hw->samples <= 0) { + audio_bug("hw->samples=%zd", hw->samples); goto err1; } - if (hw->info.is_float) { + if (audio_format_is_float(hw->info.af)) { #ifdef DAC hw->clip = mixeng_clip_float[hw->info.nchannels == 2] [hw->info.swap_endianness]; @@ -318,9 +314,9 @@ static HW *glue(audio_pcm_hw_add_new_, TYPE)(AudioBackend *s, hw->conv = mixeng_conv #endif [hw->info.nchannels == 2] - [hw->info.is_signed] + [audio_format_is_signed(hw->info.af)] [hw->info.swap_endianness] - [audio_bits_to_index(hw->info.bits)]; + [audio_format_to_index(hw->info.af)]; } glue(audio_pcm_hw_alloc_resources_, TYPE)(hw); @@ -333,8 +329,9 @@ static HW *glue(audio_pcm_hw_add_new_, TYPE)(AudioBackend *s, return hw; err1: - glue (hw->pcm_ops->fini_, TYPE) (hw); + glue(k->fini_, TYPE)(hw); err0: + object_unref(hw->s); g_free (hw); return NULL; } @@ -398,7 +395,7 @@ AudiodevPerDirectionOptions *glue(audio_get_pdo_, TYPE)(Audiodev *dev) abort(); } -static HW *glue(audio_pcm_hw_add_, TYPE)(AudioBackend *s, struct audsettings *as) +static HW *glue(audio_pcm_hw_add_, TYPE)(AudioMixengBackend *s, struct audsettings *as) { HW *hw; AudiodevPerDirectionOptions *pdo = glue(audio_get_pdo_, TYPE)(s->dev); @@ -424,9 +421,9 @@ static HW *glue(audio_pcm_hw_add_, TYPE)(AudioBackend *s, struct audsettings *as } static SW *glue(audio_pcm_create_voice_pair_, TYPE)( - AudioBackend *s, + AudioMixengBackend *s, const char *sw_name, - struct audsettings *as + const struct audsettings *as ) { SW *sw; @@ -441,11 +438,11 @@ static SW *glue(audio_pcm_create_voice_pair_, TYPE)( } sw = g_new0(SW, 1); - sw->s = s; + sw->s = AUDIO_MIXENG_BACKEND(object_ref(s)); hw = glue(audio_pcm_hw_add_, TYPE)(s, &hw_as); if (!hw) { - dolog("Could not create a backend for voice `%s'\n", sw_name); + error_report("audio: Could not create a backend for voice '%s'", sw_name); goto err1; } @@ -461,6 +458,7 @@ static SW *glue(audio_pcm_create_voice_pair_, TYPE)( glue (audio_pcm_hw_del_sw_, TYPE) (sw); glue (audio_pcm_hw_gc_, TYPE) (&hw); err1: + object_unref(sw->s); g_free(sw); return NULL; } @@ -470,14 +468,16 @@ static void glue (audio_close_, TYPE) (SW *sw) glue (audio_pcm_sw_fini_, TYPE) (sw); glue (audio_pcm_hw_del_sw_, TYPE) (sw); glue (audio_pcm_hw_gc_, TYPE) (&sw->hw); + + object_unref(sw->s); g_free (sw); } -void glue(AUD_close_, TYPE)(AudioBackend *be, SW *sw) +static void glue(audio_mixeng_backend_close_, TYPE)(AudioBackend *be, SW *sw) { if (sw) { - if (audio_bug(__func__, !be)) { - dolog("backend=%p\n", be); + if (!be) { + audio_bug("backend=%p", be); return; } @@ -485,36 +485,41 @@ void glue(AUD_close_, TYPE)(AudioBackend *be, SW *sw) } } -SW *glue (AUD_open_, TYPE) ( +static SW *glue(audio_mixeng_backend_open_, TYPE) ( AudioBackend *be, SW *sw, const char *name, void *callback_opaque , audio_callback_fn callback_fn, - struct audsettings *as - ) + const struct audsettings *as) { - AudioBackend *s = be; + AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(be); + AudioMixengBackendClass *k; AudiodevPerDirectionOptions *pdo; - if (audio_bug(__func__, !be || !name || !callback_fn || !as)) { - dolog("backend=%p name=%p callback_fn=%p as=%p\n", - be, name, callback_fn, as); + if (!be || !name || !callback_fn || !as) { + audio_bug("backend=%p name=%p callback_fn=%p as=%p", + be, name, callback_fn, as); goto fail; } + k = AUDIO_MIXENG_BACKEND_GET_CLASS(s); pdo = glue(audio_get_pdo_, TYPE)(s->dev); - ldebug ("open %s, freq %d, nchannels %d, fmt %d\n", - name, as->freq, as->nchannels, as->fmt); +#ifdef DAC + trace_audio_open_out(name, as->freq, as->nchannels, as->fmt); +#else + trace_audio_open_in(name, as->freq, as->nchannels, as->fmt); +#endif - if (audio_bug(__func__, audio_validate_settings(as))) { - audio_print_settings (as); + if (audio_validate_settings(as)) { + g_autofree char *str = audsettings_to_string(as); + error_report("audio: Invalid audio settings: %s", str); goto fail; } - if (audio_bug(__func__, !s->drv)) { - dolog ("Can not open `%s' (no host audio driver)\n", name); + if (!glue(k->init_, TYPE)) { + error_report("audio: Can not open `%s' (no host audio driver)", name); goto fail; } @@ -523,7 +528,7 @@ SW *glue (AUD_open_, TYPE) ( } if (!pdo->fixed_settings && sw) { - glue(AUD_close_, TYPE)(be, sw); + glue(audio_be_close_, TYPE)(be, sw); sw = NULL; } @@ -531,8 +536,7 @@ SW *glue (AUD_open_, TYPE) ( HW *hw = sw->hw; if (!hw) { - dolog("Internal logic error: voice `%s' has no backend\n", - SW_NAME(sw)); + audio_bug("Internal logic error: voice '%s' has no backend", SW_NAME(sw)); goto fail; } @@ -551,58 +555,24 @@ SW *glue (AUD_open_, TYPE) ( sw->callback.fn = callback_fn; sw->callback.opaque = callback_opaque; -#ifdef DEBUG_AUDIO - dolog ("%s\n", name); - audio_pcm_print_info ("hw", &sw->hw->info); - audio_pcm_print_info ("sw", &sw->info); -#endif + trace_audio_voice_pair(G_STRINGIFY(TYPE), name, + AudioFormat_str(sw->hw->info.af), + sw->hw->info.freq, sw->hw->info.nchannels, + AudioFormat_str(sw->info.af), + sw->info.freq, sw->info.nchannels); return sw; fail: - glue(AUD_close_, TYPE)(be, sw); + glue(audio_be_close_, TYPE)(be, sw); return NULL; } -bool glue(AUD_is_active_, TYPE)(SW *sw) +static bool glue(audio_mixeng_backend_is_active_, TYPE)(AudioBackend *be, SW *sw) { return sw ? sw->active : 0; } -void glue (AUD_init_time_stamp_, TYPE) (SW *sw, QEMUAudioTimeStamp *ts) -{ - if (!sw) { - return; - } - - ts->old_ts = sw->hw->ts_helper; -} - -uint64_t glue (AUD_get_elapsed_usec_, TYPE) (SW *sw, QEMUAudioTimeStamp *ts) -{ - uint64_t delta, cur_ts, old_ts; - - if (!sw) { - return 0; - } - - cur_ts = sw->hw->ts_helper; - old_ts = ts->old_ts; - /* dolog ("cur %" PRId64 " old %" PRId64 "\n", cur_ts, old_ts); */ - - if (cur_ts >= old_ts) { - delta = cur_ts - old_ts; - } else { - delta = UINT64_MAX - old_ts + cur_ts; - } - - if (!delta) { - return 0; - } - - return muldiv64 (delta, sw->hw->info.freq, 1000000); -} - #undef TYPE #undef HW #undef SW diff --git a/audio/audio_win_int.c b/audio/audio_win_int.c index 44a8ff24a6f5..ecd599c87de1 100644 --- a/audio/audio_win_int.c +++ b/audio/audio_win_int.c @@ -2,12 +2,12 @@ #include "qemu/osdep.h" -#define AUDIO_CAP "win-int" #include #include #include #include "qemu/audio.h" +#include "qemu/error-report.h" #include "audio_int.h" #include "audio_win_int.h" @@ -53,7 +53,7 @@ int waveformat_from_audio_settings (WAVEFORMATEX *wfx, break; default: - dolog("Internal logic error: Bad audio format %d\n", as->fmt); + error_report("dsound: Internal logic error: Bad audio format %d", as->fmt); return -1; } @@ -64,7 +64,7 @@ int waveformat_to_audio_settings (WAVEFORMATEX *wfx, struct audsettings *as) { if (!wfx->nSamplesPerSec) { - dolog ("Invalid wave format, frequency is zero\n"); + error_report("dsound: Invalid wave format, frequency is zero"); return -1; } as->freq = wfx->nSamplesPerSec; @@ -79,10 +79,9 @@ int waveformat_to_audio_settings (WAVEFORMATEX *wfx, break; default: - dolog ( - "Invalid wave format, number of channels is not 1 or 2, but %d\n", - wfx->nChannels - ); + error_report("dsound: Invalid wave format, " + "number of channels is not 1 or 2, but %d", + wfx->nChannels); return -1; } @@ -101,9 +100,9 @@ int waveformat_to_audio_settings (WAVEFORMATEX *wfx, break; default: - dolog("Invalid PCM wave format, bits per sample is not " - "8, 16 or 32, but %d\n", - wfx->wBitsPerSample); + error_report("dsound: Invalid PCM wave format, bits per sample is not " + "8, 16 or 32, but %d", + wfx->wBitsPerSample); return -1; } } else if (wfx->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { @@ -113,15 +112,15 @@ int waveformat_to_audio_settings (WAVEFORMATEX *wfx, break; default: - dolog("Invalid IEEE_FLOAT wave format, bits per sample is not " - "32, but %d\n", - wfx->wBitsPerSample); + error_report("dsound: Invalid IEEE_FLOAT wave format, " + "bits per sample is not 32, but %d", + wfx->wBitsPerSample); return -1; } } else { - dolog("Invalid wave format, tag is not PCM and not IEEE_FLOAT, " - "but %d\n", - wfx->wFormatTag); + error_report("dsound: Invalid wave format, " + "tag is not PCM and not IEEE_FLOAT, but %d", + wfx->wFormatTag); return -1; } diff --git a/audio/coreaudio.m b/audio/coreaudio.m index 997017a1e96d..bc9ab7477b68 100644 --- a/audio/coreaudio.m +++ b/audio/coreaudio.m @@ -28,11 +28,18 @@ #include "qemu/main-loop.h" #include "qemu/module.h" +#include "qemu/error-report.h" #include "qemu/audio.h" - -#define AUDIO_CAP "coreaudio" +#include "qom/object.h" #include "audio_int.h" +#define TYPE_AUDIO_COREAUDIO "audio-coreaudio" +OBJECT_DECLARE_SIMPLE_TYPE(AudioCoreaudio, AUDIO_COREAUDIO) + +struct AudioCoreaudio { + AudioMixengBackend parent_obj; +}; + typedef struct coreaudioVoiceOut { HWVoiceOut hw; pthread_mutex_t buf_mutex; @@ -149,7 +156,7 @@ static OSStatus coreaudio_get_isrunning(AudioDeviceID id, UInt32 *result) result); } -static void coreaudio_logstatus (OSStatus status) +static void coreaudio_logstatus(OSStatus status) { const char *str = "BUG"; @@ -199,70 +206,64 @@ static void coreaudio_logstatus (OSStatus status) break; default: - AUD_log (AUDIO_CAP, "Reason: status code %" PRId32 "\n", (int32_t)status); + error_printf(" Reason: status code %" PRId32, (int32_t)status); return; } - AUD_log (AUDIO_CAP, "Reason: %s\n", str); + error_printf(" Reason: %s", str); } -static void G_GNUC_PRINTF (2, 3) coreaudio_logerr ( - OSStatus status, - const char *fmt, - ... - ) +static void G_GNUC_PRINTF(2, 3) coreaudio_logerr(OSStatus status, + const char *fmt, ...) { va_list ap; - va_start (ap, fmt); - AUD_log (AUDIO_CAP, fmt, ap); - va_end (ap); - - coreaudio_logstatus (status); + error_printf("coreaudio: "); + va_start(ap, fmt); + error_vprintf(fmt, ap); + va_end(ap); + coreaudio_logstatus(status); + error_printf("\n"); } -static void G_GNUC_PRINTF (3, 4) coreaudio_logerr2 ( - OSStatus status, - const char *typ, - const char *fmt, - ... - ) +static void G_GNUC_PRINTF(3, 4) coreaudio_logerr2(OSStatus status, + const char *typ, + const char *fmt, ...) { va_list ap; - AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ); - - va_start (ap, fmt); - AUD_vlog (AUDIO_CAP, fmt, ap); - va_end (ap); - - coreaudio_logstatus (status); + error_printf("coreaudio: Could not initialize %s: ", typ); + va_start(ap, fmt); + error_vprintf(fmt, ap); + va_end(ap); + coreaudio_logstatus(status); + error_printf("\n"); } #define coreaudio_playback_logerr(status, ...) \ coreaudio_logerr2(status, "playback", __VA_ARGS__) -static int coreaudio_buf_lock (coreaudioVoiceOut *core, const char *fn_name) +static int coreaudio_buf_lock(coreaudioVoiceOut *core, const char *fn_name) { int err; - err = pthread_mutex_lock (&core->buf_mutex); + err = pthread_mutex_lock(&core->buf_mutex); if (err) { - dolog ("Could not lock voice for %s\nReason: %s\n", - fn_name, strerror (err)); + error_report("coreaudio: Could not lock voice for %s: %s", + fn_name, strerror(err)); return -1; } return 0; } -static int coreaudio_buf_unlock (coreaudioVoiceOut *core, const char *fn_name) +static int coreaudio_buf_unlock(coreaudioVoiceOut *core, const char *fn_name) { int err; - err = pthread_mutex_unlock (&core->buf_mutex); + err = pthread_mutex_unlock(&core->buf_mutex); if (err) { - dolog ("Could not unlock voice for %s\nReason: %s\n", - fn_name, strerror (err)); + error_report("coreaudio: Could not unlock voice for %s: %s", + fn_name, strerror(err)); return -1; } return 0; @@ -271,7 +272,7 @@ static int coreaudio_buf_unlock (coreaudioVoiceOut *core, const char *fn_name) #define COREAUDIO_WRAPPER_FUNC(name, ret_type, args_decl, args) \ static ret_type glue(coreaudio_, name)args_decl \ { \ - coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; \ + coreaudioVoiceOut *core = (coreaudioVoiceOut *)hw; \ ret_type ret; \ \ if (coreaudio_buf_lock(core, "coreaudio_" #name)) { \ @@ -309,16 +310,16 @@ static OSStatus audioDeviceIOProc( UInt32 frameCount, pending_frames; void *out = outOutputData->mBuffers[0].mData; HWVoiceOut *hw = hwptr; - coreaudioVoiceOut *core = (coreaudioVoiceOut *) hwptr; + coreaudioVoiceOut *core = hwptr; size_t len; - if (coreaudio_buf_lock (core, "audioDeviceIOProc")) { + if (coreaudio_buf_lock(core, "audioDeviceIOProc")) { inInputTime = 0; return 0; } if (inDevice != core->outputDeviceID) { - coreaudio_buf_unlock (core, "audioDeviceIOProc(old device)"); + coreaudio_buf_unlock(core, "audioDeviceIOProc(old device)"); return 0; } @@ -328,7 +329,7 @@ static OSStatus audioDeviceIOProc( /* if there are not enough samples, set signal and return */ if (pending_frames < frameCount) { inInputTime = 0; - coreaudio_buf_unlock (core, "audioDeviceIOProc(empty)"); + coreaudio_buf_unlock(core, "audioDeviceIOProc(empty)"); return 0; } @@ -348,7 +349,7 @@ static OSStatus audioDeviceIOProc( out += write_len; } - coreaudio_buf_unlock (core, "audioDeviceIOProc"); + coreaudio_buf_unlock(core, "audioDeviceIOProc"); return 0; } @@ -358,7 +359,7 @@ static OSStatus init_out_device(coreaudioVoiceOut *core) AudioValueRange frameRange; AudioStreamBasicDescription streamBasicDescription = { - .mBitsPerChannel = core->hw.info.bits, + .mBitsPerChannel = audio_format_bits(core->hw.info.af), .mBytesPerFrame = core->hw.info.bytes_per_frame, .mBytesPerPacket = core->hw.info.bytes_per_frame, .mChannelsPerFrame = core->hw.info.nchannels, @@ -370,12 +371,13 @@ static OSStatus init_out_device(coreaudioVoiceOut *core) status = coreaudio_get_voice(&core->outputDeviceID); if (status != kAudioHardwareNoError) { - coreaudio_playback_logerr (status, - "Could not get default output Device\n"); + coreaudio_playback_logerr(status, + "Could not get default output device"); return status; } if (core->outputDeviceID == kAudioDeviceUnknown) { - dolog ("Could not initialize playback - Unknown Audiodevice\n"); + error_report("coreaudio: Could not initialize playback: " + "Unknown audio device"); return status; } @@ -386,17 +388,19 @@ static OSStatus init_out_device(coreaudioVoiceOut *core) return 0; } if (status != kAudioHardwareNoError) { - coreaudio_playback_logerr (status, - "Could not get device buffer frame range\n"); + coreaudio_playback_logerr(status, + "Could not get device buffer frame range"); return status; } if (frameRange.mMinimum > core->frameSizeSetting) { - core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum; - dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum); + core->audioDevicePropertyBufferFrameSize = frameRange.mMinimum; + warn_report("coreaudio: Upsizing buffer frames to %f", + frameRange.mMinimum); } else if (frameRange.mMaximum < core->frameSizeSetting) { - core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum; - dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum); + core->audioDevicePropertyBufferFrameSize = frameRange.mMaximum; + warn_report("coreaudio: Downsizing buffer frames to %f", + frameRange.mMaximum); } else { core->audioDevicePropertyBufferFrameSize = core->frameSizeSetting; } @@ -408,9 +412,9 @@ static OSStatus init_out_device(coreaudioVoiceOut *core) return 0; } if (status != kAudioHardwareNoError) { - coreaudio_playback_logerr (status, - "Could not set device buffer frame size %" PRIu32 "\n", - (uint32_t)core->audioDevicePropertyBufferFrameSize); + coreaudio_playback_logerr(status, + "Could not set device buffer frame size %" PRIu32, + (uint32_t)core->audioDevicePropertyBufferFrameSize); return status; } @@ -421,8 +425,8 @@ static OSStatus init_out_device(coreaudioVoiceOut *core) return 0; } if (status != kAudioHardwareNoError) { - coreaudio_playback_logerr (status, - "Could not get device buffer frame size\n"); + coreaudio_playback_logerr(status, + "Could not get device buffer frame size"); return status; } core->hw.samples = core->bufferCount * core->audioDevicePropertyBufferFrameSize; @@ -434,9 +438,9 @@ static OSStatus init_out_device(coreaudioVoiceOut *core) return 0; } if (status != kAudioHardwareNoError) { - coreaudio_playback_logerr (status, - "Could not set samplerate %lf\n", - streamBasicDescription.mSampleRate); + coreaudio_playback_logerr(status, + "Could not set samplerate %lf", + streamBasicDescription.mSampleRate); core->outputDeviceID = kAudioDeviceUnknown; return status; } @@ -460,7 +464,7 @@ static OSStatus init_out_device(coreaudioVoiceOut *core) return 0; } if (status != kAudioHardwareNoError || core->ioprocid == NULL) { - coreaudio_playback_logerr (status, "Could not set IOProc\n"); + coreaudio_playback_logerr(status, "Could not set IOProc"); core->outputDeviceID = kAudioDeviceUnknown; return status; } @@ -478,13 +482,13 @@ static void fini_out_device(coreaudioVoiceOut *core) if (status != kAudioHardwareBadObjectError) { if (status != kAudioHardwareNoError) { coreaudio_logerr(status, - "Could not determine whether Device is playing\n"); + "Could not determine whether device is playing"); } if (isrunning) { status = AudioDeviceStop(core->outputDeviceID, core->ioprocid); if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { - coreaudio_logerr(status, "Could not stop playback\n"); + coreaudio_logerr(status, "Could not stop playback"); } } } @@ -493,7 +497,7 @@ static void fini_out_device(coreaudioVoiceOut *core) status = AudioDeviceDestroyIOProcID(core->outputDeviceID, core->ioprocid); if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { - coreaudio_logerr(status, "Could not remove IOProc\n"); + coreaudio_logerr(status, "Could not remove IOProc"); } core->outputDeviceID = kAudioDeviceUnknown; } @@ -507,7 +511,7 @@ static void update_device_playback_state(coreaudioVoiceOut *core) if (status != kAudioHardwareNoError) { if (status != kAudioHardwareBadObjectError) { coreaudio_logerr(status, - "Could not determine whether Device is playing\n"); + "Could not determine whether device is playing"); } return; @@ -518,7 +522,7 @@ static void update_device_playback_state(coreaudioVoiceOut *core) if (!isrunning) { status = AudioDeviceStart(core->outputDeviceID, core->ioprocid); if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { - coreaudio_logerr (status, "Could not resume playback\n"); + coreaudio_logerr(status, "Could not resume playback"); } } } else { @@ -527,7 +531,7 @@ static void update_device_playback_state(coreaudioVoiceOut *core) status = AudioDeviceStop(core->outputDeviceID, core->ioprocid); if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { - coreaudio_logerr(status, "Could not pause playback\n"); + coreaudio_logerr(status, "Could not pause playback"); } } } @@ -556,27 +560,26 @@ static OSStatus handle_voice_change( return 0; } -static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as, - void *drv_opaque) +static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as) { OSStatus status; - coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + coreaudioVoiceOut *core = (coreaudioVoiceOut *)hw; int err; - Audiodev *dev = drv_opaque; + Audiodev *dev = hw->s->dev; AudiodevCoreaudioPerDirectionOptions *cpdo = dev->u.coreaudio.out; struct audsettings obt_as; /* create mutex */ err = pthread_mutex_init(&core->buf_mutex, NULL); if (err) { - dolog("Could not create mutex\nReason: %s\n", strerror (err)); + error_report("coreaudio: Could not create mutex: %s", strerror(err)); return -1; } obt_as = *as; as = &obt_as; as->fmt = AUDIO_FORMAT_F32; - audio_pcm_init_info (&hw->info, as); + audio_pcm_init_info(&hw->info, as); core->frameSizeSetting = audio_buffer_frames( qapi_AudiodevCoreaudioPerDirectionOptions_base(cpdo), as, 11610); @@ -587,8 +590,8 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as, &voice_addr, handle_voice_change, core); if (status != kAudioHardwareNoError) { - coreaudio_playback_logerr (status, - "Could not listen to voice property change\n"); + coreaudio_playback_logerr(status, + "Could not listen to voice property change"); return -1; } @@ -599,7 +602,7 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as, core); if (status != kAudioHardwareNoError) { coreaudio_playback_logerr(status, - "Could not remove voice property change listener\n"); + "Could not remove voice property change listener"); } return -1; @@ -612,14 +615,14 @@ static void coreaudio_fini_out (HWVoiceOut *hw) { OSStatus status; int err; - coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + coreaudioVoiceOut *core = (coreaudioVoiceOut *)hw; status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &voice_addr, handle_voice_change, core); if (status != kAudioHardwareNoError) { - coreaudio_logerr(status, "Could not remove voice property change listener\n"); + coreaudio_logerr(status, "Could not remove voice property change listener"); } fini_out_device(core); @@ -627,54 +630,48 @@ static void coreaudio_fini_out (HWVoiceOut *hw) /* destroy mutex */ err = pthread_mutex_destroy(&core->buf_mutex); if (err) { - dolog("Could not destroy mutex\nReason: %s\n", strerror (err)); + error_report("coreaudio: Could not destroy mutex: %s", strerror(err)); } } static void coreaudio_enable_out(HWVoiceOut *hw, bool enable) { - coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + coreaudioVoiceOut *core = (coreaudioVoiceOut *)hw; core->enabled = enable; update_device_playback_state(core); } -static void *coreaudio_audio_init(Audiodev *dev, Error **errp) +static void audio_coreaudio_class_init(ObjectClass *klass, const void *data) { - return dev; + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_CLASS(klass); + + k->max_voices_out = 1; + k->max_voices_in = 0; + k->voice_size_out = sizeof(coreaudioVoiceOut); + k->voice_size_in = 0; + + k->init_out = coreaudio_init_out; + k->fini_out = coreaudio_fini_out; + /* wrapper for audio_generic_write */ + k->write = coreaudio_write; + /* wrapper for audio_generic_buffer_get_free */ + k->buffer_get_free = coreaudio_buffer_get_free; + /* wrapper for audio_generic_get_buffer_out */ + k->get_buffer_out = coreaudio_get_buffer_out; + /* wrapper for audio_generic_put_buffer_out */ + k->put_buffer_out = coreaudio_put_buffer_out; + k->enable_out = coreaudio_enable_out; } -static void coreaudio_audio_fini (void *opaque) -{ -} - -static struct audio_pcm_ops coreaudio_pcm_ops = { - .init_out = coreaudio_init_out, - .fini_out = coreaudio_fini_out, - /* wrapper for audio_generic_write */ - .write = coreaudio_write, - /* wrapper for audio_generic_buffer_get_free */ - .buffer_get_free = coreaudio_buffer_get_free, - /* wrapper for audio_generic_get_buffer_out */ - .get_buffer_out = coreaudio_get_buffer_out, - /* wrapper for audio_generic_put_buffer_out */ - .put_buffer_out = coreaudio_put_buffer_out, - .enable_out = coreaudio_enable_out +static const TypeInfo audio_types[] = { + { + .name = TYPE_AUDIO_COREAUDIO, + .parent = TYPE_AUDIO_MIXENG_BACKEND, + .instance_size = sizeof(AudioCoreaudio), + .class_init = audio_coreaudio_class_init, + }, }; -static struct audio_driver coreaudio_audio_driver = { - .name = "coreaudio", - .init = coreaudio_audio_init, - .fini = coreaudio_audio_fini, - .pcm_ops = &coreaudio_pcm_ops, - .max_voices_out = 1, - .max_voices_in = 0, - .voice_size_out = sizeof (coreaudioVoiceOut), - .voice_size_in = 0 -}; - -static void register_audio_coreaudio(void) -{ - audio_driver_register(&coreaudio_audio_driver); -} -type_init(register_audio_coreaudio); +DEFINE_TYPES(audio_types) +module_obj(TYPE_AUDIO_COREAUDIO); diff --git a/audio/dbusaudio.c b/audio/dbusaudio.c index d729a810aa61..e9bdf6adb5bd 100644 --- a/audio/dbusaudio.c +++ b/audio/dbusaudio.c @@ -26,15 +26,16 @@ #include "qemu/error-report.h" #include "qemu/module.h" #include "qemu/dbus.h" +#include "qom/object.h" #ifdef G_OS_UNIX #include #endif #include "ui/dbus.h" +#include "ui/dbus-display.h" #include "ui/dbus-display1.h" -#define AUDIO_CAP "dbus" #include "qemu/audio.h" #include "audio_int.h" #include "trace.h" @@ -43,15 +44,22 @@ #define DBUS_DEFAULT_AUDIO_NSAMPLES 480 -typedef struct DBusAudio { - Audiodev *dev; +#define TYPE_AUDIO_DBUS "audio-dbus" +OBJECT_DECLARE_SIMPLE_TYPE(AudioDbus, AUDIO_DBUS) + +static AudioBackendClass *audio_dbus_parent_class; + +struct AudioDbus { + AudioMixengBackend parent_obj; + GDBusObjectManagerServer *server; bool p2p; GDBusObjectSkeleton *audio; QemuDBusDisplay1Audio *iface; GHashTable *out_listeners; GHashTable *in_listeners; -} DBusAudio; +}; + typedef struct DBusVoiceOut { HWVoiceOut hw; @@ -94,7 +102,7 @@ static void *dbus_get_buffer_out(HWVoiceOut *hw, size_t *size) static size_t dbus_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size) { - DBusAudio *da = (DBusAudio *)hw->s->drv_opaque; + AudioDbus *da = AUDIO_DBUS(hw->s); DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw); GHashTableIter iter; QemuDBusDisplay1AudioOutListener *listener = NULL; @@ -139,9 +147,9 @@ dbus_init_out_listener(QemuDBusDisplay1AudioOutListener *listener, qemu_dbus_display1_audio_out_listener_call_init( listener, (uintptr_t)hw, - hw->info.bits, - hw->info.is_signed, - hw->info.is_float, + audio_format_bits(hw->info.af), + audio_format_is_signed(hw->info.af), + audio_format_is_float(hw->info.af), hw->info.freq, hw->info.nchannels, hw->info.bytes_per_frame, @@ -151,9 +159,9 @@ dbus_init_out_listener(QemuDBusDisplay1AudioOutListener *listener, } static guint -dbus_audio_get_nsamples(DBusAudio *da) +dbus_audio_get_nsamples(AudioDbus *da) { - AudiodevDBusOptions *opts = &da->dev->u.dbus; + AudiodevDBusOptions *opts = &AUDIO_MIXENG_BACKEND(da)->dev->u.dbus; if (opts->has_nsamples && opts->nsamples) { return opts->nsamples; @@ -163,9 +171,9 @@ dbus_audio_get_nsamples(DBusAudio *da) } static int -dbus_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque) +dbus_init_out(HWVoiceOut *hw, struct audsettings *as) { - DBusAudio *da = (DBusAudio *)hw->s->drv_opaque; + AudioDbus *da = AUDIO_DBUS(hw->s); DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw); GHashTableIter iter; QemuDBusDisplay1AudioOutListener *listener = NULL; @@ -184,7 +192,7 @@ dbus_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque) static void dbus_fini_out(HWVoiceOut *hw) { - DBusAudio *da = (DBusAudio *)hw->s->drv_opaque; + AudioDbus *da = AUDIO_DBUS(hw->s); DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw); GHashTableIter iter; QemuDBusDisplay1AudioOutListener *listener = NULL; @@ -203,7 +211,7 @@ dbus_fini_out(HWVoiceOut *hw) static void dbus_enable_out(HWVoiceOut *hw, bool enable) { - DBusAudio *da = (DBusAudio *)hw->s->drv_opaque; + AudioDbus *da = AUDIO_DBUS(hw->s); DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw); GHashTableIter iter; QemuDBusDisplay1AudioOutListener *listener = NULL; @@ -245,7 +253,7 @@ dbus_volume_out_listener(HWVoiceOut *hw, static void dbus_volume_out(HWVoiceOut *hw, Volume *vol) { - DBusAudio *da = (DBusAudio *)hw->s->drv_opaque; + AudioDbus *da = AUDIO_DBUS(hw->s); DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw); GHashTableIter iter; QemuDBusDisplay1AudioOutListener *listener = NULL; @@ -265,9 +273,9 @@ dbus_init_in_listener(QemuDBusDisplay1AudioInListener *listener, HWVoiceIn *hw) qemu_dbus_display1_audio_in_listener_call_init( listener, (uintptr_t)hw, - hw->info.bits, - hw->info.is_signed, - hw->info.is_float, + audio_format_bits(hw->info.af), + audio_format_is_signed(hw->info.af), + audio_format_is_float(hw->info.af), hw->info.freq, hw->info.nchannels, hw->info.bytes_per_frame, @@ -277,9 +285,9 @@ dbus_init_in_listener(QemuDBusDisplay1AudioInListener *listener, HWVoiceIn *hw) } static int -dbus_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) +dbus_init_in(HWVoiceIn *hw, struct audsettings *as) { - DBusAudio *da = (DBusAudio *)hw->s->drv_opaque; + AudioDbus *da = AUDIO_DBUS(hw->s); DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw); GHashTableIter iter; QemuDBusDisplay1AudioInListener *listener = NULL; @@ -298,7 +306,7 @@ dbus_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) static void dbus_fini_in(HWVoiceIn *hw) { - DBusAudio *da = (DBusAudio *)hw->s->drv_opaque; + AudioDbus *da = AUDIO_DBUS(hw->s); GHashTableIter iter; QemuDBusDisplay1AudioInListener *listener = NULL; @@ -335,7 +343,7 @@ dbus_volume_in_listener(HWVoiceIn *hw, static void dbus_volume_in(HWVoiceIn *hw, Volume *vol) { - DBusAudio *da = (DBusAudio *)hw->s->drv_opaque; + AudioDbus *da = AUDIO_DBUS(hw->s); DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw); GHashTableIter iter; QemuDBusDisplay1AudioInListener *listener = NULL; @@ -352,7 +360,7 @@ dbus_volume_in(HWVoiceIn *hw, Volume *vol) static size_t dbus_read(HWVoiceIn *hw, void *buf, size_t size) { - DBusAudio *da = (DBusAudio *)hw->s->drv_opaque; + AudioDbus *da = AUDIO_DBUS(hw->s); /* DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw); */ GHashTableIter iter; QemuDBusDisplay1AudioInListener *listener = NULL; @@ -387,7 +395,7 @@ dbus_read(HWVoiceIn *hw, void *buf, size_t size) static void dbus_enable_in(HWVoiceIn *hw, bool enable) { - DBusAudio *da = (DBusAudio *)hw->s->drv_opaque; + AudioDbus *da = AUDIO_DBUS(hw->s); DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw); GHashTableIter iter; QemuDBusDisplay1AudioInListener *listener = NULL; @@ -405,23 +413,31 @@ dbus_enable_in(HWVoiceIn *hw, bool enable) } } -static void * -dbus_audio_init(Audiodev *dev, Error **errp) +static bool +audio_dbus_realize(AudioBackend *abe, Audiodev *dev, Error **errp) { - DBusAudio *da = g_new0(DBusAudio, 1); + AudioDbus *da = AUDIO_DBUS(abe); + + if (!qemu_using_dbus_display(errp)) { + qapi_free_Audiodev(dev); + return false; + } + + if (!audio_dbus_parent_class->realize(abe, dev, errp)) { + return false; + } - da->dev = dev; da->out_listeners = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, g_object_unref); + g_free, g_object_unref); da->in_listeners = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, g_object_unref); - return da; + g_free, g_object_unref); + return true; } static void -dbus_audio_fini(void *opaque) +audio_dbus_finalize(Object *obj) { - DBusAudio *da = opaque; + AudioDbus *da = AUDIO_DBUS(obj); if (da->server) { g_dbus_object_manager_server_unexport(da->server, @@ -432,14 +448,13 @@ dbus_audio_fini(void *opaque) g_clear_pointer(&da->in_listeners, g_hash_table_unref); g_clear_pointer(&da->out_listeners, g_hash_table_unref); g_clear_object(&da->server); - g_free(da); } static void listener_out_vanished_cb(GDBusConnection *connection, gboolean remote_peer_vanished, GError *error, - DBusAudio *da) + AudioDbus *da) { char *name = g_object_get_data(G_OBJECT(connection), "name"); @@ -450,7 +465,7 @@ static void listener_in_vanished_cb(GDBusConnection *connection, gboolean remote_peer_vanished, GError *error, - DBusAudio *da) + AudioDbus *da) { char *name = g_object_get_data(G_OBJECT(connection), "name"); @@ -458,7 +473,7 @@ listener_in_vanished_cb(GDBusConnection *connection, } static gboolean -dbus_audio_register_listener(AudioBackend *s, +dbus_audio_register_listener(AudioMixengBackend *s, GDBusMethodInvocation *invocation, #ifdef G_OS_UNIX GUnixFDList *fd_list, @@ -466,7 +481,7 @@ dbus_audio_register_listener(AudioBackend *s, GVariant *arg_listener, bool out) { - DBusAudio *da = s->drv_opaque; + AudioDbus *da = AUDIO_DBUS(s); const char *sender = da->p2p ? "p2p" : g_dbus_method_invocation_get_sender(invocation); g_autoptr(GDBusConnection) listener_conn = NULL; @@ -615,7 +630,7 @@ dbus_audio_register_listener(AudioBackend *s, } static gboolean -dbus_audio_register_out_listener(AudioBackend *s, +dbus_audio_register_out_listener(AudioMixengBackend *s, GDBusMethodInvocation *invocation, #ifdef G_OS_UNIX GUnixFDList *fd_list, @@ -631,7 +646,7 @@ dbus_audio_register_out_listener(AudioBackend *s, } static gboolean -dbus_audio_register_in_listener(AudioBackend *s, +dbus_audio_register_in_listener(AudioMixengBackend *s, GDBusMethodInvocation *invocation, #ifdef G_OS_UNIX GUnixFDList *fd_list, @@ -651,9 +666,8 @@ dbus_audio_set_server(AudioBackend *s, bool p2p, Error **errp) { - DBusAudio *da = s->drv_opaque; + AudioDbus *da = AUDIO_DBUS(s); - g_assert(da); g_assert(!da->server); da->server = g_object_ref(server); @@ -676,39 +690,47 @@ dbus_audio_set_server(AudioBackend *s, return true; } -static struct audio_pcm_ops dbus_pcm_ops = { - .init_out = dbus_init_out, - .fini_out = dbus_fini_out, - .write = audio_generic_write, - .get_buffer_out = dbus_get_buffer_out, - .put_buffer_out = dbus_put_buffer_out, - .enable_out = dbus_enable_out, - .volume_out = dbus_volume_out, - - .init_in = dbus_init_in, - .fini_in = dbus_fini_in, - .read = dbus_read, - .run_buffer_in = audio_generic_run_buffer_in, - .enable_in = dbus_enable_in, - .volume_in = dbus_volume_in, -}; +static void audio_dbus_class_init(ObjectClass *klass, const void *data) +{ + AudioBackendClass *b = AUDIO_BACKEND_CLASS(klass); + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_CLASS(klass); + + audio_dbus_parent_class = AUDIO_BACKEND_CLASS(object_class_get_parent(klass)); + + b->realize = audio_dbus_realize; + b->set_dbus_server = dbus_audio_set_server; + k->max_voices_out = INT_MAX; + k->max_voices_in = INT_MAX; + k->voice_size_out = sizeof(DBusVoiceOut); + k->voice_size_in = sizeof(DBusVoiceIn); + + k->init_out = dbus_init_out; + k->fini_out = dbus_fini_out; + k->write = audio_generic_write; + k->get_buffer_out = dbus_get_buffer_out; + k->put_buffer_out = dbus_put_buffer_out; + k->enable_out = dbus_enable_out; + k->volume_out = dbus_volume_out; + + k->init_in = dbus_init_in; + k->fini_in = dbus_fini_in; + k->read = dbus_read; + k->run_buffer_in = audio_generic_run_buffer_in; + k->enable_in = dbus_enable_in; + k->volume_in = dbus_volume_in; +} -static struct audio_driver dbus_audio_driver = { - .name = "dbus", - .init = dbus_audio_init, - .fini = dbus_audio_fini, - .set_dbus_server = dbus_audio_set_server, - .pcm_ops = &dbus_pcm_ops, - .max_voices_out = INT_MAX, - .max_voices_in = INT_MAX, - .voice_size_out = sizeof(DBusVoiceOut), - .voice_size_in = sizeof(DBusVoiceIn) +static const TypeInfo audio_types[] = { + { + .name = TYPE_AUDIO_DBUS, + .parent = TYPE_AUDIO_MIXENG_BACKEND, + .instance_size = sizeof(AudioDbus), + .instance_finalize = audio_dbus_finalize, + .class_init = audio_dbus_class_init, + }, }; -static void register_audio_dbus(void) -{ - audio_driver_register(&dbus_audio_driver); -} -type_init(register_audio_dbus); +DEFINE_TYPES(audio_types) module_dep("ui-dbus") +module_obj(TYPE_AUDIO_DBUS) diff --git a/audio/dsound_template.h b/audio/dsound_template.h index 0678f2de38be..f97611208752 100644 --- a/audio/dsound_template.h +++ b/audio/dsound_template.h @@ -53,9 +53,9 @@ static int glue (dsound_unlock_, TYPE) ( { HRESULT hr; - hr = glue (IFACE, _Unlock) (buf, p1, blen1, p2, blen2); - if (FAILED (hr)) { - dsound_logerr (hr, "Could not unlock " NAME "\n"); + hr = glue(IFACE, _Unlock)(buf, p1, blen1, p2, blen2); + if (FAILED(hr)) { + dsound_logerr(hr, "Could not unlock " NAME); return -1; } @@ -72,7 +72,7 @@ static int glue (dsound_lock_, TYPE) ( DWORD *blen1p, DWORD *blen2p, int entire, - dsound *s + AudioDsound *s ) { HRESULT hr; @@ -85,35 +85,35 @@ static int glue (dsound_lock_, TYPE) ( #endif hr = glue(IFACE, _Lock)(buf, pos, len, p1p, blen1p, p2p, blen2p, flag); - if (FAILED (hr)) { + if (FAILED(hr)) { #ifndef DSBTYPE_IN if (hr == DSERR_BUFFERLOST) { - if (glue (dsound_restore_, TYPE) (buf, s)) { - dsound_logerr (hr, "Could not lock " NAME "\n"); + if (glue(dsound_restore_, TYPE)(buf, s)) { + dsound_logerr(hr, "Could not lock " NAME); } goto fail; } #endif - dsound_logerr (hr, "Could not lock " NAME "\n"); + dsound_logerr(hr, "Could not lock " NAME); goto fail; } if ((p1p && *p1p && (*blen1p % info->bytes_per_frame)) || (p2p && *p2p && (*blen2p % info->bytes_per_frame))) { - dolog("DirectSound returned misaligned buffer %ld %ld\n", - *blen1p, *blen2p); + error_report("dsound: returned misaligned buffer %ld %ld", + *blen1p, *blen2p); glue(dsound_unlock_, TYPE)(buf, *p1p, p2p ? *p2p : NULL, *blen1p, blen2p ? *blen2p : 0); goto fail; } if (p1p && !*p1p && *blen1p) { - dolog("warning: !p1 && blen1=%ld\n", *blen1p); + warn_report("dsound: !p1 && blen1=%ld", *blen1p); *blen1p = 0; } if (p2p && !*p2p && *blen2p) { - dolog("warning: !p2 && blen2=%ld\n", *blen2p); + warn_report("dsound: !p2 && blen2=%ld", *blen2p); *blen2p = 0; } @@ -143,30 +143,28 @@ static void dsound_fini_out (HWVoiceOut *hw) #endif if (ds->FIELD) { - hr = glue (IFACE, _Stop) (ds->FIELD); - if (FAILED (hr)) { - dsound_logerr (hr, "Could not stop " NAME "\n"); + hr = glue(IFACE, _Stop)(ds->FIELD); + if (FAILED(hr)) { + dsound_logerr(hr, "Could not stop " NAME); } - hr = glue (IFACE, _Release) (ds->FIELD); - if (FAILED (hr)) { - dsound_logerr (hr, "Could not release " NAME "\n"); + hr = glue(IFACE, _Release)(ds->FIELD); + if (FAILED(hr)) { + dsound_logerr(hr, "Could not release " NAME); } ds->FIELD = NULL; } } #ifdef DSBTYPE_IN -static int dsound_init_in(HWVoiceIn *hw, struct audsettings *as, - void *drv_opaque) +static int dsound_init_in(HWVoiceIn *hw, struct audsettings *as) #else -static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as, - void *drv_opaque) +static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as) #endif { int err; HRESULT hr; - dsound *s = drv_opaque; + AudioDsound *s = AUDIO_DSOUND(hw->s); WAVEFORMATEX wfx; struct audsettings obt_as; #ifdef DSBTYPE_IN @@ -174,17 +172,18 @@ static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as, DSoundVoiceIn *ds = (DSoundVoiceIn *) hw; DSCBUFFERDESC bd; DSCBCAPS bc; - AudiodevPerDirectionOptions *pdo = s->dev->u.dsound.in; + AudiodevPerDirectionOptions *pdo = hw->s->dev->u.dsound.in; #else const char *typ = "DAC"; DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; DSBUFFERDESC bd; DSBCAPS bc; - AudiodevPerDirectionOptions *pdo = s->dev->u.dsound.out; + AudiodevPerDirectionOptions *pdo = hw->s->dev->u.dsound.out; #endif if (!s->FIELD2) { - dolog ("Attempt to initialize voice without " NAME2 " object\n"); + error_report("dsound: Attempt to initialize voice without " + NAME2 " object"); return -1; } @@ -214,28 +213,28 @@ static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as, ); #endif - if (FAILED (hr)) { - dsound_logerr2 (hr, typ, "Could not create " NAME "\n"); + if (FAILED(hr)) { + dsound_logerr2(hr, typ, "Could not create " NAME); return -1; } - hr = glue (IFACE, _GetFormat) (ds->FIELD, &wfx, sizeof (wfx), NULL); - if (FAILED (hr)) { - dsound_logerr2 (hr, typ, "Could not get " NAME " format\n"); + hr = glue(IFACE, _GetFormat)(ds->FIELD, &wfx, sizeof(wfx), NULL); + if (FAILED(hr)) { + dsound_logerr2(hr, typ, "Could not get " NAME " format"); goto fail0; } -#ifdef DEBUG_DSOUND - dolog (NAME "\n"); - print_wave_format (&wfx); -#endif + trace_dsound_wave_format( + wfx.wFormatTag, wfx.nChannels, + wfx.nSamplesPerSec, wfx.nAvgBytesPerSec, + wfx.nBlockAlign, wfx.wBitsPerSample, wfx.cbSize); memset (&bc, 0, sizeof (bc)); bc.dwSize = sizeof (bc); - hr = glue (IFACE, _GetCaps) (ds->FIELD, &bc); - if (FAILED (hr)) { - dsound_logerr2 (hr, typ, "Could not get " NAME " format\n"); + hr = glue(IFACE, _GetCaps)(ds->FIELD, &bc); + if (FAILED(hr)) { + dsound_logerr2(hr, typ, "Could not get " NAME " caps"); goto fail0; } @@ -245,23 +244,17 @@ static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as, } ds->first_time = true; - obt_as.endianness = 0; + obt_as.big_endian = false; audio_pcm_init_info (&hw->info, &obt_as); if (bc.dwBufferBytes % hw->info.bytes_per_frame) { - dolog ( - "GetCaps returned misaligned buffer size %ld, alignment %d\n", - bc.dwBufferBytes, hw->info.bytes_per_frame - ); + warn_report("dsound: GetCaps returned misaligned buffer size %ld, " + "alignment %d", bc.dwBufferBytes, hw->info.bytes_per_frame); } hw->size_emul = bc.dwBufferBytes; hw->samples = bc.dwBufferBytes / hw->info.bytes_per_frame; - ds->s = s; -#ifdef DEBUG_DSOUND - dolog ("caps %ld, desc %ld\n", - bc.dwBufferBytes, bd.dwBufferBytes); -#endif + trace_dsound_buffer_bytes(bc.dwBufferBytes, bd.dwBufferBytes); return 0; fail0: diff --git a/audio/dsoundaudio.c b/audio/dsoundaudio.c index 7a03d1dad861..338ee49c2feb 100644 --- a/audio/dsoundaudio.c +++ b/audio/dsoundaudio.c @@ -28,40 +28,43 @@ #include "qemu/osdep.h" #include "qemu/audio.h" - -#define AUDIO_CAP "dsound" +#include "qemu/error-report.h" #include "audio_int.h" #include "qemu/module.h" #include "qapi/error.h" +#include "qom/object.h" #include #include #include #include +#include "trace.h" #include "audio_win_int.h" -/* #define DEBUG_DSOUND */ +#define TYPE_AUDIO_DSOUND "audio-dsound" +OBJECT_DECLARE_SIMPLE_TYPE(AudioDsound, AUDIO_DSOUND) + +static AudioBackendClass *audio_dsound_parent_class; + +struct AudioDsound { + AudioMixengBackend parent_obj; -typedef struct { LPDIRECTSOUND dsound; LPDIRECTSOUNDCAPTURE dsound_capture; struct audsettings settings; - Audiodev *dev; -} dsound; +}; typedef struct { HWVoiceOut hw; LPDIRECTSOUNDBUFFER dsound_buffer; bool first_time; - dsound *s; } DSoundVoiceOut; typedef struct { HWVoiceIn hw; LPDIRECTSOUNDCAPTUREBUFFER dsound_capture_buffer; bool first_time; - dsound *s; } DSoundVoiceIn; static const char *dserror(HRESULT hr) @@ -208,65 +211,46 @@ static void dsound_log_hresult(HRESULT hr) const char *str = dserror(hr); if (str) { - AUD_log (AUDIO_CAP, "Reason: %s\n", str); + error_printf(" Reason: %s", str); } else { - AUD_log (AUDIO_CAP, "Reason: Unknown (HRESULT: 0x%lx)\n", hr); + error_printf(" Reason: Unknown (HRESULT: 0x%lx)", hr); } } -static void G_GNUC_PRINTF (2, 3) dsound_logerr ( - HRESULT hr, - const char *fmt, - ... - ) +static void G_GNUC_PRINTF(2, 3) dsound_logerr(HRESULT hr, const char *fmt, ...) { va_list ap; - va_start (ap, fmt); - AUD_vlog (AUDIO_CAP, fmt, ap); - va_end (ap); - - dsound_log_hresult (hr); + error_printf("dsound: "); + va_start(ap, fmt); + error_vprintf(fmt, ap); + va_end(ap); + dsound_log_hresult(hr); + error_printf("\n"); } -static void G_GNUC_PRINTF (3, 4) dsound_logerr2 ( - HRESULT hr, - const char *typ, - const char *fmt, - ... - ) +static void G_GNUC_PRINTF(3, 4) dsound_logerr2(HRESULT hr, const char *typ, + const char *fmt, ...) { va_list ap; - AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ); - va_start (ap, fmt); - AUD_vlog (AUDIO_CAP, fmt, ap); - va_end (ap); - - dsound_log_hresult (hr); + error_printf("dsound: Could not initialize %s: ", typ); + va_start(ap, fmt); + error_vprintf(fmt, ap); + va_end(ap); + dsound_log_hresult(hr); + error_printf("\n"); } -#ifdef DEBUG_DSOUND -static void print_wave_format (WAVEFORMATEX *wfx) -{ - dolog ("tag = %d\n", wfx->wFormatTag); - dolog ("nChannels = %d\n", wfx->nChannels); - dolog ("nSamplesPerSec = %ld\n", wfx->nSamplesPerSec); - dolog ("nAvgBytesPerSec = %ld\n", wfx->nAvgBytesPerSec); - dolog ("nBlockAlign = %d\n", wfx->nBlockAlign); - dolog ("wBitsPerSample = %d\n", wfx->wBitsPerSample); - dolog ("cbSize = %d\n", wfx->cbSize); -} -#endif -static int dsound_restore_out (LPDIRECTSOUNDBUFFER dsb, dsound *s) +static int dsound_restore_out (LPDIRECTSOUNDBUFFER dsb, AudioDsound *s) { HRESULT hr; hr = IDirectSoundBuffer_Restore (dsb); if (hr != DS_OK) { - dsound_logerr (hr, "Could not restore playback buffer\n"); + dsound_logerr(hr, "Could not restore playback buffer"); return -1; } return 0; @@ -278,13 +262,13 @@ static int dsound_restore_out (LPDIRECTSOUNDBUFFER dsb, dsound *s) #undef DSBTYPE_IN static int dsound_get_status_out (LPDIRECTSOUNDBUFFER dsb, DWORD *statusp, - dsound *s) + AudioDsound *s) { HRESULT hr; hr = IDirectSoundBuffer_GetStatus (dsb, statusp); if (FAILED (hr)) { - dsound_logerr (hr, "Could not get playback buffer status\n"); + dsound_logerr(hr, "Could not get playback buffer status"); return -1; } @@ -303,7 +287,7 @@ static int dsound_get_status_in (LPDIRECTSOUNDCAPTUREBUFFER dscb, hr = IDirectSoundCaptureBuffer_GetStatus (dscb, statusp); if (FAILED (hr)) { - dsound_logerr (hr, "Could not get capture buffer status\n"); + dsound_logerr(hr, "Could not get capture buffer status"); return -1; } @@ -311,7 +295,7 @@ static int dsound_get_status_in (LPDIRECTSOUNDCAPTUREBUFFER dscb, } static void dsound_clear_sample (HWVoiceOut *hw, LPDIRECTSOUNDBUFFER dsb, - dsound *s) + AudioDsound *s) { int err; LPVOID p1, p2; @@ -334,11 +318,7 @@ static void dsound_clear_sample (HWVoiceOut *hw, LPDIRECTSOUNDBUFFER dsb, len1 = blen1 / hw->info.bytes_per_frame; len2 = blen2 / hw->info.bytes_per_frame; -#ifdef DEBUG_DSOUND - dolog ("clear %p,%ld,%ld %p,%ld,%ld\n", - p1, blen1, len1, - p2, blen2, len2); -#endif + trace_dsound_clear_sample(p1, blen1, len1, p2, blen2, len2); if (p1 && len1) { audio_pcm_info_clear_buf (&hw->info, p1, len1); @@ -355,45 +335,45 @@ static void dsound_enable_out(HWVoiceOut *hw, bool enable) { HRESULT hr; DWORD status; + AudioDsound *s = AUDIO_DSOUND(hw->s); DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; LPDIRECTSOUNDBUFFER dsb = ds->dsound_buffer; - dsound *s = ds->s; if (!dsb) { - dolog ("Attempt to control voice without a buffer\n"); + error_report("dsound: Attempt to control voice without a buffer"); return; } if (enable) { - if (dsound_get_status_out (dsb, &status, s)) { + if (dsound_get_status_out(dsb, &status, s)) { return; } if (status & DSBSTATUS_PLAYING) { - dolog ("warning: Voice is already playing\n"); + warn_report("dsound: Voice is already playing"); return; } - dsound_clear_sample (hw, dsb, s); + dsound_clear_sample(hw, dsb, s); - hr = IDirectSoundBuffer_Play (dsb, 0, 0, DSBPLAY_LOOPING); - if (FAILED (hr)) { - dsound_logerr (hr, "Could not start playing buffer\n"); + hr = IDirectSoundBuffer_Play(dsb, 0, 0, DSBPLAY_LOOPING); + if (FAILED(hr)) { + dsound_logerr(hr, "Could not start playing buffer"); return; } } else { - if (dsound_get_status_out (dsb, &status, s)) { + if (dsound_get_status_out(dsb, &status, s)) { return; } if (status & DSBSTATUS_PLAYING) { - hr = IDirectSoundBuffer_Stop (dsb); - if (FAILED (hr)) { - dsound_logerr (hr, "Could not stop playing buffer\n"); + hr = IDirectSoundBuffer_Stop(dsb); + if (FAILED(hr)) { + dsound_logerr(hr, "Could not stop playing buffer"); return; } } else { - dolog ("warning: Voice is not playing\n"); + warn_report("dsound: Voice is not playing"); } } } @@ -408,7 +388,7 @@ static size_t dsound_buffer_get_free(HWVoiceOut *hw) hr = IDirectSoundBuffer_GetCurrentPosition( dsb, &ppos, ds->first_time ? &wpos : NULL); if (FAILED(hr)) { - dsound_logerr(hr, "Could not get playback buffer position\n"); + dsound_logerr(hr, "Could not get playback buffer position"); return 0; } @@ -433,9 +413,9 @@ static void *dsound_get_buffer_out(HWVoiceOut *hw, size_t *size) assert(req_size > 0); err = dsound_lock_out(dsb, &hw->info, hw->pos_emul, req_size, &ret, NULL, - &act_size, NULL, false, ds->s); + &act_size, NULL, false, AUDIO_DSOUND(hw->s)); if (err) { - dolog("Failed to lock buffer\n"); + error_report("dsound: Failed to lock buffer"); *size = 0; return NULL; } @@ -451,7 +431,7 @@ static size_t dsound_put_buffer_out(HWVoiceOut *hw, void *buf, size_t len) int err = dsound_unlock_out(dsb, buf, NULL, len, 0); if (err) { - dolog("Failed to unlock buffer!!\n"); + error_report("dsound: Failed to unlock buffer"); return 0; } hw->pos_emul = (hw->pos_emul + len) % hw->size_emul; @@ -467,40 +447,40 @@ static void dsound_enable_in(HWVoiceIn *hw, bool enable) LPDIRECTSOUNDCAPTUREBUFFER dscb = ds->dsound_capture_buffer; if (!dscb) { - dolog ("Attempt to control capture voice without a buffer\n"); + error_report("dsound: Attempt to control capture voice without a buffer"); return; } if (enable) { - if (dsound_get_status_in (dscb, &status)) { + if (dsound_get_status_in(dscb, &status)) { return; } if (status & DSCBSTATUS_CAPTURING) { - dolog ("warning: Voice is already capturing\n"); + warn_report("dsound: Voice is already capturing"); return; } /* clear ?? */ - hr = IDirectSoundCaptureBuffer_Start (dscb, DSCBSTART_LOOPING); - if (FAILED (hr)) { - dsound_logerr (hr, "Could not start capturing\n"); + hr = IDirectSoundCaptureBuffer_Start(dscb, DSCBSTART_LOOPING); + if (FAILED(hr)) { + dsound_logerr(hr, "Could not start capturing"); return; } } else { - if (dsound_get_status_in (dscb, &status)) { + if (dsound_get_status_in(dscb, &status)) { return; } if (status & DSCBSTATUS_CAPTURING) { - hr = IDirectSoundCaptureBuffer_Stop (dscb); - if (FAILED (hr)) { - dsound_logerr (hr, "Could not stop capturing\n"); + hr = IDirectSoundCaptureBuffer_Stop(dscb); + if (FAILED(hr)) { + dsound_logerr(hr, "Could not stop capturing"); return; } } else { - dolog ("warning: Voice is not capturing\n"); + warn_report("dsound: Voice is not capturing"); } } } @@ -517,7 +497,7 @@ static void *dsound_get_buffer_in(HWVoiceIn *hw, size_t *size) hr = IDirectSoundCaptureBuffer_GetCurrentPosition(dscb, NULL, &rpos); if (FAILED(hr)) { - dsound_logerr(hr, "Could not get capture buffer position\n"); + dsound_logerr(hr, "Could not get capture buffer position"); *size = 0; return NULL; } @@ -536,9 +516,9 @@ static void *dsound_get_buffer_in(HWVoiceIn *hw, size_t *size) } err = dsound_lock_in(dscb, &hw->info, hw->pos_emul, req_size, &ret, NULL, - &act_size, NULL, false, ds->s); + &act_size, NULL, false, AUDIO_DSOUND(hw->s)); if (err) { - dolog("Failed to lock buffer\n"); + error_report("dsound: Failed to lock buffer"); *size = 0; return NULL; } @@ -554,50 +534,51 @@ static void dsound_put_buffer_in(HWVoiceIn *hw, void *buf, size_t len) int err = dsound_unlock_in(dscb, buf, NULL, len, 0); if (err) { - dolog("Failed to unlock buffer!!\n"); + error_report("dsound: Failed to unlock buffer"); return; } hw->pos_emul = (hw->pos_emul + len) % hw->size_emul; } -static void dsound_audio_fini (void *opaque) +static void audio_dsound_finalize(Object *obj) { + AudioDsound *s = AUDIO_DSOUND(obj); HRESULT hr; - dsound *s = opaque; if (!s->dsound) { - g_free(s); return; } hr = IDirectSound_Release (s->dsound); if (FAILED (hr)) { - dsound_logerr (hr, "Could not release DirectSound\n"); + dsound_logerr(hr, "Could not release DirectSound"); } s->dsound = NULL; if (!s->dsound_capture) { - g_free(s); return; } hr = IDirectSoundCapture_Release (s->dsound_capture); if (FAILED (hr)) { - dsound_logerr (hr, "Could not release DirectSoundCapture\n"); + dsound_logerr(hr, "Could not release DirectSoundCapture"); } s->dsound_capture = NULL; - - g_free(s); } -static void *dsound_audio_init(Audiodev *dev, Error **errp) +static bool +audio_dsound_realize(AudioBackend *abe, Audiodev *dev, Error **errp) { + AudioDsound *s = AUDIO_DSOUND(abe); HRESULT hr; - dsound *s = g_new0(dsound, 1); AudiodevDsoundOptions *dso; assert(dev->driver == AUDIODEV_DRIVER_DSOUND); - s->dev = dev; + + if (!audio_dsound_parent_class->realize(abe, dev, errp)) { + return false; + } + dso = &dev->u.dsound; if (!dso->has_latency) { @@ -608,8 +589,7 @@ static void *dsound_audio_init(Audiodev *dev, Error **errp) hr = CoInitialize (NULL); if (FAILED (hr)) { dserror_set(errp, hr, "Could not initialize COM"); - dsound_audio_fini(s); - return NULL; + return false; } hr = CoCreateInstance ( @@ -621,15 +601,13 @@ static void *dsound_audio_init(Audiodev *dev, Error **errp) ); if (FAILED (hr)) { dserror_set(errp, hr, "Could not create DirectSound instance"); - dsound_audio_fini(s); - return NULL; + return false; } hr = IDirectSound_Initialize (s->dsound, NULL); if (FAILED (hr)) { dserror_set(errp, hr, "Could not initialize DirectSound"); - dsound_audio_fini(s); - return NULL; + return false; } hr = CoCreateInstance ( @@ -641,15 +619,13 @@ static void *dsound_audio_init(Audiodev *dev, Error **errp) ); if (FAILED (hr)) { dserror_set(errp, hr, "Could not create DirectSoundCapture instance"); - dsound_audio_fini(s); - return NULL; + return false; } hr = IDirectSoundCapture_Initialize (s->dsound_capture, NULL); if (FAILED(hr)) { dserror_set(errp, hr, "Could not initialize DirectSoundCapture"); - dsound_audio_fini(s); - return NULL; + return false; } hr = IDirectSound_SetCooperativeLevel ( @@ -659,43 +635,50 @@ static void *dsound_audio_init(Audiodev *dev, Error **errp) ); if (FAILED(hr)) { dserror_set(errp, hr, "Could not set cooperative level"); - dsound_audio_fini(s); - return NULL; + return false; } - return s; + return true; } -static struct audio_pcm_ops dsound_pcm_ops = { - .init_out = dsound_init_out, - .fini_out = dsound_fini_out, - .write = audio_generic_write, - .buffer_get_free = dsound_buffer_get_free, - .get_buffer_out = dsound_get_buffer_out, - .put_buffer_out = dsound_put_buffer_out, - .enable_out = dsound_enable_out, - - .init_in = dsound_init_in, - .fini_in = dsound_fini_in, - .read = audio_generic_read, - .get_buffer_in = dsound_get_buffer_in, - .put_buffer_in = dsound_put_buffer_in, - .enable_in = dsound_enable_in, -}; +static void audio_dsound_class_init(ObjectClass *klass, const void *data) +{ + AudioBackendClass *b = AUDIO_BACKEND_CLASS(klass); + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_CLASS(klass); + + audio_dsound_parent_class = AUDIO_BACKEND_CLASS(object_class_get_parent(klass)); + + b->realize = audio_dsound_realize; + k->max_voices_out = INT_MAX; + k->max_voices_in = 1; + k->voice_size_out = sizeof(DSoundVoiceOut); + k->voice_size_in = sizeof(DSoundVoiceIn); + + k->init_out = dsound_init_out; + k->fini_out = dsound_fini_out; + k->write = audio_generic_write; + k->buffer_get_free = dsound_buffer_get_free; + k->get_buffer_out = dsound_get_buffer_out; + k->put_buffer_out = dsound_put_buffer_out; + k->enable_out = dsound_enable_out; + + k->init_in = dsound_init_in; + k->fini_in = dsound_fini_in; + k->read = audio_generic_read; + k->get_buffer_in = dsound_get_buffer_in; + k->put_buffer_in = dsound_put_buffer_in; + k->enable_in = dsound_enable_in; +} -static struct audio_driver dsound_audio_driver = { - .name = "dsound", - .init = dsound_audio_init, - .fini = dsound_audio_fini, - .pcm_ops = &dsound_pcm_ops, - .max_voices_out = INT_MAX, - .max_voices_in = 1, - .voice_size_out = sizeof (DSoundVoiceOut), - .voice_size_in = sizeof (DSoundVoiceIn) +static const TypeInfo audio_types[] = { + { + .name = TYPE_AUDIO_DSOUND, + .parent = TYPE_AUDIO_MIXENG_BACKEND, + .instance_size = sizeof(AudioDsound), + .class_init = audio_dsound_class_init, + .instance_finalize = audio_dsound_finalize, + }, }; -static void register_audio_dsound(void) -{ - audio_driver_register(&dsound_audio_driver); -} -type_init(register_audio_dsound); +DEFINE_TYPES(audio_types) +module_obj(TYPE_AUDIO_DSOUND); diff --git a/audio/jackaudio.c b/audio/jackaudio.c index 7a3fcaedbae7..589aecede9a2 100644 --- a/audio/jackaudio.c +++ b/audio/jackaudio.c @@ -26,14 +26,23 @@ #include "qemu/module.h" #include "qemu/atomic.h" #include "qemu/main-loop.h" +#include "qemu/error-report.h" #include "qemu/audio.h" - -#define AUDIO_CAP "jack" +#include "qom/object.h" #include "audio_int.h" +#include "trace.h" #include #include +#define TYPE_AUDIO_JACK "audio-jack" +OBJECT_DECLARE_SIMPLE_TYPE(AudioJack, AUDIO_JACK) + +struct AudioJack { + AudioMixengBackend parent_obj; +}; + + struct QJack; typedef enum QJackState { @@ -334,7 +343,7 @@ static void qjack_client_recover(QJackClient *c) /* if enabled then attempt to recover */ if (c->enabled) { - dolog("attempting to reconnect to server\n"); + trace_jack_client_recover(); qjack_client_init(c); } } @@ -390,10 +399,10 @@ static void qjack_client_connect_ports(QJackClient *c) } if (c->out) { - dolog("connect %s -> %s\n", p, ports[i]); + trace_jack_connect(p, ports[i]); jack_connect(c->client, p, ports[i]); } else { - dolog("connect %s -> %s\n", ports[i], p); + trace_jack_connect(ports[i], p); jack_connect(c->client, ports[i], p); } } @@ -432,9 +441,9 @@ static int qjack_client_init(QJackClient *c) c->opt->server_name); if (c->client == NULL) { - dolog("jack_client_open failed: status = 0x%2.0x\n", status); + error_report("jack: jack_client_open failed: status = 0x%2.0x", status); if (status & JackServerFailed) { - dolog("unable to connect to JACK server\n"); + error_report("jack: unable to connect to JACK server"); } return -1; } @@ -442,12 +451,11 @@ static int qjack_client_init(QJackClient *c) c->freq = jack_get_sample_rate(c->client); if (status & JackServerStarted) { - dolog("JACK server started\n"); + trace_jack_server_started(); } if (status & JackNameNotUnique) { - dolog("JACK unique name assigned %s\n", - jack_get_client_name(c->client)); + trace_jack_unique_name(jack_get_client_name(c->client)); } /* Allocate working buffer for process callback */ @@ -497,11 +505,10 @@ static int qjack_client_init(QJackClient *c) return 0; } -static int qjack_init_out(HWVoiceOut *hw, struct audsettings *as, - void *drv_opaque) +static int qjack_init_out(HWVoiceOut *hw, struct audsettings *as) { QJackOut *jo = (QJackOut *)hw; - Audiodev *dev = (Audiodev *)drv_opaque; + Audiodev *dev = hw->s->dev; jo->c.out = true; jo->c.enabled = false; @@ -524,21 +531,19 @@ static int qjack_init_out(HWVoiceOut *hw, struct audsettings *as, .freq = jo->c.freq, .nchannels = jo->c.nchannels, .fmt = AUDIO_FORMAT_F32, - .endianness = 0 + .big_endian = false }; audio_pcm_init_info(&hw->info, &os); - dolog("JACK output configured for %dHz (%d samples)\n", - jo->c.freq, jo->c.buffersize); + trace_jack_out_init(jo->c.freq, jo->c.buffersize); return 0; } -static int qjack_init_in(HWVoiceIn *hw, struct audsettings *as, - void *drv_opaque) +static int qjack_init_in(HWVoiceIn *hw, struct audsettings *as) { QJackIn *ji = (QJackIn *)hw; - Audiodev *dev = (Audiodev *)drv_opaque; + Audiodev *dev = hw->s->dev; ji->c.out = false; ji->c.enabled = false; @@ -561,12 +566,11 @@ static int qjack_init_in(HWVoiceIn *hw, struct audsettings *as, .freq = ji->c.freq, .nchannels = ji->c.nchannels, .fmt = AUDIO_FORMAT_F32, - .endianness = 0 + .big_endian = false }; audio_pcm_init_info(&hw->info, &is); - dolog("JACK input configured for %dHz (%d samples)\n", - ji->c.freq, ji->c.buffersize); + trace_jack_in_init(ji->c.freq, ji->c.buffersize); return 0; } @@ -629,76 +633,91 @@ static void qjack_enable_in(HWVoiceIn *hw, bool enable) ji->c.enabled = enable; } -#if !defined(WIN32) && defined(CONFIG_PTHREAD_SETNAME_NP_W_TID) +#if !defined(WIN32) +struct QJackThreadData { + void *(*function)(void *); + void *arg; +}; + +static void *qjack_thread_trampoline(void *targ) +{ + struct QJackThreadData *data = targ; + void *(*function)(void *) = data->function; + void *arg = data->arg; + + g_free(data); + qemu_thread_set_name("jack-client"); + + return function(arg); +} + static int qjack_thread_creator(jack_native_thread_t *thread, const pthread_attr_t *attr, void *(*function)(void *), void *arg) { - int ret = pthread_create(thread, attr, function, arg); + struct QJackThreadData *data = g_new0(struct QJackThreadData, 1); + data->function = function; + data->arg = arg; + int ret = pthread_create(thread, attr, qjack_thread_trampoline, data); if (ret != 0) { + g_free(data); return ret; } - /* set the name of the thread */ - pthread_setname_np(*thread, "jack-client"); - return ret; } #endif -static void *qjack_init(Audiodev *dev, Error **errp) -{ - assert(dev->driver == AUDIODEV_DRIVER_JACK); - return dev; -} - -static void qjack_fini(void *opaque) -{ -} - -static struct audio_pcm_ops jack_pcm_ops = { - .init_out = qjack_init_out, - .fini_out = qjack_fini_out, - .write = qjack_write, - .buffer_get_free = audio_generic_buffer_get_free, - .run_buffer_out = audio_generic_run_buffer_out, - .enable_out = qjack_enable_out, - - .init_in = qjack_init_in, - .fini_in = qjack_fini_in, - .read = qjack_read, - .run_buffer_in = audio_generic_run_buffer_in, - .enable_in = qjack_enable_in -}; - -static struct audio_driver jack_driver = { - .name = "jack", - .init = qjack_init, - .fini = qjack_fini, - .pcm_ops = &jack_pcm_ops, - .max_voices_out = INT_MAX, - .max_voices_in = INT_MAX, - .voice_size_out = sizeof(QJackOut), - .voice_size_in = sizeof(QJackIn) -}; - static void qjack_error(const char *msg) { - dolog("E: %s\n", msg); + error_report("jack: %s", msg); } static void qjack_info(const char *msg) { - dolog("I: %s\n", msg); + trace_jack_info(msg); } -static void register_audio_jack(void) +static void audio_jack_class_init(ObjectClass *klass, const void *data) +{ + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_CLASS(klass); + + k->max_voices_out = INT_MAX; + k->max_voices_in = INT_MAX; + k->voice_size_out = sizeof(QJackOut); + k->voice_size_in = sizeof(QJackIn); + + k->init_out = qjack_init_out; + k->fini_out = qjack_fini_out; + k->write = qjack_write; + k->buffer_get_free = audio_generic_buffer_get_free; + k->run_buffer_out = audio_generic_run_buffer_out; + k->enable_out = qjack_enable_out; + + k->init_in = qjack_init_in; + k->fini_in = qjack_fini_in; + k->read = qjack_read; + k->run_buffer_in = audio_generic_run_buffer_in; + k->enable_in = qjack_enable_in; +} + +static const TypeInfo audio_types[] = { + { + .name = TYPE_AUDIO_JACK, + .parent = TYPE_AUDIO_MIXENG_BACKEND, + .instance_size = sizeof(AudioJack), + .class_init = audio_jack_class_init, + }, +}; + +static void __attribute__((__constructor__)) audio_jack_init(void) { qemu_mutex_init(&qjack_shutdown_lock); - audio_driver_register(&jack_driver); -#if !defined(WIN32) && defined(CONFIG_PTHREAD_SETNAME_NP_W_TID) +#if !defined(WIN32) jack_set_thread_creator(qjack_thread_creator); #endif jack_set_error_function(qjack_error); jack_set_info_function(qjack_info); } -type_init(register_audio_jack); + +DEFINE_TYPES(audio_types) +module_obj(TYPE_AUDIO_JACK); diff --git a/audio/meson.build b/audio/meson.build index b2dca2c64092..0e33b6f9836e 100644 --- a/audio/meson.build +++ b/audio/meson.build @@ -1,5 +1,8 @@ -system_ss.add(files( +audio_ss = ss.source_set() +audio_ss.add(files( 'audio.c', + 'audio-be.c', + 'audio-mixeng-be.c', 'mixeng.c', 'noaudio.c', 'wavaudio.c', @@ -8,12 +11,11 @@ system_ss.add(files( # deprecated since v10.2, to be removed system_ss.add(files('audio-hmp-cmds.c', 'wavcapture.c')) -system_ss.add(when: coreaudio, if_true: files('coreaudio.m')) -system_ss.add(when: dsound, if_true: files('dsoundaudio.c', 'audio_win_int.c')) - audio_modules = {} foreach m : [ ['alsa', alsa, files('alsaaudio.c')], + ['coreaudio', coreaudio, files('coreaudio.m')], + ['dsound', dsound, files('dsoundaudio.c', 'audio_win_int.c')], ['oss', oss, files('ossaudio.c')], ['pa', pulse, files('paaudio.c')], ['sdl', sdl, files('sdlaudio.c')], @@ -32,7 +34,7 @@ endforeach if dbus_display module_ss = ss.source_set() module_ss.add(when: [gio, pixman], - if_true: [dbus_display1, files('dbusaudio.c')]) + if_true: [dbus_display1_h, files('dbusaudio.c')]) audio_modules += {'dbus': module_ss} endif diff --git a/audio/mixeng.c b/audio/mixeng.c index e63c76e021d2..a209737a9139 100644 --- a/audio/mixeng.c +++ b/audio/mixeng.c @@ -26,7 +26,6 @@ #include "qemu/bswap.h" #include "qemu/audio.h" -#define AUDIO_CAP "mixeng" #include "audio_int.h" #ifdef FLOAT_MIXENG #include "qemu/error-report.h" diff --git a/audio/noaudio.c b/audio/noaudio.c index 4ed9d2156c57..ea6a78c70558 100644 --- a/audio/noaudio.c +++ b/audio/noaudio.c @@ -25,10 +25,17 @@ #include "qemu/osdep.h" #include "qemu/module.h" #include "qemu/audio.h" +#include "qom/object.h" -#define AUDIO_CAP "noaudio" #include "audio_int.h" +#define TYPE_AUDIO_NONE "audio-none" +OBJECT_DECLARE_SIMPLE_TYPE(AudioNone, AUDIO_NONE) + +struct AudioNone { + AudioMixengBackend parent_obj; +}; + typedef struct NoVoiceOut { HWVoiceOut hw; RateCtl rate; @@ -45,7 +52,7 @@ static size_t no_write(HWVoiceOut *hw, void *buf, size_t len) return audio_rate_get_bytes(&no->rate, &hw->info, len); } -static int no_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque) +static int no_init_out(HWVoiceOut *hw, struct audsettings *as) { NoVoiceOut *no = (NoVoiceOut *) hw; @@ -69,7 +76,7 @@ static void no_enable_out(HWVoiceOut *hw, bool enable) } } -static int no_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) +static int no_init_in(HWVoiceIn *hw, struct audsettings *as) { NoVoiceIn *no = (NoVoiceIn *) hw; @@ -102,44 +109,37 @@ static void no_enable_in(HWVoiceIn *hw, bool enable) } } -static void *no_audio_init(Audiodev *dev, Error **errp) -{ - return &no_audio_init; -} - -static void no_audio_fini (void *opaque) +static void audio_none_class_init(ObjectClass *klass, const void *data) { - (void) opaque; + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_CLASS(klass); + + k->max_voices_out = INT_MAX; + k->max_voices_in = INT_MAX; + k->voice_size_out = sizeof(NoVoiceOut); + k->voice_size_in = sizeof(NoVoiceIn); + + k->init_out = no_init_out; + k->fini_out = no_fini_out; + k->write = no_write; + k->buffer_get_free = audio_generic_buffer_get_free; + k->run_buffer_out = audio_generic_run_buffer_out; + k->enable_out = no_enable_out; + + k->init_in = no_init_in; + k->fini_in = no_fini_in; + k->read = no_read; + k->run_buffer_in = audio_generic_run_buffer_in; + k->enable_in = no_enable_in; } -static struct audio_pcm_ops no_pcm_ops = { - .init_out = no_init_out, - .fini_out = no_fini_out, - .write = no_write, - .buffer_get_free = audio_generic_buffer_get_free, - .run_buffer_out = audio_generic_run_buffer_out, - .enable_out = no_enable_out, - - .init_in = no_init_in, - .fini_in = no_fini_in, - .read = no_read, - .run_buffer_in = audio_generic_run_buffer_in, - .enable_in = no_enable_in -}; - -static struct audio_driver no_audio_driver = { - .name = "none", - .init = no_audio_init, - .fini = no_audio_fini, - .pcm_ops = &no_pcm_ops, - .max_voices_out = INT_MAX, - .max_voices_in = INT_MAX, - .voice_size_out = sizeof (NoVoiceOut), - .voice_size_in = sizeof (NoVoiceIn) +static const TypeInfo audio_types[] = { + { + .name = TYPE_AUDIO_NONE, + .parent = TYPE_AUDIO_MIXENG_BACKEND, + .instance_size = sizeof(AudioNone), + .class_init = audio_none_class_init, + }, }; -static void register_audio_none(void) -{ - audio_driver_register(&no_audio_driver); -} -type_init(register_audio_none); +DEFINE_TYPES(audio_types) +module_obj(TYPE_AUDIO_NONE); diff --git a/audio/ossaudio.c b/audio/ossaudio.c index c6cad47a015e..63c6e0a291f1 100644 --- a/audio/ossaudio.c +++ b/audio/ossaudio.c @@ -23,6 +23,7 @@ */ #include "qemu/osdep.h" +#include "qapi-types-audio.h" #include #include #include "qemu/main-loop.h" @@ -30,10 +31,20 @@ #include "qemu/host-utils.h" #include "qapi/error.h" #include "qemu/audio.h" +#include "qemu/error-report.h" +#include "qom/object.h" +#include "audio_int.h" #include "trace.h" -#define AUDIO_CAP "oss" -#include "audio_int.h" +#define TYPE_AUDIO_OSS "audio-oss" +OBJECT_DECLARE_SIMPLE_TYPE(AudioOss, AUDIO_OSS) + +static AudioBackendClass *audio_oss_parent_class; + +struct AudioOss { + AudioMixengBackend parent_obj; +}; + #if defined OSS_GETVERSION && defined SNDCTL_DSP_POLICY #define USE_DSP_POLICY @@ -64,56 +75,53 @@ struct oss_params { int fragsize; }; -static void G_GNUC_PRINTF (2, 3) oss_logerr (int err, const char *fmt, ...) +static void G_GNUC_PRINTF(2, 3) oss_logerr(int err, const char *fmt, ...) { va_list ap; - va_start (ap, fmt); - AUD_vlog (AUDIO_CAP, fmt, ap); - va_end (ap); + error_printf("oss: "); + va_start(ap, fmt); + error_vprintf(fmt, ap); + va_end(ap); - AUD_log (AUDIO_CAP, "Reason: %s\n", strerror (err)); + error_printf(" Reason: %s\n", strerror(err)); } -static void G_GNUC_PRINTF (3, 4) oss_logerr2 ( - int err, - const char *typ, - const char *fmt, - ... - ) +static void G_GNUC_PRINTF(3, 4) oss_logerr2(int err, const char *typ, + const char *fmt, ...) { va_list ap; - AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ); + error_printf("oss: Could not initialize %s: ", typ); - va_start (ap, fmt); - AUD_vlog (AUDIO_CAP, fmt, ap); - va_end (ap); + va_start(ap, fmt); + error_vprintf(fmt, ap); + va_end(ap); - AUD_log (AUDIO_CAP, "Reason: %s\n", strerror (err)); + error_printf(" Reason: %s\n", strerror(err)); } -static void oss_anal_close (int *fdp) +static void oss_anal_close(int *fdp) { int err; - qemu_set_fd_handler (*fdp, NULL, NULL, NULL); - err = close (*fdp); + qemu_set_fd_handler(*fdp, NULL, NULL, NULL); + err = close(*fdp); if (err) { - oss_logerr (errno, "Failed to close file(fd=%d)\n", *fdp); + oss_logerr(errno, "Failed to close file(fd=%d)", *fdp); } *fdp = -1; } static void oss_helper_poll_out (void *opaque) { - AudioBackend *s = opaque; + AudioMixengBackend *s = opaque; audio_run(s, "oss_poll_out"); } static void oss_helper_poll_in (void *opaque) { - AudioBackend *s = opaque; + AudioMixengBackend *s = opaque; audio_run(s, "oss_poll_in"); } @@ -147,69 +155,52 @@ static int aud_to_ossfmt(AudioFormat fmt, bool big_endian) return big_endian ? AFMT_U16_BE : AFMT_U16_LE; default: - dolog ("Internal logic error: Bad audio format %d\n", fmt); -#ifdef DEBUG_AUDIO - abort (); -#endif + error_report("oss: Internal logic error: Bad audio format %d", fmt); return AFMT_U8; } } -static int oss_to_audfmt (int ossfmt, AudioFormat *fmt, int *endianness) +static int oss_to_audfmt (int ossfmt, AudioFormat *fmt, bool *big_endian) { switch (ossfmt) { case AFMT_S8: - *endianness = 0; + *big_endian = false; *fmt = AUDIO_FORMAT_S8; break; case AFMT_U8: - *endianness = 0; + *big_endian = false; *fmt = AUDIO_FORMAT_U8; break; case AFMT_S16_LE: - *endianness = 0; + *big_endian = false; *fmt = AUDIO_FORMAT_S16; break; case AFMT_U16_LE: - *endianness = 0; + *big_endian = false; *fmt = AUDIO_FORMAT_U16; break; case AFMT_S16_BE: - *endianness = 1; + *big_endian = true; *fmt = AUDIO_FORMAT_S16; break; case AFMT_U16_BE: - *endianness = 1; + *big_endian = true; *fmt = AUDIO_FORMAT_U16; break; default: - dolog ("Unrecognized audio format %d\n", ossfmt); + error_report("oss: Unrecognized audio format %d", ossfmt); return -1; } return 0; } -#if defined DEBUG_MISMATCHES || defined DEBUG -static void oss_dump_info (struct oss_params *req, struct oss_params *obt) -{ - dolog ("parameter | requested value | obtained value\n"); - dolog ("format | %10d | %10d\n", req->fmt, obt->fmt); - dolog ("channels | %10d | %10d\n", - req->nchannels, obt->nchannels); - dolog ("frequency | %10d | %10d\n", req->freq, obt->freq); - dolog ("nfrags | %10d | %10d\n", req->nfrags, obt->nfrags); - dolog ("fragsize | %10d | %10d\n", - req->fragsize, obt->fragsize); -} -#endif - #ifdef USE_DSP_POLICY static int oss_get_version (int fd, int *version, const char *typ) { @@ -228,7 +219,7 @@ static int oss_get_version (int fd, int *version, const char *typ) return 0; } #endif - oss_logerr2 (errno, typ, "Failed to get OSS version\n"); + oss_logerr2(errno, typ, "Failed to get OSS version"); return -1; } return 0; @@ -257,7 +248,7 @@ static int oss_open(int in, struct oss_params *req, audsettings *as, fd = open (dspname, oflags | O_NONBLOCK); if (-1 == fd) { - oss_logerr2 (errno, typ, "Failed to open `%s'\n", dspname); + oss_logerr2(errno, typ, "Failed to open '%s'", dspname); return -1; } @@ -269,23 +260,23 @@ static int oss_open(int in, struct oss_params *req, audsettings *as, qapi_AudiodevOssPerDirectionOptions_base(opdo), as, 23220); if (ioctl (fd, SNDCTL_DSP_SAMPLESIZE, &fmt)) { - oss_logerr2 (errno, typ, "Failed to set sample size %d\n", req->fmt); + oss_logerr2(errno, typ, "Failed to set sample size %d", req->fmt); goto err; } if (ioctl (fd, SNDCTL_DSP_CHANNELS, &nchannels)) { - oss_logerr2 (errno, typ, "Failed to set number of channels %d\n", - req->nchannels); + oss_logerr2(errno, typ, "Failed to set number of channels %d", + req->nchannels); goto err; } if (ioctl (fd, SNDCTL_DSP_SPEED, &freq)) { - oss_logerr2 (errno, typ, "Failed to set frequency %d\n", req->freq); + oss_logerr2(errno, typ, "Failed to set frequency %d", req->freq); goto err; } if (ioctl (fd, SNDCTL_DSP_NONBLOCK, NULL)) { - oss_logerr2 (errno, typ, "Failed to set non-blocking mode\n"); + oss_logerr2(errno, typ, "Failed to set non-blocking mode"); goto err; } @@ -299,9 +290,9 @@ static int oss_open(int in, struct oss_params *req, audsettings *as, if (version >= 0x040000) { int policy2 = policy; if (ioctl(fd, SNDCTL_DSP_POLICY, &policy2)) { - oss_logerr2 (errno, typ, - "Failed to set timing policy to %d\n", - policy); + oss_logerr2(errno, typ, + "Failed to set timing policy to %d", + policy); goto err; } setfragment = 0; @@ -313,20 +304,20 @@ static int oss_open(int in, struct oss_params *req, audsettings *as, if (setfragment) { int mmmmssss = (req->nfrags << 16) | ctz32 (req->fragsize); if (ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &mmmmssss)) { - oss_logerr2 (errno, typ, "Failed to set buffer length (%d, %d)\n", - req->nfrags, req->fragsize); + oss_logerr2(errno, typ, "Failed to set buffer length (%d, %d)", + req->nfrags, req->fragsize); goto err; } } if (ioctl (fd, in ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &abinfo)) { - oss_logerr2 (errno, typ, "Failed to get buffer length\n"); + oss_logerr2(errno, typ, "Failed to get buffer length"); goto err; } if (!abinfo.fragstotal || !abinfo.fragsize) { - AUD_log (AUDIO_CAP, "Returned bogus buffer information(%d, %d) for %s\n", - abinfo.fragstotal, abinfo.fragsize, typ); + error_report("oss: Returned bogus buffer information(%d, %d) for %s", + abinfo.fragstotal, abinfo.fragsize, typ); goto err; } @@ -337,20 +328,13 @@ static int oss_open(int in, struct oss_params *req, audsettings *as, obt->fragsize = abinfo.fragsize; *pfd = fd; -#ifdef DEBUG_MISMATCHES - if ((req->fmt != obt->fmt) || - (req->nchannels != obt->nchannels) || - (req->freq != obt->freq) || - (req->fragsize != obt->fragsize) || - (req->nfrags != obt->nfrags)) { - dolog ("Audio parameters mismatch\n"); - oss_dump_info (req, obt); - } -#endif + trace_oss_out_params( + req->fmt, obt->fmt, + req->nchannels, obt->nchannels, + req->freq, obt->freq, + req->nfrags, obt->nfrags, + req->fragsize, obt->fragsize); -#ifdef DEBUG - oss_dump_info (req, obt); -#endif return 0; err: @@ -366,7 +350,7 @@ static size_t oss_get_available_bytes(OSSVoiceOut *oss) err = ioctl(oss->fd, SNDCTL_DSP_GETOPTR, &cntinfo); if (err < 0) { - oss_logerr(errno, "SNDCTL_DSP_GETOPTR failed\n"); + oss_logerr(errno, "SNDCTL_DSP_GETOPTR failed"); return 0; } @@ -447,8 +431,7 @@ static size_t oss_write(HWVoiceOut *hw, void *buf, size_t len) bytes_written = write(oss->fd, pcm, len); if (bytes_written < 0) { if (errno != EAGAIN) { - oss_logerr(errno, "failed to write %zu bytes\n", - len); + oss_logerr(errno, "failed to write %zu bytes", len); } return pos; } @@ -462,38 +445,37 @@ static size_t oss_write(HWVoiceOut *hw, void *buf, size_t len) return pos; } -static void oss_fini_out (HWVoiceOut *hw) +static void oss_fini_out(HWVoiceOut *hw) { int err; - OSSVoiceOut *oss = (OSSVoiceOut *) hw; + OSSVoiceOut *oss = (OSSVoiceOut *)hw; - ldebug ("oss_fini\n"); - oss_anal_close (&oss->fd); + trace_oss_fini_out(); + oss_anal_close(&oss->fd); if (oss->mmapped && hw->buf_emul) { err = munmap(hw->buf_emul, hw->size_emul); if (err) { - oss_logerr(errno, "Failed to unmap buffer %p, size %zu\n", + oss_logerr(errno, "Failed to unmap buffer %p, size %zu", hw->buf_emul, hw->size_emul); } hw->buf_emul = NULL; } } -static int oss_init_out(HWVoiceOut *hw, struct audsettings *as, - void *drv_opaque) +static int oss_init_out(HWVoiceOut *hw, struct audsettings *as) { OSSVoiceOut *oss = (OSSVoiceOut *) hw; struct oss_params req, obt; int err; int fd; struct audsettings obt_as; - Audiodev *dev = drv_opaque; + Audiodev *dev = hw->s->dev; AudiodevOssOptions *oopts = &dev->u.oss; oss->fd = -1; - req.fmt = aud_to_ossfmt (as->fmt, as->endianness); + req.fmt = aud_to_ossfmt (as->fmt, as->big_endian); req.freq = as->freq; req.nchannels = as->nchannels; @@ -501,7 +483,7 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as, return -1; } - err = oss_to_audfmt(obt.fmt, &obt_as.fmt, &obt_as.endianness); + err = oss_to_audfmt(obt.fmt, &obt_as.fmt, &obt_as.big_endian); if (err) { oss_anal_close (&fd); return -1; @@ -515,8 +497,8 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as, oss->fragsize = obt.fragsize; if (obt.nfrags * obt.fragsize % hw->info.bytes_per_frame) { - dolog ("warning: Misaligned DAC buffer, size %d, alignment %d\n", - obt.nfrags * obt.fragsize, hw->info.bytes_per_frame); + warn_report("oss: Misaligned DAC buffer, size %d, alignment %d", + obt.nfrags * obt.fragsize, hw->info.bytes_per_frame); } hw->samples = (obt.nfrags * obt.fragsize) / hw->info.bytes_per_frame; @@ -533,20 +515,16 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as, 0 ); if (hw->buf_emul == MAP_FAILED) { - oss_logerr(errno, "Failed to map %zu bytes of DAC\n", - hw->size_emul); + oss_logerr(errno, "Failed to map %zu bytes of DAC", hw->size_emul); hw->buf_emul = NULL; } else { int trig = 0; if (ioctl (fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { - oss_logerr (errno, "SNDCTL_DSP_SETTRIGGER 0 failed\n"); + oss_logerr(errno, "SNDCTL_DSP_SETTRIGGER 0 failed"); } else { trig = PCM_ENABLE_OUTPUT; - if (ioctl (fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { - oss_logerr ( - errno, - "SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed\n" - ); + if (ioctl(fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { + oss_logerr(errno, "SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed"); } else { oss->mmapped = 1; } @@ -555,7 +533,7 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as, if (!oss->mmapped) { err = munmap(hw->buf_emul, hw->size_emul); if (err) { - oss_logerr(errno, "Failed to unmap buffer %p size %zu\n", + oss_logerr(errno, "Failed to unmap buffer %p size %zu", hw->buf_emul, hw->size_emul); } hw->buf_emul = NULL; @@ -571,13 +549,14 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as, static void oss_enable_out(HWVoiceOut *hw, bool enable) { int trig; - OSSVoiceOut *oss = (OSSVoiceOut *) hw; + OSSVoiceOut *oss = (OSSVoiceOut *)hw; AudiodevOssPerDirectionOptions *opdo = oss->dev->u.oss.out; + trace_oss_enable_out(enable); + if (enable) { hw->poll_mode = opdo->try_poll; - ldebug("enabling voice\n"); if (hw->poll_mode) { oss_poll_out(hw); } @@ -590,12 +569,12 @@ static void oss_enable_out(HWVoiceOut *hw, bool enable) trig = PCM_ENABLE_OUTPUT; if (ioctl(oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { oss_logerr(errno, - "SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed\n"); + "SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed"); return; } } else { if (hw->poll_mode) { - qemu_set_fd_handler (oss->fd, NULL, NULL, NULL); + qemu_set_fd_handler(oss->fd, NULL, NULL, NULL); hw->poll_mode = 0; } @@ -603,34 +582,33 @@ static void oss_enable_out(HWVoiceOut *hw, bool enable) return; } - ldebug ("disabling voice\n"); trig = 0; - if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { - oss_logerr (errno, "SNDCTL_DSP_SETTRIGGER 0 failed\n"); + if (ioctl(oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { + oss_logerr(errno, "SNDCTL_DSP_SETTRIGGER 0 failed"); return; } } } -static int oss_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) +static int oss_init_in(HWVoiceIn *hw, struct audsettings *as) { OSSVoiceIn *oss = (OSSVoiceIn *) hw; struct oss_params req, obt; int err; int fd; struct audsettings obt_as; - Audiodev *dev = drv_opaque; + Audiodev *dev = hw->s->dev; oss->fd = -1; - req.fmt = aud_to_ossfmt (as->fmt, as->endianness); + req.fmt = aud_to_ossfmt (as->fmt, as->big_endian); req.freq = as->freq; req.nchannels = as->nchannels; if (oss_open(1, &req, as, &obt, &fd, dev)) { return -1; } - err = oss_to_audfmt(obt.fmt, &obt_as.fmt, &obt_as.endianness); + err = oss_to_audfmt(obt.fmt, &obt_as.fmt, &obt_as.big_endian); if (err) { oss_anal_close (&fd); return -1; @@ -643,9 +621,16 @@ static int oss_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) oss->nfrags = obt.nfrags; oss->fragsize = obt.fragsize; + trace_oss_in_params( + req.fmt, obt.fmt, + req.nchannels, obt.nchannels, + req.freq, obt.freq, + req.nfrags, obt.nfrags, + req.fragsize, obt.fragsize); + if (obt.nfrags * obt.fragsize % hw->info.bytes_per_frame) { - dolog ("warning: Misaligned ADC buffer, size %d, alignment %d\n", - obt.nfrags * obt.fragsize, hw->info.bytes_per_frame); + warn_report("oss: Misaligned ADC buffer, size %d, alignment %d", + obt.nfrags * obt.fragsize, hw->info.bytes_per_frame); } hw->samples = (obt.nfrags * obt.fragsize) / hw->info.bytes_per_frame; @@ -679,7 +664,7 @@ static size_t oss_read(HWVoiceIn *hw, void *buf, size_t len) case EAGAIN: break; default: - oss_logerr(errno, "Failed to read %zu bytes of audio (to %p)\n", + oss_logerr(errno, "Failed to read %zu bytes of audio (to %p)", len, dst); break; } @@ -695,9 +680,11 @@ static size_t oss_read(HWVoiceIn *hw, void *buf, size_t len) static void oss_enable_in(HWVoiceIn *hw, bool enable) { - OSSVoiceIn *oss = (OSSVoiceIn *) hw; + OSSVoiceIn *oss = (OSSVoiceIn *)hw; AudiodevOssPerDirectionOptions *opdo = oss->dev->u.oss.out; + trace_oss_enable_in(enable); + if (enable) { hw->poll_mode = opdo->try_poll; @@ -720,7 +707,8 @@ static void oss_init_per_direction(AudiodevOssPerDirectionOptions *opdo) } } -static void *oss_audio_init(Audiodev *dev, Error **errp) +static bool +audio_oss_realize(AudioBackend *abe, Audiodev *dev, Error **errp) { AudiodevOssOptions *oopts; assert(dev->driver == AUDIODEV_DRIVER_OSS); @@ -731,49 +719,55 @@ static void *oss_audio_init(Audiodev *dev, Error **errp) if (access(oopts->in->dev ?: "/dev/dsp", R_OK | W_OK) < 0) { error_setg_errno(errp, errno, "%s not accessible", oopts->in->dev ?: "/dev/dsp"); - return NULL; + qapi_free_Audiodev(dev); + return false; } if (access(oopts->out->dev ?: "/dev/dsp", R_OK | W_OK) < 0) { error_setg_errno(errp, errno, "%s not accessible", oopts->out->dev ?: "/dev/dsp"); - return NULL; + qapi_free_Audiodev(dev); + return false; } - return dev; + + return audio_oss_parent_class->realize(abe, dev, errp); } -static void oss_audio_fini (void *opaque) +static void audio_oss_class_init(ObjectClass *klass, const void *data) { + AudioBackendClass *b = AUDIO_BACKEND_CLASS(klass); + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_CLASS(klass); + + audio_oss_parent_class = AUDIO_BACKEND_CLASS(object_class_get_parent(klass)); + + b->realize = audio_oss_realize; + k->max_voices_out = INT_MAX; + k->max_voices_in = INT_MAX; + k->voice_size_out = sizeof(OSSVoiceOut); + k->voice_size_in = sizeof(OSSVoiceIn); + + k->init_out = oss_init_out; + k->fini_out = oss_fini_out; + k->write = oss_write; + k->buffer_get_free = oss_buffer_get_free; + k->run_buffer_out = oss_run_buffer_out; + k->get_buffer_out = oss_get_buffer_out; + k->put_buffer_out = oss_put_buffer_out; + k->enable_out = oss_enable_out; + + k->init_in = oss_init_in; + k->fini_in = oss_fini_in; + k->read = oss_read; + k->run_buffer_in = audio_generic_run_buffer_in; + k->enable_in = oss_enable_in; } -static struct audio_pcm_ops oss_pcm_ops = { - .init_out = oss_init_out, - .fini_out = oss_fini_out, - .write = oss_write, - .buffer_get_free = oss_buffer_get_free, - .run_buffer_out = oss_run_buffer_out, - .get_buffer_out = oss_get_buffer_out, - .put_buffer_out = oss_put_buffer_out, - .enable_out = oss_enable_out, - - .init_in = oss_init_in, - .fini_in = oss_fini_in, - .read = oss_read, - .run_buffer_in = audio_generic_run_buffer_in, - .enable_in = oss_enable_in +static const TypeInfo audio_types[] = { + { + .name = TYPE_AUDIO_OSS, + .parent = TYPE_AUDIO_MIXENG_BACKEND, + .instance_size = sizeof(AudioOss), + .class_init = audio_oss_class_init, + }, }; -static struct audio_driver oss_audio_driver = { - .name = "oss", - .init = oss_audio_init, - .fini = oss_audio_fini, - .pcm_ops = &oss_pcm_ops, - .max_voices_out = INT_MAX, - .max_voices_in = INT_MAX, - .voice_size_out = sizeof (OSSVoiceOut), - .voice_size_in = sizeof (OSSVoiceIn) -}; - -static void register_audio_oss(void) -{ - audio_driver_register(&oss_audio_driver); -} -type_init(register_audio_oss); +DEFINE_TYPES(audio_types) +module_obj(TYPE_AUDIO_OSS); diff --git a/audio/paaudio.c b/audio/paaudio.c index 0c06a3971954..24327ecbf453 100644 --- a/audio/paaudio.c +++ b/audio/paaudio.c @@ -3,12 +3,18 @@ #include "qemu/osdep.h" #include "qemu/module.h" #include "qemu/audio.h" +#include "qemu/error-report.h" #include "qapi/error.h" +#include "qom/object.h" +#include "audio_int.h" #include -#define AUDIO_CAP "pulseaudio" -#include "audio_int.h" +#define TYPE_AUDIO_PA "audio-pa" +OBJECT_DECLARE_SIMPLE_TYPE(AudioPa, AUDIO_PA) + +static AudioBackendClass *audio_pa_parent_class; + typedef struct PAConnection { char *server; @@ -19,18 +25,19 @@ typedef struct PAConnection { pa_context *context; } PAConnection; -static QTAILQ_HEAD(PAConnectionHead, PAConnection) pa_conns = - QTAILQ_HEAD_INITIALIZER(pa_conns); +struct AudioPa { + AudioMixengBackend parent_obj; -typedef struct { - Audiodev *dev; PAConnection *conn; -} paaudio; +}; + +static QTAILQ_HEAD(PAConnectionHead, PAConnection) pa_conns = + QTAILQ_HEAD_INITIALIZER(pa_conns); typedef struct { HWVoiceOut hw; pa_stream *stream; - paaudio *g; + AudioPa *g; } PAVoiceOut; typedef struct { @@ -38,41 +45,22 @@ typedef struct { pa_stream *stream; const void *read_data; size_t read_length; - paaudio *g; + AudioPa *g; } PAVoiceIn; static void qpa_conn_fini(PAConnection *c); -static void G_GNUC_PRINTF (2, 3) qpa_logerr (int err, const char *fmt, ...) +static void G_GNUC_PRINTF(2, 3) qpa_logerr(int err, const char *fmt, ...) { va_list ap; - va_start (ap, fmt); - AUD_vlog (AUDIO_CAP, fmt, ap); - va_end (ap); - - AUD_log (AUDIO_CAP, "Reason: %s\n", pa_strerror (err)); -} - -#ifndef PA_CONTEXT_IS_GOOD -static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) -{ - return - x == PA_CONTEXT_CONNECTING || - x == PA_CONTEXT_AUTHORIZING || - x == PA_CONTEXT_SETTING_NAME || - x == PA_CONTEXT_READY; -} -#endif + error_printf("pulseaudio: "); + va_start(ap, fmt); + error_vprintf(fmt, ap); + va_end(ap); -#ifndef PA_STREAM_IS_GOOD -static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) -{ - return - x == PA_STREAM_CREATING || - x == PA_STREAM_READY; + error_printf(" Reason: %s\n", pa_strerror(err)); } -#endif #define CHECK_SUCCESS_GOTO(c, expression, label, msg) \ do { \ @@ -105,12 +93,12 @@ static void *qpa_get_buffer_in(HWVoiceIn *hw, size_t *size) pa_threaded_mainloop_lock(c->mainloop); CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail, - "pa_threaded_mainloop_lock failed\n"); + "pa_threaded_mainloop_lock failed"); if (!p->read_length) { r = pa_stream_peek(p->stream, &p->read_data, &p->read_length); CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail, - "pa_stream_peek failed\n"); + "pa_stream_peek failed"); } *size = MIN(p->read_length, *size); @@ -133,7 +121,7 @@ static void qpa_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size) pa_threaded_mainloop_lock(c->mainloop); CHECK_DEAD_GOTO(c, p->stream, unlock, - "pa_threaded_mainloop_lock failed\n"); + "pa_threaded_mainloop_lock failed"); assert(buf == p->read_data && size <= p->read_length); @@ -142,7 +130,7 @@ static void qpa_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size) if (size && !p->read_length) { r = pa_stream_drop(p->stream); - CHECK_SUCCESS_GOTO(c, r == 0, unlock, "pa_stream_drop failed\n"); + CHECK_SUCCESS_GOTO(c, r == 0, unlock, "pa_stream_drop failed"); } unlock: @@ -158,7 +146,7 @@ static size_t qpa_read(HWVoiceIn *hw, void *data, size_t length) pa_threaded_mainloop_lock(c->mainloop); CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail, - "pa_threaded_mainloop_lock failed\n"); + "pa_threaded_mainloop_lock failed"); if (pa_stream_get_state(p->stream) != PA_STREAM_READY) { /* wait for stream to become ready */ goto unlock; @@ -171,7 +159,7 @@ static size_t qpa_read(HWVoiceIn *hw, void *data, size_t length) if (!p->read_length) { r = pa_stream_peek(p->stream, &p->read_data, &p->read_length); CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail, - "pa_stream_peek failed\n"); + "pa_stream_peek failed"); if (!p->read_length) { /* buffer is empty */ break; @@ -188,7 +176,7 @@ static size_t qpa_read(HWVoiceIn *hw, void *data, size_t length) if (!p->read_length) { r = pa_stream_drop(p->stream); CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail, - "pa_stream_drop failed\n"); + "pa_stream_drop failed"); } } @@ -210,7 +198,7 @@ static size_t qpa_buffer_get_free(HWVoiceOut *hw) pa_threaded_mainloop_lock(c->mainloop); CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail, - "pa_threaded_mainloop_lock failed\n"); + "pa_threaded_mainloop_lock failed"); if (pa_stream_get_state(p->stream) != PA_STREAM_READY) { /* wait for stream to become ready */ l = 0; @@ -219,7 +207,7 @@ static size_t qpa_buffer_get_free(HWVoiceOut *hw) l = pa_stream_writable_size(p->stream); CHECK_SUCCESS_GOTO(c, l != (size_t) -1, unlock_and_fail, - "pa_stream_writable_size failed\n"); + "pa_stream_writable_size failed"); unlock: pa_threaded_mainloop_unlock(c->mainloop); @@ -240,12 +228,12 @@ static void *qpa_get_buffer_out(HWVoiceOut *hw, size_t *size) pa_threaded_mainloop_lock(c->mainloop); CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail, - "pa_threaded_mainloop_lock failed\n"); + "pa_threaded_mainloop_lock failed"); *size = -1; r = pa_stream_begin_write(p->stream, &ret, size); CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail, - "pa_stream_begin_write failed\n"); + "pa_stream_begin_write failed"); pa_threaded_mainloop_unlock(c->mainloop); return ret; @@ -265,10 +253,10 @@ static size_t qpa_put_buffer_out(HWVoiceOut *hw, void *data, size_t length) pa_threaded_mainloop_lock(c->mainloop); CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail, - "pa_threaded_mainloop_lock failed\n"); + "pa_threaded_mainloop_lock failed"); r = pa_stream_write(p->stream, data, length, NULL, 0LL, PA_SEEK_RELATIVE); - CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail, "pa_stream_write failed\n"); + CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail, "pa_stream_write failed"); pa_threaded_mainloop_unlock(c->mainloop); return length; @@ -288,7 +276,7 @@ static size_t qpa_write(HWVoiceOut *hw, void *data, size_t length) pa_threaded_mainloop_lock(c->mainloop); CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail, - "pa_threaded_mainloop_lock failed\n"); + "pa_threaded_mainloop_lock failed"); if (pa_stream_get_state(p->stream) != PA_STREAM_READY) { /* wait for stream to become ready */ l = 0; @@ -298,14 +286,14 @@ static size_t qpa_write(HWVoiceOut *hw, void *data, size_t length) l = pa_stream_writable_size(p->stream); CHECK_SUCCESS_GOTO(c, l != (size_t) -1, unlock_and_fail, - "pa_stream_writable_size failed\n"); + "pa_stream_writable_size failed"); if (l > length) { l = length; } r = pa_stream_write(p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE); - CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail, "pa_stream_write failed\n"); + CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail, "pa_stream_write failed"); unlock: pa_threaded_mainloop_unlock(c->mainloop); @@ -337,43 +325,43 @@ static pa_sample_format_t audfmt_to_pa(AudioFormat afmt, bool big_endian) format = big_endian ? PA_SAMPLE_FLOAT32BE : PA_SAMPLE_FLOAT32LE; break; default: - dolog ("Internal logic error: Bad audio format %d\n", afmt); + error_report("pulseaudio: Internal logic error: Bad audio format %d", afmt); format = PA_SAMPLE_U8; break; } return format; } -static AudioFormat pa_to_audfmt (pa_sample_format_t fmt, int *endianness) +static AudioFormat pa_to_audfmt (pa_sample_format_t fmt, bool *big_endian) { switch (fmt) { case PA_SAMPLE_U8: return AUDIO_FORMAT_U8; case PA_SAMPLE_S16BE: - *endianness = 1; + *big_endian = true; return AUDIO_FORMAT_S16; case PA_SAMPLE_S16LE: - *endianness = 0; + *big_endian = false; return AUDIO_FORMAT_S16; case PA_SAMPLE_S32BE: - *endianness = 1; + *big_endian = true; return AUDIO_FORMAT_S32; case PA_SAMPLE_S32LE: - *endianness = 0; + *big_endian = false; return AUDIO_FORMAT_S32; case PA_SAMPLE_FLOAT32BE: - *endianness = 1; + *big_endian = true; return AUDIO_FORMAT_F32; case PA_SAMPLE_FLOAT32LE: - *endianness = 0; + *big_endian = false; return AUDIO_FORMAT_F32; default: - dolog ("Internal logic error: Bad pa_sample_format %d\n", fmt); + error_report("pulseaudio: Internal logic error: Bad pa_sample_format %d", fmt); return AUDIO_FORMAT_U8; } } -static void context_state_cb (pa_context *c, void *userdata) +static void context_state_cb(pa_context *c, void *userdata) { PAConnection *conn = userdata; @@ -465,7 +453,7 @@ static pa_stream *qpa_simple_new ( break; default: - dolog("Internal error: unsupported channel count %d\n", ss->channels); + error_report("pulseaudio: unsupported channel count %d", ss->channels); goto fail; } @@ -509,34 +497,35 @@ static pa_stream *qpa_simple_new ( return NULL; } -static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, - void *drv_opaque) +static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as) { + AudioMixengBackend *amb = hw->s; + AudioPa *apa = AUDIO_PA(amb); int error; pa_sample_spec ss; pa_buffer_attr ba; struct audsettings obt_as = *as; PAVoiceOut *pa = (PAVoiceOut *) hw; - paaudio *g = pa->g = drv_opaque; - AudiodevPaOptions *popts = &g->dev->u.pa; + AudiodevPaOptions *popts = &amb->dev->u.pa; AudiodevPaPerDirectionOptions *ppdo = popts->out; - PAConnection *c = g->conn; + PAConnection *c = apa->conn; - ss.format = audfmt_to_pa (as->fmt, as->endianness); + pa->g = apa; + ss.format = audfmt_to_pa (as->fmt, as->big_endian); ss.channels = as->nchannels; ss.rate = as->freq; ba.tlength = pa_usec_to_bytes(ppdo->latency, &ss); ba.minreq = pa_usec_to_bytes(MIN(ppdo->latency >> 2, - (g->dev->timer_period >> 2) * 3), &ss); + (amb->dev->timer_period >> 2) * 3), &ss); ba.maxlength = -1; ba.prebuf = -1; - obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); + obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.big_endian); pa->stream = qpa_simple_new ( c, - ppdo->stream_name ?: g->dev->id, + ppdo->stream_name ?: amb->dev->id, PA_STREAM_PLAYBACK, ppdo->name, &ss, @@ -544,7 +533,7 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, &error ); if (!pa->stream) { - qpa_logerr (error, "pa_simple_new for playback failed\n"); + qpa_logerr(error, "pa_simple_new for playback failed"); goto fail1; } @@ -559,33 +548,35 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, return -1; } -static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) +static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as) { + AudioMixengBackend *amb = hw->s; + AudioPa *apa = AUDIO_PA(amb); int error; pa_sample_spec ss; pa_buffer_attr ba; struct audsettings obt_as = *as; PAVoiceIn *pa = (PAVoiceIn *) hw; - paaudio *g = pa->g = drv_opaque; - AudiodevPaOptions *popts = &g->dev->u.pa; + AudiodevPaOptions *popts = &amb->dev->u.pa; AudiodevPaPerDirectionOptions *ppdo = popts->in; - PAConnection *c = g->conn; + PAConnection *c = apa->conn; - ss.format = audfmt_to_pa (as->fmt, as->endianness); + pa->g = apa; + ss.format = audfmt_to_pa (as->fmt, as->big_endian); ss.channels = as->nchannels; ss.rate = as->freq; - ba.fragsize = pa_usec_to_bytes((g->dev->timer_period >> 1) * 3, &ss); + ba.fragsize = pa_usec_to_bytes((amb->dev->timer_period >> 1) * 3, &ss); ba.maxlength = pa_usec_to_bytes( - MAX(ppdo->latency, g->dev->timer_period * 3), &ss); + MAX(ppdo->latency, amb->dev->timer_period * 3), &ss); ba.minreq = -1; ba.prebuf = -1; - obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); + obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.big_endian); pa->stream = qpa_simple_new ( c, - ppdo->stream_name ?: g->dev->id, + ppdo->stream_name ?: amb->dev->id, PA_STREAM_RECORD, ppdo->name, &ss, @@ -593,7 +584,7 @@ static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) &error ); if (!pa->stream) { - qpa_logerr (error, "pa_simple_new for capture failed\n"); + qpa_logerr(error, "pa_simple_new for capture failed"); goto fail1; } @@ -622,7 +613,7 @@ static void qpa_simple_disconnect(PAConnection *c, pa_stream *stream) err = pa_stream_disconnect(stream); if (err != 0) { - dolog("Failed to disconnect! err=%d\n", err); + error_report("pulseaudio: Failed to disconnect! err=%d", err); } pa_stream_unref(stream); } @@ -653,7 +644,7 @@ static void qpa_fini_in (HWVoiceIn *hw) int r = pa_stream_drop(pa->stream); if (r) { qpa_logerr(pa_context_errno(c->context), - "pa_stream_drop failed\n"); + "pa_stream_drop failed"); } pa->read_length = 0; } @@ -671,9 +662,7 @@ static void qpa_volume_out(HWVoiceOut *hw, Volume *vol) PAConnection *c = pa->g->conn; int i; -#ifdef PA_CHECK_VERSION /* macro is present in 0.9.16+ */ - pa_cvolume_init (&v); /* function is present in 0.9.13+ */ -#endif + pa_cvolume_init(&v); v.channels = vol->channels; for (i = 0; i < vol->channels; ++i) { @@ -687,7 +676,7 @@ static void qpa_volume_out(HWVoiceOut *hw, Volume *vol) &v, NULL, NULL); if (!op) { qpa_logerr(pa_context_errno(c->context), - "set_sink_input_volume() failed\n"); + "set_sink_input_volume() failed"); } else { pa_operation_unref(op); } @@ -697,7 +686,7 @@ static void qpa_volume_out(HWVoiceOut *hw, Volume *vol) vol->mute, NULL, NULL); if (!op) { qpa_logerr(pa_context_errno(c->context), - "set_sink_input_mute() failed\n"); + "set_sink_input_mute() failed"); } else { pa_operation_unref(op); } @@ -713,9 +702,7 @@ static void qpa_volume_in(HWVoiceIn *hw, Volume *vol) PAConnection *c = pa->g->conn; int i; -#ifdef PA_CHECK_VERSION - pa_cvolume_init (&v); -#endif + pa_cvolume_init(&v); v.channels = vol->channels; for (i = 0; i < vol->channels; ++i) { @@ -729,7 +716,7 @@ static void qpa_volume_in(HWVoiceIn *hw, Volume *vol) &v, NULL, NULL); if (!op) { qpa_logerr(pa_context_errno(c->context), - "set_source_output_volume() failed\n"); + "set_source_output_volume() failed"); } else { pa_operation_unref(op); } @@ -739,7 +726,7 @@ static void qpa_volume_in(HWVoiceIn *hw, Volume *vol) vol->mute, NULL, NULL); if (!op) { qpa_logerr(pa_context_errno(c->context), - "set_source_output_mute() failed\n"); + "set_source_output_mute() failed"); } else { pa_operation_unref(op); } @@ -777,7 +764,7 @@ static void *qpa_conn_init(const char *server) if (pa_context_connect(c->context, server, 0, NULL) < 0) { qpa_logerr(pa_context_errno(c->context), - "pa_context_connect() failed\n"); + "pa_context_connect() failed"); goto fail; } @@ -798,7 +785,7 @@ static void *qpa_conn_init(const char *server) if (!PA_CONTEXT_IS_GOOD(state)) { qpa_logerr(pa_context_errno(c->context), - "Wrong context state\n"); + "Wrong context state"); goto unlock_and_fail; } @@ -812,20 +799,24 @@ static void *qpa_conn_init(const char *server) unlock_and_fail: pa_threaded_mainloop_unlock(c->mainloop); fail: - AUD_log (AUDIO_CAP, "Failed to initialize PA context"); qpa_conn_fini(c); return NULL; } -static void *qpa_audio_init(Audiodev *dev, Error **errp) +static bool +audio_pa_realize(AudioBackend *abe, Audiodev *dev, Error **errp) { - paaudio *g; + AudioPa *apa = AUDIO_PA(abe); AudiodevPaOptions *popts = &dev->u.pa; const char *server; PAConnection *c; assert(dev->driver == AUDIODEV_DRIVER_PA); + if (!audio_pa_parent_class->realize(abe, dev, errp)) { + return false; + } + if (!popts->server) { char pidfile[64]; char *runtime; @@ -834,42 +825,38 @@ static void *qpa_audio_init(Audiodev *dev, Error **errp) runtime = getenv("XDG_RUNTIME_DIR"); if (!runtime) { error_setg(errp, "XDG_RUNTIME_DIR not set"); - return NULL; + return false; } snprintf(pidfile, sizeof(pidfile), "%s/pulse/pid", runtime); if (stat(pidfile, &st) != 0) { error_setg_errno(errp, errno, "could not stat pidfile %s", pidfile); - return NULL; + return false; } } qpa_validate_per_direction_opts(dev, popts->in); qpa_validate_per_direction_opts(dev, popts->out); - g = g_new0(paaudio, 1); server = popts->server; - - g->dev = dev; - QTAILQ_FOREACH(c, &pa_conns, list) { if (server == NULL || c->server == NULL ? server == c->server : strcmp(server, c->server) == 0) { - g->conn = c; + apa->conn = c; break; } } - if (!g->conn) { - g->conn = qpa_conn_init(server); + if (!apa->conn) { + apa->conn = qpa_conn_init(server); } - if (!g->conn) { - g_free(g); + if (!apa->conn) { error_setg(errp, "could not connect to PulseAudio server"); - return NULL; + return false; } - ++g->conn->refcount; - return g; + ++apa->conn->refcount; + + return true; } static void qpa_conn_fini(PAConnection *c) @@ -891,48 +878,54 @@ static void qpa_conn_fini(PAConnection *c) g_free(c); } -static void qpa_audio_fini (void *opaque) +static void audio_pa_finalize(Object *obj) { - paaudio *g = opaque; - PAConnection *c = g->conn; + AudioPa *apa = AUDIO_PA(obj); + PAConnection *c = apa->conn; - if (--c->refcount == 0) { + if (c && --c->refcount == 0) { qpa_conn_fini(c); } - - g_free(g); } -static struct audio_pcm_ops qpa_pcm_ops = { - .init_out = qpa_init_out, - .fini_out = qpa_fini_out, - .write = qpa_write, - .buffer_get_free = qpa_buffer_get_free, - .get_buffer_out = qpa_get_buffer_out, - .put_buffer_out = qpa_put_buffer_out, - .volume_out = qpa_volume_out, - - .init_in = qpa_init_in, - .fini_in = qpa_fini_in, - .read = qpa_read, - .get_buffer_in = qpa_get_buffer_in, - .put_buffer_in = qpa_put_buffer_in, - .volume_in = qpa_volume_in -}; +static void audio_pa_class_init(ObjectClass *klass, const void *data) +{ + AudioBackendClass *b = AUDIO_BACKEND_CLASS(klass); + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_CLASS(klass); + + audio_pa_parent_class = AUDIO_BACKEND_CLASS(object_class_get_parent(klass)); + + b->realize = audio_pa_realize; + k->max_voices_out = INT_MAX; + k->max_voices_in = INT_MAX; + k->voice_size_out = sizeof(PAVoiceOut); + k->voice_size_in = sizeof(PAVoiceIn); + + k->init_out = qpa_init_out; + k->fini_out = qpa_fini_out; + k->write = qpa_write; + k->buffer_get_free = qpa_buffer_get_free; + k->get_buffer_out = qpa_get_buffer_out; + k->put_buffer_out = qpa_put_buffer_out; + k->volume_out = qpa_volume_out; + + k->init_in = qpa_init_in; + k->fini_in = qpa_fini_in; + k->read = qpa_read; + k->get_buffer_in = qpa_get_buffer_in; + k->put_buffer_in = qpa_put_buffer_in; + k->volume_in = qpa_volume_in; +} -static struct audio_driver pa_audio_driver = { - .name = "pa", - .init = qpa_audio_init, - .fini = qpa_audio_fini, - .pcm_ops = &qpa_pcm_ops, - .max_voices_out = INT_MAX, - .max_voices_in = INT_MAX, - .voice_size_out = sizeof (PAVoiceOut), - .voice_size_in = sizeof (PAVoiceIn), +static const TypeInfo audio_types[] = { + { + .name = TYPE_AUDIO_PA, + .parent = TYPE_AUDIO_MIXENG_BACKEND, + .instance_size = sizeof(AudioPa), + .class_init = audio_pa_class_init, + .instance_finalize = audio_pa_finalize, + }, }; -static void register_audio_pa(void) -{ - audio_driver_register(&pa_audio_driver); -} -type_init(register_audio_pa); +DEFINE_TYPES(audio_types) +module_obj(TYPE_AUDIO_PA); diff --git a/audio/pwaudio.c b/audio/pwaudio.c index 30f717ccacfe..a59c22e60b87 100644 --- a/audio/pwaudio.c +++ b/audio/pwaudio.c @@ -13,37 +13,43 @@ #include "qemu/audio.h" #include "qemu/error-report.h" #include "qapi/error.h" +#include "qom/object.h" #include #include #include #include #include + +#include "audio_int.h" #include "trace.h" -#define AUDIO_CAP "pipewire" #define RINGBUFFER_SIZE (1u << 22) #define RINGBUFFER_MASK (RINGBUFFER_SIZE - 1) -#include "audio_int.h" +#define TYPE_AUDIO_PW "audio-pipewire" +OBJECT_DECLARE_SIMPLE_TYPE(AudioPw, AUDIO_PW) -typedef struct pwvolume { - uint32_t channels; - float values[SPA_AUDIO_MAX_CHANNELS]; -} pwvolume; +static AudioBackendClass *audio_pw_parent_class; + +struct AudioPw { + AudioMixengBackend parent_obj; -typedef struct pwaudio { - Audiodev *dev; struct pw_thread_loop *thread_loop; struct pw_context *context; struct pw_core *core; struct spa_hook core_listener; int last_seq, pending_seq, error; -} pwaudio; +}; + + +typedef struct pwvolume { + uint32_t channels; + float values[SPA_AUDIO_MAX_CHANNELS]; +} pwvolume; typedef struct PWVoice { - pwaudio *g; struct pw_stream *stream; struct spa_hook stream_listener; struct spa_audio_info_raw info; @@ -219,9 +225,9 @@ static const struct pw_stream_events playback_stream_events = { static size_t qpw_read(HWVoiceIn *hw, void *data, size_t len) { + AudioPw *c = AUDIO_PW(hw->s); PWVoiceIn *pw = (PWVoiceIn *) hw; PWVoice *v = &pw->v; - pwaudio *c = v->g; const char *error = NULL; size_t l; int32_t avail; @@ -256,9 +262,9 @@ qpw_read(HWVoiceIn *hw, void *data, size_t len) static size_t qpw_buffer_get_free(HWVoiceOut *hw) { + AudioPw *c = AUDIO_PW(hw->s); PWVoiceOut *pw = (PWVoiceOut *)hw; PWVoice *v = &pw->v; - pwaudio *c = v->g; const char *error = NULL; int32_t filled, avail; uint32_t index; @@ -281,9 +287,9 @@ static size_t qpw_buffer_get_free(HWVoiceOut *hw) static size_t qpw_write(HWVoiceOut *hw, void *data, size_t len) { + AudioPw *c = AUDIO_PW(hw->s); PWVoiceOut *pw = (PWVoiceOut *) hw; PWVoice *v = &pw->v; - pwaudio *c = v->g; const char *error = NULL; int32_t filled, avail; uint32_t index; @@ -351,7 +357,7 @@ audfmt_to_pw(AudioFormat fmt, bool big_endian) format = big_endian ? SPA_AUDIO_FORMAT_F32_BE : SPA_AUDIO_FORMAT_F32_LE; break; default: - dolog("Internal logic error: Bad audio format %d\n", fmt); + error_report("pipewire: internal logic error: bad audio format %d", fmt); format = SPA_AUDIO_FORMAT_U8; break; } @@ -359,7 +365,7 @@ audfmt_to_pw(AudioFormat fmt, bool big_endian) } static AudioFormat -pw_to_audfmt(enum spa_audio_format fmt, int *endianness, +pw_to_audfmt(enum spa_audio_format fmt, bool *big_endian, uint32_t *sample_size) { switch (fmt) { @@ -371,53 +377,53 @@ pw_to_audfmt(enum spa_audio_format fmt, int *endianness, return AUDIO_FORMAT_U8; case SPA_AUDIO_FORMAT_S16_BE: *sample_size = 2; - *endianness = 1; + *big_endian = true; return AUDIO_FORMAT_S16; case SPA_AUDIO_FORMAT_S16_LE: *sample_size = 2; - *endianness = 0; + *big_endian = false; return AUDIO_FORMAT_S16; case SPA_AUDIO_FORMAT_U16_BE: *sample_size = 2; - *endianness = 1; + *big_endian = true; return AUDIO_FORMAT_U16; case SPA_AUDIO_FORMAT_U16_LE: *sample_size = 2; - *endianness = 0; + *big_endian = false; return AUDIO_FORMAT_U16; case SPA_AUDIO_FORMAT_S32_BE: *sample_size = 4; - *endianness = 1; + *big_endian = true; return AUDIO_FORMAT_S32; case SPA_AUDIO_FORMAT_S32_LE: *sample_size = 4; - *endianness = 0; + *big_endian = false; return AUDIO_FORMAT_S32; case SPA_AUDIO_FORMAT_U32_BE: *sample_size = 4; - *endianness = 1; + *big_endian = true; return AUDIO_FORMAT_U32; case SPA_AUDIO_FORMAT_U32_LE: *sample_size = 4; - *endianness = 0; + *big_endian = false; return AUDIO_FORMAT_U32; case SPA_AUDIO_FORMAT_F32_BE: *sample_size = 4; - *endianness = 1; + *big_endian = true; return AUDIO_FORMAT_F32; case SPA_AUDIO_FORMAT_F32_LE: *sample_size = 4; - *endianness = 0; + *big_endian = false; return AUDIO_FORMAT_F32; default: *sample_size = 1; - dolog("Internal logic error: Bad spa_audio_format %d\n", fmt); + error_report("pipewire: internal logic error: bad spa_audio_format %d", fmt); return AUDIO_FORMAT_U8; } } static int -qpw_stream_new(pwaudio *c, PWVoice *v, const char *stream_name, +qpw_stream_new(AudioPw *c, PWVoice *v, const char *stream_name, const char *name, enum spa_direction dir) { int res; @@ -435,8 +441,8 @@ qpw_stream_new(pwaudio *c, PWVoice *v, const char *stream_name, } /* 75% of the timer period for faster updates */ - buf_samples = (uint64_t)v->g->dev->timer_period * v->info.rate - * 3 / 4 / 1000000; + buf_samples = (uint64_t)AUDIO_MIXENG_BACKEND(c)->dev->timer_period + * v->info.rate * 3 / 4 / 1000000; pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%" PRIu64 "/%u", buf_samples, v->info.rate); @@ -511,37 +517,37 @@ qpw_set_position(uint32_t channels, uint32_t position[SPA_AUDIO_MAX_CHANNELS]) position[0] = SPA_AUDIO_CHANNEL_MONO; break; default: - dolog("Internal error: unsupported channel count %d\n", channels); + error_report("pipewire: unsupported channel count %d", channels); } } static int -qpw_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque) +qpw_init_out(HWVoiceOut *hw, struct audsettings *as) { + AudioPw *c = AUDIO_PW(hw->s); PWVoiceOut *pw = (PWVoiceOut *) hw; PWVoice *v = &pw->v; struct audsettings obt_as = *as; - pwaudio *c = v->g = drv_opaque; - AudiodevPipewireOptions *popts = &c->dev->u.pipewire; + AudiodevPipewireOptions *popts = &AUDIO_MIXENG_BACKEND(c)->dev->u.pipewire; AudiodevPipewirePerDirectionOptions *ppdo = popts->out; int r; pw_thread_loop_lock(c->thread_loop); - v->info.format = audfmt_to_pw(as->fmt, as->endianness); + v->info.format = audfmt_to_pw(as->fmt, as->big_endian); v->info.channels = as->nchannels; qpw_set_position(as->nchannels, v->info.position); v->info.rate = as->freq; obt_as.fmt = - pw_to_audfmt(v->info.format, &obt_as.endianness, &v->frame_size); + pw_to_audfmt(v->info.format, &obt_as.big_endian, &v->frame_size); v->frame_size *= as->nchannels; - v->req = (uint64_t)c->dev->timer_period * v->info.rate + v->req = (uint64_t)AUDIO_MIXENG_BACKEND(c)->dev->timer_period * v->info.rate * 1 / 2 / 1000000 * v->frame_size; /* call the function that creates a new stream for playback */ - r = qpw_stream_new(c, v, ppdo->stream_name ? : c->dev->id, + r = qpw_stream_new(c, v, ppdo->stream_name ?: AUDIO_MIXENG_BACKEND(c)->dev->id, ppdo->name, SPA_DIRECTION_OUTPUT); if (r < 0) { pw_thread_loop_unlock(c->thread_loop); @@ -563,29 +569,29 @@ qpw_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque) } static int -qpw_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) +qpw_init_in(HWVoiceIn *hw, struct audsettings *as) { + AudioPw *c = AUDIO_PW(hw->s); PWVoiceIn *pw = (PWVoiceIn *) hw; PWVoice *v = &pw->v; struct audsettings obt_as = *as; - pwaudio *c = v->g = drv_opaque; - AudiodevPipewireOptions *popts = &c->dev->u.pipewire; + AudiodevPipewireOptions *popts = &AUDIO_MIXENG_BACKEND(c)->dev->u.pipewire; AudiodevPipewirePerDirectionOptions *ppdo = popts->in; int r; pw_thread_loop_lock(c->thread_loop); - v->info.format = audfmt_to_pw(as->fmt, as->endianness); + v->info.format = audfmt_to_pw(as->fmt, as->big_endian); v->info.channels = as->nchannels; qpw_set_position(as->nchannels, v->info.position); v->info.rate = as->freq; obt_as.fmt = - pw_to_audfmt(v->info.format, &obt_as.endianness, &v->frame_size); + pw_to_audfmt(v->info.format, &obt_as.big_endian, &v->frame_size); v->frame_size *= as->nchannels; /* call the function that creates a new stream for recording */ - r = qpw_stream_new(c, v, ppdo->stream_name ? : c->dev->id, + r = qpw_stream_new(c, v, ppdo->stream_name ? : AUDIO_MIXENG_BACKEND(c)->dev->id, ppdo->name, SPA_DIRECTION_INPUT); if (r < 0) { pw_thread_loop_unlock(c->thread_loop); @@ -604,10 +610,8 @@ qpw_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) } static void -qpw_voice_fini(PWVoice *v) +qpw_voice_fini(AudioPw *c, PWVoice *v) { - pwaudio *c = v->g; - if (!v->stream) { return; } @@ -620,19 +624,18 @@ qpw_voice_fini(PWVoice *v) static void qpw_fini_out(HWVoiceOut *hw) { - qpw_voice_fini(&PW_VOICE_OUT(hw)->v); + qpw_voice_fini(AUDIO_PW(hw->s), &PW_VOICE_OUT(hw)->v); } static void qpw_fini_in(HWVoiceIn *hw) { - qpw_voice_fini(&PW_VOICE_IN(hw)->v); + qpw_voice_fini(AUDIO_PW(hw->s), &PW_VOICE_IN(hw)->v); } static void -qpw_voice_set_enabled(PWVoice *v, bool enable) +qpw_voice_set_enabled(AudioPw *c, PWVoice *v, bool enable) { - pwaudio *c = v->g; pw_thread_loop_lock(c->thread_loop); pw_stream_set_active(v->stream, enable); pw_thread_loop_unlock(c->thread_loop); @@ -641,19 +644,18 @@ qpw_voice_set_enabled(PWVoice *v, bool enable) static void qpw_enable_out(HWVoiceOut *hw, bool enable) { - qpw_voice_set_enabled(&PW_VOICE_OUT(hw)->v, enable); + qpw_voice_set_enabled(AUDIO_PW(hw->s), &PW_VOICE_OUT(hw)->v, enable); } static void qpw_enable_in(HWVoiceIn *hw, bool enable) { - qpw_voice_set_enabled(&PW_VOICE_IN(hw)->v, enable); + qpw_voice_set_enabled(AUDIO_PW(hw->s), &PW_VOICE_IN(hw)->v, enable); } static void -qpw_voice_set_volume(PWVoice *v, Volume *vol) +qpw_voice_set_volume(AudioPw *c, PWVoice *v, Volume *vol) { - pwaudio *c = v->g; int i, ret; pw_thread_loop_lock(c->thread_loop); @@ -676,16 +678,16 @@ qpw_voice_set_volume(PWVoice *v, Volume *vol) static void qpw_volume_out(HWVoiceOut *hw, Volume *vol) { - qpw_voice_set_volume(&PW_VOICE_OUT(hw)->v, vol); + qpw_voice_set_volume(AUDIO_PW(hw->s), &PW_VOICE_OUT(hw)->v, vol); } static void qpw_volume_in(HWVoiceIn *hw, Volume *vol) { - qpw_voice_set_volume(&PW_VOICE_IN(hw)->v, vol); + qpw_voice_set_volume(AUDIO_PW(hw->s), &PW_VOICE_IN(hw)->v, vol); } -static int wait_resync(pwaudio *pw) +static int wait_resync(AudioPw *pw) { int res; pw->pending_seq = pw_core_sync(pw->core, PW_ID_CORE, pw->pending_seq); @@ -708,7 +710,7 @@ static int wait_resync(pwaudio *pw) static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) { - pwaudio *pw = data; + AudioPw *pw = data; error_report("error id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); @@ -720,7 +722,7 @@ on_core_error(void *data, uint32_t id, int seq, int res, const char *message) static void on_core_done(void *data, uint32_t id, int seq) { - pwaudio *pw = data; + AudioPw *pw = data; assert(id == PW_ID_CORE); pw->last_seq = seq; if (pw->pending_seq == seq) { @@ -735,17 +737,20 @@ static const struct pw_core_events core_events = { .error = on_core_error, }; -static void * -qpw_audio_init(Audiodev *dev, Error **errp) +static bool +audio_pw_realize(AudioBackend *abe, Audiodev *dev, Error **errp) { - g_autofree pwaudio *pw = g_new0(pwaudio, 1); + AudioPw *pw = AUDIO_PW(abe); assert(dev->driver == AUDIODEV_DRIVER_PIPEWIRE); trace_pw_audio_init(); + if (!audio_pw_parent_class->realize(abe, dev, errp)) { + return false; + } + pw_init(NULL, NULL); - pw->dev = dev; pw->thread_loop = pw_thread_loop_new("PipeWire thread loop", NULL); if (pw->thread_loop == NULL) { error_setg_errno(errp, errno, "Could not create PipeWire loop"); @@ -784,8 +789,7 @@ qpw_audio_init(Audiodev *dev, Error **errp) } pw_thread_loop_unlock(pw->thread_loop); - - return g_steal_pointer(&pw); + return true; fail: if (pw->thread_loop) { @@ -793,13 +797,13 @@ qpw_audio_init(Audiodev *dev, Error **errp) } g_clear_pointer(&pw->context, pw_context_destroy); g_clear_pointer(&pw->thread_loop, pw_thread_loop_destroy); - return NULL; + return false; } static void -qpw_audio_fini(void *opaque) +audio_pw_finalize(Object *obj) { - pwaudio *pw = opaque; + AudioPw *pw = AUDIO_PW(obj); if (pw->thread_loop) { pw_thread_loop_stop(pw->thread_loop); @@ -814,43 +818,47 @@ qpw_audio_fini(void *opaque) if (pw->context) { pw_context_destroy(pw->context); } - pw_thread_loop_destroy(pw->thread_loop); - - g_free(pw); + g_clear_pointer(&pw->thread_loop, pw_thread_loop_destroy); } -static struct audio_pcm_ops qpw_pcm_ops = { - .init_out = qpw_init_out, - .fini_out = qpw_fini_out, - .write = qpw_write, - .buffer_get_free = qpw_buffer_get_free, - .run_buffer_out = audio_generic_run_buffer_out, - .enable_out = qpw_enable_out, - .volume_out = qpw_volume_out, - .volume_in = qpw_volume_in, - - .init_in = qpw_init_in, - .fini_in = qpw_fini_in, - .read = qpw_read, - .run_buffer_in = audio_generic_run_buffer_in, - .enable_in = qpw_enable_in -}; - -static struct audio_driver pw_audio_driver = { - .name = "pipewire", - .init = qpw_audio_init, - .fini = qpw_audio_fini, - .pcm_ops = &qpw_pcm_ops, - .max_voices_out = INT_MAX, - .max_voices_in = INT_MAX, - .voice_size_out = sizeof(PWVoiceOut), - .voice_size_in = sizeof(PWVoiceIn), -}; - -static void -register_audio_pw(void) +static void audio_pw_class_init(ObjectClass *klass, const void *data) { - audio_driver_register(&pw_audio_driver); + AudioBackendClass *b = AUDIO_BACKEND_CLASS(klass); + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_CLASS(klass); + + audio_pw_parent_class = AUDIO_BACKEND_CLASS(object_class_get_parent(klass)); + + b->realize = audio_pw_realize; + k->max_voices_out = INT_MAX; + k->max_voices_in = INT_MAX; + k->voice_size_out = sizeof(PWVoiceOut); + k->voice_size_in = sizeof(PWVoiceIn); + + k->init_out = qpw_init_out; + k->fini_out = qpw_fini_out; + k->write = qpw_write; + k->buffer_get_free = qpw_buffer_get_free; + k->run_buffer_out = audio_generic_run_buffer_out; + k->enable_out = qpw_enable_out; + k->volume_out = qpw_volume_out; + + k->init_in = qpw_init_in; + k->fini_in = qpw_fini_in; + k->read = qpw_read; + k->run_buffer_in = audio_generic_run_buffer_in; + k->enable_in = qpw_enable_in; + k->volume_in = qpw_volume_in; } -type_init(register_audio_pw); +static const TypeInfo audio_types[] = { + { + .name = TYPE_AUDIO_PW, + .parent = TYPE_AUDIO_MIXENG_BACKEND, + .instance_size = sizeof(AudioPw), + .class_init = audio_pw_class_init, + .instance_finalize = audio_pw_finalize, + }, +}; + +DEFINE_TYPES(audio_types) +module_obj(TYPE_AUDIO_PW); diff --git a/audio/sdlaudio.c b/audio/sdlaudio.c index 707110973ac9..bd6a46e2af1c 100644 --- a/audio/sdlaudio.c +++ b/audio/sdlaudio.c @@ -26,8 +26,10 @@ #include #include #include "qemu/module.h" +#include "qemu/error-report.h" #include "qapi/error.h" #include "qemu/audio.h" +#include "qom/object.h" #ifndef _WIN32 #ifdef __sun__ @@ -37,9 +39,18 @@ #endif #endif -#define AUDIO_CAP "sdl" #include "audio_int.h" +#define TYPE_AUDIO_SDL "audio-sdl" +OBJECT_DECLARE_SIMPLE_TYPE(AudioSdl, AUDIO_SDL) + +static AudioBackendClass *audio_sdl_parent_class; + +struct AudioSdl { + AudioMixengBackend parent_obj; +}; + + typedef struct SDLVoiceOut { HWVoiceOut hw; int exit; @@ -56,17 +67,6 @@ typedef struct SDLVoiceIn { SDL_AudioDeviceID devid; } SDLVoiceIn; -static void G_GNUC_PRINTF (1, 2) sdl_logerr (const char *fmt, ...) -{ - va_list ap; - - va_start (ap, fmt); - AUD_vlog (AUDIO_CAP, fmt, ap); - va_end (ap); - - AUD_log (AUDIO_CAP, "Reason: %s\n", SDL_GetError ()); -} - static int aud_to_sdlfmt (AudioFormat fmt) { switch (fmt) { @@ -91,69 +91,66 @@ static int aud_to_sdlfmt (AudioFormat fmt) return AUDIO_F32LSB; default: - dolog ("Internal logic error: Bad audio format %d\n", fmt); -#ifdef DEBUG_AUDIO - abort (); -#endif + error_report("sdl: internal logic error: bad audio format %d", fmt); return AUDIO_U8; } } -static int sdl_to_audfmt(int sdlfmt, AudioFormat *fmt, int *endianness) +static int sdl_to_audfmt(int sdlfmt, AudioFormat *fmt, bool *big_endian) { switch (sdlfmt) { case AUDIO_S8: - *endianness = 0; + *big_endian = false; *fmt = AUDIO_FORMAT_S8; break; case AUDIO_U8: - *endianness = 0; + *big_endian = false; *fmt = AUDIO_FORMAT_U8; break; case AUDIO_S16LSB: - *endianness = 0; + *big_endian = false; *fmt = AUDIO_FORMAT_S16; break; case AUDIO_U16LSB: - *endianness = 0; + *big_endian = false; *fmt = AUDIO_FORMAT_U16; break; case AUDIO_S16MSB: - *endianness = 1; + *big_endian = true; *fmt = AUDIO_FORMAT_S16; break; case AUDIO_U16MSB: - *endianness = 1; + *big_endian = true; *fmt = AUDIO_FORMAT_U16; break; case AUDIO_S32LSB: - *endianness = 0; + *big_endian = false; *fmt = AUDIO_FORMAT_S32; break; case AUDIO_S32MSB: - *endianness = 1; + *big_endian = true; *fmt = AUDIO_FORMAT_S32; break; case AUDIO_F32LSB: - *endianness = 0; + *big_endian = false; *fmt = AUDIO_FORMAT_F32; break; case AUDIO_F32MSB: - *endianness = 1; + *big_endian = true; *fmt = AUDIO_FORMAT_F32; break; default: - dolog ("Unrecognized SDL audio format %d\n", sdlfmt); + error_report("sdl: unrecognized audio format %d", sdlfmt); return -1; } @@ -171,27 +168,27 @@ static SDL_AudioDeviceID sdl_open(SDL_AudioSpec *req, SDL_AudioSpec *obt, /* Make sure potential threads created by SDL don't hog signals. */ err = sigfillset (&new); if (err) { - dolog ("sdl_open: sigfillset failed: %s\n", strerror (errno)); + error_report("sdl: sigfillset failed: %s", strerror (errno)); return 0; } err = pthread_sigmask (SIG_BLOCK, &new, &old); if (err) { - dolog ("sdl_open: pthread_sigmask failed: %s\n", strerror (err)); + error_report("sdl: pthread_sigmask failed: %s", strerror (err)); return 0; } #endif devid = SDL_OpenAudioDevice(NULL, rec, req, obt, 0); if (!devid) { - sdl_logerr("SDL_OpenAudioDevice for %s failed\n", - rec ? "recording" : "playback"); + error_report("SDL_OpenAudioDevice for %s failed: %s", + rec ? "recording" : "playback", SDL_GetError()); } #ifndef _WIN32 err = pthread_sigmask (SIG_SETMASK, &old, NULL); if (err) { - dolog ("sdl_open: pthread_sigmask (restore) failed: %s\n", - strerror (errno)); + error_report("sdl: pthread_sigmask (restore) failed: %s", + strerror (errno)); /* We have failed to restore original signal mask, all bets are off, so exit the process */ exit (EXIT_FAILURE); @@ -222,8 +219,6 @@ static void sdl_callback_out(void *opaque, Uint8 *buf, int len) if (!sdl->exit) { - /* dolog("callback_out: len=%d avail=%zu\n", len, hw->pending_emul); */ - while (hw->pending_emul && len) { size_t write_len, start; @@ -272,8 +267,6 @@ static void sdl_callback_in(void *opaque, Uint8 *buf, int len) return; } - /* dolog("callback_in: len=%d pending=%zu\n", len, hw->pending_emul); */ - while (hw->pending_emul < hw->size_emul && len) { size_t read_len = MIN(len, MIN(hw->size_emul - hw->pos_emul, hw->size_emul - hw->pending_emul)); @@ -333,13 +326,12 @@ static void sdl_fini_out(HWVoiceOut *hw) sdl_close_out(sdl); } -static int sdl_init_out(HWVoiceOut *hw, struct audsettings *as, - void *drv_opaque) +static int sdl_init_out(HWVoiceOut *hw, struct audsettings *as) { SDLVoiceOut *sdl = (SDLVoiceOut *)hw; SDL_AudioSpec req, obt; int err; - Audiodev *dev = drv_opaque; + Audiodev *dev = hw->s->dev; AudiodevSdlPerDirectionOptions *spdo = dev->u.sdl.out; struct audsettings obt_as; @@ -358,7 +350,7 @@ static int sdl_init_out(HWVoiceOut *hw, struct audsettings *as, return -1; } - err = sdl_to_audfmt(obt.format, &obt_as.fmt, &obt_as.endianness); + err = sdl_to_audfmt(obt.format, &obt_as.fmt, &obt_as.big_endian); if (err) { sdl_close_out(sdl); return -1; @@ -390,12 +382,12 @@ static void sdl_fini_in(HWVoiceIn *hw) sdl_close_in(sdl); } -static int sdl_init_in(HWVoiceIn *hw, audsettings *as, void *drv_opaque) +static int sdl_init_in(HWVoiceIn *hw, audsettings *as) { SDLVoiceIn *sdl = (SDLVoiceIn *)hw; SDL_AudioSpec req, obt; int err; - Audiodev *dev = drv_opaque; + Audiodev *dev = hw->s->dev; AudiodevSdlPerDirectionOptions *spdo = dev->u.sdl.in; struct audsettings obt_as; @@ -414,7 +406,7 @@ static int sdl_init_in(HWVoiceIn *hw, audsettings *as, void *drv_opaque) return -1; } - err = sdl_to_audfmt(obt.format, &obt_as.fmt, &obt_as.endianness); + err = sdl_to_audfmt(obt.format, &obt_as.fmt, &obt_as.big_endian); if (err) { sdl_close_in(sdl); return -1; @@ -442,57 +434,67 @@ static void sdl_enable_in(HWVoiceIn *hw, bool enable) SDL_PauseAudioDevice(sdl->devid, !enable); } -static void *sdl_audio_init(Audiodev *dev, Error **errp) +static bool audio_sdl_realize(AudioBackend *abe, Audiodev *dev, Error **errp) { - if (SDL_InitSubSystem (SDL_INIT_AUDIO)) { + if (SDL_InitSubSystem(SDL_INIT_AUDIO)) { error_setg(errp, "SDL failed to initialize audio subsystem"); - return NULL; + qapi_free_Audiodev(dev); + return false; } - return dev; + return audio_sdl_parent_class->realize(abe, dev, errp); } -static void sdl_audio_fini (void *opaque) +static void audio_sdl_finalize(Object *obj) { - SDL_QuitSubSystem (SDL_INIT_AUDIO); + SDL_QuitSubSystem(SDL_INIT_AUDIO); } -static struct audio_pcm_ops sdl_pcm_ops = { - .init_out = sdl_init_out, - .fini_out = sdl_fini_out, - /* wrapper for audio_generic_write */ - .write = sdl_write, - /* wrapper for audio_generic_buffer_get_free */ - .buffer_get_free = sdl_buffer_get_free, - /* wrapper for audio_generic_get_buffer_out */ - .get_buffer_out = sdl_get_buffer_out, - /* wrapper for audio_generic_put_buffer_out */ - .put_buffer_out = sdl_put_buffer_out, - .enable_out = sdl_enable_out, - .init_in = sdl_init_in, - .fini_in = sdl_fini_in, - /* wrapper for audio_generic_read */ - .read = sdl_read, - /* wrapper for audio_generic_get_buffer_in */ - .get_buffer_in = sdl_get_buffer_in, - /* wrapper for audio_generic_put_buffer_in */ - .put_buffer_in = sdl_put_buffer_in, - .enable_in = sdl_enable_in, -}; +static void audio_sdl_class_init(ObjectClass *klass, const void *data) +{ + AudioBackendClass *b = AUDIO_BACKEND_CLASS(klass); + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_CLASS(klass); + + audio_sdl_parent_class = AUDIO_BACKEND_CLASS(object_class_get_parent(klass)); + + b->realize = audio_sdl_realize; + k->max_voices_out = INT_MAX; + k->max_voices_in = INT_MAX; + k->voice_size_out = sizeof(SDLVoiceOut); + k->voice_size_in = sizeof(SDLVoiceIn); + + k->init_out = sdl_init_out; + k->fini_out = sdl_fini_out; + /* wrapper for audio_generic_write */ + k->write = sdl_write; + /* wrapper for audio_generic_buffer_get_free */ + k->buffer_get_free = sdl_buffer_get_free; + /* wrapper for audio_generic_get_buffer_out */ + k->get_buffer_out = sdl_get_buffer_out; + /* wrapper for audio_generic_put_buffer_out */ + k->put_buffer_out = sdl_put_buffer_out; + k->enable_out = sdl_enable_out; + + k->init_in = sdl_init_in; + k->fini_in = sdl_fini_in; + /* wrapper for audio_generic_read */ + k->read = sdl_read; + /* wrapper for audio_generic_get_buffer_in */ + k->get_buffer_in = sdl_get_buffer_in; + /* wrapper for audio_generic_put_buffer_in */ + k->put_buffer_in = sdl_put_buffer_in; + k->enable_in = sdl_enable_in; +} -static struct audio_driver sdl_audio_driver = { - .name = "sdl", - .init = sdl_audio_init, - .fini = sdl_audio_fini, - .pcm_ops = &sdl_pcm_ops, - .max_voices_out = INT_MAX, - .max_voices_in = INT_MAX, - .voice_size_out = sizeof(SDLVoiceOut), - .voice_size_in = sizeof(SDLVoiceIn), +static const TypeInfo audio_types[] = { + { + .name = TYPE_AUDIO_SDL, + .parent = TYPE_AUDIO_MIXENG_BACKEND, + .instance_size = sizeof(AudioSdl), + .class_init = audio_sdl_class_init, + .instance_finalize = audio_sdl_finalize, + }, }; -static void register_audio_sdl(void) -{ - audio_driver_register(&sdl_audio_driver); -} -type_init(register_audio_sdl); +DEFINE_TYPES(audio_types) +module_obj(TYPE_AUDIO_SDL); diff --git a/audio/sndioaudio.c b/audio/sndioaudio.c index 8197b8b0b47a..2e05ef7ea74c 100644 --- a/audio/sndioaudio.c +++ b/audio/sndioaudio.c @@ -18,11 +18,20 @@ #include #include #include "qemu/main-loop.h" +#include "qemu/error-report.h" #include "qemu/audio.h" -#include "trace.h" +#include "qom/object.h" -#define AUDIO_CAP "sndio" #include "audio_int.h" +#include "trace.h" + +#define TYPE_AUDIO_SNDIO "audio-sndio" +OBJECT_DECLARE_SIMPLE_TYPE(AudioSndio, AUDIO_SNDIO) + +struct AudioSndio { + AudioMixengBackend parent_obj; +}; + /* default latency in microseconds if no option is set */ #define SNDIO_LATENCY_US 50000 @@ -339,7 +348,7 @@ static int sndio_init(SndioVoice *self, /* open the device in non-blocking mode */ self->hdl = sio_open(dev_name, mode, 1); if (self->hdl == NULL) { - dolog("failed to open device\n"); + error_report("sndio: failed to open device"); return -1; } @@ -373,12 +382,12 @@ static int sndio_init(SndioVoice *self, req.sig = 0; break; default: - dolog("unknown audio sample format\n"); + error_report("sndio: unknown audio sample format"); return -1; } if (req.bits > 8) { - req.le = as->endianness ? 0 : 1; + req.le = !as->big_endian; } req.rate = as->freq; @@ -392,12 +401,12 @@ static int sndio_init(SndioVoice *self, req.appbufsz = req.rate * latency / 1000000; if (!sio_setpar(self->hdl, &req)) { - dolog("failed set audio params\n"); + error_report("sndio: failed to set audio params"); goto fail; } if (!sio_getpar(self->hdl, &self->par)) { - dolog("failed get audio params\n"); + error_report("sndio: failed to get audio params"); goto fail; } @@ -410,7 +419,7 @@ static int sndio_init(SndioVoice *self, if (self->par.bits != req.bits || self->par.bps != req.bits / 8 || self->par.sig != req.sig || (req.bits > 8 && self->par.le != req.le) || self->par.rate != as->freq || nch != as->nchannels) { - dolog("unsupported audio params\n"); + error_report("sndio: unsupported audio params"); goto fail; } @@ -422,7 +431,7 @@ static int sndio_init(SndioVoice *self, self->buf = g_malloc(self->buf_size); if (self->buf == NULL) { - dolog("failed to allocate audio buffer\n"); + error_report("sndio: failed to allocate audio buffer"); goto fail; } @@ -430,13 +439,13 @@ static int sndio_init(SndioVoice *self, self->pfds = g_malloc_n(nfds, sizeof(struct pollfd)); if (self->pfds == NULL) { - dolog("failed to allocate pollfd structures\n"); + error_report("sndio: failed to allocate pollfd structures"); goto fail; } self->pindexes = g_malloc_n(nfds, sizeof(struct pollindex)); if (self->pindexes == NULL) { - dolog("failed to allocate pollindex structures\n"); + error_report("sndio: failed to allocate pollindex structures"); goto fail; } @@ -478,11 +487,11 @@ static void sndio_enable_in(HWVoiceIn *hw, bool enable) sndio_enable(self, enable); } -static int sndio_init_out(HWVoiceOut *hw, struct audsettings *as, void *opaque) +static int sndio_init_out(HWVoiceOut *hw, struct audsettings *as) { SndioVoice *self = (SndioVoice *) hw; - if (sndio_init(self, as, SIO_PLAY, opaque) == -1) { + if (sndio_init(self, as, SIO_PLAY, hw->s->dev) == -1) { return -1; } @@ -491,11 +500,11 @@ static int sndio_init_out(HWVoiceOut *hw, struct audsettings *as, void *opaque) return 0; } -static int sndio_init_in(HWVoiceIn *hw, struct audsettings *as, void *opaque) +static int sndio_init_in(HWVoiceIn *hw, struct audsettings *as) { SndioVoice *self = (SndioVoice *) hw; - if (sndio_init(self, as, SIO_REC, opaque) == -1) { + if (sndio_init(self, as, SIO_REC, hw->s->dev) == -1) { return -1; } @@ -518,46 +527,39 @@ static void sndio_fini_in(HWVoiceIn *hw) sndio_fini(self); } -static void *sndio_audio_init(Audiodev *dev, Error **errp) -{ - assert(dev->driver == AUDIODEV_DRIVER_SNDIO); - return dev; -} - -static void sndio_audio_fini(void *opaque) +static void audio_sndio_class_init(ObjectClass *klass, const void *data) { + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_CLASS(klass); + + k->max_voices_out = INT_MAX; + k->max_voices_in = INT_MAX; + k->voice_size_out = sizeof(SndioVoice); + k->voice_size_in = sizeof(SndioVoice); + + k->init_out = sndio_init_out; + k->fini_out = sndio_fini_out; + k->write = audio_generic_write; + k->buffer_get_free = sndio_buffer_get_free; + k->get_buffer_out = sndio_get_buffer_out; + k->put_buffer_out = sndio_put_buffer_out; + k->enable_out = sndio_enable_out; + + k->init_in = sndio_init_in; + k->fini_in = sndio_fini_in; + k->read = audio_generic_read; + k->get_buffer_in = sndio_get_buffer_in; + k->put_buffer_in = sndio_put_buffer_in; + k->enable_in = sndio_enable_in; } -static struct audio_pcm_ops sndio_pcm_ops = { - .init_out = sndio_init_out, - .fini_out = sndio_fini_out, - .enable_out = sndio_enable_out, - .write = audio_generic_write, - .buffer_get_free = sndio_buffer_get_free, - .get_buffer_out = sndio_get_buffer_out, - .put_buffer_out = sndio_put_buffer_out, - .init_in = sndio_init_in, - .fini_in = sndio_fini_in, - .read = audio_generic_read, - .enable_in = sndio_enable_in, - .get_buffer_in = sndio_get_buffer_in, - .put_buffer_in = sndio_put_buffer_in, -}; - -static struct audio_driver sndio_audio_driver = { - .name = "sndio", - .init = sndio_audio_init, - .fini = sndio_audio_fini, - .pcm_ops = &sndio_pcm_ops, - .max_voices_out = INT_MAX, - .max_voices_in = INT_MAX, - .voice_size_out = sizeof(SndioVoice), - .voice_size_in = sizeof(SndioVoice) +static const TypeInfo audio_types[] = { + { + .name = TYPE_AUDIO_SNDIO, + .parent = TYPE_AUDIO_MIXENG_BACKEND, + .instance_size = sizeof(AudioSndio), + .class_init = audio_sndio_class_init, + }, }; -static void register_audio_sndio(void) -{ - audio_driver_register(&sndio_audio_driver); -} - -type_init(register_audio_sndio); +DEFINE_TYPES(audio_types) +module_obj(TYPE_AUDIO_SNDIO); diff --git a/audio/spiceaudio.c b/audio/spiceaudio.c index 7db2d1f0dfd1..5a97eb80a678 100644 --- a/audio/spiceaudio.c +++ b/audio/spiceaudio.c @@ -24,22 +24,33 @@ #include "qemu/timer.h" #include "qapi/error.h" #include "ui/qemu-spice.h" +#include "qom/object.h" -#define AUDIO_CAP "spice" #include "qemu/audio.h" #include "audio_int.h" -#if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3 -#define LINE_OUT_SAMPLES (480 * 4) -#else -#define LINE_OUT_SAMPLES (256 * 4) -#endif +#define TYPE_AUDIO_SPICE "audio-spice" +OBJECT_DECLARE_SIMPLE_TYPE(AudioSpice, AUDIO_SPICE) + +static AudioBackendClass *audio_spice_parent_class; + +struct AudioSpice { + AudioMixengBackend parent_obj; +}; + +static bool spice_audio_realize(AudioBackend *abe, Audiodev *dev, Error **errp) +{ + if (!using_spice) { + error_setg(errp, "Cannot use spice audio without -spice"); + qapi_free_Audiodev(dev); + return false; + } + + return audio_spice_parent_class->realize(abe, dev, errp); +} -#if SPICE_INTERFACE_RECORD_MAJOR > 2 || SPICE_INTERFACE_RECORD_MINOR >= 3 +#define LINE_OUT_SAMPLES (480 * 4) #define LINE_IN_SAMPLES (480 * 4) -#else -#define LINE_IN_SAMPLES (256 * 4) -#endif typedef struct SpiceVoiceOut { HWVoiceOut hw; @@ -72,37 +83,17 @@ static const SpiceRecordInterface record_sif = { .base.minor_version = SPICE_INTERFACE_RECORD_MINOR, }; -static void *spice_audio_init(Audiodev *dev, Error **errp) -{ - if (!using_spice) { - error_setg(errp, "Cannot use spice audio without -spice"); - return NULL; - } - - return &spice_audio_init; -} - -static void spice_audio_fini (void *opaque) -{ - /* nothing */ -} - /* playback */ -static int line_out_init(HWVoiceOut *hw, struct audsettings *as, - void *drv_opaque) +static int line_out_init(HWVoiceOut *hw, struct audsettings *as) { SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); struct audsettings settings; -#if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3 settings.freq = spice_server_get_best_playback_rate(NULL); -#else - settings.freq = SPICE_INTERFACE_PLAYBACK_FREQ; -#endif settings.nchannels = SPICE_INTERFACE_PLAYBACK_CHAN; settings.fmt = AUDIO_FORMAT_S16; - settings.endianness = HOST_BIG_ENDIAN; + settings.big_endian = HOST_BIG_ENDIAN; audio_pcm_init_info (&hw->info, &settings); hw->samples = LINE_OUT_SAMPLES; @@ -110,9 +101,7 @@ static int line_out_init(HWVoiceOut *hw, struct audsettings *as, out->sin.base.sif = &playback_sif.base; qemu_spice.add_interface(&out->sin.base); -#if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3 spice_server_set_playback_rate(&out->sin, settings.freq); -#endif return 0; } @@ -190,7 +179,6 @@ static void line_out_enable(HWVoiceOut *hw, bool enable) } } -#if ((SPICE_INTERFACE_PLAYBACK_MAJOR >= 1) && (SPICE_INTERFACE_PLAYBACK_MINOR >= 2)) static void line_out_volume(HWVoiceOut *hw, Volume *vol) { SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw); @@ -202,23 +190,18 @@ static void line_out_volume(HWVoiceOut *hw, Volume *vol) spice_server_playback_set_volume(&out->sin, 2, svol); spice_server_playback_set_mute(&out->sin, vol->mute); } -#endif /* record */ -static int line_in_init(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) +static int line_in_init(HWVoiceIn *hw, struct audsettings *as) { SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); struct audsettings settings; -#if SPICE_INTERFACE_RECORD_MAJOR > 2 || SPICE_INTERFACE_RECORD_MINOR >= 3 settings.freq = spice_server_get_best_record_rate(NULL); -#else - settings.freq = SPICE_INTERFACE_RECORD_FREQ; -#endif settings.nchannels = SPICE_INTERFACE_RECORD_CHAN; settings.fmt = AUDIO_FORMAT_S16; - settings.endianness = HOST_BIG_ENDIAN; + settings.big_endian = HOST_BIG_ENDIAN; audio_pcm_init_info (&hw->info, &settings); hw->samples = LINE_IN_SAMPLES; @@ -226,9 +209,7 @@ static int line_in_init(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) in->sin.base.sif = &record_sif.base; qemu_spice.add_interface(&in->sin.base); -#if SPICE_INTERFACE_RECORD_MAJOR > 2 || SPICE_INTERFACE_RECORD_MINOR >= 3 spice_server_set_record_rate(&in->sin, settings.freq); -#endif return 0; } @@ -277,7 +258,6 @@ static void line_in_enable(HWVoiceIn *hw, bool enable) } } -#if ((SPICE_INTERFACE_RECORD_MAJOR >= 2) && (SPICE_INTERFACE_RECORD_MINOR >= 2)) static void line_in_volume(HWVoiceIn *hw, Volume *vol) { SpiceVoiceIn *in = container_of(hw, SpiceVoiceIn, hw); @@ -289,46 +269,47 @@ static void line_in_volume(HWVoiceIn *hw, Volume *vol) spice_server_record_set_volume(&in->sin, 2, svol); spice_server_record_set_mute(&in->sin, vol->mute); } -#endif - -static struct audio_pcm_ops audio_callbacks = { - .init_out = line_out_init, - .fini_out = line_out_fini, - .write = audio_generic_write, - .buffer_get_free = line_out_get_free, - .get_buffer_out = line_out_get_buffer, - .put_buffer_out = line_out_put_buffer, - .enable_out = line_out_enable, -#if (SPICE_INTERFACE_PLAYBACK_MAJOR >= 1) && \ - (SPICE_INTERFACE_PLAYBACK_MINOR >= 2) - .volume_out = line_out_volume, -#endif - - .init_in = line_in_init, - .fini_in = line_in_fini, - .read = line_in_read, - .run_buffer_in = audio_generic_run_buffer_in, - .enable_in = line_in_enable, -#if ((SPICE_INTERFACE_RECORD_MAJOR >= 2) && (SPICE_INTERFACE_RECORD_MINOR >= 2)) - .volume_in = line_in_volume, -#endif -}; - -static struct audio_driver spice_audio_driver = { - .name = "spice", - .init = spice_audio_init, - .fini = spice_audio_fini, - .pcm_ops = &audio_callbacks, - .max_voices_out = 1, - .max_voices_in = 1, - .voice_size_out = sizeof (SpiceVoiceOut), - .voice_size_in = sizeof (SpiceVoiceIn), -}; -static void register_audio_spice(void) +static void audio_spice_class_init(ObjectClass *klass, const void *data) { - audio_driver_register(&spice_audio_driver); + AudioBackendClass *b = AUDIO_BACKEND_CLASS(klass); + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_CLASS(klass); + + audio_spice_parent_class = AUDIO_BACKEND_CLASS(object_class_get_parent(klass)); + + b->realize = spice_audio_realize; + k->max_voices_out = 1; + k->max_voices_in = 1; + k->voice_size_out = sizeof(SpiceVoiceOut); + k->voice_size_in = sizeof(SpiceVoiceIn); + + k->init_out = line_out_init; + k->fini_out = line_out_fini; + k->write = audio_generic_write; + k->buffer_get_free = line_out_get_free; + k->get_buffer_out = line_out_get_buffer; + k->put_buffer_out = line_out_put_buffer; + k->enable_out = line_out_enable; + k->volume_out = line_out_volume; + + k->init_in = line_in_init; + k->fini_in = line_in_fini; + k->read = line_in_read; + k->run_buffer_in = audio_generic_run_buffer_in; + k->enable_in = line_in_enable; + k->volume_in = line_in_volume; } -type_init(register_audio_spice); + +static const TypeInfo audio_types[] = { + { + .name = TYPE_AUDIO_SPICE, + .parent = TYPE_AUDIO_MIXENG_BACKEND, + .instance_size = sizeof(AudioSpice), + .class_init = audio_spice_class_init, + }, +}; + +DEFINE_TYPES(audio_types) +module_obj(TYPE_AUDIO_SPICE); module_dep("ui-spice-core"); diff --git a/audio/trace-events b/audio/trace-events index 7e3f1593c81e..222e73c7e88f 100644 --- a/audio/trace-events +++ b/audio/trace-events @@ -9,15 +9,39 @@ alsa_read_zero(long len) "Failed to read %ld frames (read zero)" alsa_xrun_out(void) "Recovering from playback xrun" alsa_xrun_in(void) "Recovering from capture xrun" alsa_resume_out(void) "Resuming suspended output stream" +alsa_info_params(int req_fmt, int obt_fmt, int req_channels, int obt_channels, int req_freq, int obt_freq) "format %d->%d, channels %d->%d, frequency %d->%d" +alsa_info_samples(int buffer_length, int period_length, long samples) "requested: buffer len %d, period len %d; obtained: %ld samples" +alsa_fini_out(void) "" +alsa_enable_out(bool enable) "enable=%d" +alsa_enable_in(bool enable) "enable=%d" # ossaudio.c oss_version(int version) "OSS version = 0x%x" +oss_out_params(int req_fmt, int obt_fmt, int req_channels, int obt_channels, int req_freq, int obt_freq, int req_nfrags, int obt_nfrags, int req_fragsize, int obt_fragsize) "fmt=%d->%d, channels %d->%d, freq=%d->%d, nfrags=%d->%d, fragsize=%d->%d" +oss_in_params(int req_fmt, int obt_fmt, int req_channels, int obt_channels, int req_freq, int obt_freq, int req_nfrags, int obt_nfrags, int req_fragsize, int obt_fragsize) "fmt=%d->%d, channels %d->%d, freq=%d->%d, nfrags=%d->%d, fragsize=%d->%d" +oss_fini_out(void) "" +oss_enable_out(bool enable) "enable=%d" +oss_enable_in(bool enable) "enable=%d" # dbusaudio.c dbus_audio_register(const char *s, const char *dir) "sender = %s, dir = %s" dbus_audio_put_buffer_out(size_t pos, size_t size) "buf_pos = %zu, buf_size = %zu" dbus_audio_read(size_t len) "len = %zu" +# dsoundaudio.c +dsound_clear_sample(void *p1, uint32_t blen1, uint32_t len1, void *p2, uint32_t blen2, uint32_t len2) "p1=%p, blen1=%u, len1=%u, p2=%p, blen2=%u, len2=%u" +dsound_wave_format(uint16_t wFormatTag, uint16_t nChannels, uint32_t nSamplesPerSec, uint32_t nAvgBytesPerSec, uint16_t nBlockAlign, uint16_t wBitsPerSample, uint16_t cbSize) "wFormatTag=%u, nChannels=%u, nSamplesPerSec=%u, nAvgBytesPerSec=%u, nBlockAlign=%u, wBitsPerSample=%u, cbSize=%u" +dsound_buffer_bytes(uint32_t caps_bytes, uint32_t desc_bytes) "caps_bytes=%u, desc_bytes=%u" + +# jackaudio.c +jack_client_recover(void) "attempting to reconnect to server" +jack_connect(const char *src, const char *dst) "connect %s -> %s" +jack_server_started(void) "server started" +jack_unique_name(const char *name) "unique name assigned %s" +jack_out_init(uint32_t freq, int buffersize) "output configured for %uHz (%d samples)" +jack_in_init(uint32_t freq, int buffersize) "input configured for %uHz (%d samples)" +jack_info(const char *msg) "%s" + # pwaudio.c pw_state_changed(int nodeid, const char *s) "node id: %d stream state: %s" pw_read(int32_t avail, uint32_t index, size_t len) "avail=%d index=%u len=%zu" @@ -26,7 +50,29 @@ pw_vol(const char *ret) "set volume: %s" pw_period(uint64_t quantum, uint32_t rate) "period =%" PRIu64 "/%u" pw_audio_init(void) "Initialize PipeWire context" +# audio-be.c +audio_be_set_active_in(void *sw, bool on) "sw=%p, on=%d" +audio_be_set_active_out(void *sw, bool on) "sw=%p, on=%d" + # audio.c audio_timer_start(int interval) "interval %d ms" audio_timer_stop(void) "" audio_timer_delayed(int interval) "interval %d ms" +audio_notify_capture(int cmd) "notification %d sent" +audio_capture_attach(const char *name, bool active) "capture %s active=%d" + +# audio-mixeng-be.c +audio_get_avail(const char *name, size_t live, uint32_t frontend_frames) "%s: get_avail live %zu frontend frames %u" +audio_run_poll(const char *msg, int64_t elapsed_us) "Elapsed since last %s: %" PRId64 " us" +audio_capture_free_sw(const char *name) "freeing %s" +audio_out_full(const char *name, size_t live) "%s is full %zu" +audio_sw_write(const char *name, size_t size, size_t written, size_t total_mixed) "%s: write size %zu written %zu total mixed %zu" +audio_get_free(const char *name, size_t live, size_t dead, uint32_t frontend_frames) "%s: get_free live %zu dead %zu frontend frames %u" +audio_out_disable(void) "Disabling voice" +audio_out_played(size_t played) "played=%zu" +audio_rate_reset(int64_t frames) "Resetting rate control (%" PRId64 " frames)" + +# audio_template.h +audio_open_out(const char *name, int freq, int nchannels, int fmt) "open %s, freq %d, nchannels %d, fmt %d" +audio_open_in(const char *name, int freq, int nchannels, int fmt) "open %s, freq %d, nchannels %d, fmt %d" +audio_voice_pair(const char *type, const char *name, const char *hw_fmt, int hw_freq, int hw_nchannels, const char *sw_fmt, int sw_freq, int sw_nchannels) "%s: name=%s hw=%s/%d/%d sw=%s/%d/%d" diff --git a/audio/wavaudio.c b/audio/wavaudio.c index 46460a5d5730..d247dd3ffe6c 100644 --- a/audio/wavaudio.c +++ b/audio/wavaudio.c @@ -24,11 +24,19 @@ #include "qemu/osdep.h" #include "qemu/module.h" +#include "qemu/error-report.h" #include "qemu/audio.h" +#include "qom/object.h" -#define AUDIO_CAP "wav" #include "audio_int.h" +#define TYPE_AUDIO_WAV "audio-wav" +OBJECT_DECLARE_SIMPLE_TYPE(AudioWav, AUDIO_WAV) + +struct AudioWav { + AudioMixengBackend parent_obj; +}; + typedef struct WAVVoiceOut { HWVoiceOut hw; FILE *f; @@ -43,8 +51,8 @@ static size_t wav_write_out(HWVoiceOut *hw, void *buf, size_t len) assert(bytes % hw->info.bytes_per_frame == 0); if (bytes && fwrite(buf, bytes, 1, wav->f) != 1) { - dolog("wav_write_out: fwrite of %" PRId64 " bytes failed\nReason: %s\n", - bytes, strerror(errno)); + error_report("wav: fwrite of %" PRId64 " bytes failed: %s", + bytes, strerror(errno)); } wav->total_samples += bytes / hw->info.bytes_per_frame; @@ -61,8 +69,7 @@ static void le_store (uint8_t *buf, uint32_t val, int len) } } -static int wav_init_out(HWVoiceOut *hw, struct audsettings *as, - void *drv_opaque) +static int wav_init_out(HWVoiceOut *hw, struct audsettings *as) { WAVVoiceOut *wav = (WAVVoiceOut *) hw; int bits16 = 0, stereo = 0; @@ -72,7 +79,7 @@ static int wav_init_out(HWVoiceOut *hw, struct audsettings *as, 0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04, 0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00 }; - Audiodev *dev = drv_opaque; + Audiodev *dev = hw->s->dev; AudiodevWavOptions *wopts = &dev->u.wav; struct audsettings wav_as = audiodev_to_audsettings(dev->u.wav.out); const char *wav_path = wopts->path ?: "qemu.wav"; @@ -91,11 +98,11 @@ static int wav_init_out(HWVoiceOut *hw, struct audsettings *as, case AUDIO_FORMAT_S32: case AUDIO_FORMAT_U32: - dolog ("WAVE files can not handle 32bit formats\n"); + error_report("wav: WAVE files cannot handle 32-bit formats"); return -1; case AUDIO_FORMAT_F32: - dolog("WAVE files can not handle float formats\n"); + error_report("wav: WAVE files cannot handle float formats"); return -1; default: @@ -104,7 +111,7 @@ static int wav_init_out(HWVoiceOut *hw, struct audsettings *as, hdr[34] = bits16 ? 0x10 : 0x08; - wav_as.endianness = 0; + wav_as.big_endian = false; audio_pcm_init_info (&hw->info, &wav_as); hw->samples = 1024; @@ -115,14 +122,13 @@ static int wav_init_out(HWVoiceOut *hw, struct audsettings *as, wav->f = fopen(wav_path, "wb"); if (!wav->f) { - dolog ("Failed to open wave file `%s'\nReason: %s\n", - wav_path, strerror(errno)); + error_report("wav: failed to open wave file '%s': %s", + wav_path, strerror(errno)); return -1; } if (fwrite (hdr, sizeof (hdr), 1, wav->f) != 1) { - dolog ("wav_init_out: failed to write header\nReason: %s\n", - strerror(errno)); + error_report("wav: failed to write header: %s", strerror(errno)); return -1; } @@ -146,30 +152,25 @@ static void wav_fini_out (HWVoiceOut *hw) le_store (dlen, datalen, 4); if (fseek (wav->f, 4, SEEK_SET)) { - dolog ("wav_fini_out: fseek to rlen failed\nReason: %s\n", - strerror(errno)); + error_report("wav: fseek to rlen failed: %s", strerror(errno)); goto doclose; } if (fwrite (rlen, 4, 1, wav->f) != 1) { - dolog ("wav_fini_out: failed to write rlen\nReason: %s\n", - strerror (errno)); + error_report("wav: failed to write rlen: %s", strerror(errno)); goto doclose; } if (fseek (wav->f, 32, SEEK_CUR)) { - dolog ("wav_fini_out: fseek to dlen failed\nReason: %s\n", - strerror (errno)); + error_report("wav: fseek to dlen failed: %s", strerror(errno)); goto doclose; } if (fwrite (dlen, 4, 1, wav->f) != 1) { - dolog ("wav_fini_out: failed to write dlen\nReaons: %s\n", - strerror (errno)); + error_report("wav: failed to write dlen: %s", strerror(errno)); goto doclose; } doclose: if (fclose (wav->f)) { - dolog ("wav_fini_out: fclose %p failed\nReason: %s\n", - wav->f, strerror (errno)); + error_report("wav: fclose failed: %s", strerror(errno)); } wav->f = NULL; } @@ -183,39 +184,31 @@ static void wav_enable_out(HWVoiceOut *hw, bool enable) } } -static void *wav_audio_init(Audiodev *dev, Error **errp) -{ - assert(dev->driver == AUDIODEV_DRIVER_WAV); - return dev; -} - -static void wav_audio_fini (void *opaque) +static void audio_wav_class_init(ObjectClass *klass, const void *data) { - ldebug ("wav_fini"); + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_CLASS(klass); + + k->max_voices_out = 1; + k->max_voices_in = 0; + k->voice_size_out = sizeof(WAVVoiceOut); + k->voice_size_in = 0; + + k->init_out = wav_init_out; + k->fini_out = wav_fini_out; + k->write = wav_write_out; + k->buffer_get_free = audio_generic_buffer_get_free; + k->run_buffer_out = audio_generic_run_buffer_out; + k->enable_out = wav_enable_out; } -static struct audio_pcm_ops wav_pcm_ops = { - .init_out = wav_init_out, - .fini_out = wav_fini_out, - .write = wav_write_out, - .buffer_get_free = audio_generic_buffer_get_free, - .run_buffer_out = audio_generic_run_buffer_out, - .enable_out = wav_enable_out, -}; - -static struct audio_driver wav_audio_driver = { - .name = "wav", - .init = wav_audio_init, - .fini = wav_audio_fini, - .pcm_ops = &wav_pcm_ops, - .max_voices_out = 1, - .max_voices_in = 0, - .voice_size_out = sizeof (WAVVoiceOut), - .voice_size_in = 0 +static const TypeInfo audio_types[] = { + { + .name = TYPE_AUDIO_WAV, + .parent = TYPE_AUDIO_MIXENG_BACKEND, + .instance_size = sizeof(AudioWav), + .class_init = audio_wav_class_init, + } }; -static void register_audio_wav(void) -{ - audio_driver_register(&wav_audio_driver); -} -type_init(register_audio_wav); +DEFINE_TYPES(audio_types) +module_obj(TYPE_AUDIO_WAV); diff --git a/audio/wavcapture.c b/audio/wavcapture.c index b33a38ff45ba..2dac94617104 100644 --- a/audio/wavcapture.c +++ b/audio/wavcapture.c @@ -1,4 +1,5 @@ #include "qemu/osdep.h" +#include "qemu/audio.h" #include "qemu/qemu-print.h" #include "qemu/error-report.h" #include "audio_int.h" @@ -10,6 +11,7 @@ typedef struct { int freq; int bits; int nchannels; + AudioBackend *audio_be; CaptureVoiceOut *cap; } WAVState; @@ -84,7 +86,7 @@ static void wav_capture_destroy (void *opaque) { WAVState *wav = opaque; - AUD_del_capture (wav->cap, wav); + audio_be_del_capture(wav->audio_be, wav->cap, wav); g_free (wav); } @@ -135,7 +137,7 @@ int wav_start_capture(AudioBackend *state, CaptureState *s, const char *path, as.freq = freq; as.nchannels = 1 << stereo; as.fmt = bits16 ? AUDIO_FORMAT_S16 : AUDIO_FORMAT_U8; - as.endianness = 0; + as.big_endian = false; ops.notify = wav_notify; ops.capture = wav_capture; @@ -159,6 +161,7 @@ int wav_start_capture(AudioBackend *state, CaptureState *s, const char *path, return -1; } + wav->audio_be = state; wav->path = g_strdup (path); wav->bits = bits; wav->nchannels = nchannels; @@ -169,7 +172,7 @@ int wav_start_capture(AudioBackend *state, CaptureState *s, const char *path, goto error_free; } - cap = AUD_add_capture(state, &as, &ops, wav); + cap = audio_be_add_capture(wav->audio_be, &as, &ops, wav); if (!cap) { error_report("Failed to add audio capture"); goto error_free; diff --git a/backends/iommufd.c b/backends/iommufd.c index 13822df82f08..acfab907c035 100644 --- a/backends/iommufd.c +++ b/backends/iommufd.c @@ -504,6 +504,37 @@ bool iommufd_backend_alloc_vdev(IOMMUFDBackend *be, uint32_t dev_id, return true; } +bool iommufd_backend_alloc_veventq(IOMMUFDBackend *be, uint32_t viommu_id, + uint32_t type, uint32_t depth, + uint32_t *out_veventq_id, + uint32_t *out_veventq_fd, Error **errp) +{ + int ret; + struct iommu_veventq_alloc alloc_veventq = { + .size = sizeof(alloc_veventq), + .flags = 0, + .type = type, + .veventq_depth = depth, + .viommu_id = viommu_id, + }; + + ret = ioctl(be->fd, IOMMU_VEVENTQ_ALLOC, &alloc_veventq); + + trace_iommufd_viommu_alloc_eventq(be->fd, viommu_id, type, + alloc_veventq.out_veventq_id, + alloc_veventq.out_veventq_fd, ret); + if (ret) { + error_setg_errno(errp, errno, "IOMMU_VEVENTQ_ALLOC failed"); + return false; + } + + g_assert(out_veventq_id); + g_assert(out_veventq_fd); + *out_veventq_id = alloc_veventq.out_veventq_id; + *out_veventq_fd = alloc_veventq.out_veventq_fd; + return true; +} + bool host_iommu_device_iommufd_attach_hwpt(HostIOMMUDeviceIOMMUFD *idev, uint32_t hwpt_id, Error **errp) { diff --git a/backends/trace-events b/backends/trace-events index 8dc64a20d3d9..b9365113e731 100644 --- a/backends/trace-events +++ b/backends/trace-events @@ -23,6 +23,7 @@ iommufd_backend_get_dirty_bitmap(int iommufd, uint32_t hwpt_id, uint64_t iova, u iommufd_backend_invalidate_cache(int iommufd, uint32_t id, uint32_t data_type, uint32_t entry_len, uint32_t entry_num, uint32_t done_num, uint64_t data_ptr, int ret) " iommufd=%d id=%u data_type=%u entry_len=%u entry_num=%u done_num=%u data_ptr=0x%"PRIx64" (%d)" iommufd_backend_alloc_viommu(int iommufd, uint32_t dev_id, uint32_t type, uint32_t hwpt_id, uint32_t viommu_id, int ret) " iommufd=%d type=%u dev_id=%u hwpt_id=%u viommu_id=%u (%d)" iommufd_backend_alloc_vdev(int iommufd, uint32_t dev_id, uint32_t viommu_id, uint64_t virt_id, uint32_t vdev_id, int ret) " iommufd=%d dev_id=%u viommu_id=%u virt_id=0x%"PRIx64" vdev_id=%u (%d)" +iommufd_viommu_alloc_eventq(int iommufd, uint32_t viommu_id, uint32_t type, uint32_t veventq_id, uint32_t veventq_fd, int ret) " iommufd=%d viommu_id=%u type=%u veventq_id=%u veventq_fd=%u (%d)" # igvm-cfg.c igvm_reset_enter(int type) "type=%u" diff --git a/block/curl.c b/block/curl.c index 4e77c93b46e6..6dccf002564e 100644 --- a/block/curl.c +++ b/block/curl.c @@ -324,17 +324,11 @@ curl_find_buf(BDRVCURLState *s, uint64_t start, uint64_t len, CURLAIOCB *acb) static void curl_multi_check_completion(BDRVCURLState *s) { int msgs_in_queue; + CURLMsg *msg; /* Try to find done transfers, so we can free the easy * handle again. */ - for (;;) { - CURLMsg *msg; - msg = curl_multi_info_read(s->multi, &msgs_in_queue); - - /* Quit when there are no more completions */ - if (!msg) - break; - + while ((msg = curl_multi_info_read(s->multi, &msgs_in_queue))) { if (msg->msg == CURLMSG_DONE) { int i; CURLState *state = NULL; @@ -397,7 +391,6 @@ static void curl_multi_check_completion(BDRVCURLState *s) } curl_clean_state(state); - break; } } } diff --git a/block/mirror.c b/block/mirror.c index bc982cb99a8f..fa1d975eb9f3 100644 --- a/block/mirror.c +++ b/block/mirror.c @@ -99,6 +99,7 @@ typedef struct MirrorBlockJob { typedef struct MirrorBDSOpaque { MirrorBlockJob *job; + BdrvDirtyBitmap *dirty_bitmap; bool stop; bool is_commit; } MirrorBDSOpaque; @@ -1675,9 +1676,11 @@ bdrv_mirror_top_do_write(BlockDriverState *bs, MirrorMethod method, abort(); } - if (!copy_to_target && s->job && s->job->dirty_bitmap) { - qatomic_set(&s->job->actively_synced, false); - bdrv_set_dirty_bitmap(s->job->dirty_bitmap, offset, bytes); + if (!copy_to_target) { + if (s->job) { + qatomic_set(&s->job->actively_synced, false); + } + bdrv_set_dirty_bitmap(s->dirty_bitmap, offset, bytes); } if (ret < 0) { @@ -1904,13 +1907,35 @@ static BlockJob *mirror_start_job( bdrv_drained_begin(bs); ret = bdrv_append(mirror_top_bs, bs, errp); - bdrv_drained_end(bs); - if (ret < 0) { + bdrv_drained_end(bs); + bdrv_unref(mirror_top_bs); + return NULL; + } + + bs_opaque->dirty_bitmap = bdrv_create_dirty_bitmap(mirror_top_bs, + granularity, + NULL, errp); + if (!bs_opaque->dirty_bitmap) { + bdrv_drained_end(bs); bdrv_unref(mirror_top_bs); return NULL; } + /* + * The mirror job doesn't use the block layer's dirty tracking because it + * needs to be able to switch seemlessly between background copy mode (which + * does need dirty tracking) and write blocking mode (which doesn't) and + * doing that would require draining the node. Instead, mirror_top_bs takes + * care of updating the dirty bitmap as appropriate. + * + * Note that write blocking mode only becomes effective after mirror_run() + * sets mirror_top_opaque->job (see should_copy_to_target()). Until then, + * we're still in background copy mode irrespective of @copy_mode. + */ + bdrv_disable_dirty_bitmap(bs_opaque->dirty_bitmap); + bdrv_drained_end(bs); + /* Make sure that the source is not resized while the job is running */ s = block_job_create(job_id, driver, NULL, mirror_top_bs, BLK_PERM_CONSISTENT_READ, @@ -2005,24 +2030,13 @@ static BlockJob *mirror_start_job( s->base_overlay = bdrv_find_overlay(bs, base); s->granularity = granularity; s->buf_size = ROUND_UP(buf_size, granularity); + s->dirty_bitmap = bs_opaque->dirty_bitmap; s->unmap = unmap; if (auto_complete) { s->should_complete = true; } bdrv_graph_rdunlock_main_loop(); - s->dirty_bitmap = bdrv_create_dirty_bitmap(s->mirror_top_bs, granularity, - NULL, errp); - if (!s->dirty_bitmap) { - goto fail; - } - - /* - * The dirty bitmap is set by bdrv_mirror_top_do_write() when not in active - * mode. - */ - bdrv_disable_dirty_bitmap(s->dirty_bitmap); - bdrv_graph_wrlock_drained(); ret = block_job_add_bdrv(&s->common, "source", bs, 0, BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE | @@ -2102,9 +2116,6 @@ static BlockJob *mirror_start_job( g_free(s->replaces); blk_unref(s->target); bs_opaque->job = NULL; - if (s->dirty_bitmap) { - bdrv_release_dirty_bitmap(s->dirty_bitmap); - } job_early_fail(&s->common.job); } @@ -2118,6 +2129,7 @@ static BlockJob *mirror_start_job( bdrv_graph_wrunlock(); bdrv_drained_end(bs); + bdrv_release_dirty_bitmap(bs_opaque->dirty_bitmap); bdrv_unref(mirror_top_bs); return NULL; diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c index 3391cee4d214..1fd28d59eb1d 100644 --- a/block/monitor/block-hmp-cmds.c +++ b/block/monitor/block-hmp-cmds.c @@ -422,7 +422,7 @@ void hmp_nbd_server_start(Monitor *mon, const QDict *qdict) /* Then try adding all block devices. If one fails, close all and * exit. */ - block_list = qmp_query_block(NULL); + block_list = qmp_query_block(true, true, NULL); for (info = block_list; info; info = info->next) { if (!info->value->inserted) { @@ -741,7 +741,7 @@ void hmp_info_block(Monitor *mon, const QDict *qdict) /* Print BlockBackend information */ if (!nodes) { - block_list = qmp_query_block(NULL); + block_list = qmp_query_block(false, false, NULL); } else { block_list = NULL; } diff --git a/block/nfs.c b/block/nfs.c index 1d3a34a30c98..b78f4f86e857 100644 --- a/block/nfs.c +++ b/block/nfs.c @@ -249,14 +249,15 @@ nfs_co_generic_cb(int ret, struct nfs_context *nfs, void *data, } /* - * Safe to call: nfs_service(), which called us, is only run from the FD - * handlers, never from the request coroutine. The request coroutine in - * turn will yield unconditionally. - * No need to release the lock, even if we directly enter the coroutine, as - * the lock is never re-taken after yielding. (Note: If we do enter the - * coroutine, @task will probably be dangling once aio_co_wake() returns.) + * Using aio_co_wake() here could re-enter the coroutine directly, while we + * still hold the mutex. The current request will not attempt to re-take + * the mutex, so that is fine; but if the same coroutine then goes on to + * submit another request, that new request will try to re-take the mutex, + * resulting in a deadlock. + * To prevent that, only schedule the coroutine so it will be entered later, + * with the mutex released. */ - aio_co_wake(task->co); + aio_co_schedule(qemu_coroutine_get_aio_context(task->co), task->co); } static int coroutine_fn nfs_co_preadv(BlockDriverState *bs, int64_t offset, @@ -716,8 +717,8 @@ nfs_get_allocated_file_size_cb(int ret, struct nfs_context *nfs, void *data, if (task->ret < 0) { error_report("NFS Error: %s", nfs_get_error(nfs)); } - /* Safe to call, see nfs_co_generic_cb() */ - aio_co_wake(task->co); + /* Must not use aio_co_wake(), see nfs_co_generic_cb() */ + aio_co_schedule(qemu_coroutine_get_aio_context(task->co), task->co); } static int64_t coroutine_fn nfs_co_get_allocated_file_size(BlockDriverState *bs) diff --git a/block/qapi.c b/block/qapi.c index 27e0ac6a3289..eabfbfc25853 100644 --- a/block/qapi.c +++ b/block/qapi.c @@ -456,7 +456,7 @@ void bdrv_query_block_graph_info(BlockDriverState *bs, /* @p_info will be set only on success. */ static void GRAPH_RDLOCK -bdrv_query_info(BlockBackend *blk, BlockInfo **p_info, Error **errp) +bdrv_query_info(BlockBackend *blk, bool flat, BlockInfo **p_info, Error **errp) { BlockInfo *info = g_malloc0(sizeof(*info)); BlockDriverState *bs = blk_bs(blk); @@ -488,7 +488,7 @@ bdrv_query_info(BlockBackend *blk, BlockInfo **p_info, Error **errp) } if (bs && bs->drv) { - info->inserted = bdrv_block_device_info(blk, bs, false, errp); + info->inserted = bdrv_block_device_info(blk, bs, flat, errp); if (info->inserted == NULL) { goto err; } @@ -698,7 +698,7 @@ bdrv_query_bds_stats(BlockDriverState *bs, bool blk_level) return s; } -BlockInfoList *qmp_query_block(Error **errp) +BlockInfoList *qmp_query_block(bool has_flat, bool flat, Error **errp) { BlockInfoList *head = NULL, **p_next = &head; BlockBackend *blk; @@ -714,7 +714,7 @@ BlockInfoList *qmp_query_block(Error **errp) } info = g_malloc0(sizeof(*info)); - bdrv_query_info(blk, &info->value, &local_err); + bdrv_query_info(blk, flat, &info->value, &local_err); if (local_err) { error_propagate(errp, local_err); g_free(info); diff --git a/block/qcow2.c b/block/qcow2.c index e29810d86a38..81fd299b4c7a 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -3991,6 +3991,8 @@ qcow2_co_create_opts(BlockDriver *drv, const char *filename, QemuOpts *opts, BlockDriverState *bs = NULL; BlockDriverState *data_bs = NULL; const char *val; + bool keep_data_file = false; + BlockdevCreateOptionsQcow2 *qcow2_opts; int ret; /* Only the keyval visitor supports the dotted syntax needed for @@ -4022,6 +4024,22 @@ qcow2_co_create_opts(BlockDriver *drv, const char *filename, QemuOpts *opts, qdict_put_str(qdict, BLOCK_OPT_COMPAT_LEVEL, "v3"); } + val = qdict_get_try_str(qdict, BLOCK_OPT_KEEP_DATA_FILE); + if (val) { + if (!strcmp(val, "on")) { + keep_data_file = true; + } else if (!strcmp(val, "off")) { + keep_data_file = false; + } else { + error_setg(errp, + "Invalid value '%s' for '%s': Must be 'on' or 'off'", + val, BLOCK_OPT_KEEP_DATA_FILE); + ret = -EINVAL; + goto finish; + } + qdict_del(qdict, BLOCK_OPT_KEEP_DATA_FILE); + } + /* Change legacy command line options into QMP ones */ static const QDictRenames opt_renames[] = { { BLOCK_OPT_BACKING_FILE, "backing-file" }, @@ -4058,9 +4076,11 @@ qcow2_co_create_opts(BlockDriver *drv, const char *filename, QemuOpts *opts, /* Create and open an external data file (protocol layer) */ val = qdict_get_try_str(qdict, BLOCK_OPT_DATA_FILE); if (val) { - ret = bdrv_co_create_file(val, opts, false, errp); - if (ret < 0) { - goto finish; + if (!keep_data_file) { + ret = bdrv_co_create_file(val, opts, false, errp); + if (ret < 0) { + goto finish; + } } data_bs = bdrv_co_open(val, NULL, NULL, @@ -4073,6 +4093,11 @@ qcow2_co_create_opts(BlockDriver *drv, const char *filename, QemuOpts *opts, qdict_del(qdict, BLOCK_OPT_DATA_FILE); qdict_put_str(qdict, "data-file", data_bs->node_name); + } else if (keep_data_file) { + error_setg(errp, "Must not use '%s=on' without '%s'", + BLOCK_OPT_KEEP_DATA_FILE, BLOCK_OPT_DATA_FILE); + ret = -EINVAL; + goto finish; } /* Set 'driver' and 'node' options */ @@ -4093,9 +4118,39 @@ qcow2_co_create_opts(BlockDriver *drv, const char *filename, QemuOpts *opts, goto finish; } + qcow2_opts = &create_options->u.qcow2; + + if (!qcow2_opts->has_preallocation) { + qcow2_opts->preallocation = PREALLOC_MODE_OFF; + } + + if (keep_data_file && + qcow2_opts->preallocation != PREALLOC_MODE_OFF && + qcow2_opts->preallocation != PREALLOC_MODE_METADATA) + { + error_setg(errp, "Preallocating more than only metadata would " + "overwrite the external data file's content and is " + "therefore incompatible with '%s=on'", + BLOCK_OPT_KEEP_DATA_FILE); + ret = -EINVAL; + goto finish; + } + + if (keep_data_file && + qcow2_opts->preallocation == PREALLOC_MODE_OFF && + !qcow2_opts->data_file_raw) + { + error_setg(errp, "'%s=on' requires '%s=metadata' or '%s=on', or the " + "file contents will not be visible", + BLOCK_OPT_KEEP_DATA_FILE, + BLOCK_OPT_PREALLOC, + BLOCK_OPT_DATA_FILE_RAW); + ret = -EINVAL; + goto finish; + } + /* Silently round up size */ - create_options->u.qcow2.size = ROUND_UP(create_options->u.qcow2.size, - BDRV_SECTOR_SIZE); + qcow2_opts->size = ROUND_UP(qcow2_opts->size, BDRV_SECTOR_SIZE); /* Create the qcow2 image (format layer) */ ret = qcow2_co_create(create_options, errp); @@ -4103,7 +4158,9 @@ qcow2_co_create_opts(BlockDriver *drv, const char *filename, QemuOpts *opts, if (ret < 0) { bdrv_graph_co_rdlock(); bdrv_co_delete_file_noerr(bs); - bdrv_co_delete_file_noerr(data_bs); + if (!keep_data_file) { + bdrv_co_delete_file_noerr(data_bs); + } bdrv_graph_co_rdunlock(); } else { ret = 0; @@ -6202,6 +6259,12 @@ static QemuOptsList qcow2_create_opts = { .help = "Compression method used for image cluster " \ "compression", \ .def_value_str = "zlib" \ + }, \ + { \ + .name = BLOCK_OPT_KEEP_DATA_FILE, \ + .type = QEMU_OPT_BOOL, \ + .help = "Assume the external data file already exists and " \ + "do not overwrite it" \ }, QCOW_COMMON_OPTIONS, { /* end of list */ } diff --git a/block/throttle-groups.c b/block/throttle-groups.c index 66fdce9a90e4..5329ff1fdb48 100644 --- a/block/throttle-groups.c +++ b/block/throttle-groups.c @@ -295,19 +295,15 @@ static bool throttle_group_schedule_timer(ThrottleGroupMember *tgm, /* Start the next pending I/O request for a ThrottleGroupMember. Return whether * any request was actually pending. * + * This assumes that tg->lock is held. + * * @tgm: the current ThrottleGroupMember * @direction: the ThrottleDirection */ static bool coroutine_fn throttle_group_co_restart_queue(ThrottleGroupMember *tgm, ThrottleDirection direction) { - bool ret; - - qemu_co_mutex_lock(&tgm->throttled_reqs_lock); - ret = qemu_co_queue_next(&tgm->throttled_reqs[direction]); - qemu_co_mutex_unlock(&tgm->throttled_reqs_lock); - - return ret; + return qemu_co_queue_next(&tgm->throttled_reqs[direction]); } /* Look for the next pending I/O request and schedule it. @@ -378,12 +374,8 @@ void coroutine_fn throttle_group_co_io_limits_intercept(ThrottleGroupMember *tgm /* Wait if there's a timer set or queued requests of this type */ if (must_wait || tgm->pending_reqs[direction]) { tgm->pending_reqs[direction]++; - qemu_mutex_unlock(&tg->lock); - qemu_co_mutex_lock(&tgm->throttled_reqs_lock); qemu_co_queue_wait(&tgm->throttled_reqs[direction], - &tgm->throttled_reqs_lock); - qemu_co_mutex_unlock(&tgm->throttled_reqs_lock); - qemu_mutex_lock(&tg->lock); + &tg->lock); tgm->pending_reqs[direction]--; } @@ -410,15 +402,15 @@ static void coroutine_fn throttle_group_restart_queue_entry(void *opaque) ThrottleDirection direction = data->direction; bool empty_queue; + qemu_mutex_lock(&tg->lock); empty_queue = !throttle_group_co_restart_queue(tgm, direction); /* If the request queue was empty then we have to take care of * scheduling the next one */ if (empty_queue) { - qemu_mutex_lock(&tg->lock); schedule_next_request(tgm, direction); - qemu_mutex_unlock(&tg->lock); } + qemu_mutex_unlock(&tg->lock); g_free(data); @@ -569,7 +561,6 @@ void throttle_group_register_tgm(ThrottleGroupMember *tgm, read_timer_cb, write_timer_cb, tgm); - qemu_co_mutex_init(&tgm->throttled_reqs_lock); } /* Unregister a ThrottleGroupMember from its group, removing it from the list, diff --git a/block/vmdk.c b/block/vmdk.c index 89e89cd10e35..cd8b4ec7c881 100644 --- a/block/vmdk.c +++ b/block/vmdk.c @@ -1951,10 +1951,10 @@ vmdk_read_extent(VmdkExtent *extent, int64_t cluster_offset, marker = (VmdkGrainMarker *)cluster_buf; compressed_data = marker->data; data_len = le32_to_cpu(marker->size); - } - if (!data_len || data_len > buf_bytes) { - ret = -EINVAL; - goto out; + if (!data_len || data_len > buf_bytes - sizeof(VmdkGrainMarker)) { + ret = -EINVAL; + goto out; + } } ret = uncompress(uncomp_buf, &buf_len, compressed_data, data_len); if (ret != Z_OK) { diff --git a/bsd-user/bsd-misc.c b/bsd-user/bsd-misc.c new file mode 100644 index 000000000000..3e1968718fb2 --- /dev/null +++ b/bsd-user/bsd-misc.c @@ -0,0 +1,192 @@ +/* + * BSD misc system call conversions routines + * + * Copyright (c) 2013 Stacey D. Son + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "qemu/osdep.h" + +#define _WANT_SEMUN +#include +#include +#include +#include +#include + +#include "qemu.h" +#include "qemu-bsd.h" + +/* + * BSD uuidgen(2) struct uuid conversion + */ +abi_long host_to_target_uuid(abi_ulong target_addr, struct uuid *host_uuid) +{ + struct target_uuid *target_uuid; + + if (!lock_user_struct(VERIFY_WRITE, target_uuid, target_addr, 0)) { + return -TARGET_EFAULT; + } + __put_user(host_uuid->time_low, &target_uuid->time_low); + __put_user(host_uuid->time_mid, &target_uuid->time_mid); + __put_user(host_uuid->time_hi_and_version, + &target_uuid->time_hi_and_version); + host_uuid->clock_seq_hi_and_reserved = + target_uuid->clock_seq_hi_and_reserved; + host_uuid->clock_seq_low = target_uuid->clock_seq_low; + memcpy(host_uuid->node, target_uuid->node, TARGET_UUID_NODE_LEN); + unlock_user_struct(target_uuid, target_addr, 1); + return 0; +} + +abi_long target_to_host_semarray(int semid, unsigned short **host_array, + abi_ulong target_addr) +{ + abi_long ret; + int nsems, i; + unsigned short *array; + union semun semun; + struct semid_ds semid_ds; + + semun.buf = &semid_ds; + ret = semctl(semid, 0, IPC_STAT, semun); + if (ret == -1) { + return get_errno(ret); + } + nsems = semid_ds.sem_nsems; + *host_array = g_new(unsigned short, nsems); + array = lock_user(VERIFY_READ, target_addr, + nsems * sizeof(unsigned short), 1); + if (array == NULL) { + free(*host_array); + return -TARGET_EFAULT; + } + for (i = 0; i < nsems; i++) { + __get_user((*host_array)[i], array + i); + } + unlock_user(array, target_addr, 0); + + return 0; +} + +abi_long host_to_target_semarray(int semid, abi_ulong target_addr, + unsigned short **host_arrayp) +{ + g_autofree unsigned short *host_array = *host_arrayp; + abi_long ret; + int nsems, i; + unsigned short *array; + union semun semun; + struct semid_ds semid_ds; + + semun.buf = &semid_ds; + + ret = semctl(semid, 0, IPC_STAT, semun); + if (ret == -1) { + return get_errno(ret); + } + + nsems = semid_ds.sem_nsems; + array = (unsigned short *)lock_user(VERIFY_WRITE, target_addr, + nsems * sizeof(unsigned short), 0); + if (array == NULL) { + return -TARGET_EFAULT; + } + for (i = 0; i < nsems; i++) { + __put_user(array[i], host_array + i); + } + unlock_user(array, target_addr, 1); + return 0; +} + +abi_long target_to_host_semid_ds(struct semid_ds *host_sd, + abi_ulong target_addr) +{ + struct target_semid_ds *target_sd; + + if (!lock_user_struct(VERIFY_READ, target_sd, target_addr, 1)) { + return -TARGET_EFAULT; + } + target_to_host_ipc_perm__locked(&host_sd->sem_perm, &target_sd->sem_perm); + /* sem_base is not used by kernel for IPC_STAT/IPC_SET */ + /* host_sd->sem_base = g2h_untagged(target_sd->sem_base); */ + __get_user(host_sd->sem_nsems, &target_sd->sem_nsems); + __get_user(host_sd->sem_otime, &target_sd->sem_otime); + __get_user(host_sd->sem_ctime, &target_sd->sem_ctime); + unlock_user_struct(target_sd, target_addr, 0); + return 0; +} + +abi_long host_to_target_semid_ds(abi_ulong target_addr, + struct semid_ds *host_sd) +{ + struct target_semid_ds *target_sd; + + if (!lock_user_struct(VERIFY_WRITE, target_sd, target_addr, 0)) { + return -TARGET_EFAULT; + } + host_to_target_ipc_perm__locked(&target_sd->sem_perm, + &host_sd->sem_perm); + /* sem_base is not used by kernel for IPC_STAT/IPC_SET */ + /* target_sd->sem_base = h2g((void *)host_sd->sem_base); */ + __put_user(target_sd->sem_nsems, &host_sd->sem_nsems); + __put_user(target_sd->sem_otime, &host_sd->sem_otime); + __put_user(target_sd->sem_ctime, &host_sd->sem_ctime); + unlock_user_struct(target_sd, target_addr, 1); + + return 0; +} + +abi_long target_to_host_msqid_ds(struct msqid_ds *host_md, + abi_ulong target_addr) +{ + struct target_msqid_ds *target_md; + + if (!lock_user_struct(VERIFY_READ, target_md, target_addr, 1)) { + return -TARGET_EFAULT; + } + + memset(host_md, 0, sizeof(struct msqid_ds)); + target_to_host_ipc_perm__locked(&host_md->msg_perm, + &target_md->msg_perm); + + /* msg_first and msg_last are not used by IPC_SET/IPC_STAT in kernel. */ + __get_user(host_md->msg_cbytes, &target_md->msg_cbytes); + __get_user(host_md->msg_qnum, &target_md->msg_qnum); + __get_user(host_md->msg_qbytes, &target_md->msg_qbytes); + __get_user(host_md->msg_lspid, &target_md->msg_lspid); + __get_user(host_md->msg_lrpid, &target_md->msg_lrpid); + __get_user(host_md->msg_stime, &target_md->msg_stime); + __get_user(host_md->msg_rtime, &target_md->msg_rtime); + __get_user(host_md->msg_ctime, &target_md->msg_ctime); + unlock_user_struct(target_md, target_addr, 0); + + return 0; +} + +abi_long host_to_target_msqid_ds(abi_ulong target_addr, + struct msqid_ds *host_md) +{ + struct target_msqid_ds *target_md; + + if (!lock_user_struct(VERIFY_WRITE, target_md, target_addr, 0)) { + return -TARGET_EFAULT; + } + + memset(target_md, 0, sizeof(struct target_msqid_ds)); + host_to_target_ipc_perm__locked(&target_md->msg_perm, + &host_md->msg_perm); + + /* msg_first and msg_last are not used by IPC_SET/IPC_STAT in kernel. */ + __put_user(target_md->msg_cbytes, &host_md->msg_cbytes); + __put_user(target_md->msg_qnum, &host_md->msg_qnum); + __put_user(target_md->msg_qbytes, &host_md->msg_qbytes); + __put_user(target_md->msg_lspid, &host_md->msg_lspid); + __put_user(target_md->msg_lrpid, &host_md->msg_lrpid); + __put_user(target_md->msg_stime, &host_md->msg_stime); + __put_user(target_md->msg_rtime, &host_md->msg_rtime); + __put_user(target_md->msg_ctime, &host_md->msg_ctime); + unlock_user_struct(target_md, target_addr, 1); + + return 0; +} diff --git a/bsd-user/bsd-misc.h b/bsd-user/bsd-misc.h new file mode 100644 index 000000000000..13e40d9cde42 --- /dev/null +++ b/bsd-user/bsd-misc.h @@ -0,0 +1,366 @@ +/* + * miscellaneous BSD system call shims + * + * Copyright (c) 2013 Stacey D. Son + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef BSD_MISC_H +#define BSD_MISC_H + +#include +#include +#include +#include +#include + +#include "qemu-bsd.h" + +static int bsd_msgmax; + +/* quotactl(2) */ +static inline abi_long do_bsd_quotactl(abi_ulong path, abi_long cmd, + __unused abi_ulong target_addr) +{ + qemu_log("qemu: Unsupported syscall quotactl()\n"); + return -TARGET_ENOSYS; +} + +/* reboot(2) */ +static inline abi_long do_bsd_reboot(abi_long how) +{ + qemu_log("qemu: Unsupported syscall reboot()\n"); + return -TARGET_ENOSYS; +} + +/* uuidgen(2) */ +static inline abi_long do_bsd_uuidgen(abi_ulong target_addr, int count) +{ + int i; + abi_long ret; + g_autofree struct uuid *host_uuid = NULL; + + /* + * 2048 is the kernel limit, but there's no #define for it, nor any sysctl + * to query it. + */ + if (count < 1 || count > 2048) { + return -TARGET_EINVAL; + } + + host_uuid = g_malloc(count * sizeof(struct uuid)); + + ret = get_errno(uuidgen(host_uuid, count)); + if (is_error(ret)) { + goto out; + } + for (i = 0; i < count; i++) { + ret = host_to_target_uuid(target_addr + + (abi_ulong)(sizeof(struct target_uuid) * i), &host_uuid[i]); + if (is_error(ret)) { + break; + } + } + +out: + return ret; +} + +/* + * System V Semaphores + */ + +/* semget(2) */ +static inline abi_long do_bsd_semget(abi_long key, int nsems, + int target_flags) +{ + return get_errno(semget(key, nsems, + target_to_host_bitmask(target_flags, ipc_flags_tbl))); +} + +/* semop(2) */ +static inline abi_long do_bsd_semop(int semid, abi_long ptr, unsigned nsops) +{ + g_autofree struct sembuf *sops = g_malloc(nsops * sizeof(struct sembuf)); + struct target_sembuf *target_sembuf; + int i; + + target_sembuf = lock_user(VERIFY_READ, ptr, + nsops * sizeof(struct target_sembuf), 1); + if (target_sembuf == NULL) { + return -TARGET_EFAULT; + } + for (i = 0; i < nsops; i++) { + __get_user(sops[i].sem_num, &target_sembuf[i].sem_num); + __get_user(sops[i].sem_op, &target_sembuf[i].sem_op); + __get_user(sops[i].sem_flg, &target_sembuf[i].sem_flg); + } + unlock_user(target_sembuf, ptr, 0); + + return semop(semid, sops, nsops); +} + +/* __semctl(2) */ +static inline abi_long do_bsd___semctl(int semid, int semnum, int target_cmd, + abi_ptr un_ptr) +{ + void *target_un; + union semun arg; + struct semid_ds dsarg; + unsigned short *array = NULL; + int host_cmd; + abi_long ret = 0; + abi_ulong target_array, target_buffer; + + switch (target_cmd) { + case TARGET_GETVAL: + host_cmd = GETVAL; + break; + + case TARGET_SETVAL: + host_cmd = SETVAL; + break; + + case TARGET_GETALL: + host_cmd = GETALL; + break; + + case TARGET_SETALL: + host_cmd = SETALL; + break; + + case TARGET_IPC_STAT: + host_cmd = IPC_STAT; + break; + + case TARGET_IPC_SET: + host_cmd = IPC_SET; + break; + + case TARGET_IPC_RMID: + host_cmd = IPC_RMID; + break; + + case TARGET_GETPID: + host_cmd = GETPID; + break; + + case TARGET_GETNCNT: + host_cmd = GETNCNT; + break; + + case TARGET_GETZCNT: + host_cmd = GETZCNT; + break; + + default: + return -TARGET_EINVAL; + } + + /* + * Unlike Linux and the semctl system call, we take a pointer + * to the union arg here. + */ + target_un = lock_user(VERIFY_READ, un_ptr, sizeof(union target_semun), 1); + + switch (host_cmd) { + case GETVAL: + case SETVAL: + __get_user(arg.val, (abi_int *)target_un); + ret = get_errno(semctl(semid, semnum, host_cmd, arg)); + break; + + case GETALL: + case SETALL: + __get_user(target_array, (abi_ulong *)target_un); + ret = target_to_host_semarray(semid, &array, target_array); + if (is_error(ret)) { + goto out; + } + arg.array = array; + ret = get_errno(semctl(semid, semnum, host_cmd, arg)); + if (!is_error(ret)) { + ret = host_to_target_semarray(semid, target_array, &array); + } + break; + + case IPC_STAT: + case IPC_SET: + __get_user(target_buffer, (abi_ulong *)target_un); + ret = target_to_host_semid_ds(&dsarg, target_buffer); + if (is_error(ret)) { + goto out; + } + arg.buf = &dsarg; + ret = get_errno(semctl(semid, semnum, host_cmd, arg)); + if (!is_error(ret)) { + ret = host_to_target_semid_ds(target_buffer, &dsarg); + } + break; + + case IPC_RMID: + case GETPID: + case GETNCNT: + case GETZCNT: + ret = get_errno(semctl(semid, semnum, host_cmd, NULL)); + break; + + default: + ret = -TARGET_EINVAL; + break; + } +out: + unlock_user(target_un, un_ptr, 1); + return ret; +} + +/* msgctl(2) */ +static inline abi_long do_bsd_msgctl(int msgid, int target_cmd, abi_long ptr) +{ + struct msqid_ds dsarg; + abi_long ret = -TARGET_EINVAL; + int host_cmd; + + switch (target_cmd) { + case TARGET_IPC_STAT: + host_cmd = IPC_STAT; + break; + + case TARGET_IPC_SET: + host_cmd = IPC_SET; + break; + + case TARGET_IPC_RMID: + host_cmd = IPC_RMID; + break; + + default: + return -TARGET_EINVAL; + } + + switch (host_cmd) { + case IPC_STAT: + case IPC_SET: + if (target_to_host_msqid_ds(&dsarg, ptr)) { + return -TARGET_EFAULT; + } + ret = get_errno(msgctl(msgid, host_cmd, &dsarg)); + if (host_to_target_msqid_ds(ptr, &dsarg)) { + return -TARGET_EFAULT; + } + break; + + case IPC_RMID: + ret = get_errno(msgctl(msgid, host_cmd, NULL)); + break; + + default: + ret = -TARGET_EINVAL; + break; + } + return ret; +} + +struct kern_mymsg { + long mtype; + char mtext[1]; +}; + +static inline abi_long bsd_validate_msgsz(abi_ulong msgsz) +{ + /* Fetch msgmax the first time we need it. */ + if (bsd_msgmax == 0) { + size_t len = sizeof(bsd_msgmax); + + if (sysctlbyname("kern.ipc.msgmax", &bsd_msgmax, &len, NULL, 0) == -1) { + return -TARGET_EINVAL; + } + } + + if (msgsz > bsd_msgmax) { + return -TARGET_EINVAL; + } + return 0; +} + +/* msgsnd(2) */ +static inline abi_long do_bsd_msgsnd(int msqid, abi_long msgp, + abi_ulong msgsz, int msgflg) +{ + struct target_msgbuf *target_mb; + struct kern_mymsg *host_mb; + abi_long ret; + + ret = bsd_validate_msgsz(msgsz); + if (is_error(ret)) { + return ret; + } + if (!lock_user_struct(VERIFY_READ, target_mb, msgp, 0)) { + return -TARGET_EFAULT; + } + host_mb = g_malloc(msgsz + sizeof(long)); + host_mb->mtype = (abi_long) tswapal(target_mb->mtype); + memcpy(host_mb->mtext, target_mb->mtext, msgsz); + ret = get_errno(msgsnd(msqid, host_mb, msgsz, msgflg)); + g_free(host_mb); + unlock_user_struct(target_mb, msgp, 0); + + return ret; +} + +/* msgget(2) */ +static inline abi_long do_bsd_msgget(abi_long key, abi_long msgflag) +{ + abi_long ret; + + ret = get_errno(msgget(key, msgflag)); + return ret; +} + +/* msgrcv(2) */ +static inline abi_long do_bsd_msgrcv(int msqid, abi_long msgp, + abi_ulong msgsz, abi_long msgtyp, int msgflg) +{ + struct target_msgbuf *target_mb = NULL; + char *target_mtext; + struct kern_mymsg *host_mb; + abi_long ret = 0; + + ret = bsd_validate_msgsz(msgsz); + if (is_error(ret)) { + return ret; + } + if (!lock_user_struct(VERIFY_WRITE, target_mb, msgp, 0)) { + return -TARGET_EFAULT; + } + host_mb = g_malloc(msgsz + sizeof(long)); + ret = get_errno(msgrcv(msqid, host_mb, msgsz, tswapal(msgtyp), msgflg)); + if (ret > 0) { + abi_ulong target_mtext_addr = msgp + sizeof(abi_ulong); + target_mtext = lock_user(VERIFY_WRITE, target_mtext_addr, ret, 0); + if (target_mtext == NULL) { + ret = -TARGET_EFAULT; + goto end; + } + memcpy(target_mb->mtext, host_mb->mtext, ret); + unlock_user(target_mtext, target_mtext_addr, ret); + } + if (!is_error(ret)) { + target_mb->mtype = tswapal(host_mb->mtype); + } +end: + if (target_mb != NULL) { + unlock_user_struct(target_mb, msgp, 1); + } + g_free(host_mb); + return ret; +} + +/* getdtablesize(2) */ +static inline abi_long do_bsd_getdtablesize(void) +{ + return get_errno(getdtablesize()); +} + +#endif /* BSD_MISC_H */ diff --git a/bsd-user/freebsd/os-syscall.c b/bsd-user/freebsd/os-syscall.c index 4c1191b8f476..85e5db19a366 100644 --- a/bsd-user/freebsd/os-syscall.c +++ b/bsd-user/freebsd/os-syscall.c @@ -36,6 +36,7 @@ #include "bsd-file.h" #include "bsd-mem.h" #include "bsd-proc.h" +#include "bsd-misc.h" /* BSD dependent syscall shims */ #include "os-stat.h" @@ -878,6 +879,41 @@ static abi_long freebsd_syscall(void *cpu_env, int num, abi_long arg1, ret = do_bsd_shmdt(arg1); break; + /* + * System V Semaphores + */ + case TARGET_FREEBSD_NR_semget: /* semget(2) */ + ret = do_bsd_semget(arg1, arg2, arg3); + break; + + case TARGET_FREEBSD_NR_semop: /* semop(2) */ + ret = do_bsd_semop(arg1, arg2, arg3); + break; + + case TARGET_FREEBSD_NR___semctl: { /* __semctl() undocumented */ + ret = do_bsd___semctl(arg1, arg2, arg3, arg4); + break; + } + + /* + * System V Messages + */ + case TARGET_FREEBSD_NR_msgctl: /* msgctl(2) */ + ret = do_bsd_msgctl(arg1, arg2, arg3); + break; + + case TARGET_FREEBSD_NR_msgget: /* msgget(2) */ + ret = do_bsd_msgget(arg1, arg2); + break; + + case TARGET_FREEBSD_NR_msgsnd: /* msgsnd(2) */ + ret = do_bsd_msgsnd(arg1, arg2, arg3, arg4); + break; + + case TARGET_FREEBSD_NR_msgrcv: /* msgrcv(2) */ + ret = do_bsd_msgrcv(arg1, arg2, arg3, arg4, arg5); + break; + case TARGET_FREEBSD_NR_freebsd11_vadvise: ret = do_bsd_vadvise(); break; @@ -897,6 +933,22 @@ static abi_long freebsd_syscall(void *cpu_env, int num, abi_long arg1, ret = do_obreak(arg1); break; + case TARGET_FREEBSD_NR_quotactl: /* quotactl(2) */ + ret = do_bsd_quotactl(arg1, arg2, arg3); + break; + + case TARGET_FREEBSD_NR_reboot: /* reboot(2) */ + ret = do_bsd_reboot(arg1); + break; + + case TARGET_FREEBSD_NR_uuidgen: /* uuidgen(2) */ + ret = do_bsd_uuidgen(arg1, arg2); + break; + + case TARGET_FREEBSD_NR_getdtablesize: /* getdtablesize(2) */ + ret = do_bsd_getdtablesize(); + break; + /* * sys{ctl, arch, call} */ diff --git a/bsd-user/meson.build b/bsd-user/meson.build index 37b7cd6de87c..00428fc2f8d7 100644 --- a/bsd-user/meson.build +++ b/bsd-user/meson.build @@ -8,6 +8,7 @@ common_user_inc += include_directories('include') bsd_user_ss.add(files( 'bsd-mem.c', + 'bsd-misc.c', 'bsd-proc.c', 'bsdload.c', 'elfload.c', diff --git a/bsd-user/mmap.c b/bsd-user/mmap.c index 24ba1728eb55..fe77eceb4856 100644 --- a/bsd-user/mmap.c +++ b/bsd-user/mmap.c @@ -258,12 +258,14 @@ abi_ulong mmap_next_start = TASK_UNMAPPED_BASE; static abi_ulong mmap_find_vma_reserved(abi_ulong start, abi_ulong size, abi_ulong alignment) { - abi_ulong ret; + abi_ulong ret = -1; - ret = page_find_range_empty(start, reserved_va, size, alignment); + if (start <= reserved_va) { + ret = page_find_range_empty(start, reserved_va, size, alignment); + } if (ret == -1 && start > TARGET_PAGE_SIZE) { /* Restart at the beginning of the address space. */ - ret = page_find_range_empty(TARGET_PAGE_SIZE, start - 1, + ret = page_find_range_empty(TARGET_PAGE_SIZE, MIN(start - 1, reserved_va), size, alignment); } diff --git a/bsd-user/netbsd/host-os.h b/bsd-user/netbsd/host-os.h deleted file mode 100644 index 7c14b1ea7801..000000000000 --- a/bsd-user/netbsd/host-os.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * NetBSD host dependent code and definitions - * - * Copyright (c) 2013 Stacey D. Son - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - */ - -#ifndef HOST_OS_H -#define HOST_OS_H - -#define HOST_DEFAULT_BSD_TYPE target_netbsd - -#endif /* HOST_OS_H */ diff --git a/bsd-user/netbsd/os-strace.h b/bsd-user/netbsd/os-strace.h deleted file mode 100644 index 70cf51d63a1b..000000000000 --- a/bsd-user/netbsd/os-strace.h +++ /dev/null @@ -1 +0,0 @@ -/* XXX NetBSD dependent strace print functions */ diff --git a/bsd-user/netbsd/strace.list b/bsd-user/netbsd/strace.list deleted file mode 100644 index 5609d70d65ad..000000000000 --- a/bsd-user/netbsd/strace.list +++ /dev/null @@ -1,145 +0,0 @@ -{ TARGET_NETBSD_NR___getcwd, "__getcwd", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR___syscall, "__syscall", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR___sysctl, "__sysctl", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_accept, "accept", "%s(%d,%#x,%#x)", NULL, NULL }, -{ TARGET_NETBSD_NR_access, "access", "%s(\"%s\",%#o)", NULL, NULL }, -{ TARGET_NETBSD_NR_acct, "acct", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_adjtime, "adjtime", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_bind, "bind", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_break, "break", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_chdir, "chdir", "%s(\"%s\")", NULL, NULL }, -{ TARGET_NETBSD_NR_chflags, "chflags", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_chmod, "chmod", "%s(\"%s\",%#o)", NULL, NULL }, -{ TARGET_NETBSD_NR_chown, "chown", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_chroot, "chroot", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_clock_getres, "clock_getres", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_clock_gettime, "clock_gettime", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_clock_settime, "clock_settime", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_close, "close", "%s(%d)", NULL, NULL }, -{ TARGET_NETBSD_NR_connect, "connect", "%s(%d,%#x,%d)", NULL, NULL }, -{ TARGET_NETBSD_NR_dup, "dup", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_dup2, "dup2", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_execve, "execve", NULL, print_execve, NULL }, -{ TARGET_NETBSD_NR_exit, "exit", "%s(%d)\n", NULL, NULL }, -{ TARGET_NETBSD_NR_fchdir, "fchdir", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_fchflags, "fchflags", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_fchmod, "fchmod", "%s(%d,%#o)", NULL, NULL }, -{ TARGET_NETBSD_NR_fchown, "fchown", "%s(\"%s\",%d,%d)", NULL, NULL }, -{ TARGET_NETBSD_NR_fcntl, "fcntl", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_flock, "flock", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_fork, "fork", "%s()", NULL, NULL }, -{ TARGET_NETBSD_NR_fpathconf, "fpathconf", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_fsync, "fsync", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_ftruncate, "ftruncate", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_futimes, "futimes", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_getegid, "getegid", "%s()", NULL, NULL }, -{ TARGET_NETBSD_NR_geteuid, "geteuid", "%s()", NULL, NULL }, -{ TARGET_NETBSD_NR_getgid, "getgid", "%s()", NULL, NULL }, -{ TARGET_NETBSD_NR_getgroups, "getgroups", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_getitimer, "getitimer", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_getpeername, "getpeername", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_getpgid, "getpgid", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_getpgrp, "getpgrp", "%s()", NULL, NULL }, -{ TARGET_NETBSD_NR_getpid, "getpid", "%s()", NULL, NULL }, -{ TARGET_NETBSD_NR_getppid, "getppid", "%s()", NULL, NULL }, -{ TARGET_NETBSD_NR_getpriority, "getpriority", "%s(%#x,%#x)", NULL, NULL }, -{ TARGET_NETBSD_NR_getrlimit, "getrlimit", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_getrusage, "getrusage", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_getsid, "getsid", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_getsockname, "getsockname", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_getsockopt, "getsockopt", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_gettimeofday, "gettimeofday", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_getuid, "getuid", "%s()", NULL, NULL }, -{ TARGET_NETBSD_NR_ioctl, "ioctl", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_issetugid, "issetugid", "%s()", NULL, NULL }, -{ TARGET_NETBSD_NR_kevent, "kevent", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_kill, "kill", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_kqueue, "kqueue", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_ktrace, "ktrace", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_lchown, "lchown", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_lfs_bmapv, "lfs_bmapv", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_lfs_markv, "lfs_markv", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_lfs_segclean, "lfs_segclean", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_lfs_segwait, "lfs_segwait", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_link, "link", "%s(\"%s\",\"%s\")", NULL, NULL }, -{ TARGET_NETBSD_NR_listen, "listen", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_lseek, "lseek", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_madvise, "madvise", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_mincore, "mincore", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_minherit, "minherit", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_mkdir, "mkdir", "%s(\"%s\",%#o)", NULL, NULL }, -{ TARGET_NETBSD_NR_mkfifo, "mkfifo", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_mknod, "mknod", "%s(\"%s\",%#o,%#x)", NULL, NULL }, -{ TARGET_NETBSD_NR_mlock, "mlock", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_mlockall, "mlockall", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_mmap, "mmap", NULL, NULL, print_syscall_ret_addr }, -{ TARGET_NETBSD_NR_mprotect, "mprotect", "%s(%#x,%#x,%d)", NULL, NULL }, -{ TARGET_NETBSD_NR_msgget, "msgget", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_msgrcv, "msgrcv", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_msgsnd, "msgsnd", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_munlock, "munlock", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_munlockall, "munlockall", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_munmap, "munmap", "%s(%p,%d)", NULL, NULL }, -{ TARGET_NETBSD_NR_nanosleep, "nanosleep", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_nfssvc, "nfssvc", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_open, "open", "%s(\"%s\",%#x,%#o)", NULL, NULL }, -{ TARGET_NETBSD_NR_pathconf, "pathconf", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_pipe, "pipe", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_poll, "poll", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_pread, "pread", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_preadv, "preadv", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_profil, "profil", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_ptrace, "ptrace", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_pwrite, "pwrite", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_pwritev, "pwritev", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_quotactl, "quotactl", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_read, "read", "%s(%d,%#x,%d)", NULL, NULL }, -{ TARGET_NETBSD_NR_readlink, "readlink", "%s(\"%s\",%p,%d)", NULL, NULL }, -{ TARGET_NETBSD_NR_readv, "readv", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_reboot, "reboot", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_recvfrom, "recvfrom", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_recvmsg, "recvmsg", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_rename, "rename", "%s(\"%s\",\"%s\")", NULL, NULL }, -{ TARGET_NETBSD_NR_revoke, "revoke", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_rmdir, "rmdir", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_sbrk, "sbrk", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_sched_yield, "sched_yield", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_select, "select", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_semget, "semget", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_semop, "semop", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_sendmsg, "sendmsg", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_sendto, "sendto", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_setegid, "setegid", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_seteuid, "seteuid", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_setgid, "setgid", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_setgroups, "setgroups", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_setitimer, "setitimer", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_setpgid, "setpgid", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_setpriority, "setpriority", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_setregid, "setregid", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_setreuid, "setreuid", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_setrlimit, "setrlimit", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_setsid, "setsid", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_setsockopt, "setsockopt", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_settimeofday, "settimeofday", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_setuid, "setuid", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_shmat, "shmat", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_shmdt, "shmdt", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_shmget, "shmget", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_shutdown, "shutdown", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_socketpair, "socketpair", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_sstk, "sstk", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_swapctl, "swapctl", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_symlink, "symlink", "%s(\"%s\",\"%s\")", NULL, NULL }, -{ TARGET_NETBSD_NR_sync, "sync", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_sysarch, "sysarch", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_syscall, "syscall", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_truncate, "truncate", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_umask, "umask", "%s(%#o)", NULL, NULL }, -{ TARGET_NETBSD_NR_unlink, "unlink", "%s(\"%s\")", NULL, NULL }, -{ TARGET_NETBSD_NR_unmount, "unmount", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_utimes, "utimes", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_vfork, "vfork", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_wait4, "wait4", NULL, NULL, NULL }, -{ TARGET_NETBSD_NR_write, "write", "%s(%d,%#x,%d)", NULL, NULL }, -{ TARGET_NETBSD_NR_writev, "writev", "%s(%d,%p,%#x)", NULL, NULL }, diff --git a/bsd-user/netbsd/syscall_nr.h b/bsd-user/netbsd/syscall_nr.h deleted file mode 100644 index 2e9ab5378e98..000000000000 --- a/bsd-user/netbsd/syscall_nr.h +++ /dev/null @@ -1,373 +0,0 @@ -/* $NetBSD: syscall.h,v 1.215 2008/06/17 16:07:57 tsutsui Exp $ */ - -/* - * System call numbers. - * - * created from NetBSD: syscalls.master,v 1.204 2008/06/17 16:05:23 tsutsui Exp - */ - -#define TARGET_NETBSD_NR_syscall 0 -#define TARGET_NETBSD_NR_exit 1 -#define TARGET_NETBSD_NR_fork 2 -#define TARGET_NETBSD_NR_read 3 -#define TARGET_NETBSD_NR_write 4 -#define TARGET_NETBSD_NR_open 5 -#define TARGET_NETBSD_NR_close 6 -#define TARGET_NETBSD_NR_wait4 7 -#define TARGET_NETBSD_NR_compat_43_ocreat 8 -#define TARGET_NETBSD_NR_link 9 -#define TARGET_NETBSD_NR_unlink 10 -#define TARGET_NETBSD_NR_chdir 12 -#define TARGET_NETBSD_NR_fchdir 13 -#define TARGET_NETBSD_NR_mknod 14 -#define TARGET_NETBSD_NR_chmod 15 -#define TARGET_NETBSD_NR_chown 16 -#define TARGET_NETBSD_NR_break 17 -#define TARGET_NETBSD_NR_compat_20_getfsstat 18 -#define TARGET_NETBSD_NR_compat_43_olseek 19 -#define TARGET_NETBSD_NR_getpid 20 -#define TARGET_NETBSD_NR_getpid 20 -#define TARGET_NETBSD_NR_compat_40_mount 21 -#define TARGET_NETBSD_NR_unmount 22 -#define TARGET_NETBSD_NR_setuid 23 -#define TARGET_NETBSD_NR_getuid 24 -#define TARGET_NETBSD_NR_getuid 24 -#define TARGET_NETBSD_NR_geteuid 25 -#define TARGET_NETBSD_NR_ptrace 26 -#define TARGET_NETBSD_NR_recvmsg 27 -#define TARGET_NETBSD_NR_sendmsg 28 -#define TARGET_NETBSD_NR_recvfrom 29 -#define TARGET_NETBSD_NR_accept 30 -#define TARGET_NETBSD_NR_getpeername 31 -#define TARGET_NETBSD_NR_getsockname 32 -#define TARGET_NETBSD_NR_access 33 -#define TARGET_NETBSD_NR_chflags 34 -#define TARGET_NETBSD_NR_fchflags 35 -#define TARGET_NETBSD_NR_sync 36 -#define TARGET_NETBSD_NR_kill 37 -#define TARGET_NETBSD_NR_compat_43_stat43 38 -#define TARGET_NETBSD_NR_getppid 39 -#define TARGET_NETBSD_NR_compat_43_lstat43 40 -#define TARGET_NETBSD_NR_dup 41 -#define TARGET_NETBSD_NR_pipe 42 -#define TARGET_NETBSD_NR_getegid 43 -#define TARGET_NETBSD_NR_profil 44 -#define TARGET_NETBSD_NR_ktrace 45 -#define TARGET_NETBSD_NR_compat_13_sigaction13 46 -#define TARGET_NETBSD_NR_getgid 47 -#define TARGET_NETBSD_NR_getgid 47 -#define TARGET_NETBSD_NR_compat_13_sigprocmask13 48 -#define TARGET_NETBSD_NR___getlogin 49 -#define TARGET_NETBSD_NR___setlogin 50 -#define TARGET_NETBSD_NR_acct 51 -#define TARGET_NETBSD_NR_compat_13_sigpending13 52 -#define TARGET_NETBSD_NR_compat_13_sigaltstack13 53 -#define TARGET_NETBSD_NR_ioctl 54 -#define TARGET_NETBSD_NR_compat_12_oreboot 55 -#define TARGET_NETBSD_NR_revoke 56 -#define TARGET_NETBSD_NR_symlink 57 -#define TARGET_NETBSD_NR_readlink 58 -#define TARGET_NETBSD_NR_execve 59 -#define TARGET_NETBSD_NR_umask 60 -#define TARGET_NETBSD_NR_chroot 61 -#define TARGET_NETBSD_NR_compat_43_fstat43 62 -#define TARGET_NETBSD_NR_compat_43_ogetkerninfo 63 -#define TARGET_NETBSD_NR_compat_43_ogetpagesize 64 -#define TARGET_NETBSD_NR_compat_12_msync 65 -#define TARGET_NETBSD_NR_vfork 66 -#define TARGET_NETBSD_NR_sbrk 69 -#define TARGET_NETBSD_NR_sstk 70 -#define TARGET_NETBSD_NR_compat_43_ommap 71 -#define TARGET_NETBSD_NR_vadvise 72 -#define TARGET_NETBSD_NR_munmap 73 -#define TARGET_NETBSD_NR_mprotect 74 -#define TARGET_NETBSD_NR_madvise 75 -#define TARGET_NETBSD_NR_mincore 78 -#define TARGET_NETBSD_NR_getgroups 79 -#define TARGET_NETBSD_NR_setgroups 80 -#define TARGET_NETBSD_NR_getpgrp 81 -#define TARGET_NETBSD_NR_setpgid 82 -#define TARGET_NETBSD_NR_setitimer 83 -#define TARGET_NETBSD_NR_compat_43_owait 84 -#define TARGET_NETBSD_NR_compat_12_oswapon 85 -#define TARGET_NETBSD_NR_getitimer 86 -#define TARGET_NETBSD_NR_compat_43_ogethostname 87 -#define TARGET_NETBSD_NR_compat_43_osethostname 88 -#define TARGET_NETBSD_NR_compat_43_ogetdtablesize 89 -#define TARGET_NETBSD_NR_dup2 90 -#define TARGET_NETBSD_NR_fcntl 92 -#define TARGET_NETBSD_NR_select 93 -#define TARGET_NETBSD_NR_fsync 95 -#define TARGET_NETBSD_NR_setpriority 96 -#define TARGET_NETBSD_NR_compat_30_socket 97 -#define TARGET_NETBSD_NR_connect 98 -#define TARGET_NETBSD_NR_compat_43_oaccept 99 -#define TARGET_NETBSD_NR_getpriority 100 -#define TARGET_NETBSD_NR_compat_43_osend 101 -#define TARGET_NETBSD_NR_compat_43_orecv 102 -#define TARGET_NETBSD_NR_compat_13_sigreturn13 103 -#define TARGET_NETBSD_NR_bind 104 -#define TARGET_NETBSD_NR_setsockopt 105 -#define TARGET_NETBSD_NR_listen 106 -#define TARGET_NETBSD_NR_compat_43_osigvec 108 -#define TARGET_NETBSD_NR_compat_43_osigblock 109 -#define TARGET_NETBSD_NR_compat_43_osigsetmask 110 -#define TARGET_NETBSD_NR_compat_13_sigsuspend13 111 -#define TARGET_NETBSD_NR_compat_43_osigstack 112 -#define TARGET_NETBSD_NR_compat_43_orecvmsg 113 -#define TARGET_NETBSD_NR_compat_43_osendmsg 114 -#define TARGET_NETBSD_NR_gettimeofday 116 -#define TARGET_NETBSD_NR_getrusage 117 -#define TARGET_NETBSD_NR_getsockopt 118 -#define TARGET_NETBSD_NR_readv 120 -#define TARGET_NETBSD_NR_writev 121 -#define TARGET_NETBSD_NR_settimeofday 122 -#define TARGET_NETBSD_NR_fchown 123 -#define TARGET_NETBSD_NR_fchmod 124 -#define TARGET_NETBSD_NR_compat_43_orecvfrom 125 -#define TARGET_NETBSD_NR_setreuid 126 -#define TARGET_NETBSD_NR_setregid 127 -#define TARGET_NETBSD_NR_rename 128 -#define TARGET_NETBSD_NR_compat_43_otruncate 129 -#define TARGET_NETBSD_NR_compat_43_oftruncate 130 -#define TARGET_NETBSD_NR_flock 131 -#define TARGET_NETBSD_NR_mkfifo 132 -#define TARGET_NETBSD_NR_sendto 133 -#define TARGET_NETBSD_NR_shutdown 134 -#define TARGET_NETBSD_NR_socketpair 135 -#define TARGET_NETBSD_NR_mkdir 136 -#define TARGET_NETBSD_NR_rmdir 137 -#define TARGET_NETBSD_NR_utimes 138 -#define TARGET_NETBSD_NR_adjtime 140 -#define TARGET_NETBSD_NR_compat_43_ogetpeername 141 -#define TARGET_NETBSD_NR_compat_43_ogethostid 142 -#define TARGET_NETBSD_NR_compat_43_osethostid 143 -#define TARGET_NETBSD_NR_compat_43_ogetrlimit 144 -#define TARGET_NETBSD_NR_compat_43_osetrlimit 145 -#define TARGET_NETBSD_NR_compat_43_okillpg 146 -#define TARGET_NETBSD_NR_setsid 147 -#define TARGET_NETBSD_NR_quotactl 148 -#define TARGET_NETBSD_NR_compat_43_oquota 149 -#define TARGET_NETBSD_NR_compat_43_ogetsockname 150 -#define TARGET_NETBSD_NR_nfssvc 155 -#define TARGET_NETBSD_NR_compat_43_ogetdirentries 156 -#define TARGET_NETBSD_NR_compat_20_statfs 157 -#define TARGET_NETBSD_NR_compat_20_fstatfs 158 -#define TARGET_NETBSD_NR_compat_30_getfh 161 -#define TARGET_NETBSD_NR_compat_09_ogetdomainname 162 -#define TARGET_NETBSD_NR_compat_09_osetdomainname 163 -#define TARGET_NETBSD_NR_compat_09_ouname 164 -#define TARGET_NETBSD_NR_sysarch 165 -#define TARGET_NETBSD_NR_compat_10_osemsys 169 -#define TARGET_NETBSD_NR_compat_10_omsgsys 170 -#define TARGET_NETBSD_NR_compat_10_oshmsys 171 -#define TARGET_NETBSD_NR_pread 173 -#define TARGET_NETBSD_NR_pwrite 174 -#define TARGET_NETBSD_NR_compat_30_ntp_gettime 175 -#define TARGET_NETBSD_NR_ntp_adjtime 176 -#define TARGET_NETBSD_NR_setgid 181 -#define TARGET_NETBSD_NR_setegid 182 -#define TARGET_NETBSD_NR_seteuid 183 -#define TARGET_NETBSD_NR_lfs_bmapv 184 -#define TARGET_NETBSD_NR_lfs_markv 185 -#define TARGET_NETBSD_NR_lfs_segclean 186 -#define TARGET_NETBSD_NR_lfs_segwait 187 -#define TARGET_NETBSD_NR_compat_12_stat12 188 -#define TARGET_NETBSD_NR_compat_12_fstat12 189 -#define TARGET_NETBSD_NR_compat_12_lstat12 190 -#define TARGET_NETBSD_NR_pathconf 191 -#define TARGET_NETBSD_NR_fpathconf 192 -#define TARGET_NETBSD_NR_getrlimit 194 -#define TARGET_NETBSD_NR_setrlimit 195 -#define TARGET_NETBSD_NR_compat_12_getdirentries 196 -#define TARGET_NETBSD_NR_mmap 197 -#define TARGET_NETBSD_NR___syscall 198 -#define TARGET_NETBSD_NR_lseek 199 -#define TARGET_NETBSD_NR_truncate 200 -#define TARGET_NETBSD_NR_ftruncate 201 -#define TARGET_NETBSD_NR___sysctl 202 -#define TARGET_NETBSD_NR_mlock 203 -#define TARGET_NETBSD_NR_munlock 204 -#define TARGET_NETBSD_NR_undelete 205 -#define TARGET_NETBSD_NR_futimes 206 -#define TARGET_NETBSD_NR_getpgid 207 -#define TARGET_NETBSD_NR_reboot 208 -#define TARGET_NETBSD_NR_poll 209 -#define TARGET_NETBSD_NR_compat_14___semctl 220 -#define TARGET_NETBSD_NR_semget 221 -#define TARGET_NETBSD_NR_semop 222 -#define TARGET_NETBSD_NR_semconfig 223 -#define TARGET_NETBSD_NR_compat_14_msgctl 224 -#define TARGET_NETBSD_NR_msgget 225 -#define TARGET_NETBSD_NR_msgsnd 226 -#define TARGET_NETBSD_NR_msgrcv 227 -#define TARGET_NETBSD_NR_shmat 228 -#define TARGET_NETBSD_NR_compat_14_shmctl 229 -#define TARGET_NETBSD_NR_shmdt 230 -#define TARGET_NETBSD_NR_shmget 231 -#define TARGET_NETBSD_NR_clock_gettime 232 -#define TARGET_NETBSD_NR_clock_settime 233 -#define TARGET_NETBSD_NR_clock_getres 234 -#define TARGET_NETBSD_NR_timer_create 235 -#define TARGET_NETBSD_NR_timer_delete 236 -#define TARGET_NETBSD_NR_timer_settime 237 -#define TARGET_NETBSD_NR_timer_gettime 238 -#define TARGET_NETBSD_NR_timer_getoverrun 239 -#define TARGET_NETBSD_NR_nanosleep 240 -#define TARGET_NETBSD_NR_fdatasync 241 -#define TARGET_NETBSD_NR_mlockall 242 -#define TARGET_NETBSD_NR_munlockall 243 -#define TARGET_NETBSD_NR___sigtimedwait 244 -#define TARGET_NETBSD_NR_modctl 246 -#define TARGET_NETBSD_NR__ksem_init 247 -#define TARGET_NETBSD_NR__ksem_open 248 -#define TARGET_NETBSD_NR__ksem_unlink 249 -#define TARGET_NETBSD_NR__ksem_close 250 -#define TARGET_NETBSD_NR__ksem_post 251 -#define TARGET_NETBSD_NR__ksem_wait 252 -#define TARGET_NETBSD_NR__ksem_trywait 253 -#define TARGET_NETBSD_NR__ksem_getvalue 254 -#define TARGET_NETBSD_NR__ksem_destroy 255 -#define TARGET_NETBSD_NR_mq_open 257 -#define TARGET_NETBSD_NR_mq_close 258 -#define TARGET_NETBSD_NR_mq_unlink 259 -#define TARGET_NETBSD_NR_mq_getattr 260 -#define TARGET_NETBSD_NR_mq_setattr 261 -#define TARGET_NETBSD_NR_mq_notify 262 -#define TARGET_NETBSD_NR_mq_send 263 -#define TARGET_NETBSD_NR_mq_receive 264 -#define TARGET_NETBSD_NR_mq_timedsend 265 -#define TARGET_NETBSD_NR_mq_timedreceive 266 -#define TARGET_NETBSD_NR___posix_rename 270 -#define TARGET_NETBSD_NR_swapctl 271 -#define TARGET_NETBSD_NR_compat_30_getdents 272 -#define TARGET_NETBSD_NR_minherit 273 -#define TARGET_NETBSD_NR_lchmod 274 -#define TARGET_NETBSD_NR_lchown 275 -#define TARGET_NETBSD_NR_lutimes 276 -#define TARGET_NETBSD_NR___msync13 277 -#define TARGET_NETBSD_NR_compat_30___stat13 278 -#define TARGET_NETBSD_NR_compat_30___fstat13 279 -#define TARGET_NETBSD_NR_compat_30___lstat13 280 -#define TARGET_NETBSD_NR___sigaltstack14 281 -#define TARGET_NETBSD_NR___vfork14 282 -#define TARGET_NETBSD_NR___posix_chown 283 -#define TARGET_NETBSD_NR___posix_fchown 284 -#define TARGET_NETBSD_NR___posix_lchown 285 -#define TARGET_NETBSD_NR_getsid 286 -#define TARGET_NETBSD_NR___clone 287 -#define TARGET_NETBSD_NR_fktrace 288 -#define TARGET_NETBSD_NR_preadv 289 -#define TARGET_NETBSD_NR_pwritev 290 -#define TARGET_NETBSD_NR_compat_16___sigaction14 291 -#define TARGET_NETBSD_NR___sigpending14 292 -#define TARGET_NETBSD_NR___sigprocmask14 293 -#define TARGET_NETBSD_NR___sigsuspend14 294 -#define TARGET_NETBSD_NR_compat_16___sigreturn14 295 -#define TARGET_NETBSD_NR___getcwd 296 -#define TARGET_NETBSD_NR_fchroot 297 -#define TARGET_NETBSD_NR_compat_30_fhopen 298 -#define TARGET_NETBSD_NR_compat_30_fhstat 299 -#define TARGET_NETBSD_NR_compat_20_fhstatfs 300 -#define TARGET_NETBSD_NR_____semctl13 301 -#define TARGET_NETBSD_NR___msgctl13 302 -#define TARGET_NETBSD_NR___shmctl13 303 -#define TARGET_NETBSD_NR_lchflags 304 -#define TARGET_NETBSD_NR_issetugid 305 -#define TARGET_NETBSD_NR_utrace 306 -#define TARGET_NETBSD_NR_getcontext 307 -#define TARGET_NETBSD_NR_setcontext 308 -#define TARGET_NETBSD_NR__lwp_create 309 -#define TARGET_NETBSD_NR__lwp_exit 310 -#define TARGET_NETBSD_NR__lwp_self 311 -#define TARGET_NETBSD_NR__lwp_wait 312 -#define TARGET_NETBSD_NR__lwp_suspend 313 -#define TARGET_NETBSD_NR__lwp_continue 314 -#define TARGET_NETBSD_NR__lwp_wakeup 315 -#define TARGET_NETBSD_NR__lwp_getprivate 316 -#define TARGET_NETBSD_NR__lwp_setprivate 317 -#define TARGET_NETBSD_NR__lwp_kill 318 -#define TARGET_NETBSD_NR__lwp_detach 319 -#define TARGET_NETBSD_NR__lwp_park 320 -#define TARGET_NETBSD_NR__lwp_unpark 321 -#define TARGET_NETBSD_NR__lwp_unpark_all 322 -#define TARGET_NETBSD_NR__lwp_setname 323 -#define TARGET_NETBSD_NR__lwp_getname 324 -#define TARGET_NETBSD_NR__lwp_ctl 325 -#define TARGET_NETBSD_NR_sa_register 330 -#define TARGET_NETBSD_NR_sa_stacks 331 -#define TARGET_NETBSD_NR_sa_enable 332 -#define TARGET_NETBSD_NR_sa_setconcurrency 333 -#define TARGET_NETBSD_NR_sa_yield 334 -#define TARGET_NETBSD_NR_sa_preempt 335 -#define TARGET_NETBSD_NR_sa_unblockyield 336 -#define TARGET_NETBSD_NR___sigaction_sigtramp 340 -#define TARGET_NETBSD_NR_pmc_get_info 341 -#define TARGET_NETBSD_NR_pmc_control 342 -#define TARGET_NETBSD_NR_rasctl 343 -#define TARGET_NETBSD_NR_kqueue 344 -#define TARGET_NETBSD_NR_kevent 345 -#define TARGET_NETBSD_NR__sched_setparam 346 -#define TARGET_NETBSD_NR__sched_getparam 347 -#define TARGET_NETBSD_NR__sched_setaffinity 348 -#define TARGET_NETBSD_NR__sched_getaffinity 349 -#define TARGET_NETBSD_NR_sched_yield 350 -#define TARGET_NETBSD_NR_fsync_range 354 -#define TARGET_NETBSD_NR_uuidgen 355 -#define TARGET_NETBSD_NR_getvfsstat 356 -#define TARGET_NETBSD_NR_statvfs1 357 -#define TARGET_NETBSD_NR_fstatvfs1 358 -#define TARGET_NETBSD_NR_compat_30_fhstatvfs1 359 -#define TARGET_NETBSD_NR_extattrctl 360 -#define TARGET_NETBSD_NR_extattr_set_file 361 -#define TARGET_NETBSD_NR_extattr_get_file 362 -#define TARGET_NETBSD_NR_extattr_delete_file 363 -#define TARGET_NETBSD_NR_extattr_set_fd 364 -#define TARGET_NETBSD_NR_extattr_get_fd 365 -#define TARGET_NETBSD_NR_extattr_delete_fd 366 -#define TARGET_NETBSD_NR_extattr_set_link 367 -#define TARGET_NETBSD_NR_extattr_get_link 368 -#define TARGET_NETBSD_NR_extattr_delete_link 369 -#define TARGET_NETBSD_NR_extattr_list_fd 370 -#define TARGET_NETBSD_NR_extattr_list_file 371 -#define TARGET_NETBSD_NR_extattr_list_link 372 -#define TARGET_NETBSD_NR_pselect 373 -#define TARGET_NETBSD_NR_pollts 374 -#define TARGET_NETBSD_NR_setxattr 375 -#define TARGET_NETBSD_NR_lsetxattr 376 -#define TARGET_NETBSD_NR_fsetxattr 377 -#define TARGET_NETBSD_NR_getxattr 378 -#define TARGET_NETBSD_NR_lgetxattr 379 -#define TARGET_NETBSD_NR_fgetxattr 380 -#define TARGET_NETBSD_NR_listxattr 381 -#define TARGET_NETBSD_NR_llistxattr 382 -#define TARGET_NETBSD_NR_flistxattr 383 -#define TARGET_NETBSD_NR_removexattr 384 -#define TARGET_NETBSD_NR_lremovexattr 385 -#define TARGET_NETBSD_NR_fremovexattr 386 -#define TARGET_NETBSD_NR___stat30 387 -#define TARGET_NETBSD_NR___fstat30 388 -#define TARGET_NETBSD_NR___lstat30 389 -#define TARGET_NETBSD_NR___getdents30 390 -#define TARGET_NETBSD_NR_compat_30___fhstat30 392 -#define TARGET_NETBSD_NR___ntp_gettime30 393 -#define TARGET_NETBSD_NR___socket30 394 -#define TARGET_NETBSD_NR___getfh30 395 -#define TARGET_NETBSD_NR___fhopen40 396 -#define TARGET_NETBSD_NR___fhstatvfs140 397 -#define TARGET_NETBSD_NR___fhstat40 398 -#define TARGET_NETBSD_NR_aio_cancel 399 -#define TARGET_NETBSD_NR_aio_error 400 -#define TARGET_NETBSD_NR_aio_fsync 401 -#define TARGET_NETBSD_NR_aio_read 402 -#define TARGET_NETBSD_NR_aio_return 403 -#define TARGET_NETBSD_NR_aio_suspend 404 -#define TARGET_NETBSD_NR_aio_write 405 -#define TARGET_NETBSD_NR_lio_listio 406 -#define TARGET_NETBSD_NR___mount50 410 -#define TARGET_NETBSD_NR_mremap 411 -#define TARGET_NETBSD_NR_pset_create 412 -#define TARGET_NETBSD_NR_pset_destroy 413 -#define TARGET_NETBSD_NR_pset_assign 414 -#define TARGET_NETBSD_NR__pset_bind 415 -#define TARGET_NETBSD_NR___posix_fadvise50 416 diff --git a/bsd-user/netbsd/target_os_elf.h b/bsd-user/netbsd/target_os_elf.h deleted file mode 100644 index 9de0f290c0c9..000000000000 --- a/bsd-user/netbsd/target_os_elf.h +++ /dev/null @@ -1,148 +0,0 @@ -/* - * netbsd ELF definitions - * - * Copyright (c) 2013 Stacey D. Son - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - */ - -#ifndef TARGET_OS_ELF_H -#define TARGET_OS_ELF_H - -#include "target_arch_elf.h" -#include "elf.h" -#include "user/tswap-target.h" - -/* this flag is uneffective under linux too, should be deleted */ -#ifndef MAP_DENYWRITE -#define MAP_DENYWRITE 0 -#endif - -/* should probably go in elf.h */ -#ifndef ELIBBAD -#define ELIBBAD 80 -#endif - -#ifndef ELF_PLATFORM -#define ELF_PLATFORM (NULL) -#endif - -#ifndef ELF_HWCAP -#define ELF_HWCAP 0 -#endif - -#ifdef TARGET_ABI32 -#undef ELF_CLASS -#define ELF_CLASS ELFCLASS32 -#undef bswaptls -#define bswaptls(ptr) bswap32s(ptr) -#endif - -/* max code+data+bss space allocated to elf interpreter */ -#define INTERP_MAP_SIZE (32 * 1024 * 1024) - -/* max code+data+bss+brk space allocated to ET_DYN executables */ -#define ET_DYN_MAP_SIZE (128 * 1024 * 1024) - -/* Necessary parameters */ -#define TARGET_ELF_EXEC_PAGESIZE TARGET_PAGE_SIZE -#define TARGET_ELF_PAGESTART(_v) ((_v) & \ - ~(unsigned long)(TARGET_ELF_EXEC_PAGESIZE - 1)) -#define TARGET_ELF_PAGEOFFSET(_v) ((_v) & (TARGET_ELF_EXEC_PAGESIZE - 1)) - -#define DLINFO_ITEMS 12 - -static abi_ulong target_create_elf_tables(abi_ulong p, int argc, int envc, - abi_ulong stringp, - struct elfhdr *exec, - abi_ulong load_addr, - abi_ulong load_bias, - abi_ulong interp_load_addr, - struct image_info *info) -{ - abi_ulong sp; - int size; - abi_ulong u_platform; - const char *k_platform; - const int n = sizeof(elf_addr_t); - - sp = p; - u_platform = 0; - k_platform = ELF_PLATFORM; - if (k_platform) { - size_t len = strlen(k_platform) + 1; - sp -= (len + n - 1) & ~(n - 1); - u_platform = sp; - /* FIXME - check return value of memcpy_to_target() for failure */ - memcpy_to_target(sp, k_platform, len); - } - /* - * Force 16 byte _final_ alignment here for generality. - */ - sp = sp & ~(abi_ulong)15; - size = (DLINFO_ITEMS + 1) * 2; - if (k_platform) { - size += 2; - } -#ifdef DLINFO_ARCH_ITEMS - size += DLINFO_ARCH_ITEMS * 2; -#endif - size += envc + argc + 2; - size += 1; /* argc itself */ - size *= n; - if (size & 15) { - sp -= 16 - (size & 15); - } - - /* - * NetBSD defines elf_addr_t as Elf32_Off / Elf64_Off - */ -#define NEW_AUX_ENT(id, val) do { \ - sp -= n; put_user_ual(val, sp); \ - sp -= n; put_user_ual(id, sp); \ - } while (0) - - NEW_AUX_ENT(AT_NULL, 0); - - /* There must be exactly DLINFO_ITEMS entries here. */ - NEW_AUX_ENT(AT_PHDR, (abi_ulong)(load_addr + exec->e_phoff)); - NEW_AUX_ENT(AT_PHENT, (abi_ulong)(sizeof(struct elf_phdr))); - NEW_AUX_ENT(AT_PHNUM, (abi_ulong)(exec->e_phnum)); - NEW_AUX_ENT(AT_PAGESZ, (abi_ulong)(TARGET_PAGE_SIZE)); - NEW_AUX_ENT(AT_BASE, (abi_ulong)(interp_load_addr)); - NEW_AUX_ENT(AT_FLAGS, (abi_ulong)0); - NEW_AUX_ENT(AT_ENTRY, load_bias + exec->e_entry); - NEW_AUX_ENT(AT_UID, (abi_ulong)getuid()); - NEW_AUX_ENT(AT_EUID, (abi_ulong)geteuid()); - NEW_AUX_ENT(AT_GID, (abi_ulong)getgid()); - NEW_AUX_ENT(AT_EGID, (abi_ulong)getegid()); - NEW_AUX_ENT(AT_HWCAP, (abi_ulong)ELF_HWCAP); - NEW_AUX_ENT(AT_CLKTCK, (abi_ulong)sysconf(_SC_CLK_TCK)); - if (k_platform) { - NEW_AUX_ENT(AT_PLATFORM, u_platform); - } -#ifdef ARCH_DLINFO - /* - * ARCH_DLINFO must come last so platform specific code can enforce - * special alignment requirements on the AUXV if necessary (eg. PPC). - */ - ARCH_DLINFO; -#endif -#undef NEW_AUX_ENT - - sp = loader_build_argptr(envc, argc, sp, stringp); - return sp; -} - -#endif /* TARGET_OS_ELF_H */ diff --git a/bsd-user/netbsd/target_os_siginfo.h b/bsd-user/netbsd/target_os_siginfo.h deleted file mode 100644 index eb57e0a3098f..000000000000 --- a/bsd-user/netbsd/target_os_siginfo.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef TARGET_OS_SIGINFO_H -#define TARGET_OS_SIGINFO_H - -#define TARGET_NSIG 32 /* counting 0; could be 33 (mask is 1-32) */ -#define TARGET_NSIG_BPW (sizeof(uint32_t) * 8) -#define TARGET_NSIG_WORDS (TARGET_NSIG / TARGET_NSIG_BPW) - -/* this struct defines a stack used during syscall handling */ -typedef struct target_sigaltstack { - abi_long ss_sp; - abi_ulong ss_size; - abi_long ss_flags; -} target_stack_t; - -typedef struct { - uint32_t __bits[TARGET_NSIG_WORDS]; -} target_sigset_t - -struct target_sigaction { - abi_ulong _sa_handler; - int32_t sa_flags; - target_sigset_t sa_mask; -}; - -/* Compare to sys/siginfo.h */ -typedef union target_sigval { - int sival_int; - abi_ulong sival_ptr; -} target_sigval_t; - -struct target_ksiginfo { - int32_t _signo; - int32_t _code; - int32_t _errno; -#if TARGET_ABI_BITS == 64 - int32_t _pad; -#endif - union { - struct { - int32_t _pid; - int32_t _uid; - target_sigval_t _value; - } _rt; - - struct { - int32_t _pid; - int32_t _uid; - int32_t _struct; - /* clock_t _utime; */ - /* clock_t _stime; */ - } _child; - - struct { - abi_ulong _addr; - int32_t _trap; - } _fault; - - struct { - long _band; - int _fd; - } _poll; - } _reason; -}; - -typedef union target_siginfo { - int8_t si_pad[128]; - struct target_ksiginfo _info; -} target_siginfo_t; - -#define target_si_signo _info._signo -#define target_si_code _info._code -#define target_si_errno _info._errno -#define target_si_addr _info._reason._fault._addr - -#define TARGET_SEGV_MAPERR 1 -#define TARGET_SEGV_ACCERR 2 - -#define TARGET_TRAP_BRKPT 1 -#define TARGET_TRAP_TRACE 2 - - -#endif /* TARGET_OS_SIGINFO_H */ diff --git a/bsd-user/netbsd/target_os_signal.h b/bsd-user/netbsd/target_os_signal.h deleted file mode 100644 index 4ee4f768e0e1..000000000000 --- a/bsd-user/netbsd/target_os_signal.h +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef TARGET_OS_SIGNAL_H -#define TARGET_OS_SIGNAL_H - -#include "target_os_siginfo.h" -#include "target_arch_signal.h" - -#define TARGET_SIGHUP 1 /* hangup */ -#define TARGET_SIGINT 2 /* interrupt */ -#define TARGET_SIGQUIT 3 /* quit */ -#define TARGET_SIGILL 4 /* illegal instruction (not reset when caught) */ -#define TARGET_SIGTRAP 5 /* trace trap (not reset when caught) */ -#define TARGET_SIGABRT 6 /* abort() */ -#define TARGET_SIGIOT SIGABRT /* compatibility */ -#define TARGET_SIGEMT 7 /* EMT instruction */ -#define TARGET_SIGFPE 8 /* floating point exception */ -#define TARGET_SIGKILL 9 /* kill (cannot be caught or ignored) */ -#define TARGET_SIGBUS 10 /* bus error */ -#define TARGET_SIGSEGV 11 /* segmentation violation */ -#define TARGET_SIGSYS 12 /* bad argument to system call */ -#define TARGET_SIGPIPE 13 /* write on a pipe with no one to read it */ -#define TARGET_SIGALRM 14 /* alarm clock */ -#define TARGET_SIGTERM 15 /* software termination signal from kill */ -#define TARGET_SIGURG 16 /* urgent condition on IO channel */ -#define TARGET_SIGSTOP 17 /* sendable stop signal not from tty */ -#define TARGET_SIGTSTP 18 /* stop signal from tty */ -#define TARGET_SIGCONT 19 /* continue a stopped process */ -#define TARGET_SIGCHLD 20 /* to parent on child stop or exit */ -#define TARGET_SIGTTIN 21 /* to readers pgrp upon background tty read */ -#define TARGET_SIGTTOU 22 /* like TTIN for out if (tp->t_local<OSTOP) */ -#define TARGET_SIGIO 23 /* input/output possible signal */ -#define TARGET_SIGXCPU 24 /* exceeded CPU time limit */ -#define TARGET_SIGXFSZ 25 /* exceeded file size limit */ -#define TARGET_SIGVTALRM 26 /* virtual time alarm */ -#define TARGET_SIGPROF 27 /* profiling time alarm */ -#define TARGET_SIGWINCH 28 /* window size changes */ -#define TARGET_SIGINFO 29 /* information request */ -#define TARGET_SIGUSR1 30 /* user defined signal 1 */ -#define TARGET_SIGUSR2 31 /* user defined signal 2 */ - -/* - * Language spec says we must list exactly one parameter, even though we - * actually supply three. Ugh! - */ -#define TARGET_SIG_DFL ((void (*)(int))0) -#define TARGET_SIG_IGN ((void (*)(int))1) -#define TARGET_SIG_ERR ((void (*)(int))-1) - -#define TARGET_SA_ONSTACK 0x0001 /* take signal on signal stack */ -#define TARGET_SA_RESTART 0x0002 /* restart system on signal return */ -#define TARGET_SA_RESETHAND 0x0004 /* reset to SIG_DFL when taking signal */ -#define TARGET_SA_NODEFER 0x0010 /* don't mask the signal we're delivering */ -#define TARGET_SA_NOCLDWAIT 0x0020 /* don't create zombies (assign to pid 1) */ -#define TARGET_SA_USERTRAMP 0x0100 /* do not bounce off kernel's sigtramp */ -#define TARGET_SA_NOCLDSTOP 0x0008 /* do not generate SIGCHLD on child stop */ -#define TARGET_SA_SIGINFO 0x0040 /* generate siginfo_t */ - -/* - * Flags for sigprocmask: - */ -#define TARGET_SIG_BLOCK 1 /* block specified signal set */ -#define TARGET_SIG_UNBLOCK 2 /* unblock specified signal set */ -#define TARGET_SIG_SETMASK 3 /* set specified signal set */ - -#define TARGET_BADSIG SIG_ERR - -#define TARGET_SS_ONSTACK 0x0001 /* take signals on alternate stack */ -#define TARGET_SS_DISABLE 0x0004 /* disable taking signals on alternate stack */ - -#endif /* TARGET_OS_SIGNAL_H */ diff --git a/bsd-user/netbsd/target_os_stack.h b/bsd-user/netbsd/target_os_stack.h deleted file mode 100644 index 8349e9149b89..000000000000 --- a/bsd-user/netbsd/target_os_stack.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * NetBSD setup_initial_stack() implementation. - * - * Copyright (c) 2013-14 Stacey D. Son - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - */ - -#ifndef TARGET_OS_STACK_H -#define TARGET_OS_STACK_H - -#include "target_arch_sigtramp.h" - -static inline int setup_initial_stack(struct bsd_binprm *bprm, abi_ulong *p, - abi_ulong *stringp) -{ - int i; - abi_ulong stack_base; - - stack_base = (target_stkbas + target_stksiz) - - MAX_ARG_PAGES * TARGET_PAGE_SIZE; - if (p) { - *p = stack_base; - } - if (stringp) { - *stringp = stack_base; - } - - for (i = 0; i < MAX_ARG_PAGES; i++) { - if (bprm->page[i]) { - info->rss++; - if (!memcpy_to_target(stack_base, bprm->page[i], - TARGET_PAGE_SIZE)) { - errno = EFAULT; - return -1; - } - g_free(bprm->page[i]); - } - stack_base += TARGET_PAGE_SIZE; - } - - return 0; -} - -#endif /* TARGET_OS_STACK_H */ diff --git a/bsd-user/netbsd/target_os_thread.h b/bsd-user/netbsd/target_os_thread.h deleted file mode 100644 index 8ccfa16e4bed..000000000000 --- a/bsd-user/netbsd/target_os_thread.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * NetBSD thread dependent code and definitions - * - * Copyright (c) 2013 Stacey D. Son - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - */ - -#ifndef TARGET_OS_THREAD_H -#define TARGET_OS_THREAD_H - -#include "target_arch_thread.h" - -#endif /* TARGET_OS_THREAD_H */ diff --git a/bsd-user/openbsd/host-os.h b/bsd-user/openbsd/host-os.h deleted file mode 100644 index b9222335d46e..000000000000 --- a/bsd-user/openbsd/host-os.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * OpenBSD host dependent code and definitions - * - * Copyright (c) 2013 Stacey D. Son - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - */ - -#ifndef HOST_OS_H -#define HOST_OS_H - -#define HOST_DEFAULT_BSD_TYPE target_openbsd - -#endif /* HOST_OS_H */ diff --git a/bsd-user/openbsd/os-strace.h b/bsd-user/openbsd/os-strace.h deleted file mode 100644 index 916139043359..000000000000 --- a/bsd-user/openbsd/os-strace.h +++ /dev/null @@ -1 +0,0 @@ -/* XXX OpenBSD dependent strace print functions */ diff --git a/bsd-user/openbsd/strace.list b/bsd-user/openbsd/strace.list deleted file mode 100644 index 1f0a3316f3a9..000000000000 --- a/bsd-user/openbsd/strace.list +++ /dev/null @@ -1,187 +0,0 @@ -{ TARGET_OPENBSD_NR___getcwd, "__getcwd", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR___semctl, "__semctl", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR___syscall, "__syscall", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR___sysctl, "__sysctl", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_accept, "accept", "%s(%d,%#x,%#x)", NULL, NULL }, -{ TARGET_OPENBSD_NR_access, "access", "%s(\"%s\",%#o)", NULL, NULL }, -{ TARGET_OPENBSD_NR_acct, "acct", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_adjfreq, "adjfreq", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_adjtime, "adjtime", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_bind, "bind", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_break, "break", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_chdir, "chdir", "%s(\"%s\")", NULL, NULL }, -{ TARGET_OPENBSD_NR_chflags, "chflags", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_chmod, "chmod", "%s(\"%s\",%#o)", NULL, NULL }, -{ TARGET_OPENBSD_NR_chown, "chown", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_chroot, "chroot", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_clock_getres, "clock_getres", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_clock_gettime, "clock_gettime", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_clock_settime, "clock_settime", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_close, "close", "%s(%d)", NULL, NULL }, -{ TARGET_OPENBSD_NR_closefrom, "closefrom", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_connect, "connect", "%s(%d,%#x,%d)", NULL, NULL }, -{ TARGET_OPENBSD_NR_dup, "dup", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_dup2, "dup2", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_execve, "execve", NULL, print_execve, NULL }, -{ TARGET_OPENBSD_NR_exit, "exit", "%s(%d)\n", NULL, NULL }, -{ TARGET_OPENBSD_NR_fchdir, "fchdir", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_fchflags, "fchflags", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_fchmod, "fchmod", "%s(%d,%#o)", NULL, NULL }, -{ TARGET_OPENBSD_NR_fchown, "fchown", "%s(\"%s\",%d,%d)", NULL, NULL }, -{ TARGET_OPENBSD_NR_fcntl, "fcntl", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_fhopen, "fhopen", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_fhstat, "fhstat", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_fhstatfs, "fhstatfs", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_flock, "flock", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_fork, "fork", "%s()", NULL, NULL }, -{ TARGET_OPENBSD_NR_fpathconf, "fpathconf", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_fstat, "fstat", "%s(%d,%p)", NULL, NULL }, -{ TARGET_OPENBSD_NR_fstatfs, "fstatfs", "%s(%d,%p)", NULL, NULL }, -{ TARGET_OPENBSD_NR_fsync, "fsync", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_ftruncate, "ftruncate", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_futimes, "futimes", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_getdirentries, "getdirentries", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_getegid, "getegid", "%s()", NULL, NULL }, -{ TARGET_OPENBSD_NR_geteuid, "geteuid", "%s()", NULL, NULL }, -{ TARGET_OPENBSD_NR_getfh, "getfh", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_getfsstat, "getfsstat", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_getgid, "getgid", "%s()", NULL, NULL }, -{ TARGET_OPENBSD_NR_getgroups, "getgroups", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_getitimer, "getitimer", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_getlogin, "getlogin", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_getpeereid, "getpeereid", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_getpeername, "getpeername", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_getpgid, "getpgid", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_getpgrp, "getpgrp", "%s()", NULL, NULL }, -{ TARGET_OPENBSD_NR_getpid, "getpid", "%s()", NULL, NULL }, -{ TARGET_OPENBSD_NR_getppid, "getppid", "%s()", NULL, NULL }, -{ TARGET_OPENBSD_NR_getpriority, "getpriority", "%s(%#x,%#x)", NULL, NULL }, -{ TARGET_OPENBSD_NR_getresgid, "getresgid", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_getresuid, "getresuid", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_getrlimit, "getrlimit", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_getrusage, "getrusage", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_getsid, "getsid", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_getsockname, "getsockname", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_getsockopt, "getsockopt", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_getthrid, "getthrid", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_gettimeofday, "gettimeofday", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_getuid, "getuid", "%s()", NULL, NULL }, -{ TARGET_OPENBSD_NR_ioctl, "ioctl", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_issetugid, "issetugid", "%s()", NULL, NULL }, -{ TARGET_OPENBSD_NR_kevent, "kevent", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_kill, "kill", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_kqueue, "kqueue", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_ktrace, "ktrace", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_lchown, "lchown", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_lfs_bmapv, "lfs_bmapv", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_lfs_markv, "lfs_markv", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_lfs_segclean, "lfs_segclean", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_lfs_segwait, "lfs_segwait", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_link, "link", "%s(\"%s\",\"%s\")", NULL, NULL }, -{ TARGET_OPENBSD_NR_listen, "listen", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_lseek, "lseek", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_lstat, "lstat", "%s(\"%s\",%p)", NULL, NULL }, -{ TARGET_OPENBSD_NR_madvise, "madvise", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_mincore, "mincore", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_minherit, "minherit", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_mkdir, "mkdir", "%s(\"%s\",%#o)", NULL, NULL }, -{ TARGET_OPENBSD_NR_mkfifo, "mkfifo", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_mknod, "mknod", "%s(\"%s\",%#o,%#x)", NULL, NULL }, -{ TARGET_OPENBSD_NR_mlock, "mlock", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_mlockall, "mlockall", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_mmap, "mmap", NULL, NULL, print_syscall_ret_addr }, -{ TARGET_OPENBSD_NR_mount, "mount", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_mprotect, "mprotect", "%s(%#x,%#x,%d)", NULL, NULL }, -{ TARGET_OPENBSD_NR_mquery, "mquery", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_msgctl, "msgctl", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_msgget, "msgget", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_msgrcv, "msgrcv", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_msgsnd, "msgsnd", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_msync, "msync", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_munlock, "munlock", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_munlockall, "munlockall", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_munmap, "munmap", "%s(%p,%d)", NULL, NULL }, -{ TARGET_OPENBSD_NR_nanosleep, "nanosleep", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_nfssvc, "nfssvc", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_open, "open", "%s(\"%s\",%#x,%#o)", NULL, NULL }, -{ TARGET_OPENBSD_NR_opipe, "opipe", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_osigaltstack, "osigaltstack", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_pathconf, "pathconf", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_pipe, "pipe", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_poll, "poll", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_pread, "pread", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_preadv, "preadv", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_profil, "profil", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_ptrace, "ptrace", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_pwrite, "pwrite", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_pwritev, "pwritev", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_quotactl, "quotactl", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_read, "read", "%s(%d,%#x,%d)", NULL, NULL }, -{ TARGET_OPENBSD_NR_readlink, "readlink", "%s(\"%s\",%p,%d)", NULL, NULL }, -{ TARGET_OPENBSD_NR_readv, "readv", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_reboot, "reboot", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_recvfrom, "recvfrom", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_recvmsg, "recvmsg", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_rename, "rename", "%s(\"%s\",\"%s\")", NULL, NULL }, -{ TARGET_OPENBSD_NR_revoke, "revoke", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_rfork, "rfork", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_rmdir, "rmdir", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_sbrk, "sbrk", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_sched_yield, "sched_yield", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_select, "select", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_semget, "semget", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_semop, "semop", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_sendmsg, "sendmsg", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_sendto, "sendto", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_setegid, "setegid", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_seteuid, "seteuid", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_setgid, "setgid", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_setgroups, "setgroups", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_setitimer, "setitimer", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_setlogin, "setlogin", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_setpgid, "setpgid", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_setpriority, "setpriority", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_setregid, "setregid", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_setresgid, "setresgid", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_setresuid, "setresuid", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_setreuid, "setreuid", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_setrlimit, "setrlimit", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_setsid, "setsid", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_setsockopt, "setsockopt", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_settimeofday, "settimeofday", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_setuid, "setuid", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_shmat, "shmat", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_shmctl, "shmctl", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_shmdt, "shmdt", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_shmget, "shmget", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_shutdown, "shutdown", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_sigaction, "sigaction", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_sigaltstack, "sigaltstack", "%s(%p,%p)", NULL, NULL }, -{ TARGET_OPENBSD_NR_sigpending, "sigpending", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_sigprocmask, "sigprocmask", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_sigreturn, "sigreturn", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_sigsuspend, "sigsuspend", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_socket, "socket", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_socketpair, "socketpair", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_sstk, "sstk", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_stat, "stat", "%s(\"%s\",%p)", NULL, NULL }, -{ TARGET_OPENBSD_NR_statfs, "statfs", "%s(\"%s\",%p)", NULL, NULL }, -{ TARGET_OPENBSD_NR_swapctl, "swapctl", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_symlink, "symlink", "%s(\"%s\",\"%s\")", NULL, NULL }, -{ TARGET_OPENBSD_NR_sync, "sync", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_sysarch, "sysarch", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_syscall, "syscall", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_threxit, "threxit", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_thrsigdivert, "thrsigdivert", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_thrsleep, "thrsleep", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_thrwakeup, "thrwakeup", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_truncate, "truncate", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_umask, "umask", "%s(%#o)", NULL, NULL }, -{ TARGET_OPENBSD_NR_unlink, "unlink", "%s(\"%s\")", NULL, NULL }, -{ TARGET_OPENBSD_NR_unmount, "unmount", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_utimes, "utimes", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_vfork, "vfork", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_wait4, "wait4", NULL, NULL, NULL }, -{ TARGET_OPENBSD_NR_write, "write", "%s(%d,%#x,%d)", NULL, NULL }, -{ TARGET_OPENBSD_NR_writev, "writev", "%s(%d,%p,%#x)", NULL, NULL }, -{ TARGET_OPENBSD_NR_xfspioctl, "xfspioctl", NULL, NULL, NULL }, diff --git a/bsd-user/openbsd/syscall_nr.h b/bsd-user/openbsd/syscall_nr.h deleted file mode 100644 index dececfd9e821..000000000000 --- a/bsd-user/openbsd/syscall_nr.h +++ /dev/null @@ -1,225 +0,0 @@ -/* $OpenBSD: syscall.h,v 1.101 2008/03/16 19:43:41 otto Exp $ */ - -/* - * System call numbers. - * - * created from; OpenBSD: syscalls.master,v 1.90 2008/03/16 19:42:57 otto Exp - */ - -#define TARGET_OPENBSD_NR_syscall 0 -#define TARGET_OPENBSD_NR_exit 1 -#define TARGET_OPENBSD_NR_fork 2 -#define TARGET_OPENBSD_NR_read 3 -#define TARGET_OPENBSD_NR_write 4 -#define TARGET_OPENBSD_NR_open 5 -#define TARGET_OPENBSD_NR_close 6 -#define TARGET_OPENBSD_NR_wait4 7 -#define TARGET_OPENBSD_NR_link 9 -#define TARGET_OPENBSD_NR_unlink 10 -#define TARGET_OPENBSD_NR_chdir 12 -#define TARGET_OPENBSD_NR_fchdir 13 -#define TARGET_OPENBSD_NR_mknod 14 -#define TARGET_OPENBSD_NR_chmod 15 -#define TARGET_OPENBSD_NR_chown 16 -#define TARGET_OPENBSD_NR_break 17 -#define TARGET_OPENBSD_NR_getpid 20 -#define TARGET_OPENBSD_NR_mount 21 -#define TARGET_OPENBSD_NR_unmount 22 -#define TARGET_OPENBSD_NR_setuid 23 -#define TARGET_OPENBSD_NR_getuid 24 -#define TARGET_OPENBSD_NR_geteuid 25 -#define TARGET_OPENBSD_NR_ptrace 26 -#define TARGET_OPENBSD_NR_recvmsg 27 -#define TARGET_OPENBSD_NR_sendmsg 28 -#define TARGET_OPENBSD_NR_recvfrom 29 -#define TARGET_OPENBSD_NR_accept 30 -#define TARGET_OPENBSD_NR_getpeername 31 -#define TARGET_OPENBSD_NR_getsockname 32 -#define TARGET_OPENBSD_NR_access 33 -#define TARGET_OPENBSD_NR_chflags 34 -#define TARGET_OPENBSD_NR_fchflags 35 -#define TARGET_OPENBSD_NR_sync 36 -#define TARGET_OPENBSD_NR_kill 37 -#define TARGET_OPENBSD_NR_getppid 39 -#define TARGET_OPENBSD_NR_dup 41 -#define TARGET_OPENBSD_NR_opipe 42 -#define TARGET_OPENBSD_NR_getegid 43 -#define TARGET_OPENBSD_NR_profil 44 -#define TARGET_OPENBSD_NR_ktrace 45 -#define TARGET_OPENBSD_NR_sigaction 46 -#define TARGET_OPENBSD_NR_getgid 47 -#define TARGET_OPENBSD_NR_sigprocmask 48 -#define TARGET_OPENBSD_NR_getlogin 49 -#define TARGET_OPENBSD_NR_setlogin 50 -#define TARGET_OPENBSD_NR_acct 51 -#define TARGET_OPENBSD_NR_sigpending 52 -#define TARGET_OPENBSD_NR_osigaltstack 53 -#define TARGET_OPENBSD_NR_ioctl 54 -#define TARGET_OPENBSD_NR_reboot 55 -#define TARGET_OPENBSD_NR_revoke 56 -#define TARGET_OPENBSD_NR_symlink 57 -#define TARGET_OPENBSD_NR_readlink 58 -#define TARGET_OPENBSD_NR_execve 59 -#define TARGET_OPENBSD_NR_umask 60 -#define TARGET_OPENBSD_NR_chroot 61 -#define TARGET_OPENBSD_NR_vfork 66 -#define TARGET_OPENBSD_NR_sbrk 69 -#define TARGET_OPENBSD_NR_sstk 70 -#define TARGET_OPENBSD_NR_munmap 73 -#define TARGET_OPENBSD_NR_mprotect 74 -#define TARGET_OPENBSD_NR_madvise 75 -#define TARGET_OPENBSD_NR_mincore 78 -#define TARGET_OPENBSD_NR_getgroups 79 -#define TARGET_OPENBSD_NR_setgroups 80 -#define TARGET_OPENBSD_NR_getpgrp 81 -#define TARGET_OPENBSD_NR_setpgid 82 -#define TARGET_OPENBSD_NR_setitimer 83 -#define TARGET_OPENBSD_NR_getitimer 86 -#define TARGET_OPENBSD_NR_dup2 90 -#define TARGET_OPENBSD_NR_fcntl 92 -#define TARGET_OPENBSD_NR_select 93 -#define TARGET_OPENBSD_NR_fsync 95 -#define TARGET_OPENBSD_NR_setpriority 96 -#define TARGET_OPENBSD_NR_socket 97 -#define TARGET_OPENBSD_NR_connect 98 -#define TARGET_OPENBSD_NR_getpriority 100 -#define TARGET_OPENBSD_NR_sigreturn 103 -#define TARGET_OPENBSD_NR_bind 104 -#define TARGET_OPENBSD_NR_setsockopt 105 -#define TARGET_OPENBSD_NR_listen 106 -#define TARGET_OPENBSD_NR_sigsuspend 111 -#define TARGET_OPENBSD_NR_gettimeofday 116 -#define TARGET_OPENBSD_NR_getrusage 117 -#define TARGET_OPENBSD_NR_getsockopt 118 -#define TARGET_OPENBSD_NR_readv 120 -#define TARGET_OPENBSD_NR_writev 121 -#define TARGET_OPENBSD_NR_settimeofday 122 -#define TARGET_OPENBSD_NR_fchown 123 -#define TARGET_OPENBSD_NR_fchmod 124 -#define TARGET_OPENBSD_NR_setreuid 126 -#define TARGET_OPENBSD_NR_setregid 127 -#define TARGET_OPENBSD_NR_rename 128 -#define TARGET_OPENBSD_NR_flock 131 -#define TARGET_OPENBSD_NR_mkfifo 132 -#define TARGET_OPENBSD_NR_sendto 133 -#define TARGET_OPENBSD_NR_shutdown 134 -#define TARGET_OPENBSD_NR_socketpair 135 -#define TARGET_OPENBSD_NR_mkdir 136 -#define TARGET_OPENBSD_NR_rmdir 137 -#define TARGET_OPENBSD_NR_utimes 138 -#define TARGET_OPENBSD_NR_adjtime 140 -#define TARGET_OPENBSD_NR_setsid 147 -#define TARGET_OPENBSD_NR_quotactl 148 -#define TARGET_OPENBSD_NR_nfssvc 155 -#define TARGET_OPENBSD_NR_getfh 161 -#define TARGET_OPENBSD_NR_sysarch 165 -#define TARGET_OPENBSD_NR_pread 173 -#define TARGET_OPENBSD_NR_pwrite 174 -#define TARGET_OPENBSD_NR_setgid 181 -#define TARGET_OPENBSD_NR_setegid 182 -#define TARGET_OPENBSD_NR_seteuid 183 -#define TARGET_OPENBSD_NR_lfs_bmapv 184 -#define TARGET_OPENBSD_NR_lfs_markv 185 -#define TARGET_OPENBSD_NR_lfs_segclean 186 -#define TARGET_OPENBSD_NR_lfs_segwait 187 -#define TARGET_OPENBSD_NR_pathconf 191 -#define TARGET_OPENBSD_NR_fpathconf 192 -#define TARGET_OPENBSD_NR_swapctl 193 -#define TARGET_OPENBSD_NR_getrlimit 194 -#define TARGET_OPENBSD_NR_setrlimit 195 -#define TARGET_OPENBSD_NR_getdirentries 196 -#define TARGET_OPENBSD_NR_mmap 197 -#define TARGET_OPENBSD_NR___syscall 198 -#define TARGET_OPENBSD_NR_lseek 199 -#define TARGET_OPENBSD_NR_truncate 200 -#define TARGET_OPENBSD_NR_ftruncate 201 -#define TARGET_OPENBSD_NR___sysctl 202 -#define TARGET_OPENBSD_NR_mlock 203 -#define TARGET_OPENBSD_NR_munlock 204 -#define TARGET_OPENBSD_NR_futimes 206 -#define TARGET_OPENBSD_NR_getpgid 207 -#define TARGET_OPENBSD_NR_xfspioctl 208 -#define TARGET_OPENBSD_NR_semget 221 -#define TARGET_OPENBSD_NR_msgget 225 -#define TARGET_OPENBSD_NR_msgsnd 226 -#define TARGET_OPENBSD_NR_msgrcv 227 -#define TARGET_OPENBSD_NR_shmat 228 -#define TARGET_OPENBSD_NR_shmdt 230 -#define TARGET_OPENBSD_NR_clock_gettime 232 -#define TARGET_OPENBSD_NR_clock_settime 233 -#define TARGET_OPENBSD_NR_clock_getres 234 -#define TARGET_OPENBSD_NR_nanosleep 240 -#define TARGET_OPENBSD_NR_minherit 250 -#define TARGET_OPENBSD_NR_rfork 251 -#define TARGET_OPENBSD_NR_poll 252 -#define TARGET_OPENBSD_NR_issetugid 253 -#define TARGET_OPENBSD_NR_lchown 254 -#define TARGET_OPENBSD_NR_getsid 255 -#define TARGET_OPENBSD_NR_msync 256 -#define TARGET_OPENBSD_NR_pipe 263 -#define TARGET_OPENBSD_NR_fhopen 264 -#define TARGET_OPENBSD_NR_preadv 267 -#define TARGET_OPENBSD_NR_pwritev 268 -#define TARGET_OPENBSD_NR_kqueue 269 -#define TARGET_OPENBSD_NR_kevent 270 -#define TARGET_OPENBSD_NR_mlockall 271 -#define TARGET_OPENBSD_NR_munlockall 272 -#define TARGET_OPENBSD_NR_getpeereid 273 -#define TARGET_OPENBSD_NR_getresuid 281 -#define TARGET_OPENBSD_NR_setresuid 282 -#define TARGET_OPENBSD_NR_getresgid 283 -#define TARGET_OPENBSD_NR_setresgid 284 -#define TARGET_OPENBSD_NR_mquery 286 -#define TARGET_OPENBSD_NR_closefrom 287 -#define TARGET_OPENBSD_NR_sigaltstack 288 -#define TARGET_OPENBSD_NR_shmget 289 -#define TARGET_OPENBSD_NR_semop 290 -#define TARGET_OPENBSD_NR_stat 291 -#define TARGET_OPENBSD_NR_fstat 292 -#define TARGET_OPENBSD_NR_lstat 293 -#define TARGET_OPENBSD_NR_fhstat 294 -#define TARGET_OPENBSD_NR___semctl 295 -#define TARGET_OPENBSD_NR_shmctl 296 -#define TARGET_OPENBSD_NR_msgctl 297 -#define TARGET_OPENBSD_NR_sched_yield 298 -#define TARGET_OPENBSD_NR_getthrid 299 -#define TARGET_OPENBSD_NR_thrsleep 300 -#define TARGET_OPENBSD_NR_thrwakeup 301 -#define TARGET_OPENBSD_NR_threxit 302 -#define TARGET_OPENBSD_NR_thrsigdivert 303 -#define TARGET_OPENBSD_NR___getcwd 304 -#define TARGET_OPENBSD_NR_adjfreq 305 -#define TARGET_OPENBSD_NR_getfsstat 306 -#define TARGET_OPENBSD_NR_statfs 307 -#define TARGET_OPENBSD_NR_fstatfs 308 -#define TARGET_OPENBSD_NR_fhstatfs 309 - -/* syscall flags from machine/trap.h */ - -/* $OpenBSD: trap.h,v 1.4 2008/07/04 22:04:37 kettenis Exp $ */ -/* $NetBSD: trap.h,v 1.4 1999/06/07 05:28:04 eeh Exp $ */ - -/* - * Copyright (c) 1996-1999 Eduardo Horvath - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - */ -#define TARGET_OPENBSD_SYSCALL_G2RFLAG 0x400 /* on success, return to %g2 rather than npc */ -#define TARGET_OPENBSD_SYSCALL_G7RFLAG 0x800 /* use %g7 as above (deprecated) */ diff --git a/bsd-user/openbsd/target_os_elf.h b/bsd-user/openbsd/target_os_elf.h deleted file mode 100644 index 4cf5747dcd42..000000000000 --- a/bsd-user/openbsd/target_os_elf.h +++ /dev/null @@ -1,148 +0,0 @@ -/* - * openbsd ELF definitions - * - * Copyright (c) 2013 Stacey D. Son - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - */ - -#ifndef TARGET_OS_ELF_H -#define TARGET_OS_ELF_H - -#include "target_arch_elf.h" -#include "elf.h" -#include "user/tswap-target.h" - -/* this flag is uneffective under linux too, should be deleted */ -#ifndef MAP_DENYWRITE -#define MAP_DENYWRITE 0 -#endif - -/* should probably go in elf.h */ -#ifndef ELIBBAD -#define ELIBBAD 80 -#endif - -#ifndef ELF_PLATFORM -#define ELF_PLATFORM (NULL) -#endif - -#ifndef ELF_HWCAP -#define ELF_HWCAP 0 -#endif - -#ifdef TARGET_ABI32 -#undef ELF_CLASS -#define ELF_CLASS ELFCLASS32 -#undef bswaptls -#define bswaptls(ptr) bswap32s(ptr) -#endif - -/* max code+data+bss space allocated to elf interpreter */ -#define INTERP_MAP_SIZE (32 * 1024 * 1024) - -/* max code+data+bss+brk space allocated to ET_DYN executables */ -#define ET_DYN_MAP_SIZE (128 * 1024 * 1024) - -/* Necessary parameters */ -#define TARGET_ELF_EXEC_PAGESIZE TARGET_PAGE_SIZE -#define TARGET_ELF_PAGESTART(_v) ((_v) & \ - ~(unsigned long)(TARGET_ELF_EXEC_PAGESIZE - 1)) -#define TARGET_ELF_PAGEOFFSET(_v) ((_v) & (TARGET_ELF_EXEC_PAGESIZE - 1)) - -#define DLINFO_ITEMS 12 - -static abi_ulong target_create_elf_tables(abi_ulong p, int argc, int envc, - abi_ulong stringp, - struct elfhdr *exec, - abi_ulong load_addr, - abi_ulong load_bias, - abi_ulong interp_load_addr, - struct image_info *info) -{ - abi_ulong sp; - int size; - abi_ulong u_platform; - const char *k_platform; - const int n = sizeof(elf_addr_t); - - sp = p; - u_platform = 0; - k_platform = ELF_PLATFORM; - if (k_platform) { - size_t len = strlen(k_platform) + 1; - sp -= (len + n - 1) & ~(n - 1); - u_platform = sp; - /* FIXME - check return value of memcpy_to_target() for failure */ - memcpy_to_target(sp, k_platform, len); - } - /* - * Force 16 byte _final_ alignment here for generality. - */ - sp = sp & ~(abi_ulong)15; - size = (DLINFO_ITEMS + 1) * 2; - if (k_platform) { - size += 2; - } -#ifdef DLINFO_ARCH_ITEMS - size += DLINFO_ARCH_ITEMS * 2; -#endif - size += envc + argc + 2; - size += 1; /* argc itself */ - size *= n; - if (size & 15) { - sp -= 16 - (size & 15); - } - - /* - * OpenBSD defines elf_addr_t as Elf32_Off / Elf64_Off - */ -#define NEW_AUX_ENT(id, val) do { \ - sp -= n; put_user_ual(val, sp); \ - sp -= n; put_user_ual(id, sp); \ - } while (0) - - NEW_AUX_ENT(AT_NULL, 0); - - /* There must be exactly DLINFO_ITEMS entries here. */ - NEW_AUX_ENT(AT_PHDR, (abi_ulong)(load_addr + exec->e_phoff)); - NEW_AUX_ENT(AT_PHENT, (abi_ulong)(sizeof(struct elf_phdr))); - NEW_AUX_ENT(AT_PHNUM, (abi_ulong)(exec->e_phnum)); - NEW_AUX_ENT(AT_PAGESZ, (abi_ulong)(TARGET_PAGE_SIZE)); - NEW_AUX_ENT(AT_BASE, (abi_ulong)(interp_load_addr)); - NEW_AUX_ENT(AT_FLAGS, (abi_ulong)0); - NEW_AUX_ENT(AT_ENTRY, load_bias + exec->e_entry); - NEW_AUX_ENT(AT_UID, (abi_ulong)getuid()); - NEW_AUX_ENT(AT_EUID, (abi_ulong)geteuid()); - NEW_AUX_ENT(AT_GID, (abi_ulong)getgid()); - NEW_AUX_ENT(AT_EGID, (abi_ulong)getegid()); - NEW_AUX_ENT(AT_HWCAP, (abi_ulong)ELF_HWCAP); - NEW_AUX_ENT(AT_CLKTCK, (abi_ulong)sysconf(_SC_CLK_TCK)); - if (k_platform) { - NEW_AUX_ENT(AT_PLATFORM, u_platform); - } -#ifdef ARCH_DLINFO - /* - * ARCH_DLINFO must come last so platform specific code can enforce - * special alignment requirements on the AUXV if necessary (eg. PPC). - */ - ARCH_DLINFO; -#endif -#undef NEW_AUX_ENT - - sp = loader_build_argptr(envc, argc, sp, stringp); - return sp; -} - -#endif /* TARGET_OS_ELF_H */ diff --git a/bsd-user/openbsd/target_os_siginfo.h b/bsd-user/openbsd/target_os_siginfo.h deleted file mode 100644 index 732009a8201a..000000000000 --- a/bsd-user/openbsd/target_os_siginfo.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef TARGET_OS_SIGINFO_H -#define TARGET_OS_SIGINFO_H - -#define TARGET_NSIG 32 /* counting 0; could be 33 (mask is 1-32) */ -#define TARGET_NSIG_BPW (sizeof(uint32_t) * 8) -#define TARGET_NSIG_WORDS (TARGET_NSIG / TARGET_NSIG_BPW) - -/* this struct defines a stack used during syscall handling */ -typedef struct target_sigaltstack { - abi_long ss_sp; - abi_ulong ss_size; - abi_long ss_flags; -} target_stack_t; - -typedef struct { - uint32_t __bits[TARGET_NSIG_WORDS]; -} target_sigset_t - -struct target_sigaction { - abi_ulong _sa_handler; - int32_t sa_flags; - target_sigset_t sa_mask; -}; - -/* Compare to sys/siginfo.h */ -typedef union target_sigval { - int sival_int; - abi_ulong sival_ptr; -} target_sigval_t; - -struct target_ksiginfo { - int32_t _signo; - int32_t _code; - int32_t _errno; -#if TARGET_ABI_BITS == 64 - int32_t _pad; -#endif - union { - struct { - int32_t _pid; - int32_t _uid; - target_sigval_t _value; - } _rt; - - struct { - int32_t _pid; - int32_t _uid; - int32_t _struct; - /* clock_t _utime; */ - /* clock_t _stime; */ - } _child; - - struct { - abi_ulong _addr; - int32_t _trap; - } _fault; - - struct { - long _band; - int _fd; - } _poll; - } _reason; -}; - -typedef union target_siginfo { - int8_t si_pad[128]; - struct target_ksiginfo _info; -} target_siginfo_t; - -#define target_si_signo _info._signo -#define target_si_code _info._code -#define target_si_errno _info._errno -#define target_si_addr _info._reason._fault._addr - -#define TARGET_SEGV_MAPERR 1 -#define TARGET_SEGV_ACCERR 2 - -#define TARGET_TRAP_BRKPT 1 -#define TARGET_TRAP_TRACE 2 - - -#endif /* TARGET_OS_SIGINFO_H */ diff --git a/bsd-user/openbsd/target_os_signal.h b/bsd-user/openbsd/target_os_signal.h deleted file mode 100644 index 4ee4f768e0e1..000000000000 --- a/bsd-user/openbsd/target_os_signal.h +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef TARGET_OS_SIGNAL_H -#define TARGET_OS_SIGNAL_H - -#include "target_os_siginfo.h" -#include "target_arch_signal.h" - -#define TARGET_SIGHUP 1 /* hangup */ -#define TARGET_SIGINT 2 /* interrupt */ -#define TARGET_SIGQUIT 3 /* quit */ -#define TARGET_SIGILL 4 /* illegal instruction (not reset when caught) */ -#define TARGET_SIGTRAP 5 /* trace trap (not reset when caught) */ -#define TARGET_SIGABRT 6 /* abort() */ -#define TARGET_SIGIOT SIGABRT /* compatibility */ -#define TARGET_SIGEMT 7 /* EMT instruction */ -#define TARGET_SIGFPE 8 /* floating point exception */ -#define TARGET_SIGKILL 9 /* kill (cannot be caught or ignored) */ -#define TARGET_SIGBUS 10 /* bus error */ -#define TARGET_SIGSEGV 11 /* segmentation violation */ -#define TARGET_SIGSYS 12 /* bad argument to system call */ -#define TARGET_SIGPIPE 13 /* write on a pipe with no one to read it */ -#define TARGET_SIGALRM 14 /* alarm clock */ -#define TARGET_SIGTERM 15 /* software termination signal from kill */ -#define TARGET_SIGURG 16 /* urgent condition on IO channel */ -#define TARGET_SIGSTOP 17 /* sendable stop signal not from tty */ -#define TARGET_SIGTSTP 18 /* stop signal from tty */ -#define TARGET_SIGCONT 19 /* continue a stopped process */ -#define TARGET_SIGCHLD 20 /* to parent on child stop or exit */ -#define TARGET_SIGTTIN 21 /* to readers pgrp upon background tty read */ -#define TARGET_SIGTTOU 22 /* like TTIN for out if (tp->t_local<OSTOP) */ -#define TARGET_SIGIO 23 /* input/output possible signal */ -#define TARGET_SIGXCPU 24 /* exceeded CPU time limit */ -#define TARGET_SIGXFSZ 25 /* exceeded file size limit */ -#define TARGET_SIGVTALRM 26 /* virtual time alarm */ -#define TARGET_SIGPROF 27 /* profiling time alarm */ -#define TARGET_SIGWINCH 28 /* window size changes */ -#define TARGET_SIGINFO 29 /* information request */ -#define TARGET_SIGUSR1 30 /* user defined signal 1 */ -#define TARGET_SIGUSR2 31 /* user defined signal 2 */ - -/* - * Language spec says we must list exactly one parameter, even though we - * actually supply three. Ugh! - */ -#define TARGET_SIG_DFL ((void (*)(int))0) -#define TARGET_SIG_IGN ((void (*)(int))1) -#define TARGET_SIG_ERR ((void (*)(int))-1) - -#define TARGET_SA_ONSTACK 0x0001 /* take signal on signal stack */ -#define TARGET_SA_RESTART 0x0002 /* restart system on signal return */ -#define TARGET_SA_RESETHAND 0x0004 /* reset to SIG_DFL when taking signal */ -#define TARGET_SA_NODEFER 0x0010 /* don't mask the signal we're delivering */ -#define TARGET_SA_NOCLDWAIT 0x0020 /* don't create zombies (assign to pid 1) */ -#define TARGET_SA_USERTRAMP 0x0100 /* do not bounce off kernel's sigtramp */ -#define TARGET_SA_NOCLDSTOP 0x0008 /* do not generate SIGCHLD on child stop */ -#define TARGET_SA_SIGINFO 0x0040 /* generate siginfo_t */ - -/* - * Flags for sigprocmask: - */ -#define TARGET_SIG_BLOCK 1 /* block specified signal set */ -#define TARGET_SIG_UNBLOCK 2 /* unblock specified signal set */ -#define TARGET_SIG_SETMASK 3 /* set specified signal set */ - -#define TARGET_BADSIG SIG_ERR - -#define TARGET_SS_ONSTACK 0x0001 /* take signals on alternate stack */ -#define TARGET_SS_DISABLE 0x0004 /* disable taking signals on alternate stack */ - -#endif /* TARGET_OS_SIGNAL_H */ diff --git a/bsd-user/openbsd/target_os_stack.h b/bsd-user/openbsd/target_os_stack.h deleted file mode 100644 index 264a6586089b..000000000000 --- a/bsd-user/openbsd/target_os_stack.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * OpenBSD setup_initial_stack() implementation. - * - * Copyright (c) 2013-14 Stacey D. Son - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - */ - -#ifndef TARGET_OS_STACK_H -#define TARGET_OS_STACK_H - -#include "target_arch_sigtramp.h" - -static inline int setup_initial_stack(struct bsd_binprm *bprm, abi_ulong *p, - abi_ulong *stringp) -{ - int i; - abi_ulong stack_base; - - stack_base = (target_stkbas + target_stksiz) - - MAX_ARG_PAGES * TARGET_PAGE_SIZE; - if (p) { - *p = stack_base; - } - if (stringp) { - *stringp = stack_base; - } - - for (i = 0; i < MAX_ARG_PAGES; i++) { - if (bprm->page[i]) { - info->rss++; - if (!memcpy_to_target(stack_base, bprm->page[i], - TARGET_PAGE_SIZE)) { - errno = EFAULT; - return -1; - } - g_free(bprm->page[i]); - } - stack_base += TARGET_PAGE_SIZE; - } - - return 0; -} - -#endif /* TARGET_OS_STACK_H */ diff --git a/bsd-user/openbsd/target_os_thread.h b/bsd-user/openbsd/target_os_thread.h deleted file mode 100644 index c3adc6712fe5..000000000000 --- a/bsd-user/openbsd/target_os_thread.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * OpenBSD thread dependent code and definitions - * - * Copyright (c) 2013 Stacey D. Son - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - */ - -#ifndef TARGET_OS_THREAD_H -#define TARGET_OS_THREAD_H - -#include "target_arch_thread.h" - -#endif /* TARGET_OS_THREAD_H */ diff --git a/bsd-user/qemu-bsd.h b/bsd-user/qemu-bsd.h index 56affcd31d15..b98d1d76b2f3 100644 --- a/bsd-user/qemu-bsd.h +++ b/bsd-user/qemu-bsd.h @@ -54,4 +54,19 @@ abi_long target_to_host_shmid_ds(struct shmid_ds *host_sd, abi_long host_to_target_shmid_ds(abi_ulong target_addr, struct shmid_ds *host_sd); +/* bsd-misc.c */ +abi_long host_to_target_uuid(abi_ulong target_addr, struct uuid *host_uuid); +abi_long target_to_host_semarray(int semid, unsigned short **host_array, + abi_ulong target_addr); +abi_long host_to_target_semarray(int semid, abi_ulong target_addr, + unsigned short **host_array); +abi_long target_to_host_semid_ds(struct semid_ds *host_sd, + abi_ulong target_addr); +abi_long host_to_target_semid_ds(abi_ulong target_addr, + struct semid_ds *host_sd); +abi_long target_to_host_msqid_ds(struct msqid_ds *host_md, + abi_ulong target_addr); +abi_long host_to_target_msqid_ds(abi_ulong target_addr, + struct msqid_ds *host_md); + #endif /* QEMU_BSD_H */ diff --git a/bsd-user/qemu.h b/bsd-user/qemu.h index 93388e7c34eb..2c586e546ffc 100644 --- a/bsd-user/qemu.h +++ b/bsd-user/qemu.h @@ -179,12 +179,6 @@ abi_long do_freebsd_syscall(void *cpu_env, int num, abi_long arg1, abi_long arg2, abi_long arg3, abi_long arg4, abi_long arg5, abi_long arg6, abi_long arg7, abi_long arg8); -abi_long do_netbsd_syscall(void *cpu_env, int num, abi_long arg1, - abi_long arg2, abi_long arg3, abi_long arg4, - abi_long arg5, abi_long arg6); -abi_long do_openbsd_syscall(void *cpu_env, int num, abi_long arg1, - abi_long arg2, abi_long arg3, abi_long arg4, - abi_long arg5, abi_long arg6); void gemu_log(const char *fmt, ...) G_GNUC_PRINTF(1, 2); extern __thread CPUState *thread_cpu; char *target_strerror(int err); @@ -210,16 +204,6 @@ print_freebsd_syscall(int num, abi_long arg1, abi_long arg2, abi_long arg3, abi_long arg4, abi_long arg5, abi_long arg6); void print_freebsd_syscall_ret(int num, abi_long ret); -void -print_netbsd_syscall(int num, - abi_long arg1, abi_long arg2, abi_long arg3, - abi_long arg4, abi_long arg5, abi_long arg6); -void print_netbsd_syscall_ret(int num, abi_long ret); -void -print_openbsd_syscall(int num, - abi_long arg1, abi_long arg2, abi_long arg3, - abi_long arg4, abi_long arg5, abi_long arg6); -void print_openbsd_syscall_ret(int num, abi_long ret); /** * print_taken_signal: * @target_signum: target signal being taken diff --git a/bsd-user/strace.c b/bsd-user/strace.c index 6dc01d3be7e0..41bbef72ce8d 100644 --- a/bsd-user/strace.c +++ b/bsd-user/strace.c @@ -153,12 +153,6 @@ static void print_syscall_ret_addr(const struct syscallname *name, abi_long ret) static const struct syscallname freebsd_scnames[] = { #include "freebsd/strace.list" }; -static const struct syscallname netbsd_scnames[] = { -#include "netbsd/strace.list" -}; -static const struct syscallname openbsd_scnames[] = { -#include "openbsd/strace.list" -}; static void print_syscall(int num, const struct syscallname *scnames, unsigned int nscnames, abi_long arg1, abi_long arg2, abi_long arg3, @@ -230,34 +224,6 @@ void print_freebsd_syscall_ret(int num, abi_long ret) print_syscall_ret(num, ret, freebsd_scnames, ARRAY_SIZE(freebsd_scnames)); } -void print_netbsd_syscall(int num, abi_long arg1, abi_long arg2, abi_long arg3, - abi_long arg4, abi_long arg5, abi_long arg6) -{ - - print_syscall(num, netbsd_scnames, ARRAY_SIZE(netbsd_scnames), - arg1, arg2, arg3, arg4, arg5, arg6); -} - -void print_netbsd_syscall_ret(int num, abi_long ret) -{ - - print_syscall_ret(num, ret, netbsd_scnames, ARRAY_SIZE(netbsd_scnames)); -} - -void print_openbsd_syscall(int num, abi_long arg1, abi_long arg2, abi_long arg3, - abi_long arg4, abi_long arg5, abi_long arg6) -{ - - print_syscall(num, openbsd_scnames, ARRAY_SIZE(openbsd_scnames), arg1, arg2, - arg3, arg4, arg5, arg6); -} - -void print_openbsd_syscall_ret(int num, abi_long ret) -{ - - print_syscall_ret(num, ret, openbsd_scnames, ARRAY_SIZE(openbsd_scnames)); -} - static void print_signal(abi_ulong arg, int last) { diff --git a/bsd-user/syscall_defs.h b/bsd-user/syscall_defs.h index cc4b484f3ab1..4dbd90c2f11f 100644 --- a/bsd-user/syscall_defs.h +++ b/bsd-user/syscall_defs.h @@ -26,8 +26,6 @@ #include "errno_defs.h" #include "freebsd/syscall_nr.h" -#include "netbsd/syscall_nr.h" -#include "openbsd/syscall_nr.h" /* * machine/_types.h @@ -92,6 +90,62 @@ struct bsd_shm_regions { abi_long size; }; +/* + * sys/sem.h + */ +#define TARGET_GETNCNT 3 /* Return the value of semncnt {READ} */ +#define TARGET_GETPID 4 /* Return the value of sempid {READ} */ +#define TARGET_GETVAL 5 /* Return the value of semval {READ} */ +#define TARGET_GETALL 6 /* Return semvals into arg.array {READ} */ +#define TARGET_GETZCNT 7 /* Return the value of semzcnt {READ} */ +#define TARGET_SETVAL 8 /* Set the value of semval to arg.val {ALTER} */ +#define TARGET_SETALL 9 /* Set semvals from arg.array {ALTER} */ + +struct target_sembuf { + abi_ushort sem_num; /* semaphore # */ + abi_short sem_op; /* semaphore operation */ + abi_short sem_flg; /* operation flags */ +}; + +union target_semun { + abi_int val; /* value for SETVAL */ + abi_ulong buf; /* buffer for IPC_STAT & IPC_SET */ + abi_ulong array; /* array for GETALL & SETALL */ +}; + +struct target_semid_ds { + struct target_ipc_perm sem_perm; /* operation permission struct */ + abi_ptr sem_base; /* pointer to first semaphore in set */ + abi_ushort sem_nsems; /* number of sems in set */ + target_time_t sem_otime; /* last operation time */ + target_time_t sem_ctime; /* times measured in secs */ +}; + +/* + * sys/msg.h + */ +struct target_msqid_ds { + struct target_ipc_perm msg_perm; /* msg queue permission bits */ + abi_ptr msg_first; /* first message in the queue */ + abi_ptr msg_last; /* last message in the queue */ + abi_ulong msg_cbytes; /* # of bytes in use on the queue */ + abi_ulong msg_qnum; /* number of msgs in the queue */ + abi_ulong msg_qbytes; /* max # of bytes on the queue */ + int32_t msg_lspid; /* pid of last msgsnd() */ + int32_t msg_lrpid; /* pid of last msgrcv() */ + target_time_t msg_stime; /* time of last msgsnd() */ + target_time_t msg_rtime; /* time of last msgrcv() */ + target_time_t msg_ctime; /* time of last msgctl() */ +}; + +/* + * sys/msgbuf.h + */ +struct target_msgbuf { + abi_long mtype; /* message type */ + char mtext[1]; /* body of message */ +}; + /* * sys/mman.h */ @@ -108,27 +162,6 @@ struct bsd_shm_regions { #define TARGET_FREEBSD_MAP_FLAGMASK 0x1ff7 -#define TARGET_NETBSD_MAP_INHERIT 0x0080 /* region is retained after */ - /* exec */ -#define TARGET_NETBSD_MAP_TRYFIXED 0x0400 /* attempt hint address, even */ - /* within break */ -#define TARGET_NETBSD_MAP_WIRED 0x0800 /* mlock() mapping when it is */ - /* established */ - -#define TARGET_NETBSD_MAP_STACK 0x2000 /* allocated from memory, */ - /* swap space (stack) */ - -#define TARGET_NETBSD_MAP_FLAGMASK 0x3ff7 - -#define TARGET_OPENBSD_MAP_INHERIT 0x0080 /* region is retained after */ - /* exec */ -#define TARGET_OPENBSD_MAP_NOEXTEND 0x0100 /* for MAP_FILE, don't change */ - /* file size */ -#define TARGET_OPENBSD_MAP_TRYFIXED 0x0400 /* attempt hint address, */ - /* even within heap */ - -#define TARGET_OPENBSD_MAP_FLAGMASK 0x17f7 - /* XXX */ #define TARGET_BSD_MAP_FLAGMASK 0x3ff7 @@ -470,6 +503,20 @@ struct target_procctl_reaper_kill { uint32_t rk_pad0[15]; }; +/* + * sys/uuid.h + */ +#define TARGET_UUID_NODE_LEN 6 + +struct target_uuid { + uint32_t time_low; + uint16_t time_mid; + uint16_t time_hi_and_version; + uint8_t clock_seq_hi_and_reserved; + uint8_t clock_seq_low; + uint8_t node[TARGET_UUID_NODE_LEN]; +}; + #define safe_syscall0(type, name) \ type safe_##name(void) \ diff --git a/chardev/char-io.c b/chardev/char-io.c index beac5cd245eb..0e5ba316ed9f 100644 --- a/chardev/char-io.c +++ b/chardev/char-io.c @@ -88,6 +88,9 @@ static gboolean io_watch_poll_dispatch(GSource *source, GSourceFunc callback, static void io_watch_poll_finalize(GSource *source) { IOWatchPoll *iwp = io_watch_poll_from_source(source); + + object_unref(OBJECT(iwp->ioc)); + if (iwp->src) { g_source_destroy(iwp->src); g_source_unref(iwp->src); @@ -117,6 +120,8 @@ GSource *io_add_watch_poll(Chardev *chr, iwp->fd_can_read = fd_can_read; iwp->opaque = user_data; iwp->ioc = ioc; + object_ref(OBJECT(iwp->ioc)); + iwp->fd_read = (GSourceFunc) fd_read; iwp->src = NULL; iwp->context = context; diff --git a/chardev/char-socket.c b/chardev/char-socket.c index 5adeb9086590..e064b105c50b 100644 --- a/chardev/char-socket.c +++ b/chardev/char-socket.c @@ -370,7 +370,8 @@ static void tcp_chr_free_connection(Chardev *chr) tcp_chr_set_msgfds(chr, NULL, 0); remove_fd_in_watch(chr); - if (s->registered_yank && + + if (s->registered_yank && s->sioc && (s->state == TCP_CHARDEV_STATE_CONNECTING || s->state == TCP_CHARDEV_STATE_CONNECTED)) { yank_unregister_function(CHARDEV_YANK_INSTANCE(chr->label), @@ -904,6 +905,12 @@ static int tcp_chr_new_client(Chardev *chr, QIOChannelSocket *sioc) s->sioc = sioc; object_ref(OBJECT(sioc)); + if (s->registered_yank) { + yank_register_function(CHARDEV_YANK_INSTANCE(chr->label), + char_socket_yank_iochannel, + QIO_CHANNEL(sioc)); + } + if (s->do_nodelay) { qio_channel_set_delay(s->ioc, false); } @@ -942,11 +949,6 @@ static int tcp_chr_add_client(Chardev *chr, int fd) } tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING); tcp_chr_set_client_ioc_name(chr, sioc); - if (s->registered_yank) { - yank_register_function(CHARDEV_YANK_INSTANCE(chr->label), - char_socket_yank_iochannel, - QIO_CHANNEL(sioc)); - } ret = tcp_chr_new_client(chr, sioc); object_unref(OBJECT(sioc)); return ret; @@ -961,11 +963,6 @@ static void tcp_chr_accept(QIONetListener *listener, tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING); tcp_chr_set_client_ioc_name(chr, cioc); - if (s->registered_yank) { - yank_register_function(CHARDEV_YANK_INSTANCE(chr->label), - char_socket_yank_iochannel, - QIO_CHANNEL(cioc)); - } tcp_chr_new_client(chr, cioc); } @@ -981,11 +978,6 @@ static int tcp_chr_connect_client_sync(Chardev *chr, Error **errp) object_unref(OBJECT(sioc)); return -1; } - if (s->registered_yank) { - yank_register_function(CHARDEV_YANK_INSTANCE(chr->label), - char_socket_yank_iochannel, - QIO_CHANNEL(sioc)); - } tcp_chr_new_client(chr, sioc); object_unref(OBJECT(sioc)); return 0; @@ -1001,11 +993,6 @@ static void tcp_chr_accept_server_sync(Chardev *chr) tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING); sioc = qio_net_listener_wait_client(s->listener); tcp_chr_set_client_ioc_name(chr, sioc); - if (s->registered_yank) { - yank_register_function(CHARDEV_YANK_INSTANCE(chr->label), - char_socket_yank_iochannel, - QIO_CHANNEL(sioc)); - } tcp_chr_new_client(chr, sioc); object_unref(OBJECT(sioc)); } @@ -1179,11 +1166,6 @@ static void tcp_chr_connect_client_async(Chardev *chr) tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING); sioc = qio_channel_socket_new(); tcp_chr_set_client_ioc_name(chr, sioc); - if (s->registered_yank) { - yank_register_function(CHARDEV_YANK_INSTANCE(chr->label), - char_socket_yank_iochannel, - QIO_CHANNEL(sioc)); - } /* * Normally code would use the qio_channel_socket_connect_async * method which uses a QIOTask + qio_task_set_error internally diff --git a/common-user/safe-syscall.S b/common-user/safe-syscall.S index 74f7e35694fc..2f714cfbe866 100644 --- a/common-user/safe-syscall.S +++ b/common-user/safe-syscall.S @@ -22,6 +22,6 @@ * assembly needs an executable stack and the whole QEMU binary will * needlessly end up with one. This should be the last thing in this file. */ -#if defined(__linux__) && defined(__ELF__) +#if defined(__ELF__) .section .note.GNU-stack, "", %progbits #endif diff --git a/configs/devices/arm-softmmu/default.mak b/configs/devices/arm-softmmu/default.mak index 57ef1b8a7029..71cf16451104 100644 --- a/configs/devices/arm-softmmu/default.mak +++ b/configs/devices/arm-softmmu/default.mak @@ -12,7 +12,6 @@ # keep out of the build. # CONFIG_CUBIEBOARD=n # CONFIG_EXYNOS4=n -# CONFIG_HIGHBANK=n # CONFIG_INTEGRATOR=n # CONFIG_FSL_IMX31=n # CONFIG_MUSICPAL=n diff --git a/configs/devices/microblazeel-softmmu/default.mak b/configs/devices/microblazeel-softmmu/default.mak deleted file mode 100644 index 4c1086435bf3..000000000000 --- a/configs/devices/microblazeel-softmmu/default.mak +++ /dev/null @@ -1,6 +0,0 @@ -# Default configuration for microblazeel-softmmu - -# Boards are selected by default, uncomment to keep out of the build. -# CONFIG_PETALOGIX_S3ADSP1800=n -# CONFIG_PETALOGIX_ML605=n -# CONFIG_XLNX_ZYNQMP_PMU=n diff --git a/configs/targets/aarch64-bsd-user.mak b/configs/targets/aarch64-bsd-user.mak index 7f42e0604772..03979d82402c 100644 --- a/configs/targets/aarch64-bsd-user.mak +++ b/configs/targets/aarch64-bsd-user.mak @@ -1,4 +1,4 @@ TARGET_ARCH=aarch64 TARGET_BASE_ARCH=arm -TARGET_XML_FILES= gdb-xml/aarch64-core.xml gdb-xml/aarch64-fpu.xml gdb-xml/aarch64-pauth.xml gdb-xml/aarch64-sme2.xml +TARGET_XML_FILES= gdb-xml/aarch64-core.xml gdb-xml/aarch64-fpu.xml gdb-xml/aarch64-pauth.xml gdb-xml/aarch64-mte.xml gdb-xml/aarch64-sme2.xml TARGET_LONG_BITS=64 diff --git a/configs/targets/aarch64-softmmu.c b/configs/targets/aarch64-softmmu.c index 4e1e2f64da13..82ccb5757599 100644 --- a/configs/targets/aarch64-softmmu.c +++ b/configs/targets/aarch64-softmmu.c @@ -10,6 +10,7 @@ #include "qemu/target-info-impl.h" #include "hw/arm/machines-qom.h" #include "target/arm/cpu-qom.h" +#include "target/arm/cpu-param.h" static const TargetInfo target_info_aarch64_system = { .target_name = "aarch64", @@ -18,6 +19,8 @@ static const TargetInfo target_info_aarch64_system = { .cpu_type = TYPE_ARM_CPU, .machine_typename = TYPE_TARGET_AARCH64_MACHINE, .endianness = ENDIAN_MODE_LITTLE, + .page_bits_vary = true, + .page_bits_init = TARGET_PAGE_BITS_LEGACY, }; const TargetInfo *target_info(void) diff --git a/configs/targets/arm-softmmu.c b/configs/targets/arm-softmmu.c index 9b3fdd2854a4..18940e51e558 100644 --- a/configs/targets/arm-softmmu.c +++ b/configs/targets/arm-softmmu.c @@ -10,6 +10,7 @@ #include "qemu/target-info-impl.h" #include "hw/arm/machines-qom.h" #include "target/arm/cpu-qom.h" +#include "target/arm/cpu-param.h" static const TargetInfo target_info_arm_system = { .target_name = "arm", @@ -18,6 +19,8 @@ static const TargetInfo target_info_arm_system = { .cpu_type = TYPE_ARM_CPU, .machine_typename = TYPE_TARGET_ARM_MACHINE, .endianness = ENDIAN_MODE_LITTLE, + .page_bits_vary = true, + .page_bits_init = TARGET_PAGE_BITS_LEGACY, }; const TargetInfo *target_info(void) diff --git a/configs/targets/microblazeel-softmmu.mak b/configs/targets/microblazeel-softmmu.mak deleted file mode 100644 index 52feb957b488..000000000000 --- a/configs/targets/microblazeel-softmmu.mak +++ /dev/null @@ -1,6 +0,0 @@ -TARGET_ARCH=microblaze -# needed by boot.c -TARGET_NEED_FDT=y -TARGET_XML_FILES=gdb-xml/microblaze-core.xml gdb-xml/microblaze-stack-protect.xml -TARGET_LONG_BITS=32 -TARGET_NOT_USING_LEGACY_LDST_PHYS_API=y diff --git a/configs/targets/or1k-linux-user.mak b/configs/targets/or1k-linux-user.mak index 0ee3a05884d8..9f8c178ef100 100644 --- a/configs/targets/or1k-linux-user.mak +++ b/configs/targets/or1k-linux-user.mak @@ -2,5 +2,6 @@ TARGET_ARCH=or1k TARGET_BIG_ENDIAN=y TARGET_SYSTBL_ABI=common,32,or1k,time32,stat64,rlimit,renameat TARGET_SYSTBL=syscall.tbl +TARGET_XML_FILES= gdb-xml/or1k-core.xml TARGET_LONG_BITS=32 TARGET_NOT_USING_LEGACY_NATIVE_ENDIAN_API=y diff --git a/configs/targets/or1k-softmmu.mak b/configs/targets/or1k-softmmu.mak index 204283d604fe..24d89bc3e658 100644 --- a/configs/targets/or1k-softmmu.mak +++ b/configs/targets/or1k-softmmu.mak @@ -2,6 +2,7 @@ TARGET_ARCH=or1k TARGET_BIG_ENDIAN=y # needed by boot.c and all boards TARGET_NEED_FDT=y +TARGET_XML_FILES= gdb-xml/or1k-core.xml TARGET_LONG_BITS=32 TARGET_NOT_USING_LEGACY_LDST_PHYS_API=y TARGET_NOT_USING_LEGACY_NATIVE_ENDIAN_API=y diff --git a/configs/targets/sparc-linux-user.mak b/configs/targets/sparc-linux-user.mak index d3f0716ca2db..01446e287834 100644 --- a/configs/targets/sparc-linux-user.mak +++ b/configs/targets/sparc-linux-user.mak @@ -2,5 +2,6 @@ TARGET_ARCH=sparc TARGET_SYSTBL_ABI=common,32 TARGET_SYSTBL=syscall.tbl TARGET_BIG_ENDIAN=y +TARGET_XML_FILES=gdb-xml/sparc32-cpu.xml gdb-xml/sparc32-fpu.xml gdb-xml/sparc32-cp0.xml TARGET_LONG_BITS=32 TARGET_NOT_USING_LEGACY_NATIVE_ENDIAN_API=y diff --git a/configs/targets/sparc-softmmu.mak b/configs/targets/sparc-softmmu.mak index c4c38946d543..ed846735f418 100644 --- a/configs/targets/sparc-softmmu.mak +++ b/configs/targets/sparc-softmmu.mak @@ -1,5 +1,6 @@ TARGET_ARCH=sparc TARGET_BIG_ENDIAN=y +TARGET_XML_FILES=gdb-xml/sparc32-cpu.xml gdb-xml/sparc32-fpu.xml gdb-xml/sparc32-cp0.xml TARGET_LONG_BITS=32 TARGET_NOT_USING_LEGACY_LDST_PHYS_API=y TARGET_NOT_USING_LEGACY_NATIVE_ENDIAN_API=y diff --git a/configs/targets/sparc32plus-linux-user.mak b/configs/targets/sparc32plus-linux-user.mak index 3e6c72e793ee..cf49c53ce44c 100644 --- a/configs/targets/sparc32plus-linux-user.mak +++ b/configs/targets/sparc32plus-linux-user.mak @@ -5,5 +5,6 @@ TARGET_ABI_DIR=sparc TARGET_SYSTBL_ABI=common,32 TARGET_SYSTBL=syscall.tbl TARGET_BIG_ENDIAN=y +TARGET_XML_FILES=gdb-xml/sparc64-cpu.xml gdb-xml/sparc64-fpu.xml gdb-xml/sparc64-cp0.xml TARGET_LONG_BITS=64 TARGET_NOT_USING_LEGACY_NATIVE_ENDIAN_API=y diff --git a/configs/targets/sparc64-linux-user.mak b/configs/targets/sparc64-linux-user.mak index 3bbd8495210c..81d18fcc85be 100644 --- a/configs/targets/sparc64-linux-user.mak +++ b/configs/targets/sparc64-linux-user.mak @@ -4,6 +4,6 @@ TARGET_ABI_DIR=sparc TARGET_SYSTBL_ABI=common,64 TARGET_SYSTBL=syscall.tbl TARGET_BIG_ENDIAN=y -TARGET_XML_FILES=gdb-xml/sparc64-core.xml +TARGET_XML_FILES=gdb-xml/sparc64-cpu.xml gdb-xml/sparc64-fpu.xml gdb-xml/sparc64-cp0.xml TARGET_LONG_BITS=64 TARGET_NOT_USING_LEGACY_NATIVE_ENDIAN_API=y diff --git a/configs/targets/sparc64-softmmu.mak b/configs/targets/sparc64-softmmu.mak index 8a0290c2093b..602783ef0f62 100644 --- a/configs/targets/sparc64-softmmu.mak +++ b/configs/targets/sparc64-softmmu.mak @@ -1,7 +1,7 @@ TARGET_ARCH=sparc64 TARGET_BASE_ARCH=sparc TARGET_BIG_ENDIAN=y -TARGET_XML_FILES=gdb-xml/sparc64-core.xml +TARGET_XML_FILES=gdb-xml/sparc64-cpu.xml gdb-xml/sparc64-fpu.xml gdb-xml/sparc64-cp0.xml TARGET_LONG_BITS=64 TARGET_NOT_USING_LEGACY_LDST_PHYS_API=y TARGET_NOT_USING_LEGACY_NATIVE_ENDIAN_API=y diff --git a/configure b/configure index 4b61fd3bbf80..cd1dadd8bb26 100755 --- a/configure +++ b/configure @@ -527,8 +527,8 @@ first_python= if test -z "${PYTHON}"; then # A bare 'python' is traditionally python 2.x, but some distros # have it as python 3.x, so check in both places. - for binary in python3 python python3.13 python3.12 python3.11 \ - python3.10 python3.9 ; do + for binary in python3 python python3.14 python3.13 python3.12 \ + python3.11 python3.10 python3.9 ; do if has "$binary"; then python=$(command -v "$binary") if check_py_version "$python"; then @@ -963,7 +963,9 @@ fi python="$python -B" mkvenv="$python ${source_path}/python/scripts/mkvenv.py" -# Finish preparing the virtual environment using vendored .whl files +# Finish preparing the virtual environment using vendored .whl files. +# Even if PyPI is allowed, we disallow it here to force installation +# from our preferred vendored versions. $mkvenv ensuregroup --dir "${source_path}/python/wheels" \ ${source_path}/pythondeps.toml meson || exit 1 @@ -993,13 +995,19 @@ if test "$host_os" = "haiku" && test ! -e "$meson" ; then meson="$(cd pyvenv/non-packaged/bin; get_pwd)/meson" fi -# Conditionally ensure Sphinx is installed. - mkvenv_online_flag="" if test "$download" = "enabled" ; then mkvenv_online_flag=" --online" fi +# Unconditionally install our tooling group. PyPI is allowed if enabled here. + +$mkvenv ensuregroup $mkvenv_online_flag \ + --dir "${source_path}/python/wheels" \ + ${source_path}/pythondeps.toml tooling || exit 1 + +# Conditionally ensure Sphinx is installed. + if test "$docs" != "disabled" ; then if ! $mkvenv ensuregroup \ $(test "$docs" = "enabled" && echo "$mkvenv_online_flag") \ @@ -1311,17 +1319,11 @@ fi ########################################## # functions to probe cross compilers -container="no" -runc="" +runc="no" if test $use_containers = "yes" && (has "docker" || has "podman"); then - case $($python "$source_path"/tests/docker/docker.py --engine "$container_engine" probe) in - *docker) container=docker ;; - podman) container=podman ;; - no) container=no ;; - esac - if test "$container" != "no"; then - docker_py="$python $source_path/tests/docker/docker.py --engine $container" - runc=$container + runc=$($python "$source_path"/tests/docker/docker.py --engine "$container_engine" probe) + if test "$runc" != "no"; then + docker_py="$python $source_path/tests/docker/docker.py --engine $container_engine" fi fi @@ -1441,7 +1443,7 @@ probe_target_compiler() { esac for host in $container_hosts; do - test "$container" != no || continue + test "$runc" != no || continue test "$host" = "$cpu" || continue case $target_arch in # debian-all-test-cross architectures @@ -1450,7 +1452,7 @@ probe_target_compiler() { container_image=debian-all-test-cross container_cross_prefix=aarch64-linux-gnu- ;; - hppa|m68k|mips|riscv64|sparc64) + alpha|hppa|m68k|mips|riscv64|sh4|sparc64) container_image=debian-all-test-cross ;; mips64) @@ -1462,14 +1464,6 @@ probe_target_compiler() { container_cross_prefix=powerpc${target_arch#ppc}-linux-gnu- ;; - # debian-legacy-test-cross architectures (need Debian 11) - # - libc6.1-dev-alpha-cross: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1054412 - # - sh4-linux-user: binaries don't run with bookworm compiler - - alpha|sh4) - container_image=debian-legacy-test-cross - ;; - # architectures with individual containers aarch64) @@ -1771,8 +1765,9 @@ echo all: >> $config_host_mak echo "SRC_PATH=$source_path" >> $config_host_mak echo "TARGET_DIRS=$target_list" >> $config_host_mak echo "GDB=$gdb_bin" >> $config_host_mak -if test "$container" != no; then +if test "$runc" != no; then echo "RUNC=$runc" >> $config_host_mak + echo "CONTAINER_ENGINE=$container_engine" >> $config_host_mak fi echo "SUBDIRS=$subdirs" >> $config_host_mak if test "$rust" != disabled; then diff --git a/contrib/elf2dmp/main.c b/contrib/elf2dmp/main.c index d046a72ae67f..a62abadcc049 100644 --- a/contrib/elf2dmp/main.c +++ b/contrib/elf2dmp/main.c @@ -494,18 +494,6 @@ static bool pe_check_pdb_name(uint64_t base, void *start_addr, return !strcmp(pdb_name, PDB_NAME); } -static void pe_get_pdb_symstore_hash(OMFSignatureRSDS *rsds, char *hash) -{ - sprintf(hash, "%.08x%.04x%.04x%.02x%.02x", rsds->guid.a, rsds->guid.b, - rsds->guid.c, rsds->guid.d[0], rsds->guid.d[1]); - hash += 20; - for (unsigned int i = 0; i < 6; i++, hash += 2) { - sprintf(hash, "%.02x", rsds->guid.e[i]); - } - - sprintf(hash, "%.01x", rsds->age); -} - int main(int argc, char *argv[]) { int err = 1; @@ -517,9 +505,7 @@ int main(int argc, char *argv[]) uint64_t KernBase; void *nt_start_addr = NULL; WinDumpHeader64 header; - char pdb_hash[34]; - char pdb_url[] = SYM_URL_BASE PDB_NAME - "/0123456789ABCDEF0123456789ABCDEFx/" PDB_NAME; + g_autofree char *pdb_url = NULL; struct pdb_reader pdb; uint64_t KdDebuggerDataBlock; KDDEBUGGER_DATA64 *kdbg; @@ -583,9 +569,21 @@ int main(int argc, char *argv[]) printf("KernBase = 0x%016"PRIx64", signature is \'%.2s\'\n", KernBase, (char *)nt_start_addr); - pe_get_pdb_symstore_hash(&rsds, pdb_hash); + pdb_url = g_strdup_printf("%s" + "%.08x%.04x%.04x" + "%.02x%.02x" + "%.02x%.02x" + "%.02x%.02x" + "%.02x%.02x%.01x" + "%s", + SYM_URL_BASE PDB_NAME "/", + rsds.guid.a, rsds.guid.b, rsds.guid.c, + rsds.guid.d[0], rsds.guid.d[1], + rsds.guid.e[0], rsds.guid.e[1], + rsds.guid.e[2], rsds.guid.e[3], + rsds.guid.e[4], rsds.guid.e[5], rsds.age, + "/" PDB_NAME); - sprintf(pdb_url, "%s%s/%s/%s", SYM_URL_BASE, PDB_NAME, pdb_hash, PDB_NAME); printf("PDB URL is %s\n", pdb_url); if (!download_url(PDB_NAME, pdb_url)) { diff --git a/contrib/plugins/bbv.c b/contrib/plugins/bbv.c index b9da6f815e36..5913cc6c637d 100644 --- a/contrib/plugins/bbv.c +++ b/contrib/plugins/bbv.c @@ -30,8 +30,13 @@ static uint64_t interval = 100000000; static void plugin_exit(qemu_plugin_id_t id, void *p) { + Vcpu *vcpu; + for (int i = 0; i < qemu_plugin_num_vcpus(); i++) { - fclose(((Vcpu *)qemu_plugin_scoreboard_find(vcpus, i))->file); + vcpu = qemu_plugin_scoreboard_find(vcpus, i); + if (vcpu->file) { + fclose(vcpu->file); + } } g_hash_table_unref(bbs); diff --git a/contrib/plugins/cpp.cpp b/contrib/plugins/cpp.cpp index 1ff54896d974..0673c4f074db 100644 --- a/contrib/plugins/cpp.cpp +++ b/contrib/plugins/cpp.cpp @@ -6,103 +6,360 @@ #include -/* https://en.cppreference.com/w/cpp/headers.html */ +/* + * We include all C++ standard headers (without deprecated ones), + * taken from: https://en.cppreference.com/w/cpp/headers.html + * + * To update, copy page text, and then: + * grep '^<' | + * sort -u | + * grep -v strstream | + * grep -v codecvt | + * sed -e 's/\(.*\)/#if __has_include(\1)\n#include \1\n#endif/' + */ + +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include -#include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() +#include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() +#include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() +#include +#endif +#if __has_include() #include +#endif +#if __has_include() +#include +#endif +#if __has_include() +#include +#endif +#if __has_include() +#include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() +#include +#endif +#if __has_include() +#include +#endif +#if __has_include() +#include +#endif +#if __has_include() #include +#endif +#if __has_include() +#include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() +#include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() +#include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() +#include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() +#include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() +#include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() +#include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() +#include +#endif +#if __has_include() #include +#endif +#if __has_include() +#include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() +#include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif +#if __has_include() #include +#endif QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; diff --git a/contrib/plugins/uftrace_symbols.py b/contrib/plugins/uftrace_symbols.py index 21704541a6c1..02d1221228c9 100755 --- a/contrib/plugins/uftrace_symbols.py +++ b/contrib/plugins/uftrace_symbols.py @@ -45,6 +45,8 @@ def get_symbols(elf_file): continue addr = int(addr, 16) size = int(size, 16) + if size == 0: + continue symbols.append(Symbol(name, addr, size)) symbols.sort(key = lambda x: x.addr) return symbols @@ -59,6 +61,9 @@ def find_symbols_locations(elf_file, symbols): print(e.output) raise out = out.strip().split('\n') + # filter out some addr2line error messages + skip_err='addr2line: DWARF error: mangled line number section (bad file number)' + out = [src for src in out if src != skip_err] assert len(out) == len(symbols) for i in range(len(symbols)): s = symbols[i] diff --git a/contrib/vhost-user-bridge/meson.build b/contrib/vhost-user-bridge/meson.build new file mode 100644 index 000000000000..aa58c1df20f5 --- /dev/null +++ b/contrib/vhost-user-bridge/meson.build @@ -0,0 +1,4 @@ +if have_tools and have_vhost_user and host_os == 'linux' + executable('vhost-user-bridge', files('vhost-user-bridge.c'), + dependencies: [qemuutil, vhost_user], install: false) +endif diff --git a/tests/vhost-user-bridge.c b/contrib/vhost-user-bridge/vhost-user-bridge.c similarity index 98% rename from tests/vhost-user-bridge.c rename to contrib/vhost-user-bridge/vhost-user-bridge.c index ce4c3426d393..3f0fd0fa49fe 100644 --- a/tests/vhost-user-bridge.c +++ b/contrib/vhost-user-bridge/vhost-user-bridge.c @@ -179,6 +179,9 @@ vubr_handle_tx(VuDev *dev, int qidx) assert(qidx % 2); + DPRINT("\n\n *** IN UDP TRANSMIT HANDLER ***\n\n"); + DPRINT(" hdrlen = %d\n", hdrlen); + for (;;) { ssize_t ret; unsigned int out_num; @@ -333,6 +336,10 @@ vubr_backend_recv_cb(int sock, void *ctx) }; ret = RETRY_ON_EINTR(recvmsg(vubr->backend_udp_sock, &msg, 0)); + if (ret > 0 && VHOST_USER_BRIDGE_DEBUG) { + iov_hexdump(sg, num, stderr, "RX:", ret); + } + if (i == 0) { iov_restore_front(elem->in_sg, sg, hdrlen); } diff --git a/disas/meson.build b/disas/meson.build index bbfa11978352..42977a1f74dd 100644 --- a/disas/meson.build +++ b/disas/meson.build @@ -7,7 +7,8 @@ common_ss.add(when: 'CONFIG_MIPS_DIS', if_true: files('mips.c', 'nanomips.c')) common_ss.add(when: 'CONFIG_RISCV_DIS', if_true: files( 'riscv.c', 'riscv-xthead.c', - 'riscv-xventana.c' + 'riscv-xventana.c', + 'riscv-xlrbr.c' )) common_ss.add(when: 'CONFIG_SH4_DIS', if_true: files('sh4.c')) common_ss.add(when: 'CONFIG_SPARC_DIS', if_true: files('sparc.c')) diff --git a/disas/riscv-xlrbr.c b/disas/riscv-xlrbr.c new file mode 100644 index 000000000000..57cb43452302 --- /dev/null +++ b/disas/riscv-xlrbr.c @@ -0,0 +1,79 @@ +/* + * QEMU RISC-V Disassembler for xlrbr matching the unratified Zbr CRC32 + * bitmanip extension v0.93. + * + * Copyright (c) 2023 Rivos Inc + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" + +#include "disas/riscv.h" +#include "disas/riscv-xlrbr.h" + +typedef enum { + /* 0 is reserved for rv_op_illegal. */ + rv_op_crc32_b = 1, + rv_op_crc32_h = 2, + rv_op_crc32_w = 3, + rv_op_crc32_d = 4, + rv_op_crc32c_b = 5, + rv_op_crc32c_h = 6, + rv_op_crc32c_w = 7, + rv_op_crc32c_d = 8, +} rv_xlrbr_op; + +const rv_opcode_data rv_xlrbr_opcode_data[] = { + { "illegal", rv_codec_illegal, rv_fmt_none, NULL, 0, 0, 0 }, + { "crc32.b", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "crc32.h", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "crc32.w", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "crc32.d", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "crc32c.b", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "crc32c.h", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "crc32c.w", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "crc32c.d", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, +}; + +void decode_xlrbr(rv_decode *dec, rv_isa isa) +{ + rv_inst inst = dec->inst; + rv_opcode op = rv_op_illegal; + + switch ((inst >> 0) & 0b1111111) { + case 0b0010011: + switch ((inst >> 12) & 0b111) { + case 0b001: + switch ((inst >> 20 & 0b111111111111)) { + case 0b011000010000: + op = rv_op_crc32_b; + break; + case 0b011000010001: + op = rv_op_crc32_h; + break; + case 0b011000010010: + op = rv_op_crc32_w; + break; + case 0b011000010011: + op = rv_op_crc32_d; + break; + case 0b011000011000: + op = rv_op_crc32c_b; + break; + case 0b011000011001: + op = rv_op_crc32c_h; + break; + case 0b011000011010: + op = rv_op_crc32c_w; + break; + case 0b011000011011: + op = rv_op_crc32c_d; + break; + } + break; + } + break; + } + dec->op = op; +} diff --git a/disas/riscv-xlrbr.h b/disas/riscv-xlrbr.h new file mode 100644 index 000000000000..939a69ea6db6 --- /dev/null +++ b/disas/riscv-xlrbr.h @@ -0,0 +1,19 @@ +/* + * QEMU RISC-V Disassembler for xlrbr matching the unratified Zbr CRC32 + * bitmanip extension v0.93. + * + * Copyright (c) 2023 Rivos Inc + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef DISAS_RISCV_XLRBR_H +#define DISAS_RISCV_XLRBR_H + +#include "disas/riscv.h" + +extern const rv_opcode_data rv_xlrbr_opcode_data[]; + +void decode_xlrbr(rv_decode *, rv_isa); + +#endif /* DISAS_RISCV_XLRBR_H */ diff --git a/disas/riscv.c b/disas/riscv.c index 6f2667482dc9..d416a4d6b376 100644 --- a/disas/riscv.c +++ b/disas/riscv.c @@ -26,6 +26,7 @@ /* Vendor extensions */ #include "disas/riscv-xthead.h" #include "disas/riscv-xventana.h" +#include "disas/riscv-xlrbr.h" typedef enum { /* 0 is reserved for rv_op_illegal. */ @@ -5434,6 +5435,7 @@ static GString *disasm_inst(rv_isa isa, uint64_t pc, rv_inst inst, { has_xtheadmempair_p, xthead_opcode_data, decode_xtheadmempair }, { has_xtheadsync_p, xthead_opcode_data, decode_xtheadsync }, { has_XVentanaCondOps_p, ventana_opcode_data, decode_xventanacondops }, + { has_xlrbr_p, rv_xlrbr_opcode_data, decode_xlrbr }, }; for (size_t i = 0; i < ARRAY_SIZE(decoders); i++) { diff --git a/docs/COLO-FT.txt b/docs/COLO-FT.txt deleted file mode 100644 index 2283a09c080b..000000000000 --- a/docs/COLO-FT.txt +++ /dev/null @@ -1,334 +0,0 @@ -COarse-grained LOck-stepping Virtual Machines for Non-stop Service ----------------------------------------- -Copyright (c) 2016 Intel Corporation -Copyright (c) 2016 HUAWEI TECHNOLOGIES CO., LTD. -Copyright (c) 2016 Fujitsu, Corp. - -This work is licensed under the terms of the GNU GPL, version 2 or later. -See the COPYING file in the top-level directory. - -This document gives an overview of COLO's design and how to use it. - -== Background == -Virtual machine (VM) replication is a well known technique for providing -application-agnostic software-implemented hardware fault tolerance, -also known as "non-stop service". - -COLO (COarse-grained LOck-stepping) is a high availability solution. -Both primary VM (PVM) and secondary VM (SVM) run in parallel. They receive the -same request from client, and generate response in parallel too. -If the response packets from PVM and SVM are identical, they are released -immediately. Otherwise, a VM checkpoint (on demand) is conducted. - -== Architecture == - -The architecture of COLO is shown in the diagram below. -It consists of a pair of networked physical nodes: -The primary node running the PVM, and the secondary node running the SVM -to maintain a valid replica of the PVM. -PVM and SVM execute in parallel and generate output of response packets for -client requests according to the application semantics. - -The incoming packets from the client or external network are received by the -primary node, and then forwarded to the secondary node, so that both the PVM -and the SVM are stimulated with the same requests. - -COLO receives the outbound packets from both the PVM and SVM and compares them -before allowing the output to be sent to clients. - -The SVM is qualified as a valid replica of the PVM, as long as it generates -identical responses to all client requests. Once the differences in the outputs -are detected between the PVM and SVM, COLO withholds transmission of the -outbound packets until it has successfully synchronized the PVM state to the SVM. - - Primary Node Secondary Node -+------------+ +-----------------------+ +------------------------+ +------------+ -| | | HeartBeat +<----->+ HeartBeat | | | -| Primary VM | +-----------+-----------+ +-----------+------------+ |Secondary VM| -| | | | | | -| | +-----------|-----------+ +-----------|------------+ | | -| | |QEMU +---v----+ | |QEMU +----v---+ | | | -| | | |Failover| | | |Failover| | | | -| | | +--------+ | | +--------+ | | | -| | | +---------------+ | | +---------------+ | | | -| | | | VM Checkpoint +-------------->+ VM Checkpoint | | | | -| | | +---------------+ | | +---------------+ | | | -|Requests<--------------------------\ /-----------------\ /--------------------->Requests| -| | | ^ ^ | | | | | | | -|Responses+---------------------\ /-|-|------------\ /-------------------------+Responses| -| | | | | | | | | | | | | | | | -| | | +-----------+ | | | | | | | | | | +----------+ | | | -| | | | COLO disk | | | | | | | | | | | | COLO disk| | | | -| | | | Manager +---------------------------->| Manager | | | | -| | | ++----------+ v v | | | | | v v | +---------++ | | | -| | | |+-----------+-+-+-++| | ++-+--+-+---------+ | | | | -| | | || COLO Proxy || | | COLO Proxy | | | | | -| | | || (compare packet || | |(adjust sequence | | | | | -| | | ||and mirror packet)|| | | and ACK) | | | | | -| | | |+------------+---+-+| | +-----------------+ | | | | -+------------+ +-----------------------+ +------------------------+ +------------+ -+------------+ | | | | +------------+ -| VM Monitor | | | | | | VM Monitor | -+------------+ | | | | +------------+ -+---------------------------------------+ +----------------------------------------+ -| Kernel | | | | | Kernel | | -+---------------------------------------+ +----------------------------------------+ - | | | | - +--------------v+ +---------v---+--+ +------------------+ +v-------------+ - | Storage | |External Network| | External Network | | Storage | - +---------------+ +----------------+ +------------------+ +--------------+ - - -== Components introduction == - -You can see there are several components in COLO's diagram of architecture. -Their functions are described below. - -HeartBeat: -Runs on both the primary and secondary nodes, to periodically check platform -availability. When the primary node suffers a hardware fail-stop failure, -the heartbeat stops responding, the secondary node will trigger a failover -as soon as it determines the absence. - -COLO disk Manager: -When primary VM writes data into image, the colo disk manager captures this data -and sends it to secondary VM's which makes sure the context of secondary VM's -image is consistent with the context of primary VM 's image. -For more details, please refer to docs/block-replication.txt. - -Checkpoint/Failover Controller: -Modifications of save/restore flow to realize continuous migration, -to make sure the state of VM in Secondary side is always consistent with VM in -Primary side. - -COLO Proxy: -Delivers packets to Primary and Secondary, and then compare the responses from -both side. Then decide whether to start a checkpoint according to some rules. -Please refer to docs/colo-proxy.txt for more information. - -Note: -HeartBeat has not been implemented yet, so you need to trigger failover process -by using 'x-colo-lost-heartbeat' command. - -== COLO operation status == - -+-----------------+ -| | -| Start COLO | -| | -+--------+--------+ - | - | Main qmp command: - | migrate-set-capabilities with x-colo - | migrate - | - v -+--------+--------+ -| | -| COLO running | -| | -+--------+--------+ - | - | Main qmp command: - | x-colo-lost-heartbeat - | or - | some error happened - v -+--------+--------+ -| | send qmp event: -| COLO failover | COLO_EXIT -| | -+-----------------+ - -COLO use the qmp command to switch and report operation status. -The diagram just shows the main qmp command, you can get the detail -in test procedure. - -== Test procedure == -Note: Here we are running both instances on the same host for testing, -change the IP Addresses if you want to run it on two hosts. Initially -127.0.0.1 is the Primary Host and 127.0.0.2 is the Secondary Host. - -== Startup qemu == -1. Primary: -Note: Initially, $imagefolder/primary.qcow2 needs to be copied to all hosts. -You don't need to change any IP's here, because 0.0.0.0 listens on any -interface. The chardev's with 127.0.0.1 IP's loopback to the local qemu -instance. - -# imagefolder="/mnt/vms/colo-test-primary" - -# qemu-system-x86_64 -enable-kvm -cpu qemu64,kvmclock=on -m 512 -smp 1 -qmp stdio \ - -device piix3-usb-uhci -device usb-tablet -name primary \ - -netdev tap,id=hn0,vhost=off,helper=/usr/lib/qemu/qemu-bridge-helper \ - -device rtl8139,id=e0,netdev=hn0 \ - -chardev socket,id=mirror0,host=0.0.0.0,port=9003,server=on,wait=off \ - -chardev socket,id=compare1,host=0.0.0.0,port=9004,server=on,wait=on \ - -chardev socket,id=compare0,host=127.0.0.1,port=9001,server=on,wait=off \ - -chardev socket,id=compare0-0,host=127.0.0.1,port=9001 \ - -chardev socket,id=compare_out,host=127.0.0.1,port=9005,server=on,wait=off \ - -chardev socket,id=compare_out0,host=127.0.0.1,port=9005 \ - -object filter-mirror,id=m0,netdev=hn0,queue=tx,outdev=mirror0 \ - -object filter-redirector,netdev=hn0,id=redire0,queue=rx,indev=compare_out \ - -object filter-redirector,netdev=hn0,id=redire1,queue=rx,outdev=compare0 \ - -object iothread,id=iothread1 \ - -object colo-compare,id=comp0,primary_in=compare0-0,secondary_in=compare1,\ -outdev=compare_out0,iothread=iothread1 \ - -drive if=ide,id=colo-disk0,driver=quorum,read-pattern=fifo,vote-threshold=1,\ -children.0.file.filename=$imagefolder/primary.qcow2,children.0.driver=qcow2 -S - -2. Secondary: -Note: Active and hidden images need to be created only once and the -size should be the same as primary.qcow2. Again, you don't need to change -any IP's here, except for the $primary_ip variable. - -# imagefolder="/mnt/vms/colo-test-secondary" -# primary_ip=127.0.0.1 - -# qemu-img create -f qcow2 $imagefolder/secondary-active.qcow2 10G - -# qemu-img create -f qcow2 $imagefolder/secondary-hidden.qcow2 10G - -# qemu-system-x86_64 -enable-kvm -cpu qemu64,kvmclock=on -m 512 -smp 1 -qmp stdio \ - -device piix3-usb-uhci -device usb-tablet -name secondary \ - -netdev tap,id=hn0,vhost=off,helper=/usr/lib/qemu/qemu-bridge-helper \ - -device rtl8139,id=e0,netdev=hn0 \ - -chardev socket,id=red0,host=$primary_ip,port=9003,reconnect-ms=1000 \ - -chardev socket,id=red1,host=$primary_ip,port=9004,reconnect-ms=1000 \ - -object filter-redirector,id=f1,netdev=hn0,queue=tx,indev=red0 \ - -object filter-redirector,id=f2,netdev=hn0,queue=rx,outdev=red1 \ - -object filter-rewriter,id=rew0,netdev=hn0,queue=all \ - -drive if=none,id=parent0,file.filename=$imagefolder/primary.qcow2,driver=qcow2 \ - -drive if=none,id=childs0,driver=replication,mode=secondary,file.driver=qcow2,\ -top-id=colo-disk0,file.file.filename=$imagefolder/secondary-active.qcow2,\ -file.backing.driver=qcow2,file.backing.file.filename=$imagefolder/secondary-hidden.qcow2,\ -file.backing.backing=parent0 \ - -drive if=ide,id=colo-disk0,driver=quorum,read-pattern=fifo,vote-threshold=1,\ -children.0=childs0 \ - -incoming tcp:0.0.0.0:9998 - - -3. On Secondary VM's QEMU monitor, issue command -{"execute":"qmp_capabilities"} -{"execute": "migrate-set-capabilities", "arguments": {"capabilities": [ {"capability": "x-colo", "state": true } ] } } -{"execute": "nbd-server-start", "arguments": {"addr": {"type": "inet", "data": {"host": "0.0.0.0", "port": "9999"} } } } -{"execute": "nbd-server-add", "arguments": {"device": "parent0", "writable": true } } - -Note: - a. The qmp command nbd-server-start and nbd-server-add must be run - before running the qmp command migrate on primary QEMU - b. Active disk, hidden disk and nbd target's length should be the - same. - c. It is better to put active disk and hidden disk in ramdisk. They - will be merged into the parent disk on failover. - -4. On Primary VM's QEMU monitor, issue command: -{"execute":"qmp_capabilities"} -{"execute": "human-monitor-command", "arguments": {"command-line": "drive_add -n buddy driver=replication,mode=primary,file.driver=nbd,file.host=127.0.0.2,file.port=9999,file.export=parent0,node-name=replication0"}} -{"execute": "x-blockdev-change", "arguments":{"parent": "colo-disk0", "node": "replication0" } } -{"execute": "migrate-set-capabilities", "arguments": {"capabilities": [ {"capability": "x-colo", "state": true } ] } } -{"execute": "migrate", "arguments": {"uri": "tcp:127.0.0.2:9998" } } - - Note: - a. There should be only one NBD Client for each primary disk. - b. The qmp command line must be run after running qmp command line in - secondary qemu. - -5. After the above steps, you will see, whenever you make changes to PVM, SVM will be synced. -You can issue command '{ "execute": "migrate-set-parameters" , "arguments":{ "x-checkpoint-delay": 2000 } }' -to change the idle checkpoint period time - -6. Failover test -You can kill one of the VMs and Failover on the surviving VM: - -If you killed the Secondary, then follow "Primary Failover". After that, -if you want to resume the replication, follow "Primary resume replication" - -If you killed the Primary, then follow "Secondary Failover". After that, -if you want to resume the replication, follow "Secondary resume replication" - -== Primary Failover == -The Secondary died, resume on the Primary - -{"execute": "x-blockdev-change", "arguments":{ "parent": "colo-disk0", "child": "children.1"} } -{"execute": "human-monitor-command", "arguments":{ "command-line": "drive_del replication0" } } -{"execute": "object-del", "arguments":{ "id": "comp0" } } -{"execute": "object-del", "arguments":{ "id": "iothread1" } } -{"execute": "object-del", "arguments":{ "id": "m0" } } -{"execute": "object-del", "arguments":{ "id": "redire0" } } -{"execute": "object-del", "arguments":{ "id": "redire1" } } -{"execute": "x-colo-lost-heartbeat" } - -== Secondary Failover == -The Primary died, resume on the Secondary and prepare to become the new Primary - -{"execute": "nbd-server-stop"} -{"execute": "x-colo-lost-heartbeat"} - -{"execute": "object-del", "arguments":{ "id": "f2" } } -{"execute": "object-del", "arguments":{ "id": "f1" } } -{"execute": "chardev-remove", "arguments":{ "id": "red1" } } -{"execute": "chardev-remove", "arguments":{ "id": "red0" } } - -{"execute": "chardev-add", "arguments":{ "id": "mirror0", "backend": {"type": "socket", "data": {"addr": { "type": "inet", "data": { "host": "0.0.0.0", "port": "9003" } }, "server": true } } } } -{"execute": "chardev-add", "arguments":{ "id": "compare1", "backend": {"type": "socket", "data": {"addr": { "type": "inet", "data": { "host": "0.0.0.0", "port": "9004" } }, "server": true } } } } -{"execute": "chardev-add", "arguments":{ "id": "compare0", "backend": {"type": "socket", "data": {"addr": { "type": "inet", "data": { "host": "127.0.0.1", "port": "9001" } }, "server": true } } } } -{"execute": "chardev-add", "arguments":{ "id": "compare0-0", "backend": {"type": "socket", "data": {"addr": { "type": "inet", "data": { "host": "127.0.0.1", "port": "9001" } }, "server": false } } } } -{"execute": "chardev-add", "arguments":{ "id": "compare_out", "backend": {"type": "socket", "data": {"addr": { "type": "inet", "data": { "host": "127.0.0.1", "port": "9005" } }, "server": true } } } } -{"execute": "chardev-add", "arguments":{ "id": "compare_out0", "backend": {"type": "socket", "data": {"addr": { "type": "inet", "data": { "host": "127.0.0.1", "port": "9005" } }, "server": false } } } } - -== Primary resume replication == -Resume replication after new Secondary is up. - -Start the new Secondary (Steps 2 and 3 above), then on the Primary: -{"execute": "drive-mirror", "arguments":{ "device": "colo-disk0", "job-id": "resync", "target": "nbd://127.0.0.2:9999/parent0", "mode": "existing", "format": "raw", "sync": "full"} } - -Wait until disk is synced, then: -{"execute": "stop"} -{"execute": "block-job-cancel", "arguments":{ "device": "resync"} } - -{"execute": "human-monitor-command", "arguments":{ "command-line": "drive_add -n buddy driver=replication,mode=primary,file.driver=nbd,file.host=127.0.0.2,file.port=9999,file.export=parent0,node-name=replication0"}} -{"execute": "x-blockdev-change", "arguments":{ "parent": "colo-disk0", "node": "replication0" } } - -{"execute": "object-add", "arguments":{ "qom-type": "filter-mirror", "id": "m0", "netdev": "hn0", "queue": "tx", "outdev": "mirror0" } } -{"execute": "object-add", "arguments":{ "qom-type": "filter-redirector", "id": "redire0", "netdev": "hn0", "queue": "rx", "indev": "compare_out" } } -{"execute": "object-add", "arguments":{ "qom-type": "filter-redirector", "id": "redire1", "netdev": "hn0", "queue": "rx", "outdev": "compare0" } } -{"execute": "object-add", "arguments":{ "qom-type": "iothread", "id": "iothread1" } } -{"execute": "object-add", "arguments":{ "qom-type": "colo-compare", "id": "comp0", "primary_in": "compare0-0", "secondary_in": "compare1", "outdev": "compare_out0", "iothread": "iothread1" } } - -{"execute": "migrate-set-capabilities", "arguments":{ "capabilities": [ {"capability": "x-colo", "state": true } ] } } -{"execute": "migrate", "arguments":{ "uri": "tcp:127.0.0.2:9998" } } - -Note: -If this Primary previously was a Secondary, then we need to insert the -filters before the filter-rewriter by using the -""insert": "before", "position": "id=rew0"" Options. See below. - -== Secondary resume replication == -Become Primary and resume replication after new Secondary is up. Note -that now 127.0.0.1 is the Secondary and 127.0.0.2 is the Primary. - -Start the new Secondary (Steps 2 and 3 above, but with primary_ip=127.0.0.2), -then on the old Secondary: -{"execute": "drive-mirror", "arguments":{ "device": "colo-disk0", "job-id": "resync", "target": "nbd://127.0.0.1:9999/parent0", "mode": "existing", "format": "raw", "sync": "full"} } - -Wait until disk is synced, then: -{"execute": "stop"} -{"execute": "block-job-cancel", "arguments":{ "device": "resync" } } - -{"execute": "human-monitor-command", "arguments":{ "command-line": "drive_add -n buddy driver=replication,mode=primary,file.driver=nbd,file.host=127.0.0.1,file.port=9999,file.export=parent0,node-name=replication0"}} -{"execute": "x-blockdev-change", "arguments":{ "parent": "colo-disk0", "node": "replication0" } } - -{"execute": "object-add", "arguments":{ "qom-type": "filter-mirror", "id": "m0", "insert": "before", "position": "id=rew0", "netdev": "hn0", "queue": "tx", "outdev": "mirror0" } } -{"execute": "object-add", "arguments":{ "qom-type": "filter-redirector", "id": "redire0", "insert": "before", "position": "id=rew0", "netdev": "hn0", "queue": "rx", "indev": "compare_out" } } -{"execute": "object-add", "arguments":{ "qom-type": "filter-redirector", "id": "redire1", "insert": "before", "position": "id=rew0", "netdev": "hn0", "queue": "rx", "outdev": "compare0" } } -{"execute": "object-add", "arguments":{ "qom-type": "iothread", "id": "iothread1" } } -{"execute": "object-add", "arguments":{ "qom-type": "colo-compare", "id": "comp0", "primary_in": "compare0-0", "secondary_in": "compare1", "outdev": "compare_out0", "iothread": "iothread1" } } - -{"execute": "migrate-set-capabilities", "arguments":{ "capabilities": [ {"capability": "x-colo", "state": true } ] } } -{"execute": "migrate", "arguments":{ "uri": "tcp:127.0.0.1:9998" } } - -== TODO == -1. Support shared storage. -2. Develop the heartbeat part. -3. Reduce checkpoint VM’s downtime while doing checkpoint. diff --git a/docs/about/deprecated.rst b/docs/about/deprecated.rst index 1d5c4f3707cb..c37643548d65 100644 --- a/docs/about/deprecated.rst +++ b/docs/about/deprecated.rst @@ -54,6 +54,13 @@ as short-form boolean values, and passed to plugins as ``arg_name=on``. However, short-form booleans are deprecated and full explicit ``arg_name=on`` form is preferred. +``debug-threads`` option for ``-name`` (since 11.0) +''''''''''''''''''''''''''''''''''''''''''''''''''' + +The ``debug-threads`` option of the ``-name`` argument is now +ignored. Thread naming is unconditionally enabled for all platforms +where it is supported. + QEMU Machine Protocol (QMP) commands ------------------------------------ @@ -293,26 +300,6 @@ they want to use and avoids confusion. Existing users of the ``spike`` machine must ensure that they're setting the ``spike`` machine in the command line (``-M spike``). -Arm ``highbank`` and ``midway`` machines (since 10.1) -''''''''''''''''''''''''''''''''''''''''''''''''''''' - -There are no known users left for these machines (if you still use it, -please write a mail to the qemu-devel mailing list). If you just want to -boot a Cortex-A15 or Cortex-A9 Linux, use the ``virt`` machine instead. - - -System emulator binaries ------------------------- - -``qemu-system-microblazeel`` (since 10.1) -''''''''''''''''''''''''''''''''''''''''' - -The ``qemu-system-microblaze`` binary can emulate little-endian machines -now, too, so the separate binary ``qemu-system-microblazeel`` (with the -``el`` suffix) for little-endian targets is not required anymore. The -``petalogix-s3adsp1800`` machine can now be switched to little endian by -setting its ``endianness`` property to ``little``. - Backend options --------------- diff --git a/docs/about/removed-features.rst b/docs/about/removed-features.rst index 41bec8b8e384..6f4447993c3e 100644 --- a/docs/about/removed-features.rst +++ b/docs/about/removed-features.rst @@ -1180,6 +1180,13 @@ and serves as the initial engineering sample rather than a production version. A newer revision, A1, is now supported, and the ``ast2700a1-evb`` should replace the older A0 version. +Arm ``highbank`` and ``midway`` machines (removed in 11.0) +'''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +There were no known users left for these machines. If you just want to +boot a Cortex-A15 or Cortex-A9 Linux, use the ``virt`` machine instead. + + linux-user mode CPUs -------------------- @@ -1277,6 +1284,20 @@ The 'pvrdma' device and the whole RDMA subsystem have been removed. SD physical layer specification v2.00 supersedes the v1.10 one. + +System emulator binaries +------------------------ + +``qemu-system-microblazeel`` (removed in 11.0) +'''''''''''''''''''''''''''''''''''''''''''''' + +The ``qemu-system-microblaze`` binary can emulate little-endian machines +now, too, so the separate binary ``qemu-system-microblazeel`` (with the +``el`` suffix) for little-endian targets is not required anymore. The +``petalogix-s3adsp1800`` machine can now be switched to little endian by +setting its ``endianness`` property to ``little``. + + Related binaries ---------------- diff --git a/docs/devel/memory.rst b/docs/devel/memory.rst index 8558f70a4211..9083b18f08be 100644 --- a/docs/devel/memory.rst +++ b/docs/devel/memory.rst @@ -110,12 +110,9 @@ migrated: For most devices and boards this is the correct thing. If you have a special case where you need to manage the migration of -the backing memory yourself, you can call the functions: - -- memory_region_init_ram_nomigrate() -- memory_region_init_rom_nomigrate() - -which only initialize the MemoryRegion and leave handling +the backing memory yourself, you can call the function +memory_region_init_ram_flags_nomigrate() +which only initializes the MemoryRegion and leaves handling migration to the caller. The functions: diff --git a/docs/interop/firmware.json b/docs/interop/firmware.json index 3c71914a526c..421bee0e5ed4 100644 --- a/docs/interop/firmware.json +++ b/docs/interop/firmware.json @@ -40,10 +40,12 @@ # example, firmware built from the edk2 (EFI Development Kit II) # project usually provides this interface. # +# @svsm: AMD SEV-SNP Secure VM Service Module (SVSM) guest protocol. +# # Since: 3.0 ## { 'enum' : 'FirmwareOSInterface', - 'data' : [ 'bios', 'openfirmware', 'uboot', 'uefi' ] } + 'data' : [ 'bios', 'openfirmware', 'svsm', 'uboot', 'uefi' ] } ## # @FirmwareDevice: diff --git a/docs/system/arm/cpu-features.rst b/docs/system/arm/cpu-features.rst index 37d5dfd15b34..ce19ae6a046c 100644 --- a/docs/system/arm/cpu-features.rst +++ b/docs/system/arm/cpu-features.rst @@ -204,6 +204,17 @@ the list of KVM VCPU features and their descriptions. the guest scheduler behavior and/or be exposed to the guest userspace. +``kvm-psci-version`` + Set the Power State Coordination Interface (PSCI) firmware ABI version + that KVM provides to the guest. By default KVM will use the newest + version that it knows about (which is PSCI v1.3 in Linux v6.13). + + You only need to set this if you want to be able to migrate this + VM to a host machine running an older kernel that does not + recognize the PSCI version that this host's kernel defaults to. + + Current valid values are: 0.1, 0.2, 1.0, 1.1, 1.2, and 1.3. + TCG VCPU Features ================= @@ -318,12 +329,23 @@ SVE CPU Property Parsing Semantics provided an error will be generated. To avoid this error, one must enable at least one vector length prior to enabling SVE. + 10) Enabling SVE (with ``sve=on`` or by default) enables all the SVE + sub-features that the CPU supports (for example, it may also + enable SVE2). There are not generally any lower-level controls + for disabling specific SVE sub-features. + + 11) Disabling SVE does not automatically disable SME. If you want to + disable both you must use ``sve=off,sme=off``. In particular, + for the ``max`` CPU, ``sve=off`` alone will give you a CPU with + SME only (and which therefore still has the SVE vector registers). + Most users will want to disable both at once. + SVE CPU Property Examples ------------------------- - 1) Disable SVE:: + 1) Disable SVE and SME:: - $ qemu-system-aarch64 -M virt -cpu max,sve=off + $ qemu-system-aarch64 -M virt -cpu max,sve=off,sme=off 2) Implicitly enable all vector lengths for the ``max`` CPU type:: @@ -430,6 +452,11 @@ and all vector lengths must be powers of 2. The maximum vector length supported by qemu is 2048 bits. Otherwise, there are no additional constraints on the set of vector lengths supported by SME. +As with SVE, ``sme=on`` enables all the SME sub-features the CPU +supports (for example, it may also enable SME2), and there are +no lower-level controls for fine-grained disabling of specific +SME sub-features. + SME User-mode Default Vector Length Property -------------------------------------------- diff --git a/docs/system/arm/highbank.rst b/docs/system/arm/highbank.rst deleted file mode 100644 index bb4965b367fe..000000000000 --- a/docs/system/arm/highbank.rst +++ /dev/null @@ -1,19 +0,0 @@ -Calxeda Highbank and Midway (``highbank``, ``midway``) -====================================================== - -``highbank`` is a model of the Calxeda Highbank (ECX-1000) system, -which has four Cortex-A9 cores. - -``midway`` is a model of the Calxeda Midway (ECX-2000) system, -which has four Cortex-A15 cores. - -Emulated devices: - -- L2x0 cache controller -- SP804 dual timer -- PL011 UART -- PL061 GPIOs -- PL031 RTC -- PL022 synchronous serial port controller -- AHCI -- XGMAC ethernet controllers diff --git a/docs/system/arm/virt.rst b/docs/system/arm/virt.rst index f8148b5dcfef..fbe3ca9e129d 100644 --- a/docs/system/arm/virt.rst +++ b/docs/system/arm/virt.rst @@ -226,6 +226,11 @@ dtb-randomness dtb-kaslr-seed A deprecated synonym for dtb-randomness. +virtio-mmio-transports + Set the number of virtio-mmio transports to create (between 0 and 32; + the default is 32). Unused transports are harmless, but you can + use this property to avoid exposing them to the guest if you wish. + x-oem-id Set string (up to 6 bytes) to override the default value of field OEMID in ACPI table header. diff --git a/docs/system/confidential-guest-support.rst b/docs/system/confidential-guest-support.rst index 66129fbab64c..562a7c3c2852 100644 --- a/docs/system/confidential-guest-support.rst +++ b/docs/system/confidential-guest-support.rst @@ -41,5 +41,6 @@ Currently supported confidential guest mechanisms are: * Intel Trust Domain Extension (TDX) (see :doc:`i386/tdx`) * POWER Protected Execution Facility (PEF) (see :ref:`power-papr-protected-execution-facility-pef`) * s390x Protected Virtualization (PV) (see :doc:`s390x/protvirt`) +* AWS Nitro Enclaves (see :doc:`nitro`) Other mechanisms may be supported in future. diff --git a/docs/system/cpu-models-x86.rst.inc b/docs/system/cpu-models-x86.rst.inc index 3605d05a8c47..126c5a1972b8 100644 --- a/docs/system/cpu-models-x86.rst.inc +++ b/docs/system/cpu-models-x86.rst.inc @@ -72,17 +72,13 @@ compatibility is required, use the newest CPU model that is compatible across all desired hosts. ``DiamondRapids`` - Intel Xeon Processor. + Intel Xeon Processor (DiamondRapids, 2026) - Diamond Rapids product has a topology which differs from previous Xeon - products. It does not support SMT, but instead features a dual core - module (DCM) architecture. It also has core building blocks (CBB - die - level in CPU topology). The cache hierarchy is organized as follows: - L1 i/d cache is per thread, L2 cache is per DCM, and L3 cache is per - CBB. This cache topology can be emulated for DiamondRapids CPU model - using the smp-cache configuration as shown below: - - Example: + This does not include SMT but allows the module (dual core module + - DCM) and die (core building block - CBB) topology levels. The + cache hierarchy is L1 i/d cache per thread, L2 cache per module, + and L3 cache per die, which can be emulated using the smp-cache + option: :: diff --git a/docs/system/devices/cxl.rst b/docs/system/devices/cxl.rst index ca15a0da1c1d..9d0771cdfd73 100644 --- a/docs/system/devices/cxl.rst +++ b/docs/system/devices/cxl.rst @@ -384,6 +384,29 @@ An example of 4 devices below a switch suitable for 1, 2 or 4 way interleave:: -device cxl-type3,bus=swport3,persistent-memdev=cxl-mem3,lsa=cxl-lsa3,id=cxl-pmem3,sn=0x4 \ -M cxl-fmw.0.targets.0=cxl.1,cxl-fmw.0.size=4G,cxl-fmw.0.interleave-granularity=4k +An example of 4 type3 devices with volatile memory below a switch. Two of the devices +use HDM-DB for coherence, which requires operating in Flit mode:: + + qemu-system-x86_64 -M q35,cxl=on -m 4G,maxmem=8G,slots=8 -smp 4 \ + ... + -object memory-backend-ram,id=cxl-mem0,share=on,size=256M \ + -object memory-backend-ram,id=cxl-mem1,share=on,size=256M \ + -object memory-backend-ram,id=cxl-mem2,share=on,size=256M \ + -object memory-backend-ram,id=cxl-mem3,share=on,size=256M \ + -device pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.1 \ + -device cxl-rp,port=0,bus=cxl.1,id=root_port0,chassis=0,slot=0 \ + -device cxl-rp,port=1,bus=cxl.1,id=root_port1,chassis=0,slot=1 \ + -device cxl-upstream,bus=root_port0,id=us0,x-256b-flit=on \ + -device cxl-downstream,port=0,bus=us0,id=swport0,chassis=0,slot=4 \ + -device cxl-type3,bus=swport0,volatile-memdev=cxl-mem0,id=cxl-mem0,sn=0x1,x-256b-flit=on,hdm-db=on \ + -device cxl-downstream,port=1,bus=us0,id=swport1,chassis=0,slot=5 \ + -device cxl-type3,bus=swport1,volatile-memdev=cxl-mem1,id=cxl-mem1,sn=0x2,x-256b-flit=on,hdm-db=on \ + -device cxl-downstream,port=2,bus=us0,id=swport2,chassis=0,slot=6 \ + -device cxl-type3,bus=swport2,volatile-memdev=cxl-mem2,id=cxl-mem2,sn=0x3 \ + -device cxl-downstream,port=3,bus=us0,id=swport3,chassis=0,slot=7 \ + -device cxl-type3,bus=swport3,volatile-memdev=cxl-mem3,id=cxl-mem3,sn=0x4 \ + -M cxl-fmw.0.targets.0=cxl.1,cxl-fmw.0.size=4G,cxl-fmw.0.interleave-granularity=4k + A simple arm/virt example featuring a single direct connected CXL Type 3 Volatile Memory device:: diff --git a/docs/system/devices/virtio/vhost-user-contrib.rst b/docs/system/devices/virtio/vhost-user-contrib.rst index 48d04d2ade3f..660d29a70056 100644 --- a/docs/system/devices/virtio/vhost-user-contrib.rst +++ b/docs/system/devices/virtio/vhost-user-contrib.rst @@ -85,3 +85,42 @@ vhost-user-scsi - SCSI controller The vhost-user-scsi daemon can proxy iSCSI devices onto a virtualized SCSI controller. + +.. _vhost_user_bridge: + +vhost-user-bridge - Network bridge +================================== + +The vhost-user-bridge daemon serves as a development tool for testing real +internet traffic by providing a networking backend, i.e. server, for the +vhost-user protocol. + +Example +------- +For a single QEMU instance that both runs the user-mode net stack (slirp) and +serves as a vhost-user protocol frontend, i.e. client, simultaneously: + +First, start vhost-user-bridge: + +:: + + $ vhost-user-bridge -u /tmp/vubr.sock \ + -l 127.0.0.1:4444 \ + -r 127.0.0.1:5555 + +Then, invoke QEMU: + +:: + + $ qemu-system-x86_64 \ + -m 4G \ + -object memory-backend-memfd,id=mem0,size=4G,share=on,prealloc=on \ + -numa node,memdev=mem0 \ + -chardev socket,id=char0,path=/tmp/vubr.sock \ + -netdev vhost-user,id=vhost0,chardev=char0,vhostforce=on \ + -device virtio-net-pci,netdev=vhost0 \ + -netdev socket,id=udp0,udp=localhost:4444,localaddr=localhost:5555 \ + -netdev user,id=user0 \ + -netdev hubport,id=hub0,hubid=0,netdev=udp0 \ + -netdev hubport,id=hub1,hubid=0,netdev=user0 \ + ... diff --git a/docs/system/devices/virtio/virtio-gpu.rst b/docs/system/devices/virtio/virtio-gpu.rst index 0f4bb304a9b8..d05f8cb8e3f9 100644 --- a/docs/system/devices/virtio/virtio-gpu.rst +++ b/docs/system/devices/virtio/virtio-gpu.rst @@ -7,14 +7,28 @@ VirtIO GPU ========== This document explains the setup and usage of the virtio-gpu device. -The virtio-gpu device paravirtualizes the GPU and display controller. +The virtio-gpu device provides a GPU and display controller +paravirtualized using VirtIO. It supports a number of different modes +from simple 2D displays to fully accelerated 3D graphics. -Linux kernel support --------------------- +Linux guest kernel support +-------------------------- virtio-gpu requires a guest Linux kernel built with the ``CONFIG_DRM_VIRTIO_GPU`` option. +3D acceleration +--------------- + +3D acceleration of a virtualized GPU is still an evolving field. +Depending on the 3D mode you are running you may need to override +distribution supplied libraries with more recent versions or enable +build options. There are a number of requirements the host must meet +to be able to be able to support guests. QEMU must be able to access the +host's GPU and for the best performance be able to reliably share GPU +memory with the guest. Details of 3D acceleration requirements are +described in a further sections. + QEMU virtio-gpu variants ------------------------ @@ -61,15 +75,22 @@ on typical modern Linux distributions. virtio-gpu virglrenderer ------------------------ -When using virgl accelerated graphics mode in the guest, OpenGL API calls +When using `virgl`_ accelerated graphics mode in the guest, OpenGL API calls are translated into an intermediate representation (see `Gallium3D`_). The intermediate representation is communicated to the host and the `virglrenderer`_ library on the host translates the intermediate representation back to OpenGL API calls. +By default OpenGL version on guest is limited to 4.3. In order to enable +OpenGL 4.6 support, virtio-gpu host blobs feature (``hostmem`` and ``blob`` +fields) should be enabled. The ``hostmem`` field specifies the size of +virtio-gpu host memory window. This is typically between 256M and 8G. + .. parsed-literal:: -device virtio-gpu-gl + -device virtio-gpu-gl,hostmem=8G,blob=true +.. _virgl: https://docs.mesa3d.org/drivers/virgl.html .. _Gallium3D: https://www.freedesktop.org/wiki/Software/gallium/ .. _virglrenderer: https://gitlab.freedesktop.org/virgl/virglrenderer/ @@ -82,7 +103,79 @@ of virtio-gpu host memory window. This is typically between 256M and 8G. .. parsed-literal:: -device virtio-gpu-gl,hostmem=8G,blob=true,venus=true -.. _venus: https://gitlab.freedesktop.org/virgl/venus-protocol/ +.. _venus: https://docs.mesa3d.org/drivers/venus.html + +DRM native context is supported since release of `virglrenderer`_ v1.0.0 +using `drm`_ protocol. ``DRM`` virtio-gpu capability set ("capset") requires +host blob support (``hostmem`` and ``blob`` fields) and should be enabled +using ``drm_native_context`` field. The ``hostmem`` field specifies the size +of virtio-gpu host memory window. This is typically between 256M and 8G. + +.. parsed-literal:: + -device virtio-gpu-gl,hostmem=8G,blob=on,drm_native_context=on + +.. _drm: https://gitlab.freedesktop.org/virgl/virglrenderer/-/tree/main/src/drm + +.. list-table:: Linux Host Requirements + :header-rows: 1 + + * - Capability + - Kernel Version + - Libvirglrenderer Version + * - OpenGL pass-through + - Any Linux version compatible with QEMU if not using host blobs feature, + Linux 6.13+ otherwise + - 0.8.2+ + * - Vulkan pass-through + - Linux 6.13+ + - 1.0.0+ + * - AMDGPU DRM native context + - Linux 6.13+ + - 1.1.0+ + * - Freedreno DRM native context + - Linux 6.4+ + - 1.0.0+ + * - Intel i915 DRM native context + - Linux 6.13+ + - 1.3.0+ + * - Asahi DRM native context + - `Downstream version`_ of Asahi Linux kernel + - 1.2.0+ + * - Panfrost native context + - Linux 6.13+ + - 1.3.0+ + +.. _Downstream version: https://github.com/AsahiLinux/linux + +.. list-table:: Linux Guest Requirements + :header-rows: 1 + + * - Capability + - Kernel Version + - Mesa Version + * - OpenGL pass-through + - Any Linux version supporting virtio-gpu + - 16.0.0+ + * - Vulkan pass-through + - Linux 5.16+ + - 24.2.0+ + * - AMDGPU DRM native context + - Linux 6.14+ + - 25.0.0+ + * - Freedreno DRM native context + - Linux 6.14+ + - 23.1.0+ + * - Intel i915 DRM native context + - Linux 6.14+ + - 26.1.0+ + * - Asahi DRM native context + - Linux 6.14+ + - 24.2.0+ + * - Panfrost native context + - Linux 6.14+ + - `mr36814`_ + +.. _mr36814: https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/36814 virtio-gpu rutabaga ------------------- @@ -123,3 +216,21 @@ Surfaceless is the default if ``wsi`` is not specified. .. _Wayland display passthrough: https://www.youtube.com/watch?v=OZJiHMtIQ2M .. _gfxstream-enabled rutabaga: https://crosvm.dev/book/appendix/rutabaga_gfx.html .. _guest Wayland proxy: https://crosvm.dev/book/devices/wayland.html + +.. list-table:: Linux Host Requirements + :header-rows: 1 + + * - Capability + - Kernel Version + * - Vulkan+Wayland pass-through + - Linux 6.13+ + +.. list-table:: Linux Guest Requirements + :header-rows: 1 + + * - Capability + - Kernel Version + - Mesa Version + * - Vulkan+Wayland pass-through + - Linux 5.16+ + - 24.3.0+ diff --git a/docs/system/index.rst b/docs/system/index.rst index 427b02048310..bb948e2993c9 100644 --- a/docs/system/index.rst +++ b/docs/system/index.rst @@ -39,5 +39,7 @@ or Hypervisor.Framework. multi-process confidential-guest-support igvm + nitro vm-templating sriov + qemu-colo diff --git a/docs/system/nitro.rst b/docs/system/nitro.rst new file mode 100644 index 000000000000..5907d6153eb0 --- /dev/null +++ b/docs/system/nitro.rst @@ -0,0 +1,133 @@ +AWS Nitro Enclaves +================== + +`AWS Nitro Enclaves `_ +are isolated compute environments that run alongside EC2 instances. +They are created by partitioning CPU and memory resources from a parent +instance and launching a signed Enclave Image Format (EIF) file inside +a confidential VM managed by the Nitro Hypervisor. + +QEMU supports launching Nitro Enclaves on EC2 instances that have +enclave support enabled, using the ``nitro`` accelerator and the +``nitro`` machine type. + +Prerequisites +------------- + +* An EC2 instance with Nitro Enclaves enabled +* The ``nitro_enclaves`` kernel module loaded (provides ``/dev/nitro_enclaves``) +* CPU cores allocated to the Nitro Enclaves pool via ``nitro-enclaves-allocator`` +* Huge pages allocated for Nitro Enclaves via ``nitro-enclaves-allocator`` + +Quick Start +----------- + +Launch a Nitro Enclave from a pre-built EIF file:: + + $ qemu-system-x86_64 -accel nitro,debug-mode=on -M nitro -nographic \ + -smp 2 -m 512M -kernel enclave.eif + +Launch an enclave from individual kernel and initrd files:: + + $ qemu-system-x86_64 -accel nitro,debug-mode=on -M nitro -nographic \ + -smp 2 -m 512M -kernel vmlinuz -initrd initrd.cpio \ + -append "console=ttyS0" + +The same commands work with ``qemu-system-aarch64`` on Graviton based EC2 +instances. + +Accelerator +----------- + +The ``nitro`` accelerator (``-accel nitro``) drives the +``/dev/nitro_enclaves`` device to create and manage a Nitro Enclave. +It handles: + +* Creating the enclave VM slot +* Donating memory regions (must be huge page backed) +* Adding vCPUs (must be full physical cores) +* Starting the enclave +* Notifying vsock bus devices of the enclave CID + +Accelerator options: + +``debug-mode=on|off`` + Enable debug mode. When enabled, the Nitro Hypervisor exposes the + enclave's serial console output via a vsock port that the machine + model automatically connects to. In debug mode, PCR values are zero. + Default is ``off``. + +Machine +------- + +The ``nitro`` machine (``-M nitro``) is a minimal, architecture-independent +machine that provides only what a Nitro Enclave needs: + +* RAM (huge page backed via memfd) +* vCPUs (defaults to ``host`` CPU type) +* A Nitro vsock bus with: + + - A heartbeat device (vsock server on port 9000) + - A serial console bridge (vsock client, debug mode only) + +Communication to the Nitro Enclave is limited to virtio-vsock. The Enclave +is allocated a CID at launch at which it is reachable. A specific CID can +be requested with ``-accel nitro,enclave-cid=`` (0 lets the hypervisor +choose). The assigned CID is readable from the vsock bridge device:: + + (qemu) qom-get /machine/peripheral/nitro-vsock enclave-cid + +EIF Image Format +^^^^^^^^^^^^^^^^ + +Nitro Enclaves boot from EIF (Enclave Image Format) files. When +``-kernel`` points to an EIF file (detected by the ``.eif`` magic +bytes), it is loaded directly into guest memory. + +When ``-kernel`` points to a regular kernel image (e.g. a bzImage or +Image), the machine automatically assembles a minimal EIF on the fly +from ``-kernel``, ``-initrd``, and ``-append``. This allows standard +direct kernel boot without external EIF tooling. + +CPU Requirements +^^^^^^^^^^^^^^^^ + +Nitro Enclaves require full physical CPU cores. On hyperthreaded +systems, this means ``-smp`` must be a multiple of the threads per +core (typically 2). + +Nitro Enclaves can only consume cores that are donated to the Nitro Enclave +CPU pool. You can configure the CPU pool using the ``nitro-enclaves-allocator`` +tool or manually by writing to the nitro_enclaves cpu pool parameter. To +allocate vCPUs 1, 2 and 3, you can call:: + + $ echo 1,2,3 | sudo tee /sys/module/nitro_enclaves/parameters/ne_cpus + +Beware that on x86-64 systems, hyperthread siblings are not consecutive +and must be added in pairs to the pool. Consult tools like ``lstopo`` +or ``lscpu`` for details about your instance's CPU topology. + +Memory Requirements +^^^^^^^^^^^^^^^^^^^ + +Enclave memory must be huge page backed. The machine automatically +creates a memfd memory backend with huge pages enabled. To make the +huge page allocation work, ensure that huge pages are reserved in +the system. To reserve 1 GiB of memory on a 4 KiB PAGE_SIZE system, +you can call:: + + $ echo 512 | sudo tee /proc/sys/vm/nr_hugepages + +Emulated Nitro Enclaves +----------------------- + +In addition to the native Nitro Enclaves invocation, you can also use +the emulated nitro-enclave machine target (see :doc:`i386/nitro-enclave`) +which implements the x86 Nitro Enclave device model. While -M nitro +delegates virtual machine device emulation to the Nitro Hypervisor, -M +nitro-enclave implements all devices itself, which means it also works +on non-EC2 instances. + +If you require NSM based attestation backed by valid AWS certificates, +you must use -M nitro. The -M nitro-enclave model does not provide +you with an AWS signed attestation document. diff --git a/docs/system/qemu-colo.rst b/docs/system/qemu-colo.rst new file mode 100644 index 000000000000..d45586acd892 --- /dev/null +++ b/docs/system/qemu-colo.rst @@ -0,0 +1,361 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Qemu COLO Fault Tolerance +========================= + +This document gives an overview of COLO's design and how to use it. + +Background +---------- +Virtual machine (VM) replication is a well known technique for providing +application-agnostic software-implemented hardware fault tolerance, +also known as "non-stop service". + +COLO (COarse-grained LOck-stepping) is a high availability solution. +Both primary VM (PVM) and secondary VM (SVM) run in parallel. They receive the +same request from client, and generate response in parallel too. +If the response packets from PVM and SVM are identical, they are released +immediately. Otherwise, a VM checkpoint (on demand) is conducted. + +Architecture +------------ +The architecture of COLO is shown in the diagram below. +It consists of a pair of networked physical nodes: +The primary node running the PVM, and the secondary node running the SVM +to maintain a valid replica of the PVM. +PVM and SVM execute in parallel and generate output of response packets for +client requests according to the application semantics. + +The incoming packets from the client or external network are received by the +primary node, and then forwarded to the secondary node, so that both the PVM +and the SVM are stimulated with the same requests. + +COLO receives the outbound packets from both the PVM and SVM and compares them +before allowing the output to be sent to clients. + +The SVM is qualified as a valid replica of the PVM, as long as it generates +identical responses to all client requests. Once the differences in the outputs +are detected between the PVM and SVM, COLO withholds transmission of the +outbound packets until it has successfully synchronized the PVM state to the SVM. + +Overview:: + + Primary Node Secondary Node + +------------+ +-----------------------+ +------------------------+ +------------+ + | | | HeartBeat +<----->+ HeartBeat | | | + | Primary VM | +-----------+-----------+ +-----------+------------+ |Secondary VM| + | | | | | | + | | +-----------|-----------+ +-----------|------------+ | | + | | |QEMU +---v----+ | |QEMU +----v---+ | | | + | | | |Failover| | | |Failover| | | | + | | | +--------+ | | +--------+ | | | + | | | +---------------+ | | +---------------+ | | | + | | | | VM Checkpoint +-------------->+ VM Checkpoint | | | | + | | | +---------------+ | | +---------------+ | | | + |Requests<--------------------------\ /-----------------\ /--------------------->Requests| + | | | ^ ^ | | | | | | | + |Responses+---------------------\ /-|-|------------\ /-------------------------+Responses| + | | | | | | | | | | | | | | | | + | | | +-----------+ | | | | | | | | | | +----------+ | | | + | | | | COLO disk | | | | | | | | | | | | COLO disk| | | | + | | | | Manager +---------------------------->| Manager | | | | + | | | ++----------+ v v | | | | | v v | +---------++ | | | + | | | |+-----------+-+-+-++| | ++-+--+-+---------+ | | | | + | | | || COLO Proxy || | | COLO Proxy | | | | | + | | | || (compare packet || | |(adjust sequence | | | | | + | | | ||and mirror packet)|| | | and ACK) | | | | | + | | | |+------------+---+-+| | +-----------------+ | | | | + +------------+ +-----------------------+ +------------------------+ +------------+ + +------------+ | | | | +------------+ + | VM Monitor | | | | | | VM Monitor | + +------------+ | | | | +------------+ + +---------------------------------------+ +----------------------------------------+ + | Kernel | | | | | Kernel | | + +---------------------------------------+ +----------------------------------------+ + | | | | + +--------------v+ +---------v---+--+ +------------------+ +v-------------+ + | Storage | |External Network| | External Network | | Storage | + +---------------+ +----------------+ +------------------+ +--------------+ + +Components +^^^^^^^^^^ +You can see there are several components in COLO's diagram of architecture. +Their functions are described below. + +HeartBeat +~~~~~~~~~ +Runs on both the primary and secondary nodes, to periodically check platform +availability. When the primary node suffers a hardware fail-stop failure, +the heartbeat stops responding, the secondary node will trigger a failover +as soon as it determines the absence. + +COLO disk Manager +~~~~~~~~~~~~~~~~~ +When primary VM writes data into image, the colo disk manager captures this data +and sends it to secondary VM's which makes sure the context of secondary VM's +image is consistent with the context of primary VM 's image. +For more details, please refer to docs/block-replication.txt. + +Checkpoint/Failover Controller +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Modifications of save/restore flow to realize continuous migration, +to make sure the state of VM in Secondary side is always consistent with VM in +Primary side. + +COLO Proxy +~~~~~~~~~~ +Delivers packets to Primary and Secondary, and then compare the responses from +both side. Then decide whether to start a checkpoint according to some rules. +Please refer to docs/colo-proxy.txt for more information. + +Note: +HeartBeat has not been implemented yet, so you need to trigger failover process +by using 'x-colo-lost-heartbeat' command. + +COLO operation status +^^^^^^^^^^^^^^^^^^^^^ + +Overview:: + + +-----------------+ + | | + | Start COLO | + | | + +--------+--------+ + | + | Main qmp command: + | migrate-set-capabilities with x-colo + | migrate + | + v + +--------+--------+ + | | + | COLO running | + | | + +--------+--------+ + | + | Main qmp command: + | x-colo-lost-heartbeat + | or + | some error happened + v + +--------+--------+ + | | send qmp event: + | COLO failover | COLO_EXIT + | | + +-----------------+ + + +COLO use the qmp command to switch and report operation status. +The diagram just shows the main qmp command, you can get the detail +in test procedure. + +Test procedure +-------------- + +Setup +^^^^^ + +Here we are running both instances on the same host for testing, +change the IP Addresses if you want to run it on two hosts. Initially +``127.0.0.1`` is the Primary Host and ``127.0.0.2`` is the Secondary Host. + +COLO uses double the guest ram size on the secondary side. The Qemu version +should be the same on both hosts. + +Startup qemu +^^^^^^^^^^^^ +**1. Primary**: +Initially, ``$imagefolder/primary.qcow2`` needs to be copied to all hosts. +You don't need to change any IP's here, because ``0.0.0.0`` listens on any +interface. The chardev's with ``127.0.0.1`` IP's loopback to the local qemu +instance:: + + # imagefolder="/mnt/vms/colo-test-primary" + + # qemu-system-x86_64 -enable-kvm -cpu qemu64,kvmclock=on -m 512 -smp 1 -qmp stdio \ + -device piix3-usb-uhci -device usb-tablet -name primary \ + -netdev tap,id=hn0,vhost=off,helper=/usr/lib/qemu/qemu-bridge-helper \ + -device rtl8139,id=e0,netdev=hn0 \ + -chardev socket,id=mirror0,host=0.0.0.0,port=9003,server=on,wait=off \ + -chardev socket,id=compare1,host=0.0.0.0,port=9004,server=on,wait=on \ + -chardev socket,id=compare0,host=127.0.0.1,port=9001,server=on,wait=off \ + -chardev socket,id=compare0-0,host=127.0.0.1,port=9001 \ + -chardev socket,id=compare_out,host=127.0.0.1,port=9005,server=on,wait=off \ + -chardev socket,id=compare_out0,host=127.0.0.1,port=9005 \ + -object filter-mirror,id=m0,netdev=hn0,queue=tx,outdev=mirror0 \ + -object filter-redirector,netdev=hn0,id=redire0,queue=rx,indev=compare_out \ + -object filter-redirector,netdev=hn0,id=redire1,queue=rx,outdev=compare0 \ + -object iothread,id=iothread1 \ + -object colo-compare,id=comp0,primary_in=compare0-0,secondary_in=compare1,\ + outdev=compare_out0,iothread=iothread1 \ + -drive if=ide,id=colo-disk0,driver=quorum,read-pattern=fifo,vote-threshold=1,\ + children.0.file.filename=$imagefolder/primary.qcow2,children.0.driver=qcow2 -S + + +**2. Secondary**: +Active and hidden images need to be created only once and the +size should be the same as ``primary.qcow2``. Again, you don't need to change +any IP's here, except for the ``$primary_ip`` variable:: + + # imagefolder="/mnt/vms/colo-test-secondary" + # primary_ip=127.0.0.1 + + # qemu-img create -f qcow2 $imagefolder/secondary-active.qcow2 10G + + # qemu-img create -f qcow2 $imagefolder/secondary-hidden.qcow2 10G + + # qemu-system-x86_64 -enable-kvm -cpu qemu64,kvmclock=on -m 512 -smp 1 -qmp stdio \ + -device piix3-usb-uhci -device usb-tablet -name secondary \ + -netdev tap,id=hn0,vhost=off,helper=/usr/lib/qemu/qemu-bridge-helper \ + -device rtl8139,id=e0,netdev=hn0 \ + -chardev socket,id=red0,host=$primary_ip,port=9003,reconnect-ms=1000 \ + -chardev socket,id=red1,host=$primary_ip,port=9004,reconnect-ms=1000 \ + -object filter-redirector,id=f1,netdev=hn0,queue=tx,indev=red0 \ + -object filter-redirector,id=f2,netdev=hn0,queue=rx,outdev=red1 \ + -object filter-rewriter,id=rew0,netdev=hn0,queue=all \ + -drive if=none,id=parent0,file.filename=$imagefolder/primary.qcow2,driver=qcow2 \ + -drive if=none,id=childs0,driver=replication,mode=secondary,file.driver=qcow2,\ + top-id=colo-disk0,file.file.filename=$imagefolder/secondary-active.qcow2,\ + file.backing.driver=qcow2,file.backing.file.filename=$imagefolder/secondary-hidden.qcow2,\ + file.backing.backing=parent0 \ + -drive if=ide,id=colo-disk0,driver=quorum,read-pattern=fifo,vote-threshold=1,\ + children.0=childs0 \ + -incoming tcp:0.0.0.0:9998 + + +**3.** On Secondary VM's QEMU monitor, issue command:: + + {"execute":"qmp_capabilities"} + {"execute": "migrate-set-capabilities", "arguments": {"capabilities": [ {"capability": "return-path", "state": true }, {"capability": "x-colo", "state": true } ] } } + {"execute": "nbd-server-start", "arguments": {"addr": {"type": "inet", "data": {"host": "0.0.0.0", "port": "9999"} } } } + {"execute": "nbd-server-add", "arguments": {"device": "parent0", "writable": true } } + +Note: + a. The qmp command ``nbd-server-start`` and ``nbd-server-add`` must be run + before running the qmp command migrate on primary QEMU + b. Active disk, hidden disk and nbd target's length should be the + same. + c. It is better to put active disk and hidden disk in ramdisk. They + will be merged into the parent disk on failover. + +**4.** On Primary VM's QEMU monitor, issue command:: + + {"execute":"qmp_capabilities"} + {"execute": "blockdev-add", "arguments": {"driver": "nbd", "node-name": "nbd0", "server": {"type": "inet", "host": "127.0.0.2", "port": "9999"}, "export": "parent0", "detect-zeroes": "on"} } + {"execute": "x-blockdev-change", "arguments":{"parent": "colo-disk0", "node": "nbd0" } } + {"execute": "migrate-set-capabilities", "arguments": {"capabilities": [ {"capability": "return-path", "state": true }, {"capability": "x-colo", "state": true } ] } } + {"execute": "migrate", "arguments": {"uri": "tcp:127.0.0.2:9998" } } + +Note: + a. There should be only one NBD Client for each primary disk. + b. The qmp command line must be run after running qmp command line in + secondary qemu. + +**5.** After the above steps, you will see, whenever you make changes to PVM, SVM will be synced. +You can issue command ``{ "execute": "migrate-set-parameters" , "arguments":{ "x-checkpoint-delay": 2000 } }`` +to change the idle checkpoint period time + +Failover test +^^^^^^^^^^^^^ +You can kill one of the VMs and Failover on the surviving VM: + +If you killed the Secondary, then follow "Primary Failover". +After that, if you want to resume the replication, follow "Primary resume replication" + +If you killed the Primary, then follow "Secondary Failover". +After that, if you want to resume the replication, follow "Secondary resume replication" + +Primary Failover +~~~~~~~~~~~~~~~~ +The Secondary died, resume on the Primary:: + + {"execute": "x-blockdev-change", "arguments":{ "parent": "colo-disk0", "child": "children.1"} } + {"execute": "blockdev-del", "arguments": {"node-name": "nbd0"} } + {"execute": "object-del", "arguments":{ "id": "comp0" } } + {"execute": "object-del", "arguments":{ "id": "iothread1" } } + {"execute": "object-del", "arguments":{ "id": "m0" } } + {"execute": "object-del", "arguments":{ "id": "redire0" } } + {"execute": "object-del", "arguments":{ "id": "redire1" } } + {"execute": "x-colo-lost-heartbeat" } + +Secondary Failover +~~~~~~~~~~~~~~~~~~ +The Primary died, resume on the Secondary and prepare to become the new Primary:: + + {"execute": "nbd-server-stop"} + {"execute": "x-colo-lost-heartbeat"} + + {"execute": "object-del", "arguments":{ "id": "f2" } } + {"execute": "object-del", "arguments":{ "id": "f1" } } + {"execute": "chardev-remove", "arguments":{ "id": "red1" } } + {"execute": "chardev-remove", "arguments":{ "id": "red0" } } + + {"execute": "chardev-add", "arguments":{ "id": "mirror0", "backend": {"type": "socket", "data": {"addr": { "type": "inet", "data": { "host": "0.0.0.0", "port": "9003" } }, "server": true } } } } + {"execute": "chardev-add", "arguments":{ "id": "compare1", "backend": {"type": "socket", "data": {"addr": { "type": "inet", "data": { "host": "0.0.0.0", "port": "9004" } }, "server": true } } } } + {"execute": "chardev-add", "arguments":{ "id": "compare0", "backend": {"type": "socket", "data": {"addr": { "type": "inet", "data": { "host": "127.0.0.1", "port": "9001" } }, "server": true } } } } + {"execute": "chardev-add", "arguments":{ "id": "compare0-0", "backend": {"type": "socket", "data": {"addr": { "type": "inet", "data": { "host": "127.0.0.1", "port": "9001" } }, "server": false } } } } + {"execute": "chardev-add", "arguments":{ "id": "compare_out", "backend": {"type": "socket", "data": {"addr": { "type": "inet", "data": { "host": "127.0.0.1", "port": "9005" } }, "server": true } } } } + {"execute": "chardev-add", "arguments":{ "id": "compare_out0", "backend": {"type": "socket", "data": {"addr": { "type": "inet", "data": { "host": "127.0.0.1", "port": "9005" } }, "server": false } } } } + +Primary resume replication +~~~~~~~~~~~~~~~~~~~~~~~~~~ +Resume replication after new Secondary is up. + +Start the new Secondary (Steps 2 and 3 above), then on the Primary:: + + {"execute": "drive-mirror", "arguments":{ "device": "colo-disk0", "job-id": "resync", "target": "nbd://127.0.0.2:9999/parent0", "mode": "existing", "format": "raw", "sync": "full"} } + +Wait until disk is synced, then:: + + {"execute": "stop"} + {"execute": "block-job-cancel", "arguments":{ "device": "resync"} } + + {"execute": "blockdev-add", "arguments": {"driver": "nbd", "node-name": "nbd0", "server": {"type": "inet", "host": "127.0.0.2", "port": "9999"}, "export": "parent0", "detect-zeroes": "on"} } + {"execute": "x-blockdev-change", "arguments":{ "parent": "colo-disk0", "node": "nbd0" } } + + {"execute": "object-add", "arguments":{ "qom-type": "filter-mirror", "id": "m0", "netdev": "hn0", "queue": "tx", "outdev": "mirror0" } } + {"execute": "object-add", "arguments":{ "qom-type": "filter-redirector", "id": "redire0", "netdev": "hn0", "queue": "rx", "indev": "compare_out" } } + {"execute": "object-add", "arguments":{ "qom-type": "filter-redirector", "id": "redire1", "netdev": "hn0", "queue": "rx", "outdev": "compare0" } } + {"execute": "object-add", "arguments":{ "qom-type": "iothread", "id": "iothread1" } } + {"execute": "object-add", "arguments":{ "qom-type": "colo-compare", "id": "comp0", "primary_in": "compare0-0", "secondary_in": "compare1", "outdev": "compare_out0", "iothread": "iothread1" } } + + {"execute": "migrate-set-capabilities", "arguments":{ "capabilities": [ {"capability": "x-colo", "state": true } ] } } + {"execute": "migrate", "arguments":{ "uri": "tcp:127.0.0.2:9998" } } + +Note: +If this Primary previously was a Secondary, then we need to insert the +filters before the filter-rewriter by using the +""insert": "before", "position": "id=rew0"" Options. See below. + +Secondary resume replication +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Become Primary and resume replication after new Secondary is up. Note +that now 127.0.0.1 is the Secondary and 127.0.0.2 is the Primary. + +Start the new Secondary (Steps 2 and 3 above, but with primary_ip=127.0.0.2), +then on the old Secondary:: + + {"execute": "drive-mirror", "arguments":{ "device": "colo-disk0", "job-id": "resync", "target": "nbd://127.0.0.1:9999/parent0", "mode": "existing", "format": "raw", "sync": "full"} } + +Wait until disk is synced, then:: + + {"execute": "stop"} + {"execute": "block-job-cancel", "arguments":{ "device": "resync" } } + + {"execute": "blockdev-add", "arguments": {"driver": "nbd", "node-name": "nbd0", "server": {"type": "inet", "host": "127.0.0.1", "port": "9999"}, "export": "parent0", "detect-zeroes": "on"} } + {"execute": "x-blockdev-change", "arguments":{ "parent": "colo-disk0", "node": "nbd0" } } + + {"execute": "object-add", "arguments":{ "qom-type": "filter-mirror", "id": "m0", "insert": "before", "position": "id=rew0", "netdev": "hn0", "queue": "tx", "outdev": "mirror0" } } + {"execute": "object-add", "arguments":{ "qom-type": "filter-redirector", "id": "redire0", "insert": "before", "position": "id=rew0", "netdev": "hn0", "queue": "rx", "indev": "compare_out" } } + {"execute": "object-add", "arguments":{ "qom-type": "filter-redirector", "id": "redire1", "insert": "before", "position": "id=rew0", "netdev": "hn0", "queue": "rx", "outdev": "compare0" } } + {"execute": "object-add", "arguments":{ "qom-type": "iothread", "id": "iothread1" } } + {"execute": "object-add", "arguments":{ "qom-type": "colo-compare", "id": "comp0", "primary_in": "compare0-0", "secondary_in": "compare1", "outdev": "compare_out0", "iothread": "iothread1" } } + + {"execute": "migrate-set-capabilities", "arguments":{ "capabilities": [ {"capability": "x-colo", "state": true } ] } } + {"execute": "migrate", "arguments":{ "uri": "tcp:127.0.0.1:9998" } } + +| Copyright (c) 2016 Intel Corporation +| Copyright (c) 2016 HUAWEI TECHNOLOGIES CO., LTD. +| Copyright (c) 2016 Fujitsu, Corp. +| Copyright (c) 2026 Lukas Straub diff --git a/docs/system/target-arm.rst b/docs/system/target-arm.rst index a96d1867df1d..89f7b77313f9 100644 --- a/docs/system/target-arm.rst +++ b/docs/system/target-arm.rst @@ -83,7 +83,6 @@ Board-specific documentation arm/bananapi_m2u.rst arm/b-l475e-iot01a.rst arm/sabrelite - arm/highbank arm/digic arm/cubieboard arm/emcraft-sf2 diff --git a/fpu/softfloat-parts.c.inc b/fpu/softfloat-parts.c.inc index 79b56014ab98..61b07307bf9e 100644 --- a/fpu/softfloat-parts.c.inc +++ b/fpu/softfloat-parts.c.inc @@ -227,18 +227,39 @@ static void partsN(canonicalize)(FloatPartsN *p, float_status *status, p->exp = fmt->frac_shift - fmt->exp_bias - shift + !has_pseudo_denormals; } - } else if (likely(p->exp < fmt->exp_max) || fmt->arm_althp) { - p->cls = float_class_normal; - p->exp -= fmt->exp_bias; - frac_shl(p, fmt->frac_shift); - p->frac_hi |= DECOMPOSED_IMPLICIT_BIT; - } else if (likely(frac_eqz(p))) { - p->cls = float_class_inf; - } else { - frac_shl(p, fmt->frac_shift); - p->cls = (parts_is_snan_frac(p->frac_hi, status) - ? float_class_snan : float_class_qnan); + return; } + if (unlikely(p->exp == fmt->exp_max)) { + switch (fmt->exp_max_kind) { + case float_expmax_ieee: + if (likely(frac_eqz(p))) { + p->cls = float_class_inf; + } else { + frac_shl(p, fmt->frac_shift); + p->cls = (parts_is_snan_frac(p->frac_hi, status) + ? float_class_snan : float_class_qnan); + } + return; + case float_expmax_normal: + break; + case float_expmax_e4m3: + if (p->frac_hi == 0b111) { + frac_shl(p, fmt->frac_shift); + p->cls = (parts_is_snan_frac(p->frac_hi, status) + ? float_class_snan : float_class_qnan); + return; + } + /* otherwise normal */ + break; + default: + g_assert_not_reached(); + } + } + + p->cls = float_class_normal; + p->exp -= fmt->exp_bias; + frac_shl(p, fmt->frac_shift); + p->frac_hi |= DECOMPOSED_IMPLICIT_BIT; } /* @@ -246,9 +267,27 @@ static void partsN(canonicalize)(FloatPartsN *p, float_status *status, * are FRAC_SHIFT bits that may require rounding at the bottom of the * fraction; these bits will be removed. The exponent will be biased * by EXP_BIAS and must be bounded by [EXP_MAX-1, 0]. + * + * The saturate parameter controls saturation behavior for formats that + * support it -- when true, overflow produces max normal instead of infinity. */ + +/* Helper for uncanon_normal and uncanon, for FP8 E4M3. */ +static void partsN(uncanon_e4m3_overflow)(FloatPartsN *p, float_status *s, + const FloatFmt *fmt, bool saturate) +{ + assert(N == 64); + float_raise(float_flag_overflow | float_flag_inexact, s); + if (saturate) { + p->exp = fmt->exp_max; + p->frac_hi = E4M3_NORMAL_FRAC_MAX; + } else { + parts_default_nan(p, s); + } +} + static void partsN(uncanon_normal)(FloatPartsN *p, float_status *s, - const FloatFmt *fmt) + const FloatFmt *fmt, bool saturate) { const int exp_max = fmt->exp_max; const int frac_shift = fmt->frac_shift; @@ -257,7 +296,7 @@ static void partsN(uncanon_normal)(FloatPartsN *p, float_status *s, const uint64_t frac_lsbm1 = round_mask ^ (round_mask >> 1); const uint64_t roundeven_mask = round_mask | frac_lsb; uint64_t inc; - bool overflow_norm = false; + bool overflow_norm = saturate; int exp, flags = 0; switch (s->float_rounding_mode) { @@ -282,11 +321,11 @@ static void partsN(uncanon_normal)(FloatPartsN *p, float_status *s, break; case float_round_up: inc = p->sign ? 0 : round_mask; - overflow_norm = p->sign; + overflow_norm |= p->sign; break; case float_round_down: inc = p->sign ? round_mask : 0; - overflow_norm = !p->sign; + overflow_norm |= !p->sign; break; case float_round_to_odd: overflow_norm = true; @@ -314,29 +353,45 @@ static void partsN(uncanon_normal)(FloatPartsN *p, float_status *s, p->frac_lo &= ~round_mask; } - if (fmt->arm_althp) { - /* ARM Alt HP eschews Inf and NaN for a wider exponent. */ - if (unlikely(exp > exp_max)) { - /* Overflow. Return the maximum normal. */ - flags = float_flag_invalid; - exp = exp_max; - frac_allones(p); - p->frac_lo &= ~round_mask; - } - } else if (unlikely(exp >= exp_max)) { - flags |= float_flag_overflow; - if (s->rebias_overflow) { - exp -= fmt->exp_re_bias; - } else if (overflow_norm) { - flags |= float_flag_inexact; - exp = exp_max - 1; - frac_allones(p); - p->frac_lo &= ~round_mask; - } else { - flags |= float_flag_inexact; - p->cls = float_class_inf; - exp = exp_max; - frac_clear(p); + if (unlikely(exp >= exp_max)) { + switch (fmt->exp_max_kind) { + case float_expmax_ieee: + flags |= float_flag_overflow; + if (s->rebias_overflow) { + exp -= fmt->exp_re_bias; + } else if (overflow_norm) { + flags |= float_flag_inexact; + exp = exp_max - 1; + frac_allones(p); + p->frac_lo &= ~round_mask; + } else { + flags |= float_flag_inexact; + p->cls = float_class_inf; + exp = exp_max; + frac_clear(p); + } + break; + + case float_expmax_normal: + if (unlikely(exp > exp_max)) { + /* Overflow. Return the maximum normal. */ + flags = (fmt->overflow_raises_invalid + ? float_flag_invalid + : float_flag_overflow | float_flag_inexact); + exp = exp_max; + frac_allones(p); + p->frac_lo &= ~round_mask; + } + break; + + case float_expmax_e4m3: + if (exp > exp_max || p->frac_hi > E4M3_NORMAL_FRAC_MAX) { + partsN(uncanon_e4m3_overflow)(p, s, fmt, overflow_norm); + } + break; + + default: + g_assert_not_reached(); } } frac_shr(p, frac_shift); @@ -423,10 +478,10 @@ static void partsN(uncanon_normal)(FloatPartsN *p, float_status *s, } static void partsN(uncanon)(FloatPartsN *p, float_status *s, - const FloatFmt *fmt) + const FloatFmt *fmt, bool saturate) { if (likely(is_anynorm(p->cls))) { - parts_uncanon_normal(p, s, fmt); + parts_uncanon_normal(p, s, fmt, saturate); } else { switch (p->cls) { case float_class_zero: @@ -434,13 +489,22 @@ static void partsN(uncanon)(FloatPartsN *p, float_status *s, frac_clear(p); return; case float_class_inf: - g_assert(!fmt->arm_althp); - p->exp = fmt->exp_max; - frac_clear(p); + switch (fmt->exp_max_kind) { + case float_expmax_ieee: + p->exp = fmt->exp_max; + frac_clear(p); + break; + case float_expmax_e4m3: + partsN(uncanon_e4m3_overflow)(p, s, fmt, saturate); + break; + case float_expmax_normal: + default: + g_assert_not_reached(); + } return; case float_class_qnan: case float_class_snan: - g_assert(!fmt->arm_althp); + assert(fmt->exp_max_kind != float_expmax_normal); p->exp = fmt->exp_max; frac_shr(p, fmt->frac_shift); return; diff --git a/fpu/softfloat-specialize.c.inc b/fpu/softfloat-specialize.c.inc index ba4fa08b7beb..9ed968c79b15 100644 --- a/fpu/softfloat-specialize.c.inc +++ b/fpu/softfloat-specialize.c.inc @@ -227,42 +227,26 @@ floatx80 floatx80_default_inf(bool zSign, float_status *status) } /*---------------------------------------------------------------------------- -| Returns 1 if the half-precision floating-point value `a' is a quiet -| NaN; otherwise returns 0. +| Determine if a float16 NaN is signaling NaN. *----------------------------------------------------------------------------*/ -bool float16_is_quiet_nan(float16 a_, float_status *status) +static bool float16_nan_is_snan(float16 a, float_status *status) { if (no_signaling_nans(status)) { - return float16_is_any_nan(a_); - } else { - uint16_t a = float16_val(a_); - if (snan_bit_is_one(status)) { - return (((a >> 9) & 0x3F) == 0x3E) && (a & 0x1FF); - } else { - - return ((a >> 9) & 0x3F) == 0x3F; - } + return false; } + bool frac_msb_is_one = (a >> 9) & 1; + return frac_msb_is_one == snan_bit_is_one(status); } /*---------------------------------------------------------------------------- -| Returns 1 if the bfloat16 value `a' is a quiet +| Returns 1 if the half-precision floating-point value `a' is a quiet | NaN; otherwise returns 0. *----------------------------------------------------------------------------*/ -bool bfloat16_is_quiet_nan(bfloat16 a_, float_status *status) +bool float16_is_quiet_nan(float16 a_, float_status *status) { - if (no_signaling_nans(status)) { - return bfloat16_is_any_nan(a_); - } else { - uint16_t a = a_; - if (snan_bit_is_one(status)) { - return (((a >> 6) & 0x1FF) == 0x1FE) && (a & 0x3F); - } else { - return ((a >> 6) & 0x1FF) == 0x1FF; - } - } + return float16_is_any_nan(a_) && !float16_nan_is_snan(a_, status); } /*---------------------------------------------------------------------------- @@ -271,36 +255,52 @@ bool bfloat16_is_quiet_nan(bfloat16 a_, float_status *status) *----------------------------------------------------------------------------*/ bool float16_is_signaling_nan(float16 a_, float_status *status) +{ + return float16_is_any_nan(a_) && float16_nan_is_snan(a_, status); +} + +/*---------------------------------------------------------------------------- +| Determine if a bfloat16 NaN is signaling NaN. +*----------------------------------------------------------------------------*/ + +static bool bfloat16_nan_is_snan(bfloat16 a, float_status *status) { if (no_signaling_nans(status)) { - return 0; - } else { - uint16_t a = float16_val(a_); - if (snan_bit_is_one(status)) { - return ((a >> 9) & 0x3F) == 0x3F; - } else { - return (((a >> 9) & 0x3F) == 0x3E) && (a & 0x1FF); - } + return false; } + bool frac_msb_is_one = (a >> 6) & 1; + return frac_msb_is_one == snan_bit_is_one(status); } /*---------------------------------------------------------------------------- -| Returns 1 if the bfloat16 value `a' is a signaling -| NaN; otherwise returns 0. +| Returns 1 if the bfloat16 value `a' is a quiet NaN; otherwise returns 0. +*----------------------------------------------------------------------------*/ + +bool bfloat16_is_quiet_nan(bfloat16 a_, float_status *status) +{ + return bfloat16_is_any_nan(a_) && !bfloat16_nan_is_snan(a_, status); +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the bfloat16 value `a' is a signaling NaN; otherwise returns 0. *----------------------------------------------------------------------------*/ bool bfloat16_is_signaling_nan(bfloat16 a_, float_status *status) +{ + return bfloat16_is_any_nan(a_) && bfloat16_nan_is_snan(a_, status); +} + +/*---------------------------------------------------------------------------- +| Determine if a float32 NaN is signaling NaN. +*----------------------------------------------------------------------------*/ + +static bool float32_nan_is_snan(float32 a, float_status *status) { if (no_signaling_nans(status)) { - return 0; - } else { - uint16_t a = a_; - if (snan_bit_is_one(status)) { - return ((a >> 6) & 0x1FF) == 0x1FF; - } else { - return (((a >> 6) & 0x1FF) == 0x1FE) && (a & 0x3F); - } + return false; } + bool frac_msb_is_one = (a >> 22) & 1; + return frac_msb_is_one == snan_bit_is_one(status); } /*---------------------------------------------------------------------------- @@ -310,16 +310,7 @@ bool bfloat16_is_signaling_nan(bfloat16 a_, float_status *status) bool float32_is_quiet_nan(float32 a_, float_status *status) { - if (no_signaling_nans(status)) { - return float32_is_any_nan(a_); - } else { - uint32_t a = float32_val(a_); - if (snan_bit_is_one(status)) { - return (((a >> 22) & 0x1FF) == 0x1FE) && (a & 0x003FFFFF); - } else { - return ((uint32_t)(a << 1) >= 0xFF800000); - } - } + return float32_is_any_nan(a_) && !float32_nan_is_snan(a_, status); } /*---------------------------------------------------------------------------- @@ -328,17 +319,21 @@ bool float32_is_quiet_nan(float32 a_, float_status *status) *----------------------------------------------------------------------------*/ bool float32_is_signaling_nan(float32 a_, float_status *status) +{ + return float32_is_any_nan(a_) && float32_nan_is_snan(a_, status); +} + +/*---------------------------------------------------------------------------- +| Determine if a float64 NaN is signaling NaN. +*----------------------------------------------------------------------------*/ + +static bool float64_nan_is_snan(float64 a, float_status *status) { if (no_signaling_nans(status)) { - return 0; - } else { - uint32_t a = float32_val(a_); - if (snan_bit_is_one(status)) { - return ((uint32_t)(a << 1) >= 0xFF800000); - } else { - return (((a >> 22) & 0x1FF) == 0x1FE) && (a & 0x003FFFFF); - } + return false; } + bool frac_msb_is_one = (a >> 51) & 1; + return frac_msb_is_one == snan_bit_is_one(status); } /*---------------------------------------------------------------------------- @@ -348,17 +343,7 @@ bool float32_is_signaling_nan(float32 a_, float_status *status) bool float64_is_quiet_nan(float64 a_, float_status *status) { - if (no_signaling_nans(status)) { - return float64_is_any_nan(a_); - } else { - uint64_t a = float64_val(a_); - if (snan_bit_is_one(status)) { - return (((a >> 51) & 0xFFF) == 0xFFE) - && (a & 0x0007FFFFFFFFFFFFULL); - } else { - return ((a << 1) >= 0xFFF0000000000000ULL); - } - } + return float64_is_any_nan(a_) && !float64_nan_is_snan(a_, status); } /*---------------------------------------------------------------------------- @@ -367,68 +352,43 @@ bool float64_is_quiet_nan(float64 a_, float_status *status) *----------------------------------------------------------------------------*/ bool float64_is_signaling_nan(float64 a_, float_status *status) +{ + return float64_is_any_nan(a_) && float64_nan_is_snan(a_, status); +} + +/*---------------------------------------------------------------------------- +| Determine if a floatx80 NaN is signaling NaN. +| The MSB of frac differs from the same function for other types as floatx80 +| has an explicit bit. +*----------------------------------------------------------------------------*/ + +static bool floatx80_nan_is_snan(floatx80 a, float_status *status) { if (no_signaling_nans(status)) { - return 0; - } else { - uint64_t a = float64_val(a_); - if (snan_bit_is_one(status)) { - return ((a << 1) >= 0xFFF0000000000000ULL); - } else { - return (((a >> 51) & 0xFFF) == 0xFFE) - && (a & UINT64_C(0x0007FFFFFFFFFFFF)); - } + return false; } + bool frac_msb_is_one = (a.low >> 62) & 1; + return frac_msb_is_one == snan_bit_is_one(status); } /*---------------------------------------------------------------------------- | Returns 1 if the extended double-precision floating-point value `a' is a -| quiet NaN; otherwise returns 0. This slightly differs from the same -| function for other types as floatx80 has an explicit bit. +| quiet NaN; otherwise returns 0. *----------------------------------------------------------------------------*/ -int floatx80_is_quiet_nan(floatx80 a, float_status *status) +bool floatx80_is_quiet_nan(floatx80 a, float_status *status) { - if (no_signaling_nans(status)) { - return floatx80_is_any_nan(a); - } else { - if (snan_bit_is_one(status)) { - uint64_t aLow; - - aLow = a.low & ~0x4000000000000000ULL; - return ((a.high & 0x7FFF) == 0x7FFF) - && (aLow << 1) - && (a.low == aLow); - } else { - return ((a.high & 0x7FFF) == 0x7FFF) - && (UINT64_C(0x8000000000000000) <= ((uint64_t)(a.low << 1))); - } - } + return floatx80_is_any_nan(a) && !floatx80_nan_is_snan(a, status); } /*---------------------------------------------------------------------------- | Returns 1 if the extended double-precision floating-point value `a' is a -| signaling NaN; otherwise returns 0. This slightly differs from the same -| function for other types as floatx80 has an explicit bit. +| signaling NaN; otherwise returns 0. *----------------------------------------------------------------------------*/ -int floatx80_is_signaling_nan(floatx80 a, float_status *status) +bool floatx80_is_signaling_nan(floatx80 a, float_status *status) { - if (no_signaling_nans(status)) { - return 0; - } else { - if (snan_bit_is_one(status)) { - return ((a.high & 0x7FFF) == 0x7FFF) - && ((a.low << 1) >= 0x8000000000000000ULL); - } else { - uint64_t aLow; - - aLow = a.low & ~UINT64_C(0x4000000000000000); - return ((a.high & 0x7FFF) == 0x7FFF) - && (uint64_t)(aLow << 1) - && (a.low == aLow); - } - } + return floatx80_is_any_nan(a) && floatx80_nan_is_snan(a, status); } /*---------------------------------------------------------------------------- @@ -444,6 +404,19 @@ floatx80 floatx80_silence_nan(floatx80 a, float_status *status) return a; } +/*---------------------------------------------------------------------------- +| Determine if a float128 NaN is signaling NaN. +*----------------------------------------------------------------------------*/ + +static bool float128_nan_is_snan(float128 a, float_status *status) +{ + if (no_signaling_nans(status)) { + return false; + } + bool frac_msb_is_one = (a.high >> 47) & 1; + return frac_msb_is_one == snan_bit_is_one(status); +} + /*---------------------------------------------------------------------------- | Returns 1 if the quadruple-precision floating-point value `a' is a quiet | NaN; otherwise returns 0. @@ -451,17 +424,7 @@ floatx80 floatx80_silence_nan(floatx80 a, float_status *status) bool float128_is_quiet_nan(float128 a, float_status *status) { - if (no_signaling_nans(status)) { - return float128_is_any_nan(a); - } else { - if (snan_bit_is_one(status)) { - return (((a.high >> 47) & 0xFFFF) == 0xFFFE) - && (a.low || (a.high & 0x00007FFFFFFFFFFFULL)); - } else { - return ((a.high << 1) >= 0xFFFF000000000000ULL) - && (a.low || (a.high & 0x0000FFFFFFFFFFFFULL)); - } - } + return float128_is_any_nan(a) && !float128_nan_is_snan(a, status); } /*---------------------------------------------------------------------------- @@ -471,15 +434,5 @@ bool float128_is_quiet_nan(float128 a, float_status *status) bool float128_is_signaling_nan(float128 a, float_status *status) { - if (no_signaling_nans(status)) { - return 0; - } else { - if (snan_bit_is_one(status)) { - return ((a.high << 1) >= 0xFFFF000000000000ULL) - && (a.low || (a.high & 0x0000FFFFFFFFFFFFULL)); - } else { - return (((a.high >> 47) & 0xFFFF) == 0xFFFE) - && (a.low || (a.high & UINT64_C(0x00007FFFFFFFFFFF))); - } - } + return float128_is_any_nan(a) && float128_nan_is_snan(a, status); } diff --git a/fpu/softfloat.c b/fpu/softfloat.c index 874097534830..91c34307c8e5 100644 --- a/fpu/softfloat.c +++ b/fpu/softfloat.c @@ -522,7 +522,18 @@ typedef struct { #define DECOMPOSED_BINARY_POINT 63 #define DECOMPOSED_IMPLICIT_BIT (1ull << DECOMPOSED_BINARY_POINT) -/* Structure holding all of the relevant parameters for a format. +/* Format-specific handling of exp == exp_max */ +typedef enum __attribute__((__packed__)) { + /* exp==max, frac==0 ? infinity : nan; this is ieee standard. */ + float_expmax_ieee, + /* exp==max is a normal number; no infinity or nan representation. */ + float_expmax_normal, + /* exp==max, frac==max ? nan : normal; no infinity representation. */ + float_expmax_e4m3, +} FloatFmtExpMaxKind; + +/* + * Structure holding all of the relevant parameters for a format. * exp_size: the size of the exponent field * exp_bias: the offset applied to the exponent field * exp_max: the maximum normalised exponent @@ -531,9 +542,11 @@ typedef struct { * The following are computed based the size of fraction * round_mask: bits below lsb which must be rounded * The following optional modifiers are available: - * arm_althp: handle ARM Alternative Half Precision + * exp_max_kind: affects how exp == exp_max is interpreted * has_explicit_bit: has an explicit integer bit; this affects whether - * the float_status floatx80_behaviour handling applies + * the float_status floatx80_behaviour handling applies + * overflow_raises_invalid: for float_expmax_normal, raise invalid + * instead of overflow. */ typedef struct { int exp_size; @@ -542,8 +555,9 @@ typedef struct { int exp_max; int frac_size; int frac_shift; - bool arm_althp; + FloatFmtExpMaxKind exp_max_kind; bool has_explicit_bit; + bool overflow_raises_invalid; uint64_t round_mask; } FloatFmt; @@ -560,13 +574,31 @@ typedef struct { .frac_shift = (-F - 1) & 63, \ .round_mask = (1ull << ((-F - 1) & 63)) - 1 +static const FloatFmt float4_e2m1_params = { + FLOAT_PARAMS(2, 1), + .exp_max_kind = float_expmax_normal, +}; + +static const FloatFmt float8_e4m3_params = { + FLOAT_PARAMS(4, 3), + .exp_max_kind = float_expmax_e4m3 +}; + +/* 110 << frac_shift, with the implicit bit set */ +#define E4M3_NORMAL_FRAC_MAX 0xe000000000000000ull + +static const FloatFmt float8_e5m2_params = { + FLOAT_PARAMS(5, 2) +}; + static const FloatFmt float16_params = { FLOAT_PARAMS(5, 10) }; static const FloatFmt float16_params_ahp = { FLOAT_PARAMS(5, 10), - .arm_althp = true + .exp_max_kind = float_expmax_normal, + .overflow_raises_invalid = true, }; static const FloatFmt bfloat16_params = { @@ -614,6 +646,21 @@ static void unpack_raw64(FloatParts64 *r, const FloatFmt *fmt, uint64_t raw) }; } +static void QEMU_FLATTEN float4_e2m1_unpack_raw(FloatParts64 *p, float4_e2m1 f) +{ + unpack_raw64(p, &float4_e2m1_params, f); +} + +static void QEMU_FLATTEN float8_e4m3_unpack_raw(FloatParts64 *p, float8_e4m3 f) +{ + unpack_raw64(p, &float8_e4m3_params, f); +} + +static void QEMU_FLATTEN float8_e5m2_unpack_raw(FloatParts64 *p, float8_e5m2 f) +{ + unpack_raw64(p, &float8_e5m2_params, f); +} + static void QEMU_FLATTEN float16_unpack_raw(FloatParts64 *p, float16 f) { unpack_raw64(p, &float16_params, f); @@ -671,6 +718,16 @@ static uint64_t pack_raw64(const FloatParts64 *p, const FloatFmt *fmt) return ret; } +static float8_e4m3 QEMU_FLATTEN float8_e4m3_pack_raw(const FloatParts64 *p) +{ + return pack_raw64(p, &float8_e4m3_params); +} + +static float8_e5m2 QEMU_FLATTEN float8_e5m2_pack_raw(const FloatParts64 *p) +{ + return pack_raw64(p, &float8_e5m2_params); +} + static float16 QEMU_FLATTEN float16_pack_raw(const FloatParts64 *p) { return make_float16(pack_raw64(p, &float16_params)); @@ -758,20 +815,20 @@ static void parts128_canonicalize(FloatParts128 *p, float_status *status, PARTS_GENERIC_64_128(canonicalize, A)(A, S, F) static void parts64_uncanon_normal(FloatParts64 *p, float_status *status, - const FloatFmt *fmt); + const FloatFmt *fmt, bool saturate); static void parts128_uncanon_normal(FloatParts128 *p, float_status *status, - const FloatFmt *fmt); + const FloatFmt *fmt, bool saturate); -#define parts_uncanon_normal(A, S, F) \ - PARTS_GENERIC_64_128(uncanon_normal, A)(A, S, F) +#define parts_uncanon_normal(A, S, F, X) \ + PARTS_GENERIC_64_128(uncanon_normal, A)(A, S, F, X) static void parts64_uncanon(FloatParts64 *p, float_status *status, - const FloatFmt *fmt); + const FloatFmt *fmt, bool saturate); static void parts128_uncanon(FloatParts128 *p, float_status *status, - const FloatFmt *fmt); + const FloatFmt *fmt, bool saturate); -#define parts_uncanon(A, S, F) \ - PARTS_GENERIC_64_128(uncanon, A)(A, S, F) +#define parts_uncanon(A, S, F, X) \ + PARTS_GENERIC_64_128(uncanon, A)(A, S, F, X) static void parts64_add_normal(FloatParts64 *a, FloatParts64 *b); static void parts128_add_normal(FloatParts128 *a, FloatParts128 *b); @@ -1662,6 +1719,27 @@ static const uint16_t rsqrt_tab[128] = { * Pack/unpack routines with a specific FloatFmt. */ +static void float4_e2m1_unpack_canonical(FloatParts64 *p, float4_e2m1 f, + float_status *s) +{ + float4_e2m1_unpack_raw(p, f); + parts_canonicalize(p, s, &float4_e2m1_params); +} + +static void float8_e4m3_unpack_canonical(FloatParts64 *p, float8_e4m3 f, + float_status *s) +{ + float8_e4m3_unpack_raw(p, f); + parts_canonicalize(p, s, &float8_e4m3_params); +} + +static void float8_e5m2_unpack_canonical(FloatParts64 *p, float8_e5m2 f, + float_status *s) +{ + float8_e5m2_unpack_raw(p, f); + parts_canonicalize(p, s, &float8_e5m2_params); +} + static void float16a_unpack_canonical(FloatParts64 *p, float16 f, float_status *s, const FloatFmt *params) { @@ -1682,11 +1760,27 @@ static void bfloat16_unpack_canonical(FloatParts64 *p, bfloat16 f, parts_canonicalize(p, s, &bfloat16_params); } +static float8_e4m3 float8_e4m3_round_pack_canonical(FloatParts64 *p, + float_status *s, + bool saturate) +{ + parts_uncanon(p, s, &float8_e4m3_params, saturate); + return float8_e4m3_pack_raw(p); +} + +static float8_e5m2 float8_e5m2_round_pack_canonical(FloatParts64 *p, + float_status *s, + bool saturate) +{ + parts_uncanon(p, s, &float8_e5m2_params, saturate); + return float8_e5m2_pack_raw(p); +} + static float16 float16a_round_pack_canonical(FloatParts64 *p, float_status *s, const FloatFmt *params) { - parts_uncanon(p, s, params); + parts_uncanon(p, s, params, false); return float16_pack_raw(p); } @@ -1699,7 +1793,7 @@ static float16 float16_round_pack_canonical(FloatParts64 *p, static bfloat16 bfloat16_round_pack_canonical(FloatParts64 *p, float_status *s) { - parts_uncanon(p, s, &bfloat16_params); + parts_uncanon(p, s, &bfloat16_params, false); return bfloat16_pack_raw(p); } @@ -1713,7 +1807,7 @@ static void float32_unpack_canonical(FloatParts64 *p, float32 f, static float32 float32_round_pack_canonical(FloatParts64 *p, float_status *s) { - parts_uncanon(p, s, &float32_params); + parts_uncanon(p, s, &float32_params, false); return float32_pack_raw(p); } @@ -1727,7 +1821,7 @@ static void float64_unpack_canonical(FloatParts64 *p, float64 f, static float64 float64_round_pack_canonical(FloatParts64 *p, float_status *s) { - parts_uncanon(p, s, &float64_params); + parts_uncanon(p, s, &float64_params, false); return float64_pack_raw(p); } @@ -1776,7 +1870,7 @@ static float64 float64r32_pack_raw(FloatParts64 *p) static float64 float64r32_round_pack_canonical(FloatParts64 *p, float_status *s) { - parts_uncanon(p, s, &float32_params); + parts_uncanon(p, s, &float32_params, false); return float64r32_pack_raw(p); } @@ -1790,7 +1884,7 @@ static void float128_unpack_canonical(FloatParts128 *p, float128 f, static float128 float128_round_pack_canonical(FloatParts128 *p, float_status *s) { - parts_uncanon(p, s, &float128_params); + parts_uncanon(p, s, &float128_params, false); return float128_pack_raw(p); } @@ -1838,7 +1932,7 @@ static floatx80 floatx80_round_pack_canonical(FloatParts128 *p, case float_class_normal: case float_class_denormal: if (s->floatx80_rounding_precision == floatx80_precision_x) { - parts_uncanon_normal(p, s, fmt); + parts_uncanon_normal(p, s, fmt, false); frac = p->frac_hi; exp = p->exp; } else { @@ -1847,7 +1941,7 @@ static floatx80 floatx80_round_pack_canonical(FloatParts128 *p, p64.sign = p->sign; p64.exp = p->exp; frac_truncjam(&p64, p); - parts_uncanon_normal(&p64, s, fmt); + parts_uncanon_normal(&p64, s, fmt, false); frac = p64.frac; exp = p64.exp; } @@ -2245,7 +2339,7 @@ float16_muladd_scalbn(float16 a, float16 b, float16 c, pr = parts_muladd_scalbn(&pa, &pb, &pc, scale, flags, status); /* Round before applying negate result. */ - parts_uncanon(pr, status, &float16_params); + parts_uncanon(pr, status, &float16_params, false); if ((flags & float_muladd_negate_result) && !is_nan(pr->cls)) { pr->sign ^= 1; } @@ -2270,7 +2364,7 @@ float32_muladd_scalbn(float32 a, float32 b, float32 c, pr = parts_muladd_scalbn(&pa, &pb, &pc, scale, flags, status); /* Round before applying negate result. */ - parts_uncanon(pr, status, &float32_params); + parts_uncanon(pr, status, &float32_params, false); if ((flags & float_muladd_negate_result) && !is_nan(pr->cls)) { pr->sign ^= 1; } @@ -2289,7 +2383,7 @@ float64_muladd_scalbn(float64 a, float64 b, float64 c, pr = parts_muladd_scalbn(&pa, &pb, &pc, scale, flags, status); /* Round before applying negate result. */ - parts_uncanon(pr, status, &float64_params); + parts_uncanon(pr, status, &float64_params, false); if ((flags & float_muladd_negate_result) && !is_nan(pr->cls)) { pr->sign ^= 1; } @@ -2448,7 +2542,7 @@ float64 float64r32_muladd(float64 a, float64 b, float64 c, pr = parts_muladd_scalbn(&pa, &pb, &pc, 0, flags, status); /* Round before applying negate result. */ - parts_uncanon(pr, status, &float32_params); + parts_uncanon(pr, status, &float32_params, false); if ((flags & float_muladd_negate_result) && !is_nan(pr->cls)) { pr->sign ^= 1; } @@ -2466,7 +2560,7 @@ bfloat16 QEMU_FLATTEN bfloat16_muladd(bfloat16 a, bfloat16 b, bfloat16 c, pr = parts_muladd_scalbn(&pa, &pb, &pc, 0, flags, status); /* Round before applying negate result. */ - parts_uncanon(pr, status, &bfloat16_params); + parts_uncanon(pr, status, &bfloat16_params, false); if ((flags & float_muladd_negate_result) && !is_nan(pr->cls)) { pr->sign ^= 1; } @@ -2484,7 +2578,7 @@ float128 QEMU_FLATTEN float128_muladd(float128 a, float128 b, float128 c, pr = parts_muladd_scalbn(&pa, &pb, &pc, 0, flags, status); /* Round before applying negate result. */ - parts_uncanon(pr, status, &float128_params); + parts_uncanon(pr, status, &float128_params, false); if ((flags & float_muladd_negate_result) && !is_nan(pr->cls)) { pr->sign ^= 1; } @@ -2759,6 +2853,35 @@ static void parts_float_to_ahp(FloatParts64 *a, float_status *s) } } +static void parts_float_to_e5m2(FloatParts64 *a, float_status *s, bool saturate) +{ + switch (a->cls) { + case float_class_snan: + case float_class_qnan: + parts_return_nan(a, s); + break; + + case float_class_inf: + /* Per OCP, conversion in SATURATE mode bounds Inf to MAX. */ + if (saturate) { + a->cls = float_class_normal; + a->exp = float8_e5m2_params.exp_max - 1; + a->frac = MAKE_64BIT_MASK(float8_e5m2_params.frac_shift, + float8_e5m2_params.frac_size + 1); + } + break; + + case float_class_denormal: + float_raise(float_flag_input_denormal_used, s); + break; + case float_class_normal: + case float_class_zero: + break; + default: + g_assert_not_reached(); + } +} + static void parts64_float_to_float(FloatParts64 *a, float_status *s) { if (is_nan(a->cls)) { @@ -2823,6 +2946,33 @@ static void parts_float_to_float_widen(FloatParts128 *a, FloatParts64 *b, } } +float8_e4m3 float4_e2m1_to_float8_e4m3(float4_e2m1 a, float_status *s) +{ + FloatParts64 p; + + float4_e2m1_unpack_canonical(&p, a, s); + parts_float_to_float(&p, s); + return float8_e4m3_round_pack_canonical(&p, s, false); +} + +bfloat16 float8_e4m3_to_bfloat16(float8_e4m3 a, float_status *s) +{ + FloatParts64 p; + + float8_e4m3_unpack_canonical(&p, a, s); + parts_float_to_float(&p, s); + return bfloat16_round_pack_canonical(&p, s); +} + +bfloat16 float8_e5m2_to_bfloat16(float8_e5m2 a, float_status *s) +{ + FloatParts64 p; + + float8_e5m2_unpack_canonical(&p, a, s); + parts_float_to_float(&p, s); + return bfloat16_round_pack_canonical(&p, s); +} + float32 float16_to_float32(float16 a, bool ieee, float_status *s) { const FloatFmt *fmt16 = ieee ? &float16_params : &float16_params_ahp; @@ -2843,6 +2993,24 @@ float64 float16_to_float64(float16 a, bool ieee, float_status *s) return float64_round_pack_canonical(&p, s); } +float8_e4m3 float32_to_float8_e4m3(float32 a, bool saturate, float_status *s) +{ + FloatParts64 p; + + float32_unpack_canonical(&p, a, s); + parts_float_to_float(&p, s); + return float8_e4m3_round_pack_canonical(&p, s, saturate); +} + +float8_e5m2 float32_to_float8_e5m2(float32 a, bool saturate, float_status *s) +{ + FloatParts64 p; + + float32_unpack_canonical(&p, a, s); + parts_float_to_e5m2(&p, s, saturate); + return float8_e5m2_round_pack_canonical(&p, s, saturate); +} + float16 float32_to_float16(float32 a, bool ieee, float_status *s) { FloatParts64 p; @@ -2910,6 +3078,24 @@ float32 float64_to_float32(float64 a, float_status *s) return float32_round_pack_canonical(&p, s); } +float8_e4m3 bfloat16_to_float8_e4m3(bfloat16 a, bool saturate, float_status *s) +{ + FloatParts64 p; + + bfloat16_unpack_canonical(&p, a, s); + parts_float_to_float(&p, s); + return float8_e4m3_round_pack_canonical(&p, s, saturate); +} + +float8_e5m2 bfloat16_to_float8_e5m2(bfloat16 a, bool saturate, float_status *s) +{ + FloatParts64 p; + + bfloat16_unpack_canonical(&p, a, s); + parts_float_to_e5m2(&p, s, saturate); + return float8_e5m2_round_pack_canonical(&p, s, saturate); +} + float32 bfloat16_to_float32(bfloat16 a, float_status *s) { FloatParts64 p; @@ -5422,7 +5608,7 @@ static void parts_s390_divide_to_integer(FloatParts64 *a, FloatParts64 *b, /* Round remainder to the target format */ *r = *r_precise; status->float_exception_flags = 0; - parts_uncanon(r, status, fmt); + parts_uncanon(r, status, fmt, false); r_flags = status->float_exception_flags; r->frac &= (1ULL << fmt->frac_size) - 1; parts_canonicalize(r, status, fmt); diff --git a/gdb-xml/avr-cpu.xml b/gdb-xml/avr-cpu.xml index c4747f5b40e9..926cf0532109 100644 --- a/gdb-xml/avr-cpu.xml +++ b/gdb-xml/avr-cpu.xml @@ -10,7 +10,7 @@ register descriptions. --> - + diff --git a/gdb-xml/or1k-core.xml b/gdb-xml/or1k-core.xml new file mode 100644 index 000000000000..0d13f355f529 --- /dev/null +++ b/gdb-xml/or1k-core.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb-xml/sparc32-cp0.xml b/gdb-xml/sparc32-cp0.xml new file mode 100644 index 000000000000..eacd89cf3b5f --- /dev/null +++ b/gdb-xml/sparc32-cp0.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/gdb-xml/sparc32-cpu.xml b/gdb-xml/sparc32-cpu.xml new file mode 100644 index 000000000000..242295c886eb --- /dev/null +++ b/gdb-xml/sparc32-cpu.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb-xml/sparc32-fpu.xml b/gdb-xml/sparc32-fpu.xml new file mode 100644 index 000000000000..38217ca7a925 --- /dev/null +++ b/gdb-xml/sparc32-fpu.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb-xml/sparc64-cp0.xml b/gdb-xml/sparc64-cp0.xml new file mode 100644 index 000000000000..9b938dc7ecc0 --- /dev/null +++ b/gdb-xml/sparc64-cp0.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/gdb-xml/sparc64-cpu.xml b/gdb-xml/sparc64-cpu.xml new file mode 100644 index 000000000000..a9bfc95ea653 --- /dev/null +++ b/gdb-xml/sparc64-cpu.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb-xml/sparc64-core.xml b/gdb-xml/sparc64-fpu.xml similarity index 59% rename from gdb-xml/sparc64-core.xml rename to gdb-xml/sparc64-fpu.xml index 375b9bb0cc6a..d7151b34c7f1 100644 --- a/gdb-xml/sparc64-core.xml +++ b/gdb-xml/sparc64-fpu.xml @@ -1,45 +1,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -89,11 +56,4 @@ - - - - - - - diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c index b45eb7c7b2bd..90f4b95135b8 100644 --- a/gdbstub/gdbstub.c +++ b/gdbstub/gdbstub.c @@ -593,6 +593,7 @@ void gdb_init_cpu(CPUState *cpu) gdb_register_feature(cpu, 0, cc->gdb_read_register, cc->gdb_write_register, feature); + assert(!cc->gdb_num_core_regs); cpu->gdb_num_regs = cpu->gdb_num_g_regs = feature->num_regs; } diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c index 6fbe604ce823..e2713b9eee29 100644 --- a/hw/9pfs/9p.c +++ b/hw/9pfs/9p.c @@ -560,6 +560,7 @@ static int coroutine_fn v9fs_mark_fids_unreclaim(V9fsPDU *pdu, V9fsPath *path) sizeof(V9fsFidState *), 1); gint i; + v9fs_path_read_lock(s); g_hash_table_iter_init(&iter, s->fids); /* @@ -580,6 +581,7 @@ static int coroutine_fn v9fs_mark_fids_unreclaim(V9fsPDU *pdu, V9fsPath *path) g_array_append_val(to_reopen, fidp); } } + v9fs_path_unlock(s); for (i = 0; i < to_reopen->len; i++) { fidp = g_array_index(to_reopen, V9fsFidState*, i); @@ -3514,6 +3516,12 @@ static void coroutine_fn v9fs_renameat(void *opaque) goto out_err; } + /* if fs driver is not path based, return EOPNOTSUPP */ + if (!(s->ctx.export_flags & V9FS_PATHNAME_FSCONTEXT)) { + err = -EOPNOTSUPP; + goto out_err; + } + v9fs_path_write_lock(s); err = v9fs_complete_renameat(pdu, olddirfid, &old_name, newdirfid, &new_name); @@ -3604,6 +3612,11 @@ static void coroutine_fn v9fs_wstat(void *opaque) } } if (v9stat.name.size != 0) { + /* if fs driver is not path based, return EOPNOTSUPP */ + if (!(s->ctx.export_flags & V9FS_PATHNAME_FSCONTEXT)) { + err = -EOPNOTSUPP; + goto out; + } v9fs_path_write_lock(s); err = v9fs_complete_rename(pdu, fidp, -1, &v9stat.name); v9fs_path_unlock(s); diff --git a/hw/9pfs/meson.build b/hw/9pfs/meson.build index 7f4d6e3a4517..91b51af838ff 100644 --- a/hw/9pfs/meson.build +++ b/hw/9pfs/meson.build @@ -23,4 +23,4 @@ endif fs_ss.add(when: 'CONFIG_XEN_BUS', if_true: files('xen-9p-backend.c')) system_ss.add_all(when: 'CONFIG_FSDEV_9P', if_true: fs_ss) -specific_ss.add(when: 'CONFIG_VIRTIO_9P', if_true: files('virtio-9p-device.c')) +system_ss.add(when: 'CONFIG_VIRTIO_9P', if_true: files('virtio-9p-device.c')) diff --git a/hw/Kconfig b/hw/Kconfig index f8f92b5d03d8..b3ed092f7a84 100644 --- a/hw/Kconfig +++ b/hw/Kconfig @@ -13,6 +13,7 @@ source fsi/Kconfig source gpio/Kconfig source hyperv/Kconfig source i2c/Kconfig +source i3c/Kconfig source ide/Kconfig source input/Kconfig source intc/Kconfig @@ -22,6 +23,7 @@ source isa/Kconfig source mem/Kconfig source misc/Kconfig source net/Kconfig +source nitro/Kconfig source nubus/Kconfig source nvme/Kconfig source nvram/Kconfig diff --git a/hw/acpi/acpi_interface.c b/hw/acpi/acpi_interface.c index 8637ff18fca2..a44679017ead 100644 --- a/hw/acpi/acpi_interface.c +++ b/hw/acpi/acpi_interface.c @@ -2,25 +2,6 @@ #include "hw/acpi/acpi_dev_interface.h" #include "hw/acpi/acpi_aml_interface.h" #include "qemu/module.h" -#include "qemu/queue.h" - -void acpi_send_event(DeviceState *dev, AcpiEventStatusBits event) -{ - AcpiDeviceIfClass *adevc = ACPI_DEVICE_IF_GET_CLASS(dev); - if (adevc->send_event) { - AcpiDeviceIf *adev = ACPI_DEVICE_IF(dev); - adevc->send_event(adev, event); - } -} - -void qbus_build_aml(BusState *bus, Aml *scope) -{ - BusChild *kid; - - QTAILQ_FOREACH(kid, &bus->children, sibling) { - call_dev_aml_func(DEVICE(kid->child), scope); - } -} static void register_types(void) { diff --git a/hw/acpi/aml-build.c b/hw/acpi/aml-build.c index ea1c415b211b..4b3740508819 100644 --- a/hw/acpi/aml-build.c +++ b/hw/acpi/aml-build.c @@ -25,12 +25,14 @@ #include "hw/acpi/acpi.h" #include "qemu/bswap.h" #include "qemu/bitops.h" +#include "qemu/queue.h" #include "system/numa.h" #include "hw/core/boards.h" #include "hw/acpi/tpm.h" #include "hw/pci/pci_host.h" #include "hw/pci/pci_bus.h" #include "hw/pci/pci_bridge.h" +#include "hw/acpi/acpi_aml_interface.h" #include "qemu/cutils.h" static GArray *build_alloc_array(void) @@ -2647,3 +2649,12 @@ Aml *aml_error_device(void) return dev; } + +void qbus_build_aml(BusState *bus, Aml *scope) +{ + BusChild *kid; + + QTAILQ_FOREACH(kid, &bus->children, sibling) { + call_dev_aml_func(DEVICE(kid->child), scope); + } +} diff --git a/hw/acpi/core.c b/hw/acpi/core.c index 6b65e587f2a5..a6a62a742d11 100644 --- a/hw/acpi/core.c +++ b/hw/acpi/core.c @@ -22,6 +22,7 @@ #include "qemu/osdep.h" #include "hw/core/irq.h" #include "hw/acpi/acpi.h" +#include "hw/acpi/acpi_dev_interface.h" #include "hw/nvram/fw_cfg.h" #include "qemu/config-file.h" #include "qapi/error.h" @@ -753,3 +754,12 @@ void acpi_update_sci(ACPIREGS *regs, qemu_irq irq) (regs->pm1.evt.en & ACPI_BITMASK_TIMER_ENABLE) && !(pm1a_sts & ACPI_BITMASK_TIMER_STATUS)); } + +void acpi_send_event(DeviceState *dev, AcpiEventStatusBits event) +{ + AcpiDeviceIfClass *adevc = ACPI_DEVICE_IF_GET_CLASS(dev); + if (adevc->send_event) { + AcpiDeviceIf *adev = ACPI_DEVICE_IF(dev); + adevc->send_event(adev, event); + } +} diff --git a/hw/acpi/cxl.c b/hw/acpi/cxl.c index 75edb2c0a652..f92f7fa3d558 100644 --- a/hw/acpi/cxl.c +++ b/hw/acpi/cxl.c @@ -172,7 +172,7 @@ static void cedt_build_cfmws(CXLFixedWindow *fw, Aml *cedt) build_append_int_noprefix(table_data, fw->enc_int_gran, 4); /* Window Restrictions */ - build_append_int_noprefix(table_data, 0x0f, 2); + build_append_int_noprefix(table_data, 0x2f, 2); /* QTG ID */ build_append_int_noprefix(table_data, 0, 2); diff --git a/hw/acpi/meson.build b/hw/acpi/meson.build index 66c978aae836..1c5251909b4e 100644 --- a/hw/acpi/meson.build +++ b/hw/acpi/meson.build @@ -1,39 +1,41 @@ acpi_ss = ss.source_set() acpi_ss.add(files( - 'acpi_interface.c', 'aml-build.c', 'bios-linker-loader.c', 'core.c', 'utils.c', )) acpi_ss.add(when: 'CONFIG_ACPI_CPU_HOTPLUG', if_true: files('cpu.c')) -acpi_ss.add(when: 'CONFIG_ACPI_CPU_HOTPLUG', if_false: files('acpi-cpu-hotplug-stub.c')) +stub_ss.add(files('acpi-cpu-hotplug-stub.c')) acpi_ss.add(when: 'CONFIG_ACPI_MEMORY_HOTPLUG', if_true: files('memory_hotplug.c')) -acpi_ss.add(when: 'CONFIG_ACPI_MEMORY_HOTPLUG', if_false: files('acpi-mem-hotplug-stub.c')) +stub_ss.add(files('acpi-mem-hotplug-stub.c')) acpi_ss.add(when: 'CONFIG_ACPI_NVDIMM', if_true: files('nvdimm.c')) -acpi_ss.add(when: 'CONFIG_ACPI_NVDIMM', if_false: files('acpi-nvdimm-stub.c')) +stub_ss.add(files('acpi-nvdimm-stub.c')) acpi_ss.add(when: 'CONFIG_ACPI_PCI', if_true: files('pci.c')) -acpi_ss.add(when: 'CONFIG_ACPI_CXL', if_true: files('cxl.c'), if_false: files('cxl-stub.c')) +acpi_ss.add(when: 'CONFIG_ACPI_CXL', if_true: files('cxl.c')) +stub_ss.add(files('cxl-stub.c')) acpi_ss.add(when: 'CONFIG_ACPI_VMGENID', if_true: files('vmgenid.c')) acpi_ss.add(when: 'CONFIG_ACPI_VMCLOCK', if_true: files('vmclock.c')) acpi_ss.add(when: 'CONFIG_ACPI_HW_REDUCED', if_true: files('generic_event_device.c')) acpi_ss.add(when: 'CONFIG_ACPI_HMAT', if_true: files('hmat.c')) -acpi_ss.add(when: 'CONFIG_ACPI_APEI', if_true: files('ghes.c'), if_false: files('ghes-stub.c')) +acpi_ss.add(when: 'CONFIG_ACPI_APEI', if_true: files('ghes.c')) +stub_ss.add(files('ghes-stub.c')) acpi_ss.add(when: 'CONFIG_ACPI_PIIX4', if_true: files('piix4.c')) acpi_ss.add(when: 'CONFIG_ACPI_PCI_BRIDGE', if_true: files('pci-bridge.c')) acpi_ss.add(when: 'CONFIG_ACPI_PCIHP', if_true: files('pcihp.c')) -acpi_ss.add(when: 'CONFIG_ACPI_PCIHP', if_false: files('acpi-pci-hotplug-stub.c')) +stub_ss.add(files('acpi-pci-hotplug-stub.c')) acpi_ss.add(when: 'CONFIG_ACPI_VIOT', if_true: files('viot.c')) acpi_ss.add(when: 'CONFIG_ACPI_ICH9', if_true: files('ich9.c', 'ich9_tco.c', 'ich9_timer.c')) acpi_ss.add(when: 'CONFIG_ACPI_ERST', if_true: files('erst.c')) -acpi_ss.add(when: 'CONFIG_IPMI', if_true: files('ipmi.c'), if_false: files('ipmi-stub.c')) -acpi_ss.add(when: 'CONFIG_PC', if_false: files('acpi-x86-stub.c')) +acpi_ss.add(when: 'CONFIG_IPMI', if_true: files('ipmi.c')) +stub_ss.add(files('ipmi-stub.c')) +stub_ss.add(files('acpi-x86-stub.c')) if have_tpm acpi_ss.add(files('tpm.c')) endif -system_ss.add(when: 'CONFIG_ACPI', if_false: files('acpi-stub.c', 'aml-build-stub.c', 'ghes-stub.c', 'acpi_interface.c')) -system_ss.add(when: 'CONFIG_ACPI_PCI_BRIDGE', if_false: files('pci-bridge-stub.c')) +stub_ss.add(files('acpi-stub.c', 'aml-build-stub.c', 'ghes-stub.c')) +stub_ss.add(files('pci-bridge-stub.c')) system_ss.add_all(when: 'CONFIG_ACPI', if_true: acpi_ss) system_ss.add(when: 'CONFIG_GHES_CPER', if_true: files('ghes_cper.c')) -system_ss.add(when: 'CONFIG_GHES_CPER', if_false: files('ghes_cper_stub.c')) -system_ss.add(files('acpi-qmp-cmds.c')) +stub_ss.add(files('ghes_cper_stub.c')) +system_ss.add(files('acpi-qmp-cmds.c', 'acpi_interface.c')) diff --git a/hw/acpi/piix4.c b/hw/acpi/piix4.c index 138ac3d39408..43860d122780 100644 --- a/hw/acpi/piix4.c +++ b/hw/acpi/piix4.c @@ -205,11 +205,6 @@ static const VMStateDescription vmstate_cpuhp_state = { } }; -static bool piix4_vmstate_need_smbus(void *opaque, int version_id) -{ - return pm_smbus_vmstate_needed(); -} - /* * This is a fudge to turn off the acpi_index field, * whose test was always broken on piix4 with 6.2 and older machine types. @@ -238,8 +233,7 @@ static const VMStateDescription vmstate_acpi = { VMSTATE_UINT16(ar.pm1.evt.en, PIIX4PMState), VMSTATE_UINT16(ar.pm1.cnt.cnt, PIIX4PMState), VMSTATE_STRUCT(apm, PIIX4PMState, 0, vmstate_apm, APMState), - VMSTATE_STRUCT_TEST(smb, PIIX4PMState, piix4_vmstate_need_smbus, 3, - pmsmb_vmstate, PMSMBus), + VMSTATE_STRUCT(smb, PIIX4PMState, 3, pmsmb_vmstate, PMSMBus), VMSTATE_TIMER_PTR(ar.tmr.timer, PIIX4PMState), VMSTATE_INT64(ar.tmr.overflow_time, PIIX4PMState), VMSTATE_STRUCT(ar.gpe, PIIX4PMState, 2, vmstate_gpe, ACPIGPE), diff --git a/hw/alpha/dp264.c b/hw/alpha/dp264.c index 5e64528431ed..98219f04569b 100644 --- a/hw/alpha/dp264.c +++ b/hw/alpha/dp264.c @@ -16,8 +16,6 @@ #include "hw/rtc/mc146818rtc.h" #include "hw/ide/pci.h" #include "hw/isa/superio.h" -#include "net/net.h" -#include "qemu/cutils.h" #include "qemu/datadir.h" static uint64_t cpu_alpha_superpage_to_phys(void *opaque, uint64_t addr) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index c66c452737ee..4e50fb1111f6 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -64,22 +64,6 @@ config EXYNOS4 select USB_EHCI_SYSBUS select OR_IRQ -config HIGHBANK - bool - default y - depends on TCG && ARM - select A9MPCORE - select A15MPCORE - select AHCI_SYSBUS - select ARM_TIMER # sp804 - select ARM_V7M - select PL011 # UART - select PL022 # SPI - select PL031 # RTC - select PL061 # GPIO - select PL310 # cache controller - select XGMAC # ethernet - config INTEGRATOR bool default y @@ -403,8 +387,13 @@ config STM32F405_SOC select ARM_V7M select OR_IRQ select STM32_RCC + select STM32F2XX_ADC + select STM32F2XX_SPI + select STM32F2XX_TIMER + select STM32F2XX_USART select STM32F4XX_SYSCFG select STM32F4XX_EXTI + select UNIMP config B_L475E_IOT01A bool @@ -493,7 +482,7 @@ config NPCM8XX select SMBUS select PL310 # cache controller select NPCM7XX - select SERIAL + select SERIAL_MM select SSI select UNIMP @@ -545,6 +534,9 @@ config ASPEED_SOC select DS1338 select FTGMAC100 select I2C + select I3C + select DW_I3C + select I3C_DEVICES select DPS310 select PCA9552 select PCA9554 diff --git a/hw/arm/aspeed_ast27x0-ssp.c b/hw/arm/aspeed_ast27x0-ssp.c index 9b12ba67430f..8b84300e0f60 100644 --- a/hw/arm/aspeed_ast27x0-ssp.c +++ b/hw/arm/aspeed_ast27x0-ssp.c @@ -149,6 +149,7 @@ static void aspeed_soc_ast27x0ssp_realize(DeviceState *dev_soc, Error **errp) AspeedCoprocessorState *s = ASPEED_COPROCESSOR(dev_soc); AspeedCoprocessorClass *sc = ASPEED_COPROCESSOR_GET_CLASS(s); DeviceState *armv7m; + MemoryRegion *mr; g_autofree char *sdram_name = NULL; int i; @@ -230,9 +231,9 @@ static void aspeed_soc_ast27x0ssp_realize(DeviceState *dev_soc, Error **errp) } /* UART */ - memory_region_init_alias(&s->uart_alias, OBJECT(s), "uart.alias", - &s->uart->serial.io, 0, - memory_region_size(&s->uart->serial.io)); + mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(s->uart), 0); + memory_region_init_alias(&s->uart_alias, OBJECT(s), "uart.alias", mr, 0, + memory_region_size(mr)); memory_region_add_subregion(s->memory, sc->memmap[s->uart_dev], &s->uart_alias); /* diff --git a/hw/arm/aspeed_ast27x0-tsp.c b/hw/arm/aspeed_ast27x0-tsp.c index e39d1dc17125..e7c7b744919b 100644 --- a/hw/arm/aspeed_ast27x0-tsp.c +++ b/hw/arm/aspeed_ast27x0-tsp.c @@ -149,6 +149,7 @@ static void aspeed_soc_ast27x0tsp_realize(DeviceState *dev_soc, Error **errp) AspeedCoprocessorState *s = ASPEED_COPROCESSOR(dev_soc); AspeedCoprocessorClass *sc = ASPEED_COPROCESSOR_GET_CLASS(s); DeviceState *armv7m; + MemoryRegion *mr; g_autofree char *sdram_name = NULL; int i; @@ -230,9 +231,9 @@ static void aspeed_soc_ast27x0tsp_realize(DeviceState *dev_soc, Error **errp) } /* UART */ - memory_region_init_alias(&s->uart_alias, OBJECT(s), "uart.alias", - &s->uart->serial.io, 0, - memory_region_size(&s->uart->serial.io)); + mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(s->uart), 0); + memory_region_init_alias(&s->uart_alias, OBJECT(s), "uart.alias", mr, 0, + memory_region_size(mr)); memory_region_add_subregion(s->memory, sc->memmap[s->uart_dev], &s->uart_alias); /* diff --git a/hw/arm/highbank.c b/hw/arm/highbank.c deleted file mode 100644 index 92d497999c0f..000000000000 --- a/hw/arm/highbank.c +++ /dev/null @@ -1,404 +0,0 @@ -/* - * Calxeda Highbank SoC emulation - * - * Copyright (c) 2010-2012 Calxeda - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2 or later, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - * - */ - -#include "qemu/osdep.h" -#include "qemu/datadir.h" -#include "qapi/error.h" -#include "hw/core/sysbus.h" -#include "migration/vmstate.h" -#include "hw/arm/boot.h" -#include "hw/arm/machines-qom.h" -#include "hw/core/loader.h" -#include "net/net.h" -#include "system/runstate.h" -#include "system/system.h" -#include "hw/core/boards.h" -#include "qemu/error-report.h" -#include "hw/char/pl011.h" -#include "hw/ide/ahci-sysbus.h" -#include "hw/cpu/a9mpcore.h" -#include "hw/cpu/a15mpcore.h" -#include "qemu/log.h" -#include "qom/object.h" -#include "cpu.h" -#include "target/arm/cpu-qom.h" - -#define SMP_BOOT_ADDR 0x100 -#define SMP_BOOT_REG 0x40 -#define MPCORE_PERIPHBASE 0xfff10000 - -#define MVBAR_ADDR 0x200 -#define BOARD_SETUP_ADDR (MVBAR_ADDR + 8 * sizeof(uint32_t)) - -#define GIC_EXT_IRQS 128 /* EnergyCore ECX-1000 & ECX-2000 */ - -/* Board init. */ - -#define NUM_REGS 0x200 -static void hb_regs_write(void *opaque, hwaddr offset, - uint64_t value, unsigned size) -{ - uint32_t *regs = opaque; - - if (offset == 0xf00) { - if (value == 1 || value == 2) { - qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); - } else if (value == 3) { - qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN); - } - } - - if (offset / 4 >= NUM_REGS) { - qemu_log_mask(LOG_GUEST_ERROR, - "highbank: bad write offset 0x%" HWADDR_PRIx "\n", offset); - return; - } - regs[offset / 4] = value; -} - -static uint64_t hb_regs_read(void *opaque, hwaddr offset, - unsigned size) -{ - uint32_t value; - uint32_t *regs = opaque; - - if (offset / 4 >= NUM_REGS) { - qemu_log_mask(LOG_GUEST_ERROR, - "highbank: bad read offset 0x%" HWADDR_PRIx "\n", offset); - return 0; - } - value = regs[offset / 4]; - - if ((offset == 0x100) || (offset == 0x108) || (offset == 0x10C)) { - value |= 0x30000000; - } - - return value; -} - -static const MemoryRegionOps hb_mem_ops = { - .read = hb_regs_read, - .write = hb_regs_write, - .endianness = DEVICE_NATIVE_ENDIAN, -}; - -#define TYPE_HIGHBANK_REGISTERS "highbank-regs" -OBJECT_DECLARE_SIMPLE_TYPE(HighbankRegsState, HIGHBANK_REGISTERS) - -struct HighbankRegsState { - /*< private >*/ - SysBusDevice parent_obj; - /*< public >*/ - - MemoryRegion iomem; - uint32_t regs[NUM_REGS]; -}; - -static const VMStateDescription vmstate_highbank_regs = { - .name = "highbank-regs", - .version_id = 0, - .minimum_version_id = 0, - .fields = (const VMStateField[]) { - VMSTATE_UINT32_ARRAY(regs, HighbankRegsState, NUM_REGS), - VMSTATE_END_OF_LIST(), - }, -}; - -static void highbank_regs_reset(DeviceState *dev) -{ - HighbankRegsState *s = HIGHBANK_REGISTERS(dev); - - s->regs[0x40] = 0x05F20121; - s->regs[0x41] = 0x2; - s->regs[0x42] = 0x05F30121; - s->regs[0x43] = 0x05F40121; -} - -static void highbank_regs_init(Object *obj) -{ - HighbankRegsState *s = HIGHBANK_REGISTERS(obj); - SysBusDevice *dev = SYS_BUS_DEVICE(obj); - - memory_region_init_io(&s->iomem, obj, &hb_mem_ops, s->regs, - "highbank_regs", 0x1000); - sysbus_init_mmio(dev, &s->iomem); -} - -static void highbank_regs_class_init(ObjectClass *klass, const void *data) -{ - DeviceClass *dc = DEVICE_CLASS(klass); - - dc->desc = "Calxeda Highbank registers"; - dc->vmsd = &vmstate_highbank_regs; - device_class_set_legacy_reset(dc, highbank_regs_reset); -} - -static const TypeInfo highbank_regs_info = { - .name = TYPE_HIGHBANK_REGISTERS, - .parent = TYPE_SYS_BUS_DEVICE, - .instance_size = sizeof(HighbankRegsState), - .instance_init = highbank_regs_init, - .class_init = highbank_regs_class_init, -}; - -static void highbank_regs_register_types(void) -{ - type_register_static(&highbank_regs_info); -} - -type_init(highbank_regs_register_types) - -static struct arm_boot_info highbank_binfo; - -enum cxmachines { - CALXEDA_HIGHBANK, - CALXEDA_MIDWAY, -}; - -/* ram_size must be set to match the upper bound of memory in the - * device tree (linux/arch/arm/boot/dts/highbank.dts), which is - * normally 0xff900000 or -m 4089. When running this board on a - * 32-bit host, set the reg value of memory to 0xf7ff00000 in the - * device tree and pass -m 2047 to QEMU. - */ -static void calxeda_init(MachineState *machine, enum cxmachines machine_id) -{ - DeviceState *dev = NULL; - SysBusDevice *busdev; - qemu_irq pic[GIC_EXT_IRQS]; - int n; - unsigned int smp_cpus = machine->smp.cpus; - qemu_irq cpu_irq[4]; - qemu_irq cpu_fiq[4]; - qemu_irq cpu_virq[4]; - qemu_irq cpu_vfiq[4]; - MemoryRegion *sysram; - MemoryRegion *sysmem; - char *sysboot_filename; - - switch (machine_id) { - case CALXEDA_HIGHBANK: - machine->cpu_type = ARM_CPU_TYPE_NAME("cortex-a9"); - break; - case CALXEDA_MIDWAY: - machine->cpu_type = ARM_CPU_TYPE_NAME("cortex-a15"); - break; - default: - g_assert_not_reached(); - } - - for (n = 0; n < smp_cpus; n++) { - Object *cpuobj; - ARMCPU *cpu; - - cpuobj = object_new(machine->cpu_type); - cpu = ARM_CPU(cpuobj); - - object_property_add_child(OBJECT(machine), "cpu[*]", cpuobj); - object_property_set_int(cpuobj, "psci-conduit", QEMU_PSCI_CONDUIT_SMC, - &error_abort); - - if (object_property_find(cpuobj, "reset-cbar")) { - object_property_set_int(cpuobj, "reset-cbar", MPCORE_PERIPHBASE, - &error_abort); - } - qdev_realize(DEVICE(cpuobj), NULL, &error_fatal); - cpu_irq[n] = qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ); - cpu_fiq[n] = qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_FIQ); - cpu_virq[n] = qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_VIRQ); - cpu_vfiq[n] = qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_VFIQ); - } - - sysmem = get_system_memory(); - /* SDRAM at address zero. */ - memory_region_add_subregion(sysmem, 0, machine->ram); - - sysram = g_new(MemoryRegion, 1); - memory_region_init_ram(sysram, NULL, "highbank.sysram", 0x8000, - &error_fatal); - memory_region_add_subregion(sysmem, 0xfff88000, sysram); - if (machine->firmware != NULL) { - sysboot_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, machine->firmware); - if (sysboot_filename != NULL) { - if (load_image_targphys(sysboot_filename, 0xfff88000, 0x8000, - NULL) < 0) { - error_report("Unable to load %s", machine->firmware); - exit(1); - } - g_free(sysboot_filename); - } else { - error_report("Unable to find %s", machine->firmware); - exit(1); - } - } - - switch (machine_id) { - case CALXEDA_HIGHBANK: - dev = qdev_new("l2x0"); - busdev = SYS_BUS_DEVICE(dev); - sysbus_realize_and_unref(busdev, &error_fatal); - sysbus_mmio_map(busdev, 0, 0xfff12000); - - dev = qdev_new(TYPE_A9MPCORE_PRIV); - break; - case CALXEDA_MIDWAY: - dev = qdev_new(TYPE_A15MPCORE_PRIV); - break; - } - qdev_prop_set_uint32(dev, "num-cpu", smp_cpus); - qdev_prop_set_uint32(dev, "num-irq", GIC_EXT_IRQS + GIC_INTERNAL); - busdev = SYS_BUS_DEVICE(dev); - sysbus_realize_and_unref(busdev, &error_fatal); - sysbus_mmio_map(busdev, 0, MPCORE_PERIPHBASE); - for (n = 0; n < smp_cpus; n++) { - sysbus_connect_irq(busdev, n, cpu_irq[n]); - sysbus_connect_irq(busdev, n + smp_cpus, cpu_fiq[n]); - sysbus_connect_irq(busdev, n + 2 * smp_cpus, cpu_virq[n]); - sysbus_connect_irq(busdev, n + 3 * smp_cpus, cpu_vfiq[n]); - } - - for (n = 0; n < GIC_EXT_IRQS; n++) { - pic[n] = qdev_get_gpio_in(dev, n); - } - - dev = qdev_new("sp804"); - qdev_prop_set_uint32(dev, "freq0", 150000000); - qdev_prop_set_uint32(dev, "freq1", 150000000); - busdev = SYS_BUS_DEVICE(dev); - sysbus_realize_and_unref(busdev, &error_fatal); - sysbus_mmio_map(busdev, 0, 0xfff34000); - sysbus_connect_irq(busdev, 0, pic[18]); - pl011_create(0xfff36000, pic[20], serial_hd(0)); - - dev = qdev_new(TYPE_HIGHBANK_REGISTERS); - busdev = SYS_BUS_DEVICE(dev); - sysbus_realize_and_unref(busdev, &error_fatal); - sysbus_mmio_map(busdev, 0, 0xfff3c000); - - sysbus_create_simple("pl061", 0xfff30000, pic[14]); - sysbus_create_simple("pl061", 0xfff31000, pic[15]); - sysbus_create_simple("pl061", 0xfff32000, pic[16]); - sysbus_create_simple("pl061", 0xfff33000, pic[17]); - sysbus_create_simple("pl031", 0xfff35000, pic[19]); - sysbus_create_simple("pl022", 0xfff39000, pic[23]); - - sysbus_create_simple(TYPE_SYSBUS_AHCI, 0xffe08000, pic[83]); - - dev = qemu_create_nic_device("xgmac", true, NULL); - if (dev) { - sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); - sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xfff50000); - sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[77]); - sysbus_connect_irq(SYS_BUS_DEVICE(dev), 1, pic[78]); - sysbus_connect_irq(SYS_BUS_DEVICE(dev), 2, pic[79]); - } - - dev = qemu_create_nic_device("xgmac", true, NULL); - if (dev) { - sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); - sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xfff51000); - sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[80]); - sysbus_connect_irq(SYS_BUS_DEVICE(dev), 1, pic[81]); - sysbus_connect_irq(SYS_BUS_DEVICE(dev), 2, pic[82]); - } - - /* TODO create and connect IDE devices for ide_drive_get() */ - - highbank_binfo.ram_size = machine->ram_size; - /* highbank requires a dtb in order to boot, and the dtb will override - * the board ID. The following value is ignored, so set it to -1 to be - * clear that the value is meaningless. - */ - highbank_binfo.board_id = -1; - highbank_binfo.loader_start = 0; - highbank_binfo.board_setup_addr = BOARD_SETUP_ADDR; - highbank_binfo.psci_conduit = QEMU_PSCI_CONDUIT_SMC; - - arm_load_kernel(ARM_CPU(first_cpu), machine, &highbank_binfo); -} - -static void highbank_init(MachineState *machine) -{ - calxeda_init(machine, CALXEDA_HIGHBANK); -} - -static void midway_init(MachineState *machine) -{ - calxeda_init(machine, CALXEDA_MIDWAY); -} - -static void highbank_class_init(ObjectClass *oc, const void *data) -{ - static const char * const valid_cpu_types[] = { - ARM_CPU_TYPE_NAME("cortex-a9"), - NULL - }; - MachineClass *mc = MACHINE_CLASS(oc); - - mc->desc = "Calxeda Highbank (ECX-1000)"; - mc->init = highbank_init; - mc->valid_cpu_types = valid_cpu_types; - mc->block_default_type = IF_IDE; - mc->units_per_default_bus = 1; - mc->max_cpus = 4; - mc->ignore_memory_transaction_failures = true; - mc->default_ram_id = "highbank.dram"; - mc->deprecation_reason = "no known users left for this machine"; -} - -static const TypeInfo highbank_type = { - .name = MACHINE_TYPE_NAME("highbank"), - .parent = TYPE_MACHINE, - .class_init = highbank_class_init, - .interfaces = arm_machine_interfaces, -}; - -static void midway_class_init(ObjectClass *oc, const void *data) -{ - static const char * const valid_cpu_types[] = { - ARM_CPU_TYPE_NAME("cortex-a15"), - NULL - }; - MachineClass *mc = MACHINE_CLASS(oc); - - mc->desc = "Calxeda Midway (ECX-2000)"; - mc->init = midway_init; - mc->valid_cpu_types = valid_cpu_types; - mc->block_default_type = IF_IDE; - mc->units_per_default_bus = 1; - mc->max_cpus = 4; - mc->ignore_memory_transaction_failures = true; - mc->default_ram_id = "highbank.dram"; - mc->deprecation_reason = "no known users left for this machine"; -} - -static const TypeInfo midway_type = { - .name = MACHINE_TYPE_NAME("midway"), - .parent = TYPE_MACHINE, - .class_init = midway_class_init, - .interfaces = arm_machine_interfaces, -}; - -static void calxeda_machines_init(void) -{ - type_register_static(&highbank_type); - type_register_static(&midway_type); -} - -type_init(calxeda_machines_init) diff --git a/hw/arm/meson.build b/hw/arm/meson.build index 47cdc51d135a..b187b946f04c 100644 --- a/hw/arm/meson.build +++ b/hw/arm/meson.build @@ -4,7 +4,6 @@ arm_common_ss.add(when: 'CONFIG_ARM_VIRT', if_true: files('virt.c')) arm_common_ss.add(when: 'CONFIG_ACPI', if_true: files('virt-acpi-build.c')) arm_common_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic_boards.c')) arm_common_ss.add(when: 'CONFIG_EMCRAFT_SF2', if_true: files('msf2-som.c')) -arm_common_ss.add(when: 'CONFIG_HIGHBANK', if_true: files('highbank.c')) arm_common_ss.add(when: 'CONFIG_INTEGRATOR', if_true: files('integratorcp.c')) arm_common_ss.add(when: 'CONFIG_MICROBIT', if_true: files('microbit.c')) arm_common_ss.add(when: 'CONFIG_MPS3R', if_true: files('mps3r.c')) diff --git a/hw/arm/smmuv3-accel.c b/hw/arm/smmuv3-accel.c index f5cd4df336a8..17306cd04b66 100644 --- a/hw/arm/smmuv3-accel.c +++ b/hw/arm/smmuv3-accel.c @@ -390,6 +390,133 @@ bool smmuv3_accel_issue_inv_cmd(SMMUv3State *bs, void *cmd, SMMUDevice *sdev, sizeof(Cmd), &entry_num, cmd, errp); } +static void smmuv3_accel_event_read(void *opaque) +{ + SMMUv3State *s = opaque; + IOMMUFDVeventq *veventq = s->s_accel->veventq; + struct { + struct iommufd_vevent_header hdr; + struct iommu_vevent_arm_smmuv3 vevent; + } buf; + enum iommu_veventq_type type = IOMMU_VEVENTQ_TYPE_ARM_SMMUV3; + uint32_t id = veventq->veventq_id; + uint32_t last_seq = veventq->last_event_seq; + ssize_t bytes; + + bytes = read(veventq->veventq_fd, &buf, sizeof(buf)); + if (bytes <= 0) { + if (errno == EAGAIN || errno == EINTR) { + return; + } + error_report_once("vEVENTQ(type %u id %u): read failed (%m)", type, id); + return; + } + + if (bytes == sizeof(buf.hdr) && + (buf.hdr.flags & IOMMU_VEVENTQ_FLAG_LOST_EVENTS)) { + error_report_once("vEVENTQ(type %u id %u): overflowed", type, id); + veventq->event_start = false; + return; + } + if (bytes < sizeof(buf)) { + error_report_once("vEVENTQ(type %u id %u): short read(%zd/%zd bytes)", + type, id, bytes, sizeof(buf)); + return; + } + + /* Check sequence in hdr for lost events if any */ + if (veventq->event_start && (buf.hdr.sequence - last_seq != 1)) { + error_report_once("vEVENTQ(type %u id %u): lost %u event(s)", + type, id, buf.hdr.sequence - last_seq - 1); + } + veventq->last_event_seq = buf.hdr.sequence; + veventq->event_start = true; + smmuv3_propagate_event(s, (Evt *)&buf.vevent); +} + +static void smmuv3_accel_free_veventq(SMMUv3AccelState *accel) +{ + IOMMUFDVeventq *veventq = accel->veventq; + + if (!veventq) { + return; + } + qemu_set_fd_handler(veventq->veventq_fd, NULL, NULL, NULL); + close(veventq->veventq_fd); + iommufd_backend_free_id(accel->viommu->iommufd, veventq->veventq_id); + g_free(veventq); + accel->veventq = NULL; +} + +static void smmuv3_accel_free_viommu(SMMUv3AccelState *accel) +{ + IOMMUFDViommu *viommu = accel->viommu; + + if (!viommu) { + return; + } + smmuv3_accel_free_veventq(accel); + iommufd_backend_free_id(viommu->iommufd, accel->bypass_hwpt_id); + iommufd_backend_free_id(viommu->iommufd, accel->abort_hwpt_id); + iommufd_backend_free_id(viommu->iommufd, accel->viommu->viommu_id); + g_free(viommu); + accel->viommu = NULL; +} + +bool smmuv3_accel_alloc_veventq(SMMUv3State *s, Error **errp) +{ + SMMUv3AccelState *accel = s->s_accel; + IOMMUFDVeventq *veventq; + uint32_t veventq_id; + uint32_t veventq_fd; + int flags; + + if (!accel || !accel->viommu) { + return true; + } + + if (accel->veventq) { + return true; + } + + if (!smmuv3_eventq_enabled(s)) { + return true; + } + + if (!iommufd_backend_alloc_veventq(accel->viommu->iommufd, + accel->viommu->viommu_id, + IOMMU_VEVENTQ_TYPE_ARM_SMMUV3, + 1 << s->eventq.log2size, &veventq_id, + &veventq_fd, errp)) { + return false; + } + + flags = fcntl(veventq_fd, F_GETFL); + if (flags < 0) { + error_setg_errno(errp, errno, "Failed to get flags for vEVENTQ fd"); + goto free_veventq; + } + if (fcntl(veventq_fd, F_SETFL, flags | O_NONBLOCK) < 0) { + error_setg_errno(errp, errno, "Failed to set O_NONBLOCK on vEVENTQ fd"); + goto free_veventq; + } + + veventq = g_new0(IOMMUFDVeventq, 1); + veventq->veventq_id = veventq_id; + veventq->veventq_fd = veventq_fd; + veventq->viommu = accel->viommu; + accel->veventq = veventq; + + /* Set up event handler for veventq fd */ + qemu_set_fd_handler(veventq_fd, smmuv3_accel_event_read, NULL, s); + return true; + +free_veventq: + close(veventq_fd); + iommufd_backend_free_id(accel->viommu->iommufd, veventq_id); + return false; +} + static bool smmuv3_accel_alloc_viommu(SMMUv3State *s, HostIOMMUDeviceIOMMUFD *idev, Error **errp) @@ -415,6 +542,7 @@ smmuv3_accel_alloc_viommu(SMMUv3State *s, HostIOMMUDeviceIOMMUFD *idev, viommu->viommu_id = viommu_id; viommu->s2_hwpt_id = s2_hwpt_id; viommu->iommufd = idev->iommufd; + accel->viommu = viommu; /* * Pre-allocate HWPTs for S1 bypass and abort cases. These will be attached @@ -434,14 +562,20 @@ smmuv3_accel_alloc_viommu(SMMUv3State *s, HostIOMMUDeviceIOMMUFD *idev, goto free_abort_hwpt; } + /* Allocate a vEVENTQ if guest has enabled event queue */ + if (!smmuv3_accel_alloc_veventq(s, errp)) { + goto free_bypass_hwpt; + } + /* Attach a HWPT based on SMMUv3 GBPA.ABORT value */ hwpt_id = smmuv3_accel_gbpa_hwpt(s, accel); if (!host_iommu_device_iommufd_attach_hwpt(idev, hwpt_id, errp)) { - goto free_bypass_hwpt; + goto free_veventq; } - accel->viommu = viommu; return true; +free_veventq: + smmuv3_accel_free_veventq(accel); free_bypass_hwpt: iommufd_backend_free_id(idev->iommufd, accel->bypass_hwpt_id); free_abort_hwpt: @@ -449,6 +583,7 @@ smmuv3_accel_alloc_viommu(SMMUv3State *s, HostIOMMUDeviceIOMMUFD *idev, free_viommu: iommufd_backend_free_id(idev->iommufd, viommu->viommu_id); g_free(viommu); + accel->viommu = NULL; return false; } @@ -549,12 +684,7 @@ static void smmuv3_accel_unset_iommu_device(PCIBus *bus, void *opaque, trace_smmuv3_accel_unset_iommu_device(devfn, idev->devid); if (QLIST_EMPTY(&accel->device_list)) { - iommufd_backend_free_id(accel->viommu->iommufd, accel->bypass_hwpt_id); - iommufd_backend_free_id(accel->viommu->iommufd, accel->abort_hwpt_id); - iommufd_backend_free_id(accel->viommu->iommufd, - accel->viommu->viommu_id); - g_free(accel->viommu); - accel->viommu = NULL; + smmuv3_accel_free_viommu(accel); } } diff --git a/hw/arm/smmuv3-accel.h b/hw/arm/smmuv3-accel.h index a8a64802ecaa..dba6c71de526 100644 --- a/hw/arm/smmuv3-accel.h +++ b/hw/arm/smmuv3-accel.h @@ -22,6 +22,7 @@ */ typedef struct SMMUv3AccelState { IOMMUFDViommu *viommu; + IOMMUFDVeventq *veventq; uint32_t bypass_hwpt_id; uint32_t abort_hwpt_id; QLIST_HEAD(, SMMUv3AccelDevice) device_list; @@ -50,6 +51,7 @@ bool smmuv3_accel_attach_gbpa_hwpt(SMMUv3State *s, Error **errp); bool smmuv3_accel_issue_inv_cmd(SMMUv3State *s, void *cmd, SMMUDevice *sdev, Error **errp); void smmuv3_accel_idr_override(SMMUv3State *s); +bool smmuv3_accel_alloc_veventq(SMMUv3State *s, Error **errp); void smmuv3_accel_reset(SMMUv3State *s); #else static inline void smmuv3_accel_init(SMMUv3State *s) @@ -80,6 +82,10 @@ smmuv3_accel_issue_inv_cmd(SMMUv3State *s, void *cmd, SMMUDevice *sdev, static inline void smmuv3_accel_idr_override(SMMUv3State *s) { } +static inline bool smmuv3_accel_alloc_veventq(SMMUv3State *s, Error **errp) +{ + return true; +} static inline void smmuv3_accel_reset(SMMUv3State *s) { } diff --git a/hw/arm/smmuv3-internal.h b/hw/arm/smmuv3-internal.h index a6464425ec36..eb482c7000f2 100644 --- a/hw/arm/smmuv3-internal.h +++ b/hw/arm/smmuv3-internal.h @@ -41,7 +41,7 @@ typedef enum SMMUTranslationClass { static inline int smmu_enabled(SMMUv3State *s) { - return FIELD_EX32(s->cr[0], CR0, SMMU_ENABLE); + return FIELD_EX32(s->cr[0], CR0, SMMUEN); } /* Command Queue Entry */ @@ -352,7 +352,11 @@ typedef struct SMMUEventInfo { (x)->word[6] = (uint32_t)(addr & 0xffffffff); \ } while (0) +#define EVT_GET_TYPE(x) extract32((x)->word[0], 0, 8) +#define EVT_GET_SID(x) ((x)->word[1]) + void smmuv3_record_event(SMMUv3State *s, SMMUEventInfo *event); +void smmuv3_propagate_event(SMMUv3State *s, Evt *evt); int smmu_find_ste(SMMUv3State *s, uint32_t sid, STE *ste, SMMUEventInfo *event); static inline int oas2bits(int oas_field) diff --git a/hw/arm/smmuv3.c b/hw/arm/smmuv3.c index c08d58c5790a..068108e49b84 100644 --- a/hw/arm/smmuv3.c +++ b/hw/arm/smmuv3.c @@ -168,10 +168,22 @@ static MemTxResult smmuv3_write_eventq(SMMUv3State *s, Evt *evt) return MEMTX_OK; } +void smmuv3_propagate_event(SMMUv3State *s, Evt *evt) +{ + MemTxResult r; + + trace_smmuv3_propagate_event(smmu_event_string(EVT_GET_TYPE(evt)), + EVT_GET_SID(evt)); + QEMU_LOCK_GUARD(&s->mutex); + r = smmuv3_write_eventq(s, evt); + if (r != MEMTX_OK) { + smmuv3_trigger_irq(s, SMMU_IRQ_GERROR, R_GERROR_EVENTQ_ABT_ERR_MASK); + } +} + void smmuv3_record_event(SMMUv3State *s, SMMUEventInfo *info) { Evt evt = {}; - MemTxResult r; if (!smmuv3_eventq_enabled(s)) { return; @@ -251,11 +263,7 @@ void smmuv3_record_event(SMMUv3State *s, SMMUEventInfo *info) g_assert_not_reached(); } - trace_smmuv3_record_event(smmu_event_string(info->type), info->sid); - r = smmuv3_write_eventq(s, &evt); - if (r != MEMTX_OK) { - smmuv3_trigger_irq(s, SMMU_IRQ_GERROR, R_GERROR_EVENTQ_ABT_ERR_MASK); - } + smmuv3_propagate_event(s, &evt); info->recorded = true; } @@ -1399,6 +1407,15 @@ static int smmuv3_cmdq_consume(SMMUv3State *s, Error **errp) break; } + /* + * This command raises CERROR_ILL when stage 1 is not implemented + * according to (IHI 0070G.b) Page 176. + */ + if (!STAGE1_SUPPORTED(s)) { + cmd_error = SMMU_CERROR_ILL; + break; + } + trace_smmuv3_cmdq_cfgi_cd(sid); smmuv3_flush_config(sdev); if (!smmuv3_accel_issue_inv_cmd(s, &cmd, sdev, errp)) { @@ -1605,6 +1622,12 @@ static MemTxResult smmu_writel(SMMUv3State *s, hwaddr offset, s->cr0ack = data & ~SMMU_CR0_RESERVED; /* in case the command queue has been enabled */ smmuv3_cmdq_consume(s, &local_err); + if (local_err) { + error_report_err(local_err); + local_err = NULL; + } + /* Allocate vEVENTQ if EVENTQ is enabled and a vIOMMU is available */ + smmuv3_accel_alloc_veventq(s, &local_err); break; case A_CR1: s->cr[1] = data; diff --git a/hw/arm/trace-events b/hw/arm/trace-events index 8135c0c7344f..3457536fb092 100644 --- a/hw/arm/trace-events +++ b/hw/arm/trace-events @@ -40,7 +40,7 @@ smmuv3_cmdq_opcode(const char *opcode) "<--- %s" smmuv3_cmdq_consume_out(uint32_t prod, uint32_t cons, uint8_t prod_wrap, uint8_t cons_wrap) "prod:%d, cons:%d, prod_wrap:%d, cons_wrap:%d " smmuv3_cmdq_consume_error(const char *cmd_name, uint8_t cmd_error) "Error on %s command execution: %d" smmuv3_write_mmio(uint64_t addr, uint64_t val, unsigned size, uint32_t r) "addr: 0x%"PRIx64" val:0x%"PRIx64" size: 0x%x(%d)" -smmuv3_record_event(const char *type, uint32_t sid) "%s sid=0x%x" +smmuv3_propagate_event(const char *type, uint32_t sid) "%s sid=0x%x" smmuv3_find_ste(uint16_t sid, uint32_t features, uint16_t sid_split) "sid=0x%x features:0x%x, sid_split:0x%x" smmuv3_find_ste_2lvl(uint64_t strtab_base, uint64_t l1ptr, int l1_ste_offset, uint64_t l2ptr, int l2_ste_offset, int max_l2_ste) "strtab_base:0x%"PRIx64" l1ptr:0x%"PRIx64" l1_off:0x%x, l2ptr:0x%"PRIx64" l2_off:0x%x max_l2_ste:%d" smmuv3_get_ste(uint64_t addr) "STE addr: 0x%"PRIx64 diff --git a/hw/arm/virt-acpi-build.c b/hw/arm/virt-acpi-build.c index 544004615d32..719d2f994e65 100644 --- a/hw/arm/virt-acpi-build.c +++ b/hw/arm/virt-acpi-build.c @@ -1154,7 +1154,7 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, VirtMachineState *vms) fw_cfg_acpi_dsdt_add(scope, &memmap[VIRT_FW_CFG]); virtio_acpi_dsdt_add(scope, memmap[VIRT_MMIO].base, memmap[VIRT_MMIO].size, (irqmap[VIRT_MMIO] + ARM_SPI_BASE), - 0, NUM_VIRTIO_TRANSPORTS); + 0, vms->virtio_transports); acpi_dsdt_add_pci(scope, memmap, irqmap[VIRT_PCIE] + ARM_SPI_BASE, vms); if (vms->acpi_dev) { build_ged_aml(scope, "\\_SB."GED_DEVICE, diff --git a/hw/arm/virt.c b/hw/arm/virt.c index 50865e81154e..7456614d0585 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -71,7 +71,6 @@ #include "hw/intc/arm_gicv3_its_common.h" #include "hw/core/irq.h" #include "kvm_arm.h" -#include "hvf_arm.h" #include "whpx_arm.h" #include "hw/firmware/smbios.h" #include "qapi/visitor.h" @@ -1208,7 +1207,7 @@ static void create_virtio_devices(const VirtMachineState *vms) * between kernel versions). For reliable and stable identification * of disks users must use UUIDs or similar mechanisms. */ - for (i = 0; i < NUM_VIRTIO_TRANSPORTS; i++) { + for (i = 0; i < vms->virtio_transports; i++) { int irq = vms->irqmap[VIRT_MMIO] + i; hwaddr base = vms->memmap[VIRT_MMIO].base + i * size; @@ -1223,7 +1222,7 @@ static void create_virtio_devices(const VirtMachineState *vms) * loop influences virtio device to virtio transport assignment, whereas * this loop controls how virtio transports are laid out in the dtb. */ - for (i = NUM_VIRTIO_TRANSPORTS - 1; i >= 0; i--) { + for (i = vms->virtio_transports - 1; i >= 0; i--) { char *nodename; int irq = vms->irqmap[VIRT_MMIO] + i; hwaddr base = vms->memmap[VIRT_MMIO].base + i * size; @@ -2826,6 +2825,36 @@ static void virt_set_its(Object *obj, bool value, Error **errp) } } +static void virt_get_virtio_transports(Object *obj, Visitor *v, + const char *name, void *opaque, + Error **errp) +{ + VirtMachineState *vms = VIRT_MACHINE(obj); + uint8_t transports = vms->virtio_transports; + + visit_type_uint8(v, name, &transports, errp); +} + +static void virt_set_virtio_transports(Object *obj, Visitor *v, + const char *name, void *opaque, + Error **errp) +{ + VirtMachineState *vms = VIRT_MACHINE(obj); + uint8_t transports; + + if (!visit_type_uint8(v, name, &transports, errp)) { + return; + } + + if (transports > NUM_VIRTIO_TRANSPORTS) { + error_setg(errp, "virtio-mmio-transports must not exceed %d", + NUM_VIRTIO_TRANSPORTS); + return; + } + + vms->virtio_transports = transports; +} + static bool virt_get_dtb_randomness(Object *obj, Error **errp) { VirtMachineState *vms = VIRT_MACHINE(obj); @@ -2971,7 +3000,7 @@ static void virt_set_gic_version(Object *obj, const char *value, Error **errp) vms->gic_version = VIRT_GIC_VERSION_MAX; /* Will probe later */ } else { error_setg(errp, "Invalid gic-version value"); - error_append_hint(errp, "Valid values are 3, 2, host, max.\n"); + error_append_hint(errp, "Valid values are 2, 3, 4, host, and max.\n"); } } @@ -3535,6 +3564,13 @@ static void virt_machine_class_init(ObjectClass *oc, const void *data) "Set the high memory region size " "for PCI MMIO"); + object_class_property_add(oc, "virtio-mmio-transports", "uint8", + virt_get_virtio_transports, + virt_set_virtio_transports, + NULL, NULL); + object_class_property_set_description(oc, "virtio-mmio-transports", + "Set the number of virtio-mmio transports to instantiate"); + object_class_property_add_str(oc, "gic-version", virt_get_gic_version, virt_set_gic_version); object_class_property_set_description(oc, "gic-version", @@ -3654,6 +3690,8 @@ static void virt_instance_init(Object *obj) vms->irqmap = a15irqmap; + vms->virtio_transports = NUM_VIRTIO_TRANSPORTS; + virt_flash_create(vms); vms->oem_id = g_strndup(ACPI_BUILD_APPNAME6, 6); diff --git a/hw/audio/ac97.c b/hw/audio/ac97.c index 0694f0adf9c2..fd74c249949a 100644 --- a/hw/audio/ac97.c +++ b/hw/audio/ac97.c @@ -246,15 +246,15 @@ static void voice_set_active(AC97LinkState *s, int bm_index, int on) { switch (bm_index) { case PI_INDEX: - AUD_set_active_in(s->voice_pi, on); + audio_be_set_active_in(s->audio_be, s->voice_pi, on); break; case PO_INDEX: - AUD_set_active_out(s->voice_po, on); + audio_be_set_active_out(s->audio_be, s->voice_po, on); break; case MC_INDEX: - AUD_set_active_in(s->voice_mc, on); + audio_be_set_active_in(s->audio_be, s->voice_mc, on); break; default: @@ -313,13 +313,13 @@ static void open_voice(AC97LinkState *s, int index, int freq) as.freq = freq; as.nchannels = 2; as.fmt = AUDIO_FORMAT_S16; - as.endianness = 0; + as.big_endian = false; if (freq > 0) { s->invalid_freq[index] = 0; switch (index) { case PI_INDEX: - s->voice_pi = AUD_open_in( + s->voice_pi = audio_be_open_in( s->audio_be, s->voice_pi, "ac97.pi", @@ -330,7 +330,7 @@ static void open_voice(AC97LinkState *s, int index, int freq) break; case PO_INDEX: - s->voice_po = AUD_open_out( + s->voice_po = audio_be_open_out( s->audio_be, s->voice_po, "ac97.po", @@ -341,7 +341,7 @@ static void open_voice(AC97LinkState *s, int index, int freq) break; case MC_INDEX: - s->voice_mc = AUD_open_in( + s->voice_mc = audio_be_open_in( s->audio_be, s->voice_mc, "ac97.mc", @@ -355,17 +355,17 @@ static void open_voice(AC97LinkState *s, int index, int freq) s->invalid_freq[index] = freq; switch (index) { case PI_INDEX: - AUD_close_in(s->audio_be, s->voice_pi); + audio_be_close_in(s->audio_be, s->voice_pi); s->voice_pi = NULL; break; case PO_INDEX: - AUD_close_out(s->audio_be, s->voice_po); + audio_be_close_out(s->audio_be, s->voice_po); s->voice_po = NULL; break; case MC_INDEX: - AUD_close_in(s->audio_be, s->voice_mc); + audio_be_close_in(s->audio_be, s->voice_mc); s->voice_mc = NULL; break; } @@ -378,15 +378,15 @@ static void reset_voices(AC97LinkState *s, uint8_t active[LAST_INDEX]) freq = mixer_load(s, AC97_PCM_LR_ADC_Rate); open_voice(s, PI_INDEX, freq); - AUD_set_active_in(s->voice_pi, active[PI_INDEX]); + audio_be_set_active_in(s->audio_be, s->voice_pi, active[PI_INDEX]); freq = mixer_load(s, AC97_PCM_Front_DAC_Rate); open_voice(s, PO_INDEX, freq); - AUD_set_active_out(s->voice_po, active[PO_INDEX]); + audio_be_set_active_out(s->audio_be, s->voice_po, active[PO_INDEX]); freq = mixer_load(s, AC97_MIC_ADC_Rate); open_voice(s, MC_INDEX, freq); - AUD_set_active_in(s->voice_mc, active[MC_INDEX]); + audio_be_set_active_in(s->audio_be, s->voice_mc, active[MC_INDEX]); } static void get_volume(uint16_t vol, uint16_t mask, int inverse, @@ -416,7 +416,7 @@ static void update_combined_volume_out(AC97LinkState *s) lvol = (lvol * plvol) / 255; rvol = (rvol * prvol) / 255; - AUD_set_volume_out_lr(s->voice_po, mute, lvol, rvol); + audio_be_set_volume_out_lr(s->audio_be, s->voice_po, mute, lvol, rvol); } static void update_volume_in(AC97LinkState *s) @@ -427,7 +427,7 @@ static void update_volume_in(AC97LinkState *s) get_volume(mixer_load(s, AC97_Record_Gain_Mute), 0x0f, 0, &mute, &lvol, &rvol); - AUD_set_volume_in_lr(s->voice_pi, mute, lvol, rvol); + audio_be_set_volume_in_lr(s->audio_be, s->voice_pi, mute, lvol, rvol); } static void set_volume(AC97LinkState *s, int index, uint32_t val) @@ -904,7 +904,7 @@ static int write_audio(AC97LinkState *s, AC97BusMasterRegs *r, int copied; to_copy = MIN(temp, sizeof(tmpbuf)); pci_dma_read(&s->dev, addr, tmpbuf, to_copy); - copied = AUD_write(s->voice_po, tmpbuf, to_copy); + copied = audio_be_write(s->audio_be, s->voice_po, tmpbuf, to_copy); dolog("write_audio max=%x to_copy=%x copied=%x", max, to_copy, copied); if (!copied) { @@ -948,7 +948,7 @@ static void write_bup(AC97LinkState *s, int elapsed) while (elapsed) { int temp = MIN(elapsed, sizeof(s->silence)); while (temp) { - int copied = AUD_write(s->voice_po, s->silence, temp); + int copied = audio_be_write(s->audio_be, s->voice_po, s->silence, temp); if (!copied) { return; } @@ -978,7 +978,7 @@ static int read_audio(AC97LinkState *s, AC97BusMasterRegs *r, while (temp) { int acquired; to_copy = MIN(temp, sizeof(tmpbuf)); - acquired = AUD_read(voice, tmpbuf, to_copy); + acquired = audio_be_read(s->audio_be, voice, tmpbuf, to_copy); if (!acquired) { *stop = 1; break; @@ -1275,7 +1275,7 @@ static void ac97_realize(PCIDevice *dev, Error **errp) AC97LinkState *s = AC97(dev); uint8_t *c = s->dev.config; - if (!AUD_backend_check (&s->audio_be, errp)) { + if (!audio_be_check(&s->audio_be, errp)) { return; } @@ -1301,9 +1301,9 @@ static void ac97_exit(PCIDevice *dev) { AC97LinkState *s = AC97(dev); - AUD_close_in(s->audio_be, s->voice_pi); - AUD_close_out(s->audio_be, s->voice_po); - AUD_close_in(s->audio_be, s->voice_mc); + audio_be_close_in(s->audio_be, s->voice_pi); + audio_be_close_out(s->audio_be, s->voice_po); + audio_be_close_in(s->audio_be, s->voice_mc); } static const Property ac97_properties[] = { diff --git a/hw/audio/adlib.c b/hw/audio/adlib.c index 8b9fe53e17b2..52ee5cb6256e 100644 --- a/hw/audio/adlib.c +++ b/hw/audio/adlib.c @@ -34,8 +34,6 @@ #define DEBUG 0 -#define ADLIB_KILL_TIMERS 1 - #define ADLIB_DESC "Yamaha YM3812 (OPL2)" #if DEBUG @@ -68,10 +66,8 @@ struct AdlibState { int64_t exp[2]; #endif int16_t *mixbuf; - uint64_t dexp[2]; SWVoiceOut *voice; int left, pos, samples; - QEMUAudioTimeStamp ats; FM_OPL *opl; PortioList port_list; }; @@ -88,19 +84,7 @@ static void adlib_kill_timers (AdlibState *s) for (i = 0; i < 2; ++i) { if (s->ticking[i]) { - uint64_t delta; - - delta = AUD_get_elapsed_usec_out (s->voice, &s->ats); - ldebug ( - "delta = %f dexp = %f expired => %d", - delta / 1000000.0, - s->dexp[i] / 1000000.0, - delta >= s->dexp[i] - ); - if (ADLIB_KILL_TIMERS || delta >= s->dexp[i]) { - adlib_stop_opl_timer (s, i); - AUD_init_time_stamp_out (s->voice, &s->ats); - } + adlib_stop_opl_timer(s, i); } } } @@ -111,7 +95,7 @@ static void adlib_write(void *opaque, uint32_t nport, uint32_t val) int a = nport & 3; s->active = 1; - AUD_set_active_out (s->voice, 1); + audio_be_set_active_out(s->audio_be, s->voice, 1); adlib_kill_timers (s); @@ -148,8 +132,6 @@ static void timer_handler (void *opaque, int c, double interval_Sec) s->exp[n] = exp; #endif - s->dexp[n] = interval_Sec * 1000000.0; - AUD_init_time_stamp_out (s->voice, &s->ats); } static int write_audio (AdlibState *s, int samples) @@ -161,7 +143,8 @@ static int write_audio (AdlibState *s, int samples) int nbytes, wbytes, wsampl; nbytes = samples << SHIFT; - wbytes = AUD_write ( + wbytes = audio_be_write( + s->audio_be, s->voice, s->mixbuf + (pos << (SHIFT - 1)), nbytes @@ -254,7 +237,7 @@ static void adlib_realizefn (DeviceState *dev, Error **errp) AdlibState *s = ADLIB(dev); struct audsettings as; - if (!AUD_backend_check(&s->audio_be, errp)) { + if (!audio_be_check(&s->audio_be, errp)) { return; } @@ -271,9 +254,9 @@ static void adlib_realizefn (DeviceState *dev, Error **errp) as.freq = s->freq; as.nchannels = SHIFT; as.fmt = AUDIO_FORMAT_S16; - as.endianness = HOST_BIG_ENDIAN; + as.big_endian = HOST_BIG_ENDIAN; - s->voice = AUD_open_out ( + s->voice = audio_be_open_out( s->audio_be, s->voice, "adlib", @@ -287,7 +270,7 @@ static void adlib_realizefn (DeviceState *dev, Error **errp) return; } - s->samples = AUD_get_buffer_size_out (s->voice) >> SHIFT; + s->samples = audio_be_get_buffer_size_out(s->audio_be, s->voice) >> SHIFT; s->mixbuf = g_malloc0 (s->samples << SHIFT); adlib_portio_list[0].offset = s->port; diff --git a/hw/audio/asc.c b/hw/audio/asc.c index a934c2e82c03..ea59bdde7b87 100644 --- a/hw/audio/asc.c +++ b/hw/audio/asc.c @@ -355,12 +355,12 @@ static void asc_out_cb(void *opaque, int free_b) * loop because the FIFO has run out of data, and the driver * reuses the stale content in its circular audio buffer. */ - AUD_write(s->voice, s->silentbuf, samples << s->shift); + audio_be_write(s->audio_be, s->voice, s->silentbuf, samples << s->shift); } return; } - AUD_write(s->voice, s->mixbuf, generated << s->shift); + audio_be_write(s->audio_be, s->voice, s->mixbuf, generated << s->shift); } static uint64_t asc_fifo_read(void *opaque, hwaddr addr, @@ -470,9 +470,9 @@ static void asc_write(void *opaque, hwaddr addr, uint64_t value, asc_fifo_reset(&s->fifos[1]); asc_lower_irq(s); if (value != 0) { - AUD_set_active_out(s->voice, 1); + audio_be_set_active_out(s->audio_be, s->voice, 1); } else { - AUD_set_active_out(s->voice, 0); + audio_be_set_active_out(s->audio_be, s->voice, 0); } } break; @@ -489,7 +489,7 @@ static void asc_write(void *opaque, hwaddr addr, uint64_t value, { int vol = (value & 0xe0); - AUD_set_volume_out_lr(s->voice, 0, vol, vol); + audio_be_set_volume_out_lr(s->audio_be, s->voice, 0, vol, vol); break; } } @@ -545,7 +545,7 @@ static int asc_post_load(void *opaque, int version) ASCState *s = ASC(opaque); if (s->regs[ASC_MODE] != 0) { - AUD_set_active_out(s->voice, 1); + audio_be_set_active_out(s->audio_be, s->voice, 1); } return 0; @@ -614,7 +614,7 @@ static void asc_reset_hold(Object *obj, ResetType type) { ASCState *s = ASC(obj); - AUD_set_active_out(s->voice, 0); + audio_be_set_active_out(s->audio_be, s->voice, 0); memset(s->regs, 0, sizeof(s->regs)); asc_fifo_reset(&s->fifos[0]); @@ -641,16 +641,16 @@ static void asc_realize(DeviceState *dev, Error **errp) ASCState *s = ASC(dev); struct audsettings as; - if (!AUD_backend_check(&s->audio_be, errp)) { + if (!audio_be_check(&s->audio_be, errp)) { return; } as.freq = ASC_FREQ; as.nchannels = 2; as.fmt = AUDIO_FORMAT_U8; - as.endianness = HOST_BIG_ENDIAN; + as.big_endian = HOST_BIG_ENDIAN; - s->voice = AUD_open_out(s->audio_be, s->voice, "asc.out", s, asc_out_cb, + s->voice = audio_be_open_out(s->audio_be, s->voice, "asc.out", s, asc_out_cb, &as); if (!s->voice) { error_setg(errp, "Initializing audio stream failed"); @@ -658,7 +658,7 @@ static void asc_realize(DeviceState *dev, Error **errp) } s->shift = 1; - s->samples = AUD_get_buffer_size_out(s->voice) >> s->shift; + s->samples = audio_be_get_buffer_size_out(s->audio_be, s->voice) >> s->shift; s->mixbuf = g_malloc0(s->samples << s->shift); s->silentbuf = g_malloc(s->samples << s->shift); diff --git a/hw/audio/cs4231a.c b/hw/audio/cs4231a.c index 7489cf42b7d1..c589670e855c 100644 --- a/hw/audio/cs4231a.c +++ b/hw/audio/cs4231a.c @@ -289,7 +289,7 @@ static void cs_reset_voices (CSState *s, uint32_t val) } as.nchannels = (val & (1 << 4)) ? 2 : 1; - as.endianness = 0; + as.big_endian = false; s->tab = NULL; switch ((val >> 5) & ((s->dregs[MODE_And_ID] & MODE2) ? 7 : 3)) { @@ -305,12 +305,12 @@ static void cs_reset_voices (CSState *s, uint32_t val) s->tab = ALawDecompressTable; x_law: as.fmt = AUDIO_FORMAT_S16; - as.endianness = HOST_BIG_ENDIAN; + as.big_endian = HOST_BIG_ENDIAN; s->shift = as.nchannels == 2; break; case 6: - as.endianness = 1; + as.big_endian = true; /* fall through */ case 2: as.fmt = AUDIO_FORMAT_S16; @@ -327,7 +327,7 @@ static void cs_reset_voices (CSState *s, uint32_t val) goto error; } - s->voice = AUD_open_out ( + s->voice = audio_be_open_out( s->audio_be, s->voice, "cs4231a", @@ -339,7 +339,7 @@ static void cs_reset_voices (CSState *s, uint32_t val) if (s->dregs[Interface_Configuration] & PEN) { if (!s->dma_running) { k->hold_DREQ(s->isa_dma, s->dma); - AUD_set_active_out (s->voice, 1); + audio_be_set_active_out(s->audio_be, s->voice, 1); s->transferred = 0; } s->dma_running = 1; @@ -347,7 +347,7 @@ static void cs_reset_voices (CSState *s, uint32_t val) else { if (s->dma_running) { k->release_DREQ(s->isa_dma, s->dma); - AUD_set_active_out (s->voice, 0); + audio_be_set_active_out(s->audio_be, s->voice, 0); } s->dma_running = 0; } @@ -356,7 +356,7 @@ static void cs_reset_voices (CSState *s, uint32_t val) error: if (s->dma_running) { k->release_DREQ(s->isa_dma, s->dma); - AUD_set_active_out (s->voice, 0); + audio_be_set_active_out(s->audio_be, s->voice, 0); } } @@ -465,7 +465,7 @@ static void cs_write (void *opaque, hwaddr addr, if (s->dma_running) { IsaDmaClass *k = ISADMA_GET_CLASS(s->isa_dma); k->release_DREQ(s->isa_dma, s->dma); - AUD_set_active_out (s->voice, 0); + audio_be_set_active_out(s->audio_be, s->voice, 0); s->dma_running = 0; } } @@ -551,11 +551,11 @@ static int cs_write_audio (CSState *s, int nchan, int dma_pos, for (i = 0; i < copied; ++i) linbuf[i] = s->tab[tmpbuf[i]]; - copied = AUD_write (s->voice, linbuf, copied << 1); + copied = audio_be_write(s->audio_be, s->voice, linbuf, copied << 1); copied >>= 1; } else { - copied = AUD_write (s->voice, tmpbuf, copied); + copied = audio_be_write(s->audio_be, s->voice, tmpbuf, copied); } temp -= copied; @@ -614,7 +614,7 @@ static int cs4231a_pre_load (void *opaque) if (s->dma_running) { IsaDmaClass *k = ISADMA_GET_CLASS(s->isa_dma); k->release_DREQ(s->isa_dma, s->dma); - AUD_set_active_out (s->voice, 0); + audio_be_set_active_out(s->audio_be, s->voice, 0); } s->dma_running = 0; return 0; @@ -678,7 +678,7 @@ static void cs4231a_realizefn (DeviceState *dev, Error **errp) return; } - if (!AUD_backend_check(&s->audio_be, errp)) { + if (!audio_be_check(&s->audio_be, errp)) { return; } diff --git a/hw/audio/es1370.c b/hw/audio/es1370.c index 566f93f1eae2..ca7ad16df491 100644 --- a/hw/audio/es1370.c +++ b/hw/audio/es1370.c @@ -330,10 +330,10 @@ static void es1370_reset (ES1370State *s) d->scount = 0; d->leftover = 0; if (i == ADC_CHANNEL) { - AUD_close_in(s->audio_be, s->adc_voice); + audio_be_close_in(s->audio_be, s->adc_voice); s->adc_voice = NULL; } else { - AUD_close_out(s->audio_be, s->dac_voice[i]); + audio_be_close_out(s->audio_be, s->dac_voice[i]); s->dac_voice[i] = NULL; } } @@ -407,11 +407,11 @@ static void es1370_update_voices (ES1370State *s, uint32_t ctl, uint32_t sctl) as.freq = new_freq; as.nchannels = 1 << (new_fmt & 1); as.fmt = (new_fmt & 2) ? AUDIO_FORMAT_S16 : AUDIO_FORMAT_U8; - as.endianness = 0; + as.big_endian = false; if (i == ADC_CHANNEL) { s->adc_voice = - AUD_open_in ( + audio_be_open_in( s->audio_be, s->adc_voice, "es1370.adc", @@ -421,7 +421,7 @@ static void es1370_update_voices (ES1370State *s, uint32_t ctl, uint32_t sctl) ); } else { s->dac_voice[i] = - AUD_open_out ( + audio_be_open_out( s->audio_be, s->dac_voice[i], i ? "es1370.dac2" : "es1370.dac1", @@ -438,9 +438,9 @@ static void es1370_update_voices (ES1370State *s, uint32_t ctl, uint32_t sctl) int on = (ctl & b->ctl_en) && !(sctl & b->sctl_pause); if (i == ADC_CHANNEL) { - AUD_set_active_in (s->adc_voice, on); + audio_be_set_active_in(s->audio_be, s->adc_voice, on); } else { - AUD_set_active_out (s->dac_voice[i], on); + audio_be_set_active_out(s->audio_be, s->dac_voice[i], on); } } } @@ -627,7 +627,7 @@ static void es1370_transfer_audio (ES1370State *s, struct chan *d, int loop_sel, int acquired, to_copy; to_copy = MIN(to_transfer, sizeof(tmpbuf)); - acquired = AUD_read (s->adc_voice, tmpbuf, to_copy); + acquired = audio_be_read(s->audio_be, s->adc_voice, tmpbuf, to_copy); if (!acquired) { break; } @@ -646,7 +646,7 @@ static void es1370_transfer_audio (ES1370State *s, struct chan *d, int loop_sel, to_copy = MIN(to_transfer, sizeof(tmpbuf)); pci_dma_read (&s->dev, addr, tmpbuf, to_copy); - copied = AUD_write (voice, tmpbuf, to_copy); + copied = audio_be_write(s->audio_be, voice, tmpbuf, to_copy); if (!copied) { break; } @@ -784,12 +784,12 @@ static int es1370_post_load (void *opaque, int version_id) for (i = 0; i < NB_CHANNELS; ++i) { if (i == ADC_CHANNEL) { if (s->adc_voice) { - AUD_close_in(s->audio_be, s->adc_voice); + audio_be_close_in(s->audio_be, s->adc_voice); s->adc_voice = NULL; } } else { if (s->dac_voice[i]) { - AUD_close_out(s->audio_be, s->dac_voice[i]); + audio_be_close_out(s->audio_be, s->dac_voice[i]); s->dac_voice[i] = NULL; } } @@ -833,7 +833,7 @@ static void es1370_realize(PCIDevice *dev, Error **errp) ES1370State *s = ES1370(dev); uint8_t *c = s->dev.config; - if (!AUD_backend_check(&s->audio_be, errp)) { + if (!audio_be_check(&s->audio_be, errp)) { return; } @@ -861,10 +861,10 @@ static void es1370_exit(PCIDevice *dev) int i; for (i = 0; i < 2; ++i) { - AUD_close_out(s->audio_be, s->dac_voice[i]); + audio_be_close_out(s->audio_be, s->dac_voice[i]); } - AUD_close_in(s->audio_be, s->adc_voice); + audio_be_close_in(s->audio_be, s->adc_voice); } static const Property es1370_properties[] = { diff --git a/hw/audio/gus.c b/hw/audio/gus.c index 69ddc4191916..196c4f72205c 100644 --- a/hw/audio/gus.c +++ b/hw/audio/gus.c @@ -87,7 +87,8 @@ static int write_audio (GUSState *s, int samples) int nbytes, wbytes, wsampl; nbytes = samples << s->shift; - wbytes = AUD_write ( + wbytes = audio_be_write( + s->audio_be, s->voice, s->mixbuf + (pos << (s->shift - 1)), nbytes @@ -242,7 +243,7 @@ static void gus_realizefn (DeviceState *dev, Error **errp) IsaDmaClass *k; struct audsettings as; - if (!AUD_backend_check(&s->audio_be, errp)) { + if (!audio_be_check(&s->audio_be, errp)) { return; } @@ -255,9 +256,9 @@ static void gus_realizefn (DeviceState *dev, Error **errp) as.freq = s->freq; as.nchannels = 2; as.fmt = AUDIO_FORMAT_S16; - as.endianness = HOST_BIG_ENDIAN; + as.big_endian = HOST_BIG_ENDIAN; - s->voice = AUD_open_out ( + s->voice = audio_be_open_out( s->audio_be, NULL, "gus", @@ -272,7 +273,7 @@ static void gus_realizefn (DeviceState *dev, Error **errp) } s->shift = 2; - s->samples = AUD_get_buffer_size_out (s->voice) >> s->shift; + s->samples = audio_be_get_buffer_size_out(s->audio_be, s->voice) >> s->shift; s->mixbuf = g_malloc0 (s->samples << s->shift); isa_register_portio_list(d, &s->portio_list1, s->port, @@ -287,7 +288,7 @@ static void gus_realizefn (DeviceState *dev, Error **errp) s->emu.opaque = s; s->pic = isa_bus_get_irq(bus, s->emu.gusirq); - AUD_set_active_out (s->voice, 1); + audio_be_set_active_out(s->audio_be, s->voice, 1); } static const Property gus_properties[] = { diff --git a/hw/audio/hda-codec.c b/hw/audio/hda-codec.c index 6445d227594b..173fe56bea3a 100644 --- a/hw/audio/hda-codec.c +++ b/hw/audio/hda-codec.c @@ -187,7 +187,6 @@ struct HDAAudioState { /* properties */ uint32_t debug; bool mixer; - bool use_timer; }; static inline uint32_t hda_bytes_per_second(HDAAudioStream *st) @@ -275,7 +274,8 @@ static void hda_audio_input_cb(void *opaque, int avail) while (to_transfer) { uint32_t start = (uint32_t) (wpos & B_MASK); uint32_t chunk = (uint32_t) MIN(B_SIZE - start, to_transfer); - uint32_t read = AUD_read(st->voice.in, st->buf + start, chunk); + uint32_t read = audio_be_read(st->state->audio_be, st->voice.in, + st->buf + start, chunk); wpos += read; to_transfer -= read; st->wpos += read; @@ -354,7 +354,8 @@ static void hda_audio_output_cb(void *opaque, int avail) while (to_transfer) { uint32_t start = (uint32_t) (rpos & B_MASK); uint32_t chunk = (uint32_t) MIN(B_SIZE - start, to_transfer); - uint32_t written = AUD_write(st->voice.out, st->buf + start, chunk); + uint32_t written = audio_be_write(st->state->audio_be, st->voice.out, + st->buf + start, chunk); rpos += written; to_transfer -= written; st->rpos += written; @@ -366,58 +367,6 @@ static void hda_audio_output_cb(void *opaque, int avail) hda_timer_sync_adjust(st, (wpos - rpos) - (B_SIZE >> 1)); } -static void hda_audio_compat_input_cb(void *opaque, int avail) -{ - HDAAudioStream *st = opaque; - int recv = 0; - int len; - bool rc; - - while (avail - recv >= sizeof(st->compat_buf)) { - if (st->compat_bpos != sizeof(st->compat_buf)) { - len = AUD_read(st->voice.in, st->compat_buf + st->compat_bpos, - sizeof(st->compat_buf) - st->compat_bpos); - st->compat_bpos += len; - recv += len; - if (st->compat_bpos != sizeof(st->compat_buf)) { - break; - } - } - rc = hda_codec_xfer(&st->state->hda, st->stream, false, - st->compat_buf, sizeof(st->compat_buf)); - if (!rc) { - break; - } - st->compat_bpos = 0; - } -} - -static void hda_audio_compat_output_cb(void *opaque, int avail) -{ - HDAAudioStream *st = opaque; - int sent = 0; - int len; - bool rc; - - while (avail - sent >= sizeof(st->compat_buf)) { - if (st->compat_bpos == sizeof(st->compat_buf)) { - rc = hda_codec_xfer(&st->state->hda, st->stream, true, - st->compat_buf, sizeof(st->compat_buf)); - if (!rc) { - break; - } - st->compat_bpos = 0; - } - len = AUD_write(st->voice.out, st->compat_buf + st->compat_bpos, - sizeof(st->compat_buf) - st->compat_bpos); - st->compat_bpos += len; - sent += len; - if (st->compat_bpos != sizeof(st->compat_buf)) { - break; - } - } -} - static void hda_audio_set_running(HDAAudioStream *st, bool running) { if (st->node == NULL) { @@ -428,21 +377,19 @@ static void hda_audio_set_running(HDAAudioStream *st, bool running) } st->running = running; trace_hda_audio_running(st->node->name, st->stream, st->running); - if (st->state->use_timer) { - if (running) { - int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - st->rpos = 0; - st->wpos = 0; - st->buft_start = now; - timer_mod_anticipate_ns(st->buft, now + HDA_TIMER_TICKS); - } else { - timer_del(st->buft); - } + if (running) { + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + st->rpos = 0; + st->wpos = 0; + st->buft_start = now; + timer_mod_anticipate_ns(st->buft, now + HDA_TIMER_TICKS); + } else { + timer_del(st->buft); } if (st->output) { - AUD_set_active_out(st->voice.out, st->running); + audio_be_set_active_out(st->state->audio_be, st->voice.out, st->running); } else { - AUD_set_active_in(st->voice.in, st->running); + audio_be_set_active_in(st->state->audio_be, st->voice.in, st->running); } } @@ -466,15 +413,16 @@ static void hda_audio_set_amp(HDAAudioStream *st) return; } if (st->output) { - AUD_set_volume_out_lr(st->voice.out, muted, left, right); + audio_be_set_volume_out_lr(st->state->audio_be, st->voice.out, + muted, left, right); } else { - AUD_set_volume_in_lr(st->voice.in, muted, left, right); + audio_be_set_volume_in_lr(st->state->audio_be, st->voice.in, + muted, left, right); } } static void hda_audio_setup(HDAAudioStream *st) { - bool use_timer = st->state->use_timer; audio_callback_fn cb; if (st->node == NULL) { @@ -485,22 +433,14 @@ static void hda_audio_setup(HDAAudioStream *st) fmt2name[st->as.fmt], st->as.freq); if (st->output) { - if (use_timer) { - cb = hda_audio_output_cb; - timer_del(st->buft); - } else { - cb = hda_audio_compat_output_cb; - } - st->voice.out = AUD_open_out(st->state->audio_be, st->voice.out, + cb = hda_audio_output_cb; + timer_del(st->buft); + st->voice.out = audio_be_open_out(st->state->audio_be, st->voice.out, st->node->name, st, cb, &st->as); } else { - if (use_timer) { - cb = hda_audio_input_cb; - timer_del(st->buft); - } else { - cb = hda_audio_compat_input_cb; - } - st->voice.in = AUD_open_in(st->state->audio_be, st->voice.in, + cb = hda_audio_input_cb; + timer_del(st->buft); + st->voice.in = audio_be_open_in(st->state->audio_be, st->voice.in, st->node->name, st, cb, &st->as); } } @@ -696,7 +636,7 @@ static void hda_audio_init(HDACodecDevice *hda, const desc_param *param; uint32_t i, type; - if (!AUD_backend_check(&a->audio_be, errp)) { + if (!audio_be_check(&a->audio_be, errp)) { return; } @@ -754,9 +694,9 @@ static void hda_audio_exit(HDACodecDevice *hda) } timer_free(st->buft); if (st->output) { - AUD_close_out(a->audio_be, st->voice.out); + audio_be_close_out(a->audio_be, st->voice.out); } else { - AUD_close_in(a->audio_be, st->voice.in); + audio_be_close_in(a->audio_be, st->voice.in); } } } @@ -804,7 +744,7 @@ static void hda_audio_reset(DeviceState *dev) static bool vmstate_hda_audio_stream_buf_needed(void *opaque) { HDAAudioStream *st = opaque; - return st->state && st->state->use_timer; + return st->state; } static const VMStateDescription vmstate_hda_audio_stream_buf = { @@ -860,7 +800,6 @@ static const Property hda_audio_properties[] = { DEFINE_AUDIO_PROPERTIES(HDAAudioState, audio_be), DEFINE_PROP_UINT32("debug", HDAAudioState, debug, 0), DEFINE_PROP_BOOL("mixer", HDAAudioState, mixer, true), - DEFINE_PROP_BOOL("use-timer", HDAAudioState, use_timer, true), }; static void hda_audio_init_output(HDACodecDevice *hda, Error **errp) diff --git a/hw/audio/lm4549.c b/hw/audio/lm4549.c index f85226d1ac5d..a891e975106b 100644 --- a/hw/audio/lm4549.c +++ b/hw/audio/lm4549.c @@ -101,11 +101,11 @@ static void lm4549_audio_transfer(lm4549_state *s) uint32_t i; /* Activate the voice */ - AUD_set_active_out(s->voice, 1); + audio_be_set_active_out(s->audio_be, s->voice, 1); s->voice_is_active = 1; /* Try to write the buffer content */ - written_bytes = AUD_write(s->voice, s->buffer, + written_bytes = audio_be_write(s->audio_be, s->voice, s->buffer, s->buffer_level * sizeof(uint16_t)); written_samples = written_bytes >> 1; @@ -129,14 +129,14 @@ static void lm4549_audio_out_callback(void *opaque, int free) static uint32_t prev_buffer_level; #ifdef LM4549_DEBUG - int size = AUD_get_buffer_size_out(s->voice); + int size = audio_be_get_buffer_size_out(s->audio_be, s->voice); DPRINTF("audio_out_callback size = %i free = %i\n", size, free); #endif /* Detect that no data are consumed => disable the voice */ if (s->buffer_level == prev_buffer_level) { - AUD_set_active_out(s->voice, 0); + audio_be_set_active_out(s->audio_be, s->voice, 0); s->voice_is_active = 0; } prev_buffer_level = s->buffer_level; @@ -202,9 +202,9 @@ void lm4549_write(lm4549_state *s, as.freq = value; as.nchannels = 2; as.fmt = AUDIO_FORMAT_S16; - as.endianness = 0; + as.big_endian = false; - s->voice = AUD_open_out( + s->voice = audio_be_open_out( s->audio_be, s->voice, "lm4549.out", @@ -272,9 +272,9 @@ static int lm4549_post_load(void *opaque, int version_id) as.freq = freq; as.nchannels = 2; as.fmt = AUDIO_FORMAT_S16; - as.endianness = 0; + as.big_endian = false; - s->voice = AUD_open_out( + s->voice = audio_be_open_out( s->audio_be, s->voice, "lm4549.out", @@ -285,7 +285,7 @@ static int lm4549_post_load(void *opaque, int version_id) /* Request data */ if (s->voice_is_active == 1) { - lm4549_audio_out_callback(s, AUD_get_buffer_size_out(s->voice)); + lm4549_audio_out_callback(s, audio_be_get_buffer_size_out(s->audio_be, s->voice)); } return 0; @@ -297,7 +297,7 @@ void lm4549_init(lm4549_state *s, lm4549_callback data_req_cb, void* opaque, struct audsettings as; /* Register an audio card */ - if (!AUD_backend_check(&s->audio_be, errp)) { + if (!audio_be_check(&s->audio_be, errp)) { return; } @@ -312,9 +312,9 @@ void lm4549_init(lm4549_state *s, lm4549_callback data_req_cb, void* opaque, as.freq = 48000; as.nchannels = 2; as.fmt = AUDIO_FORMAT_S16; - as.endianness = 0; + as.big_endian = false; - s->voice = AUD_open_out( + s->voice = audio_be_open_out( s->audio_be, s->voice, "lm4549.out", @@ -323,7 +323,7 @@ void lm4549_init(lm4549_state *s, lm4549_callback data_req_cb, void* opaque, &as ); - AUD_set_volume_out_lr(s->voice, 0, 255, 255); + audio_be_set_volume_out_lr(s->audio_be, s->voice, 0, 255, 255); s->voice_is_active = 0; diff --git a/hw/audio/pcspk.c b/hw/audio/pcspk.c index 0b01544941cb..6b826507ce32 100644 --- a/hw/audio/pcspk.c +++ b/hw/audio/pcspk.c @@ -105,7 +105,7 @@ static void pcspk_callback(void *opaque, int free) while (free > 0) { n = MIN(s->samples - s->play_pos, (unsigned int)free); - n = AUD_write(s->voice, &s->sample_buf[s->play_pos], n); + n = audio_be_write(s->audio_be, s->voice, &s->sample_buf[s->play_pos], n); if (!n) break; s->play_pos = (s->play_pos + n) % s->samples; @@ -122,7 +122,7 @@ static int pcspk_audio_init(PCSpkState *s) return 0; } - s->voice = AUD_open_out(s->audio_be, s->voice, s_spk, s, pcspk_callback, &as); + s->voice = audio_be_open_out(s->audio_be, s->voice, s_spk, s, pcspk_callback, &as); if (!s->voice) { error_report("pcspk: Could not open voice"); return -1; @@ -163,7 +163,7 @@ static void pcspk_io_write(void *opaque, hwaddr addr, uint64_t val, if (s->voice) { if (gate) /* restart */ s->play_pos = 0; - AUD_set_active_out(s->voice, gate & s->data_on); + audio_be_set_active_out(s->audio_be, s->voice, gate & s->data_on); } } @@ -195,7 +195,7 @@ static void pcspk_realizefn(DeviceState *dev, Error **errp) isa_register_ioport(isadev, &s->ioport, s->iobase); - if (s->audio_be && AUD_backend_check(&s->audio_be, errp)) { + if (s->audio_be && audio_be_check(&s->audio_be, errp)) { pcspk_audio_init(s); return; } diff --git a/hw/audio/sb16.c b/hw/audio/sb16.c index 3c5beb9dfa3c..1b5e452a29b9 100644 --- a/hw/audio/sb16.c +++ b/hw/audio/sb16.c @@ -171,7 +171,7 @@ static void log_dsp (SB16State *dsp) static void speaker (SB16State *s, int on) { s->speaker = on; - /* AUD_enable (s->voice, on); */ + /* audio_be_enable (s->voice, on); */ } static void control (SB16State *s, int hold) @@ -185,11 +185,11 @@ static void control (SB16State *s, int hold) if (hold) { k->hold_DREQ(isa_dma, dma); - AUD_set_active_out (s->voice, 1); + audio_be_set_active_out(s->audio_be, s->voice, 1); } else { k->release_DREQ(isa_dma, dma); - AUD_set_active_out (s->voice, 0); + audio_be_set_active_out(s->audio_be, s->voice, 0); } } @@ -213,9 +213,9 @@ static void continue_dma8 (SB16State *s) as.freq = s->freq; as.nchannels = 1 << s->fmt_stereo; as.fmt = s->fmt; - as.endianness = 0; + as.big_endian = false; - s->voice = AUD_open_out ( + s->voice = audio_be_open_out( s->audio_be, s->voice, "sb16", @@ -376,9 +376,9 @@ static void dma_cmd (SB16State *s, uint8_t cmd, uint8_t d0, int dma_len) as.freq = s->freq; as.nchannels = 1 << s->fmt_stereo; as.fmt = s->fmt; - as.endianness = 0; + as.big_endian = false; - s->voice = AUD_open_out ( + s->voice = audio_be_open_out( s->audio_be, s->voice, "sb16", @@ -877,9 +877,9 @@ static void legacy_reset (SB16State *s) as.freq = s->freq; as.nchannels = 1; as.fmt = AUDIO_FORMAT_U8; - as.endianness = 0; + as.big_endian = false; - s->voice = AUD_open_out ( + s->voice = audio_be_open_out( s->audio_be, s->voice, "sb16", @@ -889,7 +889,7 @@ static void legacy_reset (SB16State *s) ); /* Not sure about that... */ - /* AUD_set_active_out (s->voice, 1); */ + /* audio_be_set_active_out (s->voice, 1); */ } static void reset (SB16State *s) @@ -1196,7 +1196,7 @@ static int write_audio (SB16State *s, int nchan, int dma_pos, } copied = k->read_memory(isa_dma, nchan, tmpbuf, dma_pos, to_copy); - copied = AUD_write (s->voice, tmpbuf, copied); + copied = audio_be_write(s->audio_be, s->voice, tmpbuf, copied); temp -= copied; dma_pos = (dma_pos + copied) % dma_len; @@ -1287,7 +1287,7 @@ static int sb16_post_load (void *opaque, int version_id) SB16State *s = opaque; if (s->voice) { - AUD_close_out(s->audio_be, s->voice); + audio_be_close_out(s->audio_be, s->voice); s->voice = NULL; } @@ -1300,9 +1300,9 @@ static int sb16_post_load (void *opaque, int version_id) as.freq = s->freq; as.nchannels = 1 << s->fmt_stereo; as.fmt = s->fmt; - as.endianness = 0; + as.big_endian = false; - s->voice = AUD_open_out ( + s->voice = audio_be_open_out( s->audio_be, s->voice, "sb16", @@ -1401,7 +1401,7 @@ static void sb16_realizefn (DeviceState *dev, Error **errp) SB16State *s = SB16 (dev); IsaDmaClass *k; - if (!AUD_backend_check(&s->audio_be, errp)) { + if (!audio_be_check(&s->audio_be, errp)) { return; } diff --git a/hw/audio/via-ac97.c b/hw/audio/via-ac97.c index 019d29685346..9d61283542a4 100644 --- a/hw/audio/via-ac97.c +++ b/hw/audio/via-ac97.c @@ -53,7 +53,7 @@ static void codec_volume_set_out(ViaAC97State *s) rvol /= 255; mute = CODEC_REG(s, AC97_Master_Volume_Mute) >> MUTE_SHIFT; mute |= CODEC_REG(s, AC97_PCM_Out_Volume_Mute) >> MUTE_SHIFT; - AUD_set_volume_out_lr(s->vo, mute, lvol, rvol); + audio_be_set_volume_out_lr(s->audio_be, s->vo, mute, lvol, rvol); } static void codec_reset(ViaAC97State *s) @@ -189,7 +189,7 @@ static void out_cb(void *opaque, int avail) while (temp) { to_copy = MIN(temp, sizeof(tmpbuf)); pci_dma_read(&s->dev, c->addr, tmpbuf, to_copy); - copied = AUD_write(s->vo, tmpbuf, to_copy); + copied = audio_be_write(s->audio_be, s->vo, tmpbuf, to_copy); if (!copied) { stop = true; break; @@ -208,7 +208,7 @@ static void out_cb(void *opaque, int avail) c->stat |= STAT_PAUSED; } else { c->stat &= ~STAT_ACTIVE; - AUD_set_active_out(s->vo, 0); + audio_be_set_active_out(s->audio_be, s->vo, 0); } if (c->type & STAT_EOL) { via_isa_set_irq(&s->dev, 0, 1); @@ -237,9 +237,9 @@ static void open_voice_out(ViaAC97State *s) .freq = CODEC_REG(s, AC97_PCM_Front_DAC_Rate), .nchannels = s->aur.type & BIT(4) ? 2 : 1, .fmt = s->aur.type & BIT(5) ? AUDIO_FORMAT_S16 : AUDIO_FORMAT_S8, - .endianness = 0, + .big_endian = false, }; - s->vo = AUD_open_out(s->audio_be, s->vo, "via-ac97.out", s, out_cb, &as); + s->vo = audio_be_open_out(s->audio_be, s->vo, "via-ac97.out", s, out_cb, &as); } static uint64_t sgd_read(void *opaque, hwaddr addr, unsigned size) @@ -317,20 +317,20 @@ static void sgd_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) break; case 1: if (val & CNTL_START) { - AUD_set_active_out(s->vo, 1); + audio_be_set_active_out(s->audio_be, s->vo, 1); s->aur.stat = STAT_ACTIVE; } if (val & CNTL_TERM) { - AUD_set_active_out(s->vo, 0); + audio_be_set_active_out(s->audio_be, s->vo, 0); s->aur.stat &= ~(STAT_ACTIVE | STAT_PAUSED); s->aur.clen = 0; } if (val & CNTL_PAUSE) { - AUD_set_active_out(s->vo, 0); + audio_be_set_active_out(s->audio_be, s->vo, 0); s->aur.stat &= ~STAT_ACTIVE; s->aur.stat |= STAT_PAUSED; } else if (!(val & CNTL_PAUSE) && (s->aur.stat & STAT_PAUSED)) { - AUD_set_active_out(s->vo, 1); + audio_be_set_active_out(s->audio_be, s->vo, 1); s->aur.stat |= STAT_ACTIVE; s->aur.stat &= ~STAT_PAUSED; } @@ -426,7 +426,7 @@ static void via_ac97_realize(PCIDevice *pci_dev, Error **errp) ViaAC97State *s = VIA_AC97(pci_dev); Object *o = OBJECT(s); - if (!AUD_backend_check(&s->audio_be, errp)) { + if (!audio_be_check(&s->audio_be, errp)) { return; } @@ -455,7 +455,7 @@ static void via_ac97_exit(PCIDevice *dev) { ViaAC97State *s = VIA_AC97(dev); - AUD_close_out(s->audio_be, s->vo); + audio_be_close_out(s->audio_be, s->vo); } static const Property via_ac97_properties[] = { diff --git a/hw/audio/virtio-snd.c b/hw/audio/virtio-snd.c index 9101560f3879..fb5cff386606 100644 --- a/hw/audio/virtio-snd.c +++ b/hw/audio/virtio-snd.c @@ -130,7 +130,7 @@ static VirtIOSoundPCMStream *virtio_snd_pcm_get_stream(VirtIOSound *s, uint32_t stream_id) { return stream_id >= s->snd_conf.streams ? NULL : - s->pcm->streams[stream_id]; + s->pcm.streams[stream_id]; } /* @@ -143,7 +143,7 @@ static virtio_snd_pcm_set_params *virtio_snd_pcm_get_params(VirtIOSound *s, uint32_t stream_id) { return stream_id >= s->snd_conf.streams ? NULL - : &s->pcm->pcm_params[stream_id]; + : &s->pcm.pcm_params[stream_id]; } /* @@ -156,7 +156,7 @@ static virtio_snd_pcm_set_params *virtio_snd_pcm_get_params(VirtIOSound *s, static void virtio_snd_handle_pcm_info(VirtIOSound *s, virtio_snd_ctrl_command *cmd) { - uint32_t stream_id, start_id, count, size; + uint32_t stream_id, start_id, count, size, tmp; virtio_snd_pcm_info val; virtio_snd_query_info req; VirtIOSoundPCMStream *stream = NULL; @@ -168,9 +168,6 @@ static void virtio_snd_handle_pcm_info(VirtIOSound *s, sizeof(virtio_snd_query_info)); if (msg_sz != sizeof(virtio_snd_query_info)) { - /* - * TODO: do we need to set DEVICE_NEEDS_RESET? - */ qemu_log_mask(LOG_GUEST_ERROR, "%s: virtio-snd command size incorrect %zu vs \ %zu\n", __func__, msg_sz, sizeof(virtio_snd_query_info)); @@ -182,14 +179,34 @@ static void virtio_snd_handle_pcm_info(VirtIOSound *s, count = le32_to_cpu(req.count); size = le32_to_cpu(req.size); - if (iov_size(cmd->elem->in_sg, cmd->elem->in_num) < - sizeof(virtio_snd_hdr) + size * count) { - /* - * TODO: do we need to set DEVICE_NEEDS_RESET? - */ + /* + * 5.14.6.2 Driver Requirements: Item Information Request + * "The driver MUST NOT set start_id and count such that start_id + count + * is greater than the total number of particular items that is indicated + * in the device configuration space." + */ + if (start_id > s->snd_conf.streams + || !g_uint_checked_add(&tmp, start_id, count) + || start_id + count > s->snd_conf.streams) { + error_report("pcm info: start_id + count is greater than the total " + "number of streams, got: start_id = %u, count = %u", + start_id, count); + cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); + return; + } + + /* + * 5.14.6.2 Driver Requirements: Item Information Request + * "The driver MUST provide a buffer of sizeof(struct virtio_snd_hdr) + + * count * size bytes for the response." + */ + if (!g_uint_checked_mul(&tmp, size, count) + || !g_uint_checked_add(&tmp, tmp, sizeof(virtio_snd_hdr)) + || iov_size(cmd->elem->in_sg, cmd->elem->in_num) < + sizeof(virtio_snd_hdr) + size * count) { error_report("pcm info: buffer too small, got: %zu, needed: %zu", iov_size(cmd->elem->in_sg, cmd->elem->in_num), - sizeof(virtio_snd_pcm_info)); + sizeof(virtio_snd_pcm_info) * count); cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); return; } @@ -243,10 +260,7 @@ uint32_t virtio_snd_set_pcm_params(VirtIOSound *s, { virtio_snd_pcm_set_params *st_params; - if (stream_id >= s->snd_conf.streams || s->pcm->pcm_params == NULL) { - /* - * TODO: do we need to set DEVICE_NEEDS_RESET? - */ + if (stream_id >= s->snd_conf.streams || s->pcm.pcm_params == NULL) { virtio_error(VIRTIO_DEVICE(s), "Streams have not been initialized.\n"); return cpu_to_le32(VIRTIO_SND_S_BAD_MSG); } @@ -297,9 +311,6 @@ static void virtio_snd_handle_pcm_set_params(VirtIOSound *s, sizeof(virtio_snd_pcm_set_params)); if (msg_sz != sizeof(virtio_snd_pcm_set_params)) { - /* - * TODO: do we need to set DEVICE_NEEDS_RESET? - */ qemu_log_mask(LOG_GUEST_ERROR, "%s: virtio-snd command size incorrect %zu vs \ %zu\n", __func__, msg_sz, sizeof(virtio_snd_pcm_set_params)); @@ -378,7 +389,7 @@ static void virtio_snd_get_qemu_audsettings(audsettings *as, as->nchannels = MIN(AUDIO_MAX_CHANNELS, params->channels); as->fmt = virtio_snd_get_qemu_format(params->format); as->freq = virtio_snd_get_qemu_freq(params->rate); - as->endianness = 0; /* Conforming to VIRTIO 1.0: always little endian. */ + as->big_endian = false; /* Conforming to VIRTIO 1.0: always little endian. */ } /* @@ -391,10 +402,10 @@ static void virtio_snd_pcm_close(VirtIOSoundPCMStream *stream) if (stream) { virtio_snd_pcm_flush(stream); if (stream->info.direction == VIRTIO_SND_D_OUTPUT) { - AUD_close_out(stream->pcm->snd->audio_be, stream->voice.out); + audio_be_close_out(stream->s->audio_be, stream->voice.out); stream->voice.out = NULL; } else if (stream->info.direction == VIRTIO_SND_D_INPUT) { - AUD_close_in(stream->pcm->snd->audio_be, stream->voice.in); + audio_be_close_in(stream->s->audio_be, stream->voice.in); stream->voice.in = NULL; } } @@ -413,8 +424,8 @@ static uint32_t virtio_snd_pcm_prepare(VirtIOSound *s, uint32_t stream_id) virtio_snd_pcm_set_params *params; VirtIOSoundPCMStream *stream; - if (s->pcm->streams == NULL || - s->pcm->pcm_params == NULL || + if (s->pcm.streams == NULL || + s->pcm.pcm_params == NULL || stream_id >= s->snd_conf.streams) { return cpu_to_le32(VIRTIO_SND_S_BAD_MSG); } @@ -429,8 +440,8 @@ static uint32_t virtio_snd_pcm_prepare(VirtIOSound *s, uint32_t stream_id) stream = g_new0(VirtIOSoundPCMStream, 1); stream->active = false; stream->id = stream_id; - stream->pcm = s->pcm; stream->s = s; + stream->latency_bytes = 0; qemu_mutex_init(&stream->queue_mutex); QSIMPLEQ_INIT(&stream->queue); @@ -438,7 +449,7 @@ static uint32_t virtio_snd_pcm_prepare(VirtIOSound *s, uint32_t stream_id) * stream_id >= s->snd_conf.streams was checked before so this is * in-bounds */ - s->pcm->streams[stream_id] = stream; + s->pcm.streams[stream_id] = stream; } virtio_snd_get_qemu_audsettings(&as, params); @@ -457,21 +468,21 @@ static uint32_t virtio_snd_pcm_prepare(VirtIOSound *s, uint32_t stream_id) stream->as = as; if (stream->info.direction == VIRTIO_SND_D_OUTPUT) { - stream->voice.out = AUD_open_out(s->audio_be, + stream->voice.out = audio_be_open_out(s->audio_be, stream->voice.out, "virtio-sound.out", stream, virtio_snd_pcm_out_cb, &as); - AUD_set_volume_out_lr(stream->voice.out, 0, 255, 255); + audio_be_set_volume_out_lr(s->audio_be, stream->voice.out, 0, 255, 255); } else { - stream->voice.in = AUD_open_in(s->audio_be, + stream->voice.in = audio_be_open_in(s->audio_be, stream->voice.in, "virtio-sound.in", stream, virtio_snd_pcm_in_cb, &as); - AUD_set_volume_in_lr(stream->voice.in, 0, 255, 255); + audio_be_set_volume_in_lr(s->audio_be, stream->voice.in, 0, 255, 255); } return cpu_to_le32(VIRTIO_SND_S_OK); @@ -561,9 +572,9 @@ static void virtio_snd_handle_pcm_start_stop(VirtIOSound *s, stream->active = start; } if (stream->info.direction == VIRTIO_SND_D_OUTPUT) { - AUD_set_active_out(stream->voice.out, start); + audio_be_set_active_out(s->audio_be, stream->voice.out, start); } else { - AUD_set_active_in(stream->voice.in, start); + audio_be_set_active_in(s->audio_be, stream->voice.in, start); } } else { error_report("Invalid stream id: %"PRIu32, stream_id); @@ -609,9 +620,6 @@ static void virtio_snd_handle_pcm_release(VirtIOSound *s, sizeof(stream_id)); if (msg_sz != sizeof(stream_id)) { - /* - * TODO: do we need to set DEVICE_NEEDS_RESET? - */ qemu_log_mask(LOG_GUEST_ERROR, "%s: virtio-snd command size incorrect %zu vs \ %zu\n", __func__, msg_sz, sizeof(stream_id)); @@ -623,9 +631,6 @@ static void virtio_snd_handle_pcm_release(VirtIOSound *s, trace_virtio_snd_handle_pcm_release(stream_id); stream = virtio_snd_pcm_get_stream(s, stream_id); if (stream == NULL) { - /* - * TODO: do we need to set DEVICE_NEEDS_RESET? - */ error_report("already released stream %"PRIu32, stream_id); virtio_error(VIRTIO_DEVICE(s), "already released stream %"PRIu32, @@ -668,9 +673,6 @@ process_cmd(VirtIOSound *s, virtio_snd_ctrl_command *cmd) sizeof(virtio_snd_hdr)); if (msg_sz != sizeof(virtio_snd_hdr)) { - /* - * TODO: do we need to set DEVICE_NEEDS_RESET? - */ qemu_log_mask(LOG_GUEST_ERROR, "%s: virtio-snd command size incorrect %zu vs \ %zu\n", __func__, msg_sz, sizeof(virtio_snd_hdr)); @@ -881,11 +883,11 @@ static void virtio_snd_handle_tx_xfer(VirtIODevice *vdev, VirtQueue *vq) stream_id = le32_to_cpu(hdr.stream_id); if (stream_id >= vsnd->snd_conf.streams - || vsnd->pcm->streams[stream_id] == NULL) { + || vsnd->pcm.streams[stream_id] == NULL) { goto tx_err; } - stream = vsnd->pcm->streams[stream_id]; + stream = vsnd->pcm.streams[stream_id]; if (stream->info.direction != VIRTIO_SND_D_OUTPUT) { goto tx_err; } @@ -899,6 +901,7 @@ static void virtio_snd_handle_tx_xfer(VirtIODevice *vdev, VirtQueue *vq) buffer->vq = vq; buffer->size = size; buffer->offset = 0; + stream->latency_bytes += size; QSIMPLEQ_INSERT_TAIL(&stream->queue, buffer, entry); } @@ -962,11 +965,11 @@ static void virtio_snd_handle_rx_xfer(VirtIODevice *vdev, VirtQueue *vq) stream_id = le32_to_cpu(hdr.stream_id); if (stream_id >= vsnd->snd_conf.streams - || !vsnd->pcm->streams[stream_id]) { + || !vsnd->pcm.streams[stream_id]) { goto rx_err; } - stream = vsnd->pcm->streams[stream_id]; + stream = vsnd->pcm.streams[stream_id]; if (stream == NULL || stream->info.direction != VIRTIO_SND_D_INPUT) { goto rx_err; } @@ -1053,18 +1056,16 @@ static void virtio_snd_realize(DeviceState *dev, Error **errp) return; } - if (!AUD_backend_check(&vsnd->audio_be, errp)) { + if (!audio_be_check(&vsnd->audio_be, errp)) { return; } vsnd->vmstate = qemu_add_vm_change_state_handler(virtio_snd_vm_state_change, vsnd); - vsnd->pcm = g_new0(VirtIOSoundPCM, 1); - vsnd->pcm->snd = vsnd; - vsnd->pcm->streams = + vsnd->pcm.streams = g_new0(VirtIOSoundPCMStream *, vsnd->snd_conf.streams); - vsnd->pcm->pcm_params = + vsnd->pcm.pcm_params = g_new0(virtio_snd_pcm_set_params, vsnd->snd_conf.streams); virtio_init(vdev, VIRTIO_ID_SOUND, sizeof(virtio_snd_config)); @@ -1112,12 +1113,19 @@ static void virtio_snd_realize(DeviceState *dev, Error **errp) virtio_snd_unrealize(dev); } +static inline void update_latency(VirtIOSoundPCMStream *s, size_t used) +{ + s->latency_bytes = s->latency_bytes > used ? + s->latency_bytes - used : 0; +} + static inline void return_tx_buffer(VirtIOSoundPCMStream *stream, VirtIOSoundPCMBuffer *buffer) { virtio_snd_pcm_status resp = { 0 }; resp.status = cpu_to_le32(VIRTIO_SND_S_OK); - resp.latency_bytes = cpu_to_le32((uint32_t)buffer->size); + update_latency(stream, buffer->size); + resp.latency_bytes = cpu_to_le32(stream->latency_bytes); iov_from_buf(buffer->elem->in_sg, buffer->elem->in_num, 0, @@ -1135,10 +1143,10 @@ static inline void return_tx_buffer(VirtIOSoundPCMStream *stream, } /* - * AUD_* output callback. + * audio_be_* output callback. * * @data: VirtIOSoundPCMStream stream - * @available: number of bytes that can be written with AUD_write() + * @available: number of bytes that can be written with audio_be_write() */ static void virtio_snd_pcm_out_cb(void *data, int available) { @@ -1153,7 +1161,7 @@ static void virtio_snd_pcm_out_cb(void *data, int available) return; } if (!stream->active) { - /* Stream has stopped, so do not perform AUD_write. */ + /* Stream has stopped, so do not perform audio_be_write. */ return_tx_buffer(stream, buffer); continue; } @@ -1166,7 +1174,8 @@ static void virtio_snd_pcm_out_cb(void *data, int available) buffer->populated = true; } for (;;) { - size = AUD_write(stream->voice.out, + size = audio_be_write(stream->s->audio_be, + stream->voice.out, buffer->data + buffer->offset, MIN(buffer->size, available)); assert(size <= MIN(buffer->size, available)); @@ -1178,6 +1187,7 @@ static void virtio_snd_pcm_out_cb(void *data, int available) buffer->size -= size; buffer->offset += size; available -= size; + update_latency(stream, size); if (buffer->size < 1) { return_tx_buffer(stream, buffer); break; @@ -1229,16 +1239,16 @@ static inline void return_rx_buffer(VirtIOSoundPCMStream *stream, /* - * AUD_* input callback. + * audio_be_* input callback. * * @data: VirtIOSoundPCMStream stream - * @available: number of bytes that can be read with AUD_read() + * @available: number of bytes that can be read with audio_be_read() */ static void virtio_snd_pcm_in_cb(void *data, int available) { VirtIOSoundPCMStream *stream = data; VirtIOSoundPCMBuffer *buffer; - size_t size, max_size; + size_t size, max_size, to_read; WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { while (!QSIMPLEQ_EMPTY(&stream->queue)) { @@ -1247,21 +1257,30 @@ static void virtio_snd_pcm_in_cb(void *data, int available) return; } if (!stream->active) { - /* Stream has stopped, so do not perform AUD_read. */ + /* Stream has stopped, so do not perform audio_be_read. */ return_rx_buffer(stream, buffer); continue; } max_size = iov_size(buffer->elem->in_sg, buffer->elem->in_num); + if (max_size <= sizeof(virtio_snd_pcm_status)) { + return_rx_buffer(stream, buffer); + continue; + } + max_size -= sizeof(virtio_snd_pcm_status); + for (;;) { if (buffer->size >= max_size) { return_rx_buffer(stream, buffer); break; } - size = AUD_read(stream->voice.in, - buffer->data + buffer->size, - MIN(available, (stream->params.period_bytes - - buffer->size))); + to_read = stream->params.period_bytes - buffer->size; + to_read = MIN(to_read, available); + to_read = MIN(to_read, max_size - buffer->size); + size = audio_be_read(stream->s->audio_be, + stream->voice.in, + buffer->data + buffer->size, + to_read); if (!size) { available = 0; break; @@ -1313,23 +1332,19 @@ static void virtio_snd_unrealize(DeviceState *dev) qemu_del_vm_change_state_handler(vsnd->vmstate); trace_virtio_snd_unrealize(vsnd); - if (vsnd->pcm) { - if (vsnd->pcm->streams) { - for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) { - stream = vsnd->pcm->streams[i]; - if (stream) { - virtio_snd_process_cmdq(stream->s); - virtio_snd_pcm_close(stream); - qemu_mutex_destroy(&stream->queue_mutex); - g_free(stream); - } + if (vsnd->pcm.streams) { + for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) { + stream = vsnd->pcm.streams[i]; + if (stream) { + virtio_snd_process_cmdq(stream->s); + virtio_snd_pcm_close(stream); + qemu_mutex_destroy(&stream->queue_mutex); + g_free(stream); } - g_free(vsnd->pcm->streams); } - g_free(vsnd->pcm->pcm_params); - g_free(vsnd->pcm); - vsnd->pcm = NULL; + g_free(vsnd->pcm.streams); } + g_free(vsnd->pcm.pcm_params); qemu_mutex_destroy(&vsnd->cmdq_mutex); virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_CONTROL]); virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_EVENT]); diff --git a/hw/audio/wm8750.c b/hw/audio/wm8750.c index 336fb6d20bd7..e2507b0269a0 100644 --- a/hw/audio/wm8750.c +++ b/hw/audio/wm8750.c @@ -72,7 +72,7 @@ static inline void wm8750_in_load(WM8750State *s) if (s->idx_in + s->req_in <= sizeof(s->data_in)) return; s->idx_in = MAX(0, (int) sizeof(s->data_in) - s->req_in); - AUD_read(*s->in[0], s->data_in + s->idx_in, + audio_be_read(s->audio_be, *s->in[0], s->data_in + s->idx_in, sizeof(s->data_in) - s->idx_in); } @@ -80,7 +80,8 @@ static inline void wm8750_out_flush(WM8750State *s) { int sent = 0; while (sent < s->idx_out) - sent += AUD_write(*s->out[0], s->data_out + sent, s->idx_out - sent) + sent += audio_be_write(s->audio_be, *s->out[0], + s->data_out + sent, s->idx_out - sent) ?: s->idx_out; s->idx_out = 0; } @@ -145,30 +146,30 @@ static void wm8750_vol_update(WM8750State *s) { /* FIXME: multiply all volumes by s->invol[2], s->invol[3] */ - AUD_set_volume_in_lr(s->adc_voice[0], s->mute, + audio_be_set_volume_in_lr(s->audio_be, s->adc_voice[0], s->mute, s->inmute[0] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[0]), s->inmute[1] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[1])); - AUD_set_volume_in_lr(s->adc_voice[1], s->mute, + audio_be_set_volume_in_lr(s->audio_be, s->adc_voice[1], s->mute, s->inmute[0] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[0]), s->inmute[1] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[1])); - AUD_set_volume_in_lr(s->adc_voice[2], s->mute, + audio_be_set_volume_in_lr(s->audio_be, s->adc_voice[2], s->mute, s->inmute[0] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[0]), s->inmute[1] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[1])); /* FIXME: multiply all volumes by s->outvol[0], s->outvol[1] */ /* Speaker: LOUT2VOL ROUT2VOL */ - AUD_set_volume_out_lr(s->dac_voice[0], s->mute, + audio_be_set_volume_out_lr(s->audio_be, s->dac_voice[0], s->mute, s->outmute[0] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[4]), s->outmute[1] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[5])); /* Headphone: LOUT1VOL ROUT1VOL */ - AUD_set_volume_out_lr(s->dac_voice[1], s->mute, + audio_be_set_volume_out_lr(s->audio_be, s->dac_voice[1], s->mute, s->outmute[0] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[2]), s->outmute[1] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[3])); /* MONOOUT: MONOVOL MONOVOL */ - AUD_set_volume_out_lr(s->dac_voice[2], s->mute, + audio_be_set_volume_out_lr(s->audio_be, s->dac_voice[2], s->mute, s->outmute[0] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[6]), s->outmute[1] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[6])); } @@ -182,18 +183,18 @@ static void wm8750_set_format(WM8750State *s) wm8750_out_flush(s); if (s->in[0] && *s->in[0]) - AUD_set_active_in(*s->in[0], 0); + audio_be_set_active_in(s->audio_be, *s->in[0], 0); if (s->out[0] && *s->out[0]) - AUD_set_active_out(*s->out[0], 0); + audio_be_set_active_out(s->audio_be, *s->out[0], 0); for (i = 0; i < IN_PORT_N; i ++) if (s->adc_voice[i]) { - AUD_close_in(s->audio_be, s->adc_voice[i]); + audio_be_close_in(s->audio_be, s->adc_voice[i]); s->adc_voice[i] = NULL; } for (i = 0; i < OUT_PORT_N; i ++) if (s->dac_voice[i]) { - AUD_close_out(s->audio_be, s->dac_voice[i]); + audio_be_close_out(s->audio_be, s->dac_voice[i]); s->dac_voice[i] = NULL; } @@ -201,30 +202,30 @@ static void wm8750_set_format(WM8750State *s) return; /* Setup input */ - in_fmt.endianness = 0; + in_fmt.big_endian = false; in_fmt.nchannels = 2; in_fmt.freq = s->adc_hz; in_fmt.fmt = AUDIO_FORMAT_S16; - s->adc_voice[0] = AUD_open_in(s->audio_be, s->adc_voice[0], + s->adc_voice[0] = audio_be_open_in(s->audio_be, s->adc_voice[0], CODEC ".input1", s, wm8750_audio_in_cb, &in_fmt); - s->adc_voice[1] = AUD_open_in(s->audio_be, s->adc_voice[1], + s->adc_voice[1] = audio_be_open_in(s->audio_be, s->adc_voice[1], CODEC ".input2", s, wm8750_audio_in_cb, &in_fmt); - s->adc_voice[2] = AUD_open_in(s->audio_be, s->adc_voice[2], + s->adc_voice[2] = audio_be_open_in(s->audio_be, s->adc_voice[2], CODEC ".input3", s, wm8750_audio_in_cb, &in_fmt); /* Setup output */ - out_fmt.endianness = 0; + out_fmt.big_endian = false; out_fmt.nchannels = 2; out_fmt.freq = s->dac_hz; out_fmt.fmt = AUDIO_FORMAT_S16; - s->dac_voice[0] = AUD_open_out(s->audio_be, s->dac_voice[0], + s->dac_voice[0] = audio_be_open_out(s->audio_be, s->dac_voice[0], CODEC ".speaker", s, wm8750_audio_out_cb, &out_fmt); - s->dac_voice[1] = AUD_open_out(s->audio_be, s->dac_voice[1], + s->dac_voice[1] = audio_be_open_out(s->audio_be, s->dac_voice[1], CODEC ".headphone", s, wm8750_audio_out_cb, &out_fmt); /* MONOMIX is also in stereo for simplicity */ - s->dac_voice[2] = AUD_open_out(s->audio_be, s->dac_voice[2], + s->dac_voice[2] = audio_be_open_out(s->audio_be, s->dac_voice[2], CODEC ".monomix", s, wm8750_audio_out_cb, &out_fmt); /* no sense emulating OUT3 which is a mix of other outputs */ @@ -235,9 +236,9 @@ static void wm8750_set_format(WM8750State *s) * for mixing or combining paths to different ports, so we * connect both channels to where the left channel is routed. */ if (s->in[0] && *s->in[0]) - AUD_set_active_in(*s->in[0], 1); + audio_be_set_active_in(s->audio_be, *s->in[0], 1); if (s->out[0] && *s->out[0]) - AUD_set_active_out(*s->out[0], 1); + audio_be_set_active_out(s->audio_be, *s->out[0], 1); } static void wm8750_clk_update(WM8750State *s, int ext) @@ -624,7 +625,7 @@ static void wm8750_realize(DeviceState *dev, Error **errp) { WM8750State *s = WM8750(dev); - if (!AUD_backend_check(&s->audio_be, errp)) { + if (!audio_be_check(&s->audio_be, errp)) { return; } diff --git a/hw/block/meson.build b/hw/block/meson.build index 43ed296cf474..d646323b82b8 100644 --- a/hw/block/meson.build +++ b/hw/block/meson.build @@ -13,9 +13,9 @@ system_ss.add(when: 'CONFIG_SSI_M25P80', if_true: files('m25p80_sfdp.c')) system_ss.add(when: 'CONFIG_SWIM', if_true: files('swim.c')) system_ss.add(when: 'CONFIG_XEN_BUS', if_true: files('xen-block.c')) -specific_ss.add(when: 'CONFIG_VIRTIO_BLK', if_true: files('virtio-blk.c')) +system_ss.add(when: 'CONFIG_VIRTIO_BLK', if_true: files('virtio-blk.c')) system_ss.add(when: 'CONFIG_VIRTIO_BLK', if_true: files('virtio-blk-common.c')) -specific_ss.add(when: 'CONFIG_VHOST_USER_BLK', if_true: files('vhost-user-blk.c')) +system_ss.add(when: 'CONFIG_VHOST_USER_BLK', if_true: files('vhost-user-blk.c')) system_ss.add(when: 'CONFIG_VHOST_USER_BLK', if_true: files('virtio-blk-common.c')) subdir('dataplane') diff --git a/hw/block/pflash_cfi01.c b/hw/block/pflash_cfi01.c index c0f5b9d8fade..5b9ddb20b18c 100644 --- a/hw/block/pflash_cfi01.c +++ b/hw/block/pflash_cfi01.c @@ -84,7 +84,6 @@ struct PFlashCFI01 { char *name; void *storage; VMChangeStateEntry *vmstate; - bool old_multiple_chip_handling; /* block update buffer */ unsigned char *blk_bytes; @@ -703,13 +702,8 @@ static void pflash_cfi01_fill_cfi_table(PFlashCFI01 *pfl) * in the cfi_table[]. */ num_devices = pfl->device_width ? (pfl->bank_width / pfl->device_width) : 1; - if (pfl->old_multiple_chip_handling) { - blocks_per_device = pfl->nb_blocs / num_devices; - sector_len_per_device = pfl->sector_len; - } else { - blocks_per_device = pfl->nb_blocs; - sector_len_per_device = pfl->sector_len / num_devices; - } + blocks_per_device = pfl->nb_blocs; + sector_len_per_device = pfl->sector_len / num_devices; device_len = sector_len_per_device * blocks_per_device; /* Hardcoded CFI table */ @@ -765,7 +759,7 @@ static void pflash_cfi01_fill_cfi_table(PFlashCFI01 *pfl) pfl->cfi_table[0x2A] = 0x0B; } pfl->writeblock_size = 1 << pfl->cfi_table[0x2A]; - if (!pfl->old_multiple_chip_handling && num_devices > 1) { + if (num_devices > 1) { pfl->writeblock_size *= num_devices; } @@ -930,8 +924,6 @@ static const Property pflash_cfi01_properties[] = { DEFINE_PROP_UINT16("id2", PFlashCFI01, ident2, 0), DEFINE_PROP_UINT16("id3", PFlashCFI01, ident3, 0), DEFINE_PROP_STRING("name", PFlashCFI01, name), - DEFINE_PROP_BOOL("old-multiple-chip-handling", PFlashCFI01, - old_multiple_chip_handling, false), }; static void pflash_cfi01_class_init(ObjectClass *klass, const void *data) diff --git a/hw/block/xen-block.c b/hw/block/xen-block.c index 5dc4ba9d0765..474c12fe4ac8 100644 --- a/hw/block/xen-block.c +++ b/hw/block/xen-block.c @@ -883,32 +883,29 @@ static XenBlockDrive *xen_block_drive_create(const char *id, QDict *driver_layer; struct stat st; int rc; + char **v; - if (params) { - char **v = g_strsplit(params, ":", 2); + if (!params) { + error_setg(errp, "no params"); + return NULL; + } - if (v[1] == NULL) { - filename = g_strdup(v[0]); + v = g_strsplit(params, ":", 2); + if (v[1] == NULL) { + filename = g_strdup(v[0]); + driver = g_strdup("raw"); + } else { + if (strcmp(v[0], "aio") == 0) { driver = g_strdup("raw"); + } else if (strcmp(v[0], "vhd") == 0) { + driver = g_strdup("vpc"); } else { - if (strcmp(v[0], "aio") == 0) { - driver = g_strdup("raw"); - } else if (strcmp(v[0], "vhd") == 0) { - driver = g_strdup("vpc"); - } else { - driver = g_strdup(v[0]); - } - filename = g_strdup(v[1]); + driver = g_strdup(v[0]); } - - g_strfreev(v); - } else { - error_setg(errp, "no params"); - goto done; + filename = g_strdup(v[1]); } - assert(filename); - assert(driver); + g_strfreev(v); drive = g_new0(XenBlockDrive, 1); drive->id = g_strdup(id); diff --git a/hw/char/diva-gsp.c b/hw/char/diva-gsp.c index 280d0413c6e8..53fd0fe2a138 100644 --- a/hw/char/diva-gsp.c +++ b/hw/char/diva-gsp.c @@ -51,7 +51,6 @@ typedef struct PCIDivaSerialState { SerialState state[PCI_SERIAL_MAX_PORTS]; uint32_t level[PCI_SERIAL_MAX_PORTS]; qemu_irq *irqs; - bool disable; } PCIDivaSerialState; static void diva_pci_exit(PCIDevice *dev) @@ -62,8 +61,8 @@ static void diva_pci_exit(PCIDevice *dev) for (i = 0; i < pci->ports; i++) { s = pci->state + i; - qdev_unrealize(DEVICE(s)); memory_region_del_subregion(&pci->membar, &s->io); + qdev_unrealize(DEVICE(s)); g_free(pci->name[i]); } qemu_free_irqs(pci->irqs, pci->ports); @@ -159,20 +158,18 @@ static void diva_pci_realize(PCIDevice *dev, Error **errp) static const VMStateDescription vmstate_pci_diva = { .name = "pci-diva-serial", - .version_id = 1, - .minimum_version_id = 1, + .version_id = 2, + .minimum_version_id = 2, .fields = (const VMStateField[]) { VMSTATE_PCI_DEVICE(dev, PCIDivaSerialState), VMSTATE_STRUCT_ARRAY(state, PCIDivaSerialState, PCI_SERIAL_MAX_PORTS, 0, vmstate_serial, SerialState), VMSTATE_UINT32_ARRAY(level, PCIDivaSerialState, PCI_SERIAL_MAX_PORTS), - VMSTATE_BOOL(disable, PCIDivaSerialState), VMSTATE_END_OF_LIST() } }; static const Property diva_serial_properties[] = { - DEFINE_PROP_BOOL("disable", PCIDivaSerialState, disable, false), DEFINE_PROP_CHR("chardev1", PCIDivaSerialState, state[0].chr), DEFINE_PROP_CHR("chardev2", PCIDivaSerialState, state[1].chr), DEFINE_PROP_CHR("chardev3", PCIDivaSerialState, state[2].chr), diff --git a/hw/char/meson.build b/hw/char/meson.build index a9e1dc26c0fa..fc3d7ee506fc 100644 --- a/hw/char/meson.build +++ b/hw/char/meson.build @@ -17,6 +17,7 @@ system_ss.add(when: 'CONFIG_SERIAL_MM', if_true: files('serial-mm.c')) system_ss.add(when: 'CONFIG_SERIAL_PCI', if_true: files('serial-pci.c')) system_ss.add(when: 'CONFIG_SERIAL_PCI_MULTI', if_true: files('serial-pci-multi.c')) system_ss.add(when: 'CONFIG_SHAKTI_UART', if_true: files('shakti_uart.c')) +system_ss.add(when: 'CONFIG_VIRTIO', if_true: files('virtio-serial-bus.c')) system_ss.add(when: 'CONFIG_VIRTIO_SERIAL', if_true: files('virtio-console.c')) system_ss.add(when: 'CONFIG_XEN_BUS', if_true: files('xen_console.c')) system_ss.add(when: 'CONFIG_XILINX', if_true: files('xilinx_uartlite.c')) @@ -39,5 +40,4 @@ system_ss.add(when: 'CONFIG_HTIF', if_true: files('riscv_htif.c')) system_ss.add(when: 'CONFIG_GOLDFISH_TTY', if_true: files('goldfish_tty.c')) specific_ss.add(when: 'CONFIG_TERMINAL3270', if_true: files('terminal3270.c')) -specific_ss.add(when: 'CONFIG_VIRTIO', if_true: files('virtio-serial-bus.c')) specific_ss.add(when: 'CONFIG_PSERIES', if_true: files('spapr_vty.c')) diff --git a/hw/char/pl011.c b/hw/char/pl011.c index b2ca68e3e868..cb12c3e224fb 100644 --- a/hw/char/pl011.c +++ b/hw/char/pl011.c @@ -227,10 +227,25 @@ static void pl011_loopback_tx(PL011State *s, uint32_t value) static void pl011_write_txdata(PL011State *s, uint8_t data) { if (!(s->cr & CR_UARTEN)) { - qemu_log_mask(LOG_GUEST_ERROR, - "PL011 data written to disabled UART\n"); + /* + * Only log this message once, not every time the guest outputs: + * otherwise we would flood the logs with this message, making + * harder to debug guests. (Some very popular guests like Linux + * don't actively enable the UART.) + */ + if (!s->logged_disabled_uart) { + qemu_log_mask(LOG_GUEST_ERROR, + "PL011 data written to disabled UART\n"); + s->logged_disabled_uart = true; + } } if (!(s->cr & CR_TXE)) { + /* + * We don't bother with the only-log-once machinery for this check + * because TXE is enabled by default from PL011 reset, so there + * isn't likely to be existing in-the-wild guest code that trips + * over this one. + */ qemu_log_mask(LOG_GUEST_ERROR, "PL011 data written to disabled TX UART\n"); } @@ -457,6 +472,10 @@ static void pl011_write(void *opaque, hwaddr offset, break; case 12: /* UARTCR */ /* ??? Need to implement the enable bit. */ + if ((s->cr ^ value) & CR_UARTEN) { + /* Re-arm the log warning when the guest toggles UARTEN */ + s->logged_disabled_uart = false; + } s->cr = value; pl011_loopback_mdmctrl(s); break; @@ -665,6 +684,7 @@ static void pl011_reset(DeviceState *dev) s->ifl = 0x12; s->cr = 0x300; s->flags = 0; + s->logged_disabled_uart = false; pl011_reset_rx_fifo(s); pl011_reset_tx_fifo(s); } diff --git a/hw/char/serial-pci-multi.c b/hw/char/serial-pci-multi.c index 17796b93dd7a..7782452018d2 100644 --- a/hw/char/serial-pci-multi.c +++ b/hw/char/serial-pci-multi.c @@ -56,8 +56,8 @@ static void multi_serial_pci_exit(PCIDevice *dev) for (i = 0; i < pci->ports; i++) { s = pci->state + i; - qdev_unrealize(DEVICE(s)); memory_region_del_subregion(&pci->iobar, &s->io); + qdev_unrealize(DEVICE(s)); g_free(pci->name[i]); } } diff --git a/hw/char/serial.c b/hw/char/serial.c index adbd1d1d4ab2..0f3469a1e8fd 100644 --- a/hw/char/serial.c +++ b/hw/char/serial.c @@ -39,6 +39,11 @@ #include "hw/core/qdev-properties-system.h" #define UART_LCR_DLAB 0x80 /* Divisor latch access bit */ +#define UART_LCR_SB 0x40 /* Set break */ +#define UART_LCR_EPS 0x10 /* Even parity select */ +#define UART_LCR_PEN 0x08 /* Parity enable */ +#define UART_LCR_NSTB 0x04 /* Number of stop bits */ +#define UART_LCR_WLS 0x03 /* Word length select */ #define UART_IER_MSI 0x08 /* Enable Modem status interrupt */ #define UART_IER_RLSI 0x04 /* Enable receiver line status interrupt */ @@ -128,7 +133,7 @@ static void serial_update_irq(SerialState *s) tmp_iir = UART_IIR_CTI; } else if ((s->ier & UART_IER_RDI) && (s->lsr & UART_LSR_DR) && (!(s->fcr & UART_FCR_FE) || - s->recv_fifo.num >= s->recv_fifo_itl)) { + fifo8_num_used(&s->recv_fifo) >= s->recv_fifo_itl)) { tmp_iir = UART_IIR_RDI; } else if ((s->ier & UART_IER_THRI) && s->thr_ipending) { tmp_iir = UART_IIR_THRI; @@ -153,23 +158,23 @@ static void serial_update_parameters(SerialState *s) /* Start bit. */ frame_size = 1; - if (s->lcr & 0x08) { + if (s->lcr & UART_LCR_PEN) { /* Parity bit. */ frame_size++; - if (s->lcr & 0x10) + if (s->lcr & UART_LCR_EPS) parity = 'E'; else parity = 'O'; } else { parity = 'N'; } - if (s->lcr & 0x04) { + if (s->lcr & UART_LCR_NSTB) { stop_bits = 2; } else { stop_bits = 1; } - data_bits = (s->lcr & 0x03) + 5; + data_bits = (s->lcr & UART_LCR_WLS) + 5; frame_size += data_bits + stop_bits; /* Zero divisor should give about 3500 baud */ speed = (s->divider == 0) ? 3500 : (float) s->baudbase / s->divider; @@ -239,7 +244,7 @@ static void serial_xmit(SerialState *s) if (s->fcr & UART_FCR_FE) { assert(!fifo8_is_empty(&s->xmit_fifo)); s->tsr = fifo8_pop(&s->xmit_fifo); - if (!s->xmit_fifo.num) { + if (fifo8_is_empty(&s->xmit_fifo)) { s->lsr |= UART_LSR_THRE; } } else { @@ -281,9 +286,6 @@ static void serial_xmit(SerialState *s) s->lsr |= UART_LSR_TEMT; } -/* Setter for FCR. - is_load flag means, that value is set while loading VM state - and interrupt should not be invoked */ static void serial_write_fcr(SerialState *s, uint8_t val) { /* Set fcr - val only has the bits that are supposed to "stick" */ @@ -433,7 +435,7 @@ static void serial_ioport_write(void *opaque, hwaddr addr, uint64_t val, int break_enable; s->lcr = val; serial_update_parameters(s); - break_enable = (val >> 6) & 1; + break_enable = !!(val & UART_LCR_SB); if (break_enable != s->last_break_enable) { s->last_break_enable = break_enable; qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_SET_BREAK, @@ -481,7 +483,7 @@ static uint64_t serial_ioport_read(void *opaque, hwaddr addr, unsigned size) if(s->fcr & UART_FCR_FE) { ret = fifo8_is_empty(&s->recv_fifo) ? 0 : fifo8_pop(&s->recv_fifo); - if (s->recv_fifo.num == 0) { + if (fifo8_is_empty(&s->recv_fifo)) { s->lsr &= ~(UART_LSR_DR | UART_LSR_BI); } else { timer_mod(s->fifo_timeout_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->char_transmit_time * 4); @@ -555,7 +557,7 @@ static uint64_t serial_ioport_read(void *opaque, hwaddr addr, unsigned size) static int serial_can_receive(SerialState *s) { if(s->fcr & UART_FCR_FE) { - if (s->recv_fifo.num < UART_FIFO_LENGTH) { + if (!fifo8_is_full(&s->recv_fifo)) { /* * Advertise (fifo.itl - fifo.count) bytes when count < ITL, and 1 * if above. If UART_FIFO_LENGTH - fifo.count is advertised the @@ -563,8 +565,8 @@ static int serial_can_receive(SerialState *s) * the guest has a chance to respond, effectively overriding the ITL * that the guest has set. */ - return (s->recv_fifo.num <= s->recv_fifo_itl) ? - s->recv_fifo_itl - s->recv_fifo.num : 1; + return (fifo8_num_used(&s->recv_fifo) <= s->recv_fifo_itl) ? + s->recv_fifo_itl - fifo8_num_used(&s->recv_fifo) : 1; } else { return 0; } @@ -585,7 +587,7 @@ static void serial_receive_break(SerialState *s) /* There's data in recv_fifo and s->rbr has not been read for 4 char transmit times */ static void fifo_timeout_int (void *opaque) { SerialState *s = opaque; - if (s->recv_fifo.num) { + if (!fifo8_is_empty(&s->recv_fifo)) { s->timeout_ipending = 1; serial_update_irq(s); } @@ -679,7 +681,7 @@ static int serial_post_load(void *opaque, int version_id) } } - s->last_break_enable = (s->lcr >> 6) & 1; + s->last_break_enable = !!(s->lcr & UART_LCR_SB); /* Initialize fcr via setter to perform essential side-effects */ serial_write_fcr(s, s->fcr_vmstate); serial_update_parameters(s); @@ -715,7 +717,7 @@ static const VMStateDescription vmstate_serial_thr_ipending = { static bool serial_tsr_needed(void *opaque) { - SerialState *s = (SerialState *)opaque; + SerialState *s = opaque; return s->tsr_retry != 0; } @@ -734,7 +736,7 @@ static const VMStateDescription vmstate_serial_tsr = { static bool serial_recv_fifo_needed(void *opaque) { - SerialState *s = (SerialState *)opaque; + SerialState *s = opaque; return !fifo8_is_empty(&s->recv_fifo); } @@ -752,7 +754,7 @@ static const VMStateDescription vmstate_serial_recv_fifo = { static bool serial_xmit_fifo_needed(void *opaque) { - SerialState *s = (SerialState *)opaque; + SerialState *s = opaque; return !fifo8_is_empty(&s->xmit_fifo); } @@ -769,7 +771,7 @@ static const VMStateDescription vmstate_serial_xmit_fifo = { static bool serial_fifo_timeout_timer_needed(void *opaque) { - SerialState *s = (SerialState *)opaque; + SerialState *s = opaque; return timer_pending(s->fifo_timeout_timer); } @@ -786,7 +788,7 @@ static const VMStateDescription vmstate_serial_fifo_timeout_timer = { static bool serial_timeout_ipending_needed(void *opaque) { - SerialState *s = (SerialState *)opaque; + SerialState *s = opaque; return s->timeout_ipending != 0; } @@ -803,7 +805,7 @@ static const VMStateDescription vmstate_serial_timeout_ipending = { static bool serial_poll_needed(void *opaque) { - SerialState *s = (SerialState *)opaque; + SerialState *s = opaque; return s->poll_msl >= 0; } @@ -932,7 +934,6 @@ static void serial_realize(DeviceState *dev, Error **errp) serial_event, serial_be_change, s, NULL, true); fifo8_create(&s->recv_fifo, UART_FIFO_LENGTH); fifo8_create(&s->xmit_fifo, UART_FIFO_LENGTH); - serial_reset(s); } static void serial_unrealize(DeviceState *dev) diff --git a/hw/char/trace-events b/hw/char/trace-events index 9e74be2c14f5..a3fcc772877a 100644 --- a/hw/char/trace-events +++ b/hw/char/trace-events @@ -5,8 +5,8 @@ parallel_ioport_read(const char *desc, uint16_t addr, uint8_t value) "read [%s] parallel_ioport_write(const char *desc, uint16_t addr, uint8_t value) "write [%s] addr 0x%02x val 0x%02x" # serial.c -serial_read(uint16_t addr, uint8_t value) "read addr 0x%02x val 0x%02x" -serial_write(uint16_t addr, uint8_t value) "write addr 0x%02x val 0x%02x" +serial_read(uint64_t addr, uint8_t value) "[0x%02" PRIx64 "] -> 0x%02" PRIx8 +serial_write(uint64_t addr, uint64_t value) "[0x%02" PRIx64 "] <- 0x%02" PRIx64 serial_update_parameters(uint64_t baudrate, char parity, int data_bits, int stop_bits) "baudrate=%"PRIu64" parity='%c' data=%d stop=%d" # virtio-serial-bus.c diff --git a/hw/char/virtio-serial-bus.c b/hw/char/virtio-serial-bus.c index b7c57ea9678c..cd234dc6db1d 100644 --- a/hw/char/virtio-serial-bus.c +++ b/hw/char/virtio-serial-bus.c @@ -1039,10 +1039,6 @@ static void virtio_serial_device_realize(DeviceState *dev, Error **errp) return; } - if (!virtio_has_feature(vdev->host_features, - VIRTIO_CONSOLE_F_EMERG_WRITE)) { - config_size = offsetof(struct virtio_console_config, emerg_wr); - } virtio_init(vdev, VIRTIO_ID_CONSOLE, config_size); /* Spawn a new virtio-serial bus on which the ports will ride as devices */ diff --git a/hw/core/eif.c b/hw/core/eif.c index 513caec6b491..96f1d7657852 100644 --- a/hw/core/eif.c +++ b/hw/core/eif.c @@ -18,44 +18,6 @@ #include "hw/core/eif.h" -#define MAX_SECTIONS 32 - -/* members are ordered according to field order in .eif file */ -typedef struct EifHeader { - uint8_t magic[4]; /* must be .eif in ascii i.e., [46, 101, 105, 102] */ - uint16_t version; - uint16_t flags; - uint64_t default_memory; - uint64_t default_cpus; - uint16_t reserved; - uint16_t section_cnt; - uint64_t section_offsets[MAX_SECTIONS]; - uint64_t section_sizes[MAX_SECTIONS]; - uint32_t unused; - uint32_t eif_crc32; -} QEMU_PACKED EifHeader; - -/* members are ordered according to field order in .eif file */ -typedef struct EifSectionHeader { - /* - * 0 = invalid, 1 = kernel, 2 = cmdline, 3 = ramdisk, 4 = signature, - * 5 = metadata - */ - uint16_t section_type; - uint16_t flags; - uint64_t section_size; -} QEMU_PACKED EifSectionHeader; - -enum EifSectionTypes { - EIF_SECTION_INVALID = 0, - EIF_SECTION_KERNEL = 1, - EIF_SECTION_CMDLINE = 2, - EIF_SECTION_RAMDISK = 3, - EIF_SECTION_SIGNATURE = 4, - EIF_SECTION_METADATA = 5, - EIF_SECTION_MAX = 6, -}; - static const char *section_type_to_string(uint16_t type) { const char *str; diff --git a/hw/core/eif.h b/hw/core/eif.h index fed3cb551401..0c432dbc2dc5 100644 --- a/hw/core/eif.h +++ b/hw/core/eif.h @@ -11,6 +11,47 @@ #ifndef HW_CORE_EIF_H #define HW_CORE_EIF_H +#define MAX_SECTIONS 32 +#define EIF_HDR_ARCH_ARM64 0x1 + +/* members are ordered according to field order in .eif file */ +typedef struct EifHeader { + uint8_t magic[4]; /* must be .eif in ascii i.e., [46, 101, 105, 102] */ + uint16_t version; + uint16_t flags; + uint64_t default_memory; + uint64_t default_cpus; + uint16_t reserved; + uint16_t section_cnt; + uint64_t section_offsets[MAX_SECTIONS]; + uint64_t section_sizes[MAX_SECTIONS]; + uint32_t unused; + uint32_t eif_crc32; +} QEMU_PACKED EifHeader; + +/* members are ordered according to field order in .eif file */ +typedef struct EifSectionHeader { + /* + * 0 = invalid, 1 = kernel, 2 = cmdline, 3 = ramdisk, 4 = signature, + * 5 = metadata + */ + uint16_t section_type; + uint16_t flags; + uint64_t section_size; +} QEMU_PACKED EifSectionHeader; + +enum EifSectionTypes { + EIF_SECTION_INVALID = 0, + EIF_SECTION_KERNEL = 1, + EIF_SECTION_CMDLINE = 2, + EIF_SECTION_RAMDISK = 3, + EIF_SECTION_SIGNATURE = 4, + EIF_SECTION_METADATA = 5, + EIF_SECTION_MAX = 6, +}; + +#define EIF_MAGIC { '.', 'e', 'i', 'f' } + bool read_eif_file(const char *eif_path, const char *machine_initrd, char **kernel_path, char **initrd_path, char **kernel_cmdline, uint8_t *image_sha384, diff --git a/hw/core/machine.c b/hw/core/machine.c index d4ef620c178c..a14ad05b9a63 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c @@ -209,83 +209,6 @@ GlobalProperty hw_compat_4_1[] = { }; const size_t hw_compat_4_1_len = G_N_ELEMENTS(hw_compat_4_1); -GlobalProperty hw_compat_4_0[] = { - { "VGA", "edid", "false" }, - { "secondary-vga", "edid", "false" }, - { "bochs-display", "edid", "false" }, - { "virtio-vga", "edid", "false" }, - { "virtio-gpu-device", "edid", "false" }, - { "virtio-device", "use-started", "false" }, - { "virtio-balloon-device", "qemu-4-0-config-size", "true" }, - { "pl031", "migrate-tick-offset", "false" }, -}; -const size_t hw_compat_4_0_len = G_N_ELEMENTS(hw_compat_4_0); - -GlobalProperty hw_compat_3_1[] = { - { "pcie-root-port", "x-speed", "2_5" }, - { "pcie-root-port", "x-width", "1" }, - { "memory-backend-file", "x-use-canonical-path-for-ramblock-id", "true" }, - { "memory-backend-memfd", "x-use-canonical-path-for-ramblock-id", "true" }, - { "tpm-crb", "ppi", "false" }, - { "tpm-tis", "ppi", "false" }, - { "usb-kbd", "serial", "42" }, - { "usb-mouse", "serial", "42" }, - { "usb-tablet", "serial", "42" }, - { "virtio-blk-device", "discard", "false" }, - { "virtio-blk-device", "write-zeroes", "false" }, - { "virtio-balloon-device", "qemu-4-0-config-size", "false" }, - { "pcie-root-port-base", "disable-acs", "true" }, /* Added in 4.1 */ -}; -const size_t hw_compat_3_1_len = G_N_ELEMENTS(hw_compat_3_1); - -GlobalProperty hw_compat_3_0[] = {}; -const size_t hw_compat_3_0_len = G_N_ELEMENTS(hw_compat_3_0); - -GlobalProperty hw_compat_2_12[] = { - { "hda-audio", "use-timer", "false" }, - { "cirrus-vga", "global-vmstate", "true" }, - { "VGA", "global-vmstate", "true" }, - { "vmware-svga", "global-vmstate", "true" }, - { "qxl-vga", "global-vmstate", "true" }, -}; -const size_t hw_compat_2_12_len = G_N_ELEMENTS(hw_compat_2_12); - -GlobalProperty hw_compat_2_11[] = { - { "hpet", "hpet-offset-saved", "false" }, - { "virtio-blk-pci", "vectors", "2" }, - { "vhost-user-blk-pci", "vectors", "2" }, - { "e1000", "migrate_tso_props", "off" }, -}; -const size_t hw_compat_2_11_len = G_N_ELEMENTS(hw_compat_2_11); - -GlobalProperty hw_compat_2_10[] = { - { "virtio-mouse-device", "wheel-axis", "false" }, - { "virtio-tablet-device", "wheel-axis", "false" }, -}; -const size_t hw_compat_2_10_len = G_N_ELEMENTS(hw_compat_2_10); - -GlobalProperty hw_compat_2_9[] = { - { "pci-bridge", "shpc", "off" }, - { "intel-iommu", "pt", "off" }, - { "virtio-net-device", "x-mtu-bypass-backend", "off" }, - { "pcie-root-port", "x-migrate-msix", "false" }, -}; -const size_t hw_compat_2_9_len = G_N_ELEMENTS(hw_compat_2_9); - -GlobalProperty hw_compat_2_8[] = { - { "fw_cfg_mem", "x-file-slots", "0x10" }, - { "fw_cfg_io", "x-file-slots", "0x10" }, - { "pflash_cfi01", "old-multiple-chip-handling", "on" }, - { "pci-bridge", "shpc", "on" }, - { TYPE_PCI_DEVICE, "x-pcie-extcap-init", "off" }, - { "virtio-pci", "x-pcie-deverr-init", "off" }, - { "virtio-pci", "x-pcie-lnkctl-init", "off" }, - { "virtio-pci", "x-pcie-pm-init", "off" }, - { "cirrus-vga", "vgamem_mb", "8" }, - { "isa-cirrus-vga", "vgamem_mb", "8" }, -}; -const size_t hw_compat_2_8_len = G_N_ELEMENTS(hw_compat_2_8); - MachineState *current_machine; static char *machine_get_kernel(Object *obj, Error **errp) @@ -435,6 +358,21 @@ static void machine_set_dump_guest_core(Object *obj, bool value, Error **errp) ms->dump_guest_core = value; } +static bool machine_get_new_accel_vmfd_on_reset(Object *obj, Error **errp) +{ + MachineState *ms = MACHINE(obj); + + return ms->new_accel_vmfd_on_reset; +} + +static void machine_set_new_accel_vmfd_on_reset(Object *obj, + bool value, Error **errp) +{ + MachineState *ms = MACHINE(obj); + + ms->new_accel_vmfd_on_reset = value; +} + static bool machine_get_mem_merge(Object *obj, Error **errp) { MachineState *ms = MACHINE(obj); @@ -1183,6 +1121,13 @@ static void machine_class_init(ObjectClass *oc, const void *data) object_class_property_set_description(oc, "dump-guest-core", "Include guest memory in a core dump"); + object_class_property_add_bool(oc, "x-change-vmfd-on-reset", + machine_get_new_accel_vmfd_on_reset, + machine_set_new_accel_vmfd_on_reset); + object_class_property_set_description(oc, "x-change-vmfd-on-reset", + "Set on/off to enable/disable generating new accelerator guest handle " + "on guest reset. Default: off (used only for testing/debugging)."); + object_class_property_add_bool(oc, "mem-merge", machine_get_mem_merge, machine_set_mem_merge); object_class_property_set_description(oc, "mem-merge", diff --git a/hw/core/qdev.c b/hw/core/qdev.c index fc3425a8fe1a..e48616b2c6f2 100644 --- a/hw/core/qdev.c +++ b/hw/core/qdev.c @@ -420,7 +420,7 @@ const char *qdev_get_printable_name(DeviceState *vdev) * names. */ if (vdev->id) { - return vdev->id; + return g_strdup(vdev->id); } /* * Fall back to the canonical QOM device path (eg. ID for PCI @@ -437,7 +437,7 @@ const char *qdev_get_printable_name(DeviceState *vdev) * Final fallback: if all else fails, return a placeholder string. * This ensures the error message always contains a valid string. */ - return ""; + return g_strdup(""); } void qdev_add_unplug_blocker(DeviceState *dev, Error *reason) diff --git a/hw/cpu/a15mpcore.c b/hw/cpu/a15mpcore.c index 28cc4b3a4bd8..28cda55ccf81 100644 --- a/hw/cpu/a15mpcore.c +++ b/hw/cpu/a15mpcore.c @@ -22,10 +22,10 @@ #include "qapi/error.h" #include "qemu/module.h" #include "hw/cpu/a15mpcore.h" +#include "hw/core/cpu.h" #include "hw/core/irq.h" #include "hw/core/qdev-properties.h" #include "system/kvm.h" -#include "kvm_arm.h" #include "target/arm/gtimer.h" static void a15mp_priv_set_irq(void *opaque, int irq, int level) diff --git a/hw/cpu/meson.build b/hw/cpu/meson.build index 9d36bf8ae2c1..9c1535ca3251 100644 --- a/hw/cpu/meson.build +++ b/hw/cpu/meson.build @@ -4,4 +4,4 @@ system_ss.add(when: 'CONFIG_CPU_CLUSTER', if_true: files('cluster.c')) system_ss.add(when: 'CONFIG_ARM11MPCORE', if_true: files('arm11mpcore.c')) system_ss.add(when: 'CONFIG_REALVIEW', if_true: files('realview_mpcore.c')) system_ss.add(when: 'CONFIG_A9MPCORE', if_true: files('a9mpcore.c')) -specific_ss.add(when: 'CONFIG_A15MPCORE', if_true: files('a15mpcore.c')) +system_ss.add(when: 'CONFIG_A15MPCORE', if_true: files('a15mpcore.c')) diff --git a/hw/cxl/cxl-component-utils.c b/hw/cxl/cxl-component-utils.c index 473895948b3d..07aabe331c44 100644 --- a/hw/cxl/cxl-component-utils.c +++ b/hw/cxl/cxl-component-utils.c @@ -68,13 +68,40 @@ static uint64_t cxl_cache_mem_read_reg(void *opaque, hwaddr offset, ComponentRegisters *cregs = &cxl_cstate->crb; switch (size) { - case 4: - if (cregs->special_ops && cregs->special_ops->read) { - return cregs->special_ops->read(cxl_cstate, offset, 4); - } else { - QEMU_BUILD_BUG_ON(sizeof(*cregs->cache_mem_registers) != 4); - return cregs->cache_mem_registers[offset / 4]; + case 4: { + QEMU_BUILD_BUG_ON(sizeof(*cregs->cache_mem_registers) != 4); + + if (offset == A_CXL_BI_RT_STATUS || + offset == A_CXL_BI_DECODER_STATUS) { + int type; + uint64_t started; + + type = (offset == A_CXL_BI_RT_STATUS) ? + CXL_BISTATE_RT : CXL_BISTATE_DECODER; + started = cxl_cstate->bi_state[type].last_commit; + + if (started) { + uint32_t *cache_mem = cregs->cache_mem_registers; + uint32_t val = cache_mem[offset / 4]; + uint64_t now; + int set; + + now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL); + /* arbitrary 100 ms to do the commit */ + set = !!(now >= started + 100); + + if (offset == A_CXL_BI_RT_STATUS) { + val = FIELD_DP32(val, CXL_BI_RT_STATUS, COMMITTED, set); + } else { + val = FIELD_DP32(val, CXL_BI_DECODER_STATUS, COMMITTED, + set); + } + stl_le_p((uint8_t *)cache_mem + offset, val); + } } + + return cregs->cache_mem_registers[offset / 4]; + } case 8: qemu_log_mask(LOG_UNIMP, "CXL 8 byte cache mem registers not implemented\n"); @@ -118,6 +145,47 @@ static void dumb_hdm_handler(CXLComponentState *cxl_cstate, hwaddr offset, stl_le_p((uint8_t *)cache_mem + offset, value); } +static void bi_handler(CXLComponentState *cxl_cstate, hwaddr offset, + uint32_t value) +{ + ComponentRegisters *cregs = &cxl_cstate->crb; + uint32_t sts, *cache_mem = cregs->cache_mem_registers; + bool to_commit = false; + int type = 0; /* Unused value - work around for compiler warning */ + + switch (offset) { + case A_CXL_BI_RT_CTRL: + to_commit = FIELD_EX32(value, CXL_BI_RT_CTRL, COMMIT); + if (to_commit) { + sts = cxl_cache_mem_read_reg(cxl_cstate, + R_CXL_BI_RT_STATUS, 4); + sts = FIELD_DP32(sts, CXL_BI_RT_STATUS, COMMITTED, 0); + stl_le_p((uint8_t *)cache_mem + R_CXL_BI_RT_STATUS, sts); + type = CXL_BISTATE_RT; + } + break; + case A_CXL_BI_DECODER_CTRL: + to_commit = FIELD_EX32(value, CXL_BI_DECODER_CTRL, COMMIT); + if (to_commit) { + sts = cxl_cache_mem_read_reg(cxl_cstate, + R_CXL_BI_DECODER_STATUS, 4); + sts = FIELD_DP32(sts, CXL_BI_DECODER_STATUS, COMMITTED, 0); + stl_le_p((uint8_t *)cache_mem + R_CXL_BI_DECODER_STATUS, sts); + type = CXL_BISTATE_DECODER; + } + break; + default: + break; + } + + if (to_commit) { + cxl_cstate->bi_state[type].last_commit = + qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL); + } + + stl_le_p((uint8_t *)cache_mem + offset, value); +} + static void cxl_cache_mem_write_reg(void *opaque, hwaddr offset, uint64_t value, unsigned size) { @@ -141,6 +209,9 @@ static void cxl_cache_mem_write_reg(void *opaque, hwaddr offset, uint64_t value, if (offset >= A_CXL_HDM_DECODER_CAPABILITY && offset <= A_CXL_HDM_DECODER3_TARGET_LIST_HI) { dumb_hdm_handler(cxl_cstate, offset, value); + } else if (offset == A_CXL_BI_RT_CTRL || + offset == A_CXL_BI_DECODER_CTRL) { + bi_handler(cxl_cstate, offset, value); } else { cregs->cache_mem_registers[offset / 4] = value; } @@ -230,7 +301,7 @@ static void ras_init_common(uint32_t *reg_state, uint32_t *write_msk) } static void hdm_init_common(uint32_t *reg_state, uint32_t *write_msk, - enum reg_type type) + enum reg_type type, bool bi) { int decoder_count = CXL_HDM_DECODER_COUNT; int hdm_inc = R_CXL_HDM_DECODER1_BASE_LO - R_CXL_HDM_DECODER0_BASE_LO; @@ -255,7 +326,9 @@ static void hdm_init_common(uint32_t *reg_state, uint32_t *write_msk, UIO_DECODER_COUNT, 0); ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, MEMDATA_NXM_CAP, 0); ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, - SUPPORTED_COHERENCY_MODEL, 0); /* Unknown */ + SUPPORTED_COHERENCY_MODEL, + /* host+dev or Unknown */ + type == CXL2_TYPE3_DEVICE && bi ? 3 : 0); ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_GLOBAL_CONTROL, HDM_DECODER_ENABLE, 0); write_msk[R_CXL_HDM_DECODER_GLOBAL_CONTROL] = 0x3; @@ -278,38 +351,46 @@ static void hdm_init_common(uint32_t *reg_state, uint32_t *write_msk, } } +static void bi_rt_init_common(uint32_t *reg_state, uint32_t *write_msk) +{ + /* switch usp must commit the new BI-ID, timeout of 2secs */ + ARRAY_FIELD_DP32(reg_state, CXL_BI_RT_CAPABILITY, EXPLICIT_COMMIT, 1); + + ARRAY_FIELD_DP32(reg_state, CXL_BI_RT_CTRL, COMMIT, 0); + write_msk[R_CXL_BI_RT_CTRL] = 0x1; + + ARRAY_FIELD_DP32(reg_state, CXL_BI_RT_STATUS, COMMITTED, 0); + ARRAY_FIELD_DP32(reg_state, CXL_BI_RT_STATUS, ERR_NOT_COMMITTED, 0); + ARRAY_FIELD_DP32(reg_state, CXL_BI_RT_STATUS, COMMIT_TMO_SCALE, 0x6); + ARRAY_FIELD_DP32(reg_state, CXL_BI_RT_STATUS, COMMIT_TMO_BASE, 0x2); +} + +static void bi_decoder_init_common(uint32_t *reg_state, uint32_t *write_msk, + enum reg_type type) +{ + ARRAY_FIELD_DP32(reg_state, CXL_BI_DECODER_CAPABILITY, HDM_D, 0); + /* switch dsp must commit the new BI-ID, timeout of 2secs */ + ARRAY_FIELD_DP32(reg_state, CXL_BI_DECODER_CAPABILITY, EXPLICIT_COMMIT, + (type != CXL2_ROOT_PORT && type != CXL2_TYPE3_DEVICE)); + + ARRAY_FIELD_DP32(reg_state, CXL_BI_DECODER_CTRL, BI_FW, 0); + ARRAY_FIELD_DP32(reg_state, CXL_BI_DECODER_CTRL, BI_ENABLE, 0); + ARRAY_FIELD_DP32(reg_state, CXL_BI_DECODER_CTRL, COMMIT, 0); + write_msk[R_CXL_BI_DECODER_CTRL] = 0x7; + + ARRAY_FIELD_DP32(reg_state, CXL_BI_DECODER_STATUS, COMMITTED, 0); + ARRAY_FIELD_DP32(reg_state, CXL_BI_DECODER_STATUS, ERR_NOT_COMMITTED, 0); + ARRAY_FIELD_DP32(reg_state, CXL_BI_DECODER_STATUS, COMMIT_TMO_SCALE, 0x6); + ARRAY_FIELD_DP32(reg_state, CXL_BI_DECODER_STATUS, COMMIT_TMO_BASE, 0x2); +} + void cxl_component_register_init_common(uint32_t *reg_state, uint32_t *write_msk, - enum reg_type type) + enum reg_type type, + bool bi) { int caps = 0; - /* - * In CXL 2.0 the capabilities required for each CXL component are such - * that, with the ordering chosen here, a single number can be used to - * define which capabilities should be provided. - */ - switch (type) { - case CXL2_DOWNSTREAM_PORT: - case CXL2_DEVICE: - /* RAS, Link */ - caps = 2; - break; - case CXL2_UPSTREAM_PORT: - case CXL2_TYPE3_DEVICE: - case CXL2_LOGICAL_DEVICE: - /* + HDM */ - caps = 3; - break; - case CXL2_ROOT_PORT: - case CXL2_RC: - /* + Extended Security, + Snoop */ - caps = 5; - break; - default: - abort(); - } - memset(reg_state, 0, CXL2_COMPONENT_CM_REGION_SIZE); /* CXL Capability Header Register */ @@ -317,11 +398,12 @@ void cxl_component_register_init_common(uint32_t *reg_state, ARRAY_FIELD_DP32(reg_state, CXL_CAPABILITY_HEADER, VERSION, CXL_CAPABILITY_VERSION); ARRAY_FIELD_DP32(reg_state, CXL_CAPABILITY_HEADER, CACHE_MEM_VERSION, 1); - ARRAY_FIELD_DP32(reg_state, CXL_CAPABILITY_HEADER, ARRAY_SIZE, caps); #define init_cap_reg(reg, id, version) \ do { \ - int which = R_CXL_##reg##_CAPABILITY_HEADER; \ + int which = CXL_##reg##_CAP_HDR_IDX; \ + if (CXL_##reg##_CAP_HDR_IDX > caps) \ + caps = CXL_##reg##_CAP_HDR_IDX; \ reg_state[which] = FIELD_DP32(reg_state[which], \ CXL_##reg##_CAPABILITY_HEADER, ID, id); \ reg_state[which] = \ @@ -332,37 +414,53 @@ void cxl_component_register_init_common(uint32_t *reg_state, CXL_##reg##_REGISTERS_OFFSET); \ } while (0) + /* CXL r3.2 8.2.4 Table 8-22 */ switch (type) { - case CXL2_DEVICE: - case CXL2_TYPE3_DEVICE: - case CXL2_LOGICAL_DEVICE: case CXL2_ROOT_PORT: + case CXL2_RC: + /* + Extended Security, + Snoop */ + init_cap_reg(EXTSEC, 6, 1); + init_cap_reg(SNOOP, 8, 1); + /* fallthrough */ case CXL2_UPSTREAM_PORT: + case CXL2_TYPE3_DEVICE: + case CXL2_LOGICAL_DEVICE: + /* + HDM */ + init_cap_reg(HDM, 5, 1); + hdm_init_common(reg_state, write_msk, type, bi); + /* fallthrough */ case CXL2_DOWNSTREAM_PORT: - init_cap_reg(RAS, 2, CXL_RAS_CAPABILITY_VERSION); - ras_init_common(reg_state, write_msk); + case CXL2_DEVICE: + /* RAS, Link */ + if (type != CXL2_RC) { + init_cap_reg(RAS, 2, 2); + ras_init_common(reg_state, write_msk); + } + init_cap_reg(LINK, 4, 2); break; default: - break; - } - - init_cap_reg(LINK, 4, CXL_LINK_CAPABILITY_VERSION); - - if (caps < 3) { - return; + abort(); } - if (type != CXL2_ROOT_PORT) { - init_cap_reg(HDM, 5, CXL_HDM_CAPABILITY_VERSION); - hdm_init_common(reg_state, write_msk, type); - } - if (caps < 5) { - return; + /* back invalidate */ + if (bi) { + switch (type) { + case CXL2_UPSTREAM_PORT: + init_cap_reg(BI_RT, 11, CXL_BI_RT_CAP_VERSION); + bi_rt_init_common(reg_state, write_msk); + break; + case CXL2_ROOT_PORT: + case CXL2_DOWNSTREAM_PORT: + case CXL2_TYPE3_DEVICE: + init_cap_reg(BI_DECODER, 12, CXL_BI_DECODER_CAP_VERSION); + bi_decoder_init_common(reg_state, write_msk, type); + break; + default: + break; + } } - init_cap_reg(EXTSEC, 6, CXL_EXTSEC_CAP_VERSION); - init_cap_reg(SNOOP, 8, CXL_SNOOP_CAP_VERSION); - + ARRAY_FIELD_DP32(reg_state, CXL_CAPABILITY_HEADER, ARRAY_SIZE, caps); #undef init_cap_reg } diff --git a/hw/cxl/cxl-events.c b/hw/cxl/cxl-events.c index 7583dd9162f6..5356dfb5b300 100644 --- a/hw/cxl/cxl-events.c +++ b/hw/cxl/cxl-events.c @@ -271,7 +271,8 @@ void cxl_create_dc_event_records_for_extents(CXLType3Dev *ct3d, &dynamic_capacity_uuid, (1 << CXL_EVENT_TYPE_INFO), sizeof(event_rec), - cxl_device_get_timestamp(&ct3d->cxl_dstate)); + cxl_device_get_timestamp(&ct3d->cxl_dstate), + 0, 0, 0, 0, 0, 0, 0, 0); event_rec.type = type; event_rec.validity_flags = 1; event_rec.host_id = 0; diff --git a/hw/cxl/cxl-mailbox-utils.c b/hw/cxl/cxl-mailbox-utils.c index 9b99d44a8030..c83b5f90d4d3 100644 --- a/hw/cxl/cxl-mailbox-utils.c +++ b/hw/cxl/cxl-mailbox-utils.c @@ -15,7 +15,9 @@ #include "hw/cxl/cxl.h" #include "hw/cxl/cxl_events.h" #include "hw/cxl/cxl_mailbox.h" +#include "hw/cxl/cxl_port.h" #include "hw/pci/pci.h" +#include "hw/pci-bridge/cxl_downstream_port.h" #include "hw/pci-bridge/cxl_upstream_port.h" #include "qemu/cutils.h" #include "qemu/host-utils.h" @@ -86,6 +88,8 @@ enum { #define GET_SUPPORTED 0x0 #define GET_FEATURE 0x1 #define SET_FEATURE 0x2 + MAINTENANCE = 0x06, + #define PERFORM 0x0 IDENTIFY = 0x40, #define MEMORY_DEVICE 0x0 CCLS = 0x41, @@ -116,6 +120,7 @@ enum { PHYSICAL_SWITCH = 0x51, #define IDENTIFY_SWITCH_DEVICE 0x0 #define GET_PHYSICAL_PORT_STATE 0x1 + #define PHYSICAL_PORT_CONTROL 0x2 TUNNEL = 0x53, #define MANAGEMENT_COMMAND 0x0 FMAPI_DCD_MGMT = 0x56, @@ -563,16 +568,16 @@ static CXLRetCode cmd_get_physical_port_state(const struct cxl_cmd *cmd, } QEMU_PACKED *in; /* - * CXL r3.1 Table 7-19: Get Physical Port State Port Information Block + * CXL r3.2 Table 7-19: Get Physical Port State Port Information Block * Format */ struct cxl_fmapi_port_state_info_block { uint8_t port_id; uint8_t config_state; - uint8_t connected_device_cxl_version; + uint8_t connected_device_mode; uint8_t rsv1; uint8_t connected_device_type; - uint8_t port_cxl_version_bitmask; + uint8_t supported_cxl_mode_bitmask; uint8_t max_link_width; uint8_t negotiated_link_width; uint8_t supported_link_speeds_vector; @@ -613,6 +618,7 @@ static CXLRetCode cmd_get_physical_port_state(const struct cxl_cmd *cmd, struct cxl_fmapi_port_state_info_block *port; /* First try to match on downstream port */ PCIDevice *port_dev; + CXLPhyPortPerst *perst; uint16_t lnkcap, lnkcap2, lnksta; port = &out->ports[i]; @@ -621,21 +627,54 @@ static CXLRetCode cmd_get_physical_port_state(const struct cxl_cmd *cmd, if (port_dev) { /* DSP */ PCIDevice *ds_dev = pci_bridge_get_sec_bus(PCI_BRIDGE(port_dev)) ->devices[0]; - port->config_state = 3; + port->config_state = CXL_PORT_CONFIG_STATE_DSP; if (ds_dev) { if (object_dynamic_cast(OBJECT(ds_dev), TYPE_CXL_TYPE3)) { - port->connected_device_type = 5; /* Assume MLD for now */ + uint16_t lnksta2; + + if (!port_dev->exp.exp_cap) { + return CXL_MBOX_INTERNAL_ERROR; + } + + lnksta2 = port_dev->config_read(port_dev, + port_dev->exp.exp_cap + PCI_EXP_LNKSTA2, + sizeof(lnksta2)); + + /* Assume MLD for now */ + port->connected_device_type = + CXL_PORT_CONNECTED_DEV_TYPE_3_MLD; + if (lnksta2 & PCI_EXP_LNKSTA2_FLIT) { + port->connected_device_mode = + CXL_PORT_CONNECTED_DEV_MODE_256B; + } else { + port->connected_device_mode = + CXL_PORT_CONNECTED_DEV_MODE_68B_VH; + } } else { - port->connected_device_type = 1; + port->connected_device_type = + CXL_PORT_CONNECTED_DEV_TYPE_PCIE; + port->connected_device_mode = + CXL_PORT_CONNECTED_DEV_MODE_NOT_CXL_OR_DISCONN; + } } else { - port->connected_device_type = 0; + port->connected_device_type = CXL_PORT_CONNECTED_DEV_TYPE_NONE; + port->connected_device_mode = + CXL_PORT_CONNECTED_DEV_MODE_NOT_CXL_OR_DISCONN; } + /* DSP currently always support modes implemented in QEMU */ + port->supported_cxl_mode_bitmask = CXL_PORT_SUPPORTS_68B_VH | + CXL_PORT_SUPPORTS_256B; port->supported_ld_count = 3; + perst = cxl_dsp_get_perst(CXL_DSP(port_dev)); } else if (usp->port == in->ports[i]) { /* USP */ port_dev = PCI_DEVICE(usp); - port->config_state = 4; - port->connected_device_type = 0; + port->config_state = CXL_PORT_CONFIG_STATE_USP; + port->connected_device_type = 0; /* Reserved for USP */ + port->connected_device_mode = 0; /* Reserved for USP */ + port->supported_cxl_mode_bitmask = CXL_PORT_SUPPORTS_68B_VH | + (CXL_USP(usp)->flitmode ? CXL_PORT_SUPPORTS_256B : 0); + perst = &CXL_USP(usp)->perst; } else { return CXL_MBOX_INVALID_INPUT; } @@ -664,9 +703,7 @@ static CXLRetCode cmd_get_physical_port_state(const struct cxl_cmd *cmd, /* TODO: Track down if we can get the rest of the info */ port->ltssm_state = 0x7; port->first_lane_num = 0; - port->link_state = 0; - port->port_cxl_version_bitmask = 0x2; - port->connected_device_cxl_version = 0x2; + port->link_state = perst ? CXL_PORT_LINK_STATE_FLAG_PERST_ASSERTED : 0; } pl_size = sizeof(*out) + sizeof(*out->ports) * in->num_ports; @@ -675,6 +712,115 @@ static CXLRetCode cmd_get_physical_port_state(const struct cxl_cmd *cmd, return CXL_MBOX_SUCCESS; } +static void *bg_assertcb(void *opaque) +{ + CXLPhyPortPerst *perst = opaque; + + /* holding reset phase for 100ms */ + while (perst->asrt_time--) { + usleep(1000); + } + perst->issued_assert_perst = true; + return NULL; +} + +static CXLRetCode cxl_deassert_perst(Object *obj, CXLPhyPortPerst *perst) +{ + if (!perst->issued_assert_perst) { + return CXL_MBOX_INTERNAL_ERROR; + } + + QEMU_LOCK_GUARD(&perst->lock); + resettable_release_reset(obj, RESET_TYPE_COLD); + perst->issued_assert_perst = false; + perst->asrt_time = ASSERT_WAIT_TIME_MS; + + return CXL_MBOX_SUCCESS; +} + +static CXLRetCode cxl_assert_perst(Object *obj, CXLPhyPortPerst *perst) +{ + if (cxl_perst_asserted(perst)) { + return CXL_MBOX_INTERNAL_ERROR; + } + + QEMU_LOCK_GUARD(&perst->lock); + resettable_assert_reset(obj, RESET_TYPE_COLD); + qemu_thread_create(&perst->asrt_thread, "assert_thread", bg_assertcb, + perst, QEMU_THREAD_DETACHED); + + return CXL_MBOX_SUCCESS; +} + +static CXLDownstreamPort *cxl_find_dsp_on_bus(PCIBus *bus, uint8_t pn) +{ + + PCIDevice *port_dev = pcie_find_port_by_pn(bus, pn); + + if (object_dynamic_cast(OBJECT(port_dev), TYPE_CXL_DSP)) { + return CXL_DSP(port_dev); + } + + return NULL; +} + +/* CXL r3.2 Section 7.6.7.1.3: Get Physical Port Control (Opcode 5102h) */ +static CXLRetCode cmd_physical_port_control(const struct cxl_cmd *cmd, + uint8_t *payload_in, + size_t len_in, + uint8_t *payload_out, + size_t *len_out, + CXLCCI *cci) +{ + CXLUpstreamPort *pp = CXL_USP(cci->d); + CXLPhyPortPerst *perst; + PCIDevice *dev; + + struct cxl_fmapi_get_physical_port_control_req_pl { + uint8_t ppb_id; + uint8_t ports_op; + } QEMU_PACKED *in = (void *)payload_in; + + if (len_in < sizeof(*in)) { + return CXL_MBOX_INVALID_PAYLOAD_LENGTH; + } + + if (PCIE_PORT(pp)->port == in->ppb_id) { + dev = PCI_DEVICE(pp); + perst = &pp->perst; + } else { + CXLDownstreamPort *dsp = + cxl_find_dsp_on_bus(&PCI_BRIDGE(pp)->sec_bus, in->ppb_id); + + if (!dsp) { + return CXL_MBOX_INVALID_INPUT; + } + dev = PCI_DEVICE(dsp); + perst = cxl_dsp_get_perst(dsp); + } + + switch (in->ports_op) { + case 0: + return cxl_assert_perst(OBJECT(&dev->qdev), perst); + case 1: + return cxl_deassert_perst(OBJECT(&dev->qdev), perst); + case 2: { + if (!perst) { + return CXL_MBOX_INVALID_INPUT; + } + + if (perst->issued_assert_perst || + perst->asrt_time < ASSERT_WAIT_TIME_MS) { + return CXL_MBOX_INTERNAL_ERROR; + } + device_cold_reset(&dev->qdev); + return CXL_MBOX_SUCCESS; + } + default: + return CXL_MBOX_INVALID_INPUT; + } +} + /* CXL r3.1 Section 8.2.9.1.2: Background Operation Status (Opcode 0002h) */ static CXLRetCode cmd_infostat_bg_op_sts(const struct cxl_cmd *cmd, uint8_t *payload_in, @@ -1111,8 +1257,8 @@ typedef struct CXLSupportedFeatureEntry { #define CXL_FEAT_ENTRY_ATTR_FLAG_CHANGABLE BIT(0) #define CXL_FEAT_ENTRY_ATTR_FLAG_DEEPEST_RESET_PERSISTENCE_MASK GENMASK(3, 1) #define CXL_FEAT_ENTRY_ATTR_FLAG_PERSIST_ACROSS_FIRMWARE_UPDATE BIT(4) -#define CXL_FEAT_ENTRY_ATTR_FLAG_SUPPORT_DEFAULT_SELECTION BIT(5) -#define CXL_FEAT_ENTRY_ATTR_FLAG_SUPPORT_SAVED_SELECTION BIT(6) +#define CXL_FEAT_ENTRY_ATTR_FLAG_SUPPORT_DEFAULT_SEL BIT(5) +#define CXL_FEAT_ENTRY_ATTR_FLAG_SUPPORT_SAVED_SEL BIT(6) /* Supported Feature Entry : set feature effects */ #define CXL_FEAT_ENTRY_SFE_CONFIG_CHANGE_COLD_RESET BIT(0) @@ -1131,6 +1277,12 @@ typedef struct CXLSupportedFeatureEntry { enum CXL_SUPPORTED_FEATURES_LIST { CXL_FEATURE_PATROL_SCRUB = 0, CXL_FEATURE_ECS, + CXL_FEATURE_SPPR, + CXL_FEATURE_HPPR, + CXL_FEATURE_CACHELINE_SPARING, + CXL_FEATURE_ROW_SPARING, + CXL_FEATURE_BANK_SPARING, + CXL_FEATURE_RANK_SPARING, CXL_FEATURE_MAX }; @@ -1172,6 +1324,28 @@ enum CXL_SET_FEATURE_FLAG_DATA_TRANSFER { }; #define CXL_SET_FEAT_DATA_SAVED_ACROSS_RESET BIT(3) +/* CXL r3.2 section 8.2.10.7.2.1: sPPR Feature Discovery and Configuration */ +static const QemuUUID soft_ppr_uuid = { + .data = UUID(0x892ba475, 0xfad8, 0x474e, 0x9d, 0x3e, + 0x69, 0x2c, 0x91, 0x75, 0x68, 0xbb) +}; + +typedef struct CXLMemSoftPPRSetFeature { + CXLSetFeatureInHeader hdr; + CXLMemSoftPPRWriteAttrs feat_data; +} QEMU_PACKED QEMU_ALIGNED(16) CXLMemSoftPPRSetFeature; + +/* CXL r3.2 section 8.2.10.7.2.2: hPPR Feature Discovery and Configuration */ +static const QemuUUID hard_ppr_uuid = { + .data = UUID(0x80ea4521, 0x786f, 0x4127, 0xaf, 0xb1, + 0xec, 0x74, 0x59, 0xfb, 0x0e, 0x24) +}; + +typedef struct CXLMemHardPPRSetFeature { + CXLSetFeatureInHeader hdr; + CXLMemHardPPRWriteAttrs feat_data; +} QEMU_PACKED QEMU_ALIGNED(16) CXLMemHardPPRSetFeature; + /* CXL r3.1 section 8.2.9.9.11.1: Device Patrol Scrub Control Feature */ static const QemuUUID patrol_scrub_uuid = { .data = UUID(0x96dad7d6, 0xfde8, 0x482b, 0xa7, 0x33, @@ -1197,6 +1371,35 @@ typedef struct CXLMemECSSetFeature { CXLMemECSWriteAttrs feat_data[]; } QEMU_PACKED QEMU_ALIGNED(16) CXLMemECSSetFeature; +/* + * CXL r3.2 section 8.2.10.7.2.3: + * Memory Sparing Features Discovery and Configuration + */ +static const QemuUUID cacheline_sparing_uuid = { + .data = UUID(0x96C33386, 0x91dd, 0x44c7, 0x9e, 0xcb, + 0xfd, 0xaf, 0x65, 0x03, 0xba, 0xc4) +}; + +static const QemuUUID row_sparing_uuid = { + .data = UUID(0x450ebf67, 0xb135, 0x4f97, 0xa4, 0x98, + 0xc2, 0xd5, 0x7f, 0x27, 0x9b, 0xed) +}; + +static const QemuUUID bank_sparing_uuid = { + .data = UUID(0x78b79636, 0x90ac, 0x4b64, 0xa4, 0xef, + 0xfa, 0xac, 0x5d, 0x18, 0xa8, 0x63) +}; + +static const QemuUUID rank_sparing_uuid = { + .data = UUID(0x34dbaff5, 0x0552, 0x4281, 0x8f, 0x76, + 0xda, 0x0b, 0x5e, 0x7a, 0x76, 0xa7) +}; + +typedef struct CXLMemSparingSetFeature { + CXLSetFeatureInHeader hdr; + CXLMemSparingWriteAttrs feat_data; +} QEMU_PACKED QEMU_ALIGNED(16) CXLMemSparingSetFeature; + /* CXL r3.1 section 8.2.9.6.1: Get Supported Features (Opcode 0500h) */ static CXLRetCode cmd_features_get_supported(const struct cxl_cmd *cmd, uint8_t *payload_in, @@ -1235,6 +1438,38 @@ static CXLRetCode cmd_features_get_supported(const struct cxl_cmd *cmd, for (entry = 0, index = get_feats_in->start_index; entry < req_entries; index++) { switch (index) { + case CXL_FEATURE_SPPR: + /* Fill supported feature entry for soft-PPR */ + get_feats_out->feat_entries[entry++] = + (struct CXLSupportedFeatureEntry) { + .uuid = soft_ppr_uuid, + .feat_index = index, + .get_feat_size = sizeof(CXLMemSoftPPRReadAttrs), + .set_feat_size = sizeof(CXLMemSoftPPRWriteAttrs), + .attr_flags = CXL_FEAT_ENTRY_ATTR_FLAG_CHANGABLE | + CXL_FEAT_ENTRY_ATTR_FLAG_SUPPORT_DEFAULT_SEL, + .get_feat_version = CXL_MEMDEV_SPPR_GET_FEATURE_VERSION, + .set_feat_version = CXL_MEMDEV_SPPR_SET_FEATURE_VERSION, + .set_feat_effects = CXL_FEAT_ENTRY_SFE_IMMEDIATE_CONFIG_CHANGE | + CXL_FEAT_ENTRY_SFE_CEL_VALID, + }; + break; + case CXL_FEATURE_HPPR: + /* Fill supported feature entry for hard-PPR */ + get_feats_out->feat_entries[entry++] = + (struct CXLSupportedFeatureEntry) { + .uuid = hard_ppr_uuid, + .feat_index = index, + .get_feat_size = sizeof(CXLMemHardPPRReadAttrs), + .set_feat_size = sizeof(CXLMemHardPPRWriteAttrs), + .attr_flags = CXL_FEAT_ENTRY_ATTR_FLAG_CHANGABLE | + CXL_FEAT_ENTRY_ATTR_FLAG_SUPPORT_DEFAULT_SEL, + .get_feat_version = CXL_MEMDEV_HPPR_GET_FEATURE_VERSION, + .set_feat_version = CXL_MEMDEV_HPPR_SET_FEATURE_VERSION, + .set_feat_effects = CXL_FEAT_ENTRY_SFE_IMMEDIATE_CONFIG_CHANGE | + CXL_FEAT_ENTRY_SFE_CEL_VALID, + }; + break; case CXL_FEATURE_PATROL_SCRUB: /* Fill supported feature entry for device patrol scrub control */ get_feats_out->feat_entries[entry++] = @@ -1265,6 +1500,70 @@ static CXLRetCode cmd_features_get_supported(const struct cxl_cmd *cmd, CXL_FEAT_ENTRY_SFE_CEL_VALID, }; break; + case CXL_FEATURE_CACHELINE_SPARING: + /* Fill supported feature entry for Cacheline Memory Sparing */ + get_feats_out->feat_entries[entry++] = + (struct CXLSupportedFeatureEntry) { + .uuid = cacheline_sparing_uuid, + .feat_index = index, + .get_feat_size = sizeof(CXLMemSparingReadAttrs), + .set_feat_size = sizeof(CXLMemSparingWriteAttrs), + .attr_flags = CXL_FEAT_ENTRY_ATTR_FLAG_CHANGABLE | + CXL_FEAT_ENTRY_ATTR_FLAG_SUPPORT_DEFAULT_SEL, + .get_feat_version = CXL_MEMDEV_SPARING_GET_FEATURE_VERSION, + .set_feat_version = CXL_MEMDEV_SPARING_SET_FEATURE_VERSION, + .set_feat_effects = CXL_FEAT_ENTRY_SFE_IMMEDIATE_CONFIG_CHANGE | + CXL_FEAT_ENTRY_SFE_CEL_VALID, + }; + break; + case CXL_FEATURE_ROW_SPARING: + /* Fill supported feature entry for Row Memory Sparing */ + get_feats_out->feat_entries[entry++] = + (struct CXLSupportedFeatureEntry) { + .uuid = row_sparing_uuid, + .feat_index = index, + .get_feat_size = sizeof(CXLMemSparingReadAttrs), + .set_feat_size = sizeof(CXLMemSparingWriteAttrs), + .attr_flags = CXL_FEAT_ENTRY_ATTR_FLAG_CHANGABLE | + CXL_FEAT_ENTRY_ATTR_FLAG_SUPPORT_DEFAULT_SEL, + .get_feat_version = CXL_MEMDEV_SPARING_GET_FEATURE_VERSION, + .set_feat_version = CXL_MEMDEV_SPARING_SET_FEATURE_VERSION, + .set_feat_effects = CXL_FEAT_ENTRY_SFE_IMMEDIATE_CONFIG_CHANGE | + CXL_FEAT_ENTRY_SFE_CEL_VALID, + }; + break; + case CXL_FEATURE_BANK_SPARING: + /* Fill supported feature entry for Bank Memory Sparing */ + get_feats_out->feat_entries[entry++] = + (struct CXLSupportedFeatureEntry) { + .uuid = bank_sparing_uuid, + .feat_index = index, + .get_feat_size = sizeof(CXLMemSparingReadAttrs), + .set_feat_size = sizeof(CXLMemSparingWriteAttrs), + .attr_flags = CXL_FEAT_ENTRY_ATTR_FLAG_CHANGABLE | + CXL_FEAT_ENTRY_ATTR_FLAG_SUPPORT_DEFAULT_SEL, + .get_feat_version = CXL_MEMDEV_SPARING_GET_FEATURE_VERSION, + .set_feat_version = CXL_MEMDEV_SPARING_SET_FEATURE_VERSION, + .set_feat_effects = CXL_FEAT_ENTRY_SFE_IMMEDIATE_CONFIG_CHANGE | + CXL_FEAT_ENTRY_SFE_CEL_VALID, + }; + break; + case CXL_FEATURE_RANK_SPARING: + /* Fill supported feature entry for Rank Memory Sparing */ + get_feats_out->feat_entries[entry++] = + (struct CXLSupportedFeatureEntry) { + .uuid = rank_sparing_uuid, + .feat_index = index, + .get_feat_size = sizeof(CXLMemSparingReadAttrs), + .set_feat_size = sizeof(CXLMemSparingWriteAttrs), + .attr_flags = CXL_FEAT_ENTRY_ATTR_FLAG_CHANGABLE | + CXL_FEAT_ENTRY_ATTR_FLAG_SUPPORT_DEFAULT_SEL, + .get_feat_version = CXL_MEMDEV_SPARING_GET_FEATURE_VERSION, + .set_feat_version = CXL_MEMDEV_SPARING_SET_FEATURE_VERSION, + .set_feat_effects = CXL_FEAT_ENTRY_SFE_IMMEDIATE_CONFIG_CHANGE | + CXL_FEAT_ENTRY_SFE_CEL_VALID, + }; + break; default: __builtin_unreachable(); } @@ -1333,6 +1632,67 @@ static CXLRetCode cmd_features_get_feature(const struct cxl_cmd *cmd, memcpy(payload_out, (uint8_t *)&ct3d->ecs_attrs + get_feature->offset, bytes_to_copy); + } else if (qemu_uuid_is_equal(&get_feature->uuid, &soft_ppr_uuid)) { + if (get_feature->offset >= sizeof(CXLMemSoftPPRReadAttrs)) { + return CXL_MBOX_INVALID_INPUT; + } + bytes_to_copy = sizeof(CXLMemSoftPPRReadAttrs) - + get_feature->offset; + bytes_to_copy = MIN(bytes_to_copy, get_feature->count); + memcpy(payload_out, + (uint8_t *)&ct3d->soft_ppr_attrs + get_feature->offset, + bytes_to_copy); + } else if (qemu_uuid_is_equal(&get_feature->uuid, &hard_ppr_uuid)) { + if (get_feature->offset >= sizeof(CXLMemHardPPRReadAttrs)) { + return CXL_MBOX_INVALID_INPUT; + } + bytes_to_copy = sizeof(CXLMemHardPPRReadAttrs) - + get_feature->offset; + bytes_to_copy = MIN(bytes_to_copy, get_feature->count); + memcpy(payload_out, + (uint8_t *)&ct3d->hard_ppr_attrs + get_feature->offset, + bytes_to_copy); + } else if (qemu_uuid_is_equal(&get_feature->uuid, + &cacheline_sparing_uuid)) { + if (get_feature->offset >= sizeof(CXLMemSparingReadAttrs)) { + return CXL_MBOX_INVALID_INPUT; + } + bytes_to_copy = sizeof(CXLMemSparingReadAttrs) - + get_feature->offset; + bytes_to_copy = MIN(bytes_to_copy, get_feature->count); + memcpy(payload_out, + (uint8_t *)&ct3d->cacheline_sparing_attrs + get_feature->offset, + bytes_to_copy); + } else if (qemu_uuid_is_equal(&get_feature->uuid, &row_sparing_uuid)) { + if (get_feature->offset >= sizeof(CXLMemSparingReadAttrs)) { + return CXL_MBOX_INVALID_INPUT; + } + bytes_to_copy = sizeof(CXLMemSparingReadAttrs) - + get_feature->offset; + bytes_to_copy = MIN(bytes_to_copy, get_feature->count); + memcpy(payload_out, + (uint8_t *)&ct3d->row_sparing_attrs + get_feature->offset, + bytes_to_copy); + } else if (qemu_uuid_is_equal(&get_feature->uuid, &bank_sparing_uuid)) { + if (get_feature->offset >= sizeof(CXLMemSparingReadAttrs)) { + return CXL_MBOX_INVALID_INPUT; + } + bytes_to_copy = sizeof(CXLMemSparingReadAttrs) - + get_feature->offset; + bytes_to_copy = MIN(bytes_to_copy, get_feature->count); + memcpy(payload_out, + (uint8_t *)&ct3d->bank_sparing_attrs + get_feature->offset, + bytes_to_copy); + } else if (qemu_uuid_is_equal(&get_feature->uuid, &rank_sparing_uuid)) { + if (get_feature->offset >= sizeof(CXLMemSparingReadAttrs)) { + return CXL_MBOX_INVALID_INPUT; + } + bytes_to_copy = sizeof(CXLMemSparingReadAttrs) - + get_feature->offset; + bytes_to_copy = MIN(bytes_to_copy, get_feature->count); + memcpy(payload_out, + (uint8_t *)&ct3d->rank_sparing_attrs + get_feature->offset, + bytes_to_copy); } else { return CXL_MBOX_UNSUPPORTED; } @@ -1351,10 +1711,6 @@ static CXLRetCode cmd_features_set_feature(const struct cxl_cmd *cmd, CXLCCI *cci) { CXLSetFeatureInHeader *hdr = (void *)payload_in; - CXLMemPatrolScrubWriteAttrs *ps_write_attrs; - CXLMemPatrolScrubSetFeature *ps_set_feature; - CXLMemECSWriteAttrs *ecs_write_attrs; - CXLMemECSSetFeature *ecs_set_feature; CXLSetFeatureInfo *set_feat_info; uint16_t bytes_to_copy = 0; uint8_t data_transfer_flag; @@ -1396,13 +1752,14 @@ static CXLRetCode cmd_features_set_feature(const struct cxl_cmd *cmd, } if (qemu_uuid_is_equal(&hdr->uuid, &patrol_scrub_uuid)) { + CXLMemPatrolScrubSetFeature *ps_set_feature = (void *)payload_in; + CXLMemPatrolScrubWriteAttrs *ps_write_attrs = + &ps_set_feature->feat_data; + if (hdr->version != CXL_MEMDEV_PS_SET_FEATURE_VERSION) { return CXL_MBOX_UNSUPPORTED; } - ps_set_feature = (void *)payload_in; - ps_write_attrs = &ps_set_feature->feat_data; - if ((uint32_t)hdr->offset + bytes_to_copy > sizeof(ct3d->patrol_scrub_wr_attrs)) { return CXL_MBOX_INVALID_PAYLOAD_LENGTH; @@ -1423,13 +1780,13 @@ static CXLRetCode cmd_features_set_feature(const struct cxl_cmd *cmd, } } else if (qemu_uuid_is_equal(&hdr->uuid, &ecs_uuid)) { + CXLMemECSSetFeature *ecs_set_feature = (void *)payload_in; + CXLMemECSWriteAttrs *ecs_write_attrs = ecs_set_feature->feat_data; + if (hdr->version != CXL_ECS_SET_FEATURE_VERSION) { return CXL_MBOX_UNSUPPORTED; } - ecs_set_feature = (void *)payload_in; - ecs_write_attrs = ecs_set_feature->feat_data; - if ((uint32_t)hdr->offset + bytes_to_copy > sizeof(ct3d->ecs_wr_attrs)) { return CXL_MBOX_INVALID_PAYLOAD_LENGTH; @@ -1447,6 +1804,116 @@ static CXLRetCode cmd_features_set_feature(const struct cxl_cmd *cmd, ct3d->ecs_wr_attrs.fru_attrs[count].ecs_config & 0x1F; } } + } else if (qemu_uuid_is_equal(&hdr->uuid, &soft_ppr_uuid)) { + CXLMemSoftPPRSetFeature *sppr_set_feature = (void *)payload_in; + CXLMemSoftPPRWriteAttrs *sppr_write_attrs = + &sppr_set_feature->feat_data; + + if (hdr->version != CXL_MEMDEV_SPPR_SET_FEATURE_VERSION) { + return CXL_MBOX_UNSUPPORTED; + } + + memcpy((uint8_t *)&ct3d->soft_ppr_wr_attrs + hdr->offset, + sppr_write_attrs, bytes_to_copy); + set_feat_info->data_size += bytes_to_copy; + + if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER || + data_transfer_flag == CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER) { + ct3d->soft_ppr_attrs.op_mode = ct3d->soft_ppr_wr_attrs.op_mode; + ct3d->soft_ppr_attrs.sppr_op_mode = + ct3d->soft_ppr_wr_attrs.sppr_op_mode; + } + } else if (qemu_uuid_is_equal(&hdr->uuid, &hard_ppr_uuid)) { + CXLMemHardPPRSetFeature *hppr_set_feature = (void *)payload_in; + CXLMemHardPPRWriteAttrs *hppr_write_attrs = + &hppr_set_feature->feat_data; + + if (hdr->version != CXL_MEMDEV_HPPR_SET_FEATURE_VERSION) { + return CXL_MBOX_UNSUPPORTED; + } + + memcpy((uint8_t *)&ct3d->hard_ppr_wr_attrs + hdr->offset, + hppr_write_attrs, bytes_to_copy); + set_feat_info->data_size += bytes_to_copy; + + if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER || + data_transfer_flag == CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER) { + ct3d->hard_ppr_attrs.op_mode = ct3d->hard_ppr_wr_attrs.op_mode; + ct3d->hard_ppr_attrs.hppr_op_mode = + ct3d->hard_ppr_wr_attrs.hppr_op_mode; + } + } else if (qemu_uuid_is_equal(&hdr->uuid, &cacheline_sparing_uuid)) { + CXLMemSparingSetFeature *mem_sparing_set_feature = (void *)payload_in; + CXLMemSparingWriteAttrs *mem_sparing_write_attrs = + &mem_sparing_set_feature->feat_data; + + if (hdr->version != CXL_MEMDEV_SPARING_SET_FEATURE_VERSION) { + return CXL_MBOX_UNSUPPORTED; + } + + memcpy((uint8_t *)&ct3d->cacheline_sparing_wr_attrs + hdr->offset, + mem_sparing_write_attrs, bytes_to_copy); + set_feat_info->data_size += bytes_to_copy; + + if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER || + data_transfer_flag == CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER) { + ct3d->cacheline_sparing_attrs.op_mode = + ct3d->cacheline_sparing_wr_attrs.op_mode; + } + } else if (qemu_uuid_is_equal(&hdr->uuid, &row_sparing_uuid)) { + CXLMemSparingSetFeature *mem_sparing_set_feature = (void *)payload_in; + CXLMemSparingWriteAttrs *mem_sparing_write_attrs = + &mem_sparing_set_feature->feat_data; + + if (hdr->version != CXL_MEMDEV_SPARING_SET_FEATURE_VERSION) { + return CXL_MBOX_UNSUPPORTED; + } + + memcpy((uint8_t *)&ct3d->row_sparing_wr_attrs + hdr->offset, + mem_sparing_write_attrs, bytes_to_copy); + set_feat_info->data_size += bytes_to_copy; + + if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER || + data_transfer_flag == CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER) { + ct3d->row_sparing_attrs.op_mode = + ct3d->row_sparing_wr_attrs.op_mode; + } + } else if (qemu_uuid_is_equal(&hdr->uuid, &bank_sparing_uuid)) { + CXLMemSparingSetFeature *mem_sparing_set_feature = (void *)payload_in; + CXLMemSparingWriteAttrs *mem_sparing_write_attrs = + &mem_sparing_set_feature->feat_data; + + if (hdr->version != CXL_MEMDEV_SPARING_SET_FEATURE_VERSION) { + return CXL_MBOX_UNSUPPORTED; + } + + memcpy((uint8_t *)&ct3d->bank_sparing_wr_attrs + hdr->offset, + mem_sparing_write_attrs, bytes_to_copy); + set_feat_info->data_size += bytes_to_copy; + + if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER || + data_transfer_flag == CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER) { + ct3d->bank_sparing_attrs.op_mode = + ct3d->bank_sparing_wr_attrs.op_mode; + } + } else if (qemu_uuid_is_equal(&hdr->uuid, &rank_sparing_uuid)) { + CXLMemSparingSetFeature *mem_sparing_set_feature = (void *)payload_in; + CXLMemSparingWriteAttrs *mem_sparing_write_attrs = + &mem_sparing_set_feature->feat_data; + + if (hdr->version != CXL_MEMDEV_SPARING_SET_FEATURE_VERSION) { + return CXL_MBOX_UNSUPPORTED; + } + + memcpy((uint8_t *)&ct3d->rank_sparing_wr_attrs + hdr->offset, + mem_sparing_write_attrs, bytes_to_copy); + set_feat_info->data_size += bytes_to_copy; + + if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER || + data_transfer_flag == CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER) { + ct3d->rank_sparing_attrs.op_mode = + ct3d->rank_sparing_wr_attrs.op_mode; + } } else { return CXL_MBOX_UNSUPPORTED; } @@ -1459,6 +1926,19 @@ static CXLRetCode cmd_features_set_feature(const struct cxl_cmd *cmd, memset(&ct3d->patrol_scrub_wr_attrs, 0, set_feat_info->data_size); } else if (qemu_uuid_is_equal(&hdr->uuid, &ecs_uuid)) { memset(&ct3d->ecs_wr_attrs, 0, set_feat_info->data_size); + } else if (qemu_uuid_is_equal(&hdr->uuid, &soft_ppr_uuid)) { + memset(&ct3d->soft_ppr_wr_attrs, 0, set_feat_info->data_size); + } else if (qemu_uuid_is_equal(&hdr->uuid, &hard_ppr_uuid)) { + memset(&ct3d->hard_ppr_wr_attrs, 0, set_feat_info->data_size); + } else if (qemu_uuid_is_equal(&hdr->uuid, &cacheline_sparing_uuid)) { + memset(&ct3d->cacheline_sparing_wr_attrs, 0, + set_feat_info->data_size); + } else if (qemu_uuid_is_equal(&hdr->uuid, &row_sparing_uuid)) { + memset(&ct3d->row_sparing_wr_attrs, 0, set_feat_info->data_size); + } else if (qemu_uuid_is_equal(&hdr->uuid, &bank_sparing_uuid)) { + memset(&ct3d->bank_sparing_wr_attrs, 0, set_feat_info->data_size); + } else if (qemu_uuid_is_equal(&hdr->uuid, &rank_sparing_uuid)) { + memset(&ct3d->rank_sparing_wr_attrs, 0, set_feat_info->data_size); } set_feat_info->data_transfer_flag = 0; set_feat_info->data_saved_across_reset = false; @@ -1469,6 +1949,206 @@ static CXLRetCode cmd_features_set_feature(const struct cxl_cmd *cmd, return CXL_MBOX_SUCCESS; } +#define CXL_MEM_SPARING_FLAGS_QUERY_RESOURCES BIT(0) +#define CXL_MEM_SPARING_FLAGS_HARD_SPARING BIT(1) +#define CXL_MEM_SPARING_FLAGS_SUB_CHANNEL_VALID BIT(2) +#define CXL_MEM_SPARING_FLAGS_NIB_MASK_VALID BIT(3) + +typedef struct CXLMemSparingMaintInPayload { + uint8_t flags; + uint8_t channel; + uint8_t rank; + uint8_t nibble_mask[3]; + uint8_t bank_group; + uint8_t bank; + uint8_t row[3]; + uint16_t column; + uint8_t sub_channel; +} QEMU_PACKED CXLMemSparingMaintInPayload; + +static void cxl_create_mem_sparing_event_records(CXLType3Dev *ct3d, + uint8_t maint_op_class, uint8_t maint_op_sub_class, + CXLMaintenance *ent, + CXLMemSparingMaintInPayload *sparing_pi) +{ + CXLEventSparing event_rec = {}; + + cxl_assign_event_header(&event_rec.hdr, + &sparing_uuid, + (1 << CXL_EVENT_TYPE_INFO), + sizeof(event_rec), + cxl_device_get_timestamp(&ct3d->cxl_dstate), + 1, maint_op_class, 1, maint_op_sub_class, + 0, 0, 0, 0); + if (ent) { + event_rec.flags = 0; + event_rec.result = 0; + stw_le_p(&event_rec.res_avail, 2); + stw_le_p(&event_rec.validity_flags, ent->validity_flags); + event_rec.channel = ent->channel; + event_rec.rank = ent->rank; + st24_le_p(event_rec.nibble_mask, ent->nibble_mask); + event_rec.bank_group = ent->bank_group; + event_rec.bank = ent->bank; + st24_le_p(event_rec.row, ent->row); + stw_le_p(&event_rec.column, ent->column); + event_rec.sub_channel = ent->sub_channel; + if (ent->validity_flags & CXL_MSER_VALID_COMP_ID) { + strncpy((char *)event_rec.component_id, (char *)ent->component_id, + sizeof(event_rec.component_id)); + } + } else if (sparing_pi) { + event_rec.flags = CXL_MSER_FLAGS_QUERY_RESOURCES; + event_rec.result = 0; + event_rec.validity_flags = CXL_MSER_VALID_CHANNEL | + CXL_MSER_VALID_RANK | + CXL_MSER_VALID_NIB_MASK | + CXL_MSER_VALID_BANK_GROUP | + CXL_MSER_VALID_BANK | + CXL_MSER_VALID_ROW | + CXL_MSER_VALID_COLUMN; + event_rec.res_avail = 1; + event_rec.channel = sparing_pi->channel; + event_rec.rank = sparing_pi->rank; + if (sparing_pi->flags & CXL_MEM_SPARING_FLAGS_NIB_MASK_VALID) { + memcpy(event_rec.nibble_mask, sparing_pi->nibble_mask, + sizeof(sparing_pi->nibble_mask)); + } + event_rec.bank_group = sparing_pi->bank_group; + event_rec.bank = sparing_pi->bank; + event_rec.column = sparing_pi->column; + memcpy(event_rec.row, sparing_pi->row, sizeof(sparing_pi->row)); + if (sparing_pi->flags & CXL_MEM_SPARING_FLAGS_SUB_CHANNEL_VALID) { + event_rec.sub_channel = sparing_pi->sub_channel; + event_rec.validity_flags |= CXL_MSER_VALID_SUB_CHANNEL; + } + } else { + return; + } + + if (cxl_event_insert(&ct3d->cxl_dstate, + CXL_EVENT_TYPE_INFO, + (CXLEventRecordRaw *)&event_rec)) { + cxl_event_irq_assert(ct3d); + } +} + +static CXLRetCode cxl_perform_mem_sparing(CXLType3Dev *ct3d, uint8_t sub_class, + void *maint_pi) +{ + switch (sub_class) { + case CXL_MEMDEV_MAINT_SUBCLASS_CACHELINE_SPARING: + qemu_log("Cacheline Memory Sparing\n"); + return CXL_MBOX_SUCCESS; + case CXL_MEMDEV_MAINT_SUBCLASS_ROW_SPARING: + qemu_log("Row Memory Sparing\n"); + return CXL_MBOX_SUCCESS; + case CXL_MEMDEV_MAINT_SUBCLASS_BANK_SPARING: + qemu_log("Bank Memory Sparing\n"); + return CXL_MBOX_SUCCESS; + case CXL_MEMDEV_MAINT_SUBCLASS_RANK_SPARING: + qemu_log("Rank Memory Sparing\n"); + return CXL_MBOX_SUCCESS; + default: + return CXL_MBOX_INVALID_INPUT; + } +} + +static void cxl_perform_ppr(CXLType3Dev *ct3d, uint64_t dpa) +{ + CXLMaintenance *ent, *next; + + QLIST_FOREACH_SAFE(ent, &ct3d->maint_list, node, next) { + if (dpa == ent->dpa) { + /* Produce a Memory Sparing Event Record */ + if (ct3d->soft_ppr_attrs.sppr_op_mode & + CXL_MEMDEV_SPPR_OP_MODE_MEM_SPARING_EV_REC_EN) { + cxl_create_mem_sparing_event_records(ct3d, + CXL_MEMDEV_MAINT_CLASS_SPARING, + CXL_MEMDEV_MAINT_SUBCLASS_CACHELINE_SPARING, + ent, NULL); + } + break; + } + } +} + +/* CXL r3.2 section 8.2.10.7.1 - Perform Maintenance (Opcode 0600h) */ +#define MAINTENANCE_PPR_QUERY_RESOURCES BIT(0) + +static CXLRetCode cmd_media_perform_maintenance(const struct cxl_cmd *cmd, + uint8_t *payload_in, size_t len_in, + uint8_t *payload_out, size_t *len_out, + CXLCCI *cci) +{ + struct { + uint8_t class; + uint8_t subclass; + union { + struct { + uint8_t flags; + uint64_t dpa; + uint8_t nibble_mask[3]; + } QEMU_PACKED ppr; + CXLMemSparingMaintInPayload mem_sparing_pi; + }; + } QEMU_PACKED *maint_in = (void *)payload_in; + CXLType3Dev *ct3d = CXL_TYPE3(cci->d); + + if (maintenance_running(cci)) { + return CXL_MBOX_BUSY; + } + + switch (maint_in->class) { + case CXL_MEMDEV_MAINT_CLASS_NO_OP: + return CXL_MBOX_SUCCESS; /* nop */ + case CXL_MEMDEV_MAINT_CLASS_PPR: + if (maint_in->ppr.flags & MAINTENANCE_PPR_QUERY_RESOURCES) { + return CXL_MBOX_SUCCESS; + } + + switch (maint_in->subclass) { + case CXL_MEMDEV_MAINT_SUBCLASS_SPPR: + case CXL_MEMDEV_MAINT_SUBCLASS_HPPR: + cxl_perform_ppr(ct3d, ldq_le_p(&maint_in->ppr.dpa)); + return CXL_MBOX_SUCCESS; + default: + return CXL_MBOX_INVALID_INPUT; + } + break; + case CXL_MEMDEV_MAINT_CLASS_SPARING: + if (maint_in->mem_sparing_pi.flags & + CXL_MEM_SPARING_FLAGS_QUERY_RESOURCES) { + /* + * CXL r3.2 sect 8.2.10.7.1.4 - Memory Sparing Maintenance Operation + * Produce Memory Sparing Event record to report resources + * availability. + */ + cxl_create_mem_sparing_event_records(ct3d, maint_in->class, + maint_in->subclass, NULL, + &maint_in->mem_sparing_pi); + + return CXL_MBOX_SUCCESS; + } + + switch (maint_in->subclass) { + case CXL_MEMDEV_MAINT_SUBCLASS_CACHELINE_SPARING: + case CXL_MEMDEV_MAINT_SUBCLASS_ROW_SPARING: + case CXL_MEMDEV_MAINT_SUBCLASS_BANK_SPARING: + case CXL_MEMDEV_MAINT_SUBCLASS_RANK_SPARING: + return cxl_perform_mem_sparing(ct3d, maint_in->subclass, + &maint_in->mem_sparing_pi); + default: + return CXL_MBOX_INVALID_INPUT; + } + break; + default: + return CXL_MBOX_INVALID_INPUT; + } + + return CXL_MBOX_SUCCESS; +} + /* CXL r3.1 Section 8.2.9.9.1.1: Identify Memory Device (Opcode 4000h) */ static CXLRetCode cmd_identify_memory_device(const struct cxl_cmd *cmd, uint8_t *payload_in, @@ -3463,7 +4143,8 @@ static CXLRetCode cmd_fm_set_dc_region_config(const struct cxl_cmd *cmd, &dynamic_capacity_uuid, (1 << CXL_EVENT_TYPE_INFO), sizeof(dcEvent), - cxl_device_get_timestamp(&ct3d->cxl_dstate)); + cxl_device_get_timestamp(&ct3d->cxl_dstate), + 0, 0, 0, 0, 0, 0, 0, 0); dcEvent.type = DC_EVENT_REGION_CONFIG_UPDATED; dcEvent.validity_flags = 1; dcEvent.host_id = 0; @@ -3769,6 +4450,12 @@ static const struct cxl_cmd cxl_cmd_set[256][256] = { CXL_MBOX_IMMEDIATE_POLICY_CHANGE | CXL_MBOX_IMMEDIATE_LOG_CHANGE | CXL_MBOX_SECURITY_STATE_CHANGE)}, + [MAINTENANCE][PERFORM] = { "MAINTENANCE_PERFORM", + cmd_media_perform_maintenance, ~0, + CXL_MBOX_IMMEDIATE_CONFIG_CHANGE | + CXL_MBOX_IMMEDIATE_DATA_CHANGE | + CXL_MBOX_IMMEDIATE_LOG_CHANGE | + CXL_MBOX_BACKGROUND_OPERATION }, [IDENTIFY][MEMORY_DEVICE] = { "IDENTIFY_MEMORY_DEVICE", cmd_identify_memory_device, 0, 0 }, [CCLS][GET_PARTITION_INFO] = { "CCLS_GET_PARTITION_INFO", @@ -3840,6 +4527,8 @@ static const struct cxl_cmd cxl_cmd_set_sw[256][256] = { cmd_identify_switch_device, 0, 0 }, [PHYSICAL_SWITCH][GET_PHYSICAL_PORT_STATE] = { "SWITCH_PHYSICAL_PORT_STATS", cmd_get_physical_port_state, ~0, 0 }, + [PHYSICAL_SWITCH][PHYSICAL_PORT_CONTROL] = { "SWITCH_PHYSICAL_PORT_CONTROL", + cmd_physical_port_control, 2, 0 }, [TUNNEL][MANAGEMENT_COMMAND] = { "TUNNEL_MANAGEMENT_COMMAND", cmd_tunnel_management_cmd, ~0, 0 }, }; @@ -4046,6 +4735,19 @@ static void cxl_rebuild_cel(CXLCCI *cci) } } +void cxl_init_physical_port_control(CXLPhyPortPerst *perst) +{ + qemu_mutex_init(&perst->lock); + perst->issued_assert_perst = false; + /* + * Assert PERST involves physical port to be in + * hold reset phase for minimum 100ms. No other + * physical port control requests are entertained + * until Deassert PERST command. + */ + perst->asrt_time = ASSERT_WAIT_TIME_MS; +} + void cxl_init_cci(CXLCCI *cci, size_t payload_max) { cci->payload_max = payload_max; diff --git a/hw/cxl/meson.build b/hw/cxl/meson.build index 3e375f61a986..5f61273a68d7 100644 --- a/hw/cxl/meson.build +++ b/hw/cxl/meson.build @@ -7,7 +7,7 @@ system_ss.add(when: 'CONFIG_CXL', 'cxl-cdat.c', 'cxl-events.c', 'switch-mailbox-cci.c', - ), - if_false: files( + )) +stub_ss.add(files( 'cxl-host-stubs.c', )) diff --git a/hw/display/ati.c b/hw/display/ati.c index e9c3ad2cd152..05cf507bd470 100644 --- a/hw/display/ati.c +++ b/hw/display/ati.c @@ -28,7 +28,6 @@ #include "qemu/error-report.h" #include "qapi/error.h" #include "ui/console.h" -#include "hw/display/i2c-ddc.h" #include "trace.h" #define ATI_DEBUG_HW_CURSOR 0 @@ -199,7 +198,7 @@ static void ati_cursor_draw_line(VGACommonState *vga, uint8_t *d, int scr_y) ATIVGAState *s = container_of(vga, ATIVGAState, vga); uint32_t srcoff; uint32_t *dp = (uint32_t *)d; - int i, j, h; + int i, j, h, idx = 0; if (!(s->regs.crtc_gen_cntl & CRTC2_CUR_EN) || scr_y < vga->hw_cursor_y || scr_y >= vga->hw_cursor_y + 64 || @@ -214,10 +213,13 @@ static void ati_cursor_draw_line(VGACommonState *vga, uint8_t *d, int scr_y) uint32_t color; uint8_t abits = vga_read_byte(vga, srcoff + i); uint8_t xbits = vga_read_byte(vga, srcoff + i + 8); - for (j = 0; j < 8; j++, abits <<= 1, xbits <<= 1) { + for (j = 0; j < 8; j++, abits <<= 1, xbits <<= 1, idx++) { + if (vga->hw_cursor_x + idx >= h) { + return; /* end of screen, don't span to next line */ + } if (abits & BIT(7)) { if (xbits & BIT(7)) { - color = dp[i * 8 + j] ^ 0xffffffff; /* complement */ + color = dp[idx] ^ 0xffffffff; /* complement */ } else { continue; /* transparent, no change */ } @@ -225,10 +227,7 @@ static void ati_cursor_draw_line(VGACommonState *vga, uint8_t *d, int scr_y) color = (xbits & BIT(7) ? s->regs.cur_color1 : s->regs.cur_color0) | 0xff000000; } - if (vga->hw_cursor_x + i * 8 + j >= h) { - return; /* end of screen, don't span to next line */ - } - dp[i * 8 + j] = color; + dp[idx] = color; } } } @@ -361,7 +360,7 @@ static uint64_t ati_mm_read(void *opaque, hwaddr addr, unsigned int size) PCI_BASE_ADDRESS_0, size) & 0xfffffff0; break; case CONFIG_APER_SIZE: - val = s->vga.vram_size / 2; + val = memory_region_size(&s->linear_aper) / 2; break; case CONFIG_REG_1_BASE: val = pci_default_read_config(&s->dev, @@ -438,7 +437,7 @@ static uint64_t ati_mm_read(void *opaque, hwaddr addr, unsigned int size) case DST_PITCH: val = s->regs.dst_pitch; if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) { - val &= s->regs.dst_tile << 16; + val |= s->regs.dst_tile << 16; } break; case DST_WIDTH: @@ -460,7 +459,13 @@ static uint64_t ati_mm_read(void *opaque, hwaddr addr, unsigned int size) val = s->regs.dst_y; break; case DP_GUI_MASTER_CNTL: - val = s->regs.dp_gui_master_cntl; + /* DP_GUI_MASTER_CNTL aliases fields from DP_MIX and DP_DATATYPE */ + val = s->regs.dp_gui_master_cntl | + ((s->regs.dp_datatype & DP_BRUSH_DATATYPE) >> 4) | + ((s->regs.dp_datatype & DP_DST_DATATYPE) << 8) | + ((s->regs.dp_datatype & DP_SRC_DATATYPE) >> 4) | + (s->regs.dp_mix & DP_ROP3) | + ((s->regs.dp_mix & DP_SRC_SOURCE) << 16); break; case SRC_OFFSET: val = s->regs.src_offset; @@ -468,7 +473,7 @@ static uint64_t ati_mm_read(void *opaque, hwaddr addr, unsigned int size) case SRC_PITCH: val = s->regs.src_pitch; if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) { - val &= s->regs.src_tile << 16; + val |= s->regs.src_tile << 16; } break; case DP_BRUSH_BKGD_CLR: @@ -508,7 +513,32 @@ static uint64_t ati_mm_read(void *opaque, hwaddr addr, unsigned int size) val |= s->regs.default_tile << 16; break; case DEFAULT_SC_BOTTOM_RIGHT: - val = s->regs.default_sc_bottom_right; + val = (s->regs.default_sc_bottom << 16) | + s->regs.default_sc_right; + break; + case SC_TOP: + val = s->regs.sc_top; + break; + case SC_LEFT: + val = s->regs.sc_left; + break; + case SC_BOTTOM: + val = s->regs.sc_bottom; + break; + case SC_RIGHT: + val = s->regs.sc_right; + break; + case SRC_SC_BOTTOM: + val = s->regs.src_sc_bottom; + break; + case SRC_SC_RIGHT: + val = s->regs.src_sc_right; + break; + case SC_TOP_LEFT: + case SC_BOTTOM_RIGHT: + case SRC_SC_BOTTOM_RIGHT: + qemu_log_mask(LOG_GUEST_ERROR, + "Read from write-only register 0x%x\n", (unsigned)addr); break; default: break; @@ -862,6 +892,25 @@ static void ati_mm_write(void *opaque, hwaddr addr, s->regs.dp_datatype = (data & 0x0f00) >> 8 | (data & 0x30f0) << 4 | (data & 0x4000) << 16; s->regs.dp_mix = (data & GMC_ROP3_MASK) | (data & 0x7000000) >> 16; + + if (!(data & GMC_SRC_PITCH_OFFSET_CNTL)) { + s->regs.src_offset = s->regs.default_offset; + s->regs.src_pitch = s->regs.default_pitch; + } + if (!(data & GMC_DST_PITCH_OFFSET_CNTL)) { + s->regs.dst_offset = s->regs.default_offset; + s->regs.dst_pitch = s->regs.default_pitch; + } + if (!(data & GMC_SRC_CLIPPING)) { + s->regs.src_sc_right = s->regs.default_sc_right; + s->regs.src_sc_bottom = s->regs.default_sc_bottom; + } + if (!(data & GMC_DST_CLIPPING)) { + s->regs.sc_top = 0; + s->regs.sc_left = 0; + s->regs.sc_right = s->regs.default_sc_right; + s->regs.sc_bottom = s->regs.default_sc_bottom; + } break; case DST_WIDTH_X: s->regs.dst_x = data & 0x3fff; @@ -909,6 +958,12 @@ static void ati_mm_write(void *opaque, hwaddr addr, case DP_CNTL: s->regs.dp_cntl = data; break; + case DP_SRC_FRGD_CLR: + s->regs.dp_src_frgd_clr = data; + break; + case DP_SRC_BKGD_CLR: + s->regs.dp_src_bkgd_clr = data; + break; case DP_DATATYPE: s->regs.dp_datatype = data & 0xe0070f0f; break; @@ -935,7 +990,57 @@ static void ati_mm_write(void *opaque, hwaddr addr, } break; case DEFAULT_SC_BOTTOM_RIGHT: - s->regs.default_sc_bottom_right = data & 0x3fff3fff; + s->regs.default_sc_right = data & 0x3fff; + s->regs.default_sc_bottom = (data >> 16) & 0x3fff; + break; + case SC_TOP_LEFT: + s->regs.sc_left = data & 0x3fff; + s->regs.sc_top = (data >> 16) & 0x3fff; + break; + case SC_LEFT: + s->regs.sc_left = data & 0x3fff; + break; + case SC_TOP: + s->regs.sc_top = data & 0x3fff; + break; + case SC_BOTTOM_RIGHT: + s->regs.sc_right = data & 0x3fff; + s->regs.sc_bottom = (data >> 16) & 0x3fff; + break; + case SC_RIGHT: + s->regs.sc_right = data & 0x3fff; + break; + case SC_BOTTOM: + s->regs.sc_bottom = data & 0x3fff; + break; + case SRC_SC_BOTTOM_RIGHT: + s->regs.src_sc_right = data & 0x3fff; + s->regs.src_sc_bottom = (data >> 16) & 0x3fff; + break; + case SRC_SC_RIGHT: + s->regs.src_sc_right = data & 0x3fff; + break; + case SRC_SC_BOTTOM: + s->regs.src_sc_bottom = data & 0x3fff; + break; + case HOST_DATA0: + case HOST_DATA1: + case HOST_DATA2: + case HOST_DATA3: + case HOST_DATA4: + case HOST_DATA5: + case HOST_DATA6: + case HOST_DATA7: + case HOST_DATA_LAST: + if (!s->host_data.active) { + break; + } + s->host_data.acc[s->host_data.next++] = data; + if (addr == HOST_DATA_LAST) { + ati_host_data_finish(s); + } else if (s->host_data.next >= 4) { + ati_host_data_flush(s); + } break; default: break; @@ -952,6 +1057,8 @@ static void ati_vga_realize(PCIDevice *dev, Error **errp) { ATIVGAState *s = ATI_VGA(dev); VGACommonState *vga = &s->vga; + I2CBus *i2cbus; + uint64_t aper_size; #ifndef CONFIG_PIXMAN if (s->use_pixman != 0) { @@ -999,11 +1106,10 @@ static void ati_vga_realize(PCIDevice *dev, Error **errp) } /* ddc, edid */ - I2CBus *i2cbus = i2c_init_bus(DEVICE(s), "ati-vga.ddc"); + i2cbus = i2c_init_bus(DEVICE(s), "ati-vga.ddc"); bitbang_i2c_init(&s->bbi2c, i2cbus); - I2CSlave *i2cddc = I2C_SLAVE(qdev_new(TYPE_I2CDDC)); - i2c_slave_set_address(i2cddc, 0x50); - qdev_realize_and_unref(DEVICE(i2cddc), BUS(i2cbus), &error_abort); + i2c_slave_set_address(I2C_SLAVE(&s->i2cddc), 0x50); + qdev_realize(DEVICE(&s->i2cddc), BUS(i2cbus), &error_abort); /* mmio register space */ memory_region_init_io(&s->mm, OBJECT(s), &ati_mm_ops, s, @@ -1011,7 +1117,18 @@ static void ati_vga_realize(PCIDevice *dev, Error **errp) /* io space is alias to beginning of mmregs */ memory_region_init_alias(&s->io, OBJECT(s), "ati.io", &s->mm, 0, 0x100); - pci_register_bar(dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &vga->vram); + /* + * The framebuffer is at the beginning of the linear aperture. For + * Rage128 the upper half of the aperture is reserved for an AGP + * window (which we do not emulate.) + */ + aper_size = s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF ? + ATI_RAGE128_LINEAR_APER_SIZE : ATI_R100_LINEAR_APER_SIZE; + memory_region_init(&s->linear_aper, OBJECT(dev), "ati-linear-aperture0", + aper_size); + memory_region_add_subregion(&s->linear_aper, 0, &vga->vram); + + pci_register_bar(dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->linear_aper); pci_register_bar(dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &s->io); pci_register_bar(dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mm); @@ -1030,6 +1147,11 @@ static void ati_vga_reset(DeviceState *dev) /* reset vga */ vga_common_reset(&s->vga); s->mode = VGA_MODE; + + s->host_data.active = false; + s->host_data.next = 0; + s->host_data.row = 0; + s->host_data.col = 0; } static void ati_vga_exit(PCIDevice *dev) @@ -1048,6 +1170,7 @@ static const Property ati_vga_properties[] = { DEFINE_PROP_BOOL("guest_hwcursor", ATIVGAState, cursor_guest_mode, false), /* this is a debug option, prefer PROP_UINT over PROP_BIT for simplicity */ DEFINE_PROP_UINT8("x-pixman", ATIVGAState, use_pixman, DEFAULT_X_PIXMAN), + DEFINE_EDID_PROPERTIES(ATIVGAState, i2cddc.edid_info), }; static void ati_vga_class_init(ObjectClass *klass, const void *data) @@ -1070,6 +1193,9 @@ static void ati_vga_class_init(ObjectClass *klass, const void *data) static void ati_vga_init(Object *o) { + ATIVGAState *s = ATI_VGA(o); + + object_initialize_child(o, "edid", &s->i2cddc, TYPE_I2CDDC); object_property_set_description(o, "x-pixman", "Use pixman for: " "1: fill, 2: blit"); } diff --git a/hw/display/ati_2d.c b/hw/display/ati_2d.c index 309bb5ccb6cf..37fe6c17ee9f 100644 --- a/hw/display/ati_2d.c +++ b/hw/display/ati_2d.c @@ -13,6 +13,7 @@ #include "qemu/log.h" #include "ui/pixel_ops.h" #include "ui/console.h" +#include "ui/rect.h" /* * NOTE: @@ -24,7 +25,7 @@ * possible. */ -static int ati_bpp_from_datatype(ATIVGAState *s) +static int ati_bpp_from_datatype(const ATIVGAState *s) { switch (s->regs.dp_datatype & 0xf) { case 2: @@ -43,109 +44,182 @@ static int ati_bpp_from_datatype(ATIVGAState *s) } } -#define DEFAULT_CNTL (s->regs.dp_gui_master_cntl & GMC_DST_PITCH_OFFSET_CNTL) +typedef struct { + int bpp; + uint32_t rop3; + bool host_data_active; + bool left_to_right; + bool top_to_bottom; + uint32_t frgd_clr; + const uint8_t *palette; + const uint8_t *vram_end; + QemuRect scissor; -void ati_2d_blt(ATIVGAState *s) + QemuRect dst; + int dst_stride; + uint8_t *dst_bits; + uint32_t dst_offset; + + QemuRect src; + int src_stride; + const uint8_t *src_bits; +} ATI2DCtx; + +static void ati_set_dirty(VGACommonState *vga, const ATI2DCtx *ctx) { - /* FIXME it is probably more complex than this and may need to be */ - /* rewritten but for now as a start just to get some output: */ - DisplaySurface *ds = qemu_console_surface(s->vga.con); - DPRINTF("%p %u ds: %p %d %d rop: %x\n", s->vga.vram_ptr, - s->vga.vbe_start_addr, surface_data(ds), surface_stride(ds), - surface_bits_per_pixel(ds), - (s->regs.dp_mix & GMC_ROP3_MASK) >> 16); - unsigned dst_x = (s->regs.dp_cntl & DST_X_LEFT_TO_RIGHT ? - s->regs.dst_x : s->regs.dst_x + 1 - s->regs.dst_width); - unsigned dst_y = (s->regs.dp_cntl & DST_Y_TOP_TO_BOTTOM ? - s->regs.dst_y : s->regs.dst_y + 1 - s->regs.dst_height); - int bpp = ati_bpp_from_datatype(s); - if (!bpp) { - qemu_log_mask(LOG_GUEST_ERROR, "Invalid bpp\n"); - return; - } - int dst_stride = DEFAULT_CNTL ? s->regs.dst_pitch : s->regs.default_pitch; - if (!dst_stride) { - qemu_log_mask(LOG_GUEST_ERROR, "Zero dest pitch\n"); - return; + DisplaySurface *ds = qemu_console_surface(vga->con); + + DPRINTF("%p %u ds: %p %d %d rop: %x\n", vga->vram_ptr, vga->vbe_start_addr, + surface_data(ds), surface_stride(ds), surface_bits_per_pixel(ds), + ctx->rop3 >> 16); + if (ctx->dst_bits >= vga->vram_ptr + vga->vbe_start_addr && + ctx->dst_bits < vga->vram_ptr + vga->vbe_start_addr + + vga->vbe_regs[VBE_DISPI_INDEX_YRES] * vga->vbe_line_offset) { + memory_region_set_dirty(&vga->vram, + vga->vbe_start_addr + ctx->dst_offset + + ctx->dst.y * surface_stride(ds), + ctx->dst.height * surface_stride(ds)); } - uint8_t *dst_bits = s->vga.vram_ptr + (DEFAULT_CNTL ? - s->regs.dst_offset : s->regs.default_offset); +} + +static void setup_2d_blt_ctx(const ATIVGAState *s, ATI2DCtx *ctx) +{ + ctx->bpp = ati_bpp_from_datatype(s); + ctx->rop3 = s->regs.dp_mix & GMC_ROP3_MASK; + ctx->host_data_active = s->host_data.active; + ctx->left_to_right = s->regs.dp_cntl & DST_X_LEFT_TO_RIGHT; + ctx->top_to_bottom = s->regs.dp_cntl & DST_Y_TOP_TO_BOTTOM; + ctx->frgd_clr = s->regs.dp_brush_frgd_clr; + ctx->palette = s->vga.palette; + ctx->dst_offset = s->regs.dst_offset; + ctx->vram_end = s->vga.vram_ptr + s->vga.vram_size; + ctx->scissor.width = s->regs.sc_right - s->regs.sc_left + 1; + ctx->scissor.height = s->regs.sc_bottom - s->regs.sc_top + 1; + ctx->scissor.x = s->regs.sc_left; + ctx->scissor.y = s->regs.sc_top; + + ctx->dst.width = s->regs.dst_width; + ctx->dst.height = s->regs.dst_height; + ctx->dst.x = (ctx->left_to_right ? + s->regs.dst_x : s->regs.dst_x + 1 - ctx->dst.width); + ctx->dst.y = (ctx->top_to_bottom ? + s->regs.dst_y : s->regs.dst_y + 1 - ctx->dst.height); + ctx->dst_stride = s->regs.dst_pitch; + ctx->dst_bits = s->vga.vram_ptr + s->regs.dst_offset; if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) { - dst_bits += s->regs.crtc_offset & 0x07ffffff; - dst_stride *= bpp; + ctx->dst_bits += s->regs.crtc_offset & 0x07ffffff; + ctx->dst_stride *= ctx->bpp; } - uint8_t *end = s->vga.vram_ptr + s->vga.vram_size; - if (dst_x > 0x3fff || dst_y > 0x3fff || dst_bits >= end - || dst_bits + dst_x - + (dst_y + s->regs.dst_height) * dst_stride >= end) { - qemu_log_mask(LOG_UNIMP, "blt outside vram not implemented\n"); - return; + + ctx->src.x = (ctx->left_to_right ? + s->regs.src_x : s->regs.src_x + 1 - ctx->dst.width); + ctx->src.y = (ctx->top_to_bottom ? + s->regs.src_y : s->regs.src_y + 1 - ctx->dst.height); + ctx->src_stride = s->regs.src_pitch; + ctx->src_bits = s->vga.vram_ptr + s->regs.src_offset; + if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) { + ctx->src_bits += s->regs.crtc_offset & 0x07ffffff; + ctx->src_stride *= ctx->bpp; } DPRINTF("%d %d %d, %d %d %d, (%d,%d) -> (%d,%d) %dx%d %c %c\n", s->regs.src_offset, s->regs.dst_offset, s->regs.default_offset, - s->regs.src_pitch, s->regs.dst_pitch, s->regs.default_pitch, - s->regs.src_x, s->regs.src_y, dst_x, dst_y, - s->regs.dst_width, s->regs.dst_height, - (s->regs.dp_cntl & DST_X_LEFT_TO_RIGHT ? '>' : '<'), - (s->regs.dp_cntl & DST_Y_TOP_TO_BOTTOM ? 'v' : '^')); - switch (s->regs.dp_mix & GMC_ROP3_MASK) { + ctx->src_stride, ctx->dst_stride, s->regs.default_pitch, + ctx->src.x, ctx->src.y, ctx->dst.x, ctx->dst.y, + ctx->dst.width, ctx->dst.height, + (ctx->left_to_right ? '>' : '<'), + (ctx->top_to_bottom ? 'v' : '^')); +} + +static bool ati_2d_do_blt(ATI2DCtx *ctx, uint8_t use_pixman) +{ + QemuRect vis_src, vis_dst; + + if (!ctx->bpp) { + qemu_log_mask(LOG_GUEST_ERROR, "Invalid bpp\n"); + return false; + } + if (!ctx->dst_stride) { + qemu_log_mask(LOG_GUEST_ERROR, "Zero dest pitch\n"); + return false; + } + if (ctx->dst.x > 0x3fff || ctx->dst.y > 0x3fff || + ctx->dst_bits >= ctx->vram_end || ctx->dst_bits + ctx->dst.x + + (ctx->dst.y + ctx->dst.height) * ctx->dst_stride >= ctx->vram_end) { + qemu_log_mask(LOG_UNIMP, "blt outside vram not implemented\n"); + return false; + } + qemu_rect_intersect(&ctx->dst, &ctx->scissor, &vis_dst); + if (!vis_dst.height || !vis_dst.width) { + /* Nothing is visible, completely clipped */ + return false; + } + /* + * The src must be offset if clipping is applied to the dst. + * This is so that when the source is blit to a dst clipped + * on the top or left the src image is not shifted into the + * clipped region but actually clipped. + */ + vis_src.x = ctx->src.x + (vis_dst.x - ctx->dst.x); + vis_src.y = ctx->src.y + (vis_dst.y - ctx->dst.y); + vis_src.width = vis_dst.width; + vis_src.height = vis_dst.height; + + DPRINTF("dst: (%d,%d) %dx%d -> vis_dst: (%d,%d) %dx%d\n", + ctx->dst.x, ctx->dst.y, ctx->dst.width, ctx->dst.height, + vis_dst.x, vis_dst.y, vis_dst.width, vis_dst.height); + DPRINTF("src: (%d,%d) %dx%d -> vis_src: (%d,%d) %dx%d\n", + ctx->src.x, ctx->src.y, ctx->dst.width, ctx->dst.height, + vis_src.x, vis_src.y, vis_src.width, vis_src.height); + + switch (ctx->rop3) { case ROP3_SRCCOPY: { bool fallback = false; - unsigned src_x = (s->regs.dp_cntl & DST_X_LEFT_TO_RIGHT ? - s->regs.src_x : s->regs.src_x + 1 - s->regs.dst_width); - unsigned src_y = (s->regs.dp_cntl & DST_Y_TOP_TO_BOTTOM ? - s->regs.src_y : s->regs.src_y + 1 - s->regs.dst_height); - int src_stride = DEFAULT_CNTL ? - s->regs.src_pitch : s->regs.default_pitch; - if (!src_stride) { + if (!ctx->src_stride) { qemu_log_mask(LOG_GUEST_ERROR, "Zero source pitch\n"); - return; - } - uint8_t *src_bits = s->vga.vram_ptr + (DEFAULT_CNTL ? - s->regs.src_offset : s->regs.default_offset); - - if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) { - src_bits += s->regs.crtc_offset & 0x07ffffff; - src_stride *= bpp; + return false; } - if (src_x > 0x3fff || src_y > 0x3fff || src_bits >= end - || src_bits + src_x - + (src_y + s->regs.dst_height) * src_stride >= end) { + if (!ctx->host_data_active && + (vis_src.x > 0x3fff || vis_src.y > 0x3fff || + ctx->src_bits >= ctx->vram_end || ctx->src_bits + vis_src.x + + (vis_src.y + vis_dst.height) * ctx->src_stride >= ctx->vram_end)) { qemu_log_mask(LOG_UNIMP, "blt outside vram not implemented\n"); - return; + return false; } - src_stride /= sizeof(uint32_t); - dst_stride /= sizeof(uint32_t); - DPRINTF("pixman_blt(%p, %p, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)\n", - src_bits, dst_bits, src_stride, dst_stride, bpp, bpp, - src_x, src_y, dst_x, dst_y, - s->regs.dst_width, s->regs.dst_height); + DPRINTF("pixman_blt(%p, %p, %ld, %ld, %d, %d, %d, %d, %d, %d, %d, %d)\n", + ctx->src_bits, ctx->dst_bits, + ctx->src_stride / sizeof(uint32_t), + ctx->dst_stride / sizeof(uint32_t), + ctx->bpp, ctx->bpp, vis_src.x, vis_src.y, vis_dst.x, vis_dst.y, + vis_dst.width, vis_dst.height); #ifdef CONFIG_PIXMAN - if ((s->use_pixman & BIT(1)) && - s->regs.dp_cntl & DST_X_LEFT_TO_RIGHT && - s->regs.dp_cntl & DST_Y_TOP_TO_BOTTOM) { - fallback = !pixman_blt((uint32_t *)src_bits, (uint32_t *)dst_bits, - src_stride, dst_stride, bpp, bpp, - src_x, src_y, dst_x, dst_y, - s->regs.dst_width, s->regs.dst_height); - } else if (s->use_pixman & BIT(1)) { + int src_stride_words = ctx->src_stride / sizeof(uint32_t); + int dst_stride_words = ctx->dst_stride / sizeof(uint32_t); + if ((use_pixman & BIT(1)) && + ctx->left_to_right && ctx->top_to_bottom) { + fallback = !pixman_blt((uint32_t *)ctx->src_bits, + (uint32_t *)ctx->dst_bits, src_stride_words, + dst_stride_words, ctx->bpp, ctx->bpp, + vis_src.x, vis_src.y, vis_dst.x, vis_dst.y, + vis_dst.width, vis_dst.height); + } else if (use_pixman & BIT(1)) { /* FIXME: We only really need a temporary if src and dst overlap */ - int llb = s->regs.dst_width * (bpp / 8); - int tmp_stride = DIV_ROUND_UP(llb, sizeof(uint32_t)); - uint32_t *tmp = g_malloc(tmp_stride * sizeof(uint32_t) * - s->regs.dst_height); - fallback = !pixman_blt((uint32_t *)src_bits, tmp, - src_stride, tmp_stride, bpp, bpp, - src_x, src_y, 0, 0, - s->regs.dst_width, s->regs.dst_height); + int llb = vis_dst.width * (ctx->bpp / 8); + int tmp_stride_words = DIV_ROUND_UP(llb, sizeof(uint32_t)); + uint32_t *tmp = g_malloc(tmp_stride_words * sizeof(uint32_t) * + vis_dst.height); + fallback = !pixman_blt((uint32_t *)ctx->src_bits, tmp, + src_stride_words, tmp_stride_words, ctx->bpp, + ctx->bpp, vis_src.x, vis_src.y, 0, 0, + vis_dst.width, vis_dst.height); if (!fallback) { - fallback = !pixman_blt(tmp, (uint32_t *)dst_bits, - tmp_stride, dst_stride, bpp, bpp, - 0, 0, dst_x, dst_y, - s->regs.dst_width, s->regs.dst_height); + fallback = !pixman_blt(tmp, (uint32_t *)ctx->dst_bits, + tmp_stride_words, dst_stride_words, + ctx->bpp, ctx->bpp, 0, 0, + vis_dst.x, vis_dst.y, + vis_dst.width, vis_dst.height); } g_free(tmp); } else @@ -154,35 +228,23 @@ void ati_2d_blt(ATIVGAState *s) fallback = true; } if (fallback) { - unsigned int y, i, j, bypp = bpp / 8; - unsigned int src_pitch = src_stride * sizeof(uint32_t); - unsigned int dst_pitch = dst_stride * sizeof(uint32_t); - - for (y = 0; y < s->regs.dst_height; y++) { - i = dst_x * bypp; - j = src_x * bypp; - if (s->regs.dp_cntl & DST_Y_TOP_TO_BOTTOM) { - i += (dst_y + y) * dst_pitch; - j += (src_y + y) * src_pitch; + unsigned int y, i, j, bypp = ctx->bpp / 8; + for (y = 0; y < vis_dst.height; y++) { + i = vis_dst.x * bypp; + j = vis_src.x * bypp; + if (ctx->top_to_bottom) { + i += (vis_dst.y + y) * ctx->dst_stride; + j += (vis_src.y + y) * ctx->src_stride; } else { - i += (dst_y + s->regs.dst_height - 1 - y) * dst_pitch; - j += (src_y + s->regs.dst_height - 1 - y) * src_pitch; + i += (vis_dst.y + vis_dst.height - 1 - y) + * ctx->dst_stride; + j += (vis_src.y + vis_dst.height - 1 - y) + * ctx->src_stride; } - memmove(&dst_bits[i], &src_bits[j], s->regs.dst_width * bypp); + memmove(&ctx->dst_bits[i], &ctx->src_bits[j], + vis_dst.width * bypp); } } - if (dst_bits >= s->vga.vram_ptr + s->vga.vbe_start_addr && - dst_bits < s->vga.vram_ptr + s->vga.vbe_start_addr + - s->vga.vbe_regs[VBE_DISPI_INDEX_YRES] * s->vga.vbe_line_offset) { - memory_region_set_dirty(&s->vga.vram, s->vga.vbe_start_addr + - s->regs.dst_offset + - dst_y * surface_stride(ds), - s->regs.dst_height * surface_stride(ds)); - } - s->regs.dst_x = (s->regs.dp_cntl & DST_X_LEFT_TO_RIGHT ? - dst_x + s->regs.dst_width : dst_x); - s->regs.dst_y = (s->regs.dp_cntl & DST_Y_TOP_TO_BOTTOM ? - dst_y + s->regs.dst_height : dst_y); break; } case ROP3_PATCOPY: @@ -191,54 +253,183 @@ void ati_2d_blt(ATIVGAState *s) { uint32_t filler = 0; - switch (s->regs.dp_mix & GMC_ROP3_MASK) { + switch (ctx->rop3) { case ROP3_PATCOPY: - filler = s->regs.dp_brush_frgd_clr; + filler = ctx->frgd_clr; break; case ROP3_BLACKNESS: - filler = 0xffUL << 24 | rgb_to_pixel32(s->vga.palette[0], - s->vga.palette[1], s->vga.palette[2]); + filler = 0xffUL << 24 | rgb_to_pixel32(ctx->palette[0], + ctx->palette[1], + ctx->palette[2]); break; case ROP3_WHITENESS: - filler = 0xffUL << 24 | rgb_to_pixel32(s->vga.palette[3], - s->vga.palette[4], s->vga.palette[5]); + filler = 0xffUL << 24 | rgb_to_pixel32(ctx->palette[3], + ctx->palette[4], + ctx->palette[5]); break; } - dst_stride /= sizeof(uint32_t); - DPRINTF("pixman_fill(%p, %d, %d, %d, %d, %d, %d, %x)\n", - dst_bits, dst_stride, bpp, dst_x, dst_y, - s->regs.dst_width, s->regs.dst_height, filler); + DPRINTF("pixman_fill(%p, %ld, %d, %d, %d, %d, %d, %x)\n", + ctx->dst_bits, ctx->dst_stride / sizeof(uint32_t), ctx->bpp, + vis_dst.x, vis_dst.y, vis_dst.width, vis_dst.height, filler); #ifdef CONFIG_PIXMAN - if (!(s->use_pixman & BIT(0)) || - !pixman_fill((uint32_t *)dst_bits, dst_stride, bpp, dst_x, dst_y, - s->regs.dst_width, s->regs.dst_height, filler)) + if (!(use_pixman & BIT(0)) || + !pixman_fill((uint32_t *)ctx->dst_bits, + ctx->dst_stride / sizeof(uint32_t), ctx->bpp, + vis_dst.x, vis_dst.y, vis_dst.width, vis_dst.height, + filler)) #endif { /* fallback when pixman failed or we don't want to call it */ - unsigned int x, y, i, bypp = bpp / 8; - unsigned int dst_pitch = dst_stride * sizeof(uint32_t); - for (y = 0; y < s->regs.dst_height; y++) { - i = dst_x * bypp + (dst_y + y) * dst_pitch; - for (x = 0; x < s->regs.dst_width; x++, i += bypp) { - stn_he_p(&dst_bits[i], bypp, filler); + unsigned int x, y, i, bypp = ctx->bpp / 8; + for (y = 0; y < vis_dst.height; y++) { + i = vis_dst.x * bypp + (vis_dst.y + y) * ctx->dst_stride; + for (x = 0; x < vis_dst.width; x++, i += bypp) { + stn_he_p(&ctx->dst_bits[i], bypp, filler); } } } - if (dst_bits >= s->vga.vram_ptr + s->vga.vbe_start_addr && - dst_bits < s->vga.vram_ptr + s->vga.vbe_start_addr + - s->vga.vbe_regs[VBE_DISPI_INDEX_YRES] * s->vga.vbe_line_offset) { - memory_region_set_dirty(&s->vga.vram, s->vga.vbe_start_addr + - s->regs.dst_offset + - dst_y * surface_stride(ds), - s->regs.dst_height * surface_stride(ds)); - } - s->regs.dst_y = (s->regs.dp_cntl & DST_Y_TOP_TO_BOTTOM ? - dst_y + s->regs.dst_height : dst_y); break; } default: qemu_log_mask(LOG_UNIMP, "Unimplemented ati_2d blt op %x\n", - (s->regs.dp_mix & GMC_ROP3_MASK) >> 16); + ctx->rop3 >> 16); + return false; + } + + return true; +} + +void ati_2d_blt(ATIVGAState *s) +{ + ATI2DCtx ctx; + uint32_t src_source = s->regs.dp_mix & DP_SRC_SOURCE; + + /* Finish any active HOST_DATA blits before starting a new blit */ + ati_host_data_finish(s); + + if (src_source == DP_SRC_HOST || src_source == DP_SRC_HOST_BYTEALIGN) { + /* Begin a HOST_DATA blit */ + s->host_data.active = true; + s->host_data.next = 0; + s->host_data.col = 0; + s->host_data.row = 0; + return; + } + setup_2d_blt_ctx(s, &ctx); + if (ati_2d_do_blt(&ctx, s->use_pixman)) { + ati_set_dirty(&s->vga, &ctx); + } +} + +bool ati_host_data_flush(ATIVGAState *s) +{ + ATI2DCtx ctx, chunk; + uint32_t fg = s->regs.dp_src_frgd_clr; + uint32_t bg = s->regs.dp_src_bkgd_clr; + unsigned bypp, pix_count, row, col, idx; + uint8_t pix_buf[ATI_HOST_DATA_ACC_BITS * sizeof(uint32_t)]; + uint32_t byte_pix_order = s->regs.dp_datatype & DP_BYTE_PIX_ORDER; + uint32_t src_source = s->regs.dp_mix & DP_SRC_SOURCE; + uint32_t src_datatype = s->regs.dp_datatype & DP_SRC_DATATYPE; + + if (!s->host_data.active) { + return false; + } + if (src_source != DP_SRC_HOST) { + qemu_log_mask(LOG_GUEST_ERROR, + "host_data_blt: unsupported src_source %x\n", src_source); + return false; + } + if (src_datatype != SRC_MONO_FRGD_BKGD && src_datatype != SRC_MONO_FRGD && + src_datatype != SRC_COLOR) { + qemu_log_mask(LOG_GUEST_ERROR, + "host_data_blt: undefined src_datatype %x\n", + src_datatype); + return false; + } + + setup_2d_blt_ctx(s, &ctx); + + if (!ctx.left_to_right || !ctx.top_to_bottom) { + qemu_log_mask(LOG_UNIMP, + "host_data_blt: unsupported blit direction %c%c\n", + ctx.left_to_right ? '>' : '<', + ctx.top_to_bottom ? 'v' : '^'); + return false; + } + + bypp = ctx.bpp / 8; + + if (src_datatype == SRC_COLOR) { + pix_count = ATI_HOST_DATA_ACC_BITS / ctx.bpp; + memcpy(pix_buf, &s->host_data.acc[0], sizeof(s->host_data.acc)); + } else { + pix_count = ATI_HOST_DATA_ACC_BITS; + /* Expand monochrome bits to color pixels */ + idx = 0; + for (int word = 0; word < 4; word++) { + for (int byte = 0; byte < 4; byte++) { + uint8_t byte_val = s->host_data.acc[word] >> (byte * 8); + for (int i = 0; i < 8; i++) { + bool is_fg = byte_val & BIT(byte_pix_order ? i : 7 - i); + uint32_t color = is_fg ? fg : bg; + stn_he_p(&pix_buf[idx], bypp, color); + idx += bypp; + } + } + } + } + + /* Copy and then modify blit ctx for use in a chunked blit */ + chunk = ctx; + chunk.src_bits = pix_buf; + chunk.src.y = 0; + chunk.src_stride = ATI_HOST_DATA_ACC_BITS * bypp; + + /* Blit one scanline chunk at a time */ + row = s->host_data.row; + col = s->host_data.col; + idx = 0; + DPRINTF("blt %dpx @ row: %d, col: %d\n", pix_count, row, col); + while (idx < pix_count && row < ctx.dst.height) { + unsigned pix_in_scanline = MIN(pix_count - idx, + ctx.dst.width - col); + chunk.src.x = idx; + /* Build a rect for this scanline chunk */ + chunk.dst.x = ctx.dst.x + col; + chunk.dst.y = ctx.dst.y + row; + chunk.dst.width = pix_in_scanline; + chunk.dst.height = 1; + DPRINTF("blt %dpx span @ row: %d, col: %d to dst (%d,%d)\n", + pix_in_scanline, row, col, chunk.dst.x, chunk.dst.y); + if (ati_2d_do_blt(&chunk, s->use_pixman)) { + ati_set_dirty(&s->vga, &chunk); + } + idx += pix_in_scanline; + col += pix_in_scanline; + if (col >= ctx.dst.width) { + col = 0; + row += 1; + } + } + + /* Track state of the overall blit for use by the next flush */ + s->host_data.next = 0; + s->host_data.row = row; + s->host_data.col = col; + if (s->host_data.row >= ctx.dst.height) { + s->host_data.active = false; + } + + return s->host_data.active; +} + +void ati_host_data_finish(ATIVGAState *s) +{ + if (ati_host_data_flush(s)) { + qemu_log_mask(LOG_GUEST_ERROR, + "HOST_DATA blit ended before all data was written\n"); } + s->host_data.active = false; } diff --git a/hw/display/ati_dbg.c b/hw/display/ati_dbg.c index 3ffa7f35df93..5c799d540a97 100644 --- a/hw/display/ati_dbg.c +++ b/hw/display/ati_dbg.c @@ -252,6 +252,15 @@ static struct ati_regdesc ati_reg_names[] = { {"MC_SRC1_CNTL", 0x19D8}, {"TEX_CNTL", 0x1800}, {"RAGE128_MPP_TB_CONFIG", 0x01c0}, + {"HOST_DATA0", 0x17c0}, + {"HOST_DATA1", 0x17c4}, + {"HOST_DATA2", 0x17c8}, + {"HOST_DATA3", 0x17cc}, + {"HOST_DATA4", 0x17d0}, + {"HOST_DATA5", 0x17d4}, + {"HOST_DATA6", 0x17d8}, + {"HOST_DATA7", 0x17dc}, + {"HOST_DATA_LAST", 0x17e0}, {NULL, -1} }; diff --git a/hw/display/ati_int.h b/hw/display/ati_int.h index f5a47b82b08a..21b74511e089 100644 --- a/hw/display/ati_int.h +++ b/hw/display/ati_int.h @@ -10,8 +10,10 @@ #define ATI_INT_H #include "qemu/timer.h" +#include "qemu/units.h" #include "hw/pci/pci_device.h" #include "hw/i2c/bitbang_i2c.h" +#include "hw/display/i2c-ddc.h" #include "vga_int.h" #include "qom/object.h" @@ -29,6 +31,10 @@ /* Radeon RV100 (VE) */ #define PCI_DEVICE_ID_ATI_RADEON_QY 0x5159 +#define ATI_RAGE128_LINEAR_APER_SIZE (64 * MiB) +#define ATI_R100_LINEAR_APER_SIZE (128 * MiB) +#define ATI_HOST_DATA_ACC_BITS 128 + #define TYPE_ATI_VGA "ati-vga" OBJECT_DECLARE_SIMPLE_TYPE(ATIVGAState, ATI_VGA) @@ -74,16 +80,31 @@ typedef struct ATIVGARegs { uint32_t dp_brush_frgd_clr; uint32_t dp_src_frgd_clr; uint32_t dp_src_bkgd_clr; + uint16_t sc_top; + uint16_t sc_left; + uint16_t sc_bottom; + uint16_t sc_right; + uint16_t src_sc_bottom; + uint16_t src_sc_right; uint32_t dp_cntl; uint32_t dp_datatype; uint32_t dp_mix; uint32_t dp_write_mask; uint32_t default_offset; uint32_t default_pitch; + uint16_t default_sc_bottom; + uint16_t default_sc_right; uint32_t default_tile; - uint32_t default_sc_bottom_right; } ATIVGARegs; +typedef struct ATIHostDataState { + bool active; + uint32_t row; + uint32_t col; + uint32_t next; + uint32_t acc[4]; +} ATIHostDataState; + struct ATIVGAState { PCIDevice dev; VGACommonState vga; @@ -97,13 +118,18 @@ struct ATIVGAState { QEMUCursor *cursor; QEMUTimer vblank_timer; bitbang_i2c_interface bbi2c; + I2CDDCState i2cddc; + MemoryRegion linear_aper; MemoryRegion io; MemoryRegion mm; ATIVGARegs regs; + ATIHostDataState host_data; }; const char *ati_reg_name(int num); void ati_2d_blt(ATIVGAState *s); +bool ati_host_data_flush(ATIVGAState *s); +void ati_host_data_finish(ATIVGAState *s); #endif /* ATI_INT_H */ diff --git a/hw/display/ati_regs.h b/hw/display/ati_regs.h index d7127748ffee..b813fa119e9b 100644 --- a/hw/display/ati_regs.h +++ b/hw/display/ati_regs.h @@ -252,6 +252,15 @@ #define DP_T12_CNTL 0x178c #define DST_BRES_T1_LNTH 0x1790 #define DST_BRES_T2_LNTH 0x1794 +#define HOST_DATA0 0x17c0 +#define HOST_DATA1 0x17c4 +#define HOST_DATA2 0x17c8 +#define HOST_DATA3 0x17cc +#define HOST_DATA4 0x17d0 +#define HOST_DATA5 0x17d4 +#define HOST_DATA6 0x17d8 +#define HOST_DATA7 0x17dc +#define HOST_DATA_LAST 0x17e0 #define SCALE_SRC_HEIGHT_WIDTH 0x1994 #define SCALE_OFFSET_0 0x1998 #define SCALE_PITCH 0x199c @@ -386,6 +395,13 @@ #define DST_16BPP 0x00000004 #define DST_24BPP 0x00000005 #define DST_32BPP 0x00000006 +#define DP_DST_DATATYPE 0x0000000f +#define DP_BRUSH_DATATYPE 0x00000f00 +#define SRC_MONO_FRGD_BKGD 0x00000000 +#define SRC_MONO_FRGD 0x00010000 +#define SRC_COLOR 0x00030000 +#define DP_SRC_DATATYPE 0x00030000 +#define DP_BYTE_PIX_ORDER 0x40000000 #define BRUSH_SOLIDCOLOR 0x00000d00 @@ -394,6 +410,8 @@ #define GMC_DST_PITCH_OFFSET_CNTL 0x00000002 #define GMC_SRC_CLIP_DEFAULT 0x00000000 #define GMC_DST_CLIP_DEFAULT 0x00000000 +#define GMC_SRC_CLIPPING 0x00000004 +#define GMC_DST_CLIPPING 0x00000008 #define GMC_BRUSH_SOLIDCOLOR 0x000000d0 #define GMC_SRC_DSTCOLOR 0x00003000 #define GMC_BYTE_ORDER_MSB_TO_LSB 0x00000000 @@ -437,6 +455,8 @@ #define DP_SRC_RECT 0x00000200 #define DP_SRC_HOST 0x00000300 #define DP_SRC_HOST_BYTEALIGN 0x00000400 +#define DP_SRC_SOURCE 0x00000700 +#define DP_ROP3 0x00ff0000 /* LVDS_GEN_CNTL constants */ #define LVDS_BL_MOD_LEVEL_MASK 0x0000ff00 diff --git a/hw/display/cg3.c b/hw/display/cg3.c index 568d6048a637..61bdb0552e9c 100644 --- a/hw/display/cg3.c +++ b/hw/display/cg3.c @@ -282,8 +282,8 @@ static void cg3_initfn(Object *obj) SysBusDevice *sbd = SYS_BUS_DEVICE(obj); CG3State *s = CG3(obj); - memory_region_init_rom_nomigrate(&s->rom, obj, "cg3.prom", - FCODE_MAX_ROM_SIZE, &error_fatal); + memory_region_init_rom(&s->rom, obj, "cg3.prom", FCODE_MAX_ROM_SIZE, + &error_fatal); sysbus_init_mmio(sbd, &s->rom); memory_region_init_io(&s->reg, obj, &cg3_reg_ops, s, "cg3.reg", @@ -299,7 +299,6 @@ static void cg3_realizefn(DeviceState *dev, Error **errp) char *fcode_filename; /* FCode ROM */ - vmstate_register_ram_global(&s->rom); fcode_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, CG3_ROM_FILE); if (fcode_filename) { ret = load_image_mr(fcode_filename, &s->rom); diff --git a/hw/display/cirrus_vga.c b/hw/display/cirrus_vga.c index 37228ff1345a..629b34fc68de 100644 --- a/hw/display/cirrus_vga.c +++ b/hw/display/cirrus_vga.c @@ -2987,8 +2987,6 @@ static const Property pci_vga_cirrus_properties[] = { cirrus_vga.vga.vram_size_mb, 4), DEFINE_PROP_BOOL("blitter", struct PCICirrusVGAState, cirrus_vga.enable_blitter, true), - DEFINE_PROP_BOOL("global-vmstate", struct PCICirrusVGAState, - cirrus_vga.vga.global_vmstate, false), }; static void cirrus_vga_class_init(ObjectClass *klass, const void *data) diff --git a/hw/display/macfb.c b/hw/display/macfb.c index 388f8de5070a..b8115c2be138 100644 --- a/hw/display/macfb.c +++ b/hw/display/macfb.c @@ -82,7 +82,7 @@ typedef struct MacFbSense { uint8_t ext_sense; } MacFbSense; -static MacFbSense macfb_sense_table[] = { +static const MacFbSense macfb_sense_table[] = { { MACFB_DISPLAY_APPLE_21_COLOR, 0x0, 0 }, { MACFB_DISPLAY_APPLE_PORTRAIT, 0x1, 0 }, { MACFB_DISPLAY_APPLE_12_RGB, 0x2, 0 }, @@ -100,7 +100,7 @@ static MacFbSense macfb_sense_table[] = { { MACFB_DISPLAY_SVGA, 0x7, 0x5 }, }; -static MacFbMode macfb_mode_table[] = { +static const MacFbMode macfb_mode_table[] = { { MACFB_DISPLAY_VGA, 1, 0x100, 0x71e, 640, 480, 0x400, 0x1000 }, { MACFB_DISPLAY_VGA, 2, 0x100, 0x70e, 640, 480, 0x400, 0x1000 }, { MACFB_DISPLAY_VGA, 4, 0x100, 0x706, 640, 480, 0x400, 0x1000 }, @@ -342,7 +342,7 @@ static void macfb_invalidate_display(void *opaque) static uint32_t macfb_sense_read(MacfbState *s) { - MacFbSense *macfb_sense; + const MacFbSense *macfb_sense; uint8_t sense; assert(s->type < ARRAY_SIZE(macfb_sense_table)); @@ -397,7 +397,7 @@ static void macfb_update_mode(MacfbState *s) static void macfb_mode_write(MacfbState *s) { - MacFbMode *macfb_mode; + const MacFbMode *macfb_mode; int i; for (i = 0; i < ARRAY_SIZE(macfb_mode_table); i++) { @@ -418,11 +418,11 @@ static void macfb_mode_write(MacfbState *s) } } -static MacFbMode *macfb_find_mode(MacfbDisplayType display_type, +static const MacFbMode *macfb_find_mode(MacfbDisplayType display_type, uint16_t width, uint16_t height, uint8_t depth) { - MacFbMode *macfb_mode; + const MacFbMode *macfb_mode; int i; for (i = 0; i < ARRAY_SIZE(macfb_mode_table); i++) { @@ -440,7 +440,7 @@ static MacFbMode *macfb_find_mode(MacfbDisplayType display_type, static gchar *macfb_mode_list(void) { GString *list = g_string_new(""); - MacFbMode *macfb_mode; + const MacFbMode *macfb_mode; int i; for (i = 0; i < ARRAY_SIZE(macfb_mode_table); i++) { @@ -645,6 +645,10 @@ static bool macfb_common_realize(DeviceState *dev, MacfbState *s, Error **errp) { DisplaySurface *surface; + if (s->width == 1152 && s->height == 870) { + s->type = MACFB_DISPLAY_APPLE_21_COLOR; + } + s->mode = macfb_find_mode(s->type, s->width, s->height, s->depth); if (!s->mode) { gchar *list; diff --git a/hw/display/qxl.c b/hw/display/qxl.c index 0551b38230bc..3d4b56355681 100644 --- a/hw/display/qxl.c +++ b/hw/display/qxl.c @@ -2476,7 +2476,6 @@ static const Property qxl_properties[] = { DEFINE_PROP_UINT16("max_outputs", PCIQXLDevice, max_outputs, 0), DEFINE_PROP_UINT32("xres", PCIQXLDevice, xres, 0), DEFINE_PROP_UINT32("yres", PCIQXLDevice, yres, 0), - DEFINE_PROP_BOOL("global-vmstate", PCIQXLDevice, vga.global_vmstate, false), }; static void qxl_pci_class_init(ObjectClass *klass, const void *data) diff --git a/hw/display/tcx.c b/hw/display/tcx.c index 36cad82abdf8..c8a4ac21caf2 100644 --- a/hw/display/tcx.c +++ b/hw/display/tcx.c @@ -756,8 +756,8 @@ static void tcx_initfn(Object *obj) SysBusDevice *sbd = SYS_BUS_DEVICE(obj); TCXState *s = TCX(obj); - memory_region_init_rom_nomigrate(&s->rom, obj, "tcx.prom", - FCODE_MAX_ROM_SIZE, &error_fatal); + memory_region_init_rom(&s->rom, obj, "tcx.prom", FCODE_MAX_ROM_SIZE, + &error_fatal); sysbus_init_mmio(sbd, &s->rom); /* 2/STIP : Stippler */ @@ -815,14 +815,12 @@ static void tcx_realizefn(DeviceState *dev, Error **errp) uint8_t *vram_base; char *fcode_filename; - memory_region_init_ram_nomigrate(&s->vram_mem, OBJECT(s), "tcx.vram", + memory_region_init_ram(&s->vram_mem, OBJECT(s), "tcx.vram", s->vram_size * (1 + 4 + 4), &error_fatal); - vmstate_register_ram_global(&s->vram_mem); memory_region_set_log(&s->vram_mem, true, DIRTY_MEMORY_VGA); vram_base = memory_region_get_ram_ptr(&s->vram_mem); /* 10/ROM : FCode ROM */ - vmstate_register_ram_global(&s->rom); fcode_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, TCX_ROM_FILE); if (fcode_filename) { ret = load_image_mr(fcode_filename, &s->rom); diff --git a/hw/display/trace-events b/hw/display/trace-events index e323a82cff24..4bfc457fbac1 100644 --- a/hw/display/trace-events +++ b/hw/display/trace-events @@ -39,7 +39,7 @@ virtio_gpu_cmd_res_create_2d(uint32_t res, uint32_t fmt, uint32_t w, uint32_t h) virtio_gpu_cmd_res_create_3d(uint32_t res, uint32_t fmt, uint32_t w, uint32_t h, uint32_t d) "res 0x%x, fmt 0x%x, w %d, h %d, d %d" virtio_gpu_cmd_res_create_blob(uint32_t res, uint64_t size) "res 0x%x, size %" PRId64 virtio_gpu_cmd_res_map_blob(uint32_t res, void *vmr, void *mr) "res 0x%x, vmr %p, mr %p" -virtio_gpu_cmd_res_unmap_blob(uint32_t res, void *mr, bool finish_unmapping) "res 0x%x, mr %p, finish_unmapping %d" +virtio_gpu_cmd_res_unmap_blob(uint32_t res, void *mr, int mapping_state) "res 0x%x, mr %p, mapping_state %d" virtio_gpu_cmd_res_unref(uint32_t res) "res 0x%x" virtio_gpu_cmd_res_back_attach(uint32_t res) "res 0x%x" virtio_gpu_cmd_res_back_detach(uint32_t res) "res 0x%x" diff --git a/hw/display/vga-pci.c b/hw/display/vga-pci.c index d0f9de1ab359..4e68dd57a17c 100644 --- a/hw/display/vga-pci.c +++ b/hw/display/vga-pci.c @@ -338,7 +338,6 @@ static const Property vga_pci_properties[] = { DEFINE_PROP_BIT("edid", PCIVGAState, flags, PCI_VGA_FLAG_ENABLE_EDID, true), DEFINE_EDID_PROPERTIES(PCIVGAState, edid_info), - DEFINE_PROP_BOOL("global-vmstate", PCIVGAState, vga.global_vmstate, false), }; static const Property secondary_pci_properties[] = { diff --git a/hw/display/vga.c b/hw/display/vga.c index 59a65cbbff56..ee7d97b5c21c 100644 --- a/hw/display/vga.c +++ b/hw/display/vga.c @@ -2235,8 +2235,8 @@ bool vga_common_init(VGACommonState *s, Object *obj, Error **errp) return false; } - memory_region_init_ram_nomigrate(&s->vram, obj, "vga.vram", s->vram_size, - &local_err); + memory_region_init_ram_flags_nomigrate(&s->vram, obj, "vga.vram", + s->vram_size, 0, &local_err); if (local_err) { error_propagate(errp, local_err); return false; diff --git a/hw/display/virtio-gpu-base.c b/hw/display/virtio-gpu-base.c index 7269477a1c80..cb76302e2d85 100644 --- a/hw/display/virtio-gpu-base.c +++ b/hw/display/virtio-gpu-base.c @@ -233,6 +233,22 @@ virtio_gpu_base_device_realize(DeviceState *qdev, g->req_state[0].width = g->conf.xres; g->req_state[0].height = g->conf.yres; + for (output_idx = 0, node = g->conf.outputs; + node && output_idx < g->conf.max_outputs; + output_idx++, node = node->next) { + if (node->value->has_xres != node->value->has_yres) { + error_setg(errp, + "must set both outputs[%zd].xres and outputs[%zd].yres", + output_idx, output_idx); + return false; + } + if (node->value->has_xres && node->value->has_yres) { + g->enabled_output_bitmask |= (1 << output_idx); + g->req_state[output_idx].width = node->value->xres; + g->req_state[output_idx].height = node->value->yres; + } + } + g->hw_ops = &virtio_gpu_ops; for (i = 0; i < g->conf.max_outputs; i++) { g->scanout[i].con = diff --git a/hw/display/virtio-gpu-gl.c b/hw/display/virtio-gpu-gl.c index b98ef2ef9877..2b7a41c46643 100644 --- a/hw/display/virtio-gpu-gl.c +++ b/hw/display/virtio-gpu-gl.c @@ -13,6 +13,7 @@ #include "qemu/osdep.h" #include "qemu/iov.h" +#include "qemu/mmap-alloc.h" #include "qemu/module.h" #include "qemu/error-report.h" #include "qapi/error.h" @@ -63,29 +64,14 @@ static void virtio_gpu_gl_flushed(VirtIOGPUBase *b) static void virtio_gpu_gl_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) { VirtIOGPU *g = VIRTIO_GPU(vdev); - VirtIOGPUGL *gl = VIRTIO_GPU_GL(vdev); struct virtio_gpu_ctrl_command *cmd; if (!virtio_queue_ready(vq)) { return; } - switch (gl->renderer_state) { - case RS_RESET: - virtio_gpu_virgl_reset(g); - /* fallthrough */ - case RS_START: - if (virtio_gpu_virgl_init(g)) { - gl->renderer_state = RS_INIT_FAILED; - return; - } - - gl->renderer_state = RS_INITED; - break; - case RS_INIT_FAILED: + if (!virtio_gpu_virgl_update_render_state(g)) { return; - case RS_INITED: - break; } cmd = virtqueue_pop(vq, sizeof(struct virtio_gpu_ctrl_command)); @@ -121,7 +107,12 @@ static void virtio_gpu_gl_reset(VirtIODevice *vdev) static void virtio_gpu_gl_device_realize(DeviceState *qdev, Error **errp) { ERRP_GUARD(); - VirtIOGPU *g = VIRTIO_GPU(qdev); + VirtIOGPUBase *b = VIRTIO_GPU_BASE(qdev); + VirtIOGPU *g = VIRTIO_GPU(b); +#if !defined(CONFIG_WIN32) + VirtIOGPUGL *gl = VIRTIO_GPU_GL(g); + void *map; +#endif #if HOST_BIG_ENDIAN error_setg(errp, "virgl is not supported on bigendian platforms"); @@ -151,6 +142,24 @@ static void virtio_gpu_gl_device_realize(DeviceState *qdev, Error **errp) g->parent_obj.conf.flags |= 1 << VIRTIO_GPU_FLAG_CONTEXT_INIT_ENABLED; #endif +#if !defined(CONFIG_WIN32) + if (virtio_gpu_hostmem_enabled(b->conf)) { + map = qemu_ram_mmap(-1, b->conf.hostmem, qemu_real_host_page_size(), + 0, 0); + if (map == MAP_FAILED) { + error_setg_errno(errp, errno, + "virgl hostmem region could not be initialized"); + return; + } + + gl->hostmem_mmap = map; + memory_region_init_ram_ptr(&gl->hostmem_background, NULL, + "hostmem-background", b->conf.hostmem, + gl->hostmem_mmap); + memory_region_add_subregion(&b->hostmem, 0, &gl->hostmem_background); + } +#endif + virtio_gpu_device_realize(qdev, errp); } @@ -159,6 +168,8 @@ static const Property virtio_gpu_gl_properties[] = { VIRTIO_GPU_FLAG_STATS_ENABLED, false), DEFINE_PROP_BIT("venus", VirtIOGPU, parent_obj.conf.flags, VIRTIO_GPU_FLAG_VENUS_ENABLED, false), + DEFINE_PROP_BIT("drm_native_context", VirtIOGPU, parent_obj.conf.flags, + VIRTIO_GPU_FLAG_DRM_ENABLED, false), }; static void virtio_gpu_gl_device_unrealize(DeviceState *qdev) @@ -169,6 +180,11 @@ static void virtio_gpu_gl_device_unrealize(DeviceState *qdev) if (gl->renderer_state >= RS_INITED) { #if VIRGL_VERSION_MAJOR >= 1 qemu_bh_delete(gl->cmdq_resume_bh); + + if (gl->async_fence_bh) { + virtio_gpu_virgl_reset_async_fences(g); + qemu_bh_delete(gl->async_fence_bh); + } #endif if (virtio_gpu_stats_enabled(g->parent_obj.conf)) { timer_free(gl->print_stats); @@ -180,6 +196,17 @@ static void virtio_gpu_gl_device_unrealize(DeviceState *qdev) gl->renderer_state = RS_START; g_array_unref(g->capset_ids); + + /* + * It is not guaranteed that the memory region will be finalized + * immediately with memory_region_del_subregion(), there can be + * a remaining reference to gl->hostmem_mmap. VirtIO-GPU is not + * hotpluggable, hence no need to worry about the leaked mapping. + * + * The memory_region_del_subregion(gl->hostmem_background) is unnecessary + * because b->hostmem and gl->hostmem_background belong to the same + * device and will be gone at the same time. + */ } static void virtio_gpu_gl_class_init(ObjectClass *klass, const void *data) @@ -194,6 +221,7 @@ static void virtio_gpu_gl_class_init(ObjectClass *klass, const void *data) vgc->process_cmd = virtio_gpu_virgl_process_cmd; vgc->update_cursor_data = virtio_gpu_gl_update_cursor_data; + vgc->resource_destroy = virtio_gpu_virgl_resource_destroy; vdc->realize = virtio_gpu_gl_device_realize; vdc->unrealize = virtio_gpu_gl_device_unrealize; vdc->reset = virtio_gpu_gl_reset; diff --git a/hw/display/virtio-gpu-udmabuf-stubs.c b/hw/display/virtio-gpu-udmabuf-stubs.c index f692e1351034..85d03935a332 100644 --- a/hw/display/virtio-gpu-udmabuf-stubs.c +++ b/hw/display/virtio-gpu-udmabuf-stubs.c @@ -12,7 +12,7 @@ void virtio_gpu_init_udmabuf(struct virtio_gpu_simple_resource *res) /* nothing (stub) */ } -void virtio_gpu_fini_udmabuf(struct virtio_gpu_simple_resource *res) +void virtio_gpu_fini_udmabuf(VirtIOGPU *g, struct virtio_gpu_simple_resource *res) { /* nothing (stub) */ } diff --git a/hw/display/virtio-gpu-udmabuf.c b/hw/display/virtio-gpu-udmabuf.c index d804f321aa33..74b6a7766af6 100644 --- a/hw/display/virtio-gpu-udmabuf.c +++ b/hw/display/virtio-gpu-udmabuf.c @@ -151,13 +151,6 @@ void virtio_gpu_init_udmabuf(struct virtio_gpu_simple_resource *res) res->blob = pdata; } -void virtio_gpu_fini_udmabuf(struct virtio_gpu_simple_resource *res) -{ - if (res->remapped) { - virtio_gpu_destroy_udmabuf(res); - } -} - static void virtio_gpu_free_dmabuf(VirtIOGPU *g, VGPUDMABuf *dmabuf) { struct virtio_gpu_scanout *scanout; @@ -169,6 +162,26 @@ static void virtio_gpu_free_dmabuf(VirtIOGPU *g, VGPUDMABuf *dmabuf) g_free(dmabuf); } +void virtio_gpu_fini_udmabuf(VirtIOGPU *g, struct virtio_gpu_simple_resource *res) +{ + int max_outputs = g->parent_obj.conf.max_outputs; + int i; + + for (i = 0; i < max_outputs; i++) { + VGPUDMABuf *dmabuf = g->dmabuf.primary[i]; + + if (dmabuf && + qemu_dmabuf_get_num_planes(dmabuf->buf) > 0 && + qemu_dmabuf_get_fds(dmabuf->buf, NULL)[0] == res->dmabuf_fd && + res->dmabuf_fd != -1) { + qemu_dmabuf_close(dmabuf->buf); + res->dmabuf_fd = -1; + } + } + + virtio_gpu_destroy_udmabuf(res); +} + static VGPUDMABuf *virtio_gpu_create_dmabuf(VirtIOGPU *g, uint32_t scanout_id, diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c index ecf8494f3676..b7a2d160ddd8 100644 --- a/hw/display/virtio-gpu-virgl.c +++ b/hw/display/virtio-gpu-virgl.c @@ -24,9 +24,30 @@ #include +/* + * VIRGL_CHECK_VERSION available since libvirglrenderer 1.0.1 and was fixed + * in 1.1.0. Undefine bugged version of the macro and provide our own. + */ +#if defined(VIRGL_CHECK_VERSION) && \ + VIRGL_VERSION_MAJOR == 1 && VIRGL_VERSION_MINOR < 1 +#undef VIRGL_CHECK_VERSION +#endif + +#ifndef VIRGL_CHECK_VERSION +#define VIRGL_CHECK_VERSION(major, minor, micro) \ + (VIRGL_VERSION_MAJOR > (major) || \ + VIRGL_VERSION_MAJOR == (major) && VIRGL_VERSION_MINOR > (minor) || \ + VIRGL_VERSION_MAJOR == (major) && VIRGL_VERSION_MINOR == (minor) && \ + VIRGL_VERSION_MICRO >= (micro)) +#endif + +#define VIRGL_HAS_MAP_FIXED \ + (VIRGL_CHECK_VERSION(1, 3, 0) && !IS_ENABLED(CONFIG_WIN32)) + struct virtio_gpu_virgl_resource { struct virtio_gpu_simple_resource base; MemoryRegion *mr; + void *map_fixed; }; static struct virtio_gpu_virgl_resource * @@ -51,12 +72,24 @@ virgl_get_egl_display(G_GNUC_UNUSED void *cookie) #endif #if VIRGL_VERSION_MAJOR >= 1 +enum virtio_gpu_virgl_hostmem_region_mapping_state { + VIRTIO_GPU_MR_MAPPED, + VIRTIO_GPU_MR_UNMAP_STARTED, + VIRTIO_GPU_MR_UNMAP_COMPLETED, +}; + struct virtio_gpu_virgl_hostmem_region { + Object parent_obj; MemoryRegion mr; struct VirtIOGPU *g; - bool finish_unmapping; + enum virtio_gpu_virgl_hostmem_region_mapping_state mapping_state; }; +#define TYPE_VIRTIO_GPU_VIRGL_HOSTMEM_REGION "virtio-gpu-virgl-hostmem-region" + +OBJECT_DECLARE_SIMPLE_TYPE(virtio_gpu_virgl_hostmem_region, + VIRTIO_GPU_VIRGL_HOSTMEM_REGION) + static struct virtio_gpu_virgl_hostmem_region * to_hostmem_region(MemoryRegion *mr) { @@ -67,18 +100,30 @@ static void virtio_gpu_virgl_resume_cmdq_bh(void *opaque) { VirtIOGPU *g = opaque; + if (!virtio_gpu_virgl_update_render_state(g)) { + return; + } + virtio_gpu_process_cmdq(g); } -static void virtio_gpu_virgl_hostmem_region_free(void *obj) +/* + * MR could outlive the resource if MR's reference is held outside of + * virtio-gpu. In order to prevent unmapping resource while MR is alive, + * and thus, making the data pointer invalid, we will block virtio-gpu + * command processing until MR is fully unreferenced and freed. + */ +static void virtio_gpu_virgl_hostmem_region_finalize(Object *obj) { - MemoryRegion *mr = MEMORY_REGION(obj); - struct virtio_gpu_virgl_hostmem_region *vmr; + struct virtio_gpu_virgl_hostmem_region *vmr = VIRTIO_GPU_VIRGL_HOSTMEM_REGION(obj); VirtIOGPUBase *b; VirtIOGPUGL *gl; - vmr = to_hostmem_region(mr); - vmr->finish_unmapping = true; + if (!vmr->g) { + return; + } + + vmr->mapping_state = VIRTIO_GPU_MR_UNMAP_COMPLETED; b = VIRTIO_GPU_BASE(vmr->g); b->renderer_blocked--; @@ -92,13 +137,31 @@ static void virtio_gpu_virgl_hostmem_region_free(void *obj) qemu_bh_schedule(gl->cmdq_resume_bh); } +static const TypeInfo virtio_gpu_virgl_hostmem_region_info = { + .parent = TYPE_OBJECT, + .name = TYPE_VIRTIO_GPU_VIRGL_HOSTMEM_REGION, + .instance_size = sizeof(struct virtio_gpu_virgl_hostmem_region), + .instance_finalize = virtio_gpu_virgl_hostmem_region_finalize +}; + +static void virtio_gpu_virgl_types(void) +{ + type_register_static(&virtio_gpu_virgl_hostmem_region_info); +} + +type_init(virtio_gpu_virgl_types) + static int virtio_gpu_virgl_map_resource_blob(VirtIOGPU *g, struct virtio_gpu_virgl_resource *res, uint64_t offset) { + g_autofree char *name = NULL; struct virtio_gpu_virgl_hostmem_region *vmr; VirtIOGPUBase *b = VIRTIO_GPU_BASE(g); +#if VIRGL_HAS_MAP_FIXED + VirtIOGPUGL *gl = VIRTIO_GPU_GL(g); +#endif MemoryRegion *mr; uint64_t size; void *data; @@ -109,6 +172,41 @@ virtio_gpu_virgl_map_resource_blob(VirtIOGPU *g, return -EOPNOTSUPP; } +#if VIRGL_HAS_MAP_FIXED + /* + * virgl_renderer_resource_map_fixed() allows to create multiple + * mappings of the same resource, while virgl_renderer_resource_map() + * not. Don't allow mapping same resource twice. + */ + if (res->map_fixed || res->mr) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: failed to map(fixed) virgl resource: already mapped\n", + __func__); + return -EBUSY; + } + + ret = virgl_renderer_resource_map_fixed(res->base.resource_id, + gl->hostmem_mmap + offset); + switch (ret) { + case 0: + res->map_fixed = gl->hostmem_mmap + offset; + return 0; + + case -EOPNOTSUPP: + /* + * MAP_FIXED is unsupported by this resource. + * Mapping falls back to a blob subregion method in that case. + */ + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: failed to map(fixed) virgl resource: %s\n", + __func__, strerror(-ret)); + return ret; + } +#endif + ret = virgl_renderer_resource_map(res->base.resource_id, &data, &size); if (ret) { qemu_log_mask(LOG_GUEST_ERROR, "%s: failed to map virgl resource: %s\n", @@ -117,20 +215,15 @@ virtio_gpu_virgl_map_resource_blob(VirtIOGPU *g, } vmr = g_new0(struct virtio_gpu_virgl_hostmem_region, 1); + name = g_strdup_printf("blob[%" PRIu32 "]", res->base.resource_id); + object_initialize_child(OBJECT(g), name, vmr, + TYPE_VIRTIO_GPU_VIRGL_HOSTMEM_REGION); vmr->g = g; + vmr->mapping_state = VIRTIO_GPU_MR_MAPPED; mr = &vmr->mr; - memory_region_init_ram_ptr(mr, OBJECT(mr), NULL, size, data); - memory_region_add_subregion(&b->hostmem, offset, mr); - memory_region_set_enabled(mr, true); - - /* - * MR could outlive the resource if MR's reference is held outside of - * virtio-gpu. In order to prevent unmapping resource while MR is alive, - * and thus, making the data pointer invalid, we will block virtio-gpu - * command processing until MR is fully unreferenced and freed. - */ - OBJECT(mr)->free = virtio_gpu_virgl_hostmem_region_free; + memory_region_init_ram_ptr(mr, OBJECT(vmr), "mr", size, data); + memory_region_add_subregion_overlap(&b->hostmem, offset, mr, 1); res->mr = mr; @@ -149,13 +242,29 @@ virtio_gpu_virgl_unmap_resource_blob(VirtIOGPU *g, MemoryRegion *mr = res->mr; int ret; +#if VIRGL_HAS_MAP_FIXED + if (res->map_fixed) { + if (mmap(res->map_fixed, res->base.blob_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, + -1, 0) == MAP_FAILED) { + ret = -errno; + error_report("%s: failed to unmap(fixed) virgl resource: %s", + __func__, strerror(-ret)); + return ret; + } + + res->map_fixed = NULL; + } +#endif + if (!mr) { return 0; } vmr = to_hostmem_region(res->mr); - trace_virtio_gpu_cmd_res_unmap_blob(res->base.resource_id, mr, vmr->finish_unmapping); + trace_virtio_gpu_cmd_res_unmap_blob(res->base.resource_id, mr, + vmr->mapping_state); /* * Perform async unmapping in 3 steps: @@ -163,10 +272,11 @@ virtio_gpu_virgl_unmap_resource_blob(VirtIOGPU *g, * 1. Begin async unmapping with memory_region_del_subregion() * and suspend/block cmd processing. * 2. Wait for res->mr to be freed and cmd processing resumed - * asynchronously by virtio_gpu_virgl_hostmem_region_free(). + * asynchronously by virtio_gpu_virgl_hostmem_region_finalize(). * 3. Finish the unmapping with final virgl_renderer_resource_unmap(). */ - if (vmr->finish_unmapping) { + switch (vmr->mapping_state) { + case VIRTIO_GPU_MR_UNMAP_COMPLETED: res->mr = NULL; g_free(vmr); @@ -177,16 +287,22 @@ virtio_gpu_virgl_unmap_resource_blob(VirtIOGPU *g, __func__, strerror(-ret)); return ret; } - } else { - *cmd_suspended = true; + break; + case VIRTIO_GPU_MR_MAPPED: /* render will be unblocked once MR is freed */ b->renderer_blocked++; + vmr->mapping_state = VIRTIO_GPU_MR_UNMAP_STARTED; + /* memory region owns self res->mr object and frees it by itself */ - memory_region_set_enabled(mr, false); memory_region_del_subregion(&b->hostmem, mr); - object_unref(OBJECT(mr)); + object_unparent(OBJECT(vmr)); + + /* Fallthrough */ + case VIRTIO_GPU_MR_UNMAP_STARTED: + *cmd_suspended = true; + break; } return 0; @@ -289,14 +405,46 @@ static void virgl_cmd_create_resource_3d(VirtIOGPU *g, virgl_renderer_resource_create(&args, NULL, 0); } +static int +virtio_gpu_virgl_resource_unref(VirtIOGPU *g, + struct virtio_gpu_virgl_resource *res, + bool *suspended) +{ + struct iovec *res_iovs = NULL; + int num_iovs = 0; +#if VIRGL_VERSION_MAJOR >= 1 + int ret; + + ret = virtio_gpu_virgl_unmap_resource_blob(g, res, suspended); + if (ret) { + return ret; + } + if (*suspended) { + return 0; + } +#endif + + virgl_renderer_resource_detach_iov(res->base.resource_id, + &res_iovs, + &num_iovs); + if (res_iovs != NULL && num_iovs != 0) { + virtio_gpu_cleanup_mapping_iov(g, res_iovs, num_iovs); + } + virgl_renderer_resource_unref(res->base.resource_id); + + QTAILQ_REMOVE(&g->reslist, &res->base, next); + + g_free(res); + + return 0; +} + static void virgl_cmd_resource_unref(VirtIOGPU *g, struct virtio_gpu_ctrl_command *cmd, bool *cmd_suspended) { struct virtio_gpu_resource_unref unref; struct virtio_gpu_virgl_resource *res; - struct iovec *res_iovs = NULL; - int num_iovs = 0; VIRTIO_GPU_FILL_CMD(unref); trace_virtio_gpu_cmd_res_unref(unref.resource_id); @@ -309,27 +457,21 @@ static void virgl_cmd_resource_unref(VirtIOGPU *g, return; } -#if VIRGL_VERSION_MAJOR >= 1 - if (virtio_gpu_virgl_unmap_resource_blob(g, res, cmd_suspended)) { - cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; - return; - } - if (*cmd_suspended) { - return; - } -#endif + virtio_gpu_virgl_resource_unref(g, res, cmd_suspended); +} - virgl_renderer_resource_detach_iov(unref.resource_id, - &res_iovs, - &num_iovs); - if (res_iovs != NULL && num_iovs != 0) { - virtio_gpu_cleanup_mapping_iov(g, res_iovs, num_iovs); - } - virgl_renderer_resource_unref(unref.resource_id); +void virtio_gpu_virgl_resource_destroy(VirtIOGPU *g, + struct virtio_gpu_simple_resource *base, + Error **errp) +{ + struct virtio_gpu_virgl_resource *res; + bool suspended = false; - QTAILQ_REMOVE(&g->reslist, &res->base, next); + res = container_of(base, struct virtio_gpu_virgl_resource, base); - g_free(res); + if (virtio_gpu_virgl_resource_unref(g, res, &suspended)) { + error_setg(errp, "failed to destroy virgl resource"); + } } static void virgl_cmd_context_create(VirtIOGPU *g, @@ -752,6 +894,7 @@ static void virgl_cmd_resource_map_blob(VirtIOGPU *g, struct virtio_gpu_resource_map_blob mblob; struct virtio_gpu_virgl_resource *res; struct virtio_gpu_resp_map_info resp; + VirtIOGPUBase *b = VIRTIO_GPU_BASE(g); int ret; VIRTIO_GPU_FILL_CMD(mblob); @@ -765,6 +908,15 @@ static void virgl_cmd_resource_map_blob(VirtIOGPU *g, return; } + if (mblob.offset + res->base.blob_size > b->conf.hostmem || + mblob.offset + res->base.blob_size < mblob.offset) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: failed to map virgl resource: invalid offset\n", + __func__); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; + return; + } + ret = virtio_gpu_virgl_map_resource_blob(g, res, mblob.offset); if (ret) { cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; @@ -876,6 +1028,7 @@ void virtio_gpu_virgl_process_cmd(VirtIOGPU *g, struct virtio_gpu_ctrl_command *cmd) { bool cmd_suspended = false; + int ret; VIRTIO_GPU_FILL_CMD(cmd->cmd_hdr); @@ -976,14 +1129,30 @@ void virtio_gpu_virgl_process_cmd(VirtIOGPU *g, trace_virtio_gpu_fence_ctrl(cmd->cmd_hdr.fence_id, cmd->cmd_hdr.type); #if VIRGL_VERSION_MAJOR >= 1 if (cmd->cmd_hdr.flags & VIRTIO_GPU_FLAG_INFO_RING_IDX) { - virgl_renderer_context_create_fence(cmd->cmd_hdr.ctx_id, - VIRGL_RENDERER_FENCE_FLAG_MERGEABLE, - cmd->cmd_hdr.ring_idx, - cmd->cmd_hdr.fence_id); + const uint32_t flags = VIRGL_RENDERER_FENCE_FLAG_MERGEABLE; + + ret = virgl_renderer_context_create_fence(cmd->cmd_hdr.ctx_id, flags, + cmd->cmd_hdr.ring_idx, + cmd->cmd_hdr.fence_id); + if (ret) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: virgl_renderer_context_create_fence error: %s", + __func__, strerror(-ret)); + } return; } #endif - virgl_renderer_create_fence(cmd->cmd_hdr.fence_id, cmd->cmd_hdr.type); + + /* + * Unlike other virglrenderer functions, this one returns a positive + * error code. + */ + ret = virgl_renderer_create_fence(cmd->cmd_hdr.fence_id, 0); + if (ret) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: virgl_renderer_create_fence error: %s", + __func__, strerror(ret)); + } } static void virgl_write_fence(void *opaque, uint32_t fence) @@ -1038,6 +1207,103 @@ static void virgl_write_context_fence(void *opaque, uint32_t ctx_id, } #endif +void virtio_gpu_virgl_reset_async_fences(VirtIOGPU *g) +{ + struct virtio_gpu_virgl_context_fence *f; + VirtIOGPUGL *gl = VIRTIO_GPU_GL(g); + + while (!QSLIST_EMPTY(&gl->async_fenceq)) { + f = QSLIST_FIRST(&gl->async_fenceq); + + QSLIST_REMOVE_HEAD(&gl->async_fenceq, next); + + g_free(f); + } +} + +#if VIRGL_CHECK_VERSION(1, 1, 2) +static void virtio_gpu_virgl_async_fence_bh(void *opaque) +{ + QSLIST_HEAD(, virtio_gpu_virgl_context_fence) async_fenceq; + struct virtio_gpu_ctrl_command *cmd, *tmp; + struct virtio_gpu_virgl_context_fence *f; + VirtIOGPU *g = opaque; + VirtIOGPUGL *gl = VIRTIO_GPU_GL(g); + + if (gl->renderer_state != RS_INITED) { + return; + } + + QSLIST_MOVE_ATOMIC(&async_fenceq, &gl->async_fenceq); + + while (!QSLIST_EMPTY(&async_fenceq)) { + f = QSLIST_FIRST(&async_fenceq); + + QSLIST_REMOVE_HEAD(&async_fenceq, next); + + QTAILQ_FOREACH_SAFE(cmd, &g->fenceq, next, tmp) { + /* + * the guest can end up emitting fences out of order + * so we should check all fenced cmds not just the first one. + */ + if (cmd->cmd_hdr.fence_id > f->fence_id) { + continue; + } + if (cmd->cmd_hdr.flags & VIRTIO_GPU_FLAG_INFO_RING_IDX) { + if (cmd->cmd_hdr.ring_idx != f->ring_idx) { + continue; + } + if (cmd->cmd_hdr.ctx_id != f->ctx_id) { + continue; + } + } + virtio_gpu_ctrl_response_nodata(g, cmd, VIRTIO_GPU_RESP_OK_NODATA); + QTAILQ_REMOVE(&g->fenceq, cmd, next); + g_free(cmd); + } + + trace_virtio_gpu_fence_resp(f->fence_id); + g_free(f); + g->inflight--; + if (virtio_gpu_stats_enabled(g->parent_obj.conf)) { + trace_virtio_gpu_dec_inflight_fences(g->inflight); + } + } +} + +static void +virtio_gpu_virgl_push_async_fence(VirtIOGPU *g, uint32_t ctx_id, + uint32_t ring_idx, uint64_t fence_id) +{ + struct virtio_gpu_virgl_context_fence *f; + VirtIOGPUGL *gl = VIRTIO_GPU_GL(g); + + f = g_new(struct virtio_gpu_virgl_context_fence, 1); + f->ctx_id = ctx_id; + f->ring_idx = ring_idx; + f->fence_id = fence_id; + + QSLIST_INSERT_HEAD_ATOMIC(&gl->async_fenceq, f, next); + + qemu_bh_schedule(gl->async_fence_bh); +} + +static void virgl_write_async_fence(void *opaque, uint32_t fence) +{ + VirtIOGPU *g = opaque; + + virtio_gpu_virgl_push_async_fence(g, 0, UINT32_MAX, fence); +} + +static void virgl_write_async_context_fence(void *opaque, uint32_t ctx_id, + uint32_t ring_idx, uint64_t fence) +{ + VirtIOGPU *g = opaque; + + virtio_gpu_virgl_push_async_fence(g, ctx_id, ring_idx, fence); +} +#endif + static virgl_renderer_gl_context virgl_create_context(void *opaque, int scanout_idx, struct virgl_renderer_gl_ctx_param *params) @@ -1112,6 +1378,10 @@ static void virtio_gpu_fence_poll(void *opaque) VirtIOGPU *g = opaque; VirtIOGPUGL *gl = VIRTIO_GPU_GL(g); + if (!virtio_gpu_virgl_update_render_state(g)) { + return; + } + virgl_renderer_poll(); virtio_gpu_process_cmdq(g); if (!QTAILQ_EMPTY(&g->cmdq) || !QTAILQ_EMPTY(&g->fenceq)) { @@ -1134,12 +1404,30 @@ void virtio_gpu_virgl_reset_scanout(VirtIOGPU *g) } } -void virtio_gpu_virgl_reset(VirtIOGPU *g) +static bool virtio_gpu_virgl_reset(VirtIOGPU *g) { + struct virtio_gpu_simple_resource *res, *tmp; + + /* + * Virgl blob resource unmapping can be suspended and + * deferred on unref, ensure that destruction is completed. + */ + QTAILQ_FOREACH_SAFE(res, &g->reslist, next, tmp) { + virtio_gpu_virgl_resource_destroy(g, res, NULL); + } + + if (!QTAILQ_EMPTY(&g->reslist)) { + return false; + } + virgl_renderer_reset(); + + virtio_gpu_virgl_reset_async_fences(g); + + return true; } -int virtio_gpu_virgl_init(VirtIOGPU *g) +static int virtio_gpu_virgl_init(VirtIOGPU *g) { int ret; uint32_t flags = 0; @@ -1149,6 +1437,12 @@ int virtio_gpu_virgl_init(VirtIOGPU *g) if (qemu_egl_display) { virtio_gpu_3d_cbs.version = 4; virtio_gpu_3d_cbs.get_egl_display = virgl_get_egl_display; +#if VIRGL_CHECK_VERSION(1, 1, 2) + virtio_gpu_3d_cbs.write_fence = virgl_write_async_fence; + virtio_gpu_3d_cbs.write_context_fence = virgl_write_async_context_fence; + flags |= VIRGL_RENDERER_ASYNC_FENCE_CB; + flags |= VIRGL_RENDERER_THREAD_SYNC; +#endif } #endif #ifdef VIRGL_RENDERER_D3D11_SHARE_TEXTURE @@ -1160,6 +1454,25 @@ int virtio_gpu_virgl_init(VirtIOGPU *g) if (virtio_gpu_venus_enabled(g->parent_obj.conf)) { flags |= VIRGL_RENDERER_VENUS | VIRGL_RENDERER_RENDER_SERVER; } + if (virtio_gpu_drm_enabled(g->parent_obj.conf)) { + flags |= VIRGL_RENDERER_DRM; + + if (!(flags & VIRGL_RENDERER_ASYNC_FENCE_CB)) { + /* + * Virglrenderer skips enabling DRM context support without + * enabled async-fence feature. VirtIO-GPU will initialize + * successfully, but DRM context won't be available in guest. + * + * For vrend async-fencing can be enabled only if EGL display + * is used. Vrend can't be disabled in QEMU, hence DRM implicitly + * requires EGL too. + * + * Async-fence was bugged in virglrenderer versions <= 1.1.1. + */ + error_report("drm requires egl display and virglrenderer >= 1.2.0"); + return -EINVAL; + } + } #endif ret = virgl_renderer_init(g, flags, &virtio_gpu_3d_cbs); @@ -1179,14 +1492,48 @@ int virtio_gpu_virgl_init(VirtIOGPU *g) } #if VIRGL_VERSION_MAJOR >= 1 - gl->cmdq_resume_bh = aio_bh_new(qemu_get_aio_context(), - virtio_gpu_virgl_resume_cmdq_bh, - g); + gl->cmdq_resume_bh = virtio_bh_io_new_guarded(DEVICE(g), + virtio_gpu_virgl_resume_cmdq_bh, + g); +#if VIRGL_CHECK_VERSION(1, 1, 2) + gl->async_fence_bh = virtio_bh_io_new_guarded(DEVICE(g), + virtio_gpu_virgl_async_fence_bh, + g); +#endif #endif return 0; } +bool virtio_gpu_virgl_update_render_state(VirtIOGPU *g) +{ + VirtIOGPUGL *gl = VIRTIO_GPU_GL(g); + + switch (gl->renderer_state) { + case RS_RESET: + virgl_renderer_force_ctx_0(); + + if (!virtio_gpu_virgl_reset(g)) { + return false; + } + /* fallthrough */ + case RS_START: + if (virtio_gpu_virgl_init(g)) { + gl->renderer_state = RS_INIT_FAILED; + return false; + } + + gl->renderer_state = RS_INITED; + break; + case RS_INIT_FAILED: + return false; + case RS_INITED: + break; + } + + return true; +} + static void virtio_gpu_virgl_add_capset(GArray *capset_ids, uint32_t capset_id) { g_array_append_val(capset_ids, capset_id); @@ -1218,5 +1565,14 @@ GArray *virtio_gpu_virgl_get_capsets(VirtIOGPU *g) } } + if (virtio_gpu_drm_enabled(g->parent_obj.conf)) { + virgl_renderer_get_cap_set(VIRTIO_GPU_CAPSET_DRM, + &capset_max_ver, + &capset_max_size); + if (capset_max_size) { + virtio_gpu_virgl_add_capset(capset_ids, VIRTIO_GPU_CAPSET_DRM); + } + } + return capset_ids; } diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c index 643e91ca2a7a..de7a86a73d21 100644 --- a/hw/display/virtio-gpu.c +++ b/hw/display/virtio-gpu.c @@ -902,7 +902,7 @@ void virtio_gpu_cleanup_mapping(VirtIOGPU *g, res->addrs = NULL; if (res->blob) { - virtio_gpu_fini_udmabuf(res); + virtio_gpu_fini_udmabuf(g, res); } } @@ -1517,6 +1517,21 @@ void virtio_gpu_device_realize(DeviceState *qdev, Error **errp) #endif } + if (virtio_gpu_drm_enabled(g->parent_obj.conf)) { +#ifdef VIRGL_VERSION_MAJOR + #if VIRGL_VERSION_MAJOR >= 1 + if (!virtio_gpu_blob_enabled(g->parent_obj.conf) || + !virtio_gpu_hostmem_enabled(g->parent_obj.conf)) { + error_setg(errp, "drm requires enabled blob and hostmem options"); + return; + } + #else + error_setg(errp, "old virglrenderer, drm unsupported"); + return; + #endif +#endif + } + if (!virtio_gpu_base_device_realize(qdev, virtio_gpu_handle_ctrl_cb, virtio_gpu_handle_cursor_cb, @@ -1526,9 +1541,9 @@ void virtio_gpu_device_realize(DeviceState *qdev, Error **errp) g->ctrl_vq = virtio_get_queue(vdev, 0); g->cursor_vq = virtio_get_queue(vdev, 1); - g->ctrl_bh = virtio_bh_new_guarded(qdev, virtio_gpu_ctrl_bh, g); - g->cursor_bh = virtio_bh_new_guarded(qdev, virtio_gpu_cursor_bh, g); - g->reset_bh = qemu_bh_new(virtio_gpu_reset_bh, g); + g->ctrl_bh = virtio_bh_io_new_guarded(qdev, virtio_gpu_ctrl_bh, g); + g->cursor_bh = virtio_bh_io_new_guarded(qdev, virtio_gpu_cursor_bh, g); + g->reset_bh = virtio_bh_io_new_guarded(qdev, virtio_gpu_reset_bh, g); qemu_cond_init(&g->reset_cond); QTAILQ_INIT(&g->reslist); QTAILQ_INIT(&g->cmdq); diff --git a/hw/display/vmware_vga.c b/hw/display/vmware_vga.c index ea7a9fca04e1..c2c6bc76e906 100644 --- a/hw/display/vmware_vga.c +++ b/hw/display/vmware_vga.c @@ -1335,8 +1335,6 @@ static void pci_vmsvga_realize(PCIDevice *dev, Error **errp) static const Property vga_vmware_properties[] = { DEFINE_PROP_UINT32("vgamem_mb", struct pci_vmsvga_state_s, chip.vga.vram_size_mb, 16), - DEFINE_PROP_BOOL("global-vmstate", struct pci_vmsvga_state_s, - chip.vga.global_vmstate, false), }; static void vmsvga_class_init(ObjectClass *klass, const void *data) diff --git a/hw/display/xlnx_dp.c b/hw/display/xlnx_dp.c index a24e95f0a730..7d037b46a35a 100644 --- a/hw/display/xlnx_dp.c +++ b/hw/display/xlnx_dp.c @@ -331,7 +331,7 @@ static inline void xlnx_dp_audio_activate(XlnxDPState *s) { bool activated = ((s->core_registers[DP_TX_AUDIO_CONTROL] & DP_TX_AUD_CTRL) != 0); - AUD_set_active_out(s->amixer_output_stream, activated); + audio_be_set_active_out(s->audio_be, s->amixer_output_stream, activated); xlnx_dpdma_set_host_data_location(s->dpdma, DP_AUDIO_DMA_CHANNEL(0), &s->audio_buffer_0); xlnx_dpdma_set_host_data_location(s->dpdma, DP_AUDIO_DMA_CHANNEL(1), @@ -401,7 +401,7 @@ static void xlnx_dp_audio_callback(void *opaque, int avail) /* Send the buffer through the audio. */ if (s->byte_left <= MAX_QEMU_BUFFER_SIZE) { if (s->byte_left != 0) { - written = AUD_write(s->amixer_output_stream, + written = audio_be_write(s->audio_be, s->amixer_output_stream, &s->out_buffer[s->data_ptr], s->byte_left); } else { int len_to_copy; @@ -413,12 +413,12 @@ static void xlnx_dp_audio_callback(void *opaque, int avail) while (avail) { len_to_copy = MIN(AUD_CHBUF_MAX_DEPTH, avail); memset(s->out_buffer, 0, len_to_copy); - avail -= AUD_write(s->amixer_output_stream, s->out_buffer, - len_to_copy); + avail -= audio_be_write(s->audio_be, s->amixer_output_stream, + s->out_buffer, len_to_copy); } } } else { - written = AUD_write(s->amixer_output_stream, + written = audio_be_write(s->audio_be, s->amixer_output_stream, &s->out_buffer[s->data_ptr], MAX_QEMU_BUFFER_SIZE); } s->byte_left -= written; @@ -1373,7 +1373,7 @@ static void xlnx_dp_realize(DeviceState *dev, Error **errp) DisplaySurface *surface; struct audsettings as; - if (!AUD_backend_check(&s->audio_be, errp)) { + if (!audio_be_check(&s->audio_be, errp)) { return; } @@ -1393,15 +1393,15 @@ static void xlnx_dp_realize(DeviceState *dev, Error **errp) as.freq = 44100; as.nchannels = 2; as.fmt = AUDIO_FORMAT_S16; - as.endianness = 0; + as.big_endian = false; - s->amixer_output_stream = AUD_open_out(s->audio_be, + s->amixer_output_stream = audio_be_open_out(s->audio_be, s->amixer_output_stream, "xlnx_dp.audio.out", s, xlnx_dp_audio_callback, &as); - AUD_set_volume_out_lr(s->amixer_output_stream, 0, 255, 255); + audio_be_set_volume_out_lr(s->audio_be, s->amixer_output_stream, 0, 255, 255); xlnx_dp_audio_activate(s); s->vblank = ptimer_init(vblank_hit, s, DP_VBLANK_PTIMER_POLICY); ptimer_transaction_begin(s->vblank); diff --git a/hw/gpio/aspeed_gpio.c b/hw/gpio/aspeed_gpio.c index d9237d4360f0..7d0f87e90c39 100644 --- a/hw/gpio/aspeed_gpio.c +++ b/hw/gpio/aspeed_gpio.c @@ -1488,7 +1488,7 @@ static void aspeed_gpio_init(Object *obj) } for (int i = 0; i < agc->nr_gpio_sets; i++) { - char *name = g_strdup_printf("gpio-set[%d]", i); + g_autofree char *name = g_strdup_printf("gpio-set[%d]", i); object_property_add(obj, name, "uint32", aspeed_gpio_get_set, aspeed_gpio_set_set, NULL, NULL); } diff --git a/hw/hppa/machine.c b/hw/hppa/machine.c index c3680667aee5..ec63dc12979b 100644 --- a/hw/hppa/machine.c +++ b/hw/hppa/machine.c @@ -45,7 +45,7 @@ struct HppaMachineState { MachineState parent_obj; }; -#define MIN_SEABIOS_HPPA_VERSION 19 /* require at least this fw version */ +#define MIN_SEABIOS_HPPA_VERSION 22 /* require at least this fw version */ #define HPA_POWER_BUTTON (FIRMWARE_END - 0x10) static hwaddr soft_power_reg; @@ -179,19 +179,21 @@ static uint64_t linux_kernel_virt_to_phys(void *opaque, uint64_t addr) return addr; } +static HPPACPU *cpu[HPPA_MAX_CPUS]; +static uint64_t firmware_entry; + static uint64_t translate_pa10(void *dummy, uint64_t addr) { - return (uint32_t)addr; + const uint8_t pa_bits = hppa_phys_addr_bits(&cpu[0]->env); + return hppa_abs_to_phys_pa1x(pa_bits, addr); } static uint64_t translate_pa20(void *dummy, uint64_t addr) { - return hppa_abs_to_phys_pa2_w0(addr); + const uint8_t pa_bits = hppa_phys_addr_bits(&cpu[0]->env); + return hppa_abs_to_phys_pa2_w0(pa_bits, addr); } -static HPPACPU *cpu[HPPA_MAX_CPUS]; -static uint64_t firmware_entry; - static void fw_cfg_boot_set(void *opaque, const char *boot_device, Error **errp) { @@ -376,20 +378,19 @@ static void machine_HP_common_init_tail(MachineState *machine, PCIBus *pci_bus, if (pci_bus) { pci_init_nic_devices(pci_bus, mc->default_nic); + } + if (pci_bus && hppa_is_pa20(&cpu[0]->env)) { /* BMC board: HP Diva GSP PCI card */ - dev = qdev_new("diva-gsp"); - if (dev && !object_property_get_bool(OBJECT(dev), "disable", NULL)) { - pci_dev = pci_new_multifunction(PCI_DEVFN(2, 0), "diva-gsp"); - if (!lasi_dev) { - /* bind default keyboard/serial to Diva card */ - qdev_prop_set_chr(DEVICE(pci_dev), "chardev1", serial_hd(0)); - qdev_prop_set_chr(DEVICE(pci_dev), "chardev2", serial_hd(1)); - qdev_prop_set_chr(DEVICE(pci_dev), "chardev3", serial_hd(2)); - qdev_prop_set_chr(DEVICE(pci_dev), "chardev4", serial_hd(3)); - } - pci_realize_and_unref(pci_dev, pci_bus, &error_fatal); + pci_dev = pci_new_multifunction(PCI_DEVFN(2, 0), "diva-gsp"); + if (!lasi_dev) { + /* bind default keyboard/serial to Diva card */ + qdev_prop_set_chr(DEVICE(pci_dev), "chardev1", serial_hd(0)); + qdev_prop_set_chr(DEVICE(pci_dev), "chardev2", serial_hd(1)); + qdev_prop_set_chr(DEVICE(pci_dev), "chardev3", serial_hd(2)); + qdev_prop_set_chr(DEVICE(pci_dev), "chardev4", serial_hd(3)); } + pci_realize_and_unref(pci_dev, pci_bus, &error_fatal); } /* create USB OHCI controller for USB keyboard & mouse on Astro machines */ @@ -683,6 +684,9 @@ static AstroState *astro_init(void) DeviceState *dev; dev = qdev_new(TYPE_ASTRO_CHIP); + object_property_set_int(OBJECT(dev), "phys-addr-bits", + hppa_phys_addr_bits(&cpu[0]->env), + &error_abort); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); return ASTRO_CHIP(dev); @@ -727,6 +731,14 @@ static void machine_HP_C3700_init(MachineState *machine) machine_HP_common_init_tail(machine, pci_bus, translate); } +/* + * Create HP A400 server + */ +static void machine_HP_A400_init(MachineState *machine) +{ + machine_HP_C3700_init(machine); +} + static void hppa_machine_reset(MachineState *ms, ResetType type) { unsigned int smp_cpus = ms->smp.cpus; @@ -791,13 +803,13 @@ static void hppa_machine_common_class_init(ObjectClass *oc, const void *data) static void HP_B160L_machine_init_class_init(ObjectClass *oc, const void *data) { static const char * const valid_cpu_types[] = { - TYPE_HPPA_CPU, + TYPE_HPPA_CPU_PA_7300LC, NULL }; MachineClass *mc = MACHINE_CLASS(oc); mc->desc = "HP B160L workstation"; - mc->default_cpu_type = TYPE_HPPA_CPU; + mc->default_cpu_type = TYPE_HPPA_CPU_PA_7300LC; mc->valid_cpu_types = valid_cpu_types; mc->init = machine_HP_B160L_init; mc->is_default = true; @@ -807,29 +819,50 @@ static void HP_B160L_machine_init_class_init(ObjectClass *oc, const void *data) static void HP_C3700_machine_init_class_init(ObjectClass *oc, const void *data) { static const char * const valid_cpu_types[] = { - TYPE_HPPA64_CPU, + TYPE_HPPA_CPU_PA_8700, NULL }; MachineClass *mc = MACHINE_CLASS(oc); mc->desc = "HP C3700 workstation"; - mc->default_cpu_type = TYPE_HPPA64_CPU; + mc->default_cpu_type = TYPE_HPPA_CPU_PA_8700; mc->valid_cpu_types = valid_cpu_types; mc->init = machine_HP_C3700_init; mc->max_cpus = HPPA_MAX_CPUS; mc->default_ram_size = 1024 * MiB; } +static void HP_A400_machine_init_class_init(ObjectClass *oc, const void *data) +{ + static const char * const valid_cpu_types[] = { + TYPE_HPPA_CPU_PA_8500, + NULL + }; + MachineClass *mc = MACHINE_CLASS(oc); + + mc->desc = "HP A400-44 workstation"; + mc->default_cpu_type = TYPE_HPPA_CPU_PA_8500; + mc->valid_cpu_types = valid_cpu_types; + mc->init = machine_HP_A400_init; + mc->max_cpus = HPPA_MAX_CPUS; + mc->default_ram_size = 1024 * MiB; +} + static void HP_715_machine_init_class_init(ObjectClass *oc, const void *data) { static const char * const valid_cpu_types[] = { - TYPE_HPPA_CPU, + TYPE_HPPA_CPU_PA_7300LC, NULL }; MachineClass *mc = MACHINE_CLASS(oc); mc->desc = "HP 715/64 workstation"; - mc->default_cpu_type = TYPE_HPPA_CPU; + /* + * Although the 715 workstation should use a 7100LC, it can be safely + * modeled as a 7300LC as the difference is a moving of the L1 data cache + * to on-chip. + */ + mc->default_cpu_type = TYPE_HPPA_CPU_PA_7300LC; mc->valid_cpu_types = valid_cpu_types; mc->init = machine_HP_715_init; /* can only support up to max. 8 CPUs due inventory major numbers */ @@ -858,6 +891,10 @@ static const TypeInfo hppa_machine_types[] = { .name = MACHINE_TYPE_NAME("C3700"), .parent = TYPE_HPPA_COMMON_MACHINE, .class_init = HP_C3700_machine_init_class_init, + }, { + .name = MACHINE_TYPE_NAME("A400"), + .parent = TYPE_HPPA_COMMON_MACHINE, + .class_init = HP_A400_machine_init_class_init, }, { .name = MACHINE_TYPE_NAME("715"), .parent = TYPE_HPPA_COMMON_MACHINE, diff --git a/hw/hyperv/trace-events b/hw/hyperv/trace-events index 7963c215b1c5..d8c96f18e98a 100644 --- a/hw/hyperv/trace-events +++ b/hw/hyperv/trace-events @@ -16,6 +16,7 @@ vmbus_gpadl_torndown(uint32_t gpadl_id) "gpadl #%d" vmbus_open_channel(uint32_t chan_id, uint32_t gpadl_id, uint32_t target_vp) "channel #%d gpadl #%d target vp %d" vmbus_channel_open(uint32_t chan_id, uint32_t status) "channel #%d status %d" vmbus_close_channel(uint32_t chan_id) "channel #%d" +vmbus_handle_vmfd_change(void) "" # hv-balloon hv_balloon_state_change(const char *tostr) "-> %s" diff --git a/hw/hyperv/vmbus.c b/hw/hyperv/vmbus.c index c5bab5d24520..64abe4c4c160 100644 --- a/hw/hyperv/vmbus.c +++ b/hw/hyperv/vmbus.c @@ -20,6 +20,7 @@ #include "hw/hyperv/vmbus-bridge.h" #include "hw/core/sysbus.h" #include "exec/cpu-common.h" +#include "system/kvm.h" #include "exec/target_page.h" #include "trace.h" @@ -248,6 +249,12 @@ struct VMBus { * interrupt page */ EventNotifier notifier; + + /* + * Notifier to inform when vmfd is changed as a part of confidential guest + * reset mechanism. + */ + NotifierWithReturn vmbus_vmfd_change_notifier; }; static bool gpadl_full(VMBusGpadl *gpadl) @@ -2347,6 +2354,33 @@ static void vmbus_dev_unrealize(DeviceState *dev) free_channels(vdev); } +/* + * If the KVM fd changes because of VM reset in confidential guests, + * reassociate event fd with the new KVM fd. + */ +static int vmbus_handle_vmfd_change(NotifierWithReturn *notifier, + void *data, Error** errp) +{ + VMBus *vmbus = container_of(notifier, VMBus, + vmbus_vmfd_change_notifier); + int ret = 0; + + /* we are not interested in pre vmfd change notification */ + if (((VmfdChangeNotifier *)data)->pre) { + return 0; + } + + ret = hyperv_set_event_flag_handler(VMBUS_EVENT_CONNECTION_ID, + &vmbus->notifier); + /* if we are only using userland event handler, it may already exist */ + if (ret != 0 && ret != -EEXIST) { + error_setg(errp, "hyperv set event handler failed with %d", ret); + } + + trace_vmbus_handle_vmfd_change(); + return ret; +} + static const Property vmbus_dev_props[] = { DEFINE_PROP_UUID("instanceid", VMBusDevice, instanceid), }; @@ -2429,6 +2463,9 @@ static void vmbus_realize(BusState *bus, Error **errp) goto clear_event_notifier; } + vmbus->vmbus_vmfd_change_notifier.notify = vmbus_handle_vmfd_change; + kvm_vmfd_add_change_notifier(&vmbus->vmbus_vmfd_change_notifier); + return; clear_event_notifier: diff --git a/hw/i2c/aspeed_i2c.c b/hw/i2c/aspeed_i2c.c index 122bfdd63dca..8022938f3478 100644 --- a/hw/i2c/aspeed_i2c.c +++ b/hw/i2c/aspeed_i2c.c @@ -237,7 +237,7 @@ static void aspeed_i2c_set_tx_dma_dram_offset(AspeedI2CBus *bus) bus->dma_dram_offset = deposit64(bus->dma_dram_offset, 0, 32, FIELD_EX32(value, I2CM_DMA_TX_ADDR, ADDR)); - if (!aic->has_dma64) { + if (aic->has_dma64) { value = bus->regs[R_I2CM_DMA_TX_ADDR_HI]; bus->dma_dram_offset = deposit64(bus->dma_dram_offset, 32, 32, @@ -262,7 +262,7 @@ static void aspeed_i2c_set_rx_dma_dram_offset(AspeedI2CBus *bus) bus->dma_dram_offset = deposit64(bus->dma_dram_offset, 0, 32, FIELD_EX32(value, I2CM_DMA_RX_ADDR, ADDR)); - if (!aic->has_dma64) { + if (aic->has_dma64) { value = bus->regs[R_I2CM_DMA_RX_ADDR_HI]; bus->dma_dram_offset = deposit64(bus->dma_dram_offset, 32, 32, diff --git a/hw/i2c/pm_smbus.c b/hw/i2c/pm_smbus.c index ead53d2724db..5654d5163825 100644 --- a/hw/i2c/pm_smbus.c +++ b/hw/i2c/pm_smbus.c @@ -443,13 +443,6 @@ static const MemoryRegionOps pm_smbus_ops = { .endianness = DEVICE_LITTLE_ENDIAN, }; -bool pm_smbus_vmstate_needed(void) -{ - MachineClass *mc = MACHINE_GET_CLASS(qdev_get_machine()); - - return !mc->smbus_no_migration_support; -} - const VMStateDescription pmsmb_vmstate = { .name = "pmsmb", .version_id = 1, diff --git a/hw/i2c/smbus_eeprom.c b/hw/i2c/smbus_eeprom.c index d9ac556a0abc..693d47622052 100644 --- a/hw/i2c/smbus_eeprom.c +++ b/hw/i2c/smbus_eeprom.c @@ -88,11 +88,9 @@ static int eeprom_write_data(SMBusDevice *dev, uint8_t *buf, uint8_t len) static bool smbus_eeprom_vmstate_needed(void *opaque) { - MachineClass *mc = MACHINE_GET_CLASS(qdev_get_machine()); SMBusEEPROMDevice *eeprom = opaque; - return (eeprom->accessed || smbus_vmstate_needed(&eeprom->smbusdev)) && - !mc->smbus_no_migration_support; + return eeprom->accessed || smbus_vmstate_needed(&eeprom->smbusdev); } static const VMStateDescription vmstate_smbus_eeprom = { diff --git a/hw/i2c/smbus_ich9.c b/hw/i2c/smbus_ich9.c index 956c9b59bbc3..51513e73fdec 100644 --- a/hw/i2c/smbus_ich9.c +++ b/hw/i2c/smbus_ich9.c @@ -41,20 +41,14 @@ struct ICH9SMBState { PMSMBus smb; }; -static bool ich9_vmstate_need_smbus(void *opaque, int version_id) -{ - return pm_smbus_vmstate_needed(); -} - static const VMStateDescription vmstate_ich9_smbus = { .name = "ich9_smb", .version_id = 1, .minimum_version_id = 1, .fields = (const VMStateField[]) { VMSTATE_PCI_DEVICE(dev, ICH9SMBState), - VMSTATE_BOOL_TEST(irq_enabled, ICH9SMBState, ich9_vmstate_need_smbus), - VMSTATE_STRUCT_TEST(smb, ICH9SMBState, ich9_vmstate_need_smbus, 1, - pmsmb_vmstate, PMSMBus), + VMSTATE_BOOL(irq_enabled, ICH9SMBState), + VMSTATE_STRUCT(smb, ICH9SMBState, 1, pmsmb_vmstate, PMSMBus), VMSTATE_END_OF_LIST() } }; diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c index 92a367d6577c..f395fa248c00 100644 --- a/hw/i386/intel_iommu.c +++ b/hw/i386/intel_iommu.c @@ -1072,6 +1072,7 @@ static inline bool vtd_ce_type_check(X86IOMMUState *x86_iommu, { switch (vtd_ce_get_type(ce)) { case VTD_CONTEXT_TT_MULTI_LEVEL: + case VTD_CONTEXT_TT_PASS_THROUGH: /* Always supported */ break; case VTD_CONTEXT_TT_DEV_IOTLB: @@ -1080,12 +1081,6 @@ static inline bool vtd_ce_type_check(X86IOMMUState *x86_iommu, return false; } break; - case VTD_CONTEXT_TT_PASS_THROUGH: - if (!x86_iommu->pt_supported) { - error_report_once("%s: PT specified but not supported", __func__); - return false; - } - break; default: /* Unknown type */ error_report_once("%s: unknown ce type: %"PRIu32, __func__, @@ -1857,6 +1852,21 @@ static const bool vtd_qualified_faults[] = { [VTD_FR_MAX] = false, }; +static const bool vtd_recoverable_faults[] = { + [VTD_FR_WRITE] = true, + [VTD_FR_READ] = true, + [VTD_FR_PASID_DIR_ENTRY_P] = true, + [VTD_FR_PASID_ENTRY_P] = true, + [VTD_FR_FS_PAGING_ENTRY_INV] = true, + [VTD_FR_FS_PAGING_ENTRY_P] = true, + [VTD_FR_FS_PAGING_ENTRY_RSVD] = true, + [VTD_FR_PASID_ENTRY_FSPTPTR_INV] = true, + [VTD_FR_FS_NON_CANONICAL] = true, + [VTD_FR_FS_PAGING_ENTRY_US] = true, + [VTD_FR_SM_WRITE] = true, + [VTD_FR_MAX] = false, +}; + /* To see if a fault condition is "qualified", which is reported to software * only if the FPD field in the context-entry used to process the faulting * request is 0. @@ -1866,6 +1876,11 @@ static inline bool vtd_is_qualified_fault(VTDFaultReason fault) return vtd_qualified_faults[fault]; } +static inline bool vtd_is_recoverable_fault(VTDFaultReason fault, int iommu_idx) +{ + return iommu_idx == VTD_IDX_ATS && vtd_recoverable_faults[fault]; +} + static inline bool vtd_is_interrupt_addr(hwaddr addr) { return VTD_INTERRUPT_ADDR_FIRST <= addr && addr <= VTD_INTERRUPT_ADDR_LAST; @@ -2237,8 +2252,10 @@ static bool vtd_do_iommu_translate(VTDAddressSpace *vtd_as, PCIBus *bus, } if (ret_fr) { - vtd_report_fault(s, -ret_fr, is_fpd_set, source_id, - addr, is_write, pasid != PCI_NO_PASID, pasid); + if (!vtd_is_recoverable_fault(-ret_fr, iommu_idx)) { + vtd_report_fault(s, -ret_fr, is_fpd_set, source_id, + addr, is_write, pasid != PCI_NO_PASID, pasid); + } goto error; } @@ -4188,7 +4205,6 @@ static const Property vtd_properties[] = { DEFINE_PROP_BOOL("snoop-control", IntelIOMMUState, snoop_control, false), DEFINE_PROP_BOOL("x-pasid-mode", IntelIOMMUState, pasid, false), DEFINE_PROP_BOOL("svm", IntelIOMMUState, svm, false), - DEFINE_PROP_BOOL("dma-drain", IntelIOMMUState, dma_drain, true), DEFINE_PROP_BOOL("stale-tm", IntelIOMMUState, stale_tm, false), DEFINE_PROP_BOOL("fs1gp", IntelIOMMUState, fs1gp, true), }; @@ -4982,12 +4998,9 @@ static void vtd_cap_init(IntelIOMMUState *s) { X86IOMMUState *x86_iommu = X86_IOMMU_DEVICE(s); - s->cap = VTD_CAP_FRO | VTD_CAP_NFR | VTD_CAP_ND | - VTD_CAP_MAMV | VTD_CAP_PSI | VTD_CAP_SSLPS | + s->cap = VTD_CAP_FRO | VTD_CAP_NFR | VTD_CAP_ND | VTD_ECAP_PT | + VTD_CAP_MAMV | VTD_CAP_PSI | VTD_CAP_SSLPS | VTD_CAP_DRAIN | VTD_CAP_ESRTPS | VTD_CAP_MGAW(s->aw_bits); - if (s->dma_drain) { - s->cap |= VTD_CAP_DRAIN; - } if (x86_iommu->dma_translation) { if (s->aw_bits >= VTD_HOST_AW_39BIT) { s->cap |= VTD_CAP_SAGAW_39bit; @@ -5010,10 +5023,6 @@ static void vtd_cap_init(IntelIOMMUState *s) s->ecap |= VTD_ECAP_DT; } - if (x86_iommu->pt_supported) { - s->ecap |= VTD_ECAP_PT; - } - if (s->caching_mode) { s->cap |= VTD_CAP_CM; } @@ -5569,11 +5578,6 @@ static bool vtd_decide_config(IntelIOMMUState *s, Error **errp) return false; } - if (s->scalable_mode && !s->dma_drain) { - error_setg(errp, "Need to set dma_drain for scalable mode"); - return false; - } - if (s->pasid && !s->scalable_mode) { error_setg(errp, "Need to set scalable mode for PASID"); return false; diff --git a/hw/i386/kvm/clock.c b/hw/i386/kvm/clock.c index aba6842a22c3..7d167c8b006a 100644 --- a/hw/i386/kvm/clock.c +++ b/hw/i386/kvm/clock.c @@ -23,7 +23,6 @@ #include "migration/vmstate.h" #include "hw/core/sysbus.h" #include "hw/i386/kvm/clock.h" -#include "hw/core/qdev-properties.h" #include "exec/cpu-common.h" #include "qapi/error.h" @@ -44,12 +43,12 @@ struct KVMClockState { /* whether the 'clock' value was obtained in the 'paused' state */ bool runstate_paused; - /* whether machine type supports reliable KVM_GET_CLOCK */ - bool mach_use_reliable_get_clock; - /* whether the 'clock' value was obtained in a host with * reliable KVM_GET_CLOCK */ bool clock_is_reliable; + + NotifierWithReturn kvmclock_vcpufd_change_notifier; + NotifierWithReturn kvmclock_vmfd_change_notifier; }; struct pvclock_vcpu_time_info { @@ -63,6 +62,9 @@ struct pvclock_vcpu_time_info { uint8_t pad[2]; } __attribute__((__packed__)); /* 32 bytes */ +static int kvmclock_set_clock(NotifierWithReturn *notifier, + void *data, Error** errp); + static uint64_t kvmclock_current_nsec(KVMClockState *s) { CPUState *cpu = first_cpu; @@ -219,6 +221,54 @@ static void kvmclock_vm_state_change(void *opaque, bool running, } } +static int kvmclock_save_clock(NotifierWithReturn *notifier, + void *data, Error** errp) +{ + if (!((VmfdChangeNotifier *)data)->pre) { + return 0; + } + KVMClockState *s = container_of(notifier, KVMClockState, + kvmclock_vmfd_change_notifier); + kvm_update_clock(s); + return 0; +} + +static int kvmclock_set_clock(NotifierWithReturn *notifier, + void *data, Error** errp) +{ + struct kvm_clock_data clock_data = {}; + CPUState *cpu; + int ret; + KVMClockState *s = container_of(notifier, KVMClockState, + kvmclock_vcpufd_change_notifier); + int cap_clock_ctrl = kvm_check_extension(kvm_state, KVM_CAP_KVMCLOCK_CTRL); + + if (!s->clock_is_reliable) { + uint64_t pvclock_via_mem = kvmclock_current_nsec(s); + /* saved clock value before vmfd change is not reliable */ + if (pvclock_via_mem) { + s->clock = pvclock_via_mem; + } + } + + clock_data.clock = s->clock; + ret = kvm_vm_ioctl(kvm_state, KVM_SET_CLOCK, &clock_data); + if (ret < 0) { + fprintf(stderr, "KVM_SET_CLOCK failed: %s\n", strerror(-ret)); + abort(); + } + + if (!cap_clock_ctrl) { + return 0; + } + CPU_FOREACH(cpu) { + run_on_cpu(cpu, do_kvmclock_ctrl, RUN_ON_CPU_NULL); + } + + return 0; +} + + static void kvmclock_realize(DeviceState *dev, Error **errp) { KVMClockState *s = KVM_CLOCK(dev); @@ -230,21 +280,18 @@ static void kvmclock_realize(DeviceState *dev, Error **errp) kvm_update_clock(s); - qemu_add_vm_change_state_handler(kvmclock_vm_state_change, s); -} + s->kvmclock_vcpufd_change_notifier.notify = kvmclock_set_clock; + s->kvmclock_vmfd_change_notifier.notify = kvmclock_save_clock; -static bool kvmclock_clock_is_reliable_needed(void *opaque) -{ - KVMClockState *s = opaque; - - return s->mach_use_reliable_get_clock; + qemu_add_vm_change_state_handler(kvmclock_vm_state_change, s); + kvm_vcpufd_add_change_notifier(&s->kvmclock_vcpufd_change_notifier); + kvm_vmfd_add_change_notifier(&s->kvmclock_vmfd_change_notifier); } static const VMStateDescription kvmclock_reliable_get_clock = { .name = "kvmclock/clock_is_reliable", .version_id = 1, .minimum_version_id = 1, - .needed = kvmclock_clock_is_reliable_needed, .fields = (const VMStateField[]) { VMSTATE_BOOL(clock_is_reliable, KVMClockState), VMSTATE_END_OF_LIST() @@ -305,18 +352,12 @@ static const VMStateDescription kvmclock_vmsd = { } }; -static const Property kvmclock_properties[] = { - DEFINE_PROP_BOOL("x-mach-use-reliable-get-clock", KVMClockState, - mach_use_reliable_get_clock, true), -}; - static void kvmclock_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = kvmclock_realize; dc->vmsd = &kvmclock_vmsd; - device_class_set_props(dc, kvmclock_properties); } static const TypeInfo kvmclock_info = { diff --git a/hw/i386/kvm/i8254.c b/hw/i386/kvm/i8254.c index 81e742f86674..70e8fd83cd04 100644 --- a/hw/i386/kvm/i8254.c +++ b/hw/i386/kvm/i8254.c @@ -35,6 +35,7 @@ #include "hw/core/qdev-properties-system.h" #include "system/kvm.h" #include "target/i386/kvm/kvm_i386.h" +#include "trace.h" #include "qom/object.h" #define KVM_PIT_REINJECT_BIT 0 @@ -52,6 +53,8 @@ struct KVMPITState { LostTickPolicy lost_tick_policy; bool vm_stopped; int64_t kernel_clock_offset; + + NotifierWithReturn kvmpit_vmfd_change_notifier; }; struct KVMPITClass { @@ -60,6 +63,43 @@ struct KVMPITClass { DeviceRealize parent_realize; }; +static void do_pit_initialize(KVMPITState *s, Error **errp) +{ + struct kvm_pit_config config = { + .flags = 0, + }; + int ret; + + ret = kvm_vm_ioctl(kvm_state, KVM_CREATE_PIT2, &config); + if (ret < 0) { + error_setg(errp, "Create kernel PIC irqchip failed: %s", + strerror(-ret)); + return; + } + switch (s->lost_tick_policy) { + case LOST_TICK_POLICY_DELAY: + break; /* enabled by default */ + case LOST_TICK_POLICY_DISCARD: + if (kvm_check_extension(kvm_state, KVM_CAP_REINJECT_CONTROL)) { + struct kvm_reinject_control control = { .pit_reinject = 0 }; + + ret = kvm_vm_ioctl(kvm_state, KVM_REINJECT_CONTROL, &control); + if (ret < 0) { + error_setg(errp, + "Can't disable in-kernel PIT reinjection: %s", + strerror(-ret)); + return; + } + } + break; + default: + error_setg(errp, "Lost tick policy not supported."); + return; + } + + return; +} + static void kvm_pit_update_clock_offset(KVMPITState *s) { int64_t offset, clock_offset; @@ -166,6 +206,23 @@ static void kvm_pit_put(PITCommonState *pit) } } +static int kvmpit_post_vmfd_change(NotifierWithReturn *notifier, + void *data, Error** errp) +{ + KVMPITState *s = container_of(notifier, KVMPITState, + kvmpit_vmfd_change_notifier); + + /* we are not interested in pre vmfd change notification */ + if (((VmfdChangeNotifier *)data)->pre) { + return 0; + } + + do_pit_initialize(s, errp); + + trace_kvmpit_post_vmfd_change(); + return 0; +} + static void kvm_pit_set_gate(PITCommonState *s, PITChannelState *sc, int val) { kvm_pit_get(s); @@ -241,42 +298,13 @@ static void kvm_pit_realizefn(DeviceState *dev, Error **errp) PITCommonState *pit = PIT_COMMON(dev); KVMPITClass *kpc = KVM_PIT_GET_CLASS(dev); KVMPITState *s = KVM_PIT(pit); - struct kvm_pit_config config = { - .flags = 0, - }; - int ret; if (!kvm_check_extension(kvm_state, KVM_CAP_PIT_STATE2) || !kvm_check_extension(kvm_state, KVM_CAP_PIT2)) { error_setg(errp, "In-kernel PIT not available"); } - ret = kvm_vm_ioctl(kvm_state, KVM_CREATE_PIT2, &config); - if (ret < 0) { - error_setg(errp, "Create kernel PIC irqchip failed: %s", - strerror(-ret)); - return; - } - switch (s->lost_tick_policy) { - case LOST_TICK_POLICY_DELAY: - break; /* enabled by default */ - case LOST_TICK_POLICY_DISCARD: - if (kvm_check_extension(kvm_state, KVM_CAP_REINJECT_CONTROL)) { - struct kvm_reinject_control control = { .pit_reinject = 0 }; - - ret = kvm_vm_ioctl(kvm_state, KVM_REINJECT_CONTROL, &control); - if (ret < 0) { - error_setg(errp, - "Can't disable in-kernel PIT reinjection: %s", - strerror(-ret)); - return; - } - } - break; - default: - error_setg(errp, "Lost tick policy not supported."); - return; - } + do_pit_initialize(s, errp); memory_region_init_io(&pit->ioports, OBJECT(dev), NULL, NULL, "kvm-pit", 4); @@ -284,6 +312,9 @@ static void kvm_pit_realizefn(DeviceState *dev, Error **errp) qemu_add_vm_change_state_handler(kvm_pit_vm_state_change, s); + s->kvmpit_vmfd_change_notifier.notify = kvmpit_post_vmfd_change; + kvm_vmfd_add_change_notifier(&s->kvmpit_vmfd_change_notifier); + kpc->parent_realize(dev, errp); } diff --git a/hw/i386/kvm/meson.build b/hw/i386/kvm/meson.build index a4a2e23c06ef..a34b3504cbae 100644 --- a/hw/i386/kvm/meson.build +++ b/hw/i386/kvm/meson.build @@ -15,9 +15,6 @@ i386_kvm_ss.add(when: 'CONFIG_XEN_EMU', if_true: files( i386_ss.add_all(when: 'CONFIG_KVM', if_true: i386_kvm_ss) -xen_stubs_ss = ss.source_set() -xen_stubs_ss.add(when: 'CONFIG_XEN_EMU', if_false: files( +stub_ss.add(files( 'xen-stubs.c', )) - -specific_ss.add_all(when: 'CONFIG_SYSTEM_ONLY', if_true: xen_stubs_ss) diff --git a/hw/i386/kvm/trace-events b/hw/i386/kvm/trace-events index 67bf7f174ed4..33680ff82bd1 100644 --- a/hw/i386/kvm/trace-events +++ b/hw/i386/kvm/trace-events @@ -20,3 +20,4 @@ xenstore_reset_watches(void) "" xenstore_watch_event(const char *path, const char *token) "path %s token %s" xen_primary_console_create(void) "" xen_primary_console_reset(int port) "port %u" +kvmpit_post_vmfd_change(void) "" diff --git a/hw/i386/microvm.c b/hw/i386/microvm.c index 7ff205126365..5a7889f21b8a 100644 --- a/hw/i386/microvm.c +++ b/hw/i386/microvm.c @@ -330,7 +330,7 @@ static void microvm_memory_init(MicrovmMachineState *mms) rom_set_fw(fw_cfg); if (machine->kernel_filename != NULL) { - mmc->x86_load_linux(x86ms, fw_cfg, 0, true); + mmc->x86_load_linux(x86ms, fw_cfg, 0); } if (mms->option_roms) { diff --git a/hw/i386/nitro_enclave.c b/hw/i386/nitro_enclave.c index 640b1d8c548e..a29f0044d0ec 100644 --- a/hw/i386/nitro_enclave.c +++ b/hw/i386/nitro_enclave.c @@ -164,7 +164,7 @@ static void nitro_enclave_machine_initfn(Object *obj) } static void x86_load_eif(X86MachineState *x86ms, FWCfgState *fw_cfg, - int acpi_data_size, bool pvh_enabled) + int acpi_data_size) { Error *err = NULL; char *eif_kernel, *eif_initrd, *eif_cmdline; @@ -199,7 +199,7 @@ static void x86_load_eif(X86MachineState *x86ms, FWCfgState *fw_cfg, machine->kernel_cmdline = eif_cmdline; } - x86_load_linux(x86ms, fw_cfg, 0, true); + x86_load_linux(x86ms, fw_cfg, 0); unlink(machine->kernel_filename); unlink(machine->initrd_filename); diff --git a/hw/i386/pc.c b/hw/i386/pc.c index 0dd3fd01d981..addf602da086 100644 --- a/hw/i386/pc.c +++ b/hw/i386/pc.c @@ -73,15 +73,6 @@ #include "hw/xen/xen-bus.h" #endif -/* - * Helper for setting model-id for CPU models that changed model-id - * depending on QEMU versions up to QEMU 2.4. - */ -#define PC_CPU_MODEL_IDS(v) \ - { "qemu32-" TYPE_X86_CPU, "model-id", "QEMU Virtual CPU version " v, },\ - { "qemu64-" TYPE_X86_CPU, "model-id", "QEMU Virtual CPU version " v, },\ - { "athlon-" TYPE_X86_CPU, "model-id", "QEMU Virtual CPU version " v, }, - GlobalProperty pc_compat_10_2[] = {}; const size_t pc_compat_10_2_len = G_N_ELEMENTS(pc_compat_10_2); @@ -186,76 +177,6 @@ const size_t pc_compat_4_2_len = G_N_ELEMENTS(pc_compat_4_2); GlobalProperty pc_compat_4_1[] = {}; const size_t pc_compat_4_1_len = G_N_ELEMENTS(pc_compat_4_1); -GlobalProperty pc_compat_4_0[] = {}; -const size_t pc_compat_4_0_len = G_N_ELEMENTS(pc_compat_4_0); - -GlobalProperty pc_compat_3_1[] = { - { "intel-iommu", "dma-drain", "off" }, - { "Opteron_G3" "-" TYPE_X86_CPU, "rdtscp", "off" }, - { "Opteron_G4" "-" TYPE_X86_CPU, "rdtscp", "off" }, - { "Opteron_G4" "-" TYPE_X86_CPU, "npt", "off" }, - { "Opteron_G4" "-" TYPE_X86_CPU, "nrip-save", "off" }, - { "Opteron_G5" "-" TYPE_X86_CPU, "rdtscp", "off" }, - { "Opteron_G5" "-" TYPE_X86_CPU, "npt", "off" }, - { "Opteron_G5" "-" TYPE_X86_CPU, "nrip-save", "off" }, - { "EPYC" "-" TYPE_X86_CPU, "npt", "off" }, - { "EPYC" "-" TYPE_X86_CPU, "nrip-save", "off" }, - { "EPYC-IBPB" "-" TYPE_X86_CPU, "npt", "off" }, - { "EPYC-IBPB" "-" TYPE_X86_CPU, "nrip-save", "off" }, - { "Skylake-Client" "-" TYPE_X86_CPU, "mpx", "on" }, - { "Skylake-Client-IBRS" "-" TYPE_X86_CPU, "mpx", "on" }, - { "Skylake-Server" "-" TYPE_X86_CPU, "mpx", "on" }, - { "Skylake-Server-IBRS" "-" TYPE_X86_CPU, "mpx", "on" }, - { "Cascadelake-Server" "-" TYPE_X86_CPU, "mpx", "on" }, - { "Icelake-Client" "-" TYPE_X86_CPU, "mpx", "on" }, - { "Icelake-Server" "-" TYPE_X86_CPU, "mpx", "on" }, - { "Cascadelake-Server" "-" TYPE_X86_CPU, "stepping", "5" }, - { TYPE_X86_CPU, "x-intel-pt-auto-level", "off" }, -}; -const size_t pc_compat_3_1_len = G_N_ELEMENTS(pc_compat_3_1); - -GlobalProperty pc_compat_3_0[] = { - { TYPE_X86_CPU, "x-hv-synic-kvm-only", "on" }, - { "Skylake-Server" "-" TYPE_X86_CPU, "pku", "off" }, - { "Skylake-Server-IBRS" "-" TYPE_X86_CPU, "pku", "off" }, -}; -const size_t pc_compat_3_0_len = G_N_ELEMENTS(pc_compat_3_0); - -GlobalProperty pc_compat_2_12[] = { - { TYPE_X86_CPU, "legacy-cache", "on" }, - { TYPE_X86_CPU, "topoext", "off" }, - { "EPYC-" TYPE_X86_CPU, "xlevel", "0x8000000a" }, - { "EPYC-IBPB-" TYPE_X86_CPU, "xlevel", "0x8000000a" }, -}; -const size_t pc_compat_2_12_len = G_N_ELEMENTS(pc_compat_2_12); - -GlobalProperty pc_compat_2_11[] = { - { TYPE_X86_CPU, "x-migrate-smi-count", "off" }, - { "Skylake-Server" "-" TYPE_X86_CPU, "clflushopt", "off" }, -}; -const size_t pc_compat_2_11_len = G_N_ELEMENTS(pc_compat_2_11); - -GlobalProperty pc_compat_2_10[] = { - { TYPE_X86_CPU, "x-hv-max-vps", "0x40" }, - { "i440FX-pcihost", "x-pci-hole64-fix", "off" }, - { "q35-pcihost", "x-pci-hole64-fix", "off" }, -}; -const size_t pc_compat_2_10_len = G_N_ELEMENTS(pc_compat_2_10); - -GlobalProperty pc_compat_2_9[] = { - { "mch", "extended-tseg-mbytes", "0" }, -}; -const size_t pc_compat_2_9_len = G_N_ELEMENTS(pc_compat_2_9); - -GlobalProperty pc_compat_2_8[] = { - { TYPE_X86_CPU, "tcg-cpuid", "off" }, - { "kvmclock", "x-mach-use-reliable-get-clock", "off" }, - { "ICH9-LPC", "x-smi-broadcast", "off" }, - { TYPE_X86_CPU, "vmware-cpuid-freq", "off" }, - { "Haswell-" TYPE_X86_CPU, "stepping", "1" }, -}; -const size_t pc_compat_2_8_len = G_N_ELEMENTS(pc_compat_2_8); - /* * @PC_FW_DATA: * Size of the chunk of memory at the top of RAM for the BIOS ACPI tables @@ -639,7 +560,6 @@ void xen_load_linux(PCMachineState *pcms) { int i; FWCfgState *fw_cfg; - PCMachineClass *pcmc = PC_MACHINE_GET_CLASS(pcms); X86MachineState *x86ms = X86_MACHINE(pcms); assert(MACHINE(pcms)->kernel_filename != NULL); @@ -649,7 +569,7 @@ void xen_load_linux(PCMachineState *pcms) fw_cfg_add_i16(fw_cfg, FW_CFG_NB_CPUS, x86ms->boot_cpus); rom_set_fw(fw_cfg); - x86_load_linux(x86ms, fw_cfg, PC_FW_DATA, pcmc->pvh_enabled); + x86_load_linux(x86ms, fw_cfg, PC_FW_DATA); for (i = 0; i < nb_option_roms; i++) { assert(!strcmp(option_rom[i].name, "linuxboot_dma.bin") || !strcmp(option_rom[i].name, "pvh.bin") || @@ -983,7 +903,7 @@ void pc_memory_init(PCMachineState *pcms, } if (linux_boot) { - x86_load_linux(x86ms, fw_cfg, PC_FW_DATA, pcmc->pvh_enabled); + x86_load_linux(x86ms, fw_cfg, PC_FW_DATA); } for (i = 0; i < nb_option_roms; i++) { @@ -1724,7 +1644,6 @@ static void pc_machine_class_init(ObjectClass *oc, const void *data) pcmc->has_reserved_memory = true; pcmc->enforce_amd_1tb_hole = true; pcmc->isa_bios_alias = true; - pcmc->pvh_enabled = true; pcmc->kvmclock_create_always = true; x86mc->apic_xrupt_override = true; assert(!mc->get_hotplug_handler); diff --git a/hw/i386/pc_piix.c b/hw/i386/pc_piix.c index f9e0bca97436..4d71e0d51a28 100644 --- a/hw/i386/pc_piix.c +++ b/hw/i386/pc_piix.c @@ -644,85 +644,6 @@ static void pc_i440fx_machine_4_1_options(MachineClass *m) DEFINE_I440FX_MACHINE(4, 1); -static void pc_i440fx_machine_4_0_options(MachineClass *m) -{ - PCMachineClass *pcmc = PC_MACHINE_CLASS(m); - pc_i440fx_machine_4_1_options(m); - pcmc->default_cpu_version = CPU_VERSION_LEGACY; - compat_props_add(m->compat_props, hw_compat_4_0, hw_compat_4_0_len); - compat_props_add(m->compat_props, pc_compat_4_0, pc_compat_4_0_len); -} - -DEFINE_I440FX_MACHINE(4, 0); - -static void pc_i440fx_machine_3_1_options(MachineClass *m) -{ - PCMachineClass *pcmc = PC_MACHINE_CLASS(m); - - pc_i440fx_machine_4_0_options(m); - m->smbus_no_migration_support = true; - pcmc->pvh_enabled = false; - compat_props_add(m->compat_props, hw_compat_3_1, hw_compat_3_1_len); - compat_props_add(m->compat_props, pc_compat_3_1, pc_compat_3_1_len); -} - -DEFINE_I440FX_MACHINE(3, 1); - -static void pc_i440fx_machine_3_0_options(MachineClass *m) -{ - pc_i440fx_machine_3_1_options(m); - compat_props_add(m->compat_props, hw_compat_3_0, hw_compat_3_0_len); - compat_props_add(m->compat_props, pc_compat_3_0, pc_compat_3_0_len); -} - -DEFINE_I440FX_MACHINE(3, 0); - -static void pc_i440fx_machine_2_12_options(MachineClass *m) -{ - pc_i440fx_machine_3_0_options(m); - compat_props_add(m->compat_props, hw_compat_2_12, hw_compat_2_12_len); - compat_props_add(m->compat_props, pc_compat_2_12, pc_compat_2_12_len); -} - -DEFINE_I440FX_MACHINE(2, 12); - -static void pc_i440fx_machine_2_11_options(MachineClass *m) -{ - pc_i440fx_machine_2_12_options(m); - compat_props_add(m->compat_props, hw_compat_2_11, hw_compat_2_11_len); - compat_props_add(m->compat_props, pc_compat_2_11, pc_compat_2_11_len); -} - -DEFINE_I440FX_MACHINE(2, 11); - -static void pc_i440fx_machine_2_10_options(MachineClass *m) -{ - pc_i440fx_machine_2_11_options(m); - compat_props_add(m->compat_props, hw_compat_2_10, hw_compat_2_10_len); - compat_props_add(m->compat_props, pc_compat_2_10, pc_compat_2_10_len); - m->auto_enable_numa_with_memhp = false; -} - -DEFINE_I440FX_MACHINE(2, 10); - -static void pc_i440fx_machine_2_9_options(MachineClass *m) -{ - pc_i440fx_machine_2_10_options(m); - compat_props_add(m->compat_props, hw_compat_2_9, hw_compat_2_9_len); - compat_props_add(m->compat_props, pc_compat_2_9, pc_compat_2_9_len); -} - -DEFINE_I440FX_MACHINE(2, 9); - -static void pc_i440fx_machine_2_8_options(MachineClass *m) -{ - pc_i440fx_machine_2_9_options(m); - compat_props_add(m->compat_props, hw_compat_2_8, hw_compat_2_8_len); - compat_props_add(m->compat_props, pc_compat_2_8, pc_compat_2_8_len); -} - -DEFINE_I440FX_MACHINE(2, 8); - #ifdef CONFIG_XEN static void xenfv_machine_4_2_options(MachineClass *m) { @@ -734,16 +655,4 @@ static void xenfv_machine_4_2_options(MachineClass *m) DEFINE_PC_MACHINE(xenfv_4_2, "xenfv-4.2", pc_xen_hvm_init, xenfv_machine_4_2_options); - -static void xenfv_machine_3_1_options(MachineClass *m) -{ - pc_i440fx_machine_3_1_options(m); - m->desc = "Xen Fully-virtualized PC"; - m->alias = "xenfv"; - m->max_cpus = HVM_MAX_VCPUS; - m->default_machine_opts = "accel=xen,suppress-vmdesc=on"; -} - -DEFINE_PC_MACHINE(xenfv, "xenfv-3.1", pc_xen_hvm_init, - xenfv_machine_3_1_options); #endif diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c index 9158631f761a..cb23322f5a44 100644 --- a/hw/i386/pc_q35.c +++ b/hw/i386/pc_q35.c @@ -575,98 +575,3 @@ static void pc_q35_machine_4_1_options(MachineClass *m) } DEFINE_Q35_MACHINE(4, 1); - -static void pc_q35_machine_4_0_1_options(MachineClass *m) -{ - PCMachineClass *pcmc = PC_MACHINE_CLASS(m); - pc_q35_machine_4_1_options(m); - pcmc->default_cpu_version = CPU_VERSION_LEGACY; - /* - * This is the default machine for the 4.0-stable branch. It is basically - * a 4.0 that doesn't use split irqchip by default. It MUST hence apply the - * 4.0 compat props. - */ - compat_props_add(m->compat_props, hw_compat_4_0, hw_compat_4_0_len); - compat_props_add(m->compat_props, pc_compat_4_0, pc_compat_4_0_len); -} - -DEFINE_Q35_MACHINE_BUGFIX(4, 0, 1); - -static void pc_q35_machine_4_0_options(MachineClass *m) -{ - pc_q35_machine_4_0_1_options(m); - m->default_kernel_irqchip_split = true; - /* Compat props are applied by the 4.0.1 machine */ -} - -DEFINE_Q35_MACHINE(4, 0); - -static void pc_q35_machine_3_1_options(MachineClass *m) -{ - PCMachineClass *pcmc = PC_MACHINE_CLASS(m); - - pc_q35_machine_4_0_options(m); - m->default_kernel_irqchip_split = false; - m->smbus_no_migration_support = true; - pcmc->pvh_enabled = false; - compat_props_add(m->compat_props, hw_compat_3_1, hw_compat_3_1_len); - compat_props_add(m->compat_props, pc_compat_3_1, pc_compat_3_1_len); -} - -DEFINE_Q35_MACHINE(3, 1); - -static void pc_q35_machine_3_0_options(MachineClass *m) -{ - pc_q35_machine_3_1_options(m); - compat_props_add(m->compat_props, hw_compat_3_0, hw_compat_3_0_len); - compat_props_add(m->compat_props, pc_compat_3_0, pc_compat_3_0_len); -} - -DEFINE_Q35_MACHINE(3, 0); - -static void pc_q35_machine_2_12_options(MachineClass *m) -{ - pc_q35_machine_3_0_options(m); - compat_props_add(m->compat_props, hw_compat_2_12, hw_compat_2_12_len); - compat_props_add(m->compat_props, pc_compat_2_12, pc_compat_2_12_len); -} - -DEFINE_Q35_MACHINE(2, 12); - -static void pc_q35_machine_2_11_options(MachineClass *m) -{ - pc_q35_machine_2_12_options(m); - m->default_nic = "e1000"; - compat_props_add(m->compat_props, hw_compat_2_11, hw_compat_2_11_len); - compat_props_add(m->compat_props, pc_compat_2_11, pc_compat_2_11_len); -} - -DEFINE_Q35_MACHINE(2, 11); - -static void pc_q35_machine_2_10_options(MachineClass *m) -{ - pc_q35_machine_2_11_options(m); - compat_props_add(m->compat_props, hw_compat_2_10, hw_compat_2_10_len); - compat_props_add(m->compat_props, pc_compat_2_10, pc_compat_2_10_len); - m->auto_enable_numa_with_memhp = false; -} - -DEFINE_Q35_MACHINE(2, 10); - -static void pc_q35_machine_2_9_options(MachineClass *m) -{ - pc_q35_machine_2_10_options(m); - compat_props_add(m->compat_props, hw_compat_2_9, hw_compat_2_9_len); - compat_props_add(m->compat_props, pc_compat_2_9, pc_compat_2_9_len); -} - -DEFINE_Q35_MACHINE(2, 9); - -static void pc_q35_machine_2_8_options(MachineClass *m) -{ - pc_q35_machine_2_9_options(m); - compat_props_add(m->compat_props, hw_compat_2_8, hw_compat_2_8_len); - compat_props_add(m->compat_props, pc_compat_2_8, pc_compat_2_8_len); -} - -DEFINE_Q35_MACHINE(2, 8); diff --git a/hw/i386/vapic.c b/hw/i386/vapic.c index 670a50524d66..41e5ca26dfd8 100644 --- a/hw/i386/vapic.c +++ b/hw/i386/vapic.c @@ -16,6 +16,7 @@ #include "system/cpus.h" #include "system/hw_accel.h" #include "system/kvm.h" +#include "system/whpx.h" #include "system/runstate.h" #include "system/address-spaces.h" #include "hw/i386/apic_internal.h" @@ -229,7 +230,8 @@ static int evaluate_tpr_instruction(VAPICROMState *s, X86CPU *cpu, return -1; } - if (kvm_enabled() && !kvm_irqchip_in_kernel()) { + if ((kvm_enabled() && !kvm_irqchip_in_kernel()) + || (whpx_enabled() && !whpx_irqchip_in_kernel())) { /* * KVM without kernel-based TPR access reporting will pass an IP that * points after the accessing instruction. So we need to look backward @@ -549,7 +551,7 @@ static int patch_hypercalls(VAPICROMState *s) cpu_physical_memory_read(rom_paddr, rom, s->rom_size); for (pos = 0; pos < s->rom_size - sizeof(vmcall_pattern); pos++) { - if (kvm_irqchip_in_kernel()) { + if (kvm_enabled() && kvm_irqchip_in_kernel()) { pattern = outl_pattern; alternates[0] = outl_pattern[7]; alternates[1] = outl_pattern[7]; @@ -679,16 +681,25 @@ static void vapic_write(void *opaque, hwaddr addr, uint64_t data, } break; case 1: - if (kvm_enabled()) { + if (kvm_enabled() || (whpx_enabled() && !whpx_irqchip_in_kernel())) { /* * Disable triggering instruction in ROM by writing a NOP. * * We cannot do this in TCG mode as the reported IP is not * accurate. + * + * Oddly enough, KVM increments EIP _before_ the execution + * of the instruction is finished. */ pause_all_vcpus(); - patch_byte(cpu, env->eip - 2, 0x66); - patch_byte(cpu, env->eip - 1, 0x90); + if (!kvm_enabled()) { + patch_byte(cpu, env->eip, 0x66); + patch_byte(cpu, env->eip + 1, 0x90); + } + else { + patch_byte(cpu, env->eip - 2, 0x66); + patch_byte(cpu, env->eip - 1, 0x90); + } resume_all_vcpus(); } @@ -705,7 +716,8 @@ static void vapic_write(void *opaque, hwaddr addr, uint64_t data, break; default: case 4: - if (!kvm_irqchip_in_kernel()) { + if ((kvm_enabled() && !kvm_irqchip_in_kernel()) + || (whpx_enabled() && !whpx_irqchip_in_kernel())) { apic_poll_irq(cpu->apic_state); } break; diff --git a/hw/i386/vmmouse.c b/hw/i386/vmmouse.c index 2ae7f3a242e1..c1aeeca0c9a8 100644 --- a/hw/i386/vmmouse.c +++ b/hw/i386/vmmouse.c @@ -72,7 +72,7 @@ struct VMMouseState { ISAKBDState *i8042; }; -static void vmmouse_get_data(uint32_t *data) +static void vmmouse_get_data(uint64_t *data) { X86CPU *cpu = X86_CPU(current_cpu); CPUX86State *env = &cpu->env; @@ -82,7 +82,7 @@ static void vmmouse_get_data(uint32_t *data) data[4] = env->regs[R_ESI]; data[5] = env->regs[R_EDI]; } -static void vmmouse_set_data(const uint32_t *data) +static void vmmouse_set_data(const uint64_t *data) { X86CPU *cpu = X86_CPU(current_cpu); CPUX86State *env = &cpu->env; @@ -197,7 +197,7 @@ static void vmmouse_disable(VMMouseState *s) vmmouse_remove_handler(s); } -static void vmmouse_data(VMMouseState *s, uint32_t *data, uint32_t size) +static void vmmouse_data(VMMouseState *s, uint64_t *data, uint32_t size) { int i; @@ -221,7 +221,7 @@ static void vmmouse_data(VMMouseState *s, uint32_t *data, uint32_t size) static uint32_t vmmouse_ioport_read(void *opaque, uint32_t addr) { VMMouseState *s = opaque; - uint32_t data[6]; + uint64_t data[6]; uint16_t command; vmmouse_get_data(data); @@ -247,7 +247,7 @@ static uint32_t vmmouse_ioport_read(void *opaque, uint32_t addr) vmmouse_request_absolute(s); break; default: - printf("vmmouse: unknown command %x\n", data[1]); + printf("vmmouse: unknown command %" PRIx64 "\n", data[1]); break; } break; diff --git a/hw/i386/x86-common.c b/hw/i386/x86-common.c index de4cd7650a40..fde05fa7d7ba 100644 --- a/hw/i386/x86-common.c +++ b/hw/i386/x86-common.c @@ -642,8 +642,7 @@ static bool load_elfboot(const char *kernel_filename, void x86_load_linux(X86MachineState *x86ms, FWCfgState *fw_cfg, - int acpi_data_size, - bool pvh_enabled) + int acpi_data_size) { uint16_t protocol; int setup_size, kernel_size, cmdline_size; @@ -704,8 +703,7 @@ void x86_load_linux(X86MachineState *x86ms, * saving the PVH entry point used by the x86/HVM direct boot ABI. * If load_elfboot() is successful, populate the fw_cfg info. */ - if (pvh_enabled && - load_elfboot(kernel_filename, kernel_size, + if (load_elfboot(kernel_filename, kernel_size, header, pvh_start_addr, fw_cfg)) { fclose(f); @@ -1020,17 +1018,11 @@ void x86_isa_bios_init(MemoryRegion *isa_bios, MemoryRegion *isa_memory, memory_region_set_readonly(isa_bios, read_only); } -void x86_bios_rom_init(X86MachineState *x86ms, const char *default_firmware, - MemoryRegion *rom_memory, bool isapc_ram_fw) +static int get_bios_size(X86MachineState *x86ms, + const char *bios_name, char *filename) { - const char *bios_name; - char *filename; int bios_size; - ssize_t ret; - /* BIOS load */ - bios_name = MACHINE(x86ms)->firmware ?: default_firmware; - filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); if (filename) { bios_size = get_image_size(filename, NULL); } else { @@ -1040,6 +1032,21 @@ void x86_bios_rom_init(X86MachineState *x86ms, const char *default_firmware, (bios_size % 65536) != 0) { goto bios_error; } + + return bios_size; + + bios_error: + fprintf(stderr, "qemu: could not load PC BIOS '%s'\n", bios_name); + exit(1); +} + +static void load_bios_from_file(X86MachineState *x86ms, const char *bios_name, + char *filename, int bios_size, + bool isapc_ram_fw) +{ + ssize_t ret; + + /* BIOS load */ if (machine_require_guest_memfd(MACHINE(x86ms))) { memory_region_init_ram_guest_memfd(&x86ms->bios, NULL, "pc.bios", bios_size, &error_fatal); @@ -1068,7 +1075,47 @@ void x86_bios_rom_init(X86MachineState *x86ms, const char *default_firmware, goto bios_error; } } - g_free(filename); + + return; + + bios_error: + fprintf(stderr, "qemu: could not load PC BIOS '%s'\n", bios_name); + exit(1); +} + +void x86_bios_rom_reload(X86MachineState *x86ms) +{ + int bios_size; + const char *bios_name; + char *filename; + + if (memory_region_size(&x86ms->bios) == 0) { + /* if -bios is not used */ + return; + } + + bios_name = MACHINE(x86ms)->firmware ?: "bios.bin"; + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); + + bios_size = get_bios_size(x86ms, bios_name, filename); + + void *ptr = memory_region_get_ram_ptr(&x86ms->bios); + load_image_size(filename, ptr, bios_size); + x86_firmware_configure(0x100000000ULL - bios_size, ptr, bios_size); +} + +void x86_bios_rom_init(X86MachineState *x86ms, const char *default_firmware, + MemoryRegion *rom_memory, bool isapc_ram_fw) +{ + int bios_size; + const char *bios_name; + g_autofree char *filename; + + bios_name = MACHINE(x86ms)->firmware ?: default_firmware; + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); + + bios_size = get_bios_size(x86ms, bios_name, filename); + load_bios_from_file(x86ms, bios_name, filename, bios_size, isapc_ram_fw); if (!machine_require_guest_memfd(MACHINE(x86ms))) { /* map the last 128KB of the BIOS in ISA space */ @@ -1081,8 +1128,4 @@ void x86_bios_rom_init(X86MachineState *x86ms, const char *default_firmware, (uint32_t)(-bios_size), &x86ms->bios); return; - -bios_error: - fprintf(stderr, "qemu: could not load PC BIOS '%s'\n", bios_name); - exit(1); } diff --git a/hw/i386/x86-iommu.c b/hw/i386/x86-iommu.c index 33ac0bfc97e8..a57445404ef8 100644 --- a/hw/i386/x86-iommu.c +++ b/hw/i386/x86-iommu.c @@ -129,7 +129,6 @@ static const Property x86_iommu_properties[] = { DEFINE_PROP_ON_OFF_AUTO("intremap", X86IOMMUState, intr_supported, ON_OFF_AUTO_AUTO), DEFINE_PROP_BOOL("device-iotlb", X86IOMMUState, dt_supported, false), - DEFINE_PROP_BOOL("pt", X86IOMMUState, pt_supported, true), DEFINE_PROP_BOOL("dma-translation", X86IOMMUState, dma_translation, true), }; diff --git a/hw/i386/xen/xen-hvm.c b/hw/i386/xen/xen-hvm.c index a6e1683885a0..67d3e836eb10 100644 --- a/hw/i386/xen/xen-hvm.c +++ b/hw/i386/xen/xen-hvm.c @@ -622,7 +622,7 @@ void xen_hvm_init_pc(PCMachineState *pcms, MemoryRegion **ram_memory) xen_register_ioreq(state, max_cpus, HVM_IOREQSRV_BUFIOREQ_ATOMIC, - &xen_memory_listener); + &xen_memory_listener, true); xen_is_stubdomain = xen_check_stubdomain(state->xenstore); @@ -720,7 +720,8 @@ void arch_xen_set_memory(XenIOState *state, MemoryRegionSection *section, return; } - if (log_dirty != add) { + if (log_dirty != add && + !(section->mr == framebuffer && start_addr > 0xbffff)) { return; } diff --git a/hw/i3c/Kconfig b/hw/i3c/Kconfig new file mode 100644 index 000000000000..d5c6d4049bc8 --- /dev/null +++ b/hw/i3c/Kconfig @@ -0,0 +1,15 @@ +config I3C + bool + +config DW_I3C + bool + +config I3C_DEVICES + # Device group for i3c devices which can reasonably be user-plugged to any + # board's i3c bus. + bool + +config MOCK_I3C_TARGET + bool + select I3C + default y if I3C_DEVICES diff --git a/hw/i3c/aspeed_i3c.c b/hw/i3c/aspeed_i3c.c new file mode 100644 index 000000000000..bac8c55bb091 --- /dev/null +++ b/hw/i3c/aspeed_i3c.c @@ -0,0 +1,258 @@ +/* + * ASPEED I3C Controller + * + * Copyright (C) 2021 ASPEED Technology Inc. + * Copyright (C) 2025 Google, LLC. + * + * This code is licensed under the GPL version 2 or later. See + * the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/error-report.h" +#include "hw/i3c/aspeed_i3c.h" +#include "hw/core/registerfields.h" +#include "hw/core/qdev-properties.h" +#include "qapi/error.h" +#include "migration/vmstate.h" +#include "trace.h" + +/* I3C Controller Registers */ +REG32(I3C1_REG0, 0x10) +REG32(I3C1_REG1, 0x14) + FIELD(I3C1_REG1, I2C_MODE, 0, 1) + FIELD(I3C1_REG1, SLV_TEST_MODE, 1, 1) + FIELD(I3C1_REG1, ACT_MODE, 2, 2) + FIELD(I3C1_REG1, PENDING_INT, 4, 4) + FIELD(I3C1_REG1, SA, 8, 7) + FIELD(I3C1_REG1, SA_EN, 15, 1) + FIELD(I3C1_REG1, INST_ID, 16, 4) +REG32(I3C2_REG0, 0x20) +REG32(I3C2_REG1, 0x24) + FIELD(I3C2_REG1, I2C_MODE, 0, 1) + FIELD(I3C2_REG1, SLV_TEST_MODE, 1, 1) + FIELD(I3C2_REG1, ACT_MODE, 2, 2) + FIELD(I3C2_REG1, PENDING_INT, 4, 4) + FIELD(I3C2_REG1, SA, 8, 7) + FIELD(I3C2_REG1, SA_EN, 15, 1) + FIELD(I3C2_REG1, INST_ID, 16, 4) +REG32(I3C3_REG0, 0x30) +REG32(I3C3_REG1, 0x34) + FIELD(I3C3_REG1, I2C_MODE, 0, 1) + FIELD(I3C3_REG1, SLV_TEST_MODE, 1, 1) + FIELD(I3C3_REG1, ACT_MODE, 2, 2) + FIELD(I3C3_REG1, PENDING_INT, 4, 4) + FIELD(I3C3_REG1, SA, 8, 7) + FIELD(I3C3_REG1, SA_EN, 15, 1) + FIELD(I3C3_REG1, INST_ID, 16, 4) +REG32(I3C4_REG0, 0x40) +REG32(I3C4_REG1, 0x44) + FIELD(I3C4_REG1, I2C_MODE, 0, 1) + FIELD(I3C4_REG1, SLV_TEST_MODE, 1, 1) + FIELD(I3C4_REG1, ACT_MODE, 2, 2) + FIELD(I3C4_REG1, PENDING_INT, 4, 4) + FIELD(I3C4_REG1, SA, 8, 7) + FIELD(I3C4_REG1, SA_EN, 15, 1) + FIELD(I3C4_REG1, INST_ID, 16, 4) +REG32(I3C5_REG0, 0x50) +REG32(I3C5_REG1, 0x54) + FIELD(I3C5_REG1, I2C_MODE, 0, 1) + FIELD(I3C5_REG1, SLV_TEST_MODE, 1, 1) + FIELD(I3C5_REG1, ACT_MODE, 2, 2) + FIELD(I3C5_REG1, PENDING_INT, 4, 4) + FIELD(I3C5_REG1, SA, 8, 7) + FIELD(I3C5_REG1, SA_EN, 15, 1) + FIELD(I3C5_REG1, INST_ID, 16, 4) +REG32(I3C6_REG0, 0x60) +REG32(I3C6_REG1, 0x64) + FIELD(I3C6_REG1, I2C_MODE, 0, 1) + FIELD(I3C6_REG1, SLV_TEST_MODE, 1, 1) + FIELD(I3C6_REG1, ACT_MODE, 2, 2) + FIELD(I3C6_REG1, PENDING_INT, 4, 4) + FIELD(I3C6_REG1, SA, 8, 7) + FIELD(I3C6_REG1, SA_EN, 15, 1) + FIELD(I3C6_REG1, INST_ID, 16, 4) + +static const uint32_t ast2600_i3c_controller_ro[ASPEED_I3C_NR_REGS] = { + [R_I3C1_REG0] = 0xcc000000, + [R_I3C1_REG1] = 0xfff00000, + [R_I3C2_REG0] = 0xcc000000, + [R_I3C2_REG1] = 0xfff00000, + [R_I3C3_REG0] = 0xcc000000, + [R_I3C3_REG1] = 0xfff00000, + [R_I3C4_REG0] = 0xcc000000, + [R_I3C4_REG1] = 0xfff00000, + [R_I3C5_REG0] = 0xcc000000, + [R_I3C5_REG1] = 0xfff00000, + [R_I3C6_REG0] = 0xcc000000, + [R_I3C6_REG1] = 0xfff00000, +}; + +static uint64_t aspeed_i3c_read(void *opaque, hwaddr addr, unsigned int size) +{ + AspeedI3CState *s = ASPEED_I3C(opaque); + uint64_t val = 0; + + val = s->regs[addr >> 2]; + + trace_aspeed_i3c_read(addr, val); + + return val; +} + +static void aspeed_i3c_write(void *opaque, + hwaddr addr, + uint64_t data, + unsigned int size) +{ + AspeedI3CState *s = ASPEED_I3C(opaque); + + trace_aspeed_i3c_write(addr, data); + + addr >>= 2; + + data &= ~ast2600_i3c_controller_ro[addr]; + /* I3C controller register */ + switch (addr) { + case R_I3C1_REG1: + case R_I3C2_REG1: + case R_I3C3_REG1: + case R_I3C4_REG1: + case R_I3C5_REG1: + case R_I3C6_REG1: + if (data & R_I3C1_REG1_I2C_MODE_MASK) { + qemu_log_mask(LOG_UNIMP, + "%s: Unsupported I2C mode [0x%08" HWADDR_PRIx + "]=%08" PRIx64 "\n", + __func__, addr << 2, data); + break; + } + if (data & R_I3C1_REG1_SA_EN_MASK) { + qemu_log_mask(LOG_UNIMP, + "%s: Unsupported slave mode [%08" HWADDR_PRIx + "]=0x%08" PRIx64 "\n", + __func__, addr << 2, data); + break; + } + s->regs[addr] = data; + break; + default: + s->regs[addr] = data; + break; + } +} + +static const MemoryRegionOps aspeed_i3c_ops = { + .read = aspeed_i3c_read, + .write = aspeed_i3c_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + } +}; + +I3CBus *aspeed_i3c_get_bus(AspeedI3CState *s, uint8_t bus_num) +{ + if (bus_num < ARRAY_SIZE(s->devices)) { + return s->devices[bus_num].bus; + } + /* Developer error, fail fast. */ + g_assert_not_reached(); +} + +static void aspeed_i3c_reset(DeviceState *dev) +{ + AspeedI3CState *s = ASPEED_I3C(dev); + memset(s->regs, 0, sizeof(s->regs)); +} + +static void aspeed_i3c_instance_init(Object *obj) +{ + AspeedI3CState *s = ASPEED_I3C(obj); + int i; + + for (i = 0; i < ASPEED_I3C_NR_DEVICES; ++i) { + object_initialize_child(obj, "device[*]", &s->devices[i], + TYPE_DW_I3C); + } +} + +static void aspeed_i3c_realize(DeviceState *dev, Error **errp) +{ + int i; + AspeedI3CState *s = ASPEED_I3C(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + memory_region_init(&s->iomem_container, OBJECT(s), + TYPE_ASPEED_I3C ".container", 0x8000); + + sysbus_init_mmio(sbd, &s->iomem_container); + + memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_i3c_ops, s, + TYPE_ASPEED_I3C ".regs", ASPEED_I3C_NR_REGS << 2); + + memory_region_add_subregion(&s->iomem_container, 0x0, &s->iomem); + + for (i = 0; i < ASPEED_I3C_NR_DEVICES; ++i) { + Object *i3c_dev = OBJECT(&s->devices[i]); + + if (!object_property_set_uint(i3c_dev, "device-id", i, errp)) { + return; + } + + if (!sysbus_realize(SYS_BUS_DEVICE(i3c_dev), errp)) { + return; + } + + /* + * Register Address of I3CX Device = + * (Base Address of Global Register) + (Offset of I3CX) + Offset + * X = 0, 1, 2, 3, 4, 5 + * Offset of I3C0 = 0x2000 + * Offset of I3C1 = 0x3000 + * Offset of I3C2 = 0x4000 + * Offset of I3C3 = 0x5000 + * Offset of I3C4 = 0x6000 + * Offset of I3C5 = 0x7000 + */ + memory_region_add_subregion(&s->iomem_container, + 0x2000 + i * 0x1000, &s->devices[i].mr); + } + +} + +static const VMStateDescription vmstate_aspeed_i3c = { + .name = TYPE_ASPEED_I3C, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, AspeedI3CState, ASPEED_I3C_NR_REGS), + VMSTATE_STRUCT_ARRAY(devices, AspeedI3CState, ASPEED_I3C_NR_DEVICES, 1, + vmstate_dw_i3c, DWI3C), + VMSTATE_END_OF_LIST(), + } +}; + +static void aspeed_i3c_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = aspeed_i3c_realize; + device_class_set_legacy_reset(dc, aspeed_i3c_reset); + dc->desc = "Aspeed I3C Controller"; + dc->vmsd = &vmstate_aspeed_i3c; +} + +static const TypeInfo aspeed_i3c_types[] = { + { + .name = TYPE_ASPEED_I3C, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_init = aspeed_i3c_instance_init, + .instance_size = sizeof(AspeedI3CState), + .class_init = aspeed_i3c_class_init, + }, +}; + +DEFINE_TYPES(aspeed_i3c_types) diff --git a/hw/i3c/core.c b/hw/i3c/core.c new file mode 100644 index 000000000000..168526003dab --- /dev/null +++ b/hw/i3c/core.c @@ -0,0 +1,664 @@ +/* + * QEMU I3C bus interface. + * + * Copyright 2025 Google LLC + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qapi/error.h" +#include "trace.h" +#include "hw/i3c/i3c.h" +#include "hw/core/hotplug.h" +#include "hw/core/qdev-properties.h" + +/* + * In test mode (enabled by ENTTM CCC) we're supposed to send a random PID + * during ENTDAA, so we'll just send "QEMU". + */ +#define TEST_MODE_PROVISIONED_ID 0x0000554d4551ULL + +static const Property i3c_props[] = { + DEFINE_PROP_UINT8("static-address", struct I3CTarget, static_address, 0), + DEFINE_PROP_UINT8("dcr", struct I3CTarget, dcr, 0), + DEFINE_PROP_UINT8("bcr", struct I3CTarget, bcr, 0), + DEFINE_PROP_UINT64("pid", struct I3CTarget, pid, 0), +}; + +static void i3c_realize(BusState *bus, Error **errp) +{ + qbus_set_bus_hotplug_handler(bus); +} + +static void i3c_class_init(ObjectClass *klass, const void *data) +{ + BusClass *k = BUS_CLASS(klass); + k->realize = i3c_realize; +} + +I3CBus *i3c_init_bus(DeviceState *parent, const char *name) +{ + return i3c_init_bus_type(TYPE_I3C_BUS, parent, name); +} + +I3CBus *i3c_init_bus_type(const char *type, DeviceState *parent, + const char *name) +{ + I3CBus *bus; + + bus = I3C_BUS(qbus_new(type, parent, name)); + QLIST_INIT(&bus->current_devs); + bus->broadcast = false; + bus->in_entdaa = false; + bus->in_ccc = false; + + /* I2C init. */ + g_autofree gchar *i2c_bus_name = g_strdup_printf("%s-legacy-i2c", name); + bus->i2c_bus = i2c_init_bus(parent, i2c_bus_name); + + return bus; +} + +bool i3c_bus_busy(I3CBus *bus) +{ + return !QLIST_EMPTY(&bus->current_devs); +} + +static bool i3c_target_match(I3CTarget *candidate, uint8_t address, + bool is_recv, bool broadcast, bool in_entdaa) +{ + /* Once a target has a dynamic address, it only responds to that. */ + uint8_t targ_addr = candidate->address ? candidate->address : + candidate->static_address; + + if (in_entdaa) { + if (address != I3C_BROADCAST) { + g_autofree char *path = + object_get_canonical_path(OBJECT(candidate)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: I3C Address 0x%.2x sent during " + "ENTDAA instead of a broadcast address\n", + path, address); + return false; + } + + /* + * Targets should only ACK ENTDAA broadcasts if they have no dynamic + * address. + */ + return candidate->address == 0; + } + + /* Return if our addresses match, or if it's a broadcast. */ + return targ_addr == address || broadcast; +} + +bool i3c_target_match_and_add(I3CBus *bus, I3CTarget *target, uint8_t address, + enum I3CEvent event) +{ + I3CTargetClass *tc = I3C_TARGET_GET_CLASS(target); + bool matched = tc->target_match(target, address, event == I3C_START_RECV, + bus->broadcast, bus->in_entdaa); + + if (matched) { + I3CNode *node = g_new(struct I3CNode, 1); + node->target = target; + QLIST_INSERT_HEAD(&bus->current_devs, node, next); + } + return matched; +} + +bool i3c_scan_bus(I3CBus *bus, uint8_t address, enum I3CEvent event) +{ + BusChild *child; + I3CNode *node, *next; + + /* Clear out any devices from a previous (re-)START. */ + QLIST_FOREACH_SAFE(node, &bus->current_devs, next, next) { + QLIST_REMOVE(node, next); + g_free(node); + } + + QTAILQ_FOREACH(child, &bus->parent_obj.children, sibling) { + DeviceState *qdev = child->child; + I3CTarget *target = I3C_TARGET(qdev); + + if (i3c_target_match_and_add(bus, target, address, event)) { + return true; + } + } + + /* No one on the bus could respond. */ + return false; +} + +/* Class-level event handling, since we do some CCCs at the class level. */ +static int i3c_target_event(I3CTarget *t, enum I3CEvent event) +{ + I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t); + trace_i3c_target_event(t->address, event); + + if (event == I3C_STOP) { + t->curr_ccc = 0; + t->ccc_byte_offset = 0; + t->in_ccc = false; + } + return tc->event(t, event); +} + +/* + * Sends a START or repeated START and the address for an I3C transaction. + * + * This function returns 0 if a device on the bus was able to respond to the + * address, and non-zero otherwise. + * A non-zero return represents a NACK. + */ +static int i3c_do_start_transfer(I3CBus *bus, uint8_t address, + enum I3CEvent event) +{ + I3CTargetClass *tc; + I3CNode *node; + + if (address == I3C_BROADCAST) { + bus->broadcast = true; + /* If we're not in ENTDAA, a broadcast is the start of a new CCC. */ + if (!bus->in_entdaa) { + bus->in_ccc = false; + } + } else { + bus->broadcast = false; + } + + /* No one responded to the address, NACK it. */ + if (!i3c_scan_bus(bus, address, event)) { + return -1; + } + + QLIST_FOREACH(node, &bus->current_devs, next) { + I3CTarget *t = node->target; + + tc = I3C_TARGET_GET_CLASS(t); + if (tc->event) { + int rv = i3c_target_event(t, event); + if (rv && !bus->broadcast) { + return rv; + } + } + } + + return 0; +} + +int i3c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv) +{ + trace_i3c_start_transfer(address, is_recv); + return i3c_do_start_transfer(bus, address, is_recv + ? I3C_START_RECV + : I3C_START_SEND); +} + +int i3c_start_recv(I3CBus *bus, uint8_t address) +{ + trace_i3c_start_transfer(address, true); + return i3c_do_start_transfer(bus, address, I3C_START_RECV); +} + +int i3c_start_send(I3CBus *bus, uint8_t address) +{ + trace_i3c_start_transfer(address, false); + return i3c_do_start_transfer(bus, address, I3C_START_SEND); +} + +void i3c_end_transfer(I3CBus *bus) +{ + I3CTargetClass *tc; + I3CNode *node, *next; + + trace_i3c_end_transfer(); + + /* + * If we're in ENTDAA, we need to notify all devices when ENTDAA is done. + * This is because everyone initially participates due to the broadcast, + * but gradually drops out as they get assigned addresses. + * Since the current_devs list only stores who's currently participating, + * and not everyone who previously participated, we send the STOP to all + * children. + */ + if (bus->in_entdaa) { + BusChild *child; + + QTAILQ_FOREACH(child, &bus->parent_obj.children, sibling) { + DeviceState *qdev = child->child; + I3CTarget *t = I3C_TARGET(qdev); + tc = I3C_TARGET_GET_CLASS(t); + if (tc->event) { + i3c_target_event(t, I3C_STOP); + } + } + } else { + QLIST_FOREACH_SAFE(node, &bus->current_devs, next, next) { + I3CTarget *t = node->target; + tc = I3C_TARGET_GET_CLASS(t); + if (tc->event) { + i3c_target_event(t, I3C_STOP); + } + QLIST_REMOVE(node, next); + g_free(node); + } + } + bus->broadcast = false; + bus->in_entdaa = false; + bus->in_ccc = false; +} + +/* + * Any CCCs that are universal across all I3C devices should be handled here. + * Once they're handled, we pass the CCC up to the I3C target to do anything + * else it may want with the bytes. + */ +static int i3c_target_handle_ccc_write(I3CTarget *t, const uint8_t *data, + uint32_t num_to_send, uint32_t *num_sent) +{ + I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t); + *num_sent = 0; + + /* Is this the start of a new CCC? */ + if (!t->in_ccc) { + t->curr_ccc = *data; + t->in_ccc = true; + *num_sent = 1; + trace_i3c_target_handle_ccc(t->address, t->curr_ccc); + } + + switch (t->curr_ccc) { + case I3C_CCC_ENTDAA: + /* + * This is the last byte of ENTDAA, the controller is assigning us an + * address. + */ + if (t->ccc_byte_offset == 8) { + t->address = *data; + t->in_ccc = false; + t->curr_ccc = 0; + t->ccc_byte_offset = 0; + *num_sent = 1; + } + break; + case I3C_CCCD_SETDASA: + t->address = t->static_address; + break; + case I3C_CCC_SETAASA: + t->address = t->static_address; + break; + case I3C_CCC_RSTDAA: + t->address = 0; + break; + case I3C_CCCD_SETNEWDA: + /* If this isn't the CCC byte, it's our new address. */ + if (*num_sent == 0) { + t->address = *data; + *num_sent = 1; + } + break; + case I3C_CCC_ENTTM: + /* + * If there are still more to look at, the next byte is the test mode + * byte. + */ + if (*num_sent != num_to_send) { + /* Enter test mode if the byte is non-zero. Otherwise exit. */ + t->in_test_mode = !!data[*num_sent]; + ++*num_sent; + } + break; + /* Ignore other CCCs it's better to handle on a device-by-device basis. */ + default: + break; + } + return tc->handle_ccc_write(t, data, num_to_send, num_sent); +} + +int i3c_send_byte(I3CBus *bus, uint8_t data) +{ + /* + * Ignored, the caller can determine how many were sent based on if this was + * ACKed/NACKed. + */ + uint32_t num_sent = 0; + return i3c_send(bus, &data, 1, &num_sent); +} + +int i3c_send(I3CBus *bus, const uint8_t *data, uint32_t num_to_send, + uint32_t *num_sent) +{ + I3CTargetClass *tc; + I3CTarget *t; + I3CNode *node; + int ret = 0; + + /* If this message is a broadcast and no CCC has been found, grab it. */ + if (bus->broadcast && !bus->in_ccc) { + bus->ccc = *data; + bus->in_ccc = true; + /* + * We need to keep track if we're currently in ENTDAA. + * On any other CCC, the CCC is over on a RESTART or STOP, but ENTDAA + * is only over on a STOP. + */ + if (bus->ccc == I3C_CCC_ENTDAA) { + bus->in_entdaa = true; + } + } + + QLIST_FOREACH(node, &bus->current_devs, next) { + t = node->target; + tc = I3C_TARGET_GET_CLASS(t); + if (bus->in_ccc) { + if (!tc->handle_ccc_write) { + ret = -1; + continue; + } + ret = i3c_target_handle_ccc_write(t, data, num_to_send, num_sent); + /* Targets should only NACK on a direct CCC. */ + if (ret && !CCC_IS_DIRECT(bus->ccc)) { + ret = 0; + } + } else { + if (tc->send) { + ret = ret || tc->send(t, data, num_to_send, num_sent); + } else { + ret = -1; + } + } + } + + trace_i3c_send(*num_sent, num_to_send, ret == 0); + + return ret ? -1 : 0; +} + +static int i3c_target_handle_ccc_read(I3CTarget *t, uint8_t *data, + uint32_t num_to_read, uint32_t *num_read) +{ + I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t); + uint8_t read_count = 0; + uint64_t pid; + + switch (t->curr_ccc) { + case I3C_CCC_ENTDAA: + if (t->in_test_mode) { + pid = TEST_MODE_PROVISIONED_ID; + } else { + pid = t->pid; + } + /* Return the 6-byte PID, followed by BCR then DCR. */ + while (t->ccc_byte_offset < 6) { + if (read_count >= num_to_read) { + break; + } + data[read_count] = (pid >> (t->ccc_byte_offset * 8)) & 0xff; + t->ccc_byte_offset++; + read_count++; + } + if (read_count < num_to_read) { + data[read_count] = t->bcr; + t->ccc_byte_offset++; + read_count++; + } + if (read_count < num_to_read) { + data[read_count] = t->dcr; + t->ccc_byte_offset++; + read_count++; + } + *num_read = read_count; + break; + case I3C_CCCD_GETPID: + while (t->ccc_byte_offset < 6) { + if (read_count >= num_to_read) { + break; + } + data[read_count] = (t->pid >> (t->ccc_byte_offset * 8)) & 0xff; + t->ccc_byte_offset++; + read_count++; + } + *num_read = read_count; + break; + case I3C_CCCD_GETBCR: + *data = t->bcr; + *num_read = 1; + break; + case I3C_CCCD_GETDCR: + *data = t->dcr; + *num_read = 1; + break; + default: + /* Unhandled on the I3CTarget class level. */ + break; + } + + return tc->handle_ccc_read(t, data, num_to_read, num_read); +} + +int i3c_recv_byte(I3CBus *bus, uint8_t *data) +{ + /* + * Ignored, the caller can determine how many bytes were read based on if + * this is ACKed/NACKed. + */ + uint32_t num_read; + return i3c_recv(bus, data, 1, &num_read); +} + +int i3c_recv(I3CBus *bus, uint8_t *data, uint32_t num_to_read, + uint32_t *num_read) +{ + int ret = 0; + I3CTargetClass *tc; + I3CTarget *t; + + *data = 0xff; + if (!QLIST_EMPTY(&bus->current_devs)) { + tc = I3C_TARGET_GET_CLASS(QLIST_FIRST(&bus->current_devs)->target); + t = QLIST_FIRST(&bus->current_devs)->target; + if (bus->in_ccc) { + if (!tc->handle_ccc_read) { + return -1; + } + ret = i3c_target_handle_ccc_read(t, data, num_to_read, num_read); + } else { + if (tc->recv) { + /* + * Targets cannot NACK on a direct transfer, so the data + * is returned directly. + */ + *num_read = tc->recv(t, data, num_to_read); + } + } + } + + trace_i3c_recv(*num_read, num_to_read, ret == 0); + + return ret; +} + +void i3c_nack(I3CBus *bus) +{ + I3CTargetClass *tc; + I3CNode *node; + + if (QLIST_EMPTY(&bus->current_devs)) { + return; + } + + QLIST_FOREACH(node, &bus->current_devs, next) { + tc = I3C_TARGET_GET_CLASS(node->target); + if (tc->event) { + i3c_target_event(node->target, I3C_NACK); + } + } +} + +int i3c_target_send_ibi(I3CTarget *t, uint8_t addr, bool is_recv) +{ + I3CBus *bus = I3C_BUS(t->parent_obj.parent_bus); + I3CBusClass *bc = I3C_BUS_GET_CLASS(bus); + trace_i3c_target_send_ibi(addr, is_recv); + return bc->ibi_handle(bus, addr, is_recv); +} + +int i3c_target_send_ibi_bytes(I3CTarget *t, uint8_t data) +{ + I3CBus *bus = I3C_BUS(t->parent_obj.parent_bus); + I3CBusClass *bc = I3C_BUS_GET_CLASS(bus); + trace_i3c_target_send_ibi_bytes(data); + return bc->ibi_recv(bus, data); +} + +int i3c_target_ibi_finish(I3CTarget *t, uint8_t data) +{ + I3CBus *bus = I3C_BUS(t->parent_obj.parent_bus); + I3CBusClass *bc = I3C_BUS_GET_CLASS(bus); + trace_i3c_target_ibi_finish(); + return bc->ibi_finish(bus); +} + +static bool i3c_addr_is_rsvd(uint8_t addr) +{ + static const bool is_rsvd[256] = { + [0x00] = true, + [0x01] = true, + [0x02] = true, + [0x3e] = true, + [0x5e] = true, + [0x6e] = true, + [0x76] = true, + [0x7a] = true, + [0x7c] = true, + [0x7e] = true, + [0x7f] = true, + }; + + return is_rsvd[addr]; +} + +I3CTarget *i3c_target_new(const char *name, uint8_t addr, uint8_t dcr, + uint8_t bcr, uint64_t pid) +{ + DeviceState *dev; + + dev = qdev_new(name); + qdev_prop_set_uint8(dev, "static-address", addr); + qdev_prop_set_uint8(dev, "dcr", dcr); + qdev_prop_set_uint8(dev, "bcr", bcr); + qdev_prop_set_uint64(dev, "pid", pid); + + if (i3c_addr_is_rsvd(addr)) { + g_autofree char *path = object_get_canonical_path(OBJECT(dev)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: I3C target created with reserved " + "address 0x%.2x\n", path, addr); + } + return I3C_TARGET(dev); +} + +bool i3c_target_realize_and_unref(I3CTarget *dev, I3CBus *bus, Error **errp) +{ + return qdev_realize_and_unref(&dev->parent_obj, &bus->parent_obj, errp); +} + +I3CTarget *i3c_target_create_simple(I3CBus *bus, const char *name, uint8_t addr, + uint8_t dcr, uint8_t bcr, uint64_t pid) +{ + I3CTarget *dev = i3c_target_new(name, addr, dcr, bcr, pid); + dev->address = 0; + i3c_target_realize_and_unref(dev, bus, &error_abort); + + return dev; +} + +/* Legacy I2C functions. */ +void legacy_i2c_nack(I3CBus *bus) +{ + trace_legacy_i2c_nack(); + i2c_nack(bus->i2c_bus); +} + +uint8_t legacy_i2c_recv(I3CBus *bus) +{ + uint8_t byte = i2c_recv(bus->i2c_bus); + trace_legacy_i2c_recv(byte); + return byte; +} + +int legacy_i2c_send(I3CBus *bus, uint8_t data) +{ + trace_legacy_i2c_send(data); + return i2c_send(bus->i2c_bus, data); +} + +int legacy_i2c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv) +{ + trace_legacy_i2c_start_transfer(address, is_recv); + return i2c_start_transfer(bus->i2c_bus, address, is_recv); +} + +int legacy_i2c_start_recv(I3CBus *bus, uint8_t address) +{ + trace_legacy_i2c_start_transfer(address, true); + return i2c_start_transfer(bus->i2c_bus, address, /*is_recv=*/true); +} + +int legacy_i2c_start_send(I3CBus *bus, uint8_t address) +{ + trace_legacy_i2c_start_transfer(address, false); + return i2c_start_transfer(bus->i2c_bus, address, /*is_recv=*/false); +} + +void legacy_i2c_end_transfer(I3CBus *bus) +{ + trace_legacy_i2c_end_transfer(); + i2c_end_transfer(bus->i2c_bus); +} + +I2CSlave *legacy_i2c_device_create_simple(I3CBus *bus, const char *name, + uint8_t addr) +{ + I2CSlave *dev = i2c_slave_new(name, addr); + + i2c_slave_realize_and_unref(dev, bus->i2c_bus, &error_abort); + return dev; +} + +static void i3c_target_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *k = DEVICE_CLASS(klass); + I3CTargetClass *sc = I3C_TARGET_CLASS(klass); + set_bit(DEVICE_CATEGORY_MISC, k->categories); + k->bus_type = TYPE_I3C_BUS; + device_class_set_props(k, i3c_props); + sc->target_match = i3c_target_match; +} + +static const TypeInfo i3c_types[] = { + { + .name = TYPE_I3C_BUS, + .parent = TYPE_BUS, + .instance_size = sizeof(I3CBus), + .class_size = sizeof(I3CBusClass), + .class_init = i3c_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { } + } + }, + { + .name = TYPE_I3C_TARGET, + .parent = TYPE_DEVICE, + .instance_size = sizeof(I3CTarget), + .abstract = true, + .class_size = sizeof(I3CTargetClass), + .class_init = i3c_target_class_init, + }, +}; + +DEFINE_TYPES(i3c_types) diff --git a/hw/i3c/dw-i3c.c b/hw/i3c/dw-i3c.c new file mode 100644 index 000000000000..e9bdfd6af2ac --- /dev/null +++ b/hw/i3c/dw-i3c.c @@ -0,0 +1,1864 @@ +/* + * DesignWare I3C Controller + * + * Copyright (C) 2021 ASPEED Technology Inc. + * Copyright (C) 2025 Google, LLC + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/error-report.h" +#include "hw/i3c/i3c.h" +#include "hw/i3c/dw-i3c.h" +#include "hw/core/registerfields.h" +#include "hw/core/qdev-properties.h" +#include "qapi/error.h" +#include "migration/vmstate.h" +#include "trace.h" +#include "hw/core/irq.h" + +/* + * Disable event command values. sent along with a DISEC CCC to disable certain + * events on targets. + */ +#define DISEC_HJ 0x08 +#define DISEC_CR 0x02 +#define DISEC_INT 0x01 + +REG32(DEVICE_CTRL, 0x00) + FIELD(DEVICE_CTRL, I3C_BROADCAST_ADDR_INC, 0, 1) + FIELD(DEVICE_CTRL, I2C_SLAVE_PRESENT, 7, 1) + FIELD(DEVICE_CTRL, HOT_JOIN_ACK_NACK_CTRL, 8, 1) + FIELD(DEVICE_CTRL, IDLE_CNT_MULTIPLIER, 24, 2) + FIELD(DEVICE_CTRL, SLV_ADAPT_TO_I2C_I3C_MODE, 27, 1) + FIELD(DEVICE_CTRL, DMA_HANDSHAKE_EN, 28, 1) + FIELD(DEVICE_CTRL, I3C_ABORT, 29, 1) + FIELD(DEVICE_CTRL, I3C_RESUME, 30, 1) + FIELD(DEVICE_CTRL, I3C_EN, 31, 1) +REG32(DEVICE_ADDR, 0x04) + FIELD(DEVICE_ADDR, STATIC_ADDR, 0, 7) + FIELD(DEVICE_ADDR, STATIC_ADDR_VALID, 15, 1) + FIELD(DEVICE_ADDR, DYNAMIC_ADDR, 16, 7) + FIELD(DEVICE_ADDR, DYNAMIC_ADDR_VALID, 31, 1) +REG32(HW_CAPABILITY, 0x08) + FIELD(HW_CAPABILITY, DEVICE_ROLE_CONFIG, 0, 3) + FIELD(HW_CAPABILITY, HDR_DDR, 3, 1) + FIELD(HW_CAPABILITY, HDR_TS, 4, 1) +REG32(COMMAND_QUEUE_PORT, 0x0c) + FIELD(COMMAND_QUEUE_PORT, CMD_ATTR, 0, 3) + /* Transfer command structure */ + FIELD(COMMAND_QUEUE_PORT, TID, 3, 4) + FIELD(COMMAND_QUEUE_PORT, CMD, 7, 8) + FIELD(COMMAND_QUEUE_PORT, CP, 15, 1) + FIELD(COMMAND_QUEUE_PORT, DEV_INDEX, 16, 5) + FIELD(COMMAND_QUEUE_PORT, SPEED, 21, 3) + FIELD(COMMAND_QUEUE_PORT, ROC, 26, 1) + FIELD(COMMAND_QUEUE_PORT, SDAP, 27, 1) + FIELD(COMMAND_QUEUE_PORT, RNW, 28, 1) + FIELD(COMMAND_QUEUE_PORT, TOC, 30, 1) + FIELD(COMMAND_QUEUE_PORT, PEC, 31, 1) + /* Transfer argument data structure */ + FIELD(COMMAND_QUEUE_PORT, DB, 8, 8) + FIELD(COMMAND_QUEUE_PORT, DL, 16, 16) + /* Short data argument data structure */ + FIELD(COMMAND_QUEUE_PORT, BYTE_STRB, 3, 3) + FIELD(COMMAND_QUEUE_PORT, BYTE0, 8, 8) + FIELD(COMMAND_QUEUE_PORT, BYTE1, 16, 8) + FIELD(COMMAND_QUEUE_PORT, BYTE2, 24, 8) + /* Address assignment command structure */ + /* + * bits 3..21 and 26..31 are the same as the transfer command structure, or + * marked as reserved. + */ + FIELD(COMMAND_QUEUE_PORT, DEV_COUNT, 21, 3) +REG32(RESPONSE_QUEUE_PORT, 0x10) + FIELD(RESPONSE_QUEUE_PORT, DL, 0, 16) + FIELD(RESPONSE_QUEUE_PORT, CCCT, 16, 8) + FIELD(RESPONSE_QUEUE_PORT, TID, 24, 3) + FIELD(RESPONSE_QUEUE_PORT, ERR_STATUS, 28, 4) +REG32(RX_TX_DATA_PORT, 0x14) +REG32(IBI_QUEUE_STATUS, 0x18) + FIELD(IBI_QUEUE_STATUS, IBI_DATA_LEN, 0, 8) + FIELD(IBI_QUEUE_STATUS, IBI_ID, 8, 8) + FIELD(IBI_QUEUE_STATUS, LAST_STATUS, 24, 1) + FIELD(IBI_QUEUE_STATUS, ERROR, 30, 1) + FIELD(IBI_QUEUE_STATUS, IBI_STATUS, 31, 1) +REG32(IBI_QUEUE_DATA, 0x18) +REG32(QUEUE_THLD_CTRL, 0x1c) + FIELD(QUEUE_THLD_CTRL, CMD_BUF_EMPTY_THLD, 0, 8); + FIELD(QUEUE_THLD_CTRL, RESP_BUF_THLD, 8, 8); + FIELD(QUEUE_THLD_CTRL, IBI_DATA_THLD, 16, 5); + FIELD(QUEUE_THLD_CTRL, IBI_STATUS_THLD, 24, 8); +REG32(DATA_BUFFER_THLD_CTRL, 0x20) + FIELD(DATA_BUFFER_THLD_CTRL, TX_BUF_THLD, 0, 3) + FIELD(DATA_BUFFER_THLD_CTRL, RX_BUF_THLD, 8, 3) + FIELD(DATA_BUFFER_THLD_CTRL, TX_START_THLD, 16, 3) + FIELD(DATA_BUFFER_THLD_CTRL, RX_START_THLD, 24, 3) +REG32(IBI_QUEUE_CTRL, 0x24) + FIELD(IBI_QUEUE_CTRL, NOTIFY_REJECTED_HOT_JOIN, 0, 1) + FIELD(IBI_QUEUE_CTRL, NOTIFY_REJECTED_MASTER_REQ, 1, 1) + FIELD(IBI_QUEUE_CTRL, NOTIFY_REJECTED_SLAVE_IRQ, 3, 1) +REG32(IBI_MR_REQ_REJECT, 0x2c) +REG32(IBI_SIR_REQ_REJECT, 0x30) +REG32(RESET_CTRL, 0x34) + FIELD(RESET_CTRL, CORE_RESET, 0, 1) + FIELD(RESET_CTRL, CMD_QUEUE_RESET, 1, 1) + FIELD(RESET_CTRL, RESP_QUEUE_RESET, 2, 1) + FIELD(RESET_CTRL, TX_BUF_RESET, 3, 1) + FIELD(RESET_CTRL, RX_BUF_RESET, 4, 1) + FIELD(RESET_CTRL, IBI_QUEUE_RESET, 5, 1) +REG32(SLV_EVENT_CTRL, 0x38) + FIELD(SLV_EVENT_CTRL, SLV_INTERRUPT, 0, 1) + FIELD(SLV_EVENT_CTRL, MASTER_INTERRUPT, 1, 1) + FIELD(SLV_EVENT_CTRL, HOT_JOIN_INTERRUPT, 3, 1) + FIELD(SLV_EVENT_CTRL, ACTIVITY_STATE, 4, 2) + FIELD(SLV_EVENT_CTRL, MRL_UPDATED, 6, 1) + FIELD(SLV_EVENT_CTRL, MWL_UPDATED, 7, 1) +REG32(INTR_STATUS, 0x3c) + FIELD(INTR_STATUS, TX_THLD, 0, 1) + FIELD(INTR_STATUS, RX_THLD, 1, 1) + FIELD(INTR_STATUS, IBI_THLD, 2, 1) + FIELD(INTR_STATUS, CMD_QUEUE_RDY, 3, 1) + FIELD(INTR_STATUS, RESP_RDY, 4, 1) + FIELD(INTR_STATUS, TRANSFER_ABORT, 5, 1) + FIELD(INTR_STATUS, CCC_UPDATED, 6, 1) + FIELD(INTR_STATUS, DYN_ADDR_ASSGN, 8, 1) + FIELD(INTR_STATUS, TRANSFER_ERR, 9, 1) + FIELD(INTR_STATUS, DEFSLV, 10, 1) + FIELD(INTR_STATUS, READ_REQ_RECV, 11, 1) + FIELD(INTR_STATUS, IBI_UPDATED, 12, 1) + FIELD(INTR_STATUS, BUSOWNER_UPDATED, 13, 1) +REG32(INTR_STATUS_EN, 0x40) + FIELD(INTR_STATUS_EN, TX_THLD, 0, 1) + FIELD(INTR_STATUS_EN, RX_THLD, 1, 1) + FIELD(INTR_STATUS_EN, IBI_THLD, 2, 1) + FIELD(INTR_STATUS_EN, CMD_QUEUE_RDY, 3, 1) + FIELD(INTR_STATUS_EN, RESP_RDY, 4, 1) + FIELD(INTR_STATUS_EN, TRANSFER_ABORT, 5, 1) + FIELD(INTR_STATUS_EN, CCC_UPDATED, 6, 1) + FIELD(INTR_STATUS_EN, DYN_ADDR_ASSGN, 8, 1) + FIELD(INTR_STATUS_EN, TRANSFER_ERR, 9, 1) + FIELD(INTR_STATUS_EN, DEFSLV, 10, 1) + FIELD(INTR_STATUS_EN, READ_REQ_RECV, 11, 1) + FIELD(INTR_STATUS_EN, IBI_UPDATED, 12, 1) + FIELD(INTR_STATUS_EN, BUSOWNER_UPDATED, 13, 1) +REG32(INTR_SIGNAL_EN, 0x44) + FIELD(INTR_SIGNAL_EN, TX_THLD, 0, 1) + FIELD(INTR_SIGNAL_EN, RX_THLD, 1, 1) + FIELD(INTR_SIGNAL_EN, IBI_THLD, 2, 1) + FIELD(INTR_SIGNAL_EN, CMD_QUEUE_RDY, 3, 1) + FIELD(INTR_SIGNAL_EN, RESP_RDY, 4, 1) + FIELD(INTR_SIGNAL_EN, TRANSFER_ABORT, 5, 1) + FIELD(INTR_SIGNAL_EN, CCC_UPDATED, 6, 1) + FIELD(INTR_SIGNAL_EN, DYN_ADDR_ASSGN, 8, 1) + FIELD(INTR_SIGNAL_EN, TRANSFER_ERR, 9, 1) + FIELD(INTR_SIGNAL_EN, DEFSLV, 10, 1) + FIELD(INTR_SIGNAL_EN, READ_REQ_RECV, 11, 1) + FIELD(INTR_SIGNAL_EN, IBI_UPDATED, 12, 1) + FIELD(INTR_SIGNAL_EN, BUSOWNER_UPDATED, 13, 1) +REG32(INTR_FORCE, 0x48) + FIELD(INTR_FORCE, TX_THLD, 0, 1) + FIELD(INTR_FORCE, RX_THLD, 1, 1) + FIELD(INTR_FORCE, IBI_THLD, 2, 1) + FIELD(INTR_FORCE, CMD_QUEUE_RDY, 3, 1) + FIELD(INTR_FORCE, RESP_RDY, 4, 1) + FIELD(INTR_FORCE, TRANSFER_ABORT, 5, 1) + FIELD(INTR_FORCE, CCC_UPDATED, 6, 1) + FIELD(INTR_FORCE, DYN_ADDR_ASSGN, 8, 1) + FIELD(INTR_FORCE, TRANSFER_ERR, 9, 1) + FIELD(INTR_FORCE, DEFSLV, 10, 1) + FIELD(INTR_FORCE, READ_REQ_RECV, 11, 1) + FIELD(INTR_FORCE, IBI_UPDATED, 12, 1) + FIELD(INTR_FORCE, BUSOWNER_UPDATED, 13, 1) +REG32(QUEUE_STATUS_LEVEL, 0x4c) + FIELD(QUEUE_STATUS_LEVEL, CMD_QUEUE_EMPTY_LOC, 0, 8) + FIELD(QUEUE_STATUS_LEVEL, RESP_BUF_BLR, 8, 8) + FIELD(QUEUE_STATUS_LEVEL, IBI_BUF_BLR, 16, 8) + FIELD(QUEUE_STATUS_LEVEL, IBI_STATUS_CNT, 24, 5) +REG32(DATA_BUFFER_STATUS_LEVEL, 0x50) + FIELD(DATA_BUFFER_STATUS_LEVEL, TX_BUF_EMPTY_LOC, 0, 8) + FIELD(DATA_BUFFER_STATUS_LEVEL, RX_BUF_BLR, 16, 8) +REG32(PRESENT_STATE, 0x54) + FIELD(PRESENT_STATE, SCL_LINE_SIGNAL_LEVEL, 0, 1) + FIELD(PRESENT_STATE, SDA_LINE_SIGNAL_LEVEL, 1, 1) + FIELD(PRESENT_STATE, CURRENT_MASTER, 2, 1) + FIELD(PRESENT_STATE, CM_TFR_STATUS, 8, 6) + FIELD(PRESENT_STATE, CM_TFR_ST_STATUS, 16, 6) + FIELD(PRESENT_STATE, CMD_TID, 24, 4) +REG32(CCC_DEVICE_STATUS, 0x58) + FIELD(CCC_DEVICE_STATUS, PENDING_INTR, 0, 4) + FIELD(CCC_DEVICE_STATUS, PROTOCOL_ERR, 5, 1) + FIELD(CCC_DEVICE_STATUS, ACTIVITY_MODE, 6, 2) + FIELD(CCC_DEVICE_STATUS, UNDER_ERR, 8, 1) + FIELD(CCC_DEVICE_STATUS, SLV_BUSY, 9, 1) + FIELD(CCC_DEVICE_STATUS, OVERFLOW_ERR, 10, 1) + FIELD(CCC_DEVICE_STATUS, DATA_NOT_READY, 11, 1) + FIELD(CCC_DEVICE_STATUS, BUFFER_NOT_AVAIL, 12, 1) +REG32(DEVICE_ADDR_TABLE_POINTER, 0x5c) + FIELD(DEVICE_ADDR_TABLE_POINTER, DEPTH, 16, 16) + FIELD(DEVICE_ADDR_TABLE_POINTER, ADDR, 0, 16) +REG32(DEV_CHAR_TABLE_POINTER, 0x60) + FIELD(DEV_CHAR_TABLE_POINTER, P_DEV_CHAR_TABLE_START_ADDR, 0, 12) + FIELD(DEV_CHAR_TABLE_POINTER, DEV_CHAR_TABLE_DEPTH, 12, 7) + FIELD(DEV_CHAR_TABLE_POINTER, PRESENT_DEV_CHAR_TABLE_INDEX, 19, 3) +REG32(VENDOR_SPECIFIC_REG_POINTER, 0x6c) + FIELD(VENDOR_SPECIFIC_REG_POINTER, P_VENDOR_REG_START_ADDR, 0, 16) +REG32(SLV_MIPI_PID_VALUE, 0x70) +REG32(SLV_PID_VALUE, 0x74) + FIELD(SLV_PID_VALUE, SLV_PID_DCR, 0, 12) + FIELD(SLV_PID_VALUE, SLV_INST_ID, 12, 4) + FIELD(SLV_PID_VALUE, SLV_PART_ID, 16, 16) +REG32(SLV_CHAR_CTRL, 0x78) + FIELD(SLV_CHAR_CTRL, BCR, 0, 8) + FIELD(SLV_CHAR_CTRL, DCR, 8, 8) + FIELD(SLV_CHAR_CTRL, HDR_CAP, 16, 8) +REG32(SLV_MAX_LEN, 0x7c) + FIELD(SLV_MAX_LEN, MWL, 0, 16) + FIELD(SLV_MAX_LEN, MRL, 16, 16) +REG32(MAX_READ_TURNAROUND, 0x80) +REG32(MAX_DATA_SPEED, 0x84) +REG32(SLV_DEBUG_STATUS, 0x88) +REG32(SLV_INTR_REQ, 0x8c) + FIELD(SLV_INTR_REQ, SIR, 0, 1) + FIELD(SLV_INTR_REQ, SIR_CTRL, 1, 2) + FIELD(SLV_INTR_REQ, MIR, 3, 1) + FIELD(SLV_INTR_REQ, TS, 4, 1) + FIELD(SLV_INTR_REQ, IBI_STS, 8, 2) +REG32(SLV_TSX_SYMBL_TIMING, 0x90) + FIELD(SLV_TSX_SYMBL_TIMING, SLV_TSX_SYMBL_CNT, 0, 6) +REG32(DEVICE_CTRL_EXTENDED, 0xb0) + FIELD(DEVICE_CTRL_EXTENDED, MODE, 0, 2) + FIELD(DEVICE_CTRL_EXTENDED, REQMST_ACK_CTRL, 3, 1) +REG32(SCL_I3C_OD_TIMING, 0xb4) + FIELD(SCL_I3C_OD_TIMING, I3C_OD_LCNT, 0, 8) + FIELD(SCL_I3C_OD_TIMING, I3C_OD_HCNT, 16, 8) +REG32(SCL_I3C_PP_TIMING, 0xb8) + FIELD(SCL_I3C_PP_TIMING, I3C_PP_LCNT, 0, 8) + FIELD(SCL_I3C_PP_TIMING, I3C_PP_HCNT, 16, 8) +REG32(SCL_I2C_FM_TIMING, 0xbc) +REG32(SCL_I2C_FMP_TIMING, 0xc0) + FIELD(SCL_I2C_FMP_TIMING, I2C_FMP_LCNT, 0, 16) + FIELD(SCL_I2C_FMP_TIMING, I2C_FMP_HCNT, 16, 8) +REG32(SCL_EXT_LCNT_TIMING, 0xc8) +REG32(SCL_EXT_TERMN_LCNT_TIMING, 0xcc) +REG32(BUS_FREE_TIMING, 0xd4) +REG32(BUS_IDLE_TIMING, 0xd8) + FIELD(BUS_IDLE_TIMING, BUS_IDLE_TIME, 0, 20) +REG32(I3C_VER_ID, 0xe0) +REG32(I3C_VER_TYPE, 0xe4) +REG32(EXTENDED_CAPABILITY, 0xe8) +REG32(SLAVE_CONFIG, 0xec) +/* Device characteristic table fields */ +REG32(DEVICE_CHARACTERISTIC_TABLE_LOC1, 0x200) +REG32(DEVICE_CHARACTERISTIC_TABLE_LOC_SECONDARY, 0x200) + FIELD(DEVICE_CHARACTERISTIC_TABLE_LOC_SECONDARY, DYNAMIC_ADDR, 0, 8) + FIELD(DEVICE_CHARACTERISTIC_TABLE_LOC_SECONDARY, DCR, 8, 8) + FIELD(DEVICE_CHARACTERISTIC_TABLE_LOC_SECONDARY, BCR, 16, 8) + FIELD(DEVICE_CHARACTERISTIC_TABLE_LOC_SECONDARY, STATIC_ADDR, 24, 8) +REG32(DEVICE_CHARACTERISTIC_TABLE_LOC2, 0x204) + FIELD(DEVICE_CHARACTERISTIC_TABLE_LOC2, MSB_PID, 0, 16) +REG32(DEVICE_CHARACTERISTIC_TABLE_LOC3, 0x208) + FIELD(DEVICE_CHARACTERISTIC_TABLE_LOC3, DCR, 0, 8) + FIELD(DEVICE_CHARACTERISTIC_TABLE_LOC3, BCR, 8, 8) +REG32(DEVICE_CHARACTERISTIC_TABLE_LOC4, 0x20c) + FIELD(DEVICE_CHARACTERISTIC_TABLE_LOC4, DEV_DYNAMIC_ADDR, 0, 8) +/* Dev addr table fields */ +REG32(DEVICE_ADDR_TABLE_LOC1, 0x280) + FIELD(DEVICE_ADDR_TABLE_LOC1, DEV_STATIC_ADDR, 0, 7) + FIELD(DEVICE_ADDR_TABLE_LOC1, IBI_PEC_EN, 11, 1) + FIELD(DEVICE_ADDR_TABLE_LOC1, IBI_WITH_DATA, 12, 1) + FIELD(DEVICE_ADDR_TABLE_LOC1, SIR_REJECT, 13, 1) + FIELD(DEVICE_ADDR_TABLE_LOC1, MR_REJECT, 14, 1) + FIELD(DEVICE_ADDR_TABLE_LOC1, DEV_DYNAMIC_ADDR, 16, 8) + FIELD(DEVICE_ADDR_TABLE_LOC1, IBI_ADDR_MASK, 24, 2) + FIELD(DEVICE_ADDR_TABLE_LOC1, DEV_NACK_RETRY_CNT, 29, 2) + FIELD(DEVICE_ADDR_TABLE_LOC1, LEGACY_I2C_DEVICE, 31, 1) + +static const uint32_t dw_i3c_resets[DW_I3C_NR_REGS] = { + /* Target mode is not supported, don't advertise it for now. */ + [R_HW_CAPABILITY] = 0x000e00b9, + [R_QUEUE_THLD_CTRL] = 0x01000101, + [R_DATA_BUFFER_THLD_CTRL] = 0x01010100, + [R_SLV_EVENT_CTRL] = 0x0000000b, + [R_QUEUE_STATUS_LEVEL] = 0x00000002, + [R_DATA_BUFFER_STATUS_LEVEL] = 0x00000010, + [R_PRESENT_STATE] = 0x00000003, + [R_I3C_VER_ID] = 0x3130302a, + [R_I3C_VER_TYPE] = 0x6c633033, + [R_DEVICE_ADDR_TABLE_POINTER] = 0x00080280, + [R_DEV_CHAR_TABLE_POINTER] = 0x00020200, + [R_SLV_CHAR_CTRL] = 0x00010000, + [A_VENDOR_SPECIFIC_REG_POINTER] = 0x000000b0, + [R_SLV_MAX_LEN] = 0x00ff00ff, + [R_SLV_TSX_SYMBL_TIMING] = 0x0000003f, + [R_SCL_I3C_OD_TIMING] = 0x000a0010, + [R_SCL_I3C_PP_TIMING] = 0x000a000a, + [R_SCL_I2C_FM_TIMING] = 0x00100010, + [R_SCL_I2C_FMP_TIMING] = 0x00100010, + [R_SCL_EXT_LCNT_TIMING] = 0x20202020, + [R_SCL_EXT_TERMN_LCNT_TIMING] = 0x00300000, + [R_BUS_FREE_TIMING] = 0x00200020, + [R_BUS_IDLE_TIMING] = 0x00000020, + [R_EXTENDED_CAPABILITY] = 0x00000239, + [R_SLAVE_CONFIG] = 0x00000023, +}; + +static const uint32_t dw_i3c_ro[DW_I3C_NR_REGS] = { + [R_DEVICE_CTRL] = 0x04fffe00, + [R_DEVICE_ADDR] = 0x7f807f80, + [R_HW_CAPABILITY] = 0xffffffff, + [R_IBI_QUEUE_STATUS] = 0xffffffff, + [R_DATA_BUFFER_THLD_CTRL] = 0xf8f8f8f8, + [R_IBI_QUEUE_CTRL] = 0xfffffff0, + [R_RESET_CTRL] = 0xffffffc0, + [R_SLV_EVENT_CTRL] = 0xffffff3f, + [R_INTR_STATUS] = 0xffff809f, + [R_INTR_STATUS_EN] = 0xffff8080, + [R_INTR_SIGNAL_EN] = 0xffff8080, + [R_INTR_FORCE] = 0xffff8000, + [R_QUEUE_STATUS_LEVEL] = 0xffffffff, + [R_DATA_BUFFER_STATUS_LEVEL] = 0xffffffff, + [R_PRESENT_STATE] = 0xffffffff, + [R_CCC_DEVICE_STATUS] = 0xffffffff, + [R_I3C_VER_ID] = 0xffffffff, + [R_I3C_VER_TYPE] = 0xffffffff, + [R_DEVICE_ADDR_TABLE_POINTER] = 0xffffffff, + [R_DEV_CHAR_TABLE_POINTER] = 0xffcbffff, + [R_SLV_PID_VALUE] = 0xffff0fff, + [R_SLV_CHAR_CTRL] = 0xffffffff, + [A_VENDOR_SPECIFIC_REG_POINTER] = 0xffffffff, + [R_SLV_MAX_LEN] = 0xffffffff, + [R_MAX_READ_TURNAROUND] = 0xffffffff, + [R_MAX_DATA_SPEED] = 0xffffffff, + [R_SLV_INTR_REQ] = 0xfffffff0, + [R_SLV_TSX_SYMBL_TIMING] = 0xffffffc0, + [R_DEVICE_CTRL_EXTENDED] = 0xfffffff8, + [R_SCL_I3C_OD_TIMING] = 0xff00ff00, + [R_SCL_I3C_PP_TIMING] = 0xff00ff00, + [R_SCL_I2C_FMP_TIMING] = 0xff000000, + [R_SCL_EXT_TERMN_LCNT_TIMING] = 0x0000fff0, + [R_BUS_IDLE_TIMING] = 0xfff00000, + [R_EXTENDED_CAPABILITY] = 0xffffffff, + [R_SLAVE_CONFIG] = 0xffffffff, +}; + +static void dw_i3c_cmd_queue_execute(DWI3C *s); + +static inline bool dw_i3c_has_hdr_ts(DWI3C *s) +{ + return ARRAY_FIELD_EX32(s->regs, HW_CAPABILITY, HDR_TS); +} + +static inline bool dw_i3c_has_hdr_ddr(DWI3C *s) +{ + return ARRAY_FIELD_EX32(s->regs, HW_CAPABILITY, HDR_DDR); +} + +static inline bool dw_i3c_can_transmit(DWI3C *s) +{ + /* + * We can only transmit if we're enabled and the resume bit is cleared. + * The resume bit is set on a transaction error, and software must clear it. + */ + return ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, I3C_EN) && + !ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, I3C_RESUME); +} + +static inline uint8_t dw_i3c_ibi_slice_size(DWI3C *s) +{ + uint8_t ibi_slice_size = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL, + IBI_DATA_THLD); + /* The minimum supported slice size is 4 bytes. */ + if (ibi_slice_size == 0) { + ibi_slice_size = 1; + } + ibi_slice_size *= sizeof(uint32_t); + /* maximum supported size is 63 bytes. */ + if (ibi_slice_size >= 64) { + ibi_slice_size = 63; + } + + return ibi_slice_size; +} + +static inline uint8_t dw_i3c_fifo_threshold_from_reg(uint8_t regval) +{ + return regval = regval ? (2 << regval) : 1; +} + +static void dw_i3c_update_irq(DWI3C *s) +{ + bool level = !!(s->regs[R_INTR_SIGNAL_EN] & s->regs[R_INTR_STATUS]); + qemu_set_irq(s->irq, level); +} + +static void dw_i3c_end_transfer(DWI3C *s, bool is_i2c) +{ + if (is_i2c) { + legacy_i2c_end_transfer(s->bus); + } else { + i3c_end_transfer(s->bus); + } +} + +static int dw_i3c_send_start(DWI3C *s, uint8_t addr, bool is_recv, bool is_i2c) +{ + int ret; + + if (is_i2c) { + ret = legacy_i2c_start_transfer(s->bus, addr, is_recv); + } else { + ret = i3c_start_transfer(s->bus, addr, is_recv); + } + if (ret) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: NACKed on TX with addr 0x%.2x\n", + path, addr); + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS, + DW_I3C_TRANSFER_STATE_HALT); + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS, + DW_I3C_TRANSFER_STATUS_HALT); + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TRANSFER_ERR, 1); + ARRAY_FIELD_DP32(s->regs, DEVICE_CTRL, I3C_RESUME, 1); + } + + return ret; +} + +static int dw_i3c_send(DWI3C *s, const uint8_t *data, uint32_t num_to_send, + uint32_t *num_sent, bool is_i2c) +{ + int ret; + uint32_t i; + + *num_sent = 0; + if (is_i2c) { + /* Legacy I2C must be byte-by-byte. */ + for (i = 0; i < num_to_send; i++) { + ret = legacy_i2c_send(s->bus, data[i]); + if (ret) { + break; + } + (*num_sent)++; + } + } else { + ret = i3c_send(s->bus, data, num_to_send, num_sent); + } + if (ret) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: NACKed sending byte 0x%.2x\n", + path, data[*num_sent]); + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS, + DW_I3C_TRANSFER_STATE_HALT); + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS, + DW_I3C_TRANSFER_STATUS_HALT); + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TRANSFER_ERR, 1); + ARRAY_FIELD_DP32(s->regs, DEVICE_CTRL, I3C_RESUME, 1); + } + + trace_dw_i3c_send(s->cfg.id, *num_sent); + + return ret; +} + +static int dw_i3c_send_byte(DWI3C *s, uint8_t byte, bool is_i2c) +{ + /* + * Ignored, the caller will know if we sent 0 or 1 bytes depending on if + * we were ACKed/NACKed. + */ + uint32_t num_sent; + return dw_i3c_send(s, &byte, 1, &num_sent, is_i2c); +} + +static int dw_i3c_recv_data(DWI3C *s, bool is_i2c, uint8_t *data, + uint16_t num_to_read, uint32_t *num_read) +{ + int ret; + + if (is_i2c) { + for (uint16_t i = 0; i < num_to_read; i++) { + data[i] = legacy_i2c_recv(s->bus); + } + /* I2C devices can neither NACK a read, nor end transfers early. */ + *num_read = num_to_read; + trace_dw_i3c_recv_data(s->cfg.id, *num_read); + return 0; + } + /* I3C devices can NACK if the controller sends an unsupported CCC. */ + ret = i3c_recv(s->bus, data, num_to_read, num_read); + if (ret) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: NACKed receiving byte\n", + path); + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS, + DW_I3C_TRANSFER_STATE_HALT); + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS, + DW_I3C_TRANSFER_STATUS_HALT); + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TRANSFER_ERR, 1); + ARRAY_FIELD_DP32(s->regs, DEVICE_CTRL, I3C_RESUME, 1); + } + + trace_dw_i3c_recv_data(s->cfg.id, *num_read); + + return ret; +} + +static void dw_i3c_ctrl_w(DWI3C *s, uint32_t val) +{ + /* + * If the user is setting I3C_RESUME, the controller was halted. + * Try and resume execution and leave the bit cleared. + */ + if (FIELD_EX32(val, DEVICE_CTRL, I3C_RESUME)) { + dw_i3c_cmd_queue_execute(s); + val = FIELD_DP32(val, DEVICE_CTRL, I3C_RESUME, 0); + } + /* + * I3C_ABORT being set sends an I3C STOP. It's cleared when the STOP is + * sent. + */ + if (FIELD_EX32(val, DEVICE_CTRL, I3C_ABORT)) { + dw_i3c_end_transfer(s, /*is_i2c=*/true); + dw_i3c_end_transfer(s, /*is_i2c=*/false); + val = FIELD_DP32(val, DEVICE_CTRL, I3C_ABORT, 0); + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TRANSFER_ABORT, 1); + dw_i3c_update_irq(s); + } + /* Update present state. */ + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS, + DW_I3C_TRANSFER_STATE_IDLE); + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS, + DW_I3C_TRANSFER_STATUS_IDLE); + + s->regs[R_DEVICE_CTRL] = val; +} + +static inline bool dw_i3c_target_is_i2c(DWI3C *s, uint16_t offset) +{ + /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array. */ + uint16_t dev_index = (ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER, + ADDR) / sizeof(uint32_t)) + offset; + return FIELD_EX32(s->regs[dev_index], DEVICE_ADDR_TABLE_LOC1, + LEGACY_I2C_DEVICE); +} + +static uint8_t dw_i3c_target_addr(DWI3C *s, uint16_t offset) +{ + if (offset > s->cfg.num_addressable_devices) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: Device addr table offset %d out of " + "bounds\n", path, offset); + /* If we're out of bounds, return an address of 0. */ + return 0; + } + + /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array. */ + uint16_t dev_index = (ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER, + ADDR) / sizeof(uint32_t)) + offset; + /* I2C devices use a static address. */ + if (dw_i3c_target_is_i2c(s, offset)) { + return FIELD_EX32(s->regs[dev_index], DEVICE_ADDR_TABLE_LOC1, + DEV_STATIC_ADDR); + } + return FIELD_EX32(s->regs[dev_index], DEVICE_ADDR_TABLE_LOC1, + DEV_DYNAMIC_ADDR); +} + +static int dw_i3c_addr_table_index_from_addr(DWI3C *s, uint8_t addr) +{ + uint8_t table_size = ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER, + DEPTH); + for (uint8_t i = 0; i < table_size; i++) { + if (dw_i3c_target_addr(s, i) == addr) { + return i; + } + } + return -1; +} + +static void dw_i3c_send_disec(DWI3C *s) +{ + uint8_t ccc = I3C_CCC_DISEC; + if (s->ibi_data.send_direct_disec) { + ccc = I3C_CCCD_DISEC; + } + + dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false, + /*is_i2c=*/false); + dw_i3c_send_byte(s, ccc, /*is_i2c=*/false); + if (s->ibi_data.send_direct_disec) { + dw_i3c_send_start(s, s->ibi_data.disec_addr, + /*is_recv=*/false, /*is_i2c=*/false); + } + dw_i3c_send_byte(s, s->ibi_data.disec_byte, /*is_i2c=*/false); +} + +static int dw_i3c_handle_hj(DWI3C *s) +{ + if (ARRAY_FIELD_EX32(s->regs, IBI_QUEUE_CTRL, NOTIFY_REJECTED_HOT_JOIN)) { + s->ibi_data.notify_ibi_nack = true; + } + + bool nack_and_disable = ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, + HOT_JOIN_ACK_NACK_CTRL); + if (nack_and_disable) { + s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status, + IBI_QUEUE_STATUS, + IBI_STATUS, 1); + s->ibi_data.ibi_nacked = true; + s->ibi_data.disec_byte = DISEC_HJ; + return -1; + } + return 0; +} + +static int dw_i3c_handle_ctlr_req(DWI3C *s, uint8_t addr) +{ + if (ARRAY_FIELD_EX32(s->regs, IBI_QUEUE_CTRL, NOTIFY_REJECTED_MASTER_REQ)) { + s->ibi_data.notify_ibi_nack = true; + } + + int table_offset = dw_i3c_addr_table_index_from_addr(s, addr); + /* Doesn't exist in the table, NACK it, don't DISEC. */ + if (table_offset < 0) { + return -1; + } + + /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array. */ + table_offset += (ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER, + ADDR) / sizeof(uint32_t)); + if (FIELD_EX32(s->regs[table_offset], DEVICE_ADDR_TABLE_LOC1, MR_REJECT)) { + s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status, + IBI_QUEUE_STATUS, + IBI_STATUS, 1); + s->ibi_data.ibi_nacked = true; + s->ibi_data.disec_addr = addr; + /* Tell the requester to disable controller role requests. */ + s->ibi_data.disec_byte = DISEC_CR; + s->ibi_data.send_direct_disec = true; + return -1; + } + return 0; +} + +static int dw_i3c_handle_targ_irq(DWI3C *s, uint8_t addr) +{ + if (ARRAY_FIELD_EX32(s->regs, IBI_QUEUE_CTRL, NOTIFY_REJECTED_SLAVE_IRQ)) { + s->ibi_data.notify_ibi_nack = true; + } + + int table_offset = dw_i3c_addr_table_index_from_addr(s, addr); + /* Doesn't exist in the table, NACK it, don't DISEC. */ + if (table_offset < 0) { + return -1; + } + + /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array. */ + table_offset += (ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER, + ADDR) / sizeof(uint32_t)); + if (FIELD_EX32(s->regs[table_offset], DEVICE_ADDR_TABLE_LOC1, SIR_REJECT)) { + s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status, + IBI_QUEUE_STATUS, + IBI_STATUS, 1); + s->ibi_data.ibi_nacked = true; + s->ibi_data.disec_addr = addr; + /* Tell the requester to disable interrupts. */ + s->ibi_data.disec_byte = DISEC_INT; + s->ibi_data.send_direct_disec = true; + return -1; + } + return 0; +} + +static int dw_i3c_ibi_handle(I3CBus *bus, uint8_t addr, bool is_recv) +{ + DWI3C *s = DW_I3C(bus->parent_obj.parent); + + trace_dw_i3c_ibi_handle(s->cfg.id, addr, is_recv); + s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status, + IBI_QUEUE_STATUS, IBI_ID, + (addr << 1) | is_recv); + /* Is this a hot join request? */ + if (addr == I3C_HJ_ADDR) { + return dw_i3c_handle_hj(s); + } + /* Is secondary controller requesting access? */ + if (!is_recv) { + return dw_i3c_handle_ctlr_req(s, addr); + } + /* Is this a target IRQ? */ + if (is_recv) { + return dw_i3c_handle_targ_irq(s, addr); + } + + /* At this point the IBI should have been ACKed or NACKed. */ + g_assert_not_reached(); + return -1; +} + +static int dw_i3c_ibi_recv(I3CBus *bus, uint8_t data) +{ + DWI3C *s = DW_I3C(bus->parent_obj.parent); + if (fifo8_is_full(&s->ibi_data.ibi_intermediate_queue)) { + return -1; + } + + fifo8_push(&s->ibi_data.ibi_intermediate_queue, data); + trace_dw_i3c_ibi_recv(s->cfg.id, data); + return 0; +} + +static void dw_i3c_ibi_queue_push(DWI3C *s) +{ + /* Stored value is in 32-bit chunks, convert it to byte chunks. */ + uint8_t ibi_slice_size = dw_i3c_ibi_slice_size(s); + uint8_t num_slices = (fifo8_num_used(&s->ibi_data.ibi_intermediate_queue) / + ibi_slice_size) + + ((fifo8_num_used(&s->ibi_data.ibi_intermediate_queue) % + ibi_slice_size) ? 1 : 0); + uint8_t ibi_status_count = num_slices; + union { + uint8_t b[sizeof(uint32_t)]; + uint32_t val32; + } ibi_data = { + .val32 = 0 + }; + + /* The report was suppressed, do nothing. */ + if (s->ibi_data.ibi_nacked && !s->ibi_data.notify_ibi_nack) { + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS, + DW_I3C_TRANSFER_STATE_IDLE); + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS, + DW_I3C_TRANSFER_STATUS_IDLE); + return; + } + + /* If we don't have any slices to push, just push the status. */ + if (num_slices == 0) { + s->ibi_data.ibi_queue_status = + FIELD_DP32(s->ibi_data.ibi_queue_status, IBI_QUEUE_STATUS, + LAST_STATUS, 1); + fifo32_push(&s->ibi_queue, s->ibi_data.ibi_queue_status); + ibi_status_count = 1; + } + + for (uint8_t i = 0; i < num_slices; i++) { + /* If this is the last slice, set LAST_STATUS. */ + if (fifo8_num_used(&s->ibi_data.ibi_intermediate_queue) < + ibi_slice_size) { + s->ibi_data.ibi_queue_status = + FIELD_DP32(s->ibi_data.ibi_queue_status, IBI_QUEUE_STATUS, + IBI_DATA_LEN, + fifo8_num_used(&s->ibi_data.ibi_intermediate_queue)); + s->ibi_data.ibi_queue_status = + FIELD_DP32(s->ibi_data.ibi_queue_status, IBI_QUEUE_STATUS, + LAST_STATUS, 1); + } else { + s->ibi_data.ibi_queue_status = + FIELD_DP32(s->ibi_data.ibi_queue_status, IBI_QUEUE_STATUS, + IBI_DATA_LEN, ibi_slice_size); + } + + /* Push the IBI status header. */ + fifo32_push(&s->ibi_queue, s->ibi_data.ibi_queue_status); + /* Move each IBI byte into a 32-bit word and push it into the queue. */ + for (uint8_t j = 0; j < ibi_slice_size; ++j) { + if (fifo8_is_empty(&s->ibi_data.ibi_intermediate_queue)) { + break; + } + + ibi_data.b[j & 3] = fifo8_pop(&s->ibi_data.ibi_intermediate_queue); + /* We have 32-bits, push it to the IBI FIFO. */ + if ((j & 0x03) == 0x03) { + fifo32_push(&s->ibi_queue, ibi_data.val32); + ibi_data.val32 = 0; + } + } + /* If the data isn't 32-bit aligned, push the leftover bytes. */ + if (ibi_slice_size & 0x03) { + fifo32_push(&s->ibi_queue, ibi_data.val32); + } + + /* Clear out the data length for the next iteration. */ + s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status, + IBI_QUEUE_STATUS, IBI_DATA_LEN, 0); + } + + ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, IBI_BUF_BLR, + fifo32_num_used(&s->ibi_queue)); + ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, IBI_STATUS_CNT, + ibi_status_count); + /* Threshold is the register value + 1. */ + uint8_t threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL, + IBI_STATUS_THLD) + 1; + if (fifo32_num_used(&s->ibi_queue) >= threshold) { + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, IBI_THLD, 1); + dw_i3c_update_irq(s); + } + + /* State update. */ + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS, + DW_I3C_TRANSFER_STATE_IDLE); + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS, + DW_I3C_TRANSFER_STATUS_IDLE); +} + +static int dw_i3c_ibi_finish(I3CBus *bus) +{ + DWI3C *s = DW_I3C(bus->parent_obj.parent); + bool nack_and_disable_hj = ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, + HOT_JOIN_ACK_NACK_CTRL); + if (nack_and_disable_hj || s->ibi_data.send_direct_disec) { + dw_i3c_send_disec(s); + } + dw_i3c_ibi_queue_push(s); + + /* Clear out the intermediate values. */ + s->ibi_data.ibi_queue_status = 0; + s->ibi_data.disec_addr = 0; + s->ibi_data.disec_byte = 0; + s->ibi_data.send_direct_disec = false; + s->ibi_data.notify_ibi_nack = false; + s->ibi_data.ibi_nacked = false; + + return 0; +} + +static uint32_t dw_i3c_intr_status_r(DWI3C *s) +{ + /* Only return the status whose corresponding EN bits are set. */ + return s->regs[R_INTR_STATUS] & s->regs[R_INTR_STATUS_EN]; +} + +static void dw_i3c_intr_status_w(DWI3C *s, uint32_t val) +{ + /* INTR_STATUS[13:5] is w1c, other bits are RO. */ + val &= 0x3fe0; + s->regs[R_INTR_STATUS] &= ~val; + + dw_i3c_update_irq(s); +} + +static void dw_i3c_intr_status_en_w(DWI3C *s, uint32_t val) +{ + s->regs[R_INTR_STATUS_EN] = val; + dw_i3c_update_irq(s); +} + +static void dw_i3c_intr_signal_en_w(DWI3C *s, uint32_t val) +{ + s->regs[R_INTR_SIGNAL_EN] = val; + dw_i3c_update_irq(s); +} + +static void dw_i3c_intr_force_w(DWI3C *s, uint32_t val) +{ + /* INTR_FORCE is WO, just set the corresponding INTR_STATUS bits. */ + s->regs[R_INTR_STATUS] = val; + dw_i3c_update_irq(s); +} + +static void dw_i3c_cmd_queue_reset(DWI3C *s) +{ + fifo32_reset(&s->cmd_queue); + + ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, CMD_QUEUE_EMPTY_LOC, + fifo32_num_free(&s->cmd_queue)); + uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL, + CMD_BUF_EMPTY_THLD); + if (fifo32_num_free(&s->cmd_queue) >= empty_threshold) { + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, CMD_QUEUE_RDY, 1); + dw_i3c_update_irq(s); + }; +} + +static void dw_i3c_resp_queue_reset(DWI3C *s) +{ + fifo32_reset(&s->resp_queue); + + ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, RESP_BUF_BLR, + fifo32_num_used(&s->resp_queue)); + /* + * This interrupt will always be cleared because the threshold is a minimum + * of 1 and the queue size is 0. + */ + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RESP_RDY, 0); + dw_i3c_update_irq(s); +} + +static void dw_i3c_ibi_queue_reset(DWI3C *s) +{ + fifo32_reset(&s->ibi_queue); + + ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, IBI_BUF_BLR, + fifo32_num_used(&s->resp_queue)); + /* + * This interrupt will always be cleared because the threshold is a minimum + * of 1 and the queue size is 0. + */ + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, IBI_THLD, 0); + dw_i3c_update_irq(s); +} + +static void dw_i3c_tx_queue_reset(DWI3C *s) +{ + fifo32_reset(&s->tx_queue); + + ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, TX_BUF_EMPTY_LOC, + fifo32_num_free(&s->tx_queue)); + /* TX buf is empty, so this interrupt will always be set. */ + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TX_THLD, 1); + dw_i3c_update_irq(s); +} + +static void dw_i3c_rx_queue_reset(DWI3C *s) +{ + fifo32_reset(&s->rx_queue); + + ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, RX_BUF_BLR, + fifo32_num_used(&s->resp_queue)); + /* + * This interrupt will always be cleared because the threshold is a minimum + * of 1 and the queue size is 0. + */ + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RX_THLD, 0); + dw_i3c_update_irq(s); +} + +static void dw_i3c_reset(DeviceState *dev) +{ + DWI3C *s = DW_I3C(dev); + trace_dw_i3c_reset(s->cfg.id); + + memcpy(s->regs, dw_i3c_resets, sizeof(s->regs)); + /* + * The user config for these may differ from our resets array, set them + * manually. + */ + ARRAY_FIELD_DP32(s->regs, DEVICE_ADDR_TABLE_POINTER, ADDR, + s->cfg.dev_addr_table_pointer); + ARRAY_FIELD_DP32(s->regs, DEVICE_ADDR_TABLE_POINTER, DEPTH, + s->cfg.dev_addr_table_depth); + ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER, + P_DEV_CHAR_TABLE_START_ADDR, + s->cfg.dev_char_table_pointer); + ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER, DEV_CHAR_TABLE_DEPTH, + s->cfg.dev_char_table_depth); + + dw_i3c_cmd_queue_reset(s); + dw_i3c_resp_queue_reset(s); + dw_i3c_ibi_queue_reset(s); + dw_i3c_tx_queue_reset(s); + dw_i3c_rx_queue_reset(s); +} + +static void dw_i3c_reset_ctrl_w(DWI3C *s, uint32_t val) +{ + if (FIELD_EX32(val, RESET_CTRL, CORE_RESET)) { + dw_i3c_reset(DEVICE(s)); + } + if (FIELD_EX32(val, RESET_CTRL, CMD_QUEUE_RESET)) { + dw_i3c_cmd_queue_reset(s); + } + if (FIELD_EX32(val, RESET_CTRL, RESP_QUEUE_RESET)) { + dw_i3c_resp_queue_reset(s); + } + if (FIELD_EX32(val, RESET_CTRL, TX_BUF_RESET)) { + dw_i3c_tx_queue_reset(s); + } + if (FIELD_EX32(val, RESET_CTRL, RX_BUF_RESET)) { + dw_i3c_rx_queue_reset(s); + } + if (FIELD_EX32(val, RESET_CTRL, IBI_QUEUE_RESET)) { + dw_i3c_ibi_queue_reset(s); + } +} + +static uint32_t dw_i3c_pop_rx(DWI3C *s) +{ + if (fifo32_is_empty(&s->rx_queue)) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to read RX FIFO when empty\n", + path); + return 0; + } + + uint32_t val = fifo32_pop(&s->rx_queue); + ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, RX_BUF_BLR, + fifo32_num_used(&s->rx_queue)); + + /* Threshold is 2^RX_BUF_THLD. */ + uint8_t threshold = ARRAY_FIELD_EX32(s->regs, DATA_BUFFER_THLD_CTRL, + RX_BUF_THLD); + threshold = dw_i3c_fifo_threshold_from_reg(threshold); + if (fifo32_num_used(&s->rx_queue) < threshold) { + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RX_THLD, 0); + dw_i3c_update_irq(s); + } + + trace_dw_i3c_pop_rx(s->cfg.id, val); + return val; +} + +static uint32_t dw_i3c_ibi_queue_r(DWI3C *s) +{ + if (fifo32_is_empty(&s->ibi_queue)) { + return 0; + } + + uint32_t val = fifo32_pop(&s->ibi_queue); + ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, IBI_BUF_BLR, + fifo32_num_used(&s->ibi_queue)); + /* Threshold is the register value + 1. */ + uint8_t threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL, + IBI_STATUS_THLD) + 1; + if (fifo32_num_used(&s->ibi_queue) < threshold) { + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, IBI_THLD, 0); + dw_i3c_update_irq(s); + } + return val; +} + +static uint32_t dw_i3c_resp_queue_port_r(DWI3C *s) +{ + if (fifo32_is_empty(&s->resp_queue)) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to read response FIFO when " + "empty\n", path); + return 0; + } + + uint32_t val = fifo32_pop(&s->resp_queue); + ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, RESP_BUF_BLR, + fifo32_num_used(&s->resp_queue)); + + /* Threshold is the register value + 1. */ + uint8_t threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL, + RESP_BUF_THLD) + 1; + if (fifo32_num_used(&s->resp_queue) < threshold) { + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RESP_RDY, 0); + dw_i3c_update_irq(s); + } + + return val; +} + +static uint64_t dw_i3c_read(void *opaque, hwaddr offset, unsigned size) +{ + DWI3C *s = DW_I3C(opaque); + uint32_t addr = offset >> 2; + uint64_t value; + + switch (addr) { + /* RAZ */ + case R_COMMAND_QUEUE_PORT: + case R_RESET_CTRL: + case R_INTR_FORCE: + value = 0; + break; + case R_IBI_QUEUE_DATA: + value = dw_i3c_ibi_queue_r(s); + break; + case R_INTR_STATUS: + value = dw_i3c_intr_status_r(s); + break; + case R_RX_TX_DATA_PORT: + value = dw_i3c_pop_rx(s); + break; + case R_RESPONSE_QUEUE_PORT: + value = dw_i3c_resp_queue_port_r(s); + break; + default: + value = s->regs[addr]; + break; + } + + trace_dw_i3c_read(s->cfg.id, offset, value); + + return value; +} + +static void dw_i3c_resp_queue_push(DWI3C *s, uint8_t err, uint8_t tid, + uint8_t ccc_type, uint16_t data_len) +{ + uint32_t val = 0; + val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, ERR_STATUS, err); + val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, TID, tid); + val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, CCCT, ccc_type); + val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, DL, data_len); + if (!fifo32_is_full(&s->resp_queue)) { + trace_dw_i3c_resp_queue_push(s->cfg.id, val); + fifo32_push(&s->resp_queue, val); + } + + ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, RESP_BUF_BLR, + fifo32_num_used(&s->resp_queue)); + /* Threshold is the register value + 1. */ + uint8_t threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL, + RESP_BUF_THLD) + 1; + if (fifo32_num_used(&s->resp_queue) >= threshold) { + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RESP_RDY, 1); + dw_i3c_update_irq(s); + } +} + +static void dw_i3c_push_tx(DWI3C *s, uint32_t val) +{ + if (fifo32_is_full(&s->tx_queue)) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to push to TX FIFO when " + "full\n", path); + return; + } + + trace_dw_i3c_push_tx(s->cfg.id, val); + fifo32_push(&s->tx_queue, val); + ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, TX_BUF_EMPTY_LOC, + fifo32_num_free(&s->tx_queue)); + + /* Threshold is 2^TX_BUF_THLD. */ + uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs, DATA_BUFFER_THLD_CTRL, + TX_BUF_THLD); + empty_threshold = + dw_i3c_fifo_threshold_from_reg(empty_threshold); + if (fifo32_num_free(&s->tx_queue) < empty_threshold) { + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TX_THLD, 0); + dw_i3c_update_irq(s); + } +} + +static uint32_t dw_i3c_pop_tx(DWI3C *s) +{ + if (fifo32_is_empty(&s->tx_queue)) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to pop from TX FIFO when " + "empty\n", path); + return 0; + } + + uint32_t val = fifo32_pop(&s->tx_queue); + trace_dw_i3c_pop_tx(s->cfg.id, val); + ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, TX_BUF_EMPTY_LOC, + fifo32_num_free(&s->tx_queue)); + + /* Threshold is 2^TX_BUF_THLD. */ + uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs, DATA_BUFFER_THLD_CTRL, + TX_BUF_THLD); + empty_threshold = + dw_i3c_fifo_threshold_from_reg(empty_threshold); + if (fifo32_num_free(&s->tx_queue) >= empty_threshold) { + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TX_THLD, 1); + dw_i3c_update_irq(s); + } + return val; +} + +static void dw_i3c_push_rx(DWI3C *s, uint32_t val) +{ + if (fifo32_is_full(&s->rx_queue)) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to push to RX FIFO when " + "full\n", path); + return; + } + trace_dw_i3c_push_rx(s->cfg.id, val); + fifo32_push(&s->rx_queue, val); + + ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, RX_BUF_BLR, + fifo32_num_used(&s->rx_queue)); + /* Threshold is 2^RX_BUF_THLD. */ + uint8_t threshold = ARRAY_FIELD_EX32(s->regs, DATA_BUFFER_THLD_CTRL, + RX_BUF_THLD); + threshold = dw_i3c_fifo_threshold_from_reg(threshold); + if (fifo32_num_used(&s->rx_queue) >= threshold) { + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RX_THLD, 1); + dw_i3c_update_irq(s); + } +} + +static void dw_i3c_short_transfer(DWI3C *s, DWI3CTransferCmd cmd, + DWI3CShortArg arg) +{ + uint8_t err = DW_I3C_RESP_QUEUE_ERR_NONE; + uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index); + bool is_i2c = dw_i3c_target_is_i2c(s, cmd.dev_index); + uint8_t data[4]; /* Max we can send on a short transfer is 4 bytes. */ + uint8_t len = 0; + uint32_t bytes_sent; /* Ignored on short transfers. */ + + /* Can't do reads on a short transfer. */ + if (cmd.rnw) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: Cannot do a read on a short " + "transfer\n", path); + return; + } + + if (dw_i3c_send_start(s, addr, /*is_recv=*/false, is_i2c)) { + err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK; + goto transfer_done; + } + + /* Are we sending a command? */ + if (cmd.cp) { + data[len] = cmd.cmd; + len++; + /* + * byte0 is the defining byte for a command, and is only sent if a + * command is present and if the command has a defining byte present. + * (byte_strb & 0x01) is always treated as set by the controller, and is + * ignored. + */ + if (cmd.dbp) { + data[len] += arg.byte0; + len++; + } + } + + /* Send the bytes passed in the argument. */ + if (arg.byte_strb & 0x02) { + data[len] = arg.byte1; + len++; + } + if (arg.byte_strb & 0x04) { + data[len] = arg.byte2; + len++; + } + + if (dw_i3c_send(s, data, len, &bytes_sent, is_i2c)) { + err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK; + } else { + /* Only go to an idle state on a successful transfer. */ + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS, + DW_I3C_TRANSFER_STATE_IDLE); + } + +transfer_done: + if (cmd.toc) { + dw_i3c_end_transfer(s, is_i2c); + } + if (cmd.roc) { + /* + * ccc_type is always 0 in controller mode, data_len is 0 in short + * transfers. + */ + dw_i3c_resp_queue_push(s, err, cmd.tid, /*ccc_type=*/0, + /*data_len=*/0); + } +} + +/* Returns number of bytes transmitted. */ +static uint16_t dw_i3c_tx(DWI3C *s, uint16_t num, bool is_i2c) +{ + uint16_t bytes_sent = 0; + union { + uint8_t b[sizeof(uint32_t)]; + uint32_t val; + } val32; + + while (bytes_sent < num) { + val32.val = dw_i3c_pop_tx(s); + for (uint8_t i = 0; i < sizeof(val32.val); i++) { + if (dw_i3c_send_byte(s, val32.b[i], is_i2c)) { + return bytes_sent; + } + bytes_sent++; + + /* We're not sending the full 32-bits, break early. */ + if (bytes_sent >= num) { + break; + } + } + } + + return bytes_sent; +} + +/* Returns number of bytes received. */ +static uint16_t dw_i3c_rx(DWI3C *s, uint16_t num, bool is_i2c) +{ + /* + * Allocate a temporary buffer to read data from the target. + * Zero it and word-align it as well in case we're reading unaligned data. + */ + g_autofree uint8_t *data = g_new0(uint8_t, ROUND_UP(num, 4)); + uint32_t *data32 = (uint32_t *)data; + /* + * 32-bits since the I3C API wants a 32-bit number, even though the + * controller can only do 16-bit transfers. + */ + uint32_t num_read = 0; + + /* Can NACK if the target receives an unsupported CCC. */ + if (dw_i3c_recv_data(s, is_i2c, data, num, &num_read)) { + return 0; + } + + for (uint16_t i = 0; i < num_read / 4; i++) { + dw_i3c_push_rx(s, *data32); + data32++; + } + /* + * If we're pushing data that isn't 32-bit aligned, push what's left. + * It's software's responsibility to know what bits are valid in the partial + * data. + */ + if (num_read & 0x03) { + dw_i3c_push_rx(s, *data32); + } + + return num_read; +} + +static int dw_i3c_transfer_ccc(DWI3C *s, DWI3CTransferCmd cmd, + DWI3CTransferArg arg) +{ + /* CCC start is always a write. CCCs cannot be done on I2C devices. */ + if (dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false, + /*is_i2c=*/false)) { + return DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK; + } + trace_dw_i3c_transfer_ccc(s->cfg.id, cmd.cmd); + if (dw_i3c_send_byte(s, cmd.cmd, /*is_i2c=*/false)) { + return DW_I3C_RESP_QUEUE_ERR_I2C_NACK; + } + + /* On a direct CCC, we do a restart and then send the target's address. */ + if (CCC_IS_DIRECT(cmd.cmd)) { + bool is_recv = cmd.rnw; + uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index); + if (dw_i3c_send_start(s, addr, is_recv, /*is_i2c=*/false)) { + return DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK; + } + } + + return DW_I3C_RESP_QUEUE_ERR_NONE; +} + +static void dw_i3c_transfer(DWI3C *s, DWI3CTransferCmd cmd, + DWI3CTransferArg arg) +{ + bool is_recv = cmd.rnw; + uint8_t err = DW_I3C_RESP_QUEUE_ERR_NONE; + uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index); + bool is_i2c = dw_i3c_target_is_i2c(s, cmd.dev_index); + uint16_t bytes_transferred = 0; + + if (cmd.cp) { + /* We're sending a CCC. */ + err = dw_i3c_transfer_ccc(s, cmd, arg); + if (err != DW_I3C_RESP_QUEUE_ERR_NONE) { + goto transfer_done; + } + } else { + if (ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, I3C_BROADCAST_ADDR_INC) && + is_i2c == false) { + if (dw_i3c_send_start(s, I3C_BROADCAST, + /*is_recv=*/false, is_i2c)) { + err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK; + goto transfer_done; + } + } + /* Otherwise we're doing a private transfer. */ + if (dw_i3c_send_start(s, addr, is_recv, is_i2c)) { + err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK; + goto transfer_done; + } + } + + if (is_recv) { + bytes_transferred = dw_i3c_rx(s, arg.data_len, is_i2c); + } else { + bytes_transferred = dw_i3c_tx(s, arg.data_len, is_i2c); + } + + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS, + DW_I3C_TRANSFER_STATE_IDLE); + +transfer_done: + if (cmd.toc) { + dw_i3c_end_transfer(s, is_i2c); + } + if (cmd.roc) { + /* + * data_len is the number of bytes that still need to be TX'd, or the + * number of bytes RX'd. + */ + uint16_t data_len = is_recv ? bytes_transferred : arg.data_len - + bytes_transferred; + /* CCCT is always 0 in controller mode. */ + dw_i3c_resp_queue_push(s, err, cmd.tid, /*ccc_type=*/0, + data_len); + } + + dw_i3c_update_irq(s); +} + +static void dw_i3c_transfer_cmd(DWI3C *s, DWI3CTransferCmd cmd, + DWI3CCmdQueueData arg) +{ + uint8_t arg_attr = FIELD_EX32(arg.word, COMMAND_QUEUE_PORT, CMD_ATTR); + + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CMD_TID, cmd.tid); + + /* User is trying to do HDR transfers, see if we can do them. */ + if (cmd.speed == 0x06 && !dw_i3c_has_hdr_ddr(s)) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: HDR DDR is not supported\n", path); + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS, + DW_I3C_TRANSFER_STATE_HALT); + return; + } + if (cmd.speed == 0x05 && !dw_i3c_has_hdr_ts(s)) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: HDR TS is not supported\n", path); + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS, + DW_I3C_TRANSFER_STATE_HALT); + return; + } + + if (arg_attr == DW_I3C_CMD_ATTR_TRANSFER_ARG) { + dw_i3c_transfer(s, cmd, arg.transfer_arg); + } else if (arg_attr == DW_I3C_CMD_ATTR_SHORT_DATA_ARG) { + dw_i3c_short_transfer(s, cmd, arg.short_arg); + } else { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: Unknown command queue cmd_attr 0x%x" + "\n", path, arg_attr); + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS, + DW_I3C_TRANSFER_STATE_HALT); + } +} + +static void dw_i3c_update_char_table(DWI3C *s, uint8_t offset, uint64_t pid, + uint8_t bcr, uint8_t dcr, uint8_t addr) +{ + if (offset > s->cfg.num_addressable_devices) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: Device char table offset %d out of " + "bounds\n", path, offset); + /* If we're out of bounds, do nothing. */ + return; + } + + /* + * Each device offset is 128 bits apart in the table, since each device gets + * 4 * 32-bits of entries in the table. + * / sizeof(uint32_t) because we're indexing into our 32-bit reg array. + */ + uint16_t dev_index = (ARRAY_FIELD_EX32(s->regs, DEV_CHAR_TABLE_POINTER, + P_DEV_CHAR_TABLE_START_ADDR) / + sizeof(uint32_t)) + + (offset * sizeof(uint32_t)); + s->regs[dev_index] = pid & 0xffffffff; + pid >>= 32; + s->regs[dev_index + 1] = FIELD_DP32(s->regs[dev_index + 1], + DEVICE_CHARACTERISTIC_TABLE_LOC2, + MSB_PID, pid); + s->regs[dev_index + 2] = FIELD_DP32(s->regs[dev_index + 2], + DEVICE_CHARACTERISTIC_TABLE_LOC3, DCR, + dcr); + s->regs[dev_index + 2] = FIELD_DP32(s->regs[dev_index + 2], + DEVICE_CHARACTERISTIC_TABLE_LOC3, BCR, + bcr); + s->regs[dev_index + 3] = FIELD_DP32(s->regs[dev_index + 3], + DEVICE_CHARACTERISTIC_TABLE_LOC4, + DEV_DYNAMIC_ADDR, addr); + + /* Increment PRESENT_DEV_CHAR_TABLE_INDEX. */ + uint8_t idx = ARRAY_FIELD_EX32(s->regs, DEV_CHAR_TABLE_POINTER, + PRESENT_DEV_CHAR_TABLE_INDEX); + /* Increment and rollover. */ + idx++; + if (idx >= ARRAY_FIELD_EX32(s->regs, DEV_CHAR_TABLE_POINTER, + DEV_CHAR_TABLE_DEPTH) / 4) { + idx = 0; + } + ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER, + PRESENT_DEV_CHAR_TABLE_INDEX, idx); +} + +static void dw_i3c_addr_assign_cmd(DWI3C *s, DWI3CAddrAssignCmd cmd) +{ + uint8_t i = 0; + uint8_t err = DW_I3C_RESP_QUEUE_ERR_NONE; + + /* Tell everyone to ENTDAA. If these error, no one is on the bus. */ + if (dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false, + /*is_i2c=*/false)) { + err = DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK; + goto transfer_done; + } + if (dw_i3c_send_byte(s, cmd.cmd, /*is_i2c=*/false)) { + err = DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK; + goto transfer_done; + } + + /* Go through each device in the table and assign it an address. */ + for (i = 0; i < cmd.dev_count; i++) { + uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index + i); + union { + uint64_t pid:48; + uint8_t bcr; + uint8_t dcr; + uint32_t w[2]; + uint8_t b[8]; + } target_info; + + /* If this fails, there was no one left to ENTDAA. */ + if (dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false, + /*is_i2c=*/false)) { + err = DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK; + break; + } + + /* + * In ENTDAA, we read 8 bytes from the target, which will be the + * target's PID, BCR, and DCR. After that, we send it the dynamic + * address. + * Don't bother checking the number of bytes received, it must send 8 + * bytes during ENTDAA. + */ + uint32_t num_read; + if (dw_i3c_recv_data(s, /*is_i2c=*/false, target_info.b, + I3C_ENTDAA_SIZE, &num_read)) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: Target NACKed ENTDAA CCC\n", + path); + err = DW_I3C_RESP_QUEUE_ERR_DAA_NACK; + goto transfer_done; + } + if (dw_i3c_send_byte(s, addr, /*is_i2c=*/false)) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: Target NACKed addr 0x%.2x " + "during ENTDAA\n", path, addr); + err = DW_I3C_RESP_QUEUE_ERR_DAA_NACK; + break; + } + dw_i3c_update_char_table(s, cmd.dev_index + i, + target_info.pid, target_info.bcr, + target_info.dcr, addr); + + /* Push the PID, BCR, and DCR to the RX queue. */ + dw_i3c_push_rx(s, target_info.w[0]); + dw_i3c_push_rx(s, target_info.w[1]); + } + +transfer_done: + /* Do we send a STOP? */ + if (cmd.toc) { + dw_i3c_end_transfer(s, /*is_i2c=*/false); + } + /* + * For addr assign commands, the length field is the number of devices + * left to assign. CCCT is always 0 in controller mode. + */ + if (cmd.roc) { + dw_i3c_resp_queue_push(s, err, cmd.tid, /*ccc_type=*/0, + cmd.dev_count - i); + } +} + +static uint32_t dw_i3c_cmd_queue_pop(DWI3C *s) +{ + if (fifo32_is_empty(&s->cmd_queue)) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to dequeue command queue " + "when it was empty\n", path); + return 0; + } + uint32_t val = fifo32_pop(&s->cmd_queue); + + uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL, + CMD_BUF_EMPTY_THLD); + uint8_t cmd_queue_empty_loc = ARRAY_FIELD_EX32(s->regs, + QUEUE_STATUS_LEVEL, + CMD_QUEUE_EMPTY_LOC); + cmd_queue_empty_loc++; + ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, CMD_QUEUE_EMPTY_LOC, + cmd_queue_empty_loc); + if (cmd_queue_empty_loc >= empty_threshold) { + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, CMD_QUEUE_RDY, 1); + dw_i3c_update_irq(s); + } + + return val; +} + +static void dw_i3c_cmd_queue_execute(DWI3C *s) +{ + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS, + DW_I3C_TRANSFER_STATE_IDLE); + if (!dw_i3c_can_transmit(s)) { + return; + } + + /* + * We only start executing when a command is passed into the FIFO. + * We expect there to be a multiple of 2 items in the queue. The first item + * should be an argument to a command, and the command should be the second + * item. + */ + if (fifo32_num_used(&s->cmd_queue) & 1) { + return; + } + + while (!fifo32_is_empty(&s->cmd_queue)) { + DWI3CCmdQueueData arg; + arg.word = dw_i3c_cmd_queue_pop(s); + DWI3CCmdQueueData cmd; + cmd.word = dw_i3c_cmd_queue_pop(s); + trace_dw_i3c_cmd_queue_execute(s->cfg.id, cmd.word, arg.word); + + uint8_t cmd_attr = FIELD_EX32(cmd.word, COMMAND_QUEUE_PORT, CMD_ATTR); + switch (cmd_attr) { + case DW_I3C_CMD_ATTR_TRANSFER_CMD: + dw_i3c_transfer_cmd(s, cmd.transfer_cmd, arg); + break; + case DW_I3C_CMD_ATTR_ADDR_ASSIGN_CMD: + /* Arg is discarded for addr assign commands. */ + dw_i3c_addr_assign_cmd(s, cmd.addr_assign_cmd); + break; + case DW_I3C_CMD_ATTR_TRANSFER_ARG: + case DW_I3C_CMD_ATTR_SHORT_DATA_ARG: + { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: Command queue received " + "argument packet when it expected a command " + "packet\n", path); + } + break; + default: + /* + * The caller's check before queueing an item should prevent this + * from happening. + */ + g_assert_not_reached(); + break; + } + } +} + +static void dw_i3c_cmd_queue_push(DWI3C *s, uint32_t val) +{ + if (fifo32_is_full(&s->cmd_queue)) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: Command queue received packet when " + "already full\n", path); + return; + } + trace_dw_i3c_cmd_queue_push(s->cfg.id, val); + fifo32_push(&s->cmd_queue, val); + + uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL, + CMD_BUF_EMPTY_THLD); + uint8_t cmd_queue_empty_loc = ARRAY_FIELD_EX32(s->regs, + QUEUE_STATUS_LEVEL, + CMD_QUEUE_EMPTY_LOC); + if (cmd_queue_empty_loc) { + cmd_queue_empty_loc--; + ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, CMD_QUEUE_EMPTY_LOC, + cmd_queue_empty_loc); + } + if (cmd_queue_empty_loc < empty_threshold) { + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, CMD_QUEUE_RDY, 0); + dw_i3c_update_irq(s); + } +} + +static void dw_i3c_cmd_queue_port_w(DWI3C *s, uint32_t val) +{ + uint8_t cmd_attr = FIELD_EX32(val, COMMAND_QUEUE_PORT, CMD_ATTR); + + switch (cmd_attr) { + /* If a command is received we can start executing it. */ + case DW_I3C_CMD_ATTR_TRANSFER_CMD: + case DW_I3C_CMD_ATTR_ADDR_ASSIGN_CMD: + dw_i3c_cmd_queue_push(s, val); + dw_i3c_cmd_queue_execute(s); + break; + /* If we get an argument just push it. */ + case DW_I3C_CMD_ATTR_TRANSFER_ARG: + case DW_I3C_CMD_ATTR_SHORT_DATA_ARG: + dw_i3c_cmd_queue_push(s, val); + break; + default: + { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: Command queue received packet " + "with unknown cmd attr 0x%x\n", path, cmd_attr); + } + break; + } +} + +static void dw_i3c_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + DWI3C *s = DW_I3C(opaque); + uint32_t addr = offset >> 2; + uint32_t val32 = (uint32_t)value; + + trace_dw_i3c_write(s->cfg.id, offset, value); + + val32 &= ~dw_i3c_ro[addr]; + switch (addr) { + case R_HW_CAPABILITY: + case R_RESPONSE_QUEUE_PORT: + case R_IBI_QUEUE_DATA: + case R_QUEUE_STATUS_LEVEL: + case R_PRESENT_STATE: + case R_CCC_DEVICE_STATUS: + case R_DEVICE_ADDR_TABLE_POINTER: + case R_VENDOR_SPECIFIC_REG_POINTER: + case R_SLV_CHAR_CTRL: + case R_SLV_MAX_LEN: + case R_MAX_READ_TURNAROUND: + case R_I3C_VER_ID: + case R_I3C_VER_TYPE: + case R_EXTENDED_CAPABILITY: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to readonly register[0x%02" HWADDR_PRIx + "] = 0x%08" PRIx64 "\n", + __func__, offset, value); + break; + case R_DEVICE_CTRL: + dw_i3c_ctrl_w(s, val32); + break; + case R_RX_TX_DATA_PORT: + dw_i3c_push_tx(s, val32); + break; + case R_COMMAND_QUEUE_PORT: + dw_i3c_cmd_queue_port_w(s, val32); + break; + case R_RESET_CTRL: + dw_i3c_reset_ctrl_w(s, val32); + break; + case R_INTR_STATUS: + dw_i3c_intr_status_w(s, val32); + break; + case R_INTR_STATUS_EN: + dw_i3c_intr_status_en_w(s, val32); + break; + case R_INTR_SIGNAL_EN: + dw_i3c_intr_signal_en_w(s, val32); + break; + case R_INTR_FORCE: + dw_i3c_intr_force_w(s, val32); + break; + default: + s->regs[addr] = val32; + break; + } +} + +const VMStateDescription vmstate_dw_i3c = { + .name = TYPE_DW_I3C, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]){ + VMSTATE_UINT32_ARRAY(regs, DWI3C, DW_I3C_NR_REGS), + VMSTATE_END_OF_LIST(), + } +}; + +static const MemoryRegionOps dw_i3c_ops = { + .read = dw_i3c_read, + .write = dw_i3c_write, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void dw_i3c_reset_enter(Object *obj, ResetType type) +{ + DWI3C *s = DW_I3C(obj); + + memcpy(s->regs, dw_i3c_resets, sizeof(s->regs)); + /* + * The user config for these may differ from our resets array, set them + * manually. + */ + ARRAY_FIELD_DP32(s->regs, DEVICE_ADDR_TABLE_POINTER, ADDR, + s->cfg.dev_addr_table_pointer); + ARRAY_FIELD_DP32(s->regs, DEVICE_ADDR_TABLE_POINTER, DEPTH, + s->cfg.dev_addr_table_depth); + ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER, + P_DEV_CHAR_TABLE_START_ADDR, + s->cfg.dev_char_table_pointer); + ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER, DEV_CHAR_TABLE_DEPTH, + s->cfg.dev_char_table_depth); +} + +static void dw_i3c_realize(DeviceState *dev, Error **errp) +{ + DWI3C *s = DW_I3C(dev); + g_autofree char *name = g_strdup_printf(TYPE_DW_I3C ".%d", s->cfg.id); + + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq); + + memory_region_init_io(&s->mr, OBJECT(s), &dw_i3c_ops, s, name, + DW_I3C_NR_REGS << 2); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mr); + + fifo32_create(&s->cmd_queue, s->cfg.cmd_resp_queue_capacity_bytes); + fifo32_create(&s->resp_queue, s->cfg.cmd_resp_queue_capacity_bytes); + fifo32_create(&s->tx_queue, s->cfg.tx_rx_queue_capacity_bytes); + fifo32_create(&s->rx_queue, s->cfg.tx_rx_queue_capacity_bytes); + fifo32_create(&s->ibi_queue, s->cfg.ibi_queue_capacity_bytes); + /* Arbitrarily large enough to not be an issue. */ + fifo8_create(&s->ibi_data.ibi_intermediate_queue, + s->cfg.ibi_queue_capacity_bytes * 8); + + s->bus = i3c_init_bus(DEVICE(s), name); + I3CBusClass *bc = I3C_BUS_GET_CLASS(s->bus); + bc->ibi_handle = dw_i3c_ibi_handle; + bc->ibi_recv = dw_i3c_ibi_recv; + bc->ibi_finish = dw_i3c_ibi_finish; +} + +static const Property dw_i3c_properties[] = { + DEFINE_PROP_UINT8("device-id", DWI3C, cfg.id, 0), + DEFINE_PROP_UINT8("command-response-queue-capacity-bytes", DWI3C, + cfg.cmd_resp_queue_capacity_bytes, 0x10), + DEFINE_PROP_UINT16("tx-rx-queue-capacity-bytes", DWI3C, + cfg.tx_rx_queue_capacity_bytes, 0x40), + DEFINE_PROP_UINT8("ibi-queue-capacity-bytes", DWI3C, + cfg.ibi_queue_capacity_bytes, 0x10), + DEFINE_PROP_UINT8("num-addressable-devices", DWI3C, + cfg.num_addressable_devices, 8), + DEFINE_PROP_UINT16("dev-addr-table-pointer", DWI3C, + cfg.dev_addr_table_pointer, 0x280), + DEFINE_PROP_UINT16("dev-addr-table-depth", DWI3C, + cfg.dev_addr_table_depth, 0x08), + DEFINE_PROP_UINT16("dev-char-table-pointer", DWI3C, + cfg.dev_char_table_pointer, 0x200), + DEFINE_PROP_UINT16("dev-char-table-depth", DWI3C, + cfg.dev_char_table_depth, 0x20), +}; + +static void dw_i3c_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + rc->phases.enter = dw_i3c_reset_enter; + + dc->desc = "DesignWare I3C Controller"; + dc->realize = dw_i3c_realize; + dc->vmsd = &vmstate_dw_i3c; + device_class_set_props(dc, dw_i3c_properties); +} + +static const TypeInfo dw_i3c_types[] = { + { + .name = TYPE_DW_I3C, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(DWI3C), + .class_init = dw_i3c_class_init, + }, +}; + +DEFINE_TYPES(dw_i3c_types) + diff --git a/hw/i3c/meson.build b/hw/i3c/meson.build new file mode 100644 index 000000000000..e614b187123d --- /dev/null +++ b/hw/i3c/meson.build @@ -0,0 +1,6 @@ +i3c_ss = ss.source_set() +i3c_ss.add(when: 'CONFIG_I3C', if_true: files('core.c')) +i3c_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_i3c.c')) +i3c_ss.add(when: 'CONFIG_DW_I3C', if_true: files('dw-i3c.c')) +i3c_ss.add(when: 'CONFIG_MOCK_I3C_TARGET', if_true: files('mock-i3c-target.c')) +system_ss.add_all(when: 'CONFIG_I3C', if_true: i3c_ss) diff --git a/hw/i3c/mock-i3c-target.c b/hw/i3c/mock-i3c-target.c new file mode 100644 index 000000000000..1ae2cf9e1de1 --- /dev/null +++ b/hw/i3c/mock-i3c-target.c @@ -0,0 +1,303 @@ +/* + * Mock I3C Device + * + * Copyright (c) 2025 Google LLC + * + * The mock I3C device can be thought of as a simple EEPROM. It has a buffer, + * and the pointer in the buffer is reset to 0 on an I3C STOP. + * To write to the buffer, issue a private write and send data. + * To read from the buffer, issue a private read. + * + * The mock target also supports sending target interrupt IBIs. + * To issue an IBI, set the 'ibi-magic-num' property to a non-zero number, and + * send that number in a private transaction. The mock target will issue an IBI + * after 1 second. + * + * It also supports a handful of CCCs that are typically used when probing I3C + * devices. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "trace.h" +#include "hw/i3c/i3c.h" +#include "hw/i3c/mock-i3c-target.h" +#include "hw/core/irq.h" +#include "hw/core/qdev-properties.h" +#include "qapi/error.h" +#include "qemu/module.h" + +#define IBI_DELAY_NS (1 * 1000 * 1000) + +static uint32_t mock_i3c_target_rx(I3CTarget *i3c, uint8_t *data, + uint32_t num_to_read) +{ + MockI3cTargetState *s = MOCK_I3C_TARGET(i3c); + uint32_t i; + + /* Bounds check. */ + if (s->p_buf == s->cfg.buf_size) { + return 0; + } + + for (i = 0; i < num_to_read; i++) { + data[i] = s->buf[s->p_buf]; + trace_mock_i3c_target_rx(data[i]); + s->p_buf++; + if (s->p_buf == s->cfg.buf_size) { + break; + } + } + + /* Return the number of bytes we're sending to the controller. */ + return i; +} + +static void mock_i3c_target_ibi_timer_start(MockI3cTargetState *s) +{ + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + timer_mod(&s->qtimer, now + IBI_DELAY_NS); +} + +static int mock_i3c_target_tx(I3CTarget *i3c, const uint8_t *data, + uint32_t num_to_send, uint32_t *num_sent) +{ + MockI3cTargetState *s = MOCK_I3C_TARGET(i3c); + int ret; + uint32_t to_write; + + if (s->cfg.ibi_magic && num_to_send == 1 && s->cfg.ibi_magic == *data) { + mock_i3c_target_ibi_timer_start(s); + *num_sent = 1; + return 0; + } + + /* Bounds check. */ + if (num_to_send + s->p_buf > s->cfg.buf_size) { + to_write = s->cfg.buf_size - s->p_buf; + ret = -1; + } else { + to_write = num_to_send; + ret = 0; + } + for (uint32_t i = 0; i < to_write; i++) { + trace_mock_i3c_target_tx(data[i]); + s->buf[s->p_buf] = data[i]; + s->p_buf++; + } + *num_sent = to_write; + return ret; +} + +static int mock_i3c_target_event(I3CTarget *i3c, enum I3CEvent event) +{ + MockI3cTargetState *s = MOCK_I3C_TARGET(i3c); + + trace_mock_i3c_target_event(event); + if (event == I3C_STOP) { + s->in_ccc = false; + s->curr_ccc = 0; + s->ccc_byte_offset = 0; + s->p_buf = 0; + } + + return 0; +} + +static int mock_i3c_target_handle_ccc_read(I3CTarget *i3c, uint8_t *data, + uint32_t num_to_read, + uint32_t *num_read) +{ + MockI3cTargetState *s = MOCK_I3C_TARGET(i3c); + + switch (s->curr_ccc) { + case I3C_CCCD_GETMXDS: + /* Default data rate for I3C. */ + while (s->ccc_byte_offset < num_to_read) { + if (s->ccc_byte_offset >= 2) { + break; + } + data[s->ccc_byte_offset] = 0; + *num_read = s->ccc_byte_offset; + s->ccc_byte_offset++; + } + break; + case I3C_CCCD_GETCAPS: + /* Support I3C version 1.1.x, no other features. */ + while (s->ccc_byte_offset < num_to_read) { + if (s->ccc_byte_offset >= 2) { + break; + } + if (s->ccc_byte_offset == 0) { + data[s->ccc_byte_offset] = 0; + } else { + data[s->ccc_byte_offset] = 0x01; + } + *num_read = s->ccc_byte_offset; + s->ccc_byte_offset++; + } + break; + case I3C_CCCD_GETMWL: + case I3C_CCCD_GETMRL: + /* MWL/MRL is MSB first. */ + while (s->ccc_byte_offset < num_to_read) { + if (s->ccc_byte_offset >= 2) { + break; + } + if (s->ccc_byte_offset == 0) { + data[s->ccc_byte_offset] = (uint8_t)(s->cfg.buf_size >> 8); + } else { + data[s->ccc_byte_offset] = (uint8_t)s->cfg.buf_size; + } + + s->ccc_byte_offset++; + *num_read = num_to_read; + } + break; + case I3C_CCC_ENTDAA: + case I3C_CCCD_GETPID: + case I3C_CCCD_GETBCR: + case I3C_CCCD_GETDCR: + /* Nothing to do. */ + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "Unhandled CCC 0x%.2x\n", s->curr_ccc); + return -1; + } + + trace_mock_i3c_target_handle_ccc_read(*num_read, num_to_read); + return 0; +} + +static int mock_i3c_target_handle_ccc_write(I3CTarget *i3c, const uint8_t *data, + uint32_t num_to_send, + uint32_t *num_sent) +{ + MockI3cTargetState *s = MOCK_I3C_TARGET(i3c); + + if (!s->curr_ccc) { + s->in_ccc = true; + s->curr_ccc = *data; + trace_mock_i3c_target_new_ccc(s->curr_ccc); + } + + *num_sent = 1; + switch (s->curr_ccc) { + case I3C_CCC_ENEC: + case I3C_CCCD_ENEC: + s->can_ibi = true; + break; + case I3C_CCC_DISEC: + case I3C_CCCD_DISEC: + s->can_ibi = false; + break; + case I3C_CCC_ENTDAA: + case I3C_CCC_SETAASA: + case I3C_CCC_RSTDAA: + case I3C_CCCD_SETDASA: + case I3C_CCCD_GETPID: + case I3C_CCCD_GETBCR: + case I3C_CCCD_GETDCR: + case I3C_CCCD_GETMWL: + case I3C_CCCD_GETMRL: + case I3C_CCCD_GETMXDS: + case I3C_CCCD_GETCAPS: + /* Nothing to do. */ + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "Unhandled CCC 0x%.2x\n", s->curr_ccc); + return -1; + } + + trace_mock_i3c_target_handle_ccc_write(*num_sent, num_to_send); + return 0; +} + +static void mock_i3c_target_do_ibi(MockI3cTargetState *s) +{ + if (!s->can_ibi) { + return; + } + + trace_mock_i3c_target_do_ibi(s->parent_obj.address, true); + int nack = i3c_target_send_ibi(&s->parent_obj, s->parent_obj.address, + /*is_recv=*/true); + /* Getting NACKed isn't necessarily an error, just print it out. */ + if (nack) { + trace_mock_i3c_target_do_ibi_nack("sending"); + } + nack = i3c_target_ibi_finish(&s->parent_obj, 0x00); + if (nack) { + trace_mock_i3c_target_do_ibi_nack("finishing"); + } +} + +static void mock_i3c_target_timer_elapsed(void *opaque) +{ + MockI3cTargetState *s = MOCK_I3C_TARGET(opaque); + timer_del(&s->qtimer); + mock_i3c_target_do_ibi(s); +} + +static void mock_i3c_target_reset(I3CTarget *i3c) +{ + MockI3cTargetState *s = MOCK_I3C_TARGET(i3c); + s->can_ibi = false; +} + +static void mock_i3c_target_realize(DeviceState *dev, Error **errp) +{ + MockI3cTargetState *s = MOCK_I3C_TARGET(dev); + s->buf = g_new0(uint8_t, s->cfg.buf_size); + mock_i3c_target_reset(&s->parent_obj); +} + +static void mock_i3c_target_init(Object *obj) +{ + MockI3cTargetState *s = MOCK_I3C_TARGET(obj); + s->can_ibi = false; + + /* For IBIs. */ + timer_init_ns(&s->qtimer, QEMU_CLOCK_VIRTUAL, mock_i3c_target_timer_elapsed, + s); +} + +static const Property remote_i3c_props[] = { + /* The size of the internal buffer. */ + DEFINE_PROP_UINT32("buf-size", MockI3cTargetState, cfg.buf_size, 0x100), + /* + * If the mock target receives this number, it will issue an IBI after + * 1 second. Disabled if the IBI magic number is 0. + */ + DEFINE_PROP_UINT8("ibi-magic-num", MockI3cTargetState, cfg.ibi_magic, 0x00), +}; + +static void mock_i3c_target_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + I3CTargetClass *k = I3C_TARGET_CLASS(klass); + + dc->realize = mock_i3c_target_realize; + k->event = mock_i3c_target_event; + k->recv = mock_i3c_target_rx; + k->send = mock_i3c_target_tx; + k->handle_ccc_read = mock_i3c_target_handle_ccc_read; + k->handle_ccc_write = mock_i3c_target_handle_ccc_write; + + device_class_set_props(dc, remote_i3c_props); +} + +static const TypeInfo mock_i3c_target_types[] = { + { + .name = TYPE_MOCK_I3C_TARGET, + .parent = TYPE_I3C_TARGET, + .instance_size = sizeof(MockI3cTargetState), + .instance_init = mock_i3c_target_init, + .class_init = mock_i3c_target_class_init, + }, +}; + +DEFINE_TYPES(mock_i3c_target_types) + diff --git a/hw/i3c/trace-events b/hw/i3c/trace-events new file mode 100644 index 000000000000..9e58edec99ae --- /dev/null +++ b/hw/i3c/trace-events @@ -0,0 +1,48 @@ +# See docs/devel/tracing.rst for syntax documentation. + +# aspeed_i3c.c +aspeed_i3c_read(uint64_t offset, uint64_t data) "I3C read: offset 0x%" PRIx64 " data 0x%" PRIx64 +aspeed_i3c_write(uint64_t offset, uint64_t data) "I3C write: offset 0x%" PRIx64 " data 0x%" PRIx64 + +# dw-i3c,c +dw_i3c_read(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] read: offset 0x%" PRIx64 " data 0x%" PRIx64 +dw_i3c_write(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] write: offset 0x%" PRIx64 " data 0x%" PRIx64 +dw_i3c_send(uint32_t deviceid, uint32_t num_bytes) "I3C Dev[%u] send %" PRId32 " bytes to bus" +dw_i3c_recv_data(uint32_t deviceid, uint32_t num_bytes) "I3C Dev[%u] recv %" PRId32 " bytes from bus" +dw_i3c_ibi_recv(uint32_t deviceid, uint8_t ibi_byte) "I3C Dev[%u] recv IBI byte 0x%" PRIx8 +dw_i3c_ibi_handle(uint32_t deviceid, uint8_t addr, bool rnw) "I3C Dev[%u] handle IBI from address 0x%" PRIx8 " RnW=%d" +dw_i3c_reset(uint32_t deviceid) "I3C Dev[%u] reset" +dw_i3c_pop_rx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] pop 0x%" PRIx32 " from RX FIFO" +dw_i3c_resp_queue_push(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to response queue" +dw_i3c_push_tx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to TX FIFO" +dw_i3c_pop_tx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] pop 0x%" PRIx32 " from TX FIFO" +dw_i3c_push_rx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to RX FIFO" +dw_i3c_transfer_ccc(uint32_t deviceid, uint8_t ccc) "I3C Dev[%u] transfer CCC 0x%" PRIx8 +dw_i3c_cmd_queue_execute(uint32_t deviceid, uint32_t cmd, uint32_t arg) "I3C Dev[%u] execute command 0x%" PRIx32 " arg 0x%" PRIx32 +dw_i3c_cmd_queue_push(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to cmd queue" + +# core.c +i3c_target_event(uint8_t address, uint8_t event) "I3C target 0x%" PRIx8 " event 0x%" PRIx8 +i3c_target_handle_ccc(uint8_t address, uint8_t ccc) "I3C target 0x%" PRIx8 " handling CCC 0x%" PRIx8 +i3c_target_send_ibi(uint8_t address, bool is_recv) "I3C target IBI address 0x%" PRIx8 " RnW=%d" +i3c_target_send_ibi_bytes(uint8_t byte) "I3C target IBI byte 0x%" PRIx8 +i3c_target_ibi_finish(void) "I3C target IBI finish" +i3c_start_transfer(uint8_t address, bool is_recv) "I3C START with address 0x%" PRIx8 " is_recv=%d" +i3c_end_transfer(void) "I3C transfer done" +i3c_send(uint32_t num_sent, uint32_t num_to_send, bool ack) "I3C send %" PRId32 "/%" PRId32 " bytes, ack=%d" +i3c_recv(uint32_t num_read, uint32_t num_to_read, bool ack) "I3C recv %" PRId32 "/%" PRId32 " bytes, ack=%d" +legacy_i2c_nack(void) "Legacy I2C NACK" +legacy_i2c_recv(uint8_t byte) "Legacy I2C recv 0x%" PRIx8 +legacy_i2c_send(uint8_t byte) "Legacy I2C send 0x%" PRIx8 +legacy_i2c_start_transfer(uint8_t address, bool is_recv) "Legacy I2C START with address 0x%" PRIx8 " is_recv=%d" +legacy_i2c_end_transfer(void) "Legacy I2C STOP" + +# mock-target.c +mock_i3c_target_rx(uint8_t byte) "I3C mock target read 0x%" PRIx8 +mock_i3c_target_tx(uint8_t byte) "I3C mock target write 0x%" PRIx8 +mock_i3c_target_event(uint8_t event) "I3C mock target event 0x%" PRIx8 +mock_i3c_target_handle_ccc_read(uint32_t num_read, uint32_t num_to_read) "I3C mock target read %" PRId32 "/%" PRId32 " bytes" +mock_i3c_target_new_ccc(uint8_t ccc) "I3C mock target handle CCC 0x%" PRIx8 +mock_i3c_target_handle_ccc_write(uint32_t num_sent, uint32_t num_to_send) "I3C mock target send %" PRId32 "/%" PRId32 " bytes" +mock_i3c_target_do_ibi(uint8_t address, bool is_recv) "I3C mock target IBI with address 0x%" PRIx8 " RnW=%d" +mock_i3c_target_do_ibi_nack(const char *reason) "NACKed from controller when %s target interrupt" diff --git a/hw/i3c/trace.h b/hw/i3c/trace.h new file mode 100644 index 000000000000..1e0c4eadf0ed --- /dev/null +++ b/hw/i3c/trace.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "trace/trace-hw_i3c.h" diff --git a/hw/input/virtio-input-hid.c b/hw/input/virtio-input-hid.c index bcbfef0b905a..14dde90a48b3 100644 --- a/hw/input/virtio-input-hid.c +++ b/hw/input/virtio-input-hid.c @@ -81,7 +81,6 @@ static void virtio_input_extend_config(VirtIOInput *vinput, static void virtio_input_handle_event(DeviceState *dev, QemuConsole *src, InputEvent *evt) { - VirtIOInputHID *vhid = VIRTIO_INPUT_HID(dev); VirtIOInput *vinput = VIRTIO_INPUT(dev); virtio_input_event event; int qcode; @@ -109,8 +108,7 @@ static void virtio_input_handle_event(DeviceState *dev, QemuConsole *src, break; case INPUT_EVENT_KIND_BTN: btn = evt->u.btn.data; - if (vhid->wheel_axis && - (btn->button == INPUT_BUTTON_WHEEL_UP || + if ((btn->button == INPUT_BUTTON_WHEEL_UP || btn->button == INPUT_BUTTON_WHEEL_DOWN) && btn->down) { event.type = cpu_to_le16(EV_REL); @@ -328,32 +326,7 @@ static const QemuInputHandler virtio_mouse_handler = { .sync = virtio_input_handle_sync, }; -static struct virtio_input_config virtio_mouse_config_v1[] = { - { - .select = VIRTIO_INPUT_CFG_ID_NAME, - .size = sizeof(VIRTIO_ID_NAME_MOUSE), - .u.string = VIRTIO_ID_NAME_MOUSE, - },{ - .select = VIRTIO_INPUT_CFG_ID_DEVIDS, - .size = sizeof(struct virtio_input_devids), - .u.ids = { - .bustype = const_le16(BUS_VIRTUAL), - .vendor = const_le16(0x0627), /* same we use for usb hid devices */ - .product = const_le16(0x0002), - .version = const_le16(0x0001), - }, - },{ - .select = VIRTIO_INPUT_CFG_EV_BITS, - .subsel = EV_REL, - .size = 1, - .u.bitmap = { - (1 << REL_X) | (1 << REL_Y), - }, - }, - { /* end of list */ }, -}; - -static struct virtio_input_config virtio_mouse_config_v2[] = { +static struct virtio_input_config virtio_mouse_config[] = { { .select = VIRTIO_INPUT_CFG_ID_NAME, .size = sizeof(VIRTIO_ID_NAME_MOUSE), @@ -379,26 +352,13 @@ static struct virtio_input_config virtio_mouse_config_v2[] = { { /* end of list */ }, }; -static const Property virtio_mouse_properties[] = { - DEFINE_PROP_BOOL("wheel-axis", VirtIOInputHID, wheel_axis, true), -}; - -static void virtio_mouse_class_init(ObjectClass *klass, const void *data) -{ - DeviceClass *dc = DEVICE_CLASS(klass); - - device_class_set_props(dc, virtio_mouse_properties); -} - static void virtio_mouse_init(Object *obj) { VirtIOInputHID *vhid = VIRTIO_INPUT_HID(obj); VirtIOInput *vinput = VIRTIO_INPUT(obj); vhid->handler = &virtio_mouse_handler; - virtio_input_init_config(vinput, vhid->wheel_axis - ? virtio_mouse_config_v2 - : virtio_mouse_config_v1); + virtio_input_init_config(vinput, virtio_mouse_config); virtio_input_extend_config(vinput, keymap_button, ARRAY_SIZE(keymap_button), VIRTIO_INPUT_CFG_EV_BITS, EV_KEY); @@ -409,7 +369,6 @@ static const TypeInfo virtio_mouse_info = { .parent = TYPE_VIRTIO_INPUT_HID, .instance_size = sizeof(VirtIOInputHID), .instance_init = virtio_mouse_init, - .class_init = virtio_mouse_class_init, }; /* ----------------------------------------------------------------- */ @@ -421,44 +380,7 @@ static const QemuInputHandler virtio_tablet_handler = { .sync = virtio_input_handle_sync, }; -static struct virtio_input_config virtio_tablet_config_v1[] = { - { - .select = VIRTIO_INPUT_CFG_ID_NAME, - .size = sizeof(VIRTIO_ID_NAME_TABLET), - .u.string = VIRTIO_ID_NAME_TABLET, - },{ - .select = VIRTIO_INPUT_CFG_ID_DEVIDS, - .size = sizeof(struct virtio_input_devids), - .u.ids = { - .bustype = const_le16(BUS_VIRTUAL), - .vendor = const_le16(0x0627), /* same we use for usb hid devices */ - .product = const_le16(0x0003), - .version = const_le16(0x0001), - }, - },{ - .select = VIRTIO_INPUT_CFG_EV_BITS, - .subsel = EV_ABS, - .size = 1, - .u.bitmap = { - (1 << ABS_X) | (1 << ABS_Y), - }, - },{ - .select = VIRTIO_INPUT_CFG_ABS_INFO, - .subsel = ABS_X, - .size = sizeof(virtio_input_absinfo), - .u.abs.min = const_le32(INPUT_EVENT_ABS_MIN), - .u.abs.max = const_le32(INPUT_EVENT_ABS_MAX), - },{ - .select = VIRTIO_INPUT_CFG_ABS_INFO, - .subsel = ABS_Y, - .size = sizeof(virtio_input_absinfo), - .u.abs.min = const_le32(INPUT_EVENT_ABS_MIN), - .u.abs.max = const_le32(INPUT_EVENT_ABS_MAX), - }, - { /* end of list */ }, -}; - -static struct virtio_input_config virtio_tablet_config_v2[] = { +static struct virtio_input_config virtio_tablet_config[] = { { .select = VIRTIO_INPUT_CFG_ID_NAME, .size = sizeof(VIRTIO_ID_NAME_TABLET), @@ -503,26 +425,13 @@ static struct virtio_input_config virtio_tablet_config_v2[] = { { /* end of list */ }, }; -static const Property virtio_tablet_properties[] = { - DEFINE_PROP_BOOL("wheel-axis", VirtIOInputHID, wheel_axis, true), -}; - -static void virtio_tablet_class_init(ObjectClass *klass, const void *data) -{ - DeviceClass *dc = DEVICE_CLASS(klass); - - device_class_set_props(dc, virtio_tablet_properties); -} - static void virtio_tablet_init(Object *obj) { VirtIOInputHID *vhid = VIRTIO_INPUT_HID(obj); VirtIOInput *vinput = VIRTIO_INPUT(obj); vhid->handler = &virtio_tablet_handler; - virtio_input_init_config(vinput, vhid->wheel_axis - ? virtio_tablet_config_v2 - : virtio_tablet_config_v1); + virtio_input_init_config(vinput, virtio_tablet_config); virtio_input_extend_config(vinput, keymap_button, ARRAY_SIZE(keymap_button), VIRTIO_INPUT_CFG_EV_BITS, EV_KEY); @@ -533,7 +442,6 @@ static const TypeInfo virtio_tablet_info = { .parent = TYPE_VIRTIO_INPUT_HID, .instance_size = sizeof(VirtIOInputHID), .instance_init = virtio_tablet_init, - .class_init = virtio_tablet_class_init, }; /* ----------------------------------------------------------------- */ diff --git a/hw/intc/openpic_kvm.c b/hw/intc/openpic_kvm.c index fbf0bdbe0716..b099da20eb97 100644 --- a/hw/intc/openpic_kvm.c +++ b/hw/intc/openpic_kvm.c @@ -49,6 +49,7 @@ struct KVMOpenPICState { uint32_t fd; uint32_t model; hwaddr mapped; + NotifierWithReturn vmfd_change_notifier; }; static void kvm_openpic_set_irq(void *opaque, int n_IRQ, int level) @@ -114,6 +115,88 @@ static const MemoryRegionOps kvm_openpic_mem_ops = { }, }; +static int kvm_openpic_setup(KVMOpenPICState *opp, Error **errp) +{ + int kvm_openpic_model; + struct kvm_create_device cd = {0}; + KVMState *s = kvm_state; + int ret; + + switch (opp->model) { + case OPENPIC_MODEL_FSL_MPIC_20: + kvm_openpic_model = KVM_DEV_TYPE_FSL_MPIC_20; + break; + + case OPENPIC_MODEL_FSL_MPIC_42: + kvm_openpic_model = KVM_DEV_TYPE_FSL_MPIC_42; + break; + + default: + error_setg(errp, "Unsupported OpenPIC model %" PRIu32, opp->model); + return -1; + } + + cd.type = kvm_openpic_model; + ret = kvm_vm_ioctl(s, KVM_CREATE_DEVICE, &cd); + if (ret < 0) { + error_setg(errp, "Can't create device %d: %s", + cd.type, strerror(errno)); + return -1; + } + opp->fd = cd.fd; + + return 0; +} + +static int kvm_openpic_handle_vmfd_change(NotifierWithReturn *notifier, + void *data, Error **errp) +{ + KVMOpenPICState *opp = container_of(notifier, KVMOpenPICState, + vmfd_change_notifier); + uint64_t reg_base; + struct kvm_device_attr attr; + CPUState *cs; + int ret; + + /* we are not interested in pre vmfd change notification */ + if (((VmfdChangeNotifier *)data)->pre) { + return 0; + } + + /* close the old descriptor */ + close(opp->fd); + + if (kvm_openpic_setup(opp, errp) < 0) { + return -1; + } + + if (!opp->mapped) { + return 0; + } + + reg_base = opp->mapped; + attr.group = KVM_DEV_MPIC_GRP_MISC; + attr.attr = KVM_DEV_MPIC_BASE_ADDR; + attr.addr = (uint64_t)(unsigned long)®_base; + + ret = ioctl(opp->fd, KVM_SET_DEVICE_ATTR, &attr); + if (ret < 0) { + error_setg(errp, "%s: %s %" PRIx64, __func__, + strerror(errno), reg_base); + return -1; + } + + CPU_FOREACH(cs) { + ret = kvm_vcpu_enable_cap(cs, KVM_CAP_IRQ_MPIC, 0, opp->fd, + kvm_arch_vcpu_id(cs)); + if (ret < 0) { + return ret; + } + } + + return 0; +} + static void kvm_openpic_region_add(MemoryListener *listener, MemoryRegionSection *section) { @@ -197,36 +280,14 @@ static void kvm_openpic_realize(DeviceState *dev, Error **errp) SysBusDevice *d = SYS_BUS_DEVICE(dev); KVMOpenPICState *opp = KVM_OPENPIC(dev); KVMState *s = kvm_state; - int kvm_openpic_model; - struct kvm_create_device cd = {0}; - int ret, i; + int i; if (!kvm_check_extension(s, KVM_CAP_DEVICE_CTRL)) { error_setg(errp, "Kernel is lacking Device Control API"); return; } - switch (opp->model) { - case OPENPIC_MODEL_FSL_MPIC_20: - kvm_openpic_model = KVM_DEV_TYPE_FSL_MPIC_20; - break; - - case OPENPIC_MODEL_FSL_MPIC_42: - kvm_openpic_model = KVM_DEV_TYPE_FSL_MPIC_42; - break; - - default: - error_setg(errp, "Unsupported OpenPIC model %" PRIu32, opp->model); - return; - } - - cd.type = kvm_openpic_model; - ret = kvm_vm_ioctl(s, KVM_CREATE_DEVICE, &cd); - if (ret < 0) { - error_setg_errno(errp, errno, "Can't create device %d", cd.type); - return; - } - opp->fd = cd.fd; + kvm_openpic_setup(opp, errp); sysbus_init_mmio(d, &opp->mem); qdev_init_gpio_in(dev, kvm_openpic_set_irq, OPENPIC_MAX_IRQ); @@ -235,6 +296,9 @@ static void kvm_openpic_realize(DeviceState *dev, Error **errp) opp->mem_listener.region_del = kvm_openpic_region_del; opp->mem_listener.name = "openpic-kvm"; memory_listener_register(&opp->mem_listener, &address_space_memory); + opp->vmfd_change_notifier.notify = + kvm_openpic_handle_vmfd_change; + kvm_vmfd_add_change_notifier(&opp->vmfd_change_notifier); /* indicate pic capabilities */ msi_nonbroken = true; diff --git a/hw/loongarch/boot.c b/hw/loongarch/boot.c index 711d5ffbbc04..ef8eae237cb9 100644 --- a/hw/loongarch/boot.c +++ b/hw/loongarch/boot.c @@ -217,10 +217,12 @@ static void init_cmdline(struct loongarch_boot_info *info, void *p, void *start) static uint64_t cpu_loongarch_virt_to_phys(void *opaque, uint64_t addr) { - return addr & MAKE_64BIT_MASK(0, TARGET_PHYS_ADDR_SPACE_BITS); + uint64_t *phys_addr_mask = opaque; + return addr & *phys_addr_mask; } static int64_t load_loongarch_linux_image(const char *filename, + uint64_t phys_addr_mask, uint64_t *kernel_entry, uint64_t *kernel_low, uint64_t *kernel_high) @@ -251,10 +253,8 @@ static int64_t load_loongarch_linux_image(const char *filename, } /* Early kernel versions may have those fields in virtual address */ - *kernel_entry = extract64(le64_to_cpu(hdr->kernel_entry), - 0, TARGET_PHYS_ADDR_SPACE_BITS); - *kernel_low = extract64(le64_to_cpu(hdr->load_offset), - 0, TARGET_PHYS_ADDR_SPACE_BITS); + *kernel_entry = le64_to_cpu(hdr->kernel_entry) & phys_addr_mask; + *kernel_low = le64_to_cpu(hdr->load_offset) & phys_addr_mask; *kernel_high = *kernel_low + size; rom_add_blob_fixed(filename, buffer, size, *kernel_low); @@ -303,19 +303,21 @@ static ram_addr_t alloc_initrd_memory(struct loongarch_boot_info *info, exit(1); } -static int64_t load_kernel_info(struct loongarch_boot_info *info) +static int64_t load_kernel_info(struct loongarch_boot_info *info, + uint64_t phys_addr_mask) { uint64_t kernel_entry, kernel_low, kernel_high, initrd_offset = 0; ssize_t kernel_size; kernel_size = load_elf(info->kernel_filename, NULL, - cpu_loongarch_virt_to_phys, NULL, + cpu_loongarch_virt_to_phys, &phys_addr_mask, &kernel_entry, &kernel_low, &kernel_high, NULL, ELFDATA2LSB, EM_LOONGARCH, 1, 0); - kernel_entry = cpu_loongarch_virt_to_phys(NULL, kernel_entry); + kernel_entry = cpu_loongarch_virt_to_phys(&phys_addr_mask, kernel_entry); if (kernel_size < 0) { kernel_size = load_loongarch_linux_image(info->kernel_filename, + phys_addr_mask, &kernel_entry, &kernel_low, &kernel_high); } @@ -395,14 +397,15 @@ static void init_boot_rom(MachineState *ms, } static void loongarch_direct_kernel_boot(MachineState *ms, - struct loongarch_boot_info *info) + struct loongarch_boot_info *info, + uint64_t phys_addr_mask) { void *p, *bp; int64_t kernel_addr = VIRT_FLASH0_BASE; uint64_t *data; if (info->kernel_filename) { - kernel_addr = load_kernel_info(info); + kernel_addr = load_kernel_info(info, phys_addr_mask); } else { if (!qtest_enabled()) { warn_report("No kernel provided, booting from flash drive."); @@ -429,7 +432,8 @@ static void loongarch_direct_kernel_boot(MachineState *ms, g_free(bp); } -void loongarch_load_kernel(MachineState *ms, struct loongarch_boot_info *info) +void loongarch_load_kernel(MachineState *ms, struct loongarch_boot_info *info, + uint64_t phys_addr_mask) { LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(ms); @@ -440,6 +444,6 @@ void loongarch_load_kernel(MachineState *ms, struct loongarch_boot_info *info) if (lvms->bios_loaded) { loongarch_firmware_boot(lvms, info); } else { - loongarch_direct_kernel_boot(ms, info); + loongarch_direct_kernel_boot(ms, info, phys_addr_mask); } } diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c index a75968e4c959..2fc15261309e 100644 --- a/hw/loongarch/virt.c +++ b/hw/loongarch/virt.c @@ -34,6 +34,7 @@ #include "hw/misc/unimp.h" #include "hw/loongarch/fw_cfg.h" #include "target/loongarch/cpu.h" +#include "target/loongarch/cpu-mmu.h" #include "hw/firmware/smbios.h" #include "qapi/qapi-visit-common.h" #include "hw/acpi/generic_event_device.h" @@ -776,7 +777,9 @@ static MemTxResult virt_iocsr_misc_read(void *opaque, hwaddr addr, LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(opaque); uint64_t ret = 0; int features; + CPULoongArchState *env; + env = &LOONGARCH_CPU(first_cpu)->env; switch (addr) { case VERSION_REG: ret = 0x11ULL; @@ -791,10 +794,10 @@ static MemTxResult virt_iocsr_misc_read(void *opaque, hwaddr addr, } break; case VENDOR_REG: - ret = 0x6e6f73676e6f6f4cULL; /* "Loongson" */ + ret = env->vendor_id; break; case CPUNAME_REG: - ret = 0x303030354133ULL; /* "3A5000" */ + ret = env->cpu_id; break; case MISC_FUNC_REG: if (kvm_irqchip_in_kernel()) { @@ -928,6 +931,7 @@ static void virt_init(MachineState *machine) hwaddr base, size, ram_size = machine->ram_size; MachineClass *mc = MACHINE_GET_CLASS(machine); Object *cpuobj; + uint64_t phys_addr_mask = 0; if (!cpu_model) { cpu_model = LOONGARCH_CPU_TYPE_NAME("la464"); @@ -1017,7 +1021,8 @@ static void virt_init(MachineState *machine) qemu_register_powerdown_notifier(&lvms->powerdown_notifier); lvms->bootinfo.ram_size = ram_size; - loongarch_load_kernel(machine, &lvms->bootinfo); + phys_addr_mask = loongarch_palen_mask(&LOONGARCH_CPU(first_cpu)->env); + loongarch_load_kernel(machine, &lvms->bootinfo, phys_addr_mask); } static void virt_get_acpi(Object *obj, Visitor *v, const char *name, diff --git a/hw/m68k/mcf5208.c b/hw/m68k/mcf5208.c index c6d1c5fae9fa..0e07aa45e9d7 100644 --- a/hw/m68k/mcf5208.c +++ b/hw/m68k/mcf5208.c @@ -227,7 +227,7 @@ static const MemoryRegionOps m5208_rcm_ops = { .endianness = DEVICE_BIG_ENDIAN, }; -static void mcf5208_sys_init(MemoryRegion *address_space, qemu_irq *pic, +static void mcf5208_sys_init(MemoryRegion *address_space, DeviceState *intc, M68kCPU *cpu) { MemoryRegion *iomem = g_new(MemoryRegion, 1); @@ -250,11 +250,11 @@ static void mcf5208_sys_init(MemoryRegion *address_space, qemu_irq *pic, "m5208-timer", 0x00004000); memory_region_add_subregion(address_space, 0xfc080000 + 0x4000 * i, &s->iomem); - s->irq = pic[4 + i]; + s->irq = qdev_get_gpio_in(intc, 4 + i); } } -static void mcf_fec_init(MemoryRegion *sysmem, hwaddr base, qemu_irq *irqs) +static void mcf_fec_init(MemoryRegion *sysmem, hwaddr base, DeviceState *intc) { DeviceState *dev; SysBusDevice *s; @@ -268,7 +268,7 @@ static void mcf_fec_init(MemoryRegion *sysmem, hwaddr base, qemu_irq *irqs) s = SYS_BUS_DEVICE(dev); sysbus_realize_and_unref(s, &error_fatal); for (i = 0; i < FEC_NUM_IRQ; i++) { - sysbus_connect_irq(s, i, irqs[i]); + sysbus_connect_irq(s, i, qdev_get_gpio_in(intc, i + 36)); } memory_region_add_subregion(sysmem, base, sysbus_mmio_get_region(s, 0)); @@ -283,10 +283,10 @@ static void mcf5208evb_init(MachineState *machine) int kernel_size; uint64_t elf_entry; hwaddr entry; - qemu_irq *pic; MemoryRegion *address_space_mem = get_system_memory(); MemoryRegion *rom = g_new(MemoryRegion, 1); MemoryRegion *sram = g_new(MemoryRegion, 1); + DeviceState *intc; cpu = M68K_CPU(cpu_create(machine->cpu_type)); env = &cpu->env; @@ -307,17 +307,15 @@ static void mcf5208evb_init(MachineState *machine) memory_region_add_subregion(address_space_mem, 0x80000000, sram); /* Internal peripherals. */ - pic = mcf_intc_init(address_space_mem, 0xfc048000, cpu); + intc = mcf_intc_init(address_space_mem, 0xfc048000, cpu); - mcf_uart_create_mmap(0xfc060000, pic[26], serial_hd(0)); - mcf_uart_create_mmap(0xfc064000, pic[27], serial_hd(1)); - mcf_uart_create_mmap(0xfc068000, pic[28], serial_hd(2)); + mcf_uart_create_mmap(0xfc060000, qdev_get_gpio_in(intc, 26), serial_hd(0)); + mcf_uart_create_mmap(0xfc064000, qdev_get_gpio_in(intc, 27), serial_hd(1)); + mcf_uart_create_mmap(0xfc068000, qdev_get_gpio_in(intc, 28), serial_hd(2)); - mcf5208_sys_init(address_space_mem, pic, cpu); + mcf5208_sys_init(address_space_mem, intc, cpu); - mcf_fec_init(address_space_mem, 0xfc030000, pic + 36); - - g_free(pic); + mcf_fec_init(address_space_mem, 0xfc030000, intc); /* 0xfc000000 SCM. */ /* 0xfc004000 XBS. */ diff --git a/hw/m68k/mcf_intc.c b/hw/m68k/mcf_intc.c index 20112c94be19..1014fe6fa578 100644 --- a/hw/m68k/mcf_intc.c +++ b/hw/m68k/mcf_intc.c @@ -175,6 +175,7 @@ static void mcf_intc_instance_init(Object *obj) memory_region_init_io(&s->iomem, obj, &mcf_intc_ops, s, "mcf", 0x100); sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem); + qdev_init_gpio_in(DEVICE(s), mcf_intc_set_irq, 64); } static const Property mcf_intc_properties[] = { @@ -206,9 +207,7 @@ static void mcf_intc_register_types(void) type_init(mcf_intc_register_types) -qemu_irq *mcf_intc_init(MemoryRegion *sysmem, - hwaddr base, - M68kCPU *cpu) +DeviceState *mcf_intc_init(MemoryRegion *sysmem, hwaddr base, M68kCPU *cpu) { DeviceState *dev; @@ -218,6 +217,5 @@ qemu_irq *mcf_intc_init(MemoryRegion *sysmem, sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); memory_region_add_subregion(sysmem, base, sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0)); - - return qemu_allocate_irqs(mcf_intc_set_irq, dev, 64); + return dev; } diff --git a/hw/m68k/q800.c b/hw/m68k/q800.c index 25ddddb5d9fe..ded531394e68 100644 --- a/hw/m68k/q800.c +++ b/hw/m68k/q800.c @@ -255,7 +255,7 @@ static void q800_machine_init(MachineState *machine) int32_t initrd_size; uint8_t *prom; int i, checksum; - MacFbMode *macfb_mode; + const MacFbMode *macfb_mode; ram_addr_t ram_size = machine->ram_size; const char *kernel_filename = machine->kernel_filename; const char *initrd_filename = machine->initrd_filename; @@ -560,14 +560,9 @@ static void q800_machine_init(MachineState *machine) TYPE_NUBUS_MACFB); dev = DEVICE(&m->macfb); qdev_prop_set_uint32(dev, "slot", 9); - qdev_prop_set_uint32(dev, "width", graphic_width); - qdev_prop_set_uint32(dev, "height", graphic_height); - qdev_prop_set_uint8(dev, "depth", graphic_depth); - if (graphic_width == 1152 && graphic_height == 870) { - qdev_prop_set_uint8(dev, "display", MACFB_DISPLAY_APPLE_21_COLOR); - } else { - qdev_prop_set_uint8(dev, "display", MACFB_DISPLAY_VGA); - } + qdev_prop_set_uint32(dev, "width", graphic_width ?: 800); + qdev_prop_set_uint32(dev, "height", graphic_height ?: 600); + qdev_prop_set_uint8(dev, "depth", graphic_depth ?: 8); qdev_realize(dev, BUS(nubus), &error_fatal); macfb_mode = (NUBUS_MACFB(dev)->macfb).mode; @@ -605,9 +600,9 @@ static void q800_machine_init(MachineState *machine) BOOTINFO2(param_ptr, BI_MEMCHUNK, 0, ram_size); BOOTINFO1(param_ptr, BI_MAC_VADDR, VIDEO_BASE + macfb_mode->offset); - BOOTINFO1(param_ptr, BI_MAC_VDEPTH, graphic_depth); + BOOTINFO1(param_ptr, BI_MAC_VDEPTH, macfb_mode->depth); BOOTINFO1(param_ptr, BI_MAC_VDIM, - (graphic_height << 16) | graphic_width); + (graphic_height << 16) | macfb_mode->width); BOOTINFO1(param_ptr, BI_MAC_VROW, macfb_mode->stride); BOOTINFO1(param_ptr, BI_MAC_SCCBASE, SCC_BASE); diff --git a/hw/mem/cxl_type3.c b/hw/mem/cxl_type3.c index 6eb20137a04f..4739239da3c5 100644 --- a/hw/mem/cxl_type3.c +++ b/hw/mem/cxl_type3.c @@ -405,7 +405,7 @@ static void build_dvsecs(CXLType3Dev *ct3d) dvsec = (uint8_t *)&(CXLDVSECPortFlexBus){ .cap = 0x26, /* 68B, IO, Mem, non-MLD */ .ctrl = 0x02, /* IO always enabled */ - .status = 0x26, /* same as capabilities */ + .status = ct3d->flitmode ? 0x6 : 0x26, /* lack of 68B */ .rcvd_mod_ts_data_phase1 = 0xef, /* WTF? */ }; cxl_component_create_dvsec(cxl_cstate, CXL2_TYPE3_DEVICE, @@ -748,6 +748,11 @@ static bool cxl_setup_memory(CXLType3Dev *ct3d, Error **errp) return false; } + if (!ct3d->flitmode && ct3d->hdmdb) { + error_setg(errp, "hdm-db requires operating in 256b flit"); + return false; + } + if (ct3d->hostvmem) { MemoryRegion *vmr; char *v_name; @@ -964,6 +969,76 @@ static void ct3_realize(PCIDevice *pci_dev, Error **errp) ct3d->ecs_attrs.fru_attrs[count].ecs_flags = 0; } + /* Set default values for soft-PPR attributes */ + ct3d->soft_ppr_attrs = (CXLMemSoftPPRReadAttrs) { + .max_maint_latency = 0x5, /* 100 ms */ + .op_caps = 0, /* require host involvement */ + .op_mode = 0, + .maint_op_class = CXL_MEMDEV_MAINT_CLASS_PPR, + .maint_op_subclass = CXL_MEMDEV_MAINT_SUBCLASS_SPPR, + .sppr_flags = CXL_MEMDEV_SPPR_DPA_SUPPORT_FLAG | + CXL_MEMDEV_SPPR_MEM_SPARING_EV_REC_CAP_FLAG, + .restriction_flags = 0, + .sppr_op_mode = CXL_MEMDEV_SPPR_OP_MODE_MEM_SPARING_EV_REC_EN + }; + + /* Set default value for hard-PPR attributes */ + ct3d->hard_ppr_attrs = (CXLMemHardPPRReadAttrs) { + .max_maint_latency = 0x5, /* 100 ms */ + .op_caps = 0, /* require host involvement */ + .op_mode = 0, + .maint_op_class = CXL_MEMDEV_MAINT_CLASS_PPR, + .maint_op_subclass = CXL_MEMDEV_MAINT_SUBCLASS_HPPR, + .hppr_flags = CXL_MEMDEV_HPPR_DPA_SUPPORT_FLAG | + CXL_MEMDEV_HPPR_MEM_SPARING_EV_REC_CAP_FLAG, + .restriction_flags = 0, + .hppr_op_mode = CXL_MEMDEV_HPPR_OP_MODE_MEM_SPARING_EV_REC_EN + }; + + /* Set default value for Cacheline Memory Sparing attributes */ + ct3d->cacheline_sparing_attrs = (CXLMemSparingReadAttrs) { + .max_maint_latency = 0x5, /* 100 ms */ + .op_caps = 0, /* require host involvement */ + .op_mode = 0, + .maint_op_class = CXL_MEMDEV_MAINT_CLASS_SPARING, + .maint_op_subclass = CXL_MEMDEV_MAINT_SUBCLASS_CACHELINE_SPARING, + .restriction_flags = CXL_MEMDEV_HARD_SPARING_SUPPORT_FLAG | + CXL_MEMDEV_SOFT_SPARING_SUPPORT_FLAG, + }; + + /* Set default value for Row Memory Sparing attributes */ + ct3d->row_sparing_attrs = (CXLMemSparingReadAttrs) { + .max_maint_latency = 0x5, /* 100 ms */ + .op_caps = 0, /* require host involvement */ + .op_mode = 0, + .maint_op_class = CXL_MEMDEV_MAINT_CLASS_SPARING, + .maint_op_subclass = CXL_MEMDEV_MAINT_SUBCLASS_ROW_SPARING, + .restriction_flags = CXL_MEMDEV_HARD_SPARING_SUPPORT_FLAG | + CXL_MEMDEV_SOFT_SPARING_SUPPORT_FLAG, + }; + + /* Set default value for Bank Memory Sparing attributes */ + ct3d->bank_sparing_attrs = (CXLMemSparingReadAttrs) { + .max_maint_latency = 0x5, /* 100 ms */ + .op_caps = 0, /* require host involvement */ + .op_mode = 0, + .maint_op_class = CXL_MEMDEV_MAINT_CLASS_SPARING, + .maint_op_subclass = CXL_MEMDEV_MAINT_SUBCLASS_BANK_SPARING, + .restriction_flags = CXL_MEMDEV_HARD_SPARING_SUPPORT_FLAG | + CXL_MEMDEV_SOFT_SPARING_SUPPORT_FLAG, + }; + + /* Set default value for Rank Memory Sparing attributes */ + ct3d->rank_sparing_attrs = (CXLMemSparingReadAttrs) { + .max_maint_latency = 0x5, /* 100 ms */ + .op_caps = 0, /* require host involvement */ + .op_mode = 0, + .maint_op_class = CXL_MEMDEV_MAINT_CLASS_SPARING, + .maint_op_subclass = CXL_MEMDEV_MAINT_SUBCLASS_RANK_SPARING, + .restriction_flags = CXL_MEMDEV_HARD_SPARING_SUPPORT_FLAG | + CXL_MEMDEV_SOFT_SPARING_SUPPORT_FLAG, + }; + return; err_release_cdat: @@ -1245,8 +1320,10 @@ static void ct3d_reset(DeviceState *dev) uint32_t *reg_state = ct3d->cxl_cstate.crb.cache_mem_registers; uint32_t *write_msk = ct3d->cxl_cstate.crb.cache_mem_regs_write_mask; - pcie_cap_fill_link_ep_usp(PCI_DEVICE(dev), ct3d->width, ct3d->speed); - cxl_component_register_init_common(reg_state, write_msk, CXL2_TYPE3_DEVICE); + pcie_cap_fill_link_ep_usp(PCI_DEVICE(dev), ct3d->width, ct3d->speed, + ct3d->flitmode); + cxl_component_register_init_common(reg_state, write_msk, + CXL2_TYPE3_DEVICE, ct3d->hdmdb); cxl_device_register_init_t3(ct3d, CXL_T3_MSIX_MBOX); /* @@ -1284,6 +1361,8 @@ static const Property ct3_props[] = { speed, PCIE_LINK_SPEED_32), DEFINE_PROP_PCIE_LINK_WIDTH("x-width", CXLType3Dev, width, PCIE_LINK_WIDTH_16), + DEFINE_PROP_BOOL("x-256b-flit", CXLType3Dev, flitmode, false), + DEFINE_PROP_BOOL("hdm-db", CXLType3Dev, hdmdb, false), }; static uint64_t get_lsa_size(CXLType3Dev *ct3d) @@ -1592,12 +1671,39 @@ void qmp_cxl_inject_correctable_error(const char *path, CxlCorErrorType type, void cxl_assign_event_header(CXLEventRecordHdr *hdr, const QemuUUID *uuid, uint32_t flags, - uint8_t length, uint64_t timestamp) + uint8_t length, uint64_t timestamp, + bool has_maint_op_class, uint8_t maint_op_class, + bool has_maint_op_subclass, + uint8_t maint_op_subclass, + bool has_ld_id, uint16_t ld_id, + bool has_head_id, uint8_t head_id) { - st24_le_p(&hdr->flags, flags); hdr->length = length; memcpy(&hdr->id, uuid, sizeof(hdr->id)); stq_le_p(&hdr->timestamp, timestamp); + + if (has_maint_op_class) { + hdr->maint_op_class = maint_op_class; + } else { + hdr->maint_op_class = 0; + } + + if (has_maint_op_subclass) { + flags |= CXL_EVENT_REC_FLAGS_MAINT_OP_SUBCLASS_VALID; + hdr->maint_op_subclass = maint_op_subclass; + } + + if (has_ld_id) { + flags |= CXL_EVENT_REC_FLAGS_LD_ID_VALID; + stw_le_p(&hdr->ld_id, ld_id); + } + + if (has_head_id) { + flags |= CXL_EVENT_REC_FLAGS_HEAD_ID_VALID; + hdr->head_id = head_id; + } + + st24_le_p(&hdr->flags, flags); } static const QemuUUID gen_media_uuid = { @@ -1619,6 +1725,11 @@ static const QemuUUID memory_module_uuid = { #define CXL_GMER_VALID_RANK BIT(1) #define CXL_GMER_VALID_DEVICE BIT(2) #define CXL_GMER_VALID_COMPONENT BIT(3) +#define CXL_GMER_VALID_COMPONENT_ID_FORMAT BIT(4) + +#define CXL_GMER_EV_DESC_UCE BIT(0) +#define CXL_GMER_EV_DESC_THRESHOLD_EVENT BIT(1) +#define CXL_GMER_EV_DESC_POISON_LIST_OVERFLOW_EVENT BIT(2) static int ct3d_qmp_cxl_event_log_enc(CxlEventLog log) { @@ -1635,15 +1746,96 @@ static int ct3d_qmp_cxl_event_log_enc(CxlEventLog log) return -EINVAL; } } + +static void cxl_maintenance_insert(CXLType3Dev *ct3d, uint64_t dpa, + bool has_channel, uint8_t channel, + bool has_rank, uint8_t rank, + bool has_nibble_mask, uint32_t nibble_mask, + bool has_bank_group, uint8_t bank_group, + bool has_bank, uint8_t bank, + bool has_row, uint32_t row, + bool has_column, uint16_t column, + const char *component_id, + bool has_comp_id_pldm, bool is_comp_id_pldm, + bool has_sub_channel, uint8_t sub_channel) +{ + CXLMaintenance *ent, *m; + + QLIST_FOREACH(ent, &ct3d->maint_list, node) { + if (dpa == ent->dpa) { + return; + } + } + m = g_new0(CXLMaintenance, 1); + memset(m, 0, sizeof(*m)); + m->dpa = dpa; + m->validity_flags = 0; + + if (has_channel) { + m->channel = channel; + m->validity_flags |= CXL_MSER_VALID_CHANNEL; + } + if (has_rank) { + m->rank = rank; + m->validity_flags |= CXL_MSER_VALID_RANK; + } + if (has_nibble_mask) { + m->nibble_mask = nibble_mask; + m->validity_flags |= CXL_MSER_VALID_NIB_MASK; + } + if (has_bank_group) { + m->bank_group = bank_group; + m->validity_flags |= CXL_MSER_VALID_BANK_GROUP; + } + if (has_bank) { + m->bank = bank; + m->validity_flags |= CXL_MSER_VALID_BANK; + } + if (has_row) { + m->row = row; + m->validity_flags |= CXL_MSER_VALID_ROW; + } + if (has_column) { + m->column = column; + m->validity_flags |= CXL_MSER_VALID_COLUMN; + } + if (has_sub_channel) { + m->sub_channel = sub_channel; + m->validity_flags |= CXL_MSER_VALID_SUB_CHANNEL; + } + if (component_id) { + strncpy((char *)m->component_id, component_id, + sizeof(m->component_id) - 1); + m->validity_flags |= CXL_MSER_VALID_COMP_ID; + if (has_comp_id_pldm && is_comp_id_pldm) { + m->validity_flags |= CXL_MSER_VALID_COMP_ID_FORMAT; + } + } + + QLIST_INSERT_HEAD(&ct3d->maint_list, m, node); +} + /* Component ID is device specific. Define this as a string. */ void qmp_cxl_inject_general_media_event(const char *path, CxlEventLog log, - uint8_t flags, uint64_t dpa, + uint32_t flags, bool has_maint_op_class, + uint8_t maint_op_class, + bool has_maint_op_subclass, + uint8_t maint_op_subclass, + bool has_ld_id, uint16_t ld_id, + bool has_head_id, uint8_t head_id, + uint64_t dpa, uint8_t descriptor, uint8_t type, uint8_t transaction_type, bool has_channel, uint8_t channel, bool has_rank, uint8_t rank, bool has_device, uint32_t device, const char *component_id, + bool has_comp_id_pldm, + bool is_comp_id_pldm, + bool has_cme_ev_flags, + uint8_t cme_ev_flags, + bool has_cme_count, uint32_t cme_count, + uint8_t sub_type, Error **errp) { Object *obj = object_resolve_path(path, NULL); @@ -1671,14 +1863,21 @@ void qmp_cxl_inject_general_media_event(const char *path, CxlEventLog log, error_setg(errp, "Unhandled error log type"); return; } + if (rc == CXL_EVENT_TYPE_INFO && + (flags & CXL_EVENT_REC_FLAGS_MAINT_NEEDED)) { + error_setg(errp, "Informational event cannot require maintenance"); + return; + } enc_log = rc; memset(&gem, 0, sizeof(gem)); cxl_assign_event_header(hdr, &gen_media_uuid, flags, sizeof(gem), - cxl_device_get_timestamp(&ct3d->cxl_dstate)); + cxl_device_get_timestamp(&ct3d->cxl_dstate), + has_maint_op_class, maint_op_class, + has_maint_op_subclass, maint_op_subclass, + has_ld_id, ld_id, has_head_id, head_id); stq_le_p(&gem.phys_addr, dpa); - gem.descriptor = descriptor; gem.type = type; gem.transaction_type = transaction_type; @@ -1701,13 +1900,41 @@ void qmp_cxl_inject_general_media_event(const char *path, CxlEventLog log, strncpy((char *)gem.component_id, component_id, sizeof(gem.component_id) - 1); valid_flags |= CXL_GMER_VALID_COMPONENT; + if (has_comp_id_pldm && is_comp_id_pldm) { + valid_flags |= CXL_GMER_VALID_COMPONENT_ID_FORMAT; + } } stw_le_p(&gem.validity_flags, valid_flags); + if (has_cme_ev_flags) { + gem.cme_ev_flags = cme_ev_flags; + } else { + gem.cme_ev_flags = 0; + } + + if (has_cme_count) { + descriptor |= CXL_GMER_EV_DESC_THRESHOLD_EVENT; + st24_le_p(gem.cme_count, cme_count); + } else { + st24_le_p(gem.cme_count, 0); + } + gem.descriptor = descriptor; + + gem.sub_type = sub_type; + if (cxl_event_insert(cxlds, enc_log, (CXLEventRecordRaw *)&gem)) { cxl_event_irq_assert(ct3d); } + + if (flags & CXL_EVENT_REC_FLAGS_MAINT_NEEDED) { + cxl_maintenance_insert(ct3d, dpa, has_channel, channel, + has_rank, rank, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, component_id, + has_comp_id_pldm, is_comp_id_pldm, + 0, 0); + } } #define CXL_DRAM_VALID_CHANNEL BIT(0) @@ -1718,8 +1945,21 @@ void qmp_cxl_inject_general_media_event(const char *path, CxlEventLog log, #define CXL_DRAM_VALID_ROW BIT(5) #define CXL_DRAM_VALID_COLUMN BIT(6) #define CXL_DRAM_VALID_CORRECTION_MASK BIT(7) - -void qmp_cxl_inject_dram_event(const char *path, CxlEventLog log, uint8_t flags, +#define CXL_DRAM_VALID_COMPONENT BIT(8) +#define CXL_DRAM_VALID_COMPONENT_ID_FORMAT BIT(9) +#define CXL_DRAM_VALID_SUB_CHANNEL BIT(10) + +#define CXL_DRAM_EV_DESC_UCE BIT(0) +#define CXL_DRAM_EV_DESC_THRESHOLD_EVENT BIT(1) +#define CXL_DRAM_EV_DESC_POISON_LIST_OVERFLOW_EVENT BIT(2) + +void qmp_cxl_inject_dram_event(const char *path, CxlEventLog log, + uint32_t flags, + bool has_maint_op_class, uint8_t maint_op_class, + bool has_maint_op_subclass, + uint8_t maint_op_subclass, + bool has_ld_id, uint16_t ld_id, + bool has_head_id, uint8_t head_id, uint64_t dpa, uint8_t descriptor, uint8_t type, uint8_t transaction_type, bool has_channel, uint8_t channel, @@ -1731,6 +1971,12 @@ void qmp_cxl_inject_dram_event(const char *path, CxlEventLog log, uint8_t flags, bool has_column, uint16_t column, bool has_correction_mask, uint64List *correction_mask, + const char *component_id, + bool has_comp_id_pldm, bool is_comp_id_pldm, + bool has_sub_channel, uint8_t sub_channel, + bool has_cme_ev_flags, uint8_t cme_ev_flags, + bool has_cvme_count, uint32_t cvme_count, + uint8_t sub_type, Error **errp) { Object *obj = object_resolve_path(path, NULL); @@ -1758,13 +2004,20 @@ void qmp_cxl_inject_dram_event(const char *path, CxlEventLog log, uint8_t flags, error_setg(errp, "Unhandled error log type"); return; } + if (rc == CXL_EVENT_TYPE_INFO && + (flags & CXL_EVENT_REC_FLAGS_MAINT_NEEDED)) { + error_setg(errp, "Informational event cannot require maintenance"); + return; + } enc_log = rc; memset(&dram, 0, sizeof(dram)); cxl_assign_event_header(hdr, &dram_uuid, flags, sizeof(dram), - cxl_device_get_timestamp(&ct3d->cxl_dstate)); + cxl_device_get_timestamp(&ct3d->cxl_dstate), + has_maint_op_class, maint_op_class, + has_maint_op_subclass, maint_op_subclass, + has_ld_id, ld_id, has_head_id, head_id); stq_le_p(&dram.phys_addr, dpa); - dram.descriptor = descriptor; dram.type = type; dram.transaction_type = transaction_type; @@ -1814,15 +2067,65 @@ void qmp_cxl_inject_dram_event(const char *path, CxlEventLog log, uint8_t flags, valid_flags |= CXL_DRAM_VALID_CORRECTION_MASK; } + if (component_id) { + strncpy((char *)dram.component_id, component_id, + sizeof(dram.component_id) - 1); + valid_flags |= CXL_DRAM_VALID_COMPONENT; + if (has_comp_id_pldm && is_comp_id_pldm) { + valid_flags |= CXL_DRAM_VALID_COMPONENT_ID_FORMAT; + } + } + + if (has_sub_channel) { + dram.sub_channel = sub_channel; + valid_flags |= CXL_DRAM_VALID_SUB_CHANNEL; + } + + if (has_cme_ev_flags) { + dram.cme_ev_flags = cme_ev_flags; + } else { + dram.cme_ev_flags = 0; + } + + if (has_cvme_count) { + descriptor |= CXL_DRAM_EV_DESC_THRESHOLD_EVENT; + st24_le_p(dram.cvme_count, cvme_count); + } else { + st24_le_p(dram.cvme_count, 0); + } + dram.descriptor = descriptor; + + dram.sub_type = sub_type; + stw_le_p(&dram.validity_flags, valid_flags); if (cxl_event_insert(cxlds, enc_log, (CXLEventRecordRaw *)&dram)) { cxl_event_irq_assert(ct3d); } + + if (flags & CXL_EVENT_REC_FLAGS_MAINT_NEEDED) { + cxl_maintenance_insert(ct3d, dpa, has_channel, channel, + has_rank, rank, + has_nibble_mask, nibble_mask, + has_bank_group, bank_group, + has_bank, bank, has_row, row, + has_column, column, component_id, + has_comp_id_pldm, is_comp_id_pldm, + has_sub_channel, sub_channel); + } } +#define CXL_MMER_VALID_COMPONENT BIT(0) +#define CXL_MMER_VALID_COMPONENT_ID_FORMAT BIT(1) + void qmp_cxl_inject_memory_module_event(const char *path, CxlEventLog log, - uint8_t flags, uint8_t type, + uint32_t flags, bool has_maint_op_class, + uint8_t maint_op_class, + bool has_maint_op_subclass, + uint8_t maint_op_subclass, + bool has_ld_id, uint16_t ld_id, + bool has_head_id, uint8_t head_id, + uint8_t type, uint8_t health_status, uint8_t media_status, uint8_t additional_status, @@ -1831,11 +2134,16 @@ void qmp_cxl_inject_memory_module_event(const char *path, CxlEventLog log, uint32_t dirty_shutdown_count, uint32_t corrected_volatile_error_count, uint32_t corrected_persist_error_count, + const char *component_id, + bool has_comp_id_pldm, + bool is_comp_id_pldm, + uint8_t sub_type, Error **errp) { Object *obj = object_resolve_path(path, NULL); CXLEventMemoryModule module; CXLEventRecordHdr *hdr = &module.hdr; + uint16_t valid_flags = 0; CXLDeviceState *cxlds; CXLType3Dev *ct3d; uint8_t enc_log; @@ -1861,7 +2169,10 @@ void qmp_cxl_inject_memory_module_event(const char *path, CxlEventLog log, memset(&module, 0, sizeof(module)); cxl_assign_event_header(hdr, &memory_module_uuid, flags, sizeof(module), - cxl_device_get_timestamp(&ct3d->cxl_dstate)); + cxl_device_get_timestamp(&ct3d->cxl_dstate), + has_maint_op_class, maint_op_class, + has_maint_op_subclass, maint_op_subclass, + has_ld_id, ld_id, has_head_id, head_id); module.type = type; module.health_status = health_status; @@ -1875,6 +2186,18 @@ void qmp_cxl_inject_memory_module_event(const char *path, CxlEventLog log, stl_le_p(&module.corrected_persistent_error_count, corrected_persist_error_count); + if (component_id) { + strncpy((char *)module.component_id, component_id, + sizeof(module.component_id) - 1); + valid_flags |= CXL_MMER_VALID_COMPONENT; + if (has_comp_id_pldm && is_comp_id_pldm) { + valid_flags |= CXL_MMER_VALID_COMPONENT_ID_FORMAT; + } + } + module.sub_type = sub_type; + + stw_le_p(&module.validity_flags, valid_flags); + if (cxl_event_insert(cxlds, enc_log, (CXLEventRecordRaw *)&module)) { cxl_event_irq_assert(ct3d); } diff --git a/hw/mem/cxl_type3_stubs.c b/hw/mem/cxl_type3_stubs.c index c1a5e4a7c193..98292a931c16 100644 --- a/hw/mem/cxl_type3_stubs.c +++ b/hw/mem/cxl_type3_stubs.c @@ -14,16 +14,34 @@ #include "qapi/qapi-commands-cxl.h" void qmp_cxl_inject_general_media_event(const char *path, CxlEventLog log, - uint8_t flags, uint64_t dpa, + uint32_t flags, bool has_maint_op_class, + uint8_t maint_op_class, + bool has_maint_op_subclass, + uint8_t maint_op_subclass, + bool has_ld_id, uint16_t ld_id, + bool has_head_id, uint8_t head_id, + uint64_t dpa, uint8_t descriptor, uint8_t type, uint8_t transaction_type, bool has_channel, uint8_t channel, bool has_rank, uint8_t rank, bool has_device, uint32_t device, const char *component_id, + bool has_comp_id_pldm, + bool is_comp_id_pldm, + bool has_cme_ev_flags, + uint8_t cme_ev_flags, + bool has_cme_count, uint32_t cme_count, + uint8_t sub_type, Error **errp) {} -void qmp_cxl_inject_dram_event(const char *path, CxlEventLog log, uint8_t flags, +void qmp_cxl_inject_dram_event(const char *path, CxlEventLog log, + uint32_t flags, + bool has_maint_op_class, uint8_t maint_op_class, + bool has_maint_op_subclass, + uint8_t maint_op_subclass, + bool has_ld_id, uint16_t ld_id, + bool has_head_id, uint8_t head_id, uint64_t dpa, uint8_t descriptor, uint8_t type, uint8_t transaction_type, bool has_channel, uint8_t channel, @@ -35,10 +53,23 @@ void qmp_cxl_inject_dram_event(const char *path, CxlEventLog log, uint8_t flags, bool has_column, uint16_t column, bool has_correction_mask, uint64List *correction_mask, + const char *component_id, + bool has_comp_id_pldm, + bool is_comp_id_pldm, + bool has_sub_channel, uint8_t sub_channel, + bool has_cme_ev_flags, uint8_t cme_ev_flags, + bool has_cvme_count, uint32_t cvme_count, + uint8_t sub_type, Error **errp) {} void qmp_cxl_inject_memory_module_event(const char *path, CxlEventLog log, - uint8_t flags, uint8_t type, + uint32_t flags, bool has_maint_op_class, + uint8_t maint_op_class, + bool has_maint_op_subclass, + uint8_t maint_op_subclass, + bool has_ld_id, uint16_t ld_id, + bool has_head_id, uint8_t head_id, + uint8_t type, uint8_t health_status, uint8_t media_status, uint8_t additional_status, @@ -47,6 +78,10 @@ void qmp_cxl_inject_memory_module_event(const char *path, CxlEventLog log, uint32_t dirty_shutdown_count, uint32_t corrected_volatile_error_count, uint32_t corrected_persist_error_count, + const char *component_id, + bool has_comp_id_pldm, + bool is_comp_id_pldm, + uint8_t sub_type, Error **errp) {} void qmp_cxl_inject_poison(const char *path, uint64_t start, uint64_t length, diff --git a/hw/mem/meson.build b/hw/mem/meson.build index 1c1c6da24b5f..8c2beeb7d4d5 100644 --- a/hw/mem/meson.build +++ b/hw/mem/meson.build @@ -4,9 +4,9 @@ mem_ss.add(when: 'CONFIG_DIMM', if_true: files('pc-dimm.c')) mem_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_mc.c')) mem_ss.add(when: 'CONFIG_NVDIMM', if_true: files('nvdimm.c')) mem_ss.add(when: 'CONFIG_CXL_MEM_DEVICE', if_true: files('cxl_type3.c')) -system_ss.add(when: 'CONFIG_CXL_MEM_DEVICE', if_false: files('cxl_type3_stubs.c')) +stub_ss.add(files('cxl_type3_stubs.c')) -system_ss.add(when: 'CONFIG_MEM_DEVICE', if_false: files('memory-device-stubs.c')) +stub_ss.add(files('memory-device-stubs.c')) system_ss.add_all(when: 'CONFIG_MEM_DEVICE', if_true: mem_ss) system_ss.add(when: 'CONFIG_SPARSE_MEM', if_true: files('sparse-mem.c')) diff --git a/hw/meson.build b/hw/meson.build index 66e46b8090db..ef65ba51950d 100644 --- a/hw/meson.build +++ b/hw/meson.build @@ -35,6 +35,7 @@ subdir('dma') subdir('gpio') subdir('hyperv') subdir('i2c') +subdir('i3c') subdir('ide') subdir('input') subdir('intc') @@ -44,6 +45,7 @@ subdir('isa') subdir('mem') subdir('misc') subdir('net') +subdir('nitro') subdir('nubus') subdir('nvme') subdir('nvram') diff --git a/hw/misc/allwinner-cpucfg.c b/hw/misc/allwinner-cpucfg.c index 3a1526bda0d2..f8dbfdd120c7 100644 --- a/hw/misc/allwinner-cpucfg.c +++ b/hw/misc/allwinner-cpucfg.c @@ -84,7 +84,7 @@ static void allwinner_cpucfg_cpu_reset(AwCpuCfgState *s, uint8_t cpu_id) trace_allwinner_cpucfg_cpu_reset(cpu_id, s->entry_addr); - ARMCPU *target_cpu = ARM_CPU(arm_get_cpu_by_id(cpu_id)); + CPUState *target_cpu = arm_get_cpu_by_id(cpu_id); if (!target_cpu) { /* * Called with a bogus value for cpu_id. Guest error will @@ -92,7 +92,7 @@ static void allwinner_cpucfg_cpu_reset(AwCpuCfgState *s, uint8_t cpu_id) */ return; } - bool target_aa64 = arm_feature(&target_cpu->env, ARM_FEATURE_AARCH64); + bool target_aa64 = arm_feature(cpu_env(target_cpu), ARM_FEATURE_AARCH64); ret = arm_set_cpu_on(cpu_id, s->entry_addr, 0, CPU_EXCEPTION_LEVEL_ON_RESET, target_aa64); diff --git a/hw/misc/aspeed_i3c.c b/hw/misc/aspeed_i3c.c deleted file mode 100644 index ac6db214ee26..000000000000 --- a/hw/misc/aspeed_i3c.c +++ /dev/null @@ -1,383 +0,0 @@ -/* - * ASPEED I3C Controller - * - * Copyright (C) 2021 ASPEED Technology Inc. - * - * This code is licensed under the GPL version 2 or later. See - * the COPYING file in the top-level directory. - */ - -#include "qemu/osdep.h" -#include "qemu/log.h" -#include "qemu/error-report.h" -#include "hw/misc/aspeed_i3c.h" -#include "hw/core/registerfields.h" -#include "hw/core/qdev-properties.h" -#include "qapi/error.h" -#include "migration/vmstate.h" -#include "trace.h" - -/* I3C Controller Registers */ -REG32(I3C1_REG0, 0x10) -REG32(I3C1_REG1, 0x14) - FIELD(I3C1_REG1, I2C_MODE, 0, 1) - FIELD(I3C1_REG1, SA_EN, 15, 1) -REG32(I3C2_REG0, 0x20) -REG32(I3C2_REG1, 0x24) - FIELD(I3C2_REG1, I2C_MODE, 0, 1) - FIELD(I3C2_REG1, SA_EN, 15, 1) -REG32(I3C3_REG0, 0x30) -REG32(I3C3_REG1, 0x34) - FIELD(I3C3_REG1, I2C_MODE, 0, 1) - FIELD(I3C3_REG1, SA_EN, 15, 1) -REG32(I3C4_REG0, 0x40) -REG32(I3C4_REG1, 0x44) - FIELD(I3C4_REG1, I2C_MODE, 0, 1) - FIELD(I3C4_REG1, SA_EN, 15, 1) -REG32(I3C5_REG0, 0x50) -REG32(I3C5_REG1, 0x54) - FIELD(I3C5_REG1, I2C_MODE, 0, 1) - FIELD(I3C5_REG1, SA_EN, 15, 1) -REG32(I3C6_REG0, 0x60) -REG32(I3C6_REG1, 0x64) - FIELD(I3C6_REG1, I2C_MODE, 0, 1) - FIELD(I3C6_REG1, SA_EN, 15, 1) - -/* I3C Device Registers */ -REG32(DEVICE_CTRL, 0x00) -REG32(DEVICE_ADDR, 0x04) -REG32(HW_CAPABILITY, 0x08) -REG32(COMMAND_QUEUE_PORT, 0x0c) -REG32(RESPONSE_QUEUE_PORT, 0x10) -REG32(RX_TX_DATA_PORT, 0x14) -REG32(IBI_QUEUE_STATUS, 0x18) -REG32(IBI_QUEUE_DATA, 0x18) -REG32(QUEUE_THLD_CTRL, 0x1c) -REG32(DATA_BUFFER_THLD_CTRL, 0x20) -REG32(IBI_QUEUE_CTRL, 0x24) -REG32(IBI_MR_REQ_REJECT, 0x2c) -REG32(IBI_SIR_REQ_REJECT, 0x30) -REG32(RESET_CTRL, 0x34) -REG32(SLV_EVENT_CTRL, 0x38) -REG32(INTR_STATUS, 0x3c) -REG32(INTR_STATUS_EN, 0x40) -REG32(INTR_SIGNAL_EN, 0x44) -REG32(INTR_FORCE, 0x48) -REG32(QUEUE_STATUS_LEVEL, 0x4c) -REG32(DATA_BUFFER_STATUS_LEVEL, 0x50) -REG32(PRESENT_STATE, 0x54) -REG32(CCC_DEVICE_STATUS, 0x58) -REG32(DEVICE_ADDR_TABLE_POINTER, 0x5c) - FIELD(DEVICE_ADDR_TABLE_POINTER, DEPTH, 16, 16) - FIELD(DEVICE_ADDR_TABLE_POINTER, ADDR, 0, 16) -REG32(DEV_CHAR_TABLE_POINTER, 0x60) -REG32(VENDOR_SPECIFIC_REG_POINTER, 0x6c) -REG32(SLV_MIPI_PID_VALUE, 0x70) -REG32(SLV_PID_VALUE, 0x74) -REG32(SLV_CHAR_CTRL, 0x78) -REG32(SLV_MAX_LEN, 0x7c) -REG32(MAX_READ_TURNAROUND, 0x80) -REG32(MAX_DATA_SPEED, 0x84) -REG32(SLV_DEBUG_STATUS, 0x88) -REG32(SLV_INTR_REQ, 0x8c) -REG32(DEVICE_CTRL_EXTENDED, 0xb0) -REG32(SCL_I3C_OD_TIMING, 0xb4) -REG32(SCL_I3C_PP_TIMING, 0xb8) -REG32(SCL_I2C_FM_TIMING, 0xbc) -REG32(SCL_I2C_FMP_TIMING, 0xc0) -REG32(SCL_EXT_LCNT_TIMING, 0xc8) -REG32(SCL_EXT_TERMN_LCNT_TIMING, 0xcc) -REG32(BUS_FREE_TIMING, 0xd4) -REG32(BUS_IDLE_TIMING, 0xd8) -REG32(I3C_VER_ID, 0xe0) -REG32(I3C_VER_TYPE, 0xe4) -REG32(EXTENDED_CAPABILITY, 0xe8) -REG32(SLAVE_CONFIG, 0xec) - -static const uint32_t ast2600_i3c_device_resets[ASPEED_I3C_DEVICE_NR_REGS] = { - [R_HW_CAPABILITY] = 0x000e00bf, - [R_QUEUE_THLD_CTRL] = 0x01000101, - [R_I3C_VER_ID] = 0x3130302a, - [R_I3C_VER_TYPE] = 0x6c633033, - [R_DEVICE_ADDR_TABLE_POINTER] = 0x00080280, - [R_DEV_CHAR_TABLE_POINTER] = 0x00020200, - [A_VENDOR_SPECIFIC_REG_POINTER] = 0x000000b0, - [R_SLV_MAX_LEN] = 0x00ff00ff, -}; - -static uint64_t aspeed_i3c_device_read(void *opaque, hwaddr offset, - unsigned size) -{ - AspeedI3CDevice *s = ASPEED_I3C_DEVICE(opaque); - uint32_t addr = offset >> 2; - uint64_t value; - - switch (addr) { - case R_COMMAND_QUEUE_PORT: - value = 0; - break; - default: - value = s->regs[addr]; - break; - } - - trace_aspeed_i3c_device_read(s->id, offset, value); - - return value; -} - -static void aspeed_i3c_device_write(void *opaque, hwaddr offset, - uint64_t value, unsigned size) -{ - AspeedI3CDevice *s = ASPEED_I3C_DEVICE(opaque); - uint32_t addr = offset >> 2; - - trace_aspeed_i3c_device_write(s->id, offset, value); - - switch (addr) { - case R_HW_CAPABILITY: - case R_RESPONSE_QUEUE_PORT: - case R_IBI_QUEUE_DATA: - case R_QUEUE_STATUS_LEVEL: - case R_PRESENT_STATE: - case R_CCC_DEVICE_STATUS: - case R_DEVICE_ADDR_TABLE_POINTER: - case R_VENDOR_SPECIFIC_REG_POINTER: - case R_SLV_CHAR_CTRL: - case R_SLV_MAX_LEN: - case R_MAX_READ_TURNAROUND: - case R_I3C_VER_ID: - case R_I3C_VER_TYPE: - case R_EXTENDED_CAPABILITY: - qemu_log_mask(LOG_GUEST_ERROR, - "%s: write to readonly register[0x%02" HWADDR_PRIx - "] = 0x%08" PRIx64 "\n", - __func__, offset, value); - break; - case R_RX_TX_DATA_PORT: - break; - case R_RESET_CTRL: - break; - default: - s->regs[addr] = value; - break; - } -} - -static const VMStateDescription aspeed_i3c_device_vmstate = { - .name = TYPE_ASPEED_I3C, - .version_id = 1, - .minimum_version_id = 1, - .fields = (const VMStateField[]){ - VMSTATE_UINT32_ARRAY(regs, AspeedI3CDevice, ASPEED_I3C_DEVICE_NR_REGS), - VMSTATE_END_OF_LIST(), - } -}; - -static const MemoryRegionOps aspeed_i3c_device_ops = { - .read = aspeed_i3c_device_read, - .write = aspeed_i3c_device_write, - .endianness = DEVICE_LITTLE_ENDIAN, -}; - -static void aspeed_i3c_device_reset(DeviceState *dev) -{ - AspeedI3CDevice *s = ASPEED_I3C_DEVICE(dev); - - memcpy(s->regs, ast2600_i3c_device_resets, sizeof(s->regs)); -} - -static void aspeed_i3c_device_realize(DeviceState *dev, Error **errp) -{ - AspeedI3CDevice *s = ASPEED_I3C_DEVICE(dev); - g_autofree char *name = g_strdup_printf(TYPE_ASPEED_I3C_DEVICE ".%d", - s->id); - - sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq); - - memory_region_init_io(&s->mr, OBJECT(s), &aspeed_i3c_device_ops, - s, name, ASPEED_I3C_DEVICE_NR_REGS << 2); -} - -static uint64_t aspeed_i3c_read(void *opaque, hwaddr addr, unsigned int size) -{ - AspeedI3CState *s = ASPEED_I3C(opaque); - uint64_t val = 0; - - val = s->regs[addr >> 2]; - - trace_aspeed_i3c_read(addr, val); - - return val; -} - -static void aspeed_i3c_write(void *opaque, - hwaddr addr, - uint64_t data, - unsigned int size) -{ - AspeedI3CState *s = ASPEED_I3C(opaque); - - trace_aspeed_i3c_write(addr, data); - - addr >>= 2; - - /* I3C controller register */ - switch (addr) { - case R_I3C1_REG1: - case R_I3C2_REG1: - case R_I3C3_REG1: - case R_I3C4_REG1: - case R_I3C5_REG1: - case R_I3C6_REG1: - if (data & R_I3C1_REG1_I2C_MODE_MASK) { - qemu_log_mask(LOG_UNIMP, - "%s: Unsupported I2C mode [0x%08" HWADDR_PRIx - "]=%08" PRIx64 "\n", - __func__, addr << 2, data); - break; - } - if (data & R_I3C1_REG1_SA_EN_MASK) { - qemu_log_mask(LOG_UNIMP, - "%s: Unsupported slave mode [%08" HWADDR_PRIx - "]=0x%08" PRIx64 "\n", - __func__, addr << 2, data); - break; - } - s->regs[addr] = data; - break; - default: - s->regs[addr] = data; - break; - } -} - -static const MemoryRegionOps aspeed_i3c_ops = { - .read = aspeed_i3c_read, - .write = aspeed_i3c_write, - .endianness = DEVICE_LITTLE_ENDIAN, - .valid = { - .min_access_size = 1, - .max_access_size = 4, - } -}; - -static void aspeed_i3c_reset(DeviceState *dev) -{ - AspeedI3CState *s = ASPEED_I3C(dev); - memset(s->regs, 0, sizeof(s->regs)); -} - -static void aspeed_i3c_instance_init(Object *obj) -{ - AspeedI3CState *s = ASPEED_I3C(obj); - int i; - - for (i = 0; i < ASPEED_I3C_NR_DEVICES; ++i) { - object_initialize_child(obj, "device[*]", &s->devices[i], - TYPE_ASPEED_I3C_DEVICE); - } -} - -static void aspeed_i3c_realize(DeviceState *dev, Error **errp) -{ - int i; - AspeedI3CState *s = ASPEED_I3C(dev); - SysBusDevice *sbd = SYS_BUS_DEVICE(dev); - - memory_region_init(&s->iomem_container, OBJECT(s), - TYPE_ASPEED_I3C ".container", 0x8000); - - sysbus_init_mmio(sbd, &s->iomem_container); - - memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_i3c_ops, s, - TYPE_ASPEED_I3C ".regs", ASPEED_I3C_NR_REGS << 2); - - memory_region_add_subregion(&s->iomem_container, 0x0, &s->iomem); - - for (i = 0; i < ASPEED_I3C_NR_DEVICES; ++i) { - Object *i3c_dev = OBJECT(&s->devices[i]); - - if (!object_property_set_uint(i3c_dev, "device-id", i, errp)) { - return; - } - - if (!sysbus_realize(SYS_BUS_DEVICE(i3c_dev), errp)) { - return; - } - - /* - * Register Address of I3CX Device = - * (Base Address of Global Register) + (Offset of I3CX) + Offset - * X = 0, 1, 2, 3, 4, 5 - * Offset of I3C0 = 0x2000 - * Offset of I3C1 = 0x3000 - * Offset of I3C2 = 0x4000 - * Offset of I3C3 = 0x5000 - * Offset of I3C4 = 0x6000 - * Offset of I3C5 = 0x7000 - */ - memory_region_add_subregion(&s->iomem_container, - 0x2000 + i * 0x1000, &s->devices[i].mr); - } - -} - -static const Property aspeed_i3c_device_properties[] = { - DEFINE_PROP_UINT8("device-id", AspeedI3CDevice, id, 0), -}; - -static void aspeed_i3c_device_class_init(ObjectClass *klass, const void *data) -{ - DeviceClass *dc = DEVICE_CLASS(klass); - - dc->desc = "Aspeed I3C Device"; - dc->realize = aspeed_i3c_device_realize; - device_class_set_legacy_reset(dc, aspeed_i3c_device_reset); - device_class_set_props(dc, aspeed_i3c_device_properties); -} - -static const TypeInfo aspeed_i3c_device_info = { - .name = TYPE_ASPEED_I3C_DEVICE, - .parent = TYPE_SYS_BUS_DEVICE, - .instance_size = sizeof(AspeedI3CDevice), - .class_init = aspeed_i3c_device_class_init, -}; - -static const VMStateDescription vmstate_aspeed_i3c = { - .name = TYPE_ASPEED_I3C, - .version_id = 1, - .minimum_version_id = 1, - .fields = (const VMStateField[]) { - VMSTATE_UINT32_ARRAY(regs, AspeedI3CState, ASPEED_I3C_NR_REGS), - VMSTATE_STRUCT_ARRAY(devices, AspeedI3CState, ASPEED_I3C_NR_DEVICES, 1, - aspeed_i3c_device_vmstate, AspeedI3CDevice), - VMSTATE_END_OF_LIST(), - } -}; - -static void aspeed_i3c_class_init(ObjectClass *klass, const void *data) -{ - DeviceClass *dc = DEVICE_CLASS(klass); - - dc->realize = aspeed_i3c_realize; - device_class_set_legacy_reset(dc, aspeed_i3c_reset); - dc->desc = "Aspeed I3C Controller"; - dc->vmsd = &vmstate_aspeed_i3c; -} - -static const TypeInfo aspeed_i3c_info = { - .name = TYPE_ASPEED_I3C, - .parent = TYPE_SYS_BUS_DEVICE, - .instance_init = aspeed_i3c_instance_init, - .instance_size = sizeof(AspeedI3CState), - .class_init = aspeed_i3c_class_init, -}; - -static void aspeed_i3c_register_types(void) -{ - type_register_static(&aspeed_i3c_device_info); - type_register_static(&aspeed_i3c_info); -} - -type_init(aspeed_i3c_register_types); diff --git a/hw/misc/ivshmem-pci.c b/hw/misc/ivshmem-pci.c index a3a43f53bd13..c987eebb9833 100644 --- a/hw/misc/ivshmem-pci.c +++ b/hw/misc/ivshmem-pci.c @@ -442,13 +442,14 @@ static void ivshmem_add_kvm_msi_virq(IVShmemState *s, int vector, s->msi_vectors[vector].pdev = pdev; } -static void setup_interrupt(IVShmemState *s, int vector, Error **errp) +static bool setup_interrupt(IVShmemState *s, int vector, Error **errp) { EventNotifier *n = &s->peers[s->vm_id].eventfds[vector]; bool with_irqfd = kvm_msi_via_irqfd_enabled() && ivshmem_has_feature(s, IVSHMEM_MSI); PCIDevice *pdev = PCI_DEVICE(s); Error *err = NULL; + int ret; IVSHMEM_DPRINTF("setting up interrupt for vector: %d\n", vector); @@ -460,18 +461,22 @@ static void setup_interrupt(IVShmemState *s, int vector, Error **errp) ivshmem_add_kvm_msi_virq(s, vector, &err); if (err) { error_propagate(errp, err); - return; + return false; } if (!msix_is_masked(pdev, vector)) { - kvm_irqchip_add_irqfd_notifier_gsi(kvm_state, n, NULL, + ret = kvm_irqchip_add_irqfd_notifier_gsi(kvm_state, n, NULL, s->msi_vectors[vector].virq); - /* TODO handle error */ + if (ret < 0) { + error_setg(errp, "Failed to configure irqfd notifier"); + return false; + } } } else { /* it will be delayed until msix is enabled, in write_config */ IVSHMEM_DPRINTF("with irqfd, delayed until msix enabled\n"); } + return true; } static void process_msg_shmem(IVShmemState *s, int fd, Error **errp) diff --git a/hw/misc/meson.build b/hw/misc/meson.build index d304a984984a..96b6705b7d07 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -135,7 +135,6 @@ system_ss.add(when: 'CONFIG_PVPANIC_MMIO', if_true: files('pvpanic-mmio.c')) system_ss.add(when: 'CONFIG_AUX', if_true: files('auxbus.c')) system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files( 'aspeed_hace.c', - 'aspeed_i3c.c', 'aspeed_lpc.c', 'aspeed_ltpi.c', 'aspeed_pwm.c', diff --git a/hw/misc/riscv_cpc.c b/hw/misc/riscv_cpc.c index 231a419062f7..4bf2fd8db168 100644 --- a/hw/misc/riscv_cpc.c +++ b/hw/misc/riscv_cpc.c @@ -185,6 +185,13 @@ static void riscv_cpc_init(Object *obj) } } +static void riscv_cpc_finalize(Object *obj) +{ + RISCVCPCState *s = RISCV_CPC(obj); + + g_free(s->cpus); +} + static void riscv_cpc_realize(DeviceState *dev, Error **errp) { RISCVCPCState *s = RISCV_CPC(dev); @@ -254,6 +261,7 @@ static const TypeInfo riscv_cpc_info = { .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(RISCVCPCState), .instance_init = riscv_cpc_init, + .instance_finalize = riscv_cpc_finalize, .class_init = riscv_cpc_class_init, }; diff --git a/hw/misc/sifive_e_aon.c b/hw/misc/sifive_e_aon.c index 0e82ae3758b7..e78f4f567259 100644 --- a/hw/misc/sifive_e_aon.c +++ b/hw/misc/sifive_e_aon.c @@ -94,9 +94,9 @@ static void sifive_e_aon_wdt_update_state(SiFiveEAONState *r) next += muldiv64((r->wdogcmp0 - wdogs) << FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, SCALE), NANOSECONDS_PER_SECOND, r->wdogclk_freq); - timer_mod(r->wdog_timer, next); + timer_mod(&r->wdog_timer, next); } else { - timer_mod(r->wdog_timer, INT64_MAX); + timer_mod(&r->wdog_timer, INT64_MAX); } } @@ -283,12 +283,19 @@ static void sifive_e_aon_init(Object *obj) sysbus_init_mmio(sbd, &r->mmio); /* watchdog timer */ - r->wdog_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, - sifive_e_aon_wdt_expired_cb, r); + timer_init_ns(&r->wdog_timer, QEMU_CLOCK_VIRTUAL, + sifive_e_aon_wdt_expired_cb, r); r->wdogclk_freq = SIFIVE_E_LFCLK_DEFAULT_FREQ; sysbus_init_irq(sbd, &r->wdog_irq); } +static void sifive_e_aon_finalize(Object *obj) +{ + SiFiveEAONState *r = SIFIVE_E_AON(obj); + + timer_del(&r->wdog_timer); +} + static const Property sifive_e_aon_properties[] = { DEFINE_PROP_UINT64("wdogclk-frequency", SiFiveEAONState, wdogclk_freq, SIFIVE_E_LFCLK_DEFAULT_FREQ), @@ -307,6 +314,7 @@ static const TypeInfo sifive_e_aon_info = { .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(SiFiveEAONState), .instance_init = sifive_e_aon_init, + .instance_finalize = sifive_e_aon_finalize, .class_init = sifive_e_aon_class_init, }; diff --git a/hw/misc/trace-events b/hw/misc/trace-events index d6af2fcf8527..b88accc437c9 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -293,12 +293,6 @@ armsse_mhu_write(uint64_t offset, uint64_t data, unsigned size) "SSE-200 MHU wri # aspeed_xdma.c aspeed_xdma_write(uint64_t offset, uint64_t data) "XDMA write: offset 0x%" PRIx64 " data 0x%" PRIx64 -# aspeed_i3c.c -aspeed_i3c_read(uint64_t offset, uint64_t data) "I3C read: offset 0x%" PRIx64 " data 0x%" PRIx64 -aspeed_i3c_write(uint64_t offset, uint64_t data) "I3C write: offset 0x%" PRIx64 " data 0x%" PRIx64 -aspeed_i3c_device_read(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] read: offset 0x%" PRIx64 " data 0x%" PRIx64 -aspeed_i3c_device_write(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] write: offset 0x%" PRIx64 " data 0x%" PRIx64 - # aspeed_pwm.c aspeed_pwm_read(uint64_t offset, uint64_t data) "read: offset 0x%" PRIx64 " data 0x%" PRIx64 aspeed_pwm_write(uint64_t offset, uint64_t data) "write: offset 0x%" PRIx64 " data 0x%" PRIx64 diff --git a/hw/misc/virt_ctrl.c b/hw/misc/virt_ctrl.c index 40747925a290..9b82e97ffde7 100644 --- a/hw/misc/virt_ctrl.c +++ b/hw/misc/virt_ctrl.c @@ -43,7 +43,7 @@ static uint64_t virt_ctrl_read(void *opaque, hwaddr addr, unsigned size) break; } - trace_virt_ctrl_write(s, addr, size, value); + trace_virt_ctrl_read(s, addr, size, value); return value; } diff --git a/hw/net/Kconfig b/hw/net/Kconfig index 2b513d689584..f9a1dfb80de2 100644 --- a/hw/net/Kconfig +++ b/hw/net/Kconfig @@ -79,9 +79,6 @@ config NE2000_ISA config OPENCORES_ETH bool -config XGMAC - bool - config ALLWINNER_EMAC bool diff --git a/hw/net/meson.build b/hw/net/meson.build index 913eaedbc529..310258746996 100644 --- a/hw/net/meson.build +++ b/hw/net/meson.build @@ -22,7 +22,6 @@ system_ss.add(when: 'CONFIG_LAN9118', if_true: files('lan9118.c')) system_ss.add(when: 'CONFIG_LAN9118_PHY', if_true: files('lan9118_phy.c')) system_ss.add(when: 'CONFIG_NE2000_ISA', if_true: files('ne2000-isa.c')) system_ss.add(when: 'CONFIG_OPENCORES_ETH', if_true: files('opencores_eth.c')) -system_ss.add(when: 'CONFIG_XGMAC', if_true: files('xgmac.c')) system_ss.add(when: 'CONFIG_XILINX_AXI', if_true: files('xilinx_axienet.c')) system_ss.add(when: 'CONFIG_ALLWINNER_EMAC', if_true: files('allwinner_emac.c')) system_ss.add(when: 'CONFIG_ALLWINNER_SUN8I_EMAC', if_true: files('allwinner-sun8i-emac.c')) @@ -46,7 +45,7 @@ specific_ss.add(when: 'CONFIG_PSERIES', if_true: files('spapr_llan.c')) system_ss.add(when: 'CONFIG_XILINX_ETHLITE', if_true: files('xilinx_ethlite.c')) system_ss.add(when: 'CONFIG_VIRTIO_NET', if_true: files('net_rx_pkt.c')) -specific_ss.add(when: 'CONFIG_VIRTIO_NET', if_true: files('virtio-net.c')) +system_ss.add(when: 'CONFIG_VIRTIO_NET', if_true: files('virtio-net.c')) if have_vhost_net system_ss.add(when: 'CONFIG_VIRTIO_NET', if_true: files('vhost_net.c'), if_false: files('vhost_net-stub.c')) @@ -67,7 +66,8 @@ system_ss.add(when: 'CONFIG_ROCKER', if_true: files( 'rocker/rocker_fp.c', 'rocker/rocker_of_dpa.c', 'rocker/rocker_world.c', -), if_false: files('rocker/qmp-norocker.c')) +)) +stub_ss.add(files('rocker/rocker-stubs.c')) system_ss.add(files('rocker/rocker-hmp-cmds.c')) subdir('can') diff --git a/hw/net/npcm_gmac.c b/hw/net/npcm_gmac.c index 123fb92ca408..d9902d9ab5d4 100644 --- a/hw/net/npcm_gmac.c +++ b/hw/net/npcm_gmac.c @@ -700,6 +700,13 @@ static uint64_t npcm_gmac_read(void *opaque, hwaddr offset, unsigned size) NPCMGMACState *gmac = opaque; uint32_t v = 0; + if (offset >= NPCM_GMAC_REG_SIZE) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid register offset: 0x%04" HWADDR_PRIx"\n", + DEVICE(gmac)->canonical_path, offset); + return v; + } + switch (offset) { /* Write only registers */ case A_NPCM_DMA_XMT_POLL_DEMAND: @@ -724,6 +731,13 @@ static void npcm_gmac_write(void *opaque, hwaddr offset, trace_npcm_gmac_reg_write(DEVICE(gmac)->canonical_path, offset, v); + if (offset >= NPCM_GMAC_REG_SIZE) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid register offset: 0x%04" HWADDR_PRIx"\n", + DEVICE(gmac)->canonical_path, offset); + return; + } + switch (offset) { /* Read only registers */ case A_NPCM_GMAC_VERSION: diff --git a/hw/net/rocker/qmp-norocker.c b/hw/net/rocker/rocker-stubs.c similarity index 100% rename from hw/net/rocker/qmp-norocker.c rename to hw/net/rocker/rocker-stubs.c diff --git a/hw/net/rocker/rocker_of_dpa.c b/hw/net/rocker/rocker_of_dpa.c index 16b9bc7a4b8f..814f19afc58d 100644 --- a/hw/net/rocker/rocker_of_dpa.c +++ b/hw/net/rocker/rocker_of_dpa.c @@ -143,7 +143,7 @@ typedef struct of_dpa_flow { typedef struct of_dpa_flow_pkt_fields { uint32_t tunnel_id; struct eth_header *ethhdr; - uint16_t *h_proto; + void *h_proto; /* pointer to unaligned uint16_t data */ struct vlan_header *vlanhdr; struct ip_header *ipv4hdr; struct ip6_header *ipv6hdr; @@ -196,6 +196,11 @@ typedef struct of_dpa_group { }; } OfDpaGroup; +static uint16_t of_dpa_flow_pkt_h_proto(const OfDpaFlowPktFields *fields) +{ + return lduw_he_p(fields->h_proto); +} + static int of_dpa_mask2prefix(uint32_t mask) { return 32 - ctz32(ntohl(mask)); @@ -395,7 +400,7 @@ static void of_dpa_flow_pkt_parse(OfDpaFlowContext *fc, fields->ethhdr = iov->iov_base; fields->h_proto = &fields->ethhdr->h_proto; - if (ntohs(*fields->h_proto) == ETH_P_VLAN) { + if (ntohs(of_dpa_flow_pkt_h_proto(fields) == ETH_P_VLAN)) { sofar += sizeof(struct vlan_header); if (iov->iov_len < sofar) { DPRINTF("flow_pkt_parse underrun on vlan_header\n"); @@ -405,7 +410,7 @@ static void of_dpa_flow_pkt_parse(OfDpaFlowContext *fc, fields->h_proto = &fields->vlanhdr->h_proto; } - switch (ntohs(*fields->h_proto)) { + switch (ntohs(of_dpa_flow_pkt_h_proto(fields))) { case ETH_P_IP: sofar += sizeof(struct ip_header); if (iov->iov_len < sofar) { @@ -547,7 +552,7 @@ static void of_dpa_term_mac_build_match(OfDpaFlowContext *fc, { match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC; match->value.in_pport = fc->in_pport; - match->value.eth.type = *fc->fields.h_proto; + match->value.eth.type = of_dpa_flow_pkt_h_proto(&fc->fields); match->value.eth.vlan_id = fc->fields.vlanhdr->h_tci; memcpy(match->value.eth.dst.a, fc->fields.ethhdr->h_dest, sizeof(match->value.eth.dst.a)); @@ -643,7 +648,7 @@ static void of_dpa_unicast_routing_build_match(OfDpaFlowContext *fc, OfDpaFlowMatch *match) { match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING; - match->value.eth.type = *fc->fields.h_proto; + match->value.eth.type = of_dpa_flow_pkt_h_proto(&fc->fields); if (fc->fields.ipv4hdr) { match->value.ipv4.addr.dst = fc->fields.ipv4hdr->ip_dst; } @@ -672,7 +677,7 @@ of_dpa_multicast_routing_build_match(OfDpaFlowContext *fc, OfDpaFlowMatch *match) { match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING; - match->value.eth.type = *fc->fields.h_proto; + match->value.eth.type = of_dpa_flow_pkt_h_proto(&fc->fields); match->value.eth.vlan_id = fc->fields.vlanhdr->h_tci; if (fc->fields.ipv4hdr) { match->value.ipv4.addr.src = fc->fields.ipv4hdr->ip_src; @@ -713,7 +718,7 @@ static void of_dpa_acl_build_match(OfDpaFlowContext *fc, sizeof(match->value.eth.src.a)); memcpy(match->value.eth.dst.a, fc->fields.ethhdr->h_dest, sizeof(match->value.eth.dst.a)); - match->value.eth.type = *fc->fields.h_proto; + match->value.eth.type = of_dpa_flow_pkt_h_proto(&fc->fields); match->value.eth.vlan_id = fc->fields.vlanhdr->h_tci; match->value.width = FLOW_KEY_WIDTH(eth.type); if (fc->fields.ipv4hdr) { diff --git a/hw/net/rtl8139.c b/hw/net/rtl8139.c index 9fd00574d29d..2ad6338ebee5 100644 --- a/hw/net/rtl8139.c +++ b/hw/net/rtl8139.c @@ -815,7 +815,8 @@ static bool rtl8139_can_receive(NetClientState *nc) return avail == 0 || avail >= 1514 || (s->IntrMask & RxOverflow); } -static ssize_t rtl8139_do_receive(NetClientState *nc, const uint8_t *buf, size_t size_, int do_interrupt) +static ssize_t rtl8139_receive(NetClientState *nc, + const uint8_t *buf, size_t size_) { RTL8139State *s = qemu_get_nic_opaque(nc); PCIDevice *d = PCI_DEVICE(s); @@ -1173,20 +1174,11 @@ static ssize_t rtl8139_do_receive(NetClientState *nc, const uint8_t *buf, size_t } s->IntrStatus |= RxOK; - - if (do_interrupt) - { - rtl8139_update_irq(s); - } + rtl8139_update_irq(s); return size_; } -static ssize_t rtl8139_receive(NetClientState *nc, const uint8_t *buf, size_t size) -{ - return rtl8139_do_receive(nc, buf, size, 1); -} - static void rtl8139_reset_rxring(RTL8139State *s, uint32_t bufferSize) { s->RxBufferSize = bufferSize; @@ -1745,7 +1737,7 @@ static uint32_t rtl8139_RxConfig_read(RTL8139State *s) } static void rtl8139_transfer_frame(RTL8139State *s, uint8_t *buf, int size, - int do_interrupt, const uint8_t *dot1q_buf) + const uint8_t *dot1q_buf) { struct iovec *iov = NULL; struct iovec vlan_iov[3]; @@ -1828,7 +1820,7 @@ static int rtl8139_transmit_one(RTL8139State *s, int descriptor) s->TxStatus[descriptor] |= TxHostOwns; s->TxStatus[descriptor] |= TxStatOK; - rtl8139_transfer_frame(s, txbuffer, txsize, 0, NULL); + rtl8139_transfer_frame(s, txbuffer, txsize, NULL); DPRINTF("+++ transmitted %d bytes from descriptor %d\n", txsize, descriptor); @@ -2246,7 +2238,7 @@ static int rtl8139_cplus_transmit_one(RTL8139State *s) DPRINTF("+++ C+ mode TSO transferring packet size " "%d\n", tso_send_size); rtl8139_transfer_frame(s, saved_buffer, tso_send_size, - 0, (uint8_t *) dot1q_buffer); + (uint8_t *)dot1q_buffer); /* add transferred count to TCP sequence number */ stl_be_p(&p_tcp_hdr->th_seq, @@ -2323,8 +2315,8 @@ static int rtl8139_cplus_transmit_one(RTL8139State *s) DPRINTF("+++ C+ mode transmitting %d bytes packet\n", saved_size); - rtl8139_transfer_frame(s, saved_buffer, saved_size, 1, - (uint8_t *) dot1q_buffer); + rtl8139_transfer_frame(s, saved_buffer, saved_size, + (uint8_t *)dot1q_buffer); /* restore card space if there was no recursion and reset offset */ if (!s->cplus_txbuffer) diff --git a/hw/net/smc91c111.c b/hw/net/smc91c111.c index 3420d8e28e17..3b526524fb59 100644 --- a/hw/net/smc91c111.c +++ b/hw/net/smc91c111.c @@ -30,6 +30,12 @@ * LAN91C111 datasheet). */ #define MAX_PACKET_SIZE 2048 +/* + * Size of the non-data fields in a data frame: status word, + * byte count, control byte, and last data byte; this defines + * the smallest value the byte count in the frame can validly be. + */ +#define MIN_PACKET_SIZE 6 #define TYPE_SMC91C111 "smc91c111" OBJECT_DECLARE_SIMPLE_TYPE(smc91c111_state, SMC91C111) @@ -289,7 +295,7 @@ static void smc91c111_do_tx(smc91c111_state *s) *(p++) = 0x40; len = *(p++); len |= ((int)*(p++)) << 8; - if (len > MAX_PACKET_SIZE) { + if (len < MIN_PACKET_SIZE || len > MAX_PACKET_SIZE) { /* * Datasheet doesn't say what to do here, and there is no * relevant tx error condition listed. Log, and drop the packet. @@ -300,7 +306,13 @@ static void smc91c111_do_tx(smc91c111_state *s) smc91c111_complete_tx_packet(s, packetnum); continue; } - len -= 6; + /* + * Convert from size of the data frame to number of bytes of + * actual packet data. Whether the "last data byte" field is + * included in the packet depends on the ODD bit in the control + * byte at the end of the frame. + */ + len -= MIN_PACKET_SIZE; control = p[len + 1]; if (control & 0x20) len++; diff --git a/hw/net/trace-events b/hw/net/trace-events index 23efa91d0552..001a20b0e2ac 100644 --- a/hw/net/trace-events +++ b/hw/net/trace-events @@ -527,3 +527,4 @@ xen_netdev_rx(int dev, int idx, int status, int flags) "vif%u idx %d status %d f # xilinx_ethlite.c ethlite_pkt_lost(uint32_t rx_ctrl) "rx_ctrl:0x%" PRIx32 ethlite_pkt_size_too_big(uint64_t size) "size:0x%" PRIx64 +ethlite_pkt_tx_size_too_big(uint64_t size) "size:0x%" PRIx64 diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c index cc89619a433a..2a5d642a6476 100644 --- a/hw/net/virtio-net.c +++ b/hw/net/virtio-net.c @@ -301,7 +301,7 @@ static void virtio_net_vhost_status(VirtIONet *n, uint8_t status) if (n->needs_vnet_hdr_swap) { error_report("backend does not support %s vnet headers; " "falling back on userspace virtio", - virtio_is_big_endian(vdev) ? "BE" : "LE"); + virtio_vdev_is_big_endian(vdev) ? "BE" : "LE"); return; } @@ -343,7 +343,7 @@ static int virtio_net_set_vnet_endian_one(VirtIODevice *vdev, NetClientState *peer, bool enable) { - if (virtio_is_big_endian(vdev)) { + if (virtio_vdev_is_big_endian(vdev)) { return qemu_set_vnet_be(peer, enable); } else { return qemu_set_vnet_le(peer, enable); @@ -935,8 +935,7 @@ static void virtio_net_set_features(VirtIODevice *vdev, int i; virtio_features_copy(features, in_features); - if (n->mtu_bypass_backend && - !virtio_has_feature(vdev->backend_features, VIRTIO_NET_F_MTU)) { + if (!virtio_has_feature(vdev->backend_features, VIRTIO_NET_F_MTU)) { virtio_clear_feature_ex(features, VIRTIO_NET_F_MTU); } @@ -3160,8 +3159,7 @@ static void virtio_net_get_features(VirtIODevice *vdev, uint64_t *features, vhost_net_get_features_ex(get_vhost_net(nc->peer), features); virtio_features_copy(vdev->backend_features_ex, features); - if (n->mtu_bypass_backend && - (n->host_features & 1ULL << VIRTIO_NET_F_MTU)) { + if ((n->host_features & 1ULL << VIRTIO_NET_F_MTU) != 0) { virtio_add_feature_ex(features, VIRTIO_NET_F_MTU); } @@ -4251,8 +4249,6 @@ static const Property virtio_net_properties[] = { DEFINE_PROP_UINT16("tx_queue_size", VirtIONet, net_conf.tx_queue_size, VIRTIO_NET_TX_QUEUE_DEFAULT_SIZE), DEFINE_PROP_UINT16("host_mtu", VirtIONet, net_conf.mtu, 0), - DEFINE_PROP_BOOL("x-mtu-bypass-backend", VirtIONet, mtu_bypass_backend, - true), DEFINE_PROP_INT32("speed", VirtIONet, net_conf.speed, SPEED_UNKNOWN), DEFINE_PROP_STRING("duplex", VirtIONet, net_conf.duplex_str), DEFINE_PROP_BOOL("failover", VirtIONet, failover, false), diff --git a/hw/net/xgmac.c b/hw/net/xgmac.c deleted file mode 100644 index 204c78aba379..000000000000 --- a/hw/net/xgmac.c +++ /dev/null @@ -1,443 +0,0 @@ -/* - * QEMU model of XGMAC Ethernet. - * - * derived from the Xilinx AXI-Ethernet by Edgar E. Iglesias. - * - * Copyright (c) 2011 Calxeda, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "qemu/osdep.h" -#include "hw/core/irq.h" -#include "hw/core/qdev-properties.h" -#include "hw/core/sysbus.h" -#include "exec/cpu-common.h" -#include "migration/vmstate.h" -#include "qemu/module.h" -#include "net/net.h" -#include "qom/object.h" - -#ifdef DEBUG_XGMAC -#define DEBUGF_BRK(message, args...) do { \ - fprintf(stderr, (message), ## args); \ - } while (0) -#else -#define DEBUGF_BRK(message, args...) do { } while (0) -#endif - -#define XGMAC_CONTROL 0x00000000 /* MAC Configuration */ -#define XGMAC_FRAME_FILTER 0x00000001 /* MAC Frame Filter */ -#define XGMAC_FLOW_CTRL 0x00000006 /* MAC Flow Control */ -#define XGMAC_VLAN_TAG 0x00000007 /* VLAN Tags */ -#define XGMAC_VERSION 0x00000008 /* Version */ -/* VLAN tag for insertion or replacement into tx frames */ -#define XGMAC_VLAN_INCL 0x00000009 -#define XGMAC_LPI_CTRL 0x0000000a /* LPI Control and Status */ -#define XGMAC_LPI_TIMER 0x0000000b /* LPI Timers Control */ -#define XGMAC_TX_PACE 0x0000000c /* Transmit Pace and Stretch */ -#define XGMAC_VLAN_HASH 0x0000000d /* VLAN Hash Table */ -#define XGMAC_DEBUG 0x0000000e /* Debug */ -#define XGMAC_INT_STATUS 0x0000000f /* Interrupt and Control */ -/* HASH table registers */ -#define XGMAC_HASH(n) ((0x00000300/4) + (n)) -#define XGMAC_NUM_HASH 16 -/* Operation Mode */ -#define XGMAC_OPMODE (0x00000400/4) -/* Remote Wake-Up Frame Filter */ -#define XGMAC_REMOTE_WAKE (0x00000700/4) -/* PMT Control and Status */ -#define XGMAC_PMT (0x00000704/4) - -#define XGMAC_ADDR_HIGH(reg) (0x00000010+((reg) * 2)) -#define XGMAC_ADDR_LOW(reg) (0x00000011+((reg) * 2)) - -#define DMA_BUS_MODE 0x000003c0 /* Bus Mode */ -#define DMA_XMT_POLL_DEMAND 0x000003c1 /* Transmit Poll Demand */ -#define DMA_RCV_POLL_DEMAND 0x000003c2 /* Received Poll Demand */ -#define DMA_RCV_BASE_ADDR 0x000003c3 /* Receive List Base */ -#define DMA_TX_BASE_ADDR 0x000003c4 /* Transmit List Base */ -#define DMA_STATUS 0x000003c5 /* Status Register */ -#define DMA_CONTROL 0x000003c6 /* Ctrl (Operational Mode) */ -#define DMA_INTR_ENA 0x000003c7 /* Interrupt Enable */ -#define DMA_MISSED_FRAME_CTR 0x000003c8 /* Missed Frame Counter */ -/* Receive Interrupt Watchdog Timer */ -#define DMA_RI_WATCHDOG_TIMER 0x000003c9 -#define DMA_AXI_BUS 0x000003ca /* AXI Bus Mode */ -#define DMA_AXI_STATUS 0x000003cb /* AXI Status */ -#define DMA_CUR_TX_DESC_ADDR 0x000003d2 /* Current Host Tx Descriptor */ -#define DMA_CUR_RX_DESC_ADDR 0x000003d3 /* Current Host Rx Descriptor */ -#define DMA_CUR_TX_BUF_ADDR 0x000003d4 /* Current Host Tx Buffer */ -#define DMA_CUR_RX_BUF_ADDR 0x000003d5 /* Current Host Rx Buffer */ -#define DMA_HW_FEATURE 0x000003d6 /* Enabled Hardware Features */ - -/* DMA Status register defines */ -#define DMA_STATUS_GMI 0x08000000 /* MMC interrupt */ -#define DMA_STATUS_GLI 0x04000000 /* GMAC Line interface int */ -#define DMA_STATUS_EB_MASK 0x00380000 /* Error Bits Mask */ -#define DMA_STATUS_EB_TX_ABORT 0x00080000 /* Error Bits - TX Abort */ -#define DMA_STATUS_EB_RX_ABORT 0x00100000 /* Error Bits - RX Abort */ -#define DMA_STATUS_TS_MASK 0x00700000 /* Transmit Process State */ -#define DMA_STATUS_TS_SHIFT 20 -#define DMA_STATUS_RS_MASK 0x000e0000 /* Receive Process State */ -#define DMA_STATUS_RS_SHIFT 17 -#define DMA_STATUS_NIS 0x00010000 /* Normal Interrupt Summary */ -#define DMA_STATUS_AIS 0x00008000 /* Abnormal Interrupt Summary */ -#define DMA_STATUS_ERI 0x00004000 /* Early Receive Interrupt */ -#define DMA_STATUS_FBI 0x00002000 /* Fatal Bus Error Interrupt */ -#define DMA_STATUS_ETI 0x00000400 /* Early Transmit Interrupt */ -#define DMA_STATUS_RWT 0x00000200 /* Receive Watchdog Timeout */ -#define DMA_STATUS_RPS 0x00000100 /* Receive Process Stopped */ -#define DMA_STATUS_RU 0x00000080 /* Receive Buffer Unavailable */ -#define DMA_STATUS_RI 0x00000040 /* Receive Interrupt */ -#define DMA_STATUS_UNF 0x00000020 /* Transmit Underflow */ -#define DMA_STATUS_OVF 0x00000010 /* Receive Overflow */ -#define DMA_STATUS_TJT 0x00000008 /* Transmit Jabber Timeout */ -#define DMA_STATUS_TU 0x00000004 /* Transmit Buffer Unavailable */ -#define DMA_STATUS_TPS 0x00000002 /* Transmit Process Stopped */ -#define DMA_STATUS_TI 0x00000001 /* Transmit Interrupt */ - -/* DMA Control register defines */ -#define DMA_CONTROL_ST 0x00002000 /* Start/Stop Transmission */ -#define DMA_CONTROL_SR 0x00000002 /* Start/Stop Receive */ -#define DMA_CONTROL_DFF 0x01000000 /* Disable flush of rx frames */ - -struct desc { - uint32_t ctl_stat; - uint16_t buffer1_size; - uint16_t buffer2_size; - uint32_t buffer1_addr; - uint32_t buffer2_addr; - uint32_t ext_stat; - uint32_t res[3]; -}; - -#define R_MAX 0x400 - -typedef struct RxTxStats { - uint64_t rx_bytes; - uint64_t tx_bytes; - - uint64_t rx; - uint64_t rx_bcast; - uint64_t rx_mcast; -} RxTxStats; - -#define TYPE_XGMAC "xgmac" -OBJECT_DECLARE_SIMPLE_TYPE(XgmacState, XGMAC) - -struct XgmacState { - SysBusDevice parent_obj; - - MemoryRegion iomem; - qemu_irq sbd_irq; - qemu_irq pmt_irq; - qemu_irq mci_irq; - NICState *nic; - NICConf conf; - - struct RxTxStats stats; - uint32_t regs[R_MAX]; -}; - -static const VMStateDescription vmstate_rxtx_stats = { - .name = "xgmac_stats", - .version_id = 1, - .minimum_version_id = 1, - .fields = (const VMStateField[]) { - VMSTATE_UINT64(rx_bytes, RxTxStats), - VMSTATE_UINT64(tx_bytes, RxTxStats), - VMSTATE_UINT64(rx, RxTxStats), - VMSTATE_UINT64(rx_bcast, RxTxStats), - VMSTATE_UINT64(rx_mcast, RxTxStats), - VMSTATE_END_OF_LIST() - } -}; - -static const VMStateDescription vmstate_xgmac = { - .name = "xgmac", - .version_id = 1, - .minimum_version_id = 1, - .fields = (const VMStateField[]) { - VMSTATE_STRUCT(stats, XgmacState, 0, vmstate_rxtx_stats, RxTxStats), - VMSTATE_UINT32_ARRAY(regs, XgmacState, R_MAX), - VMSTATE_END_OF_LIST() - } -}; - -static void xgmac_read_desc(XgmacState *s, struct desc *d, int rx) -{ - uint32_t addr = rx ? s->regs[DMA_CUR_RX_DESC_ADDR] : - s->regs[DMA_CUR_TX_DESC_ADDR]; - cpu_physical_memory_read(addr, d, sizeof(*d)); -} - -static void xgmac_write_desc(XgmacState *s, struct desc *d, int rx) -{ - int reg = rx ? DMA_CUR_RX_DESC_ADDR : DMA_CUR_TX_DESC_ADDR; - uint32_t addr = s->regs[reg]; - - if (!rx && (d->ctl_stat & 0x00200000)) { - s->regs[reg] = s->regs[DMA_TX_BASE_ADDR]; - } else if (rx && (d->buffer1_size & 0x8000)) { - s->regs[reg] = s->regs[DMA_RCV_BASE_ADDR]; - } else { - s->regs[reg] += sizeof(*d); - } - cpu_physical_memory_write(addr, d, sizeof(*d)); -} - -static void xgmac_enet_send(XgmacState *s) -{ - struct desc bd; - int frame_size; - int len; - QEMU_UNINITIALIZED uint8_t frame[8192]; - uint8_t *ptr; - - ptr = frame; - frame_size = 0; - while (1) { - xgmac_read_desc(s, &bd, 0); - if ((bd.ctl_stat & 0x80000000) == 0) { - /* Run out of descriptors to transmit. */ - break; - } - len = (bd.buffer1_size & 0xfff) + (bd.buffer2_size & 0xfff); - - /* - * FIXME: these cases of malformed tx descriptors (bad sizes) - * should probably be reported back to the guest somehow - * rather than simply silently stopping processing, but we - * don't know what the hardware does in this situation. - * This will only happen for buggy guests anyway. - */ - if ((bd.buffer1_size & 0xfff) > 2048) { - DEBUGF_BRK("qemu:%s:ERROR...ERROR...ERROR... -- " - "xgmac buffer 1 len on send > 2048 (0x%x)\n", - __func__, bd.buffer1_size & 0xfff); - break; - } - if ((bd.buffer2_size & 0xfff) != 0) { - DEBUGF_BRK("qemu:%s:ERROR...ERROR...ERROR... -- " - "xgmac buffer 2 len on send != 0 (0x%x)\n", - __func__, bd.buffer2_size & 0xfff); - break; - } - if (frame_size + len >= sizeof(frame)) { - DEBUGF_BRK("qemu:%s: buffer overflow %d read into %zu " - "buffer\n" , __func__, frame_size + len, sizeof(frame)); - DEBUGF_BRK("qemu:%s: buffer1.size=%d; buffer2.size=%d\n", - __func__, bd.buffer1_size, bd.buffer2_size); - break; - } - - cpu_physical_memory_read(bd.buffer1_addr, ptr, len); - ptr += len; - frame_size += len; - if (bd.ctl_stat & 0x20000000) { - /* Last buffer in frame. */ - qemu_send_packet(qemu_get_queue(s->nic), frame, len); - ptr = frame; - frame_size = 0; - s->regs[DMA_STATUS] |= DMA_STATUS_TI | DMA_STATUS_NIS; - } - bd.ctl_stat &= ~0x80000000; - /* Write back the modified descriptor. */ - xgmac_write_desc(s, &bd, 0); - } -} - -static void enet_update_irq(XgmacState *s) -{ - int stat = s->regs[DMA_STATUS] & s->regs[DMA_INTR_ENA]; - qemu_set_irq(s->sbd_irq, !!stat); -} - -static uint64_t enet_read(void *opaque, hwaddr addr, unsigned size) -{ - XgmacState *s = opaque; - uint64_t r = 0; - addr >>= 2; - - switch (addr) { - case XGMAC_VERSION: - r = 0x1012; - break; - default: - if (addr < ARRAY_SIZE(s->regs)) { - r = s->regs[addr]; - } - break; - } - return r; -} - -static void enet_write(void *opaque, hwaddr addr, - uint64_t value, unsigned size) -{ - XgmacState *s = opaque; - - addr >>= 2; - switch (addr) { - case DMA_BUS_MODE: - s->regs[DMA_BUS_MODE] = value & ~0x1; - break; - case DMA_XMT_POLL_DEMAND: - xgmac_enet_send(s); - break; - case DMA_STATUS: - s->regs[DMA_STATUS] = s->regs[DMA_STATUS] & ~value; - break; - case DMA_RCV_BASE_ADDR: - s->regs[DMA_RCV_BASE_ADDR] = s->regs[DMA_CUR_RX_DESC_ADDR] = value; - break; - case DMA_TX_BASE_ADDR: - s->regs[DMA_TX_BASE_ADDR] = s->regs[DMA_CUR_TX_DESC_ADDR] = value; - break; - default: - if (addr < ARRAY_SIZE(s->regs)) { - s->regs[addr] = value; - } - break; - } - enet_update_irq(s); -} - -static const MemoryRegionOps enet_mem_ops = { - .read = enet_read, - .write = enet_write, - .endianness = DEVICE_LITTLE_ENDIAN, -}; - -static int eth_can_rx(XgmacState *s) -{ - /* RX enabled? */ - return s->regs[DMA_CONTROL] & DMA_CONTROL_SR; -} - -static ssize_t eth_rx(NetClientState *nc, const uint8_t *buf, size_t size) -{ - XgmacState *s = qemu_get_nic_opaque(nc); - static const unsigned char sa_bcast[6] = {0xff, 0xff, 0xff, - 0xff, 0xff, 0xff}; - int unicast, broadcast, multicast; - struct desc bd; - ssize_t ret; - - if (!eth_can_rx(s)) { - return -1; - } - unicast = ~buf[0] & 0x1; - broadcast = memcmp(buf, sa_bcast, 6) == 0; - multicast = !unicast && !broadcast; - if (size < 12) { - s->regs[DMA_STATUS] |= DMA_STATUS_RI | DMA_STATUS_NIS; - ret = -1; - goto out; - } - - xgmac_read_desc(s, &bd, 1); - if ((bd.ctl_stat & 0x80000000) == 0) { - s->regs[DMA_STATUS] |= DMA_STATUS_RU | DMA_STATUS_AIS; - ret = size; - goto out; - } - - cpu_physical_memory_write(bd.buffer1_addr, buf, size); - - /* Add in the 4 bytes for crc (the real hw returns length incl crc) */ - size += 4; - bd.ctl_stat = (size << 16) | 0x300; - xgmac_write_desc(s, &bd, 1); - - s->stats.rx_bytes += size; - s->stats.rx++; - if (multicast) { - s->stats.rx_mcast++; - } else if (broadcast) { - s->stats.rx_bcast++; - } - - s->regs[DMA_STATUS] |= DMA_STATUS_RI | DMA_STATUS_NIS; - ret = size; - -out: - enet_update_irq(s); - return ret; -} - -static NetClientInfo net_xgmac_enet_info = { - .type = NET_CLIENT_DRIVER_NIC, - .size = sizeof(NICState), - .receive = eth_rx, -}; - -static void xgmac_enet_realize(DeviceState *dev, Error **errp) -{ - SysBusDevice *sbd = SYS_BUS_DEVICE(dev); - XgmacState *s = XGMAC(dev); - - memory_region_init_io(&s->iomem, OBJECT(s), &enet_mem_ops, s, - "xgmac", 0x1000); - sysbus_init_mmio(sbd, &s->iomem); - sysbus_init_irq(sbd, &s->sbd_irq); - sysbus_init_irq(sbd, &s->pmt_irq); - sysbus_init_irq(sbd, &s->mci_irq); - - qemu_macaddr_default_if_unset(&s->conf.macaddr); - s->nic = qemu_new_nic(&net_xgmac_enet_info, &s->conf, - object_get_typename(OBJECT(dev)), dev->id, - &dev->mem_reentrancy_guard, s); - qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a); - - s->regs[XGMAC_ADDR_HIGH(0)] = (s->conf.macaddr.a[5] << 8) | - s->conf.macaddr.a[4]; - s->regs[XGMAC_ADDR_LOW(0)] = (s->conf.macaddr.a[3] << 24) | - (s->conf.macaddr.a[2] << 16) | - (s->conf.macaddr.a[1] << 8) | - s->conf.macaddr.a[0]; -} - -static const Property xgmac_properties[] = { - DEFINE_NIC_PROPERTIES(XgmacState, conf), -}; - -static void xgmac_enet_class_init(ObjectClass *klass, const void *data) -{ - DeviceClass *dc = DEVICE_CLASS(klass); - - dc->realize = xgmac_enet_realize; - dc->vmsd = &vmstate_xgmac; - device_class_set_props(dc, xgmac_properties); -} - -static const TypeInfo xgmac_enet_info = { - .name = TYPE_XGMAC, - .parent = TYPE_SYS_BUS_DEVICE, - .instance_size = sizeof(XgmacState), - .class_init = xgmac_enet_class_init, -}; - -static void xgmac_enet_register_types(void) -{ - type_register_static(&xgmac_enet_info); -} - -type_init(xgmac_enet_register_types) diff --git a/hw/net/xilinx_axienet.c b/hw/net/xilinx_axienet.c index d85f8bb23fe4..970732b162ba 100644 --- a/hw/net/xilinx_axienet.c +++ b/hw/net/xilinx_axienet.c @@ -141,6 +141,10 @@ tdk_write(struct PHY *phy, unsigned int req, unsigned int data) regnum = req & 0x1f; DPHY(qemu_log("%s reg[%d] = %x\n", __func__, regnum, data)); switch (regnum) { + case 2: + case 3: + /* Writes to PHY Identification registers are disallowed */ + break; default: phy->regs[regnum] = data; break; diff --git a/hw/net/xilinx_ethlite.c b/hw/net/xilinx_ethlite.c index ba3acd4c77cb..7ea194475f17 100644 --- a/hw/net/xilinx_ethlite.c +++ b/hw/net/xilinx_ethlite.c @@ -162,9 +162,15 @@ static void port_tx_write(void *opaque, hwaddr addr, uint64_t value, break; case TX_CTRL: if ((value & (CTRL_P | CTRL_S)) == CTRL_S) { - qemu_send_packet(qemu_get_queue(s->nic), - txbuf_ptr(s, port_index), - s->port[port_index].reg.tx_len); + uint32_t tx_size = s->port[port_index].reg.tx_len; + + if (tx_size >= BUFSZ_MAX) { + trace_ethlite_pkt_tx_size_too_big(tx_size); + } else { + qemu_send_packet(qemu_get_queue(s->nic), + txbuf_ptr(s, port_index), + tx_size); + } if (s->port[port_index].reg.tx_ctrl & CTRL_I) { eth_pulse_irq(s); } diff --git a/hw/nitro/Kconfig b/hw/nitro/Kconfig new file mode 100644 index 000000000000..cfae85920a0d --- /dev/null +++ b/hw/nitro/Kconfig @@ -0,0 +1,18 @@ +config NITRO_VSOCK_BUS + bool + +config NITRO_SERIAL_VSOCK + bool + depends on NITRO_VSOCK_BUS + +config NITRO_HEARTBEAT + bool + depends on NITRO_VSOCK_BUS + +config NITRO_MACHINE + bool + default y + depends on NITRO + select NITRO_VSOCK_BUS + select NITRO_HEARTBEAT + select NITRO_SERIAL_VSOCK diff --git a/hw/nitro/heartbeat.c b/hw/nitro/heartbeat.c new file mode 100644 index 000000000000..dc4132326678 --- /dev/null +++ b/hw/nitro/heartbeat.c @@ -0,0 +1,115 @@ +/* + * Nitro Enclave Heartbeat device + * + * Copyright © 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Authors: + * Alexander Graf + * + * The Nitro Enclave init process sends a heartbeat byte (0xB7) to + * CID 3 (parent) port 9000 on boot to signal it reached initramfs. + * The parent must accept the connection, read the byte, and echo it + * back. If the enclave init cannot reach the listener, it exits. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "chardev/char.h" +#include "chardev/char-fe.h" +#include "hw/nitro/heartbeat.h" +#include "trace.h" + +#define HEARTBEAT_PORT 9000 +#define VMADDR_CID_ANY_STR "4294967295" + +static int nitro_heartbeat_can_read(void *opaque) +{ + NitroHeartbeatState *s = opaque; + + /* One-shot protocol: stop reading after the first heartbeat */ + return s->done ? 0 : 1; +} + +static void nitro_heartbeat_read(void *opaque, const uint8_t *buf, int size) +{ + NitroHeartbeatState *s = opaque; + + if (s->done || size < 1) { + return; + } + + /* Echo the heartbeat byte back and disconnect */ + qemu_chr_fe_write_all(&s->vsock, buf, 1); + s->done = true; + qemu_chr_fe_deinit(&s->vsock, true); + + trace_nitro_heartbeat_done(); +} + +static void nitro_heartbeat_event(void *opaque, QEMUChrEvent event) +{ + trace_nitro_heartbeat_event(event); +} + +static void nitro_heartbeat_realize(DeviceState *dev, Error **errp) +{ + NitroHeartbeatState *s = NITRO_HEARTBEAT(dev); + g_autofree char *chardev_id = NULL; + Chardev *chr; + ChardevBackend *backend; + ChardevSocket *sock; + + chardev_id = g_strdup_printf("nitro-heartbeat"); + + backend = g_new0(ChardevBackend, 1); + backend->type = CHARDEV_BACKEND_KIND_SOCKET; + sock = backend->u.socket.data = g_new0(ChardevSocket, 1); + sock->addr = g_new0(SocketAddressLegacy, 1); + sock->addr->type = SOCKET_ADDRESS_TYPE_VSOCK; + sock->addr->u.vsock.data = g_new0(VsockSocketAddress, 1); + sock->addr->u.vsock.data->cid = g_strdup(VMADDR_CID_ANY_STR); + sock->addr->u.vsock.data->port = g_strdup_printf("%u", HEARTBEAT_PORT); + sock->server = true; + sock->has_server = true; + sock->wait = false; + sock->has_wait = true; + + chr = qemu_chardev_new(chardev_id, TYPE_CHARDEV_SOCKET, + backend, NULL, errp); + if (!chr) { + return; + } + + if (!qemu_chr_fe_init(&s->vsock, chr, errp)) { + return; + } + + qemu_chr_fe_set_handlers(&s->vsock, + nitro_heartbeat_can_read, + nitro_heartbeat_read, + nitro_heartbeat_event, + NULL, s, NULL, true); +} + +static void nitro_heartbeat_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = nitro_heartbeat_realize; +} + +static const TypeInfo nitro_heartbeat_info = { + .name = TYPE_NITRO_HEARTBEAT, + .parent = TYPE_NITRO_VSOCK_DEVICE, + .instance_size = sizeof(NitroHeartbeatState), + .class_init = nitro_heartbeat_class_init, +}; + +static void nitro_heartbeat_register(void) +{ + type_register_static(&nitro_heartbeat_info); +} + +type_init(nitro_heartbeat_register); diff --git a/hw/nitro/machine.c b/hw/nitro/machine.c new file mode 100644 index 000000000000..8849959359cb --- /dev/null +++ b/hw/nitro/machine.c @@ -0,0 +1,277 @@ +/* + * Nitro Enclaves (accel) machine + * + * Copyright © 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Authors: + * Alexander Graf + * + * Nitro Enclaves machine model for -accel nitro. This machine behaves + * like the nitro-enclave machine, but uses the real Nitro Enclaves + * backend to launch the virtual machine. It requires use of the -accel + * nitro. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include "qom/object_interfaces.h" +#include "chardev/char.h" +#include "hw/core/boards.h" +#include "hw/core/cpu.h" +#include "hw/core/qdev-properties-system.h" +#include "hw/nitro/heartbeat.h" +#include "hw/nitro/machine.h" +#include "hw/nitro/nitro-vsock-bus.h" +#include "hw/nitro/serial-vsock.h" +#include "system/address-spaces.h" +#include "system/hostmem.h" +#include "system/system.h" +#include "system/nitro-accel.h" +#include "qemu/accel.h" +#include "hw/arm/machines-qom.h" +#include "hw/core/eif.h" +#include /* for crc32 */ + +#define EIF_LOAD_ADDR (8 * 1024 * 1024) + +static bool is_eif(char *eif, gsize len) +{ + const char eif_magic[] = EIF_MAGIC; + + return len >= sizeof(eif_magic) && + !memcmp(eif, eif_magic, sizeof(eif_magic)); +} + +static void build_eif_section(EifHeader *hdr, GByteArray *buf, uint16_t type, + const char *data, uint64_t size) +{ + uint16_t section = be16_to_cpu(hdr->section_cnt); + EifSectionHeader shdr = { + .section_type = cpu_to_be16(type), + .flags = 0, + .section_size = cpu_to_be64(size), + }; + + hdr->section_offsets[section] = cpu_to_be64(buf->len); + hdr->section_sizes[section] = cpu_to_be64(size); + + g_byte_array_append(buf, (const uint8_t *)&shdr, sizeof(shdr)); + if (size) { + g_byte_array_append(buf, (const uint8_t *)data, size); + } + + hdr->section_cnt = cpu_to_be16(section + 1); +} + +/* + * Nitro Enclaves only support loading EIF files. When the user provides + * a Linux kernel, initrd and cmdline, convert them into EIF format. + */ +static char *build_eif(const char *kernel_data, gsize kernel_size, + const char *initrd_path, const char *cmdline, + gsize *out_size, Error **errp) +{ + g_autofree char *initrd_data = NULL; + static const char metadata[] = "{}"; + size_t metadata_len = sizeof(metadata) - 1; + gsize initrd_size = 0; + GByteArray *buf; + EifHeader hdr; + uint32_t crc = 0; + size_t cmdline_len; + + if (initrd_path) { + if (!g_file_get_contents(initrd_path, &initrd_data, + &initrd_size, NULL)) { + error_setg(errp, "Failed to read initrd '%s'", initrd_path); + return NULL; + } + } + + buf = g_byte_array_new(); + + cmdline_len = cmdline ? strlen(cmdline) : 0; + + hdr = (EifHeader) { + .magic = EIF_MAGIC, + .version = cpu_to_be16(4), + .flags = cpu_to_be16(target_aarch64() ? EIF_HDR_ARCH_ARM64 : 0), + }; + + g_byte_array_append(buf, (const uint8_t *)&hdr, sizeof(hdr)); + + /* Kernel */ + build_eif_section(&hdr, buf, EIF_SECTION_KERNEL, kernel_data, kernel_size); + + /* Command line */ + build_eif_section(&hdr, buf, EIF_SECTION_CMDLINE, cmdline, cmdline_len); + + /* Initramfs */ + build_eif_section(&hdr, buf, EIF_SECTION_RAMDISK, initrd_data, initrd_size); + + /* Metadata */ + build_eif_section(&hdr, buf, EIF_SECTION_METADATA, metadata, metadata_len); + + /* + * Patch the header into the buffer first (with real section offsets + * and sizes), then compute CRC over everything except the CRC field. + */ + memcpy(buf->data, &hdr, sizeof(hdr)); + crc = crc32(crc, buf->data, offsetof(EifHeader, eif_crc32)); + crc = crc32(crc, &buf->data[sizeof(hdr)], buf->len - sizeof(hdr)); + + /* Finally write the CRC into the in-buffer header */ + ((EifHeader *)buf->data)->eif_crc32 = cpu_to_be32(crc); + + *out_size = buf->len; + return (char *)g_byte_array_free(buf, false); +} + +static void nitro_machine_init(MachineState *machine) +{ + const char *eif_path = machine->kernel_filename; + const char *cpu_type = machine->cpu_type; + g_autofree char *eif_data = NULL; + gsize eif_size; + + if (!nitro_enabled()) { + error_report("The 'nitro' machine requires -accel nitro"); + exit(1); + } + + if (!cpu_type) { + ObjectClass *oc = cpu_class_by_name(target_cpu_type(), "host"); + + if (!oc) { + error_report("nitro: no 'host' CPU available"); + exit(1); + } + cpu_type = object_class_get_name(oc); + } + + if (!eif_path) { + error_report("nitro: -kernel is required"); + exit(1); + } + + /* Expose memory as normal QEMU RAM. Needs to be huge page backed. */ + memory_region_add_subregion(get_system_memory(), 0, machine->ram); + + /* + * Load EIF (-kernel) as raw blob at the EIF_LOAD_ADDR into guest RAM. + * The Nitro Hypervisor will extract its contents and bootstrap the + * Enclave from it. + */ + if (!g_file_get_contents(eif_path, &eif_data, &eif_size, NULL)) { + error_report("nitro: failed to read EIF '%s'", eif_path); + exit(1); + } + + if (!is_eif(eif_data, eif_size)) { + char *kernel_data = eif_data; + gsize kernel_size = eif_size; + Error *err = NULL; + + /* + * The user gave us a non-EIF kernel, likely a Linux kernel image. + * Assemble an EIF file from it, the -initrd and the -append arguments, + * so that users can perform a natural direct kernel boot. + */ + eif_data = build_eif(kernel_data, kernel_size, machine->initrd_filename, + machine->kernel_cmdline, &eif_size, &err); + if (!eif_data) { + error_report_err(err); + exit(1); + } + + g_free(kernel_data); + } + + address_space_write(&address_space_memory, EIF_LOAD_ADDR, + MEMTXATTRS_UNSPECIFIED, eif_data, eif_size); + + if (defaults_enabled()) { + NitroVsockBridge *bridge = nitro_vsock_bridge_create(); + + /* Nitro Enclaves require a heartbeat device. Provide one. */ + qdev_realize(qdev_new(TYPE_NITRO_HEARTBEAT), + BUS(&bridge->bus), &error_fatal); + + /* + * In debug mode, Nitro Enclaves expose the guest's serial output via + * vsock. When the accel is in debug mode, wire the vsock serial to + * the machine's serial port so that -nographic automatically works + */ + if (object_property_get_bool(OBJECT(current_accel()), "debug-mode", NULL)) { + Chardev *chr = serial_hd(0); + + if (chr) { + DeviceState *dev = qdev_new(TYPE_NITRO_SERIAL_VSOCK); + + qdev_prop_set_chr(dev, "chardev", chr); + qdev_realize(dev, BUS(&bridge->bus), &error_fatal); + } + } + } +} + +static bool nitro_create_memfd_backend(MachineState *ms, const char *path, + Error **errp) +{ + MachineClass *mc = MACHINE_GET_CLASS(ms); + Object *root = object_get_objects_root(); + Object *obj; + bool r = false; + + obj = object_new(TYPE_MEMORY_BACKEND_MEMFD); + + /* Nitro Enclaves require huge page backing */ + if (!object_property_set_int(obj, "size", ms->ram_size, errp) || + !object_property_set_bool(obj, "hugetlb", true, errp)) { + goto out; + } + + object_property_add_child(root, mc->default_ram_id, obj); + + if (!user_creatable_complete(USER_CREATABLE(obj), errp)) { + goto out; + } + r = object_property_set_link(OBJECT(ms), "memory-backend", obj, errp); + +out: + object_unref(obj); + return r; +} + +static void nitro_machine_class_init(ObjectClass *oc, const void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + + mc->desc = "Nitro Enclave"; + mc->init = nitro_machine_init; + mc->create_default_memdev = nitro_create_memfd_backend; + mc->default_ram_id = "ram"; + mc->max_cpus = 4096; +} + +static const TypeInfo nitro_machine_info = { + .name = TYPE_NITRO_MACHINE, + .parent = TYPE_MACHINE, + .instance_size = sizeof(NitroMachineState), + .class_init = nitro_machine_class_init, + .interfaces = (const InterfaceInfo[]) { + /* x86_64 and aarch64 only */ + { TYPE_TARGET_AARCH64_MACHINE }, + { } + }, +}; + +static void nitro_machine_register(void) +{ + type_register_static(&nitro_machine_info); +} + +type_init(nitro_machine_register); diff --git a/hw/nitro/meson.build b/hw/nitro/meson.build new file mode 100644 index 000000000000..b9bd0d43002a --- /dev/null +++ b/hw/nitro/meson.build @@ -0,0 +1,4 @@ +system_ss.add(when: 'CONFIG_NITRO_VSOCK_BUS', if_true: files('nitro-vsock-bus.c')) +system_ss.add(when: 'CONFIG_NITRO_SERIAL_VSOCK', if_true: files('serial-vsock.c')) +system_ss.add(when: 'CONFIG_NITRO_HEARTBEAT', if_true: files('heartbeat.c')) +system_ss.add(when: 'CONFIG_NITRO_MACHINE', if_true: [files('machine.c'), zlib]) diff --git a/hw/nitro/nitro-vsock-bus.c b/hw/nitro/nitro-vsock-bus.c new file mode 100644 index 000000000000..eed29df512e1 --- /dev/null +++ b/hw/nitro/nitro-vsock-bus.c @@ -0,0 +1,98 @@ +/* + * Nitro Enclave Vsock Bus + * + * Copyright © 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Authors: + * Alexander Graf + * + * A bus for Nitro Enclave vsock devices. In Nitro Enclaves, communication + * between parent and enclave/hypervisor happens almost exclusively through + * vsock. The nitro-vsock-bus models this dependency in QEMU, which allows + * devices in this bus to implement individual services on top of vsock. + * + * The nitro accel advertises the Enclave's CID to the bus by calling + * nitro_vsock_bridge_start_enclave() on the bridge device as soon as it + * knows the CID. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "monitor/qdev.h" +#include "hw/core/sysbus.h" +#include "hw/nitro/nitro-vsock-bus.h" + +void nitro_vsock_bridge_start_enclave(NitroVsockBridge *bridge, + uint32_t enclave_cid, Error **errp) +{ + ERRP_GUARD(); + BusState *qbus = BUS(&bridge->bus); + BusChild *kid; + + bridge->enclave_cid = enclave_cid; + + QTAILQ_FOREACH(kid, &qbus->children, sibling) { + NitroVsockDevice *ndev = NITRO_VSOCK_DEVICE(kid->child); + NitroVsockDeviceClass *ndc = NITRO_VSOCK_DEVICE_GET_CLASS(ndev); + + if (ndc->enclave_started) { + ndc->enclave_started(ndev, enclave_cid, errp); + if (*errp) { + return; + } + } + } +} + +NitroVsockBridge *nitro_vsock_bridge_create(void) +{ + DeviceState *dev = qdev_new(TYPE_NITRO_VSOCK_BRIDGE); + + qdev_set_id(dev, g_strdup("nitro-vsock"), &error_fatal); + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + + return NITRO_VSOCK_BRIDGE(dev); +} + +static void nitro_vsock_bridge_init(Object *obj) +{ + NitroVsockBridge *s = NITRO_VSOCK_BRIDGE(obj); + + qbus_init(&s->bus, sizeof(s->bus), TYPE_NITRO_VSOCK_BUS, + DEVICE(s), "nitro-vsock"); + object_property_add_uint32_ptr(obj, "enclave-cid", + &s->enclave_cid, OBJ_PROP_FLAG_READ); +} + +static void nitro_vsock_device_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->bus_type = TYPE_NITRO_VSOCK_BUS; +} + +static const TypeInfo nitro_vsock_bus_types[] = { + { + .name = TYPE_NITRO_VSOCK_BUS, + .parent = TYPE_BUS, + .instance_size = sizeof(NitroVsockBus), + }, + { + .name = TYPE_NITRO_VSOCK_BRIDGE, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NitroVsockBridge), + .instance_init = nitro_vsock_bridge_init, + }, + { + .name = TYPE_NITRO_VSOCK_DEVICE, + .parent = TYPE_DEVICE, + .instance_size = sizeof(NitroVsockDevice), + .class_size = sizeof(NitroVsockDeviceClass), + .class_init = nitro_vsock_device_class_init, + .abstract = true, + }, +}; + +DEFINE_TYPES(nitro_vsock_bus_types); diff --git a/hw/nitro/serial-vsock.c b/hw/nitro/serial-vsock.c new file mode 100644 index 000000000000..1d56c338049d --- /dev/null +++ b/hw/nitro/serial-vsock.c @@ -0,0 +1,123 @@ +/* + * Nitro Enclave Vsock Serial + * + * Copyright © 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Authors: + * Alexander Graf + * + * With Nitro Enclaves in debug mode, the Nitro Hypervisor provides a vsock + * port that the parent can connect to to receive serial console output of + * the Enclave. This driver implements short-circuit logic to establish the + * vsock connection to that port and feed its data into a chardev, so that + * a machine model can use it as serial device. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include "chardev/char.h" +#include "chardev/char-fe.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/qdev-properties-system.h" +#include "hw/nitro/serial-vsock.h" +#include "trace.h" + +#define CONSOLE_PORT_START 10000 +#define VMADDR_CID_HYPERVISOR_STR "0" + +static int nitro_serial_vsock_can_read(void *opaque) +{ + NitroSerialVsockState *s = opaque; + + /* Refuse vsock input until the output backend is ready */ + return qemu_chr_fe_backend_open(&s->output) ? 4096 : 0; +} + +static void nitro_serial_vsock_read(void *opaque, const uint8_t *buf, int size) +{ + NitroSerialVsockState *s = opaque; + + /* Forward all vsock data to the output chardev */ + qemu_chr_fe_write_all(&s->output, buf, size); +} + +static void nitro_serial_vsock_event(void *opaque, QEMUChrEvent event) +{ + /* No need to action on connect/disconnect events, but trace for debug */ + trace_nitro_serial_vsock_event(event); +} + +static void nitro_serial_vsock_enclave_started(NitroVsockDevice *dev, + uint32_t enclave_cid, + Error **errp) +{ + NitroSerialVsockState *s = NITRO_SERIAL_VSOCK(dev); + uint32_t port = enclave_cid + CONSOLE_PORT_START; + g_autofree char *chardev_id = NULL; + Chardev *chr; + ChardevBackend *backend; + ChardevSocket *sock; + + /* + * We know the Enclave CID to connect to now. Create a vsock + * client chardev that connects to the Enclave's console. + */ + chardev_id = g_strdup_printf("nitro-console-%u", enclave_cid); + + backend = g_new0(ChardevBackend, 1); + backend->type = CHARDEV_BACKEND_KIND_SOCKET; + sock = backend->u.socket.data = g_new0(ChardevSocket, 1); + sock->addr = g_new0(SocketAddressLegacy, 1); + sock->addr->type = SOCKET_ADDRESS_TYPE_VSOCK; + sock->addr->u.vsock.data = g_new0(VsockSocketAddress, 1); + sock->addr->u.vsock.data->cid = g_strdup(VMADDR_CID_HYPERVISOR_STR); + sock->addr->u.vsock.data->port = g_strdup_printf("%u", port); + sock->server = false; + sock->has_server = true; + + chr = qemu_chardev_new(chardev_id, TYPE_CHARDEV_SOCKET, + backend, NULL, errp); + if (!chr) { + return; + } + + if (!qemu_chr_fe_init(&s->vsock, chr, errp)) { + return; + } + + qemu_chr_fe_set_handlers(&s->vsock, + nitro_serial_vsock_can_read, + nitro_serial_vsock_read, + nitro_serial_vsock_event, + NULL, s, NULL, true); +} + +static const Property nitro_serial_vsock_props[] = { + DEFINE_PROP_CHR("chardev", NitroSerialVsockState, output), +}; + +static void nitro_serial_vsock_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + NitroVsockDeviceClass *ndc = NITRO_VSOCK_DEVICE_CLASS(oc); + + device_class_set_props(dc, nitro_serial_vsock_props); + ndc->enclave_started = nitro_serial_vsock_enclave_started; +} + +static const TypeInfo nitro_serial_vsock_info = { + .name = TYPE_NITRO_SERIAL_VSOCK, + .parent = TYPE_NITRO_VSOCK_DEVICE, + .instance_size = sizeof(NitroSerialVsockState), + .class_init = nitro_serial_vsock_class_init, +}; + +static void nitro_serial_vsock_register(void) +{ + type_register_static(&nitro_serial_vsock_info); +} + +type_init(nitro_serial_vsock_register); diff --git a/hw/nitro/trace-events b/hw/nitro/trace-events new file mode 100644 index 000000000000..311ab78e6994 --- /dev/null +++ b/hw/nitro/trace-events @@ -0,0 +1,8 @@ +# See docs/devel/tracing.rst for syntax documentation. + +# serial-vsock.c +nitro_serial_vsock_event(int event) "event %d" + +# heartbeat.c +nitro_heartbeat_event(int event) "event %d" +nitro_heartbeat_done(void) "enclave heartbeat received" diff --git a/hw/nitro/trace.h b/hw/nitro/trace.h new file mode 100644 index 000000000000..b455d6c17b30 --- /dev/null +++ b/hw/nitro/trace.h @@ -0,0 +1,4 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "trace/trace-hw_nitro.h" diff --git a/hw/nvram/meson.build b/hw/nvram/meson.build index b66f23605b77..263433598758 100644 --- a/hw/nvram/meson.build +++ b/hw/nvram/meson.build @@ -18,8 +18,8 @@ system_ss.add(when: 'CONFIG_XLNX_EFUSE_ZYNQMP', if_true: files( system_ss.add(when: 'CONFIG_XLNX_BBRAM', if_true: files('xlnx-bbram.c')) specific_ss.add(when: 'CONFIG_PSERIES', if_true: files('spapr_nvram.c')) -specific_ss.add(when: 'CONFIG_ACPI', if_true: files('fw_cfg-acpi.c')) +system_ss.add(when: 'CONFIG_ACPI', if_true: files('fw_cfg-acpi.c')) system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files( 'aspeed_otp.c', - )) \ No newline at end of file + )) diff --git a/hw/pci-bridge/cxl_downstream.c b/hw/pci-bridge/cxl_downstream.c index f7b131e67e8e..ec450d1aa09e 100644 --- a/hw/pci-bridge/cxl_downstream.c +++ b/hw/pci-bridge/cxl_downstream.c @@ -13,9 +13,11 @@ #include "hw/pci/msi.h" #include "hw/pci/pcie.h" #include "hw/pci/pcie_port.h" +#include "hw/pci-bridge/cxl_downstream_port.h" #include "hw/core/qdev-properties.h" #include "hw/core/qdev-properties-system.h" #include "hw/cxl/cxl.h" +#include "hw/cxl/cxl_port.h" #include "qapi/error.h" typedef struct CXLDownstreamPort { @@ -24,6 +26,7 @@ typedef struct CXLDownstreamPort { /*< public >*/ CXLComponentState cxl_cstate; + CXLPhyPortPerst perst; } CXLDownstreamPort; #define CXL_DOWNSTREAM_PORT_MSI_OFFSET 0x70 @@ -39,7 +42,7 @@ static void latch_registers(CXLDownstreamPort *dsp) uint32_t *write_msk = dsp->cxl_cstate.crb.cache_mem_regs_write_mask; cxl_component_register_init_common(reg_state, write_msk, - CXL2_DOWNSTREAM_PORT); + CXL2_DOWNSTREAM_PORT, true); } /* TODO: Look at sharing this code across all CXL port types */ @@ -81,6 +84,11 @@ static void cxl_dsp_config_write(PCIDevice *d, uint32_t address, cxl_dsp_dvsec_write_config(d, address, val, len); } +CXLPhyPortPerst *cxl_dsp_get_perst(CXLDownstreamPort *dsp) +{ + return &dsp->perst; +} + static void cxl_dsp_reset(DeviceState *qdev) { PCIDevice *d = PCI_DEVICE(qdev); @@ -92,10 +100,12 @@ static void cxl_dsp_reset(DeviceState *qdev) pci_bridge_reset(qdev); latch_registers(dsp); + cxl_init_physical_port_control(&dsp->perst); } -static void build_dvsecs(CXLComponentState *cxl) +static void build_dvsecs(PCIDevice *d, CXLComponentState *cxl) { + PCIESlot *s = PCIE_SLOT(d); uint8_t *dvsec; dvsec = (uint8_t *)&(CXLDVSECPortExt){ 0 }; @@ -107,7 +117,7 @@ static void build_dvsecs(CXLComponentState *cxl) dvsec = (uint8_t *)&(CXLDVSECPortFlexBus){ .cap = 0x27, /* Cache, IO, Mem, non-MLD */ .ctrl = 0x02, /* IO always enabled */ - .status = 0x26, /* same */ + .status = s->flitmode ? 0x6 : 0x26, /* lack of 68B */ .rcvd_mod_ts_data_phase1 = 0xef, /* WTF? */ }; cxl_component_create_dvsec(cxl, CXL2_DOWNSTREAM_PORT, @@ -182,7 +192,7 @@ static void cxl_dsp_realize(PCIDevice *d, Error **errp) cxl_cstate->dvsec_offset = CXL_DOWNSTREAM_PORT_DVSEC_OFFSET; cxl_cstate->pdev = d; - build_dvsecs(cxl_cstate); + build_dvsecs(d, cxl_cstate); cxl_component_register_block_init(OBJECT(d), cxl_cstate, TYPE_CXL_DSP); pci_register_bar(d, CXL_COMPONENT_REG_BAR_IDX, PCI_BASE_ADDRESS_SPACE_MEMORY | @@ -217,6 +227,7 @@ static const Property cxl_dsp_props[] = { speed, PCIE_LINK_SPEED_64), DEFINE_PROP_PCIE_LINK_WIDTH("x-width", PCIESlot, width, PCIE_LINK_WIDTH_16), + DEFINE_PROP_BOOL("x-256b-flit", PCIESlot, flitmode, true), }; static void cxl_dsp_class_init(ObjectClass *oc, const void *data) diff --git a/hw/pci-bridge/cxl_root_port.c b/hw/pci-bridge/cxl_root_port.c index 197d3148d201..e2093ac39ee6 100644 --- a/hw/pci-bridge/cxl_root_port.c +++ b/hw/pci-bridge/cxl_root_port.c @@ -101,11 +101,13 @@ static void latch_registers(CXLRootPort *crp) uint32_t *reg_state = crp->cxl_cstate.crb.cache_mem_registers; uint32_t *write_msk = crp->cxl_cstate.crb.cache_mem_regs_write_mask; - cxl_component_register_init_common(reg_state, write_msk, CXL2_ROOT_PORT); + cxl_component_register_init_common(reg_state, write_msk, CXL2_ROOT_PORT, + true); } -static void build_dvsecs(CXLComponentState *cxl) +static void build_dvsecs(PCIDevice *d, CXLComponentState *cxl) { + PCIESlot *s = PCIE_SLOT(d); uint8_t *dvsec; dvsec = (uint8_t *)&(CXLDVSECPortExt){ 0 }; @@ -126,7 +128,7 @@ static void build_dvsecs(CXLComponentState *cxl) dvsec = (uint8_t *)&(CXLDVSECPortFlexBus){ .cap = 0x26, /* IO, Mem, non-MLD */ .ctrl = 0x2, - .status = 0x26, /* same */ + .status = s->flitmode ? 0x6 : 0x26, /* lack of 68B */ .rcvd_mod_ts_data_phase1 = 0xef, }; cxl_component_create_dvsec(cxl, CXL2_ROOT_PORT, @@ -176,7 +178,7 @@ static void cxl_rp_realize(DeviceState *dev, Error **errp) cxl_cstate->dvsec_offset = CXL_ROOT_PORT_DVSEC_OFFSET; cxl_cstate->pdev = pci_dev; - build_dvsecs(cxl_cstate); + build_dvsecs(pci_dev, cxl_cstate); cxl_component_register_block_init(OBJECT(pci_dev), cxl_cstate, TYPE_CXL_ROOT_PORT); @@ -211,6 +213,7 @@ static const Property gen_rp_props[] = { speed, PCIE_LINK_SPEED_64), DEFINE_PROP_PCIE_LINK_WIDTH("x-width", PCIESlot, width, PCIE_LINK_WIDTH_32), + DEFINE_PROP_BOOL("x-256b-flit", PCIESlot, flitmode, true), }; static void cxl_rp_dvsec_write_config(PCIDevice *dev, uint32_t addr, diff --git a/hw/pci-bridge/cxl_upstream.c b/hw/pci-bridge/cxl_upstream.c index 6d708fadc253..b6281cbd4cb0 100644 --- a/hw/pci-bridge/cxl_upstream.c +++ b/hw/pci-bridge/cxl_upstream.c @@ -90,7 +90,7 @@ static void latch_registers(CXLUpstreamPort *usp) uint32_t *write_msk = usp->cxl_cstate.crb.cache_mem_regs_write_mask; cxl_component_register_init_common(reg_state, write_msk, - CXL2_UPSTREAM_PORT); + CXL2_UPSTREAM_PORT, usp->flitmode); ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, TARGET_COUNT, 8); } @@ -101,28 +101,30 @@ static void cxl_usp_reset(DeviceState *qdev) pci_bridge_reset(qdev); pcie_cap_deverr_reset(d); - pcie_cap_fill_link_ep_usp(d, usp->width, usp->speed); + pcie_cap_fill_link_ep_usp(d, usp->width, usp->speed, usp->flitmode); latch_registers(usp); + cxl_init_physical_port_control(&usp->perst); } -static void build_dvsecs(CXLComponentState *cxl) +static void build_dvsecs(CXLUpstreamPort *usp) { + CXLComponentState *cxl_cstate = &usp->cxl_cstate; uint8_t *dvsec; dvsec = (uint8_t *)&(CXLDVSECPortExt){ .status = 0x1, /* Port Power Management Init Complete */ }; - cxl_component_create_dvsec(cxl, CXL2_UPSTREAM_PORT, + cxl_component_create_dvsec(cxl_cstate, CXL2_UPSTREAM_PORT, EXTENSIONS_PORT_DVSEC_LENGTH, EXTENSIONS_PORT_DVSEC, EXTENSIONS_PORT_DVSEC_REVID, dvsec); dvsec = (uint8_t *)&(CXLDVSECPortFlexBus){ .cap = 0x27, /* Cache, IO, Mem, non-MLD */ .ctrl = 0x27, /* Cache, IO, Mem */ - .status = 0x26, /* same */ + .status = usp->flitmode ? 0x6 : 0x26, /* lack of 68B */ .rcvd_mod_ts_data_phase1 = 0xef, /* WTF? */ }; - cxl_component_create_dvsec(cxl, CXL2_UPSTREAM_PORT, + cxl_component_create_dvsec(cxl_cstate, CXL2_UPSTREAM_PORT, PCIE_CXL3_FLEXBUS_PORT_DVSEC_LENGTH, PCIE_FLEXBUS_PORT_DVSEC, PCIE_CXL3_FLEXBUS_PORT_DVSEC_REVID, dvsec); @@ -132,7 +134,7 @@ static void build_dvsecs(CXLComponentState *cxl) .reg0_base_lo = RBI_COMPONENT_REG | CXL_COMPONENT_REG_BAR_IDX, .reg0_base_hi = 0, }; - cxl_component_create_dvsec(cxl, CXL2_UPSTREAM_PORT, + cxl_component_create_dvsec(cxl_cstate, CXL2_UPSTREAM_PORT, REG_LOC_DVSEC_LENGTH, REG_LOC_DVSEC, REG_LOC_DVSEC_REVID, dvsec); } @@ -327,7 +329,7 @@ static void cxl_usp_realize(PCIDevice *d, Error **errp) } cxl_cstate->dvsec_offset = CXL_UPSTREAM_PORT_DVSEC_OFFSET; cxl_cstate->pdev = d; - build_dvsecs(cxl_cstate); + build_dvsecs(usp); cxl_component_register_block_init(OBJECT(d), cxl_cstate, TYPE_CXL_USP); pci_register_bar(d, CXL_COMPONENT_REG_BAR_IDX, PCI_BASE_ADDRESS_SPACE_MEMORY | @@ -369,6 +371,7 @@ static const Property cxl_upstream_props[] = { speed, PCIE_LINK_SPEED_32), DEFINE_PROP_PCIE_LINK_WIDTH("x-width", CXLUpstreamPort, width, PCIE_LINK_WIDTH_16), + DEFINE_PROP_BOOL("x-256b-flit", CXLUpstreamPort, flitmode, false), }; static void cxl_upstream_class_init(ObjectClass *oc, const void *data) diff --git a/hw/pci-bridge/gen_pcie_root_port.c b/hw/pci-bridge/gen_pcie_root_port.c index 2f7257d166ae..5434d693d921 100644 --- a/hw/pci-bridge/gen_pcie_root_port.c +++ b/hw/pci-bridge/gen_pcie_root_port.c @@ -35,8 +35,6 @@ struct GenPCIERootPort { PCIESlot parent_obj; /*< public >*/ - bool migrate_msix; - /* additional resources to reserve */ PCIResReserve res_reserve; }; @@ -66,13 +64,6 @@ static void gen_rp_interrupts_uninit(PCIDevice *d) msix_uninit_exclusive_bar(d); } -static bool gen_rp_test_migrate_msix(void *opaque, int version_id) -{ - GenPCIERootPort *rp = opaque; - - return rp->migrate_msix; -} - static void gen_rp_realize(DeviceState *dev, Error **errp) { PCIDevice *d = PCI_DEVICE(dev); @@ -121,16 +112,13 @@ static const VMStateDescription vmstate_rp_dev = { VMSTATE_PCI_DEVICE(parent_obj.parent_obj.parent_obj, PCIESlot), VMSTATE_STRUCT(parent_obj.parent_obj.parent_obj.exp.aer_log, PCIESlot, 0, vmstate_pcie_aer_log, PCIEAERLog), - VMSTATE_MSIX_TEST(parent_obj.parent_obj.parent_obj.parent_obj, - GenPCIERootPort, - gen_rp_test_migrate_msix), + VMSTATE_MSIX(parent_obj.parent_obj.parent_obj.parent_obj, + GenPCIERootPort), VMSTATE_END_OF_LIST() } }; static const Property gen_rp_props[] = { - DEFINE_PROP_BOOL("x-migrate-msix", GenPCIERootPort, - migrate_msix, true), DEFINE_PROP_UINT32("bus-reserve", GenPCIERootPort, res_reserve.bus, -1), DEFINE_PROP_SIZE("io-reserve", GenPCIERootPort, diff --git a/hw/pci-bridge/pci_expander_bridge.c b/hw/pci-bridge/pci_expander_bridge.c index b6e2eb796951..11623a5666f6 100644 --- a/hw/pci-bridge/pci_expander_bridge.c +++ b/hw/pci-bridge/pci_expander_bridge.c @@ -300,7 +300,7 @@ static void pxb_cxl_dev_reset(DeviceState *dev) uint32_t *write_msk = cxl_cstate->crb.cache_mem_regs_write_mask; int dsp_count = 0; - cxl_component_register_init_common(reg_state, write_msk, CXL2_RC); + cxl_component_register_init_common(reg_state, write_msk, CXL2_RC, false); /* * The CXL specification allows for host bridges with no HDM decoders * if they only have a single root port. diff --git a/hw/pci-bridge/pcie_root_port.c b/hw/pci-bridge/pcie_root_port.c index fe3ced56851a..7c3e78010b47 100644 --- a/hw/pci-bridge/pcie_root_port.c +++ b/hw/pci-bridge/pcie_root_port.c @@ -117,7 +117,7 @@ static void rp_realize(PCIDevice *d, Error **errp) pcie_aer_root_init(d); rp_aer_vector_update(d); - if (rpc->acs_offset && !s->disable_acs) { + if (rpc->acs_offset) { pcie_acs_init(d, rpc->acs_offset); } return; @@ -151,7 +151,6 @@ static void rp_exit(PCIDevice *d) static const Property rp_props[] = { DEFINE_PROP_BIT(COMPAT_PROP_PCP, PCIDevice, cap_present, QEMU_PCIE_SLTCAP_PCP_BITNR, true), - DEFINE_PROP_BOOL("disable-acs", PCIESlot, disable_acs, false), }; static void rp_instance_post_init(Object *obj) diff --git a/hw/pci-host/astro.c b/hw/pci-host/astro.c index 00a904277c07..626aa9ce2234 100644 --- a/hw/pci-host/astro.c +++ b/hw/pci-host/astro.c @@ -303,7 +303,7 @@ static IOMMUTLBEntry astro_translate_iommu(IOMMUMemoryRegion *iommu, * language which not-coincidentally matches the PSW.W=0 mapping. */ if (addr <= UINT32_MAX) { - entry = hppa_abs_to_phys_pa2_w0(addr); + entry = hppa_abs_to_phys_pa2_w0(s->phys_addr_bits, addr); } else { entry = addr; } @@ -910,6 +910,10 @@ static void astro_realize(DeviceState *obj, Error **errp) } } +static const Property astro_props[] = { + DEFINE_PROP_UINT8("phys-addr-bits", AstroState, phys_addr_bits, 32), +}; + static void astro_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -922,6 +926,8 @@ static void astro_class_init(ObjectClass *klass, const void *data) * be created without that hardware */ dc->user_creatable = false; + + device_class_set_props(dc, astro_props); } static const TypeInfo astro_chip_info = { diff --git a/hw/pci/meson.build b/hw/pci/meson.build index b9c34b2acfea..a6cbd89c0a38 100644 --- a/hw/pci/meson.build +++ b/hw/pci/meson.build @@ -19,4 +19,4 @@ pci_ss.add(files('pcie_doe.c')) system_ss.add(when: 'CONFIG_PCI_EXPRESS', if_true: files('pcie_port.c', 'pcie_host.c')) system_ss.add_all(when: 'CONFIG_PCI', if_true: pci_ss) -system_ss.add(when: 'CONFIG_PCI', if_false: files('pci-stub.c')) +stub_ss.add(files('pci-stub.c')) diff --git a/hw/pci/pci.c b/hw/pci/pci.c index 90d6d71efdcf..2c3657d00def 100644 --- a/hw/pci/pci.c +++ b/hw/pci/pci.c @@ -36,6 +36,7 @@ #include "migration/qemu-file-types.h" #include "migration/vmstate.h" #include "net/net.h" +#include "system/arch_init.h" #include "system/numa.h" #include "system/runstate.h" #include "system/system.h" @@ -84,8 +85,6 @@ static const Property pci_props[] = { QEMU_PCI_CAP_MULTIFUNCTION_BITNR, false), DEFINE_PROP_BIT("x-pcie-lnksta-dllla", PCIDevice, cap_present, QEMU_PCIE_LNKSTA_DLLLA_BITNR, true), - DEFINE_PROP_BIT("x-pcie-extcap-init", PCIDevice, cap_present, - QEMU_PCIE_EXTCAP_INIT_BITNR, true), DEFINE_PROP_STRING("failover_pair_id", PCIDevice, failover_pair_id), DEFINE_PROP_UINT32("acpi-index", PCIDevice, acpi_index, 0), @@ -2845,6 +2844,43 @@ int pci_qdev_find_device(const char *id, PCIDevice **pdev) return rc; } +static char *pci_qdev_property_get_loadparm(Object *obj, Error **errp) +{ + return g_strdup(PCI_DEVICE(obj)->loadparm); +} + +static void pci_qdev_property_set_loadparm(Object *obj, const char *value, + Error **errp) +{ + void *lp_str; + + if (object_property_get_int(obj, "bootindex", NULL) < 0) { + error_setg(errp, "'loadparm' is only valid for boot devices"); + return; + } + + lp_str = g_malloc0(strlen(value) + 1); + if (!qdev_prop_sanitize_s390x_loadparm(lp_str, value, errp)) { + g_free(lp_str); + return; + } + PCI_DEVICE(obj)->loadparm = lp_str; +} + +void pci_qdev_property_add_specifics(DeviceClass *dc) +{ + ObjectClass *oc = OBJECT_CLASS(dc); + + /* The loadparm property is only supported on s390x */ + if (target_s390x()) { + object_class_property_add_str(oc, "loadparm", + pci_qdev_property_get_loadparm, + pci_qdev_property_set_loadparm); + object_class_property_set_description(oc, "loadparm", + "load parameter (s390x only)"); + } +} + MemoryRegion *pci_address_space(PCIDevice *dev) { return pci_get_bus(dev)->address_space_mem; diff --git a/hw/pci/pcie.c b/hw/pci/pcie.c index 50fc4aa8eb12..4622c75e48cc 100644 --- a/hw/pci/pcie.c +++ b/hw/pci/pcie.c @@ -113,7 +113,7 @@ pcie_cap_v1_fill(PCIDevice *dev, uint8_t port, uint8_t type, uint8_t version) /* Includes setting the target speed default */ static void pcie_cap_fill_lnk(uint8_t *exp_cap, PCIExpLinkWidth width, - PCIExpLinkSpeed speed) + PCIExpLinkSpeed speed, bool flitmode) { /* Clear and fill LNKCAP from what was configured above */ pci_long_test_and_clear_mask(exp_cap + PCI_EXP_LNKCAP, @@ -158,10 +158,15 @@ static void pcie_cap_fill_lnk(uint8_t *exp_cap, PCIExpLinkWidth width, PCI_EXP_LNKCAP2_SLS_64_0GB); } } + + if (flitmode) { + pci_long_test_and_set_mask(exp_cap + PCI_EXP_LNKSTA2, + PCI_EXP_LNKSTA2_FLIT); + } } void pcie_cap_fill_link_ep_usp(PCIDevice *dev, PCIExpLinkWidth width, - PCIExpLinkSpeed speed) + PCIExpLinkSpeed speed, bool flitmode) { uint8_t *exp_cap = dev->config + dev->exp.exp_cap; @@ -175,7 +180,7 @@ void pcie_cap_fill_link_ep_usp(PCIDevice *dev, PCIExpLinkWidth width, QEMU_PCI_EXP_LNKSTA_NLW(width) | QEMU_PCI_EXP_LNKSTA_CLS(speed)); - pcie_cap_fill_lnk(exp_cap, width, speed); + pcie_cap_fill_lnk(exp_cap, width, speed, flitmode); } static void pcie_cap_fill_slot_lnk(PCIDevice *dev) @@ -212,7 +217,7 @@ static void pcie_cap_fill_slot_lnk(PCIDevice *dev) /* the PCI_EXP_LNKSTA_DLLLA will be set in the hotplug function */ } - pcie_cap_fill_lnk(exp_cap, s->width, s->speed); + pcie_cap_fill_lnk(exp_cap, s->width, s->speed, s->flitmode); } int pcie_cap_init(PCIDevice *dev, uint8_t offset, @@ -245,10 +250,8 @@ int pcie_cap_init(PCIDevice *dev, uint8_t offset, pci_set_word(dev->wmask + pos + PCI_EXP_DEVCTL2, PCI_EXP_DEVCTL2_EETLPPB); - if (dev->cap_present & QEMU_PCIE_EXTCAP_INIT) { - /* read-only to behave like a 'NULL' Extended Capability Header */ - pci_set_long(dev->wmask + PCI_CONFIG_SPACE_SIZE, 0); - } + /* read-only to behave like a 'NULL' Extended Capability Header */ + pci_set_long(dev->wmask + PCI_CONFIG_SPACE_SIZE, 0); return pos; } @@ -1175,6 +1178,8 @@ void pcie_sync_bridge_lnk(PCIDevice *bridge_dev) if (!target || !target->exp.exp_cap) { lnksta = lnkcap; } else { + uint16_t lnksta2; + lnksta = target->config_read(target, target->exp.exp_cap + PCI_EXP_LNKSTA, sizeof(lnksta)); @@ -1188,6 +1193,14 @@ void pcie_sync_bridge_lnk(PCIDevice *bridge_dev) lnksta &= ~PCI_EXP_LNKSTA_CLS; lnksta |= lnkcap & PCI_EXP_LNKCAP_SLS; } + + lnksta2 = target->config_read(target, + target->exp.exp_cap + PCI_EXP_LNKSTA2, + sizeof(lnksta2)); + pci_word_test_and_clear_mask(exp_cap + PCI_EXP_LNKSTA2, + PCI_EXP_LNKSTA2_FLIT); + pci_word_test_and_set_mask(exp_cap + PCI_EXP_LNKSTA2, + lnksta2 & PCI_EXP_LNKSTA2_FLIT); } if (!(lnksta & PCI_EXP_LNKSTA_NLW)) { diff --git a/hw/ppc/mac_newworld.c b/hw/ppc/mac_newworld.c index 7275563a1556..3680d96ed39f 100644 --- a/hw/ppc/mac_newworld.c +++ b/hw/ppc/mac_newworld.c @@ -432,6 +432,15 @@ static void ppc_core99_init(MachineState *machine) pci_vga_init(pci_bus); + if (!graphic_width) { + graphic_width = 800; + } + if (!graphic_height) { + graphic_height = 600; + } + if (!graphic_depth) { + graphic_depth = 32; + } if (graphic_depth != 15 && graphic_depth != 32 && graphic_depth != 8) { graphic_depth = 15; } diff --git a/hw/ppc/mac_oldworld.c b/hw/ppc/mac_oldworld.c index e679d3389853..24d9f2e3d5cb 100644 --- a/hw/ppc/mac_oldworld.c +++ b/hw/ppc/mac_oldworld.c @@ -288,6 +288,15 @@ static void ppc_heathrow_init(MachineState *machine) pci_create_simple(pci_bus, -1, "pci-ohci"); } + if (!graphic_width) { + graphic_width = 800; + } + if (!graphic_height) { + graphic_height = 600; + } + if (!graphic_depth) { + graphic_depth = 32; + } if (graphic_depth != 15 && graphic_depth != 32 && graphic_depth != 8) { graphic_depth = 15; } diff --git a/hw/ppc/pnv_occ.c b/hw/ppc/pnv_occ.c index 64cab3e9dc82..b9f69daffca4 100644 --- a/hw/ppc/pnv_occ.c +++ b/hw/ppc/pnv_occ.c @@ -195,6 +195,49 @@ static const TypeInfo pnv_occ_power8_type_info = { #define P9_OCB_OCI_OCCMISC_CLEAR 0x6081 #define P9_OCB_OCI_OCCMISC_OR 0x6082 +/* OCC scratch registers for flag setting */ +#define P9_OCCFLG0 0x60ac +#define P9_OCCFLG7_OR 0x60c3 + +enum ScomType { + SCOM_TYPE_RW = 0, + SCOM_TYPE_WO_CLEAR = 1, + SCOM_TYPE_WO_OR = 2, +}; + +static void rw_occ_flag_regs(PnvOCC *occ, uint32_t offset, bool read, + uint64_t *val) +{ + int flag_num; + int flag_type; + + /* + * Each OCCFLG register has SCOM0 - RW, SCOM1 - WO_CLEAR, SCOM2 - WO_OR + * hence divide by 3 to get flag index and mod 3 to get SCOM type. + */ + flag_num = (offset - P9_OCCFLG0) / 3; + flag_type = (offset - P9_OCCFLG0) % 3; + + if (read) { + if (flag_type) { + qemu_log_mask(LOG_GUEST_ERROR, "OCC: Write only register: Ox%" + PRIx32 "\n", offset); + return; + } + *val = occ->occflags[flag_num]; + } else { + switch (flag_type) { + case SCOM_TYPE_RW: + occ->occflags[flag_num] = *val; + break; + case SCOM_TYPE_WO_CLEAR: + occ->occflags[flag_num] &= ~(*val); + break; + case SCOM_TYPE_WO_OR: + occ->occflags[flag_num] |= *val; + } + } +} static uint64_t pnv_occ_power9_xscom_read(void *opaque, hwaddr addr, unsigned size) @@ -207,8 +250,11 @@ static uint64_t pnv_occ_power9_xscom_read(void *opaque, hwaddr addr, case P9_OCB_OCI_OCCMISC: val = occ->occmisc; break; + case P9_OCCFLG0 ... P9_OCCFLG7_OR: + rw_occ_flag_regs(occ, offset, 1, &val); + break; default: - qemu_log_mask(LOG_UNIMP, "OCC Unimplemented register: Ox%" + qemu_log_mask(LOG_UNIMP, "OCC Unimplemented register read: Ox%" HWADDR_PRIx "\n", addr >> 3); } return val; @@ -229,9 +275,12 @@ static void pnv_occ_power9_xscom_write(void *opaque, hwaddr addr, break; case P9_OCB_OCI_OCCMISC: pnv_occ_set_misc(occ, val); - break; + break; + case P9_OCCFLG0 ... P9_OCCFLG7_OR: + rw_occ_flag_regs(occ, offset, 0, &val); + break; default: - qemu_log_mask(LOG_UNIMP, "OCC Unimplemented register: Ox%" + qemu_log_mask(LOG_UNIMP, "OCC Unimplemented register write: Ox%" HWADDR_PRIx "\n", addr >> 3); } } diff --git a/hw/ppc/pnv_xscom.c b/hw/ppc/pnv_xscom.c index 8557b560aebf..dc1ffc6c014f 100644 --- a/hw/ppc/pnv_xscom.c +++ b/hw/ppc/pnv_xscom.c @@ -61,6 +61,8 @@ static uint32_t pnv_xscom_pcba(PnvChip *chip, uint64_t addr) static uint64_t xscom_read_default(PnvChip *chip, uint32_t pcba) { switch (pcba) { + case 0x10005: /* SECURITY SWITCH */ + return 0; case 0xf000f: return PNV_CHIP_GET_CLASS(chip)->chip_cfam_id; case 0x18002: /* ECID2 */ diff --git a/hw/ppc/prep.c b/hw/ppc/prep.c index c4efd1d39084..e973b3409929 100644 --- a/hw/ppc/prep.c +++ b/hw/ppc/prep.c @@ -412,6 +412,15 @@ static void ibm_40p_init(MachineState *machine) fw_cfg_add_i64(fw_cfg, FW_CFG_RAM_SIZE, (uint64_t)machine->ram_size); fw_cfg_add_i16(fw_cfg, FW_CFG_MACHINE_ID, ARCH_PREP); + if (!graphic_width) { + graphic_width = 800; + } + if (!graphic_height) { + graphic_height = 600; + } + if (!graphic_depth) { + graphic_depth = 32; + } fw_cfg_add_i16(fw_cfg, FW_CFG_PPC_WIDTH, graphic_width); fw_cfg_add_i16(fw_cfg, FW_CFG_PPC_HEIGHT, graphic_height); fw_cfg_add_i16(fw_cfg, FW_CFG_PPC_DEPTH, graphic_depth); diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index 274f38785f29..0ab39dfea6b8 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c @@ -1177,6 +1177,15 @@ static void spapr_dt_chosen(SpaprMachineState *spapr, void *fdt, bool reset) if (machine->boot_config.has_menu && machine->boot_config.menu) { _FDT((fdt_setprop_cell(fdt, chosen, "qemu,boot-menu", true))); } + if (!graphic_width) { + graphic_width = 800; + } + if (!graphic_height) { + graphic_height = 600; + } + if (!graphic_depth) { + graphic_depth = 32; + } _FDT(fdt_setprop_cell(fdt, chosen, "qemu,graphic-width", graphic_width)); _FDT(fdt_setprop_cell(fdt, chosen, "qemu,graphic-height", graphic_height)); _FDT(fdt_setprop_cell(fdt, chosen, "qemu,graphic-depth", graphic_depth)); diff --git a/hw/riscv/riscv-iommu-pci.c b/hw/riscv/riscv-iommu-pci.c index 5f7d3592047a..14dd5f3857e1 100644 --- a/hw/riscv/riscv-iommu-pci.c +++ b/hw/riscv/riscv-iommu-pci.c @@ -158,6 +158,9 @@ static void riscv_iommu_pci_init(Object *obj) iommu->icvec_avail_vectors = RISCV_IOMMU_PCI_ICVEC_VECTORS; riscv_iommu_set_cap_igs(iommu, RISCV_IOMMU_CAP_IGS_MSI); + + /* Report maximum physical address size of riscv64 */ + iommu->pas_bits = 56; } static const Property riscv_iommu_pci_properties[] = { diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c index b46b337375aa..98345b1280bd 100644 --- a/hw/riscv/riscv-iommu.c +++ b/hw/riscv/riscv-iommu.c @@ -2453,10 +2453,6 @@ static void riscv_iommu_instance_init(Object *obj) /* Enable translation debug interface */ s->cap = RISCV_IOMMU_CAP_DBG; - /* Report QEMU target physical address space limits */ - s->cap = set_field(s->cap, RISCV_IOMMU_CAP_PAS, - TARGET_PHYS_ADDR_SPACE_BITS); - /* TODO: method to report supported PID bits */ s->pid_bits = 8; /* restricted to size of MemTxAttrs.pid */ s->cap |= RISCV_IOMMU_CAP_PD8; @@ -2487,6 +2483,9 @@ static void riscv_iommu_realize(DeviceState *dev, Error **errp) { RISCVIOMMUState *s = RISCV_IOMMU(dev); + /* Report QEMU target physical address space limits. */ + s->cap = set_field(s->cap, RISCV_IOMMU_CAP_PAS, s->pas_bits); + s->cap |= s->version & RISCV_IOMMU_CAP_VERSION; if (s->enable_msi) { s->cap |= RISCV_IOMMU_CAP_MSI_FLAT | RISCV_IOMMU_CAP_MSI_MRIF; @@ -2645,6 +2644,7 @@ void riscv_iommu_reset(RISCVIOMMUState *s) static const Property riscv_iommu_properties[] = { DEFINE_PROP_UINT32("version", RISCVIOMMUState, version, RISCV_IOMMU_SPEC_DOT_VER), + DEFINE_PROP_UINT32("pas-bits", RISCVIOMMUState, pas_bits, 0), DEFINE_PROP_UINT32("bus", RISCVIOMMUState, bus, 0x0), DEFINE_PROP_UINT32("ioatc-limit", RISCVIOMMUState, iot_limit, LIMIT_CACHE_IOT), diff --git a/hw/riscv/riscv-iommu.h b/hw/riscv/riscv-iommu.h index 2dabd86941ba..2a9f6fccd521 100644 --- a/hw/riscv/riscv-iommu.h +++ b/hw/riscv/riscv-iommu.h @@ -34,6 +34,7 @@ struct RISCVIOMMUState { /*< public >*/ uint32_t version; /* Reported interface version number */ uint32_t pid_bits; /* process identifier width */ + uint32_t pas_bits; /* physical address bits */ uint32_t bus; /* PCI bus mapping for non-root endpoints */ uint64_t cap; /* IOMMU supported capabilities */ diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c index 07e66b39364c..bbce2fb66716 100644 --- a/hw/riscv/virt.c +++ b/hw/riscv/virt.c @@ -1739,6 +1739,13 @@ static void virt_machine_init(MachineState *machine) object_property_set_link(OBJECT(iommu_sys), "irqchip", OBJECT(mmio_irqchip), &error_fatal); + /* + * For riscv64 use a physical address size of 56 bits (44 bit PPN), + * and for riscv32 use 34 bits (22 bit PPN). + */ + object_property_set_uint(OBJECT(iommu_sys), "pas-bits", + riscv_is_32bit(&s->soc[0]) ? 34 : 56, + &error_fatal); sysbus_realize_and_unref(SYS_BUS_DEVICE(iommu_sys), &error_fatal); } diff --git a/hw/rtc/pl031.c b/hw/rtc/pl031.c index b56877c3a208..09d1b15c779a 100644 --- a/hw/rtc/pl031.c +++ b/hw/rtc/pl031.c @@ -278,18 +278,10 @@ static int pl031_tick_offset_post_load(void *opaque, int version_id) return 0; } -static bool pl031_tick_offset_needed(void *opaque) -{ - PL031State *s = opaque; - - return s->migrate_tick_offset; -} - static const VMStateDescription vmstate_pl031_tick_offset = { .name = "pl031/tick-offset", .version_id = 1, .minimum_version_id = 1, - .needed = pl031_tick_offset_needed, .post_load = pl031_tick_offset_post_load, .fields = (const VMStateField[]) { VMSTATE_UINT32(tick_offset, PL031State), @@ -319,25 +311,11 @@ static const VMStateDescription vmstate_pl031 = { } }; -static const Property pl031_properties[] = { - /* - * True to correctly migrate the tick offset of the RTC. False to - * obtain backward migration compatibility with older QEMU versions, - * at the expense of the guest RTC going backwards compared with the - * host RTC when the VM is saved/restored if using -rtc host. - * (Even if set to 'true' older QEMU can migrate forward to newer QEMU; - * 'false' also permits newer QEMU to migrate to older QEMU.) - */ - DEFINE_PROP_BOOL("migrate-tick-offset", - PL031State, migrate_tick_offset, true), -}; - static void pl031_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->vmsd = &vmstate_pl031; - device_class_set_props(dc, pl031_properties); } static const TypeInfo pl031_info = { diff --git a/hw/s390x/ipl.c b/hw/s390x/ipl.c index d34adb55220c..1babcd2b7ddc 100644 --- a/hw/s390x/ipl.c +++ b/hw/s390x/ipl.c @@ -28,6 +28,8 @@ #include "hw/s390x/ebcdic.h" #include "hw/scsi/scsi.h" #include "hw/virtio/virtio-net.h" +#include "hw/virtio/virtio-pci.h" +#include "hw/s390x/s390-pci-bus.h" #include "exec/cpu-common.h" #include "ipl.h" #include "qemu/error-report.h" @@ -340,7 +342,8 @@ static void s390_ipl_set_boot_menu(S390IPLState *ipl) ipl->qipl.boot_menu_timeout = cpu_to_be32(splash_time); } -#define CCW_DEVTYPE_NONE 0x00 +#define S390_DEVTYPE_NONE 0x00 + #define CCW_DEVTYPE_VIRTIO 0x01 #define CCW_DEVTYPE_VIRTIO_NET 0x02 #define CCW_DEVTYPE_SCSI 0x03 @@ -349,7 +352,7 @@ static void s390_ipl_set_boot_menu(S390IPLState *ipl) static CcwDevice *s390_get_ccw_device(DeviceState *dev_st, int *devtype) { CcwDevice *ccw_dev = NULL; - int tmp_dt = CCW_DEVTYPE_NONE; + int tmp_dt = S390_DEVTYPE_NONE; if (dev_st) { VirtIONet *virtio_net_dev = (VirtIONet *) @@ -396,6 +399,31 @@ static CcwDevice *s390_get_ccw_device(DeviceState *dev_st, int *devtype) return ccw_dev; } +#define PCI_DEVTYPE_VIRTIO 0x05 + +static S390PCIBusDevice *s390_get_pci_device(DeviceState *dev_st, int *devtype) +{ + S390PCIBusDevice *pbdev = NULL; + int tmp_dt = S390_DEVTYPE_NONE; + + if (dev_st) { + PCIDevice *pci_dev = (PCIDevice *) + object_dynamic_cast(OBJECT(qdev_get_parent_bus(dev_st)->parent), + TYPE_VIRTIO_PCI); + if (pci_dev) { + pbdev = s390_pci_find_dev_by_pci(s390_get_phb(), pci_dev); + if (pbdev) { + tmp_dt = PCI_DEVTYPE_VIRTIO; + } + } + } + if (devtype) { + *devtype = tmp_dt; + } + + return pbdev; +} + static uint64_t s390_ipl_map_iplb_chain(IplParameterBlock *iplb_chain) { S390IPLState *ipl = get_ipl_device(); @@ -428,14 +456,13 @@ void s390_ipl_convert_loadparm(char *ascii_lp, uint8_t *ebcdic_lp) static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb) { CcwDevice *ccw_dev = NULL; + S390PCIBusDevice *pbdev = NULL; SCSIDevice *sd; int devtype; uint8_t *lp; g_autofree void *scsi_lp = NULL; + g_autofree void *pci_lp = NULL; - /* - * Currently allow IPL only from CCW devices. - */ ccw_dev = s390_get_ccw_device(dev_st, &devtype); if (ccw_dev) { lp = ccw_dev->loadparm; @@ -485,6 +512,32 @@ static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb) return true; } + pbdev = s390_get_pci_device(dev_st, &devtype); + if (pbdev) { + pci_lp = object_property_get_str(OBJECT(pbdev->pdev), "loadparm", NULL); + if (pci_lp && strlen(pci_lp) > 0) { + lp = pci_lp; + } else { + /* Use machine loadparm as a place holder if PCI LP is unset */ + lp = S390_CCW_MACHINE(qdev_get_machine())->loadparm; + } + + switch (devtype) { + case PCI_DEVTYPE_VIRTIO: + iplb->len = cpu_to_be32(S390_IPLB_MIN_PCI_LEN); + iplb->pbt = S390_IPL_TYPE_PCI; + iplb->pci.fid = cpu_to_be32(pbdev->fid); + break; + default: + return false; + } + + s390_ipl_convert_loadparm((char *)lp, iplb->loadparm); + iplb->flags |= DIAG308_FLAGS_LP_VALID; + + return true; + } + return false; } diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h index 086e57681c29..403cd08450d0 100644 --- a/hw/s390x/ipl.h +++ b/hw/s390x/ipl.h @@ -103,15 +103,11 @@ QEMU_BUILD_BUG_MSG(offsetof(S390IPLState, iplb) & 3, "alignment of iplb wrong"); #define DIAG308_PV_STORE 9 #define DIAG308_PV_START 10 -#define S390_IPL_TYPE_FCP 0x00 -#define S390_IPL_TYPE_CCW 0x02 -#define S390_IPL_TYPE_PV 0x05 -#define S390_IPL_TYPE_QEMU_SCSI 0xff - #define S390_IPLB_HEADER_LEN 8 #define S390_IPLB_MIN_PV_LEN 148 #define S390_IPLB_MIN_CCW_LEN 200 #define S390_IPLB_MIN_FCP_LEN 384 +#define S390_IPLB_MIN_PCI_LEN 376 #define S390_IPLB_MIN_QEMU_SCSI_LEN 200 static inline bool iplb_valid_len(IplParameterBlock *iplb) @@ -184,6 +180,8 @@ static inline bool iplb_valid(IplParameterBlock *iplb) return len >= S390_IPLB_MIN_FCP_LEN; case S390_IPL_TYPE_CCW: return len >= S390_IPLB_MIN_CCW_LEN; + case S390_IPL_TYPE_PCI: + return len >= S390_IPLB_MIN_PCI_LEN; case S390_IPL_TYPE_QEMU_SCSI: default: return false; diff --git a/hw/s390x/meson.build b/hw/s390x/meson.build index 1bc858379966..57cc2a6be3d3 100644 --- a/hw/s390x/meson.build +++ b/hw/s390x/meson.build @@ -34,6 +34,7 @@ s390x_ss.add(when: 'CONFIG_S390_CCW_VIRTIO', if_true: files( )) s390x_ss.add(when: 'CONFIG_TERMINAL3270', if_true: files('3270-ccw.c')) s390x_ss.add(when: 'CONFIG_VFIO', if_true: files('s390-pci-vfio.c')) +stub_ss.add(files('s390-pci-vfio-stubs.c')) s390x_ss.add(when: 'CONFIG_VFIO_AP', if_false: files('ap-stub.c')) virtio_ss = ss.source_set() diff --git a/hw/s390x/s390-pci-bus.c b/hw/s390x/s390-pci-bus.c index b438d63c4444..4de7b587e8a5 100644 --- a/hw/s390x/s390-pci-bus.c +++ b/hw/s390x/s390-pci-bus.c @@ -250,8 +250,7 @@ S390PCIBusDevice *s390_pci_find_dev_by_target(S390pciState *s, return NULL; } -static S390PCIBusDevice *s390_pci_find_dev_by_pci(S390pciState *s, - PCIDevice *pci_dev) +S390PCIBusDevice *s390_pci_find_dev_by_pci(S390pciState *s, PCIDevice *pci_dev) { S390PCIBusDevice *pbdev; @@ -1248,7 +1247,7 @@ static void s390_pcihost_unplug(HotplugHandler *hotplug_dev, DeviceState *dev, pbdev->fid = 0; QTAILQ_REMOVE(&s->zpci_devs, pbdev, link); g_hash_table_remove(s->zpci_table, &pbdev->idx); - if (pbdev->iommu->dma_limit) { + if (pbdev->iommu && pbdev->iommu->dma_limit) { s390_pci_end_dma_count(s, pbdev->iommu->dma_limit); } qdev_unrealize(dev); diff --git a/hw/s390x/s390-pci-vfio-stubs.c b/hw/s390x/s390-pci-vfio-stubs.c new file mode 100644 index 000000000000..d9882b7aad0c --- /dev/null +++ b/hw/s390x/s390-pci-vfio-stubs.c @@ -0,0 +1,32 @@ +/* + * s390 vfio-pci stubs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/s390x/s390-pci-vfio.h" + +bool s390_pci_update_dma_avail(int fd, unsigned int *avail) +{ + return false; +} + +S390PCIDMACount *s390_pci_start_dma_count(S390pciState *s, + S390PCIBusDevice *pbdev) +{ + return NULL; +} + +void s390_pci_end_dma_count(S390pciState *s, S390PCIDMACount *cnt) +{ +} + +bool s390_pci_get_host_fh(S390PCIBusDevice *pbdev, uint32_t *fh) +{ + return false; +} + +void s390_pci_get_clp_info(S390PCIBusDevice *pbdev) +{ +} diff --git a/hw/s390x/s390-pci-vfio.c b/hw/s390x/s390-pci-vfio.c index 9e31029d7acb..8ce44dbecced 100644 --- a/hw/s390x/s390-pci-vfio.c +++ b/hw/s390x/s390-pci-vfio.c @@ -17,7 +17,7 @@ #include "trace.h" #include "hw/s390x/s390-pci-bus.h" -#include "hw/s390x/s390-pci-clp.h" +#include "hw/s390x/ipl/s390-pci-clp.h" #include "hw/s390x/s390-pci-vfio.h" #include "hw/vfio/pci.h" #include "hw/vfio/vfio-container-legacy.h" diff --git a/hw/scsi/meson.build b/hw/scsi/meson.build index b874fe1ecdb2..69fde0cf840d 100644 --- a/hw/scsi/meson.build +++ b/hw/scsi/meson.build @@ -1,7 +1,6 @@ scsi_ss = ss.source_set() specific_scsi_ss = ss.source_set() virtio_scsi_ss = ss.source_set() -specific_virtio_scsi_ss = ss.source_set() scsi_ss.add(files( 'emulation.c', @@ -21,10 +20,9 @@ virtio_scsi_ss.add(files('virtio-scsi-dataplane.c')) virtio_scsi_ss.add(when: 'CONFIG_VHOST_SCSI', if_true: files('vhost-scsi.c')) virtio_scsi_ss.add(when: 'CONFIG_VHOST_USER_SCSI', if_true: files('vhost-user-scsi.c')) -specific_virtio_scsi_ss.add(files('virtio-scsi.c')) -specific_virtio_scsi_ss.add(when: 'CONFIG_VHOST_SCSI_COMMON', if_true: files('vhost-scsi-common.c')) +virtio_scsi_ss.add(files('virtio-scsi.c')) +virtio_scsi_ss.add(when: 'CONFIG_VHOST_SCSI_COMMON', if_true: files('vhost-scsi-common.c')) -specific_scsi_ss.add_all(when: 'CONFIG_VIRTIO_SCSI', if_true: specific_virtio_scsi_ss) scsi_ss.add_all(when: 'CONFIG_VIRTIO_SCSI', if_true: virtio_scsi_ss) specific_scsi_ss.add(when: 'CONFIG_SPAPR_VSCSI', if_true: files('spapr_vscsi.c')) diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c index 2f400f5b7783..a5201855352f 100644 --- a/hw/scsi/scsi-disk.c +++ b/hw/scsi/scsi-disk.c @@ -27,13 +27,13 @@ #include "qemu/module.h" #include "qemu/hw-version.h" #include "qemu/memalign.h" +#include "qemu/target-info.h" #include "hw/scsi/scsi.h" #include "migration/misc.h" #include "migration/qemu-file-types.h" #include "migration/vmstate.h" #include "hw/scsi/emulation.h" #include "scsi/constants.h" -#include "system/arch_init.h" #include "system/block-backend.h" #include "system/blockdev.h" #include "hw/block/block.h" @@ -3180,7 +3180,7 @@ static void scsi_property_add_specifics(DeviceClass *dc) ObjectClass *oc = OBJECT_CLASS(dc); /* The loadparm property is only supported on s390x */ - if (qemu_arch_available(QEMU_ARCH_S390X)) { + if (target_s390x()) { object_class_property_add_str(oc, "loadparm", scsi_property_get_loadparm, scsi_property_set_loadparm); diff --git a/hw/smbios/meson.build b/hw/smbios/meson.build index a59039f66927..9bf4b1ad1e5a 100644 --- a/hw/smbios/meson.build +++ b/hw/smbios/meson.build @@ -1,12 +1,12 @@ smbios_ss = ss.source_set() smbios_ss.add(files('smbios.c')) smbios_ss.add(when: 'CONFIG_IPMI', - if_true: files('smbios_type_38.c'), - if_false: files('smbios_type_38-stub.c')) + if_true: files('smbios_type_38.c')) +stub_ss.add(files('smbios_type_38-stub.c')) smbios_ss.add(when: 'CONFIG_SMBIOS_LEGACY', - if_true: files('smbios_legacy.c'), - if_false: files('smbios_legacy_stub.c')) + if_true: files('smbios_legacy.c')) +stub_ss.add(files('smbios_legacy_stub.c')) system_ss.add_all(when: 'CONFIG_SMBIOS', if_true: smbios_ss) -system_ss.add(when: 'CONFIG_SMBIOS', if_false: files('smbios-stub.c')) +stub_ss.add(files('smbios-stub.c')) diff --git a/hw/sparc/sun4m.c b/hw/sparc/sun4m.c index 0c0d658d3016..46fcbdda97e3 100644 --- a/hw/sparc/sun4m.c +++ b/hw/sparc/sun4m.c @@ -196,10 +196,6 @@ static void cpu_set_irq(void *opaque, int irq, int level) } } -static void dummy_cpu_set_irq(void *opaque, int irq, int level) -{ -} - static void sun4m_cpu_reset(void *opaque) { SPARCCPU *cpu = opaque; @@ -344,7 +340,8 @@ static void *sparc32_dma_init(hwaddr dma_base, static DeviceState *slavio_intctl_init(hwaddr addr, hwaddr addrg, - qemu_irq **parent_irq) + unsigned int smp_cpus, + DeviceState **cpus) { DeviceState *dev; SysBusDevice *s; @@ -355,9 +352,10 @@ static DeviceState *slavio_intctl_init(hwaddr addr, s = SYS_BUS_DEVICE(dev); sysbus_realize_and_unref(s, &error_fatal); - for (i = 0; i < MAX_CPUS; i++) { + for (i = 0; i < smp_cpus; i++) { for (j = 0; j < MAX_PILS; j++) { - sysbus_connect_irq(s, i * MAX_PILS + j, parent_irq[i][j]); + sysbus_connect_irq(s, i * MAX_PILS + j, + qdev_get_gpio_in_named(cpus[i], "pil", j)); } } sysbus_mmio_map(s, 0, addrg); @@ -587,13 +585,10 @@ static void idreg_realize(DeviceState *ds, Error **errp) IDRegState *s = MACIO_ID_REGISTER(ds); SysBusDevice *dev = SYS_BUS_DEVICE(ds); - if (!memory_region_init_ram_nomigrate(&s->mem, OBJECT(ds), "sun4m.idreg", - sizeof(idreg_data), errp)) { + if (!memory_region_init_rom(&s->mem, OBJECT(ds), "sun4m.idreg", + sizeof(idreg_data), errp)) { return; } - - vmstate_register_ram_global(&s->mem); - memory_region_set_readonly(&s->mem, true); sysbus_init_mmio(dev, &s->mem); } @@ -638,12 +633,9 @@ static void afx_realize(DeviceState *ds, Error **errp) AFXState *s = TCX_AFX(ds); SysBusDevice *dev = SYS_BUS_DEVICE(ds); - if (!memory_region_init_ram_nomigrate(&s->mem, OBJECT(ds), "sun4m.afx", - 4, errp)) { + if (!memory_region_init_ram(&s->mem, OBJECT(ds), "sun4m.afx", 4, errp)) { return; } - - vmstate_register_ram_global(&s->mem); sysbus_init_mmio(dev, &s->mem); } @@ -719,13 +711,10 @@ static void prom_realize(DeviceState *ds, Error **errp) PROMState *s = OPENPROM(ds); SysBusDevice *dev = SYS_BUS_DEVICE(ds); - if (!memory_region_init_ram_nomigrate(&s->prom, OBJECT(ds), "sun4m.prom", - PROM_SIZE_MAX, errp)) { + if (!memory_region_init_rom(&s->prom, OBJECT(ds), "sun4m.prom", + PROM_SIZE_MAX, errp)) { return; } - - vmstate_register_ram_global(&s->prom); - memory_region_set_readonly(&s->prom, true); sysbus_init_mmio(dev, &s->prom); } @@ -788,22 +777,25 @@ static const TypeInfo ram_info = { .class_init = ram_class_init, }; -static void cpu_devinit(const char *cpu_type, unsigned int id, - uint64_t prom_addr, qemu_irq **cpu_irqs) +static DeviceState *cpu_devinit(const char *cpu_type, unsigned int id, + uint64_t prom_addr) { SPARCCPU *cpu; CPUSPARCState *env; + DeviceState *cpudev; cpu = SPARC_CPU(object_new(cpu_type)); env = &cpu->env; + cpudev = DEVICE(cpu); qemu_register_reset(sun4m_cpu_reset, cpu); object_property_set_bool(OBJECT(cpu), "start-powered-off", id != 0, &error_abort); - qdev_realize_and_unref(DEVICE(cpu), NULL, &error_fatal); + qdev_init_gpio_in_named(cpudev, cpu_set_irq, "pil", MAX_PILS); + qdev_realize_and_unref(cpudev, NULL, &error_fatal); cpu_sparc_set_id(env, id); - *cpu_irqs = qemu_allocate_irqs(cpu_set_irq, cpu, MAX_PILS); env->prom_addr = prom_addr; + return cpudev; } static void dummy_fdc_tc(void *opaque, int irq, int level) @@ -816,13 +808,14 @@ static void sun4m_hw_init(MachineState *machine) DeviceState *slavio_intctl; unsigned int i; Nvram *nvram; - qemu_irq *cpu_irqs[MAX_CPUS], slavio_irq[32], slavio_cpu_irq[MAX_CPUS]; + qemu_irq slavio_irq[32], slavio_cpu_irq[MAX_CPUS]; qemu_irq fdc_tc; unsigned long kernel_size; uint32_t initrd_size; DriveInfo *fd[MAX_FD]; FWCfgState *fw_cfg; DeviceState *dev, *ms_kb_orgate, *serial_orgate; + DeviceState *cpus[MAX_CPUS]; SysBusDevice *s; unsigned int smp_cpus = machine->smp.cpus; unsigned int max_cpus = machine->smp.max_cpus; @@ -838,12 +831,9 @@ static void sun4m_hw_init(MachineState *machine) /* init CPUs */ for(i = 0; i < smp_cpus; i++) { - cpu_devinit(machine->cpu_type, i, hwdef->slavio_base, &cpu_irqs[i]); + cpus[i] = cpu_devinit(machine->cpu_type, i, hwdef->slavio_base); } - for (i = smp_cpus; i < MAX_CPUS; i++) - cpu_irqs[i] = qemu_allocate_irqs(dummy_cpu_set_irq, NULL, MAX_PILS); - /* Create and map RAM frontend */ dev = qdev_new("memory"); object_property_set_link(OBJECT(dev), "memdev", OBJECT(ram_memdev), &error_fatal); @@ -860,7 +850,8 @@ static void sun4m_hw_init(MachineState *machine) slavio_intctl = slavio_intctl_init(hwdef->intctl_base, hwdef->intctl_base + 0x10000ULL, - cpu_irqs); + smp_cpus, + cpus); for (i = 0; i < 32; i++) { slavio_irq[i] = qdev_get_gpio_in(slavio_intctl, i); @@ -892,6 +883,15 @@ static void sun4m_hw_init(MachineState *machine) hwdef->esp_base, slavio_irq[18], hwdef->le_base, slavio_irq[16], &hostid); + if (!graphic_width) { + graphic_width = 1024; + } + if (!graphic_height) { + graphic_height = 768; + } + if (!graphic_depth) { + graphic_depth = 8; + } if (graphic_depth != 8 && graphic_depth != 24) { error_report("Unsupported depth: %d", graphic_depth); exit (1); diff --git a/hw/sparc64/sun4u.c b/hw/sparc64/sun4u.c index 5d7787fc1a52..2e41785b7836 100644 --- a/hw/sparc64/sun4u.c +++ b/hw/sparc64/sun4u.c @@ -455,13 +455,10 @@ static void prom_realize(DeviceState *ds, Error **errp) PROMState *s = OPENPROM(ds); SysBusDevice *dev = SYS_BUS_DEVICE(ds); - if (!memory_region_init_ram_nomigrate(&s->prom, OBJECT(ds), "sun4u.prom", - PROM_SIZE_MAX, errp)) { + if (!memory_region_init_rom(&s->prom, OBJECT(ds), "sun4u.prom", + PROM_SIZE_MAX, errp)) { return; } - - vmstate_register_ram_global(&s->prom); - memory_region_set_readonly(&s->prom, true); sysbus_init_mmio(dev, &s->prom); } @@ -498,9 +495,8 @@ static void ram_realize(DeviceState *dev, Error **errp) RamDevice *d = SUN4U_RAM(dev); SysBusDevice *sbd = SYS_BUS_DEVICE(dev); - memory_region_init_ram_nomigrate(&d->ram, OBJECT(d), "sun4u.ram", d->size, + memory_region_init_ram(&d->ram, OBJECT(d), "sun4u.ram", d->size, &error_fatal); - vmstate_register_ram_global(&d->ram); sysbus_init_mmio(sbd, &d->ram); } @@ -666,6 +662,16 @@ static void sun4uv_init(MemoryRegion *address_space_mem, sysbus_mmio_get_region(s, 0)); nvram = NVRAM(dev); + if (!graphic_width) { + graphic_width = 1024; + } + if (!graphic_height) { + graphic_height = 768; + } + if (!graphic_depth) { + graphic_depth = 8; + } + initrd_size = 0; initrd_addr = 0; kernel_size = sun4u_load_kernel(machine->kernel_filename, diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c index a4718fb72d9d..f6e717bc019f 100644 --- a/hw/ssi/xilinx_spips.c +++ b/hw/ssi/xilinx_spips.c @@ -369,7 +369,7 @@ static void xilinx_spips_reset(DeviceState *d) memset(s->regs, 0, sizeof(s->regs)); fifo8_reset(&s->rx_fifo); - fifo8_reset(&s->rx_fifo); + fifo8_reset(&s->tx_fifo); /* non zero resets */ s->regs[R_CONFIG] |= MODEFAIL_GEN_EN; s->regs[R_SLAVE_IDLE_COUNT] = 0xFF; @@ -397,7 +397,7 @@ static void xlnx_zynqmp_qspips_reset(DeviceState *d) memset(s->regs, 0, sizeof(s->regs)); fifo8_reset(&s->rx_fifo_g); - fifo8_reset(&s->rx_fifo_g); + fifo8_reset(&s->tx_fifo_g); fifo32_reset(&s->fifo_g); s->regs[R_INTR_STATUS] = R_INTR_STATUS_RESET; s->regs[R_GPIO] = 1; diff --git a/hw/timer/hpet.c b/hw/timer/hpet.c index 0d7b8e0c7a35..767093c431a0 100644 --- a/hw/timer/hpet.c +++ b/hw/timer/hpet.c @@ -75,7 +75,6 @@ struct HPETState { QemuMutex lock; MemoryRegion iomem; uint64_t hpet_offset; - bool hpet_offset_saved; QemuSeqLock state_version; qemu_irq irqs[HPET_NUM_IRQ_ROUTES]; uint32_t flags; @@ -272,11 +271,6 @@ static int hpet_post_load(void *opaque, int version_id) t->cmp64 = hpet_calculate_cmp64(t, s->hpet_counter, t->cmp); t->last = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - NANOSECONDS_PER_SECOND; } - /* Recalculate the offset between the main counter and guest time */ - if (!s->hpet_offset_saved) { - s->hpet_offset = ticks_to_ns(s->hpet_counter) - - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - } return 0; } @@ -285,7 +279,7 @@ static bool hpet_offset_needed(void *opaque) { HPETState *s = opaque; - return hpet_enabled(s) && s->hpet_offset_saved; + return hpet_enabled(s); } static bool hpet_rtc_irq_level_needed(void *opaque) @@ -766,7 +760,6 @@ static const Property hpet_device_properties[] = { DEFINE_PROP_UINT8("timers", HPETState, num_timers, HPET_MIN_TIMERS), DEFINE_PROP_BIT("msi", HPETState, flags, HPET_MSI_SUPPORT, false), DEFINE_PROP_UINT32(HPET_INTCAP, HPETState, intcap, 0), - DEFINE_PROP_BOOL("hpet-offset-saved", HPETState, hpet_offset_saved, true), }; static void hpet_device_class_init(ObjectClass *klass, const void *data) diff --git a/hw/timer/slavio_timer.c b/hw/timer/slavio_timer.c index 4a3e227fbab9..eccdc532fb1f 100644 --- a/hw/timer/slavio_timer.c +++ b/hw/timer/slavio_timer.c @@ -62,20 +62,21 @@ typedef struct CPUTimerState { #define TYPE_SLAVIO_TIMER "slavio_timer" OBJECT_DECLARE_SIMPLE_TYPE(SLAVIO_TIMERState, SLAVIO_TIMER) +typedef struct TimerContext { + MemoryRegion iomem; + SLAVIO_TIMERState *s; + unsigned int timer_index; /* 0 for system, 1 ... MAX_CPUS for CPU timers */ +} TimerContext; + struct SLAVIO_TIMERState { SysBusDevice parent_obj; uint32_t num_cpus; uint32_t cputimer_mode; CPUTimerState cputimer[MAX_CPUS + 1]; + TimerContext timer_context[MAX_CPUS + 1]; }; -typedef struct TimerContext { - MemoryRegion iomem; - SLAVIO_TIMERState *s; - unsigned int timer_index; /* 0 for system, 1 ... MAX_CPUS for CPU timers */ -} TimerContext; - #define SYS_TIMER_SIZE 0x14 #define CPU_TIMER_SIZE 0x10 @@ -400,7 +401,7 @@ static void slavio_timer_init(Object *obj) uint64_t size; char timer_name[20]; - tc = g_new0(TimerContext, 1); + tc = &s->timer_context[i]; tc->s = s; tc->timer_index = i; @@ -420,6 +421,15 @@ static void slavio_timer_init(Object *obj) } } +static void slavio_timer_finalize(Object *obj) +{ + SLAVIO_TIMERState *s = SLAVIO_TIMER(obj); + + for (int i = 0; i <= MAX_CPUS; i++) { + ptimer_free(s->cputimer[i].timer); + } +} + static const Property slavio_timer_properties[] = { DEFINE_PROP_UINT32("num_cpus", SLAVIO_TIMERState, num_cpus, 0), }; @@ -438,6 +448,7 @@ static const TypeInfo slavio_timer_info = { .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(SLAVIO_TIMERState), .instance_init = slavio_timer_init, + .instance_finalize = slavio_timer_finalize, .class_init = slavio_timer_class_init, }; diff --git a/hw/uefi/var-service-vars.c b/hw/uefi/var-service-vars.c index 94f40ef23684..5e3907118d4b 100644 --- a/hw/uefi/var-service-vars.c +++ b/hw/uefi/var-service-vars.c @@ -37,8 +37,41 @@ const VMStateDescription vmstate_uefi_time = { }, }; +static int uefi_vars_pre_load(void *opaque) +{ + uefi_variable *var = opaque; + + /* clear digest which is optional in the live migration data stream */ + var->digest = NULL; + var->digest_size = 0; + return 0; +} + +static bool uefi_vars_digest_is_needed(void *opaque) +{ + uefi_variable *var = opaque; + + if ((var->attributes & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) + && !uefi_vars_is_sb_any(var)) { + return true; + } + return false; +} + +const VMStateDescription vmstate_uefi_variable_digest = { + .name = "uefi-variable-digest", + .needed = uefi_vars_digest_is_needed, + .fields = (VMStateField[]) { + VMSTATE_UINT32(digest_size, uefi_variable), + VMSTATE_VBUFFER_ALLOC_UINT32(digest, uefi_variable, + 0, NULL, digest_size), + VMSTATE_END_OF_LIST() + }, +}; + const VMStateDescription vmstate_uefi_variable = { .name = "uefi-variable", + .pre_load = uefi_vars_pre_load, .fields = (VMStateField[]) { VMSTATE_UINT8_ARRAY_V(guid.data, uefi_variable, sizeof(QemuUUID), 0), VMSTATE_UINT32(name_size, uefi_variable), @@ -49,6 +82,10 @@ const VMStateDescription vmstate_uefi_variable = { VMSTATE_STRUCT(time, uefi_variable, 0, vmstate_uefi_time, efi_time), VMSTATE_END_OF_LIST() }, + .subsections = (const VMStateDescription * const []) { + &vmstate_uefi_variable_digest, + NULL + } }; uefi_variable *uefi_vars_find_variable(uefi_vars_state *uv, QemuUUID guid, diff --git a/hw/usb/dev-audio.c b/hw/usb/dev-audio.c index dfda2dccc041..e18e0a1dfd60 100644 --- a/hw/usb/dev-audio.c +++ b/hw/usb/dev-audio.c @@ -669,7 +669,7 @@ static void output_callback(void *opaque, int avail) return; } - written = AUD_write(s->out.voice, data, len); + written = audio_be_write(s->audio_be, s->out.voice, data, len); avail -= written; s->out.buf.cons += written; @@ -683,7 +683,7 @@ static int usb_audio_set_output_altset(USBAudioState *s, int altset) { switch (altset) { case ALTSET_OFF: - AUD_set_active_out(s->out.voice, false); + audio_be_set_active_out(s->audio_be, s->out.voice, false); break; case ALTSET_STEREO: case ALTSET_51: @@ -692,7 +692,7 @@ static int usb_audio_set_output_altset(USBAudioState *s, int altset) usb_audio_reinit(USB_DEVICE(s), altset_channels[altset]); } streambuf_init(&s->out.buf, s->buffer, s->out.channels); - AUD_set_active_out(s->out.voice, true); + audio_be_set_active_out(s->audio_be, s->out.voice, true); break; default: return -1; @@ -805,7 +805,7 @@ static int usb_audio_set_control(USBAudioState *s, uint8_t attrib, } fprintf(stderr, "\n"); } - AUD_set_volume_out(s->out.voice, &s->out.vol); + audio_be_set_volume_out(s->audio_be, s->out.voice, &s->out.vol); } return ret; @@ -931,7 +931,7 @@ static void usb_audio_unrealize(USBDevice *dev) } usb_audio_set_output_altset(s, ALTSET_OFF); - AUD_close_out(s->audio_be, s->out.voice); + audio_be_close_out(s->audio_be, s->out.voice); streambuf_fini(&s->out.buf); } @@ -941,7 +941,7 @@ static void usb_audio_realize(USBDevice *dev, Error **errp) USBAudioState *s = USB_AUDIO(dev); int i; - if (!AUD_backend_check(&s->audio_be, errp)) { + if (!audio_be_check(&s->audio_be, errp)) { return; } @@ -975,13 +975,13 @@ static void usb_audio_reinit(USBDevice *dev, unsigned channels) s->out.as.freq = USBAUDIO_SAMPLE_RATE; s->out.as.nchannels = s->out.channels; s->out.as.fmt = AUDIO_FORMAT_S16; - s->out.as.endianness = 0; + s->out.as.big_endian = false; streambuf_init(&s->out.buf, s->buffer, s->out.channels); - s->out.voice = AUD_open_out(s->audio_be, s->out.voice, TYPE_USB_AUDIO, + s->out.voice = audio_be_open_out(s->audio_be, s->out.voice, TYPE_USB_AUDIO, s, output_callback, &s->out.as); - AUD_set_volume_out(s->out.voice, &s->out.vol); - AUD_set_active_out(s->out.voice, 0); + audio_be_set_volume_out(s->audio_be, s->out.voice, &s->out.vol); + audio_be_set_active_out(s->audio_be, s->out.voice, 0); } static const VMStateDescription vmstate_usb_audio = { diff --git a/hw/usb/hcd-ohci.c b/hw/usb/hcd-ohci.c index c7e9c7190350..1aeed9286f5e 100644 --- a/hw/usb/hcd-ohci.c +++ b/hw/usb/hcd-ohci.c @@ -1246,6 +1246,10 @@ static void ohci_frame_boundary(void *opaque) hcca.frame = cpu_to_le16(ohci->frame_number); /* When the HC updates frame number, set pad to 0. Ref OHCI Spec 4.4.1*/ hcca.pad = 0; + /* FrameNumberOverflow happens when bit 15 of frame number changes */ + if (ohci->frame_number == 0x8000 || ohci->frame_number == 0) { + ohci_set_interrupt(ohci, OHCI_INTR_FNO); + } if (ohci->done_count == 0 && !(ohci->intr_status & OHCI_INTR_WD)) { if (!ohci->done) { diff --git a/hw/usb/meson.build b/hw/usb/meson.build index 17360a5b5a49..ba55c28ef69e 100644 --- a/hw/usb/meson.build +++ b/hw/usb/meson.build @@ -9,7 +9,8 @@ system_ss.add(when: 'CONFIG_USB', if_true: files( 'desc-msos.c', 'libhw.c', 'pcap.c', -), if_false: files('bus-stub.c')) +)) +stub_ss.add(files('bus-stub.c')) # usb host adapters system_ss.add(when: 'CONFIG_USB_UHCI', if_true: files('hcd-uhci.c')) diff --git a/hw/vfio/helpers.c b/hw/vfio/helpers.c index f68f8165d091..00d42d3b98ef 100644 --- a/hw/vfio/helpers.c +++ b/hw/vfio/helpers.c @@ -116,6 +116,88 @@ bool vfio_get_info_dma_avail(struct vfio_iommu_type1_info *info, * we'll re-use it should another vfio device be attached before then. */ int vfio_kvm_device_fd = -1; + +/* + * Confidential virtual machines: + * During reset of confidential vms, the kvm vm file descriptor changes. + * In this case, the old vfio kvm file descriptor is + * closed and a new descriptor is created against the new kvm vm file + * descriptor. + */ + +typedef struct VFIODeviceFd { + int fd; + QLIST_ENTRY(VFIODeviceFd) node; +} VFIODeviceFd; + +static QLIST_HEAD(, VFIODeviceFd) vfio_device_fds = + QLIST_HEAD_INITIALIZER(vfio_device_fds); + +static void vfio_device_fd_list_add(int fd) +{ + VFIODeviceFd *file_fd; + file_fd = g_malloc0(sizeof(*file_fd)); + file_fd->fd = fd; + QLIST_INSERT_HEAD(&vfio_device_fds, file_fd, node); +} + +static void vfio_device_fd_list_remove(int fd) +{ + VFIODeviceFd *file_fd, *next; + + QLIST_FOREACH_SAFE(file_fd, &vfio_device_fds, node, next) { + if (file_fd->fd == fd) { + QLIST_REMOVE(file_fd, node); + g_free(file_fd); + break; + } + } +} + +static int vfio_device_fd_rebind(NotifierWithReturn *notifier, void *data, + Error **errp) +{ + VFIODeviceFd *file_fd; + struct kvm_device_attr attr = { + .group = KVM_DEV_VFIO_FILE, + .attr = KVM_DEV_VFIO_FILE_ADD, + }; + struct kvm_create_device cd = { + .type = KVM_DEV_TYPE_VFIO, + }; + + /* we are not interested in pre vmfd change notification */ + if (((VmfdChangeNotifier *)data)->pre) { + return 0; + } + + if (kvm_vm_ioctl(kvm_state, KVM_CREATE_DEVICE, &cd)) { + error_setg_errno(errp, errno, "Failed to create KVM VFIO device"); + return -errno; + } + + if (vfio_kvm_device_fd != -1) { + close(vfio_kvm_device_fd); + } + + vfio_kvm_device_fd = cd.fd; + + QLIST_FOREACH(file_fd, &vfio_device_fds, node) { + attr.addr = (uint64_t)(unsigned long)&file_fd->fd; + if (ioctl(vfio_kvm_device_fd, KVM_SET_DEVICE_ATTR, &attr)) { + error_setg_errno(errp, errno, + "Failed to add fd %d to KVM VFIO device", + file_fd->fd); + return -errno; + } + } + return 0; +} + +static struct NotifierWithReturn vfio_vmfd_change_notifier = { + .notify = vfio_device_fd_rebind, +}; + #endif void vfio_kvm_device_close(void) @@ -153,6 +235,11 @@ int vfio_kvm_device_add_fd(int fd, Error **errp) } vfio_kvm_device_fd = cd.fd; + /* + * If the vm file descriptor changes, add a notifier so that we can + * re-create the vfio_kvm_device_fd. + */ + kvm_vmfd_add_change_notifier(&vfio_vmfd_change_notifier); } if (ioctl(vfio_kvm_device_fd, KVM_SET_DEVICE_ATTR, &attr)) { @@ -160,6 +247,8 @@ int vfio_kvm_device_add_fd(int fd, Error **errp) fd); return -errno; } + + vfio_device_fd_list_add(fd); #endif return 0; } @@ -183,6 +272,8 @@ int vfio_kvm_device_del_fd(int fd, Error **errp) "Failed to remove fd %d from KVM VFIO device", fd); return -errno; } + + vfio_device_fd_list_remove(fd); #endif return 0; } diff --git a/hw/vfio/listener.c b/hw/vfio/listener.c index 1087fdc142e0..960da9e0a934 100644 --- a/hw/vfio/listener.c +++ b/hw/vfio/listener.c @@ -283,7 +283,7 @@ static bool vfio_ram_discard_register_listener(VFIOContainer *bcontainer, ram_discard_listener_init(&vrdl->listener, vfio_ram_discard_notify_populate, - vfio_ram_discard_notify_discard, true); + vfio_ram_discard_notify_discard); ram_discard_manager_register_listener(rdm, &vrdl->listener, section); QLIST_INSERT_HEAD(&bcontainer->vrdl_list, vrdl, next); diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c index c89f3fbea348..94c174a773fb 100644 --- a/hw/vfio/pci.c +++ b/hw/vfio/pci.c @@ -2739,7 +2739,7 @@ void vfio_pci_post_reset(VFIOPCIDevice *vdev) bool vfio_pci_host_match(PCIHostDeviceAddress *addr, const char *name) { - char tmp[13]; + char tmp[36]; sprintf(tmp, "%04x:%02x:%02x.%1x", addr->domain, addr->bus, addr->slot, addr->function); diff --git a/hw/virtio/meson.build b/hw/virtio/meson.build index 6675b63ce653..415e359e9fc7 100644 --- a/hw/virtio/meson.build +++ b/hw/virtio/meson.build @@ -9,16 +9,14 @@ system_virtio_ss.add(when: 'CONFIG_VHOST_VSOCK_COMMON', if_true: files('vhost-vs system_virtio_ss.add(when: 'CONFIG_VIRTIO_IOMMU', if_true: files('virtio-iommu.c')) system_virtio_ss.add(when: 'CONFIG_VHOST_VDPA_DEV', if_true: files('vdpa-dev.c')) -specific_virtio_ss = ss.source_set() -specific_virtio_ss.add(files('virtio.c')) -specific_virtio_ss.add(files('virtio-qmp.c')) +system_virtio_ss.add(files('virtio.c')) +system_virtio_ss.add(files('virtio-qmp.c')) if have_vhost system_virtio_ss.add(files('vhost.c')) system_virtio_ss.add(files('vhost-backend.c', 'vhost-iova-tree.c')) if have_vhost_user - # fixme - this really should be generic - specific_virtio_ss.add(files('vhost-user.c')) + system_virtio_ss.add(files('vhost-user.c')) system_virtio_ss.add(files('vhost-user-base.c')) # MMIO Stubs @@ -50,16 +48,14 @@ if have_vhost system_virtio_ss.add(files('vhost-vdpa.c')) system_virtio_ss.add(files('vhost-shadow-virtqueue.c')) endif -else - system_virtio_ss.add(files('vhost-stub.c')) endif system_virtio_ss.add(when: 'CONFIG_VHOST_USER_VSOCK', if_true: files('vhost-user-vsock.c')) system_virtio_ss.add(when: 'CONFIG_VIRTIO_RNG', if_true: files('virtio-rng.c')) -specific_virtio_ss.add(when: 'CONFIG_VIRTIO_BALLOON', if_true: files('virtio-balloon.c')) -specific_virtio_ss.add(when: 'CONFIG_VHOST_USER_FS', if_true: files('vhost-user-fs.c')) -specific_virtio_ss.add(when: 'CONFIG_VIRTIO_PMEM', if_true: files('virtio-pmem.c')) -specific_virtio_ss.add(when: 'CONFIG_VHOST_VSOCK', if_true: files('vhost-vsock.c')) +system_virtio_ss.add(when: 'CONFIG_VIRTIO_BALLOON', if_true: files('virtio-balloon.c')) +system_virtio_ss.add(when: 'CONFIG_VHOST_USER_FS', if_true: files('vhost-user-fs.c')) +system_virtio_ss.add(when: 'CONFIG_VIRTIO_PMEM', if_true: files('virtio-pmem.c')) +system_virtio_ss.add(when: 'CONFIG_VHOST_VSOCK', if_true: files('vhost-vsock.c')) system_virtio_ss.add(when: 'CONFIG_VIRTIO_MEM', if_true: files('virtio-mem.c')) system_virtio_ss.add(when: 'CONFIG_VIRTIO_NSM', if_true: files('virtio-nsm.c')) system_virtio_ss.add(when: 'CONFIG_VIRTIO_NSM', if_true: [files('cbor-helpers.c'), libcbor]) @@ -94,12 +90,10 @@ virtio_pci_ss.add(when: 'CONFIG_VIRTIO_MD', if_true: files('virtio-md-pci.c')) system_virtio_ss.add_all(when: 'CONFIG_VIRTIO_PCI', if_true: virtio_pci_ss) system_ss.add_all(when: 'CONFIG_VIRTIO', if_true: system_virtio_ss) -system_ss.add(when: 'CONFIG_VIRTIO', if_false: files('vhost-stub.c')) -system_ss.add(when: 'CONFIG_VIRTIO', if_false: files('virtio-stub.c')) -system_ss.add(when: ['CONFIG_VIRTIO_MD', 'CONFIG_VIRTIO_PCI'], - if_false: files('virtio-md-stubs.c')) +stub_ss.add(files('vhost-stub.c')) +stub_ss.add(files('virtio-stub.c')) +stub_ss.add(files('virtio-md-stubs.c')) system_ss.add(files('virtio-hmp-cmds.c')) -specific_ss.add_all(when: 'CONFIG_VIRTIO', if_true: specific_virtio_ss) system_ss.add(when: 'CONFIG_ACPI', if_true: files('virtio-acpi.c')) diff --git a/hw/virtio/vhost-user.c b/hw/virtio/vhost-user.c index bb8f8eab77ea..a8907cca74ec 100644 --- a/hw/virtio/vhost-user.c +++ b/hw/virtio/vhost-user.c @@ -44,13 +44,8 @@ #define VHOST_USER_F_PROTOCOL_FEATURES 30 #define VHOST_USER_BACKEND_MAX_FDS 8 -#if defined(TARGET_PPC) || defined(TARGET_PPC64) -#include "hw/ppc/spapr.h" -#define VHOST_USER_MAX_RAM_SLOTS SPAPR_MAX_RAM_SLOTS - -#else +#include "hw/ppc/spapr_common.h" #define VHOST_USER_MAX_RAM_SLOTS 512 -#endif /* * Maximum size of virtio device config space @@ -2287,7 +2282,9 @@ static int vhost_user_backend_init(struct vhost_dev *dev, void *opaque, return -EINVAL; } - u->user->memory_slots = MIN(ram_slots, VHOST_USER_MAX_RAM_SLOTS); + const uint64_t vhost_user_max_ram_slots = target_base_ppc() ? + SPAPR_MAX_RAM_SLOTS : VHOST_USER_MAX_RAM_SLOTS; + u->user->memory_slots = MIN(ram_slots, vhost_user_max_ram_slots); } } diff --git a/hw/virtio/vhost.c b/hw/virtio/vhost.c index 52801c1796bc..b9dc4ed13ba0 100644 --- a/hw/virtio/vhost.c +++ b/hw/virtio/vhost.c @@ -1168,14 +1168,12 @@ static void vhost_log_stop(MemoryListener *listener, */ static inline bool vhost_needs_vring_endian(VirtIODevice *vdev) { - if (virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) { - return false; + if (virtio_vdev_is_legacy(vdev)) { + return vdev->device_endian == (HOST_BIG_ENDIAN + ? VIRTIO_DEVICE_ENDIAN_LITTLE + : VIRTIO_DEVICE_ENDIAN_BIG); } -#if HOST_BIG_ENDIAN - return vdev->device_endian == VIRTIO_DEVICE_ENDIAN_LITTLE; -#else - return vdev->device_endian == VIRTIO_DEVICE_ENDIAN_BIG; -#endif + return false; } static int vhost_virtqueue_set_vring_endian_legacy(struct vhost_dev *dev, @@ -1306,7 +1304,7 @@ int vhost_virtqueue_start(struct vhost_dev *dev, if (vhost_needs_vring_endian(vdev)) { r = vhost_virtqueue_set_vring_endian_legacy(dev, - virtio_is_big_endian(vdev), + virtio_vdev_is_big_endian(vdev), vhost_vq_index); if (r) { return r; @@ -1423,7 +1421,7 @@ static int do_vhost_virtqueue_stop(struct vhost_dev *dev, */ if (vhost_needs_vring_endian(vdev)) { vhost_virtqueue_set_vring_endian_legacy(dev, - !virtio_is_big_endian(vdev), + !virtio_vdev_is_big_endian(vdev), vhost_vq_index); } @@ -1926,8 +1924,8 @@ static bool vhost_inflight_buffer_pre_load(void *opaque, Error **errp) void *addr = qemu_memfd_alloc("vhost-inflight", inflight->size, F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL, &fd, errp); - if (*errp) { - return -ENOMEM; + if (!addr) { + return false; } inflight->offset = 0; diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c index 38bf1e84a1dd..4c5f486ba238 100644 --- a/hw/virtio/virtio-balloon.c +++ b/hw/virtio/virtio-balloon.c @@ -708,9 +708,6 @@ static size_t virtio_balloon_config_size(VirtIOBalloon *s) { uint64_t features = s->host_features; - if (s->qemu_4_0_config_size) { - return sizeof(struct virtio_balloon_config); - } if (virtio_has_feature(features, VIRTIO_BALLOON_F_PAGE_POISON)) { return sizeof(struct virtio_balloon_config); } @@ -1054,8 +1051,6 @@ static const Property virtio_balloon_properties[] = { * is disabled, resulting in QEMU 3.1 migration incompatibility. This * property retains this quirk for QEMU 4.1 machine types. */ - DEFINE_PROP_BOOL("qemu-4-0-config-size", VirtIOBalloon, - qemu_4_0_config_size, false), DEFINE_PROP_LINK("iothread", VirtIOBalloon, iothread, TYPE_IOTHREAD, IOThread *), }; diff --git a/hw/virtio/virtio-blk-pci.c b/hw/virtio/virtio-blk-pci.c index 64a434c81b59..3eecc23a6579 100644 --- a/hw/virtio/virtio-blk-pci.c +++ b/hw/virtio/virtio-blk-pci.c @@ -71,6 +71,7 @@ static void virtio_blk_pci_class_init(ObjectClass *klass, const void *data) set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); device_class_set_props(dc, virtio_blk_pci_properties); + pci_qdev_property_add_specifics(dc); k->realize = virtio_blk_pci_realize; pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET; pcidev_k->device_id = PCI_DEVICE_ID_VIRTIO_BLOCK; diff --git a/hw/virtio/virtio-mem.c b/hw/virtio/virtio-mem.c index c1e2defb68ec..a4b71974a1c9 100644 --- a/hw/virtio/virtio-mem.c +++ b/hw/virtio/virtio-mem.c @@ -331,14 +331,6 @@ static int virtio_mem_notify_populate_cb(MemoryRegionSection *s, void *arg) return rdl->notify_populate(rdl, s); } -static int virtio_mem_notify_discard_cb(MemoryRegionSection *s, void *arg) -{ - RamDiscardListener *rdl = arg; - - rdl->notify_discard(rdl, s); - return 0; -} - static void virtio_mem_notify_unplug(VirtIOMEM *vmem, uint64_t offset, uint64_t size) { @@ -398,12 +390,7 @@ static void virtio_mem_notify_unplug_all(VirtIOMEM *vmem) } QLIST_FOREACH(rdl, &vmem->rdl_list, next) { - if (rdl->double_discard_supported) { - rdl->notify_discard(rdl, rdl->section); - } else { - virtio_mem_for_each_plugged_section(vmem, rdl->section, rdl, - virtio_mem_notify_discard_cb); - } + rdl->notify_discard(rdl, rdl->section); } } @@ -607,18 +594,7 @@ static int virtio_mem_set_block_state(VirtIOMEM *vmem, uint64_t start_gpa, Error *local_err = NULL; if (!qemu_prealloc_mem(fd, area, size, 1, NULL, false, &local_err)) { - static bool warned; - - /* - * Warn only once, we don't want to fill the log with these - * warnings. - */ - if (!warned) { - warn_report_err(local_err); - warned = true; - } else { - error_free(local_err); - } + warn_report_err_once(local_err); ret = -EBUSY; } } @@ -1824,12 +1800,7 @@ static void virtio_mem_rdm_unregister_listener(RamDiscardManager *rdm, g_assert(rdl->section->mr == &vmem->memdev->mr); if (vmem->size) { - if (rdl->double_discard_supported) { - rdl->notify_discard(rdl, rdl->section); - } else { - virtio_mem_for_each_plugged_section(vmem, rdl->section, rdl, - virtio_mem_notify_discard_cb); - } + rdl->notify_discard(rdl, rdl->section); } memory_region_section_free_copy(rdl->section); diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c index fe13a7a95031..bcab2d18b80c 100644 --- a/hw/virtio/virtio-pci.c +++ b/hw/virtio/virtio-pci.c @@ -586,13 +586,13 @@ static uint64_t virtio_pci_config_read(void *opaque, hwaddr addr, break; case 2: val = virtio_config_readw(vdev, addr); - if (virtio_is_big_endian(vdev)) { + if (virtio_vdev_is_big_endian(vdev)) { val = bswap16(val); } break; case 4: val = virtio_config_readl(vdev, addr); - if (virtio_is_big_endian(vdev)) { + if (virtio_vdev_is_big_endian(vdev)) { val = bswap32(val); } break; @@ -625,13 +625,13 @@ static void virtio_pci_config_write(void *opaque, hwaddr addr, virtio_config_writeb(vdev, addr, val); break; case 2: - if (virtio_is_big_endian(vdev)) { + if (virtio_vdev_is_big_endian(vdev)) { val = bswap16(val); } virtio_config_writew(vdev, addr, val); break; case 4: - if (virtio_is_big_endian(vdev)) { + if (virtio_vdev_is_big_endian(vdev)) { val = bswap32(val); } virtio_config_writel(vdev, addr, val); @@ -1449,11 +1449,11 @@ static bool virtio_pci_queue_enabled(DeviceState *d, int n) VirtIOPCIProxy *proxy = VIRTIO_PCI(d); VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus); - if (virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) { - return proxy->vqs[n].enabled; + if (virtio_vdev_is_legacy(vdev)) { + return virtio_queue_enabled_legacy(vdev, n); } - return virtio_queue_enabled_legacy(vdev, n); + return proxy->vqs[n].enabled; } static int virtio_pci_add_mem_cap(VirtIOPCIProxy *proxy, @@ -2307,26 +2307,20 @@ static void virtio_pci_realize(PCIDevice *pci_dev, Error **errp) proxy->last_pcie_cap_offset += PCI_ERR_SIZEOF; } - if (proxy->flags & VIRTIO_PCI_FLAG_INIT_DEVERR) { - /* Init error enabling flags */ - pcie_cap_deverr_init(pci_dev); - } + /* Init error enabling flags */ + pcie_cap_deverr_init(pci_dev); - if (proxy->flags & VIRTIO_PCI_FLAG_INIT_LNKCTL) { - /* Init Link Control Register */ - pcie_cap_lnkctl_init(pci_dev); - } + /* Init Link Control Register */ + pcie_cap_lnkctl_init(pci_dev); if (proxy->flags & VIRTIO_PCI_FLAG_PM_NO_SOFT_RESET) { pci_set_word(pci_dev->config + pos + PCI_PM_CTRL, PCI_PM_CTRL_NO_SOFT_RESET); } - if (proxy->flags & VIRTIO_PCI_FLAG_INIT_PM) { - /* Init Power Management Control Register */ - pci_set_word(pci_dev->wmask + pos + PCI_PM_CTRL, - PCI_PM_CTRL_STATE_MASK); - } + /* Init Power Management Control Register */ + pci_set_word(pci_dev->wmask + pos + PCI_PM_CTRL, + PCI_PM_CTRL_STATE_MASK); if (proxy->flags & VIRTIO_PCI_FLAG_ATS) { pcie_ats_init(pci_dev, proxy->last_pcie_cap_offset, @@ -2422,16 +2416,11 @@ static void virtio_pci_bus_reset_hold(Object *obj, ResetType type) virtio_pci_reset(qdev); if (pci_is_express(dev)) { - VirtIOPCIProxy *proxy = VIRTIO_PCI(dev); - pcie_cap_deverr_reset(dev); pcie_cap_lnkctl_reset(dev); - if (proxy->flags & VIRTIO_PCI_FLAG_INIT_PM) { - pci_word_test_and_clear_mask( - dev->config + dev->pm_cap + PCI_PM_CTRL, - PCI_PM_CTRL_STATE_MASK); - } + pci_word_test_and_clear_mask(dev->config + dev->pm_cap + PCI_PM_CTRL, + PCI_PM_CTRL_STATE_MASK); } } @@ -2446,12 +2435,6 @@ static const Property virtio_pci_properties[] = { VIRTIO_PCI_FLAG_ATS_BIT, false), DEFINE_PROP_BIT("x-ats-page-aligned", VirtIOPCIProxy, flags, VIRTIO_PCI_FLAG_ATS_PAGE_ALIGNED_BIT, true), - DEFINE_PROP_BIT("x-pcie-deverr-init", VirtIOPCIProxy, flags, - VIRTIO_PCI_FLAG_INIT_DEVERR_BIT, true), - DEFINE_PROP_BIT("x-pcie-lnkctl-init", VirtIOPCIProxy, flags, - VIRTIO_PCI_FLAG_INIT_LNKCTL_BIT, true), - DEFINE_PROP_BIT("x-pcie-pm-init", VirtIOPCIProxy, flags, - VIRTIO_PCI_FLAG_INIT_PM_BIT, true), DEFINE_PROP_BIT("x-pcie-pm-no-soft-reset", VirtIOPCIProxy, flags, VIRTIO_PCI_FLAG_PM_NO_SOFT_RESET_BIT, false), DEFINE_PROP_BIT("x-pcie-flr-init", VirtIOPCIProxy, flags, diff --git a/hw/virtio/virtio-qmp.c b/hw/virtio/virtio-qmp.c index 968299fda0c9..f9cdca50d990 100644 --- a/hw/virtio/virtio-qmp.c +++ b/hw/virtio/virtio-qmp.c @@ -33,21 +33,17 @@ #include "standard-headers/linux/virtio_vsock.h" #include "standard-headers/linux/virtio_gpio.h" -#include CONFIG_DEVICES - #define FEATURE_ENTRY(name, desc) (qmp_virtio_feature_map_t) \ { .virtio_bit = name, .feature_desc = desc } /* Virtio transport features mapping */ static const qmp_virtio_feature_map_t virtio_transport_map[] = { /* Virtio device transport features */ -#ifndef VIRTIO_CONFIG_NO_LEGACY FEATURE_ENTRY(VIRTIO_F_NOTIFY_ON_EMPTY, \ "VIRTIO_F_NOTIFY_ON_EMPTY: Notify when device runs out of avail. " "descs. on VQ"), FEATURE_ENTRY(VIRTIO_F_ANY_LAYOUT, \ "VIRTIO_F_ANY_LAYOUT: Device accepts arbitrary desc. layouts"), -#endif /* !VIRTIO_CONFIG_NO_LEGACY */ FEATURE_ENTRY(VIRTIO_F_VERSION_1, \ "VIRTIO_F_VERSION_1: Device compliant for v1 spec (legacy)"), FEATURE_ENTRY(VIRTIO_F_IOMMU_PLATFORM, \ @@ -149,7 +145,6 @@ static const qmp_virtio_feature_map_t virtio_config_status_map[] = { }; /* virtio-blk features mapping */ -#ifdef CONFIG_VIRTIO_BLK static const qmp_virtio_feature_map_t virtio_blk_feature_map[] = { FEATURE_ENTRY(VIRTIO_BLK_F_SIZE_MAX, \ "VIRTIO_BLK_F_SIZE_MAX: Max segment size is size_max"), @@ -173,7 +168,6 @@ static const qmp_virtio_feature_map_t virtio_blk_feature_map[] = { "VIRTIO_BLK_F_SECURE_ERASE: Secure erase supported"), FEATURE_ENTRY(VIRTIO_BLK_F_ZONED, \ "VIRTIO_BLK_F_ZONED: Zoned block devices"), -#ifndef VIRTIO_BLK_NO_LEGACY FEATURE_ENTRY(VIRTIO_BLK_F_BARRIER, \ "VIRTIO_BLK_F_BARRIER: Request barriers supported"), FEATURE_ENTRY(VIRTIO_BLK_F_SCSI, \ @@ -183,7 +177,6 @@ static const qmp_virtio_feature_map_t virtio_blk_feature_map[] = { FEATURE_ENTRY(VIRTIO_BLK_F_CONFIG_WCE, \ "VIRTIO_BLK_F_CONFIG_WCE: Cache writeback and writethrough modes " "supported"), -#endif /* !VIRTIO_BLK_NO_LEGACY */ FEATURE_ENTRY(VHOST_F_LOG_ALL, \ "VHOST_F_LOG_ALL: Logging write descriptors supported"), FEATURE_ENTRY(VHOST_USER_F_PROTOCOL_FEATURES, \ @@ -191,10 +184,8 @@ static const qmp_virtio_feature_map_t virtio_blk_feature_map[] = { "negotiation supported"), { -1, "" } }; -#endif /* virtio-serial features mapping */ -#ifdef CONFIG_VIRTIO_SERIAL static const qmp_virtio_feature_map_t virtio_serial_feature_map[] = { FEATURE_ENTRY(VIRTIO_CONSOLE_F_SIZE, \ "VIRTIO_CONSOLE_F_SIZE: Host providing console size"), @@ -204,10 +195,8 @@ static const qmp_virtio_feature_map_t virtio_serial_feature_map[] = { "VIRTIO_CONSOLE_F_EMERG_WRITE: Emergency write supported"), { -1, "" } }; -#endif /* virtio-gpu features mapping */ -#ifdef CONFIG_VIRTIO_GPU static const qmp_virtio_feature_map_t virtio_gpu_feature_map[] = { FEATURE_ENTRY(VIRTIO_GPU_F_VIRGL, \ "VIRTIO_GPU_F_VIRGL: Virgl 3D mode supported"), @@ -227,10 +216,8 @@ static const qmp_virtio_feature_map_t virtio_gpu_feature_map[] = { "negotiation supported"), { -1, "" } }; -#endif /* virtio-input features mapping */ -#ifdef CONFIG_VIRTIO_INPUT static const qmp_virtio_feature_map_t virtio_input_feature_map[] = { FEATURE_ENTRY(VHOST_F_LOG_ALL, \ "VHOST_F_LOG_ALL: Logging write descriptors supported"), @@ -239,10 +226,8 @@ static const qmp_virtio_feature_map_t virtio_input_feature_map[] = { "negotiation supported"), { -1, "" } }; -#endif /* virtio-net features mapping */ -#ifdef CONFIG_VIRTIO_NET static const qmp_virtio_feature_map_t virtio_net_feature_map[] = { FEATURE_ENTRY(VIRTIO_NET_F_CSUM, \ "VIRTIO_NET_F_CSUM: Device handling packets with partial checksum " @@ -313,10 +298,8 @@ static const qmp_virtio_feature_map_t virtio_net_feature_map[] = { "device with same MAC addr. supported"), FEATURE_ENTRY(VIRTIO_NET_F_SPEED_DUPLEX, \ "VIRTIO_NET_F_SPEED_DUPLEX: Device set linkspeed and duplex"), -#ifndef VIRTIO_NET_NO_LEGACY FEATURE_ENTRY(VIRTIO_NET_F_GSO, \ "VIRTIO_NET_F_GSO: Handling GSO-type packets supported"), -#endif /* !VIRTIO_NET_NO_LEGACY */ FEATURE_ENTRY(VHOST_NET_F_VIRTIO_NET_HDR, \ "VHOST_NET_F_VIRTIO_NET_HDR: Virtio-net headers for RX and TX " "packets supported"), @@ -341,10 +324,8 @@ static const qmp_virtio_feature_map_t virtio_net_feature_map[] = { "header"), { -1, "" } }; -#endif /* virtio-scsi features mapping */ -#ifdef CONFIG_VIRTIO_SCSI static const qmp_virtio_feature_map_t virtio_scsi_feature_map[] = { FEATURE_ENTRY(VIRTIO_SCSI_F_INOUT, \ "VIRTIO_SCSI_F_INOUT: Requests including read and writable data " @@ -364,10 +345,8 @@ static const qmp_virtio_feature_map_t virtio_scsi_feature_map[] = { "negotiation supported"), { -1, "" } }; -#endif /* virtio/vhost-user-fs features mapping */ -#ifdef CONFIG_VHOST_USER_FS static const qmp_virtio_feature_map_t virtio_fs_feature_map[] = { FEATURE_ENTRY(VHOST_F_LOG_ALL, \ "VHOST_F_LOG_ALL: Logging write descriptors supported"), @@ -376,10 +355,8 @@ static const qmp_virtio_feature_map_t virtio_fs_feature_map[] = { "negotiation supported"), { -1, "" } }; -#endif /* virtio/vhost-user-i2c features mapping */ -#ifdef CONFIG_VIRTIO_I2C_ADAPTER static const qmp_virtio_feature_map_t virtio_i2c_feature_map[] = { FEATURE_ENTRY(VIRTIO_I2C_F_ZERO_LENGTH_REQUEST, \ "VIRTIO_I2C_F_ZERO_LEGNTH_REQUEST: Zero length requests supported"), @@ -390,10 +367,8 @@ static const qmp_virtio_feature_map_t virtio_i2c_feature_map[] = { "negotiation supported"), { -1, "" } }; -#endif /* virtio/vhost-vsock features mapping */ -#ifdef CONFIG_VHOST_VSOCK static const qmp_virtio_feature_map_t virtio_vsock_feature_map[] = { FEATURE_ENTRY(VIRTIO_VSOCK_F_SEQPACKET, \ "VIRTIO_VSOCK_F_SEQPACKET: SOCK_SEQPACKET supported"), @@ -404,10 +379,8 @@ static const qmp_virtio_feature_map_t virtio_vsock_feature_map[] = { "negotiation supported"), { -1, "" } }; -#endif /* virtio-balloon features mapping */ -#ifdef CONFIG_VIRTIO_BALLOON static const qmp_virtio_feature_map_t virtio_balloon_feature_map[] = { FEATURE_ENTRY(VIRTIO_BALLOON_F_MUST_TELL_HOST, \ "VIRTIO_BALLOON_F_MUST_TELL_HOST: Tell host before reclaiming " @@ -424,19 +397,15 @@ static const qmp_virtio_feature_map_t virtio_balloon_feature_map[] = { "VIRTIO_BALLOON_F_REPORTING: Page reporting VQ enabled"), { -1, "" } }; -#endif /* virtio-crypto features mapping */ -#ifdef CONFIG_VIRTIO_CRYPTO static const qmp_virtio_feature_map_t virtio_crypto_feature_map[] = { FEATURE_ENTRY(VHOST_F_LOG_ALL, \ "VHOST_F_LOG_ALL: Logging write descriptors supported"), { -1, "" } }; -#endif /* virtio-iommu features mapping */ -#ifdef CONFIG_VIRTIO_IOMMU static const qmp_virtio_feature_map_t virtio_iommu_feature_map[] = { FEATURE_ENTRY(VIRTIO_IOMMU_F_INPUT_RANGE, \ "VIRTIO_IOMMU_F_INPUT_RANGE: Range of available virtual addrs. " @@ -458,15 +427,11 @@ static const qmp_virtio_feature_map_t virtio_iommu_feature_map[] = { "available"), { -1, "" } }; -#endif /* virtio-mem features mapping */ -#ifdef CONFIG_VIRTIO_MEM static const qmp_virtio_feature_map_t virtio_mem_feature_map[] = { -#ifndef CONFIG_ACPI FEATURE_ENTRY(VIRTIO_MEM_F_ACPI_PXM, \ "VIRTIO_MEM_F_ACPI_PXM: node_id is an ACPI PXM and is valid"), -#endif /* !CONFIG_ACPI */ FEATURE_ENTRY(VIRTIO_MEM_F_UNPLUGGED_INACCESSIBLE, \ "VIRTIO_MEM_F_UNPLUGGED_INACCESSIBLE: Unplugged memory cannot be " "accessed"), @@ -475,10 +440,8 @@ static const qmp_virtio_feature_map_t virtio_mem_feature_map[] = { "plugged when suspending+resuming"), { -1, "" } }; -#endif /* virtio-rng features mapping */ -#ifdef CONFIG_VIRTIO_RNG static const qmp_virtio_feature_map_t virtio_rng_feature_map[] = { FEATURE_ENTRY(VHOST_F_LOG_ALL, \ "VHOST_F_LOG_ALL: Logging write descriptors supported"), @@ -487,10 +450,8 @@ static const qmp_virtio_feature_map_t virtio_rng_feature_map[] = { "negotiation supported"), { -1, "" } }; -#endif /* virtio/vhost-gpio features mapping */ -#ifdef CONFIG_VHOST_USER_GPIO static const qmp_virtio_feature_map_t virtio_gpio_feature_map[] = { FEATURE_ENTRY(VIRTIO_GPIO_F_IRQ, \ "VIRTIO_GPIO_F_IRQ: Device supports interrupts on GPIO lines"), @@ -499,7 +460,6 @@ static const qmp_virtio_feature_map_t virtio_gpio_feature_map[] = { "negotiation supported"), { -1, "" } }; -#endif #define CONVERT_FEATURES(type, map, is_status, bitmap) \ ({ \ @@ -595,96 +555,66 @@ VirtioDeviceFeatures *qmp_decode_features(uint16_t device_id, /* device features */ switch (device_id) { -#ifdef CONFIG_VIRTIO_SERIAL case VIRTIO_ID_CONSOLE: features->dev_features = CONVERT_FEATURES_EX(strList, virtio_serial_feature_map, bitmap); break; -#endif -#ifdef CONFIG_VIRTIO_BLK case VIRTIO_ID_BLOCK: features->dev_features = CONVERT_FEATURES_EX(strList, virtio_blk_feature_map, bitmap); break; -#endif -#ifdef CONFIG_VIRTIO_GPU case VIRTIO_ID_GPU: features->dev_features = CONVERT_FEATURES_EX(strList, virtio_gpu_feature_map, bitmap); break; -#endif -#ifdef CONFIG_VIRTIO_NET case VIRTIO_ID_NET: features->dev_features = CONVERT_FEATURES_EX(strList, virtio_net_feature_map, bitmap); break; -#endif -#ifdef CONFIG_VIRTIO_SCSI case VIRTIO_ID_SCSI: features->dev_features = CONVERT_FEATURES_EX(strList, virtio_scsi_feature_map, bitmap); break; -#endif -#ifdef CONFIG_VIRTIO_BALLOON case VIRTIO_ID_BALLOON: features->dev_features = CONVERT_FEATURES_EX(strList, virtio_balloon_feature_map, bitmap); break; -#endif -#ifdef CONFIG_VIRTIO_IOMMU case VIRTIO_ID_IOMMU: features->dev_features = CONVERT_FEATURES_EX(strList, virtio_iommu_feature_map, bitmap); break; -#endif -#ifdef CONFIG_VIRTIO_INPUT case VIRTIO_ID_INPUT: features->dev_features = CONVERT_FEATURES_EX(strList, virtio_input_feature_map, bitmap); break; -#endif -#ifdef CONFIG_VHOST_USER_FS case VIRTIO_ID_FS: features->dev_features = CONVERT_FEATURES_EX(strList, virtio_fs_feature_map, bitmap); break; -#endif -#ifdef CONFIG_VHOST_VSOCK case VIRTIO_ID_VSOCK: features->dev_features = CONVERT_FEATURES_EX(strList, virtio_vsock_feature_map, bitmap); break; -#endif -#ifdef CONFIG_VIRTIO_CRYPTO case VIRTIO_ID_CRYPTO: features->dev_features = CONVERT_FEATURES_EX(strList, virtio_crypto_feature_map, bitmap); break; -#endif -#ifdef CONFIG_VIRTIO_MEM case VIRTIO_ID_MEM: features->dev_features = CONVERT_FEATURES_EX(strList, virtio_mem_feature_map, bitmap); break; -#endif -#ifdef CONFIG_VIRTIO_I2C_ADAPTER case VIRTIO_ID_I2C_ADAPTER: features->dev_features = CONVERT_FEATURES_EX(strList, virtio_i2c_feature_map, bitmap); break; -#endif -#ifdef CONFIG_VIRTIO_RNG case VIRTIO_ID_RNG: features->dev_features = CONVERT_FEATURES_EX(strList, virtio_rng_feature_map, bitmap); break; -#endif -#ifdef CONFIG_VHOST_USER_GPIO case VIRTIO_ID_GPIO: features->dev_features = CONVERT_FEATURES_EX(strList, virtio_gpio_feature_map, bitmap); break; -#endif /* No features */ case VIRTIO_ID_9P: case VIRTIO_ID_PMEM: diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c index 77ca54e52065..0ba734d0bc65 100644 --- a/hw/virtio/virtio.c +++ b/hw/virtio/virtio.c @@ -222,7 +222,7 @@ static inline uint16_t virtio_lduw_phys_cached(VirtIODevice *vdev, MemoryRegionCache *cache, hwaddr pa) { - if (virtio_access_is_big_endian(vdev)) { + if (virtio_vdev_is_big_endian(vdev)) { return lduw_be_phys_cached(cache, pa); } return lduw_le_phys_cached(cache, pa); @@ -232,7 +232,7 @@ static inline void virtio_stw_phys_cached(VirtIODevice *vdev, MemoryRegionCache *cache, hwaddr pa, uint16_t value) { - if (virtio_access_is_big_endian(vdev)) { + if (virtio_vdev_is_big_endian(vdev)) { stw_be_phys_cached(cache, pa, value); } else { stw_le_phys_cached(cache, pa, value); @@ -281,10 +281,12 @@ void virtio_init_region_cache(VirtIODevice *vdev, int n) len = address_space_cache_init(&new->desc, vdev->dma_as, addr, size, packed); if (len < size) { + g_autofree const char *devname = qdev_get_printable_name(DEVICE(vdev)); + virtio_error(vdev, "Failed to map descriptor ring for device %s: " "invalid guest physical address or corrupted queue setup", - qdev_get_printable_name(DEVICE(vdev))); + devname); goto err_desc; } @@ -292,10 +294,12 @@ void virtio_init_region_cache(VirtIODevice *vdev, int n) len = address_space_cache_init(&new->used, vdev->dma_as, vq->vring.used, size, true); if (len < size) { + g_autofree const char *devname = qdev_get_printable_name(DEVICE(vdev)); + virtio_error(vdev, "Failed to map used ring for device %s: " "possible guest misconfiguration or insufficient memory", - qdev_get_printable_name(DEVICE(vdev))); + devname); goto err_used; } @@ -303,10 +307,12 @@ void virtio_init_region_cache(VirtIODevice *vdev, int n) len = address_space_cache_init(&new->avail, vdev->dma_as, vq->vring.avail, size, false); if (len < size) { + g_autofree const char *devname = qdev_get_printable_name(DEVICE(vdev)); + virtio_error(vdev, "Failed to map avalaible ring for device %s: " "possible queue misconfiguration or overlapping memory region", - qdev_get_printable_name(DEVICE(vdev))); + devname); goto err_avail; } @@ -2753,7 +2759,7 @@ static bool virtio_device_endian_needed(void *opaque) VirtIODevice *vdev = opaque; assert(vdev->device_endian != VIRTIO_DEVICE_ENDIAN_UNKNOWN); - if (!virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) { + if (virtio_vdev_is_legacy(vdev)) { return vdev->device_endian != virtio_default_endian(); } /* Devices conforming to VIRTIO 1.0 or later are always LE. */ @@ -3235,9 +3241,8 @@ int virtio_set_features_ex(VirtIODevice *vdev, const uint64_t *features) return ret; } -void virtio_reset(void *opaque) +void virtio_reset(VirtIODevice *vdev) { - VirtIODevice *vdev = opaque; VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev); uint64_t features[VIRTIO_FEATURES_NU64S]; int i; @@ -3461,10 +3466,10 @@ virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id) * to calculate used and avail ring addresses based on the desc * address. */ - if (virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) { - virtio_init_region_cache(vdev, i); - } else { + if (virtio_vdev_is_legacy(vdev)) { virtio_queue_update_rings(vdev, i); + } else { + virtio_init_region_cache(vdev, i); } if (virtio_vdev_has_feature(vdev, VIRTIO_F_RING_PACKED)) { @@ -4498,3 +4503,13 @@ QEMUBH *virtio_bh_new_guarded_full(DeviceState *dev, return qemu_bh_new_full(cb, opaque, name, &transport->mem_reentrancy_guard); } + +QEMUBH *virtio_bh_io_new_guarded_full(DeviceState *dev, + QEMUBHFunc *cb, void *opaque, + const char *name) +{ + DeviceState *transport = qdev_get_parent_bus(dev)->parent; + + return aio_bh_new_full(iohandler_get_aio_context(), cb, opaque, name, + &transport->mem_reentrancy_guard); +} diff --git a/hw/xen/xen-bus-helper.c b/hw/xen/xen-bus-helper.c index 288fad422be3..1087a585cc71 100644 --- a/hw/xen/xen-bus-helper.c +++ b/hw/xen/xen-bus-helper.c @@ -151,7 +151,7 @@ char *xs_node_read(struct qemu_xs_handle *h, xs_transaction_t tid, va_end(ap); value = qemu_xen_xs_read(h, tid, path, len); - trace_xs_node_read(path, value); + trace_xs_node_read(path, value ? value : ""); if (!value) { error_setg_errno(errp, errno, "failed to read from '%s'", path); } diff --git a/hw/xen/xen-hvm-common.c b/hw/xen/xen-hvm-common.c index 59c73dfaeb59..62d88804c432 100644 --- a/hw/xen/xen-hvm-common.c +++ b/hw/xen/xen-hvm-common.c @@ -4,6 +4,7 @@ #include "qemu/error-report.h" #include "qemu/target-info.h" #include "qapi/error.h" +#include "qapi/qapi-events-misc.h" #include "exec/target_page.h" #include "trace.h" @@ -22,19 +23,19 @@ MemoryRegion xen_memory, xen_grants; /* Check for any kind of xen memory, foreign mappings or grants. */ -bool xen_mr_is_memory(MemoryRegion *mr) +bool xen_mr_is_memory(const MemoryRegion *mr) { return mr == &xen_memory || mr == &xen_grants; } /* Check specifically for grants. */ -bool xen_mr_is_grants(MemoryRegion *mr) +bool xen_mr_is_grants(const MemoryRegion *mr) { return mr == &xen_grants; } -void xen_ram_alloc(ram_addr_t ram_addr, ram_addr_t size, MemoryRegion *mr, - Error **errp) +void xen_ram_alloc(ram_addr_t ram_addr, ram_addr_t size, + const MemoryRegion *mr, Error **errp) { unsigned target_page_bits = qemu_target_page_bits(); unsigned long nr_pfn; @@ -471,9 +472,12 @@ static void handle_ioreq(XenIOState *state, ioreq_t *req) cpu_ioreq_move(req); break; case IOREQ_TYPE_TIMEOFFSET: + qapi_event_send_rtc_change((int64_t)req->data, ""); break; case IOREQ_TYPE_INVALIDATE: - xen_invalidate_map_cache(); + if (xen_map_cache_enabled()) { + xen_invalidate_map_cache(); + } break; case IOREQ_TYPE_PCI_CONFIG: cpu_ioreq_config(state, req); @@ -823,7 +827,8 @@ void xen_shutdown_fatal_error(const char *fmt, ...) static void xen_do_ioreq_register(XenIOState *state, unsigned int max_cpus, - const MemoryListener *xen_memory_listener) + const MemoryListener *xen_memory_listener, + bool mapcache) { int i, rc; @@ -874,11 +879,13 @@ static void xen_do_ioreq_register(XenIOState *state, state->bufioreq_local_port = rc; } /* Init RAM management */ + if (mapcache) { #ifdef XEN_COMPAT_PHYSMAP - xen_map_cache_init(xen_phys_offset_to_gaddr, state); + xen_map_cache_init(xen_phys_offset_to_gaddr, state); #else - xen_map_cache_init(NULL, state); + xen_map_cache_init(NULL, state); #endif + } qemu_add_vm_change_state_handler(xen_hvm_change_state_handler, state); @@ -901,7 +908,8 @@ static void xen_do_ioreq_register(XenIOState *state, void xen_register_ioreq(XenIOState *state, unsigned int max_cpus, uint8_t handle_bufioreq, - const MemoryListener *xen_memory_listener) + const MemoryListener *xen_memory_listener, + bool mapcache) { int rc; @@ -922,7 +930,7 @@ void xen_register_ioreq(XenIOState *state, unsigned int max_cpus, state->has_bufioreq = handle_bufioreq != HVM_IOREQSRV_BUFIOREQ_OFF; rc = xen_create_ioreq_server(xen_domid, handle_bufioreq, &state->ioservid); if (!rc) { - xen_do_ioreq_register(state, max_cpus, xen_memory_listener); + xen_do_ioreq_register(state, max_cpus, xen_memory_listener, mapcache); } else { warn_report("xen: failed to create ioreq server"); } diff --git a/hw/xen/xen-mapcache.c b/hw/xen/xen-mapcache.c index 11115f608451..85cf0cf359ca 100644 --- a/hw/xen/xen-mapcache.c +++ b/hw/xen/xen-mapcache.c @@ -80,6 +80,12 @@ static MapCache *mapcache_grants_ro; static MapCache *mapcache_grants_rw; static xengnttab_handle *xen_region_gnttabdev; +bool xen_map_cache_enabled(void) +{ + /* Map cache enabled implies xen_enabled(). */ + return xen_enabled() && mapcache; +} + static inline void mapcache_lock(MapCache *mc) { qemu_mutex_lock(&mc->lock); @@ -454,7 +460,7 @@ static uint8_t *xen_map_cache_unlocked(MapCache *mc, return mc->last_entry->vaddr_base + address_offset; } -uint8_t *xen_map_cache(MemoryRegion *mr, +uint8_t *xen_map_cache(const MemoryRegion *mr, hwaddr phys_addr, hwaddr size, ram_addr_t ram_addr_offset, uint8_t lock, bool dma, @@ -464,6 +470,8 @@ uint8_t *xen_map_cache(MemoryRegion *mr, MapCache *mc = mapcache; uint8_t *p; + assert(mapcache); + if (grant) { mc = is_write ? mapcache_grants_rw : mapcache_grants_ro; } @@ -530,6 +538,8 @@ ram_addr_t xen_ram_addr_from_mapcache(void *ptr) { ram_addr_t addr; + assert(mapcache); + addr = xen_ram_addr_from_mapcache_single(mapcache, ptr); if (addr == RAM_ADDR_INVALID) { addr = xen_ram_addr_from_mapcache_single(mapcache_grants_ro, ptr); @@ -652,6 +662,8 @@ static void xen_invalidate_map_cache_entry_bh(void *opaque) void coroutine_mixed_fn xen_invalidate_map_cache_entry(uint8_t *buffer) { + assert(mapcache); + if (qemu_in_coroutine()) { XenMapCacheData data = { .co = qemu_coroutine_self(), @@ -709,6 +721,8 @@ static void xen_invalidate_map_cache_single(MapCache *mc) void xen_invalidate_map_cache(void) { + assert(mapcache); + /* Flush pending AIO before destroying the mapcache */ bdrv_drain_all(); @@ -776,6 +790,8 @@ uint8_t *xen_replace_cache_entry(hwaddr old_phys_addr, { uint8_t *p; + assert(mapcache); + mapcache_lock(mapcache); p = xen_replace_cache_entry_unlocked(mapcache, old_phys_addr, new_phys_addr, size); diff --git a/hw/xen/xen-pvh-common.c b/hw/xen/xen-pvh-common.c index 1381310fc710..cca37202ffb2 100644 --- a/hw/xen/xen-pvh-common.c +++ b/hw/xen/xen-pvh-common.c @@ -29,6 +29,69 @@ static const MemoryListener xen_memory_listener = { .priority = MEMORY_LISTENER_PRIORITY_ACCEL, }; +/* + * Map foreign RAM in bounded chunks so we don't build a PFN array for the + * entire guest size (which can be huge for large guests). We reserve a VA + * range once and then MAP_FIXED each chunk into place. + */ +#define XEN_PVH_MAP_CHUNK_PAGES 65535 + +static void *xen_map_guest_ram(XenPVHMachineState *s, + uint64_t addr, uint64_t size) +{ + size_t total_pages = size >> XC_PAGE_SHIFT; + size_t chunk_pages = MIN(XEN_PVH_MAP_CHUNK_PAGES, total_pages); + g_autofree xen_pfn_t *pfns = NULL; + void *base = NULL; + size_t offset; + + if (!total_pages) { + goto done; + } + + base = mmap(NULL, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (base == MAP_FAILED) { + base = NULL; + goto done; + } + + pfns = g_new0(xen_pfn_t, chunk_pages); + if (!pfns) { + munmap(base, size); + base = NULL; + goto done; + } + + for (offset = 0; offset < total_pages; offset += chunk_pages) { + size_t num_pages = MIN(chunk_pages, total_pages - offset); + void *mapped; + size_t i; + + for (i = 0; i < num_pages; i++) { + pfns[i] = (addr >> XC_PAGE_SHIFT) + offset + i; + } + + mapped = xenforeignmemory_map2( + xen_fmem, xen_domid, + (uint8_t *)base + (offset << XC_PAGE_SHIFT), + PROT_READ | PROT_WRITE, MAP_FIXED, + num_pages, pfns, NULL); + if (!mapped) { + munmap(base, size); + base = NULL; + goto done; + } + } +done: + if (!base) { + /* We can't recover from this. */ + error_report("FATAL: Failed to foreign-map %" PRIx64 " - %" PRIx64, + addr, addr + size); + exit(EXIT_FAILURE); + } + return base; +} + static void xen_pvh_init_ram(XenPVHMachineState *s, MemoryRegion *sysmem) { @@ -45,22 +108,42 @@ static void xen_pvh_init_ram(XenPVHMachineState *s, block_len = s->cfg.ram_high.base + ram_size[1]; } - memory_region_init_ram(&xen_memory, NULL, "xen.ram", block_len, - &error_fatal); + if (s->cfg.mapcache) { + memory_region_init_ram(&xen_memory, NULL, "xen.ram", + block_len, &error_fatal); + memory_region_init_alias(&s->ram.low, NULL, "xen.ram.lo", &xen_memory, + s->cfg.ram_low.base, ram_size[0]); + if (ram_size[1] > 0) { + memory_region_init_alias(&s->ram.high, NULL, "xen.ram.hi", + &xen_memory, + s->cfg.ram_high.base, ram_size[1]); + } + } else { + void *p; + + p = xen_map_guest_ram(s, s->cfg.ram_low.base, ram_size[0]); + memory_region_init_ram_ptr(&s->ram.low, NULL, "xen.ram.lo", + ram_size[0], p); + if (ram_size[1] > 0) { + p = xen_map_guest_ram(s, s->cfg.ram_high.base, ram_size[1]); + memory_region_init_ram_ptr(&s->ram.high, NULL, "xen.ram.hi", + ram_size[1], p); + } + } - memory_region_init_alias(&s->ram.low, NULL, "xen.ram.lo", &xen_memory, - s->cfg.ram_low.base, ram_size[0]); + /* Map them onto QEMU's address-space. */ memory_region_add_subregion(sysmem, s->cfg.ram_low.base, &s->ram.low); if (ram_size[1] > 0) { - memory_region_init_alias(&s->ram.high, NULL, "xen.ram.hi", &xen_memory, - s->cfg.ram_high.base, ram_size[1]); memory_region_add_subregion(sysmem, s->cfg.ram_high.base, &s->ram.high); } - /* Setup support for grants. */ - memory_region_init_ram(&xen_grants, NULL, "xen.grants", block_len, - &error_fatal); - memory_region_add_subregion(sysmem, XEN_GRANT_ADDR_OFF, &xen_grants); + /* Grants are only supported when the mapcache is on. */ + if (s->cfg.mapcache) { + /* Setup support for grants. */ + memory_region_init_ram(&xen_grants, NULL, "xen.grants", block_len, + &error_fatal); + memory_region_add_subregion(sysmem, XEN_GRANT_ADDR_OFF, &xen_grants); + } } static void xen_set_irq(void *opaque, int irq, int level) @@ -202,7 +285,8 @@ static void xen_pvh_init(MachineState *ms) xen_pvh_init_ram(s, sysmem); xen_register_ioreq(&s->ioreq, ms->smp.max_cpus, xpc->handle_bufioreq, - &xen_memory_listener); + &xen_memory_listener, + s->cfg.mapcache); if (s->cfg.virtio_mmio_num) { xen_create_virtio_mmio_devices(s); @@ -284,6 +368,20 @@ XEN_PVH_PROP_MEMMAP(pci_ecam) XEN_PVH_PROP_MEMMAP(pci_mmio) XEN_PVH_PROP_MEMMAP(pci_mmio_high) +static void xen_pvh_set_mapcache(Object *obj, bool value, Error **errp) +{ + XenPVHMachineState *xp = XEN_PVH_MACHINE(obj); + + xp->cfg.mapcache = value; +} + +static bool xen_pvh_get_mapcache(Object *obj, Error **errp) +{ + XenPVHMachineState *xp = XEN_PVH_MACHINE(obj); + + return xp->cfg.mapcache; +} + static void xen_pvh_set_pci_intx_irq_base(Object *obj, Visitor *v, const char *name, void *opaque, Error **errp) @@ -337,6 +435,12 @@ do { \ OC_MEMMAP_PROP_SIZE(c, prop_name, name); \ } while (0) + object_class_property_add_bool(oc, "mapcache", xen_pvh_get_mapcache, + xen_pvh_set_mapcache); + object_class_property_set_description(oc, "mapcache", + "Set on/off to enable/disable the " + "mapcache"); + /* * We provide memmap properties to allow Xen to move things to other * addresses for example when users need to accomodate the memory-map @@ -376,6 +480,13 @@ do { \ #endif } +static void xen_pvh_instance_init(Object *obj) +{ + XenPVHMachineState *xp = XEN_PVH_MACHINE(obj); + + xp->cfg.mapcache = true; +} + static void xen_pvh_class_init(ObjectClass *oc, const void *data) { MachineClass *mc = MACHINE_CLASS(oc); @@ -394,6 +505,7 @@ static const TypeInfo xen_pvh_info = { .parent = TYPE_MACHINE, .abstract = true, .instance_size = sizeof(XenPVHMachineState), + .instance_init = xen_pvh_instance_init, .class_size = sizeof(XenPVHMachineClass), .class_init = xen_pvh_class_init, }; diff --git a/hw/xen/xen_stubs.c b/hw/xen/xen_stubs.c index 5e565df39297..f830768d9983 100644 --- a/hw/xen/xen_stubs.c +++ b/hw/xen/xen_stubs.c @@ -19,16 +19,21 @@ void xen_hvm_modified_memory(ram_addr_t start, ram_addr_t length) } void xen_ram_alloc(ram_addr_t ram_addr, ram_addr_t size, - struct MemoryRegion *mr, Error **errp) + const MemoryRegion *mr, Error **errp) { g_assert_not_reached(); } -bool xen_mr_is_memory(MemoryRegion *mr) +bool xen_mr_is_memory(const MemoryRegion *mr) { g_assert_not_reached(); } +bool xen_map_cache_enabled(void) +{ + return false; +} + void xen_invalidate_map_cache_entry(uint8_t *buffer) { g_assert_not_reached(); @@ -39,7 +44,7 @@ ram_addr_t xen_ram_addr_from_mapcache(void *ptr) g_assert_not_reached(); } -uint8_t *xen_map_cache(MemoryRegion *mr, +uint8_t *xen_map_cache(const MemoryRegion *mr, hwaddr phys_addr, hwaddr size, ram_addr_t ram_addr_offset, diff --git a/hw/xtensa/xtfpga.c b/hw/xtensa/xtfpga.c index d427d68e5058..ed24720f94a0 100644 --- a/hw/xtensa/xtfpga.c +++ b/hw/xtensa/xtfpga.c @@ -50,7 +50,6 @@ #include "xtensa_memory.h" #include "hw/xtensa/mx_pic.h" #include "exec/cpu-common.h" -#include "migration/vmstate.h" typedef struct XtfpgaFlashDesc { hwaddr base; @@ -163,9 +162,8 @@ static void xtfpga_net_init(MemoryRegion *address_space, sysbus_mmio_get_region(s, 1)); ram = g_malloc(sizeof(*ram)); - memory_region_init_ram_nomigrate(ram, OBJECT(s), "open_eth.ram", 16 * KiB, + memory_region_init_ram(ram, OBJECT(s), "open_eth.ram", 16 * KiB, &error_fatal); - vmstate_register_ram_global(ram); memory_region_add_subregion(address_space, buffers, ram); } diff --git a/include/accel/accel-ops.h b/include/accel/accel-ops.h index 23a8c246e153..f46492e3fe15 100644 --- a/include/accel/accel-ops.h +++ b/include/accel/accel-ops.h @@ -23,6 +23,8 @@ struct AccelClass { AccelOpsClass *ops; int (*init_machine)(AccelState *as, MachineState *ms); + /* used mainly by confidential guests to rebuild guest state upon reset */ + int (*rebuild_guest)(MachineState *ms); bool (*cpu_common_realize)(CPUState *cpu, Error **errp); void (*cpu_common_unrealize)(CPUState *cpu); /* get_stats: Append statistics to @buf */ diff --git a/include/block/block_int-common.h b/include/block/block_int-common.h index 9324af903d0f..147c08155f7c 100644 --- a/include/block/block_int-common.h +++ b/include/block/block_int-common.h @@ -56,6 +56,7 @@ #define BLOCK_OPT_DATA_FILE_RAW "data_file_raw" #define BLOCK_OPT_COMPRESSION_TYPE "compression_type" #define BLOCK_OPT_EXTL2 "extended_l2" +#define BLOCK_OPT_KEEP_DATA_FILE "keep_data_file" #define BLOCK_PROBE_BUF_SIZE 512 diff --git a/include/block/throttle-groups.h b/include/block/throttle-groups.h index 2355e8d9de6c..7dfc81f7b508 100644 --- a/include/block/throttle-groups.h +++ b/include/block/throttle-groups.h @@ -35,8 +35,7 @@ typedef struct ThrottleGroupMember { AioContext *aio_context; - /* throttled_reqs_lock protects the CoQueues for throttled requests. */ - CoMutex throttled_reqs_lock; + /* Protected by ThrottleGroup.lock */ CoQueue throttled_reqs[THROTTLE_MAX]; /* Nonzero if the I/O limits are currently being ignored; generally diff --git a/include/exec/cpu-defs.h b/include/exec/cpu-defs.h index e01acb7c906c..141b5a992922 100644 --- a/include/exec/cpu-defs.h +++ b/include/exec/cpu-defs.h @@ -28,9 +28,6 @@ #ifndef TARGET_LONG_BITS # error TARGET_LONG_BITS must be defined in cpu-param.h #endif -#ifndef TARGET_PHYS_ADDR_SPACE_BITS -# error TARGET_PHYS_ADDR_SPACE_BITS must be defined in cpu-param.h -#endif #ifndef TARGET_VIRT_ADDR_SPACE_BITS # error TARGET_VIRT_ADDR_SPACE_BITS must be defined in cpu-param.h #endif diff --git a/include/exec/page-vary.h b/include/exec/page-vary.h index 101c25911c1b..f2d735bec2fe 100644 --- a/include/exec/page-vary.h +++ b/include/exec/page-vary.h @@ -20,17 +20,26 @@ #ifndef EXEC_PAGE_VARY_H #define EXEC_PAGE_VARY_H +/* + * For system mode, the minimum comes from the number of bits + * required for maximum alignment (6) and the number of bits + * required for TLB_FLAGS_MASK (3). + * + * For user mode, TARGET_PAGE_BITS_VARY is a hack to allow the target + * page size to match the host page size. Mostly, this reduces the + * ordinary target page size to run on a host with 4KiB pages (i.e. x86). + * There is no true minimum required by the implementation, but keep the + * same minimum as for system mode for sanity. + * See linux-user/mmap.c, mmap_h_lt_g and mmap_h_gt_g. + */ +#define TARGET_PAGE_BITS_MIN 9 + typedef struct { bool decided; int bits; uint64_t mask; } TargetPageBits; -#ifdef IN_PAGE_VARY -bool set_preferred_target_page_bits_common(int bits); -void finalize_target_page_bits_common(int min); -#endif - /** * set_preferred_target_page_bits: * @bits: number of bits needed to represent an address within the page diff --git a/include/fpu/softfloat-types.h b/include/fpu/softfloat-types.h index 8f82fdfc9752..ead146c22d6c 100644 --- a/include/fpu/softfloat-types.h +++ b/include/fpu/softfloat-types.h @@ -119,6 +119,13 @@ typedef struct { */ typedef uint16_t bfloat16; +/* + * Open Compute Project (OCP) Microscaling Formats + */ +typedef uint8_t float4_e2m1; +typedef uint8_t float8_e4m3; +typedef uint8_t float8_e5m2; + /* * Software IEC/IEEE floating-point underflow tininess-detection mode. */ diff --git a/include/fpu/softfloat.h b/include/fpu/softfloat.h index 66b0c47b5eb3..8389a07b04b7 100644 --- a/include/fpu/softfloat.h +++ b/include/fpu/softfloat.h @@ -189,6 +189,20 @@ float128 int128_to_float128(Int128, float_status *status); float128 uint64_to_float128(uint64_t, float_status *status); float128 uint128_to_float128(Int128, float_status *status); +/*---------------------------------------------------------------------------- +| OCP FP{4,8} conversion routines. +*----------------------------------------------------------------------------*/ + +float8_e4m3 float4_e2m1_to_float8_e4m3(float4_e2m1, float_status *status); + +bfloat16 float8_e4m3_to_bfloat16(float8_e4m3, float_status *status); +float8_e4m3 bfloat16_to_float8_e4m3(bfloat16, bool sat, float_status *status); +float8_e4m3 float32_to_float8_e4m3(float32, bool sat, float_status *status); + +bfloat16 float8_e5m2_to_bfloat16(float8_e5m2, float_status *status); +float8_e5m2 bfloat16_to_float8_e5m2(bfloat16, bool sat, float_status *status); +float8_e5m2 float32_to_float8_e5m2(float32, bool sat, float_status *status); + /*---------------------------------------------------------------------------- | Software half-precision conversion routines. *----------------------------------------------------------------------------*/ @@ -978,8 +992,8 @@ floatx80 floatx80_rem(floatx80, floatx80, float_status *status); floatx80 floatx80_sqrt(floatx80, float_status *status); FloatRelation floatx80_compare(floatx80, floatx80, float_status *status); FloatRelation floatx80_compare_quiet(floatx80, floatx80, float_status *status); -int floatx80_is_quiet_nan(floatx80, float_status *status); -int floatx80_is_signaling_nan(floatx80, float_status *status); +bool floatx80_is_quiet_nan(floatx80, float_status *status); +bool floatx80_is_signaling_nan(floatx80, float_status *status); floatx80 floatx80_silence_nan(floatx80, float_status *status); floatx80 floatx80_scalbn(floatx80, int, float_status *status); diff --git a/include/gdbstub/helpers.h b/include/gdbstub/helpers.h index 197592036bf6..b2f41d6d280b 100644 --- a/include/gdbstub/helpers.h +++ b/include/gdbstub/helpers.h @@ -88,6 +88,20 @@ static inline int gdb_get_zeroes(GByteArray *array, size_t len) return len; } +/** + * gdb_get_regl: append @val in @buf using 32 or 64-bit, depending on target + * + * This function is legacy and deprecated, thus should not be used in new code. + */ +static inline int gdb_get_regl(GByteArray *buf, uint64_t val) +{ + if (target_long_bits() == 64) { + return gdb_get_reg64(buf, val); + } else { + return gdb_get_reg32(buf, val); + } +} + /** * gdb_get_reg_ptr: get pointer to start of last element * @len: length of element @@ -101,18 +115,4 @@ static inline uint8_t *gdb_get_reg_ptr(GByteArray *buf, int len) return buf->data + buf->len - len; } -#ifdef COMPILING_PER_TARGET -#if TARGET_LONG_BITS == 64 -#define gdb_get_regl(buf, val) gdb_get_reg64(buf, val) -#define ldtul_p(addr) ldq_p(addr) -#define ldtul_le_p(addr) ldq_le_p(addr) -#define ldtul_be_p(addr) ldq_be_p(addr) -#else -#define gdb_get_regl(buf, val) gdb_get_reg32(buf, val) -#define ldtul_p(addr) ldl_p(addr) -#define ldtul_le_p(addr) ldl_le_p(addr) -#define ldtul_be_p(addr) ldl_be_p(addr) -#endif -#endif /* COMPILING_PER_TARGET */ - #endif /* _GDBSTUB_HELPERS_H_ */ diff --git a/include/hw/acpi/acpi.h b/include/hw/acpi/acpi.h index b036116dfb87..dc3672db5213 100644 --- a/include/hw/acpi/acpi.h +++ b/include/hw/acpi/acpi.h @@ -182,6 +182,7 @@ void acpi_gpe_reset(ACPIREGS *ar); void acpi_gpe_ioport_writeb(ACPIREGS *ar, uint32_t addr, uint32_t val); uint32_t acpi_gpe_ioport_readb(ACPIREGS *ar, uint32_t addr); +void acpi_send_event(DeviceState *dev, AcpiEventStatusBits event); void acpi_send_gpe_event(ACPIREGS *ar, qemu_irq irq, AcpiEventStatusBits status); diff --git a/include/hw/acpi/acpi_dev_interface.h b/include/hw/acpi/acpi_dev_interface.h index 5927e40eaff0..65debb90a8d4 100644 --- a/include/hw/acpi/acpi_dev_interface.h +++ b/include/hw/acpi/acpi_dev_interface.h @@ -27,8 +27,6 @@ DECLARE_CLASS_CHECKERS(AcpiDeviceIfClass, ACPI_DEVICE_IF, typedef struct AcpiDeviceIf AcpiDeviceIf; -void acpi_send_event(DeviceState *dev, AcpiEventStatusBits event); - /** * AcpiDeviceIfClass: * diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h index b185b0418678..d7b3647ca16c 100644 --- a/include/hw/arm/aspeed_soc.h +++ b/include/hw/arm/aspeed_soc.h @@ -23,7 +23,7 @@ #include "hw/timer/aspeed_timer.h" #include "hw/rtc/aspeed_rtc.h" #include "hw/i2c/aspeed_i2c.h" -#include "hw/misc/aspeed_i3c.h" +#include "hw/i3c/aspeed_i3c.h" #include "hw/ssi/aspeed_smc.h" #include "hw/misc/aspeed_hace.h" #include "hw/misc/aspeed_sbc.h" diff --git a/include/hw/arm/smmuv3-common.h b/include/hw/arm/smmuv3-common.h index 67a23fbeaaee..9f78bbe89eb0 100644 --- a/include/hw/arm/smmuv3-common.h +++ b/include/hw/arm/smmuv3-common.h @@ -351,11 +351,11 @@ REG32(IDR5, 0x14) REG32(IIDR, 0x18) REG32(AIDR, 0x1c) REG32(CR0, 0x20) - FIELD(CR0, SMMU_ENABLE, 0, 1) + FIELD(CR0, SMMUEN, 0, 1) FIELD(CR0, EVENTQEN, 2, 1) FIELD(CR0, CMDQEN, 3, 1) -#define SMMU_CR0_RESERVED 0xFFFFFC20 +#define SMMU_CR0_RESERVED 0xFFFFFA20 REG32(CR0ACK, 0x24) REG32(CR1, 0x28) diff --git a/include/hw/arm/virt.h b/include/hw/arm/virt.h index 806942276946..dba8ac7f2f49 100644 --- a/include/hw/arm/virt.h +++ b/include/hw/arm/virt.h @@ -172,6 +172,7 @@ struct VirtMachineState { uint32_t msi_phandle; uint32_t iommu_phandle; int psci_conduit; + uint8_t virtio_transports; hwaddr highest_gpa; DeviceState *gic; DeviceState *acpi_dev; diff --git a/include/hw/audio/virtio-snd.h b/include/hw/audio/virtio-snd.h index c176066584b4..e28f1be5db97 100644 --- a/include/hw/audio/virtio-snd.h +++ b/include/hw/audio/virtio-snd.h @@ -122,7 +122,6 @@ struct VirtIOSoundPCMBuffer { }; struct VirtIOSoundPCM { - VirtIOSound *snd; /* * PCM parameters are a separate field instead of a VirtIOSoundPCMStream * field, because the operation of PCM control requests is first @@ -135,7 +134,6 @@ struct VirtIOSoundPCM { }; struct VirtIOSoundPCMStream { - VirtIOSoundPCM *pcm; virtio_snd_pcm_info info; virtio_snd_pcm_set_params params; uint32_t id; @@ -150,6 +148,7 @@ struct VirtIOSoundPCMStream { } voice; QemuMutex queue_mutex; bool active; + uint32_t latency_bytes; QSIMPLEQ_HEAD(, VirtIOSoundPCMBuffer) queue; }; @@ -215,7 +214,7 @@ struct VirtIOSound { VirtQueue *queues[VIRTIO_SND_VQ_MAX]; uint64_t features; - VirtIOSoundPCM *pcm; + VirtIOSoundPCM pcm; AudioBackend *audio_be; VMChangeStateEntry *vmstate; virtio_snd_config snd_conf; diff --git a/include/hw/char/pl011.h b/include/hw/char/pl011.h index ff735b5234a5..56957876500b 100644 --- a/include/hw/char/pl011.h +++ b/include/hw/char/pl011.h @@ -51,6 +51,7 @@ struct PL011State { qemu_irq irq[6]; Clock *clk; bool migrate_clk; + bool logged_disabled_uart; const unsigned char *id; /* * Since some users embed this struct directly, we must diff --git a/include/hw/core/boards.h b/include/hw/core/boards.h index edbe8d03e562..f85f31bd90d6 100644 --- a/include/hw/core/boards.h +++ b/include/hw/core/boards.h @@ -314,7 +314,6 @@ struct MachineClass { bool auto_enable_numa_with_memhp; bool auto_enable_numa_with_memdev; bool ignore_boot_device_suffixes; - bool smbus_no_migration_support; bool nvdimm_supported; bool numa_mem_supported; bool auto_enable_numa; @@ -448,6 +447,12 @@ struct MachineState { struct NVDIMMState *nvdimms_state; struct NumaState *numa_state; bool acpi_spcr_enabled; + /* + * Whether to change virtual machine accelerator handle upon + * reset or not. Used only for debugging and testing purpose. + * Set to false by default for all regular use. + */ + bool new_accel_vmfd_on_reset; }; /* @@ -859,28 +864,4 @@ extern const size_t hw_compat_4_2_len; extern GlobalProperty hw_compat_4_1[]; extern const size_t hw_compat_4_1_len; -extern GlobalProperty hw_compat_4_0[]; -extern const size_t hw_compat_4_0_len; - -extern GlobalProperty hw_compat_3_1[]; -extern const size_t hw_compat_3_1_len; - -extern GlobalProperty hw_compat_3_0[]; -extern const size_t hw_compat_3_0_len; - -extern GlobalProperty hw_compat_2_12[]; -extern const size_t hw_compat_2_12_len; - -extern GlobalProperty hw_compat_2_11[]; -extern const size_t hw_compat_2_11_len; - -extern GlobalProperty hw_compat_2_10[]; -extern const size_t hw_compat_2_10_len; - -extern GlobalProperty hw_compat_2_9[]; -extern const size_t hw_compat_2_9_len; - -extern GlobalProperty hw_compat_2_8[]; -extern const size_t hw_compat_2_8_len; - #endif diff --git a/include/hw/core/cpu.h b/include/hw/core/cpu.h index ef20cb356a6a..7d2f4459d2bf 100644 --- a/include/hw/core/cpu.h +++ b/include/hw/core/cpu.h @@ -128,9 +128,9 @@ struct SysemuCPUOps; * @gdb_adjust_breakpoint: Callback for adjusting the address of a * breakpoint. Used by AVR to handle a gdb mis-feature with * its Harvard architecture split code and data. - * @gdb_num_core_regs: Number of core registers accessible to GDB or 0 to infer - * from @gdb_core_xml_file. * @gdb_core_xml_file: File name for core registers GDB XML description. + * @gdb_num_core_regs: Number of core registers accessible to GDB if no + * @gdb_core_xml_file available (otherwise inferred). * @gdb_get_core_xml_file: Optional callback that returns the file name for * the core registers GDB XML description. The returned value is expected to * be a simple constant string: the caller will not g_free() it. If this @@ -815,6 +815,9 @@ void cpu_list_remove(CPUState *cpu); /** * cpu_reset: * @cpu: The CPU whose state is to be reset. + * + * You should refrain from calling this during CPU realization and + * make sure this is called from the reset logic instead. */ void cpu_reset(CPUState *cpu); diff --git a/include/hw/core/qdev.h b/include/hw/core/qdev.h index 4b2730e9d8e8..f99a8979ccb1 100644 --- a/include/hw/core/qdev.h +++ b/include/hw/core/qdev.h @@ -324,6 +324,10 @@ struct BusClass { /* FIXME first arg should be BusState */ void (*print_dev)(Monitor *mon, DeviceState *dev, int indent); + /* + * Return a newly allocated string containing the path of the + * device on this bus. + */ char *(*get_dev_path)(DeviceState *dev); /* @@ -1060,7 +1064,42 @@ bool qdev_set_parent_bus(DeviceState *dev, BusState *bus, Error **errp); extern bool qdev_hot_removed; +/** + * qdev_get_dev_path(): Return the path of a device on its bus + * @dev: device to get the path of + * + * Returns: A newly allocated string containing the dev path of + * @dev. The caller must free this with g_free(). + * The format of the string depends on the bus; for instance a + * PCI device's path will be in the format:: + * + * Domain:00:Slot.Function:Slot.Function....:Slot.Function + * + * and a SCSI device's path will be:: + * + * channel:ID:LUN + * + * (possibly prefixed by the path of the SCSI controller). + * + * If @dev is NULL or not on a bus, returns NULL. + */ char *qdev_get_dev_path(DeviceState *dev); + +/** + * qdev_get_printable_name: Return human readable name for device + * @dev: Device to get name of + * + * Returns: A newly allocated string containing some human + * readable name for the device, suitable for printing in + * user-facing error messages. The function will never return NULL, + * so the name can be used without further checking or fallbacks. + * + * If the device has an explicitly set ID (e.g. by the user on the + * command line via "-device thisdev,id=myid") this is preferred. + * Otherwise we try the canonical QOM device path (which will be + * the PCI ID for PCI devices, for example). If all else fails + * we will return the placeholder ". + */ const char *qdev_get_printable_name(DeviceState *dev); void qbus_set_hotplug_handler(BusState *bus, Object *handler); diff --git a/include/hw/cxl/cxl_component.h b/include/hw/cxl/cxl_component.h index b0f2aa791431..ffc82202206c 100644 --- a/include/hw/cxl/cxl_component.h +++ b/include/hw/cxl/cxl_component.h @@ -32,10 +32,20 @@ enum reg_type { }; /* - * Capability registers are defined at the top of the CXL.cache/mem region and - * are packed. For our purposes we will always define the caps in the same - * order. - * CXL r3.1 Table 8-22: CXL_CAPABILITY_ID Assignment for details. + * CXL r3.2 - 8.2.4 Table 8-22 and 8-23 + * + * Capability registers are defined at the top of the CXL.cache/mem region. + * They are defined to be packed and at variable offsets. However, NULL + * capabilities can be added to the packed array. To facilitate easier access + * within the QEMU code, define these at specified offsets. Then NULL out any + * capabilities for devices which don't (or can't) have a particular capability + * (see cxl_component_register_init_common). NULL capabilities are to be + * ignored by software. + * + * 'offsets' are based on index's which can then be used to report the array + * size in CXL Capability Header Register (index/offset 0). + * + * See CXL r3.2 Table 8-25 for an example of allowing a 'NULL' header. */ /* CXL r3.1 Section 8.2.4.1: CXL Capability Header Register */ @@ -46,16 +56,19 @@ REG32(CXL_CAPABILITY_HEADER, 0) FIELD(CXL_CAPABILITY_HEADER, CACHE_MEM_VERSION, 20, 4) FIELD(CXL_CAPABILITY_HEADER, ARRAY_SIZE, 24, 8) -#define CXLx_CAPABILITY_HEADER(type, offset) \ - REG32(CXL_##type##_CAPABILITY_HEADER, offset) \ +#define CXLx_CAPABILITY_HEADER(type, idx) \ + enum { CXL_##type##_CAP_HDR_IDX = idx }; \ + REG32(CXL_##type##_CAPABILITY_HEADER, (idx * 0x4)) \ FIELD(CXL_##type##_CAPABILITY_HEADER, ID, 0, 16) \ FIELD(CXL_##type##_CAPABILITY_HEADER, VERSION, 16, 4) \ FIELD(CXL_##type##_CAPABILITY_HEADER, PTR, 20, 12) -CXLx_CAPABILITY_HEADER(RAS, 0x4) -CXLx_CAPABILITY_HEADER(LINK, 0x8) -CXLx_CAPABILITY_HEADER(HDM, 0xc) -CXLx_CAPABILITY_HEADER(EXTSEC, 0x10) -CXLx_CAPABILITY_HEADER(SNOOP, 0x14) +CXLx_CAPABILITY_HEADER(RAS, 1) +CXLx_CAPABILITY_HEADER(LINK, 2) +CXLx_CAPABILITY_HEADER(HDM, 3) +CXLx_CAPABILITY_HEADER(EXTSEC, 4) +CXLx_CAPABILITY_HEADER(SNOOP, 5) +CXLx_CAPABILITY_HEADER(BI_RT, 6) +CXLx_CAPABILITY_HEADER(BI_DECODER, 7) /* * Capability structures contain the actual registers that the CXL component @@ -200,10 +213,55 @@ HDM_DECODER_INIT(3); (CXL_IDE_REGISTERS_OFFSET + CXL_IDE_REGISTERS_SIZE) #define CXL_SNOOP_REGISTERS_SIZE 0x8 -QEMU_BUILD_BUG_MSG((CXL_SNOOP_REGISTERS_OFFSET + - CXL_SNOOP_REGISTERS_SIZE) >= 0x1000, +#define CXL_BI_RT_CAP_VERSION 1 +#define CXL_BI_RT_REGISTERS_OFFSET \ + (CXL_SNOOP_REGISTERS_OFFSET + CXL_SNOOP_REGISTERS_SIZE) +#define CXL_BI_RT_REGISTERS_SIZE 0xC + +REG32(CXL_BI_RT_CAPABILITY, CXL_BI_RT_REGISTERS_OFFSET) + FIELD(CXL_BI_RT_CAPABILITY, EXPLICIT_COMMIT, 0, 1) +REG32(CXL_BI_RT_CTRL, CXL_BI_RT_REGISTERS_OFFSET + 0x4) + FIELD(CXL_BI_RT_CTRL, COMMIT, 0, 1) +REG32(CXL_BI_RT_STATUS, CXL_BI_RT_REGISTERS_OFFSET + 0x8) + FIELD(CXL_BI_RT_STATUS, COMMITTED, 0, 1) + FIELD(CXL_BI_RT_STATUS, ERR_NOT_COMMITTED, 1, 1) + FIELD(CXL_BI_RT_STATUS, COMMIT_TMO_SCALE, 8, 4) + FIELD(CXL_BI_RT_STATUS, COMMIT_TMO_BASE, 12, 4) + +/* CXL r3.2 8.2.4.27 - CXL BI Decoder Capability Structure */ +#define CXL_BI_DECODER_CAP_VERSION 1 +#define CXL_BI_DECODER_REGISTERS_OFFSET \ + (CXL_BI_RT_REGISTERS_OFFSET + CXL_BI_RT_REGISTERS_SIZE) +#define CXL_BI_DECODER_REGISTERS_SIZE 0xC + +REG32(CXL_BI_DECODER_CAPABILITY, CXL_BI_DECODER_REGISTERS_OFFSET) + FIELD(CXL_BI_DECODER_CAPABILITY, HDM_D, 0, 1) + FIELD(CXL_BI_DECODER_CAPABILITY, EXPLICIT_COMMIT, 1, 1) +REG32(CXL_BI_DECODER_CTRL, CXL_BI_DECODER_REGISTERS_OFFSET + 0x4) + FIELD(CXL_BI_DECODER_CTRL, BI_FW, 0, 1) + FIELD(CXL_BI_DECODER_CTRL, BI_ENABLE, 1, 1) + FIELD(CXL_BI_DECODER_CTRL, COMMIT, 2, 1) +REG32(CXL_BI_DECODER_STATUS, CXL_BI_DECODER_REGISTERS_OFFSET + 0x8) + FIELD(CXL_BI_DECODER_STATUS, COMMITTED, 0, 1) + FIELD(CXL_BI_DECODER_STATUS, ERR_NOT_COMMITTED, 1, 1) + FIELD(CXL_BI_DECODER_STATUS, COMMIT_TMO_SCALE, 8, 4) + FIELD(CXL_BI_DECODER_STATUS, COMMIT_TMO_BASE, 12, 4) + +QEMU_BUILD_BUG_MSG((CXL_BI_DECODER_REGISTERS_OFFSET + + CXL_BI_DECODER_REGISTERS_SIZE) >= 0x1000, "No space for registers"); +/* track BI explicit commit handling for route table and decoder */ +enum { + CXL_BISTATE_RT = 0, + CXL_BISTATE_DECODER, + CXL_BISTATE_MAX +}; + +typedef struct bi_state { + uint64_t last_commit; /* last 0->1 transition */ +} BIState; + typedef struct component_registers { /* * Main memory region to be registered with QEMU core. @@ -248,6 +306,7 @@ typedef struct cxl_component { }; CDATObject cdat; + BIState bi_state[CXL_BISTATE_MAX]; } CXLComponentState; void cxl_component_register_block_init(Object *obj, @@ -255,7 +314,7 @@ void cxl_component_register_block_init(Object *obj, const char *type); void cxl_component_register_init_common(uint32_t *reg_state, uint32_t *write_msk, - enum reg_type type); + enum reg_type type, bool bi); void cxl_component_create_dvsec(CXLComponentState *cxl_cstate, enum reg_type cxl_dev_type, uint16_t length, diff --git a/include/hw/cxl/cxl_device.h b/include/hw/cxl/cxl_device.h index 165355baf9da..393f3122173b 100644 --- a/include/hw/cxl/cxl_device.h +++ b/include/hw/cxl/cxl_device.h @@ -430,6 +430,12 @@ static inline bool cxl_dev_media_disabled(CXLDeviceState *cxl_dstate) uint64_t dev_status_reg = cxl_dstate->mbox_reg_state64[R_CXL_MEM_DEV_STS]; return FIELD_EX64(dev_status_reg, CXL_MEM_DEV_STS, MEDIA_STATUS) == 0x3; } + +static inline bool maintenance_running(CXLCCI *cci) +{ + return cci->bg.runtime && cci->bg.opcode == 0x0600; +} + static inline bool scan_media_running(CXLCCI *cci) { return !!cci->bg.runtime && cci->bg.opcode == 0x4304; @@ -443,6 +449,23 @@ typedef struct CXLError { typedef QTAILQ_HEAD(, CXLError) CXLErrorList; +typedef struct CXLMaintenance { + uint64_t dpa; + uint16_t validity_flags; + uint8_t channel; + uint8_t rank; + uint32_t nibble_mask; + uint8_t bank_group; + uint8_t bank; + uint32_t row; + uint16_t column; + uint8_t component_id[CXL_EVENT_GEN_MED_COMP_ID_SIZE]; + uint8_t sub_channel; + QLIST_ENTRY(CXLMaintenance) node; +} CXLMaintenance; + +typedef QLIST_HEAD(, CXLMaintenance) CXLMaintenanceList; + typedef struct CXLPoison { uint64_t start, length; uint8_t type; @@ -455,6 +478,87 @@ typedef struct CXLPoison { typedef QLIST_HEAD(, CXLPoison) CXLPoisonList; #define CXL_POISON_LIST_LIMIT 256 +/* CXL memory maintenance operation */ +/* + * CXL r3.2 section 8.2.10.7.2, Table 8-125: Mainteance Operation: + * Classes, Subclasses, and Feature UUIDs + */ +#define CXL_MEMDEV_MAINT_CLASS_NO_OP 0x0 +#define CXL_MEMDEV_MAINT_CLASS_PPR 0x1 +#define CXL_MEMDEV_MAINT_CLASS_SPARING 0x2 +#define CXL_MEMDEV_MAINT_CLASS_DEV_BUILT_IN_TEST 0x3 + +#define CXL_MEMDEV_MAINT_SUBCLASS_SPPR 0x0 +#define CXL_MEMDEV_MAINT_SUBCLASS_HPPR 0x1 + +#define CXL_MEMDEV_MAINT_SUBCLASS_CACHELINE_SPARING 0x0 +#define CXL_MEMDEV_MAINT_SUBCLASS_ROW_SPARING 0x1 +#define CXL_MEMDEV_MAINT_SUBCLASS_BANK_SPARING 0x2 +#define CXL_MEMDEV_MAINT_SUBCLASS_RANK_SPARING 0x3 + +/* CXL memory Post Package Repair control attributes */ +/* + * CXL r3.2 section 8.2.10.7.2.1, Table 8-128 and 8-129: + * sPPR Feature Readable/Writable Attributes + */ +typedef struct CXLMemSoftPPRReadAttrs { + uint8_t max_maint_latency; + uint16_t op_caps; + uint16_t op_mode; + uint8_t maint_op_class; + uint8_t maint_op_subclass; + uint8_t rsvd[9]; + uint8_t sppr_flags; + uint16_t restriction_flags; + uint8_t sppr_op_mode; +} QEMU_PACKED CXLMemSoftPPRReadAttrs; + +typedef struct CXLMemSoftPPRWriteAttrs { + uint16_t op_mode; + uint8_t sppr_op_mode; +} QEMU_PACKED CXLMemSoftPPRWriteAttrs; + +#define CXL_MEMDEV_SPPR_GET_FEATURE_VERSION 0x03 +#define CXL_MEMDEV_SPPR_SET_FEATURE_VERSION 0x03 +#define CXL_MEMDEV_SPPR_DPA_SUPPORT_FLAG BIT(0) +#define CXL_MEMDEV_SPPR_NIBBLE_SUPPORT_FLAG BIT(1) +#define CXL_MEMDEV_SPPR_MEM_SPARING_EV_REC_CAP_FLAG BIT(2) +#define CXL_MEMDEV_SPPR_DEV_INITIATED_AT_BOOT_CAP_FLAG BIT(3) + +#define CXL_MEMDEV_SPPR_OP_MODE_MEM_SPARING_EV_REC_EN BIT(0) +#define CXL_MEMDEV_SPPR_OP_MODE_DEV_INITIATED_AT_BOOT BIT(1) + +/* + * CXL r3.2 section 8.2.10.7.2.2, Table 8-131 and 8-132: + * hPPR Feature Readable/Writable Attributes + */ +typedef struct CXLMemHardPPRReadAttrs { + uint8_t max_maint_latency; + uint16_t op_caps; + uint16_t op_mode; + uint8_t maint_op_class; + uint8_t maint_op_subclass; + uint8_t rsvd[9]; + uint8_t hppr_flags; + uint16_t restriction_flags; + uint8_t hppr_op_mode; +} QEMU_PACKED CXLMemHardPPRReadAttrs; + +typedef struct CXLMemHardPPRWriteAttrs { + uint16_t op_mode; + uint8_t hppr_op_mode; +} QEMU_PACKED CXLMemHardPPRWriteAttrs; + +#define CXL_MEMDEV_HPPR_GET_FEATURE_VERSION 0x03 +#define CXL_MEMDEV_HPPR_SET_FEATURE_VERSION 0x03 +#define CXL_MEMDEV_HPPR_DPA_SUPPORT_FLAG BIT(0) +#define CXL_MEMDEV_HPPR_NIBBLE_SUPPORT_FLAG BIT(1) +#define CXL_MEMDEV_HPPR_MEM_SPARING_EV_REC_CAP_FLAG BIT(2) +#define CXL_MEMDEV_HPPR_DEV_INITIATED_AT_BOOT_CAP_FLAG BIT(3) + +#define CXL_MEMDEV_HPPR_OP_MODE_MEM_SPARING_EV_REC_EN BIT(0) +#define CXL_MEMDEV_HPPR_OP_MODE_DEV_INITIATED_AT_BOOT BIT(1) + /* CXL memory device patrol scrub control attributes */ typedef struct CXLMemPatrolScrubReadAttrs { uint8_t scrub_cycle_cap; @@ -504,6 +608,30 @@ typedef struct CXLMemECSWriteAttrs { CXLMemECSFRUWriteAttrs fru_attrs[CXL_ECS_NUM_MEDIA_FRUS]; } QEMU_PACKED CXLMemECSWriteAttrs; +/* + * CXL r3.2 section 8.2.10.7.2.3, Table 8-134 and 8-135: + * Memory Sparing Feature Readable/Writable Attributes + */ +typedef struct CXLMemSparingReadAttrs { + uint8_t max_maint_latency; + uint16_t op_caps; + uint16_t op_mode; + uint8_t maint_op_class; + uint8_t maint_op_subclass; + uint8_t rsvd[10]; + uint16_t restriction_flags; +} QEMU_PACKED CXLMemSparingReadAttrs; + +typedef struct CXLMemSparingWriteAttrs { + uint16_t op_mode; +} QEMU_PACKED CXLMemSparingWriteAttrs; + +#define CXL_MEMDEV_SPARING_GET_FEATURE_VERSION 0x01 +#define CXL_MEMDEV_SPARING_SET_FEATURE_VERSION 0x01 +#define CXL_MEMDEV_SPARING_SAFE_IN_USE_FLAG BIT(0) +#define CXL_MEMDEV_HARD_SPARING_SUPPORT_FLAG BIT(1) +#define CXL_MEMDEV_SOFT_SPARING_SUPPORT_FLAG BIT(2) + #define DCD_MAX_NUM_REGION 8 typedef struct CXLDCExtentRaw { @@ -598,6 +726,7 @@ struct CXLType3Dev { /* PCIe link characteristics */ PCIExpLinkSpeed speed; PCIExpLinkWidth width; + bool flitmode; /* DOE */ DOECap doe_cdat; @@ -605,6 +734,9 @@ struct CXLType3Dev { /* Error injection */ CXLErrorList error_list; + /* Keep track of maintenance requests */ + CXLMaintenanceList maint_list; + /* Poison Injection - cache */ CXLPoisonList poison_list; unsigned int poison_list_cnt; @@ -617,12 +749,29 @@ struct CXLType3Dev { CXLSetFeatureInfo set_feat_info; + /* PPR control attributes */ + CXLMemSoftPPRReadAttrs soft_ppr_attrs; + CXLMemSoftPPRWriteAttrs soft_ppr_wr_attrs; + CXLMemHardPPRReadAttrs hard_ppr_attrs; + CXLMemHardPPRWriteAttrs hard_ppr_wr_attrs; /* Patrol scrub control attributes */ CXLMemPatrolScrubReadAttrs patrol_scrub_attrs; CXLMemPatrolScrubWriteAttrs patrol_scrub_wr_attrs; /* ECS control attributes */ CXLMemECSReadAttrs ecs_attrs; CXLMemECSWriteAttrs ecs_wr_attrs; + /* Memory Sparing control attributes */ + CXLMemSparingReadAttrs cacheline_sparing_attrs; + CXLMemSparingWriteAttrs cacheline_sparing_wr_attrs; + CXLMemSparingReadAttrs row_sparing_attrs; + CXLMemSparingWriteAttrs row_sparing_wr_attrs; + CXLMemSparingReadAttrs bank_sparing_attrs; + CXLMemSparingWriteAttrs bank_sparing_wr_attrs; + CXLMemSparingReadAttrs rank_sparing_attrs; + CXLMemSparingWriteAttrs rank_sparing_wr_attrs; + + /* BI flows */ + bool hdmdb; struct dynamic_capacity { HostMemoryBackend *host_dc; @@ -723,7 +872,12 @@ bool ct3_test_region_block_backed(CXLType3Dev *ct3d, uint64_t dpa, uint64_t len); void cxl_assign_event_header(CXLEventRecordHdr *hdr, const QemuUUID *uuid, uint32_t flags, - uint8_t length, uint64_t timestamp); + uint8_t length, uint64_t timestamp, + bool has_maint_op_class, uint8_t maint_op_class, + bool has_maint_op_subclass, + uint8_t maint_op_subclass, + bool has_ld_id, uint16_t ld_id, + bool has_head_id, uint8_t head_id); void cxl_create_dc_event_records_for_extents(CXLType3Dev *ct3d, CXLDCEventType type, CXLDCExtentRaw extents[], diff --git a/include/hw/cxl/cxl_events.h b/include/hw/cxl/cxl_events.h index 758b075a64b9..89f6aad53142 100644 --- a/include/hw/cxl/cxl_events.h +++ b/include/hw/cxl/cxl_events.h @@ -29,9 +29,15 @@ typedef enum CXLEventLogType { /* * Common Event Record Format - * CXL r3.1 section 8.2.9.2.1: Event Records; Table 8-43 + * CXL r3.2 section 8.2.10.2.1: Event Records; Table 8-55 */ -#define CXL_EVENT_REC_HDR_RES_LEN 0xf +#define CXL_EVENT_REC_FLAGS_PERMANENT_COND BIT(2) +#define CXL_EVENT_REC_FLAGS_MAINT_NEEDED BIT(3) +#define CXL_EVENT_REC_FLAGS_PERF_DEGRADED BIT(4) +#define CXL_EVENT_REC_FLAGS_HW_REPLACEMENT_NEEDED BIT(5) +#define CXL_EVENT_REC_FLAGS_MAINT_OP_SUBCLASS_VALID BIT(6) +#define CXL_EVENT_REC_FLAGS_LD_ID_VALID BIT(7) +#define CXL_EVENT_REC_FLAGS_HEAD_ID_VALID BIT(8) typedef struct CXLEventRecordHdr { QemuUUID id; uint8_t length; @@ -40,7 +46,10 @@ typedef struct CXLEventRecordHdr { uint16_t related_handle; uint64_t timestamp; uint8_t maint_op_class; - uint8_t reserved[CXL_EVENT_REC_HDR_RES_LEN]; + uint8_t maint_op_subclass; + uint16_t ld_id; + uint8_t head_id; + uint8_t reserved[0xb]; } QEMU_PACKED CXLEventRecordHdr; #define CXL_EVENT_RECORD_DATA_LENGTH 0x50 @@ -106,10 +115,10 @@ typedef struct CXLEventInterruptPolicy { /* * General Media Event Record - * CXL r3.1 Section 8.2.9.2.1.1; Table 8-45 + * CXL r3.2 Section 8.2.10.2.1.1; Table 8-57 */ #define CXL_EVENT_GEN_MED_COMP_ID_SIZE 0x10 -#define CXL_EVENT_GEN_MED_RES_SIZE 0x2e +#define CXL_EVENT_GEN_MED_RES_SIZE 0x29 typedef struct CXLEventGenMedia { CXLEventRecordHdr hdr; uint64_t phys_addr; @@ -121,12 +130,15 @@ typedef struct CXLEventGenMedia { uint8_t rank; uint8_t device[3]; uint8_t component_id[CXL_EVENT_GEN_MED_COMP_ID_SIZE]; + uint8_t cme_ev_flags; + uint8_t cme_count[3]; + uint8_t sub_type; uint8_t reserved[CXL_EVENT_GEN_MED_RES_SIZE]; } QEMU_PACKED CXLEventGenMedia; /* * DRAM Event Record - * CXL r3.1 Section 8.2.9.2.1.2: Table 8-46 + * CXL r3.2 Section 8.2.10.2.1.2: Table 8-58 * All fields little endian. */ typedef struct CXLEventDram { @@ -144,12 +156,17 @@ typedef struct CXLEventDram { uint8_t row[3]; uint16_t column; uint64_t correction_mask[4]; - uint8_t reserved[0x17]; + uint8_t component_id[CXL_EVENT_GEN_MED_COMP_ID_SIZE]; + uint8_t sub_channel; + uint8_t cme_ev_flags; + uint8_t cvme_count[3]; + uint8_t sub_type; + uint8_t reserved; } QEMU_PACKED CXLEventDram; /* * Memory Module Event Record - * CXL r3.1 Section 8.2.9.2.1.3: Table 8-47 + * CXL r3.2 Section 8.2.10.2.1.3: Table 8-59 * All fields little endian. */ typedef struct CXLEventMemoryModule { @@ -163,7 +180,10 @@ typedef struct CXLEventMemoryModule { uint32_t dirty_shutdown_count; uint32_t corrected_volatile_error_count; uint32_t corrected_persistent_error_count; - uint8_t reserved[0x3d]; + uint16_t validity_flags; + uint8_t component_id[CXL_EVENT_GEN_MED_COMP_ID_SIZE]; + uint8_t sub_type; + uint8_t reserved[0x2a]; } QEMU_PACKED CXLEventMemoryModule; /* @@ -199,4 +219,51 @@ typedef enum CXLDCEventType { DC_EVENT_CAPACITY_RELEASED = 0x5, } CXLDCEventType; +/* + * CXL r3.2 section Table 8-60: Memory Sparing Event Record + * All fields little endian. + */ + +#define CXL_MSER_FLAGS_QUERY_RESOURCES BIT(0) +#define CXL_MSER_FLAGS_HARD_SPARING BIT(1) +#define CXL_MSER_FLAGS_DEV_INITIATED BIT(2) + +#define CXL_MSER_VALID_CHANNEL BIT(0) +#define CXL_MSER_VALID_RANK BIT(1) +#define CXL_MSER_VALID_NIB_MASK BIT(2) +#define CXL_MSER_VALID_BANK_GROUP BIT(3) +#define CXL_MSER_VALID_BANK BIT(4) +#define CXL_MSER_VALID_ROW BIT(5) +#define CXL_MSER_VALID_COLUMN BIT(6) +#define CXL_MSER_VALID_COMP_ID BIT(7) +#define CXL_MSER_VALID_COMP_ID_FORMAT BIT(8) +#define CXL_MSER_VALID_SUB_CHANNEL BIT(9) + +typedef struct CXLEventSparing { + CXLEventRecordHdr hdr; + uint8_t maint_op_class; + uint8_t maint_op_subclass; + uint8_t flags; + uint8_t result; + uint16_t validity_flags; + uint8_t reserved1[6]; + uint16_t res_avail; + uint8_t channel; + uint8_t rank; + uint8_t nibble_mask[3]; + uint8_t bank_group; + uint8_t bank; + uint8_t row[3]; + uint16_t column; + uint8_t component_id[CXL_EVENT_GEN_MED_COMP_ID_SIZE]; + uint8_t sub_channel; + uint8_t reserved2[0x25]; +} QEMU_PACKED CXLEventSparing; + +/* CXL r3.2 Table 8-60: Memory Sparing Event Record */ +static const QemuUUID sparing_uuid = { + .data = UUID(0xe71f3a40, 0x2d29, 0x4092, 0x8a, 0x39, + 0x4d, 0x1c, 0x96, 0x6c, 0x7c, 0x65), +}; + #endif /* CXL_EVENTS_H */ diff --git a/include/hw/cxl/cxl_port.h b/include/hw/cxl/cxl_port.h new file mode 100644 index 000000000000..fb2e22a9f2ae --- /dev/null +++ b/include/hw/cxl/cxl_port.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef CXL_PORT_H +#define CXL_PORT_H + +#include "qemu/thread.h" + +/* CXL r3.2 Table 7-19: Get Physical Port State Port Information Block Format */ +#define CXL_PORT_CONFIG_STATE_DISABLED 0x0 +#define CXL_PORT_CONFIG_STATE_BIND_IN_PROGRESS 0x1 +#define CXL_PORT_CONFIG_STATE_UNBIND_IN_PROGRESS 0x2 +#define CXL_PORT_CONFIG_STATE_DSP 0x3 +#define CXL_PORT_CONFIG_STATE_USP 0x4 +#define CXL_PORT_CONFIG_STATE_FABRIC_PORT 0x5 +#define CXL_PORT_CONFIG_STATE_INVALID_PORT_ID 0xF + +#define CXL_PORT_CONNECTED_DEV_MODE_NOT_CXL_OR_DISCONN 0x00 +#define CXL_PORT_CONNECTED_DEV_MODE_RCD 0x01 +#define CXL_PORT_CONNECTED_DEV_MODE_68B_VH 0x02 +#define CXL_PORT_CONNECTED_DEV_MODE_256B 0x03 +#define CXL_PORT_CONNECTED_DEV_MODE_LO_256B 0x04 +#define CXL_PORT_CONNECTED_DEV_MODE_PBR 0x05 + +#define CXL_PORT_CONNECTED_DEV_TYPE_NONE 0x00 +#define CXL_PORT_CONNECTED_DEV_TYPE_PCIE 0x01 +#define CXL_PORT_CONNECTED_DEV_TYPE_1 0x02 +#define CXL_PORT_CONNECTED_DEV_TYPE_2_OR_HBR_SWITCH 0x03 +#define CXL_PORT_CONNECTED_DEV_TYPE_3_SLD 0x04 +#define CXL_PORT_CONNECTED_DEV_TYPE_3_MLD 0x05 +#define CXL_PORT_CONNECTED_DEV_PBR_COMPONENT 0x06 + +#define CXL_PORT_SUPPORTS_RCD BIT(0) +#define CXL_PORT_SUPPORTS_68B_VH BIT(1) +#define CXL_PORT_SUPPORTS_256B BIT(2) +#define CXL_PORT_SUPPORTS_LO_256B BIT(3) +#define CXL_PORT_SUPPORTS_PBR BIT(4) + +#define CXL_PORT_LTSSM_DETECT 0x00 +#define CXL_PORT_LTSSM_POLLING 0x01 +#define CXL_PORT_LTSSM_CONFIGURATION 0x02 +#define CXL_PORT_LTSSM_RECOVERY 0x03 +#define CXL_PORT_LTSSM_L0 0x04 +#define CXL_PORT_LTSSM_L0S 0x05 +#define CXL_PORT_LTSSM_L1 0x06 +#define CXL_PORT_LTSSM_L2 0x07 +#define CXL_PORT_LTSSM_DISABLED 0x08 +#define CXL_PORT_LTSSM_LOOPBACK 0x09 +#define CXL_PORT_LTSSM_HOT_RESET 0x0A + +#define CXL_PORT_LINK_STATE_FLAG_LANE_REVERSED BIT(0) +#define CXL_PORT_LINK_STATE_FLAG_PERST_ASSERTED BIT(1) +#define CXL_PORT_LINK_STATE_FLAG_PRSNT BIT(2) +#define CXL_PORT_LINK_STATE_FLAG_POWER_OFF BIT(3) + +#define CXL_MAX_PHY_PORTS 256 +#define ASSERT_WAIT_TIME_MS 100 /* Assert - Deassert PERST */ + +/* Assert - Deassert PERST */ +typedef struct CXLPhyPortPerst { + bool issued_assert_perst; + QemuMutex lock; /* protecting assert-deassert reset request */ + uint64_t asrt_time; + QemuThread asrt_thread; /* thread for 100ms delay */ +} CXLPhyPortPerst; + +void cxl_init_physical_port_control(CXLPhyPortPerst *perst); + +static inline bool cxl_perst_asserted(CXLPhyPortPerst *perst) +{ + return perst->issued_assert_perst || perst->asrt_time < ASSERT_WAIT_TIME_MS; +} + +#endif /* CXL_PORT_H */ diff --git a/include/hw/display/macfb.h b/include/hw/display/macfb.h index 495dead44b51..cb7aac7a4ddd 100644 --- a/include/hw/display/macfb.h +++ b/include/hw/display/macfb.h @@ -66,7 +66,7 @@ typedef struct MacfbState { uint8_t type; uint32_t regs[MACFB_NUM_REGS]; - MacFbMode *mode; + const MacFbMode *mode; QEMUTimer *vbl_timer; qemu_irq irq; diff --git a/include/hw/i2c/pm_smbus.h b/include/hw/i2c/pm_smbus.h index dafe0df4f696..62bbd45e803f 100644 --- a/include/hw/i2c/pm_smbus.h +++ b/include/hw/i2c/pm_smbus.h @@ -44,13 +44,6 @@ typedef struct PMSMBus { void pm_smbus_init(DeviceState *parent, PMSMBus *smb, bool force_aux_blk); -/* - * For backwards compatibility on migration, older versions don't have - * working migration for pm_smbus, this lets us ignore the migrations - * for older machine versions. - */ -bool pm_smbus_vmstate_needed(void); - extern const VMStateDescription pmsmb_vmstate; #endif /* PM_SMBUS_H */ diff --git a/include/hw/i386/intel_iommu.h b/include/hw/i386/intel_iommu.h index 54c2b6b77a01..e44ce3184174 100644 --- a/include/hw/i386/intel_iommu.h +++ b/include/hw/i386/intel_iommu.h @@ -314,7 +314,6 @@ struct IntelIOMMUState { bool intr_eime; /* Extended interrupt mode enabled */ OnOffAuto intr_eim; /* Toggle for EIM cabability */ uint8_t aw_bits; /* Host/IOVA address width (in bits) */ - bool dma_drain; /* Whether DMA r/w draining enabled */ bool pasid; /* Whether to support PASID */ bool fs1gp; /* First Stage 1-GByte Page Support */ diff --git a/include/hw/i386/microvm.h b/include/hw/i386/microvm.h index 6b9a50652727..184b7a8c0948 100644 --- a/include/hw/i386/microvm.h +++ b/include/hw/i386/microvm.h @@ -79,7 +79,7 @@ struct MicrovmMachineClass { HotplugHandler *(*orig_hotplug_handler)(MachineState *machine, DeviceState *dev); void (*x86_load_linux)(X86MachineState *x86ms, FWCfgState *fw_cfg, - int acpi_data_size, bool pvh_enabled); + int acpi_data_size); }; struct MicrovmMachineState { diff --git a/include/hw/i386/pc.h b/include/hw/i386/pc.h index 1cf88c169750..22325324d094 100644 --- a/include/hw/i386/pc.h +++ b/include/hw/i386/pc.h @@ -110,9 +110,6 @@ struct PCMachineClass { bool enforce_amd_1tb_hole; bool isa_bios_alias; - /* use PVH to load kernels that support this feature */ - bool pvh_enabled; - /* create kvmclock device even when KVM PV features are not exposed */ bool kvmclock_create_always; @@ -271,30 +268,6 @@ extern const size_t pc_compat_4_2_len; extern GlobalProperty pc_compat_4_1[]; extern const size_t pc_compat_4_1_len; -extern GlobalProperty pc_compat_4_0[]; -extern const size_t pc_compat_4_0_len; - -extern GlobalProperty pc_compat_3_1[]; -extern const size_t pc_compat_3_1_len; - -extern GlobalProperty pc_compat_3_0[]; -extern const size_t pc_compat_3_0_len; - -extern GlobalProperty pc_compat_2_12[]; -extern const size_t pc_compat_2_12_len; - -extern GlobalProperty pc_compat_2_11[]; -extern const size_t pc_compat_2_11_len; - -extern GlobalProperty pc_compat_2_10[]; -extern const size_t pc_compat_2_10_len; - -extern GlobalProperty pc_compat_2_9[]; -extern const size_t pc_compat_2_9_len; - -extern GlobalProperty pc_compat_2_8[]; -extern const size_t pc_compat_2_8_len; - #define DEFINE_PC_MACHINE(suffix, namestr, initfn, optsfn) \ static void pc_machine_##suffix##_class_init(ObjectClass *oc, \ const void *data) \ diff --git a/include/hw/i386/x86-iommu.h b/include/hw/i386/x86-iommu.h index c526a047ab6e..0a1e1ba72571 100644 --- a/include/hw/i386/x86-iommu.h +++ b/include/hw/i386/x86-iommu.h @@ -63,7 +63,6 @@ struct X86IOMMUState { SysBusDevice busdev; OnOffAuto intr_supported; /* Whether vIOMMU supports IR */ bool dt_supported; /* Whether vIOMMU supports DT */ - bool pt_supported; /* Whether vIOMMU supports pass-through */ bool dma_translation; /* Whether vIOMMU supports DMA translation */ QLIST_HEAD(, IEC_Notifier) iec_notifiers; /* IEC notify list */ }; diff --git a/include/hw/i386/x86.h b/include/hw/i386/x86.h index 23be62743774..71fe6b5e1220 100644 --- a/include/hw/i386/x86.h +++ b/include/hw/i386/x86.h @@ -125,11 +125,11 @@ void x86_isa_bios_init(MemoryRegion *isa_bios, MemoryRegion *isa_memory, MemoryRegion *bios, bool read_only); void x86_bios_rom_init(X86MachineState *x86ms, const char *default_firmware, MemoryRegion *rom_memory, bool isapc_ram_fw); +void x86_bios_rom_reload(X86MachineState *x86ms); void x86_load_linux(X86MachineState *x86ms, FWCfgState *fw_cfg, - int acpi_data_size, - bool pvh_enabled); + int acpi_data_size); bool x86_machine_is_smm_enabled(const X86MachineState *x86ms); bool x86_machine_is_acpi_enabled(const X86MachineState *x86ms); diff --git a/include/hw/misc/aspeed_i3c.h b/include/hw/i3c/aspeed_i3c.h similarity index 53% rename from include/hw/misc/aspeed_i3c.h rename to include/hw/i3c/aspeed_i3c.h index 7a984e1f01ab..42c9eedd8590 100644 --- a/include/hw/misc/aspeed_i3c.h +++ b/include/hw/i3c/aspeed_i3c.h @@ -2,6 +2,7 @@ * ASPEED I3C Controller * * Copyright (C) 2021 ASPEED Technology Inc. + * Copyright (C) 2023 Google, LLC * * This code is licensed under the GPL version 2 or later. See * the COPYING file in the top-level directory. @@ -10,39 +11,27 @@ #ifndef ASPEED_I3C_H #define ASPEED_I3C_H +#include "hw/i3c/dw-i3c.h" #include "hw/core/sysbus.h" #define TYPE_ASPEED_I3C "aspeed.i3c" -#define TYPE_ASPEED_I3C_DEVICE "aspeed.i3c.device" OBJECT_DECLARE_TYPE(AspeedI3CState, AspeedI3CClass, ASPEED_I3C) #define ASPEED_I3C_NR_REGS (0x70 >> 2) -#define ASPEED_I3C_DEVICE_NR_REGS (0x300 >> 2) #define ASPEED_I3C_NR_DEVICES 6 -OBJECT_DECLARE_SIMPLE_TYPE(AspeedI3CDevice, ASPEED_I3C_DEVICE) -typedef struct AspeedI3CDevice { - /* */ - SysBusDevice parent; +struct AspeedI3CState { + SysBusDevice parent_obj; - /* */ - MemoryRegion mr; - qemu_irq irq; - - uint8_t id; - uint32_t regs[ASPEED_I3C_DEVICE_NR_REGS]; -} AspeedI3CDevice; - -typedef struct AspeedI3CState { - /* */ - SysBusDevice parent; - - /* */ MemoryRegion iomem; MemoryRegion iomem_container; qemu_irq irq; uint32_t regs[ASPEED_I3C_NR_REGS]; - AspeedI3CDevice devices[ASPEED_I3C_NR_DEVICES]; -} AspeedI3CState; + DWI3C devices[ASPEED_I3C_NR_DEVICES]; + uint8_t id; +}; + +I3CBus *aspeed_i3c_get_bus(AspeedI3CState *s, uint8_t bus_num); + #endif /* ASPEED_I3C_H */ diff --git a/include/hw/i3c/dw-i3c.h b/include/hw/i3c/dw-i3c.h new file mode 100644 index 000000000000..d26f60580f02 --- /dev/null +++ b/include/hw/i3c/dw-i3c.h @@ -0,0 +1,199 @@ +/* + * DesignWare I3C Controller + * + * Copyright (C) 2021 ASPEED Technology Inc. + * Copyright (C) 2025 Google, LLC. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef DW_I3C_H +#define DW_I3C_H + +#include "qemu/fifo32.h" +#include "hw/i3c/i3c.h" +#include "hw/core/sysbus.h" + +#define TYPE_DW_I3C "dw.i3c" +OBJECT_DECLARE_SIMPLE_TYPE(DWI3C, DW_I3C) + +/* + * Sufficiently large enough to handle configurations with large device address + * tables. + */ +#define DW_I3C_NR_REGS (0x1000 >> 2) + +/* From datasheet. */ +#define DW_I3C_CMD_ATTR_TRANSFER_CMD 0 +#define DW_I3C_CMD_ATTR_TRANSFER_ARG 1 +#define DW_I3C_CMD_ATTR_SHORT_DATA_ARG 2 +#define DW_I3C_CMD_ATTR_ADDR_ASSIGN_CMD 3 + +/* Enum values from datasheet. */ +typedef enum DWI3CRespQueueErr { + DW_I3C_RESP_QUEUE_ERR_NONE = 0, + DW_I3C_RESP_QUEUE_ERR_CRC = 1, + DW_I3C_RESP_QUEUE_ERR_PARITY = 2, + DW_I3C_RESP_QUEUE_ERR_FRAME = 3, + DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK = 4, + DW_I3C_RESP_QUEUE_ERR_DAA_NACK = 5, + DW_I3C_RESP_QUEUE_ERR_OVERFLOW = 6, + DW_I3C_RESP_QUEUE_ERR_ABORTED = 8, + DW_I3C_RESP_QUEUE_ERR_I2C_NACK = 9, +} DWI3CRespQueueErr; + +typedef enum DWI3CTransferState { + DW_I3C_TRANSFER_STATE_IDLE = 0x00, + DW_I3C_TRANSFER_STATE_START = 0x01, + DW_I3C_TRANSFER_STATE_RESTART = 0x02, + DW_I3C_TRANSFER_STATE_STOP = 0x03, + DW_I3C_TRANSFER_STATE_START_HOLD = 0x04, + DW_I3C_TRANSFER_STATE_BROADCAST_W = 0x05, + DW_I3C_TRANSFER_STATE_BROADCAST_R = 0x06, + DW_I3C_TRANSFER_STATE_DAA = 0x07, + DW_I3C_TRANSFER_STATE_DAA_GEN = 0x08, + DW_I3C_TRANSFER_STATE_CCC_BYTE = 0x0b, + DW_I3C_TRANSFER_STATE_HDR_CMD = 0x0c, + DW_I3C_TRANSFER_STATE_WRITE = 0x0d, + DW_I3C_TRANSFER_STATE_READ = 0x0e, + DW_I3C_TRANSFER_STATE_IBI_READ = 0x0f, + DW_I3C_TRANSFER_STATE_IBI_DIS = 0x10, + DW_I3C_TRANSFER_STATE_HDR_DDR_CRC = 0x11, + DW_I3C_TRANSFER_STATE_CLK_STRETCH = 0x12, + DW_I3C_TRANSFER_STATE_HALT = 0x13, +} DWI3CTransferState; + +typedef enum DWI3CTransferStatus { + DW_I3C_TRANSFER_STATUS_IDLE = 0x00, + DW_I3C_TRANSFER_STATUS_BROACAST_CCC = 0x01, + DW_I3C_TRANSFER_STATUS_DIRECT_CCC_W = 0x02, + DW_I3C_TRANSFER_STATUS_DIRECT_CCC_R = 0x03, + DW_I3C_TRANSFER_STATUS_ENTDAA = 0x04, + DW_I3C_TRANSFER_STATUS_SETDASA = 0x05, + DW_I3C_TRANSFER_STATUS_I3C_SDR_W = 0x06, + DW_I3C_TRANSFER_STATUS_I3C_SDR_R = 0x07, + DW_I3C_TRANSFER_STATUS_I2C_SDR_W = 0x08, + DW_I3C_TRANSFER_STATUS_I2C_SDR_R = 0x09, + DW_I3C_TRANSFER_STATUS_HDR_TS_W = 0x0a, + DW_I3C_TRANSFER_STATUS_HDR_TS_R = 0x0b, + DW_I3C_TRANSFER_STATUS_HDR_DDR_W = 0x0c, + DW_I3C_TRANSFER_STATUS_HDR_DDR_R = 0x0d, + DW_I3C_TRANSFER_STATUS_IBI = 0x0e, + DW_I3C_TRANSFER_STATUS_HALT = 0x0f, +} DWI3CTransferStatus; + +/* + * Transfer commands and arguments are 32-bit wide values that the user passes + * into the command queue. We interpret each 32-bit word based on the cmd_attr + * field. + */ +typedef struct DWI3CTransferCmd { + uint8_t cmd_attr:3; + uint8_t tid:4; /* Transaction ID */ + uint16_t cmd:8; + uint8_t cp:1; /* Command present */ + uint8_t dev_index:5; + uint8_t speed:3; + uint8_t resv0:1; + uint8_t dbp:1; /* Defining byte present */ + uint8_t roc:1; /* Response on completion */ + uint8_t sdap:1; /* Short data argument present */ + uint8_t rnw:1; /* Read not write */ + uint8_t resv1:1; + uint8_t toc:1; /* Termination (I3C STOP) on completion */ + uint8_t pec:1; /* Parity error check enabled */ +} DWI3CTransferCmd; + +typedef struct DWI3CTransferArg { + uint8_t cmd_attr:3; + uint8_t resv:5; + uint8_t db; /* Defining byte */ + uint16_t data_len; +} DWI3CTransferArg; + +typedef struct DWI3CShortArg { + uint8_t cmd_attr:3; + uint8_t byte_strb:3; + uint8_t resv:2; + uint8_t byte0; + uint8_t byte1; + uint8_t byte2; +} DWI3CShortArg; + +typedef struct DWI3CAddrAssignCmd { + uint8_t cmd_attr:3; + uint8_t tid:4; /* Transaction ID */ + uint16_t cmd:8; + uint8_t resv0:1; + uint8_t dev_index:5; + uint16_t dev_count:5; + uint8_t roc:1; /* Response on completion */ + uint8_t resv1:3; + uint8_t toc:1; /* Termination (I3C STOP) on completion */ + uint8_t resv2:1; +} DWI3CAddrAssignCmd; + +typedef union DWI3CCmdQueueData { + uint32_t word; + DWI3CTransferCmd transfer_cmd; + DWI3CTransferArg transfer_arg; + DWI3CShortArg short_arg; + DWI3CAddrAssignCmd addr_assign_cmd; +} DWI3CCmdQueueData; + +/* + * When we receive an IBI with data, we need to store it temporarily until + * the target is finished sending data. Then we can set the IBI queue status + * appropriately. + */ +typedef struct DWI3CIBIData { + /* Do we notify the user that an IBI was NACKed? */ + bool notify_ibi_nack; + /* Intermediate storage of IBI_QUEUE_STATUS. */ + uint32_t ibi_queue_status; + /* Temporary buffer to store IBI data from the target. */ + Fifo8 ibi_intermediate_queue; + /* The address we should send a CCC_DISEC to. */ + uint8_t disec_addr; + /* The byte we should send along with the CCC_DISEC. */ + uint8_t disec_byte; + /* Should we send a direct DISEC CCC? (As opposed to global). */ + bool send_direct_disec; + /* Was this IBI NACKed? */ + bool ibi_nacked; +} DWI3CIBIData; + +struct DWI3C { + SysBusDevice parent_obj; + + MemoryRegion mr; + qemu_irq irq; + I3CBus *bus; + + Fifo32 cmd_queue; + Fifo32 resp_queue; + Fifo32 tx_queue; + Fifo32 rx_queue; + Fifo32 ibi_queue; + + /* Temporary storage for IBI data. */ + DWI3CIBIData ibi_data; + + struct { + uint8_t id; + uint8_t cmd_resp_queue_capacity_bytes; + uint16_t tx_rx_queue_capacity_bytes; + uint8_t ibi_queue_capacity_bytes; + uint8_t num_addressable_devices; + uint16_t dev_addr_table_pointer; + uint16_t dev_addr_table_depth; + uint16_t dev_char_table_pointer; + uint16_t dev_char_table_depth; + } cfg; + uint32_t regs[DW_I3C_NR_REGS]; +}; + +/* Extern for other controllers that use DesignWare I3C. */ +extern const VMStateDescription vmstate_dw_i3c; + +#endif /* DW_I3C_H */ diff --git a/include/hw/i3c/i3c.h b/include/hw/i3c/i3c.h new file mode 100644 index 000000000000..6ba90793ad01 --- /dev/null +++ b/include/hw/i3c/i3c.h @@ -0,0 +1,277 @@ +/* + * QEMU I3C bus interface. + * + * Copyright 2025 Google LLC + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef QEMU_INCLUDE_HW_I3C_I3C_H_ +#define QEMU_INCLUDE_HW_I3C_I3C_H_ + +#include "hw/core/qdev.h" +#include "qom/object.h" +#include "hw/i2c/i2c.h" + +#define TYPE_I3C_TARGET "i3c-target" +OBJECT_DECLARE_TYPE(I3CTarget, I3CTargetClass, I3C_TARGET) + +typedef enum I3CEvent { + I3C_START_RECV, + I3C_START_SEND, + I3C_STOP, + I3C_NACK, +} I3CEvent; + +typedef enum I3CCCC { + /* Broadcast CCCs */ + I3C_CCC_ENEC = 0x00, + I3C_CCC_DISEC = 0x01, + I3C_CCC_ENTAS0 = 0x02, + I3C_CCC_ENTAS1 = 0x03, + I3C_CCC_ENTAS2 = 0x04, + I3C_CCC_ENTAS3 = 0x05, + I3C_CCC_RSTDAA = 0x06, + I3C_CCC_ENTDAA = 0x07, + I3C_CCC_DEFTGTS = 0x08, + I3C_CCC_SETMWL = 0x09, + I3C_CCC_SETMRL = 0x0a, + I3C_CCC_ENTTM = 0x0b, + I3C_CCC_SETBUSCON = 0x0c, + I3C_CCC_ENDXFER = 0x12, + I3C_CCC_ENTHDR0 = 0x20, + I3C_CCC_ENTHDR1 = 0x21, + I3C_CCC_ENTHDR2 = 0x22, + I3C_CCC_ENTHDR3 = 0x23, + I3C_CCC_ENTHDR4 = 0x24, + I3C_CCC_ENTHDR5 = 0x25, + I3C_CCC_ENTHDR6 = 0x26, + I3C_CCC_ENTHDR7 = 0x27, + I3C_CCC_SETXTIME = 0x28, + I3C_CCC_SETAASA = 0x29, + I3C_CCC_RSTACT = 0x2a, + I3C_CCC_DEFGRPA = 0x2b, + I3C_CCC_RSTGRPA = 0x2c, + I3C_CCC_MLANE = 0x2d, + /* Direct CCCs */ + I3C_CCCD_ENEC = 0x80, + I3C_CCCD_DISEC = 0x81, + I3C_CCCD_ENTAS0 = 0x82, + I3C_CCCD_ENTAS1 = 0x83, + I3C_CCCD_ENTAS2 = 0x84, + I3C_CCCD_ENTAS3 = 0x85, + I3C_CCCD_SETDASA = 0x87, + I3C_CCCD_SETNEWDA = 0x88, + I3C_CCCD_SETMWL = 0x89, + I3C_CCCD_SETMRL = 0x8a, + I3C_CCCD_GETMWL = 0x8b, + I3C_CCCD_GETMRL = 0x8c, + I3C_CCCD_GETPID = 0x8d, + I3C_CCCD_GETBCR = 0x8e, + I3C_CCCD_GETDCR = 0x8f, + I3C_CCCD_GETSTATUS = 0x90, + I3C_CCCD_GETACCCR = 0x91, + I3C_CCCD_ENDXFER = 0x92, + I3C_CCCD_SETBRGTGT = 0x93, + I3C_CCCD_GETMXDS = 0x94, + I3C_CCCD_GETCAPS = 0x95, + I3C_CCCD_SETROUTE = 0x96, + I3C_CCCD_SETXTIME = 0x98, + I3C_CCCD_GETXTIME = 0x99, + I3C_CCCD_RSTACT = 0x9a, + I3C_CCCD_SETGRPA = 0x9b, + I3C_CCCD_RSTGRPA = 0x9c, + I3C_CCCD_MLANE = 0x9d, +} I3CCCC; + +#define CCC_IS_DIRECT(_ccc) (_ccc & 0x80) + +#define I3C_BROADCAST 0x7e +#define I3C_HJ_ADDR 0x02 +#define I3C_ENTDAA_SIZE 8 + +struct I3CTargetClass { + DeviceClass parent_class; + + /* + * Controller to target. Returns 0 for success, non-zero for NAK or other + * error. + */ + int (*send)(I3CTarget *s, const uint8_t *data, uint32_t num_to_send, + uint32_t *num_sent); + /* + * Target to controller. I3C targets are able to terminate reads early, so + * this returns the number of bytes read from the target. + */ + uint32_t (*recv)(I3CTarget *s, uint8_t *data, uint32_t num_to_read); + /* Notify the target of a bus state change. */ + int (*event)(I3CTarget *s, enum I3CEvent event); + /* + * Handle a read CCC transmitted from a controller. + * CCCs are I3C commands that I3C targets support. + * The target can NACK the CCC if it does not support it. + */ + int (*handle_ccc_read)(I3CTarget *s, uint8_t *data, uint32_t num_to_read, + uint32_t *num_read); + /* + * Handle a write CCC transmitted from a controller. + * CCCs are I3C commands that I3C targets support. + * The target can NACK the CCC if it does not support it. + */ + int (*handle_ccc_write)(I3CTarget *s, const uint8_t *data, + uint32_t num_to_send, uint32_t *num_sent); + + /* + * Matches and adds the candidate if the address matches the candidate's + * address. + * Returns true if the address matched, or if this was a broadcast, and + * updates the device list. Otherwise returns false. + */ + bool (*target_match)(I3CTarget *candidate, uint8_t address, bool is_read, + bool broadcast, bool in_entdaa); +}; + +struct I3CTarget { + DeviceState parent_obj; + + uint8_t address; + uint8_t static_address; + uint8_t dcr; + uint8_t bcr; + uint64_t pid; + + /* CCC State tracking. */ + I3CCCC curr_ccc; + uint8_t ccc_byte_offset; + bool in_ccc; + bool in_test_mode; +}; + +struct I3CNode { + I3CTarget *target; + QLIST_ENTRY(I3CNode) next; +}; + +typedef struct I3CNode I3CNode; + +typedef QLIST_HEAD(I3CNodeList, I3CNode) I3CNodeList; + +#define TYPE_I3C_BUS "i3c-bus" +OBJECT_DECLARE_TYPE(I3CBus, I3CBusClass, I3C_BUS) + +struct I3CBus { + BusState parent_obj; + + /* Legacy I2C. */ + I2CBus *i2c_bus; + + I3CNodeList current_devs; + bool broadcast; + uint8_t ccc; + bool in_ccc; + bool in_entdaa; + uint8_t saved_address; +}; + +struct I3CBusClass { + BusClass parent_class; + + /* Handle an incoming IBI request from a target */ + int (*ibi_handle) (I3CBus *bus, uint8_t addr, bool is_recv); + /* Receive data from an IBI request */ + int (*ibi_recv) (I3CBus *bus, uint8_t data); + /* Do anything that needs to be done, since the IBI is finished. */ + int (*ibi_finish) (I3CBus *bus); +}; + +I3CBus *i3c_init_bus(DeviceState *parent, const char *name); +I3CBus *i3c_init_bus_type(const char *type, DeviceState *parent, + const char *name); +void i3c_set_target_address(I3CTarget *dev, uint8_t address); +bool i3c_bus_busy(I3CBus *bus); + +/* + * Start a transfer on an I3C bus. + * If is_recv is known at compile-time (i.e. a device will always be sending or + * will always be receiving at a certain point), prefer to use i3c_start_recv or + * i3c_start_send instead. + * + * Returns 0 on success, non-zero on an error. + */ +int i3c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv); + +/* + * Start a receive transfer on an I3C bus. + * + * Returns 0 on success, non-zero on an error + */ +int i3c_start_recv(I3CBus *bus, uint8_t address); + +/* + * Start a send transfer on an I3C bus. + * + * Returns 0 on success, non-zero on an error + */ +int i3c_start_send(I3CBus *bus, uint8_t address); + +void i3c_end_transfer(I3CBus *bus); +void i3c_nack(I3CBus *bus); +int i3c_send_byte(I3CBus *bus, uint8_t data); +int i3c_send(I3CBus *bus, const uint8_t *data, uint32_t num_to_send, + uint32_t *num_sent); +/* + * I3C receives can only NACK on a CCC. The target should NACK a CCC it does not + * support. + */ +int i3c_recv_byte(I3CBus *bus, uint8_t *data); +int i3c_recv(I3CBus *bus, uint8_t *data, uint32_t num_to_read, + uint32_t *num_read); +bool i3c_scan_bus(I3CBus *bus, uint8_t address, enum I3CEvent event); +int i3c_do_entdaa(I3CBus *bus, uint8_t address, uint64_t *pid, uint8_t *bcr, + uint8_t *dcr); +int i3c_start_device_transfer(I3CTarget *dev, int send_length); +bool i3c_target_match_and_add(I3CBus *bus, I3CTarget *target, uint8_t address, + enum I3CEvent event); +int i3c_target_send_ibi(I3CTarget *t, uint8_t addr, bool is_recv); +int i3c_target_send_ibi_bytes(I3CTarget *t, uint8_t data); +int i3c_target_ibi_finish(I3CTarget *t, uint8_t data); + +/* + * Legacy I2C functions. + * + * These are wrapper for I2C functions that take in an I3C bus instead of an I2C + * bus. Internally they use the I2C bus (and devices attached to it) that's a + * part of the I3C bus + */ +void legacy_i2c_nack(I3CBus *bus); +uint8_t legacy_i2c_recv(I3CBus *bus); +int legacy_i2c_send(I3CBus *bus, uint8_t data); +int legacy_i2c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv); +int legacy_i2c_start_recv(I3CBus *bus, uint8_t address); +int legacy_i2c_start_send(I3CBus *bus, uint8_t address); +void legacy_i2c_end_transfer(I3CBus *bus); +I2CSlave *legacy_i2c_device_create_simple(I3CBus *bus, const char *name, + uint8_t addr); + +/** + * Create an I3C Target. + * + * The target returned from this function still needs to be realized. + */ +I3CTarget *i3c_target_new(const char *name, uint8_t addr, uint8_t dcr, + uint8_t bcr, uint64_t pid); + +/** + * Create and realize an I3C target. + * + * Create the target, initialize it, put it on the specified I3C bus, and + * realize it. + */ +I3CTarget *i3c_target_create_simple(I3CBus *bus, const char *name, + uint8_t addr, uint8_t dcr, uint8_t bcr, + uint64_t pid); + +/* Realize and drop the reference count on an I3C target. */ +bool i3c_target_realize_and_unref(I3CTarget *dev, I3CBus *bus, Error **errp); + +#endif /* QEMU_INCLUDE_HW_I3C_I3C_H_ */ diff --git a/include/hw/i3c/mock-i3c-target.h b/include/hw/i3c/mock-i3c-target.h new file mode 100644 index 000000000000..8c6003ae8b60 --- /dev/null +++ b/include/hw/i3c/mock-i3c-target.h @@ -0,0 +1,52 @@ +#ifndef MOCK_I3C_TARGET_H_ +#define MOCK_I3C_TARGET_H_ + +/* + * Mock I3C Device + * + * Copyright (c) 2025 Google LLC + * + * The mock I3C device can be thought of as a simple EEPROM. It has a buffer, + * and the pointer in the buffer is reset to 0 on an I3C STOP. + * To write to the buffer, issue a private write and send data. + * To read from the buffer, issue a private read. + * + * The mock target also supports sending target interrupt IBIs. + * To issue an IBI, set the 'ibi-magic-num' property to a non-zero number, and + * send that number in a private transaction. The mock target will issue an IBI + * after 1 second. + * + * It also supports a handful of CCCs that are typically used when probing I3C + * devices. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/timer.h" +#include "hw/i3c/i3c.h" + +#define TYPE_MOCK_I3C_TARGET "mock-i3c-target" +OBJECT_DECLARE_SIMPLE_TYPE(MockI3cTargetState, MOCK_I3C_TARGET) + +struct MockI3cTargetState { + I3CTarget parent_obj; + + /* General device state */ + bool can_ibi; + QEMUTimer qtimer; + size_t p_buf; + uint8_t *buf; + + /* For Handing CCCs. */ + bool in_ccc; + I3CCCC curr_ccc; + uint8_t ccc_byte_offset; + + struct { + uint32_t buf_size; + uint8_t ibi_magic; + } cfg; +}; + +#endif diff --git a/include/hw/loongarch/boot.h b/include/hw/loongarch/boot.h index 9819f7fbe30e..4984322f75e3 100644 --- a/include/hw/loongarch/boot.h +++ b/include/hw/loongarch/boot.h @@ -113,6 +113,7 @@ struct memmap_entry { uint32_t reserved; }; -void loongarch_load_kernel(MachineState *ms, struct loongarch_boot_info *info); +void loongarch_load_kernel(MachineState *ms, struct loongarch_boot_info *info, + uint64_t phys_addr_mask); #endif /* HW_LOONGARCH_BOOT_H */ diff --git a/include/hw/m68k/mcf.h b/include/hw/m68k/mcf.h index 5d9f876ffebc..b2a599adaadf 100644 --- a/include/hw/m68k/mcf.h +++ b/include/hw/m68k/mcf.h @@ -14,9 +14,8 @@ DeviceState *mcf_uart_create(qemu_irq irq, Chardev *chr); DeviceState *mcf_uart_create_mmap(hwaddr base, qemu_irq irq, Chardev *chr); /* mcf_intc.c */ -qemu_irq *mcf_intc_init(struct MemoryRegion *sysmem, - hwaddr base, - M68kCPU *cpu); +DeviceState *mcf_intc_init(struct MemoryRegion *sysmem, + hwaddr base, M68kCPU *cpu); /* mcf5206.c */ #define TYPE_MCF5206_MBAR "mcf5206-mbar" diff --git a/include/hw/misc/sifive_e_aon.h b/include/hw/misc/sifive_e_aon.h index efa2c3023f66..e907aa7869db 100644 --- a/include/hw/misc/sifive_e_aon.h +++ b/include/hw/misc/sifive_e_aon.h @@ -46,7 +46,7 @@ struct SiFiveEAONState { MemoryRegion mmio; /*< watchdog timer >*/ - QEMUTimer *wdog_timer; + QEMUTimer wdog_timer; qemu_irq wdog_irq; uint64_t wdog_restart_time; uint64_t wdogclk_freq; diff --git a/include/hw/net/npcm_gmac.h b/include/hw/net/npcm_gmac.h index d4fe49ada573..23b9841a80ea 100644 --- a/include/hw/net/npcm_gmac.h +++ b/include/hw/net/npcm_gmac.h @@ -24,7 +24,8 @@ #include "hw/core/sysbus.h" #include "net/net.h" -#define NPCM_GMAC_NR_REGS (0x1060 / sizeof(uint32_t)) +#define NPCM_GMAC_REG_SIZE 0x1060 +#define NPCM_GMAC_NR_REGS (NPCM_GMAC_REG_SIZE / sizeof(uint32_t)) #define NPCM_GMAC_MAX_PHYS 32 #define NPCM_GMAC_MAX_PHY_REGS 32 diff --git a/include/hw/nitro/heartbeat.h b/include/hw/nitro/heartbeat.h new file mode 100644 index 000000000000..6b9271a47df5 --- /dev/null +++ b/include/hw/nitro/heartbeat.h @@ -0,0 +1,24 @@ +/* + * Nitro Heartbeat device + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_NITRO_HEARTBEAT_H +#define HW_MISC_NITRO_HEARTBEAT_H + +#include "hw/nitro/nitro-vsock-bus.h" +#include "chardev/char-fe.h" +#include "qom/object.h" + +#define TYPE_NITRO_HEARTBEAT "nitro-heartbeat" +OBJECT_DECLARE_SIMPLE_TYPE(NitroHeartbeatState, NITRO_HEARTBEAT) + +struct NitroHeartbeatState { + NitroVsockDevice parent_obj; + + CharFrontend vsock; /* vsock server chardev for heartbeat */ + bool done; +}; + +#endif /* HW_MISC_NITRO_HEARTBEAT_H */ diff --git a/include/hw/nitro/machine.h b/include/hw/nitro/machine.h new file mode 100644 index 000000000000..d78ba7d6dc3f --- /dev/null +++ b/include/hw/nitro/machine.h @@ -0,0 +1,20 @@ +/* + * Nitro Enclaves (accel) machine + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_NITRO_MACHINE_H +#define HW_NITRO_MACHINE_H + +#include "hw/core/boards.h" +#include "qom/object.h" + +#define TYPE_NITRO_MACHINE MACHINE_TYPE_NAME("nitro") +OBJECT_DECLARE_SIMPLE_TYPE(NitroMachineState, NITRO_MACHINE) + +struct NitroMachineState { + MachineState parent; +}; + +#endif /* HW_NITRO_MACHINE_H */ diff --git a/include/hw/nitro/nitro-vsock-bus.h b/include/hw/nitro/nitro-vsock-bus.h new file mode 100644 index 000000000000..064260aa410f --- /dev/null +++ b/include/hw/nitro/nitro-vsock-bus.h @@ -0,0 +1,71 @@ +/* + * Nitro Enclave Vsock Bus + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_NITRO_VSOCK_BUS_H +#define HW_NITRO_VSOCK_BUS_H + +#include "hw/core/qdev.h" +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_NITRO_VSOCK_BUS "nitro-vsock-bus" +OBJECT_DECLARE_SIMPLE_TYPE(NitroVsockBus, NITRO_VSOCK_BUS) + +#define TYPE_NITRO_VSOCK_BRIDGE "nitro-vsock-bridge" +OBJECT_DECLARE_SIMPLE_TYPE(NitroVsockBridge, NITRO_VSOCK_BRIDGE) + +#define TYPE_NITRO_VSOCK_DEVICE "nitro-vsock-device" +OBJECT_DECLARE_TYPE(NitroVsockDevice, NitroVsockDeviceClass, + NITRO_VSOCK_DEVICE) + +struct NitroVsockBus { + BusState parent_obj; +}; + +struct NitroVsockBridge { + SysBusDevice parent_obj; + + NitroVsockBus bus; + uint32_t enclave_cid; +}; + +struct NitroVsockDevice { + DeviceState parent_obj; +}; + +struct NitroVsockDeviceClass { + DeviceClass parent_class; + + /* + * Called after the enclave has been started and the CID is known. + * Devices use this to establish vsock connections to the enclave. + */ + void (*enclave_started)(NitroVsockDevice *dev, uint32_t enclave_cid, + Error **errp); +}; + +/* + * Machine helper to create the Nitro vsock bridge sysbus device. + */ +NitroVsockBridge *nitro_vsock_bridge_create(void); + +/* + * Find the Nitro vsock bridge on the sysbus. + */ +static inline NitroVsockBridge *nitro_vsock_bridge_find(void) +{ + return NITRO_VSOCK_BRIDGE( + object_resolve_path_type("", TYPE_NITRO_VSOCK_BRIDGE, NULL)); +} + +/* + * Notify the bridge that the enclave has started. Dispatches + * enclave_started() to all devices on the bus. + */ +void nitro_vsock_bridge_start_enclave(NitroVsockBridge *bridge, + uint32_t enclave_cid, Error **errp); + +#endif /* HW_NITRO_VSOCK_BUS_H */ diff --git a/include/hw/nitro/serial-vsock.h b/include/hw/nitro/serial-vsock.h new file mode 100644 index 000000000000..c365880e110a --- /dev/null +++ b/include/hw/nitro/serial-vsock.h @@ -0,0 +1,24 @@ +/* + * Nitro Enclave Serial (vsock) + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_CHAR_NITRO_SERIAL_VSOCK_H +#define HW_CHAR_NITRO_SERIAL_VSOCK_H + +#include "hw/nitro/nitro-vsock-bus.h" +#include "chardev/char-fe.h" +#include "qom/object.h" + +#define TYPE_NITRO_SERIAL_VSOCK "nitro-serial-vsock" +OBJECT_DECLARE_SIMPLE_TYPE(NitroSerialVsockState, NITRO_SERIAL_VSOCK) + +struct NitroSerialVsockState { + NitroVsockDevice parent_obj; + + CharFrontend output; /* chardev to write console output to */ + CharFrontend vsock; /* vsock chardev to enclave console */ +}; + +#endif /* HW_CHAR_NITRO_SERIAL_VSOCK_H */ diff --git a/include/hw/pci-bridge/cxl_downstream_port.h b/include/hw/pci-bridge/cxl_downstream_port.h new file mode 100644 index 000000000000..1611504c8764 --- /dev/null +++ b/include/hw/pci-bridge/cxl_downstream_port.h @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef CXL_DOWNSTREAM_PORT_H +#define CXL_DOWNSTREAM_PORT_H +#include "include/hw/cxl/cxl_port.h" + +typedef struct CXLDownstreamPort CXLDownstreamPort; +CXLPhyPortPerst *cxl_dsp_get_perst(CXLDownstreamPort *dsp); + +#endif diff --git a/include/hw/pci-bridge/cxl_upstream_port.h b/include/hw/pci-bridge/cxl_upstream_port.h index f208397ffe96..dfe01771c7d7 100644 --- a/include/hw/pci-bridge/cxl_upstream_port.h +++ b/include/hw/pci-bridge/cxl_upstream_port.h @@ -4,6 +4,7 @@ #include "hw/pci/pcie.h" #include "hw/pci/pcie_port.h" #include "hw/cxl/cxl.h" +#include "hw/cxl/cxl_port.h" typedef struct CXLUpstreamPort { /*< private >*/ @@ -12,9 +13,11 @@ typedef struct CXLUpstreamPort { /*< public >*/ CXLComponentState cxl_cstate; CXLCCI swcci; + CXLPhyPortPerst perst; PCIExpLinkSpeed speed; PCIExpLinkWidth width; + bool flitmode; DOECap doe_cdat; uint64_t sn; diff --git a/include/hw/pci-host/astro.h b/include/hw/pci-host/astro.h index 832125a05af6..fce052c9f86f 100644 --- a/include/hw/pci-host/astro.h +++ b/include/hw/pci-host/astro.h @@ -82,6 +82,8 @@ struct AstroState { uint64_t tlb_tcnfg; uint64_t tlb_pdir_base; + uint8_t phys_addr_bits; + struct ElroyState *elroy[ELROY_NUM]; MemoryRegion this_mem; diff --git a/include/hw/pci/pci.h b/include/hw/pci/pci.h index d9835dfd0dd6..5b179091dee9 100644 --- a/include/hw/pci/pci.h +++ b/include/hw/pci/pci.h @@ -219,8 +219,6 @@ enum { /* Link active status in endpoint capability is always set */ #define QEMU_PCIE_LNKSTA_DLLLA_BITNR 8 QEMU_PCIE_LNKSTA_DLLLA = (1 << QEMU_PCIE_LNKSTA_DLLLA_BITNR), -#define QEMU_PCIE_EXTCAP_INIT_BITNR 9 - QEMU_PCIE_EXTCAP_INIT = (1 << QEMU_PCIE_EXTCAP_INIT_BITNR), #define QEMU_PCIE_CXL_BITNR 10 QEMU_PCIE_CAP_CXL = (1 << QEMU_PCIE_CXL_BITNR), #define QEMU_PCIE_ERR_UNC_MASK_BITNR 11 @@ -382,6 +380,7 @@ const char *pci_root_bus_path(PCIDevice *dev); bool pci_bus_bypass_iommu(PCIBus *bus); PCIDevice *pci_find_device(PCIBus *bus, int bus_num, uint8_t devfn); int pci_qdev_find_device(const char *id, PCIDevice **pdev); +void pci_qdev_property_add_specifics(DeviceClass *dc); void pci_bus_get_w64_range(PCIBus *bus, Range *range); void pci_device_deassert_intx(PCIDevice *dev); diff --git a/include/hw/pci/pci_device.h b/include/hw/pci/pci_device.h index 88ccea501136..5cac6e168862 100644 --- a/include/hw/pci/pci_device.h +++ b/include/hw/pci/pci_device.h @@ -62,6 +62,9 @@ struct PCIDevice { bool partially_hotplugged; bool enabled; + /* only for s390x */ + char *loadparm; + /* PCI config space */ uint8_t *config; diff --git a/include/hw/pci/pcie.h b/include/hw/pci/pcie.h index d4e065db8286..71ba94874b4b 100644 --- a/include/hw/pci/pcie.h +++ b/include/hw/pci/pcie.h @@ -144,7 +144,7 @@ void pcie_ari_init(PCIDevice *dev, uint16_t offset); void pcie_dev_ser_num_init(PCIDevice *dev, uint16_t offset, uint64_t ser_num); void pcie_ats_init(PCIDevice *dev, uint16_t offset, bool aligned); void pcie_cap_fill_link_ep_usp(PCIDevice *dev, PCIExpLinkWidth width, - PCIExpLinkSpeed speed); + PCIExpLinkSpeed speed, bool flitmode); void pcie_cap_slot_pre_plug_cb(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp); diff --git a/include/hw/pci/pcie_port.h b/include/hw/pci/pcie_port.h index 7cd7af8cfa4b..b28af067a664 100644 --- a/include/hw/pci/pcie_port.h +++ b/include/hw/pci/pcie_port.h @@ -58,9 +58,7 @@ struct PCIESlot { PCIExpLinkSpeed speed; PCIExpLinkWidth width; - - /* Disable ACS (really for a pcie_root_port) */ - bool disable_acs; + bool flitmode; /* Indicates whether any type of hot-plug is allowed on the slot */ bool hotplug; diff --git a/include/hw/ppc/pnv_occ.h b/include/hw/ppc/pnv_occ.h index 84bdf5004da3..92a4dbf1a0fb 100644 --- a/include/hw/ppc/pnv_occ.h +++ b/include/hw/ppc/pnv_occ.h @@ -47,6 +47,10 @@ struct PnvOCC { /* OCC Misc interrupt */ uint64_t occmisc; + /* OCC Flags */ +#define NR_FLAG_REGS 8 + uint32_t occflags[NR_FLAG_REGS]; + qemu_irq psi_irq; /* OCCs operate on regions of HOMER memory */ diff --git a/include/hw/ppc/spapr.h b/include/hw/ppc/spapr.h index 5476ac7ce7bf..b022f8dd25d0 100644 --- a/include/hw/ppc/spapr.h +++ b/include/hw/ppc/spapr.h @@ -4,6 +4,7 @@ #include "qemu/units.h" #include "system/dma.h" #include "hw/core/boards.h" +#include "hw/ppc/spapr_common.h" #include "hw/ppc/spapr_drc.h" #include "hw/mem/pc-dimm.h" #include "hw/ppc/spapr_ovec.h" @@ -946,13 +947,6 @@ int spapr_rtc_import_offset(SpaprRtcState *rtc, int64_t legacy_offset); #define SPAPR_MEMORY_BLOCK_SIZE ((hwaddr)1 << 28) /* 256MB */ -/* - * This defines the maximum number of DIMM slots we can have for sPAPR - * guest. This is not defined by sPAPR but we are defining it to 32 slots - * based on default number of slots provided by PowerPC kernel. - */ -#define SPAPR_MAX_RAM_SLOTS 32 - /* 1GB alignment for hotplug memory region */ #define SPAPR_DEVICE_MEM_ALIGN (1 * GiB) diff --git a/include/hw/ppc/spapr_common.h b/include/hw/ppc/spapr_common.h new file mode 100644 index 000000000000..a315b6858051 --- /dev/null +++ b/include/hw/ppc/spapr_common.h @@ -0,0 +1,17 @@ +/* + * Common definitions for PPC sPAPR + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_SPAPR_COMMON_H +#define HW_SPAPR_COMMON_H + +/* + * The maximum number of DIMM slots we can have for sPAPR guest. + * This is not defined by sPAPR but we are defining it to 32 slots + * based on default number of slots provided by PowerPC kernel. + */ +#define SPAPR_MAX_RAM_SLOTS 32 + +#endif /* HW_SPAPR_COMMON_H */ diff --git a/include/hw/rtc/pl031.h b/include/hw/rtc/pl031.h index c8b26c2f00e4..75779c631225 100644 --- a/include/hw/rtc/pl031.h +++ b/include/hw/rtc/pl031.h @@ -36,7 +36,6 @@ struct PL031State { uint32_t tick_offset_vmstate; uint32_t tick_offset; bool tick_offset_migrated; - bool migrate_tick_offset; uint32_t mr; uint32_t lr; diff --git a/include/hw/s390x/ipl/qipl.h b/include/hw/s390x/ipl/qipl.h index 6824391111cb..8d3c83a80bd5 100644 --- a/include/hw/s390x/ipl/qipl.h +++ b/include/hw/s390x/ipl/qipl.h @@ -20,6 +20,17 @@ #define LOADPARM_LEN 8 #define NO_LOADPARM "\0\0\0\0\0\0\0\0" +enum S390IplType { + S390_IPL_TYPE_FCP = 0x00, + S390_IPL_TYPE_CCW = 0x02, + S390_IPL_TYPE_PCI = 0x04, + S390_IPL_TYPE_PV = 0x05, + S390_IPL_TYPE_QEMU_SCSI = 0xff +}; +typedef enum S390IplType S390IplType; + +#define QEMU_DEFAULT_IPL S390_IPL_TYPE_CCW + /* * The QEMU IPL Parameters will be stored at absolute address * 204 (0xcc) which means it is 32-bit word aligned but not @@ -98,6 +109,14 @@ struct IplBlockQemuScsi { } QEMU_PACKED; typedef struct IplBlockQemuScsi IplBlockQemuScsi; +struct IplBlockPci { + uint32_t reserved0[76]; + uint8_t opt; + uint8_t reserved1[3]; + uint32_t fid; +} QEMU_PACKED; +typedef struct IplBlockPci IplBlockPci; + union IplParameterBlock { struct { uint32_t len; @@ -113,6 +132,7 @@ union IplParameterBlock { IplBlockFcp fcp; IPLBlockPV pv; IplBlockQemuScsi scsi; + IplBlockPci pci; }; } QEMU_PACKED; struct { diff --git a/include/hw/s390x/s390-pci-clp.h b/include/hw/s390x/ipl/s390-pci-clp.h similarity index 100% rename from include/hw/s390x/s390-pci-clp.h rename to include/hw/s390x/ipl/s390-pci-clp.h diff --git a/include/hw/s390x/s390-pci-bus.h b/include/hw/s390x/s390-pci-bus.h index 04944d4fed75..9228523ce8e5 100644 --- a/include/hw/s390x/s390-pci-bus.h +++ b/include/hw/s390x/s390-pci-bus.h @@ -19,7 +19,7 @@ #include "hw/s390x/sclp.h" #include "hw/s390x/s390_flic.h" #include "hw/s390x/css.h" -#include "hw/s390x/s390-pci-clp.h" +#include "hw/s390x/ipl/s390-pci-clp.h" #include "qom/object.h" #define TYPE_S390_PCI_HOST_BRIDGE "s390-pcihost" @@ -402,6 +402,8 @@ S390PCIBusDevice *s390_pci_find_dev_by_fh(S390pciState *s, uint32_t fh); S390PCIBusDevice *s390_pci_find_dev_by_fid(S390pciState *s, uint32_t fid); S390PCIBusDevice *s390_pci_find_dev_by_target(S390pciState *s, const char *target); +S390PCIBusDevice *s390_pci_find_dev_by_pci(S390pciState *s, + PCIDevice *pci_dev); S390PCIBusDevice *s390_pci_find_next_avail_dev(S390pciState *s, S390PCIBusDevice *pbdev); void s390_pci_ism_reset(void); diff --git a/include/hw/s390x/s390-pci-vfio.h b/include/hw/s390x/s390-pci-vfio.h index ae1b126ff70b..f7d6149daf60 100644 --- a/include/hw/s390x/s390-pci-vfio.h +++ b/include/hw/s390x/s390-pci-vfio.h @@ -13,32 +13,12 @@ #define HW_S390_PCI_VFIO_H #include "hw/s390x/s390-pci-bus.h" -#include CONFIG_DEVICES -#ifdef CONFIG_VFIO bool s390_pci_update_dma_avail(int fd, unsigned int *avail); S390PCIDMACount *s390_pci_start_dma_count(S390pciState *s, S390PCIBusDevice *pbdev); void s390_pci_end_dma_count(S390pciState *s, S390PCIDMACount *cnt); bool s390_pci_get_host_fh(S390PCIBusDevice *pbdev, uint32_t *fh); void s390_pci_get_clp_info(S390PCIBusDevice *pbdev); -#else -static inline bool s390_pci_update_dma_avail(int fd, unsigned int *avail) -{ - return false; -} -static inline S390PCIDMACount *s390_pci_start_dma_count(S390pciState *s, - S390PCIBusDevice *pbdev) -{ - return NULL; -} -static inline void s390_pci_end_dma_count(S390pciState *s, - S390PCIDMACount *cnt) { } -static inline bool s390_pci_get_host_fh(S390PCIBusDevice *pbdev, uint32_t *fh) -{ - return false; -} -static inline void s390_pci_get_clp_info(S390PCIBusDevice *pbdev) { } -#endif #endif diff --git a/include/hw/virtio/virtio-access.h b/include/hw/virtio/virtio-access.h index cd17d0c87ebd..506be642c9ff 100644 --- a/include/hw/virtio/virtio-access.h +++ b/include/hw/virtio/virtio-access.h @@ -21,28 +21,9 @@ #include "hw/virtio/virtio.h" #include "hw/virtio/virtio-bus.h" -#if defined(TARGET_PPC64) || defined(TARGET_ARM) -#define LEGACY_VIRTIO_IS_BIENDIAN 1 -#endif - -static inline bool virtio_access_is_big_endian(VirtIODevice *vdev) -{ -#if defined(LEGACY_VIRTIO_IS_BIENDIAN) - return virtio_is_big_endian(vdev); -#elif TARGET_BIG_ENDIAN - if (virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) { - /* Devices conforming to VIRTIO 1.0 or later are always LE. */ - return false; - } - return true; -#else - return false; -#endif -} - static inline void virtio_stw_p(VirtIODevice *vdev, void *ptr, uint16_t v) { - if (virtio_access_is_big_endian(vdev)) { + if (virtio_vdev_is_big_endian(vdev)) { stw_be_p(ptr, v); } else { stw_le_p(ptr, v); @@ -51,7 +32,7 @@ static inline void virtio_stw_p(VirtIODevice *vdev, void *ptr, uint16_t v) static inline void virtio_stl_p(VirtIODevice *vdev, void *ptr, uint32_t v) { - if (virtio_access_is_big_endian(vdev)) { + if (virtio_vdev_is_big_endian(vdev)) { stl_be_p(ptr, v); } else { stl_le_p(ptr, v); @@ -60,7 +41,7 @@ static inline void virtio_stl_p(VirtIODevice *vdev, void *ptr, uint32_t v) static inline void virtio_stq_p(VirtIODevice *vdev, void *ptr, uint64_t v) { - if (virtio_access_is_big_endian(vdev)) { + if (virtio_vdev_is_big_endian(vdev)) { stq_be_p(ptr, v); } else { stq_le_p(ptr, v); @@ -69,7 +50,7 @@ static inline void virtio_stq_p(VirtIODevice *vdev, void *ptr, uint64_t v) static inline int virtio_lduw_p(VirtIODevice *vdev, const void *ptr) { - if (virtio_access_is_big_endian(vdev)) { + if (virtio_vdev_is_big_endian(vdev)) { return lduw_be_p(ptr); } else { return lduw_le_p(ptr); @@ -78,7 +59,7 @@ static inline int virtio_lduw_p(VirtIODevice *vdev, const void *ptr) static inline int virtio_ldl_p(VirtIODevice *vdev, const void *ptr) { - if (virtio_access_is_big_endian(vdev)) { + if (virtio_vdev_is_big_endian(vdev)) { return ldl_be_p(ptr); } else { return ldl_le_p(ptr); @@ -87,7 +68,7 @@ static inline int virtio_ldl_p(VirtIODevice *vdev, const void *ptr) static inline uint64_t virtio_ldq_p(VirtIODevice *vdev, const void *ptr) { - if (virtio_access_is_big_endian(vdev)) { + if (virtio_vdev_is_big_endian(vdev)) { return ldq_be_p(ptr); } else { return ldq_le_p(ptr); @@ -97,9 +78,9 @@ static inline uint64_t virtio_ldq_p(VirtIODevice *vdev, const void *ptr) static inline uint16_t virtio_tswap16(VirtIODevice *vdev, uint16_t s) { #if HOST_BIG_ENDIAN - return virtio_access_is_big_endian(vdev) ? s : bswap16(s); + return virtio_vdev_is_big_endian(vdev) ? s : bswap16(s); #else - return virtio_access_is_big_endian(vdev) ? bswap16(s) : s; + return virtio_vdev_is_big_endian(vdev) ? bswap16(s) : s; #endif } @@ -111,9 +92,9 @@ static inline void virtio_tswap16s(VirtIODevice *vdev, uint16_t *s) static inline uint32_t virtio_tswap32(VirtIODevice *vdev, uint32_t s) { #if HOST_BIG_ENDIAN - return virtio_access_is_big_endian(vdev) ? s : bswap32(s); + return virtio_vdev_is_big_endian(vdev) ? s : bswap32(s); #else - return virtio_access_is_big_endian(vdev) ? bswap32(s) : s; + return virtio_vdev_is_big_endian(vdev) ? bswap32(s) : s; #endif } @@ -125,9 +106,9 @@ static inline void virtio_tswap32s(VirtIODevice *vdev, uint32_t *s) static inline uint64_t virtio_tswap64(VirtIODevice *vdev, uint64_t s) { #if HOST_BIG_ENDIAN - return virtio_access_is_big_endian(vdev) ? s : bswap64(s); + return virtio_vdev_is_big_endian(vdev) ? s : bswap64(s); #else - return virtio_access_is_big_endian(vdev) ? bswap64(s) : s; + return virtio_vdev_is_big_endian(vdev) ? bswap64(s) : s; #endif } diff --git a/include/hw/virtio/virtio-balloon.h b/include/hw/virtio/virtio-balloon.h index 79ac194ccec8..abbf33971886 100644 --- a/include/hw/virtio/virtio-balloon.h +++ b/include/hw/virtio/virtio-balloon.h @@ -70,7 +70,6 @@ struct VirtIOBalloon { int64_t stats_poll_interval; uint32_t host_features; - bool qemu_4_0_config_size; uint32_t poison_val; /* State of the resettable container */ diff --git a/include/hw/virtio/virtio-gpu.h b/include/hw/virtio/virtio-gpu.h index 58e0f91fda65..f69fc1946273 100644 --- a/include/hw/virtio/virtio-gpu.h +++ b/include/hw/virtio/virtio-gpu.h @@ -100,6 +100,7 @@ enum virtio_gpu_base_conf_flags { VIRTIO_GPU_FLAG_RUTABAGA_ENABLED, VIRTIO_GPU_FLAG_VENUS_ENABLED, VIRTIO_GPU_FLAG_RESOURCE_UUID_ENABLED, + VIRTIO_GPU_FLAG_DRM_ENABLED, }; #define virtio_gpu_virgl_enabled(_cfg) \ @@ -122,6 +123,8 @@ enum virtio_gpu_base_conf_flags { (_cfg.hostmem > 0) #define virtio_gpu_venus_enabled(_cfg) \ (_cfg.flags & (1 << VIRTIO_GPU_FLAG_VENUS_ENABLED)) +#define virtio_gpu_drm_enabled(_cfg) \ + (_cfg.flags & (1 << VIRTIO_GPU_FLAG_DRM_ENABLED)) struct virtio_gpu_base_conf { uint32_t max_outputs; @@ -233,6 +236,13 @@ struct VirtIOGPUClass { Error **errp); }; +struct virtio_gpu_virgl_context_fence { + uint32_t ctx_id; + uint32_t ring_idx; + uint64_t fence_id; + QSLIST_ENTRY(virtio_gpu_virgl_context_fence) next; +}; + /* VirtIOGPUGL renderer states */ typedef enum { RS_START, /* starting state */ @@ -250,6 +260,12 @@ struct VirtIOGPUGL { QEMUTimer *print_stats; QEMUBH *cmdq_resume_bh; + + QEMUBH *async_fence_bh; + QSLIST_HEAD(, virtio_gpu_virgl_context_fence) async_fenceq; + + MemoryRegion hostmem_background; + void *hostmem_mmap; }; struct VhostUserGPU { @@ -357,7 +373,8 @@ bool virtio_gpu_scanout_blob_to_fb(struct virtio_gpu_framebuffer *fb, /* virtio-gpu-udmabuf.c */ bool virtio_gpu_have_udmabuf(void); void virtio_gpu_init_udmabuf(struct virtio_gpu_simple_resource *res); -void virtio_gpu_fini_udmabuf(struct virtio_gpu_simple_resource *res); +void virtio_gpu_fini_udmabuf(VirtIOGPU *g, + struct virtio_gpu_simple_resource *res); int virtio_gpu_update_dmabuf(VirtIOGPU *g, uint32_t scanout_id, struct virtio_gpu_simple_resource *res, @@ -376,8 +393,11 @@ void virtio_gpu_virgl_process_cmd(VirtIOGPU *g, struct virtio_gpu_ctrl_command *cmd); void virtio_gpu_virgl_fence_poll(VirtIOGPU *g); void virtio_gpu_virgl_reset_scanout(VirtIOGPU *g); -void virtio_gpu_virgl_reset(VirtIOGPU *g); -int virtio_gpu_virgl_init(VirtIOGPU *g); GArray *virtio_gpu_virgl_get_capsets(VirtIOGPU *g); +void virtio_gpu_virgl_reset_async_fences(VirtIOGPU *g); +void virtio_gpu_virgl_resource_destroy(VirtIOGPU *g, + struct virtio_gpu_simple_resource *res, + Error **errp); +bool virtio_gpu_virgl_update_render_state(VirtIOGPU *g); #endif diff --git a/include/hw/virtio/virtio-input.h b/include/hw/virtio/virtio-input.h index e097b0b52176..1e94f8a03dbe 100644 --- a/include/hw/virtio/virtio-input.h +++ b/include/hw/virtio/virtio-input.h @@ -89,7 +89,6 @@ struct VirtIOInputHID { const QemuInputHandler *handler; QemuInputHandlerState *hs; int ledstate; - bool wheel_axis; }; struct VirtIOInputHost { diff --git a/include/hw/virtio/virtio-net.h b/include/hw/virtio/virtio-net.h index 5b8ab7bda796..371e37642829 100644 --- a/include/hw/virtio/virtio-net.h +++ b/include/hw/virtio/virtio-net.h @@ -218,7 +218,6 @@ struct VirtIONet { uint64_t saved_guest_offloads; AnnounceTimer announce_timer; bool needs_vnet_hdr_swap; - bool mtu_bypass_backend; /* primary failover device is hidden*/ bool failover_primary_hidden; bool failover; diff --git a/include/hw/virtio/virtio-pci.h b/include/hw/virtio/virtio-pci.h index 581bb830b792..5cb20b73f626 100644 --- a/include/hw/virtio/virtio-pci.h +++ b/include/hw/virtio/virtio-pci.h @@ -35,9 +35,6 @@ enum { VIRTIO_PCI_FLAG_MODERN_PIO_NOTIFY_BIT, VIRTIO_PCI_FLAG_PAGE_PER_VQ_BIT, VIRTIO_PCI_FLAG_ATS_BIT, - VIRTIO_PCI_FLAG_INIT_DEVERR_BIT, - VIRTIO_PCI_FLAG_INIT_LNKCTL_BIT, - VIRTIO_PCI_FLAG_INIT_PM_BIT, VIRTIO_PCI_FLAG_INIT_FLR_BIT, VIRTIO_PCI_FLAG_AER_BIT, VIRTIO_PCI_FLAG_ATS_PAGE_ALIGNED_BIT, @@ -63,15 +60,6 @@ enum { /* address space translation service */ #define VIRTIO_PCI_FLAG_ATS (1 << VIRTIO_PCI_FLAG_ATS_BIT) -/* Init error enabling flags */ -#define VIRTIO_PCI_FLAG_INIT_DEVERR (1 << VIRTIO_PCI_FLAG_INIT_DEVERR_BIT) - -/* Init Link Control register */ -#define VIRTIO_PCI_FLAG_INIT_LNKCTL (1 << VIRTIO_PCI_FLAG_INIT_LNKCTL_BIT) - -/* Init Power Management */ -#define VIRTIO_PCI_FLAG_INIT_PM (1 << VIRTIO_PCI_FLAG_INIT_PM_BIT) - /* Init The No_Soft_Reset bit of Power Management */ #define VIRTIO_PCI_FLAG_PM_NO_SOFT_RESET \ (1 << VIRTIO_PCI_FLAG_PM_NO_SOFT_RESET_BIT) diff --git a/include/hw/virtio/virtio.h b/include/hw/virtio/virtio.h index 27cd98d2fe11..6344bd7b689b 100644 --- a/include/hw/virtio/virtio.h +++ b/include/hw/virtio/virtio.h @@ -370,7 +370,7 @@ void virtio_queue_set_vector(VirtIODevice *vdev, int n, uint16_t vector); int virtio_queue_set_host_notifier_mr(VirtIODevice *vdev, int n, MemoryRegion *mr, bool assign); int virtio_set_status(VirtIODevice *vdev, uint8_t val); -void virtio_reset(void *opaque); +void virtio_reset(VirtIODevice *vdev); void virtio_queue_reset(VirtIODevice *vdev, uint32_t queue_index); void virtio_queue_enable(VirtIODevice *vdev, uint32_t queue_index); void virtio_update_irq(VirtIODevice *vdev); @@ -468,9 +468,14 @@ static inline bool virtio_host_has_feature(VirtIODevice *vdev, return virtio_has_feature(vdev->host_features, fbit); } -static inline bool virtio_is_big_endian(VirtIODevice *vdev) +static inline bool virtio_vdev_is_legacy(const VirtIODevice *vdev) { - if (!virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) { + return !virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1); +} + +static inline bool virtio_vdev_is_big_endian(const VirtIODevice *vdev) +{ + if (virtio_vdev_is_legacy(vdev)) { assert(vdev->device_endian != VIRTIO_DEVICE_ENDIAN_UNKNOWN); return vdev->device_endian == VIRTIO_DEVICE_ENDIAN_BIG; } @@ -547,4 +552,14 @@ QEMUBH *virtio_bh_new_guarded_full(DeviceState *dev, #define virtio_bh_new_guarded(dev, cb, opaque) \ virtio_bh_new_guarded_full((dev), (cb), (opaque), (stringify(cb))) +/* + * The "_io" variant runs BH only on a main-loop thread, while generic BH + * may run on a vCPU thread. + */ +QEMUBH *virtio_bh_io_new_guarded_full(DeviceState *dev, + QEMUBHFunc *cb, void *opaque, + const char *name); +#define virtio_bh_io_new_guarded(dev, cb, opaque) \ + virtio_bh_io_new_guarded_full((dev), (cb), (opaque), (stringify(cb))) + #endif diff --git a/include/hw/xen/xen-hvm-common.h b/include/hw/xen/xen-hvm-common.h index e1beca062ff1..d177ff14ea13 100644 --- a/include/hw/xen/xen-hvm-common.h +++ b/include/hw/xen/xen-hvm-common.h @@ -91,7 +91,8 @@ void xen_device_unrealize(DeviceListener *listener, DeviceState *dev); void xen_hvm_change_state_handler(void *opaque, bool running, RunState rstate); void xen_register_ioreq(XenIOState *state, unsigned int max_cpus, uint8_t handle_bufioreq, - const MemoryListener *xen_memory_listener); + const MemoryListener *xen_memory_listener, + bool mapcache); void cpu_ioreq_pio(ioreq_t *req); #endif /* HW_XEN_HVM_COMMON_H */ diff --git a/include/hw/xen/xen-pvh-common.h b/include/hw/xen/xen-pvh-common.h index 0ed07c569470..0209b798f3d2 100644 --- a/include/hw/xen/xen-pvh-common.h +++ b/include/hw/xen/xen-pvh-common.h @@ -84,6 +84,8 @@ struct XenPVHMachineState { /* PCI */ MemMapEntry pci_ecam, pci_mmio, pci_mmio_high; uint32_t pci_intx_irq_base; + + bool mapcache; } cfg; }; diff --git a/include/io/task.h b/include/io/task.h index 0b5342ee8432..98847f5994de 100644 --- a/include/io/task.h +++ b/include/io/task.h @@ -96,7 +96,7 @@ typedef void (*QIOTaskWorker)(QIOTask *task, * 1000, * myobject_operation_timer, * task, - * NULL); + * qio_task_free); * } * * @@ -138,9 +138,8 @@ typedef void (*QIOTaskWorker)(QIOTask *task, * the callback func 'myobject_operation_notify' shown * earlier to deal with the results. * - * Once this function returns false, object_unref will be called - * automatically on the task causing it to be released and the - * ref on QMyObject dropped too. + * Once this function returns FALSE, the task will be freed, + * causing it release the ref on QMyObject too. * * The QIOTask module can also be used to perform operations * in a background thread context, while still reporting the @@ -208,8 +207,8 @@ typedef void (*QIOTaskWorker)(QIOTask *task, * 'err' attribute in the task object to determine if * the operation was successful or not. * - * The returned task will be released when qio_task_complete() - * is invoked. + * The returned task must be released by calling + * qio_task_free() when no longer required. * * Returns: the task struct */ @@ -218,6 +217,19 @@ QIOTask *qio_task_new(Object *source, gpointer opaque, GDestroyNotify destroy); +/** + * qio_task_free: + * task: the task object to free + * + * Free the resources associated with the task. Typically + * the qio_task_complete() method will be called immediately + * before this to trigger the task callback, however, it is + * permissible to free the task in the case of cancellation. + * The destroy callback will be used to release the opaque + * data provided to qio_task_new(). + */ +void qio_task_free(QIOTask *task); + /** * qio_task_run_in_thread: * @task: the task struct @@ -268,8 +280,9 @@ void qio_task_wait_thread(QIOTask *task); * qio_task_complete: * @task: the task struct * - * Invoke the completion callback for @task and - * then free its memory. + * Invoke the completion callback for @task. This should typically + * only be invoked once on a task, and then qio_task_free() used + * to free it. */ void qio_task_complete(QIOTask *task); diff --git a/include/migration/colo.h b/include/migration/colo.h index d4fe422e4d33..8f94054a1076 100644 --- a/include/migration/colo.h +++ b/include/migration/colo.h @@ -25,9 +25,6 @@ void migrate_start_colo_process(MigrationState *s); bool migration_in_colo_state(void); /* loadvm */ -int migration_incoming_enable_colo(Error **errp); -void migration_incoming_disable_colo(void); -bool migration_incoming_colo_enabled(void); bool migration_incoming_in_colo_state(void); COLOMode get_colo_mode(void); diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h index 89f9f49d20ab..62c2abd0c49f 100644 --- a/include/migration/vmstate.h +++ b/include/migration/vmstate.h @@ -447,6 +447,16 @@ extern const VMStateInfo vmstate_info_qlist; .offset = vmstate_offset_pointer(_state, _field, _type), \ } +#define VMSTATE_VARRAY_INT32_ALLOC(_field, _state, _field_num, _version, _info, _type) {\ + .name = (stringify(_field)), \ + .version_id = (_version), \ + .num_offset = vmstate_offset_value(_state, _field_num, int32_t), \ + .info = &(_info), \ + .size = sizeof(_type), \ + .flags = VMS_VARRAY_INT32 | VMS_POINTER | VMS_ALLOC, \ + .offset = vmstate_offset_pointer(_state, _field, _type), \ +} + #define VMSTATE_VARRAY_UINT32_ALLOC(_field, _state, _field_num, _version, _info, _type) {\ .name = (stringify(_field)), \ .version_id = (_version), \ diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h index c3740ec616a0..296690e1f1c1 100644 --- a/include/monitor/monitor.h +++ b/include/monitor/monitor.h @@ -59,7 +59,4 @@ void monitor_register_hmp(const char *name, bool info, void monitor_register_hmp_info_hrt(const char *name, HumanReadableText *(*handler)(Error **errp)); -int error_vprintf_unless_qmp(const char *fmt, va_list ap) G_GNUC_PRINTF(1, 0); -int error_printf_unless_qmp(const char *fmt, ...) G_GNUC_PRINTF(1, 2); - #endif /* MONITOR_H */ diff --git a/include/net/eth.h b/include/net/eth.h index 14c34f530fee..efe270dbfe7d 100644 --- a/include/net/eth.h +++ b/include/net/eth.h @@ -39,7 +39,7 @@ struct eth_header { uint8_t h_dest[ETH_ALEN]; /* destination eth addr */ uint8_t h_source[ETH_ALEN]; /* source ether addr */ uint16_t h_proto; /* packet type ID field */ -}; +} QEMU_PACKED; struct vlan_header { uint16_t h_tci; /* priority and VLAN ID */ @@ -68,7 +68,7 @@ typedef struct tcp_header { uint16_t th_win; /* window */ uint16_t th_sum; /* checksum */ uint16_t th_urp; /* urgent pointer */ -} tcp_header; +} QEMU_PACKED tcp_header; #define TCP_FLAGS_ONLY(flags) ((flags) & 0x3f) @@ -85,7 +85,7 @@ typedef struct udp_header { uint16_t uh_dport; /* destination port */ uint16_t uh_ulen; /* udp length */ uint16_t uh_sum; /* udp checksum */ -} udp_header; +} QEMU_PACKED udp_header; typedef struct ip_pseudo_header { uint32_t ip_src; diff --git a/include/plugins/qemu-plugin.h b/include/plugins/qemu-plugin.h index 17a834dca90c..827e8e178770 100644 --- a/include/plugins/qemu-plugin.h +++ b/include/plugins/qemu-plugin.h @@ -76,6 +76,10 @@ typedef uint64_t qemu_plugin_id_t; * * version 6: * - changed return value of qemu_plugin_{read,write}_register from int to bool + * - added qemu_plugin_set_pc + * - added disconinuity callback API (for interrupts, exceptions, host calls) + * - added syscall filter callback API, which allows skipping syscalls and + * setting custom syscall return values */ extern QEMU_PLUGIN_EXPORT int qemu_plugin_version; @@ -325,11 +329,14 @@ typedef struct { * @QEMU_PLUGIN_CB_NO_REGS: callback does not access the CPU's regs * @QEMU_PLUGIN_CB_R_REGS: callback reads the CPU's regs * @QEMU_PLUGIN_CB_RW_REGS: callback reads and writes the CPU's regs + * @QEMU_PLUGIN_CB_RW_REGS_PC: callback reads and writes the CPU's + * regs and updates the PC */ enum qemu_plugin_cb_flags { QEMU_PLUGIN_CB_NO_REGS, QEMU_PLUGIN_CB_R_REGS, QEMU_PLUGIN_CB_RW_REGS, + QEMU_PLUGIN_CB_RW_REGS_PC, }; enum qemu_plugin_mem_rw { @@ -975,11 +982,14 @@ struct qemu_plugin_register; * writing value with qemu_plugin_write_register * @name: register name * @feature: optional feature descriptor, can be NULL + * @is_readonly: true if the register cannot be written via + * qemu_plugin_write_register */ typedef struct { struct qemu_plugin_register *handle; const char *name; const char *feature; + bool is_readonly; } qemu_plugin_reg_descriptor; /** @@ -1039,6 +1049,18 @@ QEMU_PLUGIN_API bool qemu_plugin_write_register(struct qemu_plugin_register *handle, GByteArray *buf); +/** + * qemu_plugin_set_pc() - set the program counter for the current vCPU + * + * @vaddr: the new virtual (guest) address for the program counter + * + * This function sets the program counter for the current vCPU to @vaddr and + * resumes execution at that address. This function does not return. + */ +QEMU_PLUGIN_API +__attribute__((__noreturn__)) +void qemu_plugin_set_pc(uint64_t vaddr); + /** * qemu_plugin_read_memory_vaddr() - read from memory using a virtual address * diff --git a/include/qemu/audio-capture.h b/include/qemu/audio-capture.h index a07412db857f..f1319c900208 100644 --- a/include/qemu/audio-capture.h +++ b/include/qemu/audio-capture.h @@ -8,19 +8,6 @@ #include "audio.h" -typedef struct CaptureVoiceOut CaptureVoiceOut; - -typedef enum { - AUD_CNOTIFY_ENABLE, - AUD_CNOTIFY_DISABLE -} audcnotification_e; - -struct audio_capture_ops { - void (*notify) (void *opaque, audcnotification_e cmd); - void (*capture) (void *opaque, const void *buf, int size); - void (*destroy) (void *opaque); -}; - struct capture_ops { void (*info) (void *opaque); void (*destroy) (void *opaque); @@ -32,12 +19,15 @@ typedef struct CaptureState { QLIST_ENTRY(CaptureState) entries; } CaptureState; -CaptureVoiceOut *AUD_add_capture( +CaptureVoiceOut *audio_be_add_capture( + AudioBackend *be, + const struct audsettings *as, + const struct audio_capture_ops *ops, + void *opaque); + +void audio_be_del_capture( AudioBackend *be, - struct audsettings *as, - struct audio_capture_ops *ops, - void *opaque - ); -void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque); + CaptureVoiceOut *cap, + void *cb_opaque); #endif /* QEMU_AUDIO_CAPTURE_H */ diff --git a/include/qemu/audio.h b/include/qemu/audio.h index a92e0b70ef07..cff8a334f369 100644 --- a/include/qemu/audio.h +++ b/include/qemu/audio.h @@ -38,92 +38,133 @@ typedef struct audsettings { int freq; int nchannels; AudioFormat fmt; - int endianness; + bool big_endian; } audsettings; typedef struct SWVoiceOut SWVoiceOut; typedef struct SWVoiceIn SWVoiceIn; +typedef struct CaptureVoiceOut CaptureVoiceOut; -struct AudioBackendClass { - ObjectClass parent_class; +typedef enum { + AUD_CNOTIFY_ENABLE, + AUD_CNOTIFY_DISABLE +} audcnotification_e; + +struct audio_capture_ops { + void (*notify) (void *opaque, audcnotification_e cmd); + void (*capture) (void *opaque, const void *buf, int size); + void (*destroy) (void *opaque); }; -typedef struct AudioBackend AudioBackend; +#define AUDIO_MAX_CHANNELS 16 +typedef struct Volume { + bool mute; + int channels; + uint8_t vol[AUDIO_MAX_CHANNELS]; +} Volume; + +typedef struct AudioBackend { + Object parent_obj; +} AudioBackend; -typedef struct QEMUAudioTimeStamp { - uint64_t old_ts; -} QEMUAudioTimeStamp; +typedef struct AudioBackendClass { + ObjectClass parent_class; + + bool (*realize)(AudioBackend *be, Audiodev *dev, Error **errp); + const char *(*get_id)(AudioBackend *be); + SWVoiceOut *(*open_out)(AudioBackend *be, + SWVoiceOut *sw, + const char *name, + void *callback_opaque, + audio_callback_fn callback_fn, + const struct audsettings *as); + SWVoiceIn *(*open_in)(AudioBackend *be, + SWVoiceIn *sw, + const char *name, + void *callback_opaque, + audio_callback_fn callback_fn, + const struct audsettings *as); + void (*close_out)(AudioBackend *be, SWVoiceOut *sw); + void (*close_in)(AudioBackend *be, SWVoiceIn *sw); + bool (*is_active_out)(AudioBackend *be, SWVoiceOut *sw); + bool (*is_active_in)(AudioBackend *be, SWVoiceIn *sw); + void (*set_active_out)(AudioBackend *be, SWVoiceOut *sw, bool on); + void (*set_active_in)(AudioBackend *be, SWVoiceIn *sw, bool on); + void (*set_volume_out)(AudioBackend *be, SWVoiceOut *sw, Volume *vol); + void (*set_volume_in)(AudioBackend *be, SWVoiceIn *sw, Volume *vol); + size_t (*write)(AudioBackend *be, SWVoiceOut *sw, void *buf, size_t size); + size_t (*read)(AudioBackend *be, SWVoiceIn *sw, void *buf, size_t size); + int (*get_buffer_size_out)(AudioBackend *be, SWVoiceOut *sw); + CaptureVoiceOut *(*add_capture)(AudioBackend *be, + const struct audsettings *as, + const struct audio_capture_ops *ops, + void *cb_opaque); + void (*del_capture)(AudioBackend *be, CaptureVoiceOut *cap, void *cb_opaque); + +#ifdef CONFIG_GIO + bool (*set_dbus_server)(AudioBackend *be, + GDBusObjectManagerServer *manager, + bool p2p, + Error **errp); +#endif +} AudioBackendClass; -bool AUD_backend_check(AudioBackend **be, Error **errp); +bool audio_be_check(AudioBackend **be, Error **errp); -SWVoiceOut *AUD_open_out ( +AudioBackend *audio_be_new(Audiodev *dev, Error **errp); + +SWVoiceOut *audio_be_open_out( AudioBackend *be, SWVoiceOut *sw, const char *name, void *callback_opaque, audio_callback_fn callback_fn, - struct audsettings *settings - ); + const struct audsettings *settings); -void AUD_close_out (AudioBackend *be, SWVoiceOut *sw); -size_t AUD_write (SWVoiceOut *sw, void *pcm_buf, size_t size); -int AUD_get_buffer_size_out (SWVoiceOut *sw); -void AUD_set_active_out(SWVoiceOut *sw, bool on); -bool AUD_is_active_out(SWVoiceOut *sw); +void audio_be_close_out(AudioBackend *be, SWVoiceOut *sw); +size_t audio_be_write(AudioBackend *be, SWVoiceOut *sw, void *pcm_buf, size_t size); +int audio_be_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw); +void audio_be_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on); +bool audio_be_is_active_out(AudioBackend *be, SWVoiceOut *sw); -void AUD_init_time_stamp_out (SWVoiceOut *sw, QEMUAudioTimeStamp *ts); -uint64_t AUD_get_elapsed_usec_out (SWVoiceOut *sw, QEMUAudioTimeStamp *ts); - -#define AUDIO_MAX_CHANNELS 16 -typedef struct Volume { - bool mute; - int channels; - uint8_t vol[AUDIO_MAX_CHANNELS]; -} Volume; -void AUD_set_volume_out(SWVoiceOut *sw, Volume *vol); -void AUD_set_volume_in(SWVoiceIn *sw, Volume *vol); +void audio_be_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol); +void audio_be_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol); static inline void -AUD_set_volume_out_lr(SWVoiceOut *sw, bool mut, uint8_t lvol, uint8_t rvol) { - AUD_set_volume_out(sw, &(Volume) { +audio_be_set_volume_out_lr(AudioBackend *be, SWVoiceOut *sw, + bool mut, uint8_t lvol, uint8_t rvol) { + audio_be_set_volume_out(be, sw, &(Volume) { .mute = mut, .channels = 2, .vol = { lvol, rvol } }); } static inline void -AUD_set_volume_in_lr(SWVoiceIn *sw, bool mut, uint8_t lvol, uint8_t rvol) { - AUD_set_volume_in(sw, &(Volume) { +audio_be_set_volume_in_lr(AudioBackend *be, SWVoiceIn *sw, + bool mut, uint8_t lvol, uint8_t rvol) { + audio_be_set_volume_in(be, sw, &(Volume) { .mute = mut, .channels = 2, .vol = { lvol, rvol } }); } -SWVoiceIn *AUD_open_in( +SWVoiceIn *audio_be_open_in( AudioBackend *be, SWVoiceIn *sw, const char *name, void *callback_opaque, audio_callback_fn callback_fn, - struct audsettings *settings + const struct audsettings *settings ); -void AUD_close_in(AudioBackend *be, SWVoiceIn *sw); -size_t AUD_read (SWVoiceIn *sw, void *pcm_buf, size_t size); -void AUD_set_active_in(SWVoiceIn *sw, bool on); -bool AUD_is_active_in(SWVoiceIn *sw); - -void AUD_init_time_stamp_in (SWVoiceIn *sw, QEMUAudioTimeStamp *ts); -uint64_t AUD_get_elapsed_usec_in (SWVoiceIn *sw, QEMUAudioTimeStamp *ts); +void audio_be_close_in(AudioBackend *be, SWVoiceIn *sw); +size_t audio_be_read(AudioBackend *be, SWVoiceIn *sw, void *pcm_buf, size_t size); +void audio_be_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on); +bool audio_be_is_active_in(AudioBackend *be, SWVoiceIn *sw); void audio_cleanup(void); typedef struct st_sample st_sample; -void audio_sample_to_uint64(const st_sample *sample, int pos, - uint64_t *left, uint64_t *right); -void audio_sample_from_uint64(st_sample *sample, int pos, - uint64_t left, uint64_t right); - void audio_add_audiodev(Audiodev *audio); void audio_add_default_audiodev(Audiodev *dev, Error **errp); void audio_parse_option(const char *opt); @@ -135,12 +176,64 @@ AudioBackend *audio_be_by_name(const char *name, Error **errp); AudioBackend *audio_get_default_audio_be(Error **errp); const char *audio_be_get_id(AudioBackend *be); #ifdef CONFIG_GIO +bool audio_be_can_set_dbus_server(AudioBackend *be); bool audio_be_set_dbus_server(AudioBackend *be, GDBusObjectManagerServer *server, bool p2p, Error **errp); #endif +const char *audio_application_name(void); + +static inline int audio_format_bits(AudioFormat fmt) +{ + switch (fmt) { + case AUDIO_FORMAT_S8: + case AUDIO_FORMAT_U8: + return 8; + + case AUDIO_FORMAT_S16: + case AUDIO_FORMAT_U16: + return 16; + + case AUDIO_FORMAT_F32: + case AUDIO_FORMAT_S32: + case AUDIO_FORMAT_U32: + return 32; + + case AUDIO_FORMAT__MAX: + break; + } + + g_assert_not_reached(); +} + +static inline bool audio_format_is_float(AudioFormat fmt) +{ + return fmt == AUDIO_FORMAT_F32; +} + +static inline bool audio_format_is_signed(AudioFormat fmt) +{ + switch (fmt) { + case AUDIO_FORMAT_S8: + case AUDIO_FORMAT_S16: + case AUDIO_FORMAT_S32: + case AUDIO_FORMAT_F32: + return true; + + case AUDIO_FORMAT_U8: + case AUDIO_FORMAT_U16: + case AUDIO_FORMAT_U32: + return false; + + case AUDIO_FORMAT__MAX: + break; + } + + g_assert_not_reached(); +} + #define DEFINE_AUDIO_PROPERTIES(_s, _f) \ DEFINE_PROP_AUDIODEV("audiodev", _s, _f) diff --git a/include/qemu/compiler.h b/include/qemu/compiler.h index 1c2b673c0583..4c49f52eb074 100644 --- a/include/qemu/compiler.h +++ b/include/qemu/compiler.h @@ -194,6 +194,14 @@ # define QEMU_USED #endif +/* + * A priority for __attribute__((constructor(...))) that + * will run earlier than the default constructors. Must + * only be used for functions that have no dependency + * on global initialization of other QEMU subsystems. + */ +#define QEMU_CONSTRUCTOR_EARLY 101 + /* * Disable -ftrivial-auto-var-init on a local variable. * diff --git a/include/qemu/crc32.h b/include/qemu/crc32.h new file mode 100644 index 000000000000..a320151acc04 --- /dev/null +++ b/include/qemu/crc32.h @@ -0,0 +1,18 @@ +/* + * CRC32 Checksum + * + * Copyright (c) 2026 QEMU contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + */ + +#ifndef QEMU_CRC32_H +#define QEMU_CRC32_H + +extern const uint32_t crc32_table[256]; + +#endif diff --git a/include/qemu/crc32c.h b/include/qemu/crc32c.h index 88b4d2b3b330..3d5ba189ef41 100644 --- a/include/qemu/crc32c.h +++ b/include/qemu/crc32c.h @@ -28,6 +28,7 @@ #ifndef QEMU_CRC32C_H #define QEMU_CRC32C_H +extern const uint32_t crc32c_table[256]; uint32_t crc32c(uint32_t crc, const uint8_t *data, unsigned int length); uint32_t iov_crc32c(uint32_t crc, const struct iovec *iov, size_t iov_cnt); diff --git a/include/qemu/fifo8.h b/include/qemu/fifo8.h index 4f768d4ee382..6b476b404eee 100644 --- a/include/qemu/fifo8.h +++ b/include/qemu/fifo8.h @@ -71,7 +71,7 @@ uint8_t fifo8_pop(Fifo8 *fifo); * * Returns: The peeked data byte. */ -uint8_t fifo8_peek(Fifo8 *fifo); +uint8_t fifo8_peek(const Fifo8 *fifo); /** * fifo8_pop_buf: @@ -181,7 +181,7 @@ void fifo8_reset(Fifo8 *fifo); * * Returns: True if the fifo is empty, false otherwise. */ -bool fifo8_is_empty(Fifo8 *fifo); +bool fifo8_is_empty(const Fifo8 *fifo); /** * fifo8_is_full: @@ -191,7 +191,7 @@ bool fifo8_is_empty(Fifo8 *fifo); * * Returns: True if the fifo is full, false otherwise. */ -bool fifo8_is_full(Fifo8 *fifo); +bool fifo8_is_full(const Fifo8 *fifo); /** * fifo8_num_free: @@ -201,7 +201,7 @@ bool fifo8_is_full(Fifo8 *fifo); * * Returns: Number of free bytes. */ -uint32_t fifo8_num_free(Fifo8 *fifo); +uint32_t fifo8_num_free(const Fifo8 *fifo); /** * fifo8_num_used: @@ -211,7 +211,7 @@ uint32_t fifo8_num_free(Fifo8 *fifo); * * Returns: Number of used bytes. */ -uint32_t fifo8_num_used(Fifo8 *fifo); +uint32_t fifo8_num_used(const Fifo8 *fifo); extern const VMStateDescription vmstate_fifo8; diff --git a/include/qemu/log-for-trace.h b/include/qemu/log-for-trace.h index f3a8791f1d4e..6861a1a4b7f1 100644 --- a/include/qemu/log-for-trace.h +++ b/include/qemu/log-for-trace.h @@ -29,7 +29,22 @@ static inline bool qemu_loglevel_mask(int mask) return (qemu_loglevel & mask) != 0; } -/* main logging function */ +/** + * qemu_log: report a log message + * @fmt: the format string for the message + * @...: the format string arguments + * + * This will emit a log message to the current output stream. + * + * The @fmt string should normally represent a complete line + * of text, and thus end with a newline character. + * + * While it is possible to incrementally output fragments of + * a complete line using qemu_log, this is inefficient and + * races with other threads. For outputting fragments it is + * strongly preferred to use the qemu_log_trylock() method + * combined with fprintf(). + */ void G_GNUC_PRINTF(1, 2) qemu_log(const char *fmt, ...); #endif diff --git a/include/qemu/log.h b/include/qemu/log.h index 7effba4da4ca..92956e5d0f5d 100644 --- a/include/qemu/log.h +++ b/include/qemu/log.h @@ -41,7 +41,46 @@ bool qemu_log_separate(void); /* Lock/unlock output. */ +/** + * Acquires a lock on the current log output stream. + * The returned FILE object should be used with the + * fprintf() function to output the log message, and + * then qemu_log_unlock() called to release the lock. + * + * The primary use case is to be able to incrementally + * output fragments of a complete log message in an + * efficient and race free manner. + * + * The simpler qemu_log() method should normally only + * be used to output complete log messages, and not + * within scope of a qemu_log_trylock() call. + * + * A typical usage pattern would be + * + * FILE *f = qemu_log_trylock() + * + * fprintf(f, "Something "); + * fprintf(f, "Something "); + * fprintf(f, "Something "); + * fprintf(f, "The end\n"); + * + * qemu_log_unlock(f); + * + * Returns: the current FILE if available, NULL on error + */ FILE *qemu_log_trylock(void) G_GNUC_WARN_UNUSED_RESULT; + +/** + * As qemu_log_trylock(), but will also print the message + * context, if any is configured and this caused the + * acquisition of the FILE lock + */ +FILE *qemu_log_trylock_with_context(void) G_GNUC_WARN_UNUSED_RESULT; + +/** + * Releases the lock on the log output, previously + * acquired by qemu_log_trylock(). + */ void qemu_log_unlock(FILE *fd); /* Logging functions: */ diff --git a/include/qemu/module.h b/include/qemu/module.h index c37ce74b16ff..9885ac9afb3d 100644 --- a/include/qemu/module.h +++ b/include/qemu/module.h @@ -63,7 +63,6 @@ typedef enum { #define migration_init(function) module_init(function, MODULE_INIT_MIGRATION) #define block_module_load(lib, errp) module_load("block-", lib, errp) #define ui_module_load(lib, errp) module_load("ui-", lib, errp) -#define audio_module_load(lib, errp) module_load("audio-", lib, errp) void register_module_init(void (*fn)(void), module_init_type type); void register_dso_module_init(void (*fn)(void), module_init_type type); @@ -78,7 +77,7 @@ void module_call_init(module_init_type type); * - get_relocated_path(CONFIG_QEMU_MODDIR); * - /var/run/qemu/${version_dir} * - * prefix: a subsystem prefix, or the empty string ("audio-", ..., "") + * prefix: a subsystem prefix, or the empty string ("ui-", ..., "") * name: name of the module * errp: error to set in case the module is found, but load failed. * diff --git a/include/qemu/target-info-impl.h b/include/qemu/target-info-impl.h index e446585bf538..c917d546eab0 100644 --- a/include/qemu/target-info-impl.h +++ b/include/qemu/target-info-impl.h @@ -25,6 +25,13 @@ typedef struct TargetInfo { const char *machine_typename; /* related to TARGET_BIG_ENDIAN definition */ EndianMode endianness; + /* + * runtime equivalent of + * TARGET_PAGE_BITS_VARY ? TARGET_PAGE_BITS_LEGACY : TARGET_PAGE_BITS + */ + unsigned page_bits_init; + /* runtime equivalent of TARGET_PAGE_BITS_VARY definition */ + bool page_bits_vary; } TargetInfo; /** diff --git a/include/qemu/target-info.h b/include/qemu/target-info.h index e32873343040..23c997de5417 100644 --- a/include/qemu/target-info.h +++ b/include/qemu/target-info.h @@ -92,4 +92,11 @@ bool target_ppc(void); */ bool target_ppc64(void); +/** + * target_s390x: + * + * Returns whether the target architecture is S390x. + */ +bool target_s390x(void); + #endif diff --git a/include/qemu/thread.h b/include/qemu/thread.h index f0302ed01fdb..98cc5c41ac58 100644 --- a/include/qemu/thread.h +++ b/include/qemu/thread.h @@ -215,7 +215,8 @@ void *qemu_thread_join(QemuThread *thread); void qemu_thread_get_self(QemuThread *thread); bool qemu_thread_is_self(QemuThread *thread); G_NORETURN void qemu_thread_exit(void *retval); -void qemu_thread_naming(bool enable); +void qemu_thread_set_name(const char *name); +const char *qemu_thread_get_name(void); struct Notifier; /** diff --git a/include/qemu/timer.h b/include/qemu/timer.h index 7c18da165258..b931181f53d1 100644 --- a/include/qemu/timer.h +++ b/include/qemu/timer.h @@ -6,6 +6,7 @@ #include "qemu/host-utils.h" #define NANOSECONDS_PER_SECOND 1000000000LL +#define MICROSECONDS_PER_SECOND 1000000LL /* timers */ diff --git a/include/qemu/typedefs.h b/include/qemu/typedefs.h index 416a8c9acead..cbe6f7f4c7c1 100644 --- a/include/qemu/typedefs.h +++ b/include/qemu/typedefs.h @@ -102,6 +102,7 @@ typedef struct QEMUSGList QEMUSGList; typedef struct QemuSpin QemuSpin; typedef struct QEMUTimer QEMUTimer; typedef struct QEMUTimerListGroup QEMUTimerListGroup; +typedef struct QIgvm QIgvm; typedef struct QList QList; typedef struct QNull QNull; typedef struct QNum QNum; diff --git a/include/standard-headers/linux/nitro_enclaves.h b/include/standard-headers/linux/nitro_enclaves.h new file mode 100644 index 000000000000..5545267dd950 --- /dev/null +++ b/include/standard-headers/linux/nitro_enclaves.h @@ -0,0 +1,359 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Copyright 2020-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + */ + +#ifndef _LINUX_NITRO_ENCLAVES_H_ +#define _LINUX_NITRO_ENCLAVES_H_ + +#include "standard-headers/linux/types.h" + +/** + * DOC: Nitro Enclaves (NE) Kernel Driver Interface + */ + +/** + * NE_CREATE_VM - The command is used to create a slot that is associated with + * an enclave VM. + * The generated unique slot id is an output parameter. + * The ioctl can be invoked on the /dev/nitro_enclaves fd, before + * setting any resources, such as memory and vCPUs, for an + * enclave. Memory and vCPUs are set for the slot mapped to an enclave. + * A NE CPU pool has to be set before calling this function. The + * pool can be set after the NE driver load, using + * /sys/module/nitro_enclaves/parameters/ne_cpus. + * Its format is the detailed in the cpu-lists section: + * https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html + * CPU 0 and its siblings have to remain available for the + * primary / parent VM, so they cannot be set for enclaves. Full + * CPU core(s), from the same NUMA node, need(s) to be included + * in the CPU pool. + * + * Context: Process context. + * Return: + * * Enclave file descriptor - Enclave file descriptor used with + * ioctl calls to set vCPUs and memory + * regions, then start the enclave. + * * -1 - There was a failure in the ioctl logic. + * On failure, errno is set to: + * * EFAULT - copy_to_user() failure. + * * ENOMEM - Memory allocation failure for internal + * bookkeeping variables. + * * NE_ERR_NO_CPUS_AVAIL_IN_POOL - No NE CPU pool set / no CPUs available + * in the pool. + * * Error codes from get_unused_fd_flags() and anon_inode_getfile(). + * * Error codes from the NE PCI device request. + */ +#define NE_CREATE_VM _IOR(0xAE, 0x20, uint64_t) + +/** + * NE_ADD_VCPU - The command is used to set a vCPU for an enclave. The vCPU can + * be auto-chosen from the NE CPU pool or it can be set by the + * caller, with the note that it needs to be available in the NE + * CPU pool. Full CPU core(s), from the same NUMA node, need(s) to + * be associated with an enclave. + * The vCPU id is an input / output parameter. If its value is 0, + * then a CPU is chosen from the enclave CPU pool and returned via + * this parameter. + * The ioctl can be invoked on the enclave fd, before an enclave + * is started. + * + * Context: Process context. + * Return: + * * 0 - Logic successfully completed. + * * -1 - There was a failure in the ioctl logic. + * On failure, errno is set to: + * * EFAULT - copy_from_user() / copy_to_user() failure. + * * ENOMEM - Memory allocation failure for internal + * bookkeeping variables. + * * EIO - Current task mm is not the same as the one + * that created the enclave. + * * NE_ERR_NO_CPUS_AVAIL_IN_POOL - No CPUs available in the NE CPU pool. + * * NE_ERR_VCPU_ALREADY_USED - The provided vCPU is already used. + * * NE_ERR_VCPU_NOT_IN_CPU_POOL - The provided vCPU is not available in the + * NE CPU pool. + * * NE_ERR_VCPU_INVALID_CPU_CORE - The core id of the provided vCPU is invalid + * or out of range. + * * NE_ERR_NOT_IN_INIT_STATE - The enclave is not in init state + * (init = before being started). + * * NE_ERR_INVALID_VCPU - The provided vCPU is not in the available + * CPUs range. + * * Error codes from the NE PCI device request. + */ +#define NE_ADD_VCPU _IOWR(0xAE, 0x21, uint32_t) + +/** + * NE_GET_IMAGE_LOAD_INFO - The command is used to get information needed for + * in-memory enclave image loading e.g. offset in + * enclave memory to start placing the enclave image. + * The image load info is an input / output parameter. + * It includes info provided by the caller - flags - + * and returns the offset in enclave memory where to + * start placing the enclave image. + * The ioctl can be invoked on the enclave fd, before + * an enclave is started. + * + * Context: Process context. + * Return: + * * 0 - Logic successfully completed. + * * -1 - There was a failure in the ioctl logic. + * On failure, errno is set to: + * * EFAULT - copy_from_user() / copy_to_user() failure. + * * NE_ERR_NOT_IN_INIT_STATE - The enclave is not in init state (init = + * before being started). + * * NE_ERR_INVALID_FLAG_VALUE - The value of the provided flag is invalid. + */ +#define NE_GET_IMAGE_LOAD_INFO _IOWR(0xAE, 0x22, struct ne_image_load_info) + +/** + * NE_SET_USER_MEMORY_REGION - The command is used to set a memory region for an + * enclave, given the allocated memory from the + * userspace. Enclave memory needs to be from the + * same NUMA node as the enclave CPUs. + * The user memory region is an input parameter. It + * includes info provided by the caller - flags, + * memory size and userspace address. + * The ioctl can be invoked on the enclave fd, + * before an enclave is started. + * + * Context: Process context. + * Return: + * * 0 - Logic successfully completed. + * * -1 - There was a failure in the ioctl logic. + * On failure, errno is set to: + * * EFAULT - copy_from_user() failure. + * * EINVAL - Invalid physical memory region(s) e.g. + * unaligned address. + * * EIO - Current task mm is not the same as + * the one that created the enclave. + * * ENOMEM - Memory allocation failure for internal + * bookkeeping variables. + * * NE_ERR_NOT_IN_INIT_STATE - The enclave is not in init state + * (init = before being started). + * * NE_ERR_INVALID_MEM_REGION_SIZE - The memory size of the region is not + * multiple of 2 MiB. + * * NE_ERR_INVALID_MEM_REGION_ADDR - Invalid user space address given. + * * NE_ERR_UNALIGNED_MEM_REGION_ADDR - Unaligned user space address given. + * * NE_ERR_MEM_REGION_ALREADY_USED - The memory region is already used. + * * NE_ERR_MEM_NOT_HUGE_PAGE - The memory region is not backed by + * huge pages. + * * NE_ERR_MEM_DIFFERENT_NUMA_NODE - The memory region is not from the same + * NUMA node as the CPUs. + * * NE_ERR_MEM_MAX_REGIONS - The number of memory regions set for + * the enclave reached maximum. + * * NE_ERR_INVALID_PAGE_SIZE - The memory region is not backed by + * pages multiple of 2 MiB. + * * NE_ERR_INVALID_FLAG_VALUE - The value of the provided flag is invalid. + * * Error codes from get_user_pages(). + * * Error codes from the NE PCI device request. + */ +#define NE_SET_USER_MEMORY_REGION _IOW(0xAE, 0x23, struct ne_user_memory_region) + +/** + * NE_START_ENCLAVE - The command is used to trigger enclave start after the + * enclave resources, such as memory and CPU, have been set. + * The enclave start info is an input / output parameter. It + * includes info provided by the caller - enclave cid and + * flags - and returns the cid (if input cid is 0). + * The ioctl can be invoked on the enclave fd, after an + * enclave slot is created and resources, such as memory and + * vCPUs are set for an enclave. + * + * Context: Process context. + * Return: + * * 0 - Logic successfully completed. + * * -1 - There was a failure in the ioctl logic. + * On failure, errno is set to: + * * EFAULT - copy_from_user() / copy_to_user() failure. + * * NE_ERR_NOT_IN_INIT_STATE - The enclave is not in init state + * (init = before being started). + * * NE_ERR_NO_MEM_REGIONS_ADDED - No memory regions are set. + * * NE_ERR_NO_VCPUS_ADDED - No vCPUs are set. + * * NE_ERR_FULL_CORES_NOT_USED - Full core(s) not set for the enclave. + * * NE_ERR_ENCLAVE_MEM_MIN_SIZE - Enclave memory is less than minimum + * memory size (64 MiB). + * * NE_ERR_INVALID_FLAG_VALUE - The value of the provided flag is invalid. + * * NE_ERR_INVALID_ENCLAVE_CID - The provided enclave CID is invalid. + * * Error codes from the NE PCI device request. + */ +#define NE_START_ENCLAVE _IOWR(0xAE, 0x24, struct ne_enclave_start_info) + +/** + * DOC: NE specific error codes + */ + +/** + * NE_ERR_VCPU_ALREADY_USED - The provided vCPU is already used. + */ +#define NE_ERR_VCPU_ALREADY_USED (256) +/** + * NE_ERR_VCPU_NOT_IN_CPU_POOL - The provided vCPU is not available in the + * NE CPU pool. + */ +#define NE_ERR_VCPU_NOT_IN_CPU_POOL (257) +/** + * NE_ERR_VCPU_INVALID_CPU_CORE - The core id of the provided vCPU is invalid + * or out of range of the NE CPU pool. + */ +#define NE_ERR_VCPU_INVALID_CPU_CORE (258) +/** + * NE_ERR_INVALID_MEM_REGION_SIZE - The user space memory region size is not + * multiple of 2 MiB. + */ +#define NE_ERR_INVALID_MEM_REGION_SIZE (259) +/** + * NE_ERR_INVALID_MEM_REGION_ADDR - The user space memory region address range + * is invalid. + */ +#define NE_ERR_INVALID_MEM_REGION_ADDR (260) +/** + * NE_ERR_UNALIGNED_MEM_REGION_ADDR - The user space memory region address is + * not aligned. + */ +#define NE_ERR_UNALIGNED_MEM_REGION_ADDR (261) +/** + * NE_ERR_MEM_REGION_ALREADY_USED - The user space memory region is already used. + */ +#define NE_ERR_MEM_REGION_ALREADY_USED (262) +/** + * NE_ERR_MEM_NOT_HUGE_PAGE - The user space memory region is not backed by + * contiguous physical huge page(s). + */ +#define NE_ERR_MEM_NOT_HUGE_PAGE (263) +/** + * NE_ERR_MEM_DIFFERENT_NUMA_NODE - The user space memory region is backed by + * pages from different NUMA nodes than the CPUs. + */ +#define NE_ERR_MEM_DIFFERENT_NUMA_NODE (264) +/** + * NE_ERR_MEM_MAX_REGIONS - The supported max memory regions per enclaves has + * been reached. + */ +#define NE_ERR_MEM_MAX_REGIONS (265) +/** + * NE_ERR_NO_MEM_REGIONS_ADDED - The command to start an enclave is triggered + * and no memory regions are added. + */ +#define NE_ERR_NO_MEM_REGIONS_ADDED (266) +/** + * NE_ERR_NO_VCPUS_ADDED - The command to start an enclave is triggered and no + * vCPUs are added. + */ +#define NE_ERR_NO_VCPUS_ADDED (267) +/** + * NE_ERR_ENCLAVE_MEM_MIN_SIZE - The enclave memory size is lower than the + * minimum supported. + */ +#define NE_ERR_ENCLAVE_MEM_MIN_SIZE (268) +/** + * NE_ERR_FULL_CORES_NOT_USED - The command to start an enclave is triggered and + * full CPU cores are not set. + */ +#define NE_ERR_FULL_CORES_NOT_USED (269) +/** + * NE_ERR_NOT_IN_INIT_STATE - The enclave is not in init state when setting + * resources or triggering start. + */ +#define NE_ERR_NOT_IN_INIT_STATE (270) +/** + * NE_ERR_INVALID_VCPU - The provided vCPU is out of range of the available CPUs. + */ +#define NE_ERR_INVALID_VCPU (271) +/** + * NE_ERR_NO_CPUS_AVAIL_IN_POOL - The command to create an enclave is triggered + * and no CPUs are available in the pool. + */ +#define NE_ERR_NO_CPUS_AVAIL_IN_POOL (272) +/** + * NE_ERR_INVALID_PAGE_SIZE - The user space memory region is not backed by pages + * multiple of 2 MiB. + */ +#define NE_ERR_INVALID_PAGE_SIZE (273) +/** + * NE_ERR_INVALID_FLAG_VALUE - The provided flag value is invalid. + */ +#define NE_ERR_INVALID_FLAG_VALUE (274) +/** + * NE_ERR_INVALID_ENCLAVE_CID - The provided enclave CID is invalid, either + * being a well-known value or the CID of the + * parent / primary VM. + */ +#define NE_ERR_INVALID_ENCLAVE_CID (275) + +/** + * DOC: Image load info flags + */ + +/** + * NE_EIF_IMAGE - Enclave Image Format (EIF) + */ +#define NE_EIF_IMAGE (0x01) + +#define NE_IMAGE_LOAD_MAX_FLAG_VAL (0x02) + +/** + * struct ne_image_load_info - Info necessary for in-memory enclave image + * loading (in / out). + * @flags: Flags to determine the enclave image type + * (e.g. Enclave Image Format - EIF) (in). + * @memory_offset: Offset in enclave memory where to start placing the + * enclave image (out). + */ +struct ne_image_load_info { + uint64_t flags; + uint64_t memory_offset; +}; + +/** + * DOC: User memory region flags + */ + +/** + * NE_DEFAULT_MEMORY_REGION - Memory region for enclave general usage. + */ +#define NE_DEFAULT_MEMORY_REGION (0x00) + +#define NE_MEMORY_REGION_MAX_FLAG_VAL (0x01) + +/** + * struct ne_user_memory_region - Memory region to be set for an enclave (in). + * @flags: Flags to determine the usage for the memory region (in). + * @memory_size: The size, in bytes, of the memory region to be set for + * an enclave (in). + * @userspace_addr: The start address of the userspace allocated memory of + * the memory region to set for an enclave (in). + */ +struct ne_user_memory_region { + uint64_t flags; + uint64_t memory_size; + uint64_t userspace_addr; +}; + +/** + * DOC: Enclave start info flags + */ + +/** + * NE_ENCLAVE_PRODUCTION_MODE - Start enclave in production mode. + */ +#define NE_ENCLAVE_PRODUCTION_MODE (0x00) +/** + * NE_ENCLAVE_DEBUG_MODE - Start enclave in debug mode. + */ +#define NE_ENCLAVE_DEBUG_MODE (0x01) + +#define NE_ENCLAVE_START_MAX_FLAG_VAL (0x02) + +/** + * struct ne_enclave_start_info - Setup info necessary for enclave start (in / out). + * @flags: Flags for the enclave to start with (e.g. debug mode) (in). + * @enclave_cid: Context ID (CID) for the enclave vsock device. If 0 as + * input, the CID is autogenerated by the hypervisor and + * returned back as output by the driver (in / out). + */ +struct ne_enclave_start_info { + uint64_t flags; + uint64_t enclave_cid; +}; + +#endif /* _LINUX_NITRO_ENCLAVES_H_ */ diff --git a/include/system/arch_init.h b/include/system/arch_init.h deleted file mode 100644 index f2f909d54064..000000000000 --- a/include/system/arch_init.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef QEMU_ARCH_INIT_H -#define QEMU_ARCH_INIT_H - - -enum { - QEMU_ARCH_ALL = -1, - QEMU_ARCH_ALPHA = (1 << 0), - QEMU_ARCH_ARM = (1 << 1), - QEMU_ARCH_I386 = (1 << 3), - QEMU_ARCH_M68K = (1 << 4), - QEMU_ARCH_MICROBLAZE = (1 << 6), - QEMU_ARCH_MIPS = (1 << 7), - QEMU_ARCH_PPC = (1 << 8), - QEMU_ARCH_S390X = (1 << 9), - QEMU_ARCH_SH4 = (1 << 10), - QEMU_ARCH_SPARC = (1 << 11), - QEMU_ARCH_XTENSA = (1 << 12), - QEMU_ARCH_OR1K = (1 << 13), - QEMU_ARCH_TRICORE = (1 << 16), - QEMU_ARCH_HPPA = (1 << 18), - QEMU_ARCH_RISCV = (1 << 19), - QEMU_ARCH_RX = (1 << 20), - QEMU_ARCH_AVR = (1 << 21), - QEMU_ARCH_HEXAGON = (1 << 22), - QEMU_ARCH_LOONGARCH = (1 << 23), -}; - -bool qemu_arch_available(unsigned qemu_arch_mask); - -#endif diff --git a/include/system/confidential-guest-support.h b/include/system/confidential-guest-support.h index 0cc8b26e644e..5dca71730880 100644 --- a/include/system/confidential-guest-support.h +++ b/include/system/confidential-guest-support.h @@ -152,6 +152,11 @@ typedef struct ConfidentialGuestSupportClass { */ int (*get_mem_map_entry)(int index, ConfidentialGuestMemoryMapEntry *entry, Error **errp); + + /* + * is it possible to rebuild the guest state? + */ + bool can_rebuild_guest_state; } ConfidentialGuestSupportClass; static inline int confidential_guest_kvm_init(ConfidentialGuestSupport *cgs, @@ -167,6 +172,21 @@ static inline int confidential_guest_kvm_init(ConfidentialGuestSupport *cgs, return 0; } +static inline bool +confidential_guest_can_rebuild_state(ConfidentialGuestSupport *cgs) +{ + ConfidentialGuestSupportClass *klass; + + if (!cgs) { + /* non-confidential guests */ + return true; + } + + klass = CONFIDENTIAL_GUEST_SUPPORT_GET_CLASS(cgs); + return klass->can_rebuild_guest_state; + +} + static inline int confidential_guest_kvm_reset(ConfidentialGuestSupport *cgs, Error **errp) { diff --git a/include/system/hw_accel.h b/include/system/hw_accel.h index 628a50e066ec..f0c10b6d805f 100644 --- a/include/system/hw_accel.h +++ b/include/system/hw_accel.h @@ -17,6 +17,7 @@ #include "system/mshv.h" #include "system/whpx.h" #include "system/nvmm.h" +#include "system/nitro-accel.h" /** * cpu_synchronize_state: diff --git a/include/system/igvm-internal.h b/include/system/igvm-internal.h index 38004bd908e7..76ae1bc3be92 100644 --- a/include/system/igvm-internal.h +++ b/include/system/igvm-internal.h @@ -42,7 +42,7 @@ typedef struct QIgvmParameterData { * QIgvm contains the information required during processing of a single IGVM * file. */ -typedef struct QIgvm { +struct QIgvm { IgvmHandle file; MachineState *machine_state; ConfidentialGuestSupportClass *cgsc; @@ -67,16 +67,11 @@ typedef struct QIgvm { unsigned region_start_index; unsigned region_last_index; unsigned region_page_count; -} QIgvm; +}; IgvmHandle qigvm_file_init(char *filename, Error **errp); QIgvmParameterData* qigvm_find_param_entry(QIgvm *igvm, uint32_t parameter_area_index); -/* - * IGVM parameter handlers - */ -int qigvm_directive_madt(QIgvm *ctx, const uint8_t *header_data, Error **errp); - #endif diff --git a/include/system/igvm.h b/include/system/igvm.h index 5573a6111ae7..f9231f03ec80 100644 --- a/include/system/igvm.h +++ b/include/system/igvm.h @@ -27,4 +27,9 @@ int qigvm_x86_get_mem_map_entry(int index, int qigvm_x86_set_vp_context(void *data, int index, Error **errp); +/* + * IGVM parameter handlers + */ +int qigvm_directive_madt(QIgvm *ctx, const uint8_t *header_data, Error **errp); + #endif diff --git a/include/system/iommufd.h b/include/system/iommufd.h index 80d72469a9f8..7062944fe61d 100644 --- a/include/system/iommufd.h +++ b/include/system/iommufd.h @@ -56,6 +56,15 @@ typedef struct IOMMUFDVdev { uint32_t virt_id; /* virtual device ID */ } IOMMUFDVdev; +/* Virtual event queue interface for a vIOMMU */ +typedef struct IOMMUFDVeventq { + IOMMUFDViommu *viommu; + uint32_t veventq_id; + uint32_t veventq_fd; + uint32_t last_event_seq; /* Sequence number of last processed event */ + bool event_start; /* True after first valid event; cleared on overflow */ +} IOMMUFDVeventq; + bool iommufd_backend_connect(IOMMUFDBackend *be, Error **errp); void iommufd_backend_disconnect(IOMMUFDBackend *be); @@ -86,6 +95,11 @@ bool iommufd_backend_alloc_vdev(IOMMUFDBackend *be, uint32_t dev_id, uint32_t viommu_id, uint64_t virt_id, uint32_t *out_vdev_id, Error **errp); +bool iommufd_backend_alloc_veventq(IOMMUFDBackend *be, uint32_t viommu_id, + uint32_t type, uint32_t depth, + uint32_t *out_veventq_id, + uint32_t *out_veventq_fd, Error **errp); + bool iommufd_backend_set_dirty_tracking(IOMMUFDBackend *be, uint32_t hwpt_id, bool start, Error **errp); bool iommufd_backend_get_dirty_bitmap(IOMMUFDBackend *be, uint32_t hwpt_id, diff --git a/include/system/kvm.h b/include/system/kvm.h index 8f9eecf044c2..5fa33eddda38 100644 --- a/include/system/kvm.h +++ b/include/system/kvm.h @@ -181,6 +181,7 @@ DECLARE_INSTANCE_CHECKER(KVMState, KVM_STATE, extern KVMState *kvm_state; typedef struct Notifier Notifier; +typedef struct NotifierWithReturn NotifierWithReturn; typedef struct KVMRouteChange { KVMState *s; @@ -218,6 +219,10 @@ int kvm_vm_ioctl(KVMState *s, unsigned long type, ...); void kvm_flush_coalesced_mmio_buffer(void); +void kvm_irqchip_add_change_notifier(Notifier *n); +void kvm_irqchip_remove_change_notifier(Notifier *n); +void kvm_irqchip_change_notify(void); + #ifdef COMPILING_PER_TARGET #include "cpu.h" @@ -392,10 +397,6 @@ int kvm_irqchip_send_msi(KVMState *s, MSIMessage msg); void kvm_irqchip_add_irq_route(KVMState *s, int gsi, int irqchip, int pin); -void kvm_irqchip_add_change_notifier(Notifier *n); -void kvm_irqchip_remove_change_notifier(Notifier *n); -void kvm_irqchip_change_notify(void); - struct kvm_guest_debug; struct kvm_debug_exit_arch; @@ -456,6 +457,9 @@ int kvm_physical_memory_addr_from_host(KVMState *s, void *ram_addr, #endif /* COMPILING_PER_TARGET */ +bool kvm_arch_supports_vmfd_change(void); +int kvm_arch_on_vmfd_change(MachineState *ms, KVMState *s); + void kvm_cpu_synchronize_state(CPUState *cpu); void kvm_init_cpu_signals(CPUState *cpu); @@ -564,4 +568,43 @@ int kvm_set_memory_attributes_shared(hwaddr start, uint64_t size); int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private); +/* argument to vmfd change notifier */ +typedef struct VmfdChangeNotifier { + int vmfd; + bool pre; +} VmfdChangeNotifier; + +/** + * kvm_vmfd_add_change_notifier - register a notifier to get notified when + * a KVM vm file descriptor changes or about to be changed as a part of the + * confidential guest "reset" process. + * Various subsystems should use this mechanism to take actions such + * as creating new fds against this new vm file descriptor. + * @n: notifier with return value. + */ +void kvm_vmfd_add_change_notifier(NotifierWithReturn *n); +/** + * kvm_vmfd_remove_change_notifier - de-register a notifer previously + * registered with kvm_vmfd_add_change_notifier call. + * @n: notifier that was previously registered. + */ +void kvm_vmfd_remove_change_notifier(NotifierWithReturn *n); + +/** + * kvm_vcpufd_add_change_notifier - register a notifier to get notified when + * a KVM vcpu file descriptors changes as a part of the confidential guest + * "reset" process. Various subsystems should use this mechanism to take + * actions such as re-issuing vcpu ioctls as a part of setting up vcpu + * features. + * @n: notifier with return value. + */ +void kvm_vcpufd_add_change_notifier(NotifierWithReturn *n); + +/** + * kvm_vcpufd_remove_change_notifier - de-register a notifer previously + * registered with kvm_vcpufd_add_change_notifier call. + * @n: notifier that was previously registered. + */ +void kvm_vcpufd_remove_change_notifier(NotifierWithReturn *n); + #endif diff --git a/include/system/kvm_int.h b/include/system/kvm_int.h index baeb166d393e..0876aac938d3 100644 --- a/include/system/kvm_int.h +++ b/include/system/kvm_int.h @@ -167,6 +167,7 @@ struct KVMState uint16_t xen_gnttab_max_frames; uint16_t xen_evtchn_max_pirq; char *device; + OnOffAuto honor_guest_pat; }; void kvm_memory_listener_register(KVMState *s, KVMMemoryListener *kml, diff --git a/include/system/memory.h b/include/system/memory.h index 0562af313615..d7b18b632d53 100644 --- a/include/system/memory.h +++ b/include/system/memory.h @@ -574,32 +574,22 @@ struct RamDiscardListener { * new population (e.g., unmap). * * @rdl: the #RamDiscardListener getting notified - * @section: the #MemoryRegionSection to get populated. The section + * @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; - /* - * @double_discard_supported: - * - * The listener suppors getting @notify_discard notifications that span - * already discarded parts. - */ - bool double_discard_supported; - MemoryRegionSection *section; QLIST_ENTRY(RamDiscardListener) next; }; static inline void ram_discard_listener_init(RamDiscardListener *rdl, NotifyRamPopulate populate_fn, - NotifyRamDiscard discard_fn, - bool double_discard_supported) + NotifyRamDiscard discard_fn) { rdl->notify_populate = populate_fn; rdl->notify_discard = discard_fn; - rdl->double_discard_supported = double_discard_supported; } /** @@ -1374,29 +1364,6 @@ void memory_region_init_io(MemoryRegion *mr, const char *name, uint64_t size); -/** - * memory_region_init_ram_nomigrate: Initialize RAM memory region. Accesses - * into the region will modify memory - * directly. - * - * @mr: the #MemoryRegion to be initialized. - * @owner: the object that tracks the region's reference count - * @name: Region name, becomes part of RAMBlock name used in migration stream - * must be unique within any device - * @size: size of the region. - * @errp: pointer to Error*, to store an error if it happens. - * - * Note that this function does not do anything to cause the data in the - * RAM memory region to be migrated; that is the responsibility of the caller. - * - * Return: true on success, else false setting @errp with error. - */ -bool memory_region_init_ram_nomigrate(MemoryRegion *mr, - Object *owner, - const char *name, - uint64_t size, - Error **errp); - /** * memory_region_init_ram_flags_nomigrate: Initialize RAM memory region. * Accesses into the region will @@ -1588,32 +1555,6 @@ void memory_region_init_alias(MemoryRegion *mr, hwaddr offset, uint64_t size); -/** - * memory_region_init_rom_nomigrate: Initialize a ROM memory region. - * - * This has the same effect as calling memory_region_init_ram_nomigrate() - * and then marking the resulting region read-only with - * memory_region_set_readonly(). - * - * Note that this function does not do anything to cause the data in the - * RAM side of the memory region to be migrated; that is the responsibility - * of the caller. - * - * @mr: the #MemoryRegion to be initialized. - * @owner: the object that tracks the region's reference count - * @name: Region name, becomes part of RAMBlock name used in migration stream - * must be unique within any device - * @size: size of the region. - * @errp: pointer to Error*, to store an error if it happens. - * - * Return: true on success, else false setting @errp with error. - */ -bool memory_region_init_rom_nomigrate(MemoryRegion *mr, - Object *owner, - const char *name, - uint64_t size, - Error **errp); - /** * memory_region_init_iommu: Initialize a memory region of a custom type * that translates addresses @@ -1749,14 +1690,14 @@ bool memory_region_init_rom_device(MemoryRegion *mr, * * @mr: the memory region being queried. */ -Object *memory_region_owner(MemoryRegion *mr); +Object *memory_region_owner(const MemoryRegion *mr); /** * memory_region_size: get a memory region's size. * * @mr: the memory region being queried. */ -uint64_t memory_region_size(MemoryRegion *mr); +uint64_t memory_region_size(const MemoryRegion *mr); /** * memory_region_is_ram: check whether a memory region is random access @@ -1765,7 +1706,7 @@ uint64_t memory_region_size(MemoryRegion *mr); * * @mr: the memory region being queried */ -static inline bool memory_region_is_ram(MemoryRegion *mr) +static inline bool memory_region_is_ram(const MemoryRegion *mr) { return mr->ram; } @@ -1777,7 +1718,7 @@ static inline bool memory_region_is_ram(MemoryRegion *mr) * * @mr: the memory region being queried */ -bool memory_region_is_ram_device(MemoryRegion *mr); +bool memory_region_is_ram_device(const MemoryRegion *mr); /** * memory_region_is_romd: check whether a memory region is in ROMD mode @@ -1787,7 +1728,7 @@ bool memory_region_is_ram_device(MemoryRegion *mr); * * @mr: the memory region being queried */ -static inline bool memory_region_is_romd(MemoryRegion *mr) +static inline bool memory_region_is_romd(const MemoryRegion *mr) { return mr->rom_device && mr->romd_mode; } @@ -1800,7 +1741,7 @@ static inline bool memory_region_is_romd(MemoryRegion *mr) * * @mr: the memory region being queried */ -bool memory_region_is_protected(MemoryRegion *mr); +bool memory_region_is_protected(const MemoryRegion *mr); /** * memory_region_has_guest_memfd: check whether a memory region has guest_memfd @@ -1810,7 +1751,7 @@ bool memory_region_is_protected(MemoryRegion *mr); * * @mr: the memory region being queried */ -bool memory_region_has_guest_memfd(MemoryRegion *mr); +bool memory_region_has_guest_memfd(const MemoryRegion *mr); /** * memory_region_get_iommu: check whether a memory region is an iommu @@ -1820,7 +1761,7 @@ bool memory_region_has_guest_memfd(MemoryRegion *mr); * * @mr: the memory region being queried */ -static inline IOMMUMemoryRegion *memory_region_get_iommu(MemoryRegion *mr) +static inline IOMMUMemoryRegion *memory_region_get_iommu(const MemoryRegion *mr) { if (mr->alias) { return memory_region_get_iommu(mr->alias); @@ -1991,7 +1932,7 @@ const char *memory_region_name(const MemoryRegion *mr); * @mr: the memory region being queried * @client: the client being queried */ -bool memory_region_is_logging(MemoryRegion *mr, uint8_t client); +bool memory_region_is_logging(const MemoryRegion *mr, uint8_t client); /** * memory_region_get_dirty_log_mask: return the clients for which a @@ -2002,7 +1943,7 @@ bool memory_region_is_logging(MemoryRegion *mr, uint8_t client); * * @mr: the memory region being queried */ -uint8_t memory_region_get_dirty_log_mask(MemoryRegion *mr); +uint8_t memory_region_get_dirty_log_mask(const MemoryRegion *mr); /** * memory_region_is_rom: check whether a memory region is ROM @@ -2011,7 +1952,7 @@ uint8_t memory_region_get_dirty_log_mask(MemoryRegion *mr); * * @mr: the memory region being queried */ -static inline bool memory_region_is_rom(MemoryRegion *mr) +static inline bool memory_region_is_rom(const MemoryRegion *mr) { return mr->ram && mr->readonly; } @@ -2023,7 +1964,7 @@ static inline bool memory_region_is_rom(MemoryRegion *mr) * * @mr: the memory region being queried */ -static inline bool memory_region_is_nonvolatile(MemoryRegion *mr) +static inline bool memory_region_is_nonvolatile(const MemoryRegion *mr) { return mr->nonvolatile; } @@ -2036,7 +1977,7 @@ static inline bool memory_region_is_nonvolatile(MemoryRegion *mr) * * @mr: the RAM or alias memory region being queried. */ -int memory_region_get_fd(MemoryRegion *mr); +int memory_region_get_fd(const MemoryRegion *mr); /** * memory_region_from_host: Convert a pointer into a RAM memory region @@ -2071,7 +2012,7 @@ MemoryRegion *memory_region_from_host(void *ptr, ram_addr_t *offset); * * @mr: the memory region being queried. */ -void *memory_region_get_ram_ptr(MemoryRegion *mr); +void *memory_region_get_ram_ptr(const MemoryRegion *mr); /* memory_region_ram_resize: Resize a RAM region. * @@ -2421,7 +2362,7 @@ void memory_region_add_subregion_overlap(MemoryRegion *mr, * * @mr: the region to be queried */ -ram_addr_t memory_region_get_ram_addr(MemoryRegion *mr); +ram_addr_t memory_region_get_ram_addr(const MemoryRegion *mr); uint64_t memory_region_get_alignment(const MemoryRegion *mr); /** @@ -2521,7 +2462,7 @@ bool memory_region_present(MemoryRegion *container, hwaddr addr); * * @mr: a #MemoryRegion which should be checked if it's mapped */ -bool memory_region_is_mapped(MemoryRegion *mr); +bool memory_region_is_mapped(const MemoryRegion *mr); /** * memory_region_get_ram_discard_manager: get the #RamDiscardManager for a @@ -2967,7 +2908,7 @@ void *qemu_map_ram_ptr(RAMBlock *ram_block, ram_addr_t addr); int memory_access_size(MemoryRegion *mr, unsigned l, hwaddr addr); bool prepare_mmio_access(MemoryRegion *mr); -static inline bool memory_region_supports_direct_access(MemoryRegion *mr) +static inline bool memory_region_supports_direct_access(const MemoryRegion *mr) { /* ROM DEVICE regions only allow direct access if in ROMD mode. */ if (memory_region_is_romd(mr)) { @@ -2984,8 +2925,8 @@ static inline bool memory_region_supports_direct_access(MemoryRegion *mr) return !memory_region_is_ram_device(mr); } -static inline bool memory_access_is_direct(MemoryRegion *mr, bool is_write, - MemTxAttrs attrs) +static inline bool memory_access_is_direct(const MemoryRegion *mr, + bool is_write, MemTxAttrs attrs) { if (!memory_region_supports_direct_access(mr)) { return false; diff --git a/include/system/mshv_int.h b/include/system/mshv_int.h index ad4d001c3cdd..35386c422fa6 100644 --- a/include/system/mshv_int.h +++ b/include/system/mshv_int.h @@ -96,9 +96,8 @@ void mshv_arch_amend_proc_features( union hv_partition_synthetic_processor_features *features); int mshv_arch_post_init_vm(int vm_fd); -#if defined COMPILING_PER_TARGET && defined CONFIG_MSHV_IS_POSSIBLE -int mshv_hvcall(int fd, const struct mshv_root_hvcall *args); -#endif +typedef struct mshv_root_hvcall mshv_root_hvcall; +int mshv_hvcall(int fd, const mshv_root_hvcall *args); /* memory */ typedef struct MshvMemoryRegion { diff --git a/include/system/nitro-accel.h b/include/system/nitro-accel.h new file mode 100644 index 000000000000..a93aa6fb00d7 --- /dev/null +++ b/include/system/nitro-accel.h @@ -0,0 +1,25 @@ +/* + * Nitro Enclaves accelerator - public interface + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef SYSTEM_NITRO_ACCEL_H +#define SYSTEM_NITRO_ACCEL_H + +#include "qemu/accel.h" + +extern bool nitro_allowed; + +static inline bool nitro_enabled(void) +{ + return nitro_allowed; +} + +#define TYPE_NITRO_ACCEL ACCEL_CLASS_NAME("nitro") + +typedef struct NitroAccelState NitroAccelState; +DECLARE_INSTANCE_CHECKER(NitroAccelState, NITRO_ACCEL, + TYPE_NITRO_ACCEL) + +#endif /* SYSTEM_NITRO_ACCEL_H */ diff --git a/include/system/physmem.h b/include/system/physmem.h index 7bb7d3e15453..da91b77bd9b5 100644 --- a/include/system/physmem.h +++ b/include/system/physmem.h @@ -51,5 +51,6 @@ physical_memory_snapshot_and_clear_dirty(MemoryRegion *mr, hwaddr offset, bool physical_memory_snapshot_get_dirty(DirtyBitmapSnapshot *snap, ram_addr_t start, ram_addr_t length); +int ram_block_rebind(Error **errp); #endif diff --git a/include/system/ramblock.h b/include/system/ramblock.h index e9f58ac04571..4435f8d55fe7 100644 --- a/include/system/ramblock.h +++ b/include/system/ramblock.h @@ -154,26 +154,26 @@ RAMBlock *qemu_ram_block_by_name(const char *name); */ RAMBlock *qemu_ram_block_from_host(void *ptr, bool round_offset, ram_addr_t *offset); -ram_addr_t qemu_ram_block_host_offset(RAMBlock *rb, void *host); +ram_addr_t qemu_ram_block_host_offset(const RAMBlock *rb, void *host); void qemu_ram_set_idstr(RAMBlock *block, const char *name, DeviceState *dev); void qemu_ram_unset_idstr(RAMBlock *block); -const char *qemu_ram_get_idstr(RAMBlock *rb); -void *qemu_ram_get_host_addr(RAMBlock *rb); -ram_addr_t qemu_ram_get_offset(RAMBlock *rb); -ram_addr_t qemu_ram_get_fd_offset(RAMBlock *rb); -ram_addr_t qemu_ram_get_used_length(RAMBlock *rb); -ram_addr_t qemu_ram_get_max_length(RAMBlock *rb); -bool qemu_ram_is_shared(RAMBlock *rb); -bool qemu_ram_is_noreserve(RAMBlock *rb); -bool qemu_ram_is_uf_zeroable(RAMBlock *rb); +const char *qemu_ram_get_idstr(const RAMBlock *rb); +void *qemu_ram_get_host_addr(const RAMBlock *rb); +ram_addr_t qemu_ram_get_offset(const RAMBlock *rb); +ram_addr_t qemu_ram_get_fd_offset(const RAMBlock *rb); +ram_addr_t qemu_ram_get_used_length(const RAMBlock *rb); +ram_addr_t qemu_ram_get_max_length(const RAMBlock *rb); +bool qemu_ram_is_shared(const RAMBlock *rb); +bool qemu_ram_is_noreserve(const RAMBlock *rb); +bool qemu_ram_is_uf_zeroable(const RAMBlock *rb); void qemu_ram_set_uf_zeroable(RAMBlock *rb); -bool qemu_ram_is_migratable(RAMBlock *rb); +bool qemu_ram_is_migratable(const RAMBlock *rb); void qemu_ram_set_migratable(RAMBlock *rb); void qemu_ram_unset_migratable(RAMBlock *rb); -bool qemu_ram_is_named_file(RAMBlock *rb); -int qemu_ram_get_fd(RAMBlock *rb); +bool qemu_ram_is_named_file(const RAMBlock *rb); +int qemu_ram_get_fd(const RAMBlock *rb); -size_t qemu_ram_pagesize(RAMBlock *block); +size_t qemu_ram_pagesize(const RAMBlock *block); size_t qemu_ram_pagesize_largest(void); #include "exec/target_page.h" #include "exec/hwaddr.h" diff --git a/include/system/replay.h b/include/system/replay.h index f8715ca9feb8..19fb6dbb3963 100644 --- a/include/system/replay.h +++ b/include/system/replay.h @@ -165,8 +165,15 @@ void replay_net_packet_event(ReplayNetState *rns, unsigned flags, /*! Saves/restores number of played samples of audio out operation. */ void replay_audio_out(size_t *played); -/*! Saves/restores recorded samples of audio in operation. */ -void replay_audio_in(size_t *recorded, st_sample *samples, size_t *wpos, size_t size); +/* + * Start saves/restores recorded samples of audio in operation. + * Must be called before replay_audio_in_sample_lr(). + */ +void replay_audio_in_start(size_t *recorded); +/* Saves/restores recorded samples. */ +void replay_audio_in_sample_lr(uint64_t *left, uint64_t *right); +/* Finish saves/restores recorded samples. */ +void replay_audio_in_finish(void); /* VM state operations */ diff --git a/include/system/whpx-accel-ops.h b/include/system/whpx-accel-ops.h index ed9d4c49f4d1..4b2a7326548d 100644 --- a/include/system/whpx-accel-ops.h +++ b/include/system/whpx-accel-ops.h @@ -22,11 +22,15 @@ void whpx_cpu_synchronize_post_reset(CPUState *cpu); void whpx_cpu_synchronize_post_init(CPUState *cpu); void whpx_cpu_synchronize_pre_loadvm(CPUState *cpu); -/* state subset only touched by the VCPU itself during runtime */ -#define WHPX_SET_RUNTIME_STATE 1 -/* state subset modified during VCPU reset */ -#define WHPX_SET_RESET_STATE 2 -/* full state set, modified during initialization or on vmload */ -#define WHPX_SET_FULL_STATE 3 +typedef enum WHPXStateLevel { + /* subset of runtime state for faster returns from vmexit */ + WHPX_LEVEL_FAST_RUNTIME_STATE, + /* state subset only touched by the VCPU itself during runtime */ + WHPX_LEVEL_RUNTIME_STATE, + /* state subset modified during VCPU reset */ + WHPX_LEVEL_RESET_STATE, + /* full state set, modified during initialization or on vmload */ + WHPX_LEVEL_FULL_STATE +} WHPXStateLevel; #endif /* TARGET_I386_WHPX_ACCEL_OPS_H */ diff --git a/include/system/whpx-all.h b/include/system/whpx-all.h index f13cdf7f660b..2cbea71b1496 100644 --- a/include/system/whpx-all.h +++ b/include/system/whpx-all.h @@ -2,10 +2,12 @@ #ifndef SYSTEM_WHPX_ALL_H #define SYSTEM_WHPX_ALL_H +#include "system/whpx-accel-ops.h" + /* Called by whpx-common */ int whpx_vcpu_run(CPUState *cpu); -void whpx_get_registers(CPUState *cpu); -void whpx_set_registers(CPUState *cpu, int level); +void whpx_get_registers(CPUState *cpu, WHPXStateLevel level); +void whpx_set_registers(CPUState *cpu, WHPXStateLevel level); int whpx_accel_init(AccelState *as, MachineState *ms); void whpx_cpu_instance_init(CPUState *cs); HRESULT whpx_set_exception_exit_bitmap(UINT64 exceptions); @@ -17,4 +19,9 @@ void whpx_translate_cpu_breakpoints( struct whpx_breakpoints *breakpoints, CPUState *cpu, int cpu_breakpoint_count); +void whpx_arch_destroy_vcpu(CPUState *cpu); + +/* called by whpx-accel-ops */ +bool whpx_arch_supports_guest_debug(void); + #endif diff --git a/include/system/whpx-common.h b/include/system/whpx-common.h index b86fe9db6eba..04289afd9738 100644 --- a/include/system/whpx-common.h +++ b/include/system/whpx-common.h @@ -3,9 +3,6 @@ #define SYSTEM_WHPX_COMMON_H struct AccelCPUState { -#ifdef HOST_X86_64 - WHV_EMULATOR_HANDLE emulator; -#endif bool window_registered; bool interruptable; bool ready_for_pic_interrupt; @@ -20,6 +17,9 @@ int whpx_first_vcpu_starting(CPUState *cpu); int whpx_last_vcpu_stopping(CPUState *cpu); void whpx_memory_init(void); struct whpx_breakpoint *whpx_lookup_breakpoint_by_addr(uint64_t address); +void whpx_flush_cpu_state(CPUState *cpu); +void whpx_get_reg(CPUState *cpu, WHV_REGISTER_NAME reg, WHV_REGISTER_VALUE* val); +void whpx_set_reg(CPUState *cpu, WHV_REGISTER_NAME reg, WHV_REGISTER_VALUE val); /* On x64: same as WHvX64ExceptionTypeDebugTrapOrFault */ #define WHPX_INTERCEPT_DEBUG_TRAPS 1 diff --git a/include/system/whpx-internal.h b/include/system/whpx-internal.h index ad6ade223eec..8482901f7149 100644 --- a/include/system/whpx-internal.h +++ b/include/system/whpx-internal.h @@ -4,9 +4,6 @@ #include #include -#ifdef HOST_X86_64 -#include -#endif #include "hw/i386/apic.h" #include "exec/vaddr.h" @@ -45,6 +42,10 @@ struct whpx_state { bool kernel_irqchip_allowed; bool kernel_irqchip_required; + + bool hyperv_enlightenments_allowed; + bool hyperv_enlightenments_required; + }; extern struct whpx_state whpx_global; @@ -89,12 +90,6 @@ void whpx_apic_get(APICCommonState *s); X(HRESULT, WHvResetPartition, \ (WHV_PARTITION_HANDLE Partition)) \ -#define LIST_WINHVEMULATION_FUNCTIONS(X) \ - X(HRESULT, WHvEmulatorCreateEmulator, (const WHV_EMULATOR_CALLBACKS* Callbacks, WHV_EMULATOR_HANDLE* Emulator)) \ - X(HRESULT, WHvEmulatorDestroyEmulator, (WHV_EMULATOR_HANDLE Emulator)) \ - X(HRESULT, WHvEmulatorTryIoEmulation, (WHV_EMULATOR_HANDLE Emulator, VOID* Context, const WHV_VP_EXIT_CONTEXT* VpContext, const WHV_X64_IO_PORT_ACCESS_CONTEXT* IoInstructionContext, WHV_EMULATOR_STATUS* EmulatorReturnStatus)) \ - X(HRESULT, WHvEmulatorTryMmioEmulation, (WHV_EMULATOR_HANDLE Emulator, VOID* Context, const WHV_VP_EXIT_CONTEXT* VpContext, const WHV_MEMORY_ACCESS_CONTEXT* MmioInstructionContext, WHV_EMULATOR_STATUS* EmulatorReturnStatus)) \ - #define WHP_DEFINE_TYPE(return_type, function_name, signature) \ typedef return_type (WINAPI *function_name ## _t) signature; @@ -103,16 +98,10 @@ void whpx_apic_get(APICCommonState *s); /* Define function typedef */ LIST_WINHVPLATFORM_FUNCTIONS(WHP_DEFINE_TYPE) -#ifdef HOST_X86_64 -LIST_WINHVEMULATION_FUNCTIONS(WHP_DEFINE_TYPE) -#endif LIST_WINHVPLATFORM_FUNCTIONS_SUPPLEMENTAL(WHP_DEFINE_TYPE) struct WHPDispatch { LIST_WINHVPLATFORM_FUNCTIONS(WHP_DECLARE_MEMBER) -#ifdef HOST_X86_64 - LIST_WINHVEMULATION_FUNCTIONS(WHP_DECLARE_MEMBER) -#endif LIST_WINHVPLATFORM_FUNCTIONS_SUPPLEMENTAL(WHP_DECLARE_MEMBER) }; @@ -122,7 +111,6 @@ bool init_whp_dispatch(void); typedef enum WHPFunctionList { WINHV_PLATFORM_FNS_DEFAULT, - WINHV_EMULATION_FNS_DEFAULT, WINHV_PLATFORM_FNS_SUPPLEMENTAL } WHPFunctionList; diff --git a/include/system/xen-mapcache.h b/include/system/xen-mapcache.h index fa2cff38d1a3..114d1929c80f 100644 --- a/include/system/xen-mapcache.h +++ b/include/system/xen-mapcache.h @@ -13,9 +13,10 @@ typedef hwaddr (*phys_offset_to_gaddr_t)(hwaddr phys_offset, ram_addr_t size); +bool xen_map_cache_enabled(void); void xen_map_cache_init(phys_offset_to_gaddr_t f, void *opaque); -uint8_t *xen_map_cache(MemoryRegion *mr, hwaddr phys_addr, hwaddr size, +uint8_t *xen_map_cache(const MemoryRegion *mr, hwaddr phys_addr, hwaddr size, ram_addr_t ram_addr_offset, uint8_t lock, bool dma, bool is_write); diff --git a/include/system/xen.h b/include/system/xen.h index 9a7c53f28381..43b857e4d156 100644 --- a/include/system/xen.h +++ b/include/system/xen.h @@ -30,7 +30,7 @@ extern bool xen_allowed; void xen_hvm_modified_memory(ram_addr_t start, ram_addr_t length); void xen_ram_alloc(ram_addr_t ram_addr, ram_addr_t size, - struct MemoryRegion *mr, Error **errp); -bool xen_mr_is_memory(MemoryRegion *mr); -bool xen_mr_is_grants(MemoryRegion *mr); + const MemoryRegion *mr, Error **errp); +bool xen_mr_is_memory(const MemoryRegion *mr); +bool xen_mr_is_grants(const MemoryRegion *mr); #endif diff --git a/include/tcg/tcg-op-common.h b/include/tcg/tcg-op-common.h index f752ef440b20..e02f209c093f 100644 --- a/include/tcg/tcg-op-common.h +++ b/include/tcg/tcg-op-common.h @@ -30,6 +30,14 @@ TCGv_i64 tcg_global_mem_new_i64(TCGv_ptr reg, intptr_t off, const char *name); TCGv_ptr tcg_global_mem_new_ptr(TCGv_ptr reg, intptr_t off, const char *name); /* Generic ops. */ +static inline void tcg_gen_insn_start(uint64_t pc, uint64_t a1, + uint64_t a2) +{ + TCGOp *op = tcg_emit_op(INDEX_op_insn_start, INSN_START_WORDS); + tcg_set_insn_start_param(op, 0, pc); + tcg_set_insn_start_param(op, 1, a1); + tcg_set_insn_start_param(op, 2, a2); +} void gen_set_label(TCGLabel *l); void tcg_gen_br(TCGLabel *l); diff --git a/include/tcg/tcg-op.h b/include/tcg/tcg-op.h index ee379994e762..7024be938e6e 100644 --- a/include/tcg/tcg-op.h +++ b/include/tcg/tcg-op.h @@ -28,35 +28,6 @@ # error Mismatch with insn-start-words.h #endif -#if TARGET_INSN_START_EXTRA_WORDS == 0 -static inline void tcg_gen_insn_start(target_ulong pc) -{ - TCGOp *op = tcg_emit_op(INDEX_op_insn_start, INSN_START_WORDS); - tcg_set_insn_start_param(op, 0, pc); - tcg_set_insn_start_param(op, 1, 0); - tcg_set_insn_start_param(op, 2, 0); -} -#elif TARGET_INSN_START_EXTRA_WORDS == 1 -static inline void tcg_gen_insn_start(target_ulong pc, target_ulong a1) -{ - TCGOp *op = tcg_emit_op(INDEX_op_insn_start, INSN_START_WORDS); - tcg_set_insn_start_param(op, 0, pc); - tcg_set_insn_start_param(op, 1, a1); - tcg_set_insn_start_param(op, 2, 0); -} -#elif TARGET_INSN_START_EXTRA_WORDS == 2 -static inline void tcg_gen_insn_start(target_ulong pc, target_ulong a1, - target_ulong a2) -{ - TCGOp *op = tcg_emit_op(INDEX_op_insn_start, INSN_START_WORDS); - tcg_set_insn_start_param(op, 0, pc); - tcg_set_insn_start_param(op, 1, a1); - tcg_set_insn_start_param(op, 2, a2); -} -#else -#error Unhandled TARGET_INSN_START_EXTRA_WORDS value -#endif - #if TARGET_LONG_BITS == 32 typedef TCGv_i32 TCGv; #define tcg_temp_new() tcg_temp_new_i32() diff --git a/include/tcg/tcg.h b/include/tcg/tcg.h index 60942ce05c27..45c7e118c3d9 100644 --- a/include/tcg/tcg.h +++ b/include/tcg/tcg.h @@ -445,12 +445,6 @@ static inline bool temp_readonly(TCGTemp *ts) return ts->kind >= TEMP_FIXED; } -#ifdef CONFIG_USER_ONLY -extern bool tcg_use_softmmu; -#else -#define tcg_use_softmmu true -#endif - extern __thread TCGContext *tcg_ctx; extern const void *tcg_code_gen_epilogue; extern uintptr_t tcg_splitwx_diff; diff --git a/include/ui/console.h b/include/ui/console.h index 98feaa58bdd1..3677a9d334d9 100644 --- a/include/ui/console.h +++ b/include/ui/console.h @@ -457,7 +457,7 @@ void qemu_display_help(void); void vnc_display_init(const char *id, Error **errp); void vnc_display_open(const char *id, Error **errp); void vnc_display_add_client(const char *id, int csock, bool skipauth); -int vnc_display_password(const char *id, const char *password); +int vnc_display_password(const char *id, const char *password, Error **errp); int vnc_display_pw_expire(const char *id, time_t expires); void vnc_parse(const char *str); int vnc_init_func(void *opaque, QemuOpts *opts, Error **errp); diff --git a/include/ui/egl-context.h b/include/ui/egl-context.h index c2761d747a4e..4d559ae7bd46 100644 --- a/include/ui/egl-context.h +++ b/include/ui/egl-context.h @@ -5,7 +5,8 @@ #include "ui/egl-helpers.h" QEMUGLContext qemu_egl_create_context(DisplayGLCtx *dgc, - QEMUGLParams *params); + QEMUGLParams *params, + EGLContext share_context); void qemu_egl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx); int qemu_egl_make_context_current(DisplayGLCtx *dgc, QEMUGLContext ctx); diff --git a/include/ui/qemu-spice-module.h b/include/ui/qemu-spice-module.h index 1f22d557ea2f..072efa0c8348 100644 --- a/include/ui/qemu-spice-module.h +++ b/include/ui/qemu-spice-module.h @@ -29,7 +29,8 @@ struct QemuSpiceOps { void (*display_init)(void); int (*migrate_info)(const char *h, int p, int t, const char *s); int (*set_passwd)(const char *passwd, - bool fail_if_connected, bool disconnect_if_connected); + bool fail_if_connected, bool disconnect_if_connected, + Error **errp); int (*set_pw_expire)(time_t expires); int (*display_add_client)(int csock, int skipauth, int tls); #ifdef CONFIG_SPICE diff --git a/include/ui/sdl2.h b/include/ui/sdl2.h index dbe6e3d9739b..9daf5ecffae7 100644 --- a/include/ui/sdl2.h +++ b/include/ui/sdl2.h @@ -45,6 +45,7 @@ struct sdl2_console { bool gui_keysym; SDL_GLContext winctx; QKbdState *kbd; + bool has_dmabuf; #ifdef CONFIG_OPENGL QemuGLShader *gls; egl_fb guest_fb; @@ -96,5 +97,11 @@ void sdl2_gl_scanout_texture(DisplayChangeListener *dcl, void *d3d_tex2d); void sdl2_gl_scanout_flush(DisplayChangeListener *dcl, uint32_t x, uint32_t y, uint32_t w, uint32_t h); +void sdl2_gl_scanout_dmabuf(DisplayChangeListener *dcl, + QemuDmaBuf *dmabuf); +void sdl2_gl_release_dmabuf(DisplayChangeListener *dcl, + QemuDmaBuf *dmabuf); +bool sdl2_gl_has_dmabuf(DisplayChangeListener *dcl); +void sdl2_gl_console_init(struct sdl2_console *scon); #endif /* SDL2_H */ diff --git a/io/channel-tls.c b/io/channel-tls.c index b0cec27cb9c0..940fc3c6d11f 100644 --- a/io/channel-tls.c +++ b/io/channel-tls.c @@ -153,13 +153,32 @@ struct QIOChannelTLSData { }; typedef struct QIOChannelTLSData QIOChannelTLSData; +static void qio_channel_tls_io_data_free(gpointer user_data) +{ + QIOChannelTLSData *data = user_data; + /* + * Usually 'task' will be NULL since the GSource + * callback will either complete the task or pass + * it on to a new GSource. We'll see a non-NULL + * task here only if the GSource was released before + * its callback triggers + */ + if (data->task) { + qio_task_free(data->task); + } + if (data->context) { + g_main_context_unref(data->context); + } + g_free(data); +} + static gboolean qio_channel_tls_handshake_io(QIOChannel *ioc, GIOCondition condition, gpointer user_data); -static void qio_channel_tls_handshake_task(QIOChannelTLS *ioc, - QIOTask *task, - GMainContext *context) +static gboolean qio_channel_tls_handshake_task(QIOChannelTLS *ioc, + QIOTask *task, + GMainContext *context) { Error *err = NULL; int status; @@ -170,7 +189,7 @@ static void qio_channel_tls_handshake_task(QIOChannelTLS *ioc, trace_qio_channel_tls_handshake_fail(ioc); qio_task_set_error(task, err); qio_task_complete(task); - return; + return TRUE; } if (status == QCRYPTO_TLS_HANDSHAKE_COMPLETE) { @@ -183,6 +202,7 @@ static void qio_channel_tls_handshake_task(QIOChannelTLS *ioc, trace_qio_channel_tls_credentials_allow(ioc); } qio_task_complete(task); + return TRUE; } else { GIOCondition condition; QIOChannelTLSData *data = g_new0(typeof(*data), 1); @@ -206,8 +226,9 @@ static void qio_channel_tls_handshake_task(QIOChannelTLS *ioc, condition, qio_channel_tls_handshake_io, data, - NULL, + qio_channel_tls_io_data_free, context); + return FALSE; } } @@ -223,11 +244,9 @@ static gboolean qio_channel_tls_handshake_io(QIOChannel *ioc, qio_task_get_source(task)); tioc->hs_ioc_tag = 0; - g_free(data); - qio_channel_tls_handshake_task(tioc, task, context); - - if (context) { - g_main_context_unref(context); + if (!qio_channel_tls_handshake_task(tioc, task, context)) { + /* task is kept by new GSource so must not be released yet */ + data->task = NULL; } return FALSE; @@ -250,14 +269,16 @@ void qio_channel_tls_handshake(QIOChannelTLS *ioc, func, opaque, destroy); trace_qio_channel_tls_handshake_start(ioc); - qio_channel_tls_handshake_task(ioc, task, context); + if (qio_channel_tls_handshake_task(ioc, task, context)) { + qio_task_free(task); + } } static gboolean qio_channel_tls_bye_io(QIOChannel *ioc, GIOCondition condition, gpointer user_data); -static void qio_channel_tls_bye_task(QIOChannelTLS *ioc, QIOTask *task, - GMainContext *context) +static gboolean qio_channel_tls_bye_task(QIOChannelTLS *ioc, QIOTask *task, + GMainContext *context) { GIOCondition condition; QIOChannelTLSData *data; @@ -270,12 +291,12 @@ static void qio_channel_tls_bye_task(QIOChannelTLS *ioc, QIOTask *task, trace_qio_channel_tls_bye_fail(ioc); qio_task_set_error(task, err); qio_task_complete(task); - return; + return TRUE; } if (status == QCRYPTO_TLS_BYE_COMPLETE) { qio_task_complete(task); - return; + return TRUE; } data = g_new0(typeof(*data), 1); @@ -295,7 +316,10 @@ static void qio_channel_tls_bye_task(QIOChannelTLS *ioc, QIOTask *task, trace_qio_channel_tls_bye_pending(ioc, status); ioc->bye_ioc_tag = qio_channel_add_watch_full(ioc->master, condition, qio_channel_tls_bye_io, - data, NULL, context); + data, + qio_channel_tls_io_data_free, + context); + return FALSE; } @@ -308,11 +332,9 @@ static gboolean qio_channel_tls_bye_io(QIOChannel *ioc, GIOCondition condition, QIOChannelTLS *tioc = QIO_CHANNEL_TLS(qio_task_get_source(task)); tioc->bye_ioc_tag = 0; - g_free(data); - qio_channel_tls_bye_task(tioc, task, context); - - if (context) { - g_main_context_unref(context); + if (!qio_channel_tls_bye_task(tioc, task, context)) { + /* task is kept by new GSource so must not be released yet */ + data->task = NULL; } return FALSE; diff --git a/io/channel-websock.c b/io/channel-websock.c index d0929ba23269..9902b014f793 100644 --- a/io/channel-websock.c +++ b/io/channel-websock.c @@ -526,11 +526,32 @@ static int qio_channel_websock_handshake_read(QIOChannelWebsock *ioc, return 1; } +typedef struct QIOChannelWebsockData { + QIOTask *task; +} QIOChannelWebsockData; + +static void qio_channel_websock_data_free(gpointer user_data) +{ + QIOChannelWebsockData *data = user_data; + /* + * Usually 'task' will be NULL since the GSource + * callback will either complete the task or pass + * it on to a new GSource. We'll see a non-NULL + * task here only if the GSource was released before + * its callback triggers + */ + if (data->task) { + qio_task_free(data->task); + } + g_free(data); +} + static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc, GIOCondition condition, gpointer user_data) { - QIOTask *task = user_data; + QIOChannelWebsockData *data = user_data; + QIOTask *task = data->task; QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK( qio_task_get_source(task)); Error *err = NULL; @@ -572,7 +593,8 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc, GIOCondition condition, gpointer user_data) { - QIOTask *task = user_data; + QIOChannelWebsockData *data = user_data, *newdata = NULL; + QIOTask *task = data->task; QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK( qio_task_get_source(task)); Error *err = NULL; @@ -600,12 +622,14 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc, error_propagate(&wioc->io_err, err); trace_qio_channel_websock_handshake_reply(ioc); + newdata = g_new0(QIOChannelWebsockData, 1); + newdata->task = g_steal_pointer(&data->task); wioc->hs_io_tag = qio_channel_add_watch( wioc->master, G_IO_OUT, qio_channel_websock_handshake_send, - task, - NULL); + newdata, + qio_channel_websock_data_free); return FALSE; } @@ -901,12 +925,12 @@ void qio_channel_websock_handshake(QIOChannelWebsock *ioc, gpointer opaque, GDestroyNotify destroy) { - QIOTask *task; + QIOChannelWebsockData *data = g_new0(QIOChannelWebsockData, 1); - task = qio_task_new(OBJECT(ioc), - func, - opaque, - destroy); + data->task = qio_task_new(OBJECT(ioc), + func, + opaque, + destroy); trace_qio_channel_websock_handshake_start(ioc); trace_qio_channel_websock_handshake_pending(ioc, G_IO_IN); @@ -914,8 +938,8 @@ void qio_channel_websock_handshake(QIOChannelWebsock *ioc, ioc->master, G_IO_IN, qio_channel_websock_handshake_io, - task, - NULL); + data, + qio_channel_websock_data_free); } diff --git a/io/task.c b/io/task.c index da79d3178264..c3615d2d74fc 100644 --- a/io/task.c +++ b/io/task.c @@ -70,8 +70,12 @@ QIOTask *qio_task_new(Object *source, return task; } -static void qio_task_free(QIOTask *task) +void qio_task_free(QIOTask *task) { + if (!task) { + return; + } + qemu_mutex_lock(&task->thread_lock); if (task->thread) { if (task->thread->destroy) { @@ -108,6 +112,7 @@ static gboolean qio_task_thread_result(gpointer opaque) trace_qio_task_thread_result(task); qio_task_complete(task); + qio_task_free(task); return FALSE; } @@ -194,7 +199,6 @@ void qio_task_complete(QIOTask *task) { task->func(task, task->opaque); trace_qio_task_complete(task); - qio_task_free(task); } diff --git a/linux-user/aarch64/cpu_loop.c b/linux-user/aarch64/cpu_loop.c index 7f66a879ea98..e7f643d69d56 100644 --- a/linux-user/aarch64/cpu_loop.c +++ b/linux-user/aarch64/cpu_loop.c @@ -181,7 +181,7 @@ void cpu_loop(CPUARMState *env) 0, 0); if (ret == -QEMU_ERESTARTSYS) { env->pc -= 4; - } else if (ret != -QEMU_ESIGRETURN) { + } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) { env->xregs[0] = ret; } break; diff --git a/linux-user/alpha/cpu_loop.c b/linux-user/alpha/cpu_loop.c index f93597c400db..bef196b1f56b 100644 --- a/linux-user/alpha/cpu_loop.c +++ b/linux-user/alpha/cpu_loop.c @@ -82,7 +82,7 @@ void cpu_loop(CPUAlphaState *env) env->pc -= 4; break; } - if (sysret == -QEMU_ESIGRETURN) { + if (sysret == -QEMU_ESIGRETURN || sysret == -QEMU_ESETPC) { break; } /* Syscall writes 0 to V0 to bypass error check, similar diff --git a/linux-user/arm/cpu_loop.c b/linux-user/arm/cpu_loop.c index 40aefc4c1d70..19874f4c7278 100644 --- a/linux-user/arm/cpu_loop.c +++ b/linux-user/arm/cpu_loop.c @@ -399,7 +399,7 @@ void cpu_loop(CPUARMState *env) 0, 0); if (ret == -QEMU_ERESTARTSYS) { env->regs[15] -= env->thumb ? 2 : 4; - } else if (ret != -QEMU_ESIGRETURN) { + } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) { env->regs[0] = ret; } } diff --git a/linux-user/hexagon/cpu_loop.c b/linux-user/hexagon/cpu_loop.c index 5711055aff21..9464246e9e35 100644 --- a/linux-user/hexagon/cpu_loop.c +++ b/linux-user/hexagon/cpu_loop.c @@ -56,7 +56,7 @@ void cpu_loop(CPUHexagonState *env) 0, 0); if (ret == -QEMU_ERESTARTSYS) { env->gpr[HEX_REG_PC] -= 4; - } else if (ret != -QEMU_ESIGRETURN) { + } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) { env->gpr[0] = ret; } break; diff --git a/linux-user/hppa/cpu_loop.c b/linux-user/hppa/cpu_loop.c index 972e85c487d4..4b4b663052b7 100644 --- a/linux-user/hppa/cpu_loop.c +++ b/linux-user/hppa/cpu_loop.c @@ -124,6 +124,7 @@ void cpu_loop(CPUHPPAState *env) break; case -QEMU_ERESTARTSYS: case -QEMU_ESIGRETURN: + case -QEMU_ESETPC: break; } break; diff --git a/linux-user/hppa/elfload.c b/linux-user/hppa/elfload.c index 460070870207..7f7ece6dc196 100644 --- a/linux-user/hppa/elfload.c +++ b/linux-user/hppa/elfload.c @@ -8,7 +8,7 @@ const char *get_elf_cpu_model(uint32_t eflags) { - return "hppa"; + return "pa-7300lc"; } const char *get_elf_platform(CPUState *cs) diff --git a/linux-user/i386/cpu_loop.c b/linux-user/i386/cpu_loop.c index f3f58576af5c..fe922fceb5ab 100644 --- a/linux-user/i386/cpu_loop.c +++ b/linux-user/i386/cpu_loop.c @@ -181,7 +181,9 @@ static void emulate_vsyscall(CPUX86State *env) if (ret == -TARGET_EFAULT) { goto sigsegv; } - env->regs[R_EAX] = ret; + if (ret != -QEMU_ESETPC) { + env->regs[R_EAX] = ret; + } /* Emulate a ret instruction to leave the vsyscall page. */ env->eip = caller; @@ -234,7 +236,7 @@ void cpu_loop(CPUX86State *env) 0, 0); if (ret == -QEMU_ERESTARTSYS) { env->eip -= 2; - } else if (ret != -QEMU_ESIGRETURN) { + } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) { env->regs[R_EAX] = ret; } break; @@ -253,7 +255,7 @@ void cpu_loop(CPUX86State *env) 0, 0); if (ret == -QEMU_ERESTARTSYS) { env->eip -= 2; - } else if (ret != -QEMU_ESIGRETURN) { + } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) { env->regs[R_EAX] = ret; } break; diff --git a/linux-user/include/special-errno.h b/linux-user/include/special-errno.h index 4120455baaf9..1db757241a31 100644 --- a/linux-user/include/special-errno.h +++ b/linux-user/include/special-errno.h @@ -29,4 +29,12 @@ */ #define QEMU_ESIGRETURN 513 +/* + * This is returned after a plugin has used the qemu_plugin_set_pc API, to + * indicate that the plugin deliberately changed the PC and potentially + * modified the register values. The main loop should not touch the guest + * registers for this reason. + */ +#define QEMU_ESETPC 514 + #endif /* SPECIAL_ERRNO_H */ diff --git a/linux-user/ioctls.h b/linux-user/ioctls.h index 6ecfe6306e4c..5b7d00e92f2d 100644 --- a/linux-user/ioctls.h +++ b/linux-user/ioctls.h @@ -26,7 +26,7 @@ IOCTL(TIOCSCTTY, 0, TYPE_INT) IOCTL(TIOCGPGRP, IOC_R, MK_PTR(TYPE_INT)) IOCTL(TIOCSPGRP, IOC_W, MK_PTR(TYPE_INT)) - IOCTL(TIOCGSID, IOC_W, MK_PTR(TYPE_INT)) + IOCTL(TIOCGSID, IOC_R, MK_PTR(TYPE_INT)) IOCTL(TIOCOUTQ, IOC_R, MK_PTR(TYPE_INT)) IOCTL(TIOCSTI, IOC_W, MK_PTR(TYPE_INT)) IOCTL(TIOCMGET, IOC_R, MK_PTR(TYPE_INT)) diff --git a/linux-user/loongarch64/cpu_loop.c b/linux-user/loongarch64/cpu_loop.c index 26a5ce3a936c..603fcc39c7f2 100644 --- a/linux-user/loongarch64/cpu_loop.c +++ b/linux-user/loongarch64/cpu_loop.c @@ -44,9 +44,10 @@ void cpu_loop(CPULoongArchState *env) env->pc -= 4; break; } - if (ret == -QEMU_ESIGRETURN) { + if (ret == -QEMU_ESIGRETURN || ret == -QEMU_ESETPC) { /* - * Returning from a successful sigreturn syscall. + * Returning from a successful sigreturn syscall or from + * control flow diversion in a plugin callback. * Avoid clobbering register state. */ break; diff --git a/linux-user/m68k/cpu_loop.c b/linux-user/m68k/cpu_loop.c index 2c9f628241f4..b98ca8ff7b91 100644 --- a/linux-user/m68k/cpu_loop.c +++ b/linux-user/m68k/cpu_loop.c @@ -66,7 +66,7 @@ void cpu_loop(CPUM68KState *env) 0, 0); if (ret == -QEMU_ERESTARTSYS) { env->pc -= 2; - } else if (ret != -QEMU_ESIGRETURN) { + } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) { env->dregs[0] = ret; } } diff --git a/linux-user/microblaze/cpu_loop.c b/linux-user/microblaze/cpu_loop.c index 78506ab23d9b..06d92c0b90db 100644 --- a/linux-user/microblaze/cpu_loop.c +++ b/linux-user/microblaze/cpu_loop.c @@ -54,7 +54,7 @@ void cpu_loop(CPUMBState *env) if (ret == -QEMU_ERESTARTSYS) { /* Wind back to before the syscall. */ env->pc -= 4; - } else if (ret != -QEMU_ESIGRETURN) { + } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) { env->regs[3] = ret; } /* All syscall exits result in guest r14 being equal to the diff --git a/linux-user/mips/cpu_loop.c b/linux-user/mips/cpu_loop.c index 2365de1de1a2..fa264b27ec5a 100644 --- a/linux-user/mips/cpu_loop.c +++ b/linux-user/mips/cpu_loop.c @@ -140,9 +140,12 @@ void cpu_loop(CPUMIPSState *env) env->active_tc.PC -= 4; break; } - if (ret == -QEMU_ESIGRETURN) { - /* Returning from a successful sigreturn syscall. - Avoid clobbering register state. */ + if (ret == -QEMU_ESIGRETURN || ret == -QEMU_ESETPC) { + /* + * Returning from a successful sigreturn syscall or from + * control flow diversion in a plugin callback. + * Avoid clobbering register state. + */ break; } if ((abi_ulong)ret >= (abi_ulong)-1133) { diff --git a/linux-user/mmap.c b/linux-user/mmap.c index 07175e11d576..76978a56a859 100644 --- a/linux-user/mmap.c +++ b/linux-user/mmap.c @@ -423,12 +423,15 @@ abi_ulong mmap_next_start; static abi_ulong mmap_find_vma_reserved(abi_ulong start, abi_ulong size, abi_ulong align) { - target_ulong ret; + target_ulong ret = -1; - ret = page_find_range_empty(start, reserved_va, size, align); + if (start <= reserved_va) { + ret = page_find_range_empty(start, reserved_va, size, align); + } if (ret == -1 && start > mmap_min_addr) { /* Restart at the beginning of the address space. */ - ret = page_find_range_empty(mmap_min_addr, start - 1, size, align); + ret = page_find_range_empty(mmap_min_addr, MIN(start - 1, reserved_va), + size, align); } return ret; diff --git a/linux-user/or1k/cpu_loop.c b/linux-user/or1k/cpu_loop.c index 2167d880d555..e7e9929e6f5b 100644 --- a/linux-user/or1k/cpu_loop.c +++ b/linux-user/or1k/cpu_loop.c @@ -48,7 +48,7 @@ void cpu_loop(CPUOpenRISCState *env) cpu_get_gpr(env, 8), 0, 0); if (ret == -QEMU_ERESTARTSYS) { env->pc -= 4; - } else if (ret != -QEMU_ESIGRETURN) { + } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) { cpu_set_gpr(env, 11, ret); } break; diff --git a/linux-user/ppc/cpu_loop.c b/linux-user/ppc/cpu_loop.c index b0b0cb14b41d..1f9ee20bd0c8 100644 --- a/linux-user/ppc/cpu_loop.c +++ b/linux-user/ppc/cpu_loop.c @@ -340,9 +340,13 @@ void cpu_loop(CPUPPCState *env) env->nip -= 4; break; } - if (ret == (target_ulong)(-QEMU_ESIGRETURN)) { - /* Returning from a successful sigreturn syscall. - Avoid corrupting register state. */ + if (ret == (target_ulong)(-QEMU_ESIGRETURN) || + ret == (target_ulong)(-QEMU_ESETPC)) { + /* + * Returning from a successful sigreturn syscall or from + * control flow diversion in a plugin callback. + * Avoid corrupting register state. + */ break; } if (ret > (target_ulong)(-515)) { diff --git a/linux-user/riscv/cpu_loop.c b/linux-user/riscv/cpu_loop.c index ce542540c28b..eecc8d151786 100644 --- a/linux-user/riscv/cpu_loop.c +++ b/linux-user/riscv/cpu_loop.c @@ -65,7 +65,7 @@ void cpu_loop(CPURISCVState *env) } if (ret == -QEMU_ERESTARTSYS) { env->pc -= 4; - } else if (ret != -QEMU_ESIGRETURN) { + } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) { env->gpr[xA0] = ret; } if (cs->singlestep_enabled) { diff --git a/linux-user/s390x/cpu_loop.c b/linux-user/s390x/cpu_loop.c index 4929b32e1fcc..67d2a803fbca 100644 --- a/linux-user/s390x/cpu_loop.c +++ b/linux-user/s390x/cpu_loop.c @@ -83,7 +83,7 @@ void cpu_loop(CPUS390XState *env) env->regs[6], env->regs[7], 0, 0); if (ret == -QEMU_ERESTARTSYS) { env->psw.addr -= env->int_svc_ilen; - } else if (ret != -QEMU_ESIGRETURN) { + } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) { env->regs[2] = ret; } diff --git a/linux-user/sh4/cpu_loop.c b/linux-user/sh4/cpu_loop.c index 0c9d7e9c46b5..ee2958d0d936 100644 --- a/linux-user/sh4/cpu_loop.c +++ b/linux-user/sh4/cpu_loop.c @@ -50,7 +50,7 @@ void cpu_loop(CPUSH4State *env) 0, 0); if (ret == -QEMU_ERESTARTSYS) { env->pc -= 2; - } else if (ret != -QEMU_ESIGRETURN) { + } else if (ret != -QEMU_ESIGRETURN && ret != -QEMU_ESETPC) { env->gregs[0] = ret; } break; diff --git a/linux-user/sparc/cpu_loop.c b/linux-user/sparc/cpu_loop.c index 7391e2add8d4..ab633eeae3fe 100644 --- a/linux-user/sparc/cpu_loop.c +++ b/linux-user/sparc/cpu_loop.c @@ -229,7 +229,9 @@ void cpu_loop (CPUSPARCState *env) env->regwptr[2], env->regwptr[3], env->regwptr[4], env->regwptr[5], 0, 0); - if (ret == -QEMU_ERESTARTSYS || ret == -QEMU_ESIGRETURN) { + if (ret == -QEMU_ERESTARTSYS || + ret == -QEMU_ESIGRETURN || + ret == -QEMU_ESETPC) { break; } if ((abi_ulong)ret >= (abi_ulong)(-515)) { diff --git a/linux-user/strace.c b/linux-user/strace.c index ca67cfd09d36..2cbaf94c8909 100644 --- a/linux-user/strace.c +++ b/linux-user/strace.c @@ -85,6 +85,10 @@ UNUSED static void print_enums(const struct enums *, abi_long, int); UNUSED static void print_at_dirfd(abi_long, int); UNUSED static void print_file_mode(abi_long, int); UNUSED static void print_open_flags(abi_long, int); +UNUSED static void print_file_offset32(abi_long offset, bool last); +UNUSED static void print_file_offset64(abi_long word0, + abi_long word1, + bool last); UNUSED static void print_syscall_prologue(const struct syscallname *); UNUSED static void print_syscall_epilogue(const struct syscallname *); UNUSED static void print_string(abi_long, int); @@ -1017,12 +1021,12 @@ print_syscall_ret_ioctl(CPUArchState *cpu_env, const struct syscallname *name, int target_size; for (ie = ioctl_entries; ie->target_cmd != 0; ie++) { - if (ie->target_cmd == arg1) { + if (ie->target_cmd == (int)arg1) { break; } } - if (ie->target_cmd == arg1 && + if (ie->target_cmd == (int)arg1 && (ie->access == IOC_R || ie->access == IOC_RW)) { arg_type = ie->arg_type; qemu_log(" ("); @@ -1125,7 +1129,9 @@ UNUSED static const struct flags openat2_resolve_flags[] = { FLAG_GENERIC(RESOLVE_NO_SYMLINKS), FLAG_GENERIC(RESOLVE_BENEATH), FLAG_GENERIC(RESOLVE_IN_ROOT), +#ifdef RESOLVE_CACHED FLAG_GENERIC(RESOLVE_CACHED), +#endif #endif FLAG_END, }; @@ -1664,6 +1670,20 @@ print_open_flags(abi_long flags, int last) print_flags(open_flags, flags, last); } +/* Prints 32-bit file offset (off_t) */ +static void +print_file_offset32(abi_long offset, bool last) +{ + print_raw_param(TARGET_ABI_FMT_ld, offset, last); +} + +/* Prints 64-bit file offset (loff_t) */ +static void +print_file_offset64(abi_long word0, abi_long word1, bool last) +{ + print_raw_param64("%" PRId64, target_offset64(word0, word1), last); +} + static void print_syscall_prologue(const struct syscallname *sc) { @@ -2256,11 +2276,13 @@ print_fallocate(CPUArchState *cpu_env, const struct syscallname *name, print_raw_param("%d", arg0, 0); print_flags(falloc_flags, arg1, 0); #if TARGET_ABI_BITS == 32 - print_raw_param("%" PRIu64, target_offset64(arg2, arg3), 0); - print_raw_param("%" PRIu64, target_offset64(arg4, arg5), 1); + /* On 32-bit targets, two registers are used for `loff_t` */ + print_file_offset64(arg2, arg3, false); + print_file_offset64(arg4, arg5, true); #else - print_raw_param(TARGET_ABI_FMT_ld, arg2, 0); - print_raw_param(TARGET_ABI_FMT_ld, arg3, 1); + /* On 64-bit targets, one register is used for `loff_t` */ + print_file_offset64(arg2, 0, false); + print_file_offset64(arg3, 0, true); #endif print_syscall_epilogue(name); } @@ -2666,8 +2688,7 @@ print__llseek(CPUArchState *cpu_env, const struct syscallname *name, const char *whence = "UNKNOWN"; print_syscall_prologue(name); print_raw_param("%d", arg0, 0); - print_raw_param("%ld", arg1, 0); - print_raw_param("%ld", arg2, 0); + print_file_offset64(arg1, arg2, false); print_pointer(arg3, 0); switch(arg4) { case SEEK_SET: whence = "SEEK_SET"; break; @@ -2688,7 +2709,7 @@ print_lseek(CPUArchState *cpu_env, const struct syscallname *name, { print_syscall_prologue(name); print_raw_param("%d", arg0, 0); - print_raw_param(TARGET_ABI_FMT_ld, arg1, 0); + print_file_offset32(arg1, false); switch (arg2) { case SEEK_SET: qemu_log("SEEK_SET"); break; @@ -2719,7 +2740,7 @@ print_truncate(CPUArchState *cpu_env, const struct syscallname *name, { print_syscall_prologue(name); print_string(arg0, 0); - print_raw_param(TARGET_ABI_FMT_ld, arg1, 1); + print_file_offset32(arg1, true); print_syscall_epilogue(name); } #endif @@ -2736,7 +2757,7 @@ print_truncate64(CPUArchState *cpu_env, const struct syscallname *name, arg1 = arg2; arg2 = arg3; } - print_raw_param("%" PRIu64, target_offset64(arg1, arg2), 1); + print_file_offset64(arg1, arg2, true); print_syscall_epilogue(name); } #endif @@ -2753,7 +2774,7 @@ print_ftruncate64(CPUArchState *cpu_env, const struct syscallname *name, arg1 = arg2; arg2 = arg3; } - print_raw_param("%" PRIu64, target_offset64(arg1, arg2), 1); + print_file_offset64(arg1, arg2, true); print_syscall_epilogue(name); } #endif @@ -3308,7 +3329,7 @@ print_stat(CPUArchState *cpu_env, const struct syscallname *name, print_syscall_epilogue(name); } #define print_lstat print_stat -#define print_stat64 print_stat +#define print_stat64 print_stat #define print_lstat64 print_stat #endif @@ -4302,7 +4323,7 @@ print_pread64(CPUArchState *cpu_env, const struct syscallname *name, print_raw_param("%d", arg0, 0); print_pointer(arg1, 0); print_raw_param("%d", arg2, 0); - print_raw_param("%" PRIu64, target_offset64(arg3, arg4), 1); + print_file_offset64(arg3, arg4, true); print_syscall_epilogue(name); } #endif @@ -4338,7 +4359,7 @@ print_ioctl(CPUArchState *cpu_env, const struct syscallname *name, int target_size; for (ie = ioctl_entries; ie->target_cmd != 0; ie++) { - if (ie->target_cmd == arg1) { + if (ie->target_cmd == (int)arg1) { break; } } diff --git a/linux-user/strace.list b/linux-user/strace.list index 51b5ead9696c..35f001fecd6a 100644 --- a/linux-user/strace.list +++ b/linux-user/strace.list @@ -641,7 +641,7 @@ { TARGET_NR_mq_unlink, "mq_unlink" , NULL, print_mq_unlink, NULL }, #endif #ifdef TARGET_NR_mremap -{ TARGET_NR_mremap, "mremap" , NULL, NULL, NULL }, +{ TARGET_NR_mremap, "mremap" , "%s(%#x,%u,%u,%d,%#x)", NULL, NULL }, #endif #ifdef TARGET_NR_msgctl { TARGET_NR_msgctl, "msgctl" , NULL, NULL, NULL }, diff --git a/linux-user/syscall.c b/linux-user/syscall.c index d466d0e32f10..064bc604c991 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -600,6 +601,9 @@ const char *target_strerror(int err) if (err == QEMU_ESIGRETURN) { return "Successful exit from sigreturn"; } + if (err == QEMU_ESETPC) { + return "Successfully redirected control flow"; + } return strerror(target_to_host_errno(err)); } @@ -8827,6 +8831,10 @@ static int do_openat2(CPUArchState *cpu_env, abi_long dirfd, } return ret; } + if (tswap64(how.flags) >> 32) { + return -TARGET_EINVAL; + } + pathname = lock_user_string(guest_pathname); if (!pathname) { return -TARGET_EFAULT; @@ -14410,6 +14418,18 @@ abi_long do_syscall(CPUArchState *cpu_env, int num, abi_long arg1, return -QEMU_ESIGRETURN; } + /* + * Set up a longjmp target here so that we can call cpu_loop_exit to + * redirect control flow back to the main loop even from within + * syscall-related plugin callbacks. + * For other types of callbacks or longjmp call sites, the longjmp target + * is set up in the cpu loop itself but in syscalls the target is not live + * anymore. + */ + if (unlikely(sigsetjmp(cpu->jmp_env, 0) != 0)) { + return -QEMU_ESETPC; + } + record_syscall_start(cpu, num, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); diff --git a/linux-user/xtensa/cpu_loop.c b/linux-user/xtensa/cpu_loop.c index a0ff10eff82f..d2b4ccdfade9 100644 --- a/linux-user/xtensa/cpu_loop.c +++ b/linux-user/xtensa/cpu_loop.c @@ -186,6 +186,7 @@ void cpu_loop(CPUXtensaState *env) break; case -QEMU_ESIGRETURN: + case -QEMU_ESETPC: break; } break; diff --git a/meson.build b/meson.build index 4af32c3e1f2d..1867560da639 100644 --- a/meson.build +++ b/meson.build @@ -302,11 +302,13 @@ accelerator_targets += { 'CONFIG_XEN': xen_targets } if cpu == 'aarch64' accelerator_targets += { 'CONFIG_HVF': ['aarch64-softmmu'], + 'CONFIG_NITRO': ['aarch64-softmmu'], 'CONFIG_WHPX': ['aarch64-softmmu'] } elif cpu == 'x86_64' accelerator_targets += { 'CONFIG_HVF': ['x86_64-softmmu'], + 'CONFIG_NITRO': ['x86_64-softmmu'], 'CONFIG_NVMM': ['i386-softmmu', 'x86_64-softmmu'], 'CONFIG_WHPX': ['i386-softmmu', 'x86_64-softmmu'], 'CONFIG_MSHV': ['x86_64-softmmu'], @@ -692,6 +694,7 @@ warn_flags = [ '-Wempty-body', '-Wendif-labels', '-Wexpansion-to-defined', + '-Wformat-overflow=2', '-Wformat-security', '-Wformat-y2k', '-Wignored-qualifiers', @@ -865,8 +868,7 @@ if get_option('whpx').allowed() and host_os == 'windows' endif # Leave CONFIG_WHPX disabled else - if cc.has_header('winhvplatform.h', required: get_option('whpx')) and \ - cc.has_header('winhvemulation.h', required: get_option('whpx')) + if cc.has_header('winhvplatform.h', required: get_option('whpx')) accelerators += 'CONFIG_WHPX' endif endif @@ -881,6 +883,11 @@ if get_option('hvf').allowed() endif endif +nitro = not_found +if get_option('nitro').allowed() and host_os == 'linux' + accelerators += 'CONFIG_NITRO' +endif + nvmm = not_found if host_os == 'netbsd' nvmm = cc.find_library('nvmm', required: get_option('nvmm')) @@ -922,6 +929,9 @@ endif if 'CONFIG_HVF' not in accelerators and get_option('hvf').enabled() error('HVF not available on this platform') endif +if 'CONFIG_NITRO' not in accelerators and get_option('nitro').enabled() + error('NITRO not available on this platform') +endif if 'CONFIG_NVMM' not in accelerators and get_option('nvmm').enabled() error('NVMM not available on this platform') endif @@ -1289,7 +1299,7 @@ endif pulse = not_found if not get_option('pa').auto() or (host_os == 'linux' and have_system) - pulse = dependency('libpulse', required: get_option('pa'), + pulse = dependency('libpulse', version: '>=0.9.13', required: get_option('pa'), method: 'pkg-config') endif alsa = not_found @@ -1316,7 +1326,7 @@ endif spice_protocol = not_found if not get_option('spice_protocol').auto() or have_system - spice_protocol = dependency('spice-protocol', version: '>=0.14.0', + spice_protocol = dependency('spice-protocol', version: '>=0.14.3', required: get_option('spice_protocol'), method: 'pkg-config') endif @@ -1937,10 +1947,8 @@ if get_option('gtk') \ endif endif -x11 = not_found -if gtkx11.found() - x11 = dependency('x11', method: 'pkg-config', required: gtkx11.found()) -endif +x11 = dependency('x11', method: 'pkg-config', required: gtkx11.found()) + png = not_found if get_option('png').allowed() and have_system png = dependency('libpng', version: '>=1.6.34', required: get_option('png'), @@ -2517,6 +2525,8 @@ config_host_data.set('CONFIG_VNC_JPEG', jpeg.found()) config_host_data.set('CONFIG_VNC_SASL', sasl.found()) if virgl.found() config_host_data.set('VIRGL_VERSION_MAJOR', virgl.version().split('.')[0]) + config_host_data.set('VIRGL_VERSION_MINOR', virgl.version().split('.')[1]) + config_host_data.set('VIRGL_VERSION_MICRO', virgl.version().split('.')[2]) endif config_host_data.set('CONFIG_VIRTFS', have_virtfs) config_host_data.set('CONFIG_VTE', vte.found()) @@ -2664,18 +2674,14 @@ else endif config_host_data.set('CONFIG_ASAN_IFACE_FIBER', have_asan_fiber) +inotify = not_found have_inotify_init = cc.has_header_symbol('sys/inotify.h', 'inotify_init') have_inotify_init1 = cc.has_header_symbol('sys/inotify.h', 'inotify_init1') -inotify = not_found -if (have_inotify_init or have_inotify_init1) and host_os == 'freebsd' - # libinotify-kqueue +if (have_inotify_init or have_inotify_init1) and not cc.has_function('inotify_init1') + # FreeBSD 14 and older need libinotify-kqueue wrapper inotify = cc.find_library('inotify') - if have_inotify_init - have_inotify_init = inotify.found() - endif - if have_inotify_init1 - have_inotify_init1 = inotify.found() - endif + have_inotify_init = have_inotify_init and inotify.found() + have_inotify_init1 = have_inotify_init1 and inotify.found() endif config_host_data.set('CONFIG_INOTIFY', have_inotify_init) config_host_data.set('CONFIG_INOTIFY1', have_inotify_init1) @@ -2851,6 +2857,27 @@ config_host_data.set('CONFIG_PTHREAD_SET_NAME_NP', cc.links(osdep_prefix + ''' pthread_set_name_np(thread, "QEMU"); return 0; }''', dependencies: threads)) + +config_host_data.set('CONFIG_PTHREAD_GETNAME_NP', cc.links(osdep_prefix + ''' + #include + + int main(void) + { + char buf[16]; + pthread_getname_np(pthread_self(), buf, sizeof(buf)); + return 0; + }''', dependencies: threads)) +config_host_data.set('CONFIG_PTHREAD_GET_NAME_NP', cc.links(osdep_prefix + ''' + #include + #include + + int main(void) + { + char buf[16]; + pthread_get_name_np(pthread_self(), buf, sizeof(buf)); + return 0; + }''', dependencies: threads)) + config_host_data.set('CONFIG_PTHREAD_CONDATTR_SETCLOCK', cc.links(osdep_prefix + ''' #include @@ -3200,7 +3227,7 @@ if host_os == 'windows' endif # Detect if ConvertStringToBSTR has been defined in _com_util namespace -if host_os == 'windows' +if host_os == 'windows' and 'cpp' in all_languages has_convert_string_to_bstr = cxx.links(''' #include int main() { @@ -3375,8 +3402,6 @@ foreach target : target_dirs config_target_data.set(k, v) endif endforeach - config_target_data.set('QEMU_ARCH', - 'QEMU_ARCH_' + config_target['TARGET_BASE_ARCH'].to_upper()) config_target_h += {target: configure_file(output: target + '-config-target.h', configuration: config_target_data)} @@ -3593,6 +3618,7 @@ if have_system 'accel/hvf', 'accel/kvm', 'accel/mshv', + 'accel/nitro', 'audio', 'backends', 'backends/tpm', @@ -3610,6 +3636,7 @@ if have_system 'hw/fsi', 'hw/hyperv', 'hw/i2c', + 'hw/i3c', 'hw/i386', 'hw/i386/xen', 'hw/i386/kvm', @@ -3623,6 +3650,7 @@ if have_system 'hw/misc/macio', 'hw/net', 'hw/net/can', + 'hw/nitro', 'hw/nubus', 'hw/nvme', 'hw/nvram', @@ -3727,6 +3755,14 @@ subdir('authz') subdir('crypto') subdir('ui') subdir('gdbstub') +subdir('semihosting') +subdir('audio') +subdir('io') +subdir('chardev') +subdir('fsdev') +subdir('dump') +subdir('accel') + if have_system subdir('hw') else @@ -3772,12 +3808,6 @@ if have_system or have_user subdir('target') endif -subdir('audio') -subdir('io') -subdir('chardev') -subdir('fsdev') -subdir('dump') - if have_block block_ss.add(files( 'block.c', @@ -3835,7 +3865,7 @@ if get_option('b_lto') pagevary = declare_dependency(link_with: pagevary) endif common_ss.add(pagevary) -specific_ss.add(files('page-vary-target.c')) +system_ss.add(files('page-vary-system.c')) common_ss.add(files('target-info.c')) system_ss.add(files('target-info-qom.c')) @@ -3847,11 +3877,9 @@ subdir('migration') subdir('monitor') subdir('net') subdir('replay') -subdir('semihosting') subdir('stats') subdir('tcg') subdir('fpu') -subdir('accel') subdir('plugins') subdir('ebpf') @@ -3869,9 +3897,6 @@ subdir('linux-user') subdir('tests/qtest/libqos') subdir('tests/qtest/fuzz') -# accel modules -target_modules += { 'accel' : { 'qtest': qtest_module_ss }} - ############################################## # Internal static_libraries and dependencies # ############################################## @@ -3879,6 +3904,7 @@ target_modules += { 'accel' : { 'qtest': qtest_module_ss }} modinfo_collect = find_program('scripts/modinfo-collect.py') modinfo_generate = find_program('scripts/modinfo-generate.py') modinfo_files = [] +audio_modinfo_files = [] block_mods = [] system_mods = [] @@ -3906,15 +3932,21 @@ foreach d, list : modules install: true, install_dir: qemu_moddir) if module_ss.sources() != [] - modinfo_files += custom_target(d + '-' + m + '.modinfo', - output: d + '-' + m + '.modinfo', - input: sl.extract_all_objects(recursive: true), - capture: true, - command: [modinfo_collect, '@INPUT@']) + modinfo = custom_target(d + '-' + m + '.modinfo', + output: d + '-' + m + '.modinfo', + input: sl.extract_all_objects(recursive: true), + capture: true, + command: [modinfo_collect, '@INPUT@']) + modinfo_files += modinfo + if d == 'audio' + audio_modinfo_files += modinfo + endif endif else if d == 'block' block_ss.add_all(module_ss) + elif d == 'audio' + audio_ss.add_all(module_ss) else system_ss.add_all(module_ss) endif @@ -4070,11 +4102,19 @@ libhwcore = static_library('hwcore', sources: hwcore_ss.sources() + genh, hwcore = declare_dependency(objects: libhwcore.extract_all_objects(recursive: false)) common_ss.add(hwcore) +audio_ss = audio_ss.apply({}) +libaudio = static_library('qemuaudio', audio_ss.sources() + genh, + dependencies: [audio_ss.dependencies()], + build_by_default: false) + +audio = declare_dependency(objects: libaudio.extract_all_objects(recursive: false), + dependencies: [audio_ss.dependencies(), qom]) + ########### # Targets # ########### -system_ss.add(authz, blockdev, chardev, crypto, io, qmp) +system_ss.add(authz, blockdev, chardev, crypto, io, qmp, audio) common_ss.add(qom, qemuutil) libuser = static_library('user', @@ -4504,6 +4544,7 @@ if have_tools if have_vhost_user subdir('contrib/vhost-user-blk') + subdir('contrib/vhost-user-bridge') subdir('contrib/vhost-user-gpu') subdir('contrib/vhost-user-input') subdir('contrib/vhost-user-scsi') @@ -4564,6 +4605,7 @@ subdir('scripts') subdir('tools') subdir('pc-bios') subdir('docs') +subdir('pyvenv') # Tests are disabled on emscripten because they rely on host features that aren't # supported by emscripten (e.g. fork and unix socket). if host_os != 'emscripten' @@ -4774,6 +4816,7 @@ endif summary_info = {} if have_system summary_info += {'KVM support': config_all_accel.has_key('CONFIG_KVM')} + summary_info += {'Nitro support': config_all_accel.has_key('CONFIG_NITRO')} summary_info += {'HVF support': config_all_accel.has_key('CONFIG_HVF')} summary_info += {'WHPX support': config_all_accel.has_key('CONFIG_WHPX')} summary_info += {'NVMM support': config_all_accel.has_key('CONFIG_NVMM')} diff --git a/meson_options.txt b/meson_options.txt index 2836156257af..31d5916cfce1 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -79,6 +79,8 @@ option('whpx', type: 'feature', value: 'auto', description: 'WHPX acceleration support') option('hvf', type: 'feature', value: 'auto', description: 'HVF acceleration support') +option('nitro', type: 'feature', value: 'auto', + description: 'Nitro acceleration support') option('nvmm', type: 'feature', value: 'auto', description: 'NVMM acceleration support') option('xen', type: 'feature', value: 'auto', diff --git a/migration/colo.c b/migration/colo.c index f7a5bd3619a4..2d36f933cf15 100644 --- a/migration/colo.c +++ b/migration/colo.c @@ -173,11 +173,13 @@ static void primary_vm_do_failover(void) * The s->rp_state.from_dst_file and s->to_dst_file may use the * same fd, but we still shutdown the fd for twice, it is harmless. */ - if (s->to_dst_file) { - qemu_file_shutdown(s->to_dst_file); - } - if (s->rp_state.from_dst_file) { - qemu_file_shutdown(s->rp_state.from_dst_file); + WITH_QEMU_LOCK_GUARD(&s->qemu_file_lock) { + if (s->to_dst_file) { + qemu_file_shutdown(s->to_dst_file); + } + if (s->rp_state.from_dst_file) { + qemu_file_shutdown(s->rp_state.from_dst_file); + } } old_state = failover_set_state(FAILOVER_STATUS_ACTIVE, @@ -455,9 +457,8 @@ static int colo_do_checkpoint_transaction(MigrationState *s, /* Note: device state is saved into buffer */ ret = qemu_save_device_state(fb, &local_err); - - bql_unlock(); if (ret < 0) { + bql_unlock(); goto out; } @@ -471,6 +472,7 @@ static int colo_do_checkpoint_transaction(MigrationState *s, */ qemu_savevm_state_complete_precopy_iterable(s->to_dst_file, false); qemu_savevm_state_end(s->to_dst_file); + bql_unlock(); /* * We need the size of the VMstate data in Secondary side, @@ -537,6 +539,8 @@ static void colo_process_checkpoint(MigrationState *s) Error *local_err = NULL; int ret; + assert(s->rp_state.from_dst_file); + assert(!s->rp_state.rp_thread_created); if (get_colo_mode() != COLO_MODE_PRIMARY) { error_report("COLO mode must be COLO_MODE_PRIMARY"); return; @@ -544,12 +548,6 @@ static void colo_process_checkpoint(MigrationState *s) failover_init_state(); - s->rp_state.from_dst_file = qemu_file_get_return_path(s->to_dst_file); - if (!s->rp_state.from_dst_file) { - error_report("Open QEMUFile from_dst_file failed"); - goto out; - } - packets_compare_notifier.notify = colo_compare_notify_checkpoint; colo_compare_register_notifier(&packets_compare_notifier); @@ -634,16 +632,6 @@ static void colo_process_checkpoint(MigrationState *s) colo_compare_unregister_notifier(&packets_compare_notifier); timer_free(s->colo_delay_timer); qemu_event_destroy(&s->colo_checkpoint_event); - - /* - * Must be called after failover BH is completed, - * Or the failover BH may shutdown the wrong fd that - * re-used by other threads after we release here. - */ - if (s->rp_state.from_dst_file) { - qemu_fclose(s->rp_state.from_dst_file); - s->rp_state.from_dst_file = NULL; - } } void migrate_start_colo_process(MigrationState *s) @@ -686,11 +674,7 @@ static void colo_incoming_process_checkpoint(MigrationIncomingState *mis, return; } - bql_lock(); - cpu_synchronize_all_states(); ret = qemu_loadvm_state_main(mis->from_src_file, mis, errp); - bql_unlock(); - if (ret < 0) { return; } @@ -729,6 +713,14 @@ static void colo_incoming_process_checkpoint(MigrationIncomingState *mis, bql_lock(); vmstate_loading = true; + /* + * With colo we load device vmstate during each checkpoint, on top of + * a vm that was already running. Some devices expect a reset before + * loading vmstate on such a previously running vm. + * + * NOTE: qemu_system_reset() calls cpu_synchronize_all_states() for us + */ + qemu_system_reset(SHUTDOWN_CAUSE_SNAPSHOT_LOAD); colo_flush_ram_cache(); ret = qemu_load_device_state(fb, errp); if (ret < 0) { @@ -832,6 +824,7 @@ static void *colo_process_incoming_thread(void *opaque) migrate_set_state(&mis->state, MIGRATION_STATUS_ACTIVE, MIGRATION_STATUS_COLO); + assert(mis->to_src_file); if (get_colo_mode() != COLO_MODE_SECONDARY) { error_report("COLO mode must be COLO_MODE_SECONDARY"); return NULL; @@ -848,7 +841,6 @@ static void *colo_process_incoming_thread(void *opaque) failover_init_state(); - mis->to_src_file = qemu_file_get_return_path(mis->from_src_file); /* * Note: the communication between Primary side and Secondary side * should be sequential, we set the fd to unblocked in migration incoming @@ -860,6 +852,12 @@ static void *colo_process_incoming_thread(void *opaque) goto out; } + /* + * rp thread still running on primary side, shut it down to go into + * colo state. + */ + migrate_send_rp_shut(mis, 0); + colo_incoming_start_dirty_log(); bioc = qio_channel_buffer_new(COLO_BUFFER_BASE_SIZE); @@ -935,7 +933,7 @@ void coroutine_fn colo_incoming_co(void) QemuThread th; assert(bql_locked()); - assert(migration_incoming_colo_enabled()); + assert(migrate_colo()); qemu_thread_create(&th, MIGRATION_THREAD_DST_COLO, colo_process_incoming_thread, @@ -949,7 +947,4 @@ void coroutine_fn colo_incoming_co(void) /* Wait checkpoint incoming thread exit before free resource */ qemu_thread_join(&th); bql_lock(); - - /* We hold the global BQL, so it is safe here */ - colo_release_ram_cache(); } diff --git a/migration/meson.build b/migration/meson.build index c7f39bdb5523..c9f0f5f9f213 100644 --- a/migration/meson.build +++ b/migration/meson.build @@ -39,7 +39,7 @@ system_ss.add(files( ), gnutls, zlib) if get_option('replication').allowed() - system_ss.add(files('colo-failover.c', 'colo.c')) + system_ss.add(files('colo-failover.c', 'colo.c', 'multifd-colo.c')) else system_ss.add(files('colo-stubs.c')) endif diff --git a/migration/migration.c b/migration/migration.c index a5b0465ed30c..f94970862934 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -97,7 +97,8 @@ static GSList *migration_blockers[MIG_MODE__MAX]; static bool migration_object_check(MigrationState *ms, Error **errp); static bool migration_switchover_start(MigrationState *s, Error **errp); -static bool close_return_path_on_source(MigrationState *s); +static bool stop_return_path_thread_on_source(MigrationState *s); +static void migration_release_dst_files(MigrationState *ms); static void migration_completion_end(MigrationState *s); static void migration_downtime_start(MigrationState *s) @@ -454,6 +455,9 @@ void migration_incoming_state_destroy(void) * BQL and retake unconditionally. */ assert(bql_locked()); + if (migrate_colo()) { + colo_release_ram_cache(); + } qemu_loadvm_state_cleanup(mis); if (mis->to_src_file) { @@ -604,40 +608,6 @@ int migrate_send_rp_req_pages(MigrationIncomingState *mis, return migrate_send_rp_message_req_pages(mis, rb, start); } -static bool migration_colo_enabled; -bool migration_incoming_colo_enabled(void) -{ - return migration_colo_enabled; -} - -void migration_incoming_disable_colo(void) -{ - ram_block_discard_disable(false); - migration_colo_enabled = false; -} - -int migration_incoming_enable_colo(Error **errp) -{ -#ifndef CONFIG_REPLICATION - error_setg(errp, "ENABLE_COLO command come in migration stream, but the " - "replication module is not built in"); - return -ENOTSUP; -#endif - - if (!migrate_colo()) { - error_setg(errp, "ENABLE_COLO command come in migration stream" - ", but x-colo capability is not set"); - return -EINVAL; - } - - if (ram_block_discard_disable(true)) { - error_setg(errp, "COLO: cannot disable RAM discard"); - return -EBUSY; - } - migration_colo_enabled = true; - return 0; -} - void migrate_add_address(SocketAddress *address) { MigrationIncomingState *mis = migration_incoming_get_current(); @@ -743,8 +713,7 @@ static void process_incoming_migration_bh(void *opaque) } else { runstate_set(RUN_STATE_PAUSED); } - } else if (migration_incoming_colo_enabled()) { - migration_incoming_disable_colo(); + } else if (migrate_colo()) { vm_start(); } else { runstate_set(global_state_get_runstate()); @@ -770,6 +739,20 @@ process_incoming_migration_co(void *opaque) assert(mis->from_src_file); + if (migrate_colo()) { + if (ram_block_discard_disable(true)) { + error_setg(&local_err, "COLO: cannot disable RAM discard"); + goto fail; + } + + ret = colo_init_ram_cache(&local_err); + if (ret) { + error_prepend(&local_err, "failed to init colo RAM cache: %d: ", + ret); + goto fail; + } + } + mis->largest_page_size = qemu_ram_pagesize_largest(); postcopy_state_set(POSTCOPY_INCOMING_NONE); migrate_set_state(&mis->state, MIGRATION_STATUS_SETUP, @@ -797,7 +780,7 @@ process_incoming_migration_co(void *opaque) goto fail; } - if (migration_incoming_colo_enabled()) { + if (migrate_colo()) { /* yield until COLO exit */ colo_incoming_co(); } @@ -1016,6 +999,7 @@ bool migration_is_running(void) case MIGRATION_STATUS_DEVICE: case MIGRATION_STATUS_WAIT_UNPLUG: case MIGRATION_STATUS_CANCELLING: + case MIGRATION_STATUS_FAILING: case MIGRATION_STATUS_COLO: return true; default: @@ -1158,6 +1142,7 @@ static void fill_source_migration_info(MigrationInfo *info) case MIGRATION_STATUS_POSTCOPY_PAUSED: case MIGRATION_STATUS_POSTCOPY_RECOVER_SETUP: case MIGRATION_STATUS_POSTCOPY_RECOVER: + case MIGRATION_STATUS_FAILING: /* TODO add some postcopy stats */ populate_time_info(info, s); populate_ram_info(info, s); @@ -1210,6 +1195,7 @@ static void fill_destination_migration_info(MigrationInfo *info) case MIGRATION_STATUS_POSTCOPY_PAUSED: case MIGRATION_STATUS_POSTCOPY_RECOVER: case MIGRATION_STATUS_FAILED: + case MIGRATION_STATUS_FAILING: case MIGRATION_STATUS_COLO: info->has_status = true; break; @@ -1296,7 +1282,8 @@ static void migration_cleanup(MigrationState *s) cpr_state_close(); cpr_transfer_source_destroy(s); - close_return_path_on_source(s); + stop_return_path_thread_on_source(s); + migration_release_dst_files(s); if (s->migration_thread_running) { bql_unlock(); @@ -1330,6 +1317,9 @@ static void migration_cleanup(MigrationState *s) if (s->state == MIGRATION_STATUS_CANCELLING) { migrate_set_state(&s->state, MIGRATION_STATUS_CANCELLING, MIGRATION_STATUS_CANCELLED); + } else if (s->state == MIGRATION_STATUS_FAILING) { + migrate_set_state(&s->state, MIGRATION_STATUS_FAILING, + MIGRATION_STATUS_FAILED); } /* @@ -1387,7 +1377,7 @@ void migration_connect_error_propagate(MigrationState *s, Error *error) switch (current) { case MIGRATION_STATUS_SETUP: - next = MIGRATION_STATUS_FAILED; + next = MIGRATION_STATUS_FAILING; break; case MIGRATION_STATUS_POSTCOPY_PAUSED: @@ -1401,9 +1391,10 @@ void migration_connect_error_propagate(MigrationState *s, Error *error) break; case MIGRATION_STATUS_CANCELLING: + case MIGRATION_STATUS_FAILING: /* - * Don't move out of CANCELLING, the only valid transition is to - * CANCELLED, at migration_cleanup(). + * Keep the current state, next transition is to be done + * in migration_cleanup(). */ break; @@ -1553,6 +1544,7 @@ bool migration_has_failed(MigrationState *s) { return (s->state == MIGRATION_STATUS_CANCELLING || s->state == MIGRATION_STATUS_CANCELLED || + s->state == MIGRATION_STATUS_FAILING || s->state == MIGRATION_STATUS_FAILED); } @@ -2227,6 +2219,7 @@ static void migration_release_dst_files(MigrationState *ms) * locking needed because this qemufile should only be managed by * return path thread. */ + assert(!ms->rp_state.rp_thread_created); if (ms->postcopy_qemufile_src) { migration_ioc_unregister_yank_from_file(ms->postcopy_qemufile_src); qemu_file_shutdown(ms->postcopy_qemufile_src); @@ -2234,7 +2227,9 @@ static void migration_release_dst_files(MigrationState *ms) ms->postcopy_qemufile_src = NULL; } - qemu_fclose(file); + if (file) { + qemu_fclose(file); + } } /* @@ -2420,7 +2415,7 @@ static void open_return_path_on_source(MigrationState *ms) } /* Return true if error detected, or false otherwise */ -static bool close_return_path_on_source(MigrationState *ms) +static bool stop_return_path_thread_on_source(MigrationState *ms) { if (!ms->rp_state.rp_thread_created) { return false; @@ -2442,7 +2437,6 @@ static bool close_return_path_on_source(MigrationState *ms) qemu_thread_join(&ms->rp_state.rp_thread); ms->rp_state.rp_thread_created = false; - migration_release_dst_files(ms); trace_migration_return_path_end_after(); /* Return path will persist the error in MigrationState when quit */ @@ -2479,7 +2473,7 @@ static int postcopy_start(MigrationState *ms, Error **errp) if (postcopy_preempt_establish_channel(ms)) { if (ms->state != MIGRATION_STATUS_CANCELLING) { migrate_set_state(&ms->state, ms->state, - MIGRATION_STATUS_FAILED); + MIGRATION_STATUS_FAILING); } error_setg(errp, "%s: Failed to establish preempt channel", __func__); @@ -2642,7 +2636,7 @@ static int postcopy_start(MigrationState *ms, Error **errp) qemu_fclose(fb); fail: if (ms->state != MIGRATION_STATUS_CANCELLING) { - migrate_set_state(&ms->state, ms->state, MIGRATION_STATUS_FAILED); + migrate_set_state(&ms->state, ms->state, MIGRATION_STATUS_FAILING); } bql_unlock(); return -1; @@ -2805,7 +2799,7 @@ static void migration_completion(MigrationState *s) goto fail; } - if (close_return_path_on_source(s)) { + if (stop_return_path_thread_on_source(s)) { goto fail; } @@ -2833,7 +2827,7 @@ static void migration_completion(MigrationState *s) } if (s->state != MIGRATION_STATUS_CANCELLING) { - migrate_set_state(&s->state, s->state, MIGRATION_STATUS_FAILED); + migrate_set_state(&s->state, s->state, MIGRATION_STATUS_FAILING); } } @@ -2870,7 +2864,7 @@ static void bg_migration_completion(MigrationState *s) fail: migrate_set_state(&s->state, current_active_state, - MIGRATION_STATUS_FAILED); + MIGRATION_STATUS_FAILING); } typedef enum MigThrError { @@ -2959,7 +2953,8 @@ static MigThrError postcopy_pause(MigrationState *s) * path and just wait for the thread to finish. It will be * re-created when we resume. */ - close_return_path_on_source(s); + stop_return_path_thread_on_source(s); + migration_release_dst_files(s); /* * Current channel is possibly broken. Release it. Note that this is @@ -3071,7 +3066,7 @@ static MigThrError migration_detect_error(MigrationState *s) * For precopy (or postcopy with error outside IO, or before dest * starts), we fail with no time. */ - migrate_set_state(&s->state, state, MIGRATION_STATUS_FAILED); + migrate_set_state(&s->state, state, MIGRATION_STATUS_FAILING); trace_migration_thread_file_err(); /* Time to stop the migration, now. */ @@ -3302,7 +3297,7 @@ static void migration_iteration_finish(MigrationState *s) migrate_start_colo_process(s); s->vm_old_state = RUN_STATE_RUNNING; /* Fallthrough */ - case MIGRATION_STATUS_FAILED: + case MIGRATION_STATUS_FAILING: case MIGRATION_STATUS_CANCELLED: case MIGRATION_STATUS_CANCELLING: if (!migration_block_activate(&local_err)) { @@ -3368,7 +3363,7 @@ static void bg_migration_iteration_finish(MigrationState *s) switch (s->state) { case MIGRATION_STATUS_COMPLETED: case MIGRATION_STATUS_ACTIVE: - case MIGRATION_STATUS_FAILED: + case MIGRATION_STATUS_FAILING: case MIGRATION_STATUS_CANCELLED: case MIGRATION_STATUS_CANCELLING: break; @@ -3529,11 +3524,6 @@ static void *migration_thread(void *opaque) qemu_savevm_send_postcopy_advise(s->to_dst_file); } - if (migrate_colo()) { - /* Notify migration destination that we enable COLO */ - qemu_savevm_send_colo_enable(s->to_dst_file); - } - if (migrate_auto_converge()) { /* Start RAMBlock dirty bitmap sync timer */ cpu_throttle_dirty_sync_timer(true); @@ -3553,7 +3543,7 @@ static void *migration_thread(void *opaque) if (ret) { migrate_error_propagate(s, local_err); migrate_set_state(&s->state, MIGRATION_STATUS_ACTIVE, - MIGRATION_STATUS_FAILED); + MIGRATION_STATUS_FAILING); goto out; } @@ -3745,7 +3735,7 @@ static void *bg_migration_thread(void *opaque) /* local_err is guaranteed to be set when reaching here */ migrate_error_propagate(s, local_err); migrate_set_state(&s->state, MIGRATION_STATUS_ACTIVE, - MIGRATION_STATUS_FAILED); + MIGRATION_STATUS_FAILING); done: bg_migration_iteration_finish(s); diff --git a/migration/multifd-colo.c b/migration/multifd-colo.c new file mode 100644 index 000000000000..fb13e38b9bc9 --- /dev/null +++ b/migration/multifd-colo.c @@ -0,0 +1,41 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * multifd colo implementation + * + * Copyright (c) Lukas Straub + */ + +#include "qemu/osdep.h" +#include "multifd.h" +#include "multifd-colo.h" +#include "migration/colo.h" +#include "system/ramblock.h" + +void multifd_colo_prepare_recv(MultiFDRecvParams *p) +{ + /* + * While we're still in precopy state (not yet in colo state), we copy + * received pages to both guest and cache. No need to set dirty bits, + * since guest and cache memory are in sync. + */ + if (migration_incoming_in_colo_state()) { + colo_record_bitmap(p->block, p->normal, p->normal_num); + colo_record_bitmap(p->block, p->zero, p->zero_num); + } +} + +void multifd_colo_process_recv(MultiFDRecvParams *p) +{ + if (!migration_incoming_in_colo_state()) { + for (int i = 0; i < p->normal_num; i++) { + void *guest = p->block->host + p->normal[i]; + void *cache = p->host + p->normal[i]; + memcpy(guest, cache, multifd_ram_page_size()); + } + for (int i = 0; i < p->zero_num; i++) { + void *guest = p->block->host + p->zero[i]; + memset(guest, 0, multifd_ram_page_size()); + } + } +} diff --git a/migration/multifd-colo.h b/migration/multifd-colo.h new file mode 100644 index 000000000000..1011c5a1da8a --- /dev/null +++ b/migration/multifd-colo.h @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * multifd colo header + * + * Copyright (c) Lukas Straub + */ + +#ifndef QEMU_MIGRATION_MULTIFD_COLO_H +#define QEMU_MIGRATION_MULTIFD_COLO_H + +#ifdef CONFIG_REPLICATION + +void multifd_colo_prepare_recv(MultiFDRecvParams *p); +void multifd_colo_process_recv(MultiFDRecvParams *p); + +#else + +static inline void multifd_colo_prepare_recv(MultiFDRecvParams *p) {} +static inline void multifd_colo_process_recv(MultiFDRecvParams *p) {} + +#endif +#endif diff --git a/migration/multifd-nocomp.c b/migration/multifd-nocomp.c index 9be79b3b8e00..9f7a792fa761 100644 --- a/migration/multifd-nocomp.c +++ b/migration/multifd-nocomp.c @@ -16,6 +16,7 @@ #include "file.h" #include "migration-stats.h" #include "multifd.h" +#include "multifd-colo.h" #include "options.h" #include "migration.h" #include "qapi/error.h" @@ -269,7 +270,6 @@ int multifd_ram_unfill_packet(MultiFDRecvParams *p, Error **errp) return -1; } - p->host = p->block->host; for (i = 0; i < p->normal_num; i++) { uint64_t offset = be64_to_cpu(packet->offset[i]); @@ -294,6 +294,14 @@ int multifd_ram_unfill_packet(MultiFDRecvParams *p, Error **errp) p->zero[i] = offset; } + if (migrate_colo()) { + multifd_colo_prepare_recv(p); + assert(p->block->colo_cache); + p->host = p->block->colo_cache; + } else { + p->host = p->block->host; + } + return 0; } diff --git a/migration/multifd.c b/migration/multifd.c index ad6261688fdf..8b9ed84805bb 100644 --- a/migration/multifd.c +++ b/migration/multifd.c @@ -29,6 +29,7 @@ #include "qemu-file.h" #include "trace.h" #include "multifd.h" +#include "multifd-colo.h" #include "options.h" #include "qemu/yank.h" #include "io/channel-file.h" @@ -431,7 +432,7 @@ static void multifd_send_error_propagate(Error *err) s->state == MIGRATION_STATUS_DEVICE || s->state == MIGRATION_STATUS_ACTIVE) { migrate_set_state(&s->state, s->state, - MIGRATION_STATUS_FAILED); + MIGRATION_STATUS_FAILING); } } } @@ -771,9 +772,14 @@ static void *multifd_send_thread(void *opaque) assert(local_err); trace_multifd_send_error(p->id); multifd_send_error_propagate(local_err); - multifd_send_kick_main(p); } + /* + * Always kick the main thread: The main thread might wait on this thread + * while another thread encounters an error and signals this thread to exit. + */ + multifd_send_kick_main(p); + rcu_unregister_thread(); trace_multifd_send_thread_end(p->id, p->packets_sent); @@ -986,7 +992,7 @@ bool multifd_send_setup(void) err: migrate_set_state(&s->state, MIGRATION_STATUS_SETUP, - MIGRATION_STATUS_FAILED); + MIGRATION_STATUS_FAILING); return false; } @@ -1253,6 +1259,22 @@ static int multifd_device_state_recv(MultiFDRecvParams *p, Error **errp) return ret; } +static int multifd_ram_state_recv(MultiFDRecvParams *p, Error **errp) +{ + int ret; + + ret = multifd_recv_state->ops->recv(p, errp); + if (ret != 0) { + return ret; + } + + if (migrate_colo()) { + multifd_colo_process_recv(p); + } + + return ret; +} + static void *multifd_recv_thread(void *opaque) { MigrationState *s = migrate_get_current(); @@ -1387,7 +1409,7 @@ static void *multifd_recv_thread(void *opaque) assert(use_packets); ret = multifd_device_state_recv(p, &local_err); } else { - ret = multifd_recv_state->ops->recv(p, &local_err); + ret = multifd_ram_state_recv(p, &local_err); } if (ret != 0) { break; diff --git a/migration/multifd.h b/migration/multifd.h index 89a395aef2b0..fbc35702b062 100644 --- a/migration/multifd.h +++ b/migration/multifd.h @@ -279,7 +279,10 @@ typedef struct { uint64_t packets_recved; /* ramblock */ RAMBlock *block; - /* ramblock host address */ + /* + * Normally, it points to ramblock's host address. When COLO + * is enabled, it points to the mirror cache for the ramblock. + */ uint8_t *host; /* buffers to recv */ struct iovec *iov; diff --git a/migration/options.c b/migration/options.c index 1ffe85a2d8cc..f33b2979290f 100644 --- a/migration/options.c +++ b/migration/options.c @@ -575,7 +575,15 @@ bool migrate_caps_check(bool *old_caps, bool *new_caps, Error **errp) ERRP_GUARD(); MigrationIncomingState *mis = migration_incoming_get_current(); -#ifndef CONFIG_REPLICATION +#ifdef CONFIG_REPLICATION + if (new_caps[MIGRATION_CAPABILITY_X_COLO]) { + if (!new_caps[MIGRATION_CAPABILITY_RETURN_PATH]) { + error_setg(errp, "Capability 'x-colo' requires capability " + "'return-path'"); + return false; + } + } +#else if (new_caps[MIGRATION_CAPABILITY_X_COLO]) { error_setg(errp, "QEMU compiled without replication module" " can't enable COLO"); diff --git a/migration/ram.c b/migration/ram.c index fc7ece2c1a10..979751f61b30 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -3116,12 +3116,12 @@ static int ram_save_setup(QEMUFile *f, void *opaque, Error **errp) RAMBlock *block; int ret, max_hg_page_size; - /* migration has already setup the bitmap, reuse it. */ - if (!migration_in_colo_state()) { - if (ram_init_all(rsp, errp) != 0) { - return -1; - } + assert(!migration_in_colo_state()); + + if (ram_init_all(rsp, errp) != 0) { + return -1; } + (*rsp)->pss[RAM_CHANNEL_PRECOPY].pss_channel = f; /* @@ -4370,7 +4370,7 @@ static int ram_load_precopy(QEMUFile *f) * speed of the migration, but it obviously reduce the downtime of * back-up all SVM'S memory in COLO preparing stage. */ - if (migration_incoming_colo_enabled()) { + if (migrate_colo()) { if (migration_incoming_in_colo_state()) { /* In COLO stage, put all pages into cache temporarily */ host = colo_cache_from_block_offset(block, addr, true); diff --git a/migration/savevm.c b/migration/savevm.c index 3a16c467b25b..197c89e0e659 100644 --- a/migration/savevm.c +++ b/migration/savevm.c @@ -90,7 +90,7 @@ enum qemu_vm_cmd { were previously sent during precopy but are dirty. */ MIG_CMD_PACKAGED, /* Send a wrapped stream within this stream */ - MIG_CMD_ENABLE_COLO, /* Enable COLO */ + MIG_CMD_DEPRECATED_0, /* Prior to 10.2, used as MIG_CMD_ENABLE_COLO */ MIG_CMD_POSTCOPY_RESUME, /* resume postcopy on dest */ MIG_CMD_RECV_BITMAP, /* Request for recved bitmap on dst */ MIG_CMD_SWITCHOVER_START, /* Switchover start notification */ @@ -1103,12 +1103,6 @@ static void qemu_savevm_command_send(QEMUFile *f, qemu_fflush(f); } -void qemu_savevm_send_colo_enable(QEMUFile *f) -{ - trace_savevm_send_colo_enable(); - qemu_savevm_command_send(f, MIG_CMD_ENABLE_COLO, 0, NULL); -} - void qemu_savevm_send_ping(QEMUFile *f, uint32_t value) { uint32_t buf; @@ -2423,25 +2417,6 @@ static int loadvm_handle_recv_bitmap(MigrationIncomingState *mis, return 0; } -static int loadvm_process_enable_colo(MigrationIncomingState *mis, - Error **errp) -{ - ERRP_GUARD(); - int ret; - - ret = migration_incoming_enable_colo(errp); - if (ret < 0) { - return ret; - } - - ret = colo_init_ram_cache(errp); - if (ret) { - error_prepend(errp, "failed to init colo RAM cache: %d: ", ret); - migration_incoming_disable_colo(); - } - return ret; -} - static int loadvm_postcopy_handle_switchover_start(Error **errp) { SaveStateEntry *se; @@ -2525,7 +2500,7 @@ static int loadvm_process_command(QEMUFile *f, Error **errp) return ret; } } - break; + return 0; case MIG_CMD_PING: tmp32 = qemu_get_be32(f); @@ -2536,7 +2511,7 @@ static int loadvm_process_command(QEMUFile *f, Error **errp) return -1; } migrate_send_rp_pong(mis, tmp32); - break; + return 0; case MIG_CMD_PACKAGED: return loadvm_handle_cmd_packaged(mis, errp); @@ -2560,14 +2535,12 @@ static int loadvm_process_command(QEMUFile *f, Error **errp) case MIG_CMD_RECV_BITMAP: return loadvm_handle_recv_bitmap(mis, len, errp); - case MIG_CMD_ENABLE_COLO: - return loadvm_process_enable_colo(mis, errp); - case MIG_CMD_SWITCHOVER_START: return loadvm_postcopy_handle_switchover_start(errp); } - return 0; + error_setg(errp, "MIG_CMD 0x%x deprecated (len 0x%x)", cmd, len); + return -EINVAL; } /* diff --git a/migration/savevm.h b/migration/savevm.h index 2ba0881f3bd2..b3d1e8a13ca9 100644 --- a/migration/savevm.h +++ b/migration/savevm.h @@ -65,7 +65,6 @@ void qemu_savevm_send_postcopy_ram_discard(QEMUFile *f, const char *name, uint16_t len, uint64_t *start_list, uint64_t *length_list); -void qemu_savevm_send_colo_enable(QEMUFile *f); int qemu_save_device_state(QEMUFile *f, Error **errp); int qemu_loadvm_state(QEMUFile *f, Error **errp); void qemu_loadvm_state_cleanup(MigrationIncomingState *mis); diff --git a/migration/trace-events b/migration/trace-events index 90629f828f80..60e5087e38be 100644 --- a/migration/trace-events +++ b/migration/trace-events @@ -37,7 +37,6 @@ savevm_send_ping(uint32_t val) "0x%x" savevm_send_postcopy_listen(void) "" savevm_send_postcopy_run(void) "" savevm_send_postcopy_resume(void) "" -savevm_send_colo_enable(void) "" savevm_send_recv_bitmap(char *name) "%s" savevm_send_switchover_start(void) "" savevm_state_setup(void) "" diff --git a/monitor/hmp.c b/monitor/hmp.c index 0a5bbf82197f..0e5913fabb1d 100644 --- a/monitor/hmp.c +++ b/monitor/hmp.c @@ -27,14 +27,18 @@ #include "hw/core/qdev.h" #include "monitor-internal.h" #include "monitor/hmp.h" +#include "monitor/hmp-target.h" #include "qobject/qdict.h" #include "qobject/qnum.h" +#include "qemu/bswap.h" #include "qemu/config-file.h" #include "qemu/ctype.h" #include "qemu/cutils.h" #include "qemu/log.h" #include "qemu/option.h" +#include "qemu/target-info.h" #include "qemu/units.h" +#include "exec/gdbstub.h" #include "system/block-backend.h" #include "trace.h" @@ -306,6 +310,46 @@ void hmp_help_cmd(Monitor *mon, const char *name) free_cmdline_args(args, nb_args); } +/* + * Set @pval to the value in the register identified by @name. + * return %true if the register is found, %false otherwise. + */ +static bool gdb_get_register(Monitor *mon, int64_t *pval, const char *name) +{ + g_autoptr(GArray) regs = NULL; + CPUState *cs = mon_get_cpu(mon); + + if (cs == NULL) { + return false; + } + + regs = gdb_get_register_list(cs); + + for (int i = 0; i < regs->len; i++) { + GDBRegDesc *reg = &g_array_index(regs, GDBRegDesc, i); + g_autoptr(GByteArray) buf = NULL; + int reg_size; + + if (!reg->name || g_strcmp0(name, reg->name)) { + continue; + } + + buf = g_byte_array_new(); + reg_size = gdb_read_register(cs, buf, reg->gdb_reg); + if (reg_size > sizeof(*pval)) { + return false; + } + + if (target_big_endian()) { + *pval = ldn_be_p(buf->data, reg_size); + } else { + *pval = ldn_le_p(buf->data, reg_size); + } + return true; + } + return false; +} + /*******************************************************************/ static const char *pch; @@ -338,7 +382,6 @@ static int64_t expr_unary(Monitor *mon) { int64_t n; char *p; - int ret; switch (*pch) { case '+': @@ -393,8 +436,8 @@ static int64_t expr_unary(Monitor *mon) pch++; } *q = 0; - ret = get_monitor_def(mon, ®, buf); - if (ret < 0) { + if (!gdb_get_register(mon, ®, buf) + && get_monitor_def(mon, ®, buf) < 0) { expr_error(mon, "unknown register"); } n = reg; diff --git a/monitor/monitor.c b/monitor/monitor.c index 1273eb726055..00b93ed61245 100644 --- a/monitor/monitor.c +++ b/monitor/monitor.c @@ -268,43 +268,6 @@ void monitor_printc(Monitor *mon, int c) monitor_printf(mon, "'"); } -/* - * Print to current monitor if we have one, else to stderr. - */ -int error_vprintf(const char *fmt, va_list ap) -{ - Monitor *cur_mon = monitor_cur(); - - if (cur_mon && !monitor_cur_is_qmp()) { - return monitor_vprintf(cur_mon, fmt, ap); - } - return vfprintf(stderr, fmt, ap); -} - -int error_vprintf_unless_qmp(const char *fmt, va_list ap) -{ - Monitor *cur_mon = monitor_cur(); - - if (!cur_mon) { - return vfprintf(stderr, fmt, ap); - } - if (!monitor_cur_is_qmp()) { - return monitor_vprintf(cur_mon, fmt, ap); - } - return -1; -} - -int error_printf_unless_qmp(const char *fmt, ...) -{ - va_list ap; - int ret; - - va_start(ap, fmt); - ret = error_vprintf_unless_qmp(fmt, ap); - va_end(ap); - return ret; -} - static MonitorQAPIEventConf monitor_qapi_event_conf[QAPI_EVENT__MAX] = { /* Limit guest-triggerable events to 1 per second */ [QAPI_EVENT_RTC_CHANGE] = { 1000 * SCALE_MS }, @@ -367,14 +330,33 @@ monitor_qapi_event_queue_no_reenter(QAPIEvent event, QDict *qdict) { MonitorQAPIEventConf *evconf; MonitorQAPIEventState *evstate; + bool throttled; assert(event < QAPI_EVENT__MAX); evconf = &monitor_qapi_event_conf[event]; trace_monitor_protocol_event_queue(event, qdict, evconf->rate); + throttled = evconf->rate; + + /* + * Rate limit BLOCK_IO_ERROR only for action != "stop". + * + * If the VM is stopped after an I/O error, this is important information + * for the management tool to keep track of the state of QEMU and we can't + * merge any events. At the same time, stopping the VM means that the guest + * can't send additional requests and the number of events is already + * limited, so we can do without rate limiting. + */ + if (event == QAPI_EVENT_BLOCK_IO_ERROR) { + QDict *data = qobject_to(QDict, qdict_get(qdict, "data")); + const char *action = qdict_get_str(data, "action"); + if (!strcmp(action, "stop")) { + throttled = false; + } + } QEMU_LOCK_GUARD(&monitor_lock); - if (!evconf->rate) { + if (!throttled) { /* Unthrottled event */ monitor_qapi_event_emit(event, qdict); } else { @@ -708,18 +690,22 @@ void monitor_cleanup(void) } } -static void monitor_qapi_event_init(void) +/* + * Initialize static vars that have no deps on external + * module initialization, and are required for external + * functions to call things like monitor_cur() + */ +static void __attribute__((__constructor__(QEMU_CONSTRUCTOR_EARLY))) +monitor_init_static(void) { + qemu_mutex_init(&monitor_lock); + coroutine_mon = g_hash_table_new(NULL, NULL); monitor_qapi_event_state = g_hash_table_new(qapi_event_throttle_hash, qapi_event_throttle_equal); } void monitor_init_globals(void) { - monitor_qapi_event_init(); - qemu_mutex_init(&monitor_lock); - coroutine_mon = g_hash_table_new(NULL, NULL); - /* * The dispatcher BH must run in the main loop thread, since we * have commands assuming that context. It would be nice to get diff --git a/net/eth.c b/net/eth.c index 3f680cc033a3..12ec316e240e 100644 --- a/net/eth.c +++ b/net/eth.c @@ -274,7 +274,7 @@ eth_strip_vlan_ex(const struct iovec *iov, int iovcnt, size_t iovoff, int index, uint16_t *payload_offset, uint16_t *tci) { struct vlan_header vlan_hdr; - uint16_t *new_ehdr_proto; + void *new_ehdr_proto; size_t new_ehdr_size; size_t copied; @@ -298,7 +298,7 @@ eth_strip_vlan_ex(const struct iovec *iov, int iovcnt, size_t iovoff, int index, return 0; } - if (copied < new_ehdr_size || be16_to_cpu(*new_ehdr_proto) != vet) { + if (copied < new_ehdr_size || lduw_be_p(new_ehdr_proto) != vet) { return 0; } @@ -308,7 +308,7 @@ eth_strip_vlan_ex(const struct iovec *iov, int iovcnt, size_t iovoff, int index, return 0; } - *new_ehdr_proto = vlan_hdr.h_proto; + stw_he_p(new_ehdr_proto, vlan_hdr.h_proto); *payload_offset = iovoff + new_ehdr_size + sizeof(vlan_hdr); *tci = be16_to_cpu(vlan_hdr.h_tci); diff --git a/net/passt.c b/net/passt.c index 9ed811a51426..4ff94ee509d4 100644 --- a/net/passt.c +++ b/net/passt.c @@ -102,7 +102,9 @@ static void net_passt_cleanup(NetClientState *nc) } #endif - kill(s->pid, SIGTERM); + if (s->pid > 0) { + kill(s->pid, SIGTERM); + } if (g_remove(s->pidfile) != 0) { warn_report("Failed to remove passt pidfile %s: %s", s->pidfile, strerror(errno)); @@ -268,8 +270,17 @@ static int net_passt_start_daemon(NetPasstState *s, int sock, Error **errp) return -1; } - if (g_subprocess_get_if_exited(daemon) && - g_subprocess_get_exit_status(daemon)) { + if (g_subprocess_get_if_exited(daemon)) { + gint status = g_subprocess_get_exit_status(daemon); + if (status) { + error_setg(errp, "Passt exited with code %d", status); + return -1; + } + } + + if (g_subprocess_get_if_signaled(daemon)) { + error_setg(errp, "Passt killed with signal %d", + g_subprocess_get_term_sig(daemon)); return -1; } diff --git a/net/vhost-vdpa.c b/net/vhost-vdpa.c index f4b1f0e9e010..3df6091274e5 100644 --- a/net/vhost-vdpa.c +++ b/net/vhost-vdpa.c @@ -116,6 +116,7 @@ static const uint64_t vdpa_svq_device_features = BIT_ULL(VIRTIO_NET_F_MRG_RXBUF) | BIT_ULL(VIRTIO_NET_F_STATUS) | BIT_ULL(VIRTIO_NET_F_CTRL_VQ) | + BIT_ULL(VIRTIO_NET_F_GSO) | BIT_ULL(VIRTIO_NET_F_CTRL_RX) | BIT_ULL(VIRTIO_NET_F_CTRL_VLAN) | BIT_ULL(VIRTIO_NET_F_CTRL_RX_EXTRA) | diff --git a/page-vary-common.c b/page-vary-common.c index ab77672dd412..ddd08633788e 100644 --- a/page-vary-common.c +++ b/page-vary-common.c @@ -20,34 +20,48 @@ #define IN_PAGE_VARY 1 #include "qemu/osdep.h" +#include "qemu/target-info-impl.h" #include "exec/page-vary.h" /* WARNING: This file must *not* be complied with -flto. */ TargetPageBits target_page; -bool set_preferred_target_page_bits_common(int bits) +bool set_preferred_target_page_bits(int bits) { - /* - * The target page size is the lowest common denominator for all - * the CPUs in the system, so we can only make it smaller, never - * larger. And we can't make it smaller once we've committed to - * a particular size. - */ - if (target_page.bits == 0 || target_page.bits > bits) { - if (target_page.decided) { - return false; + const TargetInfo *ti = target_info(); + + assert(bits >= TARGET_PAGE_BITS_MIN); + if (ti->page_bits_vary) { + + /* + * The target page size is the lowest common denominator for all + * the CPUs in the system, so we can only make it smaller, never + * larger. And we can't make it smaller once we've committed to + * a particular size. + */ + if (target_page.bits == 0 || target_page.bits > bits) { + if (target_page.decided) { + return false; + } + target_page.bits = bits; } - target_page.bits = bits; } return true; } -void finalize_target_page_bits_common(int min) +void finalize_target_page_bits(void) { - if (target_page.bits == 0) { - target_page.bits = min; + int bits = target_page.bits; + + if (bits == 0) { + const TargetInfo *ti = target_info(); + + bits = ti->page_bits_init; + assert(bits != 0); + target_page.bits = bits; } - target_page.mask = -1ull << target_page.bits; + + target_page.mask = -1ull << bits; target_page.decided = true; } diff --git a/page-vary-system.c b/page-vary-system.c new file mode 100644 index 000000000000..6c49c10e23ba --- /dev/null +++ b/page-vary-system.c @@ -0,0 +1,33 @@ +/* + * Variable page size handling -- system specific part. + * + * Copyright (c) 2003 Fabrice Bellard + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "qemu/osdep.h" +#include "exec/page-vary.h" +#include "exec/tlb-flags.h" +#include "qemu/target-info-impl.h" + +QEMU_BUILD_BUG_ON(TLB_FLAGS_MASK & ((1u < TARGET_PAGE_BITS_MIN) - 1)); + +int migration_legacy_page_bits(void) +{ + const TargetInfo *ti = target_info(); + + assert(ti->page_bits_init >= TARGET_PAGE_BITS_MIN); + return ti->page_bits_init; +} diff --git a/page-vary-target.c b/page-vary-target.c deleted file mode 100644 index 49a32b4fe517..000000000000 --- a/page-vary-target.c +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Variable page size handling -- target specific part. - * - * Copyright (c) 2003 Fabrice Bellard - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see . - */ - -#define IN_PAGE_VARY 1 - -#include "qemu/osdep.h" -#include "exec/page-vary.h" -#include "exec/target_page.h" - - -/* - * For system mode, the minimum comes from the number of bits - * required for maximum alignment (6) and the number of bits - * required for TLB_FLAGS_MASK (3). - * - * For user mode, TARGET_PAGE_BITS_VARY is a hack to allow the target - * page size to match the host page size. Mostly, this reduces the - * ordinary target page size to run on a host with 4KiB pages (i.e. x86). - * There is no true minimum required by the implementation, but keep the - * same minimum as for system mode for sanity. - * See linux-user/mmap.c, mmap_h_lt_g and mmap_h_gt_g. - */ -#define TARGET_PAGE_BITS_MIN 9 - -#ifndef TARGET_PAGE_BITS_VARY -QEMU_BUILD_BUG_ON(TARGET_PAGE_BITS < TARGET_PAGE_BITS_MIN); -#endif - -#ifndef CONFIG_USER_ONLY -#include "exec/tlb-flags.h" - -QEMU_BUILD_BUG_ON(TLB_FLAGS_MASK & ((1u < TARGET_PAGE_BITS_MIN) - 1)); - -int migration_legacy_page_bits(void) -{ -#ifdef TARGET_PAGE_BITS_VARY - QEMU_BUILD_BUG_ON(TARGET_PAGE_BITS_LEGACY < TARGET_PAGE_BITS_MIN); - return TARGET_PAGE_BITS_LEGACY; -#else - return TARGET_PAGE_BITS; -#endif -} -#endif - -bool set_preferred_target_page_bits(int bits) -{ - assert(bits >= TARGET_PAGE_BITS_MIN); -#ifdef TARGET_PAGE_BITS_VARY - return set_preferred_target_page_bits_common(bits); -#else - return true; -#endif -} - -void finalize_target_page_bits(void) -{ -#ifndef TARGET_PAGE_BITS_VARY - finalize_target_page_bits_common(TARGET_PAGE_BITS); -#elif defined(CONFIG_USER_ONLY) - assert(target_page.bits != 0); - finalize_target_page_bits_common(target_page.bits); -#else - finalize_target_page_bits_common(TARGET_PAGE_BITS_LEGACY); -#endif -} diff --git a/pc-bios/hppa-firmware.img b/pc-bios/hppa-firmware.img index d4c247152adc..09485fb0fe0c 100644 Binary files a/pc-bios/hppa-firmware.img and b/pc-bios/hppa-firmware.img differ diff --git a/pc-bios/hppa-firmware64.img b/pc-bios/hppa-firmware64.img index a9c24878f68e..f9c9b6c1de29 100644 Binary files a/pc-bios/hppa-firmware64.img and b/pc-bios/hppa-firmware64.img differ diff --git a/pc-bios/s390-ccw.img b/pc-bios/s390-ccw.img index ff60978d28c5..48ac78c89459 100644 Binary files a/pc-bios/s390-ccw.img and b/pc-bios/s390-ccw.img differ diff --git a/pc-bios/s390-ccw/Makefile b/pc-bios/s390-ccw/Makefile index a0f24c94a876..3e5dfb64d5de 100644 --- a/pc-bios/s390-ccw/Makefile +++ b/pc-bios/s390-ccw/Makefile @@ -34,7 +34,8 @@ QEMU_DGFLAGS = -MMD -MP -MT $@ -MF $(@D)/$(*F).d .PHONY : all clean build-all distclean OBJECTS = start.o main.o bootmap.o jump2ipl.o sclp.o menu.o netmain.o \ - virtio.o virtio-net.o virtio-scsi.o virtio-blkdev.o cio.o dasd-ipl.o + virtio.o virtio-net.o virtio-scsi.o virtio-blkdev.o cio.o dasd-ipl.o \ + virtio-ccw.o clp.o pci.o virtio-pci.o SLOF_DIR := $(SRC_PATH)/../../roms/SLOF diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c index 0f8baa01985d..420ee32eff8b 100644 --- a/pc-bios/s390-ccw/bootmap.c +++ b/pc-bios/s390-ccw/bootmap.c @@ -662,7 +662,7 @@ static int zipl_load_segment(ComponentEntry *entry) */ break; } - address = virtio_load_direct(cur_desc[0], cur_desc[1], 0, + address = virtio_load_direct(cur_desc[0], cur_desc[1], (void *)address); if (!address) { puts("zIPL load segment failed"); diff --git a/pc-bios/s390-ccw/clp.c b/pc-bios/s390-ccw/clp.c new file mode 100644 index 000000000000..ca9565d8dedc --- /dev/null +++ b/pc-bios/s390-ccw/clp.c @@ -0,0 +1,99 @@ +/* + * Call Logical Processor (CLP) architecture + * + * Copyright 2025 IBM Corp. + * Author(s): Jared Rossi + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "clp.h" +#include +#include + +int clp_pci(void *data) +{ + struct { uint8_t _[CLP_BLK_SIZE]; } *req = data; + int cc = 3; + + asm volatile ( + " .insn rrf,0xb9a00000,0,%[req],0,2\n" + " ipm %[cc]\n" + " srl %[cc],28\n" + : [cc] "+d" (cc), "+m" (*req) + : [req] "a" (req) + : "cc"); + if (cc) { + printf("CLP returned with non-zero condition code %d\n", cc); + } + return cc; +} + +/* + * Get the PCI function entry for a given function ID + * Return 0 on success, 1 if the FID is not found, or a negative RC on error + */ +int find_pci_function(uint32_t fid, ClpFhListEntry *entry) +{ + int count = 0; + int limit = PCI_MAX_FUNCTIONS; + ClpReqRspListPci rrb; + + rrb.request.hdr.len = sizeof(ClpReqListPci); + rrb.request.hdr.cmd = 0x02; + rrb.request.resume_token = 0; + rrb.response.hdr.len = sizeof(ClpRspListPci); + + do { + if (clp_pci(&rrb) || rrb.response.hdr.rsp != 0x0010) { + puts("Failed to list PCI functions"); + return -1; + } + + /* Resume token set when max entries are returned */ + if (rrb.response.resume_token) { + count = CLP_FH_LIST_NR_ENTRIES; + rrb.request.resume_token = rrb.response.resume_token; + } else { + count = (rrb.response.hdr.len - 32) / sizeof(ClpFhListEntry); + } + + limit -= count; + + for (int i = 0; i < count; i++) { + if (rrb.response.fh_list[i].fid == fid) { + memcpy(entry, &rrb.response.fh_list[i], sizeof(ClpFhListEntry)); + return 0; + } + } + + } while (rrb.request.resume_token && limit > 0); + + puts("No function entry found for FID!"); + + return 1; +} + +/* + * Enable the PCI function associated with a given handle + * Return 0 on success or a negative RC on error + */ +int enable_pci_function(uint32_t *fhandle) +{ + ClpReqRspSetPci rrb; + + rrb.request.hdr.len = sizeof(ClpReqSetPci); + rrb.request.hdr.cmd = 0x05; + rrb.request.fh = *fhandle; + rrb.request.oc = 0; + rrb.request.ndas = 1; + rrb.response.hdr.len = sizeof(ClpRspSetPci); + + if (clp_pci(&rrb) || rrb.response.hdr.rsp != 0x0010) { + puts("Failed to enable PCI function"); + return -1; + } + + *fhandle = rrb.response.fh; + return 0; +} diff --git a/pc-bios/s390-ccw/clp.h b/pc-bios/s390-ccw/clp.h new file mode 100644 index 000000000000..1ac2f8c177fa --- /dev/null +++ b/pc-bios/s390-ccw/clp.h @@ -0,0 +1,24 @@ +/* + * Call Logical Processor (CLP) architecture definitions + * + * Copyright 2025 IBM Corp. + * Author(s): Jared Rossi + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef CLP_H +#define CLP_H + +#ifndef QEMU_PACKED +#define QEMU_PACKED __attribute__((packed)) +#endif + +#include +#include + +int clp_pci(void *data); +int find_pci_function(uint32_t fid, ClpFhListEntry *entry); +int enable_pci_function(uint32_t *fhandle); + +#endif diff --git a/pc-bios/s390-ccw/iplb.h b/pc-bios/s390-ccw/iplb.h index 08f259ff3199..926e8eed5d4b 100644 --- a/pc-bios/s390-ccw/iplb.h +++ b/pc-bios/s390-ccw/iplb.h @@ -23,10 +23,6 @@ extern QemuIplParameters qipl; extern IplParameterBlock iplb __attribute__((__aligned__(PAGE_SIZE))); extern bool have_iplb; -#define S390_IPL_TYPE_FCP 0x00 -#define S390_IPL_TYPE_CCW 0x02 -#define S390_IPL_TYPE_QEMU_SCSI 0xff - static inline bool manage_iplb(IplParameterBlock *iplb, bool store) { register unsigned long addr asm("0") = (unsigned long) iplb; diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c index 76bf743900c7..26287cfd8112 100644 --- a/pc-bios/s390-ccw/main.c +++ b/pc-bios/s390-ccw/main.c @@ -18,6 +18,8 @@ #include "virtio.h" #include "virtio-scsi.h" #include "dasd-ipl.h" +#include "clp.h" +#include "virtio-pci.h" static SubChannelId blk_schid = { .one = 1 }; static char loadparm_str[LOADPARM_LEN + 1]; @@ -71,6 +73,7 @@ static int is_dev_possibly_bootable(int dev_no, int sch_no) bool is_virtio; Schib schib; int r; + VDev *vdev = virtio_get_device(); blk_schid.sch_no = sch_no; r = stsch_err(blk_schid, &schib); @@ -91,7 +94,8 @@ static int is_dev_possibly_bootable(int dev_no, int sch_no) * Note: we always have to run virtio_is_supported() here to make * sure that the vdev.senseid data gets pre-initialized correctly */ - is_virtio = virtio_is_supported(blk_schid); + vdev->schid = blk_schid; + is_virtio = virtio_is_supported(vdev); /* No specific devno given, just return whether the device is possibly bootable */ if (dev_no < 0) { @@ -149,7 +153,22 @@ static bool find_subch(int dev_no) return false; } -static void menu_setup(void) +static bool find_fid(uint32_t fid) +{ + ClpFhListEntry entry; + VDev *vdev = virtio_get_device(); + + if (find_pci_function(fid, &entry)) { + return false; + } + + vdev->pci_fh = entry.fh; + virtio_pci_id2type(vdev, entry.device_id); + + return vdev->dev_type != 0; +} + +static void menu_setup(VDev *vdev) { if (memcmp(loadparm_str, LOADPARM_PROMPT, LOADPARM_LEN) == 0) { menu_set_parms(QIPL_FLAG_BM_OPTS_CMD, 0); @@ -162,11 +181,13 @@ static void menu_setup(void) return; } - switch (iplb.pbt) { + switch (vdev->ipl_type) { case S390_IPL_TYPE_CCW: case S390_IPL_TYPE_QEMU_SCSI: menu_set_parms(qipl.qipl_flags & BOOT_MENU_FLAG_MASK, qipl.boot_menu_timeout); + /* fall through */ + default: return; } } @@ -190,6 +211,7 @@ static void css_setup(void) static void boot_setup(void) { char lpmsg[] = "LOADPARM=[________]\n"; + VDev *vdev = virtio_get_device(); if (have_iplb && memcmp(iplb.loadparm, NO_LOADPARM, LOADPARM_LEN) != 0) { ebcdic_to_ascii((char *) iplb.loadparm, loadparm_str, LOADPARM_LEN); @@ -198,7 +220,10 @@ static void boot_setup(void) } if (have_iplb) { - menu_setup(); + vdev->ipl_type = iplb.pbt; + menu_setup(vdev); + } else { + vdev->ipl_type = QEMU_DEFAULT_IPL; } memcpy(lpmsg + 10, loadparm_str, 8); @@ -216,7 +241,7 @@ static bool find_boot_device(void) VDev *vdev = virtio_get_device(); bool found = false; - switch (iplb.pbt) { + switch (vdev->ipl_type) { case S390_IPL_TYPE_CCW: vdev->scsi_device_selected = false; debug_print_int("device no. ", iplb.ccw.devno); @@ -232,6 +257,9 @@ static bool find_boot_device(void) blk_schid.ssid = iplb.scsi.ssid & 0x3; found = find_subch(iplb.scsi.devno); break; + case S390_IPL_TYPE_PCI: + found = find_fid(iplb.pci.fid); + break; default: puts("Unsupported IPLB"); } @@ -245,15 +273,15 @@ static int virtio_setup(void) vdev->is_cdrom = false; int ret; - switch (vdev->senseid.cu_model) { + switch (vdev->dev_type) { case VIRTIO_ID_NET: puts("Network boot device detected"); return 0; case VIRTIO_ID_BLOCK: - ret = virtio_blk_setup_device(blk_schid); + ret = virtio_blk_setup_device(vdev); break; case VIRTIO_ID_SCSI: - ret = virtio_scsi_setup_device(blk_schid); + ret = virtio_scsi_setup_device(vdev); break; default: puts("\n! No IPL device available !\n"); @@ -268,7 +296,7 @@ static int virtio_setup(void) return ret; } -static void ipl_boot_device(void) +static void ipl_ccw_device(void) { switch (cutype) { case CU_TYPE_DASD_3990: @@ -277,11 +305,49 @@ static void ipl_boot_device(void) break; case CU_TYPE_VIRTIO: if (virtio_setup() == 0) { - zipl_load(); + zipl_load(); /* only return on error */ + virtio_reset(virtio_get_device()); + } + break; + default: + printf("Cannot boot CCW device with cu type 0x%X\n", cutype); + } +} + +static void ipl_pci_device(void) +{ + VDev *vdev = virtio_get_device(); + vdev->is_cdrom = false; + vdev->scsi_device_selected = false; + + if (virtio_pci_setup_device()) { + return; + } + + switch (vdev->dev_type) { + case VIRTIO_ID_BLOCK: + if (virtio_setup() == 0) { + zipl_load(); /* only return on error */ + virtio_reset(virtio_get_device()); } break; default: - printf("Attempting to boot from unexpected device type 0x%X\n", cutype); + printf("Cannot boot PCI device type 0x%X\n", vdev->dev_type); + } +} + +static void ipl_boot_device(void) +{ + switch (virtio_get_device()->ipl_type) { + case S390_IPL_TYPE_QEMU_SCSI: + case S390_IPL_TYPE_CCW: + ipl_ccw_device(); + break; + case S390_IPL_TYPE_PCI: + ipl_pci_device(); + break; + default: + puts("Unrecognized IPL type!"); } } diff --git a/pc-bios/s390-ccw/netmain.c b/pc-bios/s390-ccw/netmain.c index a9521dff4162..651cedf6efaf 100644 --- a/pc-bios/s390-ccw/netmain.c +++ b/pc-bios/s390-ccw/netmain.c @@ -500,7 +500,7 @@ static bool find_net_dev(Schib *schib, int dev_no) continue; } enable_subchannel(net_schid); - if (!virtio_is_supported(net_schid)) { + if (!virtio_is_supported(virtio_get_device())) { continue; } if (virtio_get_device_type() != VIRTIO_ID_NET) { diff --git a/pc-bios/s390-ccw/pci.c b/pc-bios/s390-ccw/pci.c new file mode 100644 index 000000000000..247070e5f07d --- /dev/null +++ b/pc-bios/s390-ccw/pci.c @@ -0,0 +1,118 @@ +/* + * s390x PCI functionality + * + * Copyright 2025 IBM Corp. + * Author(s): Jared Rossi + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "clp.h" +#include "pci.h" +#include "bswap.h" +#include +#include + +/* PCI load */ +static inline int pcilg(uint64_t *data, uint64_t req, uint64_t offset, + uint8_t *status) +{ + union register_pair req_off = {.even = req, .odd = offset}; + int cc = -1; + uint64_t __data; + + asm volatile ( + " .insn rre,0xb9d20000,%[data],%[req_off]\n" + " ipm %[cc]\n" + " srl %[cc],28\n" + : [cc] "+d" (cc), [data] "=d" (__data), + [req_off] "+d" (req_off.pair) :: "cc"); + *status = req_off.even >> 24 & 0xff; + *data = __data; + return cc; +} + +/* PCI store */ +static inline int pcistg(uint64_t data, uint64_t req, uint64_t offset, + uint8_t *status) +{ + union register_pair req_off = {.even = req, .odd = offset}; + int cc = -1; + + asm volatile ( + " .insn rre,0xb9d00000,%[data],%[req_off]\n" + " ipm %[cc]\n" + " srl %[cc],28\n" + : [cc] "+d" (cc), [req_off] "+d" (req_off.pair) + : [data] "d" (data) + : "cc"); + *status = req_off.even >> 24 & 0xff; + return cc; +} + +int pci_write(uint32_t fhandle, uint64_t offset, uint8_t pcias, uint64_t data, + uint8_t len) +{ + + uint64_t req = ZPCI_CREATE_REQ(fhandle, pcias, len); + uint8_t status; + int rc; + + /* len must be non-zero power of 2 with a maximum of 8 bytes per write */ + switch (len) { + case 1: + case 2: + case 4: + case 8: + rc = pcistg(data, req, offset, &status); + break; + default: + return -1; + } + + /* Error condition detected */ + if (rc != 0) { + printf("PCI store failed with status condition %d, return code %d\n", + status, rc); + return -1; + } + + return 0; +} + +int pci_read(uint32_t fhandle, uint64_t offset, uint8_t pcias, void *buf, + uint8_t len) +{ + uint64_t req, data; + uint8_t status; + int rc; + + req = ZPCI_CREATE_REQ(fhandle, pcias, len); + rc = pcilg(&data, req, offset, &status); + + /* Error condition detected */ + if (rc != 0) { + printf("PCI load failed with status condition %d, return code %d\n", + status, rc); + return -1; + } + + switch (len) { + case 1: + *(uint8_t *)buf = data; + break; + case 2: + *(uint16_t *)buf = data; + break; + case 4: + *(uint32_t *)buf = data; + break; + case 8: + *(uint64_t *)buf = data; + break; + default: + return -1; + } + + return 0; +} diff --git a/pc-bios/s390-ccw/pci.h b/pc-bios/s390-ccw/pci.h new file mode 100644 index 000000000000..b9eb86b2e670 --- /dev/null +++ b/pc-bios/s390-ccw/pci.h @@ -0,0 +1,39 @@ +/* + * s390x PCI definitions + * + * Copyright 2025 IBM Corp. + * Author(s): Jared Rossi + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef PCI_H +#define PCI_H + +#include +#include +#include "clp.h" + +#define ZPCI_CREATE_REQ(handle, space, len) \ + ((uint64_t) handle << 32 | space << 16 | len) + +union register_pair { + unsigned __int128 pair; + struct { + unsigned long even; + unsigned long odd; + }; +}; + +#define PCI_CFGBAR 0xF /* Base Address Register for config space */ +#define PCI_CMD_REG 0x4 /* Offset of command register */ +#define PCI_CAPABILITY_LIST 0x34 /* Offset of first capability list entry */ + +#define PCI_BUS_MASTER_MASK 0x0020 /* LE bit 3 of 16 bit register */ + +int pci_write(uint32_t fhandle, uint64_t offset, uint8_t pcias, uint64_t data, + uint8_t len); +int pci_read(uint32_t fhandle, uint64_t offset, uint8_t pcias, void *buf, + uint8_t len); + +#endif diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h index b1dc35cdedf3..1e1f71775ecd 100644 --- a/pc-bios/s390-ccw/s390-ccw.h +++ b/pc-bios/s390-ccw/s390-ccw.h @@ -66,13 +66,6 @@ void sclp_setup(void); void sclp_get_loadparm_ascii(char *loadparm); int sclp_read(char *str, size_t count); -/* virtio.c */ -unsigned long virtio_load_direct(unsigned long rec_list1, unsigned long rec_list2, - unsigned long subchan_id, void *load_addr); -bool virtio_is_supported(SubChannelId schid); -int virtio_blk_setup_device(SubChannelId schid); -int virtio_read(unsigned long sector, void *load_addr); - /* bootmap.c */ void zipl_load(void); @@ -119,7 +112,7 @@ static inline void fill_hex_val(char *out, void *ptr, unsigned size) static inline void debug_print_int(const char *desc, u64 addr) { #ifdef DEBUG - printf("%s 0x%X\n", desc, addr); + printf("%s 0x%llx\n", desc, addr); #endif } diff --git a/pc-bios/s390-ccw/virtio-blkdev.c b/pc-bios/s390-ccw/virtio-blkdev.c index 7b2d1e20f4d9..98b6cec3a09c 100644 --- a/pc-bios/s390-ccw/virtio-blkdev.c +++ b/pc-bios/s390-ccw/virtio-blkdev.c @@ -12,10 +12,23 @@ #include "s390-ccw.h" #include "virtio.h" #include "virtio-scsi.h" +#include "virtio-ccw.h" +#include "virtio-pci.h" +#include "bswap.h" #define VIRTIO_BLK_F_GEOMETRY (1 << 4) #define VIRTIO_BLK_F_BLK_SIZE (1 << 6) +/* + * Format header for little endian IPL + */ +static void fmt_blk_hdr_le(VirtioBlkOuthdr *hdr) +{ + hdr->type = bswap32(hdr->type); + hdr->ioprio = bswap32(hdr->ioprio); + hdr->sector = bswap64(hdr->sector); +} + static int virtio_blk_read_many(VDev *vdev, unsigned long sector, void *load_addr, int sec_num) { @@ -28,6 +41,10 @@ static int virtio_blk_read_many(VDev *vdev, unsigned long sector, void *load_add out_hdr.ioprio = 99; out_hdr.sector = virtio_sector_adjust(sector); + if (!be_ipl()) { + fmt_blk_hdr_le(&out_hdr); + } + vring_send_buf(vr, &out_hdr, sizeof(out_hdr), VRING_DESC_F_NEXT); /* This is where we want to receive data */ @@ -42,7 +59,7 @@ static int virtio_blk_read_many(VDev *vdev, unsigned long sector, void *load_add /* Now we can tell the host to read */ vring_wait_reply(); - if (drain_irqs(vr->schid)) { + if (drain_irqs()) { /* Well, whatever status is supposed to contain... */ status = 1; } @@ -53,18 +70,18 @@ int virtio_read_many(unsigned long sector, void *load_addr, int sec_num) { VDev *vdev = virtio_get_device(); - switch (vdev->senseid.cu_model) { + switch (vdev->dev_type) { case VIRTIO_ID_BLOCK: return virtio_blk_read_many(vdev, sector, load_addr, sec_num); case VIRTIO_ID_SCSI: return virtio_scsi_read_many(vdev, sector, load_addr, sec_num); + default: + return -1; } - - return -1; } unsigned long virtio_load_direct(unsigned long rec_list1, unsigned long rec_list2, - unsigned long subchan_id, void *load_addr) + void *load_addr) { u8 status; int sec = rec_list1; @@ -119,7 +136,7 @@ void virtio_assume_iso9660(void) { VDev *vdev = virtio_get_device(); - switch (vdev->senseid.cu_model) { + switch (vdev->dev_type) { case VIRTIO_ID_BLOCK: vdev->guessed_disk_nature = VIRTIO_GDN_SCSI; vdev->config.blk.blk_size = VIRTIO_ISO_BLOCK_SIZE; @@ -129,6 +146,8 @@ void virtio_assume_iso9660(void) case VIRTIO_ID_SCSI: vdev->scsi_block_size = VIRTIO_ISO_BLOCK_SIZE; break; + default: + return; } } @@ -139,13 +158,15 @@ void virtio_assume_eckd(void) vdev->guessed_disk_nature = VIRTIO_GDN_DASD; vdev->blk_factor = 1; vdev->config.blk.physical_block_exp = 0; - switch (vdev->senseid.cu_model) { + switch (vdev->dev_type) { case VIRTIO_ID_BLOCK: vdev->config.blk.blk_size = VIRTIO_DASD_DEFAULT_BLOCK_SIZE; break; case VIRTIO_ID_SCSI: vdev->config.blk.blk_size = vdev->scsi_block_size; break; + default: + break; } vdev->config.blk.geometry.heads = 15; vdev->config.blk.geometry.sectors = @@ -162,50 +183,52 @@ bool virtio_ipl_disk_is_valid(void) return true; } - return (vdev->senseid.cu_model == VIRTIO_ID_BLOCK || - vdev->senseid.cu_model == VIRTIO_ID_SCSI) && - blksize >= 512 && blksize <= 4096; + return (vdev->dev_type == VIRTIO_ID_BLOCK || vdev->dev_type == VIRTIO_ID_SCSI) + && blksize >= 512 && blksize <= 4096; } int virtio_get_block_size(void) { VDev *vdev = virtio_get_device(); - switch (vdev->senseid.cu_model) { + switch (vdev->dev_type) { case VIRTIO_ID_BLOCK: return vdev->config.blk.blk_size; case VIRTIO_ID_SCSI: return vdev->scsi_block_size; + default: + return 0; } - return 0; } uint8_t virtio_get_heads(void) { VDev *vdev = virtio_get_device(); - switch (vdev->senseid.cu_model) { + switch (vdev->dev_type) { case VIRTIO_ID_BLOCK: return vdev->config.blk.geometry.heads; case VIRTIO_ID_SCSI: return vdev->guessed_disk_nature == VIRTIO_GDN_DASD ? vdev->config.blk.geometry.heads : 255; + default: + return 0; } - return 0; } uint8_t virtio_get_sectors(void) { VDev *vdev = virtio_get_device(); - switch (vdev->senseid.cu_model) { + switch (vdev->dev_type) { case VIRTIO_ID_BLOCK: return vdev->config.blk.geometry.sectors; case VIRTIO_ID_SCSI: return vdev->guessed_disk_nature == VIRTIO_GDN_DASD ? vdev->config.blk.geometry.sectors : 63; + default: + return 0; } - return 0; } uint64_t virtio_get_blocks(void) @@ -213,24 +236,29 @@ uint64_t virtio_get_blocks(void) VDev *vdev = virtio_get_device(); const uint64_t factor = virtio_get_block_size() / VIRTIO_SECTOR_SIZE; - switch (vdev->senseid.cu_model) { + switch (vdev->dev_type) { case VIRTIO_ID_BLOCK: return vdev->config.blk.capacity / factor; case VIRTIO_ID_SCSI: return vdev->scsi_last_block / factor; + default: + return 0; } - return 0; } -int virtio_blk_setup_device(SubChannelId schid) +int virtio_blk_setup_device(VDev *vdev) { - VDev *vdev = virtio_get_device(); - vdev->guest_features[0] = VIRTIO_BLK_F_GEOMETRY | VIRTIO_BLK_F_BLK_SIZE; - vdev->schid = schid; - virtio_setup_ccw(vdev); puts("Using virtio-blk."); - return 0; + switch (vdev->ipl_type) { + case S390_IPL_TYPE_QEMU_SCSI: + case S390_IPL_TYPE_CCW: + return virtio_ccw_setup(vdev); + case S390_IPL_TYPE_PCI: + return virtio_pci_setup(vdev); + default: + return 1; + } } diff --git a/pc-bios/s390-ccw/virtio-ccw.c b/pc-bios/s390-ccw/virtio-ccw.c new file mode 100644 index 000000000000..5cb2158ed209 --- /dev/null +++ b/pc-bios/s390-ccw/virtio-ccw.c @@ -0,0 +1,239 @@ +/* + * Virtio functionality for CCW devices + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (c) 2013 Alexander Graf + * Copyright 2025 IBM Corp. + * + * Author(s): Jared Rossi + */ + +#include +#include "s390-ccw.h" +#include "cio.h" +#include "virtio.h" +#include "virtio-ccw.h" +#include "virtio-scsi.h" +#include "bswap.h" +#include "helper.h" +#include "s390-time.h" + +/* virtio spec v1.0 para 4.3.3.2 */ +static long kvm_hypercall(unsigned long nr, unsigned long param1, + unsigned long param2, unsigned long param3) +{ + register unsigned long r_nr asm("1") = nr; + register unsigned long r_param1 asm("2") = param1; + register unsigned long r_param2 asm("3") = param2; + register unsigned long r_param3 asm("4") = param3; + register long retval asm("2"); + + asm volatile ("diag %%r2,%%r4,0x500" + : "=d" (retval) + : "d" (r_nr), "0" (r_param1), "r"(r_param2), "d"(r_param3) + : "memory", "cc"); + + return retval; +} + +static int run_ccw(VDev *vdev, int cmd, void *ptr, int len, bool sli) +{ + Ccw1 ccw = {}; + + ccw.cmd_code = cmd; + ccw.cda = (long)ptr; + ccw.count = len; + + if (sli) { + ccw.flags |= CCW_FLAG_SLI; + } + + return do_cio(vdev->schid, vdev->senseid.cu_type, ptr2u32(&ccw), CCW_FMT1); +} + +bool virtio_ccw_is_supported(VDev *vdev) +{ + memset(&vdev->senseid, 0, sizeof(vdev->senseid)); + + /* + * Run sense id command. + * The size of the senseid data differs between devices (notably, + * between virtio devices and dasds), so specify the largest possible + * size and suppress the incorrect length indication for smaller sizes. + */ + if (run_ccw(vdev, CCW_CMD_SENSE_ID, &vdev->senseid, sizeof(vdev->senseid), + true)) { + return false; + } + + vdev->dev_type = vdev->senseid.cu_model; + + if (vdev->senseid.cu_type == 0x3832) { + switch (vdev->dev_type) { + case VIRTIO_ID_BLOCK: + case VIRTIO_ID_SCSI: + case VIRTIO_ID_NET: + return true; + default: + return false; + } + } + return false; +} + +int drain_irqs_ccw(SubChannelId schid) +{ + Irb irb = {}; + int r = 0; + + while (1) { + /* FIXME: make use of TPI, for that enable subchannel and isc */ + if (tsch(schid, &irb)) { + /* Might want to differentiate error codes later on. */ + if (irb.scsw.cstat) { + r = -EIO; + } else if (irb.scsw.dstat != 0xc) { + r = -EIO; + } + return r; + } + } +} + +long virtio_ccw_notify(SubChannelId schid, int vq_idx, long cookie) +{ + return kvm_hypercall(KVM_S390_VIRTIO_CCW_NOTIFY, *(u32 *)&schid, + vq_idx, cookie); +} + +int virtio_ccw_run(VDev *vdev, int vqid, VirtioCmd *cmd) +{ + VRing *vr = &vdev->vrings[vqid]; + int i = 0; + + do { + vring_send_buf(vr, cmd[i].data, cmd[i].size, + cmd[i].flags | (i ? VRING_HIDDEN_IS_CHAIN : 0)); + } while (cmd[i++].flags & VRING_DESC_F_NEXT); + + vring_wait_reply(); + if (drain_irqs()) { + return -1; + } + return 0; +} + +int virtio_ccw_reset(VDev *vdev) +{ + return run_ccw(vdev, CCW_CMD_VDEV_RESET, NULL, 0, false); +} + +int virtio_ccw_setup(VDev *vdev) +{ + int i, cfg_size = 0; + uint8_t status; + struct VirtioFeatureDesc { + uint32_t features; + uint8_t index; + } __attribute__((packed)) feats; + + if (!virtio_ccw_is_supported(vdev)) { + puts("Virtio unsupported for this device ID"); + return -ENODEV; + } + /* device ID has been established now */ + + vdev->config.blk.blk_size = 0; /* mark "illegal" - setup started... */ + vdev->guessed_disk_nature = VIRTIO_GDN_NONE; + + virtio_reset(vdev); + + status = VIRTIO_CONFIG_S_ACKNOWLEDGE; + if (run_ccw(vdev, CCW_CMD_WRITE_STATUS, &status, sizeof(status), false)) { + puts("Could not write ACKNOWLEDGE status to host"); + return -EIO; + } + + switch (vdev->dev_type) { + case VIRTIO_ID_NET: + vdev->nr_vqs = 2; + vdev->cmd_vr_idx = 0; + cfg_size = sizeof(vdev->config.net); + break; + case VIRTIO_ID_BLOCK: + vdev->nr_vqs = 1; + vdev->cmd_vr_idx = 0; + cfg_size = sizeof(vdev->config.blk); + break; + case VIRTIO_ID_SCSI: + vdev->nr_vqs = 3; + vdev->cmd_vr_idx = VR_REQUEST; + cfg_size = sizeof(vdev->config.scsi); + break; + default: + puts("Unsupported virtio device"); + return -ENODEV; + } + + status |= VIRTIO_CONFIG_S_DRIVER; + if (run_ccw(vdev, CCW_CMD_WRITE_STATUS, &status, sizeof(status), false)) { + puts("Could not write DRIVER status to host"); + return -EIO; + } + + /* Feature negotiation */ + for (i = 0; i < ARRAY_SIZE(vdev->guest_features); i++) { + feats.features = 0; + feats.index = i; + if (run_ccw(vdev, CCW_CMD_READ_FEAT, &feats, sizeof(feats), false)) { + puts("Could not get features bits"); + return -EIO; + } + + vdev->guest_features[i] &= bswap32(feats.features); + feats.features = bswap32(vdev->guest_features[i]); + if (run_ccw(vdev, CCW_CMD_WRITE_FEAT, &feats, sizeof(feats), false)) { + puts("Could not set features bits"); + return -EIO; + } + } + + if (run_ccw(vdev, CCW_CMD_READ_CONF, &vdev->config, cfg_size, false)) { + puts("Could not get virtio device configuration"); + return -EIO; + } + + for (i = 0; i < vdev->nr_vqs; i++) { + VqInfo info = { + .queue = (unsigned long long) virtio_get_ring_area(i), + .align = KVM_S390_VIRTIO_RING_ALIGN, + .index = i, + .num = 0, + }; + VqConfig config = { + .index = i, + .num = 0, + }; + + if (run_ccw(vdev, CCW_CMD_READ_VQ_CONF, &config, sizeof(config), + false)) { + puts("Could not get virtio device VQ config"); + return -EIO; + } + info.num = config.num; + vring_init(&vdev->vrings[i], &info); + if (run_ccw(vdev, CCW_CMD_SET_VQ, &info, sizeof(info), false)) { + puts("Cannot set VQ info"); + return -EIO; + } + } + + status |= VIRTIO_CONFIG_S_DRIVER_OK; + if (run_ccw(vdev, CCW_CMD_WRITE_STATUS, &status, sizeof(status), false)) { + puts("Could not write DRIVER_OK status to host"); + return -EIO; + } + + return 0; +} diff --git a/pc-bios/s390-ccw/virtio-ccw.h b/pc-bios/s390-ccw/virtio-ccw.h new file mode 100644 index 000000000000..a506767eaa85 --- /dev/null +++ b/pc-bios/s390-ccw/virtio-ccw.h @@ -0,0 +1,24 @@ +/* + * Virtio definitions for CCW devices + * + * Copyright 2025 IBM Corp. + * Author(s): Jared Rossi + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef VIRTIO_CCW_H +#define VIRTIO_CCW_H + +/* main.c */ +extern SubChannelId blk_schid; + +/* virtio-ccw.c */ +int drain_irqs_ccw(SubChannelId schid); +bool virtio_ccw_is_supported(VDev *vdev); +int virtio_ccw_run(VDev *vdev, int vqid, VirtioCmd *cmd); +long virtio_ccw_notify(SubChannelId schid, int vq_idx, long cookie); +int virtio_ccw_setup(VDev *vdev); +int virtio_ccw_reset(VDev *vdev); + +#endif diff --git a/pc-bios/s390-ccw/virtio-net.c b/pc-bios/s390-ccw/virtio-net.c index 301445bf978a..f58f7ffc55b4 100644 --- a/pc-bios/s390-ccw/virtio-net.c +++ b/pc-bios/s390-ccw/virtio-net.c @@ -19,6 +19,7 @@ #include #include "s390-ccw.h" #include "virtio.h" +#include "virtio-ccw.h" #include "s390-time.h" #include "helper.h" @@ -54,7 +55,7 @@ int virtio_net_init(void *mac_addr) rx_last_idx = 0; vdev->guest_features[0] = VIRTIO_NET_F_MAC_BIT; - virtio_setup_ccw(vdev); + virtio_ccw_setup(vdev); if (!(vdev->guest_features[0] & VIRTIO_NET_F_MAC_BIT)) { puts("virtio-net device does not support the MAC address feature"); @@ -88,7 +89,7 @@ int send(int fd, const void *buf, int len, int flags) while (!vr_poll(txvq)) { yield(); } - if (drain_irqs(txvq->schid)) { + if (drain_irqs()) { puts("send: drain irqs failed"); return -1; } diff --git a/pc-bios/s390-ccw/virtio-pci.c b/pc-bios/s390-ccw/virtio-pci.c new file mode 100644 index 000000000000..53bdb52e76ab --- /dev/null +++ b/pc-bios/s390-ccw/virtio-pci.c @@ -0,0 +1,433 @@ +/* + * Functionality for virtio-pci + * + * Copyright 2025 IBM Corp. + * Author(s): Jared Rossi + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "clp.h" +#include "pci.h" +#include "helper.h" +#include "virtio.h" +#include "bswap.h" +#include "virtio-pci.h" +#include "s390-time.h" +#include + +/* Variable offsets used for reads/writes to modern memory regions */ +VirtioPciCap c_cap; /* Common capabilities */ +VirtioPciCap d_cap; /* Device capabilities */ +VirtioPciCap n_cap; /* Notify capabilities */ +uint32_t notify_mult; +uint16_t q_notify_offset; + +static int virtio_pci_set_status(uint8_t status) +{ + int rc = vpci_write_byte(c_cap.off + VPCI_C_OFFSET_STATUS, c_cap.bar, status); + if (rc) { + puts("Failed to write virtio-pci status"); + return -EIO; + } + + return 0; +} + +static int virtio_pci_get_status(uint8_t *status) +{ + int rc = vpci_read_byte(c_cap.off + VPCI_C_OFFSET_STATUS, c_cap.bar, status); + if (rc) { + puts("Failed to read virtio-pci status"); + return -EIO; + } + + return 0; +} + +/* virtio spec v1.3 section 4.1.2.1 */ +void virtio_pci_id2type(VDev *vdev, uint16_t device_id) +{ + switch (device_id) { + case 0x1042: + case 0x1001: + vdev->dev_type = VIRTIO_ID_BLOCK; + break; + default: + vdev->dev_type = 0; + } +} + +int virtio_pci_reset(VDev *vdev) +{ + int rc; + uint8_t status = 0; + + rc = virtio_pci_set_status(status); + rc |= virtio_pci_get_status(&status); + + if (rc || status) { + puts("Failed to reset virtio-pci device"); + return 1; + } + + return 0; +} + +long virtio_pci_notify(int vq_id) +{ + uint32_t offset = n_cap.off + notify_mult * q_notify_offset; + return vpci_bswap16_write(offset, n_cap.bar, (uint16_t) vq_id); +} + +/* + * Wrappers to byte swap common data sizes then write + */ +int vpci_write_byte(uint64_t offset, uint8_t pcias, uint8_t data) +{ + return pci_write(virtio_get_device()->pci_fh, offset, pcias, (uint64_t) data, 1); +} + +int vpci_bswap16_write(uint64_t offset, uint8_t pcias, uint16_t data) +{ + uint64_t le_data = bswap16(data); + return pci_write(virtio_get_device()->pci_fh, offset, pcias, le_data, 2); +} + +int vpci_bswap32_write(uint64_t offset, uint8_t pcias, uint32_t data) +{ + uint64_t le_data = bswap32(data); + return pci_write(virtio_get_device()->pci_fh, offset, pcias, le_data, 4); +} + +int vpci_bswap64_write(uint64_t offset, uint8_t pcias, uint64_t data) +{ + uint64_t le_data = bswap64(data); + return pci_write(virtio_get_device()->pci_fh, offset, pcias, le_data, 8); +} + +/* + * Wrappers to read common data sizes then byte swap + */ +int vpci_read_byte(uint64_t offset, uint8_t pcias, uint8_t *buf) +{ + return pci_read(virtio_get_device()->pci_fh, offset, pcias, buf, 1); +} + +int vpci_read_bswap16(uint64_t offset, uint8_t pcias, uint16_t *buf) +{ + int rc = pci_read(virtio_get_device()->pci_fh, offset, pcias, buf, 2); + *buf = bswap16(*buf); + return rc; +} + +int vpci_read_bswap32(uint64_t offset, uint8_t pcias, uint32_t *buf) +{ + int rc = pci_read(virtio_get_device()->pci_fh, offset, pcias, buf, 4); + *buf = bswap32(*buf); + return rc; +} + +int vpci_read_bswap64(uint64_t offset, uint8_t pcias, uint64_t *buf) +{ + int rc = pci_read(virtio_get_device()->pci_fh, offset, pcias, buf, 8); + *buf = bswap64(*buf); + return rc; +} + +/* + * Read to an arbitrary length buffer without byte swapping + */ +int vpci_read_flex(uint64_t offset, uint8_t pcias, void *buf, int len) +{ + uint8_t readlen; + int rc; + int remaining = len; + + /* Read bytes in powers of 2, up to a maximum of 8 bytes per read */ + while (remaining) { + for (int i = 3; i >= 0; i--) { + readlen = 1 << i; + if (remaining >= readlen) { + break; + } + } + + rc = pci_read(virtio_get_device()->pci_fh, offset, pcias, buf, readlen); + if (rc) { + return -1; + } + + remaining -= readlen; + buf += readlen; + offset += readlen; + } + + return 0; +} + +static int vpci_set_selected_vq(uint16_t queue_num) +{ + return vpci_bswap16_write(c_cap.off + VPCI_C_OFFSET_Q_SELECT, c_cap.bar, queue_num); +} + +static int vpci_set_queue_enable(uint16_t enabled) +{ + return vpci_bswap16_write(c_cap.off + VPCI_C_OFFSET_Q_ENABLE, c_cap.bar, enabled); +} + +static int set_pci_vq_addr(uint64_t config_off, void *addr) +{ + return vpci_bswap64_write(c_cap.off + config_off, c_cap.bar, (uint64_t) addr); +} + +static int virtio_pci_get_blk_config(void) +{ + VirtioBlkConfig *cfg = &virtio_get_device()->config.blk; + int rc = vpci_read_flex(d_cap.off, d_cap.bar, cfg, sizeof(VirtioBlkConfig)); + + /* single byte fields are not touched */ + cfg->capacity = bswap64(cfg->capacity); + cfg->size_max = bswap32(cfg->size_max); + cfg->seg_max = bswap32(cfg->seg_max); + + cfg->geometry.cylinders = bswap16(cfg->geometry.cylinders); + + cfg->blk_size = bswap32(cfg->blk_size); + cfg->min_io_size = bswap16(cfg->min_io_size); + cfg->opt_io_size = bswap32(cfg->opt_io_size); + + return rc; +} + +static int virtio_pci_negotiate(void) +{ + int i, rc; + VDev *vdev = virtio_get_device(); + struct VirtioFeatureDesc { + uint32_t features; + uint8_t index; + } __attribute__((packed)) feats; + + for (i = 0; i < ARRAY_SIZE(vdev->guest_features); i++) { + feats.features = 0; + feats.index = i; + + rc = vpci_bswap32_write(c_cap.off + VPCI_C_OFFSET_DFSELECT, c_cap.bar, + feats.index); + rc |= vpci_read_flex(c_cap.off + VPCI_C_OFFSET_DF, c_cap.bar, &feats, 4); + + vdev->guest_features[i] &= bswap32(feats.features); + feats.features = vdev->guest_features[i]; + + + rc |= vpci_bswap32_write(c_cap.off + VPCI_C_OFFSET_GFSELECT, c_cap.bar, + feats.index); + rc |= vpci_bswap32_write(c_cap.off + VPCI_C_OFFSET_GF, c_cap.bar, + feats.features); + } + + return rc; +} + +/* + * Find the position of the capability config within PCI configuration + * space for a given cfg type. Return the position if found, otherwise 0. + */ +static uint8_t virtio_pci_find_cap_pos(uint8_t cfg_type) +{ + uint8_t next, cfg; + int rc; + + rc = vpci_read_byte(PCI_CAPABILITY_LIST, PCI_CFGBAR, &next); + rc |= vpci_read_byte(next + 3, PCI_CFGBAR, &cfg); + + while (!rc && (cfg != cfg_type) && next) { + rc = vpci_read_byte(next + 1, PCI_CFGBAR, &next); + rc |= vpci_read_byte(next + 3, PCI_CFGBAR, &cfg); + } + + return rc ? 0 : next; +} + +/* + * Read PCI configuration space to find the offset of the Common, Device, and + * Notification memory regions within the modern memory space. + * Returns 0 if success, 1 if a capability could not be located, or a + * negative RC if the configuration read failed. + */ +static int virtio_pci_read_pci_cap_config(void) +{ + uint8_t pos; + int rc; + + /* Common capabilities */ + pos = virtio_pci_find_cap_pos(VPCI_CAP_COMMON_CFG); + if (!pos) { + puts("Failed to locate PCI common configuration"); + return 1; + } + + rc = vpci_read_byte(pos + VPCI_CAP_BAR, PCI_CFGBAR, &c_cap.bar); + if (rc || vpci_read_bswap32(pos + VPCI_CAP_OFFSET, PCI_CFGBAR, &c_cap.off)) { + puts("Failed to read PCI common configuration"); + return -EIO; + } + + /* Device capabilities */ + pos = virtio_pci_find_cap_pos(VPCI_CAP_DEVICE_CFG); + if (!pos) { + puts("Failed to locate PCI device configuration"); + return 1; + } + + rc = vpci_read_byte(pos + VPCI_CAP_BAR, PCI_CFGBAR, &d_cap.bar); + if (rc || vpci_read_bswap32(pos + VPCI_CAP_OFFSET, PCI_CFGBAR, &d_cap.off)) { + puts("Failed to read PCI device configuration"); + return -EIO; + } + + /* Notification capabilities */ + pos = virtio_pci_find_cap_pos(VPCI_CAP_NOTIFY_CFG); + if (!pos) { + puts("Failed to locate PCI notification configuration"); + return 1; + } + + rc = vpci_read_byte(pos + VPCI_CAP_BAR, PCI_CFGBAR, &n_cap.bar); + if (rc || vpci_read_bswap32(pos + VPCI_CAP_OFFSET, PCI_CFGBAR, &n_cap.off)) { + puts("Failed to read PCI notification configuration"); + return -EIO; + } + + rc = vpci_read_bswap32(pos + VPCI_N_CAP_MULT, PCI_CFGBAR, ¬ify_mult); + if (rc || vpci_read_bswap16(c_cap.off + VPCI_C_OFFSET_Q_NOFF, c_cap.bar, + &q_notify_offset)) { + puts("Failed to read notification queue configuration"); + return -EIO; + } + + return 0; +} + +static int enable_pci_bus_master(void) +{ + uint16_t cmd_reg; + + if (vpci_read_bswap16(PCI_CMD_REG, PCI_CFGBAR, &cmd_reg)) { + puts("Failed to read PCI command register"); + return -EIO; + } + + if (vpci_bswap16_write(PCI_CMD_REG, PCI_CFGBAR, cmd_reg | PCI_BUS_MASTER_MASK)) { + puts("Failed to enable PCI bus mastering"); + return -EIO; + } + + return 0; +} + +int virtio_pci_setup(VDev *vdev) +{ + VRing *vr; + int rc; + uint8_t status; + uint16_t vq_size; + int i = 0; + + vdev->guessed_disk_nature = VIRTIO_GDN_NONE; + vdev->cmd_vr_idx = 0; + + if (virtio_pci_read_pci_cap_config()) { + puts("Invalid virtio PCI capabilities"); + return -EIO; + } + + if (enable_pci_bus_master()) { + return -EIO; + } + + if (virtio_reset(vdev)) { + return -EIO; + } + + status = VIRTIO_CONFIG_S_ACKNOWLEDGE; + if (virtio_pci_set_status(status)) { + puts("Virtio-pci device Failed to ACKNOWLEDGE"); + return -EIO; + } + + vdev->guest_features[1] = VIRTIO_F_VERSION_1; + if (virtio_pci_negotiate()) { + panic("Virtio feature negotiation failed!"); + } + + switch (vdev->dev_type) { + case VIRTIO_ID_BLOCK: + vdev->nr_vqs = 1; + vdev->cmd_vr_idx = 0; + virtio_pci_get_blk_config(); + break; + default: + puts("Unsupported virtio device"); + return -ENODEV; + } + + status |= VIRTIO_CONFIG_S_DRIVER; + rc = virtio_pci_set_status(status); + if (rc) { + puts("Set status failed"); + return -EIO; + } + + if (vpci_read_bswap16(VPCI_C_OFFSET_Q_SIZE, c_cap.bar, &vq_size)) { + puts("Failed to read virt-queue configuration"); + return -EIO; + } + + /* Configure virt-queues for pci */ + for (i = 0; i < vdev->nr_vqs; i++) { + VqInfo info = { + .queue = (unsigned long long) virtio_get_ring_area(i), + .align = KVM_S390_VIRTIO_RING_ALIGN, + .index = i, + .num = vq_size, + }; + + vr = &vdev->vrings[i]; + vring_init(vr, &info); + + if (vpci_set_selected_vq(vr->id)) { + puts("Failed to set selected virt-queue"); + return -EIO; + } + + rc = set_pci_vq_addr(VPCI_C_OFFSET_Q_DESCLO, vr->desc); + rc |= set_pci_vq_addr(VPCI_C_OFFSET_Q_AVAILLO, vr->avail); + rc |= set_pci_vq_addr(VPCI_C_OFFSET_Q_USEDLO, vr->used); + if (rc) { + puts("Failed to configure virt-queue address"); + return -EIO; + } + + if (vpci_set_queue_enable(true)) { + puts("Failed to set virt-queue enabled"); + return -EIO; + } + } + + status |= VIRTIO_CONFIG_S_FEATURES_OK | VIRTIO_CONFIG_S_DRIVER_OK; + return virtio_pci_set_status(status); +} + +int virtio_pci_setup_device(void) +{ + VDev *vdev = virtio_get_device(); + + if (enable_pci_function(&vdev->pci_fh)) { + puts("Failed to enable PCI function"); + return -ENODEV; + } + + return 0; +} diff --git a/pc-bios/s390-ccw/virtio-pci.h b/pc-bios/s390-ccw/virtio-pci.h new file mode 100644 index 000000000000..90d07cb9a76a --- /dev/null +++ b/pc-bios/s390-ccw/virtio-pci.h @@ -0,0 +1,82 @@ +/* + * Definitions for virtio-pci + * + * Copyright 2025 IBM Corp. + * Author(s): Jared Rossi + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef VIRTIO_PCI_H +#define VIRTIO_PCI_H + +/* Common configuration */ +#define VPCI_CAP_COMMON_CFG 1 +/* Notifications */ +#define VPCI_CAP_NOTIFY_CFG 2 +/* ISR access */ +#define VPCI_CAP_ISR_CFG 3 +/* Device specific configuration */ +#define VPCI_CAP_DEVICE_CFG 4 +/* PCI configuration access */ +#define VPCI_CAP_PCI_CFG 5 +/* Additional shared memory capability */ +#define VPCI_CAP_SHARED_MEMORY_CFG 8 +/* PCI vendor data configuration */ +#define VPCI_CAP_VENDOR_CFG 9 + +/* Offsets within capability header */ +#define VPCI_CAP_VNDR 0 +#define VPCI_CAP_NEXT 1 +#define VPCI_CAP_LEN 2 +#define VPCI_CAP_CFG_TYPE 3 +#define VPCI_CAP_BAR 4 +#define VPCI_CAP_OFFSET 8 +#define VPCI_CAP_LENGTH 12 + +#define VPCI_N_CAP_MULT 16 /* Notify multiplier, VPCI_CAP_NOTIFY_CFG only */ + +/* Common Area Offsets for virtio-pci queue */ +#define VPCI_C_OFFSET_DFSELECT 0 +#define VPCI_C_OFFSET_DF 4 +#define VPCI_C_OFFSET_GFSELECT 8 +#define VPCI_C_OFFSET_GF 12 +#define VPCI_C_COMMON_NUMQ 18 +#define VPCI_C_OFFSET_STATUS 20 +#define VPCI_C_OFFSET_Q_SELECT 22 +#define VPCI_C_OFFSET_Q_SIZE 24 +#define VPCI_C_OFFSET_Q_ENABLE 28 +#define VPCI_C_OFFSET_Q_NOFF 30 +#define VPCI_C_OFFSET_Q_DESCLO 32 +#define VPCI_C_OFFSET_Q_DESCHI 36 +#define VPCI_C_OFFSET_Q_AVAILLO 40 +#define VPCI_C_OFFSET_Q_AVAILHI 44 +#define VPCI_C_OFFSET_Q_USEDLO 48 +#define VPCI_C_OFFSET_Q_USEDHI 52 + +#define VIRTIO_F_VERSION_1 1 /* Feature bit 32 */ + +struct VirtioPciCap { + uint8_t bar; /* Which PCIAS it's in */ + uint32_t off; /* Offset within bar */ +}; +typedef struct VirtioPciCap VirtioPciCap; + +void virtio_pci_id2type(VDev *vdev, uint16_t device_id); +int virtio_pci_reset(VDev *vdev); +long virtio_pci_notify(int vq_id); +int virtio_pci_setup(VDev *vdev); +int virtio_pci_setup_device(void); + +int vpci_read_flex(uint64_t offset, uint8_t pcias, void *buf, int len); +int vpci_read_bswap64(uint64_t offset, uint8_t pcias, uint64_t *buf); +int vpci_read_bswap32(uint64_t offset, uint8_t pcias, uint32_t *buf); +int vpci_read_bswap16(uint64_t offset, uint8_t pcias, uint16_t *buf); +int vpci_read_byte(uint64_t offset, uint8_t pcias, uint8_t *buf); + +int vpci_bswap64_write(uint64_t offset, uint8_t pcias, uint64_t data); +int vpci_bswap32_write(uint64_t offset, uint8_t pcias, uint32_t data); +int vpci_bswap16_write(uint64_t offset, uint8_t pcias, uint16_t data); +int vpci_write_byte(uint64_t offset, uint8_t pcias, uint8_t data); + +#endif diff --git a/pc-bios/s390-ccw/virtio-scsi.c b/pc-bios/s390-ccw/virtio-scsi.c index 71db75ce7b43..9ea00c6fe678 100644 --- a/pc-bios/s390-ccw/virtio-scsi.c +++ b/pc-bios/s390-ccw/virtio-scsi.c @@ -15,6 +15,7 @@ #include "virtio.h" #include "scsi.h" #include "virtio-scsi.h" +#include "virtio-ccw.h" #include "s390-time.h" #include "helper.h" @@ -476,12 +477,9 @@ static int virtio_scsi_setup(VDev *vdev) return 0; } -int virtio_scsi_setup_device(SubChannelId schid) +int virtio_scsi_setup_device(VDev *vdev) { - VDev *vdev = virtio_get_device(); - - vdev->schid = schid; - virtio_setup_ccw(vdev); + virtio_ccw_setup(vdev); if (vdev->config.scsi.sense_size != VIRTIO_SCSI_SENSE_SIZE) { puts("Config: sense size mismatch"); diff --git a/pc-bios/s390-ccw/virtio-scsi.h b/pc-bios/s390-ccw/virtio-scsi.h index c5612e16a268..070f24b7e5a5 100644 --- a/pc-bios/s390-ccw/virtio-scsi.h +++ b/pc-bios/s390-ccw/virtio-scsi.h @@ -69,6 +69,6 @@ static inline bool virtio_scsi_response_ok(const VirtioScsiCmdResp *r) int virtio_scsi_read_many(VDev *vdev, unsigned long sector, void *load_addr, int sec_num); -int virtio_scsi_setup_device(SubChannelId schid); +int virtio_scsi_setup_device(VDev *vdev); #endif /* VIRTIO_SCSI_H */ diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c index cd6c99c7e326..390b55c7b924 100644 --- a/pc-bios/s390-ccw/virtio.c +++ b/pc-bios/s390-ccw/virtio.c @@ -2,6 +2,9 @@ * Virtio driver bits * * Copyright (c) 2013 Alexander Graf + * Copyright 2025 IBM Corp. + * + * Author(s): Jared Rossi * * This work is licensed under the terms of the GNU GPL, version 2 or (at * your option) any later version. See the COPYING file in the top-level @@ -13,6 +16,8 @@ #include "cio.h" #include "virtio.h" #include "virtio-scsi.h" +#include "virtio-ccw.h" +#include "virtio-pci.h" #include "bswap.h" #include "helper.h" #include "s390-time.h" @@ -41,72 +46,41 @@ VDev *virtio_get_device(void) VirtioDevType virtio_get_device_type(void) { - return vdev.senseid.cu_model; + return vdev.dev_type; } -/* virtio spec v1.0 para 4.3.3.2 */ -static long kvm_hypercall(unsigned long nr, unsigned long param1, - unsigned long param2, unsigned long param3) +char *virtio_get_ring_area(int ring_num) { - register unsigned long r_nr asm("1") = nr; - register unsigned long r_param1 asm("2") = param1; - register unsigned long r_param2 asm("3") = param2; - register unsigned long r_param3 asm("4") = param3; - register long retval asm("2"); - - asm volatile ("diag %%r2,%%r4,0x500" - : "=d" (retval) - : "d" (r_nr), "0" (r_param1), "r"(r_param2), "d"(r_param3) - : "memory", "cc"); - - return retval; -} - -static long virtio_notify(SubChannelId schid, int vq_idx, long cookie) -{ - return kvm_hypercall(KVM_S390_VIRTIO_CCW_NOTIFY, *(u32 *)&schid, - vq_idx, cookie); + return ring_area + ring_num * VIRTIO_RING_SIZE; } /*********************************************** * Virtio functions * ***********************************************/ -int drain_irqs(SubChannelId schid) +int drain_irqs(void) { - Irb irb = {}; - int r = 0; - - while (1) { - /* FIXME: make use of TPI, for that enable subchannel and isc */ - if (tsch(schid, &irb)) { - /* Might want to differentiate error codes later on. */ - if (irb.scsw.cstat) { - r = -EIO; - } else if (irb.scsw.dstat != 0xc) { - r = -EIO; - } - return r; - } + switch (vdev.ipl_type) { + case S390_IPL_TYPE_QEMU_SCSI: + case S390_IPL_TYPE_CCW: + return drain_irqs_ccw(vdev.schid); + default: + return 0; } } -static int run_ccw(VDev *vdev, int cmd, void *ptr, int len, bool sli) +int virtio_run(VDev *vdev, int vqid, VirtioCmd *cmd) { - Ccw1 ccw = {}; - - ccw.cmd_code = cmd; - ccw.cda = (long)ptr; - ccw.count = len; - - if (sli) { - ccw.flags |= CCW_FLAG_SLI; + switch (vdev->ipl_type) { + case S390_IPL_TYPE_QEMU_SCSI: + case S390_IPL_TYPE_CCW: + return virtio_ccw_run(vdev, vqid, cmd); + default: + return -1; } - - return do_cio(vdev->schid, vdev->senseid.cu_type, ptr2u32(&ccw), CCW_FMT1); } -static void vring_init(VRing *vr, VqInfo *info) +void vring_init(VRing *vr, VqInfo *info) { void *p = (void *) info->queue; @@ -123,7 +97,7 @@ static void vring_init(VRing *vr, VqInfo *info) vr->avail->idx = 0; /* We're running with interrupts off anyways, so don't bother */ - vr->used->flags = VRING_USED_F_NO_NOTIFY; + vr->used->flags = be_ipl() ? VRING_USED_F_NO_NOTIFY : bswap16(VRING_USED_F_NO_NOTIFY); vr->used->idx = 0; vr->used_idx = 0; vr->next_idx = 0; @@ -134,15 +108,59 @@ static void vring_init(VRing *vr, VqInfo *info) bool vring_notify(VRing *vr) { - vr->cookie = virtio_notify(vr->schid, vr->id, vr->cookie); + switch (vdev.ipl_type) { + case S390_IPL_TYPE_QEMU_SCSI: + case S390_IPL_TYPE_CCW: + vr->cookie = virtio_ccw_notify(vdev.schid, vr->id, vr->cookie); + break; + case S390_IPL_TYPE_PCI: + vr->cookie = virtio_pci_notify(vr->id); + default: + return 1; + } + return vr->cookie >= 0; } +/* + * Get endienness of the IPL type + * Return true for s390x native big-endian + */ +bool be_ipl(void) +{ + switch (virtio_get_device()->ipl_type) { + case S390_IPL_TYPE_QEMU_SCSI: + case S390_IPL_TYPE_CCW: + return true; + case S390_IPL_TYPE_PCI: + return false; + default: + return true; + } +} + +/* + * Format the virtio ring descriptor endianness + * Return the available index increment in the appropriate endianness + */ +static void vr_bswap_descriptor(VRingDesc *desc) +{ + desc->addr = bswap64(desc->addr); + desc->len = bswap32(desc->len); + desc->flags = bswap16(desc->flags); + desc->next = bswap16(desc->next); +} + void vring_send_buf(VRing *vr, void *p, int len, int flags) { + if (!be_ipl()) { + vr->avail->idx = bswap16(vr->avail->idx); + } + /* For follow-up chains we need to keep the first entry point */ if (!(flags & VRING_HIDDEN_IS_CHAIN)) { - vr->avail->ring[vr->avail->idx % vr->num] = vr->next_idx; + vr->avail->ring[vr->avail->idx % vr->num] = be_ipl() ? vr->next_idx : + bswap16(vr->next_idx); } vr->desc[vr->next_idx].addr = (unsigned long)p; @@ -150,12 +168,21 @@ void vring_send_buf(VRing *vr, void *p, int len, int flags) vr->desc[vr->next_idx].flags = flags & ~VRING_HIDDEN_IS_CHAIN; vr->desc[vr->next_idx].next = vr->next_idx; vr->desc[vr->next_idx].next++; + + if (!be_ipl()) { + vr_bswap_descriptor(&vr->desc[vr->next_idx]); + } + vr->next_idx++; /* Chains only have a single ID */ if (!(flags & VRING_DESC_F_NEXT)) { vr->avail->idx++; } + + if (!be_ipl()) { + vr->avail->idx = bswap16(vr->avail->idx); + } } int vr_poll(VRing *vr) @@ -166,7 +193,7 @@ int vr_poll(VRing *vr) return 0; } - vr->used_idx = vr->used->idx; + vr->used_idx = vr->used->idx; /* Endianness is preserved */ vr->next_idx = 0; vr->desc[0].len = 0; vr->desc[0].flags = 0; @@ -200,160 +227,26 @@ int vring_wait_reply(void) return 1; } -int virtio_run(VDev *vdev, int vqid, VirtioCmd *cmd) -{ - VRing *vr = &vdev->vrings[vqid]; - int i = 0; - - do { - vring_send_buf(vr, cmd[i].data, cmd[i].size, - cmd[i].flags | (i ? VRING_HIDDEN_IS_CHAIN : 0)); - } while (cmd[i++].flags & VRING_DESC_F_NEXT); - - vring_wait_reply(); - if (drain_irqs(vr->schid)) { - return -1; - } - return 0; -} - int virtio_reset(VDev *vdev) { - return run_ccw(vdev, CCW_CMD_VDEV_RESET, NULL, 0, false); -} - -int virtio_setup_ccw(VDev *vdev) -{ - int i, cfg_size = 0; - uint8_t status; - struct VirtioFeatureDesc { - uint32_t features; - uint8_t index; - } __attribute__((packed)) feats; - - if (!virtio_is_supported(vdev->schid)) { - puts("Virtio unsupported for this device ID"); - return -ENODEV; - } - /* device ID has been established now */ - - vdev->config.blk.blk_size = 0; /* mark "illegal" - setup started... */ - vdev->guessed_disk_nature = VIRTIO_GDN_NONE; - - virtio_reset(vdev); - - status = VIRTIO_CONFIG_S_ACKNOWLEDGE; - if (run_ccw(vdev, CCW_CMD_WRITE_STATUS, &status, sizeof(status), false)) { - puts("Could not write ACKNOWLEDGE status to host"); - return -EIO; - } - - switch (vdev->senseid.cu_model) { - case VIRTIO_ID_NET: - vdev->nr_vqs = 2; - vdev->cmd_vr_idx = 0; - cfg_size = sizeof(vdev->config.net); - break; - case VIRTIO_ID_BLOCK: - vdev->nr_vqs = 1; - vdev->cmd_vr_idx = 0; - cfg_size = sizeof(vdev->config.blk); - break; - case VIRTIO_ID_SCSI: - vdev->nr_vqs = 3; - vdev->cmd_vr_idx = VR_REQUEST; - cfg_size = sizeof(vdev->config.scsi); - break; + switch (vdev->ipl_type) { + case S390_IPL_TYPE_QEMU_SCSI: + case S390_IPL_TYPE_CCW: + return virtio_ccw_reset(vdev); + case S390_IPL_TYPE_PCI: + return virtio_pci_reset(vdev); default: - puts("Unsupported virtio device"); - return -ENODEV; - } - - status |= VIRTIO_CONFIG_S_DRIVER; - if (run_ccw(vdev, CCW_CMD_WRITE_STATUS, &status, sizeof(status), false)) { - puts("Could not write DRIVER status to host"); - return -EIO; - } - - /* Feature negotiation */ - for (i = 0; i < ARRAY_SIZE(vdev->guest_features); i++) { - feats.features = 0; - feats.index = i; - if (run_ccw(vdev, CCW_CMD_READ_FEAT, &feats, sizeof(feats), false)) { - puts("Could not get features bits"); - return -EIO; - } - - vdev->guest_features[i] &= bswap32(feats.features); - feats.features = bswap32(vdev->guest_features[i]); - if (run_ccw(vdev, CCW_CMD_WRITE_FEAT, &feats, sizeof(feats), false)) { - puts("Could not set features bits"); - return -EIO; - } - } - - if (run_ccw(vdev, CCW_CMD_READ_CONF, &vdev->config, cfg_size, false)) { - puts("Could not get virtio device configuration"); - return -EIO; - } - - for (i = 0; i < vdev->nr_vqs; i++) { - VqInfo info = { - .queue = (unsigned long long) ring_area + (i * VIRTIO_RING_SIZE), - .align = KVM_S390_VIRTIO_RING_ALIGN, - .index = i, - .num = 0, - }; - VqConfig config = { - .index = i, - .num = 0, - }; - - if (run_ccw(vdev, CCW_CMD_READ_VQ_CONF, &config, sizeof(config), - false)) { - puts("Could not get virtio device VQ config"); - return -EIO; - } - info.num = config.num; - vring_init(&vdev->vrings[i], &info); - vdev->vrings[i].schid = vdev->schid; - if (run_ccw(vdev, CCW_CMD_SET_VQ, &info, sizeof(info), false)) { - puts("Cannot set VQ info"); - return -EIO; - } - } - - status |= VIRTIO_CONFIG_S_DRIVER_OK; - if (run_ccw(vdev, CCW_CMD_WRITE_STATUS, &status, sizeof(status), false)) { - puts("Could not write DRIVER_OK status to host"); - return -EIO; + return -1; } - - return 0; } -bool virtio_is_supported(SubChannelId schid) +bool virtio_is_supported(VDev *vdev) { - vdev.schid = schid; - memset(&vdev.senseid, 0, sizeof(vdev.senseid)); - - /* - * Run sense id command. - * The size of the senseid data differs between devices (notably, - * between virtio devices and dasds), so specify the largest possible - * size and suppress the incorrect length indication for smaller sizes. - */ - if (run_ccw(&vdev, CCW_CMD_SENSE_ID, &vdev.senseid, sizeof(vdev.senseid), - true)) { + switch (vdev->ipl_type) { + case S390_IPL_TYPE_QEMU_SCSI: + case S390_IPL_TYPE_CCW: + return virtio_ccw_is_supported(vdev); + default: return false; } - if (vdev.senseid.cu_type == 0x3832) { - switch (vdev.senseid.cu_model) { - case VIRTIO_ID_BLOCK: - case VIRTIO_ID_SCSI: - case VIRTIO_ID_NET: - return true; - } - } - return false; } diff --git a/pc-bios/s390-ccw/virtio.h b/pc-bios/s390-ccw/virtio.h index 5c5e808a500e..d32a4830cad1 100644 --- a/pc-bios/s390-ccw/virtio.h +++ b/pc-bios/s390-ccw/virtio.h @@ -18,6 +18,8 @@ #define VIRTIO_CONFIG_S_DRIVER 2 /* Driver has used its parts of the config, and is happy */ #define VIRTIO_CONFIG_S_DRIVER_OK 4 +/* Feature negotiation complete */ +#define VIRTIO_CONFIG_S_FEATURES_OK 8 /* We've given up on this device. */ #define VIRTIO_CONFIG_S_FAILED 0x80 @@ -103,12 +105,12 @@ struct VRing { VRingDesc *desc; VRingAvail *avail; VRingUsed *used; - SubChannelId schid; long cookie; int id; }; typedef struct VRing VRing; +char *virtio_get_ring_area(int ring_num); /*********************************************** * Virtio block * @@ -239,6 +241,8 @@ struct VDev { VirtioGDN guessed_disk_nature; SubChannelId schid; SenseId senseid; + S390IplType ipl_type; + VirtioDevType dev_type; union { VirtioBlkConfig blk; VirtioScsiConfig scsi; @@ -253,6 +257,7 @@ struct VDev { uint8_t scsi_dev_heads; bool scsi_device_selected; ScsiDevice selected_scsi_device; + uint32_t pci_fh; uint32_t max_transfer; uint32_t guest_features[2]; }; @@ -268,8 +273,11 @@ struct VirtioCmd { }; typedef struct VirtioCmd VirtioCmd; +bool be_ipl(void); +void vring_init(VRing *vr, VqInfo *info); +bool virtio_is_supported(VDev *vdev); bool vring_notify(VRing *vr); -int drain_irqs(SubChannelId schid); +int drain_irqs(void); void vring_send_buf(VRing *vr, void *p, int len, int flags); int vr_poll(VRing *vr); int vring_wait_reply(void); @@ -277,7 +285,14 @@ int virtio_run(VDev *vdev, int vqid, VirtioCmd *cmd); int virtio_reset(VDev *vdev); int virtio_setup_ccw(VDev *vdev); +/* virtio-net.c */ int virtio_net_init(void *mac_addr); void virtio_net_deinit(void); +/* virtio-blkdev.c */ +int virtio_blk_setup_device(VDev *vdev); +int virtio_read(unsigned long sector, void *load_addr); +unsigned long virtio_load_direct(unsigned long rec_list1, unsigned long rec_list2, + void *load_addr); + #endif /* VIRTIO_H */ diff --git a/plugins/api.c b/plugins/api.c index 04ca7da7f18f..0c348a789b2e 100644 --- a/plugins/api.c +++ b/plugins/api.c @@ -41,6 +41,7 @@ #include "qemu/log.h" #include "system/memory.h" #include "tcg/tcg.h" +#include "exec/cpu-common.h" #include "exec/gdbstub.h" #include "exec/target_page.h" #include "exec/translation-block.h" @@ -409,6 +410,12 @@ bool qemu_plugin_bool_parse(const char *name, const char *value, bool *ret) * ancillary data the plugin might find useful. */ +static const char pc_str[] = "pc"; /* generic name for program counter */ +static const char eip_str[] = "eip"; /* x86-specific name for PC */ +static const char rip_str[] = "rip"; /* x86_64-specific name for PC */ +static const char pswa_str[] = "pswa"; /* s390x-specific name for PC */ +static const char iaoq_str[] = "iaoq"; /* HP/PA-specific name for PC */ +static const char rpc_str[] = "rpc"; /* microblaze-specific name for PC */ static GArray *create_register_handles(GArray *gdbstub_regs) { GArray *find_data = g_array_new(true, true, @@ -417,6 +424,7 @@ static GArray *create_register_handles(GArray *gdbstub_regs) for (int i = 0; i < gdbstub_regs->len; i++) { GDBRegDesc *grd = &g_array_index(gdbstub_regs, GDBRegDesc, i); qemu_plugin_reg_descriptor desc; + gint plugin_ro_bit = 0; /* skip "un-named" regs */ if (!grd->name) { @@ -424,8 +432,19 @@ static GArray *create_register_handles(GArray *gdbstub_regs) } /* Create a record for the plugin */ - desc.handle = GINT_TO_POINTER(grd->gdb_reg + 1); desc.name = g_intern_string(grd->name); + desc.is_readonly = false; + if (g_strcmp0(desc.name, pc_str) == 0 + || g_strcmp0(desc.name, eip_str) == 0 + || g_strcmp0(desc.name, rip_str) == 0 + || g_strcmp0(desc.name, pswa_str) == 0 + || g_strcmp0(desc.name, iaoq_str) == 0 + || g_strcmp0(desc.name, rpc_str) == 0 + ) { + desc.is_readonly = true; + plugin_ro_bit = 1; + } + desc.handle = GINT_TO_POINTER((grd->gdb_reg << 1) | plugin_ro_bit); desc.feature = g_intern_string(grd->feature_name); g_array_append_val(find_data, desc); } @@ -450,7 +469,7 @@ bool qemu_plugin_read_register(struct qemu_plugin_register *reg, return false; } - return (gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) - 1) > 0); + return (gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg) >> 1) > 0); } bool qemu_plugin_write_register(struct qemu_plugin_register *reg, @@ -458,11 +477,26 @@ bool qemu_plugin_write_register(struct qemu_plugin_register *reg, { g_assert(current_cpu); - if (buf->len == 0 || qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS) { + /* Read-only property is encoded in least significant bit */ + g_assert((GPOINTER_TO_INT(reg) & 1) == 0); + + if (buf->len == 0 || + (qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS && + qemu_plugin_get_cb_flags() != QEMU_PLUGIN_CB_RW_REGS_PC)) { return false; } - return (gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) - 1) > 0); + return (gdb_write_register(current_cpu, buf->data, GPOINTER_TO_INT(reg) >> 1) > 0); +} + +void qemu_plugin_set_pc(uint64_t vaddr) +{ + g_assert(current_cpu); + + g_assert(qemu_plugin_get_cb_flags() == QEMU_PLUGIN_CB_RW_REGS_PC); + + cpu_set_pc(current_cpu, vaddr); + cpu_loop_exit(current_cpu); } bool qemu_plugin_read_memory_vaddr(uint64_t addr, GByteArray *data, size_t len) diff --git a/plugins/core.c b/plugins/core.c index 42fd9865930e..2324bbffa3d0 100644 --- a/plugins/core.c +++ b/plugins/core.c @@ -119,7 +119,7 @@ static void plugin_vcpu_cb__discon(CPUState *cpu, struct qemu_plugin_cb *cb, *next; uint64_t to = cpu->cc->get_pc(cpu); - qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS); + qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC); if (cpu->cpu_index < plugin.num_vcpus) { /* iterate safely; plugins might uninstall themselves at any time */ QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) { @@ -395,15 +395,16 @@ void plugin_register_dyn_cb__udata(GArray **arr, enum qemu_plugin_cb_flags flags, void *udata) { - static TCGHelperInfo info[3] = { + static TCGHelperInfo info[4] = { [QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG, [QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG, [QEMU_PLUGIN_CB_RW_REGS].flags = 0, + [QEMU_PLUGIN_CB_RW_REGS_PC].flags = 0, /* * Match qemu_plugin_vcpu_udata_cb_t: * void (*)(uint32_t, void *) */ - [0 ... 2].typemask = (dh_typemask(void, 0) | + [0 ... 3].typemask = (dh_typemask(void, 0) | dh_typemask(i32, 1) | dh_typemask(ptr, 2)) }; @@ -425,15 +426,16 @@ void plugin_register_dyn_cond_cb__udata(GArray **arr, uint64_t imm, void *udata) { - static TCGHelperInfo info[3] = { + static TCGHelperInfo info[4] = { [QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG, [QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG, [QEMU_PLUGIN_CB_RW_REGS].flags = 0, + [QEMU_PLUGIN_CB_RW_REGS_PC].flags = 0, /* * Match qemu_plugin_vcpu_udata_cb_t: * void (*)(uint32_t, void *) */ - [0 ... 2].typemask = (dh_typemask(void, 0) | + [0 ... 3].typemask = (dh_typemask(void, 0) | dh_typemask(i32, 1) | dh_typemask(ptr, 2)) }; @@ -464,15 +466,16 @@ void plugin_register_vcpu_mem_cb(GArray **arr, !__builtin_types_compatible_p(qemu_plugin_meminfo_t, uint32_t) && !__builtin_types_compatible_p(qemu_plugin_meminfo_t, int32_t)); - static TCGHelperInfo info[3] = { + static TCGHelperInfo info[4] = { [QEMU_PLUGIN_CB_NO_REGS].flags = TCG_CALL_NO_RWG, [QEMU_PLUGIN_CB_R_REGS].flags = TCG_CALL_NO_WG, [QEMU_PLUGIN_CB_RW_REGS].flags = 0, + [QEMU_PLUGIN_CB_RW_REGS_PC].flags = 0, /* * Match qemu_plugin_vcpu_mem_cb_t: * void (*)(uint32_t, qemu_plugin_meminfo_t, uint64_t, void *) */ - [0 ... 2].typemask = + [0 ... 3].typemask = (dh_typemask(void, 0) | dh_typemask(i32, 1) | (__builtin_types_compatible_p(qemu_plugin_meminfo_t, uint32_t) @@ -513,6 +516,23 @@ void qemu_plugin_tb_trans_cb(CPUState *cpu, struct qemu_plugin_tb *tb) } } +static void clamp_syscall_arguments(uint64_t *a1, uint64_t *a2, uint64_t *a3, + uint64_t *a4, uint64_t *a5, uint64_t *a6, + uint64_t *a7, uint64_t *a8) +{ + if (target_long_bits() == 32) { + const uint64_t mask = UINT32_MAX; + *a1 &= mask; + *a2 &= mask; + *a3 &= mask; + *a4 &= mask; + *a5 &= mask; + *a6 &= mask; + *a7 &= mask; + *a8 &= mask; + } +} + /* * Disable CFI checks. * The callback function has been loaded from an external library so we do not @@ -531,10 +551,12 @@ qemu_plugin_vcpu_syscall(CPUState *cpu, int64_t num, uint64_t a1, uint64_t a2, return; } + clamp_syscall_arguments(&a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8); + QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) { qemu_plugin_vcpu_syscall_cb_t func = cb->f.vcpu_syscall; - qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS); + qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC); func(cb->ctx->id, cpu->cpu_index, num, a1, a2, a3, a4, a5, a6, a7, a8); qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS); } @@ -558,7 +580,7 @@ void qemu_plugin_vcpu_syscall_ret(CPUState *cpu, int64_t num, int64_t ret) QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) { qemu_plugin_vcpu_syscall_ret_cb_t func = cb->f.vcpu_syscall_ret; - qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS); + qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC); func(cb->ctx->id, cpu->cpu_index, num, ret); qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS); } @@ -584,7 +606,9 @@ qemu_plugin_vcpu_syscall_filter(CPUState *cpu, int64_t num, uint64_t a1, return false; } - qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS); + clamp_syscall_arguments(&a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8); + + qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC); QLIST_FOREACH_SAFE_RCU(cb, &plugin.cb_lists[ev], entry, next) { qemu_plugin_vcpu_syscall_filter_cb_t func = cb->f.vcpu_syscall_filter; @@ -605,7 +629,7 @@ void qemu_plugin_vcpu_idle_cb(CPUState *cpu) { /* idle and resume cb may be called before init, ignore in this case */ if (cpu->cpu_index < plugin.num_vcpus) { - qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS); + qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC); plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_IDLE); qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS); } @@ -614,7 +638,7 @@ void qemu_plugin_vcpu_idle_cb(CPUState *cpu) void qemu_plugin_vcpu_resume_cb(CPUState *cpu) { if (cpu->cpu_index < plugin.num_vcpus) { - qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS); + qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_RW_REGS_PC); plugin_vcpu_cb__simple(cpu, QEMU_PLUGIN_EV_VCPU_RESUME); qemu_plugin_set_cb_flags(cpu, QEMU_PLUGIN_CB_NO_REGS); } @@ -885,6 +909,6 @@ enum qemu_plugin_cb_flags tcg_call_to_qemu_plugin_cb_flags(int flags) } else if (flags & TCG_CALL_NO_WG) { return QEMU_PLUGIN_CB_R_REGS; } else { - return QEMU_PLUGIN_CB_RW_REGS; + return QEMU_PLUGIN_CB_RW_REGS_PC; } } diff --git a/python/Makefile b/python/Makefile index 32aedce4137e..42994d39618f 100644 --- a/python/Makefile +++ b/python/Makefile @@ -63,8 +63,6 @@ $(QEMU_MINVENV_DIR) $(QEMU_MINVENV_DIR)/bin/activate: setup.cfg tests/minreqs.tx @( \ echo "ACTIVATE $(QEMU_MINVENV_DIR)"; \ . $(QEMU_MINVENV_DIR)/bin/activate; \ - echo "INSTALL wheel $(QEMU_MINVENV_DIR)"; \ - $(PIP_INSTALL) wheel 1>/dev/null; \ echo "INSTALL -r tests/minreqs.txt $(QEMU_MINVENV_DIR)";\ $(PIP_INSTALL) -r tests/minreqs.txt 1>/dev/null; \ echo "INSTALL -e qemu $(QEMU_MINVENV_DIR)"; \ @@ -107,7 +105,7 @@ develop: .PHONY: check check: - @avocado --config avocado.cfg run tests/ + @pytest -v tests/*.py .PHONY: check-tox check-tox: @@ -115,7 +113,7 @@ check-tox: .PHONY: check-coverage check-coverage: - @coverage run -m avocado --config avocado.cfg run tests/*.py + @coverage run -m pytest -v tests/*.py @coverage combine @coverage html @coverage report diff --git a/python/README.rst b/python/README.rst index d62e71528d24..e34d1a1c7b1c 100644 --- a/python/README.rst +++ b/python/README.rst @@ -3,7 +3,17 @@ QEMU Python Tooling This directory houses Python tooling used by the QEMU project to build, configure, and test QEMU. It is organized by namespace (``qemu``), and -then by package (e.g. ``qemu/machine``, ``qemu/qmp``, etc). +then by package (e.g. ``qemu/machine``, ``qemu/utils``, etc). + +These tools and libraries are installed to the QEMU configure-time +Python virtual environment by default (see qemu.git/pythondeps.toml +"tooling" group), and are available for use by any Python script +executed by the build system. To have these libraries available for +manual invocations of scripts, use of the "run" script in your build +directory is recommended. + +General structure +----------------- ``setup.py`` is used by ``pip`` to install this tooling to the current environment. ``setup.cfg`` provides the packaging configuration used by @@ -20,9 +30,9 @@ environment. ``setup.cfg`` provides the packaging configuration used by If you append the ``--editable`` or ``-e`` argument to either invocation above, pip will install in "editable" mode. This installs the package as -a forwarder ("qemu.egg-link") that points to the source tree. In so -doing, the installed package always reflects the latest version in your -source tree. +a forwarder that points to the source tree. In so doing, the installed +package always reflects the latest version in your source tree. This is +the mode used to install these packages at configure time. Installing ".[devel]" instead of "." will additionally pull in required packages for testing this package. They are not runtime requirements, @@ -40,8 +50,18 @@ for more information. Using these packages without installing them -------------------------------------------- -These packages may be used without installing them first, by using one -of two tricks: +It is no longer recommended to try to use these packages without +installing them to a virtual environment, but depending on your use +case, it may still be possible to do. + +The "qemu.qmp" library is now hosted outside of the qemu.git repository, +and the "qemu.machine" library that remains in-tree here has qemu.qmp as +a dependency. It is possible to install "qemu.qmp" independently and +then use the rest of these packages without installing them, but be +advised that if future dependencies are introduced, bypassing the +installation phase may introduce breakages to your script in the future. + +That said, you can use these packages without installing them by either: 1. Set your PYTHONPATH environment variable to include this source directory, e.g. ``~/src/qemu/python``. See @@ -61,8 +81,26 @@ invoke them without installation, you can invoke e.g.: ``> PYTHONPATH=~/src/qemu/python python3 -m qemu.qmp.qmp_shell`` +**It is strongly advised to just use the configure-time venv instead.** +After running configure, simply use the run script available in the QEMU +build directory: + +``> $builddir/run qmp-shell`` + The mappings between console script name and python module path can be -found in ``setup.cfg``. +found in ``setup.cfg``, but the console scripts available are listed +here for reference: + +* ``qemu-ga-client`` +* ``qmp-shell`` +* ``qmp-shell-wrap`` +* ``qmp-tui`` (prototype urwid interface for async QMP) +* ``qom`` +* ``qom-fuse`` (requires fusepy to be installed!) +* ``qom-get`` +* ``qom-list`` +* ``qom-set`` +* ``qom-tree`` Files in this directory @@ -70,8 +108,6 @@ Files in this directory - ``qemu/`` Python 'qemu' namespace package source directory. - ``tests/`` Python package tests directory. -- ``avocado.cfg`` Configuration for the Avocado test-runner. - Used by ``make check`` et al. - ``Makefile`` provides some common testing/installation invocations. Try ``make help`` to see available targets. - ``MANIFEST.in`` is read by python setuptools, it specifies additional files diff --git a/python/avocado.cfg b/python/avocado.cfg deleted file mode 100644 index a4604200594e..000000000000 --- a/python/avocado.cfg +++ /dev/null @@ -1,13 +0,0 @@ -[run] -test_runner = nrunner - -[simpletests] -# Don't show stdout/stderr in the test *summary* -status.failure_fields = ['status'] - -[job] -# Don't show the full debug.log output; only select stdout/stderr. -output.testlogs.logfiles = ['stdout', 'stderr'] - -# Show full stdout/stderr only on tests that FAIL -output.testlogs.statuses = ['FAIL'] diff --git a/python/qemu/qmp/__init__.py b/python/qemu/qmp/__init__.py deleted file mode 100644 index 058139dc3cac..000000000000 --- a/python/qemu/qmp/__init__.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -QEMU Monitor Protocol (QMP) development library & tooling. - -This package provides a fairly low-level class for communicating -asynchronously with QMP protocol servers, as implemented by QEMU, the -QEMU Guest Agent, and the QEMU Storage Daemon. - -`QMPClient` provides the main functionality of this package. All errors -raised by this library derive from `QMPError`, see `qmp.error` for -additional detail. See `qmp.events` for an in-depth tutorial on -managing QMP events. -""" - -# Copyright (C) 2020-2022 John Snow for Red Hat, Inc. -# -# Authors: -# John Snow -# -# Based on earlier work by Luiz Capitulino . -# -# This work is licensed under the terms of the GNU LGPL, version 2 or -# later. See the COPYING file in the top-level directory. - -import logging - -from .error import QMPError -from .events import EventListener -from .message import Message -from .protocol import ( - ConnectError, - Runstate, - SocketAddrT, - StateError, -) -from .qmp_client import ExecInterruptedError, ExecuteError, QMPClient - - -# Suppress logging unless an application engages it. -logging.getLogger('qemu.qmp').addHandler(logging.NullHandler()) - - -# IMPORTANT: When modifying this list, update the Sphinx overview docs. -# Anything visible in the qemu.qmp namespace should be on the overview page. -__all__ = ( - # Classes, most to least important - 'QMPClient', - 'Message', - 'EventListener', - 'Runstate', - - # Exceptions, most generic to most explicit - 'QMPError', - 'StateError', - 'ConnectError', - 'ExecuteError', - 'ExecInterruptedError', - - # Type aliases - 'SocketAddrT', -) diff --git a/python/qemu/qmp/error.py b/python/qemu/qmp/error.py deleted file mode 100644 index c87b078f620a..000000000000 --- a/python/qemu/qmp/error.py +++ /dev/null @@ -1,53 +0,0 @@ -""" -QMP Error Classes - -This package seeks to provide semantic error classes that are intended -to be used directly by clients when they would like to handle particular -semantic failures (e.g. "failed to connect") without needing to know the -enumeration of possible reasons for that failure. - -QMPError serves as the ancestor for all exceptions raised by this -package, and is suitable for use in handling semantic errors from this -library. In most cases, individual public methods will attempt to catch -and re-encapsulate various exceptions to provide a semantic -error-handling interface. - -.. admonition:: QMP Exception Hierarchy Reference - - | `Exception` - | +-- `QMPError` - | +-- `ConnectError` - | +-- `StateError` - | +-- `ExecInterruptedError` - | +-- `ExecuteError` - | +-- `ListenerError` - | +-- `ProtocolError` - | +-- `DeserializationError` - | +-- `UnexpectedTypeError` - | +-- `ServerParseError` - | +-- `BadReplyError` - | +-- `GreetingError` - | +-- `NegotiationError` -""" - - -class QMPError(Exception): - """Abstract error class for all errors originating from this package.""" - - -class ProtocolError(QMPError): - """ - Abstract error class for protocol failures. - - Semantically, these errors are generally the fault of either the - protocol server or as a result of a bug in this library. - - :param error_message: Human-readable string describing the error. - """ - def __init__(self, error_message: str, *args: object): - super().__init__(error_message, *args) - #: Human-readable error message, without any prefix. - self.error_message: str = error_message - - def __str__(self) -> str: - return self.error_message diff --git a/python/qemu/qmp/events.py b/python/qemu/qmp/events.py deleted file mode 100644 index cfb5f0ac621f..000000000000 --- a/python/qemu/qmp/events.py +++ /dev/null @@ -1,751 +0,0 @@ -""" -QMP Events and EventListeners - -Asynchronous QMP uses `EventListener` objects to listen for events. An -`EventListener` is a FIFO event queue that can be pre-filtered to listen -for only specific events. Each `EventListener` instance receives its own -copy of events that it hears, so events may be consumed without fear or -worry for depriving other listeners of events they need to hear. - - -EventListener Tutorial ----------------------- - -In all of the following examples, we assume that we have a `QMPClient` -instantiated named ``qmp`` that is already connected. For example: - -.. code:: python - - from qemu.qmp import QMPClient - - qmp = QMPClient('example-vm') - await qmp.connect('127.0.0.1', 1234) - - -`listener()` context blocks with one name -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The most basic usage is by using the `listener()` context manager to -construct them: - -.. code:: python - - with qmp.listener('STOP') as listener: - await qmp.execute('stop') - await listener.get() - -The listener is active only for the duration of the ‘with’ block. This -instance listens only for ‘STOP’ events. - - -`listener()` context blocks with two or more names -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Multiple events can be selected for by providing any ``Iterable[str]``: - -.. code:: python - - with qmp.listener(('STOP', 'RESUME')) as listener: - await qmp.execute('stop') - event = await listener.get() - assert event['event'] == 'STOP' - - await qmp.execute('cont') - event = await listener.get() - assert event['event'] == 'RESUME' - - -`listener()` context blocks with no names -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -By omitting names entirely, you can listen to ALL events. - -.. code:: python - - with qmp.listener() as listener: - await qmp.execute('stop') - event = await listener.get() - assert event['event'] == 'STOP' - -This isn’t a very good use case for this feature: In a non-trivial -running system, we may not know what event will arrive next. Grabbing -the top of a FIFO queue returning multiple kinds of events may be prone -to error. - - -Using async iterators to retrieve events -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you’d like to simply watch what events happen to arrive, you can use -the listener as an async iterator: - -.. code:: python - - with qmp.listener() as listener: - async for event in listener: - print(f"Event arrived: {event['event']}") - -This is analogous to the following code: - -.. code:: python - - with qmp.listener() as listener: - while True: - event = listener.get() - print(f"Event arrived: {event['event']}") - -This event stream will never end, so these blocks will never -terminate. Even if the QMP connection errors out prematurely, this -listener will go silent without raising an error. - - -Using asyncio.Task to concurrently retrieve events -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Since a listener’s event stream will never terminate, it is not likely -useful to use that form in a script. For longer-running clients, we can -create event handlers by using `asyncio.Task` to create concurrent -coroutines: - -.. code:: python - - async def print_events(listener): - try: - async for event in listener: - print(f"Event arrived: {event['event']}") - except asyncio.CancelledError: - return - - with qmp.listener() as listener: - task = asyncio.Task(print_events(listener)) - await qmp.execute('stop') - await qmp.execute('cont') - task.cancel() - await task - -However, there is no guarantee that these events will be received by the -time we leave this context block. Once the context block is exited, the -listener will cease to hear any new events, and becomes inert. - -Be mindful of the timing: the above example will *probably*– but does -not *guarantee*– that both STOP/RESUMED events will be printed. The -example below outlines how to use listeners outside of a context block. - - -Using `register_listener()` and `remove_listener()` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To create a listener with a longer lifetime, beyond the scope of a -single block, create a listener and then call `register_listener()`: - -.. code:: python - - class MyClient: - def __init__(self, qmp): - self.qmp = qmp - self.listener = EventListener() - - async def print_events(self): - try: - async for event in self.listener: - print(f"Event arrived: {event['event']}") - except asyncio.CancelledError: - return - - async def run(self): - self.task = asyncio.Task(self.print_events) - self.qmp.register_listener(self.listener) - await qmp.execute('stop') - await qmp.execute('cont') - - async def stop(self): - self.task.cancel() - await self.task - self.qmp.remove_listener(self.listener) - -The listener can be deactivated by using `remove_listener()`. When it is -removed, any possible pending events are cleared and it can be -re-registered at a later time. - - -Using the built-in all events listener -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The `QMPClient` object creates its own default listener named -:py:obj:`~Events.events` that can be used for the same purpose without -having to create your own: - -.. code:: python - - async def print_events(listener): - try: - async for event in listener: - print(f"Event arrived: {event['event']}") - except asyncio.CancelledError: - return - - task = asyncio.Task(print_events(qmp.events)) - - await qmp.execute('stop') - await qmp.execute('cont') - - task.cancel() - await task - - -Using both .get() and async iterators -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The async iterator and `get()` methods pull events from the same FIFO -queue. If you mix the usage of both, be aware: Events are emitted -precisely once per listener. - -If multiple contexts try to pull events from the same listener instance, -events are still emitted only precisely once. - -This restriction can be lifted by creating additional listeners. - - -Creating multiple listeners -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Additional `EventListener` objects can be created at-will. Each one -receives its own copy of events, with separate FIFO event queues. - -.. code:: python - - my_listener = EventListener() - qmp.register_listener(my_listener) - - await qmp.execute('stop') - copy1 = await my_listener.get() - copy2 = await qmp.events.get() - - assert copy1 == copy2 - -In this example, we await an event from both a user-created -`EventListener` and the built-in events listener. Both receive the same -event. - - -Clearing listeners -~~~~~~~~~~~~~~~~~~ - -`EventListener` objects can be cleared, clearing all events seen thus far: - -.. code:: python - - await qmp.execute('stop') - discarded = qmp.events.clear() - await qmp.execute('cont') - event = await qmp.events.get() - assert event['event'] == 'RESUME' - assert discarded[0]['event'] == 'STOP' - -`EventListener` objects are FIFO queues. If events are not consumed, -they will remain in the queue until they are witnessed or discarded via -`clear()`. FIFO queues will be drained automatically upon leaving a -context block, or when calling `remove_listener()`. - -Any events removed from the queue in this fashion will be returned by -the clear call. - - -Accessing listener history -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -`EventListener` objects record their history. Even after being cleared, -you can obtain a record of all events seen so far: - -.. code:: python - - await qmp.execute('stop') - await qmp.execute('cont') - qmp.events.clear() - - assert len(qmp.events.history) == 2 - assert qmp.events.history[0]['event'] == 'STOP' - assert qmp.events.history[1]['event'] == 'RESUME' - -The history is updated immediately and does not require the event to be -witnessed first. - - -Using event filters -~~~~~~~~~~~~~~~~~~~ - -`EventListener` objects can be given complex filtering criteria if names -are not sufficient: - -.. code:: python - - def job1_filter(event) -> bool: - event_data = event.get('data', {}) - event_job_id = event_data.get('id') - return event_job_id == "job1" - - with qmp.listener('JOB_STATUS_CHANGE', job1_filter) as listener: - await qmp.execute('blockdev-backup', arguments={'job-id': 'job1', ...}) - async for event in listener: - if event['data']['status'] == 'concluded': - break - -These filters might be most useful when parameterized. `EventListener` -objects expect a function that takes only a single argument (the raw -event, as a `Message`) and returns a bool; True if the event should be -accepted into the stream. You can create a function that adapts this -signature to accept configuration parameters: - -.. code:: python - - def job_filter(job_id: str) -> EventFilter: - def filter(event: Message) -> bool: - return event['data']['id'] == job_id - return filter - - with qmp.listener('JOB_STATUS_CHANGE', job_filter('job2')) as listener: - await qmp.execute('blockdev-backup', arguments={'job-id': 'job2', ...}) - async for event in listener: - if event['data']['status'] == 'concluded': - break - - -Activating an existing listener with `listen()` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Listeners with complex, long configurations can also be created manually -and activated temporarily by using `listen()` instead of `listener()`: - -.. code:: python - - listener = EventListener(('BLOCK_JOB_COMPLETED', 'BLOCK_JOB_CANCELLED', - 'BLOCK_JOB_ERROR', 'BLOCK_JOB_READY', - 'BLOCK_JOB_PENDING', 'JOB_STATUS_CHANGE')) - - with qmp.listen(listener): - await qmp.execute('blockdev-backup', arguments={'job-id': 'job3', ...}) - async for event in listener: - print(event) - if event['event'] == 'BLOCK_JOB_COMPLETED': - break - -Any events that are not witnessed by the time the block is left will be -cleared from the queue; entering the block is an implicit -`register_listener()` and leaving the block is an implicit -`remove_listener()`. - - -Activating multiple existing listeners with `listen()` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -While `listener()` is only capable of creating a single listener, -`listen()` is capable of activating multiple listeners simultaneously: - -.. code:: python - - def job_filter(job_id: str) -> EventFilter: - def filter(event: Message) -> bool: - return event['data']['id'] == job_id - return filter - - jobA = EventListener('JOB_STATUS_CHANGE', job_filter('jobA')) - jobB = EventListener('JOB_STATUS_CHANGE', job_filter('jobB')) - - with qmp.listen(jobA, jobB): - qmp.execute('blockdev-create', arguments={'job-id': 'jobA', ...}) - qmp.execute('blockdev-create', arguments={'job-id': 'jobB', ...}) - - async for event in jobA.get(): - if event['data']['status'] == 'concluded': - break - async for event in jobB.get(): - if event['data']['status'] == 'concluded': - break - - -Note that in the above example, we explicitly wait on jobA to conclude -first, and then wait for jobB to do the same. All we have guaranteed is -that the code that waits for jobA will not accidentally consume the -event intended for the jobB waiter. - - -Extending the `EventListener` class -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In the case that a more specialized `EventListener` is desired to -provide either more functionality or more compact syntax for specialized -cases, it can be extended. - -One of the key methods to extend or override is -:py:meth:`~EventListener.accept()`. The default implementation checks an -incoming message for: - -1. A qualifying name, if any :py:obj:`~EventListener.names` were - specified at initialization time -2. That :py:obj:`~EventListener.event_filter()` returns True. - -This can be modified however you see fit to change the criteria for -inclusion in the stream. - -For convenience, a ``JobListener`` class could be created that simply -bakes in configuration so it does not need to be repeated: - -.. code:: python - - class JobListener(EventListener): - def __init__(self, job_id: str): - super().__init__(('BLOCK_JOB_COMPLETED', 'BLOCK_JOB_CANCELLED', - 'BLOCK_JOB_ERROR', 'BLOCK_JOB_READY', - 'BLOCK_JOB_PENDING', 'JOB_STATUS_CHANGE')) - self.job_id = job_id - - def accept(self, event) -> bool: - if not super().accept(event): - return False - if event['event'] in ('BLOCK_JOB_PENDING', 'JOB_STATUS_CHANGE'): - return event['data']['id'] == job_id - return event['data']['device'] == job_id - -From here on out, you can conjure up a custom-purpose listener that -listens only for job-related events for a specific job-id easily: - -.. code:: python - - listener = JobListener('job4') - with qmp.listener(listener): - await qmp.execute('blockdev-backup', arguments={'job-id': 'job4', ...}) - async for event in listener: - print(event) - if event['event'] == 'BLOCK_JOB_COMPLETED': - break - - -Experimental Interfaces & Design Issues ---------------------------------------- - -These interfaces are not ones I am sure I will keep or otherwise modify -heavily. - -qmp.listen()’s type signature -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -`listen()` does not return anything, because it was assumed the caller -already had a handle to the listener. However, for -``qmp.listen(EventListener())`` forms, the caller will not have saved a -handle to the listener. - -Because this function can accept *many* listeners, I found it hard to -accurately type in a way where it could be used in both “one” or “many” -forms conveniently and in a statically type-safe manner. - -Ultimately, I removed the return altogether, but perhaps with more time -I can work out a way to re-add it. - - -API Reference -------------- - -""" - -import asyncio -from contextlib import contextmanager -import logging -from typing import ( - AsyncIterator, - Callable, - Iterable, - Iterator, - List, - Optional, - Set, - Tuple, - Union, -) - -from .error import QMPError -from .message import Message - - -EventNames = Union[str, Iterable[str], None] -EventFilter = Callable[[Message], bool] - - -class ListenerError(QMPError): - """ - Generic error class for `EventListener`-related problems. - """ - - -class EventListener: - """ - Selectively listens for events with runtime configurable filtering. - - This class is designed to be directly usable for the most common cases, - but it can be extended to provide more rigorous control. - - :param names: - One or more names of events to listen for. - When not provided, listen for ALL events. - :param event_filter: - An optional event filtering function. - When names are also provided, this acts as a secondary filter. - - When ``names`` and ``event_filter`` are both provided, the names - will be filtered first, and then the filter function will be called - second. The event filter function can assume that the format of the - event is a known format. - """ - def __init__( - self, - names: EventNames = None, - event_filter: Optional[EventFilter] = None, - ): - # Queue of 'heard' events yet to be witnessed by a caller. - self._queue: 'asyncio.Queue[Message]' = asyncio.Queue() - - # Intended as a historical record, NOT a processing queue or backlog. - self._history: List[Message] = [] - - #: Primary event filter, based on one or more event names. - self.names: Set[str] = set() - if isinstance(names, str): - self.names.add(names) - elif names is not None: - self.names.update(names) - - #: Optional, secondary event filter. - self.event_filter: Optional[EventFilter] = event_filter - - def __repr__(self) -> str: - args: List[str] = [] - if self.names: - args.append(f"names={self.names!r}") - if self.event_filter: - args.append(f"event_filter={self.event_filter!r}") - - if self._queue.qsize(): - state = f"" - else: - state = '' - - argstr = ", ".join(args) - return f"{type(self).__name__}{state}({argstr})" - - @property - def history(self) -> Tuple[Message, ...]: - """ - A read-only history of all events seen so far. - - This represents *every* event, including those not yet witnessed - via `get()` or ``async for``. It persists between `clear()` - calls and is immutable. - """ - return tuple(self._history) - - def accept(self, event: Message) -> bool: - """ - Determine if this listener accepts this event. - - This method determines which events will appear in the stream. - The default implementation simply checks the event against the - list of names and the event_filter to decide if this - `EventListener` accepts a given event. It can be - overridden/extended to provide custom listener behavior. - - User code is not expected to need to invoke this method. - - :param event: The event under consideration. - :return: `True`, if this listener accepts this event. - """ - name_ok = (not self.names) or (event['event'] in self.names) - return name_ok and ( - (not self.event_filter) or self.event_filter(event) - ) - - async def put(self, event: Message) -> None: - """ - Conditionally put a new event into the FIFO queue. - - This method is not designed to be invoked from user code, and it - should not need to be overridden. It is a public interface so - that `QMPClient` has an interface by which it can inform - registered listeners of new events. - - The event will be put into the queue if - :py:meth:`~EventListener.accept()` returns `True`. - - :param event: The new event to put into the FIFO queue. - """ - if not self.accept(event): - return - - self._history.append(event) - await self._queue.put(event) - - async def get(self) -> Message: - """ - Wait for the very next event in this stream. - - If one is already available, return that one. - """ - return await self._queue.get() - - def empty(self) -> bool: - """ - Return `True` if there are no pending events. - """ - return self._queue.empty() - - def clear(self) -> List[Message]: - """ - Clear this listener of all pending events. - - Called when an `EventListener` is being unregistered, this clears the - pending FIFO queue synchronously. It can be also be used to - manually clear any pending events, if desired. - - :return: The cleared events, if any. - - .. warning:: - Take care when discarding events. Cleared events will be - silently tossed on the floor. All events that were ever - accepted by this listener are visible in `history()`. - """ - events = [] - while True: - try: - events.append(self._queue.get_nowait()) - except asyncio.QueueEmpty: - break - - return events - - def __aiter__(self) -> AsyncIterator[Message]: - return self - - async def __anext__(self) -> Message: - """ - Enables the `EventListener` to function as an async iterator. - - It may be used like this: - - .. code:: python - - async for event in listener: - print(event) - - These iterators will never terminate of their own accord; you - must provide break conditions or otherwise prepare to run them - in an `asyncio.Task` that can be cancelled. - """ - return await self.get() - - -class Events: - """ - Events is a mix-in class that adds event functionality to the QMP class. - - It's designed specifically as a mix-in for `QMPClient`, and it - relies upon the class it is being mixed into having a 'logger' - property. - """ - def __init__(self) -> None: - self._listeners: List[EventListener] = [] - - #: Default, all-events `EventListener`. See `qmp.events` for more info. - self.events: EventListener = EventListener() - self.register_listener(self.events) - - # Parent class needs to have a logger - self.logger: logging.Logger - - async def _event_dispatch(self, msg: Message) -> None: - """ - Given a new event, propagate it to all of the active listeners. - - :param msg: The event to propagate. - """ - for listener in self._listeners: - await listener.put(msg) - - def register_listener(self, listener: EventListener) -> None: - """ - Register and activate an `EventListener`. - - :param listener: The listener to activate. - :raise ListenerError: If the given listener is already registered. - """ - if listener in self._listeners: - raise ListenerError("Attempted to re-register existing listener") - self.logger.debug("Registering %s.", str(listener)) - self._listeners.append(listener) - - def remove_listener(self, listener: EventListener) -> None: - """ - Unregister and deactivate an `EventListener`. - - The removed listener will have its pending events cleared via - `clear()`. The listener can be re-registered later when - desired. - - :param listener: The listener to deactivate. - :raise ListenerError: If the given listener is not registered. - """ - if listener == self.events: - raise ListenerError("Cannot remove the default listener.") - self.logger.debug("Removing %s.", str(listener)) - listener.clear() - self._listeners.remove(listener) - - @contextmanager - def listen(self, *listeners: EventListener) -> Iterator[None]: - r""" - Context manager: Temporarily listen with an `EventListener`. - - Accepts one or more `EventListener` objects and registers them, - activating them for the duration of the context block. - - `EventListener` objects will have any pending events in their - FIFO queue cleared upon exiting the context block, when they are - deactivated. - - :param \*listeners: One or more EventListeners to activate. - :raise ListenerError: If the given listener(s) are already active. - """ - _added = [] - - try: - for listener in listeners: - self.register_listener(listener) - _added.append(listener) - - yield - - finally: - for listener in _added: - self.remove_listener(listener) - - @contextmanager - def listener( - self, - names: EventNames = (), - event_filter: Optional[EventFilter] = None - ) -> Iterator[EventListener]: - """ - Context manager: Temporarily listen with a new `EventListener`. - - Creates an `EventListener` object and registers it, activating - it for the duration of the context block. - - :param names: - One or more names of events to listen for. - When not provided, listen for ALL events. - :param event_filter: - An optional event filtering function. - When names are also provided, this acts as a secondary filter. - - :return: The newly created and active `EventListener`. - """ - listener = EventListener(names, event_filter) - with self.listen(listener): - yield listener diff --git a/python/qemu/qmp/legacy.py b/python/qemu/qmp/legacy.py deleted file mode 100644 index 060ed0eb9d45..000000000000 --- a/python/qemu/qmp/legacy.py +++ /dev/null @@ -1,339 +0,0 @@ -""" -(Legacy) Sync QMP Wrapper - -This module provides the `QEMUMonitorProtocol` class, which is a -synchronous wrapper around `QMPClient`. - -Its design closely resembles that of the original QEMUMonitorProtocol -class, originally written by Luiz Capitulino. It is provided here for -compatibility with scripts inside the QEMU source tree that expect the -old interface. -""" - -# -# Copyright (C) 2009-2022 Red Hat Inc. -# -# Authors: -# Luiz Capitulino -# John Snow -# -# This work is licensed under the terms of the GNU GPL, version 2. See -# the COPYING file in the top-level directory. -# - -import asyncio -import socket -from types import TracebackType -from typing import ( - Any, - Awaitable, - Dict, - List, - Optional, - Type, - TypeVar, - Union, -) - -from .error import QMPError -from .protocol import Runstate, SocketAddrT -from .qmp_client import QMPClient -from .util import get_or_create_event_loop - - -#: QMPMessage is an entire QMP message of any kind. -QMPMessage = Dict[str, Any] - -#: QMPReturnValue is the 'return' value of a command. -QMPReturnValue = object - -#: QMPObject is any object in a QMP message. -QMPObject = Dict[str, object] - -# QMPMessage can be outgoing commands or incoming events/returns. -# QMPReturnValue is usually a dict/json object, but due to QAPI's -# 'command-returns-exceptions', it can actually be anything. -# -# {'return': {}} is a QMPMessage, -# {} is the QMPReturnValue. - - -class QMPBadPortError(QMPError): - """ - Unable to parse socket address: Port was non-numerical. - """ - - -class QEMUMonitorProtocol: - """ - Provide an API to connect to QEMU via QEMU Monitor Protocol (QMP) - and then allow to handle commands and events. - - :param address: QEMU address, can be a unix socket path (string), a tuple - in the form ( address, port ) for a TCP connection, or an - existing `socket.socket` object. - :param server: Act as the socket server. (See 'accept') - Not applicable when passing a socket directly. - :param nickname: Optional nickname used for logging. - """ - - def __init__(self, - address: Union[SocketAddrT, socket.socket], - server: bool = False, - nickname: Optional[str] = None): - - if server and isinstance(address, socket.socket): - raise ValueError( - "server argument should be False when passing a socket") - - self._qmp = QMPClient(nickname) - self._address = address - self._timeout: Optional[float] = None - - # This is a sync shim intended for use in fully synchronous - # programs. Create and set an event loop if necessary. - self._aloop = get_or_create_event_loop() - - if server: - assert not isinstance(self._address, socket.socket) - self._sync(self._qmp.start_server(self._address)) - - _T = TypeVar('_T') - - def _sync( - self, future: Awaitable[_T], timeout: Optional[float] = None - ) -> _T: - return self._aloop.run_until_complete( - asyncio.wait_for(future, timeout=timeout) - ) - - def _get_greeting(self) -> Optional[QMPMessage]: - if self._qmp.greeting is not None: - # pylint: disable=protected-access - return self._qmp.greeting._asdict() - return None - - def __enter__(self: _T) -> _T: - # Implement context manager enter function. - return self - - def __exit__(self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType]) -> None: - # Implement context manager exit function. - self.close() - - @classmethod - def parse_address(cls, address: str) -> SocketAddrT: - """ - Parse a string into a QMP address. - - Figure out if the argument is in the port:host form. - If it's not, it's probably a file path. - """ - components = address.split(':') - if len(components) == 2: - try: - port = int(components[1]) - except ValueError: - msg = f"Bad port: '{components[1]}' in '{address}'." - raise QMPBadPortError(msg) from None - return (components[0], port) - - # Treat as filepath. - return address - - def connect(self, negotiate: bool = True) -> Optional[QMPMessage]: - """ - Connect to the QMP Monitor and perform capabilities negotiation. - - :return: QMP greeting dict, or None if negotiate is false - :raise ConnectError: on connection errors - """ - self._qmp.await_greeting = negotiate - self._qmp.negotiate = negotiate - - self._sync( - self._qmp.connect(self._address) - ) - return self._get_greeting() - - def accept(self, timeout: Optional[float] = 15.0) -> QMPMessage: - """ - Await connection from QMP Monitor and perform capabilities negotiation. - - :param timeout: - timeout in seconds (nonnegative float number, or None). - If None, there is no timeout, and this may block forever. - - :return: QMP greeting dict - :raise ConnectError: on connection errors - """ - self._qmp.await_greeting = True - self._qmp.negotiate = True - - self._sync(self._qmp.accept(), timeout) - - ret = self._get_greeting() - assert ret is not None - return ret - - def cmd_obj(self, qmp_cmd: QMPMessage) -> QMPMessage: - """ - Send a QMP command to the QMP Monitor. - - :param qmp_cmd: QMP command to be sent as a Python dict - :return: QMP response as a Python dict - """ - return dict( - self._sync( - # pylint: disable=protected-access - - # _raw() isn't a public API, because turning off - # automatic ID assignment is discouraged. For - # compatibility with iotests *only*, do it anyway. - self._qmp._raw(qmp_cmd, assign_id=False), - self._timeout - ) - ) - - def cmd_raw(self, name: str, - args: Optional[Dict[str, object]] = None) -> QMPMessage: - """ - Build a QMP command and send it to the QMP Monitor. - - :param name: command name (string) - :param args: command arguments (dict) - """ - qmp_cmd: QMPMessage = {'execute': name} - if args: - qmp_cmd['arguments'] = args - return self.cmd_obj(qmp_cmd) - - def cmd(self, cmd: str, **kwds: object) -> QMPReturnValue: - """ - Build and send a QMP command to the monitor, report errors if any - """ - return self._sync( - self._qmp.execute(cmd, kwds), - self._timeout - ) - - def pull_event(self, - wait: Union[bool, float] = False) -> Optional[QMPMessage]: - """ - Pulls a single event. - - :param wait: - If False or 0, do not wait. Return None if no events ready. - If True, wait forever until the next event. - Otherwise, wait for the specified number of seconds. - - :raise asyncio.TimeoutError: - When a timeout is requested and the timeout period elapses. - - :return: The first available QMP event, or None. - """ - # Kick the event loop to allow events to accumulate - self._sync(asyncio.sleep(0)) - - if not wait: - # wait is False/0: "do not wait, do not except." - if self._qmp.events.empty(): - return None - - # If wait is 'True', wait forever. If wait is False/0, the events - # queue must not be empty; but it still needs some real amount - # of time to complete. - timeout = None - if wait and isinstance(wait, float): - timeout = wait - - return dict( - self._sync( - self._qmp.events.get(), - timeout - ) - ) - - def get_events(self, wait: Union[bool, float] = False) -> List[QMPMessage]: - """ - Get a list of QMP events and clear all pending events. - - :param wait: - If False or 0, do not wait. Return None if no events ready. - If True, wait until we have at least one event. - Otherwise, wait for up to the specified number of seconds for at - least one event. - - :raise asyncio.TimeoutError: - When a timeout is requested and the timeout period elapses. - - :return: A list of QMP events. - """ - events = [dict(x) for x in self._qmp.events.clear()] - if events: - return events - - event = self.pull_event(wait) - return [event] if event is not None else [] - - def clear_events(self) -> None: - """Clear current list of pending events.""" - self._qmp.events.clear() - - def close(self) -> None: - """Close the connection.""" - self._sync( - self._qmp.disconnect() - ) - - def settimeout(self, timeout: Optional[float]) -> None: - """ - Set the timeout for QMP RPC execution. - - This timeout affects the `cmd`, `cmd_obj`, and `cmd_raw` methods. - The `accept`, `pull_event` and `get_events` methods have their - own configurable timeouts. - - :param timeout: - timeout in seconds, or None. - None will wait indefinitely. - """ - self._timeout = timeout - - def send_fd_scm(self, fd: int) -> None: - """ - Send a file descriptor to the remote via SCM_RIGHTS. - """ - self._qmp.send_fd_scm(fd) - - def __del__(self) -> None: - if self._qmp.runstate != Runstate.IDLE: - self._qmp.logger.warning( - "QEMUMonitorProtocol object garbage collected without a prior " - "call to close()" - ) - - if not self._aloop.is_running(): - if self._qmp.runstate != Runstate.IDLE: - # If the user neglected to close the QMP session and we - # are not currently running in an asyncio context, we - # have the opportunity to close the QMP session. If we - # do not do this, the error messages presented over - # dangling async resources may not make any sense to the - # user. - self.close() - - if self._qmp.runstate != Runstate.IDLE: - # If QMP is still not quiesced, it means that the garbage - # collector ran from a context within the event loop and we - # are simply too late to take any corrective action. Raise - # our own error to give meaningful feedback to the user in - # order to prevent pages of asyncio stacktrace jargon. - raise QMPError( - "QEMUMonitorProtocol.close() was not called before object was " - "garbage collected, and could not be closed due to GC running " - "in the event loop" - ) diff --git a/python/qemu/qmp/message.py b/python/qemu/qmp/message.py deleted file mode 100644 index dabb8ec360ee..000000000000 --- a/python/qemu/qmp/message.py +++ /dev/null @@ -1,217 +0,0 @@ -""" -QMP Message Format - -This module provides the `Message` class, which represents a single QMP -message sent to or from the server. -""" - -import json -from json import JSONDecodeError -from typing import ( - Dict, - Iterator, - Mapping, - MutableMapping, - Optional, - Union, -) - -from .error import ProtocolError - - -class Message(MutableMapping[str, object]): - """ - Represents a single QMP protocol message. - - QMP uses JSON objects as its basic communicative unit; so this - Python object is a :py:obj:`~collections.abc.MutableMapping`. It may - be instantiated from either another mapping (like a `dict`), or from - raw `bytes` that still need to be deserialized. - - Once instantiated, it may be treated like any other - :py:obj:`~collections.abc.MutableMapping`:: - - >>> msg = Message(b'{"hello": "world"}') - >>> assert msg['hello'] == 'world' - >>> msg['id'] = 'foobar' - >>> print(msg) - { - "hello": "world", - "id": "foobar" - } - - It can be converted to `bytes`:: - - >>> msg = Message({"hello": "world"}) - >>> print(bytes(msg)) - b'{"hello":"world","id":"foobar"}' - - Or back into a garden-variety `dict`:: - - >>> dict(msg) - {'hello': 'world'} - - Or pretty-printed:: - - >>> print(str(msg)) - { - "hello": "world" - } - - :param value: Initial value, if any. - :param eager: - When `True`, attempt to serialize or deserialize the initial value - immediately, so that conversion exceptions are raised during - the call to ``__init__()``. - - """ - # pylint: disable=too-many-ancestors - - def __init__(self, - value: Union[bytes, Mapping[str, object]] = b'{}', *, - eager: bool = True): - self._data: Optional[bytes] = None - self._obj: Optional[Dict[str, object]] = None - - if isinstance(value, bytes): - self._data = value - if eager: - self._obj = self._deserialize(self._data) - else: - self._obj = dict(value) - if eager: - self._data = self._serialize(self._obj) - - # Methods necessary to implement the MutableMapping interface, see: - # https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableMapping - - # We get pop, popitem, clear, update, setdefault, __contains__, - # keys, items, values, get, __eq__ and __ne__ for free. - - def __getitem__(self, key: str) -> object: - return self._object[key] - - def __setitem__(self, key: str, value: object) -> None: - self._object[key] = value - self._data = None - - def __delitem__(self, key: str) -> None: - del self._object[key] - self._data = None - - def __iter__(self) -> Iterator[str]: - return iter(self._object) - - def __len__(self) -> int: - return len(self._object) - - # Dunder methods not related to MutableMapping: - - def __repr__(self) -> str: - if self._obj is not None: - return f"Message({self._object!r})" - return f"Message({bytes(self)!r})" - - def __str__(self) -> str: - """Pretty-printed representation of this QMP message.""" - return json.dumps(self._object, indent=2) - - def __bytes__(self) -> bytes: - """bytes representing this QMP message.""" - if self._data is None: - self._data = self._serialize(self._obj or {}) - return self._data - - # Conversion Methods - - @property - def _object(self) -> Dict[str, object]: - """ - A `dict` representing this QMP message. - - Generated on-demand, if required. This property is private - because it returns an object that could be used to invalidate - the internal state of the `Message` object. - """ - if self._obj is None: - self._obj = self._deserialize(self._data or b'{}') - return self._obj - - @classmethod - def _serialize(cls, value: object) -> bytes: - """ - Serialize a JSON object as `bytes`. - - :raise ValueError: When the object cannot be serialized. - :raise TypeError: When the object cannot be serialized. - - :return: `bytes` ready to be sent over the wire. - """ - return json.dumps(value, separators=(',', ':')).encode('utf-8') - - @classmethod - def _deserialize(cls, data: bytes) -> Dict[str, object]: - """ - Deserialize JSON `bytes` into a native Python `dict`. - - :raise DeserializationError: - If JSON deserialization fails for any reason. - :raise UnexpectedTypeError: - If the data does not represent a JSON object. - - :return: A `dict` representing this QMP message. - """ - try: - obj = json.loads(data) - except JSONDecodeError as err: - emsg = "Failed to deserialize QMP message." - raise DeserializationError(emsg, data) from err - if not isinstance(obj, dict): - raise UnexpectedTypeError( - "QMP message is not a JSON object.", - obj - ) - return obj - - -class DeserializationError(ProtocolError): - """ - A QMP message was not understood as JSON. - - When this Exception is raised, ``__cause__`` will be set to the - `json.JSONDecodeError` Exception, which can be interrogated for - further details. - - :param error_message: Human-readable string describing the error. - :param raw: The raw `bytes` that prompted the failure. - """ - def __init__(self, error_message: str, raw: bytes): - super().__init__(error_message, raw) - #: The raw `bytes` that were not understood as JSON. - self.raw: bytes = raw - - def __str__(self) -> str: - return "\n".join(( - super().__str__(), - f" raw bytes were: {str(self.raw)}", - )) - - -class UnexpectedTypeError(ProtocolError): - """ - A QMP message was JSON, but not a JSON object. - - :param error_message: Human-readable string describing the error. - :param value: The deserialized JSON value that wasn't an object. - """ - def __init__(self, error_message: str, value: object): - super().__init__(error_message, value) - #: The JSON value that was expected to be an object. - self.value: object = value - - def __str__(self) -> str: - strval = json.dumps(self.value, indent=2) - return "\n".join(( - super().__str__(), - f" json value was: {strval}", - )) diff --git a/python/qemu/qmp/models.py b/python/qemu/qmp/models.py deleted file mode 100644 index 7e0d0baf0386..000000000000 --- a/python/qemu/qmp/models.py +++ /dev/null @@ -1,146 +0,0 @@ -""" -QMP Data Models - -This module provides simplistic data classes that represent the few -structures that the QMP spec mandates; they are used to verify incoming -data to make sure it conforms to spec. -""" -# pylint: disable=too-few-public-methods - -from collections import abc -import copy -from typing import ( - Any, - Dict, - Mapping, - Optional, - Sequence, -) - - -class Model: - """ - Abstract data model, representing some QMP object of some kind. - - :param raw: The raw object to be validated. - :raise KeyError: If any required fields are absent. - :raise TypeError: If any required fields have the wrong type. - """ - def __init__(self, raw: Mapping[str, Any]): - self._raw = raw - - def _check_key(self, key: str) -> None: - if key not in self._raw: - raise KeyError(f"'{self._name}' object requires '{key}' member") - - def _check_value(self, key: str, type_: type, typestr: str) -> None: - assert key in self._raw - if not isinstance(self._raw[key], type_): - raise TypeError( - f"'{self._name}' member '{key}' must be a {typestr}" - ) - - def _check_member(self, key: str, type_: type, typestr: str) -> None: - self._check_key(key) - self._check_value(key, type_, typestr) - - @property - def _name(self) -> str: - return type(self).__name__ - - def __repr__(self) -> str: - return f"{self._name}({self._raw!r})" - - -class Greeting(Model): - """ - Defined in `interop/qmp-spec`, "Server Greeting" section. - - :param raw: The raw Greeting object. - :raise KeyError: If any required fields are absent. - :raise TypeError: If any required fields have the wrong type. - """ - def __init__(self, raw: Mapping[str, Any]): - super().__init__(raw) - #: 'QMP' member - self.QMP: QMPGreeting # pylint: disable=invalid-name - - self._check_member('QMP', abc.Mapping, "JSON object") - self.QMP = QMPGreeting(self._raw['QMP']) - - def _asdict(self) -> Dict[str, object]: - """ - For compatibility with the iotests sync QMP wrapper. - - The legacy QMP interface needs Greetings as a garden-variety Dict. - - This interface is private in the hopes that it will be able to - be dropped again in the near-future. Caller beware! - """ - return dict(copy.deepcopy(self._raw)) - - -class QMPGreeting(Model): - """ - Defined in `interop/qmp-spec`, "Server Greeting" section. - - :param raw: The raw QMPGreeting object. - :raise KeyError: If any required fields are absent. - :raise TypeError: If any required fields have the wrong type. - """ - def __init__(self, raw: Mapping[str, Any]): - super().__init__(raw) - #: 'version' member - self.version: Mapping[str, object] - #: 'capabilities' member - self.capabilities: Sequence[object] - - self._check_member('version', abc.Mapping, "JSON object") - self.version = self._raw['version'] - - self._check_member('capabilities', abc.Sequence, "JSON array") - self.capabilities = self._raw['capabilities'] - - -class ErrorResponse(Model): - """ - Defined in `interop/qmp-spec`, "Error" section. - - :param raw: The raw ErrorResponse object. - :raise KeyError: If any required fields are absent. - :raise TypeError: If any required fields have the wrong type. - """ - def __init__(self, raw: Mapping[str, Any]): - super().__init__(raw) - #: 'error' member - self.error: ErrorInfo - #: 'id' member - self.id: Optional[object] = None # pylint: disable=invalid-name - - self._check_member('error', abc.Mapping, "JSON object") - self.error = ErrorInfo(self._raw['error']) - - if 'id' in raw: - self.id = raw['id'] - - -class ErrorInfo(Model): - """ - Defined in `interop/qmp-spec`, "Error" section. - - :param raw: The raw ErrorInfo object. - :raise KeyError: If any required fields are absent. - :raise TypeError: If any required fields have the wrong type. - """ - def __init__(self, raw: Mapping[str, Any]): - super().__init__(raw) - #: 'class' member, with an underscore to avoid conflicts in Python. - self.class_: str - #: 'desc' member - self.desc: str - - self._check_member('class', str, "string") - self.class_ = self._raw['class'] - - self._check_member('desc', str, "string") - self.desc = self._raw['desc'] diff --git a/python/qemu/qmp/protocol.py b/python/qemu/qmp/protocol.py deleted file mode 100644 index 219d092a7920..000000000000 --- a/python/qemu/qmp/protocol.py +++ /dev/null @@ -1,1101 +0,0 @@ -""" -Generic Asynchronous Message-based Protocol Support - -This module provides a generic framework for sending and receiving -messages over an asyncio stream. `AsyncProtocol` is an abstract class -that implements the core mechanisms of a simple send/receive protocol, -and is designed to be extended. - -In this package, it is used as the implementation for the `QMPClient` -class. -""" - -# It's all the docstrings ... ! It's long for a good reason ^_^; -# pylint: disable=too-many-lines - -import asyncio -from asyncio import StreamReader, StreamWriter -from contextlib import asynccontextmanager -from enum import Enum -from functools import wraps -from inspect import iscoroutinefunction -import logging -import socket -from ssl import SSLContext -from typing import ( - Any, - AsyncGenerator, - Awaitable, - Callable, - Generic, - List, - Optional, - Tuple, - TypeVar, - Union, - cast, -) - -from .error import QMPError -from .util import ( - bottom_half, - exception_summary, - flush, - pretty_traceback, - upper_half, -) - - -T = TypeVar('T') -_U = TypeVar('_U') -_TaskFN = Callable[[], Awaitable[None]] # aka ``async def func() -> None`` - -InternetAddrT = Tuple[str, int] -UnixAddrT = str -SocketAddrT = Union[UnixAddrT, InternetAddrT] - -# Maximum allowable size of read buffer, default -_DEFAULT_READBUFLEN = 64 * 1024 - - -class Runstate(Enum): - """Protocol session runstate.""" - - #: Fully quiesced and disconnected. - IDLE = 0 - #: In the process of connecting or establishing a session. - CONNECTING = 1 - #: Fully connected and active session. - RUNNING = 2 - #: In the process of disconnecting. - #: Runstate may be returned to `IDLE` by calling `disconnect()`. - DISCONNECTING = 3 - - -class ConnectError(QMPError): - """ - Raised when the initial connection process has failed. - - This Exception always wraps a "root cause" exception that can be - interrogated for additional information. - - For example, when connecting to a non-existent socket:: - - await qmp.connect('not_found.sock') - # ConnectError: Failed to establish connection: - # [Errno 2] No such file or directory - - :param error_message: Human-readable string describing the error. - :param exc: The root-cause exception. - """ - def __init__(self, error_message: str, exc: Exception): - super().__init__(error_message, exc) - #: Human-readable error string - self.error_message: str = error_message - #: Wrapped root cause exception - self.exc: Exception = exc - - def __str__(self) -> str: - cause = str(self.exc) - if not cause: - # If there's no error string, use the exception name. - cause = exception_summary(self.exc) - return f"{self.error_message}: {cause}" - - -class StateError(QMPError): - """ - An API command (connect, execute, etc) was issued at an inappropriate time. - - This error is raised when a command like - :py:meth:`~AsyncProtocol.connect()` is called when the client is - already connected. - - :param error_message: Human-readable string describing the state violation. - :param state: The actual `Runstate` seen at the time of the violation. - :param required: The `Runstate` required to process this command. - """ - def __init__(self, error_message: str, - state: Runstate, required: Runstate): - super().__init__(error_message, state, required) - self.error_message = error_message - self.state = state - self.required = required - - def __str__(self) -> str: - return self.error_message - - -F = TypeVar('F', bound=Callable[..., Any]) # pylint: disable=invalid-name - - -# Don't Panic. -def require(required_state: Runstate) -> Callable[[F], F]: - """ - Decorator: protect a method so it can only be run in a certain `Runstate`. - - :param required_state: The `Runstate` required to invoke this method. - :raise StateError: When the required `Runstate` is not met. - """ - def _check(proto: 'AsyncProtocol[Any]') -> None: - name = type(proto).__name__ - if proto.runstate == required_state: - return - - if proto.runstate == Runstate.CONNECTING: - emsg = f"{name} is currently connecting." - elif proto.runstate == Runstate.DISCONNECTING: - emsg = (f"{name} is disconnecting." - " Call disconnect() to return to IDLE state.") - elif proto.runstate == Runstate.RUNNING: - emsg = f"{name} is already connected and running." - elif proto.runstate == Runstate.IDLE: - emsg = f"{name} is disconnected and idle." - else: - assert False - - raise StateError(emsg, proto.runstate, required_state) - - def _decorator(func: F) -> F: - # _decorator is the decorator that is built by calling the - # require() decorator factory; e.g.: - # - # @require(Runstate.IDLE) def foo(): ... - # will replace 'foo' with the result of '_decorator(foo)'. - - @wraps(func) - def _wrapper(proto: 'AsyncProtocol[Any]', - *args: Any, **kwargs: Any) -> Any: - _check(proto) - return func(proto, *args, **kwargs) - - @wraps(func) - async def _async_wrapper(proto: 'AsyncProtocol[Any]', - *args: Any, **kwargs: Any) -> Any: - _check(proto) - return await func(proto, *args, **kwargs) - - # Return the decorated method; F => Decorated[F] - # Use an async version when applicable, which - # preserves async signature generation in sphinx. - if iscoroutinefunction(func): - return cast(F, _async_wrapper) - return cast(F, _wrapper) - - # Return the decorator instance from the decorator factory. Phew! - return _decorator - - -class AsyncProtocol(Generic[T]): - """ - AsyncProtocol implements a generic async message-based protocol. - - This protocol assumes the basic unit of information transfer between - client and server is a "message", the details of which are left up - to the implementation. It assumes the sending and receiving of these - messages is full-duplex and not necessarily correlated; i.e. it - supports asynchronous inbound messages. - - It is designed to be extended by a specific protocol which provides - the implementations for how to read and send messages. These must be - defined in `_do_recv()` and `_do_send()`, respectively. - - Other callbacks have a default implementation, but are intended to be - either extended or overridden: - - - `_establish_session`: - The base implementation starts the reader/writer tasks. - A protocol implementation can override this call, inserting - actions to be taken prior to starting the reader/writer tasks - before the super() call; actions needing to occur afterwards - can be written after the super() call. - - `_on_message`: - Actions to be performed when a message is received. - - `_cb_outbound`: - Logging/Filtering hook for all outbound messages. - - `_cb_inbound`: - Logging/Filtering hook for all inbound messages. - This hook runs *before* `_on_message()`. - - :param name: - Name used for logging messages, if any. By default, messages - will log to 'qemu.qmp.protocol', but each individual connection - can be given its own logger by giving it a name; messages will - then log to 'qemu.qmp.protocol.${name}'. - :param readbuflen: - The maximum read buffer length of the underlying StreamReader - instance. - """ - # pylint: disable=too-many-instance-attributes - - #: Logger object for debugging messages from this connection. - logger = logging.getLogger(__name__) - - # ------------------------- - # Section: Public interface - # ------------------------- - - def __init__( - self, name: Optional[str] = None, - readbuflen: int = _DEFAULT_READBUFLEN - ) -> None: - self._name: Optional[str] - self.name = name - self.readbuflen = readbuflen - - # stream I/O - self._reader: Optional[StreamReader] = None - self._writer: Optional[StreamWriter] = None - - # Outbound Message queue - self._outgoing: asyncio.Queue[T] - - # Special, long-running tasks: - self._reader_task: Optional[asyncio.Future[None]] = None - self._writer_task: Optional[asyncio.Future[None]] = None - - # Aggregate of the above two tasks, used for Exception management. - self._bh_tasks: Optional[asyncio.Future[Tuple[None, None]]] = None - - #: Disconnect task. The disconnect implementation runs in a task - #: so that asynchronous disconnects (initiated by the - #: reader/writer) are allowed to wait for the reader/writers to - #: exit. - self._dc_task: Optional[asyncio.Future[None]] = None - - self._runstate = Runstate.IDLE - self._runstate_changed: Optional[asyncio.Event] = None - - # Server state for start_server() and _incoming() - self._server: Optional[asyncio.AbstractServer] = None - self._accepted: Optional[asyncio.Event] = None - - def __repr__(self) -> str: - cls_name = type(self).__name__ - tokens = [] - if self.name is not None: - tokens.append(f"name={self.name!r}") - tokens.append(f"runstate={self.runstate.name}") - return f"<{cls_name} {' '.join(tokens)}>" - - @property - def name(self) -> Optional[str]: - """ - The nickname for this connection, if any. - - This name is used for differentiating instances in debug output. - """ - return self._name - - @name.setter - def name(self, name: Optional[str]) -> None: - logger = logging.getLogger(__name__) - if name: - self.logger = logger.getChild(name) - else: - self.logger = logger - self._name = name - - @property # @upper_half - def runstate(self) -> Runstate: - """The current `Runstate` of the connection.""" - return self._runstate - - @upper_half - async def runstate_changed(self) -> Runstate: - """ - Wait for the `runstate` to change, then return that `Runstate`. - """ - await self._runstate_event.wait() - return self.runstate - - @upper_half - @require(Runstate.IDLE) - async def start_server_and_accept( - self, address: SocketAddrT, - ssl: Optional[SSLContext] = None - ) -> None: - """ - Accept a connection and begin processing message queues. - - If this call fails, `runstate` is guaranteed to be set back to - `IDLE`. This method is precisely equivalent to calling - `start_server()` followed by :py:meth:`~AsyncProtocol.accept()`. - - :param address: - Address to listen on; UNIX socket path or TCP address/port. - :param ssl: SSL context to use, if any. - - :raise StateError: When the `Runstate` is not `IDLE`. - :raise ConnectError: - When a connection or session cannot be established. - - This exception will wrap a more concrete one. In most cases, - the wrapped exception will be `OSError` or `EOFError`. If a - protocol-level failure occurs while establishing a new - session, the wrapped error may also be a `QMPError`. - - """ - await self.start_server(address, ssl) - await self.accept() - assert self.runstate == Runstate.RUNNING - - @upper_half - @require(Runstate.IDLE) - async def start_server(self, address: SocketAddrT, - ssl: Optional[SSLContext] = None) -> None: - """ - Start listening for an incoming connection, but do not wait for a peer. - - This method starts listening for an incoming connection, but - does not block waiting for a peer. This call will return - immediately after binding and listening on a socket. A later - call to :py:meth:`~AsyncProtocol.accept()` must be made in order - to finalize the incoming connection. - - :param address: - Address to listen on; UNIX socket path or TCP address/port. - :param ssl: SSL context to use, if any. - - :raise StateError: When the `Runstate` is not `IDLE`. - :raise ConnectError: - When the server could not start listening on this address. - - This exception will wrap a more concrete one. In most cases, - the wrapped exception will be `OSError`. - """ - async with self._session_guard('Failed to establish connection'): - await self._do_start_server(address, ssl) - assert self.runstate == Runstate.CONNECTING - - @upper_half - @require(Runstate.CONNECTING) - async def accept(self) -> None: - """ - Accept an incoming connection and begin processing message queues. - - Used after a previous call to `start_server()` to accept an - incoming connection. If this call fails, `runstate` is - guaranteed to be set back to `IDLE`. - - :raise StateError: When the `Runstate` is not `CONNECTING`. - :raise QMPError: When `start_server()` was not called first. - :raise ConnectError: - When a connection or session cannot be established. - - This exception will wrap a more concrete one. In most cases, - the wrapped exception will be `OSError` or `EOFError`. If a - protocol-level failure occurs while establishing a new - session, the wrapped error may also be an `QMPError`. - """ - if self._accepted is None: - raise QMPError("Cannot call accept() before start_server().") - async with self._session_guard('Failed to establish connection'): - await self._do_accept() - async with self._session_guard('Failed to establish session'): - await self._establish_session() - assert self.runstate == Runstate.RUNNING - - @upper_half - @require(Runstate.IDLE) - async def connect(self, address: Union[SocketAddrT, socket.socket], - ssl: Optional[SSLContext] = None) -> None: - """ - Connect to the server and begin processing message queues. - - If this call fails, `runstate` is guaranteed to be set back to `IDLE`. - - :param address: - Address to connect to; UNIX socket path or TCP address/port. - :param ssl: SSL context to use, if any. - - :raise StateError: When the `Runstate` is not `IDLE`. - :raise ConnectError: - When a connection or session cannot be established. - - This exception will wrap a more concrete one. In most cases, - the wrapped exception will be `OSError` or `EOFError`. If a - protocol-level failure occurs while establishing a new - session, the wrapped error may also be an `QMPError`. - """ - async with self._session_guard('Failed to establish connection'): - await self._do_connect(address, ssl) - async with self._session_guard('Failed to establish session'): - await self._establish_session() - assert self.runstate == Runstate.RUNNING - - @upper_half - async def disconnect(self) -> None: - """ - Disconnect and wait for all tasks to fully stop. - - If there was an exception that caused the reader/writers to - terminate prematurely, it will be raised here. - - :raise Exception: - When the reader or writer terminate unexpectedly. You can - expect to see `EOFError` if the server hangs up, or - `OSError` for connection-related issues. If there was a QMP - protocol-level problem, `ProtocolError` will be seen. - """ - self.logger.debug("disconnect() called.") - self._schedule_disconnect() - await self._wait_disconnect() - - # -------------------------- - # Section: Session machinery - # -------------------------- - - @asynccontextmanager - async def _session_guard(self, emsg: str) -> AsyncGenerator[None, None]: - """ - Async guard function used to roll back to `IDLE` on any error. - - On any Exception, the state machine will be reset back to - `IDLE`. Most Exceptions will be wrapped with `ConnectError`, but - `BaseException` events will be left alone (This includes - asyncio.CancelledError, even prior to Python 3.8). - - :param error_message: - Human-readable string describing what connection phase failed. - - :raise BaseException: - When `BaseException` occurs in the guarded block. - :raise ConnectError: - When any other error is encountered in the guarded block. - """ - try: - # Caller's code runs here. - yield - except BaseException as err: - self.logger.error("%s: %s", emsg, exception_summary(err)) - self.logger.debug("%s:\n%s\n", emsg, pretty_traceback()) - try: - # Reset the runstate back to IDLE. - await self.disconnect() - except: - # We don't expect any Exceptions from the disconnect function - # here, because we failed to connect in the first place. - # The disconnect() function is intended to perform - # only cannot-fail cleanup here, but you never know. - emsg = ( - "Unexpected bottom half exception. " - "This is a bug in the QMP library. " - "Please report it to and " - "CC: John Snow ." - ) - self.logger.critical("%s:\n%s\n", emsg, pretty_traceback()) - raise - - # CancelledError is an Exception with special semantic meaning; - # We do NOT want to wrap it up under ConnectError. - # NB: CancelledError is not a BaseException before Python 3.8 - if isinstance(err, asyncio.CancelledError): - raise - - # Any other kind of error can be treated as some kind of connection - # failure broadly. Inspect the 'exc' field to explore the root - # cause in greater detail. - if isinstance(err, Exception): - raise ConnectError(emsg, err) from err - - # Raise BaseExceptions un-wrapped, they're more important. - raise - - @property - def _runstate_event(self) -> asyncio.Event: - # asyncio.Event() objects should not be created prior to entrance into - # an event loop, so we can ensure we create it in the correct context. - # Create it on-demand *only* at the behest of an 'async def' method. - if not self._runstate_changed: - self._runstate_changed = asyncio.Event() - return self._runstate_changed - - @upper_half - @bottom_half - def _set_state(self, state: Runstate) -> None: - """ - Change the `Runstate` of the protocol connection. - - Signals the `runstate_changed` event. - """ - if state == self._runstate: - return - - self.logger.debug("Transitioning from '%s' to '%s'.", - str(self._runstate), str(state)) - self._runstate = state - self._runstate_event.set() - self._runstate_event.clear() - - @bottom_half - async def _stop_server(self) -> None: - """ - Stop listening for / accepting new incoming connections. - """ - if self._server is None: - return - - try: - self.logger.debug("Stopping server.") - self._server.close() - self.logger.debug("Server stopped.") - finally: - self._server = None - - @bottom_half # However, it does not run from the R/W tasks. - async def _incoming(self, - reader: asyncio.StreamReader, - writer: asyncio.StreamWriter) -> None: - """ - Accept an incoming connection and signal the upper_half. - - This method does the minimum necessary to accept a single - incoming connection. It signals back to the upper_half ASAP so - that any errors during session initialization can occur - naturally in the caller's stack. - - :param reader: Incoming `asyncio.StreamReader` - :param writer: Incoming `asyncio.StreamWriter` - """ - peer = writer.get_extra_info('peername', 'Unknown peer') - self.logger.debug("Incoming connection from %s", peer) - - if self._reader or self._writer: - # Sadly, we can have more than one pending connection - # because of https://bugs.python.org/issue46715 - # Close any extra connections we don't actually want. - self.logger.warning("Extraneous connection inadvertently accepted") - writer.close() - return - - # A connection has been accepted; stop listening for new ones. - assert self._accepted is not None - await self._stop_server() - self._reader, self._writer = (reader, writer) - self._accepted.set() - - @upper_half - async def _do_start_server(self, address: SocketAddrT, - ssl: Optional[SSLContext] = None) -> None: - """ - Start listening for an incoming connection, but do not wait for a peer. - - This method starts listening for an incoming connection, but does not - block waiting for a peer. This call will return immediately after - binding and listening to a socket. A later call to accept() must be - made in order to finalize the incoming connection. - - :param address: - Address to listen on; UNIX socket path or TCP address/port. - :param ssl: SSL context to use, if any. - - :raise OSError: For stream-related errors. - """ - assert self.runstate == Runstate.IDLE - self._set_state(Runstate.CONNECTING) - - self.logger.debug("Awaiting connection on %s ...", address) - self._accepted = asyncio.Event() - - if isinstance(address, tuple): - coro = asyncio.start_server( - self._incoming, - host=address[0], - port=address[1], - ssl=ssl, - backlog=1, - limit=self.readbuflen, - ) - else: - coro = asyncio.start_unix_server( - self._incoming, - path=address, - ssl=ssl, - backlog=1, - limit=self.readbuflen, - ) - - # Allow runstate watchers to witness 'CONNECTING' state; some - # failures in the streaming layer are synchronous and will not - # otherwise yield. - await asyncio.sleep(0) - - # This will start the server (bind(2), listen(2)). It will also - # call accept(2) if we yield, but we don't block on that here. - self._server = await coro - self.logger.debug("Server listening on %s", address) - - @upper_half - async def _do_accept(self) -> None: - """ - Wait for and accept an incoming connection. - - Requires that we have not yet accepted an incoming connection - from the upper_half, but it's OK if the server is no longer - running because the bottom_half has already accepted the - connection. - """ - assert self._accepted is not None - await self._accepted.wait() - assert self._server is None - self._accepted = None - - self.logger.debug("Connection accepted.") - - @upper_half - async def _do_connect(self, address: Union[SocketAddrT, socket.socket], - ssl: Optional[SSLContext] = None) -> None: - """ - Acting as the transport client, initiate a connection to a server. - - :param address: - Address to connect to; UNIX socket path or TCP address/port. - :param ssl: SSL context to use, if any. - - :raise OSError: For stream-related errors. - """ - assert self.runstate == Runstate.IDLE - self._set_state(Runstate.CONNECTING) - - # Allow runstate watchers to witness 'CONNECTING' state; some - # failures in the streaming layer are synchronous and will not - # otherwise yield. - await asyncio.sleep(0) - - if isinstance(address, socket.socket): - self.logger.debug("Connecting with existing socket: " - "fd=%d, family=%r, type=%r", - address.fileno(), address.family, address.type) - connect = asyncio.open_connection( - limit=self.readbuflen, - ssl=ssl, - sock=address, - ) - elif isinstance(address, tuple): - self.logger.debug("Connecting to %s ...", address) - connect = asyncio.open_connection( - address[0], - address[1], - ssl=ssl, - limit=self.readbuflen, - ) - else: - self.logger.debug("Connecting to file://%s ...", address) - connect = asyncio.open_unix_connection( - path=address, - ssl=ssl, - limit=self.readbuflen, - ) - - self._reader, self._writer = await connect - self.logger.debug("Connected.") - - @upper_half - async def _establish_session(self) -> None: - """ - Establish a new session. - - Starts the readers/writer tasks; subclasses may perform their - own negotiations here. The Runstate will be RUNNING upon - successful conclusion. - """ - assert self.runstate == Runstate.CONNECTING - - self._outgoing = asyncio.Queue() - - reader_coro = self._bh_loop_forever(self._bh_recv_message, 'Reader') - writer_coro = self._bh_loop_forever(self._bh_send_message, 'Writer') - - self._reader_task = asyncio.create_task(reader_coro) - self._writer_task = asyncio.create_task(writer_coro) - - self._bh_tasks = asyncio.gather( - self._reader_task, - self._writer_task, - ) - - self._set_state(Runstate.RUNNING) - await asyncio.sleep(0) # Allow runstate_event to process - - @upper_half - @bottom_half - def _schedule_disconnect(self) -> None: - """ - Initiate a disconnect; idempotent. - - This method is used both in the upper-half as a direct - consequence of `disconnect()`, and in the bottom-half in the - case of unhandled exceptions in the reader/writer tasks. - - It can be invoked no matter what the `runstate` is. - """ - if not self._dc_task: - self._set_state(Runstate.DISCONNECTING) - self.logger.debug("Scheduling disconnect.") - self._dc_task = asyncio.create_task(self._bh_disconnect()) - - @upper_half - async def _wait_disconnect(self) -> None: - """ - Waits for a previously scheduled disconnect to finish. - - This method will gather any bottom half exceptions and re-raise - the one that occurred first; presuming it to be the root cause - of any subsequent Exceptions. It is intended to be used in the - upper half of the call chain. - - :raise Exception: - Arbitrary exception re-raised on behalf of the reader/writer. - """ - assert self.runstate == Runstate.DISCONNECTING - assert self._dc_task - - aws: List[Awaitable[object]] = [self._dc_task] - if self._bh_tasks: - aws.insert(0, self._bh_tasks) - all_defined_tasks = asyncio.gather(*aws) - - # Ensure disconnect is done; Exception (if any) is not raised here: - await asyncio.wait((self._dc_task,)) - - try: - await all_defined_tasks # Raise Exceptions from the bottom half. - finally: - self._cleanup() - self._set_state(Runstate.IDLE) - - @upper_half - def _cleanup(self) -> None: - """ - Fully reset this object to a clean state and return to `IDLE`. - """ - def _paranoid_task_erase(task: Optional['asyncio.Future[_U]'] - ) -> Optional['asyncio.Future[_U]']: - # Help to erase a task, ENSURING it is fully quiesced first. - assert (task is None) or task.done() - return None if (task and task.done()) else task - - assert self.runstate == Runstate.DISCONNECTING - self._dc_task = _paranoid_task_erase(self._dc_task) - self._reader_task = _paranoid_task_erase(self._reader_task) - self._writer_task = _paranoid_task_erase(self._writer_task) - self._bh_tasks = _paranoid_task_erase(self._bh_tasks) - - self._reader = None - self._writer = None - self._accepted = None - - # NB: _runstate_changed cannot be cleared because we still need it to - # send the final runstate changed event ...! - - # ---------------------------- - # Section: Bottom Half methods - # ---------------------------- - - @bottom_half - async def _bh_disconnect(self) -> None: - """ - Disconnect and cancel all outstanding tasks. - - It is designed to be called from its task context, - :py:obj:`~AsyncProtocol._dc_task`. By running in its own task, - it is free to wait on any pending actions that may still need to - occur in either the reader or writer tasks. - """ - assert self.runstate == Runstate.DISCONNECTING - - def _done(task: Optional['asyncio.Future[Any]']) -> bool: - return task is not None and task.done() - - # If the server is running, stop it. - await self._stop_server() - - # Are we already in an error pathway? If either of the tasks are - # already done, or if we have no tasks but a reader/writer; we - # must be. - # - # NB: We can't use _bh_tasks to check for premature task - # completion, because it may not yet have had a chance to run - # and gather itself. - tasks = tuple(filter(None, (self._writer_task, self._reader_task))) - error_pathway = _done(self._reader_task) or _done(self._writer_task) - if not tasks: - error_pathway |= bool(self._reader) or bool(self._writer) - - try: - # Try to flush the writer, if possible. - # This *may* cause an error and force us over into the error path. - if not error_pathway: - await self._bh_flush_writer() - except BaseException as err: - error_pathway = True - emsg = "Failed to flush the writer" - self.logger.error("%s: %s", emsg, exception_summary(err)) - self.logger.debug("%s:\n%s\n", emsg, pretty_traceback()) - raise - finally: - # Cancel any still-running tasks (Won't raise): - if self._writer_task is not None and not self._writer_task.done(): - self.logger.debug("Cancelling writer task.") - self._writer_task.cancel() - if self._reader_task is not None and not self._reader_task.done(): - self.logger.debug("Cancelling reader task.") - self._reader_task.cancel() - - # Close out the tasks entirely (Won't raise): - if tasks: - self.logger.debug("Waiting for tasks to complete ...") - await asyncio.wait(tasks) - - # Lastly, close the stream itself. (*May raise*!): - await self._bh_close_stream(error_pathway) - self.logger.debug("Disconnected.") - - @bottom_half - async def _bh_flush_writer(self) -> None: - if not self._writer_task: - return - - self.logger.debug("Draining the outbound queue ...") - await self._outgoing.join() - if self._writer is not None: - self.logger.debug("Flushing the StreamWriter ...") - await flush(self._writer) - - @bottom_half - async def _bh_close_stream(self, error_pathway: bool = False) -> None: - # NB: Closing the writer also implicitly closes the reader. - if not self._writer: - return - - if not self._writer.is_closing(): - self.logger.debug("Closing StreamWriter.") - self._writer.close() - - self.logger.debug("Waiting for StreamWriter to close ...") - try: - await self._writer.wait_closed() - except Exception: # pylint: disable=broad-except - # It's hard to tell if the Stream is already closed or - # not. Even if one of the tasks has failed, it may have - # failed for a higher-layered protocol reason. The - # stream could still be open and perfectly fine. - # I don't know how to discern its health here. - - if error_pathway: - # We already know that *something* went wrong. Let's - # just trust that the Exception we already have is the - # better one to present to the user, even if we don't - # genuinely *know* the relationship between the two. - self.logger.debug( - "Discarding Exception from wait_closed:\n%s\n", - pretty_traceback(), - ) - else: - # Oops, this is a brand-new error! - raise - finally: - self.logger.debug("StreamWriter closed.") - - @bottom_half - async def _bh_loop_forever(self, async_fn: _TaskFN, name: str) -> None: - """ - Run one of the bottom-half methods in a loop forever. - - If the bottom half ever raises any exception, schedule a - disconnect that will terminate the entire loop. - - :param async_fn: The bottom-half method to run in a loop. - :param name: The name of this task, used for logging. - """ - try: - while True: - await async_fn() - except asyncio.CancelledError: - # We have been cancelled by _bh_disconnect, exit gracefully. - self.logger.debug("Task.%s: cancelled.", name) - return - except BaseException as err: - self.logger.log( - logging.INFO if isinstance(err, EOFError) else logging.ERROR, - "Task.%s: %s", - name, exception_summary(err) - ) - self.logger.debug("Task.%s: failure:\n%s\n", - name, pretty_traceback()) - self._schedule_disconnect() - raise - finally: - self.logger.debug("Task.%s: exiting.", name) - - @bottom_half - async def _bh_send_message(self) -> None: - """ - Wait for an outgoing message, then send it. - - Designed to be run in `_bh_loop_forever()`. - """ - msg = await self._outgoing.get() - try: - await self._send(msg) - finally: - self._outgoing.task_done() - - @bottom_half - async def _bh_recv_message(self) -> None: - """ - Wait for an incoming message and call `_on_message` to route it. - - Designed to be run in `_bh_loop_forever()`. - """ - msg = await self._recv() - await self._on_message(msg) - - # -------------------- - # Section: Message I/O - # -------------------- - - @upper_half - @bottom_half - def _cb_outbound(self, msg: T) -> T: - """ - Callback: outbound message hook. - - This is intended for subclasses to be able to add arbitrary - hooks to filter or manipulate outgoing messages. The base - implementation does nothing but log the message without any - manipulation of the message. - - :param msg: raw outbound message - :return: final outbound message - """ - self.logger.debug("--> %s", str(msg)) - return msg - - @upper_half - @bottom_half - def _cb_inbound(self, msg: T) -> T: - """ - Callback: inbound message hook. - - This is intended for subclasses to be able to add arbitrary - hooks to filter or manipulate incoming messages. The base - implementation does nothing but log the message without any - manipulation of the message. - - This method does not "handle" incoming messages; it is a filter. - The actual "endpoint" for incoming messages is `_on_message()`. - - :param msg: raw inbound message - :return: processed inbound message - """ - self.logger.debug("<-- %s", str(msg)) - return msg - - @upper_half - @bottom_half - async def _readline(self) -> bytes: - """ - Wait for a newline from the incoming reader. - - This method is provided as a convenience for upper-layer - protocols, as many are line-based. - - This method *may* return a sequence of bytes without a trailing - newline if EOF occurs, but *some* bytes were received. In this - case, the next call will raise `EOFError`. It is assumed that - the layer 5 protocol will decide if there is anything meaningful - to be done with a partial message. - - :raise OSError: For stream-related errors. - :raise EOFError: - If the reader stream is at EOF and there are no bytes to return. - :return: bytes, including the newline. - """ - assert self._reader is not None - msg_bytes = await self._reader.readline() - - if not msg_bytes: - if self._reader.at_eof(): - raise EOFError - - return msg_bytes - - @upper_half - @bottom_half - async def _do_recv(self) -> T: - """ - Abstract: Read from the stream and return a message. - - Very low-level; intended to only be called by `_recv()`. - """ - raise NotImplementedError - - @upper_half - @bottom_half - async def _recv(self) -> T: - """ - Read an arbitrary protocol message. - - .. warning:: - This method is intended primarily for `_bh_recv_message()` - to use in an asynchronous task loop. Using it outside of - this loop will "steal" messages from the normal routing - mechanism. It is safe to use prior to `_establish_session()`, - but should not be used otherwise. - - This method uses `_do_recv()` to retrieve the raw message, and - then transforms it using `_cb_inbound()`. - - :return: A single (filtered, processed) protocol message. - """ - message = await self._do_recv() - return self._cb_inbound(message) - - @upper_half - @bottom_half - def _do_send(self, msg: T) -> None: - """ - Abstract: Write a message to the stream. - - Very low-level; intended to only be called by `_send()`. - """ - raise NotImplementedError - - @upper_half - @bottom_half - async def _send(self, msg: T) -> None: - """ - Send an arbitrary protocol message. - - This method will transform any outgoing messages according to - `_cb_outbound()`. - - .. warning:: - Like `_recv()`, this method is intended to be called by - the writer task loop that processes outgoing - messages. Calling it directly may circumvent logic - implemented by the caller meant to correlate outgoing and - incoming messages. - - :raise OSError: For problems with the underlying stream. - """ - msg = self._cb_outbound(msg) - self._do_send(msg) - - @bottom_half - async def _on_message(self, msg: T) -> None: - """ - Called to handle the receipt of a new message. - - .. caution:: - This is executed from within the reader loop, so be advised - that waiting on either the reader or writer task will lead - to deadlock. Additionally, any unhandled exceptions will - directly cause the loop to halt, so logic may be best-kept - to a minimum if at all possible. - - :param msg: The incoming message, already logged/filtered. - """ - # Nothing to do in the abstract case. diff --git a/python/qemu/qmp/py.typed b/python/qemu/qmp/py.typed deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/python/qemu/qmp/qmp_client.py b/python/qemu/qmp/qmp_client.py deleted file mode 100644 index 8beccfe29d34..000000000000 --- a/python/qemu/qmp/qmp_client.py +++ /dev/null @@ -1,732 +0,0 @@ -""" -QMP Protocol Implementation - -This module provides the `QMPClient` class, which can be used to connect -and send commands to a QMP server such as QEMU. The QMP class can be -used to either connect to a listening server, or used to listen and -accept an incoming connection from that server. -""" - -import asyncio -import logging -import socket -import struct -from typing import ( - Dict, - List, - Mapping, - Optional, - Union, - cast, -) - -from .error import ProtocolError, QMPError -from .events import Events -from .message import Message -from .models import ErrorResponse, Greeting -from .protocol import AsyncProtocol, Runstate, require -from .util import ( - bottom_half, - exception_summary, - pretty_traceback, - upper_half, -) - - -class _WrappedProtocolError(ProtocolError): - """ - Abstract exception class for Protocol errors that wrap an Exception. - - :param error_message: Human-readable string describing the error. - :param exc: The root-cause exception. - """ - def __init__(self, error_message: str, exc: Exception): - super().__init__(error_message, exc) - self.exc = exc - - def __str__(self) -> str: - return f"{self.error_message}: {self.exc!s}" - - -class GreetingError(_WrappedProtocolError): - """ - An exception occurred during the Greeting phase. - - :param error_message: Human-readable string describing the error. - :param exc: The root-cause exception. - """ - - -class NegotiationError(_WrappedProtocolError): - """ - An exception occurred during the Negotiation phase. - - :param error_message: Human-readable string describing the error. - :param exc: The root-cause exception. - """ - - -class ExecuteError(QMPError): - """ - Exception raised by `QMPClient.execute()` on RPC failure. - - This exception is raised when the server received, interpreted, and - replied to a command successfully; but the command itself returned a - failure status. - - For example:: - - await qmp.execute('block-dirty-bitmap-add', - {'node': 'foo', 'name': 'my_bitmap'}) - # qemu.qmp.qmp_client.ExecuteError: - # Cannot find device='foo' nor node-name='foo' - - :param error_response: The RPC error response object. - :param sent: The sent RPC message that caused the failure. - :param received: The raw RPC error reply received. - """ - def __init__(self, error_response: ErrorResponse, - sent: Message, received: Message): - super().__init__(error_response, sent, received) - #: The sent `Message` that caused the failure - self.sent: Message = sent - #: The received `Message` that indicated failure - self.received: Message = received - #: The parsed error response - self.error: ErrorResponse = error_response - - @property - def error_class(self) -> str: - """The QMP error class""" - return self.error.error.class_ - - def __str__(self) -> str: - return self.error.error.desc - - -class ExecInterruptedError(QMPError): - """ - Exception raised by `execute()` (et al) when an RPC is interrupted. - - This error is raised when an `execute()` statement could not be - completed. This can occur because the connection itself was - terminated before a reply was received. The true cause of the - interruption will be available via `disconnect()`. - - The QMP protocol does not make it possible to know if a command - succeeded or failed after such an event; the client will need to - query the server to determine the state of the server on a - case-by-case basis. - - For example, ECONNRESET might look like this:: - - try: - await qmp.execute('query-block') - # ExecInterruptedError: Disconnected - except ExecInterruptedError: - await qmp.disconnect() - # ConnectionResetError: [Errno 104] Connection reset by peer - """ - - -class _MsgProtocolError(ProtocolError): - """ - Abstract error class for protocol errors that have a `Message` object. - - This Exception class is used for protocol errors where the `Message` - was mechanically understood, but was found to be inappropriate or - malformed. - - :param error_message: Human-readable string describing the error. - :param msg: The QMP `Message` that caused the error. - """ - def __init__(self, error_message: str, msg: Message, *args: object): - super().__init__(error_message, msg, *args) - #: The received `Message` that caused the error. - self.msg: Message = msg - - def __str__(self) -> str: - return "\n".join([ - super().__str__(), - f" Message was: {str(self.msg)}\n", - ]) - - -class ServerParseError(_MsgProtocolError): - """ - The Server sent a `Message` indicating parsing failure. - - i.e. A reply has arrived from the server, but it is missing the "ID" - field, indicating a parsing error. - - :param error_message: Human-readable string describing the error. - :param msg: The QMP `Message` that caused the error. - """ - - -class BadReplyError(_MsgProtocolError): - """ - An execution reply was successfully routed, but not understood. - - If a QMP message is received with an 'id' field to allow it to be - routed, but is otherwise malformed, this exception will be raised. - - A reply message is malformed if it is missing either the 'return' or - 'error' keys, or if the 'error' value has missing keys or members of - the wrong type. - - :param error_message: Human-readable string describing the error. - :param msg: The malformed reply that was received. - :param sent: The message that was sent that prompted the error. - """ - def __init__(self, error_message: str, msg: Message, sent: Message): - super().__init__(error_message, msg, sent) - #: The sent `Message` that caused the failure - self.sent = sent - - -class QMPClient(AsyncProtocol[Message], Events): - """Implements a QMP client connection. - - `QMPClient` can be used to either connect or listen to a QMP server, - but always acts as the QMP client. - - :param name: - Optional nickname for the connection, used to differentiate - instances when logging. - - :param readbuflen: - The maximum buffer length for reads and writes to and from the QMP - server, in bytes. Default is 10MB. If `QMPClient` is used to - connect to a guest agent to transfer files via ``guest-file-read``/ - ``guest-file-write``, increasing this value may be required. - - Basic script-style usage looks like this:: - - import asyncio - from qemu.qmp import QMPClient - - async def main(): - qmp = QMPClient('my_virtual_machine_name') - await qmp.connect(('127.0.0.1', 1234)) - ... - res = await qmp.execute('query-block') - ... - await qmp.disconnect() - - asyncio.run(main()) - - A more advanced example that starts to take advantage of asyncio - might look like this:: - - class Client: - def __init__(self, name: str): - self.qmp = QMPClient(name) - - async def watch_events(self): - try: - async for event in self.qmp.events: - print(f"Event: {event['event']}") - except asyncio.CancelledError: - return - - async def run(self, address='/tmp/qemu.socket'): - await self.qmp.connect(address) - asyncio.create_task(self.watch_events()) - await self.qmp.runstate_changed.wait() - await self.disconnect() - - See `qmp.events` for more detail on event handling patterns. - - """ - #: Logger object used for debugging messages. - logger = logging.getLogger(__name__) - - # Read buffer default limit; 10MB like libvirt default - _readbuflen = 10 * 1024 * 1024 - - # Type alias for pending execute() result items - _PendingT = Union[Message, ExecInterruptedError] - - def __init__( - self, - name: Optional[str] = None, - readbuflen: int = _readbuflen - ) -> None: - super().__init__(name, readbuflen) - Events.__init__(self) - - #: Whether or not to await a greeting after establishing a connection. - #: Defaults to True; QGA servers expect this to be False. - self.await_greeting: bool = True - - #: Whether or not to perform capabilities negotiation upon - #: connection. Implies `await_greeting`. Defaults to True; QGA - #: servers expect this to be False. - self.negotiate: bool = True - - # Cached Greeting, if one was awaited. - self._greeting: Optional[Greeting] = None - - # Command ID counter - self._execute_id = 0 - - # Incoming RPC reply messages. - self._pending: Dict[ - Union[str, None], - 'asyncio.Queue[QMPClient._PendingT]' - ] = {} - - @property - def greeting(self) -> Optional[Greeting]: - """ - The `Greeting` from the QMP server, if any. - - Defaults to ``None``, and will be set after a greeting is - received during the connection process. It is reset at the start - of each connection attempt. - """ - return self._greeting - - @upper_half - async def _establish_session(self) -> None: - """ - Initiate the QMP session. - - Wait for the QMP greeting and perform capabilities negotiation. - - :raise GreetingError: When the greeting is not understood. - :raise NegotiationError: If the negotiation fails. - :raise EOFError: When the server unexpectedly hangs up. - :raise OSError: For underlying stream errors. - """ - self._greeting = None - self._pending = {} - - if self.await_greeting or self.negotiate: - self._greeting = await self._get_greeting() - - if self.negotiate: - await self._negotiate() - - # This will start the reader/writers: - await super()._establish_session() - - @upper_half - async def _get_greeting(self) -> Greeting: - """ - :raise GreetingError: When the greeting is not understood. - :raise EOFError: When the server unexpectedly hangs up. - :raise OSError: For underlying stream errors. - - :return: the Greeting object given by the server. - """ - self.logger.debug("Awaiting greeting ...") - - try: - msg = await self._recv() - return Greeting(msg) - except (ProtocolError, KeyError, TypeError) as err: - emsg = "Did not understand Greeting" - self.logger.error("%s: %s", emsg, exception_summary(err)) - self.logger.debug("%s:\n%s\n", emsg, pretty_traceback()) - raise GreetingError(emsg, err) from err - except BaseException as err: - # EOFError, OSError, or something unexpected. - emsg = "Failed to receive Greeting" - self.logger.error("%s: %s", emsg, exception_summary(err)) - self.logger.debug("%s:\n%s\n", emsg, pretty_traceback()) - raise - - @upper_half - async def _negotiate(self) -> None: - """ - Perform QMP capabilities negotiation. - - :raise NegotiationError: When negotiation fails. - :raise EOFError: When the server unexpectedly hangs up. - :raise OSError: For underlying stream errors. - """ - self.logger.debug("Negotiating capabilities ...") - - arguments: Dict[str, List[str]] = {} - if self._greeting and 'oob' in self._greeting.QMP.capabilities: - arguments.setdefault('enable', []).append('oob') - msg = self.make_execute_msg('qmp_capabilities', arguments=arguments) - - # It's not safe to use execute() here, because the reader/writers - # aren't running. AsyncProtocol *requires* that a new session - # does not fail after the reader/writers are running! - try: - await self._send(msg) - reply = await self._recv() - assert 'return' in reply - assert 'error' not in reply - except (ProtocolError, AssertionError) as err: - emsg = "Negotiation failed" - self.logger.error("%s: %s", emsg, exception_summary(err)) - self.logger.debug("%s:\n%s\n", emsg, pretty_traceback()) - raise NegotiationError(emsg, err) from err - except BaseException as err: - # EOFError, OSError, or something unexpected. - emsg = "Negotiation failed" - self.logger.error("%s: %s", emsg, exception_summary(err)) - self.logger.debug("%s:\n%s\n", emsg, pretty_traceback()) - raise - - @bottom_half - async def _bh_disconnect(self) -> None: - try: - await super()._bh_disconnect() - finally: - if self._pending: - self.logger.debug("Cancelling pending executions") - keys = self._pending.keys() - for key in keys: - self.logger.debug("Cancelling execution '%s'", key) - self._pending[key].put_nowait( - ExecInterruptedError("Disconnected") - ) - - self.logger.debug("QMP Disconnected.") - - @upper_half - def _cleanup(self) -> None: - super()._cleanup() - assert not self._pending - - @bottom_half - async def _on_message(self, msg: Message) -> None: - """ - Add an incoming message to the appropriate queue/handler. - - :raise ServerParseError: When Message indicates server parse failure. - """ - # Incoming messages are not fully parsed/validated here; - # do only light peeking to know how to route the messages. - - if 'event' in msg: - await self._event_dispatch(msg) - return - - # Below, we assume everything left is an execute/exec-oob response. - - exec_id = cast(Optional[str], msg.get('id')) - - if exec_id in self._pending: - await self._pending[exec_id].put(msg) - return - - # We have a message we can't route back to a caller. - - is_error = 'error' in msg - has_id = 'id' in msg - - if is_error and not has_id: - # This is very likely a server parsing error. - # It doesn't inherently belong to any pending execution. - # Instead of performing clever recovery, just terminate. - # See "NOTE" in interop/qmp-spec, "Error" section. - raise ServerParseError( - ("Server sent an error response without an ID, " - "but there are no ID-less executions pending. " - "Assuming this is a server parser failure."), - msg - ) - - # qmp-spec.rst, "Commands Responses" section: - # 'Clients should drop all the responses - # that have an unknown "id" field.' - self.logger.log( - logging.ERROR if is_error else logging.WARNING, - "Unknown ID '%s', message dropped.", - exec_id, - ) - self.logger.debug("Unroutable message: %s", str(msg)) - - @upper_half - @bottom_half - async def _do_recv(self) -> Message: - """ - :raise OSError: When a stream error is encountered. - :raise EOFError: When the stream is at EOF. - :raise ProtocolError: - When the Message is not understood. - See also `Message._deserialize`. - - :return: A single QMP `Message`. - """ - msg_bytes = await self._readline() - msg = Message(msg_bytes, eager=True) - return msg - - @upper_half - @bottom_half - def _do_send(self, msg: Message) -> None: - """ - :raise ValueError: JSON serialization failure - :raise TypeError: JSON serialization failure - :raise OSError: When a stream error is encountered. - """ - assert self._writer is not None - self._writer.write(bytes(msg)) - - @upper_half - def _get_exec_id(self) -> str: - exec_id = f"__qmp#{self._execute_id:05d}" - self._execute_id += 1 - return exec_id - - @upper_half - async def _issue(self, msg: Message) -> Union[None, str]: - """ - Issue a QMP `Message` and do not wait for a reply. - - :param msg: The QMP `Message` to send to the server. - - :return: The ID of the `Message` sent. - """ - msg_id: Optional[str] = None - if 'id' in msg: - assert isinstance(msg['id'], str) - msg_id = msg['id'] - - self._pending[msg_id] = asyncio.Queue(maxsize=1) - try: - await self._outgoing.put(msg) - except: - del self._pending[msg_id] - raise - - return msg_id - - @upper_half - async def _reply(self, msg_id: Union[str, None]) -> Message: - """ - Await a reply to a previously issued QMP message. - - :param msg_id: The ID of the previously issued message. - - :return: The reply from the server. - :raise ExecInterruptedError: - When the reply could not be retrieved because the connection - was lost, or some other problem. - """ - queue = self._pending[msg_id] - - try: - result = await queue.get() - if isinstance(result, ExecInterruptedError): - raise result - return result - finally: - del self._pending[msg_id] - - @upper_half - async def _execute(self, msg: Message, assign_id: bool = True) -> Message: - """ - Send a QMP `Message` to the server and await a reply. - - This method *assumes* you are sending some kind of an execute - statement that *will* receive a reply. - - An execution ID will be assigned if assign_id is `True`. It can be - disabled, but this requires that an ID is manually assigned - instead. For manually assigned IDs, you must not use the string - '__qmp#' anywhere in the ID. - - :param msg: The QMP `Message` to execute. - :param assign_id: If True, assign a new execution ID. - - :return: Execution reply from the server. - :raise ExecInterruptedError: - When the reply could not be retrieved because the connection - was lost, or some other problem. - """ - if assign_id: - msg['id'] = self._get_exec_id() - elif 'id' in msg: - assert isinstance(msg['id'], str) - assert '__qmp#' not in msg['id'] - - exec_id = await self._issue(msg) - return await self._reply(exec_id) - - @upper_half - @require(Runstate.RUNNING) - async def _raw( - self, - msg: Union[Message, Mapping[str, object], bytes], - assign_id: bool = True, - ) -> Message: - """ - Issue a raw `Message` to the QMP server and await a reply. - - :param msg: - A Message to send to the server. It may be a `Message`, any - Mapping (including Dict), or raw bytes. - :param assign_id: - Assign an arbitrary execution ID to this message. If - `False`, the existing id must either be absent (and no other - such pending execution may omit an ID) or a string. If it is - a string, it must not start with '__qmp#' and no other such - pending execution may currently be using that ID. - - :return: Execution reply from the server. - - :raise ExecInterruptedError: - When the reply could not be retrieved because the connection - was lost, or some other problem. - :raise TypeError: - When assign_id is `False`, an ID is given, and it is not a string. - :raise ValueError: - When assign_id is `False`, but the ID is not usable; - Either because it starts with '__qmp#' or it is already in-use. - """ - # 1. convert generic Mapping or bytes to a QMP Message - # 2. copy Message objects so that we assign an ID only to the copy. - msg = Message(msg) - - exec_id = msg.get('id') - if not assign_id and 'id' in msg: - if not isinstance(exec_id, str): - raise TypeError(f"ID ('{exec_id}') must be a string.") - if exec_id.startswith('__qmp#'): - raise ValueError( - f"ID ('{exec_id}') must not start with '__qmp#'." - ) - - if not assign_id and exec_id in self._pending: - raise ValueError( - f"ID '{exec_id}' is in-use and cannot be used." - ) - - return await self._execute(msg, assign_id=assign_id) - - @upper_half - @require(Runstate.RUNNING) - async def execute_msg(self, msg: Message) -> object: - """ - Execute a QMP command on the server and return its value. - - :param msg: The QMP `Message` to execute. - - :return: - The command execution return value from the server. The type of - object returned depends on the command that was issued, - though most in QEMU return a `dict`. - :raise ValueError: - If the QMP `Message` does not have either the 'execute' or - 'exec-oob' fields set. - :raise ExecuteError: When the server returns an error response. - :raise ExecInterruptedError: - If the connection was disrupted before - receiving a reply from the server. - """ - if not ('execute' in msg or 'exec-oob' in msg): - raise ValueError("Requires 'execute' or 'exec-oob' message") - - # Copy the Message so that the ID assigned by _execute() is - # local to this method; allowing the ID to be seen in raised - # Exceptions but without modifying the caller's held copy. - msg = Message(msg) - reply = await self._execute(msg) - - if 'error' in reply: - try: - error_response = ErrorResponse(reply) - except (KeyError, TypeError) as err: - # Error response was malformed. - raise BadReplyError( - "QMP error reply is malformed", reply, msg, - ) from err - - raise ExecuteError(error_response, msg, reply) - - if 'return' not in reply: - raise BadReplyError( - "QMP reply is missing a 'error' or 'return' member", - reply, msg, - ) - - return reply['return'] - - @classmethod - def make_execute_msg(cls, cmd: str, - arguments: Optional[Mapping[str, object]] = None, - oob: bool = False) -> Message: - """ - Create an executable message to be sent by `execute_msg` later. - - :param cmd: QMP command name. - :param arguments: Arguments (if any). Must be JSON-serializable. - :param oob: - If `True`, execute "out of band". See `interop/qmp-spec` - section "Out-of-band execution". - - :return: A QMP `Message` that can be executed with `execute_msg()`. - """ - msg = Message({'exec-oob' if oob else 'execute': cmd}) - if arguments is not None: - msg['arguments'] = arguments - return msg - - @upper_half - async def execute(self, cmd: str, - arguments: Optional[Mapping[str, object]] = None, - oob: bool = False) -> object: - """ - Execute a QMP command on the server and return its value. - - :param cmd: QMP command name. - :param arguments: Arguments (if any). Must be JSON-serializable. - :param oob: - If `True`, execute "out of band". See `interop/qmp-spec` - section "Out-of-band execution". - - :return: - The command execution return value from the server. The type of - object returned depends on the command that was issued, - though most in QEMU return a `dict`. - :raise ExecuteError: When the server returns an error response. - :raise ExecInterruptedError: - If the connection was disrupted before - receiving a reply from the server. - """ - msg = self.make_execute_msg(cmd, arguments, oob=oob) - return await self.execute_msg(msg) - - @upper_half - @require(Runstate.RUNNING) - def send_fd_scm(self, fd: int) -> None: - """Send a file descriptor to the remote via SCM_RIGHTS. - - This method does not close the file descriptor. - - :param fd: The file descriptor to send to QEMU. - - This is an advanced feature of QEMU where file descriptors can - be passed from client to server. This is usually used as a - security measure to isolate the QEMU process from being able to - open its own files. See the QMP commands ``getfd`` and - ``add-fd`` for more information. - - See `socket.socket.sendmsg` for more information on the Python - implementation for sending file descriptors over a UNIX socket. - """ - assert self._writer is not None - sock = self._writer.transport.get_extra_info('socket') - - if sock.family != socket.AF_UNIX: - raise QMPError("Sending file descriptors requires a UNIX socket.") - - if not hasattr(sock, 'sendmsg'): - # We need to void the warranty sticker. - # Access to sendmsg is scheduled for removal in Python 3.11. - # Find the real backing socket to use it anyway. - sock = sock._sock # pylint: disable=protected-access - - sock.sendmsg( - [b' '], - [(socket.SOL_SOCKET, socket.SCM_RIGHTS, struct.pack('@i', fd))] - ) diff --git a/python/qemu/qmp/qmp_shell.py b/python/qemu/qmp/qmp_shell.py deleted file mode 100644 index f81880056856..000000000000 --- a/python/qemu/qmp/qmp_shell.py +++ /dev/null @@ -1,689 +0,0 @@ -# -# Copyright (C) 2009-2022 Red Hat Inc. -# -# Authors: -# Luiz Capitulino -# John Snow -# -# This work is licensed under the terms of the GNU LGPL, version 2 or -# later. See the COPYING file in the top-level directory. -# - -""" -qmp-shell - An interactive QEMU shell powered by QMP - -qmp-shell offers a simple shell with a convenient shorthand syntax as an -alternative to typing JSON by hand. This syntax is not standardized and -is not meant to be used as a scriptable interface. This shorthand *may* -change incompatibly in the future, and it is strongly encouraged to use -the QMP library to provide API-stable scripting when needed. - -usage: qmp-shell [-h] [-H] [-v] [-p] [-l LOGFILE] [-N] qmp_server - -positional arguments: - qmp_server < UNIX socket path | TCP address:port > - -optional arguments: - -h, --help show this help message and exit - -H, --hmp Use HMP interface - -v, --verbose Verbose (echo commands sent and received) - -p, --pretty Pretty-print JSON - -l LOGFILE, --logfile LOGFILE - Save log of all QMP messages to PATH - -N, --skip-negotiation - Skip negotiate (for qemu-ga) - -Usage ------ - -First, start QEMU with:: - - > qemu [...] -qmp unix:./qmp-sock,server=on[,wait=off] - -Then run the shell, passing the address of the socket:: - - > qmp-shell ./qmp-sock - -Syntax ------- - -Commands have the following format:: - - < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] - -For example, to add a network device:: - - (QEMU) device_add driver=e1000 id=net1 - {'return': {}} - (QEMU) - -key=value pairs support either Python or JSON object literal notations, -**without spaces**. Dictionaries/objects ``{}`` are supported, as are -arrays ``[]``:: - - example-command arg-name1={'key':'value','obj'={'prop':"value"}} - -Either JSON or Python formatting for compound values works, including -both styles of string literal quotes (either single or double -quotes). Both paradigms of literal values are accepted, including -``null/true/false`` for JSON and ``None/True/False`` for Python. - -Transactions ------------- - -Transactions have the following multi-line format:: - - transaction( - action-name1 [ arg-name1=arg1 ] ... [arg-nameN=argN ] - ... - action-nameN [ arg-name1=arg1 ] ... [arg-nameN=argN ] - ) - -One line transactions are also supported:: - - transaction( action-name1 ... ) - -For example:: - - (QEMU) transaction( - TRANS> block-dirty-bitmap-add node=drive0 name=bitmap1 - TRANS> block-dirty-bitmap-clear node=drive0 name=bitmap0 - TRANS> ) - {"return": {}} - (QEMU) - -Commands --------- - -Autocomplete of command names using is supported. Pressing -at a blank CLI prompt will show you a list of all available commands -that the connected QEMU instance supports. - -For documentation on QMP commands and their arguments, please see -`qmp ref`. - -Events ------- - -qmp-shell will display events received from the server, but this version -does not do so asynchronously. To check for new events from the server, -press on a blank line:: - - (QEMU) ⏎ - {'timestamp': {'seconds': 1660071944, 'microseconds': 184667}, - 'event': 'STOP'} - -Display options ---------------- - -Use the -v and -p options to activate the verbose and pretty-print -options, which will echo back the properly formatted JSON-compliant QMP -that is being sent to QEMU. This is useful for debugging to see the -wire-level QMP data being exchanged, and generating output for use in -writing documentation for QEMU. -""" - -import argparse -import ast -import json -import logging -import os -import re -import readline -from subprocess import Popen -import sys -from typing import ( - IO, - Dict, - Iterator, - List, - NoReturn, - Optional, - Sequence, - cast, -) - -from qemu.qmp import ( - ConnectError, - ExecuteError, - QMPError, - SocketAddrT, -) -from qemu.qmp.legacy import ( - QEMUMonitorProtocol, - QMPBadPortError, - QMPMessage, - QMPObject, -) - - -LOG = logging.getLogger(__name__) - - -class QMPCompleter: - """ - QMPCompleter provides a readline library tab-complete behavior. - """ - # NB: Python 3.9+ will probably allow us to subclass list[str] directly, - # but pylint as of today does not know that List[str] is simply 'list'. - def __init__(self) -> None: - self._matches: List[str] = [] - - def append(self, value: str) -> None: - """Append a new valid completion to the list of possibilities.""" - return self._matches.append(value) - - def complete(self, text: str, state: int) -> Optional[str]: - """readline.set_completer() callback implementation.""" - for cmd in self._matches: - if cmd.startswith(text): - if state == 0: - return cmd - state -= 1 - return None - - -class QMPShellError(QMPError): - """ - QMP Shell Base error class. - """ - - -class FuzzyJSON(ast.NodeTransformer): - """ - This extension of ast.NodeTransformer filters literal "true/false/null" - values in a Python AST and replaces them by proper "True/False/None" values - that Python can properly evaluate. - """ - - @classmethod - def visit_Name(cls, # pylint: disable=invalid-name - node: ast.Name) -> ast.AST: - """ - Transform Name nodes with certain values into Constant (keyword) nodes. - """ - if node.id == 'true': - return ast.Constant(value=True) - if node.id == 'false': - return ast.Constant(value=False) - if node.id == 'null': - return ast.Constant(value=None) - return node - - -class QMPShell(QEMUMonitorProtocol): - """ - QMPShell provides a basic readline-based QMP shell. - - :param address: Address of the QMP server. - :param pretty: Pretty-print QMP messages. - :param verbose: Echo outgoing QMP messages to console. - """ - def __init__(self, address: SocketAddrT, - pretty: bool = False, - verbose: bool = False, - server: bool = False, - logfile: Optional[str] = None): - super().__init__(address, server=server) - self._greeting: Optional[QMPMessage] = None - self._completer = QMPCompleter() - self._transmode = False - self._actions: List[QMPMessage] = [] - self._histfile = os.path.join(os.path.expanduser('~'), - '.qmp-shell_history') - self.pretty = pretty - self.verbose = verbose - self.logfile = None - - if logfile is not None: - self.logfile = open(logfile, "w", encoding='utf-8') - - def close(self) -> None: - # Hook into context manager of parent to save shell history. - self._save_history() - super().close() - - def _fill_completion(self) -> None: - try: - cmds = cast(List[Dict[str, str]], self.cmd('query-commands')) - for cmd in cmds: - self._completer.append(cmd['name']) - except ExecuteError: - pass - - def _completer_setup(self) -> None: - self._completer = QMPCompleter() - self._fill_completion() - readline.set_history_length(1024) - readline.set_completer(self._completer.complete) - readline.parse_and_bind("tab: complete") - # NB: default delimiters conflict with some command names - # (eg. query-), clearing everything as it doesn't seem to matter - readline.set_completer_delims('') - try: - readline.read_history_file(self._histfile) - except FileNotFoundError: - pass - except IOError as err: - msg = f"Failed to read history '{self._histfile}': {err!s}" - LOG.warning(msg) - - def _save_history(self) -> None: - try: - readline.write_history_file(self._histfile) - except IOError as err: - msg = f"Failed to save history file '{self._histfile}': {err!s}" - LOG.warning(msg) - - @classmethod - def _parse_value(cls, val: str) -> object: - try: - return int(val) - except ValueError: - pass - - if val.lower() == 'true': - return True - if val.lower() == 'false': - return False - if val.startswith(('{', '[')): - # Try first as pure JSON: - try: - return json.loads(val) - except ValueError: - pass - # Try once again as FuzzyJSON: - try: - tree = ast.parse(val, mode='eval') - transformed = FuzzyJSON().visit(tree) - return ast.literal_eval(transformed) - except (SyntaxError, ValueError): - pass - return val - - def _cli_expr(self, - tokens: Sequence[str], - parent: QMPObject) -> None: - for arg in tokens: - (key, sep, val) = arg.partition('=') - if sep != '=': - raise QMPShellError( - f"Expected a key=value pair, got '{arg!s}'" - ) - - value = self._parse_value(val) - optpath = key.split('.') - curpath = [] - for path in optpath[:-1]: - curpath.append(path) - obj = parent.get(path, {}) - if not isinstance(obj, dict): - msg = 'Cannot use "{:s}" as both leaf and non-leaf key' - raise QMPShellError(msg.format('.'.join(curpath))) - parent[path] = obj - parent = obj - if optpath[-1] in parent: - if isinstance(parent[optpath[-1]], dict): - msg = 'Cannot use "{:s}" as both leaf and non-leaf key' - raise QMPShellError(msg.format('.'.join(curpath))) - raise QMPShellError(f'Cannot set "{key}" multiple times') - parent[optpath[-1]] = value - - def _build_cmd(self, cmdline: str) -> Optional[QMPMessage]: - """ - Build a QMP input object from a user provided command-line in the - following format: - - < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] - """ - argument_regex = r'''(?:[^\s"']|"(?:\\.|[^"])*"|'(?:\\.|[^'])*')+''' - cmdargs = re.findall(argument_regex, cmdline) - qmpcmd: QMPMessage - - # Transactional CLI entry: - if cmdargs and cmdargs[0] == 'transaction(': - self._transmode = True - self._actions = [] - cmdargs.pop(0) - - # Transactional CLI exit: - if cmdargs and cmdargs[0] == ')' and self._transmode: - self._transmode = False - if len(cmdargs) > 1: - msg = 'Unexpected input after close of Transaction sub-shell' - raise QMPShellError(msg) - qmpcmd = { - 'execute': 'transaction', - 'arguments': {'actions': self._actions} - } - return qmpcmd - - # No args, or no args remaining - if not cmdargs: - return None - - if self._transmode: - # Parse and cache this Transactional Action - finalize = False - action = {'type': cmdargs[0], 'data': {}} - if cmdargs[-1] == ')': - cmdargs.pop(-1) - finalize = True - self._cli_expr(cmdargs[1:], action['data']) - self._actions.append(action) - return self._build_cmd(')') if finalize else None - - # Standard command: parse and return it to be executed. - qmpcmd = {'execute': cmdargs[0], 'arguments': {}} - self._cli_expr(cmdargs[1:], qmpcmd['arguments']) - return qmpcmd - - def _print(self, qmp_message: object, fh: IO[str] = sys.stdout) -> None: - jsobj = json.dumps(qmp_message, - indent=4 if self.pretty else None, - sort_keys=self.pretty) - print(str(jsobj), file=fh) - - def _execute_cmd(self, cmdline: str) -> bool: - try: - qmpcmd = self._build_cmd(cmdline) - except QMPShellError as err: - print( - f"Error while parsing command line: {err!s}\n" - "command format: " - "[arg-name1=arg1] ... [arg-nameN=argN", - file=sys.stderr - ) - return True - # For transaction mode, we may have just cached the action: - if qmpcmd is None: - return True - if self.verbose: - self._print(qmpcmd) - resp = self.cmd_obj(qmpcmd) - if resp is None: - print('Disconnected') - return False - self._print(resp) - if self.logfile is not None: - cmd = {**qmpcmd, **resp} - self._print(cmd, fh=self.logfile) - return True - - def connect(self, negotiate: bool = True) -> None: - self._greeting = super().connect(negotiate) - self._completer_setup() - - def show_banner(self, - msg: str = 'Welcome to the QMP low-level shell!') -> None: - """ - Print to stdio a greeting, and the QEMU version if available. - """ - print(msg) - if not self._greeting: - print('Connected') - return - version = self._greeting['QMP']['version']['qemu'] - print("Connected to QEMU {major}.{minor}.{micro}\n".format(**version)) - - @property - def prompt(self) -> str: - """ - Return the current shell prompt, including a trailing space. - """ - if self._transmode: - return 'TRANS> ' - return '(QEMU) ' - - def read_exec_command(self) -> bool: - """ - Read and execute a command. - - @return True if execution was ok, return False if disconnected. - """ - try: - cmdline = input(self.prompt) - except EOFError: - print() - return False - - if cmdline == '': - for event in self.get_events(): - print(event) - return True - - return self._execute_cmd(cmdline) - - def repl(self) -> Iterator[None]: - """ - Return an iterator that implements the REPL. - """ - self.show_banner() - while self.read_exec_command(): - yield - self.close() - - -class HMPShell(QMPShell): - """ - HMPShell provides a basic readline-based HMP shell, tunnelled via QMP. - - :param address: Address of the QMP server. - :param pretty: Pretty-print QMP messages. - :param verbose: Echo outgoing QMP messages to console. - """ - def __init__(self, address: SocketAddrT, - pretty: bool = False, - verbose: bool = False, - server: bool = False, - logfile: Optional[str] = None): - super().__init__(address, pretty, verbose, server, logfile) - self._cpu_index = 0 - - def _cmd_completion(self) -> None: - for cmd in self._cmd_passthrough('help')['return'].split('\r\n'): - if cmd and cmd[0] != '[' and cmd[0] != '\t': - name = cmd.split()[0] # drop help text - if name == 'info': - continue - if name.find('|') != -1: - # Command in the form 'foobar|f' or 'f|foobar', take the - # full name - opt = name.split('|') - if len(opt[0]) == 1: - name = opt[1] - else: - name = opt[0] - self._completer.append(name) - self._completer.append('help ' + name) # help completion - - def _info_completion(self) -> None: - for cmd in self._cmd_passthrough('info')['return'].split('\r\n'): - if cmd: - self._completer.append('info ' + cmd.split()[1]) - - def _other_completion(self) -> None: - # special cases - self._completer.append('help info') - - def _fill_completion(self) -> None: - self._cmd_completion() - self._info_completion() - self._other_completion() - - def _cmd_passthrough(self, cmdline: str, - cpu_index: int = 0) -> QMPMessage: - return self.cmd_obj({ - 'execute': 'human-monitor-command', - 'arguments': { - 'command-line': cmdline, - 'cpu-index': cpu_index - } - }) - - def _execute_cmd(self, cmdline: str) -> bool: - if cmdline.split()[0] == "cpu": - # trap the cpu command, it requires special setting - try: - idx = int(cmdline.split()[1]) - if 'return' not in self._cmd_passthrough('info version', idx): - print('bad CPU index') - return True - self._cpu_index = idx - except ValueError: - print('cpu command takes an integer argument') - return True - resp = self._cmd_passthrough(cmdline, self._cpu_index) - if resp is None: - print('Disconnected') - return False - assert 'return' in resp or 'error' in resp - if 'return' in resp: - # Success - if len(resp['return']) > 0: - print(resp['return'], end=' ') - else: - # Error - print('%s: %s' % (resp['error']['class'], resp['error']['desc'])) - return True - - def show_banner(self, msg: str = 'Welcome to the HMP shell!') -> None: - QMPShell.show_banner(self, msg) - - -def die(msg: str) -> NoReturn: - """Write an error to stderr, then exit with a return code of 1.""" - sys.stderr.write('ERROR: %s\n' % msg) - sys.exit(1) - - -def common_parser() -> argparse.ArgumentParser: - """Build common parsing options used by qmp-shell and qmp-shell-wrap.""" - parser = argparse.ArgumentParser() - parser.add_argument('-H', '--hmp', action='store_true', - help='Use HMP interface') - parser.add_argument('-v', '--verbose', action='store_true', - help='Verbose (echo commands sent and received)') - parser.add_argument('-p', '--pretty', action='store_true', - help='Pretty-print JSON') - parser.add_argument('-l', '--logfile', - help='Save log of all QMP messages to PATH') - # NOTE: When changing arguments, update both this module docstring - # and the manpage synopsis in docs/man/qmp_shell.rst. - return parser - - -def main() -> None: - """ - qmp-shell entry point: parse command line arguments and start the REPL. - """ - parser = common_parser() - parser.add_argument('-N', '--skip-negotiation', action='store_true', - help='Skip negotiate (for qemu-ga)') - - default_server = os.environ.get('QMP_SOCKET') - parser.add_argument('qmp_server', action='store', - default=default_server, - help='< UNIX socket path | TCP address:port >') - - args = parser.parse_args() - if args.qmp_server is None: - parser.error("QMP socket or TCP address must be specified") - - shell_class = HMPShell if args.hmp else QMPShell - - try: - address = shell_class.parse_address(args.qmp_server) - except QMPBadPortError: - parser.error(f"Bad port number: {args.qmp_server}") - return # pycharm doesn't know error() is noreturn - - with shell_class(address, args.pretty, args.verbose, args.logfile) as qemu: - try: - qemu.connect(negotiate=not args.skip_negotiation) - except ConnectError as err: - if isinstance(err.exc, OSError): - die(f"Couldn't connect to {args.qmp_server}: {err!s}") - die(str(err)) - - for _ in qemu.repl(): - pass - - -def main_wrap() -> None: - """ - qmp-shell-wrap - QEMU + qmp-shell launcher utility - - Launch QEMU and connect to it with `qmp-shell` in a single command. - CLI arguments will be forwarded to qemu, with additional arguments - added to allow `qmp-shell` to then connect to the recently launched - QEMU instance. - - usage: qmp-shell-wrap [-h] [-H] [-v] [-p] [-l LOGFILE] ... - - positional arguments: - command QEMU command line to invoke - - optional arguments: - -h, --help show this help message and exit - -H, --hmp Use HMP interface - -v, --verbose Verbose (echo commands sent and received) - -p, --pretty Pretty-print JSON - -l LOGFILE, --logfile LOGFILE - Save log of all QMP messages to PATH - - Usage - ----- - - Prepend "qmp-shell-wrap" to your usual QEMU command line:: - - > qmp-shell-wrap qemu-system-x86_64 -M q35 -m 4096 -display none - Welcome to the QMP low-level shell! - Connected - (QEMU) - """ - parser = common_parser() - parser.add_argument('command', nargs=argparse.REMAINDER, - help='QEMU command line to invoke') - - args = parser.parse_args() - - cmd = args.command - if len(cmd) != 0 and cmd[0] == '--': - cmd = cmd[1:] - if len(cmd) == 0: - cmd = ["qemu-system-x86_64"] - - sockpath = "qmp-shell-wrap-%d" % os.getpid() - cmd += ["-qmp", "unix:%s" % sockpath] - - shell_class = HMPShell if args.hmp else QMPShell - - try: - address = shell_class.parse_address(sockpath) - except QMPBadPortError: - parser.error(f"Bad port number: {sockpath}") - return # pycharm doesn't know error() is noreturn - - try: - with shell_class(address, args.pretty, args.verbose, - True, args.logfile) as qemu: - with Popen(cmd): - - try: - qemu.accept() - except ConnectError as err: - if isinstance(err.exc, OSError): - die(f"Couldn't connect to {args.qmp_server}: {err!s}") - die(str(err)) - - for _ in qemu.repl(): - pass - except FileNotFoundError: - sys.stderr.write(f"ERROR: QEMU executable '{cmd[0]}' not found.\n") - finally: - os.unlink(sockpath) - - -if __name__ == '__main__': - main() diff --git a/python/qemu/qmp/qmp_tui.py b/python/qemu/qmp/qmp_tui.py deleted file mode 100644 index d946c205131a..000000000000 --- a/python/qemu/qmp/qmp_tui.py +++ /dev/null @@ -1,665 +0,0 @@ -# Copyright (c) 2021 -# -# Authors: -# Niteesh Babu G S -# -# This work is licensed under the terms of the GNU LGPL, version 2 or -# later. See the COPYING file in the top-level directory. -""" -QMP TUI - -QMP TUI is an asynchronous interface built on top the of the QMP library. -It is the successor of QMP-shell and is bought-in as a replacement for it. - -Example Usage: qmp-tui -Full Usage: qmp-tui --help -""" - -import argparse -import asyncio -import json -import logging -from logging import Handler, LogRecord -import signal -import sys -from typing import ( - List, - Optional, - Tuple, - Type, - Union, - cast, -) - - -try: - from pygments import lexers - from pygments import token as Token - import urwid - import urwid_readline -except ModuleNotFoundError as exc: - print( - f"Module '{exc.name}' not found.", - "You need the optional 'tui' group: pip install qemu.qmp[tui]", - sep='\n', - file=sys.stderr, - ) - sys.exit(1) - -from .error import ProtocolError -from .legacy import QEMUMonitorProtocol, QMPBadPortError -from .message import DeserializationError, Message, UnexpectedTypeError -from .protocol import ConnectError, Runstate -from .qmp_client import ExecInterruptedError, QMPClient -from .util import get_or_create_event_loop, pretty_traceback - - -# The name of the signal that is used to update the history list -UPDATE_MSG: str = 'UPDATE_MSG' - - -palette = [ - (Token.Punctuation, '', '', '', 'h15,bold', 'g7'), - (Token.Text, '', '', '', '', 'g7'), - (Token.Name.Tag, '', '', '', 'bold,#f88', 'g7'), - (Token.Literal.Number.Integer, '', '', '', '#fa0', 'g7'), - (Token.Literal.String.Double, '', '', '', '#6f6', 'g7'), - (Token.Keyword.Constant, '', '', '', '#6af', 'g7'), - ('DEBUG', '', '', '', '#ddf', 'g7'), - ('INFO', '', '', '', 'g100', 'g7'), - ('WARNING', '', '', '', '#ff6', 'g7'), - ('ERROR', '', '', '', '#a00', 'g7'), - ('CRITICAL', '', '', '', '#a00', 'g7'), - ('background', '', 'black', '', '', 'g7'), -] - - -def format_json(msg: str) -> str: - """ - Formats valid/invalid multi-line JSON message into a single-line message. - - Formatting is first tried using the standard json module. If that fails - due to an decoding error then a simple string manipulation is done to - achieve a single line JSON string. - - Converting into single line is more aesthetically pleasing when looking - along with error messages. - - Eg: - Input: - [ 1, - true, - 3 ] - The above input is not a valid QMP message and produces the following error - "QMP message is not a JSON object." - When displaying this in TUI in multiline mode we get - - [ 1, - true, - 3 ]: QMP message is not a JSON object. - - whereas in singleline mode we get the following - - [1, true, 3]: QMP message is not a JSON object. - - The single line mode is more aesthetically pleasing. - - :param msg: - The message to formatted into single line. - - :return: Formatted singleline message. - """ - try: - msg = json.loads(msg) - return str(json.dumps(msg)) - except json.decoder.JSONDecodeError: - msg = msg.replace('\n', '') - words = msg.split(' ') - words = list(filter(None, words)) - return ' '.join(words) - - -def has_handler_type(logger: logging.Logger, - handler_type: Type[Handler]) -> bool: - """ - The Logger class has no interface to check if a certain type of handler is - installed or not. So we provide an interface to do so. - - :param logger: - Logger object - :param handler_type: - The type of the handler to be checked. - - :return: returns True if handler of type `handler_type`. - """ - for handler in logger.handlers: - if isinstance(handler, handler_type): - return True - return False - - -class App(QMPClient): - """ - Implements the QMP TUI. - - Initializes the widgets and starts the urwid event loop. - - :param address: - Address of the server to connect to. - :param num_retries: - The number of times to retry before stopping to reconnect. - :param retry_delay: - The delay(sec) before each retry - """ - def __init__(self, address: Union[str, Tuple[str, int]], num_retries: int, - retry_delay: Optional[int]) -> None: - urwid.register_signal(type(self), UPDATE_MSG) - self.window = Window(self) - self.address = address - self.aloop: Optional[asyncio.AbstractEventLoop] = None - self.num_retries = num_retries - self.retry_delay = retry_delay if retry_delay else 2 - self.retry: bool = False - self.exiting: bool = False - super().__init__() - - def add_to_history(self, msg: str, level: Optional[str] = None) -> None: - """ - Appends the msg to the history list. - - :param msg: - The raw message to be appended in string type. - """ - urwid.emit_signal(self, UPDATE_MSG, msg, level) - - def _cb_outbound(self, msg: Message) -> Message: - """ - Callback: outbound message hook. - - Appends the outgoing messages to the history box. - - :param msg: raw outbound message. - :return: final outbound message. - """ - str_msg = str(msg) - - if not has_handler_type(logging.getLogger(), TUILogHandler): - logging.debug('Request: %s', str_msg) - self.add_to_history('<-- ' + str_msg) - return msg - - def _cb_inbound(self, msg: Message) -> Message: - """ - Callback: outbound message hook. - - Appends the incoming messages to the history box. - - :param msg: raw inbound message. - :return: final inbound message. - """ - str_msg = str(msg) - - if not has_handler_type(logging.getLogger(), TUILogHandler): - logging.debug('Request: %s', str_msg) - self.add_to_history('--> ' + str_msg) - return msg - - async def _send_to_server(self, msg: Message) -> None: - """ - This coroutine sends the message to the server. - The message has to be pre-validated. - - :param msg: - Pre-validated message to be to sent to the server. - - :raise Exception: When an unhandled exception is caught. - """ - try: - await self._raw(msg, assign_id='id' not in msg) - except ExecInterruptedError as err: - logging.info('Error server disconnected before reply %s', str(err)) - self.add_to_history('Server disconnected before reply', 'ERROR') - except Exception as err: - logging.error('Exception from _send_to_server: %s', str(err)) - raise err - - def cb_send_to_server(self, raw_msg: str) -> None: - """ - Validates and sends the message to the server. - The raw string message is first converted into a Message object - and is then sent to the server. - - :param raw_msg: - The raw string message to be sent to the server. - - :raise Exception: When an unhandled exception is caught. - """ - try: - msg = Message(bytes(raw_msg, encoding='utf-8')) - asyncio.create_task(self._send_to_server(msg)) - except (DeserializationError, UnexpectedTypeError) as err: - raw_msg = format_json(raw_msg) - logging.info('Invalid message: %s', err.error_message) - self.add_to_history(f'{raw_msg}: {err.error_message}', 'ERROR') - - def unhandled_input(self, key: str) -> None: - """ - Handle's keys which haven't been handled by the child widgets. - - :param key: - Unhandled key - """ - if key == 'esc': - self.kill_app() - - def kill_app(self) -> None: - """ - Initiates killing of app. A bridge between asynchronous and synchronous - code. - """ - asyncio.create_task(self._kill_app()) - - async def _kill_app(self) -> None: - """ - This coroutine initiates the actual disconnect process and calls - urwid.ExitMainLoop() to kill the TUI. - - :raise Exception: When an unhandled exception is caught. - """ - self.exiting = True - await self.disconnect() - logging.debug('Disconnect finished. Exiting app') - raise urwid.ExitMainLoop() - - async def disconnect(self) -> None: - """ - Overrides the disconnect method to handle the errors locally. - """ - try: - await super().disconnect() - except (OSError, EOFError) as err: - logging.info('disconnect: %s', str(err)) - self.retry = True - except ProtocolError as err: - logging.info('disconnect: %s', str(err)) - except Exception as err: - logging.error('disconnect: Unhandled exception %s', str(err)) - raise err - - def _set_status(self, msg: str) -> None: - """ - Sets the message as the status. - - :param msg: - The message to be displayed in the status bar. - """ - self.window.footer.set_text(msg) - - def _get_formatted_address(self) -> str: - """ - Returns a formatted version of the server's address. - - :return: formatted address - """ - if isinstance(self.address, tuple): - host, port = self.address - addr = f'{host}:{port}' - else: - addr = f'{self.address}' - return addr - - async def _initiate_connection(self) -> Optional[ConnectError]: - """ - Tries connecting to a server a number of times with a delay between - each try. If all retries failed then return the error faced during - the last retry. - - :return: Error faced during last retry. - """ - current_retries = 0 - err = None - - # initial try - await self.connect_server() - while self.retry and current_retries < self.num_retries: - logging.info('Connection Failed, retrying in %d', self.retry_delay) - status = f'[Retry #{current_retries} ({self.retry_delay}s)]' - self._set_status(status) - - await asyncio.sleep(self.retry_delay) - - err = await self.connect_server() - current_retries += 1 - # If all retries failed report the last error - if err: - logging.info('All retries failed: %s', err) - return err - return None - - async def manage_connection(self) -> None: - """ - Manage the connection based on the current run state. - - A reconnect is issued when the current state is IDLE and the number - of retries is not exhausted. - A disconnect is issued when the current state is DISCONNECTING. - """ - while not self.exiting: - if self.runstate == Runstate.IDLE: - err = await self._initiate_connection() - # If retry is still true then, we have exhausted all our tries. - if err: - self._set_status(f'[Error: {err.error_message}]') - else: - addr = self._get_formatted_address() - self._set_status(f'[Connected {addr}]') - elif self.runstate == Runstate.DISCONNECTING: - self._set_status('[Disconnected]') - await self.disconnect() - # check if a retry is needed - # mypy 1.4.0 doesn't believe runstate can change after - # disconnect(), hence the cast. - state = cast(Runstate, self.runstate) - if state == Runstate.IDLE: - continue - await self.runstate_changed() - - async def connect_server(self) -> Optional[ConnectError]: - """ - Initiates a connection to the server at address `self.address` - and in case of a failure, sets the status to the respective error. - """ - try: - await self.connect(self.address) - self.retry = False - except ConnectError as err: - logging.info('connect_server: ConnectError %s', str(err)) - self.retry = True - return err - return None - - def run(self, debug: bool = False) -> None: - """ - Starts the long running co-routines and the urwid event loop. - - :param debug: - Enables/Disables asyncio event loop debugging - """ - screen = urwid.raw_display.Screen() - screen.set_terminal_properties(256) - self.aloop = get_or_create_event_loop() - self.aloop.set_debug(debug) - - # Gracefully handle SIGTERM and SIGINT signals - cancel_signals = [signal.SIGTERM, signal.SIGINT] - for sig in cancel_signals: - self.aloop.add_signal_handler(sig, self.kill_app) - - event_loop = urwid.AsyncioEventLoop(loop=self.aloop) - main_loop = urwid.MainLoop(urwid.AttrMap(self.window, 'background'), - unhandled_input=self.unhandled_input, - screen=screen, - palette=palette, - handle_mouse=True, - event_loop=event_loop) - - self.aloop.create_task(self.manage_connection()) - try: - main_loop.run() - except Exception as err: - logging.error('%s\n%s\n', str(err), pretty_traceback()) - raise err - - -class StatusBar(urwid.Text): - """ - A simple statusbar modelled using the Text widget. The status can be - set using the set_text function. All text set is aligned to right. - - :param text: Initial text to be displayed. Default is empty str. - """ - def __init__(self, text: str = ''): - super().__init__(text, align='right') - - -class Editor(urwid_readline.ReadlineEdit): - """ - A simple editor modelled using the urwid_readline.ReadlineEdit widget. - Mimcs GNU readline shortcuts and provides history support. - - The readline shortcuts can be found below: - https://github.com/rr-/urwid_readline#features - - Along with the readline features, this editor also has support for - history. Pressing the 'up'/'down' switches between the prev/next messages - available in the history. - - Currently there is no support to save the history to a file. The history of - previous commands is lost on exit. - - :param parent: Reference to the TUI object. - """ - def __init__(self, parent: App) -> None: - super().__init__(caption='> ', multiline=True) - self.parent = parent - self.history: List[str] = [] - self.last_index: int = -1 - self.show_history: bool = False - - def keypress(self, size: Tuple[int, int], key: str) -> Optional[str]: - """ - Handles the keypress on this widget. - - :param size: - The current size of the widget. - :param key: - The key to be handled. - - :return: Unhandled key if any. - """ - msg = self.get_edit_text() - if key == 'up' and not msg: - # Show the history when 'up arrow' is pressed with no input text. - # NOTE: The show_history logic is necessary because in 'multiline' - # mode (which we use) 'up arrow' is used to move between lines. - if not self.history: - return None - self.show_history = True - last_msg = self.history[self.last_index] - self.set_edit_text(last_msg) - self.edit_pos = len(last_msg) - elif key == 'up' and self.show_history: - self.last_index = max(self.last_index - 1, -len(self.history)) - self.set_edit_text(self.history[self.last_index]) - self.edit_pos = len(self.history[self.last_index]) - elif key == 'down' and self.show_history: - if self.last_index == -1: - self.set_edit_text('') - self.show_history = False - else: - self.last_index += 1 - self.set_edit_text(self.history[self.last_index]) - self.edit_pos = len(self.history[self.last_index]) - elif key == 'meta enter': - # When using multiline, enter inserts a new line into the editor - # send the input to the server on alt + enter - self.parent.cb_send_to_server(msg) - self.history.append(msg) - self.set_edit_text('') - self.last_index = -1 - self.show_history = False - else: - self.show_history = False - self.last_index = -1 - return cast(Optional[str], super().keypress(size, key)) - return None - - -class EditorWidget(urwid.Filler): - """ - Wrapper around the editor widget. - - The Editor is a flow widget and has to wrapped inside a box widget. - This class wraps the Editor inside filler widget. - - :param parent: Reference to the TUI object. - """ - def __init__(self, parent: App) -> None: - super().__init__(Editor(parent), valign='top') - - -class HistoryBox(urwid.ListBox): - """ - This widget is modelled using the ListBox widget, contains the list of - all messages both QMP messages and log messages to be shown in the TUI. - - The messages are urwid.Text widgets. On every append of a message, the - focus is shifted to the last appended message. - - :param parent: Reference to the TUI object. - """ - def __init__(self, parent: App) -> None: - self.parent = parent - self.history = urwid.SimpleFocusListWalker([]) - super().__init__(self.history) - - def add_to_history(self, - history: Union[str, List[Tuple[str, str]]]) -> None: - """ - Appends a message to the list and set the focus to the last appended - message. - - :param history: - The history item(message/event) to be appended to the list. - """ - self.history.append(urwid.Text(history)) - self.history.set_focus(len(self.history) - 1) - - def mouse_event(self, size: Tuple[int, int], _event: str, button: float, - _x: int, _y: int, focus: bool) -> None: - # Unfortunately there are no urwid constants that represent the mouse - # events. - if button == 4: # Scroll up event - super().keypress(size, 'up') - elif button == 5: # Scroll down event - super().keypress(size, 'down') - - -class HistoryWindow(urwid.Frame): - """ - This window composes the HistoryBox and EditorWidget in a horizontal split. - By default the first focus is given to the history box. - - :param parent: Reference to the TUI object. - """ - def __init__(self, parent: App) -> None: - self.parent = parent - self.editor_widget = EditorWidget(parent) - self.editor = urwid.LineBox(self.editor_widget) - self.history = HistoryBox(parent) - self.body = urwid.Pile([('weight', 80, self.history), - ('weight', 20, self.editor)]) - super().__init__(self.body) - urwid.connect_signal(self.parent, UPDATE_MSG, self.cb_add_to_history) - - def cb_add_to_history(self, msg: str, level: Optional[str] = None) -> None: - """ - Appends a message to the history box - - :param msg: - The message to be appended to the history box. - :param level: - The log level of the message, if it is a log message. - """ - formatted = [] - if level: - msg = f'[{level}]: {msg}' - formatted.append((level, msg)) - else: - lexer = lexers.JsonLexer() # pylint: disable=no-member - for token in lexer.get_tokens(msg): - formatted.append(token) - self.history.add_to_history(formatted) - - -class Window(urwid.Frame): - """ - This window is the top most widget of the TUI and will contain other - windows. Each child of this widget is responsible for displaying a specific - functionality. - - :param parent: Reference to the TUI object. - """ - def __init__(self, parent: App) -> None: - self.parent = parent - footer = StatusBar() - body = HistoryWindow(parent) - super().__init__(body, footer=footer) - - -class TUILogHandler(Handler): - """ - This handler routes all the log messages to the TUI screen. - It is installed to the root logger to so that the log message from all - libraries begin used is routed to the screen. - - :param tui: Reference to the TUI object. - """ - def __init__(self, tui: App) -> None: - super().__init__() - self.tui = tui - - def emit(self, record: LogRecord) -> None: - """ - Emits a record to the TUI screen. - - Appends the log message to the TUI screen - """ - level = record.levelname - msg = record.getMessage() - self.tui.add_to_history(msg, level) - - -def main() -> None: - """ - Driver of the whole script, parses arguments, initialize the TUI and - the logger. - """ - parser = argparse.ArgumentParser(description='QMP TUI') - parser.add_argument('qmp_server', help='Address of the QMP server. ' - 'Format ') - parser.add_argument('--num-retries', type=int, default=10, - help='Number of times to reconnect before giving up.') - parser.add_argument('--retry-delay', type=int, - help='Time(s) to wait before next retry. ' - 'Default action is to wait 2s between each retry.') - parser.add_argument('--log-file', help='The Log file name') - parser.add_argument('--log-level', default='WARNING', - help='Log level ') - parser.add_argument('--asyncio-debug', action='store_true', - help='Enable debug mode for asyncio loop. ' - 'Generates lot of output, makes TUI unusable when ' - 'logs are logged in the TUI. ' - 'Use only when logging to a file.') - args = parser.parse_args() - - try: - address = QEMUMonitorProtocol.parse_address(args.qmp_server) - except QMPBadPortError as err: - parser.error(str(err)) - - app = App(address, args.num_retries, args.retry_delay) - - root_logger = logging.getLogger() - root_logger.setLevel(logging.getLevelName(args.log_level)) - - if args.log_file: - root_logger.addHandler(logging.FileHandler(args.log_file)) - else: - root_logger.addHandler(TUILogHandler(app)) - - app.run(args.asyncio_debug) - - -if __name__ == '__main__': - main() diff --git a/python/qemu/qmp/util.py b/python/qemu/qmp/util.py deleted file mode 100644 index a8229e552456..000000000000 --- a/python/qemu/qmp/util.py +++ /dev/null @@ -1,150 +0,0 @@ -""" -Miscellaneous Utilities - -This module provides asyncio and various logging and debugging -utilities, such as `exception_summary()` and `pretty_traceback()`, used -primarily for adding information into the logging stream. -""" - -import asyncio -import sys -import traceback -from typing import TypeVar, cast -import warnings - - -T = TypeVar('T') - - -# -------------------------- -# Section: Utility Functions -# -------------------------- - - -def get_or_create_event_loop() -> asyncio.AbstractEventLoop: - """ - Return this thread's current event loop, or create a new one. - - This function behaves similarly to asyncio.get_event_loop() in - Python<=3.13, where if there is no event loop currently associated - with the current context, it will create and register one. It should - generally not be used in any asyncio-native applications. - """ - try: - with warnings.catch_warnings(): - # Python <= 3.13 will trigger deprecation warnings if no - # event loop is set, but will create and set a new loop. - warnings.simplefilter("ignore") - loop = asyncio.get_event_loop() - except RuntimeError: - # Python 3.14+: No event loop set for this thread, - # create and set one. - loop = asyncio.new_event_loop() - # Set this loop as the current thread's loop, to be returned - # by calls to get_event_loop() in the future. - asyncio.set_event_loop(loop) - - return loop - - -async def flush(writer: asyncio.StreamWriter) -> None: - """ - Utility function to ensure an `asyncio.StreamWriter` is *fully* drained. - - `asyncio.StreamWriter.drain` only promises we will return to below - the "high-water mark". This function ensures we flush the entire - buffer -- by setting the high water mark to 0 and then calling - drain. The flow control limits are restored after the call is - completed. - """ - transport = cast( # type: ignore[redundant-cast] - asyncio.WriteTransport, writer.transport - ) - - # https://github.com/python/typeshed/issues/5779 - low, high = transport.get_write_buffer_limits() # type: ignore - transport.set_write_buffer_limits(0, 0) - try: - await writer.drain() - finally: - transport.set_write_buffer_limits(high, low) - - -def upper_half(func: T) -> T: - """ - Do-nothing decorator that annotates a method as an "upper-half" method. - - These methods must not call bottom-half functions directly, but can - schedule them to run. - """ - return func - - -def bottom_half(func: T) -> T: - """ - Do-nothing decorator that annotates a method as a "bottom-half" method. - - These methods must take great care to handle their own exceptions whenever - possible. If they go unhandled, they will cause termination of the loop. - - These methods do not, in general, have the ability to directly - report information to a caller’s context and will usually be - collected as an `asyncio.Task` result instead. - - They must not call upper-half functions directly. - """ - return func - - -# ---------------------------- -# Section: Logging & Debugging -# ---------------------------- - - -def exception_summary(exc: BaseException) -> str: - """ - Return a summary string of an arbitrary exception. - - It will be of the form "ExceptionType: Error Message" if the error - string is non-empty, and just "ExceptionType" otherwise. - - This code is based on CPython's implementation of - `traceback.TracebackException.format_exception_only`. - """ - name = type(exc).__qualname__ - smod = type(exc).__module__ - if smod not in ("__main__", "builtins"): - name = smod + '.' + name - - error = str(exc) - if error: - return f"{name}: {error}" - return name - - -def pretty_traceback(prefix: str = " | ") -> str: - """ - Formats the current traceback, indented to provide visual distinction. - - This is useful for printing a traceback within a traceback for - debugging purposes when encapsulating errors to deliver them up the - stack; when those errors are printed, this helps provide a nice - visual grouping to quickly identify the parts of the error that - belong to the inner exception. - - :param prefix: The prefix to append to each line of the traceback. - :return: A string, formatted something like the following:: - - | Traceback (most recent call last): - | File "foobar.py", line 42, in arbitrary_example - | foo.baz() - | ArbitraryError: [Errno 42] Something bad happened! - """ - output = "".join(traceback.format_exception(*sys.exc_info())) - - exc_lines = [] - for line in output.split('\n'): - exc_lines.append(prefix + line) - - # The last line is always empty, omit it - return "\n".join(exc_lines[:-1]) diff --git a/python/qemu/utils/qom_fuse.py b/python/qemu/utils/qom_fuse.py index cf7e344bd537..e377ef6942ff 100644 --- a/python/qemu/utils/qom_fuse.py +++ b/python/qemu/utils/qom_fuse.py @@ -47,7 +47,6 @@ import fuse from fuse import FUSE, FuseOSError, Operations - from qemu.qmp import ExecuteError from .qom_common import QOMCommand diff --git a/python/scripts/mkvenv.py b/python/scripts/mkvenv.py index 5cba5770d22e..8ed6a3545059 100644 --- a/python/scripts/mkvenv.py +++ b/python/scripts/mkvenv.py @@ -662,6 +662,7 @@ def pip_install( args: Sequence[str], online: bool = False, wheels_dir: Optional[Union[str, Path]] = None, + env: Optional[Dict[str, str]] = None, ) -> None: """ Use pip to install a package or package(s) as specified in @args. @@ -687,6 +688,7 @@ def pip_install( full_args += list(args) subprocess.run( full_args, + env=env, check=True, ) @@ -733,9 +735,14 @@ def _do_ensure( :param wheels_dir: If specified, search this path for packages. """ absent = [] + local_packages = [] present = [] canary = None for name, info in group.items(): + if "path" in info: + pkgpath = Path(__file__).parents[2].joinpath(info["path"]) + local_packages.append(str(pkgpath)) + continue constraint = _make_version_constraint(info, False) matcher = Matcher(name + constraint) print(f"mkvenv: checking for {matcher}", file=sys.stderr) @@ -770,15 +777,33 @@ def _do_ensure( print(f"mkvenv: installing {', '.join(absent)}", file=sys.stderr) try: pip_install(args=absent, online=online, wheels_dir=wheels_dir) - return None + absent = [] except subprocess.CalledProcessError: pass - return diagnose( - absent[0], - online, - wheels_dir, - canary, + if absent: + return diagnose( + absent[0], + online, + wheels_dir, + canary, + ) + + # Handle local packages separately and last so we can use different + # installation arguments (-e), and so that any dependencies that may + # be covered above will be handled according to the depfile + # specifications. + if local_packages: + print(f"mkvenv: installing {', '.join(local_packages)}", + file=sys.stderr) + env = dict(os.environ) + env['PIP_CONFIG_SETTINGS'] = "editable_mode=compat" + pip_install( + args=["--no-build-isolation", + "-e"] + local_packages, + online=online, + wheels_dir=wheels_dir, + env=env, ) return None @@ -838,6 +863,12 @@ def ensure_group( raise Ouch(result[0]) raise SystemExit(f"\n{result[0]}\n\n") + if inside_a_venv(): + for group in groups: + path = Path(sys.prefix).joinpath(f"{group}.group") + with open(path, "w", encoding="UTF8"): + pass + def post_venv_setup() -> None: """ diff --git a/python/scripts/vendor.py b/python/scripts/vendor.py index 33b9b0b92b75..78058183e4c4 100755 --- a/python/scripts/vendor.py +++ b/python/scripts/vendor.py @@ -43,6 +43,10 @@ def main() -> int: packages = { "meson==1.10.0": "4b27aafce281e652dcb437b28007457411245d975c48b5db3a797d3e93ae1585", + "qemu.qmp==0.0.5": + "e05782d6df5844b34e0d2f7c68693525da074deef7b641c1401dda6e4e3d6303", + "pycotap==1.3.1": + "1c3a25b3ff89e48f4e00f1f71dbbc1642b4f65c65d416524d07e73492fff25ea", } vendor_dir = Path(__file__, "..", "..", "wheels").resolve() diff --git a/python/setup.cfg b/python/setup.cfg index f40f11396c91..809759339713 100644 --- a/python/setup.cfg +++ b/python/setup.cfg @@ -19,14 +19,16 @@ classifiers = Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 Programming Language :: Python :: 3.13 + Programming Language :: Python :: 3.14 Typing :: Typed [options] python_requires = >= 3.9 packages = - qemu.qmp qemu.machine qemu.utils +install_requires = + qemu.qmp [options.package_data] * = py.typed @@ -34,30 +36,21 @@ packages = [options.extras_require] # Remember to update tests/minreqs.txt if changing anything below: devel = - avocado-framework >= 90.0 distlib >= 0.3.6 flake8 >= 5.0.4 fusepy >= 2.0.4 - isort >= 5.1.2 + isort >= 5.6.0 mypy >= 1.4.0 pylint >= 2.17.3 pylint != 3.2.4; python_version<"3.9" + pytest >= 6.0.2 tox >= 3.18.0 - urwid >= 2.1.2 - urwid-readline >= 0.13 - Pygments >= 2.9.0 sphinx >= 3.4.3 # Provides qom-fuse functionality fuse = fusepy >= 2.0.4 -# QMP TUI dependencies -tui = - urwid >= 2.1.2 - urwid-readline >= 0.13 - Pygments >= 2.9.0 - [options.entry_points] console_scripts = qom = qemu.utils.qom:main @@ -67,9 +60,6 @@ console_scripts = qom-tree = qemu.utils.qom:QOMTree.entry_point qom-fuse = qemu.utils.qom_fuse:QOMFuse.entry_point [fuse] qemu-ga-client = qemu.utils.qemu_ga_client:main - qmp-shell = qemu.qmp.qmp_shell:main - qmp-shell-wrap = qemu.qmp.qmp_shell:main_wrap - qmp-tui = qemu.qmp.qmp_tui:main [tui] [flake8] # Prefer pylint's bare-except checks to flake8's @@ -86,10 +76,6 @@ warn_unused_ignores = False # fusepy has no type stubs: allow_subclassing_any = True -[mypy-qemu.qmp.qmp_tui] -# urwid and urwid_readline have no type stubs: -allow_subclassing_any = True - # The following missing import directives are because these libraries do not # provide type stubs. Allow them on an as-needed basis for mypy. [mypy-fuse] @@ -101,15 +87,6 @@ ignore_missing_imports = True [mypy-tomllib] ignore_missing_imports = True -[mypy-urwid] -ignore_missing_imports = True - -[mypy-urwid_readline] -ignore_missing_imports = True - -[mypy-pygments] -ignore_missing_imports = True - [mypy-distlib] ignore_missing_imports = True @@ -186,7 +163,7 @@ multi_line_output=3 # of python available on your system to run this test. [tox:tox] -envlist = py39, py310, py311, py312, py313 +envlist = py39, py310, py311, py312, py313, py314 skip_missing_interpreters = true [testenv] @@ -194,7 +171,6 @@ allowlist_externals = make deps = .[devel] .[fuse] # Workaround to trigger tox venv rebuild - .[tui] # Workaround to trigger tox venv rebuild commands = make check diff --git a/python/tests/flake8.sh b/python/tests/flake8.sh deleted file mode 100755 index e0136996453f..000000000000 --- a/python/tests/flake8.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -e -python3 -m flake8 qemu/ -python3 -m flake8 scripts/ diff --git a/python/tests/iotests-mypy.sh b/python/tests/iotests-mypy.sh deleted file mode 100755 index ee7647081990..000000000000 --- a/python/tests/iotests-mypy.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -e - -cd ../tests/qemu-iotests/ -python3 -m linters --mypy diff --git a/python/tests/iotests-pylint.sh b/python/tests/iotests-pylint.sh deleted file mode 100755 index 33c5ae900a5c..000000000000 --- a/python/tests/iotests-pylint.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -e - -cd ../tests/qemu-iotests/ -# See commit message for environment variable explainer. -SETUPTOOLS_USE_DISTUTILS=stdlib python3 -m linters --pylint diff --git a/python/tests/isort.sh b/python/tests/isort.sh deleted file mode 100755 index 66c2f7df0fdc..000000000000 --- a/python/tests/isort.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -e -python3 -m isort -c qemu/ -python3 -m isort -c scripts/ diff --git a/python/tests/linters.py b/python/tests/linters.py new file mode 100644 index 000000000000..9696c0b71b8f --- /dev/null +++ b/python/tests/linters.py @@ -0,0 +1,114 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +from subprocess import check_call +import sys + + +class TestLinters: + + def test_flake8_pkg(self): + check_call([sys.executable, "-m", "flake8", "qemu/"]) + + def test_flake8_scripts(self): + check_call([sys.executable, "-m", "flake8", "scripts/"]) + + def test_flake8_qapi(self): + check_call( + [ + sys.executable, + "-m", + "flake8", + "../scripts/qapi/", + "../docs/sphinx/qapidoc.py", + "../docs/sphinx/qapi_domain.py", + ] + ) + + def test_isort_pkg(self): + check_call([sys.executable, "-m", "isort", "-c", "qemu/"]) + + def test_isort_scripts(self): + check_call([sys.executable, "-m", "isort", "-c", "scripts/"]) + + def test_isort_qapi(self): + check_call( + [ + sys.executable, + "-m", + "isort", + "--sp", + ".", + "-c", + "../scripts/qapi/", + ] + ) + + def test_isort_qapi_sphinx(self): + # Force isort to recognize 'compat' as a local module and not + # third-party + check_call( + [ + sys.executable, + "-m", + "isort", + "--sp", + ".", + "-c", + "-p", + "compat", + "../docs/sphinx/qapi_domain.py", + "../docs/sphinx/qapidoc.py", + ] + ) + + def test_mypy_pkg(self): + check_call([sys.executable, "-m", "mypy", "-p", "qemu"]) + + def test_mypy_scripts(self): + check_call([sys.executable, "-m", "mypy", "scripts/"]) + + def test_mypy_qapi(self): + check_call([sys.executable, "-m", "mypy", "../scripts/qapi"]) + + def test_mypy_iotests(self): + check_call( + [sys.executable, "-m", "linters", "--mypy"], + cwd="../tests/qemu-iotests/", + ) + + # Setuptools v60 introduced the SETUPTOOLS_USE_DISTUTILS=stdlib + # workaround; stdlib distutils was fully removed in Python + # 3.12+. Once we are on >=3.12+ exclusively, this workaround can be + # dropped safely. Until then, it is needed for some versions on + # Fedora/Debian distributions which relied upon distro-patched + # setuptools present in CPython, but not within setuptools itself. + + def test_pylint_pkg(self): + os.environ["SETUPTOOLS_USE_DISTUTILS"] = "stdlib" + check_call([sys.executable, "-m", "pylint", "qemu/"]) + + def test_pylint_scripts(self): + os.environ["SETUPTOOLS_USE_DISTUTILS"] = "stdlib" + check_call([sys.executable, "-m", "pylint", "scripts/"]) + + def test_pylint_qapi(self): + os.environ["SETUPTOOLS_USE_DISTUTILS"] = "stdlib" + check_call( + [ + sys.executable, + "-m", + "pylint", + "--rcfile=../scripts/qapi/pylintrc", + "../scripts/qapi/", + "../docs/sphinx/qapidoc.py", + "../docs/sphinx/qapi_domain.py", + ] + ) + + def test_pylint_iotests(self): + os.environ["SETUPTOOLS_USE_DISTUTILS"] = "stdlib" + check_call( + [sys.executable, "-m", "linters", "--pylint"], + cwd="../tests/qemu-iotests/", + ) diff --git a/python/tests/minreqs.txt b/python/tests/minreqs.txt index cd2e2a81c3db..05c3bdb89f80 100644 --- a/python/tests/minreqs.txt +++ b/python/tests/minreqs.txt @@ -16,14 +16,13 @@ # installation of the QEMU package itself fails, failing to find # setuptools. setuptools<=70 +wheel==0.34.2 # Dependencies for qapidoc/qapi_domain et al sphinx==3.4.3 -# Dependencies for the TUI addon (Required for successful linting) -urwid==2.1.2 -urwid-readline==0.13 -Pygments==2.9.0 +# Dependencies for qemu.machine +qemu.qmp==0.0.5 # Dependencies for mkvenv distlib==0.3.6 @@ -32,11 +31,11 @@ distlib==0.3.6 fusepy==2.0.4 # Test-runners, utilities, etc. -avocado-framework==90.0 +pytest==6.0.2 # Linters flake8==5.0.4 -isort==5.1.2 +isort==5.6.0 mypy==1.4.0 pylint==2.17.3 diff --git a/python/tests/mypy.sh b/python/tests/mypy.sh deleted file mode 100755 index a33a3f58ab39..000000000000 --- a/python/tests/mypy.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -e -python3 -m mypy -p qemu -python3 -m mypy scripts/ diff --git a/python/tests/protocol.py b/python/tests/protocol.py deleted file mode 100644 index e565802516d2..000000000000 --- a/python/tests/protocol.py +++ /dev/null @@ -1,596 +0,0 @@ -import asyncio -from contextlib import contextmanager -import os -import socket -from tempfile import TemporaryDirectory - -import avocado - -from qemu.qmp import ConnectError, Runstate -from qemu.qmp.protocol import AsyncProtocol, StateError - - -class NullProtocol(AsyncProtocol[None]): - """ - NullProtocol is a test mockup of an AsyncProtocol implementation. - - It adds a fake_session instance variable that enables a code path - that bypasses the actual connection logic, but still allows the - reader/writers to start. - - Because the message type is defined as None, an asyncio.Event named - 'trigger_input' is created that prohibits the reader from - incessantly being able to yield None; this event can be poked to - simulate an incoming message. - - For testing symmetry with do_recv, an interface is added to "send" a - Null message. - - For testing purposes, a "simulate_disconnection" method is also - added which allows us to trigger a bottom half disconnect without - injecting any real errors into the reader/writer loops; in essence - it performs exactly half of what disconnect() normally does. - """ - def __init__(self, name=None): - self.fake_session = False - self.trigger_input: asyncio.Event - super().__init__(name) - - async def _establish_session(self): - self.trigger_input = asyncio.Event() - await super()._establish_session() - - async def _do_start_server(self, address, ssl=None): - if self.fake_session: - self._accepted = asyncio.Event() - self._set_state(Runstate.CONNECTING) - await asyncio.sleep(0) - else: - await super()._do_start_server(address, ssl) - - async def _do_accept(self): - if self.fake_session: - self._accepted = None - else: - await super()._do_accept() - - async def _do_connect(self, address, ssl=None): - if self.fake_session: - self._set_state(Runstate.CONNECTING) - await asyncio.sleep(0) - else: - await super()._do_connect(address, ssl) - - async def _do_recv(self) -> None: - await self.trigger_input.wait() - self.trigger_input.clear() - - def _do_send(self, msg: None) -> None: - pass - - async def send_msg(self) -> None: - await self._outgoing.put(None) - - async def simulate_disconnect(self) -> None: - """ - Simulates a bottom-half disconnect. - - This method schedules a disconnection but does not wait for it - to complete. This is used to put the loop into the DISCONNECTING - state without fully quiescing it back to IDLE. This is normally - something you cannot coax AsyncProtocol to do on purpose, but it - will be similar to what happens with an unhandled Exception in - the reader/writer. - - Under normal circumstances, the library design requires you to - await on disconnect(), which awaits the disconnect task and - returns bottom half errors as a pre-condition to allowing the - loop to return back to IDLE. - """ - self._schedule_disconnect() - - -class LineProtocol(AsyncProtocol[str]): - def __init__(self, name=None): - super().__init__(name) - self.rx_history = [] - - async def _do_recv(self) -> str: - raw = await self._readline() - msg = raw.decode() - self.rx_history.append(msg) - return msg - - def _do_send(self, msg: str) -> None: - assert self._writer is not None - self._writer.write(msg.encode() + b'\n') - - async def send_msg(self, msg: str) -> None: - await self._outgoing.put(msg) - - -def run_as_task(coro, allow_cancellation=False): - """ - Run a given coroutine as a task. - - Optionally, wrap it in a try..except block that allows this - coroutine to be canceled gracefully. - """ - async def _runner(): - try: - await coro - except asyncio.CancelledError: - if allow_cancellation: - return - raise - return asyncio.create_task(_runner()) - - -@contextmanager -def jammed_socket(): - """ - Opens up a random unused TCP port on localhost, then jams it. - """ - socks = [] - - try: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.bind(('127.0.0.1', 0)) - sock.listen(1) - address = sock.getsockname() - - socks.append(sock) - - # I don't *fully* understand why, but it takes *two* un-accepted - # connections to start jamming the socket. - for _ in range(2): - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect(address) - socks.append(sock) - - yield address - - finally: - for sock in socks: - sock.close() - - -class Smoke(avocado.Test): - - def setUp(self): - self.proto = NullProtocol() - - def test__repr__(self): - self.assertEqual( - repr(self.proto), - "" - ) - - def testRunstate(self): - self.assertEqual( - self.proto.runstate, - Runstate.IDLE - ) - - def testDefaultName(self): - self.assertEqual( - self.proto.name, - None - ) - - def testLogger(self): - self.assertEqual( - self.proto.logger.name, - 'qemu.qmp.protocol' - ) - - def testName(self): - self.proto = NullProtocol('Steve') - - self.assertEqual( - self.proto.name, - 'Steve' - ) - - self.assertEqual( - self.proto.logger.name, - 'qemu.qmp.protocol.Steve' - ) - - self.assertEqual( - repr(self.proto), - "" - ) - - -class TestBase(avocado.Test): - - def setUp(self): - self.proto = NullProtocol(type(self).__name__) - self.assertEqual(self.proto.runstate, Runstate.IDLE) - self.runstate_watcher = None - - def tearDown(self): - self.assertEqual(self.proto.runstate, Runstate.IDLE) - - async def _asyncSetUp(self): - pass - - async def _asyncTearDown(self): - if self.runstate_watcher: - await self.runstate_watcher - - @staticmethod - def async_test(async_test_method): - """ - Decorator; adds SetUp and TearDown to async tests. - """ - async def _wrapper(self, *args, **kwargs): - loop = asyncio.get_running_loop() - loop.set_debug(True) - - await self._asyncSetUp() - await async_test_method(self, *args, **kwargs) - await self._asyncTearDown() - - return _wrapper - - # Definitions - - # The states we expect a "bad" connect/accept attempt to transition through - BAD_CONNECTION_STATES = ( - Runstate.CONNECTING, - Runstate.DISCONNECTING, - Runstate.IDLE, - ) - - # The states we expect a "good" session to transition through - GOOD_CONNECTION_STATES = ( - Runstate.CONNECTING, - Runstate.RUNNING, - Runstate.DISCONNECTING, - Runstate.IDLE, - ) - - # Helpers - - async def _watch_runstates(self, *states): - """ - This launches a task alongside (most) tests below to confirm that - the sequence of runstate changes that occur is exactly as - anticipated. - """ - async def _watcher(): - for state in states: - new_state = await self.proto.runstate_changed() - self.assertEqual( - new_state, - state, - msg=f"Expected state '{state.name}'", - ) - - self.runstate_watcher = asyncio.create_task(_watcher()) - # Kick the loop and force the task to block on the event. - await asyncio.sleep(0) - - -class State(TestBase): - - @TestBase.async_test - async def testSuperfluousDisconnect(self): - """ - Test calling disconnect() while already disconnected. - """ - await self._watch_runstates( - Runstate.DISCONNECTING, - Runstate.IDLE, - ) - await self.proto.disconnect() - - -class Connect(TestBase): - """ - Tests primarily related to calling Connect(). - """ - async def _bad_connection(self, family: str): - assert family in ('INET', 'UNIX') - - if family == 'INET': - await self.proto.connect(('127.0.0.1', 0)) - elif family == 'UNIX': - await self.proto.connect('/dev/null') - - async def _hanging_connection(self): - with jammed_socket() as addr: - await self.proto.connect(addr) - - async def _bad_connection_test(self, family: str): - await self._watch_runstates(*self.BAD_CONNECTION_STATES) - - with self.assertRaises(ConnectError) as context: - await self._bad_connection(family) - - self.assertIsInstance(context.exception.exc, OSError) - self.assertEqual( - context.exception.error_message, - "Failed to establish connection" - ) - - @TestBase.async_test - async def testBadINET(self): - """ - Test an immediately rejected call to an IP target. - """ - await self._bad_connection_test('INET') - - @TestBase.async_test - async def testBadUNIX(self): - """ - Test an immediately rejected call to a UNIX socket target. - """ - await self._bad_connection_test('UNIX') - - @TestBase.async_test - async def testCancellation(self): - """ - Test what happens when a connection attempt is aborted. - """ - # Note that accept() cannot be cancelled outright, as it isn't a task. - # However, we can wrap it in a task and cancel *that*. - await self._watch_runstates(*self.BAD_CONNECTION_STATES) - task = run_as_task(self._hanging_connection(), allow_cancellation=True) - - state = await self.proto.runstate_changed() - self.assertEqual(state, Runstate.CONNECTING) - - # This is insider baseball, but the connection attempt has - # yielded *just* before the actual connection attempt, so kick - # the loop to make sure it's truly wedged. - await asyncio.sleep(0) - - task.cancel() - await task - - @TestBase.async_test - async def testTimeout(self): - """ - Test what happens when a connection attempt times out. - """ - await self._watch_runstates(*self.BAD_CONNECTION_STATES) - task = run_as_task(self._hanging_connection()) - - # More insider baseball: to improve the speed of this test while - # guaranteeing that the connection even gets a chance to start, - # verify that the connection hangs *first*, then await the - # result of the task with a nearly-zero timeout. - - state = await self.proto.runstate_changed() - self.assertEqual(state, Runstate.CONNECTING) - await asyncio.sleep(0) - - with self.assertRaises(asyncio.TimeoutError): - await asyncio.wait_for(task, timeout=0) - - @TestBase.async_test - async def testRequire(self): - """ - Test what happens when a connection attempt is made while CONNECTING. - """ - await self._watch_runstates(*self.BAD_CONNECTION_STATES) - task = run_as_task(self._hanging_connection(), allow_cancellation=True) - - state = await self.proto.runstate_changed() - self.assertEqual(state, Runstate.CONNECTING) - - with self.assertRaises(StateError) as context: - await self._bad_connection('UNIX') - - self.assertEqual( - context.exception.error_message, - "NullProtocol is currently connecting." - ) - self.assertEqual(context.exception.state, Runstate.CONNECTING) - self.assertEqual(context.exception.required, Runstate.IDLE) - - task.cancel() - await task - - @TestBase.async_test - async def testImplicitRunstateInit(self): - """ - Test what happens if we do not wait on the runstate event until - AFTER a connection is made, i.e., connect()/accept() themselves - initialize the runstate event. All of the above tests force the - initialization by waiting on the runstate *first*. - """ - task = run_as_task(self._hanging_connection(), allow_cancellation=True) - - # Kick the loop to coerce the state change - await asyncio.sleep(0) - assert self.proto.runstate == Runstate.CONNECTING - - # We already missed the transition to CONNECTING - await self._watch_runstates(Runstate.DISCONNECTING, Runstate.IDLE) - - task.cancel() - await task - - -class Accept(Connect): - """ - All of the same tests as Connect, but using the accept() interface. - """ - async def _bad_connection(self, family: str): - assert family in ('INET', 'UNIX') - - if family == 'INET': - await self.proto.start_server_and_accept(('example.com', 1)) - elif family == 'UNIX': - await self.proto.start_server_and_accept('/dev/null') - - async def _hanging_connection(self): - with TemporaryDirectory(suffix='.qmp') as tmpdir: - sock = os.path.join(tmpdir, type(self.proto).__name__ + ".sock") - await self.proto.start_server_and_accept(sock) - - -class FakeSession(TestBase): - - def setUp(self): - super().setUp() - self.proto.fake_session = True - - async def _asyncSetUp(self): - await super()._asyncSetUp() - await self._watch_runstates(*self.GOOD_CONNECTION_STATES) - - async def _asyncTearDown(self): - await self.proto.disconnect() - await super()._asyncTearDown() - - #### - - @TestBase.async_test - async def testFakeConnect(self): - - """Test the full state lifecycle (via connect) with a no-op session.""" - await self.proto.connect('/not/a/real/path') - self.assertEqual(self.proto.runstate, Runstate.RUNNING) - - @TestBase.async_test - async def testFakeAccept(self): - """Test the full state lifecycle (via accept) with a no-op session.""" - await self.proto.start_server_and_accept('/not/a/real/path') - self.assertEqual(self.proto.runstate, Runstate.RUNNING) - - @TestBase.async_test - async def testFakeRecv(self): - """Test receiving a fake/null message.""" - await self.proto.start_server_and_accept('/not/a/real/path') - - logname = self.proto.logger.name - with self.assertLogs(logname, level='DEBUG') as context: - self.proto.trigger_input.set() - self.proto.trigger_input.clear() - await asyncio.sleep(0) # Kick reader. - - self.assertEqual( - context.output, - [f"DEBUG:{logname}:<-- None"], - ) - - @TestBase.async_test - async def testFakeSend(self): - """Test sending a fake/null message.""" - await self.proto.start_server_and_accept('/not/a/real/path') - - logname = self.proto.logger.name - with self.assertLogs(logname, level='DEBUG') as context: - # Cheat: Send a Null message to nobody. - await self.proto.send_msg() - # Kick writer; awaiting on a queue.put isn't sufficient to yield. - await asyncio.sleep(0) - - self.assertEqual( - context.output, - [f"DEBUG:{logname}:--> None"], - ) - - async def _prod_session_api( - self, - current_state: Runstate, - error_message: str, - accept: bool = True - ): - with self.assertRaises(StateError) as context: - if accept: - await self.proto.start_server_and_accept('/not/a/real/path') - else: - await self.proto.connect('/not/a/real/path') - - self.assertEqual(context.exception.error_message, error_message) - self.assertEqual(context.exception.state, current_state) - self.assertEqual(context.exception.required, Runstate.IDLE) - - @TestBase.async_test - async def testAcceptRequireRunning(self): - """Test that accept() cannot be called when Runstate=RUNNING""" - await self.proto.start_server_and_accept('/not/a/real/path') - - await self._prod_session_api( - Runstate.RUNNING, - "NullProtocol is already connected and running.", - accept=True, - ) - - @TestBase.async_test - async def testConnectRequireRunning(self): - """Test that connect() cannot be called when Runstate=RUNNING""" - await self.proto.start_server_and_accept('/not/a/real/path') - - await self._prod_session_api( - Runstate.RUNNING, - "NullProtocol is already connected and running.", - accept=False, - ) - - @TestBase.async_test - async def testAcceptRequireDisconnecting(self): - """Test that accept() cannot be called when Runstate=DISCONNECTING""" - await self.proto.start_server_and_accept('/not/a/real/path') - - # Cheat: force a disconnect. - await self.proto.simulate_disconnect() - - await self._prod_session_api( - Runstate.DISCONNECTING, - ("NullProtocol is disconnecting." - " Call disconnect() to return to IDLE state."), - accept=True, - ) - - @TestBase.async_test - async def testConnectRequireDisconnecting(self): - """Test that connect() cannot be called when Runstate=DISCONNECTING""" - await self.proto.start_server_and_accept('/not/a/real/path') - - # Cheat: force a disconnect. - await self.proto.simulate_disconnect() - - await self._prod_session_api( - Runstate.DISCONNECTING, - ("NullProtocol is disconnecting." - " Call disconnect() to return to IDLE state."), - accept=False, - ) - - -class SimpleSession(TestBase): - - def setUp(self): - super().setUp() - self.server = LineProtocol(type(self).__name__ + '-server') - - async def _asyncSetUp(self): - await super()._asyncSetUp() - await self._watch_runstates(*self.GOOD_CONNECTION_STATES) - - async def _asyncTearDown(self): - await self.proto.disconnect() - try: - await self.server.disconnect() - except EOFError: - pass - await super()._asyncTearDown() - - @TestBase.async_test - async def testSmoke(self): - with TemporaryDirectory(suffix='.qmp') as tmpdir: - sock = os.path.join(tmpdir, type(self.proto).__name__ + ".sock") - server_task = asyncio.create_task( - self.server.start_server_and_accept(sock)) - - # give the server a chance to start listening [...] - await asyncio.sleep(0) - await self.proto.connect(sock) diff --git a/python/tests/pylint.sh b/python/tests/pylint.sh deleted file mode 100755 index 2b68da90df76..000000000000 --- a/python/tests/pylint.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -e -# See commit message for environment variable explainer. -SETUPTOOLS_USE_DISTUTILS=stdlib python3 -m pylint qemu/ -SETUPTOOLS_USE_DISTUTILS=stdlib python3 -m pylint scripts/ diff --git a/python/tests/qapi-flake8.sh b/python/tests/qapi-flake8.sh deleted file mode 100755 index c69f9ea2e00d..000000000000 --- a/python/tests/qapi-flake8.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -e -# SPDX-License-Identifier: GPL-2.0-or-later - -python3 -m flake8 ../scripts/qapi/ \ - ../docs/sphinx/qapidoc.py \ - ../docs/sphinx/qapi_domain.py diff --git a/python/tests/qapi-isort.sh b/python/tests/qapi-isort.sh deleted file mode 100755 index 067c16d5d945..000000000000 --- a/python/tests/qapi-isort.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -e -# SPDX-License-Identifier: GPL-2.0-or-later - -python3 -m isort --sp . -c ../scripts/qapi/ -# Force isort to recognize "compat" as a local module and not third-party -python3 -m isort --sp . -c -p compat \ - ../docs/sphinx/qapi_domain.py \ - ../docs/sphinx/qapidoc.py diff --git a/python/tests/qapi-mypy.sh b/python/tests/qapi-mypy.sh deleted file mode 100755 index 363dbaf8c06e..000000000000 --- a/python/tests/qapi-mypy.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -e -# SPDX-License-Identifier: GPL-2.0-or-later - -python3 -m mypy ../scripts/qapi diff --git a/python/tests/qapi-pylint.sh b/python/tests/qapi-pylint.sh deleted file mode 100755 index 8767d9d2a2d3..000000000000 --- a/python/tests/qapi-pylint.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -e -# SPDX-License-Identifier: GPL-2.0-or-later - -SETUPTOOLS_USE_DISTUTILS=stdlib python3 -m pylint \ - --rcfile=../scripts/qapi/pylintrc \ - ../scripts/qapi/ \ - ../docs/sphinx/qapidoc.py \ - ../docs/sphinx/qapi_domain.py diff --git a/python/wheels/meson-1.9.0-py3-none-any.whl b/python/wheels/meson-1.9.0-py3-none-any.whl deleted file mode 100644 index 57cc75cb1386..000000000000 Binary files a/python/wheels/meson-1.9.0-py3-none-any.whl and /dev/null differ diff --git a/python/wheels/qemu_qmp-0.0.5-py3-none-any.whl b/python/wheels/qemu_qmp-0.0.5-py3-none-any.whl new file mode 100644 index 000000000000..6372b7544ffb Binary files /dev/null and b/python/wheels/qemu_qmp-0.0.5-py3-none-any.whl differ diff --git a/pythondeps.toml b/pythondeps.toml index 9a096160e62a..a0717742423a 100644 --- a/pythondeps.toml +++ b/pythondeps.toml @@ -31,6 +31,23 @@ meson = { accepted = ">=1.10.0", installed = "1.10.0", canary = "meson" } sphinx = { accepted = ">=3.4.3", installed = "6.2.1", canary = "sphinx-build" } sphinx_rtd_theme = { accepted = ">=0.5", installed = "1.2.2" } -[testdeps] -qemu.qmp = { accepted = ">=0.0.3", installed = "0.0.3" } +# This test group is for dependencies required to run various tooling +# and tests that should always be installed at configure time. It should +# only include dependencies that can be guaranteed via configure from +# system packages, or python packages we vendor. +[tooling] +"qemu.qmp" = { accepted = ">=0.0.5", installed = "0.0.5" } +"qemu" = { path = "python/" } +# NB: The following dependencies should be a little bit more modern than +# the versions listed here, but we are still using Debian 11 for several +# GitLab CI tests, so we are further restricted. +"setuptools" = { accepted = ">=44.1.1" } +"wheel" = { accepted = ">=0.34.2" } +# pip should be guaranteed by mkvenv, this is merely a sanity check for +# which version we are counting on being present. +"pip" = { accepted = ">=20.3.4" } + +# This test group is for functional tests, and can include dependencies +# fetched from PyPI. +[functests] pygdbmi = { accepted = ">=0.11.0.0", installed = "0.11.0.0" } diff --git a/pyvenv/meson.build b/pyvenv/meson.build new file mode 100644 index 000000000000..4e7d8d9fcc4b --- /dev/null +++ b/pyvenv/meson.build @@ -0,0 +1,27 @@ +# Note that this file only controls "optional" dependency groups; groups +# unconditionally installed at configure time are handled in configure +# instead; namely: meson, sphinx, and the tooling groups. + +# Note that this command may or may not include the "--online" flag +# based on configuration. +ensuregroup_cmd = config_host['MKVENV_ENSUREGROUP'].split() + +pyvenv_common_deps = files( + meson.project_source_root() + '/pythondeps.toml', + meson.project_source_root() + '/python/scripts/mkvenv.py' +) +pyvenv_wheel_dir = meson.project_source_root() + '/python/wheels' + +# This group is allowed to (and must) rely on internet to fetch from +# PyPI. Force the use of the --online flag here. +pyvenv_functests_group = custom_target( + 'pyvenv_functests_group', + output: 'functests.group', + input: pyvenv_common_deps, + command: ensuregroup_cmd + [ + '--online', + '--dir', pyvenv_wheel_dir, + '@INPUT0@', + 'functests', + ], +) diff --git a/qapi/block-core.json b/qapi/block-core.json index b82af7425614..da0b36a3751b 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -855,6 +855,10 @@ # # Get a list of `BlockInfo` for all virtual block devices. # +# @flat: Omit nested data about the backing image, i.e. `BlockInfo` +# member 'inserted.image.backing-image' will be absent. +# Default is false. (Since 11.0) +# # Returns: a list describing each virtual block device. Filter nodes # that were created implicitly are skipped over. # @@ -945,6 +949,7 @@ # } ## { 'command': 'query-block', 'returns': ['BlockInfo'], + 'data': { '*flat': 'bool' }, 'allow-preconfig': true } ## @@ -5789,7 +5794,7 @@ # .. note:: If action is "stop", a `STOP` event will eventually follow # the `BLOCK_IO_ERROR` event. # -# .. note:: This event is rate-limited. +# .. note:: This event is rate-limited, except if action is "stop". # # Since: 0.13 # diff --git a/qapi/cxl.json b/qapi/cxl.json index eeddb58d1d3f..81d6198ba030 100644 --- a/qapi/cxl.json +++ b/qapi/cxl.json @@ -31,33 +31,55 @@ } ## -# @cxl-inject-general-media-event: +# @CXLCommonEventBase: # -# Inject an event record for a General Media Event (CXL r3.0 -# 8.2.9.2.1.1). This event type is reported via one of the event logs -# specified via the log parameter. +# Common event base for a CXL Event (CXL r3.2 8.2.10.2.1 +# Table 8-55 Common Event Record Format). # # @path: CXL type 3 device canonical QOM path # # @log: event log to add the event to # -# @flags: Event Record Flags. See CXL r3.0 Table 8-42 Common Event +# @flags: Event Record Flags. See CXL r3.2 Table 8-55 Common Event # Record Format, Event Record Flags for subfield definitions. # +# @maint-op-class: Maintenance operation class the device requests to +# initiate. +# +# @maint-op-subclass: Maintenance operation subclass the device +# requests to initiate. +# +# @ld-id: Logical Device (LD) ID of LD from where the event +# originated. +# +# @head-id: ID of the device head from where the event originated. +# +# Since: 8.1 +## +{ 'struct': 'CXLCommonEventBase', + 'data': { 'path': 'str', 'log': 'CxlEventLog', 'flags': 'uint32', + '*maint-op-class':'uint8', '*maint-op-subclass':'uint8', + '*ld-id':'uint16', '*head-id':'uint8' } } + +## +# @CXLGeneralMediaEvent: +# +# Event record for a General Media Event (CXL r3.2 8.2.10.2.1.1). +# # @dpa: Device Physical Address (relative to @path device). Note -# lower bits include some flags. See CXL r3.0 Table 8-43 General +# lower bits include some flags. See CXL r3.2 Table 8-57 General # Media Event Record, Physical Address. # # @descriptor: Memory Event Descriptor with additional memory event -# information. See CXL r3.0 Table 8-43 General Media Event +# information. See CXL r3.2 Table 8-57 General Media Event # Record, Memory Event Descriptor for bit definitions. # -# @type: Type of memory event that occurred. See CXL r3.0 Table 8-43 +# @type: Type of memory event that occurred. See CXL r3.2 Table 8-57 # General Media Event Record, Memory Event Type for possible # values. # # @transaction-type: Type of first transaction that caused the event -# to occur. See CXL r3.0 Table 8-43 General Media Event Record, +# to occur. See CXL r3-2 Table 8-57 General Media Event Record, # Transaction Type for possible values. # # @channel: The channel of the memory event location. A channel is an @@ -72,42 +94,58 @@ # @component-id: Device specific component identifier for the event. # May describe a field replaceable sub-component of the device. # +# @is-comp-id-pldm: This flag specifies whether the device-specific +# component identifier format follows PLDM. +# +# @cme-ev-flags: Advanced programmable corrected memory error +# threshold event flags. +# +# @cme-count: Corrected memory error count at event. +# +# @sub-type: Memory event sub-type. +# # Since: 8.1 ## -{ 'command': 'cxl-inject-general-media-event', - 'data': { 'path': 'str', 'log': 'CxlEventLog', 'flags': 'uint8', - 'dpa': 'uint64', 'descriptor': 'uint8', +{ 'struct': 'CXLGeneralMediaEvent', + 'base': 'CXLCommonEventBase', + 'data': { 'dpa': 'uint64', 'descriptor': 'uint8', 'type': 'uint8', 'transaction-type': 'uint8', '*channel': 'uint8', '*rank': 'uint8', - '*device': 'uint32', '*component-id': 'str' } } + '*device': 'uint32', '*component-id': 'str', + '*is-comp-id-pldm':'bool', + '*cme-ev-flags':'uint8', '*cme-count':'uint32', + 'sub-type':'uint8' } } ## -# @cxl-inject-dram-event: -# -# Inject an event record for a DRAM Event (CXL r3.0 8.2.9.2.1.2). -# This event type is reported via one of the event logs specified via -# the log parameter. +# @cxl-inject-general-media-event: # -# @path: CXL type 3 device canonical QOM path +# Inject an event record for a General Media Event (CXL r3.2 +# 8.2.10.2.1.1). This event type is reported via one of the event +# logs specified via the log parameter. # -# @log: Event log to add the event to +# Since: 8.1 +## +{ 'command': 'cxl-inject-general-media-event', + 'data': 'CXLGeneralMediaEvent' } + +## +# @CXLDRAMEvent: # -# @flags: Event Record Flags. See CXL r3.0 Table 8-42 Common Event -# Record Format, Event Record Flags for subfield definitions. +# Event record for a DRAM Event (CXL r3.2 8.2.10.2.1.2). # # @dpa: Device Physical Address (relative to @path device). Note -# lower bits include some flags. See CXL r3.0 Table 8-44 DRAM +# lower bits include some flags. See CXL r3.2 Table 8-58 DRAM # Event Record, Physical Address. # # @descriptor: Memory Event Descriptor with additional memory event -# information. See CXL r3.0 Table 8-44 DRAM Event Record, Memory +# information. See CXL r3.2 Table 8-58 DRAM Event Record, Memory # Event Descriptor for bit definitions. # -# @type: Type of memory event that occurred. See CXL r3.0 Table 8-44 +# @type: Type of memory event that occurred. See CXL r3.2 Table 8-58 # DRAM Event Record, Memory Event Type for possible values. # # @transaction-type: Type of first transaction that caused the event -# to occur. See CXL r3.0 Table 8-44 DRAM Event Record, +# to occur. See CXL r3.2 Table 8-58 DRAM Event Record, # Transaction Type for possible values. # # @channel: The channel of the memory event location. A channel is an @@ -131,43 +169,65 @@ # @correction-mask: Bits within each nibble. Used in order of bits # set in the nibble-mask. Up to 4 nibbles may be covered. # +# @component-id: Device specific component identifier for the event. +# May describe a field replaceable sub-component of the device. +# +# @is-comp-id-pldm: This flag specifies whether the device-specific +# component identifier format follows PLDM. +# +# @sub-channel: The sub-channel of the memory event location. +# +# @cme-ev-flags: Advanced programmable corrected memory error +# threshold event flags. +# +# @cvme-count: Corrected volatile memory error count at event. +# +# @sub-type: Memory event sub-type. +# # Since: 8.1 ## -{ 'command': 'cxl-inject-dram-event', - 'data': { 'path': 'str', 'log': 'CxlEventLog', 'flags': 'uint8', - 'dpa': 'uint64', 'descriptor': 'uint8', +{ 'struct': 'CXLDRAMEvent', + 'base': 'CXLCommonEventBase', + 'data': { 'dpa': 'uint64', 'descriptor': 'uint8', 'type': 'uint8', 'transaction-type': 'uint8', '*channel': 'uint8', '*rank': 'uint8', '*nibble-mask': 'uint32', '*bank-group': 'uint8', '*bank': 'uint8', '*row': 'uint32', - '*column': 'uint16', '*correction-mask': [ 'uint64' ] + '*column': 'uint16', '*correction-mask': [ 'uint64' ], + '*component-id': 'str', '*is-comp-id-pldm':'bool', + '*sub-channel':'uint8', + '*cme-ev-flags':'uint8', '*cvme-count':'uint32', + 'sub-type':'uint8' }} ## -# @cxl-inject-memory-module-event: -# -# Inject an event record for a Memory Module Event (CXL r3.0 -# 8.2.9.2.1.3). This event includes a copy of the Device Health info -# at the time of the event. +# @cxl-inject-dram-event: # -# @path: CXL type 3 device canonical QOM path +# Inject an event record for a DRAM Event (CXL r3.2 8.2.10.2.1.2). +# This event type is reported via one of the event logs +# specified via the log parameter. # -# @log: Event Log to add the event to +# Since: 8.1 +## +{ 'command': 'cxl-inject-dram-event', + 'data': 'CXLDRAMEvent' } + +## +# @CXLMemModuleEvent: # -# @flags: Event Record Flags. See CXL r3.0 Table 8-42 Common Event -# Record Format, Event Record Flags for subfield definitions. +# Event record for a Memory Module Event (CXL r3.2 8.2.10.2.1.3). # -# @type: Device Event Type. See CXL r3.0 Table 8-45 Memory Module +# @type: Device Event Type. See CXL r3.2 Table 8-59 Memory Module # Event Record for bit definitions for bit definiions. # -# @health-status: Overall health summary bitmap. See CXL r3.0 Table -# 8-100 Get Health Info Output Payload, Health Status for bit +# @health-status: Overall health summary bitmap. See CXL r3.2 Table +# 8-148 Get Health Info Output Payload, Health Status for bit # definitions. # -# @media-status: Overall media health summary. See CXL r3.0 Table -# 8-100 Get Health Info Output Payload, Media Status for bit +# @media-status: Overall media health summary. See CXL r3.2 Table +# 8-148 Get Health Info Output Payload, Media Status for bit # definitions. # -# @additional-status: See CXL r3.0 Table 8-100 Get Health Info Output +# @additional-status: See CXL r3.2 Table 8-148 Get Health Info Output # Payload, Additional Status for subfield definitions. # # @life-used: Percentage (0-100) of factory expected life span. @@ -183,18 +243,40 @@ # @corrected-persistent-error-count: Total number of correctable # errors in persistent memory # +# @component-id: Device specific component identifier for the event. +# May describe a field replaceable sub-component of the device. +# +# @is-comp-id-pldm: This flag specifies whether the device-specific +# component identifier format follows PLDM. +# +# @sub-type: Device event sub-type. +# # Since: 8.1 ## -{ 'command': 'cxl-inject-memory-module-event', - 'data': { 'path': 'str', 'log': 'CxlEventLog', 'flags' : 'uint8', - 'type': 'uint8', 'health-status': 'uint8', +{ 'struct': 'CXLMemModuleEvent', + 'base': 'CXLCommonEventBase', + 'data': { 'type': 'uint8', 'health-status': 'uint8', 'media-status': 'uint8', 'additional-status': 'uint8', 'life-used': 'uint8', 'temperature' : 'int16', 'dirty-shutdown-count': 'uint32', 'corrected-volatile-error-count': 'uint32', - 'corrected-persistent-error-count': 'uint32' + 'corrected-persistent-error-count': 'uint32', + '*component-id': 'str', '*is-comp-id-pldm':'bool', + 'sub-type':'uint8' }} +## +# @cxl-inject-memory-module-event: +# +# Inject an event record for a Memory Module Event (CXL r3.2 +# 8.2.10.2.1.3). This event includes a copy of the Device Health info +# at the time of the event. +# +# Since: 8.1 +## +{ 'command': 'cxl-inject-memory-module-event', + 'data': 'CXLMemModuleEvent' } + ## # @cxl-inject-poison: # diff --git a/qapi/machine.json b/qapi/machine.json index ef8575b6eb9f..685e4e29b87d 100644 --- a/qapi/machine.json +++ b/qapi/machine.json @@ -38,7 +38,7 @@ ## { 'enum' : 'SysEmuTarget', 'data' : [ 'aarch64', 'alpha', 'arm', 'avr', 'hexagon', 'hppa', 'i386', - 'loongarch64', 'm68k', 'microblaze', 'microblazeel', 'mips', 'mips64', + 'loongarch64', 'm68k', 'microblaze', 'mips', 'mips64', 'mips64el', 'mipsel', 'or1k', 'ppc', 'ppc64', 'riscv32', 'riscv64', 'rx', 's390x', 'sh4', 'sh4eb', 'sparc', 'sparc64', 'tricore', diff --git a/qapi/migration.json b/qapi/migration.json index f925e5541bfa..7134d4ce47ea 100644 --- a/qapi/migration.json +++ b/qapi/migration.json @@ -158,7 +158,10 @@ # # @completed: migration is finished. # -# @failed: some error occurred during migration process. +# @failing: error occurred during migration, clean-up underway. +# (since 11.0) +# +# @failed: error occurred during migration, clean-up done. # # @colo: VM is in the process of fault tolerance, VM can not get into # this state unless colo capability is enabled for migration. @@ -181,8 +184,8 @@ 'data': [ 'none', 'setup', 'cancelling', 'cancelled', 'active', 'postcopy-device', 'postcopy-active', 'postcopy-paused', 'postcopy-recover-setup', - 'postcopy-recover', 'completed', 'failed', 'colo', - 'pre-switchover', 'device', 'wait-unplug' ] } + 'postcopy-recover', 'completed', 'failing', 'failed', + 'colo', 'pre-switchover', 'device', 'wait-unplug' ] } ## # @VfioStats: diff --git a/qapi/qom.json b/qapi/qom.json index 6f5c9de0f0b2..c653248f85d8 100644 --- a/qapi/qom.json +++ b/qapi/qom.json @@ -1009,13 +1009,19 @@ # designated guest firmware page for measured boot with -kernel # (default: false) (since 6.2) # +# Features: +# +# @confidential-guest-reset: If present, the hypervisor supports +# confidential guest resets (since 11.0). +# # Since: 9.1 ## { 'struct': 'SevCommonProperties', 'data': { '*sev-device': 'str', '*cbitpos': 'uint32', 'reduced-phys-bits': 'uint32', - '*kernel-hashes': 'bool' } } + '*kernel-hashes': 'bool' }, + 'features': ['confidential-guest-reset']} ## # @SevGuestProperties: @@ -1136,6 +1142,11 @@ # it, the guest will not be able to get a TD quote for # attestation. # +# Features: +# +# @confidential-guest-reset: If present, the hypervisor supports +# confidential guest resets (since 11.0). +# # Since: 10.1 ## { 'struct': 'TdxGuestProperties', @@ -1144,7 +1155,8 @@ '*mrconfigid': 'str', '*mrowner': 'str', '*mrownerconfig': 'str', - '*quote-generation-socket': 'SocketAddress' } } + '*quote-generation-socket': 'SocketAddress' }, + 'features': ['confidential-guest-reset']} ## # @ThreadContextProperties: diff --git a/qapi/virtio.json b/qapi/virtio.json index cd67c4f52e3c..09dd0e6d05ab 100644 --- a/qapi/virtio.json +++ b/qapi/virtio.json @@ -970,15 +970,24 @@ ## # @VirtIOGPUOutput: # -# Describes configuration of a VirtIO GPU output. +# Describes configuration of a VirtIO GPU output. If both @xres and +# @yres are set, they take precedence over root virtio-gpu resolution +# configuration and enable the corresponding output. If none of @xres +# and @yres are set, root virtio-gpu resolution configuration takes +# precedence and only the first output is enabled. Only setting one +# of @xres or @yres is an error. # # @name: the name of the output # +# @xres: horizontal resolution of the output in pixels (since 11.0) +# +# @yres: vertical resolution of the output in pixels (since 11.0) +# # Since: 10.1 ## { 'struct': 'VirtIOGPUOutput', - 'data': { 'name': 'str' } } + 'data': { 'name': 'str', '*xres': 'uint16', '*yres': 'uint16' } } ## # @DummyVirtioForceArrays: diff --git a/qemu-options.hx b/qemu-options.hx index 33fcfe7ce665..890c4f1d2307 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -25,10 +25,10 @@ SRST ERST DEF("machine", HAS_ARG, QEMU_OPTION_machine, \ - "-machine [type=]name[,prop[=value][,...]]\n" + "-machine [type=]name[,prop=value[,...]]\n" " selects emulated machine ('-machine help' for list)\n" " property accel=accel1[:accel2[:...]] selects accelerator\n" - " supported accelerators are kvm, xen, hvf, nvmm, whpx, mshv or tcg (default: tcg)\n" + " supported accelerators are kvm, xen, hvf, nitro, nvmm, whpx, mshv or tcg (default: tcg)\n" " vmport=on|off|auto controls emulation of vmport (default: auto)\n" " dump-guest-core=on|off include guest memory in a core dump (default=on)\n" " mem-merge=on|off controls memory merge support (default: on)\n" @@ -67,7 +67,7 @@ SRST ``accel=accels1[:accels2[:...]]`` This is used to enable an accelerator. Depending on the target - architecture, kvm, xen, hvf, nvmm, whpx, mshv or tcg can be + architecture, kvm, xen, hvf, nitro, nvmm, whpx, mshv or tcg can be available. By default, tcg is used. If there is more than one accelerator specified, the next one is used if the previous one fails to initialize. @@ -227,8 +227,8 @@ SRST ERST DEF("accel", HAS_ARG, QEMU_OPTION_accel, - "-accel [accel=]accelerator[,prop[=value][,...]]\n" - " select accelerator (kvm, xen, hvf, nvmm, whpx, mshv or tcg; use 'help' for a list)\n" + "-accel [accel=]accelerator[,prop=value[,...]]\n" + " select accelerator (kvm, xen, hvf, nitro, nvmm, whpx, mshv or tcg; use 'help' for a list)\n" " igd-passthru=on|off (enable Xen integrated Intel graphics passthrough, default=off)\n" " kernel-irqchip=on|off|split controls accelerated irqchip support (default=on)\n" " kvm-shadow-mem=size of KVM shadow MMU in bytes\n" @@ -243,7 +243,7 @@ DEF("accel", HAS_ARG, QEMU_OPTION_accel, SRST ``-accel name[,prop=value[,...]]`` This is used to enable an accelerator. Depending on the target - architecture, kvm, xen, hvf, nvmm, whpx, mshv or tcg can be available. + architecture, kvm, xen, hvf, nitro, nvmm, whpx, mshv or tcg can be available. By default, tcg is used. If there is more than one accelerator specified, the next one is used if the previous one fails to initialize. @@ -789,17 +789,17 @@ ERST DEF("audio", HAS_ARG, QEMU_OPTION_audio, - "-audio [driver=]driver[,prop[=value][,...]]\n" + "-audio [driver=]driver[,prop=value[,...]]\n" " specifies default audio backend when `audiodev` is not\n" " used to create a machine or sound device;" " options are the same as for -audiodev\n" - "-audio [driver=]driver,model=value[,prop[=value][,...]]\n" + "-audio [driver=]driver,model=value[,prop=value[,...]]\n" " specifies the audio backend and device to use;\n" " apart from 'model', options are the same as for -audiodev.\n" " use '-audio model=help' to show possible devices.\n", QEMU_ARCH_ALL) SRST -``-audio [driver=]driver[,model=value][,prop[=value][,...]]`` +``-audio [driver=]driver[,model=value][,prop=value[,...]]`` If the ``model`` option is specified, ``-audio`` is a shortcut for configuring both the guest audio hardware and the host audio backend in one go. The guest hardware model can be set with @@ -827,7 +827,7 @@ SRST ERST DEF("audiodev", HAS_ARG, QEMU_OPTION_audiodev, - "-audiodev [driver=]driver,id=id[,prop[=value][,...]]\n" + "-audiodev [driver=]driver,id=id[,prop=value[,...]]\n" " specifies the audio backend to use\n" " Use ``-audiodev help`` to list the available drivers\n" " id= identifier of the backend\n" @@ -840,25 +840,25 @@ DEF("audiodev", HAS_ARG, QEMU_OPTION_audiodev, " valid values: s8, s16, s32, u8, u16, u32, f32\n" " in|out.voices= number of voices to use\n" " in|out.buffer-length= length of buffer in microseconds\n" - "-audiodev none,id=id,[,prop[=value][,...]]\n" + "-audiodev none,id=id,[,prop=value[,...]]\n" " dummy driver that discards all output\n" #ifdef CONFIG_AUDIO_ALSA - "-audiodev alsa,id=id[,prop[=value][,...]]\n" + "-audiodev alsa,id=id[,prop=value[,...]]\n" " in|out.dev= name of the audio device to use\n" " in|out.period-length= length of period in microseconds\n" " in|out.try-poll= attempt to use poll mode\n" " threshold= threshold (in microseconds) when playback starts\n" #endif #ifdef CONFIG_AUDIO_COREAUDIO - "-audiodev coreaudio,id=id[,prop[=value][,...]]\n" + "-audiodev coreaudio,id=id[,prop=value[,...]]\n" " in|out.buffer-count= number of buffers\n" #endif #ifdef CONFIG_AUDIO_DSOUND - "-audiodev dsound,id=id[,prop[=value][,...]]\n" + "-audiodev dsound,id=id[,prop=value[,...]]\n" " latency= add extra latency to playback in microseconds\n" #endif #ifdef CONFIG_AUDIO_OSS - "-audiodev oss,id=id[,prop[=value][,...]]\n" + "-audiodev oss,id=id[,prop=value[,...]]\n" " in|out.dev= path of the audio device to use\n" " in|out.buffer-count= number of buffers\n" " in|out.try-poll= attempt to use poll mode\n" @@ -867,40 +867,45 @@ DEF("audiodev", HAS_ARG, QEMU_OPTION_audiodev, " dsp-policy= set timing policy (0..10), -1 to use fragment mode\n" #endif #ifdef CONFIG_AUDIO_PA - "-audiodev pa,id=id[,prop[=value][,...]]\n" + "-audiodev pa,id=id[,prop=value[,...]]\n" " server= PulseAudio server address\n" " in|out.name= source/sink device name\n" " in|out.latency= desired latency in microseconds\n" #endif #ifdef CONFIG_AUDIO_PIPEWIRE - "-audiodev pipewire,id=id[,prop[=value][,...]]\n" + "-audiodev pipewire,id=id[,prop=value[,...]]\n" " in|out.name= source/sink device name\n" " in|out.stream-name= name of pipewire stream\n" " in|out.latency= desired latency in microseconds\n" #endif #ifdef CONFIG_AUDIO_SDL - "-audiodev sdl,id=id[,prop[=value][,...]]\n" + "-audiodev sdl,id=id[,prop=value[,...]]\n" " in|out.buffer-count= number of buffers\n" #endif #ifdef CONFIG_AUDIO_SNDIO - "-audiodev sndio,id=id[,prop[=value][,...]]\n" + "-audiodev sndio,id=id[,prop=value[,...]]\n" #endif #ifdef CONFIG_SPICE - "-audiodev spice,id=id[,prop[=value][,...]]\n" + "-audiodev spice,id=id[,prop=value[,...]]\n" #endif #ifdef CONFIG_DBUS_DISPLAY - "-audiodev dbus,id=id[,prop[=value][,...]]\n" + "-audiodev dbus,id=id[,prop=value[,...]]\n" #endif - "-audiodev wav,id=id[,prop[=value][,...]]\n" + "-audiodev wav,id=id[,prop=value[,...]]\n" " path= path of wav file to record\n", QEMU_ARCH_ALL) SRST -``-audiodev [driver=]driver,id=id[,prop[=value][,...]]`` - Adds a new audio backend driver identified by id. There are global - and driver specific properties. Some values can be set differently - for input and output, they're marked with ``in|out.``. You can set - the input's property with ``in.prop`` and the output's property with - ``out.prop``. For example: +``-audiodev [driver=]driver,id=id[,prop=value[,...]]`` + Adds a new audio backend driver identified by id. + + If no audio backend is specified, QEMU will attempt to select a + default one. The ``-display`` option may influence which backend is + selected. + + There are global and driver specific properties. Some values can be + set differently for input and output, they're marked with ``in|out.``. + You can set the input's property with ``in.prop`` and the output's + property with ``out.prop``. For example: :: @@ -954,11 +959,11 @@ SRST ``in|out.buffer-length=usecs`` Sets the size of the buffer in microseconds. -``-audiodev none,id=id[,prop[=value][,...]]`` +``-audiodev none,id=id[,prop=value[,...]]`` Creates a dummy backend that discards all outputs. This backend has no backend specific properties. -``-audiodev alsa,id=id[,prop[=value][,...]]`` +``-audiodev alsa,id=id[,prop=value[,...]]`` Creates backend using the ALSA. This backend is only available on Linux. @@ -977,7 +982,7 @@ SRST ``threshold=threshold`` Threshold (in microseconds) when playback starts. Default is 0. -``-audiodev coreaudio,id=id[,prop[=value][,...]]`` +``-audiodev coreaudio,id=id[,prop=value[,...]]`` Creates a backend using Apple's Core Audio. This backend is only available on Mac OS and only supports playback. @@ -986,7 +991,17 @@ SRST ``in|out.buffer-count=count`` Sets the count of the buffers. -``-audiodev dsound,id=id[,prop[=value][,...]]`` +``-audiodev dbus,id=id[,prop=value[,...]]`` + Creates a D-Bus backend. It must be associated with the display + (as ``-display dbus,audiodev=id``). (Since 7.0) + + D-Bus specific options are: + + ``nsamples`` + Number of samples per read/write (default to 480, 10ms at 48kHz) + (Since 10.0) + +``-audiodev dsound,id=id[,prop=value[,...]]`` Creates a backend using Microsoft's DirectSound. This backend is only available on Windows and only supports playback. @@ -996,7 +1011,7 @@ SRST Add extra usecs microseconds latency to playback. Default is 10000 (10 ms). -``-audiodev oss,id=id[,prop[=value][,...]]`` +``-audiodev oss,id=id[,prop=value[,...]]`` Creates a backend using OSS. This backend is available on most Unix-like systems. @@ -1025,7 +1040,7 @@ SRST buffer sizes specified by ``buffer`` and ``buffer-count``. This option is ignored if you do not have OSS 4. Default is 5. -``-audiodev pa,id=id[,prop[=value][,...]]`` +``-audiodev pa,id=id[,prop=value[,...]]`` Creates a backend using PulseAudio. This backend is available on most systems. @@ -1041,7 +1056,7 @@ SRST Desired latency in microseconds. The PulseAudio server will try to honor this value but actual latencies may be lower or higher. -``-audiodev pipewire,id=id[,prop[=value][,...]]`` +``-audiodev pipewire,id=id[,prop=value[,...]]`` Creates a backend using PipeWire. This backend is available on most systems. @@ -1056,7 +1071,7 @@ SRST ``in|out.stream-name`` Specify the name of pipewire stream. -``-audiodev sdl,id=id[,prop[=value][,...]]`` +``-audiodev sdl,id=id[,prop=value[,...]]`` Creates a backend using SDL. This backend is available on most systems, but you should use your platform's native backend if possible. @@ -1066,7 +1081,7 @@ SRST ``in|out.buffer-count=count`` Sets the count of the buffers. -``-audiodev sndio,id=id[,prop[=value][,...]]`` +``-audiodev sndio,id=id[,prop=value[,...]]`` Creates a backend using SNDIO. This backend is available on OpenBSD and most other Unix-like systems. @@ -1079,13 +1094,13 @@ SRST ``in|out.latency=usecs`` Sets the desired period length in microseconds. -``-audiodev spice,id=id[,prop[=value][,...]]`` +``-audiodev spice,id=id[,prop=value[,...]]`` Creates a backend that sends audio through SPICE. This backend requires ``-spice`` and automatically selected in that case, so usually you can ignore this option. This backend has no backend specific properties. -``-audiodev wav,id=id[,prop[=value][,...]]`` +``-audiodev wav,id=id[,prop=value[,...]]`` Creates a backend that writes audio to a WAV file. Backend specific options are: @@ -1096,21 +1111,21 @@ SRST ERST DEF("device", HAS_ARG, QEMU_OPTION_device, - "-device driver[,prop[=value][,...]]\n" + "-device driver[,prop=value[,...]]\n" " add device (based on driver)\n" " prop=value,... sets driver properties\n" " use '-device help' to print all possible drivers\n" " use '-device driver,help' to print all possible properties\n", QEMU_ARCH_ALL) SRST -``-device driver[,prop[=value][,...]]`` +``-device driver[,prop=value[,...]]`` Add device driver. prop=value sets driver properties. Valid properties depend on the driver. To get help on possible drivers and properties, use ``-device help`` and ``-device driver,help``. Some drivers are: -``-device ipmi-bmc-sim,id=id[,prop[=value][,...]]`` +``-device ipmi-bmc-sim,id=id[,prop=value[,...]]`` Add an IPMI BMC. This is a simulation of a hardware management interface processor that normally sits on a system. It provides a watchdog and the ability to reset and power control the system. You @@ -2198,8 +2213,11 @@ DEF("display", HAS_ARG, QEMU_OPTION_display, , QEMU_ARCH_ALL) SRST ``-display type`` - Select type of display to use. Use ``-display help`` to list the available - display types. Valid values for type are + Select type of display to use. This may also influence the default + audio backend selection. + + Use ``-display help`` to list the available display types. Valid values + for type are ``spice-app[,gl=on|off]`` Start QEMU as a Spice server and launch the default Spice client @@ -2209,6 +2227,9 @@ SRST ``dbus`` Export the display over D-Bus interfaces. (Since 7.0) + If no audio backend is specified and the dbus display backend is + specified, the dbus audio backend is used by default. + The connection is registered with the "org.qemu" name (and queued when already owned). @@ -2334,19 +2355,19 @@ DEF("spice", HAS_ARG, QEMU_OPTION_spice, " [,x509-dh-key-file=][,addr=addr]\n" " [,ipv4=on|off][,ipv6=on|off][,unix=on|off]\n" " [,tls-ciphers=]\n" - " [,tls-channel=[main|display|cursor|inputs|record|playback]]\n" - " [,plaintext-channel=[main|display|cursor|inputs|record|playback]]\n" + " [,tls-channel=main|display|cursor|inputs|record|playback]\n" + " [,plaintext-channel=main|display|cursor|inputs|record|playback]\n" " [,sasl=on|off][,disable-ticketing=on|off]\n" " [,password-secret=]\n" - " [,image-compression=[auto_glz|auto_lz|quic|glz|lz|off]]\n" - " [,jpeg-wan-compression=[auto|never|always]]\n" - " [,zlib-glz-wan-compression=[auto|never|always]]\n" - " [,streaming-video=[off|all|filter]][,disable-copy-paste=on|off]\n" - " [,disable-agent-file-xfer=on|off][,agent-mouse=[on|off]]\n" - " [,playback-compression=[on|off]][,seamless-migration=[on|off]]\n" + " [,image-compression=auto_glz|auto_lz|quic|glz|lz|off]\n" + " [,jpeg-wan-compression=auto|never|always]\n" + " [,zlib-glz-wan-compression=auto|never|always]\n" + " [,streaming-video=off|all|filter][,disable-copy-paste=on|off]\n" + " [,disable-agent-file-xfer=on|off][,agent-mouse=on|off]\n" + " [,playback-compression=on|off][,seamless-migration=on|off]\n" " [,video-codec=\n" " [,max-refresh-rate=rate\n" - " [,gl=[on|off]][,rendernode=]\n" + " [,gl=on|off][,rendernode=]\n" " enable spice\n" " at least one of {port, tls-port} is mandatory\n", QEMU_ARCH_ALL) @@ -2406,7 +2427,7 @@ SRST ``tls-ciphers=`` Specify which ciphers to use. - ``tls-channel=[main|display|cursor|inputs|record|playback]``; \ ``plaintext-channel=[main|display|cursor|inputs|record|playback]`` + ``tls-channel=main|display|cursor|inputs|record|playback``; \ ``plaintext-channel=main|display|cursor|inputs|record|playback`` Force specific channel to be used with or without TLS encryption. The options can be specified multiple times to configure multiple channels. The special name "default" can be @@ -2414,24 +2435,24 @@ SRST explicitly forced into one mode the spice client is allowed to pick tls/plaintext as he pleases. - ``image-compression=[auto_glz|auto_lz|quic|glz|lz|off]`` + ``image-compression=auto_glz|auto_lz|quic|glz|lz|off`` Configure image compression (lossless). Default is auto\_glz. - ``jpeg-wan-compression=[auto|never|always]``; \ ``zlib-glz-wan-compression=[auto|never|always]`` + ``jpeg-wan-compression=auto|never|always``; \ ``zlib-glz-wan-compression=auto|never|always`` Configure wan image compression (lossy for slow links). Default is auto. - ``streaming-video=[off|all|filter]`` + ``streaming-video=off|all|filter`` Configure video stream detection. Default is off. - ``agent-mouse=[on|off]`` + ``agent-mouse=on|off`` Enable/disable passing mouse events via vdagent. Default is on. - ``playback-compression=[on|off]`` + ``playback-compression=on|off`` Enable/disable audio stream compression (using celt 0.5.1). Default is on. - ``seamless-migration=[on|off]`` + ``seamless-migration=on|off`` Enable/disable spice seamless migration. Default is off. ``video-codec=`` @@ -2445,7 +2466,7 @@ SRST Provide the maximum refresh rate (or FPS) at which the encoding requests should be sent to the Spice server. Default would be 30. - ``gl=[on|off]`` + ``gl=on|off`` Enable/disable OpenGL context. Default is off. ``rendernode=`` @@ -2675,7 +2696,7 @@ SRST bandwidth when playing videos. Disabling adaptive encodings restores the original static behavior of encodings like Tight. - ``share=[allow-exclusive|force-shared|ignore]`` + ``share=allow-exclusive|force-shared|ignore`` Set display sharing policy. 'allow-exclusive' allows clients to ask for exclusive access. As suggested by the rfb spec this is implemented by dropping other connections. Connecting multiple @@ -4849,9 +4870,9 @@ SRST ERST DEF("mon", HAS_ARG, QEMU_OPTION_mon, \ - "-mon [chardev=]name[,mode=readline|control][,pretty[=on|off]]\n", QEMU_ARCH_ALL) + "-mon [chardev=]name[,mode=readline|control][,pretty=on|off]\n", QEMU_ARCH_ALL) SRST -``-mon [chardev=]name[,mode=readline|control][,pretty[=on|off]]`` +``-mon [chardev=]name[,mode=readline|control][,pretty=on|off]`` Set up a monitor connected to the chardev ``name``. QEMU supports two monitors: the Human Monitor Protocol (HMP; for human interaction), and the QEMU Monitor Protocol @@ -5540,14 +5561,14 @@ ERST #endif DEF("msg", HAS_ARG, QEMU_OPTION_msg, - "-msg [timestamp[=on|off]][,guest-name=[on|off]]\n" + "-msg [timestamp=on|off][,guest-name=on|off]\n" " control error message format\n" " timestamp=on enables timestamps (default: off)\n" " guest-name=on enables guest name prefix but only if\n" " -name guest option is set (default: off)\n", QEMU_ARCH_ALL) SRST -``-msg [timestamp[=on|off]][,guest-name[=on|off]]`` +``-msg [timestamp=on|off][,guest-name=on|off]`` Control error message format. ``timestamp=on|off`` diff --git a/replay/replay-audio.c b/replay/replay-audio.c index 1b614f41379f..7d20ae9110c6 100644 --- a/replay/replay-audio.c +++ b/replay/replay-audio.c @@ -13,7 +13,6 @@ #include "qemu/error-report.h" #include "system/replay.h" #include "replay-internal.h" -#include "qemu/audio.h" void replay_audio_out(size_t *played) { @@ -35,38 +34,48 @@ void replay_audio_out(size_t *played) } } -void replay_audio_in(size_t *recorded, st_sample *samples, size_t *wpos, size_t size) +void replay_audio_in_start(size_t *nsamples) { - int pos; - uint64_t left, right; if (replay_mode == REPLAY_MODE_RECORD) { g_assert(replay_mutex_locked()); replay_save_instructions(); replay_put_event(EVENT_AUDIO_IN); - replay_put_qword(*recorded); - replay_put_qword(*wpos); - for (pos = (*wpos - *recorded + size) % size ; pos != *wpos - ; pos = (pos + 1) % size) { - audio_sample_to_uint64(samples, pos, &left, &right); - replay_put_qword(left); - replay_put_qword(right); - } + replay_put_qword(*nsamples); + replay_state.n_audio_samples = *nsamples; } else if (replay_mode == REPLAY_MODE_PLAY) { g_assert(replay_mutex_locked()); replay_account_executed_instructions(); if (replay_next_event_is(EVENT_AUDIO_IN)) { - *recorded = replay_get_qword(); - *wpos = replay_get_qword(); - for (pos = (*wpos - *recorded + size) % size ; pos != *wpos - ; pos = (pos + 1) % size) { - left = replay_get_qword(); - right = replay_get_qword(); - audio_sample_from_uint64(samples, pos, left, right); - } - replay_finish_event(); + *nsamples = replay_get_qword(); + replay_state.n_audio_samples = *nsamples; } else { error_report("Missing audio in event in the replay log"); abort(); } } } + +void replay_audio_in_sample_lr(uint64_t *left, uint64_t *right) +{ + if (replay_mode == REPLAY_MODE_RECORD) { + replay_put_qword(*left); + replay_put_qword(*right); + } else if (replay_mode == REPLAY_MODE_PLAY) { + *left = replay_get_qword(); + *right = replay_get_qword(); + } else { + return; + } + + assert(replay_state.n_audio_samples > 0); + replay_state.n_audio_samples--; +} + +void replay_audio_in_finish(void) +{ + assert(replay_state.n_audio_samples == 0); + + if (replay_mode == REPLAY_MODE_PLAY) { + replay_finish_event(); + } +} diff --git a/replay/replay-internal.h b/replay/replay-internal.h index 75249b769361..643b357da121 100644 --- a/replay/replay-internal.h +++ b/replay/replay-internal.h @@ -85,6 +85,7 @@ enum ReplayEvents { * @file_offset: offset into replay log at replay snapshot * @block_request_id: current serialised block request id * @read_event_id: current async read event id + * @n_audio_samples: expected audio samples */ typedef struct ReplayState { int64_t cached_clock[REPLAY_CLOCK_COUNT]; @@ -96,6 +97,7 @@ typedef struct ReplayState { uint64_t file_offset; uint64_t block_request_id; uint64_t read_event_id; + size_t n_audio_samples; } ReplayState; extern ReplayState replay_state; diff --git a/replay/replay.c b/replay/replay.c index b2121788c1d7..2e5c6fa82eaa 100644 --- a/replay/replay.c +++ b/replay/replay.c @@ -22,7 +22,7 @@ /* Current version of the replay mechanism. Increase it when file format changes. */ -#define REPLAY_VERSION 0xe0200c +#define REPLAY_VERSION 0xe0200d /* Size of replay log header */ #define HEADER_SIZE (sizeof(uint32_t) + sizeof(uint64_t)) diff --git a/replay/stubs-system.c b/replay/stubs-system.c index 7f85764936f1..b2c52bc40436 100644 --- a/replay/stubs-system.c +++ b/replay/stubs-system.c @@ -15,7 +15,13 @@ void replay_input_sync_event(void) void replay_add_blocker(const char *feature) { } -void replay_audio_in(size_t *recorded, st_sample *samples, size_t *wpos, size_t size) +void replay_audio_in_start(size_t *nsamples) +{ +} +void replay_audio_in_sample_lr(uint64_t *left, uint64_t *right) +{ +} +void replay_audio_in_finish(void) { } void replay_audio_out(size_t *played) diff --git a/roms/seabios-hppa b/roms/seabios-hppa index b876684ec394..1a8ada1fb706 160000 --- a/roms/seabios-hppa +++ b/roms/seabios-hppa @@ -1 +1 @@ -Subproject commit b876684ec394856a8cc2523c57b3dc784dc119d4 +Subproject commit 1a8ada1fb70643172e251aacbac673c9ecda99e9 diff --git a/rust/Cargo.toml b/rust/Cargo.toml index ace0baf9bd7f..0d24eb84e1cd 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -46,6 +46,7 @@ redundant_explicit_links = "deny" [workspace.lints.clippy] # default-warn lints result_unit_err = "allow" +manual_checked_ops = "deny" should_implement_trait = "deny" # can be for a reason, e.g. in callbacks unused_self = "allow" diff --git a/rust/hw/core/src/qdev.rs b/rust/hw/core/src/qdev.rs index 145e20a984fa..b2e5441079d3 100644 --- a/rust/hw/core/src/qdev.rs +++ b/rust/hw/core/src/qdev.rs @@ -425,18 +425,16 @@ impl Clock { } pub const fn period_from_hz(hz: u64) -> u64 { - if hz == 0 { - 0 - } else { - Self::PERIOD_1SEC / hz + match Self::PERIOD_1SEC.checked_div(hz) { + Some(value) => value, + None => 0, } } pub const fn period_to_hz(period: u64) -> u64 { - if period == 0 { - 0 - } else { - Self::PERIOD_1SEC / period + match Self::PERIOD_1SEC.checked_div(period) { + Some(value) => value, + None => 0, } } diff --git a/rust/hw/timer/hpet/src/device.rs b/rust/hw/timer/hpet/src/device.rs index ebf715d39956..0a5c131819b5 100644 --- a/rust/hw/timer/hpet/src/device.rs +++ b/rust/hw/timer/hpet/src/device.rs @@ -627,8 +627,6 @@ pub struct HPETState { flags: u32, hpet_offset_migration: BqlCell, - #[property(rename = "hpet-offset-saved", default = true)] - hpet_offset_saved: bool, irqs: [InterruptSource; HPET_NUM_IRQ_ROUTES], rtc_irq_level: BqlCell, @@ -947,11 +945,6 @@ impl HPETState { tn_regs.last = CLOCK_VIRTUAL.get_ns() - NANOSECONDS_PER_SECOND; } - // Recalculate the offset between the main counter and guest time - if !self.hpet_offset_saved { - self.hpet_offset_migration - .set(ticks_to_ns(regs.counter) - CLOCK_VIRTUAL.get_ns()); - } regs.hpet_offset = self.hpet_offset_migration.get(); Ok(()) @@ -962,7 +955,7 @@ impl HPETState { } fn is_offset_needed(&self) -> bool { - self.regs.borrow().is_hpet_enabled() && self.hpet_offset_saved + self.regs.borrow().is_hpet_enabled() } fn validate_num_timers(&self, _version_id: u8) -> bool { diff --git a/rust/util/src/log.rs b/rust/util/src/log.rs index 0a4bc4249a17..11165707901a 100644 --- a/rust/util/src/log.rs +++ b/rust/util/src/log.rs @@ -135,6 +135,12 @@ impl Drop for LogGuard { /// error_address, /// ); /// ``` +/// +/// The `log_mask_ln` macro must only be used for emitting complete +/// log messages. Where it is required to incrementally output string +/// fragments to construct a complete message, `LogGuard::new()` must +/// be directly used in combination with `writeln()` to avoid output +/// races with other QEMU threads. #[macro_export] macro_rules! log_mask_ln { ($mask:expr, $fmt:tt $($args:tt)*) => {{ diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index 3a9557417f7e..b77bd693288b 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -373,8 +373,9 @@ sub help { "under the terms of the GNU Lesser General Public", "Permission is hereby granted, free of charge", "GNU GPL, version 2 or later", - "See the COPYING file" -); + "See the COPYING file", + "terms and conditions of the GNU General Public", + ); our $LICENSE_BOILERPLATE_RE = join("|", @LICENSE_BOILERPLATE); # Load common spelling mistakes and build regular expression list. @@ -1473,9 +1474,9 @@ sub process_file_list { # If we don't see a MAINTAINERS update, prod the user to check if (int(@maybemaintainers) > 0 && !$sawmaintainers) { - WARN("added, moved or deleted file(s):\n\n " . - join("\n ", @maybemaintainers) . - "\n\nDoes MAINTAINERS need updating?\n"); + WARN("added, moved or deleted file(s)," + . " does MAINTAINERS need updating?\n " + . join("\n ", @maybemaintainers)); } } diff --git a/scripts/coccinelle/memory-region-housekeeping.cocci b/scripts/coccinelle/memory-region-housekeeping.cocci index 7f89e9712ec9..b23647a3d86e 100644 --- a/scripts/coccinelle/memory-region-housekeeping.cocci +++ b/scripts/coccinelle/memory-region-housekeeping.cocci @@ -16,32 +16,19 @@ expression E1, E2, E3, E4, E5; symbol true; @@ -( - memory_region_init_ram(E1, E2, E3, E4, E5); + memory_region_init_rom(E1, E2, E3, E4, E5); ... WHEN != E1 - memory_region_set_readonly(E1, true); -| -- memory_region_init_ram_nomigrate(E1, E2, E3, E4, E5); -+ memory_region_init_rom_nomigrate(E1, E2, E3, E4, E5); - ... WHEN != E1 -- memory_region_set_readonly(E1, true); -) @possible_memory_region_init_rom@ expression E1, E2, E3, E4, E5; position p; @@ -( memory_region_init_ram@p(E1, E2, E3, E4, E5); ... memory_region_set_readonly(E1, true); -| - memory_region_init_ram_nomigrate@p(E1, E2, E3, E4, E5); - ... - memory_region_set_readonly(E1, true); -) @script:python@ p << possible_memory_region_init_rom.p; @@ @@ -53,50 +40,16 @@ cocci.print_main("potential use of memory_region_init_rom*() in ", p) expression ROM, E1, E2, E3, E4; expression ALIAS, E5, E6, E7, E8; @@ -( memory_region_init_rom(ROM, E1, E2, E3, E4); -| - memory_region_init_rom_nomigrate(ROM, E1, E2, E3, E4); -) ... memory_region_init_alias(ALIAS, E5, E6, ROM, E7, E8); - memory_region_set_readonly(ALIAS, true); -// Replace by-hand memory_region_init_ram_nomigrate/vmstate_register_ram -// code sequences with use of the new memory_region_init_ram function. -// Similarly for the _rom and _rom_device functions. // We don't try to replace sequences with a non-NULL owner, because // there are none in the tree that can be automatically converted // (and only a handful that can be manually converted). @@ -expression MR; -expression NAME; -expression SIZE; -expression ERRP; -@@ --memory_region_init_ram_nomigrate(MR, NULL, NAME, SIZE, ERRP); -+memory_region_init_ram(MR, NULL, NAME, SIZE, ERRP); - ... --vmstate_register_ram_global(MR); -@@ -expression MR; -expression NAME; -expression SIZE; -expression ERRP; -@@ --memory_region_init_rom_nomigrate(MR, NULL, NAME, SIZE, ERRP); -+memory_region_init_rom(MR, NULL, NAME, SIZE, ERRP); - ... --vmstate_register_ram_global(MR); -@@ -expression MR; -expression OPS; -expression OPAQUE; -expression NAME; -expression SIZE; -expression ERRP; -@@ typedef DeviceState; identifier device_fn, dev, obj; expression E1, E2, E3, E4, E5; diff --git a/scripts/codeconverter/codeconverter/test_regexps.py b/scripts/codeconverter/codeconverter/test_regexps.py index 2b9f5b80111b..f92389e9618c 100644 --- a/scripts/codeconverter/codeconverter/test_regexps.py +++ b/scripts/codeconverter/codeconverter/test_regexps.py @@ -202,7 +202,6 @@ def test_struct_re(): uint64_t dexp[2]; SWVoiceOut *voice; int left, pos, samples; - QEMUAudioTimeStamp ats; FM_OPL *opl; PortioList port_list; } AdlibState; diff --git a/scripts/compare-machine-types.py b/scripts/compare-machine-types.py index b4f899082a70..83be361f4f6c 100755 --- a/scripts/compare-machine-types.py +++ b/scripts/compare-machine-types.py @@ -27,19 +27,16 @@ # along with this program; if not, see . import sys -from os import path from argparse import ArgumentParser, RawTextHelpFormatter, Namespace import pandas as pd from contextlib import ExitStack from typing import Optional, List, Dict, Generator, Tuple, Union, Any, Set try: - qemu_dir = path.abspath(path.dirname(path.dirname(__file__))) - sys.path.append(path.join(qemu_dir, 'python')) from qemu.machine import QEMUMachine except ModuleNotFoundError as exc: - print(f"Module '{exc.name}' not found.") - print("Try export PYTHONPATH=top-qemu-dir/python or run from top-qemu-dir") + print(f"Module '{exc.name}' not found.", file=sys.stderr) + print(f"Try $builddir/run {' '.join(sys.argv)}", file=sys.stderr) sys.exit(1) diff --git a/scripts/coverity-scan/COMPONENTS.md b/scripts/coverity-scan/COMPONENTS.md index 95805b536bcf..02131f1388a5 100644 --- a/scripts/coverity-scan/COMPONENTS.md +++ b/scripts/coverity-scan/COMPONENTS.md @@ -4,7 +4,7 @@ alpha ~ .*/qemu((/include)?/hw/alpha/.*|/target/alpha/.*) arm - ~ .*/qemu((/include)?/hw/arm/.*|(/include)?/hw/.*/(arm|allwinner-a10|bcm28|digic|exynos|imx|omap|stellaris|pxa2xx|versatile|zynq|cadence).*|/hw/net/xgmac.c|/hw/ssi/xilinx_spips.c|/target/arm/.*) + ~ .*/qemu((/include)?/hw/arm/.*|(/include)?/hw/.*/(arm|allwinner-a10|bcm28|digic|exynos|imx|omap|stellaris|pxa2xx|versatile|zynq|cadence).*|/hw/ssi/xilinx_spips.c|/target/arm/.*) avr ~ .*/qemu((/include)?/hw/avr/.*|/target/avr/.*) diff --git a/scripts/coverity-scan/run-coverity-scan b/scripts/coverity-scan/run-coverity-scan index 43cf770f5e34..9b89a3303f57 100755 --- a/scripts/coverity-scan/run-coverity-scan +++ b/scripts/coverity-scan/run-coverity-scan @@ -424,7 +424,7 @@ echo "Configuring..." --enable-linux-aio --enable-attr \ --enable-cap-ng --enable-trace-backends=log --enable-spice --enable-rbd \ --enable-libusb --enable-usb-redir \ - --enable-libiscsi --enable-libnfs --enable-seccomp \ + --enable-libiscsi --enable-seccomp \ --enable-tpm --enable-libssh --enable-lzo --enable-snappy --enable-bzip2 \ --enable-numa --enable-rdma --enable-smartcard --enable-virglrenderer \ --enable-mpath --enable-glusterfs \ diff --git a/scripts/device-crash-test b/scripts/device-crash-test index f97d9909c05c..fc86babdf43e 100755 --- a/scripts/device-crash-test +++ b/scripts/device-crash-test @@ -40,9 +40,8 @@ try: except ModuleNotFoundError as exc: path = Path(__file__).resolve() print(f"Module '{exc.name}' not found.") - print(" Try 'make check-venv' from your build directory,") - print(" and then one way to run this script is like so:") - print(f' > $builddir/pyvenv/bin/python3 "{path}"') + print(" Try running this script like so:") + print(f' > $builddir/run "{path}"') sys.exit(1) logger = logging.getLogger('device-crash-test') diff --git a/scripts/feature_to_c.py b/scripts/feature_to_c.py index 807af0e685c3..3aa62fb895f9 100644 --- a/scripts/feature_to_c.py +++ b/scripts/feature_to_c.py @@ -90,13 +90,16 @@ def writeliteral(indent, bytes): writeliteral(8, read) sys.stdout.write(',\n') writeliteral(8, bytes(feature_name, 'utf-8')) - sys.stdout.write(',\n (const char * const []) {\n') + sys.stdout.write(',\n') + sys.stdout.write(f' (const char * const [{num_regs}]) {{\n') for index, regname in enumerate(regnames): sys.stdout.write(f' [{regnums[index] - base_reg}] =\n') writeliteral(16, bytes(regname, 'utf-8')) sys.stdout.write(',\n') - sys.stdout.write(f' }},\n {num_regs},\n }},\n') + sys.stdout.write( ' },\n') + sys.stdout.write(f' {num_regs},\n') + sys.stdout.write( ' },\n') sys.stdout.write(' { NULL }\n};\n') diff --git a/scripts/lsan_suppressions.txt b/scripts/lsan_suppressions.txt new file mode 100644 index 000000000000..f88bbab18b81 --- /dev/null +++ b/scripts/lsan_suppressions.txt @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +# This is a set of suppressions for LeakSanitizer; you can use it by setting +# LSAN_OPTIONS="suppressions=/path/to/scripts/lsan_suppressions.txt" +# when running a QEMU built with the leak-sanitizer. + +# The tcmalloc on Fedora37 confuses things +leak:/lib64/libtcmalloc_minimal.so.4 + +# libxkbcommon also leaks in qemu-keymap +leak:/lib64/libxkbcommon.so.0 + +# g_set_user_dirs() deliberately leaks the previous cached g_get_user_* +# values. This is documented in upstream glib's valgrind-format +# suppression file: +# https://github.com/GNOME/glib/blob/main/tools/glib.supp +# 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 diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh index e8edc5252a31..ca5b113119a2 100644 --- a/scripts/meson-buildoptions.sh +++ b/scripts/meson-buildoptions.sh @@ -158,6 +158,7 @@ meson_options_help() { printf "%s\n" ' multiprocess Out of process device emulation support' printf "%s\n" ' netmap netmap network backend support' printf "%s\n" ' nettle nettle cryptography support' + printf "%s\n" ' nitro Nitro acceleration support' printf "%s\n" ' numa libnuma support' printf "%s\n" ' nvmm NVMM acceleration support' printf "%s\n" ' opengl OpenGL support' @@ -418,6 +419,8 @@ _meson_option_parse() { --disable-netmap) printf "%s" -Dnetmap=disabled ;; --enable-nettle) printf "%s" -Dnettle=enabled ;; --disable-nettle) printf "%s" -Dnettle=disabled ;; + --enable-nitro) printf "%s" -Dnitro=enabled ;; + --disable-nitro) printf "%s" -Dnitro=disabled ;; --enable-numa) printf "%s" -Dnuma=enabled ;; --disable-numa) printf "%s" -Dnuma=disabled ;; --enable-nvmm) printf "%s" -Dnvmm=enabled ;; diff --git a/scripts/modinfo-generate.py b/scripts/modinfo-generate.py index aaf23544c462..e231c129196c 100644 --- a/scripts/modinfo-generate.py +++ b/scripts/modinfo-generate.py @@ -1,15 +1,18 @@ #!/usr/bin/env python3 +import argparse import os import sys +from typing import Optional -def print_array(name, values): + +def print_array(name: str, values: list[str]) -> None: if len(values) == 0: return list = ", ".join(values) - print(" .%s = ((const char*[]){ %s, NULL })," % (name, list)) + print(f" .{name} = ((const char*[]){{ {list}, NULL }}),") -def parse_line(line): +def parse_line(line: str) -> tuple[str, str]: kind = "" data = "" get_kind = False @@ -31,13 +34,14 @@ def parse_line(line): continue return (kind, data) -def generate(name, lines, enabled): +def parse_modinfo(name: str, lines: list[str], enabled: set[str]) -> Optional[dict]: + """Parse a modinfo file and return module metadata, or None if disabled.""" arch = "" objs = [] deps = [] opts = [] for line in lines: - if line.find("MODINFO_START") != -1: + if "MODINFO_START" in line: (kind, data) = parse_line(line) if kind == 'obj': objs.append(data) @@ -46,74 +50,124 @@ def generate(name, lines, enabled): elif kind == 'opts': opts.append(data) elif kind == 'arch': - arch = data; + arch = data elif kind == 'kconfig': # don't add a module which dependency is not enabled # in kconfig if data.strip() not in enabled: - print(" /* module {} isn't enabled in Kconfig. */" - .format(data.strip())) - print("/* },{ */") return None else: print("unknown:", kind) exit(1) - print(" .name = \"%s\"," % name) - if arch != "": - print(" .arch = %s," % arch) - print_array("objs", objs) - print_array("deps", deps) - print_array("opts", opts) + return { + 'name': name, + 'arch': arch, + 'objs': objs, + 'deps': deps, + 'opts': opts, + 'dep_names': {dep.strip('" ') for dep in deps} + } + +def generate(modinfo: str, mod: Optional[dict], + skip_reason: Optional[str]) -> None: + """Generate C code for a module.""" + print(f" /* {modinfo} */") + if mod is None: + if skip_reason == "missing_deps": + print(" /* module has missing dependencies. */") + else: + print(" /* module isn't enabled in Kconfig. */") + print("/* },{ */") + return + + print(f' .name = "{mod["name"]}",') + if mod['arch'] != "": + print(f" .arch = {mod['arch']},") + print_array("objs", mod['objs']) + print_array("deps", mod['deps']) + print_array("opts", mod['opts']) print("},{") - return {dep.strip('" ') for dep in deps} -def print_pre(): +def print_pre() -> None: print("/* generated by scripts/modinfo-generate.py */") print("#include \"qemu/osdep.h\"") print("#include \"qemu/module.h\"") print("const QemuModinfo qemu_modinfo[] = {{") -def print_post(): +def print_post() -> None: print(" /* end of list */") print("}};") -def main(args): - if len(args) < 3 or args[0] != '--devices': - print('Expected: modinfo-generate.py --devices ' - 'config-device.mak [modinfo files]', file=sys.stderr) - exit(1) +def main() -> None: + parser = argparse.ArgumentParser( + description='Generate C code for QEMU module info' + ) + parser.add_argument('--devices', + help='path to config-device.mak') + parser.add_argument('--skip-missing-deps', action='store_true', + help='warn if a dependency is missing and continue') + parser.add_argument('modinfo', nargs='+', + help='modinfo files to process') + args = parser.parse_args() # get all devices enabled in kconfig, from *-config-device.mak enabled = set() - with open(args[1]) as file: - for line in file.readlines(): - config = line.split('=') - if config[1].rstrip() == 'y': - enabled.add(config[0][7:]) # remove CONFIG_ - - deps = set() - modules = set() - print_pre() - for modinfo in args[2:]: + if args.devices: + with open(args.devices) as file: + for line in file.readlines(): + config = line.split('=') + if config[1].rstrip() == 'y': + enabled.add(config[0][7:]) # remove CONFIG_ + + # all_modules: modinfo path -> (basename, parsed module or None, skip_reason) + all_modules = {} + for modinfo in args.modinfo: with open(modinfo) as f: lines = f.readlines() - print(" /* %s */" % modinfo) (basename, _) = os.path.splitext(modinfo) - moddeps = generate(basename, lines, enabled) - if moddeps is not None: - modules.add(basename) - deps.update(moddeps) - print_post() + mod = parse_modinfo(basename, lines, enabled) + skip_reason = "kconfig" if mod is None else None + all_modules[modinfo] = (basename, mod, skip_reason) + + # Collect all available module names + available = {basename for basename, mod, _ in all_modules.values() + if mod is not None} - error = False - for dep in deps.difference(modules): - print("Dependency {} cannot be satisfied".format(dep), - file=sys.stderr) - error = True + # Collect all dependencies + all_deps = set() + for basename, mod, _ in all_modules.values(): + if mod is not None: + all_deps.update(mod['dep_names']) - if error: + # Check for missing dependencies + missing = all_deps.difference(available) + for dep in missing: + print(f"Dependency {dep} cannot be satisfied", file=sys.stderr) + + if missing and not args.skip_missing_deps: exit(1) + # When skipping missing deps, iteratively remove modules with + # unsatisfiable dependencies + if args.skip_missing_deps and missing: + changed = True + while changed: + changed = False + for modinfo, (basename, mod, skip_reason) in list(all_modules.items()): + if mod is None: + continue + if not mod['dep_names'].issubset(available): + available.discard(basename) + all_modules[modinfo] = (basename, None, "missing_deps") + changed = True + + # generate output + print_pre() + for modinfo in args.modinfo: + (basename, mod, skip_reason) = all_modules[modinfo] + generate(modinfo, mod, skip_reason) + print_post() + if __name__ == "__main__": - main(sys.argv[1:]) + main() diff --git a/scripts/oss-fuzz/lsan_suppressions.txt b/scripts/oss-fuzz/lsan_suppressions.txt deleted file mode 100644 index 7d90c280d016..000000000000 --- a/scripts/oss-fuzz/lsan_suppressions.txt +++ /dev/null @@ -1,5 +0,0 @@ -# The tcmalloc on Fedora37 confuses things -leak:/lib64/libtcmalloc_minimal.so.4 - -# libxkbcommon also leaks in qemu-keymap -leak:/lib64/libxkbcommon.so.0 diff --git a/scripts/qemu-plugin-symbols.py b/scripts/qemu-plugin-symbols.py index 69644979c19b..ce99796ce2a3 100644 --- a/scripts/qemu-plugin-symbols.py +++ b/scripts/qemu-plugin-symbols.py @@ -20,9 +20,14 @@ def extract_symbols(plugin_header): # Remove QEMU_PLUGIN_API macro definition. content = content.replace('#define QEMU_PLUGIN_API', '') expected = content.count('QEMU_PLUGIN_API') - # Find last word between QEMU_PLUGIN_API and (, matching on several lines. + # Find last word between QEMU_PLUGIN_API and ( to get the function name, + # matching on several lines. Discard attributes, if any. # We use *? non-greedy quantifier. - syms = re.findall(r'QEMU_PLUGIN_API.*?(\w+)\s*\(', content, re.DOTALL) + syms = re.findall( + r'QEMU_PLUGIN_API\s+(?:__attribute__\(\(\S+\)\))?.*?(\w+)\s*\(', + content, + re.DOTALL, + ) syms.sort() # Ensure we found as many symbols as API markers. assert len(syms) == expected diff --git a/scripts/qmp/qemu-ga-client b/scripts/qmp/qemu-ga-client index 56edd0234a62..7ea01b9a11d7 100755 --- a/scripts/qmp/qemu-ga-client +++ b/scripts/qmp/qemu-ga-client @@ -1,11 +1,10 @@ #!/usr/bin/env python3 -import os import sys -sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) -from qemu.utils import qemu_ga_client - - -if __name__ == '__main__': - sys.exit(qemu_ga_client.main()) +print( + "This script has moved; after running configure," + " please use '$builddir/run qemu-ga-client [...]' instead.", + file=sys.stderr +) +sys.exit(1) diff --git a/scripts/qmp/qmp-shell b/scripts/qmp/qmp-shell index 4a20f97db708..436c4940c263 100755 --- a/scripts/qmp/qmp-shell +++ b/scripts/qmp/qmp-shell @@ -1,11 +1,10 @@ #!/usr/bin/env python3 -import os import sys -sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) -from qemu.qmp import qmp_shell - - -if __name__ == '__main__': - qmp_shell.main() +print( + "This script has moved; after running configure," + " please use '$builddir/run qmp-shell [...]' instead.", + file=sys.stderr +) +sys.exit(1) diff --git a/scripts/qmp/qmp-shell-wrap b/scripts/qmp/qmp-shell-wrap index 9e94da114f5f..f63dadad1eda 100755 --- a/scripts/qmp/qmp-shell-wrap +++ b/scripts/qmp/qmp-shell-wrap @@ -1,11 +1,10 @@ #!/usr/bin/env python3 -import os import sys -sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) -from qemu.qmp import qmp_shell - - -if __name__ == '__main__': - qmp_shell.main_wrap() +print( + "This script has moved; after running configure," + " please use '$builddir/run qmp-shell-wrap [...]' instead.", + file=sys.stderr +) +sys.exit(1) diff --git a/scripts/qmp/qom-fuse b/scripts/qmp/qom-fuse index d453807b273f..7d4724a37100 100755 --- a/scripts/qmp/qom-fuse +++ b/scripts/qmp/qom-fuse @@ -1,11 +1,10 @@ #!/usr/bin/env python3 -import os import sys -sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) -from qemu.utils.qom_fuse import QOMFuse - - -if __name__ == '__main__': - sys.exit(QOMFuse.entry_point()) +print( + "This script has moved; after running configure," + " please use '$builddir/run qom-fuse [...]' instead.", + file=sys.stderr +) +sys.exit(1) diff --git a/scripts/qmp/qom-get b/scripts/qmp/qom-get index 04ebe052e82a..963635396248 100755 --- a/scripts/qmp/qom-get +++ b/scripts/qmp/qom-get @@ -1,11 +1,10 @@ #!/usr/bin/env python3 -import os import sys -sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) -from qemu.utils.qom import QOMGet - - -if __name__ == '__main__': - sys.exit(QOMGet.entry_point()) +print( + "This script has moved; after running configure," + " please use '$builddir/run qom-get [...]' instead.", + file=sys.stderr +) +sys.exit(1) diff --git a/scripts/qmp/qom-list b/scripts/qmp/qom-list index 853b85a8d3fc..e988274d1e75 100755 --- a/scripts/qmp/qom-list +++ b/scripts/qmp/qom-list @@ -1,11 +1,10 @@ #!/usr/bin/env python3 -import os import sys -sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) -from qemu.utils.qom import QOMList - - -if __name__ == '__main__': - sys.exit(QOMList.entry_point()) +print( + "This script has moved; after running configure," + " please use '$builddir/run qom-list [...]' instead.", + file=sys.stderr +) +sys.exit(1) diff --git a/scripts/qmp/qom-set b/scripts/qmp/qom-set index 06820feec424..c2569afdcd7b 100755 --- a/scripts/qmp/qom-set +++ b/scripts/qmp/qom-set @@ -1,11 +1,10 @@ #!/usr/bin/env python3 -import os import sys -sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) -from qemu.utils.qom import QOMSet - - -if __name__ == '__main__': - sys.exit(QOMSet.entry_point()) +print( + "This script has moved; after running configure," + " please use '$builddir/run qom-set [...]' instead.", + file=sys.stderr +) +sys.exit(1) diff --git a/scripts/qmp/qom-tree b/scripts/qmp/qom-tree index 760e172277e9..b3cd5ab6f82a 100755 --- a/scripts/qmp/qom-tree +++ b/scripts/qmp/qom-tree @@ -1,11 +1,10 @@ #!/usr/bin/env python3 -import os import sys -sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) -from qemu.utils.qom import QOMTree - - -if __name__ == '__main__': - sys.exit(QOMTree.entry_point()) +print( + "This script has moved; after running configure," + " please use '$builddir/run qom-tree [...]' instead.", + file=sys.stderr +) +sys.exit(1) diff --git a/scripts/qmp_helper.py b/scripts/qmp_helper.py index c1e7e0fd80ce..521612fb9461 100755 --- a/scripts/qmp_helper.py +++ b/scripts/qmp_helper.py @@ -13,17 +13,12 @@ import sys from datetime import datetime -from os import path as os_path try: - qemu_dir = os_path.abspath(os_path.dirname(os_path.dirname(__file__))) - sys.path.append(os_path.join(qemu_dir, 'python')) - from qemu.qmp.legacy import QEMUMonitorProtocol - except ModuleNotFoundError as exc: - print(f"Module '{exc.name}' not found.") - print("Try export PYTHONPATH=top-qemu-dir/python or run from top-qemu-dir") + print(f"Module '{exc.name}' not found.", file=sys.stderr) + print(f"Try $builddir/run {' '.join(sys.argv)}", file=sys.stderr) sys.exit(1) from base64 import b64encode diff --git a/scripts/render_block_graph.py b/scripts/render_block_graph.py index 3e1a2e3fa717..b9079bbed52e 100755 --- a/scripts/render_block_graph.py +++ b/scripts/render_block_graph.py @@ -24,9 +24,13 @@ import json from graphviz import Digraph -sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'python')) -from qemu.qmp import QMPError -from qemu.qmp.legacy import QEMUMonitorProtocol +try: + from qemu.qmp import QMPError + from qemu.qmp.legacy import QEMUMonitorProtocol +except ModuleNotFoundError as exc: + print(f"Module '{exc.name}' not found.", file=sys.stderr) + print(f"Try $builddir/run {' '.join(sys.argv)}", file=sys.stderr) + sys.exit(1) def perm(arr): diff --git a/scripts/replay-dump.py b/scripts/replay-dump.py index 097636570dd8..081aaa36c5ec 100755 --- a/scripts/replay-dump.py +++ b/scripts/replay-dump.py @@ -395,6 +395,9 @@ def decode_end(eid, name, dumpfile): Decoder(39, "EVENT_END", decode_end), ] +# EVENT_AUDIO_IN has changed +v13_event_table = v12_event_table + def parse_arguments(): "Grab arguments for script" parser = argparse.ArgumentParser() @@ -413,7 +416,10 @@ def decode_file(filename): # see REPLAY_VERSION print("HEADER: version 0x%x" % (version)) - if version == 0xe0200c: + if version == 0xe0200d: + event_decode_table = v13_event_table + replay_state.checkpoint_start = 30 + elif version == 0xe0200c: event_decode_table = v12_event_table replay_state.checkpoint_start = 30 elif version == 0xe02007: diff --git a/scripts/simplebench/bench_block_job.py b/scripts/simplebench/bench_block_job.py index e575a3af10e3..541a47e586d6 100755 --- a/scripts/simplebench/bench_block_job.py +++ b/scripts/simplebench/bench_block_job.py @@ -25,9 +25,13 @@ import socket import json -sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) -from qemu.machine import QEMUMachine -from qemu.qmp import ConnectError +try: + from qemu.machine import QEMUMachine + from qemu.qmp import ConnectError +except ModuleNotFoundError as exc: + print(f"Module '{exc.name}' not found.", file=sys.stderr) + print(f"Try $builddir/run {' '.join(sys.argv)}", file=sys.stderr) + sys.exit(1) def bench_block_job(cmd, cmd_args, qemu_args): diff --git a/scripts/update-linux-headers.sh b/scripts/update-linux-headers.sh index d09d8cf4c6f0..386d7a38e7af 100755 --- a/scripts/update-linux-headers.sh +++ b/scripts/update-linux-headers.sh @@ -254,6 +254,7 @@ for i in "$hdrdir"/include/linux/*virtio*.h \ "$hdrdir/include/linux/kvm_para.h" \ "$hdrdir/include/linux/vhost_types.h" \ "$hdrdir/include/linux/vmclock-abi.h" \ + "$hdrdir/include/linux/nitro_enclaves.h" \ "$hdrdir/include/linux/sysinfo.h"; do cp_portable "$i" "$output/include/standard-headers/linux" done diff --git a/semihosting/meson.build b/semihosting/meson.build index 99f10e2e2bbb..15e3be269890 100644 --- a/semihosting/meson.build +++ b/semihosting/meson.build @@ -1,4 +1,4 @@ -common_ss.add(when: 'CONFIG_SEMIHOSTING', if_false: files('stubs-all.c')) +stub_ss.add(files('stubs-all.c')) user_ss.add(when: 'CONFIG_SEMIHOSTING', if_true: files( 'user.c', 'guestfd.c')) @@ -8,12 +8,13 @@ system_ss.add(when: 'CONFIG_SEMIHOSTING', if_true: files( 'guestfd.c', 'uaccess.c', 'syscalls.c', -), if_false: files( +)) +stub_ss.add(files( 'stubs-system.c', )) system_ss.add(when: 'CONFIG_ARM_COMPATIBLE_SEMIHOSTING', - if_true: files('arm-compat-semi.c'), - if_false: files('arm-compat-semi-stub.c')) + if_true: files('arm-compat-semi.c')) +stub_ss.add(files('arm-compat-semi-stub.c')) specific_ss.add(when: ['CONFIG_SEMIHOSTING', 'CONFIG_USER_ONLY'], if_true: files('syscalls.c')) diff --git a/stubs/error-printf.c b/stubs/error-printf.c deleted file mode 100644 index 0e326d801059..000000000000 --- a/stubs/error-printf.c +++ /dev/null @@ -1,23 +0,0 @@ -#include "qemu/osdep.h" -#include "qemu/error-report.h" -#include "monitor/monitor.h" - -int error_vprintf(const char *fmt, va_list ap) -{ - int ret; - - if (g_test_initialized() && !g_test_subprocess() && - getenv("QTEST_SILENT_ERRORS")) { - char *msg = g_strdup_vprintf(fmt, ap); - g_test_message("%s", msg); - ret = strlen(msg); - g_free(msg); - return ret; - } - return vfprintf(stderr, fmt, ap); -} - -int error_vprintf_unless_qmp(const char *fmt, va_list ap) -{ - return error_vprintf(fmt, ap); -} diff --git a/stubs/igvm.c b/stubs/igvm.c index 47d5130d9d74..9e9f683fc960 100644 --- a/stubs/igvm.c +++ b/stubs/igvm.c @@ -12,7 +12,6 @@ #include "qemu/osdep.h" #include "system/igvm.h" -#include "system/igvm-internal.h" int qigvm_x86_get_mem_map_entry(int index, ConfidentialGuestMemoryMapEntry *entry, diff --git a/stubs/kvm.c b/stubs/kvm.c new file mode 100644 index 000000000000..2db61d89a730 --- /dev/null +++ b/stubs/kvm.c @@ -0,0 +1,22 @@ +/* + * kvm target arch specific stubs + * + * Copyright (c) 2026 Red Hat, Inc. + * + * Author: + * Ani Sinha + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "qemu/osdep.h" +#include "system/kvm.h" + +int kvm_arch_on_vmfd_change(MachineState *ms, KVMState *s) +{ + abort(); +} + +bool kvm_arch_supports_vmfd_change(void) +{ + return false; +} diff --git a/stubs/meson.build b/stubs/meson.build index 8a07059500db..fad796a49da0 100644 --- a/stubs/meson.build +++ b/stubs/meson.build @@ -3,7 +3,6 @@ # below, so that it is clear who needs the stubbed functionality. stub_ss.add(files('cpu-get-clock.c')) -stub_ss.add(files('error-printf.c')) stub_ss.add(files('fdset.c')) stub_ss.add(files('iothread-lock.c')) stub_ss.add(files('is-daemonized.c')) @@ -74,6 +73,7 @@ if have_system if igvm.found() stub_ss.add(files('igvm.c')) endif + stub_ss.add(files('kvm.c')) stub_ss.add(files('target-get-monitor-def.c')) stub_ss.add(files('target-monitor-defs.c')) stub_ss.add(files('win32-kbd-hook.c')) diff --git a/stubs/monitor-core.c b/stubs/monitor-core.c index 1894cdfe1f89..078a5012e9bb 100644 --- a/stubs/monitor-core.c +++ b/stubs/monitor-core.c @@ -7,6 +7,11 @@ Monitor *monitor_cur(void) return NULL; } +bool monitor_cur_is_qmp(void) +{ + return false; +} + Monitor *monitor_set_cur(Coroutine *co, Monitor *mon) { return NULL; @@ -18,5 +23,17 @@ void qapi_event_emit(QAPIEvent event, QDict *qdict) int monitor_vprintf(Monitor *mon, const char *fmt, va_list ap) { - abort(); + /* + * Pretend 'g_test_message' is our monitor console to + * stop the caller sending messages to stderr + */ + if (g_test_initialized() && !g_test_subprocess() && + getenv("QTEST_SILENT_ERRORS")) { + char *msg = g_strdup_vprintf(fmt, ap); + g_test_message("%s", msg); + size_t ret = strlen(msg); + g_free(msg); + return ret; + } + return -1; } diff --git a/stubs/physmem.c b/stubs/physmem.c index 14667f2bd8f9..2eca06ab6d6e 100644 --- a/stubs/physmem.c +++ b/stubs/physmem.c @@ -7,7 +7,7 @@ RAMBlock *qemu_ram_block_from_host(void *ptr, bool round_offset, return NULL; } -int qemu_ram_get_fd(RAMBlock *rb) +int qemu_ram_get_fd(const RAMBlock *rb) { return -1; } diff --git a/stubs/ram-block.c b/stubs/ram-block.c index 8790a59593e5..1c79e447ff00 100644 --- a/stubs/ram-block.c +++ b/stubs/ram-block.c @@ -3,17 +3,17 @@ #include "system/ramblock.h" #include "system/memory.h" -void *qemu_ram_get_host_addr(RAMBlock *rb) +void *qemu_ram_get_host_addr(const RAMBlock *rb) { return 0; } -ram_addr_t qemu_ram_get_offset(RAMBlock *rb) +ram_addr_t qemu_ram_get_offset(const RAMBlock *rb) { return 0; } -ram_addr_t qemu_ram_get_used_length(RAMBlock *rb) +ram_addr_t qemu_ram_get_used_length(const RAMBlock *rb) { return 0; } diff --git a/system/arch_init.c b/system/arch_init.c index e85736884c97..604d5909ed0f 100644 --- a/system/arch_init.c +++ b/system/arch_init.c @@ -23,8 +23,10 @@ */ #include "qemu/osdep.h" #include "system/arch_init.h" +#include "qemu/bitops.h" +#include "qemu/target-info-qapi.h" -bool qemu_arch_available(unsigned qemu_arch_mask) +bool qemu_arch_available(uint32_t arch_bitmask) { - return qemu_arch_mask & QEMU_ARCH; + return extract32(arch_bitmask, target_arch(), 1); } diff --git a/system/arch_init.h b/system/arch_init.h new file mode 100644 index 000000000000..0c2b1f3a5d07 --- /dev/null +++ b/system/arch_init.h @@ -0,0 +1,49 @@ +#ifndef QEMU_ARCH_INIT_H +#define QEMU_ARCH_INIT_H + +#include "qapi/qapi-types-machine.h" + +enum { + QEMU_ARCH_ALPHA = (1UL << SYS_EMU_TARGET_ALPHA), + QEMU_ARCH_ARM = (1UL << SYS_EMU_TARGET_ARM) | + (1UL << SYS_EMU_TARGET_AARCH64), + QEMU_ARCH_I386 = (1UL << SYS_EMU_TARGET_I386) | + (1UL << SYS_EMU_TARGET_X86_64), + QEMU_ARCH_M68K = (1UL << SYS_EMU_TARGET_M68K), + QEMU_ARCH_MICROBLAZE = (1UL << SYS_EMU_TARGET_MICROBLAZE), + QEMU_ARCH_MIPS = (1UL << SYS_EMU_TARGET_MIPS) | + (1UL << SYS_EMU_TARGET_MIPSEL) | + (1UL << SYS_EMU_TARGET_MIPS64) | + (1UL << SYS_EMU_TARGET_MIPS64EL), + QEMU_ARCH_PPC = (1UL << SYS_EMU_TARGET_PPC) | + (1UL << SYS_EMU_TARGET_PPC64), + QEMU_ARCH_S390X = (1UL << SYS_EMU_TARGET_S390X), + QEMU_ARCH_SH4 = (1UL << SYS_EMU_TARGET_SH4) | + (1UL << SYS_EMU_TARGET_SH4EB), + QEMU_ARCH_SPARC = (1UL << SYS_EMU_TARGET_SPARC) | + (1UL << SYS_EMU_TARGET_SPARC64), + QEMU_ARCH_XTENSA = (1UL << SYS_EMU_TARGET_XTENSA) | + (1UL << SYS_EMU_TARGET_XTENSAEB), + QEMU_ARCH_OR1K = (1UL << SYS_EMU_TARGET_OR1K), + QEMU_ARCH_TRICORE = (1UL << SYS_EMU_TARGET_TRICORE), + QEMU_ARCH_HPPA = (1UL << SYS_EMU_TARGET_HPPA), + QEMU_ARCH_RISCV = (1UL << SYS_EMU_TARGET_RISCV32) | + (1UL << SYS_EMU_TARGET_RISCV64), + QEMU_ARCH_RX = (1UL << SYS_EMU_TARGET_RX), + QEMU_ARCH_AVR = (1UL << SYS_EMU_TARGET_AVR), + QEMU_ARCH_HEXAGON = (1UL << SYS_EMU_TARGET_HEXAGON), + QEMU_ARCH_LOONGARCH = (1UL << SYS_EMU_TARGET_LOONGARCH64), + QEMU_ARCH_ALL = UINT32_MAX, +}; + +QEMU_BUILD_BUG_ON(SYS_EMU_TARGET__MAX > 32); + +/** + * qemu_arch_available: + * @arch_bitmask: bitmask of QEMU_ARCH_* constants + * + * Return whether the current target architecture is contained in @arch_bitmask + */ +bool qemu_arch_available(uint32_t arch_bitmask); + +#endif diff --git a/system/globals-target.c b/system/globals-target.c deleted file mode 100644 index 989720591e72..000000000000 --- a/system/globals-target.c +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Global variables that should not exist (target specific) - * - * Copyright (c) 2003-2008 Fabrice Bellard - * - * SPDX-License-Identifier: MIT - */ - -#include "qemu/osdep.h" -#include "system/system.h" - -#ifdef TARGET_SPARC -int graphic_width = 1024; -int graphic_height = 768; -int graphic_depth = 8; -#elif defined(TARGET_M68K) -int graphic_width = 800; -int graphic_height = 600; -int graphic_depth = 8; -#else -int graphic_width = 800; -int graphic_height = 600; -int graphic_depth = 32; -#endif diff --git a/system/globals.c b/system/globals.c index c33f6ed39022..34fd3ce9c72d 100644 --- a/system/globals.c +++ b/system/globals.c @@ -49,6 +49,9 @@ bool enable_cpu_pm; int autostart = 1; int vga_interface_type = VGA_NONE; bool vga_interface_created; +int graphic_width; +int graphic_height; +int graphic_depth; Chardev *parallel_hds[MAX_PARALLEL_PORTS]; QEMUOptionRom option_rom[MAX_OPTION_ROMS]; int nb_option_roms; diff --git a/system/memory.c b/system/memory.c index c51d0798a843..17a7bcd9af7c 100644 --- a/system/memory.c +++ b/system/memory.c @@ -1568,49 +1568,39 @@ static void memory_region_set_ops(MemoryRegion *mr, mr->terminates = true; } -void memory_region_init_io(MemoryRegion *mr, - Object *owner, - const MemoryRegionOps *ops, - void *opaque, - const char *name, - uint64_t size) +void memory_region_init_io(MemoryRegion *mr, Object *owner, + const MemoryRegionOps *ops, void *opaque, + const char *name, uint64_t size) { memory_region_init(mr, owner, name, size); memory_region_set_ops(mr, ops, opaque); } -bool memory_region_init_ram_nomigrate(MemoryRegion *mr, - Object *owner, - const char *name, - uint64_t size, - Error **errp) -{ - return memory_region_init_ram_flags_nomigrate(mr, owner, name, - size, 0, errp); -} - -bool memory_region_init_ram_flags_nomigrate(MemoryRegion *mr, - Object *owner, - const char *name, - uint64_t size, - uint32_t ram_flags, - Error **errp) +static bool memory_region_set_ram_block(MemoryRegion *mr, RAMBlock *rb) { - Error *err = NULL; - memory_region_init(mr, owner, name, size); mr->ram = true; mr->terminates = true; mr->destructor = memory_region_destructor_ram; - mr->ram_block = qemu_ram_alloc(size, ram_flags, mr, &err); - if (err) { + mr->ram_block = rb; + if (!rb) { mr->size = int128_zero(); object_unparent(OBJECT(mr)); - error_propagate(errp, err); return false; } return true; } +bool memory_region_init_ram_flags_nomigrate(MemoryRegion *mr, Object *owner, + const char *name, uint64_t size, + uint32_t ram_flags, Error **errp) +{ + RAMBlock *rb; + + memory_region_init(mr, owner, name, size); + rb = qemu_ram_alloc(size, ram_flags, mr, errp); + return memory_region_set_ram_block(mr, rb); +} + bool memory_region_init_resizeable_ram(MemoryRegion *mr, Object *owner, const char *name, @@ -1621,138 +1611,79 @@ bool memory_region_init_resizeable_ram(MemoryRegion *mr, void *host), Error **errp) { - Error *err = NULL; + RAMBlock *rb; + memory_region_init(mr, owner, name, size); - mr->ram = true; - mr->terminates = true; - mr->destructor = memory_region_destructor_ram; - mr->ram_block = qemu_ram_alloc_resizeable(size, max_size, resized, - mr, &err); - if (err) { - mr->size = int128_zero(); - object_unparent(OBJECT(mr)); - error_propagate(errp, err); - return false; - } - return true; + rb = qemu_ram_alloc_resizeable(size, max_size, resized, mr, errp); + return memory_region_set_ram_block(mr, rb); } #if defined(CONFIG_POSIX) && !defined(EMSCRIPTEN) -bool memory_region_init_ram_from_file(MemoryRegion *mr, - Object *owner, - const char *name, - uint64_t size, - uint64_t align, - uint32_t ram_flags, - const char *path, - ram_addr_t offset, +bool memory_region_init_ram_from_file(MemoryRegion *mr, Object *owner, + const char *name, uint64_t size, + uint64_t align, uint32_t ram_flags, + const char *path, ram_addr_t offset, Error **errp) { - Error *err = NULL; + RAMBlock *rb; + memory_region_init(mr, owner, name, size); - mr->ram = true; mr->readonly = !!(ram_flags & RAM_READONLY); - mr->terminates = true; - mr->destructor = memory_region_destructor_ram; mr->align = align; - mr->ram_block = qemu_ram_alloc_from_file(size, mr, ram_flags, path, - offset, &err); - if (err) { - mr->size = int128_zero(); - object_unparent(OBJECT(mr)); - error_propagate(errp, err); - return false; - } - return true; + rb = qemu_ram_alloc_from_file(size, mr, ram_flags, path, offset, errp); + return memory_region_set_ram_block(mr, rb); } -bool memory_region_init_ram_from_fd(MemoryRegion *mr, - Object *owner, - const char *name, - uint64_t size, - uint32_t ram_flags, - int fd, - ram_addr_t offset, - Error **errp) +bool memory_region_init_ram_from_fd(MemoryRegion *mr, Object *owner, + const char *name, uint64_t size, + uint32_t ram_flags, int fd, + ram_addr_t offset, Error **errp) { - Error *err = NULL; + RAMBlock *rb; + memory_region_init(mr, owner, name, size); - mr->ram = true; mr->readonly = !!(ram_flags & RAM_READONLY); - mr->terminates = true; - mr->destructor = memory_region_destructor_ram; - mr->ram_block = qemu_ram_alloc_from_fd(size, size, NULL, mr, ram_flags, fd, - offset, false, &err); - if (err) { - mr->size = int128_zero(); - object_unparent(OBJECT(mr)); - error_propagate(errp, err); - return false; - } - return true; + rb = qemu_ram_alloc_from_fd(size, size, NULL, mr, ram_flags, fd, offset, + false, errp); + return memory_region_set_ram_block(mr, rb); } #endif -void memory_region_init_ram_ptr(MemoryRegion *mr, - Object *owner, - const char *name, - uint64_t size, - void *ptr) +static void memory_region_set_ram_ptr(MemoryRegion *mr, uint64_t size, + void *ptr) { - memory_region_init(mr, owner, name, size); - mr->ram = true; - mr->terminates = true; - mr->destructor = memory_region_destructor_ram; - /* qemu_ram_alloc_from_ptr cannot fail with ptr != NULL. */ assert(ptr != NULL); - mr->ram_block = qemu_ram_alloc_from_ptr(size, ptr, mr, &error_abort); + RAMBlock *rb = qemu_ram_alloc_from_ptr(size, ptr, mr, &error_abort); + memory_region_set_ram_block(mr, rb); } -void memory_region_init_ram_device_ptr(MemoryRegion *mr, - Object *owner, - const char *name, - uint64_t size, - void *ptr) +void memory_region_init_ram_ptr(MemoryRegion *mr, Object *owner, + const char *name, uint64_t size, + void *ptr) { memory_region_init(mr, owner, name, size); - mr->ram = true; - mr->ram_device = true; - memory_region_set_ops(mr, &ram_device_mem_ops, mr); - mr->destructor = memory_region_destructor_ram; + memory_region_set_ram_ptr(mr, size, ptr); +} - /* qemu_ram_alloc_from_ptr cannot fail with ptr != NULL. */ - assert(ptr != NULL); - mr->ram_block = qemu_ram_alloc_from_ptr(size, ptr, mr, &error_abort); +void memory_region_init_ram_device_ptr(MemoryRegion *mr, Object *owner, + const char *name, uint64_t size, + void *ptr) +{ + memory_region_init_io(mr, owner, &ram_device_mem_ops, mr, name, size); + mr->ram_device = true; + memory_region_set_ram_ptr(mr, size, ptr); } -void memory_region_init_alias(MemoryRegion *mr, - Object *owner, - const char *name, - MemoryRegion *orig, - hwaddr offset, - uint64_t size) +void memory_region_init_alias(MemoryRegion *mr, Object *owner, + const char *name, MemoryRegion *orig, + hwaddr offset, uint64_t size) { memory_region_init(mr, owner, name, size); mr->alias = orig; mr->alias_offset = offset; } -bool memory_region_init_rom_nomigrate(MemoryRegion *mr, - Object *owner, - const char *name, - uint64_t size, - Error **errp) -{ - if (!memory_region_init_ram_flags_nomigrate(mr, owner, name, - size, 0, errp)) { - return false; - } - mr->readonly = true; - - return true; -} - void memory_region_init_iommu(void *_iommu_mr, size_t instance_size, const char *mrtypename, @@ -1819,9 +1750,9 @@ static void memory_region_finalize(Object *obj) g_free(mr->ioeventfds); } -Object *memory_region_owner(MemoryRegion *mr) +Object *memory_region_owner(const MemoryRegion *mr) { - Object *obj = OBJECT(mr); + const Object *obj = OBJECT(mr); return obj->parent; } @@ -1849,7 +1780,7 @@ void memory_region_unref(MemoryRegion *mr) } } -uint64_t memory_region_size(MemoryRegion *mr) +uint64_t memory_region_size(const MemoryRegion *mr) { if (int128_eq(mr->size, int128_2_64())) { return UINT64_MAX; @@ -1866,25 +1797,25 @@ const char *memory_region_name(const MemoryRegion *mr) return mr->name; } -bool memory_region_is_ram_device(MemoryRegion *mr) +bool memory_region_is_ram_device(const MemoryRegion *mr) { return mr->ram_device; } -bool memory_region_is_protected(MemoryRegion *mr) +bool memory_region_is_protected(const MemoryRegion *mr) { return mr->ram && (mr->ram_block->flags & RAM_PROTECTED); } -bool memory_region_has_guest_memfd(MemoryRegion *mr) +bool memory_region_has_guest_memfd(const MemoryRegion *mr) { return mr->ram_block && mr->ram_block->guest_memfd >= 0; } -uint8_t memory_region_get_dirty_log_mask(MemoryRegion *mr) +uint8_t memory_region_get_dirty_log_mask(const MemoryRegion *mr) { uint8_t mask = mr->dirty_log_mask; - RAMBlock *rb = mr->ram_block; + const RAMBlock *rb = mr->ram_block; if (global_dirty_tracking && ((rb && qemu_ram_is_migratable(rb)) || memory_region_is_iommu(mr))) { @@ -1898,7 +1829,7 @@ uint8_t memory_region_get_dirty_log_mask(MemoryRegion *mr) return mask; } -bool memory_region_is_logging(MemoryRegion *mr, uint8_t client) +bool memory_region_is_logging(const MemoryRegion *mr, uint8_t client) { return memory_region_get_dirty_log_mask(mr) & (1 << client); } @@ -2406,7 +2337,7 @@ void memory_region_reset_dirty(MemoryRegion *mr, hwaddr addr, memory_region_get_ram_addr(mr) + addr, size, client, NULL); } -int memory_region_get_fd(MemoryRegion *mr) +int memory_region_get_fd(const MemoryRegion *mr) { RCU_READ_LOCK_GUARD(); while (mr->alias) { @@ -2415,7 +2346,7 @@ int memory_region_get_fd(MemoryRegion *mr) return mr->ram_block->fd; } -void *memory_region_get_ram_ptr(MemoryRegion *mr) +void *memory_region_get_ram_ptr(const MemoryRegion *mr) { uint64_t offset = 0; @@ -2440,7 +2371,7 @@ MemoryRegion *memory_region_from_host(void *ptr, ram_addr_t *offset) return block->mr; } -ram_addr_t memory_region_get_ram_addr(MemoryRegion *mr) +ram_addr_t memory_region_get_ram_addr(const MemoryRegion *mr) { return mr->ram_block ? mr->ram_block->offset : RAM_ADDR_INVALID; } @@ -2806,7 +2737,7 @@ static FlatRange *flatview_lookup(FlatView *view, AddrRange addr) sizeof(FlatRange), cmp_flatrange_addr); } -bool memory_region_is_mapped(MemoryRegion *mr) +bool memory_region_is_mapped(const MemoryRegion *mr) { return !!mr->container || mr->mapped_via_alias; } @@ -3290,7 +3221,7 @@ void address_space_destroy_free(AddressSpace *as) call_rcu(as, do_address_space_destroy_free, rcu); } -static const char *memory_region_type(MemoryRegion *mr) +static const char *memory_region_type(const MemoryRegion *mr) { if (mr->alias) { return memory_region_type(mr->alias); @@ -3303,6 +3234,8 @@ static const char *memory_region_type(MemoryRegion *mr) return "rom"; } else if (memory_region_is_ram(mr)) { return "ram"; + } else if (!mr->container) { + return "container"; } else { return "i/o"; } @@ -3483,7 +3416,6 @@ static void mtree_print_flatview(gpointer key, gpointer value, GArray *fv_address_spaces = value; struct FlatViewInfo *fvi = user_data; FlatRange *range = &view->ranges[0]; - MemoryRegion *mr; int n = view->nr; int i; AddressSpace *as; @@ -3510,7 +3442,8 @@ static void mtree_print_flatview(gpointer key, gpointer value, } while (n--) { - mr = range->mr; + const MemoryRegion *mr = range->mr; + if (range->offset_in_region) { qemu_printf(MTREE_INDENT HWADDR_FMT_plx "-" HWADDR_FMT_plx " (prio %d, %s%s): %s @" HWADDR_FMT_plx, @@ -3683,8 +3616,10 @@ static void mtree_info_as(bool dispatch_tree, bool owner, bool disabled) /* print aliased regions */ QTAILQ_FOREACH(ml, &ml_head, mrqueue) { - qemu_printf("memory-region: %s\n", memory_region_name(ml->mr)); - mtree_print_mr(ml->mr, 1, 0, &ml_head, owner, disabled); + const MemoryRegion *mr = ml->mr; + + qemu_printf("memory-region: %s\n", memory_region_name(mr)); + mtree_print_mr(mr, 1, 0, &ml_head, owner, disabled); qemu_printf("\n"); } @@ -3702,17 +3637,10 @@ void mtree_info(bool flatview, bool dispatch_tree, bool owner, bool disabled) } } -bool memory_region_init_ram(MemoryRegion *mr, - Object *owner, - const char *name, - uint64_t size, - Error **errp) +static void memory_region_register_ram(MemoryRegion *mr, Object *owner) { DeviceState *owner_dev; - if (!memory_region_init_ram_nomigrate(mr, owner, name, size, errp)) { - return false; - } /* This will assert if owner is neither NULL nor a DeviceState. * We only want the owner here for the purposes of defining a * unique name for migration. TODO: Ideally we should implement @@ -3721,90 +3649,62 @@ bool memory_region_init_ram(MemoryRegion *mr, */ owner_dev = DEVICE(owner); vmstate_register_ram(mr, owner_dev); +} +bool memory_region_init_ram(MemoryRegion *mr, Object *owner, + const char *name, uint64_t size, + Error **errp) +{ + if (!memory_region_init_ram_flags_nomigrate(mr, owner, name, size, 0, + errp)) { + return false; + } + memory_region_register_ram(mr, owner); return true; } -bool memory_region_init_ram_guest_memfd(MemoryRegion *mr, - Object *owner, - const char *name, - uint64_t size, +bool memory_region_init_ram_guest_memfd(MemoryRegion *mr, Object *owner, + const char *name, uint64_t size, Error **errp) { - DeviceState *owner_dev; - if (!memory_region_init_ram_flags_nomigrate(mr, owner, name, size, RAM_GUEST_MEMFD, errp)) { return false; } - /* This will assert if owner is neither NULL nor a DeviceState. - * We only want the owner here for the purposes of defining a - * unique name for migration. TODO: Ideally we should implement - * a naming scheme for Objects which are not DeviceStates, in - * which case we can relax this restriction. - */ - owner_dev = DEVICE(owner); - vmstate_register_ram(mr, owner_dev); - + memory_region_register_ram(mr, owner); return true; } -bool memory_region_init_rom(MemoryRegion *mr, - Object *owner, - const char *name, - uint64_t size, +bool memory_region_init_rom(MemoryRegion *mr, Object *owner, + const char *name, uint64_t size, Error **errp) { - DeviceState *owner_dev; - - if (!memory_region_init_rom_nomigrate(mr, owner, name, size, errp)) { + if (!memory_region_init_ram_flags_nomigrate(mr, owner, name, size, 0, + errp)) { return false; } - /* This will assert if owner is neither NULL nor a DeviceState. - * We only want the owner here for the purposes of defining a - * unique name for migration. TODO: Ideally we should implement - * a naming scheme for Objects which are not DeviceStates, in - * which case we can relax this restriction. - */ - owner_dev = DEVICE(owner); - vmstate_register_ram(mr, owner_dev); - + mr->readonly = true; + memory_region_register_ram(mr, owner); return true; } -bool memory_region_init_rom_device(MemoryRegion *mr, - Object *owner, - const MemoryRegionOps *ops, - void *opaque, - const char *name, - uint64_t size, +bool memory_region_init_rom_device(MemoryRegion *mr, Object *owner, + const MemoryRegionOps *ops, void *opaque, + const char *name, uint64_t size, Error **errp) { - DeviceState *owner_dev; - Error *err = NULL; + RAMBlock *rb; assert(ops); - memory_region_init(mr, owner, name, size); - memory_region_set_ops(mr, ops, opaque); - mr->rom_device = true; - mr->destructor = memory_region_destructor_ram; - mr->ram_block = qemu_ram_alloc(size, 0, mr, &err); - if (err) { - mr->size = int128_zero(); - object_unparent(OBJECT(mr)); - error_propagate(errp, err); - return false; + memory_region_init_io(mr, owner, ops, opaque, name, size); + rb = qemu_ram_alloc(size, 0, mr, errp); + if (memory_region_set_ram_block(mr, rb)) { + mr->ram = false; + mr->rom_device = true; + memory_region_register_ram(mr, owner); + return true; } - /* This will assert if owner is neither NULL nor a DeviceState. - * We only want the owner here for the purposes of defining a - * unique name for migration. TODO: Ideally we should implement - * a naming scheme for Objects which are not DeviceStates, in - * which case we can relax this restriction. - */ - owner_dev = DEVICE(owner); - vmstate_register_ram(mr, owner_dev); - - return true; + return false; } /* diff --git a/system/meson.build b/system/meson.build index 4b69ef0f5fb6..579e8353d536 100644 --- a/system/meson.build +++ b/system/meson.build @@ -1,13 +1,9 @@ -specific_ss.add(when: 'CONFIG_SYSTEM_ONLY', if_true: [files( - 'arch_init.c', - 'globals-target.c', -)]) - system_ss.add(files( 'vl.c', ), sdl, libpmem, libdaxctl) system_ss.add(files( + 'arch_init.c', 'balloon.c', 'bootdevice.c', 'cpus.c', diff --git a/system/physmem.c b/system/physmem.c index 2fb0c25c93b5..4a9e0760045b 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -578,7 +578,9 @@ MemoryRegion *flatview_translate(FlatView *fv, hwaddr addr, hwaddr *xlat, is_write, true, &as, attrs); mr = section.mr; - if (xen_enabled() && memory_access_is_direct(mr, is_write, attrs)) { + if (xen_map_cache_enabled() && + memory_access_is_direct(mr, is_write, attrs)) { + /* mapcache: Next page may be unmapped or in a different bucket/VA. */ hwaddr page = ((addr & TARGET_PAGE_MASK) + TARGET_PAGE_SIZE) - addr; *plen = MIN(page, *plen); } @@ -1857,48 +1859,48 @@ static void qemu_ram_setup_dump(void *addr, ram_addr_t size) } } -const char *qemu_ram_get_idstr(RAMBlock *rb) +const char *qemu_ram_get_idstr(const RAMBlock *rb) { return rb->idstr; } -void *qemu_ram_get_host_addr(RAMBlock *rb) +void *qemu_ram_get_host_addr(const RAMBlock *rb) { return rb->host; } -ram_addr_t qemu_ram_get_offset(RAMBlock *rb) +ram_addr_t qemu_ram_get_offset(const RAMBlock *rb) { return rb->offset; } -ram_addr_t qemu_ram_get_fd_offset(RAMBlock *rb) +ram_addr_t qemu_ram_get_fd_offset(const RAMBlock *rb) { return rb->fd_offset; } -ram_addr_t qemu_ram_get_used_length(RAMBlock *rb) +ram_addr_t qemu_ram_get_used_length(const RAMBlock *rb) { return rb->used_length; } -ram_addr_t qemu_ram_get_max_length(RAMBlock *rb) +ram_addr_t qemu_ram_get_max_length(const RAMBlock *rb) { return rb->max_length; } -bool qemu_ram_is_shared(RAMBlock *rb) +bool qemu_ram_is_shared(const RAMBlock *rb) { return rb->flags & RAM_SHARED; } -bool qemu_ram_is_noreserve(RAMBlock *rb) +bool qemu_ram_is_noreserve(const RAMBlock *rb) { return rb->flags & RAM_NORESERVE; } /* Note: Only set at the start of postcopy */ -bool qemu_ram_is_uf_zeroable(RAMBlock *rb) +bool qemu_ram_is_uf_zeroable(const RAMBlock *rb) { return rb->flags & RAM_UF_ZEROPAGE; } @@ -1908,7 +1910,7 @@ void qemu_ram_set_uf_zeroable(RAMBlock *rb) rb->flags |= RAM_UF_ZEROPAGE; } -bool qemu_ram_is_migratable(RAMBlock *rb) +bool qemu_ram_is_migratable(const RAMBlock *rb) { return rb->flags & RAM_MIGRATABLE; } @@ -1923,12 +1925,12 @@ void qemu_ram_unset_migratable(RAMBlock *rb) rb->flags &= ~RAM_MIGRATABLE; } -bool qemu_ram_is_named_file(RAMBlock *rb) +bool qemu_ram_is_named_file(const RAMBlock *rb) { return rb->flags & RAM_NAMED_FILE; } -int qemu_ram_get_fd(RAMBlock *rb) +int qemu_ram_get_fd(const RAMBlock *rb) { return rb->fd; } @@ -1973,7 +1975,7 @@ void qemu_ram_unset_idstr(RAMBlock *block) } } -static char *cpr_name(MemoryRegion *mr) +static char *cpr_name(const MemoryRegion *mr) { const char *mr_name = memory_region_name(mr); g_autofree char *id = mr->dev ? qdev_get_dev_path(mr->dev) : NULL; @@ -1985,7 +1987,7 @@ static char *cpr_name(MemoryRegion *mr) } } -size_t qemu_ram_pagesize(RAMBlock *rb) +size_t qemu_ram_pagesize(const RAMBlock *rb) { return rb->page_size; } @@ -2577,7 +2579,7 @@ static void reclaim_ramblock(RAMBlock *block) { if (block->flags & RAM_PREALLOC) { ; - } else if (xen_enabled()) { + } else if (xen_map_cache_enabled()) { xen_invalidate_map_cache_entry(block->host); #if !defined(_WIN32) && !defined(EMSCRIPTEN) } else if (block->fd >= 0) { @@ -2736,7 +2738,7 @@ static void *qemu_ram_ptr_length(RAMBlock *block, ram_addr_t addr, len = *size; } - if (xen_enabled() && block->host == NULL) { + if (xen_map_cache_enabled() && block->host == NULL) { /* We need to check if the requested address is in the RAM * because we don't want to map the entire memory in QEMU. * In that case just map the requested area. @@ -2770,7 +2772,7 @@ void *qemu_map_ram_ptr(RAMBlock *ram_block, ram_addr_t addr) } /* Return the offset of a hostpointer within a ramblock */ -ram_addr_t qemu_ram_block_host_offset(RAMBlock *rb, void *host) +ram_addr_t qemu_ram_block_host_offset(const RAMBlock *rb, void *host) { ram_addr_t res = (uint8_t *)host - (uint8_t *)rb->host; assert((uintptr_t)host >= (uintptr_t)rb->host); @@ -2785,7 +2787,7 @@ RAMBlock *qemu_ram_block_from_host(void *ptr, bool round_offset, RAMBlock *block; uint8_t *host = ptr; - if (xen_enabled()) { + if (xen_map_cache_enabled()) { ram_addr_t ram_addr; RCU_READ_LOCK_GUARD(); ram_addr = xen_ram_addr_from_mapcache(ptr); @@ -2826,6 +2828,34 @@ RAMBlock *qemu_ram_block_from_host(void *ptr, bool round_offset, return block; } +/* + * Creates new guest memfd for the ramblocks and closes the + * existing memfd. + */ +int ram_block_rebind(Error **errp) +{ + RAMBlock *block; + + qemu_mutex_lock_ramlist(); + + RAMBLOCK_FOREACH(block) { + if (block->flags & RAM_GUEST_MEMFD) { + if (block->guest_memfd >= 0) { + close(block->guest_memfd); + } + block->guest_memfd = kvm_create_guest_memfd(block->max_length, + 0, errp); + if (block->guest_memfd < 0) { + qemu_mutex_unlock_ramlist(); + return -1; + } + + } + } + qemu_mutex_unlock_ramlist(); + return 0; +} + /* * Finds the named RAMBlock * @@ -3759,7 +3789,7 @@ void address_space_unmap(AddressSpace *as, void *buffer, hwaddr len, if (is_write) { invalidate_and_set_dirty(mr, addr1, access_len); } - if (xen_enabled()) { + if (xen_map_cache_enabled()) { xen_invalidate_map_cache_entry(buffer); } memory_region_unref(mr); @@ -3870,7 +3900,7 @@ void address_space_cache_destroy(MemoryRegionCache *cache) return; } - if (xen_enabled()) { + if (xen_map_cache_enabled()) { xen_invalidate_map_cache_entry(cache->ptr); } memory_region_unref(cache->mrs.mr); diff --git a/system/qtest.c b/system/qtest.c index e42b83ce6713..cf90cd53adbe 100644 --- a/system/qtest.c +++ b/system/qtest.c @@ -71,6 +71,7 @@ static void *qtest_server_send_opaque; * * Extra ASCII space characters in command inputs are permitted and ignored. * Lines containing only spaces are permitted and ignored. + * Lines that start with a '#' character (comments) are permitted and ignored. * * Valid requests * ^^^^^^^^^^^^^^ @@ -370,8 +371,8 @@ static void qtest_process_command(CharFrontend *chr, gchar **words) fprintf(qtest_log_fp, "\n"); } - if (!command) { - /* Input line was blank: ignore it */ + if (!command || command[0] == '#') { + /* Input line was blank or a comment: ignore it */ return; } diff --git a/system/ram-block-attributes.c b/system/ram-block-attributes.c index fb7c5c274677..630b0fda1265 100644 --- a/system/ram-block-attributes.c +++ b/system/ram-block-attributes.c @@ -61,16 +61,6 @@ ram_block_attributes_notify_populate_cb(MemoryRegionSection *section, return rdl->notify_populate(rdl, section); } -static int -ram_block_attributes_notify_discard_cb(MemoryRegionSection *section, - void *arg) -{ - RamDiscardListener *rdl = arg; - - rdl->notify_discard(rdl, section); - return 0; -} - static int ram_block_attributes_for_each_populated_section(const RamBlockAttributes *attr, MemoryRegionSection *section, @@ -191,22 +181,11 @@ ram_block_attributes_rdm_unregister_listener(RamDiscardManager *rdm, RamDiscardListener *rdl) { RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); - int ret; g_assert(rdl->section); g_assert(rdl->section->mr == attr->ram_block->mr); - if (rdl->double_discard_supported) { - rdl->notify_discard(rdl, rdl->section); - } else { - ret = ram_block_attributes_for_each_populated_section(attr, - rdl->section, rdl, ram_block_attributes_notify_discard_cb); - if (ret) { - error_report("%s: Failed to unregister RAM discard listener: %s", - __func__, strerror(-ret)); - exit(1); - } - } + rdl->notify_discard(rdl, rdl->section); memory_region_section_free_copy(rdl->section); rdl->section = NULL; @@ -401,8 +380,7 @@ RamBlockAttributes *ram_block_attributes_create(RAMBlock *ram_block) object_unref(OBJECT(attr)); return NULL; } - attr->bitmap_size = - ROUND_UP(int128_get64(mr->size), block_size) / block_size; + attr->bitmap_size = DIV_ROUND_UP(int128_get64(mr->size), block_size); attr->bitmap = bitmap_new(attr->bitmap_size); return attr; diff --git a/system/runstate.c b/system/runstate.c index d091a2bdddbc..eca722b43c69 100644 --- a/system/runstate.c +++ b/system/runstate.c @@ -42,6 +42,7 @@ #include "qapi/qapi-commands-run-state.h" #include "qapi/qapi-events-run-state.h" #include "qemu/accel.h" +#include "accel/accel-ops.h" #include "qemu/error-report.h" #include "qemu/job.h" #include "qemu/log.h" @@ -57,6 +58,7 @@ #include "system/reset.h" #include "system/runstate.h" #include "system/runstate-action.h" +#include "system/confidential-guest-support.h" #include "system/system.h" #include "system/tpm.h" #include "trace.h" @@ -508,6 +510,9 @@ void qemu_system_reset(ShutdownCause reason) { MachineClass *mc; ResetType type; + AccelClass *ac = ACCEL_GET_CLASS(current_accel()); + bool guest_state_rebuilt = false; + int ret; mc = current_machine ? MACHINE_GET_CLASS(current_machine) : NULL; @@ -520,6 +525,29 @@ void qemu_system_reset(ShutdownCause reason) default: type = RESET_TYPE_COLD; } + + if ((reason == SHUTDOWN_CAUSE_GUEST_RESET || + reason == SHUTDOWN_CAUSE_HOST_QMP_SYSTEM_RESET) && + (current_machine->new_accel_vmfd_on_reset || !cpus_are_resettable())) { + if (ac->rebuild_guest) { + ret = ac->rebuild_guest(current_machine); + if (ret < 0) { + error_report("unable to rebuild guest: %s(%d)", + strerror(-ret), ret); + vm_stop(RUN_STATE_INTERNAL_ERROR); + } else { + info_report("virtual machine state has been rebuilt with new " + "guest file handle."); + guest_state_rebuilt = true; + } + } else if (!cpus_are_resettable()) { + error_report("accelerator does not support reset!"); + } else { + error_report("accelerator does not support rebuilding guest state," + " proceeding with normal reset!"); + } + } + if (mc && mc->reset) { mc->reset(current_machine, type); } else { @@ -542,9 +570,16 @@ void qemu_system_reset(ShutdownCause reason) * it does _more_ than cpu_synchronize_all_post_reset(). */ if (cpus_are_resettable()) { - cpu_synchronize_all_post_reset(); - } else { - assert(runstate_check(RUN_STATE_PRELAUNCH)); + if (guest_state_rebuilt) { + /* + * If guest state has been rebuilt, then we + * need to sync full cpu state for non confidential guests post + * reset. + */ + cpu_synchronize_all_post_init(); + } else { + cpu_synchronize_all_post_reset(); + } } vm_set_suspended(false); @@ -697,7 +732,8 @@ void qemu_system_reset_request(ShutdownCause reason) if (reboot_action == REBOOT_ACTION_SHUTDOWN && reason != SHUTDOWN_CAUSE_SUBSYSTEM_RESET) { shutdown_requested = reason; - } else if (!cpus_are_resettable()) { + } else if (!cpus_are_resettable() && + !confidential_guest_can_rebuild_state(current_machine->cgs)) { error_report("cpus are not resettable, terminating"); shutdown_requested = reason; } else { diff --git a/system/vl.c b/system/vl.c index aa9a15504174..38d7b849e0ac 100644 --- a/system/vl.c +++ b/system/vl.c @@ -403,9 +403,8 @@ static QemuOptsList qemu_name_opts = { }, { .name = "debug-threads", .type = QEMU_OPT_BOOL, - .help = "When enabled, name the individual threads; defaults off.\n" - "NOTE: The thread names are for debugging and not a\n" - "stable API.", + .help = "Enable thread names" + "(deprecated, always enabled where supported)", }, { /* End of list */ } }, @@ -554,9 +553,12 @@ static int parse_name(void *opaque, QemuOpts *opts, Error **errp) { const char *proc_name; - if (qemu_opt_get(opts, "debug-threads")) { - qemu_thread_naming(qemu_opt_get_bool(opts, "debug-threads", false)); + if (qemu_opt_get(opts, "debug-threads") && + !qemu_opt_get_bool(opts, "debug-threads", false)) { + fprintf(stderr, "Ignoring deprecated 'debug-threads=no' option, " \ + "thread naming is unconditionally enabled\n"); } + qemu_name = qemu_opt_get(opts, "guest"); proc_name = qemu_opt_get(opts, "process"); @@ -2889,10 +2891,8 @@ void qemu_init(int argc, char **argv) os_setup_limits(); -#ifdef CONFIG_MODULES module_init_info(qemu_modinfo); module_allow_arch(target_name()); -#endif qemu_init_subsystems(); diff --git a/target-info-stub.c b/target-info-stub.c index 65220cc78201..f5896a72621d 100644 --- a/target-info-stub.c +++ b/target-info-stub.c @@ -11,11 +11,17 @@ #include "qemu/target-info-impl.h" #include "hw/core/boards.h" #include "cpu.h" +#include "exec/page-vary.h" /* Validate correct placement of CPUArchState. */ QEMU_BUILD_BUG_ON(offsetof(ArchCPU, parent_obj) != 0); QEMU_BUILD_BUG_ON(offsetof(ArchCPU, env) != sizeof(CPUState)); +/* Validate target page size, if invariant. */ +#ifndef TARGET_PAGE_BITS_VARY +QEMU_BUILD_BUG_ON(TARGET_PAGE_BITS < TARGET_PAGE_BITS_MIN); +#endif + static const TargetInfo target_info_stub = { .target_name = TARGET_NAME, .target_arch = glue(SYS_EMU_TARGET_, TARGET_ARCH), @@ -23,6 +29,15 @@ static const TargetInfo target_info_stub = { .cpu_type = CPU_RESOLVING_TYPE, .machine_typename = TYPE_MACHINE, .endianness = TARGET_BIG_ENDIAN ? ENDIAN_MODE_BIG : ENDIAN_MODE_LITTLE, +#ifdef TARGET_PAGE_BITS_VARY + .page_bits_vary = true, +# ifdef TARGET_PAGE_BITS_LEGACY + .page_bits_init = TARGET_PAGE_BITS_LEGACY, +# endif +#else + .page_bits_vary = false, + .page_bits_init = TARGET_PAGE_BITS, +#endif }; const TargetInfo *target_info(void) diff --git a/target-info.c b/target-info.c index a26532f660f1..28c458fc7a7c 100644 --- a/target-info.c +++ b/target-info.c @@ -88,3 +88,8 @@ bool target_ppc64(void) { return target_arch() == SYS_EMU_TARGET_PPC64; } + +bool target_s390x(void) +{ + return target_arch() == SYS_EMU_TARGET_S390X; +} diff --git a/target/alpha/cpu-param.h b/target/alpha/cpu-param.h index a799f42db314..f87b36fe873e 100644 --- a/target/alpha/cpu-param.h +++ b/target/alpha/cpu-param.h @@ -8,9 +8,6 @@ #ifndef ALPHA_CPU_PARAM_H #define ALPHA_CPU_PARAM_H -/* ??? EV4 has 34 phys addr bits, EV5 has 40, EV6 has 44. */ -#define TARGET_PHYS_ADDR_SPACE_BITS 44 - #ifdef CONFIG_USER_ONLY /* * Allow user-only to vary page size. Real hardware allows only 8k and 64k, @@ -24,6 +21,4 @@ # define TARGET_VIRT_ADDR_SPACE_BITS (30 + TARGET_PAGE_BITS) #endif -#define TARGET_INSN_START_EXTRA_WORDS 0 - #endif diff --git a/target/alpha/cpu.c b/target/alpha/cpu.c index 1780db7d1e29..ff053043a386 100644 --- a/target/alpha/cpu.c +++ b/target/alpha/cpu.c @@ -124,6 +124,7 @@ static void alpha_cpu_realizefn(DeviceState *dev, Error **errp) } qemu_init_vcpu(cs); + cpu_reset(cs); acc->parent_realize(dev, errp); } @@ -295,7 +296,6 @@ static void alpha_cpu_class_init(ObjectClass *oc, const void *data) cc->disas_set_info = alpha_cpu_disas_set_info; cc->tcg_ops = &alpha_tcg_ops; - cc->gdb_num_core_regs = 67; } #define DEFINE_ALPHA_CPU_TYPE(base_type, cpu_model, initfn) \ diff --git a/target/alpha/gdbstub.c b/target/alpha/gdbstub.c index a7110e8d1eb6..b458eadd7d9d 100644 --- a/target/alpha/gdbstub.c +++ b/target/alpha/gdbstub.c @@ -31,6 +31,9 @@ int alpha_cpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n) case 0 ... 30: val = cpu_alpha_load_gr(env, n); break; + case 31: /* zero register */ + val = 0; + break; case 32 ... 62: d.d = env->fir[n - 32]; val = d.ll; @@ -41,19 +44,16 @@ int alpha_cpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n) case 64: val = env->pc; break; - case 66: - val = env->unique; - break; - case 31: - case 65: - /* 31 really is the zero register; 65 is unassigned in the - gdb protocol, but is still required to occupy 8 bytes. */ + case 65: /* former Virtual Register (reserved as unassigned) */ val = 0; break; + case 66: /* PALcode Memory Slot */ + val = env->unique; + break; default: return 0; } - return gdb_get_regl(mem_buf, val); + return gdb_get_reg64(mem_buf, val); } int alpha_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) @@ -66,6 +66,8 @@ int alpha_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) case 0 ... 30: cpu_alpha_store_gr(env, n, tmp); break; + case 31: /* zero register */ + break; case 32 ... 62: d.ll = tmp; env->fir[n - 32] = d.d; @@ -76,13 +78,10 @@ int alpha_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) case 64: env->pc = tmp; break; - case 66: - env->unique = tmp; + case 65: /* former Virtual Register (reserved as unassigned) */ break; - case 31: - case 65: - /* 31 really is the zero register; 65 is unassigned in the - gdb protocol, but is still required to occupy 8 bytes. */ + case 66: /* PALcode Memory Slot */ + env->unique = tmp; break; default: return 0; diff --git a/target/alpha/meson.build b/target/alpha/meson.build index 9447f8020be4..818256a1c313 100644 --- a/target/alpha/meson.build +++ b/target/alpha/meson.build @@ -2,7 +2,6 @@ alpha_ss = ss.source_set() alpha_ss.add(files( 'cpu.c', 'fpu_helper.c', - 'gdbstub.c', 'helper.c', 'clk_helper.c', 'int_helper.c', @@ -11,11 +10,18 @@ alpha_ss.add(files( 'vax_helper.c', )) -alpha_system_ss = ss.source_set() -alpha_system_ss.add(files( +alpha_user_ss = ss.source_set() +alpha_user_ss.add(files( + 'gdbstub.c', +)) + +alpha_common_system_ss = ss.source_set() +alpha_common_system_ss.add(files( + 'gdbstub.c', 'machine.c', 'sys_helper.c', )) target_arch += {'alpha': alpha_ss} -target_common_system_arch += {'alpha': alpha_system_ss} +target_user_arch += {'alpha': alpha_user_ss} +target_common_system_arch += {'alpha': alpha_common_system_ss} diff --git a/target/alpha/translate.c b/target/alpha/translate.c index 4442462891ee..4d22d7d5a450 100644 --- a/target/alpha/translate.c +++ b/target/alpha/translate.c @@ -2899,9 +2899,9 @@ static void alpha_tr_insn_start(DisasContextBase *dcbase, CPUState *cpu) DisasContext *ctx = container_of(dcbase, DisasContext, base); if (ctx->pcrel) { - tcg_gen_insn_start(dcbase->pc_next & ~TARGET_PAGE_MASK); + tcg_gen_insn_start(dcbase->pc_next & ~TARGET_PAGE_MASK, 0, 0); } else { - tcg_gen_insn_start(dcbase->pc_next); + tcg_gen_insn_start(dcbase->pc_next, 0, 0); } } diff --git a/target/arm/cpu-features.h b/target/arm/cpu-features.h index 49c50e850a54..b683c9551a0e 100644 --- a/target/arm/cpu-features.h +++ b/target/arm/cpu-features.h @@ -368,12 +368,14 @@ FIELD(ID_AA64DFR0, HPMN0, 60, 4) FIELD(ID_AA64ZFR0, SVEVER, 0, 4) FIELD(ID_AA64ZFR0, AES, 4, 4) +FIELD(ID_AA64ZFR0, ELTPERM, 12, 4) FIELD(ID_AA64ZFR0, BITPERM, 16, 4) FIELD(ID_AA64ZFR0, BFLOAT16, 20, 4) FIELD(ID_AA64ZFR0, B16B16, 24, 4) FIELD(ID_AA64ZFR0, SHA3, 32, 4) FIELD(ID_AA64ZFR0, SM4, 40, 4) FIELD(ID_AA64ZFR0, I8MM, 44, 4) +FIELD(ID_AA64ZFR0, F16MM, 48, 4) FIELD(ID_AA64ZFR0, F32MM, 52, 4) FIELD(ID_AA64ZFR0, F64MM, 56, 4) @@ -1449,7 +1451,7 @@ static inline bool isar_feature_aa64_sve2_bitperm(const ARMISARegisters *id) return FIELD_EX64_IDREG(id, ID_AA64ZFR0, BITPERM) != 0; } -static inline bool isar_feature_aa64_sve_bf16(const ARMISARegisters *id) +static inline bool isar_feature_aa64_sme_sve_bf16(const ARMISARegisters *id) { return FIELD_EX64_IDREG(id, ID_AA64ZFR0, BFLOAT16) != 0; } @@ -1464,7 +1466,8 @@ static inline bool isar_feature_aa64_sve2_sm4(const ARMISARegisters *id) return FIELD_EX64_IDREG(id, ID_AA64ZFR0, SM4) != 0; } -static inline bool isar_feature_aa64_sve_i8mm(const ARMISARegisters *id) +/* Note that this is true if either SVE or SME are implemented with I8MM */ +static inline bool isar_feature_aa64_sme_sve_i8mm(const ARMISARegisters *id) { return FIELD_EX64_IDREG(id, ID_AA64ZFR0, I8MM) != 0; } @@ -1522,6 +1525,16 @@ static inline bool isar_feature_aa64_sme2p1(const ARMISARegisters *id) /* * Combinations of feature tests, for ease of use with TRANS_FEAT. */ +static inline bool isar_feature_aa64_sme_or_sve(const ARMISARegisters *id) +{ + return isar_feature_aa64_sme(id) || isar_feature_aa64_sve(id); +} + +static inline bool isar_feature_aa64_sme_or_sve2(const ARMISARegisters *id) +{ + return isar_feature_aa64_sme(id) || isar_feature_aa64_sve2(id); +} + static inline bool isar_feature_aa64_sme_or_sve2p1(const ARMISARegisters *id) { return isar_feature_aa64_sme(id) || isar_feature_aa64_sve2p1(id); @@ -1547,6 +1560,16 @@ static inline bool isar_feature_aa64_sme2_f64f64(const ARMISARegisters *id) return isar_feature_aa64_sme2(id) && isar_feature_aa64_sme_f64f64(id); } +static inline bool isar_feature_aa64_sve_i8mm(const ARMISARegisters *id) +{ + return isar_feature_aa64_sve(id) && isar_feature_aa64_sme_sve_i8mm(id); +} + +static inline bool isar_feature_aa64_sve_bf16(const ARMISARegisters *id) +{ + return isar_feature_aa64_sve(id) && isar_feature_aa64_sme_sve_bf16(id); +} + /* * Feature tests for "does this exist in either 32-bit or 64-bit?" */ diff --git a/target/arm/cpu-param.h b/target/arm/cpu-param.h index 8b46c7c5708b..f67e7b346061 100644 --- a/target/arm/cpu-param.h +++ b/target/arm/cpu-param.h @@ -9,10 +9,8 @@ #define ARM_CPU_PARAM_H #ifdef TARGET_AARCH64 -# define TARGET_PHYS_ADDR_SPACE_BITS 52 # define TARGET_VIRT_ADDR_SPACE_BITS 52 #else -# define TARGET_PHYS_ADDR_SPACE_BITS 40 # define TARGET_VIRT_ADDR_SPACE_BITS 32 #endif @@ -32,11 +30,4 @@ # define TARGET_PAGE_BITS_LEGACY 10 #endif /* !CONFIG_USER_ONLY */ -/* - * ARM-specific extra insn start words: - * 1: Conditional execution bits - * 2: Partial exception syndrome for data aborts - */ -#define TARGET_INSN_START_EXTRA_WORDS 2 - #endif diff --git a/target/arm/cpu.c b/target/arm/cpu.c index 7542444b180f..7e3e84b4bbb4 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -1144,7 +1144,13 @@ static void arm_cpu_initfn(Object *obj) * picky DTB consumer will also provide a helpful error message. */ cpu->dtb_compatible = "qemu,unknown"; - cpu->psci_version = QEMU_PSCI_VERSION_0_1; /* By default assume PSCI v0.1 */ + if (!kvm_enabled()) { + /* By default KVM will use the newest PSCI version that it knows about. + * This can be changed using the kvm-psci-version property. + * For others assume PSCI v0.1 by default. + */ + cpu->psci_version = QEMU_PSCI_VERSION_0_1; + } cpu->kvm_target = QEMU_KVM_ARM_TARGET_NONE; if (tcg_enabled() || hvf_enabled()) { @@ -1218,10 +1224,6 @@ static void arm_set_pmu(Object *obj, bool value, Error **errp) ARMCPU *cpu = ARM_CPU(obj); if (value) { - if (kvm_enabled() && !kvm_arm_pmu_supported()) { - error_setg(errp, "'pmu' feature not supported by KVM on this host"); - return; - } set_feature(&cpu->env, ARM_FEATURE_PMU); } else { unset_feature(&cpu->env, ARM_FEATURE_PMU); @@ -1580,16 +1582,6 @@ void arm_cpu_finalize_features(ARMCPU *cpu, Error **errp) return; } - /* - * FEAT_SME is not architecturally dependent on FEAT_SVE (unless - * FEAT_SME_FA64 is present). However our implementation currently - * assumes it, so if the user asked for sve=off then turn off SME also. - * (KVM doesn't currently support SME at all.) - */ - if (cpu_isar_feature(aa64_sme, cpu) && !cpu_isar_feature(aa64_sve, cpu)) { - object_property_set_bool(OBJECT(cpu), "sme", false, &error_abort); - } - arm_cpu_sme_finalize(cpu, &local_err); if (local_err != NULL) { error_propagate(errp, local_err); diff --git a/target/arm/cpu64.c b/target/arm/cpu64.c index 5d7c6b7fbbb9..d6feba220e8d 100644 --- a/target/arm/cpu64.c +++ b/target/arm/cpu64.c @@ -79,28 +79,10 @@ void arm_cpu_sve_finalize(ARMCPU *cpu, Error **errp) */ uint32_t vq_map = cpu->sve_vq.map; uint32_t vq_init = cpu->sve_vq.init; - uint32_t vq_supported; + uint32_t vq_supported = cpu->sve_vq.supported; uint32_t vq_mask = 0; uint32_t tmp, vq, max_vq = 0; - /* - * CPU models specify a set of supported vector lengths which are - * enabled by default. Attempting to enable any vector length not set - * in the supported bitmap results in an error. When KVM is enabled we - * fetch the supported bitmap from the host. - */ - if (kvm_enabled()) { - if (kvm_arm_sve_supported()) { - cpu->sve_vq.supported = kvm_arm_sve_get_vls(cpu); - vq_supported = cpu->sve_vq.supported; - } else { - assert(!cpu_isar_feature(aa64_sve, cpu)); - vq_supported = 0; - } - } else { - vq_supported = cpu->sve_vq.supported; - } - /* * Process explicit sve properties. * From the properties, sve_vq_map implies sve_vq_init. @@ -136,9 +118,17 @@ void arm_cpu_sve_finalize(ARMCPU *cpu, Error **errp) if (!cpu_isar_feature(aa64_sve, cpu)) { /* * SVE is disabled and so are all vector lengths. Good. - * Disable all SVE extensions as well. + * Disable all SVE extensions as well. Note that some ZFR0 + * fields are used also by SME so must not be wiped in + * an SME-no-SVE config. We will clear the rest in + * arm_cpu_sme_finalize() if necessary. */ - SET_IDREG(&cpu->isar, ID_AA64ZFR0, 0); + FIELD_DP64_IDREG(&cpu->isar, ID_AA64ZFR0, F64MM, 0); + FIELD_DP64_IDREG(&cpu->isar, ID_AA64ZFR0, F32MM, 0); + FIELD_DP64_IDREG(&cpu->isar, ID_AA64ZFR0, F16MM, 0); + FIELD_DP64_IDREG(&cpu->isar, ID_AA64ZFR0, SM4, 0); + FIELD_DP64_IDREG(&cpu->isar, ID_AA64ZFR0, B16B16, 0); + FIELD_DP64_IDREG(&cpu->isar, ID_AA64ZFR0, SVEVER, 0); return; } @@ -310,6 +300,30 @@ static void cpu_arm_set_vq(Object *obj, Visitor *v, const char *name, vq_map->init |= 1 << (vq - 1); } +static void prop_bool_get_false(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + bool value = false; + visit_type_bool(v, name, &value, errp); +} + +static void prop_bool_set_false(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + bool value; + + if (visit_type_bool(v, name, &value, errp) && value) { + error_setg(errp, "'%s' feature not supported by %s on this host", + name, current_accel_name()); + } +} + +static void prop_add_stub_bool(Object *obj, const char *name) +{ + object_property_add(obj, name, "bool", prop_bool_get_false, + prop_bool_set_false, NULL, NULL); +} + static bool cpu_arm_get_sve(Object *obj, Error **errp) { ARMCPU *cpu = ARM_CPU(obj); @@ -319,12 +333,6 @@ static bool cpu_arm_get_sve(Object *obj, Error **errp) static void cpu_arm_set_sve(Object *obj, bool value, Error **errp) { ARMCPU *cpu = ARM_CPU(obj); - - if (value && kvm_enabled() && !kvm_arm_sve_supported()) { - error_setg(errp, "'sve' feature not supported by KVM on this host"); - return; - } - FIELD_DP64_IDREG(&cpu->isar, ID_AA64PFR0, SVE, value); } @@ -338,6 +346,10 @@ void arm_cpu_sme_finalize(ARMCPU *cpu, Error **errp) if (vq_map == 0) { if (!cpu_isar_feature(aa64_sme, cpu)) { SET_IDREG(&cpu->isar, ID_AA64SMFR0, 0); + if (!cpu_isar_feature(aa64_sve, cpu)) { + /* This clears the "SVE or SME" fields in ZFR0 */ + SET_IDREG(&cpu->isar, ID_AA64ZFR0, 0); + } return; } @@ -366,6 +378,21 @@ void arm_cpu_sme_finalize(ARMCPU *cpu, Error **errp) cpu->sme_vq.map = vq_map; cpu->sme_max_vq = 32 - clz32(vq_map); + + /* + * The "sme" property setter writes a bool value into ID_AA64PFR1_EL1.SME + * (and at this point we know it's not 0). Correct that value to report + * the same SME version as ID_AA64SMFR0_EL1.SMEver. + */ + if (FIELD_EX64_IDREG(&cpu->isar, ID_AA64SMFR0, SMEVER) != 0) { + /* SME2 or better */ + FIELD_DP64_IDREG(&cpu->isar, ID_AA64PFR1, SME, 2); + } + + if (!cpu_isar_feature(aa64_sve, cpu)) { + /* FEAT_SME_FA64 requires SVE, not just SME */ + FIELD_DP64_IDREG(&cpu->isar, ID_AA64SMFR0, FA64, 0); + } } static bool cpu_arm_get_sme(Object *obj, Error **errp) @@ -378,6 +405,11 @@ static void cpu_arm_set_sme(Object *obj, bool value, Error **errp) { ARMCPU *cpu = ARM_CPU(obj); + /* + * For now, write 0 for "off" and 1 for "on" into the PFR1 field. + * We will correct this value to report the right SME + * level (SME vs SME2) in arm_cpu_sme_finalize() later. + */ FIELD_DP64_IDREG(&cpu->isar, ID_AA64PFR1, SME, value); } @@ -457,7 +489,23 @@ void aarch64_add_sve_properties(Object *obj) ARMCPU *cpu = ARM_CPU(obj); uint32_t vq; - object_property_add_bool(obj, "sve", cpu_arm_get_sve, cpu_arm_set_sve); + /* + * For hw virtualization, we have already probed the set of vector + * lengths supported. If there are none, the host doesn't support + * SVE at all. In which case we register a stub property, to allow + * -cpu max,sve=off + * to always be valid. + * + * For TCG, this function is only called for cpu models which + * support SVE. The error message in the stub is written + * assuming host virtualiation is being used. + */ + if (cpu->sve_vq.supported) { + object_property_add_bool(obj, "sve", cpu_arm_get_sve, cpu_arm_set_sve); + } else { + assert(!tcg_enabled()); + prop_add_stub_bool(obj, "sve"); + } for (vq = 1; vq <= ARM_MAX_VQ; ++vq) { char name[8]; @@ -765,11 +813,17 @@ static void aarch64_a53_initfn(Object *obj) static void aarch64_host_initfn(Object *obj) { ARMCPU *cpu = ARM_CPU(obj); + +#if defined(CONFIG_NITRO) + if (nitro_enabled()) { + /* The nitro accel uses -cpu host, but does not actually consume it */ + return; + } +#endif + #if defined(CONFIG_KVM) kvm_arm_set_cpu_features_from_host(cpu); - if (arm_feature(&cpu->env, ARM_FEATURE_AARCH64)) { - aarch64_add_sve_properties(obj); - } + aarch64_add_sve_properties(obj); #elif defined(CONFIG_HVF) hvf_arm_set_cpu_features_from_host(cpu); #elif defined(CONFIG_WHPX) diff --git a/target/arm/debug_helper.c b/target/arm/debug_helper.c index 579516e15414..352c8e5c8e7b 100644 --- a/target/arm/debug_helper.c +++ b/target/arm/debug_helper.c @@ -14,775 +14,6 @@ #include "exec/watchpoint.h" #include "system/tcg.h" -#define HELPER_H "tcg/helper.h" -#include "exec/helper-proto.h.inc" - -#ifdef CONFIG_TCG -/* Return the Exception Level targeted by debug exceptions. */ -static int arm_debug_target_el(CPUARMState *env) -{ - bool secure = arm_is_secure(env); - bool route_to_el2 = false; - - if (arm_feature(env, ARM_FEATURE_M)) { - return 1; - } - - if (arm_is_el2_enabled(env)) { - route_to_el2 = env->cp15.hcr_el2 & HCR_TGE || - env->cp15.mdcr_el2 & MDCR_TDE; - } - - if (route_to_el2) { - return 2; - } else if (arm_feature(env, ARM_FEATURE_EL3) && - !arm_el_is_aa64(env, 3) && secure) { - return 3; - } else { - return 1; - } -} - -/* - * Raise an exception to the debug target el. - * Modify syndrome to indicate when origin and target EL are the same. - */ -G_NORETURN static void -raise_exception_debug(CPUARMState *env, uint32_t excp, uint32_t syndrome) -{ - int debug_el = arm_debug_target_el(env); - int cur_el = arm_current_el(env); - - /* - * If singlestep is targeting a lower EL than the current one, then - * DisasContext.ss_active must be false and we can never get here. - * Similarly for watchpoint and breakpoint matches. - */ - assert(debug_el >= cur_el); - syndrome |= (debug_el == cur_el) << ARM_EL_EC_SHIFT; - raise_exception(env, excp, syndrome, debug_el); -} - -/* See AArch64.GenerateDebugExceptionsFrom() in ARM ARM pseudocode */ -static bool aa64_generate_debug_exceptions(CPUARMState *env) -{ - int cur_el = arm_current_el(env); - int debug_el; - - if (cur_el == 3) { - return false; - } - - /* MDCR_EL3.SDD disables debug events from Secure state */ - if (arm_is_secure_below_el3(env) - && extract32(env->cp15.mdcr_el3, 16, 1)) { - return false; - } - - /* - * Same EL to same EL debug exceptions need MDSCR_KDE enabled - * while not masking the (D)ebug bit in DAIF. - */ - debug_el = arm_debug_target_el(env); - - if (cur_el == debug_el) { - return extract32(env->cp15.mdscr_el1, 13, 1) - && !(env->daif & PSTATE_D); - } - - /* Otherwise the debug target needs to be a higher EL */ - return debug_el > cur_el; -} - -static bool aa32_generate_debug_exceptions(CPUARMState *env) -{ - int el = arm_current_el(env); - - if (el == 0 && arm_el_is_aa64(env, 1)) { - return aa64_generate_debug_exceptions(env); - } - - if (arm_is_secure(env)) { - int spd; - - if (el == 0 && (env->cp15.sder & 1)) { - /* - * SDER.SUIDEN means debug exceptions from Secure EL0 - * are always enabled. Otherwise they are controlled by - * SDCR.SPD like those from other Secure ELs. - */ - return true; - } - - spd = extract32(env->cp15.mdcr_el3, 14, 2); - switch (spd) { - case 1: - /* SPD == 0b01 is reserved, but behaves as 0b00. */ - case 0: - /* - * For 0b00 we return true if external secure invasive debug - * is enabled. On real hardware this is controlled by external - * signals to the core. QEMU always permits debug, and behaves - * as if DBGEN, SPIDEN, NIDEN and SPNIDEN are all tied high. - */ - return true; - case 2: - return false; - case 3: - return true; - } - } - - return el != 2; -} - -/* - * Return true if debugging exceptions are currently enabled. - * This corresponds to what in ARM ARM pseudocode would be - * if UsingAArch32() then - * return AArch32.GenerateDebugExceptions() - * else - * return AArch64.GenerateDebugExceptions() - * We choose to push the if() down into this function for clarity, - * since the pseudocode has it at all callsites except for the one in - * CheckSoftwareStep(), where it is elided because both branches would - * always return the same value. - */ -bool arm_generate_debug_exceptions(CPUARMState *env) -{ - if ((env->cp15.oslsr_el1 & 1) || (env->cp15.osdlr_el1 & 1)) { - return false; - } - if (is_a64(env)) { - return aa64_generate_debug_exceptions(env); - } else { - return aa32_generate_debug_exceptions(env); - } -} - -/* - * Is single-stepping active? (Note that the "is EL_D AArch64?" check - * implicitly means this always returns false in pre-v8 CPUs.) - */ -bool arm_singlestep_active(CPUARMState *env) -{ - return extract32(env->cp15.mdscr_el1, 0, 1) - && arm_el_is_aa64(env, arm_debug_target_el(env)) - && arm_generate_debug_exceptions(env); -} - -/* Return true if the linked breakpoint entry lbn passes its checks */ -static bool linked_bp_matches(ARMCPU *cpu, int lbn) -{ - CPUARMState *env = &cpu->env; - uint64_t bcr = env->cp15.dbgbcr[lbn]; - int brps = arm_num_brps(cpu); - int ctx_cmps = arm_num_ctx_cmps(cpu); - int bt; - uint32_t contextidr; - uint64_t hcr_el2; - - /* - * Links to unimplemented or non-context aware breakpoints are - * CONSTRAINED UNPREDICTABLE: either behave as if disabled, or - * as if linked to an UNKNOWN context-aware breakpoint (in which - * case DBGWCR_EL1.LBN must indicate that breakpoint). - * We choose the former. - */ - if (lbn >= brps || lbn < (brps - ctx_cmps)) { - return false; - } - - bcr = env->cp15.dbgbcr[lbn]; - - if (extract64(bcr, 0, 1) == 0) { - /* Linked breakpoint disabled : generate no events */ - return false; - } - - bt = extract64(bcr, 20, 4); - hcr_el2 = arm_hcr_el2_eff(env); - - switch (bt) { - case 3: /* linked context ID match */ - switch (arm_current_el(env)) { - default: - /* Context matches never fire in AArch64 EL3 */ - return false; - case 2: - if (!(hcr_el2 & HCR_E2H)) { - /* Context matches never fire in EL2 without E2H enabled. */ - return false; - } - contextidr = env->cp15.contextidr_el[2]; - break; - case 1: - contextidr = env->cp15.contextidr_el[1]; - break; - case 0: - if ((hcr_el2 & (HCR_E2H | HCR_TGE)) == (HCR_E2H | HCR_TGE)) { - contextidr = env->cp15.contextidr_el[2]; - } else { - contextidr = env->cp15.contextidr_el[1]; - } - break; - } - break; - - case 7: /* linked contextidr_el1 match */ - contextidr = env->cp15.contextidr_el[1]; - break; - case 13: /* linked contextidr_el2 match */ - contextidr = env->cp15.contextidr_el[2]; - break; - - case 9: /* linked VMID match (reserved if no EL2) */ - case 11: /* linked context ID and VMID match (reserved if no EL2) */ - case 15: /* linked full context ID match */ - default: - /* - * Links to Unlinked context breakpoints must generate no - * events; we choose to do the same for reserved values too. - */ - return false; - } - - /* - * We match the whole register even if this is AArch32 using the - * short descriptor format (in which case it holds both PROCID and ASID), - * since we don't implement the optional v7 context ID masking. - */ - return contextidr == (uint32_t)env->cp15.dbgbvr[lbn]; -} - -static bool bp_wp_matches(ARMCPU *cpu, int n, bool is_wp) -{ - CPUARMState *env = &cpu->env; - uint64_t cr; - int pac, hmc, ssc, wt, lbn; - /* - * Note that for watchpoints the check is against the CPU security - * state, not the S/NS attribute on the offending data access. - */ - bool is_secure = arm_is_secure(env); - int access_el = arm_current_el(env); - - if (is_wp) { - CPUWatchpoint *wp = env->cpu_watchpoint[n]; - - if (!wp || !(wp->flags & BP_WATCHPOINT_HIT)) { - return false; - } - cr = env->cp15.dbgwcr[n]; - if (wp->hitattrs.user) { - /* - * The LDRT/STRT/LDT/STT "unprivileged access" instructions should - * match watchpoints as if they were accesses done at EL0, even if - * the CPU is at EL1 or higher. - */ - access_el = 0; - } - } else { - uint64_t pc = is_a64(env) ? env->pc : env->regs[15]; - - if (!env->cpu_breakpoint[n] || env->cpu_breakpoint[n]->pc != pc) { - return false; - } - cr = env->cp15.dbgbcr[n]; - } - /* - * The WATCHPOINT_HIT flag guarantees us that the watchpoint is - * enabled and that the address and access type match; for breakpoints - * we know the address matched; check the remaining fields, including - * linked breakpoints. We rely on WCR and BCR having the same layout - * for the LBN, SSC, HMC, PAC/PMC and is-linked fields. - * Note that some combinations of {PAC, HMC, SSC} are reserved and - * must act either like some valid combination or as if the watchpoint - * were disabled. We choose the former, and use this together with - * the fact that EL3 must always be Secure and EL2 must always be - * Non-Secure to simplify the code slightly compared to the full - * table in the ARM ARM. - */ - pac = FIELD_EX64(cr, DBGWCR, PAC); - hmc = FIELD_EX64(cr, DBGWCR, HMC); - ssc = FIELD_EX64(cr, DBGWCR, SSC); - - switch (ssc) { - case 0: - break; - case 1: - case 3: - if (is_secure) { - return false; - } - break; - case 2: - if (!is_secure) { - return false; - } - break; - } - - switch (access_el) { - case 3: - case 2: - if (!hmc) { - return false; - } - break; - case 1: - if (extract32(pac, 0, 1) == 0) { - return false; - } - break; - case 0: - if (extract32(pac, 1, 1) == 0) { - return false; - } - break; - default: - g_assert_not_reached(); - } - - wt = FIELD_EX64(cr, DBGWCR, WT); - lbn = FIELD_EX64(cr, DBGWCR, LBN); - - if (wt && !linked_bp_matches(cpu, lbn)) { - return false; - } - - return true; -} - -static bool check_watchpoints(ARMCPU *cpu) -{ - CPUARMState *env = &cpu->env; - int n; - - /* - * If watchpoints are disabled globally or we can't take debug - * exceptions here then watchpoint firings are ignored. - */ - if (extract32(env->cp15.mdscr_el1, 15, 1) == 0 - || !arm_generate_debug_exceptions(env)) { - return false; - } - - for (n = 0; n < ARRAY_SIZE(env->cpu_watchpoint); n++) { - if (bp_wp_matches(cpu, n, true)) { - return true; - } - } - return false; -} - -bool arm_debug_check_breakpoint(CPUState *cs) -{ - ARMCPU *cpu = ARM_CPU(cs); - CPUARMState *env = &cpu->env; - vaddr pc; - int n; - - /* - * If breakpoints are disabled globally or we can't take debug - * exceptions here then breakpoint firings are ignored. - */ - if (extract32(env->cp15.mdscr_el1, 15, 1) == 0 - || !arm_generate_debug_exceptions(env)) { - return false; - } - - /* - * Single-step exceptions have priority over breakpoint exceptions. - * If single-step state is active-pending, suppress the bp. - */ - if (arm_singlestep_active(env) && !(env->pstate & PSTATE_SS)) { - return false; - } - - /* - * PC alignment faults have priority over breakpoint exceptions. - */ - pc = is_a64(env) ? env->pc : env->regs[15]; - if ((is_a64(env) || !env->thumb) && (pc & 3) != 0) { - return false; - } - - /* - * Instruction aborts have priority over breakpoint exceptions. - * TODO: We would need to look up the page for PC and verify that - * it is present and executable. - */ - - for (n = 0; n < ARRAY_SIZE(env->cpu_breakpoint); n++) { - if (bp_wp_matches(cpu, n, false)) { - return true; - } - } - return false; -} - -bool arm_debug_check_watchpoint(CPUState *cs, CPUWatchpoint *wp) -{ - /* - * Called by core code when a CPU watchpoint fires; need to check if this - * is also an architectural watchpoint match. - */ - ARMCPU *cpu = ARM_CPU(cs); - - return check_watchpoints(cpu); -} - -/* - * Return the FSR value for a debug exception (watchpoint, hardware - * breakpoint or BKPT insn) targeting the specified exception level. - */ -static uint32_t arm_debug_exception_fsr(CPUARMState *env) -{ - ARMMMUFaultInfo fi = { .type = ARMFault_Debug }; - int target_el = arm_debug_target_el(env); - bool using_lpae; - - if (arm_feature(env, ARM_FEATURE_M)) { - using_lpae = false; - } else if (target_el == 2 || arm_el_is_aa64(env, target_el)) { - using_lpae = true; - } else if (arm_feature(env, ARM_FEATURE_PMSA) && - arm_feature(env, ARM_FEATURE_V8)) { - using_lpae = true; - } else if (arm_feature(env, ARM_FEATURE_LPAE) && - (env->cp15.tcr_el[target_el] & TTBCR_EAE)) { - using_lpae = true; - } else { - using_lpae = false; - } - - if (using_lpae) { - return arm_fi_to_lfsc(&fi); - } else { - return arm_fi_to_sfsc(&fi); - } -} - -void arm_debug_excp_handler(CPUState *cs) -{ - /* - * Called by core code when a watchpoint or breakpoint fires; - * need to check which one and raise the appropriate exception. - */ - ARMCPU *cpu = ARM_CPU(cs); - CPUARMState *env = &cpu->env; - CPUWatchpoint *wp_hit = cs->watchpoint_hit; - - if (wp_hit) { - if (wp_hit->flags & BP_CPU) { - bool wnr = (wp_hit->flags & BP_WATCHPOINT_HIT_WRITE) != 0; - - cs->watchpoint_hit = NULL; - - env->exception.fsr = arm_debug_exception_fsr(env); - env->exception.vaddress = wp_hit->hitaddr; - raise_exception_debug(env, EXCP_DATA_ABORT, - syn_watchpoint(0, 0, wnr)); - } - } else { - uint64_t pc = is_a64(env) ? env->pc : env->regs[15]; - - /* - * (1) GDB breakpoints should be handled first. - * (2) Do not raise a CPU exception if no CPU breakpoint has fired, - * since singlestep is also done by generating a debug internal - * exception. - */ - if (cpu_breakpoint_test(cs, pc, BP_GDB) - || !cpu_breakpoint_test(cs, pc, BP_CPU)) { - return; - } - - env->exception.fsr = arm_debug_exception_fsr(env); - /* - * FAR is UNKNOWN: clear vaddress to avoid potentially exposing - * values to the guest that it shouldn't be able to see at its - * exception/security level. - */ - env->exception.vaddress = 0; - raise_exception_debug(env, EXCP_PREFETCH_ABORT, syn_breakpoint(0)); - } -} - -/* - * Raise an EXCP_BKPT with the specified syndrome register value, - * targeting the correct exception level for debug exceptions. - */ -void HELPER(exception_bkpt_insn)(CPUARMState *env, uint32_t syndrome) -{ - int debug_el = arm_debug_target_el(env); - int cur_el = arm_current_el(env); - - /* FSR will only be used if the debug target EL is AArch32. */ - env->exception.fsr = arm_debug_exception_fsr(env); - /* - * FAR is UNKNOWN: clear vaddress to avoid potentially exposing - * values to the guest that it shouldn't be able to see at its - * exception/security level. - */ - env->exception.vaddress = 0; - /* - * Other kinds of architectural debug exception are ignored if - * they target an exception level below the current one (in QEMU - * this is checked by arm_generate_debug_exceptions()). Breakpoint - * instructions are special because they always generate an exception - * to somewhere: if they can't go to the configured debug exception - * level they are taken to the current exception level. - */ - if (debug_el < cur_el) { - debug_el = cur_el; - } - raise_exception(env, EXCP_BKPT, syndrome, debug_el); -} - -void HELPER(exception_swstep)(CPUARMState *env, uint32_t syndrome) -{ - raise_exception_debug(env, EXCP_UDEF, syndrome); -} - -void hw_watchpoint_update(ARMCPU *cpu, int n) -{ - CPUARMState *env = &cpu->env; - vaddr len = 0; - vaddr wvr = env->cp15.dbgwvr[n]; - uint64_t wcr = env->cp15.dbgwcr[n]; - int mask; - int flags = BP_CPU | BP_STOP_BEFORE_ACCESS; - - if (env->cpu_watchpoint[n]) { - cpu_watchpoint_remove_by_ref(CPU(cpu), env->cpu_watchpoint[n]); - env->cpu_watchpoint[n] = NULL; - } - - if (!FIELD_EX64(wcr, DBGWCR, E)) { - /* E bit clear : watchpoint disabled */ - return; - } - - switch (FIELD_EX64(wcr, DBGWCR, LSC)) { - case 0: - /* LSC 00 is reserved and must behave as if the wp is disabled */ - return; - case 1: - flags |= BP_MEM_READ; - break; - case 2: - flags |= BP_MEM_WRITE; - break; - case 3: - flags |= BP_MEM_ACCESS; - break; - } - - /* - * Attempts to use both MASK and BAS fields simultaneously are - * CONSTRAINED UNPREDICTABLE; we opt to ignore BAS in this case, - * thus generating a watchpoint for every byte in the masked region. - */ - mask = FIELD_EX64(wcr, DBGWCR, MASK); - if (mask == 1 || mask == 2) { - /* - * Reserved values of MASK; we must act as if the mask value was - * some non-reserved value, or as if the watchpoint were disabled. - * We choose the latter. - */ - return; - } else if (mask) { - /* Watchpoint covers an aligned area up to 2GB in size */ - len = 1ULL << mask; - /* - * If masked bits in WVR are not zero it's CONSTRAINED UNPREDICTABLE - * whether the watchpoint fires when the unmasked bits match; we opt - * to generate the exceptions. - */ - wvr &= ~(len - 1); - } else { - /* Watchpoint covers bytes defined by the byte address select bits */ - int bas = FIELD_EX64(wcr, DBGWCR, BAS); - int basstart; - - if (extract64(wvr, 2, 1)) { - /* - * Deprecated case of an only 4-aligned address. BAS[7:4] are - * ignored, and BAS[3:0] define which bytes to watch. - */ - bas &= 0xf; - } - - if (bas == 0) { - /* This must act as if the watchpoint is disabled */ - return; - } - - /* - * The BAS bits are supposed to be programmed to indicate a contiguous - * range of bytes. Otherwise it is CONSTRAINED UNPREDICTABLE whether - * we fire for each byte in the word/doubleword addressed by the WVR. - * We choose to ignore any non-zero bits after the first range of 1s. - */ - basstart = ctz32(bas); - len = cto32(bas >> basstart); - wvr += basstart; - } - - cpu_watchpoint_insert(CPU(cpu), wvr, len, flags, - &env->cpu_watchpoint[n]); -} - -void hw_watchpoint_update_all(ARMCPU *cpu) -{ - int i; - CPUARMState *env = &cpu->env; - - /* - * Completely clear out existing QEMU watchpoints and our array, to - * avoid possible stale entries following migration load. - */ - cpu_watchpoint_remove_all(CPU(cpu), BP_CPU); - memset(env->cpu_watchpoint, 0, sizeof(env->cpu_watchpoint)); - - for (i = 0; i < ARRAY_SIZE(cpu->env.cpu_watchpoint); i++) { - hw_watchpoint_update(cpu, i); - } -} - -void hw_breakpoint_update(ARMCPU *cpu, int n) -{ - CPUARMState *env = &cpu->env; - uint64_t bvr = env->cp15.dbgbvr[n]; - uint64_t bcr = env->cp15.dbgbcr[n]; - vaddr addr; - int bt; - int flags = BP_CPU; - - if (env->cpu_breakpoint[n]) { - cpu_breakpoint_remove_by_ref(CPU(cpu), env->cpu_breakpoint[n]); - env->cpu_breakpoint[n] = NULL; - } - - if (!extract64(bcr, 0, 1)) { - /* E bit clear : watchpoint disabled */ - return; - } - - bt = extract64(bcr, 20, 4); - - switch (bt) { - case 4: /* unlinked address mismatch (reserved if AArch64) */ - case 5: /* linked address mismatch (reserved if AArch64) */ - qemu_log_mask(LOG_UNIMP, - "arm: address mismatch breakpoint types not implemented\n"); - return; - case 0: /* unlinked address match */ - case 1: /* linked address match */ - { - /* - * Bits [1:0] are RES0. - * - * It is IMPLEMENTATION DEFINED whether bits [63:49] - * ([63:53] for FEAT_LVA) are hardwired to a copy of the sign bit - * of the VA field ([48] or [52] for FEAT_LVA), or whether the - * value is read as written. It is CONSTRAINED UNPREDICTABLE - * whether the RESS bits are ignored when comparing an address. - * Therefore we are allowed to compare the entire register, which - * lets us avoid considering whether FEAT_LVA is actually enabled. - * - * The BAS field is used to allow setting breakpoints on 16-bit - * wide instructions; it is CONSTRAINED UNPREDICTABLE whether - * a bp will fire if the addresses covered by the bp and the addresses - * covered by the insn overlap but the insn doesn't start at the - * start of the bp address range. We choose to require the insn and - * the bp to have the same address. The constraints on writing to - * BAS enforced in dbgbcr_write mean we have only four cases: - * 0b0000 => no breakpoint - * 0b0011 => breakpoint on addr - * 0b1100 => breakpoint on addr + 2 - * 0b1111 => breakpoint on addr - * See also figure D2-3 in the v8 ARM ARM (DDI0487A.c). - */ - int bas = extract64(bcr, 5, 4); - addr = bvr & ~3ULL; - if (bas == 0) { - return; - } - if (bas == 0xc) { - addr += 2; - } - break; - } - case 2: /* unlinked context ID match */ - case 8: /* unlinked VMID match (reserved if no EL2) */ - case 10: /* unlinked context ID and VMID match (reserved if no EL2) */ - qemu_log_mask(LOG_UNIMP, - "arm: unlinked context breakpoint types not implemented\n"); - return; - case 9: /* linked VMID match (reserved if no EL2) */ - case 11: /* linked context ID and VMID match (reserved if no EL2) */ - case 3: /* linked context ID match */ - default: - /* - * We must generate no events for Linked context matches (unless - * they are linked to by some other bp/wp, which is handled in - * updates for the linking bp/wp). We choose to also generate no events - * for reserved values. - */ - return; - } - - cpu_breakpoint_insert(CPU(cpu), addr, flags, &env->cpu_breakpoint[n]); -} - -void hw_breakpoint_update_all(ARMCPU *cpu) -{ - int i; - CPUARMState *env = &cpu->env; - - /* - * Completely clear out existing QEMU breakpoints and our array, to - * avoid possible stale entries following migration load. - */ - cpu_breakpoint_remove_all(CPU(cpu), BP_CPU); - memset(env->cpu_breakpoint, 0, sizeof(env->cpu_breakpoint)); - - for (i = 0; i < ARRAY_SIZE(cpu->env.cpu_breakpoint); i++) { - hw_breakpoint_update(cpu, i); - } -} - -#if !defined(CONFIG_USER_ONLY) - -vaddr arm_adjust_watchpoint_address(CPUState *cs, vaddr addr, int len) -{ - ARMCPU *cpu = ARM_CPU(cs); - CPUARMState *env = &cpu->env; - - /* - * In BE32 system mode, target memory is stored byteswapped (on a - * little-endian host system), and by the time we reach here (via an - * opcode helper) the addresses of subword accesses have been adjusted - * to account for that, which means that watchpoints will not match. - * Undo the adjustment here. - */ - if (arm_sctlr_b(env)) { - if (len == 1) { - addr ^= 3; - } else if (len == 2) { - addr ^= 2; - } - } - - return addr; -} - -#endif /* !CONFIG_USER_ONLY */ -#endif /* CONFIG_TCG */ - /* * Check for traps to "powerdown debug" registers, which are controlled * by MDCR.TDOSA diff --git a/target/arm/gdbstub64.c b/target/arm/gdbstub64.c index c584e5b4e69d..b71666c3a1d1 100644 --- a/target/arm/gdbstub64.c +++ b/target/arm/gdbstub64.c @@ -158,7 +158,7 @@ int aarch64_gdb_get_sve_reg(CPUState *cs, GByteArray *buf, int reg) case 0 ... 31: { int vq, len = 0; - for (vq = 0; vq < cpu->sve_max_vq; vq++) { + for (vq = 0; vq < arm_max_vq(cpu); vq++) { len += gdb_get_reg128(buf, env->vfp.zregs[reg].d[vq * 2 + 1], env->vfp.zregs[reg].d[vq * 2]); @@ -174,7 +174,7 @@ int aarch64_gdb_get_sve_reg(CPUState *cs, GByteArray *buf, int reg) { int preg = reg - 34; int vq, len = 0; - for (vq = 0; vq < cpu->sve_max_vq; vq = vq + 4) { + for (vq = 0; vq < arm_max_vq(cpu); vq = vq + 4) { len += gdb_get_reg64(buf, env->vfp.pregs[preg].p[vq / 4]); } return len; @@ -208,7 +208,7 @@ int aarch64_gdb_set_sve_reg(CPUState *cs, uint8_t *buf, int reg) case 0 ... 31: { int vq, len = 0; - for (vq = 0; vq < cpu->sve_max_vq; vq++) { + for (vq = 0; vq < arm_max_vq(cpu); vq++) { if (target_big_endian()) { env->vfp.zregs[reg].d[vq * 2 + 1] = ldq_p(buf); buf += 8; @@ -233,7 +233,7 @@ int aarch64_gdb_set_sve_reg(CPUState *cs, uint8_t *buf, int reg) { int preg = reg - 34; int vq, len = 0; - for (vq = 0; vq < cpu->sve_max_vq; vq = vq + 4) { + for (vq = 0; vq < arm_max_vq(cpu); vq = vq + 4) { env->vfp.pregs[preg].p[vq / 4] = ldq_p(buf); buf += 8; len += 8; @@ -540,8 +540,8 @@ static void output_vector_union_type(GDBFeatureBuilder *builder, int reg_width, GDBFeature *arm_gen_dynamic_svereg_feature(CPUState *cs, int base_reg) { ARMCPU *cpu = ARM_CPU(cs); - int reg_width = cpu->sve_max_vq * 128; - int pred_width = cpu->sve_max_vq * 16; + int reg_width = arm_max_vq(cpu) * 128; + int pred_width = arm_max_vq(cpu) * 16; GDBFeatureBuilder builder; char *name; int reg = 0; diff --git a/target/arm/helper-a64.h b/target/arm/helper-a64.h new file mode 100644 index 000000000000..cda7e039b72a --- /dev/null +++ b/target/arm/helper-a64.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef HELPER_A64_H +#define HELPER_A64_H + +#include "exec/helper-proto-common.h" +#include "exec/helper-gen-common.h" + +#define HELPER_H "tcg/helper-a64-defs.h" +#include "exec/helper-proto.h.inc" +#include "exec/helper-gen.h.inc" +#undef HELPER_H + +#endif /* HELPER_A64_H */ diff --git a/target/arm/helper-mve.h b/target/arm/helper-mve.h new file mode 100644 index 000000000000..32ef3f646613 --- /dev/null +++ b/target/arm/helper-mve.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef HELPER_MVE_H +#define HELPER_MVE_H + +#include "exec/helper-proto-common.h" +#include "exec/helper-gen-common.h" + +#define HELPER_H "tcg/helper-mve-defs.h" +#include "exec/helper-proto.h.inc" +#include "exec/helper-gen.h.inc" +#undef HELPER_H + +#endif /* HELPER_MVE_H */ diff --git a/target/arm/helper-sme.h b/target/arm/helper-sme.h new file mode 100644 index 000000000000..27c85fdeef1e --- /dev/null +++ b/target/arm/helper-sme.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef HELPER_SME_H +#define HELPER_SME_H + +#include "exec/helper-proto-common.h" +#include "exec/helper-gen-common.h" + +#define HELPER_H "tcg/helper-sme-defs.h" +#include "exec/helper-proto.h.inc" +#include "exec/helper-gen.h.inc" +#undef HELPER_H + +#endif /* HELPER_SME_H */ diff --git a/target/arm/helper-sve.h b/target/arm/helper-sve.h new file mode 100644 index 000000000000..ae4f46c70a00 --- /dev/null +++ b/target/arm/helper-sve.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef HELPER_SVE_H +#define HELPER_SVE_H + +#include "exec/helper-proto-common.h" +#include "exec/helper-gen-common.h" + +#define HELPER_H "tcg/helper-sve-defs.h" +#include "exec/helper-proto.h.inc" +#include "exec/helper-gen.h.inc" +#undef HELPER_H + +#endif /* HELPER_SVE_H */ diff --git a/target/arm/helper.c b/target/arm/helper.c index 8c5769477cf1..7389f2988c44 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -36,9 +36,6 @@ #include "target/arm/gtimer.h" #include "qemu/plugin.h" -#define HELPER_H "tcg/helper.h" -#include "exec/helper-proto.h.inc" - static void switch_mode(CPUARMState *env, int mode); int compare_u64(const void *a, const void *b) @@ -268,15 +265,10 @@ void arm_init_cpreg_list(ARMCPU *cpu) if (arraylen) { cpu->cpreg_indexes = g_new(uint64_t, arraylen); cpu->cpreg_values = g_new(uint64_t, arraylen); - cpu->cpreg_vmstate_indexes = g_new(uint64_t, arraylen); - cpu->cpreg_vmstate_values = g_new(uint64_t, arraylen); } else { cpu->cpreg_indexes = NULL; cpu->cpreg_values = NULL; - cpu->cpreg_vmstate_indexes = NULL; - cpu->cpreg_vmstate_values = NULL; } - cpu->cpreg_vmstate_array_len = arraylen; cpu->cpreg_array_len = 0; g_hash_table_foreach(cpu->cp_regs, add_cpreg_to_list, cpu); @@ -4775,7 +4767,7 @@ int sme_exception_el(CPUARMState *env, int el) } /* - * Given that SVE is enabled, return the vector length for EL. + * Given that SVE or SME is enabled, return the vector length for EL. */ uint32_t sve_vqm1_for_el_sm(CPUARMState *env, int el, bool sm) { @@ -4787,6 +4779,12 @@ uint32_t sve_vqm1_for_el_sm(CPUARMState *env, int el, bool sm) if (sm) { cr = env->vfp.smcr_el; map = cpu->sme_vq.map; + } else if (map == 0) { + /* + * SME-only CPU not in streaming mode: effective VL + * is 128 bits, per R_KXKNK. + */ + return 0; } if (el <= 1 && !el_is_in_host(env, el)) { @@ -9473,7 +9471,7 @@ static void arm_cpu_do_interrupt_aarch64(CPUState *cs) aarch64_restore_sp(env, new_el); if (tcg_enabled()) { - helper_rebuild_hflags_a64(env, new_el); + arm_rebuild_hflags(env); } env->pc = addr; @@ -10088,7 +10086,7 @@ void aarch64_sve_narrow_vq(CPUARMState *env, unsigned vq) uint64_t pmask; assert(vq >= 1 && vq <= ARM_MAX_VQ); - assert(vq <= env_archcpu(env)->sve_max_vq); + assert(vq <= arm_max_vq(env_archcpu(env))); /* Zap the high bits of the zregs. */ for (i = 0; i < 32; i++) { @@ -10133,8 +10131,8 @@ void aarch64_sve_change_el(CPUARMState *env, int old_el, int old_len, new_len; bool old_a64, new_a64, sm; - /* Nothing to do if no SVE. */ - if (!cpu_isar_feature(aa64_sve, cpu)) { + /* Nothing to do if no SVE or SME. */ + if (!cpu_isar_feature(aa64_sve, cpu) && !cpu_isar_feature(aa64_sme, cpu)) { return; } diff --git a/target/arm/helper.h b/target/arm/helper.h index f340a49a28a5..b1c26c180ea4 100644 --- a/target/arm/helper.h +++ b/target/arm/helper.h @@ -1,11 +1,14 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include "tcg/helper.h" +#ifndef HELPER__H +#define HELPER__H -#ifdef TARGET_AARCH64 -#include "tcg/helper-a64.h" -#include "tcg/helper-sve.h" -#include "tcg/helper-sme.h" -#endif +#include "exec/helper-proto-common.h" +#include "exec/helper-gen-common.h" -#include "tcg/helper-mve.h" +#define HELPER_H "tcg/helper-defs.h" +#include "exec/helper-proto.h.inc" +#include "exec/helper-gen.h.inc" +#undef HELPER_H + +#endif /* HELPER__H */ diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c index 1b19d9713edd..5fc8f6bbbd98 100644 --- a/target/arm/hvf/hvf.c +++ b/target/arm/hvf/hvf.c @@ -200,9 +200,6 @@ void hvf_arm_init_debug(void) #define SYSREG_PMCEID0_EL0 SYSREG(3, 3, 9, 12, 6) #define SYSREG_PMCEID1_EL0 SYSREG(3, 3, 9, 12, 7) #define SYSREG_PMCCNTR_EL0 SYSREG(3, 3, 9, 13, 0) - -#define SYSREG_CNTV_CTL_EL0 SYSREG(3, 3, 14, 3, 1) -#define SYSREG_CNTV_CVAL_EL0 SYSREG(3, 3, 14, 3, 2) #define SYSREG_PMCCFILTR_EL0 SYSREG(3, 3, 14, 15, 7) #define SYSREG_ICC_AP0R0_EL1 SYSREG(3, 0, 12, 8, 4) @@ -318,6 +315,7 @@ typedef struct ARMHostCPUFeatures { uint64_t features; uint64_t midr; uint32_t reset_sctlr; + uint32_t sme_vq_supported; const char *dtb_compatible; } ARMHostCPUFeatures; @@ -398,6 +396,60 @@ static const struct hvf_reg_match hvf_fpreg_match[] = { { HV_SIMD_FP_REG_Q31, offsetof(CPUARMState, vfp.zregs[31]) }, }; +static const struct hvf_reg_match hvf_sme2_zreg_match[] = { + { HV_SME_Z_REG_0, offsetof(CPUARMState, vfp.zregs[0]) }, + { HV_SME_Z_REG_1, offsetof(CPUARMState, vfp.zregs[1]) }, + { HV_SME_Z_REG_2, offsetof(CPUARMState, vfp.zregs[2]) }, + { HV_SME_Z_REG_3, offsetof(CPUARMState, vfp.zregs[3]) }, + { HV_SME_Z_REG_4, offsetof(CPUARMState, vfp.zregs[4]) }, + { HV_SME_Z_REG_5, offsetof(CPUARMState, vfp.zregs[5]) }, + { HV_SME_Z_REG_6, offsetof(CPUARMState, vfp.zregs[6]) }, + { HV_SME_Z_REG_7, offsetof(CPUARMState, vfp.zregs[7]) }, + { HV_SME_Z_REG_8, offsetof(CPUARMState, vfp.zregs[8]) }, + { HV_SME_Z_REG_9, offsetof(CPUARMState, vfp.zregs[9]) }, + { HV_SME_Z_REG_10, offsetof(CPUARMState, vfp.zregs[10]) }, + { HV_SME_Z_REG_11, offsetof(CPUARMState, vfp.zregs[11]) }, + { HV_SME_Z_REG_12, offsetof(CPUARMState, vfp.zregs[12]) }, + { HV_SME_Z_REG_13, offsetof(CPUARMState, vfp.zregs[13]) }, + { HV_SME_Z_REG_14, offsetof(CPUARMState, vfp.zregs[14]) }, + { HV_SME_Z_REG_15, offsetof(CPUARMState, vfp.zregs[15]) }, + { HV_SME_Z_REG_16, offsetof(CPUARMState, vfp.zregs[16]) }, + { HV_SME_Z_REG_17, offsetof(CPUARMState, vfp.zregs[17]) }, + { HV_SME_Z_REG_18, offsetof(CPUARMState, vfp.zregs[18]) }, + { HV_SME_Z_REG_19, offsetof(CPUARMState, vfp.zregs[19]) }, + { HV_SME_Z_REG_20, offsetof(CPUARMState, vfp.zregs[20]) }, + { HV_SME_Z_REG_21, offsetof(CPUARMState, vfp.zregs[21]) }, + { HV_SME_Z_REG_22, offsetof(CPUARMState, vfp.zregs[22]) }, + { HV_SME_Z_REG_23, offsetof(CPUARMState, vfp.zregs[23]) }, + { HV_SME_Z_REG_24, offsetof(CPUARMState, vfp.zregs[24]) }, + { HV_SME_Z_REG_25, offsetof(CPUARMState, vfp.zregs[25]) }, + { HV_SME_Z_REG_26, offsetof(CPUARMState, vfp.zregs[26]) }, + { HV_SME_Z_REG_27, offsetof(CPUARMState, vfp.zregs[27]) }, + { HV_SME_Z_REG_28, offsetof(CPUARMState, vfp.zregs[28]) }, + { HV_SME_Z_REG_29, offsetof(CPUARMState, vfp.zregs[29]) }, + { HV_SME_Z_REG_30, offsetof(CPUARMState, vfp.zregs[30]) }, + { HV_SME_Z_REG_31, offsetof(CPUARMState, vfp.zregs[31]) }, +}; + +static const struct hvf_reg_match hvf_sme2_preg_match[] = { + { HV_SME_P_REG_0, offsetof(CPUARMState, vfp.pregs[0]) }, + { HV_SME_P_REG_1, offsetof(CPUARMState, vfp.pregs[1]) }, + { HV_SME_P_REG_2, offsetof(CPUARMState, vfp.pregs[2]) }, + { HV_SME_P_REG_3, offsetof(CPUARMState, vfp.pregs[3]) }, + { HV_SME_P_REG_4, offsetof(CPUARMState, vfp.pregs[4]) }, + { HV_SME_P_REG_5, offsetof(CPUARMState, vfp.pregs[5]) }, + { HV_SME_P_REG_6, offsetof(CPUARMState, vfp.pregs[6]) }, + { HV_SME_P_REG_7, offsetof(CPUARMState, vfp.pregs[7]) }, + { HV_SME_P_REG_8, offsetof(CPUARMState, vfp.pregs[8]) }, + { HV_SME_P_REG_9, offsetof(CPUARMState, vfp.pregs[9]) }, + { HV_SME_P_REG_10, offsetof(CPUARMState, vfp.pregs[10]) }, + { HV_SME_P_REG_11, offsetof(CPUARMState, vfp.pregs[11]) }, + { HV_SME_P_REG_12, offsetof(CPUARMState, vfp.pregs[12]) }, + { HV_SME_P_REG_13, offsetof(CPUARMState, vfp.pregs[13]) }, + { HV_SME_P_REG_14, offsetof(CPUARMState, vfp.pregs[14]) }, + { HV_SME_P_REG_15, offsetof(CPUARMState, vfp.pregs[15]) }, +}; + /* * QEMU uses KVM system register ids in the migration format. * Conveniently, HVF uses the same encoding of the op* and cr* parameters @@ -409,22 +461,201 @@ static const struct hvf_reg_match hvf_fpreg_match[] = { #define HVF_TO_KVMID(HVF) \ (CP_REG_ARM64 | CP_REG_SIZE_U64 | CP_REG_ARM64_SYSREG | (HVF)) -/* Verify this at compile-time. */ +/* + * Verify this at compile-time. + * + * SME2 registers are guarded by a runtime availability attribute instead of a + * compile-time def, so verify those at runtime in hvf_arch_init_vcpu() below. + */ #define DEF_SYSREG(HVF_ID, ...) \ QEMU_BUILD_BUG_ON(HVF_ID != KVMID_TO_HVF(KVMID_AA64_SYS_REG64(__VA_ARGS__))); +#define DEF_SYSREG_15_02(...) #include "sysreg.c.inc" #undef DEF_SYSREG +#undef DEF_SYSREG_15_02 #define DEF_SYSREG(HVF_ID, op0, op1, crn, crm, op2) HVF_ID, +#define DEF_SYSREG_15_02(...) static const hv_sys_reg_t hvf_sreg_list[] = { #include "sysreg.c.inc" }; #undef DEF_SYSREG +#undef DEF_SYSREG_15_02 + +#define DEF_SYSREG(...) +#define DEF_SYSREG_15_02(HVF_ID, op0, op1, crn, crm, op2) HVF_ID, + +API_AVAILABLE(macos(15.2)) +static const hv_sys_reg_t hvf_sreg_list_sme2[] = { +#include "sysreg.c.inc" +}; + +#undef DEF_SYSREG +#undef DEF_SYSREG_15_02 + +/* + * For FEAT_SME2 migration, we need to store PSTATE.{SM,ZA} bits which are + * accessible with the SVCR pseudo-register. However, in the HVF API this is + * not exposed as a system-register (i.e. HVF_SYS_REG_SVCR) but a custom + * struct, hv_vcpu_sme_state_t. So we need to define our own KVMID in order to + * store it in cpreg_values and make it migrateable. + */ +#define SVCR KVMID_AA64_SYS_REG64(3, 3, 4, 2, 2) + +API_AVAILABLE(macos(15.2)) +static void hvf_arch_put_sme(CPUState *cpu) +{ + ARMCPU *arm_cpu = ARM_CPU(cpu); + CPUARMState *env = &arm_cpu->env; + const size_t svl_bytes = hvf_arm_sme2_get_svl(); + const size_t z_size = svl_bytes; + const size_t preg_size = DIV_ROUND_UP(z_size, 8); + const size_t za_size = svl_bytes * svl_bytes; + hv_vcpu_sme_state_t sme_state = { 0 }; + hv_return_t ret; + uint64_t svcr; + int n; + + /* + * Set PSTATE.{SM,ZA} bits + */ + svcr = arm_cpu->cpreg_values[arm_cpu->cpreg_array_len - 1]; + env->svcr = svcr; + + /* + * Construct SVCR (PSTATE.{SM,ZA}) state to pass to HVF: + */ + sme_state.streaming_sve_mode_enabled = FIELD_EX64(env->svcr, SVCR, SM) > 0; + sme_state.za_storage_enabled = FIELD_EX64(env->svcr, SVCR, ZA) > 0; + ret = hv_vcpu_set_sme_state(cpu->accel->fd, &sme_state); + assert_hvf_ok(ret); + + /* + * We only care about Z/P registers if we're in streaming SVE mode, i.e. + * PSTATE.SM is set, because only then can instructions that access them be + * used. We don't care about the register values otherwise. This is because + * when the processing unit exits/enters this mode, it zeroes out those + * registers. + */ + if (sme_state.streaming_sve_mode_enabled) { + for (n = 0; n < ARRAY_SIZE(hvf_sme2_zreg_match); ++n) { + ret = hv_vcpu_set_sme_z_reg(cpu->accel->fd, + hvf_sme2_zreg_match[n].reg, + (uint8_t *)&env->vfp.zregs[n].d[0], + z_size); + assert_hvf_ok(ret); + } + + for (n = 0; n < ARRAY_SIZE(hvf_sme2_preg_match); ++n) { + ret = hv_vcpu_set_sme_p_reg(cpu->accel->fd, + hvf_sme2_preg_match[n].reg, + (uint8_t *)&env->vfp.pregs[n].p[0], + preg_size); + assert_hvf_ok(ret); + } + } + + /* + * If PSTATE.ZA bit is set then ZA and ZT0 are valid, otherwise they are + * zeroed out. + */ + if (sme_state.za_storage_enabled) { + hv_sme_zt0_uchar64_t tmp = { 0 }; + + memcpy(&tmp, &env->za_state.zt0, 64); + ret = hv_vcpu_set_sme_zt0_reg(cpu->accel->fd, &tmp); + assert_hvf_ok(ret); + + ret = hv_vcpu_set_sme_za_reg(cpu->accel->fd, + (uint8_t *)&env->za_state.za, + za_size); + assert_hvf_ok(ret); + } + + return; +} + +API_AVAILABLE(macos(15.2)) +static void hvf_arch_get_sme(CPUState *cpu) +{ + ARMCPU *arm_cpu = ARM_CPU(cpu); + CPUARMState *env = &arm_cpu->env; + const size_t svl_bytes = hvf_arm_sme2_get_svl(); + const size_t z_size = svl_bytes; + const size_t preg_size = DIV_ROUND_UP(z_size, 8); + const size_t za_size = svl_bytes * svl_bytes; + hv_vcpu_sme_state_t sme_state = { 0 }; + hv_return_t ret; + uint64_t svcr; + int n; + + /* + * Get SVCR (PSTATE.{SM,ZA}) state from HVF: + */ + ret = hv_vcpu_get_sme_state(cpu->accel->fd, &sme_state); + assert_hvf_ok(ret); + + /* + * Set SVCR first because changing it will zero out Z/P regs + */ + svcr = + (sme_state.za_storage_enabled ? R_SVCR_ZA_MASK : 0) + | (sme_state.streaming_sve_mode_enabled ? R_SVCR_SM_MASK : 0); + + aarch64_set_svcr(env, svcr, R_SVCR_ZA_MASK | R_SVCR_SM_MASK); + arm_cpu->cpreg_values[arm_cpu->cpreg_array_len - 1] = svcr; + + /* + * We only care about Z/P registers if we're in streaming SVE mode, i.e. + * PSTATE.SM is set, because only then can instructions that access them be + * used. We don't care about the register values otherwise. This is because + * when the processing unit exits/enters this mode, it zeroes out those + * registers. + */ + if (sme_state.streaming_sve_mode_enabled) { + for (n = 0; n < ARRAY_SIZE(hvf_sme2_zreg_match); ++n) { + ret = hv_vcpu_get_sme_z_reg(cpu->accel->fd, + hvf_sme2_zreg_match[n].reg, + (uint8_t *)&env->vfp.zregs[n].d[0], + z_size); + assert_hvf_ok(ret); + } + + for (n = 0; n < ARRAY_SIZE(hvf_sme2_preg_match); ++n) { + ret = hv_vcpu_get_sme_p_reg(cpu->accel->fd, + hvf_sme2_preg_match[n].reg, + (uint8_t *)&env->vfp.pregs[n].p[0], + preg_size); + assert_hvf_ok(ret); + } + } + + /* + * If PSTATE.ZA bit is set then ZA and ZT0 are valid, otherwise they are + * zeroed out. + */ + if (sme_state.za_storage_enabled) { + hv_sme_zt0_uchar64_t tmp = { 0 }; + + /* Get ZT0 in a tmp vector, and then copy it to env.za_state.zt0 */ + ret = hv_vcpu_get_sme_zt0_reg(cpu->accel->fd, &tmp); + assert_hvf_ok(ret); + + memcpy(&env->za_state.zt0, &tmp, 64); + ret = hv_vcpu_get_sme_za_reg(cpu->accel->fd, + (uint8_t *)&env->za_state.za, + za_size); + assert_hvf_ok(ret); + + } + + return; +} static uint32_t hvf_reg2cp_reg(uint32_t reg) { @@ -505,7 +736,6 @@ int hvf_arch_get_registers(CPUState *cpu) uint64_t val; hv_simd_fp_uchar16_t fpval; int i, n; - bool b; for (i = 0; i < ARRAY_SIZE(hvf_reg_match); i++) { ret = hv_vcpu_get_reg(cpu->accel->fd, hvf_reg_match[i].reg, &val); @@ -538,6 +768,10 @@ int hvf_arch_get_registers(CPUState *cpu) uint64_t kvm_id = arm_cpu->cpreg_indexes[i]; int hvf_id = KVMID_TO_HVF(kvm_id); + if (kvm_id == HVF_TO_KVMID(SVCR)) { + continue; + } + if (cpu->accel->guest_debug_enabled) { /* Handle debug registers */ switch (hvf_id) { @@ -631,20 +865,17 @@ int hvf_arch_get_registers(CPUState *cpu) arm_cpu->cpreg_values[i] = val; } + if (cpu_isar_feature(aa64_sme, arm_cpu)) { + if (__builtin_available(macOS 15.2, *)) { + hvf_arch_get_sme(cpu); + } else { + g_assert_not_reached(); + } + } assert(write_list_to_cpustate(arm_cpu)); aarch64_restore_sp(env, arm_current_el(env)); - ret = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CVAL_EL0, &val); - assert_hvf_ok(ret); - b = hvf_sysreg_write_cp(cpu, "VTimer", SYSREG_CNTV_CVAL_EL0, val); - assert(b); - - ret = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CTL_EL0, &val); - assert_hvf_ok(ret); - b = hvf_sysreg_write_cp(cpu, "VTimer", SYSREG_CNTV_CTL_EL0, val); - assert(b); - return 0; } @@ -656,7 +887,18 @@ int hvf_arch_put_registers(CPUState *cpu) uint64_t val; hv_simd_fp_uchar16_t fpval; int i, n; - bool b; + + /* + * Set SVCR first because changing it will zero out Z/P (including NEON) + * regs + */ + if (cpu_isar_feature(aa64_sme, arm_cpu)) { + if (__builtin_available(macOS 15.2, *)) { + hvf_arch_put_sme(cpu); + } else { + g_assert_not_reached(); + } + } for (i = 0; i < ARRAY_SIZE(hvf_reg_match); i++) { val = *(uint64_t *)((void *)env + hvf_reg_match[i].offset); @@ -687,6 +929,10 @@ int hvf_arch_put_registers(CPUState *cpu) uint64_t kvm_id = arm_cpu->cpreg_indexes[i]; int hvf_id = KVMID_TO_HVF(kvm_id); + if (kvm_id == HVF_TO_KVMID(SVCR)) { + continue; + } + if (cpu->accel->guest_debug_enabled) { /* Handle debug registers */ switch (hvf_id) { @@ -771,16 +1017,6 @@ int hvf_arch_put_registers(CPUState *cpu) ret = hv_vcpu_set_vtimer_offset(cpu->accel->fd, hvf_state->vtimer_offset); assert_hvf_ok(ret); - b = hvf_sysreg_read_cp(cpu, "VTimer", SYSREG_CNTV_CVAL_EL0, &val); - assert(b); - ret = hv_vcpu_set_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CVAL_EL0, val); - assert_hvf_ok(ret); - - b = hvf_sysreg_read_cp(cpu, "VTimer", SYSREG_CNTV_CTL_EL0, &val); - assert(b); - ret = hv_vcpu_set_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CTL_EL0, val); - assert_hvf_ok(ret); - return 0; } @@ -871,6 +1107,24 @@ static bool hvf_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf) r |= hv_vcpu_config_get_feature_reg(config, regs[i].reg, &host_isar.idregs[regs[i].index]); } + + if (__builtin_available(macOS 15.2, *)) { + static const struct sme_isar_regs { + hv_feature_reg_t reg; + ARMIDRegisterIdx index; + } sme_regs[] = { + { HV_FEATURE_REG_ID_AA64SMFR0_EL1, ID_AA64SMFR0_EL1_IDX }, + { HV_FEATURE_REG_ID_AA64ZFR0_EL1, ID_AA64ZFR0_EL1_IDX }, + }; + + if (hvf_arm_sme2_supported()) { + for (i = 0; i < ARRAY_SIZE(sme_regs); i++) { + r |= hv_vcpu_config_get_feature_reg(config, sme_regs[i].reg, + &host_isar.idregs[sme_regs[i].index]); + } + } + } + os_release(config); /* @@ -886,19 +1140,6 @@ static bool hvf_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf) clamp_id_aa64mmfr0_parange_to_ipa_size(&host_isar); - /* - * Disable SME, which is not properly handled by QEMU hvf yet. - * To allow this through we would need to: - * - make sure that the SME state is correctly handled in the - * get_registers/put_registers functions - * - get the SME-specific CPU properties to work with accelerators - * other than TCG - * - fix any assumptions we made that SME implies SVE (since - * on the M4 there is SME but not SVE) - */ - SET_IDREG(&host_isar, ID_AA64PFR1, - GET_IDREG(&host_isar, ID_AA64PFR1) & ~R_ID_AA64PFR1_SME_MASK); - ahcf->isar = host_isar; /* @@ -913,6 +1154,8 @@ static bool hvf_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf) */ ahcf->reset_sctlr |= 0x00800000; + ahcf->sme_vq_supported = hvf_arm_sme2_supported() ? hvf_arm_sme2_get_svl() : 0; + /* Make sure we don't advertise AArch32 support for EL0/EL1 */ if ((GET_IDREG(&host_isar, ID_AA64PFR0) & 0xff) != 0x11) { return false; @@ -964,6 +1207,7 @@ void hvf_arm_set_cpu_features_from_host(ARMCPU *cpu) cpu->env.features = arm_host_cpu_features.features; cpu->midr = arm_host_cpu_features.midr; cpu->reset_sctlr = arm_host_cpu_features.reset_sctlr; + cpu->sme_vq.supported = arm_host_cpu_features.sme_vq_supported; } void hvf_arch_vcpu_destroy(CPUState *cpu) @@ -1010,6 +1254,20 @@ int hvf_arch_init_vcpu(CPUState *cpu) hv_return_t ret; int i; + if (__builtin_available(macOS 15.2, *)) { + if (hvf_arm_sme2_supported()) { + sregs_match_len += ARRAY_SIZE(hvf_sreg_list_sme2) + 1; + } + +#define DEF_SYSREG_15_02(HVF_ID, ...) \ + g_assert(HVF_ID == KVMID_TO_HVF(KVMID_AA64_SYS_REG64(__VA_ARGS__))); +#define DEF_SYSREG(...) + +#include "sysreg.c.inc" + +#undef DEF_SYSREG +#undef DEF_SYSREG_15_02 + } env->aarch64 = true; /* system count frequency sanity check */ @@ -1030,7 +1288,7 @@ int hvf_arch_init_vcpu(CPUState *cpu) memset(arm_cpu->cpreg_values, 0, sregs_match_len * sizeof(uint64_t)); /* Populate cp list for all known sysregs */ - for (i = 0; i < sregs_match_len; i++) { + for (i = 0; i < ARRAY_SIZE(hvf_sreg_list); i++) { hv_sys_reg_t hvf_id = hvf_sreg_list[i]; uint64_t kvm_id = HVF_TO_KVMID(hvf_id); uint32_t key = kvm_to_cpreg_id(kvm_id); @@ -1041,6 +1299,26 @@ int hvf_arch_init_vcpu(CPUState *cpu) arm_cpu->cpreg_indexes[sregs_cnt++] = kvm_id; } } + if (__builtin_available(macOS 15.2, *)) { + if (hvf_arm_sme2_supported()) { + for (i = 0; i < ARRAY_SIZE(hvf_sreg_list_sme2); i++) { + hv_sys_reg_t hvf_id = hvf_sreg_list_sme2[i]; + uint64_t kvm_id = HVF_TO_KVMID(hvf_id); + uint32_t key = kvm_to_cpreg_id(kvm_id); + const ARMCPRegInfo *ri = get_arm_cp_reginfo(arm_cpu->cp_regs, key); + + if (ri) { + assert(!(ri->type & ARM_CP_NO_RAW)); + arm_cpu->cpreg_indexes[sregs_cnt++] = kvm_id; + } + } + /* + * Add SVCR last. It is elsewhere assumed its index is after + * hvf_sreg_list and hvf_sreg_list_sme2. + */ + arm_cpu->cpreg_indexes[sregs_cnt++] = HVF_TO_KVMID(SVCR); + } + } arm_cpu->cpreg_array_len = sregs_cnt; arm_cpu->cpreg_vmstate_array_len = sregs_cnt; @@ -1074,6 +1352,7 @@ int hvf_arch_init_vcpu(CPUState *cpu) arm_cpu->isar.idregs[ID_AA64MMFR0_EL1_IDX]); assert_hvf_ok(ret); + aarch64_add_sme_properties(OBJECT(cpu)); return 0; } diff --git a/target/arm/hvf/hvf_sme_stubs.h b/target/arm/hvf/hvf_sme_stubs.h new file mode 100644 index 000000000000..43686f782431 --- /dev/null +++ b/target/arm/hvf/hvf_sme_stubs.h @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +typedef int32_t hv_return_t; +typedef uint64_t hv_vcpu_t; + +static inline bool hvf_arm_sme2_supported(void) +{ + return false; +} + +static inline uint32_t hvf_arm_sme2_get_svl(void) +{ + g_assert_not_reached(); +} + +typedef enum hv_sme_p_reg_t { + HV_SME_P_REG_0, + HV_SME_P_REG_1, + HV_SME_P_REG_2, + HV_SME_P_REG_3, + HV_SME_P_REG_4, + HV_SME_P_REG_5, + HV_SME_P_REG_6, + HV_SME_P_REG_7, + HV_SME_P_REG_8, + HV_SME_P_REG_9, + HV_SME_P_REG_10, + HV_SME_P_REG_11, + HV_SME_P_REG_12, + HV_SME_P_REG_13, + HV_SME_P_REG_14, + HV_SME_P_REG_15, +} hv_sme_p_reg_t; + +/* + * The system version of this type declares it with + * __attribute__((ext_vector_type(64))) + * However, that is clang specific and not supported by GCC. + * Since these headers are only here for the case where the system + * headers do not provide these types (including both older macos + * and non-macos hosts), we don't need to make the type match + * exactly, so we declare it as a uint8_t array. + */ +typedef uint8_t hv_sme_zt0_uchar64_t[64]; + +typedef enum hv_sme_z_reg_t { + HV_SME_Z_REG_0, + HV_SME_Z_REG_1, + HV_SME_Z_REG_2, + HV_SME_Z_REG_3, + HV_SME_Z_REG_4, + HV_SME_Z_REG_5, + HV_SME_Z_REG_6, + HV_SME_Z_REG_7, + HV_SME_Z_REG_8, + HV_SME_Z_REG_9, + HV_SME_Z_REG_10, + HV_SME_Z_REG_11, + HV_SME_Z_REG_12, + HV_SME_Z_REG_13, + HV_SME_Z_REG_14, + HV_SME_Z_REG_15, + HV_SME_Z_REG_16, + HV_SME_Z_REG_17, + HV_SME_Z_REG_18, + HV_SME_Z_REG_19, + HV_SME_Z_REG_20, + HV_SME_Z_REG_21, + HV_SME_Z_REG_22, + HV_SME_Z_REG_23, + HV_SME_Z_REG_24, + HV_SME_Z_REG_25, + HV_SME_Z_REG_26, + HV_SME_Z_REG_27, + HV_SME_Z_REG_28, + HV_SME_Z_REG_29, + HV_SME_Z_REG_30, + HV_SME_Z_REG_31, +} hv_sme_z_reg_t; + +enum { + HV_SYS_REG_SMCR_EL1, + HV_SYS_REG_SMPRI_EL1, + HV_SYS_REG_TPIDR2_EL0, + HV_SYS_REG_ID_AA64ZFR0_EL1, + HV_SYS_REG_ID_AA64SMFR0_EL1, +}; + +enum { + HV_FEATURE_REG_ID_AA64SMFR0_EL1, + HV_FEATURE_REG_ID_AA64ZFR0_EL1, +}; + +typedef struct { + bool streaming_sve_mode_enabled; + bool za_storage_enabled; +} hv_vcpu_sme_state_t; + +static inline hv_return_t hv_sme_config_get_max_svl_bytes(size_t *value) +{ + g_assert_not_reached(); +} + +static inline hv_return_t hv_vcpu_get_sme_state(hv_vcpu_t vcpu, + hv_vcpu_sme_state_t *sme_state) +{ + g_assert_not_reached(); +} + +static inline hv_return_t hv_vcpu_set_sme_state(hv_vcpu_t vcpu, + const hv_vcpu_sme_state_t *sme_state) +{ + g_assert_not_reached(); +} + +static inline hv_return_t hv_vcpu_get_sme_z_reg(hv_vcpu_t vcpu, + hv_sme_z_reg_t reg, + uint8_t *value, + size_t length) +{ + g_assert_not_reached(); +} + +static inline hv_return_t hv_vcpu_set_sme_z_reg(hv_vcpu_t vcpu, + hv_sme_z_reg_t reg, + const uint8_t *value, + size_t length) +{ + g_assert_not_reached(); +} + +static inline hv_return_t hv_vcpu_get_sme_p_reg(hv_vcpu_t vcpu, + hv_sme_p_reg_t reg, + uint8_t *value, + size_t length) +{ + g_assert_not_reached(); +} + +static inline hv_return_t hv_vcpu_set_sme_p_reg(hv_vcpu_t vcpu, + hv_sme_p_reg_t reg, + const uint8_t *value, + size_t length) +{ + g_assert_not_reached(); +} + +static inline hv_return_t hv_vcpu_get_sme_za_reg(hv_vcpu_t vcpu, + uint8_t *value, + size_t length) +{ + g_assert_not_reached(); +} + +static inline hv_return_t hv_vcpu_set_sme_za_reg(hv_vcpu_t vcpu, + const uint8_t *value, + size_t length) +{ + g_assert_not_reached(); +} + +static inline hv_return_t hv_vcpu_get_sme_zt0_reg(hv_vcpu_t vcpu, + hv_sme_zt0_uchar64_t *value) +{ + g_assert_not_reached(); +} + +static inline hv_return_t hv_vcpu_set_sme_zt0_reg(hv_vcpu_t vcpu, + const hv_sme_zt0_uchar64_t *value) +{ + g_assert_not_reached(); +} diff --git a/target/arm/hvf/sysreg.c.inc b/target/arm/hvf/sysreg.c.inc index 067a8603fa78..7a2f880f784b 100644 --- a/target/arm/hvf/sysreg.c.inc +++ b/target/arm/hvf/sysreg.c.inc @@ -145,3 +145,11 @@ DEF_SYSREG(HV_SYS_REG_TPIDRRO_EL0, 3, 3, 13, 0, 3) DEF_SYSREG(HV_SYS_REG_CNTV_CTL_EL0, 3, 3, 14, 3, 1) DEF_SYSREG(HV_SYS_REG_CNTV_CVAL_EL0, 3, 3, 14, 3, 2) DEF_SYSREG(HV_SYS_REG_SP_EL1, 3, 4, 4, 1, 0) + +DEF_SYSREG_15_02(HV_SYS_REG_SMCR_EL1, 3, 0, 1, 2, 6) +DEF_SYSREG_15_02(HV_SYS_REG_SMPRI_EL1, 3, 0, 1, 2, 4) +DEF_SYSREG_15_02(HV_SYS_REG_TPIDR2_EL0, 3, 3, 13, 0, 5) +DEF_SYSREG_15_02(HV_SYS_REG_ID_AA64ZFR0_EL1, 3, 0, 0, 4, 4) +DEF_SYSREG_15_02(HV_SYS_REG_ID_AA64SMFR0_EL1, 3, 0, 0, 4, 5) +DEF_SYSREG_15_02(HV_SYS_REG_SMPRI_EL1, 3, 0, 1, 2, 4) +DEF_SYSREG_15_02(HV_SYS_REG_SMCR_EL1, 3, 0, 1, 2, 6) diff --git a/target/arm/hvf_arm.h b/target/arm/hvf_arm.h index 5d19d82e5ded..8029d48caf56 100644 --- a/target/arm/hvf_arm.h +++ b/target/arm/hvf_arm.h @@ -22,4 +22,45 @@ void hvf_arm_init_debug(void); void hvf_arm_set_cpu_features_from_host(ARMCPU *cpu); +/* + * We need access to types from macOS SDK >=15.2, so expose stubs if the + * headers are not available until we raise our minimum macOS version. + */ +#ifdef __MAC_OS_X_VERSION_MAX_ALLOWED + #if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 150200) && defined(__aarch64__) + #include "system/hvf_int.h" + + static inline bool hvf_arm_sme2_supported(void) + { + if (__builtin_available(macOS 15.2, *)) { + size_t svl_bytes; + hv_return_t result = hv_sme_config_get_max_svl_bytes(&svl_bytes); + if (result == HV_UNSUPPORTED) { + return false; + } + assert_hvf_ok(result); + return svl_bytes > 0; + } else { + return false; + } + } + + static inline uint32_t hvf_arm_sme2_get_svl(void) + { + if (__builtin_available(macOS 15.2, *)) { + size_t svl_bytes; + hv_return_t result = hv_sme_config_get_max_svl_bytes(&svl_bytes); + assert_hvf_ok(result); + return svl_bytes; + } else { + abort(); + } + } + #else /* (__MAC_OS_X_VERSION_MAX_ALLOWED >= 150200) */ + #include "hvf/hvf_sme_stubs.h" + #endif /* (__MAC_OS_X_VERSION_MAX_ALLOWED >= 150200) */ +#else /* ifdef __MAC_OS_X_VERSION_MAX_ALLOWED */ + #include "hvf/hvf_sme_stubs.h" +#endif /* ifdef __MAC_OS_X_VERSION_MAX_ALLOWED */ + #endif diff --git a/target/arm/internals.h b/target/arm/internals.h index f7b641342a4d..8ec27508473d 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -1808,6 +1808,15 @@ static inline uint64_t arm_mdcr_el2_eff(CPUARMState *env) ((1 << (1 - 1)) | (1 << (2 - 1)) | \ (1 << (4 - 1)) | (1 << (8 - 1)) | (1 << (16 - 1))) +/* + * Return the maximum SVE/SME VQ for this CPU. This defines + * the maximum possible size of the Zn vector registers. + */ +static inline int arm_max_vq(ARMCPU *cpu) +{ + return MAX(cpu->sve_max_vq, cpu->sme_max_vq); +} + /* * Return true if it is possible to take a fine-grained-trap to EL2. */ diff --git a/target/arm/kvm-stub.c b/target/arm/kvm-stub.c index ea67deea5203..88cbe8d85c41 100644 --- a/target/arm/kvm-stub.c +++ b/target/arm/kvm-stub.c @@ -32,16 +32,6 @@ bool kvm_arm_aarch32_supported(void) return false; } -bool kvm_arm_pmu_supported(void) -{ - return false; -} - -bool kvm_arm_sve_supported(void) -{ - return false; -} - bool kvm_arm_mte_supported(void) { return false; @@ -95,11 +85,6 @@ void kvm_arm_steal_time_finalize(ARMCPU *cpu, Error **errp) g_assert_not_reached(); } -uint32_t kvm_arm_sve_get_vls(ARMCPU *cpu) -{ - g_assert_not_reached(); -} - void kvm_arm_enable_mte(Object *cpuobj, Error **errp) { g_assert_not_reached(); @@ -129,3 +114,8 @@ void arm_gic_cap_kvm_probe(GICCapability *v2, GICCapability *v3) { g_assert_not_reached(); } + +char *kvm_print_register_name(uint64_t regidx) +{ + g_assert_not_reached(); +} diff --git a/target/arm/kvm.c b/target/arm/kvm.c index ded582e0da06..d4a68874b880 100644 --- a/target/arm/kvm.c +++ b/target/arm/kvm.c @@ -60,6 +60,7 @@ typedef struct ARMHostCPUFeatures { ARMISARegisters isar; uint64_t features; uint32_t target; + uint32_t sve_vq_supported; const char *dtb_compatible; } ARMHostCPUFeatures; @@ -243,6 +244,35 @@ static int get_host_cpu_reg(int fd, ARMHostCPUFeatures *ahcf, return ret; } +static uint32_t kvm_arm_sve_get_vls(int fd) +{ + uint64_t vls[KVM_ARM64_SVE_VLS_WORDS]; + struct kvm_one_reg reg = { + .id = KVM_REG_ARM64_SVE_VLS, + .addr = (uint64_t)&vls[0], + }; + uint32_t vq = 0; + int ret; + + ret = ioctl(fd, KVM_GET_ONE_REG, ®); + if (ret) { + error_report("failed to get KVM_REG_ARM64_SVE_VLS: %s", + strerror(errno)); + abort(); + } + + for (int i = KVM_ARM64_SVE_VLS_WORDS - 1; i >= 0; --i) { + if (vls[i]) { + vq = 64 - clz64(vls[i]) + i * 64; + break; + } + } + if (vq > ARM_MAX_VQ) { + warn_report("KVM supports vector lengths larger than QEMU can enable"); + } + return vls[0] & MAKE_64BIT_MASK(0, ARM_MAX_VQ); +} + static bool kvm_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf) { /* Identify the feature bits corresponding to the host CPU, and @@ -267,7 +297,7 @@ static bool kvm_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf) * Ask for SVE if supported, so that we can query ID_AA64ZFR0, * which is otherwise RAZ. */ - sve_supported = kvm_arm_sve_supported(); + sve_supported = kvm_check_extension(kvm_state, KVM_CAP_ARM_SVE); if (sve_supported) { init.features[0] |= 1 << KVM_ARM_VCPU_SVE; } @@ -289,7 +319,7 @@ static bool kvm_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf) 1 << KVM_ARM_VCPU_PTRAUTH_GENERIC); } - if (kvm_arm_pmu_supported()) { + if (kvm_check_extension(kvm_state, KVM_CAP_ARM_PMU_V3)) { init.features[0] |= 1 << KVM_ARM_VCPU_PMU_V3; pmu_supported = true; features |= 1ULL << ARM_FEATURE_PMU; @@ -415,6 +445,9 @@ static bool kvm_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf) * So only read the register if we set KVM_ARM_VCPU_SVE above. */ err |= get_host_cpu_reg(fd, ahcf, ID_AA64ZFR0_EL1_IDX); + + /* Read the set of supported vector lengths. */ + arm_host_cpu_features.sve_vq_supported = kvm_arm_sve_get_vls(fd); } } @@ -462,6 +495,7 @@ void kvm_arm_set_cpu_features_from_host(ARMCPU *cpu) cpu->kvm_target = arm_host_cpu_features.target; cpu->dtb_compatible = arm_host_cpu_features.dtb_compatible; cpu->isar = arm_host_cpu_features.isar; + cpu->sve_vq.supported = arm_host_cpu_features.sve_vq_supported; env->features = arm_host_cpu_features.features; } @@ -485,6 +519,28 @@ static void kvm_steal_time_set(Object *obj, bool value, Error **errp) ARM_CPU(obj)->kvm_steal_time = value ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF; } +static char *kvm_get_psci_version(Object *obj, Error **errp) +{ + ARMCPU *cpu = ARM_CPU(obj); + + return g_strdup_printf("%d.%d", + (int) PSCI_VERSION_MAJOR(cpu->psci_version), + (int) PSCI_VERSION_MINOR(cpu->psci_version)); +} + +static void kvm_set_psci_version(Object *obj, const char *value, Error **errp) +{ + ARMCPU *cpu = ARM_CPU(obj); + uint16_t maj, min; + + if (sscanf(value, "%hu.%hu", &maj, &min) != 2) { + error_setg(errp, "Invalid PSCI version."); + return; + } + + cpu->psci_version = PSCI_VERSION(maj, min); +} + /* KVM VCPU properties should be prefixed with "kvm-". */ void kvm_arm_add_vcpu_properties(ARMCPU *cpu) { @@ -506,11 +562,12 @@ void kvm_arm_add_vcpu_properties(ARMCPU *cpu) kvm_steal_time_set); object_property_set_description(obj, "kvm-steal-time", "Set off to disable KVM steal time."); -} -bool kvm_arm_pmu_supported(void) -{ - return kvm_check_extension(kvm_state, KVM_CAP_ARM_PMU_V3); + object_property_add_str(obj, "kvm-psci-version", kvm_get_psci_version, + kvm_set_psci_version); + object_property_set_description(obj, "kvm-psci-version", + "Set PSCI version. " + "Valid values are 0.1, 0.2, 1.0, 1.1, 1.2, 1.3"); } int kvm_arm_get_max_vm_ipa_size(MachineState *ms, bool *fixed_ipa) @@ -807,12 +864,7 @@ static int kvm_arm_init_cpreg_list(ARMCPU *cpu) cpu->cpreg_indexes = g_renew(uint64_t, cpu->cpreg_indexes, arraylen); cpu->cpreg_values = g_renew(uint64_t, cpu->cpreg_values, arraylen); - cpu->cpreg_vmstate_indexes = g_renew(uint64_t, cpu->cpreg_vmstate_indexes, - arraylen); - cpu->cpreg_vmstate_values = g_renew(uint64_t, cpu->cpreg_vmstate_values, - arraylen); cpu->cpreg_array_len = arraylen; - cpu->cpreg_vmstate_array_len = arraylen; for (i = 0, arraylen = 0; i < rlp->n; i++) { uint64_t regidx = rlp->reg[i]; @@ -918,7 +970,7 @@ static gchar *kvm_print_sve_register_name(uint64_t regidx) } } -static gchar *kvm_print_register_name(uint64_t regidx) +char *kvm_print_register_name(uint64_t regidx) { switch ((regidx & KVM_REG_ARM_COPROC_MASK)) { case KVM_REG_ARM_CORE: @@ -926,7 +978,7 @@ static gchar *kvm_print_register_name(uint64_t regidx) case KVM_REG_ARM_DEMUX: return g_strdup_printf("demuxed reg %"PRIx64, regidx); case KVM_REG_ARM64_SYSREG: - return g_strdup_printf("op0:%d op1:%d crn:%d crm:%d op2:%d", + return g_strdup_printf("system register op0:%d op1:%d crn:%d crm:%d op2:%d", CP_REG_ARM64_SYSREG_OP(regidx, OP0), CP_REG_ARM64_SYSREG_OP(regidx, OP1), CP_REG_ARM64_SYSREG_OP(regidx, CRN), @@ -1874,11 +1926,6 @@ bool kvm_arm_el2_supported(void) return kvm_check_extension(kvm_state, KVM_CAP_ARM_EL2); } -bool kvm_arm_sve_supported(void) -{ - return kvm_check_extension(kvm_state, KVM_CAP_ARM_SVE); -} - bool kvm_arm_mte_supported(void) { return kvm_check_extension(kvm_state, KVM_CAP_ARM_MTE); @@ -1886,60 +1933,6 @@ bool kvm_arm_mte_supported(void) QEMU_BUILD_BUG_ON(KVM_ARM64_SVE_VQ_MIN != 1); -uint32_t kvm_arm_sve_get_vls(ARMCPU *cpu) -{ - /* Only call this function if kvm_arm_sve_supported() returns true. */ - static uint64_t vls[KVM_ARM64_SVE_VLS_WORDS]; - static bool probed; - uint32_t vq = 0; - int i; - - /* - * KVM ensures all host CPUs support the same set of vector lengths. - * So we only need to create the scratch VCPUs once and then cache - * the results. - */ - if (!probed) { - struct kvm_vcpu_init init = { - .target = -1, - .features[0] = (1 << KVM_ARM_VCPU_SVE), - }; - struct kvm_one_reg reg = { - .id = KVM_REG_ARM64_SVE_VLS, - .addr = (uint64_t)&vls[0], - }; - int fdarray[3], ret; - - probed = true; - - if (!kvm_arm_create_scratch_host_vcpu(fdarray, &init)) { - error_report("failed to create scratch VCPU with SVE enabled"); - abort(); - } - ret = ioctl(fdarray[2], KVM_GET_ONE_REG, ®); - kvm_arm_destroy_scratch_host_vcpu(fdarray); - if (ret) { - error_report("failed to get KVM_REG_ARM64_SVE_VLS: %s", - strerror(errno)); - abort(); - } - - for (i = KVM_ARM64_SVE_VLS_WORDS - 1; i >= 0; --i) { - if (vls[i]) { - vq = 64 - clz64(vls[i]) + i * 64; - break; - } - } - if (vq > ARM_MAX_VQ) { - warn_report("KVM supports vector lengths larger than " - "QEMU can enable"); - vls[0] &= MAKE_64BIT_MASK(0, ARM_MAX_VQ); - } - } - - return vls[0]; -} - static int kvm_arm_sve_set_vls(ARMCPU *cpu) { uint64_t vls[KVM_ARM64_SVE_VLS_WORDS] = { cpu->sve_vq.map }; @@ -1976,8 +1969,12 @@ int kvm_arch_init_vcpu(CPUState *cs) if (cs->start_powered_off) { cpu->kvm_init_features[0] |= 1 << KVM_ARM_VCPU_POWER_OFF; } - if (kvm_check_extension(cs->kvm_state, KVM_CAP_ARM_PSCI_0_2)) { - cpu->psci_version = QEMU_PSCI_VERSION_0_2; + if (cpu->psci_version != QEMU_PSCI_VERSION_0_1 && + kvm_check_extension(cs->kvm_state, KVM_CAP_ARM_PSCI_0_2)) { + /* + * Versions >= v0.2 are backward compatible with v0.2 + * omit the feature flag for v0.1 . + */ cpu->kvm_init_features[0] |= 1 << KVM_ARM_VCPU_PSCI_0_2; } if (!arm_feature(env, ARM_FEATURE_AARCH64)) { @@ -1987,7 +1984,6 @@ int kvm_arch_init_vcpu(CPUState *cs) cpu->kvm_init_features[0] |= 1 << KVM_ARM_VCPU_PMU_V3; } if (cpu_isar_feature(aa64_sve, cpu)) { - assert(kvm_arm_sve_supported()); cpu->kvm_init_features[0] |= 1 << KVM_ARM_VCPU_SVE; } if (cpu_isar_feature(aa64_pauth, cpu)) { @@ -2015,6 +2011,18 @@ int kvm_arch_init_vcpu(CPUState *cs) } } + if (cpu->psci_version) { + psciver = cpu->psci_version; + ret = kvm_set_one_reg(cs, KVM_REG_ARM_PSCI_VERSION, &psciver); + if (ret) { + error_report("KVM in this kernel does not support PSCI version %d.%d", + (int) PSCI_VERSION_MAJOR(psciver), + (int) PSCI_VERSION_MINOR(psciver)); + error_printf("Consider setting the kvm-psci-version property on the " + "migration source.\n"); + return ret; + } + } /* * KVM reports the exact PSCI version it is implementing via a * special sysreg. If it is present, use its contents to determine @@ -2105,16 +2113,15 @@ static int kvm_arch_put_fpsimd(CPUState *cs) * code the slice index to zero for now as it's unlikely we'll need more than * one slice for quite some time. */ -static int kvm_arch_put_sve(CPUState *cs) +static int kvm_arch_put_sve(CPUState *cs, uint32_t vq, bool have_ffr) { - ARMCPU *cpu = ARM_CPU(cs); - CPUARMState *env = &cpu->env; + CPUARMState *env = cpu_env(cs); uint64_t tmp[ARM_MAX_VQ * 2]; uint64_t *r; int n, ret; for (n = 0; n < KVM_ARM64_SVE_NUM_ZREGS; ++n) { - r = sve_bswap64(tmp, &env->vfp.zregs[n].d[0], cpu->sve_max_vq * 2); + r = sve_bswap64(tmp, &env->vfp.zregs[n].d[0], vq * 2); ret = kvm_set_one_reg(cs, KVM_REG_ARM64_SVE_ZREG(n, 0), r); if (ret) { return ret; @@ -2122,19 +2129,20 @@ static int kvm_arch_put_sve(CPUState *cs) } for (n = 0; n < KVM_ARM64_SVE_NUM_PREGS; ++n) { - r = sve_bswap64(tmp, r = &env->vfp.pregs[n].p[0], - DIV_ROUND_UP(cpu->sve_max_vq * 2, 8)); + r = sve_bswap64(tmp, &env->vfp.pregs[n].p[0], DIV_ROUND_UP(vq * 2, 8)); ret = kvm_set_one_reg(cs, KVM_REG_ARM64_SVE_PREG(n, 0), r); if (ret) { return ret; } } - r = sve_bswap64(tmp, &env->vfp.pregs[FFR_PRED_NUM].p[0], - DIV_ROUND_UP(cpu->sve_max_vq * 2, 8)); - ret = kvm_set_one_reg(cs, KVM_REG_ARM64_SVE_FFR(0), r); - if (ret) { - return ret; + if (have_ffr) { + r = sve_bswap64(tmp, &env->vfp.pregs[FFR_PRED_NUM].p[0], + DIV_ROUND_UP(vq * 2, 8)); + ret = kvm_set_one_reg(cs, KVM_REG_ARM64_SVE_FFR(0), r); + if (ret) { + return ret; + } } return 0; @@ -2223,7 +2231,7 @@ int kvm_arch_put_registers(CPUState *cs, KvmPutState level, Error **errp) } if (cpu_isar_feature(aa64_sve, cpu)) { - ret = kvm_arch_put_sve(cs); + ret = kvm_arch_put_sve(cs, cpu->sve_max_vq, true); } else { ret = kvm_arch_put_fpsimd(cs); } @@ -2289,10 +2297,9 @@ static int kvm_arch_get_fpsimd(CPUState *cs) * code the slice index to zero for now as it's unlikely we'll need more than * one slice for quite some time. */ -static int kvm_arch_get_sve(CPUState *cs) +static int kvm_arch_get_sve(CPUState *cs, uint32_t vq, bool have_ffr) { - ARMCPU *cpu = ARM_CPU(cs); - CPUARMState *env = &cpu->env; + CPUARMState *env = cpu_env(cs); uint64_t *r; int n, ret; @@ -2302,7 +2309,7 @@ static int kvm_arch_get_sve(CPUState *cs) if (ret) { return ret; } - sve_bswap64(r, r, cpu->sve_max_vq * 2); + sve_bswap64(r, r, vq * 2); } for (n = 0; n < KVM_ARM64_SVE_NUM_PREGS; ++n) { @@ -2311,15 +2318,17 @@ static int kvm_arch_get_sve(CPUState *cs) if (ret) { return ret; } - sve_bswap64(r, r, DIV_ROUND_UP(cpu->sve_max_vq * 2, 8)); + sve_bswap64(r, r, DIV_ROUND_UP(vq * 2, 8)); } - r = &env->vfp.pregs[FFR_PRED_NUM].p[0]; - ret = kvm_get_one_reg(cs, KVM_REG_ARM64_SVE_FFR(0), r); - if (ret) { - return ret; + if (have_ffr) { + r = &env->vfp.pregs[FFR_PRED_NUM].p[0]; + ret = kvm_get_one_reg(cs, KVM_REG_ARM64_SVE_FFR(0), r); + if (ret) { + return ret; + } + sve_bswap64(r, r, DIV_ROUND_UP(vq * 2, 8)); } - sve_bswap64(r, r, DIV_ROUND_UP(cpu->sve_max_vq * 2, 8)); return 0; } @@ -2407,7 +2416,7 @@ int kvm_arch_get_registers(CPUState *cs, Error **errp) } if (cpu_isar_feature(aa64_sve, cpu)) { - ret = kvm_arch_get_sve(cs); + ret = kvm_arch_get_sve(cs, cpu->sve_max_vq, true); } else { ret = kvm_arch_get_fpsimd(cs); } @@ -2540,7 +2549,6 @@ void kvm_arm_enable_mte(Object *cpuobj, Error **errp) error_setg(&mte_migration_blocker, "Live migration disabled due to MTE enabled"); if (migrate_add_blocker(&mte_migration_blocker, errp)) { - error_free(mte_migration_blocker); return; } diff --git a/target/arm/kvm_arm.h b/target/arm/kvm_arm.h index cc0b374254e3..e7c40fb003e4 100644 --- a/target/arm/kvm_arm.h +++ b/target/arm/kvm_arm.h @@ -124,16 +124,6 @@ bool kvm_arm_create_scratch_host_vcpu(int *fdarray, */ void kvm_arm_destroy_scratch_host_vcpu(int *fdarray); -/** - * kvm_arm_sve_get_vls: - * @cpu: ARMCPU - * - * Get all the SVE vector lengths supported by the KVM host, setting - * the bits corresponding to their length in quadwords minus one - * (vq - 1) up to ARM_MAX_VQ. Return the resulting map. - */ -uint32_t kvm_arm_sve_get_vls(ARMCPU *cpu); - /** * kvm_arm_set_cpu_features_from_host: * @cpu: ARMCPU to set the features for @@ -178,21 +168,6 @@ void kvm_arm_steal_time_finalize(ARMCPU *cpu, Error **errp); */ bool kvm_arm_aarch32_supported(void); -/** - * kvm_arm_pmu_supported: - * - * Returns: true if KVM can enable the PMU - * and false otherwise. - */ -bool kvm_arm_pmu_supported(void); - -/** - * kvm_arm_sve_supported: - * - * Returns true if KVM can enable SVE and false otherwise. - */ -bool kvm_arm_sve_supported(void); - /** * kvm_arm_mte_supported: * @@ -213,16 +188,6 @@ static inline bool kvm_arm_aarch32_supported(void) return false; } -static inline bool kvm_arm_pmu_supported(void) -{ - return false; -} - -static inline bool kvm_arm_sve_supported(void) -{ - return false; -} - static inline bool kvm_arm_mte_supported(void) { return false; @@ -266,4 +231,13 @@ void arm_cpu_kvm_set_irq(void *arm_cpu, int irq, int level); void arm_gic_cap_kvm_probe(GICCapability *v2, GICCapability *v3); +/* + * kvm_print_register_name: + * @regidx: register KVM index + * + * Returns a human-readable string representing this register + * The caller must free the string with g_free(). + */ +char *kvm_print_register_name(uint64_t regidx); + #endif diff --git a/target/arm/machine.c b/target/arm/machine.c index bbaae3444928..b0e499515cf7 100644 --- a/target/arm/machine.c +++ b/target/arm/machine.c @@ -1,5 +1,7 @@ #include "qemu/osdep.h" #include "cpu.h" +#include "cpregs.h" +#include "trace.h" #include "qemu/error-report.h" #include "system/kvm.h" #include "system/tcg.h" @@ -231,7 +233,7 @@ static bool sve_needed(void *opaque) { ARMCPU *cpu = opaque; - return cpu_isar_feature(aa64_sve, cpu); + return cpu_isar_feature(aa64_sve, cpu) || cpu_isar_feature(aa64_sme, cpu); } /* The first two words of each Zreg is stored in VFP state. */ @@ -984,11 +986,14 @@ static int cpu_pre_save(void *opaque) } } + /* + * On outbound migration, send the data in our cpreg_{values,indexes} + * arrays. The migration code will not allocate anything, but just + * reads the data pointed to by the VMSTATE_VARRAY_INT32_ALLOC() fields. + */ + cpu->cpreg_vmstate_indexes = cpu->cpreg_indexes; + cpu->cpreg_vmstate_values = cpu->cpreg_values; cpu->cpreg_vmstate_array_len = cpu->cpreg_array_len; - memcpy(cpu->cpreg_vmstate_indexes, cpu->cpreg_indexes, - cpu->cpreg_array_len * sizeof(uint64_t)); - memcpy(cpu->cpreg_vmstate_values, cpu->cpreg_values, - cpu->cpreg_array_len * sizeof(uint64_t)); return 0; } @@ -1001,6 +1006,9 @@ static int cpu_post_save(void *opaque) pmu_op_finish(&cpu->env); } + cpu->cpreg_vmstate_indexes = NULL; + cpu->cpreg_vmstate_values = NULL; + return 0; } @@ -1034,15 +1042,60 @@ static int cpu_pre_load(void *opaque) pmu_op_start(env); } + g_assert(!cpu->cpreg_vmstate_indexes); + g_assert(!cpu->cpreg_vmstate_values); + return 0; } +static gchar *print_register_name(uint64_t kvm_regidx) +{ + if (kvm_enabled()) { + return kvm_print_register_name(kvm_regidx); + } else { + return g_strdup_printf("system register 0x%x", kvm_to_cpreg_id(kvm_regidx)); + } +} + +/* + * Handle the situation where @kvmidx is on destination but not + * in the incoming stream. This never fails the migration. + */ +static void handle_cpreg_missing_in_incoming_stream(ARMCPU *cpu, uint64_t kvmidx) +{ + g_autofree gchar *name = print_register_name(kvmidx); + + warn_report("%s: %s " + "expected by the destination but not in the incoming stream: " + "skip it", __func__, name); +} + +/* + * Handle the situation where @kvmidx is in the incoming stream + * but not on destination. This currently fails the migration but + * we plan to accomodate some exceptions, hence the boolean returned value. + */ +static bool handle_cpreg_only_in_incoming_stream(ARMCPU *cpu, uint64_t kvmidx) +{ + g_autofree gchar *name = print_register_name(kvmidx); + bool fail = true; + + error_report("%s: %s in the incoming stream but unknown on the " + "destination: fail migration", __func__, name); + + return fail; +} + static int cpu_post_load(void *opaque, int version_id) { ARMCPU *cpu = opaque; CPUARMState *env = &cpu->env; + bool fail = false; int i, v; + trace_cpu_post_load(cpu->cpreg_vmstate_array_len, + cpu->cpreg_array_len); + /* * Handle migration compatibility from old QEMU which didn't * send the irq-line-state subsection. A QEMU without it did not @@ -1070,20 +1123,42 @@ static int cpu_post_load(void *opaque, int version_id) */ for (i = 0, v = 0; i < cpu->cpreg_array_len - && v < cpu->cpreg_vmstate_array_len; i++) { + && v < cpu->cpreg_vmstate_array_len;) { if (cpu->cpreg_vmstate_indexes[v] > cpu->cpreg_indexes[i]) { - /* register in our list but not incoming : skip it */ + handle_cpreg_missing_in_incoming_stream(cpu, cpu->cpreg_indexes[i++]); continue; } if (cpu->cpreg_vmstate_indexes[v] < cpu->cpreg_indexes[i]) { - /* register in their list but not ours: fail migration */ - return -1; + fail = handle_cpreg_only_in_incoming_stream(cpu, + cpu->cpreg_vmstate_indexes[v++]); + continue; } /* matching register, copy the value over */ cpu->cpreg_values[i] = cpu->cpreg_vmstate_values[v]; + i++; v++; } + /* + * if we have reached the end of the incoming array but there are + * still regs in cpreg, continue parsing the regs which are missing + * in the input stream + */ + for ( ; i < cpu->cpreg_array_len; i++) { + handle_cpreg_missing_in_incoming_stream(cpu, cpu->cpreg_indexes[i]); + } + /* + * if we have reached the end of the cpreg array but there are + * still regs in the input stream, continue parsing the vmstate array + */ + for ( ; v < cpu->cpreg_vmstate_array_len; v++) { + fail = handle_cpreg_only_in_incoming_stream(cpu, + cpu->cpreg_vmstate_indexes[v]); + } + if (fail) { + return -1; + } + if (kvm_enabled()) { if (!kvm_arm_cpu_post_load(cpu)) { return -1; @@ -1094,6 +1169,11 @@ static int cpu_post_load(void *opaque, int version_id) } } + g_free(cpu->cpreg_vmstate_indexes); + g_free(cpu->cpreg_vmstate_values); + cpu->cpreg_vmstate_indexes = NULL; + cpu->cpreg_vmstate_values = NULL; + /* * Misaligned thumb pc is architecturally impossible. Fail the * incoming migration. For TCG it would trigger the assert in @@ -1167,16 +1247,17 @@ const VMStateDescription vmstate_arm_cpu = { VMSTATE_UINT32_ARRAY(env.fiq_regs, ARMCPU, 5), VMSTATE_UINT64_ARRAY(env.elr_el, ARMCPU, 4), VMSTATE_UINT64_ARRAY(env.sp_el, ARMCPU, 4), - /* The length-check must come before the arrays to avoid - * incoming data possibly overflowing the array. + /* + * The length must come before the arrays so we can + * allocate the arrays before their data arrives */ - VMSTATE_INT32_POSITIVE_LE(cpreg_vmstate_array_len, ARMCPU), - VMSTATE_VARRAY_INT32(cpreg_vmstate_indexes, ARMCPU, - cpreg_vmstate_array_len, - 0, vmstate_info_uint64, uint64_t), - VMSTATE_VARRAY_INT32(cpreg_vmstate_values, ARMCPU, - cpreg_vmstate_array_len, - 0, vmstate_info_uint64, uint64_t), + VMSTATE_INT32(cpreg_vmstate_array_len, ARMCPU), + VMSTATE_VARRAY_INT32_ALLOC(cpreg_vmstate_indexes, ARMCPU, + cpreg_vmstate_array_len, + 0, vmstate_info_uint64, uint64_t), + VMSTATE_VARRAY_INT32_ALLOC(cpreg_vmstate_values, ARMCPU, + cpreg_vmstate_array_len, + 0, vmstate_info_uint64, uint64_t), VMSTATE_UINT64(env.exclusive_addr, ARMCPU), VMSTATE_UINT64(env.exclusive_val, ARMCPU), VMSTATE_UINT64(env.exclusive_high, ARMCPU), diff --git a/target/arm/tcg/arith_helper.c b/target/arm/tcg/arith_helper.c index 97c6362992c0..cc081c8f9667 100644 --- a/target/arm/tcg/arith_helper.c +++ b/target/arm/tcg/arith_helper.c @@ -8,11 +8,9 @@ #include "qemu/osdep.h" #include "qemu/bswap.h" #include "qemu/crc32c.h" +#include "helper.h" #include /* for crc32 */ -#define HELPER_H "tcg/helper.h" -#include "exec/helper-proto.h.inc" - /* * Note that signed overflow is undefined in C. The following routines are * careful to use unsigned types where modulo arithmetic is required. diff --git a/target/arm/tcg/cpu64.c b/target/arm/tcg/cpu64.c index fa80e48d2beb..84857fb706d9 100644 --- a/target/arm/tcg/cpu64.c +++ b/target/arm/tcg/cpu64.c @@ -524,10 +524,10 @@ static void aarch64_a64fx_initfn(Object *obj) cpu->gic_pribits = 5; /* The A64FX supports only 128, 256 and 512 bit vector lengths */ - aarch64_add_sve_properties(obj); cpu->sve_vq.supported = (1 << 0) /* 128bit */ | (1 << 1) /* 256bit */ | (1 << 3); /* 512bit */ + aarch64_add_sve_properties(obj); cpu->isar.reset_pmcr_el0 = 0x46014040; diff --git a/target/arm/tcg/crypto_helper.c b/target/arm/tcg/crypto_helper.c index 3428bd1bf0b4..11977cb7723a 100644 --- a/target/arm/tcg/crypto_helper.c +++ b/target/arm/tcg/crypto_helper.c @@ -15,11 +15,9 @@ #include "tcg/tcg-gvec-desc.h" #include "crypto/aes-round.h" #include "crypto/sm4.h" +#include "helper.h" #include "vec_internal.h" -#define HELPER_H "tcg/helper.h" -#include "exec/helper-proto.h.inc" - union CRYPTO_STATE { uint8_t bytes[16]; uint32_t words[4]; diff --git a/target/arm/tcg/debug.c b/target/arm/tcg/debug.c new file mode 100644 index 000000000000..5214e3c08a80 --- /dev/null +++ b/target/arm/tcg/debug.c @@ -0,0 +1,780 @@ +/* + * ARM debug helpers used by TCG + * + * This code is licensed under the GNU GPL v2 or later. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "cpu.h" +#include "helper.h" +#include "internals.h" +#include "cpu-features.h" +#include "cpregs.h" +#include "exec/watchpoint.h" +#include "system/tcg.h" + +/* Return the Exception Level targeted by debug exceptions. */ +static int arm_debug_target_el(CPUARMState *env) +{ + bool secure = arm_is_secure(env); + bool route_to_el2 = false; + + if (arm_feature(env, ARM_FEATURE_M)) { + return 1; + } + + if (arm_is_el2_enabled(env)) { + route_to_el2 = env->cp15.hcr_el2 & HCR_TGE || + env->cp15.mdcr_el2 & MDCR_TDE; + } + + if (route_to_el2) { + return 2; + } else if (arm_feature(env, ARM_FEATURE_EL3) && + !arm_el_is_aa64(env, 3) && secure) { + return 3; + } else { + return 1; + } +} + +/* + * Raise an exception to the debug target el. + * Modify syndrome to indicate when origin and target EL are the same. + */ +static G_NORETURN void +raise_exception_debug(CPUARMState *env, uint32_t excp, uint32_t syndrome) +{ + int debug_el = arm_debug_target_el(env); + int cur_el = arm_current_el(env); + + /* + * If singlestep is targeting a lower EL than the current one, then + * DisasContext.ss_active must be false and we can never get here. + * Similarly for watchpoint and breakpoint matches. + */ + assert(debug_el >= cur_el); + syndrome |= (debug_el == cur_el) << ARM_EL_EC_SHIFT; + raise_exception(env, excp, syndrome, debug_el); +} + +/* See AArch64.GenerateDebugExceptionsFrom() in ARM ARM pseudocode */ +static bool aa64_generate_debug_exceptions(CPUARMState *env) +{ + int cur_el = arm_current_el(env); + int debug_el; + + if (cur_el == 3) { + return false; + } + + /* MDCR_EL3.SDD disables debug events from Secure state */ + if (arm_is_secure_below_el3(env) + && extract32(env->cp15.mdcr_el3, 16, 1)) { + return false; + } + + /* + * Same EL to same EL debug exceptions need MDSCR_KDE enabled + * while not masking the (D)ebug bit in DAIF. + */ + debug_el = arm_debug_target_el(env); + + if (cur_el == debug_el) { + return extract32(env->cp15.mdscr_el1, 13, 1) + && !(env->daif & PSTATE_D); + } + + /* Otherwise the debug target needs to be a higher EL */ + return debug_el > cur_el; +} + +static bool aa32_generate_debug_exceptions(CPUARMState *env) +{ + int el = arm_current_el(env); + + if (el == 0 && arm_el_is_aa64(env, 1)) { + return aa64_generate_debug_exceptions(env); + } + + if (arm_is_secure(env)) { + int spd; + + if (el == 0 && (env->cp15.sder & 1)) { + /* + * SDER.SUIDEN means debug exceptions from Secure EL0 + * are always enabled. Otherwise they are controlled by + * SDCR.SPD like those from other Secure ELs. + */ + return true; + } + + spd = extract32(env->cp15.mdcr_el3, 14, 2); + switch (spd) { + case 1: + /* SPD == 0b01 is reserved, but behaves as 0b00. */ + case 0: + /* + * For 0b00 we return true if external secure invasive debug + * is enabled. On real hardware this is controlled by external + * signals to the core. QEMU always permits debug, and behaves + * as if DBGEN, SPIDEN, NIDEN and SPNIDEN are all tied high. + */ + return true; + case 2: + return false; + case 3: + return true; + } + } + + return el != 2; +} + +/* + * Return true if debugging exceptions are currently enabled. + * This corresponds to what in ARM ARM pseudocode would be + * if UsingAArch32() then + * return AArch32.GenerateDebugExceptions() + * else + * return AArch64.GenerateDebugExceptions() + * We choose to push the if() down into this function for clarity, + * since the pseudocode has it at all callsites except for the one in + * CheckSoftwareStep(), where it is elided because both branches would + * always return the same value. + */ +bool arm_generate_debug_exceptions(CPUARMState *env) +{ + if ((env->cp15.oslsr_el1 & 1) || (env->cp15.osdlr_el1 & 1)) { + return false; + } + if (is_a64(env)) { + return aa64_generate_debug_exceptions(env); + } else { + return aa32_generate_debug_exceptions(env); + } +} + +/* + * Is single-stepping active? (Note that the "is EL_D AArch64?" check + * implicitly means this always returns false in pre-v8 CPUs.) + */ +bool arm_singlestep_active(CPUARMState *env) +{ + return extract32(env->cp15.mdscr_el1, 0, 1) + && arm_el_is_aa64(env, arm_debug_target_el(env)) + && arm_generate_debug_exceptions(env); +} + +/* Return true if the linked breakpoint entry lbn passes its checks */ +static bool linked_bp_matches(ARMCPU *cpu, int lbn) +{ + CPUARMState *env = &cpu->env; + uint64_t bcr = env->cp15.dbgbcr[lbn]; + int brps = arm_num_brps(cpu); + int ctx_cmps = arm_num_ctx_cmps(cpu); + int bt; + uint32_t contextidr; + uint64_t hcr_el2; + + /* + * Links to unimplemented or non-context aware breakpoints are + * CONSTRAINED UNPREDICTABLE: either behave as if disabled, or + * as if linked to an UNKNOWN context-aware breakpoint (in which + * case DBGWCR_EL1.LBN must indicate that breakpoint). + * We choose the former. + */ + if (lbn >= brps || lbn < (brps - ctx_cmps)) { + return false; + } + + bcr = env->cp15.dbgbcr[lbn]; + + if (extract64(bcr, 0, 1) == 0) { + /* Linked breakpoint disabled : generate no events */ + return false; + } + + bt = extract64(bcr, 20, 4); + hcr_el2 = arm_hcr_el2_eff(env); + + switch (bt) { + case 3: /* linked context ID match */ + switch (arm_current_el(env)) { + default: + /* Context matches never fire in AArch64 EL3 */ + return false; + case 2: + if (!(hcr_el2 & HCR_E2H)) { + /* Context matches never fire in EL2 without E2H enabled. */ + return false; + } + contextidr = env->cp15.contextidr_el[2]; + break; + case 1: + contextidr = env->cp15.contextidr_el[1]; + break; + case 0: + if ((hcr_el2 & (HCR_E2H | HCR_TGE)) == (HCR_E2H | HCR_TGE)) { + contextidr = env->cp15.contextidr_el[2]; + } else { + contextidr = env->cp15.contextidr_el[1]; + } + break; + } + break; + + case 7: /* linked contextidr_el1 match */ + contextidr = env->cp15.contextidr_el[1]; + break; + case 13: /* linked contextidr_el2 match */ + contextidr = env->cp15.contextidr_el[2]; + break; + + case 9: /* linked VMID match (reserved if no EL2) */ + case 11: /* linked context ID and VMID match (reserved if no EL2) */ + case 15: /* linked full context ID match */ + default: + /* + * Links to Unlinked context breakpoints must generate no + * events; we choose to do the same for reserved values too. + */ + return false; + } + + /* + * We match the whole register even if this is AArch32 using the + * short descriptor format (in which case it holds both PROCID and ASID), + * since we don't implement the optional v7 context ID masking. + */ + return contextidr == (uint32_t)env->cp15.dbgbvr[lbn]; +} + +static bool bp_wp_matches(ARMCPU *cpu, int n, bool is_wp) +{ + CPUARMState *env = &cpu->env; + uint64_t cr; + int pac, hmc, ssc, wt, lbn; + /* + * Note that for watchpoints the check is against the CPU security + * state, not the S/NS attribute on the offending data access. + */ + bool is_secure = arm_is_secure(env); + int access_el = arm_current_el(env); + + if (is_wp) { + CPUWatchpoint *wp = env->cpu_watchpoint[n]; + + if (!wp || !(wp->flags & BP_WATCHPOINT_HIT)) { + return false; + } + cr = env->cp15.dbgwcr[n]; + if (wp->hitattrs.user) { + /* + * The LDRT/STRT/LDT/STT "unprivileged access" instructions should + * match watchpoints as if they were accesses done at EL0, even if + * the CPU is at EL1 or higher. + */ + access_el = 0; + } + } else { + uint64_t pc = is_a64(env) ? env->pc : env->regs[15]; + + if (!env->cpu_breakpoint[n] || env->cpu_breakpoint[n]->pc != pc) { + return false; + } + cr = env->cp15.dbgbcr[n]; + } + /* + * The WATCHPOINT_HIT flag guarantees us that the watchpoint is + * enabled and that the address and access type match; for breakpoints + * we know the address matched; check the remaining fields, including + * linked breakpoints. We rely on WCR and BCR having the same layout + * for the LBN, SSC, HMC, PAC/PMC and is-linked fields. + * Note that some combinations of {PAC, HMC, SSC} are reserved and + * must act either like some valid combination or as if the watchpoint + * were disabled. We choose the former, and use this together with + * the fact that EL3 must always be Secure and EL2 must always be + * Non-Secure to simplify the code slightly compared to the full + * table in the ARM ARM. + */ + pac = FIELD_EX64(cr, DBGWCR, PAC); + hmc = FIELD_EX64(cr, DBGWCR, HMC); + ssc = FIELD_EX64(cr, DBGWCR, SSC); + + switch (ssc) { + case 0: + break; + case 1: + case 3: + if (is_secure) { + return false; + } + break; + case 2: + if (!is_secure) { + return false; + } + break; + } + + switch (access_el) { + case 3: + case 2: + if (!hmc) { + return false; + } + break; + case 1: + if (extract32(pac, 0, 1) == 0) { + return false; + } + break; + case 0: + if (extract32(pac, 1, 1) == 0) { + return false; + } + break; + default: + g_assert_not_reached(); + } + + wt = FIELD_EX64(cr, DBGWCR, WT); + lbn = FIELD_EX64(cr, DBGWCR, LBN); + + if (wt && !linked_bp_matches(cpu, lbn)) { + return false; + } + + return true; +} + +static bool check_watchpoints(ARMCPU *cpu) +{ + CPUARMState *env = &cpu->env; + int n; + + /* + * If watchpoints are disabled globally or we can't take debug + * exceptions here then watchpoint firings are ignored. + */ + if (extract32(env->cp15.mdscr_el1, 15, 1) == 0 + || !arm_generate_debug_exceptions(env)) { + return false; + } + + for (n = 0; n < ARRAY_SIZE(env->cpu_watchpoint); n++) { + if (bp_wp_matches(cpu, n, true)) { + return true; + } + } + return false; +} + +bool arm_debug_check_breakpoint(CPUState *cs) +{ + ARMCPU *cpu = ARM_CPU(cs); + CPUARMState *env = &cpu->env; + vaddr pc; + int n; + + /* + * If breakpoints are disabled globally or we can't take debug + * exceptions here then breakpoint firings are ignored. + */ + if (extract32(env->cp15.mdscr_el1, 15, 1) == 0 + || !arm_generate_debug_exceptions(env)) { + return false; + } + + /* + * Single-step exceptions have priority over breakpoint exceptions. + * If single-step state is active-pending, suppress the bp. + */ + if (arm_singlestep_active(env) && !(env->pstate & PSTATE_SS)) { + return false; + } + + /* + * PC alignment faults have priority over breakpoint exceptions. + */ + pc = is_a64(env) ? env->pc : env->regs[15]; + if ((is_a64(env) || !env->thumb) && (pc & 3) != 0) { + return false; + } + + /* + * Instruction aborts have priority over breakpoint exceptions. + * TODO: We would need to look up the page for PC and verify that + * it is present and executable. + */ + + for (n = 0; n < ARRAY_SIZE(env->cpu_breakpoint); n++) { + if (bp_wp_matches(cpu, n, false)) { + return true; + } + } + return false; +} + +bool arm_debug_check_watchpoint(CPUState *cs, CPUWatchpoint *wp) +{ + /* + * Called by core code when a CPU watchpoint fires; need to check if this + * is also an architectural watchpoint match. + */ + ARMCPU *cpu = ARM_CPU(cs); + + return check_watchpoints(cpu); +} + +/* + * Return the FSR value for a debug exception (watchpoint, hardware + * breakpoint or BKPT insn) targeting the specified exception level. + */ +static uint32_t arm_debug_exception_fsr(CPUARMState *env) +{ + ARMMMUFaultInfo fi = { .type = ARMFault_Debug }; + int target_el = arm_debug_target_el(env); + bool using_lpae; + + if (arm_feature(env, ARM_FEATURE_M)) { + using_lpae = false; + } else if (target_el == 2 || arm_el_is_aa64(env, target_el)) { + using_lpae = true; + } else if (arm_feature(env, ARM_FEATURE_PMSA) && + arm_feature(env, ARM_FEATURE_V8)) { + using_lpae = true; + } else if (arm_feature(env, ARM_FEATURE_LPAE) && + (env->cp15.tcr_el[target_el] & TTBCR_EAE)) { + using_lpae = true; + } else { + using_lpae = false; + } + + if (using_lpae) { + return arm_fi_to_lfsc(&fi); + } else { + return arm_fi_to_sfsc(&fi); + } +} + +void arm_debug_excp_handler(CPUState *cs) +{ + /* + * Called by core code when a watchpoint or breakpoint fires; + * need to check which one and raise the appropriate exception. + */ + ARMCPU *cpu = ARM_CPU(cs); + CPUARMState *env = &cpu->env; + CPUWatchpoint *wp_hit = cs->watchpoint_hit; + + if (wp_hit) { + if (wp_hit->flags & BP_CPU) { + bool wnr = (wp_hit->flags & BP_WATCHPOINT_HIT_WRITE) != 0; + + cs->watchpoint_hit = NULL; + + env->exception.fsr = arm_debug_exception_fsr(env); + env->exception.vaddress = wp_hit->hitaddr; + raise_exception_debug(env, EXCP_DATA_ABORT, + syn_watchpoint(0, 0, wnr)); + } + } else { + uint64_t pc = is_a64(env) ? env->pc : env->regs[15]; + + /* + * (1) GDB breakpoints should be handled first. + * (2) Do not raise a CPU exception if no CPU breakpoint has fired, + * since singlestep is also done by generating a debug internal + * exception. + */ + if (cpu_breakpoint_test(cs, pc, BP_GDB) + || !cpu_breakpoint_test(cs, pc, BP_CPU)) { + return; + } + + env->exception.fsr = arm_debug_exception_fsr(env); + /* + * FAR is UNKNOWN: clear vaddress to avoid potentially exposing + * values to the guest that it shouldn't be able to see at its + * exception/security level. + */ + env->exception.vaddress = 0; + raise_exception_debug(env, EXCP_PREFETCH_ABORT, syn_breakpoint(0)); + } +} + +/* + * Raise an EXCP_BKPT with the specified syndrome register value, + * targeting the correct exception level for debug exceptions. + */ +void HELPER(exception_bkpt_insn)(CPUARMState *env, uint32_t syndrome) +{ + int debug_el = arm_debug_target_el(env); + int cur_el = arm_current_el(env); + + /* FSR will only be used if the debug target EL is AArch32. */ + env->exception.fsr = arm_debug_exception_fsr(env); + /* + * FAR is UNKNOWN: clear vaddress to avoid potentially exposing + * values to the guest that it shouldn't be able to see at its + * exception/security level. + */ + env->exception.vaddress = 0; + /* + * Other kinds of architectural debug exception are ignored if + * they target an exception level below the current one (in QEMU + * this is checked by arm_generate_debug_exceptions()). Breakpoint + * instructions are special because they always generate an exception + * to somewhere: if they can't go to the configured debug exception + * level they are taken to the current exception level. + */ + if (debug_el < cur_el) { + debug_el = cur_el; + } + raise_exception(env, EXCP_BKPT, syndrome, debug_el); +} + +void HELPER(exception_swstep)(CPUARMState *env, uint32_t syndrome) +{ + raise_exception_debug(env, EXCP_UDEF, syndrome); +} + +void hw_watchpoint_update(ARMCPU *cpu, int n) +{ + CPUARMState *env = &cpu->env; + vaddr len = 0; + vaddr wvr = env->cp15.dbgwvr[n]; + uint64_t wcr = env->cp15.dbgwcr[n]; + int mask; + int flags = BP_CPU | BP_STOP_BEFORE_ACCESS; + + if (env->cpu_watchpoint[n]) { + cpu_watchpoint_remove_by_ref(CPU(cpu), env->cpu_watchpoint[n]); + env->cpu_watchpoint[n] = NULL; + } + + if (!FIELD_EX64(wcr, DBGWCR, E)) { + /* E bit clear : watchpoint disabled */ + return; + } + + switch (FIELD_EX64(wcr, DBGWCR, LSC)) { + case 0: + /* LSC 00 is reserved and must behave as if the wp is disabled */ + return; + case 1: + flags |= BP_MEM_READ; + break; + case 2: + flags |= BP_MEM_WRITE; + break; + case 3: + flags |= BP_MEM_ACCESS; + break; + } + + /* + * Attempts to use both MASK and BAS fields simultaneously are + * CONSTRAINED UNPREDICTABLE; we opt to ignore BAS in this case, + * thus generating a watchpoint for every byte in the masked region. + */ + mask = FIELD_EX64(wcr, DBGWCR, MASK); + if (mask == 1 || mask == 2) { + /* + * Reserved values of MASK; we must act as if the mask value was + * some non-reserved value, or as if the watchpoint were disabled. + * We choose the latter. + */ + return; + } else if (mask) { + /* Watchpoint covers an aligned area up to 2GB in size */ + len = 1ULL << mask; + /* + * If masked bits in WVR are not zero it's CONSTRAINED UNPREDICTABLE + * whether the watchpoint fires when the unmasked bits match; we opt + * to generate the exceptions. + */ + wvr &= ~(len - 1); + } else { + /* Watchpoint covers bytes defined by the byte address select bits */ + int bas = FIELD_EX64(wcr, DBGWCR, BAS); + int basstart; + + if (extract64(wvr, 2, 1)) { + /* + * Deprecated case of an only 4-aligned address. BAS[7:4] are + * ignored, and BAS[3:0] define which bytes to watch. + */ + bas &= 0xf; + } + + if (bas == 0) { + /* This must act as if the watchpoint is disabled */ + return; + } + + /* + * The BAS bits are supposed to be programmed to indicate a contiguous + * range of bytes. Otherwise it is CONSTRAINED UNPREDICTABLE whether + * we fire for each byte in the word/doubleword addressed by the WVR. + * We choose to ignore any non-zero bits after the first range of 1s. + */ + basstart = ctz32(bas); + len = cto32(bas >> basstart); + wvr += basstart; + } + + cpu_watchpoint_insert(CPU(cpu), wvr, len, flags, + &env->cpu_watchpoint[n]); +} + +void hw_watchpoint_update_all(ARMCPU *cpu) +{ + int i; + CPUARMState *env = &cpu->env; + + /* + * Completely clear out existing QEMU watchpoints and our array, to + * avoid possible stale entries following migration load. + */ + cpu_watchpoint_remove_all(CPU(cpu), BP_CPU); + memset(env->cpu_watchpoint, 0, sizeof(env->cpu_watchpoint)); + + for (i = 0; i < ARRAY_SIZE(cpu->env.cpu_watchpoint); i++) { + hw_watchpoint_update(cpu, i); + } +} + +void hw_breakpoint_update(ARMCPU *cpu, int n) +{ + CPUARMState *env = &cpu->env; + uint64_t bvr = env->cp15.dbgbvr[n]; + uint64_t bcr = env->cp15.dbgbcr[n]; + vaddr addr; + int bt; + int flags = BP_CPU; + + if (env->cpu_breakpoint[n]) { + cpu_breakpoint_remove_by_ref(CPU(cpu), env->cpu_breakpoint[n]); + env->cpu_breakpoint[n] = NULL; + } + + if (!extract64(bcr, 0, 1)) { + /* E bit clear : watchpoint disabled */ + return; + } + + bt = extract64(bcr, 20, 4); + + switch (bt) { + case 4: /* unlinked address mismatch (reserved if AArch64) */ + case 5: /* linked address mismatch (reserved if AArch64) */ + qemu_log_mask(LOG_UNIMP, + "arm: address mismatch breakpoint types not implemented\n"); + return; + case 0: /* unlinked address match */ + case 1: /* linked address match */ + { + /* + * Bits [1:0] are RES0. + * + * It is IMPLEMENTATION DEFINED whether bits [63:49] + * ([63:53] for FEAT_LVA) are hardwired to a copy of the sign bit + * of the VA field ([48] or [52] for FEAT_LVA), or whether the + * value is read as written. It is CONSTRAINED UNPREDICTABLE + * whether the RESS bits are ignored when comparing an address. + * Therefore we are allowed to compare the entire register, which + * lets us avoid considering whether FEAT_LVA is actually enabled. + * + * The BAS field is used to allow setting breakpoints on 16-bit + * wide instructions; it is CONSTRAINED UNPREDICTABLE whether + * a bp will fire if the addresses covered by the bp and the addresses + * covered by the insn overlap but the insn doesn't start at the + * start of the bp address range. We choose to require the insn and + * the bp to have the same address. The constraints on writing to + * BAS enforced in dbgbcr_write mean we have only four cases: + * 0b0000 => no breakpoint + * 0b0011 => breakpoint on addr + * 0b1100 => breakpoint on addr + 2 + * 0b1111 => breakpoint on addr + * See also figure D2-3 in the v8 ARM ARM (DDI0487A.c). + */ + int bas = extract64(bcr, 5, 4); + addr = bvr & ~3ULL; + if (bas == 0) { + return; + } + if (bas == 0xc) { + addr += 2; + } + break; + } + case 2: /* unlinked context ID match */ + case 8: /* unlinked VMID match (reserved if no EL2) */ + case 10: /* unlinked context ID and VMID match (reserved if no EL2) */ + qemu_log_mask(LOG_UNIMP, + "arm: unlinked context breakpoint types not implemented\n"); + return; + case 9: /* linked VMID match (reserved if no EL2) */ + case 11: /* linked context ID and VMID match (reserved if no EL2) */ + case 3: /* linked context ID match */ + default: + /* + * We must generate no events for Linked context matches (unless + * they are linked to by some other bp/wp, which is handled in + * updates for the linking bp/wp). We choose to also generate no events + * for reserved values. + */ + return; + } + + cpu_breakpoint_insert(CPU(cpu), addr, flags, &env->cpu_breakpoint[n]); +} + +void hw_breakpoint_update_all(ARMCPU *cpu) +{ + int i; + CPUARMState *env = &cpu->env; + + /* + * Completely clear out existing QEMU breakpoints and our array, to + * avoid possible stale entries following migration load. + */ + cpu_breakpoint_remove_all(CPU(cpu), BP_CPU); + memset(env->cpu_breakpoint, 0, sizeof(env->cpu_breakpoint)); + + for (i = 0; i < ARRAY_SIZE(cpu->env.cpu_breakpoint); i++) { + hw_breakpoint_update(cpu, i); + } +} + +#if !defined(CONFIG_USER_ONLY) + +vaddr arm_adjust_watchpoint_address(CPUState *cs, vaddr addr, int len) +{ + ARMCPU *cpu = ARM_CPU(cs); + CPUARMState *env = &cpu->env; + + /* + * In BE32 system mode, target memory is stored byteswapped (on a + * little-endian host system), and by the time we reach here (via an + * opcode helper) the addresses of subword accesses have been adjusted + * to account for that, which means that watchpoints will not match. + * Undo the adjustment here. + */ + if (arm_sctlr_b(env)) { + if (len == 1) { + addr ^= 3; + } else if (len == 2) { + addr ^= 2; + } + } + + return addr; +} + +#endif /* !CONFIG_USER_ONLY */ diff --git a/target/arm/tcg/gengvec64.c b/target/arm/tcg/gengvec64.c index c425d2b14901..c7bdd1ea82f1 100644 --- a/target/arm/tcg/gengvec64.c +++ b/target/arm/tcg/gengvec64.c @@ -18,10 +18,11 @@ */ #include "qemu/osdep.h" +#include "cpu.h" +#include "helper-sve.h" #include "translate.h" #include "translate-a64.h" - static void gen_rax1_i64(TCGv_i64 d, TCGv_i64 n, TCGv_i64 m) { tcg_gen_rotli_i64(d, m, 1); diff --git a/target/arm/tcg/helper-a64.h b/target/arm/tcg/helper-a64-defs.h similarity index 100% rename from target/arm/tcg/helper-a64.h rename to target/arm/tcg/helper-a64-defs.h diff --git a/target/arm/tcg/helper-a64.c b/target/arm/tcg/helper-a64.c index e4d2c2e3928c..2dec587d3862 100644 --- a/target/arm/tcg/helper-a64.c +++ b/target/arm/tcg/helper-a64.c @@ -21,7 +21,8 @@ #include "qemu/units.h" #include "cpu.h" #include "gdbstub/helpers.h" -#include "exec/helper-proto.h" +#include "helper.h" +#include "helper-a64.h" #include "qemu/host-utils.h" #include "qemu/log.h" #include "qemu/main-loop.h" @@ -43,6 +44,9 @@ #endif #include "vec_internal.h" +#define HELPER_H "tcg/helper-a64-defs.h" +#include "exec/helper-info.c.inc" + /* C2.4.7 Multiply and divide */ /* special cases for 0 and LLONG_MIN are mandated by the standard */ uint64_t HELPER(udiv64)(uint64_t num, uint64_t den) diff --git a/target/arm/tcg/helper.h b/target/arm/tcg/helper-defs.h similarity index 100% rename from target/arm/tcg/helper.h rename to target/arm/tcg/helper-defs.h diff --git a/target/arm/tcg/helper-mve.h b/target/arm/tcg/helper-mve-defs.h similarity index 100% rename from target/arm/tcg/helper-mve.h rename to target/arm/tcg/helper-mve-defs.h diff --git a/target/arm/tcg/helper-sme.h b/target/arm/tcg/helper-sme-defs.h similarity index 100% rename from target/arm/tcg/helper-sme.h rename to target/arm/tcg/helper-sme-defs.h diff --git a/target/arm/tcg/helper-sve.h b/target/arm/tcg/helper-sve-defs.h similarity index 100% rename from target/arm/tcg/helper-sve.h rename to target/arm/tcg/helper-sve-defs.h diff --git a/target/arm/tcg/hflags.c b/target/arm/tcg/hflags.c index 5c9b9bec3b2c..7e6f8d364758 100644 --- a/target/arm/tcg/hflags.c +++ b/target/arm/tcg/hflags.c @@ -7,15 +7,13 @@ */ #include "qemu/osdep.h" #include "cpu.h" +#include "helper.h" #include "internals.h" #include "cpu-features.h" #include "exec/translation-block.h" #include "accel/tcg/cpu-ops.h" #include "cpregs.h" -#define HELPER_H "tcg/helper.h" -#include "exec/helper-proto.h.inc" - static inline bool fgt_svc(CPUARMState *env, int el) { /* diff --git a/target/arm/tcg/m_helper.c b/target/arm/tcg/m_helper.c index 0c3832a47fd8..a0cb8cb021ed 100644 --- a/target/arm/tcg/m_helper.c +++ b/target/arm/tcg/m_helper.c @@ -8,10 +8,10 @@ #include "qemu/osdep.h" #include "cpu.h" +#include "helper.h" #include "internals.h" #include "cpu-features.h" #include "gdbstub/helpers.h" -#include "exec/helper-proto.h" #include "qemu/main-loop.h" #include "qemu/bitops.h" #include "qemu/log.h" diff --git a/target/arm/tcg/meson.build b/target/arm/tcg/meson.build index 1b115656c464..5f5915605512 100644 --- a/target/arm/tcg/meson.build +++ b/target/arm/tcg/meson.build @@ -33,7 +33,6 @@ arm_ss.add(files( 'm_helper.c', 'mve_helper.c', 'op_helper.c', - 'vec_helper.c', )) arm_ss.add(when: 'TARGET_AARCH64', if_true: files( @@ -47,13 +46,10 @@ arm_ss.add(when: 'TARGET_AARCH64', if_true: files( 'pauth_helper.c', 'sme_helper.c', 'sve_helper.c', + 'vec_helper64.c', )) -arm_system_ss.add(files( - 'psci.c', -)) - -arm_system_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('cpu-v7m.c')) +arm_common_system_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('cpu-v7m.c')) arm_user_ss.add(when: 'TARGET_AARCH64', if_false: files('cpu-v7m.c')) arm_common_ss.add(zlib) @@ -65,15 +61,20 @@ arm_common_ss.add(files( arm_common_system_ss.add(files( 'cpregs-at.c', + 'debug.c', 'hflags.c', 'neon_helper.c', + 'psci.c', 'tlb_helper.c', 'tlb-insns.c', + 'vec_helper.c', 'vfp_helper.c', )) arm_user_ss.add(files( + 'debug.c', 'hflags.c', 'neon_helper.c', 'tlb_helper.c', + 'vec_helper.c', 'vfp_helper.c', )) diff --git a/target/arm/tcg/mte_helper.c b/target/arm/tcg/mte_helper.c index 08b8e7176a6a..a9fb979f639f 100644 --- a/target/arm/tcg/mte_helper.c +++ b/target/arm/tcg/mte_helper.c @@ -20,6 +20,7 @@ #include "qemu/osdep.h" #include "qemu/log.h" #include "cpu.h" +#include "helper.h" #include "internals.h" #include "exec/target_page.h" #include "exec/page-protection.h" @@ -31,7 +32,7 @@ #endif #include "accel/tcg/cpu-ldst.h" #include "accel/tcg/probe.h" -#include "exec/helper-proto.h" +#include "helper-a64.h" #include "exec/tlb-flags.h" #include "accel/tcg/cpu-ops.h" #include "qapi/error.h" diff --git a/target/arm/tcg/mve_helper.c b/target/arm/tcg/mve_helper.c index 63ddcf3fecfb..a67d90d6c75e 100644 --- a/target/arm/tcg/mve_helper.c +++ b/target/arm/tcg/mve_helper.c @@ -19,14 +19,18 @@ #include "qemu/osdep.h" #include "cpu.h" +#include "helper.h" +#include "helper-mve.h" #include "internals.h" #include "vec_internal.h" -#include "exec/helper-proto.h" #include "accel/tcg/cpu-ldst.h" #include "tcg/tcg.h" #include "fpu/softfloat.h" #include "crypto/clmul.h" +#define HELPER_H "tcg/helper-mve-defs.h" +#include "exec/helper-info.c.inc" + static uint16_t mve_eci_mask(CPUARMState *env) { /* diff --git a/target/arm/tcg/neon_helper.c b/target/arm/tcg/neon_helper.c index 8d288f3a7006..69147969b231 100644 --- a/target/arm/tcg/neon_helper.c +++ b/target/arm/tcg/neon_helper.c @@ -9,13 +9,11 @@ #include "qemu/osdep.h" #include "cpu.h" +#include "helper.h" #include "tcg/tcg-gvec-desc.h" #include "fpu/softfloat.h" #include "vec_internal.h" -#define HELPER_H "tcg/helper.h" -#include "exec/helper-proto.h.inc" - #define SIGNBIT (uint32_t)0x80000000 #define SIGNBIT64 ((uint64_t)1 << 63) diff --git a/target/arm/tcg/op_helper.c b/target/arm/tcg/op_helper.c index c7ab462d1d12..aa14f15eb622 100644 --- a/target/arm/tcg/op_helper.c +++ b/target/arm/tcg/op_helper.c @@ -19,8 +19,8 @@ #include "qemu/osdep.h" #include "qemu/main-loop.h" #include "cpu.h" -#include "exec/helper-proto.h" #include "exec/target_page.h" +#include "helper.h" #include "internals.h" #include "cpu-features.h" #include "accel/tcg/cpu-ldst.h" @@ -448,7 +448,7 @@ void HELPER(wfit)(CPUARMState *env, uint64_t timeout) if (target_el) { env->pc -= 4; - raise_exception(env, excp, syn_wfx(1, 0xe, 0, false), target_el); + raise_exception(env, excp, syn_wfx(1, 0xe, 2, false), target_el); } if (uadd64_overflow(timeout, offset, &nexttick)) { diff --git a/target/arm/tcg/pauth_helper.c b/target/arm/tcg/pauth_helper.c index c591c3052c38..67c0d59d9e97 100644 --- a/target/arm/tcg/pauth_helper.c +++ b/target/arm/tcg/pauth_helper.c @@ -19,10 +19,11 @@ #include "qemu/osdep.h" #include "cpu.h" +#include "helper.h" #include "internals.h" #include "cpu-features.h" #include "accel/tcg/cpu-ldst.h" -#include "exec/helper-proto.h" +#include "helper-a64.h" #include "tcg/tcg-gvec-desc.h" #include "qemu/xxhash.h" diff --git a/target/arm/tcg/psci.c b/target/arm/tcg/psci.c index 2d4093015785..56754bde9516 100644 --- a/target/arm/tcg/psci.c +++ b/target/arm/tcg/psci.c @@ -18,7 +18,7 @@ #include "qemu/osdep.h" #include "cpu.h" -#include "exec/helper-proto.h" +#include "helper.h" #include "kvm-consts.h" #include "qemu/main-loop.h" #include "system/runstate.h" @@ -68,7 +68,7 @@ void arm_handle_psci_call(ARMCPU *cpu) CPUARMState *env = &cpu->env; uint64_t param[4]; uint64_t context_id, mpidr; - target_ulong entry; + uint64_t entry; int32_t ret = 0; int i; diff --git a/target/arm/tcg/sme_helper.c b/target/arm/tcg/sme_helper.c index 075360d8b8ab..ab5999c5925a 100644 --- a/target/arm/tcg/sme_helper.c +++ b/target/arm/tcg/sme_helper.c @@ -21,7 +21,8 @@ #include "cpu.h" #include "internals.h" #include "tcg/tcg-gvec-desc.h" -#include "exec/helper-proto.h" +#include "helper.h" +#include "helper-sme.h" #include "accel/tcg/cpu-ldst.h" #include "accel/tcg/helper-retaddr.h" #include "qemu/int128.h" @@ -29,6 +30,8 @@ #include "vec_internal.h" #include "sve_ldst_internal.h" +#define HELPER_H "tcg/helper-sme-defs.h" +#include "exec/helper-info.c.inc" static bool vectors_overlap(ARMVectorReg *x, unsigned nx, ARMVectorReg *y, unsigned ny) diff --git a/target/arm/tcg/sve_helper.c b/target/arm/tcg/sve_helper.c index c442fcb540df..062d8881bd0f 100644 --- a/target/arm/tcg/sve_helper.c +++ b/target/arm/tcg/sve_helper.c @@ -21,9 +21,11 @@ #include "cpu.h" #include "internals.h" #include "exec/page-protection.h" -#include "exec/helper-proto.h" #include "exec/target_page.h" #include "exec/tlb-flags.h" +#include "helper.h" +#include "helper-a64.h" +#include "helper-sve.h" #include "tcg/tcg-gvec-desc.h" #include "fpu/softfloat.h" #include "tcg/tcg.h" @@ -37,6 +39,8 @@ #include "user/page-protection.h" #endif +#define HELPER_H "tcg/helper-sve-defs.h" +#include "exec/helper-info.c.inc" /* Return a value for NZCV as per the ARM PredTest pseudofunction. * diff --git a/target/arm/tcg/tlb_helper.c b/target/arm/tcg/tlb_helper.c index 5c689d3b69ff..565954269f9d 100644 --- a/target/arm/tcg/tlb_helper.c +++ b/target/arm/tcg/tlb_helper.c @@ -7,12 +7,10 @@ */ #include "qemu/osdep.h" #include "cpu.h" +#include "helper.h" #include "internals.h" #include "cpu-features.h" -#define HELPER_H "tcg/helper.h" -#include "exec/helper-proto.h.inc" - /* * Returns true if the stage 1 translation regime is using LPAE format page * tables. Used when raising alignment exceptions, whose FSR changes depending diff --git a/target/arm/tcg/translate-a32.h b/target/arm/tcg/translate-a32.h index 0b1fa57965ce..a8df364171b9 100644 --- a/target/arm/tcg/translate-a32.h +++ b/target/arm/tcg/translate-a32.h @@ -40,7 +40,7 @@ void write_neon_element64(TCGv_i64 src, int reg, int ele, MemOp memop); TCGv_i32 add_reg_for_lit(DisasContext *s, int reg, int ofs); void gen_set_cpsr(TCGv_i32 var, uint32_t mask); void gen_set_condexec(DisasContext *s); -void gen_update_pc(DisasContext *s, target_long diff); +void gen_update_pc(DisasContext *s, int64_t diff); void gen_lookup_tb(DisasContext *s); long vfp_reg_offset(bool dp, unsigned reg); long neon_full_reg_offset(unsigned reg); diff --git a/target/arm/tcg/translate-a64.c b/target/arm/tcg/translate-a64.c index 7a8cd99e004d..5d261a5e32b8 100644 --- a/target/arm/tcg/translate-a64.c +++ b/target/arm/tcg/translate-a64.c @@ -18,6 +18,9 @@ */ #include "qemu/osdep.h" #include "exec/target_page.h" +#include "helper-a64.h" +#include "helper-sme.h" +#include "helper-sve.h" #include "translate.h" #include "translate-a64.h" #include "qemu/log.h" diff --git a/target/arm/tcg/translate-mve.c b/target/arm/tcg/translate-mve.c index b1a8d6a65c04..4ca88f4d3a3f 100644 --- a/target/arm/tcg/translate-mve.c +++ b/target/arm/tcg/translate-mve.c @@ -18,6 +18,7 @@ */ #include "qemu/osdep.h" +#include "helper-mve.h" #include "translate.h" #include "translate-a32.h" diff --git a/target/arm/tcg/translate-sme.c b/target/arm/tcg/translate-sme.c index 091c56da4f4e..7d25ac5a51f7 100644 --- a/target/arm/tcg/translate-sme.c +++ b/target/arm/tcg/translate-sme.c @@ -18,6 +18,9 @@ */ #include "qemu/osdep.h" +#include "cpu.h" +#include "helper-sme.h" +#include "helper-sve.h" #include "translate.h" #include "translate-a64.h" diff --git a/target/arm/tcg/translate-sve.c b/target/arm/tcg/translate-sve.c index 64adb5c1ce3c..5bace3fda1a0 100644 --- a/target/arm/tcg/translate-sve.c +++ b/target/arm/tcg/translate-sve.c @@ -18,6 +18,9 @@ */ #include "qemu/osdep.h" +#include "cpu.h" +#include "helper-sme.h" +#include "helper-sve.h" #include "translate.h" #include "translate-a64.h" #include "fpu/softfloat.h" @@ -570,14 +573,14 @@ static bool trans_INVALID(DisasContext *s, arg_INVALID *a) *** SVE Logical - Unpredicated Group */ -TRANS_FEAT(AND_zzz, aa64_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_and, a) -TRANS_FEAT(ORR_zzz, aa64_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_or, a) -TRANS_FEAT(EOR_zzz, aa64_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_xor, a) -TRANS_FEAT(BIC_zzz, aa64_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_andc, a) +TRANS_FEAT(AND_zzz, aa64_sme_or_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_and, a) +TRANS_FEAT(ORR_zzz, aa64_sme_or_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_or, a) +TRANS_FEAT(EOR_zzz, aa64_sme_or_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_xor, a) +TRANS_FEAT(BIC_zzz, aa64_sme_or_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_andc, a) static bool trans_XAR(DisasContext *s, arg_rrri_esz *a) { - if (a->esz < 0 || !dc_isar_feature(aa64_sve2, s)) { + if (a->esz < 0 || !dc_isar_feature(aa64_sme_or_sve2, s)) { return false; } if (sve_access_check(s)) { @@ -589,8 +592,8 @@ static bool trans_XAR(DisasContext *s, arg_rrri_esz *a) return true; } -TRANS_FEAT(EOR3, aa64_sve2, gen_gvec_fn_arg_zzzz, gen_gvec_eor3, a) -TRANS_FEAT(BCAX, aa64_sve2, gen_gvec_fn_arg_zzzz, gen_gvec_bcax, a) +TRANS_FEAT(EOR3, aa64_sme_or_sve2, gen_gvec_fn_arg_zzzz, gen_gvec_eor3, a) +TRANS_FEAT(BCAX, aa64_sme_or_sve2, gen_gvec_fn_arg_zzzz, gen_gvec_bcax, a) static void gen_bsl(unsigned vece, uint32_t d, uint32_t n, uint32_t m, uint32_t a, uint32_t oprsz, uint32_t maxsz) @@ -599,7 +602,7 @@ static void gen_bsl(unsigned vece, uint32_t d, uint32_t n, uint32_t m, tcg_gen_gvec_bitsel(vece, d, a, n, m, oprsz, maxsz); } -TRANS_FEAT(BSL, aa64_sve2, gen_gvec_fn_arg_zzzz, gen_bsl, a) +TRANS_FEAT(BSL, aa64_sme_or_sve2, gen_gvec_fn_arg_zzzz, gen_bsl, a) static void gen_bsl1n_i64(TCGv_i64 d, TCGv_i64 n, TCGv_i64 m, TCGv_i64 k) { @@ -628,7 +631,7 @@ static void gen_bsl1n(unsigned vece, uint32_t d, uint32_t n, uint32_t m, tcg_gen_gvec_4(d, n, m, a, oprsz, maxsz, &op); } -TRANS_FEAT(BSL1N, aa64_sve2, gen_gvec_fn_arg_zzzz, gen_bsl1n, a) +TRANS_FEAT(BSL1N, aa64_sme_or_sve2, gen_gvec_fn_arg_zzzz, gen_bsl1n, a) static void gen_bsl2n_i64(TCGv_i64 d, TCGv_i64 n, TCGv_i64 m, TCGv_i64 k) { @@ -666,7 +669,7 @@ static void gen_bsl2n(unsigned vece, uint32_t d, uint32_t n, uint32_t m, tcg_gen_gvec_4(d, n, m, a, oprsz, maxsz, &op); } -TRANS_FEAT(BSL2N, aa64_sve2, gen_gvec_fn_arg_zzzz, gen_bsl2n, a) +TRANS_FEAT(BSL2N, aa64_sme_or_sve2, gen_gvec_fn_arg_zzzz, gen_bsl2n, a) static void gen_nbsl_i64(TCGv_i64 d, TCGv_i64 n, TCGv_i64 m, TCGv_i64 k) { @@ -695,18 +698,18 @@ static void gen_nbsl(unsigned vece, uint32_t d, uint32_t n, uint32_t m, tcg_gen_gvec_4(d, n, m, a, oprsz, maxsz, &op); } -TRANS_FEAT(NBSL, aa64_sve2, gen_gvec_fn_arg_zzzz, gen_nbsl, a) +TRANS_FEAT(NBSL, aa64_sme_or_sve2, gen_gvec_fn_arg_zzzz, gen_nbsl, a) /* *** SVE Integer Arithmetic - Unpredicated Group */ -TRANS_FEAT(ADD_zzz, aa64_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_add, a) -TRANS_FEAT(SUB_zzz, aa64_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_sub, a) -TRANS_FEAT(SQADD_zzz, aa64_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_ssadd, a) -TRANS_FEAT(SQSUB_zzz, aa64_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_sssub, a) -TRANS_FEAT(UQADD_zzz, aa64_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_usadd, a) -TRANS_FEAT(UQSUB_zzz, aa64_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_ussub, a) +TRANS_FEAT(ADD_zzz, aa64_sme_or_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_add, a) +TRANS_FEAT(SUB_zzz, aa64_sme_or_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_sub, a) +TRANS_FEAT(SQADD_zzz, aa64_sme_or_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_ssadd, a) +TRANS_FEAT(SQSUB_zzz, aa64_sme_or_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_sssub, a) +TRANS_FEAT(UQADD_zzz, aa64_sme_or_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_usadd, a) +TRANS_FEAT(UQSUB_zzz, aa64_sme_or_sve, gen_gvec_fn_arg_zzz, tcg_gen_gvec_ussub, a) /* *** SVE Integer Arithmetic - Binary Predicated Group @@ -732,40 +735,40 @@ static bool do_sel_z(DisasContext *s, int rd, int rn, int rm, int pg, int esz) TRANS_FEAT(NAME, FEAT, gen_gvec_ool_arg_zpzz, \ name##_zpzz_fns[a->esz], a, 0) -DO_ZPZZ(AND_zpzz, aa64_sve, sve_and) -DO_ZPZZ(EOR_zpzz, aa64_sve, sve_eor) -DO_ZPZZ(ORR_zpzz, aa64_sve, sve_orr) -DO_ZPZZ(BIC_zpzz, aa64_sve, sve_bic) +DO_ZPZZ(AND_zpzz, aa64_sme_or_sve, sve_and) +DO_ZPZZ(EOR_zpzz, aa64_sme_or_sve, sve_eor) +DO_ZPZZ(ORR_zpzz, aa64_sme_or_sve, sve_orr) +DO_ZPZZ(BIC_zpzz, aa64_sme_or_sve, sve_bic) -DO_ZPZZ(ADD_zpzz, aa64_sve, sve_add) -DO_ZPZZ(SUB_zpzz, aa64_sve, sve_sub) +DO_ZPZZ(ADD_zpzz, aa64_sme_or_sve, sve_add) +DO_ZPZZ(SUB_zpzz, aa64_sme_or_sve, sve_sub) -DO_ZPZZ(SMAX_zpzz, aa64_sve, sve_smax) -DO_ZPZZ(UMAX_zpzz, aa64_sve, sve_umax) -DO_ZPZZ(SMIN_zpzz, aa64_sve, sve_smin) -DO_ZPZZ(UMIN_zpzz, aa64_sve, sve_umin) -DO_ZPZZ(SABD_zpzz, aa64_sve, sve_sabd) -DO_ZPZZ(UABD_zpzz, aa64_sve, sve_uabd) +DO_ZPZZ(SMAX_zpzz, aa64_sme_or_sve, sve_smax) +DO_ZPZZ(UMAX_zpzz, aa64_sme_or_sve, sve_umax) +DO_ZPZZ(SMIN_zpzz, aa64_sme_or_sve, sve_smin) +DO_ZPZZ(UMIN_zpzz, aa64_sme_or_sve, sve_umin) +DO_ZPZZ(SABD_zpzz, aa64_sme_or_sve, sve_sabd) +DO_ZPZZ(UABD_zpzz, aa64_sme_or_sve, sve_uabd) -DO_ZPZZ(MUL_zpzz, aa64_sve, sve_mul) -DO_ZPZZ(SMULH_zpzz, aa64_sve, sve_smulh) -DO_ZPZZ(UMULH_zpzz, aa64_sve, sve_umulh) +DO_ZPZZ(MUL_zpzz, aa64_sme_or_sve, sve_mul) +DO_ZPZZ(SMULH_zpzz, aa64_sme_or_sve, sve_smulh) +DO_ZPZZ(UMULH_zpzz, aa64_sme_or_sve, sve_umulh) -DO_ZPZZ(ASR_zpzz, aa64_sve, sve_asr) -DO_ZPZZ(LSR_zpzz, aa64_sve, sve_lsr) -DO_ZPZZ(LSL_zpzz, aa64_sve, sve_lsl) +DO_ZPZZ(ASR_zpzz, aa64_sme_or_sve, sve_asr) +DO_ZPZZ(LSR_zpzz, aa64_sme_or_sve, sve_lsr) +DO_ZPZZ(LSL_zpzz, aa64_sme_or_sve, sve_lsl) static gen_helper_gvec_4 * const sdiv_fns[4] = { NULL, NULL, gen_helper_sve_sdiv_zpzz_s, gen_helper_sve_sdiv_zpzz_d }; -TRANS_FEAT(SDIV_zpzz, aa64_sve, gen_gvec_ool_arg_zpzz, sdiv_fns[a->esz], a, 0) +TRANS_FEAT(SDIV_zpzz, aa64_sme_or_sve, gen_gvec_ool_arg_zpzz, sdiv_fns[a->esz], a, 0) static gen_helper_gvec_4 * const udiv_fns[4] = { NULL, NULL, gen_helper_sve_udiv_zpzz_s, gen_helper_sve_udiv_zpzz_d }; -TRANS_FEAT(UDIV_zpzz, aa64_sve, gen_gvec_ool_arg_zpzz, udiv_fns[a->esz], a, 0) +TRANS_FEAT(UDIV_zpzz, aa64_sme_or_sve, gen_gvec_ool_arg_zpzz, udiv_fns[a->esz], a, 0) -TRANS_FEAT(SEL_zpzz, aa64_sve, do_sel_z, a->rd, a->rn, a->rm, a->pg, a->esz) +TRANS_FEAT(SEL_zpzz, aa64_sme_or_sve, do_sel_z, a->rd, a->rn, a->rm, a->pg, a->esz) /* *** SVE Integer Arithmetic - Unary Predicated Group @@ -778,14 +781,14 @@ TRANS_FEAT(SEL_zpzz, aa64_sve, do_sel_z, a->rd, a->rn, a->rm, a->pg, a->esz) }; \ TRANS_FEAT(NAME, FEAT, gen_gvec_ool_arg_zpz, name##_fns[a->esz], a, 0) -DO_ZPZ(CLS, aa64_sve, sve_cls) -DO_ZPZ(CLZ, aa64_sve, sve_clz) -DO_ZPZ(CNT_zpz, aa64_sve, sve_cnt_zpz) -DO_ZPZ(CNOT, aa64_sve, sve_cnot) -DO_ZPZ(NOT_zpz, aa64_sve, sve_not_zpz) -DO_ZPZ(ABS, aa64_sve, sve_abs) -DO_ZPZ(NEG, aa64_sve, sve_neg) -DO_ZPZ(RBIT, aa64_sve, sve_rbit) +DO_ZPZ(CLS, aa64_sme_or_sve, sve_cls) +DO_ZPZ(CLZ, aa64_sme_or_sve, sve_clz) +DO_ZPZ(CNT_zpz, aa64_sme_or_sve, sve_cnt_zpz) +DO_ZPZ(CNOT, aa64_sme_or_sve, sve_cnot) +DO_ZPZ(NOT_zpz, aa64_sme_or_sve, sve_not_zpz) +DO_ZPZ(ABS, aa64_sme_or_sve, sve_abs) +DO_ZPZ(NEG, aa64_sme_or_sve, sve_neg) +DO_ZPZ(RBIT, aa64_sme_or_sve, sve_rbit) DO_ZPZ(ORQV, aa64_sme2p1_or_sve2p1, sve2p1_orqv) DO_ZPZ(EORQV, aa64_sme2p1_or_sve2p1, sve2p1_eorqv) DO_ZPZ(ANDQV, aa64_sme2p1_or_sve2p1, sve2p1_andqv) @@ -798,7 +801,7 @@ static gen_helper_gvec_3 * const fabs_ah_fns[4] = { NULL, gen_helper_sve_ah_fabs_h, gen_helper_sve_ah_fabs_s, gen_helper_sve_ah_fabs_d, }; -TRANS_FEAT(FABS, aa64_sve, gen_gvec_ool_arg_zpz, +TRANS_FEAT(FABS, aa64_sme_or_sve, gen_gvec_ool_arg_zpz, s->fpcr_ah ? fabs_ah_fns[a->esz] : fabs_fns[a->esz], a, 0) static gen_helper_gvec_3 * const fneg_fns[4] = { @@ -809,34 +812,34 @@ static gen_helper_gvec_3 * const fneg_ah_fns[4] = { NULL, gen_helper_sve_ah_fneg_h, gen_helper_sve_ah_fneg_s, gen_helper_sve_ah_fneg_d, }; -TRANS_FEAT(FNEG, aa64_sve, gen_gvec_ool_arg_zpz, +TRANS_FEAT(FNEG, aa64_sme_or_sve, gen_gvec_ool_arg_zpz, s->fpcr_ah ? fneg_ah_fns[a->esz] : fneg_fns[a->esz], a, 0) static gen_helper_gvec_3 * const sxtb_fns[4] = { NULL, gen_helper_sve_sxtb_h, gen_helper_sve_sxtb_s, gen_helper_sve_sxtb_d, }; -TRANS_FEAT(SXTB, aa64_sve, gen_gvec_ool_arg_zpz, sxtb_fns[a->esz], a, 0) +TRANS_FEAT(SXTB, aa64_sme_or_sve, gen_gvec_ool_arg_zpz, sxtb_fns[a->esz], a, 0) static gen_helper_gvec_3 * const uxtb_fns[4] = { NULL, gen_helper_sve_uxtb_h, gen_helper_sve_uxtb_s, gen_helper_sve_uxtb_d, }; -TRANS_FEAT(UXTB, aa64_sve, gen_gvec_ool_arg_zpz, uxtb_fns[a->esz], a, 0) +TRANS_FEAT(UXTB, aa64_sme_or_sve, gen_gvec_ool_arg_zpz, uxtb_fns[a->esz], a, 0) static gen_helper_gvec_3 * const sxth_fns[4] = { NULL, NULL, gen_helper_sve_sxth_s, gen_helper_sve_sxth_d }; -TRANS_FEAT(SXTH, aa64_sve, gen_gvec_ool_arg_zpz, sxth_fns[a->esz], a, 0) +TRANS_FEAT(SXTH, aa64_sme_or_sve, gen_gvec_ool_arg_zpz, sxth_fns[a->esz], a, 0) static gen_helper_gvec_3 * const uxth_fns[4] = { NULL, NULL, gen_helper_sve_uxth_s, gen_helper_sve_uxth_d }; -TRANS_FEAT(UXTH, aa64_sve, gen_gvec_ool_arg_zpz, uxth_fns[a->esz], a, 0) +TRANS_FEAT(UXTH, aa64_sme_or_sve, gen_gvec_ool_arg_zpz, uxth_fns[a->esz], a, 0) -TRANS_FEAT(SXTW, aa64_sve, gen_gvec_ool_arg_zpz, +TRANS_FEAT(SXTW, aa64_sme_or_sve, gen_gvec_ool_arg_zpz, a->esz == 3 ? gen_helper_sve_sxtw_d : NULL, a, 0) -TRANS_FEAT(UXTW, aa64_sve, gen_gvec_ool_arg_zpz, +TRANS_FEAT(UXTW, aa64_sme_or_sve, gen_gvec_ool_arg_zpz, a->esz == 3 ? gen_helper_sve_uxtw_d : NULL, a, 0) static gen_helper_gvec_3 * const addqv_fns[4] = { @@ -912,7 +915,7 @@ static bool do_vpz_ool(DisasContext *s, arg_rpr_esz *a, gen_helper_sve_##name##_b, gen_helper_sve_##name##_h, \ gen_helper_sve_##name##_s, gen_helper_sve_##name##_d, \ }; \ - TRANS_FEAT(NAME, aa64_sve, do_vpz_ool, a, name##_fns[a->esz]) + TRANS_FEAT(NAME, aa64_sme_or_sve, do_vpz_ool, a, name##_fns[a->esz]) DO_VPZ(ORV, orv) DO_VPZ(ANDV, andv) @@ -928,7 +931,7 @@ static gen_helper_gvec_reduc * const saddv_fns[4] = { gen_helper_sve_saddv_b, gen_helper_sve_saddv_h, gen_helper_sve_saddv_s, NULL }; -TRANS_FEAT(SADDV, aa64_sve, do_vpz_ool, a, saddv_fns[a->esz]) +TRANS_FEAT(SADDV, aa64_sme_or_sve, do_vpz_ool, a, saddv_fns[a->esz]) #undef DO_VPZ @@ -980,59 +983,59 @@ static gen_helper_gvec_3 * const asr_zpzi_fns[4] = { gen_helper_sve_asr_zpzi_b, gen_helper_sve_asr_zpzi_h, gen_helper_sve_asr_zpzi_s, gen_helper_sve_asr_zpzi_d, }; -TRANS_FEAT(ASR_zpzi, aa64_sve, do_shift_zpzi, a, true, asr_zpzi_fns) +TRANS_FEAT(ASR_zpzi, aa64_sme_or_sve, do_shift_zpzi, a, true, asr_zpzi_fns) static gen_helper_gvec_3 * const lsr_zpzi_fns[4] = { gen_helper_sve_lsr_zpzi_b, gen_helper_sve_lsr_zpzi_h, gen_helper_sve_lsr_zpzi_s, gen_helper_sve_lsr_zpzi_d, }; -TRANS_FEAT(LSR_zpzi, aa64_sve, do_shift_zpzi, a, false, lsr_zpzi_fns) +TRANS_FEAT(LSR_zpzi, aa64_sme_or_sve, do_shift_zpzi, a, false, lsr_zpzi_fns) static gen_helper_gvec_3 * const lsl_zpzi_fns[4] = { gen_helper_sve_lsl_zpzi_b, gen_helper_sve_lsl_zpzi_h, gen_helper_sve_lsl_zpzi_s, gen_helper_sve_lsl_zpzi_d, }; -TRANS_FEAT(LSL_zpzi, aa64_sve, do_shift_zpzi, a, false, lsl_zpzi_fns) +TRANS_FEAT(LSL_zpzi, aa64_sme_or_sve, do_shift_zpzi, a, false, lsl_zpzi_fns) static gen_helper_gvec_3 * const asrd_fns[4] = { gen_helper_sve_asrd_b, gen_helper_sve_asrd_h, gen_helper_sve_asrd_s, gen_helper_sve_asrd_d, }; -TRANS_FEAT(ASRD, aa64_sve, do_shift_zpzi, a, false, asrd_fns) +TRANS_FEAT(ASRD, aa64_sme_or_sve, do_shift_zpzi, a, false, asrd_fns) static gen_helper_gvec_3 * const sqshl_zpzi_fns[4] = { gen_helper_sve2_sqshl_zpzi_b, gen_helper_sve2_sqshl_zpzi_h, gen_helper_sve2_sqshl_zpzi_s, gen_helper_sve2_sqshl_zpzi_d, }; -TRANS_FEAT(SQSHL_zpzi, aa64_sve2, gen_gvec_ool_arg_zpzi, +TRANS_FEAT(SQSHL_zpzi, aa64_sme_or_sve2, gen_gvec_ool_arg_zpzi, a->esz < 0 ? NULL : sqshl_zpzi_fns[a->esz], a) static gen_helper_gvec_3 * const uqshl_zpzi_fns[4] = { gen_helper_sve2_uqshl_zpzi_b, gen_helper_sve2_uqshl_zpzi_h, gen_helper_sve2_uqshl_zpzi_s, gen_helper_sve2_uqshl_zpzi_d, }; -TRANS_FEAT(UQSHL_zpzi, aa64_sve2, gen_gvec_ool_arg_zpzi, +TRANS_FEAT(UQSHL_zpzi, aa64_sme_or_sve2, gen_gvec_ool_arg_zpzi, a->esz < 0 ? NULL : uqshl_zpzi_fns[a->esz], a) static gen_helper_gvec_3 * const srshr_fns[4] = { gen_helper_sve2_srshr_b, gen_helper_sve2_srshr_h, gen_helper_sve2_srshr_s, gen_helper_sve2_srshr_d, }; -TRANS_FEAT(SRSHR, aa64_sve2, gen_gvec_ool_arg_zpzi, +TRANS_FEAT(SRSHR, aa64_sme_or_sve2, gen_gvec_ool_arg_zpzi, a->esz < 0 ? NULL : srshr_fns[a->esz], a) static gen_helper_gvec_3 * const urshr_fns[4] = { gen_helper_sve2_urshr_b, gen_helper_sve2_urshr_h, gen_helper_sve2_urshr_s, gen_helper_sve2_urshr_d, }; -TRANS_FEAT(URSHR, aa64_sve2, gen_gvec_ool_arg_zpzi, +TRANS_FEAT(URSHR, aa64_sme_or_sve2, gen_gvec_ool_arg_zpzi, a->esz < 0 ? NULL : urshr_fns[a->esz], a) static gen_helper_gvec_3 * const sqshlu_fns[4] = { gen_helper_sve2_sqshlu_b, gen_helper_sve2_sqshlu_h, gen_helper_sve2_sqshlu_s, gen_helper_sve2_sqshlu_d, }; -TRANS_FEAT(SQSHLU, aa64_sve2, gen_gvec_ool_arg_zpzi, +TRANS_FEAT(SQSHLU, aa64_sme_or_sve2, gen_gvec_ool_arg_zpzi, a->esz < 0 ? NULL : sqshlu_fns[a->esz], a) /* @@ -1044,7 +1047,7 @@ TRANS_FEAT(SQSHLU, aa64_sve2, gen_gvec_ool_arg_zpzi, gen_helper_sve_##name##_zpzw_b, gen_helper_sve_##name##_zpzw_h, \ gen_helper_sve_##name##_zpzw_s, NULL \ }; \ - TRANS_FEAT(NAME##_zpzw, aa64_sve, gen_gvec_ool_arg_zpzz, \ + TRANS_FEAT(NAME##_zpzw, aa64_sme_or_sve, gen_gvec_ool_arg_zpzz, \ a->esz < 0 ? NULL : name##_zpzw_fns[a->esz], a, 0) DO_ZPZW(ASR, asr) @@ -1084,16 +1087,16 @@ static bool do_shift_imm(DisasContext *s, arg_rri_esz *a, bool asr, return true; } -TRANS_FEAT(ASR_zzi, aa64_sve, do_shift_imm, a, true, tcg_gen_gvec_sari) -TRANS_FEAT(LSR_zzi, aa64_sve, do_shift_imm, a, false, tcg_gen_gvec_shri) -TRANS_FEAT(LSL_zzi, aa64_sve, do_shift_imm, a, false, tcg_gen_gvec_shli) +TRANS_FEAT(ASR_zzi, aa64_sme_or_sve, do_shift_imm, a, true, tcg_gen_gvec_sari) +TRANS_FEAT(LSR_zzi, aa64_sme_or_sve, do_shift_imm, a, false, tcg_gen_gvec_shri) +TRANS_FEAT(LSL_zzi, aa64_sme_or_sve, do_shift_imm, a, false, tcg_gen_gvec_shli) #define DO_ZZW(NAME, name) \ static gen_helper_gvec_3 * const name##_zzw_fns[4] = { \ gen_helper_sve_##name##_zzw_b, gen_helper_sve_##name##_zzw_h, \ gen_helper_sve_##name##_zzw_s, NULL \ }; \ - TRANS_FEAT(NAME, aa64_sve, gen_gvec_ool_arg_zzz, \ + TRANS_FEAT(NAME, aa64_sme_or_sve, gen_gvec_ool_arg_zzz, \ name##_zzw_fns[a->esz], a, 0) DO_ZZW(ASR_zzw, asr) @@ -1125,13 +1128,13 @@ static gen_helper_gvec_5 * const mla_fns[4] = { gen_helper_sve_mla_b, gen_helper_sve_mla_h, gen_helper_sve_mla_s, gen_helper_sve_mla_d, }; -TRANS_FEAT(MLA, aa64_sve, do_zpzzz_ool, a, mla_fns[a->esz]) +TRANS_FEAT(MLA, aa64_sme_or_sve, do_zpzzz_ool, a, mla_fns[a->esz]) static gen_helper_gvec_5 * const mls_fns[4] = { gen_helper_sve_mls_b, gen_helper_sve_mls_h, gen_helper_sve_mls_s, gen_helper_sve_mls_d, }; -TRANS_FEAT(MLS, aa64_sve, do_zpzzz_ool, a, mls_fns[a->esz]) +TRANS_FEAT(MLS, aa64_sme_or_sve, do_zpzzz_ool, a, mls_fns[a->esz]) /* *** SVE Index Generation Group @@ -1172,13 +1175,13 @@ static bool do_index(DisasContext *s, int esz, int rd, return true; } -TRANS_FEAT(INDEX_ii, aa64_sve, do_index, a->esz, a->rd, +TRANS_FEAT(INDEX_ii, aa64_sme_or_sve, do_index, a->esz, a->rd, tcg_constant_i64(a->imm1), tcg_constant_i64(a->imm2)) -TRANS_FEAT(INDEX_ir, aa64_sve, do_index, a->esz, a->rd, +TRANS_FEAT(INDEX_ir, aa64_sme_or_sve, do_index, a->esz, a->rd, tcg_constant_i64(a->imm), cpu_reg(s, a->rm)) -TRANS_FEAT(INDEX_ri, aa64_sve, do_index, a->esz, a->rd, +TRANS_FEAT(INDEX_ri, aa64_sme_or_sve, do_index, a->esz, a->rd, cpu_reg(s, a->rn), tcg_constant_i64(a->imm)) -TRANS_FEAT(INDEX_rr, aa64_sve, do_index, a->esz, a->rd, +TRANS_FEAT(INDEX_rr, aa64_sme_or_sve, do_index, a->esz, a->rd, cpu_reg(s, a->rn), cpu_reg(s, a->rm)) /* @@ -1187,7 +1190,7 @@ TRANS_FEAT(INDEX_rr, aa64_sve, do_index, a->esz, a->rd, static bool trans_ADDVL(DisasContext *s, arg_ADDVL *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -1213,7 +1216,7 @@ static bool trans_ADDSVL(DisasContext *s, arg_ADDSVL *a) static bool trans_ADDPL(DisasContext *s, arg_ADDPL *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -1239,7 +1242,7 @@ static bool trans_ADDSPL(DisasContext *s, arg_ADDSPL *a) static bool trans_RDVL(DisasContext *s, arg_RDVL *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -1370,7 +1373,7 @@ static bool trans_AND_pppp(DisasContext *s, arg_rprr_s *a) .prefer_i64 = true, }; - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (!a->s) { @@ -1408,7 +1411,7 @@ static bool trans_BIC_pppp(DisasContext *s, arg_rprr_s *a) .prefer_i64 = true, }; - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (!a->s && a->pg == a->rn) { @@ -1439,7 +1442,7 @@ static bool trans_EOR_pppp(DisasContext *s, arg_rprr_s *a) .prefer_i64 = true, }; - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } /* Alias NOT (predicate) is EOR Pd.B, Pg/Z, Pn.B, Pg.B */ @@ -1451,7 +1454,7 @@ static bool trans_EOR_pppp(DisasContext *s, arg_rprr_s *a) static bool trans_SEL_pppp(DisasContext *s, arg_rprr_s *a) { - if (a->s || !dc_isar_feature(aa64_sve, s)) { + if (a->s || !dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -1486,7 +1489,7 @@ static bool trans_ORR_pppp(DisasContext *s, arg_rprr_s *a) .prefer_i64 = true, }; - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (!a->s && a->pg == a->rn && a->rn == a->rm) { @@ -1517,7 +1520,7 @@ static bool trans_ORN_pppp(DisasContext *s, arg_rprr_s *a) .prefer_i64 = true, }; - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } return do_pppp_flags(s, a, &op); @@ -1545,7 +1548,7 @@ static bool trans_NOR_pppp(DisasContext *s, arg_rprr_s *a) .prefer_i64 = true, }; - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } return do_pppp_flags(s, a, &op); @@ -1573,7 +1576,7 @@ static bool trans_NAND_pppp(DisasContext *s, arg_rprr_s *a) .prefer_i64 = true, }; - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } return do_pppp_flags(s, a, &op); @@ -1585,7 +1588,7 @@ static bool trans_NAND_pppp(DisasContext *s, arg_rprr_s *a) static bool trans_PTEST(DisasContext *s, arg_PTEST *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -1723,7 +1726,7 @@ static bool do_predset(DisasContext *s, int esz, int rd, int pat, bool setflag) return true; } -TRANS_FEAT(PTRUE, aa64_sve, do_predset, a->esz, a->rd, a->pat, a->s) +TRANS_FEAT(PTRUE, aa64_sme_or_sve, do_predset, a->esz, a->rd, a->pat, a->s) static bool trans_PTRUE_cnt(DisasContext *s, arg_PTRUE_cnt *a) { @@ -1746,7 +1749,7 @@ TRANS_FEAT_NONSTREAMING(SETFFR, aa64_sve, do_predset, 0, FFR_PRED_NUM, 31, false) /* Note pat == 32 is #unimp, to set no elements. */ -TRANS_FEAT(PFALSE, aa64_sve, do_predset, 0, a->rd, 32, false) +TRANS_FEAT(PFALSE, aa64_sme_or_sve, do_predset, 0, a->rd, 32, false) static bool trans_RDFFR_p(DisasContext *s, arg_RDFFR_p *a) { @@ -1791,8 +1794,8 @@ static bool do_pfirst_pnext(DisasContext *s, arg_rr_esz *a, return true; } -TRANS_FEAT(PFIRST, aa64_sve, do_pfirst_pnext, a, gen_helper_sve_pfirst) -TRANS_FEAT(PNEXT, aa64_sve, do_pfirst_pnext, a, gen_helper_sve_pnext) +TRANS_FEAT(PFIRST, aa64_sme_or_sve, do_pfirst_pnext, a, gen_helper_sve_pfirst) +TRANS_FEAT(PNEXT, aa64_sme_or_sve, do_pfirst_pnext, a, gen_helper_sve_pnext) /* *** SVE Element Count Group @@ -1946,7 +1949,7 @@ static void do_sat_addsub_vec(DisasContext *s, int esz, int rd, int rn, static bool trans_CNT_r(DisasContext *s, arg_CNT_r *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -1959,7 +1962,7 @@ static bool trans_CNT_r(DisasContext *s, arg_CNT_r *a) static bool trans_INCDEC_r(DisasContext *s, arg_incdec_cnt *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -1975,7 +1978,7 @@ static bool trans_INCDEC_r(DisasContext *s, arg_incdec_cnt *a) static bool trans_SINCDEC_r_32(DisasContext *s, arg_incdec_cnt *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (!sve_access_check(s)) { @@ -2002,7 +2005,7 @@ static bool trans_SINCDEC_r_32(DisasContext *s, arg_incdec_cnt *a) static bool trans_SINCDEC_r_64(DisasContext *s, arg_incdec_cnt *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (!sve_access_check(s)) { @@ -2022,7 +2025,7 @@ static bool trans_SINCDEC_r_64(DisasContext *s, arg_incdec_cnt *a) static bool trans_INCDEC_v(DisasContext *s, arg_incdec2_cnt *a) { - if (a->esz == 0 || !dc_isar_feature(aa64_sve, s)) { + if (a->esz == 0 || !dc_isar_feature(aa64_sme_or_sve, s)) { return false; } @@ -2045,7 +2048,7 @@ static bool trans_INCDEC_v(DisasContext *s, arg_incdec2_cnt *a) static bool trans_SINCDEC_v(DisasContext *s, arg_incdec2_cnt *a) { - if (a->esz == 0 || !dc_isar_feature(aa64_sve, s)) { + if (a->esz == 0 || !dc_isar_feature(aa64_sme_or_sve, s)) { return false; } @@ -2079,15 +2082,15 @@ static bool do_zz_dbm(DisasContext *s, arg_rr_dbm *a, GVecGen2iFn *gvec_fn) return gen_gvec_fn_zzi(s, gvec_fn, MO_64, a->rd, a->rn, imm); } -TRANS_FEAT(AND_zzi, aa64_sve, do_zz_dbm, a, tcg_gen_gvec_andi) -TRANS_FEAT(ORR_zzi, aa64_sve, do_zz_dbm, a, tcg_gen_gvec_ori) -TRANS_FEAT(EOR_zzi, aa64_sve, do_zz_dbm, a, tcg_gen_gvec_xori) +TRANS_FEAT(AND_zzi, aa64_sme_or_sve, do_zz_dbm, a, tcg_gen_gvec_andi) +TRANS_FEAT(ORR_zzi, aa64_sme_or_sve, do_zz_dbm, a, tcg_gen_gvec_ori) +TRANS_FEAT(EOR_zzi, aa64_sme_or_sve, do_zz_dbm, a, tcg_gen_gvec_xori) static bool trans_DUPM(DisasContext *s, arg_DUPM *a) { uint64_t imm; - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (!logic_imm_decode_wmask(&imm, extract32(a->dbm, 12, 1), @@ -2131,7 +2134,7 @@ static void do_cpy_m(DisasContext *s, int esz, int rd, int rn, int pg, static bool trans_FCPY(DisasContext *s, arg_FCPY *a) { - if (a->esz == 0 || !dc_isar_feature(aa64_sve, s)) { + if (a->esz == 0 || !dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -2144,7 +2147,7 @@ static bool trans_FCPY(DisasContext *s, arg_FCPY *a) static bool trans_CPY_m_i(DisasContext *s, arg_rpri_esz *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -2160,7 +2163,7 @@ static bool trans_CPY_z_i(DisasContext *s, arg_CPY_z_i *a) gen_helper_sve_cpy_z_s, gen_helper_sve_cpy_z_d, }; - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -2207,8 +2210,8 @@ static bool do_EXT(DisasContext *s, int rd, int rn, int rm, int imm) return true; } -TRANS_FEAT(EXT, aa64_sve, do_EXT, a->rd, a->rn, a->rm, a->imm) -TRANS_FEAT(EXT_sve2, aa64_sve2, do_EXT, a->rd, a->rn, (a->rn + 1) % 32, a->imm) +TRANS_FEAT(EXT, aa64_sme_or_sve, do_EXT, a->rd, a->rn, a->rm, a->imm) +TRANS_FEAT(EXT_sve2, aa64_sme_or_sve2, do_EXT, a->rd, a->rn, (a->rn + 1) % 32, a->imm) static bool trans_EXTQ(DisasContext *s, arg_EXTQ *a) { @@ -2265,7 +2268,7 @@ static bool trans_EXTQ(DisasContext *s, arg_EXTQ *a) static bool trans_DUP_s(DisasContext *s, arg_DUP_s *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -2278,7 +2281,7 @@ static bool trans_DUP_s(DisasContext *s, arg_DUP_s *a) static bool trans_DUP_x(DisasContext *s, arg_DUP_x *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if ((a->imm & 0x1f) == 0) { @@ -2347,7 +2350,7 @@ static void do_insr_i64(DisasContext *s, arg_rrr_esz *a, TCGv_i64 val) static bool trans_INSR_f(DisasContext *s, arg_rrr_esz *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -2360,7 +2363,7 @@ static bool trans_INSR_f(DisasContext *s, arg_rrr_esz *a) static bool trans_INSR_r(DisasContext *s, arg_rrr_esz *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -2373,19 +2376,19 @@ static gen_helper_gvec_2 * const rev_fns[4] = { gen_helper_sve_rev_b, gen_helper_sve_rev_h, gen_helper_sve_rev_s, gen_helper_sve_rev_d }; -TRANS_FEAT(REV_v, aa64_sve, gen_gvec_ool_zz, rev_fns[a->esz], a->rd, a->rn, 0) +TRANS_FEAT(REV_v, aa64_sme_or_sve, gen_gvec_ool_zz, rev_fns[a->esz], a->rd, a->rn, 0) static gen_helper_gvec_3 * const sve_tbl_fns[4] = { gen_helper_sve_tbl_b, gen_helper_sve_tbl_h, gen_helper_sve_tbl_s, gen_helper_sve_tbl_d }; -TRANS_FEAT(TBL, aa64_sve, gen_gvec_ool_arg_zzz, sve_tbl_fns[a->esz], a, 0) +TRANS_FEAT(TBL, aa64_sme_or_sve, gen_gvec_ool_arg_zzz, sve_tbl_fns[a->esz], a, 0) static gen_helper_gvec_4 * const sve2_tbl_fns[4] = { gen_helper_sve2_tbl_b, gen_helper_sve2_tbl_h, gen_helper_sve2_tbl_s, gen_helper_sve2_tbl_d }; -TRANS_FEAT(TBL_sve2, aa64_sve2, gen_gvec_ool_zzzz, sve2_tbl_fns[a->esz], +TRANS_FEAT(TBL_sve2, aa64_sme_or_sve2, gen_gvec_ool_zzzz, sve2_tbl_fns[a->esz], a->rd, a->rn, (a->rn + 1) % 32, a->rm, 0) static gen_helper_gvec_3 * const tblq_fns[4] = { @@ -2399,7 +2402,7 @@ static gen_helper_gvec_3 * const tbx_fns[4] = { gen_helper_sve2_tbx_b, gen_helper_sve2_tbx_h, gen_helper_sve2_tbx_s, gen_helper_sve2_tbx_d }; -TRANS_FEAT(TBX, aa64_sve2, gen_gvec_ool_arg_zzz, tbx_fns[a->esz], a, 0) +TRANS_FEAT(TBX, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, tbx_fns[a->esz], a, 0) static gen_helper_gvec_3 * const tbxq_fns[4] = { gen_helper_sve2p1_tbxq_b, gen_helper_sve2p1_tbxq_h, @@ -2515,7 +2518,7 @@ static bool trans_UNPK(DisasContext *s, arg_UNPK *a) { gen_helper_sve_sunpk_d, gen_helper_sve_uunpk_d }, }; - if (a->esz == 0 || !dc_isar_feature(aa64_sve, s)) { + if (a->esz == 0 || !dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -2581,16 +2584,16 @@ static bool do_perm_pred2(DisasContext *s, arg_rr_esz *a, bool high_odd, return true; } -TRANS_FEAT(ZIP1_p, aa64_sve, do_perm_pred3, a, 0, gen_helper_sve_zip_p) -TRANS_FEAT(ZIP2_p, aa64_sve, do_perm_pred3, a, 1, gen_helper_sve_zip_p) -TRANS_FEAT(UZP1_p, aa64_sve, do_perm_pred3, a, 0, gen_helper_sve_uzp_p) -TRANS_FEAT(UZP2_p, aa64_sve, do_perm_pred3, a, 1, gen_helper_sve_uzp_p) -TRANS_FEAT(TRN1_p, aa64_sve, do_perm_pred3, a, 0, gen_helper_sve_trn_p) -TRANS_FEAT(TRN2_p, aa64_sve, do_perm_pred3, a, 1, gen_helper_sve_trn_p) +TRANS_FEAT(ZIP1_p, aa64_sme_or_sve, do_perm_pred3, a, 0, gen_helper_sve_zip_p) +TRANS_FEAT(ZIP2_p, aa64_sme_or_sve, do_perm_pred3, a, 1, gen_helper_sve_zip_p) +TRANS_FEAT(UZP1_p, aa64_sme_or_sve, do_perm_pred3, a, 0, gen_helper_sve_uzp_p) +TRANS_FEAT(UZP2_p, aa64_sme_or_sve, do_perm_pred3, a, 1, gen_helper_sve_uzp_p) +TRANS_FEAT(TRN1_p, aa64_sme_or_sve, do_perm_pred3, a, 0, gen_helper_sve_trn_p) +TRANS_FEAT(TRN2_p, aa64_sme_or_sve, do_perm_pred3, a, 1, gen_helper_sve_trn_p) -TRANS_FEAT(REV_p, aa64_sve, do_perm_pred2, a, 0, gen_helper_sve_rev_p) -TRANS_FEAT(PUNPKLO, aa64_sve, do_perm_pred2, a, 0, gen_helper_sve_punpk_p) -TRANS_FEAT(PUNPKHI, aa64_sve, do_perm_pred2, a, 1, gen_helper_sve_punpk_p) +TRANS_FEAT(REV_p, aa64_sme_or_sve, do_perm_pred2, a, 0, gen_helper_sve_rev_p) +TRANS_FEAT(PUNPKLO, aa64_sme_or_sve, do_perm_pred2, a, 0, gen_helper_sve_punpk_p) +TRANS_FEAT(PUNPKHI, aa64_sme_or_sve, do_perm_pred2, a, 1, gen_helper_sve_punpk_p) /* *** SVE Permute - Interleaving Group @@ -2617,9 +2620,9 @@ static gen_helper_gvec_3 * const zip_fns[4] = { gen_helper_sve_zip_b, gen_helper_sve_zip_h, gen_helper_sve_zip_s, gen_helper_sve_zip_d, }; -TRANS_FEAT(ZIP1_z, aa64_sve, gen_gvec_ool_arg_zzz, +TRANS_FEAT(ZIP1_z, aa64_sme_or_sve, gen_gvec_ool_arg_zzz, zip_fns[a->esz], a, 0) -TRANS_FEAT(ZIP2_z, aa64_sve, gen_gvec_ool_arg_zzz, +TRANS_FEAT(ZIP2_z, aa64_sme_or_sve, gen_gvec_ool_arg_zzz, zip_fns[a->esz], a, vec_full_reg_size(s) / 2) TRANS_FEAT_NONSTREAMING(ZIP1_q, aa64_sve_f64mm, do_interleave_q, @@ -2641,9 +2644,9 @@ static gen_helper_gvec_3 * const uzp_fns[4] = { gen_helper_sve_uzp_b, gen_helper_sve_uzp_h, gen_helper_sve_uzp_s, gen_helper_sve_uzp_d, }; -TRANS_FEAT(UZP1_z, aa64_sve, gen_gvec_ool_arg_zzz, +TRANS_FEAT(UZP1_z, aa64_sme_or_sve, gen_gvec_ool_arg_zzz, uzp_fns[a->esz], a, 0) -TRANS_FEAT(UZP2_z, aa64_sve, gen_gvec_ool_arg_zzz, +TRANS_FEAT(UZP2_z, aa64_sme_or_sve, gen_gvec_ool_arg_zzz, uzp_fns[a->esz], a, 1 << a->esz) TRANS_FEAT_NONSTREAMING(UZP1_q, aa64_sve_f64mm, do_interleave_q, @@ -2665,9 +2668,9 @@ static gen_helper_gvec_3 * const trn_fns[4] = { gen_helper_sve_trn_s, gen_helper_sve_trn_d, }; -TRANS_FEAT(TRN1_z, aa64_sve, gen_gvec_ool_arg_zzz, +TRANS_FEAT(TRN1_z, aa64_sme_or_sve, gen_gvec_ool_arg_zzz, trn_fns[a->esz], a, 0) -TRANS_FEAT(TRN2_z, aa64_sve, gen_gvec_ool_arg_zzz, +TRANS_FEAT(TRN2_z, aa64_sme_or_sve, gen_gvec_ool_arg_zzz, trn_fns[a->esz], a, 1 << a->esz) TRANS_FEAT_NONSTREAMING(TRN1_q, aa64_sve_f64mm, do_interleave_q, @@ -2828,8 +2831,8 @@ static bool do_clast_vector(DisasContext *s, arg_rprr_esz *a, bool before) return true; } -TRANS_FEAT(CLASTA_z, aa64_sve, do_clast_vector, a, false) -TRANS_FEAT(CLASTB_z, aa64_sve, do_clast_vector, a, true) +TRANS_FEAT(CLASTA_z, aa64_sme_or_sve, do_clast_vector, a, false) +TRANS_FEAT(CLASTB_z, aa64_sme_or_sve, do_clast_vector, a, true) /* Compute CLAST for a scalar. */ static void do_clast_scalar(DisasContext *s, int esz, int pg, int rm, @@ -2873,8 +2876,8 @@ static bool do_clast_fp(DisasContext *s, arg_rpr_esz *a, bool before) return true; } -TRANS_FEAT(CLASTA_v, aa64_sve, do_clast_fp, a, false) -TRANS_FEAT(CLASTB_v, aa64_sve, do_clast_fp, a, true) +TRANS_FEAT(CLASTA_v, aa64_sme_or_sve, do_clast_fp, a, false) +TRANS_FEAT(CLASTB_v, aa64_sme_or_sve, do_clast_fp, a, true) /* Compute CLAST for a Xreg. */ static bool do_clast_general(DisasContext *s, arg_rpr_esz *a, bool before) @@ -2906,8 +2909,8 @@ static bool do_clast_general(DisasContext *s, arg_rpr_esz *a, bool before) return true; } -TRANS_FEAT(CLASTA_r, aa64_sve, do_clast_general, a, false) -TRANS_FEAT(CLASTB_r, aa64_sve, do_clast_general, a, true) +TRANS_FEAT(CLASTA_r, aa64_sme_or_sve, do_clast_general, a, false) +TRANS_FEAT(CLASTB_r, aa64_sme_or_sve, do_clast_general, a, true) /* Compute LAST for a scalar. */ static TCGv_i64 do_last_scalar(DisasContext *s, int esz, @@ -2935,8 +2938,8 @@ static bool do_last_fp(DisasContext *s, arg_rpr_esz *a, bool before) return true; } -TRANS_FEAT(LASTA_v, aa64_sve, do_last_fp, a, false) -TRANS_FEAT(LASTB_v, aa64_sve, do_last_fp, a, true) +TRANS_FEAT(LASTA_v, aa64_sme_or_sve, do_last_fp, a, false) +TRANS_FEAT(LASTB_v, aa64_sme_or_sve, do_last_fp, a, true) /* Compute LAST for a Xreg. */ static bool do_last_general(DisasContext *s, arg_rpr_esz *a, bool before) @@ -2948,12 +2951,12 @@ static bool do_last_general(DisasContext *s, arg_rpr_esz *a, bool before) return true; } -TRANS_FEAT(LASTA_r, aa64_sve, do_last_general, a, false) -TRANS_FEAT(LASTB_r, aa64_sve, do_last_general, a, true) +TRANS_FEAT(LASTA_r, aa64_sme_or_sve, do_last_general, a, false) +TRANS_FEAT(LASTB_r, aa64_sme_or_sve, do_last_general, a, true) static bool trans_CPY_m_r(DisasContext *s, arg_rpr_esz *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -2964,7 +2967,7 @@ static bool trans_CPY_m_r(DisasContext *s, arg_rpr_esz *a) static bool trans_CPY_m_v(DisasContext *s, arg_rpr_esz *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -2979,22 +2982,22 @@ static gen_helper_gvec_3 * const revb_fns[4] = { NULL, gen_helper_sve_revb_h, gen_helper_sve_revb_s, gen_helper_sve_revb_d, }; -TRANS_FEAT(REVB, aa64_sve, gen_gvec_ool_arg_zpz, revb_fns[a->esz], a, 0) +TRANS_FEAT(REVB, aa64_sme_or_sve, gen_gvec_ool_arg_zpz, revb_fns[a->esz], a, 0) static gen_helper_gvec_3 * const revh_fns[4] = { NULL, NULL, gen_helper_sve_revh_s, gen_helper_sve_revh_d, }; -TRANS_FEAT(REVH, aa64_sve, gen_gvec_ool_arg_zpz, revh_fns[a->esz], a, 0) +TRANS_FEAT(REVH, aa64_sme_or_sve, gen_gvec_ool_arg_zpz, revh_fns[a->esz], a, 0) -TRANS_FEAT(REVW, aa64_sve, gen_gvec_ool_arg_zpz, +TRANS_FEAT(REVW, aa64_sme_or_sve, gen_gvec_ool_arg_zpz, a->esz == 3 ? gen_helper_sve_revw_d : NULL, a, 0) TRANS_FEAT(REVD, aa64_sme, gen_gvec_ool_arg_zpz, gen_helper_sme_revd_q, a, 0) -TRANS_FEAT(SPLICE, aa64_sve, gen_gvec_ool_arg_zpzz, +TRANS_FEAT(SPLICE, aa64_sme_or_sve, gen_gvec_ool_arg_zpzz, gen_helper_sve_splice, a, a->esz) -TRANS_FEAT(SPLICE_sve2, aa64_sve2, gen_gvec_ool_zzzp, gen_helper_sve_splice, +TRANS_FEAT(SPLICE_sve2, aa64_sme_or_sve2, gen_gvec_ool_zzzp, gen_helper_sve_splice, a->rd, a->rn, (a->rn + 1) % 32, a->pg, a->esz) /* @@ -3038,7 +3041,7 @@ static bool do_ppzz_flags(DisasContext *s, arg_rprr_esz *a, gen_helper_sve_##name##_ppzz_b, gen_helper_sve_##name##_ppzz_h, \ gen_helper_sve_##name##_ppzz_s, gen_helper_sve_##name##_ppzz_d, \ }; \ - TRANS_FEAT(NAME##_ppzz, aa64_sve, do_ppzz_flags, \ + TRANS_FEAT(NAME##_ppzz, aa64_sme_or_sve, do_ppzz_flags, \ a, name##_ppzz_fns[a->esz]) DO_PPZZ(CMPEQ, cmpeq) @@ -3055,7 +3058,7 @@ DO_PPZZ(CMPHS, cmphs) gen_helper_sve_##name##_ppzw_b, gen_helper_sve_##name##_ppzw_h, \ gen_helper_sve_##name##_ppzw_s, NULL \ }; \ - TRANS_FEAT(NAME##_ppzw, aa64_sve, do_ppzz_flags, \ + TRANS_FEAT(NAME##_ppzw, aa64_sme_or_sve, do_ppzz_flags, \ a, name##_ppzw_fns[a->esz]) DO_PPZW(CMPEQ, cmpeq) @@ -3110,7 +3113,7 @@ static bool do_ppzi_flags(DisasContext *s, arg_rpri_esz *a, gen_helper_sve_##name##_ppzi_b, gen_helper_sve_##name##_ppzi_h, \ gen_helper_sve_##name##_ppzi_s, gen_helper_sve_##name##_ppzi_d, \ }; \ - TRANS_FEAT(NAME##_ppzi, aa64_sve, do_ppzi_flags, a, \ + TRANS_FEAT(NAME##_ppzi, aa64_sme_or_sve, do_ppzi_flags, a, \ name##_ppzi_fns[a->esz]) DO_PPZI(CMPEQ, cmpeq) @@ -3190,22 +3193,22 @@ static bool do_brk2(DisasContext *s, arg_rpr_s *a, return true; } -TRANS_FEAT(BRKPA, aa64_sve, do_brk3, a, +TRANS_FEAT(BRKPA, aa64_sme_or_sve, do_brk3, a, gen_helper_sve_brkpa, gen_helper_sve_brkpas) -TRANS_FEAT(BRKPB, aa64_sve, do_brk3, a, +TRANS_FEAT(BRKPB, aa64_sme_or_sve, do_brk3, a, gen_helper_sve_brkpb, gen_helper_sve_brkpbs) -TRANS_FEAT(BRKA_m, aa64_sve, do_brk2, a, +TRANS_FEAT(BRKA_m, aa64_sme_or_sve, do_brk2, a, gen_helper_sve_brka_m, gen_helper_sve_brkas_m) -TRANS_FEAT(BRKB_m, aa64_sve, do_brk2, a, +TRANS_FEAT(BRKB_m, aa64_sme_or_sve, do_brk2, a, gen_helper_sve_brkb_m, gen_helper_sve_brkbs_m) -TRANS_FEAT(BRKA_z, aa64_sve, do_brk2, a, +TRANS_FEAT(BRKA_z, aa64_sme_or_sve, do_brk2, a, gen_helper_sve_brka_z, gen_helper_sve_brkas_z) -TRANS_FEAT(BRKB_z, aa64_sve, do_brk2, a, +TRANS_FEAT(BRKB_z, aa64_sme_or_sve, do_brk2, a, gen_helper_sve_brkb_z, gen_helper_sve_brkbs_z) -TRANS_FEAT(BRKN, aa64_sve, do_brk2, a, +TRANS_FEAT(BRKN, aa64_sme_or_sve, do_brk2, a, gen_helper_sve_brkn, gen_helper_sve_brkns) /* @@ -3250,7 +3253,7 @@ static void do_cntp(DisasContext *s, TCGv_i64 val, int esz, int pn, int pg) static bool trans_CNTP(DisasContext *s, arg_CNTP *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -3291,7 +3294,7 @@ static bool trans_CNTP_c(DisasContext *s, arg_CNTP_c *a) static bool trans_INCDECP_r(DisasContext *s, arg_incdec_pred *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -3310,7 +3313,7 @@ static bool trans_INCDECP_r(DisasContext *s, arg_incdec_pred *a) static bool trans_INCDECP_z(DisasContext *s, arg_incdec2_pred *a) { - if (a->esz == 0 || !dc_isar_feature(aa64_sve, s)) { + if (a->esz == 0 || !dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -3327,7 +3330,7 @@ static bool trans_INCDECP_z(DisasContext *s, arg_incdec2_pred *a) static bool trans_SINCDECP_r_32(DisasContext *s, arg_incdec_pred *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -3342,7 +3345,7 @@ static bool trans_SINCDECP_r_32(DisasContext *s, arg_incdec_pred *a) static bool trans_SINCDECP_r_64(DisasContext *s, arg_incdec_pred *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -3357,7 +3360,7 @@ static bool trans_SINCDECP_r_64(DisasContext *s, arg_incdec_pred *a) static bool trans_SINCDECP_z(DisasContext *s, arg_incdec2_pred *a) { - if (a->esz == 0 || !dc_isar_feature(aa64_sve, s)) { + if (a->esz == 0 || !dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -3374,7 +3377,7 @@ static bool trans_SINCDECP_z(DisasContext *s, arg_incdec2_pred *a) static bool trans_CTERM(DisasContext *s, arg_CTERM *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (!sve_access_check(s)) { @@ -3498,9 +3501,9 @@ static bool do_WHILE(DisasContext *s, arg_while *a, return true; } -TRANS_FEAT(WHILE_lt, aa64_sve, do_WHILE, +TRANS_FEAT(WHILE_lt, aa64_sme_or_sve, do_WHILE, a, true, 0, 0, gen_helper_sve_whilel) -TRANS_FEAT(WHILE_gt, aa64_sve2, do_WHILE, +TRANS_FEAT(WHILE_gt, aa64_sme_or_sve2, do_WHILE, a, false, 0, 0, gen_helper_sve_whileg) TRANS_FEAT(WHILE_lt_pair, aa64_sme2_or_sve2p1, do_WHILE, @@ -3525,7 +3528,7 @@ static bool trans_WHILE_ptr(DisasContext *s, arg_WHILE_ptr *a) unsigned vsz = vec_full_reg_size(s); unsigned desc = 0; - if (!dc_isar_feature(aa64_sve2, s)) { + if (!dc_isar_feature(aa64_sme_or_sve2, s)) { return false; } if (!sve_access_check(s)) { @@ -3618,7 +3621,7 @@ TRANS_FEAT(PEXT_2, aa64_sme2_or_sve2p1, do_pext, a, 2) static bool trans_FDUP(DisasContext *s, arg_FDUP *a) { - if (a->esz == 0 || !dc_isar_feature(aa64_sve, s)) { + if (a->esz == 0 || !dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -3635,7 +3638,7 @@ static bool trans_FDUP(DisasContext *s, arg_FDUP *a) static bool trans_DUP_i(DisasContext *s, arg_DUP_i *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -3646,7 +3649,7 @@ static bool trans_DUP_i(DisasContext *s, arg_DUP_i *a) return true; } -TRANS_FEAT(ADD_zzi, aa64_sve, gen_gvec_fn_arg_zzi, tcg_gen_gvec_addi, a) +TRANS_FEAT(ADD_zzi, aa64_sme_or_sve, gen_gvec_fn_arg_zzi, tcg_gen_gvec_addi, a) static bool trans_SUB_zzi(DisasContext *s, arg_rri_esz *a) { @@ -3685,7 +3688,7 @@ static bool trans_SUBR_zzi(DisasContext *s, arg_rri_esz *a) .scalar_first = true } }; - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -3697,7 +3700,7 @@ static bool trans_SUBR_zzi(DisasContext *s, arg_rri_esz *a) return true; } -TRANS_FEAT(MUL_zzi, aa64_sve, gen_gvec_fn_arg_zzi, tcg_gen_gvec_muli, a) +TRANS_FEAT(MUL_zzi, aa64_sme_or_sve, gen_gvec_fn_arg_zzi, tcg_gen_gvec_muli, a) static bool do_zzi_sat(DisasContext *s, arg_rri_esz *a, bool u, bool d) { @@ -3708,10 +3711,10 @@ static bool do_zzi_sat(DisasContext *s, arg_rri_esz *a, bool u, bool d) return true; } -TRANS_FEAT(SQADD_zzi, aa64_sve, do_zzi_sat, a, false, false) -TRANS_FEAT(UQADD_zzi, aa64_sve, do_zzi_sat, a, true, false) -TRANS_FEAT(SQSUB_zzi, aa64_sve, do_zzi_sat, a, false, true) -TRANS_FEAT(UQSUB_zzi, aa64_sve, do_zzi_sat, a, true, true) +TRANS_FEAT(SQADD_zzi, aa64_sme_or_sve, do_zzi_sat, a, false, false) +TRANS_FEAT(UQADD_zzi, aa64_sme_or_sve, do_zzi_sat, a, true, false) +TRANS_FEAT(SQSUB_zzi, aa64_sme_or_sve, do_zzi_sat, a, false, true) +TRANS_FEAT(UQSUB_zzi, aa64_sme_or_sve, do_zzi_sat, a, true, true) static bool do_zzi_ool(DisasContext *s, arg_rri_esz *a, gen_helper_gvec_2i *fn) { @@ -3729,7 +3732,7 @@ static bool do_zzi_ool(DisasContext *s, arg_rri_esz *a, gen_helper_gvec_2i *fn) gen_helper_sve_##name##i_b, gen_helper_sve_##name##i_h, \ gen_helper_sve_##name##i_s, gen_helper_sve_##name##i_d, \ }; \ - TRANS_FEAT(NAME##_zzi, aa64_sve, do_zzi_ool, a, name##i_fns[a->esz]) + TRANS_FEAT(NAME##_zzi, aa64_sme_or_sve, do_zzi_ool, a, name##i_fns[a->esz]) DO_ZZI(SMAX, smax) DO_ZZI(UMAX, umax) @@ -3742,25 +3745,25 @@ static gen_helper_gvec_4 * const dot_fns[2][2] = { { gen_helper_gvec_sdot_4b, gen_helper_gvec_sdot_4h }, { gen_helper_gvec_udot_4b, gen_helper_gvec_udot_4h } }; -TRANS_FEAT(DOT_zzzz, aa64_sve, gen_gvec_ool_zzzz, +TRANS_FEAT(DOT_zzzz, aa64_sme_or_sve, gen_gvec_ool_zzzz, dot_fns[a->u][a->sz], a->rd, a->rn, a->rm, a->ra, 0) /* * SVE Multiply - Indexed */ -TRANS_FEAT(SDOT_zzxw_4s, aa64_sve, gen_gvec_ool_arg_zzxz, +TRANS_FEAT(SDOT_zzxw_4s, aa64_sme_or_sve, gen_gvec_ool_arg_zzxz, gen_helper_gvec_sdot_idx_4b, a) -TRANS_FEAT(SDOT_zzxw_4d, aa64_sve, gen_gvec_ool_arg_zzxz, +TRANS_FEAT(SDOT_zzxw_4d, aa64_sme_or_sve, gen_gvec_ool_arg_zzxz, gen_helper_gvec_sdot_idx_4h, a) -TRANS_FEAT(UDOT_zzxw_4s, aa64_sve, gen_gvec_ool_arg_zzxz, +TRANS_FEAT(UDOT_zzxw_4s, aa64_sme_or_sve, gen_gvec_ool_arg_zzxz, gen_helper_gvec_udot_idx_4b, a) -TRANS_FEAT(UDOT_zzxw_4d, aa64_sve, gen_gvec_ool_arg_zzxz, +TRANS_FEAT(UDOT_zzxw_4d, aa64_sme_or_sve, gen_gvec_ool_arg_zzxz, gen_helper_gvec_udot_idx_4h, a) -TRANS_FEAT(SUDOT_zzxw_4s, aa64_sve_i8mm, gen_gvec_ool_arg_zzxz, +TRANS_FEAT(SUDOT_zzxw_4s, aa64_sme_sve_i8mm, gen_gvec_ool_arg_zzxz, gen_helper_gvec_sudot_idx_4b, a) -TRANS_FEAT(USDOT_zzxw_4s, aa64_sve_i8mm, gen_gvec_ool_arg_zzxz, +TRANS_FEAT(USDOT_zzxw_4s, aa64_sme_sve_i8mm, gen_gvec_ool_arg_zzxz, gen_helper_gvec_usdot_idx_4b, a) TRANS_FEAT(SDOT_zzxw_2s, aa64_sme2_or_sve2p1, gen_gvec_ool_arg_zzxz, @@ -3769,7 +3772,7 @@ TRANS_FEAT(UDOT_zzxw_2s, aa64_sme2_or_sve2p1, gen_gvec_ool_arg_zzxz, gen_helper_gvec_udot_idx_2h, a) #define DO_SVE2_RRX(NAME, FUNC) \ - TRANS_FEAT(NAME, aa64_sve, gen_gvec_ool_zzz, FUNC, \ + TRANS_FEAT(NAME, aa64_sme_or_sve2, gen_gvec_ool_zzz, FUNC, \ a->rd, a->rn, a->rm, a->index) DO_SVE2_RRX(MUL_zzx_h, gen_helper_gvec_mul_idx_h) @@ -3787,7 +3790,7 @@ DO_SVE2_RRX(SQRDMULH_zzx_d, gen_helper_sve2_sqrdmulh_idx_d) #undef DO_SVE2_RRX #define DO_SVE2_RRX_TB(NAME, FUNC, TOP) \ - TRANS_FEAT(NAME, aa64_sve, gen_gvec_ool_zzz, FUNC, \ + TRANS_FEAT(NAME, aa64_sme_or_sve2, gen_gvec_ool_zzz, FUNC, \ a->rd, a->rn, a->rm, (a->index << 1) | TOP) DO_SVE2_RRX_TB(SQDMULLB_zzx_s, gen_helper_sve2_sqdmull_idx_s, false) @@ -3808,7 +3811,7 @@ DO_SVE2_RRX_TB(UMULLT_zzx_d, gen_helper_sve2_umull_idx_d, true) #undef DO_SVE2_RRX_TB #define DO_SVE2_RRXR(NAME, FUNC) \ - TRANS_FEAT(NAME, aa64_sve2, gen_gvec_ool_arg_zzxz, FUNC, a) + TRANS_FEAT(NAME, aa64_sme_or_sve2, gen_gvec_ool_arg_zzxz, FUNC, a) DO_SVE2_RRXR(MLA_zzxz_h, gen_helper_gvec_mla_idx_h) DO_SVE2_RRXR(MLA_zzxz_s, gen_helper_gvec_mla_idx_s) @@ -3829,7 +3832,7 @@ DO_SVE2_RRXR(SQRDMLSH_zzxz_d, gen_helper_sve2_sqrdmlsh_idx_d) #undef DO_SVE2_RRXR #define DO_SVE2_RRXR_TB(NAME, FUNC, TOP) \ - TRANS_FEAT(NAME, aa64_sve2, gen_gvec_ool_zzzz, FUNC, \ + TRANS_FEAT(NAME, aa64_sme_or_sve2, gen_gvec_ool_zzzz, FUNC, \ a->rd, a->rn, a->rm, a->ra, (a->index << 1) | TOP) DO_SVE2_RRXR_TB(SQDMLALB_zzxw_s, gen_helper_sve2_sqdmlal_idx_s, false) @@ -3865,7 +3868,7 @@ DO_SVE2_RRXR_TB(UMLSLT_zzxw_d, gen_helper_sve2_umlsl_idx_d, true) #undef DO_SVE2_RRXR_TB #define DO_SVE2_RRXR_ROT(NAME, FUNC) \ - TRANS_FEAT(NAME, aa64_sve2, gen_gvec_ool_zzzz, FUNC, \ + TRANS_FEAT(NAME, aa64_sme_or_sve2, gen_gvec_ool_zzzz, FUNC, \ a->rd, a->rn, a->rm, a->ra, (a->index << 2) | a->rot) DO_SVE2_RRXR_ROT(CMLA_zzxz_h, gen_helper_sve2_cmla_idx_h) @@ -3898,7 +3901,7 @@ static gen_helper_gvec_4_ptr * const fmla_idx_fns[4] = { gen_helper_gvec_bfmla_idx, gen_helper_gvec_fmla_idx_h, gen_helper_gvec_fmla_idx_s, gen_helper_gvec_fmla_idx_d }; -TRANS_FEAT(FMLA_zzxz, aa64_sve, do_fmla_zzxz, a, fmla_idx_fns[a->esz]) +TRANS_FEAT(FMLA_zzxz, aa64_sme_or_sve, do_fmla_zzxz, a, fmla_idx_fns[a->esz]) static gen_helper_gvec_4_ptr * const fmls_idx_fns[4][2] = { { gen_helper_gvec_bfmls_idx, gen_helper_gvec_ah_bfmls_idx }, @@ -3906,7 +3909,7 @@ static gen_helper_gvec_4_ptr * const fmls_idx_fns[4][2] = { { gen_helper_gvec_fmls_idx_s, gen_helper_gvec_ah_fmls_idx_s }, { gen_helper_gvec_fmls_idx_d, gen_helper_gvec_ah_fmls_idx_d }, }; -TRANS_FEAT(FMLS_zzxz, aa64_sve, do_fmla_zzxz, a, +TRANS_FEAT(FMLS_zzxz, aa64_sme_or_sve, do_fmla_zzxz, a, fmls_idx_fns[a->esz][s->fpcr_ah]) /* @@ -3917,7 +3920,7 @@ static gen_helper_gvec_3_ptr * const fmul_idx_fns[4] = { gen_helper_gvec_fmul_idx_b16, gen_helper_gvec_fmul_idx_h, gen_helper_gvec_fmul_idx_s, gen_helper_gvec_fmul_idx_d, }; -TRANS_FEAT(FMUL_zzx, aa64_sve, gen_gvec_fpst_zzz, +TRANS_FEAT(FMUL_zzx, aa64_sme_or_sve, gen_gvec_fpst_zzz, fmul_idx_fns[a->esz], a->rd, a->rn, a->rm, a->index, a->esz == MO_16 ? FPST_A64_F16 : FPST_A64) @@ -3965,7 +3968,7 @@ static bool do_reduce(DisasContext *s, arg_rpr_esz *a, NULL, gen_helper_sve_##name##_h, \ gen_helper_sve_##name##_s, gen_helper_sve_##name##_d, \ }; \ - TRANS_FEAT(NAME, aa64_sve, do_reduce, a, name##_fns[a->esz]) + TRANS_FEAT(NAME, aa64_sme_or_sve, do_reduce, a, name##_fns[a->esz]) #define DO_VPZ_AH(NAME, name) \ static gen_helper_fp_reduce * const name##_fns[4] = { \ @@ -3976,7 +3979,7 @@ static bool do_reduce(DisasContext *s, arg_rpr_esz *a, NULL, gen_helper_sve_ah_##name##_h, \ gen_helper_sve_ah_##name##_s, gen_helper_sve_ah_##name##_d, \ }; \ - TRANS_FEAT(NAME, aa64_sve, do_reduce, a, \ + TRANS_FEAT(NAME, aa64_sme_or_sve, do_reduce, a, \ s->fpcr_ah ? name##_ah_fns[a->esz] : name##_fns[a->esz]) DO_VPZ(FADDV, faddv) @@ -4047,7 +4050,7 @@ static gen_helper_gvec_2_ptr * const frecpe_rpres_fns[] = { NULL, gen_helper_gvec_frecpe_h, gen_helper_gvec_frecpe_rpres_s, gen_helper_gvec_frecpe_d, }; -TRANS_FEAT(FRECPE, aa64_sve, gen_gvec_fpst_ah_arg_zz, +TRANS_FEAT(FRECPE, aa64_sme_or_sve, gen_gvec_fpst_ah_arg_zz, s->fpcr_ah && dc_isar_feature(aa64_rpres, s) ? frecpe_rpres_fns[a->esz] : frecpe_fns[a->esz], a, 0) @@ -4059,7 +4062,7 @@ static gen_helper_gvec_2_ptr * const frsqrte_rpres_fns[] = { NULL, gen_helper_gvec_frsqrte_h, gen_helper_gvec_frsqrte_rpres_s, gen_helper_gvec_frsqrte_d, }; -TRANS_FEAT(FRSQRTE, aa64_sve, gen_gvec_fpst_ah_arg_zz, +TRANS_FEAT(FRSQRTE, aa64_sme_or_sve, gen_gvec_fpst_ah_arg_zz, s->fpcr_ah && dc_isar_feature(aa64_rpres, s) ? frsqrte_rpres_fns[a->esz] : frsqrte_fns[a->esz], a, 0) @@ -4091,7 +4094,7 @@ static bool do_ppz_fp(DisasContext *s, arg_rpr_esz *a, NULL, gen_helper_sve_##name##_h, \ gen_helper_sve_##name##_s, gen_helper_sve_##name##_d, \ }; \ - TRANS_FEAT(NAME, aa64_sve, do_ppz_fp, a, name##_fns[a->esz]) + TRANS_FEAT(NAME, aa64_sme_or_sve, do_ppz_fp, a, name##_fns[a->esz]) DO_PPZ(FCMGE_ppz0, fcmge0) DO_PPZ(FCMGT_ppz0, fcmgt0) @@ -4164,7 +4167,7 @@ static bool trans_FADDA(DisasContext *s, arg_rprr_esz *a) gen_helper_gvec_##name##_b16, gen_helper_gvec_##name##_h, \ gen_helper_gvec_##name##_s, gen_helper_gvec_##name##_d \ }; \ - TRANS_FEAT(NAME, aa64_sve, gen_gvec_fpst_arg_zzz, name##_fns[a->esz], a, 0) + TRANS_FEAT(NAME, aa64_sme_or_sve, gen_gvec_fpst_arg_zzz, name##_fns[a->esz], a, 0) #define DO_FP3_AH(NAME, name) \ static gen_helper_gvec_3_ptr * const name##_fns[4] = { \ @@ -4175,7 +4178,7 @@ static bool trans_FADDA(DisasContext *s, arg_rprr_esz *a) NULL, gen_helper_gvec_ah_##name##_h, \ gen_helper_gvec_ah_##name##_s, gen_helper_gvec_ah_##name##_d \ }; \ - TRANS_FEAT(NAME, aa64_sve, gen_gvec_fpst_ah_arg_zzz, \ + TRANS_FEAT(NAME, aa64_sme_or_sve, gen_gvec_fpst_ah_arg_zzz, \ s->fpcr_ah ? name##_ah_fns[a->esz] : name##_fns[a->esz], a, 0) DO_FP3(FADD_zzz, fadd) @@ -4238,17 +4241,17 @@ TRANS_FEAT_NONSTREAMING(FTSMUL, aa64_sve, gen_gvec_fpst_arg_zzz, s->fpcr_ah ? name##_ah_zpzz_fns[a->esz] : \ name##_zpzz_fns[a->esz], a) -DO_ZPZZ_FP_B16(FADD_zpzz, aa64_sve, sve_fadd) -DO_ZPZZ_FP_B16(FSUB_zpzz, aa64_sve, sve_fsub) -DO_ZPZZ_FP_B16(FMUL_zpzz, aa64_sve, sve_fmul) -DO_ZPZZ_AH_FP_B16(FMIN_zpzz, aa64_sve, sve_fmin, sve_ah_fmin) -DO_ZPZZ_AH_FP_B16(FMAX_zpzz, aa64_sve, sve_fmax, sve_ah_fmax) -DO_ZPZZ_FP_B16(FMINNM_zpzz, aa64_sve, sve_fminnum) -DO_ZPZZ_FP_B16(FMAXNM_zpzz, aa64_sve, sve_fmaxnum) -DO_ZPZZ_AH_FP(FABD, aa64_sve, sve_fabd, sve_ah_fabd) -DO_ZPZZ_FP(FSCALE, aa64_sve, sve_fscalbn) -DO_ZPZZ_FP(FDIV, aa64_sve, sve_fdiv) -DO_ZPZZ_FP(FMULX, aa64_sve, sve_fmulx) +DO_ZPZZ_FP_B16(FADD_zpzz, aa64_sme_or_sve, sve_fadd) +DO_ZPZZ_FP_B16(FSUB_zpzz, aa64_sme_or_sve, sve_fsub) +DO_ZPZZ_FP_B16(FMUL_zpzz, aa64_sme_or_sve, sve_fmul) +DO_ZPZZ_AH_FP_B16(FMIN_zpzz, aa64_sme_or_sve, sve_fmin, sve_ah_fmin) +DO_ZPZZ_AH_FP_B16(FMAX_zpzz, aa64_sme_or_sve, sve_fmax, sve_ah_fmax) +DO_ZPZZ_FP_B16(FMINNM_zpzz, aa64_sme_or_sve, sve_fminnum) +DO_ZPZZ_FP_B16(FMAXNM_zpzz, aa64_sme_or_sve, sve_fmaxnum) +DO_ZPZZ_AH_FP(FABD, aa64_sme_or_sve, sve_fabd, sve_ah_fabd) +DO_ZPZZ_FP(FSCALE, aa64_sme_or_sve, sve_fscalbn) +DO_ZPZZ_FP(FDIV, aa64_sme_or_sve, sve_fdiv) +DO_ZPZZ_FP(FMULX, aa64_sme_or_sve, sve_fmulx) typedef void gen_helper_sve_fp2scalar(TCGv_ptr, TCGv_ptr, TCGv_ptr, TCGv_i64, TCGv_ptr, TCGv_i32); @@ -4297,7 +4300,7 @@ static bool do_fp_imm(DisasContext *s, arg_rpri_esz *a, uint64_t imm, { float32_##const0, float32_##const1 }, \ { float64_##const0, float64_##const1 }, \ }; \ - TRANS_FEAT(NAME##_zpzi, aa64_sve, do_fp_imm, a, \ + TRANS_FEAT(NAME##_zpzi, aa64_sme_or_sve, do_fp_imm, a, \ name##_const[a->esz][a->imm], name##_fns[a->esz]) #define DO_FP_AH_IMM(NAME, name, const0, const1) \ @@ -4317,7 +4320,7 @@ static bool do_fp_imm(DisasContext *s, arg_rpri_esz *a, uint64_t imm, { float32_##const0, float32_##const1 }, \ { float64_##const0, float64_##const1 }, \ }; \ - TRANS_FEAT(NAME##_zpzi, aa64_sve, do_fp_imm, a, \ + TRANS_FEAT(NAME##_zpzi, aa64_sme_or_sve, do_fp_imm, a, \ name##_const[a->esz][a->imm], \ s->fpcr_ah ? name##_ah_fns[a->esz] : name##_fns[a->esz]) @@ -4355,7 +4358,7 @@ static bool do_fp_cmp(DisasContext *s, arg_rprr_esz *a, NULL, gen_helper_sve_##name##_h, \ gen_helper_sve_##name##_s, gen_helper_sve_##name##_d \ }; \ - TRANS_FEAT(NAME##_ppzz, aa64_sve, do_fp_cmp, a, name##_fns[a->esz]) + TRANS_FEAT(NAME##_ppzz, aa64_sme_or_sve, do_fp_cmp, a, name##_fns[a->esz]) DO_FPCMP(FCMGE, fcmge) DO_FPCMP(FCMGT, fcmgt) @@ -4371,7 +4374,7 @@ static gen_helper_gvec_4_ptr * const fcadd_fns[] = { NULL, gen_helper_sve_fcadd_h, gen_helper_sve_fcadd_s, gen_helper_sve_fcadd_d, }; -TRANS_FEAT(FCADD, aa64_sve, gen_gvec_fpst_zzzp, fcadd_fns[a->esz], +TRANS_FEAT(FCADD, aa64_sme_or_sve, gen_gvec_fpst_zzzp, fcadd_fns[a->esz], a->rd, a->rn, a->rm, a->pg, a->rot | (s->fpcr_ah << 1), a->esz == MO_16 ? FPST_A64_F16 : FPST_A64) @@ -4395,7 +4398,7 @@ static bool do_fmla_zpzzz(DisasContext *s, arg_rprrr_esz *a, gen_helper_sve_##ah_name##_b16, gen_helper_sve_##ah_name##_h, \ gen_helper_sve_##ah_name##_s, gen_helper_sve_##ah_name##_d \ }; \ - TRANS_FEAT(NAME, aa64_sve, do_fmla_zpzzz, a, \ + TRANS_FEAT(NAME, aa64_sme_or_sve, do_fmla_zpzzz, a, \ s->fpcr_ah ? name##_ah_fns[a->esz] : name##_fns[a->esz]) /* We don't need an ah_fmla_zpzzz because fmla doesn't negate anything */ @@ -4410,14 +4413,14 @@ static gen_helper_gvec_5_ptr * const fcmla_fns[4] = { NULL, gen_helper_sve_fcmla_zpzzz_h, gen_helper_sve_fcmla_zpzzz_s, gen_helper_sve_fcmla_zpzzz_d, }; -TRANS_FEAT(FCMLA_zpzzz, aa64_sve, gen_gvec_fpst_zzzzp, fcmla_fns[a->esz], +TRANS_FEAT(FCMLA_zpzzz, aa64_sme_or_sve, gen_gvec_fpst_zzzzp, fcmla_fns[a->esz], a->rd, a->rn, a->rm, a->ra, a->pg, a->rot | (s->fpcr_ah << 2), a->esz == MO_16 ? FPST_A64_F16 : FPST_A64) static gen_helper_gvec_4_ptr * const fcmla_idx_fns[4] = { NULL, gen_helper_gvec_fcmlah_idx, gen_helper_gvec_fcmlas_idx, NULL }; -TRANS_FEAT(FCMLA_zzxz, aa64_sve, gen_gvec_fpst_zzzz, fcmla_idx_fns[a->esz], +TRANS_FEAT(FCMLA_zzxz, aa64_sme_or_sve, gen_gvec_fpst_zzzz, fcmla_idx_fns[a->esz], a->rd, a->rn, a->rm, a->ra, a->index * 4 + a->rot, a->esz == MO_16 ? FPST_A64_F16 : FPST_A64) @@ -4425,53 +4428,53 @@ TRANS_FEAT(FCMLA_zzxz, aa64_sve, gen_gvec_fpst_zzzz, fcmla_idx_fns[a->esz], *** SVE Floating Point Unary Operations Predicated Group */ -TRANS_FEAT(FCVT_sh, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVT_sh, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvt_sh, a, 0, FPST_A64) -TRANS_FEAT(FCVT_hs, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVT_hs, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvt_hs, a, 0, FPST_A64_F16) -TRANS_FEAT(BFCVT, aa64_sve_bf16, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(BFCVT, aa64_sme_sve_bf16, gen_gvec_fpst_arg_zpz, gen_helper_sve_bfcvt, a, 0, s->fpcr_ah ? FPST_AH : FPST_A64) -TRANS_FEAT(FCVT_dh, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVT_dh, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvt_dh, a, 0, FPST_A64) -TRANS_FEAT(FCVT_hd, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVT_hd, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvt_hd, a, 0, FPST_A64_F16) -TRANS_FEAT(FCVT_ds, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVT_ds, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvt_ds, a, 0, FPST_A64) -TRANS_FEAT(FCVT_sd, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVT_sd, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvt_sd, a, 0, FPST_A64) -TRANS_FEAT(FCVTZS_hh, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVTZS_hh, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvtzs_hh, a, 0, FPST_A64_F16) -TRANS_FEAT(FCVTZU_hh, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVTZU_hh, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvtzu_hh, a, 0, FPST_A64_F16) -TRANS_FEAT(FCVTZS_hs, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVTZS_hs, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvtzs_hs, a, 0, FPST_A64_F16) -TRANS_FEAT(FCVTZU_hs, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVTZU_hs, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvtzu_hs, a, 0, FPST_A64_F16) -TRANS_FEAT(FCVTZS_hd, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVTZS_hd, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvtzs_hd, a, 0, FPST_A64_F16) -TRANS_FEAT(FCVTZU_hd, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVTZU_hd, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvtzu_hd, a, 0, FPST_A64_F16) -TRANS_FEAT(FCVTZS_ss, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVTZS_ss, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvtzs_ss, a, 0, FPST_A64) -TRANS_FEAT(FCVTZU_ss, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVTZU_ss, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvtzu_ss, a, 0, FPST_A64) -TRANS_FEAT(FCVTZS_sd, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVTZS_sd, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvtzs_sd, a, 0, FPST_A64) -TRANS_FEAT(FCVTZU_sd, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVTZU_sd, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvtzu_sd, a, 0, FPST_A64) -TRANS_FEAT(FCVTZS_ds, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVTZS_ds, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvtzs_ds, a, 0, FPST_A64) -TRANS_FEAT(FCVTZU_ds, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVTZU_ds, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvtzu_ds, a, 0, FPST_A64) -TRANS_FEAT(FCVTZS_dd, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVTZS_dd, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvtzs_dd, a, 0, FPST_A64) -TRANS_FEAT(FCVTZU_dd, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVTZU_dd, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_fcvtzu_dd, a, 0, FPST_A64) static gen_helper_gvec_3_ptr * const frint_fns[] = { @@ -4480,7 +4483,7 @@ static gen_helper_gvec_3_ptr * const frint_fns[] = { gen_helper_sve_frint_s, gen_helper_sve_frint_d }; -TRANS_FEAT(FRINTI, aa64_sve, gen_gvec_fpst_arg_zpz, frint_fns[a->esz], +TRANS_FEAT(FRINTI, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, frint_fns[a->esz], a, 0, a->esz == MO_16 ? FPST_A64_F16 : FPST_A64) static gen_helper_gvec_3_ptr * const frintx_fns[] = { @@ -4489,7 +4492,7 @@ static gen_helper_gvec_3_ptr * const frintx_fns[] = { gen_helper_sve_frintx_s, gen_helper_sve_frintx_d }; -TRANS_FEAT(FRINTX, aa64_sve, gen_gvec_fpst_arg_zpz, frintx_fns[a->esz], +TRANS_FEAT(FRINTX, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, frintx_fns[a->esz], a, 0, a->esz == MO_16 ? FPST_A64_F16 : FPST_A64); static bool do_frint_mode(DisasContext *s, arg_rpr_esz *a, @@ -4519,63 +4522,63 @@ static bool do_frint_mode(DisasContext *s, arg_rpr_esz *a, return true; } -TRANS_FEAT(FRINTN, aa64_sve, do_frint_mode, a, +TRANS_FEAT(FRINTN, aa64_sme_or_sve, do_frint_mode, a, FPROUNDING_TIEEVEN, frint_fns[a->esz]) -TRANS_FEAT(FRINTP, aa64_sve, do_frint_mode, a, +TRANS_FEAT(FRINTP, aa64_sme_or_sve, do_frint_mode, a, FPROUNDING_POSINF, frint_fns[a->esz]) -TRANS_FEAT(FRINTM, aa64_sve, do_frint_mode, a, +TRANS_FEAT(FRINTM, aa64_sme_or_sve, do_frint_mode, a, FPROUNDING_NEGINF, frint_fns[a->esz]) -TRANS_FEAT(FRINTZ, aa64_sve, do_frint_mode, a, +TRANS_FEAT(FRINTZ, aa64_sme_or_sve, do_frint_mode, a, FPROUNDING_ZERO, frint_fns[a->esz]) -TRANS_FEAT(FRINTA, aa64_sve, do_frint_mode, a, +TRANS_FEAT(FRINTA, aa64_sme_or_sve, do_frint_mode, a, FPROUNDING_TIEAWAY, frint_fns[a->esz]) static gen_helper_gvec_3_ptr * const frecpx_fns[] = { NULL, gen_helper_sve_frecpx_h, gen_helper_sve_frecpx_s, gen_helper_sve_frecpx_d, }; -TRANS_FEAT(FRECPX, aa64_sve, gen_gvec_fpst_arg_zpz, frecpx_fns[a->esz], +TRANS_FEAT(FRECPX, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, frecpx_fns[a->esz], a, 0, select_ah_fpst(s, a->esz)) static gen_helper_gvec_3_ptr * const fsqrt_fns[] = { NULL, gen_helper_sve_fsqrt_h, gen_helper_sve_fsqrt_s, gen_helper_sve_fsqrt_d, }; -TRANS_FEAT(FSQRT, aa64_sve, gen_gvec_fpst_arg_zpz, fsqrt_fns[a->esz], +TRANS_FEAT(FSQRT, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, fsqrt_fns[a->esz], a, 0, a->esz == MO_16 ? FPST_A64_F16 : FPST_A64) -TRANS_FEAT(SCVTF_hh, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(SCVTF_hh, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_scvt_hh, a, 0, FPST_A64_F16) -TRANS_FEAT(SCVTF_sh, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(SCVTF_sh, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_scvt_sh, a, 0, FPST_A64_F16) -TRANS_FEAT(SCVTF_dh, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(SCVTF_dh, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_scvt_dh, a, 0, FPST_A64_F16) -TRANS_FEAT(SCVTF_ss, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(SCVTF_ss, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_scvt_ss, a, 0, FPST_A64) -TRANS_FEAT(SCVTF_ds, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(SCVTF_ds, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_scvt_ds, a, 0, FPST_A64) -TRANS_FEAT(SCVTF_sd, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(SCVTF_sd, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_scvt_sd, a, 0, FPST_A64) -TRANS_FEAT(SCVTF_dd, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(SCVTF_dd, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_scvt_dd, a, 0, FPST_A64) -TRANS_FEAT(UCVTF_hh, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(UCVTF_hh, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_ucvt_hh, a, 0, FPST_A64_F16) -TRANS_FEAT(UCVTF_sh, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(UCVTF_sh, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_ucvt_sh, a, 0, FPST_A64_F16) -TRANS_FEAT(UCVTF_dh, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(UCVTF_dh, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_ucvt_dh, a, 0, FPST_A64_F16) -TRANS_FEAT(UCVTF_ss, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(UCVTF_ss, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_ucvt_ss, a, 0, FPST_A64) -TRANS_FEAT(UCVTF_ds, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(UCVTF_ds, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_ucvt_ds, a, 0, FPST_A64) -TRANS_FEAT(UCVTF_sd, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(UCVTF_sd, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_ucvt_sd, a, 0, FPST_A64) -TRANS_FEAT(UCVTF_dd, aa64_sve, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(UCVTF_dd, aa64_sme_or_sve, gen_gvec_fpst_arg_zpz, gen_helper_sve_ucvt_dd, a, 0, FPST_A64) /* @@ -4803,7 +4806,7 @@ void gen_sve_str(DisasContext *s, TCGv_ptr base, int vofs, static bool trans_LDR_zri(DisasContext *s, arg_rri *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -4817,7 +4820,7 @@ static bool trans_LDR_zri(DisasContext *s, arg_rri *a) static bool trans_LDR_pri(DisasContext *s, arg_rri *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -4831,7 +4834,7 @@ static bool trans_LDR_pri(DisasContext *s, arg_rri *a) static bool trans_STR_zri(DisasContext *s, arg_rri *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -4845,7 +4848,7 @@ static bool trans_STR_zri(DisasContext *s, arg_rri *a) static bool trans_STR_pri(DisasContext *s, arg_rri *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -5101,7 +5104,7 @@ static bool trans_LD_zprr(DisasContext *s, arg_rprr_load *a) /* dtypes 16-18 are artificial, representing 128-bit element */ switch (a->dtype) { case 0 ... 15: - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } break; @@ -5134,7 +5137,7 @@ static bool trans_LD_zpri(DisasContext *s, arg_rpri_load *a) /* dtypes 16-18 are artificial, representing 128-bit element */ switch (a->dtype) { case 0 ... 15: - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } break; @@ -5420,7 +5423,7 @@ static void do_ldrq(DisasContext *s, int zt, int pg, TCGv_i64 addr, int dtype) static bool trans_LD1RQ_zprr(DisasContext *s, arg_rprr_load *a) { - if (a->rm == 31 || !dc_isar_feature(aa64_sve, s)) { + if (a->rm == 31 || !dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -5435,7 +5438,7 @@ static bool trans_LD1RQ_zprr(DisasContext *s, arg_rprr_load *a) static bool trans_LD1RQ_zpri(DisasContext *s, arg_rpri_load *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (sve_access_check(s)) { @@ -5554,7 +5557,7 @@ static bool trans_LD1R_zpri(DisasContext *s, arg_rpri_load *a) TCGv_i64 temp, clean_addr; MemOp memop; - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } if (!sve_access_check(s)) { @@ -5746,7 +5749,7 @@ static bool trans_ST_zprr(DisasContext *s, arg_rprr_store *a) } switch (a->esz) { case MO_8 ... MO_64: - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } break; @@ -5783,7 +5786,7 @@ static bool trans_ST_zpri(DisasContext *s, arg_rpri_store *a) } switch (a->esz) { case MO_8 ... MO_64: - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } break; @@ -6525,7 +6528,7 @@ static bool trans_STNT1_zprz(DisasContext *s, arg_ST1_zprz *a) static bool trans_PRF(DisasContext *s, arg_PRF *a) { - if (!dc_isar_feature(aa64_sve, s)) { + if (!dc_isar_feature(aa64_sme_or_sve, s)) { return false; } /* Prefetch is a nop within QEMU. */ @@ -6535,7 +6538,7 @@ static bool trans_PRF(DisasContext *s, arg_PRF *a) static bool trans_PRF_rr(DisasContext *s, arg_PRF_rr *a) { - if (a->rm == 31 || !dc_isar_feature(aa64_sve, s)) { + if (a->rm == 31 || !dc_isar_feature(aa64_sme_or_sve, s)) { return false; } /* Prefetch is a nop within QEMU. */ @@ -6568,39 +6571,39 @@ static bool trans_PRF_ns(DisasContext *s, arg_PRF_ns *a) * In the meantime, just emit the moves. */ -TRANS_FEAT(MOVPRFX, aa64_sve, do_mov_z, a->rd, a->rn) -TRANS_FEAT(MOVPRFX_m, aa64_sve, do_sel_z, a->rd, a->rn, a->rd, a->pg, a->esz) -TRANS_FEAT(MOVPRFX_z, aa64_sve, do_movz_zpz, a->rd, a->rn, a->pg, a->esz, false) +TRANS_FEAT(MOVPRFX, aa64_sme_or_sve, do_mov_z, a->rd, a->rn) +TRANS_FEAT(MOVPRFX_m, aa64_sme_or_sve, do_sel_z, a->rd, a->rn, a->rd, a->pg, a->esz) +TRANS_FEAT(MOVPRFX_z, aa64_sme_or_sve, do_movz_zpz, a->rd, a->rn, a->pg, a->esz, false) /* * SVE2 Integer Multiply - Unpredicated */ -TRANS_FEAT(MUL_zzz, aa64_sve2, gen_gvec_fn_arg_zzz, tcg_gen_gvec_mul, a) -TRANS_FEAT(SQDMULH_zzz, aa64_sve2, gen_gvec_fn_arg_zzz, gen_gvec_sve2_sqdmulh, a) +TRANS_FEAT(MUL_zzz, aa64_sme_or_sve2, gen_gvec_fn_arg_zzz, tcg_gen_gvec_mul, a) +TRANS_FEAT(SQDMULH_zzz, aa64_sme_or_sve2, gen_gvec_fn_arg_zzz, gen_gvec_sve2_sqdmulh, a) static gen_helper_gvec_3 * const smulh_zzz_fns[4] = { gen_helper_gvec_smulh_b, gen_helper_gvec_smulh_h, gen_helper_gvec_smulh_s, gen_helper_gvec_smulh_d, }; -TRANS_FEAT(SMULH_zzz, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(SMULH_zzz, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, smulh_zzz_fns[a->esz], a, 0) static gen_helper_gvec_3 * const umulh_zzz_fns[4] = { gen_helper_gvec_umulh_b, gen_helper_gvec_umulh_h, gen_helper_gvec_umulh_s, gen_helper_gvec_umulh_d, }; -TRANS_FEAT(UMULH_zzz, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(UMULH_zzz, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, umulh_zzz_fns[a->esz], a, 0) -TRANS_FEAT(PMUL_zzz, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(PMUL_zzz, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, gen_helper_gvec_pmul_b, a, 0) static gen_helper_gvec_3 * const sqrdmulh_zzz_fns[4] = { gen_helper_sve2_sqrdmulh_b, gen_helper_sve2_sqrdmulh_h, gen_helper_sve2_sqrdmulh_s, gen_helper_sve2_sqrdmulh_d, }; -TRANS_FEAT(SQRDMULH_zzz, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(SQRDMULH_zzz, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, sqrdmulh_zzz_fns[a->esz], a, 0) /* @@ -6611,66 +6614,66 @@ static gen_helper_gvec_4 * const sadlp_fns[4] = { NULL, gen_helper_sve2_sadalp_zpzz_h, gen_helper_sve2_sadalp_zpzz_s, gen_helper_sve2_sadalp_zpzz_d, }; -TRANS_FEAT(SADALP_zpzz, aa64_sve2, gen_gvec_ool_arg_zpzz, +TRANS_FEAT(SADALP_zpzz, aa64_sme_or_sve2, gen_gvec_ool_arg_zpzz, sadlp_fns[a->esz], a, 0) static gen_helper_gvec_4 * const uadlp_fns[4] = { NULL, gen_helper_sve2_uadalp_zpzz_h, gen_helper_sve2_uadalp_zpzz_s, gen_helper_sve2_uadalp_zpzz_d, }; -TRANS_FEAT(UADALP_zpzz, aa64_sve2, gen_gvec_ool_arg_zpzz, +TRANS_FEAT(UADALP_zpzz, aa64_sme_or_sve2, gen_gvec_ool_arg_zpzz, uadlp_fns[a->esz], a, 0) /* * SVE2 integer unary operations (predicated) */ -TRANS_FEAT(URECPE, aa64_sve2, gen_gvec_ool_arg_zpz, +TRANS_FEAT(URECPE, aa64_sme_or_sve2, gen_gvec_ool_arg_zpz, a->esz == 2 ? gen_helper_sve2_urecpe_s : NULL, a, 0) -TRANS_FEAT(URSQRTE, aa64_sve2, gen_gvec_ool_arg_zpz, +TRANS_FEAT(URSQRTE, aa64_sme_or_sve2, gen_gvec_ool_arg_zpz, a->esz == 2 ? gen_helper_sve2_ursqrte_s : NULL, a, 0) static gen_helper_gvec_3 * const sqabs_fns[4] = { gen_helper_sve2_sqabs_b, gen_helper_sve2_sqabs_h, gen_helper_sve2_sqabs_s, gen_helper_sve2_sqabs_d, }; -TRANS_FEAT(SQABS, aa64_sve2, gen_gvec_ool_arg_zpz, sqabs_fns[a->esz], a, 0) +TRANS_FEAT(SQABS, aa64_sme_or_sve2, gen_gvec_ool_arg_zpz, sqabs_fns[a->esz], a, 0) static gen_helper_gvec_3 * const sqneg_fns[4] = { gen_helper_sve2_sqneg_b, gen_helper_sve2_sqneg_h, gen_helper_sve2_sqneg_s, gen_helper_sve2_sqneg_d, }; -TRANS_FEAT(SQNEG, aa64_sve2, gen_gvec_ool_arg_zpz, sqneg_fns[a->esz], a, 0) +TRANS_FEAT(SQNEG, aa64_sme_or_sve2, gen_gvec_ool_arg_zpz, sqneg_fns[a->esz], a, 0) -DO_ZPZZ(SQSHL, aa64_sve2, sve2_sqshl) -DO_ZPZZ(SQRSHL, aa64_sve2, sve2_sqrshl) -DO_ZPZZ(SRSHL, aa64_sve2, sve2_srshl) +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) -DO_ZPZZ(UQSHL, aa64_sve2, sve2_uqshl) -DO_ZPZZ(UQRSHL, aa64_sve2, sve2_uqrshl) -DO_ZPZZ(URSHL, aa64_sve2, sve2_urshl) +DO_ZPZZ(UQSHL, aa64_sme_or_sve2, sve2_uqshl) +DO_ZPZZ(UQRSHL, aa64_sme_or_sve2, sve2_uqrshl) +DO_ZPZZ(URSHL, aa64_sme_or_sve2, sve2_urshl) -DO_ZPZZ(SHADD, aa64_sve2, sve2_shadd) -DO_ZPZZ(SRHADD, aa64_sve2, sve2_srhadd) -DO_ZPZZ(SHSUB, aa64_sve2, sve2_shsub) +DO_ZPZZ(SHADD, aa64_sme_or_sve2, sve2_shadd) +DO_ZPZZ(SRHADD, aa64_sme_or_sve2, sve2_srhadd) +DO_ZPZZ(SHSUB, aa64_sme_or_sve2, sve2_shsub) -DO_ZPZZ(UHADD, aa64_sve2, sve2_uhadd) -DO_ZPZZ(URHADD, aa64_sve2, sve2_urhadd) -DO_ZPZZ(UHSUB, aa64_sve2, sve2_uhsub) +DO_ZPZZ(UHADD, aa64_sme_or_sve2, sve2_uhadd) +DO_ZPZZ(URHADD, aa64_sme_or_sve2, sve2_urhadd) +DO_ZPZZ(UHSUB, aa64_sme_or_sve2, sve2_uhsub) -DO_ZPZZ(ADDP, aa64_sve2, sve2_addp) -DO_ZPZZ(SMAXP, aa64_sve2, sve2_smaxp) -DO_ZPZZ(UMAXP, aa64_sve2, sve2_umaxp) -DO_ZPZZ(SMINP, aa64_sve2, sve2_sminp) -DO_ZPZZ(UMINP, aa64_sve2, sve2_uminp) +DO_ZPZZ(ADDP, aa64_sme_or_sve2, sve2_addp) +DO_ZPZZ(SMAXP, aa64_sme_or_sve2, sve2_smaxp) +DO_ZPZZ(UMAXP, aa64_sme_or_sve2, sve2_umaxp) +DO_ZPZZ(SMINP, aa64_sme_or_sve2, sve2_sminp) +DO_ZPZZ(UMINP, aa64_sme_or_sve2, sve2_uminp) -DO_ZPZZ(SQADD_zpzz, aa64_sve2, sve2_sqadd) -DO_ZPZZ(UQADD_zpzz, aa64_sve2, sve2_uqadd) -DO_ZPZZ(SQSUB_zpzz, aa64_sve2, sve2_sqsub) -DO_ZPZZ(UQSUB_zpzz, aa64_sve2, sve2_uqsub) -DO_ZPZZ(SUQADD, aa64_sve2, sve2_suqadd) -DO_ZPZZ(USQADD, aa64_sve2, sve2_usqadd) +DO_ZPZZ(SQADD_zpzz, aa64_sme_or_sve2, sve2_sqadd) +DO_ZPZZ(UQADD_zpzz, aa64_sme_or_sve2, sve2_uqadd) +DO_ZPZZ(SQSUB_zpzz, aa64_sme_or_sve2, sve2_sqsub) +DO_ZPZZ(UQSUB_zpzz, aa64_sme_or_sve2, sve2_uqsub) +DO_ZPZZ(SUQADD, aa64_sme_or_sve2, sve2_suqadd) +DO_ZPZZ(USQADD, aa64_sme_or_sve2, sve2_usqadd) /* * SVE2 Widening Integer Arithmetic @@ -6680,95 +6683,95 @@ static gen_helper_gvec_3 * const saddl_fns[4] = { NULL, gen_helper_sve2_saddl_h, gen_helper_sve2_saddl_s, gen_helper_sve2_saddl_d, }; -TRANS_FEAT(SADDLB, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(SADDLB, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, saddl_fns[a->esz], a, 0) -TRANS_FEAT(SADDLT, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(SADDLT, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, saddl_fns[a->esz], a, 3) -TRANS_FEAT(SADDLBT, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(SADDLBT, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, saddl_fns[a->esz], a, 2) static gen_helper_gvec_3 * const ssubl_fns[4] = { NULL, gen_helper_sve2_ssubl_h, gen_helper_sve2_ssubl_s, gen_helper_sve2_ssubl_d, }; -TRANS_FEAT(SSUBLB, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(SSUBLB, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, ssubl_fns[a->esz], a, 0) -TRANS_FEAT(SSUBLT, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(SSUBLT, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, ssubl_fns[a->esz], a, 3) -TRANS_FEAT(SSUBLBT, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(SSUBLBT, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, ssubl_fns[a->esz], a, 2) -TRANS_FEAT(SSUBLTB, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(SSUBLTB, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, ssubl_fns[a->esz], a, 1) static gen_helper_gvec_3 * const sabdl_fns[4] = { NULL, gen_helper_sve2_sabdl_h, gen_helper_sve2_sabdl_s, gen_helper_sve2_sabdl_d, }; -TRANS_FEAT(SABDLB, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(SABDLB, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, sabdl_fns[a->esz], a, 0) -TRANS_FEAT(SABDLT, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(SABDLT, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, sabdl_fns[a->esz], a, 3) static gen_helper_gvec_3 * const uaddl_fns[4] = { NULL, gen_helper_sve2_uaddl_h, gen_helper_sve2_uaddl_s, gen_helper_sve2_uaddl_d, }; -TRANS_FEAT(UADDLB, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(UADDLB, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, uaddl_fns[a->esz], a, 0) -TRANS_FEAT(UADDLT, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(UADDLT, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, uaddl_fns[a->esz], a, 3) static gen_helper_gvec_3 * const usubl_fns[4] = { NULL, gen_helper_sve2_usubl_h, gen_helper_sve2_usubl_s, gen_helper_sve2_usubl_d, }; -TRANS_FEAT(USUBLB, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(USUBLB, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, usubl_fns[a->esz], a, 0) -TRANS_FEAT(USUBLT, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(USUBLT, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, usubl_fns[a->esz], a, 3) static gen_helper_gvec_3 * const uabdl_fns[4] = { NULL, gen_helper_sve2_uabdl_h, gen_helper_sve2_uabdl_s, gen_helper_sve2_uabdl_d, }; -TRANS_FEAT(UABDLB, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(UABDLB, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, uabdl_fns[a->esz], a, 0) -TRANS_FEAT(UABDLT, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(UABDLT, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, uabdl_fns[a->esz], a, 3) static gen_helper_gvec_3 * const sqdmull_fns[4] = { NULL, gen_helper_sve2_sqdmull_zzz_h, gen_helper_sve2_sqdmull_zzz_s, gen_helper_sve2_sqdmull_zzz_d, }; -TRANS_FEAT(SQDMULLB_zzz, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(SQDMULLB_zzz, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, sqdmull_fns[a->esz], a, 0) -TRANS_FEAT(SQDMULLT_zzz, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(SQDMULLT_zzz, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, sqdmull_fns[a->esz], a, 3) static gen_helper_gvec_3 * const smull_fns[4] = { NULL, gen_helper_sve2_smull_zzz_h, gen_helper_sve2_smull_zzz_s, gen_helper_sve2_smull_zzz_d, }; -TRANS_FEAT(SMULLB_zzz, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(SMULLB_zzz, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, smull_fns[a->esz], a, 0) -TRANS_FEAT(SMULLT_zzz, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(SMULLT_zzz, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, smull_fns[a->esz], a, 3) static gen_helper_gvec_3 * const umull_fns[4] = { NULL, gen_helper_sve2_umull_zzz_h, gen_helper_sve2_umull_zzz_s, gen_helper_sve2_umull_zzz_d, }; -TRANS_FEAT(UMULLB_zzz, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(UMULLB_zzz, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, umull_fns[a->esz], a, 0) -TRANS_FEAT(UMULLT_zzz, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(UMULLT_zzz, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, umull_fns[a->esz], a, 3) static gen_helper_gvec_3 * const eoril_fns[4] = { gen_helper_sve2_eoril_b, gen_helper_sve2_eoril_h, gen_helper_sve2_eoril_s, gen_helper_sve2_eoril_d, }; -TRANS_FEAT(EORBT, aa64_sve2, gen_gvec_ool_arg_zzz, eoril_fns[a->esz], a, 2) -TRANS_FEAT(EORTB, aa64_sve2, gen_gvec_ool_arg_zzz, eoril_fns[a->esz], a, 1) +TRANS_FEAT(EORBT, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, eoril_fns[a->esz], a, 2) +TRANS_FEAT(EORTB, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, eoril_fns[a->esz], a, 1) static bool do_trans_pmull(DisasContext *s, arg_rrr_esz *a, bool sel) { @@ -6782,42 +6785,40 @@ static bool do_trans_pmull(DisasContext *s, arg_rrr_esz *a, bool sel) return false; } s->is_nonstreaming = true; - } else if (!dc_isar_feature(aa64_sve, s)) { - return false; } return gen_gvec_ool_arg_zzz(s, fns[a->esz], a, sel); } -TRANS_FEAT(PMULLB, aa64_sve2, do_trans_pmull, a, false) -TRANS_FEAT(PMULLT, aa64_sve2, do_trans_pmull, a, true) +TRANS_FEAT(PMULLB, aa64_sme_or_sve2, do_trans_pmull, a, false) +TRANS_FEAT(PMULLT, aa64_sme_or_sve2, do_trans_pmull, a, true) static gen_helper_gvec_3 * const saddw_fns[4] = { NULL, gen_helper_sve2_saddw_h, gen_helper_sve2_saddw_s, gen_helper_sve2_saddw_d, }; -TRANS_FEAT(SADDWB, aa64_sve2, gen_gvec_ool_arg_zzz, saddw_fns[a->esz], a, 0) -TRANS_FEAT(SADDWT, aa64_sve2, gen_gvec_ool_arg_zzz, saddw_fns[a->esz], a, 1) +TRANS_FEAT(SADDWB, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, saddw_fns[a->esz], a, 0) +TRANS_FEAT(SADDWT, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, saddw_fns[a->esz], a, 1) static gen_helper_gvec_3 * const ssubw_fns[4] = { NULL, gen_helper_sve2_ssubw_h, gen_helper_sve2_ssubw_s, gen_helper_sve2_ssubw_d, }; -TRANS_FEAT(SSUBWB, aa64_sve2, gen_gvec_ool_arg_zzz, ssubw_fns[a->esz], a, 0) -TRANS_FEAT(SSUBWT, aa64_sve2, gen_gvec_ool_arg_zzz, ssubw_fns[a->esz], a, 1) +TRANS_FEAT(SSUBWB, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, ssubw_fns[a->esz], a, 0) +TRANS_FEAT(SSUBWT, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, ssubw_fns[a->esz], a, 1) static gen_helper_gvec_3 * const uaddw_fns[4] = { NULL, gen_helper_sve2_uaddw_h, gen_helper_sve2_uaddw_s, gen_helper_sve2_uaddw_d, }; -TRANS_FEAT(UADDWB, aa64_sve2, gen_gvec_ool_arg_zzz, uaddw_fns[a->esz], a, 0) -TRANS_FEAT(UADDWT, aa64_sve2, gen_gvec_ool_arg_zzz, uaddw_fns[a->esz], a, 1) +TRANS_FEAT(UADDWB, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, uaddw_fns[a->esz], a, 0) +TRANS_FEAT(UADDWT, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, uaddw_fns[a->esz], a, 1) static gen_helper_gvec_3 * const usubw_fns[4] = { NULL, gen_helper_sve2_usubw_h, gen_helper_sve2_usubw_s, gen_helper_sve2_usubw_d, }; -TRANS_FEAT(USUBWB, aa64_sve2, gen_gvec_ool_arg_zzz, usubw_fns[a->esz], a, 0) -TRANS_FEAT(USUBWT, aa64_sve2, gen_gvec_ool_arg_zzz, usubw_fns[a->esz], a, 1) +TRANS_FEAT(USUBWB, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, usubw_fns[a->esz], a, 0) +TRANS_FEAT(USUBWT, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, usubw_fns[a->esz], a, 1) static void gen_sshll_vec(unsigned vece, TCGv_vec d, TCGv_vec n, int64_t imm) { @@ -6937,8 +6938,8 @@ static const GVecGen2i sshll_ops[3] = { .fno = gen_helper_sve2_sshll_d, .vece = MO_64 } }; -TRANS_FEAT(SSHLLB, aa64_sve2, do_shll_tb, a, sshll_ops, false) -TRANS_FEAT(SSHLLT, aa64_sve2, do_shll_tb, a, sshll_ops, true) +TRANS_FEAT(SSHLLB, aa64_sme_or_sve2, do_shll_tb, a, sshll_ops, false) +TRANS_FEAT(SSHLLT, aa64_sme_or_sve2, do_shll_tb, a, sshll_ops, true) static const TCGOpcode ushll_list[] = { INDEX_op_shli_vec, INDEX_op_shri_vec, 0 @@ -6960,8 +6961,8 @@ static const GVecGen2i ushll_ops[3] = { .fno = gen_helper_sve2_ushll_d, .vece = MO_64 }, }; -TRANS_FEAT(USHLLB, aa64_sve2, do_shll_tb, a, ushll_ops, false) -TRANS_FEAT(USHLLT, aa64_sve2, do_shll_tb, a, ushll_ops, true) +TRANS_FEAT(USHLLB, aa64_sme_or_sve2, do_shll_tb, a, ushll_ops, false) +TRANS_FEAT(USHLLT, aa64_sme_or_sve2, do_shll_tb, a, ushll_ops, true) static gen_helper_gvec_3 * const bext_fns[4] = { gen_helper_sve2_bext_b, gen_helper_sve2_bext_h, @@ -6988,33 +6989,33 @@ static gen_helper_gvec_3 * const cadd_fns[4] = { gen_helper_sve2_cadd_b, gen_helper_sve2_cadd_h, gen_helper_sve2_cadd_s, gen_helper_sve2_cadd_d, }; -TRANS_FEAT(CADD_rot90, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(CADD_rot90, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, cadd_fns[a->esz], a, 0) -TRANS_FEAT(CADD_rot270, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(CADD_rot270, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, cadd_fns[a->esz], a, 1) static gen_helper_gvec_3 * const sqcadd_fns[4] = { gen_helper_sve2_sqcadd_b, gen_helper_sve2_sqcadd_h, gen_helper_sve2_sqcadd_s, gen_helper_sve2_sqcadd_d, }; -TRANS_FEAT(SQCADD_rot90, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(SQCADD_rot90, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, sqcadd_fns[a->esz], a, 0) -TRANS_FEAT(SQCADD_rot270, aa64_sve2, gen_gvec_ool_arg_zzz, +TRANS_FEAT(SQCADD_rot270, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, sqcadd_fns[a->esz], a, 1) static gen_helper_gvec_4 * const sabal_fns[4] = { NULL, gen_helper_sve2_sabal_h, gen_helper_sve2_sabal_s, gen_helper_sve2_sabal_d, }; -TRANS_FEAT(SABALB, aa64_sve2, gen_gvec_ool_arg_zzzz, sabal_fns[a->esz], a, 0) -TRANS_FEAT(SABALT, aa64_sve2, gen_gvec_ool_arg_zzzz, sabal_fns[a->esz], a, 1) +TRANS_FEAT(SABALB, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, sabal_fns[a->esz], a, 0) +TRANS_FEAT(SABALT, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, sabal_fns[a->esz], a, 1) static gen_helper_gvec_4 * const uabal_fns[4] = { NULL, gen_helper_sve2_uabal_h, gen_helper_sve2_uabal_s, gen_helper_sve2_uabal_d, }; -TRANS_FEAT(UABALB, aa64_sve2, gen_gvec_ool_arg_zzzz, uabal_fns[a->esz], a, 0) -TRANS_FEAT(UABALT, aa64_sve2, gen_gvec_ool_arg_zzzz, uabal_fns[a->esz], a, 1) +TRANS_FEAT(UABALB, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, uabal_fns[a->esz], a, 0) +TRANS_FEAT(UABALT, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, uabal_fns[a->esz], a, 1) static bool do_adcl(DisasContext *s, arg_rrrr_esz *a, bool sel) { @@ -7029,18 +7030,18 @@ static bool do_adcl(DisasContext *s, arg_rrrr_esz *a, bool sel) return gen_gvec_ool_arg_zzzz(s, fns[a->esz & 1], a, (a->esz & 2) | sel); } -TRANS_FEAT(ADCLB, aa64_sve2, do_adcl, a, false) -TRANS_FEAT(ADCLT, aa64_sve2, do_adcl, a, true) +TRANS_FEAT(ADCLB, aa64_sme_or_sve2, do_adcl, a, false) +TRANS_FEAT(ADCLT, aa64_sme_or_sve2, do_adcl, a, true) -TRANS_FEAT(SSRA, aa64_sve2, gen_gvec_fn_arg_zzi, gen_gvec_ssra, a) -TRANS_FEAT(USRA, aa64_sve2, gen_gvec_fn_arg_zzi, gen_gvec_usra, a) -TRANS_FEAT(SRSRA, aa64_sve2, gen_gvec_fn_arg_zzi, gen_gvec_srsra, a) -TRANS_FEAT(URSRA, aa64_sve2, gen_gvec_fn_arg_zzi, gen_gvec_ursra, a) -TRANS_FEAT(SRI, aa64_sve2, gen_gvec_fn_arg_zzi, gen_gvec_sri, a) -TRANS_FEAT(SLI, aa64_sve2, gen_gvec_fn_arg_zzi, gen_gvec_sli, a) +TRANS_FEAT(SSRA, aa64_sme_or_sve2, gen_gvec_fn_arg_zzi, gen_gvec_ssra, a) +TRANS_FEAT(USRA, aa64_sme_or_sve2, gen_gvec_fn_arg_zzi, gen_gvec_usra, a) +TRANS_FEAT(SRSRA, aa64_sme_or_sve2, gen_gvec_fn_arg_zzi, gen_gvec_srsra, a) +TRANS_FEAT(URSRA, aa64_sme_or_sve2, gen_gvec_fn_arg_zzi, gen_gvec_ursra, a) +TRANS_FEAT(SRI, aa64_sme_or_sve2, gen_gvec_fn_arg_zzi, gen_gvec_sri, a) +TRANS_FEAT(SLI, aa64_sme_or_sve2, gen_gvec_fn_arg_zzi, gen_gvec_sli, a) -TRANS_FEAT(SABA, aa64_sve2, gen_gvec_fn_arg_zzz, gen_gvec_saba, a) -TRANS_FEAT(UABA, aa64_sve2, gen_gvec_fn_arg_zzz, gen_gvec_uaba, a) +TRANS_FEAT(SABA, aa64_sme_or_sve2, gen_gvec_fn_arg_zzz, gen_gvec_saba, a) +TRANS_FEAT(UABA, aa64_sme_or_sve2, gen_gvec_fn_arg_zzz, gen_gvec_uaba, a) static bool do_narrow_extract(DisasContext *s, arg_rri_esz *a, const GVecGen2 ops[3]) @@ -7087,7 +7088,7 @@ static const GVecGen2 sqxtnb_ops[3] = { .fno = gen_helper_sve2_sqxtnb_d, .vece = MO_64 }, }; -TRANS_FEAT(SQXTNB, aa64_sve2, do_narrow_extract, a, sqxtnb_ops) +TRANS_FEAT(SQXTNB, aa64_sme_or_sve2, do_narrow_extract, a, sqxtnb_ops) static void gen_sqxtnt_vec(unsigned vece, TCGv_vec d, TCGv_vec n) { @@ -7119,7 +7120,7 @@ static const GVecGen2 sqxtnt_ops[3] = { .fno = gen_helper_sve2_sqxtnt_d, .vece = MO_64 }, }; -TRANS_FEAT(SQXTNT, aa64_sve2, do_narrow_extract, a, sqxtnt_ops) +TRANS_FEAT(SQXTNT, aa64_sme_or_sve2, do_narrow_extract, a, sqxtnt_ops) static const TCGOpcode uqxtn_list[] = { INDEX_op_shli_vec, INDEX_op_umin_vec, 0 @@ -7147,7 +7148,7 @@ static const GVecGen2 uqxtnb_ops[3] = { .fno = gen_helper_sve2_uqxtnb_d, .vece = MO_64 }, }; -TRANS_FEAT(UQXTNB, aa64_sve2, do_narrow_extract, a, uqxtnb_ops) +TRANS_FEAT(UQXTNB, aa64_sme_or_sve2, do_narrow_extract, a, uqxtnb_ops) static void gen_uqxtnt_vec(unsigned vece, TCGv_vec d, TCGv_vec n) { @@ -7177,7 +7178,7 @@ static const GVecGen2 uqxtnt_ops[3] = { .fno = gen_helper_sve2_uqxtnt_d, .vece = MO_64 }, }; -TRANS_FEAT(UQXTNT, aa64_sve2, do_narrow_extract, a, uqxtnt_ops) +TRANS_FEAT(UQXTNT, aa64_sme_or_sve2, do_narrow_extract, a, uqxtnt_ops) static const TCGOpcode sqxtun_list[] = { INDEX_op_shli_vec, INDEX_op_umin_vec, INDEX_op_smax_vec, 0 @@ -7206,7 +7207,7 @@ static const GVecGen2 sqxtunb_ops[3] = { .fno = gen_helper_sve2_sqxtunb_d, .vece = MO_64 }, }; -TRANS_FEAT(SQXTUNB, aa64_sve2, do_narrow_extract, a, sqxtunb_ops) +TRANS_FEAT(SQXTUNB, aa64_sme_or_sve2, do_narrow_extract, a, sqxtunb_ops) static void gen_sqxtunt_vec(unsigned vece, TCGv_vec d, TCGv_vec n) { @@ -7237,7 +7238,7 @@ static const GVecGen2 sqxtunt_ops[3] = { .fno = gen_helper_sve2_sqxtunt_d, .vece = MO_64 }, }; -TRANS_FEAT(SQXTUNT, aa64_sve2, do_narrow_extract, a, sqxtunt_ops) +TRANS_FEAT(SQXTUNT, aa64_sme_or_sve2, do_narrow_extract, a, sqxtunt_ops) static bool do_shr_narrow(DisasContext *s, arg_rri_esz *a, const GVecGen2i ops[3]) @@ -7306,7 +7307,7 @@ static const GVecGen2i shrnb_ops[3] = { .fno = gen_helper_sve2_shrnb_d, .vece = MO_64 }, }; -TRANS_FEAT(SHRNB, aa64_sve2, do_shr_narrow, a, shrnb_ops) +TRANS_FEAT(SHRNB, aa64_sme_or_sve2, do_shr_narrow, a, shrnb_ops) static void gen_shrnt_i64(unsigned vece, TCGv_i64 d, TCGv_i64 n, int shr) { @@ -7365,21 +7366,21 @@ static const GVecGen2i shrnt_ops[3] = { .fno = gen_helper_sve2_shrnt_d, .vece = MO_64 }, }; -TRANS_FEAT(SHRNT, aa64_sve2, do_shr_narrow, a, shrnt_ops) +TRANS_FEAT(SHRNT, aa64_sme_or_sve2, do_shr_narrow, a, shrnt_ops) static const GVecGen2i rshrnb_ops[3] = { { .fno = gen_helper_sve2_rshrnb_h }, { .fno = gen_helper_sve2_rshrnb_s }, { .fno = gen_helper_sve2_rshrnb_d }, }; -TRANS_FEAT(RSHRNB, aa64_sve2, do_shr_narrow, a, rshrnb_ops) +TRANS_FEAT(RSHRNB, aa64_sme_or_sve2, do_shr_narrow, a, rshrnb_ops) static const GVecGen2i rshrnt_ops[3] = { { .fno = gen_helper_sve2_rshrnt_h }, { .fno = gen_helper_sve2_rshrnt_s }, { .fno = gen_helper_sve2_rshrnt_d }, }; -TRANS_FEAT(RSHRNT, aa64_sve2, do_shr_narrow, a, rshrnt_ops) +TRANS_FEAT(RSHRNT, aa64_sme_or_sve2, do_shr_narrow, a, rshrnt_ops) static void gen_sqshrunb_vec(unsigned vece, TCGv_vec d, TCGv_vec n, int64_t shr) @@ -7409,7 +7410,7 @@ static const GVecGen2i sqshrunb_ops[3] = { .fno = gen_helper_sve2_sqshrunb_d, .vece = MO_64 }, }; -TRANS_FEAT(SQSHRUNB, aa64_sve2, do_shr_narrow, a, sqshrunb_ops) +TRANS_FEAT(SQSHRUNB, aa64_sme_or_sve2, do_shr_narrow, a, sqshrunb_ops) static void gen_sqshrunt_vec(unsigned vece, TCGv_vec d, TCGv_vec n, int64_t shr) @@ -7446,21 +7447,21 @@ static const GVecGen2i sqshrunt_ops[3] = { .fno = gen_helper_sve2_sqshrunt_d, .vece = MO_64 }, }; -TRANS_FEAT(SQSHRUNT, aa64_sve2, do_shr_narrow, a, sqshrunt_ops) +TRANS_FEAT(SQSHRUNT, aa64_sme_or_sve2, do_shr_narrow, a, sqshrunt_ops) static const GVecGen2i sqrshrunb_ops[3] = { { .fno = gen_helper_sve2_sqrshrunb_h }, { .fno = gen_helper_sve2_sqrshrunb_s }, { .fno = gen_helper_sve2_sqrshrunb_d }, }; -TRANS_FEAT(SQRSHRUNB, aa64_sve2, do_shr_narrow, a, sqrshrunb_ops) +TRANS_FEAT(SQRSHRUNB, aa64_sme_or_sve2, do_shr_narrow, a, sqrshrunb_ops) static const GVecGen2i sqrshrunt_ops[3] = { { .fno = gen_helper_sve2_sqrshrunt_h }, { .fno = gen_helper_sve2_sqrshrunt_s }, { .fno = gen_helper_sve2_sqrshrunt_d }, }; -TRANS_FEAT(SQRSHRUNT, aa64_sve2, do_shr_narrow, a, sqrshrunt_ops) +TRANS_FEAT(SQRSHRUNT, aa64_sme_or_sve2, do_shr_narrow, a, sqrshrunt_ops) static void gen_sqshrnb_vec(unsigned vece, TCGv_vec d, TCGv_vec n, int64_t shr) @@ -7493,7 +7494,7 @@ static const GVecGen2i sqshrnb_ops[3] = { .fno = gen_helper_sve2_sqshrnb_d, .vece = MO_64 }, }; -TRANS_FEAT(SQSHRNB, aa64_sve2, do_shr_narrow, a, sqshrnb_ops) +TRANS_FEAT(SQSHRNB, aa64_sme_or_sve2, do_shr_narrow, a, sqshrnb_ops) static void gen_sqshrnt_vec(unsigned vece, TCGv_vec d, TCGv_vec n, int64_t shr) @@ -7531,21 +7532,21 @@ static const GVecGen2i sqshrnt_ops[3] = { .fno = gen_helper_sve2_sqshrnt_d, .vece = MO_64 }, }; -TRANS_FEAT(SQSHRNT, aa64_sve2, do_shr_narrow, a, sqshrnt_ops) +TRANS_FEAT(SQSHRNT, aa64_sme_or_sve2, do_shr_narrow, a, sqshrnt_ops) static const GVecGen2i sqrshrnb_ops[3] = { { .fno = gen_helper_sve2_sqrshrnb_h }, { .fno = gen_helper_sve2_sqrshrnb_s }, { .fno = gen_helper_sve2_sqrshrnb_d }, }; -TRANS_FEAT(SQRSHRNB, aa64_sve2, do_shr_narrow, a, sqrshrnb_ops) +TRANS_FEAT(SQRSHRNB, aa64_sme_or_sve2, do_shr_narrow, a, sqrshrnb_ops) static const GVecGen2i sqrshrnt_ops[3] = { { .fno = gen_helper_sve2_sqrshrnt_h }, { .fno = gen_helper_sve2_sqrshrnt_s }, { .fno = gen_helper_sve2_sqrshrnt_d }, }; -TRANS_FEAT(SQRSHRNT, aa64_sve2, do_shr_narrow, a, sqrshrnt_ops) +TRANS_FEAT(SQRSHRNT, aa64_sme_or_sve2, do_shr_narrow, a, sqrshrnt_ops) static void gen_uqshrnb_vec(unsigned vece, TCGv_vec d, TCGv_vec n, int64_t shr) @@ -7574,7 +7575,7 @@ static const GVecGen2i uqshrnb_ops[3] = { .fno = gen_helper_sve2_uqshrnb_d, .vece = MO_64 }, }; -TRANS_FEAT(UQSHRNB, aa64_sve2, do_shr_narrow, a, uqshrnb_ops) +TRANS_FEAT(UQSHRNB, aa64_sme_or_sve2, do_shr_narrow, a, uqshrnb_ops) static void gen_uqshrnt_vec(unsigned vece, TCGv_vec d, TCGv_vec n, int64_t shr) @@ -7609,28 +7610,28 @@ static const GVecGen2i uqshrnt_ops[3] = { .fno = gen_helper_sve2_uqshrnt_d, .vece = MO_64 }, }; -TRANS_FEAT(UQSHRNT, aa64_sve2, do_shr_narrow, a, uqshrnt_ops) +TRANS_FEAT(UQSHRNT, aa64_sme_or_sve2, do_shr_narrow, a, uqshrnt_ops) static const GVecGen2i uqrshrnb_ops[3] = { { .fno = gen_helper_sve2_uqrshrnb_h }, { .fno = gen_helper_sve2_uqrshrnb_s }, { .fno = gen_helper_sve2_uqrshrnb_d }, }; -TRANS_FEAT(UQRSHRNB, aa64_sve2, do_shr_narrow, a, uqrshrnb_ops) +TRANS_FEAT(UQRSHRNB, aa64_sme_or_sve2, do_shr_narrow, a, uqrshrnb_ops) static const GVecGen2i uqrshrnt_ops[3] = { { .fno = gen_helper_sve2_uqrshrnt_h }, { .fno = gen_helper_sve2_uqrshrnt_s }, { .fno = gen_helper_sve2_uqrshrnt_d }, }; -TRANS_FEAT(UQRSHRNT, aa64_sve2, do_shr_narrow, a, uqrshrnt_ops) +TRANS_FEAT(UQRSHRNT, aa64_sme_or_sve2, do_shr_narrow, a, uqrshrnt_ops) #define DO_SVE2_ZZZ_NARROW(NAME, name) \ static gen_helper_gvec_3 * const name##_fns[4] = { \ NULL, gen_helper_sve2_##name##_h, \ gen_helper_sve2_##name##_s, gen_helper_sve2_##name##_d, \ }; \ - TRANS_FEAT(NAME, aa64_sve2, gen_gvec_ool_arg_zzz, \ + TRANS_FEAT(NAME, aa64_sme_or_sve2, gen_gvec_ool_arg_zzz, \ name##_fns[a->esz], a, 0) DO_SVE2_ZZZ_NARROW(ADDHNB, addhnb) @@ -7662,11 +7663,11 @@ TRANS_FEAT_NONSTREAMING(HISTCNT, aa64_sve2, gen_gvec_ool_arg_zpzz, TRANS_FEAT_NONSTREAMING(HISTSEG, aa64_sve2, gen_gvec_ool_arg_zzz, a->esz == 0 ? gen_helper_sve2_histseg : NULL, a, 0) -DO_ZPZZ_FP(FADDP, aa64_sve2, sve2_faddp_zpzz) -DO_ZPZZ_FP(FMAXNMP, aa64_sve2, sve2_fmaxnmp_zpzz) -DO_ZPZZ_FP(FMINNMP, aa64_sve2, sve2_fminnmp_zpzz) -DO_ZPZZ_FP(FMAXP, aa64_sve2, sve2_fmaxp_zpzz) -DO_ZPZZ_FP(FMINP, aa64_sve2, sve2_fminp_zpzz) +DO_ZPZZ_FP(FADDP, aa64_sme_or_sve2, sve2_faddp_zpzz) +DO_ZPZZ_FP(FMAXNMP, aa64_sme_or_sve2, sve2_fmaxnmp_zpzz) +DO_ZPZZ_FP(FMINNMP, aa64_sme_or_sve2, sve2_fminnmp_zpzz) +DO_ZPZZ_FP(FMAXP, aa64_sme_or_sve2, sve2_fmaxp_zpzz) +DO_ZPZZ_FP(FMINP, aa64_sme_or_sve2, sve2_fminp_zpzz) static bool do_fmmla(DisasContext *s, arg_rrrr_esz *a, gen_helper_gvec_4_ptr *fn) @@ -7692,95 +7693,95 @@ static gen_helper_gvec_4 * const sqdmlal_zzzw_fns[] = { NULL, gen_helper_sve2_sqdmlal_zzzw_h, gen_helper_sve2_sqdmlal_zzzw_s, gen_helper_sve2_sqdmlal_zzzw_d, }; -TRANS_FEAT(SQDMLALB_zzzw, aa64_sve2, gen_gvec_ool_arg_zzzz, +TRANS_FEAT(SQDMLALB_zzzw, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, sqdmlal_zzzw_fns[a->esz], a, 0) -TRANS_FEAT(SQDMLALT_zzzw, aa64_sve2, gen_gvec_ool_arg_zzzz, +TRANS_FEAT(SQDMLALT_zzzw, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, sqdmlal_zzzw_fns[a->esz], a, 3) -TRANS_FEAT(SQDMLALBT, aa64_sve2, gen_gvec_ool_arg_zzzz, +TRANS_FEAT(SQDMLALBT, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, sqdmlal_zzzw_fns[a->esz], a, 2) static gen_helper_gvec_4 * const sqdmlsl_zzzw_fns[] = { NULL, gen_helper_sve2_sqdmlsl_zzzw_h, gen_helper_sve2_sqdmlsl_zzzw_s, gen_helper_sve2_sqdmlsl_zzzw_d, }; -TRANS_FEAT(SQDMLSLB_zzzw, aa64_sve2, gen_gvec_ool_arg_zzzz, +TRANS_FEAT(SQDMLSLB_zzzw, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, sqdmlsl_zzzw_fns[a->esz], a, 0) -TRANS_FEAT(SQDMLSLT_zzzw, aa64_sve2, gen_gvec_ool_arg_zzzz, +TRANS_FEAT(SQDMLSLT_zzzw, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, sqdmlsl_zzzw_fns[a->esz], a, 3) -TRANS_FEAT(SQDMLSLBT, aa64_sve2, gen_gvec_ool_arg_zzzz, +TRANS_FEAT(SQDMLSLBT, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, sqdmlsl_zzzw_fns[a->esz], a, 2) static gen_helper_gvec_4 * const sqrdmlah_fns[] = { gen_helper_sve2_sqrdmlah_b, gen_helper_sve2_sqrdmlah_h, gen_helper_sve2_sqrdmlah_s, gen_helper_sve2_sqrdmlah_d, }; -TRANS_FEAT(SQRDMLAH_zzzz, aa64_sve2, gen_gvec_ool_arg_zzzz, +TRANS_FEAT(SQRDMLAH_zzzz, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, sqrdmlah_fns[a->esz], a, 0) static gen_helper_gvec_4 * const sqrdmlsh_fns[] = { gen_helper_sve2_sqrdmlsh_b, gen_helper_sve2_sqrdmlsh_h, gen_helper_sve2_sqrdmlsh_s, gen_helper_sve2_sqrdmlsh_d, }; -TRANS_FEAT(SQRDMLSH_zzzz, aa64_sve2, gen_gvec_ool_arg_zzzz, +TRANS_FEAT(SQRDMLSH_zzzz, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, sqrdmlsh_fns[a->esz], a, 0) static gen_helper_gvec_4 * const smlal_zzzw_fns[] = { NULL, gen_helper_sve2_smlal_zzzw_h, gen_helper_sve2_smlal_zzzw_s, gen_helper_sve2_smlal_zzzw_d, }; -TRANS_FEAT(SMLALB_zzzw, aa64_sve2, gen_gvec_ool_arg_zzzz, +TRANS_FEAT(SMLALB_zzzw, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, smlal_zzzw_fns[a->esz], a, 0) -TRANS_FEAT(SMLALT_zzzw, aa64_sve2, gen_gvec_ool_arg_zzzz, +TRANS_FEAT(SMLALT_zzzw, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, smlal_zzzw_fns[a->esz], a, 1) static gen_helper_gvec_4 * const umlal_zzzw_fns[] = { NULL, gen_helper_sve2_umlal_zzzw_h, gen_helper_sve2_umlal_zzzw_s, gen_helper_sve2_umlal_zzzw_d, }; -TRANS_FEAT(UMLALB_zzzw, aa64_sve2, gen_gvec_ool_arg_zzzz, +TRANS_FEAT(UMLALB_zzzw, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, umlal_zzzw_fns[a->esz], a, 0) -TRANS_FEAT(UMLALT_zzzw, aa64_sve2, gen_gvec_ool_arg_zzzz, +TRANS_FEAT(UMLALT_zzzw, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, umlal_zzzw_fns[a->esz], a, 1) static gen_helper_gvec_4 * const smlsl_zzzw_fns[] = { NULL, gen_helper_sve2_smlsl_zzzw_h, gen_helper_sve2_smlsl_zzzw_s, gen_helper_sve2_smlsl_zzzw_d, }; -TRANS_FEAT(SMLSLB_zzzw, aa64_sve2, gen_gvec_ool_arg_zzzz, +TRANS_FEAT(SMLSLB_zzzw, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, smlsl_zzzw_fns[a->esz], a, 0) -TRANS_FEAT(SMLSLT_zzzw, aa64_sve2, gen_gvec_ool_arg_zzzz, +TRANS_FEAT(SMLSLT_zzzw, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, smlsl_zzzw_fns[a->esz], a, 1) static gen_helper_gvec_4 * const umlsl_zzzw_fns[] = { NULL, gen_helper_sve2_umlsl_zzzw_h, gen_helper_sve2_umlsl_zzzw_s, gen_helper_sve2_umlsl_zzzw_d, }; -TRANS_FEAT(UMLSLB_zzzw, aa64_sve2, gen_gvec_ool_arg_zzzz, +TRANS_FEAT(UMLSLB_zzzw, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, umlsl_zzzw_fns[a->esz], a, 0) -TRANS_FEAT(UMLSLT_zzzw, aa64_sve2, gen_gvec_ool_arg_zzzz, +TRANS_FEAT(UMLSLT_zzzw, aa64_sme_or_sve2, gen_gvec_ool_arg_zzzz, umlsl_zzzw_fns[a->esz], a, 1) static gen_helper_gvec_4 * const cmla_fns[] = { gen_helper_sve2_cmla_zzzz_b, gen_helper_sve2_cmla_zzzz_h, gen_helper_sve2_cmla_zzzz_s, gen_helper_sve2_cmla_zzzz_d, }; -TRANS_FEAT(CMLA_zzzz, aa64_sve2, gen_gvec_ool_zzzz, +TRANS_FEAT(CMLA_zzzz, aa64_sme_or_sve2, gen_gvec_ool_zzzz, cmla_fns[a->esz], a->rd, a->rn, a->rm, a->ra, a->rot) static gen_helper_gvec_4 * const cdot_fns[] = { NULL, NULL, gen_helper_sve2_cdot_zzzz_s, gen_helper_sve2_cdot_zzzz_d }; -TRANS_FEAT(CDOT_zzzz, aa64_sve2, gen_gvec_ool_zzzz, +TRANS_FEAT(CDOT_zzzz, aa64_sme_or_sve2, gen_gvec_ool_zzzz, cdot_fns[a->esz], a->rd, a->rn, a->rm, a->ra, a->rot) static gen_helper_gvec_4 * const sqrdcmlah_fns[] = { gen_helper_sve2_sqrdcmlah_zzzz_b, gen_helper_sve2_sqrdcmlah_zzzz_h, gen_helper_sve2_sqrdcmlah_zzzz_s, gen_helper_sve2_sqrdcmlah_zzzz_d, }; -TRANS_FEAT(SQRDCMLAH_zzzz, aa64_sve2, gen_gvec_ool_zzzz, +TRANS_FEAT(SQRDCMLAH_zzzz, aa64_sme_or_sve2, gen_gvec_ool_zzzz, sqrdcmlah_fns[a->esz], a->rd, a->rn, a->rm, a->ra, a->rot) -TRANS_FEAT(USDOT_zzzz_4s, aa64_sve_i8mm, gen_gvec_ool_arg_zzzz, +TRANS_FEAT(USDOT_zzzz_4s, aa64_sme_sve_i8mm, gen_gvec_ool_arg_zzzz, gen_helper_gvec_usdot_4b, a, 0) TRANS_FEAT(SDOT_zzzz_2s, aa64_sme2_or_sve2p1, gen_gvec_ool_arg_zzzz, @@ -7803,33 +7804,42 @@ TRANS_FEAT_NONSTREAMING(SM4E, aa64_sve2_sm4, gen_gvec_ool_arg_zzz, TRANS_FEAT_NONSTREAMING(SM4EKEY, aa64_sve2_sm4, gen_gvec_ool_arg_zzz, gen_helper_crypto_sm4ekey, a, 0) -TRANS_FEAT_NONSTREAMING(RAX1, aa64_sve2_sha3, gen_gvec_fn_arg_zzz, - gen_gvec_rax1, a) +static bool trans_RAX1(DisasContext *s, arg_RAX1 *a) +{ + if (!dc_isar_feature(aa64_sve2_sha3, s)) { + return false; + } + if (!dc_isar_feature(aa64_sme2p1, s)) { + /* SME2p1 adds this as valid in streaming SVE mode */ + s->is_nonstreaming = true; + } + return gen_gvec_fn_arg_zzz(s, gen_gvec_rax1, a); +} -TRANS_FEAT(FCVTNT_sh, aa64_sve2, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVTNT_sh, aa64_sme_or_sve2, gen_gvec_fpst_arg_zpz, gen_helper_sve2_fcvtnt_sh, a, 0, FPST_A64) -TRANS_FEAT(FCVTNT_ds, aa64_sve2, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVTNT_ds, aa64_sme_or_sve2, gen_gvec_fpst_arg_zpz, gen_helper_sve2_fcvtnt_ds, a, 0, FPST_A64) -TRANS_FEAT(BFCVTNT, aa64_sve_bf16, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(BFCVTNT, aa64_sme_sve_bf16, gen_gvec_fpst_arg_zpz, gen_helper_sve_bfcvtnt, a, 0, s->fpcr_ah ? FPST_AH : FPST_A64) -TRANS_FEAT(FCVTLT_hs, aa64_sve2, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVTLT_hs, aa64_sme_or_sve2, gen_gvec_fpst_arg_zpz, gen_helper_sve2_fcvtlt_hs, a, 0, FPST_A64) -TRANS_FEAT(FCVTLT_sd, aa64_sve2, gen_gvec_fpst_arg_zpz, +TRANS_FEAT(FCVTLT_sd, aa64_sme_or_sve2, gen_gvec_fpst_arg_zpz, gen_helper_sve2_fcvtlt_sd, a, 0, FPST_A64) -TRANS_FEAT(FCVTX_ds, aa64_sve2, do_frint_mode, a, +TRANS_FEAT(FCVTX_ds, aa64_sme_or_sve2, do_frint_mode, a, FPROUNDING_ODD, gen_helper_sve_fcvt_ds) -TRANS_FEAT(FCVTXNT_ds, aa64_sve2, do_frint_mode, a, +TRANS_FEAT(FCVTXNT_ds, aa64_sme_or_sve2, do_frint_mode, a, FPROUNDING_ODD, gen_helper_sve2_fcvtnt_ds) static gen_helper_gvec_3_ptr * const flogb_fns[] = { NULL, gen_helper_flogb_h, gen_helper_flogb_s, gen_helper_flogb_d }; -TRANS_FEAT(FLOGB, aa64_sve2, gen_gvec_fpst_arg_zpz, flogb_fns[a->esz], +TRANS_FEAT(FLOGB, aa64_sme_or_sve2, gen_gvec_fpst_arg_zpz, flogb_fns[a->esz], a, 0, a->esz == MO_16 ? FPST_A64_F16 : FPST_A64) static bool do_FMLAL_zzzw(DisasContext *s, arg_rrrr_esz *a, bool sub, bool sel) @@ -7839,10 +7849,10 @@ static bool do_FMLAL_zzzw(DisasContext *s, arg_rrrr_esz *a, bool sub, bool sel) (sel << 1) | sub, tcg_env); } -TRANS_FEAT(FMLALB_zzzw, aa64_sve2, do_FMLAL_zzzw, a, false, false) -TRANS_FEAT(FMLALT_zzzw, aa64_sve2, do_FMLAL_zzzw, a, false, true) -TRANS_FEAT(FMLSLB_zzzw, aa64_sve2, do_FMLAL_zzzw, a, true, false) -TRANS_FEAT(FMLSLT_zzzw, aa64_sve2, do_FMLAL_zzzw, a, true, true) +TRANS_FEAT(FMLALB_zzzw, aa64_sme_or_sve2, do_FMLAL_zzzw, a, false, false) +TRANS_FEAT(FMLALT_zzzw, aa64_sme_or_sve2, do_FMLAL_zzzw, a, false, true) +TRANS_FEAT(FMLSLB_zzzw, aa64_sme_or_sve2, do_FMLAL_zzzw, a, true, false) +TRANS_FEAT(FMLSLT_zzzw, aa64_sme_or_sve2, do_FMLAL_zzzw, a, true, true) static bool do_FMLAL_zzxw(DisasContext *s, arg_rrxr_esz *a, bool sub, bool sel) { @@ -7851,10 +7861,10 @@ static bool do_FMLAL_zzxw(DisasContext *s, arg_rrxr_esz *a, bool sub, bool sel) (a->index << 3) | (sel << 1) | sub, tcg_env); } -TRANS_FEAT(FMLALB_zzxw, aa64_sve2, do_FMLAL_zzxw, a, false, false) -TRANS_FEAT(FMLALT_zzxw, aa64_sve2, do_FMLAL_zzxw, a, false, true) -TRANS_FEAT(FMLSLB_zzxw, aa64_sve2, do_FMLAL_zzxw, a, true, false) -TRANS_FEAT(FMLSLT_zzxw, aa64_sve2, do_FMLAL_zzxw, a, true, true) +TRANS_FEAT(FMLALB_zzxw, aa64_sme_or_sve2, do_FMLAL_zzxw, a, false, false) +TRANS_FEAT(FMLALT_zzxw, aa64_sme_or_sve2, do_FMLAL_zzxw, a, false, true) +TRANS_FEAT(FMLSLB_zzxw, aa64_sme_or_sve2, do_FMLAL_zzxw, a, true, false) +TRANS_FEAT(FMLSLT_zzxw, aa64_sme_or_sve2, do_FMLAL_zzxw, a, true, true) TRANS_FEAT_NONSTREAMING(SMMLA, aa64_sve_i8mm, gen_gvec_ool_arg_zzzz, gen_helper_gvec_smmla_b, a, 0) @@ -7868,9 +7878,9 @@ TRANS_FEAT(FDOT_zzzz, aa64_sme2_or_sve2p1, gen_gvec_env_arg_zzzz, TRANS_FEAT(FDOT_zzxz, aa64_sme2_or_sve2p1, gen_gvec_env_arg_zzxz, gen_helper_sme2_fdot_idx_h, a) -TRANS_FEAT(BFDOT_zzzz, aa64_sve_bf16, gen_gvec_env_arg_zzzz, +TRANS_FEAT(BFDOT_zzzz, aa64_sme_sve_bf16, gen_gvec_env_arg_zzzz, gen_helper_gvec_bfdot, a, 0) -TRANS_FEAT(BFDOT_zzxz, aa64_sve_bf16, gen_gvec_env_arg_zzxz, +TRANS_FEAT(BFDOT_zzxz, aa64_sme_sve_bf16, gen_gvec_env_arg_zzxz, gen_helper_gvec_bfdot_idx, a) TRANS_FEAT_NONSTREAMING(BFMMLA, aa64_sve_bf16, gen_gvec_env_arg_zzzz, @@ -7883,8 +7893,8 @@ static bool do_BFMLAL_zzzw(DisasContext *s, arg_rrrr_esz *a, bool sel) s->fpcr_ah ? FPST_AH : FPST_A64); } -TRANS_FEAT(BFMLALB_zzzw, aa64_sve_bf16, do_BFMLAL_zzzw, a, false) -TRANS_FEAT(BFMLALT_zzzw, aa64_sve_bf16, do_BFMLAL_zzzw, a, true) +TRANS_FEAT(BFMLALB_zzzw, aa64_sme_sve_bf16, do_BFMLAL_zzzw, a, false) +TRANS_FEAT(BFMLALT_zzzw, aa64_sme_sve_bf16, do_BFMLAL_zzzw, a, true) static bool do_BFMLAL_zzxw(DisasContext *s, arg_rrxr_esz *a, bool sel) { @@ -7894,8 +7904,8 @@ static bool do_BFMLAL_zzxw(DisasContext *s, arg_rrxr_esz *a, bool sel) s->fpcr_ah ? FPST_AH : FPST_A64); } -TRANS_FEAT(BFMLALB_zzxw, aa64_sve_bf16, do_BFMLAL_zzxw, a, false) -TRANS_FEAT(BFMLALT_zzxw, aa64_sve_bf16, do_BFMLAL_zzxw, a, true) +TRANS_FEAT(BFMLALB_zzxw, aa64_sme_sve_bf16, do_BFMLAL_zzxw, a, false) +TRANS_FEAT(BFMLALT_zzxw, aa64_sme_sve_bf16, do_BFMLAL_zzxw, a, true) static bool do_BFMLSL_zzzw(DisasContext *s, arg_rrrr_esz *a, bool sel) { diff --git a/target/arm/tcg/translate.c b/target/arm/tcg/translate.c index c90b0106f75c..f9d1b8897d2a 100644 --- a/target/arm/tcg/translate.c +++ b/target/arm/tcg/translate.c @@ -26,12 +26,9 @@ #include "arm_ldst.h" #include "semihosting/semihost.h" #include "cpregs.h" -#include "exec/helper-proto.h" #include "exec/target_page.h" - -#define HELPER_H "helper.h" -#include "exec/helper-info.c.inc" -#undef HELPER_H +#include "helper.h" +#include "helper-mve.h" #define ENABLE_ARCH_4T arm_dc_feature(s, ARM_FEATURE_V4T) #define ENABLE_ARCH_5 arm_dc_feature(s, ARM_FEATURE_V5) @@ -44,6 +41,9 @@ #define ENABLE_ARCH_7 arm_dc_feature(s, ARM_FEATURE_V7) #define ENABLE_ARCH_8 arm_dc_feature(s, ARM_FEATURE_V8) +#define HELPER_H "tcg/helper-defs.h" +#include "exec/helper-info.c.inc" + /* These are TCG globals which alias CPUARMState fields */ static TCGv_i32 cpu_R[16]; TCGv_i32 cpu_CF, cpu_NF, cpu_VF, cpu_ZF; @@ -253,12 +253,12 @@ static inline int get_a32_user_mem_index(DisasContext *s) } /* The pc_curr difference for an architectural jump. */ -static target_long jmp_diff(DisasContext *s, target_long diff) +static int64_t jmp_diff(DisasContext *s, int64_t diff) { return diff + (s->thumb ? 4 : 8); } -static void gen_pc_plus_diff(DisasContext *s, TCGv_i32 var, target_long diff) +static void gen_pc_plus_diff(DisasContext *s, TCGv_i32 var, int64_t diff) { assert(s->pc_save != -1); if (tb_cflags(s->base.tb) & CF_PCREL) { @@ -738,7 +738,7 @@ void gen_set_condexec(DisasContext *s) } } -void gen_update_pc(DisasContext *s, target_long diff) +void gen_update_pc(DisasContext *s, int64_t diff) { gen_pc_plus_diff(s, cpu_R[15], diff); s->pc_save = s->pc_curr + diff; @@ -1058,7 +1058,7 @@ static void gen_exception(int excp, uint32_t syndrome) tcg_constant_i32(syndrome)); } -static void gen_exception_insn_el_v(DisasContext *s, target_long pc_diff, +static void gen_exception_insn_el_v(DisasContext *s, int64_t pc_diff, int excp, uint32_t syn, TCGv_i32 tcg_el) { if (s->aarch64) { @@ -1071,14 +1071,14 @@ static void gen_exception_insn_el_v(DisasContext *s, target_long pc_diff, s->base.is_jmp = DISAS_NORETURN; } -void gen_exception_insn_el(DisasContext *s, target_long pc_diff, int excp, +void gen_exception_insn_el(DisasContext *s, int64_t pc_diff, int excp, uint32_t syn, uint32_t target_el) { gen_exception_insn_el_v(s, pc_diff, excp, syn, tcg_constant_i32(target_el)); } -void gen_exception_insn(DisasContext *s, target_long pc_diff, +void gen_exception_insn(DisasContext *s, int64_t pc_diff, int excp, uint32_t syn) { if (s->aarch64) { @@ -1313,7 +1313,7 @@ static void gen_goto_ptr(void) * cpu_loop_exec. Any live exit_requests will be processed as we * enter the next TB. */ -static void gen_goto_tb(DisasContext *s, unsigned tb_slot_idx, target_long diff) +static void gen_goto_tb(DisasContext *s, unsigned tb_slot_idx, int64_t diff) { if (translator_use_goto_tb(&s->base, s->pc_curr + diff)) { /* @@ -1340,7 +1340,7 @@ static void gen_goto_tb(DisasContext *s, unsigned tb_slot_idx, target_long diff) } /* Jump, specifying which TB number to use if we gen_goto_tb() */ -static void gen_jmp_tb(DisasContext *s, target_long diff, int tbno) +static void gen_jmp_tb(DisasContext *s, int64_t diff, int tbno) { if (unlikely(s->ss_active)) { /* An indirect jump so that we still trigger the debug exception. */ @@ -1383,7 +1383,7 @@ static void gen_jmp_tb(DisasContext *s, target_long diff, int tbno) } } -static inline void gen_jmp(DisasContext *s, target_long diff) +static inline void gen_jmp(DisasContext *s, int64_t diff) { gen_jmp_tb(s, diff, 0); } diff --git a/target/arm/tcg/translate.h b/target/arm/tcg/translate.h index 1e30d7c77c37..3e3094a463e5 100644 --- a/target/arm/tcg/translate.h +++ b/target/arm/tcg/translate.h @@ -6,7 +6,7 @@ #include "tcg/tcg-op-gvec.h" #include "exec/translator.h" #include "exec/translation-block.h" -#include "exec/helper-gen.h" +#include "helper.h" #include "internals.h" #include "cpu-features.h" @@ -18,7 +18,7 @@ */ typedef struct DisasLabel { TCGLabel *label; - target_ulong pc_save; + vaddr pc_save; } DisasLabel; /* @@ -27,8 +27,8 @@ typedef struct DisasLabel { typedef struct DisasDelayException { struct DisasDelayException *next; TCGLabel *lab; - target_long pc_curr; - target_long pc_save; + vaddr pc_curr; + vaddr pc_save; int condexec_mask; int condexec_cond; uint32_t excp; @@ -42,7 +42,7 @@ typedef struct DisasContext { DisasDelayException *delay_excp_list; /* The address of the current instruction being translated. */ - target_ulong pc_curr; + vaddr pc_curr; /* * For CF_PCREL, the full value of cpu_pc is not known * (although the page offset is known). For convenience, the @@ -56,8 +56,8 @@ typedef struct DisasContext { * pc_save contains -1 to indicate that relative updates are no * longer possible. */ - target_ulong pc_save; - target_ulong page_start; + vaddr pc_save; + vaddr page_start; uint32_t insn; /* Nonzero if this instruction has been conditionally skipped. */ int condjmp; @@ -359,14 +359,14 @@ static inline int curr_insn_len(DisasContext *s) #ifdef TARGET_AARCH64 void a64_translate_init(void); -void gen_a64_update_pc(DisasContext *s, target_long diff); +void gen_a64_update_pc(DisasContext *s, int64_t diff); extern const TranslatorOps aarch64_translator_ops; #else static inline void a64_translate_init(void) { } -static inline void gen_a64_update_pc(DisasContext *s, target_long diff) +static inline void gen_a64_update_pc(DisasContext *s, int64_t diff) { } #endif @@ -377,9 +377,9 @@ void arm_gen_test_cc(int cc, TCGLabel *label); MemOp pow2_align(unsigned i); void unallocated_encoding(DisasContext *s); void gen_exception_internal(int excp); -void gen_exception_insn_el(DisasContext *s, target_long pc_diff, int excp, +void gen_exception_insn_el(DisasContext *s, int64_t pc_diff, int excp, uint32_t syn, uint32_t target_el); -void gen_exception_insn(DisasContext *s, target_long pc_diff, +void gen_exception_insn(DisasContext *s, int64_t pc_diff, int excp, uint32_t syn); TCGLabel *delay_exception_el(DisasContext *s, int excp, uint32_t syn, uint32_t target_el); diff --git a/target/arm/tcg/vec_helper.c b/target/arm/tcg/vec_helper.c index 33a136b90a61..91e98d28aea5 100644 --- a/target/arm/tcg/vec_helper.c +++ b/target/arm/tcg/vec_helper.c @@ -19,7 +19,7 @@ #include "qemu/osdep.h" #include "cpu.h" -#include "exec/helper-proto.h" +#include "helper.h" #include "tcg/tcg-gvec-desc.h" #include "fpu/softfloat.h" #include "qemu/int128.h" @@ -1455,18 +1455,6 @@ static float32 float32_rsqrts_nf(float32 op1, float32 op2, float_status *stat) return float32_div(op1, float32_two, stat); } -#define DO_3OP(NAME, FUNC, TYPE) \ -void HELPER(NAME)(void *vd, void *vn, void *vm, \ - float_status *stat, uint32_t desc) \ -{ \ - intptr_t i, oprsz = simd_oprsz(desc); \ - TYPE *d = vd, *n = vn, *m = vm; \ - for (i = 0; i < oprsz / sizeof(TYPE); i++) { \ - d[i] = FUNC(n[i], m[i], stat); \ - } \ - clear_tail(d, oprsz, simd_maxsz(desc)); \ -} - DO_3OP(gvec_fadd_b16, bfloat16_add, float16) DO_3OP(gvec_fadd_h, float16_add, float16) DO_3OP(gvec_fadd_s, float32_add, float32) @@ -1538,49 +1526,6 @@ DO_3OP(gvec_recps_nf_s, float32_recps_nf, float32) DO_3OP(gvec_rsqrts_nf_h, float16_rsqrts_nf, float16) DO_3OP(gvec_rsqrts_nf_s, float32_rsqrts_nf, float32) -#ifdef TARGET_AARCH64 -DO_3OP(gvec_fdiv_h, float16_div, float16) -DO_3OP(gvec_fdiv_s, float32_div, float32) -DO_3OP(gvec_fdiv_d, float64_div, float64) - -DO_3OP(gvec_fmulx_h, helper_advsimd_mulxh, float16) -DO_3OP(gvec_fmulx_s, helper_vfp_mulxs, float32) -DO_3OP(gvec_fmulx_d, helper_vfp_mulxd, float64) - -DO_3OP(gvec_recps_h, helper_recpsf_f16, float16) -DO_3OP(gvec_recps_s, helper_recpsf_f32, float32) -DO_3OP(gvec_recps_d, helper_recpsf_f64, float64) - -DO_3OP(gvec_rsqrts_h, helper_rsqrtsf_f16, float16) -DO_3OP(gvec_rsqrts_s, helper_rsqrtsf_f32, float32) -DO_3OP(gvec_rsqrts_d, helper_rsqrtsf_f64, float64) - -DO_3OP(gvec_ah_recps_h, helper_recpsf_ah_f16, float16) -DO_3OP(gvec_ah_recps_s, helper_recpsf_ah_f32, float32) -DO_3OP(gvec_ah_recps_d, helper_recpsf_ah_f64, float64) - -DO_3OP(gvec_ah_rsqrts_h, helper_rsqrtsf_ah_f16, float16) -DO_3OP(gvec_ah_rsqrts_s, helper_rsqrtsf_ah_f32, float32) -DO_3OP(gvec_ah_rsqrts_d, helper_rsqrtsf_ah_f64, float64) - -DO_3OP(gvec_ah_fmax_h, helper_vfp_ah_maxh, float16) -DO_3OP(gvec_ah_fmax_s, helper_vfp_ah_maxs, float32) -DO_3OP(gvec_ah_fmax_d, helper_vfp_ah_maxd, float64) - -DO_3OP(gvec_ah_fmin_h, helper_vfp_ah_minh, float16) -DO_3OP(gvec_ah_fmin_s, helper_vfp_ah_mins, float32) -DO_3OP(gvec_ah_fmin_d, helper_vfp_ah_mind, float64) - -DO_3OP(gvec_fmax_b16, bfloat16_max, bfloat16) -DO_3OP(gvec_fmin_b16, bfloat16_min, bfloat16) -DO_3OP(gvec_fmaxnum_b16, bfloat16_maxnum, bfloat16) -DO_3OP(gvec_fminnum_b16, bfloat16_minnum, bfloat16) -DO_3OP(gvec_ah_fmax_b16, helper_sme2_ah_fmax_b16, bfloat16) -DO_3OP(gvec_ah_fmin_b16, helper_sme2_ah_fmin_b16, bfloat16) - -#endif -#undef DO_3OP - /* Non-fused multiply-add (unlike float16_muladd etc, which are fused) */ static float16 float16_muladd_nf(float16 dest, float16 op1, float16 op2, float_status *stat) @@ -1766,23 +1711,6 @@ DO_MLA_IDX(gvec_mls_idx_d, uint64_t, -, H8) #undef DO_MLA_IDX -#define DO_FMUL_IDX(NAME, ADD, MUL, TYPE, H) \ -void HELPER(NAME)(void *vd, void *vn, void *vm, \ - float_status *stat, uint32_t desc) \ -{ \ - intptr_t i, j, oprsz = simd_oprsz(desc); \ - intptr_t segment = MIN(16, oprsz) / sizeof(TYPE); \ - intptr_t idx = simd_data(desc); \ - TYPE *d = vd, *n = vn, *m = vm; \ - for (i = 0; i < oprsz / sizeof(TYPE); i += segment) { \ - TYPE mm = m[H(i + idx)]; \ - for (j = 0; j < segment; j++) { \ - d[i + j] = ADD(d[i + j], MUL(n[i + j], mm, stat), stat); \ - } \ - } \ - clear_tail(d, oprsz, simd_maxsz(desc)); \ -} - #define nop(N, M, S) (M) DO_FMUL_IDX(gvec_fmul_idx_b16, nop, bfloat16_mul, float16, H2) @@ -1790,14 +1718,6 @@ DO_FMUL_IDX(gvec_fmul_idx_h, nop, float16_mul, float16, H2) DO_FMUL_IDX(gvec_fmul_idx_s, nop, float32_mul, float32, H4) DO_FMUL_IDX(gvec_fmul_idx_d, nop, float64_mul, float64, H8) -#ifdef TARGET_AARCH64 - -DO_FMUL_IDX(gvec_fmulx_idx_h, nop, helper_advsimd_mulxh, float16, H2) -DO_FMUL_IDX(gvec_fmulx_idx_s, nop, helper_vfp_mulxs, float32, H4) -DO_FMUL_IDX(gvec_fmulx_idx_d, nop, helper_vfp_mulxd, float64, H8) - -#endif - #undef nop /* @@ -1809,8 +1729,6 @@ DO_FMUL_IDX(gvec_fmla_nf_idx_s, float32_add, float32_mul, float32, H4) DO_FMUL_IDX(gvec_fmls_nf_idx_h, float16_sub, float16_mul, float16, H2) DO_FMUL_IDX(gvec_fmls_nf_idx_s, float32_sub, float32_mul, float32, H4) -#undef DO_FMUL_IDX - #define DO_FMLA_IDX(NAME, TYPE, H, NEGX, NEGF) \ void HELPER(NAME)(void *vd, void *vn, void *vm, void *va, \ float_status *stat, uint32_t desc) \ @@ -2527,31 +2445,6 @@ void HELPER(neon_pmull_h)(void *vd, void *vn, void *vm, uint32_t desc) clear_tail(d, 16, simd_maxsz(desc)); } -#ifdef TARGET_AARCH64 -void HELPER(sve2_pmull_h)(void *vd, void *vn, void *vm, uint32_t desc) -{ - int shift = simd_data(desc) * 8; - intptr_t i, opr_sz = simd_oprsz(desc); - uint64_t *d = vd, *n = vn, *m = vm; - - for (i = 0; i < opr_sz / 8; ++i) { - d[i] = clmul_8x4_even(n[i] >> shift, m[i] >> shift); - } -} - -void HELPER(sve2_pmull_d)(void *vd, void *vn, void *vm, uint32_t desc) -{ - intptr_t sel = H4(simd_data(desc)); - intptr_t i, opr_sz = simd_oprsz(desc); - uint32_t *n = vn, *m = vm; - uint64_t *d = vd; - - for (i = 0; i < opr_sz / 8; ++i) { - d[i] = clmul_32(n[2 * i + sel], m[2 * i + sel]); - } -} -#endif - #define DO_CMP0(NAME, TYPE, OP) \ void HELPER(NAME)(void *vd, void *vn, uint32_t desc) \ { \ @@ -2625,26 +2518,6 @@ DO_ABA(gvec_uaba_d, uint64_t) #undef DO_ABA -#define DO_3OP_PAIR(NAME, FUNC, TYPE, H) \ -void HELPER(NAME)(void *vd, void *vn, void *vm, \ - float_status *stat, uint32_t desc) \ -{ \ - ARMVectorReg scratch; \ - intptr_t oprsz = simd_oprsz(desc); \ - intptr_t half = oprsz / sizeof(TYPE) / 2; \ - TYPE *d = vd, *n = vn, *m = vm; \ - if (unlikely(d == m)) { \ - m = memcpy(&scratch, m, oprsz); \ - } \ - for (intptr_t i = 0; i < half; ++i) { \ - d[H(i)] = FUNC(n[H(i * 2)], n[H(i * 2 + 1)], stat); \ - } \ - for (intptr_t i = 0; i < half; ++i) { \ - d[H(i + half)] = FUNC(m[H(i * 2)], m[H(i * 2 + 1)], stat); \ - } \ - clear_tail(d, oprsz, simd_maxsz(desc)); \ -} - DO_3OP_PAIR(gvec_faddp_h, float16_add, float16, H2) DO_3OP_PAIR(gvec_faddp_s, float32_add, float32, H4) DO_3OP_PAIR(gvec_faddp_d, float64_add, float64, ) @@ -2665,19 +2538,7 @@ DO_3OP_PAIR(gvec_fminnump_h, float16_minnum, float16, H2) DO_3OP_PAIR(gvec_fminnump_s, float32_minnum, float32, H4) DO_3OP_PAIR(gvec_fminnump_d, float64_minnum, float64, ) -#ifdef TARGET_AARCH64 -DO_3OP_PAIR(gvec_ah_fmaxp_h, helper_vfp_ah_maxh, float16, H2) -DO_3OP_PAIR(gvec_ah_fmaxp_s, helper_vfp_ah_maxs, float32, H4) -DO_3OP_PAIR(gvec_ah_fmaxp_d, helper_vfp_ah_maxd, float64, ) - -DO_3OP_PAIR(gvec_ah_fminp_h, helper_vfp_ah_minh, float16, H2) -DO_3OP_PAIR(gvec_ah_fminp_s, helper_vfp_ah_mins, float32, H4) -DO_3OP_PAIR(gvec_ah_fminp_d, helper_vfp_ah_mind, float64, ) -#endif - -#undef DO_3OP_PAIR - -#define DO_3OP_PAIR(NAME, FUNC, TYPE, H) \ +#define DO_3OP_PAIR_NO_STATUS(NAME, FUNC, TYPE, H) \ void HELPER(NAME)(void *vd, void *vn, void *vm, uint32_t desc) \ { \ ARMVectorReg scratch; \ @@ -2697,29 +2558,29 @@ void HELPER(NAME)(void *vd, void *vn, void *vm, uint32_t desc) \ } #define ADD(A, B) (A + B) -DO_3OP_PAIR(gvec_addp_b, ADD, uint8_t, H1) -DO_3OP_PAIR(gvec_addp_h, ADD, uint16_t, H2) -DO_3OP_PAIR(gvec_addp_s, ADD, uint32_t, H4) -DO_3OP_PAIR(gvec_addp_d, ADD, uint64_t, ) +DO_3OP_PAIR_NO_STATUS(gvec_addp_b, ADD, uint8_t, H1) +DO_3OP_PAIR_NO_STATUS(gvec_addp_h, ADD, uint16_t, H2) +DO_3OP_PAIR_NO_STATUS(gvec_addp_s, ADD, uint32_t, H4) +DO_3OP_PAIR_NO_STATUS(gvec_addp_d, ADD, uint64_t, /**/) #undef ADD -DO_3OP_PAIR(gvec_smaxp_b, MAX, int8_t, H1) -DO_3OP_PAIR(gvec_smaxp_h, MAX, int16_t, H2) -DO_3OP_PAIR(gvec_smaxp_s, MAX, int32_t, H4) +DO_3OP_PAIR_NO_STATUS(gvec_smaxp_b, MAX, int8_t, H1) +DO_3OP_PAIR_NO_STATUS(gvec_smaxp_h, MAX, int16_t, H2) +DO_3OP_PAIR_NO_STATUS(gvec_smaxp_s, MAX, int32_t, H4) -DO_3OP_PAIR(gvec_umaxp_b, MAX, uint8_t, H1) -DO_3OP_PAIR(gvec_umaxp_h, MAX, uint16_t, H2) -DO_3OP_PAIR(gvec_umaxp_s, MAX, uint32_t, H4) +DO_3OP_PAIR_NO_STATUS(gvec_umaxp_b, MAX, uint8_t, H1) +DO_3OP_PAIR_NO_STATUS(gvec_umaxp_h, MAX, uint16_t, H2) +DO_3OP_PAIR_NO_STATUS(gvec_umaxp_s, MAX, uint32_t, H4) -DO_3OP_PAIR(gvec_sminp_b, MIN, int8_t, H1) -DO_3OP_PAIR(gvec_sminp_h, MIN, int16_t, H2) -DO_3OP_PAIR(gvec_sminp_s, MIN, int32_t, H4) +DO_3OP_PAIR_NO_STATUS(gvec_sminp_b, MIN, int8_t, H1) +DO_3OP_PAIR_NO_STATUS(gvec_sminp_h, MIN, int16_t, H2) +DO_3OP_PAIR_NO_STATUS(gvec_sminp_s, MIN, int32_t, H4) -DO_3OP_PAIR(gvec_uminp_b, MIN, uint8_t, H1) -DO_3OP_PAIR(gvec_uminp_h, MIN, uint16_t, H2) -DO_3OP_PAIR(gvec_uminp_s, MIN, uint32_t, H4) +DO_3OP_PAIR_NO_STATUS(gvec_uminp_b, MIN, uint8_t, H1) +DO_3OP_PAIR_NO_STATUS(gvec_uminp_h, MIN, uint16_t, H2) +DO_3OP_PAIR_NO_STATUS(gvec_uminp_s, MIN, uint32_t, H4) -#undef DO_3OP_PAIR +#undef DO_3OP_PAIR_NO_STATUS #define DO_VCVT_FIXED(NAME, FUNC, TYPE) \ void HELPER(NAME)(void *vd, void *vn, float_status *stat, uint32_t desc) \ @@ -2794,53 +2655,6 @@ DO_VRINT_RMODE(gvec_vrint_rm_s, helper_rints, uint32_t) #undef DO_VRINT_RMODE -#ifdef TARGET_AARCH64 -void HELPER(simd_tblx)(void *vd, void *vm, CPUARMState *env, uint32_t desc) -{ - const uint8_t *indices = vm; - size_t oprsz = simd_oprsz(desc); - uint32_t rn = extract32(desc, SIMD_DATA_SHIFT, 5); - bool is_tbx = extract32(desc, SIMD_DATA_SHIFT + 5, 1); - uint32_t table_len = desc >> (SIMD_DATA_SHIFT + 6); - union { - uint8_t b[16]; - uint64_t d[2]; - } result; - - /* - * We must construct the final result in a temp, lest the output - * overlaps the input table. For TBL, begin with zero; for TBX, - * begin with the original register contents. Note that we always - * copy 16 bytes here to avoid an extra branch; clearing the high - * bits of the register for oprsz == 8 is handled below. - */ - if (is_tbx) { - memcpy(&result, vd, 16); - } else { - memset(&result, 0, 16); - } - - for (size_t i = 0; i < oprsz; ++i) { - uint32_t index = indices[H1(i)]; - - if (index < table_len) { - /* - * Convert index (a byte offset into the virtual table - * which is a series of 128-bit vectors concatenated) - * into the correct register element, bearing in mind - * that the table can wrap around from V31 to V0. - */ - const uint8_t *table = (const uint8_t *) - aa64_vfp_qreg(env, (rn + (index >> 4)) % 32); - result.b[H1(i)] = table[H1(index % 16)]; - } - } - - memcpy(vd, &result, 16); - clear_tail(vd, oprsz, simd_maxsz(desc)); -} -#endif - /* * NxN -> N highpart multiply * diff --git a/target/arm/tcg/vec_helper64.c b/target/arm/tcg/vec_helper64.c new file mode 100644 index 000000000000..249a257177e1 --- /dev/null +++ b/target/arm/tcg/vec_helper64.c @@ -0,0 +1,142 @@ +/* + * ARM AdvSIMD / SVE Vector Operations + * + * Copyright (c) 2026 Linaro + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "cpu.h" +#include "helper.h" +#include "helper-a64.h" +#include "helper-sme.h" +#include "helper-sve.h" +#include "tcg/tcg-gvec-desc.h" +#include "fpu/softfloat.h" +#include "qemu/int128.h" +#include "crypto/clmul.h" +#include "vec_internal.h" + +DO_3OP(gvec_fdiv_h, float16_div, float16) +DO_3OP(gvec_fdiv_s, float32_div, float32) +DO_3OP(gvec_fdiv_d, float64_div, float64) + +DO_3OP(gvec_fmulx_h, helper_advsimd_mulxh, float16) +DO_3OP(gvec_fmulx_s, helper_vfp_mulxs, float32) +DO_3OP(gvec_fmulx_d, helper_vfp_mulxd, float64) + +DO_3OP(gvec_recps_h, helper_recpsf_f16, float16) +DO_3OP(gvec_recps_s, helper_recpsf_f32, float32) +DO_3OP(gvec_recps_d, helper_recpsf_f64, float64) + +DO_3OP(gvec_rsqrts_h, helper_rsqrtsf_f16, float16) +DO_3OP(gvec_rsqrts_s, helper_rsqrtsf_f32, float32) +DO_3OP(gvec_rsqrts_d, helper_rsqrtsf_f64, float64) + +DO_3OP(gvec_ah_recps_h, helper_recpsf_ah_f16, float16) +DO_3OP(gvec_ah_recps_s, helper_recpsf_ah_f32, float32) +DO_3OP(gvec_ah_recps_d, helper_recpsf_ah_f64, float64) + +DO_3OP(gvec_ah_rsqrts_h, helper_rsqrtsf_ah_f16, float16) +DO_3OP(gvec_ah_rsqrts_s, helper_rsqrtsf_ah_f32, float32) +DO_3OP(gvec_ah_rsqrts_d, helper_rsqrtsf_ah_f64, float64) + +DO_3OP(gvec_ah_fmax_h, helper_vfp_ah_maxh, float16) +DO_3OP(gvec_ah_fmax_s, helper_vfp_ah_maxs, float32) +DO_3OP(gvec_ah_fmax_d, helper_vfp_ah_maxd, float64) + +DO_3OP(gvec_ah_fmin_h, helper_vfp_ah_minh, float16) +DO_3OP(gvec_ah_fmin_s, helper_vfp_ah_mins, float32) +DO_3OP(gvec_ah_fmin_d, helper_vfp_ah_mind, float64) + +DO_3OP(gvec_fmax_b16, bfloat16_max, bfloat16) +DO_3OP(gvec_fmin_b16, bfloat16_min, bfloat16) +DO_3OP(gvec_fmaxnum_b16, bfloat16_maxnum, bfloat16) +DO_3OP(gvec_fminnum_b16, bfloat16_minnum, bfloat16) +DO_3OP(gvec_ah_fmax_b16, helper_sme2_ah_fmax_b16, bfloat16) +DO_3OP(gvec_ah_fmin_b16, helper_sme2_ah_fmin_b16, bfloat16) + +#define nop(N, M, S) (M) + +DO_FMUL_IDX(gvec_fmulx_idx_h, nop, helper_advsimd_mulxh, float16, H2) +DO_FMUL_IDX(gvec_fmulx_idx_s, nop, helper_vfp_mulxs, float32, H4) +DO_FMUL_IDX(gvec_fmulx_idx_d, nop, helper_vfp_mulxd, float64, H8) + +#undef nop + +void HELPER(sve2_pmull_h)(void *vd, void *vn, void *vm, uint32_t desc) +{ + int shift = simd_data(desc) * 8; + intptr_t i, opr_sz = simd_oprsz(desc); + uint64_t *d = vd, *n = vn, *m = vm; + + for (i = 0; i < opr_sz / 8; ++i) { + d[i] = clmul_8x4_even(n[i] >> shift, m[i] >> shift); + } +} + +void HELPER(sve2_pmull_d)(void *vd, void *vn, void *vm, uint32_t desc) +{ + intptr_t sel = H4(simd_data(desc)); + intptr_t i, opr_sz = simd_oprsz(desc); + uint32_t *n = vn, *m = vm; + uint64_t *d = vd; + + for (i = 0; i < opr_sz / 8; ++i) { + d[i] = clmul_32(n[2 * i + sel], m[2 * i + sel]); + } +} + +DO_3OP_PAIR(gvec_ah_fmaxp_h, helper_vfp_ah_maxh, float16, H2) +DO_3OP_PAIR(gvec_ah_fmaxp_s, helper_vfp_ah_maxs, float32, H4) +DO_3OP_PAIR(gvec_ah_fmaxp_d, helper_vfp_ah_maxd, float64, /**/) + +DO_3OP_PAIR(gvec_ah_fminp_h, helper_vfp_ah_minh, float16, H2) +DO_3OP_PAIR(gvec_ah_fminp_s, helper_vfp_ah_mins, float32, H4) +DO_3OP_PAIR(gvec_ah_fminp_d, helper_vfp_ah_mind, float64, /**/) + +void HELPER(simd_tblx)(void *vd, void *vm, CPUARMState *env, uint32_t desc) +{ + const uint8_t *indices = vm; + size_t oprsz = simd_oprsz(desc); + uint32_t rn = extract32(desc, SIMD_DATA_SHIFT, 5); + bool is_tbx = extract32(desc, SIMD_DATA_SHIFT + 5, 1); + uint32_t table_len = desc >> (SIMD_DATA_SHIFT + 6); + union { + uint8_t b[16]; + uint64_t d[2]; + } result; + + /* + * We must construct the final result in a temp, lest the output + * overlaps the input table. For TBL, begin with zero; for TBX, + * begin with the original register contents. Note that we always + * copy 16 bytes here to avoid an extra branch; clearing the high + * bits of the register for oprsz == 8 is handled below. + */ + if (is_tbx) { + memcpy(&result, vd, 16); + } else { + memset(&result, 0, 16); + } + + for (size_t i = 0; i < oprsz; ++i) { + uint32_t index = indices[H1(i)]; + + if (index < table_len) { + /* + * Convert index (a byte offset into the virtual table + * which is a series of 128-bit vectors concatenated) + * into the correct register element, bearing in mind + * that the table can wrap around from V31 to V0. + */ + const uint8_t *table = (const uint8_t *) + aa64_vfp_qreg(env, (rn + (index >> 4)) % 32); + result.b[H1(i)] = table[H1(index % 16)]; + } + } + + memcpy(vd, &result, 16); + clear_tail(vd, oprsz, simd_maxsz(desc)); +} diff --git a/target/arm/tcg/vec_internal.h b/target/arm/tcg/vec_internal.h index cf41b03dbcd5..4edd2b4fc18f 100644 --- a/target/arm/tcg/vec_internal.h +++ b/target/arm/tcg/vec_internal.h @@ -450,4 +450,53 @@ static inline void depositn(uint64_t *p, unsigned pos, } } +#define DO_3OP(NAME, FUNC, TYPE) \ +void HELPER(NAME)(void *vd, void *vn, void *vm, \ + float_status * stat, uint32_t desc) \ +{ \ + intptr_t i, oprsz = simd_oprsz(desc); \ + TYPE *d = vd, *n = vn, *m = vm; \ + for (i = 0; i < oprsz / sizeof(TYPE); i++) { \ + d[i] = FUNC(n[i], m[i], stat); \ + } \ + clear_tail(d, oprsz, simd_maxsz(desc)); \ +} + +#define DO_3OP_PAIR(NAME, FUNC, TYPE, H) \ +void HELPER(NAME)(void *vd, void *vn, void *vm, \ + float_status * stat, uint32_t desc) \ +{ \ + ARMVectorReg scratch; \ + intptr_t oprsz = simd_oprsz(desc); \ + intptr_t half = oprsz / sizeof(TYPE) / 2; \ + TYPE *d = vd, *n = vn, *m = vm; \ + if (unlikely(d == m)) { \ + m = memcpy(&scratch, m, oprsz); \ + } \ + for (intptr_t i = 0; i < half; ++i) { \ + d[H(i)] = FUNC(n[H(i * 2)], n[H(i * 2 + 1)], stat); \ + } \ + for (intptr_t i = 0; i < half; ++i) { \ + d[H(i + half)] = FUNC(m[H(i * 2)], m[H(i * 2 + 1)], stat); \ + } \ + clear_tail(d, oprsz, simd_maxsz(desc)); \ +} + +#define DO_FMUL_IDX(NAME, ADD, MUL, TYPE, H) \ +void HELPER(NAME)(void *vd, void *vn, void *vm, \ + float_status * stat, uint32_t desc) \ +{ \ + intptr_t i, j, oprsz = simd_oprsz(desc); \ + intptr_t segment = MIN(16, oprsz) / sizeof(TYPE); \ + intptr_t idx = simd_data(desc); \ + TYPE *d = vd, *n = vn, *m = vm; \ + for (i = 0; i < oprsz / sizeof(TYPE); i += segment) { \ + TYPE mm = m[H(i + idx)]; \ + for (j = 0; j < segment; j++) { \ + d[i + j] = ADD(d[i + j], MUL(n[i + j], mm, stat), stat); \ + } \ + } \ + clear_tail(d, oprsz, simd_maxsz(desc)); \ +} + #endif /* TARGET_ARM_VEC_INTERNAL_H */ diff --git a/target/arm/tcg/vfp_helper.c b/target/arm/tcg/vfp_helper.c index e156e3774ad8..45f2eb0930fa 100644 --- a/target/arm/tcg/vfp_helper.c +++ b/target/arm/tcg/vfp_helper.c @@ -19,14 +19,12 @@ #include "qemu/osdep.h" #include "cpu.h" +#include "helper.h" #include "internals.h" #include "cpu-features.h" #include "fpu/softfloat.h" #include "qemu/log.h" -#define HELPER_H "tcg/helper.h" -#include "exec/helper-proto.h.inc" - /* * Set the float_status behaviour to match the Arm defaults: * * tininess-before-rounding diff --git a/target/arm/trace-events b/target/arm/trace-events index 676d29fe5165..2de0406f784d 100644 --- a/target/arm/trace-events +++ b/target/arm/trace-events @@ -26,3 +26,6 @@ arm_powerctl_reset_cpu(uint64_t mp_aff) "cpu %" PRIu64 # tcg/psci.c and hvf/hvf.c arm_psci_call(uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint32_t cpuid) "PSCI Call x0=0x%016"PRIx64" x1=0x%016"PRIx64" x2=0x%016"PRIx64" x3=0x%016"PRIx64" cpuid=0x%x" + +# machine.c +cpu_post_load(uint32_t cpreg_vmstate_array_len, uint32_t cpreg_array_len) "cpreg_vmstate_array_len=%d cpreg_array_len=%d" diff --git a/target/arm/whpx/whpx-all.c b/target/arm/whpx/whpx-all.c index 36c5e30a0349..513551bec1ba 100644 --- a/target/arm/whpx/whpx-all.c +++ b/target/arm/whpx/whpx-all.c @@ -27,7 +27,6 @@ #include #include "syndrome.h" -#include "cpu.h" #include "target/arm/cpregs.h" #include "internals.h" @@ -274,14 +273,6 @@ static struct whpx_sreg_match whpx_sreg_match[] = { { WHvArm64RegisterSpEl1, ENCODE_AA64_CP_REG(4, 1, 3, 4, 0) }, }; -static void flush_cpu_state(CPUState *cpu) -{ - if (cpu->vcpu_dirty) { - whpx_set_registers(cpu, WHPX_SET_RUNTIME_STATE); - cpu->vcpu_dirty = false; - } -} - HRESULT whpx_set_exception_exit_bitmap(UINT64 exceptions) { if (exceptions != 0) { @@ -304,31 +295,14 @@ void whpx_translate_cpu_breakpoints( /* Breakpoints aren’t supported on this platform */ } -static void whpx_get_reg(CPUState *cpu, WHV_REGISTER_NAME reg, WHV_REGISTER_VALUE* val) +bool whpx_arch_supports_guest_debug(void) { - struct whpx_state *whpx = &whpx_global; - HRESULT hr; - - flush_cpu_state(cpu); - - hr = whp_dispatch.WHvGetVirtualProcessorRegisters(whpx->partition, cpu->cpu_index, - ®, 1, val); - - if (FAILED(hr)) { - error_report("WHPX: Failed to get register %08x, hr=%08lx", reg, hr); - } + return false; } -static void whpx_set_reg(CPUState *cpu, WHV_REGISTER_NAME reg, WHV_REGISTER_VALUE val) +void whpx_arch_destroy_vcpu(CPUState *cpu) { - struct whpx_state *whpx = &whpx_global; - HRESULT hr; - hr = whp_dispatch.WHvSetVirtualProcessorRegisters(whpx->partition, cpu->cpu_index, - ®, 1, &val); - - if (FAILED(hr)) { - error_report("WHPX: Failed to set register %08x, hr=%08lx", reg, hr); - } + /* currently empty on Arm */ } static void whpx_get_global_reg(WHV_REGISTER_NAME reg, WHV_REGISTER_VALUE *val) @@ -443,7 +417,7 @@ int whpx_vcpu_run(CPUState *cpu) do { bool advance_pc = false; if (cpu->vcpu_dirty) { - whpx_set_registers(cpu, WHPX_SET_RUNTIME_STATE); + whpx_set_registers(cpu, WHPX_LEVEL_RUNTIME_STATE); cpu->vcpu_dirty = false; } @@ -508,7 +482,7 @@ int whpx_vcpu_run(CPUState *cpu) default: error_report("WHPX: Unexpected VP exit code 0x%08x", vcpu->exit_ctx.ExitReason); - whpx_get_registers(cpu); + whpx_get_registers(cpu, WHPX_LEVEL_FULL_STATE); bql_lock(); qemu_system_guest_panicked(cpu_get_crash_info(cpu)); bql_unlock(); @@ -517,7 +491,7 @@ int whpx_vcpu_run(CPUState *cpu) if (advance_pc) { WHV_REGISTER_VALUE pc; - flush_cpu_state(cpu); + whpx_flush_cpu_state(cpu); pc.Reg64 = vcpu->exit_ctx.MemoryAccess.Header.Pc + 4; whpx_set_reg(cpu, WHvArm64RegisterPc, pc); } @@ -542,7 +516,7 @@ static void clean_whv_register_value(WHV_REGISTER_VALUE *val) memset(val, 0, sizeof(WHV_REGISTER_VALUE)); } -void whpx_get_registers(CPUState *cpu) +void whpx_get_registers(CPUState *cpu, WHPXStateLevel level) { ARMCPU *arm_cpu = ARM_CPU(cpu); CPUARMState *env = &arm_cpu->env; @@ -555,7 +529,7 @@ void whpx_get_registers(CPUState *cpu) } for (i = 0; i < ARRAY_SIZE(whpx_fpreg_match); i++) { - whpx_get_reg(cpu, whpx_reg_match[i].reg, &val); + whpx_get_reg(cpu, whpx_fpreg_match[i].reg, &val); memcpy((char *)env + whpx_fpreg_match[i].offset, &val, sizeof(val.Reg128)); } @@ -589,7 +563,7 @@ void whpx_get_registers(CPUState *cpu) aarch64_restore_sp(env, arm_current_el(env)); } -void whpx_set_registers(CPUState *cpu, int level) +void whpx_set_registers(CPUState *cpu, WHPXStateLevel level) { ARMCPU *arm_cpu = ARM_CPU(cpu); CPUARMState *env = &arm_cpu->env; @@ -606,7 +580,7 @@ void whpx_set_registers(CPUState *cpu, int level) for (i = 0; i < ARRAY_SIZE(whpx_fpreg_match); i++) { memcpy(&val.Reg128, (char *)env + whpx_fpreg_match[i].offset, sizeof(val.Reg128)); - whpx_set_reg(cpu, whpx_reg_match[i].reg, val); + whpx_set_reg(cpu, whpx_fpreg_match[i].reg, val); } clean_whv_register_value(&val); @@ -809,12 +783,6 @@ int whpx_init_vcpu(CPUState *cpu) sregs_match_len); arm_cpu->cpreg_values = g_renew(uint64_t, arm_cpu->cpreg_values, sregs_match_len); - arm_cpu->cpreg_vmstate_indexes = g_renew(uint64_t, - arm_cpu->cpreg_vmstate_indexes, - sregs_match_len); - arm_cpu->cpreg_vmstate_values = g_renew(uint64_t, - arm_cpu->cpreg_vmstate_values, - sregs_match_len); memset(arm_cpu->cpreg_values, 0, sregs_match_len * sizeof(uint64_t)); @@ -833,7 +801,6 @@ int whpx_init_vcpu(CPUState *cpu) } } arm_cpu->cpreg_array_len = sregs_cnt; - arm_cpu->cpreg_vmstate_array_len = sregs_cnt; assert(write_cpustate_to_list(arm_cpu, false)); @@ -973,6 +940,43 @@ int whpx_accel_init(AccelState *as, MachineState *ms) goto error; } + /* Enable synthetic processor features */ + WHV_SYNTHETIC_PROCESSOR_FEATURES_BANKS synthetic_features; + memset(&synthetic_features, 0, sizeof(WHV_SYNTHETIC_PROCESSOR_FEATURES_BANKS)); + synthetic_features.BanksCount = 1; + + synthetic_features.Bank0.HypervisorPresent = 1; + synthetic_features.Bank0.Hv1 = 1; + synthetic_features.Bank0.AccessVpRunTimeReg = 1; + synthetic_features.Bank0.AccessPartitionReferenceCounter = 1; + synthetic_features.Bank0.AccessPartitionReferenceTsc = 1; + synthetic_features.Bank0.AccessHypercallRegs = 1; + synthetic_features.Bank0.AccessVpIndex = 1; + synthetic_features.Bank0.AccessHypercallRegs = 1; + synthetic_features.Bank0.TbFlushHypercalls = 1; + synthetic_features.Bank0.AccessSynicRegs = 1; + synthetic_features.Bank0.AccessSyntheticTimerRegs = 1; + synthetic_features.Bank0.AccessIntrCtrlRegs = 1; + synthetic_features.Bank0.SyntheticClusterIpi = 1; + synthetic_features.Bank0.DirectSyntheticTimers = 1; + + /* + * On ARM64, have enlightenments off by default + * as they're not needed for performance. + */ + if (whpx->hyperv_enlightenments_required) { + hr = whp_dispatch.WHvSetPartitionProperty( + whpx->partition, + WHvPartitionPropertyCodeSyntheticProcessorFeaturesBanks, + &synthetic_features, + sizeof(WHV_SYNTHETIC_PROCESSOR_FEATURES_BANKS)); + if (FAILED(hr)) { + error_report("WHPX: Failed to set synthetic features, hr=%08lx", hr); + ret = -EINVAL; + goto error; + } + } + hr = whp_dispatch.WHvSetupPartition(whpx->partition); if (FAILED(hr)) { error_report("WHPX: Failed to setup partition, hr=%08lx", hr); diff --git a/target/avr/cpu-param.h b/target/avr/cpu-param.h index f74bfc258046..130768db51fa 100644 --- a/target/avr/cpu-param.h +++ b/target/avr/cpu-param.h @@ -22,9 +22,6 @@ #define AVR_CPU_PARAM_H #define TARGET_PAGE_BITS 10 -#define TARGET_PHYS_ADDR_SPACE_BITS 24 #define TARGET_VIRT_ADDR_SPACE_BITS 24 -#define TARGET_INSN_START_EXTRA_WORDS 0 - #endif diff --git a/target/avr/meson.build b/target/avr/meson.build index 3e172bde1ce5..af5e7d63741d 100644 --- a/target/avr/meson.build +++ b/target/avr/meson.build @@ -11,10 +11,13 @@ avr_ss.add(files( 'translate.c', 'helper.c', 'cpu.c', - 'gdbstub.c', 'disas.c')) +avr_common_system_ss = ss.source_set() +avr_common_system_ss.add(files('gdbstub.c')) + avr_system_ss.add(files('machine.c')) target_arch += {'avr': avr_ss} target_system_arch += {'avr': avr_system_ss} +target_common_system_arch += {'avr': avr_common_system_ss} diff --git a/target/avr/translate.c b/target/avr/translate.c index 78ae83df219c..649dd4b0112f 100644 --- a/target/avr/translate.c +++ b/target/avr/translate.c @@ -2689,7 +2689,7 @@ static void avr_tr_insn_start(DisasContextBase *dcbase, CPUState *cs) { DisasContext *ctx = container_of(dcbase, DisasContext, base); - tcg_gen_insn_start(ctx->npc); + tcg_gen_insn_start(ctx->npc, 0, 0); } static void avr_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) diff --git a/target/hexagon/cpu-param.h b/target/hexagon/cpu-param.h index 635d509e7436..1f0f22a79687 100644 --- a/target/hexagon/cpu-param.h +++ b/target/hexagon/cpu-param.h @@ -20,9 +20,6 @@ #define TARGET_PAGE_BITS 16 /* 64K pages */ -#define TARGET_PHYS_ADDR_SPACE_BITS 36 #define TARGET_VIRT_ADDR_SPACE_BITS 32 -#define TARGET_INSN_START_EXTRA_WORDS 0 - #endif diff --git a/target/hexagon/gdbstub.c b/target/hexagon/gdbstub.c index 12d6b3bbcbb1..b9856cfc978f 100644 --- a/target/hexagon/gdbstub.c +++ b/target/hexagon/gdbstub.c @@ -29,11 +29,11 @@ int hexagon_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n) for (int i = 0; i < NUM_PREGS; i++) { p3_0 = deposit32(p3_0, i * 8, 8, env->pred[i]); } - return gdb_get_regl(mem_buf, p3_0); + return gdb_get_reg32(mem_buf, p3_0); } if (n < TOTAL_PER_THREAD_REGS) { - return gdb_get_regl(mem_buf, env->gpr[n]); + return gdb_get_reg32(mem_buf, env->gpr[n]); } n -= TOTAL_PER_THREAD_REGS; @@ -56,12 +56,12 @@ int hexagon_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) for (int i = 0; i < NUM_PREGS; i++) { env->pred[i] = extract32(p3_0, i * 8, 8); } - return sizeof(target_ulong); + return 4; } if (n < TOTAL_PER_THREAD_REGS) { env->gpr[n] = ldl_le_p(mem_buf); - return sizeof(target_ulong); + return 4; } n -= TOTAL_PER_THREAD_REGS; @@ -81,7 +81,7 @@ static int gdb_get_vreg(CPUHexagonState *env, GByteArray *mem_buf, int n) int total = 0; int i; for (i = 0; i < ARRAY_SIZE(env->VRegs[n].uw); i++) { - total += gdb_get_regl(mem_buf, env->VRegs[n].uw[i]); + total += gdb_get_reg32(mem_buf, env->VRegs[n].uw[i]); } return total; } @@ -91,7 +91,7 @@ static int gdb_get_qreg(CPUHexagonState *env, GByteArray *mem_buf, int n) int total = 0; int i; for (i = 0; i < ARRAY_SIZE(env->QRegs[n].uw); i++) { - total += gdb_get_regl(mem_buf, env->QRegs[n].uw[i]); + total += gdb_get_reg32(mem_buf, env->QRegs[n].uw[i]); } return total; } diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c index 2fdc956bf997..8a223f6e13e5 100644 --- a/target/hexagon/translate.c +++ b/target/hexagon/translate.c @@ -998,7 +998,7 @@ static void hexagon_tr_insn_start(DisasContextBase *dcbase, CPUState *cpu) { DisasContext *ctx = container_of(dcbase, DisasContext, base); - tcg_gen_insn_start(ctx->base.pc_next); + tcg_gen_insn_start(ctx->base.pc_next, 0, 0); } static bool pkt_crosses_page(CPUHexagonState *env, DisasContext *ctx) diff --git a/target/hppa/cpu-param.h b/target/hppa/cpu-param.h index 9bf7ac76d0c5..3763bca12526 100644 --- a/target/hppa/cpu-param.h +++ b/target/hppa/cpu-param.h @@ -9,16 +9,11 @@ #define HPPA_CPU_PARAM_H #if defined(CONFIG_USER_ONLY) && defined(TARGET_ABI32) -# define TARGET_PHYS_ADDR_SPACE_BITS 32 # define TARGET_VIRT_ADDR_SPACE_BITS 32 #else -/* ??? PA-8000 through 8600 have 40 bits; PA-8700 and 8900 have 44 bits. */ -# define TARGET_PHYS_ADDR_SPACE_BITS 40 # define TARGET_VIRT_ADDR_SPACE_BITS 64 #endif #define TARGET_PAGE_BITS 12 -#define TARGET_INSN_START_EXTRA_WORDS 2 - #endif diff --git a/target/hppa/cpu-qom.h b/target/hppa/cpu-qom.h index 5c454bf543b1..7541c25b3d50 100644 --- a/target/hppa/cpu-qom.h +++ b/target/hppa/cpu-qom.h @@ -23,7 +23,13 @@ #include "hw/core/cpu.h" #define TYPE_HPPA_CPU "hppa-cpu" -#define TYPE_HPPA64_CPU "hppa64-cpu" + +#define HPPA_CPU_TYPE_SUFFIX "-" TYPE_HPPA_CPU +#define HPPA_CPU_TYPE_NAME(name) (name HPPA_CPU_TYPE_SUFFIX) + +#define TYPE_HPPA_CPU_PA_7300LC HPPA_CPU_TYPE_NAME("pa-7300lc") +#define TYPE_HPPA_CPU_PA_8500 HPPA_CPU_TYPE_NAME("pa-8500") +#define TYPE_HPPA_CPU_PA_8700 HPPA_CPU_TYPE_NAME("pa-8700") OBJECT_DECLARE_CPU_TYPE(HPPACPU, HPPACPUClass, HPPA_CPU) diff --git a/target/hppa/cpu.c b/target/hppa/cpu.c index 714f3bbdaf72..92027d129a7a 100644 --- a/target/hppa/cpu.c +++ b/target/hppa/cpu.c @@ -203,13 +203,6 @@ static void hppa_cpu_realizefn(DeviceState *dev, Error **errp) tcg_cflags_set(cs, CF_PCREL); } -static void hppa_cpu_initfn(Object *obj) -{ - CPUHPPAState *env = cpu_env(CPU(obj)); - - env->is_pa20 = !!object_dynamic_cast(obj, TYPE_HPPA64_CPU); -} - static void hppa_cpu_reset_hold(Object *obj, ResetType type) { HPPACPUClass *scc = HPPA_CPU_GET_CLASS(obj); @@ -236,9 +229,14 @@ static void hppa_cpu_reset_hold(Object *obj, ResetType type) static ObjectClass *hppa_cpu_class_by_name(const char *cpu_model) { - g_autofree char *typename = g_strconcat(cpu_model, "-cpu", NULL); + ObjectClass *oc; + char *typename; + + typename = g_strdup_printf(HPPA_CPU_TYPE_NAME("%s"), cpu_model); + oc = object_class_by_name(typename); + g_free(typename); - return object_class_by_name(typename); + return oc; } #ifndef CONFIG_USER_ONLY @@ -279,6 +277,19 @@ static const TCGCPUOps hppa_tcg_ops = { #endif /* !CONFIG_USER_ONLY */ }; +static void hppa_cpu_class_base_init(ObjectClass *oc, const void *data) +{ + HPPACPUClass *acc = HPPA_CPU_CLASS(oc); + /* Make sure all CPU models define a HPPACPUDef */ + g_assert(!object_class_is_abstract(oc) && data != NULL); + acc->def = data; + /* + * Verify assumptions made in hppa_abs_to_phys_pa2_w1() on the size + * of the physical address space. + */ + g_assert(acc->def->phys_addr_bits <= 54); +} + static void hppa_cpu_class_init(ObjectClass *oc, const void *data) { DeviceClass *dc = DEVICE_CLASS(oc); @@ -313,14 +324,34 @@ static const TypeInfo hppa_cpu_type_infos[] = { .parent = TYPE_CPU, .instance_size = sizeof(HPPACPU), .instance_align = __alignof(HPPACPU), - .instance_init = hppa_cpu_initfn, - .abstract = false, + .abstract = true, .class_size = sizeof(HPPACPUClass), .class_init = hppa_cpu_class_init, + .class_base_init = hppa_cpu_class_base_init, + }, + { + .name = TYPE_HPPA_CPU_PA_7300LC, + .parent = TYPE_HPPA_CPU, + .class_data = &(const HPPACPUDef) { + .phys_addr_bits = 32, + .is_pa20 = false, + }, + }, + { + .name = TYPE_HPPA_CPU_PA_8500, + .parent = TYPE_HPPA_CPU, + .class_data = &(const HPPACPUDef) { + .phys_addr_bits = 40, + .is_pa20 = true, + }, }, { - .name = TYPE_HPPA64_CPU, + .name = TYPE_HPPA_CPU_PA_8700, .parent = TYPE_HPPA_CPU, + .class_data = &(const HPPACPUDef) { + .phys_addr_bits = 44, + .is_pa20 = true, + }, }, }; diff --git a/target/hppa/cpu.h b/target/hppa/cpu.h index 012e54f8f6fa..7d47afe8efa5 100644 --- a/target/hppa/cpu.h +++ b/target/hppa/cpu.h @@ -270,8 +270,6 @@ typedef struct CPUArchState { /* Fields up to this point are cleared by a CPU reset */ struct {} end_reset_fields; - bool is_pa20; - target_ulong kernel_entry; /* Linux kernel was loaded here */ target_ulong cmdline_or_bootorder; target_ulong initrd_base, initrd_end; @@ -290,6 +288,18 @@ struct ArchCPU { QEMUTimer *alarm_timer; }; +/** + * HPPACPUDef: + * @phys_addr_bits: Number of bits in the physical address space. + * @is_pa20: Whether the CPU model follows the PA-RISC 2.0 or 1.1 spec. + * + * Configuration options for a HPPA CPU model. + */ +typedef struct HPPACPUDef { + uint8_t phys_addr_bits; + bool is_pa20; +} HPPACPUDef; + /** * HPPACPUClass: * @parent_realize: The parent class' realize handler. @@ -302,11 +312,22 @@ struct HPPACPUClass { DeviceRealize parent_realize; ResettablePhases parent_phases; + const HPPACPUDef *def; }; -static inline bool hppa_is_pa20(const CPUHPPAState *env) +static inline const HPPACPUDef *hppa_def(CPUHPPAState *env) +{ + return HPPA_CPU_GET_CLASS(env_cpu(env))->def; +} + +static inline uint8_t hppa_phys_addr_bits(CPUHPPAState *env) +{ + return hppa_def(env)->phys_addr_bits; +} + +static inline bool hppa_is_pa20(CPUHPPAState *env) { - return env->is_pa20; + return hppa_def(env)->is_pa20; } static inline int HPPA_BTLB_ENTRIES(CPUHPPAState *env) @@ -336,8 +357,9 @@ static inline vaddr hppa_form_gva(CPUHPPAState *env, uint64_t spc, return hppa_form_gva_mask(env->gva_offset_mask, spc, off); } -hwaddr hppa_abs_to_phys_pa2_w0(vaddr addr); -hwaddr hppa_abs_to_phys_pa2_w1(vaddr addr); +hwaddr hppa_abs_to_phys_pa1x(uint8_t phys_addr_bits, vaddr addr); +hwaddr hppa_abs_to_phys_pa2_w0(uint8_t phys_addr_bits, vaddr addr); +hwaddr hppa_abs_to_phys_pa2_w1(uint8_t phys_addr_bits, vaddr addr); /* * Since PSW_{I,CB} will never need to be in tb->flags, reuse them. diff --git a/target/hppa/mem_helper.c b/target/hppa/mem_helper.c index cce82e659998..a4b382069d89 100644 --- a/target/hppa/mem_helper.c +++ b/target/hppa/mem_helper.c @@ -29,7 +29,12 @@ #include "hw/core/cpu.h" #include "trace.h" -hwaddr hppa_abs_to_phys_pa2_w1(vaddr addr) +hwaddr hppa_abs_to_phys_pa1x(uint8_t phys_addr_bits, vaddr addr) +{ + return extract64(addr, 0, phys_addr_bits); +} + +hwaddr hppa_abs_to_phys_pa2_w1(uint8_t phys_addr_bits, vaddr addr) { /* * Figure H-8 "62-bit Absolute Accesses when PSW W-bit is 1" describes @@ -42,11 +47,10 @@ hwaddr hppa_abs_to_phys_pa2_w1(vaddr addr) * Since the supported physical address space is below 54 bits, the * H-8 algorithm is moot and all that is left is to truncate. */ - QEMU_BUILD_BUG_ON(TARGET_PHYS_ADDR_SPACE_BITS > 54); - return sextract64(addr, 0, TARGET_PHYS_ADDR_SPACE_BITS); + return sextract64(addr, 0, phys_addr_bits); } -hwaddr hppa_abs_to_phys_pa2_w0(vaddr addr) +hwaddr hppa_abs_to_phys_pa2_w0(uint8_t phys_addr_bits, vaddr addr) { /* * See Figure H-10, "Absolute Accesses when PSW W-bit is 0", @@ -67,7 +71,7 @@ hwaddr hppa_abs_to_phys_pa2_w0(vaddr addr) * is what can be seen on physical machines too. */ addr = (uint32_t)addr; - addr |= -1ull << (TARGET_PHYS_ADDR_SPACE_BITS - 4); + addr |= -1ull << (phys_addr_bits - 4); } return addr; } @@ -209,15 +213,16 @@ int hppa_get_physical_address(CPUHPPAState *env, vaddr addr, int mmu_idx, /* Virtual translation disabled. Map absolute to physical. */ if (MMU_IDX_MMU_DISABLED(mmu_idx)) { + const uint8_t phys_addr_bits = hppa_phys_addr_bits(env); switch (mmu_idx) { case MMU_ABS_W_IDX: - phys = hppa_abs_to_phys_pa2_w1(addr); + phys = hppa_abs_to_phys_pa2_w1(phys_addr_bits, addr); break; case MMU_ABS_IDX: if (hppa_is_pa20(env)) { - phys = hppa_abs_to_phys_pa2_w0(addr); + phys = hppa_abs_to_phys_pa2_w0(phys_addr_bits, addr); } else { - phys = (uint32_t)addr; + phys = hppa_abs_to_phys_pa1x(phys_addr_bits, addr); } break; default: @@ -558,7 +563,7 @@ static void itlbt_pa20(CPUHPPAState *env, target_ulong r1, /* Align per the page size. */ ent->pa &= TARGET_PAGE_MASK << mask_shift; /* Ignore the bits beyond physical address space. */ - ent->pa = sextract64(ent->pa, 0, TARGET_PHYS_ADDR_SPACE_BITS); + ent->pa = sextract64(ent->pa, 0, hppa_phys_addr_bits(env)); ent->t = extract64(r2, 61, 1); ent->d = extract64(r2, 60, 1); diff --git a/target/hppa/translate.c b/target/hppa/translate.c index 0f8a66f77321..70c20c003776 100644 --- a/target/hppa/translate.c +++ b/target/hppa/translate.c @@ -307,9 +307,9 @@ void hppa_translate_init(void) cpu_gr[0] = NULL; for (i = 1; i < 32; i++) { - cpu_gr[i] = tcg_global_mem_new(tcg_env, - offsetof(CPUHPPAState, gr[i]), - gr_names[i]); + cpu_gr[i] = tcg_global_mem_new_i64(tcg_env, + offsetof(CPUHPPAState, gr[i]), + gr_names[i]); } for (i = 0; i < 4; i++) { cpu_sr[i] = tcg_global_mem_new_i64(tcg_env, @@ -322,7 +322,7 @@ void hppa_translate_init(void) for (i = 0; i < ARRAY_SIZE(vars); ++i) { const GlobalVar *v = &vars[i]; - *v->var = tcg_global_mem_new(tcg_env, v->ofs, v->name); + *v->var = tcg_global_mem_new_i64(tcg_env, v->ofs, v->name); } cpu_psw_xb = tcg_global_mem_new_i32(tcg_env, diff --git a/target/i386/cpu-param.h b/target/i386/cpu-param.h index ebb844bcc83b..5915ef6266c1 100644 --- a/target/i386/cpu-param.h +++ b/target/i386/cpu-param.h @@ -9,7 +9,6 @@ #define I386_CPU_PARAM_H #ifdef TARGET_X86_64 -# define TARGET_PHYS_ADDR_SPACE_BITS 52 /* * ??? This is really 48 bits, sign-extended, but the only thing * accessible to userland with bit 48 set is the VSYSCALL, and that @@ -17,11 +16,8 @@ */ # define TARGET_VIRT_ADDR_SPACE_BITS 47 #else -# define TARGET_PHYS_ADDR_SPACE_BITS 36 # define TARGET_VIRT_ADDR_SPACE_BITS 32 #endif #define TARGET_PAGE_BITS 12 -#define TARGET_INSN_START_EXTRA_WORDS 1 - #endif diff --git a/target/i386/cpu.c b/target/i386/cpu.c index eaa01438c1b9..0a29ff805fa1 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -1656,7 +1656,7 @@ FeatureWordInfo feature_word_info[FEATURE_WORDS] = { "vmx-apicv-register", "vmx-apicv-vid", "vmx-ple", "vmx-rdrand-exit", "vmx-invpcid-exit", "vmx-vmfunc", "vmx-shadow-vmcs", "vmx-encls-exit", "vmx-rdseed-exit", "vmx-pml", NULL, NULL, - "vmx-xsaves", NULL, NULL, NULL, + "vmx-xsaves", NULL, "vmx-mbec", NULL, NULL, "vmx-tsc-scaling", "vmx-enable-user-wait-pause", NULL, NULL, NULL, NULL, NULL, }, @@ -1971,6 +1971,10 @@ static FeatureDep feature_dependencies[] = { .from = { FEAT_VMX_SECONDARY_CTLS, VMX_SECONDARY_EXEC_ENABLE_EPT }, .to = { FEAT_VMX_SECONDARY_CTLS, VMX_SECONDARY_EXEC_UNRESTRICTED_GUEST }, }, + { + .from = { FEAT_VMX_SECONDARY_CTLS, VMX_SECONDARY_EXEC_ENABLE_EPT }, + .to = { FEAT_VMX_SECONDARY_CTLS, VMX_SECONDARY_EXEC_MODE_BASED_EPT_EXEC }, + }, { .from = { FEAT_VMX_SECONDARY_CTLS, VMX_SECONDARY_EXEC_ENABLE_VPID }, .to = { FEAT_VMX_EPT_VPID_CAPS, 0xffffffffull << 32 }, @@ -5257,6 +5261,15 @@ static const X86CPUDefinition builtin_x86_defs[] = { { /* end of list */ }, } }, + { + .version = 6, + .note = "with cet-ss, cet-ibt, its-no", + .cache_info = &xeon_spr_cache_info, + .props = (PropValue[]) { + { "its-no", "on" }, + { /* end of list */ }, + } + }, { /* end of list */ } } }, @@ -5430,6 +5443,15 @@ static const X86CPUDefinition builtin_x86_defs[] = { { /* end of list */ }, } }, + { + .version = 5, + .note = "with cet-ss, cet-ibt, its-no", + .cache_info = &xeon_gnr_cache_info, + .props = (PropValue[]) { + { "its-no", "on" }, + { /* end of list */ }, + } + }, { /* end of list */ }, }, }, @@ -5787,6 +5809,15 @@ static const X86CPUDefinition builtin_x86_defs[] = { { /* end of list */ }, } }, + { + .version = 5, + .note = "with ITS_NO", + .cache_info = &xeon_srf_cache_info, + .props = (PropValue[]) { + { "its-no", "on" }, + { /* end of list */ }, + } + }, { /* end of list */ }, }, }, @@ -5933,6 +5964,14 @@ static const X86CPUDefinition builtin_x86_defs[] = { { /* end of list */ }, } }, + { + .version = 3, + .note = "with cet-ss, cet-ibt, ITS_NO", + .props = (PropValue[]) { + { "its-no", "on" }, + { /* end of list */ }, + } + }, { /* end of list */ }, }, }, @@ -9526,13 +9565,7 @@ void x86_cpu_expand_features(X86CPU *cpu, Error **errp) /* Intel Processor Trace requires CPUID[0x14] */ if ((env->features[FEAT_7_0_EBX] & CPUID_7_0_EBX_INTEL_PT)) { - if (cpu->intel_pt_auto_level) { - x86_cpu_adjust_level(cpu, &cpu->env.cpuid_min_level, 0x14); - } else if (cpu->env.cpuid_min_level < 0x14) { - mark_unavailable_features(cpu, FEAT_7_0_EBX, - CPUID_7_0_EBX_INTEL_PT, - "Intel PT need CPUID leaf 0x14, please set by \"-cpu ...,intel-pt=on,min-level=0x14\""); - } + x86_cpu_adjust_level(cpu, &cpu->env.cpuid_min_level, 0x14); } /* @@ -9941,12 +9974,9 @@ static void x86_cpu_realizefn(DeviceState *dev, Error **errp) * accel-specific code in cpu_exec_realizefn. */ if (env->features[FEAT_8000_0001_EDX] & CPUID_EXT2_LM) { - if (cpu->phys_bits && - (cpu->phys_bits > TARGET_PHYS_ADDR_SPACE_BITS || - cpu->phys_bits < 32)) { - error_setg(errp, "phys-bits should be between 32 and %u " - " (but is %u)", - TARGET_PHYS_ADDR_SPACE_BITS, cpu->phys_bits); + if (cpu->phys_bits && cpu->phys_bits < 32) { + error_setg(errp, "phys-bits should be at least 32" + " (but is %u)", cpu->phys_bits); return; } /* @@ -10553,10 +10583,6 @@ static const Property x86_cpu_properties[] = { * to the specific Windows version being used." */ DEFINE_PROP_INT32("x-hv-max-vps", X86CPU, hv_max_vps, -1), - DEFINE_PROP_BOOL("x-hv-synic-kvm-only", X86CPU, hyperv_synic_kvm_only, - false), - DEFINE_PROP_BOOL("x-intel-pt-auto-level", X86CPU, intel_pt_auto_level, - true), DEFINE_PROP_BOOL("x-l1-cache-per-thread", X86CPU, l1_cache_per_core, true), DEFINE_PROP_BOOL("x-force-cpuid-0x1f", X86CPU, force_cpuid_0x1f, false), diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 9f222a0c9fe0..5a62aa615790 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -1329,6 +1329,7 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w); #define MSR_ARCH_CAP_PBRSB_NO (1U << 24) #define MSR_ARCH_CAP_GDS_NO (1U << 26) #define MSR_ARCH_CAP_RFDS_NO (1U << 27) +#define MSR_ARCH_CAP_ITS_NO (1U << 62) #define MSR_CORE_CAP_SPLIT_LOCK_DETECT (1U << 5) @@ -1414,6 +1415,7 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w); #define VMX_SECONDARY_EXEC_RDSEED_EXITING 0x00010000 #define VMX_SECONDARY_EXEC_ENABLE_PML 0x00020000 #define VMX_SECONDARY_EXEC_XSAVES 0x00100000 +#define VMX_SECONDARY_EXEC_MODE_BASED_EPT_EXEC 0x00400000 #define VMX_SECONDARY_EXEC_TSC_SCALING 0x02000000 #define VMX_SECONDARY_EXEC_ENABLE_USER_WAIT_PAUSE 0x04000000 @@ -2286,7 +2288,7 @@ typedef struct CPUArchState { QEMUTimer *xen_periodic_timer; QemuMutex xen_timers_lock; #endif -#if defined(CONFIG_HVF) || defined(CONFIG_MSHV) +#if defined(CONFIG_HVF) || defined(CONFIG_MSHV) || defined(CONFIG_WHPX) void *emu_mmio_buf; #endif @@ -2333,7 +2335,6 @@ struct ArchCPU { uint32_t hyperv_spinlock_attempts; char *hyperv_vendor; - bool hyperv_synic_kvm_only; uint64_t hyperv_features; bool hyperv_passthrough; OnOffAuto hyperv_no_nonarch_cs; @@ -2456,9 +2457,6 @@ struct ArchCPU { /* Only advertise TOPOEXT features that AMD defines */ bool amd_topoext_features_only; - /* Enable auto level-increase for Intel Processor Trace leave */ - bool intel_pt_auto_level; - /* if true fill the top bits of the MTRR_PHYSMASKn variable range */ bool fill_mtrr_mask; diff --git a/target/i386/emulate/meson.build b/target/i386/emulate/meson.build index b6dafb6a5be6..1fa1a8e8ec8f 100644 --- a/target/i386/emulate/meson.build +++ b/target/i386/emulate/meson.build @@ -2,7 +2,16 @@ emulator_files = files( 'x86_decode.c', 'x86_emu.c', 'x86_flags.c', + 'x86_mmu.c' +) + +emulator_helper_files = files( + 'x86_helpers.c' ) i386_system_ss.add(when: [hvf, 'CONFIG_HVF'], if_true: emulator_files) i386_system_ss.add(when: 'CONFIG_MSHV', if_true: emulator_files) +i386_system_ss.add(when: 'CONFIG_WHPX', if_true: emulator_files) + +i386_system_ss.add(when: 'CONFIG_MSHV', if_true: emulator_helper_files) +i386_system_ss.add(when: 'CONFIG_WHPX', if_true: emulator_helper_files) diff --git a/target/i386/emulate/x86.h b/target/i386/emulate/x86.h index 73edccfba006..caf0e3be50ef 100644 --- a/target/i386/emulate/x86.h +++ b/target/i386/emulate/x86.h @@ -263,6 +263,7 @@ bool x86_is_protected(CPUState *cpu); bool x86_is_real(CPUState *cpu); bool x86_is_v8086(CPUState *cpu); bool x86_is_long_mode(CPUState *cpu); +bool x86_is_la57(CPUState *cpu); bool x86_is_long64_mode(CPUState *cpu); bool x86_is_paging_mode(CPUState *cpu); bool x86_is_pae_enabled(CPUState *cpu); diff --git a/target/i386/emulate/x86_decode.c b/target/i386/emulate/x86_decode.c index d037ed114201..bae1dd4d6f84 100644 --- a/target/i386/emulate/x86_decode.c +++ b/target/i386/emulate/x86_decode.c @@ -77,11 +77,7 @@ static inline uint64_t decode_bytes(CPUX86State *env, struct x86_decode *decode, memcpy(&val, decode->stream->bytes + decode->len, size); } else { target_ulong va = linear_rip(env_cpu(env), env->eip) + decode->len; - if (emul_ops->fetch_instruction) { - emul_ops->fetch_instruction(env_cpu(env), &val, va, size); - } else { - emul_ops->read_mem(env_cpu(env), &val, va, size); - } + x86_read_mem(env_cpu(env), &val, va, size); } decode->len += size; @@ -1699,7 +1695,7 @@ void *get_reg_ref(CPUX86State *env, int reg, int rex_present, target_ulong get_reg_val(CPUX86State *env, int reg, int rex_present, int is_extended, int size) { - target_ulong val = 0; + uint64_t val = 0; memcpy(&val, get_reg_ref(env, reg, rex_present, is_extended, size), size); @@ -2088,8 +2084,6 @@ static void decode_opcodes(CPUX86State *env, struct x86_decode *decode) static uint32_t decode_opcode(CPUX86State *env, struct x86_decode *decode) { - memset(decode, 0, sizeof(*decode)); - decode_prefix(env, decode); set_addressing_size(env, decode); set_operand_size(env, decode); @@ -2101,6 +2095,8 @@ static uint32_t decode_opcode(CPUX86State *env, struct x86_decode *decode) uint32_t decode_instruction(CPUX86State *env, struct x86_decode *decode) { + memset(decode, 0, sizeof(*decode)); + return decode_opcode(env, decode); } diff --git a/target/i386/emulate/x86_emu.c b/target/i386/emulate/x86_emu.c index 4409f7bc134b..55b1a68eb6c9 100644 --- a/target/i386/emulate/x86_emu.c +++ b/target/i386/emulate/x86_emu.c @@ -36,15 +36,36 @@ ///////////////////////////////////////////////////////////////////////// #include "qemu/osdep.h" +#include "qemu/error-report.h" #include "panic.h" #include "x86_decode.h" #include "x86.h" #include "x86_emu.h" #include "x86_flags.h" +#include "x86_mmu.h" + +#ifdef TARGET_X86_64 +#define EXEC_2OP_FLAGS_CMD_64(env, decode, cmd, FLAGS_FUNC, save_res) \ + case 8: \ + { \ + uint64_t v1 = (uint64_t)decode->op[0].val; \ + uint64_t v2 = (uint64_t)decode->op[1].val; \ + uint64_t diff = v1 cmd v2; \ + if (save_res) { \ + if (write_val_ext(env, &decode->op[0], diff, 8)) { return 1; } \ + } \ + FLAGS_FUNC##64(env, v1, v2, diff); \ + break; \ + } +#else +#define EXEC_2OP_FLAGS_CMD_64(env, decode, cmd, FLAGS_FUNC, save_res) +#endif #define EXEC_2OP_FLAGS_CMD(env, decode, cmd, FLAGS_FUNC, save_res) \ { \ - fetch_operands(env, decode, 2, true, true, false); \ + if (fetch_operands(env, decode, 2, true, true, false)) {\ + return 1; \ + }\ switch (decode->operand_size) { \ case 1: \ { \ @@ -52,7 +73,7 @@ uint8_t v2 = (uint8_t)decode->op[1].val; \ uint8_t diff = v1 cmd v2; \ if (save_res) { \ - write_val_ext(env, &decode->op[0], diff, 1); \ + if (write_val_ext(env, &decode->op[0], diff, 1)) { return 1; } \ } \ FLAGS_FUNC##8(env, v1, v2, diff); \ break; \ @@ -63,7 +84,7 @@ uint16_t v2 = (uint16_t)decode->op[1].val; \ uint16_t diff = v1 cmd v2; \ if (save_res) { \ - write_val_ext(env, &decode->op[0], diff, 2); \ + if (write_val_ext(env, &decode->op[0], diff, 2)) { return 1; } \ } \ FLAGS_FUNC##16(env, v1, v2, diff); \ break; \ @@ -74,11 +95,12 @@ uint32_t v2 = (uint32_t)decode->op[1].val; \ uint32_t diff = v1 cmd v2; \ if (save_res) { \ - write_val_ext(env, &decode->op[0], diff, 4); \ + if (write_val_ext(env, &decode->op[0], diff, 4)) { return 1; } \ } \ FLAGS_FUNC##32(env, v1, v2, diff); \ break; \ } \ + EXEC_2OP_FLAGS_CMD_64(env, decode, cmd, FLAGS_FUNC, save_res) \ default: \ VM_PANIC("bad size\n"); \ } \ @@ -164,63 +186,77 @@ void write_val_to_reg(void *reg_ptr, target_ulong val, int size) } } -static void write_val_to_mem(CPUX86State *env, target_ulong ptr, target_ulong val, int size) -{ - emul_ops->write_mem(env_cpu(env), &val, ptr, size); -} - -void write_val_ext(CPUX86State *env, struct x86_decode_op *decode, target_ulong val, int size) +bool write_val_ext(CPUX86State *env, struct x86_decode_op *decode, target_ulong val, int size) { if (decode->type == X86_VAR_REG) { write_val_to_reg(decode->regptr, val, size); } else { - write_val_to_mem(env, decode->addr, val, size); + MMUTranslateResult res = x86_write_mem(env_cpu(env), &val, decode->addr, size); + if (res) { + if (res == MMU_TRANSLATE_GPA_UNMAPPED) { + return 0; + } + return 1; + } } + return 0; } uint8_t *read_mmio(CPUX86State *env, target_ulong ptr, int bytes) { - emul_ops->read_mem(env_cpu(env), env->emu_mmio_buf, ptr, bytes); + MMUTranslateResult res = x86_read_mem(env_cpu(env), env->emu_mmio_buf, ptr, bytes); + if (res) { + if (res == MMU_TRANSLATE_GPA_UNMAPPED) { + memset(env->emu_mmio_buf, 0xFF, bytes); + return env->emu_mmio_buf; + } + return NULL; + } return env->emu_mmio_buf; } -static target_ulong read_val_from_mem(CPUX86State *env, target_long ptr, int size) +static bool read_val_from_mem(CPUX86State *env, target_long ptr, int size, target_ulong* val) { - target_ulong val; uint8_t *mmio_ptr; mmio_ptr = read_mmio(env, ptr, size); + if (mmio_ptr == NULL) { + return 1; + } switch (size) { case 1: - val = *(uint8_t *)mmio_ptr; + *val = *(uint8_t *)mmio_ptr; break; case 2: - val = *(uint16_t *)mmio_ptr; + *val = *(uint16_t *)mmio_ptr; break; case 4: - val = *(uint32_t *)mmio_ptr; + *val = *(uint32_t *)mmio_ptr; break; case 8: - val = *(uint64_t *)mmio_ptr; + *val = *(uint64_t *)mmio_ptr; break; default: VM_PANIC("bad size\n"); break; } - return val; + return 0; } -target_ulong read_val_ext(CPUX86State *env, struct x86_decode_op *decode, int size) +bool read_val_ext(CPUX86State *env, struct x86_decode_op *decode, int size, target_ulong* val) { if (decode->type == X86_VAR_REG) { - return read_val_from_reg(decode->regptr, size); + *val = read_val_from_reg(decode->regptr, size); } else { - return read_val_from_mem(env, decode->addr, size); + if (read_val_from_mem(env, decode->addr, size, val)) { + return 1; + } } + return 0; } -static void fetch_operands(CPUX86State *env, struct x86_decode *decode, +static bool fetch_operands(CPUX86State *env, struct x86_decode *decode, int n, bool val_op0, bool val_op1, bool val_op2) { int i; @@ -240,8 +276,10 @@ static void fetch_operands(CPUX86State *env, struct x86_decode *decode, case X86_VAR_RM: calc_modrm_operand(env, decode, &decode->op[i]); if (calc_val[i]) { - decode->op[i].val = read_val_ext(env, &decode->op[i], - decode->operand_size); + if (read_val_ext(env, &decode->op[i],decode->operand_size, + &decode->op[i].val)) { + return 1; + } } break; case X86_VAR_OFFSET: @@ -249,68 +287,81 @@ static void fetch_operands(CPUX86State *env, struct x86_decode *decode, decode->op[i].addr, R_DS); if (calc_val[i]) { - decode->op[i].val = read_val_ext(env, &decode->op[i], - decode->operand_size); + if (read_val_ext(env, &decode->op[i], decode->operand_size, + &decode->op[i].val)) { + return 1; + } } break; default: break; } } + return 0; } -static void exec_mov(CPUX86State *env, struct x86_decode *decode) +static bool exec_mov(CPUX86State *env, struct x86_decode *decode) { fetch_operands(env, decode, 2, false, true, false); - write_val_ext(env, &decode->op[0], decode->op[1].val, - decode->operand_size); + if (write_val_ext(env, &decode->op[0], decode->op[1].val, + decode->operand_size)) { + return 1; + } env->eip += decode->len; + return 0; } -static void exec_add(CPUX86State *env, struct x86_decode *decode) +static bool exec_add(CPUX86State *env, struct x86_decode *decode) { EXEC_2OP_FLAGS_CMD(env, decode, +, SET_FLAGS_OSZAPC_ADD, true); env->eip += decode->len; + return 0; } -static void exec_or(CPUX86State *env, struct x86_decode *decode) +static bool exec_or(CPUX86State *env, struct x86_decode *decode) { EXEC_2OP_FLAGS_CMD(env, decode, |, SET_FLAGS_OSZAPC_LOGIC, true); env->eip += decode->len; + return 0; } -static void exec_adc(CPUX86State *env, struct x86_decode *decode) +static bool exec_adc(CPUX86State *env, struct x86_decode *decode) { EXEC_2OP_FLAGS_CMD(env, decode, +get_CF(env)+, SET_FLAGS_OSZAPC_ADD, true); env->eip += decode->len; + return 0; } -static void exec_sbb(CPUX86State *env, struct x86_decode *decode) +static bool exec_sbb(CPUX86State *env, struct x86_decode *decode) { EXEC_2OP_FLAGS_CMD(env, decode, -get_CF(env)-, SET_FLAGS_OSZAPC_SUB, true); env->eip += decode->len; + return 0; } -static void exec_and(CPUX86State *env, struct x86_decode *decode) +static bool exec_and(CPUX86State *env, struct x86_decode *decode) { EXEC_2OP_FLAGS_CMD(env, decode, &, SET_FLAGS_OSZAPC_LOGIC, true); env->eip += decode->len; + return 0; } -static void exec_sub(CPUX86State *env, struct x86_decode *decode) +static bool exec_sub(CPUX86State *env, struct x86_decode *decode) { EXEC_2OP_FLAGS_CMD(env, decode, -, SET_FLAGS_OSZAPC_SUB, true); env->eip += decode->len; + return 0; } -static void exec_xor(CPUX86State *env, struct x86_decode *decode) +static bool exec_xor(CPUX86State *env, struct x86_decode *decode) { EXEC_2OP_FLAGS_CMD(env, decode, ^, SET_FLAGS_OSZAPC_LOGIC, true); env->eip += decode->len; + return 0; } -static void exec_neg(CPUX86State *env, struct x86_decode *decode) +static bool exec_neg(CPUX86State *env, struct x86_decode *decode) { /*EXEC_2OP_FLAGS_CMD(env, decode, -, SET_FLAGS_OSZAPC_SUB, false);*/ int32_t val; @@ -331,15 +382,17 @@ static void exec_neg(CPUX86State *env, struct x86_decode *decode) /*lflags_to_rflags(env);*/ env->eip += decode->len; + return 0; } -static void exec_cmp(CPUX86State *env, struct x86_decode *decode) +static bool exec_cmp(CPUX86State *env, struct x86_decode *decode) { EXEC_2OP_FLAGS_CMD(env, decode, -, SET_FLAGS_OSZAPC_SUB, false); env->eip += decode->len; + return 0; } -static void exec_inc(CPUX86State *env, struct x86_decode *decode) +static bool exec_inc(CPUX86State *env, struct x86_decode *decode) { decode->op[1].type = X86_VAR_IMMEDIATE; decode->op[1].val = 0; @@ -347,33 +400,37 @@ static void exec_inc(CPUX86State *env, struct x86_decode *decode) EXEC_2OP_FLAGS_CMD(env, decode, +1+, SET_FLAGS_OSZAP_ADD, true); env->eip += decode->len; + return 0; } -static void exec_dec(CPUX86State *env, struct x86_decode *decode) +static bool exec_dec(CPUX86State *env, struct x86_decode *decode) { decode->op[1].type = X86_VAR_IMMEDIATE; decode->op[1].val = 0; EXEC_2OP_FLAGS_CMD(env, decode, -1-, SET_FLAGS_OSZAP_SUB, true); env->eip += decode->len; + return 0; } -static void exec_tst(CPUX86State *env, struct x86_decode *decode) +static bool exec_tst(CPUX86State *env, struct x86_decode *decode) { EXEC_2OP_FLAGS_CMD(env, decode, &, SET_FLAGS_OSZAPC_LOGIC, false); env->eip += decode->len; + return 0; } -static void exec_not(CPUX86State *env, struct x86_decode *decode) +static bool exec_not(CPUX86State *env, struct x86_decode *decode) { fetch_operands(env, decode, 1, true, false, false); write_val_ext(env, &decode->op[0], ~decode->op[0].val, decode->operand_size); env->eip += decode->len; + return 0; } -void exec_movzx(CPUX86State *env, struct x86_decode *decode) +bool exec_movzx(CPUX86State *env, struct x86_decode *decode) { int src_op_size; int op_size = decode->operand_size; @@ -387,13 +444,16 @@ void exec_movzx(CPUX86State *env, struct x86_decode *decode) } decode->operand_size = src_op_size; calc_modrm_operand(env, decode, &decode->op[1]); - decode->op[1].val = read_val_ext(env, &decode->op[1], src_op_size); + if (read_val_ext(env, &decode->op[1], src_op_size, &decode->op[1].val)) { + return 1; + } write_val_ext(env, &decode->op[0], decode->op[1].val, op_size); env->eip += decode->len; + return 0; } -static void exec_out(CPUX86State *env, struct x86_decode *decode) +static bool exec_out(CPUX86State *env, struct x86_decode *decode) { switch (decode->opcode[0]) { case 0xe6: @@ -415,9 +475,10 @@ static void exec_out(CPUX86State *env, struct x86_decode *decode) break; } env->eip += decode->len; + return 0; } -static void exec_in(CPUX86State *env, struct x86_decode *decode) +static bool exec_in(CPUX86State *env, struct x86_decode *decode) { target_ulong val = 0; switch (decode->opcode[0]) { @@ -452,6 +513,7 @@ static void exec_in(CPUX86State *env, struct x86_decode *decode) } env->eip += decode->len; + return 0; } static inline void string_increment_reg(CPUX86State *env, int reg, @@ -466,99 +528,138 @@ static inline void string_increment_reg(CPUX86State *env, int reg, write_reg(env, reg, val, decode->addressing_size); } -static inline void string_rep(CPUX86State *env, struct x86_decode *decode, - void (*func)(CPUX86State *env, +static inline int get_ZF(CPUX86State *env) { + return env->cc_dst ? 0 : CC_Z; +} + +static inline bool string_rep(CPUX86State *env, struct x86_decode *decode, + bool (*func)(CPUX86State *env, struct x86_decode *ins), int rep) { target_ulong rcx = read_reg(env, R_ECX, decode->addressing_size); - while (rcx--) { - func(env, decode); + + while (rcx != 0) { + bool is_cmps_or_scas = decode->cmd == X86_DECODE_CMD_CMPS || decode->cmd == X86_DECODE_CMD_SCAS; + if (func(env, decode)) { + return 1; + } + rcx--; write_reg(env, R_ECX, rcx, decode->addressing_size); - if ((PREFIX_REP == rep) && !env->cc_dst) { + if ((PREFIX_REP == rep) && !get_ZF(env) && is_cmps_or_scas) { break; } - if ((PREFIX_REPN == rep) && env->cc_dst) { + if ((PREFIX_REPN == rep) && get_ZF(env)&& is_cmps_or_scas) { break; } } + return 0; } -static void exec_ins_single(CPUX86State *env, struct x86_decode *decode) +static bool exec_ins_single(CPUX86State *env, struct x86_decode *decode) { + MMUTranslateResult res; + target_ulong addr = linear_addr_size(env_cpu(env), RDI(env), decode->addressing_size, R_ES); emul_ops->handle_io(env_cpu(env), DX(env), env->emu_mmio_buf, 0, decode->operand_size, 1); - emul_ops->write_mem(env_cpu(env), env->emu_mmio_buf, addr, + res = x86_write_mem(env_cpu(env), env->emu_mmio_buf, addr, decode->operand_size); + if (res) { + return 1; + } string_increment_reg(env, R_EDI, decode); + return 0; } -static void exec_ins(CPUX86State *env, struct x86_decode *decode) +static bool exec_ins(CPUX86State *env, struct x86_decode *decode) { + bool res; if (decode->rep) { - string_rep(env, decode, exec_ins_single, 0); + res = string_rep(env, decode, exec_ins_single, 0); } else { - exec_ins_single(env, decode); + res = exec_ins_single(env, decode); } + if (res) { + return 1; + } env->eip += decode->len; + return 0; } -static void exec_outs_single(CPUX86State *env, struct x86_decode *decode) +static bool exec_outs_single(CPUX86State *env, struct x86_decode *decode) { target_ulong addr = decode_linear_addr(env, decode, RSI(env), R_DS); - emul_ops->read_mem(env_cpu(env), env->emu_mmio_buf, addr, + x86_read_mem(env_cpu(env), env->emu_mmio_buf, addr, decode->operand_size); emul_ops->handle_io(env_cpu(env), DX(env), env->emu_mmio_buf, 1, decode->operand_size, 1); string_increment_reg(env, R_ESI, decode); + return 0; } -static void exec_outs(CPUX86State *env, struct x86_decode *decode) +static bool exec_outs(CPUX86State *env, struct x86_decode *decode) { + bool res; if (decode->rep) { - string_rep(env, decode, exec_outs_single, 0); + res = string_rep(env, decode, exec_outs_single, 0); } else { - exec_outs_single(env, decode); + res = exec_outs_single(env, decode); } + if (res) { + return 1; + } env->eip += decode->len; + return 0; } -static void exec_movs_single(CPUX86State *env, struct x86_decode *decode) +static bool exec_movs_single(CPUX86State *env, struct x86_decode *decode) { target_ulong src_addr; target_ulong dst_addr; target_ulong val; + MMUTranslateResult res; src_addr = decode_linear_addr(env, decode, RSI(env), R_DS); dst_addr = linear_addr_size(env_cpu(env), RDI(env), decode->addressing_size, R_ES); - val = read_val_from_mem(env, src_addr, decode->operand_size); - write_val_to_mem(env, dst_addr, val, decode->operand_size); + if (read_val_from_mem(env, src_addr, decode->operand_size, &val)) { + return 1; + } + res = x86_write_mem(env_cpu(env), &val, dst_addr, decode->operand_size); + if (res) { + return 1; + } string_increment_reg(env, R_ESI, decode); string_increment_reg(env, R_EDI, decode); + return 0; } -static void exec_movs(CPUX86State *env, struct x86_decode *decode) +static bool exec_movs(CPUX86State *env, struct x86_decode *decode) { + bool res; if (decode->rep) { - string_rep(env, decode, exec_movs_single, 0); + res = string_rep(env, decode, exec_movs_single, 0); } else { - exec_movs_single(env, decode); + res = exec_movs_single(env, decode); } + if (res) { + return 1; + } env->eip += decode->len; + return 0; } -static void exec_cmps_single(CPUX86State *env, struct x86_decode *decode) +static bool exec_cmps_single(CPUX86State *env, struct x86_decode *decode) { target_ulong src_addr; target_ulong dst_addr; @@ -568,17 +669,22 @@ static void exec_cmps_single(CPUX86State *env, struct x86_decode *decode) decode->addressing_size, R_ES); decode->op[0].type = X86_VAR_IMMEDIATE; - decode->op[0].val = read_val_from_mem(env, src_addr, decode->operand_size); + if (read_val_from_mem(env, src_addr, decode->operand_size, &decode->op[0].val)) { + return 1; + } decode->op[1].type = X86_VAR_IMMEDIATE; - decode->op[1].val = read_val_from_mem(env, dst_addr, decode->operand_size); + if (read_val_from_mem(env, dst_addr, decode->operand_size, &decode->op[1].val)) { + return 1; + } EXEC_2OP_FLAGS_CMD(env, decode, -, SET_FLAGS_OSZAPC_SUB, false); string_increment_reg(env, R_ESI, decode); string_increment_reg(env, R_EDI, decode); + return 0; } -static void exec_cmps(CPUX86State *env, struct x86_decode *decode) +static bool exec_cmps(CPUX86State *env, struct x86_decode *decode) { if (decode->rep) { string_rep(env, decode, exec_cmps_single, decode->rep); @@ -586,24 +692,30 @@ static void exec_cmps(CPUX86State *env, struct x86_decode *decode) exec_cmps_single(env, decode); } env->eip += decode->len; + return 0; } -static void exec_stos_single(CPUX86State *env, struct x86_decode *decode) +static bool exec_stos_single(CPUX86State *env, struct x86_decode *decode) { target_ulong addr; target_ulong val; + MMUTranslateResult res; addr = linear_addr_size(env_cpu(env), RDI(env), decode->addressing_size, R_ES); val = read_reg(env, R_EAX, decode->operand_size); - emul_ops->write_mem(env_cpu(env), &val, addr, decode->operand_size); + res = x86_write_mem(env_cpu(env), &val, addr, decode->operand_size); + if (res) { + return 1; + } string_increment_reg(env, R_EDI, decode); + return 0; } -static void exec_stos(CPUX86State *env, struct x86_decode *decode) +static bool exec_stos(CPUX86State *env, struct x86_decode *decode) { if (decode->rep) { string_rep(env, decode, exec_stos_single, 0); @@ -612,25 +724,29 @@ static void exec_stos(CPUX86State *env, struct x86_decode *decode) } env->eip += decode->len; + return 0; } -static void exec_scas_single(CPUX86State *env, struct x86_decode *decode) +static bool exec_scas_single(CPUX86State *env, struct x86_decode *decode) { target_ulong addr; addr = linear_addr_size(env_cpu(env), RDI(env), decode->addressing_size, R_ES); decode->op[1].type = X86_VAR_IMMEDIATE; - emul_ops->read_mem(env_cpu(env), &decode->op[1].val, addr, decode->operand_size); + x86_read_mem(env_cpu(env), &decode->op[1].val, addr, decode->operand_size); EXEC_2OP_FLAGS_CMD(env, decode, -, SET_FLAGS_OSZAPC_SUB, false); string_increment_reg(env, R_EDI, decode); + return 0; } -static void exec_scas(CPUX86State *env, struct x86_decode *decode) +static bool exec_scas(CPUX86State *env, struct x86_decode *decode) { decode->op[0].type = X86_VAR_REG; decode->op[0].reg = R_EAX; + decode->op[0].regptr = x86_reg(env, R_EAX); + if (decode->rep) { string_rep(env, decode, exec_scas_single, decode->rep); } else { @@ -638,21 +754,23 @@ static void exec_scas(CPUX86State *env, struct x86_decode *decode) } env->eip += decode->len; + return 0; } -static void exec_lods_single(CPUX86State *env, struct x86_decode *decode) +static bool exec_lods_single(CPUX86State *env, struct x86_decode *decode) { target_ulong addr; target_ulong val = 0; addr = decode_linear_addr(env, decode, RSI(env), R_DS); - emul_ops->read_mem(env_cpu(env), &val, addr, decode->operand_size); + x86_read_mem(env_cpu(env), &val, addr, decode->operand_size); write_reg(env, R_EAX, val, decode->operand_size); string_increment_reg(env, R_ESI, decode); + return 0; } -static void exec_lods(CPUX86State *env, struct x86_decode *decode) +static bool exec_lods(CPUX86State *env, struct x86_decode *decode) { if (decode->rep) { string_rep(env, decode, exec_lods_single, 0); @@ -661,6 +779,7 @@ static void exec_lods(CPUX86State *env, struct x86_decode *decode) } env->eip += decode->len; + return 0; } void x86_emul_raise_exception(CPUX86State *env, int exception_index, int error_code) @@ -671,23 +790,25 @@ void x86_emul_raise_exception(CPUX86State *env, int exception_index, int error_c env->exception_injected = 1; } -static void exec_rdmsr(CPUX86State *env, struct x86_decode *decode) +static bool exec_rdmsr(CPUX86State *env, struct x86_decode *decode) { emul_ops->simulate_rdmsr(env_cpu(env)); env->eip += decode->len; + return 0; } -static void exec_wrmsr(CPUX86State *env, struct x86_decode *decode) +static bool exec_wrmsr(CPUX86State *env, struct x86_decode *decode) { emul_ops->simulate_wrmsr(env_cpu(env)); env->eip += decode->len; + return 0; } /* * flag: * 0 - bt, 1 - btc, 2 - bts, 3 - btr */ -static void do_bt(CPUX86State *env, struct x86_decode *decode, int flag) +static bool do_bt(CPUX86State *env, struct x86_decode *decode, int flag) { int32_t displacement; uint8_t index; @@ -696,7 +817,9 @@ static void do_bt(CPUX86State *env, struct x86_decode *decode, int flag) VM_PANIC_ON(decode->rex.rex); - fetch_operands(env, decode, 2, false, true, false); + if (fetch_operands(env, decode, 2, false, true, false)) { + return 1; + } index = decode->op[1].val & mask; if (decode->op[0].type != X86_VAR_REG) { @@ -710,14 +833,16 @@ static void do_bt(CPUX86State *env, struct x86_decode *decode, int flag) VM_PANIC("bt 64bit\n"); } } - decode->op[0].val = read_val_ext(env, &decode->op[0], - decode->operand_size); + if (read_val_ext(env, &decode->op[0], + decode->operand_size, &decode->op[0].val)) { + return 1; + } cf = (decode->op[0].val >> index) & 0x01; switch (flag) { case 0: set_CF(env, cf); - return; + return 0; case 1: decode->op[0].val ^= (1u << index); break; @@ -728,41 +853,58 @@ static void do_bt(CPUX86State *env, struct x86_decode *decode, int flag) decode->op[0].val &= ~(1u << index); break; } - write_val_ext(env, &decode->op[0], decode->op[0].val, - decode->operand_size); + if (write_val_ext(env, &decode->op[0], decode->op[0].val, + decode->operand_size)) { + return 1; + } set_CF(env, cf); + return 0; } -static void exec_bt(CPUX86State *env, struct x86_decode *decode) +static bool exec_bt(CPUX86State *env, struct x86_decode *decode) { - do_bt(env, decode, 0); + if (do_bt(env, decode, 0)) { + return 1; + } env->eip += decode->len; + return 0; } -static void exec_btc(CPUX86State *env, struct x86_decode *decode) +static bool exec_btc(CPUX86State *env, struct x86_decode *decode) { - do_bt(env, decode, 1); + if (do_bt(env, decode, 1)) { + return 1; + } env->eip += decode->len; + return 0; } -static void exec_btr(CPUX86State *env, struct x86_decode *decode) +static bool exec_btr(CPUX86State *env, struct x86_decode *decode) { - do_bt(env, decode, 3); + if (do_bt(env, decode, 3)) { + return 1; + } env->eip += decode->len; + return 0; } -static void exec_bts(CPUX86State *env, struct x86_decode *decode) +static bool exec_bts(CPUX86State *env, struct x86_decode *decode) { - do_bt(env, decode, 2); + if (do_bt(env, decode, 2)) { + return 1; + } env->eip += decode->len; + return 0; } -void exec_shl(CPUX86State *env, struct x86_decode *decode) +bool exec_shl(CPUX86State *env, struct x86_decode *decode) { uint8_t count; int of = 0, cf = 0; - fetch_operands(env, decode, 2, true, true, false); + if (fetch_operands(env, decode, 2, true, true, false)) { + return 1; + } count = decode->op[1].val; count &= 0x1f; /* count is masked to 5 bits*/ @@ -819,12 +961,14 @@ void exec_shl(CPUX86State *env, struct x86_decode *decode) exit: /* lflags_to_rflags(env); */ env->eip += decode->len; + return 0; } -void exec_movsx(CPUX86State *env, struct x86_decode *decode) +bool exec_movsx(CPUX86State *env, struct x86_decode *decode) { int src_op_size; int op_size = decode->operand_size; + target_ulong val; fetch_operands(env, decode, 2, false, false, false); @@ -836,15 +980,18 @@ void exec_movsx(CPUX86State *env, struct x86_decode *decode) decode->operand_size = src_op_size; calc_modrm_operand(env, decode, &decode->op[1]); - decode->op[1].val = sign(read_val_ext(env, &decode->op[1], src_op_size), - src_op_size); + if (read_val_ext(env, &decode->op[1], src_op_size, &val)) { + return 1; + } + decode->op[1].val = sign(val, src_op_size); write_val_ext(env, &decode->op[0], decode->op[1].val, op_size); env->eip += decode->len; + return 0; } -void exec_ror(CPUX86State *env, struct x86_decode *decode) +bool exec_ror(CPUX86State *env, struct x86_decode *decode) { uint8_t count; @@ -920,9 +1067,10 @@ void exec_ror(CPUX86State *env, struct x86_decode *decode) } } env->eip += decode->len; + return 0; } -void exec_rol(CPUX86State *env, struct x86_decode *decode) +bool exec_rol(CPUX86State *env, struct x86_decode *decode) { uint8_t count; @@ -1001,10 +1149,11 @@ void exec_rol(CPUX86State *env, struct x86_decode *decode) } } env->eip += decode->len; + return 0; } -void exec_rcl(CPUX86State *env, struct x86_decode *decode) +bool exec_rcl(CPUX86State *env, struct x86_decode *decode) { uint8_t count; int of = 0, cf = 0; @@ -1087,9 +1236,10 @@ void exec_rcl(CPUX86State *env, struct x86_decode *decode) } } env->eip += decode->len; + return 0; } -void exec_rcr(CPUX86State *env, struct x86_decode *decode) +bool exec_rcr(CPUX86State *env, struct x86_decode *decode) { uint8_t count; int of = 0, cf = 0; @@ -1162,9 +1312,10 @@ void exec_rcr(CPUX86State *env, struct x86_decode *decode) } } env->eip += decode->len; + return 0; } -static void exec_xchg(CPUX86State *env, struct x86_decode *decode) +static bool exec_xchg(CPUX86State *env, struct x86_decode *decode) { fetch_operands(env, decode, 2, true, true, false); @@ -1174,20 +1325,22 @@ static void exec_xchg(CPUX86State *env, struct x86_decode *decode) decode->operand_size); env->eip += decode->len; + return 0; } -static void exec_xadd(CPUX86State *env, struct x86_decode *decode) +static bool exec_xadd(CPUX86State *env, struct x86_decode *decode) { EXEC_2OP_FLAGS_CMD(env, decode, +, SET_FLAGS_OSZAPC_ADD, true); write_val_ext(env, &decode->op[1], decode->op[0].val, decode->operand_size); env->eip += decode->len; + return 0; } static struct cmd_handler { enum x86_decode_cmd cmd; - void (*handler)(CPUX86State *env, struct x86_decode *ins); + bool (*handler)(CPUX86State *env, struct x86_decode *ins); } handlers[] = { {X86_DECODE_CMD_INVL, NULL,}, {X86_DECODE_CMD_MOV, exec_mov}, diff --git a/target/i386/emulate/x86_emu.h b/target/i386/emulate/x86_emu.h index 05686b162f64..0f284b0c3d15 100644 --- a/target/i386/emulate/x86_emu.h +++ b/target/i386/emulate/x86_emu.h @@ -21,13 +21,11 @@ #include "x86.h" #include "x86_decode.h" +#include "x86_mmu.h" #include "cpu.h" struct x86_emul_ops { - void (*fetch_instruction)(CPUState *cpu, void *data, target_ulong addr, - int bytes); - void (*read_mem)(CPUState *cpu, void *data, target_ulong addr, int bytes); - void (*write_mem)(CPUState *cpu, void *data, target_ulong addr, int bytes); + MMUTranslateResult (*mmu_gva_to_gpa) (CPUState *cpu, target_ulong gva, uint64_t *gpa, MMUTranslateFlags flags); void (*read_segment_descriptor)(CPUState *cpu, struct x86_segment_descriptor *desc, enum X86Seg seg); void (*handle_io)(CPUState *cpu, uint16_t port, void *data, int direction, @@ -46,15 +44,15 @@ target_ulong read_reg(CPUX86State *env, int reg, int size); void write_reg(CPUX86State *env, int reg, target_ulong val, int size); target_ulong read_val_from_reg(void *reg_ptr, int size); void write_val_to_reg(void *reg_ptr, target_ulong val, int size); -void write_val_ext(CPUX86State *env, struct x86_decode_op *decode, target_ulong val, int size); +bool write_val_ext(CPUX86State *env, struct x86_decode_op *decode, target_ulong val, int size); uint8_t *read_mmio(CPUX86State *env, target_ulong ptr, int bytes); -target_ulong read_val_ext(CPUX86State *env, struct x86_decode_op *decode, int size); +bool read_val_ext(CPUX86State *env, struct x86_decode_op *decode, int size, target_ulong* val); -void exec_movzx(CPUX86State *env, struct x86_decode *decode); -void exec_shl(CPUX86State *env, struct x86_decode *decode); -void exec_movsx(CPUX86State *env, struct x86_decode *decode); -void exec_ror(CPUX86State *env, struct x86_decode *decode); -void exec_rol(CPUX86State *env, struct x86_decode *decode); -void exec_rcl(CPUX86State *env, struct x86_decode *decode); -void exec_rcr(CPUX86State *env, struct x86_decode *decode); +bool exec_movzx(CPUX86State *env, struct x86_decode *decode); +bool exec_shl(CPUX86State *env, struct x86_decode *decode); +bool exec_movsx(CPUX86State *env, struct x86_decode *decode); +bool exec_ror(CPUX86State *env, struct x86_decode *decode); +bool exec_rol(CPUX86State *env, struct x86_decode *decode); +bool exec_rcl(CPUX86State *env, struct x86_decode *decode); +bool exec_rcr(CPUX86State *env, struct x86_decode *decode); #endif diff --git a/target/i386/emulate/x86_flags.c b/target/i386/emulate/x86_flags.c index 6592193b5e00..3c4270a14c1c 100644 --- a/target/i386/emulate/x86_flags.c +++ b/target/i386/emulate/x86_flags.c @@ -82,6 +82,10 @@ SET_FLAGS_OSZAPC_SIZE(16, carries, result) #define SET_FLAGS_OSZAPC_32(carries, result) \ SET_FLAGS_OSZAPC_SIZE(32, carries, result) +#ifdef TARGET_X86_64 +#define SET_FLAGS_OSZAPC_64(carries, result) \ + SET_FLAGS_OSZAPC_SIZE(64, carries, result) +#endif /* ******************* */ /* OSZAP */ @@ -107,6 +111,10 @@ SET_FLAGS_OSZAP_SIZE(16, carries, result) #define SET_FLAGS_OSZAP_32(carries, result) \ SET_FLAGS_OSZAP_SIZE(32, carries, result) +#ifdef TARGET_X86_64 +#define SET_FLAGS_OSZAP_64(carries, result) \ + SET_FLAGS_OSZAP_SIZE(64, carries, result) +#endif void SET_FLAGS_OxxxxC(CPUX86State *env, bool new_of, bool new_cf) { @@ -115,6 +123,14 @@ void SET_FLAGS_OxxxxC(CPUX86State *env, bool new_of, bool new_cf) env->cc_src ^= ((target_ulong)new_of << LF_BIT_PO); } +#ifdef TARGET_X86_64 +void SET_FLAGS_OSZAPC_SUB64(CPUX86State *env, uint64_t v1, uint64_t v2, + uint64_t diff) +{ + SET_FLAGS_OSZAPC_64(SUB_COUT_VEC(v1, v2, diff), diff); +} +#endif + void SET_FLAGS_OSZAPC_SUB32(CPUX86State *env, uint32_t v1, uint32_t v2, uint32_t diff) { @@ -133,6 +149,14 @@ void SET_FLAGS_OSZAPC_SUB8(CPUX86State *env, uint8_t v1, uint8_t v2, SET_FLAGS_OSZAPC_8(SUB_COUT_VEC(v1, v2, diff), diff); } +#ifdef TARGET_X86_64 +void SET_FLAGS_OSZAPC_ADD64(CPUX86State *env, uint64_t v1, uint64_t v2, + uint64_t diff) +{ + SET_FLAGS_OSZAPC_64(ADD_COUT_VEC(v1, v2, diff), diff); +} +#endif + void SET_FLAGS_OSZAPC_ADD32(CPUX86State *env, uint32_t v1, uint32_t v2, uint32_t diff) { @@ -151,6 +175,14 @@ void SET_FLAGS_OSZAPC_ADD8(CPUX86State *env, uint8_t v1, uint8_t v2, SET_FLAGS_OSZAPC_8(ADD_COUT_VEC(v1, v2, diff), diff); } +#ifdef TARGET_X86_64 +void SET_FLAGS_OSZAP_SUB64(CPUX86State *env, uint64_t v1, uint64_t v2, + uint64_t diff) +{ + SET_FLAGS_OSZAP_64(SUB_COUT_VEC(v1, v2, diff), diff); +} +#endif + void SET_FLAGS_OSZAP_SUB32(CPUX86State *env, uint32_t v1, uint32_t v2, uint32_t diff) { @@ -169,6 +201,14 @@ void SET_FLAGS_OSZAP_SUB8(CPUX86State *env, uint8_t v1, uint8_t v2, SET_FLAGS_OSZAP_8(SUB_COUT_VEC(v1, v2, diff), diff); } +#ifdef TARGET_X86_64 +void SET_FLAGS_OSZAP_ADD64(CPUX86State *env, uint64_t v1, uint64_t v2, + uint64_t diff) +{ + SET_FLAGS_OSZAP_64(ADD_COUT_VEC(v1, v2, diff), diff); +} +#endif + void SET_FLAGS_OSZAP_ADD32(CPUX86State *env, uint32_t v1, uint32_t v2, uint32_t diff) { @@ -187,6 +227,13 @@ void SET_FLAGS_OSZAP_ADD8(CPUX86State *env, uint8_t v1, uint8_t v2, SET_FLAGS_OSZAP_8(ADD_COUT_VEC(v1, v2, diff), diff); } +#ifdef TARGET_X86_64 +void SET_FLAGS_OSZAPC_LOGIC64(CPUX86State *env, uint64_t v1, uint64_t v2, + uint64_t diff) +{ + SET_FLAGS_OSZAPC_64(0, diff); +} +#endif void SET_FLAGS_OSZAPC_LOGIC32(CPUX86State *env, uint32_t v1, uint32_t v2, uint32_t diff) diff --git a/target/i386/emulate/x86_flags.h b/target/i386/emulate/x86_flags.h index a395c837a0e5..7ffbbe5c1229 100644 --- a/target/i386/emulate/x86_flags.h +++ b/target/i386/emulate/x86_flags.h @@ -33,6 +33,10 @@ void set_CF(CPUX86State *env, bool val); void SET_FLAGS_OxxxxC(CPUX86State *env, bool new_of, bool new_cf); +#ifdef TARGET_X86_64 +void SET_FLAGS_OSZAPC_SUB64(CPUX86State *env, uint64_t v1, uint64_t v2, + uint64_t diff); +#endif void SET_FLAGS_OSZAPC_SUB32(CPUX86State *env, uint32_t v1, uint32_t v2, uint32_t diff); void SET_FLAGS_OSZAPC_SUB16(CPUX86State *env, uint16_t v1, uint16_t v2, @@ -40,6 +44,10 @@ void SET_FLAGS_OSZAPC_SUB16(CPUX86State *env, uint16_t v1, uint16_t v2, void SET_FLAGS_OSZAPC_SUB8(CPUX86State *env, uint8_t v1, uint8_t v2, uint8_t diff); +#ifdef TARGET_X86_64 +void SET_FLAGS_OSZAPC_ADD64(CPUX86State *env, uint64_t v1, uint64_t v2, + uint64_t diff); +#endif void SET_FLAGS_OSZAPC_ADD32(CPUX86State *env, uint32_t v1, uint32_t v2, uint32_t diff); void SET_FLAGS_OSZAPC_ADD16(CPUX86State *env, uint16_t v1, uint16_t v2, @@ -47,6 +55,10 @@ void SET_FLAGS_OSZAPC_ADD16(CPUX86State *env, uint16_t v1, uint16_t v2, void SET_FLAGS_OSZAPC_ADD8(CPUX86State *env, uint8_t v1, uint8_t v2, uint8_t diff); +#ifdef TARGET_X86_64 +void SET_FLAGS_OSZAP_SUB64(CPUX86State *env, uint64_t v1, uint64_t v2, + uint64_t diff); +#endif void SET_FLAGS_OSZAP_SUB32(CPUX86State *env, uint32_t v1, uint32_t v2, uint32_t diff); void SET_FLAGS_OSZAP_SUB16(CPUX86State *env, uint16_t v1, uint16_t v2, @@ -54,6 +66,10 @@ void SET_FLAGS_OSZAP_SUB16(CPUX86State *env, uint16_t v1, uint16_t v2, void SET_FLAGS_OSZAP_SUB8(CPUX86State *env, uint8_t v1, uint8_t v2, uint8_t diff); +#ifdef TARGET_X86_64 +void SET_FLAGS_OSZAP_ADD64(CPUX86State *env, uint64_t v1, uint64_t v2, + uint64_t diff); +#endif void SET_FLAGS_OSZAP_ADD32(CPUX86State *env, uint32_t v1, uint32_t v2, uint32_t diff); void SET_FLAGS_OSZAP_ADD16(CPUX86State *env, uint16_t v1, uint16_t v2, @@ -61,6 +77,10 @@ void SET_FLAGS_OSZAP_ADD16(CPUX86State *env, uint16_t v1, uint16_t v2, void SET_FLAGS_OSZAP_ADD8(CPUX86State *env, uint8_t v1, uint8_t v2, uint8_t diff); +#ifdef TARGET_X86_64 +void SET_FLAGS_OSZAPC_LOGIC64(CPUX86State *env, uint64_t v1, uint64_t v2, + uint64_t diff); +#endif void SET_FLAGS_OSZAPC_LOGIC32(CPUX86State *env, uint32_t v1, uint32_t v2, uint32_t diff); void SET_FLAGS_OSZAPC_LOGIC16(CPUX86State *env, uint16_t v1, uint16_t v2, diff --git a/target/i386/mshv/x86.c b/target/i386/emulate/x86_helpers.c similarity index 95% rename from target/i386/mshv/x86.c rename to target/i386/emulate/x86_helpers.c index 0700cc05efb3..024f9a2afcf1 100644 --- a/target/i386/mshv/x86.c +++ b/target/i386/emulate/x86_helpers.c @@ -13,6 +13,7 @@ #include "cpu.h" #include "emulate/x86_decode.h" #include "emulate/x86_emu.h" +#include "emulate/x86_mmu.h" #include "qemu/error-report.h" #include "system/mshv.h" @@ -176,7 +177,7 @@ bool x86_read_segment_descriptor(CPUState *cpu, } gva = base + sel.index * 8; - emul_ops->read_mem(cpu, desc, gva, sizeof(*desc)); + x86_read_mem_priv(cpu, desc, gva, sizeof(*desc)); return true; } @@ -200,7 +201,7 @@ bool x86_read_call_gate(CPUState *cpu, struct x86_call_gate *idt_desc, } gva = base + gate * 8; - emul_ops->read_mem(cpu, idt_desc, gva, sizeof(*idt_desc)); + x86_read_mem_priv(cpu, idt_desc, gva, sizeof(*idt_desc)); return true; } @@ -236,6 +237,14 @@ bool x86_is_long_mode(CPUState *cpu) return ((efer & lme_lma) == lme_lma); } +bool x86_is_la57(CPUState *cpu) +{ + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + uint64_t is_la57 = env->cr[4] & CR4_LA57_MASK; + return is_la57; +} + bool x86_is_long64_mode(CPUState *cpu) { error_report("unimplemented: is_long64_mode()"); diff --git a/target/i386/emulate/x86_mmu.c b/target/i386/emulate/x86_mmu.c new file mode 100644 index 000000000000..4e39bae025e7 --- /dev/null +++ b/target/i386/emulate/x86_mmu.c @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2016 Veertu Inc, + * Copyright (C) 2017 Google Inc, + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see . + */ + +#include "qemu/osdep.h" +#include "panic.h" +#include "cpu.h" +#include "system/address-spaces.h" +#include "system/memory.h" +#include "qemu/error-report.h" +#include "emulate/x86.h" +#include "emulate/x86_emu.h" +#include "emulate/x86_mmu.h" + +#define pte_present(pte) (pte & PT_PRESENT) +#define pte_write_access(pte) (pte & PT_WRITE) +#define pte_user_access(pte) (pte & PT_USER) +#define pte_exec_access(pte) (!(pte & PT_NX)) + +#define pte_large_page(pte) (pte & PT_PS) +#define pte_global_access(pte) (pte & PT_GLOBAL) + +#define mmu_validate_write(flags) (flags & MMU_TRANSLATE_VALIDATE_WRITE) +#define mmu_validate_execute(flags) (flags & MMU_TRANSLATE_VALIDATE_EXECUTE) +#define mmu_priv_checks_exempt(flags) (flags & MMU_TRANSLATE_PRIV_CHECKS_EXEMPT) + + +#define PAE_CR3_MASK (~0x1fllu) +#define LEGACY_CR3_MASK (0xffffffff) + +#define LEGACY_PTE_PAGE_MASK (0xffffffffllu << 12) +#define PAE_PTE_PAGE_MASK ((-1llu << 12) & ((1llu << 52) - 1)) +#define PAE_PTE_LARGE_PAGE_MASK ((-1llu << (21)) & ((1llu << 52) - 1)) +#define PAE_PTE_SUPER_PAGE_MASK ((-1llu << (30)) & ((1llu << 52) - 1)) + +static bool is_user(CPUState *cpu) +{ + return false; +} + + +struct gpt_translation { + target_ulong gva; + uint64_t gpa; + uint64_t pte[6]; +}; + +static int gpt_top_level(CPUState *cpu, bool pae) +{ + if (!pae) { + return 2; + } + if (x86_is_long_mode(cpu)) { + if (x86_is_la57(cpu)) { + return 5; + } + return 4; + } + + return 3; +} + +static inline int gpt_entry(target_ulong addr, int level, bool pae) +{ + int level_shift = pae ? 9 : 10; + return (addr >> (level_shift * (level - 1) + 12)) & ((1 << level_shift) - 1); +} + +static inline int pte_size(bool pae) +{ + return pae ? 8 : 4; +} + + +static bool get_pt_entry(CPUState *cpu, struct gpt_translation *pt, + int level, bool pae) +{ + int index; + uint64_t pte = 0; + uint64_t page_mask = pae ? PAE_PTE_PAGE_MASK : LEGACY_PTE_PAGE_MASK; + uint64_t gpa = pt->pte[level] & page_mask; + + if (level == 3 && !x86_is_long_mode(cpu)) { + gpa = pt->pte[level]; + } + + index = gpt_entry(pt->gva, level, pae); + address_space_read(&address_space_memory, gpa + index * pte_size(pae), + MEMTXATTRS_UNSPECIFIED, &pte, pte_size(pae)); + + pt->pte[level - 1] = pte; + + return true; +} + +/* test page table entry */ +static MMUTranslateResult test_pt_entry(CPUState *cpu, struct gpt_translation *pt, + int level, int *largeness, bool pae, MMUTranslateFlags flags) +{ + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + uint64_t pte = pt->pte[level]; + + if (!pte_present(pte)) { + return MMU_TRANSLATE_PAGE_NOT_MAPPED; + } + + if (pae && !x86_is_long_mode(cpu) && 2 == level) { + goto exit; + } + + if (level && pte_large_page(pte)) { + *largeness = level; + } + + uint32_t cr0 = env->cr[0]; + /* check protection */ + if (cr0 & CR0_WP_MASK) { + if (mmu_validate_write(flags) && !pte_write_access(pte)) { + return MMU_TRANSLATE_PRIV_VIOLATION; + } + } + + if (!mmu_priv_checks_exempt(flags)) { + if (is_user(cpu) && !pte_user_access(pte)) { + return MMU_TRANSLATE_PRIV_VIOLATION; + } + } + + if (pae && mmu_validate_execute(flags) && !pte_exec_access(pte)) { + return MMU_TRANSLATE_PRIV_VIOLATION; + } + +exit: + /* TODO: check reserved bits */ + return MMU_TRANSLATE_SUCCESS; +} + +static inline uint64_t pse_pte_to_page(uint64_t pte) +{ + return ((pte & 0x1fe000) << 19) | (pte & 0xffc00000); +} + +static inline uint64_t large_page_gpa(struct gpt_translation *pt, bool pae, + int largeness) +{ + VM_PANIC_ON(!pte_large_page(pt->pte[largeness])) + + /* 1Gib large page */ + if (pae && largeness == 2) { + return (pt->pte[2] & PAE_PTE_SUPER_PAGE_MASK) | (pt->gva & 0x3fffffff); + } + + VM_PANIC_ON(largeness != 1) + + /* 2Mb large page */ + if (pae) { + return (pt->pte[1] & PAE_PTE_LARGE_PAGE_MASK) | (pt->gva & 0x1fffff); + } + + /* 4Mb large page */ + return pse_pte_to_page(pt->pte[1]) | (pt->gva & 0x3fffff); +} + + + +static MMUTranslateResult walk_gpt(CPUState *cpu, target_ulong addr, MMUTranslateFlags flags, + struct gpt_translation *pt, bool pae) +{ + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + int top_level, level; + int largeness = 0; + target_ulong cr3 = env->cr[3]; + uint64_t page_mask = pae ? PAE_PTE_PAGE_MASK : LEGACY_PTE_PAGE_MASK; + MMUTranslateResult res; + + memset(pt, 0, sizeof(*pt)); + top_level = gpt_top_level(cpu, pae); + + pt->pte[top_level] = pae ? (cr3 & PAE_CR3_MASK) : (cr3 & LEGACY_CR3_MASK); + pt->gva = addr; + + for (level = top_level; level > 0; level--) { + get_pt_entry(cpu, pt, level, pae); + res = test_pt_entry(cpu, pt, level - 1, &largeness, pae, flags); + + if (res) { + return res; + } + + if (largeness) { + break; + } + } + + if (!largeness) { + pt->gpa = (pt->pte[0] & page_mask) | (pt->gva & 0xfff); + } else { + pt->gpa = large_page_gpa(pt, pae, largeness); + } + + return res; +} + + +MMUTranslateResult mmu_gva_to_gpa(CPUState *cpu, target_ulong gva, uint64_t *gpa, MMUTranslateFlags flags) +{ + if (emul_ops->mmu_gva_to_gpa) { + return emul_ops->mmu_gva_to_gpa(cpu, gva, gpa, flags); + } + + bool res; + struct gpt_translation pt; + + if (!x86_is_paging_mode(cpu)) { + *gpa = gva; + return MMU_TRANSLATE_SUCCESS; + } + + res = walk_gpt(cpu, gva, flags, &pt, x86_is_pae_enabled(cpu)); + if (res == MMU_TRANSLATE_SUCCESS) { + *gpa = pt.gpa; + } + + return res; +} + +static int translate_res_to_error_code(MMUTranslateResult res, bool is_write, bool is_user) +{ + int error_code = 0; + if (is_user) { + error_code |= PG_ERROR_U_MASK; + } + if (!(res & MMU_TRANSLATE_PAGE_NOT_MAPPED)) { + error_code |= PG_ERROR_P_MASK; + } + if (is_write && (res & MMU_TRANSLATE_PRIV_VIOLATION)) { + error_code |= PG_ERROR_W_MASK; + } + if (res & MMU_TRANSLATE_INVALID_PT_FLAGS) { + error_code |= PG_ERROR_RSVD_MASK; + } + return error_code; +} + +static MMUTranslateResult x86_write_mem_ex(CPUState *cpu, void *data, target_ulong gva, int bytes, bool priv_check_exempt) +{ + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + + MMUTranslateResult translate_res = MMU_TRANSLATE_SUCCESS; + MemTxResult mem_tx_res; + uint64_t gpa; + + while (bytes > 0) { + /* copy page */ + int copy = MIN(bytes, 0x1000 - (gva & 0xfff)); + + translate_res = mmu_gva_to_gpa(cpu, gva, &gpa, MMU_TRANSLATE_VALIDATE_WRITE); + if (translate_res) { + int error_code = translate_res_to_error_code(translate_res, true, is_user(cpu)); + env->cr[2] = gva; + x86_emul_raise_exception(env, EXCP0E_PAGE, error_code); + return translate_res; + } + + mem_tx_res = address_space_write(&address_space_memory, gpa, + MEMTXATTRS_UNSPECIFIED, data, copy); + + if (mem_tx_res == MEMTX_DECODE_ERROR) { + warn_report("write to unmapped mmio region gpa=0x%" PRIx64 " size=%i", gpa, bytes); + return MMU_TRANSLATE_GPA_UNMAPPED; + } else if (mem_tx_res == MEMTX_ACCESS_ERROR) { + return MMU_TRANSLATE_GPA_NO_WRITE_ACCESS; + } + + bytes -= copy; + gva += copy; + data += copy; + } + return translate_res; +} + +MMUTranslateResult x86_write_mem(CPUState *cpu, void *data, target_ulong gva, int bytes) +{ + return x86_write_mem_ex(cpu, data, gva, bytes, false); +} + +MMUTranslateResult x86_write_mem_priv(CPUState *cpu, void *data, target_ulong gva, int bytes) +{ + return x86_write_mem_ex(cpu, data, gva, bytes, true); +} + +static MMUTranslateResult x86_read_mem_ex(CPUState *cpu, void *data, target_ulong gva, int bytes, bool priv_check_exempt) +{ + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + + MMUTranslateResult translate_res = MMU_TRANSLATE_SUCCESS; + MemTxResult mem_tx_res; + uint64_t gpa; + + while (bytes > 0) { + /* copy page */ + int copy = MIN(bytes, 0x1000 - (gva & 0xfff)); + + translate_res = mmu_gva_to_gpa(cpu, gva, &gpa, 0); + if (translate_res) { + int error_code = translate_res_to_error_code(translate_res, false, is_user(cpu)); + env->cr[2] = gva; + x86_emul_raise_exception(env, EXCP0E_PAGE, error_code); + return translate_res; + } + mem_tx_res = address_space_read(&address_space_memory, gpa, MEMTXATTRS_UNSPECIFIED, + data, copy); + + if (mem_tx_res == MEMTX_DECODE_ERROR) { + warn_report("read from unmapped mmio region gpa=0x%" PRIx64 " size=%i", gpa, bytes); + return MMU_TRANSLATE_GPA_UNMAPPED; + } else if (mem_tx_res == MEMTX_ACCESS_ERROR) { + return MMU_TRANSLATE_GPA_NO_READ_ACCESS; + } + + bytes -= copy; + gva += copy; + data += copy; + } + return translate_res; +} + +MMUTranslateResult x86_read_mem(CPUState *cpu, void *data, target_ulong gva, int bytes) +{ + return x86_read_mem_ex(cpu, data, gva, bytes, false); +} + +MMUTranslateResult x86_read_mem_priv(CPUState *cpu, void *data, target_ulong gva, int bytes) +{ + return x86_read_mem_ex(cpu, data, gva, bytes, true); +} diff --git a/target/i386/hvf/x86_mmu.h b/target/i386/emulate/x86_mmu.h similarity index 51% rename from target/i386/hvf/x86_mmu.h rename to target/i386/emulate/x86_mmu.h index 9447ae072cd3..190bd272a230 100644 --- a/target/i386/hvf/x86_mmu.h +++ b/target/i386/emulate/x86_mmu.h @@ -30,15 +30,30 @@ #define PT_GLOBAL (1 << 8) #define PT_NX (1llu << 63) -/* error codes */ -#define MMU_PAGE_PT (1 << 0) -#define MMU_PAGE_WT (1 << 1) -#define MMU_PAGE_US (1 << 2) -#define MMU_PAGE_NX (1 << 3) +typedef enum MMUTranslateFlags { + MMU_TRANSLATE_VALIDATE_WRITE = BIT(1), + MMU_TRANSLATE_VALIDATE_EXECUTE = BIT(2), + MMU_TRANSLATE_PRIV_CHECKS_EXEMPT = BIT(3) +} MMUTranslateFlags; -bool mmu_gva_to_gpa(CPUState *cpu, target_ulong gva, uint64_t *gpa); +typedef enum MMUTranslateResult { + MMU_TRANSLATE_SUCCESS = 0, + MMU_TRANSLATE_PAGE_NOT_MAPPED = 1, + MMU_TRANSLATE_PRIV_VIOLATION = 2, + MMU_TRANSLATE_INVALID_PT_FLAGS = 3, + MMU_TRANSLATE_GPA_UNMAPPED = 4, + MMU_TRANSLATE_GPA_NO_READ_ACCESS = 5, + MMU_TRANSLATE_GPA_NO_WRITE_ACCESS = 6 +} MMUTranslateResult; + +MMUTranslateResult mmu_gva_to_gpa(CPUState *cpu, target_ulong gva, uint64_t *gpa, MMUTranslateFlags flags); + +/* Thin wrappers x86_write_mem_ex/x86_read_mem_ex for code readability */ +MMUTranslateResult x86_write_mem(CPUState *cpu, void *data, target_ulong gva, int bytes); +MMUTranslateResult x86_read_mem(CPUState *cpu, void *data, target_ulong gva, int bytes); + +MMUTranslateResult x86_write_mem_priv(CPUState *cpu, void *data, target_ulong gva, int bytes); +MMUTranslateResult x86_read_mem_priv(CPUState *cpu, void *data, target_ulong gva, int bytes); -void vmx_write_mem(CPUState *cpu, target_ulong gva, void *data, int bytes); -void vmx_read_mem(CPUState *cpu, void *data, target_ulong gva, int bytes); #endif /* X86_MMU_H */ diff --git a/target/i386/gdbstub.c b/target/i386/gdbstub.c index be41601cffc0..f1ce90a046ed 100644 --- a/target/i386/gdbstub.c +++ b/target/i386/gdbstub.c @@ -79,15 +79,9 @@ static const int gpr_map32[8] = { 0, 1, 2, 3, 4, 5, 6, 7 }; #define IDX_CTL_CR8_REG (IDX_CTL_REGS + 4) #define IDX_CTL_EFER_REG (IDX_CTL_REGS + 5) -#ifdef TARGET_X86_64 -#define GDB_FORCE_64 1 -#else -#define GDB_FORCE_64 0 -#endif - static int gdb_read_reg_cs64(uint32_t hflags, GByteArray *buf, target_ulong val) { - if ((hflags & HF_CS64_MASK) || GDB_FORCE_64) { + if ((hflags & HF_CS64_MASK) || TARGET_LONG_BITS == 64) { return gdb_get_reg64(buf, val); } return gdb_get_reg32(buf, val); @@ -135,7 +129,7 @@ int x86_cpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n) return gdb_get_reg64(mem_buf, env->regs[gpr_map[n]] & 0xffffffffUL); } else { - return gdb_get_regl(mem_buf, 0); + return gdb_get_reg64(mem_buf, 0); } } else { return gdb_get_reg32(mem_buf, env->regs[gpr_map32[n]]); @@ -289,9 +283,9 @@ int x86_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) if (n < CPU_NB_REGS) { if (TARGET_LONG_BITS == 64) { if (env->hflags & HF_CS64_MASK) { - env->regs[gpr_map[n]] = ldtul_p(mem_buf); + env->regs[gpr_map[n]] = ldq_p(mem_buf); } else if (n < CPU_NB_REGS32) { - env->regs[gpr_map[n]] = ldtul_p(mem_buf) & 0xffffffffUL; + env->regs[gpr_map[n]] = ldq_p(mem_buf) & 0xffffffffUL; } return sizeof(target_ulong); } else if (n < CPU_NB_REGS32) { @@ -455,8 +449,10 @@ static int i386_cpu_gdb_get_egprs(CPUState *cs, GByteArray *mem_buf, int n) /* EGPRs can be only directly accessible in 64-bit mode. */ if (env->hflags & HF_CS64_MASK) { return gdb_get_reg64(mem_buf, env->regs[gpr_map[n + CPU_NB_REGS]]); + } else if (TARGET_LONG_BITS == 64) { + return gdb_get_reg64(mem_buf, 0); } else { - return gdb_get_regl(mem_buf, 0); + return gdb_get_reg32(mem_buf, 0); } } @@ -465,6 +461,7 @@ static int i386_cpu_gdb_get_egprs(CPUState *cs, GByteArray *mem_buf, int n) static int i386_cpu_gdb_set_egprs(CPUState *cs, uint8_t *mem_buf, int n) { + const unsigned regsz = target_long_bits() / 8; CPUX86State *env = &X86_CPU(cs)->env; if (n >= 0 && n < EGPR_NUM) { @@ -473,7 +470,7 @@ static int i386_cpu_gdb_set_egprs(CPUState *cs, uint8_t *mem_buf, int n) * XCR0[APX_F] (at least for modification in gdbstub) to be enabled. */ if (env->hflags & HF_CS64_MASK && env->xcr0 & XSTATE_APX_MASK) { - env->regs[gpr_map[n + CPU_NB_REGS]] = ldtul_p(mem_buf); + env->regs[gpr_map[n + CPU_NB_REGS]] = ldn_p(mem_buf, regsz); /* * Per SDM Vol 1, "Processor Tracking of XSAVE-Managed State", @@ -492,7 +489,7 @@ static int i386_cpu_gdb_set_egprs(CPUState *cs, uint8_t *mem_buf, int n) env->xstate_bv |= XSTATE_APX_MASK; } } - return sizeof(target_ulong); + return regsz; } return 0; } diff --git a/target/i386/hvf/hvf.c b/target/i386/hvf/hvf.c index ce54020f0036..c0d028b1473c 100644 --- a/target/i386/hvf/hvf.c +++ b/target/i386/hvf/hvf.c @@ -62,7 +62,7 @@ #include "emulate/x86.h" #include "x86_descr.h" #include "emulate/x86_flags.h" -#include "x86_mmu.h" +#include "emulate/x86_mmu.h" #include "emulate/x86_decode.h" #include "emulate/x86_emu.h" #include "x86_task.h" @@ -252,19 +252,7 @@ static void hvf_read_segment_descriptor(CPUState *s, struct x86_segment_descript vmx_segment_to_x86_descriptor(s, &vmx_segment, desc); } -static void hvf_read_mem(CPUState *cpu, void *data, target_ulong gva, int bytes) -{ - vmx_read_mem(cpu, data, gva, bytes); -} - -static void hvf_write_mem(CPUState *cpu, void *data, target_ulong gva, int bytes) -{ - vmx_write_mem(cpu, gva, data, bytes); -} - static const struct x86_emul_ops hvf_x86_emul_ops = { - .read_mem = hvf_read_mem, - .write_mem = hvf_write_mem, .read_segment_descriptor = hvf_read_segment_descriptor, .handle_io = hvf_handle_io, .simulate_rdmsr = hvf_simulate_rdmsr, @@ -482,6 +470,26 @@ static void hvf_cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, } } +static void hvf_load_crs(CPUState *cs) +{ + X86CPU *x86_cpu = X86_CPU(cs); + CPUX86State *env = &x86_cpu->env; + + env->cr[0] = rvmcs(cs->accel->fd, VMCS_GUEST_CR0); + env->cr[3] = rvmcs(cs->accel->fd, VMCS_GUEST_CR3); + env->cr[2] = rreg(cs->accel->fd, HV_X86_CR2); +} + +static void hvf_save_crs(CPUState *cs) +{ + X86CPU *x86_cpu = X86_CPU(cs); + CPUX86State *env = &x86_cpu->env; + + wvmcs(cs->accel->fd, VMCS_GUEST_CR0, env->cr[0]); + wvmcs(cs->accel->fd, VMCS_GUEST_CR3, env->cr[3]); + wreg(cs->accel->fd, HV_X86_CR2, env->cr[2]); +} + void hvf_load_regs(CPUState *cs) { X86CPU *cpu = X86_CPU(cs); @@ -794,9 +802,11 @@ static int hvf_handle_vmexit(CPUState *cpu) struct x86_decode decode; hvf_load_regs(cpu); + hvf_load_crs(cpu); decode_instruction(env, &decode); exec_instruction(env, &decode); hvf_store_regs(cpu); + hvf_save_crs(cpu); break; } break; @@ -835,10 +845,12 @@ static int hvf_handle_vmexit(CPUState *cpu) } hvf_load_regs(cpu); + hvf_load_crs(cpu); decode_instruction(env, &decode); assert(ins_len == decode.len); exec_instruction(env, &decode); hvf_store_regs(cpu); + hvf_save_crs(cpu); break; } @@ -940,9 +952,11 @@ static int hvf_handle_vmexit(CPUState *cpu) struct x86_decode decode; hvf_load_regs(cpu); + hvf_load_crs(cpu); decode_instruction(env, &decode); exec_instruction(env, &decode); hvf_store_regs(cpu); + hvf_save_crs(cpu); break; } case EXIT_REASON_TPR: { diff --git a/target/i386/hvf/meson.build b/target/i386/hvf/meson.build index 519d190f0e6b..22bf886978f3 100644 --- a/target/i386/hvf/meson.build +++ b/target/i386/hvf/meson.build @@ -3,7 +3,6 @@ i386_system_ss.add(when: [hvf, 'CONFIG_HVF'], if_true: files( 'x86.c', 'x86_cpuid.c', 'x86_descr.c', - 'x86_mmu.c', 'x86_task.c', 'x86hvf.c', 'hvf-cpu.c', diff --git a/target/i386/hvf/x86.c b/target/i386/hvf/x86.c index 5c75ec9a0079..7fe710aca3b3 100644 --- a/target/i386/hvf/x86.c +++ b/target/i386/hvf/x86.c @@ -23,7 +23,7 @@ #include "emulate/x86_emu.h" #include "vmcs.h" #include "vmx.h" -#include "x86_mmu.h" +#include "emulate/x86_mmu.h" #include "x86_descr.h" /* static uint32_t x86_segment_access_rights(struct x86_segment_descriptor *var) @@ -72,7 +72,7 @@ bool x86_read_segment_descriptor(CPUState *cpu, return false; } - vmx_read_mem(cpu, desc, base + sel.index * 8, sizeof(*desc)); + x86_read_mem_priv(cpu, desc, base + sel.index * 8, sizeof(*desc)); return true; } @@ -95,7 +95,7 @@ bool x86_write_segment_descriptor(CPUState *cpu, printf("%s: gdt limit\n", __func__); return false; } - vmx_write_mem(cpu, base + sel.index * 8, desc, sizeof(*desc)); + x86_write_mem_priv(cpu, desc, base + sel.index * 8, sizeof(*desc)); return true; } @@ -111,7 +111,7 @@ bool x86_read_call_gate(CPUState *cpu, struct x86_call_gate *idt_desc, return false; } - vmx_read_mem(cpu, idt_desc, base + gate * 8, sizeof(*idt_desc)); + x86_read_mem_priv(cpu, idt_desc, base + gate * 8, sizeof(*idt_desc)); return true; } @@ -138,6 +138,11 @@ bool x86_is_long_mode(CPUState *cpu) return rvmcs(cpu->accel->fd, VMCS_GUEST_IA32_EFER) & MSR_EFER_LMA; } +bool x86_is_la57(CPUState *cpu) +{ + return false; +} + bool x86_is_long64_mode(CPUState *cpu) { struct vmx_segment desc; diff --git a/target/i386/hvf/x86_mmu.c b/target/i386/hvf/x86_mmu.c deleted file mode 100644 index afc5c17d5d5c..000000000000 --- a/target/i386/hvf/x86_mmu.c +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) 2016 Veertu Inc, - * Copyright (C) 2017 Google Inc, - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this program; if not, see . - */ - -#include "qemu/osdep.h" -#include "panic.h" -#include "cpu.h" -#include "emulate/x86.h" -#include "x86_mmu.h" -#include "vmcs.h" -#include "vmx.h" - -#define pte_present(pte) (pte & PT_PRESENT) -#define pte_write_access(pte) (pte & PT_WRITE) -#define pte_user_access(pte) (pte & PT_USER) -#define pte_exec_access(pte) (!(pte & PT_NX)) - -#define pte_large_page(pte) (pte & PT_PS) -#define pte_global_access(pte) (pte & PT_GLOBAL) - -#define PAE_CR3_MASK (~0x1fllu) -#define LEGACY_CR3_MASK (0xffffffff) - -#define LEGACY_PTE_PAGE_MASK (0xffffffffllu << 12) -#define PAE_PTE_PAGE_MASK ((-1llu << 12) & ((1llu << 52) - 1)) -#define PAE_PTE_LARGE_PAGE_MASK ((-1llu << (21)) & ((1llu << 52) - 1)) -#define PAE_PTE_SUPER_PAGE_MASK ((-1llu << (30)) & ((1llu << 52) - 1)) - -struct gpt_translation { - target_ulong gva; - uint64_t gpa; - int err_code; - uint64_t pte[5]; - bool write_access; - bool user_access; - bool exec_access; -}; - -static int gpt_top_level(CPUState *cpu, bool pae) -{ - if (!pae) { - return 2; - } - if (x86_is_long_mode(cpu)) { - return 4; - } - - return 3; -} - -static inline int gpt_entry(target_ulong addr, int level, bool pae) -{ - int level_shift = pae ? 9 : 10; - return (addr >> (level_shift * (level - 1) + 12)) & ((1 << level_shift) - 1); -} - -static inline int pte_size(bool pae) -{ - return pae ? 8 : 4; -} - - -static bool get_pt_entry(CPUState *cpu, struct gpt_translation *pt, - int level, bool pae) -{ - int index; - uint64_t pte = 0; - uint64_t page_mask = pae ? PAE_PTE_PAGE_MASK : LEGACY_PTE_PAGE_MASK; - uint64_t gpa = pt->pte[level] & page_mask; - - if (level == 3 && !x86_is_long_mode(cpu)) { - gpa = pt->pte[level]; - } - - index = gpt_entry(pt->gva, level, pae); - address_space_read(&address_space_memory, gpa + index * pte_size(pae), - MEMTXATTRS_UNSPECIFIED, &pte, pte_size(pae)); - - pt->pte[level - 1] = pte; - - return true; -} - -/* test page table entry */ -static bool test_pt_entry(CPUState *cpu, struct gpt_translation *pt, - int level, int *largeness, bool pae) -{ - uint64_t pte = pt->pte[level]; - - if (pt->write_access) { - pt->err_code |= MMU_PAGE_WT; - } - if (pt->user_access) { - pt->err_code |= MMU_PAGE_US; - } - if (pt->exec_access) { - pt->err_code |= MMU_PAGE_NX; - } - - if (!pte_present(pte)) { - return false; - } - - if (pae && !x86_is_long_mode(cpu) && 2 == level) { - goto exit; - } - - if (level && pte_large_page(pte)) { - pt->err_code |= MMU_PAGE_PT; - *largeness = level; - } - if (!level) { - pt->err_code |= MMU_PAGE_PT; - } - - uint32_t cr0 = rvmcs(cpu->accel->fd, VMCS_GUEST_CR0); - /* check protection */ - if (cr0 & CR0_WP_MASK) { - if (pt->write_access && !pte_write_access(pte)) { - return false; - } - } - - if (pt->user_access && !pte_user_access(pte)) { - return false; - } - - if (pae && pt->exec_access && !pte_exec_access(pte)) { - return false; - } - -exit: - /* TODO: check reserved bits */ - return true; -} - -static inline uint64_t pse_pte_to_page(uint64_t pte) -{ - return ((pte & 0x1fe000) << 19) | (pte & 0xffc00000); -} - -static inline uint64_t large_page_gpa(struct gpt_translation *pt, bool pae, - int largeness) -{ - VM_PANIC_ON(!pte_large_page(pt->pte[largeness])) - - /* 1Gib large page */ - if (pae && largeness == 2) { - return (pt->pte[2] & PAE_PTE_SUPER_PAGE_MASK) | (pt->gva & 0x3fffffff); - } - - VM_PANIC_ON(largeness != 1) - - /* 2Mb large page */ - if (pae) { - return (pt->pte[1] & PAE_PTE_LARGE_PAGE_MASK) | (pt->gva & 0x1fffff); - } - - /* 4Mb large page */ - return pse_pte_to_page(pt->pte[1]) | (pt->gva & 0x3fffff); -} - - - -static bool walk_gpt(CPUState *cpu, target_ulong addr, int err_code, - struct gpt_translation *pt, bool pae) -{ - int top_level, level; - int largeness = 0; - target_ulong cr3 = rvmcs(cpu->accel->fd, VMCS_GUEST_CR3); - uint64_t page_mask = pae ? PAE_PTE_PAGE_MASK : LEGACY_PTE_PAGE_MASK; - - memset(pt, 0, sizeof(*pt)); - top_level = gpt_top_level(cpu, pae); - - pt->pte[top_level] = pae ? (cr3 & PAE_CR3_MASK) : (cr3 & LEGACY_CR3_MASK); - pt->gva = addr; - pt->user_access = (err_code & MMU_PAGE_US); - pt->write_access = (err_code & MMU_PAGE_WT); - pt->exec_access = (err_code & MMU_PAGE_NX); - - for (level = top_level; level > 0; level--) { - get_pt_entry(cpu, pt, level, pae); - - if (!test_pt_entry(cpu, pt, level - 1, &largeness, pae)) { - return false; - } - - if (largeness) { - break; - } - } - - if (!largeness) { - pt->gpa = (pt->pte[0] & page_mask) | (pt->gva & 0xfff); - } else { - pt->gpa = large_page_gpa(pt, pae, largeness); - } - - return true; -} - - -bool mmu_gva_to_gpa(CPUState *cpu, target_ulong gva, uint64_t *gpa) -{ - bool res; - struct gpt_translation pt; - int err_code = 0; - - if (!x86_is_paging_mode(cpu)) { - *gpa = gva; - return true; - } - - res = walk_gpt(cpu, gva, err_code, &pt, x86_is_pae_enabled(cpu)); - if (res) { - *gpa = pt.gpa; - return true; - } - - return false; -} - -void vmx_write_mem(CPUState *cpu, target_ulong gva, void *data, int bytes) -{ - uint64_t gpa; - - while (bytes > 0) { - /* copy page */ - int copy = MIN(bytes, 0x1000 - (gva & 0xfff)); - - if (!mmu_gva_to_gpa(cpu, gva, &gpa)) { - VM_PANIC_EX("%s: mmu_gva_to_gpa %llx failed\n", __func__, gva); - } else { - address_space_write(&address_space_memory, gpa, - MEMTXATTRS_UNSPECIFIED, data, copy); - } - - bytes -= copy; - gva += copy; - data += copy; - } -} - -void vmx_read_mem(CPUState *cpu, void *data, target_ulong gva, int bytes) -{ - uint64_t gpa; - - while (bytes > 0) { - /* copy page */ - int copy = MIN(bytes, 0x1000 - (gva & 0xfff)); - - if (!mmu_gva_to_gpa(cpu, gva, &gpa)) { - VM_PANIC_EX("%s: mmu_gva_to_gpa %llx failed\n", __func__, gva); - } - address_space_read(&address_space_memory, gpa, MEMTXATTRS_UNSPECIFIED, - data, copy); - - bytes -= copy; - gva += copy; - data += copy; - } -} diff --git a/target/i386/hvf/x86_task.c b/target/i386/hvf/x86_task.c index bdf8b51ae670..64e30e970d9a 100644 --- a/target/i386/hvf/x86_task.c +++ b/target/i386/hvf/x86_task.c @@ -16,7 +16,7 @@ #include "vmx.h" #include "emulate/x86.h" #include "x86_descr.h" -#include "x86_mmu.h" +#include "emulate/x86_mmu.h" #include "emulate/x86_decode.h" #include "emulate/x86_emu.h" #include "x86_task.h" @@ -93,16 +93,16 @@ static int task_switch_32(CPUState *cpu, x86_segment_selector tss_sel, x86_segme uint32_t eip_offset = offsetof(struct x86_tss_segment32, eip); uint32_t ldt_sel_offset = offsetof(struct x86_tss_segment32, ldt); - vmx_read_mem(cpu, &tss_seg, old_tss_base, sizeof(tss_seg)); + x86_read_mem_priv(cpu, &tss_seg, old_tss_base, sizeof(tss_seg)); save_state_to_tss32(cpu, &tss_seg); - vmx_write_mem(cpu, old_tss_base + eip_offset, &tss_seg.eip, ldt_sel_offset - eip_offset); - vmx_read_mem(cpu, &tss_seg, new_tss_base, sizeof(tss_seg)); + x86_write_mem_priv(cpu, &tss_seg.eip, old_tss_base + eip_offset, ldt_sel_offset - eip_offset); + x86_read_mem_priv(cpu, &tss_seg, new_tss_base, sizeof(tss_seg)); if (old_tss_sel.sel != 0xffff) { tss_seg.prev_tss = old_tss_sel.sel; - vmx_write_mem(cpu, new_tss_base, &tss_seg.prev_tss, sizeof(tss_seg.prev_tss)); + x86_write_mem_priv(cpu, &tss_seg.prev_tss, new_tss_base, sizeof(tss_seg.prev_tss)); } load_state_from_tss32(cpu, &tss_seg); return 0; diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c index 9f1a4d4cbb26..a29f757c168a 100644 --- a/target/i386/kvm/kvm.c +++ b/target/i386/kvm/kvm.c @@ -112,6 +112,11 @@ typedef struct { static void kvm_init_msrs(X86CPU *cpu); static int kvm_filter_msr(KVMState *s, uint32_t msr, QEMURDMSRHandler *rdmsr, QEMUWRMSRHandler *wrmsr); +static int unregister_smram_listener(NotifierWithReturn *notifier, + void *data, Error** errp); +NotifierWithReturn kvm_vmfd_change_notifier = { + .notify = unregister_smram_listener, +}; const KVMCapabilityInfo kvm_arch_required_capabilities[] = { KVM_CAP_INFO(SET_TSS_ADDR), @@ -1273,10 +1278,7 @@ static struct kvm_cpuid2 *get_supported_hv_cpuid_legacy(CPUState *cs) } if (has_msr_hv_synic) { - unsigned int cap = cpu->hyperv_synic_kvm_only ? - KVM_CAP_HYPERV_SYNIC : KVM_CAP_HYPERV_SYNIC2; - - if (kvm_check_extension(cs->kvm_state, cap) > 0) { + if (kvm_check_extension(cs->kvm_state, KVM_CAP_HYPERV_SYNIC2) > 0) { entry_feat->eax |= HV_SYNIC_AVAILABLE; } } @@ -1538,7 +1540,6 @@ bool kvm_hyperv_expand_features(X86CPU *cpu, Error **errp) /* Additional dependencies not covered by kvm_hyperv_properties[] */ if (hyperv_feat_enabled(cpu, HYPERV_FEAT_SYNIC) && - !cpu->hyperv_synic_kvm_only && !hyperv_feat_enabled(cpu, HYPERV_FEAT_VPINDEX)) { error_setg(errp, "Hyper-V %s requires Hyper-V %s", kvm_hyperv_properties[HYPERV_FEAT_SYNIC].desc, @@ -1603,8 +1604,7 @@ static int hyperv_fill_cpuids(CPUState *cs, c->eax |= HV_HYPERCALL_AVAILABLE; /* SynIC and Vmbus devices require messages/signals hypercalls */ - if (hyperv_feat_enabled(cpu, HYPERV_FEAT_SYNIC) && - !cpu->hyperv_synic_kvm_only) { + if (hyperv_feat_enabled(cpu, HYPERV_FEAT_SYNIC)) { c->ebx |= HV_POST_MESSAGES | HV_SIGNAL_EVENTS; } @@ -1747,16 +1747,14 @@ static int hyperv_init_vcpu(X86CPU *cpu) } if (hyperv_feat_enabled(cpu, HYPERV_FEAT_SYNIC)) { - uint32_t synic_cap = cpu->hyperv_synic_kvm_only ? - KVM_CAP_HYPERV_SYNIC : KVM_CAP_HYPERV_SYNIC2; - ret = kvm_vcpu_enable_cap(cs, synic_cap, 0); + ret = kvm_vcpu_enable_cap(cs, KVM_CAP_HYPERV_SYNIC2, 0); if (ret < 0) { error_report("failed to turn on HyperV SynIC in KVM: %s", strerror(-ret)); return ret; } - if (!cpu->hyperv_synic_kvm_only) { + if (!hyperv_is_synic_enabled()) { ret = hyperv_x86_synic_add(cpu); if (ret < 0) { error_report("failed to create HyperV SynIC: %s", @@ -2885,6 +2883,17 @@ static void register_smram_listener(Notifier *n, void *unused) } } +static int unregister_smram_listener(NotifierWithReturn *notifier, + void *data, Error** errp) +{ + if (!((VmfdChangeNotifier *)data)->pre) { + return 0; + } + + memory_listener_unregister(&smram_listener.listener); + return 0; +} + /* It should only be called in cpu's hotplug callback */ void kvm_smm_cpu_address_space_init(X86CPU *cpu) { @@ -3389,11 +3398,65 @@ static int kvm_vm_enable_energy_msrs(KVMState *s) return 0; } +int kvm_arch_on_vmfd_change(MachineState *ms, KVMState *s) +{ + int ret; + + ret = kvm_arch_init(ms, s); + if (ret < 0) { + return ret; + } + + if (object_dynamic_cast(OBJECT(ms), TYPE_X86_MACHINE)) { + X86MachineState *x86ms = X86_MACHINE(ms); + /* + * For confidential guests, reload bios ROM if IGVM is not specified. + * If an IGVM file is specified then the firmware must be provided + * in the IGVM file. + */ + if (ms->cgs && !x86ms->igvm) { + x86_bios_rom_reload(x86ms); + } + if (x86_machine_is_smm_enabled(x86ms)) { + memory_listener_register(&smram_listener.listener, + &smram_address_space); + } + kvm_set_max_apic_id(x86ms->apic_id_limit); + } + + trace_kvm_arch_on_vmfd_change(); + return 0; +} + +bool kvm_arch_supports_vmfd_change(void) +{ + return true; +} + +static int xen_init(MachineState *ms, KVMState *s) +{ +#ifdef CONFIG_XEN_EMU + int ret = 0; + if (!object_dynamic_cast(OBJECT(ms), TYPE_PC_MACHINE)) { + error_report("kvm: Xen support only available in PC machine"); + return -ENOTSUP; + } + /* hyperv_enabled() doesn't work yet. */ + uint32_t msr = XEN_HYPERCALL_MSR; + ret = kvm_xen_init(s, msr); + return ret; +#else + error_report("kvm: Xen support not enabled in qemu"); + return -ENOTSUP; +#endif +} + int kvm_arch_init(MachineState *ms, KVMState *s) { int ret; struct utsname utsname; Error *local_err = NULL; + static bool first = true; /* * Initialize confidential guest (SEV/TDX) context, if required @@ -3422,21 +3485,10 @@ int kvm_arch_init(MachineState *ms, KVMState *s) } if (s->xen_version) { -#ifdef CONFIG_XEN_EMU - if (!object_dynamic_cast(OBJECT(ms), TYPE_PC_MACHINE)) { - error_report("kvm: Xen support only available in PC machine"); - return -ENOTSUP; - } - /* hyperv_enabled() doesn't work yet. */ - uint32_t msr = XEN_HYPERCALL_MSR; - ret = kvm_xen_init(s, msr); + ret = xen_init(ms, s); if (ret < 0) { return ret; } -#else - error_report("kvm: Xen support not enabled in qemu"); - return -ENOTSUP; -#endif } ret = kvm_get_supported_msrs(s); @@ -3463,16 +3515,17 @@ int kvm_arch_init(MachineState *ms, KVMState *s) return ret; } - /* Tell fw_cfg to notify the BIOS to reserve the range. */ - e820_add_entry(KVM_IDENTITY_BASE, 0x4000, E820_RESERVED); - + if (first) { + /* Tell fw_cfg to notify the BIOS to reserve the range. */ + e820_add_entry(KVM_IDENTITY_BASE, 0x4000, E820_RESERVED); + } ret = kvm_vm_set_nr_mmu_pages(s); if (ret < 0) { return ret; } if (object_dynamic_cast(OBJECT(ms), TYPE_X86_MACHINE) && - x86_machine_is_smm_enabled(X86_MACHINE(ms))) { + x86_machine_is_smm_enabled(X86_MACHINE(ms)) && first) { smram_machine_done.notify = register_smram_listener; qemu_add_machine_init_done_notifier(&smram_machine_done); } @@ -3519,16 +3572,46 @@ int kvm_arch_init(MachineState *ms, KVMState *s) return ret; } - ret = kvm_msr_energy_thread_init(s, ms); - if (ret < 0) { - error_report("kvm : error RAPL feature requirement not met"); - return ret; + if (first) { + ret = kvm_msr_energy_thread_init(s, ms); + if (ret < 0) { + error_report("kvm : " + "error RAPL feature requirement not met"); + return ret; + } } } } pmu_cap = kvm_check_extension(s, KVM_CAP_PMU_CAPABILITY); + if (first) { + kvm_vmfd_add_change_notifier(&kvm_vmfd_change_notifier); + } + + /* + * Most x86 CPUs in current use have self-snoop, so honoring guest PAT is + * preferable. As well, the bochs video driver bug which motivated making + * this a default-enabled quirk in KVM was fixed long ago. + */ + if (s->honor_guest_pat != ON_OFF_AUTO_OFF) { + ret = kvm_check_extension(s, KVM_CAP_DISABLE_QUIRKS2); + if (ret & KVM_X86_QUIRK_IGNORE_GUEST_PAT) { + ret = kvm_vm_enable_cap(s, KVM_CAP_DISABLE_QUIRKS2, 0, + KVM_X86_QUIRK_IGNORE_GUEST_PAT); + if (ret < 0) { + error_report("failed to disable KVM_X86_QUIRK_IGNORE_GUEST_PAT"); + return ret; + } + } else { + if (s->honor_guest_pat == ON_OFF_AUTO_ON) { + error_report("KVM does not support disabling ignore-guest-PAT quirk"); + return -EINVAL; + } + } + } + + first = false; return 0; } @@ -4983,8 +5066,7 @@ static int kvm_get_msrs(X86CPU *cpu) */ if (cpu->fill_mtrr_mask) { - QEMU_BUILD_BUG_ON(TARGET_PHYS_ADDR_SPACE_BITS > 52); - assert(cpu->phys_bits <= TARGET_PHYS_ADDR_SPACE_BITS); + assert(cpu->phys_bits <= 52); mtrr_top_bits = MAKE_64BIT_MASK(cpu->phys_bits, 52 - cpu->phys_bits); } else { mtrr_top_bits = 0; @@ -5502,8 +5584,6 @@ static int kvm_get_vcpu_events(X86CPU *cpu) } if (events.smi.pending) { cpu_interrupt(CPU(cpu), CPU_INTERRUPT_SMI); - } else { - cpu_reset_interrupt(CPU(cpu), CPU_INTERRUPT_SMI); } if (events.smi.smm_inside_nmi) { env->hflags2 |= HF2_SMM_INSIDE_NMI_MASK; @@ -5512,8 +5592,6 @@ static int kvm_get_vcpu_events(X86CPU *cpu) } if (events.smi.latched_init) { cpu_interrupt(CPU(cpu), CPU_INTERRUPT_INIT); - } else { - cpu_reset_interrupt(CPU(cpu), CPU_INTERRUPT_INIT); } } @@ -6278,27 +6356,33 @@ static int kvm_install_msr_filters(KVMState *s) static int kvm_filter_msr(KVMState *s, uint32_t msr, QEMURDMSRHandler *rdmsr, QEMUWRMSRHandler *wrmsr) { - int i, ret; + int i, ret = 0; for (i = 0; i < ARRAY_SIZE(msr_handlers); i++) { - if (!msr_handlers[i].msr) { + if (msr_handlers[i].msr == msr) { + break; + } else if (!msr_handlers[i].msr) { msr_handlers[i] = (KVMMSRHandlers) { .msr = msr, .rdmsr = rdmsr, .wrmsr = wrmsr, }; + break; + } + } - ret = kvm_install_msr_filters(s); - if (ret) { - msr_handlers[i] = (KVMMSRHandlers) { }; - return ret; - } + if (i == ARRAY_SIZE(msr_handlers)) { + ret = -EINVAL; + goto end; + } - return 0; - } + ret = kvm_install_msr_filters(s); + if (ret) { + msr_handlers[i] = (KVMMSRHandlers) { }; } - return -EINVAL; + end: + return ret; } static int kvm_handle_rdmsr(X86CPU *cpu, struct kvm_run *run) @@ -6984,6 +7068,24 @@ static void kvm_arch_set_xen_evtchn_max_pirq(Object *obj, Visitor *v, s->xen_evtchn_max_pirq = value; } +static int kvm_arch_get_honor_guest_pat(Object *obj, Error **errp) +{ + KVMState *s = KVM_STATE(obj); + return s->honor_guest_pat; +} + +static void kvm_arch_set_honor_guest_pat(Object *obj, int value, Error **errp) +{ + KVMState *s = KVM_STATE(obj); + + if (s->fd != -1) { + error_setg(errp, "Cannot set properties after the accelerator has been initialized"); + return; + } + + s->honor_guest_pat = value; +} + void kvm_arch_accel_class_init(ObjectClass *oc) { object_class_property_add_enum(oc, "notify-vmexit", "NotifyVMexitOption", @@ -7023,6 +7125,14 @@ void kvm_arch_accel_class_init(ObjectClass *oc) NULL, NULL); object_class_property_set_description(oc, "xen-evtchn-max-pirq", "Maximum number of Xen PIRQs"); + + object_class_property_add_enum(oc, "honor-guest-pat", "OnOffAuto", + &OnOffAuto_lookup, + kvm_arch_get_honor_guest_pat, + kvm_arch_set_honor_guest_pat); + object_class_property_set_description(oc, "honor-guest-pat", + "Disable KVM quirk that ignores guest PAT " + "memory type settings (default: auto)"); } void kvm_set_max_apic_id(uint32_t max_apic_id) diff --git a/target/i386/kvm/tdx.c b/target/i386/kvm/tdx.c index 01619857685b..4cae99c281ac 100644 --- a/target/i386/kvm/tdx.c +++ b/target/i386/kvm/tdx.c @@ -19,6 +19,7 @@ #include "crypto/hash.h" #include "system/kvm_int.h" #include "system/runstate.h" +#include "system/reset.h" #include "system/system.h" #include "system/ramblock.h" #include "system/address-spaces.h" @@ -38,6 +39,7 @@ #include "kvm_i386.h" #include "tdx.h" #include "tdx-quote-generator.h" +#include "trace.h" #include "standard-headers/asm-x86/kvm_para.h" @@ -295,14 +297,51 @@ static void tdx_post_init_vcpus(void) } } -static void tdx_finalize_vm(Notifier *notifier, void *unused) +static void tdx_init_fw_mem_region(void) { TdxFirmware *tdvf = &tdx_guest->tdvf; TdxFirmwareEntry *entry; - RAMBlock *ram_block; Error *local_err = NULL; int r; + for_each_tdx_fw_entry(tdvf, entry) { + struct kvm_tdx_init_mem_region region; + uint32_t flags; + + region = (struct kvm_tdx_init_mem_region) { + .source_addr = (uintptr_t)entry->mem_ptr, + .gpa = entry->address, + .nr_pages = entry->size >> 12, + }; + + flags = entry->attributes & TDVF_SECTION_ATTRIBUTES_MR_EXTEND ? + KVM_TDX_MEASURE_MEMORY_REGION : 0; + + do { + error_free(local_err); + local_err = NULL; + r = tdx_vcpu_ioctl(first_cpu, KVM_TDX_INIT_MEM_REGION, flags, + ®ion, &local_err); + } while (r == -EAGAIN || r == -EINTR); + if (r < 0) { + error_report_err(local_err); + exit(1); + } + + if (entry->type == TDVF_SECTION_TYPE_TD_HOB || + entry->type == TDVF_SECTION_TYPE_TEMP_MEM) { + qemu_ram_munmap(-1, entry->mem_ptr, entry->size); + entry->mem_ptr = NULL; + } + } +} + +static void tdx_finalize_vm(Notifier *notifier, void *unused) +{ + TdxFirmware *tdvf = &tdx_guest->tdvf; + TdxFirmwareEntry *entry; + RAMBlock *ram_block; + tdx_init_ram_entries(); for_each_tdx_fw_entry(tdvf, entry) { @@ -339,37 +378,7 @@ static void tdx_finalize_vm(Notifier *notifier, void *unused) tdvf_hob_create(tdx_guest, tdx_get_hob_entry(tdx_guest)); tdx_post_init_vcpus(); - - for_each_tdx_fw_entry(tdvf, entry) { - struct kvm_tdx_init_mem_region region; - uint32_t flags; - - region = (struct kvm_tdx_init_mem_region) { - .source_addr = (uintptr_t)entry->mem_ptr, - .gpa = entry->address, - .nr_pages = entry->size >> 12, - }; - - flags = entry->attributes & TDVF_SECTION_ATTRIBUTES_MR_EXTEND ? - KVM_TDX_MEASURE_MEMORY_REGION : 0; - - do { - error_free(local_err); - local_err = NULL; - r = tdx_vcpu_ioctl(first_cpu, KVM_TDX_INIT_MEM_REGION, flags, - ®ion, &local_err); - } while (r == -EAGAIN || r == -EINTR); - if (r < 0) { - error_report_err(local_err); - exit(1); - } - - if (entry->type == TDVF_SECTION_TYPE_TD_HOB || - entry->type == TDVF_SECTION_TYPE_TEMP_MEM) { - qemu_ram_munmap(-1, entry->mem_ptr, entry->size); - entry->mem_ptr = NULL; - } - } + tdx_init_fw_mem_region(); /* * TDVF image has been copied into private region above via @@ -382,8 +391,48 @@ static void tdx_finalize_vm(Notifier *notifier, void *unused) CONFIDENTIAL_GUEST_SUPPORT(tdx_guest)->ready = true; } -static Notifier tdx_machine_done_notify = { - .notify = tdx_finalize_vm, +static void tdx_handle_reset(Object *obj, ResetType type) +{ + if (!runstate_is_running() && !phase_check(PHASE_MACHINE_READY)) { + return; + } + + if (!kvm_enable_hypercall(BIT_ULL(KVM_HC_MAP_GPA_RANGE))) { + error_setg(&error_fatal, "KVM_HC_MAP_GPA_RANGE not enabled for guest"); + } + + tdx_finalize_vm(NULL, NULL); + trace_tdx_handle_reset(); +} + +/* TDX guest reset will require us to reinitialize some of tdx guest state. */ +static int set_tdx_vm_uninitialized(NotifierWithReturn *notifier, + void *data, Error** errp) +{ + TdxFirmware *fw = &tdx_guest->tdvf; + + if (!((VmfdChangeNotifier *)data)->pre) { + return 0; + } + + if (tdx_guest->initialized) { + tdx_guest->initialized = false; + } + + g_free(tdx_guest->ram_entries); + + /* + * the firmware entries will be parsed again, see + * x86_firmware_configure() -> tdx_parse_tdvf() + */ + fw->entries = 0; + g_free(fw->entries); + + return 0; +} + +static NotifierWithReturn tdx_vmfd_change_notifier = { + .notify = set_tdx_vm_uninitialized, }; /* @@ -731,8 +780,6 @@ static int tdx_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) */ kvm_readonly_mem_allowed = false; - qemu_add_machine_init_done_notifier(&tdx_machine_done_notify); - tdx_guest = tdx; return 0; } @@ -1498,6 +1545,7 @@ OBJECT_DEFINE_TYPE_WITH_INTERFACES(TdxGuest, TDX_GUEST, X86_CONFIDENTIAL_GUEST, { TYPE_USER_CREATABLE }, + { TYPE_RESETTABLE_INTERFACE }, { NULL }) static void tdx_guest_init(Object *obj) @@ -1531,20 +1579,39 @@ static void tdx_guest_init(Object *obj) tdx->event_notify_vector = -1; tdx->event_notify_apicid = -1; + kvm_vmfd_add_change_notifier(&tdx_vmfd_change_notifier); + qemu_register_resettable(obj); } static void tdx_guest_finalize(Object *obj) { } +static ResettableState *tdx_reset_state(Object *obj) +{ + TdxGuest *tdx = TDX_GUEST(obj); + return &tdx->reset_state; +} + static void tdx_guest_class_init(ObjectClass *oc, const void *data) { ConfidentialGuestSupportClass *klass = CONFIDENTIAL_GUEST_SUPPORT_CLASS(oc); X86ConfidentialGuestClass *x86_klass = X86_CONFIDENTIAL_GUEST_CLASS(oc); + ResettableClass *rc = RESETTABLE_CLASS(oc); klass->kvm_init = tdx_kvm_init; + klass->can_rebuild_guest_state = true; x86_klass->kvm_type = tdx_kvm_type; x86_klass->cpu_instance_init = tdx_cpu_instance_init; x86_klass->adjust_cpuid_features = tdx_adjust_cpuid_features; x86_klass->check_features = tdx_check_features; + + /* + * the exit phase makes sure sev handles reset after all legacy resets + * have taken place (in the hold phase) and IGVM has also properly + * set up the boot state. + */ + rc->phases.exit = tdx_handle_reset; + rc->get_state = tdx_reset_state; + } diff --git a/target/i386/kvm/tdx.h b/target/i386/kvm/tdx.h index 1c38faf9834c..264fbe530cc8 100644 --- a/target/i386/kvm/tdx.h +++ b/target/i386/kvm/tdx.h @@ -70,6 +70,7 @@ typedef struct TdxGuest { uint32_t event_notify_vector; uint32_t event_notify_apicid; + ResettableState reset_state; } TdxGuest; #ifdef CONFIG_TDX diff --git a/target/i386/kvm/trace-events b/target/i386/kvm/trace-events index 74a6234ff7f5..a38623457141 100644 --- a/target/i386/kvm/trace-events +++ b/target/i386/kvm/trace-events @@ -6,6 +6,7 @@ kvm_x86_add_msi_route(int virq) "Adding route entry for virq %d" kvm_x86_remove_msi_route(int virq) "Removing route entry for virq %d" kvm_x86_update_msi_routes(int num) "Updated %d MSI routes" kvm_hc_map_gpa_range(uint64_t gpa, uint64_t size, uint64_t attributes, uint64_t flags) "gpa 0x%" PRIx64 " size 0x%" PRIx64 " attributes 0x%" PRIx64 " flags 0x%" PRIx64 +kvm_arch_on_vmfd_change(void) "" # xen-emu.c kvm_xen_hypercall(int cpu, uint8_t cpl, uint64_t input, uint64_t a0, uint64_t a1, uint64_t a2, uint64_t ret) "xen_hypercall: cpu %d cpl %d input %" PRIu64 " a0 0x%" PRIx64 " a1 0x%" PRIx64 " a2 0x%" PRIx64" ret 0x%" PRIx64 @@ -13,3 +14,6 @@ kvm_xen_soft_reset(void) "" kvm_xen_set_shared_info(uint64_t gfn) "shared info at gfn 0x%" PRIx64 kvm_xen_set_vcpu_attr(int cpu, int type, uint64_t gpa) "vcpu attr cpu %d type %d gpa 0x%" PRIx64 kvm_xen_set_vcpu_callback(int cpu, int vector) "callback vcpu %d vector %d" + +# tdx.c +tdx_handle_reset(void) "" diff --git a/target/i386/kvm/xen-emu.c b/target/i386/kvm/xen-emu.c index 52de01983436..29364a927979 100644 --- a/target/i386/kvm/xen-emu.c +++ b/target/i386/kvm/xen-emu.c @@ -44,9 +44,12 @@ #include "xen-compat.h" +NotifierWithReturn xen_vmfd_change_notifier; +static uint32_t xen_msr; static void xen_vcpu_singleshot_timer_event(void *opaque); static void xen_vcpu_periodic_timer_event(void *opaque); static int vcpuop_stop_singleshot_timer(CPUState *cs); +static int do_initialize_xen_caps(KVMState *s, uint32_t hypercall_msr); #ifdef TARGET_X86_64 #define hypercall_compat32(longmode) (!(longmode)) @@ -54,6 +57,23 @@ static int vcpuop_stop_singleshot_timer(CPUState *cs); #define hypercall_compat32(longmode) (false) #endif +static int xen_handle_vmfd_change(NotifierWithReturn *n, + void *data, Error** errp) +{ + int ret; + + /* we are not interested in pre vmfd change notification */ + if (((VmfdChangeNotifier *)data)->pre) { + return 0; + } + + ret = do_initialize_xen_caps(kvm_state, xen_msr); + if (ret < 0) { + return ret; + } + return 0; +} + static bool kvm_gva_to_gpa(CPUState *cs, uint64_t gva, uint64_t *gpa, size_t *len, bool is_write) { @@ -111,7 +131,7 @@ static inline int kvm_copy_to_gva(CPUState *cs, uint64_t gva, void *buf, return kvm_gva_rw(cs, gva, buf, sz, true); } -int kvm_xen_init(KVMState *s, uint32_t hypercall_msr) +static int do_initialize_xen_caps(KVMState *s, uint32_t hypercall_msr) { const int required_caps = KVM_XEN_HVM_CONFIG_HYPERCALL_MSR | KVM_XEN_HVM_CONFIG_INTERCEPT_HCALL | KVM_XEN_HVM_CONFIG_SHARED_INFO; @@ -143,6 +163,19 @@ int kvm_xen_init(KVMState *s, uint32_t hypercall_msr) strerror(-ret)); return ret; } + return xen_caps; +} + +int kvm_xen_init(KVMState *s, uint32_t hypercall_msr) +{ + int xen_caps; + + xen_caps = do_initialize_xen_caps(s, hypercall_msr); + if (xen_caps < 0) { + return xen_caps; + } + + xen_msr = hypercall_msr; /* If called a second time, don't repeat the rest of the setup. */ if (s->xen_caps) { @@ -185,6 +218,9 @@ int kvm_xen_init(KVMState *s, uint32_t hypercall_msr) xen_primary_console_reset(); xen_xenstore_reset(); + xen_vmfd_change_notifier.notify = xen_handle_vmfd_change; + kvm_vmfd_add_change_notifier(&xen_vmfd_change_notifier); + return 0; } diff --git a/target/i386/monitor.c b/target/i386/monitor.c index 99b32cb7b0f3..446428602ef2 100644 --- a/target/i386/monitor.c +++ b/target/i386/monitor.c @@ -602,46 +602,7 @@ static target_long monitor_get_pc(Monitor *mon, const struct MonitorDef *md, const MonitorDef monitor_defs[] = { #define SEG(name, seg) \ - { name, offsetof(CPUX86State, segs[seg].selector), NULL, MD_I32 },\ - { name ".base", offsetof(CPUX86State, segs[seg].base) },\ { name ".limit", offsetof(CPUX86State, segs[seg].limit), NULL, MD_I32 }, - - { "eax", offsetof(CPUX86State, regs[0]) }, - { "ecx", offsetof(CPUX86State, regs[1]) }, - { "edx", offsetof(CPUX86State, regs[2]) }, - { "ebx", offsetof(CPUX86State, regs[3]) }, - { "esp|sp", offsetof(CPUX86State, regs[4]) }, - { "ebp|fp", offsetof(CPUX86State, regs[5]) }, - { "esi", offsetof(CPUX86State, regs[6]) }, - { "edi", offsetof(CPUX86State, regs[7]) }, -#ifdef TARGET_X86_64 - { "r8", offsetof(CPUX86State, regs[8]) }, - { "r9", offsetof(CPUX86State, regs[9]) }, - { "r10", offsetof(CPUX86State, regs[10]) }, - { "r11", offsetof(CPUX86State, regs[11]) }, - { "r12", offsetof(CPUX86State, regs[12]) }, - { "r13", offsetof(CPUX86State, regs[13]) }, - { "r14", offsetof(CPUX86State, regs[14]) }, - { "r15", offsetof(CPUX86State, regs[15]) }, - { "r16", offsetof(CPUX86State, regs[16]) }, - { "r17", offsetof(CPUX86State, regs[17]) }, - { "r18", offsetof(CPUX86State, regs[18]) }, - { "r19", offsetof(CPUX86State, regs[19]) }, - { "r20", offsetof(CPUX86State, regs[20]) }, - { "r21", offsetof(CPUX86State, regs[21]) }, - { "r22", offsetof(CPUX86State, regs[22]) }, - { "r23", offsetof(CPUX86State, regs[23]) }, - { "r24", offsetof(CPUX86State, regs[24]) }, - { "r25", offsetof(CPUX86State, regs[25]) }, - { "r26", offsetof(CPUX86State, regs[26]) }, - { "r27", offsetof(CPUX86State, regs[27]) }, - { "r28", offsetof(CPUX86State, regs[28]) }, - { "r29", offsetof(CPUX86State, regs[29]) }, - { "r30", offsetof(CPUX86State, regs[30]) }, - { "r31", offsetof(CPUX86State, regs[31]) }, -#endif - { "eflags", offsetof(CPUX86State, eflags) }, - { "eip", offsetof(CPUX86State, eip) }, SEG("cs", R_CS) SEG("ds", R_DS) SEG("es", R_ES) diff --git a/target/i386/mshv/meson.build b/target/i386/mshv/meson.build index 647e5dafb77f..49f28d4a5b9d 100644 --- a/target/i386/mshv/meson.build +++ b/target/i386/mshv/meson.build @@ -2,7 +2,7 @@ i386_mshv_ss = ss.source_set() i386_mshv_ss.add(files( 'mshv-cpu.c', - 'x86.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 f190e83bd150..2bc978deb252 100644 --- a/target/i386/mshv/mshv-cpu.c +++ b/target/i386/mshv/mshv-cpu.c @@ -1548,74 +1548,6 @@ int mshv_create_vcpu(int vm_fd, uint8_t vp_index, int *cpu_fd) return 0; } -static int guest_mem_read_with_gva(const CPUState *cpu, uint64_t gva, - uint8_t *data, uintptr_t size, - bool fetch_instruction) -{ - int ret; - uint64_t gpa, flags; - - flags = HV_TRANSLATE_GVA_VALIDATE_READ; - ret = translate_gva(cpu, gva, &gpa, flags); - if (ret < 0) { - error_report("failed to translate gva to gpa"); - return -1; - } - - ret = mshv_guest_mem_read(gpa, data, size, false, fetch_instruction); - if (ret < 0) { - error_report("failed to read from guest memory"); - return -1; - } - - return 0; -} - -static int guest_mem_write_with_gva(const CPUState *cpu, uint64_t gva, - const uint8_t *data, uintptr_t size) -{ - int ret; - uint64_t gpa, flags; - - flags = HV_TRANSLATE_GVA_VALIDATE_WRITE; - ret = translate_gva(cpu, gva, &gpa, flags); - if (ret < 0) { - error_report("failed to translate gva to gpa"); - return -1; - } - ret = mshv_guest_mem_write(gpa, data, size, false); - if (ret < 0) { - error_report("failed to write to guest memory"); - return -1; - } - return 0; -} - -static void write_mem(CPUState *cpu, void *data, target_ulong addr, int bytes) -{ - if (guest_mem_write_with_gva(cpu, addr, data, bytes) < 0) { - error_report("failed to write memory"); - abort(); - } -} - -static void fetch_instruction(CPUState *cpu, void *data, - target_ulong addr, int bytes) -{ - if (guest_mem_read_with_gva(cpu, addr, data, bytes, true) < 0) { - error_report("failed to fetch instruction"); - abort(); - } -} - -static void read_mem(CPUState *cpu, void *data, target_ulong addr, int bytes) -{ - if (guest_mem_read_with_gva(cpu, addr, data, bytes, false) < 0) { - error_report("failed to read memory"); - abort(); - } -} - static void read_segment_descriptor(CPUState *cpu, struct x86_segment_descriptor *desc, enum X86Seg seg_idx) @@ -1634,9 +1566,6 @@ static void read_segment_descriptor(CPUState *cpu, } static const struct x86_emul_ops mshv_x86_emul_ops = { - .fetch_instruction = fetch_instruction, - .read_mem = read_mem, - .write_mem = write_mem, .read_segment_descriptor = read_segment_descriptor, }; diff --git a/target/i386/sev.c b/target/i386/sev.c index acdcb9c4e681..cddffe0da8dd 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -30,8 +30,10 @@ #include "system/kvm.h" #include "kvm/kvm_i386.h" #include "sev.h" +#include "system/cpus.h" #include "system/system.h" #include "system/runstate.h" +#include "system/reset.h" #include "trace.h" #include "migration/blocker.h" #include "qom/object.h" @@ -86,6 +88,10 @@ typedef struct QEMU_PACKED PaddedSevHashTable { uint8_t padding[ROUND_UP(sizeof(SevHashTable), 16) - sizeof(SevHashTable)]; } PaddedSevHashTable; +static void sev_handle_reset(Object *obj, ResetType type); + +SevKernelLoaderContext sev_load_ctx = {}; + QEMU_BUILD_BUG_ON(sizeof(PaddedSevHashTable) % 16 != 0); #define SEV_INFO_BLOCK_GUID "00f771de-1a7e-4fcb-890e-68c77e2fb44e" @@ -129,6 +135,7 @@ struct SevCommonState { uint8_t build_id; int sev_fd; SevState state; + ResettableState reset_state; QTAILQ_HEAD(, SevLaunchVmsa) launch_vmsa; }; @@ -1421,11 +1428,6 @@ sev_launch_finish(SevCommonState *sev_common) } sev_set_guest_state(sev_common, SEV_STATE_RUNNING); - - /* add migration blocker */ - error_setg(&sev_mig_blocker, - "SEV: Migration is not implemented"); - migrate_add_blocker(&sev_mig_blocker, &error_fatal); } static int snp_launch_update_data(uint64_t gpa, void *hva, size_t len, @@ -1608,7 +1610,6 @@ static void sev_snp_launch_finish(SevCommonState *sev_common) { int ret, error; - Error *local_err = NULL; OvmfSevMetadata *metadata; SevLaunchUpdateData *data; SevSnpGuestState *sev_snp = SEV_SNP_GUEST(sev_common); @@ -1655,15 +1656,6 @@ sev_snp_launch_finish(SevCommonState *sev_common) kvm_mark_guest_state_protected(); sev_set_guest_state(sev_common, SEV_STATE_RUNNING); - - /* add migration blocker */ - error_setg(&sev_mig_blocker, - "SEV-SNP: Migration is not implemented"); - ret = migrate_add_blocker(&sev_mig_blocker, &local_err); - if (local_err) { - error_report_err(local_err); - exit(1); - } } @@ -1676,6 +1668,16 @@ sev_vm_state_change(void *opaque, bool running, RunState state) if (running) { if (!sev_check_state(sev_common, SEV_STATE_RUNNING)) { klass->launch_finish(sev_common); + + /* add migration blocker */ + error_setg(&sev_mig_blocker, + "SEV: Migration is not implemented"); + migrate_add_blocker(&sev_mig_blocker, &error_fatal); + /* + * mark SEV guest as resettable so that we can reinitialize + * SEV upon reset. + */ + qemu_register_resettable(OBJECT(sev_common)); } } } @@ -1783,6 +1785,7 @@ static int sev_common_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) uint32_t ebx; uint32_t host_cbitpos; struct sev_user_data_status status = {}; + SevLaunchUpdateData *data, *next_elm; SevCommonState *sev_common = SEV_COMMON(cgs); SevCommonStateClass *klass = SEV_COMMON_GET_CLASS(cgs); X86ConfidentialGuestClass *x86_klass = @@ -1790,6 +1793,11 @@ static int sev_common_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) sev_common->state = SEV_STATE_UNINIT; + /* free existing launch update data if any */ + QTAILQ_FOREACH_SAFE(data, &launch_update, next, next_elm) { + g_free(data); + } + host_cpuid(0x8000001F, 0, NULL, &ebx, NULL, NULL); host_cbitpos = ebx & 0x3f; @@ -1930,8 +1938,9 @@ static int sev_common_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) return -1; } - qemu_add_vm_change_state_handler(sev_vm_state_change, sev_common); - + if (!cgs->ready) { + qemu_add_vm_change_state_handler(sev_vm_state_change, sev_common); + } cgs->ready = true; return 0; @@ -1953,22 +1962,23 @@ static int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) return -1; } - /* - * SEV uses these notifiers to register/pin pages prior to guest use, - * but SNP relies on guest_memfd for private pages, which has its - * own internal mechanisms for registering/pinning private memory. - */ - ram_block_notifier_add(&sev_ram_notifier); - - /* - * The machine done notify event is used for SEV guests to get the - * measurement of the encrypted images. When SEV-SNP is enabled, the - * measurement is part of the guest attestation process where it can - * be collected without any reliance on the VMM. So skip registering - * the notifier for SNP in favor of using guest attestation instead. - */ - qemu_add_machine_init_done_notifier(&sev_machine_done_notify); + if (!cgs->ready) { + /* + * SEV uses these notifiers to register/pin pages prior to guest use, + * but SNP relies on guest_memfd for private pages, which has its + * own internal mechanisms for registering/pinning private memory. + */ + ram_block_notifier_add(&sev_ram_notifier); + /* + * The machine done notify event is used for SEV guests to get the + * measurement of the encrypted images. When SEV-SNP is enabled, the + * measurement is part of the guest attestation process where it can + * be collected without any reliance on the VMM. So skip registering + * the notifier for SNP in favor of using guest attestation instead. + */ + qemu_add_machine_init_done_notifier(&sev_machine_done_notify); + } return 0; } @@ -1976,6 +1986,8 @@ static int sev_snp_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) { MachineState *ms = MACHINE(qdev_get_machine()); X86MachineState *x86ms = X86_MACHINE(ms); + SevCommonState *sev_common = SEV_COMMON(cgs); + SevSnpGuestState *sev_snp_guest = SEV_SNP_GUEST(sev_common); if (x86ms->smm == ON_OFF_AUTO_AUTO) { x86ms->smm = ON_OFF_AUTO_OFF; @@ -1984,9 +1996,48 @@ static int sev_snp_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) return -1; } + /* free existing kernel hashes data if any */ + g_free(sev_snp_guest->kernel_hashes_data); + sev_snp_guest->kernel_hashes_data = NULL; + return 0; } +/* + * handle sev vm reset + */ +static void sev_handle_reset(Object *obj, ResetType type) +{ + SevCommonState *sev_common = SEV_COMMON(MACHINE(qdev_get_machine())->cgs); + SevCommonStateClass *klass = SEV_COMMON_GET_CLASS(sev_common); + + if (!sev_common) { + return; + } + + if (!runstate_is_running()) { + return; + } + + sev_add_kernel_loader_hashes(&sev_load_ctx, &error_fatal); + if (sev_es_enabled() && !sev_snp_enabled()) { + sev_launch_get_measure(NULL, NULL); + } + if (!sev_check_state(sev_common, SEV_STATE_RUNNING)) { + /* this calls sev_snp_launch_finish() etc */ + klass->launch_finish(sev_common); + } + + trace_sev_handle_reset(); + return; +} + +static ResettableState *sev_reset_state(Object *obj) +{ + SevCommonState *sev_common = SEV_COMMON(obj); + return &sev_common->reset_state; +} + int sev_encrypt_flash(hwaddr gpa, uint8_t *ptr, uint64_t len, Error **errp) { @@ -2465,6 +2516,8 @@ bool sev_add_kernel_loader_hashes(SevKernelLoaderContext *ctx, Error **errp) return false; } + /* save the context here so that it can be re-used when vm is reset */ + memcpy(&sev_load_ctx, ctx, sizeof(*ctx)); return klass->build_kernel_loader_hashes(sev_common, area, ctx, errp); } @@ -2707,7 +2760,11 @@ static int cgs_set_guest_policy(ConfidentialGuestPolicyType policy_type, id_auth->author_key[0] ? 1 : 0; finish->id_block_en = 1; } - sev_snp_guest->kvm_start_conf.policy = policy; + + /* do not reset existing policy if policy was not set in IGVM */ + if (policy != 0) { + sev_snp_guest->kvm_start_conf.policy = policy; + } } else { SevGuestState *sev_guest = SEV_GUEST(MACHINE(qdev_get_machine())->cgs); /* Only the policy flags are supported for SEV and SEV-ES */ @@ -2716,7 +2773,11 @@ static int cgs_set_guest_policy(ConfidentialGuestPolicyType policy_type, "but SEV-SNP is not enabled", __func__); return -1; } - sev_guest->policy = policy; + + /* do not reset existing policy if policy was not set in IGVM */ + if (policy != 0) { + sev_guest->policy = policy; + } } return 0; } @@ -2725,8 +2786,16 @@ static void sev_common_class_init(ObjectClass *oc, const void *data) { ConfidentialGuestSupportClass *klass = CONFIDENTIAL_GUEST_SUPPORT_CLASS(oc); + ResettableClass *rc = RESETTABLE_CLASS(oc); klass->kvm_init = sev_common_kvm_init; + /* + * the exit phase makes sure sev handles reset after all legacy resets + * have taken place (in the hold phase) and IGVM has also properly + * set up the boot state. + */ + rc->phases.exit = sev_handle_reset; + rc->get_state = sev_reset_state; object_class_property_add_str(oc, "sev-device", sev_common_get_sev_device, @@ -2760,6 +2829,7 @@ sev_common_instance_init(Object *obj) cgs->set_guest_state = cgs_set_guest_state; cgs->get_mem_map_entry = cgs_get_mem_map_entry; cgs->set_guest_policy = cgs_set_guest_policy; + cgs->can_rebuild_guest_state = true; QTAILQ_INIT(&sev_common->launch_vmsa); } @@ -2775,6 +2845,7 @@ static const TypeInfo sev_common_info = { .abstract = true, .interfaces = (const InterfaceInfo[]) { { TYPE_USER_CREATABLE }, + { TYPE_RESETTABLE_INTERFACE }, { } } }; diff --git a/target/i386/tcg/fpu_helper.c b/target/i386/tcg/fpu_helper.c index b3b23823fda9..37c83ded389a 100644 --- a/target/i386/tcg/fpu_helper.c +++ b/target/i386/tcg/fpu_helper.c @@ -1377,16 +1377,16 @@ void helper_fpatan(CPUX86State *env) int32_t arg1_exp = extractFloatx80Exp(ST1); bool arg1_sign = extractFloatx80Sign(ST1); - if (floatx80_is_signaling_nan(ST0, &env->fp_status)) { + if (floatx80_invalid_encoding(ST0, &env->fp_status) || + floatx80_invalid_encoding(ST1, &env->fp_status)) { + float_raise(float_flag_invalid, &env->fp_status); + ST1 = floatx80_default_nan(&env->fp_status); + } else if (floatx80_is_signaling_nan(ST0, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST1 = floatx80_silence_nan(ST0, &env->fp_status); } else if (floatx80_is_signaling_nan(ST1, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST1 = floatx80_silence_nan(ST1, &env->fp_status); - } else if (floatx80_invalid_encoding(ST0, &env->fp_status) || - floatx80_invalid_encoding(ST1, &env->fp_status)) { - float_raise(float_flag_invalid, &env->fp_status); - ST1 = floatx80_default_nan(&env->fp_status); } else if (floatx80_is_any_nan(ST0)) { ST1 = ST0; } else if (floatx80_is_any_nan(ST1)) { @@ -2061,16 +2061,16 @@ void helper_fyl2xp1(CPUX86State *env) int32_t arg1_exp = extractFloatx80Exp(ST1); bool arg1_sign = extractFloatx80Sign(ST1); - if (floatx80_is_signaling_nan(ST0, &env->fp_status)) { + if (floatx80_invalid_encoding(ST0, &env->fp_status) || + floatx80_invalid_encoding(ST1, &env->fp_status)) { + float_raise(float_flag_invalid, &env->fp_status); + ST1 = floatx80_default_nan(&env->fp_status); + } else if (floatx80_is_signaling_nan(ST0, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST1 = floatx80_silence_nan(ST0, &env->fp_status); } else if (floatx80_is_signaling_nan(ST1, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST1 = floatx80_silence_nan(ST1, &env->fp_status); - } else if (floatx80_invalid_encoding(ST0, &env->fp_status) || - floatx80_invalid_encoding(ST1, &env->fp_status)) { - float_raise(float_flag_invalid, &env->fp_status); - ST1 = floatx80_default_nan(&env->fp_status); } else if (floatx80_is_any_nan(ST0)) { ST1 = ST0; } else if (floatx80_is_any_nan(ST1)) { @@ -2159,16 +2159,16 @@ void helper_fyl2x(CPUX86State *env) int32_t arg1_exp = extractFloatx80Exp(ST1); bool arg1_sign = extractFloatx80Sign(ST1); - if (floatx80_is_signaling_nan(ST0, &env->fp_status)) { + if (floatx80_invalid_encoding(ST0, &env->fp_status) || + floatx80_invalid_encoding(ST1, &env->fp_status)) { + float_raise(float_flag_invalid, &env->fp_status); + ST1 = floatx80_default_nan(&env->fp_status); + } else if (floatx80_is_signaling_nan(ST0, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST1 = floatx80_silence_nan(ST0, &env->fp_status); } else if (floatx80_is_signaling_nan(ST1, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST1 = floatx80_silence_nan(ST1, &env->fp_status); - } else if (floatx80_invalid_encoding(ST0, &env->fp_status) || - floatx80_invalid_encoding(ST1, &env->fp_status)) { - float_raise(float_flag_invalid, &env->fp_status); - ST1 = floatx80_default_nan(&env->fp_status); } else if (floatx80_is_any_nan(ST0)) { ST1 = ST0; } else if (floatx80_is_any_nan(ST1)) { diff --git a/target/i386/tcg/helper-tcg.h b/target/i386/tcg/helper-tcg.h index e41cbda407ae..f4b2ff740d54 100644 --- a/target/i386/tcg/helper-tcg.h +++ b/target/i386/tcg/helper-tcg.h @@ -31,8 +31,6 @@ # define TCG_PHYS_ADDR_BITS 36 #endif -QEMU_BUILD_BUG_ON(TCG_PHYS_ADDR_BITS > TARGET_PHYS_ADDR_SPACE_BITS); - /** * x86_cpu_do_interrupt: * @cpu: vCPU the interrupt is to be handled by. diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c index 7186517239cf..14210d569f7c 100644 --- a/target/i386/tcg/translate.c +++ b/target/i386/tcg/translate.c @@ -3501,7 +3501,7 @@ static void i386_tr_insn_start(DisasContextBase *dcbase, CPUState *cpu) if (tb_cflags(dcbase->tb) & CF_PCREL) { pc_arg &= ~TARGET_PAGE_MASK; } - tcg_gen_insn_start(pc_arg, dc->cc_op); + tcg_gen_insn_start(pc_arg, dc->cc_op, 0); } static void i386_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) diff --git a/target/i386/trace-events b/target/i386/trace-events index 51301673f0c4..b320f655eebb 100644 --- a/target/i386/trace-events +++ b/target/i386/trace-events @@ -14,3 +14,4 @@ kvm_sev_attestation_report(const char *mnonce, const char *data) "mnonce %s data kvm_sev_snp_launch_start(uint64_t policy, char *gosvw) "policy 0x%" PRIx64 " gosvw %s" kvm_sev_snp_launch_update(uint64_t src, uint64_t gpa, uint64_t len, const char *type) "src 0x%" PRIx64 " gpa 0x%" PRIx64 " len 0x%" PRIx64 " (%s page)" kvm_sev_snp_launch_finish(char *id_block, char *id_auth, char *host_data) "id_block %s id_auth %s host_data %s" +sev_handle_reset(void) "" diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c index 8210250dc3bc..4d5d3dbd243a 100644 --- a/target/i386/whpx/whpx-all.c +++ b/target/i386/whpx/whpx-all.c @@ -15,6 +15,7 @@ #include "gdbstub/helpers.h" #include "qemu/accel.h" #include "accel/accel-ops.h" +#include "system/memory.h" #include "system/whpx.h" #include "system/cpus.h" #include "system/runstate.h" @@ -36,10 +37,16 @@ #include "system/whpx-all.h" #include "system/whpx-common.h" +#include "emulate/x86_decode.h" +#include "emulate/x86_emu.h" +#include "emulate/x86_flags.h" +#include "emulate/x86_mmu.h" + #include -#include #define HYPERV_APIC_BUS_FREQUENCY (200000000ULL) +/* for kernel-irqchip=off */ +#define HV_X64_MSR_APIC_FREQUENCY 0x40000023 static const WHV_REGISTER_NAME whpx_register_names[] = { @@ -362,7 +369,7 @@ static uint64_t whpx_cr8_to_apic_tpr(uint64_t cr8) return cr8 << 4; } -void whpx_set_registers(CPUState *cpu, int level) +void whpx_set_registers(CPUState *cpu, WHPXStateLevel level) { struct whpx_state *whpx = &whpx_global; AccelCPUState *vcpu = cpu->accel; @@ -381,7 +388,7 @@ void whpx_set_registers(CPUState *cpu, int level) * Following MSRs have side effects on the guest or are too heavy for * runtime. Limit them to full state update. */ - if (level >= WHPX_SET_RESET_STATE) { + if (level >= WHPX_LEVEL_RESET_STATE) { whpx_set_tsc(cpu); } @@ -407,6 +414,7 @@ void whpx_set_registers(CPUState *cpu, int level) vcxt.values[idx++].Reg64 = env->eip; assert(whpx_register_names[idx] == WHvX64RegisterRflags); + lflags_to_rflags(env); vcxt.values[idx++].Reg64 = env->eflags; /* Translate 6+4 segment registers. HV and QEMU order matches */ @@ -416,118 +424,124 @@ void whpx_set_registers(CPUState *cpu, int level) } assert(idx == WHvX64RegisterLdtr); - vcxt.values[idx++].Segment = whpx_seg_q2h(&env->ldt, 0, 0); - - assert(idx == WHvX64RegisterTr); - vcxt.values[idx++].Segment = whpx_seg_q2h(&env->tr, 0, 0); - - assert(idx == WHvX64RegisterIdtr); - vcxt.values[idx].Table.Base = env->idt.base; - vcxt.values[idx].Table.Limit = env->idt.limit; - idx += 1; - - assert(idx == WHvX64RegisterGdtr); - vcxt.values[idx].Table.Base = env->gdt.base; - vcxt.values[idx].Table.Limit = env->gdt.limit; - idx += 1; - - /* CR0, 2, 3, 4, 8 */ - assert(whpx_register_names[idx] == WHvX64RegisterCr0); - vcxt.values[idx++].Reg64 = env->cr[0]; - assert(whpx_register_names[idx] == WHvX64RegisterCr2); - vcxt.values[idx++].Reg64 = env->cr[2]; - assert(whpx_register_names[idx] == WHvX64RegisterCr3); - vcxt.values[idx++].Reg64 = env->cr[3]; - assert(whpx_register_names[idx] == WHvX64RegisterCr4); - vcxt.values[idx++].Reg64 = env->cr[4]; - assert(whpx_register_names[idx] == WHvX64RegisterCr8); - vcxt.values[idx++].Reg64 = vcpu->tpr; - - /* 8 Debug Registers - Skipped */ - /* - * Extended control registers needs to be handled separately depending - * on whether xsave is supported/enabled or not. + * Skip those registers for synchronisation after MMIO accesses + * as they're not going to be modified in that case. */ - whpx_set_xcrs(cpu); - - /* 16 XMM registers */ - assert(whpx_register_names[idx] == WHvX64RegisterXmm0); - idx_next = idx + 16; - for (i = 0; i < sizeof(env->xmm_regs) / sizeof(ZMMReg); i += 1, idx += 1) { - vcxt.values[idx].Reg128.Low64 = env->xmm_regs[i].ZMM_Q(0); - vcxt.values[idx].Reg128.High64 = env->xmm_regs[i].ZMM_Q(1); - } - idx = idx_next; - - /* 8 FP registers */ - assert(whpx_register_names[idx] == WHvX64RegisterFpMmx0); - for (i = 0; i < 8; i += 1, idx += 1) { - vcxt.values[idx].Fp.AsUINT128.Low64 = env->fpregs[i].mmx.MMX_Q(0); - /* vcxt.values[idx].Fp.AsUINT128.High64 = - env->fpregs[i].mmx.MMX_Q(1); - */ - } - - /* FP control status register */ - assert(whpx_register_names[idx] == WHvX64RegisterFpControlStatus); - vcxt.values[idx].FpControlStatus.FpControl = env->fpuc; - vcxt.values[idx].FpControlStatus.FpStatus = - (env->fpus & ~0x3800) | (env->fpstt & 0x7) << 11; - vcxt.values[idx].FpControlStatus.FpTag = 0; - for (i = 0; i < 8; ++i) { - vcxt.values[idx].FpControlStatus.FpTag |= (!env->fptags[i]) << i; - } - vcxt.values[idx].FpControlStatus.Reserved = 0; - vcxt.values[idx].FpControlStatus.LastFpOp = env->fpop; - vcxt.values[idx].FpControlStatus.LastFpRip = env->fpip; - idx += 1; + if (level > WHPX_LEVEL_FAST_RUNTIME_STATE) { + vcxt.values[idx++].Segment = whpx_seg_q2h(&env->ldt, 0, 0); + + assert(idx == WHvX64RegisterTr); + vcxt.values[idx++].Segment = whpx_seg_q2h(&env->tr, 0, 0); + + assert(idx == WHvX64RegisterIdtr); + vcxt.values[idx].Table.Base = env->idt.base; + vcxt.values[idx].Table.Limit = env->idt.limit; + idx += 1; + + assert(idx == WHvX64RegisterGdtr); + vcxt.values[idx].Table.Base = env->gdt.base; + vcxt.values[idx].Table.Limit = env->gdt.limit; + idx += 1; + + /* CR0, 2, 3, 4, 8 */ + assert(whpx_register_names[idx] == WHvX64RegisterCr0); + vcxt.values[idx++].Reg64 = env->cr[0]; + assert(whpx_register_names[idx] == WHvX64RegisterCr2); + vcxt.values[idx++].Reg64 = env->cr[2]; + assert(whpx_register_names[idx] == WHvX64RegisterCr3); + vcxt.values[idx++].Reg64 = env->cr[3]; + assert(whpx_register_names[idx] == WHvX64RegisterCr4); + vcxt.values[idx++].Reg64 = env->cr[4]; + assert(whpx_register_names[idx] == WHvX64RegisterCr8); + vcxt.values[idx++].Reg64 = vcpu->tpr; + + /* 8 Debug Registers - Skipped */ - /* XMM control status register */ - assert(whpx_register_names[idx] == WHvX64RegisterXmmControlStatus); - vcxt.values[idx].XmmControlStatus.LastFpRdp = 0; - vcxt.values[idx].XmmControlStatus.XmmStatusControl = env->mxcsr; - vcxt.values[idx].XmmControlStatus.XmmStatusControlMask = 0x0000ffff; - idx += 1; + /* + * Extended control registers needs to be handled separately depending + * on whether xsave is supported/enabled or not. + */ + whpx_set_xcrs(cpu); + + /* 16 XMM registers */ + assert(whpx_register_names[idx] == WHvX64RegisterXmm0); + idx_next = idx + 16; + for (i = 0; i < sizeof(env->xmm_regs) / sizeof(ZMMReg); i += 1, idx += 1) { + vcxt.values[idx].Reg128.Low64 = env->xmm_regs[i].ZMM_Q(0); + vcxt.values[idx].Reg128.High64 = env->xmm_regs[i].ZMM_Q(1); + } + idx = idx_next; + + /* 8 FP registers */ + assert(whpx_register_names[idx] == WHvX64RegisterFpMmx0); + for (i = 0; i < 8; i += 1, idx += 1) { + vcxt.values[idx].Fp.AsUINT128.Low64 = env->fpregs[i].mmx.MMX_Q(0); + /* vcxt.values[idx].Fp.AsUINT128.High64 = + env->fpregs[i].mmx.MMX_Q(1); + */ + } - /* MSRs */ - assert(whpx_register_names[idx] == WHvX64RegisterEfer); - vcxt.values[idx++].Reg64 = env->efer; + /* FP control status register */ + assert(whpx_register_names[idx] == WHvX64RegisterFpControlStatus); + vcxt.values[idx].FpControlStatus.FpControl = env->fpuc; + vcxt.values[idx].FpControlStatus.FpStatus = + (env->fpus & ~0x3800) | (env->fpstt & 0x7) << 11; + vcxt.values[idx].FpControlStatus.FpTag = 0; + for (i = 0; i < 8; ++i) { + vcxt.values[idx].FpControlStatus.FpTag |= (!env->fptags[i]) << i; + } + vcxt.values[idx].FpControlStatus.Reserved = 0; + vcxt.values[idx].FpControlStatus.LastFpOp = env->fpop; + vcxt.values[idx].FpControlStatus.LastFpRip = env->fpip; + idx += 1; + + /* XMM control status register */ + assert(whpx_register_names[idx] == WHvX64RegisterXmmControlStatus); + vcxt.values[idx].XmmControlStatus.LastFpRdp = 0; + vcxt.values[idx].XmmControlStatus.XmmStatusControl = env->mxcsr; + vcxt.values[idx].XmmControlStatus.XmmStatusControlMask = 0x0000ffff; + idx += 1; + + /* MSRs */ + assert(whpx_register_names[idx] == WHvX64RegisterEfer); + vcxt.values[idx++].Reg64 = env->efer; #ifdef TARGET_X86_64 - assert(whpx_register_names[idx] == WHvX64RegisterKernelGsBase); - vcxt.values[idx++].Reg64 = env->kernelgsbase; + assert(whpx_register_names[idx] == WHvX64RegisterKernelGsBase); + vcxt.values[idx++].Reg64 = env->kernelgsbase; #endif - assert(whpx_register_names[idx] == WHvX64RegisterApicBase); - vcxt.values[idx++].Reg64 = vcpu->apic_base; + assert(whpx_register_names[idx] == WHvX64RegisterApicBase); + vcxt.values[idx++].Reg64 = vcpu->apic_base; - /* WHvX64RegisterPat - Skipped */ + /* WHvX64RegisterPat - Skipped */ - assert(whpx_register_names[idx] == WHvX64RegisterSysenterCs); - vcxt.values[idx++].Reg64 = env->sysenter_cs; - assert(whpx_register_names[idx] == WHvX64RegisterSysenterEip); - vcxt.values[idx++].Reg64 = env->sysenter_eip; - assert(whpx_register_names[idx] == WHvX64RegisterSysenterEsp); - vcxt.values[idx++].Reg64 = env->sysenter_esp; - assert(whpx_register_names[idx] == WHvX64RegisterStar); - vcxt.values[idx++].Reg64 = env->star; + assert(whpx_register_names[idx] == WHvX64RegisterSysenterCs); + vcxt.values[idx++].Reg64 = env->sysenter_cs; + assert(whpx_register_names[idx] == WHvX64RegisterSysenterEip); + vcxt.values[idx++].Reg64 = env->sysenter_eip; + assert(whpx_register_names[idx] == WHvX64RegisterSysenterEsp); + vcxt.values[idx++].Reg64 = env->sysenter_esp; + assert(whpx_register_names[idx] == WHvX64RegisterStar); + vcxt.values[idx++].Reg64 = env->star; #ifdef TARGET_X86_64 - assert(whpx_register_names[idx] == WHvX64RegisterLstar); - vcxt.values[idx++].Reg64 = env->lstar; - assert(whpx_register_names[idx] == WHvX64RegisterCstar); - vcxt.values[idx++].Reg64 = env->cstar; - assert(whpx_register_names[idx] == WHvX64RegisterSfmask); - vcxt.values[idx++].Reg64 = env->fmask; + assert(whpx_register_names[idx] == WHvX64RegisterLstar); + vcxt.values[idx++].Reg64 = env->lstar; + assert(whpx_register_names[idx] == WHvX64RegisterCstar); + vcxt.values[idx++].Reg64 = env->cstar; + assert(whpx_register_names[idx] == WHvX64RegisterSfmask); + vcxt.values[idx++].Reg64 = env->fmask; #endif - /* Interrupt / Event Registers - Skipped */ + /* Interrupt / Event Registers - Skipped */ - assert(idx == RTL_NUMBER_OF(whpx_register_names)); + assert(idx == RTL_NUMBER_OF(whpx_register_names)); + } hr = whp_dispatch.WHvSetVirtualProcessorRegisters( whpx->partition, cpu->cpu_index, whpx_register_names, - RTL_NUMBER_OF(whpx_register_names), + idx, &vcxt.values[0]); if (FAILED(hr)) { @@ -577,7 +591,7 @@ static void whpx_get_xcrs(CPUState *cpu) cpu_env(cpu)->xcr0 = xcr0.Reg64; } -void whpx_get_registers(CPUState *cpu) +void whpx_get_registers(CPUState *cpu, WHPXStateLevel level) { struct whpx_state *whpx = &whpx_global; AccelCPUState *vcpu = cpu->accel; @@ -607,7 +621,7 @@ void whpx_get_registers(CPUState *cpu) hr); } - if (whpx_irqchip_in_kernel()) { + if (level > WHPX_LEVEL_FAST_RUNTIME_STATE && whpx_irqchip_in_kernel()) { /* * Fetch the TPR value from the emulated APIC. It may get overwritten * below with the value from CR8 returned by @@ -632,6 +646,7 @@ void whpx_get_registers(CPUState *cpu) env->eip = vcxt.values[idx++].Reg64; assert(whpx_register_names[idx] == WHvX64RegisterRflags); env->eflags = vcxt.values[idx++].Reg64; + rflags_to_lflags(env); /* Translate 6+4 segment registers. HV and QEMU order matches */ assert(idx == WHvX64RegisterEs); @@ -663,7 +678,7 @@ void whpx_get_registers(CPUState *cpu) env->cr[4] = vcxt.values[idx++].Reg64; assert(whpx_register_names[idx] == WHvX64RegisterCr8); tpr = vcxt.values[idx++].Reg64; - if (tpr != vcpu->tpr) { + if (level > WHPX_LEVEL_FAST_RUNTIME_STATE && tpr != vcpu->tpr) { vcpu->tpr = tpr; cpu_set_apic_tpr(x86_cpu->apic_state, whpx_cr8_to_apic_tpr(tpr)); } @@ -749,165 +764,133 @@ void whpx_get_registers(CPUState *cpu) assert(idx == RTL_NUMBER_OF(whpx_register_names)); - if (whpx_irqchip_in_kernel()) { + if (level > WHPX_LEVEL_FAST_RUNTIME_STATE && whpx_irqchip_in_kernel()) { whpx_apic_get(x86_cpu->apic_state); } x86_update_hflags(env); } -static HRESULT CALLBACK whpx_emu_ioport_callback( - void *ctx, - WHV_EMULATOR_IO_ACCESS_INFO *IoAccess) +static int emulate_instruction(CPUState *cpu, const uint8_t *insn_bytes, size_t insn_len) { - MemTxAttrs attrs = { 0 }; - address_space_rw(&address_space_io, IoAccess->Port, attrs, - &IoAccess->Data, IoAccess->AccessSize, - IoAccess->Direction); - return S_OK; -} + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + struct x86_decode decode = { 0 }; + x86_insn_stream stream = { .bytes = insn_bytes, .len = insn_len }; -static HRESULT CALLBACK whpx_emu_mmio_callback( - void *ctx, - WHV_EMULATOR_MEMORY_ACCESS_INFO *ma) -{ - CPUState *cs = (CPUState *)ctx; - AddressSpace *as = cpu_addressspace(cs, MEMTXATTRS_UNSPECIFIED); + whpx_get_registers(cpu, WHPX_LEVEL_FAST_RUNTIME_STATE); + decode_instruction_stream(env, &decode, &stream); + exec_instruction(env, &decode); + whpx_set_registers(cpu, WHPX_LEVEL_FAST_RUNTIME_STATE); - address_space_rw(as, ma->GpaAddress, MEMTXATTRS_UNSPECIFIED, - ma->Data, ma->AccessSize, ma->Direction); - return S_OK; + return 0; } -static HRESULT CALLBACK whpx_emu_getreg_callback( - void *ctx, - const WHV_REGISTER_NAME *RegisterNames, - UINT32 RegisterCount, - WHV_REGISTER_VALUE *RegisterValues) +static int whpx_handle_mmio(CPUState *cpu, WHV_RUN_VP_EXIT_CONTEXT *exit_ctx) { - HRESULT hr; - struct whpx_state *whpx = &whpx_global; - CPUState *cpu = (CPUState *)ctx; + WHV_MEMORY_ACCESS_CONTEXT *ctx = &exit_ctx->MemoryAccess; + int ret; - hr = whp_dispatch.WHvGetVirtualProcessorRegisters( - whpx->partition, cpu->cpu_index, - RegisterNames, RegisterCount, - RegisterValues); - if (FAILED(hr)) { - error_report("WHPX: Failed to get virtual processor registers," - " hr=%08lx", hr); + ret = emulate_instruction(cpu, ctx->InstructionBytes, ctx->InstructionByteCount); + if (ret < 0) { + error_report("failed to emulate mmio"); + return -1; } - return hr; + return 0; } -static HRESULT CALLBACK whpx_emu_setreg_callback( - void *ctx, - const WHV_REGISTER_NAME *RegisterNames, - UINT32 RegisterCount, - const WHV_REGISTER_VALUE *RegisterValues) +static void handle_io(CPUState *env, uint16_t port, void *buffer, + int direction, int size, int count) { - HRESULT hr; - struct whpx_state *whpx = &whpx_global; - CPUState *cpu = (CPUState *)ctx; + int i; + uint8_t *ptr = buffer; - hr = whp_dispatch.WHvSetVirtualProcessorRegisters( - whpx->partition, cpu->cpu_index, - RegisterNames, RegisterCount, - RegisterValues); - if (FAILED(hr)) { - error_report("WHPX: Failed to set virtual processor registers," - " hr=%08lx", hr); + for (i = 0; i < count; i++) { + address_space_rw(&address_space_io, port, MEMTXATTRS_UNSPECIFIED, + ptr, size, + direction); + ptr += size; } - - /* - * The emulator just successfully wrote the register state. We clear the - * dirty state so we avoid the double write on resume of the VP. - */ - cpu->vcpu_dirty = false; - - return hr; } -static HRESULT CALLBACK whpx_emu_translate_callback( - void *ctx, - WHV_GUEST_VIRTUAL_ADDRESS Gva, - WHV_TRANSLATE_GVA_FLAGS TranslateFlags, - WHV_TRANSLATE_GVA_RESULT_CODE *TranslationResult, - WHV_GUEST_PHYSICAL_ADDRESS *Gpa) +static void whpx_bump_rip(CPUState *cpu, WHV_RUN_VP_EXIT_CONTEXT *exit_ctx) { - HRESULT hr; - struct whpx_state *whpx = &whpx_global; - CPUState *cpu = (CPUState *)ctx; - WHV_TRANSLATE_GVA_RESULT res; - - hr = whp_dispatch.WHvTranslateGva(whpx->partition, cpu->cpu_index, - Gva, TranslateFlags, &res, Gpa); - if (FAILED(hr)) { - error_report("WHPX: Failed to translate GVA, hr=%08lx", hr); - } else { - *TranslationResult = res.ResultCode; - } - - return hr; + WHV_REGISTER_VALUE reg; + whpx_get_reg(cpu, WHvX64RegisterRip, ®); + reg.Reg64 = exit_ctx->VpContext.Rip + exit_ctx->VpContext.InstructionLength; + whpx_set_reg(cpu, WHvX64RegisterRip, reg); } -static const WHV_EMULATOR_CALLBACKS whpx_emu_callbacks = { - .Size = sizeof(WHV_EMULATOR_CALLBACKS), - .WHvEmulatorIoPortCallback = whpx_emu_ioport_callback, - .WHvEmulatorMemoryCallback = whpx_emu_mmio_callback, - .WHvEmulatorGetVirtualProcessorRegisters = whpx_emu_getreg_callback, - .WHvEmulatorSetVirtualProcessorRegisters = whpx_emu_setreg_callback, - .WHvEmulatorTranslateGvaPage = whpx_emu_translate_callback, -}; - -static int whpx_handle_mmio(CPUState *cpu, WHV_MEMORY_ACCESS_CONTEXT *ctx) +static int whpx_handle_portio(CPUState *cpu, + WHV_RUN_VP_EXIT_CONTEXT *exit_ctx) { - HRESULT hr; - AccelCPUState *vcpu = cpu->accel; - WHV_EMULATOR_STATUS emu_status; - - hr = whp_dispatch.WHvEmulatorTryMmioEmulation( - vcpu->emulator, cpu, - &vcpu->exit_ctx.VpContext, ctx, - &emu_status); - if (FAILED(hr)) { - error_report("WHPX: Failed to parse MMIO access, hr=%08lx", hr); - return -1; - } + WHV_X64_IO_PORT_ACCESS_CONTEXT *ctx = &exit_ctx->IoPortAccess; + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + int ret; - if (!emu_status.EmulationSuccessful) { - error_report("WHPX: Failed to emulate MMIO access with" - " EmulatorReturnStatus: %u", emu_status.AsUINT32); + if (!ctx->AccessInfo.StringOp && !ctx->AccessInfo.IsWrite) { + uint64_t val = 0; + WHV_REGISTER_VALUE reg; + + whpx_get_reg(cpu, WHvX64RegisterRax, ®); + handle_io(cpu, ctx->PortNumber, &val, 0, ctx->AccessInfo.AccessSize, 1); + if (ctx->AccessInfo.AccessSize == 1) { + reg.Reg8 = val; + } else if (ctx->AccessInfo.AccessSize == 2) { + reg.Reg16 = val; + } else if (ctx->AccessInfo.AccessSize == 4) { + reg.Reg64 = (uint32_t)val; + } else { + reg.Reg64 = (uint64_t)val; + } + whpx_bump_rip(cpu, exit_ctx); + whpx_set_reg(cpu, WHvX64RegisterRax, reg); + return 0; + } else if (!ctx->AccessInfo.StringOp && ctx->AccessInfo.IsWrite) { + RAX(env) = ctx->Rax; + handle_io(cpu, ctx->PortNumber, &RAX(env), 1, ctx->AccessInfo.AccessSize, 1); + whpx_bump_rip(cpu, exit_ctx); + return 0; + } + + ret = emulate_instruction(cpu, ctx->InstructionBytes, exit_ctx->VpContext.InstructionLength); + if (ret < 0) { + error_report("failed to emulate I/O port access"); return -1; } return 0; } -static int whpx_handle_portio(CPUState *cpu, - WHV_X64_IO_PORT_ACCESS_CONTEXT *ctx) +static void read_segment_descriptor(CPUState *cpu, + struct x86_segment_descriptor *desc, + enum X86Seg seg_idx) { - HRESULT hr; - AccelCPUState *vcpu = cpu->accel; - WHV_EMULATOR_STATUS emu_status; + bool ret; + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + SegmentCache *seg = &env->segs[seg_idx]; + x86_segment_selector sel = { .sel = seg->selector & 0xFFFF }; - hr = whp_dispatch.WHvEmulatorTryIoEmulation( - vcpu->emulator, cpu, - &vcpu->exit_ctx.VpContext, ctx, - &emu_status); - if (FAILED(hr)) { - error_report("WHPX: Failed to parse PortIO access, hr=%08lx", hr); - return -1; + ret = x86_read_segment_descriptor(cpu, desc, sel); + if (ret == false) { + error_report("failed to read segment descriptor"); + abort(); } +} - if (!emu_status.EmulationSuccessful) { - error_report("WHPX: Failed to emulate PortIO access with" - " EmulatorReturnStatus: %u", emu_status.AsUINT32); - return -1; - } - return 0; +static const struct x86_emul_ops whpx_x86_emul_ops = { + .read_segment_descriptor = read_segment_descriptor, + .handle_io = handle_io +}; + +static void whpx_init_emu(void) +{ + init_decoder(); + init_emu(&whpx_x86_emul_ops); } /* @@ -1272,6 +1255,18 @@ void whpx_apply_breakpoints( } } +bool whpx_arch_supports_guest_debug(void) +{ + return true; +} + +void whpx_arch_destroy_vcpu(CPUState *cpu) +{ + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + g_free(env->emu_mmio_buf); +} + /* Returns the address of the next instruction that is about to be executed. */ static vaddr whpx_vcpu_get_pc(CPUState *cpu, bool exit_context_valid) { @@ -1330,6 +1325,16 @@ static int whpx_handle_halt(CPUState *cpu) return ret; } +static void whpx_vcpu_kick_out_of_hlt(CPUState *cpu) +{ + WHV_REGISTER_VALUE reg; + whpx_get_reg(cpu, WHvRegisterInternalActivityState, ®); + if (reg.InternalActivity.HaltSuspend) { + reg.InternalActivity.HaltSuspend = 0; + whpx_set_reg(cpu, WHvRegisterInternalActivityState, reg); + } +} + static void whpx_vcpu_pre_run(CPUState *cpu) { HRESULT hr; @@ -1413,6 +1418,17 @@ static void whpx_vcpu_pre_run(CPUState *cpu) .Vector = irq, }; reg_count += 1; + /* + * When the Hyper-V APIC is enabled, to get out of HLT we + * either have to request an interrupt or manually get it away + * from HLT. + * + * We also manually do inject some interrupts via WHvRegisterPendingEvent + * instead of WHVRequestInterrupt, which does not reset the HLT state. + */ + if (whpx_irqchip_in_kernel()) { + whpx_vcpu_kick_out_of_hlt(cpu); + } } } @@ -1475,6 +1491,7 @@ static void whpx_vcpu_post_run(CPUState *cpu) !vcpu->exit_ctx.VpContext.ExecutionState.InterruptShadow; } + static void whpx_vcpu_process_async_events(CPUState *cpu) { X86CPU *x86_cpu = X86_CPU(cpu); @@ -1513,6 +1530,26 @@ static void whpx_vcpu_process_async_events(CPUState *cpu) } } +static void whpx_inject_exceptions(CPUState* cpu) +{ + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + + if (env->exception_injected) { + env->exception_injected = 0; + WHV_REGISTER_VALUE reg = {}; + reg.ExceptionEvent.EventPending = 1; + reg.ExceptionEvent.EventType = WHvX64PendingEventException; + reg.ExceptionEvent.DeliverErrorCode = 1; + reg.ExceptionEvent.Vector = env->exception_nr; + reg.ExceptionEvent.ErrorCode = env->error_code; + if (env->exception_nr == EXCP0E_PAGE) { + reg.ExceptionEvent.ExceptionParameter = env->cr[2]; + } + whpx_set_reg(cpu, WHvRegisterPendingEvent, reg); + } +} + int whpx_vcpu_run(CPUState *cpu) { HRESULT hr; @@ -1590,7 +1627,7 @@ int whpx_vcpu_run(CPUState *cpu) do { if (cpu->vcpu_dirty) { - whpx_set_registers(cpu, WHPX_SET_RUNTIME_STATE); + whpx_set_registers(cpu, WHPX_LEVEL_RUNTIME_STATE); cpu->vcpu_dirty = false; } @@ -1607,6 +1644,8 @@ int whpx_vcpu_run(CPUState *cpu) whpx_vcpu_configure_single_stepping(cpu, true, NULL); } + whpx_inject_exceptions(cpu); + hr = whp_dispatch.WHvRunVirtualProcessor( whpx->partition, cpu->cpu_index, &vcpu->exit_ctx, sizeof(vcpu->exit_ctx)); @@ -1628,11 +1667,11 @@ int whpx_vcpu_run(CPUState *cpu) switch (vcpu->exit_ctx.ExitReason) { case WHvRunVpExitReasonMemoryAccess: - ret = whpx_handle_mmio(cpu, &vcpu->exit_ctx.MemoryAccess); + ret = whpx_handle_mmio(cpu, &vcpu->exit_ctx); break; case WHvRunVpExitReasonX64IoPortAccess: - ret = whpx_handle_portio(cpu, &vcpu->exit_ctx.IoPortAccess); + ret = whpx_handle_portio(cpu, &vcpu->exit_ctx); break; case WHvRunVpExitReasonX64InterruptWindow: @@ -1648,106 +1687,11 @@ int whpx_vcpu_run(CPUState *cpu) case WHvRunVpExitReasonX64Halt: /* - * WARNING: as of build 19043.1526 (21H1), this exit reason is no - * longer used. + * Used for kernel-irqchip=off */ ret = whpx_handle_halt(cpu); break; - case WHvRunVpExitReasonX64ApicInitSipiTrap: { - WHV_INTERRUPT_CONTROL ipi = {0}; - uint64_t icr = vcpu->exit_ctx.ApicInitSipi.ApicIcr; - uint32_t delivery_mode = - (icr & APIC_ICR_DELIV_MOD) >> APIC_ICR_DELIV_MOD_SHIFT; - int dest_shorthand = - (icr & APIC_ICR_DEST_SHORT) >> APIC_ICR_DEST_SHORT_SHIFT; - bool broadcast = false; - bool include_self = false; - uint32_t i; - - /* We only registered for INIT and SIPI exits. */ - if ((delivery_mode != APIC_DM_INIT) && - (delivery_mode != APIC_DM_SIPI)) { - error_report( - "WHPX: Unexpected APIC exit that is not a INIT or SIPI"); - break; - } - - if (delivery_mode == APIC_DM_INIT) { - ipi.Type = WHvX64InterruptTypeInit; - } else { - ipi.Type = WHvX64InterruptTypeSipi; - } - - ipi.DestinationMode = - ((icr & APIC_ICR_DEST_MOD) >> APIC_ICR_DEST_MOD_SHIFT) ? - WHvX64InterruptDestinationModeLogical : - WHvX64InterruptDestinationModePhysical; - - ipi.TriggerMode = - ((icr & APIC_ICR_TRIGGER_MOD) >> APIC_ICR_TRIGGER_MOD_SHIFT) ? - WHvX64InterruptTriggerModeLevel : - WHvX64InterruptTriggerModeEdge; - - ipi.Vector = icr & APIC_VECTOR_MASK; - switch (dest_shorthand) { - /* no shorthand. Bits 56-63 contain the destination. */ - case 0: - ipi.Destination = (icr >> 56) & APIC_VECTOR_MASK; - hr = whp_dispatch.WHvRequestInterrupt(whpx->partition, - &ipi, sizeof(ipi)); - if (FAILED(hr)) { - error_report("WHPX: Failed to request interrupt hr=%08lx", - hr); - } - - break; - - /* self */ - case 1: - include_self = true; - break; - - /* broadcast, including self */ - case 2: - broadcast = true; - include_self = true; - break; - - /* broadcast, excluding self */ - case 3: - broadcast = true; - break; - } - - if (!broadcast && !include_self) { - break; - } - - for (i = 0; i <= max_vcpu_index; i++) { - if (i == cpu->cpu_index && !include_self) { - continue; - } - - /* - * Assuming that APIC Ids are identity mapped since - * WHvX64RegisterApicId & WHvX64RegisterInitialApicId registers - * are not handled yet and the hypervisor doesn't allow the - * guest to modify the APIC ID. - */ - ipi.Destination = i; - hr = whp_dispatch.WHvRequestInterrupt(whpx->partition, - &ipi, sizeof(ipi)); - if (FAILED(hr)) { - error_report( - "WHPX: Failed to request SIPI for %d, hr=%08lx", - i, hr); - } - } - - break; - } - case WHvRunVpExitReasonCanceled: if (exclusive_step_mode != WHPX_STEP_NONE) { /* @@ -1766,6 +1710,7 @@ int whpx_vcpu_run(CPUState *cpu) WHV_REGISTER_VALUE reg_values[3] = {0}; WHV_REGISTER_NAME reg_names[3]; UINT32 reg_count; + bool is_known_msr = 0; reg_names[0] = WHvX64RegisterRip; reg_names[1] = WHvX64RegisterRax; @@ -1775,6 +1720,12 @@ int whpx_vcpu_run(CPUState *cpu) vcpu->exit_ctx.VpContext.Rip + vcpu->exit_ctx.VpContext.InstructionLength; + if (vcpu->exit_ctx.MsrAccess.MsrNumber == HV_X64_MSR_APIC_FREQUENCY + && !vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite + && !whpx_irqchip_in_kernel()) { + is_known_msr = 1; + reg_values[1].Reg32 = (uint32_t)X86_CPU(cpu)->env.apic_bus_freq; + } /* * For all unsupported MSR access we: * ignore writes @@ -1783,6 +1734,11 @@ int whpx_vcpu_run(CPUState *cpu) reg_count = vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite ? 1 : 3; + if (!is_known_msr) { + warn_report("WHPX: Unsupported MSR access (0x%x), IsWrite=%i", + vcpu->exit_ctx.MsrAccess.MsrNumber, vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite); + } + hr = whp_dispatch.WHvSetVirtualProcessorRegisters( whpx->partition, cpu->cpu_index, @@ -1796,77 +1752,8 @@ int whpx_vcpu_run(CPUState *cpu) ret = 0; break; } - case WHvRunVpExitReasonX64Cpuid: { - WHV_REGISTER_VALUE reg_values[5]; - WHV_REGISTER_NAME reg_names[5]; - UINT32 reg_count = 5; - UINT64 cpuid_fn, rip = 0, rax = 0, rcx = 0, rdx = 0, rbx = 0; - X86CPU *x86_cpu = X86_CPU(cpu); - CPUX86State *env = &x86_cpu->env; - - memset(reg_values, 0, sizeof(reg_values)); - - rip = vcpu->exit_ctx.VpContext.Rip + - vcpu->exit_ctx.VpContext.InstructionLength; - cpuid_fn = vcpu->exit_ctx.CpuidAccess.Rax; - - /* - * Ideally, these should be supplied to the hypervisor during VCPU - * initialization and it should be able to satisfy this request. - * But, currently, WHPX doesn't support setting CPUID values in the - * hypervisor once the partition has been setup, which is too late - * since VCPUs are realized later. For now, use the values from - * QEMU to satisfy these requests, until WHPX adds support for - * being able to set these values in the hypervisor at runtime. - */ - cpu_x86_cpuid(env, cpuid_fn, 0, (UINT32 *)&rax, (UINT32 *)&rbx, - (UINT32 *)&rcx, (UINT32 *)&rdx); - switch (cpuid_fn) { - case 0x40000000: - /* Expose the vmware cpu frequency cpuid leaf */ - rax = 0x40000010; - rbx = rcx = rdx = 0; - break; - - case 0x40000010: - rax = env->tsc_khz; - rbx = env->apic_bus_freq / 1000; /* Hz to KHz */ - rcx = rdx = 0; - break; - - case 0x80000001: - /* Remove any support of OSVW */ - rcx &= ~CPUID_EXT3_OSVW; - break; - } - - reg_names[0] = WHvX64RegisterRip; - reg_names[1] = WHvX64RegisterRax; - reg_names[2] = WHvX64RegisterRcx; - reg_names[3] = WHvX64RegisterRdx; - reg_names[4] = WHvX64RegisterRbx; - - reg_values[0].Reg64 = rip; - reg_values[1].Reg64 = rax; - reg_values[2].Reg64 = rcx; - reg_values[3].Reg64 = rdx; - reg_values[4].Reg64 = rbx; - - hr = whp_dispatch.WHvSetVirtualProcessorRegisters( - whpx->partition, cpu->cpu_index, - reg_names, - reg_count, - reg_values); - - if (FAILED(hr)) { - error_report("WHPX: Failed to set CpuidAccess state registers," - " hr=%08lx", hr); - } - ret = 0; - break; - } case WHvRunVpExitReasonException: - whpx_get_registers(cpu); + whpx_get_registers(cpu, WHPX_LEVEL_FULL_STATE); if ((vcpu->exit_ctx.VpException.ExceptionType == WHvX64ExceptionTypeDebugTrapOrFault) && @@ -1898,7 +1785,7 @@ int whpx_vcpu_run(CPUState *cpu) default: error_report("WHPX: Unexpected VP exit code %d", vcpu->exit_ctx.ExitReason); - whpx_get_registers(cpu); + whpx_get_registers(cpu, WHPX_LEVEL_FULL_STATE); bql_lock(); qemu_system_guest_panicked(cpu_get_crash_info(cpu)); bql_unlock(); @@ -1979,22 +1866,11 @@ int whpx_init_vcpu(CPUState *cpu) vcpu = g_new0(AccelCPUState, 1); - hr = whp_dispatch.WHvEmulatorCreateEmulator( - &whpx_emu_callbacks, - &vcpu->emulator); - if (FAILED(hr)) { - error_report("WHPX: Failed to setup instruction completion support," - " hr=%08lx", hr); - ret = -EINVAL; - goto error; - } - hr = whp_dispatch.WHvCreateVirtualProcessor( whpx->partition, cpu->cpu_index, 0); if (FAILED(hr)) { error_report("WHPX: Failed to create a virtual processor," " hr=%08lx", hr); - whp_dispatch.WHvEmulatorDestroyEmulator(vcpu->emulator); ret = -EINVAL; goto error; } @@ -2029,25 +1905,9 @@ int whpx_init_vcpu(CPUState *cpu) } } - /* - * If the vmware cpuid frequency leaf option is set, and we have a valid - * tsc value, trap the corresponding cpuid's. - */ - if (x86_cpu->vmware_cpuid_freq && env->tsc_khz) { - UINT32 cpuidExitList[] = {1, 0x80000001, 0x40000000, 0x40000010}; - - hr = whp_dispatch.WHvSetPartitionProperty( - whpx->partition, - WHvPartitionPropertyCodeCpuidExitList, - cpuidExitList, - RTL_NUMBER_OF(cpuidExitList) * sizeof(UINT32)); - - if (FAILED(hr)) { - error_report("WHPX: Failed to set partition CpuidExitList hr=%08lx", - hr); - ret = -EINVAL; - goto error; - } + /* When not using the Hyper-V APIC, the frequency is 1 GHz */ + if (!whpx_irqchip_in_kernel()) { + env->apic_bus_freq = 1000000000; } vcpu->interruptable = true; @@ -2056,6 +1916,8 @@ int whpx_init_vcpu(CPUState *cpu) max_vcpu_index = max(max_vcpu_index, cpu->cpu_index); qemu_add_vm_change_state_handler(whpx_cpu_update_state, env); + env->emu_mmio_buf = g_new(char, 4096); + return 0; error: @@ -2083,8 +1945,9 @@ int whpx_accel_init(AccelState *as, MachineState *ms) WHV_CAPABILITY whpx_cap; UINT32 whpx_cap_size; WHV_PARTITION_PROPERTY prop; - UINT32 cpuidExitList[] = {1, 0x80000001}; WHV_CAPABILITY_FEATURES features = {0}; + WHV_PROCESSOR_FEATURES_BANKS processor_features; + WHV_PROCESSOR_PERFMON_FEATURES perfmon_features; whpx = &whpx_global; @@ -2172,8 +2035,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms) if (whpx->kernel_irqchip_allowed && features.LocalApicEmulation && whp_dispatch.WHvSetVirtualProcessorInterruptControllerState2) { WHV_X64_LOCAL_APIC_EMULATION_MODE mode = - WHvX64LocalApicEmulationModeXApic; - printf("WHPX: setting APIC emulation mode in the hypervisor\n"); + WHvX64LocalApicEmulationModeX2Apic; hr = whp_dispatch.WHvSetPartitionProperty( whpx->partition, WHvPartitionPropertyCodeLocalApicEmulationMode, @@ -2191,35 +2053,116 @@ int whpx_accel_init(AccelState *as, MachineState *ms) } } - /* Register for MSR and CPUID exits */ - memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY)); - prop.ExtendedVmExits.X64MsrExit = 1; - prop.ExtendedVmExits.X64CpuidExit = 1; - prop.ExtendedVmExits.ExceptionExit = 1; - if (whpx_irqchip_in_kernel()) { - prop.ExtendedVmExits.X64ApicInitSipiExitTrap = 1; + /* Set all the supported features, to follow the MSHV example */ + memset(&processor_features, 0, sizeof(WHV_PROCESSOR_FEATURES_BANKS)); + processor_features.BanksCount = 2; + + hr = whp_dispatch.WHvGetCapability( + WHvCapabilityCodeProcessorFeaturesBanks, &processor_features, + sizeof(WHV_PROCESSOR_FEATURES_BANKS), &whpx_cap_size); + if (FAILED(hr)) { + error_report("WHPX: Failed to get processor features, hr=%08lx", hr); + ret = -ENOSPC; + goto error; } - hr = whp_dispatch.WHvSetPartitionProperty( + if (whpx_irqchip_in_kernel() && processor_features.Bank1.NestedVirtSupport) { + memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY)); + prop.NestedVirtualization = 1; + hr = whp_dispatch.WHvSetPartitionProperty( whpx->partition, - WHvPartitionPropertyCodeExtendedVmExits, + WHvPartitionPropertyCodeNestedVirtualization, &prop, sizeof(WHV_PARTITION_PROPERTY)); + if (FAILED(hr)) { + error_report("WHPX: Failed to enable nested virtualization, hr=%08lx", hr); + ret = -EINVAL; + goto error; + } + } + + hr = whp_dispatch.WHvSetPartitionProperty( + whpx->partition, + WHvPartitionPropertyCodeProcessorFeaturesBanks, + &processor_features, + sizeof(WHV_PROCESSOR_FEATURES_BANKS)); if (FAILED(hr)) { - error_report("WHPX: Failed to enable MSR & CPUIDexit, hr=%08lx", hr); + error_report("WHPX: Failed to set processor features, hr=%08lx", hr); ret = -EINVAL; goto error; } + /* Enable supported performance monitoring capabilities */ + hr = whp_dispatch.WHvGetCapability( + WHvCapabilityCodeProcessorPerfmonFeatures, &perfmon_features, + sizeof(WHV_PROCESSOR_PERFMON_FEATURES), &whpx_cap_size); + if (FAILED(hr)) { + error_report("WHPX: Failed to get performance monitoring features, hr=%08lx", hr); + ret = -ENOSPC; + goto error; + } + hr = whp_dispatch.WHvSetPartitionProperty( - whpx->partition, - WHvPartitionPropertyCodeCpuidExitList, - cpuidExitList, - RTL_NUMBER_OF(cpuidExitList) * sizeof(UINT32)); + whpx->partition, + WHvPartitionPropertyCodeProcessorPerfmonFeatures, + &perfmon_features, + sizeof(WHV_PROCESSOR_PERFMON_FEATURES)); + if (FAILED(hr)) { + error_report("WHPX: Failed to set performance monitoring features, hr=%08lx", hr); + ret = -EINVAL; + goto error; + } + + /* Enable synthetic processor features */ + WHV_SYNTHETIC_PROCESSOR_FEATURES_BANKS synthetic_features; + memset(&synthetic_features, 0, sizeof(WHV_SYNTHETIC_PROCESSOR_FEATURES_BANKS)); + synthetic_features.BanksCount = 1; + + synthetic_features.Bank0.HypervisorPresent = 1; + synthetic_features.Bank0.Hv1 = 1; + synthetic_features.Bank0.AccessVpRunTimeReg = 1; + synthetic_features.Bank0.AccessPartitionReferenceCounter = 1; + synthetic_features.Bank0.AccessPartitionReferenceTsc = 1; + synthetic_features.Bank0.AccessHypercallRegs = 1; + synthetic_features.Bank0.AccessFrequencyRegs = 1; + synthetic_features.Bank0.EnableExtendedGvaRangesForFlushVirtualAddressList = 1; + synthetic_features.Bank0.AccessVpIndex = 1; + synthetic_features.Bank0.AccessHypercallRegs = 1; + synthetic_features.Bank0.TbFlushHypercalls = 1; + + if (whpx_irqchip_in_kernel()) { + synthetic_features.Bank0.AccessSynicRegs = 1; + synthetic_features.Bank0.AccessSyntheticTimerRegs = 1; + synthetic_features.Bank0.AccessIntrCtrlRegs = 1; + synthetic_features.Bank0.SyntheticClusterIpi = 1; + synthetic_features.Bank0.DirectSyntheticTimers = 1; + } + + if (whpx->hyperv_enlightenments_allowed) { + hr = whp_dispatch.WHvSetPartitionProperty( + whpx->partition, + WHvPartitionPropertyCodeSyntheticProcessorFeaturesBanks, + &synthetic_features, + sizeof(WHV_SYNTHETIC_PROCESSOR_FEATURES_BANKS)); + if (FAILED(hr)) { + error_report("WHPX: Failed to set synthetic features, hr=%08lx", hr); + ret = -EINVAL; + goto error; + } + } + + /* Register for MSR and CPUID exits */ + memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY)); + prop.ExtendedVmExits.X64MsrExit = 1; + prop.ExtendedVmExits.ExceptionExit = 1; + hr = whp_dispatch.WHvSetPartitionProperty( + whpx->partition, + WHvPartitionPropertyCodeExtendedVmExits, + &prop, + sizeof(WHV_PARTITION_PROPERTY)); if (FAILED(hr)) { - error_report("WHPX: Failed to set partition CpuidExitList hr=%08lx", - hr); + error_report("WHPX: Failed to enable MSR & CPUIDexit, hr=%08lx", hr); ret = -EINVAL; goto error; } @@ -2245,8 +2188,8 @@ int whpx_accel_init(AccelState *as, MachineState *ms) } whpx_memory_init(); + whpx_init_emu(); - printf("Windows Hypervisor Platform accelerator is operational\n"); return 0; error: diff --git a/target/i386/whpx/whpx-apic.c b/target/i386/whpx/whpx-apic.c index b934fdcbe199..f26ecaf6e831 100644 --- a/target/i386/whpx/whpx-apic.c +++ b/target/i386/whpx/whpx-apic.c @@ -192,6 +192,11 @@ static void whpx_send_msi(MSIMessage *msg) uint8_t trigger_mode = (data >> MSI_DATA_TRIGGER_SHIFT) & 0x1; uint8_t delivery = (data >> MSI_DATA_DELIVERY_MODE_SHIFT) & 0x7; + if (vector == 0) { + warn_report("Ignoring request for interrupt vector 0"); + return; + } + WHV_INTERRUPT_CONTROL interrupt = { /* Values correspond to delivery modes */ .Type = delivery, diff --git a/target/loongarch/cpu-mmu.h b/target/loongarch/cpu-mmu.h index 2259de9d36df..3286accc143e 100644 --- a/target/loongarch/cpu-mmu.h +++ b/target/loongarch/cpu-mmu.h @@ -98,5 +98,6 @@ TLBRet loongarch_ptw(CPULoongArchState *env, MMUContext *context, void get_dir_base_width(CPULoongArchState *env, uint64_t *dir_base, uint64_t *dir_width, unsigned int level); hwaddr loongarch_cpu_get_phys_page_debug(CPUState *cpu, vaddr addr); +uint64_t loongarch_palen_mask(CPULoongArchState *env); #endif /* LOONGARCH_CPU_MMU_H */ diff --git a/target/loongarch/cpu-param.h b/target/loongarch/cpu-param.h index 58cc45a377e3..3bcf77b3755a 100644 --- a/target/loongarch/cpu-param.h +++ b/target/loongarch/cpu-param.h @@ -8,11 +8,8 @@ #ifndef LOONGARCH_CPU_PARAM_H #define LOONGARCH_CPU_PARAM_H -#define TARGET_PHYS_ADDR_SPACE_BITS 48 #define TARGET_VIRT_ADDR_SPACE_BITS 48 #define TARGET_PAGE_BITS 12 -#define TARGET_INSN_START_EXTRA_WORDS 0 - #endif diff --git a/target/loongarch/cpu.c b/target/loongarch/cpu.c index 54dbee2554bb..e22568c84ac8 100644 --- a/target/loongarch/cpu.c +++ b/target/loongarch/cpu.c @@ -283,8 +283,14 @@ static void loongarch_la464_initfn(Object *obj) } cpu->dtb_compatible = "loongarch,Loongson-3A5000"; - env->cpucfg[0] = 0x14c010; /* PRID */ + data = FIELD_DP32(data, CPUCFG0, PRID, 0x10); + data = FIELD_DP32(data, CPUCFG0, SERID, PRID_SERIES_LA464); + data = FIELD_DP32(data, CPUCFG0, VENID, PRID_VENDOR_LOONGSON); + env->cpucfg[0] = data; + memccpy((void *)&env->vendor_id, CPU_VENDOR_LOONGSON, 0, 8); + memccpy((void *)&env->cpu_id, CPU_MODEL_3A5000, 0, 8); + data = 0; data = FIELD_DP32(data, CPUCFG1, ARCH, 2); data = FIELD_DP32(data, CPUCFG1, PGMMU, 1); data = FIELD_DP32(data, CPUCFG1, IOCSR, 1); @@ -317,6 +323,22 @@ static void loongarch_la464_initfn(Object *obj) data = FIELD_DP32(data, CPUCFG2, LAM, 1); env->cpucfg[2] = data; + data = 0; + data = FIELD_DP32(data, CPUCFG3, CCDMA, 1); + data = FIELD_DP32(data, CPUCFG3, UCACC, 1); + data = FIELD_DP32(data, CPUCFG3, LLEXC, 1); + data = FIELD_DP32(data, CPUCFG3, SCDLY, 1); + data = FIELD_DP32(data, CPUCFG3, LLDBAR, 1); + data = FIELD_DP32(data, CPUCFG3, ITLBHMC, 1); + data = FIELD_DP32(data, CPUCFG3, ICHMC, 1); + data = FIELD_DP32(data, CPUCFG3, SPW_LVL, 4); + data = FIELD_DP32(data, CPUCFG3, SPW_HP_HF, 1); + if (kvm_enabled()) { + data = FIELD_DP32(data, CPUCFG3, RVA, 1); + data = FIELD_DP32(data, CPUCFG3, RVAMAX, 7); + } + env->cpucfg[3] = data; + env->cpucfg[4] = 100 * 1000 * 1000; /* Crystal frequency */ data = 0; @@ -389,8 +411,14 @@ static void loongarch_la132_initfn(Object *obj) } cpu->dtb_compatible = "loongarch,Loongson-1C103"; - env->cpucfg[0] = 0x148042; /* PRID */ + data = FIELD_DP32(data, CPUCFG0, PRID, 0x42); + data = FIELD_DP32(data, CPUCFG0, SERID, PRID_SERIES_LA132); + data = FIELD_DP32(data, CPUCFG0, VENID, PRID_VENDOR_LOONGSON); + env->cpucfg[0] = data; + memccpy((void *)&env->vendor_id, CPU_VENDOR_LOONGSON, 0, 8); + memccpy((void *)&env->cpu_id, CPU_MODEL_1C101, 0, 8); + data = 0; data = FIELD_DP32(data, CPUCFG1, ARCH, 1); /* LA32 */ data = FIELD_DP32(data, CPUCFG1, PGMMU, 1); data = FIELD_DP32(data, CPUCFG1, IOCSR, 1); @@ -430,6 +458,131 @@ static void loongarch_max_initfn(Object *obj) } } +#if defined(CONFIG_KVM) +static int read_cpuinfo(const char *field, char *value, int len) +{ + FILE *f; + int ret = -1; + int field_len = strlen(field); + char line[512]; + + f = fopen("/proc/cpuinfo", "r"); + if (!f) { + return -1; + } + + do { + if (!fgets(line, sizeof(line), f)) { + break; + } + if (!strncmp(line, field, field_len)) { + strncpy(value, line, len); + ret = 0; + break; + } + } while (*line); + + fclose(f); + + return ret; +} + +static uint64_t get_host_cpu_model(void) +{ + char line[512]; + char *ns; + static uint64_t cpuid; + + if (cpuid) { + return cpuid; + } + + if (read_cpuinfo("Model Name", line, sizeof(line))) { + return 0; + } + + ns = strchr(line, ':'); + if (!ns) { + return 0; + } + + ns = strstr(ns, "Loongson-"); + if (!ns) { + return 0; + } + + ns += strlen("Loongson-"); + memccpy((void *)&cpuid, ns, 0, 8); + return cpuid; +} + +static uint32_t get_host_cpucfg(int number) +{ + unsigned int data = 0; + +#ifdef __loongarch__ + asm volatile("cpucfg %[val], %[reg]" + : [val] "=r" (data) + : [reg] "r" (number) + : "memory"); +#endif + + return data; +} + +static void loongarch_host_initfn(Object *obj) +{ + uint32_t data, cpucfg, field; + uint64_t cpuid; + LoongArchCPU *cpu = LOONGARCH_CPU(obj); + + loongarch_max_initfn(obj); + data = get_host_cpucfg(0); + if (data) { + cpu->env.cpucfg[0] = data; + } + + /* + * There is no exception in KVM hypervisor when these intructions are + * executed if HW support, KVM hypervisor cannot control this. + * + * Set cpucfg bits which cannot be controlled by KVM hypervisor. + */ + data = get_host_cpucfg(2); + cpucfg = cpu->env.cpucfg[2]; + field = FIELD_EX32(data, CPUCFG2, FRECIPE); + cpucfg = FIELD_DP32(cpucfg, CPUCFG2, FRECIPE, field); + field = FIELD_EX32(data, CPUCFG2, DIV32); + cpucfg = FIELD_DP32(cpucfg, CPUCFG2, DIV32, field); + field = FIELD_EX32(data, CPUCFG2, LAM_BH); + cpucfg = FIELD_DP32(cpucfg, CPUCFG2, LAM_BH, field); + field = FIELD_EX32(data, CPUCFG2, LAMCAS); + cpucfg = FIELD_DP32(cpucfg, CPUCFG2, LAMCAS, field); + field = FIELD_EX32(data, CPUCFG2, LLACQ_SCREL); + cpucfg = FIELD_DP32(cpucfg, CPUCFG2, LLACQ_SCREL, field); + field = FIELD_EX32(data, CPUCFG2, SCQ); + cpucfg = FIELD_DP32(cpucfg, CPUCFG2, SCQ, field); + cpu->env.cpucfg[2] = cpucfg; + + data = get_host_cpucfg(3); + cpucfg = cpu->env.cpucfg[3]; + field = FIELD_EX32(data, CPUCFG3, DBAR_HINTS); + cpucfg = FIELD_DP32(cpucfg, CPUCFG3, DBAR_HINTS, field); + field = FIELD_EX32(data, CPUCFG3, ALDORDER_STA); + cpucfg = FIELD_DP32(cpucfg, CPUCFG3, ALDORDER_STA, field); + field = FIELD_EX32(data, CPUCFG3, ASTORDER_STA); + cpucfg = FIELD_DP32(cpucfg, CPUCFG3, ASTORDER_STA, field); + field = FIELD_EX32(data, CPUCFG3, SLDORDER_STA); + cpucfg = FIELD_DP32(cpucfg, CPUCFG3, SLDORDER_STA, field); + cpu->env.cpucfg[3] = cpucfg; + + cpuid = get_host_cpu_model(); + if (cpuid) { + cpu->env.cpu_id = cpuid; + } +} +#endif + static void loongarch_cpu_reset_hold(Object *obj, ResetType type) { uint8_t tlb_ps; @@ -443,6 +596,17 @@ static void loongarch_cpu_reset_hold(Object *obj, ResetType type) #ifdef CONFIG_TCG env->fcsr0_mask = FCSR0_M1 | FCSR0_M2 | FCSR0_M3; + + if (is_la64(env)) { + env->hw_pte_mask = MAKE_64BIT_MASK(0, 9) | + R_TLBENTRY_64_PPN_MASK | + R_TLBENTRY_64_NR_MASK | + R_TLBENTRY_64_NX_MASK | + R_TLBENTRY_64_RPLV_MASK; + } else { + env->hw_pte_mask = MAKE_64BIT_MASK(0, 9) | + R_TLBENTRY_32_PPN_MASK; + } #endif env->fcsr0 = 0x0; @@ -783,6 +947,9 @@ static const TypeInfo loongarch_cpu_type_infos[] = { DEFINE_LOONGARCH_CPU_TYPE(64, "la464", loongarch_la464_initfn), DEFINE_LOONGARCH_CPU_TYPE(32, "la132", loongarch_la132_initfn), DEFINE_LOONGARCH_CPU_TYPE(64, "max", loongarch_max_initfn), +#if defined(CONFIG_KVM) + DEFINE_LOONGARCH_CPU_TYPE(64, "host", loongarch_host_initfn), +#endif }; DEFINE_TYPES(loongarch_cpu_type_infos) diff --git a/target/loongarch/cpu.h b/target/loongarch/cpu.h index 8c198fa58489..4d333806ed8b 100644 --- a/target/loongarch/cpu.h +++ b/target/loongarch/cpu.h @@ -97,7 +97,15 @@ FIELD(FCSR0, CAUSE, 24, 5) #define EXCCODE_DBP EXCODE(26, 0) /* Reserved subcode used for debug */ /* cpucfg[0] bits */ -FIELD(CPUCFG0, PRID, 0, 32) +FIELD(CPUCFG0, PRID, 0, 12) +FIELD(CPUCFG0, SERID, 12, 4) +FIELD(CPUCFG0, VENID, 16, 8) +#define PRID_SERIES_LA132 0x8 /* Loongson 32bit */ +#define PRID_SERIES_LA264 0xa /* Loongson 64bit, 2-issue */ +#define PRID_SERIES_LA364 0xb /* Loongson 64bit, 3-issue */ +#define PRID_SERIES_LA464 0xc /* Loongson 64bit, 4-issue */ +#define PRID_SERIES_LA664 0xd /* Loongson 64bit, 6-issue */ +#define PRID_VENDOR_LOONGSON 0x14 /* cpucfg[1] bits */ FIELD(CPUCFG1, ARCH, 0, 2) @@ -139,6 +147,7 @@ FIELD(CPUCFG2, LSPW, 21, 1) FIELD(CPUCFG2, LAM, 22, 1) FIELD(CPUCFG2, HPTW, 24, 1) FIELD(CPUCFG2, FRECIPE, 25, 1) +FIELD(CPUCFG2, DIV32, 26, 1) FIELD(CPUCFG2, LAM_BH, 27, 1) FIELD(CPUCFG2, LAMCAS, 28, 1) FIELD(CPUCFG2, LLACQ_SCREL, 29, 1) @@ -157,6 +166,13 @@ FIELD(CPUCFG3, SPW_LVL, 8, 3) FIELD(CPUCFG3, SPW_HP_HF, 11, 1) FIELD(CPUCFG3, RVA, 12, 1) FIELD(CPUCFG3, RVAMAX, 13, 4) +FIELD(CPUCFG3, DBAR_HINTS, 17, 1) +FIELD(CPUCFG3, ALDORDER_CAP, 18, 1) +FIELD(CPUCFG3, ASTORDER_CAP, 19, 1) +FIELD(CPUCFG3, ALDORDER_STA, 20, 1) +FIELD(CPUCFG3, ASTORDER_STA, 21, 1) +FIELD(CPUCFG3, SLDORDER_CAP, 22, 1) +FIELD(CPUCFG3, SLDORDER_STA, 23, 1) /* cpucfg[4] bits */ FIELD(CPUCFG4, CC_FREQ, 0, 32) @@ -299,6 +315,10 @@ typedef struct LoongArchBT { uint32_t ftop; } lbt_t; +#define CPU_VENDOR_LOONGSON "Loongson" +#define CPU_MODEL_3A5000 "3A5000" +#define CPU_MODEL_1C101 "1C101" + typedef struct CPUArchState { uint64_t gpr[32]; uint64_t pc; @@ -310,6 +330,8 @@ typedef struct CPUArchState { uint32_t cpucfg[21]; uint32_t pv_features; + uint64_t vendor_id; + uint64_t cpu_id; /* LoongArch CSRs */ uint64_t CSR_CRMD; @@ -384,6 +406,7 @@ typedef struct CPUArchState { uint64_t llval; uint64_t llval_high; /* For 128-bit atomic SC.Q */ uint64_t llbit_scq; /* Potential LL.D+LD.D+SC.Q sequence in effect */ + uint64_t hw_pte_mask; /* Mask of architecturally-defined (hardware) PTE bits. */ #endif #ifndef CONFIG_USER_ONLY #ifdef CONFIG_TCG diff --git a/target/loongarch/cpu_helper.c b/target/loongarch/cpu_helper.c index 51ad9ff2b462..60441687667f 100644 --- a/target/loongarch/cpu_helper.c +++ b/target/loongarch/cpu_helper.c @@ -147,6 +147,7 @@ TLBRet loongarch_ptw(CPULoongArchState *env, MMUContext *context, { CPUState *cs = env_cpu(env); hwaddr index = 0, phys = 0; + uint64_t palen_mask = loongarch_palen_mask(env); uint64_t dir_base, dir_width; uint64_t base, pte; int level; @@ -154,13 +155,14 @@ TLBRet loongarch_ptw(CPULoongArchState *env, MMUContext *context, TLBRet ret; MemTxResult ret1; + address = context->addr; if ((address >> 63) & 0x1) { base = env->CSR_PGDH; } else { base = env->CSR_PGDL; } - base &= TARGET_PHYS_MASK; + base &= palen_mask; for (level = 4; level >= 0; level--) { get_dir_base_width(env, &dir_base, &dir_width, level); @@ -181,7 +183,7 @@ TLBRet loongarch_ptw(CPULoongArchState *env, MMUContext *context, break; } else { /* Discard high bits with page directory table */ - base &= TARGET_PHYS_MASK; + base &= palen_mask; } } } @@ -315,7 +317,7 @@ TLBRet get_physical_address(CPULoongArchState *env, MMUContext *context, /* Check PG and DA */ address = context->addr; if (da & !pg) { - context->physical = address & TARGET_PHYS_MASK; + context->physical = address & loongarch_palen_mask(env); context->prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC; context->mmu_index = MMU_DA_IDX; return TLBRET_MATCH; @@ -364,3 +366,10 @@ hwaddr loongarch_cpu_get_phys_page_debug(CPUState *cs, vaddr addr) } return context.physical; } + +uint64_t loongarch_palen_mask(CPULoongArchState *env) +{ + /* PALEN stores physical address bits - 1 */ + uint64_t phys_bits = FIELD_EX32(env->cpucfg[1], CPUCFG1, PALEN) + 1; + return MAKE_64BIT_MASK(0, phys_bits); +} diff --git a/target/loongarch/internals.h b/target/loongarch/internals.h index 8793bd9df65a..e01dbed40f6c 100644 --- a/target/loongarch/internals.h +++ b/target/loongarch/internals.h @@ -13,7 +13,6 @@ #define FCMP_UN 0b0100 /* unordered */ #define FCMP_GT 0b1000 /* fp0 > fp1 */ -#define TARGET_PHYS_MASK MAKE_64BIT_MASK(0, TARGET_PHYS_ADDR_SPACE_BITS) #define TARGET_VIRT_MASK MAKE_64BIT_MASK(0, TARGET_VIRT_ADDR_SPACE_BITS) void loongarch_translate_init(void); diff --git a/target/loongarch/loongarch-qmp-cmds.c b/target/loongarch/loongarch-qmp-cmds.c index 1d8cd32f5fcc..f053f22bb8c4 100644 --- a/target/loongarch/loongarch-qmp-cmds.c +++ b/target/loongarch/loongarch-qmp-cmds.c @@ -41,7 +41,8 @@ CpuDefinitionInfoList *qmp_query_cpu_definitions(Error **errp) } static const char *cpu_model_advertised_features[] = { - "lsx", "lasx", "lbt", "pmu", "kvm-pv-ipi", "kvm-steal-time", NULL + "lsx", "lasx", "lbt", "pmu", "kvm-pv-ipi", "kvm-steal-time", "msgint", + "ptw", NULL }; CpuModelExpansionInfo *qmp_query_cpu_model_expansion(CpuModelExpansionType type, @@ -49,7 +50,6 @@ CpuModelExpansionInfo *qmp_query_cpu_model_expansion(CpuModelExpansionType type, Error **errp) { Visitor *visitor; - bool ok; CpuModelExpansionInfo *expansion_info; QDict *qdict_out; ObjectClass *oc; @@ -57,35 +57,53 @@ CpuModelExpansionInfo *qmp_query_cpu_model_expansion(CpuModelExpansionType type, const char *name; int i; - if (type != CPU_MODEL_EXPANSION_TYPE_STATIC) { + if ((type != CPU_MODEL_EXPANSION_TYPE_STATIC) && + (type != CPU_MODEL_EXPANSION_TYPE_FULL)) { error_setg(errp, "The requested expansion type is not supported"); return NULL; } + oc = cpu_class_by_name(TYPE_LOONGARCH_CPU, model->name); + if (!oc) { + error_setg(errp, "The CPU type '%s' is not a recognized LoongArch " + "CPU type", model->name); + return NULL; + } + + obj = object_new(object_class_get_name(oc)); if (model->props) { + Error *err = NULL; + const QDict *qdict_in; + visitor = qobject_input_visitor_new(model->props); if (!visit_start_struct(visitor, "model.props", NULL, 0, errp)) { visit_free(visitor); + object_unref(obj); return NULL; } - ok = visit_check_struct(visitor, errp); + qdict_in = qobject_to(QDict, model->props); + i = 0; + while ((name = cpu_model_advertised_features[i++]) != NULL) { + if (qdict_get(qdict_in, name)) { + if (!object_property_set(obj, name, visitor, &err)) { + break; + } + } + } + + if (!err) { + visit_check_struct(visitor, &err); + } visit_end_struct(visitor, NULL); visit_free(visitor); - if (!ok) { + if (err) { + error_propagate(errp, err); + object_unref(obj); return NULL; } } - oc = cpu_class_by_name(TYPE_LOONGARCH_CPU, model->name); - if (!oc) { - error_setg(errp, "The CPU type '%s' is not a recognized LoongArch CPU type", - model->name); - return NULL; - } - - obj = object_new(object_class_get_name(oc)); - expansion_info = g_new0(CpuModelExpansionInfo, 1); expansion_info->model = g_malloc0(sizeof(*expansion_info->model)); expansion_info->model->name = g_strdup(model->name); diff --git a/target/loongarch/meson.build b/target/loongarch/meson.build index 20bd3e2f0a3c..2ae96d68695d 100644 --- a/target/loongarch/meson.build +++ b/target/loongarch/meson.build @@ -3,9 +3,14 @@ gen = decodetree.process('insns.decode') loongarch_ss = ss.source_set() loongarch_ss.add(files( 'cpu.c', - 'gdbstub.c', )) +loongarch_user_ss = ss.source_set() +loongarch_user_ss.add(files('gdbstub.c')) + +loongarch_common_system_ss = ss.source_set() +loongarch_common_system_ss.add(files('gdbstub.c')) + loongarch_system_ss = ss.source_set() loongarch_system_ss.add(files( 'arch_dump.c', @@ -20,5 +25,7 @@ common_ss.add(when: 'CONFIG_LOONGARCH_DIS', if_true: [files('disas.c'), gen]) subdir('tcg') target_arch += {'loongarch': loongarch_ss} +target_user_arch += {'loongarch': loongarch_user_ss} target_system_arch += {'loongarch': loongarch_system_ss} +target_common_system_arch += {'loongarch': loongarch_common_system_ss} subdir('kvm') diff --git a/target/loongarch/tcg/tcg_cpu.c b/target/loongarch/tcg/tcg_cpu.c index af922776692e..31d3db6e8e1d 100644 --- a/target/loongarch/tcg/tcg_cpu.c +++ b/target/loongarch/tcg/tcg_cpu.c @@ -109,6 +109,7 @@ static void loongarch_cpu_do_interrupt(CPUState *cs) } QEMU_FALLTHROUGH; case EXCCODE_PIF: + case EXCCODE_PNX: case EXCCODE_ADEF: cause = cs->exception_index; update_badinstr = 0; @@ -129,7 +130,6 @@ static void loongarch_cpu_do_interrupt(CPUState *cs) case EXCCODE_PIS: case EXCCODE_PME: case EXCCODE_PNR: - case EXCCODE_PNX: case EXCCODE_PPI: cause = cs->exception_index; break; diff --git a/target/loongarch/tcg/tlb_helper.c b/target/loongarch/tcg/tlb_helper.c index b6e9a3a3c7f5..c0fd8527fe95 100644 --- a/target/loongarch/tcg/tlb_helper.c +++ b/target/loongarch/tcg/tlb_helper.c @@ -686,14 +686,31 @@ bool loongarch_cpu_tlb_fill(CPUState *cs, vaddr address, int size, cpu_loop_exit_restore(cs, retaddr); } +static inline uint64_t loongarch_sanitize_hw_pte(CPULoongArchState *env, + uint64_t pte) +{ + uint64_t palen_mask = loongarch_palen_mask(env); + uint64_t ppn_mask = is_la64(env) ? R_TLBENTRY_64_PPN_MASK : R_TLBENTRY_32_PPN_MASK; + + /* + * Keep only architecturally-defined PTE bits. Guests may use some + * otherwise-unused bits for software purposes. + */ + pte &= env->hw_pte_mask; + + return (pte & ~ppn_mask) | ((pte & ppn_mask) & palen_mask); +} + target_ulong helper_lddir(CPULoongArchState *env, target_ulong base, uint32_t level, uint32_t mem_idx) { CPUState *cs = env_cpu(env); uint64_t badvaddr; hwaddr index, phys; + uint64_t palen_mask = loongarch_palen_mask(env); uint64_t dir_base, dir_width; + if (unlikely((level == 0) || (level > 4))) { qemu_log_mask(LOG_GUEST_ERROR, "Attepted LDDIR with level %u\n", level); @@ -715,11 +732,11 @@ target_ulong helper_lddir(CPULoongArchState *env, target_ulong base, } badvaddr = env->CSR_TLBRBADV; - base = base & TARGET_PHYS_MASK; + base = base & palen_mask; get_dir_base_width(env, &dir_base, &dir_width, level); index = (badvaddr >> dir_base) & ((1 << dir_width) - 1); phys = base | index << 3; - return ldq_le_phys(cs->as, phys) & TARGET_PHYS_MASK; + return ldq_le_phys(cs->as, phys) & palen_mask; } void helper_ldpte(CPULoongArchState *env, target_ulong base, target_ulong odd, @@ -727,12 +744,15 @@ void helper_ldpte(CPULoongArchState *env, target_ulong base, target_ulong odd, { CPUState *cs = env_cpu(env); hwaddr phys, tmp0, ptindex, ptoffset0, ptoffset1; + uint64_t pte_raw; uint64_t badv; uint64_t ptbase = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, PTBASE); uint64_t ptwidth = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, PTWIDTH); + uint64_t palen_mask = loongarch_palen_mask(env); uint64_t dir_base, dir_width; uint8_t ps; + /* * The parameter "base" has only two types, * one is the page table base address, @@ -740,7 +760,6 @@ void helper_ldpte(CPULoongArchState *env, target_ulong base, target_ulong odd, * and the other is the huge page entry, * whose bit 6 should be 1. */ - base = base & TARGET_PHYS_MASK; if (FIELD_EX64(base, TLBENTRY, HUGE)) { /* * Gets the huge page level and Gets huge page size. @@ -764,7 +783,7 @@ void helper_ldpte(CPULoongArchState *env, target_ulong base, target_ulong odd, * when loaded into the tlb, * so the tlb page size needs to be divided by 2. */ - tmp0 = base; + tmp0 = loongarch_sanitize_hw_pte(env, base); if (odd) { tmp0 += MAKE_64BIT_MASK(ps, 1); } @@ -776,12 +795,15 @@ void helper_ldpte(CPULoongArchState *env, target_ulong base, target_ulong odd, } else { badv = env->CSR_TLBRBADV; + base = base & palen_mask; + ptindex = (badv >> ptbase) & ((1 << ptwidth) - 1); ptindex = ptindex & ~0x1; /* clear bit 0 */ ptoffset0 = ptindex << 3; ptoffset1 = (ptindex + 1) << 3; phys = base | (odd ? ptoffset1 : ptoffset0); - tmp0 = ldq_le_phys(cs->as, phys) & TARGET_PHYS_MASK; + pte_raw = ldq_le_phys(cs->as, phys); + tmp0 = loongarch_sanitize_hw_pte(env, pte_raw); ps = ptbase; } diff --git a/target/loongarch/tcg/translate.c b/target/loongarch/tcg/translate.c index 30f375b33f07..b9ed13d19c6c 100644 --- a/target/loongarch/tcg/translate.c +++ b/target/loongarch/tcg/translate.c @@ -159,7 +159,7 @@ static void loongarch_tr_insn_start(DisasContextBase *dcbase, CPUState *cs) { DisasContext *ctx = container_of(dcbase, DisasContext, base); - tcg_gen_insn_start(ctx->base.pc_next); + tcg_gen_insn_start(ctx->base.pc_next, 0, 0); } /* diff --git a/target/m68k/cpu-param.h b/target/m68k/cpu-param.h index 256a2b5f8b29..59e04d85ff92 100644 --- a/target/m68k/cpu-param.h +++ b/target/m68k/cpu-param.h @@ -14,9 +14,6 @@ * use the smallest one */ #define TARGET_PAGE_BITS 12 -#define TARGET_PHYS_ADDR_SPACE_BITS 32 #define TARGET_VIRT_ADDR_SPACE_BITS 32 -#define TARGET_INSN_START_EXTRA_WORDS 1 - #endif diff --git a/target/m68k/cpu.c b/target/m68k/cpu.c index c721a23b9668..d849a4a90fcf 100644 --- a/target/m68k/cpu.c +++ b/target/m68k/cpu.c @@ -19,12 +19,15 @@ */ #include "qemu/osdep.h" +#include "accel/tcg/cpu-ops.h" +#include "fpu/softfloat.h" #include "qapi/error.h" -#include "cpu.h" + +#ifndef CONFIG_USER_ONLY #include "migration/vmstate.h" -#include "fpu/softfloat.h" -#include "exec/translation-block.h" -#include "accel/tcg/cpu-ops.h" +#endif + +#include "cpu.h" static void m68k_cpu_set_pc(CPUState *cs, vaddr value) { diff --git a/target/m68k/meson.build b/target/m68k/meson.build index 4d213daaf67f..c36d817134b8 100644 --- a/target/m68k/meson.build +++ b/target/m68k/meson.build @@ -2,13 +2,18 @@ m68k_ss = ss.source_set() m68k_ss.add(files( 'cpu.c', 'fpu_helper.c', - 'gdbstub.c', 'helper.c', 'op_helper.c', 'softfloat.c', 'translate.c', )) +m68k_user_ss = ss.source_set() +m68k_user_ss.add(files('gdbstub.c')) + +m68k_common_system_ss = ss.source_set() +m68k_common_system_ss.add(files('gdbstub.c')) + m68k_system_ss = ss.source_set() m68k_system_ss.add(files( 'monitor.c' @@ -19,4 +24,6 @@ m68k_system_ss.add(when: ['CONFIG_SEMIHOSTING'], ) target_arch += {'m68k': m68k_ss} +target_user_arch += {'m68k': m68k_user_ss} target_system_arch += {'m68k': m68k_system_ss} +target_common_system_arch += {'m68k': m68k_common_system_ss} diff --git a/target/m68k/monitor.c b/target/m68k/monitor.c index 6d101c75df05..08ced037b47d 100644 --- a/target/m68k/monitor.c +++ b/target/m68k/monitor.c @@ -24,24 +24,6 @@ void hmp_info_tlb(Monitor *mon, const QDict *qdict) } static const MonitorDef monitor_defs[] = { - { "d0", offsetof(CPUM68KState, dregs[0]), NULL, MD_I32 }, - { "d1", offsetof(CPUM68KState, dregs[1]), NULL, MD_I32 }, - { "d2", offsetof(CPUM68KState, dregs[2]), NULL, MD_I32 }, - { "d3", offsetof(CPUM68KState, dregs[3]), NULL, MD_I32 }, - { "d4", offsetof(CPUM68KState, dregs[4]), NULL, MD_I32 }, - { "d5", offsetof(CPUM68KState, dregs[5]), NULL, MD_I32 }, - { "d6", offsetof(CPUM68KState, dregs[6]), NULL, MD_I32 }, - { "d7", offsetof(CPUM68KState, dregs[7]), NULL, MD_I32 }, - { "a0", offsetof(CPUM68KState, aregs[0]), NULL, MD_I32 }, - { "a1", offsetof(CPUM68KState, aregs[1]), NULL, MD_I32 }, - { "a2", offsetof(CPUM68KState, aregs[2]), NULL, MD_I32 }, - { "a3", offsetof(CPUM68KState, aregs[3]), NULL, MD_I32 }, - { "a4", offsetof(CPUM68KState, aregs[4]), NULL, MD_I32 }, - { "a5", offsetof(CPUM68KState, aregs[5]), NULL, MD_I32 }, - { "a6", offsetof(CPUM68KState, aregs[6]), NULL, MD_I32 }, - { "a7", offsetof(CPUM68KState, aregs[7]), NULL, MD_I32 }, - { "pc", offsetof(CPUM68KState, pc), NULL, MD_I32 }, - { "sr", offsetof(CPUM68KState, sr), NULL, MD_I32 }, { "ssp", offsetof(CPUM68KState, sp[0]), NULL, MD_I32 }, { "usp", offsetof(CPUM68KState, sp[1]), NULL, MD_I32 }, { "isp", offsetof(CPUM68KState, sp[2]), NULL, MD_I32 }, diff --git a/target/m68k/translate.c b/target/m68k/translate.c index a0309939012b..abc1c79f3cd8 100644 --- a/target/m68k/translate.c +++ b/target/m68k/translate.c @@ -6041,7 +6041,7 @@ static void m68k_tr_tb_start(DisasContextBase *dcbase, CPUState *cpu) static void m68k_tr_insn_start(DisasContextBase *dcbase, CPUState *cpu) { DisasContext *dc = container_of(dcbase, DisasContext, base); - tcg_gen_insn_start(dc->base.pc_next, dc->cc_op); + tcg_gen_insn_start(dc->base.pc_next, dc->cc_op, 0); } static void m68k_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) diff --git a/target/microblaze/cpu-param.h b/target/microblaze/cpu-param.h index e0a37945136e..0b13f219c2a5 100644 --- a/target/microblaze/cpu-param.h +++ b/target/microblaze/cpu-param.h @@ -17,16 +17,12 @@ * of address space. */ #ifdef CONFIG_USER_ONLY -#define TARGET_PHYS_ADDR_SPACE_BITS 32 #define TARGET_VIRT_ADDR_SPACE_BITS 32 #else -#define TARGET_PHYS_ADDR_SPACE_BITS 64 #define TARGET_VIRT_ADDR_SPACE_BITS 64 #endif /* FIXME: MB uses variable pages down to 1K but linux only uses 4k. */ #define TARGET_PAGE_BITS 12 -#define TARGET_INSN_START_EXTRA_WORDS 1 - #endif diff --git a/target/microblaze/meson.build b/target/microblaze/meson.build index b30b8f70ace5..b86b37d92389 100644 --- a/target/microblaze/meson.build +++ b/target/microblaze/meson.build @@ -4,12 +4,17 @@ microblaze_ss = ss.source_set() microblaze_ss.add(gen) microblaze_ss.add(files( 'cpu.c', - 'gdbstub.c', 'helper.c', 'op_helper.c', 'translate.c', )) +microblaze_user_ss = ss.source_set() +microblaze_user_ss.add(files('gdbstub.c')) + +microblaze_common_system_ss = ss.source_set() +microblaze_common_system_ss.add(files('gdbstub.c')) + microblaze_system_ss = ss.source_set() microblaze_system_ss.add(files( 'mmu.c', @@ -17,4 +22,6 @@ microblaze_system_ss.add(files( )) target_arch += {'microblaze': microblaze_ss} -target_common_system_arch += {'microblaze': microblaze_system_ss} +target_user_arch += {'microblaze': microblaze_user_ss} +target_system_arch += {'microblaze': microblaze_system_ss} +target_common_system_arch += {'microblaze': microblaze_common_system_ss} diff --git a/target/microblaze/translate.c b/target/microblaze/translate.c index 0be3c98dc17e..2af67beecec0 100644 --- a/target/microblaze/translate.c +++ b/target/microblaze/translate.c @@ -1630,7 +1630,7 @@ static void mb_tr_insn_start(DisasContextBase *dcb, CPUState *cs) { DisasContext *dc = container_of(dcb, DisasContext, base); - tcg_gen_insn_start(dc->base.pc_next, dc->tb_flags & ~MSR_TB_MASK); + tcg_gen_insn_start(dc->base.pc_next, dc->tb_flags & ~MSR_TB_MASK, 0); } static void mb_tr_translate_insn(DisasContextBase *dcb, CPUState *cs) diff --git a/target/mips/cpu-param.h b/target/mips/cpu-param.h index 58f450827f79..122193b0db3e 100644 --- a/target/mips/cpu-param.h +++ b/target/mips/cpu-param.h @@ -8,10 +8,8 @@ #define MIPS_CPU_PARAM_H #ifdef TARGET_ABI_MIPSN64 -#define TARGET_PHYS_ADDR_SPACE_BITS 48 #define TARGET_VIRT_ADDR_SPACE_BITS 48 #else -#define TARGET_PHYS_ADDR_SPACE_BITS 40 # ifdef CONFIG_USER_ONLY # define TARGET_VIRT_ADDR_SPACE_BITS 31 # else @@ -20,6 +18,4 @@ #endif #define TARGET_PAGE_BITS 12 -#define TARGET_INSN_START_EXTRA_WORDS 2 - #endif diff --git a/target/mips/gdbstub.c b/target/mips/gdbstub.c index 169d47416a60..8e0b08a096c5 100644 --- a/target/mips/gdbstub.c +++ b/target/mips/gdbstub.c @@ -18,6 +18,7 @@ * License along with this library; if not, see . */ #include "qemu/osdep.h" +#include "qemu/bitops.h" #include "cpu.h" #include "internal.h" #include "gdbstub/helpers.h" @@ -77,14 +78,15 @@ int mips_cpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n) int mips_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) { + const unsigned regsz = target_long_bits() / 8; CPUMIPSState *env = cpu_env(cs); - target_ulong tmp; + uint64_t tmp; - tmp = ldtul_p(mem_buf); + tmp = ldn_p(mem_buf, regsz); if (n < 32) { env->active_tc.gpr[n] = tmp; - return sizeof(target_ulong); + return regsz; } if (env->CP0_Config1 & (1 << CP0C1_FP) && n >= 38 && n < 72) { switch (n) { @@ -104,7 +106,7 @@ int mips_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) } break; } - return sizeof(target_ulong); + return regsz; } switch (n) { case 32: @@ -127,7 +129,7 @@ int mips_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) #endif break; case 37: - env->active_tc.PC = tmp & ~(target_ulong)1; + env->active_tc.PC = deposit64(tmp, 63, 1, 0); if (tmp & 1) { env->hflags |= MIPS_HFLAG_M16; } else { @@ -144,5 +146,5 @@ int mips_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) break; } - return sizeof(target_ulong); + return regsz; } diff --git a/target/or1k/cpu-param.h b/target/or1k/cpu-param.h index b4f57bbe692c..1ff89d523f16 100644 --- a/target/or1k/cpu-param.h +++ b/target/or1k/cpu-param.h @@ -9,9 +9,6 @@ #define OPENRISC_CPU_PARAM_H #define TARGET_PAGE_BITS 13 -#define TARGET_PHYS_ADDR_SPACE_BITS 32 #define TARGET_VIRT_ADDR_SPACE_BITS 32 -#define TARGET_INSN_START_EXTRA_WORDS 1 - #endif diff --git a/target/or1k/cpu.c b/target/or1k/cpu.c index c64542a59a29..3d1c22bf75d0 100644 --- a/target/or1k/cpu.c +++ b/target/or1k/cpu.c @@ -288,13 +288,13 @@ static void openrisc_cpu_class_init(ObjectClass *oc, const void *data) cc->dump_state = openrisc_cpu_dump_state; cc->set_pc = openrisc_cpu_set_pc; cc->get_pc = openrisc_cpu_get_pc; + cc->gdb_core_xml_file = "or1k-core.xml"; cc->gdb_read_register = openrisc_cpu_gdb_read_register; cc->gdb_write_register = openrisc_cpu_gdb_write_register; #ifndef CONFIG_USER_ONLY dc->vmsd = &vmstate_openrisc_cpu; cc->sysemu_ops = &openrisc_sysemu_ops; #endif - cc->gdb_num_core_regs = 32 + 3; cc->disas_set_info = openrisc_disas_set_info; cc->tcg_ops = &openrisc_tcg_ops; } diff --git a/target/or1k/meson.build b/target/or1k/meson.build index cad8c1b1ae06..7cea45287a27 100644 --- a/target/or1k/meson.build +++ b/target/or1k/meson.build @@ -1,25 +1,32 @@ gen = decodetree.process('insns.decode') -openrisc_ss = ss.source_set() -openrisc_ss.add(gen) -openrisc_ss.add(files( +or1k_ss = ss.source_set() +or1k_ss.add(gen) +or1k_ss.add(files( 'cpu.c', 'disas.c', 'exception.c', 'exception_helper.c', 'fpu_helper.c', - 'gdbstub.c', 'interrupt_helper.c', 'sys_helper.c', 'translate.c', )) -openrisc_system_ss = ss.source_set() -openrisc_system_ss.add(files( +or1k_user_ss = ss.source_set() +or1k_user_ss.add(files('gdbstub.c')) + +or1k_common_system_ss = ss.source_set() +or1k_common_system_ss.add(files('gdbstub.c')) + +or1k_system_ss = ss.source_set() +or1k_system_ss.add(files( 'interrupt.c', 'machine.c', 'mmu.c', )) -target_arch += {'or1k': openrisc_ss} -target_common_system_arch += {'or1k': openrisc_system_ss} +target_arch += {'or1k': or1k_ss} +target_user_arch += {'or1k': or1k_user_ss} +target_system_arch += {'or1k': or1k_system_ss} +target_common_system_arch += {'or1k': or1k_common_system_ss} diff --git a/target/or1k/translate.c b/target/or1k/translate.c index ce2dc466dc7e..de81dc6ef8d2 100644 --- a/target/or1k/translate.c +++ b/target/or1k/translate.c @@ -1552,7 +1552,7 @@ static void openrisc_tr_insn_start(DisasContextBase *dcbase, CPUState *cs) DisasContext *dc = container_of(dcbase, DisasContext, base); tcg_gen_insn_start(dc->base.pc_next, (dc->delayed_branch ? 1 : 0) - | (dc->base.num_insns > 1 ? 2 : 0)); + | (dc->base.num_insns > 1 ? 2 : 0), 0); } static void openrisc_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) diff --git a/target/ppc/cpu-param.h b/target/ppc/cpu-param.h index e4ed9080ee96..5e211d10b0be 100644 --- a/target/ppc/cpu-param.h +++ b/target/ppc/cpu-param.h @@ -9,12 +9,6 @@ #define PPC_CPU_PARAM_H #ifdef TARGET_PPC64 -/* - * Note that the official physical address space bits is 62-M where M - * is implementation dependent. I've not looked up M for the set of - * cpus we emulate at the system level. - */ -#define TARGET_PHYS_ADDR_SPACE_BITS 62 /* * Note that the PPC environment architecture talks about 80 bit virtual * addresses, with segmentation. Obviously that's not all visible to a @@ -26,7 +20,6 @@ # define TARGET_VIRT_ADDR_SPACE_BITS 64 # endif #else -# define TARGET_PHYS_ADDR_SPACE_BITS 36 # define TARGET_VIRT_ADDR_SPACE_BITS 32 #endif @@ -37,6 +30,4 @@ # define TARGET_PAGE_BITS 12 #endif -#define TARGET_INSN_START_EXTRA_WORDS 0 - #endif diff --git a/target/ppc/cpu.h b/target/ppc/cpu.h index 49445eb4ca88..d637a50798fc 100644 --- a/target/ppc/cpu.h +++ b/target/ppc/cpu.h @@ -1637,9 +1637,7 @@ static inline bool vhyp_cpu_in_nested(PowerPCCPU *cpu) void ppc_cpu_dump_state(CPUState *cpu, FILE *f, int flags); int ppc_cpu_gdb_read_register(CPUState *cpu, GByteArray *buf, int reg); -int ppc_cpu_gdb_read_register_apple(CPUState *cpu, GByteArray *buf, int reg); int ppc_cpu_gdb_write_register(CPUState *cpu, uint8_t *buf, int reg); -int ppc_cpu_gdb_write_register_apple(CPUState *cpu, uint8_t *buf, int reg); #ifndef CONFIG_USER_ONLY hwaddr ppc_cpu_get_phys_page_debug(CPUState *cpu, vaddr addr); #endif diff --git a/target/ppc/cpu_init.c b/target/ppc/cpu_init.c index c36fd118a998..3d932a5642b7 100644 --- a/target/ppc/cpu_init.c +++ b/target/ppc/cpu_init.c @@ -52,8 +52,6 @@ #endif #include "cpu_init.h" -/* #define PPC_DEBUG_SPR */ -/* #define USE_APPLE_GDB */ static const Property powerpc_cpu_properties[] = { DEFINE_PROP_BOOL("rtas-stopped-state", PowerPCCPU, @@ -7553,14 +7551,6 @@ static void ppc_cpu_class_init(ObjectClass *oc, const void *data) MMU_INST_FETCH == 2 && PAGE_READ == 1 && PAGE_WRITE == 2 && PAGE_EXEC == 4); #endif - - cc->gdb_num_core_regs = 71; -#ifdef USE_APPLE_GDB - cc->gdb_read_register = ppc_cpu_gdb_read_register_apple; - cc->gdb_write_register = ppc_cpu_gdb_write_register_apple; - cc->gdb_num_core_regs = 71 + 32; -#endif - cc->gdb_arch_name = ppc_gdb_arch_name; #if defined(TARGET_PPC64) cc->gdb_core_xml_file = "power64-core.xml"; diff --git a/target/ppc/gdbstub.c b/target/ppc/gdbstub.c index b19c0f1ea9c5..e0aae9c9eaf0 100644 --- a/target/ppc/gdbstub.c +++ b/target/ppc/gdbstub.c @@ -23,37 +23,12 @@ #include "gdbstub/helpers.h" #include "internal.h" -static int ppc_gdb_register_len_apple(int n) +static unsigned ppc_gdb_register_len(int n) { switch (n) { case 0 ... 31: /* gprs */ - return 8; - case 32 ... 63: - /* fprs */ - return 8; - case 64 ... 95: - return 16; - case 64 + 32: /* nip */ - case 65 + 32: /* msr */ - case 67 + 32: /* lr */ - case 68 + 32: /* ctr */ - case 70 + 32: /* fpscr */ - return 8; - case 66 + 32: /* cr */ - case 69 + 32: /* xer */ - return 4; - default: - return 0; - } -} - -static int ppc_gdb_register_len(int n) -{ - switch (n) { - case 0 ... 31: - /* gprs */ - return sizeof(target_ulong); + return target_long_bits() / 8; case 66: /* cr */ case 69: @@ -67,7 +42,7 @@ static int ppc_gdb_register_len(int n) /* lr */ case 68: /* ctr */ - return sizeof(target_ulong); + return target_long_bits() / 8; default: return 0; } @@ -110,7 +85,7 @@ int ppc_cpu_gdb_read_register(CPUState *cs, GByteArray *buf, int n) { CPUPPCState *env = cpu_env(cs); uint8_t *mem_buf; - int r = ppc_gdb_register_len(n); + unsigned r = ppc_gdb_register_len(n); if (!r) { return r; @@ -149,63 +124,10 @@ int ppc_cpu_gdb_read_register(CPUState *cs, GByteArray *buf, int n) return r; } -int ppc_cpu_gdb_read_register_apple(CPUState *cs, GByteArray *buf, int n) -{ - CPUPPCState *env = cpu_env(cs); - uint8_t *mem_buf; - int r = ppc_gdb_register_len_apple(n); - - if (!r) { - return r; - } - - if (n < 32) { - /* gprs */ - gdb_get_reg64(buf, env->gpr[n]); - } else if (n < 64) { - /* fprs */ - gdb_get_reg64(buf, *cpu_fpr_ptr(env, n - 32)); - } else if (n < 96) { - /* Altivec */ - gdb_get_reg64(buf, n - 64); - gdb_get_reg64(buf, 0); - } else { - switch (n) { - case 64 + 32: - gdb_get_reg64(buf, env->nip); - break; - case 65 + 32: - gdb_get_reg64(buf, env->msr); - break; - case 66 + 32: - { - uint32_t cr = ppc_get_cr(env); - gdb_get_reg32(buf, cr); - break; - } - case 67 + 32: - gdb_get_reg64(buf, env->lr); - break; - case 68 + 32: - gdb_get_reg64(buf, env->ctr); - break; - case 69 + 32: - gdb_get_reg32(buf, cpu_read_xer(env)); - break; - case 70 + 32: - gdb_get_reg64(buf, env->fpscr); - break; - } - } - mem_buf = buf->data + buf->len - r; - ppc_maybe_bswap_register(env, mem_buf, r); - return r; -} - int ppc_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) { CPUPPCState *env = cpu_env(cs); - int r = ppc_gdb_register_len(n); + unsigned r = ppc_gdb_register_len(n); if (!r) { return r; @@ -213,17 +135,17 @@ int ppc_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) ppc_maybe_bswap_register(env, mem_buf, r); if (n < 32) { /* gprs */ - env->gpr[n] = ldtul_p(mem_buf); + env->gpr[n] = ldn_p(mem_buf, r); } else if (n < 64) { /* fprs */ *cpu_fpr_ptr(env, n - 32) = ldq_p(mem_buf); } else { switch (n) { case 64: - env->nip = ldtul_p(mem_buf); + env->nip = ldn_p(mem_buf, r); break; case 65: - ppc_store_msr(env, ldtul_p(mem_buf)); + ppc_store_msr(env, ldn_p(mem_buf, r)); break; case 66: { @@ -232,63 +154,17 @@ int ppc_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) break; } case 67: - env->lr = ldtul_p(mem_buf); + env->lr = ldn_p(mem_buf, r); break; case 68: - env->ctr = ldtul_p(mem_buf); + env->ctr = ldn_p(mem_buf, r); break; case 69: cpu_write_xer(env, ldl_p(mem_buf)); break; case 70: /* fpscr */ - ppc_store_fpscr(env, ldtul_p(mem_buf)); - break; - } - } - return r; -} -int ppc_cpu_gdb_write_register_apple(CPUState *cs, uint8_t *mem_buf, int n) -{ - CPUPPCState *env = cpu_env(cs); - int r = ppc_gdb_register_len_apple(n); - - if (!r) { - return r; - } - ppc_maybe_bswap_register(env, mem_buf, r); - if (n < 32) { - /* gprs */ - env->gpr[n] = ldq_p(mem_buf); - } else if (n < 64) { - /* fprs */ - *cpu_fpr_ptr(env, n - 32) = ldq_p(mem_buf); - } else { - switch (n) { - case 64 + 32: - env->nip = ldq_p(mem_buf); - break; - case 65 + 32: - ppc_store_msr(env, ldq_p(mem_buf)); - break; - case 66 + 32: - { - uint32_t cr = ldl_p(mem_buf); - ppc_set_cr(env, cr); - break; - } - case 67 + 32: - env->lr = ldq_p(mem_buf); - break; - case 68 + 32: - env->ctr = ldq_p(mem_buf); - break; - case 69 + 32: - cpu_write_xer(env, ldl_p(mem_buf)); - break; - case 70 + 32: - /* fpscr */ - ppc_store_fpscr(env, ldq_p(mem_buf)); + ppc_store_fpscr(env, ldn_p(mem_buf, r)); break; } } diff --git a/target/ppc/meson.build b/target/ppc/meson.build index 8eed1fa40ca9..d4ebbaf0a194 100644 --- a/target/ppc/meson.build +++ b/target/ppc/meson.build @@ -31,7 +31,10 @@ gen = [ ] ppc_ss.add(when: 'CONFIG_TCG', if_true: gen) -ppc_ss.add(when: 'CONFIG_USER_ONLY', if_true: files('user_only_helper.c')) +ppc_user_ss = ss.source_set() +ppc_user_ss.add(files( + 'user_only_helper.c', +)) ppc_system_ss = ss.source_set() ppc_system_ss.add(files( @@ -40,6 +43,7 @@ ppc_system_ss.add(files( 'mmu-hash32.c', 'mmu-booke.c', 'mmu_common.c', + 'monitor.c', 'ppc-qmp-cmds.c', )) ppc_system_ss.add(when: 'CONFIG_TCG', if_true: files( @@ -57,4 +61,5 @@ ppc_system_ss.add(when: 'TARGET_PPC64', if_true: files( )) target_arch += {'ppc': ppc_ss} +target_user_arch += {'ppc': ppc_user_ss} target_system_arch += {'ppc': ppc_system_ss} diff --git a/target/ppc/monitor.c b/target/ppc/monitor.c new file mode 100644 index 000000000000..776a76602eaf --- /dev/null +++ b/target/ppc/monitor.c @@ -0,0 +1,25 @@ +/* + * QEMU PPC (monitor definitions) + * + * Copyright (c) 2003-2004 Fabrice Bellard + * + * SPDX-License-Identifier: MIT + */ + +#include "qemu/osdep.h" +#include "qemu/ctype.h" +#include "monitor/monitor.h" +#include "monitor/hmp-target.h" +#include "monitor/hmp.h" +#include "cpu.h" + +void hmp_info_tlb(Monitor *mon, const QDict *qdict) +{ + CPUArchState *env1 = mon_get_cpu_env(mon); + + if (!env1) { + monitor_printf(mon, "No CPU available\n"); + return; + } + dump_mmu(env1); +} diff --git a/target/ppc/ppc-qmp-cmds.c b/target/ppc/ppc-qmp-cmds.c index 7022564604f5..96228919966e 100644 --- a/target/ppc/ppc-qmp-cmds.c +++ b/target/ppc/ppc-qmp-cmds.c @@ -1,5 +1,5 @@ /* - * QEMU PPC (monitor definitions) + * QEMU PPC (QMP definitions) * * Copyright (c) 2003-2004 Fabrice Bellard * @@ -24,158 +24,12 @@ #include "qemu/osdep.h" #include "cpu.h" -#include "monitor/monitor.h" #include "qemu/ctype.h" -#include "monitor/hmp-target.h" -#include "monitor/hmp.h" #include "qapi/error.h" #include "qapi/qapi-commands-machine.h" #include "cpu-models.h" #include "cpu-qom.h" -static target_long monitor_get_ccr(Monitor *mon, const struct MonitorDef *md, - int val) -{ - CPUArchState *env = mon_get_cpu_env(mon); - unsigned int u; - - u = ppc_get_cr(env); - - return u; -} - -static target_long monitor_get_xer(Monitor *mon, const struct MonitorDef *md, - int val) -{ - CPUArchState *env = mon_get_cpu_env(mon); - return cpu_read_xer(env); -} - -static target_long monitor_get_decr(Monitor *mon, const struct MonitorDef *md, - int val) -{ - CPUArchState *env = mon_get_cpu_env(mon); - if (!env->tb_env) { - return 0; - } - return cpu_ppc_load_decr(env); -} - -static target_long monitor_get_tbu(Monitor *mon, const struct MonitorDef *md, - int val) -{ - CPUArchState *env = mon_get_cpu_env(mon); - if (!env->tb_env) { - return 0; - } - return cpu_ppc_load_tbu(env); -} - -static target_long monitor_get_tbl(Monitor *mon, const struct MonitorDef *md, - int val) -{ - CPUArchState *env = mon_get_cpu_env(mon); - if (!env->tb_env) { - return 0; - } - return cpu_ppc_load_tbl(env); -} - -void hmp_info_tlb(Monitor *mon, const QDict *qdict) -{ - CPUArchState *env1 = mon_get_cpu_env(mon); - - if (!env1) { - monitor_printf(mon, "No CPU available\n"); - return; - } - dump_mmu(env1); -} - -const MonitorDef monitor_defs[] = { - { "fpscr", offsetof(CPUPPCState, fpscr) }, - /* Next instruction pointer */ - { "nip|pc", offsetof(CPUPPCState, nip) }, - { "lr", offsetof(CPUPPCState, lr) }, - { "ctr", offsetof(CPUPPCState, ctr) }, - { "decr", 0, &monitor_get_decr, }, - { "ccr|cr", 0, &monitor_get_ccr, }, - /* Machine state register */ - { "xer", 0, &monitor_get_xer }, - { "msr", offsetof(CPUPPCState, msr) }, - { "tbu", 0, &monitor_get_tbu, }, -#if defined(TARGET_PPC64) - { "tb", 0, &monitor_get_tbl, }, -#else - { "tbl", 0, &monitor_get_tbl, }, -#endif - { NULL }, -}; - -const MonitorDef *target_monitor_defs(void) -{ - return monitor_defs; -} - -static int ppc_cpu_get_reg_num(const char *numstr, int maxnum, int *pregnum) -{ - int regnum; - char *endptr = NULL; - - if (!*numstr) { - return false; - } - - regnum = strtoul(numstr, &endptr, 10); - if (*endptr || (regnum >= maxnum)) { - return false; - } - *pregnum = regnum; - - return true; -} - -int target_get_monitor_def(CPUState *cs, const char *name, uint64_t *pval) -{ - int i, regnum; - CPUPPCState *env = cpu_env(cs); - - /* General purpose registers */ - if ((qemu_tolower(name[0]) == 'r') && - ppc_cpu_get_reg_num(name + 1, ARRAY_SIZE(env->gpr), ®num)) { - *pval = env->gpr[regnum]; - return 0; - } - - /* Floating point registers */ - if ((qemu_tolower(name[0]) == 'f') && - ppc_cpu_get_reg_num(name + 1, 32, ®num)) { - *pval = *cpu_fpr_ptr(env, regnum); - return 0; - } - - /* Special purpose registers */ - for (i = 0; i < ARRAY_SIZE(env->spr_cb); ++i) { - ppc_spr_t *spr = &env->spr_cb[i]; - - if (spr->name && (strcasecmp(name, spr->name) == 0)) { - *pval = env->spr[i]; - return 0; - } - } - - /* Segment registers */ -#if !defined(CONFIG_USER_ONLY) - if ((strncasecmp(name, "sr", 2) == 0) && - ppc_cpu_get_reg_num(name + 2, ARRAY_SIZE(env->sr), ®num)) { - *pval = env->sr[regnum]; - return 0; - } -#endif - - return -EINVAL; -} - CpuModelExpansionInfo * qmp_query_cpu_model_expansion(CpuModelExpansionType type, CpuModelInfo *model, diff --git a/target/ppc/translate.c b/target/ppc/translate.c index e9acfa239ec3..a09a6df93fd1 100644 --- a/target/ppc/translate.c +++ b/target/ppc/translate.c @@ -6575,7 +6575,7 @@ static void ppc_tr_tb_start(DisasContextBase *db, CPUState *cs) static void ppc_tr_insn_start(DisasContextBase *dcbase, CPUState *cs) { - tcg_gen_insn_start(dcbase->pc_next); + tcg_gen_insn_start(dcbase->pc_next, 0, 0); } static bool is_prefix_insn(DisasContext *ctx, uint32_t insn) diff --git a/target/ppc/translate/ppe-impl.c.inc b/target/ppc/translate/ppe-impl.c.inc index 0a0590344ea5..1c27facb8908 100644 --- a/target/ppc/translate/ppe-impl.c.inc +++ b/target/ppc/translate/ppe-impl.c.inc @@ -424,11 +424,15 @@ static bool do_mask_branch(DisasContext *ctx, arg_FCB * a, bool invert, shift = tcg_temp_new(); tcg_gen_andi_tl(shift, cpu_gpr[a->rb], 0x1f); tcg_gen_shr_tl(mask, tcg_constant_tl(0x80000000), shift); + if (invert) { + tcg_gen_not_tl(mask, mask); + } } else { - mask = tcg_constant_tl(PPC_BIT32(a->rb)); - } - if (invert) { - tcg_gen_not_tl(mask, mask); + target_ulong mask_const = PPC_BIT32(a->rb); + if (invert) { + mask_const = ~mask_const; + } + mask = tcg_constant_tl(mask_const); } /* apply mask to ra */ diff --git a/target/riscv/bitmanip_helper.c b/target/riscv/bitmanip_helper.c index e9c8d7f77803..1156a87dd31e 100644 --- a/target/riscv/bitmanip_helper.c +++ b/target/riscv/bitmanip_helper.c @@ -23,6 +23,8 @@ #include "exec/target_long.h" #include "exec/helper-proto.h" #include "tcg/tcg.h" +#include "qemu/crc32.h" +#include "qemu/crc32c.h" target_ulong HELPER(clmul)(target_ulong rs1, target_ulong rs2) { @@ -129,3 +131,21 @@ target_ulong HELPER(xperm8)(target_ulong rs1, target_ulong rs2) { return do_xperm(rs1, rs2, 3); } + +target_ulong HELPER(crc32)(target_ulong rs1, target_ulong sz) +{ + for (target_ulong i = 0; i < sz; i++) { + rs1 = crc32_table[rs1 & 0xFF] ^ (rs1 >> 8); + } + + return rs1; +} + +target_ulong HELPER(crc32c)(target_ulong rs1, target_ulong sz) +{ + for (target_ulong i = 0; i < sz; i++) { + rs1 = crc32c_table[rs1 & 0xFF] ^ (rs1 >> 8); + } + + return rs1; +} diff --git a/target/riscv/cpu-param.h b/target/riscv/cpu-param.h index cfdc67c258c7..ddb76a5300fe 100644 --- a/target/riscv/cpu-param.h +++ b/target/riscv/cpu-param.h @@ -9,21 +9,12 @@ #define RISCV_CPU_PARAM_H #if defined(TARGET_RISCV64) -# define TARGET_PHYS_ADDR_SPACE_BITS 56 /* 44-bit PPN */ # define TARGET_VIRT_ADDR_SPACE_BITS 48 /* sv48 */ #elif defined(TARGET_RISCV32) -# define TARGET_PHYS_ADDR_SPACE_BITS 34 /* 22-bit PPN */ # define TARGET_VIRT_ADDR_SPACE_BITS 32 /* sv32 */ #endif #define TARGET_PAGE_BITS 12 /* 4 KiB Pages */ -/* - * RISC-V-specific extra insn start words: - * 1: Original instruction opcode - * 2: more information about instruction - */ -#define TARGET_INSN_START_EXTRA_WORDS 2 - /* * The current MMU Modes are: * - U mode 0b000 diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index e56470a37484..22bab85288cd 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -1370,6 +1370,7 @@ const RISCVCPUMultiExtConfig riscv_cpu_vendor_exts[] = { MULTI_EXT_CFG_BOOL("xmipscbop", ext_xmipscbop, false), MULTI_EXT_CFG_BOOL("xmipscmov", ext_xmipscmov, false), MULTI_EXT_CFG_BOOL("xmipslsp", ext_xmipslsp, false), + MULTI_EXT_CFG_BOOL("xlrbr", ext_xlrbr, false), { }, }; @@ -3056,7 +3057,8 @@ static const TypeInfo riscv_cpu_type_infos[] = { .cfg.ext_zba = true, .cfg.ext_zbb = true, .cfg.ext_zbc = true, - .cfg.ext_zbs = true + .cfg.ext_zbs = true, + .cfg.ext_xlrbr = true ), DEFINE_RISCV_CPU(TYPE_RISCV_CPU_SIFIVE_E31, TYPE_RISCV_CPU_SIFIVE_E, diff --git a/target/riscv/cpu_cfg.h b/target/riscv/cpu_cfg.h index cd1cba797c62..211d0708ba4c 100644 --- a/target/riscv/cpu_cfg.h +++ b/target/riscv/cpu_cfg.h @@ -69,5 +69,6 @@ MATERIALISE_EXT_PREDICATE(xtheadmemidx) MATERIALISE_EXT_PREDICATE(xtheadmempair) MATERIALISE_EXT_PREDICATE(xtheadsync) MATERIALISE_EXT_PREDICATE(XVentanaCondOps) +MATERIALISE_EXT_PREDICATE(xlrbr); #endif diff --git a/target/riscv/cpu_cfg_fields.h.inc b/target/riscv/cpu_cfg_fields.h.inc index 70ec650abfa7..7ded97534c55 100644 --- a/target/riscv/cpu_cfg_fields.h.inc +++ b/target/riscv/cpu_cfg_fields.h.inc @@ -153,6 +153,7 @@ BOOL_FIELD(ext_XVentanaCondOps) BOOL_FIELD(ext_xmipscbop) BOOL_FIELD(ext_xmipscmov) BOOL_FIELD(ext_xmipslsp) +BOOL_FIELD(ext_xlrbr) BOOL_FIELD(mmu) BOOL_FIELD(pmp) diff --git a/target/riscv/gdbstub.c b/target/riscv/gdbstub.c index 1934f919c010..a053009ccd32 100644 --- a/target/riscv/gdbstub.c +++ b/target/riscv/gdbstub.c @@ -52,7 +52,7 @@ int riscv_cpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n) RISCVCPUClass *mcc = RISCV_CPU_GET_CLASS(cs); RISCVCPU *cpu = RISCV_CPU(cs); CPURISCVState *env = &cpu->env; - target_ulong tmp; + uint64_t tmp; if (n < 32) { tmp = env->gpr[n]; @@ -80,7 +80,7 @@ int riscv_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) RISCVCPU *cpu = RISCV_CPU(cs); CPURISCVState *env = &cpu->env; int length = 0; - target_ulong tmp; + uint64_t tmp; switch (mcc->def->misa_mxl_max) { case MXL_RV32: @@ -191,14 +191,15 @@ static int riscv_gdb_set_csr(CPUState *cs, uint8_t *mem_buf, int n) { RISCVCPU *cpu = RISCV_CPU(cs); CPURISCVState *env = &cpu->env; + const unsigned regsz = riscv_cpu_is_32bit(cpu) ? 4 : 8; if (n < CSR_TABLE_SIZE) { - target_ulong val = ldtul_p(mem_buf); + uint64_t val = ldn_p(mem_buf, regsz); int result; result = riscv_csrrw_debug(env, n, NULL, val, -1); if (result == RISCV_EXCP_NONE) { - return sizeof(target_ulong); + return regsz; } } return 0; @@ -214,7 +215,7 @@ static int riscv_gdb_get_virtual(CPUState *cs, GByteArray *buf, int n) CPURISCVState *env = &cpu->env; /* Per RiscV debug spec v1.0.0 rc4 */ - target_ulong vbit = (env->virt_enabled) ? BIT(2) : 0; + uint32_t vbit = (env->virt_enabled) ? BIT(2) : 0; return gdb_get_regl(buf, env->priv | vbit); #endif @@ -225,11 +226,12 @@ static int riscv_gdb_get_virtual(CPUState *cs, GByteArray *buf, int n) static int riscv_gdb_set_virtual(CPUState *cs, uint8_t *mem_buf, int n) { if (n == 0) { -#ifndef CONFIG_USER_ONLY RISCVCPU *cpu = RISCV_CPU(cs); + const unsigned regsz = riscv_cpu_is_32bit(cpu) ? 4 : 8; +#ifndef CONFIG_USER_ONLY CPURISCVState *env = &cpu->env; - target_ulong new_priv = ldtul_p(mem_buf) & 0x3; + target_ulong new_priv = ldn_p(mem_buf, regsz) & 0x3; bool new_virt = 0; if (new_priv == PRV_RESERVED) { @@ -237,7 +239,7 @@ static int riscv_gdb_set_virtual(CPUState *cs, uint8_t *mem_buf, int n) } if (new_priv != PRV_M) { - new_virt = (ldtul_p(mem_buf) & BIT(2)) >> 2; + new_virt = (ldn_p(mem_buf, regsz) & BIT(2)) >> 2; } if (riscv_has_ext(env, RVH) && new_virt != env->virt_enabled) { @@ -246,7 +248,7 @@ static int riscv_gdb_set_virtual(CPUState *cs, uint8_t *mem_buf, int n) riscv_cpu_set_mode(env, new_priv, new_virt); #endif - return sizeof(target_ulong); + return regsz; } return 0; } diff --git a/target/riscv/helper.h b/target/riscv/helper.h index b785456ee08d..7722c590bd8c 100644 --- a/target/riscv/helper.h +++ b/target/riscv/helper.h @@ -84,6 +84,8 @@ DEF_HELPER_FLAGS_1(unzip, TCG_CALL_NO_RWG_SE, tl, tl) DEF_HELPER_FLAGS_1(zip, TCG_CALL_NO_RWG_SE, tl, tl) DEF_HELPER_FLAGS_2(xperm4, TCG_CALL_NO_RWG_SE, tl, tl, tl) DEF_HELPER_FLAGS_2(xperm8, TCG_CALL_NO_RWG_SE, tl, tl, tl) +DEF_HELPER_FLAGS_2(crc32, TCG_CALL_NO_RWG_SE, tl, tl, tl) +DEF_HELPER_FLAGS_2(crc32c, TCG_CALL_NO_RWG_SE, tl, tl, tl) /* Floating Point - Half Precision */ DEF_HELPER_FLAGS_3(fadd_h, TCG_CALL_NO_RWG, i64, env, i64, i64) diff --git a/target/riscv/insn_trans/trans_xlrbr.c.inc b/target/riscv/insn_trans/trans_xlrbr.c.inc new file mode 100644 index 000000000000..63ad132feb67 --- /dev/null +++ b/target/riscv/insn_trans/trans_xlrbr.c.inc @@ -0,0 +1,55 @@ +/* + * RISC-V translation routines for xlrbr matching the unratified Zbr CRC32 + * bitmanip extension v0.93. + * + * Copyright (c) 2026 Rivos Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#define REQUIRE_XLRBR(ctx) do { \ + if (!ctx->cfg_ptr->ext_xlrbr) { \ + return false; \ + } \ +} while (0) + +static bool gen_crc(DisasContext *ctx, arg_r2 *a, + void (*func)(TCGv, TCGv, TCGv), TCGv tsz) +{ + REQUIRE_XLRBR(ctx); + TCGv dest = dest_gpr(ctx, a->rd); + TCGv src1 = get_gpr(ctx, a->rs1, EXT_NONE); + + func(dest, src1, tsz); + gen_set_gpr(ctx, a->rd, dest); + + return true; +} + +#define TRANS_CRC32(NAME, SIZE) \ + static bool trans_crc32_##NAME(DisasContext *ctx, arg_r2 *a) \ + { if (SIZE == 8) { REQUIRE_64BIT(ctx); }; \ + return gen_crc(ctx, a, gen_helper_crc32, tcg_constant_tl(SIZE)); } +#define TRANS_CRC32C(NAME, SIZE) \ + static bool trans_crc32c_##NAME(DisasContext *ctx, arg_r2 *a) \ + { if (SIZE == 8) { REQUIRE_64BIT(ctx); }; \ + return gen_crc(ctx, a, gen_helper_crc32c, tcg_constant_tl(SIZE)); } + +TRANS_CRC32(b, 1); +TRANS_CRC32(h, 2); +TRANS_CRC32(w, 4); +TRANS_CRC32(d, 8); +TRANS_CRC32C(b, 1); +TRANS_CRC32C(h, 2); +TRANS_CRC32C(w, 4); +TRANS_CRC32C(d, 8); diff --git a/target/riscv/meson.build b/target/riscv/meson.build index 3842c7c1a860..79f36abd63b4 100644 --- a/target/riscv/meson.build +++ b/target/riscv/meson.build @@ -5,6 +5,7 @@ gen = [ decodetree.process('xthead.decode', extra_args: '--static-decode=decode_xthead'), decodetree.process('XVentanaCondOps.decode', extra_args: '--static-decode=decode_XVentanaCodeOps'), decodetree.process('xmips.decode', extra_args: '--static-decode=decode_xmips'), + decodetree.process('xlrbr.decode', extra_args: '--static-decode=decode_xlrbr'), ] riscv_ss = ss.source_set() diff --git a/target/riscv/monitor.c b/target/riscv/monitor.c index 478fd392ac60..a9d311144420 100644 --- a/target/riscv/monitor.c +++ b/target/riscv/monitor.c @@ -19,6 +19,8 @@ */ #include "qemu/osdep.h" +#include "qemu/ctype.h" +#include "qemu/qemu-print.h" #include "cpu.h" #include "cpu_bits.h" #include "monitor/monitor.h" @@ -241,3 +243,136 @@ void hmp_info_mem(Monitor *mon, const QDict *qdict) mem_info_svxx(mon, env); } + +static bool reg_is_ulong_integer(CPURISCVState *env, const char *name, + target_ulong *val, bool is_gprh) +{ + const char * const *reg_names; + target_ulong *vals; + + if (is_gprh) { + reg_names = riscv_int_regnamesh; + vals = env->gprh; + } else { + reg_names = riscv_int_regnames; + vals = env->gpr; + } + + for (int i = 0; i < 32; i++) { + g_auto(GStrv) reg_name = g_strsplit(reg_names[i], "/", 2); + + g_assert(reg_name[0]); + g_assert(reg_name[1]); + + if (g_ascii_strcasecmp(reg_name[0], name) == 0 || + g_ascii_strcasecmp(reg_name[1], name) == 0) { + *val = vals[i]; + return true; + } + } + + return false; +} + +static bool reg_is_u64_fpu(CPURISCVState *env, const char *name, uint64_t *val) +{ + if (qemu_tolower(name[0]) != 'f') { + return false; + } + + for (int i = 0; i < 32; i++) { + g_auto(GStrv) reg_name = g_strsplit(riscv_fpr_regnames[i], "/", 2); + + g_assert(reg_name[0]); + g_assert(reg_name[1]); + + if (g_ascii_strcasecmp(reg_name[0], name) == 0 || + g_ascii_strcasecmp(reg_name[1], name) == 0) { + *val = env->fpr[i]; + return true; + } + } + + return false; +} + +static bool reg_is_vreg(const char *name) +{ + if (qemu_tolower(name[0]) != 'v' || strlen(name) > 3) { + return false; + } + + for (int i = 0; i < 32; i++) { + if (strcasecmp(name, riscv_rvv_regnames[i]) == 0) { + return true; + } + } + + return false; +} + +int target_get_monitor_def(CPUState *cs, const char *name, uint64_t *pval) +{ + CPURISCVState *env = &RISCV_CPU(cs)->env; + target_ulong val = 0; + uint64_t val64 = 0; + int i; + + if (reg_is_ulong_integer(env, name, &val, false) || + reg_is_ulong_integer(env, name, &val, true)) { + *pval = val; + return 0; + } + + if (reg_is_u64_fpu(env, name, &val64)) { + *pval = val64; + return 0; + } + + if (reg_is_vreg(name)) { + if (!riscv_cpu_cfg(env)->ext_zve32x) { + return -EINVAL; + } + + qemu_printf("Unable to print the value of vector " + "vreg '%s' from this API\n", name); + + /* + * We're returning 0 because returning -EINVAL triggers + * an 'unknown register' message in exp_unary() later, + * which feels ankward after our own error message. + */ + *pval = 0; + return 0; + } + + for (i = 0; i < ARRAY_SIZE(csr_ops); i++) { + RISCVException res; + int csrno = i; + + /* + * Early skip when possible since we're going + * through a lot of NULL entries. + */ + if (csr_ops[csrno].predicate == NULL) { + continue; + } + + if (strcasecmp(csr_ops[csrno].name, name) != 0) { + continue; + } + + res = riscv_csrrw_debug(env, csrno, &val, 0, 0); + + /* + * Rely on the smode, hmode, etc, predicates within csr.c + * to do the filtering of the registers that are present. + */ + if (res == RISCV_EXCP_NONE) { + *pval = val; + return 0; + } + } + + return -EINVAL; +} diff --git a/target/riscv/riscv-qmp-cmds.c b/target/riscv/riscv-qmp-cmds.c index d5e9bec0f86f..8a1856c50e0f 100644 --- a/target/riscv/riscv-qmp-cmds.c +++ b/target/riscv/riscv-qmp-cmds.c @@ -31,10 +31,6 @@ #include "qapi/qobject-input-visitor.h" #include "qapi/visitor.h" #include "qom/qom-qobject.h" -#include "qemu/ctype.h" -#include "qemu/qemu-print.h" -#include "monitor/hmp.h" -#include "monitor/hmp-target.h" #include "system/kvm.h" #include "system/tcg.h" #include "cpu-qom.h" @@ -244,149 +240,3 @@ CpuModelExpansionInfo *qmp_query_cpu_model_expansion(CpuModelExpansionType type, return expansion_info; } - -/* - * We have way too many potential CSRs and regs being added - * regularly to register them in a static array. - * - * Declare an empty array instead, making get_monitor_def() use - * the target_get_monitor_def() API directly. - */ -const MonitorDef monitor_defs[] = { { } }; -const MonitorDef *target_monitor_defs(void) -{ - return monitor_defs; -} - -static bool reg_is_ulong_integer(CPURISCVState *env, const char *name, - target_ulong *val, bool is_gprh) -{ - const char * const *reg_names; - target_ulong *vals; - - if (is_gprh) { - reg_names = riscv_int_regnamesh; - vals = env->gprh; - } else { - reg_names = riscv_int_regnames; - vals = env->gpr; - } - - for (int i = 0; i < 32; i++) { - g_auto(GStrv) reg_name = g_strsplit(reg_names[i], "/", 2); - - g_assert(reg_name[0]); - g_assert(reg_name[1]); - - if (g_ascii_strcasecmp(reg_name[0], name) == 0 || - g_ascii_strcasecmp(reg_name[1], name) == 0) { - *val = vals[i]; - return true; - } - } - - return false; -} - -static bool reg_is_u64_fpu(CPURISCVState *env, const char *name, uint64_t *val) -{ - if (qemu_tolower(name[0]) != 'f') { - return false; - } - - for (int i = 0; i < 32; i++) { - g_auto(GStrv) reg_name = g_strsplit(riscv_fpr_regnames[i], "/", 2); - - g_assert(reg_name[0]); - g_assert(reg_name[1]); - - if (g_ascii_strcasecmp(reg_name[0], name) == 0 || - g_ascii_strcasecmp(reg_name[1], name) == 0) { - *val = env->fpr[i]; - return true; - } - } - - return false; -} - -static bool reg_is_vreg(const char *name) -{ - if (qemu_tolower(name[0]) != 'v' || strlen(name) > 3) { - return false; - } - - for (int i = 0; i < 32; i++) { - if (strcasecmp(name, riscv_rvv_regnames[i]) == 0) { - return true; - } - } - - return false; -} - -int target_get_monitor_def(CPUState *cs, const char *name, uint64_t *pval) -{ - CPURISCVState *env = &RISCV_CPU(cs)->env; - target_ulong val = 0; - uint64_t val64 = 0; - int i; - - if (reg_is_ulong_integer(env, name, &val, false) || - reg_is_ulong_integer(env, name, &val, true)) { - *pval = val; - return 0; - } - - if (reg_is_u64_fpu(env, name, &val64)) { - *pval = val64; - return 0; - } - - if (reg_is_vreg(name)) { - if (!riscv_cpu_cfg(env)->ext_zve32x) { - return -EINVAL; - } - - qemu_printf("Unable to print the value of vector " - "vreg '%s' from this API\n", name); - - /* - * We're returning 0 because returning -EINVAL triggers - * an 'unknown register' message in exp_unary() later, - * which feels ankward after our own error message. - */ - *pval = 0; - return 0; - } - - for (i = 0; i < ARRAY_SIZE(csr_ops); i++) { - RISCVException res; - int csrno = i; - - /* - * Early skip when possible since we're going - * through a lot of NULL entries. - */ - if (csr_ops[csrno].predicate == NULL) { - continue; - } - - if (strcasecmp(csr_ops[csrno].name, name) != 0) { - continue; - } - - res = riscv_csrrw_debug(env, csrno, &val, 0, 0); - - /* - * Rely on the smode, hmode, etc, predicates within csr.c - * to do the filtering of the registers that are present. - */ - if (res == RISCV_EXCP_NONE) { - *pval = val; - return 0; - } - } - - return -EINVAL; -} diff --git a/target/riscv/translate.c b/target/riscv/translate.c index cb4f44360185..9b05eb3695ab 100644 --- a/target/riscv/translate.c +++ b/target/riscv/translate.c @@ -1213,9 +1213,11 @@ static uint32_t opcode_at(DisasContextBase *dcbase, target_ulong pc) #include "insn_trans/trans_rvbf16.c.inc" #include "decode-xthead.c.inc" #include "decode-xmips.c.inc" +#include "decode-xlrbr.c.inc" #include "insn_trans/trans_xthead.c.inc" #include "insn_trans/trans_xventanacondops.c.inc" #include "insn_trans/trans_xmips.c.inc" +#include "insn_trans/trans_xlrbr.c.inc" /* Include the auto-generated decoder for 16 bit insn */ #include "decode-insn16.c.inc" @@ -1235,6 +1237,7 @@ const RISCVDecoder decoder_table[] = { { has_xmips_p, decode_xmips}, { has_xthead_p, decode_xthead}, { has_XVentanaCondOps_p, decode_XVentanaCodeOps}, + { has_xlrbr_p, decode_xlrbr}, }; const size_t decoder_table_size = ARRAY_SIZE(decoder_table); diff --git a/target/riscv/xlrbr.decode b/target/riscv/xlrbr.decode new file mode 100644 index 000000000000..97397b2c09de --- /dev/null +++ b/target/riscv/xlrbr.decode @@ -0,0 +1,42 @@ +# +# Translation routines for the instructions of the xlrbr ISA extension +# (matching the draft encodings in the standard reserved encoding space for the +# unratified Zbr CRC32 bitmanip extension version 0.93). +# +# Copyright (c) 2026 Rivos Inc. +# +# SPDX-License-Identifier: GPL-2.0-or-later +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2 or later, as published by the Free Software Foundation. +# +# This program is distributed in the hope it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +# Fields: +%rs1 15:5 +%rd 7:5 + +# Argument sets: +&r2 rd rs1 !extern + +# Formats 32: +@r2 ....... ..... ..... ... ..... ....... &r2 %rs1 %rd + +# *** RV32 xlrbr extension *** +crc32_b 0110000 10000 ..... 001 ..... 0010011 @r2 +crc32_h 0110000 10001 ..... 001 ..... 0010011 @r2 +crc32_w 0110000 10010 ..... 001 ..... 0010011 @r2 +crc32c_b 0110000 11000 ..... 001 ..... 0010011 @r2 +crc32c_h 0110000 11001 ..... 001 ..... 0010011 @r2 +crc32c_w 0110000 11010 ..... 001 ..... 0010011 @r2 + +# *** RV64 xlrbr extension (in addition to RV32) *** +crc32_d 0110000 10011 ..... 001 ..... 0010011 @r2 +crc32c_d 0110000 11011 ..... 001 ..... 0010011 @r2 diff --git a/target/rx/cpu-param.h b/target/rx/cpu-param.h index 84934f3bcaf0..3806f41b2fe1 100644 --- a/target/rx/cpu-param.h +++ b/target/rx/cpu-param.h @@ -21,9 +21,6 @@ #define TARGET_PAGE_BITS 12 -#define TARGET_PHYS_ADDR_SPACE_BITS 32 #define TARGET_VIRT_ADDR_SPACE_BITS 32 -#define TARGET_INSN_START_EXTRA_WORDS 0 - #endif diff --git a/target/rx/gdbstub.c b/target/rx/gdbstub.c index 30074c9da7be..8be5141f5bc7 100644 --- a/target/rx/gdbstub.c +++ b/target/rx/gdbstub.c @@ -25,25 +25,25 @@ int rx_cpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n) switch (n) { case 0 ... 15: - return gdb_get_regl(mem_buf, env->regs[n]); + return gdb_get_reg32(mem_buf, env->regs[n]); case 16: - return gdb_get_regl(mem_buf, (env->psw_u) ? env->regs[0] : env->usp); + return gdb_get_reg32(mem_buf, (env->psw_u) ? env->regs[0] : env->usp); case 17: - return gdb_get_regl(mem_buf, (!env->psw_u) ? env->regs[0] : env->isp); + return gdb_get_reg32(mem_buf, (!env->psw_u) ? env->regs[0] : env->isp); case 18: - return gdb_get_regl(mem_buf, rx_cpu_pack_psw(env)); + return gdb_get_reg32(mem_buf, rx_cpu_pack_psw(env)); case 19: - return gdb_get_regl(mem_buf, env->pc); + return gdb_get_reg32(mem_buf, env->pc); case 20: - return gdb_get_regl(mem_buf, env->intb); + return gdb_get_reg32(mem_buf, env->intb); case 21: - return gdb_get_regl(mem_buf, env->bpsw); + return gdb_get_reg32(mem_buf, env->bpsw); case 22: - return gdb_get_regl(mem_buf, env->bpc); + return gdb_get_reg32(mem_buf, env->bpc); case 23: - return gdb_get_regl(mem_buf, env->fintv); + return gdb_get_reg32(mem_buf, env->fintv); case 24: - return gdb_get_regl(mem_buf, env->fpsw); + return gdb_get_reg32(mem_buf, env->fpsw); case 25: return 0; } diff --git a/target/rx/meson.build b/target/rx/meson.build index 86dc231eb0aa..d80ced11e2f8 100644 --- a/target/rx/meson.build +++ b/target/rx/meson.build @@ -9,8 +9,10 @@ rx_ss.add(files( 'op_helper.c', 'helper.c', 'cpu.c', - 'gdbstub.c', 'disas.c')) +rx_common_system_ss = ss.source_set() +rx_common_system_ss.add(files('gdbstub.c')) + target_arch += {'rx': rx_ss} -target_common_system_arch += {'rx': ss.source_set()} +target_common_system_arch += {'rx': rx_common_system_ss} diff --git a/target/rx/translate.c b/target/rx/translate.c index 26d41548294f..a245b9db8fe7 100644 --- a/target/rx/translate.c +++ b/target/rx/translate.c @@ -2217,7 +2217,7 @@ static void rx_tr_insn_start(DisasContextBase *dcbase, CPUState *cs) { DisasContext *ctx = container_of(dcbase, DisasContext, base); - tcg_gen_insn_start(ctx->base.pc_next); + tcg_gen_insn_start(ctx->base.pc_next, 0, 0); } static void rx_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) diff --git a/target/s390x/cpu-param.h b/target/s390x/cpu-param.h index abfae3bedfb1..caef5a0e5c89 100644 --- a/target/s390x/cpu-param.h +++ b/target/s390x/cpu-param.h @@ -9,9 +9,6 @@ #define S390_CPU_PARAM_H #define TARGET_PAGE_BITS 12 -#define TARGET_PHYS_ADDR_SPACE_BITS 64 #define TARGET_VIRT_ADDR_SPACE_BITS 64 -#define TARGET_INSN_START_EXTRA_WORDS 2 - #endif diff --git a/target/s390x/meson.build b/target/s390x/meson.build index 096b7dcc42e5..6f98ce34d731 100644 --- a/target/s390x/meson.build +++ b/target/s390x/meson.build @@ -3,7 +3,6 @@ s390x_ss.add(files( 'cpu.c', 'cpu_features.c', 'cpu_models.c', - 'gdbstub.c', 'interrupt.c', 'cpu-dump.c', )) @@ -34,11 +33,13 @@ s390x_common_system_ss.add(files( 'sigp.c', 'cpu-system.c', 'cpu_models_system.c', + 'gdbstub.c', )) s390x_user_ss = ss.source_set() s390x_user_ss.add(files( 'cpu_models_user.c', + 'gdbstub.c', )) subdir('tcg') diff --git a/target/sh4/cpu-param.h b/target/sh4/cpu-param.h index f328715ee862..c3b8114e538b 100644 --- a/target/sh4/cpu-param.h +++ b/target/sh4/cpu-param.h @@ -9,13 +9,10 @@ #define SH4_CPU_PARAM_H #define TARGET_PAGE_BITS 12 /* 4k */ -#define TARGET_PHYS_ADDR_SPACE_BITS 32 #ifdef CONFIG_USER_ONLY # define TARGET_VIRT_ADDR_SPACE_BITS 31 #else # define TARGET_VIRT_ADDR_SPACE_BITS 32 #endif -#define TARGET_INSN_START_EXTRA_WORDS 1 - #endif diff --git a/target/sh4/gdbstub.c b/target/sh4/gdbstub.c index 75926d4e049f..4f36e800d2c2 100644 --- a/target/sh4/gdbstub.c +++ b/target/sh4/gdbstub.c @@ -31,43 +31,43 @@ int superh_cpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n) switch (n) { case 0 ... 7: if ((env->sr & (1u << SR_MD)) && (env->sr & (1u << SR_RB))) { - return gdb_get_regl(mem_buf, env->gregs[n + 16]); + return gdb_get_reg32(mem_buf, env->gregs[n + 16]); } else { - return gdb_get_regl(mem_buf, env->gregs[n]); + return gdb_get_reg32(mem_buf, env->gregs[n]); } case 8 ... 15: - return gdb_get_regl(mem_buf, env->gregs[n]); + return gdb_get_reg32(mem_buf, env->gregs[n]); case 16: - return gdb_get_regl(mem_buf, env->pc); + return gdb_get_reg32(mem_buf, env->pc); case 17: - return gdb_get_regl(mem_buf, env->pr); + return gdb_get_reg32(mem_buf, env->pr); case 18: - return gdb_get_regl(mem_buf, env->gbr); + return gdb_get_reg32(mem_buf, env->gbr); case 19: - return gdb_get_regl(mem_buf, env->vbr); + return gdb_get_reg32(mem_buf, env->vbr); case 20: - return gdb_get_regl(mem_buf, env->mach); + return gdb_get_reg32(mem_buf, env->mach); case 21: - return gdb_get_regl(mem_buf, env->macl); + return gdb_get_reg32(mem_buf, env->macl); case 22: - return gdb_get_regl(mem_buf, cpu_read_sr(env)); + return gdb_get_reg32(mem_buf, cpu_read_sr(env)); case 23: - return gdb_get_regl(mem_buf, env->fpul); + return gdb_get_reg32(mem_buf, env->fpul); case 24: - return gdb_get_regl(mem_buf, env->fpscr); + return gdb_get_reg32(mem_buf, env->fpscr); case 25 ... 40: if (env->fpscr & FPSCR_FR) { return gdb_get_reg32(mem_buf, env->fregs[n - 9]); } return gdb_get_reg32(mem_buf, env->fregs[n - 25]); case 41: - return gdb_get_regl(mem_buf, env->ssr); + return gdb_get_reg32(mem_buf, env->ssr); case 42: - return gdb_get_regl(mem_buf, env->spc); + return gdb_get_reg32(mem_buf, env->spc); case 43 ... 50: - return gdb_get_regl(mem_buf, env->gregs[n - 43]); + return gdb_get_reg32(mem_buf, env->gregs[n - 43]); case 51 ... 58: - return gdb_get_regl(mem_buf, env->gregs[n - (51 - 16)]); + return gdb_get_reg32(mem_buf, env->gregs[n - (51 - 16)]); } return 0; diff --git a/target/sh4/meson.build b/target/sh4/meson.build index 221700bcf8c8..3861d9b194ae 100644 --- a/target/sh4/meson.build +++ b/target/sh4/meson.build @@ -1,14 +1,20 @@ sh4_ss = ss.source_set() sh4_ss.add(files( 'cpu.c', - 'gdbstub.c', 'helper.c', 'op_helper.c', 'translate.c', )) -sh4_system_ss = ss.source_set() -sh4_system_ss.add(files('monitor.c')) +sh4_common_system_ss = ss.source_set() +sh4_common_system_ss.add(files( + 'gdbstub.c', + 'monitor.c', +)) + +sh4_user_ss = ss.source_set() +sh4_user_ss.add(files('gdbstub.c')) target_arch += {'sh4': sh4_ss} -target_common_system_arch += {'sh4': sh4_system_ss} +target_user_arch += {'sh4': sh4_user_ss} +target_common_system_arch += {'sh4': sh4_common_system_ss} diff --git a/target/sh4/translate.c b/target/sh4/translate.c index b3ae0a3814c7..b1057727c55e 100644 --- a/target/sh4/translate.c +++ b/target/sh4/translate.c @@ -2181,7 +2181,7 @@ static void decode_gusa(DisasContext *ctx, CPUSH4State *env) * tb->icount * insn_start. */ for (i = 1; i < max_insns; ++i) { - tcg_gen_insn_start(pc + i * 2, ctx->envflags); + tcg_gen_insn_start(pc + i * 2, ctx->envflags, 0); ctx->base.insn_start = tcg_last_op(); } } @@ -2241,7 +2241,7 @@ static void sh4_tr_insn_start(DisasContextBase *dcbase, CPUState *cs) { DisasContext *ctx = container_of(dcbase, DisasContext, base); - tcg_gen_insn_start(ctx->base.pc_next, ctx->envflags); + tcg_gen_insn_start(ctx->base.pc_next, ctx->envflags, 0); } static void sh4_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) diff --git a/target/sparc/cpu-param.h b/target/sparc/cpu-param.h index 45eea9d6bac8..7ec4ac84dbb2 100644 --- a/target/sparc/cpu-param.h +++ b/target/sparc/cpu-param.h @@ -9,7 +9,6 @@ #ifdef TARGET_SPARC64 # define TARGET_PAGE_BITS 13 /* 8k */ -# define TARGET_PHYS_ADDR_SPACE_BITS 41 # ifdef TARGET_ABI32 # define TARGET_VIRT_ADDR_SPACE_BITS 32 # else @@ -17,10 +16,7 @@ # endif #else # define TARGET_PAGE_BITS 12 /* 4k */ -# define TARGET_PHYS_ADDR_SPACE_BITS 36 # define TARGET_VIRT_ADDR_SPACE_BITS 32 #endif -#define TARGET_INSN_START_EXTRA_WORDS 1 - #endif diff --git a/target/sparc/cpu.c b/target/sparc/cpu.c index 3991681d1d1b..1493336e7a2b 100644 --- a/target/sparc/cpu.c +++ b/target/sparc/cpu.c @@ -897,6 +897,8 @@ static void sparc_cpu_realizefn(DeviceState *dev, Error **errp) return; } + sparc_cpu_register_gdb_regs(cs); + qemu_init_vcpu(cs); scc->parent_realize(dev, errp); @@ -1091,10 +1093,9 @@ static void sparc_cpu_class_init(ObjectClass *oc, const void *data) cc->disas_set_info = cpu_sparc_disas_set_info; #if defined(TARGET_SPARC64) && !defined(TARGET_ABI32) - cc->gdb_core_xml_file = "sparc64-core.xml"; - cc->gdb_num_core_regs = 86; + cc->gdb_core_xml_file = "sparc64-cpu.xml"; #else - cc->gdb_num_core_regs = 72; + cc->gdb_core_xml_file = "sparc32-cpu.xml"; #endif cc->tcg_ops = &sparc_tcg_ops; } diff --git a/target/sparc/cpu.h b/target/sparc/cpu.h index 7169a502432d..0139732e4cc2 100644 --- a/target/sparc/cpu.h +++ b/target/sparc/cpu.h @@ -586,6 +586,7 @@ hwaddr sparc_cpu_get_phys_page_debug(CPUState *cpu, vaddr addr); void sparc_cpu_do_interrupt(CPUState *cpu); int sparc_cpu_gdb_read_register(CPUState *cpu, GByteArray *buf, int reg); int sparc_cpu_gdb_write_register(CPUState *cpu, uint8_t *buf, int reg); +void sparc_cpu_register_gdb_regs(CPUState *cs); G_NORETURN void sparc_cpu_do_unaligned_access(CPUState *cpu, vaddr addr, MMUAccessType access_type, int mmu_idx, diff --git a/target/sparc/gdbstub.c b/target/sparc/gdbstub.c index 134617fb232b..792bf70a145a 100644 --- a/target/sparc/gdbstub.c +++ b/target/sparc/gdbstub.c @@ -18,14 +18,18 @@ * License along with this library; if not, see . */ #include "qemu/osdep.h" +#include "exec/gdbstub.h" #include "cpu.h" #include "gdbstub/helpers.h" -#ifdef TARGET_ABI32 -#define gdb_get_rega(buf, val) gdb_get_reg32(buf, val) +static inline int gdb_get_rega(GByteArray *buf, uint64_t val) +{ +#if defined(TARGET_ABI32) || !defined(TARGET_SPARC64) + return gdb_get_reg32(buf, val); #else -#define gdb_get_rega(buf, val) gdb_get_regl(buf, val) + return gdb_get_reg64(buf, val); #endif +} int sparc_cpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n) { @@ -39,179 +43,248 @@ int sparc_cpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n) /* register window */ return gdb_get_rega(mem_buf, env->regwptr[n - 8]); } + return 0; +} + +static int sparc_fpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n) +{ + CPUSPARCState *env = cpu_env(cs); + #if defined(TARGET_ABI32) || !defined(TARGET_SPARC64) - if (n < 64) { + if (n < 32) { /* fprs */ if (n & 1) { - return gdb_get_reg32(mem_buf, env->fpr[(n - 32) / 2].l.lower); + return gdb_get_reg32(mem_buf, env->fpr[n / 2].l.lower); } else { - return gdb_get_reg32(mem_buf, env->fpr[(n - 32) / 2].l.upper); + return gdb_get_reg32(mem_buf, env->fpr[n / 2].l.upper); } } +#else + if (n < 32) { + /* f0-f31 */ + if (n & 1) { + return gdb_get_reg32(mem_buf, env->fpr[n / 2].l.lower); + } else { + return gdb_get_reg32(mem_buf, env->fpr[n / 2].l.upper); + } + } + if (n < 48) { + /* f32-f62 (16 double width registers, even register numbers only) + * n == 32: f32 : env->fpr[16] + * n == 33: f34 : env->fpr[17] + * etc... + * n == 47: f62 : env->fpr[31] + */ + return gdb_get_reg64(mem_buf, env->fpr[(n - 32) + 16].ll); + } +#endif + return 0; +} + +static int sparc_cp0_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n) +{ + CPUSPARCState *env = cpu_env(cs); + +#if defined(TARGET_ABI32) || !defined(TARGET_SPARC64) /* Y, PSR, WIM, TBR, PC, NPC, FPSR, CPSR */ switch (n) { - case 64: + case 0: return gdb_get_rega(mem_buf, env->y); - case 65: + case 1: return gdb_get_rega(mem_buf, cpu_get_psr(env)); - case 66: + case 2: return gdb_get_rega(mem_buf, env->wim); - case 67: + case 3: return gdb_get_rega(mem_buf, env->tbr); - case 68: + case 4: return gdb_get_rega(mem_buf, env->pc); - case 69: + case 5: return gdb_get_rega(mem_buf, env->npc); - case 70: + case 6: return gdb_get_rega(mem_buf, cpu_get_fsr(env)); - case 71: + case 7: return gdb_get_rega(mem_buf, 0); /* csr */ - default: - return gdb_get_rega(mem_buf, 0); } #else - if (n < 64) { - /* f0-f31 */ - if (n & 1) { - return gdb_get_reg32(mem_buf, env->fpr[(n - 32) / 2].l.lower); - } else { - return gdb_get_reg32(mem_buf, env->fpr[(n - 32) / 2].l.upper); - } - } - if (n < 80) { - /* f32-f62 (16 double width registers, even register numbers only) - * n == 64: f32 : env->fpr[16] - * n == 65: f34 : env->fpr[17] - * etc... - * n == 79: f62 : env->fpr[31] - */ - return gdb_get_reg64(mem_buf, env->fpr[(n - 64) + 16].ll); - } switch (n) { - case 80: + case 0: return gdb_get_regl(mem_buf, env->pc); - case 81: + case 1: return gdb_get_regl(mem_buf, env->npc); - case 82: + case 2: return gdb_get_regl(mem_buf, (cpu_get_ccr(env) << 32) | ((env->asi & 0xff) << 24) | ((env->pstate & 0xfff) << 8) | cpu_get_cwp64(env)); - case 83: + case 3: return gdb_get_regl(mem_buf, cpu_get_fsr(env)); - case 84: + case 4: return gdb_get_regl(mem_buf, env->fprs); - case 85: + case 5: return gdb_get_regl(mem_buf, env->y); } #endif return 0; } +static unsigned sparc_gdb_register_bytes(void) +{ +#ifdef CONFIG_USER_ONLY +# if defined(TARGET_ABI32) + return 4; +# endif +#endif + return target_long_bits() / 8; +} + int sparc_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) { SPARCCPU *cpu = SPARC_CPU(cs); CPUSPARCState *env = &cpu->env; -#if defined(TARGET_ABI32) - uint32_t tmp; - - tmp = ldl_p(mem_buf); -#else - target_ulong tmp; - - tmp = ldtul_p(mem_buf); -#endif + const unsigned regsz = sparc_gdb_register_bytes(); + uint64_t tmp = ldn_p(mem_buf, regsz); if (n < 8) { /* g0..g7 */ env->gregs[n] = tmp; - } else if (n < 32) { + } else { /* register window */ env->regwptr[n - 8] = tmp; } + return regsz; +} + +static int sparc_fpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) +{ + CPUSPARCState *env = cpu_env(cs); + #if defined(TARGET_ABI32) || !defined(TARGET_SPARC64) - else if (n < 64) { - /* fprs */ - /* f0-f31 */ - if (n & 1) { - env->fpr[(n - 32) / 2].l.lower = tmp; - } else { - env->fpr[(n - 32) / 2].l.upper = tmp; - } + uint32_t tmp; + + tmp = ldl_p(mem_buf); + + /* fprs */ + /* f0-f31 */ + if (n & 1) { + env->fpr[n / 2].l.lower = tmp; } else { - /* Y, PSR, WIM, TBR, PC, NPC, FPSR, CPSR */ - switch (n) { - case 64: - env->y = tmp; - break; - case 65: - cpu_put_psr(env, tmp); - break; - case 66: - env->wim = tmp; - break; - case 67: - env->tbr = tmp; - break; - case 68: - env->pc = tmp; - break; - case 69: - env->npc = tmp; - break; - case 70: - cpu_put_fsr(env, tmp); - break; - default: - return 0; - } + env->fpr[n / 2].l.upper = tmp; } + return 4; #else - else if (n < 64) { + if (n < 32) { /* f0-f31 */ - tmp = ldl_p(mem_buf); + uint32_t tmp = ldl_p(mem_buf); if (n & 1) { - env->fpr[(n - 32) / 2].l.lower = tmp; + env->fpr[n / 2].l.lower = tmp; } else { - env->fpr[(n - 32) / 2].l.upper = tmp; + env->fpr[n / 2].l.upper = tmp; } return 4; - } else if (n < 80) { + } else { + uint64_t tmp = ldq_p(mem_buf); /* f32-f62 (16 double width registers, even register numbers only) - * n == 64: f32 : env->fpr[16] - * n == 65: f34 : env->fpr[17] + * n == 32: f32 : env->fpr[16] + * n == 33: f34 : env->fpr[17] * etc... - * n == 79: f62 : env->fpr[31] + * n == 47: f62 : env->fpr[31] */ - env->fpr[(n - 64) + 16].ll = tmp; - } else { - switch (n) { - case 80: - env->pc = tmp; - break; - case 81: - env->npc = tmp; - break; - case 82: - cpu_put_ccr(env, tmp >> 32); - env->asi = (tmp >> 24) & 0xff; - env->pstate = (tmp >> 8) & 0xfff; - cpu_put_cwp64(env, tmp & 0xff); - break; - case 83: - cpu_put_fsr(env, tmp); - break; - case 84: - env->fprs = tmp; - break; - case 85: - env->y = tmp; - break; - default: - return 0; - } + env->fpr[(n - 32) + 16].ll = tmp; + } + return 8; +#endif +} + +static int sparc_cp0_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) +{ + CPUSPARCState *env = cpu_env(cs); + +#if defined(TARGET_ABI32) || !defined(TARGET_SPARC64) + uint32_t tmp; + + tmp = ldl_p(mem_buf); + + /* Y, PSR, WIM, TBR, PC, NPC, FPSR, CPSR */ + switch (n) { + case 0: + env->y = tmp; + break; + case 1: + cpu_put_psr(env, tmp); + break; + case 2: + env->wim = tmp; + break; + case 3: + env->tbr = tmp; + break; + case 4: + env->pc = tmp; + break; + case 5: + env->npc = tmp; + break; + case 6: + cpu_put_fsr(env, tmp); + break; + default: + return 0; + } + return 4; +#else + uint64_t tmp; + + tmp = ldq_p(mem_buf); + + switch (n) { + case 0: + env->pc = tmp; + break; + case 1: + env->npc = tmp; + break; + case 2: + cpu_put_ccr(env, tmp >> 32); + env->asi = (tmp >> 24) & 0xff; + env->pstate = (tmp >> 8) & 0xfff; + cpu_put_cwp64(env, tmp & 0xff); + break; + case 3: + cpu_put_fsr(env, tmp); + break; + case 4: + env->fprs = tmp; + break; + case 5: + env->y = tmp; + break; + default: + return 0; } return 8; #endif } + +void sparc_cpu_register_gdb_regs(CPUState *cs) +{ +#if defined(TARGET_ABI32) || !defined(TARGET_SPARC64) + gdb_register_coprocessor(cs, sparc_fpu_gdb_read_register, + sparc_fpu_gdb_write_register, + gdb_find_static_feature("sparc32-fpu.xml"), + 0); + gdb_register_coprocessor(cs, sparc_cp0_gdb_read_register, + sparc_cp0_gdb_write_register, + gdb_find_static_feature("sparc32-cp0.xml"), + 0); +#else + gdb_register_coprocessor(cs, sparc_fpu_gdb_read_register, + sparc_fpu_gdb_write_register, + gdb_find_static_feature("sparc64-fpu.xml"), + 0); + gdb_register_coprocessor(cs, sparc_cp0_gdb_read_register, + sparc_cp0_gdb_write_register, + gdb_find_static_feature("sparc64-cp0.xml"), + 0); +#endif +} diff --git a/target/sparc/monitor.c b/target/sparc/monitor.c index 73f15aa272df..a60671a60a42 100644 --- a/target/sparc/monitor.c +++ b/target/sparc/monitor.c @@ -39,114 +39,8 @@ void hmp_info_tlb(Monitor *mon, const QDict *qdict) dump_mmu(env1); } -#ifndef TARGET_SPARC64 -static target_long monitor_get_psr(Monitor *mon, const struct MonitorDef *md, - int val) -{ - CPUArchState *env = mon_get_cpu_env(mon); - - return cpu_get_psr(env); -} -#endif - -static target_long monitor_get_reg(Monitor *mon, const struct MonitorDef *md, - int val) -{ - CPUArchState *env = mon_get_cpu_env(mon); - return env->regwptr[val]; -} - const MonitorDef monitor_defs[] = { - { "g0", offsetof(CPUSPARCState, gregs[0]) }, - { "g1", offsetof(CPUSPARCState, gregs[1]) }, - { "g2", offsetof(CPUSPARCState, gregs[2]) }, - { "g3", offsetof(CPUSPARCState, gregs[3]) }, - { "g4", offsetof(CPUSPARCState, gregs[4]) }, - { "g5", offsetof(CPUSPARCState, gregs[5]) }, - { "g6", offsetof(CPUSPARCState, gregs[6]) }, - { "g7", offsetof(CPUSPARCState, gregs[7]) }, - { "o0", 0, monitor_get_reg }, - { "o1", 1, monitor_get_reg }, - { "o2", 2, monitor_get_reg }, - { "o3", 3, monitor_get_reg }, - { "o4", 4, monitor_get_reg }, - { "o5", 5, monitor_get_reg }, - { "o6", 6, monitor_get_reg }, - { "o7", 7, monitor_get_reg }, - { "l0", 8, monitor_get_reg }, - { "l1", 9, monitor_get_reg }, - { "l2", 10, monitor_get_reg }, - { "l3", 11, monitor_get_reg }, - { "l4", 12, monitor_get_reg }, - { "l5", 13, monitor_get_reg }, - { "l6", 14, monitor_get_reg }, - { "l7", 15, monitor_get_reg }, - { "i0", 16, monitor_get_reg }, - { "i1", 17, monitor_get_reg }, - { "i2", 18, monitor_get_reg }, - { "i3", 19, monitor_get_reg }, - { "i4", 20, monitor_get_reg }, - { "i5", 21, monitor_get_reg }, - { "i6", 22, monitor_get_reg }, - { "i7", 23, monitor_get_reg }, - { "pc", offsetof(CPUSPARCState, pc) }, - { "npc", offsetof(CPUSPARCState, npc) }, - { "y", offsetof(CPUSPARCState, y) }, -#ifndef TARGET_SPARC64 - { "psr", 0, &monitor_get_psr, }, - { "wim", offsetof(CPUSPARCState, wim) }, -#endif - { "tbr", offsetof(CPUSPARCState, tbr) }, - { "fsr", offsetof(CPUSPARCState, fsr) }, - { "f0", offsetof(CPUSPARCState, fpr[0].l.upper) }, - { "f1", offsetof(CPUSPARCState, fpr[0].l.lower) }, - { "f2", offsetof(CPUSPARCState, fpr[1].l.upper) }, - { "f3", offsetof(CPUSPARCState, fpr[1].l.lower) }, - { "f4", offsetof(CPUSPARCState, fpr[2].l.upper) }, - { "f5", offsetof(CPUSPARCState, fpr[2].l.lower) }, - { "f6", offsetof(CPUSPARCState, fpr[3].l.upper) }, - { "f7", offsetof(CPUSPARCState, fpr[3].l.lower) }, - { "f8", offsetof(CPUSPARCState, fpr[4].l.upper) }, - { "f9", offsetof(CPUSPARCState, fpr[4].l.lower) }, - { "f10", offsetof(CPUSPARCState, fpr[5].l.upper) }, - { "f11", offsetof(CPUSPARCState, fpr[5].l.lower) }, - { "f12", offsetof(CPUSPARCState, fpr[6].l.upper) }, - { "f13", offsetof(CPUSPARCState, fpr[6].l.lower) }, - { "f14", offsetof(CPUSPARCState, fpr[7].l.upper) }, - { "f15", offsetof(CPUSPARCState, fpr[7].l.lower) }, - { "f16", offsetof(CPUSPARCState, fpr[8].l.upper) }, - { "f17", offsetof(CPUSPARCState, fpr[8].l.lower) }, - { "f18", offsetof(CPUSPARCState, fpr[9].l.upper) }, - { "f19", offsetof(CPUSPARCState, fpr[9].l.lower) }, - { "f20", offsetof(CPUSPARCState, fpr[10].l.upper) }, - { "f21", offsetof(CPUSPARCState, fpr[10].l.lower) }, - { "f22", offsetof(CPUSPARCState, fpr[11].l.upper) }, - { "f23", offsetof(CPUSPARCState, fpr[11].l.lower) }, - { "f24", offsetof(CPUSPARCState, fpr[12].l.upper) }, - { "f25", offsetof(CPUSPARCState, fpr[12].l.lower) }, - { "f26", offsetof(CPUSPARCState, fpr[13].l.upper) }, - { "f27", offsetof(CPUSPARCState, fpr[13].l.lower) }, - { "f28", offsetof(CPUSPARCState, fpr[14].l.upper) }, - { "f29", offsetof(CPUSPARCState, fpr[14].l.lower) }, - { "f30", offsetof(CPUSPARCState, fpr[15].l.upper) }, - { "f31", offsetof(CPUSPARCState, fpr[15].l.lower) }, #ifdef TARGET_SPARC64 - { "f32", offsetof(CPUSPARCState, fpr[16]) }, - { "f34", offsetof(CPUSPARCState, fpr[17]) }, - { "f36", offsetof(CPUSPARCState, fpr[18]) }, - { "f38", offsetof(CPUSPARCState, fpr[19]) }, - { "f40", offsetof(CPUSPARCState, fpr[20]) }, - { "f42", offsetof(CPUSPARCState, fpr[21]) }, - { "f44", offsetof(CPUSPARCState, fpr[22]) }, - { "f46", offsetof(CPUSPARCState, fpr[23]) }, - { "f48", offsetof(CPUSPARCState, fpr[24]) }, - { "f50", offsetof(CPUSPARCState, fpr[25]) }, - { "f52", offsetof(CPUSPARCState, fpr[26]) }, - { "f54", offsetof(CPUSPARCState, fpr[27]) }, - { "f56", offsetof(CPUSPARCState, fpr[28]) }, - { "f58", offsetof(CPUSPARCState, fpr[29]) }, - { "f60", offsetof(CPUSPARCState, fpr[30]) }, - { "f62", offsetof(CPUSPARCState, fpr[31]) }, { "asi", offsetof(CPUSPARCState, asi) }, { "pstate", offsetof(CPUSPARCState, pstate) }, { "cansave", offsetof(CPUSPARCState, cansave) }, @@ -154,7 +48,6 @@ const MonitorDef monitor_defs[] = { { "otherwin", offsetof(CPUSPARCState, otherwin) }, { "wstate", offsetof(CPUSPARCState, wstate) }, { "cleanwin", offsetof(CPUSPARCState, cleanwin) }, - { "fprs", offsetof(CPUSPARCState, fprs), NULL, MD_I32 }, #endif { NULL }, }; diff --git a/target/sparc/translate.c b/target/sparc/translate.c index 57b50ff8b9a3..7e8558dbbd88 100644 --- a/target/sparc/translate.c +++ b/target/sparc/translate.c @@ -5735,7 +5735,7 @@ static void sparc_tr_insn_start(DisasContextBase *dcbase, CPUState *cs) g_assert_not_reached(); } } - tcg_gen_insn_start(dc->pc, npc); + tcg_gen_insn_start(dc->pc, npc, 0); } static void sparc_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) diff --git a/target/tricore/cpu-param.h b/target/tricore/cpu-param.h index eb33a67c4194..7a5f67fe15e4 100644 --- a/target/tricore/cpu-param.h +++ b/target/tricore/cpu-param.h @@ -9,9 +9,6 @@ #define TRICORE_CPU_PARAM_H #define TARGET_PAGE_BITS 14 -#define TARGET_PHYS_ADDR_SPACE_BITS 32 #define TARGET_VIRT_ADDR_SPACE_BITS 32 -#define TARGET_INSN_START_EXTRA_WORDS 0 - #endif diff --git a/target/tricore/meson.build b/target/tricore/meson.build index f40daee61ed5..76cd78e990e7 100644 --- a/target/tricore/meson.build +++ b/target/tricore/meson.build @@ -5,11 +5,15 @@ tricore_ss.add(files( 'helper.c', 'op_helper.c', 'translate.c', - 'gdbstub.c', )) tricore_ss.add(zlib) -tricore_system_ss = ss.source_set() +tricore_user_ss = ss.source_set() +tricore_user_ss.add(files('gdbstub.c')) + +tricore_common_system_ss = ss.source_set() +tricore_common_system_ss.add(files('gdbstub.c')) target_arch += {'tricore': tricore_ss} -target_common_system_arch += {'tricore': tricore_system_ss} +target_user_arch += {'tricore': tricore_user_ss} +target_common_system_arch += {'tricore': tricore_common_system_ss} diff --git a/target/tricore/translate.c b/target/tricore/translate.c index 18d8726af6d7..0eaf7a82f87e 100644 --- a/target/tricore/translate.c +++ b/target/tricore/translate.c @@ -8410,7 +8410,7 @@ static void tricore_tr_insn_start(DisasContextBase *dcbase, CPUState *cpu) { DisasContext *ctx = container_of(dcbase, DisasContext, base); - tcg_gen_insn_start(ctx->base.pc_next); + tcg_gen_insn_start(ctx->base.pc_next, 0, 0); } static bool insn_crosses_page(DisasContext *ctx, CPUTriCoreState *env) diff --git a/target/xtensa/cpu-param.h b/target/xtensa/cpu-param.h index 7a0c22c90056..1a9cdcefb49a 100644 --- a/target/xtensa/cpu-param.h +++ b/target/xtensa/cpu-param.h @@ -9,13 +9,10 @@ #define XTENSA_CPU_PARAM_H #define TARGET_PAGE_BITS 12 -#define TARGET_PHYS_ADDR_SPACE_BITS 32 #ifdef CONFIG_USER_ONLY #define TARGET_VIRT_ADDR_SPACE_BITS 30 #else #define TARGET_VIRT_ADDR_SPACE_BITS 32 #endif -#define TARGET_INSN_START_EXTRA_WORDS 0 - #endif diff --git a/target/xtensa/translate.c b/target/xtensa/translate.c index bb8d2ed86cf5..5e3707d3fdf9 100644 --- a/target/xtensa/translate.c +++ b/target/xtensa/translate.c @@ -1159,7 +1159,7 @@ static void xtensa_tr_tb_start(DisasContextBase *dcbase, CPUState *cpu) static void xtensa_tr_insn_start(DisasContextBase *dcbase, CPUState *cpu) { - tcg_gen_insn_start(dcbase->pc_next); + tcg_gen_insn_start(dcbase->pc_next, 0, 0); } static void xtensa_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) diff --git a/tcg/optimize.c b/tcg/optimize.c index 801a0a2c686e..b1abec69a5db 100644 --- a/tcg/optimize.c +++ b/tcg/optimize.c @@ -1652,12 +1652,17 @@ static bool fold_ctpop(OptContext *ctx, TCGOp *op) static bool fold_deposit(OptContext *ctx, TCGOp *op) { - TempOptInfo *t1 = arg_info(op->args[1]); - TempOptInfo *t2 = arg_info(op->args[2]); + TCGArg ret = op->args[0]; + TCGArg arg1 = op->args[1]; + TCGArg arg2 = op->args[2]; int ofs = op->args[3]; int len = op->args[4]; - int width = 8 * tcg_type_size(ctx->type); - uint64_t z_mask, o_mask, s_mask; + TempOptInfo *t1 = arg_info(arg1); + TempOptInfo *t2 = arg_info(arg2); + int width; + uint64_t z_mask, o_mask, s_mask, type_mask, len_mask; + TCGOp *op2; + bool valid; if (ti_is_const(t1) && ti_is_const(t2)) { return tcg_opt_gen_movi(ctx, op, op->args[0], @@ -1665,35 +1670,191 @@ static bool fold_deposit(OptContext *ctx, TCGOp *op) ti_const_val(t2))); } - /* Inserting a value into zero at offset 0. */ - if (ti_is_const_val(t1, 0) && ofs == 0) { - uint64_t mask = MAKE_64BIT_MASK(0, len); + width = 8 * tcg_type_size(ctx->type); + type_mask = MAKE_64BIT_MASK(0, width); + len_mask = MAKE_64BIT_MASK(0, len); + /* Inserting all-zero into a value. */ + if ((t2->z_mask & len_mask) == 0) { op->opc = INDEX_op_and; - op->args[1] = op->args[2]; - op->args[2] = arg_new_constant(ctx, mask); + op->args[2] = arg_new_constant(ctx, ~(len_mask << ofs)); return fold_and(ctx, op); } - /* Inserting zero into a value. */ - if (ti_is_const_val(t2, 0)) { - uint64_t mask = deposit64(-1, ofs, len, 0); - - op->opc = INDEX_op_and; - op->args[2] = arg_new_constant(ctx, mask); - return fold_and(ctx, op); + /* Inserting all-one into a value. */ + if ((t2->o_mask & len_mask) == len_mask) { + op->opc = INDEX_op_or; + op->args[2] = arg_new_constant(ctx, len_mask << ofs); + return fold_or(ctx, op); } - /* The s_mask from the top portion of the deposit is still valid. */ - if (ofs + len == width) { - s_mask = t2->s_mask << ofs; - } else { - s_mask = t1->s_mask & ~MAKE_64BIT_MASK(0, ofs + len); + valid = TCG_TARGET_deposit_valid(ctx->type, ofs, len); + + /* Lower invalid deposit of constant as AND + OR. */ + if (!valid && ti_is_const(t2)) { + uint64_t ins_val = (ti_const_val(t2) & len_mask) << ofs; + + op2 = opt_insert_before(ctx, op, INDEX_op_and, 3); + op2->args[0] = ret; + op2->args[1] = arg1; + op2->args[2] = arg_new_constant(ctx, ~(len_mask << ofs)); + fold_and(ctx, op2); + + op->opc = INDEX_op_or; + op->args[1] = ret; + op->args[2] = arg_new_constant(ctx, ins_val); + return fold_or(ctx, op); } + /* + * Compute result masks before calling other fold_* subroutines + * which could modify the masks of our inputs. + */ z_mask = deposit64(t1->z_mask, ofs, len, t2->z_mask); o_mask = deposit64(t1->o_mask, ofs, len, t2->o_mask); + if (ofs + len < width) { + s_mask = t1->s_mask & ~MAKE_64BIT_MASK(0, ofs + len); + } else { + s_mask = t2->s_mask << ofs; + } + + /* Inserting a value into zero. */ + if (ti_is_const_val(t1, 0)) { + uint64_t need_mask; + + /* Always lower deposit into zero at 0 as AND. */ + if (ofs == 0) { + op->opc = INDEX_op_and; + op->args[1] = arg2; + op->args[2] = arg_new_constant(ctx, len_mask); + return fold_and(ctx, op); + } + + /* + * If the portion of the value outside len that remains after + * shifting is zero, we can elide the mask and just shift. + */ + need_mask = t2->z_mask & ~len_mask; + need_mask = (need_mask << ofs) & type_mask; + if (!need_mask) { + op->opc = INDEX_op_shl; + op->args[1] = arg2; + op->args[2] = arg_new_constant(ctx, ofs); + goto done; + } + + /* Lower invalid deposit into zero as AND + SHL or SHL + SHR. */ + if (!valid) { + if (TCG_TARGET_extract_valid(ctx->type, 0, len)) { + /* EXTRACT (at 0) + SHL */ + op2 = opt_insert_before(ctx, op, INDEX_op_extract, 4); + op2->args[0] = ret; + op2->args[1] = arg2; + op2->args[2] = 0; + op2->args[3] = len; + } else if (tcg_op_imm_match(INDEX_op_and, ctx->type, len_mask)) { + /* AND + SHL */ + op2 = opt_insert_before(ctx, op, INDEX_op_and, 3); + op2->args[0] = ret; + op2->args[1] = arg2; + op2->args[2] = arg_new_constant(ctx, len_mask); + } else { + /* SHL + SHR */ + int shl = width - len; + int shr = width - len - ofs; + + op2 = opt_insert_before(ctx, op, INDEX_op_shl, 3); + op2->args[0] = ret; + op2->args[1] = arg2; + op2->args[2] = arg_new_constant(ctx, shl); + + op->opc = INDEX_op_shr; + op->args[1] = ret; + op->args[2] = arg_new_constant(ctx, shr); + goto done; + } + + /* Finish the (EXTRACT|AND) + SHL cases. */ + op->opc = INDEX_op_shl; + op->args[1] = ret; + op->args[2] = arg_new_constant(ctx, ofs); + goto done; + } + } + /* After special cases, lower invalid deposit. */ + if (!valid) { + TCGArg tmp; + + if (tcg_op_supported(INDEX_op_extract2, ctx->type, 0)) { + if (ofs == 0 && tcg_op_supported(INDEX_op_rotl, ctx->type, 0)) { + /* + * ret = arg2:arg1 >> len + * ret = rotl(ret, len) + */ + op2 = opt_insert_before(ctx, op, INDEX_op_extract2, 4); + op2->args[0] = ret; + op2->args[1] = arg1; + op2->args[2] = arg2; + op2->args[3] = len; + + op->opc = INDEX_op_rotl; + op->args[1] = ret; + op->args[2] = arg_new_constant(ctx, len); + goto done; + } + if (ofs + len == width) { + /* + * tmp = arg1 << len + * ret = arg2:tmp >> len + */ + tmp = ret == arg2 ? arg_new_temp(ctx) : ret; + + op2 = opt_insert_before(ctx, op, INDEX_op_shl, 4); + op2->args[0] = tmp; + op2->args[1] = arg1; + op2->args[2] = arg_new_constant(ctx, len); + + op->opc = INDEX_op_extract2; + op->args[0] = ret; + op->args[1] = tmp; + op->args[2] = arg2; + op->args[3] = len; + goto done; + } + } + + /* + * tmp = arg2 & mask + * ret = arg1 & ~(mask << ofs) + * tmp = tmp << ofs + * ret = ret | tmp + */ + tmp = arg_new_temp(ctx); + + op2 = opt_insert_before(ctx, op, INDEX_op_and, 3); + op2->args[0] = tmp; + op2->args[1] = arg2; + op2->args[2] = arg_new_constant(ctx, len_mask); + fold_and(ctx, op2); + + op2 = opt_insert_before(ctx, op, INDEX_op_shl, 3); + op2->args[0] = tmp; + op2->args[1] = tmp; + op2->args[2] = arg_new_constant(ctx, ofs); + + op2 = opt_insert_before(ctx, op, INDEX_op_and, 3); + op2->args[0] = ret; + op2->args[1] = arg1; + op2->args[2] = arg_new_constant(ctx, ~(len_mask << ofs)); + fold_and(ctx, op2); + + op->opc = INDEX_op_or; + op->args[1] = ret; + op->args[2] = tmp; + } + + done: return fold_masks_zos(ctx, op, z_mask, o_mask, s_mask); } @@ -1781,21 +1942,74 @@ static bool fold_extract2(OptContext *ctx, TCGOp *op) uint64_t z2 = t2->z_mask; uint64_t o1 = t1->o_mask; uint64_t o2 = t2->o_mask; + uint64_t zr, or; int shr = op->args[3]; + int shl; if (ctx->type == TCG_TYPE_I32) { z1 = (uint32_t)z1 >> shr; o1 = (uint32_t)o1 >> shr; - z2 = (uint64_t)((int32_t)z2 << (32 - shr)); - o2 = (uint64_t)((int32_t)o2 << (32 - shr)); + shl = 32 - shr; + z2 = (uint64_t)((int32_t)z2 << shl); + o2 = (uint64_t)((int32_t)o2 << shl); } else { z1 >>= shr; o1 >>= shr; - z2 <<= 64 - shr; - o2 <<= 64 - shr; + shl = 64 - shr; + z2 <<= shl; + o2 <<= shl; + } + zr = z1 | z2; + or = o1 | o2; + + if (zr == or) { + return tcg_opt_gen_movi(ctx, op, op->args[0], zr); + } + + if (z2 == 0) { + /* High part zeros folds to simple right shift. */ + op->opc = INDEX_op_shr; + op->args[2] = arg_new_constant(ctx, shr); + } else if (z1 == 0) { + /* Low part zeros folds to simple left shift. */ + op->opc = INDEX_op_shl; + op->args[1] = op->args[2]; + op->args[2] = arg_new_constant(ctx, shl); + } else if (!tcg_op_supported(INDEX_op_extract2, ctx->type, 0)) { + TCGArg tmp = arg_new_temp(ctx); + TCGOp *op2 = opt_insert_before(ctx, op, INDEX_op_shr, 3); + + op2->args[0] = tmp; + op2->args[1] = op->args[1]; + op2->args[2] = arg_new_constant(ctx, shr); + + if (TCG_TARGET_deposit_valid(ctx->type, shl, shr)) { + /* + * Deposit has more arguments than extract2, + * so we need to create a new TCGOp. + */ + op2 = opt_insert_before(ctx, op, INDEX_op_deposit, 5); + op2->args[0] = op->args[0]; + op2->args[1] = tmp; + op2->args[2] = op->args[2]; + op2->args[3] = shl; + op2->args[4] = shr; + + tcg_op_remove(ctx->tcg, op); + op = op2; + } else { + op2 = opt_insert_before(ctx, op, INDEX_op_shl, 3); + op2->args[0] = op->args[0]; + op2->args[1] = op->args[2]; + op2->args[2] = arg_new_constant(ctx, shl); + + op->opc = INDEX_op_or; + op->args[1] = op->args[0]; + op->args[2] = tmp; + } } - return fold_masks_zo(ctx, op, z1 | z2, o1 | o2); + return fold_masks_zo(ctx, op, zr, or); } static bool fold_exts(OptContext *ctx, TCGOp *op) diff --git a/tcg/tcg-internal.h b/tcg/tcg-internal.h index 2cbfb5d5caa2..c0997ab2243c 100644 --- a/tcg/tcg-internal.h +++ b/tcg/tcg-internal.h @@ -34,6 +34,12 @@ extern TCGContext **tcg_ctxs; extern unsigned int tcg_cur_ctxs; extern unsigned int tcg_max_ctxs; +#ifdef CONFIG_USER_ONLY +#define tcg_use_softmmu false +#else +#define tcg_use_softmmu true +#endif + void tcg_region_init(size_t tb_size, int splitwx, unsigned max_threads); bool tcg_region_alloc(TCGContext *s); void tcg_region_initial_alloc(TCGContext *s); @@ -94,4 +100,9 @@ TCGOp *tcg_op_insert_before(TCGContext *s, TCGOp *op, TCGOp *tcg_op_insert_after(TCGContext *s, TCGOp *op, TCGOpcode, TCGType, unsigned nargs); +/* + * For a binary opcode OP, return true if the second input operand allows IMM. + */ +bool tcg_op_imm_match(TCGOpcode op, TCGType type, tcg_target_ulong imm); + #endif /* TCG_INTERNAL_H */ diff --git a/tcg/tcg-op.c b/tcg/tcg-op.c index 8d67acc4fce8..d8ae57d6047d 100644 --- a/tcg/tcg-op.c +++ b/tcg/tcg-op.c @@ -826,23 +826,12 @@ void tcg_gen_rotl_i32(TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2) void tcg_gen_rotli_i32(TCGv_i32 ret, TCGv_i32 arg1, int32_t arg2) { tcg_debug_assert(arg2 >= 0 && arg2 < 32); - /* some cases can be optimized here */ if (arg2 == 0) { tcg_gen_mov_i32(ret, arg1); } else if (tcg_op_supported(INDEX_op_rotl, TCG_TYPE_I32, 0)) { - TCGv_i32 t0 = tcg_constant_i32(arg2); - tcg_gen_op3_i32(INDEX_op_rotl, ret, arg1, t0); - } else if (tcg_op_supported(INDEX_op_rotr, TCG_TYPE_I32, 0)) { - TCGv_i32 t0 = tcg_constant_i32(32 - arg2); - tcg_gen_op3_i32(INDEX_op_rotr, ret, arg1, t0); + tcg_gen_op3_i32(INDEX_op_rotl, ret, arg1, tcg_constant_i32(arg2)); } else { - TCGv_i32 t0 = tcg_temp_ebb_new_i32(); - TCGv_i32 t1 = tcg_temp_ebb_new_i32(); - tcg_gen_shli_i32(t0, arg1, arg2); - tcg_gen_shri_i32(t1, arg1, 32 - arg2); - tcg_gen_or_i32(ret, t0, t1); - tcg_temp_free_i32(t0); - tcg_temp_free_i32(t1); + tcg_gen_rotri_i32(ret, arg1, -arg2 & 31); } } @@ -870,15 +859,21 @@ void tcg_gen_rotr_i32(TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2) void tcg_gen_rotri_i32(TCGv_i32 ret, TCGv_i32 arg1, int32_t arg2) { tcg_debug_assert(arg2 >= 0 && arg2 < 32); - tcg_gen_rotli_i32(ret, arg1, -arg2 & 31); + if (arg2 == 0) { + tcg_gen_mov_i32(ret, arg1); + } else if (tcg_op_supported(INDEX_op_rotr, TCG_TYPE_I32, 0)) { + tcg_gen_op3_i32(INDEX_op_rotr, ret, arg1, tcg_constant_i32(arg2)); + } else if (tcg_op_supported(INDEX_op_rotl, TCG_TYPE_I32, 0)) { + tcg_gen_op3_i32(INDEX_op_rotl, ret, arg1, tcg_constant_i32(32 - arg2)); + } else { + /* Do not recurse with the rotri simplification. */ + tcg_gen_op4i_i32(INDEX_op_extract2, ret, arg1, arg1, arg2); + } } void tcg_gen_deposit_i32(TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2, unsigned int ofs, unsigned int len) { - uint32_t mask; - TCGv_i32 t1; - tcg_debug_assert(ofs < 32); tcg_debug_assert(len > 0); tcg_debug_assert(len <= 32); @@ -886,39 +881,9 @@ void tcg_gen_deposit_i32(TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2, if (len == 32) { tcg_gen_mov_i32(ret, arg2); - return; - } - if (TCG_TARGET_deposit_valid(TCG_TYPE_I32, ofs, len)) { - tcg_gen_op5ii_i32(INDEX_op_deposit, ret, arg1, arg2, ofs, len); - return; - } - - t1 = tcg_temp_ebb_new_i32(); - - if (tcg_op_supported(INDEX_op_extract2, TCG_TYPE_I32, 0)) { - if (ofs + len == 32) { - tcg_gen_shli_i32(t1, arg1, len); - tcg_gen_extract2_i32(ret, t1, arg2, len); - goto done; - } - if (ofs == 0) { - tcg_gen_extract2_i32(ret, arg1, arg2, len); - tcg_gen_rotli_i32(ret, ret, len); - goto done; - } - } - - mask = (1u << len) - 1; - if (ofs + len < 32) { - tcg_gen_andi_i32(t1, arg2, mask); - tcg_gen_shli_i32(t1, t1, ofs); } else { - tcg_gen_shli_i32(t1, arg2, ofs); + tcg_gen_op5ii_i32(INDEX_op_deposit, ret, arg1, arg2, ofs, len); } - tcg_gen_andi_i32(ret, arg1, ~(mask << ofs)); - tcg_gen_or_i32(ret, ret, t1); - done: - tcg_temp_free_i32(t1); } void tcg_gen_deposit_z_i32(TCGv_i32 ret, TCGv_i32 arg, @@ -932,34 +897,18 @@ void tcg_gen_deposit_z_i32(TCGv_i32 ret, TCGv_i32 arg, if (ofs + len == 32) { tcg_gen_shli_i32(ret, arg, ofs); } else if (ofs == 0) { - tcg_gen_andi_i32(ret, arg, (1u << len) - 1); - } else if (TCG_TARGET_deposit_valid(TCG_TYPE_I32, ofs, len)) { + tcg_gen_extract_i32(ret, arg, 0, len); + } else { TCGv_i32 zero = tcg_constant_i32(0); tcg_gen_op5ii_i32(INDEX_op_deposit, ret, zero, arg, ofs, len); - } else { - /* - * To help two-operand hosts we prefer to zero-extend first, - * which allows ARG to stay live. - */ - if (TCG_TARGET_extract_valid(TCG_TYPE_I32, 0, len)) { - tcg_gen_extract_i32(ret, arg, 0, len); - tcg_gen_shli_i32(ret, ret, ofs); - return; - } - /* Otherwise prefer zero-extension over AND for code size. */ - if (TCG_TARGET_extract_valid(TCG_TYPE_I32, 0, ofs + len)) { - tcg_gen_shli_i32(ret, arg, ofs); - tcg_gen_extract_i32(ret, ret, 0, ofs + len); - return; - } - tcg_gen_andi_i32(ret, arg, (1u << len) - 1); - tcg_gen_shli_i32(ret, ret, ofs); } } void tcg_gen_extract_i32(TCGv_i32 ret, TCGv_i32 arg, unsigned int ofs, unsigned int len) { + uint32_t mask; + tcg_debug_assert(ofs < 32); tcg_debug_assert(len > 0); tcg_debug_assert(len <= 32); @@ -975,8 +924,10 @@ void tcg_gen_extract_i32(TCGv_i32 ret, TCGv_i32 arg, tcg_gen_op4ii_i32(INDEX_op_extract, ret, arg, ofs, len); return; } + + mask = (1u << len) - 1; if (ofs == 0) { - tcg_gen_andi_i32(ret, arg, (1u << len) - 1); + tcg_gen_andi_i32(ret, arg, mask); return; } @@ -987,18 +938,12 @@ void tcg_gen_extract_i32(TCGv_i32 ret, TCGv_i32 arg, return; } - /* ??? Ideally we'd know what values are available for immediate AND. - Assume that 8 bits are available, plus the special case of 16, - so that we get ext8u, ext16u. */ - switch (len) { - case 1 ... 8: case 16: + if (tcg_op_imm_match(INDEX_op_and, TCG_TYPE_I32, mask)) { tcg_gen_shri_i32(ret, arg, ofs); - tcg_gen_andi_i32(ret, ret, (1u << len) - 1); - break; - default: + tcg_gen_andi_i32(ret, ret, mask); + } else { tcg_gen_shli_i32(ret, arg, 32 - len - ofs); tcg_gen_shri_i32(ret, ret, 32 - len); - break; } } @@ -1051,13 +996,8 @@ void tcg_gen_extract2_i32(TCGv_i32 ret, TCGv_i32 al, TCGv_i32 ah, tcg_gen_mov_i32(ret, ah); } else if (al == ah) { tcg_gen_rotri_i32(ret, al, ofs); - } else if (tcg_op_supported(INDEX_op_extract2, TCG_TYPE_I32, 0)) { - tcg_gen_op4i_i32(INDEX_op_extract2, ret, al, ah, ofs); } else { - TCGv_i32 t0 = tcg_temp_ebb_new_i32(); - tcg_gen_shri_i32(t0, al, ofs); - tcg_gen_deposit_i32(ret, t0, ah, 32 - ofs, ofs); - tcg_temp_free_i32(t0); + tcg_gen_op4i_i32(INDEX_op_extract2, ret, al, ah, ofs); } } @@ -2098,23 +2038,12 @@ void tcg_gen_rotl_i64(TCGv_i64 ret, TCGv_i64 arg1, TCGv_i64 arg2) void tcg_gen_rotli_i64(TCGv_i64 ret, TCGv_i64 arg1, int64_t arg2) { tcg_debug_assert(arg2 >= 0 && arg2 < 64); - /* some cases can be optimized here */ if (arg2 == 0) { tcg_gen_mov_i64(ret, arg1); } else if (tcg_op_supported(INDEX_op_rotl, TCG_TYPE_I64, 0)) { - TCGv_i64 t0 = tcg_constant_i64(arg2); - tcg_gen_op3_i64(INDEX_op_rotl, ret, arg1, t0); - } else if (tcg_op_supported(INDEX_op_rotr, TCG_TYPE_I64, 0)) { - TCGv_i64 t0 = tcg_constant_i64(64 - arg2); - tcg_gen_op3_i64(INDEX_op_rotr, ret, arg1, t0); + tcg_gen_op3_i64(INDEX_op_rotl, ret, arg1, tcg_constant_i64(arg2)); } else { - TCGv_i64 t0 = tcg_temp_ebb_new_i64(); - TCGv_i64 t1 = tcg_temp_ebb_new_i64(); - tcg_gen_shli_i64(t0, arg1, arg2); - tcg_gen_shri_i64(t1, arg1, 64 - arg2); - tcg_gen_or_i64(ret, t0, t1); - tcg_temp_free_i64(t0); - tcg_temp_free_i64(t1); + tcg_gen_rotri_i64(ret, arg1, -arg2 & 63); } } @@ -2142,15 +2071,21 @@ void tcg_gen_rotr_i64(TCGv_i64 ret, TCGv_i64 arg1, TCGv_i64 arg2) void tcg_gen_rotri_i64(TCGv_i64 ret, TCGv_i64 arg1, int64_t arg2) { tcg_debug_assert(arg2 >= 0 && arg2 < 64); - tcg_gen_rotli_i64(ret, arg1, -arg2 & 63); + if (arg2 == 0) { + tcg_gen_mov_i64(ret, arg1); + } else if (tcg_op_supported(INDEX_op_rotr, TCG_TYPE_I64, 0)) { + tcg_gen_op3_i64(INDEX_op_rotr, ret, arg1, tcg_constant_i64(arg2)); + } else if (tcg_op_supported(INDEX_op_rotl, TCG_TYPE_I64, 0)) { + tcg_gen_op3_i64(INDEX_op_rotl, ret, arg1, tcg_constant_i64(64 - arg2)); + } else { + /* Do not recurse with the rotri simplification. */ + tcg_gen_op4i_i64(INDEX_op_extract2, ret, arg1, arg1, arg2); + } } void tcg_gen_deposit_i64(TCGv_i64 ret, TCGv_i64 arg1, TCGv_i64 arg2, unsigned int ofs, unsigned int len) { - uint64_t mask; - TCGv_i64 t1; - tcg_debug_assert(ofs < 64); tcg_debug_assert(len > 0); tcg_debug_assert(len <= 64); @@ -2158,40 +2093,9 @@ void tcg_gen_deposit_i64(TCGv_i64 ret, TCGv_i64 arg1, TCGv_i64 arg2, if (len == 64) { tcg_gen_mov_i64(ret, arg2); - return; - } - - if (TCG_TARGET_deposit_valid(TCG_TYPE_I64, ofs, len)) { - tcg_gen_op5ii_i64(INDEX_op_deposit, ret, arg1, arg2, ofs, len); - return; - } - - t1 = tcg_temp_ebb_new_i64(); - - if (tcg_op_supported(INDEX_op_extract2, TCG_TYPE_I64, 0)) { - if (ofs + len == 64) { - tcg_gen_shli_i64(t1, arg1, len); - tcg_gen_extract2_i64(ret, t1, arg2, len); - goto done; - } - if (ofs == 0) { - tcg_gen_extract2_i64(ret, arg1, arg2, len); - tcg_gen_rotli_i64(ret, ret, len); - goto done; - } - } - - mask = (1ull << len) - 1; - if (ofs + len < 64) { - tcg_gen_andi_i64(t1, arg2, mask); - tcg_gen_shli_i64(t1, t1, ofs); } else { - tcg_gen_shli_i64(t1, arg2, ofs); + tcg_gen_op5ii_i64(INDEX_op_deposit, ret, arg1, arg2, ofs, len); } - tcg_gen_andi_i64(ret, arg1, ~(mask << ofs)); - tcg_gen_or_i64(ret, ret, t1); - done: - tcg_temp_free_i64(t1); } void tcg_gen_deposit_z_i64(TCGv_i64 ret, TCGv_i64 arg, @@ -2206,33 +2110,17 @@ void tcg_gen_deposit_z_i64(TCGv_i64 ret, TCGv_i64 arg, tcg_gen_shli_i64(ret, arg, ofs); } else if (ofs == 0) { tcg_gen_andi_i64(ret, arg, (1ull << len) - 1); - } else if (TCG_TARGET_deposit_valid(TCG_TYPE_I64, ofs, len)) { + } else { TCGv_i64 zero = tcg_constant_i64(0); tcg_gen_op5ii_i64(INDEX_op_deposit, ret, zero, arg, ofs, len); - } else { - /* - * To help two-operand hosts we prefer to zero-extend first, - * which allows ARG to stay live. - */ - if (TCG_TARGET_extract_valid(TCG_TYPE_I64, 0, len)) { - tcg_gen_extract_i64(ret, arg, 0, len); - tcg_gen_shli_i64(ret, ret, ofs); - return; - } - /* Otherwise prefer zero-extension over AND for code size. */ - if (TCG_TARGET_extract_valid(TCG_TYPE_I64, 0, ofs + len)) { - tcg_gen_shli_i64(ret, arg, ofs); - tcg_gen_extract_i64(ret, ret, 0, ofs + len); - return; - } - tcg_gen_andi_i64(ret, arg, (1ull << len) - 1); - tcg_gen_shli_i64(ret, ret, ofs); } } void tcg_gen_extract_i64(TCGv_i64 ret, TCGv_i64 arg, unsigned int ofs, unsigned int len) { + uint64_t mask; + tcg_debug_assert(ofs < 64); tcg_debug_assert(len > 0); tcg_debug_assert(len <= 64); @@ -2248,8 +2136,10 @@ void tcg_gen_extract_i64(TCGv_i64 ret, TCGv_i64 arg, tcg_gen_op4ii_i64(INDEX_op_extract, ret, arg, ofs, len); return; } + + mask = (1ull << len) - 1; if (ofs == 0) { - tcg_gen_andi_i64(ret, arg, (1ull << len) - 1); + tcg_gen_andi_i64(ret, arg, mask); return; } @@ -2260,18 +2150,12 @@ void tcg_gen_extract_i64(TCGv_i64 ret, TCGv_i64 arg, return; } - /* ??? Ideally we'd know what values are available for immediate AND. - Assume that 8 bits are available, plus the special cases of 16 and 32, - so that we get ext8u, ext16u, and ext32u. */ - switch (len) { - case 1 ... 8: case 16: case 32: + if (tcg_op_imm_match(INDEX_op_and, TCG_TYPE_I64, mask)) { tcg_gen_shri_i64(ret, arg, ofs); - tcg_gen_andi_i64(ret, ret, (1ull << len) - 1); - break; - default: + tcg_gen_andi_i64(ret, ret, mask); + } else { tcg_gen_shli_i64(ret, arg, 64 - len - ofs); tcg_gen_shri_i64(ret, ret, 64 - len); - break; } } @@ -2324,13 +2208,8 @@ void tcg_gen_extract2_i64(TCGv_i64 ret, TCGv_i64 al, TCGv_i64 ah, tcg_gen_mov_i64(ret, ah); } else if (al == ah) { tcg_gen_rotri_i64(ret, al, ofs); - } else if (tcg_op_supported(INDEX_op_extract2, TCG_TYPE_I64, 0)) { - tcg_gen_op4i_i64(INDEX_op_extract2, ret, al, ah, ofs); } else { - TCGv_i64 t0 = tcg_temp_ebb_new_i64(); - tcg_gen_shri_i64(t0, al, ofs); - tcg_gen_deposit_i64(ret, t0, ah, 64 - ofs, ofs); - tcg_temp_free_i64(t0); + tcg_gen_op4i_i64(INDEX_op_extract2, ret, al, ah, ofs); } } diff --git a/tcg/tcg.c b/tcg/tcg.c index e7bf4dad4ee2..2ca44766f649 100644 --- a/tcg/tcg.c +++ b/tcg/tcg.c @@ -236,10 +236,6 @@ static TCGAtomAlign atom_and_align_for_opc(TCGContext *s, MemOp opc, MemOp host_atom, bool allow_two_ops) __attribute__((unused)); -#ifdef CONFIG_USER_ONLY -bool tcg_use_softmmu; -#endif - TCGContext tcg_init_ctx; __thread TCGContext *tcg_ctx; @@ -3391,11 +3387,9 @@ static void process_constraint_sets(void) } } -static const TCGArgConstraint *opcode_args_ct(const TCGOp *op) +static const TCGArgConstraint *op_args_ct(TCGOpcode opc, TCGType type, + unsigned flags) { - TCGOpcode opc = op->opc; - TCGType type = TCGOP_TYPE(op); - unsigned flags = TCGOP_FLAGS(op); const TCGOpDef *def = &tcg_op_defs[opc]; const TCGOutOp *outop = all_outop[opc]; TCGConstraintSetIndex con_set; @@ -3422,6 +3416,21 @@ static const TCGArgConstraint *opcode_args_ct(const TCGOp *op) return all_cts[con_set]; } +static const TCGArgConstraint *opcode_args_ct(const TCGOp *op) +{ + return op_args_ct(op->opc, TCGOP_TYPE(op), TCGOP_FLAGS(op)); +} + +bool tcg_op_imm_match(TCGOpcode opc, TCGType type, tcg_target_ulong imm) +{ + const TCGArgConstraint *args_ct = op_args_ct(opc, type, 0); + const TCGOpDef *def = &tcg_op_defs[opc]; + + tcg_debug_assert(def->nb_oargs == 1); + tcg_debug_assert(def->nb_iargs == 2); + return tcg_target_const_match(imm, args_ct[2].ct, type, 0, 0); +} + static void remove_label_use(TCGOp *op, int idx) { TCGLabel *label = arg_label(op->args[idx]); diff --git a/tests/Makefile.include b/tests/Makefile.include index be3b78fdfb7c..59b9a4b92209 100644 --- a/tests/Makefile.include +++ b/tests/Makefile.include @@ -22,7 +22,6 @@ ifneq ($(filter $(all-check-targets), check-softfloat),) endif @echo @echo " $(MAKE) check-report.junit.xml Generates an aggregated XML test report" - @echo " $(MAKE) check-venv Creates a Python venv for tests" @echo " $(MAKE) check-clean Clean the tests and related data" @echo @echo "The following are useful for CI builds" @@ -93,33 +92,16 @@ clean-tcg: $(CLEAN_TCG_TARGET_RULES) .PHONY: distclean-tcg distclean-tcg: $(DISTCLEAN_TCG_TARGET_RULES) -# Python venv for running tests - -.PHONY: check-venv - # Build up our target list from the filtered list of ninja targets TARGETS=$(patsubst libqemu-%.a, %, $(filter libqemu-%.a, $(ninja-targets))) -TESTS_VENV_TOKEN=$(BUILD_DIR)/pyvenv/tests.group - -quiet-venv-pip = $(quiet-@)$(call quiet-command-run, \ - $(PYTHON) -m pip -q --disable-pip-version-check $1, \ - "VENVPIP","$1") - -$(TESTS_VENV_TOKEN): $(SRC_PATH)/pythondeps.toml - $(call quiet-venv-pip,install -e "$(SRC_PATH)/python/") - $(MKVENV_ENSUREGROUP) $< testdeps - $(call quiet-command, touch $@) - -check-venv: $(TESTS_VENV_TOKEN) - FUNCTIONAL_TARGETS=$(patsubst %-softmmu,check-functional-%, $(filter %-softmmu,$(TARGETS))) .PHONY: $(FUNCTIONAL_TARGETS) -$(FUNCTIONAL_TARGETS): check-venv +$(FUNCTIONAL_TARGETS): @$(MAKE) SPEED=thorough $(subst -functional,-func,$@) .PHONY: check-functional -check-functional: check-venv +check-functional: @$(NINJA) precache-functional @$(PYTHON) $(SRC_PATH)/scripts/clean_functional_cache.py @QEMU_TEST_NO_DOWNLOAD=1 $(MAKE) SPEED=thorough check-func check-func-quick diff --git a/tests/audio/audio-stubs.c b/tests/audio/audio-stubs.c new file mode 100644 index 000000000000..2c19dfa7d6bb --- /dev/null +++ b/tests/audio/audio-stubs.c @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* + * Stubs for audio test - provides missing functions for standalone audio test + */ + +#include "qemu/osdep.h" +#include "qemu/dbus.h" +#include "ui/qemu-spice-module.h" +#include "ui/dbus-module.h" +#include "system/replay.h" +#include "system/runstate.h" + +int using_spice; +int using_dbus_display; + +struct QemuSpiceOps qemu_spice; + +GQuark dbus_display_error_quark(void) +{ + return g_quark_from_static_string("dbus-display-error-quark"); +} + +#ifdef WIN32 +/* from ui/dbus.h */ +bool +dbus_win32_import_socket(GDBusMethodInvocation *invocation, + GVariant *arg_listener, int *socket); + +bool +dbus_win32_import_socket(GDBusMethodInvocation *invocation, + GVariant *arg_listener, int *socket) +{ + return true; +} +#endif + +void replay_audio_out(size_t *played) +{ +} + +void replay_audio_in_start(size_t *nsamples) +{ +} + +void replay_audio_in_sample_lr(uint64_t *left, uint64_t *right) +{ +} + +void replay_audio_in_finish(void) +{ +} + +static int dummy_vmse; + +VMChangeStateEntry *qemu_add_vm_change_state_handler(VMChangeStateHandler *cb, + void *opaque) +{ + return (VMChangeStateEntry *)&dummy_vmse; +} + +void qemu_del_vm_change_state_handler(VMChangeStateEntry *e) +{ +} + +bool runstate_is_running(void) +{ + return true; +} diff --git a/tests/audio/meson.build b/tests/audio/meson.build new file mode 100644 index 000000000000..84754bde2218 --- /dev/null +++ b/tests/audio/meson.build @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +if not have_system + subdir_done() +endif + +modinfo_dep = not_found +if enable_modules + modinfo_src = custom_target('modinfo.c', + output: 'modinfo.c', + input: audio_modinfo_files, + command: [modinfo_generate, '--skip-missing-deps', '@INPUT@'], + capture: true) + + modinfo_lib = static_library('modinfo.c', modinfo_src) + modinfo_dep = declare_dependency(link_with: modinfo_lib) +endif + +# manual audio test - not part of automated test suite +# as it relies on audio system +executable('test-audio', + sources: [files('test-audio.c', 'audio-stubs.c'), dbus_display1], + dependencies: [audio, qemuutil, modinfo_dep, gio, spice, sdl]) diff --git a/tests/audio/test-audio.c b/tests/audio/test-audio.c new file mode 100644 index 000000000000..af8cf03d10ef --- /dev/null +++ b/tests/audio/test-audio.c @@ -0,0 +1,619 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "qemu/osdep.h" +#include "qemu/config-file.h" +#include "qemu/cutils.h" +#include "qemu/help_option.h" +#include "qemu/module.h" +#include "qemu/main-loop.h" +#include "qemu/audio.h" +#include "qemu/log.h" +#include "qapi/error.h" +#include "trace/control.h" +#include "glib.h" + +#include "audio/audio_int.h" + +#include +#ifdef CONFIG_SDL +/* + * SDL insists on wrapping the main() function with its own implementation on + * some platforms; it does so via a macro that renames our main function, so + * must be #included here even with no SDL code called from this file. + */ +#include +#endif + +#define SAMPLE_RATE 44100 +#define CHANNELS 2 +#define DURATION_SECS 2 +#define FREQUENCY 440.0 +#define BUFFER_FRAMES 1024 +#define TIMEOUT_SECS (DURATION_SECS + 1) + +/* Command-line options */ +static gchar *opt_audiodev; +static gchar *opt_trace; + +static GOptionEntry test_options[] = { + { "audiodev", 'a', 0, G_OPTION_ARG_STRING, &opt_audiodev, + "Audio device spec (e.g., none or pa,out.buffer-length=50000)", "DEV" }, + { "trace", 'T', 0, G_OPTION_ARG_STRING, &opt_trace, + "Trace options (e.g., 'pw_*')", "TRACE" }, + { NULL } +}; + +#define TEST_AUDIODEV_ID "test" + +typedef struct TestSineState { + AudioBackend *be; + SWVoiceOut *voice; + int64_t total_frames; + int64_t frames_written; +} TestSineState; + +/* Default audio settings for tests */ +static const struct audsettings default_test_settings = { + .freq = SAMPLE_RATE, + .nchannels = CHANNELS, + .fmt = AUDIO_FORMAT_S16, + .big_endian = false, +}; + +static void dummy_audio_callback(void *opaque, int avail) +{ +} + +static AudioBackend *get_test_audio_backend(void) +{ + AudioBackend *be; + Error *err = NULL; + + if (opt_audiodev) { + be = audio_be_by_name(TEST_AUDIODEV_ID, &err); + } else { + be = audio_get_default_audio_be(&err); + } + + if (err) { + g_error("%s", error_get_pretty(err)); + error_free(err); + exit(1); + } + g_assert_nonnull(be); + return be; +} + +/* + * Helper functions for opening test voices with default settings. + * These reduce boilerplate in test functions. + */ +static SWVoiceOut *open_test_voice_out(AudioBackend *be, const char *name, + void *opaque, audio_callback_fn cb) +{ + struct audsettings as = default_test_settings; + SWVoiceOut *voice; + + voice = audio_be_open_out(be, NULL, name, opaque, cb, &as); + g_assert_nonnull(voice); + return voice; +} + +static SWVoiceIn *open_test_voice_in(AudioBackend *be, const char *name, + void *opaque, audio_callback_fn cb) +{ + struct audsettings as = default_test_settings; + + return audio_be_open_in(be, NULL, name, opaque, cb, &as); +} + +/* + * Generate 440Hz sine wave samples into buffer. + */ +static void generate_sine_samples(int16_t *buffer, int frames, + int64_t start_frame) +{ + for (int i = 0; i < frames; i++) { + double t = (double)(start_frame + i) / SAMPLE_RATE; + double sample = sin(2.0 * M_PI * FREQUENCY * t); + int16_t s = (int16_t)(sample * 32767.0); + + buffer[i * 2] = s; /* left channel */ + buffer[i * 2 + 1] = s; /* right channel */ + } +} + +static void test_sine_callback(void *opaque, int avail) +{ + TestSineState *s = opaque; + int16_t buffer[BUFFER_FRAMES * CHANNELS]; + int frames_remaining; + int frames_to_write; + size_t bytes_written; + + frames_remaining = s->total_frames - s->frames_written; + if (frames_remaining <= 0) { + return; + } + + frames_to_write = avail / (sizeof(int16_t) * CHANNELS); + frames_to_write = MIN(frames_to_write, BUFFER_FRAMES); + frames_to_write = MIN(frames_to_write, frames_remaining); + + generate_sine_samples(buffer, frames_to_write, s->frames_written); + + bytes_written = audio_be_write(s->be, s->voice, buffer, + frames_to_write * sizeof(int16_t) * CHANNELS); + s->frames_written += bytes_written / (sizeof(int16_t) * CHANNELS); +} + + +static void test_audio_out_sine_wave(void) +{ + TestSineState state = {0}; + int64_t start_time; + int64_t elapsed_ms; + + state.be = get_test_audio_backend(); + state.total_frames = SAMPLE_RATE * DURATION_SECS; + state.frames_written = 0; + + g_test_message("Opening audio output..."); + state.voice = open_test_voice_out(state.be, "test-sine", + &state, test_sine_callback); + + g_test_message("Playing 440Hz sine wave for %d seconds...", DURATION_SECS); + audio_be_set_active_out(state.be, state.voice, true); + + /* + * Run the audio subsystem until all frames are written or timeout. + */ + start_time = g_get_monotonic_time(); + while (state.frames_written < state.total_frames) { + audio_run(AUDIO_MIXENG_BACKEND(state.be), "test"); + main_loop_wait(true); + + elapsed_ms = (g_get_monotonic_time() - start_time) / 1000; + if (elapsed_ms > TIMEOUT_SECS * 1000) { + g_test_message("Timeout waiting for audio to complete"); + break; + } + + g_usleep(G_USEC_PER_SEC / 100); /* 10ms */ + } + + g_test_message("Wrote %" PRId64 " frames (%.2f seconds)", + state.frames_written, + (double)state.frames_written / SAMPLE_RATE); + + g_assert_cmpint(state.frames_written, ==, state.total_frames); + + audio_be_set_active_out(state.be, state.voice, false); + audio_be_close_out(state.be, state.voice); +} + +static void test_audio_prio_list(void) +{ + g_autofree gchar *backends = NULL; + GString *str = g_string_new(NULL); + bool has_none = false; + + for (int i = 0; audio_prio_list[i]; i++) { + if (i > 0) { + g_string_append_c(str, ' '); + } + g_string_append(str, audio_prio_list[i]); + + if (g_strcmp0(audio_prio_list[i], "none") == 0) { + has_none = true; + } + } + + backends = g_string_free(str, FALSE); + g_test_message("Available backends: %s", backends); + + /* The 'none' backend should always be available */ + g_assert_true(has_none); +} + +static void test_audio_out_active_state(void) +{ + AudioBackend *be; + SWVoiceOut *voice; + + be = get_test_audio_backend(); + voice = open_test_voice_out(be, "test-active", NULL, dummy_audio_callback); + + g_assert_false(audio_be_is_active_out(be, voice)); + + audio_be_set_active_out(be, voice, true); + g_assert_true(audio_be_is_active_out(be, voice)); + + audio_be_set_active_out(be, voice, false); + g_assert_false(audio_be_is_active_out(be, voice)); + + audio_be_close_out(be, voice); +} + +static void test_audio_out_buffer_size(void) +{ + AudioBackend *be; + SWVoiceOut *voice; + int buffer_size; + + be = get_test_audio_backend(); + voice = open_test_voice_out(be, "test-buffer", NULL, dummy_audio_callback); + + buffer_size = audio_be_get_buffer_size_out(be, voice); + g_test_message("Buffer size: %d bytes", buffer_size); + g_assert_cmpint(buffer_size, >, 0); + + audio_be_close_out(be, voice); + + g_assert_cmpint(audio_be_get_buffer_size_out(be, NULL), ==, 0); +} + +static void test_audio_out_volume(void) +{ + AudioBackend *be; + SWVoiceOut *voice; + Volume vol; + + be = get_test_audio_backend(); + voice = open_test_voice_out(be, "test-volume", NULL, dummy_audio_callback); + + vol = (Volume){ .mute = false, .channels = 2, .vol = {255, 255} }; + audio_be_set_volume_out(be, voice, &vol); + + vol = (Volume){ .mute = true, .channels = 2, .vol = {255, 255} }; + audio_be_set_volume_out(be, voice, &vol); + + vol = (Volume){ .mute = false, .channels = 2, .vol = {128, 128} }; + audio_be_set_volume_out(be, voice, &vol); + + audio_be_close_out(be, voice); +} + +static void test_audio_in_active_state(void) +{ + AudioBackend *be; + SWVoiceIn *voice; + + be = get_test_audio_backend(); + voice = open_test_voice_in(be, "test-in-active", NULL, dummy_audio_callback); + if (!voice) { + g_test_skip("The backend may not support input"); + return; + } + + g_assert_false(audio_be_is_active_in(be, voice)); + + audio_be_set_active_in(be, voice, true); + g_assert_true(audio_be_is_active_in(be, voice)); + + audio_be_set_active_in(be, voice, false); + g_assert_false(audio_be_is_active_in(be, voice)); + + audio_be_close_in(be, voice); +} + +static void test_audio_in_volume(void) +{ + AudioBackend *be; + SWVoiceIn *voice; + Volume vol; + + be = get_test_audio_backend(); + voice = open_test_voice_in(be, "test-in-volume", NULL, dummy_audio_callback); + if (!voice) { + g_test_skip("The backend may not support input"); + return; + } + + vol = (Volume){ .mute = false, .channels = 2, .vol = {255, 255} }; + audio_be_set_volume_in(be, voice, &vol); + + vol = (Volume){ .mute = true, .channels = 2, .vol = {255, 255} }; + audio_be_set_volume_in(be, voice, &vol); + + audio_be_close_in(be, voice); +} + + +/* Capture test state */ +#define CAPTURE_BUFFER_FRAMES (SAMPLE_RATE / 10) /* 100ms of audio */ +#define CAPTURE_BUFFER_SIZE (CAPTURE_BUFFER_FRAMES * CHANNELS * sizeof(int16_t)) + +typedef struct TestCaptureState { + bool notify_called; + bool capture_called; + bool destroy_called; + audcnotification_e last_notify; + int16_t *captured_samples; + size_t captured_bytes; + size_t capture_buffer_size; +} TestCaptureState; + +static void test_capture_notify(void *opaque, audcnotification_e cmd) +{ + TestCaptureState *s = opaque; + s->notify_called = true; + s->last_notify = cmd; +} + +static void test_capture_capture(void *opaque, const void *buf, int size) +{ + TestCaptureState *s = opaque; + size_t bytes_to_copy; + + s->capture_called = true; + + if (!s->captured_samples || s->captured_bytes >= s->capture_buffer_size) { + return; + } + + bytes_to_copy = MIN(size, s->capture_buffer_size - s->captured_bytes); + memcpy((uint8_t *)s->captured_samples + s->captured_bytes, buf, bytes_to_copy); + s->captured_bytes += bytes_to_copy; +} + +static void test_capture_destroy(void *opaque) +{ + TestCaptureState *s = opaque; + s->destroy_called = true; +} + +/* + * Compare captured audio with expected sine wave. + * Returns the number of matching samples (within tolerance). + */ +static int compare_sine_samples(const int16_t *captured, int frames, + int64_t start_frame, int tolerance) +{ + int matching = 0; + + for (int i = 0; i < frames; i++) { + double t = (double)(start_frame + i) / SAMPLE_RATE; + double sample = sin(2.0 * M_PI * FREQUENCY * t); + int16_t expected = (int16_t)(sample * 32767.0); + + /* Check left channel */ + if (abs(captured[i * 2] - expected) <= tolerance) { + matching++; + } + /* Check right channel */ + if (abs(captured[i * 2 + 1] - expected) <= tolerance) { + matching++; + } + } + + return matching; +} + +static void test_audio_capture(void) +{ + AudioBackend *be; + CaptureVoiceOut *cap; + SWVoiceOut *voice; + TestCaptureState state = {0}; + TestSineState sine_state = {0}; + struct audsettings as = default_test_settings; + struct audio_capture_ops ops = { + .notify = test_capture_notify, + .capture = test_capture_capture, + .destroy = test_capture_destroy, + }; + int64_t start_time; + int64_t elapsed_ms; + int captured_frames; + int matching_samples; + int total_samples; + double match_ratio; + + be = get_test_audio_backend(); + + state.captured_samples = g_malloc0(CAPTURE_BUFFER_SIZE); + state.captured_bytes = 0; + state.capture_buffer_size = CAPTURE_BUFFER_SIZE; + + cap = audio_be_add_capture(be, &as, &ops, &state); + g_assert_nonnull(cap); + + sine_state.be = be; + sine_state.total_frames = CAPTURE_BUFFER_FRAMES; + sine_state.frames_written = 0; + + voice = open_test_voice_out(be, "test-capture-sine", + &sine_state, test_sine_callback); + sine_state.voice = voice; + + audio_be_set_active_out(be, voice, true); + + start_time = g_get_monotonic_time(); + while (sine_state.frames_written < sine_state.total_frames || + state.captured_bytes < CAPTURE_BUFFER_SIZE) { + audio_run(AUDIO_MIXENG_BACKEND(be), "test-capture"); + main_loop_wait(true); + + elapsed_ms = (g_get_monotonic_time() - start_time) / 1000; + if (elapsed_ms > 1000) { /* 1 second timeout */ + break; + } + + g_usleep(G_USEC_PER_SEC / 1000); /* 1ms */ + } + + g_test_message("Wrote %" PRId64 " frames, captured %zu bytes", + sine_state.frames_written, state.captured_bytes); + + g_assert_true(state.capture_called); + g_assert_cmpuint(state.captured_bytes, >, 0); + + /* Compare captured data with expected sine wave */ + captured_frames = state.captured_bytes / (CHANNELS * sizeof(int16_t)); + if (captured_frames > 0) { + /* + * Allow some tolerance due to mixing/conversion. + * The tolerance accounts for potential rounding differences. + */ + matching_samples = compare_sine_samples(state.captured_samples, + captured_frames, 0, 100); + total_samples = captured_frames * CHANNELS; + match_ratio = (double)matching_samples / total_samples; + + g_test_message("Captured %d frames, %d/%d samples match (%.1f%%)", + captured_frames, matching_samples, total_samples, + match_ratio * 100.0); + + /* + * Expect at least 90% of samples to match within tolerance. + * Some variation is expected due to mixing engine processing. + */ + g_assert_cmpfloat(match_ratio, >=, 0.9); + } + + audio_be_set_active_out(be, voice, false); + audio_be_close_out(be, voice); + + audio_be_del_capture(be, cap, &state); + g_assert_true(state.destroy_called); + + g_free(state.captured_samples); +} + +static void test_audio_null_handling(void) +{ + AudioBackend *be = get_test_audio_backend(); + uint8_t buffer[64]; + + /* audio_be_is_active_out/in(NULL) should return false */ + g_assert_false(audio_be_is_active_out(be, NULL)); + g_assert_false(audio_be_is_active_in(be, NULL)); + + /* audio_be_get_buffer_size_out(NULL) should return 0 */ + g_assert_cmpint(audio_be_get_buffer_size_out(be, NULL), ==, 0); + + /* audio_be_write/read(NULL, ...) should return 0 */ + g_assert_cmpuint(audio_be_write(be, NULL, buffer, sizeof(buffer)), ==, 0); + g_assert_cmpuint(audio_be_read(be, NULL, buffer, sizeof(buffer)), ==, 0); + + /* These should not crash */ + audio_be_set_active_out(be, NULL, true); + audio_be_set_active_out(be, NULL, false); + audio_be_set_active_in(be, NULL, true); + audio_be_set_active_in(be, NULL, false); +} + +static void test_audio_multiple_voices(void) +{ + AudioBackend *be; + SWVoiceOut *out1, *out2; + SWVoiceIn *in1; + + be = get_test_audio_backend(); + out1 = open_test_voice_out(be, "test-multi-out1", NULL, dummy_audio_callback); + out2 = open_test_voice_out(be, "test-multi-out2", NULL, dummy_audio_callback); + in1 = open_test_voice_in(be, "test-multi-in1", NULL, dummy_audio_callback); + + audio_be_set_active_out(be, out1, true); + audio_be_set_active_out(be, out2, true); + audio_be_set_active_in(be, in1, true); + + g_assert_true(audio_be_is_active_out(be, out1)); + g_assert_true(audio_be_is_active_out(be, out2)); + if (in1) { + g_assert_true(audio_be_is_active_in(be, in1)); + } + + audio_be_set_active_out(be, out1, false); + audio_be_set_active_out(be, out2, false); + audio_be_set_active_in(be, in1, false); + + audio_be_close_in(be, in1); + audio_be_close_out(be, out2); + audio_be_close_out(be, out1); +} + +static const struct audsettings invalid_test_settings = { + .nchannels = 0, + .freq = SAMPLE_RATE, + .fmt = AUDIO_FORMAT_S16, + .big_endian = false, +}; + +static void test_audio_invalid_settings(void) +{ + AudioBackend *be = get_test_audio_backend(); + void *voice; + + voice = audio_be_open_out(be, NULL, "invalid", NULL, + dummy_audio_callback, &invalid_test_settings); + g_assert_null(voice); + voice = audio_be_open_in(be, NULL, "invalid", NULL, + dummy_audio_callback, &invalid_test_settings); + g_assert_null(voice); +} + +int main(int argc, char **argv) +{ + GOptionContext *context; + g_autoptr(GError) error = NULL; + g_autofree gchar *dir = NULL; + int ret; + + context = g_option_context_new("- QEMU audio test"); + g_option_context_add_main_entries(context, test_options, NULL); + + if (!g_option_context_parse(context, &argc, &argv, &error)) { + g_printerr("Option parsing failed: %s\n", error->message); + return 1; + } + g_option_context_free(context); + + g_test_init(&argc, &argv, NULL); + + module_call_init(MODULE_INIT_TRACE); + qemu_add_opts(&qemu_trace_opts); + if (opt_trace) { + trace_opt_parse(opt_trace); + qemu_set_log(LOG_TRACE, &error_fatal); + } + trace_init_backends(); + trace_init_file(); + + dir = g_test_build_filename(G_TEST_BUILT, "..", "..", NULL); + g_setenv("QEMU_MODULE_DIR", dir, true); + qemu_init_exec_dir(argv[0]); + module_call_init(MODULE_INIT_QOM); + module_init_info(qemu_modinfo); + + qemu_init_main_loop(&error_abort); + if (opt_audiodev) { + g_autofree gchar *spec = is_help_option(opt_audiodev) ? + opt_audiodev : g_strdup_printf("%s,id=%s", opt_audiodev, TEST_AUDIODEV_ID); + audio_parse_option(spec); + } + audio_create_default_audiodevs(); + audio_init_audiodevs(); + + g_test_add_func("/audio/prio-list", test_audio_prio_list); + + g_test_add_func("/audio/out/active-state", test_audio_out_active_state); + g_test_add_func("/audio/out/sine-wave", test_audio_out_sine_wave); + g_test_add_func("/audio/out/buffer-size", test_audio_out_buffer_size); + g_test_add_func("/audio/out/volume", test_audio_out_volume); + g_test_add_func("/audio/out/capture", test_audio_capture); + + g_test_add_func("/audio/in/active-state", test_audio_in_active_state); + g_test_add_func("/audio/in/volume", test_audio_in_volume); + + g_test_add_func("/audio/null-handling", test_audio_null_handling); + g_test_add_func("/audio/multiple-voices", test_audio_multiple_voices); + g_test_add_func("/audio/invalid-settings", test_audio_invalid_settings); + + ret = g_test_run(); + + audio_cleanup(); + + return ret; +} diff --git a/tests/data/acpi/disassemle-aml.sh b/tests/data/acpi/disassemble-aml.sh similarity index 92% rename from tests/data/acpi/disassemle-aml.sh rename to tests/data/acpi/disassemble-aml.sh index 89561d233d24..62e1991ace59 100755 --- a/tests/data/acpi/disassemle-aml.sh +++ b/tests/data/acpi/disassemble-aml.sh @@ -1,4 +1,4 @@ -#!/usr/bin/bash +#!/usr/bin/env bash outdir= while getopts "o:" arg; do @@ -7,7 +7,7 @@ while getopts "o:" arg; do outdir=$OPTARG ;; \? ) - echo "Usage: ./tests/data/acpi/disassemle-aml.sh [-o ]" + echo "Usage: ./tests/data/acpi/disassemble-aml.sh [-o ]" exit 1 ;; diff --git a/tests/data/acpi/rebuild-expected-aml.sh b/tests/data/acpi/rebuild-expected-aml.sh index cbf9ffe0ddd9..af45cf30070b 100755 --- a/tests/data/acpi/rebuild-expected-aml.sh +++ b/tests/data/acpi/rebuild-expected-aml.sh @@ -57,7 +57,7 @@ old_allowed_dif=`grep -v -e 'List of comma-separated changed AML files to ignore echo '/* List of comma-separated changed AML files to ignore */' > ${SRC_PATH}/tests/qtest/bios-tables-test-allowed-diff.h echo "The files were rebuilt and can be added to git." -echo "You can use ${SRC_PATH}/tests/data/acpi/disassemle-aml.sh to disassemble them to ASL." +echo "You can use ${SRC_PATH}/tests/data/acpi/disassemble-aml.sh to disassemble them to ASL." if [ -z "$old_allowed_dif" ]; then echo "Note! Please do not commit expected files with source changes" diff --git a/tests/data/acpi/x86/q35/CEDT.cxl b/tests/data/acpi/x86/q35/CEDT.cxl index ff8203af0702..c35f3882eee6 100644 Binary files a/tests/data/acpi/x86/q35/CEDT.cxl and b/tests/data/acpi/x86/q35/CEDT.cxl differ diff --git a/tests/docker/Makefile.include b/tests/docker/Makefile.include index 38467cca6104..df14538c0f52 100644 --- a/tests/docker/Makefile.include +++ b/tests/docker/Makefile.include @@ -16,12 +16,15 @@ DOCKER_DEFAULT_REGISTRY := registry.gitlab.com/qemu-project/qemu endif DOCKER_REGISTRY := $(if $(REGISTRY),$(REGISTRY),$(DOCKER_DEFAULT_REGISTRY)) -RUNC ?= $(if $(shell command -v docker), docker, podman) -DOCKER_SCRIPT=$(SRC_PATH)/tests/docker/docker.py --engine $(RUNC) +CONTAINER_ENGINE = auto +DOCKER_SCRIPT=$(SRC_PATH)/tests/docker/docker.py --engine $(CONTAINER_ENGINE) +RUNC ?= $(shell $(DOCKER_SCRIPT) probe) CUR_TIME := $(shell date +%Y-%m-%d-%H.%M.%S.$$$$) DOCKER_SRC_COPY := $(BUILD_DIR)/docker-src.$(CUR_TIME) +DOCKER_V ?= $(V) + .DELETE_ON_ERROR: $(DOCKER_SRC_COPY) $(DOCKER_SRC_COPY): @mkdir $@ @@ -39,14 +42,14 @@ docker-qemu-src: $(DOCKER_SRC_COPY) docker-image-%: $(DOCKER_FILES_DIR)/%.docker $(call quiet-command, \ DOCKER_BUILDKIT=1 $(RUNC) build \ - $(if $V,,--quiet) \ + $(if $(DOCKER_V),,--quiet) \ $(if $(NOCACHE),--no-cache, \ $(if $(DOCKER_REGISTRY),--cache-from $(DOCKER_REGISTRY)/qemu/$*)) \ --build-arg BUILDKIT_INLINE_CACHE=1 \ $(if $(NOUSER),, \ --build-arg USER=$(USER) \ --build-arg UID=$(UID)) \ - -t qemu/$* - < $< $(if $V,,> /dev/null),\ + -t qemu/$* - < $< $(if $(DOCKER_V),,> /dev/null),\ "BUILD", $*) # General rule for inspecting registry images. @@ -72,7 +75,7 @@ docker-binfmt-image-debian-%: $(DOCKER_FILES_DIR)/debian-bootstrap.docker DEB_TYPE=$(DEB_TYPE) \ $(if $(DEB_URL),DEB_URL=$(DEB_URL),) \ $(DOCKER_SCRIPT) build -t qemu/debian-$* -f $< \ - $(if $V,,--quiet) $(if $(NOCACHE),--no-cache) \ + $(if $(DOCKER_V),,--quiet) $(if $(NOCACHE),--no-cache) \ $(if $(NOUSER),,--add-current-user) \ $(if $(EXTRA_FILES),--extra-files $(EXTRA_FILES)) \ $(if $(EXECUTABLE),--include-executable=$(EXECUTABLE)), \ @@ -104,16 +107,17 @@ debian-toolchain-run = \ $(if $(NOCACHE)$(NOFETCH), \ $(call quiet-command, \ $(DOCKER_SCRIPT) build -t qemu/$1 -f $< \ - $(if $V,,--quiet) $(if $(NOCACHE),--no-cache) \ + $(if $(DOCKER_V),,--quiet) \ + $(if $(NOCACHE),--no-cache) \ --registry $(DOCKER_REGISTRY) --extra-files \ $(DOCKER_FILES_DIR)/$1.d/build-toolchain.sh, \ "BUILD", $1), \ $(call quiet-command, \ - $(DOCKER_SCRIPT) fetch $(if $V,,--quiet) \ + $(DOCKER_SCRIPT) fetch $(if $(DOCKER_V),,--quiet) \ qemu/$1 $(DOCKER_REGISTRY), \ "FETCH", $1) \ $(call quiet-command, \ - $(DOCKER_SCRIPT) update $(if $V,,--quiet) \ + $(DOCKER_SCRIPT) update $(if $(DOCKER_V),,--quiet) \ qemu/$1 \ $(if $(NOUSER),,--add-current-user) \ "PREPARE", $1)) @@ -230,7 +234,10 @@ docker-run: docker-qemu-src -e TARGET_LIST=$(subst $(SPACE),$(COMMA),$(TARGET_LIST)) \ -e EXTRA_CONFIGURE_OPTS="$(EXTRA_CONFIGURE_OPTS)" \ -e TEST_COMMAND="$(TEST_COMMAND)" \ - -e V=$V -e J=$J -e DEBUG=$(DEBUG) \ + -e V=$V \ + -e DOCKER_V=$(DOCKER_V) \ + -e J=$J \ + -e DEBUG=$(DEBUG) \ -e SHOW_ENV=$(SHOW_ENV) \ $(if $(NOUSER),, \ -v $(DOCKER_QEMU_CACHE_DIR):$(DOCKER_QEMU_CACHE_DIR) \ diff --git a/tests/docker/docker.py b/tests/docker/docker.py index 3b8a26704df1..9e18b984f45f 100755 --- a/tests/docker/docker.py +++ b/tests/docker/docker.py @@ -76,14 +76,16 @@ def _guess_engine_command(): commands = [] if USE_ENGINE in [EngineEnum.AUTO, EngineEnum.PODMAN]: - commands += [["podman"]] + commands += [["podman"], ["podman-remote"], ["podman", "--remote"]] if USE_ENGINE in [EngineEnum.AUTO, EngineEnum.DOCKER]: commands += [["docker"], ["sudo", "-n", "docker"]] for cmd in commands: try: - # docker version will return the client details in stdout - # but still report a status of 1 if it can't contact the daemon - if subprocess.call(cmd + ["version"], + # 'version' is not sufficient to prove a working binary + # for podman. 'info' is a stronger check that is more + # likely to correlate with ability to create containers, + # and required to detect the need for podman remote + if subprocess.call(cmd + ["info"], stdout=DEVNULL, stderr=DEVNULL) == 0: return cmd except OSError: @@ -618,12 +620,7 @@ class ProbeCommand(SubCommand): def run(self, args, argv): try: docker = Docker() - if docker._command[0] == "docker": - print("docker") - elif docker._command[0] == "sudo": - print("sudo docker") - elif docker._command[0] == "podman": - print("podman") + print(" ".join(docker._command)) except Exception: print("no") diff --git a/tests/docker/dockerfiles/alpine.docker b/tests/docker/dockerfiles/alpine.docker index 03dd6851f60e..c0303feb48ab 100644 --- a/tests/docker/dockerfiles/alpine.docker +++ b/tests/docker/dockerfiles/alpine.docker @@ -1,10 +1,10 @@ # THIS FILE WAS AUTO-GENERATED # -# $ lcitool dockerfile --layers all alpine-321 qemu +# $ lcitool dockerfile --layers all alpine-323 qemu # # https://gitlab.com/libvirt/libvirt-ci -FROM docker.io/library/alpine:3.21 +FROM docker.io/library/alpine:3.23 RUN apk update && \ apk upgrade && \ @@ -87,8 +87,10 @@ RUN apk update && \ py3-numpy \ py3-pillow \ py3-pip \ + py3-setuptools \ py3-sphinx \ py3-sphinx_rtd_theme \ + py3-wheel \ py3-yaml \ python3 \ rpm2cpio \ diff --git a/tests/docker/dockerfiles/centos9.docker b/tests/docker/dockerfiles/centos9.docker index 670e22be5adb..6b1aa6dc0de7 100644 --- a/tests/docker/dockerfiles/centos9.docker +++ b/tests/docker/dockerfiles/centos9.docker @@ -6,126 +6,128 @@ FROM quay.io/centos/centos:stream9 -RUN dnf distro-sync -y && \ - dnf install 'dnf-command(config-manager)' -y && \ - dnf config-manager --set-enabled -y crb && \ - dnf install -y epel-release && \ - dnf install -y epel-next-release && \ - dnf install -y \ - SDL2-devel \ - alsa-lib-devel \ - bash \ - bc \ - bindgen-cli \ - bison \ - brlapi-devel \ - bzip2 \ - bzip2-devel \ - ca-certificates \ - capstone-devel \ - ccache \ - clang \ - compiler-rt \ - coreutils-single \ - ctags \ - cyrus-sasl-devel \ - daxctl-devel \ - dbus-daemon \ - device-mapper-multipath-devel \ - diffutils \ - findutils \ - flex \ - fuse3-devel \ - gcc \ - gettext \ - git \ - glib2-devel \ - glib2-static \ - glibc-langpack-en \ - glibc-static \ - gnutls-devel \ - gtk3-devel \ - hostname \ - jemalloc-devel \ - json-c-devel \ - libaio-devel \ - libasan \ - libattr-devel \ - libbpf-devel \ - libcacard-devel \ - libcap-ng-devel \ - libcmocka-devel \ - libcurl-devel \ - libdrm-devel \ - libepoxy-devel \ - libfdt-devel \ - libffi-devel \ - libgcrypt-devel \ - libiscsi-devel \ - libjpeg-devel \ - libnfs-devel \ - libpmem-devel \ - libpng-devel \ - librbd-devel \ - libseccomp-devel \ - libselinux-devel \ - libslirp-devel \ - libssh-devel \ - libtasn1-devel \ - libubsan \ - liburing-devel \ - libusbx-devel \ - libxdp-devel \ - libzstd-devel \ - llvm \ - lttng-ust-devel \ - lzo-devel \ - make \ - mesa-libgbm-devel \ - meson \ - mtools \ - ncurses-devel \ - nettle-devel \ - ninja-build \ - nmap-ncat \ - numactl-devel \ - openssh-clients \ - pam-devel \ - pcre-static \ - pipewire-devel \ - pixman-devel \ - pkgconfig \ - pulseaudio-libs-devel \ - python3 \ - python3-PyYAML \ - python3-numpy \ - python3-pillow \ - python3-pip \ - python3-sphinx \ - python3-sphinx_rtd_theme \ - python3-tomli \ - rdma-core-devel \ - rust \ - rust-std-static \ - sed \ - snappy-devel \ - socat \ - spice-protocol \ - swtpm \ - systemd-devel \ - systemtap-sdt-devel \ - tar \ - usbredir-devel \ - util-linux \ - vte291-devel \ - vulkan-tools \ - which \ - xorriso \ - zlib-devel \ - zlib-static \ - zstd && \ - dnf autoremove -y && \ - dnf clean all -y && \ +RUN dnf --quiet distro-sync -y && \ + dnf --quiet install 'dnf-command(config-manager)' -y && \ + dnf --quiet config-manager --set-enabled -y crb && \ + dnf --quiet install -y epel-release && \ + dnf --quiet install -y epel-next-release && \ + dnf --quiet install -y \ + SDL2-devel \ + alsa-lib-devel \ + bash \ + bc \ + bindgen-cli \ + bison \ + brlapi-devel \ + bzip2 \ + bzip2-devel \ + ca-certificates \ + capstone-devel \ + ccache \ + clang \ + compiler-rt \ + coreutils-single \ + ctags \ + cyrus-sasl-devel \ + daxctl-devel \ + dbus-daemon \ + device-mapper-multipath-devel \ + diffutils \ + findutils \ + flex \ + fuse3-devel \ + gcc \ + gettext \ + git \ + glib2-devel \ + glib2-static \ + glibc-langpack-en \ + glibc-static \ + gnutls-devel \ + gtk3-devel \ + hostname \ + jemalloc-devel \ + json-c-devel \ + libaio-devel \ + libasan \ + libattr-devel \ + libbpf-devel \ + libcacard-devel \ + libcap-ng-devel \ + libcmocka-devel \ + libcurl-devel \ + libdrm-devel \ + libepoxy-devel \ + libfdt-devel \ + libffi-devel \ + libgcrypt-devel \ + libiscsi-devel \ + libjpeg-devel \ + libnfs-devel \ + libpmem-devel \ + libpng-devel \ + librbd-devel \ + libseccomp-devel \ + libselinux-devel \ + libslirp-devel \ + libssh-devel \ + libtasn1-devel \ + libubsan \ + liburing-devel \ + libusbx-devel \ + libxdp-devel \ + libzstd-devel \ + llvm \ + lttng-ust-devel \ + lzo-devel \ + make \ + mesa-libgbm-devel \ + meson \ + mtools \ + ncurses-devel \ + nettle-devel \ + ninja-build \ + nmap-ncat \ + numactl-devel \ + openssh-clients \ + pam-devel \ + pcre-static \ + pipewire-devel \ + pixman-devel \ + pkgconfig \ + pulseaudio-libs-devel \ + python3 \ + python3-PyYAML \ + python3-numpy \ + python3-pillow \ + python3-pip \ + python3-setuptools \ + python3-sphinx \ + python3-sphinx_rtd_theme \ + python3-tomli \ + python3-wheel \ + rdma-core-devel \ + rust \ + rust-std-static \ + sed \ + snappy-devel \ + socat \ + spice-protocol \ + swtpm \ + systemd-devel \ + systemtap-sdt-devel \ + tar \ + usbredir-devel \ + util-linux \ + vte291-devel \ + vulkan-tools \ + which \ + xorriso \ + zlib-devel \ + zlib-static \ + zstd && \ + dnf --quiet autoremove -y && \ + dnf --quiet clean all -y && \ rm -f /usr/lib*/python3*/EXTERNALLY-MANAGED && \ rpm -qa | sort > /packages.txt && \ mkdir -p /usr/libexec/ccache-wrappers && \ diff --git a/tests/docker/dockerfiles/debian-all-test-cross.docker b/tests/docker/dockerfiles/debian-all-test-cross.docker index 1823233438a6..b73776d95b53 100644 --- a/tests/docker/dockerfiles/debian-all-test-cross.docker +++ b/tests/docker/dockerfiles/debian-all-test-cross.docker @@ -64,6 +64,8 @@ ENV AVAILABLE_COMPILERS gcc-aarch64-linux-gnu \ libc6-dev-arm64-cross \ gcc-arm-linux-gnueabihf \ libc6-dev-armhf-cross \ + gcc-alpha-linux-gnu \ + libc6.1-dev-alpha-cross \ gcc-mips-linux-gnu \ libc6-dev-mips-cross \ gcc-mips64-linux-gnuabi64 \ @@ -77,7 +79,9 @@ ENV AVAILABLE_COMPILERS gcc-aarch64-linux-gnu \ gcc-riscv64-linux-gnu \ libc6-dev-riscv64-cross \ gcc-s390x-linux-gnu \ - libc6-dev-s390x-cross + libc6-dev-s390x-cross\ + gcc-sh4-linux-gnu \ + libc6-dev-sh4-cross RUN if dpkg-architecture -e amd64; then \ export AVAILABLE_COMPILERS="${AVAILABLE_COMPILERS} gcc-hppa-linux-gnu libc6-dev-hppa-cross"; \ export AVAILABLE_COMPILERS="${AVAILABLE_COMPILERS} gcc-m68k-linux-gnu libc6-dev-m68k-cross"; \ @@ -90,7 +94,7 @@ apt install -y --no-install-recommends \ ${AVAILABLE_COMPILERS} && \ dpkg-query --showformat '${Package}_${Version}_${Architecture}' --show > /packages.txt ENV QEMU_CONFIGURE_OPTS --disable-docs -ENV DEF_TARGET_LIST aarch64-linux-user,arm-linux-user,hppa-linux-user,i386-linux-user,m68k-linux-user,mips-linux-user,mips64-linux-user,mips64el-linux-user,mipsel-linux-user,ppc-linux-user,ppc64-linux-user,ppc64le-linux-user,riscv64-linux-user,s390x-linux-user,sparc64-linux-user +ENV DEF_TARGET_LIST aarch64-linux-user,alpha-linux-user,arm-linux-user,hppa-linux-user,i386-linux-user,m68k-linux-user,mips-linux-user,mips64-linux-user,mips64el-linux-user,mipsel-linux-user,ppc-linux-user,ppc64-linux-user,ppc64le-linux-user,riscv64-linux-user,s390x-linux-user,sh4-linux-user,sparc64-linux-user # As a final step configure the user (if env is defined) ARG USER ARG UID diff --git a/tests/docker/dockerfiles/debian-hexagon-cross.docker b/tests/docker/dockerfiles/debian-hexagon-cross.docker index 23152b4918b6..91d4b71ac956 100644 --- a/tests/docker/dockerfiles/debian-hexagon-cross.docker +++ b/tests/docker/dockerfiles/debian-hexagon-cross.docker @@ -5,10 +5,12 @@ # needs to be able to build QEMU itself in CI we include its # build-deps. # -FROM docker.io/library/debian:11-slim +FROM docker.io/library/debian:13-slim + +# Add deb-src repository sources +RUN sed -i "s/^Types: deb$/Types: deb deb-src/" \ + /etc/apt/sources.list.d/debian.sources -# Duplicate deb line as deb-src -RUN cat /etc/apt/sources.list | sed "s/^deb\ /deb-src /" >> /etc/apt/sources.list RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt install -yy eatmydata && \ DEBIAN_FRONTEND=noninteractive eatmydata \ @@ -24,6 +26,7 @@ RUN apt-get update && \ ninja-build \ python3-pip \ python3-setuptools \ + python3-tomli \ python3-venv \ python3-wheel && \ # Install QEMU build deps for use in CI @@ -36,8 +39,6 @@ RUN apt-get update && \ ln -s /usr/bin/ccache /usr/libexec/ccache-wrappers/gcc && \ dpkg-query --showformat '${Package}_${Version}_${Architecture}\n' --show > /packages.txt -RUN /usr/bin/pip3 install tomli - ENV TOOLCHAIN_INSTALL /opt ENV TOOLCHAIN_RELEASE 12.Dec.2023 ENV TOOLCHAIN_BASENAME "clang+llvm-${TOOLCHAIN_RELEASE}-cross-hexagon-unknown-linux-musl" diff --git a/tests/docker/dockerfiles/debian-legacy-test-cross.docker b/tests/docker/dockerfiles/debian-legacy-test-cross.docker deleted file mode 100644 index 5a6616b7d393..000000000000 --- a/tests/docker/dockerfiles/debian-legacy-test-cross.docker +++ /dev/null @@ -1,51 +0,0 @@ -# Docker legacy cross-compiler target (tests and minimal qemu) -# -# Compilers for some of our older targets which we cant currently -# upgrade. Currently: -# -# libc6.1-dev-alpha-cross: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1054412 -# sh4-linux-user: binaries don't run with bookworm compiler -# -# As we are targeting check-tcg here we only need minimal qemu -# dependencies and the relevant cross compilers. - -FROM docker.io/library/debian:11-slim - -# Duplicate deb line as deb-src -RUN cat /etc/apt/sources.list | sed "s/^deb\ /deb-src /" >> /etc/apt/sources.list - -RUN export DEBIAN_FRONTEND=noninteractive && \ - apt-get update && \ - apt-get install -y eatmydata && \ - eatmydata apt-get dist-upgrade -y && \ - apt build-dep -yy qemu - -# Add extra build tools and as many cross compilers as we can for testing -RUN DEBIAN_FRONTEND=noninteractive eatmydata \ - apt install -y --no-install-recommends \ - bison \ - ccache \ - clang \ - flex \ - git \ - ninja-build \ - gcc-alpha-linux-gnu \ - libc6.1-dev-alpha-cross \ - gcc-sh4-linux-gnu \ - libc6-dev-sh4-cross \ - python3-pip \ - python3-setuptools \ - python3-venv \ - python3-wheel && \ - dpkg-query --showformat '${Package}_${Version}_${Architecture}\n' --show > /packages.txt - -RUN /usr/bin/pip3 install tomli - -ENV QEMU_CONFIGURE_OPTS --disable-system --disable-docs --disable-tools -ENV DEF_TARGET_LIST alpha-linux-user,sh4-linux-user -ENV MAKE /usr/bin/make -# As a final step configure the user (if env is defined) -ARG USER -ARG UID -RUN if [ "${USER}" ]; then \ - id ${USER} 2>/dev/null || useradd -u ${UID} -U ${USER}; fi diff --git a/tests/docker/dockerfiles/debian-loongarch-cross.docker b/tests/docker/dockerfiles/debian-loongarch-cross.docker index 538ab534902a..55b3dbe45107 100644 --- a/tests/docker/dockerfiles/debian-loongarch-cross.docker +++ b/tests/docker/dockerfiles/debian-loongarch-cross.docker @@ -4,10 +4,11 @@ # This docker target uses prebuilt toolchains for LoongArch64 from: # https://github.com/loongson/build-tools/releases # -FROM docker.io/library/debian:11-slim +FROM docker.io/library/debian:13-slim -# Duplicate deb line as deb-src -RUN cat /etc/apt/sources.list | sed "s/^deb\ /deb-src /" >> /etc/apt/sources.list +# Add deb-src repository sources +RUN sed -i "s/^Types: deb$/Types: deb deb-src/" \ + /etc/apt/sources.list.d/debian.sources RUN export DEBIAN_FRONTEND=noninteractive && \ apt-get update && \ @@ -31,12 +32,11 @@ RUN apt-get update && \ ninja-build \ python3-pip \ python3-setuptools \ + python3-tomli \ python3-venv \ python3-wheel && \ dpkg-query --showformat '${Package}_${Version}_${Architecture}\n' --show > /packages.txt -RUN /usr/bin/pip3 install tomli - RUN curl -#SL https://github.com/loongson/build-tools/releases/download/2023.08.08/CLFS-loongarch64-8.1-x86_64-cross-tools-gcc-glibc.tar.xz \ | tar -xJC /opt diff --git a/tests/docker/dockerfiles/debian-toolchain.docker b/tests/docker/dockerfiles/debian-toolchain.docker index ab4ce29533dc..9a256209a78d 100644 --- a/tests/docker/dockerfiles/debian-toolchain.docker +++ b/tests/docker/dockerfiles/debian-toolchain.docker @@ -4,13 +4,15 @@ # This dockerfile is used for building a cross-compiler toolchain. # The script for building the toolchain is supplied via extra-files. # -FROM docker.io/library/debian:11-slim +FROM docker.io/library/debian:13-slim # Install build utilities for building gcc and glibc. # ??? The build-dep isn't working, missing a number of # minimal build dependiencies, e.g. libmpc. -RUN sed 's/^deb /deb-src /' /etc/apt/sources.list.d/deb-src.list +# Add deb-src repository sources +RUN sed -i "s/^Types: deb$/Types: deb deb-src/" \ + /etc/apt/sources.list.d/debian.sources RUN apt update && \ DEBIAN_FRONTEND=noninteractive apt install -yy eatmydata && \ @@ -34,7 +36,7 @@ RUN cd /root && ./build-toolchain.sh # Throw away the extra toolchain build deps, the downloaded source, # and the build trees by restoring the original image, # then copying the built toolchain from stage 0. -FROM docker.io/library/debian:11-slim +FROM docker.io/library/debian:13-slim RUN apt update && \ DEBIAN_FRONTEND=noninteractive apt install -yy eatmydata && \ DEBIAN_FRONTEND=noninteractive eatmydata \ diff --git a/tests/docker/dockerfiles/debian-tricore-cross.docker b/tests/docker/dockerfiles/debian-tricore-cross.docker index 7e00e870ceb0..fd797dc7ee44 100644 --- a/tests/docker/dockerfiles/debian-tricore-cross.docker +++ b/tests/docker/dockerfiles/debian-tricore-cross.docker @@ -9,7 +9,7 @@ # # SPDX-License-Identifier: GPL-2.0-or-later # -FROM docker.io/library/debian:11-slim +FROM docker.io/library/debian:13-slim RUN apt update && \ DEBIAN_FRONTEND=noninteractive apt install -yy eatmydata && \ @@ -31,12 +31,11 @@ RUN apt update && \ pkgconf \ python3-pip \ python3-setuptools \ + python3-tomli \ python3-wheel \ python3-venv && \ dpkg-query --showformat '${Package}_${Version}_${Architecture}\n' --show > /packages.txt -RUN /usr/bin/pip3 install tomli - RUN curl -#SL https://github.com/bkoppelmann/package_940/releases/download/tricore-toolchain-9.40/tricore-toolchain-9.4.0.tar.gz \ | tar -xzC /usr/local/ diff --git a/tests/docker/dockerfiles/debian-xtensa-cross.docker b/tests/docker/dockerfiles/debian-xtensa-cross.docker index d011eee2ad33..ef63e44e2efb 100644 --- a/tests/docker/dockerfiles/debian-xtensa-cross.docker +++ b/tests/docker/dockerfiles/debian-xtensa-cross.docker @@ -5,7 +5,7 @@ # using a prebuilt toolchains for Xtensa cores from: # https://github.com/foss-xtensa/toolchain/releases # -FROM docker.io/library/debian:11-slim +FROM docker.io/library/debian:13-slim RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt install -yy eatmydata && \ diff --git a/tests/docker/dockerfiles/fedora-rust-nightly.docker b/tests/docker/dockerfiles/fedora-rust-nightly.docker index 8e3b3a9fd906..043b42a0a9b0 100644 --- a/tests/docker/dockerfiles/fedora-rust-nightly.docker +++ b/tests/docker/dockerfiles/fedora-rust-nightly.docker @@ -1,12 +1,12 @@ # THIS FILE WAS AUTO-GENERATED # -# $ lcitool dockerfile --layers all fedora-41 qemu +# $ lcitool dockerfile --layers all fedora-43 qemu # # https://gitlab.com/libvirt/libvirt-ci -FROM registry.fedoraproject.org/fedora:41 +FROM registry.fedoraproject.org/fedora:43 -RUN dnf install -y nosync && \ +RUN dnf --quiet install -y nosync && \ printf '#!/bin/sh\n\ if test -d /usr/lib64\n\ then\n\ @@ -16,134 +16,134 @@ else\n\ fi\n\ exec "$@"\n' > /usr/bin/nosync && \ chmod +x /usr/bin/nosync && \ - nosync dnf update -y && \ - nosync dnf install -y \ - SDL2-devel \ - SDL2_image-devel \ - alsa-lib-devel \ - bash \ - bc \ - bindgen-cli \ - bison \ - brlapi-devel \ - bzip2 \ - bzip2-devel \ - ca-certificates \ - capstone-devel \ - ccache \ - clang \ - compiler-rt \ - coreutils \ - ctags \ - cyrus-sasl-devel \ - daxctl-devel \ - dbus-daemon \ - device-mapper-multipath-devel \ - diffutils \ - findutils \ - flex \ - fuse3-devel \ - gcc \ - gcovr \ - gettext \ - git \ - glib2-devel \ - glib2-static \ - glibc-langpack-en \ - glibc-static \ - glusterfs-api-devel \ - gnutls-devel \ - gtk-vnc2-devel \ - gtk3-devel \ - hostname \ - jemalloc-devel \ - json-c-devel \ - libaio-devel \ - libasan \ - libattr-devel \ - libbpf-devel \ - libcacard-devel \ - libcap-ng-devel \ - libcbor-devel \ - libcmocka-devel \ - libcurl-devel \ - libdrm-devel \ - libepoxy-devel \ - libfdt-devel \ - libffi-devel \ - libgcrypt-devel \ - libiscsi-devel \ - libjpeg-devel \ - libnfs-devel \ - libpmem-devel \ - libpng-devel \ - librbd-devel \ - libseccomp-devel \ - libselinux-devel \ - libslirp-devel \ - libssh-devel \ - libtasn1-devel \ - libubsan \ - liburing-devel \ - libusbx-devel \ - libxdp-devel \ - libzstd-devel \ - llvm \ - lttng-ust-devel \ - lzo-devel \ - make \ - mesa-libgbm-devel \ - mtools \ - ncurses-devel \ - nettle-devel \ - ninja-build \ - nmap-ncat \ - numactl-devel \ - openssh-clients \ - pam-devel \ - pcre2-static \ - pipewire-devel \ - pixman-devel \ - pkgconfig \ - pulseaudio-libs-devel \ - python3 \ - python3-PyYAML \ - python3-numpy \ - python3-opencv \ - python3-pillow \ - python3-pip \ - python3-setuptools \ - python3-sphinx \ - python3-sphinx_rtd_theme \ - python3-wheel \ - rdma-core-devel \ - rust \ - rust-std-static \ - sed \ - snappy-devel \ - socat \ - sparse \ - spice-protocol \ - spice-server-devel \ - swtpm \ - systemd-devel \ - systemtap-sdt-dtrace \ - tar \ - tesseract \ - tesseract-langpack-eng \ - usbredir-devel \ - util-linux \ - virglrenderer-devel \ - vte291-devel \ - vulkan-tools \ - which \ - xen-devel \ - xorriso \ - zlib-devel \ - zlib-static \ - zstd && \ - nosync dnf autoremove -y && \ - nosync dnf clean all -y && \ + nosync dnf --quiet update -y && \ + nosync dnf --quiet install -y \ + SDL2_image-devel \ + alsa-lib-devel \ + bash \ + bc \ + bindgen-cli \ + bison \ + brlapi-devel \ + bzip2 \ + bzip2-devel \ + ca-certificates \ + capstone-devel \ + ccache \ + clang \ + compiler-rt \ + coreutils \ + ctags \ + cyrus-sasl-devel \ + daxctl-devel \ + dbus-daemon \ + device-mapper-multipath-devel \ + diffutils \ + findutils \ + flex \ + fuse3-devel \ + gcc \ + gcovr \ + gettext \ + git \ + glib2-devel \ + glib2-static \ + glibc-langpack-en \ + glibc-static \ + glusterfs-api-devel \ + gnutls-devel \ + gtk-vnc2-devel \ + gtk3-devel \ + hostname \ + jemalloc-devel \ + json-c-devel \ + libaio-devel \ + libasan \ + libattr-devel \ + libbpf-devel \ + libcacard-devel \ + libcap-ng-devel \ + libcbor-devel \ + libcmocka-devel \ + libcurl-devel \ + libdrm-devel \ + libepoxy-devel \ + libfdt-devel \ + libffi-devel \ + libgcrypt-devel \ + libiscsi-devel \ + libjpeg-devel \ + libnfs-devel \ + libpmem-devel \ + libpng-devel \ + librbd-devel \ + libseccomp-devel \ + libselinux-devel \ + libslirp-devel \ + libssh-devel \ + libtasn1-devel \ + libubsan \ + liburing-devel \ + libusbx-devel \ + libxdp-devel \ + libzstd-devel \ + llvm \ + lttng-ust-devel \ + lzo-devel \ + make \ + mesa-libgbm-devel \ + mtools \ + ncurses-devel \ + nettle-devel \ + ninja-build \ + nmap-ncat \ + numactl-devel \ + openssh-clients \ + pam-devel \ + pcre2-static \ + pipewire-devel \ + pixman-devel \ + pkgconfig \ + pulseaudio-libs-devel \ + python3 \ + python3-PyYAML \ + python3-numpy \ + python3-opencv \ + python3-pillow \ + python3-pip \ + python3-setuptools \ + python3-sphinx \ + python3-sphinx_rtd_theme \ + python3-wheel \ + rdma-core-devel \ + rust \ + rust-std-static \ + sdl2-compat-devel \ + sed \ + snappy-devel \ + socat \ + sparse \ + spice-protocol \ + spice-server-devel \ + swtpm \ + systemd-devel \ + systemtap-sdt-dtrace \ + tar \ + tesseract \ + tesseract-langpack-eng \ + usbredir-devel \ + util-linux \ + virglrenderer-devel \ + vte291-devel \ + vulkan-tools \ + which \ + xen-devel \ + xorriso \ + zlib-devel \ + zlib-static \ + zstd && \ + nosync dnf --quiet autoremove -y && \ + nosync dnf --quiet clean all -y && \ rm -f /usr/lib*/python3*/EXTERNALLY-MANAGED && \ rpm -qa | sort > /packages.txt && \ mkdir -p /usr/libexec/ccache-wrappers && \ diff --git a/tests/docker/dockerfiles/fedora-win64-cross.docker b/tests/docker/dockerfiles/fedora-win64-cross.docker index c5e1fcebf517..818485c4ba21 100644 --- a/tests/docker/dockerfiles/fedora-win64-cross.docker +++ b/tests/docker/dockerfiles/fedora-win64-cross.docker @@ -1,12 +1,12 @@ # THIS FILE WAS AUTO-GENERATED # -# $ lcitool dockerfile --layers all --cross-arch mingw64 fedora-41 qemu,qemu-win-installer +# $ lcitool dockerfile --layers all --cross-arch mingw64 fedora-43 qemu,qemu-win-installer # # https://gitlab.com/libvirt/libvirt-ci -FROM registry.fedoraproject.org/fedora:41 +FROM registry.fedoraproject.org/fedora:43 -RUN dnf install -y nosync && \ +RUN dnf --quiet install -y nosync && \ printf '#!/bin/sh\n\ if test -d /usr/lib64\n\ then\n\ @@ -16,59 +16,59 @@ else\n\ fi\n\ exec "$@"\n' > /usr/bin/nosync && \ chmod +x /usr/bin/nosync && \ - nosync dnf update -y && \ - nosync dnf install -y \ - bash \ - bc \ - bindgen-cli \ - bison \ - bzip2 \ - ca-certificates \ - ccache \ - compiler-rt \ - coreutils \ - ctags \ - dbus-daemon \ - diffutils \ - findutils \ - flex \ - gcc \ - gcovr \ - git \ - glib2-devel \ - glibc-langpack-en \ - hostname \ - llvm \ - make \ - mtools \ - ninja-build \ - nmap-ncat \ - openssh-clients \ - python3 \ - python3-PyYAML \ - python3-numpy \ - python3-opencv \ - python3-pillow \ - python3-pip \ - python3-setuptools \ - python3-sphinx \ - python3-sphinx_rtd_theme \ - python3-wheel \ - rust \ - sed \ - socat \ - sparse \ - swtpm \ - tar \ - tesseract \ - tesseract-langpack-eng \ - util-linux \ - vulkan-tools \ - which \ - xorriso \ - zstd && \ - nosync dnf autoremove -y && \ - nosync dnf clean all -y && \ + nosync dnf --quiet update -y && \ + nosync dnf --quiet install -y \ + bash \ + bc \ + bindgen-cli \ + bison \ + bzip2 \ + ca-certificates \ + ccache \ + compiler-rt \ + coreutils \ + ctags \ + dbus-daemon \ + diffutils \ + findutils \ + flex \ + gcc \ + gcovr \ + git \ + glib2-devel \ + glibc-langpack-en \ + hostname \ + llvm \ + make \ + mtools \ + ninja-build \ + nmap-ncat \ + openssh-clients \ + python3 \ + python3-PyYAML \ + python3-numpy \ + python3-opencv \ + python3-pillow \ + python3-pip \ + python3-setuptools \ + python3-sphinx \ + python3-sphinx_rtd_theme \ + python3-wheel \ + rust \ + sed \ + socat \ + sparse \ + swtpm \ + tar \ + tesseract \ + tesseract-langpack-eng \ + util-linux \ + vulkan-tools \ + which \ + xorriso \ + zstd && \ + nosync dnf --quiet autoremove -y && \ + nosync dnf --quiet clean all -y && \ rm -f /usr/lib*/python3*/EXTERNALLY-MANAGED RUN /usr/bin/pip3 install meson==1.8.1 @@ -79,31 +79,31 @@ ENV MAKE="/usr/bin/make" ENV NINJA="/usr/bin/ninja" ENV PYTHON="/usr/bin/python3" -RUN nosync dnf install -y \ - mingw-w64-tools \ - mingw32-nsis \ - mingw64-SDL2 \ - mingw64-SDL2_image \ - mingw64-bzip2 \ - mingw64-curl \ - mingw64-gcc \ - mingw64-gcc-c++ \ - mingw64-gettext \ - mingw64-glib2 \ - mingw64-gnutls \ - mingw64-gtk-vnc2 \ - mingw64-gtk3 \ - mingw64-libepoxy \ - mingw64-libfdt \ - mingw64-libgcrypt \ - mingw64-libjpeg-turbo \ - mingw64-libpng \ - mingw64-libtasn1 \ - mingw64-nettle \ - mingw64-pixman \ - mingw64-pkg-config \ - rust-std-static-x86_64-pc-windows-gnu && \ - nosync dnf clean all -y && \ +RUN nosync dnf --quiet install -y \ + mingw-w64-tools \ + mingw32-nsis \ + mingw64-SDL2 \ + mingw64-SDL2_image \ + mingw64-bzip2 \ + mingw64-curl \ + mingw64-gcc \ + mingw64-gcc-c++ \ + mingw64-gettext \ + mingw64-glib2 \ + mingw64-gnutls \ + mingw64-gtk-vnc2 \ + mingw64-gtk3 \ + mingw64-libepoxy \ + mingw64-libfdt \ + mingw64-libgcrypt \ + mingw64-libjpeg-turbo \ + mingw64-libpng \ + mingw64-libtasn1 \ + mingw64-nettle \ + mingw64-pixman \ + mingw64-pkg-config \ + rust-std-static-x86_64-pc-windows-gnu && \ + nosync dnf --quiet clean all -y && \ rpm -qa | sort > /packages.txt && \ mkdir -p /usr/libexec/ccache-wrappers && \ ln -s /usr/bin/ccache /usr/libexec/ccache-wrappers/x86_64-w64-mingw32-c++ && \ diff --git a/tests/docker/dockerfiles/fedora.docker b/tests/docker/dockerfiles/fedora.docker index 9278d7976937..32aaf01aff4d 100644 --- a/tests/docker/dockerfiles/fedora.docker +++ b/tests/docker/dockerfiles/fedora.docker @@ -1,12 +1,12 @@ # THIS FILE WAS AUTO-GENERATED # -# $ lcitool dockerfile --layers all fedora-41 qemu +# $ lcitool dockerfile --layers all fedora-43 qemu # # https://gitlab.com/libvirt/libvirt-ci -FROM registry.fedoraproject.org/fedora:41 +FROM registry.fedoraproject.org/fedora:43 -RUN dnf install -y nosync && \ +RUN dnf --quiet install -y nosync && \ printf '#!/bin/sh\n\ if test -d /usr/lib64\n\ then\n\ @@ -16,134 +16,134 @@ else\n\ fi\n\ exec "$@"\n' > /usr/bin/nosync && \ chmod +x /usr/bin/nosync && \ - nosync dnf update -y && \ - nosync dnf install -y \ - SDL2-devel \ - SDL2_image-devel \ - alsa-lib-devel \ - bash \ - bc \ - bindgen-cli \ - bison \ - brlapi-devel \ - bzip2 \ - bzip2-devel \ - ca-certificates \ - capstone-devel \ - ccache \ - clang \ - compiler-rt \ - coreutils \ - ctags \ - cyrus-sasl-devel \ - daxctl-devel \ - dbus-daemon \ - device-mapper-multipath-devel \ - diffutils \ - findutils \ - flex \ - fuse3-devel \ - gcc \ - gcovr \ - gettext \ - git \ - glib2-devel \ - glib2-static \ - glibc-langpack-en \ - glibc-static \ - glusterfs-api-devel \ - gnutls-devel \ - gtk-vnc2-devel \ - gtk3-devel \ - hostname \ - jemalloc-devel \ - json-c-devel \ - libaio-devel \ - libasan \ - libattr-devel \ - libbpf-devel \ - libcacard-devel \ - libcap-ng-devel \ - libcbor-devel \ - libcmocka-devel \ - libcurl-devel \ - libdrm-devel \ - libepoxy-devel \ - libfdt-devel \ - libffi-devel \ - libgcrypt-devel \ - libiscsi-devel \ - libjpeg-devel \ - libnfs-devel \ - libpmem-devel \ - libpng-devel \ - librbd-devel \ - libseccomp-devel \ - libselinux-devel \ - libslirp-devel \ - libssh-devel \ - libtasn1-devel \ - libubsan \ - liburing-devel \ - libusbx-devel \ - libxdp-devel \ - libzstd-devel \ - llvm \ - lttng-ust-devel \ - lzo-devel \ - make \ - mesa-libgbm-devel \ - mtools \ - ncurses-devel \ - nettle-devel \ - ninja-build \ - nmap-ncat \ - numactl-devel \ - openssh-clients \ - pam-devel \ - pcre2-static \ - pipewire-devel \ - pixman-devel \ - pkgconfig \ - pulseaudio-libs-devel \ - python3 \ - python3-PyYAML \ - python3-numpy \ - python3-opencv \ - python3-pillow \ - python3-pip \ - python3-setuptools \ - python3-sphinx \ - python3-sphinx_rtd_theme \ - python3-wheel \ - rdma-core-devel \ - rust \ - rust-std-static \ - sed \ - snappy-devel \ - socat \ - sparse \ - spice-protocol \ - spice-server-devel \ - swtpm \ - systemd-devel \ - systemtap-sdt-dtrace \ - tar \ - tesseract \ - tesseract-langpack-eng \ - usbredir-devel \ - util-linux \ - virglrenderer-devel \ - vte291-devel \ - vulkan-tools \ - which \ - xen-devel \ - xorriso \ - zlib-devel \ - zlib-static \ - zstd && \ - nosync dnf autoremove -y && \ - nosync dnf clean all -y && \ + nosync dnf --quiet update -y && \ + nosync dnf --quiet install -y \ + SDL2_image-devel \ + alsa-lib-devel \ + bash \ + bc \ + bindgen-cli \ + bison \ + brlapi-devel \ + bzip2 \ + bzip2-devel \ + ca-certificates \ + capstone-devel \ + ccache \ + clang \ + compiler-rt \ + coreutils \ + ctags \ + cyrus-sasl-devel \ + daxctl-devel \ + dbus-daemon \ + device-mapper-multipath-devel \ + diffutils \ + findutils \ + flex \ + fuse3-devel \ + gcc \ + gcovr \ + gettext \ + git \ + glib2-devel \ + glib2-static \ + glibc-langpack-en \ + glibc-static \ + glusterfs-api-devel \ + gnutls-devel \ + gtk-vnc2-devel \ + gtk3-devel \ + hostname \ + jemalloc-devel \ + json-c-devel \ + libaio-devel \ + libasan \ + libattr-devel \ + libbpf-devel \ + libcacard-devel \ + libcap-ng-devel \ + libcbor-devel \ + libcmocka-devel \ + libcurl-devel \ + libdrm-devel \ + libepoxy-devel \ + libfdt-devel \ + libffi-devel \ + libgcrypt-devel \ + libiscsi-devel \ + libjpeg-devel \ + libnfs-devel \ + libpmem-devel \ + libpng-devel \ + librbd-devel \ + libseccomp-devel \ + libselinux-devel \ + libslirp-devel \ + libssh-devel \ + libtasn1-devel \ + libubsan \ + liburing-devel \ + libusbx-devel \ + libxdp-devel \ + libzstd-devel \ + llvm \ + lttng-ust-devel \ + lzo-devel \ + make \ + mesa-libgbm-devel \ + mtools \ + ncurses-devel \ + nettle-devel \ + ninja-build \ + nmap-ncat \ + numactl-devel \ + openssh-clients \ + pam-devel \ + pcre2-static \ + pipewire-devel \ + pixman-devel \ + pkgconfig \ + pulseaudio-libs-devel \ + python3 \ + python3-PyYAML \ + python3-numpy \ + python3-opencv \ + python3-pillow \ + python3-pip \ + python3-setuptools \ + python3-sphinx \ + python3-sphinx_rtd_theme \ + python3-wheel \ + rdma-core-devel \ + rust \ + rust-std-static \ + sdl2-compat-devel \ + sed \ + snappy-devel \ + socat \ + sparse \ + spice-protocol \ + spice-server-devel \ + swtpm \ + systemd-devel \ + systemtap-sdt-dtrace \ + tar \ + tesseract \ + tesseract-langpack-eng \ + usbredir-devel \ + util-linux \ + virglrenderer-devel \ + vte291-devel \ + vulkan-tools \ + which \ + xen-devel \ + xorriso \ + zlib-devel \ + zlib-static \ + zstd && \ + nosync dnf --quiet autoremove -y && \ + nosync dnf --quiet clean all -y && \ rm -f /usr/lib*/python3*/EXTERNALLY-MANAGED && \ rpm -qa | sort > /packages.txt && \ mkdir -p /usr/libexec/ccache-wrappers && \ diff --git a/tests/docker/dockerfiles/opensuse-leap.docker b/tests/docker/dockerfiles/opensuse-leap.docker index a041d4397681..d693f2c6f857 100644 --- a/tests/docker/dockerfiles/opensuse-leap.docker +++ b/tests/docker/dockerfiles/opensuse-leap.docker @@ -98,6 +98,7 @@ RUN zypper update -y && \ python311-base \ python311-pip \ python311-setuptools \ + python311-wheel \ rdma-core-devel \ rust \ rust-bindgen \ diff --git a/tests/docker/test-fuzz b/tests/docker/test-fuzz index 7e506ae1f6e1..d2bdc8afbaab 100755 --- a/tests/docker/test-fuzz +++ b/tests/docker/test-fuzz @@ -18,7 +18,7 @@ cd "$BUILD_DIR" cp -a $QEMU_SRC . cd src mkdir build-oss-fuzz -export LSAN_OPTIONS=suppressions=scripts/oss-fuzz/lsan_suppressions.txt +export LSAN_OPTIONS=suppressions=scripts/lsan_suppressions.txt env CC="clang" CXX="clang++" CFLAGS="-fsanitize=address" ./scripts/oss-fuzz/build.sh export ASAN_OPTIONS="fast_unwind_on_malloc=0" for fuzzer in $(find ./build-oss-fuzz/DEST_DIR/ -executable -type f | grep -v slirp); do diff --git a/tests/functional/aarch64/meson.build b/tests/functional/aarch64/meson.build index 49eca1205898..7ea8c22b048e 100644 --- a/tests/functional/aarch64/meson.build +++ b/tests/functional/aarch64/meson.build @@ -46,6 +46,7 @@ tests_aarch64_system_thorough = [ 'tuxrun', 'virt', 'virt_gpu', + 'virt_vbsa', 'xen', 'xlnx_versal', ] diff --git a/tests/functional/aarch64/test_aspeed_ast2700a1.py b/tests/functional/aarch64/test_aspeed_ast2700a1.py index 61373ffe5bd4..5c0c4b0ed50f 100755 --- a/tests/functional/aarch64/test_aspeed_ast2700a1.py +++ b/tests/functional/aarch64/test_aspeed_ast2700a1.py @@ -78,13 +78,13 @@ def verify_openbmc_boot_and_login(self, name, enable_pcie=True): exec_command_and_wait_for_pattern(self, 'root', 'Password:') exec_command_and_wait_for_pattern(self, '0penBmc', f'root@{name}:~#') - ASSET_SDK_V1100_AST2700A1 = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.00/ast2700-a1-obmc.tar.gz', - 'd5ceed511cd0dfefbb102fff2d731159e0472948a28066dc0d90bcd54be76525') + ASSET_SDK_V1101_AST2700A1 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-a1-image.tar.gz', + '859808828531a51931aad3b4e70b28143eebb3cde1838ba7d8e7a2b844c8a1ab') - ASSET_SDK_V1100_AST2700A1_DCSCM = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.00/ast2700-a1-dcscm-obmc.tar.gz', - '4f8778be176ece1b57d33c4aee13bb989be114c3e4703150eaeb6f996bd5587f') + ASSET_SDK_V1101_AST2700A1_DCSCM = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-a1-dcscm-image.tar.gz', + '4654eabad75da3fd33635cd6d29b7635181daefee7294b68feb124b9d4c24116') def do_ast2700_i2c_test(self, bus_id): bus_str = str(bus_id) @@ -147,38 +147,38 @@ def start_ast2700_test_vbootrom(self, name, bus_id): self.do_test_aarch64_aspeed_sdk_start( self.scratch_file(name, 'image-bmc'), bus_id) - def test_aarch64_ast2700a1_evb_sdk_v11_00(self): + def test_aarch64_ast2700a1_evb_sdk_v11_01(self): self.set_machine('ast2700a1-evb') self.require_netdev('user') - self.archive_extract(self.ASSET_SDK_V1100_AST2700A1) + self.archive_extract(self.ASSET_SDK_V1101_AST2700A1) self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.2') self.vm.add_args('-netdev', 'user,id=net1') - self.start_ast2700_test('ast2700-a1', 1) + self.start_ast2700_test('ast2700-a1-image', 1) self.verify_openbmc_boot_and_login('ast2700-a1') self.do_ast2700_i2c_test(1) self.do_ast2700_pcie_test() - def test_aarch64_ast2700a1_evb_sdk_vbootrom_v11_00(self): + def test_aarch64_ast2700a1_evb_sdk_vbootrom_v11_01(self): self.set_machine('ast2700a1-evb') self.require_netdev('user') - self.archive_extract(self.ASSET_SDK_V1100_AST2700A1) + self.archive_extract(self.ASSET_SDK_V1101_AST2700A1) self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.2') self.vm.add_args('-netdev', 'user,id=net1') - self.start_ast2700_test_vbootrom('ast2700-a1', 1) + self.start_ast2700_test_vbootrom('ast2700-a1-image', 1) self.verify_vbootrom_firmware_flow() self.verify_openbmc_boot_start() - def test_aarch64_ast2700a1_evb_ioexp_v11_00(self): + def test_aarch64_ast2700a1_evb_ioexp_v11_01(self): self.set_machine('ast2700a1-evb') self.require_netdev('user') - self.archive_extract(self.ASSET_SDK_V1100_AST2700A1_DCSCM) + self.archive_extract(self.ASSET_SDK_V1101_AST2700A1_DCSCM) self.vm.set_machine('ast2700a1-evb,fmc-model=w25q512jv') self.vm.add_args('-device', 'tmp105,bus=ioexp0.0,address=0x4d,id=tmp-test-16') - self.start_ast2700_test('ast2700-a1-dcscm', 8) + self.start_ast2700_test('ast2700-a1-dcscm-image', 8) self.verify_openbmc_boot_and_login('ast2700-a1-dcscm', False) self.do_ast2700_i2c_test(8) self.do_ast2700_i2c_test(16) diff --git a/tests/functional/aarch64/test_aspeed_ast2700a2.py b/tests/functional/aarch64/test_aspeed_ast2700a2.py index e527518a5537..cc62a915b538 100755 --- a/tests/functional/aarch64/test_aspeed_ast2700a2.py +++ b/tests/functional/aarch64/test_aspeed_ast2700a2.py @@ -78,13 +78,13 @@ def verify_openbmc_boot_and_login(self, name, enable_pcie=True): exec_command_and_wait_for_pattern(self, 'root', 'Password:') exec_command_and_wait_for_pattern(self, '0penBmc', f'root@{name}:~#') - ASSET_SDK_V1100_AST2700A2 = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.00/ast2700-default-obmc.tar.gz', - 'e2b8f043fe8063dd3b6ded93422e38bd41914dc9c3202199507652df024de4dc') + ASSET_SDK_V1101_AST2700A2 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-default-image.tar.gz', + 'ce89dcd995cf284d41a6a4bd17a1b97d59939f0277bfe54fdaaf30e741ce7487') - ASSET_SDK_V1100_AST2700A2_DCSCM = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.00/ast2700-dcscm-obmc.tar.gz', - '0e93f7976139da71fab9df7952a58bdd80650e23e7abf5853b0eb6695deb02d0') + ASSET_SDK_V1101_AST2700A2_DCSCM = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-dcscm-image.tar.gz', + 'b92ece9ca733dfd7a20193a12582f743b77f1898116b6d6f1abe57ac8db01c56') def do_ast2700_i2c_test(self, bus_id): bus_str = str(bus_id) @@ -150,38 +150,38 @@ def start_ast2700_test_vbootrom(self, name, bus_id): self.do_test_aarch64_aspeed_sdk_start( self.scratch_file(name, 'image-bmc'), bus_id) - def test_aarch64_ast2700a2_evb_sdk_v11_00(self): + def test_aarch64_ast2700a2_evb_sdk_v11_01(self): self.set_machine('ast2700a2-evb') self.require_netdev('user') - self.archive_extract(self.ASSET_SDK_V1100_AST2700A2) + self.archive_extract(self.ASSET_SDK_V1101_AST2700A2) self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.2') self.vm.add_args('-netdev', 'user,id=net1') - self.start_ast2700_test('ast2700-default', 1) + self.start_ast2700_test('ast2700-default-image', 1) self.verify_openbmc_boot_and_login('ast2700-default') self.do_ast2700_i2c_test(1) self.do_ast2700_pcie_test() - def test_aarch64_ast2700a2_evb_sdk_vbootrom_v11_00(self): + def test_aarch64_ast2700a2_evb_sdk_vbootrom_v11_01(self): self.set_machine('ast2700a2-evb') self.require_netdev('user') - self.archive_extract(self.ASSET_SDK_V1100_AST2700A2) + self.archive_extract(self.ASSET_SDK_V1101_AST2700A2) self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.2') self.vm.add_args('-netdev', 'user,id=net1') - self.start_ast2700_test_vbootrom('ast2700-default', 1) + self.start_ast2700_test_vbootrom('ast2700-default-image', 1) self.verify_vbootrom_firmware_flow() self.verify_openbmc_boot_start() - def test_aarch64_ast2700a2_evb_ioexp_v11_00(self): + def test_aarch64_ast2700a2_evb_ioexp_v11_01(self): self.set_machine('ast2700a2-evb') self.require_netdev('user') - self.archive_extract(self.ASSET_SDK_V1100_AST2700A2_DCSCM) + self.archive_extract(self.ASSET_SDK_V1101_AST2700A2_DCSCM) self.vm.set_machine('ast2700a2-evb,fmc-model=w25q512jv') self.vm.add_args('-device', 'tmp105,bus=ioexp0.0,address=0x4d,id=tmp-test-16') - self.start_ast2700_test('ast2700-dcscm', 8) + self.start_ast2700_test('ast2700-dcscm-image', 8) self.verify_openbmc_boot_and_login('ast2700-dcscm', False) self.do_ast2700_i2c_test(8) self.do_ast2700_i2c_test(16) diff --git a/tests/functional/aarch64/test_aspeed_ast2700fc.py b/tests/functional/aarch64/test_aspeed_ast2700fc.py index 51ee8bc78791..f68f40a1bfaf 100755 --- a/tests/functional/aarch64/test_aspeed_ast2700fc.py +++ b/tests/functional/aarch64/test_aspeed_ast2700fc.py @@ -61,9 +61,9 @@ def load_ast2700fc_coprocessor(self, name): self.vm.add_args('-device', f'loader,file={file},cpu-num={cpu_num}') - ASSET_SDK_V1100_AST2700 = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.00/ast2700-default-obmc.tar.gz', - 'e2b8f043fe8063dd3b6ded93422e38bd41914dc9c3202199507652df024de4dc') + ASSET_SDK_V1101_AST2700 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-default-image.tar.gz', + 'ce89dcd995cf284d41a6a4bd17a1b97d59939f0277bfe54fdaaf30e741ce7487') def do_ast2700_i2c_test(self): exec_command_and_wait_for_pattern(self, @@ -148,23 +148,23 @@ def start_ast2700fc_test_vbootrom(self, name): self.do_test_aarch64_aspeed_sdk_start( self.scratch_file(name, 'image-bmc')) - def test_aarch64_ast2700fc_sdk_v11_00(self): + def test_aarch64_ast2700fc_sdk_v11_01(self): self.set_machine('ast2700fc') self.require_netdev('user') - self.archive_extract(self.ASSET_SDK_V1100_AST2700) - self.start_ast2700fc_test('ast2700-default') + self.archive_extract(self.ASSET_SDK_V1101_AST2700) + self.start_ast2700fc_test('ast2700-default-image') self.verify_openbmc_boot_and_login('ast2700-default') self.do_ast2700_i2c_test() self.do_ast2700_pcie_test() self.do_ast2700fc_ssp_test() self.do_ast2700fc_tsp_test() - def test_aarch64_ast2700fc_sdk_vbootrom_v11_00(self): + def test_aarch64_ast2700fc_sdk_vbootrom_v11_01(self): self.set_machine('ast2700fc') - self.archive_extract(self.ASSET_SDK_V1100_AST2700) - self.start_ast2700fc_test_vbootrom('ast2700-default') + self.archive_extract(self.ASSET_SDK_V1101_AST2700) + self.start_ast2700fc_test_vbootrom('ast2700-default-image') self.verify_openbmc_boot_and_login('ast2700-default') self.do_ast2700fc_ssp_test() self.do_ast2700fc_tsp_test() diff --git a/tests/functional/aarch64/test_virt_vbsa.py b/tests/functional/aarch64/test_virt_vbsa.py new file mode 100755 index 000000000000..1dd4cecde1e1 --- /dev/null +++ b/tests/functional/aarch64/test_virt_vbsa.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# +# Functional test that runs the Arm VBSA conformance tests. +# +# Copyright (c) 2026 Linaro Ltd. +# +# Author: +# Alex Bennée +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import shutil +from subprocess import check_call, DEVNULL + +from qemu_test import QemuSystemTest, Asset +from qemu_test import get_qemu_img, skipIfMissingCommands +from qemu_test import wait_for_console_pattern +from qemu_test import exec_command_and_wait_for_pattern as ec_and_wait + + +@skipIfMissingCommands("mformat", "mcopy", "mmd") +class Aarch64VirtMachine(QemuSystemTest): + KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 ' + timeout = 360 + + def wait_for_console_pattern(self, success_message, vm=None): + wait_for_console_pattern(self, success_message, + failure_message='FAILED', + vm=vm) + + ASSET_VBSA_EFI = Asset( + 'https://github.com/ARM-software/sysarch-acs/raw/refs/heads/main' + '/prebuilt_images/VBSA/v25.12_VBSA_0.7.0/Vbsa.efi', + '80f37d2fb86d152d95dec4d05ff099c9e47ee8a89314268e08056b0e1359e1fa') + + ASSET_BSA_SHELL = Asset( + 'https://github.com/ARM-software/sysarch-acs/raw/refs/heads/main' + '/prebuilt_images/VBSA/v25.12_VBSA_0.7.0/Shell.efi', + 'e526604f0d329b481c6a1f62f7a0db8ea24ce8178b2c6abda8e247425f38775c') + + def test_aarch64_vbsa_uefi_tests(self): + """ + Launch the UEFI based VBSA test from an EFI file-system + """ + + self.vm.set_console() + + # virt machine wi + self.set_machine('virt') + self.vm.add_args('-M', 'virt,gic-version=max,virtualization=on') + self.vm.add_args('-cpu', 'max', '-m', '1024') + + # We will use the QEMU firmware blobs to boot + code_path = self.build_file('pc-bios', 'edk2-aarch64-code.fd') + vars_source = self.build_file('pc-bios', 'edk2-arm-vars.fd') + vars_path = self.scratch_file('vars.fd') + shutil.copy(vars_source, vars_path) + + self.vm.add_args('-drive', + f'if=pflash,format=raw,readonly=on,file={code_path}') + self.vm.add_args('-drive', f'if=pflash,format=raw,file={vars_path}') + + # Build an EFI FAT32 file-system for the UEFI tests + vbsa_efi = self.ASSET_VBSA_EFI.fetch() + bsa_shell = self.ASSET_BSA_SHELL.fetch() + + img_path = self.scratch_file('vbsa.img') + qemu_img = get_qemu_img(self) + check_call([qemu_img, 'create', '-f', 'raw', img_path, '64M'], + stdout=DEVNULL, stderr=DEVNULL) + + check_call(['mformat', '-i', img_path, '-v', 'VBSA', '::'], + stdout=DEVNULL, stderr=DEVNULL) + + check_call(['mmd', '-i', img_path, '::/EFI'], + stdout=DEVNULL, stderr=DEVNULL) + + check_call(['mmd', '-i', img_path, '::/EFI/BOOT'], + stdout=DEVNULL, stderr=DEVNULL) + + check_call(['mcopy', '-i', img_path, bsa_shell, + '::/EFI/BOOT/BOOTAA64.EFI'], + stdout=DEVNULL, stderr=DEVNULL) + + check_call(['mcopy', '-i', img_path, vbsa_efi, '::/Vbsa.efi'], + stdout=DEVNULL, stderr=DEVNULL) + + self.vm.add_args('-drive', + f'file={img_path},format=raw,if=none,id=drive0') + self.vm.add_args('-device', 'virtio-blk-pci,drive=drive0') + + self.vm.launch() + + # wait for EFI prompt + self.wait_for_console_pattern('Shell>') + + # Start the VBSA tests + ec_and_wait(self, "FS0:Vbsa.efi", 'VBSA Architecture Compliance Suite') + + # could we parse the summary somehow? + + self.wait_for_console_pattern('VBSA tests complete. Reset the system.') + + +if __name__ == '__main__': + QemuSystemTest.main() diff --git a/tests/functional/arm/test_aspeed_ast1030.py b/tests/functional/arm/test_aspeed_ast1030.py index 0defa7c38e25..03fee55b5f8e 100755 --- a/tests/functional/arm/test_aspeed_ast1030.py +++ b/tests/functional/arm/test_aspeed_ast1030.py @@ -12,17 +12,17 @@ class AST1030Machine(AspeedTest): - ASSET_ZEPHYR_3_05 = Asset( + ASSET_ZEPHYR_3_06 = Asset( ('https://github.com/AspeedTech-BMC' - '/zephyr/releases/download/v00.03.05/ast1030-evb-demo.zip'), - '057528d343490b1fbb5a721e91084b4f04fec60dc114bd65e724554f9c217f4b') + '/zephyr/releases/download/v00.03.06/ast1030-evb-demo.zip'), + '056f37fcd9f165308cedca3a08f2bed37ed40c0a1402c4fa515613b80a369f38') - def test_arm_ast1030_zephyros_3_05(self): + def test_arm_ast1030_zephyros_3_06(self): self.set_machine('ast1030-evb') kernel_name = "ast1030-evb-demo/zephyr.elf" kernel_file = self.archive_extract( - self.ASSET_ZEPHYR_3_05, member=kernel_name) + self.ASSET_ZEPHYR_3_06, member=kernel_name) self.vm.set_console() self.vm.add_args('-kernel', kernel_file, '-nographic') @@ -72,7 +72,7 @@ def test_arm_ast1030_otp_blockdev_device(self): self.vm.set_machine("ast1030-evb") kernel_name = "ast1030-evb-demo/zephyr.elf" - kernel_file = self.archive_extract(self.ASSET_ZEPHYR_3_05, + kernel_file = self.archive_extract(self.ASSET_ZEPHYR_3_06, member=kernel_name) otp_img = self.generate_otpmem_image() diff --git a/tests/functional/arm/test_aspeed_ast1060.py b/tests/functional/arm/test_aspeed_ast1060.py index eccb4252d86a..833cfb8272e7 100755 --- a/tests/functional/arm/test_aspeed_ast1060.py +++ b/tests/functional/arm/test_aspeed_ast1060.py @@ -11,18 +11,18 @@ class AST1060Machine(AspeedTest): - ASSET_ASPEED_AST1060_PROT_3_04 = Asset( + ASSET_ASPEED_AST1060_PROT_3_05 = Asset( ('https://github.com/AspeedTech-BMC' - '/aspeed-zephyr-project/releases/download/v03.04' - '/ast1060_prot_v03.04.tgz'), - 'c0319df55f5b7a547efefc5a6ba374b881223d5fe1a776bfdd36f97fd1f31d50') + '/aspeed-zephyr-project/releases/download/v03.05' + '/ast1060_prot_v03.05.tgz'), + '63b36d7420290726ca80477de254474b7cb79539a42819bb1fe2665d598dadb5') - def test_arm_ast1060_prot_3_04(self): + def test_arm_ast1060_prot_3_05(self): self.set_machine('ast1060-evb') kernel_name = "ast1060_prot/zephyr.bin" kernel_file = self.archive_extract( - self.ASSET_ASPEED_AST1060_PROT_3_04, member=kernel_name) + self.ASSET_ASPEED_AST1060_PROT_3_05, member=kernel_name) self.vm.set_console() self.vm.add_args('-kernel', kernel_file, '-nographic') @@ -35,7 +35,7 @@ def test_arm_ast1060_otp_blockdev_device(self): self.vm.set_machine("ast1060-evb") kernel_name = "ast1060_prot/zephyr.bin" - kernel_file = self.archive_extract(self.ASSET_ASPEED_AST1060_PROT_3_04, + kernel_file = self.archive_extract(self.ASSET_ASPEED_AST1060_PROT_3_05, member=kernel_name) otp_img = self.generate_otpmem_image() diff --git a/tests/functional/arm/test_aspeed_ast2500_sdk.py b/tests/functional/arm/test_aspeed_ast2500_sdk.py index 2c9211aeddc8..5ab36b99ab47 100755 --- a/tests/functional/arm/test_aspeed_ast2500_sdk.py +++ b/tests/functional/arm/test_aspeed_ast2500_sdk.py @@ -10,14 +10,14 @@ class AST2500Machine(AspeedTest): - ASSET_SDK_V1000_AST2500 = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v10.00/ast2500-default-obmc.tar.gz', - '7d71a3f71d5f4d9f3451f59a73bf9baf8fd9f6a24107eb504a3216151a8b2b5b') + ASSET_SDK_V1101_AST2500 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2500-default-obmc.tar.gz', + '3faa1188198da2216837be4b53861c483a58c3ad63784089720bf8421e157da1') def test_arm_ast2500_evb_sdk(self): self.set_machine('ast2500-evb') - self.archive_extract(self.ASSET_SDK_V1000_AST2500) + self.archive_extract(self.ASSET_SDK_V1101_AST2500) self.do_test_arm_aspeed_sdk_start( self.scratch_file("ast2500-default", "image-bmc")) diff --git a/tests/functional/arm/test_aspeed_ast2500_sdk_515.py b/tests/functional/arm/test_aspeed_ast2500_sdk_515.py index 464a4e43961d..2b257986f622 100755 --- a/tests/functional/arm/test_aspeed_ast2500_sdk_515.py +++ b/tests/functional/arm/test_aspeed_ast2500_sdk_515.py @@ -10,14 +10,14 @@ class AST2500Machine(AspeedTest): - ASSET_SDK_V1100_AST2500_515 = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.00/ast2500-default-515-obmc.tar.gz', - '5732255d4617d98b76bbbc116d331d6ac89906fa212969eb8213fdc4aea86451') + ASSET_SDK_V1101_AST2500_515 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2500-default-515-obmc.tar.gz', + 'b848ff620d2e9c83e2fb4736b4d1c39b82fdb041058cd42be42c3b177bf38eb9') def test_arm_ast2500_evb_sdk_515(self): self.set_machine('ast2500-evb') - self.archive_extract(self.ASSET_SDK_V1100_AST2500_515) + self.archive_extract(self.ASSET_SDK_V1101_AST2500_515) self.do_test_arm_aspeed_sdk_start( self.scratch_file("ast2500-default-515", "image-bmc")) diff --git a/tests/functional/arm/test_aspeed_ast2600_sdk.py b/tests/functional/arm/test_aspeed_ast2600_sdk.py index 971fa3390d21..d787e90d1024 100755 --- a/tests/functional/arm/test_aspeed_ast2600_sdk.py +++ b/tests/functional/arm/test_aspeed_ast2600_sdk.py @@ -14,9 +14,9 @@ class AST2600Machine(AspeedTest): - ASSET_SDK_V1100_AST2600 = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.00/ast2600-default-obmc.tar.gz', - '64d8926a7d01b649168be96c986603b5690f06391286c438a3a772c8c7039e93') + ASSET_SDK_V1101_AST2600 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2600-default-image.tar.gz', + '3c5b4d4ccf27b0d208a073f98426db54cd751b96143180cd15df1a83978f832c') def do_ast2600_pcie_test(self): exec_command_and_wait_for_pattern(self, @@ -31,11 +31,20 @@ def do_ast2600_pcie_test(self): 'ip addr show dev eth4', 'inet 10.0.2.15/24') + def do_ast2600_i3c_test(self): + exec_command_and_wait_for_pattern(self, + 'i3ctransfer -d /dev/bus/i3c/5-1234567890ab' + ' -w 0x12,0x34,0x56,0x78,0x90,0xab,0xcd,0xef', + 'Success on message 0') + exec_command_and_wait_for_pattern(self, + 'i3ctransfer -d /dev/bus/i3c/5-1234567890ab -r 8 | grep 0x | xargs', + '0x12 0x34 0x56 0x78 0x90 0xab 0xcd 0xef') + def test_arm_ast2600_evb_sdk(self): self.set_machine('ast2600-evb') self.require_netdev('user') - self.archive_extract(self.ASSET_SDK_V1100_AST2600) + self.archive_extract(self.ASSET_SDK_V1101_AST2600) self.vm.add_args('-device', 'tmp105,bus=aspeed.i2c.bus.5,address=0x4d,id=tmp-test') @@ -43,8 +52,10 @@ def test_arm_ast2600_evb_sdk(self): 'ds1338,bus=aspeed.i2c.bus.5,address=0x32') self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.0') self.vm.add_args('-netdev', 'user,id=net1') + self.vm.add_args('-device', + 'mock-i3c-target,bus=dw.i3c.5,pid=0xab9078563412') self.do_test_arm_aspeed_sdk_start( - self.scratch_file("ast2600-default", "image-bmc")) + self.scratch_file("ast2600-default-image", "image-bmc")) self.wait_for_console_pattern('ast2600-default login:') @@ -69,6 +80,7 @@ def test_arm_ast2600_evb_sdk(self): exec_command_and_wait_for_pattern(self, '/sbin/hwclock -f /dev/rtc1', year) self.do_ast2600_pcie_test() + self.do_ast2600_i3c_test() if __name__ == '__main__': diff --git a/tests/functional/arm/test_aspeed_ast2600_sdk_515.py b/tests/functional/arm/test_aspeed_ast2600_sdk_515.py index f48da301d567..ec043e7d6116 100755 --- a/tests/functional/arm/test_aspeed_ast2600_sdk_515.py +++ b/tests/functional/arm/test_aspeed_ast2600_sdk_515.py @@ -10,17 +10,17 @@ class AST2600Machine(AspeedTest): - ASSET_SDK_V1100_AST2600_515 = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.00/ast2600-default-515-obmc.tar.gz', - 'ece1a934095378929780f03e7d092e562f4b33b2841b80ad7c3d12a85744c0f6') + ASSET_SDK_V1101_AST2600_515 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2600-default-515-image.tar.gz', + 'f3ccf1c08db71cf891637fc73131b80b2c0c0e005c06d5dcae0cf74fc458b43c') def test_arm_ast2600_evb_sdk_515(self): self.set_machine('ast2600-evb') - self.archive_extract(self.ASSET_SDK_V1100_AST2600_515) + self.archive_extract(self.ASSET_SDK_V1101_AST2600_515) self.do_test_arm_aspeed_sdk_start( - self.scratch_file("ast2600-default-515", "image-bmc")) + self.scratch_file("ast2600-default-515-image", "image-bmc")) self.wait_for_console_pattern('ast2600-default-515 login:') diff --git a/tests/functional/arm/test_aspeed_ast2600_sdk_otp.py b/tests/functional/arm/test_aspeed_ast2600_sdk_otp.py index 31a1c3bed670..f24dea1e8f03 100755 --- a/tests/functional/arm/test_aspeed_ast2600_sdk_otp.py +++ b/tests/functional/arm/test_aspeed_ast2600_sdk_otp.py @@ -12,15 +12,15 @@ class AST2600Machine(AspeedTest): - ASSET_SDK_V1100_AST2600 = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.00/ast2600-default-obmc.tar.gz', - '64d8926a7d01b649168be96c986603b5690f06391286c438a3a772c8c7039e93') + ASSET_SDK_V1101_AST2600 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2600-default-image.tar.gz', + '3c5b4d4ccf27b0d208a073f98426db54cd751b96143180cd15df1a83978f832c') def test_arm_ast2600_otp_blockdev_device(self): self.vm.set_machine("ast2600-evb") self.require_netdev('user') - image_path = self.archive_extract(self.ASSET_SDK_V1100_AST2600) + image_path = self.archive_extract(self.ASSET_SDK_V1101_AST2600) otp_img = self.generate_otpmem_image() self.vm.set_console() @@ -29,7 +29,7 @@ def test_arm_ast2600_otp_blockdev_device(self): "-global", "aspeed-otp.drive=otp", ) self.vm.add_args('-drive', 'file=' + - self.scratch_file("ast2600-default", "image-bmc") + + self.scratch_file("ast2600-default-image", "image-bmc") + ',if=mtd,format=raw', '-net', 'nic', '-net', 'user', '-snapshot') self.vm.launch() diff --git a/tests/functional/generic/test_vnc.py b/tests/functional/generic/test_vnc.py index f1dd1597cf18..097f858ca166 100755 --- a/tests/functional/generic/test_vnc.py +++ b/tests/functional/generic/test_vnc.py @@ -48,7 +48,7 @@ def test_no_vnc_change_password(self): self.assertEqual(set_password_response['error']['class'], 'GenericError') self.assertEqual(set_password_response['error']['desc'], - 'Could not set password') + 'No VNC display is present'); def launch_guarded(self): try: @@ -73,7 +73,7 @@ def test_change_password_requires_a_password(self): self.assertEqual(set_password_response['error']['class'], 'GenericError') self.assertEqual(set_password_response['error']['desc'], - 'Could not set password') + 'VNC password authentication is disabled') def test_change_password(self): self.set_machine('none') diff --git a/tests/functional/hppa/test_seabios.py b/tests/functional/hppa/test_seabios.py index 661b2464e132..bdb9d534efe9 100755 --- a/tests/functional/hppa/test_seabios.py +++ b/tests/functional/hppa/test_seabios.py @@ -12,7 +12,7 @@ class HppaSeabios(QemuSystemTest): timeout = 5 - MACH_BITS = {'B160L': 32, 'C3700': 64} + MACH_BITS = {'B160L': 32, 'A400': 64} def boot_seabios(self): mach = self.machine @@ -28,7 +28,7 @@ def test_hppa_32(self): self.boot_seabios() def test_hppa_64(self): - self.set_machine('C3700') + self.set_machine('A400') self.boot_seabios() if __name__ == '__main__': diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 725630d30826..9bec5a07516d 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -4,8 +4,9 @@ # (e.g. tests that fetch assets from the internet) should be put into # the 'thorough' category instead. -# Most tests run too slow with TCI enabled, so skip the functional tests there -if get_option('tcg_interpreter') +# Most tests run too slow with TCI enabled, and they haven't been adapted +# to Windows yet, so skip the functional tests in these environments +if get_option('tcg_interpreter') or host_os == 'windows' subdir_done() endif @@ -18,7 +19,6 @@ subdir('i386') subdir('loongarch64') subdir('m68k') subdir('microblaze') -subdir('microblazeel') subdir('mips') subdir('mipsel') subdir('mips64') @@ -57,6 +57,8 @@ foreach speed : ['quick', 'thorough'] continue endif + test_deps = [roms, keymap_targets] + if speed == 'quick' suites = ['func-quick', 'func-' + target_base] target_tests = get_variable('tests_' + target_base + '_' + sysmode + '_quick', []) \ @@ -64,9 +66,9 @@ foreach speed : ['quick', 'thorough'] else suites = ['func-' + speed, 'func-' + target_base + '-' + speed, speed] target_tests = get_variable('tests_' + target_base + '_' + sysmode + '_' + speed, []) + test_deps += [pyvenv_functests_group] endif - test_deps = [roms, keymap_targets] test_env = environment() if have_tools test_env.set('QEMU_TEST_QEMU_IMG', meson.global_build_root() / 'qemu-img') @@ -99,6 +101,7 @@ foreach speed : ['quick', 'thorough'] precache = custom_target('func-precache-' + testname, output: teststamp, command: [python, testpath], + depends: pyvenv_functests_group, depend_files: files(testpath), build_by_default: false, env: test_precache_env) diff --git a/tests/functional/microblaze/test_s3adsp1800.py b/tests/functional/microblaze/test_s3adsp1800.py index f093b162c0a5..be78c208e3b1 100755 --- a/tests/functional/microblaze/test_s3adsp1800.py +++ b/tests/functional/microblaze/test_s3adsp1800.py @@ -14,8 +14,6 @@ class MicroblazeMachine(QemuSystemTest): - timeout = 90 - ASSET_IMAGE_BE = Asset( ('https://qemu-advcal.gitlab.io/qac-best-of-multiarch/download/' 'day17.tar.xz'), @@ -25,14 +23,12 @@ class MicroblazeMachine(QemuSystemTest): ('http://www.qemu-advent-calendar.org/2023/download/day13.tar.gz'), 'b9b3d43c5dd79db88ada495cc6e0d1f591153fe41355e925d791fbf44de50c22') - def do_ballerina_be_test(self, force_endianness=False): + def test_microblaze_s3adsp1800_big_endian(self): self.set_machine('petalogix-s3adsp1800') self.archive_extract(self.ASSET_IMAGE_BE) self.vm.set_console() self.vm.add_args('-kernel', self.scratch_file('day17', 'ballerina.bin')) - if force_endianness: - self.vm.add_args('-M', 'endianness=big') self.vm.launch() wait_for_console_pattern(self, 'This architecture does not have ' 'kernel memory protection') @@ -41,14 +37,13 @@ def do_ballerina_be_test(self, force_endianness=False): # message, that's why we don't test for a later string here. This # needs some investigation by a microblaze wizard one day... - def do_xmaton_le_test(self, force_endianness=False): + def test_microblaze_s3adsp1800_little_endian(self): self.require_netdev('user') self.set_machine('petalogix-s3adsp1800') self.archive_extract(self.ASSET_IMAGE_LE) self.vm.set_console() self.vm.add_args('-kernel', self.scratch_file('day13', 'xmaton.bin')) - if force_endianness: - self.vm.add_args('-M', 'endianness=little') + self.vm.add_args('-M', 'endianness=little') tftproot = self.scratch_file('day13') self.vm.add_args('-nic', f'user,tftp={tftproot}') self.vm.launch() @@ -60,17 +55,5 @@ def do_xmaton_le_test(self, force_endianness=False): '821cd3cab8efd16ad6ee5acc3642a8ea') -class MicroblazeBigEndianMachine(MicroblazeMachine): - - ASSET_IMAGE_BE = MicroblazeMachine.ASSET_IMAGE_BE - ASSET_IMAGE_LE = MicroblazeMachine.ASSET_IMAGE_LE - - def test_microblaze_s3adsp1800_legacy_be(self): - self.do_ballerina_be_test() - - def test_microblaze_s3adsp1800_legacy_le(self): - self.do_xmaton_le_test(force_endianness=True) - - if __name__ == '__main__': QemuSystemTest.main() diff --git a/tests/functional/microblazeel/meson.build b/tests/functional/microblazeel/meson.build deleted file mode 100644 index 27619dc5a9a6..000000000000 --- a/tests/functional/microblazeel/meson.build +++ /dev/null @@ -1,5 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later - -tests_microblazeel_system_thorough = [ - 's3adsp1800' -] diff --git a/tests/functional/microblazeel/test_s3adsp1800.py b/tests/functional/microblazeel/test_s3adsp1800.py deleted file mode 100755 index 75ce8856ed15..000000000000 --- a/tests/functional/microblazeel/test_s3adsp1800.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python3 -# -# Functional test that boots a microblaze Linux kernel and checks the console -# -# Copyright (c) 2018, 2021 Red Hat, Inc. -# -# This work is licensed under the terms of the GNU GPL, version 2 or -# later. See the COPYING file in the top-level directory. - -from microblaze.test_s3adsp1800 import MicroblazeMachine - - -class MicroblazeLittleEndianMachine(MicroblazeMachine): - - ASSET_IMAGE_LE = MicroblazeMachine.ASSET_IMAGE_LE - ASSET_IMAGE_BE = MicroblazeMachine.ASSET_IMAGE_BE - - def test_microblaze_s3adsp1800_legacy_le(self): - self.do_xmaton_le_test() - - def test_microblaze_s3adsp1800_legacy_be(self): - self.do_ballerina_be_test(force_endianness=True) - - -if __name__ == '__main__': - MicroblazeMachine.main() diff --git a/tests/functional/migration.py b/tests/functional/migration.py index 2bfb1f779012..e995328e833f 100644 --- a/tests/functional/migration.py +++ b/tests/functional/migration.py @@ -11,7 +11,6 @@ # This work is licensed under the terms of the GNU GPL, version 2 or # later. See the COPYING file in the top-level directory. -import tempfile import time from qemu_test import QemuSystemTest, which @@ -41,17 +40,24 @@ def assert_migration(self, src_vm, dst_vm): self.assertEqual(dst_vm.cmd('query-status')['status'], 'running') self.assertEqual(src_vm.cmd('query-status')['status'],'postmigrate') - def do_migrate(self, dest_uri, src_uri=None): - dest_vm = self.get_vm('-incoming', dest_uri, name="dest-qemu") - dest_vm.add_args('-nodefaults') - dest_vm.launch() + def migrate_vms(self, dst_uri, src_uri, dst_vm, src_vm): + dst_vm.qmp('migrate-incoming', uri=dst_uri) + src_vm.qmp('migrate', uri=src_uri) + self.assert_migration(src_vm, dst_vm) + + def migrate(self, dst_uri, src_uri=None): + dst_vm = self.get_vm('-incoming', 'defer', name="dst-qemu") + dst_vm.add_args('-nodefaults') + dst_vm.launch() + + src_vm = self.get_vm(name="src-qemu") + src_vm.add_args('-nodefaults') + src_vm.launch() + if src_uri is None: - src_uri = dest_uri - source_vm = self.get_vm(name="source-qemu") - source_vm.add_args('-nodefaults') - source_vm.launch() - source_vm.qmp('migrate', uri=src_uri) - self.assert_migration(source_vm, dest_vm) + src_uri = dst_uri + + self.migrate_vms(dst_uri, src_uri, dst_vm, src_vm) def _get_free_port(self, ports): port = ports.find_free_port() @@ -59,21 +65,25 @@ def _get_free_port(self, ports): self.skipTest('Failed to find a free port') return port + def migration_with_tcp_localhost_vms(self, dst_vm, src_vm): + with Ports() as ports: + uri = 'tcp:localhost:%u' % self._get_free_port(ports) + self.migrate_vms(uri, uri, dst_vm, src_vm) + def migration_with_tcp_localhost(self): with Ports() as ports: - dest_uri = 'tcp:localhost:%u' % self._get_free_port(ports) - self.do_migrate(dest_uri) + dst_uri = 'tcp:localhost:%u' % self._get_free_port(ports) + self.migrate(dst_uri) def migration_with_unix(self): - with tempfile.TemporaryDirectory(prefix='socket_') as socket_path: - dest_uri = 'unix:%s/qemu-test.sock' % socket_path - self.do_migrate(dest_uri) + dst_uri = 'unix:%s/migration.sock' % self.socket_dir().name + self.migrate(dst_uri) def migration_with_exec(self): if not which('ncat'): self.skipTest('ncat is not available') with Ports() as ports: free_port = self._get_free_port(ports) - dest_uri = 'exec:ncat -l localhost %u' % free_port + dst_uri = 'exec:ncat -l localhost %u' % free_port src_uri = 'exec:ncat localhost %u' % free_port - self.do_migrate(dest_uri, src_uri) + self.migrate(dst_uri, src_uri) diff --git a/tests/functional/ppc/test_40p.py b/tests/functional/ppc/test_40p.py index 614972a7eb38..ed272147f6e2 100755 --- a/tests/functional/ppc/test_40p.py +++ b/tests/functional/ppc/test_40p.py @@ -27,7 +27,7 @@ class IbmPrep40pMachine(QemuSystemTest): 'f86236e9d01b3f0dd0f5d3b8d5bbd40c68e78b4db560a108358f5ad58e636619') ASSET_NETBSD71 = Asset( ('https://archive.netbsd.org/pub/NetBSD-archive/' - 'NetBSD-7.1.2/iso/NetBSD-7.1.2-prep.iso'), + 'NetBSD-7.1.2/iso/NetBSD-7.1.2-prep.iso?key=NetBSD'), 'cc7cb290b06aaa839362deb7bd9f417ac5015557db24088508330f76c3f825ec') # 12H0455 PPS Firmware Licensed Materials diff --git a/tests/functional/ppc/test_ppe42.py b/tests/functional/ppc/test_ppe42.py old mode 100644 new mode 100755 diff --git a/tests/functional/ppc64/test_migration.py b/tests/functional/ppc64/test_migration.py index a3b819680bce..7d49ee175bb9 100755 --- a/tests/functional/ppc64/test_migration.py +++ b/tests/functional/ppc64/test_migration.py @@ -22,17 +22,6 @@ def test_migration_with_exec(self): self.set_machine('mac99') self.migration_with_exec() - def do_migrate_ppc64_linux(self, source_vm, dest_vm): - with Ports() as ports: - port = ports.find_free_port() - if port is None: - self.skipTest('Failed to find a free port') - uri = 'tcp:localhost:%u' % port - - dest_vm.qmp('migrate-incoming', uri=uri) - source_vm.qmp('migrate', uri=uri) - self.assert_migration(source_vm, dest_vm) - if __name__ == '__main__': MigrationTest.main() diff --git a/tests/functional/ppc64/test_pseries.py b/tests/functional/ppc64/test_pseries.py index b45763c30508..cb6d25dc816f 100755 --- a/tests/functional/ppc64/test_pseries.py +++ b/tests/functional/ppc64/test_pseries.py @@ -7,11 +7,11 @@ # This work is licensed under the terms of the GNU GPL, version 2 or # later. See the COPYING file in the top-level directory. -from qemu_test import QemuSystemTest, Asset +from qemu_test import Asset from qemu_test import wait_for_console_pattern -from test_migration import PpcMigrationTest +from migration import MigrationTest -class PseriesMachine(QemuSystemTest): +class PseriesMachine(MigrationTest): timeout = 90 KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 console=hvc0 ' @@ -116,11 +116,11 @@ def test_ppc64_linux_migration(self): wait_for_console_pattern(self, console_pattern, self.panic_message, vm=source_vm) - PpcMigrationTest().do_migrate_ppc64_linux(source_vm, dest_vm); + self.migration_with_tcp_localhost_vms(dest_vm, source_vm) # ensure the boot proceeds after migration wait_for_console_pattern(self, self.good_message, self.panic_message, vm=dest_vm) if __name__ == '__main__': - QemuSystemTest.main() + MigrationTest.main() diff --git a/tests/functional/x86_64/meson.build b/tests/functional/x86_64/meson.build index f78eec5e6cf4..1ed10ad6c295 100644 --- a/tests/functional/x86_64/meson.build +++ b/tests/functional/x86_64/meson.build @@ -14,13 +14,20 @@ tests_x86_64_system_quick = [ 'cpu_model_versions', 'cpu_queries', 'mem_addr_space', - 'memlock', 'migration', 'pc_cpu_hotplug_props', 'virtio_version', 'vmstate', ] +# The address-sanitizer makes mlock() a no-op because it +# interacts badly with the sanitizer; this means the memlock +# test (which checks via /proc for whether pages were locked) +# will always fail on a sanitizer build, so don't run it. +if not get_option('asan') + tests_x86_64_system_quick += [ 'memlock' ] +endif + tests_x86_64_system_thorough = [ 'acpi_bits', 'hotplug_blk', @@ -34,6 +41,8 @@ tests_x86_64_system_thorough = [ 'reverse_debug', 'tuxrun', 'vfio_user_client', + 'vhost_user_bridge', 'virtio_balloon', 'virtio_gpu', + 'rebuild_vmfd', ] diff --git a/tests/functional/x86_64/test_rebuild_vmfd.py b/tests/functional/x86_64/test_rebuild_vmfd.py new file mode 100755 index 000000000000..5a8e5fd89b49 --- /dev/null +++ b/tests/functional/x86_64/test_rebuild_vmfd.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +# +# Functional tests exercising guest KVM file descriptor change on reset. +# +# Copyright © 2026 Red Hat, Inc. +# +# Author: +# Ani Sinha +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +from qemu.machine import machine + +from qemu_test import QemuSystemTest, Asset, exec_command_and_wait_for_pattern +from qemu_test import wait_for_console_pattern + +class KVMGuest(QemuSystemTest): + + # ASSET UKI was generated using + # https://gitlab.com/kraxel/edk2-tests/-/blob/unittest/tools/make-supermin.sh + ASSET_UKI = Asset('https://gitlab.com/anisinha/misc-artifacts/' + '-/raw/main/uki.x86-64.efi?ref_type=heads', + 'e0f806bd1fa24111312e1fe849d2ee69808d4343930a5' + 'dc8c1688da17c65f576') + # ASSET_OVMF comes from /usr/share/edk2/ovmf/OVMF.stateless.fd of a + # fedora core 43 distribution which in turn comes from the + # edk2-ovmf-20251119-3.fc43.noarch rpm of that distribution. + ASSET_OVMF = Asset('https://gitlab.com/anisinha/misc-artifacts/' + '-/raw/main/OVMF.stateless.fd?ref_type=heads', + '58a4275aafa8774bd6b1540adceae4ea434b8db75b476' + '11839ff47be88cfcf22') + + def common_vm_setup(self, kvm_args=None, cpu_args=None): + self.set_machine('q35') + self.require_accelerator("kvm") + + self.vm.set_console() + if kvm_args: + self.vm.add_args("-accel", "kvm,%s" %kvm_args) + else: + self.vm.add_args("-accel", "kvm") + self.vm.add_args("-smp", "2") + if cpu_args: + self.vm.add_args("-cpu", "host,%s" %cpu_args) + else: + self.vm.add_args("-cpu", "host") + self.vm.add_args("-m", "2G") + self.vm.add_args("-nographic", "-nodefaults") + + + self.uki_path = self.ASSET_UKI.fetch() + self.ovmf_path = self.ASSET_OVMF.fetch() + + self.vm.add_args('-kernel', self.uki_path) + self.vm.add_args("-bios", self.ovmf_path) + # enable KVM VMFD change on reset for a non-coco VM + self.vm.add_args("-machine", "q35,x-change-vmfd-on-reset=on") + + # enable tracing of basic vmfd change function + self.vm.add_args("--trace", "kvm_reset_vmfd") + + def launch_vm(self): + try: + self.vm.launch() + except machine.VMLaunchFailure as e: + if "Xen HVM guest support not present" in e.output: + self.skipTest("KVM Xen support is not present " + "(need v5.12+ kernel with CONFIG_KVM_XEN)") + elif "Property 'kvm-accel.xen-version' not found" in e.output: + self.skipTest("QEMU not built with CONFIG_XEN_EMU support") + else: + raise e + + self.log.info('VM launched') + console_pattern = 'bash-5.1#' + wait_for_console_pattern(self, console_pattern) + self.log.info('VM ready with a bash prompt') + + def vm_console_reset(self): + exec_command_and_wait_for_pattern(self, '/usr/sbin/reboot -f', + 'reboot: machine restart') + console_pattern = '# --- Hello world ---' + wait_for_console_pattern(self, console_pattern) + self.vm.shutdown() + + def vm_qmp_reset(self): + self.vm.qmp('system_reset') + console_pattern = '# --- Hello world ---' + wait_for_console_pattern(self, console_pattern) + self.vm.shutdown() + + def check_logs(self): + self.assertRegex(self.vm.get_log(), + r'kvm_reset_vmfd') + self.assertRegex(self.vm.get_log(), + r'virtual machine state has been rebuilt') + + def test_reset_console(self): + self.common_vm_setup() + self.launch_vm() + self.vm_console_reset() + self.check_logs() + + def test_reset_qmp(self): + self.common_vm_setup() + self.launch_vm() + self.vm_qmp_reset() + self.check_logs() + + def test_reset_kvmpit(self): + self.common_vm_setup() + self.vm.add_args("--trace", "kvmpit_post_vmfd_change") + self.launch_vm() + self.vm_console_reset() + self.assertRegex(self.vm.get_log(), + r'kvmpit_post_vmfd_change') + + def test_reset_xen_emulation(self): + self.common_vm_setup("xen-version=0x4000a,kernel-irqchip=split") + self.launch_vm() + self.vm_console_reset() + self.check_logs() + + def test_reset_hyperv_vmbus(self): + self.common_vm_setup(None, "hv-syndbg,hv-relaxed,hv_time,hv-synic," + "hv-vpindex,hv-runtime,hv-stimer") + self.vm.add_args("-device", "vmbus-bridge,irq=15") + self.vm.add_args("-trace", "vmbus_handle_vmfd_change") + self.launch_vm() + self.vm_console_reset() + self.assertRegex(self.vm.get_log(), + r'vmbus_handle_vmfd_change') + +if __name__ == '__main__': + QemuSystemTest.main() diff --git a/tests/functional/x86_64/test_vhost_user_bridge.py b/tests/functional/x86_64/test_vhost_user_bridge.py new file mode 100755 index 000000000000..c36c62542053 --- /dev/null +++ b/tests/functional/x86_64/test_vhost_user_bridge.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2025 Software Freedom Conservancy, Inc. +# +# Author: Yodel Eldar +# +# SPDX-License-Identifier: GPL-2.0-or-later +""" +Test vhost-user-bridge (vubr) functionality: + + 1) Run vhost-user-bridge on the host. + 2) Launch a guest VM: + a) Instantiate a unix domain socket to the vubr-created path + b) Instantiate a vhost-user backend on top of that socket + c) Map a virtio-net-pci device to the vhost-user backend + d) Instantiate a UDP socket backend + e) Instantiate a user-mode net backend + i) Forward an ephemeral port to port 8080 in-guest with hostfwd= + ii) Expose a generated scratch file to the guest with tftp= + f) Hub the UDP and user-mode backends. + 3) Invoke tftp in the guest to download exported scratch file from the host. + 4) Serve a file to the host via http server in the guest. +""" + +import os +import shutil +import subprocess +from qemu_test import Asset, LinuxKernelTest, which +from qemu_test import exec_command_and_wait_for_pattern +from qemu_test import is_readable_executable_file +from qemu_test import wait_for_console_pattern +from qemu_test.ports import Ports + +class VhostUserBridge(LinuxKernelTest): + + ASSET_KERNEL_INITRAMFS = Asset( + "https://github.com/yodel/vhost-user-bridge-test/raw/refs/heads/main/bzImage", + "8860d7aa59434f483542cdf25b42eacae0d4d4aa7ec923af9589d1ad4703d42b") + + HOST_UUID = "ba4c2e39-627f-487d-ae3b-93cc5d783eb8" + HOST_UUID_HSUM = \ + "d2932e34bf6c17b33e7325140b691e27c191d9ac4dfa550f68c09506facb09b9" + + GUEST_UUID = "143d2b21-fdf0-4c5e-a9ef-f35ebbac8945" + GUEST_UUID_HSUM = \ + "14b64203f5cf2afe520f8be0fdfe630aafc1e85d1301f55a0d1681e68881f3a2" + + def configure_vm(self, ud_socket_path, lport, rport, hostfwd_port, tftpdir): + self.require_accelerator("kvm") + self.require_netdev("vhost-user") + self.require_netdev("socket") + self.require_netdev("hubport") + self.require_netdev("user") + self.require_device("virtio-net-pci") + self.set_machine("q35") + self.vm.add_args( + "-cpu", "host", + "-accel", "kvm", + "-append", "printk.time=0 console=ttyS0", + "-smp", "2", + "-m", "128M", + "-object", "memory-backend-memfd,id=mem0," + "size=128M,share=on,prealloc=on", + "-numa", "node,memdev=mem0", + "-chardev", f"socket,id=char0,path={ud_socket_path}", + "-netdev", "vhost-user,id=vhost0,chardev=char0,vhostforce=on", + "-device", "virtio-net-pci,netdev=vhost0", + "-netdev", f"socket,id=udp0,udp=localhost:{lport}," + f"localaddr=localhost:{rport}", + "-netdev", "hubport,id=hub0,hubid=0,netdev=udp0", + "-netdev", f"user,id=user0,tftp={tftpdir}," + f"hostfwd=tcp:127.0.0.1:{hostfwd_port}-:8080", + "-netdev", "hubport,id=hub1,hubid=0,netdev=user0" + ) + + def assemble_vubr_args(self, vubr_path, ud_socket_path, lport, rport): + vubr_args = [] + + if (stdbuf_path := which("stdbuf")) is None: + self.log.info("Could not find stdbuf: vhost-user-bridge " + "log lines may appear out of order") + else: + vubr_args += [stdbuf_path, "-o0", "-e0"] + + vubr_args += [vubr_path, "-u", f"{ud_socket_path}", + "-l", f"127.0.0.1:{lport}", "-r", f"127.0.0.1:{rport}"] + + return vubr_args + + def test_vhost_user_bridge(self): + prompt = "~ # " + host_uuid_filename = "vubr-test-uuid.txt" + guest_uuid_path = "/tmp/uuid.txt" + kernel_path = self.ASSET_KERNEL_INITRAMFS.fetch() + + vubr_path = self.build_file("contrib", "vhost-user-bridge", + "vhost-user-bridge") + if not is_readable_executable_file(vubr_path): + self.skipTest("Could not find a readable and executable " + "vhost-user-bridge") + + vubr_log_path = self.log_file("vhost-user-bridge.log") + self.log.info("For the vhost-user-bridge application log," + f" see: {vubr_log_path}") + + sock_dir = self.socket_dir() + ud_socket_path = os.path.join(sock_dir.name, "vubr-test.sock") + + tftpdir = self.scratch_file("tftp") + shutil.rmtree(tftpdir, ignore_errors=True) + os.mkdir(tftpdir) + host_uuid_path = self.scratch_file("tftp", host_uuid_filename) + with open(host_uuid_path, "w", encoding="utf-8") as host_uuid_file: + host_uuid_file.write(self.HOST_UUID) + + with Ports() as ports: + # pylint: disable=unbalanced-tuple-unpacking + lport, rport, hostfwd_port = ports.find_free_ports(3) + + self.configure_vm(ud_socket_path, lport, rport, hostfwd_port, + tftpdir) + + vubr_args = self.assemble_vubr_args(vubr_path, ud_socket_path, + lport, rport) + + with open(vubr_log_path, "w", encoding="utf-8") as vubr_log, \ + subprocess.Popen(vubr_args, stdin=subprocess.DEVNULL, + stdout=vubr_log, + stderr=subprocess.STDOUT) as vubr_proc: + self.launch_kernel(kernel_path, wait_for=prompt) + + exec_command_and_wait_for_pattern(self, + f"tftp -g -r {host_uuid_filename} 10.0.2.2 ; " + f"sha256sum {host_uuid_filename}", self.HOST_UUID_HSUM) + wait_for_console_pattern(self, prompt) + + exec_command_and_wait_for_pattern(self, + f"echo -n '{self.GUEST_UUID}' > {guest_uuid_path}", prompt) + self.check_http_download(guest_uuid_path, self.GUEST_UUID_HSUM) + wait_for_console_pattern(self, prompt) + + self.vm.shutdown() + vubr_proc.terminate() + vubr_proc.wait() + +if __name__ == '__main__': + LinuxKernelTest.main() diff --git a/tests/lcitool/libvirt-ci b/tests/lcitool/libvirt-ci index caed407a0f1d..5176e136ab11 160000 --- a/tests/lcitool/libvirt-ci +++ b/tests/lcitool/libvirt-ci @@ -1 +1 @@ -Subproject commit caed407a0f1dfe3a3293a41c378c382deefbac36 +Subproject commit 5176e136ab11e275eb9f57c3d5c80e77af6507cb diff --git a/tests/lcitool/mappings.yml b/tests/lcitool/mappings.yml index a749cf8c5153..606189715849 100644 --- a/tests/lcitool/mappings.yml +++ b/tests/lcitool/mappings.yml @@ -66,7 +66,7 @@ mappings: OpenSUSELeap15: python311-base python3-wheel: - OpenSUSELeap15: python311-pip + OpenSUSELeap15: python311-wheel rust: Debian12: rustc-web diff --git a/tests/lcitool/projects/qemu.yml b/tests/lcitool/projects/qemu.yml index 1ee7dcf3d46e..5e34e9535144 100644 --- a/tests/lcitool/projects/qemu.yml +++ b/tests/lcitool/projects/qemu.yml @@ -98,11 +98,13 @@ packages: - python3-pillow - python3-pip - python3-PyYAML + - python3-setuptools - python3-sphinx - python3-sphinx-rtd-theme - python3-sqlite3 - python3-tomli - python3-venv + - python3-wheel - rpm2cpio - rust - rust-std diff --git a/tests/lcitool/refresh b/tests/lcitool/refresh index 01a719295428..3e4b026035bd 100755 --- a/tests/lcitool/refresh +++ b/tests/lcitool/refresh @@ -87,9 +87,9 @@ def generate_cirrus(target, trailer=None): generate(filename, cmd, trailer) -def generate_pkglist(vm, target): +def generate_pkglist(vm, target, project="qemu"): filename = Path(src_dir, "tests", "vm", "generated", vm + ".json") - cmd = lcitool_cmd + ["variables", "--format", "json", target, "qemu"] + cmd = lcitool_cmd + ["variables", "--format", "json", target, project] generate(filename, cmd, None) @@ -172,6 +172,8 @@ debian_all_test_cross_compilers = [ " libc6-dev-arm64-cross \\\n", " gcc-arm-linux-gnueabihf \\\n", " libc6-dev-armhf-cross \\\n", + " gcc-alpha-linux-gnu \\\n" + " libc6.1-dev-alpha-cross \\\n" " gcc-mips-linux-gnu \\\n", " libc6-dev-mips-cross \\\n", " gcc-mips64-linux-gnuabi64 \\\n", @@ -185,7 +187,9 @@ debian_all_test_cross_compilers = [ " gcc-riscv64-linux-gnu \\\n", " libc6-dev-riscv64-cross \\\n", " gcc-s390x-linux-gnu \\\n", - " libc6-dev-s390x-cross\n", + " libc6-dev-s390x-cross\\\n", + " gcc-sh4-linux-gnu \\\n", + " libc6-dev-sh4-cross\n", "RUN if dpkg-architecture -e amd64; then \\\n", " export AVAILABLE_COMPILERS=\"${AVAILABLE_COMPILERS} gcc-hppa-linux-gnu libc6-dev-hppa-cross\"; \\\n", " export AVAILABLE_COMPILERS=\"${AVAILABLE_COMPILERS} gcc-m68k-linux-gnu libc6-dev-m68k-cross\"; \\\n", @@ -198,7 +202,7 @@ debian_all_test_cross_compilers = [ "${AVAILABLE_COMPILERS} && \\\n", "dpkg-query --showformat '${Package}_${Version}_${Architecture}' --show > /packages.txt\n", "ENV QEMU_CONFIGURE_OPTS --disable-docs\n", - "ENV DEF_TARGET_LIST aarch64-linux-user,arm-linux-user,hppa-linux-user,i386-linux-user,m68k-linux-user,mips-linux-user,mips64-linux-user,mips64el-linux-user,mipsel-linux-user,ppc-linux-user,ppc64-linux-user,ppc64le-linux-user,riscv64-linux-user,s390x-linux-user,sparc64-linux-user\n", + "ENV DEF_TARGET_LIST aarch64-linux-user,alpha-linux-user,arm-linux-user,hppa-linux-user,i386-linux-user,m68k-linux-user,mips-linux-user,mips64-linux-user,mips64el-linux-user,mipsel-linux-user,ppc-linux-user,ppc64-linux-user,ppc64le-linux-user,riscv64-linux-user,s390x-linux-user,sh4-linux-user,sparc64-linux-user\n", ] def cross_build(prefix, targets): @@ -215,12 +219,12 @@ try: # # Standard native builds # - generate_dockerfile("alpine", "alpine-321", + generate_dockerfile("alpine", "alpine-323", trailer="".join(alpine_extras)) generate_dockerfile("centos9", "centos-stream-9") generate_dockerfile("debian", "debian-13", trailer="".join(debian13_extras)) - generate_dockerfile("fedora", "fedora-41") + generate_dockerfile("fedora", "fedora-43") generate_dockerfile("opensuse-leap", "opensuse-leap-15") generate_dockerfile("ubuntu2204", "ubuntu-2204", trailer="".join(ubuntu2204_rust_extras), @@ -230,7 +234,7 @@ try: # # Non-fatal Rust-enabled build # - generate_dockerfile("fedora-rust-nightly", "fedora-41", + generate_dockerfile("fedora-rust-nightly", "fedora-43", trailer="".join(fedora_rustup_nightly_extras)) # @@ -287,7 +291,7 @@ try: trailer=cross_build("s390x-linux-gnu-", "s390x-softmmu,s390x-linux-user")) - generate_dockerfile("fedora-win64-cross", "fedora-41", + generate_dockerfile("fedora-win64-cross", "fedora-43", project='qemu,qemu-win-installer', cross="mingw64", trailer=cross_build("x86_64-w64-mingw32-", @@ -312,6 +316,7 @@ try: # VM packages lists # generate_pkglist("freebsd", "freebsd-14") + generate_pkglist("openbsd", "openbsd-78", project="qemu-minimal") # # Ansible package lists diff --git a/tests/meson.build b/tests/meson.build index cbe79162411b..9ba04bbedd30 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -70,12 +70,6 @@ test_deps = { 'test-qht-par': qht_bench, } -if have_tools and have_vhost_user and host_os == 'linux' - executable('vhost-user-bridge', - sources: files('vhost-user-bridge.c'), - dependencies: [qemuutil, vhost_user]) -endif - subdir('decode') if 'CONFIG_TCG' in config_all_accel @@ -83,6 +77,7 @@ if 'CONFIG_TCG' in config_all_accel subdir('tcg/plugins') endif +subdir('audio') subdir('unit') subdir('qapi-schema') subdir('qtest') diff --git a/tests/migration-stress/guestperf/engine.py b/tests/migration-stress/guestperf/engine.py index d8462db76534..8d2ed757af3a 100644 --- a/tests/migration-stress/guestperf/engine.py +++ b/tests/migration-stress/guestperf/engine.py @@ -27,9 +27,18 @@ from guestperf.report import Report, ReportResult from guestperf.timings import TimingRecord, Timings -sys.path.append(os.path.join(os.path.dirname(__file__), - '..', '..', '..', 'python')) -from qemu.machine import QEMUMachine +try: + from qemu.machine import QEMUMachine +except ModuleNotFoundError as exc: + print( + f"Module '{exc.name}' not found.\n" + "It should be installed as part of the configure-time " + "virtual environment in $builddir/pyvenv.\n" + "Try re-running this script as:\n" + f"> $builddir/run {' '.join(sys.argv)}", + file=sys.stderr + ) + sys.exit(1) # multifd supported compression algorithms MULTIFD_CMP_ALGS = ("zlib", "zstd", "qpl", "uadk") diff --git a/tests/qemu-iotests/082.out b/tests/qemu-iotests/082.out index d0dd333117a5..e0463815c659 100644 --- a/tests/qemu-iotests/082.out +++ b/tests/qemu-iotests/082.out @@ -66,6 +66,7 @@ Supported options: encryption= - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes) extended_l2= - Extended L2 tables extent_size_hint= - Extent size hint for the image file, 0 to disable + keep_data_file= - Assume the external data file already exists and do not overwrite it lazy_refcounts= - Postpone refcount updates nocow= - Turn off copy-on-write (valid only on btrfs) preallocation= - Preallocation mode (allowed values: off, metadata, falloc, full) @@ -92,6 +93,7 @@ Supported options: encryption= - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes) extended_l2= - Extended L2 tables extent_size_hint= - Extent size hint for the image file, 0 to disable + keep_data_file= - Assume the external data file already exists and do not overwrite it lazy_refcounts= - Postpone refcount updates nocow= - Turn off copy-on-write (valid only on btrfs) preallocation= - Preallocation mode (allowed values: off, metadata, falloc, full) @@ -118,6 +120,7 @@ Supported options: encryption= - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes) extended_l2= - Extended L2 tables extent_size_hint= - Extent size hint for the image file, 0 to disable + keep_data_file= - Assume the external data file already exists and do not overwrite it lazy_refcounts= - Postpone refcount updates nocow= - Turn off copy-on-write (valid only on btrfs) preallocation= - Preallocation mode (allowed values: off, metadata, falloc, full) @@ -144,6 +147,7 @@ Supported options: encryption= - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes) extended_l2= - Extended L2 tables extent_size_hint= - Extent size hint for the image file, 0 to disable + keep_data_file= - Assume the external data file already exists and do not overwrite it lazy_refcounts= - Postpone refcount updates nocow= - Turn off copy-on-write (valid only on btrfs) preallocation= - Preallocation mode (allowed values: off, metadata, falloc, full) @@ -170,6 +174,7 @@ Supported options: encryption= - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes) extended_l2= - Extended L2 tables extent_size_hint= - Extent size hint for the image file, 0 to disable + keep_data_file= - Assume the external data file already exists and do not overwrite it lazy_refcounts= - Postpone refcount updates nocow= - Turn off copy-on-write (valid only on btrfs) preallocation= - Preallocation mode (allowed values: off, metadata, falloc, full) @@ -196,6 +201,7 @@ Supported options: encryption= - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes) extended_l2= - Extended L2 tables extent_size_hint= - Extent size hint for the image file, 0 to disable + keep_data_file= - Assume the external data file already exists and do not overwrite it lazy_refcounts= - Postpone refcount updates nocow= - Turn off copy-on-write (valid only on btrfs) preallocation= - Preallocation mode (allowed values: off, metadata, falloc, full) @@ -222,6 +228,7 @@ Supported options: encryption= - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes) extended_l2= - Extended L2 tables extent_size_hint= - Extent size hint for the image file, 0 to disable + keep_data_file= - Assume the external data file already exists and do not overwrite it lazy_refcounts= - Postpone refcount updates nocow= - Turn off copy-on-write (valid only on btrfs) preallocation= - Preallocation mode (allowed values: off, metadata, falloc, full) @@ -248,6 +255,7 @@ Supported options: encryption= - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes) extended_l2= - Extended L2 tables extent_size_hint= - Extent size hint for the image file, 0 to disable + keep_data_file= - Assume the external data file already exists and do not overwrite it lazy_refcounts= - Postpone refcount updates nocow= - Turn off copy-on-write (valid only on btrfs) preallocation= - Preallocation mode (allowed values: off, metadata, falloc, full) @@ -288,6 +296,7 @@ Supported qcow2 options: encrypt.key-secret= - ID of secret providing qcow AES key or LUKS passphrase encryption= - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes) extended_l2= - Extended L2 tables + keep_data_file= - Assume the external data file already exists and do not overwrite it lazy_refcounts= - Postpone refcount updates preallocation= - Preallocation mode (allowed values: off, metadata, falloc, full) refcount_bits= - Width of a reference count entry in bits @@ -376,6 +385,7 @@ Supported options: encryption= - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes) extended_l2= - Extended L2 tables extent_size_hint= - Extent size hint for the image file, 0 to disable + keep_data_file= - Assume the external data file already exists and do not overwrite it lazy_refcounts= - Postpone refcount updates nocow= - Turn off copy-on-write (valid only on btrfs) preallocation= - Preallocation mode (allowed values: off, metadata, falloc, full) @@ -402,6 +412,7 @@ Supported options: encryption= - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes) extended_l2= - Extended L2 tables extent_size_hint= - Extent size hint for the image file, 0 to disable + keep_data_file= - Assume the external data file already exists and do not overwrite it lazy_refcounts= - Postpone refcount updates nocow= - Turn off copy-on-write (valid only on btrfs) preallocation= - Preallocation mode (allowed values: off, metadata, falloc, full) @@ -428,6 +439,7 @@ Supported options: encryption= - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes) extended_l2= - Extended L2 tables extent_size_hint= - Extent size hint for the image file, 0 to disable + keep_data_file= - Assume the external data file already exists and do not overwrite it lazy_refcounts= - Postpone refcount updates nocow= - Turn off copy-on-write (valid only on btrfs) preallocation= - Preallocation mode (allowed values: off, metadata, falloc, full) @@ -454,6 +466,7 @@ Supported options: encryption= - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes) extended_l2= - Extended L2 tables extent_size_hint= - Extent size hint for the image file, 0 to disable + keep_data_file= - Assume the external data file already exists and do not overwrite it lazy_refcounts= - Postpone refcount updates nocow= - Turn off copy-on-write (valid only on btrfs) preallocation= - Preallocation mode (allowed values: off, metadata, falloc, full) @@ -480,6 +493,7 @@ Supported options: encryption= - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes) extended_l2= - Extended L2 tables extent_size_hint= - Extent size hint for the image file, 0 to disable + keep_data_file= - Assume the external data file already exists and do not overwrite it lazy_refcounts= - Postpone refcount updates nocow= - Turn off copy-on-write (valid only on btrfs) preallocation= - Preallocation mode (allowed values: off, metadata, falloc, full) @@ -506,6 +520,7 @@ Supported options: encryption= - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes) extended_l2= - Extended L2 tables extent_size_hint= - Extent size hint for the image file, 0 to disable + keep_data_file= - Assume the external data file already exists and do not overwrite it lazy_refcounts= - Postpone refcount updates nocow= - Turn off copy-on-write (valid only on btrfs) preallocation= - Preallocation mode (allowed values: off, metadata, falloc, full) @@ -532,6 +547,7 @@ Supported options: encryption= - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes) extended_l2= - Extended L2 tables extent_size_hint= - Extent size hint for the image file, 0 to disable + keep_data_file= - Assume the external data file already exists and do not overwrite it lazy_refcounts= - Postpone refcount updates nocow= - Turn off copy-on-write (valid only on btrfs) preallocation= - Preallocation mode (allowed values: off, metadata, falloc, full) @@ -558,6 +574,7 @@ Supported options: encryption= - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes) extended_l2= - Extended L2 tables extent_size_hint= - Extent size hint for the image file, 0 to disable + keep_data_file= - Assume the external data file already exists and do not overwrite it lazy_refcounts= - Postpone refcount updates nocow= - Turn off copy-on-write (valid only on btrfs) preallocation= - Preallocation mode (allowed values: off, metadata, falloc, full) @@ -598,6 +615,7 @@ Supported qcow2 options: encrypt.key-secret= - ID of secret providing qcow AES key or LUKS passphrase encryption= - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes) extended_l2= - Extended L2 tables + keep_data_file= - Assume the external data file already exists and do not overwrite it lazy_refcounts= - Postpone refcount updates preallocation= - Preallocation mode (allowed values: off, metadata, falloc, full) refcount_bits= - Width of a reference count entry in bits diff --git a/tests/qemu-iotests/182 b/tests/qemu-iotests/182 index bbd1132b052f..af5eeb599c18 100755 --- a/tests/qemu-iotests/182 +++ b/tests/qemu-iotests/182 @@ -43,6 +43,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 _supported_fmt qcow2 _supported_proto file +_supported_os Linux size=32M diff --git a/tests/qemu-iotests/244 b/tests/qemu-iotests/244 index bb9cc6512f35..2a98ad58f5dc 100755 --- a/tests/qemu-iotests/244 +++ b/tests/qemu-iotests/244 @@ -384,6 +384,77 @@ $QEMU_IMG compare --image-opts \ "driver=raw,file.filename=$TEST_IMG.data" \ "file.filename=$TEST_IMG,backing.file.filename=$TEST_IMG.base" +echo +echo '=== keep_data_file tests ===' + +echo +echo '--- Creating test data file ---' + +# Easiest way to create the raw data file without having to create and +# access it manually +_make_test_img -o "data_file=$TEST_IMG.data,data_file_raw=on" 1M +# Values chosen by a fair random.org evaluation +$QEMU_IO -c 'write -P 3 0 512k' -c 'write -P 96 512k 512k' "$TEST_IMG" | + _filter_qemu_io + +echo +echo '--- Testing stand-alone option ---' + +# Cannot work, needs data file +_make_test_img -o "keep_data_file=on" 1M + +# Invalid option value +_make_test_img -o "keep_data_file=true" 1M + +# Should be the same as omitting +_make_test_img -o "keep_data_file=off" 1M + +# No preallocation is OK when also specifying data_file_raw; otherwise, none of +# the data file will be mapped, i.e. its contents will stay hidden, so +# requesting its contents to be kept (but hidden) doesn't make much sense. +# +# Metadata preallocation is OK: It will not overwrite the data file's contents, +# but ensure the contents are mapped and visible. +# +# Any data preallocation (like falloc) is not OK, as this would overwrite the +# data file's contents despite keep_data_file requesting they should not be +# overwritten. +# +# Note that all of these cases use the data file created above: This verifies +# that when passing keep_data_file=on, the data file is always kept as-is (and +# e.g. not deleted on error). +for prealloc in off metadata falloc full; do + # Without metadata preallocation, the data_file_raw flag is required so that + # the data file's contents are visible. + for data_file_raw in off on; do + echo + echo "--- Testing prealloc=$prealloc data_file_raw=$data_file_raw ---" + + # Remove previously existing qcow2 (metadata) file + _cleanup_test_img + + opts="data_file=$TEST_IMG.data,keep_data_file=on" + opts+=",preallocation=$prealloc" + opts+=",data_file_raw=$data_file_raw" + + _make_test_img -o "$opts" 1M + if [ -f "$TEST_IMG" ]; then + $QEMU_IO -c 'read -P 3 0 512k' -c 'read -P 96 512k 512k' "$TEST_IMG" | + _filter_qemu_io + fi + done +done + +echo +echo '--- Testing non-existent data file ---' + +# Maybe a matter of taste whether this should fail or create the file, but +# failing is simpler (= will always skip create) and seems safer (users may +# expect the file to exist, and the error will warn them when it does not). +_make_test_img \ + -o "data_file=$TEST_IMG.doesnotexist,keep_data_file=on,data_file_raw=on" \ + 1M + # success, all done echo "*** done" rm -f $seq.full diff --git a/tests/qemu-iotests/244.out b/tests/qemu-iotests/244.out index f46cfe93f11c..c62f5aec25b9 100644 --- a/tests/qemu-iotests/244.out +++ b/tests/qemu-iotests/244.out @@ -197,4 +197,65 @@ wrote 1048576/1048576 bytes at offset 0 Comparing qcow2 image and raw data file: Images are identical. + +=== keep_data_file tests === + +--- Creating test data file --- +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 data_file=TEST_DIR/t.IMGFMT.data data_file_raw=on +wrote 524288/524288 bytes at offset 0 +512 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 524288/524288 bytes at offset 524288 +512 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +--- Testing stand-alone option --- +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 keep_data_file=on +qemu-img: TEST_DIR/t.IMGFMT: Must not use 'keep_data_file=on' without 'data_file' +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 keep_data_file=true +qemu-img: TEST_DIR/t.IMGFMT: Invalid value 'true' for 'keep_data_file': Must be 'on' or 'off' +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 keep_data_file=off + +--- Testing prealloc=off data_file_raw=off --- +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 data_file=TEST_DIR/t.IMGFMT.data data_file_raw=off keep_data_file=on preallocation=off +qemu-img: TEST_DIR/t.IMGFMT: 'keep_data_file=on' requires 'preallocation=metadata' or 'data_file_raw=on', or the file contents will not be visible + +--- Testing prealloc=off data_file_raw=on --- +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 data_file=TEST_DIR/t.IMGFMT.data data_file_raw=on keep_data_file=on preallocation=off +read 524288/524288 bytes at offset 0 +512 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 524288/524288 bytes at offset 524288 +512 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +--- Testing prealloc=metadata data_file_raw=off --- +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 data_file=TEST_DIR/t.IMGFMT.data data_file_raw=off keep_data_file=on preallocation=metadata +read 524288/524288 bytes at offset 0 +512 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 524288/524288 bytes at offset 524288 +512 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +--- Testing prealloc=metadata data_file_raw=on --- +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 data_file=TEST_DIR/t.IMGFMT.data data_file_raw=on keep_data_file=on preallocation=metadata +read 524288/524288 bytes at offset 0 +512 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 524288/524288 bytes at offset 524288 +512 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +--- Testing prealloc=falloc data_file_raw=off --- +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 data_file=TEST_DIR/t.IMGFMT.data data_file_raw=off keep_data_file=on preallocation=falloc +qemu-img: TEST_DIR/t.IMGFMT: Preallocating more than only metadata would overwrite the external data file's content and is therefore incompatible with 'keep_data_file=on' + +--- Testing prealloc=falloc data_file_raw=on --- +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 data_file=TEST_DIR/t.IMGFMT.data data_file_raw=on keep_data_file=on preallocation=falloc +qemu-img: TEST_DIR/t.IMGFMT: Preallocating more than only metadata would overwrite the external data file's content and is therefore incompatible with 'keep_data_file=on' + +--- Testing prealloc=full data_file_raw=off --- +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 data_file=TEST_DIR/t.IMGFMT.data data_file_raw=off keep_data_file=on preallocation=full +qemu-img: TEST_DIR/t.IMGFMT: Preallocating more than only metadata would overwrite the external data file's content and is therefore incompatible with 'keep_data_file=on' + +--- Testing prealloc=full data_file_raw=on --- +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 data_file=TEST_DIR/t.IMGFMT.data data_file_raw=on keep_data_file=on preallocation=full +qemu-img: TEST_DIR/t.IMGFMT: Preallocating more than only metadata would overwrite the external data file's content and is therefore incompatible with 'keep_data_file=on' + +--- Testing non-existent data file --- +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 data_file=TEST_DIR/t.IMGFMT.doesnotexist data_file_raw=on keep_data_file=on +qemu-img: TEST_DIR/t.IMGFMT: Could not open 'TEST_DIR/t.IMGFMT.doesnotexist': No such file or directory *** done diff --git a/tests/qemu-iotests/common.filter b/tests/qemu-iotests/common.filter index 26e6b45b0411..ce4213cb974a 100644 --- a/tests/qemu-iotests/common.filter +++ b/tests/qemu-iotests/common.filter @@ -182,7 +182,7 @@ _do_filter_img_create() -e 's/^\(fmt\)/0-\1/' \ -e 's/^\(size\)/1-\1/' \ -e 's/^\(backing\)/2-\1/' \ - -e 's/^\(data_file\)/3-\1/' \ + -e 's/^\(\(keep_\)\?data_file\)/3-\1/' \ -e 's/^\(encryption\)/4-\1/' \ -e 's/^\(preallocation\)/8-\1/' \ | LC_ALL=C sort \ diff --git a/tests/qemu-iotests/testenv.py b/tests/qemu-iotests/testenv.py index 29caaa8a349c..c357e6ebf503 100644 --- a/tests/qemu-iotests/testenv.py +++ b/tests/qemu-iotests/testenv.py @@ -20,6 +20,7 @@ import sys import tempfile from pathlib import Path +import shlex import shutil import collections import contextlib @@ -140,7 +141,29 @@ def init_binaries(self) -> None: PYTHON (for bash tests) QEMU_PROG, QEMU_IMG_PROG, QEMU_IO_PROG, QEMU_NBD_PROG, QSD_PROG """ - self.python = sys.executable + self.python = str(Path(sys.executable).absolute()) + + # QEMU configure-time venv python executable + venv_python = Path( + os.path.join(self.build_root, "pyvenv", "bin", "python3") + ).absolute() + + if self.python != str(venv_python): + runpath = os.path.join(self.build_root, "run") + cmd = ' '.join(shlex.quote(x) for x in sys.argv) + print( + "\n\033[93m\033[1mWARNING\033[0m: " + "iotests is being run from outside of the configure-time " + "python virtual environment\n\n" + f"current python: {self.python}\n" + f"pyvenv python: {venv_python}\n\n" + "Individual python tests will be executed inside the pyvenv,\n" + "but the test runner will continue to run outside.\n\n" + "\033[1mPlease use the meson run script:\033[0m\n" + f"\t{runpath} {cmd}\n", + file=sys.stderr + ) + self.python = str(venv_python) def root(*names: str) -> str: return os.path.join(self.build_root, *names) diff --git a/tests/qtest/boot-serial-test.c b/tests/qtest/boot-serial-test.c index a05d26ee9967..bcd0a9c50e70 100644 --- a/tests/qtest/boot-serial-test.c +++ b/tests/qtest/boot-serial-test.c @@ -186,7 +186,7 @@ static const testdef_t tests[] = { { "m68k", "next-cube", "", "TT", sizeof(bios_nextcube), 0, bios_nextcube }, { "microblaze", "petalogix-s3adsp1800", "", "TT", sizeof(kernel_pls3adsp1800), kernel_pls3adsp1800 }, - { "microblazeel", "petalogix-ml605", "", "TT", + { "microblaze", "petalogix-ml605", "", "TT", sizeof(kernel_plml605), kernel_plml605 }, { "arm", "raspi2b", "", "TT", sizeof(bios_raspi2), 0, bios_raspi2 }, { "aarch64", "virt", "-cpu max", "TT", sizeof(kernel_aarch64), diff --git a/tests/qtest/cdrom-test.c b/tests/qtest/cdrom-test.c index 56e2d283a9db..a65854d2bc53 100644 --- a/tests/qtest/cdrom-test.c +++ b/tests/qtest/cdrom-test.c @@ -246,6 +246,13 @@ static void add_s390x_tests(void) "-drive if=none,id=d2,media=cdrom,file=", test_cdboot); } + if (qtest_has_device("virtio-blk-pci")) { + qtest_add_data_func("cdrom/boot/pci-bus-with-bootindex", + "-device virtio-scsi -device virtio-serial " + "-device virtio-blk-pci,drive=d1,bootindex=1 " + "-drive if=none,id=d1,media=cdrom,file=", + test_cdboot); + } } int main(int argc, char **argv) diff --git a/tests/qtest/dbus-display-test.c b/tests/qtest/dbus-display-test.c index 1d5951b71170..5773776cad59 100644 --- a/tests/qtest/dbus-display-test.c +++ b/tests/qtest/dbus-display-test.c @@ -76,6 +76,7 @@ test_dbus_display_vm(void) qemu_dbus_display1_vm_get_name(QEMU_DBUS_DISPLAY1_VM(vm)), ==, "dbus-test"); + g_clear_object(&conn); qtest_quit(qts); } @@ -97,6 +98,8 @@ static gboolean listener_handle_scanout( GVariant *arg_data, TestDBusConsoleRegister *test) { + qemu_dbus_display1_listener_complete_scanout(object, invocation); + if (!test->with_map) { g_main_loop_quit(test->loop); } @@ -131,6 +134,9 @@ static gboolean listener_handle_scanout_map( g_assert_no_errno(addr == MAP_FAILED ? -1 : 0); g_assert_no_errno(munmap(addr, len)); + qemu_dbus_display1_listener_unix_map_complete_scanout_map(object, invocation, + NULL); + g_main_loop_quit(test->loop); close(fd); @@ -287,6 +293,7 @@ test_dbus_display_console(const void* data) g_clear_object(&test.server); g_clear_object(&test.listener_conn); + g_clear_object(&conn); qtest_quit(qts); } @@ -322,6 +329,7 @@ test_dbus_display_keyboard(void) &err); if (g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) { g_test_skip("The VM doesn't have a console!"); + g_clear_object(&conn); qtest_quit(qts); return; } @@ -348,6 +356,7 @@ test_dbus_display_keyboard(void) g_assert_cmpint(qemu_dbus_display1_keyboard_get_modifiers( QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard)), ==, 0); + g_clear_object(&conn); qtest_quit(qts); } diff --git a/tests/qtest/iommu-smmuv3-test.c b/tests/qtest/iommu-smmuv3-test.c index cced49a9b695..dae9821b921a 100644 --- a/tests/qtest/iommu-smmuv3-test.c +++ b/tests/qtest/iommu-smmuv3-test.c @@ -77,6 +77,7 @@ static void run_smmuv3_translation(const QSMMUTestConfig *cfg) g_test_message("### SMMUv3 translation mode=%d sec_sid=%d ###", cfg->trans_mode, cfg->sec_sid); qsmmu_run_translation_case(qts, dev, bar, VIRT_SMMU_BASE, cfg); + g_free(dev); qtest_quit(qts); } diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c index 794d87008577..051faf31e143 100644 --- a/tests/qtest/libqtest.c +++ b/tests/qtest/libqtest.c @@ -1815,6 +1815,7 @@ void qtest_cb_for_every_machine(void (*cb)(const char *machine), g_str_equal("xenpv", machines[i].name) || g_str_equal("xenpvh", machines[i].name) || g_str_equal("vmapple", machines[i].name) || + g_str_equal("nitro", machines[i].name) || g_str_equal("nitro-enclave", machines[i].name)) { continue; } diff --git a/tests/qtest/machine-none-test.c b/tests/qtest/machine-none-test.c index b6a87d27edb7..bafd7d660ec7 100644 --- a/tests/qtest/machine-none-test.c +++ b/tests/qtest/machine-none-test.c @@ -32,7 +32,6 @@ static struct arch2cpu cpus_map[] = { { "alpha", "ev67" }, { "m68k", "m5206" }, { "microblaze", "any" }, - { "microblazeel", "any" }, { "mips", "4Kc" }, { "mipsel", "I7200" }, { "mips64", "20Kc" }, @@ -48,7 +47,7 @@ static struct arch2cpu cpus_map[] = { { "tricore", "tc1796" }, { "xtensa", "dc233c" }, { "xtensaeb", "fsf" }, - { "hppa", "hppa" }, + { "hppa", "pa-7300lc" }, { "riscv64", "rv64" }, { "riscv32", "rv32" }, { "rx", "rx62n" }, diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 25fdbc798010..be4fa627b5f8 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -159,8 +159,6 @@ qtests_m68k = ['boot-serial-test'] + \ qtests_microblaze = ['boot-serial-test'] + \ qtests_filter -qtests_microblazeel = qtests_microblaze - qtests_mips = \ qtests_filter + \ (config_all_devices.has_key('CONFIG_ISA_TESTDEV') ? ['endianness-test'] : []) + \ @@ -374,6 +372,11 @@ if gnutls.found() endif endif +migration_colo_files = [] +if get_option('replication').allowed() + migration_colo_files = [files('migration/colo-tests.c')] +endif + qtests = { 'aspeed_hace-test': files('aspeed-hace-utils.c', 'aspeed_hace-test.c'), 'aspeed_smc-test': files('aspeed-smc-utils.c', 'aspeed_smc-test.c'), @@ -385,7 +388,7 @@ qtests = { 'migration/migration-util.c') + dbus_vmstate1, 'erst-test': files('erst-test.c'), 'ivshmem-test': [rt, '../../contrib/ivshmem-server/ivshmem-server.c'], - 'migration-test': test_migration_files + migration_tls_files, + 'migration-test': test_migration_files + migration_tls_files + migration_colo_files, 'pxe-test': files('boot-sector.c'), 'pnv-xive2-test': files('pnv-xive2-common.c', 'pnv-xive2-flush-sync.c', 'pnv-xive2-nvpg_bar.c'), diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c index 089368717415..e582f05c7dc2 100644 --- a/tests/qtest/migration-test.c +++ b/tests/qtest/migration-test.c @@ -55,6 +55,7 @@ int main(int argc, char **argv) migration_test_add_precopy(env); migration_test_add_cpr(env); migration_test_add_misc(env); + migration_test_add_colo(env); ret = g_test_run(); diff --git a/tests/qtest/migration/colo-tests.c b/tests/qtest/migration/colo-tests.c new file mode 100644 index 000000000000..ef880f51147f --- /dev/null +++ b/tests/qtest/migration/colo-tests.c @@ -0,0 +1,195 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * QTest testcases for COLO migration + * + * Copyright (c) 2025 Lukas Straub + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "migration/framework.h" +#include "migration/migration-qmp.h" +#include "migration/migration-util.h" +#include "qemu/module.h" + +static int test_colo_common(MigrateCommon *args, + bool failover_during_checkpoint, + bool primary_failover) +{ + QTestState *from, *to; + void *data_hook = NULL; + + /* + * For the COLO test, both VMs will run in parallel. Thus both VMs want to + * open the image read/write at the same time. Using read-only=on is not + * possible here, because ide-hd does not support read-only backing image. + * + * So use -snapshot, where each qemu instance creates its own writable + * snapshot internally while leaving the real image read-only. + */ + args->start.opts_source = "-snapshot"; + args->start.opts_target = "-snapshot"; + + /* + * COLO migration code logs many errors when the migration socket + * is shut down, these are expected so we hide them here. + */ + args->start.hide_stderr = true; + + /* + * Test with yank with out of band capability since that is how it is + * used in production. + */ + args->start.oob = true; + args->start.caps[MIGRATION_CAPABILITY_RETURN_PATH] = true; + args->start.caps[MIGRATION_CAPABILITY_X_COLO] = true; + + if (migrate_start(&from, &to, args->listen_uri, &args->start)) { + return -1; + } + + migrate_set_parameter_int(from, "x-checkpoint-delay", 300); + + if (args->start_hook) { + data_hook = args->start_hook(from, to); + } + + migrate_ensure_converge(from); + wait_for_serial("src_serial"); + + migrate_qmp(from, to, args->connect_uri, NULL, "{}"); + + wait_for_migration_status(from, "colo", NULL); + wait_for_resume(to, get_dst()); + + wait_for_serial("src_serial"); + wait_for_serial("dest_serial"); + + /* wait for 3 checkpoints */ + for (int i = 0; i < 3; i++) { + qtest_qmp_eventwait(to, "RESUME"); + wait_for_serial("src_serial"); + wait_for_serial("dest_serial"); + } + + if (failover_during_checkpoint) { + qtest_qmp_eventwait(to, "STOP"); + } + if (primary_failover) { + qtest_qmp_assert_success(from, "{'exec-oob': 'yank', 'id': 'yank-cmd', " + "'arguments': {'instances':" + "[{'type': 'migration'}]}}"); + qtest_qmp_assert_success(from, "{'execute': 'x-colo-lost-heartbeat'}"); + wait_for_serial("src_serial"); + } else { + qtest_qmp_assert_success(to, "{'exec-oob': 'yank', 'id': 'yank-cmd', " + "'arguments': {'instances':" + "[{'type': 'migration'}]}}"); + qtest_qmp_assert_success(to, "{'execute': 'x-colo-lost-heartbeat'}"); + wait_for_serial("dest_serial"); + } + + if (args->end_hook) { + args->end_hook(from, to, data_hook); + } + + migrate_end(from, to, !primary_failover); + + return 0; +} + +static void test_colo_plain_common(MigrateCommon *args, + bool failover_during_checkpoint, + bool primary_failover) +{ + args->listen_uri = "tcp:127.0.0.1:0"; + test_colo_common(args, failover_during_checkpoint, primary_failover); +} + +static void *hook_start_multifd(QTestState *from, QTestState *to) +{ + return migrate_hook_start_precopy_tcp_multifd_common(from, to, "none"); +} + +static void test_colo_multifd_common(MigrateCommon *args, + bool failover_during_checkpoint, + bool primary_failover) +{ + args->listen_uri = "defer"; + args->start_hook = hook_start_multifd; + args->start.caps[MIGRATION_CAPABILITY_MULTIFD] = true; + test_colo_common(args, failover_during_checkpoint, primary_failover); +} + +static void test_colo_plain_primary_failover(char *name, MigrateCommon *args) +{ + test_colo_plain_common(args, false, true); +} + +static void test_colo_plain_secondary_failover(char *name, MigrateCommon *args) +{ + test_colo_plain_common(args, false, false); +} + +static void test_colo_multifd_primary_failover(char *name, MigrateCommon *args) +{ + test_colo_multifd_common(args, false, true); +} + +static void test_colo_multifd_secondary_failover(char *name, + MigrateCommon *args) +{ + test_colo_multifd_common(args, false, false); +} + +static void test_colo_plain_primary_failover_checkpoint(char *name, + MigrateCommon *args) +{ + test_colo_plain_common(args, true, true); +} + +static void test_colo_plain_secondary_failover_checkpoint(char *name, + MigrateCommon *args) +{ + test_colo_plain_common(args, true, false); +} + +static void test_colo_multifd_primary_failover_checkpoint(char *name, + MigrateCommon *args) +{ + test_colo_multifd_common(args, true, true); +} + +static void test_colo_multifd_secondary_failover_checkpoint(char *name, + MigrateCommon *args) +{ + test_colo_multifd_common(args, true, false); +} + +void migration_test_add_colo(MigrationTestEnv *env) +{ + if (!env->full_set) { + return; + } + + migration_test_add("/migration/colo/plain/primary_failover", + test_colo_plain_primary_failover); + migration_test_add("/migration/colo/plain/secondary_failover", + test_colo_plain_secondary_failover); + + migration_test_add("/migration/colo/multifd/primary_failover", + test_colo_multifd_primary_failover); + migration_test_add("/migration/colo/multifd/secondary_failover", + test_colo_multifd_secondary_failover); + + migration_test_add("/migration/colo/plain/primary_failover_checkpoint", + test_colo_plain_primary_failover_checkpoint); + migration_test_add("/migration/colo/plain/secondary_failover_checkpoint", + test_colo_plain_secondary_failover_checkpoint); + + migration_test_add("/migration/colo/multifd/primary_failover_checkpoint", + test_colo_multifd_primary_failover_checkpoint); + migration_test_add("/migration/colo/multifd/secondary_failover_checkpoint", + test_colo_multifd_secondary_failover_checkpoint); +} diff --git a/tests/qtest/migration/framework.c b/tests/qtest/migration/framework.c index 2a3efeb80740..0bfc24191446 100644 --- a/tests/qtest/migration/framework.c +++ b/tests/qtest/migration/framework.c @@ -216,6 +216,19 @@ static void migrate_start_set_capabilities(QTestState *from, QTestState *to, * MigrationCapability_lookup and MIGRATION_CAPABILITY_ constants * are from qapi-types-migration.h. */ + + /* + * Enable return path first, since other features depend on it. + */ + if (args->caps[MIGRATION_CAPABILITY_RETURN_PATH]) { + if (from) { + migrate_set_capability(from, "return-path", true); + } + if (to) { + migrate_set_capability(to, "return-path", true); + } + } + for (uint8_t i = 0; i < MIGRATION_CAPABILITY__MAX; i++) { if (!args->caps[i]) { continue; diff --git a/tests/qtest/migration/framework.h b/tests/qtest/migration/framework.h index 40984d04930d..80eef758932c 100644 --- a/tests/qtest/migration/framework.h +++ b/tests/qtest/migration/framework.h @@ -264,5 +264,10 @@ void migration_test_add_file(MigrationTestEnv *env); void migration_test_add_precopy(MigrationTestEnv *env); void migration_test_add_cpr(MigrationTestEnv *env); void migration_test_add_misc(MigrationTestEnv *env); +#ifdef CONFIG_REPLICATION +void migration_test_add_colo(MigrationTestEnv *env); +#else +static inline void migration_test_add_colo(MigrationTestEnv *env) {}; +#endif #endif /* TEST_FRAMEWORK_H */ diff --git a/tests/qtest/migration/migration-qmp.c b/tests/qtest/migration/migration-qmp.c index 5c46ceb3e650..8279504db19e 100644 --- a/tests/qtest/migration/migration-qmp.c +++ b/tests/qtest/migration/migration-qmp.c @@ -241,7 +241,8 @@ void wait_for_migration_fail(QTestState *from, bool allow_active) do { status = migrate_query_status(from); bool result = !strcmp(status, "setup") || !strcmp(status, "failed") || - (allow_active && !strcmp(status, "active")); + (allow_active && !strcmp(status, "active")) || + !strcmp(status, "failing"); if (!result) { fprintf(stderr, "%s: unexpected status status=%s allow_active=%d\n", __func__, status, allow_active); diff --git a/tests/qtest/migration/precopy-tests.c b/tests/qtest/migration/precopy-tests.c index a5423ca33c11..f17dc5176d84 100644 --- a/tests/qtest/migration/precopy-tests.c +++ b/tests/qtest/migration/precopy-tests.c @@ -1247,7 +1247,7 @@ void migration_test_add_precopy(MigrationTestEnv *env) } /* ensure new status don't go unnoticed */ - assert(MIGRATION_STATUS__MAX == 16); + assert(MIGRATION_STATUS__MAX == 17); for (int i = MIGRATION_STATUS_NONE; i < MIGRATION_STATUS__MAX; i++) { switch (i) { @@ -1259,6 +1259,7 @@ void migration_test_add_precopy(MigrationTestEnv *env) case MIGRATION_STATUS_POSTCOPY_PAUSED: case MIGRATION_STATUS_POSTCOPY_RECOVER_SETUP: case MIGRATION_STATUS_POSTCOPY_RECOVER: + case MIGRATION_STATUS_FAILING: continue; default: migration_test_add_suffix("/migration/cancel/src/after/", diff --git a/tests/qtest/qos-test.c b/tests/qtest/qos-test.c index 00f39f33f65d..50fa0ceef36d 100644 --- a/tests/qtest/qos-test.c +++ b/tests/qtest/qos-test.c @@ -31,6 +31,7 @@ #include "libqos/qos_external.h" static char *old_path; +static GSList *path_vecs; /** @@ -182,11 +183,16 @@ static void run_one_test(const void *arg) static void subprocess_run_one_test(const void *arg) { - const gchar *path = arg; - g_test_trap_subprocess(path, 180 * G_USEC_PER_SEC, + char **path_vec = (char **) arg; + gchar *path = g_strjoinv("/", path_vec + 1); + gchar *subprocess_path = g_strdup_printf("/%s/subprocess", path); + + g_test_trap_subprocess(subprocess_path, 180 * G_USEC_PER_SEC, G_TEST_SUBPROCESS_INHERIT_STDOUT | G_TEST_SUBPROCESS_INHERIT_STDERR); g_test_trap_assert_passed(); + g_free(path); + g_free(subprocess_path); } static void destroy_pathv(void *arg) @@ -238,6 +244,7 @@ static void walk_path(QOSGraphNode *orig_path, int len) GString *cmd_line = g_string_new(""); GString *cmd_line2 = g_string_new(""); + path_vecs = g_slist_append(path_vecs, path_vec); path = qos_graph_get_node(node_name); /* root */ node_name = qos_graph_edge_get_dest(path->path_edge); /* machine name */ @@ -297,15 +304,15 @@ static void walk_path(QOSGraphNode *orig_path, int len) path_vec[0] = g_string_free(cmd_line, false); if (path->u.test.subprocess) { - gchar *subprocess_path = g_strdup_printf("/%s/%s/subprocess", - qtest_get_arch(), path_str); - qtest_add_data_func_full(path_str, subprocess_path, - subprocess_run_one_test, g_free); - g_test_add_data_func_full(subprocess_path, path_vec, - run_one_test, destroy_pathv); + gchar *subprocess_path = g_strdup_printf("%s/%s", path_str, + "subprocess"); + + qtest_add_data_func(path_str, path_vec, subprocess_run_one_test); + qtest_add_data_func(subprocess_path, path_vec, run_one_test); + + g_free(subprocess_path); } else { - qtest_add_data_func_full(path_str, path_vec, - run_one_test, destroy_pathv); + qtest_add_data_func(path_str, path_vec, run_one_test); } g_free(path_str); @@ -340,6 +347,14 @@ int main(int argc, char **argv, char** envp) module_call_init(MODULE_INIT_LIBQOS); qos_set_machines_devices_available(); + /* + * Even if this invocation was done to run a single test in a + * subprocess (i.e. g_test_subprocess() is true), gtester doesn't + * expose the test name, so w still need to execute the whole + * thing as normal, including walking the QOS graph to add all + * the tests, in order for g_test_run() to find the one /subprocess + * test that it is going to execute. + */ qos_graph_foreach_test_path(walk_path); if (g_test_verbose()) { qos_dump_graph(); @@ -348,5 +363,6 @@ int main(int argc, char **argv, char** envp) qtest_end(); qos_graph_destroy(); g_free(old_path); + g_slist_free_full(path_vecs, (GDestroyNotify)destroy_pathv); return 0; } diff --git a/tests/qtest/test-x86-cpuid-compat.c b/tests/qtest/test-x86-cpuid-compat.c index 5e0547e81b7b..be67631a85f9 100644 --- a/tests/qtest/test-x86-cpuid-compat.c +++ b/tests/qtest/test-x86-cpuid-compat.c @@ -65,6 +65,8 @@ static void test_cpuid_prop(const void *data) qobject_unref(value); g_free(path); + g_free((void *)args->cmdline); + g_free((void *)data); } static void add_cpuid_test(const char *name, const char *cpu, @@ -161,6 +163,8 @@ static void test_feature_flag(const void *data) qobject_unref(present); qobject_unref(filtered); g_free(path); + g_free((void *)args->cmdline); + g_free((void *)data); } /* @@ -343,17 +347,6 @@ int main(int argc, char **argv) "486", "xlevel2=0xC0000002,xstore=on", NULL, "xlevel2", 0xC0000002); - /* Check compatibility of old machine-types that didn't - * auto-increase level/xlevel/xlevel2: */ - if (qtest_has_machine("pc-i440fx-2.9")) { - add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-2.9/off", - "Conroe", NULL, "pc-i440fx-2.9", - "level", 10); - add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-2.9/on", - "Conroe", "erms=on", "pc-i440fx-2.9", - "level", 10); - } - /* Test feature parsing */ add_feature_test("x86/cpuid/features/plus", "486", "+arat", diff --git a/tests/tcg/arm/Makefile.target b/tests/tcg/arm/Makefile.target index 6189d7a0e243..613bbf0939ad 100644 --- a/tests/tcg/arm/Makefile.target +++ b/tests/tcg/arm/Makefile.target @@ -78,4 +78,10 @@ sha512-vector: sha512.c ARM_TESTS += sha512-vector +ifeq ($(CONFIG_PLUGIN),y) +# Require emitting arm32 instructions, otherwise the vCPU might accidentally +# try to execute Thumb instructions in arm32 mode after qemu_plugin_set_pc() +test-plugin-set-pc: CFLAGS+=-marm +endif + TESTS += $(ARM_TESTS) diff --git a/tests/tcg/hexagon/Makefile.target b/tests/tcg/hexagon/Makefile.target index f86f02bb31c4..a70ef2f6607e 100644 --- a/tests/tcg/hexagon/Makefile.target +++ b/tests/tcg/hexagon/Makefile.target @@ -126,3 +126,11 @@ v73_scalar: CFLAGS += -Wno-unused-function hvx_histogram: hvx_histogram.c hvx_histogram_row.S $(CC) $(CFLAGS) $(CROSS_CC_GUEST_CFLAGS) $^ -o $@ $(LDFLAGS) + +ifeq ($(CONFIG_PLUGIN),y) +# LLVM is way too aggressive with inlining and dead code elimination even at +# -O0, which interferes with the test. What looks like dead code in this test +# to the compiler isn't actually dead code, so we need to disable all potential +# LLVM optimization passes. +test-plugin-set-pc: CFLAGS += -Xclang -disable-llvm-passes +endif diff --git a/tests/tcg/multiarch/Makefile.target b/tests/tcg/multiarch/Makefile.target index 07d0b27bdd35..a347efbadf02 100644 --- a/tests/tcg/multiarch/Makefile.target +++ b/tests/tcg/multiarch/Makefile.target @@ -14,6 +14,10 @@ ifeq ($(filter %-linux-user, $(TARGET)),$(TARGET)) VPATH += $(MULTIARCH_SRC)/linux MULTIARCH_SRCS += $(notdir $(wildcard $(MULTIARCH_SRC)/linux/*.c)) endif +ifeq ($(CONFIG_PLUGIN),y) +VPATH += $(MULTIARCH_SRC)/plugin +MULTIARCH_SRCS += $(notdir $(wildcard $(MULTIARCH_SRC)/plugin/*.c)) +endif MULTIARCH_TESTS = $(MULTIARCH_SRCS:.c=) # @@ -200,13 +204,20 @@ run-plugin-test-plugin-mem-access-with-libmem.so: \ PLUGIN_ARGS=$(COMMA)print-accesses=true run-plugin-test-plugin-mem-access-with-libmem.so: \ CHECK_PLUGIN_OUTPUT_COMMAND= \ - $(SRC_PATH)/tests/tcg/multiarch/check-plugin-output.sh \ + $(SRC_PATH)/tests/tcg/multiarch/plugin/check-plugin-output.sh \ $(QEMU) $< run-plugin-test-plugin-syscall-filter-with-libsyscall.so: +run-plugin-test-plugin-set-pc-with-libsetpc.so: EXTRA_RUNS_WITH_PLUGIN += run-plugin-test-plugin-mem-access-with-libmem.so \ - run-plugin-test-plugin-syscall-filter-with-libsyscall.so -else + run-plugin-test-plugin-syscall-filter-with-libsyscall.so \ + run-plugin-test-plugin-set-pc-with-libsetpc.so + +else # CONFIG_PLUGIN=n +# Do not build the syscall skipping test if it's not tested with the setpc +# plugin because it will simply fail the test. +MULTIARCH_TESTS := $(filter-out test-plugin-set-pc, $(MULTIARCH_TESTS)) + # test-plugin-syscall-filter needs syscall plugin to succeed test-plugin-syscall-filter: CFLAGS+=-DSKIP endif diff --git a/tests/tcg/multiarch/check-plugin-output.sh b/tests/tcg/multiarch/plugin/check-plugin-output.sh similarity index 100% rename from tests/tcg/multiarch/check-plugin-output.sh rename to tests/tcg/multiarch/plugin/check-plugin-output.sh diff --git a/tests/tcg/multiarch/test-plugin-mem-access.c b/tests/tcg/multiarch/plugin/test-plugin-mem-access.c similarity index 100% rename from tests/tcg/multiarch/test-plugin-mem-access.c rename to tests/tcg/multiarch/plugin/test-plugin-mem-access.c diff --git a/tests/tcg/multiarch/plugin/test-plugin-set-pc.c b/tests/tcg/multiarch/plugin/test-plugin-set-pc.c new file mode 100644 index 000000000000..f8343dfba849 --- /dev/null +++ b/tests/tcg/multiarch/plugin/test-plugin-set-pc.c @@ -0,0 +1,134 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2026, Florian Hofhammer + * + * This test set exercises the qemu_plugin_set_pc() function in four different + * contexts: + * 1. in an instruction callback during normal execution, + * 2. in an instruction callback during signal handling, + * 3. in a memory access callback. + * 4. in a syscall callback, + */ +#include +#include +#include +#include +#include +#include + +/* If we issue this magic syscall, ... */ +#define MAGIC_SYSCALL 4096 +/* ... the plugin either jumps directly to the target address ... */ +#define SETPC 0 +/* ... or just updates the target address for future use in callbacks. */ +#define SETTARGET 1 + +static int signal_handled; + +void panic(const char *msg) +{ + fprintf(stderr, "Panic: %s\n", msg); + abort(); +} + +/* + * This test executes a magic syscall which communicates two addresses to the + * plugin via the syscall arguments. Whenever we reach the "bad" instruction + * during normal execution, the plugin should redirect control flow to the + * "good" instruction instead. + */ +void test_insn(void) +{ + long ret = syscall(MAGIC_SYSCALL, SETTARGET, &&bad_insn, &&good_insn, + NULL); + assert(ret == 0 && "Syscall filter did not return expected value"); +bad_insn: + panic("PC redirection in instruction callback failed"); +good_insn: + puts("PC redirection in instruction callback succeeded"); +} + +/* + * This signal handler communicates a "bad" and a "good" address to the plugin + * similar to the previous test, and skips to the "good" address when the "bad" + * one is reached. This serves to test whether PC redirection via + * qemu_plugin_set_pc() also works properly in a signal handler context. + */ +void usr1_handler(int signum) +{ + long ret = syscall(MAGIC_SYSCALL, SETTARGET, &&bad_signal, &&good_signal, + NULL); + assert(ret == 0 && "Syscall filter did not return expected value"); +bad_signal: + panic("PC redirection in instruction callback failed"); +good_signal: + signal_handled = 1; + puts("PC redirection in instruction callback succeeded"); +} + +/* + * This test sends a signal to the process, which should trigger the above + * signal handler. The signal handler should then exercise the PC redirection + * functionality in the context of a signal handler, which behaves a bit + * differently from normal execution. + */ +void test_sighandler(void) +{ + struct sigaction sa = {0}; + sa.sa_handler = usr1_handler; + sigaction(SIGUSR1, &sa, NULL); + pid_t pid = getpid(); + kill(pid, SIGUSR1); + assert(signal_handled == 1 && "Signal handler was not executed properly"); +} + +/* + * This test communicates a "good" address and the address of a local variable + * to the plugin. Upon accessing the local variable, the plugin should then + * redirect control flow to the "good" address via qemu_plugin_set_pc(). + */ +void test_mem(void) +{ + static uint32_t test = 1; + long ret = syscall(MAGIC_SYSCALL, SETTARGET, NULL, &&good_mem, &test); + assert(ret == 0 && "Syscall filter did not return expected value"); + /* Ensure read access to the variable to trigger the plugin callback */ + assert(test == 1); + panic("PC redirection in memory access callback failed"); +good_mem: + puts("PC redirection in memory access callback succeeded"); +} + +/* + * This test executes a magic syscall which is intercepted and its actual + * execution skipped via the qemu_plugin_set_pc() API. In a proper plugin, + * syscall skipping would rather be implemented via the syscall filtering + * callback, but we want to make sure qemu_plugin_set_pc() works in different + * contexts. + */ +__attribute__((noreturn)) +void test_syscall(void) +{ + syscall(MAGIC_SYSCALL, SETPC, &&good_syscall); + panic("PC redirection in syscall callback failed"); +good_syscall: + /* + * Note: we execute this test last and exit straight from here because when + * the plugin redirects control flow upon syscall, the stack frame for the + * syscall function (and potential other functions in the call chain in + * libc) is still live and the stack is not unwound properly. Thus, + * returning from here is risky and breaks on some architectures, so we + * just exit directly from this test. + */ + _exit(EXIT_SUCCESS); +} + + +int main(int argc, char *argv[]) +{ + test_insn(); + test_sighandler(); + test_mem(); + test_syscall(); +} diff --git a/tests/tcg/multiarch/test-mmap.c b/tests/tcg/multiarch/test-mmap.c index e297f4b1e932..2bfa52912704 100644 --- a/tests/tcg/multiarch/test-mmap.c +++ b/tests/tcg/multiarch/test-mmap.c @@ -442,19 +442,19 @@ void check_invalid_mmaps(void) /* Attempt to map a zero length page. */ addr = mmap(NULL, 0, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - fprintf(stdout, "%s addr=%p", __func__, (void *)addr); + fprintf(stdout, "%s addr=%p errno=%d\n", __func__, (void *)addr, errno); fail_unless(addr == MAP_FAILED); fail_unless(errno == EINVAL); /* Attempt to map a over length page. */ addr = mmap(NULL, -4, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - fprintf(stdout, "%s addr=%p", __func__, (void *)addr); + fprintf(stdout, "%s addr=%p errno=%d\n", __func__, (void *)addr, errno); fail_unless(addr == MAP_FAILED); fail_unless(errno == ENOMEM); /* Attempt to remap a region which exceeds the bounds of memory. */ addr = mremap((void *)((uintptr_t)pagesize * 10), SIZE_MAX & ~(size_t)pagemask, pagesize, 0); - fprintf(stdout, "%s mremap addr=%p", __func__, (void *)addr); + fprintf(stdout, "%s mremap addr=%p errno=%d\n", __func__, (void *)addr, errno); fail_unless(addr == MAP_FAILED); fail_unless(errno == EFAULT); @@ -465,8 +465,11 @@ void check_shrink_mmaps(void) { unsigned char *a, *b, *c; a = mmap(NULL, pagesize * 2, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + fprintf(stdout, "%s addr=%p errno=%d\n", __func__, (void *)a, errno); b = mmap(NULL, pagesize * 2, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + fprintf(stdout, "%s addr=%p errno=%d\n", __func__, (void *)b, errno); c = mmap(NULL, pagesize * 2, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + fprintf(stdout, "%s addr=%p errno=%d\n", __func__, (void *)c, errno); fail_unless(a != MAP_FAILED); fail_unless(b != MAP_FAILED); @@ -479,6 +482,7 @@ void check_shrink_mmaps(void) /* Shrink the middle mapping in-place; the others should be unaffected */ b = mremap(b, pagesize * 2, pagesize, 0); + fprintf(stdout, "%s mremap addr=%p errno=%d\n", __func__, (void *)b, errno); fail_unless(b != MAP_FAILED); /* Ensure we can still access all valid mappings */ @@ -489,6 +493,22 @@ void check_shrink_mmaps(void) munmap(a, 2 * pagesize); munmap(b, pagesize); munmap(c, 2 * pagesize); + + fprintf(stdout, " passed\n"); +} + +void check_mmaps_beyond_addr_space(void) +{ + unsigned char *addr; + addr = mmap((void *)(-(unsigned long)pagesize * 10), pagesize * 2, + PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + fprintf(stdout, "%s addr=%p errno=%d", __func__, (void *)addr, errno); + fail_unless(addr != MAP_FAILED); + + memcpy(dummybuf, addr, 2 * pagesize); + munmap(addr, 2 * pagesize); + + fprintf(stdout, " passed\n"); } int main(int argc, char **argv) @@ -534,6 +554,7 @@ int main(int argc, char **argv) check_file_unfixed_eof_mmaps(); check_invalid_mmaps(); check_shrink_mmaps(); + check_mmaps_beyond_addr_space(); /* Fails at the moment. */ /* check_aligned_anonymous_fixed_mmaps_collide_with_host(); */ diff --git a/tests/tcg/plugins/mem.c b/tests/tcg/plugins/mem.c index 7d64e7018f25..1ee257f855be 100644 --- a/tests/tcg/plugins/mem.c +++ b/tests/tcg/plugins/mem.c @@ -84,24 +84,22 @@ static void plugin_exit(qemu_plugin_id_t id, void *p) if (do_region_summary) { - GList *counts = g_hash_table_get_values(regions); + g_autoptr(GList) regionlist = g_hash_table_get_values(regions); - counts = g_list_sort_with_data(counts, addr_order, NULL); + regionlist = g_list_sort_with_data(regionlist, addr_order, NULL); g_string_printf(out, "Region Base, Reads, Writes, Seen all\n"); - if (counts && g_list_next(counts)) { - for (/* counts */; counts; counts = counts->next) { - RegionInfo *ri = (RegionInfo *) counts->data; - - g_string_append_printf(out, - "0x%016"PRIx64", " - "%"PRId64", %"PRId64", %s\n", - ri->region_address, - ri->reads, - ri->writes, - ri->seen_all ? "true" : "false"); - } + for (GList *l = regionlist; l; l = g_list_next(l)) { + RegionInfo *ri = (RegionInfo *) l->data; + + g_string_append_printf(out, + "0x%016"PRIx64", " + "%"PRId64", %"PRId64", %s\n", + ri->region_address, + ri->reads, + ri->writes, + ri->seen_all ? "true" : "false"); } qemu_plugin_outs(out->str); } @@ -123,6 +121,9 @@ static void update_region_info(uint64_t region, uint64_t offset, bool is_store = qemu_plugin_mem_is_store(meminfo); RegionInfo *ri; bool unseen_data = false; + void *val_ptr; + unsigned int val_size; + qemu_plugin_mem_value swapped_value; g_assert(offset + size <= region_size); @@ -144,61 +145,46 @@ static void update_region_info(uint64_t region, uint64_t offset, } void *ri_data = &ri->data[offset]; + + swapped_value.type = value.type; switch (value.type) { case QEMU_PLUGIN_MEM_VALUE_U8: - { - uint8_t val = value.data.u8; - uint8_t *p = ri_data; - if (is_store) { - *p = val; - } else { - unseen_data = *p != val; - } + swapped_value.data.u8 = value.data.u8; + val_ptr = &swapped_value.data.u8; + val_size = 1; break; - } case QEMU_PLUGIN_MEM_VALUE_U16: - { - uint16_t val = be ? GUINT16_FROM_BE(value.data.u16) : - GUINT16_FROM_LE(value.data.u16); - uint16_t *p = ri_data; - if (is_store) { - *p = val; - } else { - unseen_data = *p != val; - } + swapped_value.data.u16 = be ? GUINT16_FROM_BE(value.data.u16) : + GUINT16_FROM_LE(value.data.u16); + val_ptr = &swapped_value.data.u16; + val_size = 2; break; - } case QEMU_PLUGIN_MEM_VALUE_U32: - { - uint32_t val = be ? GUINT32_FROM_BE(value.data.u32) : - GUINT32_FROM_LE(value.data.u32); - uint32_t *p = ri_data; - if (is_store) { - *p = val; - } else { - unseen_data = *p != val; - } + swapped_value.data.u32 = be ? GUINT32_FROM_BE(value.data.u32) : + GUINT32_FROM_LE(value.data.u32); + val_ptr = &swapped_value.data.u32; + val_size = 4; break; - } case QEMU_PLUGIN_MEM_VALUE_U64: - { - uint64_t val = be ? GUINT64_FROM_BE(value.data.u64) : - GUINT64_FROM_LE(value.data.u64); - uint64_t *p = ri_data; - if (is_store) { - *p = val; - } else { - unseen_data = *p != val; - } + swapped_value.data.u64 = be ? GUINT64_FROM_BE(value.data.u64) : + GUINT64_FROM_LE(value.data.u64); + val_ptr = &swapped_value.data.u64; + val_size = 8; break; - } case QEMU_PLUGIN_MEM_VALUE_U128: - /* non in test so skip */ - break; + /* none in test so skip */ + goto done; default: g_assert_not_reached(); } + /* ri_data may not be aligned, so we use memcpy/memcmp */ + if (is_store) { + memcpy(ri_data, val_ptr, val_size); + } else { + unseen_data = memcmp(ri_data, val_ptr, val_size) != 0; + } + /* * This is expected for regions initialised by QEMU (.text etc) but we * expect to see all data read and written to the test_data region @@ -213,6 +199,7 @@ static void update_region_info(uint64_t region, uint64_t offset, ri->seen_all = false; } +done: g_mutex_unlock(&lock); } diff --git a/tests/tcg/plugins/meson.build b/tests/tcg/plugins/meson.build index c5e49753fd9d..d7f8f0ae0ad4 100644 --- a/tests/tcg/plugins/meson.build +++ b/tests/tcg/plugins/meson.build @@ -6,7 +6,9 @@ test_plugins = [ 'insn.c', 'mem.c', 'patch.c', +'registers.c', 'reset.c', +'setpc.c', 'syscall.c', ] diff --git a/tests/tcg/plugins/patch.c b/tests/tcg/plugins/patch.c index 111c5c1f1698..eba2f8b8d6c1 100644 --- a/tests/tcg/plugins/patch.c +++ b/tests/tcg/plugins/patch.c @@ -65,7 +65,7 @@ static void patch_hwaddr(unsigned int vcpu_index, void *userdata) return; } - GByteArray *read_data = g_byte_array_new(); + g_autoptr(GByteArray) read_data = g_byte_array_new(); result = qemu_plugin_read_memory_hwaddr(addr, read_data, patch_data->len); diff --git a/tests/tcg/plugins/registers.c b/tests/tcg/plugins/registers.c new file mode 100644 index 000000000000..6d627c703714 --- /dev/null +++ b/tests/tcg/plugins/registers.c @@ -0,0 +1,79 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2026, Florian Hofhammer + */ +#include "glib.h" +#include +#include +#include +#include +#include +#include + +#include + +QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; + +/* + * This plugin tests whether we can read and write registers via the plugin + * API. We try to just read/write a single register, as some architectures have + * registers that cannot be written to, which would fail the test. + * See: https://lists.gnu.org/archive/html/qemu-devel/2026-02/msg07025.html + */ +static void vcpu_init_cb(qemu_plugin_id_t id, unsigned int vcpu_index) +{ + g_autoptr(GArray) regs = qemu_plugin_get_registers(); + g_assert(regs != NULL); + g_autoptr(GByteArray) buf = g_byte_array_sized_new(0); + qemu_plugin_reg_descriptor *reg_desc = NULL; + bool success = false; + + /* Make sure we can read and write a register not marked as readonly */ + for (size_t i = 0; i < regs->len; i++) { + reg_desc = &g_array_index(regs, qemu_plugin_reg_descriptor, i); + if (!reg_desc->is_readonly) { + g_byte_array_set_size(buf, 0); + success = qemu_plugin_read_register(reg_desc->handle, buf); + g_assert(success); + g_assert(buf->len > 0); + success = qemu_plugin_write_register(reg_desc->handle, buf); + g_assert(success); + break; + } else { + reg_desc = NULL; + } + } + g_assert(regs->len == 0 || reg_desc != NULL); + + /* + * Check whether we can still read a read-only register. On each + * architecture, at least the PC should be read-only because it's only + * supposed to be modified via the qemu_plugin_set_pc() function. + */ + for (size_t i = 0; i < regs->len; i++) { + reg_desc = &g_array_index(regs, qemu_plugin_reg_descriptor, i); + if (reg_desc->is_readonly) { + g_byte_array_set_size(buf, 0); + success = qemu_plugin_read_register(reg_desc->handle, buf); + g_assert(success); + g_assert(buf->len > 0); + break; + } else { + reg_desc = NULL; + } + } + g_assert(regs->len == 0 || reg_desc != NULL); + /* + * Note: we currently do not test whether the read-only register can be + * written to, because doing so would throw an assert in the plugin API. + */ +} + +QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id, + const qemu_info_t *info, + int argc, char **argv) +{ + qemu_plugin_register_vcpu_init_cb(id, vcpu_init_cb); + return 0; +} diff --git a/tests/tcg/plugins/setpc.c b/tests/tcg/plugins/setpc.c new file mode 100644 index 000000000000..8f2d025e245a --- /dev/null +++ b/tests/tcg/plugins/setpc.c @@ -0,0 +1,105 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2026, Florian Hofhammer + */ +#include +#include +#include +#include + +#include + +/* If we detect this magic syscall, ... */ +#define MAGIC_SYSCALL 4096 +/* ... the plugin either jumps directly to the target address ... */ +#define SETPC 0 +/* ... or just updates the target address for future use in callbacks. */ +#define SETTARGET 1 + +QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; + +static uint64_t source_pc; +static uint64_t target_pc; +static uint64_t target_vaddr; + +static bool vcpu_syscall_filter(qemu_plugin_id_t id, unsigned int vcpu_index, + int64_t num, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5, + uint64_t a6, uint64_t a7, uint64_t a8, + uint64_t *sysret) +{ + if (num == MAGIC_SYSCALL) { + if (a1 == SETPC) { + qemu_plugin_outs("Magic syscall detected, jump to clean exit\n"); + qemu_plugin_set_pc(a2); + } else if (a1 == SETTARGET) { + qemu_plugin_outs("Magic syscall detected, set target_pc / " + "target_vaddr\n"); + source_pc = a2; + target_pc = a3; + target_vaddr = a4; + *sysret = 0; + return true; + } else { + qemu_plugin_outs("Unknown magic syscall argument, ignoring\n"); + } + } + return false; +} + +static void vcpu_insn_exec(unsigned int vcpu_index, void *userdata) +{ + uint64_t vaddr = (uint64_t)userdata; + if (vaddr == source_pc) { + g_assert(target_pc != 0); + g_assert(target_vaddr == 0); + + qemu_plugin_outs("Marker insn detected, jump to clean return\n"); + qemu_plugin_set_pc(target_pc); + } +} + +static void vcpu_mem_access(unsigned int vcpu_index, + qemu_plugin_meminfo_t info, + uint64_t vaddr, void *userdata) +{ + if (vaddr != 0 && vaddr == target_vaddr) { + g_assert(source_pc == 0); + g_assert(target_pc != 0); + + qemu_plugin_outs("Marker mem access detected, jump to clean return\n"); + qemu_plugin_set_pc(target_pc); + } +} + +static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb) +{ + size_t insns = qemu_plugin_tb_n_insns(tb); + for (size_t i = 0; i < insns; i++) { + struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i); + uint64_t insn_vaddr = qemu_plugin_insn_vaddr(insn); + /* + * Note: we cannot only register the callbacks if the instruction is + * in one of the functions of interest, because symbol lookup for + * filtering does not work for all architectures (e.g., ppc64). + */ + qemu_plugin_register_vcpu_insn_exec_cb(insn, vcpu_insn_exec, + QEMU_PLUGIN_CB_RW_REGS_PC, + (void *)insn_vaddr); + qemu_plugin_register_vcpu_mem_cb(insn, vcpu_mem_access, + QEMU_PLUGIN_CB_RW_REGS_PC, + QEMU_PLUGIN_MEM_R, NULL); + } +} + + +QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id, + const qemu_info_t *info, + int argc, char **argv) +{ + + qemu_plugin_register_vcpu_syscall_filter_cb(id, vcpu_syscall_filter); + qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans); + return 0; +} diff --git a/tests/tcg/riscv64/Makefile.softmmu-target b/tests/tcg/riscv64/Makefile.softmmu-target index eb1ce6504a0b..82be8a2c915c 100644 --- a/tests/tcg/riscv64/Makefile.softmmu-target +++ b/tests/tcg/riscv64/Makefile.softmmu-target @@ -36,5 +36,10 @@ run-plugin-interruptedmemory: interruptedmemory $(QEMU) -plugin ../plugins/libdiscons.so -d plugin -D $<.pout \ $(QEMU_OPTS)$<) +EXTRA_RUNS += run-test-crc32 +comma:= , +run-test-crc32: test-crc32 + $(call run-test, $<, $(QEMU) -cpu rv64$(comma)xlrbr=true $(QEMU_OPTS)$<) + # We don't currently support the multiarch system tests undefine MULTIARCH_TESTS diff --git a/tests/tcg/riscv64/test-crc32.S b/tests/tcg/riscv64/test-crc32.S new file mode 100644 index 000000000000..70d70b16a95a --- /dev/null +++ b/tests/tcg/riscv64/test-crc32.S @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2026 lowRISC CIC + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#define crc32(op, rd, rs1) .insn r 19, 1, 48, rd, rs1, x##op + +#define crc32_b(rd, rs1) crc32(16, rd, rs1) +#define crc32_h(rd, rs1) crc32(17, rd, rs1) +#define crc32_w(rd, rs1) crc32(18, rd, rs1) +#define crc32_d(rd, rs1) crc32(19, rd, rs1) +#define crc32c_b(rd, rs1) crc32(24, rd, rs1) +#define crc32c_h(rd, rs1) crc32(25, rd, rs1) +#define crc32c_w(rd, rs1) crc32(26, rd, rs1) +#define crc32c_d(rd, rs1) crc32(27, rd, rs1) + + .option norvc + + .text + .globl _start +_start: + lla t0, trap + csrw mtvec, t0 + + li t0, 0x34e24a2cd65650d4 + + crc32_b (t0, t0) + crc32_h (t0, t0) + crc32_w (t0, t0) + crc32_d (t0, t0) + crc32c_b (t0, t0) + crc32c_h (t0, t0) + crc32c_w (t0, t0) + crc32c_d (t0, t0) + + li t1, 0x68167e78 + + li a0, 0 + beq t0, t1, _exit +fail: + li a0, 1 +_exit: + lla a1, semiargs + li t0, 0x20026 # ADP_Stopped_ApplicationExit + sd t0, 0(a1) + sd a0, 8(a1) + li a0, 0x20 # TARGET_SYS_EXIT_EXTENDED + + # Semihosting call sequence + .balign 16 + slli zero, zero, 0x1f + ebreak + srai zero, zero, 0x7 + j . + + .data + .balign 16 +semiargs: + .space 16 + +trap: + csrr t0, mepc + addi t0, t0, 4 + mret diff --git a/tests/unit/test-io-task.c b/tests/unit/test-io-task.c index 115dba89702b..b1c8ecb7abbd 100644 --- a/tests/unit/test-io-task.c +++ b/tests/unit/test-io-task.c @@ -73,6 +73,7 @@ static void test_task_complete(void) src = qio_task_get_source(task); qio_task_complete(task); + qio_task_free(task); g_assert(obj == src); @@ -84,6 +85,28 @@ static void test_task_complete(void) } +static void test_task_cancel(void) +{ + QIOTask *task; + Object *obj = object_new(TYPE_DUMMY); + Object *src; + struct TestTaskData data = { NULL, NULL, false }; + + task = qio_task_new(obj, task_callback, &data, NULL); + src = qio_task_get_source(task); + + qio_task_free(task); + + g_assert(obj == src); + + object_unref(obj); + + g_assert(data.source == NULL); + g_assert(data.err == NULL); + g_assert(data.freed == false); +} + + static void task_data_free(gpointer opaque) { struct TestTaskData *data = opaque; @@ -101,6 +124,7 @@ static void test_task_data_free(void) task = qio_task_new(obj, task_callback, &data, task_data_free); qio_task_complete(task); + qio_task_free(task); object_unref(obj); @@ -123,6 +147,7 @@ static void test_task_failure(void) qio_task_set_error(task, err); qio_task_complete(task); + qio_task_free(task); object_unref(obj); @@ -260,6 +285,7 @@ int main(int argc, char **argv) module_call_init(MODULE_INIT_QOM); type_register_static(&dummy_info); g_test_add_func("/crypto/task/complete", test_task_complete); + g_test_add_func("/crypto/task/cancel", test_task_cancel); g_test_add_func("/crypto/task/datafree", test_task_data_free); g_test_add_func("/crypto/task/failure", test_task_failure); g_test_add_func("/crypto/task/thread_complete", test_task_thread_complete); diff --git a/tests/unit/test-qobject-input-visitor.c b/tests/unit/test-qobject-input-visitor.c index 84bdcdf702e0..beee11db4e47 100644 --- a/tests/unit/test-qobject-input-visitor.c +++ b/tests/unit/test-qobject-input-visitor.c @@ -500,7 +500,7 @@ static void test_visitor_in_list_struct(TestInputVisitorData *data, g_string_append_printf(json, "'number': ["); sep = ""; for (i = 0; i < 32; i++) { - g_string_append_printf(json, "%s%f", sep, (double)i / 3); + g_string_append_printf(json, "%s%f", sep, (double)i / FLT_RADIX); sep = ", "; } g_string_append_printf(json, "], "); @@ -583,11 +583,7 @@ static void test_visitor_in_list_struct(TestInputVisitorData *data, i = 0; for (num_list = arrs->number; num_list; num_list = num_list->next) { - char expected[32], actual[32]; - - sprintf(expected, "%.6f", (double)i / 3); - sprintf(actual, "%.6f", num_list->value); - g_assert_cmpstr(expected, ==, actual); + g_assert_cmpfloat(num_list->value, ==, (double)i / FLT_RADIX); i++; } diff --git a/tests/unit/test-qobject-output-visitor.c b/tests/unit/test-qobject-output-visitor.c index 407ab9ed505a..3c47b28f6638 100644 --- a/tests/unit/test-qobject-output-visitor.c +++ b/tests/unit/test-qobject-output-visitor.c @@ -538,7 +538,7 @@ static void test_visitor_out_list_struct(TestOutputVisitorData *data, } for (i = 31; i >= 0; i--) { - QAPI_LIST_PREPEND(arrs->number, (double)i / 3); + QAPI_LIST_PREPEND(arrs->number, (double)i / FLT_RADIX); } for (i = 31; i >= 0; i--) { @@ -571,12 +571,9 @@ static void test_visitor_out_list_struct(TestOutputVisitorData *data, i = 0; QLIST_FOREACH_ENTRY(qlist, e) { QNum *qvalue = qobject_to(QNum, qlist_entry_obj(e)); - char expected[32], actual[32]; g_assert(qvalue); - sprintf(expected, "%.6f", (double)i / 3); - sprintf(actual, "%.6f", qnum_get_double(qvalue)); - g_assert_cmpstr(actual, ==, expected); + g_assert_cmpfloat(qnum_get_double(qvalue), ==, (double)i / FLT_RADIX); i++; } diff --git a/tests/unit/test-util-sockets.c b/tests/unit/test-util-sockets.c index ee66d727c388..b9f2453e299f 100644 --- a/tests/unit/test-util-sockets.c +++ b/tests/unit/test-util-sockets.c @@ -74,6 +74,7 @@ int monitor_get_fd(Monitor *mon, const char *fdname, Error **errp) Monitor *monitor_cur(void) { return cur_mon; } Monitor *monitor_set_cur(Coroutine *co, Monitor *mon) { abort(); } int monitor_vprintf(Monitor *mon, const char *fmt, va_list ap) { abort(); } +bool monitor_cur_is_qmp(void) { abort(); }; #ifndef _WIN32 static void test_socket_fd_pass_name_good(void) diff --git a/tests/vm/Makefile.include b/tests/vm/Makefile.include index 14188bba1c64..0a53dc9fdd61 100644 --- a/tests/vm/Makefile.include +++ b/tests/vm/Makefile.include @@ -2,13 +2,25 @@ # Hack to allow running in an unconfigured build tree ifeq ($(realpath $(SRC_PATH)),$(realpath .)) -VM_PYTHON = PYTHONPATH=$(SRC_PATH)/python /usr/bin/env python3 -VM_VENV = +VM_PYTHON = $(SRC_PATH)/vm-venv/bin/python3 +VM_VENV = vm-venv else VM_PYTHON = $(PYTHON) -VM_VENV = check-venv +VM_VENV = endif +VM_VENV_TOKEN=$(SRC_PATH)/vm-venv/tooling.group $(SRC_PATH)/vm-venv/functests.group + +$(VM_VENV_TOKEN): $(SRC_PATH)/pythondeps.toml + /usr/bin/env python3 $(SRC_PATH)/python/scripts/mkvenv.py create vm-venv + $(SRC_PATH)/vm-venv/bin/python3 \ + $(SRC_PATH)/python/scripts/mkvenv.py ensuregroup --online \ + $(SRC_PATH)/pythondeps.toml tooling functests + +.PHONY: vm-venv +vm-venv: $(VM_VENV_TOKEN) +# end hackery + .PHONY: vm-build-all vm-clean-all EFI_AARCH64 = $(wildcard $(BUILD_DIR)/pc-bios/edk2-aarch64-code.fd) @@ -102,7 +114,7 @@ $(IMAGES_DIR)/%.img: $(SRC_PATH)/tests/vm/% \ $(if $(LOG_CONSOLE),--log-console) \ --source-path $(SRC_PATH) \ --image "$@" \ - $(if $(filter-out check-venv, $?), --force) \ + --force \ --build-image $@, \ " VM-IMAGE $*") @@ -142,6 +154,6 @@ vm-boot-ssh-%: $(IMAGES_DIR)/%.img $(VM_VENV) $(if $(EFI_AARCH64),--efi-aarch64 $(EFI_AARCH64)) \ $(if $(LOG_CONSOLE),--log-console) \ --image "$<" \ - $(if $(ROOT_USER),--interactive-root,-interactive) \ + $(if $(ROOT_USER),--interactive-root,--interactive) \ false, \ " VM-BOOT-SSH $*") || true diff --git a/tests/vm/generated/freebsd.json b/tests/vm/generated/freebsd.json index f586827b1364..08b6eb615533 100644 --- a/tests/vm/generated/freebsd.json +++ b/tests/vm/generated/freebsd.json @@ -57,9 +57,11 @@ "py311-pillow", "py311-pip", "py311-pyyaml", + "py311-setuptools", "py311-sphinx", "py311-sphinx_rtd_theme", "py311-tomli", + "py311-wheel", "python3", "rpm2cpio", "rust", diff --git a/tests/vm/generated/openbsd.json b/tests/vm/generated/openbsd.json new file mode 100644 index 000000000000..0886afd0d3d1 --- /dev/null +++ b/tests/vm/generated/openbsd.json @@ -0,0 +1,27 @@ +{ + "ccache": "/usr/local/bin/ccache", + "cpan_pkgs": [], + "cross_pkgs": [], + "make": "/usr/local/bin/gmake", + "ninja": "/usr/local/bin/ninja", + "packaging_command": "pkg_add", + "pip3": "/usr/local/bin/pip3", + "pkgs": [ + "bash", + "bison", + "bzip2", + "ccache", + "dtc", + "git", + "glib2", + "gmake", + "gsed", + "libffi", + "meson", + "ninja", + "pkgconf", + "python3" + ], + "pypi_pkgs": [], + "python": "/usr/local/bin/python3" +} diff --git a/tests/vm/haiku.x86_64 b/tests/vm/haiku.x86_64 index 529283c39f66..3ea48dc38a1d 100755 --- a/tests/vm/haiku.x86_64 +++ b/tests/vm/haiku.x86_64 @@ -80,6 +80,8 @@ class HaikuVM(basevm.BaseVM): "ninja", "pip", "tomli_python310", + "wheel_python310", + "setuptools_python310", ] BUILD_SCRIPT = """ diff --git a/tests/vm/netbsd b/tests/vm/netbsd index 77d17a0dedf2..b365045066c2 100755 --- a/tests/vm/netbsd +++ b/tests/vm/netbsd @@ -31,6 +31,7 @@ class NetBSDVM(basevm.BaseVM): "pkgconf", "xz", "python313", + "py313-wheel", "ninja-build", # gnu tools diff --git a/tests/vm/openbsd b/tests/vm/openbsd index 2ea86a01bad3..4f8eeb28e242 100755 --- a/tests/vm/openbsd +++ b/tests/vm/openbsd @@ -14,7 +14,6 @@ import os import sys -import socket import subprocess import basevm @@ -22,45 +21,9 @@ class OpenBSDVM(basevm.BaseVM): name = "openbsd" arch = "x86_64" - link = "https://cdn.openbsd.org/pub/OpenBSD/7.7/amd64/install77.iso" - csum = "da0106e39463f015524dca806f407c37a9bdd17e6dfffe533b06a2dd2edd8a27" + link = "https://cdn.openbsd.org/pub/OpenBSD/7.8/amd64/install78.iso" + csum = "a228d0a1ef558b4d9ec84c698f0d3ffd13cd38c64149487cba0f1ad873be07b2" size = "20G" - pkgs = [ - # tools - "dtc", - "git", - "pkgconf", - "bzip2", "xz", - "ninja", - - # gnu tools - "bash", - "gmake", - "gsed", - "gettext-tools", - - # libs: usb - "libusb1--", - - # libs: crypto - "gnutls", - - # libs: images - "jpeg", - "png", - - # libs: ui - "capstone", - "sdl2", - "gtk+3", - "libxkbcommon", - - # libs: migration - "zstd", - - # libs: networking - "libslirp", - ] BUILD_SCRIPT = """ set -e; @@ -196,8 +159,9 @@ class OpenBSDVM(basevm.BaseVM): self.console_wait("login:") self.wait_ssh() + pkgs = self.get_qemu_packages_from_lcitool_json() self.print_step("Installing packages") - self.ssh_root_check("pkg_add %s\n" % " ".join(self.pkgs)) + self.ssh_root_check("pkg_add %s\n" % " ".join(pkgs)) # shutdown self.ssh_root(self.poweroff) diff --git a/ui/cocoa.m b/ui/cocoa.m index 23b7a736d700..9093d1e408ff 100644 --- a/ui/cocoa.m +++ b/ui/cocoa.m @@ -627,7 +627,10 @@ - (void) resizeWindow [[self window] setContentAspectRatio:NSMakeSize(screen.width, screen.height)]; if (!([[self window] styleMask] & NSWindowStyleMaskResizable)) { - [[self window] setContentSize:NSMakeSize(screen.width, screen.height)]; + CGFloat width = screen.width / [[self window] backingScaleFactor]; + CGFloat height = screen.height / [[self window] backingScaleFactor]; + + [[self window] setContentSize:NSMakeSize(width, height)]; [[self window] center]; } else if ([[self window] styleMask] & NSWindowStyleMaskFullScreen) { [[self window] setContentSize:[self fixAspectRatio:[self screenSafeAreaSize]]]; @@ -685,8 +688,8 @@ - (void) updateUIInfoLocked info.xoff = 0; info.yoff = 0; - info.width = frameSize.width; - info.height = frameSize.height; + info.width = frameSize.width * [[self window] backingScaleFactor]; + info.height = frameSize.height * [[self window] backingScaleFactor]; dpy_set_ui_info(dcl.con, &info, TRUE); } @@ -1849,7 +1852,7 @@ static void addRemovableDevicesMenuItems(void) BlockInfoList *currentDevice, *pointerToFree; NSString *deviceName; - currentDevice = qmp_query_block(NULL); + currentDevice = qmp_query_block(false, false, NULL); pointerToFree = currentDevice; menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu]; diff --git a/ui/dbus-listener.c b/ui/dbus-listener.c index 52e041edb0f7..3e2b4adf41fa 100644 --- a/ui/dbus-listener.c +++ b/ui/dbus-listener.c @@ -815,8 +815,7 @@ static void ddl_scanout(DBusDisplayListener *ddl) qemu_dbus_display1_listener_call_scanout( ddl->proxy, surface_width(ddl->ds), surface_height(ddl->ds), surface_stride(ddl->ds), surface_format(ddl->ds), v_data, - G_DBUS_CALL_FLAGS_NONE, DBUS_DEFAULT_TIMEOUT, NULL, NULL, - g_object_ref(ddl)); + G_DBUS_CALL_FLAGS_NONE, DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL); } static void dbus_gfx_update(DisplayChangeListener *dcl, @@ -961,19 +960,19 @@ dbus_display_listener_dispose(GObject *object) g_clear_object(&ddl->conn); g_clear_pointer(&ddl->bus_name, g_free); g_clear_object(&ddl->proxy); -#ifdef WIN32 g_clear_object(&ddl->map_proxy); +#ifdef WIN32 g_clear_object(&ddl->d3d11_proxy); g_clear_pointer(&ddl->peer_process, CloseHandle); -#ifdef CONFIG_PIXMAN - pixman_region32_fini(&ddl->gl_damage); -#endif #ifdef CONFIG_OPENGL egl_fb_destroy(&ddl->fb); #endif #else /* !WIN32 */ g_clear_object(&ddl->scanout_dmabuf_v2_proxy); #endif +#ifdef CONFIG_PIXMAN + pixman_region32_fini(&ddl->gl_damage); +#endif G_OBJECT_CLASS(dbus_display_listener_parent_class)->dispose(object); } diff --git a/ui/dbus.c b/ui/dbus.c index 31f6eb1189fe..4f24215555a4 100644 --- a/ui/dbus.c +++ b/ui/dbus.c @@ -35,6 +35,7 @@ #include "ui/egl-context.h" #endif #include "qemu/audio.h" +#include "audio/audio_int.h" /* FIXME: use QOM dynamic cast instead of drv->name */ #include "qapi/error.h" #include "trace.h" @@ -46,9 +47,7 @@ static DBusDisplay *dbus_display; static QEMUGLContext dbus_create_context(DisplayGLCtx *dgc, QEMUGLParams *params) { - eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, - qemu_egl_rn_ctx); - return qemu_egl_create_context(dgc, params); + return qemu_egl_create_context(dgc, params, qemu_egl_rn_ctx); } static bool @@ -218,9 +217,18 @@ dbus_display_complete(UserCreatable *uc, Error **errp) return; } - if (dd->audiodev && *dd->audiodev) { - AudioBackend *audio_be = audio_be_by_name(dd->audiodev, errp); - if (!audio_be || !audio_be_set_dbus_server(audio_be, dd->server, dd->p2p, errp)) { + { + AudioBackend *audio_be = audio_get_default_audio_be(NULL); + if (audio_be && !audio_be_can_set_dbus_server(audio_be)) { + audio_be = NULL; + } + if (dd->audiodev && *dd->audiodev) { + audio_be = audio_be_by_name(dd->audiodev, errp); + if (!audio_be) { + return; + } + } + if (audio_be && !audio_be_set_dbus_server(audio_be, dd->server, dd->p2p, errp)) { return; } } @@ -475,6 +483,8 @@ early_dbus_init(DisplayOptions *opts) #endif } + using_dbus_display = 1; + type_register_static(&dbus_vc_type_info); } @@ -488,8 +498,6 @@ dbus_init(DisplayState *ds, DisplayOptions *opts) exit(1); } - using_dbus_display = 1; - object_new_with_props(TYPE_DBUS_DISPLAY, object_get_objects_root(), "dbus-display", &error_fatal, diff --git a/ui/egl-context.c b/ui/egl-context.c index aed3e3ba1f3f..5787199347bc 100644 --- a/ui/egl-context.c +++ b/ui/egl-context.c @@ -3,7 +3,8 @@ #include "ui/egl-context.h" QEMUGLContext qemu_egl_create_context(DisplayGLCtx *dgc, - QEMUGLParams *params) + QEMUGLParams *params, + EGLContext share_context) { EGLContext ctx; EGLint ctx_att_core[] = { @@ -19,8 +20,7 @@ QEMUGLContext qemu_egl_create_context(DisplayGLCtx *dgc, }; bool gles = (qemu_egl_mode == DISPLAY_GL_MODE_ES); - ctx = eglCreateContext(qemu_egl_display, qemu_egl_config, - eglGetCurrentContext(), + ctx = eglCreateContext(qemu_egl_display, qemu_egl_config, share_context, gles ? ctx_att_gles : ctx_att_core); return ctx; } diff --git a/ui/egl-headless.c b/ui/egl-headless.c index 1f6b845500dd..352b30b43fb9 100644 --- a/ui/egl-headless.c +++ b/ui/egl-headless.c @@ -42,9 +42,7 @@ static void egl_gfx_switch(DisplayChangeListener *dcl, static QEMUGLContext egl_create_context(DisplayGLCtx *dgc, QEMUGLParams *params) { - eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, - qemu_egl_rn_ctx); - return qemu_egl_create_context(dgc, params); + return qemu_egl_create_context(dgc, params, qemu_egl_rn_ctx); } static void egl_scanout_disable(DisplayChangeListener *dcl) diff --git a/ui/gtk-egl.c b/ui/gtk-egl.c index ae9239999cdb..1b5c1d4533c0 100644 --- a/ui/gtk-egl.c +++ b/ui/gtk-egl.c @@ -180,7 +180,6 @@ void gd_egl_refresh(DisplayChangeListener *dcl) if (vc->gfx.glupdates) { vc->gfx.glupdates = 0; - gtk_egl_set_scanout_mode(vc, false); gd_egl_draw(vc); } } @@ -220,9 +219,7 @@ QEMUGLContext gd_egl_create_context(DisplayGLCtx *dgc, { VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc); - eglMakeCurrent(qemu_egl_display, vc->gfx.esurface, - vc->gfx.esurface, vc->gfx.ectx); - return qemu_egl_create_context(dgc, params); + return qemu_egl_create_context(dgc, params, vc->gfx.ectx); } void gd_egl_scanout_disable(DisplayChangeListener *dcl) diff --git a/ui/gtk-gl-area.c b/ui/gtk-gl-area.c index cd86022d264a..ce49000d3f17 100644 --- a/ui/gtk-gl-area.c +++ b/ui/gtk-gl-area.c @@ -199,7 +199,6 @@ void gd_gl_area_refresh(DisplayChangeListener *dcl) if (vc->gfx.glupdates) { vc->gfx.glupdates = 0; - gtk_gl_area_set_scanout_mode(vc, false); gtk_gl_area_queue_render(GTK_GL_AREA(vc->gfx.drawing_area)); } } @@ -251,11 +250,13 @@ QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc, QEMUGLParams *params) { VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc); + GdkGLContext *ctx, *current_ctx; GdkWindow *window; - GdkGLContext *ctx; GError *err = NULL; int major, minor; + current_ctx = gdk_gl_context_get_current(); + window = gtk_widget_get_window(vc->gfx.drawing_area); ctx = gdk_window_create_gl_context(window, &err); if (err) { @@ -276,8 +277,12 @@ QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc, gdk_gl_context_make_current(ctx); gdk_gl_context_get_version(ctx, &major, &minor); - gdk_gl_context_clear_current(); - gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); + + if (current_ctx) { + gdk_gl_context_make_current(current_ctx); + } else { + gdk_gl_context_clear_current(); + } if (gd_cmp_gl_context_version(major, minor, params) == -1) { /* created ctx version < requested version */ diff --git a/ui/meson.build b/ui/meson.build index 6371422c4604..69404bca71ae 100644 --- a/ui/meson.build +++ b/ui/meson.build @@ -70,6 +70,7 @@ if opengl.found() ui_modules += {'egl-headless' : egl_headless_ss} endif +dbus_display1 = files() if dbus_display dbus_ss = ss.source_set() env = environment() @@ -88,6 +89,8 @@ if dbus_display '--interface-prefix', 'org.qemu.', '--c-namespace', 'QemuDBus', '--generate-c-code', '@BASENAME@']) + dbus_display1_h = declare_dependency(dependencies: [gio], + sources: dbus_display1[0]) dbus_ss.add(when: gio, if_true: [files( 'dbus-chardev.c', diff --git a/ui/sdl2-gl.c b/ui/sdl2-gl.c index 3be17d1079af..bb066cdd885a 100644 --- a/ui/sdl2-gl.c +++ b/ui/sdl2-gl.c @@ -26,6 +26,8 @@ */ #include "qemu/osdep.h" +#include "qemu/main-loop.h" +#include "qemu/error-report.h" #include "ui/console.h" #include "ui/input.h" #include "ui/sdl2.h" @@ -51,7 +53,6 @@ static void sdl2_gl_render_surface(struct sdl2_console *scon) int ww, wh; SDL_GL_MakeCurrent(scon->real_window, scon->winctx); - sdl2_set_scanout_mode(scon, false); SDL_GetWindowSize(scon->real_window, &ww, &wh); surface_gl_setup_viewport(scon->gls, scon->surface, ww, wh); @@ -140,10 +141,12 @@ QEMUGLContext sdl2_gl_create_context(DisplayGLCtx *dgc, QEMUGLParams *params) { struct sdl2_console *scon = container_of(dgc, struct sdl2_console, dgc); - SDL_GLContext ctx; + SDL_GLContext ctx, current_ctx; assert(scon->opengl); + current_ctx = SDL_GL_GetCurrentContext(); + SDL_GL_MakeCurrent(scon->real_window, scon->winctx); SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); @@ -168,6 +171,9 @@ QEMUGLContext sdl2_gl_create_context(DisplayGLCtx *dgc, SDL_GL_CONTEXT_PROFILE_ES); ctx = SDL_GL_CreateContext(scon->real_window); } + + SDL_GL_MakeCurrent(scon->real_window, current_ctx); + return (QEMUGLContext)ctx; } @@ -246,3 +252,69 @@ void sdl2_gl_scanout_flush(DisplayChangeListener *dcl, SDL_GL_SwapWindow(scon->real_window); } + +#ifdef CONFIG_GBM +void sdl2_gl_scanout_dmabuf(DisplayChangeListener *dcl, + QemuDmaBuf *dmabuf) +{ + struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl); + const int *fds; + + assert(scon->opengl); + SDL_GL_MakeCurrent(scon->real_window, scon->winctx); + + egl_dmabuf_import_texture(dmabuf); + if (!qemu_dmabuf_get_texture(dmabuf)) { + fds = qemu_dmabuf_get_fds(dmabuf, NULL); + error_report("%s: failed fd=%d", __func__, fds ? fds[0] : -1); + return; + } + + sdl2_gl_scanout_texture(dcl, qemu_dmabuf_get_texture(dmabuf), false, + qemu_dmabuf_get_width(dmabuf), + qemu_dmabuf_get_height(dmabuf), + 0, 0, + qemu_dmabuf_get_width(dmabuf), + qemu_dmabuf_get_height(dmabuf), + NULL); + + if (qemu_dmabuf_get_allow_fences(dmabuf)) { + scon->guest_fb.dmabuf = dmabuf; + } +} + +void sdl2_gl_release_dmabuf(DisplayChangeListener *dcl, + QemuDmaBuf *dmabuf) +{ + egl_dmabuf_release_texture(dmabuf); +} + +bool sdl2_gl_has_dmabuf(DisplayChangeListener *dcl) +{ + struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl); + + return scon->has_dmabuf; +} +#endif + +void sdl2_gl_console_init(struct sdl2_console *scon) +{ + bool hidden = scon->hidden; + + scon->hidden = true; + scon->surface = qemu_create_displaysurface(1, 1); + sdl2_window_create(scon); + + /* + * QEMU checks whether console supports dma-buf before switching + * to the console. To break this chicken-egg problem we pre-check + * dma-buf availability beforehand using a dummy SDL window. + */ + scon->has_dmabuf = qemu_egl_has_dmabuf(); + + sdl2_window_destroy(scon); + qemu_free_displaysurface(scon->surface); + + scon->surface = NULL; + scon->hidden = hidden; +} diff --git a/ui/sdl2.c b/ui/sdl2.c index 032dc14bc398..aaaede56e0e9 100644 --- a/ui/sdl2.c +++ b/ui/sdl2.c @@ -35,6 +35,10 @@ #include "qemu/log.h" #include "qemu-main.h" +#ifdef CONFIG_X11 +#include +#endif + static int sdl2_num_outputs; static struct sdl2_console *sdl2_console; @@ -120,6 +124,11 @@ void sdl2_window_create(struct sdl2_console *scon) /* The SDL renderer is only used by sdl2-2D, when OpenGL is disabled */ scon->real_renderer = SDL_CreateRenderer(scon->real_window, -1, 0); } + +#ifdef CONFIG_OPENGL + qemu_egl_display = eglGetCurrentDisplay(); +#endif + sdl_update_caption(scon); } @@ -808,6 +817,12 @@ static const DisplayChangeListenerOps dcl_gl_ops = { .dpy_gl_scanout_disable = sdl2_gl_scanout_disable, .dpy_gl_scanout_texture = sdl2_gl_scanout_texture, .dpy_gl_update = sdl2_gl_scanout_flush, + +#ifdef CONFIG_GBM + .dpy_gl_scanout_dmabuf = sdl2_gl_scanout_dmabuf, + .dpy_gl_release_dmabuf = sdl2_gl_release_dmabuf, + .dpy_has_dmabuf = sdl2_gl_has_dmabuf, +#endif }; static bool @@ -835,6 +850,35 @@ static void sdl2_display_early_init(DisplayOptions *o) } } +static void sdl2_set_hint_x11_force_egl(void) +{ +#if defined(SDL_HINT_VIDEO_X11_FORCE_EGL) && defined(CONFIG_OPENGL) && \ + defined(CONFIG_X11) + Display *x_disp = XOpenDisplay(NULL); + EGLDisplay egl_display; + + if (!x_disp) { + return; + } + + /* Prefer EGL over GLX to get dma-buf support. */ + egl_display = eglGetDisplay((EGLNativeDisplayType)x_disp); + + if (egl_display != EGL_NO_DISPLAY) { + /* + * Setting X11_FORCE_EGL hint doesn't make SDL to prefer X11 over + * Wayland. SDL will use Wayland driver even if XWayland presents. + * It's always safe to set the hint even if X11 is not used by SDL. + * SDL will work regardless of the hint. + */ + SDL_SetHint(SDL_HINT_VIDEO_X11_FORCE_EGL, "1"); + eglTerminate(egl_display); + } + + XCloseDisplay(x_disp); +#endif +} + static void sdl2_display_init(DisplayState *ds, DisplayOptions *o) { uint8_t data = 0; @@ -862,6 +906,7 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o) SDL_SetHint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, "0"); #endif SDL_SetHint(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, "1"); + sdl2_set_hint_x11_force_egl(); SDL_EnableScreenSaver(); memset(&info, 0, sizeof(info)); SDL_VERSION(&info.version); @@ -906,9 +951,12 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o) #endif sdl2_console[i].dcl.con = con; sdl2_console[i].kbd = qkbd_state_init(con); +#ifdef CONFIG_OPENGL if (display_opengl) { qemu_console_set_display_gl_ctx(con, &sdl2_console[i].dgc); + sdl2_gl_console_init(&sdl2_console[i]); } +#endif register_displaychangelistener(&sdl2_console[i].dcl); #if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_X11) diff --git a/ui/spice-core.c b/ui/spice-core.c index ee13ecc4a530..ef1c00134fa4 100644 --- a/ui/spice-core.c +++ b/ui/spice-core.c @@ -757,7 +757,7 @@ static void qemu_spice_init(void) tls_ciphers); } if (password) { - qemu_spice.set_passwd(password, false, false); + qemu_spice.set_passwd(password, false, false, NULL); } if (qemu_opt_get_bool(opts, "sasl", 0)) { if (spice_server_set_sasl(spice_server, 1) == -1) { @@ -920,8 +920,10 @@ int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con) return qemu_spice_add_interface(&qxlin->base); } -static int qemu_spice_set_ticket(bool fail_if_conn, bool disconnect_if_conn) +static int qemu_spice_set_ticket(bool fail_if_conn, bool disconnect_if_conn, + Error **errp) { + int ret; time_t lifetime, now = time(NULL); char *passwd; @@ -935,26 +937,35 @@ static int qemu_spice_set_ticket(bool fail_if_conn, bool disconnect_if_conn) passwd = NULL; lifetime = 1; } - return spice_server_set_ticket(spice_server, passwd, lifetime, - fail_if_conn, disconnect_if_conn); + ret = spice_server_set_ticket(spice_server, passwd, lifetime, + fail_if_conn, disconnect_if_conn); + if (ret < 0) { + error_setg(errp, "Unable to set SPICE server ticket"); + return -1; + } + return 0; } static int qemu_spice_set_passwd(const char *passwd, - bool fail_if_conn, bool disconnect_if_conn) + bool fail_if_conn, bool disconnect_if_conn, + Error **errp) { if (strcmp(auth, "spice") != 0) { + error_setg(errp, "SPICE authentication is disabled"); + error_append_hint(errp, + "To enable it use '-spice ...,password-secret=ID'"); return -1; } g_free(auth_passwd); auth_passwd = g_strdup(passwd); - return qemu_spice_set_ticket(fail_if_conn, disconnect_if_conn); + return qemu_spice_set_ticket(fail_if_conn, disconnect_if_conn, errp); } static int qemu_spice_set_pw_expire(time_t expires) { auth_expires = expires; - return qemu_spice_set_ticket(false, false); + return qemu_spice_set_ticket(false, false, NULL); } static int qemu_spice_display_add_client(int csock, int skipauth, int tls) diff --git a/ui/spice-display.c b/ui/spice-display.c index 28399f8a8174..5052f371f44e 100644 --- a/ui/spice-display.c +++ b/ui/spice-display.c @@ -1033,9 +1033,7 @@ static void spice_gl_switch(DisplayChangeListener *dcl, static QEMUGLContext qemu_spice_gl_create_context(DisplayGLCtx *dgc, QEMUGLParams *params) { - eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, - qemu_egl_rn_ctx); - return qemu_egl_create_context(dgc, params); + return qemu_egl_create_context(dgc, params, qemu_egl_rn_ctx); } static void qemu_spice_gl_scanout_disable(DisplayChangeListener *dcl) diff --git a/ui/spice-module.c b/ui/spice-module.c index 32223358722c..7651c85885f9 100644 --- a/ui/spice-module.c +++ b/ui/spice-module.c @@ -45,14 +45,15 @@ static int qemu_spice_migrate_info_stub(const char *h, int p, int t, static int qemu_spice_set_passwd_stub(const char *passwd, bool fail_if_connected, - bool disconnect_if_connected) + bool disconnect_if_connected, + Error **errp) { - return -1; + g_assert_not_reached(); } static int qemu_spice_set_pw_expire_stub(time_t expires) { - return -1; + g_assert_not_reached(); } static int qemu_spice_display_add_client_stub(int csock, int skipauth, diff --git a/ui/ui-qmp-cmds.c b/ui/ui-qmp-cmds.c index b49b6361520b..1173c82cf7f5 100644 --- a/ui/ui-qmp-cmds.c +++ b/ui/ui-qmp-cmds.c @@ -31,15 +31,14 @@ void qmp_set_password(SetPasswordOptions *opts, Error **errp) { - int rc; - if (opts->protocol == DISPLAY_PROTOCOL_SPICE) { if (!qemu_using_spice(errp)) { return; } - rc = qemu_spice.set_passwd(opts->password, - opts->connected == SET_PASSWORD_ACTION_FAIL, - opts->connected == SET_PASSWORD_ACTION_DISCONNECT); + qemu_spice.set_passwd(opts->password, + opts->connected == SET_PASSWORD_ACTION_FAIL, + opts->connected == SET_PASSWORD_ACTION_DISCONNECT, + errp); } else { assert(opts->protocol == DISPLAY_PROTOCOL_VNC); if (opts->connected != SET_PASSWORD_ACTION_KEEP) { @@ -52,11 +51,7 @@ void qmp_set_password(SetPasswordOptions *opts, Error **errp) * Note that setting an empty password will not disable login * through this interface. */ - rc = vnc_display_password(opts->u.vnc.display, opts->password); - } - - if (rc != 0) { - error_setg(errp, "Could not set password"); + vnc_display_password(opts->u.vnc.display, opts->password, errp); } } @@ -107,9 +102,7 @@ void qmp_expire_password(ExpirePasswordOptions *opts, Error **errp) #ifdef CONFIG_VNC void qmp_change_vnc_password(const char *password, Error **errp) { - if (vnc_display_password(NULL, password) < 0) { - error_setg(errp, "Could not set password"); - } + vnc_display_password(NULL, password, errp); } #endif diff --git a/ui/vdagent.c b/ui/vdagent.c index 7ff0861f3e92..5a5e4bf6818b 100644 --- a/ui/vdagent.c +++ b/ui/vdagent.c @@ -17,14 +17,6 @@ #include "spice/vd_agent.h" -#define CHECK_SPICE_PROTOCOL_VERSION(major, minor, micro) \ - (CONFIG_SPICE_PROTOCOL_MAJOR > (major) || \ - (CONFIG_SPICE_PROTOCOL_MAJOR == (major) && \ - CONFIG_SPICE_PROTOCOL_MINOR > (minor)) || \ - (CONFIG_SPICE_PROTOCOL_MAJOR == (major) && \ - CONFIG_SPICE_PROTOCOL_MINOR == (minor) && \ - CONFIG_SPICE_PROTOCOL_MICRO >= (micro))) - #define VDAGENT_BUFFER_LIMIT (1 * MiB) #define VDAGENT_MOUSE_DEFAULT true #define VDAGENT_CLIPBOARD_DEFAULT false @@ -87,10 +79,8 @@ static const char *cap_name[] = { [VD_AGENT_CAP_FILE_XFER_DISABLED] = "file-xfer-disabled", [VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS] = "file-xfer-detailed-errors", [VD_AGENT_CAP_GRAPHICS_DEVICE_INFO] = "graphics-device-info", -#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1) [VD_AGENT_CAP_CLIPBOARD_NO_RELEASE_ON_REGRAB] = "clipboard-no-release-on-regrab", [VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL] = "clipboard-grab-serial", -#endif }; static const char *msg_name[] = { @@ -125,9 +115,7 @@ static const char *type_name[] = { [VD_AGENT_CLIPBOARD_IMAGE_BMP] = "bmp", [VD_AGENT_CLIPBOARD_IMAGE_TIFF] = "tiff", [VD_AGENT_CLIPBOARD_IMAGE_JPG] = "jpg", -#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 3) [VD_AGENT_CLIPBOARD_FILE_LIST] = "files", -#endif }; #define GET_NAME(_m, _v) \ @@ -197,9 +185,7 @@ static void vdagent_send_caps(VDAgentChardev *vd, bool request) if (vd->clipboard) { caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND); caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION); -#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1) caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL); -#endif } caps->request = request; @@ -318,11 +304,7 @@ static bool have_selection(VDAgentChardev *vd) static bool have_clipboard_serial(VDAgentChardev *vd) { -#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1) return vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL); -#else - return false; -#endif } static uint32_t type_qemu_to_vdagent(enum QemuClipboardType type) diff --git a/ui/vnc-stubs.c b/ui/vnc-stubs.c index a96bc862363c..5de9bf9d70cb 100644 --- a/ui/vnc-stubs.c +++ b/ui/vnc-stubs.c @@ -2,11 +2,11 @@ #include "ui/console.h" #include "qapi/error.h" -int vnc_display_password(const char *id, const char *password) +int vnc_display_password(const char *id, const char *password, Error **errp) { - return -ENODEV; + g_assert_not_reached(); } int vnc_display_pw_expire(const char *id, time_t expires) { - return -ENODEV; + g_assert_not_reached(); }; diff --git a/ui/vnc.c b/ui/vnc.c index a61a4f937d69..952976e96491 100644 --- a/ui/vnc.c +++ b/ui/vnc.c @@ -1280,7 +1280,7 @@ static void audio_add(VncState *vs) ops.destroy = audio_capture_destroy; ops.capture = audio_capture; - vs->audio_cap = AUD_add_capture(vs->vd->audio_be, &vs->as, &ops, vs); + vs->audio_cap = audio_be_add_capture(vs->vd->audio_be, &vs->as, &ops, vs); if (!vs->audio_cap) { error_report("Failed to add audio capture"); } @@ -1289,7 +1289,7 @@ static void audio_add(VncState *vs) static void audio_del(VncState *vs) { if (vs->audio_cap) { - AUD_del_capture(vs->audio_cap, vs); + audio_be_del_capture(vs->vd->audio_be, vs->audio_cap, vs); vs->audio_cap = NULL; } } @@ -3372,7 +3372,7 @@ static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc, vs->as.freq = 44100; vs->as.nchannels = 2; vs->as.fmt = AUDIO_FORMAT_S16; - vs->as.endianness = 0; + vs->as.big_endian = false; qemu_mutex_init(&vs->output_mutex); vs->bh = qemu_bh_new(vnc_jobs_bh, vs); @@ -3526,16 +3526,20 @@ static void vnc_display_close(VncDisplay *vd) #endif } -int vnc_display_password(const char *id, const char *password) +int vnc_display_password(const char *id, const char *password, Error **errp) { VncDisplay *vd = vnc_display_find(id); if (!vd) { + error_setg(errp, "No VNC display is present"); + error_append_hint(errp, + "To enable it, use '-vnc ...'"); return -EINVAL; } if (vd->auth == VNC_AUTH_NONE) { - error_printf_unless_qmp("If you want use passwords please enable " - "password auth using '-vnc ${dpy},password'.\n"); + error_setg(errp, "VNC password authentication is disabled"); + error_append_hint(errp, + "To enable it, use '-vnc ...,password-secret=ID'"); return -EINVAL; } @@ -3574,9 +3578,9 @@ static void vnc_display_print_local_addr(VncDisplay *vd) qapi_free_SocketAddress(addr); return; } - error_printf_unless_qmp("VNC server running on %s:%s\n", - addr->u.inet.host, - addr->u.inet.port); + error_printf("VNC server running on %s:%s\n", + addr->u.inet.host, + addr->u.inet.port); qapi_free_SocketAddress(addr); } diff --git a/util/aio-posix.c b/util/aio-posix.c index e24b955fd91a..488d964611bb 100644 --- a/util/aio-posix.c +++ b/util/aio-posix.c @@ -23,6 +23,7 @@ #include "qemu/rcu_queue.h" #include "qemu/sockets.h" #include "qemu/cutils.h" +#include "system/iothread.h" #include "trace.h" #include "aio-posix.h" @@ -813,5 +814,13 @@ void aio_add_sqe(void (*prep_sqe)(struct io_uring_sqe *sqe, void *opaque), { AioContext *ctx = qemu_get_current_aio_context(); ctx->fdmon_ops->add_sqe(ctx, prep_sqe, opaque, cqe_handler); + + /* + * Wake the main loop if it is sleeping in ppoll(). When a vCPU thread + * queues SQEs, the actual io_uring_submit() only happens in + * gsource_prepare() in the main loop thread. Without this notify, the + * main loop thread's ppoll() can sleep up to 499ms before submitting. + */ + aio_notify(ctx); } #endif /* CONFIG_LINUX_IO_URING */ diff --git a/util/crc32.c b/util/crc32.c new file mode 100644 index 000000000000..590dd6e3a209 --- /dev/null +++ b/util/crc32.c @@ -0,0 +1,85 @@ +/* + * Constants for computing CRC32 checksums + * + * Copyright (c) 2026 QEMU contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + */ + +#include "qemu/osdep.h" +#include "qemu/crc32.h" + +/* + * CRC-32 table (reversed; polynomial 0xEDB88320). + */ + +const uint32_t crc32_table[256] = { + 0x00000000u, 0x77073096u, 0xee0e612cu, 0x990951bau, + 0x076dc419u, 0x706af48fu, 0xe963a535u, 0x9e6495a3u, + 0x0edb8832u, 0x79dcb8a4u, 0xe0d5e91eu, 0x97d2d988u, + 0x09b64c2bu, 0x7eb17cbdu, 0xe7b82d07u, 0x90bf1d91u, + 0x1db71064u, 0x6ab020f2u, 0xf3b97148u, 0x84be41deu, + 0x1adad47du, 0x6ddde4ebu, 0xf4d4b551u, 0x83d385c7u, + 0x136c9856u, 0x646ba8c0u, 0xfd62f97au, 0x8a65c9ecu, + 0x14015c4fu, 0x63066cd9u, 0xfa0f3d63u, 0x8d080df5u, + 0x3b6e20c8u, 0x4c69105eu, 0xd56041e4u, 0xa2677172u, + 0x3c03e4d1u, 0x4b04d447u, 0xd20d85fdu, 0xa50ab56bu, + 0x35b5a8fau, 0x42b2986cu, 0xdbbbc9d6u, 0xacbcf940u, + 0x32d86ce3u, 0x45df5c75u, 0xdcd60dcfu, 0xabd13d59u, + 0x26d930acu, 0x51de003au, 0xc8d75180u, 0xbfd06116u, + 0x21b4f4b5u, 0x56b3c423u, 0xcfba9599u, 0xb8bda50fu, + 0x2802b89eu, 0x5f058808u, 0xc60cd9b2u, 0xb10be924u, + 0x2f6f7c87u, 0x58684c11u, 0xc1611dabu, 0xb6662d3du, + 0x76dc4190u, 0x01db7106u, 0x98d220bcu, 0xefd5102au, + 0x71b18589u, 0x06b6b51fu, 0x9fbfe4a5u, 0xe8b8d433u, + 0x7807c9a2u, 0x0f00f934u, 0x9609a88eu, 0xe10e9818u, + 0x7f6a0dbbu, 0x086d3d2du, 0x91646c97u, 0xe6635c01u, + 0x6b6b51f4u, 0x1c6c6162u, 0x856530d8u, 0xf262004eu, + 0x6c0695edu, 0x1b01a57bu, 0x8208f4c1u, 0xf50fc457u, + 0x65b0d9c6u, 0x12b7e950u, 0x8bbeb8eau, 0xfcb9887cu, + 0x62dd1ddfu, 0x15da2d49u, 0x8cd37cf3u, 0xfbd44c65u, + 0x4db26158u, 0x3ab551ceu, 0xa3bc0074u, 0xd4bb30e2u, + 0x4adfa541u, 0x3dd895d7u, 0xa4d1c46du, 0xd3d6f4fbu, + 0x4369e96au, 0x346ed9fcu, 0xad678846u, 0xda60b8d0u, + 0x44042d73u, 0x33031de5u, 0xaa0a4c5fu, 0xdd0d7cc9u, + 0x5005713cu, 0x270241aau, 0xbe0b1010u, 0xc90c2086u, + 0x5768b525u, 0x206f85b3u, 0xb966d409u, 0xce61e49fu, + 0x5edef90eu, 0x29d9c998u, 0xb0d09822u, 0xc7d7a8b4u, + 0x59b33d17u, 0x2eb40d81u, 0xb7bd5c3bu, 0xc0ba6cadu, + 0xedb88320u, 0x9abfb3b6u, 0x03b6e20cu, 0x74b1d29au, + 0xead54739u, 0x9dd277afu, 0x04db2615u, 0x73dc1683u, + 0xe3630b12u, 0x94643b84u, 0x0d6d6a3eu, 0x7a6a5aa8u, + 0xe40ecf0bu, 0x9309ff9du, 0x0a00ae27u, 0x7d079eb1u, + 0xf00f9344u, 0x8708a3d2u, 0x1e01f268u, 0x6906c2feu, + 0xf762575du, 0x806567cbu, 0x196c3671u, 0x6e6b06e7u, + 0xfed41b76u, 0x89d32be0u, 0x10da7a5au, 0x67dd4accu, + 0xf9b9df6fu, 0x8ebeeff9u, 0x17b7be43u, 0x60b08ed5u, + 0xd6d6a3e8u, 0xa1d1937eu, 0x38d8c2c4u, 0x4fdff252u, + 0xd1bb67f1u, 0xa6bc5767u, 0x3fb506ddu, 0x48b2364bu, + 0xd80d2bdau, 0xaf0a1b4cu, 0x36034af6u, 0x41047a60u, + 0xdf60efc3u, 0xa867df55u, 0x316e8eefu, 0x4669be79u, + 0xcb61b38cu, 0xbc66831au, 0x256fd2a0u, 0x5268e236u, + 0xcc0c7795u, 0xbb0b4703u, 0x220216b9u, 0x5505262fu, + 0xc5ba3bbeu, 0xb2bd0b28u, 0x2bb45a92u, 0x5cb36a04u, + 0xc2d7ffa7u, 0xb5d0cf31u, 0x2cd99e8bu, 0x5bdeae1du, + 0x9b64c2b0u, 0xec63f226u, 0x756aa39cu, 0x026d930au, + 0x9c0906a9u, 0xeb0e363fu, 0x72076785u, 0x05005713u, + 0x95bf4a82u, 0xe2b87a14u, 0x7bb12baeu, 0x0cb61b38u, + 0x92d28e9bu, 0xe5d5be0du, 0x7cdcefb7u, 0x0bdbdf21u, + 0x86d3d2d4u, 0xf1d4e242u, 0x68ddb3f8u, 0x1fda836eu, + 0x81be16cdu, 0xf6b9265bu, 0x6fb077e1u, 0x18b74777u, + 0x88085ae6u, 0xff0f6a70u, 0x66063bcau, 0x11010b5cu, + 0x8f659effu, 0xf862ae69u, 0x616bffd3u, 0x166ccf45u, + 0xa00ae278u, 0xd70dd2eeu, 0x4e048354u, 0x3903b3c2u, + 0xa7672661u, 0xd06016f7u, 0x4969474du, 0x3e6e77dbu, + 0xaed16a4au, 0xd9d65adcu, 0x40df0b66u, 0x37d83bf0u, + 0xa9bcae53u, 0xdebb9ec5u, 0x47b2cf7fu, 0x30b5ffe9u, + 0xbdbdf21cu, 0xcabac28au, 0x53b39330u, 0x24b4a3a6u, + 0xbad03605u, 0xcdd70693u, 0x54de5729u, 0x23d967bfu, + 0xb3667a2eu, 0xc4614ab8u, 0x5d681b02u, 0x2a6f2b94u, + 0xb40bbe37u, 0xc30c8ea1u, 0x5a05df1bu, 0x2d02ef8du +}; diff --git a/util/crc32c.c b/util/crc32c.c index ea7f345de860..f40597f80df7 100644 --- a/util/crc32c.c +++ b/util/crc32c.c @@ -1,7 +1,7 @@ /* * Castagnoli CRC32C Checksum Algorithm * - * Polynomial: 0x11EDC6F41 + * Polynomial: 0x1EDC6F41 * * Castagnoli93: Guy Castagnoli and Stefan Braeuer and Martin Herrman * "Optimization of Cyclic Redundancy-Check Codes with 24 @@ -37,7 +37,7 @@ * reflect output bytes = true */ -static const uint32_t crc32c_table[256] = { +const uint32_t crc32c_table[256] = { 0x00000000L, 0xF26B8303L, 0xE13B70F7L, 0x1350F3F4L, 0xC79A971FL, 0x35F1141CL, 0x26A1E7E8L, 0xD4CA64EBL, 0x8AD958CFL, 0x78B2DBCCL, 0x6BE22838L, 0x9989AB3BL, diff --git a/util/error-report.c b/util/error-report.c index d6b7448183ad..bbea49a55d22 100644 --- a/util/error-report.c +++ b/util/error-report.c @@ -29,13 +29,51 @@ bool message_with_timestamp; bool error_with_guestname; const char *error_guest_name; +/* + * Print to the current human monitor if we have one, else to stderr. + */ +static int G_GNUC_PRINTF(2, 0) +error_vprintf_mon(Monitor *cur_mon, const char *fmt, va_list ap) +{ + /* + * This will return -1 if 'cur_mon' is NULL, or is QMP. + * IOW this will only print if in HMP, otherwise we + * fallback to stderr for QMP / no-monitor scenarios. + */ + int ret = monitor_vprintf(cur_mon, fmt, ap); + if (ret == -1) { + ret = vfprintf(stderr, fmt, ap); + } + return ret; +} + +/* + * Print to the current human monitor if we have one, else to stderr. + */ +static int G_GNUC_PRINTF(2, 3) +error_printf_mon(Monitor *cur_mon, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = error_vprintf_mon(cur_mon, fmt, ap); + va_end(ap); + return ret; +} + +int error_vprintf(const char *fmt, va_list ap) +{ + return error_vprintf_mon(monitor_cur(), fmt, ap); +} + int error_printf(const char *fmt, ...) { va_list ap; int ret; va_start(ap, fmt); - ret = error_vprintf(fmt, ap); + ret = error_vprintf_mon(monitor_cur(), fmt, ap); va_end(ap); return ret; } @@ -138,34 +176,34 @@ void loc_set_file(const char *fname, int lno) /* * Print current location to current monitor if we have one, else to stderr. */ -static void print_loc(void) +static void print_loc(Monitor *cur) { const char *sep = ""; int i; const char *const *argp; - if (!monitor_cur() && g_get_prgname()) { - error_printf("%s:", g_get_prgname()); + if (!cur && g_get_prgname()) { + fprintf(stderr, "%s:", g_get_prgname()); sep = " "; } switch (cur_loc->kind) { case LOC_CMDLINE: argp = cur_loc->ptr; for (i = 0; i < cur_loc->num; i++) { - error_printf("%s%s", sep, argp[i]); + error_printf_mon(cur, "%s%s", sep, argp[i]); sep = " "; } - error_printf(": "); + error_printf_mon(cur, ": "); break; case LOC_FILE: - error_printf("%s:", (const char *)cur_loc->ptr); + error_printf_mon(cur, "%s:", (const char *)cur_loc->ptr); if (cur_loc->num) { - error_printf("%d:", cur_loc->num); + error_printf_mon(cur, "%d:", cur_loc->num); } - error_printf(" "); + error_printf_mon(cur, " "); break; default: - error_printf("%s", sep); + error_printf_mon(cur, "%s", sep); } } @@ -185,34 +223,50 @@ char *real_time_iso8601(void) G_GNUC_PRINTF(2, 0) static void vreport(report_type type, const char *fmt, va_list ap) { + Monitor *cur = monitor_cur(); gchar *timestr; - if (message_with_timestamp && !monitor_cur()) { + /* + * When current monitor is QMP, messages must go to stderr + * and have prefixes added + */ + if (monitor_cur_is_qmp()) { + cur = NULL; + } + if (!cur) { + qemu_flockfile(stderr); + } + + if (message_with_timestamp && !cur) { timestr = real_time_iso8601(); - error_printf("%s ", timestr); + fprintf(stderr, "%s ", timestr); g_free(timestr); } /* Only prepend guest name if -msg guest-name and -name guest=... are set */ - if (error_with_guestname && error_guest_name && !monitor_cur()) { - error_printf("%s ", error_guest_name); + if (error_with_guestname && error_guest_name && !cur) { + fprintf(stderr, "%s ", error_guest_name); } - print_loc(); + print_loc(cur); switch (type) { case REPORT_TYPE_ERROR: break; case REPORT_TYPE_WARNING: - error_printf("warning: "); + error_printf_mon(cur, "warning: "); break; case REPORT_TYPE_INFO: - error_printf("info: "); + error_printf_mon(cur, "info: "); break; } - error_vprintf(fmt, ap); - error_printf("\n"); + error_vprintf_mon(cur, fmt, ap); + error_printf_mon(cur, "\n"); + + if (!cur) { + qemu_funlockfile(stderr); + } } /* diff --git a/util/fdmon-io_uring.c b/util/fdmon-io_uring.c index d0b56127c670..b81e412402b1 100644 --- a/util/fdmon-io_uring.c +++ b/util/fdmon-io_uring.c @@ -344,7 +344,19 @@ static void fdmon_io_uring_gsource_prepare(AioContext *ctx) static bool fdmon_io_uring_gsource_check(AioContext *ctx) { gpointer tag = ctx->io_uring_fd_tag; - return g_source_query_unix_fd(&ctx->source, tag) & G_IO_IN; + + /* Check ppoll revents (normal path) */ + if (g_source_query_unix_fd(&ctx->source, tag) & G_IO_IN) { + return true; + } + + /* + * Also check for CQEs that may have been posted during prepare's + * io_uring_submit() via task_work on syscall exit. Without this, + * the main loop can miss completions and sleep in ppoll() until the + * next timer fires. + */ + return io_uring_cq_ready(&ctx->fdmon_io_uring); } /* Dispatch CQE handlers that are ready */ diff --git a/util/fifo8.c b/util/fifo8.c index a26da66ad2c8..cc4f590b7af1 100644 --- a/util/fifo8.c +++ b/util/fifo8.c @@ -71,7 +71,7 @@ uint8_t fifo8_pop(Fifo8 *fifo) return ret; } -uint8_t fifo8_peek(Fifo8 *fifo) +uint8_t fifo8_peek(const Fifo8 *fifo) { assert(fifo->num > 0); return fifo->data[fifo->head]; @@ -157,22 +157,22 @@ void fifo8_drop(Fifo8 *fifo, uint32_t len) assert(len == 0); } -bool fifo8_is_empty(Fifo8 *fifo) +bool fifo8_is_empty(const Fifo8 *fifo) { return (fifo->num == 0); } -bool fifo8_is_full(Fifo8 *fifo) +bool fifo8_is_full(const Fifo8 *fifo) { return (fifo->num == fifo->capacity); } -uint32_t fifo8_num_free(Fifo8 *fifo) +uint32_t fifo8_num_free(const Fifo8 *fifo) { return fifo->capacity - fifo->num; } -uint32_t fifo8_num_used(Fifo8 *fifo) +uint32_t fifo8_num_used(const Fifo8 *fifo) { return fifo->num; } diff --git a/util/log.c b/util/log.c index c44d66b5ce78..7cffbc1bf89c 100644 --- a/util/log.c +++ b/util/log.c @@ -118,6 +118,7 @@ static FILE *qemu_log_trylock_with_err(Error **errp) logfile = qatomic_rcu_read((void **)&global_file); if (!logfile) { rcu_read_unlock(); + error_setg(errp, "Global log file output is not open"); return NULL; } } @@ -127,13 +128,39 @@ static FILE *qemu_log_trylock_with_err(Error **errp) return logfile; } +/* + * Zero if there's been no opening qemu_log_trylock call, + * indicating the need for message context to be emitted + * + * Non-zero if we're in the middle of printing a message, + * possibly over multiple lines and must skip further + * message context + */ +static __thread unsigned int log_depth; + FILE *qemu_log_trylock(void) { - return qemu_log_trylock_with_err(NULL); + FILE *f = qemu_log_trylock_with_err(NULL); + log_depth++; + return f; +} + +FILE *qemu_log_trylock_with_context(void) +{ + FILE *f = qemu_log_trylock(); + if (f && log_depth == 1 && message_with_timestamp) { + g_autofree const char *timestr = NULL; + g_autoptr(GDateTime) dt = g_date_time_new_now_utc(); + timestr = g_date_time_format_iso8601(dt); + fprintf(f, "%s ", timestr); + } + return f; } void qemu_log_unlock(FILE *logfile) { + assert(log_depth); + log_depth--; if (logfile) { fflush(logfile); qemu_funlockfile(logfile); @@ -145,28 +172,9 @@ void qemu_log_unlock(FILE *logfile) void qemu_log(const char *fmt, ...) { - FILE *f; - g_autofree const char *timestr = NULL; - - /* - * Prepare the timestamp *outside* the logging - * lock so it better reflects when the message - * was emitted if we are delayed acquiring the - * mutex - */ - if (message_with_timestamp) { - g_autoptr(GDateTime) dt = g_date_time_new_now_utc(); - timestr = g_date_time_format_iso8601(dt); - } - - f = qemu_log_trylock(); + FILE *f = qemu_log_trylock_with_context(); if (f) { va_list ap; - - if (timestr) { - fprintf(f, "%s ", timestr); - } - va_start(ap, fmt); vfprintf(f, fmt, ap); va_end(ap); diff --git a/util/meson.build b/util/meson.build index 7c9445615d77..d5789b131695 100644 --- a/util/meson.build +++ b/util/meson.build @@ -44,6 +44,7 @@ util_ss.add(files('id.c')) util_ss.add(files('qemu-config.c', 'notify.c')) util_ss.add(files('qemu-option.c', 'qemu-progress.c')) util_ss.add(files('keyval.c')) +util_ss.add(files('crc32.c')) util_ss.add(files('crc32c.c')) util_ss.add(files('uuid.c')) util_ss.add(files('getauxval.c')) @@ -102,11 +103,7 @@ if have_block util_ss.add(files('throttle.c')) util_ss.add(files('timed-average.c')) if config_host_data.get('CONFIG_INOTIFY1') - freebsd_dep = [] - if host_os == 'freebsd' - freebsd_dep = inotify - endif - util_ss.add(files('filemonitor-inotify.c'), freebsd_dep) + util_ss.add(files('filemonitor-inotify.c'), inotify) else util_ss.add(files('filemonitor-stub.c')) endif diff --git a/util/module.c b/util/module.c index 1aa2079d0132..09dc43f51eb3 100644 --- a/util/module.c +++ b/util/module.c @@ -396,6 +396,9 @@ void qemu_load_module_for_opts(const char *group) #else +const QemuModinfo qemu_modinfo[] = {}; + +void module_init_info(const QemuModinfo *info) {} void module_allow_arch(const char *arch) {} void qemu_load_module_for_opts(const char *group) {} int module_load(const char *prefix, const char *name, Error **errp) { return 2; } diff --git a/util/oslib-posix.c b/util/oslib-posix.c index 3c14b726659f..dc001da66dd4 100644 --- a/util/oslib-posix.c +++ b/util/oslib-posix.c @@ -61,7 +61,7 @@ #include "qemu/memalign.h" #include "qemu/mmap-alloc.h" -#define MAX_MEM_PREALLOC_THREAD_COUNT 16 +#define MAX_MEM_PREALLOC_THREAD_COUNT 32 struct MemsetThread; diff --git a/util/qemu-coroutine-lock.c b/util/qemu-coroutine-lock.c index fac91582b5f6..c82ee754bebf 100644 --- a/util/qemu-coroutine-lock.c +++ b/util/qemu-coroutine-lock.c @@ -173,7 +173,7 @@ static CoWaitRecord *pop_waiter(CoMutex *mutex) static bool has_waiters(CoMutex *mutex) { - return QSLIST_EMPTY(&mutex->to_pop) || QSLIST_EMPTY(&mutex->from_push); + return !QSLIST_EMPTY(&mutex->to_pop) || !QSLIST_EMPTY(&mutex->from_push); } void qemu_co_mutex_init(CoMutex *mutex) diff --git a/util/qemu-thread-posix.c b/util/qemu-thread-posix.c index ba725444ba63..bd1c2ad2a596 100644 --- a/util/qemu-thread-posix.c +++ b/util/qemu-thread-posix.c @@ -18,24 +18,39 @@ #include "qemu/tsan.h" #include "qemu/bitmap.h" -#ifdef CONFIG_PTHREAD_SET_NAME_NP +#if defined(CONFIG_PTHREAD_SET_NAME_NP) || defined(CONFIG_PTHREAD_GET_NAME_NP) #include #endif -static bool name_threads; +/* + * This is not defined on Linux, but the man page indicates + * the buffer must be at least 16 bytes, including the NUL + * terminator + */ +#ifndef PTHREAD_MAX_NAMELEN_NP +#define PTHREAD_MAX_NAMELEN_NP 16 +#endif -void qemu_thread_naming(bool enable) -{ - name_threads = enable; +static __thread char namebuf[PTHREAD_MAX_NAMELEN_NP]; -#if !defined CONFIG_PTHREAD_SETNAME_NP_W_TID && \ - !defined CONFIG_PTHREAD_SETNAME_NP_WO_TID && \ - !defined CONFIG_PTHREAD_SET_NAME_NP - /* This is a debugging option, not fatal */ - if (enable) { - fprintf(stderr, "qemu: thread naming not supported on this host\n"); - } -#endif +static void __attribute__((__constructor__(QEMU_CONSTRUCTOR_EARLY))) +qemu_thread_init(void) +{ + /* + * Initialize the main thread name. We must not use + * qemu_thread_setname(), since on some platforms (at least Linux) + * this can change the process name that is reported by tools like + * 'ps'. + * + * This workaround suffices to ensure QEMU log/error messages + * get the main thread name, but at the cost of external tools + * like GDB not seeing it. + * + * NB using a constructor instead of static initializing namebuf, + * to ensure it only initializes the thread-local in the main + * thread + */ + g_strlcpy(namebuf, "main", sizeof(namebuf)); } static void error_exit(int err, const char *msg) @@ -345,6 +360,21 @@ static void qemu_thread_atexit_notify(void *arg) notifier_list_notify(&thread_exit, NULL); } +void qemu_thread_set_name(const char *name) +{ + /* + * Attempt to set the thread name; note that this is for debug, so + * we're not going to fail if we can't set it. + */ +# if defined(CONFIG_PTHREAD_SETNAME_NP_W_TID) + pthread_setname_np(pthread_self(), name); +# elif defined(CONFIG_PTHREAD_SETNAME_NP_WO_TID) + pthread_setname_np(name); +# elif defined(CONFIG_PTHREAD_SET_NAME_NP) + pthread_set_name_np(pthread_self(), name); +# endif +} + typedef struct { void *(*start_routine)(void *); void *arg; @@ -358,17 +388,8 @@ static void *qemu_thread_start(void *args) void *arg = qemu_thread_args->arg; void *r; - /* Attempt to set the threads name; note that this is for debug, so - * we're not going to fail if we can't set it. - */ - if (name_threads && qemu_thread_args->name) { -# if defined(CONFIG_PTHREAD_SETNAME_NP_W_TID) - pthread_setname_np(pthread_self(), qemu_thread_args->name); -# elif defined(CONFIG_PTHREAD_SETNAME_NP_WO_TID) - pthread_setname_np(qemu_thread_args->name); -# elif defined(CONFIG_PTHREAD_SET_NAME_NP) - pthread_set_name_np(pthread_self(), qemu_thread_args->name); -# endif + if (qemu_thread_args->name) { + qemu_thread_set_name(qemu_thread_args->name); } QEMU_TSAN_ANNOTATE_THREAD_NAME(qemu_thread_args->name); g_free(qemu_thread_args->name); @@ -536,3 +557,23 @@ void *qemu_thread_join(QemuThread *thread) } return ret; } + +const char *qemu_thread_get_name(void) +{ + int rv; + if (namebuf[0] != '\0') { + return namebuf; + } + +# if defined(CONFIG_PTHREAD_GETNAME_NP) + rv = pthread_getname_np(pthread_self(), namebuf, sizeof(namebuf)); +# elif defined(CONFIG_PTHREAD_GET_NAME_NP) + rv = pthread_get_name_np(pthread_self(), namebuf, sizeof(namebuf)); +# else + rv = -1; +# endif + if (rv != 0) { + g_strlcpy(namebuf, "unnamed", G_N_ELEMENTS(namebuf)); + } + return namebuf; +} diff --git a/util/qemu-thread-win32.c b/util/qemu-thread-win32.c index ca2e0b512e26..272afc338562 100644 --- a/util/qemu-thread-win32.c +++ b/util/qemu-thread-win32.c @@ -17,14 +17,21 @@ #include "qemu-thread-common.h" #include -static bool name_threads; - typedef HRESULT (WINAPI *pSetThreadDescription) (HANDLE hThread, PCWSTR lpThreadDescription); +typedef HRESULT (WINAPI *pGetThreadDescription) (HANDLE hThread, + PWSTR *lpThreadDescription); static pSetThreadDescription SetThreadDescriptionFunc; +static pGetThreadDescription GetThreadDescriptionFunc; static HMODULE kernel32_module; -static bool load_set_thread_description(void) +static void __attribute__((__constructor__(QEMU_CONSTRUCTOR_EARLY))) +qemu_thread_init(void) +{ + qemu_thread_set_name("main"); +} + +static bool load_thread_description(void) { static gsize _init_once = 0; @@ -34,24 +41,17 @@ static bool load_set_thread_description(void) SetThreadDescriptionFunc = (pSetThreadDescription)GetProcAddress(kernel32_module, "SetThreadDescription"); - if (!SetThreadDescriptionFunc) { + GetThreadDescriptionFunc = + (pGetThreadDescription)GetProcAddress(kernel32_module, + "GetThreadDescription"); + if (!SetThreadDescriptionFunc || !GetThreadDescriptionFunc) { FreeLibrary(kernel32_module); } } g_once_init_leave(&_init_once, 1); } - return !!SetThreadDescriptionFunc; -} - -void qemu_thread_naming(bool enable) -{ - name_threads = enable; - - if (enable && !load_set_thread_description()) { - fprintf(stderr, "qemu: thread naming not supported on this host\n"); - name_threads = false; - } + return (SetThreadDescriptionFunc && GetThreadDescriptionFunc); } static void error_exit(int err, const char *msg) @@ -237,6 +237,7 @@ struct QemuThreadData { void *arg; short mode; NotifierList exit; + char *name; /* Freed in win32_start_routine */ /* Only used for joinable threads. */ bool exited; @@ -278,6 +279,10 @@ static unsigned __stdcall win32_start_routine(void *arg) void *(*start_routine)(void *) = data->start_routine; void *thread_arg = data->arg; + if (data->name) { + qemu_thread_set_name(data->name); + g_clear_pointer(&data->name, g_free); + } qemu_thread_data = data; qemu_thread_exit(start_routine(thread_arg)); abort(); @@ -328,23 +333,20 @@ void *qemu_thread_join(QemuThread *thread) return ret; } -static bool set_thread_description(HANDLE h, const char *name) +void qemu_thread_set_name(const char *name) { - HRESULT hr; g_autofree wchar_t *namew = NULL; - if (!load_set_thread_description()) { - return false; + if (!load_thread_description()) { + return; } namew = g_utf8_to_utf16(name, -1, NULL, NULL, NULL); if (!namew) { - return false; + return; } - hr = SetThreadDescriptionFunc(h, namew); - - return SUCCEEDED(hr); + SetThreadDescriptionFunc(GetCurrentThread(), namew); } void qemu_thread_create(QemuThread *thread, const char *name, @@ -359,6 +361,7 @@ void qemu_thread_create(QemuThread *thread, const char *name, data->arg = arg; data->mode = mode; data->exited = false; + data->name = g_strdup(name); notifier_list_init(&data->exit); if (data->mode != QEMU_THREAD_DETACHED) { @@ -370,9 +373,6 @@ void qemu_thread_create(QemuThread *thread, const char *name, if (!hThread) { error_exit(GetLastError(), __func__); } - if (name_threads && name && !set_thread_description(hThread, name)) { - fprintf(stderr, "qemu: failed to set thread description: %s\n", name); - } CloseHandle(hThread); thread->data = data; @@ -421,3 +421,38 @@ bool qemu_thread_is_self(QemuThread *thread) { return GetCurrentThreadId() == thread->tid; } + +static __thread char namebuf[64]; + +const char *qemu_thread_get_name(void) +{ + HRESULT hr; + wchar_t *namew = NULL; + g_autofree char *name = NULL; + + if (namebuf[0] != '\0') { + return namebuf; + } + + if (!load_thread_description()) { + goto error; + } + + hr = GetThreadDescriptionFunc(GetCurrentThread(), &namew); + if (!SUCCEEDED(hr)) { + goto error; + } + + name = g_utf16_to_utf8(namew, -1, NULL, NULL, NULL); + LocalFree(namew); + if (!name) { + goto error; + } + + g_strlcpy(namebuf, name, G_N_ELEMENTS(namebuf)); + return namebuf; + + error: + g_strlcpy(namebuf, "unnamed", G_N_ELEMENTS(namebuf)); + return namebuf; +} diff --git a/util/rcu.c b/util/rcu.c index b703c86f15a3..acac9446ea98 100644 --- a/util/rcu.c +++ b/util/rcu.c @@ -43,10 +43,14 @@ #define RCU_GP_LOCKED (1UL << 0) #define RCU_GP_CTR (1UL << 1) + +#define RCU_CALL_MIN_SIZE 30 + unsigned long rcu_gp_ctr = RCU_GP_LOCKED; QemuEvent rcu_gp_event; static int in_drain_call_rcu; +static int rcu_call_count; static QemuMutex rcu_registry_lock; static QemuMutex rcu_sync_lock; @@ -76,15 +80,29 @@ static void wait_for_readers(void) { ThreadList qsreaders = QLIST_HEAD_INITIALIZER(qsreaders); struct rcu_reader_data *index, *tmp; + int sleeps = 0; + bool forced = false; for (;;) { - /* We want to be notified of changes made to rcu_gp_ongoing - * while we walk the list. + /* + * Force the grace period to end and wait for it if any of the + * following heuristical conditions are satisfied: + * - A decent number of callbacks piled up. + * - It timed out. + * - It is in a drain_call_rcu() call. + * + * Otherwise, periodically poll the grace period, hoping it ends + * promptly. */ - qemu_event_reset(&rcu_gp_event); + if (!forced && + (qatomic_read(&rcu_call_count) >= RCU_CALL_MIN_SIZE || + sleeps >= 5 || qatomic_read(&in_drain_call_rcu))) { + forced = true; - QLIST_FOREACH(index, ®istry, node) { - qatomic_set(&index->waiting, true); + QLIST_FOREACH(index, ®istry, node) { + notifier_list_notify(&index->force_rcu, NULL); + qatomic_set(&index->waiting, true); + } } /* Here, order the stores to index->waiting before the loads of @@ -106,8 +124,6 @@ static void wait_for_readers(void) * get some extra futex wakeups. */ qatomic_set(&index->waiting, false); - } else if (qatomic_read(&in_drain_call_rcu)) { - notifier_list_notify(&index->force_rcu, NULL); } } @@ -115,7 +131,8 @@ static void wait_for_readers(void) break; } - /* Wait for one thread to report a quiescent state and try again. + /* + * Sleep for a while and try again. * Release rcu_registry_lock, so rcu_(un)register_thread() doesn't * wait too much time. * @@ -133,7 +150,20 @@ static void wait_for_readers(void) * rcu_registry_lock is released. */ qemu_mutex_unlock(&rcu_registry_lock); - qemu_event_wait(&rcu_gp_event); + + if (forced) { + qemu_event_wait(&rcu_gp_event); + + /* + * We want to be notified of changes made to rcu_gp_ongoing + * while we walk the list. + */ + qemu_event_reset(&rcu_gp_event); + } else { + g_usleep(10000); + sleeps++; + } + qemu_mutex_lock(&rcu_registry_lock); } @@ -173,15 +203,11 @@ void synchronize_rcu(void) } } - -#define RCU_CALL_MIN_SIZE 30 - /* Multi-producer, single-consumer queue based on urcu/static/wfqueue.h * from liburcu. Note that head is only used by the consumer. */ static struct rcu_head dummy; static struct rcu_head *head = &dummy, **tail = &dummy.next; -static int rcu_call_count; static QemuEvent rcu_call_ready_event; static void enqueue(struct rcu_head *node) @@ -259,30 +285,27 @@ static void *call_rcu_thread(void *opaque) rcu_register_thread(); for (;;) { - int tries = 0; - int n = qatomic_read(&rcu_call_count); + int n; - /* Heuristically wait for a decent number of callbacks to pile up. + /* * Fetch rcu_call_count now, we only must process elements that were * added before synchronize_rcu() starts. */ - while (n == 0 || (n < RCU_CALL_MIN_SIZE && ++tries <= 5)) { - g_usleep(10000); - if (n == 0) { - qemu_event_reset(&rcu_call_ready_event); - n = qatomic_read(&rcu_call_count); - if (n == 0) { + for (;;) { + qemu_event_reset(&rcu_call_ready_event); + n = qatomic_read(&rcu_call_count); + if (n) { + break; + } + #if defined(CONFIG_MALLOC_TRIM) - malloc_trim(4 * 1024 * 1024); + malloc_trim(4 * 1024 * 1024); #endif - qemu_event_wait(&rcu_call_ready_event); - } - } - n = qatomic_read(&rcu_call_count); + qemu_event_wait(&rcu_call_ready_event); } - qatomic_sub(&rcu_call_count, n); synchronize_rcu(); + qatomic_sub(&rcu_call_count, n); bql_lock(); while (n > 0) { node = try_dequeue();