mirror of https://gitlab.com/qemu-project/qemu
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
477 lines
19 KiB
ReStructuredText
477 lines
19 KiB
ReStructuredText
.. |msrv| replace:: 1.63.0
|
|
|
|
Rust in QEMU
|
|
============
|
|
|
|
Rust in QEMU is a project to enable using the Rust programming language
|
|
to add new functionality to QEMU.
|
|
|
|
Right now, the focus is on making it possible to write devices that inherit
|
|
from ``SysBusDevice`` in `*safe*`__ Rust. Later, it may become possible
|
|
to write other kinds of devices (e.g. PCI devices that can do DMA),
|
|
complete boards, or backends (e.g. block device formats).
|
|
|
|
__ https://doc.rust-lang.org/nomicon/meet-safe-and-unsafe.html
|
|
|
|
Building the Rust in QEMU code
|
|
------------------------------
|
|
|
|
The Rust in QEMU code is included in the emulators via Meson. Meson
|
|
invokes rustc directly, building static libraries that are then linked
|
|
together with the C code. This is completely automatic when you run
|
|
``make`` or ``ninja``.
|
|
|
|
However, QEMU's build system also tries to be easy to use for people who
|
|
are accustomed to the more "normal" Cargo-based development workflow.
|
|
In particular:
|
|
|
|
* the set of warnings and lints that are used to build QEMU always
|
|
comes from the ``rust/Cargo.toml`` workspace file
|
|
|
|
* it is also possible to use ``cargo`` for common Rust-specific coding
|
|
tasks, in particular to invoke ``clippy``, ``rustfmt`` and ``rustdoc``.
|
|
|
|
To this end, QEMU includes a ``build.rs`` build script that picks up
|
|
generated sources from QEMU's build directory and puts it in Cargo's
|
|
output directory (typically ``rust/target/``). A vanilla invocation
|
|
of Cargo will complain that it cannot find the generated sources,
|
|
which can be fixed in different ways:
|
|
|
|
* by using special shorthand targets in the QEMU build directory::
|
|
|
|
make clippy
|
|
make rustfmt
|
|
make rustdoc
|
|
|
|
* by invoking ``cargo`` through the Meson `development environment`__
|
|
feature::
|
|
|
|
pyvenv/bin/meson devenv -w ../rust cargo clippy --tests
|
|
pyvenv/bin/meson devenv -w ../rust cargo fmt
|
|
|
|
If you are going to use ``cargo`` repeatedly, ``pyvenv/bin/meson devenv``
|
|
will enter a shell where commands like ``cargo clippy`` just work.
|
|
|
|
__ https://mesonbuild.com/Commands.html#devenv
|
|
|
|
* by pointing the ``MESON_BUILD_ROOT`` to the top of your QEMU build
|
|
tree. This third method is useful if you are using ``rust-analyzer``;
|
|
you can set the environment variable through the
|
|
``rust-analyzer.cargo.extraEnv`` setting.
|
|
|
|
As shown above, you can use the ``--tests`` option as usual to operate on test
|
|
code. Note however that you cannot *build* or run tests via ``cargo``, because
|
|
they need support C code from QEMU that Cargo does not know about. Tests can
|
|
be run via ``meson test`` or ``make``::
|
|
|
|
make check-rust
|
|
|
|
Building Rust code with ``--enable-modules`` is not supported yet.
|
|
|
|
Supported tools
|
|
'''''''''''''''
|
|
|
|
QEMU supports rustc version 1.63.0 and newer. Notably, the following features
|
|
are missing:
|
|
|
|
* ``core::ffi`` (1.64.0). Use ``std::os::raw`` and ``std::ffi`` instead.
|
|
|
|
* ``cast_mut()``/``cast_const()`` (1.65.0). Use ``as`` instead.
|
|
|
|
* "let ... else" (1.65.0). Use ``if let`` instead. This is currently patched
|
|
in QEMU's vendored copy of the bilge crate.
|
|
|
|
* Generic Associated Types (1.65.0)
|
|
|
|
* ``CStr::from_bytes_with_nul()`` as a ``const`` function (1.72.0).
|
|
|
|
* "Return position ``impl Trait`` in Traits" (1.75.0, blocker for including
|
|
the pinned-init create).
|
|
|
|
* ``MaybeUninit::zeroed()`` as a ``const`` function (1.75.0). QEMU's
|
|
``Zeroable`` trait can be implemented without ``MaybeUninit::zeroed()``,
|
|
so this would be just a cleanup.
|
|
|
|
* ``c"" literals`` (stable in 1.77.0). QEMU provides a ``c_str!()`` macro
|
|
to define ``CStr`` constants easily
|
|
|
|
* ``offset_of!`` (stable in 1.77.0). QEMU uses ``offset_of!()`` heavily; it
|
|
provides a replacement in the ``qemu_api`` crate, but it does not support
|
|
lifetime parameters and therefore ``&'a Something`` fields in the struct
|
|
may have to be replaced by ``NonNull<Something>``. *Nested* ``offset_of!``
|
|
was only stabilized in Rust 1.82.0, but it is not used.
|
|
|
|
* inline const expression (stable in 1.79.0), currently worked around with
|
|
associated constants in the ``FnCall`` trait.
|
|
|
|
* associated constants have to be explicitly marked ``'static`` (`changed in
|
|
1.81.0`__)
|
|
|
|
* ``&raw`` (stable in 1.82.0). Use ``addr_of!`` and ``addr_of_mut!`` instead,
|
|
though hopefully the need for raw pointers will go down over time.
|
|
|
|
* ``new_uninit`` (stable in 1.82.0). This is used internally by the ``pinned_init``
|
|
crate, which is planned for inclusion in QEMU, but it can be easily patched
|
|
out.
|
|
|
|
* referencing statics in constants (stable in 1.83.0). For now use a const
|
|
function; this is an important limitation for QEMU's migration stream
|
|
architecture (VMState). Right now, VMState lacks type safety because
|
|
it is hard to place the ``VMStateField`` definitions in traits.
|
|
|
|
* associated const equality would be nice to have for some users of
|
|
``callbacks::FnCall``, but is still experimental. ``ASSERT_IS_SOME``
|
|
replaces it.
|
|
|
|
__ https://github.com/rust-lang/rust/pull/125258
|
|
|
|
It is expected that QEMU will advance its minimum supported version of
|
|
rustc to 1.77.0 as soon as possible; as of January 2025, blockers
|
|
for that right now are Debian bookworm and 32-bit MIPS processors.
|
|
This unfortunately means that references to statics in constants will
|
|
remain an issue.
|
|
|
|
QEMU also supports version 0.60.x of bindgen, which is missing option
|
|
``--generate-cstr``. This option requires version 0.66.x and will
|
|
be adopted as soon as supporting these older versions is not necessary
|
|
anymore.
|
|
|
|
Writing Rust code in QEMU
|
|
-------------------------
|
|
|
|
Right now QEMU includes three crates:
|
|
|
|
* ``qemu_api`` for bindings to C code and useful functionality
|
|
|
|
* ``qemu_api_macros`` defines several procedural macros that are useful when
|
|
writing C code
|
|
|
|
* ``pl011`` (under ``rust/hw/char/pl011``) is the sample device that is being
|
|
used to further develop ``qemu_api`` and ``qemu_api_macros``. It is a functional
|
|
replacement for the ``hw/char/pl011.c`` file.
|
|
|
|
This section explains how to work with them.
|
|
|
|
Status
|
|
''''''
|
|
|
|
Modules of ``qemu_api`` can be defined as:
|
|
|
|
- *complete*: ready for use in new devices; if applicable, the API supports the
|
|
full functionality available in C
|
|
|
|
- *stable*: ready for production use, the API is safe and should not undergo
|
|
major changes
|
|
|
|
- *proof of concept*: the API is subject to change but allows working with safe
|
|
Rust
|
|
|
|
- *initial*: the API is in its initial stages; it requires large amount of
|
|
unsafe code; it might have soundness or type-safety issues
|
|
|
|
The status of the modules is as follows:
|
|
|
|
================ ======================
|
|
module status
|
|
================ ======================
|
|
``assertions`` stable
|
|
``bitops`` complete
|
|
``callbacks`` complete
|
|
``cell`` stable
|
|
``c_str`` complete
|
|
``irq`` complete
|
|
``memory`` stable
|
|
``module`` complete
|
|
``offset_of`` stable
|
|
``qdev`` stable
|
|
``qom`` stable
|
|
``sysbus`` stable
|
|
``timer`` stable
|
|
``vmstate`` proof of concept
|
|
``zeroable`` stable
|
|
================ ======================
|
|
|
|
.. note::
|
|
API stability is not a promise, if anything because the C APIs are not a stable
|
|
interface either. Also, ``unsafe`` interfaces may be replaced by safe interfaces
|
|
later.
|
|
|
|
Naming convention
|
|
'''''''''''''''''
|
|
|
|
C function names usually are prefixed according to the data type that they
|
|
apply to, for example ``timer_mod`` or ``sysbus_connect_irq``. Furthermore,
|
|
both function and structs sometimes have a ``qemu_`` or ``QEMU`` prefix.
|
|
Generally speaking, these are all removed in the corresponding Rust functions:
|
|
``QEMUTimer`` becomes ``timer::Timer``, ``timer_mod`` becomes ``Timer::modify``,
|
|
``sysbus_connect_irq`` becomes ``SysBusDeviceMethods::connect_irq``.
|
|
|
|
Sometimes however a name appears multiple times in the QOM class hierarchy,
|
|
and the only difference is in the prefix. An example is ``qdev_realize`` and
|
|
``sysbus_realize``. In such cases, whenever a name is not unique in
|
|
the hierarchy, always add the prefix to the classes that are lower in
|
|
the hierarchy; for the top class, decide on a case by case basis.
|
|
|
|
For example:
|
|
|
|
========================== =========================================
|
|
``device_cold_reset()`` ``DeviceMethods::cold_reset()``
|
|
``pci_device_reset()`` ``PciDeviceMethods::pci_device_reset()``
|
|
``pci_bridge_reset()`` ``PciBridgeMethods::pci_bridge_reset()``
|
|
========================== =========================================
|
|
|
|
Here, the name is not exactly the same, but nevertheless ``PciDeviceMethods``
|
|
adds the prefix to avoid confusion, because the functionality of
|
|
``device_cold_reset()`` and ``pci_device_reset()`` is subtly different.
|
|
|
|
In this case, however, no prefix is needed:
|
|
|
|
========================== =========================================
|
|
``device_realize()`` ``DeviceMethods::realize()``
|
|
``sysbus_realize()`` ``SysbusDeviceMethods::sysbus_realize()``
|
|
``pci_realize()`` ``PciDeviceMethods::pci_realize()``
|
|
========================== =========================================
|
|
|
|
Here, the lower classes do not add any functionality, and mostly
|
|
provide extra compile-time checking; the basic *realize* functionality
|
|
is the same for all devices. Therefore, ``DeviceMethods`` does not
|
|
add the prefix.
|
|
|
|
Whenever a name is unique in the hierarchy, instead, you should
|
|
always remove the class name prefix.
|
|
|
|
Common pitfalls
|
|
'''''''''''''''
|
|
|
|
Rust has very strict rules with respect to how you get an exclusive (``&mut``)
|
|
reference; failure to respect those rules is a source of undefined behavior.
|
|
In particular, even if a value is loaded from a raw mutable pointer (``*mut``),
|
|
it *cannot* be casted to ``&mut`` unless the value was stored to the ``*mut``
|
|
from a mutable reference. Furthermore, it is undefined behavior if any
|
|
shared reference was created between the store to the ``*mut`` and the load::
|
|
|
|
let mut p: u32 = 42;
|
|
let p_mut = &mut p; // 1
|
|
let p_raw = p_mut as *mut u32; // 2
|
|
|
|
// p_raw keeps the mutable reference "alive"
|
|
|
|
let p_shared = &p; // 3
|
|
println!("access from &u32: {}", *p_shared);
|
|
|
|
// Bring back the mutable reference, its lifetime overlaps
|
|
// with that of a shared reference.
|
|
let p_mut = unsafe { &mut *p_raw }; // 4
|
|
println!("access from &mut 32: {}", *p_mut);
|
|
|
|
println!("access from &u32: {}", *p_shared); // 5
|
|
|
|
These rules can be tested with `MIRI`__, for example.
|
|
|
|
__ https://github.com/rust-lang/miri
|
|
|
|
Almost all Rust code in QEMU will involve QOM objects, and pointers to these
|
|
objects are *shared*, for example because they are part of the QOM composition
|
|
tree. This creates exactly the above scenario:
|
|
|
|
1. a QOM object is created
|
|
|
|
2. a ``*mut`` is created, for example as the opaque value for a ``MemoryRegion``
|
|
|
|
3. the QOM object is placed in the composition tree
|
|
|
|
4. a memory access dereferences the opaque value to a ``&mut``
|
|
|
|
5. but the shared reference is still present in the composition tree
|
|
|
|
Because of this, QOM objects should almost always use ``&self`` instead
|
|
of ``&mut self``; access to internal fields must use *interior mutability*
|
|
to go from a shared reference to a ``&mut``.
|
|
|
|
Whenever C code provides you with an opaque ``void *``, avoid converting it
|
|
to a Rust mutable reference, and use a shared reference instead. Rust code
|
|
will then have to use QEMU's ``BqlRefCell`` and ``BqlCell`` type, which
|
|
enforce that locking rules for the "Big QEMU Lock" are respected. These cell
|
|
types are also known to the ``vmstate`` crate, which is able to "look inside"
|
|
them when building an in-memory representation of a ``struct``s layout.
|
|
Note that the same is not true of a ``RefCell`` or ``Mutex``.
|
|
|
|
In the future, similar cell types might also be provided for ``AioContext``-based
|
|
locking as well.
|
|
|
|
Writing bindings to C code
|
|
''''''''''''''''''''''''''
|
|
|
|
Here are some things to keep in mind when working on the ``qemu_api`` crate.
|
|
|
|
**Look at existing code**
|
|
Very often, similar idioms in C code correspond to similar tricks in
|
|
Rust bindings. If the C code uses ``offsetof``, look at qdev properties
|
|
or ``vmstate``. If the C code has a complex const struct, look at
|
|
``MemoryRegion``. Reuse existing patterns for handling lifetimes;
|
|
for example use ``&T`` for QOM objects that do not need a reference
|
|
count (including those that can be embedded in other objects) and
|
|
``Owned<T>`` for those that need it.
|
|
|
|
**Use the type system**
|
|
Bindings often will need access information that is specific to a type
|
|
(either a builtin one or a user-defined one) in order to pass it to C
|
|
functions. Put them in a trait and access it through generic parameters.
|
|
The ``vmstate`` module has examples of how to retrieve type information
|
|
for the fields of a Rust ``struct``.
|
|
|
|
**Prefer unsafe traits to unsafe functions**
|
|
Unsafe traits are much easier to prove correct than unsafe functions.
|
|
They are an excellent place to store metadata that can later be accessed
|
|
by generic functions. C code usually places metadata in global variables;
|
|
in Rust, they can be stored in traits and then turned into ``static``
|
|
variables. Often, unsafe traits can be generated by procedural macros.
|
|
|
|
**Document limitations due to old Rust versions**
|
|
If you need to settle for an inferior solution because of the currently
|
|
supported set of Rust versions, document it in the source and in this
|
|
file. This ensures that it can be fixed when the minimum supported
|
|
version is bumped.
|
|
|
|
**Keep locking in mind**.
|
|
When marking a type ``Sync``, be careful of whether it needs the big
|
|
QEMU lock. Use ``BqlCell`` and ``BqlRefCell`` for interior data,
|
|
or assert ``bql_locked()``.
|
|
|
|
**Don't be afraid of complexity, but document and isolate it**
|
|
It's okay to be tricky; device code is written more often than bindings
|
|
code and it's important that it is idiomatic. However, you should strive
|
|
to isolate any tricks in a place (for example a ``struct``, a trait
|
|
or a macro) where it can be documented and tested. If needed, include
|
|
toy versions of the code in the documentation.
|
|
|
|
Writing procedural macros
|
|
'''''''''''''''''''''''''
|
|
|
|
By conventions, procedural macros are split in two functions, one
|
|
returning ``Result<proc_macro2::TokenStream, MacroError>` with the body of
|
|
the procedural macro, and the second returning ``proc_macro::TokenStream``
|
|
which is the actual procedural macro. The former's name is the same as
|
|
the latter with the ``_or_error`` suffix. The code for the latter is more
|
|
or less fixed; it follows the following template, which is fixed apart
|
|
from the type after ``as`` in the invocation of ``parse_macro_input!``::
|
|
|
|
#[proc_macro_derive(Object)]
|
|
pub fn derive_object(input: TokenStream) -> TokenStream {
|
|
let input = parse_macro_input!(input as DeriveInput);
|
|
let expanded = derive_object_or_error(input).unwrap_or_else(Into::into);
|
|
|
|
TokenStream::from(expanded)
|
|
}
|
|
|
|
The ``qemu_api_macros`` crate has utility functions to examine a
|
|
``DeriveInput`` and perform common checks (e.g. looking for a struct
|
|
with named fields). These functions return ``Result<..., MacroError>``
|
|
and can be used easily in the procedural macro function::
|
|
|
|
fn derive_object_or_error(input: DeriveInput) ->
|
|
Result<proc_macro2::TokenStream, MacroError>
|
|
{
|
|
is_c_repr(&input, "#[derive(Object)]")?;
|
|
|
|
let name = &input.ident;
|
|
let parent = &get_fields(&input, "#[derive(Object)]")?[0].ident;
|
|
...
|
|
}
|
|
|
|
Use procedural macros with care. They are mostly useful for two purposes:
|
|
|
|
* Performing consistency checks; for example ``#[derive(Object)]`` checks
|
|
that the structure has ``#[repr[C])`` and that the type of the first field
|
|
is consistent with the ``ObjectType`` declaration.
|
|
|
|
* Extracting information from Rust source code into traits, typically based
|
|
on types and attributes. For example, ``#[derive(TryInto)]`` builds an
|
|
implementation of ``TryFrom``, and it uses the ``#[repr(...)]`` attribute
|
|
as the ``TryFrom`` source and error types.
|
|
|
|
Procedural macros can be hard to debug and test; if the code generation
|
|
exceeds a few lines of code, it may be worthwhile to delegate work to
|
|
"regular" declarative (``macro_rules!``) macros and write unit tests for
|
|
those instead.
|
|
|
|
|
|
Coding style
|
|
''''''''''''
|
|
|
|
Code should pass clippy and be formatted with rustfmt.
|
|
|
|
Right now, only the nightly version of ``rustfmt`` is supported. This
|
|
might change in the future. While CI checks for correct formatting via
|
|
``cargo fmt --check``, maintainers can fix this for you when applying patches.
|
|
|
|
It is expected that ``qemu_api`` provides full ``rustdoc`` documentation for
|
|
bindings that are in their final shape or close.
|
|
|
|
Adding dependencies
|
|
-------------------
|
|
|
|
Generally, the set of dependent crates is kept small. Think twice before
|
|
adding a new external crate, especially if it comes with a large set of
|
|
dependencies itself. Sometimes QEMU only needs a small subset of the
|
|
functionality; see for example QEMU's ``assertions`` or ``c_str`` modules.
|
|
|
|
On top of this recommendation, adding external crates to QEMU is a
|
|
slightly complicated process, mostly due to the need to teach Meson how
|
|
to build them. While Meson has initial support for parsing ``Cargo.lock``
|
|
files, it is still highly experimental and is therefore not used.
|
|
|
|
Therefore, external crates must be added as subprojects for Meson to
|
|
learn how to build them, as well as to the relevant ``Cargo.toml`` files.
|
|
The versions specified in ``rust/Cargo.lock`` must be the same as the
|
|
subprojects; note that the ``rust/`` directory forms a Cargo `workspace`__,
|
|
and therefore there is a single lock file for the whole build.
|
|
|
|
__ https://doc.rust-lang.org/cargo/reference/workspaces.html#virtual-workspace
|
|
|
|
Choose a version of the crate that works with QEMU's minimum supported
|
|
Rust version (|msrv|).
|
|
|
|
Second, a new ``wrap`` file must be added to teach Meson how to download the
|
|
crate. The wrap file must be named ``NAME-SEMVER-rs.wrap``, where ``NAME``
|
|
is the name of the crate and ``SEMVER`` is the version up to and including the
|
|
first non-zero number. For example, a crate with version ``0.2.3`` will use
|
|
``0.2`` for its ``SEMVER``, while a crate with version ``1.0.84`` will use ``1``.
|
|
|
|
Third, the Meson rules to build the crate must be added at
|
|
``subprojects/NAME-SEMVER-rs/meson.build``. Generally this includes:
|
|
|
|
* ``subproject`` and ``dependency`` lines for all dependent crates
|
|
|
|
* a ``static_library`` or ``rust.proc_macro`` line to perform the actual build
|
|
|
|
* ``declare_dependency`` and a ``meson.override_dependency`` lines to expose
|
|
the result to QEMU and to other subprojects
|
|
|
|
Remember to add ``native: true`` to ``dependency``, ``static_library`` and
|
|
``meson.override_dependency`` for dependencies of procedural macros.
|
|
If a crate is needed in both procedural macros and QEMU binaries, everything
|
|
apart from ``subproject`` must be duplicated to build both native and
|
|
non-native versions of the crate.
|
|
|
|
It's important to specify the right compiler options. These include:
|
|
|
|
* the language edition (which can be found in the ``Cargo.toml`` file)
|
|
|
|
* the ``--cfg`` (which have to be "reverse engineered" from the ``build.rs``
|
|
file of the crate).
|
|
|
|
* usually, a ``--cap-lints allow`` argument to hide warnings from rustc
|
|
or clippy.
|
|
|
|
After every change to the ``meson.build`` file you have to update the patched
|
|
version with ``meson subprojects update --reset ``NAME-SEMVER-rs``. This might
|
|
be automated in the future.
|
|
|
|
Also, after every change to the ``meson.build`` file it is strongly suggested to
|
|
do a dummy change to the ``.wrap`` file (for example adding a comment like
|
|
``# version 2``), which will help Meson notice that the subproject is out of date.
|
|
|
|
As a last step, add the new subproject to ``scripts/archive-source.sh``,
|
|
``scripts/make-release`` and ``subprojects/.gitignore``.
|