summaryrefslogtreecommitdiff
path: root/rust/kernel/io.rs
diff options
context:
space:
mode:
authorDave Airlie <airlied@redhat.com>2026-04-01 07:20:59 +1000
committerDave Airlie <airlied@redhat.com>2026-04-01 07:32:05 +1000
commit9bdbf7eb25b3121ef19533df4fb70f2c39fc0d6a (patch)
tree488e7fbc6301e76c49d975d98ae5b3bfff2ff200 /rust/kernel/io.rs
parent28899037b85e77490f202fa9361c3c2780be3ec2 (diff)
parent7c50d748b4a635bc39802ea3f6b120e66b1b9067 (diff)
Merge tag 'drm-rust-next-2026-03-30' of https://gitlab.freedesktop.org/drm/rust/kernel into drm-next
DRM Rust changes for v7.1-rc1 - DMA: - Rework the DMA coherent API: introduce Coherent<T> as a generalized container for arbitrary types, replacing the slice-only CoherentAllocation<T>. Add CoherentBox for memory initialization before exposing a buffer to hardware (converting to Coherent when ready), and CoherentHandle for allocations without kernel mapping. - Add Coherent::init() / init_with_attrs() for one-shot initialization via pin-init, and from-slice constructors for both Coherent and CoherentBox - Add uaccess write_dma() for copying from DMA buffers to userspace and BinaryWriter support for Coherent<T> - DRM: - Add GPU buddy allocator abstraction - Add DRM shmem GEM helper abstraction - Allow drm::Device to dispatch work and delayed work items to driver private data - Add impl_aref_for_gem_obj!() macro to reduce GEM refcount boilerplate, and introduce DriverObject::Args for constructor context - Add dma_resv_lock helper and raw_dma_resv() accessor on GEM objects - Clean up imports across the DRM module - I/O: - Merged via a signed tag from the driver-core tree: register!() macro and I/O infrastructure improvements (IoCapable refactor, RelaxedMmio wrapper, IoLoc trait, generic accessors, write_reg / LocatedRegister) - Nova (Core): - Fix and harden the GSP command queue: correct write pointer advancing, empty slot handling, and ring buffer indexing; add mutex locking and make Cmdq a pinned type; distinguish wait vs no-wait commands - Add support for large RPCs via continuation records, splitting oversized commands across multiple queue slots - Simplify GSP sequencer and message handling code: remove unused trait and Display impls, derive Debug and Zeroable where applicable, warn on unconsumed message data - Refactor Falcon firmware handling: create DMA objects lazily, add PIO upload support, and use the Generic Bootloader to boot FWSEC on Turing - Convert all register definitions (PMC, PBUS, PFB, GC6, FUSE, PDISP, Falcon) to the kernel register!() macro; add bounded_enum macro to define enums usable as register fields - Migrate all DMA usage to the new Coherent, CoherentBox, and CoherentHandle APIs - Harden firmware parsing with checked arithmetic throughout FWSEC, Booter, RISC-V parsing paths - Add debugfs support for reading GSP-RM log buffers; replace module_pci_driver!() with explicit module init to support module-level debugfs setup - Fix auxiliary device registration for multi-GPU systems - Various cleanups: import style, firmware parsing refactoring, framebuffer size logging - Rust: - Add interop::list module providing a C linked list interface - Extend num::Bounded with shift operations, into_bool(), and const get() to support register bitfield manipulation - Enable the generic_arg_infer Rust feature and add EMSGSIZE error code - Tyr: - Adopt vertical import style per kernel Rust guidelines - Clarify driver/device type names and use DRM device type alias consistently across the driver - Fix GPU model/version decoding in GpuInfo - Workqueue: - Add ARef<T> support for work and delayed work Signed-off-by: Dave Airlie <airlied@redhat.com> From: "Danilo Krummrich" <dakr@kernel.org> Link: https://patch.msgid.link/DHGH4BLT03BU.ZJH5U52WE8BY@kernel.org
Diffstat (limited to 'rust/kernel/io.rs')
-rw-r--r--rust/kernel/io.rs780
1 files changed, 490 insertions, 290 deletions
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index e5fba6bf6db0..fcc7678fd9e3 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -11,10 +11,14 @@ use crate::{
pub mod mem;
pub mod poll;
+pub mod register;
pub mod resource;
+pub use crate::register;
pub use resource::Resource;
+use register::LocatedRegister;
+
/// Physical address type.
///
/// This is a type alias to either `u32` or `u64` depending on the config option
@@ -137,177 +141,6 @@ impl<const SIZE: usize> MmioRaw<SIZE> {
#[repr(transparent)]
pub struct Mmio<const SIZE: usize = 0>(MmioRaw<SIZE>);
-/// Internal helper macros used to invoke C MMIO read functions.
-///
-/// This macro is intended to be used by higher-level MMIO access macros (io_define_read) and
-/// provides a unified expansion for infallible vs. fallible read semantics. It emits a direct call
-/// into the corresponding C helper and performs the required cast to the Rust return type.
-///
-/// # Parameters
-///
-/// * `$c_fn` – The C function performing the MMIO read.
-/// * `$self` – The I/O backend object.
-/// * `$ty` – The type of the value to be read.
-/// * `$addr` – The MMIO address to read.
-///
-/// This macro does not perform any validation; all invariants must be upheld by the higher-level
-/// abstraction invoking it.
-macro_rules! call_mmio_read {
- (infallible, $c_fn:ident, $self:ident, $type:ty, $addr:expr) => {
- // SAFETY: By the type invariant `addr` is a valid address for MMIO operations.
- unsafe { bindings::$c_fn($addr as *const c_void) as $type }
- };
-
- (fallible, $c_fn:ident, $self:ident, $type:ty, $addr:expr) => {{
- // SAFETY: By the type invariant `addr` is a valid address for MMIO operations.
- Ok(unsafe { bindings::$c_fn($addr as *const c_void) as $type })
- }};
-}
-
-/// Internal helper macros used to invoke C MMIO write functions.
-///
-/// This macro is intended to be used by higher-level MMIO access macros (io_define_write) and
-/// provides a unified expansion for infallible vs. fallible write semantics. It emits a direct call
-/// into the corresponding C helper and performs the required cast to the Rust return type.
-///
-/// # Parameters
-///
-/// * `$c_fn` – The C function performing the MMIO write.
-/// * `$self` – The I/O backend object.
-/// * `$ty` – The type of the written value.
-/// * `$addr` – The MMIO address to write.
-/// * `$value` – The value to write.
-///
-/// This macro does not perform any validation; all invariants must be upheld by the higher-level
-/// abstraction invoking it.
-macro_rules! call_mmio_write {
- (infallible, $c_fn:ident, $self:ident, $ty:ty, $addr:expr, $value:expr) => {
- // SAFETY: By the type invariant `addr` is a valid address for MMIO operations.
- unsafe { bindings::$c_fn($value, $addr as *mut c_void) }
- };
-
- (fallible, $c_fn:ident, $self:ident, $ty:ty, $addr:expr, $value:expr) => {{
- // SAFETY: By the type invariant `addr` is a valid address for MMIO operations.
- unsafe { bindings::$c_fn($value, $addr as *mut c_void) };
- Ok(())
- }};
-}
-
-/// Generates an accessor method for reading from an I/O backend.
-///
-/// This macro reduces boilerplate by automatically generating either compile-time bounds-checked
-/// (infallible) or runtime bounds-checked (fallible) read methods. It abstracts the address
-/// calculation and bounds checking, and delegates the actual I/O read operation to a specified
-/// helper macro, making it generic over different I/O backends.
-///
-/// # Parameters
-///
-/// * `infallible` / `fallible` - Determines the bounds-checking strategy. `infallible` relies on
-/// `IoKnownSize` for compile-time checks and returns the value directly. `fallible` performs
-/// runtime checks against `maxsize()` and returns a `Result<T>`.
-/// * `$(#[$attr:meta])*` - Optional attributes to apply to the generated method (e.g.,
-/// `#[cfg(CONFIG_64BIT)]` or inline directives).
-/// * `$vis:vis` - The visibility of the generated method (e.g., `pub`).
-/// * `$name:ident` / `$try_name:ident` - The name of the generated method (e.g., `read32`,
-/// `try_read8`).
-/// * `$call_macro:ident` - The backend-specific helper macro used to emit the actual I/O call
-/// (e.g., `call_mmio_read`).
-/// * `$c_fn:ident` - The backend-specific C function or identifier to be passed into the
-/// `$call_macro`.
-/// * `$type_name:ty` - The Rust type of the value being read (e.g., `u8`, `u32`).
-#[macro_export]
-macro_rules! io_define_read {
- (infallible, $(#[$attr:meta])* $vis:vis $name:ident, $call_macro: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])*
- // Always inline to optimize out error path of `io_addr_assert`.
- #[inline(always)]
- $vis 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 IO operations.
- $call_macro!(infallible, $c_fn, self, $type_name, addr)
- }
- };
-
- (fallible, $(#[$attr:meta])* $vis:vis $try_name:ident, $call_macro:ident($c_fn:ident) ->
- $type_name:ty) => {
- /// 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])*
- $vis 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 IO operations.
- $call_macro!(fallible, $c_fn, self, $type_name, addr)
- }
- };
-}
-pub use io_define_read;
-
-/// Generates an accessor method for writing to an I/O backend.
-///
-/// This macro reduces boilerplate by automatically generating either compile-time bounds-checked
-/// (infallible) or runtime bounds-checked (fallible) write methods. It abstracts the address
-/// calculation and bounds checking, and delegates the actual I/O write operation to a specified
-/// helper macro, making it generic over different I/O backends.
-///
-/// # Parameters
-///
-/// * `infallible` / `fallible` - Determines the bounds-checking strategy. `infallible` relies on
-/// `IoKnownSize` for compile-time checks and returns `()`. `fallible` performs runtime checks
-/// against `maxsize()` and returns a `Result`.
-/// * `$(#[$attr:meta])*` - Optional attributes to apply to the generated method (e.g.,
-/// `#[cfg(CONFIG_64BIT)]` or inline directives).
-/// * `$vis:vis` - The visibility of the generated method (e.g., `pub`).
-/// * `$name:ident` / `$try_name:ident` - The name of the generated method (e.g., `write32`,
-/// `try_write8`).
-/// * `$call_macro:ident` - The backend-specific helper macro used to emit the actual I/O call
-/// (e.g., `call_mmio_write`).
-/// * `$c_fn:ident` - The backend-specific C function or identifier to be passed into the
-/// `$call_macro`.
-/// * `$type_name:ty` - The Rust type of the value being written (e.g., `u8`, `u32`). Note the use
-/// of `<-` before the type to denote a write operation.
-#[macro_export]
-macro_rules! io_define_write {
- (infallible, $(#[$attr:meta])* $vis:vis $name:ident, $call_macro: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])*
- // Always inline to optimize out error path of `io_addr_assert`.
- #[inline(always)]
- $vis fn $name(&self, value: $type_name, offset: usize) {
- let addr = self.io_addr_assert::<$type_name>(offset);
-
- $call_macro!(infallible, $c_fn, self, $type_name, addr, value);
- }
- };
-
- (fallible, $(#[$attr:meta])* $vis:vis $try_name:ident, $call_macro:ident($c_fn:ident) <-
- $type_name:ty) => {
- /// 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])*
- $vis fn $try_name(&self, value: $type_name, offset: usize) -> Result {
- let addr = self.io_addr::<$type_name>(offset)?;
-
- $call_macro!(fallible, $c_fn, self, $type_name, addr, value)
- }
- };
-}
-pub use io_define_write;
-
/// Checks whether an access of type `U` at the given `offset`
/// is valid within this region.
#[inline]
@@ -320,14 +153,74 @@ const fn offset_valid<U>(offset: usize, size: usize) -> bool {
}
}
-/// Marker trait indicating that an I/O backend supports operations of a certain type.
+/// Trait indicating that an I/O backend supports operations of a certain type and providing an
+/// implementation for these operations.
///
/// Different I/O backends can implement this trait to expose only the operations they support.
///
/// For example, a PCI configuration space may implement `IoCapable<u8>`, `IoCapable<u16>`,
/// and `IoCapable<u32>`, but not `IoCapable<u64>`, while an MMIO region on a 64-bit
/// system might implement all four.
-pub trait IoCapable<T> {}
+pub trait IoCapable<T> {
+ /// Performs an I/O read of type `T` at `address` and returns the result.
+ ///
+ /// # Safety
+ ///
+ /// The range `[address..address + size_of::<T>()]` must be within the bounds of `Self`.
+ unsafe fn io_read(&self, address: usize) -> T;
+
+ /// Performs an I/O write of `value` at `address`.
+ ///
+ /// # Safety
+ ///
+ /// The range `[address..address + size_of::<T>()]` must be within the bounds of `Self`.
+ unsafe fn io_write(&self, value: T, address: usize);
+}
+
+/// Describes a given I/O location: its offset, width, and type to convert the raw value from and
+/// into.
+///
+/// This trait is the key abstraction allowing [`Io::read`], [`Io::write`], and [`Io::update`] (and
+/// their fallible [`try_read`](Io::try_read), [`try_write`](Io::try_write) and
+/// [`try_update`](Io::try_update) counterparts) to work uniformly with both raw [`usize`] offsets
+/// (for primitive types like [`u32`]) and typed ones (like those generated by the [`register!`]
+/// macro).
+///
+/// An `IoLoc<T>` carries three pieces of information:
+///
+/// - The offset to access (returned by [`IoLoc::offset`]),
+/// - The width of the access (determined by [`IoLoc::IoType`]),
+/// - The type `T` in which the raw data is returned or provided.
+///
+/// `T` and `IoLoc::IoType` may differ: for instance, a typed register has `T` = the register type
+/// with its bitfields, and `IoType` = its backing primitive (e.g. `u32`).
+pub trait IoLoc<T> {
+ /// Size ([`u8`], [`u16`], etc) of the I/O performed on the returned [`offset`](IoLoc::offset).
+ type IoType: Into<T> + From<T>;
+
+ /// Consumes `self` and returns the offset of this location.
+ fn offset(self) -> usize;
+}
+
+/// Implements [`IoLoc<$ty>`] for [`usize`], allowing [`usize`] to be used as a parameter of
+/// [`Io::read`] and [`Io::write`].
+macro_rules! impl_usize_ioloc {
+ ($($ty:ty),*) => {
+ $(
+ impl IoLoc<$ty> for usize {
+ type IoType = $ty;
+
+ #[inline(always)]
+ fn offset(self) -> usize {
+ self
+ }
+ }
+ )*
+ }
+}
+
+// Provide the ability to read any primitive type from a [`usize`].
+impl_usize_ioloc!(u8, u16, u32, u64);
/// Types implementing this trait (e.g. MMIO BARs or PCI config regions)
/// can perform I/O operations on regions of memory.
@@ -369,146 +262,445 @@ pub trait Io {
/// Fallible 8-bit read with runtime bounds check.
#[inline(always)]
- fn try_read8(&self, _offset: usize) -> Result<u8>
+ fn try_read8(&self, offset: usize) -> Result<u8>
where
Self: IoCapable<u8>,
{
- build_error!("Backend does not support fallible 8-bit read")
+ self.try_read(offset)
}
/// Fallible 16-bit read with runtime bounds check.
#[inline(always)]
- fn try_read16(&self, _offset: usize) -> Result<u16>
+ fn try_read16(&self, offset: usize) -> Result<u16>
where
Self: IoCapable<u16>,
{
- build_error!("Backend does not support fallible 16-bit read")
+ self.try_read(offset)
}
/// Fallible 32-bit read with runtime bounds check.
#[inline(always)]
- fn try_read32(&self, _offset: usize) -> Result<u32>
+ fn try_read32(&self, offset: usize) -> Result<u32>
where
Self: IoCapable<u32>,
{
- build_error!("Backend does not support fallible 32-bit read")
+ self.try_read(offset)
}
/// Fallible 64-bit read with runtime bounds check.
#[inline(always)]
- fn try_read64(&self, _offset: usize) -> Result<u64>
+ fn try_read64(&self, offset: usize) -> Result<u64>
where
Self: IoCapable<u64>,
{
- build_error!("Backend does not support fallible 64-bit read")
+ self.try_read(offset)
}
/// Fallible 8-bit write with runtime bounds check.
#[inline(always)]
- fn try_write8(&self, _value: u8, _offset: usize) -> Result
+ fn try_write8(&self, value: u8, offset: usize) -> Result
where
Self: IoCapable<u8>,
{
- build_error!("Backend does not support fallible 8-bit write")
+ self.try_write(offset, value)
}
/// Fallible 16-bit write with runtime bounds check.
#[inline(always)]
- fn try_write16(&self, _value: u16, _offset: usize) -> Result
+ fn try_write16(&self, value: u16, offset: usize) -> Result
where
Self: IoCapable<u16>,
{
- build_error!("Backend does not support fallible 16-bit write")
+ self.try_write(offset, value)
}
/// Fallible 32-bit write with runtime bounds check.
#[inline(always)]
- fn try_write32(&self, _value: u32, _offset: usize) -> Result
+ fn try_write32(&self, value: u32, offset: usize) -> Result
where
Self: IoCapable<u32>,
{
- build_error!("Backend does not support fallible 32-bit write")
+ self.try_write(offset, value)
}
/// Fallible 64-bit write with runtime bounds check.
#[inline(always)]
- fn try_write64(&self, _value: u64, _offset: usize) -> Result
+ fn try_write64(&self, value: u64, offset: usize) -> Result
where
Self: IoCapable<u64>,
{
- build_error!("Backend does not support fallible 64-bit write")
+ self.try_write(offset, value)
}
/// Infallible 8-bit read with compile-time bounds check.
#[inline(always)]
- fn read8(&self, _offset: usize) -> u8
+ fn read8(&self, offset: usize) -> u8
where
Self: IoKnownSize + IoCapable<u8>,
{
- build_error!("Backend does not support infallible 8-bit read")
+ self.read(offset)
}
/// Infallible 16-bit read with compile-time bounds check.
#[inline(always)]
- fn read16(&self, _offset: usize) -> u16
+ fn read16(&self, offset: usize) -> u16
where
Self: IoKnownSize + IoCapable<u16>,
{
- build_error!("Backend does not support infallible 16-bit read")
+ self.read(offset)
}
/// Infallible 32-bit read with compile-time bounds check.
#[inline(always)]
- fn read32(&self, _offset: usize) -> u32
+ fn read32(&self, offset: usize) -> u32
where
Self: IoKnownSize + IoCapable<u32>,
{
- build_error!("Backend does not support infallible 32-bit read")
+ self.read(offset)
}
/// Infallible 64-bit read with compile-time bounds check.
#[inline(always)]
- fn read64(&self, _offset: usize) -> u64
+ fn read64(&self, offset: usize) -> u64
where
Self: IoKnownSize + IoCapable<u64>,
{
- build_error!("Backend does not support infallible 64-bit read")
+ self.read(offset)
}
/// Infallible 8-bit write with compile-time bounds check.
#[inline(always)]
- fn write8(&self, _value: u8, _offset: usize)
+ fn write8(&self, value: u8, offset: usize)
where
Self: IoKnownSize + IoCapable<u8>,
{
- build_error!("Backend does not support infallible 8-bit write")
+ self.write(offset, value)
}
/// Infallible 16-bit write with compile-time bounds check.
#[inline(always)]
- fn write16(&self, _value: u16, _offset: usize)
+ fn write16(&self, value: u16, offset: usize)
where
Self: IoKnownSize + IoCapable<u16>,
{
- build_error!("Backend does not support infallible 16-bit write")
+ self.write(offset, value)
}
/// Infallible 32-bit write with compile-time bounds check.
#[inline(always)]
- fn write32(&self, _value: u32, _offset: usize)
+ fn write32(&self, value: u32, offset: usize)
where
Self: IoKnownSize + IoCapable<u32>,
{
- build_error!("Backend does not support infallible 32-bit write")
+ self.write(offset, value)
}
/// Infallible 64-bit write with compile-time bounds check.
#[inline(always)]
- fn write64(&self, _value: u64, _offset: usize)
+ fn write64(&self, value: u64, offset: usize)
where
Self: IoKnownSize + IoCapable<u64>,
{
- build_error!("Backend does not support infallible 64-bit write")
+ self.write(offset, value)
+ }
+
+ /// Generic fallible read with runtime bounds check.
+ ///
+ /// # Examples
+ ///
+ /// Read a primitive type from an I/O address:
+ ///
+ /// ```no_run
+ /// use kernel::io::{
+ /// Io,
+ /// Mmio,
+ /// };
+ ///
+ /// fn do_reads(io: &Mmio) -> Result {
+ /// // 32-bit read from address `0x10`.
+ /// let v: u32 = io.try_read(0x10)?;
+ ///
+ /// // 8-bit read from address `0xfff`.
+ /// let v: u8 = io.try_read(0xfff)?;
+ ///
+ /// Ok(())
+ /// }
+ /// ```
+ #[inline(always)]
+ fn try_read<T, L>(&self, location: L) -> Result<T>
+ where
+ L: IoLoc<T>,
+ Self: IoCapable<L::IoType>,
+ {
+ let address = self.io_addr::<L::IoType>(location.offset())?;
+
+ // SAFETY: `address` has been validated by `io_addr`.
+ Ok(unsafe { self.io_read(address) }.into())
+ }
+
+ /// Generic fallible write with runtime bounds check.
+ ///
+ /// # Examples
+ ///
+ /// Write a primitive type to an I/O address:
+ ///
+ /// ```no_run
+ /// use kernel::io::{
+ /// Io,
+ /// Mmio,
+ /// };
+ ///
+ /// fn do_writes(io: &Mmio) -> Result {
+ /// // 32-bit write of value `1` at address `0x10`.
+ /// io.try_write(0x10, 1u32)?;
+ ///
+ /// // 8-bit write of value `0xff` at address `0xfff`.
+ /// io.try_write(0xfff, 0xffu8)?;
+ ///
+ /// Ok(())
+ /// }
+ /// ```
+ #[inline(always)]
+ fn try_write<T, L>(&self, location: L, value: T) -> Result
+ where
+ L: IoLoc<T>,
+ Self: IoCapable<L::IoType>,
+ {
+ let address = self.io_addr::<L::IoType>(location.offset())?;
+ let io_value = value.into();
+
+ // SAFETY: `address` has been validated by `io_addr`.
+ unsafe { self.io_write(io_value, address) }
+
+ Ok(())
+ }
+
+ /// Generic fallible write of a fully-located register value.
+ ///
+ /// # Examples
+ ///
+ /// Tuples carrying a location and a value can be used with this method:
+ ///
+ /// ```no_run
+ /// use kernel::io::{
+ /// register,
+ /// Io,
+ /// Mmio,
+ /// };
+ ///
+ /// register! {
+ /// VERSION(u32) @ 0x100 {
+ /// 15:8 major;
+ /// 7:0 minor;
+ /// }
+ /// }
+ ///
+ /// impl VERSION {
+ /// fn new(major: u8, minor: u8) -> Self {
+ /// VERSION::zeroed().with_major(major).with_minor(minor)
+ /// }
+ /// }
+ ///
+ /// fn do_write_reg(io: &Mmio) -> Result {
+ ///
+ /// io.try_write_reg(VERSION::new(1, 0))
+ /// }
+ /// ```
+ #[inline(always)]
+ fn try_write_reg<T, L, V>(&self, value: V) -> Result
+ where
+ L: IoLoc<T>,
+ V: LocatedRegister<Location = L, Value = T>,
+ Self: IoCapable<L::IoType>,
+ {
+ let (location, value) = value.into_io_op();
+
+ self.try_write(location, value)
+ }
+
+ /// Generic fallible update with runtime bounds check.
+ ///
+ /// Note: this does not perform any synchronization. The caller is responsible for ensuring
+ /// exclusive access if required.
+ ///
+ /// # Examples
+ ///
+ /// Read the u32 value at address `0x10`, increment it, and store the updated value back:
+ ///
+ /// ```no_run
+ /// use kernel::io::{
+ /// Io,
+ /// Mmio,
+ /// };
+ ///
+ /// fn do_update(io: &Mmio<0x1000>) -> Result {
+ /// io.try_update(0x10, |v: u32| {
+ /// v + 1
+ /// })
+ /// }
+ /// ```
+ #[inline(always)]
+ fn try_update<T, L, F>(&self, location: L, f: F) -> Result
+ where
+ L: IoLoc<T>,
+ Self: IoCapable<L::IoType>,
+ F: FnOnce(T) -> T,
+ {
+ let address = self.io_addr::<L::IoType>(location.offset())?;
+
+ // SAFETY: `address` has been validated by `io_addr`.
+ let value: T = unsafe { self.io_read(address) }.into();
+ let io_value = f(value).into();
+
+ // SAFETY: `address` has been validated by `io_addr`.
+ unsafe { self.io_write(io_value, address) }
+
+ Ok(())
+ }
+
+ /// Generic infallible read with compile-time bounds check.
+ ///
+ /// # Examples
+ ///
+ /// Read a primitive type from an I/O address:
+ ///
+ /// ```no_run
+ /// use kernel::io::{
+ /// Io,
+ /// Mmio,
+ /// };
+ ///
+ /// fn do_reads(io: &Mmio<0x1000>) {
+ /// // 32-bit read from address `0x10`.
+ /// let v: u32 = io.read(0x10);
+ ///
+ /// // 8-bit read from the top of the I/O space.
+ /// let v: u8 = io.read(0xfff);
+ /// }
+ /// ```
+ #[inline(always)]
+ fn read<T, L>(&self, location: L) -> T
+ where
+ L: IoLoc<T>,
+ Self: IoKnownSize + IoCapable<L::IoType>,
+ {
+ let address = self.io_addr_assert::<L::IoType>(location.offset());
+
+ // SAFETY: `address` has been validated by `io_addr_assert`.
+ unsafe { self.io_read(address) }.into()
+ }
+
+ /// Generic infallible write with compile-time bounds check.
+ ///
+ /// # Examples
+ ///
+ /// Write a primitive type to an I/O address:
+ ///
+ /// ```no_run
+ /// use kernel::io::{
+ /// Io,
+ /// Mmio,
+ /// };
+ ///
+ /// fn do_writes(io: &Mmio<0x1000>) {
+ /// // 32-bit write of value `1` at address `0x10`.
+ /// io.write(0x10, 1u32);
+ ///
+ /// // 8-bit write of value `0xff` at the top of the I/O space.
+ /// io.write(0xfff, 0xffu8);
+ /// }
+ /// ```
+ #[inline(always)]
+ fn write<T, L>(&self, location: L, value: T)
+ where
+ L: IoLoc<T>,
+ Self: IoKnownSize + IoCapable<L::IoType>,
+ {
+ let address = self.io_addr_assert::<L::IoType>(location.offset());
+ let io_value = value.into();
+
+ // SAFETY: `address` has been validated by `io_addr_assert`.
+ unsafe { self.io_write(io_value, address) }
+ }
+
+ /// Generic infallible write of a fully-located register value.
+ ///
+ /// # Examples
+ ///
+ /// Tuples carrying a location and a value can be used with this method:
+ ///
+ /// ```no_run
+ /// use kernel::io::{
+ /// register,
+ /// Io,
+ /// Mmio,
+ /// };
+ ///
+ /// register! {
+ /// VERSION(u32) @ 0x100 {
+ /// 15:8 major;
+ /// 7:0 minor;
+ /// }
+ /// }
+ ///
+ /// impl VERSION {
+ /// fn new(major: u8, minor: u8) -> Self {
+ /// VERSION::zeroed().with_major(major).with_minor(minor)
+ /// }
+ /// }
+ ///
+ /// fn do_write_reg(io: &Mmio<0x1000>) {
+ /// io.write_reg(VERSION::new(1, 0));
+ /// }
+ /// ```
+ #[inline(always)]
+ fn write_reg<T, L, V>(&self, value: V)
+ where
+ L: IoLoc<T>,
+ V: LocatedRegister<Location = L, Value = T>,
+ Self: IoKnownSize + IoCapable<L::IoType>,
+ {
+ let (location, value) = value.into_io_op();
+
+ self.write(location, value)
+ }
+
+ /// Generic infallible update with compile-time bounds check.
+ ///
+ /// Note: this does not perform any synchronization. The caller is responsible for ensuring
+ /// exclusive access if required.
+ ///
+ /// # Examples
+ ///
+ /// Read the u32 value at address `0x10`, increment it, and store the updated value back:
+ ///
+ /// ```no_run
+ /// use kernel::io::{
+ /// Io,
+ /// Mmio,
+ /// };
+ ///
+ /// fn do_update(io: &Mmio<0x1000>) {
+ /// io.update(0x10, |v: u32| {
+ /// v + 1
+ /// })
+ /// }
+ /// ```
+ #[inline(always)]
+ fn update<T, L, F>(&self, location: L, f: F)
+ where
+ L: IoLoc<T>,
+ Self: IoKnownSize + IoCapable<L::IoType> + Sized,
+ F: FnOnce(T) -> T,
+ {
+ let address = self.io_addr_assert::<L::IoType>(location.offset());
+
+ // SAFETY: `address` has been validated by `io_addr_assert`.
+ let value: T = unsafe { self.io_read(address) }.into();
+ let io_value = f(value).into();
+
+ // SAFETY: `address` has been validated by `io_addr_assert`.
+ unsafe { self.io_write(io_value, address) }
}
}
@@ -534,14 +726,36 @@ pub trait IoKnownSize: Io {
}
}
-// MMIO regions support 8, 16, and 32-bit accesses.
-impl<const SIZE: usize> IoCapable<u8> for Mmio<SIZE> {}
-impl<const SIZE: usize> IoCapable<u16> for Mmio<SIZE> {}
-impl<const SIZE: usize> IoCapable<u32> for Mmio<SIZE> {}
+/// Implements [`IoCapable`] on `$mmio` for `$ty` using `$read_fn` and `$write_fn`.
+macro_rules! impl_mmio_io_capable {
+ ($mmio:ident, $(#[$attr:meta])* $ty:ty, $read_fn:ident, $write_fn:ident) => {
+ $(#[$attr])*
+ impl<const SIZE: usize> IoCapable<$ty> for $mmio<SIZE> {
+ unsafe fn io_read(&self, address: usize) -> $ty {
+ // SAFETY: By the trait invariant `address` is a valid address for MMIO operations.
+ unsafe { bindings::$read_fn(address as *const c_void) }
+ }
+
+ unsafe fn io_write(&self, value: $ty, address: usize) {
+ // SAFETY: By the trait invariant `address` is a valid address for MMIO operations.
+ unsafe { bindings::$write_fn(value, address as *mut c_void) }
+ }
+ }
+ };
+}
+// MMIO regions support 8, 16, and 32-bit accesses.
+impl_mmio_io_capable!(Mmio, u8, readb, writeb);
+impl_mmio_io_capable!(Mmio, u16, readw, writew);
+impl_mmio_io_capable!(Mmio, u32, readl, writel);
// MMIO regions on 64-bit systems also support 64-bit accesses.
-#[cfg(CONFIG_64BIT)]
-impl<const SIZE: usize> IoCapable<u64> for Mmio<SIZE> {}
+impl_mmio_io_capable!(
+ Mmio,
+ #[cfg(CONFIG_64BIT)]
+ u64,
+ readq,
+ writeq
+);
impl<const SIZE: usize> Io for Mmio<SIZE> {
/// Returns the base address of this mapping.
@@ -555,46 +769,6 @@ impl<const SIZE: usize> Io for Mmio<SIZE> {
fn maxsize(&self) -> usize {
self.0.maxsize()
}
-
- io_define_read!(fallible, try_read8, call_mmio_read(readb) -> u8);
- io_define_read!(fallible, try_read16, call_mmio_read(readw) -> u16);
- io_define_read!(fallible, try_read32, call_mmio_read(readl) -> u32);
- io_define_read!(
- fallible,
- #[cfg(CONFIG_64BIT)]
- try_read64,
- call_mmio_read(readq) -> u64
- );
-
- io_define_write!(fallible, try_write8, call_mmio_write(writeb) <- u8);
- io_define_write!(fallible, try_write16, call_mmio_write(writew) <- u16);
- io_define_write!(fallible, try_write32, call_mmio_write(writel) <- u32);
- io_define_write!(
- fallible,
- #[cfg(CONFIG_64BIT)]
- try_write64,
- call_mmio_write(writeq) <- u64
- );
-
- io_define_read!(infallible, read8, call_mmio_read(readb) -> u8);
- io_define_read!(infallible, read16, call_mmio_read(readw) -> u16);
- io_define_read!(infallible, read32, call_mmio_read(readl) -> u32);
- io_define_read!(
- infallible,
- #[cfg(CONFIG_64BIT)]
- read64,
- call_mmio_read(readq) -> u64
- );
-
- io_define_write!(infallible, write8, call_mmio_write(writeb) <- u8);
- io_define_write!(infallible, write16, call_mmio_write(writew) <- u16);
- io_define_write!(infallible, write32, call_mmio_write(writel) <- u32);
- io_define_write!(
- infallible,
- #[cfg(CONFIG_64BIT)]
- write64,
- call_mmio_write(writeq) <- u64
- );
}
impl<const SIZE: usize> IoKnownSize for Mmio<SIZE> {
@@ -612,44 +786,70 @@ impl<const SIZE: usize> Mmio<SIZE> {
// SAFETY: `Mmio` is a transparent wrapper around `MmioRaw`.
unsafe { &*core::ptr::from_ref(raw).cast() }
}
+}
+
+/// [`Mmio`] wrapper using relaxed accessors.
+///
+/// This type provides an implementation of [`Io`] that uses relaxed I/O MMIO operands instead of
+/// the regular ones.
+///
+/// See [`Mmio::relaxed`] for a usage example.
+#[repr(transparent)]
+pub struct RelaxedMmio<const SIZE: usize = 0>(Mmio<SIZE>);
+
+impl<const SIZE: usize> Io for RelaxedMmio<SIZE> {
+ #[inline]
+ fn addr(&self) -> usize {
+ self.0.addr()
+ }
- io_define_read!(infallible, pub read8_relaxed, call_mmio_read(readb_relaxed) -> u8);
- io_define_read!(infallible, pub read16_relaxed, call_mmio_read(readw_relaxed) -> u16);
- io_define_read!(infallible, pub read32_relaxed, call_mmio_read(readl_relaxed) -> u32);
- io_define_read!(
- infallible,
- #[cfg(CONFIG_64BIT)]
- pub read64_relaxed,
- call_mmio_read(readq_relaxed) -> u64
- );
-
- io_define_read!(fallible, pub try_read8_relaxed, call_mmio_read(readb_relaxed) -> u8);
- io_define_read!(fallible, pub try_read16_relaxed, call_mmio_read(readw_relaxed) -> u16);
- io_define_read!(fallible, pub try_read32_relaxed, call_mmio_read(readl_relaxed) -> u32);
- io_define_read!(
- fallible,
- #[cfg(CONFIG_64BIT)]
- pub try_read64_relaxed,
- call_mmio_read(readq_relaxed) -> u64
- );
-
- io_define_write!(infallible, pub write8_relaxed, call_mmio_write(writeb_relaxed) <- u8);
- io_define_write!(infallible, pub write16_relaxed, call_mmio_write(writew_relaxed) <- u16);
- io_define_write!(infallible, pub write32_relaxed, call_mmio_write(writel_relaxed) <- u32);
- io_define_write!(
- infallible,
- #[cfg(CONFIG_64BIT)]
- pub write64_relaxed,
- call_mmio_write(writeq_relaxed) <- u64
- );
-
- io_define_write!(fallible, pub try_write8_relaxed, call_mmio_write(writeb_relaxed) <- u8);
- io_define_write!(fallible, pub try_write16_relaxed, call_mmio_write(writew_relaxed) <- u16);
- io_define_write!(fallible, pub try_write32_relaxed, call_mmio_write(writel_relaxed) <- u32);
- io_define_write!(
- fallible,
- #[cfg(CONFIG_64BIT)]
- pub try_write64_relaxed,
- call_mmio_write(writeq_relaxed) <- u64
- );
+ #[inline]
+ fn maxsize(&self) -> usize {
+ self.0.maxsize()
+ }
+}
+
+impl<const SIZE: usize> IoKnownSize for RelaxedMmio<SIZE> {
+ const MIN_SIZE: usize = SIZE;
}
+
+impl<const SIZE: usize> Mmio<SIZE> {
+ /// Returns a [`RelaxedMmio`] reference that performs relaxed I/O operations.
+ ///
+ /// Relaxed accessors do not provide ordering guarantees with respect to DMA or memory accesses
+ /// and can be used when such ordering is not required.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use kernel::io::{
+ /// Io,
+ /// Mmio,
+ /// RelaxedMmio,
+ /// };
+ ///
+ /// fn do_io(io: &Mmio<0x100>) {
+ /// // The access is performed using `readl_relaxed` instead of `readl`.
+ /// let v = io.relaxed().read32(0x10);
+ /// }
+ ///
+ /// ```
+ pub fn relaxed(&self) -> &RelaxedMmio<SIZE> {
+ // SAFETY: `RelaxedMmio` is `#[repr(transparent)]` over `Mmio`, so `Mmio<SIZE>` and
+ // `RelaxedMmio<SIZE>` have identical layout.
+ unsafe { core::mem::transmute(self) }
+ }
+}
+
+// MMIO regions support 8, 16, and 32-bit accesses.
+impl_mmio_io_capable!(RelaxedMmio, u8, readb_relaxed, writeb_relaxed);
+impl_mmio_io_capable!(RelaxedMmio, u16, readw_relaxed, writew_relaxed);
+impl_mmio_io_capable!(RelaxedMmio, u32, readl_relaxed, writel_relaxed);
+// MMIO regions on 64-bit systems also support 64-bit accesses.
+impl_mmio_io_capable!(
+ RelaxedMmio,
+ #[cfg(CONFIG_64BIT)]
+ u64,
+ readq_relaxed,
+ writeq_relaxed
+);