Re: [TECH TOPIC] Rust for Linux

Wedson Almeida Filho
2021-07-19T12:24:55Z

On Fri, Jul 09, 2021 at 12:13:25AM +0200, Linus Walleij wrote:
> I have seen that QEMU has a piece of code for the Arm PrimeCell
> PL061 GPIO block which corresponds to drivers/gpio/gpio-pl061.c
> Note that this hardware apart from being used in all Arm reference
> designs is used on ARMv4T systems that are not supported by
> LLVM but only GCC, which might complicate things.

Here is a working PL061 driver in Rust (converted form the C one):
https://raw.githubusercontent.com/wedsonaf/linux/pl061/drivers/gpio/gpio_pl061_rust.rs

(I tested it on QEMU through the sysfs interface and also gpio-keys as QEMU
uses one of the PL061 pins as the power button.)

I have a long list of ways in which Rust affords us extra guarantees but in the
interest of brevity I will try to describe how Rust helps us address the two (or
more) lifetime issues Greg mentioned the other day.

Rust allows us to build abstractions that guarantee safety. Here are the ones I
used/built for this:

1. State created on `probe` is ref-counted.
2. Hardware resources (device mem and irq in this case) are "revocable".
3. On `remove`, we automatically revoke access to hardware resources, then free
them.

What this gives us:
1. With ref-counted objects Rust allows us to avoid dangling pointers. No more
UAF because memory was freed when the device was removed. (C can also do this,
of course, but the compiler doesn't help us if/when we forget to
increment/decrement the ref count.)
2. Given that references to device state may outlive the device, revocable hw
resources allows us to prevent the use of these resources after the device is
gone. Rust ensures that such access is only allowed before resources are
revoked. (In C we can also do something similar, but the compiler won't enforce
this invariant for us, i.e., we can make mistakes where we forget to check if
something was revoked, or forget to hold locks keeping resources alive, etc.)
3. After revoking access, we need to ensure that existing concurrent users
finish before we can free resources. In this implementation, we use RCU so that
resource users need to hold an RCU read lock and we ensure that they've also
completed their use before freeing the resources (synchronize_rcu between
revoking & freeing). Locking/unlocking happens automatically.

This, naturally, doesn't solve any problems with the existing C code. However, I
think it addresses things on the Rust side. For example, suppose that in
addition to registering with gpio, we also wanted to expose the device as a
miscdev (I use this as an example because we have miscdevs in Rust). The
refcounted device state can be stored in the miscdev registration, and each
opened file can also have a reference to it (device state). We don't control
when the latter gets released, but it's ok for them to hold on to state because
they won't be able to use hw resources after the device is removed; once all
file descriptors are closed, the refcount goes to zero and the memory is freed.

Any thoughts on this?

(A quick disclaimer: I'm sure there are scenarios that don't fit exactly with
this, but the intent ATM is not to cover all scenarios, it's just to show a
working example of what Rust enables. Eventually we want to generalise these
ideas in cooperation with maintainers, who know about all scenarios and subtle
issues.)

Cheers,
-Wedson