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/qemu-api/src/qdev.rs | 143 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 rust/qemu-api/src/qdev.rs (limited to 'rust/qemu-api/src/qdev.rs') 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) }; +} -- 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/qdev.rs') 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 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/qdev.rs') 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