[HN Gopher] Asynchronous Rust on Cortex-M Microcontrollers
       ___________________________________________________________________
        
       Asynchronous Rust on Cortex-M Microcontrollers
        
       Author : picture
       Score  : 115 points
       Date   : 2023-07-19 17:39 UTC (5 hours ago)
        
 (HTM) web link (interrupt.memfault.com)
 (TXT) w3m dump (interrupt.memfault.com)
        
       | chaxor wrote:
       | I believe that Tock (tockos.org) and Theseus
       | (https://github.com/theseus-os/Theseus) are in this area a bit as
       | well, just from an actual OS perspective.
       | 
       | I don't know much about this area, but it would be wonderful if
       | these could work with the Libre compute boards, like the AM Logic
       | S905X (Lepotato) or the Rock chip, since they're so much cheaper
       | than a Pi. Would be great to get a dedicated simple system up to
       | toy with Rust OS for only 30$.
        
       | jvanderbot wrote:
       | I could be wrong, but isn't every multi-task / RTOS essentially
       | supporting this? Wake up, check flags, do work if required.
       | 
       | Of course if you're bare-metal programming, it might be nice to
       | have a lightweight mechanism for this.
        
         | Conscat wrote:
         | If you're asking why do coroutines exist, it's because context
         | switching in kernel space and synchronization have very high
         | overhead by comparison. You use those features when you want
         | parallelism, not concurrency.
         | 
         | For parallelism, you also usually want a userspace task
         | scheduler because simply spawning threads in kernelspace is
         | much slower. Cilk and Go provide this built into their language
         | runtime.
        
           | kevin_thibedeau wrote:
           | Context switches on Cortex-M are relatively cheap with
           | optimizations to limit the amount of data that has to be
           | stacked.
        
             | lmm wrote:
             | Relative to what? They might be cheaper than some
             | architectures, but they're still a hell of a lot more
             | expensive than not doing them.
        
         | tadfisher wrote:
         | There's also the simplicity of bare embedded in Rust, because
         | you can mostly[1] use Cargo, depend on the right embedded_hal
         | crates for your target, and be up and running with a hardware-
         | backed async runtime and everything. Zephyr, Tock, Mynewt, etc.
         | all have bespoke build/deployment systems and driver
         | ecosystems. That said, RTOS makes actually deploying working
         | products much more realizable, so pick your poison.
         | 
         | 1: I think you still need xargo/cargo-xbuild for no-std
         | embedded sysroots, but there may have been some upstream
         | development on that front since I last played in this sandbox.
        
           | the__alchemist wrote:
           | Embedded-HAL is interesting to me - in principle it's a great
           | concept; writing hardware-agnostic, portable code.
           | 
           | I don't use it in my firmwares because:                 - All
           | of the embedded-HAL libs I've found (eg drivers) have had
           | notable limitations or ergonomics problems.       - It
           | appears unsuitable for use with DMA, which I use for all
           | runtime IO       - The APIs tend to be a mess due to heavy
           | use of typestates
        
           | junon wrote:
           | Building core/std/whatever using unstable flags and --target
           | works for me these days. I can do everything with cargo and
           | make it even more ergonomic with a toolchain file.
           | 
           | Now if Cargo had a way of producing multiple variants of the
           | same project (e.g. different --targets with pre-set features)
           | just with `cargo build` then things would be a lot better.
           | 
           | As of now, a Makefile is still needed to bring it all
           | together.
        
             | kibwen wrote:
             | We were using cargo-make for something like this, though
             | eventually we switched to using Cargo's experimental
             | support for artifact dependencies: https://doc.rust-
             | lang.org/nightly/cargo/reference/unstable.h...
        
           | monocasa wrote:
           | > I think you still need xargo/cargo-xbuild for no-std
           | embedded sysroots, but there may have been some upstream
           | development on that front since I last played in this
           | sandbox.
           | 
           | Cargo's build-std flag is the bespoke way now. Unstable, but
           | working it's way to stable. AFAIK cargo/build aren't getting
           | any more development in lieu of build-std.
        
             | vvanders wrote:
             | That's good to hear, I've used that in the past to drive
             | down codesize even on non embedded targets by compiling for
             | size and rebuilding the stdlib.
        
           | Archit3ch wrote:
           | > you can mostly[1] use Cargo, depend on the right
           | embedded_hal crates for your target, and be up and running
           | with a hardware-backed async runtime and everything
           | 
           | Can one use Rust's embedded_hal from a different programming
           | language?
        
             | sophacles wrote:
             | IIUC you can make some api in rust that uses the HAL, which
             | ties your code into board specific stuff.
             | 
             | That api you write can be exposed via C abi so you can link
             | C or languages that have an FFI to C to the rust code.
             | https://docs.rust-embedded.org/book/interoperability/rust-
             | wi...
        
         | azubinski wrote:
         | It's different and very old story.
         | 
         | Protothreads are classic now:
         | https://dunkels.com/adam/pt/index.html
         | 
         | Contiki OS is also a piece of embedded development classic:
         | https://en.wikipedia.org/wiki/Contiki
         | 
         | In general, it's more about syntax than semantics. Semantics is
         | generally difficult and rarely changes.
        
       | dmitrygr wrote:
       | So, cooperative multitasking with no control of actual
       | scheduling? Cute, but a real RTOS is the solution to most any "i
       | need to do more than one thing" in the embedded world.
        
       | amluto wrote:
       | spawner.spawn(blinky(led)).unwrap();
       | 
       | Has anyone seriously tried structured concurrency for single-
       | threaded async Rust? The pattern where main effectively leaks a
       | task seems kind of gross to me, and it's exactly what structured
       | concurrency tries to solve.
       | 
       | (I realize that Rust's async is inherently semi-structured.)
        
         | swsieber wrote:
         | IIRC been some proposals floating around, but one major block
         | is asncy drop. This is the article I'm thinking of
         | specifically: https://blog.yoshuawuyts.com/tree-structured-
         | concurrency/
        
       | AceJohnny2 wrote:
       | Edit: commented on wrong post, meant for
       | https://news.ycombinator.com/item?id=36791506
       | 
       | Offtopic:
       | 
       | The cursive italics are apparently a feature of the Victor Mono
       | [1] font used for the full page. While it'd be amusing in Tumblr
       | context (where cursive is used for hyperbolic emphasis), I can't
       | fathom why one would consider it in a code context.
       | 
       | You can change it (at least on Safari) by going into developer
       | tools, clicking any node, and removing "Victor Mono" from --font-
       | family
       | 
       | [1] https://rubjo.github.io/victor-mono/
        
         | kibwen wrote:
         | That may be specific to your machine, I don't see any italics
         | on mine, and the CSS just looks like `font-family: 'Source Code
         | Pro', monospace;`.
        
           | [deleted]
        
       | strangattractor wrote:
       | I new all that time I spent programming the Mac back in 1987
       | would come in handy. Asnyc programming - cooperative multitasking
       | never go out of style.
        
       | 0xfedbee wrote:
       | Cooperative multitasking in embedded has to be the worst idea
       | ever. Anyone who has done any serious work in embedded systems
       | will tell you how bad is it when a faulty task blocks everything.
       | Please stop advertising async in embedded.
        
         | fleventynine wrote:
         | It really comes down to memory requirements. If you can afford
         | to give every task it's own stack and dispatch it directly from
         | prioritized interrupts, that's great. Even better if you can
         | use memory protection hardware to isolate the tasks from each
         | other.
         | 
         | But if you have many long running tasks that only need to keep
         | a handful of bytes of state when they're waiting, a mechanism
         | like rust async can allow for huge memory savings while mostly
         | retaining the same code style as stackful tasks.
         | 
         | And you can even use both approaches in the same program, with
         | isolated stacks for realtime critical tasks and cooperative
         | state machines for everything else.
        
         | junon wrote:
         | ... anything faulty in an embedded world can block the chip.
         | 
         | This is why watchdog timers exist. Async does not shut off
         | watchdog. Not sure what your point is.
        
           | kevin_thibedeau wrote:
           | Watchdogs shouldn't be triggered in the normal course of
           | events. They are a last line of defense for exceptional
           | circumstances that will render a device inoperative, not a
           | cure-all for poor system design.
        
             | rcxdude wrote:
             | Sure, but a system where a task has ceased to execute is in
             | pretty much all circumstances a system where the watchdog
             | (or some other assert) should trigger. (the whole toyota
             | case involved them getting raked over the coals because
             | their system did not in fact detect a failed task and do
             | this).
        
           | jacquesm wrote:
           | Think of a watchdog timer in the same way you think about
           | that big red handle on a train, 'emergency use only, abuse
           | will be punished'.
        
             | RealityVoid wrote:
             | You have a task. You do an infinite loop in the task. The
             | task is now blocking lower prio tasks in the RTOS. If the
             | task is broken, it's broken.
        
         | blackbeans wrote:
         | Actually I never found cooperative multitasking a real issue.
         | You don't want faulty tasks in your embedded application
         | anyway. With cooperative multitasking it at least is easy to
         | spot which task is the culprit.
         | 
         | If I understand the article correctly, it seems that in this
         | case the multitasking also isn't controlled by calling yield in
         | custom user code, but rather always from the implementation
         | that makes async/await work.
        
           | jacquesm wrote:
           | > You don't want faulty tasks in your embedded application
           | anyway.
           | 
           | Faults are a way of life in software, they are unavoidable
           | because each and every piece of software rests on a bunch of
           | assumptions and if any one of those or a combination of them
           | do not hold then you have a fault. Failure to envision that
           | fault and a way to deal with it in embedded systems can cause
           | damage to property, injury and loss of life.
           | 
           | None of this is simple, not in theory and definitely not in
           | practice.
        
             | rcxdude wrote:
             | Sure, but an RTOS doesn't help you much with safety
             | critical guarantees. When you're worrying about that you're
             | also worrying about redundancy of the hardware your
             | software is running on, for example. The only thing that an
             | RTOS is really helpful for is making it a bit easier to
             | argue that some realtime guarantees can be met even if
             | other code running on the device may have worst-case
             | runtimes longer than your deadline (this doesn't really
             | become a mitigation for a truly defective task, though,
             | since at that point all bets are off unless you have some
             | good task isolation). But this is not all embedded systems,
             | not even all safety-critical ones, and there are other
             | solutions available if you are not using an RTOS which can
             | give you the same or better options in that case(like
             | placing the critical code in a high-priority interrupt
             | handler, for example).
        
       | the__alchemist wrote:
       | Most of my programming these days is on Cortex-M, using Rust. The
       | article is another prior in my suspicion that Async embedded Rust
       | is designed with toy use cases in mind (blinky); I don't know how
       | it would work in practical firemware because I haven't seen
       | examples.
       | 
       | Another possibility is that my brain and learning patterns don't
       | work well with Async. I have a hard time understanding it, and it
       | feels like a layer of obfuscation or misdirection. It's an
       | abstraction that may be more suitable for other learning styles,
       | or different program structures or requirements than the ones
       | I've worked on.
       | 
       | I'd love to see an RTOS for Rust, but am worried it will be
       | Async.
       | 
       | (I am referring to the Async/Await pattern; not asynchronous
       | programming or concurrency)
        
         | steveklabnik wrote:
         | At work, we have a non-async RTOS
         | https://hubris.oxide.computer/ though it is a bit harder to use
         | than "add a dependency in Cargo.toml" like most projects.
         | 
         | The original designer of Hubris, Cliff, has his own async RTOS
         | that he uses for personal projects. He recently has been
         | writing some blog posts on this that may be of interest to you:
         | 
         | * http://cliffle.com/blog/async-inversion/
         | 
         | * http://cliffle.com/blog/composed-concurrency-in-drivers/
         | 
         | The former is a general introduction to async, and the second
         | is about applying it to write an I2C driver.
         | 
         | There already are some general RTOSes for Rust, and they do
         | tend to use async:
         | 
         | * https://rtic.rs/2/book/en/
         | 
         | * https://github.com/embassy-rs/embassy (this one is used in
         | the article)
         | 
         | * https://tockos.org/
        
           | the__alchemist wrote:
           | Cliff's articles are outstanding!
           | 
           | Re Rtic: I'm using the V1 of it, which is non-Async; it seems
           | to offer smarter locking than using critical sections +
           | Mutex, since it's capable of interrupting a lower-priority
           | task with a higher one, depending on which resources are
           | locked.
        
         | KingLancelot wrote:
         | [dead]
        
         | sebastiandb wrote:
         | This article about async in Python helped me understand it
         | pretty well, since it explains them in terms of coroutines,
         | which are very intuitive for me: https://mleue.com/posts/yield-
         | to-async-await/
         | 
         | Another thing that helps me get it is comparing it to the
         | continuation passing style, where you never return from a
         | function, you just take an argument that's basically a function
         | pointer bound to an environment, and at the end of the
         | function, instead of returning, you call your input function,
         | giving it another function and environment as input, repeating
         | the cycle. It's very similar to the transformation of callbacks
         | within callbacks within callbacks pattern in JavaScript to the
         | async/await pattern.
        
           | jayd16 wrote:
           | Once you realize async/await is just sugar over the familiar
           | callback hell, a lot of the mystery fades away and it's
           | easier to groc.
        
       ___________________________________________________________________
       (page generated 2023-07-19 23:00 UTC)