aboutsummaryrefslogtreecommitdiff
path: root/rust
diff options
context:
space:
mode:
authorRichard Henderson <richard.henderson@linaro.org>2025-09-24 12:04:18 -0700
committerRichard Henderson <richard.henderson@linaro.org>2025-09-24 12:04:18 -0700
commit95b9e0d2ade5d633fd13ffba96a54e87c65baf39 (patch)
treef94d1493977dc2457608abeab74c549da6b01a06 /rust
parent687a9b83833cda591a04f997a5260f85bd0c5e44 (diff)
parentcd64320e1e27168d3796a847bbfde66c8b1116f9 (diff)
downloadqemu-master.zip
qemu-master.tar.gz
qemu-master.tar.bz2
Merge tag 'for-upstream' of https://gitlab.com/bonzini/qemu into stagingHEADmaster
* 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.lock11
-rw-r--r--rust/bql/meson.build1
-rw-r--r--rust/common/meson.build4
-rw-r--r--rust/common/src/uninit.rs4
-rw-r--r--rust/hw/core/src/qdev.rs105
-rw-r--r--rust/hw/timer/hpet/src/device.rs55
-rw-r--r--rust/meson.build2
-rw-r--r--rust/migration/meson.build1
-rw-r--r--rust/migration/src/vmstate.rs2
-rw-r--r--rust/qemu-macros/Cargo.toml1
-rw-r--r--rust/qemu-macros/meson.build1
-rw-r--r--rust/qemu-macros/src/lib.rs108
-rw-r--r--rust/qemu-macros/src/tests.rs113
-rw-r--r--rust/qom/meson.build1
-rw-r--r--rust/util/meson.build5
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']
)