From: Boqun Feng <boqun.feng@gmail.com>
To: Andreas Hindborg <nmi@metaspace.dk>
Cc: "Benno Lossin" <benno.lossin@proton.me>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Alex Gaynor" <alex.gaynor@gmail.com>,
"Wedson Almeida Filho" <wedsonaf@gmail.com>,
"Anna-Maria Behnsen" <anna-maria@linutronix.de>,
"Frederic Weisbecker" <frederic@kernel.org>,
"Thomas Gleixner" <tglx@linutronix.de>,
"Andreas Hindborg" <a.hindborg@samsung.com>,
"Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Alice Ryhl" <aliceryhl@google.com>,
rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH] rust: hrtimer: introduce hrtimer support
Date: Mon, 29 Apr 2024 10:31:27 -0700 [thread overview]
Message-ID: <Zi_Zb1lBOBBUFJFV@boqun-archlinux> (raw)
In-Reply-To: <87v844lbhm.fsf@metaspace.dk>
On Fri, Apr 26, 2024 at 11:27:49AM +0200, Andreas Hindborg wrote:
> Benno Lossin <benno.lossin@proton.me> writes:
>
> > On 25.04.24 11:46, Andreas Hindborg wrote:
> >> From: Andreas Hindborg <a.hindborg@samsung.com>
> >>
> >> This patch adds support for intrusive use of the hrtimer system. For now, only
> >> one timer can be embedded in a Rust struct.
> >>
> >> The hrtimer Rust API is based on the intrusive style pattern introduced by the
> >> Rust workqueue API.
> >>
> >> Signed-off-by: Andreas Hindborg <a.hindborg@samsung.com>
> >>
> >> ---
> >>
> >> This patch is a dependency for the Rust null block driver [1].
> >>
> >> Link: https://lore.kernel.org/rust-for-linux/20240313110515.70088-1-nmi@metaspace.dk/T/#me0990150b9ba9f5b3d00293ec9a473c7bc3cc506 [1]
> >>
> >> rust/kernel/hrtimer.rs | 283 +++++++++++++++++++++++++++++++++++++++++
> >> rust/kernel/lib.rs | 1 +
> >> 2 files changed, 284 insertions(+)
> >> create mode 100644 rust/kernel/hrtimer.rs
> >>
> >> diff --git a/rust/kernel/hrtimer.rs b/rust/kernel/hrtimer.rs
> >
> > Hmm is this the right place? I imagine there are other timers, does this
> > fit better into the `time` module (ie make `hrtimer` a submodule of
> > `time`) or should we later introduce a `timer` parent module?
>
> We can always move it. We will move stuff anyway when the kernel crate
> is split.
>
> We can also take it to `kernel::time::hrtimer` now, either way is fine.
>
I think `kernel::time::hrtimer` makes more sense, since ideally
schedule() function should take a time delta type as the input instead
of `u64`. So hrtimer has some logical connection to timekeeping module.
> >
> >> new file mode 100644
> >> index 000000000000..1e282608e70c
> >> --- /dev/null
> >> +++ b/rust/kernel/hrtimer.rs
> >> @@ -0,0 +1,283 @@
> >> +// SPDX-License-Identifier: GPL-2.0
> >> +
> >> +//! Intrusive high resolution timers.
> >> +//!
> >> +//! Allows scheduling timer callbacks without doing allocations at the time of
> >> +//! scheduling. For now, only one timer per type is allowed.
> >> +//!
> >> +//! # Example
> >> +//!
> >> +//! ```rust
> >> +//! use kernel::{
> >> +//! sync::Arc, hrtimer::{RawTimer, Timer, TimerCallback},
> >> +//! impl_has_timer, prelude::*, stack_pin_init
> >> +//! };
> >> +//! use core::sync::atomic::AtomicBool;
> >> +//! use core::sync::atomic::Ordering;
> >> +//!
> >> +//! #[pin_data]
> >> +//! struct IntrusiveTimer {
> >> +//! #[pin]
> >> +//! timer: Timer<Self>,
> >> +//! flag: AtomicBool,
Could you see if you can replace this with a `SpinLock<bool>` +
`CondVar`? We shouldn't use Rust atomic in kernel now. I know it's
unfortunate that LKMM atomics are still work in process, but in real
world, you won't do busy waiting for a timer to fire, so a
`CondVar::wait` is better for example purpose.
> >> +//! }
> >> +//!
> >> +//! impl IntrusiveTimer {
> >> +//! fn new() -> impl PinInit<Self> {
> >> +//! pin_init!(Self {
> >> +//! timer <- Timer::new(),
> >> +//! flag: AtomicBool::new(false),
> >> +//! })
> >> +//! }
> >> +//! }
> >> +//!
> >> +//! impl TimerCallback for IntrusiveTimer {
> >> +//! type Receiver = Arc<IntrusiveTimer>;
> >> +//!
> >> +//! fn run(this: Self::Receiver) {
> >> +//! pr_info!("Timer called\n");
> >> +//! this.flag.store(true, Ordering::Relaxed);
> >> +//! }
> >> +//! }
> >> +//!
> >> +//! impl_has_timer! {
> >> +//! impl HasTimer<Self> for IntrusiveTimer { self.timer }
> >> +//! }
> >> +//!
> >> +//! let has_timer = Arc::pin_init(IntrusiveTimer::new())?;
> >
> > I would not name this variable `has_timer`. Maybe `my_timer` is better?
>
> Right, thanks.
>
> >
> >> +//! has_timer.clone().schedule(200_000_000);
> >> +//! while !has_timer.flag.load(Ordering::Relaxed) { core::hint::spin_loop() }
> >
> > Weird formatting, we should also use `rustfmt` in examples.
>
> `format_code_in_doc_comments` is a nightly `rustfmt` feature. I tried
> enabling it in `.rustfmt.toml` and running `rustfmt +nightly
> hrtimer.rs`. It did not have any effect. There is some discussion here:
> https://github.com/rust-lang/rustfmt/issues/3348
>
> >
[...]
> >> +#[pinned_drop]
> >> +impl<T> PinnedDrop for Timer<T> {
> >> + fn drop(self: Pin<&mut Self>) {
> >> + // SAFETY: By struct invariant `self.timer` was initialized by
> >> + // `hrtimer_init` so by C API contract it is safe to call
> >> + // `hrtimer_cancel`.
> >> + unsafe {
> >> + bindings::hrtimer_cancel(self.timer.get());
> >> + }
> >> + }
> >> +}
> >
> > Why is this needed? The only way to schedule a timer using this API is
> > by having an `Arc` with a timer-containing struct inside. But to
> > schedule the `Arc`, you consume one refcount which is then sent to the
> > timer subsystem. So it is impossible for the refcount to drop below zero
> > while the timer is scheduled, but not yet running.
> > Do you need to call `hrtimer_cancel` after/while a timer is running?
>
> This is not required any longer. It is a leftover from an earlier
> revision where timers could be stack allocated. I will remove it.
>
So the plan is to add Arc<HasTimer> support first and stack allocated
timer later? If so, please do add a paragraph in the module level doc
describing the limition (e.g. stack allocated timers are not supported).
> > Also is it ok to call `hrtimer_cancel` inside the timer callback? Since
> > that can happen when the timer callback owns the last refcount.
>
> That should be fine, `self` is still valid when the drop method is run?
>
> >
> >> +
> >> +/// Implemented by pointer types to structs that embed a [`Timer`]. This trait
> >> +/// facilitates queueing the timer through the pointer that implements the
> >> +/// trait.
> >> +///
> >> +/// Typical implementers would be [`Box<T>`], [`Arc<T>`], [`ARef<T>`] where `T`
> >> +/// has a field of type `Timer`.
> >> +///
> >> +/// Target must be [`Sync`] because timer callbacks happen in another thread of
> >> +/// execution.
> >> +///
> >> +/// [`Box<T>`]: Box
> >> +/// [`Arc<T>`]: Arc
> >> +/// [`ARef<T>`]: crate::types::ARef
> >> +pub trait RawTimer: Sync {
> >> + /// Schedule the timer after `expires` time units
> >> + fn schedule(self, expires: u64);
This function should have a return value, see below:
> >> +}
[...]
> >> +impl<T> RawTimer for Arc<T>
> >> +where
> >> + T: Send + Sync,
> >> + T: HasTimer<T>,
> >> +{
> >> + fn schedule(self, expires: u64) {
> >> + let self_ptr = Arc::into_raw(self);
> >> +
so if the timer is already scheduled, re-scheduling will leak it, e.g.
let timer: Arc<SomeTimer> = ...;
let reschedule_handle = timer.clone(); // refcount == 2
timer.schedule(...);
...
// later on, a reschedule is needed
reschedule_handle.schedule(...); // refcount == 2
// <timer callback invoked>
Arc::drop();
// refcount == 1, the Arc is leaked.
Looks to me `schedule()` should return the `Arc` back if it's already
in the queue.
TBH, if you don't need the re-schedule and cancel functionality, maybe
start with `impl<T> RawTimer for Pin<Box<T>>` first.
Regards,
Boqun
> >> + // SAFETY: `self_ptr` is a valid pointer to a `T`
> >> + let timer_ptr = unsafe { T::raw_get_timer(self_ptr) };
> >> +
> >> + // `Timer` is `repr(transparent)`
> >> + let c_timer_ptr = timer_ptr.cast::<bindings::hrtimer>();
> >> +
> >> + // Schedule the timer - if it is already scheduled it is removed and
> >> + // inserted
> >> +
> >> + // SAFETY: c_timer_ptr points to a valid hrtimer instance that was
> >> + // initialized by `hrtimer_init`
> >> + unsafe {
> >> + bindings::hrtimer_start_range_ns(
> >> + c_timer_ptr.cast_mut(),
> >> + expires as i64,
> >> + 0,
> >> + bindings::hrtimer_mode_HRTIMER_MODE_REL,
> >> + );
> >> + }
> >> + }
> >> +}
> >> +
[...]
next prev parent reply other threads:[~2024-04-29 17:31 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-04-25 9:46 [PATCH] rust: hrtimer: introduce hrtimer support Andreas Hindborg
2024-04-26 7:52 ` Benno Lossin
2024-04-26 9:27 ` Andreas Hindborg
2024-04-29 17:31 ` Boqun Feng [this message]
2024-04-30 12:33 ` Andreas Hindborg
2024-04-30 15:17 ` Boqun Feng
2024-04-30 18:22 ` Andreas Hindborg
2024-04-30 17:05 ` Thomas Gleixner
2024-04-29 12:49 ` Alice Ryhl
2024-04-29 13:47 ` Andreas Hindborg
2024-04-30 17:02 ` Thomas Gleixner
2024-04-30 18:18 ` Andreas Hindborg
2024-04-30 22:25 ` Thomas Gleixner
2024-05-01 11:37 ` Andreas Hindborg
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=Zi_Zb1lBOBBUFJFV@boqun-archlinux \
--to=boqun.feng@gmail.com \
--cc=a.hindborg@samsung.com \
--cc=alex.gaynor@gmail.com \
--cc=aliceryhl@google.com \
--cc=anna-maria@linutronix.de \
--cc=benno.lossin@proton.me \
--cc=bjorn3_gh@protonmail.com \
--cc=frederic@kernel.org \
--cc=gary@garyguo.net \
--cc=linux-kernel@vger.kernel.org \
--cc=nmi@metaspace.dk \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tglx@linutronix.de \
--cc=wedsonaf@gmail.com \
/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).