The maple tree will be used in the Tyr driver to allocate and keep track
of GPU allocations created internally (i.e. not by userspace). It will
likely also be used in the Nova driver eventually.
This adds the simplest methods for additional and removal that do not
require any special care with respect to concurrency.
This implementation is based on the RFC by Andrew but with significant
changes to simplify the implementation.
Co-developed-by: Andrew Ballance <andrewjballance@gmail.com>
Signed-off-by: Andrew Ballance <andrewjballance@gmail.com>
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
---
MAINTAINERS | 2 +
rust/helpers/helpers.c | 1 +
rust/helpers/maple_tree.c | 14 +++
rust/kernel/lib.rs | 1 +
rust/kernel/maple_tree.rs | 286 ++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 304 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index dd810da5261b5d664ef9750f66ec022412e8014b..b7e7308ce07c050239c14c4f3a0fd89bdd8e8796 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15956,7 +15956,9 @@ L: rust-for-linux@vger.kernel.org
S: Maintained
W: http://www.linux-mm.org
T: git git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
+F: rust/helpers/maple_tree.c
F: rust/helpers/mm.c
+F: rust/kernel/maple_tree.rs
F: rust/kernel/mm.rs
F: rust/kernel/mm/
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 0683fffdbde25b89d285e3b0d8e6d8f7f5fd7474..ed7888a2661ad91f0fb78023311b3a266d30615c 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -26,6 +26,7 @@
#include "io.c"
#include "jump_label.c"
#include "kunit.c"
+#include "maple_tree.c"
#include "mm.c"
#include "mutex.c"
#include "page.c"
diff --git a/rust/helpers/maple_tree.c b/rust/helpers/maple_tree.c
new file mode 100644
index 0000000000000000000000000000000000000000..119665846e8e8b018f8dc791a22fe20ace8e9c2c
--- /dev/null
+++ b/rust/helpers/maple_tree.c
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/maple_tree.h>
+
+void rust_helper_mt_init_flags(struct maple_tree *mt, unsigned int flags)
+{
+ mt_init_flags(mt, flags);
+}
+
+struct ma_state rust_helper_MA_STATE(struct maple_tree *mt, unsigned long start, unsigned long end)
+{
+ MA_STATE(mas, mt, start, end);
+ return mas;
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 11a6461e98daab597e1eb2b513c5123686a1bb73..6cc152090a2f1986781800897ad48947c2d02e40 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -88,6 +88,7 @@
#[cfg(CONFIG_KUNIT)]
pub mod kunit;
pub mod list;
+pub mod maple_tree;
pub mod miscdevice;
pub mod mm;
#[cfg(CONFIG_NET)]
diff --git a/rust/kernel/maple_tree.rs b/rust/kernel/maple_tree.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0f26c173eedc7c79bb8e2b56fe85e8a266b3ae0c
--- /dev/null
+++ b/rust/kernel/maple_tree.rs
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Maple trees.
+//!
+//! C header: [`include/linux/maple_tree.h`](srctree/include/linux/maple_tree.h)
+//!
+//! Reference: <https://docs.kernel.org/core-api/maple_tree.html>
+
+use core::{
+ marker::PhantomData,
+ ops::{Bound, RangeBounds},
+};
+
+use kernel::{
+ alloc::Flags,
+ error::code::{EEXIST, ENOMEM},
+ error::to_result,
+ prelude::*,
+ types::{ForeignOwnable, Opaque},
+};
+
+/// A maple tree optimized for storing non-overlapping ranges.
+///
+/// # Invariants
+///
+/// Each range in the maple tree owns an instance of `T`.
+#[pin_data(PinnedDrop)]
+#[repr(transparent)]
+pub struct MapleTree<T: ForeignOwnable> {
+ #[pin]
+ tree: Opaque<bindings::maple_tree>,
+ _p: PhantomData<T>,
+}
+
+#[inline]
+fn to_maple_range(range: impl RangeBounds<usize>) -> Option<(usize, usize)> {
+ let first = match range.start_bound() {
+ Bound::Included(start) => *start,
+ Bound::Excluded(start) => start.checked_add(1)?,
+ Bound::Unbounded => 0,
+ };
+
+ let last = match range.end_bound() {
+ Bound::Included(end) => *end,
+ Bound::Excluded(end) => end.checked_sub(1)?,
+ Bound::Unbounded => usize::MAX,
+ };
+
+ if last < first {
+ return None;
+ }
+
+ Some((first, last))
+}
+
+impl<T: ForeignOwnable> MapleTree<T> {
+ /// Create a new maple tree.
+ ///
+ /// The tree will use the regular implementation with a higher branching factor.
+ #[inline]
+ pub fn new() -> impl PinInit<Self> {
+ pin_init!(MapleTree {
+ // SAFETY: This initializes a maple tree into a pinned slot. The maple tree will be
+ // destroyed in Drop before the memory location becomes invalid.
+ tree <- Opaque::ffi_init(|slot| unsafe { bindings::mt_init_flags(slot, 0) }),
+ _p: PhantomData,
+ })
+ }
+
+ /// Insert the value at the given index.
+ ///
+ /// If the maple tree already contains a range using the given index, then this call will fail.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::maple_tree::{MapleTree, InsertErrorKind};
+ ///
+ /// let tree = KBox::pin_init(MapleTree::<KBox<i32>>::new(), GFP_KERNEL)?;
+ ///
+ /// let ten = KBox::new(10, GFP_KERNEL)?;
+ /// let twenty = KBox::new(20, GFP_KERNEL)?;
+ /// let the_answer = KBox::new(42, GFP_KERNEL)?;
+ ///
+ /// // These calls will succeed.
+ /// tree.insert(100, ten, GFP_KERNEL)?;
+ /// tree.insert(101, twenty, GFP_KERNEL)?;
+ ///
+ /// // This will fail because the index is already in use.
+ /// assert_eq!(
+ /// tree.insert(100, the_answer, GFP_KERNEL).unwrap_err().cause,
+ /// InsertErrorKind::Occupied,
+ /// );
+ /// # Ok::<_, Error>(())
+ /// ```
+ #[inline]
+ pub fn insert(&self, index: usize, value: T, gfp: Flags) -> Result<(), InsertError<T>> {
+ self.insert_range(index..=index, value, gfp)
+ }
+
+ /// Insert a value to the specified range, failing on overlap.
+ ///
+ /// This accepts the usual types of Rust ranges using the `..` and `..=` syntax for exclusive
+ /// and inclusive ranges respectively. The range must not be empty, and must not overlap with
+ /// any existing range.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::maple_tree::{MapleTree, InsertErrorKind};
+ ///
+ /// let tree = KBox::pin_init(MapleTree::<KBox<i32>>::new(), GFP_KERNEL)?;
+ ///
+ /// let ten = KBox::new(10, GFP_KERNEL)?;
+ /// let twenty = KBox::new(20, GFP_KERNEL)?;
+ /// let the_answer = KBox::new(42, GFP_KERNEL)?;
+ /// let hundred = KBox::new(100, GFP_KERNEL)?;
+ ///
+ /// // Insert the value 10 at the indices 100 to 499.
+ /// tree.insert_range(100..500, ten, GFP_KERNEL)?;
+ ///
+ /// // Insert the value 20 at the indices 500 to 1000.
+ /// tree.insert_range(500..=1000, twenty, GFP_KERNEL)?;
+ ///
+ /// // This will fail due to overlap with the previous range on index 1000.
+ /// assert_eq!(
+ /// tree.insert_range(1000..1200, the_answer, GFP_KERNEL).unwrap_err().cause,
+ /// InsertErrorKind::Occupied,
+ /// );
+ ///
+ /// // When using .. to specify the range, you must be careful to ensure that the range is
+ /// // non-empty.
+ /// assert_eq!(
+ /// tree.insert_range(72..72, hundred, GFP_KERNEL).unwrap_err().cause,
+ /// InsertErrorKind::InvalidRequest,
+ /// );
+ /// # Ok::<_, Error>(())
+ /// ```
+ pub fn insert_range<R>(&self, range: R, value: T, gfp: Flags) -> Result<(), InsertError<T>>
+ where
+ R: RangeBounds<usize>,
+ {
+ let Some((first, last)) = to_maple_range(range) else {
+ return Err(InsertError {
+ value,
+ cause: InsertErrorKind::InvalidRequest,
+ });
+ };
+
+ let ptr = T::into_foreign(value);
+
+ // SAFETY: The tree is valid, and we are passing a pointer to an owned instance of `T`.
+ let res = to_result(unsafe {
+ bindings::mtree_insert_range(self.tree.get(), first, last, ptr, gfp.as_raw())
+ });
+
+ if let Err(err) = res {
+ // SAFETY: As `mtree_insert_range` failed, it is safe to take back ownership.
+ let value = unsafe { T::from_foreign(ptr) };
+
+ let cause = if err == ENOMEM {
+ InsertErrorKind::Nomem
+ } else if err == EEXIST {
+ InsertErrorKind::Occupied
+ } else {
+ InsertErrorKind::InvalidRequest
+ };
+ Err(InsertError { value, cause })
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Erase the range containing the given index.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::maple_tree::{MapleTree, InsertErrorKind};
+ ///
+ /// let tree = KBox::pin_init(MapleTree::<KBox<i32>>::new(), GFP_KERNEL)?;
+ ///
+ /// let ten = KBox::new(10, GFP_KERNEL)?;
+ /// let twenty = KBox::new(20, GFP_KERNEL)?;
+ ///
+ /// tree.insert_range(100..500, ten, GFP_KERNEL)?;
+ /// tree.insert(67, twenty, GFP_KERNEL)?;
+ ///
+ /// let twenty = tree.erase(67).unwrap();
+ /// assert_eq!(*twenty, 20);
+ ///
+ /// let ten = tree.erase(275).unwrap();
+ /// assert_eq!(*ten, 10);
+ ///
+ /// // The previous call erased the entire range, not just index 275.
+ /// assert!(tree.erase(127).is_none());
+ /// # Ok::<_, Error>(())
+ /// ```
+ #[inline]
+ pub fn erase(&self, index: usize) -> Option<T> {
+ // SAFETY: `self.tree` contains a valid maple tree.
+ let ret = unsafe { bindings::mtree_erase(self.tree.get(), index) };
+
+ // SAFETY: If the pointer is not null, then we took ownership of a valid instance of `T`
+ // from the tree.
+ unsafe { T::try_from_foreign(ret) }
+ }
+
+ /// Free all `T` instances in this tree.
+ ///
+ /// # Safety
+ ///
+ /// This frees Rust data referenced by the maple tree without removing it from the maple tree.
+ /// The caller must ensure that no reference that remains in the maple tree is used incorrectly
+ /// after this call.
+ unsafe fn free_all_entries(self: Pin<&mut Self>) {
+ // SAFETY: The pointer references a valid maple tree.
+ let ma_state = unsafe { Opaque::new(bindings::MA_STATE(self.tree.get(), 0, usize::MAX)) };
+
+ loop {
+ // SAFETY: The maple tree is valid. This call to `free_all_entries` has exclusive
+ // access to the maple tree, so no further synchronization is required.
+ let ptr = unsafe { bindings::mas_find(ma_state.get(), usize::MAX) };
+ if ptr.is_null() {
+ break;
+ }
+ // SAFETY: By the type invariants, this pointer references a valid value of type `T`.
+ // By the safety requirements, it is okay to free it without removing it from the maple
+ // tree.
+ unsafe { drop(T::from_foreign(ptr)) };
+ }
+ }
+}
+
+#[pinned_drop]
+impl<T: ForeignOwnable> PinnedDrop for MapleTree<T> {
+ #[inline]
+ fn drop(mut self: Pin<&mut Self>) {
+ // We only iterate the tree if the Rust value have a destructor.
+ if core::mem::needs_drop::<T>() {
+ // SAFETY: The tree is valid, and other than the below `mtree_destroy` call, it will
+ // not be accessed after this call.
+ unsafe { self.as_mut().free_all_entries() };
+ }
+
+ // SAFETY: The tree is valid, and will not be accessed after this call.
+ unsafe { bindings::mtree_destroy(self.tree.get()) };
+ }
+}
+
+/// Error type for failure to insert a new value.
+pub struct InsertError<T> {
+ /// The value that could not be inserted.
+ pub value: T,
+ /// The reason for the failure to insert.
+ pub cause: InsertErrorKind,
+}
+
+/// The reason for the failure to insert.
+#[derive(PartialEq, Eq, Copy, Clone)]
+pub enum InsertErrorKind {
+ /// There is already a value in the requested range.
+ Occupied,
+ /// Failure to allocate memory.
+ Nomem,
+ /// The insertion request was invalid.
+ InvalidRequest,
+}
+
+impl From<InsertErrorKind> for Error {
+ #[inline]
+ fn from(kind: InsertErrorKind) -> Error {
+ match kind {
+ InsertErrorKind::Occupied => EEXIST,
+ InsertErrorKind::Nomem => ENOMEM,
+ InsertErrorKind::InvalidRequest => EINVAL,
+ }
+ }
+}
+
+impl<T> From<InsertError<T>> for Error {
+ #[inline]
+ fn from(insert_err: InsertError<T>) -> Error {
+ Error::from(insert_err.cause)
+ }
+}
--
2.50.1.470.g6ba607880d-goog
* Alice Ryhl <aliceryhl@google.com> [250726 09:23]: > The maple tree will be used in the Tyr driver to allocate and keep track > of GPU allocations created internally (i.e. not by userspace). It will > likely also be used in the Nova driver eventually. > > This adds the simplest methods for additional and removal that do not > require any special care with respect to concurrency. > > This implementation is based on the RFC by Andrew but with significant > changes to simplify the implementation. > > Co-developed-by: Andrew Ballance <andrewjballance@gmail.com> > Signed-off-by: Andrew Ballance <andrewjballance@gmail.com> > Signed-off-by: Alice Ryhl <aliceryhl@google.com> > --- > MAINTAINERS | 2 + > rust/helpers/helpers.c | 1 + > rust/helpers/maple_tree.c | 14 +++ > rust/kernel/lib.rs | 1 + > rust/kernel/maple_tree.rs | 286 ++++++++++++++++++++++++++++++++++++++++++++++ > 5 files changed, 304 insertions(+) > > diff --git a/MAINTAINERS b/MAINTAINERS > index dd810da5261b5d664ef9750f66ec022412e8014b..b7e7308ce07c050239c14c4f3a0fd89bdd8e8796 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -15956,7 +15956,9 @@ L: rust-for-linux@vger.kernel.org > S: Maintained > W: http://www.linux-mm.org > T: git git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm > +F: rust/helpers/maple_tree.c > F: rust/helpers/mm.c > +F: rust/kernel/maple_tree.rs > F: rust/kernel/mm.rs > F: rust/kernel/mm/ We should have another section for the maple tree, since it's not just used for mm. Your stated plan is to use it for GPU allocations and the code doesn't live in mm/, wdyt? ... Thanks, Liam
On Thu, Aug 07, 2025 at 12:12:47PM -0400, Liam R. Howlett wrote: > * Alice Ryhl <aliceryhl@google.com> [250726 09:23]: > > The maple tree will be used in the Tyr driver to allocate and keep track > > of GPU allocations created internally (i.e. not by userspace). It will > > likely also be used in the Nova driver eventually. > > > > This adds the simplest methods for additional and removal that do not > > require any special care with respect to concurrency. > > > > This implementation is based on the RFC by Andrew but with significant > > changes to simplify the implementation. > > > > Co-developed-by: Andrew Ballance <andrewjballance@gmail.com> > > Signed-off-by: Andrew Ballance <andrewjballance@gmail.com> > > Signed-off-by: Alice Ryhl <aliceryhl@google.com> > > --- > > MAINTAINERS | 2 + > > rust/helpers/helpers.c | 1 + > > rust/helpers/maple_tree.c | 14 +++ > > rust/kernel/lib.rs | 1 + > > rust/kernel/maple_tree.rs | 286 ++++++++++++++++++++++++++++++++++++++++++++++ > > 5 files changed, 304 insertions(+) > > > > diff --git a/MAINTAINERS b/MAINTAINERS > > index dd810da5261b5d664ef9750f66ec022412e8014b..b7e7308ce07c050239c14c4f3a0fd89bdd8e8796 100644 > > --- a/MAINTAINERS > > +++ b/MAINTAINERS > > @@ -15956,7 +15956,9 @@ L: rust-for-linux@vger.kernel.org > > S: Maintained > > W: http://www.linux-mm.org > > T: git git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm > > +F: rust/helpers/maple_tree.c > > F: rust/helpers/mm.c > > +F: rust/kernel/maple_tree.rs > > F: rust/kernel/mm.rs > > F: rust/kernel/mm/ > > We should have another section for the maple tree, since it's not just > used for mm. Your stated plan is to use it for GPU allocations and the > code doesn't live in mm/, wdyt? Sure, I can add a new section if you prefer that. Alice
On Sat, Jul 26, 2025 at 01:23:22PM +0000, Alice Ryhl wrote: > The maple tree will be used in the Tyr driver to allocate and keep track > of GPU allocations created internally (i.e. not by userspace). It will > likely also be used in the Nova driver eventually. > > This adds the simplest methods for additional and removal that do not > require any special care with respect to concurrency. > > This implementation is based on the RFC by Andrew but with significant > changes to simplify the implementation. > > Co-developed-by: Andrew Ballance <andrewjballance@gmail.com> > Signed-off-by: Andrew Ballance <andrewjballance@gmail.com> > Signed-off-by: Alice Ryhl <aliceryhl@google.com> > --- [...] > + /// Free all `T` instances in this tree. > + /// > + /// # Safety > + /// > + /// This frees Rust data referenced by the maple tree without removing it from the maple tree. > + /// The caller must ensure that no reference that remains in the maple tree is used incorrectly > + /// after this call. > + unsafe fn free_all_entries(self: Pin<&mut Self>) { > + // SAFETY: The pointer references a valid maple tree. > + let ma_state = unsafe { Opaque::new(bindings::MA_STATE(self.tree.get(), 0, usize::MAX)) }; > + A meta comment here for the future direction: I think it really makes a lot of sense if we could have the Rust abstraction for struct ma_state, that'll allow us to have flexible locking strategy and Iterator-like interface. Maybe it's something Andrew can take a deeper look when MapleTree binding is in-tree (no word play intented ;-))? For example, with a ma_state binding, we can do: let mas = MAState::new(self, 0..); while let Some(v) = mas.next() { drop(v) } Regards, Boqun > + loop { > + // SAFETY: The maple tree is valid. This call to `free_all_entries` has exclusive > + // access to the maple tree, so no further synchronization is required. > + let ptr = unsafe { bindings::mas_find(ma_state.get(), usize::MAX) }; > + if ptr.is_null() { > + break; > + } > + // SAFETY: By the type invariants, this pointer references a valid value of type `T`. > + // By the safety requirements, it is okay to free it without removing it from the maple > + // tree. > + unsafe { drop(T::from_foreign(ptr)) }; > + } > + } > +} > + [...]
On Mon Jul 28, 2025 at 6:04 PM CEST, Boqun Feng wrote: > On Sat, Jul 26, 2025 at 01:23:22PM +0000, Alice Ryhl wrote: >> The maple tree will be used in the Tyr driver to allocate and keep track >> of GPU allocations created internally (i.e. not by userspace). It will >> likely also be used in the Nova driver eventually. >> >> This adds the simplest methods for additional and removal that do not >> require any special care with respect to concurrency. >> >> This implementation is based on the RFC by Andrew but with significant >> changes to simplify the implementation. >> >> Co-developed-by: Andrew Ballance <andrewjballance@gmail.com> >> Signed-off-by: Andrew Ballance <andrewjballance@gmail.com> >> Signed-off-by: Alice Ryhl <aliceryhl@google.com> >> --- > [...] >> + /// Free all `T` instances in this tree. >> + /// >> + /// # Safety >> + /// >> + /// This frees Rust data referenced by the maple tree without removing it from the maple tree. >> + /// The caller must ensure that no reference that remains in the maple tree is used incorrectly >> + /// after this call. >> + unsafe fn free_all_entries(self: Pin<&mut Self>) { >> + // SAFETY: The pointer references a valid maple tree. >> + let ma_state = unsafe { Opaque::new(bindings::MA_STATE(self.tree.get(), 0, usize::MAX)) }; >> + > > A meta comment here for the future direction: I think it really makes a > lot of sense if we could have the Rust abstraction for struct ma_state, > that'll allow us to have flexible locking strategy and Iterator-like > interface. Maybe it's something Andrew can take a deeper look when > MapleTree binding is in-tree (no word play intented ;-))? > > For example, with a ma_state binding, we can do: > > let mas = MAState::new(self, 0..); > > while let Some(v) = mas.next() { > drop(v) > } FYI: Left a similar comment on MapleLock [1]. :) I'd rather have that sooner than later, free_all_entries() is a good internal user. [1] https://lore.kernel.org/all/DBNO0N1TDAGI.2OEWH6Y60JNYZ@kernel.org/
On Sat, Jul 26, 2025 at 01:23:22PM +0000, Alice Ryhl wrote: > +struct ma_state rust_helper_MA_STATE(struct maple_tree *mt, unsigned long start, unsigned long end) > +{ > + MA_STATE(mas, mt, start, end); > + return mas; > +} This seems very inefficient. Returning a struct larger than two words (on x86 anyway) means that the compiler implements this as: void rust_helper_MA_STATE(struct ma_state *masp, ...) { MA_STATE(mas, mt, start, end); *masp = mas; } so that's about 72 bytes being memcpy'd per access to the maple tree. Sure, it's stack, so it's cache hot, but surely we can implement the equivalent of MA_STATE in Rust and see a significant performance win, at least on read operations.
On Sat, Jul 26, 2025 at 6:23 PM Matthew Wilcox <willy@infradead.org> wrote: > > On Sat, Jul 26, 2025 at 01:23:22PM +0000, Alice Ryhl wrote: > > +struct ma_state rust_helper_MA_STATE(struct maple_tree *mt, unsigned long start, unsigned long end) > > +{ > > + MA_STATE(mas, mt, start, end); > > + return mas; > > +} > > This seems very inefficient. Returning a struct larger than two words > (on x86 anyway) means that the compiler implements this as: > > void rust_helper_MA_STATE(struct ma_state *masp, ...) > { > MA_STATE(mas, mt, start, end); > *masp = mas; > } > > so that's about 72 bytes being memcpy'd per access to the maple tree. > Sure, it's stack, so it's cache hot, but surely we can implement > the equivalent of MA_STATE in Rust and see a significant performance > win, at least on read operations. Some of the methods go through the mtree_* functions that create the MA_STATE on the C side, so this isn't always the case. Regardless, we definitely can avoid this helper. It has the consequence that if MA_STATE is changed in the future, then the Rust version of the method must also be changed. I can add a comment to that effect to the header file to remind people of that. Alice
On Sat, 26 Jul 2025 13:23:22 +0000 Alice Ryhl <aliceryhl@google.com> wrote: > The maple tree will be used in the Tyr driver to allocate and keep track > of GPU allocations created internally (i.e. not by userspace). It will > likely also be used in the Nova driver eventually. > > This adds the simplest methods for additional and removal that do not > require any special care with respect to concurrency. > > This implementation is based on the RFC by Andrew but with significant > changes to simplify the implementation. > > Co-developed-by: Andrew Ballance <andrewjballance@gmail.com> > Signed-off-by: Andrew Ballance <andrewjballance@gmail.com> > Signed-off-by: Alice Ryhl <aliceryhl@google.com> Overall looks good to me, some nits and thoughts about the error type below. Best, GAry > --- > MAINTAINERS | 2 + > rust/helpers/helpers.c | 1 + > rust/helpers/maple_tree.c | 14 +++ > rust/kernel/lib.rs | 1 + > rust/kernel/maple_tree.rs | 286 ++++++++++++++++++++++++++++++++++++++++++++++ > 5 files changed, 304 insertions(+) > > diff --git a/MAINTAINERS b/MAINTAINERS > index dd810da5261b5d664ef9750f66ec022412e8014b..b7e7308ce07c050239c14c4f3a0fd89bdd8e8796 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -15956,7 +15956,9 @@ L: rust-for-linux@vger.kernel.org > S: Maintained > W: http://www.linux-mm.org > T: git git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm > +F: rust/helpers/maple_tree.c > F: rust/helpers/mm.c > +F: rust/kernel/maple_tree.rs > F: rust/kernel/mm.rs > F: rust/kernel/mm/ > > diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c > index 0683fffdbde25b89d285e3b0d8e6d8f7f5fd7474..ed7888a2661ad91f0fb78023311b3a266d30615c 100644 > --- a/rust/helpers/helpers.c > +++ b/rust/helpers/helpers.c > @@ -26,6 +26,7 @@ > #include "io.c" > #include "jump_label.c" > #include "kunit.c" > +#include "maple_tree.c" > #include "mm.c" > #include "mutex.c" > #include "page.c" > diff --git a/rust/helpers/maple_tree.c b/rust/helpers/maple_tree.c > new file mode 100644 > index 0000000000000000000000000000000000000000..119665846e8e8b018f8dc791a22fe20ace8e9c2c > --- /dev/null > +++ b/rust/helpers/maple_tree.c > @@ -0,0 +1,14 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +#include <linux/maple_tree.h> > + > +void rust_helper_mt_init_flags(struct maple_tree *mt, unsigned int flags) > +{ > + mt_init_flags(mt, flags); > +} > + > +struct ma_state rust_helper_MA_STATE(struct maple_tree *mt, unsigned long start, unsigned long end) > +{ > + MA_STATE(mas, mt, start, end); > + return mas; > +} > diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs > index 11a6461e98daab597e1eb2b513c5123686a1bb73..6cc152090a2f1986781800897ad48947c2d02e40 100644 > --- a/rust/kernel/lib.rs > +++ b/rust/kernel/lib.rs > @@ -88,6 +88,7 @@ > #[cfg(CONFIG_KUNIT)] > pub mod kunit; > pub mod list; > +pub mod maple_tree; > pub mod miscdevice; > pub mod mm; > #[cfg(CONFIG_NET)] > diff --git a/rust/kernel/maple_tree.rs b/rust/kernel/maple_tree.rs > new file mode 100644 > index 0000000000000000000000000000000000000000..0f26c173eedc7c79bb8e2b56fe85e8a266b3ae0c > --- /dev/null > +++ b/rust/kernel/maple_tree.rs > @@ -0,0 +1,286 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +//! Maple trees. > +//! > +//! C header: [`include/linux/maple_tree.h`](srctree/include/linux/maple_tree.h) > +//! > +//! Reference: <https://docs.kernel.org/core-api/maple_tree.html> > + > +use core::{ > + marker::PhantomData, > + ops::{Bound, RangeBounds}, > +}; > + > +use kernel::{ > + alloc::Flags, > + error::code::{EEXIST, ENOMEM}, > + error::to_result, > + prelude::*, > + types::{ForeignOwnable, Opaque}, > +}; > + > +/// A maple tree optimized for storing non-overlapping ranges. > +/// > +/// # Invariants > +/// > +/// Each range in the maple tree owns an instance of `T`. > +#[pin_data(PinnedDrop)] > +#[repr(transparent)] > +pub struct MapleTree<T: ForeignOwnable> { > + #[pin] > + tree: Opaque<bindings::maple_tree>, > + _p: PhantomData<T>, > +} > + > +#[inline] > +fn to_maple_range(range: impl RangeBounds<usize>) -> Option<(usize, usize)> { > + let first = match range.start_bound() { > + Bound::Included(start) => *start, > + Bound::Excluded(start) => start.checked_add(1)?, > + Bound::Unbounded => 0, > + }; > + > + let last = match range.end_bound() { > + Bound::Included(end) => *end, > + Bound::Excluded(end) => end.checked_sub(1)?, > + Bound::Unbounded => usize::MAX, > + }; > + > + if last < first { > + return None; > + } > + > + Some((first, last)) > +} > + > +impl<T: ForeignOwnable> MapleTree<T> { > + /// Create a new maple tree. > + /// > + /// The tree will use the regular implementation with a higher branching factor. > + #[inline] > + pub fn new() -> impl PinInit<Self> { > + pin_init!(MapleTree { > + // SAFETY: This initializes a maple tree into a pinned slot. The maple tree will be > + // destroyed in Drop before the memory location becomes invalid. > + tree <- Opaque::ffi_init(|slot| unsafe { bindings::mt_init_flags(slot, 0) }), > + _p: PhantomData, > + }) > + } > + > + /// Insert the value at the given index. > + /// > + /// If the maple tree already contains a range using the given index, then this call will fail. > + /// > + /// # Examples > + /// > + /// ``` > + /// use kernel::maple_tree::{MapleTree, InsertErrorKind}; > + /// > + /// let tree = KBox::pin_init(MapleTree::<KBox<i32>>::new(), GFP_KERNEL)?; > + /// > + /// let ten = KBox::new(10, GFP_KERNEL)?; > + /// let twenty = KBox::new(20, GFP_KERNEL)?; > + /// let the_answer = KBox::new(42, GFP_KERNEL)?; > + /// > + /// // These calls will succeed. > + /// tree.insert(100, ten, GFP_KERNEL)?; > + /// tree.insert(101, twenty, GFP_KERNEL)?; > + /// > + /// // This will fail because the index is already in use. > + /// assert_eq!( > + /// tree.insert(100, the_answer, GFP_KERNEL).unwrap_err().cause, > + /// InsertErrorKind::Occupied, > + /// ); > + /// # Ok::<_, Error>(()) > + /// ``` > + #[inline] > + pub fn insert(&self, index: usize, value: T, gfp: Flags) -> Result<(), InsertError<T>> { > + self.insert_range(index..=index, value, gfp) > + } > + > + /// Insert a value to the specified range, failing on overlap. > + /// > + /// This accepts the usual types of Rust ranges using the `..` and `..=` syntax for exclusive > + /// and inclusive ranges respectively. The range must not be empty, and must not overlap with > + /// any existing range. > + /// > + /// # Examples > + /// > + /// ``` > + /// use kernel::maple_tree::{MapleTree, InsertErrorKind}; > + /// > + /// let tree = KBox::pin_init(MapleTree::<KBox<i32>>::new(), GFP_KERNEL)?; > + /// > + /// let ten = KBox::new(10, GFP_KERNEL)?; > + /// let twenty = KBox::new(20, GFP_KERNEL)?; > + /// let the_answer = KBox::new(42, GFP_KERNEL)?; > + /// let hundred = KBox::new(100, GFP_KERNEL)?; > + /// > + /// // Insert the value 10 at the indices 100 to 499. > + /// tree.insert_range(100..500, ten, GFP_KERNEL)?; > + /// > + /// // Insert the value 20 at the indices 500 to 1000. > + /// tree.insert_range(500..=1000, twenty, GFP_KERNEL)?; > + /// > + /// // This will fail due to overlap with the previous range on index 1000. > + /// assert_eq!( > + /// tree.insert_range(1000..1200, the_answer, GFP_KERNEL).unwrap_err().cause, > + /// InsertErrorKind::Occupied, > + /// ); > + /// > + /// // When using .. to specify the range, you must be careful to ensure that the range is > + /// // non-empty. > + /// assert_eq!( > + /// tree.insert_range(72..72, hundred, GFP_KERNEL).unwrap_err().cause, > + /// InsertErrorKind::InvalidRequest, > + /// ); > + /// # Ok::<_, Error>(()) > + /// ``` > + pub fn insert_range<R>(&self, range: R, value: T, gfp: Flags) -> Result<(), InsertError<T>> > + where > + R: RangeBounds<usize>, > + { > + let Some((first, last)) = to_maple_range(range) else { > + return Err(InsertError { > + value, > + cause: InsertErrorKind::InvalidRequest, > + }); > + }; > + > + let ptr = T::into_foreign(value); > + > + // SAFETY: The tree is valid, and we are passing a pointer to an owned instance of `T`. > + let res = to_result(unsafe { > + bindings::mtree_insert_range(self.tree.get(), first, last, ptr, gfp.as_raw()) > + }); > + > + if let Err(err) = res { > + // SAFETY: As `mtree_insert_range` failed, it is safe to take back ownership. > + let value = unsafe { T::from_foreign(ptr) }; > + > + let cause = if err == ENOMEM { > + InsertErrorKind::Nomem > + } else if err == EEXIST { > + InsertErrorKind::Occupied > + } else { > + InsertErrorKind::InvalidRequest > + }; > + Err(InsertError { value, cause }) > + } else { > + Ok(()) > + } > + } > + > + /// Erase the range containing the given index. > + /// > + /// # Examples > + /// > + /// ``` > + /// use kernel::maple_tree::{MapleTree, InsertErrorKind}; > + /// > + /// let tree = KBox::pin_init(MapleTree::<KBox<i32>>::new(), GFP_KERNEL)?; > + /// > + /// let ten = KBox::new(10, GFP_KERNEL)?; > + /// let twenty = KBox::new(20, GFP_KERNEL)?; > + /// > + /// tree.insert_range(100..500, ten, GFP_KERNEL)?; > + /// tree.insert(67, twenty, GFP_KERNEL)?; > + /// > + /// let twenty = tree.erase(67).unwrap(); > + /// assert_eq!(*twenty, 20); > + /// > + /// let ten = tree.erase(275).unwrap(); > + /// assert_eq!(*ten, 10); > + /// > + /// // The previous call erased the entire range, not just index 275. > + /// assert!(tree.erase(127).is_none()); > + /// # Ok::<_, Error>(()) > + /// ``` > + #[inline] > + pub fn erase(&self, index: usize) -> Option<T> { > + // SAFETY: `self.tree` contains a valid maple tree. > + let ret = unsafe { bindings::mtree_erase(self.tree.get(), index) }; > + > + // SAFETY: If the pointer is not null, then we took ownership of a valid instance of `T` > + // from the tree. > + unsafe { T::try_from_foreign(ret) } > + } > + > + /// Free all `T` instances in this tree. > + /// > + /// # Safety > + /// > + /// This frees Rust data referenced by the maple tree without removing it from the maple tree. > + /// The caller must ensure that no reference that remains in the maple tree is used incorrectly > + /// after this call. > + unsafe fn free_all_entries(self: Pin<&mut Self>) { > + // SAFETY: The pointer references a valid maple tree. > + let ma_state = unsafe { Opaque::new(bindings::MA_STATE(self.tree.get(), 0, usize::MAX)) }; > + > + loop { > + // SAFETY: The maple tree is valid. This call to `free_all_entries` has exclusive > + // access to the maple tree, so no further synchronization is required. > + let ptr = unsafe { bindings::mas_find(ma_state.get(), usize::MAX) }; > + if ptr.is_null() { > + break; > + } > + // SAFETY: By the type invariants, this pointer references a valid value of type `T`. > + // By the safety requirements, it is okay to free it without removing it from the maple > + // tree. > + unsafe { drop(T::from_foreign(ptr)) }; > + } > + } > +} > + > +#[pinned_drop] > +impl<T: ForeignOwnable> PinnedDrop for MapleTree<T> { > + #[inline] > + fn drop(mut self: Pin<&mut Self>) { > + // We only iterate the tree if the Rust value have a destructor. > + if core::mem::needs_drop::<T>() { > + // SAFETY: The tree is valid, and other than the below `mtree_destroy` call, it will > + // not be accessed after this call. > + unsafe { self.as_mut().free_all_entries() }; > + } > + > + // SAFETY: The tree is valid, and will not be accessed after this call. > + unsafe { bindings::mtree_destroy(self.tree.get()) }; > + } > +} > + > +/// Error type for failure to insert a new value. > +pub struct InsertError<T> { > + /// The value that could not be inserted. > + pub value: T, > + /// The reason for the failure to insert. > + pub cause: InsertErrorKind, > +} Hmm, we've already have quite a few errors that look like this, e.g. `StoreError`. I wonder if we should just have a generic struct ErrroWithData<T, E> { pub value: T, pub cause: E, } > + > +/// The reason for the failure to insert. > +#[derive(PartialEq, Eq, Copy, Clone)] > +pub enum InsertErrorKind { > + /// There is already a value in the requested range. > + Occupied, > + /// Failure to allocate memory. > + Nomem, Given that we already have an error type for allocation failure, how about AllocError(crate::alloc::AllocError) ? I know we're getting ENOMEM from C, but this would match what the error type would be if it were from pure Rust code. > + /// The insertion request was invalid. > + InvalidRequest, > +} > + > +impl From<InsertErrorKind> for Error { > + #[inline] > + fn from(kind: InsertErrorKind) -> Error { > + match kind { > + InsertErrorKind::Occupied => EEXIST, > + InsertErrorKind::Nomem => ENOMEM, > + InsertErrorKind::InvalidRequest => EINVAL, > + } > + } > +} > + > +impl<T> From<InsertError<T>> for Error { > + #[inline] > + fn from(insert_err: InsertError<T>) -> Error { > + Error::from(insert_err.cause) > + } > +} >
On Sat, Jul 26, 2025 at 5:45 PM Gary Guo <gary@garyguo.net> wrote: > > On Sat, 26 Jul 2025 13:23:22 +0000 > Alice Ryhl <aliceryhl@google.com> wrote: > > > The maple tree will be used in the Tyr driver to allocate and keep track > > of GPU allocations created internally (i.e. not by userspace). It will > > likely also be used in the Nova driver eventually. > > > > This adds the simplest methods for additional and removal that do not > > require any special care with respect to concurrency. > > > > This implementation is based on the RFC by Andrew but with significant > > changes to simplify the implementation. > > > > Co-developed-by: Andrew Ballance <andrewjballance@gmail.com> > > Signed-off-by: Andrew Ballance <andrewjballance@gmail.com> > > Signed-off-by: Alice Ryhl <aliceryhl@google.com> > > Overall looks good to me, some nits and thoughts about the error type > below. > > Best, > GAry > > > +/// Error type for failure to insert a new value. > > +pub struct InsertError<T> { > > + /// The value that could not be inserted. > > + pub value: T, > > + /// The reason for the failure to insert. > > + pub cause: InsertErrorKind, > > +} > > Hmm, we've already have quite a few errors that look like this, e.g. > `StoreError`. I wonder if we should just have a generic > > struct ErrroWithData<T, E> { > pub value: T, > pub cause: E, > } I don't think we have any existing errors that look like this? > > + > > +/// The reason for the failure to insert. > > +#[derive(PartialEq, Eq, Copy, Clone)] > > +pub enum InsertErrorKind { > > + /// There is already a value in the requested range. > > + Occupied, > > + /// Failure to allocate memory. > > + Nomem, > > Given that we already have an error type for allocation failure, how > about > > AllocError(crate::alloc::AllocError) > > ? I know we're getting ENOMEM from C, but this would match what the > error type would be if it were from pure Rust code. I don't think it makes a big difference, but ok. Alice
© 2016 - 2025 Red Hat, Inc.