`ExclusivePage` wraps a regular page but adds an invariant that the page data area does not incur data races. This means `ExclusivePage` cannot be mapped to user space or shared with devices, and it becomes simpler to directly reference the contents of the page.
Since `Page` implements `AlwaysRefCounted`, handing out a `&Page` from an `ExclusivePage` would allow safe code to obtain an `ARef<Page>` to the page and break the aliasing invariant of `ExclusivePage`. Thus, do not implement `Deref<Target = Page>` for `ExclusivePage`. Assisted-by: LLM Signed-off-by: Andreas Hindborg <[email protected]> --- This patch was previously submitted as part of a different series, see link below. It is included in this series to provide an example user of `Owned`. Link: https://lore.kernel.org/r/[email protected] --- rust/kernel/page.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/rust/kernel/page.rs b/rust/kernel/page.rs index cd394b0656c0..4e5b1c2f4346 100644 --- a/rust/kernel/page.rs +++ b/rust/kernel/page.rs @@ -17,7 +17,11 @@ AlwaysRefCounted, RefCounted, // }, - types::Opaque, + types::{ + Opaque, + Ownable, + Owned, // + }, uaccess::UserSliceReader, // }; use core::ptr::{ @@ -349,3 +353,52 @@ unsafe fn dec_ref(obj: NonNull<Self>) { // SAFETY: We do not implement `Ownable`, thus it is okay to obtain an `ARef<Page>` from a // `&Page`. unsafe impl AlwaysRefCounted for Page {} + +/// A page whose data area follows standard Rust aliasing rules. +/// +/// [`ExclusivePage`] has the same usage constraints as other Rust types. Thus, it cannot be mapped +/// to user space or shared with devices. This makes it safe to reference the contents of the page +/// while the page is mapped in kernel space. +/// +/// Note: [`ExclusivePage`] does not provide access to the underlying [`Page`]. Handing out a +/// `&Page` would allow safe code to obtain an [`ARef<Page>`] to the page, which would violate the +/// invariants of `ExclusivePage`. +/// +/// # Invariants +/// +/// The data of this page is accessed only through references to [`ExclusivePage`]. While a shared +/// reference to a [`ExclusivePage`] exists, there are no writes to its data. While an exclusive +/// reference exists, there are no other reads or writes of its data. +#[repr(transparent)] +pub struct ExclusivePage(Page); + +impl ExclusivePage { + /// Allocates a new `ExclusivePage`. + pub fn alloc_page(flags: Flags) -> Result<Owned<Self>, AllocError> { + // SAFETY: Depending on the value of `gfp_flags`, this call may sleep. Other than that, it + // is always safe to call this method. + let page = unsafe { bindings::alloc_pages(flags.as_raw(), 0) }; + let page = NonNull::new(page).ok_or(AllocError)?; + + // INVARIANT: The page was just allocated, so its data is only accessible through the + // returned `Owned<ExclusivePage>`. + // SAFETY: + // - We just successfully allocated a page, so we hold the only reference to it, and we can + // transfer that exclusive ownership to the new `Owned<ExclusivePage>`. Since + // `ExclusivePage` + // is transparent over `Page`, we can cast the pointer directly. + // - The page is never moved out of its allocation, so we can treat it as pinned. + Ok(unsafe { Owned::from_raw(page.cast()) }) + } +} + +impl Ownable for ExclusivePage { + #[inline] + unsafe fn release(this: NonNull<Self>) { + // SAFETY: By the function safety requirements, we have exclusive ownership of the page, and + // by the type invariant no other references to it exist, so we relinquish the last + // reference count and the page is freed. Since `ExclusivePage` is transparent over `Page`, + // we can cast the pointer directly. + unsafe { bindings::put_page(this.cast().as_ptr()) }; + } +} -- 2.51.2
