From e8dc87fef2677dc286b3fe72e04d1b763cf98fef Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 3 Mar 2025 16:27:08 +0100 Subject: rust: hpet: embed Timer without the Option and Box indirection This simplifies things for migration, since Option> does not implement VMState. This also shows a soundness issue because Timer::new() will leave a NULL timer list pointer, which can then be dereferenced by Timer::modify(). It will be fixed shortly. Signed-off-by: Paolo Bonzini --- rust/hw/timer/hpet/src/hpet.rs | 61 ++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 32 deletions(-) (limited to 'rust/hw/timer') diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs index be27eb0eff..02c81ae048 100644 --- a/rust/hw/timer/hpet/src/hpet.rs +++ b/rust/hw/timer/hpet/src/hpet.rs @@ -151,14 +151,14 @@ fn timer_handler(timer_cell: &BqlRefCell) { /// HPET Timer Abstraction #[repr(C)] -#[derive(Debug, Default, qemu_api_macros::offsets)] +#[derive(Debug, qemu_api_macros::offsets)] pub struct HPETTimer { /// timer N index within the timer block (`HPETState`) #[doc(alias = "tn")] index: usize, - qemu_timer: Option>, + qemu_timer: Timer, /// timer block abstraction containing this timer - state: Option>, + state: NonNull, // Memory-mapped, software visible timer registers /// Timer N Configuration and Capability Register @@ -181,32 +181,34 @@ pub struct HPETTimer { } impl HPETTimer { - fn init(&mut self, index: usize, state_ptr: *mut HPETState) -> &mut Self { - *self = HPETTimer::default(); - self.index = index; - self.state = NonNull::new(state_ptr); - self - } - - fn init_timer_with_state(&mut self) { - self.qemu_timer = Some(Box::new({ - let mut t = Timer::new(); - t.init_full( - None, - CLOCK_VIRTUAL, - Timer::NS, - 0, - timer_handler, - &self.get_state().timers[self.index], - ); - t - })); + fn init(&mut self, index: usize, state: &HPETState) { + *self = HPETTimer { + index, + qemu_timer: Timer::new(), + state: NonNull::new(state as *const _ as *mut _).unwrap(), + config: 0, + cmp: 0, + fsb: 0, + cmp64: 0, + period: 0, + wrap_flag: 0, + last: 0, + }; + + self.qemu_timer.init_full( + None, + CLOCK_VIRTUAL, + Timer::NS, + 0, + timer_handler, + &state.timers[self.index], + ) } fn get_state(&self) -> &HPETState { // SAFETY: // the pointer is convertible to a reference - unsafe { self.state.unwrap().as_ref() } + unsafe { self.state.as_ref() } } fn is_int_active(&self) -> bool { @@ -330,7 +332,7 @@ impl HPETTimer { } self.last = ns; - self.qemu_timer.as_ref().unwrap().modify(self.last); + self.qemu_timer.modify(self.last); } fn set_timer(&mut self) { @@ -353,7 +355,7 @@ impl HPETTimer { fn del_timer(&mut self) { // Just remove the timer from the timer_list without destroying // this timer instance. - self.qemu_timer.as_ref().unwrap().delete(); + self.qemu_timer.delete(); if self.is_int_active() { // For level-triggered interrupt, this leaves interrupt status @@ -581,13 +583,8 @@ impl HPETState { } fn init_timer(&self) { - let raw_ptr: *mut HPETState = self as *const HPETState as *mut HPETState; - for (index, timer) in self.timers.iter().enumerate() { - timer - .borrow_mut() - .init(index, raw_ptr) - .init_timer_with_state(); + timer.borrow_mut().init(index, self); } } -- cgit 1.4.1 From a32b239699377f09bba08b2e8ae0d167c1488b1f Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 14 Feb 2025 12:06:13 +0100 Subject: 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 --- 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(-) (limited to 'rust/hw/timer') diff --git a/meson.build b/meson.build index 67ec2b7831..6da4eb317c 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 02c81ae048..3d3d6ef8ee 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 @@ 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, diff --git a/rust/qemu-api/src/timer.rs b/rust/qemu-api/src/timer.rs index a593538917..f0b04ef95d 100644 --- a/rust/qemu-api/src/timer.rs +++ b/rust/qemu-api/src/timer.rs @@ -2,31 +2,51 @@ // Author(s): Zhao Liu // 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); + +unsafe impl Send for Timer {} +unsafe impl Sync for Timer {} + +#[repr(transparent)] +#[derive(qemu_api_macros::Wrapper)] +pub struct TimerListGroup(Opaque); + +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 @@ impl Timer { // 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 @@ impl Timer { } 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() -- cgit 1.4.1 From 09fda8f5dc925ba059aca539163d16796af6a299 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 25 Feb 2025 10:06:20 +0100 Subject: rust: hpet: do not access fields of SysBusDevice Fields of SysBusDevice must only be accessed with the BQL taken. Add a wrapper that verifies that. Signed-off-by: Paolo Bonzini --- rust/hw/timer/hpet/src/hpet.rs | 4 +--- rust/qemu-api/src/sysbus.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) (limited to 'rust/hw/timer') diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs index 3d3d6ef8ee..d989360ede 100644 --- a/rust/hw/timer/hpet/src/hpet.rs +++ b/rust/hw/timer/hpet/src/hpet.rs @@ -730,8 +730,6 @@ impl HPETState { } fn reset_hold(&self, _type: ResetType) { - let sbd = self.upcast::(); - for timer in self.timers.iter().take(self.num_timers.get()) { timer.borrow_mut().reset(); } @@ -744,7 +742,7 @@ impl HPETState { HPETFwConfig::update_hpet_cfg( self.hpet_id.get(), self.capability.get() as u32, - sbd.mmio[0].addr, + self.mmio_addr(0).unwrap(), ); // to document that the RTC lowers its output on reset as well diff --git a/rust/qemu-api/src/sysbus.rs b/rust/qemu-api/src/sysbus.rs index 48803a655f..0790576d44 100644 --- a/rust/qemu-api/src/sysbus.rs +++ b/rust/qemu-api/src/sysbus.rs @@ -64,6 +64,18 @@ where } } + // TODO: do we want a type like GuestAddress here? + fn mmio_addr(&self, id: u32) -> Option { + assert!(bql_locked()); + let sbd = self.upcast(); + 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_locked()); -- cgit 1.4.1 From 519088b7cf6dbdef08d8753b57aa29162b83d1a1 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 27 Feb 2025 18:19:35 +0100 Subject: rust: hpet: decode HPET registers into enums Generalize timer_and_addr() to decode all registers into a single enum HPETRegister, and use the TryInto derive to separate valid and invalid values. The main advantage lies in checking that all registers are enumerated in the "match" statements. Signed-off-by: Paolo Bonzini --- rust/Cargo.toml | 2 + rust/hw/char/pl011/src/lib.rs | 2 - rust/hw/timer/hpet/src/hpet.rs | 206 ++++++++++++++++++++++------------------- 3 files changed, 111 insertions(+), 99 deletions(-) (limited to 'rust/hw/timer') diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 5041d6291f..ab1185a814 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -37,6 +37,8 @@ result_unit_err = "allow" should_implement_trait = "deny" # can be for a reason, e.g. in callbacks unused_self = "allow" +# common in device crates +upper_case_acronyms = "allow" # default-allow lints as_ptr_cast_mut = "deny" diff --git a/rust/hw/char/pl011/src/lib.rs b/rust/hw/char/pl011/src/lib.rs index 45c13ba899..dbae76991c 100644 --- a/rust/hw/char/pl011/src/lib.rs +++ b/rust/hw/char/pl011/src/lib.rs @@ -12,8 +12,6 @@ //! See [`PL011State`](crate::device::PL011State) for the device model type and //! the [`registers`] module for register types. -#![allow(clippy::upper_case_acronyms)] - use qemu_api::c_str; mod device; diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs index d989360ede..20e0afdfca 100644 --- a/rust/hw/timer/hpet/src/hpet.rs +++ b/rust/hw/timer/hpet/src/hpet.rs @@ -48,8 +48,6 @@ const RTC_ISA_IRQ: usize = 8; const HPET_CLK_PERIOD: u64 = 10; // 10 ns const FS_PER_NS: u64 = 1000000; // 1000000 femtoseconds == 1 ns -/// General Capabilities and ID Register -const HPET_CAP_REG: u64 = 0x000; /// Revision ID (bits 0:7). Revision 1 is implemented (refer to v1.0a spec). const HPET_CAP_REV_ID_VALUE: u64 = 0x1; const HPET_CAP_REV_ID_SHIFT: usize = 0; @@ -65,8 +63,6 @@ const HPET_CAP_VENDER_ID_SHIFT: usize = 16; /// Main Counter Tick Period (bits 32:63) const HPET_CAP_CNT_CLK_PERIOD_SHIFT: usize = 32; -/// General Configuration Register -const HPET_CFG_REG: u64 = 0x010; /// Overall Enable (bit 0) const HPET_CFG_ENABLE_SHIFT: usize = 0; /// Legacy Replacement Route (bit 1) @@ -74,14 +70,6 @@ const HPET_CFG_LEG_RT_SHIFT: usize = 1; /// Other bits are reserved. const HPET_CFG_WRITE_MASK: u64 = 0x003; -/// General Interrupt Status Register -const HPET_INT_STATUS_REG: u64 = 0x020; - -/// Main Counter Value Register -const HPET_COUNTER_REG: u64 = 0x0f0; - -/// Timer N Configuration and Capability Register (masked by 0x18) -const HPET_TN_CFG_REG: u64 = 0x000; /// bit 0, 7, and bits 16:31 are reserved. /// bit 4, 5, 15, and bits 32:64 are read-only. const HPET_TN_CFG_WRITE_MASK: u64 = 0x7f4e; @@ -109,11 +97,51 @@ const HPET_TN_CFG_FSB_CAP_SHIFT: usize = 15; /// Timer N Interrupt Routing Capability (bits 32:63) const HPET_TN_CFG_INT_ROUTE_CAP_SHIFT: usize = 32; -/// Timer N Comparator Value Register (masked by 0x18) -const HPET_TN_CMP_REG: u64 = 0x008; +#[derive(qemu_api_macros::TryInto)] +#[repr(u64)] +#[allow(non_camel_case_types)] +/// Timer registers, masked by 0x18 +enum TimerRegister { + /// Timer N Configuration and Capability Register + CFG = 0, + /// Timer N Comparator Value Register + CMP = 8, + /// Timer N FSB Interrupt Route Register + ROUTE = 16, +} + +#[derive(qemu_api_macros::TryInto)] +#[repr(u64)] +#[allow(non_camel_case_types)] +/// Global registers +enum GlobalRegister { + /// General Capabilities and ID Register + CAP = 0, + /// General Configuration Register + CFG = 0x10, + /// General Interrupt Status Register + INT_STATUS = 0x20, + /// Main Counter Value Register + COUNTER = 0xF0, +} -/// Timer N FSB Interrupt Route Register (masked by 0x18) -const HPET_TN_FSB_ROUTE_REG: u64 = 0x010; +enum HPETRegister<'a> { + /// Global register in the range from `0` to `0xff` + Global(GlobalRegister), + + /// Register in the timer block `0x100`...`0x3ff` + Timer(&'a BqlRefCell, TimerRegister), + + /// Invalid address + #[allow(dead_code)] + Unknown(hwaddr), +} + +struct HPETAddrDecode<'a> { + shift: u32, + len: u32, + reg: HPETRegister<'a>, +} const fn hpet_next_wrap(cur_tick: u64) -> u64 { (cur_tick | 0xffffffff) + 1 @@ -471,33 +499,21 @@ impl HPETTimer { self.update_irq(true); } - const fn read(&self, addr: hwaddr, _size: u32) -> u64 { - let shift: u64 = (addr & 4) * 8; - - match addr & !4 { - HPET_TN_CFG_REG => self.config >> shift, // including interrupt capabilities - HPET_TN_CMP_REG => self.cmp >> shift, // comparator register - HPET_TN_FSB_ROUTE_REG => self.fsb >> shift, - _ => { - // TODO: Add trace point - trace_hpet_ram_read_invalid() - // Reserved. - 0 - } + const fn read(&self, reg: TimerRegister) -> u64 { + use TimerRegister::*; + match reg { + CFG => self.config, // including interrupt capabilities + CMP => self.cmp, // comparator register + ROUTE => self.fsb, } } - fn write(&mut self, addr: hwaddr, value: u64, size: u32) { - let shift = ((addr & 4) * 8) as u32; - let len = std::cmp::min(size * 8, 64 - shift); - - match addr & !4 { - HPET_TN_CFG_REG => self.set_tn_cfg_reg(shift, len, value), - HPET_TN_CMP_REG => self.set_tn_cmp_reg(shift, len, value), - HPET_TN_FSB_ROUTE_REG => self.set_tn_fsb_route_reg(shift, len, value), - _ => { - // TODO: Add trace point - trace_hpet_ram_write_invalid() - // Reserved. - } + fn write(&mut self, reg: TimerRegister, value: u64, shift: u32, len: u32) { + use TimerRegister::*; + match reg { + CFG => self.set_tn_cfg_reg(shift, len, value), + CMP => self.set_tn_cmp_reg(shift, len, value), + ROUTE => self.set_tn_fsb_route_reg(shift, len, value), } } } @@ -749,76 +765,72 @@ impl HPETState { self.rtc_irq_level.set(0); } - fn timer_and_addr(&self, addr: hwaddr) -> Option<(&BqlRefCell, hwaddr)> { - let timer_id: usize = ((addr - 0x100) / 0x20) as usize; + fn decode(&self, mut addr: hwaddr, size: u32) -> HPETAddrDecode { + let shift = ((addr & 4) * 8) as u32; + let len = std::cmp::min(size * 8, 64 - shift); - // TODO: Add trace point - trace_hpet_ram_[read|write]_timer_id(timer_id) - if timer_id > self.num_timers.get() { - // TODO: Add trace point - trace_hpet_timer_id_out_of_range(timer_id) - None + addr &= !4; + let reg = if (0..=0xff).contains(&addr) { + GlobalRegister::try_from(addr).map(HPETRegister::Global) } else { - // Keep the complete address so that HPETTimer's read and write could - // detect the invalid access. - Some((&self.timers[timer_id], addr & 0x1F)) - } + let timer_id: usize = ((addr - 0x100) / 0x20) as usize; + if timer_id <= self.num_timers.get() { + // TODO: Add trace point - trace_hpet_ram_[read|write]_timer_id(timer_id) + TimerRegister::try_from(addr) + .map(|reg| HPETRegister::Timer(&self.timers[timer_id], reg)) + } else { + // TODO: Add trace point - trace_hpet_timer_id_out_of_range(timer_id) + Err(addr) + } + }; + + // reg is now a Result + // convert the Err case into HPETRegister as well + let reg = reg.unwrap_or_else(HPETRegister::Unknown); + HPETAddrDecode { shift, len, reg } } fn read(&self, addr: hwaddr, size: u32) -> u64 { - let shift: u64 = (addr & 4) * 8; - - // address range of all TN regs // TODO: Add trace point - trace_hpet_ram_read(addr) - if (0x100..=0x3ff).contains(&addr) { - match self.timer_and_addr(addr) { - None => 0, // Reserved, - Some((timer, tn_addr)) => timer.borrow_mut().read(tn_addr, size), - } - } else { - match addr & !4 { - HPET_CAP_REG => self.capability.get() >> shift, /* including HPET_PERIOD 0x004 */ - // (CNT_CLK_PERIOD field) - HPET_CFG_REG => self.config.get() >> shift, - HPET_COUNTER_REG => { - let cur_tick: u64 = if self.is_hpet_enabled() { - self.get_ticks() - } else { - self.counter.get() - }; - - // TODO: Add trace point - trace_hpet_ram_read_reading_counter(addr & 4, - // cur_tick) - cur_tick >> shift - } - HPET_INT_STATUS_REG => self.int_status.get() >> shift, - _ => { - // TODO: Add trace point- trace_hpet_ram_read_invalid() - // Reserved. - 0 + let HPETAddrDecode { shift, reg, .. } = self.decode(addr, size); + + use GlobalRegister::*; + use HPETRegister::*; + (match reg { + Timer(timer, tn_reg) => timer.borrow_mut().read(tn_reg), + Global(CAP) => self.capability.get(), /* including HPET_PERIOD 0x004 */ + Global(CFG) => self.config.get(), + Global(INT_STATUS) => self.int_status.get(), + Global(COUNTER) => { + // TODO: Add trace point + // trace_hpet_ram_read_reading_counter(addr & 4, cur_tick) + if self.is_hpet_enabled() { + self.get_ticks() + } else { + self.counter.get() } } - } + Unknown(_) => { + // TODO: Add trace point- trace_hpet_ram_read_invalid() + 0 + } + }) >> shift } fn write(&self, addr: hwaddr, value: u64, size: u32) { - let shift = ((addr & 4) * 8) as u32; - let len = std::cmp::min(size * 8, 64 - shift); + let HPETAddrDecode { shift, len, reg } = self.decode(addr, size); // TODO: Add trace point - trace_hpet_ram_write(addr, value) - if (0x100..=0x3ff).contains(&addr) { - match self.timer_and_addr(addr) { - None => (), // Reserved. - Some((timer, tn_addr)) => timer.borrow_mut().write(tn_addr, value, size), - } - } else { - match addr & !0x4 { - HPET_CAP_REG => {} // General Capabilities and ID Register: Read Only - HPET_CFG_REG => self.set_cfg_reg(shift, len, value), - HPET_INT_STATUS_REG => self.set_int_status_reg(shift, len, value), - HPET_COUNTER_REG => self.set_counter_reg(shift, len, value), - _ => { - // TODO: Add trace point - trace_hpet_ram_write_invalid() - // Reserved. - } + use GlobalRegister::*; + use HPETRegister::*; + match reg { + Timer(timer, tn_reg) => timer.borrow_mut().write(tn_reg, value, shift, len), + Global(CAP) => {} // General Capabilities and ID Register: Read Only + Global(CFG) => self.set_cfg_reg(shift, len, value), + Global(INT_STATUS) => self.set_int_status_reg(shift, len, value), + Global(COUNTER) => self.set_counter_reg(shift, len, value), + Unknown(_) => { + // TODO: Add trace point - trace_hpet_ram_write_invalid() } } } -- cgit 1.4.1