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

Proposal for separating cortex-m into smaller parts #239

Open
cbiffle opened this issue Jul 6, 2020 · 8 comments
Open

Proposal for separating cortex-m into smaller parts #239

cbiffle opened this issue Jul 6, 2020 · 8 comments

Comments

@cbiffle
Copy link
Contributor

cbiffle commented Jul 6, 2020

Summary

The cortex-m crate provides facilities for using peripherals and special
instructions on the ARM Cortex-M series. This makes it useful in a wide range of
embedded applications. However, the crate also does a few other things that
restrict its applicability. I would like to propose finding a seam in the crate,
between its low-level generally-applicable bits and its higher-level bits, and
separating it into two.

Context

Most Rust applications using the cortex-m crate are what I would call
monolithic applications: they consist of a single program, compiled/linked
together, and do not use memory protection to isolate components, nor the
processor's privileged/unprivileged distinction to isolate a kernel.

There are (for the sake of this discussion) three other kinds of Cortex-M
applications, however: those that use limited memory protection and privilege
within a single linked application (FreeRTOS CM3_MPU port); those that isolate
a kernel and run drivers in privileged mode (Tock; uCLinux and programs running
atop it); and those that use memory protection to run drivers in unprivileged
mode (so-called "multiserver" systems). The current APIs aren't well suited for
any of these applications.

As an example, consider issue #223. The memory safety properties of the current
cortex-m API rely on critical sections, and they turn out to be void in
unprivileged mode -- because the library assumes a particular method of
implementing critical sections.

A taxonomy of cortex-m APIs

I've been thinking about this a lot recently, while using cortex-m in both the
kernel and userland of an isolated multiserver operating system (not yet
released). I see the following broad groups of functionality. Each of these is
probably not a separate crate, to be clear, these are merely categories.

The Universally Relevant

Things that make sense to use in a kernel, in userland, with or without
protection, etc. Stuff in this category should include:

  1. Operations that are reasonable in both privileged and unprivileged mode.
    (Note: operations that reliably trap in unprivileged mode are OK. MSR, MRS,
    and CPS do not trap and should be exposed only very carefully.)

  2. Operations that do not assume a single linked program (e.g. they must not use
    a static to coordinate access to a shared resource).

This list may be really short; even with MPU shenanigans, you cannot make the
peripherals on the Private Peripheral Bus (which is to say, most of those
defined in cortex-m) accessible to unprivileged code.

At first glance, here are some things that belong in this level:

Peripheral Register Definitions

The address and layout of memory-mapped peripherals. This information isn't
dangerous to expose to unprivileged code, because direct accesses to PPB
peripherals from unprivileged code won't work. However, a system might
reasonably opt to intercept the MPU or Bus faults and emulate the peripheral.

  • All peripheral layouts currently named RegisterBlock.
  • All register access types (W/R proxies and friends if you're using svd2rust).

Handy Algorithms

Pre-built code that implements the "right way" to do certain operations, such as
enabling/disabling the caches or adjusting system handler priority.

Ideally, these would be as widely applicable as possible, so that people don't
keep reinventing the wheel. (Particularly if the wheel is reinvented
incorrectly.) Currently, they contain Concurrency-Model Dependent Bits (below)
in some cases, and are tied to Opinionated Peripheral Access in others (farther
below).

Concurrency-Model Dependent Bits

interrupt::free, CriticalSection, etc. These operations assume a particular
concurrency model, namely

  • All code is privileged.
  • Critical sections are imposed by stopping all interrupts.

These assumptions aren't general; the first fails on Tock (userland) or uCLinux,
and the second fails on many real-time systems, which tend to have a few
interrupt priority levels reserved as "never disable" (with attendant safety
restrictions).

Opinionated Peripheral Access

take() and friends. There's a fair amount of surface area in cortex-m
devoted to controlling peripheral access and aliasing. This is useful stuff, but
it only makes sense in monolithic programs, for two reasons:

  1. It relies on the type system for safety. In general, guarantees from the type
    system cannot be extended to separate programs.

  2. It relies on static flags for mutual exclusion. In a system containing
    multiple separately compiled programs, every program has a taken flag, and
    the guarantees rapidly fall apart.

Proposed crate seam

The split that makes the most sense to me has three parts. We can debate names
later; here are placeholder names for the sake of discussion. I'm starting at the
current level of abstraction and working down.

  • cortex-m-monohal - the Opinionated Peripheral Access and Concurrency-Model
    Dependent Bits, which provide abstractions over the actual hardware (i.e.
    take() is not a hardware operation) and only make sense in monolithic
    programs. This API would be fairly safe (equivalent to the current cortex-m
    surface area).

  • cortex-m-raw - the lower level systemsy bits that are mostly needed if
    you're writing a kernel or driver: Register Definitions and Handy Algorithms.
    This layer cannot make assumptions about privilege or concurrency model for
    correctness. As a result, it's going to have a lot of unsafe API; the
    monohal above it can provide safe wrappers. This crate might get pulled into
    very strange unprivileged programs that want knowledge of register layouts.

  • cortex-m-intrinsics - all the Universally Relevant bits for accessing WFI
    and the like. This would be as useful in a userland program on uCLinux as it
    would be in a kernel. This API would be mostly or entirely safe. (It's
    possible that some of the Handy Algorithms wind up here.)

The monohal parts would be implemented in terms of raw and intrinsics. An
application could use monohal and reach into raw for particular things, but
doing so would potentially violate monohal's safety guarantees -- though the
raw bits are likely unsafe so that's not surprising.

Really, I feel like the monohal bits above belong in the cortex-m-rt crate,
along with things like #[exception] and other niceties for writing safe code
that make assumptions about system architecture.

NAME vs name::RegisterBlock

Currently, if you're working in a context where take() doesn't make sense, you
wind up dealing with types named RegisterBlock a lot. This is kind of
unfortunate, because it's verbose -- you need to always partially qualify the
types (gpioa::RegisterBlock, scb::RegisterBlock) to have any idea what's
going on. However, the conveniently named types in the monohal (e.g. SCB) do
not have new() operations, even unsafe ones, for getting an instance. (Yes,
you could do a steal() of the entire peripheral set and chop it down, but that
seems odd.)

This is also an issue because some Handy Algorithms are provided on the NAME
types, and others on the name::RegisterBlock types -- and the former ones are
unavailable to people not using the monohal. (For instance, having a
&scb::RegisterBlock is enough for me to enable the instruction cache, but only
if I'm willing to write the code myself -- the canned algorithm is on SCB.)

I bring this up because, in the split I'm proposing, the Handy Algorithms would
need to get split up in many cases: a reusable core, likely unsafe, that can
operate on the RegisterBlock; and a safer counterpart, using critical sections
and the like, in the monohal.

I would also like to register a vote for distinct names, instead of having a
whole bunch of types named RegisterBlock. ScbRegisters would do in this
case. :-)

@therealprof therealprof added the nominated Issue nominated as discussion topic for the Embedded WG meeting label Jul 20, 2020
@therealprof
Copy link
Contributor

Discussion from todays meeting: Probably best to toy around with the idea and see how that would work and if people are motivated to see it through. Also if people are interested in working on this, please speak up.

@therealprof
Copy link
Contributor

Briefly discussed this in todays meeting. We might come back to this after the next release.

Unnominating for now.

@therealprof therealprof removed the nominated Issue nominated as discussion topic for the Embedded WG meeting label Mar 16, 2021
@hydra
Copy link

hydra commented Sep 20, 2021

I would fully support extracting the register definitions into a separate crate. Register definitions are pretty much fixed by the hardware and the only reason to change them is due to bugs or a new cpu revision (and corresponding SVD from the vendor). Everything else in the cortex-m crate is essentially open to change for any number of reasons.

When writing very low level code, or when implementing solutions that have very specific requirements (e.g. vector tables, linker scripts, debugging requirements), much of the other stuff that the crate provides either causes problems (even when unused, as compile times are still increased), gets in the way and can cause developer confusion as to which part of the crate can/should be used, or needs working around.

If your first cut at splitting things up a bit was to just extract the register access into a separate crate and make it a dependency, whilst also allowing it to be used directly, that would be awesome.

@Quick-Flash
Copy link

Discussion from todays meeting: Probably best to toy around with the idea and see how that would work and if people are motivated to see it through. Also if people are interested in working on this, please speak up.

I've just started using rust for embedded and this is something I'm definitely interested in. The current implementation is monolithic and includes much that is not needed or wanted in my project. For many applications it just includes to much extra code, while for many just the registers and other low level code is what they need.

@cbiffle
Copy link
Contributor Author

cbiffle commented Sep 21, 2021

Discussion from todays meeting: Probably best to toy around with the idea and see how that would work and if people are motivated to see it through. Also if people are interested in working on this, please speak up.

I'd be interested in cooking up a prototype if there's interest from upstream. We've been using cortex-m in unprivileged isolated environments for a little over a year now, and just avoiding touching the parts that are unsound there. :-)

@hydra
Copy link

hydra commented Sep 21, 2021

I created a for-discussion PR #354 as another alternative to this problem which might be worth also considering.

@cbiffle
Copy link
Contributor Author

cbiffle commented Sep 21, 2021

I may have been unclear -- my initial concerns are not that cortex-m is doing too much, but rather that portions of cortex-m make assumptions about environment and processor state that are incorrect in some cases. For instance, the use of cpsid/cpsie around critical sections is invalid if you're targeting the processor's unprivileged mode, meaning large portions of cortex-m are currently unsound when run unprivileged -- it doesn't look like that PR does anything to address that. My suggestion to divide the crate along functional lines is intended to give people finer-grained tools that they can use to draw these platform distinctions as needed.

FWIW, in this case, I prefer finer-grained crates to larger crates with Cargo features because

  1. Cargo features complicate the code they control (with cfg attributes and alternate code paths).
  2. They restrict users to swapping between the options the crate maintainer considered, whereas finer-grained crates would let us swap in a different implementation of something.

However, in the split-crate situation I described initially, I could totally imagine having a cortex-m facade crate with features for controlling which lower-level crates it pulled in. If the user needed something more complex than that, they'd need to directly depend on the implementation crates, including potentially ones they'd written in-house.

@hydra
Copy link

hydra commented Sep 29, 2021

@cbiffle I agree with all of that.

adamgreig pushed a commit that referenced this issue Jan 12, 2022
239: Make `ExceptionFrame`s fields private r=korken89 a=jonas-schievink

Closes #234

~~I can also add the `unsafe` setters, but they don't have any use right now.~~ (added them in order to not regress available operations on `ExceptionFrame`)

Co-authored-by: Jonas Schievink <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants