From 5f9976486970b0fec50ff4c07da7af620cd7d0a0 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Wed, 18 Dec 2024 07:42:44 -0600 Subject: rust/qemu-api: Use device_class_set_props_n This means we can update declare_properties to drop the zero terminator at the end of the array as well. Reviewed-by: Paolo Bonzini Signed-off-by: Richard Henderson Tested-by: Lei Yang Link: https://lore.kernel.org/r/20241218134251.4724-18-richard.henderson@linaro.org Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/device_class.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'rust/qemu-api/src') diff --git a/rust/qemu-api/src/device_class.rs b/rust/qemu-api/src/device_class.rs index 03d03feee8..c98f0b2c7d 100644 --- a/rust/qemu-api/src/device_class.rs +++ b/rust/qemu-api/src/device_class.rs @@ -7,7 +7,6 @@ use std::{ffi::CStr, os::raw::c_void}; use crate::{ bindings::{self, DeviceClass, DeviceState, Error, ObjectClass, Property, VMStateDescription}, prelude::*, - zeroable::Zeroable, }; /// Trait providing the contents of [`DeviceClass`]. @@ -31,7 +30,7 @@ pub trait DeviceImpl { /// device. Not a `const` because referencing statics in constants /// is unstable until Rust 1.83.0. fn properties() -> &'static [Property] { - &[Zeroable::ZERO; 1] + &[] } /// A `VMStateDescription` providing the migration format for the device @@ -87,7 +86,10 @@ pub unsafe extern "C" fn rust_device_class_init( if let Some(vmsd) = ::vmsd() { dc.vmsd = vmsd; } - bindings::device_class_set_props(dc, ::properties().as_ptr()); + let prop = ::properties(); + if !prop.is_empty() { + bindings::device_class_set_props_n(dc, prop.as_ptr(), prop.len()); + } } } @@ -134,7 +136,7 @@ macro_rules! define_property { macro_rules! declare_properties { ($ident:ident, $($prop:expr),*$(,)*) => { pub static $ident: [$crate::bindings::Property; { - let mut len = 1; + let mut len = 0; $({ _ = stringify!($prop); len += 1; @@ -142,7 +144,6 @@ macro_rules! declare_properties { len }] = [ $($prop),*, - $crate::zeroable::Zeroable::ZERO, ]; }; } -- cgit 1.4.1 From 6dd818fbbbe3efc63889e7d811ac6b70e788c629 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 29 Nov 2024 15:19:23 +0100 Subject: rust: qom: put class_init together from multiple ClassInitImpl<> Parameterize the implementation of ClassInitImpl so that it is possible to call up the chain of implementations, one superclass at a time starting at ClassInitImpl. In order to avoid having to implement (for example) ClassInitImpl, also remove the dummy PL011Class and PL011LuminaryClass structs and specify the same ObjectType::Class as the superclass. In the future this default behavior can be handled by a procedural macro, by looking at the first field in the struct. Note that the new trait is safe: the calls are started by rust_class_init<>(), which is not public and can convert the class pointer to a Rust reference. Since CLASS_BASE_INIT applies to the type that is being defined, and only to it, move it to ObjectImpl. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 19 +------ rust/qemu-api/src/definitions.rs | 111 ++++++++++++++++++++++++++++++-------- rust/qemu-api/src/device_class.rs | 50 ++++++----------- rust/qemu-api/src/sysbus.rs | 18 ++++++- rust/qemu-api/tests/tests.rs | 9 +--- 5 files changed, 127 insertions(+), 80 deletions(-) (limited to 'rust/qemu-api/src') diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 3e29442a62..d9e9f35f45 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -13,7 +13,6 @@ use qemu_api::{ c_str, definitions::ObjectImpl, device_class::DeviceImpl, - impl_device_class, irq::InterruptSource, prelude::*, }; @@ -108,7 +107,7 @@ pub struct PL011State { } unsafe impl ObjectType for PL011State { - type Class = PL011Class; + type Class = ::Class; const TYPE_NAME: &'static CStr = crate::TYPE_PL011; } @@ -118,11 +117,6 @@ impl ObjectImpl for PL011State { const INSTANCE_INIT: Option = Some(Self::init); } -#[repr(C)] -pub struct PL011Class { - _inner: [u8; 0], -} - impl DeviceImpl for PL011State { fn properties() -> &'static [Property] { &device_class::PL011_PROPERTIES @@ -134,8 +128,6 @@ impl DeviceImpl for PL011State { const RESET: Option = Some(Self::reset); } -impl_device_class!(PL011State); - impl PL011State { /// Initializes a pre-allocated, unitialized instance of `PL011State`. /// @@ -627,11 +619,6 @@ pub struct PL011Luminary { parent_obj: PL011State, } -#[repr(C)] -pub struct PL011LuminaryClass { - _inner: [u8; 0], -} - impl PL011Luminary { /// Initializes a pre-allocated, unitialized instance of `PL011Luminary`. /// @@ -646,7 +633,7 @@ impl PL011Luminary { } unsafe impl ObjectType for PL011Luminary { - type Class = PL011LuminaryClass; + type Class = ::Class; const TYPE_NAME: &'static CStr = crate::TYPE_PL011_LUMINARY; } @@ -657,5 +644,3 @@ impl ObjectImpl for PL011Luminary { } impl DeviceImpl for PL011Luminary {} - -impl_device_class!(PL011Luminary); diff --git a/rust/qemu-api/src/definitions.rs b/rust/qemu-api/src/definitions.rs index df91a2e31a..13f8f6fd2a 100644 --- a/rust/qemu-api/src/definitions.rs +++ b/rust/qemu-api/src/definitions.rs @@ -26,6 +26,16 @@ unsafe extern "C" fn rust_instance_post_init(obj: *mut Object) { T::INSTANCE_POST_INIT.unwrap()(unsafe { &mut *obj.cast::() }) } +unsafe extern "C" fn rust_class_init>( + klass: *mut ObjectClass, + _data: *mut c_void, +) { + // SAFETY: klass is a T::Class, since rust_class_init + // is called from QOM core as the class_init function + // for class T + T::class_init(unsafe { &mut *klass.cast::() }) +} + /// Trait exposed by all structs corresponding to QOM objects. /// /// # Safety @@ -50,7 +60,8 @@ unsafe extern "C" fn rust_instance_post_init(obj: *mut Object) { /// - likewise, the first field of the `Class` must be of the class struct /// corresponding to the superclass, which is `ObjectImpl::ParentType::Class`. pub unsafe trait ObjectType: Sized { - /// The QOM class object corresponding to this struct. Not used yet. + /// The QOM class object corresponding to this struct. This is used + /// to automatically generate a `class_init` method. type Class; /// The name of the type, which can be passed to `object_new()` to @@ -59,7 +70,7 @@ pub unsafe trait ObjectType: Sized { } /// Trait a type must implement to be registered with QEMU. -pub trait ObjectImpl: ObjectType + ClassInitImpl { +pub trait ObjectImpl: ObjectType + ClassInitImpl { /// The parent of the type. This should match the first field of /// the struct that implements `ObjectImpl`: type ParentType: ObjectType; @@ -80,6 +91,15 @@ pub trait ObjectImpl: ObjectType + ClassInitImpl { /// `INSTANCE_INIT` functions have been called. 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 + /// is only useful if a class is not a leaf, and can be used to undo + /// the effects of copying the contents of the parent's class struct + /// to the descendants. + const CLASS_BASE_INIT: Option< + unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void), + > = None; + const TYPE_INFO: TypeInfo = TypeInfo { name: Self::TYPE_NAME.as_ptr(), parent: Self::ParentType::TYPE_NAME.as_ptr(), @@ -96,37 +116,86 @@ pub trait ObjectImpl: ObjectType + ClassInitImpl { instance_finalize: Self::INSTANCE_FINALIZE, abstract_: Self::ABSTRACT, class_size: core::mem::size_of::(), - class_init: ::CLASS_INIT, - class_base_init: ::CLASS_BASE_INIT, + class_init: Some(rust_class_init::), + class_base_init: Self::CLASS_BASE_INIT, class_data: core::ptr::null_mut(), interfaces: core::ptr::null_mut(), }; } -/// Trait used to fill in a class struct. +/// Internal trait used to automatically fill in a class struct. /// /// Each QOM class that has virtual methods describes them in a /// _class struct_. Class structs include a parent field corresponding /// to the vtable of the parent class, all the way up to [`ObjectClass`]. -/// Each QOM type has one such class struct. +/// Each QOM type has one such class struct; this trait takes care of +/// initializing the `T` part of the class struct, for the type that +/// implements the trait. +/// +/// Each struct will implement this trait with `T` equal to each +/// superclass. For example, a device should implement at least +/// `ClassInitImpl<`[`DeviceClass`](crate::bindings::DeviceClass)`>`. +/// Such implementations are made in one of two ways. +/// +/// For most superclasses, `ClassInitImpl` is provided by the `qemu-api` +/// crate itself. The Rust implementation of methods will come from a +/// trait like [`ObjectImpl`] or +/// [`DeviceImpl`](crate::device_class::DeviceImpl), and `ClassInitImpl` is +/// provided by blanket implementations that operate on all implementors of the +/// `*Impl`* trait. For example: +/// +/// ```ignore +/// impl ClassInitImpl for T +/// where +/// T: DeviceImpl, +/// ``` /// -/// The Rust implementation of methods will usually come from a trait -/// like [`ObjectImpl`] or [`DeviceImpl`](crate::device_class::DeviceImpl). -pub trait ClassInitImpl { - /// Function that is called after all parent class initialization - /// has occurred. On entry, the virtual method pointers are set to +/// The other case is when manual implementation of the trait is needed. +/// This covers the following cases: +/// +/// * if a class implements a QOM interface, the Rust code _has_ to define its +/// own class struct `FooClass` and implement `ClassInitImpl`. +/// `ClassInitImpl`'s `class_init` method will then forward to +/// multiple other `class_init`s, for the interfaces as well as the +/// superclass. (Note that there is no Rust example yet for using interfaces). +/// +/// * for classes implemented outside the ``qemu-api`` crate, it's not possible +/// to add blanket implementations like the above one, due to orphan rules. In +/// that case, the easiest solution is to implement +/// `ClassInitImpl` for each subclass and not have a +/// `YourSuperclassImpl` trait at all. +/// +/// ```ignore +/// impl ClassInitImpl for YourSubclass { +/// fn class_init(klass: &mut YourSuperclass) { +/// klass.some_method = Some(Self::some_method); +/// >::class_init(&mut klass.parent_class); +/// } +/// } +/// ``` +/// +/// While this method incurs a small amount of code duplication, +/// it is generally limited to the recursive call on the last line. +/// This is because classes defined in Rust do not need the same +/// glue code that is needed when the classes are defined in C code. +/// You may consider using a macro if you have many subclasses. +pub trait ClassInitImpl { + /// Initialize `klass` to point to the virtual method implementations + /// for `Self`. On entry, the virtual method pointers are set to /// the default values coming from the parent classes; the function /// can change them to override virtual methods of a parent class. - const CLASS_INIT: Option; - - /// Called on descendent classes after all parent class initialization - /// has occurred, but before the class itself is initialized. This - /// is only useful if a class is not a leaf, and can be used to undo - /// the effects of copying the contents of the parent's class struct - /// to the descendants. - const CLASS_BASE_INIT: Option< - unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void), - >; + /// + /// The virtual method implementations usually come from another + /// trait, for example [`DeviceImpl`](crate::device_class::DeviceImpl) + /// when `T` is [`DeviceClass`](crate::bindings::DeviceClass). + /// + /// On entry, `klass`'s parent class is initialized, while the other fields + /// are all zero; it is therefore assumed that all fields in `T` can be + /// zeroed, otherwise it would not be possible to provide the class as a + /// `&mut T`. TODO: add a bound of [`Zeroable`](crate::zeroable::Zeroable) + /// to T; this is more easily done once Zeroable does not require a manual + /// implementation (Rust 1.75.0). + fn class_init(klass: &mut T); } #[macro_export] diff --git a/rust/qemu-api/src/device_class.rs b/rust/qemu-api/src/device_class.rs index c98f0b2c7d..dcec548829 100644 --- a/rust/qemu-api/src/device_class.rs +++ b/rust/qemu-api/src/device_class.rs @@ -2,10 +2,11 @@ // Author(s): Manos Pitsidianakis // SPDX-License-Identifier: GPL-2.0-or-later -use std::{ffi::CStr, os::raw::c_void}; +use std::ffi::CStr; use crate::{ - bindings::{self, DeviceClass, DeviceState, Error, ObjectClass, Property, VMStateDescription}, + bindings::{self, DeviceClass, DeviceState, Error, Property, VMStateDescription}, + definitions::ClassInitImpl, prelude::*, }; @@ -44,7 +45,7 @@ pub trait DeviceImpl { /// # Safety /// /// This function is only called through the QOM machinery and -/// the `impl_device_class!` macro. +/// used by the `ClassInitImpl` trait. /// We expect the FFI user of this function to pass a valid pointer that /// can be downcasted to type `T`. We also expect the device is /// readable/writeable from one thread at any time. @@ -65,48 +66,31 @@ unsafe extern "C" fn rust_reset_fn(dev: *mut DeviceState) { T::RESET.unwrap()(unsafe { &mut *state }); } -/// # Safety -/// -/// We expect the FFI user of this function to pass a valid pointer that -/// can be downcasted to type `DeviceClass`, because `T` implements -/// `DeviceImpl`. -pub unsafe extern "C" fn rust_device_class_init( - klass: *mut ObjectClass, - _: *mut c_void, -) { - let mut dc = ::core::ptr::NonNull::new(klass.cast::()).unwrap(); - unsafe { - let dc = dc.as_mut(); +impl ClassInitImpl for T +where + T: DeviceImpl, +{ + fn class_init(dc: &mut DeviceClass) { if ::REALIZE.is_some() { dc.realize = Some(rust_realize_fn::); } if ::RESET.is_some() { - bindings::device_class_set_legacy_reset(dc, Some(rust_reset_fn::)); + unsafe { + bindings::device_class_set_legacy_reset(dc, Some(rust_reset_fn::)); + } } if let Some(vmsd) = ::vmsd() { dc.vmsd = vmsd; } let prop = ::properties(); if !prop.is_empty() { - bindings::device_class_set_props_n(dc, prop.as_ptr(), prop.len()); + unsafe { + bindings::device_class_set_props_n(dc, prop.as_ptr(), prop.len()); + } } } } -#[macro_export] -macro_rules! impl_device_class { - ($type:ty) => { - impl $crate::definitions::ClassInitImpl for $type { - const CLASS_INIT: Option< - unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut ::std::os::raw::c_void), - > = Some($crate::device_class::rust_device_class_init::<$type>); - const CLASS_BASE_INIT: Option< - unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut ::std::os::raw::c_void), - > = None; - } - }; -} - #[macro_export] macro_rules! define_property { ($name:expr, $state:ty, $field:ident, $prop:expr, $type:ty, default = $defval:expr$(,)*) => { @@ -148,8 +132,8 @@ macro_rules! declare_properties { }; } -unsafe impl ObjectType for bindings::DeviceState { - type Class = bindings::DeviceClass; +unsafe impl ObjectType for DeviceState { + type Class = DeviceClass; const TYPE_NAME: &'static CStr = unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_DEVICE) }; } diff --git a/rust/qemu-api/src/sysbus.rs b/rust/qemu-api/src/sysbus.rs index 5ee068541c..5d15b31740 100644 --- a/rust/qemu-api/src/sysbus.rs +++ b/rust/qemu-api/src/sysbus.rs @@ -6,7 +6,13 @@ use std::{ffi::CStr, ptr::addr_of}; pub use bindings::{SysBusDevice, SysBusDeviceClass}; -use crate::{bindings, cell::bql_locked, irq::InterruptSource, prelude::*}; +use crate::{ + bindings::{self, DeviceClass}, + cell::bql_locked, + definitions::ClassInitImpl, + irq::InterruptSource, + prelude::*, +}; unsafe impl ObjectType for SysBusDevice { type Class = SysBusDeviceClass; @@ -14,6 +20,16 @@ unsafe impl ObjectType for SysBusDevice { unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_SYS_BUS_DEVICE) }; } +// TODO: add SysBusDeviceImpl +impl ClassInitImpl for T +where + T: ClassInitImpl, +{ + fn class_init(sdc: &mut SysBusDeviceClass) { + >::class_init(&mut sdc.parent_class); + } +} + impl SysBusDevice { /// Return `self` cast to a mutable pointer, for use in calls to C code. const fn as_mut_ptr(&self) -> *mut SysBusDevice { diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index 278efe967f..ed3a555e76 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -26,11 +26,6 @@ fn test_device_decl_macros() { pub migrate_clock: bool, } - #[repr(C)] - pub struct DummyClass { - pub _parent: DeviceClass, - } - declare_properties! { DUMMY_PROPERTIES, define_property!( @@ -43,7 +38,7 @@ fn test_device_decl_macros() { } unsafe impl ObjectType for DummyState { - type Class = DummyClass; + type Class = ::Class; const TYPE_NAME: &'static CStr = c_str!("dummy"); } @@ -61,8 +56,6 @@ fn test_device_decl_macros() { } } - impl_device_class!(DummyState); - unsafe { module_call_init(module_init_type::MODULE_INIT_QOM); object_unref(object_new(DummyState::TYPE_NAME.as_ptr()).cast()); -- cgit 1.4.1 From cb36da9bd84076470f36da56542e85a2436e3d95 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 29 Oct 2024 15:00:26 +0100 Subject: rust: qom: add possibility of overriding unparent Add a blanket definition of ClassInitImpl that thunks ObjectImpl::UNPARENT and overrides it in ObjectClass if it is not None. ClassInitImpl can now call its superclass's ClassInitImpl, so that the C and Rust hierarchies match more closely. This is mostly done as an example of implementing the metaclass hierarchy under ClassInitImpl. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/definitions.rs | 44 ++++++++++++++++++++++++++++++++++++--- rust/qemu-api/src/device_class.rs | 6 ++++-- 2 files changed, 45 insertions(+), 5 deletions(-) (limited to 'rust/qemu-api/src') diff --git a/rust/qemu-api/src/definitions.rs b/rust/qemu-api/src/definitions.rs index 13f8f6fd2a..a2481c1fe7 100644 --- a/rust/qemu-api/src/definitions.rs +++ b/rust/qemu-api/src/definitions.rs @@ -6,7 +6,7 @@ use std::{ffi::CStr, os::raw::c_void}; -use crate::bindings::{Object, ObjectClass, TypeInfo}; +use crate::bindings::{self, Object, ObjectClass, TypeInfo}; unsafe extern "C" fn rust_instance_init(obj: *mut Object) { // SAFETY: obj is an instance of T, since rust_instance_init @@ -121,6 +121,9 @@ pub trait ObjectImpl: ObjectType + ClassInitImpl { class_data: core::ptr::null_mut(), interfaces: core::ptr::null_mut(), }; + + // methods on ObjectClass + const UNPARENT: Option = None; } /// Internal trait used to automatically fill in a class struct. @@ -134,7 +137,8 @@ pub trait ObjectImpl: ObjectType + ClassInitImpl { /// /// Each struct will implement this trait with `T` equal to each /// superclass. For example, a device should implement at least -/// `ClassInitImpl<`[`DeviceClass`](crate::bindings::DeviceClass)`>`. +/// `ClassInitImpl<`[`DeviceClass`](crate::bindings::DeviceClass)`>` and +/// `ClassInitImpl<`[`ObjectClass`](crate::bindings::ObjectClass)`>`. /// Such implementations are made in one of two ways. /// /// For most superclasses, `ClassInitImpl` is provided by the `qemu-api` @@ -147,9 +151,13 @@ pub trait ObjectImpl: ObjectType + ClassInitImpl { /// ```ignore /// impl ClassInitImpl for T /// where -/// T: DeviceImpl, +/// T: ClassInitImpl + DeviceImpl, /// ``` /// +/// The bound on `ClassInitImpl` is needed so that, +/// after initializing the `DeviceClass` part of the class struct, +/// the parent [`ObjectClass`] is initialized as well. +/// /// The other case is when manual implementation of the trait is needed. /// This covers the following cases: /// @@ -235,3 +243,33 @@ macro_rules! module_init { } }; } + +/// # Safety +/// +/// We expect the FFI user of this function to pass a valid pointer that +/// can be downcasted to type `T`. We also expect the device is +/// readable/writeable from one thread at any time. +unsafe extern "C" fn rust_unparent_fn(dev: *mut Object) { + unsafe { + assert!(!dev.is_null()); + let state = core::ptr::NonNull::new_unchecked(dev.cast::()); + T::UNPARENT.unwrap()(state.as_ref()); + } +} + +impl ClassInitImpl for T +where + T: ObjectImpl, +{ + fn class_init(oc: &mut ObjectClass) { + if ::UNPARENT.is_some() { + oc.unparent = Some(rust_unparent_fn::); + } + } +} + +unsafe impl ObjectType for Object { + type Class = ObjectClass; + const TYPE_NAME: &'static CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_OBJECT) }; +} diff --git a/rust/qemu-api/src/device_class.rs b/rust/qemu-api/src/device_class.rs index dcec548829..a9965d1f12 100644 --- a/rust/qemu-api/src/device_class.rs +++ b/rust/qemu-api/src/device_class.rs @@ -5,7 +5,7 @@ use std::ffi::CStr; use crate::{ - bindings::{self, DeviceClass, DeviceState, Error, Property, VMStateDescription}, + bindings::{self, DeviceClass, DeviceState, Error, ObjectClass, Property, VMStateDescription}, definitions::ClassInitImpl, prelude::*, }; @@ -68,7 +68,7 @@ unsafe extern "C" fn rust_reset_fn(dev: *mut DeviceState) { impl ClassInitImpl for T where - T: DeviceImpl, + T: ClassInitImpl + DeviceImpl, { fn class_init(dc: &mut DeviceClass) { if ::REALIZE.is_some() { @@ -88,6 +88,8 @@ where bindings::device_class_set_props_n(dc, prop.as_ptr(), prop.len()); } } + + >::class_init(&mut dc.parent_class); } } -- cgit 1.4.1 From 4aed0296b307b6e2e3b7d38ee6c5204cf2dfe1ca Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 29 Oct 2024 14:15:27 +0100 Subject: rust: rename qemu-api modules to follow C code a bit more A full match would mean calling them qom::object and hw::core::qdev. For now, keep the names shorter but still a bit easier to find. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 4 +- rust/qemu-api-macros/src/lib.rs | 2 +- rust/qemu-api/meson.build | 5 +- rust/qemu-api/src/definitions.rs | 275 -------------------------------------- rust/qemu-api/src/device_class.rs | 141 ------------------- rust/qemu-api/src/lib.rs | 5 +- rust/qemu-api/src/module.rs | 43 ++++++ rust/qemu-api/src/prelude.rs | 2 +- rust/qemu-api/src/qdev.rs | 143 ++++++++++++++++++++ rust/qemu-api/src/qom.rs | 263 ++++++++++++++++++++++++++++++++++++ rust/qemu-api/src/sysbus.rs | 2 +- rust/qemu-api/tests/tests.rs | 4 +- 12 files changed, 462 insertions(+), 427 deletions(-) delete mode 100644 rust/qemu-api/src/definitions.rs delete mode 100644 rust/qemu-api/src/device_class.rs create mode 100644 rust/qemu-api/src/module.rs create mode 100644 rust/qemu-api/src/qdev.rs create mode 100644 rust/qemu-api/src/qom.rs (limited to 'rust/qemu-api/src') diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index d9e9f35f45..3fed8b4ad2 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -11,10 +11,10 @@ use std::{ use qemu_api::{ bindings::{self, *}, c_str, - definitions::ObjectImpl, - device_class::DeviceImpl, irq::InterruptSource, prelude::*, + qdev::DeviceImpl, + qom::ObjectImpl, }; use crate::{ diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index cf99ac04b8..74a8bc7503 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -40,7 +40,7 @@ pub fn derive_object(input: TokenStream) -> TokenStream { let expanded = quote! { ::qemu_api::module_init! { MODULE_INIT_QOM => unsafe { - ::qemu_api::bindings::type_register_static(&<#name as ::qemu_api::definitions::ObjectImpl>::TYPE_INFO); + ::qemu_api::bindings::type_register_static(&<#name as ::qemu_api::qom::ObjectImpl>::TYPE_INFO); } } }; diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index adcee66115..7ff408ad68 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -19,11 +19,12 @@ _qemu_api_rs = static_library( 'src/bitops.rs', 'src/cell.rs', 'src/c_str.rs', - 'src/definitions.rs', - 'src/device_class.rs', 'src/irq.rs', + 'src/module.rs', 'src/offset_of.rs', 'src/prelude.rs', + 'src/qdev.rs', + 'src/qom.rs', 'src/sysbus.rs', 'src/vmstate.rs', 'src/zeroable.rs', diff --git a/rust/qemu-api/src/definitions.rs b/rust/qemu-api/src/definitions.rs deleted file mode 100644 index a2481c1fe7..0000000000 --- a/rust/qemu-api/src/definitions.rs +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright 2024, Linaro Limited -// Author(s): Manos Pitsidianakis -// SPDX-License-Identifier: GPL-2.0-or-later - -//! Definitions required by QEMU when registering a device. - -use std::{ffi::CStr, os::raw::c_void}; - -use crate::bindings::{self, Object, ObjectClass, TypeInfo}; - -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 - // for class T - unsafe { T::INSTANCE_INIT.unwrap()(&mut *obj.cast::()) } -} - -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::() }) -} - -unsafe extern "C" fn rust_class_init>( - klass: *mut ObjectClass, - _data: *mut c_void, -) { - // SAFETY: klass is a T::Class, since rust_class_init - // is called from QOM core as the class_init function - // for class T - T::class_init(unsafe { &mut *klass.cast::() }) -} - -/// Trait exposed by all structs corresponding to QOM objects. -/// -/// # Safety -/// -/// For classes declared in C: -/// -/// - `Class` and `TYPE` must match the data in the `TypeInfo`; -/// -/// - the first field of the struct must be of the instance type corresponding -/// to the superclass, as declared in the `TypeInfo` -/// -/// - likewise, the first field of the `Class` struct must be of the class type -/// corresponding to the superclass -/// -/// For classes declared in Rust and implementing [`ObjectImpl`]: -/// -/// - 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` -/// -/// - likewise, the first field of the `Class` must be of the class struct -/// corresponding to the superclass, which is `ObjectImpl::ParentType::Class`. -pub unsafe trait ObjectType: Sized { - /// The QOM class object corresponding to this struct. This is used - /// to automatically generate a `class_init` method. - type Class; - - /// The name of the type, which can be passed to `object_new()` to - /// generate an instance of this type. - const TYPE_NAME: &'static CStr; -} - -/// 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`: - type ParentType: ObjectType; - - /// 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 - /// initializing its own members. - /// - /// FIXME: The argument is not really a valid reference. `&mut - /// MaybeUninit` would be a better description. - const INSTANCE_INIT: Option = None; - - /// Function that is called to finish initialization of an object, once - /// `INSTANCE_INIT` functions have been called. - 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 - /// is only useful if a class is not a leaf, and can be used to undo - /// the effects of copying the contents of the parent's class struct - /// to the descendants. - const CLASS_BASE_INIT: Option< - unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void), - > = None; - - const TYPE_INFO: TypeInfo = TypeInfo { - name: Self::TYPE_NAME.as_ptr(), - parent: Self::ParentType::TYPE_NAME.as_ptr(), - instance_size: core::mem::size_of::(), - instance_align: core::mem::align_of::(), - instance_init: match Self::INSTANCE_INIT { - None => None, - Some(_) => Some(rust_instance_init::), - }, - instance_post_init: match Self::INSTANCE_POST_INIT { - None => None, - Some(_) => Some(rust_instance_post_init::), - }, - instance_finalize: Self::INSTANCE_FINALIZE, - abstract_: Self::ABSTRACT, - class_size: core::mem::size_of::(), - class_init: Some(rust_class_init::), - class_base_init: Self::CLASS_BASE_INIT, - class_data: core::ptr::null_mut(), - interfaces: core::ptr::null_mut(), - }; - - // methods on ObjectClass - const UNPARENT: Option = None; -} - -/// Internal trait used to automatically fill in a class struct. -/// -/// Each QOM class that has virtual methods describes them in a -/// _class struct_. Class structs include a parent field corresponding -/// to the vtable of the parent class, all the way up to [`ObjectClass`]. -/// Each QOM type has one such class struct; this trait takes care of -/// initializing the `T` part of the class struct, for the type that -/// implements the trait. -/// -/// Each struct will implement this trait with `T` equal to each -/// superclass. For example, a device should implement at least -/// `ClassInitImpl<`[`DeviceClass`](crate::bindings::DeviceClass)`>` and -/// `ClassInitImpl<`[`ObjectClass`](crate::bindings::ObjectClass)`>`. -/// Such implementations are made in one of two ways. -/// -/// For most superclasses, `ClassInitImpl` is provided by the `qemu-api` -/// crate itself. The Rust implementation of methods will come from a -/// trait like [`ObjectImpl`] or -/// [`DeviceImpl`](crate::device_class::DeviceImpl), and `ClassInitImpl` is -/// provided by blanket implementations that operate on all implementors of the -/// `*Impl`* trait. For example: -/// -/// ```ignore -/// impl ClassInitImpl for T -/// where -/// T: ClassInitImpl + DeviceImpl, -/// ``` -/// -/// The bound on `ClassInitImpl` is needed so that, -/// after initializing the `DeviceClass` part of the class struct, -/// the parent [`ObjectClass`] is initialized as well. -/// -/// The other case is when manual implementation of the trait is needed. -/// This covers the following cases: -/// -/// * if a class implements a QOM interface, the Rust code _has_ to define its -/// own class struct `FooClass` and implement `ClassInitImpl`. -/// `ClassInitImpl`'s `class_init` method will then forward to -/// multiple other `class_init`s, for the interfaces as well as the -/// superclass. (Note that there is no Rust example yet for using interfaces). -/// -/// * for classes implemented outside the ``qemu-api`` crate, it's not possible -/// to add blanket implementations like the above one, due to orphan rules. In -/// that case, the easiest solution is to implement -/// `ClassInitImpl` for each subclass and not have a -/// `YourSuperclassImpl` trait at all. -/// -/// ```ignore -/// impl ClassInitImpl for YourSubclass { -/// fn class_init(klass: &mut YourSuperclass) { -/// klass.some_method = Some(Self::some_method); -/// >::class_init(&mut klass.parent_class); -/// } -/// } -/// ``` -/// -/// While this method incurs a small amount of code duplication, -/// it is generally limited to the recursive call on the last line. -/// This is because classes defined in Rust do not need the same -/// glue code that is needed when the classes are defined in C code. -/// You may consider using a macro if you have many subclasses. -pub trait ClassInitImpl { - /// Initialize `klass` to point to the virtual method implementations - /// for `Self`. On entry, the virtual method pointers are set to - /// the default values coming from the parent classes; the function - /// can change them to override virtual methods of a parent class. - /// - /// The virtual method implementations usually come from another - /// trait, for example [`DeviceImpl`](crate::device_class::DeviceImpl) - /// when `T` is [`DeviceClass`](crate::bindings::DeviceClass). - /// - /// On entry, `klass`'s parent class is initialized, while the other fields - /// are all zero; it is therefore assumed that all fields in `T` can be - /// zeroed, otherwise it would not be possible to provide the class as a - /// `&mut T`. TODO: add a bound of [`Zeroable`](crate::zeroable::Zeroable) - /// to T; this is more easily done once Zeroable does not require a manual - /// implementation (Rust 1.75.0). - fn class_init(klass: &mut T); -} - -#[macro_export] -macro_rules! module_init { - ($type:ident => $body:block) => { - const _: () = { - #[used] - #[cfg_attr( - not(any(target_vendor = "apple", target_os = "windows")), - link_section = ".init_array" - )] - #[cfg_attr(target_vendor = "apple", link_section = "__DATA,__mod_init_func")] - #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")] - pub static LOAD_MODULE: extern "C" fn() = { - extern "C" fn init_fn() { - $body - } - - extern "C" fn ctor_fn() { - unsafe { - $crate::bindings::register_module_init( - Some(init_fn), - $crate::bindings::module_init_type::$type, - ); - } - } - - ctor_fn - }; - }; - }; - - // shortcut because it's quite common that $body needs unsafe {} - ($type:ident => unsafe $body:block) => { - $crate::module_init! { - $type => { unsafe { $body } } - } - }; -} - -/// # Safety -/// -/// We expect the FFI user of this function to pass a valid pointer that -/// can be downcasted to type `T`. We also expect the device is -/// readable/writeable from one thread at any time. -unsafe extern "C" fn rust_unparent_fn(dev: *mut Object) { - unsafe { - assert!(!dev.is_null()); - let state = core::ptr::NonNull::new_unchecked(dev.cast::()); - T::UNPARENT.unwrap()(state.as_ref()); - } -} - -impl ClassInitImpl for T -where - T: ObjectImpl, -{ - fn class_init(oc: &mut ObjectClass) { - if ::UNPARENT.is_some() { - oc.unparent = Some(rust_unparent_fn::); - } - } -} - -unsafe impl ObjectType for Object { - type Class = ObjectClass; - const TYPE_NAME: &'static CStr = - unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_OBJECT) }; -} diff --git a/rust/qemu-api/src/device_class.rs b/rust/qemu-api/src/device_class.rs deleted file mode 100644 index a9965d1f12..0000000000 --- a/rust/qemu-api/src/device_class.rs +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2024, Linaro Limited -// Author(s): Manos Pitsidianakis -// SPDX-License-Identifier: GPL-2.0-or-later - -use std::ffi::CStr; - -use crate::{ - bindings::{self, DeviceClass, DeviceState, Error, ObjectClass, Property, VMStateDescription}, - definitions::ClassInitImpl, - prelude::*, -}; - -/// Trait providing the contents of [`DeviceClass`]. -pub trait DeviceImpl { - /// _Realization_ is the second stage of device creation. It contains - /// all operations that depend on device properties and can fail (note: - /// this is not yet supported for Rust devices). - /// - /// If not `None`, the parent class's `realize` method is overridden - /// with the function pointed to by `REALIZE`. - const REALIZE: Option = None; - - /// If not `None`, the parent class's `reset` method is overridden - /// with the function pointed to by `RESET`. - /// - /// Rust does not yet support the three-phase reset protocol; this is - /// usually okay for leaf classes. - const RESET: Option = None; - - /// An array providing the properties that the user can set on the - /// device. Not a `const` because referencing statics in constants - /// is unstable until Rust 1.83.0. - fn properties() -> &'static [Property] { - &[] - } - - /// A `VMStateDescription` providing the migration format for the device - /// Not a `const` because referencing statics in constants is unstable - /// until Rust 1.83.0. - fn vmsd() -> Option<&'static VMStateDescription> { - None - } -} - -/// # Safety -/// -/// This function is only called through the QOM machinery and -/// used by the `ClassInitImpl` trait. -/// We expect the FFI user of this function to pass a valid pointer that -/// can be downcasted to type `T`. We also expect the device is -/// readable/writeable from one thread at any time. -unsafe extern "C" fn rust_realize_fn(dev: *mut DeviceState, _errp: *mut *mut Error) { - assert!(!dev.is_null()); - let state = dev.cast::(); - T::REALIZE.unwrap()(unsafe { &mut *state }); -} - -/// # Safety -/// -/// We expect the FFI user of this function to pass a valid pointer that -/// can be downcasted to type `T`. We also expect the device is -/// readable/writeable from one thread at any time. -unsafe extern "C" fn rust_reset_fn(dev: *mut DeviceState) { - assert!(!dev.is_null()); - let state = dev.cast::(); - T::RESET.unwrap()(unsafe { &mut *state }); -} - -impl ClassInitImpl for T -where - T: ClassInitImpl + DeviceImpl, -{ - fn class_init(dc: &mut DeviceClass) { - if ::REALIZE.is_some() { - dc.realize = Some(rust_realize_fn::); - } - if ::RESET.is_some() { - unsafe { - bindings::device_class_set_legacy_reset(dc, Some(rust_reset_fn::)); - } - } - if let Some(vmsd) = ::vmsd() { - dc.vmsd = vmsd; - } - let prop = ::properties(); - if !prop.is_empty() { - unsafe { - bindings::device_class_set_props_n(dc, prop.as_ptr(), prop.len()); - } - } - - >::class_init(&mut dc.parent_class); - } -} - -#[macro_export] -macro_rules! define_property { - ($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: $crate::offset_of!($state, $field) as isize, - set_default: true, - defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval as u64 }, - ..$crate::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: $crate::offset_of!($state, $field) as isize, - set_default: false, - ..$crate::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 = - unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_DEVICE) }; -} diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs index 9e007e1635..124bece044 100644 --- a/rust/qemu-api/src/lib.rs +++ b/rust/qemu-api/src/lib.rs @@ -15,10 +15,11 @@ pub mod prelude; pub mod bitops; pub mod c_str; pub mod cell; -pub mod definitions; -pub mod device_class; pub mod irq; +pub mod module; pub mod offset_of; +pub mod qdev; +pub mod qom; pub mod sysbus; pub mod vmstate; pub mod zeroable; diff --git a/rust/qemu-api/src/module.rs b/rust/qemu-api/src/module.rs new file mode 100644 index 0000000000..fa5cea3598 --- /dev/null +++ b/rust/qemu-api/src/module.rs @@ -0,0 +1,43 @@ +// Copyright 2024, Linaro Limited +// Author(s): Manos Pitsidianakis +// SPDX-License-Identifier: GPL-2.0-or-later + +//! Macro to register blocks of code that run as QEMU starts up. + +#[macro_export] +macro_rules! module_init { + ($type:ident => $body:block) => { + const _: () = { + #[used] + #[cfg_attr( + not(any(target_vendor = "apple", target_os = "windows")), + link_section = ".init_array" + )] + #[cfg_attr(target_vendor = "apple", link_section = "__DATA,__mod_init_func")] + #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")] + pub static LOAD_MODULE: extern "C" fn() = { + extern "C" fn init_fn() { + $body + } + + extern "C" fn ctor_fn() { + unsafe { + $crate::bindings::register_module_init( + Some(init_fn), + $crate::bindings::module_init_type::$type, + ); + } + } + + ctor_fn + }; + }; + }; + + // shortcut because it's quite common that $body needs unsafe {} + ($type:ident => unsafe $body:block) => { + $crate::module_init! { + $type => { unsafe { $body } } + } + }; +} diff --git a/rust/qemu-api/src/prelude.rs b/rust/qemu-api/src/prelude.rs index 1b8677b2d9..5cc41f081f 100644 --- a/rust/qemu-api/src/prelude.rs +++ b/rust/qemu-api/src/prelude.rs @@ -7,4 +7,4 @@ pub use crate::bitops::IntegerExt; pub use crate::cell::BqlCell; pub use crate::cell::BqlRefCell; -pub use crate::definitions::ObjectType; +pub use crate::qom::ObjectType; diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs new file mode 100644 index 0000000000..ad4c12d097 --- /dev/null +++ b/rust/qemu-api/src/qdev.rs @@ -0,0 +1,143 @@ +// Copyright 2024, Linaro Limited +// Author(s): Manos Pitsidianakis +// SPDX-License-Identifier: GPL-2.0-or-later + +//! Bindings to create devices and access device functionality from Rust. + +use std::ffi::CStr; + +use crate::{ + bindings::{self, DeviceClass, DeviceState, Error, ObjectClass, Property, VMStateDescription}, + prelude::*, + qom::ClassInitImpl, +}; + +/// Trait providing the contents of [`DeviceClass`]. +pub trait DeviceImpl { + /// _Realization_ is the second stage of device creation. It contains + /// all operations that depend on device properties and can fail (note: + /// this is not yet supported for Rust devices). + /// + /// If not `None`, the parent class's `realize` method is overridden + /// with the function pointed to by `REALIZE`. + const REALIZE: Option = None; + + /// If not `None`, the parent class's `reset` method is overridden + /// with the function pointed to by `RESET`. + /// + /// Rust does not yet support the three-phase reset protocol; this is + /// usually okay for leaf classes. + const RESET: Option = None; + + /// An array providing the properties that the user can set on the + /// device. Not a `const` because referencing statics in constants + /// is unstable until Rust 1.83.0. + fn properties() -> &'static [Property] { + &[] + } + + /// A `VMStateDescription` providing the migration format for the device + /// Not a `const` because referencing statics in constants is unstable + /// until Rust 1.83.0. + fn vmsd() -> Option<&'static VMStateDescription> { + None + } +} + +/// # Safety +/// +/// This function is only called through the QOM machinery and +/// used by the `ClassInitImpl` trait. +/// We expect the FFI user of this function to pass a valid pointer that +/// can be downcasted to type `T`. We also expect the device is +/// readable/writeable from one thread at any time. +unsafe extern "C" fn rust_realize_fn(dev: *mut DeviceState, _errp: *mut *mut Error) { + assert!(!dev.is_null()); + let state = dev.cast::(); + T::REALIZE.unwrap()(unsafe { &mut *state }); +} + +/// # Safety +/// +/// We expect the FFI user of this function to pass a valid pointer that +/// can be downcasted to type `T`. We also expect the device is +/// readable/writeable from one thread at any time. +unsafe extern "C" fn rust_reset_fn(dev: *mut DeviceState) { + assert!(!dev.is_null()); + let state = dev.cast::(); + T::RESET.unwrap()(unsafe { &mut *state }); +} + +impl ClassInitImpl for T +where + T: ClassInitImpl + DeviceImpl, +{ + fn class_init(dc: &mut DeviceClass) { + if ::REALIZE.is_some() { + dc.realize = Some(rust_realize_fn::); + } + if ::RESET.is_some() { + unsafe { + bindings::device_class_set_legacy_reset(dc, Some(rust_reset_fn::)); + } + } + if let Some(vmsd) = ::vmsd() { + dc.vmsd = vmsd; + } + let prop = ::properties(); + if !prop.is_empty() { + unsafe { + bindings::device_class_set_props_n(dc, prop.as_ptr(), prop.len()); + } + } + + >::class_init(&mut dc.parent_class); + } +} + +#[macro_export] +macro_rules! define_property { + ($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: $crate::offset_of!($state, $field) as isize, + set_default: true, + defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval as u64 }, + ..$crate::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: $crate::offset_of!($state, $field) as isize, + set_default: false, + ..$crate::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 = + unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_DEVICE) }; +} diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs new file mode 100644 index 0000000000..2222d1a5ab --- /dev/null +++ b/rust/qemu-api/src/qom.rs @@ -0,0 +1,263 @@ +// Copyright 2024, Linaro Limited +// Author(s): Manos Pitsidianakis +// SPDX-License-Identifier: GPL-2.0-or-later + +//! Bindings to access QOM functionality from Rust. +//! +//! This module provides automatic creation and registration of `TypeInfo` +//! for classes that are written in Rust, and mapping between Rust traits +//! and QOM vtables. +//! +//! # Structure of a class +//! +//! A leaf class only needs a struct holding instance state. The struct must +//! implement the [`ObjectType`] trait, as well as any `*Impl` traits that exist +//! for its superclasses. +//! +//! If a class has subclasses, it will also provide a struct for instance data, +//! with the same characteristics as for concrete classes, but it also needs +//! additional components to support virtual methods: +//! +//! * a struct for class data, for example `DeviceClass`. This corresponds to +//! the C "class struct" and holds the vtable that is used by instances of the +//! class and its subclasses. It must start with its parent's class struct. +//! +//! * a trait for virtual method implementations, for example `DeviceImpl`. +//! Child classes implement this trait to provide their own behavior for +//! virtual methods. The trait's methods take `&self` to access instance data. +//! +//! * an implementation of [`ClassInitImpl`], for example +//! `ClassInitImpl`. This fills the vtable in the class struct; +//! the source for this is the `*Impl` trait; the associated consts and +//! functions if needed are wrapped to map C types into Rust types. + +use std::{ffi::CStr, os::raw::c_void}; + +use crate::bindings::{self, Object, ObjectClass, TypeInfo}; + +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 + // for class T + unsafe { T::INSTANCE_INIT.unwrap()(&mut *obj.cast::()) } +} + +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::() }) +} + +unsafe extern "C" fn rust_class_init>( + klass: *mut ObjectClass, + _data: *mut c_void, +) { + // SAFETY: klass is a T::Class, since rust_class_init + // is called from QOM core as the class_init function + // for class T + T::class_init(unsafe { &mut *klass.cast::() }) +} + +/// Trait exposed by all structs corresponding to QOM objects. +/// +/// # Safety +/// +/// For classes declared in C: +/// +/// - `Class` and `TYPE` must match the data in the `TypeInfo`; +/// +/// - the first field of the struct must be of the instance type corresponding +/// to the superclass, as declared in the `TypeInfo` +/// +/// - likewise, the first field of the `Class` struct must be of the class type +/// corresponding to the superclass +/// +/// For classes declared in Rust and implementing [`ObjectImpl`]: +/// +/// - 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` +/// +/// - likewise, the first field of the `Class` must be of the class struct +/// corresponding to the superclass, which is `ObjectImpl::ParentType::Class`. +pub unsafe trait ObjectType: Sized { + /// The QOM class object corresponding to this struct. This is used + /// to automatically generate a `class_init` method. + type Class; + + /// The name of the type, which can be passed to `object_new()` to + /// generate an instance of this type. + const TYPE_NAME: &'static CStr; +} + +/// 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`: + type ParentType: ObjectType; + + /// 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 + /// initializing its own members. + /// + /// FIXME: The argument is not really a valid reference. `&mut + /// MaybeUninit` would be a better description. + const INSTANCE_INIT: Option = None; + + /// Function that is called to finish initialization of an object, once + /// `INSTANCE_INIT` functions have been called. + 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 + /// is only useful if a class is not a leaf, and can be used to undo + /// the effects of copying the contents of the parent's class struct + /// to the descendants. + const CLASS_BASE_INIT: Option< + unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void), + > = None; + + const TYPE_INFO: TypeInfo = TypeInfo { + name: Self::TYPE_NAME.as_ptr(), + parent: Self::ParentType::TYPE_NAME.as_ptr(), + instance_size: core::mem::size_of::(), + instance_align: core::mem::align_of::(), + instance_init: match Self::INSTANCE_INIT { + None => None, + Some(_) => Some(rust_instance_init::), + }, + instance_post_init: match Self::INSTANCE_POST_INIT { + None => None, + Some(_) => Some(rust_instance_post_init::), + }, + instance_finalize: Self::INSTANCE_FINALIZE, + abstract_: Self::ABSTRACT, + class_size: core::mem::size_of::(), + class_init: Some(rust_class_init::), + class_base_init: Self::CLASS_BASE_INIT, + class_data: core::ptr::null_mut(), + interfaces: core::ptr::null_mut(), + }; + + // methods on ObjectClass + const UNPARENT: Option = None; +} + +/// Internal trait used to automatically fill in a class struct. +/// +/// Each QOM class that has virtual methods describes them in a +/// _class struct_. Class structs include a parent field corresponding +/// to the vtable of the parent class, all the way up to [`ObjectClass`]. +/// Each QOM type has one such class struct; this trait takes care of +/// initializing the `T` part of the class struct, for the type that +/// implements the trait. +/// +/// Each struct will implement this trait with `T` equal to each +/// superclass. For example, a device should implement at least +/// `ClassInitImpl<`[`DeviceClass`](crate::bindings::DeviceClass)`>` and +/// `ClassInitImpl<`[`ObjectClass`](crate::bindings::ObjectClass)`>`. +/// Such implementations are made in one of two ways. +/// +/// For most superclasses, `ClassInitImpl` is provided by the `qemu-api` +/// crate itself. The Rust implementation of methods will come from a +/// trait like [`ObjectImpl`] or [`DeviceImpl`](crate::qdev::DeviceImpl), +/// and `ClassInitImpl` is provided by blanket implementations that +/// operate on all implementors of the `*Impl`* trait. For example: +/// +/// ```ignore +/// impl ClassInitImpl for T +/// where +/// T: ClassInitImpl + DeviceImpl, +/// ``` +/// +/// The bound on `ClassInitImpl` is needed so that, +/// after initializing the `DeviceClass` part of the class struct, +/// the parent [`ObjectClass`] is initialized as well. +/// +/// The other case is when manual implementation of the trait is needed. +/// This covers the following cases: +/// +/// * if a class implements a QOM interface, the Rust code _has_ to define its +/// own class struct `FooClass` and implement `ClassInitImpl`. +/// `ClassInitImpl`'s `class_init` method will then forward to +/// multiple other `class_init`s, for the interfaces as well as the +/// superclass. (Note that there is no Rust example yet for using interfaces). +/// +/// * for classes implemented outside the ``qemu-api`` crate, it's not possible +/// to add blanket implementations like the above one, due to orphan rules. In +/// that case, the easiest solution is to implement +/// `ClassInitImpl` for each subclass and not have a +/// `YourSuperclassImpl` trait at all. +/// +/// ```ignore +/// impl ClassInitImpl for YourSubclass { +/// fn class_init(klass: &mut YourSuperclass) { +/// klass.some_method = Some(Self::some_method); +/// >::class_init(&mut klass.parent_class); +/// } +/// } +/// ``` +/// +/// While this method incurs a small amount of code duplication, +/// it is generally limited to the recursive call on the last line. +/// This is because classes defined in Rust do not need the same +/// glue code that is needed when the classes are defined in C code. +/// You may consider using a macro if you have many subclasses. +pub trait ClassInitImpl { + /// Initialize `klass` to point to the virtual method implementations + /// for `Self`. On entry, the virtual method pointers are set to + /// the default values coming from the parent classes; the function + /// can change them to override virtual methods of a parent class. + /// + /// The virtual method implementations usually come from another + /// trait, for example [`DeviceImpl`](crate::qdev::DeviceImpl) + /// when `T` is [`DeviceClass`](crate::bindings::DeviceClass). + /// + /// On entry, `klass`'s parent class is initialized, while the other fields + /// are all zero; it is therefore assumed that all fields in `T` can be + /// zeroed, otherwise it would not be possible to provide the class as a + /// `&mut T`. TODO: add a bound of [`Zeroable`](crate::zeroable::Zeroable) + /// to T; this is more easily done once Zeroable does not require a manual + /// implementation (Rust 1.75.0). + fn class_init(klass: &mut T); +} + +/// # Safety +/// +/// We expect the FFI user of this function to pass a valid pointer that +/// can be downcasted to type `T`. We also expect the device is +/// readable/writeable from one thread at any time. +unsafe extern "C" fn rust_unparent_fn(dev: *mut Object) { + unsafe { + assert!(!dev.is_null()); + let state = core::ptr::NonNull::new_unchecked(dev.cast::()); + T::UNPARENT.unwrap()(state.as_ref()); + } +} + +impl ClassInitImpl for T +where + T: ObjectImpl, +{ + fn class_init(oc: &mut ObjectClass) { + if ::UNPARENT.is_some() { + oc.unparent = Some(rust_unparent_fn::); + } + } +} + +unsafe impl ObjectType for Object { + type Class = ObjectClass; + const TYPE_NAME: &'static CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_OBJECT) }; +} diff --git a/rust/qemu-api/src/sysbus.rs b/rust/qemu-api/src/sysbus.rs index 5d15b31740..fa69cadd7c 100644 --- a/rust/qemu-api/src/sysbus.rs +++ b/rust/qemu-api/src/sysbus.rs @@ -9,9 +9,9 @@ pub use bindings::{SysBusDevice, SysBusDeviceClass}; use crate::{ bindings::{self, DeviceClass}, cell::bql_locked, - definitions::ClassInitImpl, irq::InterruptSource, prelude::*, + qom::ClassInitImpl, }; unsafe impl ObjectType for SysBusDevice { diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index ed3a555e76..78f7da474b 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -5,8 +5,8 @@ use std::ffi::CStr; use qemu_api::{ - bindings::*, c_str, declare_properties, define_property, definitions::ObjectImpl, - device_class::DeviceImpl, impl_device_class, prelude::*, zeroable::Zeroable, + bindings::*, c_str, declare_properties, define_property, prelude::*, qdev::DeviceImpl, + qom::ObjectImpl, zeroable::Zeroable, }; #[test] -- cgit 1.4.1 From 716d89f9cc14faf784d83c945c40b7e8256ae525 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 31 Oct 2024 10:14:11 +0100 Subject: rust: re-export C types from qemu-api submodules Long term we do not want device code to use "bindings" at all, so make it possible to get the relevant types from the other modules of qemu-api. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/qdev.rs | 7 +++++-- rust/qemu-api/src/qom.rs | 12 +++++++----- rust/qemu-api/src/sysbus.rs | 5 +---- rust/qemu-api/src/vmstate.rs | 9 +++++---- rust/qemu-api/tests/tests.rs | 9 +++++++-- 5 files changed, 25 insertions(+), 17 deletions(-) (limited to 'rust/qemu-api/src') diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index ad4c12d097..07a502a837 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -6,10 +6,13 @@ use std::ffi::CStr; +pub use bindings::{DeviceClass, DeviceState, Property}; + use crate::{ - bindings::{self, DeviceClass, DeviceState, Error, ObjectClass, Property, VMStateDescription}, + bindings::{self, Error}, prelude::*, - qom::ClassInitImpl, + qom::{ClassInitImpl, ObjectClass}, + vmstate::VMStateDescription, }; /// Trait providing the contents of [`DeviceClass`]. diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index 2222d1a5ab..a663647ffe 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -33,7 +33,9 @@ use std::{ffi::CStr, os::raw::c_void}; -use crate::bindings::{self, Object, ObjectClass, TypeInfo}; +pub use bindings::{Object, ObjectClass}; + +use crate::bindings::{self, TypeInfo}; unsafe extern "C" fn rust_instance_init(obj: *mut Object) { // SAFETY: obj is an instance of T, since rust_instance_init @@ -164,9 +166,9 @@ pub trait ObjectImpl: ObjectType + ClassInitImpl { /// /// Each struct will implement this trait with `T` equal to each /// superclass. For example, a device should implement at least -/// `ClassInitImpl<`[`DeviceClass`](crate::bindings::DeviceClass)`>` and -/// `ClassInitImpl<`[`ObjectClass`](crate::bindings::ObjectClass)`>`. -/// Such implementations are made in one of two ways. +/// `ClassInitImpl<`[`DeviceClass`](crate::qdev::DeviceClass)`>` and +/// `ClassInitImpl<`[`ObjectClass`]`>`. Such implementations are made +/// in one of two ways. /// /// For most superclasses, `ClassInitImpl` is provided by the `qemu-api` /// crate itself. The Rust implementation of methods will come from a @@ -221,7 +223,7 @@ pub trait ClassInitImpl { /// /// The virtual method implementations usually come from another /// trait, for example [`DeviceImpl`](crate::qdev::DeviceImpl) - /// when `T` is [`DeviceClass`](crate::bindings::DeviceClass). + /// when `T` is [`DeviceClass`](crate::qdev::DeviceClass). /// /// On entry, `klass`'s parent class is initialized, while the other fields /// are all zero; it is therefore assumed that all fields in `T` can be diff --git a/rust/qemu-api/src/sysbus.rs b/rust/qemu-api/src/sysbus.rs index fa69cadd7c..9abc687a26 100644 --- a/rust/qemu-api/src/sysbus.rs +++ b/rust/qemu-api/src/sysbus.rs @@ -7,10 +7,7 @@ use std::{ffi::CStr, ptr::addr_of}; pub use bindings::{SysBusDevice, SysBusDeviceClass}; use crate::{ - bindings::{self, DeviceClass}, - cell::bql_locked, - irq::InterruptSource, - prelude::*, + bindings, cell::bql_locked, irq::InterruptSource, prelude::*, qdev::DeviceClass, qom::ClassInitImpl, }; diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs index bedcf1e8f3..25c68b703e 100644 --- a/rust/qemu-api/src/vmstate.rs +++ b/rust/qemu-api/src/vmstate.rs @@ -10,6 +10,8 @@ //! [`vmstate_fields`](crate::vmstate_fields) are meant to be used when //! declaring a device model state struct. +pub use crate::bindings::VMStateDescription; + #[doc(alias = "VMSTATE_UNUSED_BUFFER")] #[macro_export] macro_rules! vmstate_unused_buffer { @@ -328,7 +330,7 @@ macro_rules! vmstate_fields { } /// A transparent wrapper type for the `subsections` field of -/// [`VMStateDescription`](crate::bindings::VMStateDescription). +/// [`VMStateDescription`]. /// /// This is necessary to be able to declare subsection descriptions as statics, /// because the only way to implement `Sync` for a foreign type (and `*const` @@ -342,9 +344,8 @@ pub struct VMStateSubsectionsWrapper(pub &'static [*const crate::bindings::VMSta unsafe impl Sync for VMStateSubsectionsWrapper {} -/// Helper macro to declare a list of subsections -/// ([`VMStateDescription`](`crate::bindings::VMStateDescription`)) into a -/// static and return a pointer to the array of pointers it created. +/// Helper macro to declare a list of subsections ([`VMStateDescription`]) +/// into a static and return a pointer to the array of pointers it created. #[macro_export] macro_rules! vmstate_subsections { ($($subsection:expr),*$(,)*) => {{ diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index 78f7da474b..68557fb85c 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -5,8 +5,13 @@ use std::ffi::CStr; use qemu_api::{ - bindings::*, c_str, declare_properties, define_property, prelude::*, qdev::DeviceImpl, - qom::ObjectImpl, zeroable::Zeroable, + bindings::*, + c_str, declare_properties, define_property, + prelude::*, + qdev::{DeviceImpl, DeviceState, Property}, + qom::ObjectImpl, + vmstate::VMStateDescription, + zeroable::Zeroable, }; #[test] -- cgit 1.4.1 From d4873c5d4fdd11f484df183e01c0825fe347fd8b Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 15 Nov 2024 12:08:43 +0100 Subject: bql: add a "mock" BQL for Rust unit tests Right now, the stub BQL in stubs/iothread-lock.c always reports itself as unlocked. However, Rust would like to run its tests in an environment where the BQL *is* locked. Provide an extremely dirty function that flips the return value of bql_is_locked() to true. Signed-off-by: Paolo Bonzini --- include/qemu/main-loop.h | 8 ++++++++ rust/qemu-api/meson.build | 2 +- rust/qemu-api/src/cell.rs | 26 +++++++++++++++++++++++--- stubs/iothread-lock.c | 8 +++++++- system/cpus.c | 6 ++++++ 5 files changed, 45 insertions(+), 5 deletions(-) (limited to 'rust/qemu-api/src') diff --git a/include/qemu/main-loop.h b/include/qemu/main-loop.h index 646306c272..3935a57339 100644 --- a/include/qemu/main-loop.h +++ b/include/qemu/main-loop.h @@ -247,6 +247,14 @@ void event_notifier_set_handler(EventNotifier *e, GSource *iohandler_get_g_source(void); AioContext *iohandler_get_aio_context(void); +/** + * rust_bql_mock_lock: + * + * Called from Rust doctests to make bql_lock() return true. + * Do not touch. + */ +void rust_bql_mock_lock(void); + /** * bql_locked: Return lock status of the Big QEMU Lock (BQL) * diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index 7ff408ad68..50ec00e128 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -60,7 +60,7 @@ test('rust-qemu-api-integration', dependencies: [qemu_api, qemu_api_macros], link_whole: [rust_qemu_api_objs, libqemuutil]), args: [ - '--test', + '--test', '--test-threads', '1', '--format', 'pretty', ], protocol: 'rust', diff --git a/rust/qemu-api/src/cell.rs b/rust/qemu-api/src/cell.rs index 28349de291..eae4e2ce78 100644 --- a/rust/qemu-api/src/cell.rs +++ b/rust/qemu-api/src/cell.rs @@ -124,9 +124,18 @@ use std::{ use crate::bindings; -// TODO: When building doctests do not include the actual BQL, because cargo -// does not know how to link them to libqemuutil. This can be fixed by -// running rustdoc from "meson test" instead of relying on cargo. +/// An internal function that is used by doctests. +pub fn bql_start_test() { + if cfg!(MESON) { + // SAFETY: integration tests are run with --test-threads=1, while + // unit tests and doctests are not multithreaded and do not have + // any BQL-protected data. Just set bql_locked to true. + unsafe { + bindings::rust_bql_mock_lock(); + } + } +} + pub fn bql_locked() -> bool { // SAFETY: the function does nothing but return a thread-local bool !cfg!(MESON) || unsafe { bindings::bql_locked() } @@ -220,6 +229,7 @@ impl BqlCell { /// /// ``` /// use qemu_api::cell::BqlCell; + /// # qemu_api::cell::bql_start_test(); /// /// let c = BqlCell::new(5); /// ``` @@ -236,6 +246,7 @@ impl BqlCell { /// /// ``` /// use qemu_api::cell::BqlCell; + /// # qemu_api::cell::bql_start_test(); /// /// let c = BqlCell::new(5); /// @@ -253,6 +264,7 @@ impl BqlCell { /// /// ``` /// use qemu_api::cell::BqlCell; + /// # qemu_api::cell::bql_start_test(); /// /// let cell = BqlCell::new(5); /// assert_eq!(cell.get(), 5); @@ -274,6 +286,7 @@ impl BqlCell { /// /// ``` /// use qemu_api::cell::BqlCell; + /// # qemu_api::cell::bql_start_test(); /// /// let c = BqlCell::new(5); /// let five = c.into_inner(); @@ -293,6 +306,7 @@ impl BqlCell { /// /// ``` /// use qemu_api::cell::BqlCell; + /// # qemu_api::cell::bql_start_test(); /// /// let c = BqlCell::new(5); /// @@ -315,6 +329,7 @@ impl BqlCell { /// /// ``` /// use qemu_api::cell::BqlCell; + /// # qemu_api::cell::bql_start_test(); /// /// let c = BqlCell::new(5); /// @@ -333,6 +348,7 @@ impl BqlCell { /// /// ``` /// use qemu_api::cell::BqlCell; + /// # qemu_api::cell::bql_start_test(); /// /// let c = BqlCell::new(5); /// let five = c.take(); @@ -461,6 +477,7 @@ impl BqlRefCell { /// /// ``` /// use qemu_api::cell::BqlRefCell; + /// # qemu_api::cell::bql_start_test(); /// /// let c = BqlRefCell::new(5); /// @@ -472,6 +489,7 @@ impl BqlRefCell { /// /// ```should_panic /// use qemu_api::cell::BqlRefCell; + /// # qemu_api::cell::bql_start_test(); /// /// let c = BqlRefCell::new(5); /// @@ -513,6 +531,7 @@ impl BqlRefCell { /// /// ``` /// use qemu_api::cell::BqlRefCell; + /// # qemu_api::cell::bql_start_test(); /// /// let c = BqlRefCell::new("hello".to_owned()); /// @@ -525,6 +544,7 @@ impl BqlRefCell { /// /// ```should_panic /// use qemu_api::cell::BqlRefCell; + /// # qemu_api::cell::bql_start_test(); /// /// let c = BqlRefCell::new(5); /// let m = c.borrow(); diff --git a/stubs/iothread-lock.c b/stubs/iothread-lock.c index 5467659895..6050c081f5 100644 --- a/stubs/iothread-lock.c +++ b/stubs/iothread-lock.c @@ -1,11 +1,17 @@ #include "qemu/osdep.h" #include "qemu/main-loop.h" +static bool bql_is_locked = false; static uint32_t bql_unlock_blocked; bool bql_locked(void) { - return false; + return bql_is_locked; +} + +void rust_bql_mock_lock(void) +{ + bql_is_locked = true; } void bql_lock_impl(const char *file, int line) diff --git a/system/cpus.c b/system/cpus.c index ba633c7688..4b43ceb543 100644 --- a/system/cpus.c +++ b/system/cpus.c @@ -538,6 +538,12 @@ bool qemu_in_main_thread(void) return bql_locked(); } +void rust_bql_mock_lock(void) +{ + error_report("This function should be used only from tests"); + abort(); +} + /* * The BQL is taken from so many places that it is worth profiling the * callers directly, instead of funneling them all through a single function. -- cgit 1.4.1 From f50cd85c8475c16374d0e138efda222ce4455f53 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 19 Dec 2024 14:32:16 +0100 Subject: rust: qom: add casting functionality Add traits that let client cast typecast safely between object types. In particular, an upcast is compile-time guaranteed to succeed, and a YOLO C-style downcast must be marked as unsafe. The traits are based on an IsA<> trait that declares what is a subclass of what, which is an idea taken from glib-rs (https://docs.rs/glib/latest/glib/object/trait.IsA.html). The four primitives are also taken from there (https://docs.rs/glib/latest/glib/object/trait.Cast.html). However, the implementation of casting itself is a bit different and uses the Deref trait. This removes some pointer arithmetic from the pl011 device; it is also a prerequisite for the definition of methods, so that they can be invoked on all subclass structs. This will use the IsA<> trait to detect the structs that support the methods. glib also has a "monadic" casting trait which could be implemented on Option (as in https://docs.rs/glib/latest/glib/object/trait.CastNone.html) and perhaps even Result. For now I'm leaving it out, as the patch is already big enough and the benefit seems debatable. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/Cargo.toml | 1 + rust/hw/char/pl011/src/device.rs | 12 +- rust/qemu-api/src/prelude.rs | 7 + rust/qemu-api/src/qdev.rs | 1 + rust/qemu-api/src/qom.rs | 283 ++++++++++++++++++++++++++++++++++++++- rust/qemu-api/src/sysbus.rs | 7 +- rust/qemu-api/tests/tests.rs | 68 +++++++++- 7 files changed, 366 insertions(+), 13 deletions(-) (limited to 'rust/qemu-api/src') diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 6ec19b6729..5b6b6ca438 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -38,6 +38,7 @@ should_implement_trait = "deny" unused_self = "allow" # default-allow lints +as_ptr_cast_mut = "deny" as_underscore = "deny" assertions_on_result_states = "deny" bool_to_int_with_if = "deny" diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 3fed8b4ad2..e85d13c5a2 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -106,6 +106,8 @@ pub struct PL011State { device_id: DeviceId, } +qom_isa!(PL011State : SysBusDevice, DeviceState, Object); + unsafe impl ObjectType for PL011State { type Class = ::Class; const TYPE_NAME: &'static CStr = crate::TYPE_PL011; @@ -140,8 +142,6 @@ impl PL011State { unsafe fn init(&mut self) { const CLK_NAME: &CStr = c_str!("clk"); - let sbd = unsafe { &mut *(addr_of_mut!(*self).cast::()) }; - // SAFETY: // // self and self.iomem are guaranteed to be valid at this point since callers @@ -155,15 +155,16 @@ 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); } - let dev = addr_of_mut!(*self).cast::(); - // SAFETY: // // self.clock is not initialized at this point; but since `NonNull<_>` is Copy, @@ -172,6 +173,7 @@ impl PL011State { // calls this function to initialize the fields; therefore no code is // able to access an invalid self.clock value. unsafe { + let dev: &mut DeviceState = self.upcast_mut(); self.clock = NonNull::new(qdev_init_clock_in( dev, CLK_NAME.as_ptr(), @@ -632,6 +634,8 @@ impl PL011Luminary { } } +qom_isa!(PL011Luminary : PL011State, SysBusDevice, DeviceState, Object); + unsafe impl ObjectType for PL011Luminary { type Class = ::Class; const TYPE_NAME: &'static CStr = crate::TYPE_PL011_LUMINARY; diff --git a/rust/qemu-api/src/prelude.rs b/rust/qemu-api/src/prelude.rs index 5cc41f081f..a0a71fcd6b 100644 --- a/rust/qemu-api/src/prelude.rs +++ b/rust/qemu-api/src/prelude.rs @@ -7,4 +7,11 @@ pub use crate::bitops::IntegerExt; pub use crate::cell::BqlCell; pub use crate::cell::BqlRefCell; +pub use crate::qom::IsA; +pub use crate::qom::Object; +pub use crate::qom::ObjectCast; +pub use crate::qom::ObjectCastMut; +pub use crate::qom::ObjectDeref; pub use crate::qom::ObjectType; + +pub use crate::qom_isa; diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index 07a502a837..686054e737 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -144,3 +144,4 @@ unsafe impl ObjectType for DeviceState { const TYPE_NAME: &'static CStr = unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_DEVICE) }; } +qom_isa!(DeviceState: Object); diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index a663647ffe..74ea5721f7 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -4,15 +4,22 @@ //! Bindings to access QOM functionality from Rust. //! -//! This module provides automatic creation and registration of `TypeInfo` -//! for classes that are written in Rust, and mapping between Rust traits -//! and QOM vtables. +//! The QEMU Object Model (QOM) provides inheritance and dynamic typing for QEMU +//! devices. This module makes QOM's features available in Rust through two main +//! mechanisms: +//! +//! * Automatic creation and registration of `TypeInfo` for classes that are +//! written in Rust, as well as mapping between Rust traits and QOM vtables. +//! +//! * Type-safe casting between parent and child classes, through the [`IsA`] +//! trait and methods such as [`upcast`](ObjectCast::upcast) and +//! [`downcast`](ObjectCast::downcast). //! //! # Structure of a class //! //! A leaf class only needs a struct holding instance state. The struct must -//! implement the [`ObjectType`] trait, as well as any `*Impl` traits that exist -//! for its superclasses. +//! implement the [`ObjectType`] and [`IsA`] traits, as well as any `*Impl` +//! traits that exist for its superclasses. //! //! If a class has subclasses, it will also provide a struct for instance data, //! with the same characteristics as for concrete classes, but it also needs @@ -31,11 +38,57 @@ //! the source for this is the `*Impl` trait; the associated consts and //! functions if needed are wrapped to map C types into Rust types. -use std::{ffi::CStr, os::raw::c_void}; +use std::{ + ffi::CStr, + ops::{Deref, DerefMut}, + os::raw::c_void, +}; pub use bindings::{Object, ObjectClass}; -use crate::bindings::{self, TypeInfo}; +use crate::bindings::{self, object_dynamic_cast, TypeInfo}; + +/// Marker trait: `Self` can be statically upcasted to `P` (i.e. `P` is a direct +/// or indirect parent of `Self`). +/// +/// # Safety +/// +/// The struct `Self` must be `#[repr(C)]` and must begin, directly or +/// indirectly, with a field of type `P`. This ensures that invalid casts, +/// which rely on `IsA<>` for static checking, are rejected at compile time. +pub unsafe trait IsA: ObjectType {} + +// SAFETY: it is always safe to cast to your own type +unsafe impl IsA for T {} + +/// Macro to mark superclasses of QOM classes. This enables type-safe +/// up- and downcasting. +/// +/// # Safety +/// +/// This macro is a thin wrapper around the [`IsA`] trait and performs +/// no checking whatsoever of what is declared. It is the caller's +/// responsibility to have $struct begin, directly or indirectly, with +/// a field of type `$parent`. +#[macro_export] +macro_rules! qom_isa { + ($struct:ty : $($parent:ty),* ) => { + $( + // SAFETY: it is the caller responsibility to have $parent as the + // first field + unsafe impl $crate::qom::IsA<$parent> for $struct {} + + impl AsRef<$parent> for $struct { + fn as_ref(&self) -> &$parent { + // SAFETY: follows the same rules as for IsA, which is + // declared above. + let ptr: *const Self = self; + unsafe { &*ptr.cast::<$parent>() } + } + } + )* + }; +} unsafe extern "C" fn rust_instance_init(obj: *mut Object) { // SAFETY: obj is an instance of T, since rust_instance_init @@ -96,8 +149,224 @@ pub unsafe trait ObjectType: Sized { /// The name of the type, which can be passed to `object_new()` to /// generate an instance of this type. const TYPE_NAME: &'static CStr; + + /// Return the receiver as an Object. This is always safe, even + /// if this type represents an interface. + fn as_object(&self) -> &Object { + unsafe { &*self.as_object_ptr() } + } + + /// Return the receiver as a const raw pointer to Object. + /// This is preferrable to `as_object_mut_ptr()` if a C + /// function only needs a `const Object *`. + fn as_object_ptr(&self) -> *const Object { + self.as_ptr().cast() + } + + /// Return the receiver as a mutable raw pointer to Object. + /// + /// # Safety + /// + /// This cast is always safe, but because the result is mutable + /// and the incoming reference is not, this should only be used + /// for calls to C functions, and only if needed. + unsafe fn as_object_mut_ptr(&self) -> *mut Object { + self.as_object_ptr() as *mut _ + } } +/// This trait provides safe casting operations for QOM objects to raw pointers, +/// to be used for example for FFI. The trait can be applied to any kind of +/// reference or smart pointers, and enforces correctness through the [`IsA`] +/// trait. +pub trait ObjectDeref: Deref +where + Self::Target: ObjectType, +{ + /// Convert to a const Rust pointer, to be used for example for FFI. + /// The target pointer type must be the type of `self` or a superclass + fn as_ptr(&self) -> *const U + where + Self::Target: IsA, + { + let ptr: *const Self::Target = self.deref(); + ptr.cast::() + } + + /// Convert to a mutable Rust pointer, to be used for example for FFI. + /// The target pointer type must be the type of `self` or a superclass. + /// Used to implement interior mutability for objects. + /// + /// # Safety + /// + /// This method is unsafe because it overrides const-ness of `&self`. + /// Bindings to C APIs will use it a lot, but otherwise it should not + /// be necessary. + unsafe fn as_mut_ptr(&self) -> *mut U + where + Self::Target: IsA, + { + #[allow(clippy::as_ptr_cast_mut)] + { + self.as_ptr::() as *mut _ + } + } +} + +/// Trait that adds extra functionality for `&T` where `T` is a QOM +/// object type. Allows conversion to/from C objects in generic code. +pub trait ObjectCast: ObjectDeref + Copy +where + Self::Target: ObjectType, +{ + /// Safely convert from a derived type to one of its parent types. + /// + /// This is always safe; the [`IsA`] trait provides static verification + /// trait that `Self` dereferences to `U` or a child of `U`. + fn upcast<'a, U: ObjectType>(self) -> &'a U + where + Self::Target: IsA, + Self: 'a, + { + // SAFETY: soundness is declared via IsA, which is an unsafe trait + unsafe { self.unsafe_cast::() } + } + + /// Attempt to convert to a derived type. + /// + /// Returns `None` if the object is not actually of type `U`. This is + /// verified at runtime by checking the object's type information. + fn downcast<'a, U: IsA>(self) -> Option<&'a U> + where + Self: 'a, + { + self.dynamic_cast::() + } + + /// Attempt to convert between any two types in the QOM hierarchy. + /// + /// Returns `None` if the object is not actually of type `U`. This is + /// verified at runtime by checking the object's type information. + fn dynamic_cast<'a, U: ObjectType>(self) -> Option<&'a U> + where + Self: 'a, + { + unsafe { + // SAFETY: upcasting to Object is always valid, and the + // return type is either NULL or the argument itself + let result: *const U = + object_dynamic_cast(self.as_object_mut_ptr(), U::TYPE_NAME.as_ptr()).cast(); + + result.as_ref() + } + } + + /// Convert to any QOM type without verification. + /// + /// # Safety + /// + /// What safety? You need to know yourself that the cast is correct; only + /// use when performance is paramount. It is still better than a raw + /// pointer `cast()`, which does not even check that you remain in the + /// realm of QOM `ObjectType`s. + /// + /// `unsafe_cast::()` is always safe. + unsafe fn unsafe_cast<'a, U: ObjectType>(self) -> &'a U + where + Self: 'a, + { + unsafe { &*(self.as_ptr::().cast::()) } + } +} + +impl ObjectDeref for &T {} +impl ObjectCast for &T {} + +/// Trait for mutable type casting operations in the QOM hierarchy. +/// +/// This trait provides the mutable counterparts to [`ObjectCast`]'s conversion +/// functions. Unlike `ObjectCast`, this trait returns `Result` for fallible +/// conversions to preserve the original smart pointer if the cast fails. This +/// is necessary because mutable references cannot be copied, so a failed cast +/// must return ownership of the original reference. For example: +/// +/// ```ignore +/// let mut dev = get_device(); +/// // If this fails, we need the original `dev` back to try something else +/// match dev.dynamic_cast_mut::() { +/// Ok(foodev) => /* use foodev */, +/// Err(dev) => /* still have ownership of dev */ +/// } +/// ``` +pub trait ObjectCastMut: Sized + ObjectDeref + DerefMut +where + Self::Target: ObjectType, +{ + /// Safely convert from a derived type to one of its parent types. + /// + /// This is always safe; the [`IsA`] trait provides static verification + /// that `Self` dereferences to `U` or a child of `U`. + fn upcast_mut<'a, U: ObjectType>(self) -> &'a mut U + where + Self::Target: IsA, + Self: 'a, + { + // SAFETY: soundness is declared via IsA, which is an unsafe trait + unsafe { self.unsafe_cast_mut::() } + } + + /// Attempt to convert to a derived type. + /// + /// Returns `Ok(..)` if the object is of type `U`, or `Err(self)` if the + /// object if the conversion failed. This is verified at runtime by + /// checking the object's type information. + fn downcast_mut<'a, U: IsA>(self) -> Result<&'a mut U, Self> + where + Self: 'a, + { + self.dynamic_cast_mut::() + } + + /// Attempt to convert between any two types in the QOM hierarchy. + /// + /// Returns `Ok(..)` if the object is of type `U`, or `Err(self)` if the + /// object if the conversion failed. This is verified at runtime by + /// checking the object's type information. + fn dynamic_cast_mut<'a, U: ObjectType>(self) -> Result<&'a mut U, Self> + where + Self: 'a, + { + unsafe { + // SAFETY: upcasting to Object is always valid, and the + // return type is either NULL or the argument itself + let result: *mut U = + object_dynamic_cast(self.as_object_mut_ptr(), U::TYPE_NAME.as_ptr()).cast(); + + result.as_mut().ok_or(self) + } + } + + /// Convert to any QOM type without verification. + /// + /// # Safety + /// + /// What safety? You need to know yourself that the cast is correct; only + /// use when performance is paramount. It is still better than a raw + /// pointer `cast()`, which does not even check that you remain in the + /// realm of QOM `ObjectType`s. + /// + /// `unsafe_cast::()` is always safe. + unsafe fn unsafe_cast_mut<'a, U: ObjectType>(self) -> &'a mut U + where + Self: 'a, + { + unsafe { &mut *self.as_mut_ptr::().cast::() } + } +} + +impl ObjectDeref for &mut T {} +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 diff --git a/rust/qemu-api/src/sysbus.rs b/rust/qemu-api/src/sysbus.rs index 9abc687a26..8193734bde 100644 --- a/rust/qemu-api/src/sysbus.rs +++ b/rust/qemu-api/src/sysbus.rs @@ -7,7 +7,11 @@ use std::{ffi::CStr, ptr::addr_of}; pub use bindings::{SysBusDevice, SysBusDeviceClass}; use crate::{ - bindings, cell::bql_locked, irq::InterruptSource, prelude::*, qdev::DeviceClass, + bindings, + cell::bql_locked, + irq::InterruptSource, + prelude::*, + qdev::{DeviceClass, DeviceState}, qom::ClassInitImpl, }; @@ -16,6 +20,7 @@ unsafe impl ObjectType for SysBusDevice { const TYPE_NAME: &'static CStr = unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_SYS_BUS_DEVICE) }; } +qom_isa!(SysBusDevice: DeviceState, Object); // TODO: add SysBusDeviceImpl impl ClassInitImpl for T diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index dc0ed19019..7b63e28c2f 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -2,7 +2,11 @@ // Author(s): Manos Pitsidianakis // SPDX-License-Identifier: GPL-2.0-or-later -use std::ffi::CStr; +use std::{ + ffi::CStr, + os::raw::c_void, + ptr::{addr_of, addr_of_mut}, +}; use qemu_api::{ bindings::*, @@ -31,6 +35,8 @@ pub struct DummyState { migrate_clock: bool, } +qom_isa!(DummyState: Object, DeviceState); + declare_properties! { DUMMY_PROPERTIES, define_property!( @@ -81,3 +87,63 @@ fn test_object_new() { object_unref(object_new(DummyState::TYPE_NAME.as_ptr()).cast()); } } + +// a note on all "cast" tests: usually, especially for downcasts the desired +// class would be placed on the right, for example: +// +// let sbd_ref = p.dynamic_cast::(); +// +// Here I am doing the opposite to check that the resulting type is correct. + +#[test] +#[allow(clippy::shadow_unrelated)] +/// Test casts on shared references. +fn test_cast() { + init_qom(); + let p: *mut DummyState = unsafe { object_new(DummyState::TYPE_NAME.as_ptr()).cast() }; + + let p_ref: &DummyState = unsafe { &*p }; + let obj_ref: &Object = p_ref.upcast(); + assert_eq!(addr_of!(*obj_ref), p.cast()); + + let sbd_ref: Option<&SysBusDevice> = obj_ref.dynamic_cast(); + assert!(sbd_ref.is_none()); + + let dev_ref: Option<&DeviceState> = obj_ref.downcast(); + assert_eq!(addr_of!(*dev_ref.unwrap()), p.cast()); + + // SAFETY: the cast is wrong, but the value is only used for comparison + unsafe { + let sbd_ref: &SysBusDevice = obj_ref.unsafe_cast(); + assert_eq!(addr_of!(*sbd_ref), p.cast()); + + object_unref(p_ref.as_object_mut_ptr().cast::()); + } +} + +#[test] +#[allow(clippy::shadow_unrelated)] +/// Test casts on mutable references. +fn test_cast_mut() { + init_qom(); + let p: *mut DummyState = unsafe { object_new(DummyState::TYPE_NAME.as_ptr()).cast() }; + + let p_ref: &mut DummyState = unsafe { &mut *p }; + let obj_ref: &mut Object = p_ref.upcast_mut(); + assert_eq!(addr_of_mut!(*obj_ref), p.cast()); + + let sbd_ref: Result<&mut SysBusDevice, &mut Object> = obj_ref.dynamic_cast_mut(); + let obj_ref = sbd_ref.unwrap_err(); + + let dev_ref: Result<&mut DeviceState, &mut Object> = obj_ref.downcast_mut(); + let dev_ref = dev_ref.unwrap(); + assert_eq!(addr_of_mut!(*dev_ref), p.cast()); + + // SAFETY: the cast is wrong, but the value is only used for comparison + unsafe { + let sbd_ref: &mut SysBusDevice = obj_ref.unsafe_cast_mut(); + assert_eq!(addr_of_mut!(*sbd_ref), p.cast()); + + object_unref(p_ref.as_object_mut_ptr().cast::()); + } +} -- cgit 1.4.1 From ba3b81f3b668d3faa6cbdb39e123394f7bf637c7 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 6 Nov 2024 00:01:57 +0100 Subject: rust: qom: add initial subset of methods on Object Add an example of implementing instance methods and converting the result back to a Rust type. In this case the returned types are a string (actually a Cow; but that's transparent as long as it derefs to &str) and a QOM class. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/prelude.rs | 1 + rust/qemu-api/src/qom.rs | 56 +++++++++++++++++++++++++++++++++++++++++--- rust/qemu-api/tests/tests.rs | 12 ++++++++++ 3 files changed, 66 insertions(+), 3 deletions(-) (limited to 'rust/qemu-api/src') diff --git a/rust/qemu-api/src/prelude.rs b/rust/qemu-api/src/prelude.rs index a0a71fcd6b..6f32deeb2e 100644 --- a/rust/qemu-api/src/prelude.rs +++ b/rust/qemu-api/src/prelude.rs @@ -12,6 +12,7 @@ pub use crate::qom::Object; pub use crate::qom::ObjectCast; pub use crate::qom::ObjectCastMut; pub use crate::qom::ObjectDeref; +pub use crate::qom::ObjectMethods; pub use crate::qom::ObjectType; pub use crate::qom_isa; diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index 74ea5721f7..7d5fbef1e1 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -5,8 +5,8 @@ //! Bindings to access QOM functionality from Rust. //! //! The QEMU Object Model (QOM) provides inheritance and dynamic typing for QEMU -//! devices. This module makes QOM's features available in Rust through two main -//! mechanisms: +//! devices. This module makes QOM's features available in Rust through three +//! main mechanisms: //! //! * Automatic creation and registration of `TypeInfo` for classes that are //! written in Rust, as well as mapping between Rust traits and QOM vtables. @@ -15,6 +15,11 @@ //! trait and methods such as [`upcast`](ObjectCast::upcast) and //! [`downcast`](ObjectCast::downcast). //! +//! * Automatic delegation of parent class methods to child classes. When a +//! trait uses [`IsA`] as a bound, its contents become available to all child +//! classes through blanket implementations. This works both for class methods +//! and for instance methods accessed through references or smart pointers. +//! //! # Structure of a class //! //! A leaf class only needs a struct holding instance state. The struct must @@ -37,6 +42,16 @@ //! `ClassInitImpl`. This fills the vtable in the class struct; //! the source for this is the `*Impl` trait; the associated consts and //! functions if needed are wrapped to map C types into Rust types. +//! +//! * a trait for instance methods, for example `DeviceMethods`. This trait is +//! automatically implemented for any reference or smart pointer to a device +//! instance. It calls into the vtable provides access across all subclasses +//! to methods defined for the class. +//! +//! * optionally, a trait for class methods, for example `DeviceClassMethods`. +//! This provides access to class-wide functionality that doesn't depend on +//! instance data. Like instance methods, these are automatically inherited by +//! child classes. use std::{ ffi::CStr, @@ -46,7 +61,7 @@ use std::{ pub use bindings::{Object, ObjectClass}; -use crate::bindings::{self, object_dynamic_cast, TypeInfo}; +use crate::bindings::{self, object_dynamic_cast, object_get_class, object_get_typename, TypeInfo}; /// Marker trait: `Self` can be statically upcasted to `P` (i.e. `P` is a direct /// or indirect parent of `Self`). @@ -532,3 +547,38 @@ unsafe impl ObjectType for Object { const TYPE_NAME: &'static CStr = unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_OBJECT) }; } + +/// Trait for methods exposed by the Object class. The methods can be +/// called on all objects that have the trait `IsA`. +/// +/// The trait should only be used through the blanket implementation, +/// which guarantees safety via `IsA` +pub trait ObjectMethods: ObjectDeref +where + Self::Target: IsA, +{ + /// Return the name of the type of `self` + fn typename(&self) -> std::borrow::Cow<'_, str> { + let obj = self.upcast::(); + // SAFETY: safety of this is the requirement for implementing IsA + // The result of the C API has static lifetime + unsafe { + let p = object_get_typename(obj.as_mut_ptr()); + CStr::from_ptr(p).to_string_lossy() + } + } + + fn get_class(&self) -> &'static ::Class { + let obj = self.upcast::(); + + // SAFETY: all objects can call object_get_class; the actual class + // type is guaranteed by the implementation of `ObjectType` and + // `ObjectImpl`. + let klass: &'static ::Class = + unsafe { &*object_get_class(obj.as_mut_ptr()).cast() }; + + klass + } +} + +impl ObjectMethods for R where R::Target: IsA {} diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index 7b63e28c2f..1d2825b098 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -88,6 +88,18 @@ fn test_object_new() { } } +#[test] +/// Try invoking a method on an object. +fn test_typename() { + init_qom(); + let p: *mut DummyState = unsafe { object_new(DummyState::TYPE_NAME.as_ptr()).cast() }; + let p_ref: &DummyState = unsafe { &*p }; + assert_eq!(p_ref.typename(), "dummy"); + unsafe { + object_unref(p_ref.as_object_mut_ptr().cast::()); + } +} + // a note on all "cast" tests: usually, especially for downcasts the desired // class would be placed on the right, for example: // -- cgit 1.4.1 From e05fbacd20366de436db82b776dfbc77071f6e29 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Sat, 30 Nov 2024 17:26:24 +0100 Subject: rust: qemu-api: add a module to wrap functions and zero-sized closures One recurring issue when writing Rust bindings is how to convert a Rust function ("fn" or "impl Fn") to a C function, and how to pass around "self" to a C function that only takes a void*. An easy solution would be to store on the heap a pair consisting of a pointer to the Rust function and the pointer to "self", but it is possible to do better. If an "Fn" has zero size (that is, if it is a zero-capture closures or a function pointer---which in turn includes all methods), it is possible to build a generic Rust function that calls it even if you only have the type; you don't need either the pointer to the function itself (because the address of the code is part of the type) or any closure data (because it has size zero). Introduce a wrapper that provides the functionality of calling the function given only its type. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/meson.build | 1 + rust/qemu-api/src/callbacks.rs | 144 +++++++++++++++++++++++++++++++++++++++++ rust/qemu-api/src/lib.rs | 1 + 3 files changed, 146 insertions(+) create mode 100644 rust/qemu-api/src/callbacks.rs (limited to 'rust/qemu-api/src') diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index 50ec00e128..ccb20f38c1 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -17,6 +17,7 @@ _qemu_api_rs = static_library( 'src/lib.rs', 'src/bindings.rs', 'src/bitops.rs', + 'src/callbacks.rs', 'src/cell.rs', 'src/c_str.rs', 'src/irq.rs', diff --git a/rust/qemu-api/src/callbacks.rs b/rust/qemu-api/src/callbacks.rs new file mode 100644 index 0000000000..314f9dce96 --- /dev/null +++ b/rust/qemu-api/src/callbacks.rs @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: MIT + +//! Utility functions to deal with callbacks from C to Rust. + +use std::{mem, ptr::NonNull}; + +/// Trait for functions (types implementing [`Fn`]) that can be used as +/// callbacks. These include both zero-capture closures and function pointers. +/// +/// In Rust, calling a function through the `Fn` trait normally requires a +/// `self` parameter, even though for zero-sized functions (including function +/// pointers) the type itself contains all necessary information to call the +/// function. This trait provides a `call` function that doesn't require `self`, +/// allowing zero-sized functions to be called using only their type. +/// +/// This enables zero-sized functions to be passed entirely through generic +/// parameters and resolved at compile-time. A typical use is a function +/// receiving an unused parameter of generic type `F` and calling it via +/// `F::call` or passing it to another function via `func::`. +/// +/// QEMU uses this trick to create wrappers to C callbacks. The wrappers +/// are needed to convert an opaque `*mut c_void` into a Rust reference, +/// but they only have a single opaque that they can use. The `FnCall` +/// trait makes it possible to use that opaque for `self` or any other +/// reference: +/// +/// ```ignore +/// // The compiler creates a new `rust_bh_cb` wrapper for each function +/// // passed to `qemu_bh_schedule_oneshot` below. +/// unsafe extern "C" fn rust_bh_cb FnCall<(&'a T,)>>( +/// opaque: *mut c_void, +/// ) { +/// // SAFETY: the opaque was passed as a reference to `T`. +/// F::call((unsafe { &*(opaque.cast::()) }, )) +/// } +/// +/// // The `_f` parameter is unused but it helps the compiler build the appropriate `F`. +/// // Using a reference allows usage in const context. +/// fn qemu_bh_schedule_oneshot FnCall<(&'a T,)>>(_f: &F, opaque: &T) { +/// let cb: unsafe extern "C" fn(*mut c_void) = rust_bh_cb::; +/// unsafe { +/// bindings::qemu_bh_schedule_oneshot(cb, opaque as *const T as *const c_void as *mut c_void) +/// } +/// } +/// ``` +/// +/// Each wrapper is a separate instance of `rust_bh_cb` and is therefore +/// compiled to a separate function ("monomorphization"). If you wanted +/// to pass `self` as the opaque value, the generic parameters would be +/// `rust_bh_cb::`. +/// +/// `Args` is a tuple type whose types are the arguments of the function, +/// while `R` is the returned type. +/// +/// # Examples +/// +/// ``` +/// # use qemu_api::callbacks::FnCall; +/// fn call_it FnCall<(&'a str,), String>>(_f: &F, s: &str) -> String { +/// F::call((s,)) +/// } +/// +/// let s: String = call_it(&str::to_owned, "hello world"); +/// assert_eq!(s, "hello world"); +/// ``` +/// +/// Note that the compiler will produce a different version of `call_it` for +/// each function that is passed to it. Therefore the argument is not really +/// used, except to decide what is `F` and what `F::call` does. +/// +/// Attempting to pass a non-zero-sized closure causes a compile-time failure: +/// +/// ```compile_fail +/// # use qemu_api::callbacks::FnCall; +/// # fn call_it<'a, F: FnCall<(&'a str,), String>>(_f: &F, s: &'a str) -> String { +/// # F::call((s,)) +/// # } +/// let x: &'static str = "goodbye world"; +/// call_it(&move |_| String::from(x), "hello workd"); +/// ``` +/// +/// # Safety +/// +/// Because `Self` is a zero-sized type, all instances of the type are +/// equivalent. However, in addition to this, `Self` must have no invariants +/// that could be violated by creating a reference to it. +/// +/// This is always true for zero-capture closures and function pointers, as long +/// as the code is able to name the function in the first place. +pub unsafe trait FnCall: 'static + Sync + Sized { + /// Referring to this internal constant asserts that the `Self` type is + /// zero-sized. Can be replaced by an inline const expression in + /// Rust 1.79.0+. + const ASSERT_ZERO_SIZED: () = { assert!(mem::size_of::() == 0) }; + + /// Call the function with the arguments in args. + fn call(a: Args) -> R; +} + +macro_rules! impl_call { + ($($args:ident,)* ) => ( + // SAFETY: because each function is treated as a separate type, + // accessing `FnCall` is only possible in code that would be + // allowed to call the function. + unsafe impl FnCall<($($args,)*), R> for F + where + F: 'static + Sync + Sized + Fn($($args, )*) -> R, + { + #[inline(always)] + fn call(a: ($($args,)*)) -> R { + let _: () = Self::ASSERT_ZERO_SIZED; + + // SAFETY: the safety of this method is the condition for implementing + // `FnCall`. As to the `NonNull` idiom to create a zero-sized type, + // see https://github.com/rust-lang/libs-team/issues/292. + let f: &'static F = unsafe { &*NonNull::::dangling().as_ptr() }; + let ($($args,)*) = a; + f($($args,)*) + } + } + ) +} + +impl_call!(_1, _2, _3, _4, _5,); +impl_call!(_1, _2, _3, _4,); +impl_call!(_1, _2, _3,); +impl_call!(_1, _2,); +impl_call!(_1,); +impl_call!(); + +#[cfg(test)] +mod tests { + use super::*; + + // The `_f` parameter is unused but it helps the compiler infer `F`. + fn do_test_call<'a, F: FnCall<(&'a str,), String>>(_f: &F) -> String { + F::call(("hello world",)) + } + + #[test] + fn test_call() { + assert_eq!(do_test_call(&str::to_owned), "hello world") + } +} diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs index 124bece044..4b43e02c0f 100644 --- a/rust/qemu-api/src/lib.rs +++ b/rust/qemu-api/src/lib.rs @@ -14,6 +14,7 @@ pub mod prelude; pub mod bitops; pub mod c_str; +pub mod callbacks; pub mod cell; pub mod irq; pub mod module; -- cgit 1.4.1 From 6b4f7b0705be31c7df6ea01c81a42a42950959a9 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 10 Dec 2024 12:53:22 +0100 Subject: rust: pl011: fix migration stream The Rust vmstate macros lack the type-safety of their C equivalents (so safe, much abstraction), and therefore they were predictably wrong. The registers have already been changed to 32-bits in the previous patch, but read_pos/read_count/read_trigger also have to be u32 instead of usize. The easiest way to do so is to let the FIFO use u32 indices instead of usize. My plan for making VMStateField typesafe is to have a trait to retrieve a basic VMStateField; for example something like vmstate_uint32 would become an implementation of the VMState trait on u32. Then you'd write something like "vmstate_of!(Type, field).with_version_id(2)". That is, vmstate_of retrieves the basic VMStateField and fills in the offset, and then more changes can be applied on top. Signed-off-by: Paolo Bonzini --- rust/hw/char/pl011/src/device.rs | 38 ++++++++++++++++++++++++++++------ rust/hw/char/pl011/src/device_class.rs | 10 ++++----- rust/qemu-api/src/vmstate.rs | 22 -------------------- 3 files changed, 37 insertions(+), 33 deletions(-) (limited to 'rust/qemu-api/src') diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index 090e5d6450..4d620b442e 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -31,7 +31,7 @@ const IBRD_MASK: u32 = 0xffff; const FBRD_MASK: u32 = 0x3f; /// QEMU sourced constant. -pub const PL011_FIFO_DEPTH: usize = 16_usize; +pub const PL011_FIFO_DEPTH: u32 = 16; #[derive(Clone, Copy, Debug)] enum DeviceId { @@ -56,6 +56,32 @@ impl DeviceId { const PL011_ID_LUMINARY: [c_uchar; 8] = [0x11, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1]; } +// FIFOs use 32-bit indices instead of usize, for compatibility with +// the migration stream produced by the C version of this device. +#[repr(transparent)] +#[derive(Debug, Default)] +pub struct Fifo([registers::Data; PL011_FIFO_DEPTH as usize]); + +impl Fifo { + const fn len(&self) -> u32 { + self.0.len() as u32 + } +} + +impl std::ops::IndexMut for Fifo { + fn index_mut(&mut self, idx: u32) -> &mut Self::Output { + &mut self.0[idx as usize] + } +} + +impl std::ops::Index for Fifo { + type Output = registers::Data; + + fn index(&self, idx: u32) -> &Self::Output { + &self.0[idx as usize] + } +} + #[repr(C)] #[derive(Debug, qemu_api_macros::Object, qemu_api_macros::offsets)] /// PL011 Device Model in QEMU @@ -73,14 +99,14 @@ pub struct PL011State { pub dmacr: u32, pub int_enabled: u32, pub int_level: u32, - pub read_fifo: [registers::Data; PL011_FIFO_DEPTH], + pub read_fifo: Fifo, pub ilpr: u32, pub ibrd: u32, pub fbrd: u32, pub ifl: u32, - pub read_pos: usize, - pub read_count: usize, - pub read_trigger: usize, + pub read_pos: u32, + pub read_count: u32, + pub read_trigger: u32, #[doc(alias = "chr")] pub char_backend: CharBackend, /// QEMU interrupts @@ -480,7 +506,7 @@ impl PL011State { } #[inline] - pub fn fifo_depth(&self) -> usize { + pub fn fifo_depth(&self) -> u32 { // Note: FIFO depth is expected to be power-of-2 if self.fifo_enabled() { return PL011_FIFO_DEPTH; diff --git a/rust/hw/char/pl011/src/device_class.rs b/rust/hw/char/pl011/src/device_class.rs index 975c3d42be..7f3ca89507 100644 --- a/rust/hw/char/pl011/src/device_class.rs +++ b/rust/hw/char/pl011/src/device_class.rs @@ -6,8 +6,8 @@ use core::ptr::NonNull; use std::os::raw::{c_int, c_void}; use qemu_api::{ - bindings::*, c_str, vmstate_clock, vmstate_fields, vmstate_int32, vmstate_subsections, - vmstate_uint32, vmstate_uint32_array, vmstate_unused, zeroable::Zeroable, + bindings::*, c_str, vmstate_clock, vmstate_fields, vmstate_subsections, vmstate_uint32, + vmstate_uint32_array, vmstate_unused, zeroable::Zeroable, }; use crate::device::{PL011State, PL011_FIFO_DEPTH}; @@ -64,9 +64,9 @@ pub static VMSTATE_PL011: VMStateDescription = VMStateDescription { vmstate_uint32!(ibrd, PL011State), vmstate_uint32!(fbrd, PL011State), vmstate_uint32!(ifl, PL011State), - vmstate_int32!(read_pos, PL011State), - vmstate_int32!(read_count, PL011State), - vmstate_int32!(read_trigger, PL011State), + vmstate_uint32!(read_pos, PL011State), + vmstate_uint32!(read_count, PL011State), + vmstate_uint32!(read_trigger, PL011State), }, subsections: vmstate_subsections! { VMSTATE_PL011_CLOCK diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs index 25c68b703e..63c897abcd 100644 --- a/rust/qemu-api/src/vmstate.rs +++ b/rust/qemu-api/src/vmstate.rs @@ -106,28 +106,6 @@ macro_rules! vmstate_uint32 { }}; } -#[doc(alias = "VMSTATE_INT32_V")] -#[macro_export] -macro_rules! vmstate_int32_v { - ($field_name:ident, $struct_name:ty, $version_id:expr) => {{ - $crate::vmstate_single!( - $field_name, - $struct_name, - $version_id, - ::core::ptr::addr_of!($crate::bindings::vmstate_info_int32), - ::core::mem::size_of::() - ) - }}; -} - -#[doc(alias = "VMSTATE_INT32")] -#[macro_export] -macro_rules! vmstate_int32 { - ($field_name:ident, $struct_name:ty) => {{ - $crate::vmstate_int32_v!($field_name, $struct_name, 0) - }}; -} - #[doc(alias = "VMSTATE_ARRAY")] #[macro_export] macro_rules! vmstate_array { -- cgit 1.4.1