aboutsummaryrefslogtreecommitdiff
path: root/rust/qemu-api
diff options
context:
space:
mode:
Diffstat (limited to 'rust/qemu-api')
-rw-r--r--rust/qemu-api/Cargo.lock7
-rw-r--r--rust/qemu-api/Cargo.toml10
-rw-r--r--rust/qemu-api/build.rs9
-rw-r--r--rust/qemu-api/meson.build44
-rw-r--r--rust/qemu-api/src/c_str.rs53
-rw-r--r--rust/qemu-api/src/definitions.rs68
-rw-r--r--rust/qemu-api/src/device_class.rs114
-rw-r--r--rust/qemu-api/src/lib.rs23
-rw-r--r--rust/qemu-api/src/offset_of.rs161
-rw-r--r--rust/qemu-api/src/tests.rs49
-rw-r--r--rust/qemu-api/src/vmstate.rs360
-rw-r--r--rust/qemu-api/src/zeroable.rs86
-rw-r--r--rust/qemu-api/tests/tests.rs79
13 files changed, 868 insertions, 195 deletions
diff --git a/rust/qemu-api/Cargo.lock b/rust/qemu-api/Cargo.lock
deleted file mode 100644
index e9c51a243a..0000000000
--- a/rust/qemu-api/Cargo.lock
+++ /dev/null
@@ -1,7 +0,0 @@
-# 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
index 3677def3fe..cc716d75d4 100644
--- a/rust/qemu-api/Cargo.toml
+++ b/rust/qemu-api/Cargo.toml
@@ -14,13 +14,15 @@ keywords = []
categories = []
[dependencies]
+qemu_api_macros = { path = "../qemu-api-macros" }
+
+[build-dependencies]
+version_check = "~0.9"
[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)'] }
+unexpected_cfgs = { level = "warn", check-cfg = ['cfg(MESON)', 'cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)',
+ 'cfg(has_offset_of)'] }
diff --git a/rust/qemu-api/build.rs b/rust/qemu-api/build.rs
index 419b154c2d..20f8f718b9 100644
--- a/rust/qemu-api/build.rs
+++ b/rust/qemu-api/build.rs
@@ -4,6 +4,8 @@
use std::path::Path;
+use version_check as rustc;
+
fn main() {
if !Path::new("src/bindings.rs").exists() {
panic!(
@@ -11,4 +13,11 @@ fn main() {
(`ninja bindings.rs`) and copy them to src/bindings.rs, or build through meson."
);
}
+
+ // Check for available rustc features
+ if rustc::is_min_version("1.77.0").unwrap_or(false) {
+ println!("cargo:rustc-cfg=has_offset_of");
+ }
+
+ println!("cargo:rerun-if-changed=build.rs");
}
diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build
index c72d34b607..6f637af7b1 100644
--- a/rust/qemu-api/meson.build
+++ b/rust/qemu-api/meson.build
@@ -1,24 +1,54 @@
+_qemu_api_cfg = ['--cfg', 'MESON']
+# _qemu_api_cfg += ['--cfg', 'feature="allocator"']
+if rustc.version().version_compare('>=1.77.0')
+ _qemu_api_cfg += ['--cfg', 'has_offset_of']
+endif
+
_qemu_api_rs = static_library(
'qemu_api',
structured_sources(
[
'src/lib.rs',
+ 'src/c_str.rs',
'src/definitions.rs',
'src/device_class.rs',
+ 'src/offset_of.rs',
+ 'src/vmstate.rs',
+ 'src/zeroable.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,
- ],
+ rust_args: _qemu_api_cfg,
)
+rust.test('rust-qemu-api-tests', _qemu_api_rs,
+ suite: ['unit', 'rust'])
+
qemu_api = declare_dependency(
link_with: _qemu_api_rs,
+ dependencies: qemu_api_macros,
)
+
+# Rust executables do not support objects, so add an intermediate step.
+rust_qemu_api_objs = static_library(
+ 'rust_qemu_api_objs',
+ objects: [libqom.extract_all_objects(recursive: false),
+ libhwcore.extract_all_objects(recursive: false)])
+
+test('rust-qemu-api-integration',
+ executable(
+ 'rust-qemu-api-integration',
+ 'tests/tests.rs',
+ override_options: ['rust_std=2021', 'build.rust_std=2021'],
+ rust_args: ['--test'],
+ install: false,
+ dependencies: [qemu_api, qemu_api_macros],
+ link_whole: [rust_qemu_api_objs, libqemuutil]),
+ args: [
+ '--test',
+ '--format', 'pretty',
+ ],
+ protocol: 'rust',
+ suite: ['unit', 'rust'])
diff --git a/rust/qemu-api/src/c_str.rs b/rust/qemu-api/src/c_str.rs
new file mode 100644
index 0000000000..4cd96da0b4
--- /dev/null
+++ b/rust/qemu-api/src/c_str.rs
@@ -0,0 +1,53 @@
+// Copyright 2024 Red Hat, Inc.
+// Author(s): Paolo Bonzini <pbonzini@redhat.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#[macro_export]
+/// Given a string constant _without_ embedded or trailing NULs, return
+/// a `CStr`.
+///
+/// Needed for compatibility with Rust <1.77.
+macro_rules! c_str {
+ ($str:expr) => {{
+ const STRING: &str = concat!($str, "\0");
+ const BYTES: &[u8] = STRING.as_bytes();
+
+ // "for" is not allowed in const context... oh well,
+ // everybody loves some lisp. This could be turned into
+ // a procedural macro if this is a problem; alternatively
+ // Rust 1.72 makes CStr::from_bytes_with_nul a const function.
+ const fn f(b: &[u8], i: usize) {
+ if i == b.len() - 1 {
+ } else if b[i] == 0 {
+ panic!("c_str argument contains NUL")
+ } else {
+ f(b, i + 1)
+ }
+ }
+ f(BYTES, 0);
+
+ // SAFETY: absence of NULs apart from the final byte was checked above
+ unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(BYTES) }
+ }};
+}
+
+#[cfg(test)]
+mod tests {
+ use std::ffi::CStr;
+
+ use crate::c_str;
+
+ #[test]
+ fn test_cstr_macro() {
+ let good = c_str!("🦀");
+ let good_bytes = b"\xf0\x9f\xa6\x80\0";
+ assert_eq!(good.to_bytes_with_nul(), good_bytes);
+ }
+
+ #[test]
+ fn test_cstr_macro_const() {
+ const GOOD: &CStr = c_str!("🦀");
+ const GOOD_BYTES: &[u8] = b"\xf0\x9f\xa6\x80\0";
+ assert_eq!(GOOD.to_bytes_with_nul(), GOOD_BYTES);
+ }
+}
diff --git a/rust/qemu-api/src/definitions.rs b/rust/qemu-api/src/definitions.rs
index 60bd3f8aaa..26597934bb 100644
--- a/rust/qemu-api/src/definitions.rs
+++ b/rust/qemu-api/src/definitions.rs
@@ -4,7 +4,7 @@
//! Definitions required by QEMU when registering a device.
-use ::core::ffi::{c_void, CStr};
+use std::{ffi::CStr, os::raw::c_void};
use crate::bindings::{Object, ObjectClass, TypeInfo};
@@ -29,46 +29,40 @@ pub trait Class {
#[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() {
+ ($type:ident => $body:block) => {
+ const _: () = {
+ #[used]
+ #[cfg_attr(
+ not(any(target_vendor = "apple", target_os = "windows")),
+ link_section = ".init_array"
+ )]
+ #[cfg_attr(target_vendor = "apple", 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 init_fn() {
$body
}
- unsafe {
- $crate::bindings::register_module_init(
- Some($func),
- $crate::bindings::module_init_type::MODULE_INIT_QOM,
- );
+ extern "C" fn ctor_fn() {
+ unsafe {
+ $crate::bindings::register_module_init(
+ Some(init_fn),
+ $crate::bindings::module_init_type::$type,
+ );
+ }
}
- }
- __load
+ ctor_fn
+ };
};
};
+
+ // shortcut because it's quite common that $body needs unsafe {}
+ ($type:ident => unsafe $body:block) => {
+ $crate::module_init! {
+ $type => { unsafe { $body } }
+ }
+ };
}
#[macro_export]
@@ -81,13 +75,13 @@ macro_rules! type_info {
} 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_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>() as $crate::bindings::size_t,
+ 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(),
diff --git a/rust/qemu-api/src/device_class.rs b/rust/qemu-api/src/device_class.rs
index 1ea95beb78..0ba798d3e3 100644
--- a/rust/qemu-api/src/device_class.rs
+++ b/rust/qemu-api/src/device_class.rs
@@ -2,127 +2,73 @@
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later
-use std::sync::OnceLock;
+use std::ffi::CStr;
-use crate::bindings::Property;
+use crate::bindings;
#[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,
+ _: *mut ::std::os::raw::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());
+ unsafe {
+ 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_ptr());
+ }
}
};
}
#[macro_export]
macro_rules! define_property {
- ($name:expr, $state:ty, $field:expr, $prop:expr, $type:expr, default = $defval:expr$(,)*) => {
+ ($name:expr, $state:ty, $field:ident, $prop:expr, $type:expr, default = $defval:expr$(,)*) => {
$crate::bindings::Property {
- name: {
- #[used]
- static _TEMP: &::core::ffi::CStr = $name;
- _TEMP.as_ptr()
- },
+ // use associated function syntax for type checking
+ name: ::std::ffi::CStr::as_ptr($name),
info: $prop,
- offset: ::core::mem::offset_of!($state, $field)
- .try_into()
- .expect("Could not fit offset value to type"),
- bitnr: 0,
- bitmask: 0,
+ offset: $crate::offset_of!($state, $field) as isize,
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(),
+ defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval as u64 },
+ ..$crate::zeroable::Zeroable::ZERO
}
};
- ($name:expr, $state:ty, $field:expr, $prop:expr, $type:expr$(,)*) => {
+ ($name:expr, $state:ty, $field:ident, $prop:expr, $type:expr$(,)*) => {
$crate::bindings::Property {
- name: {
- #[used]
- static _TEMP: &::core::ffi::CStr = $name;
- _TEMP.as_ptr()
- },
+ // use associated function syntax for type checking
+ name: ::std::ffi::CStr::as_ptr($name),
info: $prop,
- offset: ::core::mem::offset_of!($state, $field)
- .try_into()
- .expect("Could not fit offset value to type"),
- bitnr: 0,
- bitmask: 0,
+ offset: $crate::offset_of!($state, $field) as isize,
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(),
+ ..$crate::zeroable::Zeroable::ZERO
}
};
}
-#[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 {
+ pub static $ident: [$crate::bindings::Property; {
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);
+ }] = [
+ $($prop),*,
+ $crate::zeroable::Zeroable::ZERO,
+ ];
};
}
-#[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() }
- };
- }
-}
+// workaround until we can use --generate-cstr in bindgen.
+pub const TYPE_DEVICE: &CStr =
+ unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_DEVICE) };
+pub const TYPE_SYS_BUS_DEVICE: &CStr =
+ unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_SYS_BUS_DEVICE) };
diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs
index e72fb4b4bb..aa8d16ec94 100644
--- a/rust/qemu-api/src/lib.rs
+++ b/rust/qemu-api/src/lib.rs
@@ -26,14 +26,20 @@ unsafe impl Send for bindings::Property {}
unsafe impl Sync for bindings::Property {}
unsafe impl Sync for bindings::TypeInfo {}
unsafe impl Sync for bindings::VMStateDescription {}
+unsafe impl Sync for bindings::VMStateField {}
+unsafe impl Sync for bindings::VMStateInfo {}
+pub mod c_str;
pub mod definitions;
pub mod device_class;
+pub mod offset_of;
+pub mod vmstate;
+pub mod zeroable;
-#[cfg(test)]
-mod tests;
-
-use std::alloc::{GlobalAlloc, Layout};
+use std::{
+ alloc::{GlobalAlloc, Layout},
+ os::raw::c_void,
+};
#[cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)]
extern "C" {
@@ -47,8 +53,8 @@ extern "C" {
#[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);
+ fn qemu_memalign(alignment: usize, size: usize) -> *mut c_void;
+ fn qemu_vfree(ptr: *mut c_void);
}
extern "C" {
@@ -113,7 +119,7 @@ impl Default for QemuAllocator {
}
// Sanity check.
-const _: [(); 8] = [(); ::core::mem::size_of::<*mut ::core::ffi::c_void>()];
+const _: [(); 8] = [(); ::core::mem::size_of::<*mut c_void>()];
unsafe impl GlobalAlloc for QemuAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
@@ -164,3 +170,6 @@ unsafe impl GlobalAlloc for QemuAllocator {
}
}
}
+
+#[cfg(has_offset_of)]
+pub use core::mem::offset_of;
diff --git a/rust/qemu-api/src/offset_of.rs b/rust/qemu-api/src/offset_of.rs
new file mode 100644
index 0000000000..075e98f986
--- /dev/null
+++ b/rust/qemu-api/src/offset_of.rs
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: MIT
+
+/// This macro provides the same functionality as `core::mem::offset_of`,
+/// except that only one level of field access is supported. The declaration
+/// of the struct must be wrapped with `with_offsets! { }`.
+///
+/// It is needed because `offset_of!` was only stabilized in Rust 1.77.
+#[cfg(not(has_offset_of))]
+#[macro_export]
+macro_rules! offset_of {
+ ($Container:ty, $field:ident) => {
+ <$Container>::OFFSET_TO__.$field
+ };
+}
+
+/// A wrapper for struct declarations, that allows using `offset_of!` in
+/// versions of Rust prior to 1.77
+#[macro_export]
+macro_rules! with_offsets {
+ // This method to generate field offset constants comes from:
+ //
+ // https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=10a22a9b8393abd7b541d8fc844bc0df
+ //
+ // used under MIT license with permission of Yandros aka Daniel Henry-Mantilla
+ (
+ $(#[$struct_meta:meta])*
+ $struct_vis:vis
+ struct $StructName:ident {
+ $(
+ $(#[$field_meta:meta])*
+ $field_vis:vis
+ $field_name:ident : $field_ty:ty
+ ),*
+ $(,)?
+ }
+ ) => (
+ #[cfg(not(has_offset_of))]
+ const _: () = {
+ struct StructOffsetsHelper<T>(std::marker::PhantomData<T>);
+ const END_OF_PREV_FIELD: usize = 0;
+
+ // populate StructOffsetsHelper<T> with associated consts,
+ // one for each field
+ $crate::with_offsets! {
+ @struct $StructName
+ @names [ $($field_name)* ]
+ @tys [ $($field_ty ,)*]
+ }
+
+ // now turn StructOffsetsHelper<T>'s consts into a single struct,
+ // applying field visibility. This provides better error messages
+ // than if offset_of! used StructOffsetsHelper::<T> directly.
+ pub
+ struct StructOffsets {
+ $(
+ $field_vis
+ $field_name: usize,
+ )*
+ }
+ impl $StructName {
+ pub
+ const OFFSET_TO__: StructOffsets = StructOffsets {
+ $(
+ $field_name: StructOffsetsHelper::<$StructName>::$field_name,
+ )*
+ };
+ }
+ };
+ );
+
+ (
+ @struct $StructName:ident
+ @names []
+ @tys []
+ ) => ();
+
+ (
+ @struct $StructName:ident
+ @names [$field_name:ident $($other_names:tt)*]
+ @tys [$field_ty:ty , $($other_tys:tt)*]
+ ) => (
+ #[allow(non_local_definitions)]
+ #[allow(clippy::modulo_one)]
+ impl StructOffsetsHelper<$StructName> {
+ #[allow(nonstandard_style)]
+ const $field_name: usize = {
+ const ALIGN: usize = std::mem::align_of::<$field_ty>();
+ const TRAIL: usize = END_OF_PREV_FIELD % ALIGN;
+ END_OF_PREV_FIELD + (if TRAIL == 0 { 0usize } else { ALIGN - TRAIL })
+ };
+ }
+ const _: () = {
+ const END_OF_PREV_FIELD: usize =
+ StructOffsetsHelper::<$StructName>::$field_name +
+ std::mem::size_of::<$field_ty>()
+ ;
+ $crate::with_offsets! {
+ @struct $StructName
+ @names [$($other_names)*]
+ @tys [$($other_tys)*]
+ }
+ };
+ );
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::offset_of;
+
+ #[repr(C)]
+ struct Foo {
+ a: u16,
+ b: u32,
+ c: u64,
+ d: u16,
+ }
+
+ #[repr(C)]
+ struct Bar {
+ pub a: u16,
+ pub b: u64,
+ c: Foo,
+ d: u64,
+ }
+
+ crate::with_offsets! {
+ #[repr(C)]
+ struct Bar {
+ pub a: u16,
+ pub b: u64,
+ c: Foo,
+ d: u64,
+ }
+ }
+
+ #[repr(C)]
+ pub struct Baz {
+ b: u32,
+ a: u8,
+ }
+ crate::with_offsets! {
+ #[repr(C)]
+ pub struct Baz {
+ b: u32,
+ a: u8,
+ }
+ }
+
+ #[test]
+ fn test_offset_of() {
+ const OFFSET_TO_C: usize = offset_of!(Bar, c);
+
+ assert_eq!(offset_of!(Bar, a), 0);
+ assert_eq!(offset_of!(Bar, b), 8);
+ assert_eq!(OFFSET_TO_C, 16);
+ assert_eq!(offset_of!(Bar, d), 40);
+
+ assert_eq!(offset_of!(Baz, b), 0);
+ assert_eq!(offset_of!(Baz, a), 4);
+ }
+}
diff --git a/rust/qemu-api/src/tests.rs b/rust/qemu-api/src/tests.rs
deleted file mode 100644
index df54edbd4e..0000000000
--- a/rust/qemu-api/src/tests.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-// 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/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs
new file mode 100644
index 0000000000..bedcf1e8f3
--- /dev/null
+++ b/rust/qemu-api/src/vmstate.rs
@@ -0,0 +1,360 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+//! Helper macros to declare migration state for device models.
+//!
+//! Some macros are direct equivalents to the C macros declared in
+//! `include/migration/vmstate.h` while
+//! [`vmstate_subsections`](crate::vmstate_subsections) and
+//! [`vmstate_fields`](crate::vmstate_fields) are meant to be used when
+//! declaring a device model state struct.
+
+#[doc(alias = "VMSTATE_UNUSED_BUFFER")]
+#[macro_export]
+macro_rules! vmstate_unused_buffer {
+ ($field_exists_fn:expr, $version_id:expr, $size:expr) => {{
+ $crate::bindings::VMStateField {
+ name: c_str!("unused").as_ptr(),
+ err_hint: ::core::ptr::null(),
+ offset: 0,
+ size: $size,
+ start: 0,
+ num: 0,
+ num_offset: 0,
+ size_offset: 0,
+ info: unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_info_unused_buffer) },
+ flags: VMStateFlags::VMS_BUFFER,
+ vmsd: ::core::ptr::null(),
+ version_id: $version_id,
+ struct_version_id: 0,
+ field_exists: $field_exists_fn,
+ }
+ }};
+}
+
+#[doc(alias = "VMSTATE_UNUSED_V")]
+#[macro_export]
+macro_rules! vmstate_unused_v {
+ ($version_id:expr, $size:expr) => {{
+ $crate::vmstate_unused_buffer!(None, $version_id, $size)
+ }};
+}
+
+#[doc(alias = "VMSTATE_UNUSED")]
+#[macro_export]
+macro_rules! vmstate_unused {
+ ($size:expr) => {{
+ $crate::vmstate_unused_v!(0, $size)
+ }};
+}
+
+#[doc(alias = "VMSTATE_SINGLE_TEST")]
+#[macro_export]
+macro_rules! vmstate_single_test {
+ ($field_name:ident, $struct_name:ty, $field_exists_fn:expr, $version_id:expr, $info:expr, $size:expr) => {{
+ $crate::bindings::VMStateField {
+ name: ::core::concat!(::core::stringify!($field_name), 0)
+ .as_bytes()
+ .as_ptr() as *const ::std::os::raw::c_char,
+ err_hint: ::core::ptr::null(),
+ offset: $crate::offset_of!($struct_name, $field_name),
+ size: $size,
+ start: 0,
+ num: 0,
+ num_offset: 0,
+ size_offset: 0,
+ info: unsafe { $info },
+ flags: VMStateFlags::VMS_SINGLE,
+ vmsd: ::core::ptr::null(),
+ version_id: $version_id,
+ struct_version_id: 0,
+ field_exists: $field_exists_fn,
+ }
+ }};
+}
+
+#[doc(alias = "VMSTATE_SINGLE")]
+#[macro_export]
+macro_rules! vmstate_single {
+ ($field_name:ident, $struct_name:ty, $version_id:expr, $info:expr, $size:expr) => {{
+ $crate::vmstate_single_test!($field_name, $struct_name, None, $version_id, $info, $size)
+ }};
+}
+
+#[doc(alias = "VMSTATE_UINT32_V")]
+#[macro_export]
+macro_rules! vmstate_uint32_v {
+ ($field_name:ident, $struct_name:ty, $version_id:expr) => {{
+ $crate::vmstate_single!(
+ $field_name,
+ $struct_name,
+ $version_id,
+ ::core::ptr::addr_of!($crate::bindings::vmstate_info_uint32),
+ ::core::mem::size_of::<u32>()
+ )
+ }};
+}
+
+#[doc(alias = "VMSTATE_UINT32")]
+#[macro_export]
+macro_rules! vmstate_uint32 {
+ ($field_name:ident, $struct_name:ty) => {{
+ $crate::vmstate_uint32_v!($field_name, $struct_name, 0)
+ }};
+}
+
+#[doc(alias = "VMSTATE_INT32_V")]
+#[macro_export]
+macro_rules! vmstate_int32_v {
+ ($field_name:ident, $struct_name:ty, $version_id:expr) => {{
+ $crate::vmstate_single!(
+ $field_name,
+ $struct_name,
+ $version_id,
+ ::core::ptr::addr_of!($crate::bindings::vmstate_info_int32),
+ ::core::mem::size_of::<i32>()
+ )
+ }};
+}
+
+#[doc(alias = "VMSTATE_INT32")]
+#[macro_export]
+macro_rules! vmstate_int32 {
+ ($field_name:ident, $struct_name:ty) => {{
+ $crate::vmstate_int32_v!($field_name, $struct_name, 0)
+ }};
+}
+
+#[doc(alias = "VMSTATE_ARRAY")]
+#[macro_export]
+macro_rules! vmstate_array {
+ ($field_name:ident, $struct_name:ty, $length:expr, $version_id:expr, $info:expr, $size:expr) => {{
+ $crate::bindings::VMStateField {
+ name: ::core::concat!(::core::stringify!($field_name), 0)
+ .as_bytes()
+ .as_ptr() as *const ::std::os::raw::c_char,
+ err_hint: ::core::ptr::null(),
+ offset: $crate::offset_of!($struct_name, $field_name),
+ size: $size,
+ start: 0,
+ num: $length as _,
+ num_offset: 0,
+ size_offset: 0,
+ info: unsafe { $info },
+ flags: VMStateFlags::VMS_ARRAY,
+ vmsd: ::core::ptr::null(),
+ version_id: $version_id,
+ struct_version_id: 0,
+ field_exists: None,
+ }
+ }};
+}
+
+#[doc(alias = "VMSTATE_UINT32_ARRAY_V")]
+#[macro_export]
+macro_rules! vmstate_uint32_array_v {
+ ($field_name:ident, $struct_name:ty, $length:expr, $version_id:expr) => {{
+ $crate::vmstate_array!(
+ $field_name,
+ $struct_name,
+ $length,
+ $version_id,
+ ::core::ptr::addr_of!($crate::bindings::vmstate_info_uint32),
+ ::core::mem::size_of::<u32>()
+ )
+ }};
+}
+
+#[doc(alias = "VMSTATE_UINT32_ARRAY")]
+#[macro_export]
+macro_rules! vmstate_uint32_array {
+ ($field_name:ident, $struct_name:ty, $length:expr) => {{
+ $crate::vmstate_uint32_array_v!($field_name, $struct_name, $length, 0)
+ }};
+}
+
+#[doc(alias = "VMSTATE_STRUCT_POINTER_V")]
+#[macro_export]
+macro_rules! vmstate_struct_pointer_v {
+ ($field_name:ident, $struct_name:ty, $version_id:expr, $vmsd:expr, $type:ty) => {{
+ $crate::bindings::VMStateField {
+ name: ::core::concat!(::core::stringify!($field_name), 0)
+ .as_bytes()
+ .as_ptr() as *const ::std::os::raw::c_char,
+ err_hint: ::core::ptr::null(),
+ offset: $crate::offset_of!($struct_name, $field_name),
+ size: ::core::mem::size_of::<*const $type>(),
+ start: 0,
+ num: 0,
+ num_offset: 0,
+ size_offset: 0,
+ info: ::core::ptr::null(),
+ flags: VMStateFlags(VMStateFlags::VMS_STRUCT.0 | VMStateFlags::VMS_POINTER.0),
+ vmsd: unsafe { $vmsd },
+ version_id: $version_id,
+ struct_version_id: 0,
+ field_exists: None,
+ }
+ }};
+}
+
+#[doc(alias = "VMSTATE_ARRAY_OF_POINTER")]
+#[macro_export]
+macro_rules! vmstate_array_of_pointer {
+ ($field_name:ident, $struct_name:ty, $num:expr, $version_id:expr, $info:expr, $type:ty) => {{
+ $crate::bindings::VMStateField {
+ name: ::core::concat!(::core::stringify!($field_name), 0)
+ .as_bytes()
+ .as_ptr() as *const ::std::os::raw::c_char,
+ version_id: $version_id,
+ num: $num as _,
+ info: unsafe { $info },
+ size: ::core::mem::size_of::<*const $type>(),
+ flags: VMStateFlags(VMStateFlags::VMS_ARRAY.0 | VMStateFlags::VMS_ARRAY_OF_POINTER.0),
+ offset: $crate::offset_of!($struct_name, $field_name),
+ err_hint: ::core::ptr::null(),
+ start: 0,
+ num_offset: 0,
+ size_offset: 0,
+ vmsd: ::core::ptr::null(),
+ struct_version_id: 0,
+ field_exists: None,
+ }
+ }};
+}
+
+#[doc(alias = "VMSTATE_ARRAY_OF_POINTER_TO_STRUCT")]
+#[macro_export]
+macro_rules! vmstate_array_of_pointer_to_struct {
+ ($field_name:ident, $struct_name:ty, $num:expr, $version_id:expr, $vmsd:expr, $type:ty) => {{
+ $crate::bindings::VMStateField {
+ name: ::core::concat!(::core::stringify!($field_name), 0)
+ .as_bytes()
+ .as_ptr() as *const ::std::os::raw::c_char,
+ version_id: $version_id,
+ num: $num as _,
+ vmsd: unsafe { $vmsd },
+ size: ::core::mem::size_of::<*const $type>(),
+ flags: VMStateFlags(
+ VMStateFlags::VMS_ARRAY.0
+ | VMStateFlags::VMS_STRUCT.0
+ | VMStateFlags::VMS_ARRAY_OF_POINTER.0,
+ ),
+ offset: $crate::offset_of!($struct_name, $field_name),
+ err_hint: ::core::ptr::null(),
+ start: 0,
+ num_offset: 0,
+ size_offset: 0,
+ vmsd: ::core::ptr::null(),
+ struct_version_id: 0,
+ field_exists: None,
+ }
+ }};
+}
+
+#[doc(alias = "VMSTATE_CLOCK_V")]
+#[macro_export]
+macro_rules! vmstate_clock_v {
+ ($field_name:ident, $struct_name:ty, $version_id:expr) => {{
+ $crate::vmstate_struct_pointer_v!(
+ $field_name,
+ $struct_name,
+ $version_id,
+ ::core::ptr::addr_of!($crate::bindings::vmstate_clock),
+ $crate::bindings::Clock
+ )
+ }};
+}
+
+#[doc(alias = "VMSTATE_CLOCK")]
+#[macro_export]
+macro_rules! vmstate_clock {
+ ($field_name:ident, $struct_name:ty) => {{
+ $crate::vmstate_clock_v!($field_name, $struct_name, 0)
+ }};
+}
+
+#[doc(alias = "VMSTATE_ARRAY_CLOCK_V")]
+#[macro_export]
+macro_rules! vmstate_array_clock_v {
+ ($field_name:ident, $struct_name:ty, $num:expr, $version_id:expr) => {{
+ $crate::vmstate_array_of_pointer_to_struct!(
+ $field_name,
+ $struct_name,
+ $num,
+ $version_id,
+ ::core::ptr::addr_of!($crate::bindings::vmstate_clock),
+ $crate::bindings::Clock
+ )
+ }};
+}
+
+#[doc(alias = "VMSTATE_ARRAY_CLOCK")]
+#[macro_export]
+macro_rules! vmstate_array_clock {
+ ($field_name:ident, $struct_name:ty, $num:expr) => {{
+ $crate::vmstate_array_clock_v!($field_name, $struct_name, $name, 0)
+ }};
+}
+
+/// Helper macro to declare a list of
+/// ([`VMStateField`](`crate::bindings::VMStateField`)) into a static and return
+/// a pointer to the array of values it created.
+#[macro_export]
+macro_rules! vmstate_fields {
+ ($($field:expr),*$(,)*) => {{
+ static _FIELDS: &[$crate::bindings::VMStateField] = &[
+ $($field),*,
+ $crate::bindings::VMStateField {
+ name: ::core::ptr::null(),
+ err_hint: ::core::ptr::null(),
+ offset: 0,
+ size: 0,
+ start: 0,
+ num: 0,
+ num_offset: 0,
+ size_offset: 0,
+ info: ::core::ptr::null(),
+ flags: VMStateFlags::VMS_END,
+ vmsd: ::core::ptr::null(),
+ version_id: 0,
+ struct_version_id: 0,
+ field_exists: None,
+ }
+ ];
+ _FIELDS.as_ptr()
+ }}
+}
+
+/// A transparent wrapper type for the `subsections` field of
+/// [`VMStateDescription`](crate::bindings::VMStateDescription).
+///
+/// This is necessary to be able to declare subsection descriptions as statics,
+/// because the only way to implement `Sync` for a foreign type (and `*const`
+/// pointers are foreign types in Rust) is to create a wrapper struct and
+/// `unsafe impl Sync` for it.
+///
+/// This struct is used in the
+/// [`vm_state_subsections`](crate::vmstate_subsections) macro implementation.
+#[repr(transparent)]
+pub struct VMStateSubsectionsWrapper(pub &'static [*const crate::bindings::VMStateDescription]);
+
+unsafe impl Sync for VMStateSubsectionsWrapper {}
+
+/// Helper macro to declare a list of subsections
+/// ([`VMStateDescription`](`crate::bindings::VMStateDescription`)) into a
+/// static and return a pointer to the array of pointers it created.
+#[macro_export]
+macro_rules! vmstate_subsections {
+ ($($subsection:expr),*$(,)*) => {{
+ static _SUBSECTIONS: $crate::vmstate::VMStateSubsectionsWrapper = $crate::vmstate::VMStateSubsectionsWrapper(&[
+ $({
+ static _SUBSECTION: $crate::bindings::VMStateDescription = $subsection;
+ ::core::ptr::addr_of!(_SUBSECTION)
+ }),*,
+ ::core::ptr::null()
+ ]);
+ _SUBSECTIONS.0.as_ptr()
+ }}
+}
diff --git a/rust/qemu-api/src/zeroable.rs b/rust/qemu-api/src/zeroable.rs
new file mode 100644
index 0000000000..13cdb2ccba
--- /dev/null
+++ b/rust/qemu-api/src/zeroable.rs
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use std::ptr;
+
+/// Encapsulates the requirement that
+/// `MaybeUninit::<Self>::zeroed().assume_init()` does not cause undefined
+/// behavior. This trait in principle could be implemented as just:
+///
+/// ```
+/// const ZERO: Self = unsafe {
+/// ::core::mem::MaybeUninit::<$crate::bindings::Property>::zeroed().assume_init()
+/// },
+/// ```
+///
+/// The need for a manual implementation is only because `zeroed()` cannot
+/// be used as a `const fn` prior to Rust 1.75.0. Once we can assume a new
+/// enough version of the compiler, we could provide a `#[derive(Zeroable)]`
+/// macro to check at compile-time that all struct fields are Zeroable, and
+/// use the above blanket implementation of the `ZERO` constant.
+///
+/// # Safety
+///
+/// Because the implementation of `ZERO` is manual, it does not make
+/// any assumption on the safety of `zeroed()`. However, other users of the
+/// trait could use it that way. Do not add this trait to a type unless
+/// all-zeroes is a valid value for the type. In particular, remember that
+/// raw pointers can be zero, but references and `NonNull<T>` cannot
+pub unsafe trait Zeroable: Default {
+ const ZERO: Self;
+}
+
+unsafe impl Zeroable for crate::bindings::Property__bindgen_ty_1 {
+ const ZERO: Self = Self { i: 0 };
+}
+
+unsafe impl Zeroable for crate::bindings::Property {
+ const ZERO: Self = Self {
+ name: ptr::null(),
+ info: ptr::null(),
+ offset: 0,
+ bitnr: 0,
+ bitmask: 0,
+ set_default: false,
+ defval: Zeroable::ZERO,
+ arrayoffset: 0,
+ arrayinfo: ptr::null(),
+ arrayfieldsize: 0,
+ link_type: ptr::null(),
+ };
+}
+
+unsafe impl Zeroable for crate::bindings::VMStateDescription {
+ const ZERO: Self = Self {
+ name: ptr::null(),
+ unmigratable: false,
+ early_setup: false,
+ version_id: 0,
+ minimum_version_id: 0,
+ priority: crate::bindings::MigrationPriority::MIG_PRI_DEFAULT,
+ pre_load: None,
+ post_load: None,
+ pre_save: None,
+ post_save: None,
+ needed: None,
+ dev_unplug_pending: None,
+ fields: ptr::null(),
+ subsections: ptr::null(),
+ };
+}
+
+unsafe impl Zeroable for crate::bindings::MemoryRegionOps__bindgen_ty_1 {
+ const ZERO: Self = Self {
+ min_access_size: 0,
+ max_access_size: 0,
+ unaligned: false,
+ accepts: None,
+ };
+}
+
+unsafe impl Zeroable for crate::bindings::MemoryRegionOps__bindgen_ty_2 {
+ const ZERO: Self = Self {
+ min_access_size: 0,
+ max_access_size: 0,
+ unaligned: false,
+ };
+}
diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs
new file mode 100644
index 0000000000..43a4827de1
--- /dev/null
+++ b/rust/qemu-api/tests/tests.rs
@@ -0,0 +1,79 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use std::{ffi::CStr, os::raw::c_void};
+
+use qemu_api::{
+ bindings::*,
+ c_str, declare_properties, define_property,
+ definitions::{Class, ObjectImpl},
+ device_class, device_class_init,
+ zeroable::Zeroable,
+};
+
+#[test]
+fn test_device_decl_macros() {
+ // Test that macros can compile.
+ pub static VMSTATE: VMStateDescription = VMStateDescription {
+ name: c_str!("name").as_ptr(),
+ unmigratable: true,
+ ..Zeroable::ZERO
+ };
+
+ #[derive(qemu_api_macros::offsets)]
+ #[repr(C)]
+ #[derive(qemu_api_macros::Object)]
+ pub struct DummyState {
+ pub _parent: DeviceState,
+ pub migrate_clock: bool,
+ }
+
+ #[repr(C)]
+ pub struct DummyClass {
+ pub _parent: DeviceClass,
+ }
+
+ declare_properties! {
+ DUMMY_PROPERTIES,
+ define_property!(
+ c_str!("migrate-clk"),
+ DummyState,
+ migrate_clock,
+ unsafe { &qdev_prop_bool },
+ bool
+ ),
+ }
+
+ device_class_init! {
+ dummy_class_init,
+ props => DUMMY_PROPERTIES,
+ realize_fn => None,
+ legacy_reset_fn => None,
+ vmsd => VMSTATE,
+ }
+
+ impl ObjectImpl for DummyState {
+ type Class = DummyClass;
+ const TYPE_INFO: qemu_api::bindings::TypeInfo = qemu_api::type_info! { Self };
+ const TYPE_NAME: &'static CStr = c_str!("dummy");
+ const PARENT_TYPE_NAME: Option<&'static CStr> = Some(device_class::TYPE_DEVICE);
+ const ABSTRACT: bool = false;
+ const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> = None;
+ 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;
+ }
+
+ impl Class for DummyClass {
+ const CLASS_INIT: Option<unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void)> =
+ Some(dummy_class_init);
+ const CLASS_BASE_INIT: Option<
+ unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void),
+ > = None;
+ }
+
+ unsafe {
+ module_call_init(module_init_type::MODULE_INIT_QOM);
+ object_unref(object_new(DummyState::TYPE_NAME.as_ptr()) as *mut _);
+ }
+}