Skip to content

Commit 1d0d4b2

Browse files
danielalmeida-collaboraDanilo Krummrich
authored andcommitted
rust: io: mem: add a generic iomem abstraction
Add a generic iomem abstraction to safely read and write ioremapped regions. This abstraction requires a previously acquired IoRequest instance. This makes it so that both the resource and the device match, or, in other words, that the resource is indeed a valid resource for a given bound device. A subsequent patch will add the ability to retrieve IoRequest instances from platform devices. The reads and writes are done through IoRaw, and are thus checked either at compile-time, if the size of the region is known at that point, or at runtime otherwise. Non-exclusive access to the underlying memory region is made possible to cater to cases where overlapped regions are unavoidable. Acked-by: Miguel Ojeda <ojeda@kernel.org> Reviewed-by: Alice Ryhl <aliceryhl@google.com> Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com> Link: https://lore.kernel.org/r/20250717-topics-tyr-platform_iomem-v15-2-beca780b77e3@collabora.com [ Add #[expect(dead_code)] to avoid a temporary warning, remove unnecessary OF_ID_TABLE constants in doc-tests and ignore doc-tests for now to avoid a temporary build failure. - Danilo ] Signed-off-by: Danilo Krummrich <dakr@kernel.org>
1 parent 493fc33 commit 1d0d4b2

3 files changed

Lines changed: 286 additions & 0 deletions

File tree

rust/helpers/io.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ void __iomem *rust_helper_ioremap(phys_addr_t offset, size_t size)
88
return ioremap(offset, size);
99
}
1010

11+
void __iomem *rust_helper_ioremap_np(phys_addr_t offset, size_t size)
12+
{
13+
return ioremap_np(offset, size);
14+
}
15+
1116
void rust_helper_iounmap(void __iomem *addr)
1217
{
1318
iounmap(addr);

rust/kernel/io.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use crate::error::{code::EINVAL, Result};
88
use crate::{bindings, build_assert};
99

10+
pub mod mem;
1011
pub mod resource;
1112

1213
pub use resource::Resource;

rust/kernel/io/mem.rs

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
//! Generic memory-mapped IO.
4+
5+
use core::ops::Deref;
6+
7+
use crate::c_str;
8+
use crate::device::Bound;
9+
use crate::device::Device;
10+
use crate::devres::Devres;
11+
use crate::io;
12+
use crate::io::resource::Region;
13+
use crate::io::resource::Resource;
14+
use crate::io::Io;
15+
use crate::io::IoRaw;
16+
use crate::prelude::*;
17+
18+
/// An IO request for a specific device and resource.
19+
pub struct IoRequest<'a> {
20+
device: &'a Device<Bound>,
21+
resource: &'a Resource,
22+
}
23+
24+
impl<'a> IoRequest<'a> {
25+
/// Creates a new [`IoRequest`] instance.
26+
///
27+
/// # Safety
28+
///
29+
/// Callers must ensure that `resource` is valid for `device` during the
30+
/// lifetime `'a`.
31+
#[expect(dead_code)]
32+
pub(crate) unsafe fn new(device: &'a Device<Bound>, resource: &'a Resource) -> Self {
33+
IoRequest { device, resource }
34+
}
35+
36+
/// Maps an [`IoRequest`] where the size is known at compile time.
37+
///
38+
/// This uses the [`ioremap()`] C API.
39+
///
40+
/// [`ioremap()`]: https://docs.kernel.org/driver-api/device-io.html#getting-access-to-the-device
41+
///
42+
/// # Examples
43+
///
44+
/// The following example uses a [`platform::Device`] for illustration
45+
/// purposes.
46+
///
47+
/// ```ignore
48+
/// use kernel::{bindings, c_str, platform, of, device::Core};
49+
/// struct SampleDriver;
50+
///
51+
/// impl platform::Driver for SampleDriver {
52+
/// # type IdInfo = ();
53+
///
54+
/// fn probe(
55+
/// pdev: &platform::Device<Core>,
56+
/// info: Option<&Self::IdInfo>,
57+
/// ) -> Result<Pin<KBox<Self>>> {
58+
/// let offset = 0; // Some offset.
59+
///
60+
/// // If the size is known at compile time, use [`Self::iomap_sized`].
61+
/// //
62+
/// // No runtime checks will apply when reading and writing.
63+
/// let request = pdev.io_request_by_index(0).ok_or(ENODEV)?;
64+
/// let iomem = request.iomap_sized::<42>();
65+
/// let iomem = KBox::pin_init(iomem, GFP_KERNEL)?;
66+
///
67+
/// let io = iomem.access(pdev.as_ref())?;
68+
///
69+
/// // Read and write a 32-bit value at `offset`.
70+
/// let data = io.read32_relaxed(offset);
71+
///
72+
/// io.write32_relaxed(data, offset);
73+
///
74+
/// # Ok(KBox::new(SampleDriver, GFP_KERNEL)?.into())
75+
/// }
76+
/// }
77+
/// ```
78+
pub fn iomap_sized<const SIZE: usize>(self) -> impl PinInit<Devres<IoMem<SIZE>>, Error> + 'a {
79+
IoMem::new(self)
80+
}
81+
82+
/// Same as [`Self::iomap_sized`] but with exclusive access to the
83+
/// underlying region.
84+
///
85+
/// This uses the [`ioremap()`] C API.
86+
///
87+
/// [`ioremap()`]: https://docs.kernel.org/driver-api/device-io.html#getting-access-to-the-device
88+
pub fn iomap_exclusive_sized<const SIZE: usize>(
89+
self,
90+
) -> impl PinInit<Devres<ExclusiveIoMem<SIZE>>, Error> + 'a {
91+
ExclusiveIoMem::new(self)
92+
}
93+
94+
/// Maps an [`IoRequest`] where the size is not known at compile time,
95+
///
96+
/// This uses the [`ioremap()`] C API.
97+
///
98+
/// [`ioremap()`]: https://docs.kernel.org/driver-api/device-io.html#getting-access-to-the-device
99+
///
100+
/// # Examples
101+
///
102+
/// The following example uses a [`platform::Device`] for illustration
103+
/// purposes.
104+
///
105+
/// ```ignore
106+
/// use kernel::{bindings, c_str, platform, of, device::Core};
107+
/// struct SampleDriver;
108+
///
109+
/// impl platform::Driver for SampleDriver {
110+
/// # type IdInfo = ();
111+
///
112+
/// fn probe(
113+
/// pdev: &platform::Device<Core>,
114+
/// info: Option<&Self::IdInfo>,
115+
/// ) -> Result<Pin<KBox<Self>>> {
116+
/// let offset = 0; // Some offset.
117+
///
118+
/// // Unlike [`Self::iomap_sized`], here the size of the memory region
119+
/// // is not known at compile time, so only the `try_read*` and `try_write*`
120+
/// // family of functions should be used, leading to runtime checks on every
121+
/// // access.
122+
/// let request = pdev.io_request_by_index(0).ok_or(ENODEV)?;
123+
/// let iomem = request.iomap();
124+
/// let iomem = KBox::pin_init(iomem, GFP_KERNEL)?;
125+
///
126+
/// let io = iomem.access(pdev.as_ref())?;
127+
///
128+
/// let data = io.try_read32_relaxed(offset)?;
129+
///
130+
/// io.try_write32_relaxed(data, offset)?;
131+
///
132+
/// # Ok(KBox::new(SampleDriver, GFP_KERNEL)?.into())
133+
/// }
134+
/// }
135+
/// ```
136+
pub fn iomap(self) -> impl PinInit<Devres<IoMem<0>>, Error> + 'a {
137+
Self::iomap_sized::<0>(self)
138+
}
139+
140+
/// Same as [`Self::iomap`] but with exclusive access to the underlying
141+
/// region.
142+
pub fn iomap_exclusive(self) -> impl PinInit<Devres<ExclusiveIoMem<0>>, Error> + 'a {
143+
Self::iomap_exclusive_sized::<0>(self)
144+
}
145+
}
146+
147+
/// An exclusive memory-mapped IO region.
148+
///
149+
/// # Invariants
150+
///
151+
/// - [`ExclusiveIoMem`] has exclusive access to the underlying [`IoMem`].
152+
pub struct ExclusiveIoMem<const SIZE: usize> {
153+
/// The underlying `IoMem` instance.
154+
iomem: IoMem<SIZE>,
155+
156+
/// The region abstraction. This represents exclusive access to the
157+
/// range represented by the underlying `iomem`.
158+
///
159+
/// This field is needed for ownership of the region.
160+
_region: Region,
161+
}
162+
163+
impl<const SIZE: usize> ExclusiveIoMem<SIZE> {
164+
/// Creates a new `ExclusiveIoMem` instance.
165+
fn ioremap(resource: &Resource) -> Result<Self> {
166+
let start = resource.start();
167+
let size = resource.size();
168+
let name = resource.name().unwrap_or(c_str!(""));
169+
170+
let region = resource
171+
.request_region(
172+
start,
173+
size,
174+
name.to_cstring()?,
175+
io::resource::Flags::IORESOURCE_MEM,
176+
)
177+
.ok_or(EBUSY)?;
178+
179+
let iomem = IoMem::ioremap(resource)?;
180+
181+
let iomem = ExclusiveIoMem {
182+
iomem,
183+
_region: region,
184+
};
185+
186+
Ok(iomem)
187+
}
188+
189+
/// Creates a new `ExclusiveIoMem` instance from a previously acquired [`IoRequest`].
190+
pub fn new<'a>(io_request: IoRequest<'a>) -> impl PinInit<Devres<Self>, Error> + 'a {
191+
let dev = io_request.device;
192+
let res = io_request.resource;
193+
194+
Devres::new(dev, Self::ioremap(res))
195+
}
196+
}
197+
198+
impl<const SIZE: usize> Deref for ExclusiveIoMem<SIZE> {
199+
type Target = Io<SIZE>;
200+
201+
fn deref(&self) -> &Self::Target {
202+
&self.iomem
203+
}
204+
}
205+
206+
/// A generic memory-mapped IO region.
207+
///
208+
/// Accesses to the underlying region is checked either at compile time, if the
209+
/// region's size is known at that point, or at runtime otherwise.
210+
///
211+
/// # Invariants
212+
///
213+
/// [`IoMem`] always holds an [`IoRaw`] instance that holds a valid pointer to the
214+
/// start of the I/O memory mapped region.
215+
pub struct IoMem<const SIZE: usize = 0> {
216+
io: IoRaw<SIZE>,
217+
}
218+
219+
impl<const SIZE: usize> IoMem<SIZE> {
220+
fn ioremap(resource: &Resource) -> Result<Self> {
221+
// Note: Some ioremap() implementations use types that depend on the CPU
222+
// word width rather than the bus address width.
223+
//
224+
// TODO: Properly address this in the C code to avoid this `try_into`.
225+
let size = resource.size().try_into()?;
226+
if size == 0 {
227+
return Err(EINVAL);
228+
}
229+
230+
let res_start = resource.start();
231+
232+
let addr = if resource
233+
.flags()
234+
.contains(io::resource::Flags::IORESOURCE_MEM_NONPOSTED)
235+
{
236+
// SAFETY:
237+
// - `res_start` and `size` are read from a presumably valid `struct resource`.
238+
// - `size` is known not to be zero at this point.
239+
unsafe { bindings::ioremap_np(res_start, size) }
240+
} else {
241+
// SAFETY:
242+
// - `res_start` and `size` are read from a presumably valid `struct resource`.
243+
// - `size` is known not to be zero at this point.
244+
unsafe { bindings::ioremap(res_start, size) }
245+
};
246+
247+
if addr.is_null() {
248+
return Err(ENOMEM);
249+
}
250+
251+
let io = IoRaw::new(addr as usize, size)?;
252+
let io = IoMem { io };
253+
254+
Ok(io)
255+
}
256+
257+
/// Creates a new `IoMem` instance from a previously acquired [`IoRequest`].
258+
pub fn new<'a>(io_request: IoRequest<'a>) -> impl PinInit<Devres<Self>, Error> + 'a {
259+
let dev = io_request.device;
260+
let res = io_request.resource;
261+
262+
Devres::new(dev, Self::ioremap(res))
263+
}
264+
}
265+
266+
impl<const SIZE: usize> Drop for IoMem<SIZE> {
267+
fn drop(&mut self) {
268+
// SAFETY: Safe as by the invariant of `Io`.
269+
unsafe { bindings::iounmap(self.io.addr() as *mut c_void) }
270+
}
271+
}
272+
273+
impl<const SIZE: usize> Deref for IoMem<SIZE> {
274+
type Target = Io<SIZE>;
275+
276+
fn deref(&self) -> &Self::Target {
277+
// SAFETY: Safe as by the invariant of `IoMem`.
278+
unsafe { Io::from_raw(&self.io) }
279+
}
280+
}

0 commit comments

Comments
 (0)