From 122748c83d2a331b43ea17efd78c4117a362f3f2 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 19 Dec 2024 16:37:20 +0100 Subject: rust: fix --enable-debug-mutex --feature is an option for cargo but not for rustc. Reported-by: Bernhard Beschow Reviewed-by: Bernhard Beschow Signed-off-by: Paolo Bonzini --- rust/qemu-api/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rust') diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index ccb20f3..9425ba7 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -7,7 +7,7 @@ if rustc.version().version_compare('>=1.77.0') _qemu_api_cfg += ['--cfg', 'has_offset_of'] endif if get_option('debug_mutex') - _qemu_api_cfg += ['--feature', 'debug_cell'] + _qemu_api_cfg += ['--cfg', 'feature="debug_cell"'] endif _qemu_api_rs = static_library( -- cgit v1.1 From ca0d60a6ad777ab617cbc4e6f328eaff60617b3f Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 11 Dec 2024 11:38:20 +0100 Subject: rust: qom: add ParentField Add a type that, together with the C function object_deinit, ensures the correct drop order for QOM objects relative to their superclasses. Right now it is not possible to implement the Drop trait for QOM classes that are defined in Rust, as the drop() function would not be called when the object goes away; instead what is called is ObjectImpl::INSTANCE_FINALIZE. It would be nice for INSTANCE_FINALIZE to just drop the object, but this has a problem: suppose you have pub struct MySuperclass { parent: DeviceState, field: Box, ... } impl Drop for MySuperclass { ... } pub struct MySubclass { parent: MySuperclass, ... } and an instance_finalize implementation that is like unsafe extern "C" fn drop_object(obj: *mut Object) { unsafe { std::ptr::drop_in_place(obj.cast::()) } } When instance_finalize is called for MySubclass, it will walk the struct's list of fields and call the drop method for MySuperclass. Then, object_deinit recurses to the superclass and calls the same drop method again. This will cause double-freeing of the Box. What's happening here is that QOM wants to control the drop order of MySuperclass and MySubclass's fields. To do so, the parent field must be marked ManuallyDrop<>, which is quite ugly. Instead, add a wrapper type ParentField<> that is specific to QOM. This hides the implementation detail of *what* is special about the ParentField, and will also be easy to check in the #[derive(Object)] macro. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 6 ++-- rust/qemu-api/src/qom.rs | 64 ++++++++++++++++++++++++++++++++++++---- rust/qemu-api/tests/tests.rs | 4 +-- 3 files changed, 63 insertions(+), 11 deletions(-) (limited to 'rust') diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 18cc122..689202f 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -14,7 +14,7 @@ use qemu_api::{ irq::InterruptSource, prelude::*, qdev::DeviceImpl, - qom::ObjectImpl, + qom::{ObjectImpl, ParentField}, }; use crate::{ @@ -86,7 +86,7 @@ impl std::ops::Index for Fifo { #[derive(Debug, qemu_api_macros::Object, qemu_api_macros::offsets)] /// PL011 Device Model in QEMU pub struct PL011State { - pub parent_obj: SysBusDevice, + pub parent_obj: ParentField, pub iomem: MemoryRegion, #[doc(alias = "fr")] pub flags: registers::Flags, @@ -645,7 +645,7 @@ pub unsafe extern "C" fn pl011_create( #[derive(Debug, qemu_api_macros::Object)] /// PL011 Luminary device model. pub struct PL011Luminary { - parent_obj: PL011State, + parent_obj: ParentField, } impl PL011Luminary { diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index 7d5fbef..40d17a9 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -55,6 +55,7 @@ use std::{ ffi::CStr, + fmt, ops::{Deref, DerefMut}, os::raw::c_void, }; @@ -105,6 +106,52 @@ macro_rules! qom_isa { }; } +/// This is the same as [`ManuallyDrop`](std::mem::ManuallyDrop), though +/// it hides the standard methods of `ManuallyDrop`. +/// +/// The first field of an `ObjectType` must be of type `ParentField`. +/// (Technically, this is only necessary if there is at least one Rust +/// superclass in the hierarchy). This is to ensure that the parent field is +/// dropped after the subclass; this drop order is enforced by the C +/// `object_deinit` function. +/// +/// # Examples +/// +/// ```ignore +/// #[repr(C)] +/// #[derive(qemu_api_macros::Object)] +/// pub struct MyDevice { +/// parent: ParentField, +/// ... +/// } +/// ``` +#[derive(Debug)] +#[repr(transparent)] +pub struct ParentField(std::mem::ManuallyDrop); + +impl Deref for ParentField { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for ParentField { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl fmt::Display for ParentField { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + self.0.fmt(f) + } +} + unsafe extern "C" fn rust_instance_init(obj: *mut Object) { // SAFETY: obj is an instance of T, since rust_instance_init // is called from QOM core as the instance_init function @@ -151,11 +198,16 @@ unsafe extern "C" fn rust_class_init>( /// /// - the struct must be `#[repr(C)]`; /// -/// - the first field of the struct must be of the instance struct corresponding -/// to the superclass, which is `ObjectImpl::ParentType` +/// - the first field of the struct must be of type +/// [`ParentField`](ParentField), where `T` is the parent type +/// [`ObjectImpl::ParentType`] +/// +/// - the first field of the `Class` must be of the class struct corresponding +/// to the superclass, which is `ObjectImpl::ParentType::Class`. `ParentField` +/// is not needed here. /// -/// - likewise, the first field of the `Class` must be of the class struct -/// corresponding to the superclass, which is `ObjectImpl::ParentType::Class`. +/// In both cases, having a separate class type is not necessary if the subclass +/// does not add any field. pub unsafe trait ObjectType: Sized { /// The QOM class object corresponding to this struct. This is used /// to automatically generate a `class_init` method. @@ -384,8 +436,8 @@ impl ObjectCastMut for &mut T {} /// Trait a type must implement to be registered with QEMU. pub trait ObjectImpl: ObjectType + ClassInitImpl { - /// The parent of the type. This should match the first field of - /// the struct that implements `ObjectImpl`: + /// The parent of the type. This should match the first field of the + /// struct that implements `ObjectImpl`, minus the `ParentField<_>` wrapper. type ParentType: ObjectType; /// Whether the object can be instantiated diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index 1d2825b..526c3f4 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -15,7 +15,7 @@ use qemu_api::{ declare_properties, define_property, prelude::*, qdev::{DeviceImpl, DeviceState, Property}, - qom::ObjectImpl, + qom::{ObjectImpl, ParentField}, vmstate::VMStateDescription, zeroable::Zeroable, }; @@ -31,7 +31,7 @@ pub static VMSTATE: VMStateDescription = VMStateDescription { #[repr(C)] #[derive(qemu_api_macros::Object)] pub struct DummyState { - parent: DeviceState, + parent: ParentField, migrate_clock: bool, } -- cgit v1.1 From 7f65d4e58b67d6e5ee05c9381a50ef1eba3a5a1e Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 11 Dec 2024 12:18:06 +0100 Subject: rust: add a utility module for compile-time type checks It is relatively common in the low-level qemu_api code to assert that a field of a struct has a specific type; for example, it can be used to ensure that the fields match what the qemu_api and C code expects for safety. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/meson.build | 1 + rust/qemu-api/src/assertions.rs | 90 +++++++++++++++++++++++++++++++++++++++++ rust/qemu-api/src/lib.rs | 1 + 3 files changed, 92 insertions(+) create mode 100644 rust/qemu-api/src/assertions.rs (limited to 'rust') diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index 9425ba7..60944a6 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -15,6 +15,7 @@ _qemu_api_rs = static_library( structured_sources( [ 'src/lib.rs', + 'src/assertions.rs', 'src/bindings.rs', 'src/bitops.rs', 'src/callbacks.rs', diff --git a/rust/qemu-api/src/assertions.rs b/rust/qemu-api/src/assertions.rs new file mode 100644 index 0000000..6e42046 --- /dev/null +++ b/rust/qemu-api/src/assertions.rs @@ -0,0 +1,90 @@ +// Copyright 2024, Red Hat Inc. +// Author(s): Paolo Bonzini +// SPDX-License-Identifier: GPL-2.0-or-later + +//! This module provides macros to check the equality of types and +//! the type of `struct` fields. This can be useful to ensure that +//! types match the expectations of C code. + +// Based on https://stackoverflow.com/questions/64251852/x/70978292#70978292 +// (stackoverflow answers are released under MIT license). + +#[doc(hidden)] +pub trait EqType { + type Itself; +} + +impl EqType for T { + type Itself = T; +} + +/// Assert that two types are the same. +/// +/// # Examples +/// +/// ``` +/// # use qemu_api::assert_same_type; +/// # use std::ops::Deref; +/// assert_same_type!(u32, u32); +/// assert_same_type!( as Deref>::Target, u32); +/// ``` +/// +/// Different types will cause a compile failure +/// +/// ```compile_fail +/// # use qemu_api::assert_same_type; +/// assert_same_type!(&Box, &u32); +/// ``` +#[macro_export] +macro_rules! assert_same_type { + ($t1:ty, $t2:ty) => { + const _: () = { + #[allow(unused)] + fn assert_same_type(v: $t1) { + fn types_must_be_equal(_: T) + where + T: $crate::assertions::EqType, + { + } + types_must_be_equal::<_, $t2>(v); + } + }; + }; +} + +/// Assert that a field of a struct has the given type. +/// +/// # Examples +/// +/// ``` +/// # use qemu_api::assert_field_type; +/// pub struct A { +/// field1: u32, +/// } +/// +/// assert_field_type!(A, field1, u32); +/// ``` +/// +/// Different types will cause a compile failure +/// +/// ```compile_fail +/// # use qemu_api::assert_field_type; +/// # pub struct A { field1: u32 } +/// assert_field_type!(A, field1, i32); +/// ``` +#[macro_export] +macro_rules! assert_field_type { + ($t:ty, $i:tt, $ti:ty) => { + const _: () = { + #[allow(unused)] + fn assert_field_type(v: $t) { + fn types_must_be_equal(_: T) + where + T: $crate::assertions::EqType, + { + } + types_must_be_equal::<_, $ti>(v.$i); + } + }; + }; +} diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs index 4b43e02..83c6a98 100644 --- a/rust/qemu-api/src/lib.rs +++ b/rust/qemu-api/src/lib.rs @@ -12,6 +12,7 @@ pub mod bindings; #[rustfmt::skip] pub mod prelude; +pub mod assertions; pub mod bitops; pub mod c_str; pub mod callbacks; -- cgit v1.1 From 20f0b8e98b4851bfd52bbb4edd4b602d08b9b817 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 24 Oct 2024 11:57:02 +0200 Subject: rust: macros: check that #[derive(Object)] requires #[repr(C)] Convert derive_object to the same pattern of first making a Result, and then doing .unwrap_or_else(Into::into) to support checking the validity of the input. Add is_c_repr to check that all QOM structs include a #[repr(C)] attribute. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api-macros/src/lib.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'rust') diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index 74a8bc7..160b283 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -32,18 +32,23 @@ fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), CompileError> { } } -#[proc_macro_derive(Object)] -pub fn derive_object(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let name = input.ident; +fn derive_object_or_error(input: DeriveInput) -> Result { + is_c_repr(&input, "#[derive(Object)]")?; - let expanded = quote! { + let name = &input.ident; + Ok(quote! { ::qemu_api::module_init! { MODULE_INIT_QOM => unsafe { ::qemu_api::bindings::type_register_static(&<#name as ::qemu_api::qom::ObjectImpl>::TYPE_INFO); } } - }; + }) +} + +#[proc_macro_derive(Object)] +pub fn derive_object(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let expanded = derive_object_or_error(input).unwrap_or_else(Into::into); TokenStream::from(expanded) } -- cgit v1.1 From e3ff5a17aa9fc2420197403e69876db48e390ee4 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 11 Dec 2024 11:48:44 +0100 Subject: rust: macros: check that the first field of a #[derive(Object)] struct is a ParentField Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api-macros/src/lib.rs | 46 ++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 19 deletions(-) (limited to 'rust') diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index 160b283..0f04cca 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -19,6 +19,27 @@ impl From for proc_macro2::TokenStream { } } +fn get_fields<'a>( + input: &'a DeriveInput, + msg: &str, +) -> Result<&'a Punctuated, CompileError> { + if let Data::Struct(s) = &input.data { + if let Fields::Named(fs) = &s.fields { + Ok(&fs.named) + } else { + Err(CompileError( + format!("Named fields required for {}", msg), + input.ident.span(), + )) + } + } else { + Err(CompileError( + format!("Struct required for {}", msg), + input.ident.span(), + )) + } +} + fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), CompileError> { let expected = parse_quote! { #[repr(C)] }; @@ -36,7 +57,12 @@ fn derive_object_or_error(input: DeriveInput) -> Result::ParentType>); + ::qemu_api::module_init! { MODULE_INIT_QOM => unsafe { ::qemu_api::bindings::type_register_static(&<#name as ::qemu_api::qom::ObjectImpl>::TYPE_INFO); @@ -53,30 +79,12 @@ pub fn derive_object(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } -fn get_fields(input: &DeriveInput) -> Result<&Punctuated, CompileError> { - if let Data::Struct(s) = &input.data { - if let Fields::Named(fs) = &s.fields { - Ok(&fs.named) - } else { - Err(CompileError( - "Cannot generate offsets for unnamed fields.".to_string(), - input.ident.span(), - )) - } - } else { - Err(CompileError( - "Cannot generate offsets for union or enum.".to_string(), - input.ident.span(), - )) - } -} - #[rustfmt::skip::macros(quote)] fn derive_offsets_or_error(input: DeriveInput) -> Result { is_c_repr(&input, "#[derive(offsets)]")?; let name = &input.ident; - let fields = get_fields(&input)?; + let fields = get_fields(&input, "#[derive(offsets)]")?; let field_names: Vec<&Ident> = fields.iter().map(|f| f.ident.as_ref().unwrap()).collect(); let field_types: Vec<&Type> = fields.iter().map(|f| &f.ty).collect(); let field_vis: Vec<&Visibility> = fields.iter().map(|f| &f.vis).collect(); -- cgit v1.1 From 33aa660575f7174752a7308229e5fc108921c2a6 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 11 Dec 2024 10:33:31 +0100 Subject: rust: qom: automatically use Drop trait to implement instance_finalize Replace the customizable INSTANCE_FINALIZE with a generic function that drops the Rust object. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/qom.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'rust') diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index 40d17a9..b0332ba 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -180,6 +180,16 @@ unsafe extern "C" fn rust_class_init>( T::class_init(unsafe { &mut *klass.cast::() }) } +unsafe extern "C" fn drop_object(obj: *mut Object) { + // SAFETY: obj is an instance of T, since drop_object is called + // from the QOM core function object_deinit() as the instance_finalize + // function for class T. Note that while object_deinit() will drop the + // superclass field separately after this function returns, `T` must + // implement the unsafe trait ObjectType; the safety rules for the + // trait mandate that the parent field is manually dropped. + unsafe { std::ptr::drop_in_place(obj.cast::()) } +} + /// Trait exposed by all structs corresponding to QOM objects. /// /// # Safety @@ -442,7 +452,6 @@ pub trait ObjectImpl: ObjectType + ClassInitImpl { /// Whether the object can be instantiated const ABSTRACT: bool = false; - const INSTANCE_FINALIZE: Option = None; /// Function that is called to initialize an object. The parent class will /// have already been initialized so the type is only responsible for @@ -478,7 +487,7 @@ pub trait ObjectImpl: ObjectType + ClassInitImpl { None => None, Some(_) => Some(rust_instance_post_init::), }, - instance_finalize: Self::INSTANCE_FINALIZE, + instance_finalize: Some(drop_object::), abstract_: Self::ABSTRACT, class_size: core::mem::size_of::(), class_init: Some(rust_class_init::), -- cgit v1.1 From d9434f29ca83e114fe02ed24c8ad2ccfa7ac3fe9 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 29 Nov 2024 11:38:59 +0100 Subject: rust: qom: move device_id to PL011 class side There is no need to monkeypatch DeviceId::Luminary into the already-initialized PL011State. Instead, now that we can define a class hierarchy, we can define PL011Class and make device_id a field in there. There is also no need anymore to have "Arm" as zero, so change DeviceId into a wrapper for the array; all it does is provide an Index implementation because arrays can only be indexed by usize. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 59 +++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 31 deletions(-) (limited to 'rust') diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 689202f..215f94a 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -5,7 +5,7 @@ use core::ptr::{addr_of_mut, NonNull}; use std::{ ffi::CStr, - os::raw::{c_int, c_uchar, c_uint, c_void}, + os::raw::{c_int, c_uint, c_void}, }; use qemu_api::{ @@ -14,7 +14,7 @@ use qemu_api::{ irq::InterruptSource, prelude::*, qdev::DeviceImpl, - qom::{ObjectImpl, ParentField}, + qom::{ClassInitImpl, ObjectImpl, ParentField}, }; use crate::{ @@ -33,27 +33,20 @@ const FBRD_MASK: u32 = 0x3f; /// QEMU sourced constant. pub const PL011_FIFO_DEPTH: u32 = 16; -#[derive(Clone, Copy, Debug)] -enum DeviceId { - #[allow(dead_code)] - Arm = 0, - Luminary, -} +#[derive(Clone, Copy)] +struct DeviceId(&'static [u8; 8]); impl std::ops::Index for DeviceId { - type Output = c_uchar; + type Output = u8; fn index(&self, idx: hwaddr) -> &Self::Output { - match self { - Self::Arm => &Self::PL011_ID_ARM[idx as usize], - Self::Luminary => &Self::PL011_ID_LUMINARY[idx as usize], - } + &self.0[idx as usize] } } impl DeviceId { - const PL011_ID_ARM: [c_uchar; 8] = [0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1]; - const PL011_ID_LUMINARY: [c_uchar; 8] = [0x11, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1]; + const ARM: Self = Self(&[0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1]); + const LUMINARY: Self = Self(&[0x11, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1]); } // FIFOs use 32-bit indices instead of usize, for compatibility with @@ -126,17 +119,28 @@ pub struct PL011State { pub clock: NonNull, #[doc(alias = "migrate_clk")] pub migrate_clock: bool, - /// The byte string that identifies the device. - device_id: DeviceId, } qom_isa!(PL011State : SysBusDevice, DeviceState, Object); +pub struct PL011Class { + parent_class: ::Class, + /// The byte string that identifies the device. + device_id: DeviceId, +} + unsafe impl ObjectType for PL011State { - type Class = ::Class; + type Class = PL011Class; const TYPE_NAME: &'static CStr = crate::TYPE_PL011; } +impl ClassInitImpl for PL011State { + fn class_init(klass: &mut PL011Class) { + klass.device_id = DeviceId::ARM; + >::class_init(&mut klass.parent_class); + } +} + impl ObjectImpl for PL011State { type ParentType = SysBusDevice; @@ -214,7 +218,8 @@ impl PL011State { let value = match RegisterOffset::try_from(offset) { Err(v) if (0x3f8..0x400).contains(&(v >> 2)) => { - u32::from(self.device_id[(offset - 0xfe0) >> 2]) + let device_id = self.get_class().device_id; + u32::from(device_id[(offset - 0xfe0) >> 2]) } Err(_) => { // qemu_log_mask(LOG_GUEST_ERROR, "pl011_read: Bad offset 0x%x\n", (int)offset); @@ -648,16 +653,10 @@ pub struct PL011Luminary { parent_obj: ParentField, } -impl PL011Luminary { - /// Initializes a pre-allocated, unitialized instance of `PL011Luminary`. - /// - /// # Safety - /// - /// We expect the FFI user of this function to pass a valid pointer, that - /// has the same size as [`PL011Luminary`]. We also expect the device is - /// readable/writeable from one thread at any time. - unsafe fn init(&mut self) { - self.parent_obj.device_id = DeviceId::Luminary; +impl ClassInitImpl for PL011Luminary { + fn class_init(klass: &mut PL011Class) { + klass.device_id = DeviceId::LUMINARY; + >::class_init(&mut klass.parent_class); } } @@ -670,8 +669,6 @@ unsafe impl ObjectType for PL011Luminary { impl ObjectImpl for PL011Luminary { type ParentType = PL011State; - - const INSTANCE_INIT: Option = Some(Self::init); } impl DeviceImpl for PL011Luminary {} -- cgit v1.1 From af68b41d403b81b18de07ebab0ca4c1025c94bf7 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 2 Dec 2024 13:16:19 +0100 Subject: rust: pl011: only leave embedded object initialization in instance_init Leave IRQ and MMIO initialization to instance_post_init. In Rust the two callbacks are more distinct, because only instance_post_init has a fully initialized object available. While at it, add a wrapper for sysbus_init_mmio so that accesses to the SysBusDevice correctly use shared references. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 18 ++++++++++-------- rust/qemu-api/src/sysbus.rs | 12 ++++++++++++ 2 files changed, 22 insertions(+), 8 deletions(-) (limited to 'rust') diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 215f94a..72a4cea 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -145,6 +145,7 @@ impl ObjectImpl for PL011State { type ParentType = SysBusDevice; const INSTANCE_INIT: Option = Some(Self::init); + const INSTANCE_POST_INIT: Option = Some(Self::post_init); } impl DeviceImpl for PL011State { @@ -183,14 +184,6 @@ impl PL011State { Self::TYPE_NAME.as_ptr(), 0x1000, ); - - let sbd: &mut SysBusDevice = self.upcast_mut(); - sysbus_init_mmio(sbd, addr_of_mut!(self.iomem)); - } - - for irq in self.interrupts.iter() { - let sbd: &SysBusDevice = self.upcast(); - sbd.init_irq(irq); } // SAFETY: @@ -213,6 +206,15 @@ impl PL011State { } } + fn post_init(&mut self) { + let sbd: &SysBusDevice = self.upcast(); + + sbd.init_mmio(&self.iomem); + for irq in self.interrupts.iter() { + sbd.init_irq(irq); + } + } + pub fn read(&mut self, offset: hwaddr, _size: c_uint) -> std::ops::ControlFlow { use RegisterOffset::*; diff --git a/rust/qemu-api/src/sysbus.rs b/rust/qemu-api/src/sysbus.rs index 8193734..b96eaaf 100644 --- a/rust/qemu-api/src/sysbus.rs +++ b/rust/qemu-api/src/sysbus.rs @@ -38,6 +38,18 @@ impl SysBusDevice { addr_of!(*self) as *mut _ } + /// Expose a memory region to the board so that it can give it an address + /// in guest memory. Note that the ordering of calls to `init_mmio` is + /// important, since whoever creates the sysbus device will refer to the + /// region with a number that corresponds to the order of calls to + /// `init_mmio`. + pub fn init_mmio(&self, iomem: &bindings::MemoryRegion) { + assert!(bql_locked()); + unsafe { + bindings::sysbus_init_mmio(self.as_mut_ptr(), addr_of!(*iomem) as *mut _); + } + } + /// Expose an interrupt source outside the device as a qdev GPIO output. /// Note that the ordering of calls to `init_irq` is important, since /// whoever creates the sysbus device will refer to the interrupts with -- cgit v1.1 From 22a18f0a98a1ca5d0cd8fff1d81cc74a269083de Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 29 Nov 2024 08:48:07 +0100 Subject: rust: qom: make INSTANCE_POST_INIT take a shared reference Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 4 ++-- rust/qemu-api/src/qom.rs | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) (limited to 'rust') diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 72a4cea..6792d13 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -145,7 +145,7 @@ impl ObjectImpl for PL011State { type ParentType = SysBusDevice; const INSTANCE_INIT: Option = Some(Self::init); - const INSTANCE_POST_INIT: Option = Some(Self::post_init); + const INSTANCE_POST_INIT: Option = Some(Self::post_init); } impl DeviceImpl for PL011State { @@ -206,7 +206,7 @@ impl PL011State { } } - fn post_init(&mut self) { + fn post_init(&self) { let sbd: &SysBusDevice = self.upcast(); sbd.init_mmio(&self.iomem); diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index b0332ba..97901fb 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -163,11 +163,7 @@ unsafe extern "C" fn rust_instance_post_init(obj: *mut Object) { // SAFETY: obj is an instance of T, since rust_instance_post_init // is called from QOM core as the instance_post_init function // for class T - // - // FIXME: it's not really guaranteed that there are no backpointers to - // obj; it's quite possible that they have been created by instance_init(). - // The receiver should be &self, not &mut self. - T::INSTANCE_POST_INIT.unwrap()(unsafe { &mut *obj.cast::() }) + T::INSTANCE_POST_INIT.unwrap()(unsafe { &*obj.cast::() }) } unsafe extern "C" fn rust_class_init>( @@ -463,7 +459,7 @@ pub trait ObjectImpl: ObjectType + ClassInitImpl { /// Function that is called to finish initialization of an object, once /// `INSTANCE_INIT` functions have been called. - const INSTANCE_POST_INIT: Option = None; + const INSTANCE_POST_INIT: Option = None; /// Called on descendent classes after all parent class initialization /// has occurred, but before the class itself is initialized. This -- cgit v1.1 From a3b620fff73ec762f2a77a077eb8389dddab4833 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 4 Dec 2024 08:57:27 +0100 Subject: rust: qemu-api-macros: extend error reporting facility to parse errors Generalize the CompileError tuple to an enum, that can be either an error message or a parse error from syn. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api-macros/src/lib.rs | 27 ++++++++++----------------- rust/qemu-api-macros/src/utils.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 17 deletions(-) create mode 100644 rust/qemu-api-macros/src/utils.rs (limited to 'rust') diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index 0f04cca..539c48d 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -3,57 +3,50 @@ // SPDX-License-Identifier: GPL-2.0-or-later use proc_macro::TokenStream; -use proc_macro2::Span; -use quote::{quote, quote_spanned}; +use quote::quote; use syn::{ parse_macro_input, parse_quote, punctuated::Punctuated, token::Comma, Data, DeriveInput, Field, Fields, Ident, Type, Visibility, }; -struct CompileError(String, Span); - -impl From for proc_macro2::TokenStream { - fn from(err: CompileError) -> Self { - let CompileError(msg, span) = err; - quote_spanned! { span => compile_error!(#msg); } - } -} +mod utils; +use utils::MacroError; fn get_fields<'a>( input: &'a DeriveInput, msg: &str, -) -> Result<&'a Punctuated, CompileError> { +) -> Result<&'a Punctuated, MacroError> { if let Data::Struct(s) = &input.data { if let Fields::Named(fs) = &s.fields { Ok(&fs.named) } else { - Err(CompileError( + Err(MacroError::Message( format!("Named fields required for {}", msg), input.ident.span(), )) } } else { - Err(CompileError( + Err(MacroError::Message( format!("Struct required for {}", msg), input.ident.span(), )) } } -fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), CompileError> { +fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), MacroError> { let expected = parse_quote! { #[repr(C)] }; if input.attrs.iter().any(|attr| attr == &expected) { Ok(()) } else { - Err(CompileError( + Err(MacroError::Message( format!("#[repr(C)] required for {}", msg), input.ident.span(), )) } } -fn derive_object_or_error(input: DeriveInput) -> Result { +fn derive_object_or_error(input: DeriveInput) -> Result { is_c_repr(&input, "#[derive(Object)]")?; let name = &input.ident; @@ -80,7 +73,7 @@ pub fn derive_object(input: TokenStream) -> TokenStream { } #[rustfmt::skip::macros(quote)] -fn derive_offsets_or_error(input: DeriveInput) -> Result { +fn derive_offsets_or_error(input: DeriveInput) -> Result { is_c_repr(&input, "#[derive(offsets)]")?; let name = &input.ident; diff --git a/rust/qemu-api-macros/src/utils.rs b/rust/qemu-api-macros/src/utils.rs new file mode 100644 index 0000000..02c91ae --- /dev/null +++ b/rust/qemu-api-macros/src/utils.rs @@ -0,0 +1,26 @@ +// Procedural macro utilities. +// Author(s): Paolo Bonzini +// SPDX-License-Identifier: GPL-2.0-or-later + +use proc_macro2::Span; +use quote::quote_spanned; + +pub enum MacroError { + Message(String, Span), + ParseError(syn::Error), +} + +impl From for MacroError { + fn from(err: syn::Error) -> Self { + MacroError::ParseError(err) + } +} + +impl From for proc_macro2::TokenStream { + fn from(err: MacroError) -> Self { + match err { + MacroError::Message(msg, span) => quote_spanned! { span => compile_error!(#msg); }, + MacroError::ParseError(err) => err.into_compile_error(), + } + } +} -- cgit v1.1 From 809c703a60240125eec16ec134f60793134b4f61 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 4 Dec 2024 08:58:46 +0100 Subject: rust: qemu-api-macros: add automatic TryFrom/TryInto derivation This is going to be fairly common. Using a custom procedural macro provides better error messages and automatically finds the right type. Note that this is different from the same-named macro in the derive_more crate. That one provides conversion from e.g. tuples to enums with tuple variants, not from integers to enums. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/lib.rs | 28 +--------------- rust/qemu-api-macros/src/lib.rs | 74 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 73 insertions(+), 29 deletions(-) (limited to 'rust') diff --git a/rust/hw/char/pl011/src/lib.rs b/rust/hw/char/pl011/src/lib.rs index 69064d6..0a89d39 100644 --- a/rust/hw/char/pl011/src/lib.rs +++ b/rust/hw/char/pl011/src/lib.rs @@ -45,7 +45,7 @@ pub const TYPE_PL011_LUMINARY: &::std::ffi::CStr = c_str!("pl011_luminary"); #[doc(alias = "offset")] #[allow(non_camel_case_types)] #[repr(u64)] -#[derive(Debug)] +#[derive(Debug, qemu_api_macros::TryInto)] pub enum RegisterOffset { /// Data Register /// @@ -102,32 +102,6 @@ pub enum RegisterOffset { //Reserved = 0x04C, } -impl core::convert::TryFrom for RegisterOffset { - type Error = u64; - - fn try_from(value: u64) -> Result { - macro_rules! case { - ($($discriminant:ident),*$(,)*) => { - /* check that matching on all macro arguments compiles, which means we are not - * missing any enum value; if the type definition ever changes this will stop - * compiling. - */ - const fn _assert_exhaustive(val: RegisterOffset) { - match val { - $(RegisterOffset::$discriminant => (),)* - } - } - - match value { - $(x if x == Self::$discriminant as u64 => Ok(Self::$discriminant),)* - _ => Err(value), - } - } - } - case! { DR, RSR, FR, FBRD, ILPR, IBRD, LCR_H, CR, FLS, IMSC, RIS, MIS, ICR, DMACR } - } -} - pub mod registers { //! Device registers exposed as typed structs which are backed by arbitrary //! integer bitmaps. [`Data`], [`Control`], [`LineControl`], etc. diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index 539c48d..7ec2182 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -5,8 +5,8 @@ use proc_macro::TokenStream; use quote::quote; use syn::{ - parse_macro_input, parse_quote, punctuated::Punctuated, token::Comma, Data, DeriveInput, Field, - Fields, Ident, Type, Visibility, + parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Data, + DeriveInput, Field, Fields, Ident, Meta, Path, Token, Type, Variant, Visibility, }; mod utils; @@ -98,3 +98,73 @@ pub fn derive_offsets(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } + +#[allow(non_snake_case)] +fn get_repr_uN(input: &DeriveInput, msg: &str) -> Result { + let repr = input.attrs.iter().find(|attr| attr.path().is_ident("repr")); + if let Some(repr) = repr { + let nested = repr.parse_args_with(Punctuated::::parse_terminated)?; + for meta in nested { + match meta { + Meta::Path(path) if path.is_ident("u8") => return Ok(path), + Meta::Path(path) if path.is_ident("u16") => return Ok(path), + Meta::Path(path) if path.is_ident("u32") => return Ok(path), + Meta::Path(path) if path.is_ident("u64") => return Ok(path), + _ => {} + } + } + } + + Err(MacroError::Message( + format!("#[repr(u8/u16/u32/u64) required for {}", msg), + input.ident.span(), + )) +} + +fn get_variants(input: &DeriveInput) -> Result<&Punctuated, MacroError> { + if let Data::Enum(e) = &input.data { + if let Some(v) = e.variants.iter().find(|v| v.fields != Fields::Unit) { + return Err(MacroError::Message( + "Cannot derive TryInto for enum with non-unit variants.".to_string(), + v.fields.span(), + )); + } + Ok(&e.variants) + } else { + Err(MacroError::Message( + "Cannot derive TryInto for union or struct.".to_string(), + input.ident.span(), + )) + } +} + +#[rustfmt::skip::macros(quote)] +fn derive_tryinto_or_error(input: DeriveInput) -> Result { + let repr = get_repr_uN(&input, "#[derive(TryInto)]")?; + + let name = &input.ident; + let variants = get_variants(&input)?; + let discriminants: Vec<&Ident> = variants.iter().map(|f| &f.ident).collect(); + + Ok(quote! { + impl core::convert::TryFrom<#repr> for #name { + type Error = #repr; + + fn try_from(value: #repr) -> Result { + #(const #discriminants: #repr = #name::#discriminants as #repr;)*; + match value { + #(#discriminants => Ok(Self::#discriminants),)* + _ => Err(value), + } + } + } + }) +} + +#[proc_macro_derive(TryInto)] +pub fn derive_tryinto(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let expanded = derive_tryinto_or_error(input).unwrap_or_else(Into::into); + + TokenStream::from(expanded) +} -- cgit v1.1 From 559a779c6aa309853474240b01fcc2beff1f04ca Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 29 Nov 2024 10:46:44 +0100 Subject: rust: qdev: expose inherited methods to subclasses of SysBusDevice The ObjectDeref trait now provides all the magic that is required to fake inheritance. Replace the "impl SysBusDevice" block of qemu_api::sysbus with a trait, so that sysbus_init_irq() can be invoked as "self.init_irq()" without any intermediate upcast. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 6 ++---- rust/qemu-api/src/irq.rs | 3 +-- rust/qemu-api/src/prelude.rs | 2 ++ rust/qemu-api/src/sysbus.rs | 17 +++++++++-------- 4 files changed, 14 insertions(+), 14 deletions(-) (limited to 'rust') diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 6792d13..994c2fc 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -207,11 +207,9 @@ impl PL011State { } fn post_init(&self) { - let sbd: &SysBusDevice = self.upcast(); - - sbd.init_mmio(&self.iomem); + self.init_mmio(&self.iomem); for irq in self.interrupts.iter() { - sbd.init_irq(irq); + self.init_irq(irq); } } diff --git a/rust/qemu-api/src/irq.rs b/rust/qemu-api/src/irq.rs index 6258141..378e520 100644 --- a/rust/qemu-api/src/irq.rs +++ b/rust/qemu-api/src/irq.rs @@ -24,8 +24,7 @@ use crate::{ /// /// Interrupts are implemented as a pointer to the interrupt "sink", which has /// type [`IRQState`]. A device exposes its source as a QOM link property using -/// a function such as -/// [`SysBusDevice::init_irq`](crate::sysbus::SysBusDevice::init_irq), and +/// a function such as [`SysBusDeviceMethods::init_irq`], and /// initially leaves the pointer to a NULL value, representing an unconnected /// interrupt. To connect it, whoever creates the device fills the pointer with /// the sink's `IRQState *`, for example using `sysbus_connect_irq`. Because diff --git a/rust/qemu-api/src/prelude.rs b/rust/qemu-api/src/prelude.rs index 6f32dee..4ea70b9 100644 --- a/rust/qemu-api/src/prelude.rs +++ b/rust/qemu-api/src/prelude.rs @@ -16,3 +16,5 @@ pub use crate::qom::ObjectMethods; pub use crate::qom::ObjectType; pub use crate::qom_isa; + +pub use crate::sysbus::SysBusDeviceMethods; diff --git a/rust/qemu-api/src/sysbus.rs b/rust/qemu-api/src/sysbus.rs index b96eaaf..e6762b5 100644 --- a/rust/qemu-api/src/sysbus.rs +++ b/rust/qemu-api/src/sysbus.rs @@ -32,18 +32,17 @@ where } } -impl SysBusDevice { - /// Return `self` cast to a mutable pointer, for use in calls to C code. - const fn as_mut_ptr(&self) -> *mut SysBusDevice { - addr_of!(*self) as *mut _ - } - +/// Trait for methods of [`SysBusDevice`] and its subclasses. +pub trait SysBusDeviceMethods: ObjectDeref +where + Self::Target: IsA, +{ /// Expose a memory region to the board so that it can give it an address /// in guest memory. Note that the ordering of calls to `init_mmio` is /// important, since whoever creates the sysbus device will refer to the /// region with a number that corresponds to the order of calls to /// `init_mmio`. - pub fn init_mmio(&self, iomem: &bindings::MemoryRegion) { + fn init_mmio(&self, iomem: &bindings::MemoryRegion) { assert!(bql_locked()); unsafe { bindings::sysbus_init_mmio(self.as_mut_ptr(), addr_of!(*iomem) as *mut _); @@ -54,10 +53,12 @@ impl SysBusDevice { /// Note that the ordering of calls to `init_irq` is important, since /// whoever creates the sysbus device will refer to the interrupts with /// a number that corresponds to the order of calls to `init_irq`. - pub fn init_irq(&self, irq: &InterruptSource) { + fn init_irq(&self, irq: &InterruptSource) { assert!(bql_locked()); unsafe { bindings::sysbus_init_irq(self.as_mut_ptr(), irq.as_ptr()); } } } + +impl SysBusDeviceMethods for R where R::Target: IsA {} -- cgit v1.1