rust: timer: wrap QEMUTimer with Opaque<> and express pinning requirements

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>
This commit is contained in:
Paolo Bonzini 2025-02-14 12:06:13 +01:00
parent e8dc87fef2
commit a32b239699
3 changed files with 44 additions and 20 deletions

View file

@ -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 @@ impl HPETTimer {
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,