* first commit for Rust support

* add CI job using Fedora + Rust nightly
 * fix detection of ATOMIC128 on x86_64
 * fix compilation with Sphinx 8.1.0
 -----BEGIN PGP SIGNATURE-----
 
 iQFIBAABCAAyFiEE8TM4V0tmI4mGbHaCv/vSX3jHroMFAmcJEKUUHHBib256aW5p
 QHJlZGhhdC5jb20ACgkQv/vSX3jHroOSZQf+LlvZm9npHR6lZ9DEruhu/uf0c1gO
 9+dBJiKQ1OWopSQOqEgOsLL0J123Ls4V8O3tzZwIDuuRofCB2+wKswad6CHoydJx
 4p9rRXv6MLlnTqqGxemm/dPZqJ7+6L0poHoDKW+s7AgfVDshhj1RSbQfs8Ujh41F
 f1sdi3DzopVWtK4CE+8/UeLy5Cxlixke9SKhYQrFHrdsANARP81gxQjczKApMc1z
 v9qkrLtkM06VUyuvbPps7CHSHDpzx9mXcmkkPgLqLX9MfbCztzi44aVSaS9HYk5G
 y54dSKdY7VJEuGhG916G+GMDJyow4nhT9Gk6tWtk63TQN5nExVsoZMOmdw==
 =PFGL
 -----END PGP SIGNATURE-----

Merge tag 'for-upstream' of https://gitlab.com/bonzini/qemu into staging

* first commit for Rust support
* add CI job using Fedora + Rust nightly
* fix detection of ATOMIC128 on x86_64
* fix compilation with Sphinx 8.1.0

# -----BEGIN PGP SIGNATURE-----
#
# iQFIBAABCAAyFiEE8TM4V0tmI4mGbHaCv/vSX3jHroMFAmcJEKUUHHBib256aW5p
# QHJlZGhhdC5jb20ACgkQv/vSX3jHroOSZQf+LlvZm9npHR6lZ9DEruhu/uf0c1gO
# 9+dBJiKQ1OWopSQOqEgOsLL0J123Ls4V8O3tzZwIDuuRofCB2+wKswad6CHoydJx
# 4p9rRXv6MLlnTqqGxemm/dPZqJ7+6L0poHoDKW+s7AgfVDshhj1RSbQfs8Ujh41F
# f1sdi3DzopVWtK4CE+8/UeLy5Cxlixke9SKhYQrFHrdsANARP81gxQjczKApMc1z
# v9qkrLtkM06VUyuvbPps7CHSHDpzx9mXcmkkPgLqLX9MfbCztzi44aVSaS9HYk5G
# y54dSKdY7VJEuGhG916G+GMDJyow4nhT9Gk6tWtk63TQN5nExVsoZMOmdw==
# =PFGL
# -----END PGP SIGNATURE-----
# gpg: Signature made Fri 11 Oct 2024 12:48:53 BST
# gpg:                using RSA key F13338574B662389866C7682BFFBD25F78C7AE83
# gpg:                issuer "pbonzini@redhat.com"
# gpg: Good signature from "Paolo Bonzini <bonzini@gnu.org>" [full]
# gpg:                 aka "Paolo Bonzini <pbonzini@redhat.com>" [full]
# Primary key fingerprint: 46F5 9FBD 57D6 12E7 BFD4  E2F7 7E15 100C CD36 69B1
#      Subkey fingerprint: F133 3857 4B66 2389 866C  7682 BFFB D25F 78C7 AE83

* tag 'for-upstream' of https://gitlab.com/bonzini/qemu:
  docs: use consistent markup for footnotes
  docs: avoid footnotes consisting of just URLs
  docs: fix invalid footnote syntax
  gitlab-ci: add Rust-enabled CI job
  dockerfiles: add a Dockerfile using a nightly Rust toolchain
  meson: ensure -mcx16 is passed when detecting ATOMIC128
  meson: define qemu_isa_flags
  meson: fix machine option for x86_version
  rust: add PL011 device model
  rust: add utility procedural macro crate
  scripts/archive-source: find directory name for subprojects
  rust: add crate to expose bindings and interfaces
  meson.build: add HAVE_GLIB_WITH_ALIGNED_ALLOC flag
  .gitattributes: add Rust diff and merge attributes
  rust: add bindgen step as a meson dependency
  configure, meson: detect Rust toolchain
  build-sys: Add rust feature option
  Require meson version 1.5.0

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2024-10-11 18:19:37 +01:00
commit b38d263bca
88 changed files with 3474 additions and 75 deletions

3
.gitattributes vendored
View File

@ -2,3 +2,6 @@
*.h.inc diff=c *.h.inc diff=c
*.m diff=objc *.m diff=objc
*.py diff=python *.py diff=python
*.rs diff=rust
*.rs.inc diff=rust
Cargo.lock diff=toml merge=binary

View File

@ -120,6 +120,19 @@ build-system-fedora:
xtensa-softmmu m68k-softmmu riscv32-softmmu ppc-softmmu sparc64-softmmu xtensa-softmmu m68k-softmmu riscv32-softmmu ppc-softmmu sparc64-softmmu
MAKE_CHECK_ARGS: check-build MAKE_CHECK_ARGS: check-build
build-system-fedora-rust-nightly:
extends:
- .native_build_job_template
- .native_build_artifact_template
needs:
job: amd64-fedora-rust-nightly-container
variables:
IMAGE: fedora-rust-nightly
CONFIGURE_ARGS: --disable-docs --enable-rust
TARGETS: aarch64-softmmu
MAKE_CHECK_ARGS: check-build
allow_failure: true
check-system-fedora: check-system-fedora:
extends: .native_test_job_template extends: .native_test_job_template
needs: needs:

View File

@ -27,3 +27,9 @@ python-container:
extends: .container_job_template extends: .container_job_template
variables: variables:
NAME: python NAME: python
amd64-fedora-rust-nightly-container:
extends: .container_job_template
variables:
NAME: fedora-rust-nightly
allow_failure: true

View File

@ -4,3 +4,4 @@ source accel/Kconfig
source target/Kconfig source target/Kconfig
source hw/Kconfig source hw/Kconfig
source semihosting/Kconfig source semihosting/Kconfig
source rust/Kconfig

View File

@ -52,3 +52,6 @@ config VFIO_USER_SERVER_ALLOWED
config HV_BALLOON_POSSIBLE config HV_BALLOON_POSSIBLE
bool bool
config HAVE_RUST
bool

View File

@ -1134,6 +1134,11 @@ F: include/hw/*/microbit*.h
F: tests/qtest/microbit-test.c F: tests/qtest/microbit-test.c
F: docs/system/arm/nrf.rst F: docs/system/arm/nrf.rst
ARM PL011 Rust device
M: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
S: Maintained
F: rust/hw/char/pl011/
AVR Machines AVR Machines
------------- -------------
@ -3286,6 +3291,13 @@ F: hw/core/register.c
F: include/hw/register.h F: include/hw/register.h
F: include/hw/registerfields.h F: include/hw/registerfields.h
Rust
M: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
S: Maintained
F: rust/qemu-api
F: rust/qemu-api-macros
F: rust/rustfmt.toml
SLIRP SLIRP
M: Samuel Thibault <samuel.thibault@ens-lyon.org> M: Samuel Thibault <samuel.thibault@ens-lyon.org>
S: Maintained S: Maintained
@ -4183,6 +4195,15 @@ F: docs/sphinx/
F: docs/_templates/ F: docs/_templates/
F: docs/devel/docs.rst F: docs/devel/docs.rst
Rust build system integration
M: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
S: Maintained
F: scripts/rust/
F: rust/.gitignore
F: rust/Kconfig
F: rust/meson.build
F: rust/wrapper.h
Miscellaneous Miscellaneous
------------- -------------
Performance Tools and Tests Performance Tools and Tests

170
configure vendored
View File

@ -207,6 +207,8 @@ for opt do
;; ;;
--objcc=*) objcc="$optarg" --objcc=*) objcc="$optarg"
;; ;;
--rustc=*) RUSTC="$optarg"
;;
--cpu=*) cpu="$optarg" --cpu=*) cpu="$optarg"
;; ;;
--extra-cflags=*) --extra-cflags=*)
@ -252,6 +254,8 @@ python=
download="enabled" download="enabled"
skip_meson=no skip_meson=no
use_containers="yes" use_containers="yes"
rust="disabled"
rust_target_triple=""
gdb_bin=$(command -v "gdb-multiarch" || command -v "gdb") gdb_bin=$(command -v "gdb-multiarch" || command -v "gdb")
gdb_arches="" gdb_arches=""
@ -310,6 +314,7 @@ objcopy="${OBJCOPY-${cross_prefix}objcopy}"
ld="${LD-${cross_prefix}ld}" ld="${LD-${cross_prefix}ld}"
ranlib="${RANLIB-${cross_prefix}ranlib}" ranlib="${RANLIB-${cross_prefix}ranlib}"
nm="${NM-${cross_prefix}nm}" nm="${NM-${cross_prefix}nm}"
readelf="${READELF-${cross_prefix}readelf}"
strip="${STRIP-${cross_prefix}strip}" strip="${STRIP-${cross_prefix}strip}"
widl="${WIDL-${cross_prefix}widl}" widl="${WIDL-${cross_prefix}widl}"
windres="${WINDRES-${cross_prefix}windres}" windres="${WINDRES-${cross_prefix}windres}"
@ -317,6 +322,8 @@ windmc="${WINDMC-${cross_prefix}windmc}"
pkg_config="${PKG_CONFIG-${cross_prefix}pkg-config}" pkg_config="${PKG_CONFIG-${cross_prefix}pkg-config}"
sdl2_config="${SDL2_CONFIG-${cross_prefix}sdl2-config}" sdl2_config="${SDL2_CONFIG-${cross_prefix}sdl2-config}"
rustc="${RUSTC-rustc}"
check_define() { check_define() {
cat > $TMPC <<EOF cat > $TMPC <<EOF
#if !defined($1) #if !defined($1)
@ -425,6 +432,7 @@ fi
# Please keep it sorted and synchronized with meson.build's host_arch. # Please keep it sorted and synchronized with meson.build's host_arch.
host_arch= host_arch=
linux_arch= linux_arch=
raw_cpu=$cpu
case "$cpu" in case "$cpu" in
aarch64) aarch64)
host_arch=aarch64 host_arch=aarch64
@ -658,6 +666,8 @@ for opt do
;; ;;
--objcc=*) --objcc=*)
;; ;;
--rustc=*)
;;
--make=*) --make=*)
;; ;;
--install=*) --install=*)
@ -777,8 +787,14 @@ for opt do
;; ;;
--container-engine=*) container_engine="$optarg" --container-engine=*) container_engine="$optarg"
;; ;;
--rust-target-triple=*) rust_target_triple="$optarg"
;;
--gdb=*) gdb_bin="$optarg" --gdb=*) gdb_bin="$optarg"
;; ;;
--enable-rust) rust=enabled
;;
--disable-rust) rust=disabled
;;
# everything else has the same name in configure and meson # everything else has the same name in configure and meson
--*) meson_option_parse "$opt" "$optarg" --*) meson_option_parse "$opt" "$optarg"
;; ;;
@ -881,6 +897,7 @@ Advanced options (experts only):
at build time [$host_cc] at build time [$host_cc]
--cxx=CXX use C++ compiler CXX [$cxx] --cxx=CXX use C++ compiler CXX [$cxx]
--objcc=OBJCC use Objective-C compiler OBJCC [$objcc] --objcc=OBJCC use Objective-C compiler OBJCC [$objcc]
--rustc=RUSTC use Rust compiler RUSTC [$rustc]
--extra-cflags=CFLAGS append extra C compiler flags CFLAGS --extra-cflags=CFLAGS append extra C compiler flags CFLAGS
--extra-cxxflags=CXXFLAGS append extra C++ compiler flags CXXFLAGS --extra-cxxflags=CXXFLAGS append extra C++ compiler flags CXXFLAGS
--extra-objcflags=OBJCFLAGS append extra Objective C compiler flags OBJCFLAGS --extra-objcflags=OBJCFLAGS append extra Objective C compiler flags OBJCFLAGS
@ -891,8 +908,9 @@ Advanced options (experts only):
--python=PYTHON use specified python [$python] --python=PYTHON use specified python [$python]
--ninja=NINJA use specified ninja [$ninja] --ninja=NINJA use specified ninja [$ninja]
--static enable static build [$static] --static enable static build [$static]
--without-default-features default all --enable-* options to "disabled" --rust-target-triple=TRIPLE compilation target for Rust code [autodetect]
--without-default-devices do not include any device that is not needed to --without-default-features default all --enable-* options to "disabled"
--without-default-devices do not include any device that is not needed to
start the emulator (only use if you are including start the emulator (only use if you are including
desired devices in configs/devices/) desired devices in configs/devices/)
--with-devices-ARCH=NAME override default configs/devices --with-devices-ARCH=NAME override default configs/devices
@ -1166,6 +1184,132 @@ EOF
fi fi
fi fi
##########################################
# detect rust triple
if test "$rust" != disabled && has "$rustc" && $rustc -vV > "${TMPDIR1}/${TMPB}.out"; then
rust_host_triple=$(sed -n 's/^host: //p' "${TMPDIR1}/${TMPB}.out")
else
if test "$rust" = enabled; then
error_exit "could not execute rustc binary \"$rustc\""
fi
rust=disabled
fi
if test "$rust" != disabled && test -z "$rust_target_triple"; then
# arch and os generally matches between meson and rust
rust_arch=$host_arch
rust_os=$host_os
rust_machine=unknown
rust_osvariant=
# tweak rust_os if needed; also, machine and variant depend on the OS
android=no
case "$host_os" in
darwin)
# e.g. aarch64-apple-darwin
rust_machine=apple
;;
linux)
# detect android/glibc/musl
if check_define __ANDROID__; then
rust_osvariant=android
android=yes
else
cat > $TMPC << EOF
#define _GNU_SOURCE
#include <features.h>
#ifndef __USE_GNU
error using musl
#endif
EOF
if compile_object; then
rust_osvariant=gnu
else
rust_osvariant=musl
fi
fi
case "$host_arch" in
arm)
# e.g. arm-unknown-linux-gnueabi, arm-unknown-linux-gnueabihf
write_c_skeleton
compile_object
if $READELF -A $TMPO | grep Tag_API_VFP_args: > /dev/null; then
rust_osvariant=${rust_osvariant}eabihf
else
rust_osvariant=${rust_osvariant}eabi
fi
;;
mips64)
# e.g. mips64-unknown-linux-gnuabi64
rust_osvariant=${rust_osvariant}abi64
;;
esac
;;
netbsd)
# e.g. arm-unknown-netbsd-eabihf
test "$host_arch" = arm && rust_osvariant=eabihf
;;
sunos)
rust_machine=pc
rust_os=solaris
;;
windows)
# e.g. aarch64-pc-windows-gnullvm, x86_64-pc-windows-gnu (MSVC not supported)
rust_machine=pc
if test "$host_arch" = aarch64; then
rust_osvariant=gnullvm
else
rust_osvariant=gnu
fi
;;
esac
# now tweak the architecture part, possibly based on pre-canonicalization --cpu
case "$host_arch" in
arm)
# preserve ISA version (armv7 etc.) from $raw_cpu if passed via --cpu
rust_arch=$raw_cpu
test "$rust_arch" = arm && test "$rust_os" != linux && rust_arch=armv7
;;
mips|mips64)
# preserve ISA version (mipsisa64r6 etc.) and include endianness
rust_arch=${raw_cpu%el}
test "$bigendian" = no && rust_arch=${rust_arch}el
;;
riscv32|riscv64)
# e.g. riscv64gc-unknown-linux-gnu, but riscv64-linux-android
test "$android" = no && rust_arch=${rust_arch}gc
;;
sparc64)
if test "$rust_os" = solaris; then
rust_arch=sparcv9
rust_machine=sun
fi
;;
x86_64)
# e.g. x86_64-unknown-linux-gnux32
test "$raw_cpu" = x32 && rust_osvariant=${rust_osvariant}x32
;;
esac
if test "$android" = yes; then
# e.g. aarch64-linux-android
rust_target_triple=$rust_arch-$rust_os-$rust_osvariant
else
rust_target_triple=$rust_arch-$rust_machine-$rust_os${rust_osvariant:+-$rust_osvariant}
fi
fi
########################################## ##########################################
# functions to probe cross compilers # functions to probe cross compilers
@ -1628,6 +1772,9 @@ if test "$container" != no; then
echo "RUNC=$runc" >> $config_host_mak echo "RUNC=$runc" >> $config_host_mak
fi fi
echo "SUBDIRS=$subdirs" >> $config_host_mak echo "SUBDIRS=$subdirs" >> $config_host_mak
if test "$rust" != disabled; then
echo "RUST_TARGET_TRIPLE=$rust_target_triple" >> $config_host_mak
fi
echo "PYTHON=$python" >> $config_host_mak echo "PYTHON=$python" >> $config_host_mak
echo "MKVENV_ENSUREGROUP=$mkvenv ensuregroup $mkvenv_online_flag" >> $config_host_mak echo "MKVENV_ENSUREGROUP=$mkvenv ensuregroup $mkvenv_online_flag" >> $config_host_mak
echo "GENISOIMAGE=$genisoimage" >> $config_host_mak echo "GENISOIMAGE=$genisoimage" >> $config_host_mak
@ -1764,12 +1911,20 @@ if test "$skip_meson" = no; then
echo "c = [$(meson_quote $cc $CPU_CFLAGS)]" >> $cross echo "c = [$(meson_quote $cc $CPU_CFLAGS)]" >> $cross
test -n "$cxx" && echo "cpp = [$(meson_quote $cxx $CPU_CFLAGS)]" >> $cross test -n "$cxx" && echo "cpp = [$(meson_quote $cxx $CPU_CFLAGS)]" >> $cross
test -n "$objcc" && echo "objc = [$(meson_quote $objcc $CPU_CFLAGS)]" >> $cross test -n "$objcc" && echo "objc = [$(meson_quote $objcc $CPU_CFLAGS)]" >> $cross
if test "$rust" != disabled; then
if test "$rust_host_triple" != "$rust_target_triple"; then
echo "rust = [$(meson_quote $rustc --target "$rust_target_triple")]" >> $cross
else
echo "rust = [$(meson_quote $rustc)]" >> $cross
fi
fi
echo "ar = [$(meson_quote $ar)]" >> $cross echo "ar = [$(meson_quote $ar)]" >> $cross
echo "dlltool = [$(meson_quote $dlltool)]" >> $cross echo "dlltool = [$(meson_quote $dlltool)]" >> $cross
echo "nm = [$(meson_quote $nm)]" >> $cross echo "nm = [$(meson_quote $nm)]" >> $cross
echo "pkgconfig = [$(meson_quote $pkg_config)]" >> $cross echo "pkgconfig = [$(meson_quote $pkg_config)]" >> $cross
echo "pkg-config = [$(meson_quote $pkg_config)]" >> $cross echo "pkg-config = [$(meson_quote $pkg_config)]" >> $cross
echo "ranlib = [$(meson_quote $ranlib)]" >> $cross echo "ranlib = [$(meson_quote $ranlib)]" >> $cross
echo "readelf = [$(meson_quote $readelf)]" >> $cross
if has $sdl2_config; then if has $sdl2_config; then
echo "sdl2-config = [$(meson_quote $sdl2_config)]" >> $cross echo "sdl2-config = [$(meson_quote $sdl2_config)]" >> $cross
fi fi
@ -1799,6 +1954,9 @@ if test "$skip_meson" = no; then
echo "# Automatically generated by configure - do not modify" > $native echo "# Automatically generated by configure - do not modify" > $native
echo "[binaries]" >> $native echo "[binaries]" >> $native
echo "c = [$(meson_quote $host_cc)]" >> $native echo "c = [$(meson_quote $host_cc)]" >> $native
if test "$rust" != disabled; then
echo "rust = [$(meson_quote $rustc)]" >> $cross
fi
mv $native config-meson.native mv $native config-meson.native
meson_option_add --native-file meson_option_add --native-file
meson_option_add config-meson.native meson_option_add config-meson.native
@ -1817,6 +1975,7 @@ if test "$skip_meson" = no; then
test "$pie" = no && meson_option_add -Db_pie=false test "$pie" = no && meson_option_add -Db_pie=false
# QEMU options # QEMU options
test "$rust" != "auto" && meson_option_add "-Drust=$rust"
test "$cfi" != false && meson_option_add "-Dcfi=$cfi" "-Db_lto=$cfi" test "$cfi" != false && meson_option_add "-Dcfi=$cfi" "-Db_lto=$cfi"
test "$docs" != auto && meson_option_add "-Ddocs=$docs" test "$docs" != auto && meson_option_add "-Ddocs=$docs"
test -n "${LIB_FUZZING_ENGINE+xxx}" && meson_option_add "-Dfuzzing_engine=$LIB_FUZZING_ENGINE" test -n "${LIB_FUZZING_ENGINE+xxx}" && meson_option_add "-Dfuzzing_engine=$LIB_FUZZING_ENGINE"
@ -1901,3 +2060,10 @@ echo ' "$@"' >>config.status
chmod +x config.status chmod +x config.status
rm -r "$TMPDIR1" rm -r "$TMPDIR1"
if test "$rust" != disabled; then
echo '\nINFO: Rust bindings generation with `bindgen` might fail in some cases where'
echo 'the detected `libclang` does not match the expected `clang` version/target. In'
echo 'this case you must pass the path to `clang` and `libclang` to your build'
echo 'command invocation using the environment variables CLANG_PATH and LIBCLANG_PATH'
fi

View File

@ -204,7 +204,7 @@ They come in six kinds:
before the second with respect to the other components of the system. before the second with respect to the other components of the system.
Therefore, unlike ``smp_rmb()`` or ``qatomic_load_acquire()``, Therefore, unlike ``smp_rmb()`` or ``qatomic_load_acquire()``,
``smp_read_barrier_depends()`` can be just a compiler barrier on ``smp_read_barrier_depends()`` can be just a compiler barrier on
weakly-ordered architectures such as Arm or PPC[#]_. weakly-ordered architectures such as Arm or PPC\ [#alpha]_.
Note that the first load really has to have a _data_ dependency and not Note that the first load really has to have a _data_ dependency and not
a control dependency. If the address for the second load is dependent a control dependency. If the address for the second load is dependent
@ -212,7 +212,7 @@ They come in six kinds:
than actually loading the address itself, then it's a _control_ than actually loading the address itself, then it's a _control_
dependency and a full read barrier or better is required. dependency and a full read barrier or better is required.
.. [#] The DEC Alpha is an exception, because ``smp_read_barrier_depends()`` .. [#alpha] The DEC Alpha is an exception, because ``smp_read_barrier_depends()``
needs a processor barrier. On strongly-ordered architectures such needs a processor barrier. On strongly-ordered architectures such
as x86 or s390, ``smp_rmb()`` and ``qatomic_load_acquire()`` can as x86 or s390, ``smp_rmb()`` and ``qatomic_load_acquire()`` can
also be compiler barriers only. also be compiler barriers only.
@ -295,7 +295,7 @@ Acquire/release pairing and the *synchronizes-with* relation
------------------------------------------------------------ ------------------------------------------------------------
Atomic operations other than ``qatomic_set()`` and ``qatomic_read()`` have Atomic operations other than ``qatomic_set()`` and ``qatomic_read()`` have
either *acquire* or *release* semantics [#rmw]_. This has two effects: either *acquire* or *release* semantics\ [#rmw]_. This has two effects:
.. [#rmw] Read-modify-write operations can have both---acquire applies to the .. [#rmw] Read-modify-write operations can have both---acquire applies to the
read part, and release to the write. read part, and release to the write.

View File

@ -145,13 +145,13 @@ was installed in the ``site-packages`` directory of another interpreter,
or with the wrong ``pip`` program. or with the wrong ``pip`` program.
If a package is available for the chosen interpreter, ``configure`` If a package is available for the chosen interpreter, ``configure``
prepares a small script that invokes it from the venv itself[#distlib]_. prepares a small script that invokes it from the venv itself\ [#distlib]_.
If not, ``configure`` can also optionally install dependencies in the If not, ``configure`` can also optionally install dependencies in the
virtual environment with ``pip``, either from wheels in ``python/wheels`` virtual environment with ``pip``, either from wheels in ``python/wheels``
or by downloading the package with PyPI. Downloading can be disabled with or by downloading the package with PyPI. Downloading can be disabled with
``--disable-download``; and anyway, it only happens when a ``configure`` ``--disable-download``; and anyway, it only happens when a ``configure``
option (currently, only ``--enable-docs``) is explicitly enabled but option (currently, only ``--enable-docs``) is explicitly enabled but
the dependencies are not present[#pip]_. the dependencies are not present\ [#pip]_.
.. [#distlib] The scripts are created based on the package's metadata, .. [#distlib] The scripts are created based on the package's metadata,
specifically the ``console_script`` entry points. This is the specifically the ``console_script`` entry points. This is the
@ -333,7 +333,7 @@ into each emulator:
``default-configs/targets/*.mak`` ``default-configs/targets/*.mak``
These files mostly define symbols that appear in the ``*-config-target.h`` These files mostly define symbols that appear in the ``*-config-target.h``
file for each emulator [#cfgtarget]_. However, the ``TARGET_ARCH`` file for each emulator\ [#cfgtarget]_. However, the ``TARGET_ARCH``
and ``TARGET_BASE_ARCH`` will also be used to select the ``hw/`` and and ``TARGET_BASE_ARCH`` will also be used to select the ``hw/`` and
``target/`` subdirectories that are compiled into each target. ``target/`` subdirectories that are compiled into each target.

View File

@ -95,7 +95,7 @@ guest CPU state in case of a guest CPU exception. This is passed
to ``cpu_restore_state()``. Therefore the value should either be 0, to ``cpu_restore_state()``. Therefore the value should either be 0,
to indicate that the guest CPU state is already synchronized, or to indicate that the guest CPU state is already synchronized, or
the result of ``GETPC()`` from the top level ``HELPER(foo)`` the result of ``GETPC()`` from the top level ``HELPER(foo)``
function, which is a return address into the generated code [#gpc]_. function, which is a return address into the generated code\ [#gpc]_.
.. [#gpc] Note that ``GETPC()`` should be used with great care: calling .. [#gpc] Note that ``GETPC()`` should be used with great care: calling
it in other functions that are *not* the top level it in other functions that are *not* the top level

View File

@ -99,9 +99,9 @@ members of the QEMU community, you should make arrangements to attend
a `KeySigningParty <https://wiki.qemu.org/KeySigningParty>`__ (for a `KeySigningParty <https://wiki.qemu.org/KeySigningParty>`__ (for
example at KVM Forum) or make alternative arrangements to have your example at KVM Forum) or make alternative arrangements to have your
key signed by an attendee. Key signing requires meeting another key signed by an attendee. Key signing requires meeting another
community member **in person** [#]_ so please make appropriate community member **in person**\ [#2020]_ so please make appropriate
arrangements. arrangements.
.. [#] In recent pandemic times we have had to exercise some .. [#2020] In recent pandemic times we have had to exercise some
flexibility here. Maintainers still need to sign their pull flexibility here. Maintainers still need to sign their pull
requests though. requests though.

View File

@ -44,7 +44,7 @@ Use-cases
The mapped-ram feature was designed for use cases where the migration The mapped-ram feature was designed for use cases where the migration
stream will be directed to a file in the filesystem and not stream will be directed to a file in the filesystem and not
immediately restored on the destination VM [#]_. These could be immediately restored on the destination VM\ [#alternatives]_. These could be
thought of as snapshots. We can further categorize them into live and thought of as snapshots. We can further categorize them into live and
non-live. non-live.
@ -70,7 +70,7 @@ mapped-ram in this scenario is portability since background-snapshot
depends on async dirty tracking (KVM_GET_DIRTY_LOG) which is not depends on async dirty tracking (KVM_GET_DIRTY_LOG) which is not
supported outside of Linux. supported outside of Linux.
.. [#] While this same effect could be obtained with the usage of .. [#alternatives] While this same effect could be obtained with the usage of
snapshots or the ``file:`` migration alone, mapped-ram provides snapshots or the ``file:`` migration alone, mapped-ram provides
a performance increase for VMs with larger RAM sizes (10s to a performance increase for VMs with larger RAM sizes (10s to
100s of GiBs), specially if the VM has been stopped beforehand. 100s of GiBs), specially if the VM has been stopped beforehand.

View File

@ -30,15 +30,20 @@ OS modules are generally written using low level languages such as C and
low level assembly machine language. Writing test routines in a low level low level assembly machine language. Writing test routines in a low level
language makes things more cumbersome. These and other reasons makes using language makes things more cumbersome. These and other reasons makes using
bios-bits very attractive for testing bioses. More details on the inspiration bios-bits very attractive for testing bioses. More details on the inspiration
for developing biosbits and its real life uses can be found in [#a]_ and [#b]_. for developing biosbits and its real life uses were presented `at Plumbers
in 2011 <Plumbers_>`__ and `at Linux.conf.au in 2012 <Linux.conf.au_>`__.
For QEMU, we maintain a fork of bios bits in gitlab along with all the For QEMU, we maintain a fork of bios bits in `gitlab`_, along with all
dependent submodules `here <https://gitlab.com/qemu-project/biosbits-bits>`__. the dependent submodules. This fork contains numerous fixes, a newer
This fork contains numerous fixes, a newer acpica and changes specific to acpica and changes specific to running these functional QEMU tests using
running these functional QEMU tests using bits. The author of this document bits. The author of this document is the current maintainer of the QEMU
is the sole maintainer of the QEMU fork of bios bits repository. For more fork of bios bits repository. For more information, please see `the
information, please see author's `FOSDEM talk on this bios-bits based test author's FOSDEM presentation <FOSDEM_>`__ on this bios-bits based test framework.
framework <https://fosdem.org/2024/schedule/event/fosdem-2024-2262-exercising-qemu-generated-acpi-smbios-tables-using-biosbits-from-within-a-guest-vm-/>`__.
.. _Plumbers: https://blog.linuxplumbersconf.org/2011/ocw/system/presentations/867/original/bits.pdf
.. _Linux.conf.au: https://www.youtube.com/watch?v=36QIepyUuhg
.. _gitlab: https://gitlab.com/qemu-project/biosbits-bits
.. _FOSDEM: https://fosdem.org/2024/schedule/event/fosdem-2024-2262-exercising-qemu-generated-acpi-smbios-tables-using-biosbits-from-within-a-guest-vm-/
********************************* *********************************
Description of the test framework Description of the test framework
@ -148,8 +153,3 @@ Under ``tests/functional/`` as the root we have:
Author: Ani Sinha <anisinha@redhat.com> Author: Ani Sinha <anisinha@redhat.com>
References:
-----------
.. [#a] https://blog.linuxplumbersconf.org/2011/ocw/system/presentations/867/original/bits.pdf
.. [#b] https://www.youtube.com/watch?v=36QIepyUuhg
.. [#c] https://fosdem.org/2024/schedule/event/fosdem-2024-2262-exercising-qemu-generated-acpi-smbios-tables-using-biosbits-from-within-a-guest-vm-/

View File

@ -54,11 +54,11 @@ Data Register
------------- -------------
* Read/Write (writes ignored as of QEMU v2.4, but see the DMA interface) * Read/Write (writes ignored as of QEMU v2.4, but see the DMA interface)
* Location: platform dependent (IOport [#]_ or MMIO) * Location: platform dependent (IOport\ [#placement]_ or MMIO)
* Width: 8-bit (if IOport), 8/16/32/64-bit (if MMIO) * Width: 8-bit (if IOport), 8/16/32/64-bit (if MMIO)
* Endianness: string-preserving * Endianness: string-preserving
.. [#] .. [#placement]
On platforms where the data register is exposed as an IOport, its On platforms where the data register is exposed as an IOport, its
port number will always be one greater than the port number of the port number will always be one greater than the port number of the
selector register. In other words, the two ports overlap, and can not selector register. In other words, the two ports overlap, and can not

View File

@ -9,11 +9,12 @@ The consumption is reported via MSRs (model specific registers) like
MSR_PKG_ENERGY_STATUS for the CPU package power domain. These MSRs are 64 bits MSR_PKG_ENERGY_STATUS for the CPU package power domain. These MSRs are 64 bits
registers that represent the accumulated energy consumption in micro Joules. registers that represent the accumulated energy consumption in micro Joules.
Thanks to the MSR Filtering patch [#a]_ not all MSRs are handled by KVM. Some Thanks to KVM's `MSR filtering <msr-filter-patch_>`__ functionality,
of them can now be handled by the userspace (QEMU). It uses a mechanism called not all MSRs are handled by KVM. Some of them can now be handled by the
"MSR filtering" where a list of MSRs is given at init time of a VM to KVM so userspace (QEMU); a list of MSRs is given at VM creation time to KVM, and
that a callback is put in place. The design of this patch uses only this a userspace exit occurs when they are accessed.
mechanism for handling the MSRs between guest/host.
.. _msr-filter-patch: https://patchwork.kernel.org/project/kvm/patch/20200916202951.23760-7-graf@amazon.com/
At the moment the following MSRs are involved: At the moment the following MSRs are involved:
@ -92,9 +93,12 @@ found by the sysconf system call. A typical value of clock ticks per second is
package has 4 cores, 400 ticks maximum can be scheduled on all the cores package has 4 cores, 400 ticks maximum can be scheduled on all the cores
of the package for a period of 1 second. of the package for a period of 1 second.
The /proc/[pid]/stat [#b]_ is a sysfs file that can give the executed time of a `/proc/[pid]/stat <stat_>`__ is a procfs file that can give the executed
process with the [pid] as the process ID. It gives the amount of ticks the time of a process with the [pid] as the process ID. It gives the amount
process has been scheduled in userspace (utime) and kernel space (stime). of ticks the process has been scheduled in userspace (utime) and kernel
space (stime).
.. _stat: https://man7.org/linux/man-pages/man5/proc.5.html
By reading those metrics for a thread, one can calculate the ratio of time the By reading those metrics for a thread, one can calculate the ratio of time the
package has spent executing the thread. package has spent executing the thread.
@ -148,8 +152,3 @@ Current Limitations
- Only the Package Power-Plane (MSR_PKG_ENERGY_STATUS) is reported at the - Only the Package Power-Plane (MSR_PKG_ENERGY_STATUS) is reported at the
moment. moment.
References
----------
.. [#a] https://patchwork.kernel.org/project/kvm/patch/20200916202951.23760-7-graf@amazon.com/
.. [#b] https://man7.org/linux/man-pages/man5/proc.5.html

View File

@ -20,7 +20,8 @@ config ARM_VIRT
select PCI_EXPRESS select PCI_EXPRESS
select PCI_EXPRESS_GENERIC_BRIDGE select PCI_EXPRESS_GENERIC_BRIDGE
select PFLASH_CFI01 select PFLASH_CFI01
select PL011 # UART select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select PL031 # RTC select PL031 # RTC
select PL061 # GPIO select PL061 # GPIO
select GPIO_PWR select GPIO_PWR
@ -73,7 +74,8 @@ config HIGHBANK
select AHCI select AHCI
select ARM_TIMER # sp804 select ARM_TIMER # sp804
select ARM_V7M select ARM_V7M
select PL011 # UART select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select PL022 # SPI select PL022 # SPI
select PL031 # RTC select PL031 # RTC
select PL061 # GPIO select PL061 # GPIO
@ -86,7 +88,8 @@ config INTEGRATOR
depends on TCG && ARM depends on TCG && ARM
select ARM_TIMER select ARM_TIMER
select INTEGRATOR_DEBUG select INTEGRATOR_DEBUG
select PL011 # UART select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select PL031 # RTC select PL031 # RTC
select PL041 # audio select PL041 # audio
select PL050 # keyboard/mouse select PL050 # keyboard/mouse
@ -104,7 +107,8 @@ config MUSCA
default y default y
depends on TCG && ARM depends on TCG && ARM
select ARMSSE select ARMSSE
select PL011 select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select PL031 select PL031
select SPLIT_IRQ select SPLIT_IRQ
select UNIMP select UNIMP
@ -169,7 +173,8 @@ config REALVIEW
select WM8750 # audio codec select WM8750 # audio codec
select LSI_SCSI_PCI select LSI_SCSI_PCI
select PCI select PCI
select PL011 # UART select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select PL031 # RTC select PL031 # RTC
select PL041 # audio codec select PL041 # audio codec
select PL050 # keyboard/mouse select PL050 # keyboard/mouse
@ -194,7 +199,8 @@ config SBSA_REF
select PCI_EXPRESS select PCI_EXPRESS
select PCI_EXPRESS_GENERIC_BRIDGE select PCI_EXPRESS_GENERIC_BRIDGE
select PFLASH_CFI01 select PFLASH_CFI01
select PL011 # UART select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select PL031 # RTC select PL031 # RTC
select PL061 # GPIO select PL061 # GPIO
select USB_XHCI_SYSBUS select USB_XHCI_SYSBUS
@ -218,7 +224,8 @@ config STELLARIS
select ARM_V7M select ARM_V7M
select CMSDK_APB_WATCHDOG select CMSDK_APB_WATCHDOG
select I2C select I2C
select PL011 # UART select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select PL022 # SPI select PL022 # SPI
select PL061 # GPIO select PL061 # GPIO
select SSD0303 # OLED display select SSD0303 # OLED display
@ -278,7 +285,8 @@ config VEXPRESS
select ARM_TIMER # sp804 select ARM_TIMER # sp804
select LAN9118 select LAN9118
select PFLASH_CFI01 select PFLASH_CFI01
select PL011 # UART select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select PL041 # audio codec select PL041 # audio codec
select PL181 # display select PL181 # display
select REALVIEW select REALVIEW
@ -362,7 +370,8 @@ config RASPI
default y default y
depends on TCG && ARM depends on TCG && ARM
select FRAMEBUFFER select FRAMEBUFFER
select PL011 # UART select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select SDHCI select SDHCI
select USB_DWC2 select USB_DWC2
select BCM2835_SPI select BCM2835_SPI
@ -437,7 +446,8 @@ config XLNX_VERSAL
select ARM_GIC select ARM_GIC
select CPU_CLUSTER select CPU_CLUSTER
select DEVICE_TREE select DEVICE_TREE
select PL011 select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select CADENCE select CADENCE
select VIRTIO_MMIO select VIRTIO_MMIO
select UNIMP select UNIMP

View File

@ -1,4 +1,4 @@
project('qemu', ['c'], meson_version: '>=1.1.0', project('qemu', ['c'], meson_version: '>=1.5.0',
default_options: ['warning_level=1', 'c_std=gnu11', 'cpp_std=gnu++11', 'b_colorout=auto', default_options: ['warning_level=1', 'c_std=gnu11', 'cpp_std=gnu++11', 'b_colorout=auto',
'b_staticpic=false', 'stdsplit=false', 'optimization=2', 'b_pie=true'], 'b_staticpic=false', 'stdsplit=false', 'optimization=2', 'b_pie=true'],
version: files('VERSION')) version: files('VERSION'))
@ -70,6 +70,19 @@ if host_os == 'darwin' and \
all_languages += ['objc'] all_languages += ['objc']
objc = meson.get_compiler('objc') objc = meson.get_compiler('objc')
endif endif
have_rust = false
if not get_option('rust').disabled() and add_languages('rust', required: get_option('rust'), native: false)
rustc = meson.get_compiler('rust')
have_rust = true
if rustc.version().version_compare('<1.80.0')
if get_option('rust').enabled()
error('rustc version ' + rustc.version() + ' is unsupported: Please upgrade to at least 1.80.0')
else
warning('rustc version ' + rustc.version() + ' is unsupported: Disabling Rust compilation. Please upgrade to at least 1.80.0 to use Rust.')
have_rust = false
endif
endif
endif
dtrace = not_found dtrace = not_found
stap = not_found stap = not_found
@ -322,6 +335,10 @@ elif host_os == 'windows'
endif endif
endif endif
# Choose instruction set (currently x86-only)
qemu_isa_flags = []
# __sync_fetch_and_and requires at least -march=i486. Many toolchains # __sync_fetch_and_and requires at least -march=i486. Many toolchains
# use i686 as default anyway, but for those that don't, an explicit # use i686 as default anyway, but for those that don't, an explicit
# specification is necessary # specification is necessary
@ -338,7 +355,7 @@ if host_arch == 'i386' and not cc.links('''
sfaa(&val); sfaa(&val);
return val; return val;
}''') }''')
qemu_common_flags = ['-march=i486'] + qemu_common_flags qemu_isa_flags += ['-march=i486']
endif endif
# Pick x86-64 baseline version # Pick x86-64 baseline version
@ -354,29 +371,31 @@ if host_arch in ['i386', 'x86_64']
else else
# present on basically all processors but technically not part of # present on basically all processors but technically not part of
# x86-64-v1, so only include -mneeded for x86-64 version 2 and above # x86-64-v1, so only include -mneeded for x86-64 version 2 and above
qemu_common_flags = ['-mcx16'] + qemu_common_flags qemu_isa_flags += ['-mcx16']
endif endif
endif endif
if get_option('x86_version') >= '2' if get_option('x86_version') >= '2'
qemu_common_flags = ['-mpopcnt'] + qemu_common_flags qemu_isa_flags += ['-mpopcnt']
qemu_common_flags = cc.get_supported_arguments('-mneeded') + qemu_common_flags qemu_isa_flags += cc.get_supported_arguments('-mneeded')
endif endif
if get_option('x86_version') >= '3' if get_option('x86_version') >= '3'
qemu_common_flags = ['-mmovbe', '-mabm', '-mbmi1', '-mbmi2', '-mfma', '-mf16c'] + qemu_common_flags qemu_isa_flags += ['-mmovbe', '-mabm', '-mbmi', '-mbmi2', '-mfma', '-mf16c']
endif endif
# add required vector instruction set (each level implies those below) # add required vector instruction set (each level implies those below)
if get_option('x86_version') == '1' if get_option('x86_version') == '1'
qemu_common_flags = ['-msse2'] + qemu_common_flags qemu_isa_flags += ['-msse2']
elif get_option('x86_version') == '2' elif get_option('x86_version') == '2'
qemu_common_flags = ['-msse4.2'] + qemu_common_flags qemu_isa_flags += ['-msse4.2']
elif get_option('x86_version') == '3' elif get_option('x86_version') == '3'
qemu_common_flags = ['-mavx2'] + qemu_common_flags qemu_isa_flags += ['-mavx2']
elif get_option('x86_version') == '4' elif get_option('x86_version') == '4'
qemu_common_flags = ['-mavx512f', '-mavx512bw', '-mavx512cd', '-mavx512dq', '-mavx512vl'] + qemu_common_flags qemu_isa_flags += ['-mavx512f', '-mavx512bw', '-mavx512cd', '-mavx512dq', '-mavx512vl']
endif endif
endif endif
qemu_common_flags = qemu_isa_flags + qemu_common_flags
if get_option('prefer_static') if get_option('prefer_static')
qemu_ldflags += get_option('b_pie') ? '-static-pie' : '-static' qemu_ldflags += get_option('b_pie') ? '-static-pie' : '-static'
endif endif
@ -935,7 +954,9 @@ have_xen_pci_passthrough = get_option('xen_pci_passthrough') \
################ ################
# When bumping glib minimum version, please check also whether to increase # When bumping glib minimum version, please check also whether to increase
# the _WIN32_WINNT setting in osdep.h according to the value from glib # the _WIN32_WINNT setting in osdep.h according to the value from glib.
# You should also check if any of the glib.version() checks
# below can also be removed.
glib_req_ver = '>=2.66.0' glib_req_ver = '>=2.66.0'
glib_pc = dependency('glib-2.0', version: glib_req_ver, required: true, glib_pc = dependency('glib-2.0', version: glib_req_ver, required: true,
method: 'pkg-config') method: 'pkg-config')
@ -985,6 +1006,9 @@ glib = declare_dependency(dependencies: [glib_pc, gmodule],
# TODO: remove this check and the corresponding workaround (qtree) when # TODO: remove this check and the corresponding workaround (qtree) when
# the minimum supported glib is >= 2.75.3 # the minimum supported glib is >= 2.75.3
glib_has_gslice = glib.version().version_compare('<2.75.3') glib_has_gslice = glib.version().version_compare('<2.75.3')
# Check whether glib has the aligned_alloc family of functions.
# <https://docs.gtk.org/glib/func.aligned_alloc.html>
glib_has_aligned_alloc = glib.version().version_compare('>=2.72.0')
# override glib dep to include the above refinements # override glib dep to include the above refinements
meson.override_dependency('glib-2.0', glib) meson.override_dependency('glib-2.0', glib)
@ -2158,6 +2182,7 @@ endif
config_host_data = configuration_data() config_host_data = configuration_data()
config_host_data.set('CONFIG_HAVE_RUST', have_rust)
audio_drivers_selected = [] audio_drivers_selected = []
if have_system if have_system
audio_drivers_available = { audio_drivers_available = {
@ -2516,6 +2541,7 @@ config_host_data.set('CONFIG_TIMERFD', cc.has_function('timerfd_create'))
config_host_data.set('HAVE_COPY_FILE_RANGE', cc.has_function('copy_file_range')) config_host_data.set('HAVE_COPY_FILE_RANGE', cc.has_function('copy_file_range'))
config_host_data.set('HAVE_GETIFADDRS', cc.has_function('getifaddrs')) config_host_data.set('HAVE_GETIFADDRS', cc.has_function('getifaddrs'))
config_host_data.set('HAVE_GLIB_WITH_SLICE_ALLOCATOR', glib_has_gslice) config_host_data.set('HAVE_GLIB_WITH_SLICE_ALLOCATOR', glib_has_gslice)
config_host_data.set('HAVE_GLIB_WITH_ALIGNED_ALLOC', glib_has_aligned_alloc)
config_host_data.set('HAVE_OPENPTY', cc.has_function('openpty', dependencies: util)) config_host_data.set('HAVE_OPENPTY', cc.has_function('openpty', dependencies: util))
config_host_data.set('HAVE_STRCHRNUL', cc.has_function('strchrnul')) config_host_data.set('HAVE_STRCHRNUL', cc.has_function('strchrnul'))
config_host_data.set('HAVE_SYSTEM_FUNCTION', cc.has_function('system', prefix: '#include <stdlib.h>')) config_host_data.set('HAVE_SYSTEM_FUNCTION', cc.has_function('system', prefix: '#include <stdlib.h>'))
@ -2806,7 +2832,7 @@ config_host_data.set('CONFIG_ATOMIC64', cc.links('''
__atomic_exchange_n(&x, y, __ATOMIC_RELAXED); __atomic_exchange_n(&x, y, __ATOMIC_RELAXED);
__atomic_fetch_add(&x, y, __ATOMIC_RELAXED); __atomic_fetch_add(&x, y, __ATOMIC_RELAXED);
return 0; return 0;
}''')) }''', args: qemu_isa_flags))
has_int128_type = cc.compiles(''' has_int128_type = cc.compiles('''
__int128_t a; __int128_t a;
@ -2840,7 +2866,7 @@ if has_int128_type
__atomic_compare_exchange_n(&p[4], &p[5], p[6], 0, __ATOMIC_RELAXED, __ATOMIC_RELAXED); __atomic_compare_exchange_n(&p[4], &p[5], p[6], 0, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
return 0; return 0;
}''' }'''
has_atomic128 = cc.links(atomic_test_128) has_atomic128 = cc.links(atomic_test_128, args: qemu_isa_flags)
config_host_data.set('CONFIG_ATOMIC128', has_atomic128) config_host_data.set('CONFIG_ATOMIC128', has_atomic128)
@ -2849,7 +2875,8 @@ if has_int128_type
# without optimization enabled. Try again with optimizations locally # without optimization enabled. Try again with optimizations locally
# enabled for the function. See # enabled for the function. See
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107389 # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107389
has_atomic128_opt = cc.links('__attribute__((optimize("O1")))' + atomic_test_128) has_atomic128_opt = cc.links('__attribute__((optimize("O1")))' + atomic_test_128,
args: qemu_isa_flags)
config_host_data.set('CONFIG_ATOMIC128_OPT', has_atomic128_opt) config_host_data.set('CONFIG_ATOMIC128_OPT', has_atomic128_opt)
if not has_atomic128_opt if not has_atomic128_opt
@ -2860,7 +2887,7 @@ if has_int128_type
__sync_val_compare_and_swap_16(&x, y, x); __sync_val_compare_and_swap_16(&x, y, x);
return 0; return 0;
} }
''')) ''', args: qemu_isa_flags))
endif endif
endif endif
endif endif
@ -3097,7 +3124,8 @@ host_kconfig = \
(host_os == 'linux' ? ['CONFIG_LINUX=y'] : []) + \ (host_os == 'linux' ? ['CONFIG_LINUX=y'] : []) + \
(multiprocess_allowed ? ['CONFIG_MULTIPROCESS_ALLOWED=y'] : []) + \ (multiprocess_allowed ? ['CONFIG_MULTIPROCESS_ALLOWED=y'] : []) + \
(vfio_user_server_allowed ? ['CONFIG_VFIO_USER_SERVER_ALLOWED=y'] : []) + \ (vfio_user_server_allowed ? ['CONFIG_VFIO_USER_SERVER_ALLOWED=y'] : []) + \
(hv_balloon ? ['CONFIG_HV_BALLOON_POSSIBLE=y'] : []) (hv_balloon ? ['CONFIG_HV_BALLOON_POSSIBLE=y'] : []) + \
(have_rust ? ['CONFIG_HAVE_RUST=y'] : [])
ignored = [ 'TARGET_XML_FILES', 'TARGET_ABI_DIR', 'TARGET_ARCH' ] ignored = [ 'TARGET_XML_FILES', 'TARGET_ABI_DIR', 'TARGET_ARCH' ]
@ -3491,6 +3519,7 @@ qom_ss = ss.source_set()
system_ss = ss.source_set() system_ss = ss.source_set()
specific_fuzz_ss = ss.source_set() specific_fuzz_ss = ss.source_set()
specific_ss = ss.source_set() specific_ss = ss.source_set()
rust_devices_ss = ss.source_set()
stub_ss = ss.source_set() stub_ss = ss.source_set()
trace_ss = ss.source_set() trace_ss = ss.source_set()
user_ss = ss.source_set() user_ss = ss.source_set()
@ -3877,6 +3906,74 @@ common_all = static_library('common',
implicit_include_directories: false, implicit_include_directories: false,
dependencies: common_ss.all_dependencies()) dependencies: common_ss.all_dependencies())
if have_rust and have_system
rustc_args = run_command(
find_program('scripts/rust/rustc_args.py'),
'--config-headers', meson.project_build_root() / 'config-host.h',
capture : true,
check: true).stdout().strip().split()
rustc_args += ['-D', 'unsafe_op_in_unsafe_fn']
bindgen_args = [
'--disable-header-comment',
'--raw-line', '// @generated',
'--ctypes-prefix', 'core::ffi',
'--formatter', 'rustfmt',
'--generate-block',
'--generate-cstr',
'--impl-debug',
'--merge-extern-blocks',
'--no-doc-comments',
'--use-core',
'--with-derive-default',
'--no-size_t-is-usize',
'--no-layout-tests',
'--no-prepend-enum-name',
'--allowlist-file', meson.project_source_root() + '/include/.*',
'--allowlist-file', meson.project_source_root() + '/.*',
'--allowlist-file', meson.project_build_root() + '/.*'
]
c_enums = [
'DeviceCategory',
'GpioPolarity',
'MachineInitPhase',
'MemoryDeviceInfoKind',
'MigrationPolicy',
'MigrationPriority',
'QEMUChrEvent',
'QEMUClockType',
'device_endian',
'module_init_type',
]
foreach enum : c_enums
bindgen_args += ['--rustified-enum', enum]
endforeach
c_bitfields = [
'ClockEvent',
'VMStateFlags',
]
foreach enum : c_bitfields
bindgen_args += ['--bitfield-enum', enum]
endforeach
# TODO: Remove this comment when the clang/libclang mismatch issue is solved.
#
# Rust bindings generation with `bindgen` might fail in some cases where the
# detected `libclang` does not match the expected `clang` version/target. In
# this case you must pass the path to `clang` and `libclang` to your build
# command invocation using the environment variables CLANG_PATH and
# LIBCLANG_PATH
bindings_rs = import('rust').bindgen(
input: 'rust/wrapper.h',
dependencies: common_ss.all_dependencies(),
output: 'bindings.rs',
include_directories: include_directories('.', 'include'),
bindgen_version: ['>=0.69.4'],
args: bindgen_args,
)
subdir('rust')
endif
feature_to_c = find_program('scripts/feature_to_c.py') feature_to_c = find_program('scripts/feature_to_c.py')
if host_os == 'darwin' if host_os == 'darwin'
@ -3970,6 +4067,29 @@ foreach target : target_dirs
arch_srcs += target_specific.sources() arch_srcs += target_specific.sources()
arch_deps += target_specific.dependencies() arch_deps += target_specific.dependencies()
if have_rust and have_system
target_rust = rust_devices_ss.apply(config_target, strict: false)
crates = []
foreach dep : target_rust.dependencies()
crates += dep.get_variable('crate')
endforeach
if crates.length() > 0
rlib_rs = custom_target('rust_' + target.underscorify() + '.rs',
output: 'rust_' + target.underscorify() + '.rs',
command: [find_program('scripts/rust/rust_root_crate.sh')] + crates,
capture: true,
build_by_default: true,
build_always_stale: true)
rlib = static_library('rust_' + target.underscorify(),
rlib_rs,
dependencies: target_rust.dependencies(),
override_options: ['rust_std=2021', 'build.rust_std=2021'],
rust_args: rustc_args,
rust_abi: 'c')
arch_deps += declare_dependency(link_whole: [rlib])
endif
endif
# allow using headers from the dependencies but do not include the sources, # allow using headers from the dependencies but do not include the sources,
# because this emulator only needs those in "objects". For external # because this emulator only needs those in "objects". For external
# dependencies, the full dependency is included below in the executable. # dependencies, the full dependency is included below in the executable.
@ -4308,6 +4428,12 @@ if 'objc' in all_languages
else else
summary_info += {'Objective-C compiler': false} summary_info += {'Objective-C compiler': false}
endif endif
summary_info += {'Rust support': have_rust}
if have_rust
summary_info += {'rustc version': rustc.version()}
summary_info += {'rustc': ' '.join(rustc.cmd_array())}
summary_info += {'Rust target': config_host['RUST_TARGET_TRIPLE']}
endif
option_cflags = (get_option('debug') ? ['-g'] : []) option_cflags = (get_option('debug') ? ['-g'] : [])
if get_option('optimization') != 'plain' if get_option('optimization') != 'plain'
option_cflags += ['-O' + get_option('optimization')] option_cflags += ['-O' + get_option('optimization')]

View File

@ -373,3 +373,6 @@ option('hexagon_idef_parser', type : 'boolean', value : true,
option('x86_version', type : 'combo', choices : ['0', '1', '2', '3', '4'], value: '1', option('x86_version', type : 'combo', choices : ['0', '1', '2', '3', '4'], value: '1',
description: 'tweak required x86_64 architecture version beyond compiler default') description: 'tweak required x86_64 architecture version beyond compiler default')
option('rust', type: 'feature', value: 'auto',
description: 'Rust support')

View File

@ -41,8 +41,8 @@ def main() -> int:
parser.parse_args() parser.parse_args()
packages = { packages = {
"meson==1.2.3": "meson==1.5.0":
"4533a43c34548edd1f63a276a42690fce15bde9409bcf20c4b8fa3d7e4d7cac1", "52b34f4903b882df52ad0d533146d4b992c018ea77399f825579737672ae7b20",
} }
vendor_dir = Path(__file__, "..", "..", "wheels").resolve() vendor_dir = Path(__file__, "..", "..", "wheels").resolve()

Binary file not shown.

View File

@ -19,7 +19,7 @@
[meson] [meson]
# The install key should match the version in python/wheels/ # The install key should match the version in python/wheels/
meson = { accepted = ">=1.1.0", installed = "1.2.3", canary = "meson" } meson = { accepted = ">=1.5.0", installed = "1.5.0", canary = "meson" }
pycotap = { accepted = ">=1.1.0", installed = "1.3.1" } pycotap = { accepted = ">=1.1.0", installed = "1.3.1" }
[docs] [docs]

3
rust/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Ignore any cargo development build artifacts; for qemu-wide builds, all build
# artifacts will go to the meson build directory.
target

1
rust/Kconfig Normal file
View File

@ -0,0 +1 @@
source hw/Kconfig

2
rust/hw/Kconfig Normal file
View File

@ -0,0 +1,2 @@
# devices Kconfig
source char/Kconfig

3
rust/hw/char/Kconfig Normal file
View File

@ -0,0 +1,3 @@
config X_PL011_RUST
bool
default y if HAVE_RUST

1
rust/hw/char/meson.build Normal file
View File

@ -0,0 +1 @@
subdir('pl011')

2
rust/hw/char/pl011/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# Ignore generated bindings file overrides.
src/bindings.rs.inc

134
rust/hw/char/pl011/Cargo.lock generated Normal file
View File

@ -0,0 +1,134 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "arbitrary-int"
version = "1.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c84fc003e338a6f69fbd4f7fe9f92b535ff13e9af8997f3b14b6ddff8b1df46d"
[[package]]
name = "bilge"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc707ed8ebf81de5cd6c7f48f54b4c8621760926cdf35a57000747c512e67b57"
dependencies = [
"arbitrary-int",
"bilge-impl",
]
[[package]]
name = "bilge-impl"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feb11e002038ad243af39c2068c8a72bcf147acf05025dcdb916fcc000adb2d8"
dependencies = [
"itertools",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "either"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
[[package]]
name = "itertools"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
dependencies = [
"either",
]
[[package]]
name = "pl011"
version = "0.1.0"
dependencies = [
"bilge",
"bilge-impl",
"qemu_api",
"qemu_api_macros",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6"
dependencies = [
"unicode-ident",
]
[[package]]
name = "qemu_api"
version = "0.1.0"
[[package]]
name = "qemu_api_macros"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"

View File

@ -0,0 +1,26 @@
[package]
name = "pl011"
version = "0.1.0"
edition = "2021"
authors = ["Manos Pitsidianakis <manos.pitsidianakis@linaro.org>"]
license = "GPL-2.0-or-later"
readme = "README.md"
homepage = "https://www.qemu.org"
description = "pl011 device model for QEMU"
repository = "https://gitlab.com/epilys/rust-for-qemu"
resolver = "2"
publish = false
keywords = []
categories = []
[lib]
crate-type = ["staticlib"]
[dependencies]
bilge = { version = "0.2.0" }
bilge-impl = { version = "0.2.0" }
qemu_api = { path = "../../../qemu-api" }
qemu_api_macros = { path = "../../../qemu-api-macros" }
# Do not include in any global workspace
[workspace]

View File

@ -0,0 +1,31 @@
# PL011 QEMU Device Model
This library implements a device model for the PrimeCell® UART (PL011)
device in QEMU.
## Build static lib
Host build target must be explicitly specified:
```sh
cargo build --target x86_64-unknown-linux-gnu
```
Replace host target triplet if necessary.
## Generate Rust documentation
To generate docs for this crate, including private items:
```sh
cargo doc --no-deps --document-private-items --target x86_64-unknown-linux-gnu
```
To include direct dependencies like `bilge` (bitmaps for register types):
```sh
cargo tree --depth 1 -e normal --prefix none \
| cut -d' ' -f1 \
| xargs printf -- '-p %s\n' \
| xargs cargo doc --no-deps --document-private-items --target x86_64-unknown-linux-gnu
```

View File

@ -0,0 +1,26 @@
subproject('bilge-0.2-rs', required: true)
subproject('bilge-impl-0.2-rs', required: true)
bilge_dep = dependency('bilge-0.2-rs')
bilge_impl_dep = dependency('bilge-impl-0.2-rs')
_libpl011_rs = static_library(
'pl011',
files('src/lib.rs'),
override_options: ['rust_std=2021', 'build.rust_std=2021'],
rust_abi: 'rust',
dependencies: [
bilge_dep,
bilge_impl_dep,
qemu_api,
qemu_api_macros,
],
)
rust_devices_ss.add(when: 'CONFIG_X_PL011_RUST', if_true: [declare_dependency(
link_whole: [_libpl011_rs],
# Putting proc macro crates in `dependencies` is necessary for Meson to find
# them when compiling the root per-target static rust lib.
dependencies: [bilge_impl_dep, qemu_api_macros],
variables: {'crate': 'pl011'},
)])

View File

@ -0,0 +1,599 @@
// Copyright 2024, Linaro Limited
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later
use core::{
ffi::{c_int, c_uchar, c_uint, c_void, CStr},
ptr::{addr_of, addr_of_mut, NonNull},
};
use qemu_api::{
bindings::{self, *},
definitions::ObjectImpl,
};
use crate::{
memory_ops::PL011_OPS,
registers::{self, Interrupt},
RegisterOffset,
};
static PL011_ID_ARM: [c_uchar; 8] = [0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
const DATA_BREAK: u32 = 1 << 10;
/// QEMU sourced constant.
pub const PL011_FIFO_DEPTH: usize = 16_usize;
#[repr(C)]
#[derive(Debug, qemu_api_macros::Object)]
/// PL011 Device Model in QEMU
pub struct PL011State {
pub parent_obj: SysBusDevice,
pub iomem: MemoryRegion,
#[doc(alias = "fr")]
pub flags: registers::Flags,
#[doc(alias = "lcr")]
pub line_control: registers::LineControl,
#[doc(alias = "rsr")]
pub receive_status_error_clear: registers::ReceiveStatusErrorClear,
#[doc(alias = "cr")]
pub control: registers::Control,
pub dmacr: u32,
pub int_enabled: u32,
pub int_level: u32,
pub read_fifo: [u32; PL011_FIFO_DEPTH],
pub ilpr: u32,
pub ibrd: u32,
pub fbrd: u32,
pub ifl: u32,
pub read_pos: usize,
pub read_count: usize,
pub read_trigger: usize,
#[doc(alias = "chr")]
pub char_backend: CharBackend,
/// QEMU interrupts
///
/// ```text
/// * sysbus MMIO region 0: device registers
/// * sysbus IRQ 0: `UARTINTR` (combined interrupt line)
/// * sysbus IRQ 1: `UARTRXINTR` (receive FIFO interrupt line)
/// * sysbus IRQ 2: `UARTTXINTR` (transmit FIFO interrupt line)
/// * sysbus IRQ 3: `UARTRTINTR` (receive timeout interrupt line)
/// * sysbus IRQ 4: `UARTMSINTR` (momem status interrupt line)
/// * sysbus IRQ 5: `UARTEINTR` (error interrupt line)
/// ```
#[doc(alias = "irq")]
pub interrupts: [qemu_irq; 6usize],
#[doc(alias = "clk")]
pub clock: NonNull<Clock>,
#[doc(alias = "migrate_clk")]
pub migrate_clock: bool,
}
impl ObjectImpl for PL011State {
type Class = PL011Class;
const TYPE_INFO: qemu_api::bindings::TypeInfo = qemu_api::type_info! { Self };
const TYPE_NAME: &'static CStr = crate::TYPE_PL011;
const PARENT_TYPE_NAME: Option<&'static CStr> = Some(TYPE_SYS_BUS_DEVICE);
const ABSTRACT: bool = false;
const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> = Some(pl011_init);
const INSTANCE_POST_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> = None;
const INSTANCE_FINALIZE: Option<unsafe extern "C" fn(obj: *mut Object)> = None;
}
#[repr(C)]
pub struct PL011Class {
_inner: [u8; 0],
}
impl qemu_api::definitions::Class for PL011Class {
const CLASS_INIT: Option<
unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut core::ffi::c_void),
> = Some(crate::device_class::pl011_class_init);
const CLASS_BASE_INIT: Option<
unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut core::ffi::c_void),
> = None;
}
#[used]
pub static CLK_NAME: &CStr = c"clk";
impl PL011State {
/// Initializes a pre-allocated, unitialized instance of `PL011State`.
///
/// # Safety
///
/// `self` must point to a correctly sized and aligned location for the
/// `PL011State` type. It must not be called more than once on the same
/// location/instance. All its fields are expected to hold unitialized
/// values with the sole exception of `parent_obj`.
pub unsafe fn init(&mut self) {
let dev = addr_of_mut!(*self).cast::<DeviceState>();
// SAFETY:
//
// self and self.iomem are guaranteed to be valid at this point since callers
// must make sure the `self` reference is valid.
unsafe {
memory_region_init_io(
addr_of_mut!(self.iomem),
addr_of_mut!(*self).cast::<Object>(),
&PL011_OPS,
addr_of_mut!(*self).cast::<c_void>(),
Self::TYPE_INFO.name,
0x1000,
);
let sbd = addr_of_mut!(*self).cast::<SysBusDevice>();
sysbus_init_mmio(sbd, addr_of_mut!(self.iomem));
for irq in self.interrupts.iter_mut() {
sysbus_init_irq(sbd, irq);
}
}
// SAFETY:
//
// self.clock is not initialized at this point; but since `NonNull<_>` is Copy,
// we can overwrite the undefined value without side effects. This is
// safe since all PL011State instances are created by QOM code which
// calls this function to initialize the fields; therefore no code is
// able to access an invalid self.clock value.
unsafe {
self.clock = NonNull::new(qdev_init_clock_in(
dev,
CLK_NAME.as_ptr(),
None, /* pl011_clock_update */
addr_of_mut!(*self).cast::<c_void>(),
ClockEvent::ClockUpdate.0,
))
.unwrap();
}
}
pub fn read(
&mut self,
offset: hwaddr,
_size: core::ffi::c_uint,
) -> std::ops::ControlFlow<u64, u64> {
use RegisterOffset::*;
std::ops::ControlFlow::Break(match RegisterOffset::try_from(offset) {
Err(v) if (0x3f8..0x400).contains(&v) => {
u64::from(PL011_ID_ARM[((offset - 0xfe0) >> 2) as usize])
}
Err(_) => {
// qemu_log_mask(LOG_GUEST_ERROR, "pl011_read: Bad offset 0x%x\n", (int)offset);
0
}
Ok(DR) => {
// s->flags &= ~PL011_FLAG_RXFF;
self.flags.set_receive_fifo_full(false);
let c = self.read_fifo[self.read_pos];
if self.read_count > 0 {
self.read_count -= 1;
self.read_pos = (self.read_pos + 1) & (self.fifo_depth() - 1);
}
if self.read_count == 0 {
// self.flags |= PL011_FLAG_RXFE;
self.flags.set_receive_fifo_empty(true);
}
if self.read_count + 1 == self.read_trigger {
//self.int_level &= ~ INT_RX;
self.int_level &= !registers::INT_RX;
}
// Update error bits.
self.receive_status_error_clear = c.to_be_bytes()[3].into();
self.update();
// Must call qemu_chr_fe_accept_input, so return Continue:
return std::ops::ControlFlow::Continue(c.into());
}
Ok(RSR) => u8::from(self.receive_status_error_clear).into(),
Ok(FR) => u16::from(self.flags).into(),
Ok(FBRD) => self.fbrd.into(),
Ok(ILPR) => self.ilpr.into(),
Ok(IBRD) => self.ibrd.into(),
Ok(LCR_H) => u16::from(self.line_control).into(),
Ok(CR) => {
// We exercise our self-control.
u16::from(self.control).into()
}
Ok(FLS) => self.ifl.into(),
Ok(IMSC) => self.int_enabled.into(),
Ok(RIS) => self.int_level.into(),
Ok(MIS) => u64::from(self.int_level & self.int_enabled),
Ok(ICR) => {
// "The UARTICR Register is the interrupt clear register and is write-only"
// Source: ARM DDI 0183G 3.3.13 Interrupt Clear Register, UARTICR
0
}
Ok(DMACR) => self.dmacr.into(),
})
}
pub fn write(&mut self, offset: hwaddr, value: u64) {
// eprintln!("write offset {offset} value {value}");
use RegisterOffset::*;
let value: u32 = value as u32;
match RegisterOffset::try_from(offset) {
Err(_bad_offset) => {
eprintln!("write bad offset {offset} value {value}");
}
Ok(DR) => {
// ??? Check if transmitter is enabled.
let ch: u8 = value as u8;
// XXX this blocks entire thread. Rewrite to use
// qemu_chr_fe_write and background I/O callbacks
// SAFETY: self.char_backend is a valid CharBackend instance after it's been
// initialized in realize().
unsafe {
qemu_chr_fe_write_all(addr_of_mut!(self.char_backend), &ch, 1);
}
self.loopback_tx(value);
self.int_level |= registers::INT_TX;
self.update();
}
Ok(RSR) => {
self.receive_status_error_clear = 0.into();
}
Ok(FR) => {
// flag writes are ignored
}
Ok(ILPR) => {
self.ilpr = value;
}
Ok(IBRD) => {
self.ibrd = value;
}
Ok(FBRD) => {
self.fbrd = value;
}
Ok(LCR_H) => {
let value = value as u16;
let new_val: registers::LineControl = value.into();
// Reset the FIFO state on FIFO enable or disable
if bool::from(self.line_control.fifos_enabled())
^ bool::from(new_val.fifos_enabled())
{
self.reset_fifo();
}
if self.line_control.send_break() ^ new_val.send_break() {
let mut break_enable: c_int = new_val.send_break().into();
// SAFETY: self.char_backend is a valid CharBackend instance after it's been
// initialized in realize().
unsafe {
qemu_chr_fe_ioctl(
addr_of_mut!(self.char_backend),
CHR_IOCTL_SERIAL_SET_BREAK as i32,
addr_of_mut!(break_enable).cast::<c_void>(),
);
}
self.loopback_break(break_enable > 0);
}
self.line_control = new_val;
self.set_read_trigger();
}
Ok(CR) => {
// ??? Need to implement the enable bit.
let value = value as u16;
self.control = value.into();
self.loopback_mdmctrl();
}
Ok(FLS) => {
self.ifl = value;
self.set_read_trigger();
}
Ok(IMSC) => {
self.int_enabled = value;
self.update();
}
Ok(RIS) => {}
Ok(MIS) => {}
Ok(ICR) => {
self.int_level &= !value;
self.update();
}
Ok(DMACR) => {
self.dmacr = value;
if value & 3 > 0 {
// qemu_log_mask(LOG_UNIMP, "pl011: DMA not implemented\n");
eprintln!("pl011: DMA not implemented");
}
}
}
}
#[inline]
fn loopback_tx(&mut self, value: u32) {
if !self.loopback_enabled() {
return;
}
// Caveat:
//
// In real hardware, TX loopback happens at the serial-bit level
// and then reassembled by the RX logics back into bytes and placed
// into the RX fifo. That is, loopback happens after TX fifo.
//
// Because the real hardware TX fifo is time-drained at the frame
// rate governed by the configured serial format, some loopback
// bytes in TX fifo may still be able to get into the RX fifo
// that could be full at times while being drained at software
// pace.
//
// In such scenario, the RX draining pace is the major factor
// deciding which loopback bytes get into the RX fifo, unless
// hardware flow-control is enabled.
//
// For simplicity, the above described is not emulated.
self.put_fifo(value);
}
fn loopback_mdmctrl(&mut self) {
if !self.loopback_enabled() {
return;
}
/*
* Loopback software-driven modem control outputs to modem status inputs:
* FR.RI <= CR.Out2
* FR.DCD <= CR.Out1
* FR.CTS <= CR.RTS
* FR.DSR <= CR.DTR
*
* The loopback happens immediately even if this call is triggered
* by setting only CR.LBE.
*
* CTS/RTS updates due to enabled hardware flow controls are not
* dealt with here.
*/
//fr = s->flags & ~(PL011_FLAG_RI | PL011_FLAG_DCD |
// PL011_FLAG_DSR | PL011_FLAG_CTS);
//fr |= (cr & CR_OUT2) ? PL011_FLAG_RI : 0;
//fr |= (cr & CR_OUT1) ? PL011_FLAG_DCD : 0;
//fr |= (cr & CR_RTS) ? PL011_FLAG_CTS : 0;
//fr |= (cr & CR_DTR) ? PL011_FLAG_DSR : 0;
//
self.flags.set_ring_indicator(self.control.out_2());
self.flags.set_data_carrier_detect(self.control.out_1());
self.flags.set_clear_to_send(self.control.request_to_send());
self.flags
.set_data_set_ready(self.control.data_transmit_ready());
// Change interrupts based on updated FR
let mut il = self.int_level;
il &= !Interrupt::MS;
//il |= (fr & PL011_FLAG_DSR) ? INT_DSR : 0;
//il |= (fr & PL011_FLAG_DCD) ? INT_DCD : 0;
//il |= (fr & PL011_FLAG_CTS) ? INT_CTS : 0;
//il |= (fr & PL011_FLAG_RI) ? INT_RI : 0;
if self.flags.data_set_ready() {
il |= Interrupt::DSR as u32;
}
if self.flags.data_carrier_detect() {
il |= Interrupt::DCD as u32;
}
if self.flags.clear_to_send() {
il |= Interrupt::CTS as u32;
}
if self.flags.ring_indicator() {
il |= Interrupt::RI as u32;
}
self.int_level = il;
self.update();
}
fn loopback_break(&mut self, enable: bool) {
if enable {
self.loopback_tx(DATA_BREAK);
}
}
fn set_read_trigger(&mut self) {
self.read_trigger = 1;
}
pub fn realize(&mut self) {
// SAFETY: self.char_backend has the correct size and alignment for a
// CharBackend object, and its callbacks are of the correct types.
unsafe {
qemu_chr_fe_set_handlers(
addr_of_mut!(self.char_backend),
Some(pl011_can_receive),
Some(pl011_receive),
Some(pl011_event),
None,
addr_of_mut!(*self).cast::<c_void>(),
core::ptr::null_mut(),
true,
);
}
}
pub fn reset(&mut self) {
self.line_control.reset();
self.receive_status_error_clear.reset();
self.dmacr = 0;
self.int_enabled = 0;
self.int_level = 0;
self.ilpr = 0;
self.ibrd = 0;
self.fbrd = 0;
self.read_trigger = 1;
self.ifl = 0x12;
self.control.reset();
self.flags = 0.into();
self.reset_fifo();
}
pub fn reset_fifo(&mut self) {
self.read_count = 0;
self.read_pos = 0;
/* Reset FIFO flags */
self.flags.reset();
}
pub fn can_receive(&self) -> bool {
// trace_pl011_can_receive(s->lcr, s->read_count, r);
self.read_count < self.fifo_depth()
}
pub fn event(&mut self, event: QEMUChrEvent) {
if event == bindings::QEMUChrEvent::CHR_EVENT_BREAK && !self.fifo_enabled() {
self.put_fifo(DATA_BREAK);
self.receive_status_error_clear.set_break_error(true);
}
}
#[inline]
pub fn fifo_enabled(&self) -> bool {
matches!(self.line_control.fifos_enabled(), registers::Mode::FIFO)
}
#[inline]
pub fn loopback_enabled(&self) -> bool {
self.control.enable_loopback()
}
#[inline]
pub fn fifo_depth(&self) -> usize {
// Note: FIFO depth is expected to be power-of-2
if self.fifo_enabled() {
return PL011_FIFO_DEPTH;
}
1
}
pub fn put_fifo(&mut self, value: c_uint) {
let depth = self.fifo_depth();
assert!(depth > 0);
let slot = (self.read_pos + self.read_count) & (depth - 1);
self.read_fifo[slot] = value;
self.read_count += 1;
// s->flags &= ~PL011_FLAG_RXFE;
self.flags.set_receive_fifo_empty(false);
if self.read_count == depth {
//s->flags |= PL011_FLAG_RXFF;
self.flags.set_receive_fifo_full(true);
}
if self.read_count == self.read_trigger {
self.int_level |= registers::INT_RX;
self.update();
}
}
pub fn update(&self) {
let flags = self.int_level & self.int_enabled;
for (irq, i) in self.interrupts.iter().zip(IRQMASK) {
// SAFETY: self.interrupts have been initialized in init().
unsafe { qemu_set_irq(*irq, i32::from(flags & i != 0)) };
}
}
}
/// Which bits in the interrupt status matter for each outbound IRQ line ?
pub const IRQMASK: [u32; 6] = [
/* combined IRQ */
Interrupt::E
| Interrupt::MS
| Interrupt::RT as u32
| Interrupt::TX as u32
| Interrupt::RX as u32,
Interrupt::RX as u32,
Interrupt::TX as u32,
Interrupt::RT as u32,
Interrupt::MS,
Interrupt::E,
];
/// # Safety
///
/// We expect the FFI user of this function to pass a valid pointer, that has
/// the same size as [`PL011State`]. We also expect the device is
/// readable/writeable from one thread at any time.
#[no_mangle]
pub unsafe extern "C" fn pl011_can_receive(opaque: *mut c_void) -> c_int {
unsafe {
debug_assert!(!opaque.is_null());
let state = NonNull::new_unchecked(opaque.cast::<PL011State>());
state.as_ref().can_receive().into()
}
}
/// # Safety
///
/// We expect the FFI user of this function to pass a valid pointer, that has
/// the same size as [`PL011State`]. We also expect the device is
/// readable/writeable from one thread at any time.
///
/// The buffer and size arguments must also be valid.
#[no_mangle]
pub unsafe extern "C" fn pl011_receive(
opaque: *mut core::ffi::c_void,
buf: *const u8,
size: core::ffi::c_int,
) {
unsafe {
debug_assert!(!opaque.is_null());
let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
if state.as_ref().loopback_enabled() {
return;
}
if size > 0 {
debug_assert!(!buf.is_null());
state.as_mut().put_fifo(c_uint::from(buf.read_volatile()))
}
}
}
/// # Safety
///
/// We expect the FFI user of this function to pass a valid pointer, that has
/// the same size as [`PL011State`]. We also expect the device is
/// readable/writeable from one thread at any time.
#[no_mangle]
pub unsafe extern "C" fn pl011_event(opaque: *mut core::ffi::c_void, event: QEMUChrEvent) {
unsafe {
debug_assert!(!opaque.is_null());
let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
state.as_mut().event(event)
}
}
/// # Safety
///
/// We expect the FFI user of this function to pass a valid pointer for `chr`.
#[no_mangle]
pub unsafe extern "C" fn pl011_create(
addr: u64,
irq: qemu_irq,
chr: *mut Chardev,
) -> *mut DeviceState {
unsafe {
let dev: *mut DeviceState = qdev_new(PL011State::TYPE_INFO.name);
let sysbus: *mut SysBusDevice = dev.cast::<SysBusDevice>();
qdev_prop_set_chr(dev, bindings::TYPE_CHARDEV.as_ptr(), chr);
sysbus_realize_and_unref(sysbus, addr_of!(error_fatal) as *mut *mut Error);
sysbus_mmio_map(sysbus, 0, addr);
sysbus_connect_irq(sysbus, 0, irq);
dev
}
}
/// # Safety
///
/// We expect the FFI user of this function to pass a valid pointer, that has
/// the same size as [`PL011State`]. We also expect the device is
/// readable/writeable from one thread at any time.
#[no_mangle]
pub unsafe extern "C" fn pl011_init(obj: *mut Object) {
unsafe {
debug_assert!(!obj.is_null());
let mut state = NonNull::new_unchecked(obj.cast::<PL011State>());
state.as_mut().init();
}
}

View File

@ -0,0 +1,70 @@
// Copyright 2024, Linaro Limited
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later
use core::ptr::NonNull;
use qemu_api::{bindings::*, definitions::ObjectImpl};
use crate::device::PL011State;
#[used]
pub static VMSTATE_PL011: VMStateDescription = VMStateDescription {
name: PL011State::TYPE_INFO.name,
unmigratable: true,
..unsafe { ::core::mem::MaybeUninit::<VMStateDescription>::zeroed().assume_init() }
};
qemu_api::declare_properties! {
PL011_PROPERTIES,
qemu_api::define_property!(
c"chardev",
PL011State,
char_backend,
unsafe { &qdev_prop_chr },
CharBackend
),
qemu_api::define_property!(
c"migrate-clk",
PL011State,
migrate_clock,
unsafe { &qdev_prop_bool },
bool
),
}
qemu_api::device_class_init! {
pl011_class_init,
props => PL011_PROPERTIES,
realize_fn => Some(pl011_realize),
legacy_reset_fn => Some(pl011_reset),
vmsd => VMSTATE_PL011,
}
/// # Safety
///
/// We expect the FFI user of this function to pass a valid pointer, that has
/// the same size as [`PL011State`]. We also expect the device is
/// readable/writeable from one thread at any time.
#[no_mangle]
pub unsafe extern "C" fn pl011_realize(dev: *mut DeviceState, _errp: *mut *mut Error) {
unsafe {
assert!(!dev.is_null());
let mut state = NonNull::new_unchecked(dev.cast::<PL011State>());
state.as_mut().realize();
}
}
/// # Safety
///
/// We expect the FFI user of this function to pass a valid pointer, that has
/// the same size as [`PL011State`]. We also expect the device is
/// readable/writeable from one thread at any time.
#[no_mangle]
pub unsafe extern "C" fn pl011_reset(dev: *mut DeviceState) {
unsafe {
assert!(!dev.is_null());
let mut state = NonNull::new_unchecked(dev.cast::<PL011State>());
state.as_mut().reset();
}
}

View File

@ -0,0 +1,586 @@
// Copyright 2024, Linaro Limited
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later
//
// PL011 QEMU Device Model
//
// This library implements a device model for the PrimeCell® UART (PL011)
// device in QEMU.
//
#![doc = include_str!("../README.md")]
//! # Library crate
//!
//! See [`PL011State`](crate::device::PL011State) for the device model type and
//! the [`registers`] module for register types.
#![deny(
rustdoc::broken_intra_doc_links,
rustdoc::redundant_explicit_links,
clippy::correctness,
clippy::suspicious,
clippy::complexity,
clippy::perf,
clippy::cargo,
clippy::nursery,
clippy::style,
// restriction group
clippy::dbg_macro,
clippy::as_underscore,
clippy::assertions_on_result_states,
// pedantic group
clippy::doc_markdown,
clippy::borrow_as_ptr,
clippy::cast_lossless,
clippy::option_if_let_else,
clippy::missing_const_for_fn,
clippy::cognitive_complexity,
clippy::missing_safety_doc,
)]
extern crate bilge;
extern crate bilge_impl;
extern crate qemu_api;
pub mod device;
pub mod device_class;
pub mod memory_ops;
pub const TYPE_PL011: &::core::ffi::CStr = c"pl011";
/// Offset of each register from the base memory address of the device.
///
/// # Source
/// ARM DDI 0183G, Table 3-1 p.3-3
#[doc(alias = "offset")]
#[allow(non_camel_case_types)]
#[repr(u64)]
#[derive(Debug)]
pub enum RegisterOffset {
/// Data Register
///
/// A write to this register initiates the actual data transmission
#[doc(alias = "UARTDR")]
DR = 0x000,
/// Receive Status Register or Error Clear Register
#[doc(alias = "UARTRSR")]
#[doc(alias = "UARTECR")]
RSR = 0x004,
/// Flag Register
///
/// A read of this register shows if transmission is complete
#[doc(alias = "UARTFR")]
FR = 0x018,
/// Fractional Baud Rate Register
///
/// responsible for baud rate speed
#[doc(alias = "UARTFBRD")]
FBRD = 0x028,
/// `IrDA` Low-Power Counter Register
#[doc(alias = "UARTILPR")]
ILPR = 0x020,
/// Integer Baud Rate Register
///
/// Responsible for baud rate speed
#[doc(alias = "UARTIBRD")]
IBRD = 0x024,
/// line control register (data frame format)
#[doc(alias = "UARTLCR_H")]
LCR_H = 0x02C,
/// Toggle UART, transmission or reception
#[doc(alias = "UARTCR")]
CR = 0x030,
/// Interrupt FIFO Level Select Register
#[doc(alias = "UARTIFLS")]
FLS = 0x034,
/// Interrupt Mask Set/Clear Register
#[doc(alias = "UARTIMSC")]
IMSC = 0x038,
/// Raw Interrupt Status Register
#[doc(alias = "UARTRIS")]
RIS = 0x03C,
/// Masked Interrupt Status Register
#[doc(alias = "UARTMIS")]
MIS = 0x040,
/// Interrupt Clear Register
#[doc(alias = "UARTICR")]
ICR = 0x044,
/// DMA control Register
#[doc(alias = "UARTDMACR")]
DMACR = 0x048,
///// Reserved, offsets `0x04C` to `0x07C`.
//Reserved = 0x04C,
}
impl core::convert::TryFrom<u64> for RegisterOffset {
type Error = u64;
fn try_from(value: u64) -> Result<Self, Self::Error> {
macro_rules! case {
($($discriminant:ident),*$(,)*) => {
/* check that matching on all macro arguments compiles, which means we are not
* missing any enum value; if the type definition ever changes this will stop
* compiling.
*/
const fn _assert_exhaustive(val: RegisterOffset) {
match val {
$(RegisterOffset::$discriminant => (),)*
}
}
match value {
$(x if x == Self::$discriminant as u64 => Ok(Self::$discriminant),)*
_ => Err(value),
}
}
}
case! { DR, RSR, FR, FBRD, ILPR, IBRD, LCR_H, CR, FLS, IMSC, RIS, MIS, ICR, DMACR }
}
}
pub mod registers {
//! Device registers exposed as typed structs which are backed by arbitrary
//! integer bitmaps. [`Data`], [`Control`], [`LineControl`], etc.
//!
//! All PL011 registers are essentially 32-bit wide, but are typed here as
//! bitmaps with only the necessary width. That is, if a struct bitmap
//! in this module is for example 16 bits long, it should be conceived
//! as a 32-bit register where the unmentioned higher bits are always
//! unused thus treated as zero when read or written.
use bilge::prelude::*;
// TODO: FIFO Mode has different semantics
/// Data Register, `UARTDR`
///
/// The `UARTDR` register is the data register.
///
/// For words to be transmitted:
///
/// - if the FIFOs are enabled, data written to this location is pushed onto
/// the transmit
/// FIFO
/// - if the FIFOs are not enabled, data is stored in the transmitter
/// holding register (the
/// bottom word of the transmit FIFO).
///
/// The write operation initiates transmission from the UART. The data is
/// prefixed with a start bit, appended with the appropriate parity bit
/// (if parity is enabled), and a stop bit. The resultant word is then
/// transmitted.
///
/// For received words:
///
/// - if the FIFOs are enabled, the data byte and the 4-bit status (break,
/// frame, parity,
/// and overrun) is pushed onto the 12-bit wide receive FIFO
/// - if the FIFOs are not enabled, the data byte and status are stored in
/// the receiving
/// holding register (the bottom word of the receive FIFO).
///
/// The received data byte is read by performing reads from the `UARTDR`
/// register along with the corresponding status information. The status
/// information can also be read by a read of the `UARTRSR/UARTECR`
/// register.
///
/// # Note
///
/// You must disable the UART before any of the control registers are
/// reprogrammed. When the UART is disabled in the middle of
/// transmission or reception, it completes the current character before
/// stopping.
///
/// # Source
/// ARM DDI 0183G 3.3.1 Data Register, UARTDR
#[bitsize(16)]
#[derive(Clone, Copy, DebugBits, FromBits)]
#[doc(alias = "UARTDR")]
pub struct Data {
_reserved: u4,
pub data: u8,
pub framing_error: bool,
pub parity_error: bool,
pub break_error: bool,
pub overrun_error: bool,
}
// TODO: FIFO Mode has different semantics
/// Receive Status Register / Error Clear Register, `UARTRSR/UARTECR`
///
/// The UARTRSR/UARTECR register is the receive status register/error clear
/// register. Receive status can also be read from the `UARTRSR`
/// register. If the status is read from this register, then the status
/// information for break, framing and parity corresponds to the
/// data character read from the [Data register](Data), `UARTDR` prior to
/// reading the UARTRSR register. The status information for overrun is
/// set immediately when an overrun condition occurs.
///
///
/// # Note
/// The received data character must be read first from the [Data
/// Register](Data), `UARTDR` before reading the error status associated
/// with that data character from the `UARTRSR` register. This read
/// sequence cannot be reversed, because the `UARTRSR` register is
/// updated only when a read occurs from the `UARTDR` register. However,
/// the status information can also be obtained by reading the `UARTDR`
/// register
///
/// # Source
/// ARM DDI 0183G 3.3.2 Receive Status Register/Error Clear Register,
/// UARTRSR/UARTECR
#[bitsize(8)]
#[derive(Clone, Copy, DebugBits, FromBits)]
pub struct ReceiveStatusErrorClear {
pub framing_error: bool,
pub parity_error: bool,
pub break_error: bool,
pub overrun_error: bool,
_reserved_unpredictable: u4,
}
impl ReceiveStatusErrorClear {
pub fn reset(&mut self) {
// All the bits are cleared to 0 on reset.
*self = 0.into();
}
}
impl Default for ReceiveStatusErrorClear {
fn default() -> Self {
0.into()
}
}
#[bitsize(16)]
#[derive(Clone, Copy, DebugBits, FromBits)]
/// Flag Register, `UARTFR`
#[doc(alias = "UARTFR")]
pub struct Flags {
/// CTS Clear to send. This bit is the complement of the UART clear to
/// send, `nUARTCTS`, modem status input. That is, the bit is 1
/// when `nUARTCTS` is LOW.
pub clear_to_send: bool,
/// DSR Data set ready. This bit is the complement of the UART data set
/// ready, `nUARTDSR`, modem status input. That is, the bit is 1 when
/// `nUARTDSR` is LOW.
pub data_set_ready: bool,
/// DCD Data carrier detect. This bit is the complement of the UART data
/// carrier detect, `nUARTDCD`, modem status input. That is, the bit is
/// 1 when `nUARTDCD` is LOW.
pub data_carrier_detect: bool,
/// BUSY UART busy. If this bit is set to 1, the UART is busy
/// transmitting data. This bit remains set until the complete
/// byte, including all the stop bits, has been sent from the
/// shift register. This bit is set as soon as the transmit FIFO
/// becomes non-empty, regardless of whether the UART is enabled
/// or not.
pub busy: bool,
/// RXFE Receive FIFO empty. The meaning of this bit depends on the
/// state of the FEN bit in the UARTLCR_H register. If the FIFO
/// is disabled, this bit is set when the receive holding
/// register is empty. If the FIFO is enabled, the RXFE bit is
/// set when the receive FIFO is empty.
pub receive_fifo_empty: bool,
/// TXFF Transmit FIFO full. The meaning of this bit depends on the
/// state of the FEN bit in the UARTLCR_H register. If the FIFO
/// is disabled, this bit is set when the transmit holding
/// register is full. If the FIFO is enabled, the TXFF bit is
/// set when the transmit FIFO is full.
pub transmit_fifo_full: bool,
/// RXFF Receive FIFO full. The meaning of this bit depends on the state
/// of the FEN bit in the UARTLCR_H register. If the FIFO is
/// disabled, this bit is set when the receive holding register
/// is full. If the FIFO is enabled, the RXFF bit is set when
/// the receive FIFO is full.
pub receive_fifo_full: bool,
/// Transmit FIFO empty. The meaning of this bit depends on the state of
/// the FEN bit in the [Line Control register](LineControl),
/// `UARTLCR_H`. If the FIFO is disabled, this bit is set when the
/// transmit holding register is empty. If the FIFO is enabled,
/// the TXFE bit is set when the transmit FIFO is empty. This
/// bit does not indicate if there is data in the transmit shift
/// register.
pub transmit_fifo_empty: bool,
/// `RI`, is `true` when `nUARTRI` is `LOW`.
pub ring_indicator: bool,
_reserved_zero_no_modify: u7,
}
impl Flags {
pub fn reset(&mut self) {
// After reset TXFF, RXFF, and BUSY are 0, and TXFE and RXFE are 1
self.set_receive_fifo_full(false);
self.set_transmit_fifo_full(false);
self.set_busy(false);
self.set_receive_fifo_empty(true);
self.set_transmit_fifo_empty(true);
}
}
impl Default for Flags {
fn default() -> Self {
let mut ret: Self = 0.into();
ret.reset();
ret
}
}
#[bitsize(16)]
#[derive(Clone, Copy, DebugBits, FromBits)]
/// Line Control Register, `UARTLCR_H`
#[doc(alias = "UARTLCR_H")]
pub struct LineControl {
/// 15:8 - Reserved, do not modify, read as zero.
_reserved_zero_no_modify: u8,
/// 7 SPS Stick parity select.
/// 0 = stick parity is disabled
/// 1 = either:
/// • if the EPS bit is 0 then the parity bit is transmitted and checked
/// as a 1 • if the EPS bit is 1 then the parity bit is
/// transmitted and checked as a 0. This bit has no effect when
/// the PEN bit disables parity checking and generation. See Table 3-11
/// on page 3-14 for the parity truth table.
pub sticky_parity: bool,
/// WLEN Word length. These bits indicate the number of data bits
/// transmitted or received in a frame as follows: b11 = 8 bits
/// b10 = 7 bits
/// b01 = 6 bits
/// b00 = 5 bits.
pub word_length: WordLength,
/// FEN Enable FIFOs:
/// 0 = FIFOs are disabled (character mode) that is, the FIFOs become
/// 1-byte-deep holding registers 1 = transmit and receive FIFO
/// buffers are enabled (FIFO mode).
pub fifos_enabled: Mode,
/// 3 STP2 Two stop bits select. If this bit is set to 1, two stop bits
/// are transmitted at the end of the frame. The receive
/// logic does not check for two stop bits being received.
pub two_stops_bits: bool,
/// EPS Even parity select. Controls the type of parity the UART uses
/// during transmission and reception:
/// - 0 = odd parity. The UART generates or checks for an odd number of
/// 1s in the data and parity bits.
/// - 1 = even parity. The UART generates or checks for an even number
/// of 1s in the data and parity bits.
/// This bit has no effect when the `PEN` bit disables parity checking
/// and generation. See Table 3-11 on page 3-14 for the parity
/// truth table.
pub parity: Parity,
/// 1 PEN Parity enable:
///
/// - 0 = parity is disabled and no parity bit added to the data frame
/// - 1 = parity checking and generation is enabled.
///
/// See Table 3-11 on page 3-14 for the parity truth table.
pub parity_enabled: bool,
/// BRK Send break.
///
/// If this bit is set to `1`, a low-level is continually output on the
/// `UARTTXD` output, after completing transmission of the
/// current character. For the proper execution of the break command,
/// the software must set this bit for at least two complete
/// frames. For normal use, this bit must be cleared to `0`.
pub send_break: bool,
}
impl LineControl {
pub fn reset(&mut self) {
// All the bits are cleared to 0 when reset.
*self = 0.into();
}
}
impl Default for LineControl {
fn default() -> Self {
0.into()
}
}
#[bitsize(1)]
#[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)]
/// `EPS` "Even parity select", field of [Line Control
/// register](LineControl).
pub enum Parity {
/// - 0 = odd parity. The UART generates or checks for an odd number of
/// 1s in the data and parity bits.
Odd = 0,
/// - 1 = even parity. The UART generates or checks for an even number
/// of 1s in the data and parity bits.
Even = 1,
}
#[bitsize(1)]
#[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)]
/// `FEN` "Enable FIFOs" or Device mode, field of [Line Control
/// register](LineControl).
pub enum Mode {
/// 0 = FIFOs are disabled (character mode) that is, the FIFOs become
/// 1-byte-deep holding registers
Character = 0,
/// 1 = transmit and receive FIFO buffers are enabled (FIFO mode).
FIFO = 1,
}
impl From<Mode> for bool {
fn from(val: Mode) -> Self {
matches!(val, Mode::FIFO)
}
}
#[bitsize(2)]
#[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)]
/// `WLEN` Word length, field of [Line Control register](LineControl).
///
/// These bits indicate the number of data bits transmitted or received in a
/// frame as follows:
pub enum WordLength {
/// b11 = 8 bits
_8Bits = 0b11,
/// b10 = 7 bits
_7Bits = 0b10,
/// b01 = 6 bits
_6Bits = 0b01,
/// b00 = 5 bits.
_5Bits = 0b00,
}
/// Control Register, `UARTCR`
///
/// The `UARTCR` register is the control register. All the bits are cleared
/// to `0` on reset except for bits `9` and `8` that are set to `1`.
///
/// # Source
/// ARM DDI 0183G, 3.3.8 Control Register, `UARTCR`, Table 3-12
#[bitsize(16)]
#[doc(alias = "UARTCR")]
#[derive(Clone, Copy, DebugBits, FromBits)]
pub struct Control {
/// `UARTEN` UART enable: 0 = UART is disabled. If the UART is disabled
/// in the middle of transmission or reception, it completes the current
/// character before stopping. 1 = the UART is enabled. Data
/// transmission and reception occurs for either UART signals or SIR
/// signals depending on the setting of the SIREN bit.
pub enable_uart: bool,
/// `SIREN` `SIR` enable: 0 = IrDA SIR ENDEC is disabled. `nSIROUT`
/// remains LOW (no light pulse generated), and signal transitions on
/// SIRIN have no effect. 1 = IrDA SIR ENDEC is enabled. Data is
/// transmitted and received on nSIROUT and SIRIN. UARTTXD remains HIGH,
/// in the marking state. Signal transitions on UARTRXD or modem status
/// inputs have no effect. This bit has no effect if the UARTEN bit
/// disables the UART.
pub enable_sir: bool,
/// `SIRLP` SIR low-power IrDA mode. This bit selects the IrDA encoding
/// mode. If this bit is cleared to 0, low-level bits are transmitted as
/// an active high pulse with a width of 3/ 16th of the bit period. If
/// this bit is set to 1, low-level bits are transmitted with a pulse
/// width that is 3 times the period of the IrLPBaud16 input signal,
/// regardless of the selected bit rate. Setting this bit uses less
/// power, but might reduce transmission distances.
pub sir_lowpower_irda_mode: u1,
/// Reserved, do not modify, read as zero.
_reserved_zero_no_modify: u4,
/// `LBE` Loopback enable. If this bit is set to 1 and the SIREN bit is
/// set to 1 and the SIRTEST bit in the Test Control register, UARTTCR
/// on page 4-5 is set to 1, then the nSIROUT path is inverted, and fed
/// through to the SIRIN path. The SIRTEST bit in the test register must
/// be set to 1 to override the normal half-duplex SIR operation. This
/// must be the requirement for accessing the test registers during
/// normal operation, and SIRTEST must be cleared to 0 when loopback
/// testing is finished. This feature reduces the amount of external
/// coupling required during system test. If this bit is set to 1, and
/// the SIRTEST bit is set to 0, the UARTTXD path is fed through to the
/// UARTRXD path. In either SIR mode or UART mode, when this bit is set,
/// the modem outputs are also fed through to the modem inputs. This bit
/// is cleared to 0 on reset, to disable loopback.
pub enable_loopback: bool,
/// `TXE` Transmit enable. If this bit is set to 1, the transmit section
/// of the UART is enabled. Data transmission occurs for either UART
/// signals, or SIR signals depending on the setting of the SIREN bit.
/// When the UART is disabled in the middle of transmission, it
/// completes the current character before stopping.
pub enable_transmit: bool,
/// `RXE` Receive enable. If this bit is set to 1, the receive section
/// of the UART is enabled. Data reception occurs for either UART
/// signals or SIR signals depending on the setting of the SIREN bit.
/// When the UART is disabled in the middle of reception, it completes
/// the current character before stopping.
pub enable_receive: bool,
/// `DTR` Data transmit ready. This bit is the complement of the UART
/// data transmit ready, `nUARTDTR`, modem status output. That is, when
/// the bit is programmed to a 1 then `nUARTDTR` is LOW.
pub data_transmit_ready: bool,
/// `RTS` Request to send. This bit is the complement of the UART
/// request to send, `nUARTRTS`, modem status output. That is, when the
/// bit is programmed to a 1 then `nUARTRTS` is LOW.
pub request_to_send: bool,
/// `Out1` This bit is the complement of the UART Out1 (`nUARTOut1`)
/// modem status output. That is, when the bit is programmed to a 1 the
/// output is 0. For DTE this can be used as Data Carrier Detect (DCD).
pub out_1: bool,
/// `Out2` This bit is the complement of the UART Out2 (`nUARTOut2`)
/// modem status output. That is, when the bit is programmed to a 1, the
/// output is 0. For DTE this can be used as Ring Indicator (RI).
pub out_2: bool,
/// `RTSEn` RTS hardware flow control enable. If this bit is set to 1,
/// RTS hardware flow control is enabled. Data is only requested when
/// there is space in the receive FIFO for it to be received.
pub rts_hardware_flow_control_enable: bool,
/// `CTSEn` CTS hardware flow control enable. If this bit is set to 1,
/// CTS hardware flow control is enabled. Data is only transmitted when
/// the `nUARTCTS` signal is asserted.
pub cts_hardware_flow_control_enable: bool,
}
impl Control {
pub fn reset(&mut self) {
*self = 0.into();
self.set_enable_receive(true);
self.set_enable_transmit(true);
}
}
impl Default for Control {
fn default() -> Self {
let mut ret: Self = 0.into();
ret.reset();
ret
}
}
/// Interrupt status bits in UARTRIS, UARTMIS, UARTIMSC
pub const INT_OE: u32 = 1 << 10;
pub const INT_BE: u32 = 1 << 9;
pub const INT_PE: u32 = 1 << 8;
pub const INT_FE: u32 = 1 << 7;
pub const INT_RT: u32 = 1 << 6;
pub const INT_TX: u32 = 1 << 5;
pub const INT_RX: u32 = 1 << 4;
pub const INT_DSR: u32 = 1 << 3;
pub const INT_DCD: u32 = 1 << 2;
pub const INT_CTS: u32 = 1 << 1;
pub const INT_RI: u32 = 1 << 0;
pub const INT_E: u32 = INT_OE | INT_BE | INT_PE | INT_FE;
pub const INT_MS: u32 = INT_RI | INT_DSR | INT_DCD | INT_CTS;
#[repr(u32)]
pub enum Interrupt {
OE = 1 << 10,
BE = 1 << 9,
PE = 1 << 8,
FE = 1 << 7,
RT = 1 << 6,
TX = 1 << 5,
RX = 1 << 4,
DSR = 1 << 3,
DCD = 1 << 2,
CTS = 1 << 1,
RI = 1 << 0,
}
impl Interrupt {
pub const E: u32 = INT_OE | INT_BE | INT_PE | INT_FE;
pub const MS: u32 = INT_RI | INT_DSR | INT_DCD | INT_CTS;
}
}
// TODO: You must disable the UART before any of the control registers are
// reprogrammed. When the UART is disabled in the middle of transmission or
// reception, it completes the current character before stopping

View File

@ -0,0 +1,59 @@
// Copyright 2024, Linaro Limited
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later
use core::{mem::MaybeUninit, ptr::NonNull};
use qemu_api::bindings::*;
use crate::device::PL011State;
pub static PL011_OPS: MemoryRegionOps = MemoryRegionOps {
read: Some(pl011_read),
write: Some(pl011_write),
read_with_attrs: None,
write_with_attrs: None,
endianness: device_endian::DEVICE_NATIVE_ENDIAN,
valid: unsafe { MaybeUninit::<MemoryRegionOps__bindgen_ty_1>::zeroed().assume_init() },
impl_: MemoryRegionOps__bindgen_ty_2 {
min_access_size: 4,
max_access_size: 4,
..unsafe { MaybeUninit::<MemoryRegionOps__bindgen_ty_2>::zeroed().assume_init() }
},
};
#[no_mangle]
unsafe extern "C" fn pl011_read(
opaque: *mut core::ffi::c_void,
addr: hwaddr,
size: core::ffi::c_uint,
) -> u64 {
assert!(!opaque.is_null());
let mut state = unsafe { NonNull::new_unchecked(opaque.cast::<PL011State>()) };
let val = unsafe { state.as_mut().read(addr, size) };
match val {
std::ops::ControlFlow::Break(val) => val,
std::ops::ControlFlow::Continue(val) => {
// SAFETY: self.char_backend is a valid CharBackend instance after it's been
// initialized in realize().
let cb_ptr = unsafe { core::ptr::addr_of_mut!(state.as_mut().char_backend) };
unsafe { qemu_chr_fe_accept_input(cb_ptr) };
val
}
}
}
#[no_mangle]
unsafe extern "C" fn pl011_write(
opaque: *mut core::ffi::c_void,
addr: hwaddr,
data: u64,
_size: core::ffi::c_uint,
) {
unsafe {
assert!(!opaque.is_null());
let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
state.as_mut().write(addr, data)
}
}

1
rust/hw/meson.build Normal file
View File

@ -0,0 +1 @@
subdir('char')

4
rust/meson.build Normal file
View File

@ -0,0 +1,4 @@
subdir('qemu-api-macros')
subdir('qemu-api')
subdir('hw')

47
rust/qemu-api-macros/Cargo.lock generated Normal file
View File

@ -0,0 +1,47 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "proc-macro2"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [
"unicode-ident",
]
[[package]]
name = "qemu_api_macros"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"

View File

@ -0,0 +1,25 @@
[package]
name = "qemu_api_macros"
version = "0.1.0"
edition = "2021"
authors = ["Manos Pitsidianakis <manos.pitsidianakis@linaro.org>"]
license = "GPL-2.0-or-later"
readme = "README.md"
homepage = "https://www.qemu.org"
description = "Rust bindings for QEMU - Utility macros"
repository = "https://gitlab.com/qemu-project/qemu/"
resolver = "2"
publish = false
keywords = []
categories = []
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1"
quote = "1"
syn = "2"
# Do not include in any global workspace
[workspace]

View File

@ -0,0 +1 @@
# `qemu-api-macros` - Utility macros for defining QEMU devices

View File

@ -0,0 +1,25 @@
add_languages('rust', required: true, native: true)
quote_dep = dependency('quote-1-rs', native: true)
syn_dep = dependency('syn-2-rs', native: true)
proc_macro2_dep = dependency('proc-macro2-1-rs', native: true)
_qemu_api_macros_rs = import('rust').proc_macro(
'qemu_api_macros',
files('src/lib.rs'),
override_options: ['rust_std=2021', 'build.rust_std=2021'],
rust_args: [
'--cfg', 'use_fallback',
'--cfg', 'feature="syn-error"',
'--cfg', 'feature="proc-macro"',
],
dependencies: [
proc_macro2_dep,
quote_dep,
syn_dep,
],
)
qemu_api_macros = declare_dependency(
link_with: _qemu_api_macros_rs,
)

View File

@ -0,0 +1,43 @@
// Copyright 2024, Linaro Limited
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(Object)]
pub fn derive_object(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let module_static = format_ident!("__{}_LOAD_MODULE", name);
let expanded = quote! {
#[allow(non_upper_case_globals)]
#[used]
#[cfg_attr(target_os = "linux", link_section = ".ctors")]
#[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
#[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
pub static #module_static: extern "C" fn() = {
extern "C" fn __register() {
unsafe {
::qemu_api::bindings::type_register_static(&<#name as ::qemu_api::definitions::ObjectImpl>::TYPE_INFO);
}
}
extern "C" fn __load() {
unsafe {
::qemu_api::bindings::register_module_init(
Some(__register),
::qemu_api::bindings::module_init_type::MODULE_INIT_QOM
);
}
}
__load
};
};
TokenStream::from(expanded)
}

2
rust/qemu-api/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# Ignore generated bindings file overrides.
src/bindings.rs

7
rust/qemu-api/Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "qemu_api"
version = "0.1.0"

26
rust/qemu-api/Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[package]
name = "qemu_api"
version = "0.1.0"
edition = "2021"
authors = ["Manos Pitsidianakis <manos.pitsidianakis@linaro.org>"]
license = "GPL-2.0-or-later"
readme = "README.md"
homepage = "https://www.qemu.org"
description = "Rust bindings for QEMU"
repository = "https://gitlab.com/qemu-project/qemu/"
resolver = "2"
publish = false
keywords = []
categories = []
[dependencies]
[features]
default = []
allocator = []
# Do not include in any global workspace
[workspace]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(MESON)', 'cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)'] }

17
rust/qemu-api/README.md Normal file
View File

@ -0,0 +1,17 @@
# QEMU bindings and API wrappers
This library exports helper Rust types, Rust macros and C FFI bindings for internal QEMU APIs.
The C bindings can be generated with `bindgen`, using this build target:
```console
$ ninja bindings.rs
```
## Generate Rust documentation
To generate docs for this crate, including private items:
```sh
cargo doc --no-deps --document-private-items
```

14
rust/qemu-api/build.rs Normal file
View File

@ -0,0 +1,14 @@
// Copyright 2024, Linaro Limited
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later
use std::path::Path;
fn main() {
if !Path::new("src/bindings.rs").exists() {
panic!(
"No generated C bindings found! Either build them manually with bindgen or with meson \
(`ninja bindings.rs`) and copy them to src/bindings.rs, or build through meson."
);
}
}

24
rust/qemu-api/meson.build Normal file
View File

@ -0,0 +1,24 @@
_qemu_api_rs = static_library(
'qemu_api',
structured_sources(
[
'src/lib.rs',
'src/definitions.rs',
'src/device_class.rs',
],
{'.' : bindings_rs},
),
override_options: ['rust_std=2021', 'build.rust_std=2021'],
rust_abi: 'rust',
rust_args: rustc_args + [
'--cfg', 'MESON',
# '--cfg', 'feature="allocator"',
],
dependencies: [
qemu_api_macros,
],
)
qemu_api = declare_dependency(
link_with: _qemu_api_rs,
)

View File

@ -0,0 +1,97 @@
// Copyright 2024, Linaro Limited
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later
//! Definitions required by QEMU when registering a device.
use ::core::ffi::{c_void, CStr};
use crate::bindings::{Object, ObjectClass, TypeInfo};
/// Trait a type must implement to be registered with QEMU.
pub trait ObjectImpl {
type Class;
const TYPE_INFO: TypeInfo;
const TYPE_NAME: &'static CStr;
const PARENT_TYPE_NAME: Option<&'static CStr>;
const ABSTRACT: bool;
const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)>;
const INSTANCE_POST_INIT: Option<unsafe extern "C" fn(obj: *mut Object)>;
const INSTANCE_FINALIZE: Option<unsafe extern "C" fn(obj: *mut Object)>;
}
pub trait Class {
const CLASS_INIT: Option<unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void)>;
const CLASS_BASE_INIT: Option<
unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void),
>;
}
#[macro_export]
macro_rules! module_init {
($func:expr, $type:expr) => {
#[used]
#[cfg_attr(target_os = "linux", link_section = ".ctors")]
#[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
#[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
pub static LOAD_MODULE: extern "C" fn() = {
extern "C" fn __load() {
unsafe {
$crate::bindings::register_module_init(Some($func), $type);
}
}
__load
};
};
(qom: $func:ident => $body:block) => {
// NOTE: To have custom identifiers for the ctor func we need to either supply
// them directly as a macro argument or create them with a proc macro.
#[used]
#[cfg_attr(target_os = "linux", link_section = ".ctors")]
#[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
#[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
pub static LOAD_MODULE: extern "C" fn() = {
extern "C" fn __load() {
#[no_mangle]
unsafe extern "C" fn $func() {
$body
}
unsafe {
$crate::bindings::register_module_init(
Some($func),
$crate::bindings::module_init_type::MODULE_INIT_QOM,
);
}
}
__load
};
};
}
#[macro_export]
macro_rules! type_info {
($t:ty) => {
$crate::bindings::TypeInfo {
name: <$t as $crate::definitions::ObjectImpl>::TYPE_NAME.as_ptr(),
parent: if let Some(pname) = <$t as $crate::definitions::ObjectImpl>::PARENT_TYPE_NAME {
pname.as_ptr()
} else {
::core::ptr::null_mut()
},
instance_size: ::core::mem::size_of::<$t>() as $crate::bindings::size_t,
instance_align: ::core::mem::align_of::<$t>() as $crate::bindings::size_t,
instance_init: <$t as $crate::definitions::ObjectImpl>::INSTANCE_INIT,
instance_post_init: <$t as $crate::definitions::ObjectImpl>::INSTANCE_POST_INIT,
instance_finalize: <$t as $crate::definitions::ObjectImpl>::INSTANCE_FINALIZE,
abstract_: <$t as $crate::definitions::ObjectImpl>::ABSTRACT,
class_size: ::core::mem::size_of::<<$t as $crate::definitions::ObjectImpl>::Class>() as $crate::bindings::size_t,
class_init: <<$t as $crate::definitions::ObjectImpl>::Class as $crate::definitions::Class>::CLASS_INIT,
class_base_init: <<$t as $crate::definitions::ObjectImpl>::Class as $crate::definitions::Class>::CLASS_BASE_INIT,
class_data: ::core::ptr::null_mut(),
interfaces: ::core::ptr::null_mut(),
};
}
}

View File

@ -0,0 +1,128 @@
// Copyright 2024, Linaro Limited
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later
use std::sync::OnceLock;
use crate::bindings::Property;
#[macro_export]
macro_rules! device_class_init {
($func:ident, props => $props:ident, realize_fn => $realize_fn:expr, legacy_reset_fn => $legacy_reset_fn:expr, vmsd => $vmsd:ident$(,)*) => {
#[no_mangle]
pub unsafe extern "C" fn $func(
klass: *mut $crate::bindings::ObjectClass,
_: *mut ::core::ffi::c_void,
) {
let mut dc =
::core::ptr::NonNull::new(klass.cast::<$crate::bindings::DeviceClass>()).unwrap();
dc.as_mut().realize = $realize_fn;
dc.as_mut().vmsd = &$vmsd;
$crate::bindings::device_class_set_legacy_reset(dc.as_mut(), $legacy_reset_fn);
$crate::bindings::device_class_set_props(dc.as_mut(), $props.as_mut_ptr());
}
};
}
#[macro_export]
macro_rules! define_property {
($name:expr, $state:ty, $field:expr, $prop:expr, $type:expr, default = $defval:expr$(,)*) => {
$crate::bindings::Property {
name: {
#[used]
static _TEMP: &::core::ffi::CStr = $name;
_TEMP.as_ptr()
},
info: $prop,
offset: ::core::mem::offset_of!($state, $field)
.try_into()
.expect("Could not fit offset value to type"),
bitnr: 0,
bitmask: 0,
set_default: true,
defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval.into() },
arrayoffset: 0,
arrayinfo: ::core::ptr::null(),
arrayfieldsize: 0,
link_type: ::core::ptr::null(),
}
};
($name:expr, $state:ty, $field:expr, $prop:expr, $type:expr$(,)*) => {
$crate::bindings::Property {
name: {
#[used]
static _TEMP: &::core::ffi::CStr = $name;
_TEMP.as_ptr()
},
info: $prop,
offset: ::core::mem::offset_of!($state, $field)
.try_into()
.expect("Could not fit offset value to type"),
bitnr: 0,
bitmask: 0,
set_default: false,
defval: $crate::bindings::Property__bindgen_ty_1 { i: 0 },
arrayoffset: 0,
arrayinfo: ::core::ptr::null(),
arrayfieldsize: 0,
link_type: ::core::ptr::null(),
}
};
}
#[repr(C)]
pub struct Properties<const N: usize>(pub OnceLock<[Property; N]>, pub fn() -> [Property; N]);
impl<const N: usize> Properties<N> {
pub fn as_mut_ptr(&mut self) -> *mut Property {
_ = self.0.get_or_init(self.1);
self.0.get_mut().unwrap().as_mut_ptr()
}
}
#[macro_export]
macro_rules! declare_properties {
($ident:ident, $($prop:expr),*$(,)*) => {
const fn _calc_prop_len() -> usize {
let mut len = 1;
$({
_ = stringify!($prop);
len += 1;
})*
len
}
const PROP_LEN: usize = _calc_prop_len();
fn _make_properties() -> [$crate::bindings::Property; PROP_LEN] {
[
$($prop),*,
unsafe { ::core::mem::MaybeUninit::<$crate::bindings::Property>::zeroed().assume_init() },
]
}
#[no_mangle]
pub static mut $ident: $crate::device_class::Properties<PROP_LEN> = $crate::device_class::Properties(::std::sync::OnceLock::new(), _make_properties);
};
}
#[macro_export]
macro_rules! vm_state_description {
($(#[$outer:meta])*
$name:ident,
$(name: $vname:expr,)*
$(unmigratable: $um_val:expr,)*
) => {
#[used]
$(#[$outer])*
pub static $name: $crate::bindings::VMStateDescription = $crate::bindings::VMStateDescription {
$(name: {
#[used]
static VMSTATE_NAME: &::core::ffi::CStr = $vname;
$vname.as_ptr()
},)*
unmigratable: true,
..unsafe { ::core::mem::MaybeUninit::<$crate::bindings::VMStateDescription>::zeroed().assume_init() }
};
}
}

166
rust/qemu-api/src/lib.rs Normal file
View File

@ -0,0 +1,166 @@
// Copyright 2024, Linaro Limited
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#![cfg_attr(not(MESON), doc = include_str!("../README.md"))]
#[allow(
dead_code,
improper_ctypes_definitions,
improper_ctypes,
non_camel_case_types,
non_snake_case,
non_upper_case_globals,
unsafe_op_in_unsafe_fn,
clippy::missing_const_for_fn,
clippy::too_many_arguments,
clippy::approx_constant,
clippy::use_self,
clippy::useless_transmute,
clippy::missing_safety_doc,
)]
#[rustfmt::skip]
pub mod bindings;
unsafe impl Send for bindings::Property {}
unsafe impl Sync for bindings::Property {}
unsafe impl Sync for bindings::TypeInfo {}
unsafe impl Sync for bindings::VMStateDescription {}
pub mod definitions;
pub mod device_class;
#[cfg(test)]
mod tests;
use std::alloc::{GlobalAlloc, Layout};
#[cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)]
extern "C" {
fn g_aligned_alloc0(
n_blocks: bindings::gsize,
n_block_bytes: bindings::gsize,
alignment: bindings::gsize,
) -> bindings::gpointer;
fn g_aligned_free(mem: bindings::gpointer);
}
#[cfg(not(HAVE_GLIB_WITH_ALIGNED_ALLOC))]
extern "C" {
fn qemu_memalign(alignment: usize, size: usize) -> *mut ::core::ffi::c_void;
fn qemu_vfree(ptr: *mut ::core::ffi::c_void);
}
extern "C" {
fn g_malloc0(n_bytes: bindings::gsize) -> bindings::gpointer;
fn g_free(mem: bindings::gpointer);
}
/// An allocator that uses the same allocator as QEMU in C.
///
/// It is enabled by default with the `allocator` feature.
///
/// To set it up manually as a global allocator in your crate:
///
/// ```ignore
/// use qemu_api::QemuAllocator;
///
/// #[global_allocator]
/// static GLOBAL: QemuAllocator = QemuAllocator::new();
/// ```
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct QemuAllocator {
_unused: [u8; 0],
}
#[cfg_attr(all(feature = "allocator", not(test)), global_allocator)]
pub static GLOBAL: QemuAllocator = QemuAllocator::new();
impl QemuAllocator {
// From the glibc documentation, on GNU systems, malloc guarantees 16-byte
// alignment on 64-bit systems and 8-byte alignment on 32-bit systems. See
// https://www.gnu.org/software/libc/manual/html_node/Malloc-Examples.html.
// This alignment guarantee also applies to Windows and Android. On Darwin
// and OpenBSD, the alignment is 16 bytes on both 64-bit and 32-bit systems.
#[cfg(all(
target_pointer_width = "32",
not(any(target_os = "macos", target_os = "openbsd"))
))]
pub const DEFAULT_ALIGNMENT_BYTES: Option<usize> = Some(8);
#[cfg(all(
target_pointer_width = "64",
not(any(target_os = "macos", target_os = "openbsd"))
))]
pub const DEFAULT_ALIGNMENT_BYTES: Option<usize> = Some(16);
#[cfg(all(
any(target_pointer_width = "32", target_pointer_width = "64"),
any(target_os = "macos", target_os = "openbsd")
))]
pub const DEFAULT_ALIGNMENT_BYTES: Option<usize> = Some(16);
#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
pub const DEFAULT_ALIGNMENT_BYTES: Option<usize> = None;
pub const fn new() -> Self {
Self { _unused: [] }
}
}
impl Default for QemuAllocator {
fn default() -> Self {
Self::new()
}
}
// Sanity check.
const _: [(); 8] = [(); ::core::mem::size_of::<*mut ::core::ffi::c_void>()];
unsafe impl GlobalAlloc for QemuAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
if matches!(Self::DEFAULT_ALIGNMENT_BYTES, Some(default) if default.checked_rem(layout.align()) == Some(0))
{
// SAFETY: g_malloc0() is safe to call.
unsafe { g_malloc0(layout.size().try_into().unwrap()).cast::<u8>() }
} else {
#[cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)]
{
// SAFETY: g_aligned_alloc0() is safe to call.
unsafe {
g_aligned_alloc0(
layout.size().try_into().unwrap(),
1,
layout.align().try_into().unwrap(),
)
.cast::<u8>()
}
}
#[cfg(not(HAVE_GLIB_WITH_ALIGNED_ALLOC))]
{
// SAFETY: qemu_memalign() is safe to call.
unsafe { qemu_memalign(layout.align(), layout.size()).cast::<u8>() }
}
}
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
if matches!(Self::DEFAULT_ALIGNMENT_BYTES, Some(default) if default.checked_rem(layout.align()) == Some(0))
{
// SAFETY: `ptr` must have been allocated by Self::alloc thus a valid
// glib-allocated pointer, so `g_free`ing is safe.
unsafe { g_free(ptr.cast::<_>()) }
} else {
#[cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)]
{
// SAFETY: `ptr` must have been allocated by Self::alloc thus a valid aligned
// glib-allocated pointer, so `g_aligned_free`ing is safe.
unsafe { g_aligned_free(ptr.cast::<_>()) }
}
#[cfg(not(HAVE_GLIB_WITH_ALIGNED_ALLOC))]
{
// SAFETY: `ptr` must have been allocated by Self::alloc thus a valid aligned
// glib-allocated pointer, so `qemu_vfree`ing is safe.
unsafe { qemu_vfree(ptr.cast::<_>()) }
}
}
}
}

View File

@ -0,0 +1,49 @@
// Copyright 2024, Linaro Limited
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later
use crate::{
bindings::*, declare_properties, define_property, device_class_init, vm_state_description,
};
#[test]
fn test_device_decl_macros() {
// Test that macros can compile.
vm_state_description! {
VMSTATE,
name: c"name",
unmigratable: true,
}
#[repr(C)]
pub struct DummyState {
pub char_backend: CharBackend,
pub migrate_clock: bool,
}
declare_properties! {
DUMMY_PROPERTIES,
define_property!(
c"chardev",
DummyState,
char_backend,
unsafe { &qdev_prop_chr },
CharBackend
),
define_property!(
c"migrate-clk",
DummyState,
migrate_clock,
unsafe { &qdev_prop_bool },
bool
),
}
device_class_init! {
dummy_class_init,
props => DUMMY_PROPERTIES,
realize_fn => None,
reset_fn => None,
vmsd => VMSTATE,
}
}

7
rust/rustfmt.toml Normal file
View File

@ -0,0 +1,7 @@
edition = "2021"
format_generated_files = false
format_code_in_doc_comments = true
format_strings = true
imports_granularity = "Crate"
group_imports = "StdExternalCrate"
wrap_comments = true

47
rust/wrapper.h Normal file
View File

@ -0,0 +1,47 @@
/*
* QEMU System Emulator
*
* Copyright (c) 2024 Linaro Ltd.
*
* Authors: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
*
* 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.
*/
/*
* This header file is meant to be used as input to the `bindgen` application
* in order to generate C FFI compatible Rust bindings.
*/
#include "qemu/osdep.h"
#include "qemu/module.h"
#include "qemu-io.h"
#include "sysemu/sysemu.h"
#include "hw/sysbus.h"
#include "exec/memory.h"
#include "chardev/char-fe.h"
#include "hw/clock.h"
#include "hw/qdev-clock.h"
#include "hw/qdev-properties.h"
#include "hw/qdev-properties-system.h"
#include "hw/irq.h"
#include "qapi/error.h"
#include "migration/vmstate.h"
#include "chardev/char-serial.h"

View File

@ -26,7 +26,11 @@ sub_file="${sub_tdir}/submodule.tar"
# independent of what the developer currently has initialized # independent of what the developer currently has initialized
# in their checkout, because the build environment is completely # in their checkout, because the build environment is completely
# different to the host OS. # different to the host OS.
subprojects="keycodemapdb libvfio-user berkeley-softfloat-3 berkeley-testfloat-3" subprojects="keycodemapdb libvfio-user berkeley-softfloat-3
berkeley-testfloat-3 arbitrary-int-1-rs bilge-0.2-rs
bilge-impl-0.2-rs either-1-rs itertools-0.11-rs proc-macro2-1-rs
proc-macro-error-1-rs proc-macro-error-attr-1-rs quote-1-rs
syn-2-rs unicode-ident-1-rs"
sub_deinit="" sub_deinit=""
function cleanup() { function cleanup() {
@ -48,13 +52,34 @@ function tree_ish() {
echo "$retval" echo "$retval"
} }
function subproject_dir() {
if test ! -f "subprojects/$1.wrap"; then
error "scripts/archive-source.sh should only process wrap subprojects"
fi
# Print the directory key of the wrap file, defaulting to the
# subproject name. The wrap file is in ini format and should
# have a single section only. There should be only one section
# named "[wrap-*]", which helps keeping the script simple.
local dir
dir=$(sed -n \
-e '/^\[wrap-[a-z][a-z]*\]$/,/^\[/{' \
-e '/^directory *= */!b' \
-e 's///p' \
-e 'q' \
-e '}' \
"subprojects/$1.wrap")
echo "${dir:-$1}"
}
git archive --format tar "$(tree_ish)" > "$tar_file" git archive --format tar "$(tree_ish)" > "$tar_file"
test $? -ne 0 && error "failed to archive qemu" test $? -ne 0 && error "failed to archive qemu"
for sp in $subprojects; do for sp in $subprojects; do
meson subprojects download $sp meson subprojects download $sp
test $? -ne 0 && error "failed to download subproject $sp" test $? -ne 0 && error "failed to download subproject $sp"
tar --append --file "$tar_file" --exclude=.git subprojects/$sp tar --append --file "$tar_file" --exclude=.git subprojects/"$(subproject_dir $sp)"
test $? -ne 0 && error "failed to append subproject $sp to $tar_file" test $? -ne 0 && error "failed to append subproject $sp to $tar_file"
done done
exit 0 exit 0

View File

@ -17,7 +17,11 @@ if [ $# -ne 2 ]; then
fi fi
# Only include wraps that are invoked with subproject() # Only include wraps that are invoked with subproject()
SUBPROJECTS="libvfio-user keycodemapdb berkeley-softfloat-3 berkeley-testfloat-3" SUBPROJECTS="libvfio-user keycodemapdb berkeley-softfloat-3
berkeley-testfloat-3 arbitrary-int-1-rs bilge-0.2-rs
bilge-impl-0.2-rs either-1-rs itertools-0.11-rs proc-macro2-1-rs
proc-macro-error-1-rs proc-macro-error-attr-1-rs quote-1-rs
syn-2-rs unicode-ident-1-rs"
src="$1" src="$1"
version="$2" version="$2"

View File

@ -172,6 +172,7 @@ meson_options_help() {
printf "%s\n" ' rbd Ceph block device driver' printf "%s\n" ' rbd Ceph block device driver'
printf "%s\n" ' rdma Enable RDMA-based migration' printf "%s\n" ' rdma Enable RDMA-based migration'
printf "%s\n" ' replication replication support' printf "%s\n" ' replication replication support'
printf "%s\n" ' rust Rust support'
printf "%s\n" ' rutabaga-gfx rutabaga_gfx support' printf "%s\n" ' rutabaga-gfx rutabaga_gfx support'
printf "%s\n" ' sdl SDL user interface' printf "%s\n" ' sdl SDL user interface'
printf "%s\n" ' sdl-image SDL Image support for icons' printf "%s\n" ' sdl-image SDL Image support for icons'
@ -456,6 +457,8 @@ _meson_option_parse() {
--disable-replication) printf "%s" -Dreplication=disabled ;; --disable-replication) printf "%s" -Dreplication=disabled ;;
--enable-rng-none) printf "%s" -Drng_none=true ;; --enable-rng-none) printf "%s" -Drng_none=true ;;
--disable-rng-none) printf "%s" -Drng_none=false ;; --disable-rng-none) printf "%s" -Drng_none=false ;;
--enable-rust) printf "%s" -Drust=enabled ;;
--disable-rust) printf "%s" -Drust=disabled ;;
--enable-rutabaga-gfx) printf "%s" -Drutabaga_gfx=enabled ;; --enable-rutabaga-gfx) printf "%s" -Drutabaga_gfx=enabled ;;
--disable-rutabaga-gfx) printf "%s" -Drutabaga_gfx=disabled ;; --disable-rutabaga-gfx) printf "%s" -Drutabaga_gfx=disabled ;;
--enable-safe-stack) printf "%s" -Dsafe_stack=true ;; --enable-safe-stack) printf "%s" -Dsafe_stack=true ;;

13
scripts/rust/rust_root_crate.sh Executable file
View File

@ -0,0 +1,13 @@
#!/bin/sh
set -eu
cat <<EOF
// @generated
// This file is autogenerated by scripts/rust_root_crate.sh
EOF
for crate in $*; do
echo "extern crate $crate;"
done

View File

@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Generate rustc arguments for meson rust builds.
This program generates --cfg compile flags for the configuration headers passed
as arguments.
Copyright (c) 2024 Linaro Ltd.
Authors:
Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
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 <http://www.gnu.org/licenses/>.
"""
import argparse
import logging
from typing import List
def generate_cfg_flags(header: str) -> List[str]:
"""Converts defines from config[..].h headers to rustc --cfg flags."""
def cfg_name(name: str) -> str:
"""Filter function for C #defines"""
if (
name.startswith("CONFIG_")
or name.startswith("TARGET_")
or name.startswith("HAVE_")
):
return name
return ""
with open(header, encoding="utf-8") as cfg:
config = [l.split()[1:] for l in cfg if l.startswith("#define")]
cfg_list = []
for cfg in config:
name = cfg_name(cfg[0])
if not name:
continue
if len(cfg) >= 2 and cfg[1] != "1":
continue
cfg_list.append("--cfg")
cfg_list.append(name)
return cfg_list
def main() -> None:
# pylint: disable=missing-function-docstring
parser = argparse.ArgumentParser()
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument(
"--config-headers",
metavar="CONFIG_HEADER",
action="append",
dest="config_headers",
help="paths to any configuration C headers (*.h files), if any",
required=False,
default=[],
)
args = parser.parse_args()
if args.verbose:
logging.basicConfig(level=logging.DEBUG)
logging.debug("args: %s", args)
for header in args.config_headers:
for tok in generate_cfg_flags(header):
print(tok)
if __name__ == "__main__":
main()

View File

@ -6,3 +6,14 @@
/keycodemapdb /keycodemapdb
/libvfio-user /libvfio-user
/slirp /slirp
/arbitrary-int-1.2.7
/bilge-0.2.0
/bilge-impl-0.2.0
/either-1.12.0
/itertools-0.11.0
/proc-macro-error-1.0.4
/proc-macro-error-attr-1.0.4
/proc-macro2-1.0.84
/quote-1.0.36
/syn-2.0.66
/unicode-ident-1.0.12

View File

@ -0,0 +1,7 @@
[wrap-file]
directory = arbitrary-int-1.2.7
source_url = https://crates.io/api/v1/crates/arbitrary-int/1.2.7/download
source_filename = arbitrary-int-1.2.7.tar.gz
source_hash = c84fc003e338a6f69fbd4f7fe9f92b535ff13e9af8997f3b14b6ddff8b1df46d
#method = cargo
patch_directory = arbitrary-int-1-rs

View File

@ -0,0 +1,7 @@
[wrap-file]
directory = bilge-0.2.0
source_url = https://crates.io/api/v1/crates/bilge/0.2.0/download
source_filename = bilge-0.2.0.tar.gz
source_hash = dc707ed8ebf81de5cd6c7f48f54b4c8621760926cdf35a57000747c512e67b57
#method = cargo
patch_directory = bilge-0.2-rs

View File

@ -0,0 +1,7 @@
[wrap-file]
directory = bilge-impl-0.2.0
source_url = https://crates.io/api/v1/crates/bilge-impl/0.2.0/download
source_filename = bilge-impl-0.2.0.tar.gz
source_hash = feb11e002038ad243af39c2068c8a72bcf147acf05025dcdb916fcc000adb2d8
#method = cargo
patch_directory = bilge-impl-0.2-rs

View File

@ -0,0 +1,7 @@
[wrap-file]
directory = either-1.12.0
source_url = https://crates.io/api/v1/crates/either/1.12.0/download
source_filename = either-1.12.0.tar.gz
source_hash = 3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b
#method = cargo
patch_directory = either-1-rs

View File

@ -0,0 +1,7 @@
[wrap-file]
directory = itertools-0.11.0
source_url = https://crates.io/api/v1/crates/itertools/0.11.0/download
source_filename = itertools-0.11.0.tar.gz
source_hash = b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57
#method = cargo
patch_directory = itertools-0.11-rs

View File

@ -0,0 +1,19 @@
project('arbitrary-int-1-rs', 'rust',
version: '1.2.7',
license: 'MIT',
default_options: [])
_arbitrary_int_rs = static_library(
'arbitrary_int',
files('src/lib.rs'),
gnu_symbol_visibility: 'hidden',
override_options: ['rust_std=2021', 'build.rust_std=2021'],
rust_abi: 'rust',
dependencies: [],
)
arbitrary_int_dep = declare_dependency(
link_with: _arbitrary_int_rs,
)
meson.override_dependency('arbitrary-int-1-rs', arbitrary_int_dep)

View File

@ -0,0 +1,29 @@
project(
'bilge-0.2-rs',
'rust',
version : '0.2.0',
license : 'MIT or Apache-2.0',
)
subproject('arbitrary-int-1-rs', required: true)
subproject('bilge-impl-0.2-rs', required: true)
arbitrary_int_dep = dependency('arbitrary-int-1-rs')
bilge_impl_dep = dependency('bilge-impl-0.2-rs')
lib = static_library(
'bilge',
'src/lib.rs',
override_options : ['rust_std=2021', 'build.rust_std=2021'],
rust_abi : 'rust',
dependencies: [
arbitrary_int_dep,
bilge_impl_dep,
],
)
bilge_dep = declare_dependency(
link_with : [lib],
)
meson.override_dependency('bilge-0.2-rs', bilge_dep)

View File

@ -0,0 +1,45 @@
project('bilge-impl-0.2-rs', 'rust',
version: '0.2.0',
license: 'MIT OR Apache-2.0',
default_options: [])
subproject('itertools-0.11-rs', required: true)
subproject('proc-macro-error-attr-1-rs', required: true)
subproject('proc-macro-error-1-rs', required: true)
subproject('quote-1-rs', required: true)
subproject('syn-2-rs', required: true)
subproject('proc-macro2-1-rs', required: true)
itertools_dep = dependency('itertools-0.11-rs', native: true)
proc_macro_error_attr_dep = dependency('proc-macro-error-attr-1-rs', native: true)
proc_macro_error_dep = dependency('proc-macro-error-1-rs', native: true)
quote_dep = dependency('quote-1-rs', native: true)
syn_dep = dependency('syn-2-rs', native: true)
proc_macro2_dep = dependency('proc-macro2-1-rs', native: true)
rust = import('rust')
_bilge_impl_rs = rust.proc_macro(
'bilge_impl',
files('src/lib.rs'),
override_options: ['rust_std=2021', 'build.rust_std=2021'],
rust_args: [
'--cfg', 'use_fallback',
'--cfg', 'feature="syn-error"',
'--cfg', 'feature="proc-macro"',
],
dependencies: [
itertools_dep,
proc_macro_error_attr_dep,
proc_macro_error_dep,
quote_dep,
syn_dep,
proc_macro2_dep,
],
)
bilge_impl_dep = declare_dependency(
link_with: _bilge_impl_rs,
)
meson.override_dependency('bilge-impl-0.2-rs', bilge_impl_dep)

View File

@ -0,0 +1,24 @@
project('either-1-rs', 'rust',
version: '1.12.0',
license: 'MIT OR Apache-2.0',
default_options: [])
_either_rs = static_library(
'either',
files('src/lib.rs'),
gnu_symbol_visibility: 'hidden',
override_options: ['rust_std=2018', 'build.rust_std=2018'],
rust_abi: 'rust',
rust_args: [
'--cfg', 'feature="use_std"',
'--cfg', 'feature="use_alloc"',
],
dependencies: [],
native: true,
)
either_dep = declare_dependency(
link_with: _either_rs,
)
meson.override_dependency('either-1-rs', either_dep, native: true)

View File

@ -0,0 +1,30 @@
project('itertools-0.11-rs', 'rust',
version: '0.11.0',
license: 'MIT OR Apache-2.0',
default_options: [])
subproject('either-1-rs', required: true)
either_dep = dependency('either-1-rs', native: true)
_itertools_rs = static_library(
'itertools',
files('src/lib.rs'),
gnu_symbol_visibility: 'hidden',
override_options: ['rust_std=2018', 'build.rust_std=2018'],
rust_abi: 'rust',
rust_args: [
'--cfg', 'feature="use_std"',
'--cfg', 'feature="use_alloc"',
],
dependencies: [
either_dep,
],
native: true,
)
itertools_dep = declare_dependency(
link_with: _itertools_rs,
)
meson.override_dependency('itertools-0.11-rs', itertools_dep, native: true)

View File

@ -0,0 +1,40 @@
project('proc-macro-error-1-rs', 'rust',
version: '1.0.4',
license: 'MIT OR Apache-2.0',
default_options: [])
subproject('proc-macro-error-attr-1-rs', required: true)
subproject('quote-1-rs', required: true)
subproject('syn-2-rs', required: true)
subproject('proc-macro2-1-rs', required: true)
proc_macro_error_attr_dep = dependency('proc-macro-error-attr-1-rs', native: true)
proc_macro2_dep = dependency('proc-macro2-1-rs', native: true)
quote_dep = dependency('quote-1-rs', native: true)
syn_dep = dependency('syn-2-rs', native: true)
_proc_macro_error_rs = static_library(
'proc_macro_error',
files('src/lib.rs'),
override_options: ['rust_std=2018', 'build.rust_std=2018'],
rust_abi: 'rust',
rust_args: [
'--cfg', 'use_fallback',
'--cfg', 'feature="syn-error"',
'--cfg', 'feature="proc-macro"',
'-A', 'non_fmt_panics'
],
dependencies: [
proc_macro_error_attr_dep,
proc_macro2_dep,
quote_dep,
syn_dep,
],
native: true,
)
proc_macro_error_dep = declare_dependency(
link_with: _proc_macro_error_rs,
)
meson.override_dependency('proc-macro-error-1-rs', proc_macro_error_dep, native: true)

View File

@ -0,0 +1,32 @@
project('proc-macro-error-attr-1-rs', 'rust',
version: '1.12.0',
license: 'MIT OR Apache-2.0',
default_options: [])
subproject('proc-macro2-1-rs', required: true)
subproject('quote-1-rs', required: true)
proc_macro2_dep = dependency('proc-macro2-1-rs', native: true)
quote_dep = dependency('quote-1-rs', native: true)
rust = import('rust')
_proc_macro_error_attr_rs = rust.proc_macro(
'proc_macro_error_attr',
files('src/lib.rs'),
override_options: ['rust_std=2018', 'build.rust_std=2018'],
rust_args: [
'--cfg', 'use_fallback',
'--cfg', 'feature="syn-error"',
'--cfg', 'feature="proc-macro"'
],
dependencies: [
proc_macro2_dep,
quote_dep,
],
)
proc_macro_error_attr_dep = declare_dependency(
link_with: _proc_macro_error_attr_rs,
)
meson.override_dependency('proc-macro-error-attr-1-rs', proc_macro_error_attr_dep, native: true)

View File

@ -0,0 +1,31 @@
project('proc-macro2-1-rs', 'rust',
version: '1.0.84',
license: 'MIT OR Apache-2.0',
default_options: [])
subproject('unicode-ident-1-rs', required: true)
unicode_ident_dep = dependency('unicode-ident-1-rs', native: true)
_proc_macro2_rs = static_library(
'proc_macro2',
files('src/lib.rs'),
gnu_symbol_visibility: 'hidden',
override_options: ['rust_std=2021', 'build.rust_std=2021'],
rust_abi: 'rust',
rust_args: [
'--cfg', 'feature="proc-macro"',
'--cfg', 'span_locations',
'--cfg', 'wrap_proc_macro',
],
dependencies: [
unicode_ident_dep,
],
native: true,
)
proc_macro2_dep = declare_dependency(
link_with: _proc_macro2_rs,
)
meson.override_dependency('proc-macro2-1-rs', proc_macro2_dep, native: true)

View File

@ -0,0 +1,29 @@
project('quote-1-rs', 'rust',
version: '1.12.0',
license: 'MIT OR Apache-2.0',
default_options: [])
subproject('proc-macro2-1-rs', required: true)
proc_macro2_dep = dependency('proc-macro2-1-rs', native: true)
_quote_rs = static_library(
'quote',
files('src/lib.rs'),
gnu_symbol_visibility: 'hidden',
override_options: ['rust_std=2021', 'build.rust_std=2021'],
rust_abi: 'rust',
rust_args: [
'--cfg', 'feature="proc-macro"',
],
dependencies: [
proc_macro2_dep,
],
native: true,
)
quote_dep = declare_dependency(
link_with: _quote_rs,
)
meson.override_dependency('quote-1-rs', quote_dep, native: true)

View File

@ -0,0 +1,40 @@
project('syn-2-rs', 'rust',
version: '2.0.66',
license: 'MIT OR Apache-2.0',
default_options: [])
subproject('proc-macro2-1-rs', required: true)
subproject('quote-1-rs', required: true)
subproject('unicode-ident-1-rs', required: true)
proc_macro2_dep = dependency('proc-macro2-1-rs', native: true)
quote_dep = dependency('quote-1-rs', native: true)
unicode_ident_dep = dependency('unicode-ident-1-rs', native: true)
_syn_rs = static_library(
'syn',
files('src/lib.rs'),
gnu_symbol_visibility: 'hidden',
override_options: ['rust_std=2021', 'build.rust_std=2021'],
rust_abi: 'rust',
rust_args: [
'--cfg', 'feature="full"',
'--cfg', 'feature="derive"',
'--cfg', 'feature="parsing"',
'--cfg', 'feature="printing"',
'--cfg', 'feature="clone-impls"',
'--cfg', 'feature="proc-macro"',
],
dependencies: [
quote_dep,
proc_macro2_dep,
unicode_ident_dep,
],
native: true,
)
syn_dep = declare_dependency(
link_with: _syn_rs,
)
meson.override_dependency('syn-2-rs', syn_dep, native: true)

View File

@ -0,0 +1,20 @@
project('unicode-ident-1-rs', 'rust',
version: '1.0.12',
license: '(MIT OR Apache-2.0) AND Unicode-DFS-2016',
default_options: [])
_unicode_ident_rs = static_library(
'unicode_ident',
files('src/lib.rs'),
gnu_symbol_visibility: 'hidden',
override_options: ['rust_std=2021', 'build.rust_std=2021'],
rust_abi: 'rust',
dependencies: [],
native: true,
)
unicode_ident_dep = declare_dependency(
link_with: _unicode_ident_rs,
)
meson.override_dependency('unicode-ident-1-rs', unicode_ident_dep, native: true)

View File

@ -0,0 +1,7 @@
[wrap-file]
directory = proc-macro-error-1.0.4
source_url = https://crates.io/api/v1/crates/proc-macro-error/1.0.4/download
source_filename = proc-macro-error-1.0.4.tar.gz
source_hash = da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c
#method = cargo
patch_directory = proc-macro-error-1-rs

View File

@ -0,0 +1,7 @@
[wrap-file]
directory = proc-macro-error-attr-1.0.4
source_url = https://crates.io/api/v1/crates/proc-macro-error-attr/1.0.4/download
source_filename = proc-macro-error-attr-1.0.4.tar.gz
source_hash = a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869
#method = cargo
patch_directory = proc-macro-error-attr-1-rs

View File

@ -0,0 +1,7 @@
[wrap-file]
directory = proc-macro2-1.0.84
source_url = https://crates.io/api/v1/crates/proc-macro2/1.0.84/download
source_filename = proc-macro2-1.0.84.0.tar.gz
source_hash = ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6
#method = cargo
patch_directory = proc-macro2-1-rs

View File

@ -0,0 +1,7 @@
[wrap-file]
directory = quote-1.0.36
source_url = https://crates.io/api/v1/crates/quote/1.0.36/download
source_filename = quote-1.0.36.0.tar.gz
source_hash = 0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7
#method = cargo
patch_directory = quote-1-rs

View File

@ -0,0 +1,7 @@
[wrap-file]
directory = syn-2.0.66
source_url = https://crates.io/api/v1/crates/syn/2.0.66/download
source_filename = syn-2.0.66.0.tar.gz
source_hash = c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5
#method = cargo
patch_directory = syn-2-rs

View File

@ -0,0 +1,7 @@
[wrap-file]
directory = unicode-ident-1.0.12
source_url = https://crates.io/api/v1/crates/unicode-ident/1.0.12/download
source_filename = unicode-ident-1.0.12.tar.gz
source_hash = 3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b
#method = cargo
patch_directory = unicode-ident-1-rs

View File

@ -0,0 +1,20 @@
project('unicode-ident-1-rs', 'rust',
version: '1.0.12',
license: '(MIT OR Apache-2.0) AND Unicode-DFS-2016',
default_options: [])
_unicode_ident_rs = static_library(
'unicode_ident',
files('src/lib.rs'),
gnu_symbol_visibility: 'hidden',
override_options: ['rust_std=2021', 'build.rust_std=2021'],
rust_abi: 'rust',
dependencies: [],
native: true,
)
unicode_ident_dep = declare_dependency(
link_with: _unicode_ident_rs,
)
meson.override_dependency('unicode-ident-1-rs', unicode_ident_dep, native: true)

View File

@ -0,0 +1,173 @@
# THIS FILE WAS AUTO-GENERATED
#
# $ lcitool dockerfile --layers all fedora-40 qemu
#
# https://gitlab.com/libvirt/libvirt-ci
FROM registry.fedoraproject.org/fedora:40
RUN dnf install -y nosync && \
printf '#!/bin/sh\n\
if test -d /usr/lib64\n\
then\n\
export LD_PRELOAD=/usr/lib64/nosync/nosync.so\n\
else\n\
export LD_PRELOAD=/usr/lib/nosync/nosync.so\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 \
bison \
brlapi-devel \
bzip2 \
bzip2-devel \
ca-certificates \
capstone-devel \
ccache \
clang \
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 \
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-opencv \
python3-pillow \
python3-pip \
python3-sphinx \
python3-sphinx_rtd_theme \
python3-zombie-imp \
rdma-core-devel \
sed \
snappy-devel \
socat \
sparse \
spice-protocol \
spice-server-devel \
swtpm \
systemd-devel \
systemtap-sdt-devel \
tar \
tesseract \
tesseract-langpack-eng \
usbredir-devel \
util-linux \
virglrenderer-devel \
vte291-devel \
which \
xen-devel \
xorriso \
zlib-devel \
zlib-static \
zstd && \
nosync dnf autoremove -y && \
nosync dnf clean all -y && \
rm -f /usr/lib*/python3*/EXTERNALLY-MANAGED && \
rpm -qa | sort > /packages.txt && \
mkdir -p /usr/libexec/ccache-wrappers && \
ln -s /usr/bin/ccache /usr/libexec/ccache-wrappers/cc && \
ln -s /usr/bin/ccache /usr/libexec/ccache-wrappers/clang && \
ln -s /usr/bin/ccache /usr/libexec/ccache-wrappers/gcc
ENV CCACHE_WRAPPERSDIR "/usr/libexec/ccache-wrappers"
ENV LANG "en_US.UTF-8"
ENV MAKE "/usr/bin/make"
ENV NINJA "/usr/bin/ninja"
ENV PYTHON "/usr/bin/python3"
RUN dnf install -y wget
ENV RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo
ENV RUSTC=/usr/local/rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rustc
RUN set -eux && \
rustArch='x86_64-unknown-linux-gnu' && \
rustupSha256='6aeece6993e902708983b209d04c0d1dbb14ebb405ddb87def578d41f920f56d' && \
url="https://static.rust-lang.org/rustup/archive/1.27.1/${rustArch}/rustup-init" && \
wget "$url" && \
echo "${rustupSha256} *rustup-init" | sha256sum -c - && \
chmod +x rustup-init && \
./rustup-init -y --no-modify-path --profile default --default-toolchain nightly --default-host ${rustArch} && \
chmod -R a+w $RUSTUP_HOME $CARGO_HOME && \
/usr/local/cargo/bin/rustup --version && \
/usr/local/cargo/bin/rustup run nightly rustc --version && \
test "$RUSTC" = "$(/usr/local/cargo/bin/rustup +nightly which rustc)"
ENV PATH=$CARGO_HOME/bin:$PATH
RUN /usr/local/cargo/bin/rustup run nightly cargo install bindgen-cli
# 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

View File

@ -127,7 +127,7 @@ RUN zypper update -y && \
RUN /usr/bin/pip3.11 install \ RUN /usr/bin/pip3.11 install \
PyYAML \ PyYAML \
meson==0.63.2 \ meson==1.5.0 \
pillow \ pillow \
sphinx \ sphinx \
sphinx-rtd-theme sphinx-rtd-theme

View File

@ -89,7 +89,7 @@ mappings:
pypi_mappings: pypi_mappings:
# Request more recent version # Request more recent version
meson: meson:
default: meson==0.63.2 default: meson==1.5.0
# Drop packages that need devel headers # Drop packages that need devel headers
python3-numpy: python3-numpy:

View File

@ -116,6 +116,26 @@ debian12_extras = [
"ENV QEMU_CONFIGURE_OPTS --enable-netmap\n" "ENV QEMU_CONFIGURE_OPTS --enable-netmap\n"
] ]
# Based on the hub.docker.com/library/rust Dockerfiles
fedora_rustup_nightly_extras = [
"RUN dnf install -y wget\n",
"ENV RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo\n",
"ENV RUSTC=/usr/local/rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rustc\n",
"RUN set -eux && \\\n",
" rustArch='x86_64-unknown-linux-gnu' && \\\n",
" rustupSha256='6aeece6993e902708983b209d04c0d1dbb14ebb405ddb87def578d41f920f56d' && \\\n",
' url="https://static.rust-lang.org/rustup/archive/1.27.1/${rustArch}/rustup-init" && \\\n',
' wget "$url" && \\\n',
' echo "${rustupSha256} *rustup-init" | sha256sum -c - && \\\n',
" chmod +x rustup-init && \\\n",
" ./rustup-init -y --no-modify-path --profile default --default-toolchain nightly --default-host ${rustArch} && \\\n",
" chmod -R a+w $RUSTUP_HOME $CARGO_HOME && \\\n",
" /usr/local/cargo/bin/rustup --version && \\\n",
" /usr/local/cargo/bin/rustup run nightly rustc --version && \\\n",
' test "$RUSTC" = "$(/usr/local/cargo/bin/rustup +nightly which rustc)"\n',
'ENV PATH=$CARGO_HOME/bin:$PATH\n',
'RUN /usr/local/cargo/bin/rustup run nightly cargo install bindgen-cli\n',
]
def cross_build(prefix, targets): def cross_build(prefix, targets):
conf = "ENV QEMU_CONFIGURE_OPTS --cross-prefix=%s\n" % (prefix) conf = "ENV QEMU_CONFIGURE_OPTS --cross-prefix=%s\n" % (prefix)
@ -139,6 +159,12 @@ try:
generate_dockerfile("opensuse-leap", "opensuse-leap-15") generate_dockerfile("opensuse-leap", "opensuse-leap-15")
generate_dockerfile("ubuntu2204", "ubuntu-2204") generate_dockerfile("ubuntu2204", "ubuntu-2204")
#
# Non-fatal Rust-enabled build
#
generate_dockerfile("fedora-rust-nightly", "fedora-40",
trailer="".join(fedora_rustup_nightly_extras))
# #
# Cross compiling builds # Cross compiling builds
# #