* [PATCH v3 1/5] rust: sync: move lockdep types to rust/kernel/sync/lockdep.rs
2026-07-22 10:12 [PATCH v3 0/5] Rate limited printing for Rust Alice Ryhl
@ 2026-07-22 10:12 ` Alice Ryhl
2026-07-22 10:12 ` [PATCH v3 2/5] rust: sync: add const constructor for raw_spinlock_t Alice Ryhl
` (3 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Alice Ryhl @ 2026-07-22 10:12 UTC (permalink / raw)
To: Greg Kroah-Hartman, Carlos Llamas, Boqun Feng, Gary Guo
Cc: Onur Özkan, Andreas Hindborg, Benno Lossin,
Björn Roy Baron, Daniel Almeida, Danilo Krummrich,
Ingo Molnar, Lyude Paul, Miguel Ojeda, Peter Zijlstra,
Trevor Gross, Waiman Long, Will Deacon, linux-kernel,
rust-for-linux, Alice Ryhl
The lockdep types are currently stored directly in rust/kernel/sync.rs,
but there are starting to be too many of them to keep them in that file.
Thus, move them to a submodule. The new module is listed under LOCKING
PRIMITIVES in the MAINTAINERS file.
For commonly used lockdep logic it's useful to keep re-exports in
kernel::sync, and this also avoids the need to update any users.
Reviewed-by: Gary Guo <gary@garyguo.net>
Reviewed-by: Boqun Feng <boqun@kernel.org>
Reviewed-by: Carlos Llamas <cmllamas@google.com>
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
---
MAINTAINERS | 1 +
rust/kernel/sync.rs | 135 +-----------------------------------------
rust/kernel/sync/lockdep.rs | 139 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 142 insertions(+), 133 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index a3edb1fa7954..814ca2b12f16 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15156,6 +15156,7 @@ F: rust/helpers/mutex.c
F: rust/helpers/spinlock.c
F: rust/kernel/sync/lock.rs
F: rust/kernel/sync/lock/
+F: rust/kernel/sync/lockdep.rs
F: rust/kernel/sync/locked_by.rs
X: kernel/locking/locktorture.c
diff --git a/rust/kernel/sync.rs b/rust/kernel/sync.rs
index 993dbf2caa0e..e87a7e339994 100644
--- a/rust/kernel/sync.rs
+++ b/rust/kernel/sync.rs
@@ -5,10 +5,6 @@
//! This module contains the kernel APIs related to synchronisation that have been ported or
//! wrapped for usage by Rust code in the kernel.
-use crate::prelude::*;
-use crate::types::Opaque;
-use pin_init;
-
mod arc;
pub mod aref;
pub mod atomic;
@@ -16,6 +12,7 @@
pub mod completion;
mod condvar;
pub mod lock;
+pub mod lockdep;
mod locked_by;
pub mod poll;
pub mod rcu;
@@ -28,135 +25,7 @@
pub use lock::global::{global_lock, GlobalGuard, GlobalLock, GlobalLockBackend, GlobalLockedBy};
pub use lock::mutex::{new_mutex, Mutex, MutexGuard};
pub use lock::spinlock::{new_spinlock, SpinLock, SpinLockGuard};
+pub use lockdep::{static_lock_class, LockClassKey};
pub use locked_by::LockedBy;
pub use refcount::Refcount;
pub use set_once::SetOnce;
-
-/// Represents a lockdep class.
-///
-/// Wraps the kernel's `struct lock_class_key`.
-#[repr(transparent)]
-#[pin_data(PinnedDrop)]
-pub struct LockClassKey {
- #[pin]
- inner: Opaque<bindings::lock_class_key>,
-}
-
-// SAFETY: Unregistering a lock class key from a different thread than where it was registered is
-// allowed.
-unsafe impl Send for LockClassKey {}
-
-// SAFETY: `bindings::lock_class_key` is designed to be used concurrently from multiple threads and
-// provides its own synchronization.
-unsafe impl Sync for LockClassKey {}
-
-impl LockClassKey {
- /// Initializes a statically allocated lock class key.
- ///
- /// This is usually used indirectly through the [`static_lock_class!`] macro. See its
- /// documentation for more information.
- ///
- /// # Safety
- ///
- /// * Before using the returned value, it must be pinned in a static memory location.
- /// * The destructor must never run on the returned `LockClassKey`.
- pub const unsafe fn new_static() -> Self {
- LockClassKey {
- inner: Opaque::uninit(),
- }
- }
-
- /// Initializes a dynamically allocated lock class key.
- ///
- /// In the common case of using a statically allocated lock class key, the
- /// [`static_lock_class!`] macro should be used instead.
- ///
- /// # Examples
- ///
- /// ```
- /// use kernel::alloc::KBox;
- /// use kernel::types::ForeignOwnable;
- /// use kernel::sync::{LockClassKey, SpinLock};
- /// use pin_init::stack_pin_init;
- ///
- /// let key = KBox::pin_init(LockClassKey::new_dynamic(), GFP_KERNEL)?;
- /// let key_ptr = key.into_foreign();
- ///
- /// {
- /// stack_pin_init!(let num: SpinLock<u32> = SpinLock::new(
- /// 0,
- /// c"my_spinlock",
- /// // SAFETY: `key_ptr` is returned by the above `into_foreign()`, whose
- /// // `from_foreign()` has not yet been called.
- /// unsafe { <Pin<KBox<LockClassKey>> as ForeignOwnable>::borrow(key_ptr) }
- /// ));
- /// }
- ///
- /// // SAFETY: We dropped `num`, the only use of the key, so the result of the previous
- /// // `borrow` has also been dropped. Thus, it's safe to use from_foreign.
- /// unsafe { drop(<Pin<KBox<LockClassKey>> as ForeignOwnable>::from_foreign(key_ptr)) };
- /// # Ok::<(), Error>(())
- /// ```
- pub fn new_dynamic() -> impl PinInit<Self> {
- pin_init!(Self {
- // SAFETY: lockdep_register_key expects an uninitialized block of memory
- inner <- Opaque::ffi_init(|slot| unsafe { bindings::lockdep_register_key(slot) })
- })
- }
-
- /// Returns a raw pointer to the inner C struct.
- ///
- /// It is up to the caller to use the raw pointer correctly.
- pub fn as_ptr(&self) -> *mut bindings::lock_class_key {
- self.inner.get()
- }
-}
-
-#[pinned_drop]
-impl PinnedDrop for LockClassKey {
- fn drop(self: Pin<&mut Self>) {
- // SAFETY: `self.as_ptr()` was registered with lockdep and `self` is pinned, so the address
- // hasn't changed. Thus, it's safe to pass it to unregister.
- unsafe { bindings::lockdep_unregister_key(self.as_ptr()) }
- }
-}
-
-/// Defines a new static lock class and returns a pointer to it.
-///
-/// # Examples
-///
-/// ```
-/// use kernel::sync::{static_lock_class, Arc, SpinLock};
-///
-/// fn new_locked_int() -> Result<Arc<SpinLock<u32>>> {
-/// Arc::pin_init(SpinLock::new(
-/// 42,
-/// c"new_locked_int",
-/// static_lock_class!(),
-/// ), GFP_KERNEL)
-/// }
-/// ```
-#[macro_export]
-macro_rules! static_lock_class {
- () => {{
- static CLASS: $crate::sync::LockClassKey =
- // SAFETY: The returned `LockClassKey` is stored in static memory and we pin it. Drop
- // never runs on a static global.
- unsafe { $crate::sync::LockClassKey::new_static() };
- $crate::prelude::Pin::static_ref(&CLASS)
- }};
-}
-pub use static_lock_class;
-
-/// Returns the given string, if one is provided, otherwise generates one based on the source code
-/// location.
-#[doc(hidden)]
-#[macro_export]
-macro_rules! optional_name {
- () => {
- $crate::c_str!(::core::concat!(::core::file!(), ":", ::core::line!()))
- };
- ($name:literal) => {
- $crate::c_str!($name)
- };
-}
diff --git a/rust/kernel/sync/lockdep.rs b/rust/kernel/sync/lockdep.rs
new file mode 100644
index 000000000000..784821cc2a39
--- /dev/null
+++ b/rust/kernel/sync/lockdep.rs
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Utilities related to lockdep.
+//!
+//! C headers: [`include/linux/lockdep.h`](srctree/include/linux/lockdep.h)
+
+use crate::{
+ prelude::*,
+ types::Opaque, //
+};
+
+/// Represents a lockdep class.
+///
+/// Wraps the kernel's `struct lock_class_key`.
+#[repr(transparent)]
+#[pin_data(PinnedDrop)]
+pub struct LockClassKey {
+ #[pin]
+ inner: Opaque<bindings::lock_class_key>,
+}
+
+// SAFETY: Unregistering a lock class key from a different thread than where it was registered is
+// allowed.
+unsafe impl Send for LockClassKey {}
+
+// SAFETY: `bindings::lock_class_key` is designed to be used concurrently from multiple threads and
+// provides its own synchronization.
+unsafe impl Sync for LockClassKey {}
+
+impl LockClassKey {
+ /// Initializes a statically allocated lock class key.
+ ///
+ /// This is usually used indirectly through the [`static_lock_class!`] macro. See its
+ /// documentation for more information.
+ ///
+ /// # Safety
+ ///
+ /// * Before using the returned value, it must be pinned in a static memory location.
+ /// * The destructor must never run on the returned `LockClassKey`.
+ pub const unsafe fn new_static() -> Self {
+ LockClassKey {
+ inner: Opaque::uninit(),
+ }
+ }
+
+ /// Initializes a dynamically allocated lock class key.
+ ///
+ /// In the common case of using a statically allocated lock class key, the
+ /// [`static_lock_class!`] macro should be used instead.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::alloc::KBox;
+ /// use kernel::types::ForeignOwnable;
+ /// use kernel::sync::{LockClassKey, SpinLock};
+ /// use pin_init::stack_pin_init;
+ ///
+ /// let key = KBox::pin_init(LockClassKey::new_dynamic(), GFP_KERNEL)?;
+ /// let key_ptr = key.into_foreign();
+ ///
+ /// {
+ /// stack_pin_init!(let num: SpinLock<u32> = SpinLock::new(
+ /// 0,
+ /// c"my_spinlock",
+ /// // SAFETY: `key_ptr` is returned by the above `into_foreign()`, whose
+ /// // `from_foreign()` has not yet been called.
+ /// unsafe { <Pin<KBox<LockClassKey>> as ForeignOwnable>::borrow(key_ptr) }
+ /// ));
+ /// }
+ ///
+ /// // SAFETY: We dropped `num`, the only use of the key, so the result of the previous
+ /// // `borrow` has also been dropped. Thus, it's safe to use from_foreign.
+ /// unsafe { drop(<Pin<KBox<LockClassKey>> as ForeignOwnable>::from_foreign(key_ptr)) };
+ /// # Ok::<(), Error>(())
+ /// ```
+ pub fn new_dynamic() -> impl PinInit<Self> {
+ pin_init!(Self {
+ // SAFETY: lockdep_register_key expects an uninitialized block of memory
+ inner <- Opaque::ffi_init(|slot| unsafe { bindings::lockdep_register_key(slot) })
+ })
+ }
+
+ /// Returns a raw pointer to the inner C struct.
+ ///
+ /// It is up to the caller to use the raw pointer correctly.
+ pub fn as_ptr(&self) -> *mut bindings::lock_class_key {
+ self.inner.get()
+ }
+}
+
+#[pinned_drop]
+impl PinnedDrop for LockClassKey {
+ fn drop(self: Pin<&mut Self>) {
+ // SAFETY: `self.as_ptr()` was registered with lockdep and `self` is pinned, so the address
+ // hasn't changed. Thus, it's safe to pass it to unregister.
+ unsafe { bindings::lockdep_unregister_key(self.as_ptr()) }
+ }
+}
+
+/// Defines a new static lock class and returns a pointer to it.
+///
+/// # Examples
+///
+/// ```
+/// use kernel::sync::{static_lock_class, Arc, SpinLock};
+///
+/// fn new_locked_int() -> Result<Arc<SpinLock<u32>>> {
+/// Arc::pin_init(SpinLock::new(
+/// 42,
+/// c"new_locked_int",
+/// static_lock_class!(),
+/// ), GFP_KERNEL)
+/// }
+/// ```
+#[macro_export]
+macro_rules! static_lock_class {
+ () => {{
+ static CLASS: $crate::sync::LockClassKey =
+ // SAFETY: The returned `LockClassKey` is stored in static memory and we pin it. Drop
+ // never runs on a static global.
+ unsafe { $crate::sync::LockClassKey::new_static() };
+ $crate::prelude::Pin::static_ref(&CLASS)
+ }};
+}
+pub use static_lock_class;
+
+/// Returns the given string, if one is provided, otherwise generates one based on the source code
+/// location.
+#[doc(hidden)]
+#[macro_export]
+macro_rules! optional_name {
+ () => {
+ $crate::c_str!(::core::concat!(::core::file!(), ":", ::core::line!()))
+ };
+ ($name:literal) => {
+ $crate::c_str!($name)
+ };
+}
--
2.55.0.229.g6434b31f56-goog
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v3 2/5] rust: sync: add const constructor for raw_spinlock_t
2026-07-22 10:12 [PATCH v3 0/5] Rate limited printing for Rust Alice Ryhl
2026-07-22 10:12 ` [PATCH v3 1/5] rust: sync: move lockdep types to rust/kernel/sync/lockdep.rs Alice Ryhl
@ 2026-07-22 10:12 ` Alice Ryhl
2026-07-22 10:12 ` [PATCH v3 3/5] rust: add pr_*_ratelimit! macros for printing Alice Ryhl
` (2 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Alice Ryhl @ 2026-07-22 10:12 UTC (permalink / raw)
To: Greg Kroah-Hartman, Carlos Llamas, Boqun Feng, Gary Guo
Cc: Onur Özkan, Andreas Hindborg, Benno Lossin,
Björn Roy Baron, Daniel Almeida, Danilo Krummrich,
Ingo Molnar, Lyude Paul, Miguel Ojeda, Peter Zijlstra,
Trevor Gross, Waiman Long, Will Deacon, linux-kernel,
rust-for-linux, Alice Ryhl
The abstractions for pr_*_ratelimited! need to construct a global
`struct ratelimit_state`, which contains a `raw_spinlock_t` field. Thus,
add a const constructor for the `raw_spinlock_t` type.
The SPINLOCK_OWNER_INIT constant isn't mirrored via a const helper
because bindgen generates a 'static mut' instead of a constant from the
pointer constant.,
The __ARCH_SPIN_LOCK_UNLOCKED constant cannot be translated by bindgen
because it's a define for a struct without type annotations, so it's
explicitly declared in Rust.
Reviewed-by: Boqun Feng <boqun@kernel.org>
Reviewed-by: Carlos Llamas <cmllamas@google.com>
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
---
include/linux/spinlock_types_raw.h | 4 ++++
rust/bindings/lib.rs | 24 ++++++++++++++++++++++++
rust/kernel/sync/lock/spinlock.rs | 31 +++++++++++++++++++++++++++++++
rust/kernel/sync/lockdep.rs | 22 ++++++++++++++++++++++
4 files changed, 81 insertions(+)
diff --git a/include/linux/spinlock_types_raw.h b/include/linux/spinlock_types_raw.h
index e5644ab2161f..942c229c90bb 100644
--- a/include/linux/spinlock_types_raw.h
+++ b/include/linux/spinlock_types_raw.h
@@ -11,6 +11,10 @@
#include <linux/lockdep_types.h>
+/*
+ * Keep in sync with rust/kernel/sync/lock/spinlock.rs
+ */
+
context_lock_struct(raw_spinlock) {
arch_spinlock_t raw_lock;
#ifdef CONFIG_DEBUG_SPINLOCK
diff --git a/rust/bindings/lib.rs b/rust/bindings/lib.rs
index 854e7c471434..adde41e41edc 100644
--- a/rust/bindings/lib.rs
+++ b/rust/bindings/lib.rs
@@ -77,3 +77,27 @@ mod bindings_helper {
None
}
};
+
+// Explicitly list architectures where this logic is checked correct.
+#[cfg(any(
+ CONFIG_ARM,
+ CONFIG_ARM64,
+ CONFIG_LOONGARCH,
+ CONFIG_PPC,
+ CONFIG_RISCV,
+ CONFIG_S390,
+ CONFIG_X86,
+))]
+pub const __ARCH_SPIN_LOCK_UNLOCKED: arch_spinlock_t = {
+ // SAFETY: The `arch_spinlock_t` type can be zeroed.
+ #[allow(unused_mut)]
+ let mut lock: arch_spinlock_t = unsafe { core::mem::zeroed() };
+
+ #[cfg(not(CONFIG_SMP))]
+ #[cfg(CONFIG_DEBUG_SPINLOCK)]
+ {
+ lock.slock = 1;
+ }
+
+ lock
+};
diff --git a/rust/kernel/sync/lock/spinlock.rs b/rust/kernel/sync/lock/spinlock.rs
index ef76fa07ca3a..697efa7e04c6 100644
--- a/rust/kernel/sync/lock/spinlock.rs
+++ b/rust/kernel/sync/lock/spinlock.rs
@@ -4,6 +4,8 @@
//!
//! This module allows Rust code to use the kernel's `spinlock_t`.
+use kernel::prelude::*;
+
/// Creates a [`SpinLock`] initialiser with the given name and a newly-created lock class.
///
/// It uses the name if one is given, otherwise it generates one based on the file name and line
@@ -144,3 +146,32 @@ unsafe fn assert_is_held(ptr: *mut Self::State) {
unsafe { bindings::spin_assert_is_held(ptr) }
}
}
+
+/// Helper for creating a raw unlocked `bindings::raw_spinlock_t`.
+///
+/// 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))]
+ let _ = name;
+
+ bindings::raw_spinlock_t {
+ raw_lock: bindings::__ARCH_SPIN_LOCK_UNLOCKED,
+
+ #[cfg(CONFIG_DEBUG_SPINLOCK)]
+ magic: bindings::SPINLOCK_MAGIC,
+ #[cfg(CONFIG_DEBUG_SPINLOCK)]
+ owner_cpu: u32::MAX,
+ #[cfg(CONFIG_DEBUG_SPINLOCK)]
+ owner: usize::MAX as *mut c_void,
+
+ #[cfg(CONFIG_DEBUG_LOCK_ALLOC)]
+ dep_map: kernel::sync::lockdep::raw_lockdep_map(
+ name,
+ kernel::sync::lockdep::LD_WAIT_SPIN,
+ kernel::sync::lockdep::LD_WAIT_INV,
+ ),
+ }
+}
diff --git a/rust/kernel/sync/lockdep.rs b/rust/kernel/sync/lockdep.rs
index 784821cc2a39..d9222be0fb29 100644
--- a/rust/kernel/sync/lockdep.rs
+++ b/rust/kernel/sync/lockdep.rs
@@ -137,3 +137,25 @@ macro_rules! optional_name {
$crate::c_str!($name)
};
}
+
+/// Not checked, catch all.
+pub const LD_WAIT_INV: u8 = bindings::lockdep_wait_type_LD_WAIT_INV as u8;
+/// Spin loops, `raw_spinlock_t` etc
+pub const LD_WAIT_SPIN: u8 = bindings::lockdep_wait_type_LD_WAIT_SPIN as u8;
+
+/// Helper for declaring a raw `struct lockdep_map` for locks in statics.
+///
+/// It's up to the caller to use the returned `struct lockdep_map` correctly.
+#[cfg(CONFIG_DEBUG_LOCK_ALLOC)]
+pub(crate) const fn raw_lockdep_map(
+ name: &'static CStr,
+ wait_type_inner: u8,
+ wait_type_outer: u8,
+) -> bindings::lockdep_map {
+ // SAFETY: All zeros is valid for this type.
+ let mut map: bindings::lockdep_map = unsafe { core::mem::zeroed() };
+ map.name = kernel::str::as_char_ptr_in_const_context(name);
+ map.wait_type_inner = wait_type_inner;
+ map.wait_type_outer = wait_type_outer;
+ map
+}
--
2.55.0.229.g6434b31f56-goog
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v3 3/5] rust: add pr_*_ratelimit! macros for printing
2026-07-22 10:12 [PATCH v3 0/5] Rate limited printing for Rust Alice Ryhl
2026-07-22 10:12 ` [PATCH v3 1/5] rust: sync: move lockdep types to rust/kernel/sync/lockdep.rs Alice Ryhl
2026-07-22 10:12 ` [PATCH v3 2/5] rust: sync: add const constructor for raw_spinlock_t Alice Ryhl
@ 2026-07-22 10:12 ` Alice Ryhl
2026-07-22 18:15 ` Gary Guo
2026-07-22 10:12 ` [PATCH v3 4/5] rust_binder: consolidate transaction failure prints Alice Ryhl
2026-07-22 10:12 ` [PATCH v3 5/5] rust_binder: use pr_*_ratelimited! for printing Alice Ryhl
4 siblings, 1 reply; 7+ messages in thread
From: Alice Ryhl @ 2026-07-22 10:12 UTC (permalink / raw)
To: Greg Kroah-Hartman, Carlos Llamas, Boqun Feng, Gary Guo
Cc: Onur Özkan, Andreas Hindborg, Benno Lossin,
Björn Roy Baron, Daniel Almeida, Danilo Krummrich,
Ingo Molnar, Lyude Paul, Miguel Ojeda, Peter Zijlstra,
Trevor Gross, Waiman Long, Will Deacon, linux-kernel,
rust-for-linux, Alice Ryhl, Alvin Sun
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.
Tested-by: Alvin Sun <alvin.sun@linux.dev>
Reviewed-by: Carlos Llamas <cmllamas@google.com>
Link: https://github.com/Rust-for-Linux/linux/issues/122
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
---
rust/helpers/helpers.c | 1 +
rust/helpers/ratelimit.c | 14 +++
rust/kernel/lib.rs | 1 +
rust/kernel/prelude.rs | 8 ++
rust/kernel/ratelimit.rs | 215 ++++++++++++++++++++++++++++++++++++++
rust/kernel/sync/lock/spinlock.rs | 1 -
6 files changed, 239 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..426992e452a2
--- /dev/null
+++ b/rust/kernel/ratelimit.rs
@@ -0,0 +1,215 @@
+// 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.
+ 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.229.g6434b31f56-goog
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [PATCH v3 3/5] rust: add pr_*_ratelimit! macros for printing
2026-07-22 10:12 ` [PATCH v3 3/5] rust: add pr_*_ratelimit! macros for printing Alice Ryhl
@ 2026-07-22 18:15 ` Gary Guo
0 siblings, 0 replies; 7+ messages in thread
From: Gary Guo @ 2026-07-22 18:15 UTC (permalink / raw)
To: Alice Ryhl, Greg Kroah-Hartman, Carlos Llamas, Boqun Feng, Gary Guo
Cc: Onur Özkan, Andreas Hindborg, Benno Lossin,
Björn Roy Baron, Daniel Almeida, Danilo Krummrich,
Ingo Molnar, Lyude Paul, Miguel Ojeda, Peter Zijlstra,
Trevor Gross, Waiman Long, Will Deacon, linux-kernel,
rust-for-linux, Alvin Sun
On Wed Jul 22, 2026 at 11:12 AM BST, Alice Ryhl wrote:
> 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.
>
> Tested-by: Alvin Sun <alvin.sun@linux.dev>
> Reviewed-by: Carlos Llamas <cmllamas@google.com>
> Link: https://github.com/Rust-for-Linux/linux/issues/122
> Signed-off-by: Alice Ryhl <aliceryhl@google.com>
> ---
> rust/helpers/helpers.c | 1 +
> rust/helpers/ratelimit.c | 14 +++
> rust/kernel/lib.rs | 1 +
> rust/kernel/prelude.rs | 8 ++
> rust/kernel/ratelimit.rs | 215 ++++++++++++++++++++++++++++++++++++++
> rust/kernel/sync/lock/spinlock.rs | 1 -
> 6 files changed, 239 insertions(+), 1 deletion(-)
>
> diff --git a/rust/kernel/ratelimit.rs b/rust/kernel/ratelimit.rs
> new file mode 100644
> index 000000000000..426992e452a2
> --- /dev/null
> +++ b/rust/kernel/ratelimit.rs
> @@ -0,0 +1,215 @@
> +// 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.
This one is const-only so shouldn't be code-generated.
#[inline]
Best,
Gary
> + 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()
> + }),
> + }
> + }
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v3 4/5] rust_binder: consolidate transaction failure prints
2026-07-22 10:12 [PATCH v3 0/5] Rate limited printing for Rust Alice Ryhl
` (2 preceding siblings ...)
2026-07-22 10:12 ` [PATCH v3 3/5] rust: add pr_*_ratelimit! macros for printing Alice Ryhl
@ 2026-07-22 10:12 ` Alice Ryhl
2026-07-22 10:12 ` [PATCH v3 5/5] rust_binder: use pr_*_ratelimited! for printing Alice Ryhl
4 siblings, 0 replies; 7+ messages in thread
From: Alice Ryhl @ 2026-07-22 10:12 UTC (permalink / raw)
To: Greg Kroah-Hartman, Carlos Llamas, Boqun Feng, Gary Guo
Cc: Onur Özkan, Andreas Hindborg, Benno Lossin,
Björn Roy Baron, Daniel Almeida, Danilo Krummrich,
Ingo Molnar, Lyude Paul, Miguel Ojeda, Peter Zijlstra,
Trevor Gross, Waiman Long, Will Deacon, linux-kernel,
rust-for-linux, Alice Ryhl
When a transaction fails, it currently hits multiple print statements
meaning that a single failure can result in several lines in the kernel
log. This is unnecessary, so consolidate them into one print used for
all transaction failures.
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
---
drivers/android/binder/error.rs | 4 ---
drivers/android/binder/thread.rs | 55 ++++++++++++++---------------------
drivers/android/binder/transaction.rs | 20 ++-----------
rust/kernel/error.rs | 2 +-
4 files changed, 26 insertions(+), 55 deletions(-)
diff --git a/drivers/android/binder/error.rs b/drivers/android/binder/error.rs
index 1296072c35d9..aed1c747640b 100644
--- a/drivers/android/binder/error.rs
+++ b/drivers/android/binder/error.rs
@@ -37,10 +37,6 @@ pub(crate) fn new_frozen_oneway() -> Self {
source: None,
}
}
-
- pub(crate) fn is_dead(&self) -> bool {
- self.reply == BR_DEAD_REPLY
- }
}
/// Convert an errno into a `BinderError` and store the errno used to construct it. The errno
diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs
index a51821dde0ad..44ad4c2e8786 100644
--- a/drivers/android/binder/thread.rs
+++ b/drivers/android/binder/thread.rs
@@ -25,7 +25,7 @@
use crate::{
allocation::{Allocation, AllocationView, BinderObject, BinderObjectRef, NewAllocation},
defs::*,
- error::BinderResult,
+ error::{BinderError, BinderResult},
process::{GetWorkOrRegister, Process},
ptr_align,
stats::GLOBAL_STATS,
@@ -1022,17 +1022,7 @@ pub(crate) fn copy_transaction_data(
size_of::<u64>(),
);
let secctx_off = aligned_data_size + offsets_size + buffers_size;
- let mut alloc = match to_process.buffer_alloc(debug_id, len, info) {
- Ok(alloc) => alloc,
- Err(err) => {
- pr_warn!(
- "Failed to allocate buffer. len:{}, is_oneway:{}",
- len,
- info.is_oneway(),
- );
- return Err(err);
- }
- };
+ let mut alloc = to_process.buffer_alloc(debug_id, len, info)?;
let mut buffer_reader = UserSlice::new(info.data_ptr, data_size).reader();
let mut end_of_previous_object = 0;
@@ -1283,6 +1273,9 @@ fn transaction(self: &Arc<Self>, cmd: u32, reader: &mut UserSliceReader) -> Resu
self.transaction_inner(&mut info)
};
+ // This runs when return work is passed to the caller. This is not
+ // always the same as the transaction failing, as reply errors are
+ // delivered to the remote process.
if let Err(err) = ret {
self.push_return_work(err.reply);
if err.reply != BR_TRANSACTION_COMPLETE {
@@ -1290,13 +1283,21 @@ fn transaction(self: &Arc<Self>, cmd: u32, reader: &mut UserSliceReader) -> Resu
if let Some(source) = &err.source {
info.errno = source.to_errno();
- {
- let mut inner = self.inner.lock();
- inner.extended_error =
- ExtendedError::new(info.debug_id as u32, err.reply, source.to_errno());
- }
+ self.inner.lock().extended_error =
+ ExtendedError::new(info.debug_id as u32, err.reply, source.to_errno());
}
+ }
+ }
+ if info.oneway_spam_suspect {
+ // If this is both a oneway spam suspect and a failure, we report it twice. This is
+ // useful in case the transaction failed with BR_TRANSACTION_PENDING_FROZEN.
+ info.report_netlink(BR_ONEWAY_SPAM_SUSPECT, &self.process.ctx);
+ }
+ // This runs when the transaction failed.
+ if info.reply != 0 {
+ info.report_netlink(info.reply, &self.process.ctx);
+ if info.errno != 0 {
binder_debug!(
FailedTransaction,
"transaction {} to {}:{} failed {:?}, code {} size {}-{}",
@@ -1309,7 +1310,10 @@ fn transaction(self: &Arc<Self>, cmd: u32, reader: &mut UserSliceReader) -> Resu
},
info.to_pid,
info.to_tid,
- err,
+ BinderError {
+ reply: info.reply,
+ source: Error::try_from_errno(info.errno),
+ },
info.code,
info.data_size,
info.offsets_size
@@ -1317,15 +1321,6 @@ fn transaction(self: &Arc<Self>, cmd: u32, reader: &mut UserSliceReader) -> Resu
}
}
- if info.oneway_spam_suspect {
- // If this is both a oneway spam suspect and a failure, we report it twice. This is
- // useful in case the transaction failed with BR_TRANSACTION_PENDING_FROZEN.
- info.report_netlink(BR_ONEWAY_SPAM_SUSPECT, &self.process.ctx);
- }
- if info.reply != 0 {
- info.report_netlink(info.reply, &self.process.ctx);
- }
-
Ok(())
}
@@ -1407,12 +1402,6 @@ fn reply_inner(self: &Arc<Self>, info: &mut TransactionInfo) -> BinderResult {
// At this point we only return `BR_TRANSACTION_COMPLETE` to the caller, and we must let
// the sender know that the transaction has completed (with an error in this case).
- pr_warn!(
- "{}:{} reply to {} failed: {err:?}",
- info.from_pid,
- info.from_tid,
- info.to_pid
- );
let param = err.source.as_ref().map_or(0, |e| e.to_errno());
let ee = ExtendedError::new(info.debug_id as u32, err.reply, param);
orig.from
diff --git a/drivers/android/binder/transaction.rs b/drivers/android/binder/transaction.rs
index 13dfb5c5c955..9c9e11d08c7e 100644
--- a/drivers/android/binder/transaction.rs
+++ b/drivers/android/binder/transaction.rs
@@ -139,21 +139,13 @@ pub(crate) fn new(
let txn_security_ctx = node_ref.node.flags & FLAT_BINDER_FLAG_TXN_SECURITY_CTX != 0;
let mut txn_security_ctx_off = if txn_security_ctx { Some(0) } else { None };
let to = node_ref.node.owner.clone();
- let mut alloc = match from.copy_transaction_data(
+ let mut alloc = from.copy_transaction_data(
to.clone(),
info,
info.debug_id,
allow_fds,
txn_security_ctx_off.as_mut(),
- ) {
- Ok(alloc) => alloc,
- Err(err) => {
- if !err.is_dead() {
- pr_warn!("Failure in copy_transaction_data: {:?}", err);
- }
- return Err(err);
- }
- };
+ )?;
if info.is_oneway() {
if from_parent.is_some() {
pr_warn!("Oneway transaction should not be in a transaction stack.");
@@ -194,13 +186,7 @@ pub(crate) fn new_reply(
allow_fds: bool,
) -> BinderResult<DLArc<Self>> {
let mut alloc =
- match from.copy_transaction_data(to.clone(), info, info.debug_id, allow_fds, None) {
- Ok(alloc) => alloc,
- Err(err) => {
- pr_warn!("Failure in copy_transaction_data: {:?}", err);
- return Err(err);
- }
- };
+ from.copy_transaction_data(to.clone(), info, info.debug_id, allow_fds, None)?;
if info.flags & TF_CLEAR_BUF != 0 {
alloc.set_info_clear_on_drop();
}
diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs
index a56ba6309594..380cd3f7276b 100644
--- a/rust/kernel/error.rs
+++ b/rust/kernel/error.rs
@@ -135,7 +135,7 @@ pub fn from_errno(errno: crate::ffi::c_int) -> Error {
/// Creates an [`Error`] from a kernel error code.
///
/// Returns [`None`] if `errno` is out-of-range.
- const fn try_from_errno(errno: crate::ffi::c_int) -> Option<Error> {
+ pub const fn try_from_errno(errno: crate::ffi::c_int) -> Option<Error> {
if errno < -(bindings::MAX_ERRNO as i32) || errno >= 0 {
return None;
}
--
2.55.0.229.g6434b31f56-goog
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v3 5/5] rust_binder: use pr_*_ratelimited! for printing
2026-07-22 10:12 [PATCH v3 0/5] Rate limited printing for Rust Alice Ryhl
` (3 preceding siblings ...)
2026-07-22 10:12 ` [PATCH v3 4/5] rust_binder: consolidate transaction failure prints Alice Ryhl
@ 2026-07-22 10:12 ` Alice Ryhl
4 siblings, 0 replies; 7+ messages in thread
From: Alice Ryhl @ 2026-07-22 10:12 UTC (permalink / raw)
To: Greg Kroah-Hartman, Carlos Llamas, Boqun Feng, Gary Guo
Cc: Onur Özkan, Andreas Hindborg, Benno Lossin,
Björn Roy Baron, Daniel Almeida, Danilo Krummrich,
Ingo Molnar, Lyude Paul, Miguel Ojeda, Peter Zijlstra,
Trevor Gross, Waiman Long, Will Deacon, linux-kernel,
rust-for-linux, Alice Ryhl
To avoid DoS from printing too much, make printing in Binder rate
limited.
A big portion of these print statements have been updated to use
binder_debug!, but some still remain to be converted. For now, just
update them to use pr_*_ratelimted! until we get around to converting
them to use binder_debug! too. While we're at it, fix the missing
newlines at the end of some of those println statements.
Acked-by: Carlos Llamas <cmllamas@google.com>
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
---
drivers/android/binder/allocation.rs | 4 +--
drivers/android/binder/context.rs | 6 ++---
drivers/android/binder/debug.rs | 4 +--
drivers/android/binder/freeze.rs | 2 +-
drivers/android/binder/node.rs | 4 +--
drivers/android/binder/page_range.rs | 12 ++++-----
drivers/android/binder/process.rs | 22 ++++++++--------
drivers/android/binder/thread.rs | 48 ++++++++++++++++++++---------------
drivers/android/binder/transaction.rs | 4 +--
9 files changed, 57 insertions(+), 49 deletions(-)
diff --git a/drivers/android/binder/allocation.rs b/drivers/android/binder/allocation.rs
index 165cb797eb1e..8151ba3ea7f4 100644
--- a/drivers/android/binder/allocation.rs
+++ b/drivers/android/binder/allocation.rs
@@ -261,7 +261,7 @@ fn drop(&mut self) {
let view = AllocationView::new(self, offsets.start);
for i in offsets.step_by(size_of::<u64>()) {
if view.cleanup_object(i).is_err() {
- pr_warn!("Error cleaning up object at offset {}\n", i)
+ pr_warn_ratelimited!("Error cleaning up object at offset {}\n", i)
}
}
}
@@ -286,7 +286,7 @@ fn drop(&mut self) {
if info.clear_on_free {
if let Err(e) = self.fill_zero() {
- pr_warn!("Failed to clear data on free: {:?}", e);
+ pr_warn_ratelimited!("Failed to clear data on free: {:?}\n", e);
}
}
}
diff --git a/drivers/android/binder/context.rs b/drivers/android/binder/context.rs
index ddddb66b3557..431d6007a9b1 100644
--- a/drivers/android/binder/context.rs
+++ b/drivers/android/binder/context.rs
@@ -80,7 +80,7 @@ pub(crate) fn deregister(self: &Arc<Self>) {
pub(crate) fn register_process(self: &Arc<Self>, proc: Arc<Process>) -> Result {
if !Arc::ptr_eq(self, &proc.ctx) {
- pr_err!("Context::register_process called on the wrong context.");
+ pr_err_ratelimited!("Context::register_process called on the wrong context.\n");
return Err(EINVAL);
}
self.manager.lock().all_procs.push(proc, GFP_KERNEL)?;
@@ -89,7 +89,7 @@ pub(crate) fn register_process(self: &Arc<Self>, proc: Arc<Process>) -> Result {
pub(crate) fn deregister_process(self: &Arc<Self>, proc: &Arc<Process>) {
if !Arc::ptr_eq(self, &proc.ctx) {
- pr_err!("Context::deregister_process called on the wrong context.");
+ pr_err_ratelimited!("Context::deregister_process called on the wrong context.\n");
return;
}
let mut manager = self.manager.lock();
@@ -110,7 +110,7 @@ pub(crate) fn deregister_process(self: &Arc<Self>, proc: &Arc<Process>) {
pub(crate) fn set_manager_node(&self, node_ref: NodeRef) -> Result {
let mut manager = self.manager.lock();
if manager.node.is_some() {
- pr_warn!("BINDER_SET_CONTEXT_MGR already set");
+ pr_warn_ratelimited!("BINDER_SET_CONTEXT_MGR already set\n");
return Err(EBUSY);
}
security::binder_set_context_mgr(&node_ref.node.owner.cred)?;
diff --git a/drivers/android/binder/debug.rs b/drivers/android/binder/debug.rs
index 824b10c004c3..6d8dcddf4619 100644
--- a/drivers/android/binder/debug.rs
+++ b/drivers/android/binder/debug.rs
@@ -53,7 +53,7 @@ macro_rules! binder_debug {
// Rule to explicitly specify a PID (used in kworkers).
(pid=$pid:expr, $mask:ident, $($arg:tt)*) => {
if $crate::debug::debug_mask_enabled($crate::debug::DebugMask::$mask) {
- kernel::pr_info!(
+ kernel::pr_info_ratelimited!(
"{}: {}\n",
$pid,
kernel::prelude::fmt!($($arg)*)
@@ -65,7 +65,7 @@ macro_rules! binder_debug {
($mask:ident, $($arg:tt)*) => {
if $crate::debug::debug_mask_enabled($crate::debug::DebugMask::$mask) {
let thread = kernel::current!();
- kernel::pr_info!(
+ kernel::pr_info_ratelimited!(
"{}:{} {}\n",
thread.tgid(),
thread.pid(),
diff --git a/drivers/android/binder/freeze.rs b/drivers/android/binder/freeze.rs
index 66912b4cb527..7fbde3345ced 100644
--- a/drivers/android/binder/freeze.rs
+++ b/drivers/android/binder/freeze.rs
@@ -412,7 +412,7 @@ fn find_freeze_recipients(&self) -> Result<KVVec<(DArc<Node>, Arc<Process>)>, Al
recipients
.push_within_capacity(node_proc_pair)
.map_err(|_| {
- pr_err!(
+ pr_err_ratelimited!(
"push_within_capacity failed even though we checked the capacity\n"
);
AllocError
diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs
index b74ef32b0d94..d6d093dc2cd0 100644
--- a/drivers/android/binder/node.rs
+++ b/drivers/android/binder/node.rs
@@ -401,7 +401,7 @@ pub(crate) fn update_refcount_locked(
!is_dead && !state.has_count
} else {
if state.count < count {
- pr_err!("Failure: refcount underflow!");
+ pr_err_ratelimited!("Failure: refcount underflow!\n");
return None;
}
state.count -= count;
@@ -689,7 +689,7 @@ pub(crate) fn remove_freeze_listener(&self, p: &Process) -> KVVec<Arc<Process>>
.freeze_list
.retain(|proc| !core::ptr::eq::<Process>(&**proc, p));
if len == inner.freeze_list.len() {
- pr_warn!(
+ pr_warn_ratelimited!(
"Could not remove freeze listener for {}\n",
p.pid_in_current_ns()
);
diff --git a/drivers/android/binder/page_range.rs b/drivers/android/binder/page_range.rs
index 52ffbf3504e7..c1f4f635c2c0 100644
--- a/drivers/android/binder/page_range.rs
+++ b/drivers/android/binder/page_range.rs
@@ -212,7 +212,7 @@ unsafe fn set_page(me: *mut PageInfo, page: Page) {
// SAFETY: The pointer is valid for writing, so also valid for reading.
if unsafe { (*ptr).is_some() } {
- pr_err!("set_page called when there is already a page");
+ pr_err_ratelimited!("set_page called when there is already a page\n");
// SAFETY: We will initialize the page again below.
unsafe { ptr::drop_in_place(ptr) };
}
@@ -300,11 +300,11 @@ pub(crate) fn register_with_vma(&self, vma: &virt::VmaNew) -> Result<usize> {
let num_pages = num_bytes >> PAGE_SHIFT;
if !ptr::eq::<Mm>(&*self.mm, &**vma.mm()) {
- pr_debug!("Failed to register with vma: invalid vma->vm_mm");
+ pr_debug_ratelimited!("Failed to register with vma: invalid vma->vm_mm\n");
return Err(EINVAL);
}
if num_pages == 0 {
- pr_debug!("Failed to register with vma: size zero");
+ pr_debug_ratelimited!("Failed to register with vma: size zero\n");
return Err(EINVAL);
}
@@ -325,7 +325,7 @@ pub(crate) fn register_with_vma(&self, vma: &virt::VmaNew) -> Result<usize> {
let mut inner = self.lock.lock();
if inner.size > 0 {
- pr_debug!("Failed to register with vma: already registered");
+ pr_debug_ratelimited!("Failed to register with vma: already registered\n");
drop(inner);
return Err(EBUSY);
}
@@ -380,7 +380,7 @@ pub(crate) fn use_range(&self, start: usize, end: usize) -> Result<()> {
match unsafe { self.use_page_slow(i) } {
Ok(()) => {}
Err(err) => {
- pr_warn!("Error in use_page_slow: {:?}", err);
+ pr_warn_ratelimited!("Error in use_page_slow: {:?}\n", err);
return Err(err);
}
}
@@ -529,7 +529,7 @@ unsafe fn iterate<T>(&self, mut offset: usize, mut size: usize, mut cb: T) -> Re
// duration of this call to `iterate`, so nobody will change the page.
let page = unsafe { PageInfo::get_page(page_info) };
if page.is_none() {
- pr_warn!("Page is null!");
+ pr_warn_ratelimited!("Page is null!\n");
}
let page = page.ok_or(EFAULT)?;
cb(page, offset, available)?;
diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs
index 1778628d8acd..87628e7a3306 100644
--- a/drivers/android/binder/process.rs
+++ b/drivers/android/binder/process.rs
@@ -324,7 +324,7 @@ pub(crate) fn death_delivered(&mut self, death: DArc<NodeDeath>) {
if let Some(death) = ListArc::try_from_arc_or_drop(death) {
self.delivered_deaths.push_back(death);
} else {
- pr_warn!("Notification added to `delivered_deaths` twice.");
+ pr_warn_ratelimited!("Notification added to `delivered_deaths` twice.\n");
}
}
@@ -701,7 +701,7 @@ fn get_current_thread(self: ArcBorrow<'_, Self>) -> Result<Arc<Thread>> {
let id = {
let current = kernel::current!();
if self.task != current.group_leader() {
- pr_err!("get_current_thread was called from the wrong process.");
+ pr_err_ratelimited!("get_current_thread was called from the wrong process.\n");
return Err(EINVAL);
}
current.pid()
@@ -725,7 +725,7 @@ fn get_current_thread(self: ArcBorrow<'_, Self>) -> Result<Arc<Thread>> {
Ok(ta)
}
rbtree::Entry::Occupied(_entry) => {
- pr_err!("Cannot create two threads with the same id.");
+ pr_err_ratelimited!("Cannot create two threads with the same id.\n");
Err(EINVAL)
}
}
@@ -861,7 +861,9 @@ pub(crate) fn insert_or_update_handle(
match refs.by_handle.entry(res.as_u32()) {
rbtree::Entry::Vacant(entry) => break (res, entry),
rbtree::Entry::Occupied(_) => {
- pr_err!("Detected mismatch between handle_is_present and by_handle");
+ pr_err_ratelimited!(
+ "Detected mismatch between handle_is_present and by_handle\n"
+ );
res.acquire();
kernel::warn_on!(true);
return Err(EINVAL);
@@ -1101,7 +1103,7 @@ pub(crate) fn buffer_alloc(
) {
Ok(()) => {}
Err(err) => {
- pr_warn!("use_range failure {:?}", err);
+ pr_warn_ratelimited!("use_range failure {:?}\n", err);
return Err(err.into());
}
}
@@ -1132,7 +1134,7 @@ pub(crate) fn buffer_raw_free(&self, ptr: usize) {
let freed_range = match mapping.alloc.reservation_abort(offset) {
Ok(freed_range) => freed_range,
Err(_) => {
- pr_warn!(
+ pr_warn_ratelimited!(
"Pointer {:x} failed to free, base = {:x}\n",
ptr,
mapping.address
@@ -1154,7 +1156,7 @@ pub(crate) fn buffer_make_freeable(&self, offset: usize, mut data: Option<Alloca
let mut inner = self.inner.lock();
if let Some(ref mut mapping) = &mut inner.mapping {
if mapping.alloc.reservation_commit(offset, &mut data).is_err() {
- pr_warn!("Offset {} failed to be marked freeable\n", offset);
+ pr_warn_ratelimited!("Offset {} failed to be marked freeable\n", offset);
}
}
}
@@ -1516,7 +1518,7 @@ pub(crate) fn drop_outstanding_txn(&self) {
let wake = {
let mut inner = self.inner.lock();
if inner.outstanding_txns == 0 {
- pr_err!("outstanding_txns underflow");
+ pr_err_ratelimited!("outstanding_txns underflow\n");
return;
}
inner.outstanding_txns -= 1;
@@ -1832,7 +1834,7 @@ fn new(thread: &'a Arc<Thread>, guard: &mut Guard<'_, ProcessInner, SpinLockBack
// It is an error to hit this branch, and it should not be reachable. We try to do
// something reasonable when the failure path happens. Most likely, the thread in
// question will sleep forever.
- pr_err!("Same thread registered with `ready_threads` twice.");
+ pr_err_ratelimited!("Same thread registered with `ready_threads` twice.\n");
}
Self { thread }
}
@@ -1857,7 +1859,7 @@ impl Drop for WithNodes<'_> {
fn drop(&mut self) {
core::mem::swap(&mut self.nodes, &mut self.inner.nodes);
if self.nodes.iter().next().is_some() {
- pr_err!("nodes array was modified while using lock_with_nodes\n");
+ pr_err_ratelimited!("nodes array was modified while using lock_with_nodes\n");
}
}
}
diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs
index 44ad4c2e8786..e5d11b47ad2f 100644
--- a/drivers/android/binder/thread.rs
+++ b/drivers/android/binder/thread.rs
@@ -159,8 +159,8 @@ fn validate_parent_fixup(
let sg_entry = match self.sg_entries.get(sg_idx) {
Some(sg_entry) => sg_entry,
None => {
- pr_err!(
- "self.ancestors[{}] is {}, but self.sg_entries.len() is {}",
+ pr_err_ratelimited!(
+ "self.ancestors[{}] is {}, but self.sg_entries.len() is {}\n",
ancestors_i,
sg_idx,
self.sg_entries.len()
@@ -169,8 +169,8 @@ fn validate_parent_fixup(
}
};
if sg_entry.fixup_min_offset > parent_offset {
- pr_warn!(
- "validate_parent_fixup: fixup_min_offset={}, parent_offset={}",
+ pr_warn_ratelimited!(
+ "validate_parent_fixup: fixup_min_offset={}, parent_offset={}\n",
sg_entry.fixup_min_offset,
parent_offset
);
@@ -178,8 +178,8 @@ fn validate_parent_fixup(
}
let new_min_offset = parent_offset.checked_add(length).ok_or(EINVAL)?;
if new_min_offset > sg_entry.length {
- pr_warn!(
- "validate_parent_fixup: new_min_offset={}, sg_entry.length={}",
+ pr_warn_ratelimited!(
+ "validate_parent_fixup: new_min_offset={}, sg_entry.length={}\n",
new_min_offset,
sg_entry.length
);
@@ -328,7 +328,7 @@ fn push_reply_work(&mut self, code: u32) -> PushWorkRes {
work.set_error_code(code);
self.push_work(work)
} else {
- pr_warn!("Thread reply work is already in use.");
+ pr_warn_ratelimited!("Thread reply work is already in use.\n");
PushWorkRes::Ok
}
}
@@ -339,7 +339,7 @@ fn push_return_work(&mut self, reply: u32) {
// Not notifying: Reply to current thread.
let _ = self.push_work(work);
} else {
- pr_warn!("Thread return work is already in use.");
+ pr_warn_ratelimited!("Thread return work is already in use.\n");
}
}
@@ -791,8 +791,8 @@ fn translate_object(
let parent_entry = match sg_state.sg_entries.get_mut(info.parent_sg_index) {
Some(parent_entry) => parent_entry,
None => {
- pr_err!(
- "validate_parent_fixup returned index out of bounds for sg.entries"
+ pr_err_ratelimited!(
+ "validate_parent_fixup returned index out of bounds for sg.entries\n"
);
return Err(EINVAL.into());
}
@@ -838,8 +838,8 @@ fn translate_object(
let parent_entry = match sg_state.sg_entries.get_mut(info.parent_sg_index) {
Some(parent_entry) => parent_entry,
None => {
- pr_err!(
- "validate_parent_fixup returned index out of bounds for sg.entries"
+ pr_err_ratelimited!(
+ "validate_parent_fixup returned index out of bounds for sg.entries\n"
);
return Err(EINVAL.into());
}
@@ -872,7 +872,9 @@ fn translate_object(
.read_all(&mut fda_bytes, GFP_KERNEL)?;
if fds_len != fda_bytes.len() {
- pr_err!("UserSlice::read_all returned wrong length in BINDER_TYPE_FDA");
+ pr_err_ratelimited!(
+ "UserSlice::read_all returned wrong length in BINDER_TYPE_FDA\n"
+ );
return Err(EINVAL.into());
}
@@ -987,7 +989,11 @@ pub(crate) fn copy_transaction_data(
let ctx = match security::SecurityCtx::from_secid(secid) {
Ok(ctx) => ctx,
Err(err) => {
- pr_warn!("Failed to get security ctx for id {}: {:?}", secid, err);
+ pr_warn_ratelimited!(
+ "Failed to get security ctx for id {}: {:?}\n",
+ secid,
+ err
+ );
return Err(err.into());
}
};
@@ -1211,7 +1217,7 @@ fn top_of_transaction_stack(&self) -> Result<Option<DArc<Transaction>>> {
let inner = self.inner.lock();
if let Some(cur) = &inner.current_transaction {
if core::ptr::eq(self, cur.from.as_ref()) {
- pr_warn!("got new transaction with bad transaction stack");
+ pr_warn_ratelimited!("got new transaction with bad transaction stack\n");
return Err(EINVAL);
}
Ok(Some(cur.clone()))
@@ -1539,7 +1545,7 @@ fn read(self: &Arc<Self>, req: &mut BinderWriteRead, wait: bool) -> Result {
let mut has_noop_placeholder = false;
if req.read_consumed == 0 {
if let Err(err) = writer.write_code(BR_NOOP) {
- pr_warn!("Failure when writing BR_NOOP at beginning of buffer.");
+ pr_warn_ratelimited!("Failure when writing BR_NOOP at beginning of buffer.\n");
return Err(err);
}
has_noop_placeholder = true;
@@ -1562,7 +1568,7 @@ fn read(self: &Arc<Self>, req: &mut BinderWriteRead, wait: bool) -> Result {
Err(err) => {
// Propagate the error if we haven't written anything else.
if err != EINTR && err != EAGAIN {
- pr_warn!("Failure in work getter: {:?}", err);
+ pr_warn_ratelimited!("Failure in work getter: {:?}\n", err);
}
if initial_len == writer.len() {
return Err(err);
@@ -1597,8 +1603,8 @@ pub(crate) fn write_read(self: &Arc<Self>, data: UserSlice, wait: bool) -> Resul
ret = self.write(&mut req);
crate::trace::trace_write_done(ret);
if let Err(err) = ret {
- pr_warn!(
- "Write failure {:?} in pid:{}",
+ pr_warn_ratelimited!(
+ "Write failure {:?} in pid:{}\n",
err,
self.process.pid_in_current_ns()
);
@@ -1614,8 +1620,8 @@ pub(crate) fn write_read(self: &Arc<Self>, data: UserSlice, wait: bool) -> Resul
ret = self.read(&mut req, wait);
crate::trace::trace_read_done(ret);
if ret.is_err() && ret != Err(EINTR) {
- pr_warn!(
- "Read failure {:?} in pid:{}",
+ pr_warn_ratelimited!(
+ "Read failure {:?} in pid:{}\n",
ret,
self.process.pid_in_current_ns()
);
diff --git a/drivers/android/binder/transaction.rs b/drivers/android/binder/transaction.rs
index 9c9e11d08c7e..2682c2e0695a 100644
--- a/drivers/android/binder/transaction.rs
+++ b/drivers/android/binder/transaction.rs
@@ -148,7 +148,7 @@ pub(crate) fn new(
)?;
if info.is_oneway() {
if from_parent.is_some() {
- pr_warn!("Oneway transaction should not be in a transaction stack.");
+ pr_warn_ratelimited!("Oneway transaction should not be in a transaction stack.\n");
return Err(EINVAL.into());
}
alloc.set_info_oneway_node(node_ref.node.clone());
@@ -343,7 +343,7 @@ pub(crate) fn submit(self: DLArc<Self>, info: &mut TransactionInfo) -> BinderRes
return Ok(());
}
} else {
- pr_err!("Failed to submit oneway transaction to node.");
+ pr_err_ratelimited!("Failed to submit oneway transaction to node.\n");
}
}
--
2.55.0.229.g6434b31f56-goog
^ permalink raw reply [flat|nested] 7+ messages in thread