Skip to content

Commit 839b653

Browse files
author
Sven Van Asbroeck
committed
rust/kernel/of: construct of_match_table structure at build time
Use const generics and const fns to construct our `of_match_table` structure at build time. A few runtime errors now become build time errors, which is a good thing. If the platdev's `Registration` structure takes a const generics table object directly, it acquires/inherits the table's const generics, which looks very complex. Prevent this from happening by passing the table to `Registration` as a non-generic "reference object" in the style of `&str` and `&Path`. Suggested-by: Gary Guo <[email protected]> Signed-off-by: Sven Van Asbroeck <[email protected]>
1 parent c93f25f commit 839b653

File tree

3 files changed

+85
-72
lines changed

3 files changed

+85
-72
lines changed

drivers/char/hw_random/bcm2835_rng_rust.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use kernel::{
1212
file_operations::{FileOpener, FileOperations},
1313
io_buffer::IoBufferWriter,
1414
miscdev,
15-
of::OfMatchTable,
15+
of::ConstOfMatchTable,
1616
platdev::PlatformDriver,
1717
prelude::*,
1818
{c_str, platdev},
@@ -72,11 +72,12 @@ struct RngModule {
7272

7373
impl KernelModule for RngModule {
7474
fn init() -> Result<Self> {
75-
let of_match_tbl = OfMatchTable::new(&c_str!("brcm,bcm2835-rng"))?;
75+
const OF_MATCH_TBL: ConstOfMatchTable<1> =
76+
ConstOfMatchTable::new_const([&c_str!("brcm,bcm2835-rng")]);
7677

7778
let pdev = platdev::Registration::new_pinned::<RngDriver>(
7879
c_str!("bcm2835-rng-rust"),
79-
Some(of_match_tbl),
80+
Some(&OF_MATCH_TBL),
8081
&THIS_MODULE,
8182
)?;
8283

rust/kernel/of.rs

Lines changed: 75 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,72 +4,93 @@
44
//!
55
//! C header: [`include/linux/of_*.h`](../../../../include/linux/of_*.h)
66
7-
use alloc::boxed::Box;
7+
use crate::{bindings, c_types, str::CStr};
88

9-
use crate::{
10-
bindings, c_types,
11-
error::{Error, Result},
12-
str::CStr,
13-
types::PointerWrapper,
14-
};
9+
use core::ops::Deref;
10+
use core::ptr;
1511

16-
use core::mem::transmute;
12+
/// A kernel Open Firmware / devicetree match table.
13+
///
14+
/// Can only exist as an `&OfMatchTable` reference (akin to `&str` or
15+
/// `&Path` in Rust std).
16+
#[repr(transparent)]
17+
pub struct OfMatchTable(bindings::of_device_id);
1718

18-
type InnerTable = Box<[bindings::of_device_id; 2]>;
19+
impl<const N: usize> Deref for ConstOfMatchTable<N> {
20+
type Target = OfMatchTable;
1921

20-
/// Wraps a kernel Open Firmware / devicetree match table.
21-
///
22-
/// Rust drivers may create this structure to match against devices
23-
/// described in the devicetree.
24-
///
25-
/// The ['PointerWrapper'] trait provides conversion to/from a raw pointer,
26-
/// suitable to be assigned to a `bindings::device_driver::of_match_table`.
27-
///
28-
/// # Invariants
29-
///
30-
/// The final array element is always filled with zeros (the default).
31-
pub struct OfMatchTable(InnerTable);
22+
fn deref(&self) -> &OfMatchTable {
23+
let head = &self.table[0] as *const bindings::of_device_id as *const OfMatchTable;
24+
// The returned reference must remain valid for the lifetime of `self`.
25+
// The raw pointer `head` points to memory inside `self`. So the reference created
26+
// from this raw pointer has the same lifetime as `self`.
27+
// Therefore this reference is safe to return.
28+
unsafe { &*head }
29+
}
30+
}
3231

3332
impl OfMatchTable {
34-
/// Creates a [`OfMatchTable`] from a single `compatible` string.
35-
pub fn new(compatible: &'static CStr) -> Result<Self> {
36-
let tbl = Box::try_new([
37-
Self::new_of_device_id(compatible)?,
38-
bindings::of_device_id::default(),
39-
])?;
40-
// INVARIANTS: we allocated an array with `default()` as its final
41-
// element, therefore that final element will be filled with zeros,
42-
// and the invariant above will hold.
43-
Ok(Self(tbl))
33+
/// Return the table as a static lifetime, sentinel-terminated C array.
34+
///
35+
/// This is suitable to be assigned to the kernel's `of_match_table` field.
36+
pub fn as_ptr(&'static self) -> *const bindings::of_device_id {
37+
&self.0
4438
}
39+
}
4540

46-
fn new_of_device_id(compatible: &'static CStr) -> Result<bindings::of_device_id> {
47-
let mut buf = [0_u8; 128];
48-
if compatible.len() > buf.len() {
49-
return Err(Error::EINVAL);
41+
/// An Open Firmware Match Table that can be constructed at build time.
42+
///
43+
/// # Invariants
44+
///
45+
/// `sentinel` always contains zeroes.
46+
#[repr(C)]
47+
pub struct ConstOfMatchTable<const N: usize> {
48+
table: [bindings::of_device_id; N],
49+
sentinel: bindings::of_device_id,
50+
}
51+
52+
impl<const N: usize> ConstOfMatchTable<N> {
53+
/// Create a new Open Firmware Match Table from a list of compatible strings.
54+
pub const fn new_const(compatibles: [&'static CStr; N]) -> Self {
55+
let mut table = [Self::zeroed_of_device_id(); N];
56+
let mut i = 0;
57+
loop {
58+
if i == N {
59+
break;
60+
}
61+
table[i] = Self::new_of_device_id(compatibles[i]);
62+
i += 1;
63+
}
64+
Self {
65+
table,
66+
// INVARIANT: we zero the sentinel here, and never change it
67+
// anwhere. Therefore it always contains zeroes.
68+
sentinel: Self::zeroed_of_device_id(),
5069
}
51-
buf.get_mut(..compatible.len())
52-
.ok_or(Error::EINVAL)?
53-
.copy_from_slice(compatible.as_bytes());
54-
Ok(bindings::of_device_id {
55-
// SAFETY: re-interpretation from [u8] to [c_types::c_char] of same length is always safe.
56-
compatible: unsafe { transmute::<[u8; 128], [c_types::c_char; 128]>(buf) },
57-
..Default::default()
58-
})
5970
}
60-
}
6171

62-
impl PointerWrapper for OfMatchTable {
63-
fn into_pointer(self) -> *const c_types::c_void {
64-
// Per the invariant above, the generated pointer points to an
65-
// array of `bindings::of_device_id`, where the final element is
66-
// filled with zeros (the sentinel). Therefore, it's suitable to
67-
// be assigned to `bindings::device_driver::of_match_table`.
68-
self.0.into_pointer()
72+
const fn zeroed_of_device_id() -> bindings::of_device_id {
73+
bindings::of_device_id {
74+
name: [0; 32usize],
75+
type_: [0; 32usize],
76+
compatible: [0; 128usize],
77+
data: ptr::null(),
78+
}
6979
}
7080

71-
unsafe fn from_pointer(p: *const c_types::c_void) -> Self {
72-
// SAFETY: The passed pointer comes from a previous call to [`InnerTable::into_pointer()`].
73-
Self(unsafe { InnerTable::from_pointer(p) })
81+
const fn new_of_device_id(compatible: &'static CStr) -> bindings::of_device_id {
82+
let mut id = Self::zeroed_of_device_id();
83+
let compatible = compatible.as_bytes_with_nul();
84+
let mut i = 0;
85+
loop {
86+
if i == compatible.len() {
87+
break;
88+
}
89+
// if `compatible` does not fit in `id.compatible`, an
90+
// "index out of bounds" build time exception will be triggered.
91+
id.compatible[i] = compatible[i] as c_types::c_char;
92+
i += 1;
93+
}
94+
id
7495
}
7596
}

rust/kernel/platdev.rs

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ use core::{marker::PhantomPinned, pin::Pin};
2121
#[derive(Default)]
2222
pub struct Registration {
2323
registered: bool,
24-
of_table: Option<*const c_types::c_void>,
2524
pdrv: bindings::platform_driver,
2625
_pin: PhantomPinned,
2726
}
@@ -83,7 +82,7 @@ impl Registration {
8382
fn register<P: PlatformDriver>(
8483
self: Pin<&mut Self>,
8584
name: &'static CStr,
86-
of_match_table: Option<OfMatchTable>,
85+
of_match_table: Option<&'static OfMatchTable>,
8786
module: &'static crate::ThisModule,
8887
) -> Result {
8988
// SAFETY: We must ensure that we never move out of `this`.
@@ -94,9 +93,7 @@ impl Registration {
9493
}
9594
this.pdrv.driver.name = name.as_char_ptr();
9695
if let Some(tbl) = of_match_table {
97-
let ptr = tbl.into_pointer();
98-
this.of_table = Some(ptr);
99-
this.pdrv.driver.of_match_table = ptr.cast();
96+
this.pdrv.driver.of_match_table = tbl.as_ptr();
10097
}
10198
this.pdrv.probe = Some(probe_callback::<P>);
10299
this.pdrv.remove = Some(remove_callback::<P>);
@@ -105,10 +102,9 @@ impl Registration {
105102
// - `name` pointer has static lifetime.
106103
// - `module.0` lives at least as long as the module.
107104
// - `probe()` and `remove()` are static functions.
108-
// - `of_match_table` is either:
109-
// - a raw pointer which lives until after the call to
110-
// `bindings::platform_driver_unregister()`, or
111-
// - null.
105+
// - `of_match_table` is either a raw pointer with static lifetime,
106+
// as guaranteed by the [`of::OfMatchTable::as_ptr()`] invariant,
107+
// or null.
112108
let ret = unsafe { bindings::__platform_driver_register(&mut this.pdrv, module.0) };
113109
if ret < 0 {
114110
return Err(Error::from_kernel_errno(ret));
@@ -122,7 +118,7 @@ impl Registration {
122118
/// Returns a pinned heap-allocated representation of the registration.
123119
pub fn new_pinned<P: PlatformDriver>(
124120
name: &'static CStr,
125-
of_match_tbl: Option<OfMatchTable>,
121+
of_match_tbl: Option<&'static OfMatchTable>,
126122
module: &'static crate::ThisModule,
127123
) -> Result<Pin<Box<Self>>> {
128124
let mut r = Pin::from(Box::try_new(Self::default())?);
@@ -139,11 +135,6 @@ impl Drop for Registration {
139135
// safe to call.
140136
unsafe { bindings::platform_driver_unregister(&mut self.pdrv) }
141137
}
142-
if let Some(ptr) = self.of_table {
143-
// SAFETY: `ptr` came from an `OfMatchTable`.
144-
let tbl = unsafe { OfMatchTable::from_pointer(ptr) };
145-
drop(tbl);
146-
}
147138
}
148139
}
149140

0 commit comments

Comments
 (0)