Hi Manos,
On 8/23/2024 4:11 PM, Manos Pitsidianakis wrote:
Add rust/qemu-api, which exposes rust-bindgen generated FFI bindings and
provides some declaration macros for symbols visible to the rest of
QEMU.
Co-authored-by: Junjie Mao <junjie....@intel.com>
Co-authored-by: Paolo Bonzini <pbonz...@redhat.com>
Signed-off-by: Junjie Mao <junjie....@intel.com>
Signed-off-by: Paolo Bonzini <pbonz...@redhat.com>
Signed-off-by: Manos Pitsidianakis <manos.pitsidiana...@linaro.org>
---
MAINTAINERS | 6 ++
rust/meson.build | 1 +
rust/qemu-api/.gitignore | 2 +
rust/qemu-api/Cargo.lock | 7 +++
rust/qemu-api/Cargo.toml | 26 ++++++++
rust/qemu-api/README.md | 17 +++++
rust/qemu-api/build.rs | 14 +++++
rust/qemu-api/meson.build | 20 ++++++
rust/qemu-api/rustfmt.toml | 1 +
rust/qemu-api/src/definitions.rs | 109 ++++++++++++++++++++++++++++++++
rust/qemu-api/src/device_class.rs | 128 ++++++++++++++++++++++++++++++++++++++
rust/qemu-api/src/lib.rs | 102 ++++++++++++++++++++++++++++++
rust/qemu-api/src/tests.rs | 49 +++++++++++++++
rust/rustfmt.toml | 7 +++
14 files changed, 489 insertions(+)
[snip]
diff --git a/rust/qemu-api/README.md b/rust/qemu-api/README.md
new file mode 100644
index 0000000000..7588fa29ef
--- /dev/null
+++ b/rust/qemu-api/README.md
@@ -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
+```
+
I suggest mentioning here that cargo test requires --no-default-features.
+## Generate Rust documentation
+
+To generate docs for this crate, including private items:
+
+```sh
+cargo doc --no-deps --document-private-items
+```
[snip]
diff --git a/rust/qemu-api/rustfmt.toml b/rust/qemu-api/rustfmt.toml
new file mode 120000
index 0000000000..39f97b043b
--- /dev/null
+++ b/rust/qemu-api/rustfmt.toml
@@ -0,0 +1 @@
+../rustfmt.toml
\ No newline at end of file
This symbolic link is unnecessary. rustfmt will recursively search the parent
directories for rustfmt.toml [1].
[1] https://github.com/rust-lang/rustfmt?tab=readme-ov-file#configuring-rustfmt
diff --git a/rust/qemu-api/src/definitions.rs b/rust/qemu-api/src/definitions.rs
new file mode 100644
index 0000000000..4abd0253bd
--- /dev/null
+++ b/rust/qemu-api/src/definitions.rs
@@ -0,0 +1,109 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidiana...@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+//! Definitions required by QEMU when registering a device.
+
+/// Trait a type must implement to be registered with QEMU.
+pub trait ObjectImpl {
+ type Class;
+ const TYPE_INFO: crate::bindings::TypeInfo;
+ const TYPE_NAME: &'static ::core::ffi::CStr;
+ const PARENT_TYPE_NAME: Option<&'static ::core::ffi::CStr>;
+ const INSTANCE_INIT: ::core::option::Option<
+ unsafe extern "C" fn(obj: *mut crate::bindings::Object),
+ >;
+ const INSTANCE_POST_INIT: ::core::option::Option<
+ unsafe extern "C" fn(obj: *mut crate::bindings::Object),
+ >;
+ const INSTANCE_FINALIZE: ::core::option::Option<
+ unsafe extern "C" fn(obj: *mut crate::bindings::Object),
+ >;
+ const ABSTRACT: bool;
+}
+
+pub trait Class {
+ const CLASS_INIT: ::core::option::Option<
+ unsafe extern "C" fn(
+ klass: *mut crate::bindings::ObjectClass,
+ data: *mut core::ffi::c_void,
+ ),
+ >;
+ const CLASS_BASE_INIT: ::core::option::Option<
+ unsafe extern "C" fn(
+ klass: *mut crate::bindings::ObjectClass,
+ data: *mut core::ffi::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() = {
+ assert!($type <
$crate::bindings::module_init_type_MODULE_INIT_MAX);
+
+ extern "C" fn __load() {
+ unsafe {
+ $crate::bindings::register_module_init(Some($func), $type);
+ }
+ }
+
+ __load
+ };
+ };
+ (qom: $func:ident => $body:block) => {
This arm looks duplicating what #[derive(Object)] is for, while both have their
strengths and limitations: module_init!() provides more flexibility on the
registration function body, and #[derive(Object)] is much more convenient to use.
Complex registration functions are not rare, and thus the Rust APIs should
ideally have both strengths: clean type declaration in most cases, and full
flexibility when needed. In the current codebase, we have ~1080 uses of
type_init(), with 750 of them having a registration function as simple as a
single call to type_register_static() (disclaimer: those numbers are collected
via brute-force searches and may not be accurate). More complex cases include:
1. Registering multiple types (e.g., multiple models of same device) that share
the same data structure, e.g., hw/misc/aspeed_xdma.c and hw/xtensa/xtfpga.c.
There are ~200 uses of this kind in the codebase.
2. Use domain-specific registration function, e.g., ui/egl-headless.c,
audio/ossaudio.c and hw/virtio/virtio-net-pci.c.
3. Other device-specific operations, e.g., hw/net/spapr_llan.c.
My rough idea is to define a proc macro around an impl block to collect
constants (type names, parent names, etc.) as tokens and callbacks (class init,
instance init, etc.) as functions, from which we generate TypeInfo and
(optionally) type registration code. As an example:
pub struct PL011State {
...
}
#[qemu_type(name = "pl011", parent = TYPE_SYS_BUS_DEVICE, (abstract)*)]
impl PL011State {
#[class_init]
pub fn class_init(klass: *mut ObjectClass, data: *mut core::ffi::c_void) {
...
}
#[instance_init]
pub fn init(obj: *mut Object) { ... }
...
}
The proc macro then generates a TypeInfo instance named TYPE_INFO_pl011, with
optional callbacks being None when not given. A registration function will also
be generated unless qemu_type! has a no_register token. Devices can still use
module_init! to define their own registration function.
The class_init callback is specified together with instance_init because it is
common for multi-model devices to provide a different class_init even they share
the same class structure. Refer to hw/misc/aspeed_xdma.c for an example.
What do you think? It is still preliminary and the example can have grammatical
issues, but I can try drafting if you think that is a good direction.
---
Best Regards
Junjie Mao
+ // 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>(),
+ instance_align: ::core::mem::align_of::<$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>(),
+ 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(),
+ };
+ }
+}