diff mbox series

[v8,6/8] rust: add crate to expose bindings and interfaces

Message ID 20240823-rust-pl011-v8-6-b9f5746bdaf3@linaro.org (mailing list archive)
State New
Headers show
Series Add Rust build support, ARM PL011 device impl | expand

Commit Message

Manos Pitsidianakis Aug. 23, 2024, 8:11 a.m. UTC
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.mao@intel.com>
Co-authored-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Junjie Mao <junjie.mao@intel.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@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(+)

Comments

Junjie Mao Aug. 26, 2024, 5:03 a.m. UTC | #1
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.mao@intel.com>
> Co-authored-by: Paolo Bonzini <pbonzini@redhat.com>
> Signed-off-by: Junjie Mao <junjie.mao@intel.com>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@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.pitsidianakis@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(),
> +        };
> +    }
> +}
Junjie Mao Aug. 26, 2024, 5:31 a.m. UTC | #2
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.mao@intel.com>
> Co-authored-by: Paolo Bonzini <pbonzini@redhat.com>
> Signed-off-by: Junjie Mao <junjie.mao@intel.com>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@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/src/lib.rs b/rust/qemu-api/src/lib.rs
> new file mode 100644
> index 0000000000..ab95d0d5f7
> --- /dev/null
> +++ b/rust/qemu-api/src/lib.rs
> @@ -0,0 +1,102 @@
> +// 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,
> +    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};
> +
> +extern "C" {
> +    pub fn g_aligned_alloc0(
> +        n_blocks: bindings::gsize,
> +        n_block_bytes: bindings::gsize,
> +        alignment: bindings::gsize,
> +    ) -> bindings::gpointer;
> +    pub fn g_aligned_free(mem: bindings::gpointer);
> +    pub fn g_malloc0(n_bytes: bindings::gsize) -> bindings::gpointer;
> +    pub 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(feature = "allocator", global_allocator)]
> +pub static GLOBAL: QemuAllocator = QemuAllocator::new();
> +
> +impl QemuAllocator {
> +    pub const fn new() -> Self {
> +        Self { _unused: [] }
> +    }
> +}
> +
> +impl Default for QemuAllocator {
> +    fn default() -> Self {
> +        Self::new()
> +    }
> +}
> +
> +unsafe impl GlobalAlloc for QemuAllocator {
> +    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
> +        if layout.align() == 0 {
> +            g_malloc0(layout.size().try_into().unwrap()).cast::<u8>()
> +        } else {
> +            g_aligned_alloc0(

One more thing: g_aligned_alloc0() was introduced in glib 2.72 [1] but the 
current glib version check in meson is >= 2.66.0.

Glib 2.72 still supports Win 7+, so no increase to _WIN32_WINNT is needed for 
this version bumping.

[1] https://docs.gtk.org/glib/func.aligned_alloc0.html
[2] https://gitlab.gnome.org/GNOME/glib/-/blob/2.72.0/meson.build?ref_type=tags#L509

---
Best Regards
Junjie Mao

> +                layout.size().try_into().unwrap(),
> +                1,
> +                layout.align().try_into().unwrap(),
> +            )
> +            .cast::<u8>()
> +        }
> +    }
> +
> +    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
> +        if layout.align() == 0 {
> +            g_free(ptr.cast::<_>())
> +        } else {
> +            g_aligned_free(ptr.cast::<_>())
> +        }
> +    }
> +}
Manos Pitsidianakis Aug. 26, 2024, 6:12 a.m. UTC | #3
On Mon, 26 Aug 2024 08:03, Junjie Mao <junjie.mao@intel.com> wrote:
>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.mao@intel.com>
>> Co-authored-by: Paolo Bonzini <pbonzini@redhat.com>
>> Signed-off-by: Junjie Mao <junjie.mao@intel.com>
>> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
>> Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@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.


Right. I will make #[global_allocator] depend on both the `allocator` 
feature being on, and `test` off.

>
>> +## 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

Good to know, will remove it.

>> 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.pitsidianakis@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.
>

This is why I left the decl macro, I was prototyping this series with a 
second Rust device (that is not included in the patches) and I needed 
more logic in the module init.

>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:

Do you think we should not use a trait to define type info at all by the 
way?

>
>   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.

Maybe this too can be a trait method people can override with a blank 
implementation to avoid registration...

>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.

Thanks I will take a look. QEMU Classes are a bit complex indeed.

>
>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.

In my plan I wanted to eventually have all these callbacks available to 
Rust code via trait methods which only take rust references instead of 
pointers. Then the proc macros would generate extern "C" wrappers for 
each of them, make a typeinfo declaration, set everything up. I like 
your approach too. Should we wait until we have an actual device that 
requires redesigning this? We're free to change things anyway.

>
>---
>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(),
>> +        };
>> +    }
>> +}
Manos Pitsidianakis Aug. 26, 2024, 6:41 a.m. UTC | #4
On Mon, 26 Aug 2024 08:31, Junjie Mao <junjie.mao@intel.com> wrote:
>> +unsafe impl GlobalAlloc for QemuAllocator {
>> +    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
>> +        if layout.align() == 0 {
>> +            g_malloc0(layout.size().try_into().unwrap()).cast::<u8>()
>> +        } else {
>> +            g_aligned_alloc0(
>
>One more thing: g_aligned_alloc0() was introduced in glib 2.72 [1] but the 
>current glib version check in meson is >= 2.66.0.
>
>Glib 2.72 still supports Win 7+, so no increase to _WIN32_WINNT is needed for 
>this version bumping.
>
>[1] https://docs.gtk.org/glib/func.aligned_alloc0.html
>[2] https://gitlab.gnome.org/GNOME/glib/-/blob/2.72.0/meson.build?ref_type=tags#L509

Hm. Was there no way to have aligned allocations before 2.72? We can 
emit a cfg from meson if glib is <2.72 and handle it differently.

Manos
Junjie Mao Aug. 26, 2024, 7:45 a.m. UTC | #5
On 8/26/2024 2:41 PM, Manos Pitsidianakis wrote:
> On Mon, 26 Aug 2024 08:31, Junjie Mao <junjie.mao@intel.com> wrote:
>>> +unsafe impl GlobalAlloc for QemuAllocator {
>>> +    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
>>> +        if layout.align() == 0 {
>>> +            g_malloc0(layout.size().try_into().unwrap()).cast::<u8>()
>>> +        } else {
>>> +            g_aligned_alloc0(
>>
>> One more thing: g_aligned_alloc0() was introduced in glib 2.72 [1] but the 
>> current glib version check in meson is >= 2.66.0.
>>
>> Glib 2.72 still supports Win 7+, so no increase to _WIN32_WINNT is needed for 
>> this version bumping.
>>
>> [1] https://docs.gtk.org/glib/func.aligned_alloc0.html
>> [2] 
>> https://gitlab.gnome.org/GNOME/glib/-/blob/2.72.0/meson.build?ref_type=tags#L509
> 
> Hm. Was there no way to have aligned allocations before 2.72? We can emit a cfg 
> from meson if glib is <2.72 and handle it differently.

I attempted to look for alternatives in older glibs, but unfortunately didn't 
find any.

---
Best Regards
Junjie Mao

> 
> Manos
Thomas Huth Aug. 26, 2024, 8:24 a.m. UTC | #6
On 26/08/2024 08.41, Manos Pitsidianakis wrote:
> On Mon, 26 Aug 2024 08:31, Junjie Mao <junjie.mao@intel.com> wrote:
>>> +unsafe impl GlobalAlloc for QemuAllocator {
>>> +    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
>>> +        if layout.align() == 0 {
>>> +            g_malloc0(layout.size().try_into().unwrap()).cast::<u8>()
>>> +        } else {
>>> +            g_aligned_alloc0(
>>
>> One more thing: g_aligned_alloc0() was introduced in glib 2.72 [1] but the 
>> current glib version check in meson is >= 2.66.0.
>>
>> Glib 2.72 still supports Win 7+, so no increase to _WIN32_WINNT is needed 
>> for this version bumping.
>>
>> [1] https://docs.gtk.org/glib/func.aligned_alloc0.html
>> [2] 
>> https://gitlab.gnome.org/GNOME/glib/-/blob/2.72.0/meson.build?ref_type=tags#L509
> 
> Hm. Was there no way to have aligned allocations before 2.72? We can emit a 
> cfg from meson if glib is <2.72 and handle it differently.

Can't you simply use our qemu_memalign() function instead?

  Thomas
Junjie Mao Aug. 26, 2024, 8:41 a.m. UTC | #7
On 8/26/2024 2:12 PM, Manos Pitsidianakis wrote:
> On Mon, 26 Aug 2024 08:03, Junjie Mao <junjie.mao@intel.com> wrote:
>> 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.mao@intel.com>
>>> Co-authored-by: Paolo Bonzini <pbonzini@redhat.com>
>>> Signed-off-by: Junjie Mao <junjie.mao@intel.com>
>>> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
>>> Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@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/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.pitsidianakis@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.
>>
> 
> This is why I left the decl macro, I was prototyping this series with a second 
> Rust device (that is not included in the patches) and I needed more logic in the 
> module init.
> 
>> 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:
> 
> Do you think we should not use a trait to define type info at all by the way?
> 

I'm not sure, to be honest. Traits are a great way to specify a series of 
functions and mostly fit our needs here. I'm still thinking about how a 
trait-based approach works for multi-model devices where multiple TypeInfo can 
be defined for one structure. A naive way is to define one struct for each 
TypeInfo, which should work but does not look perfect to me.

>>
>>   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.
> 
> Maybe this too can be a trait method people can override with a blank 
> implementation to avoid registration...
> 

Agree.

>> 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.
> 
> Thanks I will take a look. QEMU Classes are a bit complex indeed.
> 
>>
>> 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.
> 
> In my plan I wanted to eventually have all these callbacks available to Rust 
> code via trait methods which only take rust references instead of pointers. Then 
> the proc macros would generate extern "C" wrappers for each of them, make a 
> typeinfo declaration, set everything up. I like your approach too. Should we 
> wait until we have an actual device that requires redesigning this? We're free 
> to change things anyway.
> 

Your idea looks promising, too, esp. the "take rust references" part. It may be 
difficult to figure out if a callback should be None since the trait will always 
define a default, empty implementation, but the performance overhead of calling 
empty constructors / destructors should be negligible.

I agree that we'd better try a variety of device types to better understand 
different use cases before we conclude on the API design. I'll also try 
prototyping some device for a deeper understanding of the current APIs.

---
Best Regards
Junjie Mao

>>
>> ---
>> 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(),
>>> +        };
>>> +    }
>>> +}
Manos Pitsidianakis Aug. 26, 2024, 11:29 a.m. UTC | #8
On Mon, 26 Aug 2024 11:24, Thomas Huth <thuth@redhat.com> wrote:
>On 26/08/2024 08.41, Manos Pitsidianakis wrote:
>> On Mon, 26 Aug 2024 08:31, Junjie Mao <junjie.mao@intel.com> wrote:
>>>> +unsafe impl GlobalAlloc for QemuAllocator {
>>>> +    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
>>>> +        if layout.align() == 0 {
>>>> +            g_malloc0(layout.size().try_into().unwrap()).cast::<u8>()
>>>> +        } else {
>>>> +            g_aligned_alloc0(
>>>
>>> One more thing: g_aligned_alloc0() was introduced in glib 2.72 [1] but the 
>>> current glib version check in meson is >= 2.66.0.
>>>
>>> Glib 2.72 still supports Win 7+, so no increase to _WIN32_WINNT is needed 
>>> for this version bumping.
>>>
>>> [1] https://docs.gtk.org/glib/func.aligned_alloc0.html
>>> [2] 
>>> https://gitlab.gnome.org/GNOME/glib/-/blob/2.72.0/meson.build?ref_type=tags#L509
>> 
>> Hm. Was there no way to have aligned allocations before 2.72? We can emit a 
>> cfg from meson if glib is <2.72 and handle it differently.
>
>Can't you simply use our qemu_memalign() function instead?

Thanks, that'd solve the problem.
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 642c07a9ff..d35e9f6b20 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3348,6 +3348,12 @@  F: hw/core/register.c
 F: include/hw/register.h
 F: include/hw/registerfields.h
 
+Rust
+M: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+S: Maintained
+F: rust/qemu-api
+F: rust/rustfmt.toml
+
 SLIRP
 M: Samuel Thibault <samuel.thibault@ens-lyon.org>
 S: Maintained
diff --git a/rust/meson.build b/rust/meson.build
index e69de29bb2..4a58d106b1 100644
--- a/rust/meson.build
+++ b/rust/meson.build
@@ -0,0 +1 @@ 
+subdir('qemu-api')
diff --git a/rust/qemu-api/.gitignore b/rust/qemu-api/.gitignore
new file mode 100644
index 0000000000..b9e7e004c8
--- /dev/null
+++ b/rust/qemu-api/.gitignore
@@ -0,0 +1,2 @@ 
+# Ignore generated bindings file overrides.
+src/bindings.rs
diff --git a/rust/qemu-api/Cargo.lock b/rust/qemu-api/Cargo.lock
new file mode 100644
index 0000000000..e9c51a243a
--- /dev/null
+++ b/rust/qemu-api/Cargo.lock
@@ -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"
diff --git a/rust/qemu-api/Cargo.toml b/rust/qemu-api/Cargo.toml
new file mode 100644
index 0000000000..df6e8a78f4
--- /dev/null
+++ b/rust/qemu-api/Cargo.toml
@@ -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"]
+allocator = []
+
+# Do not include in any global workspace
+[workspace]
+
+[lints.rust]
+unexpected_cfgs = { level = "warn", check-cfg = ['cfg(MESON)'] }
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
+```
+
+## Generate Rust documentation
+
+To generate docs for this crate, including private items:
+
+```sh
+cargo doc --no-deps --document-private-items
+```
diff --git a/rust/qemu-api/build.rs b/rust/qemu-api/build.rs
new file mode 100644
index 0000000000..419b154c2d
--- /dev/null
+++ b/rust/qemu-api/build.rs
@@ -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."
+        );
+    }
+}
diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build
new file mode 100644
index 0000000000..85838d31b4
--- /dev/null
+++ b/rust/qemu-api/meson.build
@@ -0,0 +1,20 @@ 
+_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: [
+    '--cfg', 'MESON',
+  ],
+)
+
+qemu_api = declare_dependency(
+  link_with: _qemu_api_rs,
+)
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
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.pitsidianakis@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) => {
+        // 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(),
+        };
+    }
+}
diff --git a/rust/qemu-api/src/device_class.rs b/rust/qemu-api/src/device_class.rs
new file mode 100644
index 0000000000..69ee912c33
--- /dev/null
+++ b/rust/qemu-api/src/device_class.rs
@@ -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, reset_fn => $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().reset = $reset_fn;
+            dc.as_mut().vmsd = &$vmsd;
+            $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() }
+        };
+    }
+}
diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs
new file mode 100644
index 0000000000..ab95d0d5f7
--- /dev/null
+++ b/rust/qemu-api/src/lib.rs
@@ -0,0 +1,102 @@ 
+// 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,
+    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};
+
+extern "C" {
+    pub fn g_aligned_alloc0(
+        n_blocks: bindings::gsize,
+        n_block_bytes: bindings::gsize,
+        alignment: bindings::gsize,
+    ) -> bindings::gpointer;
+    pub fn g_aligned_free(mem: bindings::gpointer);
+    pub fn g_malloc0(n_bytes: bindings::gsize) -> bindings::gpointer;
+    pub 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(feature = "allocator", global_allocator)]
+pub static GLOBAL: QemuAllocator = QemuAllocator::new();
+
+impl QemuAllocator {
+    pub const fn new() -> Self {
+        Self { _unused: [] }
+    }
+}
+
+impl Default for QemuAllocator {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+unsafe impl GlobalAlloc for QemuAllocator {
+    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
+        if layout.align() == 0 {
+            g_malloc0(layout.size().try_into().unwrap()).cast::<u8>()
+        } else {
+            g_aligned_alloc0(
+                layout.size().try_into().unwrap(),
+                1,
+                layout.align().try_into().unwrap(),
+            )
+            .cast::<u8>()
+        }
+    }
+
+    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
+        if layout.align() == 0 {
+            g_free(ptr.cast::<_>())
+        } else {
+            g_aligned_free(ptr.cast::<_>())
+        }
+    }
+}
diff --git a/rust/qemu-api/src/tests.rs b/rust/qemu-api/src/tests.rs
new file mode 100644
index 0000000000..df54edbd4e
--- /dev/null
+++ b/rust/qemu-api/src/tests.rs
@@ -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,
+    }
+}
diff --git a/rust/rustfmt.toml b/rust/rustfmt.toml
new file mode 100644
index 0000000000..ebecb99fe0
--- /dev/null
+++ b/rust/rustfmt.toml
@@ -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