* [PATCH v2 0/6] rust: add support for port io
@ 2025-05-14 10:57 Andrew Ballance
2025-05-14 10:57 ` [PATCH v2 1/6] rust: helpers: io: use macro to generate io accessor functions Andrew Ballance
` (6 more replies)
0 siblings, 7 replies; 9+ messages in thread
From: Andrew Ballance @ 2025-05-14 10:57 UTC (permalink / raw)
To: dakr, a.hindborg, airlied, akpm, alex.gaynor, aliceryhl,
andrewjballance, andriy.shevchenko, arnd, benno.lossin, bhelgaas,
bjorn3_gh, boqun.feng, daniel.almeida, fujita.tomonori, gary,
gregkh, kwilczynski, me, ojeda, raag.jadav, rafael, simona,
tmgross
Cc: dri-devel, linux-kernel, linux-pci, nouveau, rust-for-linux
currently the rust `Io` type maps to the c read{b, w, l, q}/write{b, w, l, q}
functions and have no support for port io. this can be a problem for pci::Bar
because the pointer returned by pci_iomap can be either PIO or MMIO [0].
this patch series splits the `Io` type into `Io`, and `MMIo`. `Io` can be
used to access PIO or MMIO. `MMIo` can only access memory mapped IO but
might, depending on the arch, be faster than `Io`. and updates pci::Bar,
so that it is generic over Io and, a user can optionally give a compile
time hint about the type of io.
Link: https://docs.kernel.org/6.11/driver-api/pci/pci.html#c.pci_iomap [0]
changes in v2:
- remove `PortIo`
- typo fixes
- squash "fixup" patches so that patches will not introduce build fails
- move some changes across patches so that build will not fail
- changes macro define in rust/helpers/io.c to use full rust name
- specialize `io_backend` for the x86 case
- do not modify lib/iomap.c
- rebased on v6.15-rc6
Link to v1: https://lore.kernel.org/rust-for-linux/20250509031524.2604087-1-andrewjballance@gmail.com/
Andrew Ballance (3):
rust: io: add new Io type
rust: io: add from_raw_cookie functions
rust: pci: make Bar generic over Io
Fiona Behrens (3):
rust: helpers: io: use macro to generate io accessor functions
rust: io: make Io use IoAccess trait
rust: io: implement Debug for IoRaw and add some doctests
drivers/gpu/nova-core/driver.rs | 4 +-
drivers/gpu/nova-core/regs.rs | 1 +
rust/helpers/io.c | 112 ++----
rust/kernel/devres.rs | 4 +-
rust/kernel/io.rs | 645 +++++++++++++++++++++++---------
rust/kernel/pci.rs | 101 +++--
samples/rust/rust_driver_pci.rs | 6 +-
7 files changed, 595 insertions(+), 278 deletions(-)
base-commit: 82f2b0b97b36ee3fcddf0f0780a9a0825d52fec3
--
2.49.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 1/6] rust: helpers: io: use macro to generate io accessor functions
2025-05-14 10:57 [PATCH v2 0/6] rust: add support for port io Andrew Ballance
@ 2025-05-14 10:57 ` Andrew Ballance
2025-05-14 10:57 ` [PATCH v2 2/6] rust: io: make Io use IoAccess trait Andrew Ballance
` (5 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Andrew Ballance @ 2025-05-14 10:57 UTC (permalink / raw)
To: dakr, a.hindborg, airlied, akpm, alex.gaynor, aliceryhl,
andrewjballance, andriy.shevchenko, arnd, benno.lossin, bhelgaas,
bjorn3_gh, boqun.feng, daniel.almeida, fujita.tomonori, gary,
gregkh, kwilczynski, me, ojeda, raag.jadav, rafael, simona,
tmgross
Cc: dri-devel, linux-kernel, linux-pci, nouveau, rust-for-linux
From: Fiona Behrens <me@kloenk.dev>
Generate the `rust_helper_read{b,w,l,q}`, `rust_helper_write{b,w,l,q}`
and the relaxed version using a C macro.
This removes a lot of redundant code and is in preparation for pio
functions which uses a similar C macro.
Signed-off-by: Fiona Behrens <me@kloenk.dev>
Signed-off-by: Andrew Ballance <andrewjballance@gmail.com>
---
rust/helpers/io.c | 105 +++++++++++++---------------------------------
1 file changed, 29 insertions(+), 76 deletions(-)
diff --git a/rust/helpers/io.c b/rust/helpers/io.c
index 15ea187c5466..d419b5b3b7c7 100644
--- a/rust/helpers/io.c
+++ b/rust/helpers/io.c
@@ -12,90 +12,43 @@ void rust_helper_iounmap(void __iomem *addr)
iounmap(addr);
}
-u8 rust_helper_readb(const void __iomem *addr)
-{
- return readb(addr);
-}
-
-u16 rust_helper_readw(const void __iomem *addr)
-{
- return readw(addr);
-}
-
-u32 rust_helper_readl(const void __iomem *addr)
-{
- return readl(addr);
-}
-
+#define define_rust_io_read_helper(rust_name, name, type) \
+ type rust_name(void __iomem *addr) \
+ { \
+ return name(addr); \
+ }
+
+#define define_rust_io_write_helper(rust_name, name, type) \
+ void rust_name(type value, void __iomem *addr) \
+ { \
+ name(value, addr); \
+ }
+
+define_rust_io_read_helper(rust_helper_readb, readb, u8);
+define_rust_io_read_helper(rust_helper_readw, readw, u16);
+define_rust_io_read_helper(rust_helper_readl, readl, u32);
#ifdef CONFIG_64BIT
-u64 rust_helper_readq(const void __iomem *addr)
-{
- return readq(addr);
-}
+define_rust_io_read_helper(rust_helper_readq, readq, u64);
#endif
-void rust_helper_writeb(u8 value, void __iomem *addr)
-{
- writeb(value, addr);
-}
-
-void rust_helper_writew(u16 value, void __iomem *addr)
-{
- writew(value, addr);
-}
-
-void rust_helper_writel(u32 value, void __iomem *addr)
-{
- writel(value, addr);
-}
-
+define_rust_io_write_helper(rust_helper_writeb, writeb, u8);
+define_rust_io_write_helper(rust_helper_writew, writew, u16);
+define_rust_io_write_helper(rust_helper_writel, writel, u32);
#ifdef CONFIG_64BIT
-void rust_helper_writeq(u64 value, void __iomem *addr)
-{
- writeq(value, addr);
-}
+define_rust_io_write_helper(rust_helper_writeq, writeq, u64);
#endif
-u8 rust_helper_readb_relaxed(const void __iomem *addr)
-{
- return readb_relaxed(addr);
-}
-
-u16 rust_helper_readw_relaxed(const void __iomem *addr)
-{
- return readw_relaxed(addr);
-}
-
-u32 rust_helper_readl_relaxed(const void __iomem *addr)
-{
- return readl_relaxed(addr);
-}
-
+define_rust_io_read_helper(rust_helper_readb_relaxed, readb_relaxed, u8);
+define_rust_io_read_helper(rust_helper_readw_relaxed, readw_relaxed, u16);
+define_rust_io_read_helper(rust_helper_readl_relaxed, readl_relaxed, u32);
#ifdef CONFIG_64BIT
-u64 rust_helper_readq_relaxed(const void __iomem *addr)
-{
- return readq_relaxed(addr);
-}
+define_rust_io_read_helper(rust_helper_readq_relaxed, readq_relaxed, u64);
#endif
-void rust_helper_writeb_relaxed(u8 value, void __iomem *addr)
-{
- writeb_relaxed(value, addr);
-}
-
-void rust_helper_writew_relaxed(u16 value, void __iomem *addr)
-{
- writew_relaxed(value, addr);
-}
-
-void rust_helper_writel_relaxed(u32 value, void __iomem *addr)
-{
- writel_relaxed(value, addr);
-}
-
+define_rust_io_write_helper(rust_helper_writeb_relaxed, writeb_relaxed, u8);
+define_rust_io_write_helper(rust_helper_writew_relaxed, writew_relaxed, u16);
+define_rust_io_write_helper(rust_helper_writel_relaxed, writel_relaxed, u32);
#ifdef CONFIG_64BIT
-void rust_helper_writeq_relaxed(u64 value, void __iomem *addr)
-{
- writeq_relaxed(value, addr);
-}
+define_rust_io_write_helper(rust_helper_writeq_relaxed, writeq_relaxed, u64);
#endif
+
--
2.49.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v2 2/6] rust: io: make Io use IoAccess trait
2025-05-14 10:57 [PATCH v2 0/6] rust: add support for port io Andrew Ballance
2025-05-14 10:57 ` [PATCH v2 1/6] rust: helpers: io: use macro to generate io accessor functions Andrew Ballance
@ 2025-05-14 10:57 ` Andrew Ballance
2025-05-14 10:57 ` [PATCH v2 3/6] rust: io: add new Io type Andrew Ballance
` (4 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Andrew Ballance @ 2025-05-14 10:57 UTC (permalink / raw)
To: dakr, a.hindborg, airlied, akpm, alex.gaynor, aliceryhl,
andrewjballance, andriy.shevchenko, arnd, benno.lossin, bhelgaas,
bjorn3_gh, boqun.feng, daniel.almeida, fujita.tomonori, gary,
gregkh, kwilczynski, me, ojeda, raag.jadav, rafael, simona,
tmgross
Cc: dri-devel, linux-kernel, linux-pci, nouveau, rust-for-linux
From: Fiona Behrens <me@kloenk.dev>
makes the Io struct use the new traits (`IoAccess`, `IoAccess64`,
`IoAccessRelaxed` and `IoAccess64Relaxed)` to perform io.
This allows to later implement a generic Io framework.
update pci,rust_driver_pci.rs, nova-core and a devres doc test to use
the new io api.
Signed-off-by: Fiona Behrens <me@kloenk.dev>
Co-developed-by: Andrew Ballance <andrewjballance@gmail.com>
Signed-off-by: Andrew Ballance <andrewjballance@gmail.com>
---
drivers/gpu/nova-core/regs.rs | 1 +
rust/kernel/devres.rs | 4 +-
rust/kernel/io.rs | 438 +++++++++++++++++++-------------
rust/kernel/pci.rs | 2 +-
samples/rust/rust_driver_pci.rs | 4 +-
5 files changed, 263 insertions(+), 186 deletions(-)
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index b1a25b86ef17..079c3d275a47 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
use crate::driver::Bar0;
+use kernel::io::IoAccess;
// TODO
//
diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs
index ddb1ce4a78d9..88d145821ca8 100644
--- a/rust/kernel/devres.rs
+++ b/rust/kernel/devres.rs
@@ -45,7 +45,7 @@ struct DevresInner<T> {
/// # Example
///
/// ```no_run
-/// # use kernel::{bindings, c_str, device::Device, devres::Devres, io::{Io, IoRaw}};
+/// # use kernel::{bindings, c_str, device::Device, devres::Devres, io::{Io, IoRaw, IoAccess}};
/// # use core::ops::Deref;
///
/// // See also [`pci::Bar`] for a real example.
@@ -80,7 +80,7 @@ struct DevresInner<T> {
///
/// fn deref(&self) -> &Self::Target {
/// // SAFETY: The memory range stored in `self` has been properly mapped in `Self::new`.
-/// unsafe { Io::from_raw(&self.0) }
+/// unsafe { Io::from_raw_ref(&self.0) }
/// }
/// }
/// # fn no_run() -> Result<(), Error> {
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index 72d80a6f131e..368167d57863 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -7,6 +7,210 @@
use crate::error::{code::EINVAL, Result};
use crate::{bindings, build_assert};
+/// Private macro to define the [`IoAccess`] functions.
+macro_rules! define_io_access_function {
+ (@read_derived $(#[$attr:meta])* $name_unchecked:ident, $vis:vis $name:ident, $try_vis:vis $try_name:ident, $type_name:ty) => {
+ /// Read data from a given offset known at compile time.
+ ///
+ /// Bound checks are perfomed on compile time, hence if the offset is not known at compile
+ /// time, the build will fail.
+ $(#[$attr])*
+ #[inline]
+ $vis fn $name(&self, offset: usize) -> $type_name {
+ build_assert!(offset_valid::<$type_name>(offset, SIZE));
+
+ // SAFETY: offset checked to be valid above.
+ unsafe { self.$name_unchecked(offset) }
+ }
+
+ /// Read data from a given offset.
+ ///
+ /// Bound checks are performed at runtime, it fails if the offset (plus type size) is
+ /// out of bounds.
+ $(#[$attr])*
+ #[inline]
+ $try_vis fn $try_name(&self, offset: usize) -> Result<$type_name> {
+ if !(offset_valid::<$type_name>(offset, self.maxsize())) {
+ return Err(EINVAL);
+ }
+
+ // SAFETY: offset checked to be valid above.
+ Ok(unsafe { self.$name_unchecked(offset) })
+ }
+ };
+ (@read $(#[$attr:meta])* $name_unchecked:ident, $name:ident, $try_name:ident, $type_name:ty) => {
+ /// Read data from a given offset without doing any bound checks.
+ /// The offset is relative to the base address of Self.
+ ///
+ /// # Safety
+ ///
+ /// The offset has to be valid for self.
+ $(#[$attr])*
+ unsafe fn $name_unchecked(&self, offset: usize) -> $type_name;
+
+ define_io_access_function!(@read_derived $(#[$attr])* $name_unchecked, $name, $try_name, $type_name);
+ };
+ (@read $($(#[$attr:meta])* $name_unchecked:ident, $name:ident, $try_name:ident, $type_name:ty;)+) => {
+ $(
+ define_io_access_function!(@read $(#[$attr])* $name_unchecked, $name, $try_name, $type_name);
+ )*
+ };
+ (@write_derived $(#[$attr:meta])* $name_unchecked:ident, $vis:vis $name:ident, $try_vis:vis $try_name:ident, $type_name:ty) => {
+ /// Write data to a given offset known at compile time.
+ /// Bound checks are performed at compile time, hence if the offset is not known at compile
+ /// time, the build will fail.
+ $(#[$attr])*
+ #[inline]
+ $vis fn $name(&self, value: $type_name, offset: usize) {
+ build_assert!(offset_valid::<$type_name>(offset, SIZE));
+
+ // SAFETY: offset checked to be valid above.
+ unsafe { self.$name_unchecked(value, offset) }
+ }
+
+ /// Write data to a given offset.
+ ///
+ /// Bound checks are performed at runtime, it fails if the offset (plus the type size) is
+ /// out of bounds.
+ $(#[$attr])*
+ #[inline]
+ $try_vis fn $try_name(&self, value: $type_name, offset: usize) -> Result {
+ if !(offset_valid::<$type_name>(offset, self.maxsize())) {
+ return Err(EINVAL);
+ }
+
+ // SAFETY: offset checked to be valid above.
+ Ok(unsafe { self.$name_unchecked(value, offset) })
+ }
+ };
+ (@write $(#[$attr:meta])* $name_unchecked:ident, $name:ident, $try_name:ident, $type_name:ty) => {
+ /// Write data to a given offset without doing any bound checks.
+ /// The offset is relative to the base address of self.
+ ///
+ /// # Safety
+ ///
+ /// The offset has to be valid for Self.
+ $(#[$attr])*
+ unsafe fn $name_unchecked(&self, value: $type_name, offset: usize);
+
+ define_io_access_function!(@write_derived $(#[$attr])* $name_unchecked, $name, $try_name, $type_name);
+ };
+ (@write $($(#[$attr:meta])* $name_unchecked:ident, $name:ident, $try_name:ident, $type_name:ty;)+) => {
+ $(
+ define_io_access_function!(@write $(#[$attr])* $name_unchecked, $name, $try_name, $type_name);
+ )*
+ };
+}
+
+/// Private macro to generate accessor functions that call the correct C functions given as `fn_c`.
+///
+/// This takes either `@read` or `@write` to generate a single read or write accessor function.
+///
+/// This also can take a list of read write pairs to generate both at the same time.
+macro_rules! impl_accessor_fn {
+ (@read $(#[$attr:meta])* $vis:vis $fn_rust:ident, $fn_c:ident, $type_name:ty) => {
+ $(#[$attr])*
+ $vis unsafe fn $fn_rust(&self, offset: usize) -> $type_name {
+ // SAFETY: by the safety requirement of the function `self.addr() + offset` is valid to read
+ // TODO: once MSRV is >= 1.79.0 replace `+` with `unchecked_add`
+ unsafe { bindings::$fn_c((self.addr() + offset) as _) as _ }
+ }
+ };
+ (@write $(#[$attr:meta])* $vis:vis $fn_rust:ident, $fn_c:ident, $type_name:ty) => {
+ $(#[$attr])*
+ $vis unsafe fn $fn_rust(&self, value: $type_name, offset: usize) {
+ // SAFETY:
+ // by the safety requirement of the function `self.addr() + offset` is valid to write
+ // TODO: once MSRV is >= 1.79.0 replace `+` with `unchecked_add`
+ unsafe { bindings::$fn_c(value, (self.addr() + offset) as _) as _ }
+ }
+ };
+ (
+ $(
+ $(#[$attr:meta])*
+ $vis_read:vis $fn_rust_read:ident, $fn_c_read:ident,
+ $vis_write:vis $fn_rust_write:ident, $fn_c_write:ident,
+ $type_name:ty $(;)?
+ )+
+ ) => {
+ $(
+ impl_accessor_fn!(@read $(#[$attr])* $vis_read $fn_rust_read, $fn_c_read, $type_name);
+ impl_accessor_fn!(@write $(#[$attr])* $vis_write $fn_rust_write, $fn_c_write, $type_name);
+ )+
+ };
+}
+
+/// Check if the offset is valid to still support the type U in the given size
+const fn offset_valid<U>(offset: usize, size: usize) -> bool {
+ let type_size = core::mem::size_of::<U>();
+ if let Some(end) = offset.checked_add(type_size) {
+ end <= size && offset % type_size == 0
+ } else {
+ false
+ }
+}
+
+/// Io Access functions.
+///
+/// # Safety
+///
+/// `SIZE` and `maxsize()` has to always be valid to add to the base address.
+pub unsafe trait IoAccess<const SIZE: usize = 0> {
+ /// Returns the maximum size of the accessed IO area.
+ fn maxsize(&self) -> usize;
+
+ /// Returns the base address of the accessed IO area.
+ fn addr(&self) -> usize;
+
+ define_io_access_function!(@read
+ read8_unchecked, read8, try_read8, u8;
+ read16_unchecked, read16, try_read16, u16;
+ read32_unchecked, read32, try_read32, u32;
+ );
+
+ define_io_access_function!(@write
+ write8_unchecked, write8, try_write8, u8;
+ write16_unchecked, write16, try_write16, u16;
+ write32_unchecked, write32, try_write32, u32;
+ );
+}
+
+/// Extending trait of [`IoAccess`] offering 64 bit functions.
+#[cfg(CONFIG_64BIT)]
+pub trait IoAccess64<const SIZE: usize = 0>: IoAccess<SIZE> {
+ define_io_access_function!(@read read64_unchecked, read64, try_read64, u64);
+ define_io_access_function!(@write write64_unchecked, write64, try_write64, u64);
+}
+
+/// Io Relaxed Access functions.
+///
+/// Similar to [`IoAccess`] but using relaxed memory boundries.
+pub trait IoAccessRelaxed<const SIZE: usize = 0>: IoAccess<SIZE> {
+ define_io_access_function!(@read
+ read8_relaxed_unchecked, read8_relaxed, try_read8_relaxed, u8;
+ read16_relaxed_unchecked, read16_relaxed, try_read16_relaxed, u16;
+ read32_relaxed_unchecked, read32_relaxed, try_read32_relaxed, u32;
+ );
+
+ define_io_access_function!(@write
+ write8_relaxed_unchecked, write8_relaxed, try_write8_relaxed, u8;
+ write16_relaxed_unchecked, write16_relaxed, try_write16_relaxed, u16;
+ write32_relaxed_unchecked, write32_relaxed, try_write32_relaxed, u32;
+ );
+}
+
+/// Extending trait of [`IoAccessRelaxed`] offering 64 bit functions.
+#[cfg(CONFIG_64BIT)]
+pub trait IoAccess64Relaxed<const SIZE: usize = 0>: IoAccess<SIZE> {
+ define_io_access_function!(@read
+ read64_relaxed_unchecked, read64_relaxed, try_read64_relaxed, u64;
+ );
+
+ define_io_access_function!(@write
+ write64_relaxed_unchecked, write64_relaxed, try_write64_relaxed, u64;
+ );
+}
+
/// Raw representation of an MMIO region.
///
/// By itself, the existence of an instance of this structure does not provide any guarantees that
@@ -43,218 +247,88 @@ pub fn maxsize(&self) -> usize {
}
}
-/// IO-mapped memory, starting at the base address @addr and spanning @maxlen bytes.
+/// IO-mapped memory, starting at the base address [`addr`] and spanning [`maxsize`] bytes.
///
/// The creator (usually a subsystem / bus such as PCI) is responsible for creating the
-/// mapping, performing an additional region request etc.
-///
-/// # Invariant
-///
-/// `addr` is the start and `maxsize` the length of valid I/O mapped memory region of size
-/// `maxsize`.
-///
-/// # Examples
-///
-/// ```no_run
-/// # use kernel::{bindings, io::{Io, IoRaw}};
-/// # use core::ops::Deref;
+/// mapping, performing an additional region request, etc.
///
-/// // See also [`pci::Bar`] for a real example.
-/// struct IoMem<const SIZE: usize>(IoRaw<SIZE>);
+/// # Invariants
///
-/// impl<const SIZE: usize> IoMem<SIZE> {
-/// /// # Safety
-/// ///
-/// /// [`paddr`, `paddr` + `SIZE`) must be a valid MMIO region that is mappable into the CPUs
-/// /// virtual address space.
-/// unsafe fn new(paddr: usize) -> Result<Self>{
-/// // SAFETY: By the safety requirements of this function [`paddr`, `paddr` + `SIZE`) is
-/// // valid for `ioremap`.
-/// let addr = unsafe { bindings::ioremap(paddr as _, SIZE as _) };
-/// if addr.is_null() {
-/// return Err(ENOMEM);
-/// }
+/// [`addr`] is the start and [`maxsize`] the length of valid I/O mapped memory region of
+/// size [`maxsize`].
///
-/// Ok(IoMem(IoRaw::new(addr as _, SIZE)?))
-/// }
-/// }
+/// [`addr`] is valid to access with the C [`read`]/[`write`] family of functions.
///
-/// impl<const SIZE: usize> Drop for IoMem<SIZE> {
-/// fn drop(&mut self) {
-/// // SAFETY: `self.0.addr()` is guaranteed to be properly mapped by `Self::new`.
-/// unsafe { bindings::iounmap(self.0.addr() as _); };
-/// }
-/// }
-///
-/// impl<const SIZE: usize> Deref for IoMem<SIZE> {
-/// type Target = Io<SIZE>;
-///
-/// fn deref(&self) -> &Self::Target {
-/// // SAFETY: The memory range stored in `self` has been properly mapped in `Self::new`.
-/// unsafe { Io::from_raw(&self.0) }
-/// }
-/// }
-///
-///# fn no_run() -> Result<(), Error> {
-/// // SAFETY: Invalid usage for example purposes.
-/// let iomem = unsafe { IoMem::<{ core::mem::size_of::<u32>() }>::new(0xBAAAAAAD)? };
-/// iomem.write32(0x42, 0x0);
-/// assert!(iomem.try_write32(0x42, 0x0).is_ok());
-/// assert!(iomem.try_write32(0x42, 0x4).is_err());
-/// # Ok(())
-/// # }
-/// ```
+/// [`addr`]: IoAccess::addr
+/// [`maxsize`]: IoAccess::maxsize
+/// [`read`]: https://docs.kernel.org/driver-api/device-io.html#differences-between-i-o-access-functions
+/// [`write`]: https://docs.kernel.org/driver-api/device-io.html#differences-between-i-o-access-functions
#[repr(transparent)]
pub struct Io<const SIZE: usize = 0>(IoRaw<SIZE>);
-macro_rules! define_read {
- ($(#[$attr:meta])* $name:ident, $try_name:ident, $c_fn:ident -> $type_name:ty) => {
- /// Read IO data from a given offset known at compile time.
- ///
- /// Bound checks are performed on compile time, hence if the offset is not known at compile
- /// time, the build will fail.
- $(#[$attr])*
- #[inline]
- pub fn $name(&self, offset: usize) -> $type_name {
- let addr = self.io_addr_assert::<$type_name>(offset);
-
- // SAFETY: By the type invariant `addr` is a valid address for MMIO operations.
- unsafe { bindings::$c_fn(addr as _) }
- }
-
- /// Read IO data from a given offset.
- ///
- /// Bound checks are performed on runtime, it fails if the offset (plus the type size) is
- /// out of bounds.
- $(#[$attr])*
- pub fn $try_name(&self, offset: usize) -> Result<$type_name> {
- let addr = self.io_addr::<$type_name>(offset)?;
-
- // SAFETY: By the type invariant `addr` is a valid address for MMIO operations.
- Ok(unsafe { bindings::$c_fn(addr as _) })
- }
- };
-}
-
-macro_rules! define_write {
- ($(#[$attr:meta])* $name:ident, $try_name:ident, $c_fn:ident <- $type_name:ty) => {
- /// Write IO data from a given offset known at compile time.
- ///
- /// Bound checks are performed on compile time, hence if the offset is not known at compile
- /// time, the build will fail.
- $(#[$attr])*
- #[inline]
- pub fn $name(&self, value: $type_name, offset: usize) {
- let addr = self.io_addr_assert::<$type_name>(offset);
-
- // SAFETY: By the type invariant `addr` is a valid address for MMIO operations.
- unsafe { bindings::$c_fn(value, addr as _, ) }
- }
-
- /// Write IO data from a given offset.
- ///
- /// Bound checks are performed on runtime, it fails if the offset (plus the type size) is
- /// out of bounds.
- $(#[$attr])*
- pub fn $try_name(&self, value: $type_name, offset: usize) -> Result {
- let addr = self.io_addr::<$type_name>(offset)?;
-
- // SAFETY: By the type invariant `addr` is a valid address for MMIO operations.
- unsafe { bindings::$c_fn(value, addr as _) }
- Ok(())
- }
- };
-}
-
impl<const SIZE: usize> Io<SIZE> {
- /// Converts an `IoRaw` into an `Io` instance, providing the accessors to the MMIO mapping.
+ /// Convert a [`IoRaw`] into an [`MMIo`] instance, providing the accessors to the MMIO mapping.
///
/// # Safety
///
- /// Callers must ensure that `addr` is the start of a valid I/O mapped memory region of size
- /// `maxsize`.
- pub unsafe fn from_raw(raw: &IoRaw<SIZE>) -> &Self {
- // SAFETY: `Io` is a transparent wrapper around `IoRaw`.
- unsafe { &*core::ptr::from_ref(raw).cast() }
- }
-
- /// Returns the base address of this mapping.
+ /// Callers must ensure that `addr` is the start of a valid I/O mapped memory region of
+ /// size `maxsize`.
#[inline]
- pub fn addr(&self) -> usize {
- self.0.addr()
- }
-
- /// Returns the maximum size of this mapping.
- #[inline]
- pub fn maxsize(&self) -> usize {
- self.0.maxsize()
+ pub unsafe fn from_raw(raw: IoRaw<SIZE>) -> Self {
+ Self(raw)
}
+ /// Convert a ref to [`IoRaw`] into an [`MMIo`] instance, providing the accessors to the
+ /// MMIo mapping.
+ ///
+ /// # Safety
+ ///
+ /// Callers must ensure that `addr` is the start of a valid I/O mapped memory region of
+ /// size `maxsize`.
#[inline]
- const fn offset_valid<U>(offset: usize, size: usize) -> bool {
- let type_size = core::mem::size_of::<U>();
- if let Some(end) = offset.checked_add(type_size) {
- end <= size && offset % type_size == 0
- } else {
- false
- }
+ pub unsafe fn from_raw_ref(raw: &IoRaw<SIZE>) -> &Self {
+ // SAFETY: `MMIo` is a transparent wrapper around `IoRaw`.
+ unsafe { &*core::ptr::from_ref(raw).cast() }
}
+}
+// SAFETY: as per invariant `raw` is valid
+unsafe impl<const SIZE: usize> IoAccess<SIZE> for Io<SIZE> {
#[inline]
- fn io_addr<U>(&self, offset: usize) -> Result<usize> {
- if !Self::offset_valid::<U>(offset, self.maxsize()) {
- return Err(EINVAL);
- }
-
- // Probably no need to check, since the safety requirements of `Self::new` guarantee that
- // this can't overflow.
- self.addr().checked_add(offset).ok_or(EINVAL)
+ fn maxsize(&self) -> usize {
+ self.0.maxsize()
}
#[inline]
- fn io_addr_assert<U>(&self, offset: usize) -> usize {
- build_assert!(Self::offset_valid::<U>(offset, SIZE));
-
- self.addr() + offset
+ fn addr(&self) -> usize {
+ self.0.addr()
}
- define_read!(read8, try_read8, readb -> u8);
- define_read!(read16, try_read16, readw -> u16);
- define_read!(read32, try_read32, readl -> u32);
- define_read!(
- #[cfg(CONFIG_64BIT)]
- read64,
- try_read64,
- readq -> u64
+ impl_accessor_fn!(
+ read8_unchecked, readb, write8_unchecked, writeb, u8;
+ read16_unchecked, readw, write16_unchecked, writew, u16;
+ read32_unchecked, readl, write32_unchecked, writel, u32;
);
+}
- define_read!(read8_relaxed, try_read8_relaxed, readb_relaxed -> u8);
- define_read!(read16_relaxed, try_read16_relaxed, readw_relaxed -> u16);
- define_read!(read32_relaxed, try_read32_relaxed, readl_relaxed -> u32);
- define_read!(
- #[cfg(CONFIG_64BIT)]
- read64_relaxed,
- try_read64_relaxed,
- readq_relaxed -> u64
+#[cfg(CONFIG_64BIT)]
+impl<const SIZE: usize> IoAccess64<SIZE> for Io<SIZE> {
+ impl_accessor_fn!(
+ read64_unchecked, readq, write64_unchecked, writeq, u64;
);
+}
- define_write!(write8, try_write8, writeb <- u8);
- define_write!(write16, try_write16, writew <- u16);
- define_write!(write32, try_write32, writel <- u32);
- define_write!(
- #[cfg(CONFIG_64BIT)]
- write64,
- try_write64,
- writeq <- u64
+impl<const SIZE: usize> IoAccessRelaxed<SIZE> for Io<SIZE> {
+ impl_accessor_fn!(
+ read8_relaxed_unchecked, readb_relaxed, write8_relaxed_unchecked, writeb_relaxed, u8;
+ read16_relaxed_unchecked, readw_relaxed, write16_relaxed_unchecked, writew_relaxed, u16;
+ read32_relaxed_unchecked, readl_relaxed, write32_relaxed_unchecked, writel_relaxed, u32;
);
+}
- define_write!(write8_relaxed, try_write8_relaxed, writeb_relaxed <- u8);
- define_write!(write16_relaxed, try_write16_relaxed, writew_relaxed <- u16);
- define_write!(write32_relaxed, try_write32_relaxed, writel_relaxed <- u32);
- define_write!(
- #[cfg(CONFIG_64BIT)]
- write64_relaxed,
- try_write64_relaxed,
- writeq_relaxed <- u64
+#[cfg(CONFIG_64BIT)]
+impl<const SIZE: usize> IoAccess64Relaxed<SIZE> for Io<SIZE> {
+ impl_accessor_fn!(
+ read64_relaxed_unchecked, readq_relaxed, write64_relaxed_unchecked, writeq_relaxed, u64;
);
}
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index c97d6d470b28..9f5ca22d327a 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -356,7 +356,7 @@ impl<const SIZE: usize> Deref for Bar<SIZE> {
fn deref(&self) -> &Self::Target {
// SAFETY: By the type invariant of `Self`, the MMIO range in `self.io` is properly mapped.
- unsafe { Io::from_raw(&self.io) }
+ unsafe { Io::from_raw_ref(&self.io) }
}
}
diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs
index 2bb260aebc9e..a8d292f4c1b3 100644
--- a/samples/rust/rust_driver_pci.rs
+++ b/samples/rust/rust_driver_pci.rs
@@ -4,7 +4,9 @@
//!
//! To make this driver probe, QEMU must be run with `-device pci-testdev`.
-use kernel::{bindings, c_str, device::Core, devres::Devres, pci, prelude::*, types::ARef};
+use kernel::{
+ bindings, c_str, device::Core, devres::Devres, io::IoAccess, pci, prelude::*, types::ARef,
+};
struct Regs;
--
2.49.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v2 3/6] rust: io: add new Io type
2025-05-14 10:57 [PATCH v2 0/6] rust: add support for port io Andrew Ballance
2025-05-14 10:57 ` [PATCH v2 1/6] rust: helpers: io: use macro to generate io accessor functions Andrew Ballance
2025-05-14 10:57 ` [PATCH v2 2/6] rust: io: make Io use IoAccess trait Andrew Ballance
@ 2025-05-14 10:57 ` Andrew Ballance
2025-05-14 10:57 ` [PATCH v2 4/6] rust: io: implement Debug for IoRaw and add some doctests Andrew Ballance
` (3 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Andrew Ballance @ 2025-05-14 10:57 UTC (permalink / raw)
To: dakr, a.hindborg, airlied, akpm, alex.gaynor, aliceryhl,
andrewjballance, andriy.shevchenko, arnd, benno.lossin, bhelgaas,
bjorn3_gh, boqun.feng, daniel.almeida, fujita.tomonori, gary,
gregkh, kwilczynski, me, ojeda, raag.jadav, rafael, simona,
tmgross
Cc: dri-devel, linux-kernel, linux-pci, nouveau, rust-for-linux
adds a new Io type that uses the C ioread/iowrite family of functions
and implements the IoAccess trait for it and renames the old `Io`
to `MMIo`.
Signed-off-by: Andrew Ballance <andrewjballance@gmail.com>
---
rust/helpers/io.c | 7 ++++
rust/kernel/io.rs | 97 ++++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 98 insertions(+), 6 deletions(-)
diff --git a/rust/helpers/io.c b/rust/helpers/io.c
index d419b5b3b7c7..a138914523c8 100644
--- a/rust/helpers/io.c
+++ b/rust/helpers/io.c
@@ -52,3 +52,10 @@ define_rust_io_write_helper(rust_helper_writel_relaxed, writel_relaxed, u32);
define_rust_io_write_helper(rust_helper_writeq_relaxed, writeq_relaxed, u64);
#endif
+define_rust_io_read_helper(rust_helper_ioread8, ioread8, u8);
+define_rust_io_read_helper(rust_helper_ioread16, ioread16, u16);
+define_rust_io_read_helper(rust_helper_ioread32, ioread32, u32);
+
+define_rust_io_write_helper(rust_helper_iowrite8, iowrite8, u8);
+define_rust_io_write_helper(rust_helper_iowrite16, iowrite16, u16);
+define_rust_io_write_helper(rust_helper_iowrite32, iowrite32, u32);
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index 368167d57863..ce044c155b16 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -264,9 +264,9 @@ pub fn maxsize(&self) -> usize {
/// [`read`]: https://docs.kernel.org/driver-api/device-io.html#differences-between-i-o-access-functions
/// [`write`]: https://docs.kernel.org/driver-api/device-io.html#differences-between-i-o-access-functions
#[repr(transparent)]
-pub struct Io<const SIZE: usize = 0>(IoRaw<SIZE>);
+pub struct MMIo<const SIZE: usize = 0>(IoRaw<SIZE>);
-impl<const SIZE: usize> Io<SIZE> {
+impl<const SIZE: usize> MMIo<SIZE> {
/// Convert a [`IoRaw`] into an [`MMIo`] instance, providing the accessors to the MMIO mapping.
///
/// # Safety
@@ -293,7 +293,7 @@ pub unsafe fn from_raw_ref(raw: &IoRaw<SIZE>) -> &Self {
}
// SAFETY: as per invariant `raw` is valid
-unsafe impl<const SIZE: usize> IoAccess<SIZE> for Io<SIZE> {
+unsafe impl<const SIZE: usize> IoAccess<SIZE> for MMIo<SIZE> {
#[inline]
fn maxsize(&self) -> usize {
self.0.maxsize()
@@ -312,13 +312,13 @@ fn addr(&self) -> usize {
}
#[cfg(CONFIG_64BIT)]
-impl<const SIZE: usize> IoAccess64<SIZE> for Io<SIZE> {
+impl<const SIZE: usize> IoAccess64<SIZE> for MMIo<SIZE> {
impl_accessor_fn!(
read64_unchecked, readq, write64_unchecked, writeq, u64;
);
}
-impl<const SIZE: usize> IoAccessRelaxed<SIZE> for Io<SIZE> {
+impl<const SIZE: usize> IoAccessRelaxed<SIZE> for MMIo<SIZE> {
impl_accessor_fn!(
read8_relaxed_unchecked, readb_relaxed, write8_relaxed_unchecked, writeb_relaxed, u8;
read16_relaxed_unchecked, readw_relaxed, write16_relaxed_unchecked, writew_relaxed, u16;
@@ -327,8 +327,93 @@ impl<const SIZE: usize> IoAccessRelaxed<SIZE> for Io<SIZE> {
}
#[cfg(CONFIG_64BIT)]
-impl<const SIZE: usize> IoAccess64Relaxed<SIZE> for Io<SIZE> {
+impl<const SIZE: usize> IoAccess64Relaxed<SIZE> for MMIo<SIZE> {
impl_accessor_fn!(
read64_relaxed_unchecked, readq_relaxed, write64_relaxed_unchecked, writeq_relaxed, u64;
);
}
+
+/// Io that can be either PortIo or MMIo,
+/// starting at the base address [`addr`] and spanning [`maxsize`] bytes.
+///
+/// The creator (usually a subsystem / bus such as PCI) is responsible for creating the
+/// mapping, performing an additional region request, etc.
+///
+/// # Invariants
+///
+/// [`addr`] is the start and [`maxsize`] the length of a valid io region of size [`maxsize`].
+///
+/// [`addr`] is valid to access with the C [`ioread`]/[`iowrite`] family of functions.
+///
+/// [`addr`]: IoAccess::addr
+/// [`maxsize`]: IoAccess::maxsize
+/// [`ioread`]: https://docs.kernel.org/driver-api/device-io.html#differences-between-i-o-access-functions
+/// [`iowrite`]: https://docs.kernel.org/driver-api/device-io.html#differences-between-i-o-access-functions
+#[repr(transparent)]
+pub struct Io<const SIZE: usize = 0>(IoRaw<SIZE>);
+
+impl<const SIZE: usize> Io<SIZE> {
+ /// Convert a [`IoRaw`] into an [`Io`] instance, providing the accessors to the
+ /// Io mapping.
+ ///
+ /// # Safety
+ ///
+ /// Callers must ensure that `addr` is the start of a valid I/O region of size `maxsize`.
+ ///
+ /// ```
+ /// use kernel::io::{IoRaw, Io, IoAccess};
+ ///
+ /// let raw = IoRaw::<2>::new(0xDEADBEEFC0DE, 2).unwrap();
+ /// // SAFETY: test, value is not actually written to.
+ /// let io: Io<2> = unsafe { Io::from_raw(raw) };
+ /// # assert_eq!(0xDEADBEEFC0DE, io.addr());
+ /// # assert_eq!(2, io.maxsize());
+ /// ```
+ pub unsafe fn from_raw(raw: IoRaw<SIZE>) -> Self {
+ Self(raw)
+ }
+
+ /// Convert a ref to [`IoRaw`] into an [`Io`] instance, providing the accessors to
+ /// the Io mapping.
+ ///
+ /// # Safety
+ ///
+ /// Callers must ensure that `addr` is the start of a valid I/O mapped memory region of
+ /// size `maxsize`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::io::{IoRaw, Io, IoAccess};
+ ///
+ /// let raw = IoRaw::<2>::new(0xDEADBEEFC0DE, 2).unwrap();
+ /// // SAFETY: test, value is not actually written to.
+ /// let io: &Io<2> = unsafe { Io::from_raw_ref(&raw) };
+ /// # assert_eq!(raw.addr(), io.addr());
+ /// # assert_eq!(raw.maxsize(), io.maxsize());
+ /// ```
+ #[inline]
+ pub unsafe fn from_raw_ref(raw: &IoRaw<SIZE>) -> &Self {
+ // SAFETY: `Io` is a transparent wrapper around `IoRaw`.
+ unsafe { &*core::ptr::from_ref(raw).cast() }
+ }
+}
+
+// SAFETY: as per invariant `raw` is valid
+unsafe impl<const SIZE: usize> IoAccess<SIZE> for Io<SIZE> {
+ #[inline]
+ fn addr(&self) -> usize {
+ self.0.addr()
+ }
+
+ #[inline]
+ fn maxsize(&self) -> usize {
+ self.0.maxsize()
+ }
+
+ impl_accessor_fn!(
+ read8_unchecked, ioread8, write8_unchecked, iowrite8, u8;
+ read16_unchecked, ioread16, write16_unchecked, iowrite16, u16;
+ read32_unchecked, ioread32, write32_unchecked, iowrite32, u32;
+ );
+}
--
2.49.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v2 4/6] rust: io: implement Debug for IoRaw and add some doctests
2025-05-14 10:57 [PATCH v2 0/6] rust: add support for port io Andrew Ballance
` (2 preceding siblings ...)
2025-05-14 10:57 ` [PATCH v2 3/6] rust: io: add new Io type Andrew Ballance
@ 2025-05-14 10:57 ` Andrew Ballance
2025-05-14 10:57 ` [PATCH v2 5/6] rust: io: add from_raw_cookie functions Andrew Ballance
` (2 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Andrew Ballance @ 2025-05-14 10:57 UTC (permalink / raw)
To: dakr, a.hindborg, airlied, akpm, alex.gaynor, aliceryhl,
andrewjballance, andriy.shevchenko, arnd, benno.lossin, bhelgaas,
bjorn3_gh, boqun.feng, daniel.almeida, fujita.tomonori, gary,
gregkh, kwilczynski, me, ojeda, raag.jadav, rafael, simona,
tmgross
Cc: dri-devel, linux-kernel, linux-pci, nouveau, rust-for-linux
From: Fiona Behrens <me@kloenk.dev>
Implement `Debug` for `kernel::io::IoRaw` which also outputs the const
generic SIZE as a field.
Add some doctests to `IoRaw::new` and `MMIo::from_raw(_ref)`.
Signed-off-by: Fiona Behrens <me@kloenk.dev>
Signed-off-by: Andrew Ballance <andrewjballance@gmail.com>
---
rust/kernel/io.rs | 63 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index ce044c155b16..9445451f4b02 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -226,6 +226,33 @@ pub struct IoRaw<const SIZE: usize = 0> {
impl<const SIZE: usize> IoRaw<SIZE> {
/// Returns a new `IoRaw` instance on success, an error otherwise.
+ ///
+ /// # Examples
+ ///
+ /// Const generic size 0, only allowing runtime checks:
+ /// ```
+ /// use kernel::io::IoRaw;
+ ///
+ /// let raw: IoRaw<0> = IoRaw::new(0xDEADBEEFC0DE, 8).unwrap();
+ /// # assert_eq!(raw.addr(), 0xDEADBEEFC0DE);
+ /// # assert_eq!(raw.maxsize(), 8);
+ /// ```
+ ///
+ /// Const generic size equals maxsize:
+ /// ```
+ /// use kernel::io::IoRaw;
+ ///
+ /// let raw: IoRaw<8> = IoRaw::new(0xDEADBEEFC0DE, 8).unwrap();
+ /// # assert_eq!(raw.addr(), 0xDEADBEEFC0DE);
+ /// # assert_eq!(raw.maxsize(), 8);
+ /// ```
+ ///
+ /// Const generic size bigger then maxsize:
+ /// ```
+ /// use kernel::io::IoRaw;
+ ///
+ /// IoRaw::<16>::new(0xDEADBEEFC0DE, 8).unwrap_err();
+ /// ```
pub fn new(addr: usize, maxsize: usize) -> Result<Self> {
if maxsize < SIZE {
return Err(EINVAL);
@@ -247,6 +274,16 @@ pub fn maxsize(&self) -> usize {
}
}
+impl<const SIZE: usize> core::fmt::Debug for IoRaw<SIZE> {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ f.debug_struct("IoRaw")
+ .field("SIZE", &SIZE)
+ .field("addr", &self.addr)
+ .field("maxsize", &self.maxsize)
+ .finish()
+ }
+}
+
/// IO-mapped memory, starting at the base address [`addr`] and spanning [`maxsize`] bytes.
///
/// The creator (usually a subsystem / bus such as PCI) is responsible for creating the
@@ -263,6 +300,7 @@ pub fn maxsize(&self) -> usize {
/// [`maxsize`]: IoAccess::maxsize
/// [`read`]: https://docs.kernel.org/driver-api/device-io.html#differences-between-i-o-access-functions
/// [`write`]: https://docs.kernel.org/driver-api/device-io.html#differences-between-i-o-access-functions
+#[derive(Debug)]
#[repr(transparent)]
pub struct MMIo<const SIZE: usize = 0>(IoRaw<SIZE>);
@@ -273,6 +311,18 @@ impl<const SIZE: usize> MMIo<SIZE> {
///
/// Callers must ensure that `addr` is the start of a valid I/O mapped memory region of
/// size `maxsize`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::io::{IoRaw, MMIo, IoAccess};
+ ///
+ /// let raw = IoRaw::<2>::new(0xDEADBEEFC0DE, 2).unwrap();
+ /// // SAFETY: test, value is not actually written to.
+ /// let mmio: MMIo<2> = unsafe { MMIo::from_raw(raw) };
+ /// # assert_eq!(0xDEADBEEFC0DE, mmio.addr());
+ /// # assert_eq!(2, mmio.maxsize());
+ /// ```
#[inline]
pub unsafe fn from_raw(raw: IoRaw<SIZE>) -> Self {
Self(raw)
@@ -285,6 +335,18 @@ pub unsafe fn from_raw(raw: IoRaw<SIZE>) -> Self {
///
/// Callers must ensure that `addr` is the start of a valid I/O mapped memory region of
/// size `maxsize`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::io::{IoRaw, MMIo, IoAccess};
+ ///
+ /// let raw = IoRaw::<2>::new(0xDEADBEEFC0DE, 2).unwrap();
+ /// // SAFETY: test, value is not actually written to.
+ /// let mmio: &MMIo<2> = unsafe { MMIo::from_raw_ref(&raw) };
+ /// # assert_eq!(raw.addr(), mmio.addr());
+ /// # assert_eq!(raw.maxsize(), mmio.maxsize());
+ /// ```
#[inline]
pub unsafe fn from_raw_ref(raw: &IoRaw<SIZE>) -> &Self {
// SAFETY: `MMIo` is a transparent wrapper around `IoRaw`.
@@ -349,6 +411,7 @@ impl<const SIZE: usize> IoAccess64Relaxed<SIZE> for MMIo<SIZE> {
/// [`maxsize`]: IoAccess::maxsize
/// [`ioread`]: https://docs.kernel.org/driver-api/device-io.html#differences-between-i-o-access-functions
/// [`iowrite`]: https://docs.kernel.org/driver-api/device-io.html#differences-between-i-o-access-functions
+#[derive(Debug)]
#[repr(transparent)]
pub struct Io<const SIZE: usize = 0>(IoRaw<SIZE>);
--
2.49.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v2 5/6] rust: io: add from_raw_cookie functions
2025-05-14 10:57 [PATCH v2 0/6] rust: add support for port io Andrew Ballance
` (3 preceding siblings ...)
2025-05-14 10:57 ` [PATCH v2 4/6] rust: io: implement Debug for IoRaw and add some doctests Andrew Ballance
@ 2025-05-14 10:57 ` Andrew Ballance
2025-05-14 10:57 ` [PATCH v2 6/6] rust: pci: make Bar generic over Io Andrew Ballance
2025-06-16 8:03 ` [PATCH v2 0/6] rust: add support for port io Alice Ryhl
6 siblings, 0 replies; 9+ messages in thread
From: Andrew Ballance @ 2025-05-14 10:57 UTC (permalink / raw)
To: dakr, a.hindborg, airlied, akpm, alex.gaynor, aliceryhl,
andrewjballance, andriy.shevchenko, arnd, benno.lossin, bhelgaas,
bjorn3_gh, boqun.feng, daniel.almeida, fujita.tomonori, gary,
gregkh, kwilczynski, me, ojeda, raag.jadav, rafael, simona,
tmgross
Cc: dri-devel, linux-kernel, linux-pci, nouveau, rust-for-linux
adds a `from_raw_cookie` function to the IoAccess trait.
`from_raw_cookie` attempts to convert a iomem address that can be
accessed by the ioread/iowrite family of C functions into either
a `Io` or `MMIo`.
This is done so that devices that know what type of Io they are at
compile time can give a hint about their type.
Suggested-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Andrew Ballance <andrewjballance@gmail.com>
---
rust/kernel/io.rs | 73 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 73 insertions(+)
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index 9445451f4b02..81b26602d3bc 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -6,6 +6,47 @@
use crate::error::{code::EINVAL, Result};
use crate::{bindings, build_assert};
+use io_backend::*;
+
+/// `io_backend` is private and implements the config specific logic for
+/// `IoAccess::from_raw_cookie`.
+#[cfg(all(CONFIG_X86, CONFIG_GENERIC_IOMAP))]
+mod io_backend {
+ // if on x86, generic_iomap is enabled so copy the logic
+ // from IO_COND in `lib/iomap.c`
+
+ // values copied from `lib/iomap.c`
+ const PIO_OFFSET: usize = 0x10000;
+ const PIO_RESERVED: usize = 0x40000;
+
+ #[inline]
+ pub(super) fn is_mmio(addr: usize) -> bool {
+ addr >= PIO_RESERVED
+ }
+
+ #[inline]
+ pub(super) fn is_portio(addr: usize) -> bool {
+ !is_mmio(addr) && addr > PIO_OFFSET
+ }
+}
+#[cfg(not(CONFIG_GENERIC_IOMAP))]
+mod io_backend {
+ // for everyone who does not use generic iomap
+ // except for alpha and parisc, neither of which has a rust compiler,
+ // ioread/iowrite is defined in `include/asm-generic/io.h`.
+ //
+ // for these ioread/iowrite, maps to read/write.
+ // so allow any io to be converted because they use the same backend
+ #[inline]
+ pub(super) fn is_mmio(_addr: usize) -> bool {
+ true
+ }
+
+ #[inline]
+ pub(super) fn is_portio(_addr: usize) -> bool {
+ false
+ }
+}
/// Private macro to define the [`IoAccess`] functions.
macro_rules! define_io_access_function {
@@ -162,6 +203,14 @@ pub unsafe trait IoAccess<const SIZE: usize = 0> {
/// Returns the base address of the accessed IO area.
fn addr(&self) -> usize;
+ /// Attempts to create a `Self` from a [`IoRaw`].
+ ///
+ /// # Safety
+ /// `raw` should be a io cookie that can be accessed by the C `ioread`/`iowrite` functions
+ unsafe fn from_raw_cookie(raw: IoRaw<SIZE>) -> Result<Self>
+ where
+ Self: Sized;
+
define_io_access_function!(@read
read8_unchecked, read8, try_read8, u8;
read16_unchecked, read16, try_read16, u16;
@@ -366,6 +415,18 @@ fn addr(&self) -> usize {
self.0.addr()
}
+ unsafe fn from_raw_cookie(raw: IoRaw<SIZE>) -> Result<Self>
+ where
+ Self: Sized,
+ {
+ if is_mmio(raw.addr()) {
+ // INVARIANT: `addr` is checked so it should be ok to access with read/write
+ Ok(Self(raw))
+ } else {
+ Err(EINVAL)
+ }
+ }
+
impl_accessor_fn!(
read8_unchecked, readb, write8_unchecked, writeb, u8;
read16_unchecked, readw, write16_unchecked, writew, u16;
@@ -474,6 +535,18 @@ fn maxsize(&self) -> usize {
self.0.maxsize()
}
+ unsafe fn from_raw_cookie(raw: IoRaw<SIZE>) -> Result<Self>
+ where
+ Self: Sized,
+ {
+ if is_mmio(raw.addr()) || is_portio(raw.addr()) {
+ // INVARIANT: `addr` is not touched so it should be able to be read with ioread/iowrite
+ Ok(Self(raw))
+ } else {
+ Err(EINVAL)
+ }
+ }
+
impl_accessor_fn!(
read8_unchecked, ioread8, write8_unchecked, iowrite8, u8;
read16_unchecked, ioread16, write16_unchecked, iowrite16, u16;
--
2.49.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v2 6/6] rust: pci: make Bar generic over Io
2025-05-14 10:57 [PATCH v2 0/6] rust: add support for port io Andrew Ballance
` (4 preceding siblings ...)
2025-05-14 10:57 ` [PATCH v2 5/6] rust: io: add from_raw_cookie functions Andrew Ballance
@ 2025-05-14 10:57 ` Andrew Ballance
2025-06-16 8:03 ` [PATCH v2 0/6] rust: add support for port io Alice Ryhl
6 siblings, 0 replies; 9+ messages in thread
From: Andrew Ballance @ 2025-05-14 10:57 UTC (permalink / raw)
To: dakr, a.hindborg, airlied, akpm, alex.gaynor, aliceryhl,
andrewjballance, andriy.shevchenko, arnd, benno.lossin, bhelgaas,
bjorn3_gh, boqun.feng, daniel.almeida, fujita.tomonori, gary,
gregkh, kwilczynski, me, ojeda, raag.jadav, rafael, simona,
tmgross
Cc: dri-devel, linux-kernel, linux-pci, nouveau, rust-for-linux
renames `Bar` to `RawBar` and makes it generic over `IoAccess`.
a user can give a compile time suggestion when mapping a bar so
that the type of io can be known.
updates nova-core and rust_driver_pci to use new bar api.
Suggested-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Andrew Ballance <andrewjballance@gmail.com>
---
drivers/gpu/nova-core/driver.rs | 4 +-
rust/kernel/pci.rs | 101 +++++++++++++++++++++++++-------
samples/rust/rust_driver_pci.rs | 2 +-
3 files changed, 83 insertions(+), 24 deletions(-)
diff --git a/drivers/gpu/nova-core/driver.rs b/drivers/gpu/nova-core/driver.rs
index a08fb6599267..c03283d1e60e 100644
--- a/drivers/gpu/nova-core/driver.rs
+++ b/drivers/gpu/nova-core/driver.rs
@@ -11,7 +11,7 @@ pub(crate) struct NovaCore {
}
const BAR0_SIZE: usize = 8;
-pub(crate) type Bar0 = pci::Bar<BAR0_SIZE>;
+pub(crate) type Bar0 = pci::MMIoBar<BAR0_SIZE>;
kernel::pci_device_table!(
PCI_TABLE,
@@ -33,7 +33,7 @@ fn probe(pdev: &pci::Device<Core>, _info: &Self::IdInfo) -> Result<Pin<KBox<Self
pdev.enable_device_mem()?;
pdev.set_master();
- let bar = pdev.iomap_region_sized::<BAR0_SIZE>(0, c_str!("nova-core/bar0"))?;
+ let bar = pdev.iomap_region_sized_mmio::<BAR0_SIZE>(0, c_str!("nova-core/bar0"))?;
let this = KBox::pin_init(
try_pin_init!(Self {
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index 9f5ca22d327a..42fbe597b06e 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -11,8 +11,7 @@
devres::Devres,
driver,
error::{to_result, Result},
- io::Io,
- io::IoRaw,
+ io::{Io, IoAccess, IoRaw, MMIo},
str::CStr,
types::{ARef, ForeignOwnable, Opaque},
ThisModule,
@@ -259,15 +258,21 @@ pub struct Device<Ctx: device::DeviceContext = device::Normal>(
///
/// # Invariants
///
-/// `Bar` always holds an `IoRaw` inststance that holds a valid pointer to the start of the I/O
+/// `Bar` always holds an `I` inststance that holds a valid pointer to the start of the I/O
/// memory mapped PCI bar and its size.
-pub struct Bar<const SIZE: usize = 0> {
+pub struct RawBar<const SIZE: usize = 0, I: IoAccess<SIZE> = Io<SIZE>> {
pdev: ARef<Device>,
- io: IoRaw<SIZE>,
+ io: I,
num: i32,
}
-impl<const SIZE: usize> Bar<SIZE> {
+/// a pci bar that can be either PortIo or MMIo
+pub type IoBar<const SIZE: usize = 0> = RawBar<SIZE, Io<SIZE>>;
+
+/// a pci bar that maps a [`MMIo`].
+pub type MMIoBar<const SIZE: usize = 0> = RawBar<SIZE, MMIo<SIZE>>;
+
+impl<const SIZE: usize, I: IoAccess<SIZE>> RawBar<SIZE, I> {
fn new(pdev: &Device, num: u32, name: &CStr) -> Result<Self> {
let len = pdev.resource_len(num)?;
if len == 0 {
@@ -299,7 +304,7 @@ fn new(pdev: &Device, num: u32, name: &CStr) -> Result<Self> {
return Err(ENOMEM);
}
- let io = match IoRaw::new(ioptr, len as usize) {
+ let raw = match IoRaw::new(ioptr, len as usize) {
Ok(io) => io,
Err(err) => {
// SAFETY:
@@ -311,7 +316,22 @@ fn new(pdev: &Device, num: u32, name: &CStr) -> Result<Self> {
}
};
- Ok(Bar {
+ // SAFETY:
+ // - `raw` is from `pci_iomap`
+ // - addresses from `pci_iomap` should be accesed through ioread/iowrite
+ let io = match unsafe { I::from_raw_cookie(raw) } {
+ Ok(io) => io,
+ Err(err) => {
+ // SAFETY:
+ // `pdev` is valid by the invariants of `Device`.
+ // `ioptr` is guaranteed to be the start of a valid I/O mapped memory region.
+ // `num` is checked for validity by a previous call to `Device::resource_len`.
+ unsafe { Self::do_release(pdev, ioptr, num) };
+ return Err(err);
+ }
+ };
+
+ Ok(RawBar {
pdev: pdev.into(),
io,
num,
@@ -338,25 +358,24 @@ fn release(&self) {
}
}
-impl Bar {
+impl RawBar {
fn index_is_valid(index: u32) -> bool {
// A `struct pci_dev` owns an array of resources with at most `PCI_NUM_RESOURCES` entries.
index < bindings::PCI_NUM_RESOURCES
}
}
-impl<const SIZE: usize> Drop for Bar<SIZE> {
+impl<const SIZE: usize, I: IoAccess<SIZE>> Drop for RawBar<SIZE, I> {
fn drop(&mut self) {
self.release();
}
}
-impl<const SIZE: usize> Deref for Bar<SIZE> {
- type Target = Io<SIZE>;
+impl<const SIZE: usize, I: IoAccess<SIZE>> Deref for RawBar<SIZE, I> {
+ type Target = I;
fn deref(&self) -> &Self::Target {
- // SAFETY: By the type invariant of `Self`, the MMIO range in `self.io` is properly mapped.
- unsafe { Io::from_raw_ref(&self.io) }
+ &self.io
}
}
@@ -379,7 +398,7 @@ pub fn device_id(&self) -> u16 {
/// Returns the size of the given PCI bar resource.
pub fn resource_len(&self, bar: u32) -> Result<bindings::resource_size_t> {
- if !Bar::index_is_valid(bar) {
+ if !RawBar::index_is_valid(bar) {
return Err(EINVAL);
}
@@ -389,22 +408,62 @@ pub fn resource_len(&self, bar: u32) -> Result<bindings::resource_size_t> {
Ok(unsafe { bindings::pci_resource_len(self.as_raw(), bar.try_into()?) })
}
- /// Mapps an entire PCI-BAR after performing a region-request on it. I/O operation bound checks
+ /// Maps an entire PCI-BAR after performing a region-request on it. I/O operation bound checks
/// can be performed on compile time for offsets (plus the requested type size) < SIZE.
pub fn iomap_region_sized<const SIZE: usize>(
&self,
bar: u32,
name: &CStr,
- ) -> Result<Devres<Bar<SIZE>>> {
- let bar = Bar::<SIZE>::new(self, bar, name)?;
+ ) -> Result<Devres<IoBar<SIZE>>> {
+ self.iomap_region_sized_hint::<SIZE, Io<SIZE>>(bar, name)
+ }
+
+ /// Maps an entire PCI-BAR after performing a region-request on it.
+ pub fn iomap_region(&self, bar: u32, name: &CStr) -> Result<Devres<IoBar>> {
+ self.iomap_region_sized::<0>(bar, name)
+ }
+
+ /// Maps an entire PCI-BAR after performing a region-request on it. I/O operation bound checks
+ /// can be performed on compile time for offsets (plus the requested type size) < SIZE.
+ /// where it is known that the bar is [`MMIo`]
+ pub fn iomap_region_sized_mmio<const SIZE: usize>(
+ &self,
+ bar: u32,
+ name: &CStr,
+ ) -> Result<Devres<MMIoBar<SIZE>>> {
+ self.iomap_region_sized_hint::<SIZE, MMIo<SIZE>>(bar, name)
+ }
+
+ /// Maps an entire PCI-BAR after performing a region-request on it.
+ /// where it is known that the bar is [`MMIo`]
+ pub fn iomap_region_mmio(&self, bar: u32, name: &CStr) -> Result<Devres<MMIoBar>> {
+ self.iomap_region_sized_hint::<0, MMIo<0>>(bar, name)
+ }
+
+ /// Maps an entire PCI-BAR after performing a region-request where the
+ /// type of Io backend is known at compile time.
+ pub fn iomap_region_hint<I: IoAccess>(
+ &self,
+ bar: u32,
+ name: &CStr,
+ ) -> Result<Devres<RawBar<0, I>>> {
+ let bar = RawBar::<0, I>::new(self, bar, name)?;
let devres = Devres::new(self.as_ref(), bar, GFP_KERNEL)?;
Ok(devres)
}
+ /// Maps an entire PCI-BAR after performing a region-request where the
+ /// type of Io backend is known at compile time. I/O operation bound checks
+ /// can be performed on compile time for offsets (plus the requested type size) < SIZE.
+ pub fn iomap_region_sized_hint<const SIZE: usize, I: IoAccess<SIZE>>(
+ &self,
+ bar: u32,
+ name: &CStr,
+ ) -> Result<Devres<RawBar<SIZE, I>>> {
+ let bar = RawBar::<SIZE, I>::new(self, bar, name)?;
+ let devres = Devres::new(self.as_ref(), bar, GFP_KERNEL)?;
- /// Mapps an entire PCI-BAR after performing a region-request on it.
- pub fn iomap_region(&self, bar: u32, name: &CStr) -> Result<Devres<Bar>> {
- self.iomap_region_sized::<0>(bar, name)
+ Ok(devres)
}
}
diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs
index a8d292f4c1b3..b645155142db 100644
--- a/samples/rust/rust_driver_pci.rs
+++ b/samples/rust/rust_driver_pci.rs
@@ -18,7 +18,7 @@ impl Regs {
const END: usize = 0x10;
}
-type Bar0 = pci::Bar<{ Regs::END }>;
+type Bar0 = pci::IoBar<{ Regs::END }>;
#[derive(Debug)]
struct TestIndex(u8);
--
2.49.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v2 0/6] rust: add support for port io
2025-05-14 10:57 [PATCH v2 0/6] rust: add support for port io Andrew Ballance
` (5 preceding siblings ...)
2025-05-14 10:57 ` [PATCH v2 6/6] rust: pci: make Bar generic over Io Andrew Ballance
@ 2025-06-16 8:03 ` Alice Ryhl
2025-06-16 9:04 ` Danilo Krummrich
6 siblings, 1 reply; 9+ messages in thread
From: Alice Ryhl @ 2025-06-16 8:03 UTC (permalink / raw)
To: Andrew Ballance
Cc: dakr, a.hindborg, airlied, akpm, alex.gaynor, andriy.shevchenko,
arnd, benno.lossin, bhelgaas, bjorn3_gh, boqun.feng,
daniel.almeida, fujita.tomonori, gary, gregkh, kwilczynski, me,
ojeda, raag.jadav, rafael, simona, tmgross, dri-devel,
linux-kernel, linux-pci, nouveau, rust-for-linux
On Wed, May 14, 2025 at 12:58 PM Andrew Ballance
<andrewjballance@gmail.com> wrote:
>
> currently the rust `Io` type maps to the c read{b, w, l, q}/write{b, w, l, q}
> functions and have no support for port io. this can be a problem for pci::Bar
> because the pointer returned by pci_iomap can be either PIO or MMIO [0].
>
> this patch series splits the `Io` type into `Io`, and `MMIo`. `Io` can be
> used to access PIO or MMIO. `MMIo` can only access memory mapped IO but
> might, depending on the arch, be faster than `Io`. and updates pci::Bar,
> so that it is generic over Io and, a user can optionally give a compile
> time hint about the type of io.
>
> Link: https://docs.kernel.org/6.11/driver-api/pci/pci.html#c.pci_iomap [0]
This series seems to try and solve parts of the same problems as
Daniel's patchset:
https://lore.kernel.org/rust-for-linux/20250603-topics-tyr-platform_iomem-v9-0-a27e04157e3e@collabora.com/#r
We should probably align these two patchsets so that they do not add
incompatible abstractions for the same thing.
Alice
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 0/6] rust: add support for port io
2025-06-16 8:03 ` [PATCH v2 0/6] rust: add support for port io Alice Ryhl
@ 2025-06-16 9:04 ` Danilo Krummrich
0 siblings, 0 replies; 9+ messages in thread
From: Danilo Krummrich @ 2025-06-16 9:04 UTC (permalink / raw)
To: Alice Ryhl
Cc: Andrew Ballance, a.hindborg, airlied, akpm, alex.gaynor,
andriy.shevchenko, arnd, benno.lossin, bhelgaas, bjorn3_gh,
boqun.feng, daniel.almeida, fujita.tomonori, gary, gregkh,
kwilczynski, me, ojeda, raag.jadav, rafael, simona, tmgross,
dri-devel, linux-kernel, linux-pci, nouveau, rust-for-linux
On 6/16/25 10:03 AM, Alice Ryhl wrote:
> On Wed, May 14, 2025 at 12:58 PM Andrew Ballance
> <andrewjballance@gmail.com> wrote:
>>
>> currently the rust `Io` type maps to the c read{b, w, l, q}/write{b, w, l, q}
>> functions and have no support for port io. this can be a problem for pci::Bar
>> because the pointer returned by pci_iomap can be either PIO or MMIO [0].
>>
>> this patch series splits the `Io` type into `Io`, and `MMIo`. `Io` can be
>> used to access PIO or MMIO. `MMIo` can only access memory mapped IO but
>> might, depending on the arch, be faster than `Io`. and updates pci::Bar,
>> so that it is generic over Io and, a user can optionally give a compile
>> time hint about the type of io.
>>
>> Link: https://docs.kernel.org/6.11/driver-api/pci/pci.html#c.pci_iomap [0]
>
> This series seems to try and solve parts of the same problems as
> Daniel's patchset:
> https://lore.kernel.org/rust-for-linux/20250603-topics-tyr-platform_iomem-v9-0-a27e04157e3e@collabora.com/#r
>
> We should probably align these two patchsets so that they do not add
> incompatible abstractions for the same thing.
AFAICS, they solve different problems, i.e.
1) Add Port I/O support to the generic I/O abstractions.
2) Add an abstraction for generic ioremap() used to map a struct resource
obtained from a platform device.
The patch series will conflict though, I think it would be best to rebase this
one onto Daniel's patch series, since it is close to land.
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2025-06-16 9:04 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-05-14 10:57 [PATCH v2 0/6] rust: add support for port io Andrew Ballance
2025-05-14 10:57 ` [PATCH v2 1/6] rust: helpers: io: use macro to generate io accessor functions Andrew Ballance
2025-05-14 10:57 ` [PATCH v2 2/6] rust: io: make Io use IoAccess trait Andrew Ballance
2025-05-14 10:57 ` [PATCH v2 3/6] rust: io: add new Io type Andrew Ballance
2025-05-14 10:57 ` [PATCH v2 4/6] rust: io: implement Debug for IoRaw and add some doctests Andrew Ballance
2025-05-14 10:57 ` [PATCH v2 5/6] rust: io: add from_raw_cookie functions Andrew Ballance
2025-05-14 10:57 ` [PATCH v2 6/6] rust: pci: make Bar generic over Io Andrew Ballance
2025-06-16 8:03 ` [PATCH v2 0/6] rust: add support for port io Alice Ryhl
2025-06-16 9:04 ` Danilo Krummrich
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).