linux-mm.kvack.org archive mirror
 help / color / mirror / Atom feed
* [PATCH RFC v2 0/3] rust: Add Per-CPU Variable API
@ 2025-04-14 19:28 Mitchell Levy
  2025-04-14 19:28 ` [PATCH RFC v2 1/3] rust: percpu: introduce a rust API for per-CPU variables Mitchell Levy
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Mitchell Levy @ 2025-04-14 19:28 UTC (permalink / raw)
  To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Andrew Morton, Dennis Zhou, Tejun Heo,
	Christoph Lameter, Danilo Krummrich
  Cc: linux-kernel, rust-for-linux, linux-mm, Mitchell Levy

This series adds an API for declaring an using per-CPU variables from
Rust, and it also adds support for Rust access to C per-CPU variables
(subject to some soundness requirements). It also adds a small test
module, lib/percpu_test_rust.rs, in the vein of lib/percpu_test.c.

---
This series builds on my earlier RFC by adding support for dynamically
allocated per-CPU variables. In the process of this work, I became
convinced that the API in the original RFC was untenable. In particular,
copying a PerCpuRef would need to be an unsafe operation whose safety
requirements would be very onerous to verify. Essentially, a PerCpuRef
would need to be entirely unique on each CPU core (except for the moment
the copy is created, immediately before it is sent off to another thread
--- technically the requirement is that the copy could not be Deref'd
before being sent). This is essentially impossible to satisfy if you
wish to use on_each_cpu, and it is certainly not ergonomic.

The key challenge was preventing the creation of aliasing &mut T's while
still allowing PerCpu<T>'s to be freely copied. Ultimately, Boqun
suggested a closure-based API in which the user asserts that the
contents of the closure don't "nest" with clones. That is, the user
asserts that they're *not* doing something like:
```
let mut my_pcpu: PerCpu<u32> = /* ... */;
let mut my_pcpu2: PerCpu<u32> = my_pcpu.clone();
unsafe { my_pcpu.get(CpuGuard::new()) }.with(|my_pcpu_val: &mut u32| {
    // UNSAFETY: This is bad
    unsafe { my_pcpu2.get(CpuGuard::new()) }
      .with(|aliasing_ref: &mut u32| { /* ... */ });
}
```
This safety condition is (in my opinion) much easier to verify because
we must only ensure the contents of the closure are well-behaved, rather
than try and trace all possible paths that a PerCpuRef may take.

This API is certainly different from the original RFC, which is why I've
sent another RFC. It's now much closer to the user-space thread-local
API, though still avoids requiring the use of Cell-like types.

A small number of simplifying choices are made here, namely that
allocations are always GFP_KERNEL. This was done with the intention of
keeping the patch small while the overall shape of the API is evaluated.

Signed-off-by: Mitchell Levy <levymitchell0@gmail.com>

---
Changes from RFC:
- Renamed PerCpuVariable to StaticPerCpuSymbol to be more descriptive
- Support dynamically allocated per-CPU variables via the
  PerCpuAllocation type. Rework statically allocated variables to use
  this new type.
- Make use of a token/closure-based API via the PerCpu and PerCpuToken
  types, rather than an API based on PerCpuRef that automatically
  Deref(Mut)'s into a &(mut) T.
- Rebased
- Link to RFC: https://lore.kernel.org/r/20241219-rust-percpu-v1-0-209117e822b1@gmail.com

---
Mitchell Levy (3):
      rust: percpu: introduce a rust API for per-CPU variables
      rust: rust-analyzer: add lib to dirs searched for crates
      rust: percpu: add a rust per-CPU variable test

 lib/Kconfig.debug                 |   9 ++
 lib/Makefile                      |   1 +
 lib/percpu_test_rust.rs           | 119 +++++++++++++++++++
 rust/helpers/helpers.c            |   2 +
 rust/helpers/percpu.c             |  20 ++++
 rust/helpers/preempt.c            |  14 +++
 rust/kernel/lib.rs                |   3 +
 rust/kernel/percpu.rs             | 239 ++++++++++++++++++++++++++++++++++++++
 rust/kernel/percpu/cpu_guard.rs   |  29 +++++
 scripts/generate_rust_analyzer.py |   2 +-
 10 files changed, 437 insertions(+), 1 deletion(-)
---
base-commit: a2cc6ff5ec8f91bc463fd3b0c26b61166a07eb11
change-id: 20240813-rust-percpu-ea2f54b5da33

Best regards,
-- 
Mitchell Levy <levymitchell0@gmail.com>



^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH RFC v2 1/3] rust: percpu: introduce a rust API for per-CPU variables
  2025-04-14 19:28 [PATCH RFC v2 0/3] rust: Add Per-CPU Variable API Mitchell Levy
@ 2025-04-14 19:28 ` Mitchell Levy
  2025-04-14 19:28 ` [PATCH RFC v2 2/3] rust: rust-analyzer: add lib to dirs searched for crates Mitchell Levy
  2025-04-14 19:28 ` [PATCH RFC v2 3/3] rust: percpu: add a rust per-CPU variable test Mitchell Levy
  2 siblings, 0 replies; 4+ messages in thread
From: Mitchell Levy @ 2025-04-14 19:28 UTC (permalink / raw)
  To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Andrew Morton, Dennis Zhou, Tejun Heo,
	Christoph Lameter, Danilo Krummrich
  Cc: linux-kernel, rust-for-linux, linux-mm, Mitchell Levy

Add a CpuGuard type that disables preemption for its lifetime. Add a
PerCpuAllocation type used to track and, in the case of dynamic
allocations, create and free per-CPU allocations. Add a define_per_cpu!
macro to create static per-CPU allocations. Add a PerCpu type that
manages PerCpuAllocation's being used from multiple cores, and add a
PerCpuToken type used to assert exclusive access to a particular per-CPU
variable.

Co-developed-by: Boqun Feng <boqun.feng@gmail.com>
Signed-off-by: Boqun Feng <boqun.feng@gmail.com>
Signed-off-by: Mitchell Levy <levymitchell0@gmail.com>
---
 rust/helpers/helpers.c          |   2 +
 rust/helpers/percpu.c           |   9 ++
 rust/helpers/preempt.c          |  14 +++
 rust/kernel/lib.rs              |   3 +
 rust/kernel/percpu.rs           | 239 ++++++++++++++++++++++++++++++++++++++++
 rust/kernel/percpu/cpu_guard.rs |  29 +++++
 6 files changed, 296 insertions(+)

diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index e1c21eba9b15..cff605710f77 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -23,7 +23,9 @@
 #include "page.c"
 #include "platform.c"
 #include "pci.c"
+#include "percpu.c"
 #include "pid_namespace.c"
+#include "preempt.c"
 #include "rbtree.c"
 #include "rcu.c"
 #include "refcount.c"
diff --git a/rust/helpers/percpu.c b/rust/helpers/percpu.c
new file mode 100644
index 000000000000..a091389f730f
--- /dev/null
+++ b/rust/helpers/percpu.c
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/percpu.h>
+
+void __percpu *rust_helper_alloc_percpu(size_t sz, size_t align)
+{
+	return __alloc_percpu(sz, align);
+}
+
diff --git a/rust/helpers/preempt.c b/rust/helpers/preempt.c
new file mode 100644
index 000000000000..2c7529528ddd
--- /dev/null
+++ b/rust/helpers/preempt.c
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/preempt.h>
+
+void rust_helper_preempt_disable(void)
+{
+	preempt_disable();
+}
+
+void rust_helper_preempt_enable(void)
+{
+	preempt_enable();
+}
+
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index de07aadd1ff5..dc88becdb338 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -67,6 +67,9 @@
 pub mod page;
 #[cfg(CONFIG_PCI)]
 pub mod pci;
+// Only x86_64 is supported by percpu for now
+#[cfg(CONFIG_X86_64)]
+pub mod percpu;
 pub mod pid_namespace;
 pub mod platform;
 pub mod prelude;
diff --git a/rust/kernel/percpu.rs b/rust/kernel/percpu.rs
new file mode 100644
index 000000000000..ecce8cdaa22a
--- /dev/null
+++ b/rust/kernel/percpu.rs
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: GPL-2.0
+//! This module contains abstractions for creating and using per-CPU variables from Rust.
+//! See the define_per_cpu! macro and the PerCpu<T> type.
+pub mod cpu_guard;
+
+use bindings::{alloc_percpu, free_percpu};
+
+use crate::percpu::cpu_guard::CpuGuard;
+use crate::prelude::*;
+use crate::sync::Arc;
+
+use core::arch::asm;
+use core::marker::PhantomData;
+
+use ffi::c_void;
+
+/// Represents an allocation of a per-CPU variable via alloc_percpu or a static per-CPU
+/// allocation. Calls free_percpu when dropped if not a static allocation.
+pub struct PerCpuAllocation<T> {
+    /// Offset into the per-CPU area: either the address of a statically-allocated per-CPU variable
+    /// or a "pointer" returned by alloc_percpu or similar.
+    offset: usize,
+    /// Whether `self` is statically allocated
+    is_static: bool,
+    /// PhantomData so the compiler doesn't complain about `T` being unused.
+    deref_type: PhantomData<T>,
+}
+
+/// Holds a per-CPU variable.
+pub struct PerCpu<T> {
+    alloc: Arc<PerCpuAllocation<T>>,
+}
+
+/// Represents exclusive access to the memory location pointed at by a particular PerCpu<T>.
+pub struct PerCpuToken<'a, T> {
+    _guard: CpuGuard,
+    pcpu: &'a mut PerCpu<T>,
+}
+
+/// A wrapper used for declaring static per-CPU variables. These symbols are "virtual" in that the
+/// linker uses them to generate offsets into each cpu's per-cpu area, but shouldn't be read
+/// from/written to directly. The fact that the statics are immutable prevents them being written
+/// to (generally), this struct having _val be non-public prevents reading from them.
+///
+/// The end-user of the per-CPU API should make use of the define_per_cpu! macro instead of
+/// declaring variables of this type directly.
+#[repr(transparent)]
+pub struct StaticPerCpuSymbol<T> {
+    _val: T, // generate a correctly sized type
+}
+
+impl<T> PerCpuAllocation<T> {
+    /// Dynamically allocates a space in the per-CPU area suitably sized and aligned to hold a `T`.
+    ///
+    /// Returns `None` under the same circumstances the C function `alloc_percpu` returns `NULL`.
+    pub fn new() -> Option<PerCpuAllocation<T>> {
+        // SAFETY: No preconditions to call alloc_percpu
+        let ptr: *mut c_void = unsafe { alloc_percpu(size_of::<T>(), align_of::<T>()) };
+        if ptr.is_null() {
+            return None;
+        }
+
+        Some(Self {
+            offset: ptr as usize,
+            is_static: false,
+            deref_type: PhantomData,
+        })
+    }
+
+    /// Convert a statically allocated per-CPU variable (via its offset, i.e., the address of the
+    /// declared symbol) into a `PerCpuAllocation`.
+    ///
+    /// # Safety
+    /// `offset` must be a valid offset into the per-CPU area that's suitably sized and aligned to
+    /// hold a `T`.
+    pub unsafe fn new_static(offset: usize) -> PerCpuAllocation<T> {
+        Self {
+            offset,
+            is_static: true,
+            deref_type: PhantomData,
+        }
+    }
+}
+
+impl<T> Drop for PerCpuAllocation<T> {
+    fn drop(&mut self) {
+        if !self.is_static {
+            // SAFETY: self.offset was returned by alloc_percpu, and so was a valid pointer into
+            // the percpu area, and has remained valid by the invariants of PerCpuAllocation<T>.
+            unsafe { free_percpu(self.offset as *mut c_void) }
+        }
+    }
+}
+
+impl<T> PerCpu<T> {
+    /// Allocates a new per-CPU variable
+    pub fn new() -> Option<Self> {
+        let alloc: PerCpuAllocation<T> = PerCpuAllocation::new()?;
+
+        let arc = Arc::new(alloc, GFP_KERNEL).ok()?;
+
+        Some(Self { alloc: arc })
+    }
+
+    /// Wraps a `PerCpuAllocation<T>` in a `PerCpu<T>`
+    pub fn new_from_allocation(alloc: PerCpuAllocation<T>) -> Option<Self> {
+        let arc = Arc::new(alloc, GFP_KERNEL).ok()?;
+        Some(Self { alloc: arc })
+    }
+
+    /// Creates a new PerCpu<T> pointing to the same underlying variable
+    pub fn clone(&self) -> Self {
+        Self {
+            alloc: self.alloc.clone(),
+        }
+    }
+
+    /// Get a `&mut T` to the per-CPU variable represented by `&mut self`
+    ///
+    /// # Safety
+    /// The returned `&mut T` must follow Rust's aliasing rules. That is, no other `&(mut) T` may
+    /// exist that points to the same location in memory. In practice, this means that any PerCpu
+    /// holding a reference to the same PerCpuAllocation as `self` mut not call `get_ref` for as
+    /// long as the returned reference lives.
+    unsafe fn get_ref(&mut self) -> &mut T {
+        // SAFETY: addr_of!(this_cpu_off) is guaranteed to be a valid per-CPU offset by the per-CPU
+        // subsystem
+        let this_cpu_off_pcpu: PerCpuAllocation<u64> =
+            unsafe { PerCpuAllocation::new_static(core::ptr::addr_of!(this_cpu_off) as usize) };
+        let mut this_cpu_area: *mut c_void;
+        // SAFETY: gs + this_cpu_off_pcpu.offset is guaranteed to be a valid pointer because `gs`
+        // points to the per-CPU area and this_cpu_off_pcpu is a valid per-CPU allocation.
+        unsafe {
+            asm!(
+                // For some reason, the asm! parser doesn't like
+                //     mov {out}, [gs:{off_val}]
+                // so we use the less intuitive prefix version instead
+                "gs mov {out}, [{off_val}]",
+                off_val = in(reg) this_cpu_off_pcpu.offset,
+                out = out(reg) this_cpu_area,
+            )
+        };
+        // SAFETY: this_cpu_area + self.alloc.offset is guaranteed to be a valid pointer by the
+        // per-CPU subsystem and the invariant that self.offset is a valid offset into the per-CPU
+        // area.
+        //
+        // We have exclusive access to self via &mut self, so we know no-one else has a reference
+        // to the underlying pcpu variable because of the safety requirements of this function.
+        unsafe { &mut *((this_cpu_area.add(self.alloc.offset)) as *mut T) }
+    }
+
+    /// Produces a token, asserting that the holder has exclusive access to the underlying memory
+    /// pointed to by `self`
+    ///
+    /// # Safety
+    /// `func` (or its callees that execute on the same CPU) may not call `get_ref` on another
+    /// `PerCpu<T>` that represents the same per-CPU variable as `&mut self` (that is, they must
+    /// not be `clone()`s of each other or, in the case of statically allocated variables,
+    /// additionally can't both have come from the same `define_per_cpu!`) for the lifetime of the
+    /// returned token.
+    ///
+    /// In particular, this requires that the underlying per-CPU variable cannot ever be mutated
+    /// from an interrupt context, unless irqs are disabled for the lifetime of the returned
+    /// `PerCpuToken`.
+    pub unsafe fn get(&mut self, guard: CpuGuard) -> PerCpuToken<'_, T> {
+        PerCpuToken {
+            _guard: guard,
+            pcpu: self,
+        }
+    }
+}
+
+impl<T> PerCpuToken<'_, T> {
+    /// Immediately invokes `func` with a `&mut T` that points at the underlying per-CPU variable
+    /// that `&mut self` represents.
+    pub fn with<U>(&mut self, func: U)
+    where
+        U: FnOnce(&mut T),
+    {
+        // SAFETY: The existence of a PerCpuToken means that the requirements for get_ref are
+        // satisfied.
+        func(unsafe { self.pcpu.get_ref() });
+    }
+}
+
+/// define_per_cpu! is analogous to the C DEFINE_PER_CPU macro in that it lets you create a
+/// statically allocated per-CPU variable.
+///
+/// # Example
+/// ```
+/// use kernel::define_per_cpu;
+/// use kernel::percpu::StaticPerCpuSymbol;
+///
+/// define_per_cpu!(pub MY_PERCPU: u64 = 0);
+/// ```
+#[macro_export]
+macro_rules! define_per_cpu {
+    ($vis:vis $id:ident: $ty:ty = $expr:expr) => {
+        $crate::macros::paste! {
+            // Expand $expr outside of the unsafe block to avoid silently allowing unsafe code to be
+            // used without a user-facing unsafe block
+            static [<__INIT_ $id>]: $ty = $expr;
+
+            // SAFETY: StaticPerCpuSymbol<T> is #[repr(transparent)], so we can freely convert from T
+            #[link_section = ".data..percpu"]
+            $vis static $id: StaticPerCpuSymbol<$ty> = unsafe {
+                core::mem::transmute::<$ty, StaticPerCpuSymbol<$ty>>([<__INIT_ $id>])
+            };
+        }
+    };
+}
+
+/// Gets a `PerCpu<T>` from a symbol declared with `define_per_cpu!` or `declare_extern_per_cpu!`.
+///
+/// # Safety
+/// `$id` must be declared with either `define_per_cpu!` or `declare_extern_per_cpu!`, and the
+/// returned value must be stored in a `PerCpu<T>` where `T` matches the declared type of `$id`.
+#[macro_export]
+macro_rules! unsafe_get_per_cpu {
+    ($id:ident, $guard:expr) => {{
+        let off = core::ptr::addr_of!($id);
+        $crate::percpu::PerCpu::new_from_allocation($crate::percpu::PerCpuAllocation::new_static(
+            off as usize,
+        ))
+    }};
+}
+
+/// Declares a StaticPerCpuSymbol corresponding to a per-CPU variable defined in C. Be sure to read
+/// the safety requirements of `PerCpu::get`.
+#[macro_export]
+macro_rules! declare_extern_per_cpu {
+    ($id:ident: $ty:ty) => {
+        extern "C" {
+            static $id: StaticPerCpuSymbol<$ty>;
+        }
+    };
+}
+
+declare_extern_per_cpu!(this_cpu_off: u64);
diff --git a/rust/kernel/percpu/cpu_guard.rs b/rust/kernel/percpu/cpu_guard.rs
new file mode 100644
index 000000000000..6f7d320a5c9a
--- /dev/null
+++ b/rust/kernel/percpu/cpu_guard.rs
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0
+//! Contains abstractions for disabling CPU preemption. See CpuGuard.
+
+/// A RAII guard for bindings::preempt_disable and bindings::preempt_enable. Guarantees preemption
+/// is disabled for as long as this object exists.
+pub struct CpuGuard {
+    // Don't make one without using new()
+    _phantom: (),
+}
+
+impl CpuGuard {
+    /// Create a new CpuGuard. Disables preemption for its lifetime.
+    pub fn new() -> Self {
+        // SAFETY: There are no preconditions required to call preempt_disable
+        unsafe {
+            bindings::preempt_disable();
+        }
+        CpuGuard { _phantom: () }
+    }
+}
+
+impl Drop for CpuGuard {
+    fn drop(&mut self) {
+        // SAFETY: There are no preconditions required to call preempt_enable
+        unsafe {
+            bindings::preempt_enable();
+        }
+    }
+}

-- 
2.34.1



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH RFC v2 2/3] rust: rust-analyzer: add lib to dirs searched for crates
  2025-04-14 19:28 [PATCH RFC v2 0/3] rust: Add Per-CPU Variable API Mitchell Levy
  2025-04-14 19:28 ` [PATCH RFC v2 1/3] rust: percpu: introduce a rust API for per-CPU variables Mitchell Levy
@ 2025-04-14 19:28 ` Mitchell Levy
  2025-04-14 19:28 ` [PATCH RFC v2 3/3] rust: percpu: add a rust per-CPU variable test Mitchell Levy
  2 siblings, 0 replies; 4+ messages in thread
From: Mitchell Levy @ 2025-04-14 19:28 UTC (permalink / raw)
  To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Andrew Morton, Dennis Zhou, Tejun Heo,
	Christoph Lameter, Danilo Krummrich
  Cc: linux-kernel, rust-for-linux, linux-mm, Mitchell Levy

When generating rust-project.json, also include crates in lib/

Signed-off-by: Mitchell Levy <levymitchell0@gmail.com>
---
 scripts/generate_rust_analyzer.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py
index cd41bc906fbd..64c5283a509f 100755
--- a/scripts/generate_rust_analyzer.py
+++ b/scripts/generate_rust_analyzer.py
@@ -144,7 +144,7 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs):
     # Then, the rest outside of `rust/`.
     #
     # We explicitly mention the top-level folders we want to cover.
-    extra_dirs = map(lambda dir: srctree / dir, ("samples", "drivers"))
+    extra_dirs = map(lambda dir: srctree / dir, ("samples", "drivers", "lib"))
     if external_src is not None:
         extra_dirs = [external_src]
     for folder in extra_dirs:

-- 
2.34.1



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH RFC v2 3/3] rust: percpu: add a rust per-CPU variable test
  2025-04-14 19:28 [PATCH RFC v2 0/3] rust: Add Per-CPU Variable API Mitchell Levy
  2025-04-14 19:28 ` [PATCH RFC v2 1/3] rust: percpu: introduce a rust API for per-CPU variables Mitchell Levy
  2025-04-14 19:28 ` [PATCH RFC v2 2/3] rust: rust-analyzer: add lib to dirs searched for crates Mitchell Levy
@ 2025-04-14 19:28 ` Mitchell Levy
  2 siblings, 0 replies; 4+ messages in thread
From: Mitchell Levy @ 2025-04-14 19:28 UTC (permalink / raw)
  To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Andrew Morton, Dennis Zhou, Tejun Heo,
	Christoph Lameter, Danilo Krummrich
  Cc: linux-kernel, rust-for-linux, linux-mm, Mitchell Levy

Add a short exercise for Rust's per-CPU variable API, modelled after
lib/percpu_test.c

Signed-off-by: Mitchell Levy <levymitchell0@gmail.com>
---
 lib/Kconfig.debug       |   9 ++++
 lib/Makefile            |   1 +
 lib/percpu_test_rust.rs | 119 ++++++++++++++++++++++++++++++++++++++++++++++++
 rust/helpers/percpu.c   |  11 +++++
 4 files changed, 140 insertions(+)

diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index df9587aa5c5e..e9fa12940cf2 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2425,6 +2425,15 @@ config PERCPU_TEST
 
 	  If unsure, say N.
 
+config PERCPU_TEST_RUST
+	tristate "Rust per cpu operations test"
+	depends on m && DEBUG_KERNEL && RUST
+	help
+	  Enable this option to build a test module which validates Rust per-cpu
+	  operations.
+
+	  If unsure, say N.
+
 config ATOMIC64_SELFTEST
 	tristate "Perform an atomic64_t self-test"
 	help
diff --git a/lib/Makefile b/lib/Makefile
index f07b24ce1b3f..adace4f3ae63 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -289,6 +289,7 @@ obj-$(CONFIG_RBTREE_TEST) += rbtree_test.o
 obj-$(CONFIG_INTERVAL_TREE_TEST) += interval_tree_test.o
 
 obj-$(CONFIG_PERCPU_TEST) += percpu_test.o
+obj-$(CONFIG_PERCPU_TEST_RUST) += percpu_test_rust.o
 
 obj-$(CONFIG_ASN1) += asn1_decoder.o
 obj-$(CONFIG_ASN1_ENCODER) += asn1_encoder.o
diff --git a/lib/percpu_test_rust.rs b/lib/percpu_test_rust.rs
new file mode 100644
index 000000000000..61d8793b1cb0
--- /dev/null
+++ b/lib/percpu_test_rust.rs
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0
+//! A simple self test for the rust per-CPU API.
+
+use core::ffi::c_void;
+
+use kernel::{
+    bindings::{on_each_cpu, smp_processor_id},
+    define_per_cpu,
+    percpu::{cpu_guard::*, *},
+    pr_info,
+    prelude::*,
+    unsafe_get_per_cpu,
+};
+
+module! {
+    type: PerCpuTestModule,
+    name: "percpu_test_rust",
+    author: "Mitchell Levy",
+    description: "Test code to exercise the Rust Per CPU variable API",
+    license: "GPL v2",
+}
+
+struct PerCpuTestModule;
+
+define_per_cpu!(PERCPU: i64 = 0);
+define_per_cpu!(UPERCPU: u64 = 0);
+
+impl kernel::Module for PerCpuTestModule {
+    fn init(_module: &'static ThisModule) -> Result<Self, Error> {
+        pr_info!("rust percpu test start\n");
+
+        let mut native: i64 = 0;
+        let mut pcpu: PerCpu<i64> =
+            unsafe { unsafe_get_per_cpu!(PERCPU, CpuGuard::new()).unwrap() };
+        // SAFETY: We only have one PerCpu that points at PERCPU
+        unsafe { pcpu.get(CpuGuard::new()) }.with(|val: &mut i64| {
+            pr_info!("The contents of pcpu are {}", val);
+
+            native += -1;
+            *val += -1;
+            pr_info!("Native: {}, *pcpu: {}\n", native, val);
+            assert!(native == *val && native == -1);
+
+            native += 1;
+            *val += 1;
+            pr_info!("Native: {}, *pcpu: {}\n", native, val);
+            assert!(native == *val && native == 0);
+        });
+
+        let mut unative: u64 = 0;
+        let mut upcpu: PerCpu<u64> =
+            unsafe { unsafe_get_per_cpu!(UPERCPU, CpuGuard::new()).unwrap() };
+
+        // SAFETY: We only have one PerCpu pointing at UPERCPU
+        unsafe { upcpu.get(CpuGuard::new()) }.with(|val: &mut u64| {
+            unative += 1;
+            *val += 1;
+            pr_info!("Unative: {}, *upcpu: {}\n", unative, val);
+            assert!(unative == *val && unative == 1);
+
+            unative = unative.wrapping_add((-1i64) as u64);
+            *val = val.wrapping_add((-1i64) as u64);
+            pr_info!("Unative: {}, *upcpu: {}\n", unative, val);
+            assert!(unative == *val && unative == 0);
+
+            unative = unative.wrapping_add((-1i64) as u64);
+            *val = val.wrapping_add((-1i64) as u64);
+            pr_info!("Unative: {}, *upcpu: {}\n", unative, val);
+            assert!(unative == *val && unative == (-1i64) as u64);
+
+            unative = 0;
+            *val = 0;
+
+            unative = unative.wrapping_sub(1);
+            *val = val.wrapping_sub(1);
+            pr_info!("Unative: {}, *upcpu: {}\n", unative, val);
+            assert!(unative == *val && unative == (-1i64) as u64);
+            assert!(unative == *val && unative == u64::MAX);
+        });
+
+        pr_info!("rust static percpu test done\n");
+
+        pr_info!("rust dynamic percpu test start\n");
+        let mut test: PerCpu<u64> = PerCpu::new().unwrap();
+
+        unsafe {
+            on_each_cpu(Some(inc_percpu), (&raw mut test) as *mut c_void, 0);
+            on_each_cpu(Some(inc_percpu), (&raw mut test) as *mut c_void, 0);
+            on_each_cpu(Some(inc_percpu), (&raw mut test) as *mut c_void, 0);
+            on_each_cpu(Some(inc_percpu), (&raw mut test) as *mut c_void, 1);
+            on_each_cpu(Some(check_percpu), (&raw mut test) as *mut c_void, 1);
+        }
+
+        pr_info!("rust dynamic percpu test done\n");
+
+        // Return Err to unload the module
+        Result::Err(EINVAL)
+    }
+}
+
+unsafe extern "C" fn inc_percpu(info: *mut c_void) {
+    // SAFETY: We know that info is a void *const PerCpu<u64> and PerCpu<u64> is Send.
+    let mut pcpu = unsafe { (&*(info as *const PerCpu<u64>)).clone() };
+    // SAFETY: smp_processor_id has no preconditions
+    pr_info!("Incrementing on {}\n", unsafe { smp_processor_id() });
+
+    // SAFETY: We don't have multiple clones of pcpu in scope
+    unsafe { pcpu.get(CpuGuard::new()) }.with(|val: &mut u64| *val += 1);
+}
+
+unsafe extern "C" fn check_percpu(info: *mut c_void) {
+    // SAFETY: We know that info is a void *const PerCpu<u64> and PerCpu<u64> is Send.
+    let mut pcpu = unsafe { (&*(info as *const PerCpu<u64>)).clone() };
+    // SAFETY: smp_processor_id has no preconditions
+    pr_info!("Asserting on {}\n", unsafe { smp_processor_id() });
+
+    // SAFETY: We don't have multiple clones of pcpu in scope
+    unsafe { pcpu.get(CpuGuard::new()) }.with(|val: &mut u64| assert!(*val == 4));
+}
diff --git a/rust/helpers/percpu.c b/rust/helpers/percpu.c
index a091389f730f..0e9b2fed3ebd 100644
--- a/rust/helpers/percpu.c
+++ b/rust/helpers/percpu.c
@@ -1,9 +1,20 @@
 // SPDX-License-Identifier: GPL-2.0
 
 #include <linux/percpu.h>
+#include <linux/smp.h>
 
 void __percpu *rust_helper_alloc_percpu(size_t sz, size_t align)
 {
 	return __alloc_percpu(sz, align);
 }
 
+void rust_helper_on_each_cpu(smp_call_func_t func, void *info, int wait)
+{
+	on_each_cpu(func, info, wait);
+}
+
+int rust_helper_smp_processor_id(void)
+{
+	return smp_processor_id();
+}
+

-- 
2.34.1



^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2025-04-14 19:29 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-04-14 19:28 [PATCH RFC v2 0/3] rust: Add Per-CPU Variable API Mitchell Levy
2025-04-14 19:28 ` [PATCH RFC v2 1/3] rust: percpu: introduce a rust API for per-CPU variables Mitchell Levy
2025-04-14 19:28 ` [PATCH RFC v2 2/3] rust: rust-analyzer: add lib to dirs searched for crates Mitchell Levy
2025-04-14 19:28 ` [PATCH RFC v2 3/3] rust: percpu: add a rust per-CPU variable test Mitchell Levy

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