Skip to content

Commit ad9ec4e

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 e016888 commit ad9ec4e

File tree

4 files changed

+312
-0
lines changed

4 files changed

+312
-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: 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
@@ -62,6 +62,7 @@ pub mod workqueue;
6262

6363
#[doc(hidden)]
6464
pub use bindings;
65+
pub mod io;
6566
pub use macros;
6667
pub use uapi;
6768

0 commit comments

Comments
 (0)