-
Notifications
You must be signed in to change notification settings - Fork 151
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
Comments
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. |
Briefly discussed this in todays meeting. We might come back to this after the next release. Unnominating for now. |
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. |
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. |
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. :-) |
I created a for-discussion PR #354 as another alternative to this problem which might be worth also considering. |
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
However, in the split-crate situation I described initially, I could totally imagine having a |
@cbiffle I agree with all of that. |
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]>
Summary
The
cortex-m
crate provides facilities for using peripherals and specialinstructions 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 callmonolithic 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 isolatea 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 inunprivileged mode -- because the library assumes a particular method of
implementing critical sections.
A taxonomy of
cortex-m
APIsI've been thinking about this a lot recently, while using
cortex-m
in both thekernel 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:
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.)
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:
wfi
/wfe
, probablysev
bkpt
delay
, were it correct (seecortex_m::asm::delay
is wrong on anything more complex than an M4 #236)nop
udf
isb
/dsb
/dmb
apsr
,lr
,pc
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.
RegisterBlock
.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 particularconcurrency model, namely
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 incortex-m
devoted to controlling peripheral access and aliasing. This is useful stuff, but
it only makes sense in monolithic programs, for two reasons:
It relies on the type system for safety. In general, guarantees from the type
system cannot be extended to separate programs.
It relies on
static
flags for mutual exclusion. In a system containingmultiple 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-ModelDependent Bits, which provide abstractions over the actual hardware (i.e.
take()
is not a hardware operation) and only make sense in monolithicprograms. 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 ifyou'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; themonohal
above it can provide safe wrappers. This crate might get pulled intovery strange unprivileged programs that want knowledge of register layouts.
cortex-m-intrinsics
- all the Universally Relevant bits for accessingWFI
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 ofraw
andintrinsics
. Anapplication could use
monohal
and reach intoraw
for particular things, butdoing so would potentially violate
monohal
's safety guarantees -- though theraw
bits are likelyunsafe
so that's not surprising.Really, I feel like the
monohal
bits above belong in thecortex-m-rt
crate,along with things like
#[exception]
and other niceties for writing safe codethat make assumptions about system architecture.
NAME
vsname::RegisterBlock
Currently, if you're working in a context where
take()
doesn't make sense, youwind up dealing with types named
RegisterBlock
a lot. This is kind ofunfortunate, because it's verbose -- you need to always partially qualify the
types (
gpioa::RegisterBlock
,scb::RegisterBlock
) to have any idea what'sgoing on. However, the conveniently named types in the monohal (e.g.
SCB
) donot 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 thatseems 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 areunavailable to people not using the monohal. (For instance, having a
&scb::RegisterBlock
is enough for me to enable the instruction cache, but onlyif 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 canoperate on the
RegisterBlock
; and a safer counterpart, using critical sectionsand 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 thiscase. :-)
The text was updated successfully, but these errors were encountered: