[PATCH v5 3/5] rust: add pr_*_ratelimit! macros for printing
Alice Ryhl <[email protected]> Mon, 03 Aug 2026 07:29:54 +0000
| Newsgroups | org.kernel.vger.rust-for-linux,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
Printing can be very expensive if it occurs often, so printing that can be triggered by userspace should be rate limited. For this purpose, add a Rust wrapper around `struct ratelimit_state` and use it in the new macros. The new files will fall under the catch-all RUST maintainers entry. Tested-by: Alvin Sun <[email protected]> Reviewed-by: Carlos Llamas <[email protected]> Link: https://github.com/Rust-for-Linux/linux/issues/122 Acked-by: Miguel Ojeda <[email protected]> Signed-off-by: Alice Ryhl <[email protected]> --- rust/helpers/helpers.c | 1 + rust/helpers/ratelimit.c | 14 +++ rust/kernel/lib.rs | 1 + rust/kernel/prelude.rs | 8 ++ rust/kernel/ratelimit.rs | 216 ++++++++++++++++++++++++++++++++++++++ rust/kernel/sync/lock/spinlock.rs | 1 - 6 files changed, 240 insertions(+), 1 deletion(-) diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 1d4ee51f576b..cbecf152f647 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -82,6 +82,7 @@ #include "processor.c" #include "property.c" #include "pwm.c" +#include "ratelimit.c" #include "rbtree.c" #include "rcu.c" #include "refcount.c" diff --git a/rust/helpers/ratelimit.c b/rust/helpers/ratelimit.c new file mode 100644 index 000000000000..e5052f568b81 --- /dev/null +++ b/rust/helpers/ratelimit.c @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/ratelimit.h> + +__rust_helper void rust_helper_ratelimit_state_init(struct ratelimit_state *rs, + int interval, int burst) +{ + ratelimit_state_init(rs, interval, burst); +} + +__rust_helper void rust_helper_ratelimit_state_exit(struct ratelimit_state *rs) +{ + ratelimit_state_exit(rs); +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 9512af7156df..f53dd564aef5 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -112,6 +112,7 @@ pub mod ptr; #[cfg(CONFIG_RUST_PWM_ABSTRACTIONS)] pub mod pwm; +pub mod ratelimit; pub mod rbtree; pub mod regulator; pub mod revocable; diff --git a/rust/kernel/prelude.rs b/rust/kernel/prelude.rs index ca396f1f78a6..bcaa232205be 100644 --- a/rust/kernel/prelude.rs +++ b/rust/kernel/prelude.rs @@ -107,13 +107,21 @@ }, init::InPlaceInit, pr_alert, + pr_alert_ratelimited, pr_crit, + pr_crit_ratelimited, pr_debug, + pr_debug_ratelimited, pr_emerg, + pr_emerg_ratelimited, pr_err, + pr_err_ratelimited, pr_info, + pr_info_ratelimited, pr_notice, + pr_notice_ratelimited, pr_warn, + pr_warn_ratelimited, str::CStrExt as _, try_init, try_pin_init, diff --git a/rust/kernel/ratelimit.rs b/rust/kernel/ratelimit.rs new file mode 100644 index 000000000000..5f0deae819dd --- /dev/null +++ b/rust/kernel/ratelimit.rs @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rate limiting support. +//! +//! C header: [`include/linux/ratelimit.h`](srctree/include/linux/ratelimit.h) + +use crate::{ + bindings, + prelude::*, + types::Opaque, // +}; + +/// Defines a `static` containing a [`Ratelimit`]. +#[macro_export] +macro_rules! ratelimit_state_init { + ($name:ident, $interval:expr, $burst:expr $(,)?) => { + static $name: $crate::ratelimit::Ratelimit = { + let name = $crate::c_str!(::core::stringify!($name)); + let interval = $interval; + let burst = $burst; + // SAFETY: This will be stored in static memory. + unsafe { $crate::ratelimit::Ratelimit::new_static(name, interval, burst) } + }; + }; +} +pub use ratelimit_state_init; + +/// Rate limiter state. +/// +/// # Invariants +/// +/// The `inner` field contains an initialized `struct ratelimit_state`. +#[pin_data(PinnedDrop)] +#[repr(transparent)] +pub struct Ratelimit { + #[pin] + inner: Opaque<bindings::ratelimit_state>, +} + +// SAFETY: `Ratelimit` is safe to be sent to any task. +unsafe impl Send for Ratelimit {} + +// SAFETY: `Ratelimit` is safe to be accessed concurrently as it is protected by an internal +// spinlock. +unsafe impl Sync for Ratelimit {} + +impl Ratelimit { + /// Constructs a [`Ratelimit`] with the specified configuration. + /// + /// If `interval` is zero, then no rate limit is applied. + #[inline] + pub fn new(interval: i32, burst: i32) -> impl PinInit<Self> { + // INVARIANT: This creates a `Ratelimit` containing an initialized `struct ratelimit_state` + pin_init!(Self { + inner <- Opaque::ffi_init(|slot: *mut bindings::ratelimit_state| { + // SAFETY: `slot` is a valid pointer to an uninitialized `struct ratelimit_state`. + // The memory is pinned so it remains valid until `ratelimit_state_exit` is called. + unsafe { bindings::ratelimit_state_init(slot, interval, burst) }; + }), + }) + } + + /// Constructs a [`Ratelimit`] with the default configuration. + #[inline] + pub fn new_default() -> impl PinInit<Self> { + Ratelimit::new(Ratelimit::DEFAULT_INTERVAL, Ratelimit::DEFAULT_BURST) + } + + /// Constructs a [`Ratelimit`] with the specified configuration. + /// + /// The name will be used for the lockdep name of the internal spinlock. See [`Self::new`] for + /// the meaning of `interval` and `burst`. + /// + /// # Safety + /// + /// The resulting value must be stored in static memory. + #[inline] + pub const unsafe fn new_static(name: &'static CStr, interval: i32, burst: i32) -> Self { + Self { + inner: Opaque::new(bindings::ratelimit_state { + lock: kernel::sync::lock::spinlock::raw_spin_lock_unlocked(name), + interval, + burst, + ..pin_init::zeroed() + }), + } + } + + /// The default interval used for rate limiting. + pub const DEFAULT_INTERVAL: i32 = bindings::DEFAULT_RATELIMIT_INTERVAL as i32; + + /// The default burst size. + pub const DEFAULT_BURST: i32 = bindings::DEFAULT_RATELIMIT_BURST as i32; + + /// Check if an action should be rate-limited. + /// + /// Returns [`true`] if the action is allowed, and [`false`] if it should be suppressed. + #[inline] + pub fn ratelimit(&self) -> bool { + // We don't set `RATELIMIT_MSG_ON_RELEASE`, so the function name parameter is not used. + // + // SAFETY: `self.inner.get()` is a valid pointer to a `struct ratelimit_state`. + // The lifetime of `func` ensures the pointer remains valid for the duration of the call. + // The C function `___ratelimit` handles its own internal locking, so it is safe to call + // concurrently. + unsafe { bindings::___ratelimit(self.inner.get(), c"Rust".as_char_ptr()) != 0 } + } +} + +#[pinned_drop] +impl PinnedDrop for Ratelimit { + #[inline] + fn drop(self: Pin<&mut Self>) { + // SAFETY: By the type invariants, this struct contains an initialized `struct + // ratelimit_state`. + unsafe { bindings::ratelimit_state_exit(self.inner.get()) }; + } +} + +/// Helper macro to implement ratelimited printing. +#[macro_export] +#[doc(hidden)] +macro_rules! print_ratelimited { + ($print_macro:ident, $($arg:tt)*) => {{ + $crate::ratelimit::ratelimit_state_init!( + _rs, + $crate::ratelimit::Ratelimit::DEFAULT_INTERVAL, + $crate::ratelimit::Ratelimit::DEFAULT_BURST, + ); + if $crate::ratelimit::Ratelimit::ratelimit(&_rs) { + $crate::$print_macro!($($arg)*); + } + }}; +} + +/// Prints an emergency-level message (level 0) if allowed by a rate limiter. +/// +/// [`Ratelimit`]: $crate::ratelimit::Ratelimit +#[macro_export] +macro_rules! pr_emerg_ratelimited ( + ($($arg:tt)*) => ( + $crate::print_ratelimited!(pr_emerg, $($arg)*) + ) +); + +/// Prints an alert-level message (level 1) if allowed by a rate limiter. +/// +/// [`Ratelimit`]: $crate::ratelimit::Ratelimit +#[macro_export] +macro_rules! pr_alert_ratelimited ( + ($($arg:tt)*) => ( + $crate::print_ratelimited!(pr_alert, $($arg)*) + ) +); + +/// Prints a critical-level message (level 2) if allowed by a rate limiter. +/// +/// [`Ratelimit`]: $crate::ratelimit::Ratelimit +#[macro_export] +macro_rules! pr_crit_ratelimited ( + ($($arg:tt)*) => ( + $crate::print_ratelimited!(pr_crit, $($arg)*) + ) +); + +/// Prints an error-level message (level 3) if allowed by a rate limiter. +/// +/// [`Ratelimit`]: $crate::ratelimit::Ratelimit +#[macro_export] +macro_rules! pr_err_ratelimited ( + ($($arg:tt)*) => ( + $crate::print_ratelimited!(pr_err, $($arg)*) + ) +); + +/// Prints a warning-level message (level 4) if allowed by a rate limiter. +/// +/// [`Ratelimit`]: $crate::ratelimit::Ratelimit +#[macro_export] +macro_rules! pr_warn_ratelimited ( + ($($arg:tt)*) => ( + $crate::print_ratelimited!(pr_warn, $($arg)*) + ) +); + +/// Prints a notice-level message (level 5) if allowed by a rate limiter. +/// +/// [`Ratelimit`]: $crate::ratelimit::Ratelimit +#[macro_export] +macro_rules! pr_notice_ratelimited ( + ($($arg:tt)*) => ( + $crate::print_ratelimited!(pr_notice, $($arg)*) + ) +); + +/// Prints an info-level message (level 6) if allowed by a rate limiter. +/// +/// [`Ratelimit`]: $crate::ratelimit::Ratelimit +#[macro_export] +macro_rules! pr_info_ratelimited ( + ($($arg:tt)*) => ( + $crate::print_ratelimited!(pr_info, $($arg)*) + ) +); + +/// Prints a debug-level message (level 7) if allowed by a rate limiter. +/// +/// [`Ratelimit`]: $crate::ratelimit::Ratelimit +#[macro_export] +macro_rules! pr_debug_ratelimited ( + ($($arg:tt)*) => ( + if cfg!(debug_assertions) { + $crate::print_ratelimited!(pr_debug, $($arg)*) + } + ) +); diff --git a/rust/kernel/sync/lock/spinlock.rs b/rust/kernel/sync/lock/spinlock.rs index 697efa7e04c6..b9869f958ce0 100644 --- a/rust/kernel/sync/lock/spinlock.rs +++ b/rust/kernel/sync/lock/spinlock.rs @@ -151,7 +151,6 @@ unsafe fn assert_is_held(ptr: *mut Self::State) { /// /// For use in statics containing raw spinlocks. #[doc(alias("__SPIN_LOCK_UNLOCKED", "DEFINE_SPINLOCK"))] -#[expect(dead_code)] pub(crate) const fn raw_spin_lock_unlocked(name: &'static CStr) -> bindings::raw_spinlock_t { // Silence unused variable warnings. #[cfg(not(CONFIG_DEBUG_LOCK_ALLOC))] -- 2.55.0.508.g3f0d502094-goog