Skip to content

Commit 41bf8ed

Browse files
dakrDanilo Krummrich
authored andcommitted
rust: add io::Io base type
I/O memory is typically either mapped through direct calls to ioremap() or subsystem / bus specific ones such as pci_iomap(). Even though subsystem / bus specific functions to map I/O memory are based on ioremap() / iounmap() it is not desirable to re-implement them in Rust. Instead, implement a base type for I/O mapped memory, which generically provides the corresponding accessors, such as `Io::readb` or `Io:try_readb`. `Io` supports an optional const generic, such that a driver can indicate the minimal expected and required size of the mapping at compile time. Correspondingly, calls to the 'non-try' accessors, support compile time checks of the I/O memory offset to read / write, while the 'try' accessors, provide boundary checks on runtime. `Io` is meant to be embedded into a structure (e.g. pci::Bar or io::IoMem) which creates the actual I/O memory mapping and initializes `Io` accordingly. To ensure that I/O mapped memory can't out-live the device it may be bound to, subsystems should embedd the corresponding I/O memory type (e.g. pci::Bar) into a `Devres` container, such that it gets revoked once the device is unbound. Co-developed-by: Philipp Stanner <[email protected]> Signed-off-by: Philipp Stanner <[email protected]> Signed-off-by: Danilo Krummrich <[email protected]>
1 parent bbdc4bc commit 41bf8ed

File tree

3 files changed

+326
-0
lines changed

3 files changed

+326
-0
lines changed

rust/helpers.c

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <linux/errname.h>
2929
#include <linux/gfp.h>
3030
#include <linux/highmem.h>
31+
#include <linux/io.h>
3132
#include <linux/mutex.h>
3233
#include <linux/rcupdate.h>
3334
#include <linux/refcount.h>
@@ -215,6 +216,111 @@ void rust_helper_rcu_read_unlock(void)
215216
EXPORT_SYMBOL_GPL(rust_helper_rcu_read_unlock);
216217
/* end rcu */
217218

219+
/* io.h */
220+
u8 rust_helper_readb(const volatile void __iomem *addr)
221+
{
222+
return readb(addr);
223+
}
224+
EXPORT_SYMBOL_GPL(rust_helper_readb);
225+
226+
u16 rust_helper_readw(const volatile void __iomem *addr)
227+
{
228+
return readw(addr);
229+
}
230+
EXPORT_SYMBOL_GPL(rust_helper_readw);
231+
232+
u32 rust_helper_readl(const volatile void __iomem *addr)
233+
{
234+
return readl(addr);
235+
}
236+
EXPORT_SYMBOL_GPL(rust_helper_readl);
237+
238+
#ifdef CONFIG_64BIT
239+
u64 rust_helper_readq(const volatile void __iomem *addr)
240+
{
241+
return readq(addr);
242+
}
243+
EXPORT_SYMBOL_GPL(rust_helper_readq);
244+
#endif
245+
246+
void rust_helper_writeb(u8 value, volatile void __iomem *addr)
247+
{
248+
writeb(value, addr);
249+
}
250+
EXPORT_SYMBOL_GPL(rust_helper_writeb);
251+
252+
void rust_helper_writew(u16 value, volatile void __iomem *addr)
253+
{
254+
writew(value, addr);
255+
}
256+
EXPORT_SYMBOL_GPL(rust_helper_writew);
257+
258+
void rust_helper_writel(u32 value, volatile void __iomem *addr)
259+
{
260+
writel(value, addr);
261+
}
262+
EXPORT_SYMBOL_GPL(rust_helper_writel);
263+
264+
#ifdef CONFIG_64BIT
265+
void rust_helper_writeq(u64 value, volatile void __iomem *addr)
266+
{
267+
writeq(value, addr);
268+
}
269+
EXPORT_SYMBOL_GPL(rust_helper_writeq);
270+
#endif
271+
272+
u8 rust_helper_readb_relaxed(const volatile void __iomem *addr)
273+
{
274+
return readb_relaxed(addr);
275+
}
276+
EXPORT_SYMBOL_GPL(rust_helper_readb_relaxed);
277+
278+
u16 rust_helper_readw_relaxed(const volatile void __iomem *addr)
279+
{
280+
return readw_relaxed(addr);
281+
}
282+
EXPORT_SYMBOL_GPL(rust_helper_readw_relaxed);
283+
284+
u32 rust_helper_readl_relaxed(const volatile void __iomem *addr)
285+
{
286+
return readl_relaxed(addr);
287+
}
288+
EXPORT_SYMBOL_GPL(rust_helper_readl_relaxed);
289+
290+
#ifdef CONFIG_64BIT
291+
u64 rust_helper_readq_relaxed(const volatile void __iomem *addr)
292+
{
293+
return readq_relaxed(addr);
294+
}
295+
EXPORT_SYMBOL_GPL(rust_helper_readq_relaxed);
296+
#endif
297+
298+
void rust_helper_writeb_relaxed(u8 value, volatile void __iomem *addr)
299+
{
300+
writeb_relaxed(value, addr);
301+
}
302+
EXPORT_SYMBOL_GPL(rust_helper_writeb_relaxed);
303+
304+
void rust_helper_writew_relaxed(u16 value, volatile void __iomem *addr)
305+
{
306+
writew_relaxed(value, addr);
307+
}
308+
EXPORT_SYMBOL_GPL(rust_helper_writew_relaxed);
309+
310+
void rust_helper_writel_relaxed(u32 value, volatile void __iomem *addr)
311+
{
312+
writel_relaxed(value, addr);
313+
}
314+
EXPORT_SYMBOL_GPL(rust_helper_writel_relaxed);
315+
316+
#ifdef CONFIG_64BIT
317+
void rust_helper_writeq_relaxed(u64 value, volatile void __iomem *addr)
318+
{
319+
writeq_relaxed(value, addr);
320+
}
321+
EXPORT_SYMBOL_GPL(rust_helper_writeq_relaxed);
322+
#endif
323+
218324
/*
219325
* `bindgen` binds the C `size_t` type as the Rust `usize` type, so we can
220326
* use it in contexts where Rust expects a `usize` like slice (array) indices.

rust/kernel/io.rs

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
//! Memory-mapped IO.
4+
//!
5+
//! C header: [`include/asm-generic/io.h`](srctree/include/asm-generic/io.h)
6+
7+
use crate::error::{code::EINVAL, Result};
8+
use crate::{bindings, build_assert};
9+
10+
/// IO-mapped memory, starting at the base address @addr and spanning @maxlen bytes.
11+
///
12+
/// The creator (usually a subsystem such as PCI) is responsible for creating the
13+
/// mapping, performing an additional region request etc.
14+
///
15+
/// # Invariant
16+
///
17+
/// `addr` is the start and `maxsize` the length of valid I/O remapped memory region.
18+
///
19+
/// # Examples
20+
///
21+
/// ```
22+
/// # use kernel::{bindings, io::Io};
23+
/// # use core::ops::Deref;
24+
///
25+
/// // See also [`pci::Bar`] for a real example.
26+
/// struct IoMem<const SIZE: usize>(Io<SIZE>);
27+
///
28+
/// impl<const SIZE: usize> IoMem<SIZE> {
29+
/// fn new(paddr: usize) -> Result<Self>{
30+
///
31+
/// // SAFETY: assert safety for this example
32+
/// let addr = unsafe { bindings::ioremap(paddr as _, SIZE.try_into().unwrap()) };
33+
/// if addr.is_null() {
34+
/// return Err(ENOMEM);
35+
/// }
36+
///
37+
/// // SAFETY: `addr` is guaranteed to be the start of a valid I/O mapped memory region of
38+
/// // size `SIZE`.
39+
/// let io = unsafe { Io::new(addr as _, SIZE)? };
40+
///
41+
/// Ok(IoMem(io))
42+
/// }
43+
/// }
44+
///
45+
/// impl<const SIZE: usize> Drop for IoMem<SIZE> {
46+
/// fn drop(&mut self) {
47+
/// // SAFETY: Safe as by the invariant of `Io`.
48+
/// unsafe { bindings::iounmap(self.0.base_addr() as _); };
49+
/// }
50+
/// }
51+
///
52+
/// impl<const SIZE: usize> Deref for IoMem<SIZE> {
53+
/// type Target = Io<SIZE>;
54+
///
55+
/// fn deref(&self) -> &Self::Target {
56+
/// &self.0
57+
/// }
58+
/// }
59+
///
60+
/// let iomem = IoMem::<{ core::mem::size_of::<u32>() }>::new(0xBAAAAAAD).unwrap();
61+
/// iomem.writel(0x42, 0x0);
62+
/// assert!(iomem.try_writel(0x42, 0x0).is_ok());
63+
/// assert!(iomem.try_writel(0x42, 0x4).is_err());
64+
/// ```
65+
pub struct Io<const SIZE: usize = 0> {
66+
addr: usize,
67+
maxsize: usize,
68+
}
69+
70+
macro_rules! define_read {
71+
($(#[$attr:meta])* $name:ident, $try_name:ident, $type_name:ty) => {
72+
/// Read IO data from a given offset known at compile time.
73+
///
74+
/// Bound checks are performed on compile time, hence if the offset is not known at compile
75+
/// time, the build will fail.
76+
$(#[$attr])*
77+
#[inline]
78+
pub fn $name(&self, offset: usize) -> $type_name {
79+
let addr = self.io_addr_assert::<$type_name>(offset);
80+
81+
unsafe { bindings::$name(addr as _) }
82+
}
83+
84+
/// Read IO data from a given offset.
85+
///
86+
/// Bound checks are performed on runtime, it fails if the offset (plus the type size) is
87+
/// out of bounds.
88+
$(#[$attr])*
89+
pub fn $try_name(&self, offset: usize) -> Result<$type_name> {
90+
let addr = self.io_addr::<$type_name>(offset)?;
91+
92+
Ok(unsafe { bindings::$name(addr as _) })
93+
}
94+
};
95+
}
96+
97+
macro_rules! define_write {
98+
($(#[$attr:meta])* $name:ident, $try_name:ident, $type_name:ty) => {
99+
/// Write IO data from a given offset known at compile time.
100+
///
101+
/// Bound checks are performed on compile time, hence if the offset is not known at compile
102+
/// time, the build will fail.
103+
$(#[$attr])*
104+
#[inline]
105+
pub fn $name(&self, value: $type_name, offset: usize) {
106+
let addr = self.io_addr_assert::<$type_name>(offset);
107+
108+
unsafe { bindings::$name(value, addr as _, ) }
109+
}
110+
111+
/// Write IO data from a given offset.
112+
///
113+
/// Bound checks are performed on runtime, it fails if the offset (plus the type size) is
114+
/// out of bounds.
115+
$(#[$attr])*
116+
pub fn $try_name(&self, value: $type_name, offset: usize) -> Result {
117+
let addr = self.io_addr::<$type_name>(offset)?;
118+
119+
unsafe { bindings::$name(value, addr as _) }
120+
Ok(())
121+
}
122+
};
123+
}
124+
125+
impl<const SIZE: usize> Io<SIZE> {
126+
///
127+
///
128+
/// # Safety
129+
///
130+
/// Callers must ensure that `addr` is the start of a valid I/O mapped memory region of size
131+
/// `maxsize`.
132+
pub unsafe fn new(addr: usize, maxsize: usize) -> Result<Self> {
133+
if maxsize < SIZE {
134+
return Err(EINVAL);
135+
}
136+
137+
Ok(Self { addr, maxsize })
138+
}
139+
140+
/// Returns the base address of this mapping.
141+
#[inline]
142+
pub fn base_addr(&self) -> usize {
143+
self.addr
144+
}
145+
146+
/// Returns the size of this mapping.
147+
#[inline]
148+
pub fn maxsize(&self) -> usize {
149+
self.maxsize
150+
}
151+
152+
#[inline]
153+
const fn offset_valid<U>(offset: usize, size: usize) -> bool {
154+
let type_size = core::mem::size_of::<U>();
155+
if let Some(end) = offset.checked_add(type_size) {
156+
end <= size && offset % type_size == 0
157+
} else {
158+
false
159+
}
160+
}
161+
162+
#[inline]
163+
fn io_addr<U>(&self, offset: usize) -> Result<usize> {
164+
if !Self::offset_valid::<U>(offset, self.maxsize()) {
165+
return Err(EINVAL);
166+
}
167+
168+
// Probably no need to check, since the safety requirements of `Self::new` guarantee that
169+
// this can't overflow.
170+
self.base_addr().checked_add(offset).ok_or(EINVAL)
171+
}
172+
173+
#[inline]
174+
fn io_addr_assert<U>(&self, offset: usize) -> usize {
175+
build_assert!(Self::offset_valid::<U>(offset, SIZE));
176+
177+
self.base_addr() + offset
178+
}
179+
180+
define_read!(readb, try_readb, u8);
181+
define_read!(readw, try_readw, u16);
182+
define_read!(readl, try_readl, u32);
183+
define_read!(
184+
#[cfg(CONFIG_64BIT)]
185+
readq,
186+
try_readq,
187+
u64
188+
);
189+
190+
define_read!(readb_relaxed, try_readb_relaxed, u8);
191+
define_read!(readw_relaxed, try_readw_relaxed, u16);
192+
define_read!(readl_relaxed, try_readl_relaxed, u32);
193+
define_read!(
194+
#[cfg(CONFIG_64BIT)]
195+
readq_relaxed,
196+
try_readq_relaxed,
197+
u64
198+
);
199+
200+
define_write!(writeb, try_writeb, u8);
201+
define_write!(writew, try_writew, u16);
202+
define_write!(writel, try_writel, u32);
203+
define_write!(
204+
#[cfg(CONFIG_64BIT)]
205+
writeq,
206+
try_writeq,
207+
u64
208+
);
209+
210+
define_write!(writeb_relaxed, try_writeb_relaxed, u8);
211+
define_write!(writew_relaxed, try_writew_relaxed, u16);
212+
define_write!(writel_relaxed, try_writel_relaxed, u32);
213+
define_write!(
214+
#[cfg(CONFIG_64BIT)]
215+
writeq_relaxed,
216+
try_writeq_relaxed,
217+
u64
218+
);
219+
}

rust/kernel/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ pub mod workqueue;
6060

6161
#[doc(hidden)]
6262
pub use bindings;
63+
pub mod io;
6364
pub use macros;
6465
pub use uapi;
6566

0 commit comments

Comments
 (0)