diff options
author | Richard Henderson <richard.henderson@linaro.org> | 2025-09-24 12:04:18 -0700 |
---|---|---|
committer | Richard Henderson <richard.henderson@linaro.org> | 2025-09-24 12:04:18 -0700 |
commit | 95b9e0d2ade5d633fd13ffba96a54e87c65baf39 (patch) | |
tree | f94d1493977dc2457608abeab74c549da6b01a06 /rust | |
parent | 687a9b83833cda591a04f997a5260f85bd0c5e44 (diff) | |
parent | cd64320e1e27168d3796a847bbfde66c8b1116f9 (diff) | |
download | qemu-master.zip qemu-master.tar.gz qemu-master.tar.bz2 |
* qom: Do not unparent in instance_finalize
* linux-user: avoid -Werror=int-in-bool-context
* docs: use the pyvenv version of Meson
* rust: parse attributes using the attrs crate
* rust: complete conversion of qdev properties to proc macro
* docs: clarify AI-generated content policy
# -----BEGIN PGP SIGNATURE-----
#
# iQFIBAABCgAyFiEE8TM4V0tmI4mGbHaCv/vSX3jHroMFAmjTnTgUHHBib256aW5p
# QHJlZGhhdC5jb20ACgkQv/vSX3jHroNYUwf9EpJbiCN8Qif9JU3XQEaOMDGTDO07
# nMvn6RnRTFyn4iYzCc+pn6GFKWfJGZ6/cD9Qby7lyi3lHlhW8fLYbAcTXn1HoLNk
# lr/Ibmyaa8U2WP5u/QG+3dwn9zTgNFza3BFLguKrOhWjbv3ZL85xez29yChGgtYq
# sTUTigtl261JF4SvtOhzCMqUPo4wzqD0m0Vc/pjxrlgpHAb3rKf32Y6xPkNMVN84
# 81egbF0ZRtUbubjvGzPFstMdRcVBdrac5wnFPWum9GazuWwB4K8p2iBFdmuXMOhy
# NW6M8HP516zhoNk7bA5zQghxmhPWLXah4iA7MflAzLTI30s23TNIMCeJRw==
# =ug+J
# -----END PGP SIGNATURE-----
# gpg: Signature made Wed 24 Sep 2025 12:26:48 AM PDT
# gpg: using RSA key F13338574B662389866C7682BFFBD25F78C7AE83
# gpg: issuer "pbonzini@redhat.com"
# gpg: Good signature from "Paolo Bonzini <bonzini@gnu.org>" [unknown]
# gpg: aka "Paolo Bonzini <pbonzini@redhat.com>" [unknown]
# gpg: WARNING: The key's User ID is not certified with a trusted signature!
# gpg: There is no indication that the signature belongs to the owner.
# Primary key fingerprint: 46F5 9FBD 57D6 12E7 BFD4 E2F7 7E15 100C CD36 69B1
# Subkey fingerprint: F133 3857 4B66 2389 866C 7682 BFFB D25F 78C7 AE83
* tag 'for-upstream' of https://gitlab.com/bonzini/qemu: (29 commits)
docs/code-provenance: AI exceptions are in addition to DCO
docs/code-provenance: make the exception process more prominent
docs/code-provenance: clarify scope very early
hw/xen: Do not unparent in instance_finalize()
vfio: Do not unparent in instance_finalize()
hw/sd/sdhci: Do not unparent in instance_finalize()
hv-balloon: hw/core/register: Do not unparent in instance_finalize()
hw/core/register: Do not unparent in instance_finalize()
vfio/pci: Do not unparent in instance_finalize()
docs/devel: Do not unparent in instance_finalize()
linux-user: avoid -Werror=int-in-bool-context
rust/qdev: Drop declare_properties & define_property macros
rust/hpet: Convert qdev properties to #property macro
rust/hpet: Clean up type mismatch for num_timers property
rust/qdev: Test bit property for #property
rust/qdev: Support bit property in #property macro
rust/qdev: Support property info for more common types
rust/qdev: Refine the documentation for QDevProp trait
rust/qdev: use addr_of! in QDevProp
rust/common/uninit: Fix Clippy's complaints about lifetime
...
Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
Diffstat (limited to 'rust')
-rw-r--r-- | rust/Cargo.lock | 11 | ||||
-rw-r--r-- | rust/bql/meson.build | 1 | ||||
-rw-r--r-- | rust/common/meson.build | 4 | ||||
-rw-r--r-- | rust/common/src/uninit.rs | 4 | ||||
-rw-r--r-- | rust/hw/core/src/qdev.rs | 105 | ||||
-rw-r--r-- | rust/hw/timer/hpet/src/device.rs | 55 | ||||
-rw-r--r-- | rust/meson.build | 2 | ||||
-rw-r--r-- | rust/migration/meson.build | 1 | ||||
-rw-r--r-- | rust/migration/src/vmstate.rs | 2 | ||||
-rw-r--r-- | rust/qemu-macros/Cargo.toml | 1 | ||||
-rw-r--r-- | rust/qemu-macros/meson.build | 1 | ||||
-rw-r--r-- | rust/qemu-macros/src/lib.rs | 108 | ||||
-rw-r--r-- | rust/qemu-macros/src/tests.rs | 113 | ||||
-rw-r--r-- | rust/qom/meson.build | 1 | ||||
-rw-r--r-- | rust/util/meson.build | 5 |
15 files changed, 224 insertions, 190 deletions
diff --git a/rust/Cargo.lock b/rust/Cargo.lock index eea9286..8315f98 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -15,6 +15,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c84fc003e338a6f69fbd4f7fe9f92b535ff13e9af8997f3b14b6ddff8b1df46d" [[package]] +name = "attrs" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a207d40f43de65285f3de0509bb6cb16bc46098864fce957122bbacce327e5f" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] name = "bilge" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -188,6 +198,7 @@ dependencies = [ name = "qemu_macros" version = "0.1.0" dependencies = [ + "attrs", "proc-macro2", "quote", "syn", diff --git a/rust/bql/meson.build b/rust/bql/meson.build index f369209..7214d94 100644 --- a/rust/bql/meson.build +++ b/rust/bql/meson.build @@ -47,6 +47,5 @@ bql_rs = declare_dependency(link_with: [_bql_rs], # in a separate suite that is run by the "build" CI jobs rather than "check". rust.doctest('rust-bql-rs-doctests', _bql_rs, - protocol: 'rust', dependencies: bql_rs, suite: ['doc', 'rust']) diff --git a/rust/common/meson.build b/rust/common/meson.build index b805e0f..aff601d 100644 --- a/rust/common/meson.build +++ b/rust/common/meson.build @@ -24,11 +24,13 @@ _common_rs = static_library( common_rs = declare_dependency(link_with: [_common_rs]) +rust.test('rust-common-tests', _common_rs, + suite: ['unit', 'rust']) + # Doctests are essentially integration tests, so they need the same dependencies. # Note that running them requires the object files for C code, so place them # in a separate suite that is run by the "build" CI jobs rather than "check". rust.doctest('rust-common-doctests', _common_rs, - protocol: 'rust', dependencies: common_rs, suite: ['doc', 'rust']) diff --git a/rust/common/src/uninit.rs b/rust/common/src/uninit.rs index e7f9fcd..8d021b1 100644 --- a/rust/common/src/uninit.rs +++ b/rust/common/src/uninit.rs @@ -35,7 +35,7 @@ impl<'a, T, U> MaybeUninitField<'a, T, U> { } } -impl<'a, T, U> Deref for MaybeUninitField<'a, T, U> { +impl<T, U> Deref for MaybeUninitField<'_, T, U> { type Target = MaybeUninit<U>; fn deref(&self) -> &MaybeUninit<U> { @@ -46,7 +46,7 @@ impl<'a, T, U> Deref for MaybeUninitField<'a, T, U> { } } -impl<'a, T, U> DerefMut for MaybeUninitField<'a, T, U> { +impl<T, U> DerefMut for MaybeUninitField<'_, T, U> { fn deref_mut(&mut self) -> &mut MaybeUninit<U> { // SAFETY: self.child was obtained by dereferencing a valid mutable // reference; the content of the memory may be invalid or uninitialized diff --git a/rust/hw/core/src/qdev.rs b/rust/hw/core/src/qdev.rs index 71b9ef1..a4493db 100644 --- a/rust/hw/core/src/qdev.rs +++ b/rust/hw/core/src/qdev.rs @@ -6,7 +6,7 @@ use std::{ ffi::{c_int, c_void, CStr, CString}, - ptr::NonNull, + ptr::{addr_of, NonNull}, }; use chardev::Chardev; @@ -109,9 +109,16 @@ unsafe extern "C" fn rust_resettable_exit_fn<T: ResettablePhasesImpl>( /// /// # Safety /// -/// This trait is marked as `unsafe` because currently having a `const` refer to -/// an `extern static` as a reference instead of a raw pointer results in this -/// compiler error: +/// This trait is marked as `unsafe` because `BASE_INFO` and `BIT_INFO` must be +/// valid raw references to [`bindings::PropertyInfo`]. +/// +/// Note we could not use a regular reference: +/// +/// ```text +/// const VALUE: &bindings::PropertyInfo = ... +/// ``` +/// +/// because this results in the following compiler error: /// /// ```text /// constructing invalid value: encountered reference to `extern` static in `const` @@ -119,28 +126,37 @@ unsafe extern "C" fn rust_resettable_exit_fn<T: ResettablePhasesImpl>( /// /// This is because the compiler generally might dereference a normal reference /// during const evaluation, but not in this case (if it did, it'd need to -/// dereference the raw pointer so this would fail to compile). +/// dereference the raw pointer so using a `*const` would also fail to compile). /// /// It is the implementer's responsibility to provide a valid /// [`bindings::PropertyInfo`] pointer for the trait implementation to be safe. pub unsafe trait QDevProp { - const VALUE: *const bindings::PropertyInfo; -} - -/// Use [`bindings::qdev_prop_bool`] for `bool`. -unsafe impl QDevProp for bool { - const VALUE: *const bindings::PropertyInfo = unsafe { &bindings::qdev_prop_bool }; + const BASE_INFO: *const bindings::PropertyInfo; + const BIT_INFO: *const bindings::PropertyInfo = { + panic!("invalid type for bit property"); + }; } -/// Use [`bindings::qdev_prop_uint64`] for `u64`. -unsafe impl QDevProp for u64 { - const VALUE: *const bindings::PropertyInfo = unsafe { &bindings::qdev_prop_uint64 }; +macro_rules! impl_qdev_prop { + ($type:ty,$info:ident$(, $bit_info:ident)?) => { + unsafe impl $crate::qdev::QDevProp for $type { + const BASE_INFO: *const $crate::bindings::PropertyInfo = + addr_of!($crate::bindings::$info); + $(const BIT_INFO: *const $crate::bindings::PropertyInfo = + addr_of!($crate::bindings::$bit_info);)? + } + }; } -/// Use [`bindings::qdev_prop_chr`] for [`chardev::CharBackend`]. -unsafe impl QDevProp for chardev::CharBackend { - const VALUE: *const bindings::PropertyInfo = unsafe { &bindings::qdev_prop_chr }; -} +impl_qdev_prop!(bool, qdev_prop_bool); +impl_qdev_prop!(u8, qdev_prop_uint8); +impl_qdev_prop!(u16, qdev_prop_uint16); +impl_qdev_prop!(u32, qdev_prop_uint32, qdev_prop_bit); +impl_qdev_prop!(u64, qdev_prop_uint64, qdev_prop_bit64); +impl_qdev_prop!(usize, qdev_prop_usize); +impl_qdev_prop!(i32, qdev_prop_int32); +impl_qdev_prop!(i64, qdev_prop_int64); +impl_qdev_prop!(chardev::CharBackend, qdev_prop_chr); /// Trait to define device properties. /// @@ -232,59 +248,6 @@ impl DeviceClass { } } -#[macro_export] -macro_rules! define_property { - ($name:expr, $state:ty, $field:ident, $prop:expr, $type:ty, bit = $bitnr:expr, default = $defval:expr$(,)*) => { - $crate::bindings::Property { - // use associated function syntax for type checking - name: ::std::ffi::CStr::as_ptr($name), - info: $prop, - offset: ::std::mem::offset_of!($state, $field) as isize, - bitnr: $bitnr, - set_default: true, - defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval as u64 }, - ..::common::zeroable::Zeroable::ZERO - } - }; - ($name:expr, $state:ty, $field:ident, $prop:expr, $type:ty, default = $defval:expr$(,)*) => { - $crate::bindings::Property { - // use associated function syntax for type checking - name: ::std::ffi::CStr::as_ptr($name), - info: $prop, - offset: ::std::mem::offset_of!($state, $field) as isize, - set_default: true, - defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval as u64 }, - ..::common::zeroable::Zeroable::ZERO - } - }; - ($name:expr, $state:ty, $field:ident, $prop:expr, $type:ty$(,)*) => { - $crate::bindings::Property { - // use associated function syntax for type checking - name: ::std::ffi::CStr::as_ptr($name), - info: $prop, - offset: ::std::mem::offset_of!($state, $field) as isize, - set_default: false, - ..::common::zeroable::Zeroable::ZERO - } - }; -} - -#[macro_export] -macro_rules! declare_properties { - ($ident:ident, $($prop:expr),*$(,)*) => { - pub static $ident: [$crate::bindings::Property; { - let mut len = 0; - $({ - _ = stringify!($prop); - len += 1; - })* - len - }] = [ - $($prop),*, - ]; - }; -} - unsafe impl ObjectType for DeviceState { type Class = DeviceClass; const TYPE_NAME: &'static CStr = diff --git a/rust/hw/timer/hpet/src/device.rs b/rust/hw/timer/hpet/src/device.rs index 3cfbe9c..86638c0 100644 --- a/rust/hw/timer/hpet/src/device.rs +++ b/rust/hw/timer/hpet/src/device.rs @@ -13,9 +13,8 @@ use std::{ use bql::{BqlCell, BqlRefCell}; use common::{bitops::IntegerExt, uninit_field_mut}; use hwcore::{ - bindings::{qdev_prop_bit, qdev_prop_bool, qdev_prop_uint32, qdev_prop_usize}, - declare_properties, define_property, DeviceImpl, DeviceMethods, DeviceState, InterruptSource, - Property, ResetType, ResettablePhasesImpl, SysBusDevice, SysBusDeviceImpl, SysBusDeviceMethods, + DeviceImpl, DeviceMethods, DeviceState, InterruptSource, ResetType, ResettablePhasesImpl, + SysBusDevice, SysBusDeviceImpl, SysBusDeviceMethods, }; use migration::{ self, impl_vmstate_struct, vmstate_fields, vmstate_of, vmstate_subsections, vmstate_validate, @@ -520,7 +519,7 @@ impl HPETTimer { /// HPET Event Timer Block Abstraction #[repr(C)] -#[derive(qom::Object)] +#[derive(qom::Object, hwcore::Device)] pub struct HPETState { parent_obj: ParentField<SysBusDevice>, iomem: MemoryRegion, @@ -540,10 +539,12 @@ pub struct HPETState { // Internal state /// Capabilities that QEMU HPET supports. /// bit 0: MSI (or FSB) support. + #[property(rename = "msi", bit = HPET_FLAG_MSI_SUPPORT_SHIFT as u8, default = false)] flags: u32, /// Offset of main counter relative to qemu clock. hpet_offset: BqlCell<u64>, + #[property(rename = "hpet-offset-saved", default = true)] hpet_offset_saved: bool, irqs: [InterruptSource; HPET_NUM_IRQ_ROUTES], @@ -555,11 +556,13 @@ pub struct HPETState { /// the timers' interrupt can be routed, and is encoded in the /// bits 32:64 of timer N's config register: #[doc(alias = "intcap")] + #[property(rename = "hpet-intcap", default = 0)] int_route_cap: u32, /// HPET timer array managed by this timer block. #[doc(alias = "timer")] timers: [BqlRefCell<HPETTimer>; HPET_MAX_TIMERS], + #[property(rename = "timers", default = HPET_MIN_TIMERS)] num_timers: usize, num_timers_save: BqlCell<u8>, @@ -901,44 +904,6 @@ impl ObjectImpl for HPETState { const CLASS_INIT: fn(&mut Self::Class) = Self::Class::class_init::<Self>; } -// TODO: Make these properties user-configurable! -declare_properties! { - HPET_PROPERTIES, - define_property!( - c"timers", - HPETState, - num_timers, - unsafe { &qdev_prop_usize }, - u8, - default = HPET_MIN_TIMERS - ), - define_property!( - c"msi", - HPETState, - flags, - unsafe { &qdev_prop_bit }, - u32, - bit = HPET_FLAG_MSI_SUPPORT_SHIFT as u8, - default = false, - ), - define_property!( - c"hpet-intcap", - HPETState, - int_route_cap, - unsafe { &qdev_prop_uint32 }, - u32, - default = 0 - ), - define_property!( - c"hpet-offset-saved", - HPETState, - hpet_offset_saved, - unsafe { &qdev_prop_bool }, - bool, - default = true - ), -} - static VMSTATE_HPET_RTC_IRQ_LEVEL: VMStateDescription<HPETState> = VMStateDescriptionBuilder::<HPETState>::new() .name(c"hpet/rtc_irq_level") @@ -1001,12 +966,6 @@ const VMSTATE_HPET: VMStateDescription<HPETState> = )) .build(); -// SAFETY: HPET_PROPERTIES is a valid Property array constructed with the -// hwcore::declare_properties macro. -unsafe impl hwcore::DevicePropertiesImpl for HPETState { - const PROPERTIES: &'static [Property] = &HPET_PROPERTIES; -} - impl DeviceImpl for HPETState { const VMSTATE: Option<VMStateDescription<Self>> = Some(VMSTATE_HPET); const REALIZE: Option<fn(&Self) -> util::Result<()>> = Some(Self::realize); diff --git a/rust/meson.build b/rust/meson.build index c7bd6ab..b3ac3a7 100644 --- a/rust/meson.build +++ b/rust/meson.build @@ -13,10 +13,12 @@ libc_rs = dependency('libc-0.2-rs') subproject('proc-macro2-1-rs', required: true) subproject('quote-1-rs', required: true) subproject('syn-2-rs', required: true) +subproject('attrs-0.2-rs', required: true) quote_rs_native = dependency('quote-1-rs', native: true) syn_rs_native = dependency('syn-2-rs', native: true) proc_macro2_rs_native = dependency('proc-macro2-1-rs', native: true) +attrs_rs_native = dependency('attrs-0.2-rs', native: true) genrs = [] diff --git a/rust/migration/meson.build b/rust/migration/meson.build index 5e820d4..2a49bd1 100644 --- a/rust/migration/meson.build +++ b/rust/migration/meson.build @@ -48,6 +48,5 @@ migration_rs = declare_dependency(link_with: [_migration_rs], # in a separate suite that is run by the "build" CI jobs rather than "check". rust.doctest('rust-migration-rs-doctests', _migration_rs, - protocol: 'rust', dependencies: migration_rs, suite: ['doc', 'rust']) diff --git a/rust/migration/src/vmstate.rs b/rust/migration/src/vmstate.rs index c05c4a1..e04b19b 100644 --- a/rust/migration/src/vmstate.rs +++ b/rust/migration/src/vmstate.rs @@ -144,7 +144,7 @@ macro_rules! vmstate_of { $crate::bindings::VMStateField { name: ::core::concat!(::core::stringify!($field_name), "\0") .as_bytes() - .as_ptr() as *const ::std::os::raw::c_char, + .as_ptr().cast::<::std::os::raw::c_char>(), offset: ::std::mem::offset_of!($struct_name, $field_name), $(num_offset: ::std::mem::offset_of!($struct_name, $num),)? $(field_exists: $crate::vmstate_exist_fn!($struct_name, $test_fn),)? diff --git a/rust/qemu-macros/Cargo.toml b/rust/qemu-macros/Cargo.toml index 3b6f1d3..c25b6c0 100644 --- a/rust/qemu-macros/Cargo.toml +++ b/rust/qemu-macros/Cargo.toml @@ -16,6 +16,7 @@ rust-version.workspace = true proc-macro = true [dependencies] +attrs = "0.2.9" proc-macro2 = "1" quote = "1" syn = { version = "2", features = ["extra-traits"] } diff --git a/rust/qemu-macros/meson.build b/rust/qemu-macros/meson.build index d0b2992..0f27e0d 100644 --- a/rust/qemu-macros/meson.build +++ b/rust/qemu-macros/meson.build @@ -8,6 +8,7 @@ _qemu_macros_rs = rust.proc_macro( '--cfg', 'feature="proc-macro"', ], dependencies: [ + attrs_rs_native, proc_macro2_rs_native, quote_rs_native, syn_rs_native, diff --git a/rust/qemu-macros/src/lib.rs b/rust/qemu-macros/src/lib.rs index 830b432..3e21b67 100644 --- a/rust/qemu-macros/src/lib.rs +++ b/rust/qemu-macros/src/lib.rs @@ -3,10 +3,14 @@ // SPDX-License-Identifier: GPL-2.0-or-later use proc_macro::TokenStream; -use quote::{quote, quote_spanned, ToTokens}; +use quote::{quote, quote_spanned}; use syn::{ - parse::Parse, parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, - token::Comma, Data, DeriveInput, Error, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token, + parse::{Parse, ParseStream}, + parse_macro_input, parse_quote, + punctuated::Punctuated, + spanned::Spanned, + token::Comma, + Attribute, Data, DeriveInput, Error, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token, Variant, }; mod bits; @@ -159,61 +163,39 @@ enum DevicePropertyName { Str(syn::LitStr), } -#[derive(Debug)] +impl Parse for DevicePropertyName { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + let lo = input.lookahead1(); + if lo.peek(syn::LitStr) { + Ok(Self::Str(input.parse()?)) + } else if lo.peek(syn::LitCStr) { + Ok(Self::CStr(input.parse()?)) + } else { + Err(lo.error()) + } + } +} + +#[derive(Default, Debug)] struct DeviceProperty { rename: Option<DevicePropertyName>, + bitnr: Option<syn::Expr>, defval: Option<syn::Expr>, } -impl Parse for DeviceProperty { - fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { - let _: syn::Token![#] = input.parse()?; - let bracketed; - _ = syn::bracketed!(bracketed in input); - let attribute = bracketed.parse::<syn::Ident>()?; - debug_assert_eq!(&attribute.to_string(), "property"); - let mut retval = Self { - rename: None, - defval: None, - }; - let content; - _ = syn::parenthesized!(content in bracketed); - while !content.is_empty() { - let value: syn::Ident = content.parse()?; - if value == "rename" { - let _: syn::Token![=] = content.parse()?; - if retval.rename.is_some() { - return Err(syn::Error::new( - value.span(), - "`rename` can only be used at most once", - )); - } - if content.peek(syn::LitStr) { - retval.rename = Some(DevicePropertyName::Str(content.parse::<syn::LitStr>()?)); - } else { - retval.rename = - Some(DevicePropertyName::CStr(content.parse::<syn::LitCStr>()?)); - } - } else if value == "default" { - let _: syn::Token![=] = content.parse()?; - if retval.defval.is_some() { - return Err(syn::Error::new( - value.span(), - "`default` can only be used at most once", - )); - } - retval.defval = Some(content.parse()?); - } else { - return Err(syn::Error::new( - value.span(), - format!("unrecognized field `{value}`"), - )); - } +impl DeviceProperty { + fn parse_from(&mut self, a: &Attribute) -> syn::Result<()> { + use attrs::{set, with, Attrs}; + let mut parser = Attrs::new(); + parser.once("rename", with::eq(set::parse(&mut self.rename))); + parser.once("bit", with::eq(set::parse(&mut self.bitnr))); + parser.once("default", with::eq(set::parse(&mut self.defval))); + a.parse_args_with(&mut parser) + } - if !content.is_empty() { - let _: syn::Token![,] = content.parse()?; - } - } + fn parse(a: &Attribute) -> syn::Result<Self> { + let mut retval = Self::default(); + retval.parse_from(a)?; Ok(retval) } } @@ -235,14 +217,18 @@ fn derive_device_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream f.attrs .iter() .filter(|a| a.path().is_ident("property")) - .map(|a| Ok((f.clone(), syn::parse2(a.to_token_stream())?))) + .map(|a| Ok((f.clone(), DeviceProperty::parse(a)?))) }) .collect::<Result<Vec<_>, Error>>()?; let name = &input.ident; let mut properties_expanded = vec![]; for (field, prop) in properties { - let DeviceProperty { rename, defval } = prop; + let DeviceProperty { + rename, + bitnr, + defval, + } = prop; let field_name = field.ident.unwrap(); macro_rules! str_to_c_str { ($value:expr, $span:expr) => {{ @@ -262,8 +248,8 @@ fn derive_device_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream let prop_name = rename.map_or_else( || str_to_c_str!(field_name.to_string(), field_name.span()), - |rename| -> Result<proc_macro2::TokenStream, Error> { - match rename { + |prop_rename| -> Result<proc_macro2::TokenStream, Error> { + match prop_rename { DevicePropertyName::CStr(cstr_lit) => Ok(quote! { #cstr_lit }), DevicePropertyName::Str(str_lit) => { str_to_c_str!(str_lit.value(), str_lit.span()) @@ -272,14 +258,20 @@ fn derive_device_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream }, )?; let field_ty = field.ty.clone(); - let qdev_prop = quote! { <#field_ty as ::hwcore::QDevProp>::VALUE }; + let qdev_prop = if bitnr.is_none() { + quote! { <#field_ty as ::hwcore::QDevProp>::BASE_INFO } + } else { + quote! { <#field_ty as ::hwcore::QDevProp>::BIT_INFO } + }; + let bitnr = bitnr.unwrap_or(syn::Expr::Verbatim(quote! { 0 })); let set_default = defval.is_some(); let defval = defval.unwrap_or(syn::Expr::Verbatim(quote! { 0 })); properties_expanded.push(quote! { ::hwcore::bindings::Property { name: ::std::ffi::CStr::as_ptr(#prop_name), - info: #qdev_prop , + info: #qdev_prop, offset: ::core::mem::offset_of!(#name, #field_name) as isize, + bitnr: #bitnr, set_default: #set_default, defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: #defval as u64 }, ..::common::Zeroable::ZERO diff --git a/rust/qemu-macros/src/tests.rs b/rust/qemu-macros/src/tests.rs index 9ab7eab..ac998d2 100644 --- a/rust/qemu-macros/src/tests.rs +++ b/rust/qemu-macros/src/tests.rs @@ -60,7 +60,7 @@ fn test_derive_device() { migrate_clock: bool, } }, - "unrecognized field `defalt`" + "Expected one of `bit`, `default` or `rename`" ); // Check that repeated attributes are not allowed: derive_compile_fail!( @@ -73,7 +73,8 @@ fn test_derive_device() { migrate_clock: bool, } }, - "`rename` can only be used at most once" + "Duplicate argument", + "Already used here", ); derive_compile_fail!( derive_device_or_error, @@ -85,7 +86,21 @@ fn test_derive_device() { migrate_clock: bool, } }, - "`default` can only be used at most once" + "Duplicate argument", + "Already used here", + ); + derive_compile_fail!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + struct DummyState { + #[property(bit = 0, bit = 1)] + flags: u32, + } + }, + "Duplicate argument", + "Already used here", ); // Check that the field name is preserved when `rename` isn't used: derive_compile!( @@ -104,8 +119,9 @@ fn test_derive_device() { const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ ::hwcore::bindings::Property { name: ::std::ffi::CStr::as_ptr(c"migrate_clock"), - info: <bool as ::hwcore::QDevProp>::VALUE, + info: <bool as ::hwcore::QDevProp>::BASE_INFO, offset: ::core::mem::offset_of!(DummyState, migrate_clock) as isize, + bitnr: 0, set_default: true, defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: true as u64 }, ..::common::Zeroable::ZERO @@ -131,8 +147,9 @@ fn test_derive_device() { const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ ::hwcore::bindings::Property { name: ::std::ffi::CStr::as_ptr(c"migrate-clk"), - info: <bool as ::hwcore::QDevProp>::VALUE, + info: <bool as ::hwcore::QDevProp>::BASE_INFO, offset: ::core::mem::offset_of!(DummyState, migrate_clock) as isize, + bitnr: 0, set_default: true, defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: true as u64 }, ..::common::Zeroable::ZERO @@ -141,6 +158,92 @@ fn test_derive_device() { } } ); + // Check that `bit` value is used for the bit property without default + // value (note: though C macro (e.g., DEFINE_PROP_BIT) always requires + // default value, Rust side allows to default this field to "0"): + derive_compile!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + pub struct DummyState { + parent: ParentField<DeviceState>, + #[property(bit = 3)] + flags: u32, + } + }, + quote! { + unsafe impl ::hwcore::DevicePropertiesImpl for DummyState { + const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ + ::hwcore::bindings::Property { + name: ::std::ffi::CStr::as_ptr(c"flags"), + info: <u32 as ::hwcore::QDevProp>::BIT_INFO, + offset: ::core::mem::offset_of!(DummyState, flags) as isize, + bitnr: 3, + set_default: false, + defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: 0 as u64 }, + ..::common::Zeroable::ZERO + } + ]; + } + } + ); + // Check that `bit` value is used for the bit property when used: + derive_compile!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + pub struct DummyState { + parent: ParentField<DeviceState>, + #[property(bit = 3, default = true)] + flags: u32, + } + }, + quote! { + unsafe impl ::hwcore::DevicePropertiesImpl for DummyState { + const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ + ::hwcore::bindings::Property { + name: ::std::ffi::CStr::as_ptr(c"flags"), + info: <u32 as ::hwcore::QDevProp>::BIT_INFO, + offset: ::core::mem::offset_of!(DummyState, flags) as isize, + bitnr: 3, + set_default: true, + defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: true as u64 }, + ..::common::Zeroable::ZERO + } + ]; + } + } + ); + // Check that `bit` value is used for the bit property with rename when used: + derive_compile!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + pub struct DummyState { + parent: ParentField<DeviceState>, + #[property(rename = "msi", bit = 3, default = false)] + flags: u64, + } + }, + quote! { + unsafe impl ::hwcore::DevicePropertiesImpl for DummyState { + const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ + ::hwcore::bindings::Property { + name: ::std::ffi::CStr::as_ptr(c"msi"), + info: <u64 as ::hwcore::QDevProp>::BIT_INFO, + offset: ::core::mem::offset_of!(DummyState, flags) as isize, + bitnr: 3, + set_default: true, + defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: false as u64 }, + ..::common::Zeroable::ZERO + } + ]; + } + } + ); } #[test] diff --git a/rust/qom/meson.build b/rust/qom/meson.build index 40c51b7..21e1214 100644 --- a/rust/qom/meson.build +++ b/rust/qom/meson.build @@ -38,6 +38,5 @@ qom_rs = declare_dependency(link_with: [_qom_rs], dependencies: [qemu_macros, qo # in a separate suite that is run by the "build" CI jobs rather than "check". rust.doctest('rust-qom-rs-doctests', _qom_rs, - protocol: 'rust', dependencies: qom_rs, suite: ['doc', 'rust']) diff --git a/rust/util/meson.build b/rust/util/meson.build index 87a8936..7ca6993 100644 --- a/rust/util/meson.build +++ b/rust/util/meson.build @@ -44,12 +44,15 @@ _util_rs = static_library( util_rs = declare_dependency(link_with: [_util_rs], dependencies: [qemuutil, qom]) +rust.test('rust-util-tests', _util_rs, + dependencies: [qemuutil, qom], + suite: ['unit', 'rust']) + # Doctests are essentially integration tests, so they need the same dependencies. # Note that running them requires the object files for C code, so place them # in a separate suite that is run by the "build" CI jobs rather than "check". rust.doctest('rust-util-rs-doctests', _util_rs, - protocol: 'rust', dependencies: util_rs, suite: ['doc', 'rust'] ) |