Skip to content
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

Simple Rust driver that touches real hardware: PR 5 #381

Closed
wants to merge 2 commits into from

Conversation

TheSven73
Copy link
Collaborator

Simple Rust driver that touches real h/w: step 5

@alex and @ojeda suggested that #254 should be split into smaller PRs, so they can be reviewed and merged individually.

Roadmap:

  • platform_device
  • devicetree (of) matching
  • probe
  • DrvData
  • Pinning cleanup (you are here)
  • DrvData references in PlatformDriver trait
  • regmap/iomem

@ksquirrel

This comment has been minimized.

of_table: Option<*const c_types::c_void>,
pdrv: bindings::platform_driver,
_pin: PhantomPinned,
pdrv: Box<bindings::platform_driver>,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the code be much more complicated if you had Pin<Box<...>> here?

My impression is that it wouldn't be too different (after the simplification you're applying), and the compiler would give you a little bit of help in enforcing the invariant.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review! I'm glad you are asking this question. I can use it as a vehicle to check and update my understanding of the whole Pinning concept.

As far as I know, using Pin<Box<bindings::platform_driver>> on its own here won't change a thing. It won't prevent the platform_driver from moving out of the Box. Why? Because platform_driver: Unpin by default. So we can always call Pin::into_inner() safely, and the inner object will simply move out without complaints.

If we really want to prevent moving out, we need to mark platform_driver as !Unpin:

impl !Unpin for bindings::platform_driver {}

But Rust doesn't currently allow that!

error[E0658]: negative trait bounds are not yet fully implemented; use marker types for now
  --> rust/kernel/platdev.rs:20:6
   |
20 | impl !Unpin for bindings::platform_driver {}

So we'd have to use PhantomPinned, which in turn requires us to use some wrapper type:

struct pinned_platform_driver {
    inner: bindings::platform_driver,
    _pin: PhantomPinned,
}
// can't move out of Pin<Box<pinned_platform_driver>>, yay !

But now we are storing a different structure, one we can't get an inner mutable reference to without using unsafe blocks. Yet the kernel's platform_register()/platform_unregister() require mutable references !

During registration, it's not so bad, because we can register first, then pin. Which kind of defeats the reason for using Pin<> in the first place.. But the unregistration function now becomes problematic: it needs a mutable reference to a bindings::platform_driver, but that structure is wrapped in a !Unpin outer wrapper, so we need a bunch of unsafe acrobatics there to get the reference.

As far as I can tell, it all becomes a huge unreadable, unsafe mess. IMHO using a "do not move out" Invariant works pretty well. But I am open to suggestions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would not be an invariant, no? i.e. an invariant is something that is guaranteed (as long as one does not break preconditions on e.g. constructors).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be an invariant, but not one the compiler will help enforcing.

Copy link
Collaborator Author

@TheSven73 TheSven73 Jun 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to avoid using Pin<> here (and I think that's probably the way to go, subject to further feedback), then we'll need a way to document / enforce that no code inside platdev::Registration moves pdrv out of its Box. We should not need to enforce this externally, as pdrv is a private member field, and is never exposed externally. Except through a kernel function, but that doesn't count.

What is the best way to accomplish that? Using an invariant? Or some other way?

Copy link
Collaborator Author

@TheSven73 TheSven73 Jun 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the tip! I'm sorry that my confusion is so confusing :) I'd like to ask a few questions, to update my internal model.

When we register a miscdev for example, we pass a pointer to a bindings::miscdevice to the kernel on registration. I'm assuming we need two guarantees:

  • the struct's memory area will continue to exist until after we unregister the miscdevice and
  • the struct's memory area won't be overwritten until after we unregister the miscdevice

The easiest way is probably like this: (a)

struct Registration {
    mdev: Box<bindings::miscdevice>,
    // etc
}
impl Registration {
    fn new() -> Self {
        let mut mdev = Box::new(default());
        unsafe { bindings::misc_register(&mut *mdev) };
        Self { mdev }
    }
}

Yet the existing codebase does the following: (b)

struct Registration {
    mdev: bindings::miscdevice,
    _pin: PhantomPinned,
}
impl Registration {
    fn new() -> Pin<Box<Self>> {
        let mut r = Pin::from(Box::new(Self::default()));
        let mut this = unsafe { /* r.get_mut() and get_unchecked_mut() gymnastics */ };
        unsafe { bindings::misc_register(&mut this.mdev) };
        r
    }
}

So my question is: why does the current codebase choose (b) over (a)? What are the advantages of (b) over (a)?
According to the comments in the code,

    /// It must be pinned because the memory block that represents the registration is
    /// self-referential.

but I am not sure that's a sufficient justification, because (a) is not Pinned, yet also allows self-referential structures.

Copy link
Member

@ojeda ojeda Jun 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds correct to me. If one keeps the things that need to be pinned inside a Box, and the box remains private (i.e. others cannot mess with it), and the implementation of the abstraction takes care of never moving it, then it is OK. It is also OK to Pin the private Box to ensure it is not moved by mistake by the implementation.

Though I do not recall why Registration is pinned here, rather than leaving it a detail of the implementation. @wedsonaf?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Option (b) isn't really what we have in the current codebase. What we have is (simplified):

impl Registration {
    fn new() -> Self {
        [...]
    }
}

Then as a helper we have:

    fn new_pinned() -> Result<Pin<Box<Self>>> {
        [...]
    } 

And the reason is that we want to have a zero-cost abstraction. If we forced everyone to use new_pinned or option (a) above, we would require everyone to have an extra allocation. Here's an example (C Binder) on how miscdev is embedded in some outer structure: https://elixir.bootlin.com/linux/latest/source/drivers/android/binder_internal.h#L36 -- it would require an extra allocation if we were to require miscdev to be always boxed; such a solution would preclude the zero-cost case, but the current implementation allows both: if you want zero-cost, you embed Registration in your outer structure and ensure that it's pinned (by pinning the outer structure and ensuring that the projection is also pinned), but if you don't care about zero-cost, just use new_pinned() and pay the cost of the extra allocation.

Regarding Sven's example, that is perfectly fine: note that pd1.set(pd2) causes the destructor of whatever was pinned in pd1 to be called before it is overwritten by pd2, that is, if what was initially pinned was a Registration, its destructor would unregister it first, then it would be overwritten with the new one.

Copy link
Collaborator Author

@TheSven73 TheSven73 Jun 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wedsonaf and @ojeda, thank you so much for your patience and willingness to explain, I really appreciate it !! Let’s see if I understand the underlying issues better now. I encourage you to update my understanding if something doesn’t sound quite right.

When we create an abstraction that uses a kernel C structure in Rust, e.g. via misc_register() or __mutex_init(), there are a couple of “gotchas”:

  1. We must not allow our users to modify the structure, other than through the proper C APIs and/or rules.
  2. We must not allow our users to move the kernel C structure around in memory.

(1) is easy. Users from outside the kernel crate can’t see private fields, so we can always make the C structure private, and ensure our public functions use it as prescribed in the kernel.

(2) is trickier! If the C structure is just a straight-forward field inside our Rust struct, then our users could move it around in memory, which breaks the C API. There are two solutions to this problem:

  1. Store the C structure in a private, Boxed field of the Rust struct. Our users can’t touch it because it’s private. They also can’t move it around - moving the Rust structure in memory is fine, because it’s just holding a pointer to the Boxed C struct.
  2. Store the C structure inside the Rust structure in a straightforward way. And prevent our users from moving it around using various techniques. Such as # Safety requirements, or method functions with a self type that’s Pin<&Self> or Pin<&mut Self>. All of these require our users to use unsafe blocks and make careful reasonings about pinning and safety.

All are partially flawed. Solution (1) leads to multiple allocations and indirections, which is a non-zero cost abstraction - hey that’s not what Rust is supposed to be about! Solution (2) is true zero-cost, yet requires users to write tricky unsafe code just to use what are essentially very simple abstractions. They have a good chance of getting it wrong, and go down in flames - hey that’s also not what Rust is all about!

I am not sure what the group consensus is here. Are the unsafe blocks and tricky pin projections in driver code not seen as a problem?

For platdev I will go with solution (1) for now, which is easy to use for driver writers, yet leads to multiple allocations and indirections. For a stone-cold path such as registering a platform driver (not a device), that shouldn't be a problem.

Edited to add: I'm assuming a solution along the lines of pin-project has been considered at some point, and rejected?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think your understanding is correct!

I think it is reasonable to favor the easiest solution for cold paths, although people may still complain because, in the case of allocations, they may lead to other issues outside the cold path (say, memory fragmentation with SLOB).

Then there is the problem of having to return more Results. And, in general, the overall feeling that the language "forces" us to do allocations just to have a nicer API.

I don't think what way would be best for us, but I tend to think that medium-/long-term we should invest in finding out the best ergonomic solution while keeping the zero-cost (via the pin-*-like crates, a new language feature, or something else).

let mut r = Pin::from(Box::try_new(Self::default())?);
r.as_mut().register::<P>(name, of_match_tbl, module)?;
Ok(r)
Ok(Self { of_table, pdrv })

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you'd only have to pin here.

Sven Van Asbroeck added 2 commits June 29, 2021 11:35
`register()` serves no purpose, except to complicate `Registration`
construction. It's a copy-and-paste leftover from miscdev.

Eliminate this function to get the following benefits:
- `bool registered` disappears, which simplifies logic flow
- an `unsafe` block disappears

Signed-off-by: Sven Van Asbroeck <thesven73@gmail.com>
`Registration` contains a self-referential structure.
We need to ensure that users never move that structure around.
This is currently done by marking `Registration` as `!Unpin`,
and returning it as a `Pin<Box<Registration>>`, which users cannot
move out of - not without the use of `unsafe`, anyway.

However, the self-referenial structure is an implementation detail,
which we are pushing onto users, by using `Pin` in the public function
signature.

Treat the self-referential structure as a true, internal implementation
detail: store it into a private `Box` member of `Registration`. This is
safe as long as `Registration` never moves that structure out of its
`Box`.

Now `Registration` can safely be moved by its users, and there is no
longer a need to mark `Registration` as `!Unpin`, or return it
`Pin`ned to users. This greatly simplifies and clarifies driver-side
code.

Signed-off-by: Sven Van Asbroeck <thesven73@gmail.com>
@TheSven73 TheSven73 force-pushed the rust-for-linux-pr5 branch from 9888b04 to 221d99b Compare June 29, 2021 16:32
@ksquirrel
Copy link
Member

Review of 221d99b96378:

  • ✔️ Commit dbbd1dc: Looks fine!
  • ✔️ Commit 221d99b: Looks fine!

@TheSven73
Copy link
Collaborator Author

v1 -> v2:

@TheSven73
Copy link
Collaborator Author

PTAL. Do we have a consensus that using a private Box + comment is strict enough for a C structure shared with the kernel, that must not move? See explanation in #381 (comment) as to why Pin<Box<T>> is problematic.

If not, I'll just drop this PR. But I'd like us to make a decision, so we can move on.

@nbdd0121
Copy link
Member

A private Box + comment is sufficient for C structures that can't move. Rust std Mutex uses pthread mutex in the same way.

However I am uncertain that we want an internal allocation, because that'll make platdev::Registration API differ from miscdev::Registration.

@TheSven73
Copy link
Collaborator Author

We can reopen if there is more consensus on Pinning.

@TheSven73 TheSven73 closed this Jul 2, 2021
@TheSven73 TheSven73 deleted the rust-for-linux-pr5 branch July 8, 2021 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

6 participants