-
Notifications
You must be signed in to change notification settings - Fork 465
rust/kernel/of: construct of_match_table
structure at build time
#380
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
rust/kernel/of: construct of_match_table
structure at build time
#380
Conversation
This comment has been minimized.
This comment has been minimized.
631ff9c
to
a04a783
Compare
This comment has been minimized.
This comment has been minimized.
rust/kernel/of.rs
Outdated
/// | ||
/// The final array element is always filled with zeros (the default). | ||
pub struct OfMatchTable(InnerTable); | ||
pub trait OfMatchTable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think trait is correct abstraction here. It should look more like CStr
and CBoundedStr
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You think so? All an OfMatchTable
consumer (i.e. a driver) needs, is a way to retrieve a static-lifetime pointer to the C table, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect the strange thing you observe is related to the usage of incomplete features.
I haven't seen any strange things yet - I was just wondering if the array behaviour could be different for platforms I cannot test. Good to hear that it's correct, and no repr
is required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nbdd0121 can you elaborate why trait isn't the correct abstraction here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Conceptually OfMatchTable
is to ConstOfMatchTable
as slice is to array. Obvious slice is not abstracted as a trait that all arrays implement.
I think what you should do here is to have a
/// INVARIANT: end with a sentinel
#[repr(transparent)]
pub struct OfMatchTable([bindings::of_device_id]);
or maybe
/// INVARIANT: point to a slice that end with a sentinel
#[repr(transparent)]
pub struct OfMatchTable(bindings::of_device_id);
and then have ConstOfMatchTable
implement Deref<Target=OfMatchTable>
. This way you'll only need to accept a &'static OfMatchTable
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with you that, in general, a String/&str
type abstraction pair looks prettier than dyn trait
. However, I can't find a way to implement your suggestion without using unsafe code:
/// Wraps a kernel Open Firmware / devicetree match table.
#[repr(transparent)]
pub struct OfMatchTable([bindings::of_device_id]);
impl<const N: usize> Deref for ConstOfMatchTable<N> {
type Target = OfMatchTable;
fn deref(&self) -> &OfMatchTable {
unsafe { &*(&self.table[..] as *const [bindings::of_device_id] as *const OfMatchTable) }
}
}
That's really unfortunate, because the dyn trait
solution uses only safe Rust. Even coercing the *const
reference is fully safe, as the pointer has 'static
lifetime.
Would I be able to convince you to prefer the slightly more unseemly, yet safe, abstraction, over the pretty-yet-unsafe version?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the kernel
crate should aim to provide ergonomic abstractions, accepting internal usage of unsafe. What kernel
crate is to the drivers is pretty similar to what std
is to userspace Rust programs, and it is ubiquitous in std
to perform this sort of cast (e.g. https://doc.rust-lang.org/stable/src/std/path.rs.html#1791-1793).
I think we should stop demonizing unsafe
s and avoiding them at all cost. If usage of unsafe
is helpful, then use it and just justify why it's safe with safety comments. It's not "pretty-yet-unsafe" abstraction; it is a pretty-and-safe abstraction, with a justified use of unsafe
operation internally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind - I think there is a way that is provably correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should stop demonizing
unsafe
s and avoiding them at all cost. If usage ofunsafe
is helpful, then use it and just justify why it's safe with safety comments. It's not "pretty-yet-unsafe" abstraction; it is a pretty-and-safe abstraction, with a justified use ofunsafe
operation internally.
The main reason I want rust in the kernel is its safe subset. I generally agree that when the use of unsafe brings a great usability improvement, then we should consider it. However, I think we should still avoid it -- we are all susceptible to making mistakes, having the compiler catch them for us is highly desirable, especially in the kernel crate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a04a783
to
a3ce5a6
Compare
This comment has been minimized.
This comment has been minimized.
v1 -> v2
|
a3ce5a6
to
839b653
Compare
This comment has been minimized.
This comment has been minimized.
v2 -> v3nbdd0121:
|
839b653
to
d9f0c59
Compare
This comment has been minimized.
This comment has been minimized.
v3 -> v4wedsonaf:
|
Your "safe" abstraction is unsound: struct Foo;
impl OfMatchTable for Foo {
fn as_ptr(&'static self) -> *const bindings::of_device_id {
core::ptr::null()
}
}
static foo: Foo = Foo;
let table: &'static dyn OfMatchTable = &foo;
// Using table causes UB. To solve soundness you'll have to mark trait |
Thanks for noticing Gary ! We should be able to make this sound without the use of |
d9f0c59
to
4052cc0
Compare
This comment has been minimized.
This comment has been minimized.
v4 -> v5nbdd0121:
|
No, it's still unsound unfortunately; the last element needs to be sentinel which cannot be enforced: struct Foo;
impl OfMatchTable for Foo {
fn as_ptr(&'static self) -> &'static bindings::of_device_id {
static DEVICE_ID: bindings::of_device_id = /* something */;
// This does not end with sentinel, so is still unsound
&DEVICE_ID
}
} |
Hm, this problem exists independently of whether we use a |
This won't be an issue for |
Edited: I think I understand now... Thanks ! Sounds like we do need the |
4052cc0
to
99bd13c
Compare
This comment has been minimized.
This comment has been minimized.
v5 -> v6nbdd0121:
|
99bd13c
to
9b3718e
Compare
This comment has been minimized.
This comment has been minimized.
9b3718e
to
0ba9760
Compare
This comment has been minimized.
This comment has been minimized.
Rebased against the latest |
rust/kernel/of.rs
Outdated
Self { | ||
table, | ||
// INVARIANTS: we zero the sentinel here, and never change it | ||
// anwhere. Therefore it always contains zeroes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: typo: anywhere
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]>
0ba9760
to
5b2ed14
Compare
Review of
|
Corrected typo |
let compatible = compatible.as_bytes_with_nul(); | ||
let mut i = 0; | ||
while i < compatible.len() { | ||
// if `compatible` does not fit in `id.compatible`, an |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// if `compatible` does not fit in `id.compatible`, an | |
// If `compatible` does not fit in `id.compatible`, an |
let head = &self.table[0] as *const bindings::of_device_id as *const OfMatchTable; | ||
// SAFETY: The returned reference must remain valid for the lifetime of `self`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let head = &self.table[0] as *const bindings::of_device_id as *const OfMatchTable; | |
// SAFETY: The returned reference must remain valid for the lifetime of `self`. | |
let head = &self.table[0] as *const bindings::of_device_id as *const OfMatchTable; | |
// SAFETY: The returned reference must remain valid for the lifetime of `self`. |
// `bindings::platform_driver_unregister()`, or | ||
// - null. | ||
// - `of_match_table` is either a raw pointer with static lifetime, | ||
// as guaranteed by the [`of::OfMatchTable::as_ptr()`] invariant, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// as guaranteed by the [`of::OfMatchTable::as_ptr()`] invariant, | |
// as guaranteed by the [`of::OfMatchTable::as_ptr()`] return type, |
?
let mut i = 0; | ||
while i < compatible.len() { | ||
// if `compatible` does not fit in `id.compatible`, an | ||
// "index out of bounds" build time exception will be triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// "index out of bounds" build time exception will be triggered. | |
// "index out of bounds" build time error will be triggered. |
Some nits for later since it was merged :-P |
Oops, sorry Miguel, I thought there were no more comments on this. |
@ojeda I can do a quick PR for those nits, if you like. |
Some documentation suggestions on Rust-for-Linux#380 were not applied when that PR was merged. Apply them in this PR instead. Signed-off-by: Sven Van Asbroeck <[email protected]>
@wedsonaf No worries! |
PS @nbdd0121 Thank you for your advice and expertise with this PR Gary, I really appreciate it. |
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.
To avoid the const generics propagating into the platdev's
Registration
structure definition, use adyn trait
to passthe table into the registration.
Signed-off-by: Sven Van Asbroeck [email protected]