12 ms·
A GPIO Driver in Rust
- Animats 5y agoAnd in the end, it's basically safe PEEK and POKE.
- wahern 5y agoThe unsafe readb and writeb is hidden inside another API--IoMem module. The same API could be created for C, but isn't--either deliberately or just because nobody bothered.
- deleted 5y ago[deleted]
- bsder 5y agoThis ... isn't really a great example--either of Rust or of embedded programming in Rust. Someone should ask one of the Rust Embedded folks to review this and provide commentary.
- jabedude 5y ago> This ... isn't really a great example--either of Rust or of embedded programming in Rust. Why not? You could also feel free to respond to the mailing list with your critiques
- stefan_ 5y agoAll the Rust Embedded code I've seen is off the rails deep into "C++ template meta programming" and the absolute antithesis of what to do. Even the code here, given the absolute impossibility to detect errors with something like a GPIO chip, has too many Results. It's an incredible code smell for "terrible abstractions".
- Jweb_Guru 5y agoThe Results here aren't from the GPIO operations, so that's not really relevant.
- MrBuddyCasino 5y agoThanks! So its no just me. This feels like reading one of those Haskell-Blogs: certainly impressive, where author explains the dimensions along which this solution is superior, but its just incomprehensible brainfuck and doesn't pass any standards of beauty. I've written some Rust; I know it is workable, and "if it compiles it works" is mostly true. But its so ugly.
- stormbrew 5y agoThis is very nearly a mechanical line by line conversion of the C to rust. What on earth is so much uglier about it that it renders it "incomprehensible brainfuck"? Is this satire and I'm missing the joke
- db48x 5y agoI assume that they are talking about the abstractions that people sometimes build when working with hardware at a low level. In Rust you can literally build the hardware restrictions into your types, so that for example all registers (or pins in this case) have both read and write methods except the ones that the hardware does not allow you to write to; those only have read methods. Or think about access control: it’s not ok for two programs (or two kernel threads) to interleave accesses to the same GPIO pin, for example. You decide that you want to have some kind of handle or struct that lets you read and write to the pin, but that cannot be duplicated or dropped. This way you can create that object once during initialization, and then pass it to whoever needs it. If you want to call something that needs access to the pin, you then have to pass it the object and you can only regain access to the pin if it passes the object back as a return value. The compiler just won’t let you interleave accesses, because the code won’t even compile if you try to do something not allowed. But all of those neat features add complexity, and often some people just prefer to write comments instead. Usually I recommend starting with very straight–forward code and only adding abstractions in later refactoring steps. Another option is to write the usage code first; this can help you design abstractions without going full architecture astronaut.
- bsder 5y agoI would be interested in what you think of something like BBQueue: https://github.com/jamesmunns/bbqueue https://github.com/jamesmunns/bbqueue
- the__alchemist 5y agoExactly! I think the point of OP's example is how to write clean driver code. Much of the Rust embedded code available open source is a mess of abstractions, with no specific use case in mind. Ie, no way to weight if the abstractions' utility is worth their complications. This includes code by the Rust Embedded Working Group.
- axegon_ 5y agoI'm already in bed so reading the code on my phone is really painful. That said, at first glance this looks really promising. This is great news on two fronts: on one hand this will drastically simplify supporting devices and would speed up development a lot. Also it's great news for rust: the fact that it's been used in a place where it can show it's potential and isn't another case of "let's use rust for the sake of using rust", which is a lot more common than I used to imagine.
- gnull 5y ago> the fact that it's been used in a place where it can show it's potential And more importantly, a place where C++ has failed.
- brundolf 5y agoDo you have specific knowledge of how C++ tried and failed to apply to this usecase?
- gnull 5y agoEven though neither Rust nor C++ are supported Kernel languages, Rust has passed a few iterations of trial and discussion, C++ on the other hand was filtered out at the first one. Linus refused to even take C++ seriously (and also mentioned trying it for Kernel in 1992) [1]. Some might argue this doesn't qualify as "C++ trying", but I think it does. [1]: http://harmful.cat-v.org/software/c++/linus http://harmful.cat-v.org/software/c++/linus
- bsdubernerd 5y agoI've been programming on embedded with c++ for quite a while and I really can't agree with linus here. Just the superior type checking, being able to use RAII and simple templates to replace macros completely transform the way you work on a fundamental level without doing anything fancy. Although it's not as pleasant as it should be given its historical baggage (the bad rap is sadly well deserved), I still consider it to be miles ahead of C for embedded work to the point that while I do like Rust a lot, I still consider it to be overhyped in this area.
- uncomputation 5y agoThis is a perfect resource for exactly the use case Rust is (supposedly?) designed for. It’s a great, brief way to really get the gist of the language.
- saghm 5y agoIn the Rust code, where is the Result type they're using defined? I assume that the parameterless `Result` return types are intended to be `Result<()>`, and I suppose it might be possible to achieve this with default generics, but I've never seen that used before, so it sticks out as a bit odd to me.
- nicoburns 5y agoI see they're importing `kernel::prelude::*`, so I would guess there.
- swsieber 5y agoIt looks like something like that is indeed possible. See a post on the rust internals forum where that's setup with a type alias. It was called `Fallible` in that post, but it's common in the Rust ecosystem to override the default Result type with a custom error parameter but still calling it Result. https://internals.rust-lang.org/t/make-stds-result-a-type-alias-with-default-type-parameters/4619/5 https://internals.rust-lang.org/t/make-stds-result-a-type-al... So I'd guess in this driver that the Result type being used is a type alias defined in the prelude that's glob imported.
- saghm 5y ago> it's common in the Rust ecosystem to override the default Result type with a custom error parameter but still calling it Result. Yep, I do that all the time! I never considered putting a default value for the parameter though
- dexwiz 5y agoLook's like kernel::Result does just that. It is re-exported as part of the prelude. https://rust-for-linux.github.io/docs/kernel/type.Result.html https://rust-for-linux.github.io/docs/kernel/type.Result.htm...
- saghm 5y agoInteresting! TIL
- secondcoming 5y agoIn the function 'pl061_irq_type' the original code has (line 224): writeb(gpiois, pl061->base + GPIOIS); writeb(gpioibe, pl061->base + GPIOIBE); writeb(gpioiev, pl061->base + GPIOIEV); but the translated code has a differing order: pl061.base.writeb(gpioiev, GPIOIEV); pl061.base.writeb(gpiois, GPIOIS); pl061.base.writeb(gpioibe, GPIOIBE); Isn't the order of operations important when talking to I/O?
- uxp100 5y agoI doubt order of operations is important here, but nice catch, I bet that was not on purpose.
- geocar 5y ago> Isn't the order of operations important when talking to I/O? Sometimes, yes. It’s hard to tell if this is one of those times without a closer look at the documentation or at least some experimentation, but because “Rust is Memory Safe(tm)” I think it is safe assume this is fine simply because it compiled, and without knowing anything about you, would recommend you assume the same, because, well, Rust. Obviously.
- karatinversion 5y ago> it is safe assume this is fine simply because it compiled, and without knowing anything about you I think this is sarcasm, but for the record, no compiler can make guarantees about the behaviour of a different computer.
- geocar 5y ago> I think this is sarcasm Yes. Maybe satire as well. It’s hard to tell because most comments about rust’s Memory Safety are utter gibberish as well. > no compiler can make guarantees about the behaviour of a different computer. Honestly, I think it’s worse than that: the computer simply cannot tell you what your program is going to do. This should be obvious, and yet many people are using Rust in prayer of exactly this.
- 5y ago
- Santosh83 5y agoAllowing two languages in such a large, collaborative project like Linux may give rise to friction and issues in the future. There are not many people who are expert in both C and Rust who could review pull requests that lie at the border between the two in the kernel. Or when an apparent bug in Rust code is actually caused by calling out to a C API, or vice versa. All this eventually means most kernel contributors in the future will have to become well-versed in both C and Rust, eventually. And in the meantime the high-level, subsystem maintainers are going to have to learn enough Rust to review submissions.
- jillesvangurp 5y agoIt's a small price to pay for improved safety with respect to memory and concurrency, which is why Rust is being allowed into the kernel. I think we'll see a gradual shift where some corners of the kernel are Rust only and others stay C; with occasional efforts to replace one with the other where that makes sense and where there are enough people to do the work and get it reviewed. Ultimately that's what will drive this: people and their contributions. If other large code bases where Rust was introduced, e.g. Fuchsia, Firefox, etc. are an indication, we should expect some positive outcomes over time.
- brundolf 5y agoI've never seen learning a new language as a huge barrier. I've learned new languages on the job several times, and I'm probably not as smart as a large portion of Linux's contributors (especially the ones who would be reviewing pull requests). I think they can work it out.
- the__alchemist 5y agoI wrote a GPIO driver in Rust for STM32. It's macro and DRY soup! Although this is because I wanted a single `Pin` type abstraction, while STM32 register blocks are defined at the port level. (STM32 has ~7 GPIO ports depending on variant) Results in a clean API, but a messy implementation. Alternatively, I could have written a port-based approach that would have had a cleaner implementation, but a more complex API.
- chrismorgan 5y agoImporting DerefMut and not Deref surprised me: I’ve never done it and never expect to, since you can’t implement DerefMut without implementing Deref. Turns out it was being imported to call it, which is something I’ve never done and never expect to: instead of writing `d.deref_mut()`, I’d write something like `&mut d` or `&mut **d`. (Autodereferencing makes the question of what asterisks are needed always a bit fun—but that largely applies to d.deref_mut() too: I think (*d).deref_mut() would produce the same result.)
- brundolf 5y agoThis sort of thing is mostly a matter of preference; Rust does a good job being unopinionated about things like how you invoke a function. In this particular case I think the explicit call is more readable than the character-soup, personally
- chrismorgan 5y agoI think it might be trying to get at “dereferencing this does something special” (i.e. it’s not just a reference, but some type that manually implements Deref/DerefMut). But it doesn’t actually do that—if you have a &mut String, .deref_mut() will give a &mut str (equivalent to &mut *self), but if you have a &mut str, .deref_mut() will give a &mut str too, by doing reference reborrowing (so the final result is equivalent to &mut **self). In the end, I’d prefer to deal with the sigils, because I think it’s a little easier to be confident what they’re going to do than calling the method that takes &mut self but effectively changes how it behaves depending on the type in question.
- nusaru 5y agoI feel that this would be a tad bit easier to read with syntax highlighting.
- wongarsu 5y agoIf it had syntax highlighting someone would ask if their email client now has to implement syntax highlighting too, and comment on how they've always discussed unhighlighted C code just fine.
- mlindner 5y agoThe original did have syntax highlighting (it's in the linked comment) but when whoever posted it to lwn, they stripped that out.
- larodi 5y agosorry to say this, but Rust version is much more difficult to read and comprehend than the C version. cannot comment on the benefits from memory-safety perspective, but from maintenance point of view the C version would be much easier to keep going. my wild guess is that (as others pointed out) a C++ version may simplify and make things easier than Rust does with this example. this said with all due respect to C, C++ and Rust and having been introduced to C back in 1993, and to C++ in 1995, and then teaching some C++ again in 2003-2005 and then some basic programs written in Rust. code like this makes one really wonder whether Cxx14/17 programs are easier to get right/read than Rust, considering the massive amounts of template enums that inevitably finds way into every larger Rust program.
- apatheticonion 5y agoI agree. I like the ideas brought in by the borrow checker but I find Rust to be an unergonomic language to read. I would love to see something more like C, with a better type system, module import management, and a borrow checker. Think C + TypeScript
- vips7L 5y agoWhat about Zig? I think it checks everything off but a borrow checker.
- twic 5y ago> Rust version is much more difficult to read and comprehend than the C version By this, do you simply mean "I know C better than i know Rust"?
- larodi 5y agoPerhaps so, but then - also from the perspective of someone who easily reads/writes Perl, Python, ES6, complex PLSQL and templated C++. From my (personal) perspective something went in weird way with Rust borrower system, making it over-complex... although I have high regard for everyone who actually masters and uses it.
- 5y ago
- megous 5y agoRading through it... how do I know whether data.lock() is a spinlock or mutex, or whether it saves flags or not?
- db48x 5y agoFrom the type. `data` is a `&Ref<DeviceData>` which means it is a smart pointer to something of type `DeviceData`. `DeviceData` is defined in this module as `type DeviceData = device::Data<PL061Registrations, PL061Resources, IrqDisableSpinLock<PL061Data>>`. I would have to peek at `device::Data` to be sure, but my supposition is that the lock method acts on the `IrqDisableSpinLock` type. I assume that the name is meaningful, making this one a spinlock that disables irqs. This lets each driver using `device::Data` supply the most appropriate type of lock.
- megous 5y agoCan rust sanely encode constraints like "I don't want to be able to call functions that may call functions that sleep from atomic context" that would be checked at compile time?
- guntars 5y agoI don't think so. Once a function is compiled, it basically becomes a black box with a type signature so unless sleeping in a function affects its signature, that information is erased. If you pass in some kind of a sleep token that has to be used to sleep, then yeah I think you could enforce it by only being able to get that token in a non-atomic context and making it leak proof. The Cortex-M crate does something similar, but for proving that you are in an atomic context. Another function that expects a CriticalSection type is then assured that it's running without interrupts enabled. The type itself is zero sized so it's completely optimized away. https://github.com/rust-embedded/cortex-m/blob/master/src/interrupt.rs#L55 https://github.com/rust-embedded/cortex-m/blob/master/src/in...
- hvasilev 5y agoThe C version is so much cleaner.