Skip to content

Commit 18a0c20

Browse files
hoshinolinajannau
authored andcommitted
*RFL import: kernel::io_mem
Commit reference: 3dfc5eb
1 parent 43abfca commit 18a0c20

4 files changed

Lines changed: 293 additions & 0 deletions

File tree

rust/helpers/helpers.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "drm.c"
1616
#include "err.c"
1717
#include "io.c"
18+
#include "iomem.c"
1819
#include "kunit.c"
1920
#include "lockdep.c"
2021
#include "mutex.c"

rust/helpers/iomem.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
#include <asm/io.h>
4+
5+
void __iomem *rust_helper_ioremap(resource_size_t offset, unsigned long size)
6+
{
7+
return ioremap(offset, size);
8+
}
9+
10+
void rust_helper_memcpy_fromio(void *to, const volatile void __iomem *from, long count)
11+
{
12+
memcpy_fromio(to, from, count);
13+
}

rust/kernel/io_mem.rs

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
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+
#![allow(dead_code)]
8+
9+
use crate::{bindings, error::code::*, error::Result};
10+
use core::convert::TryInto;
11+
12+
/// Represents a memory resource.
13+
pub struct Resource {
14+
offset: bindings::resource_size_t,
15+
size: bindings::resource_size_t,
16+
}
17+
18+
impl Resource {
19+
pub(crate) fn new(
20+
start: bindings::resource_size_t,
21+
end: bindings::resource_size_t,
22+
) -> Option<Self> {
23+
if start == 0 {
24+
return None;
25+
}
26+
Some(Self {
27+
offset: start,
28+
size: end.checked_sub(start)?.checked_add(1)?,
29+
})
30+
}
31+
}
32+
33+
/// Represents a memory block of at least `SIZE` bytes.
34+
///
35+
/// # Invariants
36+
///
37+
/// `ptr` is a non-null and valid address of at least `SIZE` bytes and returned by an `ioremap`
38+
/// variant. `ptr` is also 8-byte aligned.
39+
///
40+
/// # Examples
41+
///
42+
/// ```
43+
/// # use kernel::prelude::*;
44+
/// use kernel::io_mem::{IoMem, Resource};
45+
///
46+
/// fn test(res: Resource) -> Result {
47+
/// // Create an io mem block of at least 100 bytes.
48+
/// // SAFETY: No DMA operations are initiated through `mem`.
49+
/// let mem = unsafe { IoMem::<100>::try_new(res) }?;
50+
///
51+
/// // Read one byte from offset 10.
52+
/// let v = mem.readb(10);
53+
///
54+
/// // Write value to offset 20.
55+
/// mem.writeb(v, 20);
56+
///
57+
/// Ok(())
58+
/// }
59+
/// ```
60+
pub struct IoMem<const SIZE: usize> {
61+
ptr: usize,
62+
}
63+
64+
macro_rules! define_read {
65+
($(#[$attr:meta])* $name:ident, $try_name:ident, $type_name:ty) => {
66+
/// Reads IO data from the given offset known, at compile time.
67+
///
68+
/// If the offset is not known at compile time, the build will fail.
69+
$(#[$attr])*
70+
#[inline]
71+
pub fn $name(&self, offset: usize) -> $type_name {
72+
Self::check_offset::<$type_name>(offset);
73+
let ptr = self.ptr.wrapping_add(offset);
74+
// SAFETY: The type invariants guarantee that `ptr` is a valid pointer. The check above
75+
// guarantees that the code won't build if `offset` makes the read go out of bounds
76+
// (including the type size).
77+
unsafe { bindings::$name(ptr as _) }
78+
}
79+
80+
/// Reads IO data from the given offset.
81+
///
82+
/// It fails if/when the offset (plus the type size) is out of bounds.
83+
$(#[$attr])*
84+
pub fn $try_name(&self, offset: usize) -> Result<$type_name> {
85+
if !Self::offset_ok::<$type_name>(offset) {
86+
return Err(EINVAL);
87+
}
88+
let ptr = self.ptr.wrapping_add(offset);
89+
// SAFETY: The type invariants guarantee that `ptr` is a valid pointer. The check above
90+
// returns an error if `offset` would make the read go out of bounds (including the
91+
// type size).
92+
Ok(unsafe { bindings::$name(ptr as _) })
93+
}
94+
};
95+
}
96+
97+
macro_rules! define_write {
98+
($(#[$attr:meta])* $name:ident, $try_name:ident, $type_name:ty) => {
99+
/// Writes IO data to the given offset, known at compile time.
100+
///
101+
/// If the offset is not known at compile time, the build will fail.
102+
$(#[$attr])*
103+
#[inline]
104+
pub fn $name(&self, value: $type_name, offset: usize) {
105+
Self::check_offset::<$type_name>(offset);
106+
let ptr = self.ptr.wrapping_add(offset);
107+
// SAFETY: The type invariants guarantee that `ptr` is a valid pointer. The check above
108+
// guarantees that the code won't link if `offset` makes the write go out of bounds
109+
// (including the type size).
110+
unsafe { bindings::$name(value, ptr as _) }
111+
}
112+
113+
/// Writes IO data to the given offset.
114+
///
115+
/// It fails if/when the offset (plus the type size) is out of bounds.
116+
$(#[$attr])*
117+
pub fn $try_name(&self, value: $type_name, offset: usize) -> Result {
118+
if !Self::offset_ok::<$type_name>(offset) {
119+
return Err(EINVAL);
120+
}
121+
let ptr = self.ptr.wrapping_add(offset);
122+
// SAFETY: The type invariants guarantee that `ptr` is a valid pointer. The check above
123+
// returns an error if `offset` would make the write go out of bounds (including the
124+
// type size).
125+
unsafe { bindings::$name(value, ptr as _) };
126+
Ok(())
127+
}
128+
};
129+
}
130+
131+
impl<const SIZE: usize> IoMem<SIZE> {
132+
/// Tries to create a new instance of a memory block.
133+
///
134+
/// The resource described by `res` is mapped into the CPU's address space so that it can be
135+
/// accessed directly. It is also consumed by this function so that it can't be mapped again
136+
/// to a different address.
137+
///
138+
/// # Safety
139+
///
140+
/// Callers must ensure that either (a) the resulting interface cannot be used to initiate DMA
141+
/// operations, or (b) that DMA operations initiated via the returned interface use DMA handles
142+
/// allocated through the `dma` module.
143+
pub unsafe fn try_new(res: Resource) -> Result<Self> {
144+
// Check that the resource has at least `SIZE` bytes in it.
145+
if res.size < SIZE.try_into()? {
146+
return Err(EINVAL);
147+
}
148+
149+
// To be able to check pointers at compile time based only on offsets, we need to guarantee
150+
// that the base pointer is minimally aligned. So we conservatively expect at least 8 bytes.
151+
if res.offset % 8 != 0 {
152+
crate::pr_err!("Physical address is not 64-bit aligned: {:x}", res.offset);
153+
return Err(EDOM);
154+
}
155+
156+
// Try to map the resource.
157+
// SAFETY: Just mapping the memory range.
158+
let addr = unsafe { bindings::ioremap(res.offset, res.size as _) };
159+
if addr.is_null() {
160+
Err(ENOMEM)
161+
} else {
162+
// INVARIANT: `addr` is non-null and was returned by `ioremap`, so it is valid. It is
163+
// also 8-byte aligned because we checked it above.
164+
Ok(Self { ptr: addr as usize })
165+
}
166+
}
167+
168+
#[inline]
169+
const fn offset_ok<T>(offset: usize) -> bool {
170+
let type_size = core::mem::size_of::<T>();
171+
if let Some(end) = offset.checked_add(type_size) {
172+
end <= SIZE && offset % type_size == 0
173+
} else {
174+
false
175+
}
176+
}
177+
178+
fn offset_ok_of_val<T: ?Sized>(offset: usize, value: &T) -> bool {
179+
let value_size = core::mem::size_of_val(value);
180+
let value_alignment = core::mem::align_of_val(value);
181+
if let Some(end) = offset.checked_add(value_size) {
182+
end <= SIZE && offset % value_alignment == 0
183+
} else {
184+
false
185+
}
186+
}
187+
188+
#[inline]
189+
const fn check_offset<T>(offset: usize) {
190+
crate::build_assert!(Self::offset_ok::<T>(offset), "IoMem offset overflow");
191+
}
192+
193+
/// Copy memory block from an i/o memory by filling the specified buffer with it.
194+
///
195+
/// # Examples
196+
/// ```
197+
/// use kernel::io_mem::{self, IoMem, Resource};
198+
///
199+
/// fn test(res: Resource) -> Result {
200+
/// // Create an i/o memory block of at least 100 bytes.
201+
/// let mem = unsafe { IoMem::<100>::try_new(res) }?;
202+
///
203+
/// let mut buffer: [u8; 32] = [0; 32];
204+
///
205+
/// // Memcpy 16 bytes from an offset 10 of i/o memory block into the buffer.
206+
/// mem.try_memcpy_fromio(&mut buffer[..16], 10)?;
207+
///
208+
/// Ok(())
209+
/// }
210+
/// ```
211+
pub fn try_memcpy_fromio(&self, buffer: &mut [u8], offset: usize) -> Result {
212+
if !Self::offset_ok_of_val(offset, buffer) {
213+
return Err(EINVAL);
214+
}
215+
216+
let ptr = self.ptr.wrapping_add(offset);
217+
218+
// SAFETY:
219+
// - The type invariants guarantee that `ptr` is a valid pointer.
220+
// - The bounds of `buffer` are checked with a call to `offset_ok_of_val()`.
221+
unsafe {
222+
bindings::memcpy_fromio(
223+
buffer.as_mut_ptr() as *mut _,
224+
ptr as *const _,
225+
buffer.len() as _,
226+
)
227+
};
228+
Ok(())
229+
}
230+
231+
define_read!(readb, try_readb, u8);
232+
define_read!(readw, try_readw, u16);
233+
define_read!(readl, try_readl, u32);
234+
define_read!(
235+
#[cfg(CONFIG_64BIT)]
236+
readq,
237+
try_readq,
238+
u64
239+
);
240+
241+
define_read!(readb_relaxed, try_readb_relaxed, u8);
242+
define_read!(readw_relaxed, try_readw_relaxed, u16);
243+
define_read!(readl_relaxed, try_readl_relaxed, u32);
244+
define_read!(
245+
#[cfg(CONFIG_64BIT)]
246+
readq_relaxed,
247+
try_readq_relaxed,
248+
u64
249+
);
250+
251+
define_write!(writeb, try_writeb, u8);
252+
define_write!(writew, try_writew, u16);
253+
define_write!(writel, try_writel, u32);
254+
define_write!(
255+
#[cfg(CONFIG_64BIT)]
256+
writeq,
257+
try_writeq,
258+
u64
259+
);
260+
261+
define_write!(writeb_relaxed, try_writeb_relaxed, u8);
262+
define_write!(writew_relaxed, try_writew_relaxed, u16);
263+
define_write!(writel_relaxed, try_writel_relaxed, u32);
264+
define_write!(
265+
#[cfg(CONFIG_64BIT)]
266+
writeq_relaxed,
267+
try_writeq_relaxed,
268+
u64
269+
);
270+
}
271+
272+
impl<const SIZE: usize> Drop for IoMem<SIZE> {
273+
fn drop(&mut self) {
274+
// SAFETY: By the type invariant, `self.ptr` is a value returned by a previous successful
275+
// call to `ioremap`.
276+
unsafe { bindings::iounmap(self.ptr as _) };
277+
}
278+
}

rust/kernel/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ pub mod error;
5454
pub mod firmware;
5555
pub mod init;
5656
pub mod io_buffer;
57+
pub mod io_mem;
5758
pub mod io_pgtable;
5859
pub mod ioctl;
5960
#[cfg(CONFIG_KUNIT)]

0 commit comments

Comments
 (0)