diff options
Diffstat (limited to 'rust/kernel/alloc/allocator.rs')
| -rw-r--r-- | rust/kernel/alloc/allocator.rs | 165 | 
1 files changed, 139 insertions, 26 deletions
| diff --git a/rust/kernel/alloc/allocator.rs b/rust/kernel/alloc/allocator.rs index 2692cf90c948..63bfb91b3671 100644 --- a/rust/kernel/alloc/allocator.rs +++ b/rust/kernel/alloc/allocator.rs @@ -13,9 +13,14 @@ use core::alloc::Layout;  use core::ptr;  use core::ptr::NonNull; -use crate::alloc::{AllocError, Allocator}; +use crate::alloc::{AllocError, Allocator, NumaNode};  use crate::bindings; -use crate::pr_warn; +use crate::page; + +const ARCH_KMALLOC_MINALIGN: usize = bindings::ARCH_KMALLOC_MINALIGN; + +mod iter; +pub use self::iter::VmallocPageIter;  /// The contiguous kernel allocator.  /// @@ -45,20 +50,26 @@ pub struct KVmalloc;  /// # Invariants  /// -/// One of the following: `krealloc`, `vrealloc`, `kvrealloc`. +/// One of the following: `krealloc_node_align`, `vrealloc_node_align`, `kvrealloc_node_align`.  struct ReallocFunc( -    unsafe extern "C" fn(*const crate::ffi::c_void, usize, u32) -> *mut crate::ffi::c_void, +    unsafe extern "C" fn( +        *const crate::ffi::c_void, +        usize, +        crate::ffi::c_ulong, +        u32, +        crate::ffi::c_int, +    ) -> *mut crate::ffi::c_void,  );  impl ReallocFunc { -    // INVARIANT: `krealloc` satisfies the type invariants. -    const KREALLOC: Self = Self(bindings::krealloc); +    // INVARIANT: `krealloc_node_align` satisfies the type invariants. +    const KREALLOC: Self = Self(bindings::krealloc_node_align); -    // INVARIANT: `vrealloc` satisfies the type invariants. -    const VREALLOC: Self = Self(bindings::vrealloc); +    // INVARIANT: `vrealloc_node_align` satisfies the type invariants. +    const VREALLOC: Self = Self(bindings::vrealloc_node_align); -    // INVARIANT: `kvrealloc` satisfies the type invariants. -    const KVREALLOC: Self = Self(bindings::kvrealloc); +    // INVARIANT: `kvrealloc_node_align` satisfies the type invariants. +    const KVREALLOC: Self = Self(bindings::kvrealloc_node_align);      /// # Safety      /// @@ -76,6 +87,7 @@ impl ReallocFunc {          layout: Layout,          old_layout: Layout,          flags: Flags, +        nid: NumaNode,      ) -> Result<NonNull<[u8]>, AllocError> {          let size = layout.size();          let ptr = match ptr { @@ -99,7 +111,7 @@ impl ReallocFunc {          // - Those functions provide the guarantees of this function.          let raw_ptr = unsafe {              // If `size == 0` and `ptr != NULL` the memory behind the pointer is freed. -            self.0(ptr.cast(), size, flags.0).cast() +            self.0(ptr.cast(), size, layout.align(), flags.0, nid.0).cast()          };          let ptr = if size == 0 { @@ -128,17 +140,68 @@ impl Kmalloc {  // - passing a pointer to a valid memory allocation is OK,  // - `realloc` satisfies the guarantees, since `ReallocFunc::call` has the same.  unsafe impl Allocator for Kmalloc { +    const MIN_ALIGN: usize = ARCH_KMALLOC_MINALIGN; +      #[inline]      unsafe fn realloc(          ptr: Option<NonNull<u8>>,          layout: Layout,          old_layout: Layout,          flags: Flags, +        nid: NumaNode,      ) -> Result<NonNull<[u8]>, AllocError> {          let layout = Kmalloc::aligned_layout(layout);          // SAFETY: `ReallocFunc::call` has the same safety requirements as `Allocator::realloc`. -        unsafe { ReallocFunc::KREALLOC.call(ptr, layout, old_layout, flags) } +        unsafe { ReallocFunc::KREALLOC.call(ptr, layout, old_layout, flags, nid) } +    } +} + +impl Vmalloc { +    /// Convert a pointer to a [`Vmalloc`] allocation to a [`page::BorrowedPage`]. +    /// +    /// # Examples +    /// +    /// ``` +    /// # use core::ptr::{NonNull, from_mut}; +    /// # use kernel::{page, prelude::*}; +    /// use kernel::alloc::allocator::Vmalloc; +    /// +    /// let mut vbox = VBox::<[u8; page::PAGE_SIZE]>::new_uninit(GFP_KERNEL)?; +    /// +    /// { +    ///     // SAFETY: By the type invariant of `Box` the inner pointer of `vbox` is non-null. +    ///     let ptr = unsafe { NonNull::new_unchecked(from_mut(&mut *vbox)) }; +    /// +    ///     // SAFETY: +    ///     // `ptr` is a valid pointer to a `Vmalloc` allocation. +    ///     // `ptr` is valid for the entire lifetime of `page`. +    ///     let page = unsafe { Vmalloc::to_page(ptr.cast()) }; +    /// +    ///     // SAFETY: There is no concurrent read or write to the same page. +    ///     unsafe { page.fill_zero_raw(0, page::PAGE_SIZE)? }; +    /// } +    /// # Ok::<(), Error>(()) +    /// ``` +    /// +    /// # Safety +    /// +    /// - `ptr` must be a valid pointer to a [`Vmalloc`] allocation. +    /// - `ptr` must remain valid for the entire duration of `'a`. +    pub unsafe fn to_page<'a>(ptr: NonNull<u8>) -> page::BorrowedPage<'a> { +        // SAFETY: `ptr` is a valid pointer to `Vmalloc` memory. +        let page = unsafe { bindings::vmalloc_to_page(ptr.as_ptr().cast()) }; + +        // SAFETY: `vmalloc_to_page` returns a valid pointer to a `struct page` for a valid pointer +        // to `Vmalloc` memory. +        let page = unsafe { NonNull::new_unchecked(page) }; + +        // SAFETY: +        // - `page` is a valid pointer to a `struct page`, given that by the safety requirements of +        //   this function `ptr` is a valid pointer to a `Vmalloc` allocation. +        // - By the safety requirements of this function `ptr` is valid for the entire lifetime of +        //   `'a`. +        unsafe { page::BorrowedPage::from_raw(page) }      }  } @@ -147,22 +210,19 @@ unsafe impl Allocator for Kmalloc {  // - passing a pointer to a valid memory allocation is OK,  // - `realloc` satisfies the guarantees, since `ReallocFunc::call` has the same.  unsafe impl Allocator for Vmalloc { +    const MIN_ALIGN: usize = kernel::page::PAGE_SIZE; +      #[inline]      unsafe fn realloc(          ptr: Option<NonNull<u8>>,          layout: Layout,          old_layout: Layout,          flags: Flags, +        nid: NumaNode,      ) -> Result<NonNull<[u8]>, AllocError> { -        // TODO: Support alignments larger than PAGE_SIZE. -        if layout.align() > bindings::PAGE_SIZE { -            pr_warn!("Vmalloc does not support alignments larger than PAGE_SIZE yet.\n"); -            return Err(AllocError); -        } -          // SAFETY: If not `None`, `ptr` is guaranteed to point to valid memory, which was previously          // allocated with this `Allocator`. -        unsafe { ReallocFunc::VREALLOC.call(ptr, layout, old_layout, flags) } +        unsafe { ReallocFunc::VREALLOC.call(ptr, layout, old_layout, flags, nid) }      }  } @@ -171,25 +231,78 @@ unsafe impl Allocator for Vmalloc {  // - passing a pointer to a valid memory allocation is OK,  // - `realloc` satisfies the guarantees, since `ReallocFunc::call` has the same.  unsafe impl Allocator for KVmalloc { +    const MIN_ALIGN: usize = ARCH_KMALLOC_MINALIGN; +      #[inline]      unsafe fn realloc(          ptr: Option<NonNull<u8>>,          layout: Layout,          old_layout: Layout,          flags: Flags, +        nid: NumaNode,      ) -> Result<NonNull<[u8]>, AllocError> {          // `KVmalloc` may use the `Kmalloc` backend, hence we have to enforce a `Kmalloc`          // compatible layout.          let layout = Kmalloc::aligned_layout(layout); -        // TODO: Support alignments larger than PAGE_SIZE. -        if layout.align() > bindings::PAGE_SIZE { -            pr_warn!("KVmalloc does not support alignments larger than PAGE_SIZE yet.\n"); -            return Err(AllocError); -        } -          // SAFETY: If not `None`, `ptr` is guaranteed to point to valid memory, which was previously          // allocated with this `Allocator`. -        unsafe { ReallocFunc::KVREALLOC.call(ptr, layout, old_layout, flags) } +        unsafe { ReallocFunc::KVREALLOC.call(ptr, layout, old_layout, flags, nid) } +    } +} + +#[macros::kunit_tests(rust_allocator)] +mod tests { +    use super::*; +    use core::mem::MaybeUninit; +    use kernel::prelude::*; + +    #[test] +    fn test_alignment() -> Result { +        const TEST_SIZE: usize = 1024; +        const TEST_LARGE_ALIGN_SIZE: usize = kernel::page::PAGE_SIZE * 4; + +        // These two structs are used to test allocating aligned memory. +        // they don't need to be accessed, so they're marked as dead_code. +        #[expect(dead_code)] +        #[repr(align(128))] +        struct Blob([u8; TEST_SIZE]); +        #[expect(dead_code)] +        #[repr(align(8192))] +        struct LargeAlignBlob([u8; TEST_LARGE_ALIGN_SIZE]); + +        struct TestAlign<T, A: Allocator>(Box<MaybeUninit<T>, A>); +        impl<T, A: Allocator> TestAlign<T, A> { +            fn new() -> Result<Self> { +                Ok(Self(Box::<_, A>::new_uninit(GFP_KERNEL)?)) +            } + +            fn is_aligned_to(&self, align: usize) -> bool { +                assert!(align.is_power_of_two()); + +                let addr = self.0.as_ptr() as usize; +                addr & (align - 1) == 0 +            } +        } + +        let ta = TestAlign::<Blob, Kmalloc>::new()?; +        assert!(ta.is_aligned_to(128)); + +        let ta = TestAlign::<LargeAlignBlob, Kmalloc>::new()?; +        assert!(ta.is_aligned_to(8192)); + +        let ta = TestAlign::<Blob, Vmalloc>::new()?; +        assert!(ta.is_aligned_to(128)); + +        let ta = TestAlign::<LargeAlignBlob, Vmalloc>::new()?; +        assert!(ta.is_aligned_to(8192)); + +        let ta = TestAlign::<Blob, KVmalloc>::new()?; +        assert!(ta.is_aligned_to(128)); + +        let ta = TestAlign::<LargeAlignBlob, KVmalloc>::new()?; +        assert!(ta.is_aligned_to(8192)); + +        Ok(())      }  } | 
