Skip to content

Commit e2da85f

Browse files
author
Maciej Falkowski
committed
rust: iopoll: add iopoll bindings
Signed-off-by: Maciej Falkowski <[email protected]>
1 parent 6415b2a commit e2da85f

File tree

4 files changed

+150
-1
lines changed

4 files changed

+150
-1
lines changed

rust/helpers.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
#include <linux/bug.h>
44
#include <linux/build_bug.h>
55
#include <linux/clk.h>
6+
#include <linux/delay.h>
7+
#include <linux/ktime.h>
8+
#include <linux/kernel.h>
69
#include <linux/uaccess.h>
710
#include <linux/sched/signal.h>
811
#include <linux/gfp.h>
@@ -23,6 +26,26 @@ __noreturn void rust_helper_BUG(void)
2326
BUG();
2427
}
2528

29+
void rust_helper_usleep_range(unsigned long min, unsigned long max)
30+
{
31+
usleep_range(min, max);
32+
}
33+
34+
void rust_helper_might_sleep(void)
35+
{
36+
might_sleep();
37+
}
38+
39+
int rust_helper_ktime_compare(const ktime_t cmp1, const ktime_t cmp2)
40+
{
41+
return ktime_compare(cmp1, cmp2);
42+
}
43+
44+
ktime_t rust_helper_ktime_add_us(const ktime_t kt, const u64 usec)
45+
{
46+
return ktime_add_us(kt, usec);
47+
}
48+
2649
int rust_helper_clk_prepare_enable(struct clk *clk)
2750
{
2851
return clk_prepare_enable(clk);

rust/kernel/io_mem.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
#![allow(dead_code)]
88

99
use crate::{
10-
bindings
10+
bindings, iopoll,
1111
platdev::{self, PlatformDevice},
1212
Error, Result,
1313
};
@@ -131,6 +131,32 @@ macro_rules! define_write {
131131
};
132132
}
133133

134+
macro_rules! define_readx_poll_timeout {
135+
($(#[$attr:meta])* $fname:ident, $callback_name:ident, $type_name:ty) => {
136+
/// Polls IO data from the given offset known, at compile time.
137+
///
138+
/// The IO data is polled until it matches specific condition or fails
139+
/// at timeout.
140+
///
141+
/// If the offset is not known at compile time, the build will fail.
142+
$(#[$attr])*
143+
pub fn $fname<F: Fn(&$type_name) -> bool>(
144+
&self,
145+
offset: usize,
146+
cond: F,
147+
sleep_us: u32,
148+
timeout_us: u64,
149+
) -> Result<$type_name> {
150+
Self::check_offset::<$type_name>(offset);
151+
let ptr = self.ptr.wrapping_add(offset);
152+
// SAFETY: The type invariants guarantee that `ptr` is a valid pointer. The check above
153+
// guarantees that the code won't build if `offset` makes the read go out of bounds
154+
// (including the type size).
155+
unsafe { iopoll::readx_poll_timeout(bindings::$callback_name, cond, sleep_us, timeout_us, ptr as _) }
156+
}
157+
};
158+
}
159+
134160
impl<const SIZE: usize> IoMem<SIZE> {
135161
/// Tries to create a new instance of a memory block.
136162
///
@@ -189,6 +215,16 @@ impl<const SIZE: usize> IoMem<SIZE> {
189215
crate::build_assert!(Self::offset_ok::<T>(offset), "IoMem offset overflow");
190216
}
191217

218+
define_readx_poll_timeout!(readb_poll_timeout, readb, u8);
219+
define_readx_poll_timeout!(readw_poll_timeout, readw, u16);
220+
define_readx_poll_timeout!(readl_poll_timeout, readl, u32);
221+
define_readx_poll_timeout!(
222+
#[cfg(CONFIG_64BIT)]
223+
readq_poll_timeout,
224+
readq,
225+
u64
226+
);
227+
192228
define_read!(readb, try_readb, u8);
193229
define_read!(readw, try_readw, u16);
194230
define_read!(readl, try_readl, u32);

rust/kernel/iopoll.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
//! Helper routines for polling IO addresses.
4+
//!
5+
//! C header: [`include/linux/iopoll.h`](../../../../include/linux/iopoll.h)
6+
7+
use core::mem::MaybeUninit;
8+
9+
use crate::{
10+
bindings, c_types,
11+
error::{Error, Result},
12+
might_sleep_if,
13+
};
14+
15+
/// Periodically poll an address until a condition is met or a timeout occurs.
16+
///
17+
/// # Safety
18+
///
19+
/// `addr` must be non-null pointer valid io address.
20+
pub unsafe fn read_poll_timeout<T, F: Fn(&T) -> bool>(
21+
op: unsafe extern "C" fn(*const c_types::c_void) -> T,
22+
cond: F,
23+
sleep_us: u32,
24+
timeout_us: u64,
25+
sleep_before_read: bool,
26+
addr: *const c_types::c_void,
27+
) -> Result<T> {
28+
// SAFETY: FFI call.
29+
let timeout = unsafe { bindings::ktime_add_us(bindings::ktime_get(), timeout_us) };
30+
might_sleep_if!(sleep_us != 0);
31+
32+
if sleep_before_read && sleep_us != 0 {
33+
// SAFETY: FFI call.
34+
unsafe { bindings::usleep_range((sleep_us >> 2) + 1, sleep_us) };
35+
}
36+
37+
let mut val = MaybeUninit::<T>::uninit();
38+
39+
loop {
40+
unsafe { val.as_mut_ptr().write(op(addr)) };
41+
if cond(unsafe { &*val.as_mut_ptr() }) {
42+
break;
43+
}
44+
45+
// SAFETY: FFI call.
46+
if timeout_us != 0 && unsafe { bindings::ktime_compare(bindings::ktime_get(), timeout) } > 0
47+
{
48+
unsafe { val.as_mut_ptr().write(op(addr)) };
49+
break;
50+
}
51+
52+
if sleep_us != 0 {
53+
// SAFETY: FFI call.
54+
unsafe { bindings::usleep_range((sleep_us >> 2) + 1, sleep_us) };
55+
}
56+
}
57+
58+
if cond(unsafe { &*val.as_mut_ptr() }) {
59+
// SAFETY: variable is properly initialized as there is at least one write to it at loop.
60+
Ok(unsafe { val.assume_init() })
61+
} else {
62+
Err(Error::ETIMEDOUT)
63+
}
64+
}
65+
66+
/// # Safety
67+
///
68+
/// `addr` must be non-null pointer valid io address.
69+
pub unsafe fn readx_poll_timeout<T, F: Fn(&T) -> bool>(
70+
op: unsafe extern "C" fn(*const c_types::c_void) -> T,
71+
cond: F,
72+
sleep_us: u32,
73+
timeout_us: u64,
74+
addr: *const c_types::c_void,
75+
) -> Result<T> {
76+
// SAFETY: `addr` is valid by the safety contract.
77+
unsafe { read_poll_timeout(op, cond, sleep_us, timeout_us, false, addr) }
78+
}

rust/kernel/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ pub mod file;
5555
pub mod file_operations;
5656
pub mod gpio;
5757
pub mod hw_random;
58+
pub mod iopoll;
5859
pub mod irq;
5960
pub mod miscdev;
6061
pub mod pages;
@@ -177,6 +178,17 @@ impl<'a> Drop for KParamGuard<'a> {
177178
}
178179
}
179180

181+
#[macro_export]
182+
macro_rules! might_sleep_if {
183+
($cond:expr) => {
184+
if $cond {
185+
unsafe {
186+
bindings::might_sleep();
187+
}
188+
}
189+
};
190+
}
191+
180192
/// Calculates the offset of a field from the beginning of the struct it belongs to.
181193
///
182194
/// # Example

0 commit comments

Comments
 (0)