Skip to content

Commit 718f222

Browse files
authored
Merge pull request #462 from wedsonaf/io_mem
rust: add `IoMem` abstraction.
2 parents 5f44006 + f9e2f7e commit 718f222

File tree

4 files changed

+272
-0
lines changed

4 files changed

+272
-0
lines changed

rust/helpers.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <linux/mutex.h>
1212
#include <linux/platform_device.h>
1313
#include <linux/security.h>
14+
#include <asm/io.h>
1415

1516
void rust_helper_BUG(void)
1617
{
@@ -32,6 +33,64 @@ unsigned long rust_helper_clear_user(void __user *to, unsigned long n)
3233
return clear_user(to, n);
3334
}
3435

36+
void __iomem *rust_helper_ioremap(resource_size_t offset, unsigned long size)
37+
{
38+
return ioremap(offset, size);
39+
}
40+
EXPORT_SYMBOL_GPL(rust_helper_ioremap);
41+
42+
u8 rust_helper_readb(const volatile void __iomem *addr)
43+
{
44+
return readb(addr);
45+
}
46+
EXPORT_SYMBOL_GPL(rust_helper_readb);
47+
48+
u16 rust_helper_readw(const volatile void __iomem *addr)
49+
{
50+
return readw(addr);
51+
}
52+
EXPORT_SYMBOL_GPL(rust_helper_readw);
53+
54+
u32 rust_helper_readl(const volatile void __iomem *addr)
55+
{
56+
return readl(addr);
57+
}
58+
EXPORT_SYMBOL_GPL(rust_helper_readl);
59+
60+
#ifdef CONFIG_64BIT
61+
u64 rust_helper_readq(const volatile void __iomem *addr)
62+
{
63+
return readq(addr);
64+
}
65+
EXPORT_SYMBOL_GPL(rust_helper_readq);
66+
#endif
67+
68+
void rust_helper_writeb(u8 value, volatile void __iomem *addr)
69+
{
70+
writeb(value, addr);
71+
}
72+
EXPORT_SYMBOL_GPL(rust_helper_writeb);
73+
74+
void rust_helper_writew(u16 value, volatile void __iomem *addr)
75+
{
76+
writew(value, addr);
77+
}
78+
EXPORT_SYMBOL_GPL(rust_helper_writew);
79+
80+
void rust_helper_writel(u32 value, volatile void __iomem *addr)
81+
{
82+
writel(value, addr);
83+
}
84+
EXPORT_SYMBOL_GPL(rust_helper_writel);
85+
86+
#ifdef CONFIG_64BIT
87+
void rust_helper_writeq(u64 value, volatile void __iomem *addr)
88+
{
89+
writeq(value, addr);
90+
}
91+
EXPORT_SYMBOL_GPL(rust_helper_writeq);
92+
#endif
93+
3594
void rust_helper_spin_lock_init(spinlock_t *lock, const char *name,
3695
struct lock_class_key *key)
3796
{

rust/kernel/bindings_helper.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <linux/platform_device.h>
1919
#include <linux/of_platform.h>
2020
#include <linux/security.h>
21+
#include <asm/io.h>
2122

2223
// `bindgen` gets confused at certain things
2324
const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL;

rust/kernel/io_mem.rs

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
//! Memory-mapped IO.
4+
//!
5+
//! C header: [`include/asm-generic/io.h`](../../../../include/asm-generic/io.h)
6+
7+
use crate::{bindings, c_types, Error, Result};
8+
use core::convert::TryInto;
9+
10+
extern "C" {
11+
fn rust_helper_ioremap(
12+
offset: bindings::resource_size_t,
13+
size: c_types::c_ulong,
14+
) -> *mut c_types::c_void;
15+
16+
fn rust_helper_readb(addr: *const c_types::c_void) -> u8;
17+
fn rust_helper_readw(addr: *const c_types::c_void) -> u16;
18+
fn rust_helper_readl(addr: *const c_types::c_void) -> u32;
19+
fn rust_helper_readq(addr: *const c_types::c_void) -> u64;
20+
21+
fn rust_helper_writeb(value: u8, addr: *mut c_types::c_void);
22+
fn rust_helper_writew(value: u16, addr: *mut c_types::c_void);
23+
fn rust_helper_writel(value: u32, addr: *mut c_types::c_void);
24+
fn rust_helper_writeq(value: u64, addr: *mut c_types::c_void);
25+
}
26+
27+
/// Represents a memory resource.
28+
pub struct Resource {
29+
offset: bindings::resource_size_t,
30+
size: bindings::resource_size_t,
31+
}
32+
33+
impl Resource {
34+
pub(crate) fn new(
35+
start: bindings::resource_size_t,
36+
end: bindings::resource_size_t,
37+
) -> Option<Self> {
38+
if start == 0 {
39+
return None;
40+
}
41+
Some(Self {
42+
offset: start,
43+
size: end.checked_sub(start)?.checked_add(1)?,
44+
})
45+
}
46+
}
47+
48+
/// Represents a memory block of at least `SIZE` bytes.
49+
///
50+
/// # Invariants
51+
///
52+
/// `ptr` is a non-null and valid address of at least `SIZE` bytes and returned by an `ioremap`
53+
/// variant. `ptr` is also 8-byte aligned.
54+
///
55+
/// # Examples
56+
///
57+
/// ```
58+
/// # use kernel::prelude::*;
59+
/// use kernel::io_mem::{IoMem, Resource};
60+
///
61+
/// fn test(res: Resource) -> Result {
62+
/// // Create an io mem block of at least 100 bytes.
63+
/// // SAFETY: No DMA operations are initiated through `mem`.
64+
/// let mem = unsafe { IoMem::<100>::try_new(res) }?;
65+
///
66+
/// // Read one byte from offset 10.
67+
/// let v = mem.readb(10);
68+
///
69+
/// // Write value to offset 20.
70+
/// mem.writeb(v, 20);
71+
///
72+
/// Ok(())
73+
/// }
74+
///
75+
/// ```
76+
pub struct IoMem<const SIZE: usize> {
77+
ptr: usize,
78+
}
79+
80+
macro_rules! define_read {
81+
($name:ident, $try_name:ident, $type_name:ty) => {
82+
/// Reads IO data from the given offset known, at compile time.
83+
///
84+
/// If the offset is not known at compile time, the build will fail.
85+
pub fn $name(&self, offset: usize) -> $type_name {
86+
Self::check_offset::<$type_name>(offset);
87+
let ptr = self.ptr.wrapping_add(offset);
88+
// SAFETY: The type invariants guarantee that `ptr` is a valid pointer. The check above
89+
// guarantees that the code won't build if `offset` makes the read go out of bounds
90+
// (including the type size).
91+
unsafe { concat_idents!(rust_helper_, $name)(ptr as _) }
92+
}
93+
94+
/// Reads IO data from the given offset.
95+
///
96+
/// It fails if/when the offset (plus the type size) is out of bounds.
97+
pub fn $try_name(&self, offset: usize) -> Result<$type_name> {
98+
if !Self::offset_ok::<$type_name>(offset) {
99+
return Err(Error::EINVAL);
100+
}
101+
let ptr = self.ptr.wrapping_add(offset);
102+
// SAFETY: The type invariants guarantee that `ptr` is a valid pointer. The check above
103+
// returns an error if `offset` would make the read go out of bounds (including the
104+
// type size).
105+
Ok(unsafe { concat_idents!(rust_helper_, $name)(ptr as _) })
106+
}
107+
};
108+
}
109+
110+
macro_rules! define_write {
111+
($name:ident, $try_name:ident, $type_name:ty) => {
112+
/// Writes IO data to the given offset, known at compile time.
113+
///
114+
/// If the offset is not known at compile time, the build will fail.
115+
pub fn $name(&self, value: $type_name, offset: usize) {
116+
Self::check_offset::<$type_name>(offset);
117+
let ptr = self.ptr.wrapping_add(offset);
118+
// SAFETY: The type invariants guarantee that `ptr` is a valid pointer. The check above
119+
// guarantees that the code won't link if `offset` makes the write go out of bounds
120+
// (including the type size).
121+
unsafe { concat_idents!(rust_helper_, $name)(value, ptr as _) }
122+
}
123+
124+
/// Writes IO data to the given offset.
125+
///
126+
/// It fails if/when the offset (plus the type size) is out of bounds.
127+
pub fn $try_name(&self, value: $type_name, offset: usize) -> Result {
128+
if !Self::offset_ok::<$type_name>(offset) {
129+
return Err(Error::EINVAL);
130+
}
131+
let ptr = self.ptr.wrapping_add(offset);
132+
// SAFETY: The type invariants guarantee that `ptr` is a valid pointer. The check above
133+
// returns an error if `offset` would make the write go out of bounds (including the
134+
// type size).
135+
unsafe { concat_idents!(rust_helper_, $name)(value, ptr as _) };
136+
Ok(())
137+
}
138+
};
139+
}
140+
141+
impl<const SIZE: usize> IoMem<SIZE> {
142+
/// Tries to create a new instance of a memory block.
143+
///
144+
/// The resource described by `res` is mapped into the CPU's address space so that it can be
145+
/// accessed directly. It is also consumed by this function so that it can't be mapped again
146+
/// to a different address.
147+
///
148+
/// # Safety
149+
///
150+
/// Callers must ensure that either (a) the resulting interface cannot be used to initiate DMA
151+
/// operations, or (b) that DMA operations initiated via the returned interface use DMA handles
152+
/// allocated through the `dma` module.
153+
pub unsafe fn try_new(res: Resource) -> Result<Self> {
154+
// Check that the resource has at least `SIZE` bytes in it.
155+
if res.size < SIZE.try_into()? {
156+
return Err(Error::EINVAL);
157+
}
158+
159+
// To be able to check pointers at compile time based only on offsets, we need to guarantee
160+
// that the base pointer is minimally aligned. So we conservatively expect at least 8 bytes.
161+
if res.offset % 8 != 0 {
162+
crate::pr_err!("Physical address is not 64-bit aligned: {:x}", res.offset);
163+
return Err(Error::EDOM);
164+
}
165+
166+
// Try to map the resource.
167+
// SAFETY: Just mapping the memory range.
168+
// TODO: Remove `into` call below (and disabling of clippy warning) once #465 is fixed.
169+
#[allow(clippy::complexity)]
170+
let addr = unsafe { rust_helper_ioremap(res.offset, res.size.into()) };
171+
if addr.is_null() {
172+
Err(Error::ENOMEM)
173+
} else {
174+
// INVARIANT: `addr` is non-null and was returned by `ioremap`, so it is valid. It is
175+
// also 8-byte aligned because we checked it above.
176+
Ok(Self { ptr: addr as usize })
177+
}
178+
}
179+
180+
const fn offset_ok<T>(offset: usize) -> bool {
181+
let type_size = core::mem::size_of::<T>();
182+
if let Some(end) = offset.checked_add(type_size) {
183+
end <= SIZE && offset % type_size == 0
184+
} else {
185+
false
186+
}
187+
}
188+
189+
const fn check_offset<T>(offset: usize) {
190+
crate::build_assert!(Self::offset_ok::<T>(offset), "IoMem offset overflow");
191+
}
192+
193+
define_read!(readb, try_readb, u8);
194+
define_read!(readw, try_readw, u16);
195+
define_read!(readl, try_readl, u32);
196+
define_read!(readq, try_readq, u64);
197+
198+
define_write!(writeb, try_writeb, u8);
199+
define_write!(writew, try_writew, u16);
200+
define_write!(writel, try_writel, u32);
201+
define_write!(writeq, try_writeq, u64);
202+
}
203+
204+
impl<const SIZE: usize> Drop for IoMem<SIZE> {
205+
fn drop(&mut self) {
206+
// SAFETY: By the type invariant, `self.ptr` is a value returned by a previous successful
207+
// call to `ioremap`.
208+
unsafe { bindings::iounmap(self.ptr as _) };
209+
}
210+
}

rust/kernel/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#![feature(
1616
allocator_api,
1717
associated_type_defaults,
18+
concat_idents,
1819
const_fn_trait_bound,
1920
const_mut_refs,
2021
const_panic,
@@ -74,6 +75,7 @@ pub mod sync;
7475
pub mod sysctl;
7576

7677
pub mod io_buffer;
78+
pub mod io_mem;
7779
pub mod iov_iter;
7880
pub mod of;
7981
pub mod platdev;

0 commit comments

Comments
 (0)