Files
Danilo Krummrich b07fc8d60b Merge patch series "rust: I/O type generalization and projection"
Gary Guo <gary@garyguo.net> says:

This series presents a major rework of I/O types, as a summary:

- Make I/O regions typed. The existing untyped region still exists
  with a dynamically sized `Region` type.

- Create I/O view types to represent subregion of a full I/O region mapped.
  A projection macro is added to allow safely create such subviews.

- Split I/O traits, make I/O views play a central role, avoid
  duplicate monomorphization and less `unsafe` code.

- Add a `SysMem` backend, and make `Coherent` implement `Io`.

- Add copying methods (memcpy_{from,to}io and friends).

This series generalize `Mmio` type from just an untyped region to typed
representations (so `MmioRaw<T>` is `__iomem *T`). This allows us to remove
the `IoKnownSize` trait; the information is sourced from just the pointer
from the `KnownSize` trait instead.

Building on top of that, `Mmio` and `ConfigSpace` have been converted to
typed views of I/O regions rather than just a big chunk of untyped I/O
memory. These changes made it possible to implement `Io` trait for
`Coherent<T>`.

Shared system memory, `SysMem` is also added to the series, given it
similarity in implementation compared to `Coherent`. In fact, the series
use `SysMem` to implement `Coherent`'s I/O methods.

Built on these generalization, this series add `io_project!()`.
`io_project!()` performs a safe way to project a bigger view to a small
subviews, and some Nova code has been converted in this series to
demonstrate cleanups possible with this addition.

New `io_read!()`, `io_write!()` has been added that supersedes
`dma_read!()`, `dma_write!()` macro. Although, they work for primitives
only (to be exact, types that the backend is `IoCapable` of).
One feature that was lost from the old `dma_read!()` and `dma_write!()`
series was the ability to read/write a large structs. However, the
semantics was unclear to begin with, as there was no guarantee about their
atomicity even for structs that were small enough to fit in u32.

Suggested-by: Danilo Krummrich <dakr@kernel.org>
Link: https://rust-for-linux.zulipchat.com/#narrow/channel/288089-General/topic/Generic.20I.2FO.20backends/near/571198078
Link: https://patch.msgid.link/20260706-io_projection-v6-0-72cd5d055d54@garyguo.net
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
2026-07-13 23:50:39 +02:00

330 lines
10 KiB
Rust

// SPDX-License-Identifier: GPL-2.0
//! Generic memory-mapped IO.
use crate::{
device::{
Bound,
Device, //
},
devres::DevresLt,
io::{
self,
resource::{
Region,
Resource, //
},
IoBase,
Mmio,
MmioBackend,
MmioRaw, //
},
prelude::*,
types::{
CovariantForLt,
ForLt, //
},
};
/// An IO request for a specific device and resource.
pub struct IoRequest<'a> {
device: &'a Device<Bound>,
resource: &'a Resource,
}
impl<'a> IoRequest<'a> {
/// Creates a new [`IoRequest`] instance.
///
/// # Safety
///
/// Callers must ensure that `resource` is valid for `device` during the
/// lifetime `'a`.
pub(crate) unsafe fn new(device: &'a Device<Bound>, resource: &'a Resource) -> Self {
IoRequest { device, resource }
}
/// Maps an [`IoRequest`] where the size is known at compile time.
///
/// This uses the [`ioremap()`] C API.
///
/// [`ioremap()`]: https://docs.kernel.org/driver-api/device-io.html#getting-access-to-the-device
///
/// # Examples
///
/// The following example uses a [`kernel::platform::Device`] for
/// illustration purposes.
///
/// ```no_run
/// use kernel::{
/// bindings,
/// device::Core,
/// io::Io,
/// of,
/// platform,
/// };
/// struct SampleDriver;
///
/// impl platform::Driver for SampleDriver {
/// # type IdInfo = ();
/// # type Data<'bound> = Self;
///
/// fn probe<'bound>(
/// pdev: &'bound platform::Device<Core<'_>>,
/// info: Option<&'bound Self::IdInfo>,
/// ) -> impl PinInit<Self, Error> + 'bound {
/// let offset = 0; // Some offset.
///
/// // If the size is known at compile time, use [`Self::iomap_sized`].
/// //
/// // No runtime checks will apply when reading and writing.
/// let request = pdev.io_request_by_index(0).ok_or(ENODEV)?;
/// let iomem = request.iomap_sized::<42>()?;
///
/// // Read and write a 32-bit value at `offset`.
/// let data = iomem.read32(offset);
///
/// iomem.write32(data, offset);
///
/// # Ok(SampleDriver)
/// }
/// }
/// ```
pub fn iomap_sized<const SIZE: usize>(self) -> Result<IoMem<'a, SIZE>> {
IoMem::ioremap(self.device, self.resource)
}
/// Same as [`Self::iomap_sized`] but with exclusive access to the
/// underlying region.
///
/// This uses the [`ioremap()`] C API.
///
/// [`ioremap()`]: https://docs.kernel.org/driver-api/device-io.html#getting-access-to-the-device
pub fn iomap_exclusive_sized<const SIZE: usize>(self) -> Result<ExclusiveIoMem<'a, SIZE>> {
ExclusiveIoMem::ioremap(self.device, self.resource)
}
/// Maps an [`IoRequest`] where the size is not known at compile time,
///
/// This uses the [`ioremap()`] C API.
///
/// [`ioremap()`]: https://docs.kernel.org/driver-api/device-io.html#getting-access-to-the-device
///
/// # Examples
///
/// The following example uses a [`kernel::platform::Device`] for
/// illustration purposes.
///
/// ```no_run
/// use kernel::{
/// bindings,
/// device::Core,
/// io::Io,
/// of,
/// platform,
/// };
/// struct SampleDriver;
///
/// impl platform::Driver for SampleDriver {
/// # type IdInfo = ();
/// # type Data<'bound> = Self;
///
/// fn probe<'bound>(
/// pdev: &'bound platform::Device<Core<'_>>,
/// info: Option<&'bound Self::IdInfo>,
/// ) -> impl PinInit<Self, Error> + 'bound {
/// let offset = 0; // Some offset.
///
/// // Unlike [`Self::iomap_sized`], here the size of the memory region
/// // is not known at compile time, so only the `try_read*` and `try_write*`
/// // family of functions should be used, leading to runtime checks on every
/// // access.
/// let request = pdev.io_request_by_index(0).ok_or(ENODEV)?;
/// let iomem = request.iomap()?;
///
/// let data = iomem.try_read32(offset)?;
///
/// iomem.try_write32(data, offset)?;
///
/// # Ok(SampleDriver)
/// }
/// }
/// ```
pub fn iomap(self) -> Result<IoMem<'a>> {
self.iomap_sized::<0>()
}
/// Same as [`Self::iomap`] but with exclusive access to the underlying
/// region.
pub fn iomap_exclusive(self) -> Result<ExclusiveIoMem<'a, 0>> {
self.iomap_exclusive_sized::<0>()
}
}
/// An exclusive memory-mapped IO region.
///
/// # Invariants
///
/// - [`ExclusiveIoMem`] has exclusive access to the underlying [`IoMem`].
pub struct ExclusiveIoMem<'a, const SIZE: usize> {
/// The underlying `IoMem` instance.
iomem: IoMem<'a, SIZE>,
/// The region abstraction. This represents exclusive access to the
/// range represented by the underlying `iomem`.
///
/// This field is needed for ownership of the region.
_region: Region,
}
impl<const SIZE: usize> ForLt for ExclusiveIoMem<'static, SIZE> {
type Of<'a> = ExclusiveIoMem<'a, SIZE>;
}
// SAFETY: `ExclusiveIoMem<'a, SIZE>` is covariant over `'a`; it holds an `IoMem<'a, SIZE>`,
// which holds `&'a Device<Bound>`, which is covariant.
unsafe impl<const SIZE: usize> CovariantForLt for ExclusiveIoMem<'static, SIZE> {}
/// A device-managed exclusive I/O memory region.
///
/// See [`ExclusiveIoMem::into_devres`].
pub type DevresExclusiveIoMem<const SIZE: usize> = DevresLt<ExclusiveIoMem<'static, SIZE>>;
impl<'a, const SIZE: usize> ExclusiveIoMem<'a, SIZE> {
/// Creates a new `ExclusiveIoMem` instance.
fn ioremap(dev: &'a Device<Bound>, resource: &Resource) -> Result<Self> {
let start = resource.start();
let size = resource.size();
let name = resource.name().unwrap_or_default();
let region = resource
.request_region(
start,
size,
name.to_cstring()?,
io::resource::Flags::IORESOURCE_MEM,
)
.ok_or(EBUSY)?;
let iomem = IoMem::ioremap(dev, resource)?;
Ok(ExclusiveIoMem {
iomem,
_region: region,
})
}
/// Consume the `ExclusiveIoMem` and register it as a device-managed resource.
///
/// The returned [`DevresExclusiveIoMem`] can outlive the original borrow and be stored in
/// driver data. Access to the I/O memory is revoked automatically when the device is unbound.
pub fn into_devres(self) -> Result<DevresExclusiveIoMem<SIZE>> {
let dev = self.iomem.dev;
// SAFETY: `ExclusiveIoMem` only holds a device reference and an I/O mapping, both of
// which remain valid for the device's full bound scope, not just for `'a`.
unsafe { DevresLt::new(dev, self) }
}
}
impl<'a, const SIZE: usize> IoBase<'a> for &'a ExclusiveIoMem<'_, SIZE> {
type Backend = MmioBackend;
type Target = super::Region<SIZE>;
#[inline]
fn as_view(self) -> Mmio<'a, Self::Target> {
self.iomem.as_view()
}
}
/// A generic memory-mapped IO region.
///
/// Accesses to the underlying region is checked either at compile time, if the
/// region's size is known at that point, or at runtime otherwise.
///
/// # Invariants
///
/// [`IoMem`] always holds an [`MmioRaw`] instance that holds a valid pointer to the
/// start of the I/O memory mapped region.
pub struct IoMem<'a, const SIZE: usize = 0> {
dev: &'a Device<Bound>,
io: MmioRaw<super::Region<SIZE>>,
}
impl<const SIZE: usize> ForLt for IoMem<'static, SIZE> {
type Of<'a> = IoMem<'a, SIZE>;
}
// SAFETY: `IoMem<'a, SIZE>` is covariant over `'a`; it holds `&'a Device<Bound>`,
// which is covariant.
unsafe impl<const SIZE: usize> CovariantForLt for IoMem<'static, SIZE> {}
/// A device-managed I/O memory region.
///
/// See [`IoMem::into_devres`].
pub type DevresIoMem<const SIZE: usize = 0> = DevresLt<IoMem<'static, SIZE>>;
impl<'a, const SIZE: usize> IoMem<'a, SIZE> {
fn ioremap(dev: &'a Device<Bound>, resource: &Resource) -> Result<Self> {
// Note: Some ioremap() implementations use types that depend on the CPU
// word width rather than the bus address width.
//
// TODO: Properly address this in the C code to avoid this `try_into`.
let size = resource.size().try_into()?;
if size == 0 {
return Err(EINVAL);
}
let res_start = resource.start();
let addr = if resource
.flags()
.contains(io::resource::Flags::IORESOURCE_MEM_NONPOSTED)
{
// SAFETY:
// - `res_start` and `size` are read from a presumably valid `struct resource`.
// - `size` is known not to be zero at this point.
unsafe { bindings::ioremap_np(res_start, size) }
} else {
// SAFETY:
// - `res_start` and `size` are read from a presumably valid `struct resource`.
// - `size` is known not to be zero at this point.
unsafe { bindings::ioremap(res_start, size) }
};
if addr.is_null() {
return Err(ENOMEM);
}
let io = MmioRaw::new_region(addr as usize, size)?;
Ok(IoMem { dev, io })
}
/// Consume the `IoMem` and register it as a device-managed resource.
///
/// The returned [`DevresIoMem`] can outlive the original borrow and be stored in driver data.
/// Access to the I/O memory is revoked automatically when the device is unbound.
pub fn into_devres(self) -> Result<DevresIoMem<SIZE>> {
let dev = self.dev;
// SAFETY: `IoMem` only holds a device reference and an I/O mapping, both of which
// remain valid for the device's full bound scope, not just for `'a`.
unsafe { DevresLt::new(dev, self) }
}
}
impl<const SIZE: usize> Drop for IoMem<'_, SIZE> {
fn drop(&mut self) {
// SAFETY: Safe as by the invariant of `Io`.
unsafe { bindings::iounmap(self.io.addr() as *mut c_void) }
}
}
impl<'a, const SIZE: usize> IoBase<'a> for &'a IoMem<'_, SIZE> {
type Backend = MmioBackend;
type Target = super::Region<SIZE>;
#[inline]
fn as_view(self) -> Mmio<'a, Self::Target> {
// SAFETY: Safe as by the invariant of `IoMem`.
unsafe { Mmio::from_raw(self.io) }
}
}