diff options
| author | Marc-André Lureau <marcandre.lureau@redhat.com> | 2025-09-08 12:49:56 +0200 |
|---|---|---|
| committer | Paolo Bonzini <pbonzini@redhat.com> | 2025-09-17 19:00:57 +0200 |
| commit | 5e588c9d08b0da64fab7f370e65744cb7a4174ef (patch) | |
| tree | a5b5408764725a1ce8d15b3d3a1b135ee104ec0d /rust/hw/core | |
| parent | ee4ffbf239cbd9de8c6b6cc33283b7a64a95a956 (diff) | |
| download | focaccia-qemu-5e588c9d08b0da64fab7f370e65744cb7a4174ef.tar.gz focaccia-qemu-5e588c9d08b0da64fab7f370e65744cb7a4174ef.zip | |
rust: split "hwcore" crate
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Link: https://lore.kernel.org/r/20250827104147.717203-16-marcandre.lureau@redhat.com Reviewed-by: Zhao Liu <zhao1.liu@intel.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Diffstat (limited to 'rust/hw/core')
| -rw-r--r-- | rust/hw/core/Cargo.toml | 26 | ||||
| l--------- | rust/hw/core/build.rs | 1 | ||||
| -rw-r--r-- | rust/hw/core/meson.build | 80 | ||||
| -rw-r--r-- | rust/hw/core/src/bindings.rs | 41 | ||||
| -rw-r--r-- | rust/hw/core/src/irq.rs | 115 | ||||
| -rw-r--r-- | rust/hw/core/src/lib.rs | 14 | ||||
| -rw-r--r-- | rust/hw/core/src/qdev.rs | 459 | ||||
| -rw-r--r-- | rust/hw/core/src/sysbus.rs | 122 | ||||
| -rw-r--r-- | rust/hw/core/tests/tests.rs | 156 | ||||
| -rw-r--r-- | rust/hw/core/wrapper.h | 32 |
10 files changed, 1046 insertions, 0 deletions
diff --git a/rust/hw/core/Cargo.toml b/rust/hw/core/Cargo.toml new file mode 100644 index 0000000000..0b35380264 --- /dev/null +++ b/rust/hw/core/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "hwcore" +version = "0.1.0" +description = "Rust bindings for QEMU/hwcore" +resolver = "2" +publish = false + +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +common = { path = "../../common" } +bql = { path = "../../bql" } +qom = { path = "../../qom" } +chardev = { path = "../../chardev" } +migration = { path = "../../migration" } +system = { path = "../../system" } +util = { path = "../../util" } +qemu_api_macros = { path = "../../qemu-api-macros" } + +[lints] +workspace = true diff --git a/rust/hw/core/build.rs b/rust/hw/core/build.rs new file mode 120000 index 0000000000..2a79ee31b8 --- /dev/null +++ b/rust/hw/core/build.rs @@ -0,0 +1 @@ +../../util/build.rs \ No newline at end of file diff --git a/rust/hw/core/meson.build b/rust/hw/core/meson.build new file mode 100644 index 0000000000..7dd1ade6f0 --- /dev/null +++ b/rust/hw/core/meson.build @@ -0,0 +1,80 @@ +_hwcore_bindgen_args = [] +c_enums = [ + 'DeviceCategory', + 'GpioPolarity', + 'MachineInitPhase', + 'ResetType', +] +foreach enum : c_enums + _hwcore_bindgen_args += ['--rustified-enum', enum] +endforeach + +blocked_type = [ + 'Chardev', + 'Error', + 'ObjectClass', + 'MemoryRegion', + 'VMStateDescription', +] +foreach type: blocked_type + _hwcore_bindgen_args += ['--blocklist-type', type] +endforeach + +c_bitfields = [ + 'ClockEvent', +] +foreach enum : c_bitfields + _hwcore_bindgen_args += ['--bitfield-enum', enum] +endforeach + +# TODO: Remove this comment when the clang/libclang mismatch issue is solved. +# +# Rust bindings generation with `bindgen` might fail in some cases where the +# detected `libclang` does not match the expected `clang` version/target. In +# this case you must pass the path to `clang` and `libclang` to your build +# command invocation using the environment variables CLANG_PATH and +# LIBCLANG_PATH +_hwcore_bindings_inc_rs = rust.bindgen( + input: 'wrapper.h', + dependencies: common_ss.all_dependencies(), + output: 'bindings.inc.rs', + include_directories: bindings_incdir, + bindgen_version: ['>=0.60.0'], + args: bindgen_args_common + _hwcore_bindgen_args, +) + +_hwcore_rs = static_library( + 'hwcore', + structured_sources( + [ + 'src/lib.rs', + 'src/bindings.rs', + 'src/irq.rs', + 'src/qdev.rs', + 'src/sysbus.rs', + ], + {'.': _hwcore_bindings_inc_rs} + ), + override_options: ['rust_std=2021', 'build.rust_std=2021'], + rust_abi: 'rust', + link_with: [_bql_rs, _chardev_rs, _migration_rs, _qom_rs, _system_rs, _util_rs], + dependencies: [qemu_api_macros, common_rs], +) + +hwcore_rs = declare_dependency(link_with: [_hwcore_rs], + dependencies: [qom_rs, hwcore]) + +test('rust-hwcore-rs-integration', + executable( + 'rust-hwcore-rs-integration', + files('tests/tests.rs'), + override_options: ['rust_std=2021', 'build.rust_std=2021'], + rust_args: ['--test'], + install: false, + dependencies: [common_rs, hwcore_rs, bql_rs, migration_rs, qemu_api_macros, util_rs]), + args: [ + '--test', '--test-threads', '1', + '--format', 'pretty', + ], + protocol: 'rust', + suite: ['unit', 'rust']) diff --git a/rust/hw/core/src/bindings.rs b/rust/hw/core/src/bindings.rs new file mode 100644 index 0000000000..919c02b56a --- /dev/null +++ b/rust/hw/core/src/bindings.rs @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#![allow( + dead_code, + improper_ctypes_definitions, + improper_ctypes, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unnecessary_transmutes, + unsafe_op_in_unsafe_fn, + clippy::pedantic, + clippy::restriction, + clippy::style, + clippy::missing_const_for_fn, + clippy::ptr_offset_with_cast, + clippy::useless_transmute, + clippy::missing_safety_doc, + clippy::too_many_arguments +)] + +use chardev::bindings::Chardev; +use common::Zeroable; +use migration::bindings::VMStateDescription; +use qom::bindings::ObjectClass; +use system::bindings::MemoryRegion; +use util::bindings::Error; + +#[cfg(MESON)] +include!("bindings.inc.rs"); + +#[cfg(not(MESON))] +include!(concat!(env!("OUT_DIR"), "/bindings.inc.rs")); + +unsafe impl Send for Property {} +unsafe impl Sync for Property {} + +unsafe impl Send for TypeInfo {} +unsafe impl Sync for TypeInfo {} + +unsafe impl Zeroable for Property__bindgen_ty_1 {} +unsafe impl Zeroable for Property {} diff --git a/rust/hw/core/src/irq.rs b/rust/hw/core/src/irq.rs new file mode 100644 index 0000000000..fead2bbe8e --- /dev/null +++ b/rust/hw/core/src/irq.rs @@ -0,0 +1,115 @@ +// Copyright 2024 Red Hat, Inc. +// Author(s): Paolo Bonzini <pbonzini@redhat.com> +// SPDX-License-Identifier: GPL-2.0-or-later + +//! Bindings for interrupt sources + +use std::{ + ffi::{c_int, CStr}, + marker::PhantomData, + ptr, +}; + +use bql::BqlCell; +use common::Opaque; +use qom::{prelude::*, ObjectClass}; + +use crate::bindings::{self, qemu_set_irq}; + +/// An opaque wrapper around [`bindings::IRQState`]. +#[repr(transparent)] +#[derive(Debug, qemu_api_macros::Wrapper)] +pub struct IRQState(Opaque<bindings::IRQState>); + +/// Interrupt sources are used by devices to pass changes to a value (typically +/// a boolean). The interrupt sink is usually an interrupt controller or +/// GPIO controller. +/// +/// As far as devices are concerned, interrupt sources are always active-high: +/// for example, `InterruptSource<bool>`'s [`raise`](InterruptSource::raise) +/// method sends a `true` value to the sink. If the guest has to see a +/// different polarity, that change is performed by the board between the +/// device and the interrupt controller. +/// +/// Interrupts are implemented as a pointer to the interrupt "sink", which has +/// type [`IRQState`]. A device exposes its source as a QOM link property using +/// a function such as [`crate::sysbus::SysBusDeviceMethods::init_irq`], and +/// initially leaves the pointer to a NULL value, representing an unconnected +/// interrupt. To connect it, whoever creates the device fills the pointer with +/// the sink's `IRQState *`, for example using `sysbus_connect_irq`. Because +/// devices are generally shared objects, interrupt sources are an example of +/// the interior mutability pattern. +/// +/// Interrupt sources can only be triggered under the Big QEMU Lock; `BqlCell` +/// allows access from whatever thread has it. +#[derive(Debug)] +#[repr(transparent)] +pub struct InterruptSource<T = bool> +where + c_int: From<T>, +{ + cell: BqlCell<*mut bindings::IRQState>, + _marker: PhantomData<T>, +} + +// SAFETY: the implementation asserts via `BqlCell` that the BQL is taken +unsafe impl<T> Sync for InterruptSource<T> where c_int: From<T> {} + +impl InterruptSource<bool> { + /// Send a low (`false`) value to the interrupt sink. + pub fn lower(&self) { + self.set(false); + } + + /// Send a high-low pulse to the interrupt sink. + pub fn pulse(&self) { + self.set(true); + self.set(false); + } + + /// Send a high (`true`) value to the interrupt sink. + pub fn raise(&self) { + self.set(true); + } +} + +impl<T> InterruptSource<T> +where + c_int: From<T>, +{ + /// Send `level` to the interrupt sink. + pub fn set(&self, level: T) { + let ptr = self.cell.get(); + // SAFETY: the pointer is retrieved under the BQL and remains valid + // until the BQL is released, which is after qemu_set_irq() is entered. + unsafe { + qemu_set_irq(ptr, level.into()); + } + } + + pub(crate) const fn as_ptr(&self) -> *mut *mut bindings::IRQState { + self.cell.as_ptr() + } + + pub(crate) const fn slice_as_ptr(slice: &[Self]) -> *mut *mut bindings::IRQState { + assert!(!slice.is_empty()); + slice[0].as_ptr() + } +} + +impl Default for InterruptSource { + fn default() -> Self { + InterruptSource { + cell: BqlCell::new(ptr::null_mut()), + _marker: PhantomData, + } + } +} + +unsafe impl ObjectType for IRQState { + type Class = ObjectClass; + const TYPE_NAME: &'static CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_IRQ) }; +} + +qom_isa!(IRQState: Object); diff --git a/rust/hw/core/src/lib.rs b/rust/hw/core/src/lib.rs new file mode 100644 index 0000000000..c5588d9bc2 --- /dev/null +++ b/rust/hw/core/src/lib.rs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pub use qom; + +pub mod bindings; + +mod irq; +pub use irq::*; + +mod qdev; +pub use qdev::*; + +mod sysbus; +pub use sysbus::*; diff --git a/rust/hw/core/src/qdev.rs b/rust/hw/core/src/qdev.rs new file mode 100644 index 0000000000..8e9702ce0b --- /dev/null +++ b/rust/hw/core/src/qdev.rs @@ -0,0 +1,459 @@ +// Copyright 2024, Linaro Limited +// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org> +// SPDX-License-Identifier: GPL-2.0-or-later + +//! Bindings to create devices and access device functionality from Rust. + +use std::{ + ffi::{c_int, c_void, CStr, CString}, + ptr::NonNull, +}; + +use chardev::Chardev; +use common::{callbacks::FnCall, Opaque}; +use migration::{impl_vmstate_c_struct, VMStateDescription}; +use qom::{prelude::*, ObjectClass, ObjectImpl, Owned, ParentInit}; +use util::{Error, Result}; + +pub use crate::bindings::{ClockEvent, DeviceClass, Property, ResetType}; +use crate::{ + bindings::{self, qdev_init_gpio_in, qdev_init_gpio_out, ResettableClass}, + irq::InterruptSource, +}; + +/// A safe wrapper around [`bindings::Clock`]. +#[repr(transparent)] +#[derive(Debug, qemu_api_macros::Wrapper)] +pub struct Clock(Opaque<bindings::Clock>); + +unsafe impl Send for Clock {} +unsafe impl Sync for Clock {} + +/// A safe wrapper around [`bindings::DeviceState`]. +#[repr(transparent)] +#[derive(Debug, qemu_api_macros::Wrapper)] +pub struct DeviceState(Opaque<bindings::DeviceState>); + +unsafe impl Send for DeviceState {} +unsafe impl Sync for DeviceState {} + +/// Trait providing the contents of the `ResettablePhases` struct, +/// which is part of the QOM `Resettable` interface. +pub trait ResettablePhasesImpl { + /// If not None, this is called when the object enters reset. It + /// can reset local state of the object, but it must not do anything that + /// has a side-effect on other objects, such as raising or lowering an + /// [`InterruptSource`], or reading or writing guest memory. It takes the + /// reset's type as argument. + const ENTER: Option<fn(&Self, ResetType)> = None; + + /// If not None, this is called when the object for entry into reset, once + /// every object in the system which is being reset has had its + /// `ResettablePhasesImpl::ENTER` method called. At this point devices + /// can do actions that affect other objects. + /// + /// If in doubt, implement this method. + const HOLD: Option<fn(&Self, ResetType)> = None; + + /// If not None, this phase is called when the object leaves the reset + /// state. Actions affecting other objects are permitted. + const EXIT: Option<fn(&Self, ResetType)> = None; +} + +/// # 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_resettable_enter_fn<T: ResettablePhasesImpl>( + obj: *mut bindings::Object, + typ: ResetType, +) { + let state = NonNull::new(obj).unwrap().cast::<T>(); + T::ENTER.unwrap()(unsafe { state.as_ref() }, typ); +} + +/// # 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_resettable_hold_fn<T: ResettablePhasesImpl>( + obj: *mut bindings::Object, + typ: ResetType, +) { + let state = NonNull::new(obj).unwrap().cast::<T>(); + T::HOLD.unwrap()(unsafe { state.as_ref() }, typ); +} + +/// # 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_resettable_exit_fn<T: ResettablePhasesImpl>( + obj: *mut bindings::Object, + typ: ResetType, +) { + let state = NonNull::new(obj).unwrap().cast::<T>(); + T::EXIT.unwrap()(unsafe { state.as_ref() }, typ); +} + +/// Helper trait to return pointer to a [`bindings::PropertyInfo`] for a type. +/// +/// This trait is used by [`qemu_api_macros::Device`] derive macro. +/// +/// Base types that already have `qdev_prop_*` globals in the QEMU API should +/// use those values as exported by the [`bindings`] module, instead of +/// redefining them. +/// +/// # Safety +/// +/// This trait is marked as `unsafe` because currently having a `const` refer to +/// an `extern static` as a reference instead of a raw pointer results in this +/// compiler error: +/// +/// ```text +/// constructing invalid value: encountered reference to `extern` static in `const` +/// ``` +/// +/// This is because the compiler generally might dereference a normal reference +/// during const evaluation, but not in this case (if it did, it'd need to +/// dereference the raw pointer so this would fail to compile). +/// +/// It is the implementer's responsibility to provide a valid +/// [`bindings::PropertyInfo`] pointer for the trait implementation to be safe. +pub unsafe trait QDevProp { + const VALUE: *const bindings::PropertyInfo; +} + +/// Use [`bindings::qdev_prop_bool`] for `bool`. +unsafe impl QDevProp for bool { + const VALUE: *const bindings::PropertyInfo = unsafe { &bindings::qdev_prop_bool }; +} + +/// Use [`bindings::qdev_prop_uint64`] for `u64`. +unsafe impl QDevProp for u64 { + const VALUE: *const bindings::PropertyInfo = unsafe { &bindings::qdev_prop_uint64 }; +} + +/// Use [`bindings::qdev_prop_chr`] for [`chardev::CharBackend`]. +unsafe impl QDevProp for chardev::CharBackend { + const VALUE: *const bindings::PropertyInfo = unsafe { &bindings::qdev_prop_chr }; +} + +/// Trait to define device properties. +/// +/// # Safety +/// +/// Caller is responsible for the validity of properties array. +pub unsafe trait DevicePropertiesImpl { + /// An array providing the properties that the user can set on the + /// device. + const PROPERTIES: &'static [Property] = &[]; +} + +/// Trait providing the contents of [`DeviceClass`]. +pub trait DeviceImpl: + ObjectImpl + ResettablePhasesImpl + DevicePropertiesImpl + IsA<DeviceState> +{ + /// _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<fn(&Self) -> Result<()>> = None; + + /// A `VMStateDescription` providing the migration format for the device + /// Not a `const` because referencing statics in constants is unstable + /// until Rust 1.83.0. + const VMSTATE: Option<VMStateDescription<Self>> = None; +} + +/// # Safety +/// +/// This function is only called through the QOM machinery and +/// used by `DeviceClass::class_init`. +/// 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<T: DeviceImpl>( + dev: *mut bindings::DeviceState, + errp: *mut *mut util::bindings::Error, +) { + let state = NonNull::new(dev).unwrap().cast::<T>(); + let result = T::REALIZE.unwrap()(unsafe { state.as_ref() }); + unsafe { + Error::ok_or_propagate(result, errp); + } +} + +unsafe impl InterfaceType for ResettableClass { + const TYPE_NAME: &'static CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_RESETTABLE_INTERFACE) }; +} + +impl ResettableClass { + /// Fill in the virtual methods of `ResettableClass` based on the + /// definitions in the `ResettablePhasesImpl` trait. + pub fn class_init<T: ResettablePhasesImpl>(&mut self) { + if <T as ResettablePhasesImpl>::ENTER.is_some() { + self.phases.enter = Some(rust_resettable_enter_fn::<T>); + } + if <T as ResettablePhasesImpl>::HOLD.is_some() { + self.phases.hold = Some(rust_resettable_hold_fn::<T>); + } + if <T as ResettablePhasesImpl>::EXIT.is_some() { + self.phases.exit = Some(rust_resettable_exit_fn::<T>); + } + } +} + +impl DeviceClass { + /// Fill in the virtual methods of `DeviceClass` based on the definitions in + /// the `DeviceImpl` trait. + pub fn class_init<T: DeviceImpl>(&mut self) { + if <T as DeviceImpl>::REALIZE.is_some() { + self.realize = Some(rust_realize_fn::<T>); + } + if let Some(ref vmsd) = <T as DeviceImpl>::VMSTATE { + self.vmsd = vmsd.as_ref(); + } + let prop = <T as DevicePropertiesImpl>::PROPERTIES; + if !prop.is_empty() { + unsafe { + bindings::device_class_set_props_n(self, prop.as_ptr(), prop.len()); + } + } + + ResettableClass::cast::<DeviceState>(self).class_init::<T>(); + self.parent_class.class_init::<T>(); + } +} + +#[macro_export] +macro_rules! define_property { + ($name:expr, $state:ty, $field:ident, $prop:expr, $type:ty, bit = $bitnr:expr, default = $defval:expr$(,)*) => { + $crate::bindings::Property { + // use associated function syntax for type checking + name: ::std::ffi::CStr::as_ptr($name), + info: $prop, + offset: ::std::mem::offset_of!($state, $field) as isize, + bitnr: $bitnr, + set_default: true, + defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval as u64 }, + ..::common::zeroable::Zeroable::ZERO + } + }; + ($name:expr, $state:ty, $field:ident, $prop:expr, $type:ty, default = $defval:expr$(,)*) => { + $crate::bindings::Property { + // use associated function syntax for type checking + name: ::std::ffi::CStr::as_ptr($name), + info: $prop, + offset: ::std::mem::offset_of!($state, $field) as isize, + set_default: true, + defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval as u64 }, + ..::common::zeroable::Zeroable::ZERO + } + }; + ($name:expr, $state:ty, $field:ident, $prop:expr, $type:ty$(,)*) => { + $crate::bindings::Property { + // use associated function syntax for type checking + name: ::std::ffi::CStr::as_ptr($name), + info: $prop, + offset: ::std::mem::offset_of!($state, $field) as isize, + set_default: false, + ..::common::zeroable::Zeroable::ZERO + } + }; +} + +#[macro_export] +macro_rules! declare_properties { + ($ident:ident, $($prop:expr),*$(,)*) => { + pub static $ident: [$crate::bindings::Property; { + let mut len = 0; + $({ + _ = stringify!($prop); + len += 1; + })* + len + }] = [ + $($prop),*, + ]; + }; +} + +unsafe impl ObjectType for DeviceState { + type Class = DeviceClass; + const TYPE_NAME: &'static CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_DEVICE) }; +} + +qom_isa!(DeviceState: Object); + +/// Initialization methods take a [`ParentInit`] and can be called as +/// associated functions. +impl DeviceState { + /// Add an input clock named `name`. Invoke the callback with + /// `self` as the first parameter for the events that are requested. + /// + /// The resulting clock is added as a child of `self`, but it also + /// stays alive until after `Drop::drop` is called because C code + /// keeps an extra reference to it until `device_finalize()` calls + /// `qdev_finalize_clocklist()`. Therefore (unlike most cases in + /// which Rust code has a reference to a child object) it would be + /// possible for this function to return a `&Clock` too. + #[inline] + pub fn init_clock_in<T: DeviceImpl, F: for<'a> FnCall<(&'a T, ClockEvent)>>( + this: &mut ParentInit<T>, + name: &str, + _cb: &F, + events: ClockEvent, + ) -> Owned<Clock> + where + T::ParentType: IsA<DeviceState>, + { + fn do_init_clock_in( + dev: &DeviceState, + name: &str, + cb: Option<unsafe extern "C" fn(*mut c_void, ClockEvent)>, + events: ClockEvent, + ) -> Owned<Clock> { + assert!(bql::is_locked()); + + // SAFETY: the clock is heap allocated, but qdev_init_clock_in() + // does not gift the reference to its caller; so use Owned::from to + // add one. The callback is disabled automatically when the clock + // is unparented, which happens before the device is finalized. + unsafe { + let cstr = CString::new(name).unwrap(); + let clk = bindings::qdev_init_clock_in( + dev.0.as_mut_ptr(), + cstr.as_ptr(), + cb, + dev.0.as_void_ptr(), + events.0, + ); + + let clk: &Clock = Clock::from_raw(clk); + Owned::from(clk) + } + } + + let cb: Option<unsafe extern "C" fn(*mut c_void, ClockEvent)> = if F::is_some() { + unsafe extern "C" fn rust_clock_cb<T, F: for<'a> FnCall<(&'a T, ClockEvent)>>( + opaque: *mut c_void, + event: ClockEvent, + ) { + // SAFETY: the opaque is "this", which is indeed a pointer to T + F::call((unsafe { &*(opaque.cast::<T>()) }, event)) + } + Some(rust_clock_cb::<T, F>) + } else { + None + }; + + do_init_clock_in(unsafe { this.upcast_mut() }, name, cb, events) + } + + /// Add an output clock named `name`. + /// + /// The resulting clock is added as a child of `self`, but it also + /// stays alive until after `Drop::drop` is called because C code + /// keeps an extra reference to it until `device_finalize()` calls + /// `qdev_finalize_clocklist()`. Therefore (unlike most cases in + /// which Rust code has a reference to a child object) it would be + /// possible for this function to return a `&Clock` too. + #[inline] + pub fn init_clock_out<T: DeviceImpl>(this: &mut ParentInit<T>, name: &str) -> Owned<Clock> + where + T::ParentType: IsA<DeviceState>, + { + unsafe { + let cstr = CString::new(name).unwrap(); + let dev: &mut DeviceState = this.upcast_mut(); + let clk = bindings::qdev_init_clock_out(dev.0.as_mut_ptr(), cstr.as_ptr()); + + let clk: &Clock = Clock::from_raw(clk); + Owned::from(clk) + } + } +} + +/// Trait for methods exposed by the [`DeviceState`] class. The methods can be +/// called on all objects that have the trait `IsA<DeviceState>`. +/// +/// The trait should only be used through the blanket implementation, +/// which guarantees safety via `IsA`. +pub trait DeviceMethods: ObjectDeref +where + Self::Target: IsA<DeviceState>, +{ + fn prop_set_chr(&self, propname: &str, chr: &Owned<Chardev>) { + assert!(bql::is_locked()); + let c_propname = CString::new(propname).unwrap(); + let chr: &Chardev = chr; + unsafe { + bindings::qdev_prop_set_chr( + self.upcast().as_mut_ptr(), + c_propname.as_ptr(), + chr.as_mut_ptr(), + ); + } + } + + fn init_gpio_in<F: for<'a> FnCall<(&'a Self::Target, u32, u32)>>( + &self, + num_lines: u32, + _cb: F, + ) { + fn do_init_gpio_in( + dev: &DeviceState, + num_lines: u32, + gpio_in_cb: unsafe extern "C" fn(*mut c_void, c_int, c_int), + ) { + unsafe { + qdev_init_gpio_in(dev.as_mut_ptr(), Some(gpio_in_cb), num_lines as c_int); + } + } + + const { assert!(F::IS_SOME) }; + unsafe extern "C" fn rust_irq_handler<T, F: for<'a> FnCall<(&'a T, u32, u32)>>( + opaque: *mut c_void, + line: c_int, + level: c_int, + ) { + // SAFETY: the opaque was passed as a reference to `T` + F::call((unsafe { &*(opaque.cast::<T>()) }, line as u32, level as u32)) + } + + let gpio_in_cb: unsafe extern "C" fn(*mut c_void, c_int, c_int) = + rust_irq_handler::<Self::Target, F>; + + do_init_gpio_in(self.upcast(), num_lines, gpio_in_cb); + } + + fn init_gpio_out(&self, pins: &[InterruptSource]) { + unsafe { + qdev_init_gpio_out( + self.upcast().as_mut_ptr(), + InterruptSource::slice_as_ptr(pins), + pins.len() as c_int, + ); + } + } +} + +impl<R: ObjectDeref> DeviceMethods for R where R::Target: IsA<DeviceState> {} + +unsafe impl ObjectType for Clock { + type Class = ObjectClass; + const TYPE_NAME: &'static CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_CLOCK) }; +} + +qom_isa!(Clock: Object); + +impl_vmstate_c_struct!(Clock, bindings::vmstate_clock); diff --git a/rust/hw/core/src/sysbus.rs b/rust/hw/core/src/sysbus.rs new file mode 100644 index 0000000000..dda71ebda7 --- /dev/null +++ b/rust/hw/core/src/sysbus.rs @@ -0,0 +1,122 @@ +// Copyright 2024 Red Hat, Inc. +// Author(s): Paolo Bonzini <pbonzini@redhat.com> +// SPDX-License-Identifier: GPL-2.0-or-later + +//! Bindings to access `sysbus` functionality from Rust. + +use std::{ffi::CStr, ptr::addr_of_mut}; + +pub use bindings::SysBusDeviceClass; +use common::Opaque; +use qom::{prelude::*, Owned}; +use system::MemoryRegion; + +use crate::{ + bindings, + irq::{IRQState, InterruptSource}, + qdev::{DeviceImpl, DeviceState}, +}; + +/// A safe wrapper around [`bindings::SysBusDevice`]. +#[repr(transparent)] +#[derive(Debug, qemu_api_macros::Wrapper)] +pub struct SysBusDevice(Opaque<bindings::SysBusDevice>); + +unsafe impl Send for SysBusDevice {} +unsafe impl Sync for SysBusDevice {} + +unsafe impl ObjectType for SysBusDevice { + type Class = SysBusDeviceClass; + const TYPE_NAME: &'static CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_SYS_BUS_DEVICE) }; +} + +qom_isa!(SysBusDevice: DeviceState, Object); + +// TODO: add virtual methods +pub trait SysBusDeviceImpl: DeviceImpl + IsA<SysBusDevice> {} + +impl SysBusDeviceClass { + /// Fill in the virtual methods of `SysBusDeviceClass` based on the + /// definitions in the `SysBusDeviceImpl` trait. + pub fn class_init<T: SysBusDeviceImpl>(self: &mut SysBusDeviceClass) { + self.parent_class.class_init::<T>(); + } +} + +/// Trait for methods of [`SysBusDevice`] and its subclasses. +pub trait SysBusDeviceMethods: ObjectDeref +where + Self::Target: IsA<SysBusDevice>, +{ + /// Expose a memory region to the board so that it can give it an address + /// in guest memory. Note that the ordering of calls to `init_mmio` is + /// important, since whoever creates the sysbus device will refer to the + /// region with a number that corresponds to the order of calls to + /// `init_mmio`. + fn init_mmio(&self, iomem: &MemoryRegion) { + assert!(bql::is_locked()); + unsafe { + bindings::sysbus_init_mmio(self.upcast().as_mut_ptr(), iomem.as_mut_ptr()); + } + } + + /// Expose an interrupt source outside the device as a qdev GPIO output. + /// Note that the ordering of calls to `init_irq` is important, since + /// whoever creates the sysbus device will refer to the interrupts with + /// a number that corresponds to the order of calls to `init_irq`. + fn init_irq(&self, irq: &InterruptSource) { + assert!(bql::is_locked()); + unsafe { + bindings::sysbus_init_irq(self.upcast().as_mut_ptr(), irq.as_ptr()); + } + } + + // TODO: do we want a type like GuestAddress here? + fn mmio_addr(&self, id: u32) -> Option<u64> { + assert!(bql::is_locked()); + // SAFETY: the BQL ensures that no one else writes to sbd.mmio[], and + // the SysBusDevice must be initialized to get an IsA<SysBusDevice>. + let sbd = unsafe { *self.upcast().as_ptr() }; + let id: usize = id.try_into().unwrap(); + if sbd.mmio[id].memory.is_null() { + None + } else { + Some(sbd.mmio[id].addr) + } + } + + // TODO: do we want a type like GuestAddress here? + fn mmio_map(&self, id: u32, addr: u64) { + assert!(bql::is_locked()); + let id: i32 = id.try_into().unwrap(); + unsafe { + bindings::sysbus_mmio_map(self.upcast().as_mut_ptr(), id, addr); + } + } + + // Owned<> is used here because sysbus_connect_irq (via + // object_property_set_link) adds a reference to the IRQState, + // which can prolong its life + fn connect_irq(&self, id: u32, irq: &Owned<IRQState>) { + assert!(bql::is_locked()); + let id: i32 = id.try_into().unwrap(); + let irq: &IRQState = irq; + unsafe { + bindings::sysbus_connect_irq(self.upcast().as_mut_ptr(), id, irq.as_mut_ptr()); + } + } + + fn sysbus_realize(&self) { + // TODO: return an Error + assert!(bql::is_locked()); + unsafe { + bindings::sysbus_realize( + self.upcast().as_mut_ptr(), + addr_of_mut!(util::bindings::error_fatal), + ); + } + } +} + +impl<R: ObjectDeref> SysBusDeviceMethods for R where R::Target: IsA<SysBusDevice> {} diff --git a/rust/hw/core/tests/tests.rs b/rust/hw/core/tests/tests.rs new file mode 100644 index 0000000000..21ee301fa6 --- /dev/null +++ b/rust/hw/core/tests/tests.rs @@ -0,0 +1,156 @@ +// Copyright 2024, Linaro Limited +// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org> +// SPDX-License-Identifier: GPL-2.0-or-later + +use std::{ffi::CStr, ptr::addr_of}; + +use bql::BqlCell; +use hwcore::{DeviceImpl, DeviceState, ResettablePhasesImpl, SysBusDevice}; +use migration::{VMStateDescription, VMStateDescriptionBuilder}; +use qom::{prelude::*, ObjectImpl, ParentField}; +use util::bindings::{module_call_init, module_init_type}; + +// Test that macros can compile. +pub const VMSTATE: VMStateDescription<DummyState> = VMStateDescriptionBuilder::<DummyState>::new() + .name(c"name") + .unmigratable() + .build(); + +#[repr(C)] +#[derive(qemu_api_macros::Object, qemu_api_macros::Device)] +pub struct DummyState { + parent: ParentField<DeviceState>, + #[property(rename = "migrate-clk", default = true)] + migrate_clock: bool, +} + +qom_isa!(DummyState: Object, DeviceState); + +pub struct DummyClass { + parent_class: <DeviceState as ObjectType>::Class, +} + +impl DummyClass { + pub fn class_init<T: DeviceImpl>(self: &mut DummyClass) { + self.parent_class.class_init::<T>(); + } +} + +unsafe impl ObjectType for DummyState { + type Class = DummyClass; + const TYPE_NAME: &'static CStr = c"dummy"; +} + +impl ObjectImpl for DummyState { + type ParentType = DeviceState; + const ABSTRACT: bool = false; + const CLASS_INIT: fn(&mut DummyClass) = DummyClass::class_init::<Self>; +} + +impl ResettablePhasesImpl for DummyState {} + +impl DeviceImpl for DummyState { + const VMSTATE: Option<VMStateDescription<Self>> = Some(VMSTATE); +} + +#[repr(C)] +#[derive(qemu_api_macros::Object, qemu_api_macros::Device)] +pub struct DummyChildState { + parent: ParentField<DummyState>, +} + +qom_isa!(DummyChildState: Object, DeviceState, DummyState); + +pub struct DummyChildClass { + parent_class: <DummyState as ObjectType>::Class, +} + +unsafe impl ObjectType for DummyChildState { + type Class = DummyChildClass; + const TYPE_NAME: &'static CStr = c"dummy_child"; +} + +impl ObjectImpl for DummyChildState { + type ParentType = DummyState; + const ABSTRACT: bool = false; + const CLASS_INIT: fn(&mut DummyChildClass) = DummyChildClass::class_init::<Self>; +} + +impl ResettablePhasesImpl for DummyChildState {} +impl DeviceImpl for DummyChildState {} + +impl DummyChildClass { + pub fn class_init<T: DeviceImpl>(self: &mut DummyChildClass) { + self.parent_class.class_init::<T>(); + } +} + +fn init_qom() { + static ONCE: BqlCell<bool> = BqlCell::new(false); + + bql::start_test(); + if !ONCE.get() { + unsafe { + module_call_init(module_init_type::MODULE_INIT_QOM); + } + ONCE.set(true); + } +} + +#[test] +/// Create and immediately drop an instance. +fn test_object_new() { + init_qom(); + drop(DummyState::new()); + drop(DummyChildState::new()); +} + +#[test] +#[allow(clippy::redundant_clone)] +/// Create, clone and then drop an instance. +fn test_clone() { + init_qom(); + let p = DummyState::new(); + assert_eq!(p.clone().typename(), "dummy"); + drop(p); +} + +#[test] +/// Try invoking a method on an object. +fn test_typename() { + init_qom(); + let p = DummyState::new(); + assert_eq!(p.typename(), "dummy"); +} + +// 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::<SysBusDevice>(); +// +// 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 = DummyState::new(); + let p_ptr: *mut DummyState = p.as_mut_ptr(); + let p_ref: &mut DummyState = unsafe { &mut *p_ptr }; + + let obj_ref: &Object = p_ref.upcast(); + assert_eq!(addr_of!(*obj_ref), p_ptr.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_ptr.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_ptr.cast()); + } +} diff --git a/rust/hw/core/wrapper.h b/rust/hw/core/wrapper.h new file mode 100644 index 0000000000..3bdbd1249e --- /dev/null +++ b/rust/hw/core/wrapper.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* + * This header file is meant to be used as input to the `bindgen` application + * in order to generate C FFI compatible Rust bindings. + */ + +#ifndef __CLANG_STDATOMIC_H +#define __CLANG_STDATOMIC_H +/* + * Fix potential missing stdatomic.h error in case bindgen does not insert the + * correct libclang header paths on its own. We do not use stdatomic.h symbols + * in QEMU code, so it's fine to declare dummy types instead. + */ +typedef enum memory_order { + memory_order_relaxed, + memory_order_consume, + memory_order_acquire, + memory_order_release, + memory_order_acq_rel, + memory_order_seq_cst, +} memory_order; +#endif /* __CLANG_STDATOMIC_H */ + +#include "qemu/osdep.h" + +#include "hw/sysbus.h" +#include "hw/clock.h" +#include "hw/qdev-clock.h" +#include "hw/qdev-properties.h" +#include "hw/qdev-properties-system.h" +#include "hw/irq.h" |