rust-for-linux.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
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,
> >> +            );
> >> +        }
> >> +    }
> >> +}
> >> +
[...]

  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).