From: FUJITA Tomonori <tomo@flapping.org>
To: a.hindborg@kernel.org, ojeda@kernel.org
Cc: acourbot@nvidia.com, aliceryhl@google.com,
anna-maria@linutronix.de, bjorn3_gh@protonmail.com,
boqun@kernel.org, dakr@kernel.org, daniel.almeida@collabora.com,
frederic@kernel.org, gary@garyguo.net, jstultz@google.com,
lossin@kernel.org, lyude@redhat.com, sboyd@kernel.org,
tamird@kernel.org, tglx@kernel.org, tmgross@umich.edu,
work@onurozkan.dev, rust-for-linux@vger.kernel.org,
FUJITA Tomonori <fujita.tomonori@gmail.com>
Subject: [PATCH v1 1/4] rust: hrtimer: Introduce HrTimerArc to make arming exclusive
Date: Thu, 13 Aug 2026 22:48:31 +0900 [thread overview]
Message-ID: <20260813134834.1562995-2-tomo@flapping.org> (raw)
In-Reply-To: <20260813134834.1562995-1-tomo@flapping.org>
From: FUJITA Tomonori <fujita.tomonori@gmail.com>
A hrtimer callback may change its own expiry time with
hrtimer_forward(), which is sound only because __run_hrtimer() dequeues
the timer for the duration of the callback. Arming the same timer from
another CPU puts it back into the rbtree while the callback runs, so
that expiry update lands on a queued timer and the tree ordering goes
stale. Arc<T> is Clone and Arc<T>::start() is safe, so safe Rust can arm
a timer on any CPU, including while its callback is running.
"No arming while the callback runs" cannot be expressed in the type
system, because the callback begins when the timer expires rather than
at any point in the Rust program, so use the stronger "no arming while
armed" instead. hrtimer_cancel() waits for the handler to return, which
makes that the point where the right to arm can be handed back.
Pin<Box<T, A>> and Pin<&mut T> already work this way through ownership
and exclusive borrow.
Split the right to arm out of Arc<T> into HrTimerArc<T>, a non-clonable
wrapper modelled on ListArc that is created from a Pin<UniqueArc<T>> and
consumed by start(); the object stays shareable through plain Arc
references.
Fixes: 3f2a5ba784b8 ("rust: hrtimer: Add HrTimerCallbackContext and ::forward()")
Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>
---
rust/kernel/time/hrtimer.rs | 33 +++++-----
rust/kernel/time/hrtimer/arc.rs | 111 +++++++++++++++++++++++++-------
2 files changed, 103 insertions(+), 41 deletions(-)
diff --git a/rust/kernel/time/hrtimer.rs b/rust/kernel/time/hrtimer.rs
index 2d7f1131a813..a7587db1d552 100644
--- a/rust/kernel/time/hrtimer.rs
+++ b/rust/kernel/time/hrtimer.rs
@@ -172,12 +172,12 @@
//! # sync::{
//! # atomic::{ordering, Atomic},
//! # completion::Completion,
-//! # Arc, ArcBorrow,
+//! # ArcBorrow,
//! # },
//! # time::{
//! # hrtimer::{
-//! # RelativeMode, HrTimer, HrTimerCallback, HrTimerPointer, HrTimerRestart,
-//! # HasHrTimer, HrTimerCallbackContext
+//! # RelativeMode, HrTimer, HrTimerArc, HrTimerCallback, HrTimerPointer,
+//! # HrTimerRestart, HasHrTimer, HrTimerCallbackContext
//! # },
//! # Delta, Monotonic,
//! # },
@@ -204,7 +204,7 @@
//! }
//!
//! impl HrTimerCallback for ArcIntrusiveHrTimer {
-//! type Pointer<'a> = Arc<Self>;
+//! type Pointer<'a> = HrTimerArc<Self>;
//!
//! fn run(
//! this: ArcBorrow<'_, Self>,
@@ -229,11 +229,12 @@
//! }
//! }
//!
-//! let has_timer = Arc::pin_init(ArcIntrusiveHrTimer::new(), GFP_KERNEL)?;
-//! let _handle = has_timer.clone().start(Delta::from_micros(200));
+//! let has_timer = HrTimerArc::pin_init(ArcIntrusiveHrTimer::new(), GFP_KERNEL)?;
+//! let shared = has_timer.clone_arc();
+//! let _handle = has_timer.start(Delta::from_micros(200));
//!
-//! while has_timer.flag.load(ordering::Relaxed) != 5 {
-//! has_timer.cond.wait_for_completion();
+//! while shared.flag.load(ordering::Relaxed) != 5 {
+//! shared.cond.wait_for_completion();
//! }
//!
//! pr_info!("Counted to 5\n");
@@ -589,18 +590,17 @@ pub fn expires(&self) -> HrTimerInstant<T>
/// `Self` must be [`Sync`] because it is passed to timer callbacks in another
/// thread of execution (hard or soft interrupt context).
///
-/// Starting a timer returns a [`HrTimerHandle`] that can be used to manipulate
-/// the timer. Note that it is OK to call the start function repeatedly, and
-/// that more than one [`HrTimerHandle`] associated with a [`HrTimerPointer`] may
-/// exist. A timer can be manipulated through any of the handles, and a handle
-/// may represent a cancelled timer.
+/// Starting a timer consumes `Self` and returns a [`HrTimerHandle`] that can be
+/// used to manipulate the timer. As a timer in the **started** or **running**
+/// state cannot be started again, at most one [`HrTimerHandle`] for a timer
+/// exists at a time. A handle may represent a cancelled timer.
pub trait HrTimerPointer: Sync + Sized {
/// The operational mode associated with this timer.
///
/// This defines how the expiration value is interpreted.
type TimerMode: HrTimerMode;
- /// A handle representing a started or restarted timer.
+ /// A handle representing a started timer.
///
/// If the timer is running or if the timer callback is executing when the
/// handle is dropped, the drop method of [`HrTimerHandle`] should not return
@@ -610,8 +610,7 @@ pub trait HrTimerPointer: Sync + Sized {
/// leak the handle.
type TimerHandle: HrTimerHandle;
- /// Start the timer with expiry after `expires` time units. If the timer was
- /// already running, it is restarted with the new expiry time.
+ /// Start the timer with expiry after `expires` time units.
fn start(self, expires: <Self::TimerMode as HrTimerMode>::Expires) -> Self::TimerHandle;
}
@@ -1110,7 +1109,7 @@ unsafe fn timer_container_of(
}
mod arc;
-pub use arc::ArcHrTimerHandle;
+pub use arc::{ArcHrTimerHandle, HrTimerArc};
mod pin;
pub use pin::PinHrTimerHandle;
mod pin_mut;
diff --git a/rust/kernel/time/hrtimer/arc.rs b/rust/kernel/time/hrtimer/arc.rs
index 7be82bcb352a..2134d12d558c 100644
--- a/rust/kernel/time/hrtimer/arc.rs
+++ b/rust/kernel/time/hrtimer/arc.rs
@@ -8,47 +8,71 @@
use super::HrTimerMode;
use super::HrTimerPointer;
use super::RawHrTimerCallback;
-use crate::sync::Arc;
-use crate::sync::ArcBorrow;
+use crate::alloc::Flags;
+use crate::error::{Error, Result};
+use crate::init::InPlaceInit;
+use crate::sync::{Arc, ArcBorrow, UniqueArc};
+use core::pin::Pin;
+use pin_init::PinInit;
-/// A handle for an `Arc<HasHrTimer<T>>` returned by a call to
-/// [`HrTimerPointer::start`].
-pub struct ArcHrTimerHandle<T>
+/// A wrapper around [`Arc`] that's guaranteed unique.
+///
+/// The `HrTimerArc` type can be thought of as a special reference to a refcounted object that owns
+/// the permission to arm the [`HrTimer`] stored in the refcounted object. By ensuring that each
+/// object has only one `HrTimerArc` reference, the owner of that reference is assured exclusive
+/// access to the arming operation. When a timer is started, the returned [`ArcHrTimerHandle`] takes
+/// ownership of the `HrTimerArc` reference.
+///
+/// While this `HrTimerArc` is unique, there still might exist normal [`Arc`] references to the
+/// object. Use [`HrTimerArc::clone_arc`] to obtain one.
+///
+/// # Invariants
+///
+/// * Each reference counted object has at most one `HrTimerArc`.
+pub struct HrTimerArc<T>
where
T: HasHrTimer<T>,
{
- pub(crate) inner: Arc<T>,
+ arc: Arc<T>,
}
-// SAFETY: We implement drop below, and we cancel the timer in the drop
-// implementation.
-unsafe impl<T> HrTimerHandle for ArcHrTimerHandle<T>
+impl<T> HrTimerArc<T>
where
T: HasHrTimer<T>,
{
- fn cancel(&mut self) -> bool {
- let self_ptr = Arc::as_ptr(&self.inner);
-
- // SAFETY: As we obtained `self_ptr` from a valid reference above, it
- // must point to a valid `T`.
- let timer_ptr = unsafe { <T as HasHrTimer<T>>::raw_get_timer(self_ptr) };
+ /// Use the given pin-initializer to pin-initialize a `T` inside of a new `HrTimerArc`.
+ #[inline]
+ pub fn pin_init<E>(init: impl PinInit<T, E>, flags: Flags) -> Result<Self>
+ where
+ Error: From<E>,
+ {
+ Ok(Self::from(UniqueArc::pin_init(init, flags)?))
+ }
- // SAFETY: As `timer_ptr` points into `T` and `T` is valid, `timer_ptr`
- // must point to a valid `HrTimer` instance.
- unsafe { HrTimer::<T>::raw_cancel(timer_ptr) }
+ /// Clone an [`Arc`] from this `HrTimerArc`.
+ ///
+ /// The returned [`Arc`] can be used to access the object, but not to arm its timer.
+ #[inline]
+ pub fn clone_arc(&self) -> Arc<T> {
+ self.arc.clone()
}
}
-impl<T> Drop for ArcHrTimerHandle<T>
+impl<T> From<Pin<UniqueArc<T>>> for HrTimerArc<T>
where
T: HasHrTimer<T>,
{
- fn drop(&mut self) {
- self.cancel();
+ /// Convert a pinned [`UniqueArc`] into a [`HrTimerArc`].
+ #[inline]
+ fn from(unique: Pin<UniqueArc<T>>) -> Self {
+ // INVARIANT: We have a `UniqueArc`, so there is no `HrTimerArc` for this object.
+ Self {
+ arc: Arc::from(unique),
+ }
}
}
-impl<T> HrTimerPointer for Arc<T>
+impl<T> HrTimerPointer for HrTimerArc<T>
where
T: 'static,
T: Send + Sync,
@@ -66,12 +90,51 @@ fn start(
// - We keep `self` alive by wrapping it in a handle below.
// - Since we generate the pointer passed to `start` from a valid
// reference, it is a valid pointer.
- unsafe { T::start(Arc::as_ptr(&self), expires) };
+ unsafe { T::start(Arc::as_ptr(&self.arc), expires) };
ArcHrTimerHandle { inner: self }
}
}
-impl<T> RawHrTimerCallback for Arc<T>
+/// A handle for a [`HrTimerArc`] returned by a call to [`HrTimerPointer::start`].
+///
+/// This handle owns the [`HrTimerArc`] reference for the object, so the timer cannot be armed
+/// again while this handle exists.
+pub struct ArcHrTimerHandle<T>
+where
+ T: HasHrTimer<T>,
+{
+ pub(crate) inner: HrTimerArc<T>,
+}
+
+// SAFETY: We implement drop below, and we cancel the timer in the drop
+// implementation.
+unsafe impl<T> HrTimerHandle for ArcHrTimerHandle<T>
+where
+ T: HasHrTimer<T>,
+{
+ fn cancel(&mut self) -> bool {
+ let self_ptr = Arc::as_ptr(&self.inner.arc);
+
+ // SAFETY: As we obtained `self_ptr` from a valid reference above, it
+ // must point to a valid `T`.
+ let timer_ptr = unsafe { <T as HasHrTimer<T>>::raw_get_timer(self_ptr) };
+
+ // SAFETY: As `timer_ptr` points into `T` and `T` is valid, `timer_ptr`
+ // must point to a valid `HrTimer` instance.
+ unsafe { HrTimer::<T>::raw_cancel(timer_ptr) }
+ }
+}
+
+impl<T> Drop for ArcHrTimerHandle<T>
+where
+ T: HasHrTimer<T>,
+{
+ fn drop(&mut self) {
+ self.cancel();
+ }
+}
+
+impl<T> RawHrTimerCallback for HrTimerArc<T>
where
T: 'static,
T: HasHrTimer<T>,
--
2.43.0
next prev parent reply other threads:[~2026-08-13 13:48 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-13 13:48 [PATCH 0/4] Fix forward()/expires() racing with concurrent arming FUJITA Tomonori
2026-08-13 13:48 ` FUJITA Tomonori [this message]
2026-08-13 13:48 ` [PATCH v1 2/4] rust: hrtimer: Introduce HrTimerPin to make arming exclusive FUJITA Tomonori
2026-08-13 13:48 ` [PATCH v1 3/4] rust: hrtimer: Restrict expires() to safe contexts FUJITA Tomonori
2026-08-13 13:48 ` [PATCH v1 4/4] rust: hrtimer: Make HrTimer repr(transparent) FUJITA Tomonori
2026-08-13 14:16 ` [PATCH 0/4] Fix forward()/expires() racing with concurrent arming Gary Guo
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=20260813134834.1562995-2-tomo@flapping.org \
--to=tomo@flapping.org \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=aliceryhl@google.com \
--cc=anna-maria@linutronix.de \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=frederic@kernel.org \
--cc=fujita.tomonori@gmail.com \
--cc=gary@garyguo.net \
--cc=jstultz@google.com \
--cc=lossin@kernel.org \
--cc=lyude@redhat.com \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=sboyd@kernel.org \
--cc=tamird@kernel.org \
--cc=tglx@kernel.org \
--cc=tmgross@umich.edu \
--cc=work@onurozkan.dev \
/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