aboutsummaryrefslogtreecommitdiff
path: root/rust/qemu-macros
diff options
context:
space:
mode:
Diffstat (limited to 'rust/qemu-macros')
-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
4 files changed, 160 insertions, 63 deletions
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]