Skip to content

Commit b1c72ac

Browse files
author
Danilo Krummrich
committed
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 f444613 commit b1c72ac

File tree

4 files changed

+327
-0
lines changed

4 files changed

+327
-0
lines changed

rust/helpers/helpers.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "build_assert.c"
1313
#include "build_bug.c"
1414
#include "err.c"
15+
#include "io.c"
1516
#include "kunit.c"
1617
#include "mutex.c"
1718
#include "page.c"

rust/helpers/io.c

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
#include <linux/io.h>
4+
5+
u8 rust_helper_readb(const volatile void __iomem *addr)
6+
{
7+
return readb(addr);
8+
}
9+
10+
u16 rust_helper_readw(const volatile void __iomem *addr)
11+
{
12+
return readw(addr);
13+
}
14+
15+
u32 rust_helper_readl(const volatile void __iomem *addr)
16+
{
17+
return readl(addr);
18+
}
19+
20+
#ifdef CONFIG_64BIT
21+
u64 rust_helper_readq(const volatile void __iomem *addr)
22+
{
23+
return readq(addr);
24+
}
25+
#endif
26+
27+
void rust_helper_writeb(u8 value, volatile void __iomem *addr)
28+
{
29+
writeb(value, addr);
30+
}
31+
32+
void rust_helper_writew(u16 value, volatile void __iomem *addr)
33+
{
34+
writew(value, addr);
35+
}
36+
37+
void rust_helper_writel(u32 value, volatile void __iomem *addr)
38+
{
39+
writel(value, addr);
40+
}
41+
42+
#ifdef CONFIG_64BIT
43+
void rust_helper_writeq(u64 value, volatile void __iomem *addr)
44+
{
45+
writeq(value, addr);
46+
}
47+
#endif
48+
49+
u8 rust_helper_readb_relaxed(const volatile void __iomem *addr)
50+
{
51+
return readb_relaxed(addr);
52+
}
53+
54+
u16 rust_helper_readw_relaxed(const volatile void __iomem *addr)
55+
{
56+
return readw_relaxed(addr);
57+
}
58+
59+
u32 rust_helper_readl_relaxed(const volatile void __iomem *addr)
60+
{
61+
return readl_relaxed(addr);
62+
}
63+
64+
#ifdef CONFIG_64BIT
65+
u64 rust_helper_readq_relaxed(const volatile void __iomem *addr)
66+
{
67+
return readq_relaxed(addr);
68+
}
69+
#endif
70+
71+
void rust_helper_writeb_relaxed(u8 value, volatile void __iomem *addr)
72+
{
73+
writeb_relaxed(value, addr);
74+
}
75+
76+
void rust_helper_writew_relaxed(u16 value, volatile void __iomem *addr)
77+
{
78+
writew_relaxed(value, addr);
79+
}
80+
81+
void rust_helper_writel_relaxed(u32 value, volatile void __iomem *addr)
82+
{
83+
writel_relaxed(value, addr);
84+
}
85+
86+
#ifdef CONFIG_64BIT
87+
void rust_helper_writeq_relaxed(u64 value, volatile void __iomem *addr)
88+
{
89+
writeq_relaxed(value, addr);
90+
}
91+
#endif

rust/kernel/io.rs

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

rust/kernel/lib.rs

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

7171
#[doc(hidden)]
7272
pub use bindings;
73+
pub mod io;
7374
pub use macros;
7475
pub use uapi;
7576

0 commit comments

Comments
 (0)