cortex-m icon indicating copy to clipboard operation
cortex-m copied to clipboard

Porcelain and Plumbing

Open thejpster opened this issue 5 years ago • 5 comments
trafficstars

See: https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain

Perhaps cortex-m and cortex-m-rt could be split into two pieces in a different way. We would have one crate which is the plumbing for Cortex-M - it would handle the fundamentals of booting a Cortex-M core, interrupts, etc. Everyone, no matter what they were doing, would use this crate if they were on a Cortex-M. Then we have some porcelain, which is the nice user-facing bit. We might have several of those (for example, you could consider RTFM as some porcelain, or some RTOS). In a ideal world, the porcelain would fit with lots of different sorts of plumbing (risc-v-rt, msp430-rtc, etc), but sometimes a platform is just too weird for that to work.

We would perhaps need some traits (or some other API) to allow porcelain to access the plumbing, but it can be gritty and internal as users won't normally be exposed to it - they get exposed to the nice wipe-clean porcelain.

Example crates (some are fictional, for now)

Plumbing:

  • cortex-m-plumbing
  • risc-v-plumbing
  • msp430-plumbing
  • nrf9160-pac

Porcelain

  • arduino-loop (some new crate I just made up - imagine it does a basic setup()/loop() thing like Arduino)
  • freertos-rs (although being a C based RTOS it will have its own internal mechanism for handling types of CPU, imagine this is a pure-Rust equivalent to FreeRTOS)
  • rtfm
  • nrf52-hal

thejpster avatar Mar 03 '20 19:03 thejpster

I like this idea but I worry it couldn't work quite the same way git does, because we're not only composing high-level operations made out of simple low-level constructs. Instead, our higher level (porcelain) crates generally provide additional functionality, but the low-level (plumbing) functionality is often still required as-is, directly by end applications. I guess basically "user-facing" is a hard line to draw when everything we're providing is a library; it's sort of all "user-facing".

For example, even using a frontend like RTFM which provides its own interrupt management, I still want to call cortex_m::sys_reset() from my own application, and I wouldn't expect RTFM to wrap all such methods (not least since they may vary per-platform). A very complete frontend that was essentially a full RTOS might totally wrap everything you could need, while a very simple frontend like the arduino-loop idea probably doesn't want to wrap much of anything.

Nevertheless I think splitting things up into more backend and more frontend crates could be useful, I'm just not sure exactly where to draw the line or how flexible to make it. Some things seem fairly easy -- like cortex_m_asm to provide access to the asm functions without bringing in all the peripheral stuff, or splitting the cortex_m PAC/HAL levels -- but I'd want to be happy we were getting something worth the complexity of making even more crates.

adamgreig avatar Mar 03 '20 20:03 adamgreig

Further, to avoid the kind of "oh no! maybe this isn't perfect?" kind of paralysis that can affect open-source projects with lots of members, here's a possible route to stabilisation of this idea that doesn't turn the world upside down and discourage adoption (I'm looking at you, KDE 4).

  1. Think about what people want from their porcelain.
  2. Think about that kind of plumbing best exposes the underlying features of our chosen architectures (MSP430, Cortex-M, Cortex-R and RISC-V at the moment, I think).
  3. Write a new porcelain crate for people who aren't using an RTOS or RTFM or anything like that. It looks nice on top, but it's ugly as hell underneath.
  4. Slowly iterate the back-end of the porcelain, moving stuff into cortex-m-rt.

This is broadly equivalent to a roadmap for fixing the peripheral side of Cortex-M:

  1. Think about the Cortex-M HAL.
  2. Think about the Cortex-M PAC.
  3. Write a Cortex-M HAL that's ugly inside but works and is nice to use.
  4. Stitch in a Cortex-M PAC underneath (and hopefully no-one using the HAL will notice as you do it)
  5. Deprecate/abandon/delete the cortex-m crate.

I'll also note some important prior art, because we aren't doing these things in a vacuum:

  • FreeRTOS works on a bunch of CPU architectures, and handles interrupts, mutual-exclusion, critical sections, etc.
  • TockOS I think supports several architectures too?
  • Arm's CMSIS will have some good ideas on how to abstract a Cortex-M, although obviously in a vendor specific way.
  • Linux is another example of abstracting CPU features across a variety of architectures.

thejpster avatar Mar 03 '20 20:03 thejpster

want to call cortex_m::sys_reset() from my own application,

Right, but isn't that just rtfm::reset_cpu(), or freertos::mcu_reset() or somesuch?

thejpster avatar Mar 03 '20 20:03 thejpster

want to call cortex_m::sys_reset() from my own application,

Right, but isn't that just rtfm::reset_cpu(), or freertos::mcu_reset() or somesuch?

It could be, but then does every frontend have to fully implement all its own methods for every backend function? Getting away from that sort of all-in-one thing is an advantage to having easy crates I think, unlike in C where the overhead of extra dependencies encourages monolithic RTOSs. There's no reason all the cortex-m specific functionality can't be exposed by a cortex-m crate that anyone can use, while RTFM can focus on just providing the interrupt-priority-based scheduling, and your HAL can focus on providing peripheral access, etc.

adamgreig avatar Mar 03 '20 20:03 adamgreig

I guess it depends what value you place on portability for an application. If you accept that an embedded application is targeted at and only runs on one particular model of one particular SoC, then sure, you can depend on some SoC specific porcelain. And actually, as I write that out, I'm starting to convince myself that that is generally true...

OK, so what does any embedded application do:

  • Provide a reset vector for the CPU
  • Provide other Interrupt vectors, either statically, or dynamically at run-time (with a vector table in RAM, or through a fixed redirection function)
  • Initialise the stack pointer, init .data, and zero .bss
  • Configure the peripherals - clocks, UART, SPI, timers, etc
  • Run the rest of the program meme
    • Rattle between a bunch of threads, sleeping when there's nothing to do or
    • Rattle around an event loop, sleeping when there's nothing to do

We should make it easy to do that, for a specific platform, but for any platform.

thejpster avatar Mar 03 '20 20:03 thejpster