linux/rust/kernel/io/mem.rs
Miguel Ojeda 51a486feac rust: io: fix broken intra-doc links to platform::Device
`platform` is not accessible from here.

Thus fix the intra-doc links by qualifying the paths a bit more.

Fixes: 1d0d4b2851 ("rust: io: mem: add a generic iomem abstraction")
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
Link: https://lore.kernel.org/r/20250722085500.1360401-2-ojeda@kernel.org
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
2025-07-22 11:08:59 +02:00

279 lines
8.6 KiB
Rust

// SPDX-License-Identifier: GPL-2.0
//! Generic memory-mapped IO.
use core::ops::Deref;
use crate::c_str;
use crate::device::Bound;
use crate::device::Device;
use crate::devres::Devres;
use crate::io;
use crate::io::resource::Region;
use crate::io::resource::Resource;
use crate::io::Io;
use crate::io::IoRaw;
use crate::prelude::*;
/// 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, c_str, platform, of, device::Core};
/// struct SampleDriver;
///
/// impl platform::Driver for SampleDriver {
/// # type IdInfo = ();
///
/// fn probe(
/// pdev: &platform::Device<Core>,
/// info: Option<&Self::IdInfo>,
/// ) -> Result<Pin<KBox<Self>>> {
/// 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>();
/// let iomem = KBox::pin_init(iomem, GFP_KERNEL)?;
///
/// let io = iomem.access(pdev.as_ref())?;
///
/// // Read and write a 32-bit value at `offset`.
/// let data = io.read32_relaxed(offset);
///
/// io.write32_relaxed(data, offset);
///
/// # Ok(KBox::new(SampleDriver, GFP_KERNEL)?.into())
/// }
/// }
/// ```
pub fn iomap_sized<const SIZE: usize>(self) -> impl PinInit<Devres<IoMem<SIZE>>, Error> + 'a {
IoMem::new(self)
}
/// 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,
) -> impl PinInit<Devres<ExclusiveIoMem<SIZE>>, Error> + 'a {
ExclusiveIoMem::new(self)
}
/// 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, c_str, platform, of, device::Core};
/// struct SampleDriver;
///
/// impl platform::Driver for SampleDriver {
/// # type IdInfo = ();
///
/// fn probe(
/// pdev: &platform::Device<Core>,
/// info: Option<&Self::IdInfo>,
/// ) -> Result<Pin<KBox<Self>>> {
/// 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 iomem = KBox::pin_init(iomem, GFP_KERNEL)?;
///
/// let io = iomem.access(pdev.as_ref())?;
///
/// let data = io.try_read32_relaxed(offset)?;
///
/// io.try_write32_relaxed(data, offset)?;
///
/// # Ok(KBox::new(SampleDriver, GFP_KERNEL)?.into())
/// }
/// }
/// ```
pub fn iomap(self) -> impl PinInit<Devres<IoMem<0>>, Error> + 'a {
Self::iomap_sized::<0>(self)
}
/// Same as [`Self::iomap`] but with exclusive access to the underlying
/// region.
pub fn iomap_exclusive(self) -> impl PinInit<Devres<ExclusiveIoMem<0>>, Error> + 'a {
Self::iomap_exclusive_sized::<0>(self)
}
}
/// An exclusive memory-mapped IO region.
///
/// # Invariants
///
/// - [`ExclusiveIoMem`] has exclusive access to the underlying [`IoMem`].
pub struct ExclusiveIoMem<const SIZE: usize> {
/// The underlying `IoMem` instance.
iomem: IoMem<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> ExclusiveIoMem<SIZE> {
/// Creates a new `ExclusiveIoMem` instance.
fn ioremap(resource: &Resource) -> Result<Self> {
let start = resource.start();
let size = resource.size();
let name = resource.name().unwrap_or(c_str!(""));
let region = resource
.request_region(
start,
size,
name.to_cstring()?,
io::resource::Flags::IORESOURCE_MEM,
)
.ok_or(EBUSY)?;
let iomem = IoMem::ioremap(resource)?;
let iomem = ExclusiveIoMem {
iomem,
_region: region,
};
Ok(iomem)
}
/// Creates a new `ExclusiveIoMem` instance from a previously acquired [`IoRequest`].
pub fn new<'a>(io_request: IoRequest<'a>) -> impl PinInit<Devres<Self>, Error> + 'a {
let dev = io_request.device;
let res = io_request.resource;
Devres::new(dev, Self::ioremap(res))
}
}
impl<const SIZE: usize> Deref for ExclusiveIoMem<SIZE> {
type Target = Io<SIZE>;
fn deref(&self) -> &Self::Target {
&self.iomem
}
}
/// 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 [`IoRaw`] instance that holds a valid pointer to the
/// start of the I/O memory mapped region.
pub struct IoMem<const SIZE: usize = 0> {
io: IoRaw<SIZE>,
}
impl<const SIZE: usize> IoMem<SIZE> {
fn ioremap(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 = IoRaw::new(addr as usize, size)?;
let io = IoMem { io };
Ok(io)
}
/// Creates a new `IoMem` instance from a previously acquired [`IoRequest`].
pub fn new<'a>(io_request: IoRequest<'a>) -> impl PinInit<Devres<Self>, Error> + 'a {
let dev = io_request.device;
let res = io_request.resource;
Devres::new(dev, Self::ioremap(res))
}
}
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<const SIZE: usize> Deref for IoMem<SIZE> {
type Target = Io<SIZE>;
fn deref(&self) -> &Self::Target {
// SAFETY: Safe as by the invariant of `IoMem`.
unsafe { Io::from_raw(&self.io) }
}
}