qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Paolo Bonzini <pbonzini@redhat.com>
To: qemu-devel@nongnu.org
Subject: [PULL 07/25] rust: timer: wrap QEMUTimer with Opaque<> and express pinning requirements
Date: Sun,  9 Mar 2025 11:31:01 +0100	[thread overview]
Message-ID: <20250309103120.1116448-8-pbonzini@redhat.com> (raw)
In-Reply-To: <20250309103120.1116448-1-pbonzini@redhat.com>

Timers must be pinned in memory, because modify() stores a pointer to them
in the TimerList.  To express this requirement, change init_full() to take
a pinned reference.  Because the only way to obtain a Timer is through
Timer::new(), which is unsafe, modify() can assume that the timer it got
was later initialized; and because the initialization takes a Pin<&mut
Timer> modify() can assume that the timer is pinned.  In the future the
pinning requirement will be expressed through the pin_init crate instead.

Note that Timer is a bit different from other users of Opaque, in that
it is created in Rust code rather than C code.  This is why it has to
use the unsafe constructors provided by Opaque; and in fact Timer::new()
is also unsafe, because it leaves it to the caller to invoke init_full()
before modify().  Without a call to init_full(), modify() will cause a
NULL pointer dereference.

An alternative could be to combine new() + init_full() by returning a
pinned box; however, using a reference makes it easier to express
the requirement that the opaque outlives the timer.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 meson.build                    |  7 -----
 rust/hw/timer/hpet/src/hpet.rs | 10 ++++++--
 rust/qemu-api/src/timer.rs     | 47 ++++++++++++++++++++++++++--------
 3 files changed, 44 insertions(+), 20 deletions(-)

diff --git a/meson.build b/meson.build
index 67ec2b78319..6da4eb317c2 100644
--- a/meson.build
+++ b/meson.build
@@ -4100,13 +4100,6 @@ if have_rust
   foreach enum : c_bitfields
     bindgen_args += ['--bitfield-enum', enum]
   endforeach
-  c_nocopy = [
-    'QEMUTimer',
-  ]
-  # Used to customize Drop trait
-  foreach struct : c_nocopy
-    bindgen_args += ['--no-copy', struct]
-  endforeach
 
   # TODO: Remove this comment when the clang/libclang mismatch issue is solved.
   #
diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs
index 02c81ae048f..3d3d6ef8eec 100644
--- a/rust/hw/timer/hpet/src/hpet.rs
+++ b/rust/hw/timer/hpet/src/hpet.rs
@@ -4,6 +4,7 @@
 
 use std::{
     ffi::CStr,
+    pin::Pin,
     ptr::{addr_of_mut, null_mut, NonNull},
     slice::from_ref,
 };
@@ -184,7 +185,9 @@ impl HPETTimer {
     fn init(&mut self, index: usize, state: &HPETState) {
         *self = HPETTimer {
             index,
-            qemu_timer: Timer::new(),
+            // SAFETY: the HPETTimer will only be used after the timer
+            // is initialized below.
+            qemu_timer: unsafe { Timer::new() },
             state: NonNull::new(state as *const _ as *mut _).unwrap(),
             config: 0,
             cmp: 0,
@@ -195,7 +198,10 @@ fn init(&mut self, index: usize, state: &HPETState) {
             last: 0,
         };
 
-        self.qemu_timer.init_full(
+        // SAFETY: HPETTimer is only used as part of HPETState, which is
+        // always pinned.
+        let qemu_timer = unsafe { Pin::new_unchecked(&mut self.qemu_timer) };
+        qemu_timer.init_full(
             None,
             CLOCK_VIRTUAL,
             Timer::NS,
diff --git a/rust/qemu-api/src/timer.rs b/rust/qemu-api/src/timer.rs
index a593538917a..f0b04ef95d7 100644
--- a/rust/qemu-api/src/timer.rs
+++ b/rust/qemu-api/src/timer.rs
@@ -2,31 +2,51 @@
 // Author(s): Zhao Liu <zhai1.liu@intel.com>
 // SPDX-License-Identifier: GPL-2.0-or-later
 
-use std::os::raw::{c_int, c_void};
+use std::{
+    os::raw::{c_int, c_void},
+    pin::Pin,
+};
 
 use crate::{
     bindings::{self, qemu_clock_get_ns, timer_del, timer_init_full, timer_mod, QEMUClockType},
     callbacks::FnCall,
+    cell::Opaque,
 };
 
-pub type Timer = bindings::QEMUTimer;
-pub type TimerListGroup = bindings::QEMUTimerListGroup;
+/// A safe wrapper around [`bindings::QEMUTimer`].
+#[repr(transparent)]
+#[derive(Debug, qemu_api_macros::Wrapper)]
+pub struct Timer(Opaque<bindings::QEMUTimer>);
+
+unsafe impl Send for Timer {}
+unsafe impl Sync for Timer {}
+
+#[repr(transparent)]
+#[derive(qemu_api_macros::Wrapper)]
+pub struct TimerListGroup(Opaque<bindings::QEMUTimerListGroup>);
+
+unsafe impl Send for TimerListGroup {}
+unsafe impl Sync for TimerListGroup {}
 
 impl Timer {
     pub const MS: u32 = bindings::SCALE_MS;
     pub const US: u32 = bindings::SCALE_US;
     pub const NS: u32 = bindings::SCALE_NS;
 
-    pub fn new() -> Self {
-        Default::default()
-    }
-
-    const fn as_mut_ptr(&self) -> *mut Self {
-        self as *const Timer as *mut _
+    /// Create a `Timer` struct without initializing it.
+    ///
+    /// # Safety
+    ///
+    /// The timer must be initialized before it is armed with
+    /// [`modify`](Self::modify).
+    pub unsafe fn new() -> Self {
+        // SAFETY: requirements relayed to callers of Timer::new
+        Self(unsafe { Opaque::zeroed() })
     }
 
+    /// Create a new timer with the given attributes.
     pub fn init_full<'timer, 'opaque: 'timer, T, F>(
-        &'timer mut self,
+        self: Pin<&'timer mut Self>,
         timer_list_group: Option<&TimerListGroup>,
         clk_type: ClockType,
         scale: u32,
@@ -51,7 +71,7 @@ pub fn init_full<'timer, 'opaque: 'timer, T, F>(
         // SAFETY: the opaque outlives the timer
         unsafe {
             timer_init_full(
-                self,
+                self.as_mut_ptr(),
                 if let Some(g) = timer_list_group {
                     g as *const TimerListGroup as *mut _
                 } else {
@@ -67,14 +87,19 @@ pub fn init_full<'timer, 'opaque: 'timer, T, F>(
     }
 
     pub fn modify(&self, expire_time: u64) {
+        // SAFETY: the only way to obtain a Timer safely is via methods that
+        // take a Pin<&mut Self>, therefore the timer is pinned
         unsafe { timer_mod(self.as_mut_ptr(), expire_time as i64) }
     }
 
     pub fn delete(&self) {
+        // SAFETY: the only way to obtain a Timer safely is via methods that
+        // take a Pin<&mut Self>, therefore the timer is pinned
         unsafe { timer_del(self.as_mut_ptr()) }
     }
 }
 
+// FIXME: use something like PinnedDrop from the pinned_init crate
 impl Drop for Timer {
     fn drop(&mut self) {
         self.delete()
-- 
2.48.1



  parent reply	other threads:[~2025-03-09 10:34 UTC|newest]

Thread overview: 30+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-03-09 10:30 [PULL 00/25] (Mostly) Rust patches for QEMU 10.0 soft freeze Paolo Bonzini
2025-03-09 10:30 ` [PULL 01/25] chardev: express dependency on io/ Paolo Bonzini
2025-03-09 10:30 ` [PULL 02/25] scripts: dump stdin on meson-buildoptions error Paolo Bonzini
2025-03-09 10:30 ` [PULL 03/25] rust: cell: add wrapper for FFI types Paolo Bonzini
2025-03-09 10:30 ` [PULL 04/25] rust: qemu_api_macros: add Wrapper derive macro Paolo Bonzini
2025-03-09 10:30 ` [PULL 05/25] rust: vmstate: add std::pin::Pin as transparent wrapper Paolo Bonzini
2025-03-09 10:31 ` [PULL 06/25] rust: hpet: embed Timer without the Option and Box indirection Paolo Bonzini
2025-03-09 10:31 ` Paolo Bonzini [this message]
2025-03-09 10:31 ` [PULL 08/25] rust: irq: wrap IRQState with Opaque<> Paolo Bonzini
2025-03-09 10:31 ` [PULL 09/25] rust: qom: wrap Object " Paolo Bonzini
2025-03-09 10:31 ` [PULL 10/25] rust: qdev: wrap Clock and DeviceState " Paolo Bonzini
2025-03-09 10:31 ` [PULL 11/25] rust: hpet: do not access fields of SysBusDevice Paolo Bonzini
2025-03-09 10:31 ` [PULL 12/25] rust: sysbus: wrap SysBusDevice with Opaque<> Paolo Bonzini
2025-03-09 10:31 ` [PULL 13/25] rust: memory: wrap MemoryRegion " Paolo Bonzini
2025-03-09 10:31 ` [PULL 14/25] rust: chardev: wrap Chardev " Paolo Bonzini
2025-03-09 10:31 ` [PULL 15/25] rust: bindings: remove more unnecessary Send/Sync impls Paolo Bonzini
2025-03-09 10:31 ` [PULL 16/25] rust: chardev: provide basic bindings to character devices Paolo Bonzini
2025-03-09 10:31 ` [PULL 17/25] rust: pl011: move register definitions out of lib.rs Paolo Bonzini
2025-03-09 10:31 ` [PULL 18/25] rust: pl011: clean up visibilities of callbacks Paolo Bonzini
2025-03-09 10:31 ` [PULL 19/25] rust: pl011: switch to safe chardev operation Paolo Bonzini
2025-03-19 19:25   ` Peter Maydell
2025-03-19 20:51     ` Paolo Bonzini
2025-03-20 10:39       ` Peter Maydell
2025-03-09 10:31 ` [PULL 20/25] rust: pl011: pass around registers::Data Paolo Bonzini
2025-03-09 10:31 ` [PULL 21/25] rust: hpet: decode HPET registers into enums Paolo Bonzini
2025-03-21 12:03   ` Peter Maydell
2025-03-09 10:31 ` [PULL 22/25] rust: cell: add full example of declaring a SysBusDevice Paolo Bonzini
2025-03-09 10:31 ` [PULL 23/25] rust: qom: remove operations on &mut Paolo Bonzini
2025-03-09 10:31 ` [PULL 24/25] meson.build: default to -gsplit-dwarf for debug info Paolo Bonzini
2025-03-09 10:31 ` [PULL 25/25] rust: pl011: Allow NULL chardev argument to pl011_create() Paolo Bonzini

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250309103120.1116448-8-pbonzini@redhat.com \
    --to=pbonzini@redhat.com \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).