From: Andreas Hindborg <a.hindborg@kernel.org>
To: "Alice Ryhl" <aliceryhl@google.com>,
"Lorenzo Stoakes" <ljs@kernel.org>,
"Liam R. Howlett" <liam@infradead.org>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>,
"Will Deacon" <will@kernel.org>,
"Peter Zijlstra" <peterz@infradead.org>,
"Mark Rutland" <mark.rutland@arm.com>,
"Daniel Almeida" <daniel.almeida@collabora.com>,
"Tamir Duberstein" <tamird@kernel.org>,
"Alexandre Courbot" <acourbot@nvidia.com>,
"Onur Özkan" <work@onurozkan.dev>,
"Lyude Paul" <lyude@redhat.com>
Cc: linux-mm@kvack.org, rust-for-linux@vger.kernel.org,
linux-kernel@vger.kernel.org, driver-core@lists.linux.dev,
Andreas Hindborg <a.hindborg@kernel.org>
Subject: [PATCH v5 4/4] rust: page: add byte-wise atomic copy methods for `SysMem`
Date: Tue, 10 Nov 2026 15:44:22 +0100 [thread overview]
Message-ID: <20261110-page-volatile-io-v5-4-a91ef252c24b@kernel.org> (raw)
In-Reply-To: <20261110-page-volatile-io-v5-0-a91ef252c24b@kernel.org>
When copying data between a `Page` and a buffer that is mapped to user
space, or otherwise shared outside the kernel, it is impossible to
guarantee absence of concurrent memory operations on that buffer. Copying
with `Page::{read,write}_raw` would be a data race in that case.
Add `Page::{read,write}_bytewise_atomic`, which take a `SysMem` view of the
shared buffer. The accesses to the buffer go through `SysMemBackend`, which
uses byte-wise atomic operations, so they are well-defined when racing with
external actors or with atomic accesses from other kernel code. The page
side of the copy uses plain accesses, so the page itself must not be
accessed concurrently, as is the case for `Page::{read,write}_raw`.
Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
rust/kernel/page.rs | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 121 insertions(+)
diff --git a/rust/kernel/page.rs b/rust/kernel/page.rs
index 1c0796ea229f..92486d312b0c 100644
--- a/rust/kernel/page.rs
+++ b/rust/kernel/page.rs
@@ -12,6 +12,12 @@
code::*,
Result, //
},
+ io::{
+ Io,
+ IoCopyable,
+ SysMem,
+ SysMemBackend, //
+ },
uaccess::UserSliceReader, //
};
use core::{
@@ -305,6 +311,58 @@ pub unsafe fn read_raw(&self, dst: *mut u8, offset: usize, len: usize) -> Result
})
}
+ /// Maps the page and reads from it into the given system memory region using byte-wise atomic
+ /// memory operations.
+ ///
+ /// This method will perform bounds checks on the page offset. If `offset .. offset+dst.len()`
+ /// goes outside of the page, then this call returns [`EINVAL`].
+ ///
+ /// The writes to `dst` are byte-wise atomic, so they are well-defined even if `dst` is
+ /// concurrently accessed by hardware, by user space, or by other kernel code using atomic or
+ /// volatile accesses. The reads from the page are plain accesses, so the page must not be
+ /// written to concurrently.
+ ///
+ /// # Safety
+ ///
+ /// Callers must ensure that:
+ ///
+ /// - This call does not race with a write to the source page that overlaps with this read.
+ /// - The memory region of `dst` does not overlap with the memory of the page.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::{
+ /// io::SysMem,
+ /// page::Page,
+ /// };
+ ///
+ /// let page = Page::alloc_page(GFP_KERNEL | __GFP_ZERO)?;
+ /// let mut buf = [0xffu8; 16];
+ ///
+ /// // SAFETY: `buf` is a local array that outlives `dst`, and it is not accessed in any other
+ /// // way while `dst` is alive.
+ /// let dst = unsafe { SysMem::new(&raw mut buf as *mut [u8]) };
+ ///
+ /// // SAFETY: `page` is not written to during this call, and `buf` is not part of the page.
+ /// unsafe { page.read_bytewise_atomic(dst, 0)? };
+ /// assert_eq!(buf, [0u8; 16]);
+ /// # Ok::<(), Error>(())
+ /// ```
+ pub unsafe fn read_bytewise_atomic(&self, dst: SysMem<'_, [u8]>, offset: usize) -> Result {
+ self.with_pointer_into_page(offset, dst.len(), move |src| {
+ // SAFETY:
+ // - If `with_pointer_into_page` calls into this closure, then it has performed a
+ // bounds check and guarantees that `src` is valid for `dst.len()` bytes, which is
+ // equal to `dst.size()`.
+ // - By the safety requirements of this function, the page is not written to during
+ // this call, so `src` is valid for volatile reads.
+ // - By the safety requirements of this function, `src` does not overlap with `dst`.
+ unsafe { SysMemBackend::copy_to_io(dst, src) };
+ Ok(())
+ })
+ }
+
/// Maps the page and writes into it from the given buffer.
///
/// This method will perform bounds checks on the page offset. If `offset .. offset+len` goes
@@ -326,6 +384,69 @@ pub unsafe fn write_raw(&self, src: *const u8, offset: usize, len: usize) -> Res
})
}
+ /// Maps the page and writes into it from the given system memory region using byte-wise atomic
+ /// memory operations.
+ ///
+ /// This method will perform bounds checks on the page offset. If `offset .. offset+src.len()`
+ /// goes outside of the page, then this call returns [`EINVAL`].
+ ///
+ /// The reads from `src` are byte-wise atomic, so they are well-defined even if `src` is
+ /// concurrently accessed by hardware, by user space, or by other kernel code using atomic or
+ /// volatile accesses. The writes to the page are plain accesses, so the page must not be read
+ /// from or written to concurrently.
+ ///
+ /// # Safety
+ ///
+ /// Callers must ensure that:
+ ///
+ /// - This call does not race with a read or write to the destination page that overlaps with
+ /// this write.
+ /// - The memory region of `src` does not overlap with the memory of the page.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::{
+ /// io::SysMem,
+ /// page::Page,
+ /// };
+ ///
+ /// let page = Page::alloc_page(GFP_KERNEL)?;
+ /// let mut data = [0xabu8; 16];
+ /// let mut buf = [0u8; 16];
+ ///
+ /// // SAFETY: `data` and `buf` are local arrays that outlive the views, and they are not
+ /// // accessed in any other way while the views are alive.
+ /// let (src, dst) = unsafe {
+ /// (
+ /// SysMem::new(&raw mut data as *mut [u8]),
+ /// SysMem::new(&raw mut buf as *mut [u8]),
+ /// )
+ /// };
+ ///
+ /// // SAFETY: `page` is not accessed in any other way during these calls, and neither `data`
+ /// // nor `buf` is part of the page.
+ /// unsafe {
+ /// page.write_bytewise_atomic(src, 0)?;
+ /// page.read_bytewise_atomic(dst, 0)?;
+ /// }
+ /// assert_eq!(buf, data);
+ /// # Ok::<(), Error>(())
+ /// ```
+ pub unsafe fn write_bytewise_atomic(&self, src: SysMem<'_, [u8]>, offset: usize) -> Result {
+ self.with_pointer_into_page(offset, src.len(), move |dst| {
+ // SAFETY:
+ // - If `with_pointer_into_page` calls into this closure, then it has performed a
+ // bounds check and guarantees that `dst` is valid for `src.len()` bytes, which is
+ // equal to `src.size()`.
+ // - By the safety requirements of this function, the page is not read from or written
+ // to during this call, so `dst` is valid for volatile writes.
+ // - By the safety requirements of this function, `dst` does not overlap with `src`.
+ unsafe { SysMemBackend::copy_from_io(src, dst) };
+ Ok(())
+ })
+ }
+
/// Maps the page and zeroes the given slice.
///
/// This method will perform bounds checks on the page offset. If `offset .. offset+len` goes
--
2.54.0
prev parent reply other threads:[~2026-11-10 14:45 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-10 14:44 [PATCH v5 0/4] rust: byte-wise atomic copies between `Page` and shared memory Andreas Hindborg
2026-11-10 14:44 ` [PATCH v5 1/4] rust: io: require freedom from plain accesses for `SysMem` Andreas Hindborg
2026-11-10 14:44 ` [PATCH v5 2/4] rust: sync: atomic: add atomic_per_byte_memcpy Andreas Hindborg
2026-11-10 14:44 ` [PATCH v5 3/4] rust: io: copy `SysMem` with `atomic_per_byte_memcpy` Andreas Hindborg
2026-11-10 14:44 ` Andreas Hindborg [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261110-page-volatile-io-v5-4-a91ef252c24b@kernel.org \
--to=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=driver-core@lists.linux.dev \
--cc=gary@garyguo.net \
--cc=liam@infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=lossin@kernel.org \
--cc=lyude@redhat.com \
--cc=mark.rutland@arm.com \
--cc=ojeda@kernel.org \
--cc=peterz@infradead.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tamird@kernel.org \
--cc=tmgross@umich.edu \
--cc=will@kernel.org \
--cc=work@onurozkan.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®